From df361328a2ad6551434e26d8d6b29c03bf159bca Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 10 Dec 2018 15:40:51 -0800 Subject: [PATCH 001/844] Release version 4.0.0 beta 1. --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .gitignore | 6 + .travis.yml | 85 +- CHANGELOG.md | 233 -- CMakeLists.txt | 79 + CODE_OF_CONDUCT.md | 4 - CONTRIBUTING.md | 6 +- CppUTestMakefileWorker.mk | 575 ---- LICENSE | 21 + LICENSE.txt | 120 - Makefile | 131 - NOTICE.txt | 16 - PortingGuide.md | 150 -- README.md | 351 +-- certs/README.txt | 4 - demos/aws_iot_demo.h | 69 + demos/aws_iot_demo_config.h | 58 + demos/aws_iot_demo_mqtt.c | 663 +++++ demos/aws_iot_demo_shadow.c | 133 + demos/posix/CMakeLists.txt | 31 + demos/posix/aws_iot_demo_common_posix.c | 234 ++ demos/posix/aws_iot_demo_mqtt_posix.c | 175 ++ demos/posix/aws_iot_demo_posix.h | 94 + demos/posix/aws_iot_demo_shadow_posix.c | 185 ++ doc/config/common | 111 + doc/config/html/footer.html | 35 + doc/config/html/header.html | 88 + doc/config/html/style.css | 132 + doc/config/layout_library.xml | 197 ++ doc/config/layout_main.xml | 204 ++ doc/config/logging | 26 + doc/config/main | 35 + doc/config/mqtt | 34 + doc/config/platform | 29 + doc/config/queue | 25 + doc/config/shadow | 28 + doc/guide/building.txt | 124 + doc/guide/developer.txt | 8 + doc/guide/style.txt | 282 ++ doc/lib/logging.txt | 118 + doc/lib/mqtt.txt | 369 +++ doc/lib/platform.txt | 348 +++ doc/lib/queue.txt | 63 + doc/lib/shadow.txt | 65 + doc/mainpage.txt | 194 ++ doc/plantuml/images/mqtt_demo.png | Bin 0 -> 50307 bytes .../images/mqtt_design_typicaloperation.png | Bin 0 -> 109457 bytes ...qtt_function_receivecallback_nopartial.png | Bin 0 -> 42752 bytes .../mqtt_function_receivecallback_partial.png | Bin 0 -> 59817 bytes doc/plantuml/mqtt_demo.pu | 38 + doc/plantuml/mqtt_design_typicaloperation.pu | 71 + ...mqtt_function_receivecallback_nopartial.pu | 38 + .../mqtt_function_receivecallback_partial.pu | 40 + external_libs/CppUTest/README.txt | 2 - external_libs/jsmn/jsmn.c | 344 --- external_libs/jsmn/jsmn.h | 106 - external_libs/mbedTLS/README.txt | 5 - filterGcov.sh | 63 - include/aws_iot_error.h | 166 -- include/aws_iot_jobs_interface.h | 267 -- include/aws_iot_jobs_json.h | 108 - include/aws_iot_jobs_topics.h | 88 - include/aws_iot_jobs_types.h | 109 - include/aws_iot_json_utils.h | 212 -- include/aws_iot_log.h | 131 - include/aws_iot_mqtt_client.h | 418 --- include/aws_iot_mqtt_client_common_internal.h | 134 - include/aws_iot_mqtt_client_interface.h | 213 -- include/aws_iot_shadow_actions.h | 33 - include/aws_iot_shadow_interface.h | 305 --- include/aws_iot_shadow_json.h | 51 - include/aws_iot_shadow_json_data.h | 150 -- include/aws_iot_shadow_key.h | 22 - include/aws_iot_shadow_records.h | 55 - include/aws_iot_version.h | 48 - include/network_interface.h | 167 -- include/threads_interface.h | 108 - include/timer_interface.h | 105 - lib/config/aws_iot_config_build.h.in | 51 + lib/include/aws_iot_json_utils.h | 41 + lib/include/aws_iot_logging_setup.h | 221 ++ lib/include/aws_iot_mqtt.h | 1804 +++++++++++++ lib/include/aws_iot_queue.h | 498 ++++ lib/include/aws_iot_shadow.h | 766 ++++++ lib/include/platform/aws_iot_clock.h | 219 ++ lib/include/platform/aws_iot_network.h | 411 +++ lib/include/platform/aws_iot_static_memory.h | 346 +++ lib/include/platform/aws_iot_threads.h | 387 +++ lib/include/private/aws_iot_logging.h | 227 ++ lib/include/private/aws_iot_mqtt_internal.h | 911 +++++++ lib/include/private/aws_iot_shadow_internal.h | 676 +++++ lib/source/common/CMakeLists.txt | 24 + lib/source/common/aws_iot_json_utils.c | 258 ++ lib/source/common/aws_iot_logging.c | 470 ++++ lib/source/common/aws_iot_queue.c | 620 +++++ lib/source/mqtt/CMakeLists.txt | 26 + lib/source/mqtt/aws_iot_mqtt_api.c | 2318 +++++++++++++++++ lib/source/mqtt/aws_iot_mqtt_operation.c | 693 +++++ lib/source/mqtt/aws_iot_mqtt_serialize.c | 1933 ++++++++++++++ lib/source/mqtt/aws_iot_mqtt_subscription.c | 519 ++++ lib/source/mqtt/aws_iot_mqtt_validate.c | 430 +++ lib/source/platform/posix/CMakeLists.txt | 106 + .../platform/posix/aws_iot_clock_posix.c | 351 +++ .../platform/posix/aws_iot_threads_posix.c | 436 ++++ .../posix/network/aws_iot_network_openssl.c | 1061 ++++++++ .../aws_iot_static_memory_common_posix.c | 235 ++ .../aws_iot_static_memory_mqtt_posix.c | 264 ++ .../aws_iot_static_memory_shadow_posix.c | 182 ++ lib/source/shadow/CMakeLists.txt | 25 + lib/source/shadow/aws_iot_shadow_api.c | 1111 ++++++++ lib/source/shadow/aws_iot_shadow_operation.c | 870 +++++++ lib/source/shadow/aws_iot_shadow_parser.c | 295 +++ .../shadow/aws_iot_shadow_subscription.c | 663 +++++ platform/linux/common/timer.c | 82 - platform/linux/common/timer_platform.h | 48 - .../linux/mbedtls/network_mbedtls_wrapper.c | 383 --- platform/linux/mbedtls/network_platform.h | 59 - platform/linux/pthread/threads_platform.h | 43 - .../linux/pthread/threads_pthread_wrapper.c | 112 - samples/README.md | 42 - samples/linux/jobs_sample/Makefile | 70 - samples/linux/jobs_sample/aws_iot_config.h | 73 - samples/linux/jobs_sample/jobs_sample.c | 370 --- samples/linux/shadow_sample/Makefile | 71 - samples/linux/shadow_sample/aws_iot_config.h | 58 - samples/linux/shadow_sample/shadow_sample.c | 274 -- .../linux/shadow_sample_console_echo/Makefile | 74 - .../aws_iot_config.h | 58 - .../shadow_console_echo.c | 271 -- .../subscribe_publish_library_sample/Makefile | 75 - .../aws_iot_config.h | 58 - .../subscribe_publish_library_sample.c | 264 -- .../linux/subscribe_publish_sample/Makefile | 69 - .../subscribe_publish_sample/aws_iot_config.h | 58 - .../subscribe_publish_sample.c | 270 -- scripts/build_check_commit.sh | 47 + scripts/build_check_pr.sh | 35 + scripts/coverage.sh | 34 + scripts/generate_doc.sh | 61 + src/aws_iot_jobs_interface.c | 215 -- src/aws_iot_jobs_json.c | 197 -- src/aws_iot_jobs_topics.c | 129 - src/aws_iot_jobs_types.c | 73 - src/aws_iot_json_utils.c | 227 -- src/aws_iot_mqtt_client.c | 369 --- src/aws_iot_mqtt_client_common_internal.c | 720 ----- src/aws_iot_mqtt_client_connect.c | 627 ----- src/aws_iot_mqtt_client_publish.c | 428 --- src/aws_iot_mqtt_client_subscribe.c | 449 ---- src/aws_iot_mqtt_client_unsubscribe.c | 250 -- src/aws_iot_mqtt_client_yield.c | 311 --- src/aws_iot_shadow.c | 250 -- src/aws_iot_shadow_actions.c | 80 - src/aws_iot_shadow_json.c | 559 ---- src/aws_iot_shadow_records.c | 529 ---- tests/CMakeLists.txt | 10 + tests/README.md | 9 - tests/aws_iot_tests_config.h | 118 + tests/aws_iot_tests_network.c | 252 ++ tests/integration/Makefile | 107 - tests/integration/README.md | 44 - tests/integration/include/aws_iot_config.h | 68 - .../include/aws_iot_integ_tests_config.h | 46 - .../include/aws_iot_test_integration_common.h | 43 - .../aws_iot_test_multithreading_validation.c | 362 --- .../src/aws_iot_test_auto_reconnect.c | 236 -- .../src/aws_iot_test_basic_connectivity.c | 281 -- .../src/aws_iot_test_integration_runner.c | 91 - tests/integration/src/aws_iot_test_jobs_api.c | 284 -- .../src/aws_iot_test_multiple_clients.c | 286 -- tests/mqtt/CMakeLists.txt | 13 + tests/mqtt/access/aws_iot_test_access_mqtt.h | 106 + .../access/aws_iot_test_access_mqtt_api.c | 47 + .../aws_iot_test_access_mqtt_serialize.c | 40 + .../aws_iot_test_access_mqtt_subscription.c | 47 + tests/mqtt/aws_iot_tests_mqtt.c | 106 + tests/mqtt/system/aws_iot_tests_mqtt_stress.c | 639 +++++ tests/mqtt/system/aws_iot_tests_mqtt_system.c | 856 ++++++ tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 961 +++++++ tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 1723 ++++++++++++ .../unit/aws_iot_tests_mqtt_subscription.c | 907 +++++++ tests/mqtt/unit/aws_iot_tests_mqtt_validate.c | 440 ++++ tests/shadow/CMakeLists.txt | 10 + tests/shadow/aws_iot_tests_shadow.c | 109 + .../system/aws_iot_tests_shadow_system.c | 714 +++++ tests/shadow/unit/aws_iot_tests_shadow_api.c | 731 ++++++ .../shadow/unit/aws_iot_tests_shadow_parser.c | 473 ++++ tests/unit/README.md | 12 - tests/unit/include/aws_iot_config.h | 73 - .../aws_iot_tests_unit_helper_functions.h | 100 - .../aws_iot_tests_unit_shadow_helper.h | 43 - .../src/aws_iot_tests_unit_common_tests.cpp | 34 - .../aws_iot_tests_unit_common_tests_helper.c | 186 -- tests/unit/src/aws_iot_tests_unit_connect.cpp | 84 - .../src/aws_iot_tests_unit_connect_helper.c | 720 ----- .../src/aws_iot_tests_unit_disconnect.cpp | 42 - .../aws_iot_tests_unit_disconnect_helper.c | 244 -- .../src/aws_iot_tests_unit_helper_functions.c | 563 ---- tests/unit/src/aws_iot_tests_unit_jobs.cpp | 60 - .../src/aws_iot_tests_unit_jobs_interface.c | 337 --- tests/unit/src/aws_iot_tests_unit_jobs_json.c | 244 -- .../unit/src/aws_iot_tests_unit_jobs_topics.c | 161 -- .../unit/src/aws_iot_tests_unit_jobs_types.c | 70 - .../src/aws_iot_tests_unit_json_utils.cpp | 91 - .../aws_iot_tests_unit_json_utils_helper.c | 834 ------ tests/unit/src/aws_iot_tests_unit_publish.cpp | 48 - .../src/aws_iot_tests_unit_publish_helper.c | 205 -- tests/unit/src/aws_iot_tests_unit_runner.cpp | 25 - .../src/aws_iot_tests_unit_shadow_action.cpp | 51 - .../aws_iot_tests_unit_shadow_action_helper.c | 944 ------- .../src/aws_iot_tests_unit_shadow_delta.cpp | 33 - .../aws_iot_tests_unit_shadow_delta_helper.c | 324 --- ...aws_iot_tests_unit_shadow_json_builder.cpp | 31 - ...ot_tests_unit_shadow_json_builder_helper.c | 131 - .../aws_iot_tests_unit_shadow_null_fields.cpp | 40 - ...iot_tests_unit_shadow_null_fields_helper.c | 151 -- .../unit/src/aws_iot_tests_unit_subscribe.cpp | 79 - .../src/aws_iot_tests_unit_subscribe_helper.c | 606 ----- .../src/aws_iot_tests_unit_unsubscribe.cpp | 55 - .../aws_iot_tests_unit_unsubscribe_helper.c | 343 --- tests/unit/src/aws_iot_tests_unit_yield.cpp | 54 - .../src/aws_iot_tests_unit_yield_helper.c | 452 ---- .../tls_mock/aws_iot_tests_unit_mock_tls.c | 202 -- .../aws_iot_tests_unit_mock_tls_params.c | 47 - .../aws_iot_tests_unit_mock_tls_params.h | 71 - tests/unit/tls_mock/network_platform.h | 19 - tests/unity/CMakeLists.txt | 11 + tests/unity/fixture/unity_fixture.c | 444 ++++ tests/unity/fixture/unity_fixture.h | 83 + tests/unity/fixture/unity_fixture_internals.h | 51 + .../fixture/unity_fixture_malloc_overrides.h | 53 + tests/unity/fixture/unity_memory_mt.c | 67 + tests/unity/unity.c | 1570 +++++++++++ tests/unity/unity.h | 503 ++++ tests/unity/unity_internals.h | 872 +++++++ 235 files changed, 36127 insertions(+), 22952 deletions(-) create mode 100644 .gitignore delete mode 100644 CHANGELOG.md create mode 100644 CMakeLists.txt delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 CppUTestMakefileWorker.mk create mode 100644 LICENSE delete mode 100644 LICENSE.txt delete mode 100644 Makefile delete mode 100644 NOTICE.txt delete mode 100644 PortingGuide.md delete mode 100644 certs/README.txt create mode 100644 demos/aws_iot_demo.h create mode 100644 demos/aws_iot_demo_config.h create mode 100644 demos/aws_iot_demo_mqtt.c create mode 100644 demos/aws_iot_demo_shadow.c create mode 100644 demos/posix/CMakeLists.txt create mode 100644 demos/posix/aws_iot_demo_common_posix.c create mode 100644 demos/posix/aws_iot_demo_mqtt_posix.c create mode 100644 demos/posix/aws_iot_demo_posix.h create mode 100644 demos/posix/aws_iot_demo_shadow_posix.c create mode 100644 doc/config/common create mode 100644 doc/config/html/footer.html create mode 100644 doc/config/html/header.html create mode 100644 doc/config/html/style.css create mode 100644 doc/config/layout_library.xml create mode 100644 doc/config/layout_main.xml create mode 100644 doc/config/logging create mode 100644 doc/config/main create mode 100644 doc/config/mqtt create mode 100644 doc/config/platform create mode 100644 doc/config/queue create mode 100644 doc/config/shadow create mode 100644 doc/guide/building.txt create mode 100644 doc/guide/developer.txt create mode 100644 doc/guide/style.txt create mode 100644 doc/lib/logging.txt create mode 100644 doc/lib/mqtt.txt create mode 100644 doc/lib/platform.txt create mode 100644 doc/lib/queue.txt create mode 100644 doc/lib/shadow.txt create mode 100644 doc/mainpage.txt create mode 100644 doc/plantuml/images/mqtt_demo.png create mode 100644 doc/plantuml/images/mqtt_design_typicaloperation.png create mode 100644 doc/plantuml/images/mqtt_function_receivecallback_nopartial.png create mode 100644 doc/plantuml/images/mqtt_function_receivecallback_partial.png create mode 100644 doc/plantuml/mqtt_demo.pu create mode 100644 doc/plantuml/mqtt_design_typicaloperation.pu create mode 100644 doc/plantuml/mqtt_function_receivecallback_nopartial.pu create mode 100644 doc/plantuml/mqtt_function_receivecallback_partial.pu delete mode 100644 external_libs/CppUTest/README.txt delete mode 100644 external_libs/jsmn/jsmn.c delete mode 100644 external_libs/jsmn/jsmn.h delete mode 100644 external_libs/mbedTLS/README.txt delete mode 100755 filterGcov.sh delete mode 100644 include/aws_iot_error.h delete mode 100644 include/aws_iot_jobs_interface.h delete mode 100644 include/aws_iot_jobs_json.h delete mode 100644 include/aws_iot_jobs_topics.h delete mode 100644 include/aws_iot_jobs_types.h delete mode 100644 include/aws_iot_json_utils.h delete mode 100644 include/aws_iot_log.h delete mode 100644 include/aws_iot_mqtt_client.h delete mode 100644 include/aws_iot_mqtt_client_common_internal.h delete mode 100644 include/aws_iot_mqtt_client_interface.h delete mode 100644 include/aws_iot_shadow_actions.h delete mode 100644 include/aws_iot_shadow_interface.h delete mode 100644 include/aws_iot_shadow_json.h delete mode 100644 include/aws_iot_shadow_json_data.h delete mode 100644 include/aws_iot_shadow_key.h delete mode 100644 include/aws_iot_shadow_records.h delete mode 100644 include/aws_iot_version.h delete mode 100644 include/network_interface.h delete mode 100644 include/threads_interface.h delete mode 100644 include/timer_interface.h create mode 100644 lib/config/aws_iot_config_build.h.in create mode 100644 lib/include/aws_iot_json_utils.h create mode 100644 lib/include/aws_iot_logging_setup.h create mode 100644 lib/include/aws_iot_mqtt.h create mode 100644 lib/include/aws_iot_queue.h create mode 100644 lib/include/aws_iot_shadow.h create mode 100644 lib/include/platform/aws_iot_clock.h create mode 100644 lib/include/platform/aws_iot_network.h create mode 100644 lib/include/platform/aws_iot_static_memory.h create mode 100644 lib/include/platform/aws_iot_threads.h create mode 100644 lib/include/private/aws_iot_logging.h create mode 100644 lib/include/private/aws_iot_mqtt_internal.h create mode 100644 lib/include/private/aws_iot_shadow_internal.h create mode 100644 lib/source/common/CMakeLists.txt create mode 100644 lib/source/common/aws_iot_json_utils.c create mode 100644 lib/source/common/aws_iot_logging.c create mode 100644 lib/source/common/aws_iot_queue.c create mode 100644 lib/source/mqtt/CMakeLists.txt create mode 100644 lib/source/mqtt/aws_iot_mqtt_api.c create mode 100644 lib/source/mqtt/aws_iot_mqtt_operation.c create mode 100644 lib/source/mqtt/aws_iot_mqtt_serialize.c create mode 100644 lib/source/mqtt/aws_iot_mqtt_subscription.c create mode 100644 lib/source/mqtt/aws_iot_mqtt_validate.c create mode 100644 lib/source/platform/posix/CMakeLists.txt create mode 100644 lib/source/platform/posix/aws_iot_clock_posix.c create mode 100644 lib/source/platform/posix/aws_iot_threads_posix.c create mode 100644 lib/source/platform/posix/network/aws_iot_network_openssl.c create mode 100644 lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c create mode 100644 lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c create mode 100644 lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c create mode 100644 lib/source/shadow/CMakeLists.txt create mode 100644 lib/source/shadow/aws_iot_shadow_api.c create mode 100644 lib/source/shadow/aws_iot_shadow_operation.c create mode 100644 lib/source/shadow/aws_iot_shadow_parser.c create mode 100644 lib/source/shadow/aws_iot_shadow_subscription.c delete mode 100644 platform/linux/common/timer.c delete mode 100644 platform/linux/common/timer_platform.h delete mode 100644 platform/linux/mbedtls/network_mbedtls_wrapper.c delete mode 100644 platform/linux/mbedtls/network_platform.h delete mode 100644 platform/linux/pthread/threads_platform.h delete mode 100644 platform/linux/pthread/threads_pthread_wrapper.c delete mode 100644 samples/README.md delete mode 100644 samples/linux/jobs_sample/Makefile delete mode 100644 samples/linux/jobs_sample/aws_iot_config.h delete mode 100644 samples/linux/jobs_sample/jobs_sample.c delete mode 100644 samples/linux/shadow_sample/Makefile delete mode 100644 samples/linux/shadow_sample/aws_iot_config.h delete mode 100644 samples/linux/shadow_sample/shadow_sample.c delete mode 100644 samples/linux/shadow_sample_console_echo/Makefile delete mode 100644 samples/linux/shadow_sample_console_echo/aws_iot_config.h delete mode 100644 samples/linux/shadow_sample_console_echo/shadow_console_echo.c delete mode 100644 samples/linux/subscribe_publish_library_sample/Makefile delete mode 100644 samples/linux/subscribe_publish_library_sample/aws_iot_config.h delete mode 100644 samples/linux/subscribe_publish_library_sample/subscribe_publish_library_sample.c delete mode 100644 samples/linux/subscribe_publish_sample/Makefile delete mode 100644 samples/linux/subscribe_publish_sample/aws_iot_config.h delete mode 100644 samples/linux/subscribe_publish_sample/subscribe_publish_sample.c create mode 100644 scripts/build_check_commit.sh create mode 100644 scripts/build_check_pr.sh create mode 100644 scripts/coverage.sh create mode 100644 scripts/generate_doc.sh delete mode 100644 src/aws_iot_jobs_interface.c delete mode 100644 src/aws_iot_jobs_json.c delete mode 100644 src/aws_iot_jobs_topics.c delete mode 100644 src/aws_iot_jobs_types.c delete mode 100644 src/aws_iot_json_utils.c delete mode 100644 src/aws_iot_mqtt_client.c delete mode 100644 src/aws_iot_mqtt_client_common_internal.c delete mode 100644 src/aws_iot_mqtt_client_connect.c delete mode 100644 src/aws_iot_mqtt_client_publish.c delete mode 100644 src/aws_iot_mqtt_client_subscribe.c delete mode 100644 src/aws_iot_mqtt_client_unsubscribe.c delete mode 100644 src/aws_iot_mqtt_client_yield.c delete mode 100644 src/aws_iot_shadow.c delete mode 100644 src/aws_iot_shadow_actions.c delete mode 100644 src/aws_iot_shadow_json.c delete mode 100644 src/aws_iot_shadow_records.c create mode 100644 tests/CMakeLists.txt delete mode 100644 tests/README.md create mode 100644 tests/aws_iot_tests_config.h create mode 100644 tests/aws_iot_tests_network.c delete mode 100644 tests/integration/Makefile delete mode 100644 tests/integration/README.md delete mode 100644 tests/integration/include/aws_iot_config.h delete mode 100644 tests/integration/include/aws_iot_integ_tests_config.h delete mode 100644 tests/integration/include/aws_iot_test_integration_common.h delete mode 100644 tests/integration/multithreadingTest/aws_iot_test_multithreading_validation.c delete mode 100644 tests/integration/src/aws_iot_test_auto_reconnect.c delete mode 100644 tests/integration/src/aws_iot_test_basic_connectivity.c delete mode 100644 tests/integration/src/aws_iot_test_integration_runner.c delete mode 100644 tests/integration/src/aws_iot_test_jobs_api.c delete mode 100644 tests/integration/src/aws_iot_test_multiple_clients.c create mode 100644 tests/mqtt/CMakeLists.txt create mode 100644 tests/mqtt/access/aws_iot_test_access_mqtt.h create mode 100644 tests/mqtt/access/aws_iot_test_access_mqtt_api.c create mode 100644 tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c create mode 100644 tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c create mode 100644 tests/mqtt/aws_iot_tests_mqtt.c create mode 100644 tests/mqtt/system/aws_iot_tests_mqtt_stress.c create mode 100644 tests/mqtt/system/aws_iot_tests_mqtt_system.c create mode 100644 tests/mqtt/unit/aws_iot_tests_mqtt_api.c create mode 100644 tests/mqtt/unit/aws_iot_tests_mqtt_receive.c create mode 100644 tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c create mode 100644 tests/mqtt/unit/aws_iot_tests_mqtt_validate.c create mode 100644 tests/shadow/CMakeLists.txt create mode 100644 tests/shadow/aws_iot_tests_shadow.c create mode 100644 tests/shadow/system/aws_iot_tests_shadow_system.c create mode 100644 tests/shadow/unit/aws_iot_tests_shadow_api.c create mode 100644 tests/shadow/unit/aws_iot_tests_shadow_parser.c delete mode 100644 tests/unit/README.md delete mode 100644 tests/unit/include/aws_iot_config.h delete mode 100644 tests/unit/include/aws_iot_tests_unit_helper_functions.h delete mode 100644 tests/unit/include/aws_iot_tests_unit_shadow_helper.h delete mode 100644 tests/unit/src/aws_iot_tests_unit_common_tests.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_common_tests_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_connect.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_connect_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_disconnect.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_disconnect_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_helper_functions.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_jobs.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_jobs_interface.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_jobs_json.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_jobs_topics.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_jobs_types.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_json_utils.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_json_utils_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_publish.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_publish_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_runner.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_action.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_action_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_delta.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_delta_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_json_builder.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_json_builder_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_null_fields.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_shadow_null_fields_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_subscribe.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_subscribe_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_unsubscribe.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_unsubscribe_helper.c delete mode 100644 tests/unit/src/aws_iot_tests_unit_yield.cpp delete mode 100644 tests/unit/src/aws_iot_tests_unit_yield_helper.c delete mode 100644 tests/unit/tls_mock/aws_iot_tests_unit_mock_tls.c delete mode 100644 tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.c delete mode 100644 tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.h delete mode 100644 tests/unit/tls_mock/network_platform.h create mode 100644 tests/unity/CMakeLists.txt create mode 100644 tests/unity/fixture/unity_fixture.c create mode 100644 tests/unity/fixture/unity_fixture.h create mode 100644 tests/unity/fixture/unity_fixture_internals.h create mode 100644 tests/unity/fixture/unity_fixture_malloc_overrides.h create mode 100644 tests/unity/fixture/unity_memory_mt.c create mode 100644 tests/unity/unity.c create mode 100644 tests/unity/unity.h create mode 100644 tests/unity/unity_internals.h diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ab40d21d77..87441e3c82 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,4 +3,4 @@ *Description of changes:* -By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. +By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..3ca54d3698 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Ignore documentation output. +doc/output/* +doc/tag/* + +# Ignore CMake build directory. +build/ diff --git a/.travis.yml b/.travis.yml index 0da28b3bea..1b91b9af81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,66 +1,37 @@ +# Build on Ubuntu 16.04. +os: linux +dist: xenial + +# Build with both clang and gcc. language: c +compiler: + - clang + - gcc + +# Only run on the v4 beta branch for now. +branches: + only: + - v4_beta -# Get Coverity certificate. before_install: - - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + # Install coveralls only for commit check builds. + - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then pip install --user cpp-coveralls; fi -# Coverity configuration. +# Install dependencies. addons: - coverity_scan: - project: - name: "aws-iot-device-sdk-embedded-C" - description: "SDK for connecting to AWS IoT from a device using embedded C. " - notification_email: nobody@amazon.com - build_command_prepend: "cd tests/integration" - build_command: "make app" - branch_pattern: master - -install: - # Remove placeholders. - - rm external_libs/CppUTest/* - - rm -rf external_libs/mbedTLS - - # Get mbedtls. - - git clone https://github.com/ARMmbed/mbedtls.git external_libs/mbedTLS - - # Get CppUTest. - - wget -qO- https://github.com/cpputest/cpputest/archive/v3.6.tar.gz | tar xvz -C external_libs/CppUTest --strip-components=1 + apt: + packages: + - cmake + - libssl-dev + - lcov script: - # Verify that the samples build. - - cd samples/linux/jobs_sample - - make - - cd ../shadow_sample - - make - - cd ../shadow_sample_console_echo - - make - - cd ../subscribe_publish_library_sample - - make - - cd ../subscribe_publish_sample - - make - - # Set the AWS IoT endpoint. - - cd ../../../tests/integration - - sed -i 's/^.*#define AWS_IOT_MQTT_HOST.*$/#define AWS_IOT_MQTT_HOST "'"$INTEGRATION_TEST_ENDPOINT"'"/' include/aws_iot_config.h - - # Build the integration tests. - - make app - - # Build the unit tests. - - cd ../../ - - make build-cpputest - - make all_no_tests - - # Execute unit tests. - - ./IotSdkC_tests + # Run the pull request build check script for pull requests. + - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./scripts/build_check_pr.sh; fi - # Import credentials. - - echo -e $INTEGRATION_TEST_CLIENT_CERT > certs/cert.pem - - echo -e $INTEGRATION_TEST_ROOT_CA > certs/rootCA.crt - - echo -e $INTEGRATION_TEST_PRIVATE_KEY > certs/privkey.pem + # Run the checks with AWS IoT credentials for commits. + - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then bash ./scripts/build_check_commit.sh; fi - # Execute integration tests. - - cd tests/integration - - ./integration_tests_mbedtls - - ./integration_tests_mbedtls_mt - +after_success: + # Send code coverage report to Coveralls, but only for one of the build jobs. + - if [ "$CC" = "gcc" ]; then bash ./scripts/coverage.sh; fi diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1e72896aeb..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,233 +0,0 @@ -# Change Log - -## [3.0.1](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v3.0.1) (May 10, 2018) - -Bugfixes: - - - [#167], [#168] Fixed issues reported by Coverity Scan. - - [#177] Fixes a memory corruption bug and handling of timeouts. - -Other: - - - Add .travis.yml for Travis CI. - - Removed C++ sample. - - Removed includes of `inttypes.h`, which doesn't exist on some systems. - - [#175] Added comments on static allocation of MQTT topics. - -## [3.0.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v3.0.0) (Apr 17, 2018) - -Bugfixes: - - - [#152] Fixes potential buffer overflows in `parseStringValue` by requiring a size parameter in `jsonStruct_t`. - - [#155] Fixes other memory corruption bugs; also improves stability. - -The two bug fixes above are not backwards compatible with v2.3.0. Please see [README.md](README.md#migrating-from-2x-to-3x) for details on migrating to v3.0.0. - -## [2.3.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v2.3.0) (Mar 21, 2018) - -New Features: - - - Add [AWS IoT Jobs](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html) support. - - Use AWS IoT Core support for MQTT over port 443. MQTT connection now defaults to port 443. - -Pull requests: - - - [#124](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/124) - Thing Shadow: Fix potential shadow buffer overflow - - [#135](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/135) - mbedtls_wrap: Fix unintialized variable usage - - Fix bugs in long-running integration tests. - -## [2.2.1](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v2.2.1) (Dec 26, 2017) - -Bugfixes: - - - [#115](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/115) - Issue with new client metrics - -Pull requests: - - - [#112](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/112) - Initialize msqParams.isRetained to 0 in publishToShadowAction() - - [#118](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/118) - mqttInitParams.mqttPacketTimeout_ms initialized - -## [2.2.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v2.2.0) (Nov 22, 2017) - -New Features: - - - Added SDK metrics string into connect packet - -Bugfixes: - - - [#49](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/49) - Add support for SHADOW_JSON_STRING as supported value - - [#57](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/57) - remove unistd.h - - [#58](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/58) - Fix return type of aws_iot_mqtt_internal_is_topic_matched - - [#59](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/95) - Fix extraneous assignment - - [#62](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/62) - Clearing SubscriptionList entries in shadowActionAcks after subscription failure - - [#63](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/63) - Stack overflow when IOT_DEBUG is enabled - - [#66](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/66) - Bug in send packet function - - [#69](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/69) - Fix for broken deleteActionHandler in shadow API - - [#71](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/71) - Prevent messages on /update/accepted from incrementing shadowJsonVersionNum in delta - - [#73](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/73) - wait for all messages to be received in subscribe publish sample - - [#96](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/96) - destroy TLS instance even if disconnect send fails - - Fix for aws_iot_mqtt_resubscribe not properly resubscribing to all topics - - Update MbedTLS Network layer Readme to remove specific version link - - Fix for not Passing througheError code on aws_iot_shadow_connect failure - - Allow sending of SHADOW_JSON_OBJECT to the shadow - - Ignore delta token callback for metadata - - Fix infinite publish exiting early in subscribe publish sample - -Improvements: - - - Updated jsmn to latest commit - - Change default keepalive interval to 600 seconds - -Pull requests: - - - [#29](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/29) - three small fixes - - [#59](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/59) - Fixed MQTT header constructing and parsing - - [#88](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/88) - Fix username and password are confused - - [#78](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/78) - Fixed compilation warnings - - [#102](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/102) - Corrected markdown headers - - [#105](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/105) - Fixed warnings when compiling - -## [2.1.1](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v2.1.1) (Sep 5, 2016) - -Bugfixes/Improvements: - - - Network layer interface improvements to address reported issues - - Incorporated GitHub pull request [#41](https://github.com/aws/aws-iot-device-sdk-embedded-c/pull/41) - - Bugfixes for issues [#36](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/36) and [#33](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/33) - -## [2.1.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v2.1.0) (Jun 15, 2016) - -New features: - - - Added unit tests, further details can be found in the testing readme [here](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/tests/README.md) - - Added sample to demonstrate building the SDK as library - - Added sample to demonstrate building the SDK in C++ - -Bugfixes/Improvements: - - - Increased default value of Maximum Reconnect Wait interval to 128 secs - - Increased default value of MQTT Command Timeout in Shadow Connect to 20 secs - - Shadow null/length checks - - Client Id Length not passed correctly in shadow connect - - Add extern C to headers and source files, added sample to demonstrate usage with C++ - - Delete/Accepted not being reported, callback added for delete/accepted - - Append IOT_ to all Debug macros (eg. DEBUG is now IOT_DEBUG) - - Fixed exit on error for subscribe_publish_sample - -## [2.0.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v2.0.0) (April 28, 2016) - -New features: - - - Refactored API to make it instance specific. This is a breaking change in the API from 1.x releases because a Client Instance parameter had to be added to all APIs - - Added Threading library porting layer wrapper - - Added support for multiple connections from one application - - Shadows and connections de-linked, connection needs to be set up separately, can be used independently of shadow - - Added integration tests for testing SDK functionality - -Bugfixes/Improvements: - - - Yield cannot be called again while waiting for application callback to return - - Fixed issue with TLS layer handles not being cleaned up properly on connection failure reported [here](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/16) - - Renamed timer_linux.h to timer_platform.h as requested [here](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/5) - - Adds support for disconnect handler to shadow. A similar pull request can be found [here](https://github.com/aws/aws-iot-device-sdk-embedded-C/pull/9) - - New SDK folder structure, cleaned and simplified code structure - - Removed Paho Wrapper, Merge MQTT into SDK code, added specific error codes - - Refactored Network and Timer layer wrappers, added specific error codes - - Refactored samples and makefiles - -## [1.1.2](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v1.1.2) (April 22, 2016) - -Bugfixes/Improvements: - - - Signature mismatch in MQTT library file fixed - - Makefiles have a protective target on the top to prevent accidental execution - -## [1.1.1](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v1.1.1) (April 1, 2016) - -Bugfixes/Improvements: - - - Removing the Executable bit from all the files in the repository. Fixing [this](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues/14) issue - - Refactoring MQTT client to remove declaration after statement warnings - - Fixing [this](https://forums.aws.amazon.com/thread.jspa?threadID=222467&tstart=0) bug - - -## [1.1.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v1.1.0) (February 10, 2016) -Features: - - - Auto Reconnect and Resubscribe - -Bugfixes/Improvements: - - - MQTT buffer handling incase of bigger message - - Large timeout values converted to seconds and milliseconds - - Dynamic loading of Shadow parameters. Client ID and Thing Name are not hard-coded - - MQTT Library refactored - - -## [1.0.1](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v1.0.1) (October 21, 2015) - -Bugfixes/Improvements: - - - Paho name changed to Eclipse Paho - - Renamed the Makefiles in the samples directory - - Device Shadow - Delete functionality macro fixed - - `subscribe_publish_sample` updated - -## [1.0.0](https://github.com/aws/aws-iot-device-sdk-embedded-C/releases/tag/v1.0.0) (October 8, 2015) - -Features: - - - Release to github - - SDK tarballs made available for public download - -Bugfixes/Improvements: - - Updated API documentation - -## 0.4.0 (October 5, 2015) - -Features: - - - Thing Shadow Actions - Update, Delete, Get for any Thing Name - - aws_iot_config.h file for easy configuration of parameters - - Sample app for talking with console's Interactive guide - - disconnect handler for the MQTT client library - -Bugfixes/Improvements: - - - mbedTLS read times out every 10 ms instead of hanging for ever - - mbedTLS handshake failure handled - -## 0.3.0 (September 14, 2015) - -Features: - - - Testing with mbedTLS, prepping for relase - -Bugfixes/Improvements: - - - Refactored to break out timer and network interfaces - -## 0.2.0 (September 2, 2015) - -Features: - - - Added initial Shadow implementation + example - - Added hostname verification to OpenSSL example - - Added iot_log interface - - Initial API Docs (Doxygen) - -Bugfixes/Improvements: - - - Fixed yield timeout - - Refactored APIs to pass by reference vs value - -## 0.1.0 (August 12, 2015) - -Features: - - - Initial beta release - - MQTT Publish and Subscribe - - TLS mutual auth on linux with OpenSSL - -Bugfixes/Improvements: - - N/A diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..5d4138b46c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +# Project information. +cmake_minimum_required( VERSION 3.5.0 ) +project( AwsIotDeviceSdkC + VERSION 4.0.0 + LANGUAGES C ) +add_definitions( -DAWS_IOT_SDK_VERSION="${PROJECT_VERSION}" ) + +# Use C99. +set( CMAKE_C_STANDARD 99 ) +set( CMAKE_C_STANDARD_REQUIRED ON ) + +# Do not allow in-source build. +if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) + message( FATAL_ERROR "In-source build is not allowed. Please build in a separate directory, such as ${PROJECT_SOURCE_DIR}/build." ) +endif() + +# Check for system support. +list( APPEND SUPPORTED_SYSTEMS "Linux" ) + +if( NOT ${CMAKE_SYSTEM_NAME} IN_LIST SUPPORTED_SYSTEMS ) + message( FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}. Currently supported systems are: ${SUPPORTED_SYSTEMS}." ) +endif() + +# Set output directories. +set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) +set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) + +# SDK include path. +include_directories( ${PROJECT_SOURCE_DIR}/lib/include ) + +# CMake configure_file output. +include_directories( ${PROJECT_BINARY_DIR}/config ) + +# Demo include path. +include_directories( ${PROJECT_SOURCE_DIR}/demos ) + +# Use either the demo or test configuration file. +if( ${AWS_IOT_BUILD_TESTS} ) + # Tests include directories. + include_directories( ${PROJECT_SOURCE_DIR}/tests + tests/unity + tests/unity/fixture + tests/mqtt/access ) + + # Tests config file. + add_definitions( -DAWS_IOT_USER_CONFIG_FILE="aws_iot_tests_config.h" ) + + # Build unity test framework. + add_subdirectory( tests/unity ) +else() + add_definitions( -DAWS_IOT_USER_CONFIG_FILE="aws_iot_demo_config.h" ) +endif() + +# Platform libraries. +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + add_subdirectory( lib/source/platform/posix ) + + # Set the configuration file for all subsequent libraries. + add_definitions( -DAWS_IOT_CONFIG_FILE="aws_iot_config_build.h" ) +endif() + +# Common libraries (queue, logging, etc.) +add_subdirectory( lib/source/common ) + +# MQTT library. +add_subdirectory( lib/source/mqtt ) + +# Shadow library. +add_subdirectory( lib/source/shadow ) + +# Demo executables. +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + add_subdirectory( demos/posix ) +endif() + +# Test executables. +if( ${AWS_IOT_BUILD_TESTS} ) + add_subdirectory( tests/ ) +endif() diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 3b64466870..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,4 +0,0 @@ -## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb63eaed2d..8908ec04b1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ information to effectively respond to your bug report or contribution. We welcome you to use the GitHub issue tracker to report bugs or suggest features. -When filing an issue, please check [existing open](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues), or [recently closed](https://github.com/aws/aws-iot-device-sdk-embedded-C/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already +When filing an issue, please check [existing open](https://github.com/awslabs/aws-iot-device-sdk-c/issues), or [recently closed](https://github.com/awslabs/aws-iot-device-sdk-c/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: * A reproducible test case or series of steps @@ -41,7 +41,7 @@ GitHub provides additional document on [forking a repository](https://help.githu ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-iot-device-sdk-embedded-C/labels/help%20wanted) issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-iot-device-sdk-c/labels/help%20wanted) issues is a great place to start. ## Code of Conduct @@ -56,6 +56,6 @@ If you discover a potential security issue in this project we ask that you notif ## Licensing -See the [LICENSE](https://github.com/aws/aws-iot-device-sdk-embedded-C/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the [LICENSE](https://github.com/awslabs/aws-iot-device-sdk-c/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/CppUTestMakefileWorker.mk b/CppUTestMakefileWorker.mk deleted file mode 100644 index 9f3c3054f7..0000000000 --- a/CppUTestMakefileWorker.mk +++ /dev/null @@ -1,575 +0,0 @@ -#--------- -# -# MakefileWorker.mk -# -# Include this helper file in your makefile -# It makes -# A static library -# A test executable -# -# See this example for parameter settings -# examples/Makefile -# -#---------- -# Inputs - these variables describe what to build -# -# INCLUDE_DIRS - Directories used to search for include files. -# This generates a -I for each directory -# SRC_DIRS - Directories containing source file to built into the library -# SRC_FILES - Specific source files to build into library. Helpful when not all code -# in a directory can be built for test (hopefully a temporary situation) -# TEST_SRC_DIRS - Directories containing unit test code build into the unit test runner -# These do not go in a library. They are explicitly included in the test runner -# TEST_SRC_FILES - Specific source files to build into the unit test runner -# These do not go in a library. They are explicitly included in the test runner -# MOCKS_SRC_DIRS - Directories containing mock source files to build into the test runner -# These do not go in a library. They are explicitly included in the test runner -#---------- -# You can adjust these variables to influence how to build the test target -# and where to put and name outputs -# See below to determine defaults -# COMPONENT_NAME - the name of the thing being built -# TEST_TARGET - name the test executable. By default it is -# $(COMPONENT_NAME)_tests -# Helpful if you want 1 > make files in the same directory with different -# executables as output. -# CPPUTEST_HOME - where CppUTest home dir found -# TARGET_PLATFORM - Influences how the outputs are generated by modifying the -# CPPUTEST_OBJS_DIR and CPPUTEST_LIB_DIR to use a sub-directory under the -# normal objs and lib directories. Also modifies where to search for the -# CPPUTEST_LIB to link against. -# CPPUTEST_OBJS_DIR - a directory where o and d files go -# CPPUTEST_LIB_DIR - a directory where libs go -# CPPUTEST_ENABLE_DEBUG - build for debug -# CPPUTEST_USE_MEM_LEAK_DETECTION - Links with overridden new and delete -# CPPUTEST_USE_STD_CPP_LIB - Set to N to keep the standard C++ library out -# of the test harness -# CPPUTEST_USE_GCOV - Turn on coverage analysis -# Clean then build with this flag set to Y, then 'make gcov' -# CPPUTEST_MAPFILE - generate a map file -# CPPUTEST_WARNINGFLAGS - overly picky by default -# OTHER_MAKEFILE_TO_INCLUDE - a hook to use this makefile to make -# other targets. Like CSlim, which is part of fitnesse -# CPPUTEST_USE_VPATH - Use Make's VPATH functionality to support user -# specification of source files and directories that aren't below -# the user's Makefile in the directory tree, like: -# SRC_DIRS += ../../lib/foo -# It defaults to N, and shouldn't be necessary except in the above case. -#---------- -# -# Other flags users can initialize to sneak in their settings -# CPPUTEST_CXXFLAGS - flags for the C++ compiler -# CPPUTEST_CPPFLAGS - flags for the C++ AND C preprocessor -# CPPUTEST_CFLAGS - flags for the C complier -# CPPUTEST_LDFLAGS - Linker flags -#---------- - -# Some behavior is weird on some platforms. Need to discover the platform. - -# Platforms -UNAME_OUTPUT = "$(shell uname -a)" -MACOSX_STR = Darwin -MINGW_STR = MINGW -CYGWIN_STR = CYGWIN -LINUX_STR = Linux -SUNOS_STR = SunOS -UNKNWOWN_OS_STR = Unknown - -# Compilers -CC_VERSION_OUTPUT ="$(shell $(CXX) -v 2>&1)" -CLANG_STR = clang -SUNSTUDIO_CXX_STR = SunStudio - -UNAME_OS = $(UNKNWOWN_OS_STR) - -ifeq ($(findstring $(MINGW_STR),$(UNAME_OUTPUT)),$(MINGW_STR)) - UNAME_OS = $(MINGW_STR) -endif - -ifeq ($(findstring $(CYGWIN_STR),$(UNAME_OUTPUT)),$(CYGWIN_STR)) - UNAME_OS = $(CYGWIN_STR) -endif - -ifeq ($(findstring $(LINUX_STR),$(UNAME_OUTPUT)),$(LINUX_STR)) - UNAME_OS = $(LINUX_STR) -endif - -ifeq ($(findstring $(MACOSX_STR),$(UNAME_OUTPUT)),$(MACOSX_STR)) - UNAME_OS = $(MACOSX_STR) -#lion has a problem with the 'v' part of -a - UNAME_OUTPUT = "$(shell uname -pmnrs)" -endif - -ifeq ($(findstring $(SUNOS_STR),$(UNAME_OUTPUT)),$(SUNOS_STR)) - UNAME_OS = $(SUNOS_STR) - - SUNSTUDIO_CXX_ERR_STR = CC -flags -ifeq ($(findstring $(SUNSTUDIO_CXX_ERR_STR),$(CC_VERSION_OUTPUT)),$(SUNSTUDIO_CXX_ERR_STR)) - CC_VERSION_OUTPUT ="$(shell $(CXX) -V 2>&1)" - COMPILER_NAME = $(SUNSTUDIO_CXX_STR) -endif -endif - -ifeq ($(findstring $(CLANG_STR),$(CC_VERSION_OUTPUT)),$(CLANG_STR)) - COMPILER_NAME = $(CLANG_STR) -endif - -#Kludge for mingw, it does not have cc.exe, but gcc.exe will do -ifeq ($(UNAME_OS),$(MINGW_STR)) - CC := gcc -endif -# RHEL5 is always going to use GCC, CC is going for the old verison -CC := gcc -#And another kludge. Exception handling in gcc 4.6.2 is broken when linking the -# Standard C++ library as a shared library. Unbelievable. -ifeq ($(UNAME_OS),$(MINGW_STR)) - CPPUTEST_LDFLAGS += -static -endif -ifeq ($(UNAME_OS),$(CYGWIN_STR)) - CPPUTEST_LDFLAGS += -static -endif - - -#Kludge for MacOsX gcc compiler on Darwin9 who can't handle pendantic -ifeq ($(UNAME_OS),$(MACOSX_STR)) -ifeq ($(findstring Version 9,$(UNAME_OUTPUT)),Version 9) - CPPUTEST_PEDANTIC_ERRORS = N -endif -endif - -ifndef COMPONENT_NAME - COMPONENT_NAME = name_this_in_the_makefile -endif - -# Debug on by default -ifndef CPPUTEST_ENABLE_DEBUG - CPPUTEST_ENABLE_DEBUG = Y -endif - -# new and delete for memory leak detection on by default -ifndef CPPUTEST_USE_MEM_LEAK_DETECTION - CPPUTEST_USE_MEM_LEAK_DETECTION = Y -endif - -# Use the standard C library -ifndef CPPUTEST_USE_STD_C_LIB - CPPUTEST_USE_STD_C_LIB = Y -endif - -# Use the standard C++ library -ifndef CPPUTEST_USE_STD_CPP_LIB - CPPUTEST_USE_STD_CPP_LIB = Y -endif - -# Use gcov, off by default -ifndef CPPUTEST_USE_GCOV - CPPUTEST_USE_GCOV = N -endif - -ifndef CPPUTEST_PEDANTIC_ERRORS - CPPUTEST_PEDANTIC_ERRORS = Y -endif - -# Default warnings -ifndef CPPUTEST_WARNINGFLAGS - CPPUTEST_WARNINGFLAGS = -Wall -Wextra -Wswitch-default -Wswitch-enum -Wconversion -ifeq ($(CPPUTEST_PEDANTIC_ERRORS), Y) - CPPUTEST_WARNINGFLAGS += -pedantic-errors -endif -ifeq ($(UNAME_OS),$(LINUX_STR)) -# CPPUTEST_WARNINGFLAGS += -Wsign-conversion -endif - CPPUTEST_CXX_WARNINGFLAGS = -Woverloaded-virtual - CPPUTEST_C_WARNINGFLAGS = -Wstrict-prototypes -endif - -#Wonderful extra compiler warnings with clang -ifeq ($(COMPILER_NAME),$(CLANG_STR)) -# -Wno-disabled-macro-expansion -> Have to disable the macro expansion warning as the operator new overload warns on that. -# -Wno-padded -> I sort-of like this warning but if there is a bool at the end of the class, it seems impossible to remove it! (except by making padding explicit) -# -Wno-global-constructors Wno-exit-time-destructors -> Great warnings, but in CppUTest it is impossible to avoid as the automatic test registration depends on the global ctor and dtor -# -Wno-weak-vtables -> The TEST_GROUP macro declares a class and will automatically inline its methods. Thats ok as they are only in one translation unit. Unfortunately, the warning can't detect that, so it must be disabled. - CPPUTEST_CXX_WARNINGFLAGS += -Weverything -Wno-disabled-macro-expansion -Wno-padded -Wno-global-constructors -Wno-exit-time-destructors -Wno-weak-vtables - CPPUTEST_C_WARNINGFLAGS += -Weverything -Wno-padded -endif - -# Uhm. Maybe put some warning flags for SunStudio here? -ifeq ($(COMPILER_NAME),$(SUNSTUDIO_CXX_STR)) - CPPUTEST_CXX_WARNINGFLAGS = - CPPUTEST_C_WARNINGFLAGS = -endif - -# Default dir for temporary files (d, o) -ifndef CPPUTEST_OBJS_DIR -ifndef TARGET_PLATFORM - CPPUTEST_OBJS_DIR = objs -else - CPPUTEST_OBJS_DIR = objs/$(TARGET_PLATFORM) -endif -endif - -# Default dir for the output library -ifndef CPPUTEST_LIB_DIR -ifndef TARGET_PLATFORM - CPPUTEST_LIB_DIR = testLibs -else - CPPUTEST_LIB_DIR = testLibs/$(TARGET_PLATFORM) -endif -endif - -# No map by default -ifndef CPPUTEST_MAP_FILE - CPPUTEST_MAP_FILE = N -endif - -# No extentions is default -ifndef CPPUTEST_USE_EXTENSIONS - CPPUTEST_USE_EXTENSIONS = N -endif - -# No VPATH is default -ifndef CPPUTEST_USE_VPATH - CPPUTEST_USE_VPATH := N -endif -# Make empty, instead of 'N', for usage in $(if ) conditionals -ifneq ($(CPPUTEST_USE_VPATH), Y) - CPPUTEST_USE_VPATH := -endif - -ifndef TARGET_PLATFORM -CPPUTEST_LIB_LINK_DIR = $(CPPUTEST_BUILD_LIB) -else -CPPUTEST_LIB_LINK_DIR = $(CPPUTEST_BUILD_LIB)/$(TARGET_PLATFORM) -endif - -# -------------------------------------- -# derived flags in the following area -# -------------------------------------- - -# Without the C library, we'll need to disable the C++ library and ... -ifeq ($(CPPUTEST_USE_STD_C_LIB), N) - CPPUTEST_USE_STD_CPP_LIB = N - CPPUTEST_USE_MEM_LEAK_DETECTION = N - CPPUTEST_CPPFLAGS += -DCPPUTEST_STD_C_LIB_DISABLED - CPPUTEST_CPPFLAGS += -nostdinc -endif - -CPPUTEST_CPPFLAGS += -DCPPUTEST_COMPILATION - -ifeq ($(CPPUTEST_USE_MEM_LEAK_DETECTION), N) - CPPUTEST_CPPFLAGS += -DCPPUTEST_MEM_LEAK_DETECTION_DISABLED -else - ifndef CPPUTEST_MEMLEAK_DETECTOR_NEW_MACRO_FILE - CPPUTEST_MEMLEAK_DETECTOR_NEW_MACRO_FILE = -include $(CPPUTEST_INCLUDE)/CppUTest/MemoryLeakDetectorNewMacros.h - endif - ifndef CPPUTEST_MEMLEAK_DETECTOR_MALLOC_MACRO_FILE - CPPUTEST_MEMLEAK_DETECTOR_MALLOC_MACRO_FILE = -include $(CPPUTEST_INCLUDE)/CppUTest/MemoryLeakDetectorMallocMacros.h - endif -endif - -ifeq ($(CPPUTEST_ENABLE_DEBUG), Y) - CPPUTEST_CXXFLAGS += -g - CPPUTEST_CFLAGS += -g - CPPUTEST_LDFLAGS += -g -endif - -ifeq ($(CPPUTEST_USE_STD_CPP_LIB), N) - CPPUTEST_CPPFLAGS += -DCPPUTEST_STD_CPP_LIB_DISABLED -ifeq ($(CPPUTEST_USE_STD_C_LIB), Y) - CPPUTEST_CXXFLAGS += -nostdinc++ -endif -endif - -ifdef $(GMOCK_HOME) - GTEST_HOME = $(GMOCK_HOME)/gtest - CPPUTEST_CPPFLAGS += -I$(GMOCK_HOME)/include - GMOCK_LIBRARY = $(GMOCK_HOME)/lib/.libs/libgmock.a - LD_LIBRARIES += $(GMOCK_LIBRARY) - CPPUTEST_CPPFLAGS += -DINCLUDE_GTEST_TESTS - CPPUTEST_WARNINGFLAGS = - CPPUTEST_CPPFLAGS += -I$(GTEST_HOME)/include -I$(GTEST_HOME) - GTEST_LIBRARY = $(GTEST_HOME)/lib/.libs/libgtest.a - LD_LIBRARIES += $(GTEST_LIBRARY) -endif - - -ifeq ($(CPPUTEST_USE_GCOV), Y) - CPPUTEST_CXXFLAGS += -fprofile-arcs -ftest-coverage - CPPUTEST_CFLAGS += -fprofile-arcs -ftest-coverage -endif - -CPPUTEST_CXXFLAGS += $(CPPUTEST_WARNINGFLAGS) $(CPPUTEST_CXX_WARNINGFLAGS) -CPPUTEST_CPPFLAGS += $(CPPUTEST_WARNINGFLAGS) -CPPUTEST_CXXFLAGS += $(CPPUTEST_MEMLEAK_DETECTOR_NEW_MACRO_FILE) -CPPUTEST_CPPFLAGS += $(CPPUTEST_MEMLEAK_DETECTOR_MALLOC_MACRO_FILE) -CPPUTEST_CFLAGS += $(CPPUTEST_C_WARNINGFLAGS) - -TARGET_MAP = $(COMPONENT_NAME).map.txt -ifeq ($(CPPUTEST_MAP_FILE), Y) - CPPUTEST_LDFLAGS += -Wl,-map,$(TARGET_MAP) -endif - -# Link with CppUTest lib -CPPUTEST_LIB = $(CPPUTEST_LIB_LINK_DIR)/libCppUTest.a - -ifeq ($(CPPUTEST_USE_EXTENSIONS), Y) -CPPUTEST_LIB += $(CPPUTEST_LIB_LINK_DIR)/libCppUTestExt.a -endif - -ifdef CPPUTEST_STATIC_REALTIME - LD_LIBRARIES += -lrt -endif - -TARGET_LIB = \ - $(CPPUTEST_LIB_DIR)/lib$(COMPONENT_NAME).a - -ifndef TEST_TARGET - ifndef TARGET_PLATFORM - TEST_TARGET = $(COMPONENT_NAME)_tests - else - TEST_TARGET = $(COMPONENT_NAME)_$(TARGET_PLATFORM)_tests - endif -endif - -#Helper Functions -get_src_from_dir = $(wildcard $1/*.cpp) $(wildcard $1/*.cc) $(wildcard $1/*.c) -get_dirs_from_dirspec = $(wildcard $1) -get_src_from_dir_list = $(foreach dir, $1, $(call get_src_from_dir,$(dir))) -__src_to = $(subst .c,$1, $(subst .cc,$1, $(subst .cpp,$1,$(if $(CPPUTEST_USE_VPATH),$(notdir $2),$2)))) -src_to = $(addprefix $(CPPUTEST_OBJS_DIR)/,$(call __src_to,$1,$2)) -src_to_o = $(call src_to,.o,$1) -src_to_d = $(call src_to,.d,$1) -src_to_gcda = $(call src_to,.gcda,$1) -src_to_gcno = $(call src_to,.gcno,$1) -time = $(shell date +%s) -delta_t = $(eval minus, $1, $2) -debug_print_list = $(foreach word,$1,echo " $(word)";) echo; - -#Derived -STUFF_TO_CLEAN += $(TEST_TARGET) $(TEST_TARGET).exe $(TARGET_LIB) $(TARGET_MAP) - -SRC += $(call get_src_from_dir_list, $(SRC_DIRS)) $(SRC_FILES) -OBJ = $(call src_to_o,$(SRC)) - -STUFF_TO_CLEAN += $(OBJ) - -TEST_SRC += $(call get_src_from_dir_list, $(TEST_SRC_DIRS)) $(TEST_SRC_FILES) -TEST_OBJS = $(call src_to_o,$(TEST_SRC)) -STUFF_TO_CLEAN += $(TEST_OBJS) - - -MOCKS_SRC += $(call get_src_from_dir_list, $(MOCKS_SRC_DIRS)) -MOCKS_OBJS = $(call src_to_o,$(MOCKS_SRC)) -STUFF_TO_CLEAN += $(MOCKS_OBJS) - -ALL_SRC = $(SRC) $(TEST_SRC) $(MOCKS_SRC) - -# If we're using VPATH -ifeq ($(CPPUTEST_USE_VPATH), Y) -# gather all the source directories and add them - VPATH += $(sort $(dir $(ALL_SRC))) -# Add the component name to the objs dir path, to differentiate between same-name objects - CPPUTEST_OBJS_DIR := $(addsuffix /$(COMPONENT_NAME),$(CPPUTEST_OBJS_DIR)) -endif - -#LCOV html generation -BUILD_OUTPUT_DIR=build_output -COVERAGE_DIR=$(BUILD_OUTPUT_DIR)/generated-coverage -LCOV_INFO_FILE=$(TEST_TARGET).info -LCOV_SUMMARY_FILE=$(TEST_TARGET)_summary.info - -#Test coverage with gcov -GCOV_OUTPUT = gcov_output.txt -GCOV_REPORT = gcov_report.txt -GCOV_ERROR = gcov_error.txt -GCOV_GCDA_FILES = $(call src_to_gcda, $(ALL_SRC)) -GCOV_GCNO_FILES = $(call src_to_gcno, $(ALL_SRC)) -TEST_OUTPUT = $(TEST_TARGET).txt -STUFF_TO_CLEAN += \ - $(GCOV_OUTPUT)\ - $(GCOV_REPORT)\ - $(GCOV_REPORT).html\ - $(GCOV_ERROR)\ - $(GCOV_GCDA_FILES)\ - $(GCOV_GCNO_FILES)\ - $(TEST_OUTPUT) - -#The gcda files for gcov need to be deleted before each run -#To avoid annoying messages. -GCOV_CLEAN = $(SILENCE)$(RM) -f $(GCOV_GCDA_FILES) $(GCOV_OUTPUT) $(GCOV_REPORT) $(GCOV_ERROR) -RUN_TEST_TARGET = $(SILENCE) $(GCOV_CLEAN) ; echo "Running $(TEST_TARGET)"; ./$(TEST_TARGET) $(CPPUTEST_EXE_FLAGS) - -ifeq ($(CPPUTEST_USE_GCOV), Y) - - ifeq ($(COMPILER_NAME),$(CLANG_STR)) - LD_LIBRARIES += --coverage - else - LD_LIBRARIES += -lgcov - endif -endif - - -INCLUDES_DIRS_EXPANDED = $(call get_dirs_from_dirspec, $(INCLUDE_DIRS)) -INCLUDES += $(foreach dir, $(INCLUDES_DIRS_EXPANDED), -I$(dir)) -MOCK_DIRS_EXPANDED = $(call get_dirs_from_dirspec, $(MOCKS_SRC_DIRS)) -INCLUDES += $(foreach dir, $(MOCK_DIRS_EXPANDED), -I$(dir)) - -CPPUTEST_CPPFLAGS += $(INCLUDES) - -DEP_FILES = $(call src_to_d, $(ALL_SRC)) -STUFF_TO_CLEAN += $(DEP_FILES) $(PRODUCTION_CODE_START) $(PRODUCTION_CODE_END) -STUFF_TO_CLEAN += $(STDLIB_CODE_START) $(MAP_FILE) cpputest_*.xml junit_run_output - -# We'll use the CPPUTEST_CFLAGS etc so that you can override AND add to the CppUTest flags -CFLAGS = $(CPPUTEST_CFLAGS) $(CPPUTEST_ADDITIONAL_CFLAGS) -CPPFLAGS = $(CPPUTEST_CPPFLAGS) $(CPPUTEST_ADDITIONAL_CPPFLAGS) -CXXFLAGS = $(CPPUTEST_CXXFLAGS) $(CPPUTEST_ADDITIONAL_CXXFLAGS) -LDFLAGS = $(CPPUTEST_LDFLAGS) $(CPPUTEST_ADDITIONAL_LDFLAGS) - -# Don't consider creating the archive a warning condition that does STDERR output -ARFLAGS := $(ARFLAGS)c - -DEP_FLAGS=-MMD -MP - -# Some macros for programs to be overridden. For some reason, these are not in Make defaults -RANLIB = ranlib - -# Targets - -ALL_TARGETS += cpputest_all -ALL_TARGETS_CLEAN += cpputest_clean -.PHONY: cpputest_all -cpputest_all: start $(TEST_TARGET) gcov - $(RUN_TEST_TARGET) - -.PHONY: start -start: $(TEST_TARGET) - $(SILENCE)START_TIME=$(call time) - -.PHONY: all_no_tests -all_no_tests: $(TEST_TARGET) - -.PHONY: flags -flags: - @echo - @echo "OS ${UNAME_OS}" - @echo "Compile C and C++ source with CPPFLAGS:" - @$(call debug_print_list,$(CPPFLAGS)) - @echo "Compile C++ source with CXXFLAGS:" - @$(call debug_print_list,$(CXXFLAGS)) - @echo "Compile C source with CFLAGS:" - @$(call debug_print_list,$(CFLAGS)) - @echo "Link with LDFLAGS:" - @$(call debug_print_list,$(LDFLAGS)) - @echo "Link with LD_LIBRARIES:" - @$(call debug_print_list,$(LD_LIBRARIES)) - @echo "Create libraries with ARFLAGS:" - @$(call debug_print_list,$(ARFLAGS)) - -TEST_DEPS = $(TEST_OBJS) $(MOCKS_OBJS) $(PRODUCTION_CODE_START) $(TARGET_LIB) $(USER_LIBS) $(PRODUCTION_CODE_END) $(CPPUTEST_LIB) $(STDLIB_CODE_START) -test-deps: $(TEST_DEPS) - -$(TEST_TARGET): $(TEST_DEPS) - @echo Linking $@ - $(SILENCE)$(CXX) -o $@ $^ $(LD_LIBRARIES) $(LDFLAGS) - -$(TARGET_LIB): $(OBJ) - @echo Building archive $@ - $(SILENCE)mkdir -p $(dir $@) - $(SILENCE)$(AR) $(ARFLAGS) $@ $^ - $(SILENCE)$(RANLIB) $@ - -test: $(TEST_TARGET) - $(RUN_TEST_TARGET) $(COMMAND_LINE_ARGUMENTS) | tee $(TEST_OUTPUT) -vtest: $(TEST_TARGET) - $(RUN_TEST_TARGET) -v | tee $(TEST_OUTPUT) - -$(CPPUTEST_OBJS_DIR)/%.o: %.cc - @echo compiling $(notdir $<) - $(SILENCE)mkdir -p $(dir $@) - $(SILENCE)$(COMPILE.cpp) $(DEP_FLAGS) $(OUTPUT_OPTION) $< - -$(CPPUTEST_OBJS_DIR)/%.o: %.cpp - @echo compiling $(notdir $<) - $(SILENCE)mkdir -p $(dir $@) - $(SILENCE)$(COMPILE.cpp) $(DEP_FLAGS) $(OUTPUT_OPTION) $< - -$(CPPUTEST_OBJS_DIR)/%.o: %.c - @echo compiling $(notdir $<) - $(SILENCE)mkdir -p $(dir $@) - $(SILENCE)$(COMPILE.c) $(DEP_FLAGS) $(OUTPUT_OPTION) $< - -ifneq "$(MAKECMDGOALS)" "clean" --include $(DEP_FILES) -endif - -.PHONY: cpputest_clean -cpputest_clean: - @echo Making clean - $(SILENCE)$(RM) $(STUFF_TO_CLEAN) - $(SILENCE)$(RM) -rf gcov $(CPPUTEST_OBJS_DIR) - $(SILENCE)find . -name "*.gcno" | xargs $(RM) -f - $(SILENCE)find . -name "*.gcda" | xargs $(RM) -f - -#realclean gets rid of all gcov, o and d files in the directory tree -#not just the ones made by this makefile -.PHONY: realclean -realclean: clean - $(SILENCE)$(RM) -rf gcov - $(SILENCE)$(RM) -rf $(BUILD_OUTPUT_DIR) - $(SILENCE)find . -name "*.gdcno" | xargs $(RM) -f - $(SILENCE)find . -name "*.[do]" | xargs $(RM) -f - -gcov: test - $(SILENCE)mkdir -p $(BUILD_OUTPUT_DIR) -ifeq ($(CPPUTEST_USE_VPATH), Y) - $(SILENCE)gcov --object-directory $(CPPUTEST_OBJS_DIR) $(SRC) >> $(GCOV_OUTPUT) 2>> $(GCOV_ERROR) -else - $(SILENCE)for d in $(SRC_DIRS) ; do \ - FILES=`ls $$d/*.c $$d/*.cc $$d/*.cpp 2> /dev/null` ; \ - gcov --object-directory $(CPPUTEST_OBJS_DIR)/$$d $$FILES >> $(GCOV_OUTPUT) 2>>$(GCOV_ERROR) ; \ - done - $(SILENCE)for f in $(SRC_FILES) ; do \ - gcov --object-directory $(CPPUTEST_OBJS_DIR)/$$f $$f >> $(GCOV_OUTPUT) 2>>$(GCOV_ERROR) ; \ - done -endif - $(SILENCE)./filterGcov.sh $(GCOV_OUTPUT) $(GCOV_ERROR) $(GCOV_REPORT) $(TEST_OUTPUT) - $(SILENCE)mkdir -p gcov - $(SILENCE)mv *.gcov gcov - $(SILENCE)mv gcov_* gcov - $(SILENCE)mkdir -p $(COVERAGE_DIR) - $(SILENCE)lcov -d $(CPPUTEST_OBJS_DIR) -c -o $(COVERAGE_DIR)/$(LCOV_INFO_FILE) -q --rc lcov_branch_coverage=1 - $(SILENCE)lcov --remove $(COVERAGE_DIR)/$(LCOV_INFO_FILE) $(LCOV_EXCLUDE_PATTERN) -o $(COVERAGE_DIR)/$(LCOV_INFO_FILE) - $(SILENCE)lcov --remove $(COVERAGE_DIR)/$(LCOV_INFO_FILE) "*CppUTest*" -o $(COVERAGE_DIR)/$(LCOV_INFO_FILE) - $(SILENCE)lcov --summary ./$(COVERAGE_DIR)/$(LCOV_INFO_FILE) &> $(COVERAGE_DIR)/$(LCOV_SUMMARY_FILE) - $(SILENCE)echo ansic:line:`grep -E -o "([0-9]*\.[0-9]+|[0-9]+)" $(COVERAGE_DIR)/$(LCOV_SUMMARY_FILE) | head -1` >> $(COVERAGE_DIR)/coverage-data.txt - $(SILENCE)genhtml -o $(COVERAGE_DIR) $(COVERAGE_DIR)/$(LCOV_INFO_FILE) -q --rc lcov_branch_coverage=1 - @echo "See gcov directory for details" - -.PHONEY: format -format: - $(CPPUTEST_HOME)/scripts/reformat.sh $(PROJECT_HOME_DIR) - -.PHONEY: debug -debug: - @echo - @echo "Target Source files:" - @$(call debug_print_list,$(SRC)) - @echo "Target Object files:" - @$(call debug_print_list,$(OBJ)) - @echo "Test Source files:" - @$(call debug_print_list,$(TEST_SRC)) - @echo "Test Object files:" - @$(call debug_print_list,$(TEST_OBJS)) - @echo "Mock Source files:" - @$(call debug_print_list,$(MOCKS_SRC)) - @echo "Mock Object files:" - @$(call debug_print_list,$(MOCKS_OBJS)) - @echo "All Input Dependency files:" - @$(call debug_print_list,$(DEP_FILES)) - @echo Stuff to clean: - @$(call debug_print_list,$(STUFF_TO_CLEAN)) - @echo Includes: - @$(call debug_print_list,$(INCLUDES)) - --include $(OTHER_MAKEFILE_TO_INCLUDE) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..c578cc0536 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Amazon Web Services + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 1b6cbea0cf..0000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,120 +0,0 @@ -############################################################################################################################# - - -Apache License -Version 2.0, January 2004 - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and - 2. You must cause any modified files to carry prominent notices stating that You changed the files; and - 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - - -############################################################################################################################# - - -Components are made available under the terms of the Eclipse Public License v1.0 -and Eclipse Distribution License v1.0 which accompany this distribution. - -The Eclipse Public License is available at - http://www.eclipse.org/legal/epl-v10.html -and the Eclipse Distribution License is available at - http://www.eclipse.org/org/documents/edl-v10.php. - - -############################################################################################################################# - - -Copyright (C) 2012, iSEC Partners. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -############################################################################################################################# - - _ _ ____ _ - Project ___| | | | _ \| | - / __| | | | |_) | | - | (__| |_| | _ <| |___ - \___|\___/|_| \_\_____| - -Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - -This software is licensed as described in the file COPYING, which -you should have received as part of this distribution. The terms -are also available at http://curl.haxx.se/docs/copyright.html. - -You may opt to use, copy, modify, merge, publish, distribute and/or sell -copies of the Software, and permit persons to whom the Software is -furnished to do so, under the terms of the COPYING file. - -This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY -KIND, either express or implied. - - -############################################################################################################################# - - -Copyright (c) 2010 Serge A. Zaitsev - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - -############################################################################################################################# - diff --git a/Makefile b/Makefile deleted file mode 100644 index 1b2355ee2a..0000000000 --- a/Makefile +++ /dev/null @@ -1,131 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -#Set this to @ to keep the makefile quiet -ifndef SILENCE - SILENCE = @ -endif - -CC = gcc -RM = rm - -DEBUG = - -#--- Inputs ----# -COMPONENT_NAME = IotSdkC - -ALL_TARGETS := build-cpputest -ALL_TARGETS_CLEAN := - -CPPUTEST_USE_EXTENSIONS = Y -CPP_PLATFORM = Gcc -CPPUTEST_CFLAGS += -std=gnu99 -CPPUTEST_LDFLAGS += -lpthread -CPPUTEST_CFLAGS += -D__USE_BSD -CPPUTEST_USE_GCOV = Y - -#IoT client directory -IOT_CLIENT_DIR = . - -APP_DIR = $(IOT_CLIENT_DIR)/tests/unit -APP_NAME = aws_iot_sdk_unit_tests -APP_SRC_FILES = $(shell find $(APP_DIR)/src -name '*.cpp') -APP_SRC_FILES = $(shell find $(APP_DIR)/src -name '*.c') -APP_INCLUDE_DIRS = -I $(APP_DIR)/include - -CPPUTEST_DIR = $(IOT_CLIENT_DIR)/external_libs/CppUTest - -# Provide paths for CppUTest to run Unit Tests otherwise build will fail -ifndef CPPUTEST_INCLUDE - CPPUTEST_INCLUDE = $(CPPUTEST_DIR)/include -endif - -ifndef CPPUTEST_BUILD_LIB - CPPUTEST_BUILD_LIB = $(CPPUTEST_DIR) -endif - -CPPUTEST_LDFLAGS += -ldl $(CPPUTEST_BUILD_LIB)/libCppUTest.a - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux - -#MbedTLS directory -TEMP_MBEDTLS_SRC_DIR = $(APP_DIR)/tls_mock -TLS_LIB_DIR = $(TEMP_MBEDTLS_SRC_DIR) -TLS_INCLUDE_DIR = -I $(TEMP_MBEDTLS_SRC_DIR) - -# Logging level control -#LOG_FLAGS += -DENABLE_IOT_DEBUG -#LOG_FLAGS += -DENABLE_IOT_TRACE -#LOG_FLAGS += -DENABLE_IOT_INFO -#LOG_FLAGS += -DENABLE_IOT_WARN -#LOG_FLAGS += -DENABLE_IOT_ERROR -COMPILER_FLAGS += $(LOG_FLAGS) - -EXTERNAL_LIBS += -L$(CPPUTEST_BUILD_LIB) - -#IoT client directory -PLATFORM_COMMON_DIR = $(PLATFORM_DIR)/common - -IOT_INCLUDE_DIRS = -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn - -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn/ -name '*.c') - -#Aggregate all include and src directories -INCLUDE_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_DIRS += $(APP_INCLUDE_DIRS) -INCLUDE_DIRS += $(TLS_INCLUDE_DIR) -INCLUDE_DIRS += $(CPPUTEST_INCLUDE) - -TEST_SRC_DIRS = $(APP_DIR)/src - -SRC_FILES += $(APP_SRC_FILES) -SRC_FILES += $(IOT_SRC_FILES) - -COMPILER_FLAGS += -g -COMPILER_FLAGS += $(LOG_FLAGS) -PRE_MAKE_CMDS = cd $(CPPUTEST_DIR) && -PRE_MAKE_CMDS += cmake CMakeLists.txt && -PRE_MAKE_CMDS += make && -PRE_MAKE_CMDS += cd - && -PRE_MAKE_CMDS += pwd && -PRE_MAKE_CMDS += cp -f $(CPPUTEST_DIR)/src/CppUTest/libCppUTest.a $(CPPUTEST_DIR)/libCppUTest.a && -PRE_MAKE_CMDS += cp -f $(CPPUTEST_DIR)/src/CppUTestExt/libCppUTestExt.a $(CPPUTEST_DIR)/libCppUTestExt.a - -# Using TLS Mock for running Unit Tests -MOCKS_SRC += $(APP_DIR)/tls_mock/aws_iot_tests_unit_mock_tls_params.c -MOCKS_SRC += $(APP_DIR)/tls_mock/aws_iot_tests_unit_mock_tls.c - -ISYSTEM_HEADERS += $(IOT_ISYSTEM_HEADERS) -CPPUTEST_CPPFLAGS += $(ISYSTEM_HEADERS) -CPPUTEST_CPPFLAGS += $(LOG_FLAGS) - -LCOV_EXCLUDE_PATTERN = "tests/unit/*" -LCOV_EXCLUDE_PATTERN += "tests/integration/*" -LCOV_EXCLUDE_PATTERN += "external_libs/*" - -#use this section for running a specific group of tests, comment this to run all -#ONLY FOR TESTING PURPOSE -#COMMAND_LINE_ARGUMENTS += -g CommonTests -#COMMAND_LINE_ARGUMENTS += -v - -build-cpputest: - $(PRE_MAKE_CMDS) - -include CppUTestMakefileWorker.mk - -.PHONY: run-unit-tests -run-unit-tests: $(ALL_TARGETS) - @echo $(ALL_TARGETS) - -.PHONY: clean -clean: - $(MAKE) -C $(CPPUTEST_DIR) clean - $(RM) -rf build_output - $(RM) -rf gcov - $(RM) -rf objs - $(RM) -rf testLibs diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100644 index c3f856f111..0000000000 --- a/NOTICE.txt +++ /dev/null @@ -1,16 +0,0 @@ -AWS C SDK for Internet of Things Service -Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - -This product includes software developed by -Amazon Inc (http://www.amazon.com/). - -********************** -THIRD PARTY COMPONENTS -********************** -This software includes third party software subject to the following licensing: -- Embedded C MQTT Client - From the Eclipse Paho Project - EPL v1.0 -- mbedTLS (external library, included in tarball or downloaded separately) - Apache 2.0 -- jsmn (JSON Parsing) - MIT -- cURL (hostname verification) - MIT - -The licenses for these third party components are included in LICENSE.txt diff --git a/PortingGuide.md b/PortingGuide.md deleted file mode 100644 index 2a87a28088..0000000000 --- a/PortingGuide.md +++ /dev/null @@ -1,150 +0,0 @@ -# Porting Guide - -## Scope -The scope of this document is to provide instructions to modify the provided source files and functions in this SDK to run in a variety of embedded C–based environments (e.g. real-time OS, embedded Linux) and to be adjusted to use a specific TLS implementation as available with specific hardware platforms. - -## Contents of the SDK - -The C-code files of this SDK are delivered via the following directory structure (see comment behind folder name for an explanation of its content). - -Current SDK Directory Layout (mbedTLS) - -|--`certs` (Private key, device certificate and Root CA)
-|--`docs` (Developer guide & API documentation)
-|--`external_libs` (external libraries - jsmn, mbedTLS)
-|--`include` (Header files of the AWS IoT device SDK)
-|--`src` (Source files of the AWS IoT device SDK)
-|--`platform` (Platform specific files)
-|--`samples` (Samples including makefiles for building on mbedTLS)
-|--`tests` (Tests for verifying SDK is functioning as expected)
- -All makefiles in this SDK were configured using the documented folder structure above, so moving or renaming folders will require modifications to makefiles. - -## Explanation of folders and their content - - * `certs` : This directory is initially empty and will need to contain the private key, the client certificate and the root CA. The client certificate and private key can be downloaded from the AWS IoT console or be created using the AWS CLI commands. The root CA can be downloaded from [Symantec](https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem). - - * `docs` : SDK API and file documentation. - - * `external_libs` : The mbedTLS and jsmn source files. The jsmn source files are always present. The mbedTLS source files are only included when downloading the tarball version of the SDK. - - * `include` : This directory contains the header files that an application using the SDK needs to include. - - * `src` : This directory contains the SDK source code including the MQTT library, device shadow code and utilities. - - * `platform` : Platform specific files for timer, TLS and threading layers. Includes a reference implementation for the linux using mbedTLS and pthread. - - * `samples` : This directory contains sample applications as well as their makefiles. The samples include a simple MQTT example which publishes and subscribes to the AWS IoT service as well as a device shadow example that shows example usage of the device shadow functionality. - - * `tests` : Contains tests for verifying SDK functionality. For further details please check the readme file included with the tests [here](https://github.com/aws/aws-iot-device-sdk-embedded-C/blob/master/tests/README.md/). - -## Integrating the SDK into your environment - -This section explains the API calls that need to be implemented in order for the Device SDK to run on your platform. The SDK interfaces follow the driver model where only prototypes are defined by the Device SDK itself while the implementation is delegated to the user of the SDK to adjust it to the platform in use. The following sections list the needed functionality for the device SDK to run successfully on any given platform. - -### Timer Functions - -A timer implementation is necessary to handle request timeouts (sending MQTT connect, subscribe, etc. commands) as well as connection maintenance (MQTT keep-alive pings). Timers need millisecond resolution and are polled for expiration so these can be implemented using a "milliseconds since startup" free-running counter if desired. In the synchronous sample provided with this SDK only one command will be "in flight" at one point in time plus the client's ping timer. - -Define the `Timer` Struct as in `timer_platform.h` - -`void init_timer(Timer *);` -init_timer - A timer structure is initialized to a clean state. - -`bool has_timer_expired(Timer *);` -has_timer_expired - a polling function to determine if the timer has expired. - -`void countdown_ms(Timer *, uint32_t);` -countdown_ms - set the timer to expire in x milliseconds and start the timer. - -`void countdown_sec(Timer *, uint32_t);` -countdown_sec - set the timer to expire in x seconds and start the timer. - -`uint32_t left_ms(Timer *);` -left_ms - query time in milliseconds left on the timer. - -`void delay(unsigned milliseconds)` -delay - sleep for the specified number of milliseconds. - - -### Network Functions - -In order for the MQTT client stack to be able to communicate via the TCP/IP network protocol stack using a mutually authenticated TLS connection, the following API calls need to be implemented for your platform. - -For additional details about API parameters refer to the [API documentation](http://aws-iot-device-sdk-embedded-c-docs.s3-website-us-east-1.amazonaws.com/index.html). - -Define the `TLSDataParams` Struct as in `network_platform.h` -This is used for data specific to the TLS library being used. - -`IoT_Error_t iot_tls_init(Network *pNetwork, char *pRootCALocation, char *pDeviceCertLocation, - char *pDevicePrivateKeyLocation, char *pDestinationURL, - uint16_t DestinationPort, uint32_t timeout_ms, bool ServerVerificationFlag);` -Initialize the network client / structure. - -`IoT_Error_t iot_tls_connect(Network *pNetwork, TLSConnectParams *TLSParams);` -Create a TLS TCP socket to the configure address using the credentials provided via the NewNetwork API call. This will include setting up certificate locations / arrays. - - -`IoT_Error_t iot_tls_write(Network*, unsigned char*, size_t, Timer *, size_t *);` -Write to the TLS network buffer. - -`IoT_Error_t iot_tls_read(Network*, unsigned char*, size_t, Timer *, size_t *);` -Read from the TLS network buffer. - -`IoT_Error_t iot_tls_disconnect(Network *pNetwork);` -Disconnect API - -`IoT_Error_t iot_tls_destroy(Network *pNetwork);` -Clean up the connection - -`IoT_Error_t iot_tls_is_connected(Network *pNetwork);` -Check if the TLS layer is still connected - -The TLS library generally provides the API for the underlying TCP socket. - - -### Threading Functions - -The MQTT client uses a state machine to control operations in multi-threaded situations. However it requires a mutex implementation to guarantee thread safety. This is not required in situations where thread safety is not important and it is disabled by default. The _ENABLE_THREAD_SUPPORT_ macro needs to be defined in aws_iot_config.h to enable this layer. You will also need to add the -lpthread linker flag for the compiler if you are using the provided reference implementation. - -For additional details about API parameters refer to the [API documentation](http://aws-iot-device-sdk-embedded-c-docs.s3-website-us-east-1.amazonaws.com/index.html). - -Define the `IoT_Mutex_t` Struct as in `threads_platform.h` -This is used for data specific to the TLS library being used. - -`IoT_Error_t aws_iot_thread_mutex_init(IoT_Mutex_t *);` -Initialize the mutex provided as argument. - -`IoT_Error_t aws_iot_thread_mutex_lock(IoT_Mutex_t *);` -Lock the mutex provided as argument - -`IoT_Error_t aws_iot_thread_mutex_unlock(IoT_Mutex_t *);` -Unlock the mutex provided as argument. - -`IoT_Error_t aws_iot_thread_mutex_destroy(IoT_Mutex_t *);` -Destroy the mutex provided as argument. - -The threading layer provides the implementation of mutexes used for thread-safe operations. - -### Sample Porting: - -Marvell has ported the SDK for their development boards. [These](https://github.com/marvell-iot/aws_starter_sdk/tree/master/sdk/external/aws_iot/platform/wmsdk) files are example implementations of the above mentioned functions. -This provides a port of the timer and network layer. The threading layer is not a part of this port. - -## Time source for certificate validation - -As part of the TLS handshake the device (client) needs to validate the server certificate which includes validation of the certificate lifetime requiring that the device is aware of the actual time. Devices should be equipped with a real time clock or should be able to obtain the current time via NTP. Bypassing validation of the lifetime of a certificate is not recommended as it exposes the device to a security vulnerability, as it will still accept server certificates even when they have already has_timer_expired. - -## Integration into operating system -### Single-Threaded implementation - -The single threaded implementation implies that the sample application code (SDK + MQTT client) is called periodically by the firmware application running on the main thread. This is done by calling the function `aws_iot_mqtt_yield` (in the simple pub-sub example) and by calling `aws_iot_shadow_yield()` (in the device shadow example). In both cases the keep-alive time is set to 10 seconds. This means that the yield functions need to be called at a minimum frequency of once every 10 seconds. Note however that the `iot_mqtt_yield()` function takes care of reading incoming MQTT messages from the IoT service as well and hence should be called more frequently depending on the timing requirements of an application. All incoming messages can only be processed at the frequency at which `yield` is called. - -### Multi-Threaded implementation - -In the simple multi-threaded case the `yield` function can be moved to a background thread. Ensure this task runs at the frequency described above. In this case, depending on the OS mechanism, a message queue or mailbox could be used to proxy incoming MQTT messages from the callback to the worker task responsible for responding to or dispatching messages. A similar mechanism could be employed to queue publish messages from threads into a publish queue that are processed by a publishing task. Ensure the threading layer is enabled as the library is not thread safe otherwise. -There is a validation test for the multi-threaded implementation that can be found with the integration tests. You can find further details in the Readme for the integration tests [here](https://github.com/aws/aws-iot-device-sdk-embedded-C/blob/master/tests/integration/README.md). We have run the validation test with 10 threads sending 500 messages each and verified to be working fine. It can be used as a reference testing application to validate whether your use case will work with multi-threading enabled. - -## Sample applications - -The sample apps in this SDK provide a working implementation for mbedTLS. They use a reference implementation for linux provided with the SDK. Threading layer is enabled in the subscribe publish sample. diff --git a/README.md b/README.md index 3c2859d9ef..4a9b953756 100644 --- a/README.md +++ b/README.md @@ -1,287 +1,64 @@ - -[![Build Status](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C.svg?branch=master)](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) - - - Coverity Scan Build Status - - -**We have released version 4.0.0 beta 1 of this SDK on the [v4_beta](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta) branch and encourage everyone to give it a try.** - -Version 4 is a new design, and therefore **NOT** backwards compatible with version 3.0.1. We will continue to fix bugs in v3.0.1 even after v4.0.0 is released, but we may not add new features to v3.0.1. - -Please be aware that v4 beta may have bugs and performance issues. Additionally, there are currently missing features compared to v3.0.1. See the [README](https://github.com/aws/aws-iot-device-sdk-embedded-C/blob/v4_beta/README.md) on the v4_beta branch for more information. - -## Branches - -### Master branch -The master branch will now contain bug fixes/features that have been minimally tested to ensure nothing major is broken. The current version on the master branch is v3.0.1. Eventually, we will move v4.0.0 to the master branch and move v3.0.1 to a legacy branch. - -### Release branch -The release branch will contain new releases for the SDK that have been tested thoroughly on all supported platforms. Please ensure that you are tracking the release branch for all production work. The current version on the release branch is v3.0.1. Eventually, we will move v4.0.0 to the release branch and move v3.0.1 to a legacy branch. - -## Overview - -The AWS IoT device SDK for embedded C is a collection of C source files which can be used in embedded applications to securely connect to the [AWS IoT platform](http://docs.aws.amazon.com/iot/latest/developerguide/what-is-aws-iot.html). It includes transport clients **MQTT**, **TLS** implementations and examples for their use. It also supports AWS IoT specific features such as **Thing Shadow**. It is distributed in source form and intended to be built into customer firmware along with application code, other libraries and RTOS. For additional information about porting the Device SDK for embedded C onto additional platforms please refer to the [PortingGuide](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/PortingGuide.md/). - -## Features -The Device SDK simplifies access to the Pub/Sub functionality of the AWS IoT broker via MQTT and provide APIs to interact with Thing Shadows. The SDK has been tested to work with the AWS IoT platform to ensure best interoperability of a device with the AWS IoT platform. - -### MQTT Connection -The Device SDK provides functionality to create and maintain a mutually authenticated TLS connection over which it runs MQTT. This connection is used for any further publish operations and allow for subscribing to MQTT topics which will call a configurable callback function when these topics are received. - -### Thing Shadow -The Device SDK implements the specific protocol for Thing Shadows to retrieve, update and delete Thing Shadows adhering to the protocol that is implemented to ensure correct versioning and support for client tokens. It abstracts the necessary MQTT topic subscriptions by automatically subscribing to and unsubscribing from the reserved topics as needed for each API call. Inbound state change requests are automatically signalled via a configurable callback. - -### Jobs -The Device SDK implements features to facilitate use of the AWS Jobs service. The Jobs service can be used for device management tasks such as updating program files, rotating device certificates, or running other maintenance tasks such are restoring device settings or restarting devices. - -## Design Goals of this SDK -The embedded C SDK was specifically designed for resource constrained devices (running on micro-controllers and RTOS). - -Primary aspects are: - * Flexibility in picking and choosing functionality (reduce memory footprint) - * Static memory only (no malloc’s) - * Configurable resource usage(JSON tokens, MQTT subscription handlers, etc…) - * Can be ported to a different RTOS, uses wrappers for OS specific functions - -For more information on the Architecture of the SDK refer [here](http://aws-iot-device-sdk-embedded-c-docs.s3-website-us-east-1.amazonaws.com/index.html) - -## Collection of Metrics -Beginning with Release v2.2.0 of the SDK, AWS collects usage metrics indicating which language and version of the SDK is being used. This allows us to prioritize our resources towards addressing issues faster in SDKs that see the most and is an important data point. However, we do understand that not all customers would want to report this data by default. In that case, the sending of usage metrics can be easily disabled by the user by setting the `DISABLE_METRICS` flag to true in the -`aws_iot_config.h` for each application. - -## How to get started ? -Ensure you understand the AWS IoT platform and create the necessary certificates and policies. For more information on the AWS IoT platform please visit the [AWS IoT developer guide](http://docs.aws.amazon.com/iot/latest/developerguide/iot-security-identity.html). - -In order to quickly get started with the AWS IoT platform, we have ported the SDK for POSIX type Operating Systems like Ubuntu, OS X and RHEL. The SDK is configured for the mbedTLS library and can be built out of the box with *GCC* using *make utility*. You'll need to download mbedTLS from the official ARMmbed repository. We recommend that you pick the latest version in order to have up-to-date security fixes. - -## Installation -This section explains the individual steps to retrieve the necessary files and be able to build your first application using the AWS IoT device SDK for embedded C. - -Steps: - - * Create a directory to hold your application e.g. (/home//aws_iot/my_app) - * Change directory to this new directory - * Download the SDK to device and place in the newly created directory - * Expand the tarball (tar -xf ). This will create the below directories: - * `certs` - TLS certificates directory - * `docs` - SDK API and file documentation. This folder is not present on GitHub. You can access the documentation [here](http://aws-iot-device-sdk-embedded-c-docs.s3-website-us-east-1.amazonaws.com/index.html) - * `external_libs` - The mbedTLS and jsmn source files - * `include` - The AWS IoT SDK header files - * `platform` - Platform specific files for timer, TLS and threading layers - * `samples` - The sample applications - * `src` - The AWS IoT SDK source files - * `tests` - Contains tests for verifying that the SDK is functioning as expected. More information can be found [here](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/tests/README.md) - * View further information on how to use the SDK in the Readme file for samples that can be found [here](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/samples/README.md) - -Also, for a guided example on getting started with the Thing Shadow, visit the AWS IoT Console's [Interactive Guide](https://console.aws.amazon.com/iot). - -## Porting to different platforms -As Embedded devices run on different Real Time Operating Systems and TCP/IP stacks, it is one of the important design goals for the Device SDK to keep it portable. Please refer to the [porting guide](https://github.com/aws/aws-iot-device-sdk-embedded-C/blob/master/PortingGuide.md) to get more information on how to make this SDK run on your devices (i.e. micro-controllers). - -## Migrating from 1.x to 2.x -The 2.x branch makes several changes to the SDK. This section provides information on what changes will be required in the client application for migrating from v1.x to 2.x. - - * The first change is in the folder structure. Client applications using the SDK now need to keep only the certs, external_libs, include, src and platform folder in their application. The folder descriptions can be found above - * All the SDK headers are in the `include` folder. These need to be added to the makefile as include directories - * The source files are in the `src` folder. These need to be added to the makefile as one of the source directories - * Similar to 1.x, the platform folder contains the platform specific headers and source files. These need to be added to the makefile as well - * The `platform/threading` folder only needs to be added if multi-threading is required, and the `_ENABLE_THREAD_SUPPORT_` macro is defined in config - * The list below provides a mapping for migrating from the major APIs used in 1.x to the new APIs: - - | Description | 1.x | 2.x | - | :---------- | :-- | :-- | - | Initializing the client | ```void aws_iot_mqtt_init(MQTTClient_t *pClient);``` | ```IoT_Error_t aws_iot_mqtt_init(AWS_IoT_Client *pClient, IoT_Client_Init_Params *pInitParams);``` | - | Connect | ```IoT_Error_t aws_iot_mqtt_connect(MQTTConnectParams *pParams);``` | ```IoT_Error_t aws_iot_mqtt_connect(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pConnectParams);``` | - | Subscribe | ```IoT_Error_t aws_iot_mqtt_subscribe(MQTTSubscribeParams *pParams);``` | ```IoT_Error_t aws_iot_mqtt_subscribe(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen, QoS qos, pApplicationHandler_t pApplicationHandler, void *pApplicationHandlerData);``` | - | Unsubscribe | ```IoT_Error_t aws_iot_mqtt_unsubscribe(char *pTopic);``` | ```IoT_Error_t aws_iot_mqtt_unsubscribe(AWS_IoT_Client *pClient, const char *pTopicFilter, uint16_t topicFilterLen);``` | - | Yield | ```IoT_Error_t aws_iot_mqtt_yield(int timeout);``` | ```IoT_Error_t aws_iot_mqtt_yield(AWS_IoT_Client *pClient, uint32_t timeout_ms);``` | - | Publish | ```IoT_Error_t aws_iot_mqtt_publish(MQTTPublishParams *pParams);``` | ```IoT_Error_t aws_iot_mqtt_publish(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen, IoT_Publish_Message_Params *pParams);``` | - | Disconnect | ```IoT_Error_t aws_iot_mqtt_disconnect(void);``` | ```IoT_Error_t aws_iot_mqtt_disconnect(AWS_IoT_Client *pClient);``` | - -You can find more information on how to use the new APIs in the Readme file for samples that can be found [here](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/samples/README.md) - -## Migrating from 2.x to 3.x -AWS IoT Device SDK for Embedded C v3.0.0 fixes two bugs (see #152 and #155) that create a potential buffer overflows. This version is not backward compatible with previous versions, so users will need to recompile their applications with the new version. - -Users of AWS IoT Device Shadows or Json utility functions such as `extractClientToken`, `emptyJsonWithClientToken`, `isJsonValidAndParse` and `isReceivedJsonValid` are encouraged to upgrade to version v3.0.0. For users who cannot upgrade, review all parts of your solution where user input can be sent to the device, and ensure sufficient authorization of these operations is enforced. - -Details of the required changes to public functions and data structures are shown below: - -### Changes in the `jsonStruct` data structure: -The member `dataLength` has been added to struct `jsonStruct`, which is declared in [include/aws_iot_shadow_json_data.h](include/aws_iot_shadow_json_data.h#L60). - -```c -struct jsonStruct { - const char * pKey; - void * pData; - size_t dataLength; - JsonPrimitiveType type; - JsonStructCallback_t cb; -}; -``` - -The size of the buffer `pData` must now be specified by `dataLength`. **Failure to do so may result in undefined behavior**. Below are examples of the code changes required to use the new jsonStruct. - -With a primitive data type, such as `int32_t`: - -```c -… -jsonStruct_t exampleJsonStruct; -int32_t value = 0L; - -/* Set the members of exampleJsonStruct. */ -exampleJsonStruct.pKey = “exampleKey”; -exampleJsonStruct.pData = &value; -exampleJsonStruct.type = SHADOW_JSON_INT32; -exampleJsonStruct.cb = exampleCallback; - -/* Register a delta callback using example JsonStruct. */ -aws_iot_shadow_register_delta(&mqttClient, &exampleJsonStruct); -… -``` - -Version 3.0.0 will require the following code: - -```c -… -jsonStruct_t exampleJsonStruct; -int32_t value = 0L; - -/* Set the members of exampleJsonStruct. */ -exampleJsonStruct.pKey = “exampleKey”; -exampleJsonStruct.pData = &value; -exampleJsonStruct.dataLength = sizeof(int32_t); /* sizeof(value) also OK.*/ -exampleJsonStruct.type = SHADOW_JSON_INT32; -exampleJsonStruct.cb = exampleCallback; - -/* Register a delta callback using example JsonStruct. */ -aws_iot_shadow_register_delta(&mqttClient, &exampleJsonStruct); -… -``` - -With a string, versions up to v2.3.0 would require the following code: - -```c -… -jsonStruct_t exampleJsonStruct; -char stringBuffer[SIZE_OF_BUFFER]; -/* Set the members of exampleJsonStruct. */ -exampleJsonStruct.pKey = “exampleKey”; -exampleJsonStruct.pData = stringBuffer; -exampleJsonStruct.type = SHADOW_JSON_STRING; -exampleJsonStruct.cb = exampleCallback; -/* Register a delta callback using example JsonStruct. */ -aws_iot_shadow_register_delta(&mqttClient, &exampleJsonStruct); -… -``` - -Version 3.0.0 will require the following code: - -```c -… -jsonStruct_t exampleJsonStruct; -char stringBuffer[SIZE_OF_BUFFER]; -/* Set the members of exampleJsonStruct. */ -exampleJsonStruct.pKey = “exampleKey”; -exampleJsonStruct.pData = stringBuffer; -exampleJsonStruct.dataLength = SIZE_OF_BUFFER; -exampleJsonStruct.type = SHADOW_JSON_STRING; -exampleJsonStruct.cb = exampleCallback; -/* Register a delta callback using example JsonStruct. */ -aws_iot_shadow_register_delta(&mqttClient, &exampleJsonStruct); -… -``` - -### Changes in parseStringValue: -The function `parseStringValue`, declared in [include/aws_iot_json_utils.h](include/aws_iot_json_utils.h#L179) and implemented in [src/aws_iot_json_utils.c](src/aws_iot_json_utils.c#L184), now requires the inclusion of a buffer length. Its new function signature is: - -```c -IoT_Error_t parseStringValue(char *buf, size_t bufLen, const char *jsonString, jsmntok_t *token); -``` - -Below is an example of code changes required to use the new parseStringValue. - -With up to version v2.3.0: - -```c -… -char* jsonString = “…”; -jsmntok_t jsmnTokens[NUMBER_OF_JSMN_TOKENS]; -char stringBuffer[SIZE_OF_BUFFER]; -parseStringValue(stringBuffer, jsonString, jsmnTokens); -… -``` - -Version 3.0.0 will require the following code: - -```c -… -char* jsonString = “…”; -jsmntok_t jsmnTokens[NUMBER_OF_JSMN_TOKENS]; -char stringBuffer[SIZE_OF_BUFFER]; -parseStringValue(stringBuffer, SIZE_OF_BUFFER, jsonString, jsmnTokens); -… -``` - -### Changes to functions intended for internal usage: -Version 3.0.0 changes the signature of four functions intended for internal usage. The new signatures explicitly carry the information for the size of the buffer or JSON document passed as a parameter to the functions. Users of the SDK may need to change their code and recompile to ingest the changes. We report the old and new signatures below. - -#### Old signatures: - -```c -bool extractClientToken(const char *pJsonDocument, char *pExtractedClientToken); - -static void emptyJsonWithClientToken(char *pBuffer); - -bool isJsonValidAndParse(const char *pJsonDocument, void *pJsonHandler, int32_t *pTokenCount); - -bool isReceivedJsonValid(const char *pJsonDocument); -``` - -#### New signatures: - -```c -bool extractClientToken(const char *pJsonDocument, size_t jsonSize, char *pExtractedClientToken, size_t clientTokenSize); - -static void emptyJsonWithClientToken(char *pBuffer, size_t bufferSize); - -bool isJsonValidAndParse(const char *pJsonDocument, size_t jsonSize, void *pJsonHandler, int32_t *pTokenCount); - -bool isReceivedJsonValid(const char *pJsonDocument, size_t jsonSize); -``` - -## Resources -[API Documentation](http://aws-iot-device-sdk-embedded-c-docs.s3-website-us-east-1.amazonaws.com/index.html) - -[MQTT 3.1.1 Spec](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html) - -## Support -If you have any technical questions about AWS IoT Device SDK, use the [AWS IoT forum](https://forums.aws.amazon.com/forum.jspa?forumID=210). -For any other questions on AWS IoT, contact [AWS Support](https://aws.amazon.com/contact-us/). - -## Sample APIs -Connecting to the AWS IoT MQTT platform - -``` -AWS_IoT_Client client; -rc = aws_iot_mqtt_init(&client, &iotInitParams); -rc = aws_iot_mqtt_connect(&client, &iotConnectParams); -``` - - -Subscribe to a topic - -``` -AWS_IoT_Client client; -rc = aws_iot_mqtt_subscribe(&client, "sdkTest/sub", 11, QOS0, iot_subscribe_callback_handler, NULL); -``` - - -Update Thing Shadow from a device - -``` -rc = aws_iot_shadow_update(&mqttClient, AWS_IOT_MY_THING_NAME, pJsonDocumentBuffer, ShadowUpdateStatusCallback, - pCallbackContext, TIMEOUT_4SEC, persistenSubscription); -``` +# AWS IoT Device SDK C v4.0.0 Beta 1 + +**[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/index.html)** + +The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be build into firmware along with application code. + +This library, currently a Beta release, will supersede both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. + +## Building and Running Demos + +This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. **As of now, this Beta release only builds on Linux.** + +### Prerequisites +- Linux system with support for POSIX threads and timers. +- CMake 3.5.0 or later. +- OpenSSL development libraries and header files, version 1.0.2g or later. This is usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + +### Build Steps +1. Complete the first 6 steps in the guide [Getting Started with AWS IoT](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html). The guide mentions the AWS IoT Button, but you do not need one to use this SDK. + 1. [Sign in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) + 2. [Register a Device in the Registry](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html) + 3. [Create and Activate a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) + 4. [Create an AWS IoT Policy](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-policy.html) + 5. [Attach an AWS IoT Policy to a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/attach-policy-to-certificate.html) + 6. [Attach a Certificate to a Thing](https://docs.aws.amazon.com/iot/latest/developerguide/attach-cert-thing.html) +2. *Optional:* Set the following `#define` in [aws_iot_demo_config.h](demos/aws_iot_demo_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. + - Set `AWS_IOT_DEMO_THING_NAME` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. + - Set `AWS_IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. + - Set `AWS_IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. + - Set `AWS_IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. + - Set `AWS_IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. +3. Make a build directory in the SDK's root directory and `cd` into it. + ```sh + mkdir build + cd build + ``` +4. Run CMake, then `make`. This builds the demo executables and places them in `build/bin`. + ```sh + cmake .. + make + ``` + +## Beta Features + +This Beta library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. In addition, it provides the following new features: +- Asynchronous API for both MQTT and Thing Shadow. +- Multithreaded API by default (removed the `yield` function). +- More efficient platform layer (especially timers). +- Complete separation of MQTT and network stack, allowing MQTT to run over any network stack. +- Configurable memory allocation (static-only or dynamic). Memory allocation functions may also be set by the user. +- Network stack based on OpenSSL. +- MQTT persistent session support. + +Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: +- Documentation and demo for Shadow library is incomplete. +- Auto-reconnect for MQTT connections. +- mbedTLS network stack. +- Shadow JSON document generator. +- Jobs API. +- Build support for Apple macOS. + +## License + +This library is licensed under the [MIT License](LICENSE). diff --git a/certs/README.txt b/certs/README.txt deleted file mode 100644 index 8df3d02085..0000000000 --- a/certs/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Copy certificates for running the samples and tests provided with the SDK into this directory -# Certificates can be created and downloaded from the AWS IoT Console -# The IoT Client takes the full path of the certificates as an input parameter while initializing -# This is the default folder for the certificates only for samples and tests. A different path can be specified if required. \ No newline at end of file diff --git a/demos/aws_iot_demo.h b/demos/aws_iot_demo.h new file mode 100644 index 0000000000..d541decff3 --- /dev/null +++ b/demos/aws_iot_demo.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo.h + * @brief Declares the platform-independent demo functions. + */ + +#ifndef _AWS_IOT_DEMO_H_ +#define _AWS_IOT_DEMO_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/* Configure logs for the demos. */ +#ifdef AWS_IOT_LOG_LEVEL_DEMO + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEMO +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "DEMO" ) +#include "aws_iot_logging_setup.h" + +/*----------------------------- Demo functions ------------------------------*/ + +/* See aws_iot_demo_mqtt.c for documentation of this function. */ +int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, + const char * const pClientIdentifier, + AwsIotMqttConnection_t * const pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface ); + +/* See aws_iot_demo_shadow.c for documentation of this function. */ +int AwsIotDemo_RunShadowDemo( const char * const pThingName, + AwsIotMqttConnection_t * const pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface ); + +#endif /* ifndef _AWS_IOT_DEMO_H_ */ diff --git a/demos/aws_iot_demo_config.h b/demos/aws_iot_demo_config.h new file mode 100644 index 0000000000..133dcdee95 --- /dev/null +++ b/demos/aws_iot_demo_config.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* This file contains configuration settings for the demos. */ + +#ifndef _AWS_IOT_DEMO_CONFIG_H_ +#define _AWS_IOT_DEMO_CONFIG_H_ + +/* Server endpoints used for the demos. May be overridden with command line + * options. */ +#define AWS_IOT_DEMO_SECURED_CONNECTION ( true ) +#define AWS_IOT_DEMO_SERVER "" +#define AWS_IOT_DEMO_PORT ( 443 ) + +/* Credential paths. May be overridden with command line options. */ +#define AWS_IOT_DEMO_ROOT_CA "" +#define AWS_IOT_DEMO_CLIENT_CERT "" +#define AWS_IOT_DEMO_PRIVATE_KEY "" + +/* Default Thing Name to use for AWS IoT demos. */ +/* #define AWS_IOT_DEMO_THING_NAME "" */ + +/* MQTT demo configuration. */ +/* #define AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER "" */ +#define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) +#define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) + +/* Enable asserts in queues and MQTT. */ +#define AWS_IOT_QUEUE_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_MQTT_ENABLE_ASSERTS ( 1 ) + +/* Library logging configuration. */ +#define AWS_IOT_LOG_LEVEL_GLOBAL AWS_IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_PLATFORM AWS_IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_NETWORK AWS_IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_MQTT AWS_IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_SHADOW AWS_IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_DEMO AWS_IOT_LOG_INFO + +#endif /* ifndef _AWS_IOT_DEMO_CONFIG_H_ */ diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c new file mode 100644 index 0000000000..82ce61e810 --- /dev/null +++ b/demos/aws_iot_demo_mqtt.c @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo_mqtt.c + * @brief Demonstrates usage of the MQTT library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Common demo include. */ +#include "aws_iot_demo.h" + +/* Platform layer includes. */ +#include "platform/aws_iot_clock.h" +#include "platform/aws_iot_threads.h" + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int snprintf( char *, + size_t, + const char *, + ... ); +/** @endcond */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration settings. + */ +#ifndef AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE + #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) +#endif +#ifndef AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT + #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) +#endif +/** @endcond */ + +/* Validate MQTT demo configuration settings. */ +#if AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE <= 0 + #error "AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE cannot be 0 or negative." +#endif +#if AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT <= 0 + #error "AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT cannot be 0 or negative." +#endif + +/** + * @brief The first characters in the client identifier. A timestamp is appended + * to this prefix to create a unique client identifer. + * + * This prefix is also used to generate topic names and topic filters used in this + * demo. + */ +#define _CLIENT_IDENTIFIER_PREFIX "awsiotdemo" + +/** + * @brief The longest client identifier that an MQTT server must accept (as defined + * by the MQTT 3.1.1 spec) is 23 characters. + */ +#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) + +/** + * @brief The keep-alive interval used for this demo. + * + * An MQTT ping request will be sent periodically at this interval. + */ +#define _KEEP_ALIVE_SECONDS ( 60 ) + +/** + * @brief The timeout for MQTT operations in this demo. + */ +#define _MQTT_TIMEOUT_MS ( 5000 ) + +/** + * @brief The Last Will and Testament topic name in this demo. + * + * The MQTT server will publish a message to this topic name if this client is + * unexpectedly disconnected. + */ +#define _WILL_TOPIC_NAME _CLIENT_IDENTIFIER_PREFIX "/will" + +/** + * @brief The length of #_WILL_TOPIC_NAME. + */ +#define _WILL_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _WILL_TOPIC_NAME ) - 1 ) ) + +/** + * @brief The message to publish to #_WILL_TOPIC_NAME. + */ +#define _WILL_MESSAGE "MQTT demo unexpectedly disconnected." + +/** + * @brief The length of #_WILL_MESSAGE. + */ +#define _WILL_MESSAGE_LENGTH ( ( size_t ) ( sizeof( _WILL_MESSAGE ) - 1 ) ) + +/** + * @brief How many topic filters will be used in this demo. + */ +#define _TOPIC_FILTER_COUNT ( 4 ) + +/** + * @brief The length of each topic filter. + * + * For convenience, all topic filters are the same length. + */ +#define _TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( _CLIENT_IDENTIFIER_PREFIX "/topic/1" ) - 1 ) ) + +/** + * @brief Format string of the PUBLISH messages in this demo. + */ +#define _PUBLISH_PAYLOAD_FORMAT "Hello world %d!" + +/** + * @brief Size of the buffer that holds the PUBLISH messages in this demo. + */ +#define _PUBLISH_PAYLOAD_BUFFER_LENGTH ( sizeof( _PUBLISH_PAYLOAD_FORMAT ) + 2 ) + +/** + * @brief The maximum number of times each PUBLISH in this demo will be retried. + */ +#define _PUBLISH_RETRY_LIMIT ( 10 ) + +/** + * @brief A PUBLISH message is retried if no response is received within this + * time. + */ +#define _PUBLISH_RETRY_MS ( 1000 ) + +/** + * @brief The topic name on which acknowledgement messages for incoming publishes + * should be published. + */ +#define _ACKNOWLEDGEMENT_TOPIC_NAME _CLIENT_IDENTIFIER_PREFIX "/acknowledgements" + +/** + * @brief The length of #_ACKNOWLEDGEMENT_TOPIC_NAME. + */ +#define _ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _ACKNOWLEDGEMENT_TOPIC_NAME ) - 1 ) ) + +/** + * @brief Format string of PUBLISH acknowledgement messages in this demo. + */ +#define _ACKNOWLEDGEMENT_MESSAGE_FORMAT "Client has received PUBLISH %.*s from server." + +/** + * @brief Size of the buffers that hold acknowledgement messages in this demo. + */ +#define _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ( sizeof( _ACKNOWLEDGEMENT_MESSAGE_FORMAT ) + 2 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Called by the MQTT library when an operation completes. + * + * The demo uses this callback to determine the result of PUBLISH operations. + * @param[in] param1 The number of the PUBLISH that completed, passed as an intptr_t. + * @param[in] pOperation Information about the completed operation passed by the + * MQTT library. + */ +static void _operationCompleteCallback( void * param1, + AwsIotMqttCallbackParam_t * const pOperation ) +{ + intptr_t publishCount = ( intptr_t ) param1; + + /* Print the status of the completed operation. A PUBLISH operation is + * successful when transmitted over the network. */ + if( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogInfo( "MQTT %s %d successfully sent.", + AwsIotMqtt_OperationType( pOperation->operation.type ), + ( int ) publishCount ); + } + else + { + AwsIotLogError( "MQTT %s %d could not be sent. Error %s.", + AwsIotMqtt_OperationType( pOperation->operation.type ), + ( int ) publishCount, + AwsIotMqtt_strerror( pOperation->operation.result ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Called by the MQTT library when an incoming PUBLISH message is received. + * + * The demo uses this callback to handle incoming PUBLISH messages. This callback + * prints the contents of an incoming message and publishes an acknowledgement + * to the MQTT server. + * @param[in] param1 Counts the total number of received PUBLISH messages. This + * callback will increment this counter. + * @param[in] pPublish Information about the incoming PUBLISH message passed by + * the MQTT library. + */ +static void _mqttSubscriptionCallback( void * param1, + AwsIotMqttCallbackParam_t * const pPublish ) +{ + int acknowledgementLength = 0; + size_t messageNumberIndex = 0, messageNumberLength = 1; + AwsIotSemaphore_t * pPublishesReceived = ( AwsIotSemaphore_t * ) param1; + const char * pPayload = pPublish->message.info.pPayload; + char pAcknowledgementMessage[ _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; + AwsIotMqttPublishInfo_t acknowledgementInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Print information about the incoming PUBLISH message. */ + AwsIotLogInfo( "Incoming PUBLISH received:\n" + "Subscription topic filter: %.*s\n" + "Publish topic name: %.*s\n" + "Publish retain flag: %d\n" + "Publish QoS: %d\n" + "Publish payload: %.*s", + pPublish->message.topicFilterLength, + pPublish->message.pTopicFilter, + pPublish->message.info.topicNameLength, + pPublish->message.info.pTopicName, + pPublish->message.info.retain, + pPublish->message.info.QoS, + pPublish->message.info.payloadLength, + pPayload ); + + /* Find the message number inside of the PUBLISH message. */ + for( messageNumberIndex = 0; messageNumberIndex < pPublish->message.info.payloadLength; messageNumberIndex++ ) + { + /* The payload that was published contained ASCII characters, so find + * beginning of the message number by checking for ASCII digits. */ + if( ( pPayload[ messageNumberIndex ] >= '0' ) && + ( pPayload[ messageNumberIndex ] <= '9' ) ) + { + break; + } + } + + /* Check that a message number was found within the PUBLISH payload. */ + if( messageNumberIndex < pPublish->message.info.payloadLength ) + { + /* Compute the length of the message number. */ + while( ( messageNumberIndex + messageNumberLength < pPublish->message.info.payloadLength ) && + ( *( pPayload + messageNumberIndex + messageNumberLength ) >= '0' ) && + ( *( pPayload + messageNumberIndex + messageNumberLength ) <= '9' ) ) + { + messageNumberLength++; + } + + /* Generate an acknowledgement message. */ + acknowledgementLength = snprintf( pAcknowledgementMessage, + _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH, + _ACKNOWLEDGEMENT_MESSAGE_FORMAT, + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); + + /* Check for errors from snprintf. */ + if( acknowledgementLength < 0 ) + { + AwsIotLogWarn( "Failed to generate acknowledgement message for PUBLISH *.*s.", + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); + } + else + { + /* Set the members of the publish info for the acknowledgement message. */ + acknowledgementInfo.QoS = 1; + acknowledgementInfo.pTopicName = _ACKNOWLEDGEMENT_TOPIC_NAME; + acknowledgementInfo.topicNameLength = _ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH; + acknowledgementInfo.pPayload = pAcknowledgementMessage; + acknowledgementInfo.payloadLength = ( size_t ) acknowledgementLength; + acknowledgementInfo.retryMs = _PUBLISH_RETRY_MS; + acknowledgementInfo.retryLimit = _PUBLISH_RETRY_LIMIT; + + /* Send the acknowledgement for the received message. This demo program + * will not be notified on the status of the acknowledgement because + * neither a callback nor AWS_IOT_MQTT_FLAG_WAITABLE is set. However, + * the MQTT library will still guarantee at-least-once delivery (subject + * to the retransmission strategy) because the acknowledgement message is + * sent at QoS 1. */ + if( AwsIotMqtt_Publish( pPublish->mqttConnection, + &acknowledgementInfo, + 0, + NULL, + NULL ) == AWS_IOT_MQTT_STATUS_PENDING ) + { + AwsIotLogInfo( "Acknowledgment message for PUBLISH %.*s will be sent.", + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); + } + else + { + AwsIotLogWarn( "Acknowledgment message for PUBLISH %.*s will NOT be sent.", + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); + } + } + } + + /* Increment the number of PUBLISH messages received. */ + AwsIotSemaphore_Post( pPublishesReceived ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The function that runs the MQTT demo. + * + * This function is called to run the MQTT demo once a network connection has + * been established. + * @param[in] awsIotMqttMode Specify if this demo is running with the AWS IoT + * MQTT server. Set this to false if using another MQTT server. + * @param[in] pClientIdentifier NULL-terminated MQTT client identifier. + * @param[in] pMqttConnection Pointer to the MQTT connection to use. This MQTT + * connection must be initialized to AWS_IOT_MQTT_CONNECTION_INITIALIZER. + * @param[in] pNetworkInterface Pointer to an MQTT network interface to use. + * All necessary members of the network interface should be set before calling + * this function. + * + * @return 0 if the demo completes successfully; -1 if some part of it fails. + */ +int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, + const char * const pClientIdentifier, + AwsIotMqttConnection_t * const pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface ) +{ + int status = 0, i = 0; + intptr_t publishCount = 0; + char pClientIdentifierBuffer[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + char pPublishPayload[ _PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; + AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER, + publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + AwsIotMqttCallbackInfo_t publishComplete = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + AwsIotSemaphore_t publishesReceived; + const char * pTopicFilters[ _TOPIC_FILTER_COUNT ] = + { + _CLIENT_IDENTIFIER_PREFIX "/topic/1", + _CLIENT_IDENTIFIER_PREFIX "/topic/2", + _CLIENT_IDENTIFIER_PREFIX "/topic/3", + _CLIENT_IDENTIFIER_PREFIX "/topic/4", + }; + + /* Set the common members of the connection info. */ + connectInfo.awsIotMqttMode = awsIotMqttMode; + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + + /* Set the members of the Last Will and Testament (LWT) message info. The + * MQTT server will publish the LWT message if this client disconnects + * unexpectedly. */ + willInfo.pTopicName = _WILL_TOPIC_NAME; + willInfo.topicNameLength = _WILL_TOPIC_NAME_LENGTH; + willInfo.pPayload = _WILL_MESSAGE; + willInfo.payloadLength = _WILL_MESSAGE_LENGTH; + + /* Use the parameter client identifier if provided. Otherwise, generate a + * unique client identifier. */ + if( pClientIdentifier != NULL ) + { + connectInfo.pClientIdentifier = pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pClientIdentifier ); + } + else + { + /* Every active MQTT connection must have a unique client identifier. The demos + * generate this unique client identifier by appending a timestamp to a common + * prefix. */ + status = snprintf( pClientIdentifierBuffer, + _CLIENT_IDENTIFIER_MAX_LENGTH, + _CLIENT_IDENTIFIER_PREFIX "%lu", + ( long unsigned int ) AwsIotClock_GetTimeMs() ); + + /* Check for errors from snprintf. */ + if( status < 0 ) + { + AwsIotLogError( "Failed to generate unique client identifier for demo." ); + status = -1; + } + else + { + /* Set the client identifier buffer and length. */ + connectInfo.pClientIdentifier = pClientIdentifierBuffer; + connectInfo.clientIdentifierLength = ( uint16_t ) status; + + status = 0; + } + } + + if( status == 0 ) + { + AwsIotLogInfo( "MQTT demo client identifier is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); + + /* Establish the MQTT connection. */ + mqttStatus = AwsIotMqtt_Connect( pMqttConnection, + pNetworkInterface, + &connectInfo, + &willInfo, + _MQTT_TIMEOUT_MS ); + + if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "MQTT CONNECT returned error %s.", + AwsIotMqtt_strerror( mqttStatus ) ); + + status = -1; + } + } + + if( status == 0 ) + { + /* Set the members of the subscription list. */ + for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) + { + pSubscriptions[ i ].QoS = 1; + pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; + pSubscriptions[ i ].topicFilterLength = _TOPIC_FILTER_LENGTH; + pSubscriptions[ i ].callback.param1 = &publishesReceived; + pSubscriptions[ i ].callback.function = _mqttSubscriptionCallback; + } + + /* Subscribe to all the topic filters in the subscription list. The + * blocking SUBSCRIBE function is used because the demo should not + * continue until SUBSCRIBE completes. */ + mqttStatus = AwsIotMqtt_TimedSubscribe( *pMqttConnection, + pSubscriptions, + _TOPIC_FILTER_COUNT, + 0, + _MQTT_TIMEOUT_MS ); + + switch( mqttStatus ) + { + case AWS_IOT_MQTT_SUCCESS: + AwsIotLogInfo( "All demo topic filter subscriptions accepted." ); + break; + + case AWS_IOT_MQTT_SERVER_REFUSED: + + /* Check which subscriptions were rejected before exiting the demo. */ + for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) + { + if( AwsIotMqtt_IsSubscribed( *pMqttConnection, + pSubscriptions[ i ].pTopicFilter, + pSubscriptions[ i ].topicFilterLength, + NULL ) == true ) + { + AwsIotLogInfo( "Topic filter %.*s was accepted.", + pSubscriptions[ i ].topicFilterLength, + pSubscriptions[ i ].pTopicFilter ); + } + else + { + AwsIotLogInfo( "Topic filter %.*s was rejected.", + pSubscriptions[ i ].topicFilterLength, + pSubscriptions[ i ].pTopicFilter ); + } + } + + status = -1; + break; + + default: + + status = -1; + break; + } + } + + if( status == 0 ) + { + /* The MQTT library should invoke this callback when a PUBLISH message + * is successfully transmitted. */ + publishComplete.function = _operationCompleteCallback; + + /* Set the common members of the publish info. */ + publishInfo.QoS = 1; + publishInfo.topicNameLength = _TOPIC_FILTER_LENGTH; + publishInfo.pPayload = pPublishPayload; + publishInfo.retryMs = _PUBLISH_RETRY_MS; + publishInfo.retryLimit = _PUBLISH_RETRY_LIMIT; + + /* Create the semaphore that counts received PUBLISH messages.*/ + if( AwsIotSemaphore_Create( &publishesReceived, + 0, + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) + { + for( publishCount = 0; + publishCount < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; + publishCount++ ) + { + if( publishCount % AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) + { + AwsIotLogInfo( "Publishing messages %d to %d.", + publishCount, + publishCount + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); + } + + /* Pass the PUBLISH number to the operation complete callback. */ + publishComplete.param1 = ( void * ) publishCount; + + /* Choose a topic name. */ + publishInfo.pTopicName = pTopicFilters[ publishCount % _TOPIC_FILTER_COUNT ]; + + /* Generate the payload for the PUBLISH. */ + status = snprintf( pPublishPayload, + _PUBLISH_PAYLOAD_BUFFER_LENGTH, + _PUBLISH_PAYLOAD_FORMAT, + ( int ) publishCount ); + + /* Check for errors from snprintf. */ + if( status < 0 ) + { + AwsIotLogError( "Failed to generate MQTT PUBLISH payload for PUBLISH %d.", + ( int ) publishCount ); + status = -1; + + break; + } + else + { + publishInfo.payloadLength = ( size_t ) status; + status = 0; + } + + /* PUBLISH a message. */ + mqttStatus = AwsIotMqtt_Publish( *pMqttConnection, + &publishInfo, + 0, + &publishComplete, + NULL ); + + if( mqttStatus != AWS_IOT_MQTT_STATUS_PENDING ) + { + AwsIotLogError( "MQTT PUBLISH %d returned error %s.", + ( int ) publishCount, + AwsIotMqtt_strerror( mqttStatus ) ); + status = -1; + break; + } + + /* If a complete burst of messages has been published, wait for + * an equal number of messages to be received. Note that messages + * may be received out-of-order, especially if a message was + * dropped and had to be retried. */ + if( ( publishCount > 0 ) && + ( publishCount % AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) + { + AwsIotLogInfo( "Waiting for %d publishes to be received.", + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + + for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) + { + if( AwsIotSemaphore_TimedWait( &publishesReceived, + _MQTT_TIMEOUT_MS ) == false ) + { + AwsIotLogError( "Timed out waiting for incoming PUBLISH messages." ); + status = -1; + break; + } + } + + AwsIotLogInfo( "%d publishes received.", + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + } + + /* Stop publishing if there was an error. */ + if( status == -1 ) + { + break; + } + } + + /* Wait for the messages in the last burst to be received. This + * should also wait for all previously published messages. */ + if( status == 0 ) + { + AwsIotLogInfo( "Waiting for all publishes to be received." ); + + for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) + { + if( AwsIotSemaphore_TimedWait( &publishesReceived, + _MQTT_TIMEOUT_MS ) == false ) + { + AwsIotLogError( "Timed out waiting for incoming PUBLISH messages." ); + status = -1; + break; + } + } + + AwsIotLogInfo( "All publishes received." ); + } + + /* Destroy the received message counter. */ + AwsIotSemaphore_Destroy( &publishesReceived ); + } + else + { + /* Couldn't create received message counter. */ + status = -1; + } + } + + if( status == 0 ) + { + /* Unsubscribe from all demo topic filters. */ + mqttStatus = AwsIotMqtt_TimedUnsubscribe( *pMqttConnection, + pSubscriptions, + _TOPIC_FILTER_COUNT, + 0, + _MQTT_TIMEOUT_MS ); + + if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "MQTT UNSUBSCRIBE returned error %s.", + AwsIotMqtt_strerror( mqttStatus ) ); + status = -1; + } + } + + /* Disconnect the MQTT connection if it was established. */ + if( *pMqttConnection != AWS_IOT_MQTT_CONNECTION_INITIALIZER ) + { + AwsIotMqtt_Disconnect( *pMqttConnection, false ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c new file mode 100644 index 0000000000..2424884157 --- /dev/null +++ b/demos/aws_iot_demo_shadow.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo_shadow.c + * @brief Demonstrates usage of the Thing Shadow library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Common demo include. */ +#include "aws_iot_demo.h" + +/* Platform layer includes. */ +#include "platform/aws_iot_clock.h" +#include "platform/aws_iot_threads.h" + +/* Shadow include. */ +#include "aws_iot_shadow.h" + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int snprintf( char *, + size_t, + const char *, + ... ); +/** @endcond */ + +/** + * @brief The keep-alive interval used for this demo. + * + * An MQTT ping request will be sent periodically at this interval. + */ +#define _KEEP_ALIVE_SECONDS ( 60 ) + +/** + * @brief The timeout for Shadow and MQTT operations in this demo. + */ +#define _TIMEOUT_MS ( 5000 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief The function that runs the Shadow demo. + * + * This function is called to run the Shadow demo once a network connection has + * been established. + * @param[in] pThingName NULL-terminated Thing Name to use for this demo. + * @param[in] pMqttConnection Pointer to the MQTT connection to use. This MQTT + * connection must be initialized to AWS_IOT_MQTT_CONNECTION_INITIALIZER. + * @param[in] pNetworkInterface Pointer to an MQTT network interface to use. + * All necessary members of the network interface should be set before calling + * this function. + * + * @return 0 if the demo completes successfully; -1 if some part of it fails. + */ +int AwsIotDemo_RunShadowDemo( const char * const pThingName, + AwsIotMqttConnection_t * const pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface ) +{ + int status = 0; + AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Set the common members of the connection info. */ + connectInfo.awsIotMqttMode = true; + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + + /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ + connectInfo.pClientIdentifier = pThingName; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pThingName ); + + AwsIotLogInfo( "Shadow Thing Name is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); + + /* Establish the MQTT connection. */ + mqttStatus = AwsIotMqtt_Connect( pMqttConnection, + pNetworkInterface, + &connectInfo, + NULL, + _TIMEOUT_MS ); + + if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "MQTT CONNECT returned error %s.", + AwsIotMqtt_strerror( mqttStatus ) ); + + status = -1; + } + + /* Disconnect the MQTT connection if it was established. */ + if( *pMqttConnection != AWS_IOT_MQTT_CONNECTION_INITIALIZER ) + { + AwsIotMqtt_Disconnect( *pMqttConnection, false ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/posix/CMakeLists.txt b/demos/posix/CMakeLists.txt new file mode 100644 index 0000000000..f89bf79499 --- /dev/null +++ b/demos/posix/CMakeLists.txt @@ -0,0 +1,31 @@ +# Common demo files. +set( DEMO_COMMON_INCLUDE_FILES ".;${CMAKE_SOURCE_DIR}/demos" ) +set( DEMO_COMMON_SOURCE_FILES aws_iot_demo_common_posix.c ) + +# MQTT demo source files. +add_executable( aws_iot_demo_mqtt + ${CMAKE_SOURCE_DIR}/demos/aws_iot_demo_mqtt.c + aws_iot_demo_mqtt_posix.c + ${DEMO_COMMON_SOURCE_FILES} ) + +# MQTT demo include files. +target_include_directories( aws_iot_demo_mqtt + PRIVATE + ${DEMO_COMMON_INCLUDE_FILES} ) + +# MQTT demo library dependencies. +target_link_libraries( aws_iot_demo_mqtt awsiotplatform awsiotmqtt ) + +# Shadow demo source files. +add_executable( aws_iot_demo_shadow + ${CMAKE_SOURCE_DIR}/demos/aws_iot_demo_shadow.c + aws_iot_demo_shadow_posix.c + ${DEMO_COMMON_SOURCE_FILES} ) + +# Shadow demo include files. +target_include_directories( aws_iot_demo_mqtt + PRIVATE + ${DEMO_COMMON_INCLUDE_FILES} ) + +# Shadow demo library dependencies. +target_link_libraries( aws_iot_demo_shadow awsiotplatform awsiotmqtt awsiotshadow ) diff --git a/demos/posix/aws_iot_demo_common_posix.c b/demos/posix/aws_iot_demo_common_posix.c new file mode 100644 index 0000000000..9d2c34ac44 --- /dev/null +++ b/demos/posix/aws_iot_demo_common_posix.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_demo_common_posix.c + * @brief Implements the common demo functions for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include +#include + +/* Common demo includes. */ +#include "aws_iot_demo.h" +#include "aws_iot_demo_posix.h" + +/*-----------------------------------------------------------*/ + +bool AwsIotDemo_ParseArguments( int argc, + char ** argv, + AwsIotDemoArguments_t * const pArguments ) +{ + int option = 0; + unsigned long int port = 0; + + /* Default to AWS IoT MQTT mode. */ + pArguments->awsIotMqttMode = true; + + /* Set default secured connection status if defined. */ + #ifdef AWS_IOT_DEMO_SECURED_CONNECTION + pArguments->securedConnection = AWS_IOT_DEMO_SECURED_CONNECTION; + #endif + + /* Set default MQTT server if defined. */ + #ifdef AWS_IOT_DEMO_SERVER + pArguments->pHostName = AWS_IOT_DEMO_SERVER; + #endif + + /* Set default MQTT server port if defined. */ + #ifdef AWS_IOT_DEMO_PORT + pArguments->port = AWS_IOT_DEMO_PORT; + #endif + + /* Set default root CA path if defined. */ + #ifdef AWS_IOT_DEMO_ROOT_CA + pArguments->pRootCaPath = AWS_IOT_DEMO_ROOT_CA; + #endif + + /* Set default client certificate path if defined. */ + #ifdef AWS_IOT_DEMO_CLIENT_CERT + pArguments->pClientCertPath = AWS_IOT_DEMO_CLIENT_CERT; + #endif + + /* Set default client certificate private key path if defined. */ + #ifdef AWS_IOT_DEMO_PRIVATE_KEY + pArguments->pPrivateKeyPath = AWS_IOT_DEMO_PRIVATE_KEY; + #endif + + AwsIotLogInfo( "Parsing command line arguments." ); + + /* Silence any error or warning messages printed by the system. The demos + * will use the logging library instead. */ + opterr = 0; + + /* Retrieve all command line arguments. */ + while( ( option = getopt( argc, argv, ":sunh:p:r:c:k:i:" ) ) != -1 ) + { + switch( option ) + { + /* Secured connection. */ + case ( int ) ( 's' ): + pArguments->securedConnection = true; + + break; + + /* Unsecured connection. */ + case ( int ) ( 'u' ): + pArguments->securedConnection = false; + + break; + + /* MQTT server is not AWS IoT. */ + case ( int ) ( 'n' ): + pArguments->awsIotMqttMode = false; + break; + + /* Server. */ + case ( int ) ( 'h' ): + pArguments->pHostName = optarg; + break; + + /* Server port. */ + case ( int ) ( 'p' ): + /* Convert argument string to unsigned int. */ + port = strtoul( optarg, NULL, 10 ); + + /* Check that port is valid. */ + if( ( port == 0 ) || ( port > UINT16_MAX ) ) + { + AwsIotLogWarn( "Ignoring invalid port '%lu'.", port ); + } + else + { + pArguments->port = ( uint16_t ) port; + } + + break; + + /* Root CA path. */ + case ( int ) ( 'r' ): + pArguments->pRootCaPath = optarg; + break; + + /* Client certificate path. */ + case ( int ) ( 'c' ): + pArguments->pClientCertPath = optarg; + break; + + /* Client certificate private key path. */ + case ( int ) ( 'k' ): + pArguments->pPrivateKeyPath = optarg; + break; + + /* Client identifier or Thing Name. */ + case ( int ) ( 'i' ): + pArguments->pIdentifier = optarg; + break; + + /* Unknown argument. */ + case ( int ) ( '?' ): + AwsIotLogWarn( "Ignoring unknown argument '-%c'.", ( char ) optopt ); + break; + + /* Argument known, but missing value. */ + case ( int ) ( ':' ): + AwsIotLogWarn( "Ignoring invalid argument '-%c'. Option '-%c' requires a value.", + ( char ) optopt, + ( char ) optopt ); + break; + + /* The default case should not be executed. */ + default: + abort(); + break; + } + } + + /* Check that a server was set. */ + if( ( pArguments->pHostName == NULL ) || + ( strlen( pArguments->pHostName ) == 0 ) ) + { + AwsIotLogError( "MQTT server not set. Exiting." ); + + return false; + } + + /* Check that a server port was set. */ + if( pArguments->port == 0 ) + { + AwsIotLogError( "MQTT server port not set. Exiting." ); + + return false; + } + + /* Check credentials for a secured connection. */ + if( pArguments->securedConnection == true ) + { + /* Check that a root CA path was set. */ + if( ( pArguments->pRootCaPath == NULL ) || + ( strlen( pArguments->pRootCaPath ) == 0 ) ) + { + AwsIotLogError( "Root CA path not set. Exiting." ); + + return false; + } + + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + AwsIotLogError( "Client certificate path not set. Exiting." ); + + return false; + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + AwsIotLogError( "Client certificate private key not set. Exiting." ); + + return false; + } + } + + AwsIotLogInfo( "Command line arguments successfully parsed." ); + + AwsIotLogDebug( "AWS IoT MQTT mode: %s", pArguments->awsIotMqttMode == true ? "true" : "false" ); + AwsIotLogDebug( "Secured connection: %s", pArguments->securedConnection == true ? "true" : "false" ); + AwsIotLogDebug( "Host: %s", pArguments->pHostName ); + AwsIotLogDebug( "Port: %hu", pArguments->port ); + AwsIotLogDebug( "Root CA: %s", pArguments->pRootCaPath ); + AwsIotLogDebug( "Client certificate: %s", pArguments->pClientCertPath ); + AwsIotLogDebug( "Private key: %s", pArguments->pPrivateKeyPath ); + + return true; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/aws_iot_demo_mqtt_posix.c new file mode 100644 index 0000000000..e8923d9718 --- /dev/null +++ b/demos/posix/aws_iot_demo_mqtt_posix.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo_mqtt_posix.c + * @brief Runs the MQTT demo on POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/* Common demo includes. */ +#include "aws_iot_demo.h" +#include "aws_iot_demo_posix.h" + +/* Platform layer include. */ +#include "platform/aws_iot_network.h" + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + int status = 0; + AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; + AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER, * pTlsInfo = NULL; + AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + + /* This function parses arguments and establishes the network connection + * before running the MQTT demo. */ + + /* Set default client identifier. */ + #ifdef AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER + demoArguments.pIdentifier = AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER; + #endif + + /* Parse any command line arguments. */ + if( AwsIotDemo_ParseArguments( argc, + argv, + &demoArguments ) == false ) + { + status = -1; + } + + /* Initialize the network. */ + if( ( status == 0 ) && + ( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) ) + { + status = -1; + } + + if( status == 0 ) + { + /* Set the TLS connection information for secured connections. */ + if( demoArguments.securedConnection == true ) + { + pTlsInfo = &tlsInfo; + + /* By default AWS_IOT_NETWORK_TLS_INFO_INITIALIZER enables ALPN. ALPN + * must be used with port 443; disable ALPN if another port is being used. */ + if( demoArguments.port != 443 ) + { + tlsInfo.pAlpnProtos = NULL; + } + + /* Set the paths to the credentials. Lengths of credential paths are + * ignored by the POSIX platform layer, so they are not set. */ + tlsInfo.pRootCa = demoArguments.pRootCaPath; + tlsInfo.pClientCert = demoArguments.pClientCertPath; + tlsInfo.pPrivateKey = demoArguments.pPrivateKeyPath; + } + + /* Establish a TCP connection to the MQTT server. */ + if( AwsIotNetwork_CreateConnection( &networkConnection, + demoArguments.pHostName, + demoArguments.port, + pTlsInfo ) != AWS_IOT_NETWORK_SUCCESS ) + { + status = -1; + } + } + + if( status == 0 ) + { + /* Set the MQTT receive callback for a network connection. This receive + * callback processes MQTT data from the network. */ + if( AwsIotNetwork_SetMqttReceiveCallback( networkConnection, + &mqttConnection, + AwsIotMqtt_ReceiveCallback ) != AWS_IOT_NETWORK_SUCCESS ) + { + status = -1; + } + } + + if( status == 0 ) + { + /* Set the members of the network interface used by the MQTT connection. */ + networkInterface.pDisconnectContext = ( void * ) networkConnection; + networkInterface.pSendContext = ( void * ) networkConnection; + networkInterface.disconnect = AwsIotNetwork_CloseConnection; + networkInterface.send = AwsIotNetwork_Send; + + /* Initialize the MQTT library. */ + if( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) + { + /* Run the MQTT demo. */ + status = AwsIotDemo_RunMqttDemo( demoArguments.awsIotMqttMode, + demoArguments.pIdentifier, + &mqttConnection, + &networkInterface ); + + /* Clean up the MQTT library. */ + AwsIotMqtt_Cleanup(); + } + else + { + status = -1; + } + } + + /* Close and destroy the network connection (if it was established). */ + if( networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + { + /* Note that the MQTT library may have called AwsIotNetwork_CloseConnection. + * However, AwsIotNetwork_CloseConnection is safe to call on a closed connection. + * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. + */ + AwsIotNetwork_CloseConnection( networkConnection ); + AwsIotNetwork_DestroyConnection( networkConnection ); + } + + /* Clean up the network. */ + AwsIotNetwork_Cleanup(); + + /* Log the demo status. */ + if( status == 0 ) + { + AwsIotLogInfo( "Demo completed successfully." ); + } + else + { + AwsIotLogError( "Error running demo, status %d.", status ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/posix/aws_iot_demo_posix.h b/demos/posix/aws_iot_demo_posix.h new file mode 100644 index 0000000000..7275b8d1d2 --- /dev/null +++ b/demos/posix/aws_iot_demo_posix.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo_posix.h + * @brief Declares the POSIX-specific demo functions. + */ + +#ifndef _AWS_IOT_DEMO_POSIX_H_ +#define _AWS_IOT_DEMO_POSIX_H_ + +/** + * @brief Holds the arguments for a single demo. + * + * Each demo will use one of these structs to hold its arguments. + * + * The default values of this struct may be set using compile-time constants, + * either through a [config file](@ref AWS_IOT_CONFIG_FILE) or a compiler option + * like `-D`. + * + * The default values may be overridden using command line arguments. If a default + * value was not set, then a valid value must be set using a command line argument. + * + * @initializer{AwsIotDemoArguments_t,AWS_IOT_DEMO_ARGUMENTS_INITIALIZER} + */ +typedef struct AwsIotDemoArguments +{ + bool awsIotMqttMode; /**< @brief Whether the demo is using the AWS IoT MQTT server. */ + bool securedConnection; /**< @brief Whether to secure the network connection with TLS. */ + const char * pHostName; /**< @brief The remote host name that the demo will connect to. */ + uint16_t port; /**< @brief The remote host port that the demo will connect to. */ + + /* These credentials are only used if securedConnection is true. */ + const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ + const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ + const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ + + const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ +} AwsIotDemoArguments_t; + +/** + * @brief Provides default values for an #AwsIotDemoArguments_t. + * + * All instances of #AwsIotDemoArguments_t should be initialized with this + * constant. + * + * @code{c} + * AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; + * @endcode + * + * @warning Failing to initialize an #AwsIotDemoArguments_t with this initializer + * may result in undefined behavior! + * @note This initializer may change at any time in future versions, but its + * names will remain the same. + */ +#define AWS_IOT_DEMO_ARGUMENTS_INITIALIZER { 0 } + +/** + * @brief Parses command line arguments. + * + * The functions for parsing command line arguments differ depending on the + * operating system. Therefore, this function is re-implemented for different + * platforms. + * @param[in] argc The argument count originally passed to main(). + * @param[in] argv The argument vector originally passed to main(). + * @param[out] pArguments Set to the arguments parsed from the command line. + * + * @return `true` if arguments were successfully parsed and all necessary variables + * were set; `false` otherwise. If this function returns `false`, the demo program + * should exit. + */ +bool AwsIotDemo_ParseArguments( int argc, + char ** argv, + AwsIotDemoArguments_t * const pArguments ); + +#endif /* ifndef _AWS_IOT_DEMO_POSIX_H_ */ diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c new file mode 100644 index 0000000000..a97fbebe1a --- /dev/null +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo_shadow_posix.c + * @brief Runs the Thing Shadow demo on POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/* Shadow include. */ +#include "aws_iot_shadow.h" + +/* Common demo includes. */ +#include "aws_iot_demo.h" +#include "aws_iot_demo_posix.h" + +/* Platform layer include. */ +#include "platform/aws_iot_network.h" + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + int status = 0; + AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; + AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER, * pTlsInfo = NULL; + AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + + /* This function parses arguments and establishes the network connection + * before running the Shadow demo. */ + + /* Set the default Thing Name. */ + #ifdef AWS_IOT_DEMO_THING_NAME + demoArguments.pIdentifier = AWS_IOT_DEMO_THING_NAME; + #endif + + /* Parse any command line arguments. */ + if( AwsIotDemo_ParseArguments( argc, + argv, + &demoArguments ) == false ) + { + status = -1; + } + + /* Initialize the network. */ + if( ( status == 0 ) && + ( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) ) + { + status = -1; + } + + /* Thing Name must be set for this demo. */ + if( demoArguments.pIdentifier == NULL ) + { + AwsIotLogError( "Thing Name must be set for Shadow demo." ); + + status = -1; + } + + if( status == 0 ) + { + /* Set the TLS connection information for secured connections. Thing + * Shadow is specific to AWS IoT, so it always requires a secured connection. */ + pTlsInfo = &tlsInfo; + + /* By default AWS_IOT_NETWORK_TLS_INFO_INITIALIZER enables ALPN. ALPN + * must be used with port 443; disable ALPN if another port is being used. */ + if( demoArguments.port != 443 ) + { + tlsInfo.pAlpnProtos = NULL; + } + + /* Set the paths to the credentials. Lengths of credential paths are + * ignored by the POSIX platform layer, so they are not set. */ + tlsInfo.pRootCa = demoArguments.pRootCaPath; + tlsInfo.pClientCert = demoArguments.pClientCertPath; + tlsInfo.pPrivateKey = demoArguments.pPrivateKeyPath; + + /* Establish a TCP connection to the MQTT server. */ + if( AwsIotNetwork_CreateConnection( &networkConnection, + demoArguments.pHostName, + demoArguments.port, + pTlsInfo ) != AWS_IOT_NETWORK_SUCCESS ) + { + status = -1; + } + } + + if( status == 0 ) + { + /* Set the MQTT receive callback for a network connection. This receive + * callback processes MQTT data from the network. */ + if( AwsIotNetwork_SetMqttReceiveCallback( networkConnection, + &mqttConnection, + AwsIotMqtt_ReceiveCallback ) != AWS_IOT_NETWORK_SUCCESS ) + { + status = -1; + } + } + + if( status == 0 ) + { + /* Set the members of the network interface used by the MQTT connection. */ + networkInterface.pDisconnectContext = ( void * ) networkConnection; + networkInterface.pSendContext = ( void * ) networkConnection; + networkInterface.disconnect = AwsIotNetwork_CloseConnection; + networkInterface.send = AwsIotNetwork_Send; + + /* Initialize the MQTT library and Shadow library. */ + if( ( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) && + ( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ) ) + { + /* Run the Shadow demo. */ + status = AwsIotDemo_RunShadowDemo( demoArguments.pIdentifier, + &mqttConnection, + &networkInterface ); + + /* Clean up the MQTT library and Shadow library. */ + AwsIotShadow_Cleanup(); + AwsIotMqtt_Cleanup(); + } + else + { + status = -1; + } + } + + /* Close and destroy the network connection (if it was established). */ + if( networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + { + /* Note that the MQTT library may have called AwsIotNetwork_CloseConnection. + * However, AwsIotNetwork_CloseConnection is safe to call on a closed connection. + * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. + */ + AwsIotNetwork_CloseConnection( networkConnection ); + AwsIotNetwork_DestroyConnection( networkConnection ); + } + + /* Clean up the network. */ + AwsIotNetwork_Cleanup(); + + /* Log the demo status. */ + if( status == 0 ) + { + AwsIotLogInfo( "Demo completed successfully." ); + } + else + { + AwsIotLogError( "Error running demo, status %d.", status ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/doc/config/common b/doc/config/common new file mode 100644 index 0000000000..d8c012316d --- /dev/null +++ b/doc/config/common @@ -0,0 +1,111 @@ +# SDK version. +PROJECT_NUMBER = "4.0.0b1" + +# Doxygen layout file for libraries. +LAYOUT_FILE = doc/config/layout_library.xml + +# Documentation output directory. +OUTPUT_DIRECTORY = doc/output/ + +# Don't generate LaTeX documentation +GENERATE_LATEX = NO + +# Directories containing images. +IMAGE_PATH = doc/plantuml/images + +# Don't rearrange members in the input files. +SORT_MEMBER_DOCS = NO + +# Silence output (warnings only). +QUIET = YES + +# Define the following preprocessor constants when generating documentation. +PREDEFINED = "DOXYGEN=1" \ + "AWS_IOT_STATIC_MEMORY_ONLY=1" \ + "_LIBRARY_LOG_LEVEL=AWS_IOT_LOG_DEBUG" \ + "_LIBRARY_LOG_NAME=\"DOXYGEN\"" + +# Ignore the constants used for setting log levels and names. +EXCLUDE_SYMBOLS = "_LIBRARY_LOG_*" + +# Configure Doxygen for C. +OPTIMIZE_OUTPUT_FOR_C = YES +TYPEDEF_HIDES_STRUCT = YES +EXTRACT_STATIC = YES + +# Disable the tab bar and use treeview instead. +DISABLE_INDEX = YES +GENERATE_TREEVIEW = YES + +# All files should have unique names, so showing the full path is unnecessary. +FULL_PATH_NAMES = NO + +# Disable the default Doxygen diagrams. +HAVE_DOT = NO + +# Disable the default Doxygen search engine (for now). +SEARCHENGINE = NO + +# Use custom header file, footer file, and stylesheet. +HTML_HEADER = doc/config/html/header.html +HTML_FOOTER = doc/config/html/footer.html +HTML_EXTRA_STYLESHEET = doc/config/html/style.css + +# Don't show external pages or groups. +EXTERNAL_GROUPS = NO +EXTERNAL_PAGES = NO + +# Enable expansion of Unity test macros. +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES + +# Include path for expanding Unity test macros. +INCLUDE_PATH = tests/unity/fixture + +# Expand the TEST macro, but ignore the _run functions generated from macro expansion. +EXPAND_AS_DEFINED = TEST +EXCLUDE_SYMBOLS += "TEST_*_run" + +# Alias for starting a dependencies section. +ALIASES += dependencies{2}="@section \1_dependencies Dependencies^^@brief Dependencies of the \2.^^^^" + +# Alias for starting a configuration settings page. +ALIASES += describeconfig="Configuration settings are C proprocessor constants. They can be set with a @c #`define` in an @ref AWS_IOT_CONFIG_FILE or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." +ALIASES += configpage{2}="@page \1_config Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^@par configpagemarker" +ALIASES += configpage{4}="@page \1_config \3 Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^^^The settings on this page only affect the [\2](@ref \1). In addition to the settings on this page, them \2 will also be affected by [settings that affect all \4](@ref global_\4_config).^^@par configpagemarker" +ALIASES += globalconfigpage{3}="@page global_\1_config Global \2 Configuration^^^^@describeconfig^^@brief Configuration settings that affect all \3.^^@par configpagemarker" + +# Aliases for "Possible values", "Recommended values", and "Default values" +# used in configuration setting pages. +ALIASES += configpossible="Possible values: " +ALIASES += configrecommended="Recommended values: " +ALIASES += configdefault="Default value (if undefined): " + +# Alias for starting a constants page. +ALIASES += constantspage{2}="@page \1_constants Constants^^@brief Defined constants of the \2.^^^^Libraries may @c #`define` constants in their headers with special meanings. This page describes the meanings and uses of any constants defined by the \2. Related constants are shown in a single section on this page.^^" + +# Alias for starting a functions page. +ALIASES += functionspage{2}="@page \1_functions Functions^^@brief Functions of the \2.^^^^The \2 consists of the following functions." +ALIASES += functionspage{3}="@page \1_functions \3^^@brief Functions of the \2.^^^^The \2 consists of the following functions." + +# Alias for listing a single function on a functions page. +ALIASES += functionname{1}="@subpage \1
^^ @copybrief \1^^" + +# Alias for creating a page for a single function. +ALIASES += functionpage{3}="@page \2_function_\3 \1^^^^@snippet this declare_\2_\3^^@copydoc \1" + +# Alias for starting a handles group. +ALIASES += handles{2}="@defgroup \1_datatypes_handles Handles^^@brief Opaque handles of the \2." + +# Alias for starting an enum group. +ALIASES += enums{2}="@defgroup \1_datatypes_enums Enumerated types^^@brief Enumerated types of the \2." + +# Alias for starting a parameter structures group. +ALIASES += paramstructs{2}="@defgroup \1_datatypes_paramstructs Parameter structures^^@brief Structures passed as parameters to [\2 functions](@ref \1_functions)^^^^These structures are passed as parameters to library functions. Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member." + +# Alias for "Parameter for". +ALIASES += paramfor="Parameter for: " + +# Alias for parameter structure initializers. +ALIASES += initializer{2}="All instances of #\1 should be initialized with #\2.^^" diff --git a/doc/config/html/footer.html b/doc/config/html/footer.html new file mode 100644 index 0000000000..18c84eb786 --- /dev/null +++ b/doc/config/html/footer.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/doc/config/html/header.html b/doc/config/html/header.html new file mode 100644 index 0000000000..a60bb68d3d --- /dev/null +++ b/doc/config/html/header.html @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + $projectname: $title + + + + + $title + + + + + + $search + $mathjax + + $extrastylesheet + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ AWS IoT Device SDK C: + $projectname +
+ +
+ $projectbrief +
+ +
+
+ $projectbrief +
+
+ $searchbox +
+ Return to main page ↑ +
+
+ +$treeview + + + diff --git a/doc/config/html/style.css b/doc/config/html/style.css new file mode 100644 index 0000000000..928a523942 --- /dev/null +++ b/doc/config/html/style.css @@ -0,0 +1,132 @@ +/* + * Stylesheet for Doxygen HTML output. + * + * This file defines styles for custom elements in the header/footer and + * overrides some of the default Doxygen styles. + * + * Styles in this file do not affect the treeview sidebar. + */ + +/* Set the margins to place a small amount of whitespace on the left and right + * side of the page. */ +div.contents { + margin-left:4em; + margin-right:4em; +} + +/* Justify text in paragraphs. */ +p { + text-align: justify; +} + +/* Style of section headings. */ +h1 { + border-bottom: 1px solid #879ECB; + color: #354C7B; + font-size: 160%; + font-weight: normal; + padding-bottom: 4px; + padding-top: 8px; +} + +/* Style of subsection headings. */ +h2:not(.memtitle):not(.groupheader) { + font-size: 125%; + margin-bottom: 0px; + margin-top: 16px; + padding: 0px; +} + +/* Style of paragraphs immediately after subsection headings. */ +h2 + p { + margin: 0px; + padding: 0px; +} + +/* Style of subsubsection headings. */ +h3 { + font-size: 100%; + margin-bottom: 0px; + margin-left: 2em; + margin-right: 2em; +} + +/* Style of paragraphs immediately after subsubsection headings. */ +h3 + p { + margin-top: 0px; + margin-left: 2em; + margin-right: 2em; +} + +/* Style of the prefix "AWS IoT Device SDK C" that appears in the header. */ +#csdkprefix { + color: #757575; +} + +/* Style of the "Return to main page" link that appears in the header. */ +#returntomain { + padding: 0.5em; +} + +/* Style of the dividers on Configuration Settings pages. */ +div.configpagedivider { + margin-left: 0px !important; + margin-right: 0px !important; + margin-top: 20px !important; +} + +/* Style of configuration setting names. */ +dl.section.user ~ h1 { + border-bottom: none; + color: #000000; + font-family: monospace, fixed; + font-size: 16px; + margin-bottom: 0px; + margin-left: 2em; + margin-top: 1.5em; +} + +/* Style of paragraphs on a configuration settings page. */ +dl.section.user ~ * { + margin-bottom: 10px; + margin-left: 4em; + margin-right: 4em; + margin-top: 0px; +} + +/* Hide the configuration setting marker. */ +dl.section.user { + display: none; +} + +/* Overrides for code fragments and lines. */ +div.fragment { + background: #ffffff; + border: none; + padding: 5px; +} + +div.line { + color: #3a3a3a; +} + +/* Overrides for code syntax highlighting colors. */ +span.comment { + color: #008000; +} + +span.keyword, span.keywordtype, span.keywordflow { + color: #0000ff; +} + +span.preprocessor { + color: #50015a; +} + +span.stringliteral, span.charliteral { + color: #800c0c; +} + +a.code, a.code:visited, a.line, a.line:visited { + color: #496194; +} diff --git a/doc/config/layout_library.xml b/doc/config/layout_library.xml new file mode 100644 index 0000000000..7aebbfee9b --- /dev/null +++ b/doc/config/layout_library.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/config/layout_main.xml b/doc/config/layout_main.xml new file mode 100644 index 0000000000..f45cd386a2 --- /dev/null +++ b/doc/config/layout_main.xml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/config/logging b/doc/config/logging new file mode 100644 index 0000000000..14a4a4677e --- /dev/null +++ b/doc/config/logging @@ -0,0 +1,26 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Logging" +PROJECT_BRIEF = "Generate and print log messages" + +# Library documentation output directory. +HTML_OUTPUT = logging + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/logging.tag + +# Directories containing library source code. +INPUT = doc/lib \ + lib/include \ + lib/include/private \ + lib/source/common + +# Library file names. +FILE_PATTERNS = *logging*.c *logging*.h *logging*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/platform.tag=../platform diff --git a/doc/config/main b/doc/config/main new file mode 100644 index 0000000000..ea9e618f36 --- /dev/null +++ b/doc/config/main @@ -0,0 +1,35 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Main" + +# Library documentation output directory. +HTML_OUTPUT = main + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/main.tag + +# Input directories. +INPUT = doc/ \ + doc/guide + +# Library file names. +FILE_PATTERNS = *.txt + +# Don't automatically link to library symbols. +AUTOLINK_SUPPORT = NO + +# External tag files required by this library. +TAGFILES = doc/tag/logging.tag=../logging \ + doc/tag/platform.tag=../platform \ + doc/tag/mqtt.tag=../mqtt \ + doc/tag/shadow.tag=../shadow + +# Use the Main page layout file +LAYOUT_FILE = doc/config/layout_main.xml + +# Aliases for tables on the Style Guide page. +ALIASES += formattable{1}="This table contains the formats for the names of the most common SDK \1. It is not intended to be comprehensive. Bold text indicates a variable but required part of the \1 name. Italic text indicates a variable but optional part of the \1 name.^^^^| Format | Applies to | Example |^^| ------ | ---------- | ------- |" +ALIASES += formattableentry{3}="| \1 | \2 | \3 |" diff --git a/doc/config/mqtt b/doc/config/mqtt new file mode 100644 index 0000000000..60e34d035c --- /dev/null +++ b/doc/config/mqtt @@ -0,0 +1,34 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "MQTT" +PROJECT_BRIEF = "MQTT 3.1.1 client library" + +# Library documentation output directory. +HTML_OUTPUT = mqtt + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/mqtt.tag + +# Directories containing library source code. +INPUT = doc \ + doc/lib \ + lib/include \ + lib/include/private \ + lib/source/mqtt \ + demos/ \ + tests/ \ + tests/mqtt/unit \ + tests/mqtt/access \ + tests/mqtt/system + +# Library file names. +FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt aws_iot_tests_network.c + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/queue.tag=../queue \ + doc/tag/logging.tag=../logging \ + doc/tag/platform.tag=../platform \ diff --git a/doc/config/platform b/doc/config/platform new file mode 100644 index 0000000000..46132c8c1d --- /dev/null +++ b/doc/config/platform @@ -0,0 +1,29 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Platform" +PROJECT_BRIEF = "Platform portability layer" + +# Library documentation output directory. +HTML_OUTPUT = platform + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/platform.tag + +# Directories containing library source code. +INPUT = doc/lib \ + lib/include/platform \ + lib/source/platform/posix \ + lib/source/platform/posix/static_memory \ + lib/source/platform/posix/network + +# Library file names. +FILE_PATTERNS = *.c *.h *platform*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/logging.tag=../logging \ + doc/tag/mqtt.tag=../mqtt \ + doc/tag/shadow.tag=../shadow diff --git a/doc/config/queue b/doc/config/queue new file mode 100644 index 0000000000..cd9ebe3f27 --- /dev/null +++ b/doc/config/queue @@ -0,0 +1,25 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Queue" +PROJECT_BRIEF = "Queues and lists" + +# Library documentation output directory. +HTML_OUTPUT = queue + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/queue.tag + +# Directories containing library source code. +INPUT = doc/lib \ + lib/include/ \ + lib/source/common + +# Library file names. +FILE_PATTERNS = *queue*.c *queue*.h *queue*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/platform.tag=../platform diff --git a/doc/config/shadow b/doc/config/shadow new file mode 100644 index 0000000000..877596c05d --- /dev/null +++ b/doc/config/shadow @@ -0,0 +1,28 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Shadow" +PROJECT_BRIEF = "AWS IoT Device Shadow library" + +# Library documentation output directory. +HTML_OUTPUT = shadow + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/shadow.tag + +# Directories containing library source code. +INPUT = doc/lib/ \ + lib/include \ + lib/include/private \ + lib/source/shadow + +# Library file names. +FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/mqtt.tag=../mqtt \ + doc/tag/queue.tag=../queue \ + doc/tag/logging.tag=../logging diff --git a/doc/guide/building.txt b/doc/guide/building.txt new file mode 100644 index 0000000000..0844cd72db --- /dev/null +++ b/doc/guide/building.txt @@ -0,0 +1,124 @@ +/** +@page building Building the SDK +@brief Guide for building the SDK. + +This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. + +@pre +- CMake version 3.5.0 or later and a supported C compiler. +- One of the following security libraries: + - OpenSSL development libraries and header files, version 1.0.2g or later. + +@pre +Additional requirements for POSIX systems: +- POSIX threads, mutexes, and semaphores. +- POSIX timers. + +In addition, credentials and Things for AWS IoT must be provisioned before running the demos. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html) for a tutorial on setting up AWS IoT. + +@section building_demo Building the demo applications +@brief How to build the demo applications. + +Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/aws_iot_demo_config.h`. Any undefined settings will use a default value when possible. + +The demo applications build with CMake. + +In-source CMake builds are not allowed. A build directory should be created under the SDK root directory. Since CMake is cross-platform, build steps will vary depending on host OS. On Linux, the demo applications are built as follows: +@code{sh} +# Create build directory and change to build directory. +mkdir build +cd build + +# Configure build using CMake. +cmake .. + +# Build the demo applications. +make +@endcode + +Demo application executables will be placed in a `bin` directory of the CMake build directory, i.e. `build/bin` in the example above. The executables will be named `aws_iot_demo_library`. For example, the MQTT demo application will be named `aws_iot_demo_mqtt`. + +Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/aws_iot_demo_config.h` is recommended over setting them using CMake. +@code{sh} +# Example: Set AWS_IOT_STATIC_MEMORY_ONLY using CMake +cmake .. -DCMAKE_C_FLAGS=-DAWS_IOT_STATIC_MEMORY_ONLY=1 +@endcode + +By default, CMake may build in `Release` configuration. To enable debugging symbols, set `CMAKE_BUILD_TYPE` to `Debug`. +@code{sh} +cmake .. -DCMAKE_BUILD_TYPE=Debug +@endcode + +Delete the build directory to remove all demo application executables and build files. + +@section demo_commandlineoptions Demo command line options +@brief Command line options of the demo applications. + +Demo applications accept the following command line options. + +@subsection demo_optionn -n +@brief Disable AWS IoT MQTT mode. + +By default, the demo applications expect to run with an AWS IoT MQTT server. Passing this option disables [AWS IoT MQTT mode](@ref AwsIotMqttConnectInfo_t.awsIotMqttMode) and treats the remote MQTT server as a fully-compliant MQTT server. This option should not have an argument. + +Because Thing Shadows are specific to AWS IoT, this option is ignored by the [Shadow demo](@ref shadow_demo). + +@subsection demo_optionsu -s and -u +@brief Secured or unsecured demo connection, respectively. + +Neither `-s` nor `-u` should have an argument, and only one of the two should be used at a time. + +See @ref AWS_IOT_DEMO_SECURED_CONNECTION for the compile-time default setting of this option. + +Because Thing Shadows are specific to AWS IoT (which requires secured connections), this option is ignored by the [Shadow demo](@ref shadow_demo). + +@subsection demo_optionh -h host +@brief Remote host for demo application. + +Must be followed by a host name. + +See @ref AWS_IOT_DEMO_SERVER for the compile-time default setting of this option. + +@subsection demo_optionp -p port +@brief Remote port for demo application. + +Must be followed by a port in the range of `[1,65535]`. + +See @ref AWS_IOT_DEMO_PORT for the compile-time default setting of this option. + +@subsection demo_optionr -r rootCA, -c clientCert, -k privateKey +@brief Paths to trusted server root certificate, client certificate, and client certificate private key, respectively. + +Must be followed by a filesystem path to the respective PEM-encoded credential. + +See @ref AWS_IOT_DEMO_ROOT_CA, @ref AWS_IOT_DEMO_CLIENT_CERT, and @ref AWS_IOT_DEMO_PRIVATE_KEY for the compile-time default settings of these options. + +@subsection demo_optioni -i identifier +@brief MQTT client identifier OR Thing Name. + +Must be followed by a string representing either an MQTT client identifier (MQTT demo only) or a Thing Name (all other demos). Because AWS IoT recommends that MQTT client identifier and Thing Name match, demos will use the Thing Name as the MQTT client identifier when possible. + +See @ref AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER (MQTT demo only) or @ref AWS_IOT_DEMO_THING_NAME for the compile-time default settings of this option. + +@section building_tests Building and running the tests +@brief How to build and run the SDK tests. + +Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/aws_iot_tests_config.h`. + +Unlike the demos, the tests are currently only supported on POSIX systems that support all of the POSIX prerequisites. They are built by passing the option `AWS_IOT_BUILD_TESTS=1` to CMake. +@code{sh} +# Create build directory and change to build directory. +mkdir build +cd build + +# Configure build using CMake. +cmake .. -DAWS_IOT_BUILD_TESTS=1 + +# Build both the demos and tests. +make +@endcode + +Test executables will be placed in a `bin` directory of the CMake build directory (i.e. `build/bin`) alongside the demo applications. They will be named `aws_iot_tests_library`. For example, the MQTT tests executable will be named `aws_iot_tests_mqtt`. + +By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. +*/ diff --git a/doc/guide/developer.txt b/doc/guide/developer.txt new file mode 100644 index 0000000000..46e88dd5ba --- /dev/null +++ b/doc/guide/developer.txt @@ -0,0 +1,8 @@ +/** +@page guide_developer Developer's Guide +@brief Guide for maintaining and contributing code to this project. + +This guide contains the following pages. All pages assume the reader has intermediate familiarity with this SDK. +- @subpage guide_developer_styleguide
+ @copybrief guide_developer_styleguide +*/ diff --git a/doc/guide/style.txt b/doc/guide/style.txt new file mode 100644 index 0000000000..b30f490996 --- /dev/null +++ b/doc/guide/style.txt @@ -0,0 +1,282 @@ +/** +@page guide_developer_styleguide Style Guide +@brief Guide for the coding style used in this SDK. + +The goal of this style guide is to enforce a readable and consistent coding style across the entire SDK. + +@section guide_developer_styleguide_codingstyle Coding Style +@brief The coding style used in this SDK. + +The coding style aims to produce code that is readable and easy to debug. All library code follows these general rules: +- Libraries should only use features from [C99](https://en.wikipedia.org/wiki/C99) and earlier. +- Libraries should [log](@ref logging) extensively. +- Code should be well-commented. +- Only `/*` and @c *`/` should be used to start and end comments. +- All comments end with a period. +- Only spaces should be used for indenting. A single indent is 4 spaces. No tab characters should be used. +- A parenthesis is usually followed by a space (see @ref guide_developer_styleguide_codingstyle_example). +- All lines of code should be less than 80 characters long, although longer lines are permitted if necessary. +- All local variables should be declared at the top of a function. +- All global variables should be declared at the top of a file. +- Variables are always initialized. +- A separator is placed between different sections of a file. The current separator is: +@code{c} +/*-----------------------------------------------------------*/ +@endcode +- All files must include @ref AWS_IOT_CONFIG_FILE at the top of the file before any other includes. +- `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. + +See @ref guide_developer_styleguide_codingstyle_example for a more complete example. + +@subsection guide_developer_styleguide_codingstyle_example Example File +@brief An example file that follows the coding style rules. + +See @ref guide_developer_styleguide_naming for how to name the functions, variables, and macros. + +@code{c} +/* Included headers are at the top of the file. The config file include is always first. */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE /* Lines between #ifdef/#endif are indented. */ +#endif + +/* Standard includes are immediately after the config file. They are sorted alphabetically. + * They use angle brackets <> around the file name. */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* Library internal headers are included next. They use quotes "" around the file name. */ + +/* Library internal include. */ +#include "private/aws_iot_library_internal.h" + +/* Library application-facing headers are included last. They use quotes "" around the file name. */ + +/* Library include. */ +#include "aws_iot_library.h" + +/*-----------------------------------------------------------*/ + +/* Defined constants follow the included headers. */ + +#define _LIBRARY_CONSTANT ( 10 ) /* When possible, parentheses () should be placed around contant values */ + +#define _LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ + { \ /* Function-like macros are surrounded by curly braces {}. */ + macro_body( argument ); \ + } + +/*-----------------------------------------------------------*/ + +/* Library typedefs follow the defined constants. */ + +/* Forward declarations are used only when necessary. They are placed before all + * other typedefs. */ + +typedef int _type_t; + +typedef struct _structType /* Structs are named along with the typedef. */ +{ + int member; + + union /* Anonymous structs/unions are permitted only inside of other structs. */ + { + int a; + int b; + }; + + int variableLengthMember[]; /* Variable length arrays (a C99 feature) are permitted. */ +} _structType_t; + +/*-----------------------------------------------------------*/ + +/* Declarations of static and extern functions follow the typedefs. */ + +static bool _libraryStaticFunction( void * pArgument, + size_t argumentLength ); + +/* External function declarations should be used sparingly (using an internal + * header file to declare functions is preferred). */ +extern int AwsIotLibrary_ExternalFunction( void * pArgument ); + +/*-----------------------------------------------------------*/ + +/* Declarations of global variables follow the static and extern function + * declarations. Global variables are permitted, but should be avoided when + * possible. */ + +/* Global variables are always initialized. */ +static int _globalVariable = 0; +static int _globalArray[ _LIBRARY_CONSTANT ] = { 0 }; + +/*-----------------------------------------------------------*/ + +/* Implementations of static functions follow the global variable declarations. */ + +static bool _libraryStaticFunction( void * pArgument, + size_t argumentLength ) +{ + /* All local variables are declared at the top of the function. Variables are + * always initialized. */ + size_t i = 0; + int localVariable = 0; + int * pLocalPointer = ( int * ) pArgument; + + /* All functions make generous use of the logging library. */ + AwsIotLogInfo( "Performing calculation..." ); + + /* Checking parameters at the beginning of functions and returning on bad + * parameter values is encouraged. */ + if( ( pArgument == NULL ) || ( argumentLength == 0 ) ) /* Note the parentheses and spacing in if statements */ + { + AwsIotLogError( "Bad parameters." ); + + return false; + } + + for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ + { + localVariable += AwsIotLibrary_ExternalFunction( pArgument ); + + AwsIotLogDebug( "Current value is %d.", localVariable ); + } + + if( localVariable < 0 ) + { + AwsIotLogWarn( "Failed to calculate positive value." ); + } + + AwsIotLogInfo( "Calculation done." ); + + return true; +} + +/* A separator is placed between all function implementations. */ +/*-----------------------------------------------------------*/ + +/* Implementations of application-facing functions are at the bottom of the file. */ + +bool AwsIotLibrary_ApplicationFunction( void ) /* Functions with no arguments have void in their argument list. */ +{ + _LIBRARY_FUNCTION_MACRO( _globalArray ); + + return true; +} + +/* Separator and newline at end of file */ +/*-----------------------------------------------------------*/ + +@endcode + +@section guide_developer_styleguide_naming Naming +@brief Naming convention used in this SDK. + +The naming convention aims to differentiate this SDK's files, variables, and functions to avoid name collisions. In general: +- The first characters of all publicly visible names should identify the name as part of this AWS IoT SDK.
+ Example: Names starting with `aws_iot_` or `AwsIot`. +- Words in names should be ordered with the most general word first and the most specific word last.
+ Example: `aws_iot_mqtt_api.c` identifies a file as part of the general MQTT library. `AwsIotMqttInternal_ValidateNetIf` identifies a function as part of the Internal component of the general MQTT library. +- Names should avoid using abbreviations. + +@subsection guide_developer_styleguide_naming_definedconstantsandenumvalues Defined constants and enum values +@brief Naming convention for constants set using preprocessor @c #`define` and enum values. + +@formattable{defined constants and enum values} +@formattableentry{`AWS_IOT_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in application-facing library header files,`AWS_IOT_MQTT_SUCCESS` (aws_iot_mqtt.h)} +@formattableentry{`AWS_IOT_DEMO_`LIBRARY`_`DESCRIPTION
`AWS_IOT_TEST_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in demos and tests,`AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE`
`AWS_IOT_TEST_MQTT_THREADS`} +@formattableentry{`_`DESCRIPTION,Internal constants and enum values,`_MQTT_PACKET_TYPE_CONNECT` (aws_iot_mqtt_internal.h)} + +Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `AWS_IOT_`, while names intended for internal use must begin with only `_`. + +@subsection guide_developer_styleguide_naming_files Files +@brief Naming convention for files. + +@formattable{files} +@formattableentry{`aws_iot_`library`_`description`.extension`,General library file,`aws_iot_mqtt_api.c`} +@formattableentry{`aws_iot_`library`_internal.h`,Internal library header,`aws_iot_mqtt_internal.h`} +@formattableentry{`aws_iot_demo_`library`.c`,Library demo source,`aws_iot_demo_mqtt.c`} +@formattableentry{`aws_iot_tests_`library`_`description`.c`,Library test source,`aws_iot_tests_mqtt_api.c`} + +File names contain only lowercase letters and underscores. All file names should start with `aws_iot_` and be named according to their purpose. For example: +- `aws_iot_mqtt_api.c`: A file in the MQTT library that implements the MQTT API functions. +- `aws_iot_demo_mqtt.c`: A file in the Demos for the MQTT library. +- `aws_iot_demo_mqtt_posix.c`: A file in the Demos for the MQTT library on POSIX systems. +- `aws_iot_tests_mqtt_api.c`: A file in the Tests for the MQTT library. Since the tests currently only run on POSIX systems, test file names do not use the `_posix` suffix. + +Library file names should use one or two words to describe the functions implemented in that file. For example: +- `aws_iot_mqtt_serialize.c`: Implements the MQTT library's packet serialization and deserialization functions. +- `aws_iot_clock_posix.c`: Implements the platform clock component for POSIX systems. + +Declarations of internal functions, structures, macros, etc. of a library should be placed in a header file with an `_internal` suffix. The `_internal` header file should go in the `lib/include/private` directory. For example: +- `aws_iot_mqtt_internal.h`: Declares the MQTT library's internal functions, structures, macros, etc. + +File names for tests and demos should all begin with `aws_iot_demo_` and `aws_iot_tests_`, respectively. The names should then specify the library being demoed or or tested; for example, the files names of the MQTT library's demos and tests start with `aws_iot_demo_mqtt_` and `aws_iot_tests_mqtt_`. Additionally, test file names should describe what tests are implemented in the file, such as `aws_iot_tests_mqtt_api.c` for a file containing tests for the MQTT library API functions. + +@subsection guide_developer_styleguide_naming_functions Functions (and function-like macros) +@brief Naming convention of functions and function-like macros. + +@formattable{functions} +@formattableentry{`AwsIot`Library`_`Description,Externally-visible library function,`AwsIotMqtt_Publish`} +@formattableentry{`AwsIot`Library`Internal_`Description,Internal (but not `static`) library function,`AwsIotMqttInternal_ValidateNetIf`} +@formattableentry{`AwsIotTest`Library`_`Description,Library test-only function,`AwsIotTest_NetworkConnect`
`AwsIotTestMqtt_createMqttConnection`} +@formattableentry{`AwsIotDemo_`Library,Library demo function,`AwsIotDemo_Mqtt`} +@formattableentry{`_`description,`static` function,`_createMqttConnection`} +@formattableentry{`AwsIotTest`Library`_`accessedFunction,Test access function,`AwsIotTestMqtt_createMqttConnection`} + +Externally visible (i.e. not `static`) functions are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case) and must begin with `AwsIot`. Function names should then specify their library name; followed by an underscore; followed by a brief description of what the function does. Internal library functions that are not `static` should have the word `Internal` after the library name. For example: +- `AwsIotMqtt_Publish`: This function is part of the MQTT library. It Publishes an MQTT message. +- `AwsIotMqttInternal_ValidateNetIf`: This function is internal to the MQTT library, but not `static`. It validates an `AwsIotMqttNetIf_t`. + +Functions not visible outside their source file (i.e. `static` functions) have names that are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and begin with an underscore. These function names do not contain the library name or `AwsIot`. For example: +- `_createMqttConnection`: A `static` function in `aws_iot_mqtt_api.c`. + +Functions that are specific to the demos and tests begin with `AwsIotDemo` and `AwsIotTest`, respectively. Test access functions begin with `AwsIotTest`; followed by the library name; followed by an underscore; followed by the function that the test function accesses. Since the accessed function is always `static`, the accessed function will be [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). + +@subsection guide_developer_styleguide_naming_types Types +@brief Naming conventions of library `typedef` types. + +@formattable{types} +@formattableentry{`AwsIot`LibraryDescription`_t`,General types in application-facing library header files,`AwsIotMqttConnection_t` (aws_iot_mqtt.h)} +@formattableentry{`AwsIot`LibraryFunction`Info_t`,Application-facing parameter structure,`AwsIotMqttPublishInfo_t`
(Parameter structure to `AwsIotMqtt_Publish`)} +@formattableentry{`_`libraryDescription`_t`,Type in an `internal` header,`_mqttOperation_t` (aws_iot_mqtt_internal.h)} +@formattableentry{`_`description`_t`,Internal type in source file,`_topicMatchParams_t` (aws_iot_mqtt_subscription.c)} + +Types intended for use in applications are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `AwsIot` and end with `_t`. Parameter structures must indicate their associated function: for example, `AwsIotMqttPublishInfo_t` is passed as a parameter to `AwsIotMqtt_Publish`. + +Types intended for internal library use defined in a header file are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `_`, followed by the library name, and end with `_t`. Internal types defined in a library source file must start with `_`, end with `_t`, and not include the library name. + +`struct` typedefs should always be named along with the `typedef`. The struct name should be identical to the `typedef` name, but without the `_t` at the end. For example: +@code{c} +typedef struct AwsIotLibraryStruct +{ + int member; +} AwsIotLibraryStruct_t; + +typedef struct _libraryInternalStruct +{ + int member; +} _libraryInternalStruct_t; +@endcode + +A `struct` may contain anonymous `struct` or `union` members. + +@subsection guide_developer_styleguide_naming_variables Variables +@brief Naming conventions of variables. + +@formattable{variables} +@formattableentry{variableDescription,General local variable,`startTime`} +@formattableentry{`p`VariableDescription,Local variable pointers and arrays (including strings),`pSubscriptionList`} +@formattableentry{`_`variableDescription,Global variable that is `static`,`_connectMutex` (aws_iot_mqtt_api.c)
`_pSamplePayload` (string)} +@formattableentry{`_AwsIot`LibraryDescription,Global variable that is NOT `static`,`_AwsIotMqttSendQueue` (aws_iot_mqtt_operation.c)
`_pAwsIotSamplePayload` (string)} + +Local variable names are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and consist only of a description of the variable. Names like `i` or `j` are acceptable for loop counters, but all other variables should have a descriptive name. + +Global variable names always start with a `_`. Global variables that are `static` consist of only the description in [lowerCamelCase](https://en.wikipedia.org/wiki/Camel_case). Global variables that are not static consist of the library name and the description in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case); for example: `_AwsIotLibraryNonStaticGlobalVariable`. + +All pointers, arrays, and strings (both global and local) start with `p` (local) or `_p` (global). +*/ diff --git a/doc/lib/logging.txt b/doc/lib/logging.txt new file mode 100644 index 0000000000..b236c8a246 --- /dev/null +++ b/doc/lib/logging.txt @@ -0,0 +1,118 @@ +/** +@mainpage Logging +@anchor logging +@brief Generate and print log messages.

+ +This library allows other libraries to generate and print log messages, which can aid in debugging. Log messages are printed by passing strings to one of the [logging functions]( @ref logging_functions). The features of this library include: +- [Configurable levels](@ref logging_constants_levels) for log messages and libraries. +- Automatic printing of log level, library name, and time with every message. The information printed with each message [may be customized](@ref AwsIotLogConfig_t). +- A [function to print the contents of buffers](@ref logging_function_printbuffer) when using the [debug](@ref AWS_IOT_LOG_DEBUG) log level. This allows the contents of memory to be easily examined. + +@dependencies{logging,logging library} + +@dot "Logging direct dependencies" +digraph logging_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + logging[label="Logging", fillcolor="#aed8a9ff"]; + subgraph + { + rank = same; + platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; + platform_static_memory[label="Static memory", fillcolor="#e89025ff" URL="@ref platform_static_memory"]; + } + standard_library[label="Standard library\nstdarg, stdbool, stddef,\nstdint, stdio, string", fillcolor="#d15555ff"]; + logging -> platform_clock; + logging -> platform_static_memory [style="dashed", label=" if static memory only"]; + logging -> standard_library; +} +@enddot + +Currently, the logging library has the following dependencies: +- The [platform clock component](@ref platform_clock) for [generating the timestring](@ref platform_clock_function_gettimestring) to print in log messages. +- When @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, the logging library depends on the [platform static memory component](@ref platform_static_memory) to allocate buffers for log messages. When @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`, the logging library will default to using the standard library's [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) functions. The logging library's memory allocation functions [may always be overridden](@ref logging_config_memory). Note that the logging library will silently discard logs if it fails to allocate memory for the message. + +@section logging_setup_use Setup and use +@brief How to set up and use the logging library. + +The file aws_iot_logging_setup.h should be included to configure logging for a single source file. Before including aws_iot_logging_setup.h, the constants @ref _LIBRARY_LOG_LEVEL and @ref _LIBRARY_LOG_NAME must be defined. + +For example, to configure all the "SAMPLE" library to print all messages below the [info](@ref AWS_IOT_LOG_INFO) log level: + +@code{c} +// Print log messages up to the "info" level. +#define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_INFO + +// Print library name "SAMPLE". +#define _LIBRARY_LOG_NAME "SAMPLE" + +// Including this header defines the logging macros using _LIBRARY_LOG_LEVEL and +// _LIBRARY_LOG_NAME. +#include "aws_iot_logging_setup.h" + +int main( void ) +{ + // After including aws_iot_logging_setup.h, the logging functions can be used. + AwsIotLogError( "Error." ); // Will be printed because AWS_IOT_LOG_ERROR <= _LIBRARY_LOG_LEVEL + AwsIotLogWarn( "Warning. " ); // Will be printed because AWS_IOT_LOG_WARN <= _LIBRARY_LOG_LEVEL + AwsIotLogInfo( "Info." ); // Will be printed because AWS_IOT_LOG_INFO <= _LIBRARY_LOG_LEVEL + AwsIotLogDebug( "Debug." ); // Will not be printed because AWS_IOT_LOG_DEBUG > _LIBRARY_LOG_LEVEL + + return 0; +} +@endcode + +The code above will print something like the following: + +@code +[ERROR][SAMPLE][2018-01-01 12:00:00] Error. +[WARN ][SAMPLE][2018-01-01 12:00:00] Warning. +[INFO ][SAMPLE][2018-01-01 12:00:00] Info. +@endcode +*/ + +/** +@configpage{logging,logging library} + +@section _LIBRARY_LOG_LEVEL +@brief The log level for a source file. + +Sets the log level for a single source file. A log message will only be printed if its level is less than this setting. + +Both this constant and @ref _LIBRARY_LOG_NAME must be defined before including aws_iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. + +@configpossible One of the @ref logging_constants_levels. +@note This value must be defined if aws_iot_logging_setup.h is included. The library will not provide a default value for this setting. + +@section _LIBRARY_LOG_NAME +@brief The library name printed in log messages. + +Sets the library name for a single source file. By default, all log messages contain the library name. The library name may be disabled for a single log message by setting passing an #AwsIotLogConfig_t with [hideLibraryName](@ref AwsIotLogConfig_t.hideLibraryName) set to `true`. + +Both this constant and @ref _LIBRARY_LOG_LEVEL must be defined before including aws_iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. + +@configpossible Any string. +@note This value must be defined if aws_iot_logging_setup.h is included. The library will not provide a default value for this setting. + +@section AwsIotLogging_Puts +@brief Logging library output function. + +The logging library calls this function to print strings. Like the standard library's [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function, this function should write a newline after the string, as log messages may not end with a newline. This setting provided the flexibility to log over different channels. For example, if no `stdout` is available, this function may be set to log over other channels such as UART or a network. + +Although this function is supposed to return an `int`, the logging library does not check its return value. Therefore, a function with no return type is also acceptable. + +@configpossible Any function with the same parameter as the standard library's [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function. Since the logging library does not check the return value of this function, the return type may differ from [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html).
+@configdefault Standard library [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function. + +@section logging_config_memory Memory allocation +@brief If @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the logging library. +- #AwsIotLogging_Malloc
+ @copybrief AwsIotLogging_Malloc +- #AwsIotLogging_Free
+ @copybrief AwsIotLogging_Free +- #AwsIotLogging_StaticBufferSize
+ @copybrief AwsIotLogging_StaticBufferSize + +Note that the logging library will silently discard logs if it fails to allocate memory for the message. +*/ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt new file mode 100644 index 0000000000..1b14cef64f --- /dev/null +++ b/doc/lib/mqtt.txt @@ -0,0 +1,369 @@ +/** +@mainpage +@anchor mqtt +@brief MQTT 3.1.1 client library. + +> MQTT stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. The design principles are to minimise network bandwidth and device resource requirements whilst also attempting to ensure reliability and some degree of assurance of delivery. These principles also turn out to make the protocol ideal of the emerging "machine-to-machine" (M2M) or "Internet of Things" world of connected devices, and for mobile applications where bandwidth and battery power are at a premium. + +Official description of MQTT from [mqtt.org](http://mqtt.org)
+ +This MQTT library implements a subset of the MQTT 3.1.1 standard for compatibility with the [AWS IoT MQTT server](https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt). It is also compatible with other MQTT servers. Features of this library include: +- Both fully asynchronous and blocking API functions. +- Thread-aware and parallelizable for high throughput. +- Scalable performance and footprint. The [configuration settings](@ref mqtt_config) allow this library to be tailored to a system's resources. + +@attention Currently, this library does not support QoS 2 MQTT messages. + +@dependencies{mqtt,MQTT library} +@dot "MQTT direct dependencies" +digraph mqtt_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph + { + mqtt[label="MQTT", fillcolor="#cc00ccff"]; + } + subgraph + { + node[fillcolor="#aed8a9ff"]; + rank = same; + queue[label="Queue", URL="@ref queue"]; + logging[label="Logging", URL="@ref logging"]; + } + subgraph + { + rank = same; + platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; + platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; + platform_network[label="Network", fillcolor="#e89025ff", URL="@ref platform_network"]; + platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; + } + mqtt -> queue; + mqtt -> logging [label=" if logging enabled", style="dashed"]; + mqtt -> platform_threads; + mqtt -> platform_clock; + mqtt -> platform_network; + mqtt -> platform_static_memory [label=" if static memory only", style="dashed"]; + queue -> platform_threads; + logging -> platform_clock; + logging -> platform_static_memory [label=" if static memory only", style="dashed"]; +} +@enddot + +Currently, the MQTT library has the following dependencies: +- The queue library for maintains the data structures for managing in-progress MQTT operations. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_MQTT is not @ref AWS_IOT_LOG_NONE. +- The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. + +In addition to the components above, the MQTT library also depends on C standard library headers. +*/ + +/** +@page mqtt_design Design +@brief Architecture behind the MQTT library. + +This library uses threads to process outgoing and incoming MQTT operations, and queues to store MQTT operations waiting to be processed. +- [MQTT API functions](@ref mqtt_functions) only enqueue a waiting operation on the Send Queue. API functions return after successfully enqueuing an operation and do not block to wait for the result of the operation. +- Send threads are created when operations arrive in the send queue. These threads remove operations from the send queue in FIFO order and transmit MQTT packets across the network. After transmitting the packet, send threads add operations awaiting responses from the server to the Receive Queue. The setting @ref AWS_IOT_MQTT_MAX_SEND_THREADS controls the maximum number of simultaneous send threads. +- When a response is received from the server, the system's network stack invokes the [MQTT receive callback](@ref mqtt_function_receivecallback). This callback parses the server response, matches the incoming data with an operation in the receive queue, and enqueues the operation in the Callback Queue. +- Callback threads are created when operations arrive in the callback queue. These threads handle notification of an MQTT operation's result. The notification is handled by a separate thread (not the [receive callback](@ref mqtt_function_receivecallback)) to allow [callback functions](@ref AwsIotMqttCallbackInfo_t.function) to block without slowing down the receive callback. The setting @ref AWS_IOT_MQTT_MAX_CALLBACK_THREADS controls the maximum number of simultaneous callback threads. +- Periodically, timer threads will be created to send keep-alive packets and retransmit unacknowledged QoS 1 PUBLISH operations. + +Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the MQTT library on-the-go on some systems, while other systems may use an always-allocated thread pool. + +The sequence diagram below illustrates the workflow described above. The application thread is able to continue executing while the MQTT library processes the operation. + +@image html mqtt_design_typicaloperation.png width=100% +*/ + +/** +@page mqtt_demo Demo +@brief The MQTT demo demonstrates usage of the MQTT library. + +The MQTT demo demonstrates the subscribe-publish workflow of MQTT. After subscribing to multiple topic filters, it publishes [bursts](@ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) of data to various topic names. The demo then waits for all messages in a burst to be received on a topic filter. As each message arrives, the demo publishes an acknowledgement message back to the MQTT server. It repeats this cycle @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times. + +@image html mqtt_demo.png "MQTT Demo Workflow" width=80% + +See @subpage mqtt_demo_config for configuration settings that change the behavior of the demo. + +The main MQTT demo file is aws_iot_demo_mqtt.c, which contains platform-independent code. See @ref building_demo for instructions on building the MQTT demo. +*/ + +/** +@page mqtt_tests Tests +@brief Tests written for the MQTT library. + +The MQTT tests reside in the `tests/mqtt` directory. They are divided into the following subdirectories: +- `access`: Helper files that allow access to internal variables and functions of the MQTT library. +- `system`: MQTT system and stress tests. These tests require a network connection. Stress tests may run for a long time, so they are not run unless the command line option `-l` is given to the test executable. +- `unit`: MQTT unit tests. These tests do not require a network connection. + +See @subpage mqtt_tests_config for configuration settings that change the behavior of the tests. + +The current MQTT tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. See @ref building_tests for a guide on running the tests. +*/ + +/** +@configpage{mqtt_tests,MQTT tests,Test,tests} + +@section AWS_IOT_TEST_MQTT_MOSQUITTO +@brief Test the MQTT library against the [public Mosquitto test server](https://test.mosquitto.org/). + +When this setting is `1`, the MQTT tests will be built to test against an unsecured [public Mosquitto test server](https://test.mosquitto.org/). This allows the MQTT library to be tested against a fully-compliant MQTT server. Because the connection is unsecured, no credentials are needed for testing against Mosquitto. + +@configpossible `0` (use AWS IoT MQTT server) or `1` (use public Mosquitto server)
+@configdefault `0` + +
The settings below only affect the [system](@ref aws_iot_tests_mqtt_system.c) and [stress](@ref aws_iot_tests_mqtt_stress.c) tests.
+ +@section AWS_IOT_TEST_MQTT_TIMEOUT_MS +@brief Timeout in milliseconds for MQTT operations. + +This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait (and similar functions requiring a timeout). Ensure that this value is large enough to accommodate delays caused by the network. + +@configpossible Any non-negative integer.
+@configrecommended This setting should be at least `1000`.
+@configdefault `5000` + +@section AWS_IOT_TEST_MQTT_TOPIC_PREFIX +@brief The string prepended to topic filters and client identifiers. + +This string will be prepended as the common part of topic filters and client identifiers as a way to differentiate MQTT traffic generated by the tests. + +@configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
+@configdefault `"awsiotmqtttest"` + +
The settings below only affect the [stress](@ref aws_iot_tests_mqtt_stress.c) tests.
+ +@section AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S +@brief The keep-alive interval for the stress tests. + +MQTT PINGREQ packets will be sent at this interval. + +@configpossible Any positive integer.
+@configrecommended This value should be the shortest keep-alive interval supported by the connection.
+@configdefault `30` when using the AWS IoT MQTT server; `5` otherwise + +@section AWS_IOT_TEST_MQTT_RETRY_MS +@brief The value of #AwsIotMqttPublishInfo_t.retryMs used in the stress tests. + +Any lost publish messages will be retransmitted at this time. + +@configpossible Any positive integer.
+@configrecommended This setting should be at least `250` to avoid excessive network congestion caused by retransmissions.
+@configdefault `350` + +@section AWS_IOT_TEST_MQTT_RETRY_LIMIT +@brief The value of #AwsIotMqttPublishInfo_t.retryLimit used in the stress tests. + +Any lost publish messages will be retried up to this limit. + +@configpossible Any positive integer.
+@configdefault `32` + +@section AWS_IOT_TEST_MQTT_DECONGEST_S +@brief The time to wait for resources to be freed. + +Should an MQTT operation fail due to insufficient resources (such as a return value of @ref AWS_IOT_MQTT_NO_MEMORY), the test thread will sleep for this amount to time before retrying to wait for resources to be freed. Larger values for this setting provide a higher change for a successful retry, but will cause the tests to run longer. + +@configpossible Any positive integer.
+@configdefault `30` + +@section AWS_IOT_TEST_MQTT_THREADS +@brief The number of threads to use in the stress tests. + +The number of threads to spawn for the stress tests. Up to @ref AWS_IOT_TEST_MQTT_THREADS `*` @ref AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. + +@configpossible Any positive integer.
+@configdefault `16` + +@section AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD +@brief The number of publish messages each thread in the stress test should send. + +Up to @ref AWS_IOT_TEST_MQTT_THREADS `*` @ref AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. + +@configpossible Any non-negative integer.
+@configdefault When @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, this value defaults to @ref AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. Otherwise, it defaults to `100`. +*/ + +/** +@configpage{mqtt_demo,MQTT demo,Demo,demos} + +@section AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER +@brief The MQTT client identifier to use for the demo. + +No two clients may connect using the same client identifier simultaneously. + +The MQTT client identifier may also be set with [the command line option -i](@ref demo_optioni). The command line option will override this setting. If this setting is undefined and no command line option is provided, the demo will generate a unique client identifier to use. + +@configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
+@configdefault The demo will generate a unique client identifier if this setting is undefined. + +@section AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE +@brief The number of messages published in each burst. + +Messages in a burst are rapidly published. After a complete burst is published, the demo waits for the messages to be received on a subscription topic filter. This value may be increased for higher throughput, at the expense of an increased chance of dropped messages. The MQTT demo publishes a total of @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. + +@configpossible Any positive integer.
+@configdefault `10` + +@section AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT +@brief The number of [publish bursts](@ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) in this demo. + +Each burst will rapidly publish @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE messages. After publishing, the demo will wait for the published messages to be received on a subscription topic filter. + +This setting can be increased for a longer demo. The MQTT demo publishes a total of @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. + +@configpossible Any positive integer.
+@configdefault `10` +*/ + +/** +@configpage{mqtt,MQTT library} + +@section AWS_IOT_MQTT_ENABLE_ASSERTS +@brief Set this to `1` to perform sanity checks when using the MQTT library. + +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotMqtt_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. + +@configpossible `0` (asserts disabled) or `1` (asserts enabled)
+@configrecommended `1` when debugging; `0` in production code.
+@configdefault `0` + +@section AWS_IOT_MQTT_ENABLE_METRICS +@brief Set this to `1` to enable anonymous metrics reporting to AWS IoT. + +Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SDK name and version are reported; no personal or identifying information is reported. + +@configpossible `0` (metrics reporting disabled) or `1` (metrics reporting enabled)
+@configrecommended `1`
+@configdefault `1` +@note This setting is only in effect for [MQTT connections with AWS IoT](@ref AwsIotMqttConnectInfo_t.awsIotMqttMode). Metrics are reported through [the MQTT username.](@ref AwsIotMqttConnectInfo_t.pUserName) The MQTT library may overwrite any provided user name if metrics are enabled. + +@section AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES +@brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. + +Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #AwsIotMqttNetIf_t for a list of functions that can be overridden. If this setting is `1`, the serializer [initialization](@ref AwsIotMqttInternal_InitSerialize) and [cleanup](@ref AwsIotMqttInternal_CleanupSerialize) functions may be extended by defining `AwsIotMqttInternal_InitSerializeAdditional` and `AwsIotMqttInternal_CleanupSerializeAdditional`. These functions will be called along with the default serializer initialization and cleanup functions. They must have the following signatures: +@code{c} +#include + +// Returns true on success and false on failure. +bool AwsIotMqttInternal_InitSerializeAdditional( void ); +void AwsIotMqttInternal_CleanupSerializeAdditional( void ); +@endcode + +@configpossible `0` (serializer overrides disabled) or `1` (serializer overrides enabled)
+@configrecommended The default value is strongly recommended.
+@configdefault The default value of this setting depends on the platform. For example, when running on FreeRTOS with BLE support, this setting will be automatically enabled. Conversely, when running on Linux, this setting will be disabled. + +@section AWS_IOT_MQTT_MAX_CALLBACK_THREADS +@brief Set the maximum number of simultaneous callback threads. + +Callback threads process notifications from completed MQTT operations or incoming PUBLISH messages. Incoming notifications are queued, then removed from the queue and processed by any available callback threads. User callback functions are invoked from the callback threads. This means that user callback functions should avoid blocking, since blocking will make a callback thread unavailable and cause the queue of notifications to grow. A situation where all callback threads are blocked should certainly be avoided. + +@configpossible Any positive integer.
+@configrecommended In most use cases, this value can be `1` or `2`. In high-throughput use cases with many MQTT operations or incoming PUBLISH messages, this value can be increased for better performance.
+@configdefault `2` + +@section AWS_IOT_MQTT_MAX_SEND_THREADS +@brief Set the number of simultaneous send threads. + +Send threads process outgoing network packets. Packets are queued, then removed from the queue and sent over the network by any available send threads. + +@configpossible Any positive integer
+@configrecommended `1`. Unless the [network interface send function](@ref AwsIotMqttNetIf_t.send) supports parallel sends, any value greater than `1` will not provide any performance benefit.
+@configdefault `1` + +@section AWS_IOT_LOG_LEVEL_MQTT +@brief Set the log level of the MQTT library. + +Log messages from the MQTT library at or below this setting will be printed. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. + +@section AWS_IOT_MQTT_RESPONSE_WAIT_MS +@brief A "reasonable amount of time" to wait for keep-alive responses from the MQTT server. + +The MQTT spec states that if a response to a keep-alive request is not received within a "reasonable amount of time", the network connection should be closed. Since the meaning of "reasonable" depends heavily on use case, the amount of time to wait for keep-alive responses is defined by this setting. This setting is also used as the amount of time to wait for a final PUBACK to a QoS 1 PUBLISH message before returning #AWS_IOT_MQTT_RETRY_NO_RESPONSE. See #AwsIotMqttPublishInfo_t for a description of QoS 1 PUBLISH retries. + +@configpossible Any positive integer.
+@configdefault `1000` + +@section AWS_IOT_MQTT_RETRY_MS_CEILING +@brief Controls the maximum [retry interval](@ref AwsIotMqttPublishInfo_t.retryMs) of QoS 1 PUBLISH retransmissions. + +QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. The interval of time between retransmissions increases exponentially until @ref AWS_IOT_MQTT_RETRY_MS_CEILING (or the [retransmission limit)](@ref AwsIotMqttPublishInfo_t.retryLimit) is reached, then increases by @ref AWS_IOT_MQTT_RETRY_MS_CEILING until the [retransmission limit](@ref AwsIotMqttPublishInfo_t.retryLimit) is reached. + +@see #AwsIotMqttPublishInfo_t for a detailed description of the QoS 1 PUBLISH retransmission strategy. + +@configpossible Any positive integer.
+@configdefault `60000` + +@section AWS_IOT_MQTT_TEST +@brief Set this to `1` to enable test access for the MQTT library. + +This setting is used by the [MQTT tests](@ref mqtt_tests) to access the internal `static` functions and variables of the MQTT library. When `1`, it enables the @c \#include directives at the bottom of the [MQTT library source files](@ref lib/source/mqtt). These @c \#include directives include the test access files (located in `tests/mqtt/access`) that interact with the library's internal symbols. Unless the MQTT library is being tested, this setting should be `0`. + +@configpossible `0` (test access disabled) or `1` (test access enabled)
+@configrecommended `0`
+@configdefault `0` + +@section AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS +@brief Set the threshold for processing MQTT timer events in the future. + +MQTT timer events (such as keep-alive or PUBLISH retry) are sorted by their expiration time and place in a queue. A timer thread processes the events in this queue as each event's expiration time passes. To conserve memory, all MQTT connections (and therefore, all timer events for an MQTT connection) share a single system timer. The purpose of this setting is to avoid having an MQTT connection's timer expire too often. It allows the timer thread to process timer events before their expiration time if their expiration time is in the near future. + +Consider the following timer queue: +@dot +graph timer_event_example +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + subgraph + { + rank = same; + event1[label="Event 1\nExpiration time = NOW"]; + event2[label="Event 2\nExpiration time = NOW + 50"]; + event3[label="Event 3\nExpiration time = NOW + 150"]; + } + event1 -- event2; + event2 -- event3; +} +@enddot + +If @ref AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS is `100`, then both `Event 1` and `Event 2` will be processed now because their expiration times are within `100` of the current time. Because `Event 3` has an expiration time greater than `100` in the future, it will not be processed now. + +@configpossible Any non-negative integer.
+@configrecommended This value should be small to avoid premature processing of timer events. In almost no use case should this value be greater than `1000`.
+@configdefault `100` + +@section AwsIotMqtt_Assert +@brief Assertion function used when @ref AWS_IOT_MQTT_ENABLE_ASSERTS is `1`. + +@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
+@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. + +@section mqtt_config_memory Memory allocation +@brief If @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the MQTT library. +- #AwsIotMqtt_MallocConnection
+ @copybrief AwsIotMqtt_MallocConnection +- #AwsIotMqtt_FreeConnection
+ @copybrief AwsIotMqtt_FreeConnection +- #AwsIotMqtt_MallocMessage
+ @copybrief AwsIotMqtt_MallocMessage +- #AwsIotMqtt_FreeMessage
+ @copybrief AwsIotMqtt_FreeMessage +- #AwsIotMqtt_MallocOperation
+ @copybrief AwsIotMqtt_MallocOperation +- #AwsIotMqtt_FreeOperation
+ @copybrief AwsIotMqtt_FreeOperation +- #AwsIotMqtt_MallocSubscription
+ @copybrief AwsIotMqtt_MallocSubscription +- #AwsIotMqtt_FreeSubscription
+ @copybrief AwsIotMqtt_FreeSubscription +- #AwsIotMqtt_MallocTimerEvent
+ @copybrief AwsIotMqtt_MallocTimerEvent +- #AwsIotMqtt_FreeTimerEvent
+ @copybrief AwsIotMqtt_FreeTimerEvent +*/ diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt new file mode 100644 index 0000000000..5577d8af32 --- /dev/null +++ b/doc/lib/platform.txt @@ -0,0 +1,348 @@ +/** +@mainpage +@anchor platform +@brief The platform layer provides portability across different operating systems.

+ +All system calls (including networking) used in the AWS IoT libraries go through a lightweight platform layer. The functions of the platform layer are intended to be easily implementable on a wide variety of operating systems. The current platform layer has the following components: +- @ref platform_clock
+ @copybrief platform_clock +- @ref platform_static_memory
+ @copybrief platform_static_memory +- @ref platform_threads
+ @copybrief platform_threads +- @ref platform_network
+ @copybrief platform_network + +All of the functions in each component will need to be implemented to port the libraries to a new platform. Currently, implementations exist for the following platforms: +Component | Supported platforms +--------- | ------------------- +@ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) +@ref platform_static_memory | [POSIX](https://en.wikipedia.org/wiki/POSIX) +@ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) +@ref platform_network | [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) +*/ + +/** +@page platform_clock Clock +@brief @copybrief aws_iot_clock.h + +The platform clock component provides other libraries with functions relating to timers and clocks. It interfaces directly with the operation system to provide: +- Clocks for reading the current time. +- Timers that create a notification thread when they expire. + +@dependencies{platform_clock,platform clock component} +@dot "Clock direct dependencies" +digraph clock_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff" URL="@ref logging"]; } + subgraph + { + platform_clock[label="Clock", fillcolor="#e89025ff"]; + } + subgraph + { + rank = same; + rankdir = LR; + operating_system[label="Operating system", fillcolor="#999999ff"] + standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; + } + platform_clock -> operating_system; + platform_clock -> standard_library; + platform_clock -> logging [label=" if logging enabled", style="dashed"]; +} +@enddot + +Currently, the platform clock component has the following dependencies: +- The operating system must provide the necessary APIs to implement the clock component's functions. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. +*/ + +/** +@page platform_network Networking +@brief @copybrief aws_iot_network.h + +The platform networking component provides other libraries with functions for managing network connections. It interfaces directly with the operating system to provide: +- Functions for opening and closing network connections; function for sending and receiving data. +- Functions for securing network connections. + +@dependencies{platform_network,platform networking component} +@dot "Networking direct dependencies" +digraph network_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff", URL="@ref logging"]; } + subgraph + { + rank = same; + platform_network[label="Networking", fillcolor="#e89025ff"]; + platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; + } + subgraph + { + rank = same; + rankdir = LR; + operating_system[label="Operating system", fillcolor="#999999ff"]; + security_library[label="Security library", fillcolor="#999999ff"]; + standard_library[label="Standard library", fillcolor="#d15555ff"]; + } + platform_network -> platform_threads; + platform_network -> operating_system; + platform_network -> security_library [label=" secured connection", style="dashed"]; + platform_network -> standard_library; + platform_network -> logging [label=" if logging enabled", style="dashed"]; + security_library -> operating_system; +} +@enddot + +Currently, the networking component has the following dependencies: +- The operating system must provide the necessary networking APIs, such as a sockets API. +- A third-party security library is needed to encrypt secured connections. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. +*/ + +/** +@page platform_static_memory Static Memory +@brief @copybrief aws_iot_static_memory.h + +The platform static memory component provides statically-allocated buffers that are used instead of dynamic memory allocation when @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`. Using static memory only does not guarantee that memory allocation will always succeed; it's possible for all statically-allocated buffers to be in-use. However, static memory only can guarantee the availability of at least a certain amount of resources. Because space must be reserved for statically-allocated buffers, binaries compiled with static memory only will be larger. + +@section platform_static_memory_types Types of buffers +@brief The types of statically-allocated buffers provided by the static memory component. + +@subsection platform_static_memory_types_messagebuffers Message buffers +Message buffers are fixed-size buffers used for strings, such as log messages or bytes transmitted over a network. Their size and number can be configured with the constants @ref AWS_IOT_MESSAGE_BUFFERS (number) and @ref AWS_IOT_MESSAGE_BUFFER_SIZE (size of each message buffer). Message buffers may be used by any library, and are analogous to the generic buffers allocated by [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) (though all message buffers are the same size). + +@subsection platform_static_memory_mqtt MQTT static buffers +@brief Statically-allocated buffers used by the [MQTT library](@ref mqtt). + +@subsubsection platform_static_memory_types_mqttconnections Connections +MQTT connections correspond to a statically-allocated MQTT session between a single client and server. In static memory mode, the number of simultaneous, active MQTT connections is controlled by the constant @ref AWS_IOT_MQTT_CONNECTIONS. + +@subsubsection platform_static_memory_types_mqttoperations Operations +MQTT operations correspond to in-progress requests between a client and the MQTT server, such as CONNECT, PUBLISH, or publish acknowledgement (PUBACK). In static memory mode, the number of simultaneous, active MQTT operations is controlled by the constant @ref AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. + +@subsubsection platform_static_memory_types_mqtttimerevents Timer events +MQTT timer events store data on operations that require responses within a certain timeout: PINGREQ and QoS 1 PUBLISH. In static memory mode, the number of simultaneous, active MQTT timer events is controlled by the constant @ref AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. + +@subsubsection platform_static_memory_types_mqttsubscriptions Subscriptions +MQTT subscriptions store records on callbacks registered for MQTT topic filters. In static memory mode, the number of simultaneous, active MQTT subscriptions (across all connections) is controlled by the constant @ref AWS_IOT_MQTT_SUBSCRIPTIONS. + +@subsection platform_static_memory_shadow Shadow static buffers +@brief Statically-allocated buffers used by the [Shadow library](@ref shadow). + +@subsubsection platform_static_memory_types_shadowoperations Operations +Shadow operations correspond to in-progress Shadow [DELETE](@ref shadow_function_delete), [GET](@ref shadow_function_get), or [UPDATE](@ref shadow_function_update) operations. In static memory mode, the number of simultaneous, active Shadow operations is controlled by the constant @ref AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS. + +@subsubsection platform_static_memory_types_shadowsubscriptions Subscriptions +A Shadow subscriptions object groups MQTT subscriptions for a Thing. Shadow operations require pairs of subscriptions to determine their status (i.e. `/accepted` or `/rejected`); these subscriptions (as well as subscriptions for Shadow callbacks) are tracked by a Shadow subscriptions object. In static memory mode, the number of simultaneous, active Shadow subscriptions is controlled by the constant @ref AWS_IOT_SHADOW_SUBSCRIPTIONS. + +@dependencies{platform_static_memory,platform static memory component} +@dot "Static memory direct dependencies" +digraph static_memory_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + platform_static_memory[label="Static memory", fillcolor="#e89025ff"]; + subgraph + { + rank = same; + operating_system[label="Operating system", fillcolor="#999999ff"] + standard_library[label="Standard library\nstdbool, stddef, string", fillcolor="#d15555ff"]; + } + platform_static_memory -> operating_system; + platform_static_memory -> standard_library; +} +@enddot + +Currently, the platform static memory component has the following dependencies: +- The operating system must provide a mechanism to implement critical sections. +*/ + +/** +@page platform_threads Thread Management +@brief @copybrief aws_iot_threads.h + +The platform thread management component provides other libraries with functions relating to threading and synchronization. It interfaces directly with the operating system to provide: +- A function to create new threads. +- Synchronization mechanisms such as [mutexes](@ref AwsIotMutex_t) and [counting semaphores](@ref AwsIotSemaphore_t). + +@dependencies{platform_threads,platform thread management component} +@dot "Thread management direct dependencies" +digraph threads_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff" URL="@ref logging"]; } + subgraph + { + platform_threads[label="Thread management", fillcolor="#e89025ff"]; + } + subgraph + { + rank = same; + operating_system[label="Operating system", fillcolor="#999999ff"] + standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; + } + platform_threads -> operating_system; + platform_threads -> standard_library; + platform_threads -> logging [label=" if logging enabled", style="dashed"]; +} +@enddot + +Currently, the platform thread management component has the following dependencies: +- The operating system must provide the necessary APIs to implement the [thread management functions](@ref platform_threads_functions). +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. +*/ + +/** +@configpage{platform,platform layer} + +@section AWS_IOT_LOG_LEVEL_PLATFORM +@brief Set the log level of all platform components except the [networking component](@ref platform_network). + +This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the platform layer components that it affects. All log messages with a level at or below this setting will be printed. The [platform networking component](@ref platform_network) is generally more verbose than others, so its logging is controlled separately by @ref AWS_IOT_LOG_LEVEL_NETWORK. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. + +@section AWS_IOT_LOG_LEVEL_NETWORK +@brief Set the log level of the [platform networking component](@ref platform_network). + +This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the [platform networking component](@ref platform_network). All log messages with a level at or below this setting will be printed. See @ref AWS_IOT_LOG_LEVEL_PLATFORM to set the log level of other platform components. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. + +@section AWS_IOT_MUTEX_TYPE +@brief Set the type used to represent mutexes. + +@copydetails AwsIotMutex_t + +@configpossible Any C data type.
+@configdefault No default value is provided, but this setting will be automatically configured during build. + +@section AWS_IOT_SEMAPHORE_TYPE +@brief Set the type used to represent semaphores. + +@copydetails AwsIotSemaphore_t + +@configpossible Any C data type.
+@configdefault No default value is provided, but this setting will be automatically configured during build. + +@section AWS_IOT_TIMER_TYPE +@brief Set the type used to represent timers. + +@copydetails AwsIotTimer_t + +@configpossible Any C data type.
+@configdefault No default value is provided, but this setting will be automatically configured during build. + +@section AWS_IOT_MESSAGE_BUFFERS +@brief The number of statically-allocated [message buffers](@ref platform_static_memory_types_messagebuffers). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see @ref platform_static_memory_types_messagebuffers + +@configpossible Any positive integer.
+@configdefault `8` + +@section AWS_IOT_MESSAGE_BUFFER_SIZE +@brief The size (in bytes) of each statically-allocated [message buffer](@ref platform_static_memory_types_messagebuffers). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see @ref platform_static_memory_types_messagebuffers + +@configpossible Any positive integer.
+@configdefault `1024` + +@section AWS_IOT_MQTT_CONNECTIONS +@brief The number of statically-allocated [MQTT connections](@ref platform_static_memory_types_mqttconnections). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see [MQTT connections](@ref platform_static_memory_types_mqttconnections) + +@configpossible Any positive integer.
+@configdefault `1` + +@section AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS +@brief The number of statically-allocated [MQTT operations](@ref platform_static_memory_types_mqttoperations). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see [In-progress MQTT operations](@ref platform_static_memory_types_mqttoperations) + +@configpossible Any positive integer.
+@configdefault `10` + +@section AWS_IOT_MQTT_SUBSCRIPTIONS +@brief The number of statically-allocated [MQTT subscriptions](@ref platform_static_memory_types_mqttsubscriptions). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see [MQTT subscriptions](@ref platform_static_memory_types_mqttsubscriptions) + +@configpossible Any positive integer.
+@configdefault `8` + +@section AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS +@brief The number of statically-allocated [Shadow operations](@ref platform_static_memory_types_shadowoperations). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see [In-progress Shadow operations](@ref platform_static_memory_types_shadowoperations) + +@configpossible Any positive integer.
+@configdefault `10` + +@section AWS_IOT_SHADOW_SUBSCRIPTIONS +@brief The number of statically-allocated [Shadow subscriptions](@ref platform_static_memory_types_shadowsubscriptions). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. + +@see [Shadow subscriptions objects](@ref platform_static_memory_types_shadowsubscriptions) + +@configpossible Any positive integer.
+@configdefault `2` + +@section platform_config_memory Memory allocation +@brief Memory allocation function overrides for the platform layer. + +Some platform layers are not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. Currently, the following platform clock implementations require memory allocation: +- [POSIX](@ref aws_iot_clock_posix.c)
+ This implementation is not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + - #AwsIotClock_Malloc and #AwsIotClock_Free + - #AwsIotThreads_Malloc and #AwsIotThreads_Free. + - #AwsIotNetwork_Malloc and #AwsIotNetwork_Free. + +@section platform_config_posixheaders POSIX headers +@brief The POSIX platform layer allows the standard POSIX header includes to be overridden. Overrides only affect the POSIX platform layer. + +Any substitute headers are expected to provide the same functionality as the standard POSIX headers. The POSIX header overrides should only be used if the system's headers do not follow the standard names for POSIX headers. The POSIX headers may be overridden by defining the following settings: +Standard name | Setting +------------- | ------- +[errno.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html) | `POSIX_ERRNO_HEADER` +[limits.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) | `POSIX_LIMITS_HEADER` +[pthread.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) | `POSIX_PTHREAD_HEADER` +[signal.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html) | `POSIX_SIGNAL_HEADER` +[semaphore.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html) | `POSIX_SEMAPHORE_HEADER` +[time.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html) | `POSIX_TIME_HEADER` + +Example:
+To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HEADER` setting should be defined. +@code{c} +#define POSIX_ERRNO_HEADER "custom_errno.h" +@endcode + +@configpossible Strings representing file names. These files must be in the compiler's include path.
+@configdefault Standard POSIX header names. +*/ + +/** +@handles{platform,platform layer} +@enums{platform,platform layer} +@paramstructs{platform, platform layer} +*/ + +/** +@functionspage{platform,platform layer} + +- @subpage platform_clock_functions
+ @copybrief platform_clock_functions +- @subpage platform_network_functions
+ @copybrief platform_network_functions +- @subpage platform_static_memory_functions
+ @copybrief platform_static_memory +- @subpage platform_threads_functions
+ @copybrief platform_threads_functions +*/ diff --git a/doc/lib/queue.txt b/doc/lib/queue.txt new file mode 100644 index 0000000000..c5d7d3a6f7 --- /dev/null +++ b/doc/lib/queue.txt @@ -0,0 +1,63 @@ +/** +@mainpage +@anchor queue +@brief Queue and linked list data structures and functions.

+ +This library provides queue and linked list data structures which other libraries use for organizing data. Features of this library include: +- Double-ended notification queue that may also function as a doubly-linked list. +- Simple doubly-linked list. +- Queues and lists may hold any data types. + +@section queue_vs_list Queue vs List +@brief Comparison between #AwsIotQueue_t and #AwsIotList_t. + +This library provides two similar data structures, #AwsIotQueue_t (queue) and #AwsIotList_t (list). + +#AwsIotQueue_t is a double-ended "notify queue" that can create threads to process queued data. It may also be searched linearly, allowing it to function as a doubly-linked list. All functions that operate on an #AwsIotQueue_t provide thread safety. + +#AwsIotList_t implements a simpler doubly-linked list for storing data. It requires less memory than an #AwsIotQueue_t. However, unlike #AwsIotQueue_t, most of its functions do not provide thread safety. Only @ref list_function_removeallmatches provides thread safety. For all other list functions, #AwsIotList_t.mutex must be locked before the function call if thread safety is required. + +A comparison of these two data structures is below. +@anchor queue_vs_list_table | Queue | List +--------------------------- | ------------------------------------ | ---- +Thread safety | All queue functions are thread safe. | Only @ref list_function_removeallmatches is thread safe.
#AwsIotList_t.mutex must be locked before calling any other function if thread safety is required. +Data insertion | Data may only be [inserted at the head.](@ref queue_function_inserthead) | Data may be inserted [at the head](@ref list_function_inserthead) or [sorted into place.](@ref list_function_insertsorted) +Data searching | No in-place searches. Data can only be [search-and-removed.](@ref queue_function_removefirstmatch) | [Search function](@ref list_function_findfirstmatch) does not remove data. +Data removal | [Remove tail (FIFO)](@ref queue_function_removetail), [search and remove first matching data](@ref queue_function_removefirstmatch), or [search and remove all matching data](@ref queue_function_removeallmatches). | Remove data after [searching](@ref list_function_findfirstmatch) or [search and remove all matching data.](@ref list_function_removeallmatches) +Other | [Can provide notifications](@ref AwsIotQueueNotifyParams_t.notifyRoutine) when data is inserted. | Uses less memory than queue. + +@dependencies{queue,queue library} +@dot "Queue direct dependencies" +digraph queue_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + queue[label="Queue", fillcolor="#aed8a9ff"]; + platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; + standard_library[label="Standard library\nstdbool, stddef, stdint, string", fillcolor="#d15555ff"]; + queue -> platform_threads; + queue -> standard_library; +} +@enddot + +Currently, the queue library has the following dependencies: +- The [platform thread management component](@ref platform_threads) for creating notification threads when data is [inserted into a queue](@ref queue_function_inserthead). The queue library also depends on the mutexes provided by this component. +*/ + +/** +@configpage{queue,queue library} + +@section AWS_IOT_QUEUE_ENABLE_ASSERTS +@brief Set this to `1` to perform sanity checks when using queues and lists. + +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotQueue_Assert can be defined to set the assert function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. + +@configpossible `0` (asserts disabled) or `1` (asserts enabled)
+@configrecommended `1` when debugging; `0` in production code.
+@configdefault `0` + +@section AwsIotQueue_Assert +@brief Assertion function used when @ref AWS_IOT_QUEUE_ENABLE_ASSERTS is `1`. + +@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
+@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_QUEUE_ENABLE_ASSERTS is `1`; otherwise, nothing. +*/ diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt new file mode 100644 index 0000000000..cb68264be3 --- /dev/null +++ b/doc/lib/shadow.txt @@ -0,0 +1,65 @@ +/** +@mainpage +@anchor shadow +@brief AWS IoT Device Shadow library. + +> A device's shadow is a JSON document that is used to store and retrieve current state information for a device. The Device Shadow service maintains a shadow for each device you connect to AWS IoT. You can use the shadow to get and set the state of a device over MQTT or HTTP, regardless of whether the device is connected to the Internet. Each device's shadow is uniquely identified by the name of the corresponding thing. + +Description of Device Shadows from [AWS IoT documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html)
+*/ + +/** +@page shadow_demo Demo +@brief The Shadow demo demonstrates usage of the Shadow library. +*/ + +/** +@configpage{shadow,Shadow library} + +@section AWS_IOT_SHADOW_ENABLE_ASSERTS +@brief Set this to `1` to perform sanity checks when using the Shadow library. + +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotShadow_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. + +@configpossible `0` (asserts disabled) or `1` (asserts enabled)
+@configrecommended `1` when debugging; `0` in production code.
+@configdefault `0` + +@section AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS +@brief Set the default timeout (in milliseconds) for [MQTT library](@ref mqtt_functions) called by the Shadow library. + +If the `mqttTimeout` argument of @ref shadow_function_init is `0`, the Shadow library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_timedsubscribe, @ref mqtt_function_timedunsubscribe, and @ref mqtt_function_timedpublish to limit amount of time an MQTT function may block. + +@configpossible Any positive integer.
+@configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
+@configdefault `5000` + +@section AWS_IOT_LOG_LEVEL_SHADOW +@brief Set the log level of the Shadow library. + +Log messages from the Shadow library at or below this setting will be printed. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. + +@section AwsIotShadow_Assert +@brief Assertion function used when @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`. + +@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
+@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`; otherwise, nothing. + +@section shadow_config_memory Memory allocation +@brief If @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the Shadow library. +- #AwsIotShadow_MallocOperation
+ @copybrief AwsIotShadow_MallocOperation +- #AwsIotShadow_FreeOperation
+ @copybrief AwsIotShadow_FreeOperation +- #AwsIotShadow_MallocString
+ @copybrief AwsIotShadow_MallocString +- #AwsIotShadow_FreeString
+ @copybrief AwsIotShadow_FreeString +- #AwsIotShadow_MallocSubscription
+ @copybrief AwsIotShadow_MallocSubscription +- #AwsIotShadow_FreeSubscription
+ @copybrief AwsIotShadow_FreeSubscription +*/ diff --git a/doc/mainpage.txt b/doc/mainpage.txt new file mode 100644 index 0000000000..26495f1ccf --- /dev/null +++ b/doc/mainpage.txt @@ -0,0 +1,194 @@ +/** +@mainpage Overview +@brief C SDK providing secured connections to the AWS IoT platform.

+ +The AWS IoT Device SDK for C is a collection of [C99](https://en.wikipedia.org/wiki/C99) source files that allow applications to securely connect to the AWS IoT platform. It includes an [MQTT 3.1.1 client](../mqtt/index.html), as well as libraries specific to AWS IoT, such as [Thing Shadows](../shadow/index.html). It is distributed in source form and may be build into firmware along with application code. + +Design goals of this SDK include: +- Configurability
+ Each library in this SDK has Configuration Settings that allow it to be tailored to systems ranging from microcontrollers to desktop computers. +- Portability
+ All system calls go through a lightweight portability layer to allow this SDK to be easily ported to many systems. See [platform layer](../platform/index.html) for a list of functions that must be ported. +*/ + +/** +@globalconfigpage{library,Library,libraries} + +@section AWS_IOT_CONFIG_FILE +@brief Configuration header for the AWS IoT libraries. + +Define a file to be included in all library source files before any other files. Unlike other settings, this one must be set using a compiler option, such as `-D` for gcc. The option should specify a filename that is in the include path; for example, `-DAWS_IOT_CONFIG_FILE="aws_iot_config.h"` (quotes may need to be escaped for some compilers). + +@configpossible A string representing a file name. This file must be in the compiler's include path.
+@configdefault None. A configuration header will not be used if this value is undefined. + +@section AWS_IOT_LOG_LEVEL_GLOBAL +@brief Set a default log level. + +Any libraries that do not define a log level will use this setting. If a both a global log level and a library log level are defined, the library log level overrides the global one. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault #AWS_IOT_LOG_NONE + +@section AWS_IOT_STATIC_MEMORY_ONLY +@brief Set this to `1` to disable the usage of dynamic memory allocation ([malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html)) throughout the AWS IoT libraries. + +When dynamic memory allocation is disabled, all of the memory allocation functions used in the libraries must be re-implemented. See each library's Configuration/Memory allocation section for a list of functions that must be re-implemented for that library. + +@configpossible `0` (dynamic memory allocation enabled) or `1` (dynamic memory allocation disabled)
+@configrecommended `0`
+@configdefault `0` + +@attention This constant only affects the AWS IoT libraries. It has no effect on system calls or third-party libraries; it may also have no effect on [platform layer] (@ref platform) implementations. +*/ + +/** +@globalconfigpage{demos,Demo,demos} + +@section AWS_IOT_DEMO_SECURED_CONNECTION +@brief Determines if the demos default to a TLS-secured connection with the remote host. + +When this setting is `true`, all demo applications will use a TLS-secured connection by default. The default credentials for connections are @ref AWS_IOT_DEMO_ROOT_CA, @ref AWS_IOT_DEMO_CLIENT_CERT, and @ref AWS_IOT_DEMO_PRIVATE_KEY. Any connection with AWS IoT must be secured. + +In demo applications, the default secured connection setting can be overridden using the command line options `-s` or `-u`. Neither of these command line options have an argument, and only one of the two should be used at a time. Passing `-s` will cause the demo application to use a secured connection, and passing `-u` will cause the demo application to use an unsecured connection. + +@configpossible `true` (secured connection) or `false` (unsecured connection)
+@configrecommended When using the demo applications with AWS IoT, connections must be secured. Therefore, the recommended value for this setting is `true`.
+@configdefault If this setting is undefined and neither `-s` nor `-u` are passed as command line options, then any connections will be unsecured. + +@section AWS_IOT_DEMO_SERVER +@brief The default remote host to use in the demos. + +All demo applications will open TCP connections to this host. When using the demos with AWS IoT, this should be set to the AWS account's custom IoT endpoint. This custom endpoint can be found under the Settings tab of the AWS IoT console. It has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`, where `ABCDEFG1234567` is a placeholder subdomain and `us-east-2` is the region. + +In demo applications, the default server can be overridden using the command line option `-h`. The command line option will override this setting. + +No default value is provided for this setting. If this setting is undefined and no command line option `-h` is given to the demo application, the demo will fail. + +@configpossible Any string representing a hostname, such as +- `ABCDEFG1234567.iot.us-east-2.amazonaws.com` +- `192.168.1.1` +- `localhost` + +@section AWS_IOT_DEMO_PORT +@brief The default remote port to use in the demos. + +All demo applications will open TCP connections to @ref AWS_IOT_DEMO_SERVER on this port. When using the demos with AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). + +In demo applications, the default server port may be overridden using the command line option `-p`. The command line option will override this setting. + +No default value is provided for this setting. If this setting is undefined and no command line option `-p` is given to the demo application, the demo will fail. + +@configpossible Any positive integer below `65536`.
+@configrecommended When using the demo applications with AWS IoT, port `443` is recommended. + +@section AWS_IOT_DEMO_ROOT_CA +@brief The path to the default trusted server root certificate to use in the demos. + +A trusted server root certificate only needs to be set for [secured connections](@ref AWS_IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. + +In demo applications, the default trusted server root certificate can be overridden using the command line option `-r`. The command line option will override this setting. + +No default value is provided for this setting. If this setting is undefined and no command line option `-r` is given to a demo application using a secured connection, the demo will fail. + +@configpossible Any string representing a filesystem path to a PEM-encoded trusted server root certificate.
+@configrecommended When using the demo applications with AWS IoT, one of [these root certificates](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication) should be used. + +@section AWS_IOT_DEMO_CLIENT_CERT +@brief The path to the default client certificate to use in the demos. + +A client certificate only needs to be set for [secured connections](@ref AWS_IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. + +In demo applications, the default client certificate can be overridden with the command line option `-c`. The command line option will override this setting. + +No default value is provided for this setting. If this setting is undefined and no command line option `-c` is given to a demo application using a secured connection, the demo will fail. + +@configpossible Any string representing a filesystem path to a PEM-encoded client certificate. + +@section AWS_IOT_DEMO_PRIVATE_KEY +@brief The path to the default client certificate private key to use in the demos. + +A client certificate private key only needs to be set for [secured connections](@ref AWS_IOT_DEMO_SECURED_CONNECTION). This private key must match the [client certificate](@ref AWS_IOT_DEMO_CLIENT_CERT). + +In demo applications, the default client certificate private key can be overridden with the command line option `-k`. The command line option will override this setting. + +No default value is provided for this setting. If this setting is undefined and no command line option `-k` is given to a demo application using a secured connection, the demo will fail. + +@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref AWS_IOT_DEMO_CLIENT_CERT). + +@section AWS_IOT_DEMO_THING_NAME +@brief Default Thing Name used for demos specific to AWS IoT. + +Thing Names are used to manage devices in AWS IoT. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-thing-management.html) for more information. + +In demo applications, the default Thing Name can be overridden with the command line option `-i`. The command line option will override this setting. + +No default value is provided for this setting. For demos requiring a Thing Name (such as Shadow) this setting must be defined (or a command line option `-i` given to the demo application); otherwise, the demo will fail. + +This setting does not affect the MQTT demo (which is not specific to AWS IoT); see @ref AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER for the equivalent setting for the MQTT demo. + +@configpossible Any string representing an AWS IoT Thing Name. +*/ + +/** +@globalconfigpage{tests,Test,tests} + +@section AWS_IOT_TEST_SECURED_CONNECTION +@brief Determines if the tests use a TLS-secured connection with the remote host. + +When this setting is `true`, all test applications will use a TLS-secured connection. The credentials for connections are @ref AWS_IOT_TEST_ROOT_CA, @ref AWS_IOT_TEST_CLIENT_CERT, and @ref AWS_IOT_TEST_PRIVATE_KEY. Any connection with AWS IoT must be secured. + +@configpossible `true` (secured connection) or `false` (unsecured connection)
+@configrecommended When testing against AWS IoT, connections must be secured. Therefore, the recommended value for this setting is `true`.
+@configdefault `true` + +@section AWS_IOT_TEST_SERVER +@brief The remote host to use in the tests. + +Tests using the network will open TCP connections to this host. When testing against AWS IoT, this should be set to the AWS account's custom IoT endpoint. This custom endpoint can be found under the Settings tab of the AWS IoT console. It has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`, where `ABCDEFG1234567` is a placeholder subdomain and `us-east-2` is the region. + +No default value is provided for this setting. If this setting is undefined, the tests will fail to compile. + +@configpossible Any string representing a hostname, such as +- `ABCDEFG1234567.iot.us-east-2.amazonaws.com` +- `192.168.1.1` +- `localhost` + +@section AWS_IOT_TEST_PORT +@brief The remote port to use in the tests. + +Tests using the network will open TCP connections to @ref AWS_IOT_TEST_SERVER on this port. When testing against AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). + +No default value is provided for this setting. If this setting is undefined, the tests will fail to compile. + +@configpossible Any positive integer below `65536`.
+@configrecommended When testing against AWS IoT, port `443` is recommended. + +@section AWS_IOT_TEST_ROOT_CA +@brief The path to the trusted server root certificate to use in the tests. + +A trusted server root certificate only needs to be set for [secured connections](@ref AWS_IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. + +No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. + +@configpossible Any string representing a filesystem path to a PEM-encoded trusted server root certificate.
+@configrecommended When testing against AWS IoT, one of [these root certificates](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication) should be used. + +@section AWS_IOT_TEST_CLIENT_CERT +@brief The path to the client certificate to use in the tests. + +A client certificate only needs to be set for [secured connections](@ref AWS_IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. + +No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. + +@configpossible Any string representing a filesystem path to a PEM-encoded client certificate. + +@section AWS_IOT_TEST_PRIVATE_KEY +@brief The path to the client certificate private key to use in the tests. + +A client certificate private key only needs to be set for [secured connections](@ref AWS_IOT_TEST_SECURED_CONNECTION). This private key must match the [client certificate](@ref AWS_IOT_TEST_CLIENT_CERT). + +No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. + +@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref AWS_IOT_TEST_CLIENT_CERT). +*/ diff --git a/doc/plantuml/images/mqtt_demo.png b/doc/plantuml/images/mqtt_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..2ccdf79f7c90ebe5464e2b1b3da2c0b05ea69dd9 GIT binary patch literal 50307 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

Kn|MU%mAb%;hmFh_+8LCPO5Q7O1q4KDMts~y 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 bGIw?^f{xOH%@Y}g`lyVgqC~Ek{_Fn*s`R8Z literal 0 HcmV?d00001 diff --git a/doc/plantuml/images/mqtt_function_receivecallback_nopartial.png b/doc/plantuml/images/mqtt_function_receivecallback_nopartial.png new file mode 100644 index 0000000000000000000000000000000000000000..a083896560359b4a208b37751553b587ec12001d GIT binary patch literal 42752 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 server: Subscribe to topic filters +server -> main: Subscription ACK + +loop AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times + loop AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE times + main -> server: Publish "Hello world n!" + server -> main: Publish ACK + server -> server: Check for\nmatching subscriptions + server -> callback: Publish to\nmatching topic filter + callback -> server: Publish "Received message n" + callback -> main: Notify "Publish received" + server -> callback: Publish ACK + end + + main -> main: Wait for\nAWS_IOT_DEMO_MQTT_BURST_SIZE\nmessages to be received +end + +main -> server: Unsubscribe from topic filters +server -> main: Unsubscribe ACK + +main -> server: Disconnect + +@enduml diff --git a/doc/plantuml/mqtt_design_typicaloperation.pu b/doc/plantuml/mqtt_design_typicaloperation.pu new file mode 100644 index 0000000000..740209438e --- /dev/null +++ b/doc/plantuml/mqtt_design_typicaloperation.pu @@ -0,0 +1,71 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica +autonumber + +box "Application thread" #LightGreen + participant "Application" as application + create participant "MQTT API\nfunction" as api +end box + +database "Send queue" as send_queue + +== Enqueue Operation == +activate application +application -> api: Call MQTT\noperation\nfunction +deactivate application +activate api +api -> api: Allocate resources\nand generate\nMQTT packet +api -> send_queue: Add operation to\nsend queue +activate send_queue +api -> application: Return\nSTATUS_PENDING +destroy api +activate application + +== Transmit MQTT Packet == +create participant "Send thread" as send_thread +database "Receive queue" as receive_queue +participant "Network" as network + +send_queue -> send_thread: Remove oldest\noperation +activate send_thread +send_thread -> network: Transmit operation +activate network +send_thread -> receive_queue: Add operation to\nreceive queue +activate receive_queue +send_thread -> send_queue: Check for more\noperations +send_queue -> send_thread: Send queue empty +deactivate send_queue +destroy send_thread + +network -> network: Wait for\nserver response + +== Parse Server Response == +create participant "Receive\ncallback" as receive_callback +network -> receive_callback: Parse server\nresponse +deactivate network + +database "Callback queue" as callback_queue + +activate receive_callback +receive_callback -> receive_queue: Find operation\nwaiting for response +receive_queue -> receive_callback: Remove waiting\noperation +deactivate receive_queue +receive_callback -> callback_queue: Add operation to\ncallback queue +activate callback_queue +deactivate receive_callback + +== Notify Application of Result == +create participant "Callback\nthread" as callback_thread +callback_queue -> callback_thread: Remove oldest\noperation + +activate callback_thread +callback_thread -> application: Notify application\nof result +callback_thread -> callback_queue: Check for more\noperations +callback_queue -> callback_thread: Callback queue empty +deactivate callback_queue +destroy callback_thread + +deactivate application + +@enduml diff --git a/doc/plantuml/mqtt_function_receivecallback_nopartial.pu b/doc/plantuml/mqtt_function_receivecallback_nopartial.pu new file mode 100644 index 0000000000..32bf604ca8 --- /dev/null +++ b/doc/plantuml/mqtt_function_receivecallback_nopartial.pu @@ -0,0 +1,38 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica +autonumber + +participant "System" as system +participant "MQTT Receive Callback" as mqtt_receive +participant "Other MQTT functions" as mqtt_other + +-> system: Incoming bytes\nfrom network +activate system +system -> system: Allocate pReceivedData +group pReceivedData owned by system +system -> system: Read network bytes\ninto pReceivedData +end + +group pReceivedData owned by the MQTT library +system -> mqtt_receive: Call MQTT receive\ncallback +deactivate system + +activate mqtt_receive +mqtt_receive -> mqtt_receive: Process all bytes\nin pReceivedData +mqtt_receive -> mqtt_other: Pass processed data +activate mqtt_other +mqtt_receive -> system: Receive callback returns +deactivate mqtt_receive +activate system + +mqtt_other -> mqtt_other: Handle processed data +mqtt_other -> system: Call freeReceivedData +deactivate mqtt_other +end + +group pReceivedData owned by system +system -> system: Free pReceivedData +deactivate system +end +@enduml diff --git a/doc/plantuml/mqtt_function_receivecallback_partial.pu b/doc/plantuml/mqtt_function_receivecallback_partial.pu new file mode 100644 index 0000000000..3814fbbf94 --- /dev/null +++ b/doc/plantuml/mqtt_function_receivecallback_partial.pu @@ -0,0 +1,40 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica +autonumber + +participant "System" as system +participant "MQTT Receive Callback" as mqtt_receive +participant "Other MQTT functions" as mqtt_other + +-> system: 1 complete packet\nand 1 partial packet\nfrom network +activate system +system -> system: Allocate pReceivedData +group pReceivedData owned by system +system -> system: Read packets into\npReceivedData +end + +group pReceivedData owned by the MQTT library +system -> mqtt_receive: Call MQTT receive\ncallback +deactivate system + +activate mqtt_receive +mqtt_receive -> mqtt_receive: Process complete packet\nin pReceivedData +mqtt_receive -> mqtt_other: Pass complete packet +activate mqtt_other +mqtt_receive -> mqtt_receive: Detect partial packet +mqtt_receive -> system: Return bytes processed +deactivate mqtt_receive +activate system +end +group pReceivedData owned by system +mqtt_other -> mqtt_other: Handle complete packet;\ndon't call freeReceivedData +deactivate mqtt_other +-> system: Remainder of partial\npacket from network +system -> system: Read remainder of\npacket in pReceivedData.\nOverwrite bytes processed. +end +group pReceivedData owned by MQTT library +system -> mqtt_receive: Call MQTT receive callback +deactivate system +end +@enduml diff --git a/external_libs/CppUTest/README.txt b/external_libs/CppUTest/README.txt deleted file mode 100644 index 5b2e09255b..0000000000 --- a/external_libs/CppUTest/README.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Copy source code for CppUTest into this directory -# The SDK was tested internally with CppUTest v3.6 which can be found here - https://github.com/cpputest/cpputest/releases/tag/v3.6 \ No newline at end of file diff --git a/external_libs/jsmn/jsmn.c b/external_libs/jsmn/jsmn.c deleted file mode 100644 index e920e86205..0000000000 --- a/external_libs/jsmn/jsmn.c +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2010 Serge A. Zaitsev - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * @file jsmn.c - * @brief Implementation of the JSMN (Jasmine) JSON parser. - * - * For more information on JSMN: - * @see http://zserge.com/jsmn.html - */ - -#include "jsmn.h" - -/** - * Allocates a fresh unused token from the token pull. - */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, - jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *tok; - if (parser->toknext >= num_tokens) { - return NULL; - } - tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, - int start, int end) { - token->type = type; - token->start = start; - token->end = end; - token->size = 0; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t' : case '\r' : case '\n' : case ' ' : - case ',' : case ']' : case '}' : - goto found; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - - int start = parser->pos; - - parser->pos++; - - /* Skip starting quote */ - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': case '/' : case '\\' : case 'b' : - case 'f' : case 'r' : case 'n' : case 't' : - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { - /* If it isn't a hex character we have an error */ - if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens) { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - return JSMN_ERROR_NOMEM; - if (parser->toksuper != -1) { - tokens[parser->toksuper].size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': case ']': - if (tokens == NULL) - break; - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if(token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) return JSMN_ERROR_INVAL; - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - case '\t' : case '\r' : case '\n' : case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - case 't': case 'f': case 'n' : - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} - diff --git a/external_libs/jsmn/jsmn.h b/external_libs/jsmn/jsmn.h deleted file mode 100644 index e0ffb1bc4b..0000000000 --- a/external_libs/jsmn/jsmn.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2010 Serge A. Zaitsev - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * @file jsmn.h - * @brief Definition of the JSMN (Jasmine) JSON parser. - * - * For more information on JSMN: - * @see http://zserge.com/jsmn.html - */ - -#ifndef __JSMN_H_ -#define __JSMN_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null - */ -typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 -} jsmntype_t; - -enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 -}; - -/** - * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string - */ -typedef struct { - jsmntype_t type; - int start; - int end; - int size; -#ifdef JSMN_PARENT_LINKS - int parent; -#endif -} jsmntok_t; - -/** - * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string - */ -typedef struct { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g parent object or array */ -} jsmn_parser; - -/** - * Create JSON parser over an array of tokens - */ -void jsmn_init(jsmn_parser *parser); - -/** - * Run JSON parser. It parses a JSON data string into and array of tokens, each describing - * a single JSON object. - */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens); - -#ifdef __cplusplus -} -#endif - -#endif /* __JSMN_H_ */ diff --git a/external_libs/mbedTLS/README.txt b/external_libs/mbedTLS/README.txt deleted file mode 100644 index a3ff466bae..0000000000 --- a/external_libs/mbedTLS/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Copy source code for mbedTLS into this directory -# -# You'll need to download mbedTLS from the official ARMmbed repository and -# place the files here. We recommend that you pick the latest version in -# order to have up-to-date security fixes. diff --git a/filterGcov.sh b/filterGcov.sh deleted file mode 100755 index fee4a03278..0000000000 --- a/filterGcov.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash -INPUT_FILE=$1 -TEMP_FILE1=${INPUT_FILE}1.tmp -TEMP_FILE2=${INPUT_FILE}2.tmp -TEMP_FILE3=${INPUT_FILE}3.tmp -ERROR_FILE=$2 -OUTPUT_FILE=$3 -HTML_OUTPUT_FILE=$3.html -TEST_RESULTS=$4 - -flattenGcovOutput() { -while read line1 -do - read line2 - echo $line2 " " $line1 - read junk - read junk -done < ${INPUT_FILE} -} - -getRidOfCruft() { -sed '-e s/^Lines.*://g' \ - '-e s/^[0-9]\./ &/g' \ - '-e s/^[0-9][0-9]\./ &/g' \ - '-e s/of.*File/ /g' \ - "-e s/'//g" \ - '-e s/^.*\/usr\/.*$//g' \ - '-e s/^.*\.$//g' -} - -flattenPaths() { -sed \ - -e 's/\/[^/][^/]*\/[^/][^/]*\/\.\.\/\.\.\//\//g' \ - -e 's/\/[^/][^/]*\/[^/][^/]*\/\.\.\/\.\.\//\//g' \ - -e 's/\/[^/][^/]*\/[^/][^/]*\/\.\.\/\.\.\//\//g' \ - -e 's/\/[^/][^/]*\/\.\.\//\//g' -} - -getFileNameRootFromErrorFile() { -sed '-e s/gc..:cannot open .* file//g' ${ERROR_FILE} -} - -writeEachNoTestCoverageFile() { -while read line -do - echo " 0.00% " ${line} -done -} - -createHtmlOutput() { - echo "" - echo "" - sed "-e s/.*% /
CoverageFile
&<\/td>/" \ - "-e s/[a-zA-Z0-9_]*\.[ch][a-z]*/&<\/a><\/td><\/tr>/" - echo "
" - sed "-e s/.*/&
/g" < ${TEST_RESULTS} -} - -flattenGcovOutput | getRidOfCruft | flattenPaths > ${TEMP_FILE1} -getFileNameRootFromErrorFile | writeEachNoTestCoverageFile | flattenPaths > ${TEMP_FILE2} -cat ${TEMP_FILE1} ${TEMP_FILE2} | sort | uniq > ${OUTPUT_FILE} -createHtmlOutput < ${OUTPUT_FILE} > ${HTML_OUTPUT_FILE} -rm -f ${TEMP_FILE1} ${TEMP_FILE2} diff --git a/include/aws_iot_error.h b/include/aws_iot_error.h deleted file mode 100644 index f0ccc3963d..0000000000 --- a/include/aws_iot_error.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_error.h - * @brief Definition of error types for the SDK. - */ - -#ifndef AWS_IOT_SDK_SRC_IOT_ERROR_H_ -#define AWS_IOT_SDK_SRC_IOT_ERROR_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* Used to avoid warnings in case of unused parameters in function pointers */ -#define IOT_UNUSED(x) (void)(x) - -/*! \public - * @brief IoT Error enum - * - * Enumeration of return values from the IoT_* functions within the SDK. - * Values less than -1 are specific error codes - * Value of -1 is a generic failure response - * Value of 0 is a generic success response - * Values greater than 0 are specific non-error return codes - */ -typedef enum { - /** Returned when the Network physical layer is connected */ - NETWORK_PHYSICAL_LAYER_CONNECTED = 6, - /** Returned when the Network is manually disconnected */ - NETWORK_MANUALLY_DISCONNECTED = 5, - /** Returned when the Network is disconnected and the reconnect attempt is in progress */ - NETWORK_ATTEMPTING_RECONNECT = 4, - /** Return value of yield function to indicate auto-reconnect was successful */ - NETWORK_RECONNECTED = 3, - /** Returned when a read attempt is made on the TLS buffer and it is empty */ - MQTT_NOTHING_TO_READ = 2, - /** Returned when a connection request is successful and packet response is connection accepted */ - MQTT_CONNACK_CONNECTION_ACCEPTED = 1, - /** Success return value - no error occurred */ - SUCCESS = 0, - /** A generic error. Not enough information for a specific error code */ - FAILURE = -1, - /** A required parameter was passed as null */ - NULL_VALUE_ERROR = -2, - /** The TCP socket could not be established */ - TCP_CONNECTION_ERROR = -3, - /** The TLS handshake failed */ - SSL_CONNECTION_ERROR = -4, - /** Error associated with setting up the parameters of a Socket */ - TCP_SETUP_ERROR = -5, - /** A timeout occurred while waiting for the TLS handshake to complete. */ - NETWORK_SSL_CONNECT_TIMEOUT_ERROR = -6, - /** A Generic write error based on the platform used */ - NETWORK_SSL_WRITE_ERROR = -7, - /** SSL initialization error at the TLS layer */ - NETWORK_SSL_INIT_ERROR = -8, - /** An error occurred when loading the certificates. The certificates could not be located or are incorrectly formatted. */ - NETWORK_SSL_CERT_ERROR = -9, - /** SSL Write times out */ - NETWORK_SSL_WRITE_TIMEOUT_ERROR = -10, - /** SSL Read times out */ - NETWORK_SSL_READ_TIMEOUT_ERROR = -11, - /** A Generic error based on the platform used */ - NETWORK_SSL_READ_ERROR = -12, - /** Returned when the Network is disconnected and reconnect is either disabled or physical layer is disconnected */ - NETWORK_DISCONNECTED_ERROR = -13, - /** Returned when the Network is disconnected and the reconnect attempt has timed out */ - NETWORK_RECONNECT_TIMED_OUT_ERROR = -14, - /** Returned when the Network is already connected and a connection attempt is made */ - NETWORK_ALREADY_CONNECTED_ERROR = -15, - /** Network layer Error Codes */ - /** Network layer Random number generator seeding failed */ - NETWORK_MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED = -16, - /** A generic error code for Network layer errors */ - NETWORK_SSL_UNKNOWN_ERROR = -17, - /** Returned when the physical layer is disconnected */ - NETWORK_PHYSICAL_LAYER_DISCONNECTED = -18, - /** Returned when the root certificate is invalid */ - NETWORK_X509_ROOT_CRT_PARSE_ERROR = -19, - /** Returned when the device certificate is invalid */ - NETWORK_X509_DEVICE_CRT_PARSE_ERROR = -20, - /** Returned when the private key failed to parse */ - NETWORK_PK_PRIVATE_KEY_PARSE_ERROR = -21, - /** Returned when the network layer failed to open a socket */ - NETWORK_ERR_NET_SOCKET_FAILED = -22, - /** Returned when the server is unknown */ - NETWORK_ERR_NET_UNKNOWN_HOST = -23, - /** Returned when connect request failed */ - NETWORK_ERR_NET_CONNECT_FAILED = -24, - /** Returned when there is nothing to read in the TLS read buffer */ - NETWORK_SSL_NOTHING_TO_READ = -25, - /** A connection could not be established. */ - MQTT_CONNECTION_ERROR = -26, - /** A timeout occurred while waiting for the TLS handshake to complete */ - MQTT_CONNECT_TIMEOUT_ERROR = -27, - /** A timeout occurred while waiting for the TLS request complete */ - MQTT_REQUEST_TIMEOUT_ERROR = -28, - /** The current client state does not match the expected value */ - MQTT_UNEXPECTED_CLIENT_STATE_ERROR = -29, - /** The client state is not idle when request is being made */ - MQTT_CLIENT_NOT_IDLE_ERROR = -30, - /** The MQTT RX buffer received corrupt or unexpected message */ - MQTT_RX_MESSAGE_PACKET_TYPE_INVALID_ERROR = -31, - /** The MQTT RX buffer received a bigger message. The message will be dropped */ - MQTT_RX_BUFFER_TOO_SHORT_ERROR = -32, - /** The MQTT TX buffer is too short for the outgoing message. Request will fail */ - MQTT_TX_BUFFER_TOO_SHORT_ERROR = -33, - /** The client is subscribed to the maximum possible number of subscriptions */ - MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR = -34, - /** Failed to decode the remaining packet length on incoming packet */ - MQTT_DECODE_REMAINING_LENGTH_ERROR = -35, - /** Connect request failed with the server returning an unknown error */ - MQTT_CONNACK_UNKNOWN_ERROR = -36, - /** Connect request failed with the server returning an unacceptable protocol version error */ - MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR = -37, - /** Connect request failed with the server returning an identifier rejected error */ - MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR = -38, - /** Connect request failed with the server returning an unavailable error */ - MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR = -39, - /** Connect request failed with the server returning a bad userdata error */ - MQTT_CONNACK_BAD_USERDATA_ERROR = -40, - /** Connect request failed with the server failing to authenticate the request */ - MQTT_CONNACK_NOT_AUTHORIZED_ERROR = -41, - /** An error occurred while parsing the JSON string. Usually malformed JSON. */ - JSON_PARSE_ERROR = -42, - /** Shadow: The response Ack table is currently full waiting for previously published updates */ - SHADOW_WAIT_FOR_PUBLISH = -43, - /** Any time an snprintf writes more than size value, this error will be returned */ - SHADOW_JSON_BUFFER_TRUNCATED = -44, - /** Any time an snprintf encounters an encoding error or not enough space in the given buffer */ - SHADOW_JSON_ERROR = -45, - /** Mutex initialization failed */ - MUTEX_INIT_ERROR = -46, - /** Mutex lock request failed */ - MUTEX_LOCK_ERROR = -47, - /** Mutex unlock request failed */ - MUTEX_UNLOCK_ERROR = -48, - /** Mutex destroy failed */ - MUTEX_DESTROY_ERROR = -49, - /** Input argument exceeded the allowed maximum size */ - MAX_SIZE_ERROR = -50, - /** Some limit has been exceeded, e.g. the maximum number of subscriptions has been reached */ - LIMIT_EXCEEDED_ERROR = -51, - /** Invalid input topic type */ - INVALID_TOPIC_TYPE_ERROR = -52 -} IoT_Error_t; - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_SDK_SRC_IOT_ERROR_H_ */ diff --git a/include/aws_iot_jobs_interface.h b/include/aws_iot_jobs_interface.h deleted file mode 100644 index 4f033d9d6a..0000000000 --- a/include/aws_iot_jobs_interface.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_jobs_interface.h - * @brief An interface for interacting with the AWS IoT Jobs system. - * - * This file defines utility functions for interacting with the AWS IoT jobs - * APIs over MQTT. It provides functions for managing subscriptions to job - * related topics and for sending queries and updates requests for jobs. - * Callers are responsible for managing the subscriptions and associating - * responses with the queries and update messages. - */ -#ifndef AWS_IOT_JOBS_INTERFACE_H_ -#define AWS_IOT_JOBS_INTERFACE_H_ - -#ifdef DISABLE_IOT_JOBS -#error "Jobs API is disabled" -#endif - -/** - * @file aws_iot_jobs_interface.h - * @brief Functions for interacting with the AWS IoT Jobs system. - */ -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_jobs_topics.h" -#include "aws_iot_jobs_types.h" -#include "aws_iot_error.h" -#include "aws_iot_json_utils.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Subscribe to jobs messages for the given thing and/or jobs. - * - * The function can be used to subscribe to all job related messages. To subscribe - * to messages not related to a job the jobId passed should be null. The jobId - * can also be "+" to subscribe to messages related to any job, or "$next" to - * indicate the next pending job. - * - * See also #aws_iot_jobs_subscribe_to_all_job_messages to subscribe to all possible - * messages in one operation. - * - * \note Subscribing with the same thing, job and topic type more than - * once is undefined. - * - * \param pClient the client to use - * \param qos the qos to use - * \param thingName the name of the thing to subscribe to - * \param jobId the job id to subscribe to. To subscribe to messages not related to - * a job the jobId passed should be null. The jobId can also be "+" to subscribe to - * messages related to any job, or "$next" to indicate the next pending job. - * \param topicType the topic type to subscribe to - * \param replyType the reply topic type to subscribe to - * \param pApplicationHandler the callback handler - * \param pApplicationHandlerData the callback context data. This must remain valid at least until - * aws_iot_jobs_unsubscribe_from_job_messages is called. - * \param topicBuffer. A buffer to use to hold the topic name for the subscription. This buffer - * must remain valid at least until aws_iot_jobs_unsubscribe_from_job_messages is called. - * \param topicBufferSize the size of the topic buffer. The function will fail - * with LIMIT_EXCEEDED_ERROR if this is too small. - * \return the result of subscribing to the topic (see aws_iot_mqtt_subscribe) - */ -IoT_Error_t aws_iot_jobs_subscribe_to_job_messages( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - AwsIotJobExecutionTopicType topicType, - AwsIotJobExecutionTopicReplyType replyType, - pApplicationHandler_t pApplicationHandler, - void *pApplicationHandlerData, - char *topicBuffer, - uint16_t topicBufferSize); - -/** - * @brief Subscribe to all job messages. - * - * Subscribe to all job messages for the given thing. - * - * \note Subscribing with the same thing more than once is undefined. - * - * \param pClient the client to use - * \param qos the qos to use - * \param thingName the name of the thing to subscribe to - * \param pApplicationHandler the callback handler - * \param pApplicationHandlerData the callback context data. This must remain valid at least until - * aws_iot_jobs_unsubscribe_from_job_messages is called. - * \param topicBuffer. A buffer to use to hold the topic name for the subscription. This buffer - * must remain valid at least until aws_iot_jobs_unsubscribe_from_job_messages is called. - * \param topicBufferSize the size of the topic buffer. The function will fail - * with LIMIT_EXCEEDED_ERROR if this is too small. - * \return the result of subscribing to the topic (see aws_iot_mqtt_subscribe) - */ -IoT_Error_t aws_iot_jobs_subscribe_to_all_job_messages( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - pApplicationHandler_t pApplicationHandler, - void *pApplicationHandlerData, - char *topicBuffer, - uint16_t topicBufferSize); - -/** - * @brief Unsubscribe from a job subscription - * - * Remove the subscription created using #aws_iot_jobs_subscribe_to_job_messages or - * #aws_iot_jobs_subscribe_to_all_job_messages. - * - * \param pClient the client to use - * \param topicBuffer the topic buffer passed to #aws_iot_jobs_subscribe_to_job_messages or - * #aws_iot_jobs_subscribe_to_all_job_messages when the subscription was created. - * \return #SUCCESS or the first error encountered. - */ -IoT_Error_t aws_iot_jobs_unsubscribe_from_job_messages( - AWS_IoT_Client *pClient, - char *topicBuffer); - -/** - * @brief Send a query to one of the job query APIs. - * - * Send a query to one of the job query APIs. If jobId is null this - * requests a list of pending jobs for the thing. If jobId is - * not null then it sends a query for the details of that job. - * If jobId is $next then it sends a query for the details for - * the next pending job. - * - * \param pClient the client to use - * \param qos the qos to use - * \param thingName the thing name to query for - * \param jobId the id of the job to query for. If null a list - * of all pending jobs for the thing is requested. - * \param clientToken the client token to use for the query. - * If null no clientToken is sent resulting in an empty message. - * \param topicBuffer the topic buffer to use. This may be discarded - * as soon as this function returns - * \param topicBufferSize the size of topicBuffer - * \param messageBuffer the message buffer to use. May be NULL - * if clientToken is NULL - * \param messageBufferSize the size of messageBuffer - * \param topicType the topic type to publish query to - * \return LIMIT_EXCEEDED_ERROR if the topic buffer or - * message buffer is too small, NULL_VALUE_ERROR if the any of - * the required inputs are NULL, otherwise the result - * of the mqtt publish - */ -IoT_Error_t aws_iot_jobs_send_query( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - const char *clientToken, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize, - AwsIotJobExecutionTopicType topicType); - -/** - * @brief Send a start next command to the job start-next API. - * - * Send a start next command to the job start-next API. - * - * \param pClient the client to use - * \param qos the qos to use - * \param thingName the thing name to query for - * \param startNextRequest the start-next request to send - * \param topicBuffer the topic buffer to use. This may be discarded - * as soon as this function returns - * \param topicBufferSize the size of topicBuffer - * \param messageBuffer the message buffer to use. May be NULL - * if clientToken is NULL - * \param messageBufferSize the size of messageBuffer - * \return LIMIT_EXCEEDED_ERROR if the topic buffer or - * message buffer is too small, NULL_VALUE_ERROR if the any of - * the required inputs are NULL, otherwise the result - * of the mqtt publish - */ -IoT_Error_t aws_iot_jobs_start_next( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const AwsIotStartNextPendingJobExecutionRequest *startNextRequest, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize); - -/** - * @brief Send a describe job query to the job query API. - * - * Send a describe job query to the job query API. If jobId is null this - * requests a list of pending jobs for the thing. If jobId is - * not null then it sends a query for the details of that job. - * - * \param pClient the client to use - * \param qos the qos to use - * \param thingName the thing name to query for - * \param jobId the id of the job to query for. If null a list - * of all pending jobs for the thing is requested. - * \param describeRequest the describe request to send - * \param topicBuffer the topic buffer to use. This may be discarded - * as soon as this function returns - * \param topicBufferSize the size of topicBuffer - * \param messageBuffer the message buffer to use. May be NULL - * if clientToken is NULL - * \param messageBufferSize the size of messageBuffer - * \return LIMIT_EXCEEDED_ERROR if the topic buffer or - * message buffer is too small, NULL_VALUE_ERROR if the any of - * the required inputs are NULL, otherwise the result - * of the mqtt publish - */ -IoT_Error_t aws_iot_jobs_describe( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - const AwsIotDescribeJobExecutionRequest *describeRequest, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize); - -/** - * @brief Send an update about a job execution. - * - * Send an update about a job execution. - * - * \param pClient the client to use - * \param qos the qos to use - * \param thingName the thing name to send the update for - * \param jobId the id of the job to send the update for - * \param updateRequest the update request to send - * \param topicBuffer the topic buffer to use. This may be discarded - * as soon as this function returns - * \param topicBufferSize the size of topicBuffer - * \param messageBuffer the message buffer to use. - * \param messageBufferSize the size of messageBuffer - * \return LIMIT_EXCEEDED_ERROR if the topic buffer or - * message buffer is too small, NULL_VALUE_ERROR if the any of - * the required inputs are NULL, otherwise the result - * of the mqtt publish - */ -IoT_Error_t aws_iot_jobs_send_update( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - const AwsIotJobExecutionUpdateRequest *updateRequest, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize); - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_JOBS_INTERFACE_H_ */ diff --git a/include/aws_iot_jobs_json.h b/include/aws_iot_jobs_json.h deleted file mode 100644 index 55e45142b9..0000000000 --- a/include/aws_iot_jobs_json.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_jobs_json.h - * @brief Functions for mapping between json and the AWS Iot Job data structures. - */ - -#ifdef DISABLE_IOT_JOBS -#error "Jobs API is disabled" -#endif - -#ifndef AWS_IOT_JOBS_JSON_H_ -#define AWS_IOT_JOBS_JSON_H_ - -#include -#include "jsmn.h" -#include "aws_iot_jobs_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Serialize a job execution update request into a json string. - * - * \param requestBuffer buffer to contain the serialized request. If null - * this function will return the size of the buffer required - * \param bufferSize the size of the buffer. If this is smaller than the required - * length the string will be truncated to fit. - * \request the request to serialize. - * \return The size of the json string to store the serialized request or -1 - * if the request is invalid. Note that the return value should be checked against - * the size of the buffer and if its larger handle the fact that the string has - * been truncated. - */ -int aws_iot_jobs_json_serialize_update_job_execution_request( - char *requestBuffer, size_t bufferSize, - const AwsIotJobExecutionUpdateRequest *request); - -/** - * Serialize a job API request that contains only a client token. - * - * \param requestBuffer buffer to contain the serialized request. If null - * this function will return the size of the buffer required - * \param bufferSize the size of the buffer. If this is smaller than the required - * length the string will be truncated to fit. - * \param clientToken the client token to use for the request. - * \return The size of the json string to store the serialized request or -1 - * if the request is invalid. Note that the return value should be checked against - * the size of the buffer and if its larger handle the fact that the string has - * been truncated. - */ -int aws_iot_jobs_json_serialize_client_token_only_request( - char *requestBuffer, size_t bufferSize, - const char *clientToken); - -/** - * Serialize describe job execution request into json string. - * - * \param requestBuffer buffer to contain the serialized request. If null - * this function will return the size of the buffer required - * \param bufferSize the size of the buffer. If this is smaller than the required - * length the string will be truncated to fit. - * \param request the request to serialize. - * \return The size of the json string to store the serialized request or -1 - * if the request is invalid. Note that the return value should be checked against - * the size of the buffer and if its larger handle the fact that the string has - * been truncated. - */ -int aws_iot_jobs_json_serialize_describe_job_execution_request( - char *requestBuffer, size_t bufferSize, - const AwsIotDescribeJobExecutionRequest *request); - -/** - * Serialize start next job execution request into json string. - * - * \param requestBuffer buffer to contain the serialized request. If null - * this function will return the size of the buffer required - * \param bufferSize the size of the buffer. If this is smaller than the required - * length the string will be truncated to fit. - * \param request the start-next request to serialize. - * \return The size of the json string to store the serialized request or -1 - * if the request is invalid. Note that the return value should be checked against - * the size of the buffer and if its larger handle the fact that the string has - * been truncated. - */ -int aws_iot_jobs_json_serialize_start_next_job_execution_request( - char *requestBuffer, size_t bufferSize, - const AwsIotStartNextPendingJobExecutionRequest *request); - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_JOBS_JSON_H_ */ diff --git a/include/aws_iot_jobs_topics.h b/include/aws_iot_jobs_topics.h deleted file mode 100644 index 3e291878b5..0000000000 --- a/include/aws_iot_jobs_topics.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_job_topics.h - * @brief Functions for parsing and creating MQTT topics used by the AWS IoT Jobs system. - */ - -#ifdef DISABLE_IOT_JOBS -#error "Jobs API is disabled" -#endif - -#ifndef AWS_IOT_JOBS_TOPICS_H_ -#define AWS_IOT_JOBS_TOPICS_H_ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define JOB_ID_NEXT "$next" -#define JOB_ID_WILDCARD "+" - -/** - * The type of job topic. - */ -typedef enum { - JOB_UNRECOGNIZED_TOPIC = 0, - JOB_GET_PENDING_TOPIC, - JOB_START_NEXT_TOPIC, - JOB_DESCRIBE_TOPIC, - JOB_UPDATE_TOPIC, - JOB_NOTIFY_TOPIC, - JOB_NOTIFY_NEXT_TOPIC, - JOB_WILDCARD_TOPIC -} AwsIotJobExecutionTopicType; - -/** - * The type of reply topic, or #JOB_REQUEST_TYPE for - * topics that are not replies. - */ -typedef enum { - JOB_UNRECOGNIZED_TOPIC_TYPE = 0, - JOB_REQUEST_TYPE, - JOB_ACCEPTED_REPLY_TYPE, - JOB_REJECTED_REPLY_TYPE, - JOB_WILDCARD_REPLY_TYPE -} AwsIotJobExecutionTopicReplyType; - -/** - * @brief Get the topic matching the provided details and put into the provided buffer. - * - * If the buffer is not large enough to store the full topic the topic will be truncated - * to fit, with the last character always being a null terminator. - * - * \param buffer the buffer to put the results into - * \param bufferSize the size of the buffer - * \param topicType the type of the topic - * \param replyType the reply type of the topic - * \param thingName the name of the thing in the topic - * \param jobId the name of the job id in the topic - * \return the number of characters in the topic excluding the null terminator. A return - * value of bufferSize or more means that the topic string was truncated. - */ -int aws_iot_jobs_get_api_topic(char *buffer, size_t bufferSize, - AwsIotJobExecutionTopicType topicType, AwsIotJobExecutionTopicReplyType replyType, - const char* thingName, const char* jobId); - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_JOBS_TOPICS_H_ */ diff --git a/include/aws_iot_jobs_types.h b/include/aws_iot_jobs_types.h deleted file mode 100644 index 22ba62febd..0000000000 --- a/include/aws_iot_jobs_types.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_jobs_types.h - * @brief Structures defining the interface with the AWS IoT Jobs system - * - * This file defines the structures returned by and sent to the AWS IoT Jobs system. - * - */ - -#ifdef DISABLE_IOT_JOBS -#error "Jobs API is disabled" -#endif - -#ifndef AWS_IOT_JOBS_TYPES_H_ -#define AWS_IOT_JOBS_TYPES_H_ - -#include -#include -#include "jsmn.h" -#include "timer_interface.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - JOB_EXECUTION_STATUS_NOT_SET = 0, - JOB_EXECUTION_QUEUED, - JOB_EXECUTION_IN_PROGRESS, - JOB_EXECUTION_FAILED, - JOB_EXECUTION_SUCCEEDED, - JOB_EXECUTION_CANCELED, - JOB_EXECUTION_REJECTED, - /*** - * Used for any status not in the supported list of statuses - */ - JOB_EXECUTION_UNKNOWN_STATUS = 99 -} JobExecutionStatus; - -extern const char *JOB_EXECUTION_QUEUED_STR; -extern const char *JOB_EXECUTION_IN_PROGRESS_STR; -extern const char *JOB_EXECUTION_FAILED_STR; -extern const char *JOB_EXECUTION_SUCCESS_STR; -extern const char *JOB_EXECUTION_CANCELED_STR; -extern const char *JOB_EXECUTION_REJECTED_STR; - -/** - * Convert a string to its matching status. - * - * \return the matching status, or JOB_EXECUTION_UNKNOWN_STATUS if the string was not recognized. - */ -JobExecutionStatus aws_iot_jobs_map_string_to_job_status(const char *str); - -/** - * Convert a status to its string. - * - * \return a string representing the status, or null if the status is not recognized. - */ -const char *aws_iot_jobs_map_status_to_string(JobExecutionStatus status); - -/** - * A request to update the status of a job execution. - */ -typedef struct { - int64_t expectedVersion; // set to 0 to ignore - int64_t executionNumber; // set to 0 to ignore - JobExecutionStatus status; - const char *statusDetails; - bool includeJobExecutionState; - bool includeJobDocument; - const char *clientToken; -} AwsIotJobExecutionUpdateRequest; - -/** - * A request to get the status of a job execution. - */ -typedef struct { - int64_t executionNumber; // set to 0 to ignore - bool includeJobDocument; - const char *clientToken; -} AwsIotDescribeJobExecutionRequest; - -/** - * A request to get and start the next pending (not in a terminal state) job execution for a Thing. - */ -typedef struct { - const char *statusDetails; - const char *clientToken; -} AwsIotStartNextPendingJobExecutionRequest; - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_JOBS_TYPES_H_ */ diff --git a/include/aws_iot_json_utils.h b/include/aws_iot_json_utils.h deleted file mode 100644 index af762d8a9f..0000000000 --- a/include/aws_iot_json_utils.h +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_json_utils.h - * @brief Utilities for manipulating JSON - * - * json_utils provides JSON parsing utilities for use with the IoT SDK. - * Underlying JSON parsing relies on the Jasmine JSON parser. - * - */ - -#ifndef AWS_IOT_SDK_SRC_JSON_UTILS_H_ -#define AWS_IOT_SDK_SRC_JSON_UTILS_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#include "aws_iot_error.h" -#include "jsmn.h" - -// utility functions -/** - * @brief JSON Equality Check - * - * Given a token pointing to a particular JSON node and an - * input string, check to see if the key is equal to the string. - * - * @param json json string - * @param tok json token - pointer to key to test for equality - * @param s input string for key to test equality - * - * @return 0 if equal, 1 otherwise - */ -int8_t jsoneq(const char *json, jsmntok_t *tok, const char *s); - -/** - * @brief Parse a signed 32-bit integer value from a JSON node. - * - * Given a JSON node parse the integer value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param i address of int32_t to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseInteger32Value(int32_t *i, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse a signed 16-bit integer value from a JSON node. - * - * Given a JSON node parse the integer value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param i address of int16_t to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseInteger16Value(int16_t *i, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse a signed 8-bit integer value from a JSON node. - * - * Given a JSON node parse the integer value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param i address of int8_t to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseInteger8Value(int8_t *i, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse an unsigned 32-bit integer value from a JSON node. - * - * Given a JSON node parse the integer value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param i address of uint32_t to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseUnsignedInteger32Value(uint32_t *i, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse an unsigned 16-bit integer value from a JSON node. - * - * Given a JSON node parse the integer value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param i address of uint16_t to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseUnsignedInteger16Value(uint16_t *i, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse an unsigned 8-bit integer value from a JSON node. - * - * Given a JSON node parse the integer value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param i address of uint8_t to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseUnsignedInteger8Value(uint8_t *i, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse a float value from a JSON node. - * - * Given a JSON node parse the float value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param f address of float to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseFloatValue(float *f, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse a double value from a JSON node. - * - * Given a JSON node parse the double value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param d address of double to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseDoubleValue(double *d, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse a boolean value from a JSON node. - * - * Given a JSON node parse the boolean value from the value. - * - * @param jsonString json string - * @param tok json token - pointer to JSON node - * @param b address of boolean to be updated - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseBooleanValue(bool *b, const char *jsonString, jsmntok_t *token); - -/** - * @brief Parse a string value from a JSON node. - * - * Given a JSON node parse the string value from the value. - * - * @param buf address of string to be updated - * @param bufLen length of buf in bytes - * @param jsonString json string - * @param token json token - pointer to JSON node - * - * @return SUCCESS - success - * @return JSON_PARSE_ERROR - error parsing value - */ -IoT_Error_t parseStringValue(char *buf, size_t bufLen, const char *jsonString, jsmntok_t *token); - -/** - * @brief Find the JSON node associated with the given key in the given object. - * - * Given a JSON node parse the string value from the value. - * - * @param key json key - * @param token json token - pointer to JSON node - * @param jsonString json string - * - * @return pointer to found property value - * @return NULL - not found - */ -jsmntok_t *findToken(const char *key, const char *jsonString, jsmntok_t *token); - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_SDK_SRC_JSON_UTILS_H_ */ diff --git a/include/aws_iot_log.h b/include/aws_iot_log.h deleted file mode 100644 index 60c26086c9..0000000000 --- a/include/aws_iot_log.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_log.h - * @brief Logging macros for the SDK. - * This file defines common logging macros with log levels to be used within the SDK. - * These macros can also be used in the IoT application code as a common way to output - * logs. The log levels can be tuned by modifying the makefile. Removing (commenting - * out) the IOT_* statement in the makefile disables that log level. - * - * It is expected that the macros below will be modified or replaced when porting to - * specific hardware platforms as printf may not be the desired behavior. - */ - -#ifndef _IOT_LOG_H -#define _IOT_LOG_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/** - * @brief Debug level logging macro. - * - * Macro to expose function, line number as well as desired log message. - */ -#ifdef ENABLE_IOT_DEBUG -#define IOT_DEBUG(...) \ - {\ - printf("DEBUG: %s L#%d ", __func__, __LINE__); \ - printf(__VA_ARGS__); \ - printf("\n"); \ - } -#else -#define IOT_DEBUG(...) -#endif - -/** - * @brief Debug level trace logging macro. - * - * Macro to print message function entry and exit - */ -#ifdef ENABLE_IOT_TRACE -#define FUNC_ENTRY \ - {\ - printf("FUNC_ENTRY: %s L#%d \n", __func__, __LINE__); \ - } -#define FUNC_EXIT \ - {\ - printf("FUNC_EXIT: %s L#%d \n", __func__, __LINE__); \ - } -#define FUNC_EXIT_RC(x) \ - {\ - printf("FUNC_EXIT: %s L#%d Return Code : %d \n", __func__, __LINE__, x); \ - return x; \ - } -#else -#define FUNC_ENTRY - -#define FUNC_EXIT -#define FUNC_EXIT_RC(x) { return x; } -#endif - -/** - * @brief Info level logging macro. - * - * Macro to expose desired log message. Info messages do not include automatic function names and line numbers. - */ -#ifdef ENABLE_IOT_INFO -#define IOT_INFO(...) \ - {\ - printf(__VA_ARGS__); \ - printf("\n"); \ - } -#else -#define IOT_INFO(...) -#endif - -/** - * @brief Warn level logging macro. - * - * Macro to expose function, line number as well as desired log message. - */ -#ifdef ENABLE_IOT_WARN -#define IOT_WARN(...) \ - { \ - printf("WARN: %s L#%d ", __func__, __LINE__); \ - printf(__VA_ARGS__); \ - printf("\n"); \ - } -#else -#define IOT_WARN(...) -#endif - -/** - * @brief Error level logging macro. - * - * Macro to expose function, line number as well as desired log message. - */ -#ifdef ENABLE_IOT_ERROR -#define IOT_ERROR(...) \ - { \ - printf("ERROR: %s L#%d ", __func__, __LINE__); \ - printf(__VA_ARGS__); \ - printf("\n"); \ - } -#else -#define IOT_ERROR(...) -#endif - -#ifdef __cplusplus -} -#endif - -#endif // _IOT_LOG_H diff --git a/include/aws_iot_mqtt_client.h b/include/aws_iot_mqtt_client.h deleted file mode 100644 index 66894ce502..0000000000 --- a/include/aws_iot_mqtt_client.h +++ /dev/null @@ -1,418 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - * Xiang Rong - 442039 Add makefile to Embedded C client - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client.h - * @brief Client definition for MQTT - */ - -#ifndef AWS_IOT_SDK_SRC_IOT_MQTT_CLIENT_H -#define AWS_IOT_SDK_SRC_IOT_MQTT_CLIENT_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* Library Header files */ -#include "stdio.h" -#include "stdbool.h" -#include "stdint.h" -#include "stddef.h" - -/* AWS Specific header files */ -#include "aws_iot_error.h" -#include "aws_iot_config.h" - -/* Platform specific implementation header files */ -#include "network_interface.h" -#include "timer_interface.h" - -#ifdef _ENABLE_THREAD_SUPPORT_ -#include "threads_interface.h" -#endif - -#define MAX_PACKET_ID 65535 - -typedef struct _Client AWS_IoT_Client; - -/** - * @brief Quality of Service Type - * - * Defining a QoS type. - * @note QoS 2 is \b NOT supported by the AWS IoT Service at the time of this SDK release. - * - */ -typedef enum QoS { - QOS0 = 0, - QOS1 = 1 -} QoS; - -/** - * @brief Publish Message Parameters Type - * - * Defines a type for MQTT Publish messages. Used for both incoming and out going messages - * - */ -typedef struct { - QoS qos; ///< Message Quality of Service - uint8_t isRetained; ///< Retained messages are \b NOT supported by the AWS IoT Service at the time of this SDK release. - uint8_t isDup; ///< Is this message a duplicate QoS > 0 message? Handled automatically by the MQTT client. - uint16_t id; ///< Message sequence identifier. Handled automatically by the MQTT client. - void *payload; ///< Pointer to MQTT message payload (bytes). - size_t payloadLen; ///< Length of MQTT payload. -} IoT_Publish_Message_Params; - -/** - * @brief MQTT Version Type - * - * Defining an MQTT version type. Only 3.1.1 is supported at this time - * - */ -typedef enum { - MQTT_3_1_1 = 4 ///< MQTT 3.1.1 (protocol message byte = 4) -} MQTT_Ver_t; - -/** - * @brief Last Will and Testament Definition - * - * Defining a type for the MQTT "Last Will and Testament" (LWT) parameters. - * @note Retained messages are \b NOT supported by the AWS IoT Service at the time of this SDK release. - * - */ -typedef struct { - char struct_id[4]; ///< The eyecatcher for this structure. must be MQTW - char *pTopicName; ///< The LWT topic to which the LWT message will be published - uint16_t topicNameLen; ///< The length of the LWT topic, 16 bit unsinged integer - char *pMessage; ///< Message to be delivered as LWT - uint16_t msgLen; ///< The length of the Message, 16 bit unsinged integer - bool isRetained; ///< NOT supported. The retained flag for the LWT message (see MQTTAsync_message.retained) - QoS qos; ///< QoS of LWT message -} IoT_MQTT_Will_Options; -extern const IoT_MQTT_Will_Options iotMqttWillOptionsDefault; - -#define IoT_MQTT_Will_Options_Initializer { {'M', 'Q', 'T', 'W'}, NULL, 0, NULL, 0, false, QOS0 } - -/** - * @brief MQTT Connection Parameters - * - * Defining a type for MQTT connection parameters. Passed into client when establishing a connection. - * - */ -typedef struct { - char struct_id[4]; ///< The eyecatcher for this structure. must be MQTC - MQTT_Ver_t MQTTVersion; ///< Desired MQTT version used during connection - char *pClientID; ///< Pointer to a string defining the MQTT client ID (this needs to be unique \b per \b device across your AWS account) - uint16_t clientIDLen; ///< Client Id Length. 16 bit unsigned integer - uint16_t keepAliveIntervalInSec; ///< MQTT keep alive interval in seconds. Defines inactivity time allowed before determining the connection has been lost. - bool isCleanSession; ///< MQTT clean session. True = this session is to be treated as clean. Previous server state is cleared and no stated is retained from this connection. - bool isWillMsgPresent; ///< Is there a LWT associated with this connection? - IoT_MQTT_Will_Options will; ///< MQTT LWT parameters. - char *pUsername; ///< Not used in the AWS IoT Service, will need to be cstring if used - uint16_t usernameLen; ///< Username Length. 16 bit unsigned integer - char *pPassword; ///< Not used in the AWS IoT Service, will need to be cstring if used - uint16_t passwordLen; ///< Password Length. 16 bit unsigned integer -} IoT_Client_Connect_Params; -extern const IoT_Client_Connect_Params iotClientConnectParamsDefault; - -#define IoT_Client_Connect_Params_initializer { {'M', 'Q', 'T', 'C'}, MQTT_3_1_1, NULL, 0, 60, true, false, \ - IoT_MQTT_Will_Options_Initializer, NULL, 0, NULL, 0 } - -/** - * @brief Disconnect Callback Handler Type - * - * Defining a TYPE for definition of disconnect callback function pointers. - * - */ -typedef void (*iot_disconnect_handler)(AWS_IoT_Client *, void *); - -/** - * @brief MQTT Initialization Parameters - * - * Defining a type for MQTT initialization parameters. - * Passed into client when to initialize the client - * - */ -typedef struct { - bool enableAutoReconnect; ///< Set to true to enable auto reconnect - char *pHostURL; ///< Pointer to a string defining the endpoint for the MQTT service - uint16_t port; ///< MQTT service listening port - char *pRootCALocation; ///< Pointer to a string defining the Root CA file (full file, not path) - char *pDeviceCertLocation; ///< Pointer to a string defining the device identity certificate file (full file, not path) - char *pDevicePrivateKeyLocation; ///< Pointer to a string defining the device private key file (full file, not path) - uint32_t mqttPacketTimeout_ms; ///< Timeout for reading a complete MQTT packet. In milliseconds - uint32_t mqttCommandTimeout_ms; ///< Timeout for MQTT blocking calls. In milliseconds - uint32_t tlsHandshakeTimeout_ms; ///< TLS handshake timeout. In milliseconds - bool isSSLHostnameVerify; ///< Client should perform server certificate hostname validation - iot_disconnect_handler disconnectHandler; ///< Callback to be invoked upon connection loss - void *disconnectHandlerData; ///< Data to pass as argument when disconnect handler is called -#ifdef _ENABLE_THREAD_SUPPORT_ - bool isBlockOnThreadLockEnabled; ///< Timeout for Thread blocking calls. Set to 0 to block until lock is obtained. In milliseconds -#endif -} IoT_Client_Init_Params; -extern const IoT_Client_Init_Params iotClientInitParamsDefault; - -#ifdef _ENABLE_THREAD_SUPPORT_ -#define IoT_Client_Init_Params_initializer { true, NULL, 0, NULL, NULL, NULL, 2000, 20000, 5000, true, NULL, NULL, false } -#else -#define IoT_Client_Init_Params_initializer { true, NULL, 0, NULL, NULL, NULL, 2000, 20000, 5000, true, NULL, NULL } -#endif - -/** - * @brief MQTT Client State Type - * - * Defining a type for MQTT Client State - * - */ -typedef enum _ClientState { - CLIENT_STATE_INVALID = 0, - CLIENT_STATE_INITIALIZED = 1, - CLIENT_STATE_CONNECTING = 2, - CLIENT_STATE_CONNECTED_IDLE = 3, - CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS = 4, - CLIENT_STATE_CONNECTED_PUBLISH_IN_PROGRESS = 5, - CLIENT_STATE_CONNECTED_SUBSCRIBE_IN_PROGRESS = 6, - CLIENT_STATE_CONNECTED_UNSUBSCRIBE_IN_PROGRESS = 7, - CLIENT_STATE_CONNECTED_RESUBSCRIBE_IN_PROGRESS = 8, - CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN = 9, - CLIENT_STATE_DISCONNECTING = 10, - CLIENT_STATE_DISCONNECTED_ERROR = 11, - CLIENT_STATE_DISCONNECTED_MANUALLY = 12, - CLIENT_STATE_PENDING_RECONNECT = 13 -} ClientState; - -/** - * @brief Application Callback Handler Type - * - * Defining a TYPE for definition of application callback function pointers. - * Used to send incoming data to the application - * - */ -typedef void (*pApplicationHandler_t)(AWS_IoT_Client *pClient, char *pTopicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *pParams, void *pClientData); - -/** - * @brief MQTT Message Handler - * - * Defining a type for MQTT Message Handlers. - * Used to pass incoming data back to the application - * - */ -typedef struct _MessageHandlers { - const char *topicName; - uint16_t topicNameLen; - QoS qos; - pApplicationHandler_t pApplicationHandler; - void *pApplicationHandlerData; -} MessageHandlers; /* Message handlers are indexed by subscription topic */ - -/** - * @brief MQTT Client Status - * - * Defining a type for MQTT Client Status - * Contains information about the state of the MQTT Client - * - */ -typedef struct _ClientStatus { - ClientState clientState; - bool isPingOutstanding; - bool isAutoReconnectEnabled; -} ClientStatus; - -/** - * @brief MQTT Client Data - * - * Defining a type for MQTT Client Data - * Contains data used by the MQTT Client - * - */ -typedef struct _ClientData { - uint16_t nextPacketId; - - uint32_t packetTimeoutMs; - uint32_t commandTimeoutMs; - uint16_t keepAliveInterval; - uint32_t currentReconnectWaitInterval; - uint32_t counterNetworkDisconnected; - - /* The below values are initialized with the - * lengths of the TX/RX buffers and never modified - * afterwards */ - size_t writeBufSize; - size_t readBufSize; - size_t readBufIndex; - unsigned char writeBuf[AWS_IOT_MQTT_TX_BUF_LEN]; - unsigned char readBuf[AWS_IOT_MQTT_RX_BUF_LEN]; - -#ifdef _ENABLE_THREAD_SUPPORT_ - bool isBlockOnThreadLockEnabled; - IoT_Mutex_t state_change_mutex; - IoT_Mutex_t tls_read_mutex; - IoT_Mutex_t tls_write_mutex; -#endif - - IoT_Client_Connect_Params options; - - MessageHandlers messageHandlers[AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS]; - iot_disconnect_handler disconnectHandler; - - void *disconnectHandlerData; -} ClientData; - -/** - * @brief MQTT Client - * - * Defining a type for MQTT Client - * - */ -struct _Client { - Timer pingTimer; - Timer reconnectDelayTimer; - - ClientStatus clientStatus; - ClientData clientData; - Network networkStack; -}; - -/** - * @brief What is the next available packet Id - * - * Called to retrieve the next packet id to be used for outgoing packets. - * Automatically increments the last sent packet id variable - * - * @param pClient Reference to the IoT Client - * - * @return next packet id as a 16 bit unsigned integer - */ -uint16_t aws_iot_mqtt_get_next_packet_id(AWS_IoT_Client *pClient); - -/** - * @brief Set the connection parameters for the IoT Client - * - * Called to set the connection parameters for the IoT Client. - * Used to update the connection parameters provided before the last connect. - * Won't take effect until the next time connect is called - * - * @param pClient Reference to the IoT Client - * @param pNewConnectParams Reference to the new Connection Parameters structure - * - * @return IoT_Error_t Type defining successful/failed API call - */ -IoT_Error_t aws_iot_mqtt_set_connect_params(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pNewConnectParams); - -/** - * @brief Is the MQTT client currently connected? - * - * Called to determine if the MQTT client is currently connected. Used to support logic - * in the device application around reconnecting and managing offline state. - * - * @param pClient Reference to the IoT Client - * - * @return true = connected, false = not currently connected - */ -bool aws_iot_mqtt_is_client_connected(AWS_IoT_Client *pClient); - -/** - * @brief Get the current state of the client - * - * Called to get the current state of the client - * - * @param pClient Reference to the IoT Client - * - * @return ClientState value equal to the current state of the client - */ -ClientState aws_iot_mqtt_get_client_state(AWS_IoT_Client *pClient); - -/** - * @brief Is the MQTT client set to reconnect automatically? - * - * Called to determine if the MQTT client is set to reconnect automatically. - * Used to support logic in the device application around reconnecting - * - * @param pClient Reference to the IoT Client - * - * @return true = enabled, false = disabled - */ -bool aws_iot_is_autoreconnect_enabled(AWS_IoT_Client *pClient); - -/** - * @brief Set the IoT Client disconnect handler - * - * Called to set the IoT Client disconnect handler - * The disconnect handler is called whenever the client disconnects with error - * - * @param pClient Reference to the IoT Client - * @param pConnectHandler Reference to the new Disconnect Handler - * @param pDisconnectHandlerData Reference to the data to be passed as argument when disconnect handler is called - * - * @return IoT_Error_t Type defining successful/failed API call - */ -IoT_Error_t aws_iot_mqtt_set_disconnect_handler(AWS_IoT_Client *pClient, iot_disconnect_handler pDisconnectHandler, - void *pDisconnectHandlerData); - -/** - * @brief Enable or Disable AutoReconnect on Network Disconnect - * - * Called to enable or disabled the auto reconnect features provided with the SDK - * - * @param pClient Reference to the IoT Client - * @param newStatus set to true for enabling and false for disabling - * - * @return IoT_Error_t Type defining successful/failed API call - */ -IoT_Error_t aws_iot_mqtt_autoreconnect_set_status(AWS_IoT_Client *pClient, bool newStatus); - -/** - * @brief Get count of Network Disconnects - * - * Called to get the number of times a network disconnect occurred due to errors - * - * @param pClient Reference to the IoT Client - * - * @return uint32_t the disconnect count - */ -uint32_t aws_iot_mqtt_get_network_disconnected_count(AWS_IoT_Client *pClient); - -/** - * @brief Reset Network Disconnect conter - * - * Called to reset the Network Disconnect counter to zero - * - * @param pClient Reference to the IoT Client - */ -void aws_iot_mqtt_reset_network_disconnected_count(AWS_IoT_Client *pClient); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/include/aws_iot_mqtt_client_common_internal.h b/include/aws_iot_mqtt_client_common_internal.h deleted file mode 100644 index 9f4d8125ef..0000000000 --- a/include/aws_iot_mqtt_client_common_internal.h +++ /dev/null @@ -1,134 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - * Xiang Rong - 442039 Add makefile to Embedded C client - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_common_internal.h - * @brief Internal MQTT functions not exposed to application - */ - -#ifndef AWS_IOT_SDK_SRC_IOT_COMMON_INTERNAL_H -#define AWS_IOT_SDK_SRC_IOT_COMMON_INTERNAL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "aws_iot_log.h" -#include "aws_iot_mqtt_client_interface.h" - -/* Enum order should match the packet ids array defined in MQTTFormat.c */ -typedef enum msgTypes { - UNKNOWN = -1, - CONNECT = 1, - CONNACK = 2, - PUBLISH = 3, - PUBACK = 4, - PUBREC = 5, - PUBREL = 6, - PUBCOMP = 7, - SUBSCRIBE = 8, - SUBACK = 9, - UNSUBSCRIBE = 10, - UNSUBACK = 11, - PINGREQ = 12, - PINGRESP = 13, - DISCONNECT = 14 -} MessageTypes; - -/* Macros for parsing header fields from incoming MQTT frame. */ -#define MQTT_HEADER_FIELD_TYPE(_byte) ((_byte >> 4) & 0x0F) -#define MQTT_HEADER_FIELD_DUP(_byte) ((_byte & (1 << 3)) >> 3) -#define MQTT_HEADER_FIELD_QOS(_byte) ((_byte & (3 << 1)) >> 1) -#define MQTT_HEADER_FIELD_RETAIN(_byte) ((_byte & (1 << 0)) >> 0) - -/** - * Bitfields for the MQTT header byte. - */ -typedef union { - unsigned char byte; /**< the whole byte */ -} MQTTHeader; - -IoT_Error_t aws_iot_mqtt_internal_init_header(MQTTHeader *pHeader, MessageTypes message_type, - QoS qos, uint8_t dup, uint8_t retained); - -IoT_Error_t aws_iot_mqtt_internal_serialize_ack(unsigned char *pTxBuf, size_t txBufLen, - MessageTypes msgType, uint8_t dup, uint16_t packetId, - uint32_t *pSerializedLen); -IoT_Error_t aws_iot_mqtt_internal_deserialize_ack(unsigned char *, unsigned char *, - uint16_t *, unsigned char *, size_t); - -uint32_t aws_iot_mqtt_internal_get_final_packet_length_from_remaining_length(uint32_t rem_len); - -size_t aws_iot_mqtt_internal_write_len_to_buffer(unsigned char *buf, uint32_t length); -IoT_Error_t aws_iot_mqtt_internal_decode_remaining_length_from_buffer(unsigned char *buf, uint32_t *decodedLen, - uint32_t *readBytesLen); - -uint16_t aws_iot_mqtt_internal_read_uint16_t(unsigned char **pptr); -void aws_iot_mqtt_internal_write_uint_16(unsigned char **pptr, uint16_t anInt); - -unsigned char aws_iot_mqtt_internal_read_char(unsigned char **pptr); -void aws_iot_mqtt_internal_write_char(unsigned char **pptr, unsigned char c); -void aws_iot_mqtt_internal_write_utf8_string(unsigned char **pptr, const char *string, uint16_t stringLen); - -IoT_Error_t aws_iot_mqtt_internal_flushBuffers( AWS_IoT_Client *pClient ); -IoT_Error_t aws_iot_mqtt_internal_send_packet(AWS_IoT_Client *pClient, size_t length, Timer *pTimer); -IoT_Error_t aws_iot_mqtt_internal_cycle_read(AWS_IoT_Client *pClient, Timer *pTimer, uint8_t *pPacketType); -IoT_Error_t aws_iot_mqtt_internal_wait_for_read(AWS_IoT_Client *pClient, uint8_t packetType, Timer *pTimer); -IoT_Error_t aws_iot_mqtt_internal_serialize_zero(unsigned char *pTxBuf, size_t txBufLen, - MessageTypes packetType, size_t *pSerializedLength); -IoT_Error_t aws_iot_mqtt_internal_deserialize_publish(uint8_t *dup, QoS *qos, - uint8_t *retained, uint16_t *pPacketId, - char **pTopicName, uint16_t *topicNameLen, - unsigned char **payload, size_t *payloadLen, - unsigned char *pRxBuf, size_t rxBufLen); - -IoT_Error_t aws_iot_mqtt_set_client_state(AWS_IoT_Client *pClient, ClientState expectedCurrentState, - ClientState newState); - -#ifdef _ENABLE_THREAD_SUPPORT_ - -IoT_Error_t aws_iot_mqtt_client_lock_mutex(AWS_IoT_Client *pClient, IoT_Mutex_t *pMutex); - -IoT_Error_t aws_iot_mqtt_client_unlock_mutex(AWS_IoT_Client *pClient, IoT_Mutex_t *pMutex); - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* AWS_IOT_SDK_SRC_IOT_COMMON_INTERNAL_H */ diff --git a/include/aws_iot_mqtt_client_interface.h b/include/aws_iot_mqtt_client_interface.h deleted file mode 100644 index e12dbc57c8..0000000000 --- a/include/aws_iot_mqtt_client_interface.h +++ /dev/null @@ -1,213 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - * Xiang Rong - 442039 Add makefile to Embedded C client - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_interface.h - * @brief Interface definition for MQTT client. - */ - -#ifndef AWS_IOT_SDK_SRC_IOT_MQTT_INTERFACE_H -#define AWS_IOT_SDK_SRC_IOT_MQTT_INTERFACE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* Library Header files */ -#include "stdio.h" -#include "stdbool.h" -#include "stdint.h" -#include "stddef.h" - -/* AWS Specific header files */ -#include "aws_iot_error.h" -#include "aws_iot_config.h" -#include "aws_iot_mqtt_client.h" - -/* Platform specific implementation header files */ -#include "network_interface.h" -#include "timer_interface.h" - - -/** -* @brief Clean mqtt client from all dynamic memory allocate -* -* This function will free up memory that was dynamically allocated for the client. -* -* @param pClient MQTT Client that was previously created by calling aws_iot_mqtt_init -* @return An IoT Error Type defining successful/failed freeing -*/ - IoT_Error_t aws_iot_mqtt_free( AWS_IoT_Client *pClient ); - -/** - * @brief MQTT Client Initialization Function - * - * Called to initialize the MQTT Client - * - * @param pClient Reference to the IoT Client - * @param pInitParams Pointer to MQTT connection parameters - * - * @return IoT_Error_t Type defining successful/failed API call - */ -IoT_Error_t aws_iot_mqtt_init(AWS_IoT_Client *pClient, IoT_Client_Init_Params *pInitParams); - -/** - * @brief MQTT Connection Function - * - * Called to establish an MQTT connection with the AWS IoT Service - * - * @param pClient Reference to the IoT Client - * @param pConnectParams Pointer to MQTT connection parameters - * - * @return An IoT Error Type defining successful/failed connection - */ -IoT_Error_t aws_iot_mqtt_connect(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pConnectParams); - -/** - * @brief Publish an MQTT message on a topic - * - * Called to publish an MQTT message on a topic. - * @note Call is blocking. In the case of a QoS 0 message the function returns - * after the message was successfully passed to the TLS layer. In the case of QoS 1 - * the function returns after the receipt of the PUBACK control packet. - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to - * @param topicNameLen Length of the topic name - * @param pParams Pointer to Publish Message parameters - * - * @return An IoT Error Type defining successful/failed publish - */ -IoT_Error_t aws_iot_mqtt_publish(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *pParams); - -/** - * @brief Subscribe to an MQTT topic. - * - * Called to send a subscribe message to the broker requesting a subscription - * to an MQTT topic. - * @note Call is blocking. The call returns after the receipt of the SUBACK control packet. - * @warning pTopicName and pApplicationHandlerData need to be static in memory. - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to. pTopicName needs to be static in memory since - * no malloc are performed by the SDK - * @param topicNameLen Length of the topic name - * @param pApplicationHandler_t Reference to the handler function for this subscription - * @param pApplicationHandlerData Point to data passed to the callback. - * pApplicationHandlerData also needs to be static in memory since no malloc are performed by the SDK - * - * @return An IoT Error Type defining successful/failed subscription - */ -IoT_Error_t aws_iot_mqtt_subscribe(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen, - QoS qos, pApplicationHandler_t pApplicationHandler, void *pApplicationHandlerData); - -/** - * @brief Subscribe to an MQTT topic. - * - * Called to resubscribe to the topics that the client has active subscriptions on. - * Internally called when autoreconnect is enabled - * - * @note Call is blocking. The call returns after the receipt of the SUBACK control packet. - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed subscription - */ -IoT_Error_t aws_iot_mqtt_resubscribe(AWS_IoT_Client *pClient); - -/** - * @brief Unsubscribe to an MQTT topic. - * - * Called to send an unsubscribe message to the broker requesting removal of a subscription - * to an MQTT topic. - * @note Call is blocking. The call returns after the receipt of the UNSUBACK control packet. - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to - * @param topicNameLen Length of the topic name - * - * @return An IoT Error Type defining successful/failed unsubscribe call - */ -IoT_Error_t aws_iot_mqtt_unsubscribe(AWS_IoT_Client *pClient, const char *pTopicFilter, uint16_t topicFilterLen); - -/** - * @brief Disconnect an MQTT Connection - * - * Called to send a disconnect message to the broker. - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed send of the disconnect control packet. - */ -IoT_Error_t aws_iot_mqtt_disconnect(AWS_IoT_Client *pClient); - -/** - * @brief Yield to the MQTT client - * - * Called to yield the current thread to the underlying MQTT client. This time is used by - * the MQTT client to manage PING requests to monitor the health of the TCP connection as - * well as periodically check the socket receive buffer for subscribe messages. Yield() - * must be called at a rate faster than the keepalive interval. It must also be called - * at a rate faster than the incoming message rate as this is the only way the client receives - * processing time to manage incoming messages. - * - * @param pClient Reference to the IoT Client - * @param timeout_ms Maximum number of milliseconds to pass thread execution to the client. - * - * @return An IoT Error Type defining successful/failed client processing. - * If this call results in an error it is likely the MQTT connection has dropped. - * iot_is_mqtt_connected can be called to confirm. - */ -IoT_Error_t aws_iot_mqtt_yield(AWS_IoT_Client *pClient, uint32_t timeout_ms); - -/** - * @brief MQTT Manual Re-Connection Function - * - * Called to establish an MQTT connection with the AWS IoT Service - * using parameters from the last time a connection was attempted - * Use after disconnect to start the reconnect process manually - * Makes only one reconnect attempt Sets the client state to - * pending reconnect in case of failure - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed connection - */ -IoT_Error_t aws_iot_mqtt_attempt_reconnect(AWS_IoT_Client *pClient); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/include/aws_iot_shadow_actions.h b/include/aws_iot_shadow_actions.h deleted file mode 100644 index 279c0cbece..0000000000 --- a/include/aws_iot_shadow_actions.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef SRC_SHADOW_AWS_IOT_SHADOW_ACTIONS_H_ -#define SRC_SHADOW_AWS_IOT_SHADOW_ACTIONS_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_shadow_interface.h" - -IoT_Error_t aws_iot_shadow_internal_action(const char *pThingName, ShadowActions_t action, - const char *pJsonDocumentToBeSent, size_t jsonSize, fpActionCallback_t callback, - void *pCallbackContext, uint32_t timeout_seconds, bool isSticky); - -#ifdef __cplusplus -} -#endif - -#endif /* SRC_SHADOW_AWS_IOT_SHADOW_ACTIONS_H_ */ diff --git a/include/aws_iot_shadow_interface.h b/include/aws_iot_shadow_interface.h deleted file mode 100644 index 241edba70e..0000000000 --- a/include/aws_iot_shadow_interface.h +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -#ifndef AWS_IOT_SDK_SRC_IOT_SHADOW_H_ -#define AWS_IOT_SDK_SRC_IOT_SHADOW_H_ - -#ifdef __cplusplus -extern "C" { -#endif - - -/** - * @file aws_iot_shadow_interface.h - * @brief Interface for thing shadow - * - * These are the functions and structs to manage/interact the Thing Shadow(in the cloud). - * This SDK will let you interact with your own thing shadow or any other shadow using its Thing Name. - * There are totally 3 actions a device can perform on the shadow - Get, Update and Delete. - * - * Currently the device should use MQTT/S underneath. In the future this will also support other protocols. As it supports MQTT, the shadow needs to connect and disconnect. - * It will also work on the pub/sub model. On performing any action, the acknowledgment will be received in either accepted or rejected. For Example: - * If we want to perform a GET on the thing shadow the following messages will be sent and received: - * 1. A MQTT Publish on the topic - $aws/things/{thingName}/shadow/get - * 2. Subscribe to MQTT topics - $aws/things/{thingName}/shadow/get/accepted and $aws/things/{thingName}/shadow/get/rejected. - * If the request was successful we will receive the things json document in the accepted topic. - * - * - */ -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_shadow_json_data.h" - -/*! - * @brief Shadow Initialization parameters - * - * As the Shadow SDK uses MQTT underneath, it could be connected and disconnected on events to save some battery. - * @note Always use the \c ShadowIniTParametersDefault to initialize this struct - * - * - * - */ -typedef struct { - char *pHost; ///< This will be unique to a customer and can be retrieved from the console - uint16_t port; ///< Network port for TCP/IP socket - char *pRootCA; ///< Location with the Filename of the Root CA - char *pClientCRT; ///< Location of Device certs signed by AWS IoT service - char *pClientKey; ///< Location of Device private key - bool enableAutoReconnect; ///< Set to true to enable auto reconnect - iot_disconnect_handler disconnectHandler; ///< Callback to be invoked upon connection loss. -} ShadowInitParameters_t; - -/*! - * @brief Shadow Connect parameters - * - * As the Shadow SDK uses MQTT underneath, it could be connected and disconnected on events to save some battery. - * @note Always use the \c ShadowConnectParametersDefault to initialize this struct - * - *d - * - */ -typedef struct { - char *pMyThingName; ///< Every device has a Thing Shadow and this is the placeholder for name - char *pMqttClientId; ///< Currently the Shadow uses MQTT to connect and it is important to ensure we have unique client id - uint16_t mqttClientIdLen; ///< Currently the Shadow uses MQTT to connect and it is important to ensure we have unique client id - pApplicationHandler_t deleteActionHandler; ///< Callback to be invoked when Thing shadow for this device is deleted -} ShadowConnectParameters_t; - -/*! - * @brief This is set to defaults from the configuration file - * The certs are set to NULL because they need the path to the file. shadow_sample.c file demonstrates on how to get the relative path - * - * \relates ShadowInitParameters_t - */ -extern const ShadowInitParameters_t ShadowInitParametersDefault; - -/*! - * @brief This is set to defaults from the configuration file - * The length of the client id is initialized as 0. This is due to C language limitations of using constant literals - * only for creating const variables. The client id will be assigned using the value from aws_iot_config.h but the - * length needs to be assigned in code. shadow_sample.c file demonstrates this. - * - * \relates ShadowConnectParameters_t - */ -extern const ShadowConnectParameters_t ShadowConnectParametersDefault; - -/** -* @brief Clean shadow client from all dynamic memory allocate -* -* This function will free up memory that was dynamically allocated for the client. -* -* @param pClient MQTT Client that was previously created by calling aws_iot_shadow_init -* @return An IoT Error Type defining successful/failed freeing -*/ -IoT_Error_t aws_iot_shadow_free(AWS_IoT_Client *pClient); - -/** - * @brief Initialize the Thing Shadow before use - * - * This function takes care of initializing the internal book-keeping data structures and initializing the IoT client. - * - * @param pClient A new MQTT Client to be used as the protocol layer. Will be initialized with pParams. - * @return An IoT Error Type defining successful/failed Initialization - */ -IoT_Error_t aws_iot_shadow_init(AWS_IoT_Client *pClient, ShadowInitParameters_t *pParams); - -/** - * @brief Connect to the AWS IoT Thing Shadow service over MQTT - * - * This function does the TLSv1.2 handshake and establishes the MQTT connection - * - * @param pClient MQTT Client used as the protocol layer - * @param pParams Shadow Conenction parameters like TLS cert location - * @return An IoT Error Type defining successful/failed Connection - */ -IoT_Error_t aws_iot_shadow_connect(AWS_IoT_Client *pClient, ShadowConnectParameters_t *pParams); - -/** - * @brief Yield function to let the background tasks of MQTT and Shadow - * - * This function could be use in a separate thread waiting for the incoming messages, ensuring the connection is kept alive with the AWS Service. - * It also ensures the expired requests of Shadow actions are cleared and Timeout callback is executed. - * @note All callbacks ever used in the SDK will be executed in the context of this function. - * - * @param pClient MQTT Client used as the protocol layer - * @param timeout in milliseconds, This is the maximum time the yield function will wait for a message and/or read the messages from the TLS buffer - * @return An IoT Error Type defining successful/failed Yield - */ -IoT_Error_t aws_iot_shadow_yield(AWS_IoT_Client *pClient, uint32_t timeout); - -/** - * @brief Disconnect from the AWS IoT Thing Shadow service over MQTT - * - * This will close the underlying TCP connection, MQTT connection will also be closed - * - * @param pClient MQTT Client used as the protocol layer - * @return An IoT Error Type defining successful/failed disconnect status - */ -IoT_Error_t aws_iot_shadow_disconnect(AWS_IoT_Client *pClient); - -/** - * @brief Thing Shadow Acknowledgment enum - * - * This enum type is use in the callback for the action response - * - */ -typedef enum { - SHADOW_ACK_TIMEOUT, SHADOW_ACK_REJECTED, SHADOW_ACK_ACCEPTED -} Shadow_Ack_Status_t; - -/** - * @brief Thing Shadow Action type enum - * - * This enum type is use in the callback for the action response - * - */ -typedef enum { - SHADOW_GET, SHADOW_UPDATE, SHADOW_DELETE -} ShadowActions_t; - - -/** - * @brief Function Pointer typedef used as the callback for every action - * - * This function will be called from the context of \c aws_iot_shadow_yield() context - * - * @param pThingName Thing Name of the response received - * @param action The response of the action - * @param status Informs if the action was Accepted/Rejected or Timed out - * @param pReceivedJsonDocument Received JSON document - * @param pContextData the void* data passed in during the action call(update, get or delete) - * - */ -typedef void (*fpActionCallback_t)(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, - const char *pReceivedJsonDocument, void *pContextData); - -/** - * @brief This function is the one used to perform an Update action to a Thing Name's Shadow. - * - * update is one of the most frequently used functionality by a device. In most cases the device may be just reporting few params to update the thing shadow in the cloud - * Update Action if no callback or if the JSON document does not have a client token then will just publish the update and not track it. - * - * @note The update has to subscribe to two topics update/accepted and update/rejected. This function waits 2 seconds to ensure the subscriptions are registered before publishing the update message. - * The following steps are performed on using this function: - * 1. Subscribe to Shadow topics - $aws/things/{thingName}/shadow/update/accepted and $aws/things/{thingName}/shadow/update/rejected - * 2. wait for 2 seconds for the subscription to take effect - * 3. Publish on the update topic - $aws/things/{thingName}/shadow/update - * 4. In the \c aws_iot_shadow_yield() function the response will be handled. In case of timeout or if the response is received, the subscription to shadow response topics are un-subscribed from. - * On the contrary if the persistent subscription is set to true then the un-subscribe will not be done. The topics will always be listened to. - * - * @param pClient MQTT Client used as the protocol layer - * @param pThingName Thing Name of the shadow that needs to be Updated - * @param pJsonString The update action expects a JSON document to send. The JSON String should be a null terminated string. This JSON document should adhere to the AWS IoT Thing Shadow specification. To help in the process of creating this document- SDK provides apis in \c aws_iot_shadow_json_data.h - * @param callback This is the callback that will be used to inform the caller of the response from the AWS IoT Shadow service.Callback could be set to NULL if response is not important - * @param pContextData This is an extra parameter that could be passed along with the callback. It should be set to NULL if not used - * @param timeout_seconds It is the time the SDK will wait for the response on either accepted/rejected before declaring timeout on the action - * @param isPersistentSubscribe As mentioned above, every time if a device updates the same shadow then this should be set to true to avoid repeated subscription and unsubscription. If the Thing Name is one off update then this should be set to false - * @return An IoT Error Type defining successful/failed update action - */ -IoT_Error_t aws_iot_shadow_update(AWS_IoT_Client *pClient, const char *pThingName, char *pJsonString, - fpActionCallback_t callback, void *pContextData, uint8_t timeout_seconds, - bool isPersistentSubscribe); - -/** - * @brief This function is the one used to perform an Get action to a Thing Name's Shadow. - * - * One use of this function is usually to get the config of a device at boot up. - * It is similar to the Update function internally except it does not take a JSON document as the input. The entire JSON document will be sent over the accepted topic - * - * @param pClient MQTT Client used as the protocol layer - * @param pThingName Thing Name of the JSON document that is needed - * @param callback This is the callback that will be used to inform the caller of the response from the AWS IoT Shadow service.Callback could be set to NULL if response is not important - * @param pContextData This is an extra parameter that could be passed along with the callback. It should be set to NULL if not used - * @param timeout_seconds It is the time the SDK will wait for the response on either accepted/rejected before declaring timeout on the action - * @param isPersistentSubscribe As mentioned above, every time if a device gets the same Sahdow (JSON document) then this should be set to true to avoid repeated subscription and un-subscription. If the Thing Name is one off get then this should be set to false - * @return An IoT Error Type defining successful/failed get action - */ -IoT_Error_t aws_iot_shadow_get(AWS_IoT_Client *pClient, const char *pThingName, fpActionCallback_t callback, - void *pContextData, uint8_t timeout_seconds, bool isPersistentSubscribe); - -/** - * @brief This function is the one used to perform an Delete action to a Thing Name's Shadow. - * - * This is not a very common use case for device. It is generally the responsibility of the accompanying app to do the delete. - * It is similar to the Update function internally except it does not take a JSON document as the input. The Thing Shadow referred by the ThingName will be deleted. - * - * @param pClient MQTT Client used as the protocol layer - * @param pThingName Thing Name of the Shadow that should be deleted - * @param callback This is the callback that will be used to inform the caller of the response from the AWS IoT Shadow service.Callback could be set to NULL if response is not important - * @param pContextData This is an extra parameter that could be passed along with the callback. It should be set to NULL if not used - * @param timeout_seconds It is the time the SDK will wait for the response on either accepted/rejected before declaring timeout on the action - * @param isPersistentSubscribe As mentioned above, every time if a device deletes the same Shadow (JSON document) then this should be set to true to avoid repeated subscription and un-subscription. If the Thing Name is one off delete then this should be set to false - * @return An IoT Error Type defining successful/failed delete action - */ -IoT_Error_t aws_iot_shadow_delete(AWS_IoT_Client *pClient, const char *pThingName, fpActionCallback_t callback, - void *pContextData, uint8_t timeout_seconds, bool isPersistentSubscriptions); - -/** - * @brief This function is used to listen on the delta topic of #AWS_IOT_MY_THING_NAME mentioned in the aws_iot_config.h file. - * - * Any time a delta is published the Json document will be delivered to the pStruct->cb. If you don't want the parsing done by the SDK then use the jsonStruct_t key set to "state". A good example of this is displayed in the sample_apps/shadow_console_echo.c - * - * @param pClient MQTT Client used as the protocol layer - * @param pStruct The struct used to parse JSON value - * @return An IoT Error Type defining successful/failed delta registering - */ -IoT_Error_t aws_iot_shadow_register_delta(AWS_IoT_Client *pClient, jsonStruct_t *pStruct); - -/** - * @brief Reset the last received version number to zero. - * This will be useful if the Thing Shadow is deleted and would like to to reset the local version - * @return no return values - * - */ -void aws_iot_shadow_reset_last_received_version(void); - -/** - * @brief Version of a document is received with every accepted/rejected and the SDK keeps track of the last received version of the JSON document of #AWS_IOT_MY_THING_NAME shadow - * - * One exception to this version tracking is that, the SDK will ignore the version from update/accepted topic. Rest of the responses will be scanned to update the version number. - * Accepting version change for update/accepted may cause version conflicts for delta message if the update message is received before the delta. - * - * @return version number of the last received response - * - */ -uint32_t aws_iot_shadow_get_last_received_version(void); - -/** - * @brief Enable the ignoring of delta messages with old version number - * - * As we use MQTT underneath, there could be more than 1 of the same message if we use QoS 0. To avoid getting called for the same message, this functionality should be enabled. All the old message will be ignored - */ -void aws_iot_shadow_enable_discard_old_delta_msgs(void); - -/** - * @brief Disable the ignoring of delta messages with old version number - */ -void aws_iot_shadow_disable_discard_old_delta_msgs(void); - -/** - * @brief This function is used to enable or disable autoreconnect - * - * Any time a disconnect happens the underlying MQTT client attempts to reconnect if this is set to true - * - * @param pClient MQTT Client used as the protocol layer - * @param newStatus The new status to set the autoreconnect option to - * - * @return An IoT Error Type defining successful/failed operation - */ -IoT_Error_t aws_iot_shadow_set_autoreconnect_status(AWS_IoT_Client *pClient, bool newStatus); - -#ifdef __cplusplus -} -#endif - -#endif //AWS_IOT_SDK_SRC_IOT_SHADOW_H_ diff --git a/include/aws_iot_shadow_json.h b/include/aws_iot_shadow_json.h deleted file mode 100644 index 748ff08840..0000000000 --- a/include/aws_iot_shadow_json.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -#ifndef AWS_IOT_SDK_SRC_IOT_SHADOW_JSON_H_ -#define AWS_IOT_SDK_SRC_IOT_SHADOW_JSON_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include "aws_iot_error.h" -#include "aws_iot_shadow_json_data.h" - -bool isJsonValidAndParse(const char *pJsonDocument, size_t jsonSize, void *pJsonHandler, int32_t *pTokenCount); - -bool isJsonKeyMatchingAndUpdateValue(const char *pJsonDocument, void *pJsonHandler, int32_t tokenCount, - jsonStruct_t *pDataStruct, uint32_t *pDataLength, int32_t *pDataPosition); - -IoT_Error_t aws_iot_shadow_internal_get_request_json(char *pBuffer, size_t bufferSize); - -IoT_Error_t aws_iot_shadow_internal_delete_request_json(char *pBuffer, size_t bufferSize); - -void resetClientTokenSequenceNum(void); - - -bool isReceivedJsonValid(const char *pJsonDocument, size_t jsonSize); - -bool extractClientToken(const char *pJsonDocument, size_t jsonSize, char *pExtractedClientToken, size_t clientTokenSize); - -bool extractVersionNumber(const char *pJsonDocument, void *pJsonHandler, int32_t tokenCount, uint32_t *pVersionNumber); - -#ifdef __cplusplus -} -#endif - -#endif // AWS_IOT_SDK_SRC_IOT_SHADOW_JSON_H_ diff --git a/include/aws_iot_shadow_json_data.h b/include/aws_iot_shadow_json_data.h deleted file mode 100644 index e2c61ac515..0000000000 --- a/include/aws_iot_shadow_json_data.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef SRC_SHADOW_AWS_IOT_SHADOW_JSON_DATA_H_ -#define SRC_SHADOW_AWS_IOT_SHADOW_JSON_DATA_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @file aws_iot_shadow_json_data.h - * @brief This file is the interface for all the Shadow related JSON functions. - */ - -#include - -/** - * @brief This is a static JSON object that could be used in code - * - */ -typedef struct jsonStruct jsonStruct_t; - -/** - * @brief Every JSON name value can have a callback. The callback should follow this signature - */ -typedef void (*jsonStructCallback_t)(const char *pJsonValueBuffer, uint32_t valueLength, jsonStruct_t *pJsonStruct_t); - -/** - * @brief All the JSON object types enum - * - * JSON number types need to be split into proper integer / floating point data types and sizes on embedded platforms. - */ -typedef enum { - SHADOW_JSON_INT32, - SHADOW_JSON_INT16, - SHADOW_JSON_INT8, - SHADOW_JSON_UINT32, - SHADOW_JSON_UINT16, - SHADOW_JSON_UINT8, - SHADOW_JSON_FLOAT, - SHADOW_JSON_DOUBLE, - SHADOW_JSON_BOOL, - SHADOW_JSON_STRING, - SHADOW_JSON_OBJECT -} JsonPrimitiveType; - -/** - * @brief This is the struct form of a JSON Key value pair - */ -struct jsonStruct { - const char *pKey; ///< JSON key - void *pData; ///< pointer to the data (JSON value) - size_t dataLength; ///< Length (in bytes) of pData - JsonPrimitiveType type; ///< type of JSON - jsonStructCallback_t cb; ///< callback to be executed on receiving the Key value pair -}; - -/** - * @brief Initialize the JSON document with Shadow expected name/value - * - * This Function will fill the JSON Buffer with a null terminated string. Internally it uses snprintf - * This function should always be used First, followed by iot_shadow_add_reported and/or iot_shadow_add_desired. - * Always finish the call sequence with iot_finalize_json_document - * - * @note Ensure the size of the Buffer is enough to hold the entire JSON Document. - * - * - * @param pJsonDocument The JSON Document filled in this char buffer - * @param maxSizeOfJsonDocument maximum size of the pJsonDocument that can be used to fill the JSON document - * @return An IoT Error Type defining if the buffer was null or the entire string was not filled up - */ -IoT_Error_t aws_iot_shadow_init_json_document(char *pJsonDocument, size_t maxSizeOfJsonDocument); - -/** - * @brief Add the reported section of the JSON document of jsonStruct_t - * - * This is a variadic function and please be careful with the usage. count is the number of jsonStruct_t types that you would like to add in the reported section - * This function will add "reported":{} - * - * @note Ensure the size of the Buffer is enough to hold the reported section + the init section. Always use the same JSON document buffer used in the iot_shadow_init_json_document function. This function will accommodate the size of previous null terminated string, so pass teh max size of the buffer - * - * - * @param pJsonDocument The JSON Document filled in this char buffer - * @param maxSizeOfJsonDocument maximum size of the pJsonDocument that can be used to fill the JSON document - * @param count total number of arguments(jsonStruct_t object) passed in the arguments - * @return An IoT Error Type defining if the buffer was null or the entire string was not filled up - */ -IoT_Error_t aws_iot_shadow_add_reported(char *pJsonDocument, size_t maxSizeOfJsonDocument, uint8_t count, ...); - -/** - * @brief Add the desired section of the JSON document of jsonStruct_t - * - * This is a variadic function and please be careful with the usage. count is the number of jsonStruct_t types that you would like to add in the reported section - * This function will add "desired":{} - * - * @note Ensure the size of the Buffer is enough to hold the reported section + the init section. Always use the same JSON document buffer used in the iot_shadow_init_json_document function. This function will accommodate the size of previous null terminated string, so pass the max size of the buffer - * - * - * @param pJsonDocument The JSON Document filled in this char buffer - * @param maxSizeOfJsonDocument maximum size of the pJsonDocument that can be used to fill the JSON document - * @param count total number of arguments(jsonStruct_t object) passed in the arguments - * @return An IoT Error Type defining if the buffer was null or the entire string was not filled up - */ -IoT_Error_t aws_iot_shadow_add_desired(char *pJsonDocument, size_t maxSizeOfJsonDocument, uint8_t count, ...); - -/** - * @brief Finalize the JSON document with Shadow expected client Token. - * - * This function will automatically increment the client token every time this function is called. - * - * @note Ensure the size of the Buffer is enough to hold the entire JSON Document. If the finalized section is not invoked then the JSON doucment will not be valid - * - * - * @param pJsonDocument The JSON Document filled in this char buffer - * @param maxSizeOfJsonDocument maximum size of the pJsonDocument that can be used to fill the JSON document - * @return An IoT Error Type defining if the buffer was null or the entire string was not filled up - */ -IoT_Error_t aws_iot_finalize_json_document(char *pJsonDocument, size_t maxSizeOfJsonDocument); - -/** - * @brief Fill the given buffer with client token for tracking the Repsonse. - * - * This function will add the AWS_IOT_MQTT_CLIENT_ID with a sequence number. Every time this function is used the sequence number gets incremented - * - * - * @param pBufferToBeUpdatedWithClientToken buffer to be updated with the client token string - * @param maxSizeOfJsonDocument maximum size of the pBufferToBeUpdatedWithClientToken that can be used - * @return An IoT Error Type defining if the buffer was null or the entire string was not filled up - */ - -IoT_Error_t aws_iot_fill_with_client_token(char *pBufferToBeUpdatedWithClientToken, size_t maxSizeOfJsonDocument); - -#ifdef __cplusplus -} -#endif - -#endif /* SRC_SHADOW_AWS_IOT_SHADOW_JSON_DATA_H_ */ diff --git a/include/aws_iot_shadow_key.h b/include/aws_iot_shadow_key.h deleted file mode 100644 index 075a726daa..0000000000 --- a/include/aws_iot_shadow_key.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef SRC_SHADOW_AWS_IOT_SHADOW_KEY_H_ -#define SRC_SHADOW_AWS_IOT_SHADOW_KEY_H_ - -#define SHADOW_CLIENT_TOKEN_STRING "clientToken" -#define SHADOW_VERSION_STRING "version" - -#endif /* SRC_SHADOW_AWS_IOT_SHADOW_KEY_H_ */ diff --git a/include/aws_iot_shadow_records.h b/include/aws_iot_shadow_records.h deleted file mode 100644 index fc2de03223..0000000000 --- a/include/aws_iot_shadow_records.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef SRC_SHADOW_AWS_IOT_SHADOW_RECORDS_H_ -#define SRC_SHADOW_AWS_IOT_SHADOW_RECORDS_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include "aws_iot_shadow_interface.h" -#include "aws_iot_config.h" - - -extern uint32_t shadowJsonVersionNum; -extern bool shadowDiscardOldDeltaFlag; - -extern char myThingName[MAX_SIZE_OF_THING_NAME]; -extern uint16_t myThingNameLen; -extern char mqttClientID[MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES]; -extern uint16_t mqttClientIDLen; - -void initializeRecords(AWS_IoT_Client *pClient); -bool isSubscriptionPresent(const char *pThingName, ShadowActions_t action); -IoT_Error_t subscribeToShadowActionAcks(const char *pThingName, ShadowActions_t action, bool isSticky); -void incrementSubscriptionCnt(const char *pThingName, ShadowActions_t action, bool isSticky); - -IoT_Error_t publishToShadowAction(const char *pThingName, ShadowActions_t action, const char *pJsonDocumentToBeSent); -void addToAckWaitList(uint8_t indexAckWaitList, const char *pThingName, ShadowActions_t action, - const char *pExtractedClientToken, fpActionCallback_t callback, void *pCallbackContext, - uint32_t timeout_seconds); -bool getNextFreeIndexOfAckWaitList(uint8_t *pIndex); -void HandleExpiredResponseCallbacks(void); -void initDeltaTokens(void); -IoT_Error_t registerJsonTokenOnDelta(jsonStruct_t *pStruct); - -#ifdef __cplusplus -} -#endif - -#endif /* SRC_SHADOW_AWS_IOT_SHADOW_RECORDS_H_ */ diff --git a/include/aws_iot_version.h b/include/aws_iot_version.h deleted file mode 100644 index 873949b07e..0000000000 --- a/include/aws_iot_version.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_version.h - * @brief Constants defining the release version of the SDK. - * - * This file contains constants defining the release version of the SDK. - * This file is modified by AWS upon release of the SDK and should not be - * modified by the consumer of the SDK. The provided samples show example - * usage of these constants. - * - * Versioning of the SDK follows the MAJOR.MINOR.PATCH Semantic Versioning guidelines. - * @see http://semver.org/ - */ -#ifndef SRC_UTILS_AWS_IOT_VERSION_H_ -#define SRC_UTILS_AWS_IOT_VERSION_H_ - -/** - * @brief MAJOR version, incremented when incompatible API changes are made. - */ -#define VERSION_MAJOR 3 -/** - * @brief MINOR version when functionality is added in a backwards-compatible manner. - */ -#define VERSION_MINOR 0 -/** - * @brief PATCH version when backwards-compatible bug fixes are made. - */ -#define VERSION_PATCH 1 -/** - * @brief TAG is an (optional) tag appended to the version if a more descriptive verion is needed. - */ -#define VERSION_TAG "" - -#endif /* SRC_UTILS_AWS_IOT_VERSION_H_ */ diff --git a/include/network_interface.h b/include/network_interface.h deleted file mode 100644 index cf28341062..0000000000 --- a/include/network_interface.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file network_interface.h - * @brief Network interface definition for MQTT client. - * - * Defines an interface to the TLS layer to be used by the MQTT client. - * Starting point for porting the SDK to the networking layer of a new platform. - */ - -#ifndef __NETWORK_INTERFACE_H_ -#define __NETWORK_INTERFACE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include "timer_interface.h" -#include "network_platform.h" - -/** - * @brief Network Type - * - * Defines a type for the network struct. See structure definition below. - */ -typedef struct Network Network; - -/** - * @brief TLS Connection Parameters - * - * Defines a type containing TLS specific parameters to be passed down to the - * TLS networking layer to create a TLS secured socket. - */ -typedef struct { - char *pRootCALocation; ///< Pointer to string containing the filename (including path) of the root CA file. - char *pDeviceCertLocation; ///< Pointer to string containing the filename (including path) of the device certificate. - char *pDevicePrivateKeyLocation; ///< Pointer to string containing the filename (including path) of the device private key file. - char *pDestinationURL; ///< Pointer to string containing the endpoint of the MQTT service. - uint16_t DestinationPort; ///< Integer defining the connection port of the MQTT service. - uint32_t timeout_ms; ///< Unsigned integer defining the TLS handshake timeout value in milliseconds. - bool ServerVerificationFlag; ///< Boolean. True = perform server certificate hostname validation. False = skip validation \b NOT recommended. -} TLSConnectParams; - -/** - * @brief Network Structure - * - * Structure for defining a network connection. - */ -struct Network { - IoT_Error_t (*connect)(Network *, TLSConnectParams *); - - IoT_Error_t (*read)(Network *, unsigned char *, size_t, Timer *, size_t *); ///< Function pointer pointing to the network function to read from the network - IoT_Error_t (*write)(Network *, unsigned char *, size_t, Timer *, size_t *); ///< Function pointer pointing to the network function to write to the network - IoT_Error_t (*disconnect)(Network *); ///< Function pointer pointing to the network function to disconnect from the network - IoT_Error_t (*isConnected)(Network *); ///< Function pointer pointing to the network function to check if TLS is connected - IoT_Error_t (*destroy)(Network *); ///< Function pointer pointing to the network function to destroy the network object - - TLSConnectParams tlsConnectParams; ///< TLSConnect params structure containing the common connection parameters - TLSDataParams tlsDataParams; ///< TLSData params structure containing the connection data parameters that are specific to the library being used -}; - -/** - * @brief Initialize the TLS implementation - * - * Perform any initialization required by the TLS layer. - * Connects the interface to implementation by setting up - * the network layer function pointers to platform implementations. - * - * @param pNetwork - Pointer to a Network struct defining the network interface. - * @param pRootCALocation - Path of the location of the Root CA - * @param pDeviceCertLocation - Path to the location of the Device Cert - * @param pDevicyPrivateKeyLocation - Path to the location of the device private key file - * @param pDestinationURL - The target endpoint to connect to - * @param DestinationPort - The port on the target to connect to - * @param timeout_ms - The value to use for timeout of operation - * @param ServerVerificationFlag - used to decide whether server verification is needed or not - * - * @return IoT_Error_t - successful initialization or TLS error - */ -IoT_Error_t iot_tls_init(Network *pNetwork, char *pRootCALocation, char *pDeviceCertLocation, - char *pDevicePrivateKeyLocation, char *pDestinationURL, - uint16_t DestinationPort, uint32_t timeout_ms, bool ServerVerificationFlag); - -/** - * @brief Create a TLS socket and open the connection - * - * Creates an open socket connection including TLS handshake. - * - * @param pNetwork - Pointer to a Network struct defining the network interface. - * @param TLSParams - TLSConnectParams defines the properties of the TLS connection. - * @return IoT_Error_t - successful connection or TLS error - */ -IoT_Error_t iot_tls_connect(Network *pNetwork, TLSConnectParams *TLSParams); - -/** - * @brief Write bytes to the network socket - * - * @param Network - Pointer to a Network struct defining the network interface. - * @param unsigned char pointer - buffer to write to socket - * @param integer - number of bytes to write - * @param Timer * - operation timer - * @return integer - number of bytes written or TLS error - * @return IoT_Error_t - successful write or TLS error code - */ -IoT_Error_t iot_tls_write(Network *, unsigned char *, size_t, Timer *, size_t *); - -/** - * @brief Read bytes from the network socket - * - * @param Network - Pointer to a Network struct defining the network interface. - * @param unsigned char pointer - pointer to buffer where read bytes should be copied - * @param size_t - number of bytes to read - * @param Timer * - operation timer - * @param size_t - pointer to store number of bytes read - * @return IoT_Error_t - successful read or TLS error code - */ -IoT_Error_t iot_tls_read(Network *, unsigned char *, size_t, Timer *, size_t *); - -/** - * @brief Disconnect from network socket - * - * @param Network - Pointer to a Network struct defining the network interface. - * @return IoT_Error_t - successful read or TLS error code - */ -IoT_Error_t iot_tls_disconnect(Network *pNetwork); - -/** - * @brief Perform any tear-down or cleanup of TLS layer - * - * Called to cleanup any resources required for the TLS layer. - * - * @param Network - Pointer to a Network struct defining the network interface - * @return IoT_Error_t - successful cleanup or TLS error code - */ -IoT_Error_t iot_tls_destroy(Network *pNetwork); - -/** - * @brief Check if TLS layer is still connected - * - * Called to check if the TLS layer is still connected or not. - * - * @param Network - Pointer to a Network struct defining the network interface - * @return IoT_Error_t - TLS error code indicating status of network physical layer connection - */ -IoT_Error_t iot_tls_is_connected(Network *pNetwork); - -#ifdef __cplusplus -} -#endif - -#endif //__NETWORK_INTERFACE_H_ diff --git a/include/threads_interface.h b/include/threads_interface.h deleted file mode 100644 index b4bc3705d2..0000000000 --- a/include/threads_interface.h +++ /dev/null @@ -1,108 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file threads_interface.h - * @brief Thread interface definition for MQTT client. - * - * Defines an interface that can be used by system components for multithreaded situations. - * Starting point for porting the SDK to the threading hardware layer of a new platform. - */ - -#include "aws_iot_config.h" - -#ifdef _ENABLE_THREAD_SUPPORT_ -#ifndef __THREADS_INTERFACE_H_ -#define __THREADS_INTERFACE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * The platform specific timer header that defines the Timer struct - */ -#include "threads_platform.h" - -#include - -/** - * @brief Mutex Type - * - * Forward declaration of a mutex struct. The definition of this struct is - * platform dependent. When porting to a new platform add this definition - * in "threads_platform.h". - * - */ -typedef struct _IoT_Mutex_t IoT_Mutex_t; - -/** - * @brief Initialize the provided mutex - * - * Call this function to initialize the mutex - * - * @param IoT_Mutex_t - pointer to the mutex to be initialized - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_init(IoT_Mutex_t *); - -/** - * @brief Lock the provided mutex - * - * Call this function to lock the mutex before performing a state change - * This is a blocking call. - * - * @param IoT_Mutex_t - pointer to the mutex to be locked - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_lock(IoT_Mutex_t *); - -/** - * @brief Lock the provided mutex - * - * Call this function to lock the mutex before performing a state change. - * This is not a blocking call. - * - * @param IoT_Mutex_t - pointer to the mutex to be locked - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_trylock(IoT_Mutex_t *); - -/** - * @brief Unlock the provided mutex - * - * Call this function to unlock the mutex before performing a state change - * - * @param IoT_Mutex_t - pointer to the mutex to be unlocked - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_unlock(IoT_Mutex_t *); - -/** - * @brief Destroy the provided mutex - * - * Call this function to destroy the mutex - * - * @param IoT_Mutex_t - pointer to the mutex to be destroyed - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_destroy(IoT_Mutex_t *); - -#ifdef __cplusplus -} -#endif - -#endif /*__THREADS_INTERFACE_H_*/ -#endif /*_ENABLE_THREAD_SUPPORT_*/ diff --git a/include/timer_interface.h b/include/timer_interface.h deleted file mode 100644 index 0bef0fc940..0000000000 --- a/include/timer_interface.h +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Allan Stockdill-Mander - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file timer_interface.h - * @brief Timer interface definition for MQTT client. - * - * Defines an interface to timers that can be used by other system - * components. MQTT client requires timers to handle timeouts and - * MQTT keep alive. - * Starting point for porting the SDK to the timer hardware layer of a new platform. - */ - -#ifndef __TIMER_INTERFACE_H_ -#define __TIMER_INTERFACE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * The platform specific timer header that defines the Timer struct - */ -#include "timer_platform.h" - -#include -#include - -/** - * @brief Timer Type - * - * Forward declaration of a timer struct. The definition of this struct is - * platform dependent. When porting to a new platform add this definition - * in "timer_.h" and include that file above. - * - */ -typedef struct Timer Timer; - -/** - * @brief Check if a timer is expired - * - * Call this function passing in a timer to check if that timer has expired. - * - * @param Timer - pointer to the timer to be checked for expiration - * @return bool - true = timer expired, false = timer not expired - */ -bool has_timer_expired(Timer *); - -/** - * @brief Create a timer (milliseconds) - * - * Sets the timer to expire in a specified number of milliseconds. - * - * @param Timer - pointer to the timer to be set to expire in milliseconds - * @param uint32_t - set the timer to expire in this number of milliseconds - */ -void countdown_ms(Timer *, uint32_t); - -/** - * @brief Create a timer (seconds) - * - * Sets the timer to expire in a specified number of seconds. - * - * @param Timer - pointer to the timer to be set to expire in seconds - * @param uint32_t - set the timer to expire in this number of seconds - */ -void countdown_sec(Timer *, uint32_t); - -/** - * @brief Check the time remaining on a given timer - * - * Checks the input timer and returns the number of milliseconds remaining on the timer. - * - * @param Timer - pointer to the timer to be set to checked - * @return int - milliseconds left on the countdown timer - */ -uint32_t left_ms(Timer *); - -/** - * @brief Initialize a timer - * - * Performs any initialization required to the timer passed in. - * - * @param Timer - pointer to the timer to be initialized - */ -void init_timer(Timer *); - -#ifdef __cplusplus -} -#endif - -#endif //__TIMER_INTERFACE_H_ diff --git a/lib/config/aws_iot_config_build.h.in b/lib/config/aws_iot_config_build.h.in new file mode 100644 index 0000000000..1f29d0264b --- /dev/null +++ b/lib/config/aws_iot_config_build.h.in @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* This file is used by CMake to configure the libraries during build. */ + +#ifndef _AWS_IOT_CONFIG_BUILD_H_ +#define _AWS_IOT_CONFIG_BUILD_H_ + +/* Include any provided user config file, which may override any options in this + * file. */ +#ifdef AWS_IOT_USER_CONFIG_FILE + #include AWS_IOT_USER_CONFIG_FILE +#endif + +/* Mutex type. */ +#ifndef AWS_IOT_MUTEX_TYPE + #include <@CONFIG_MUTEX_HEADER@> + #define AWS_IOT_MUTEX_TYPE @CONFIG_MUTEX_TYPE@ +#endif + +/* Semaphore type. */ +#ifndef AWS_IOT_SEMAPHORE_TYPE + #include <@CONFIG_SEMAPHORE_HEADER@> + #define AWS_IOT_SEMAPHORE_TYPE @CONFIG_SEMAPHORE_TYPE@ +#endif + +/* Timer type. */ +#ifndef AWS_IOT_TIMER_TYPE + #include <@CONFIG_TIMER_HEADER@> + #define AWS_IOT_TIMER_TYPE @CONFIG_TIMER_TYPE@ +#endif + +#endif diff --git a/lib/include/aws_iot_json_utils.h b/lib/include/aws_iot_json_utils.h new file mode 100644 index 0000000000..a15365540c --- /dev/null +++ b/lib/include/aws_iot_json_utils.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_json_utils.h + * @brief Declares JSON utility functions. + */ + +#ifndef _AWS_IOT_JSON_UTILS_H_ +#define _AWS_IOT_JSON_UTILS_H_ + +/* Standard includes. */ +#include +#include + +bool AwsIotJsonUtils_FindJsonValue( const char * const pJsonDocument, + size_t jsonDocumentLength, + const char * const pJsonKey, + size_t jsonKeyLength, + const char ** const pJsonValue, + size_t * const pJsonValueLength ); + +#endif /* ifndef _AWS_IOT_JSON_UTILS_H_ */ diff --git a/lib/include/aws_iot_logging_setup.h b/lib/include/aws_iot_logging_setup.h new file mode 100644 index 0000000000..8f70c5ea9c --- /dev/null +++ b/lib/include/aws_iot_logging_setup.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_logging_setup.h + * @brief Defines the logging macro #AwsIotLog. + */ + +#ifndef _AWS_IOT_LOGGING_SETUP_H_ +#define _AWS_IOT_LOGGING_SETUP_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Logging include. Because it's included here, aws_iot_logging.h never needs + * to be included in source. */ +#include "private/aws_iot_logging.h" + +/** + * @functionpage{AwsIotLog,logging,log} + * @functionpage{AwsIotLog_PrintBuffer,logging,printbuffer} + */ + +/** + * @def AwsIotLog( messageLevel, pLogConfig, ... ) + * @brief Logging function for a specific library. In most cases, this is the + * logging function to call. + * + * This function prints a single log message. It is available when @ref + * _LIBRARY_LOG_LEVEL is not #AWS_IOT_LOG_NONE. Log messages automatically + * include the [log level](@ref logging_constants_levels), [library name] + * (@ref _LIBRARY_LOG_NAME), and time. An optional @ref AwsIotLogConfig_t may + * be passed to this function to hide information for a single log message. + * + * The logging library must be set up before this function may be called. See + * @ref logging_setup_use for more information. + * + * This logging function also has the following abbreviated forms that can be used + * when an #AwsIotLogConfig_t isn't needed. + * + * Name | Equivalent to + * ---- | ------------- + * #AwsIotLogError | @code{c} AwsIotLog( AWS_IOT_LOG_ERROR, NULL, ... ) @endcode + * #AwsIotLogWarn | @code{c} AwsIotLog( AWS_IOT_LOG_WARN, NULL, ... ) @endcode + * #AwsIotLogInfo | @code{c} AwsIotLog( AWS_IOT_LOG_INFO, NULL, ... ) @endcode + * #AwsIotLogDebug | @code{c} AwsIotLog( AWS_IOT_LOG_DEBUG, NULL, ... ) @endcode + * + * @param[in] messageLevel Log level of this message. Must be one of the + * @ref logging_constants_levels. + * @param[in] pLogConfig Pointer to an #AwsIotLogConfig_t. Optional; pass `NULL` + * to ignore. + * @param[in] ... Message and format specification. + * + * @return No return value. On errors, it prints nothing. + * + * @note This function may be implemented as a macro. + * @see @ref logging_function_loggeneric for the generic (not library-specific) + * logging function. + */ + +/** + * @def AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) + * @brief Log the contents of buffer as bytes. Only available when @ref + * _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_DEBUG. + * + * This function prints the bytes located at a given memory address. It is + * intended for debugging only, and is therefore only available when @ref + * _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_DEBUG. + * + * Log messages printed by this function always include the [log level] + * (@ref logging_constants_levels), [library name](@ref _LIBRARY_LOG_NAME), + * and time. In addition, this function may print an optional header `pHeader` + * before it prints the contents of the buffer. This function does not have an + * #AwsIotLogConfig_t parameter. + * + * The logging library must be set up before this function may be called. See + * @ref logging_setup_use for more information. + * + * @param[in] pHeader A message to log before the buffer. Optional; pass `NULL` + * to ignore. + * @param[in] pBuffer Pointer to start of buffer. + * @param[in] bufferSize Size of `pBuffer`. + * + * @return No return value. On errors, it prints nothing. + * + * @note This function may be implemented as a macro. + * @note To conserve memory, @ref logging_function_genericprintbuffer (the underlying + * implementation) only allocates enough memory for a single line of output. Therefore, + * in multithreaded systems, its output may appear "fragmented" if other threads are + * logging simultaneously. + * @see @ref logging_function_genericprintbuffer for the generic (not library-specific) + * buffer logging function. + * + * **Example** + * @code{c} + * const uint8_t pBuffer[] = { 0x00, 0x01, 0x02, 0x03 }; + * + * AwsIotLog_PrintBuffer( "This buffer contains:", + * pBuffer, + * 4 ); + * @endcode + * The code above prints something like the following: + * @code{c} + * [DEBUG][LIB_NAME][2018-01-01 12:00:00] This buffer contains: + * 00 01 02 03 + * @endcode + */ + +/** + * @def AwsIotLogError( ... ) + * @brief Abbreviated logging macro for level #AWS_IOT_LOG_ERROR. + * + * Equivalent to: + * @code{c} + * AwsIotLog( AWS_IOT_LOG_ERROR, NULL, ... ) + * @endcode + */ + +/** + * @def AwsIotLogWarn( ... ) + * @brief Abbreviated logging macro for level #AWS_IOT_LOG_WARN. + * + * Equivalent to: + * @code{c} + * AwsIotLog( AWS_IOT_LOG_WARN, NULL, ... ) + * @endcode + */ + +/** + * @def AwsIotLogInfo( ... ) + * @brief Abbreviated logging macro for level #AWS_IOT_LOG_INFO. + * + * Equivalent to: + * @code{c} + * AwsIotLog( AWS_IOT_LOG_INFO, NULL, ... ) + * @endcode + */ + +/** + * @def AwsIotLogDebug( ... ) + * @brief Abbreviated logging macro for level #AWS_IOT_LOG_DEBUG. + * + * Equivalent to: + * @code{c} + * AwsIotLog( AWS_IOT_LOG_DEBUG, NULL, ... ) + * @endcode + */ + +/* Check that _LIBRARY_LOG_LEVEL is defined and has a valid value. */ +#if !defined( _LIBRARY_LOG_LEVEL ) || \ + ( _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_NONE && \ + _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_ERROR && \ + _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_WARN && \ + _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_INFO && \ + _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_DEBUG ) + #error "Please define _LIBRARY_LOG_LEVEL as either AWS_IOT_LOG_NONE, AWS_IOT_LOG_ERROR, AWS_IOT_LOG_WARN, AWS_IOT_LOG_INFO, or AWS_IOT_LOG_DEBUG." +/* Check that _LIBRARY_LOG_NAME is defined and has a valid value. */ +#elif !defined( _LIBRARY_LOG_NAME ) + #error "Please define _LIBRARY_LOG_NAME." +#else + /* Define AwsIotLog if the log level is greater than "none". */ + #if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + #define AwsIotLog( messageLevel, pLogConfig, ... ) \ + AwsIotLogGeneric( _LIBRARY_LOG_LEVEL, \ + _LIBRARY_LOG_NAME, \ + messageLevel, \ + pLogConfig, \ + __VA_ARGS__ ) + + /* Define the abbreviated logging macros. */ + #define AwsIotLogError( ... ) AwsIotLog( AWS_IOT_LOG_ERROR, NULL, __VA_ARGS__ ) + #define AwsIotLogWarn( ... ) AwsIotLog( AWS_IOT_LOG_WARN, NULL, __VA_ARGS__ ) + #define AwsIotLogInfo( ... ) AwsIotLog( AWS_IOT_LOG_INFO, NULL, __VA_ARGS__ ) + #define AwsIotLogDebug( ... ) AwsIotLog( AWS_IOT_LOG_DEBUG, NULL, __VA_ARGS__ ) + + /* If log level is DEBUG, enable the function to print buffers. */ + #if _LIBRARY_LOG_LEVEL >= AWS_IOT_LOG_DEBUG + #define AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) \ + AwsIotLogGeneric_PrintBuffer( _LIBRARY_LOG_NAME, \ + pHeader, \ + pBuffer, \ + bufferSize ) + #else + #define AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) + #endif + /* Remove references to AwsIotLog from the source code if logging is disabled. */ + #else + /* @[declare_logging_log] */ + #define AwsIotLog( messageLevel, pLogConfig, ... ) + /* @[declare_logging_log] */ + /* @[declare_logging_printbuffer] */ + #define AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) + /* @[declare_logging_printbuffer] */ + #define AwsIotLogError( ... ) + #define AwsIotLogWarn( ... ) + #define AwsIotLogInfo( ... ) + #define AwsIotLogDebug( ... ) + #endif +#endif + +#endif /* ifndef _AWS_IOT_LOGGING_SETUP_H_ */ diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h new file mode 100644 index 0000000000..bd1c9e5caa --- /dev/null +++ b/lib/include/aws_iot_mqtt.h @@ -0,0 +1,1804 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt.h + * @brief User-facing functions and structs of the MQTT 3.1.1 library. + */ + +#ifndef _AWS_IOT_MQTT_H_ +#define _AWS_IOT_MQTT_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/* Platform threads include. */ +#include "platform/aws_iot_threads.h" + +/*---------------------------- MQTT handle types ----------------------------*/ + +/** + * @handles{mqtt,MQTT library} + */ + +/** + * @ingroup mqtt_datatypes_handles + * @brief Opaque handle of an MQTT connection. + * + * This type identifies an MQTT connection, which is valid after a successful call + * to @ref mqtt_function_connect. A variable of this type is passed as the first + * argument to [MQTT library functions](@ref mqtt_functions) to identify which + * connection that function acts on. + * + * A call to @ref mqtt_function_disconnect makes a connection handle invalid. Once + * @ref mqtt_function_disconnect returns, the connection handle should no longer + * be used. + * + * @initializer{AwsIotMqttConnection_t,AWS_IOT_MQTT_CONNECTION_INITIALIZER} + */ +typedef void * AwsIotMqttConnection_t; + +/** + * @ingroup mqtt_datatypes_handles + * @brief Opaque handle that references an in-progress MQTT operation. + * + * Set as an output parameter of @ref mqtt_function_publish, @ref mqtt_function_subscribe, + * and @ref mqtt_function_unsubscribe. These functions queue an MQTT operation; the result + * of the operation is unknown until a response from the MQTT server is received. Therefore, + * this handle serves as a reference to MQTT operations awaiting MQTT server response. + * + * This reference will be valid from the successful return of @ref mqtt_function_publish, + * @ref mqtt_function_subscribe, or @ref mqtt_function_unsubscribe. The reference becomes + * invalid once the [completion callback](@ref AwsIotMqttCallbackInfo_t) is invoked, or + * @ref mqtt_function_wait returns. + * + * @initializer{AwsIotMqttReference_t,AWS_IOT_MQTT_REFERENCE_INITIALIZER} + * + * @see @ref mqtt_function_wait and #AWS_IOT_MQTT_FLAG_WAITABLE for waiting on a reference. + * #AwsIotMqttCallbackInfo_t and #AwsIotMqttCallbackParam_t for an asynchronous notification + * of completion. + */ +typedef void * AwsIotMqttReference_t; + +/*-------------------------- MQTT enumerated types --------------------------*/ + +/** + * @enums{mqtt,MQTT library} + */ + +/** + * @ingroup mqtt_datatypes_enums + * @brief Return codes of [MQTT functions](@ref mqtt_functions). + * + * The function @ref mqtt_function_strerror can be used to get a return code's + * description. + */ +typedef enum AwsIotMqttError +{ + /** + * @brief MQTT operation completed successfully. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_publish with QoS 0 parameter + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * Will also be the value of an operation completion callback's + * #AwsIotMqttCallbackParam_t.result when successful. + */ + AWS_IOT_MQTT_SUCCESS = 0, + + /** + * @brief MQTT operation queued, awaiting result. + * + * Functions that may return this value: + * - @ref mqtt_function_subscribe + * - @ref mqtt_function_unsubscribe + * - @ref mqtt_function_publish with QoS 1 parameter + */ + AWS_IOT_MQTT_STATUS_PENDING, + + /** + * @brief Initialization failed. + * + * Functions that may return this value: + * - @ref mqtt_function_init + */ + AWS_IOT_MQTT_INIT_FAILED, + + /** + * @brief At least one parameter is invalid. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + * - @ref mqtt_function_wait + */ + AWS_IOT_MQTT_BAD_PARAMETER, + + /** + * @brief MQTT operation failed because of memory allocation failure. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + */ + AWS_IOT_MQTT_NO_MEMORY, + + /** + * @brief MQTT packet could not be transmitted on the network. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #AwsIotMqttCallbackParam_t.result. + */ + AWS_IOT_MQTT_SEND_ERROR, + + /** + * @brief MQTT response packet received from the network is malformed. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #AwsIotMqttCallbackParam_t.result. + * + * @note If this value is received, the network connection has been closed + * (unless a [disconnect function](@ref AwsIotMqttNetIf_t.disconnect) was not + * provided to @ref mqtt_function_connect). + */ + AWS_IOT_MQTT_BAD_RESPONSE, + + /** + * @brief A blocking MQTT operation timed out. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + */ + AWS_IOT_MQTT_TIMEOUT, + + /** + * @brief A CONNECT or at least one subscription was refused by the server. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait, but only when its #AwsIotMqttReference_t parameter + * is associated with a SUBSCRIBE operation. + * - @ref mqtt_function_timedsubscribe + * + * May also be the value of an operation completion callback's + * #AwsIotMqttCallbackParam_t.result for a SUBSCRIBE. + * + * @note If this value is returned and multiple subscriptions were passed to + * @ref mqtt_function_subscribe (or @ref mqtt_function_timedsubscribe), it's + * still possible that some of the subscriptions succeeded. This value only + * signifies that AT LEAST ONE subscription was rejected. The function @ref + * mqtt_function_issubscribed can be used to determine which subscriptions + * were accepted or rejected. + */ + AWS_IOT_MQTT_SERVER_REFUSED, + + /** + * @brief A QoS 1 PUBLISH received no response and [the retry limit] + * (#AwsIotMqttPublishInfo_t.retryLimit) was reached. + * + * Functions that may return this value: + * - @ref mqtt_function_wait, but only when its #AwsIotMqttReference_t parameter + * is associated with a QoS 1 PUBLISH operation + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #AwsIotMqttCallbackParam_t.result for a QoS 1 PUBLISH. + */ + AWS_IOT_MQTT_RETRY_NO_RESPONSE +} AwsIotMqttError_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief Types of MQTT operations. + * + * The function @ref mqtt_function_operationtype can be used to get an operation + * type's description. + */ +typedef enum AwsIotMqttOperationType +{ + AWS_IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ + AWS_IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ + AWS_IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ + AWS_IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ + AWS_IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ + AWS_IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ + AWS_IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ +} AwsIotMqttOperationType_t; + +/*------------------------- MQTT parameter structs --------------------------*/ + +/** + * @paramstructs{mqtt,MQTT} + */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a new MQTT connection. + * + * @paramfor @ref mqtt_function_connect + * + * Passed as an argument to @ref mqtt_function_connect. The members of this struct + * (except for `awsIotMqttMode`) correspond to the content of an [MQTT CONNECT packet.] + * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) + * + * @initializer{AwsIotMqttConnectInfo_t,AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + */ +typedef struct AwsIotMqttConnectInfo +{ + /** + * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. + * + * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] + * (https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt) + * When this member is `true`, the MQTT library will accommodate these + * differences. This setting should be `false` when communicating with a + * fully-compliant MQTT broker. + * + * @attention This setting MUST be `true` when using the AWS IoT MQTT + * server; it MUST be `false` otherwise. + * @note Currently, @ref AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER sets this + * this member to `true`. + */ + bool awsIotMqttMode; + + /** + * @brief Whether this connection is a clean session. + * + * If this parameter is `false`, the MQTT connection should be established with + * @ref mqtt_function_connect. Otherwise, if this parameter is `true`, the MQTT + * connection should be established with @ref mqtt_function_connectrestoresession. + */ + bool cleanSession; + + uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ + + const char * pClientIdentifier; /**< @brief MQTT client identifier. */ + uint16_t clientIdentifierLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pClientIdentifier. */ + + /* These credentials are not used by AWS IoT and may be ignored if + * awsIotMqttMode is true. */ + const char * pUserName; /**< @brief Username for MQTT connection. */ + uint16_t userNameLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pUserName. */ + const char * pPassword; /**< @brief Password for MQTT connection. */ + uint16_t passwordLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pPassword. */ +} AwsIotMqttConnectInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a PUBLISH message. + * + * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publish + * + * Passed to @ref mqtt_function_publish as the message to publish and @ref + * mqtt_function_connect as the Last Will and Testament (LWT) message. + * + * @initializer{AwsIotMqttPublishInfo_t,AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER} + * + * #AwsIotMqttPublishInfo_t.retryMs and #AwsIotMqttPublishInfo_t.retryLimit are only + * relevant to QoS 1 PUBLISH messages. They are ignored for QoS 0 PUBLISH + * messages and LWT messages. These members control retransmissions of QoS 1 + * messages under the following rules: + * - Retransmission is disabled when #AwsIotMqttPublishInfo_t.retryLimit is 0. + * After sending the PUBLISH, the library will wait indefinitely for a PUBACK. + * - If #AwsIotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes + * that do not receive a PUBACK within #AwsIotMqttPublishInfo_t.retryMs will be + * retransmitted, up to #AwsIotMqttPublishInfo_t.retryLimit times. + * + * Retransmission follows a truncated exponential backoff strategy. The constant + * @ref AWS_IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. + * + * After #AwsIotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT + * library will wait @ref AWS_IOT_MQTT_RESPONSE_WAIT_MS before a final check + * for a PUBACK. If no PUBACK was received within this time, the QoS 1 PUBLISH + * fails with the code #AWS_IOT_MQTT_RETRY_NO_RESPONSE. + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + * + * @note The AWS IoT MQTT server does not support the DUP bit. When + * [using this library with the AWS IoT MQTT server](@ref AwsIotMqttConnectInfo_t.awsIotMqttMode), + * retransmissions will instead be sent with a new packet identifier in the PUBLISH + * packet. This is a nonstandard workaround. Note that this workaround has some + * flaws, including the following: + * - The previous packet identifier is forgotten, so if a PUBACK arrives for that + * packet identifier, it will be ignored. On an exceptionally busy network, this + * may cause excessive retransmissions when too many PUBACKS arrive after the + * PUBLISH packet identifier is changed. However, the exponential backoff + * retransmission strategy should mitigate this problem. + * - Log messages will be printed using the new packet identifier; the old packet + * identifier is not saved. + * + * Example + * + * Consider a situation where + * - @ref AWS_IOT_MQTT_RETRY_MS_CEILING is 60000 + * - #AwsIotMqttPublishInfo_t.retryMs is 2000 + * - #AwsIotMqttPublishInfo_t.retryLimit is 20 + * + * A PUBLISH message will be retransmitted at the following times after the initial + * transmission if no PUBACK is received: + * - 2000 ms (2000 ms after previous transmission) + * - 6000 ms (4000 ms after previous transmission) + * - 14000 ms (8000 ms after previous transmission) + * - 30000 ms (16000 ms after previous transmission) + * - 62000 ms (32000 ms after previous transmission) + * - 122000 ms, 182000 ms, 242000 ms... (every 60000 ms until 20 transmissions have been sent) + * + * After the 20th retransmission, the MQTT library will wait + * @ref AWS_IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. + */ +typedef struct AwsIotMqttPublishInfo +{ + int QoS; /**< @brief QoS of message. Must be 0 or 1. */ + bool retain; /**< @brief MQTT message retain flag. */ + + const char * pTopicName; /**< @brief Topic name of PUBLISH. */ + uint16_t topicNameLength; /**< @brief Length of #AwsIotMqttPublishInfo_t.pTopicName. */ + + const void * pPayload; /**< @brief Payload of PUBLISH. */ + size_t payloadLength; /**< @brief Length of #AwsIotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ + + uint64_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ + int retryLimit; /**< @brief How many times to attempt retransmission. */ +} AwsIotMqttPublishInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Parameter to an MQTT callback function. + * + * @paramfor MQTT callback functions + * + * The MQTT library passes this struct to registered callback whenever an + * operation completes or a message is received on a topic filter. + * + * The members of this struct are different based on the callback trigger. If the + * callback function was triggered for completed operation, the `operation` + * member is valid. Otherwise, if the callback was triggered because of a + * server-to-client PUBLISH, the `message` member is valid. + * + * For an incoming PUBLISH, the `message.pTopicFilter` parameter provides the + * subscription topic filter that matched the topic name in the PUBLISH. Because + * topic filters may use MQTT wildcards, the topic filter may be different from the + * topic name. This pointer must be treated as read-only; the topic filter must not + * be modified. Additionally, the topic filter may go out of scope as soon as the + * callback function returns, so it must be copied if it is needed at a later time. + * + * @attention Any pointers in this callback parameter may be freed as soon as + * the [callback function](@ref AwsIotMqttCallbackInfo_t.function) returns. + * Therefore, data must be copied if it is needed after the callback function + * returns. + * @attention The MQTT library may set strings that are not NULL-terminated. + * + * @see #AwsIotMqttCallbackInfo_t for the signature of a callback function. + */ +typedef struct AwsIotMqttCallbackParam +{ + /** + * @brief The MQTT connection associated with this completed operation or + * incoming PUBLISH. + * + * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback. + * However, blocking function calls (including @ref mqtt_function_wait) are + * not recommended (though still safe). + */ + AwsIotMqttConnection_t mqttConnection; + + union + { + /* Valid for completed operations. */ + struct + { + AwsIotMqttOperationType_t type; /**< @brief Type of operation that completed. */ + AwsIotMqttReference_t reference; /**< @brief Reference to the operation that completed. */ + AwsIotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ + } operation; + + /* Valid for incoming PUBLISH messages. */ + struct + { + const char * pTopicFilter; /**< @brief Topic filter that matched the message. */ + uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ + AwsIotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ + } message; + }; +} AwsIotMqttCallbackParam_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a user-provided MQTT callback function. + * + * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, + * and @ref mqtt_function_publish. Cannot be used with #AWS_IOT_MQTT_FLAG_WAITABLE. + * + * Provides a function to be invoked when an operation completes or when a + * server-to-client PUBLISH is received. + * + * @initializer{AwsIotMqttCallbackInfo_t,AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER} + * + * Below is an example for receiving an asynchronous notification on operation + * completion. See @ref mqtt_function_subscribe for an example of using this struct + * with for incoming PUBLISH messages. + * + * @code{c} + * // Operation completion callback. + * void operationComplete( void * pArgument, AwsIotMqttCallbackParam_t * const pOperation ); + * + * // Callback information. + * AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * callbackInfo.function = operationComplete; + * + * // Operation to wait for. + * AwsIotMqttError_t result = AwsIotMqtt_Publish( mqttConnection, + * &publishInfo, + * 0, + * &callbackInfo, + * &reference ); + * + * // Publish should have returned AWS_IOT_MQTT_STATUS_PENDING. Once a response + * // is received, operationComplete is executed with the actual status passed + * // in pOperation. + * @endcode + */ +typedef struct AwsIotMqttCallbackInfo +{ + void * param1; /**< @brief The first parameter to pass to the callback function. */ + + /** + * @brief User-provided callback function signature. + * + * @param[in] void * #AwsIotMqttCallbackInfo_t.param1 + * @param[in] AwsIotMqttCallbackParam_t * Details on the outcome of the MQTT operation + * or an incoming MQTT PUBLISH. + * + * @see #AwsIotMqttCallbackParam_t for more information on the second parameter. + */ + void ( * function )( void *, + AwsIotMqttCallbackParam_t * const ); +} AwsIotMqttCallbackInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on an MQTT subscription. + * + * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe + * + * An array of these is passed to @ref mqtt_function_subscribe and @ref + * mqtt_function_unsubscribe. However, #AwsIotMqttSubscription_t.callback and + * and #AwsIotMqttSubscription_t.QoS are ignored by @ref mqtt_function_unsubscribe. + * + * @initializer{AwsIotMqttSubscription_t,AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + * @see #AwsIotMqttCallbackInfo_t for details on setting a callback function. + */ +typedef struct AwsIotMqttSubscription +{ + int QoS; /**< @brief QoS of messages delivered on subscription. + * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe */ + + const char * pTopicFilter; /**< @brief Topic filter of subscription. */ + uint16_t topicFilterLength; /**< @brief Length of #AwsIotMqttSubscription_t.pTopicFilter. */ + + AwsIotMqttCallbackInfo_t callback; /**< @brief Callback to invoke when a message is received. + * See #AwsIotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. */ +} AwsIotMqttSubscription_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Provides the network functions for sending data and closing connections. + * + * @paramfor @ref mqtt_function_connect + * + * The MQTT library needs to be able to send and receive data over a network. + * This struct provides the functions (as function pointers) for sending data + * and closing the network connection. In addition to providing this struct to + * @ref mqtt_function_connect, the function @ref mqtt_function_receivecallback + * should be called to process data received from the network. + * + * @initializer{AwsIotMqttNetIf_t,AWS_IOT_MQTT_NETIF_INITIALIZER} + */ +typedef struct AwsIotMqttNetIf +{ + void * pSendContext; /**< Passed as the first argument to #AwsIotMqttNetIf_t.send. */ + void * pDisconnectContext; /**< Passed as the first argument to #AwsIotMqttNetIf_t.disconnect. */ + + /** + * @brief Function that sends data on the network. + * + * @param[in] void * #AwsIotMqttNetIf_t.pSendContext + * @param[in] const void * const Pointer to the data to send. + * @param[in] size_t Size of the data to send. + * + * @return Number of bytes successfully sent, 0 on failure. + */ + size_t ( * send )( void *, + const void * const, + size_t ); + + /** + * @brief Function that closes the network connection. + * + * If this function is not provided, the network connection will not be closed + * by the MQTT library. + * + * @param[in] void * #AwsIotMqttNetIf_t.pDisconnectContext + * + * @note Optional; set to `NULL` to ignore. The MQTT spec states that connections + * must be closed in certain conditions; if this function is not provided, the + * MQTT library is noncompliant. + */ + void ( * disconnect )( void * ); + + /* + * In addition to providing the network send and disconnect functions, this + * struct also allows the MQTT serialization and deserialization functions + * to be overridden for an MQTT connection. The compile-time setting + * AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 1 to enable this + * functionality. See the AwsIotMqttNetIf_t.serialize and + * AwsIotMqttNetIf_t.deserialize members for a list of functions that can be + * overridden. In addition, the functions for freeing packets and determining + * the packet type can also be overridden. If + * AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is 1, the serializer initialization + * and cleanup functions may be extended. See documentation of + * AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. + * + * If any function pointers that are NULL (the default value set by + * AWS_IOT_MQTT_NETIF_INITIALIZER), then the default implementation of that + * function will be used. + */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + + struct + { + /** + * @brief CONNECT packet serializer function. + * @param[in] AwsIotMqttConnectInfo_t* User-provided CONNECT information. + * @param[in] AwsIotMqttPublishInfo_t* User-provided Last Will and Testament information. + * @param[out] uint8_t** Where the CONNECT packet is written. + * @param[out] size_t* Size of the CONNECT packet. + * + * Default implementation: #AwsIotMqttInternal_SerializeConnect + */ + AwsIotMqttError_t ( * connect )( const AwsIotMqttConnectInfo_t * const /* pConnectInfo */, + const AwsIotMqttPublishInfo_t * const /* pWillInfo */, + uint8_t ** const /* pConnectPacket */, + size_t * const /* pPacketSize */ ); + + /** + * @brief PUBLISH packet serializer function. + * @param[in] AwsIotMqttPublishInfo_t* User-provided PUBLISH information. + * @param[out] uint8_t** Where the PUBLISH packet is written. + * @param[out] size_t* Size of the PUBLISH packet. + * @param[out] uint16_t* The packet identifier generated for this PUBLISH. + * + * Default implementation: #AwsIotMqttInternal_SerializePublish + */ + AwsIotMqttError_t ( * publish )( const AwsIotMqttPublishInfo_t * const /* pPublishInfo */, + uint8_t ** const /* pPublishPacket */, + size_t * const /* pPacketSize */, + uint16_t * const /* pPacketIdentifier */ ); + + /** + * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. + * @param[in] bool Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. + * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). + * + * Default implementation: #AwsIotMqttInternal_PublishSetDup + */ + void ( * publishSetDup )( bool /* awsIotMqttMode */, + uint8_t * const /* pPublishPacket */, + uint16_t * const /* pNewPacketIdentifier */ ); + + /** + * @brief PUBACK packet serializer function. + * @param[in] uint16_t The packet identifier to place in PUBACK. + * @param[out] uint8_t** Where the PUBACK packet is written. + * @param[out] size_t* Size of the PUBACK packet. + * + * Default implementation: #AwsIotMqttInternal_SerializePuback + */ + AwsIotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, + uint8_t ** const /* pPubackPacket */, + size_t * const /* pPacketSize */ ); + + /** + * @brief SUBSCRIBE packet serializer function. + * @param[in] AwsIotMqttSubscription_t* User-provided array of subscriptions. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the SUBSCRIBE packet is written. + * @param[out] size_t* Size of the SUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. + * + * Default implementation: #AwsIotMqttInternal_SerializeSubscribe + */ + AwsIotMqttError_t ( * subscribe )( const AwsIotMqttSubscription_t * const /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** const /* pSubscribePacket */, + size_t * const /* pPacketSize */, + uint16_t * const /* pPacketIdentifier */ ); + + /** + * @brief UNSUBSCRIBE packet serializer function. + * @param[in] AwsIotMqttSubscription_t* User-provided array of subscriptions to remove. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the UNSUBSCRIBE packet is written. + * @param[out] size_t* Size of the UNSUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this UNSUBSCRIBE. + * + * Default implementation: #AwsIotMqttInternal_SerializeUnsubscribe + */ + AwsIotMqttError_t ( * unsubscribe )( const AwsIotMqttSubscription_t * const /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** const /* pUnsubscribePacket */, + size_t * const /* pPacketSize */, + uint16_t * const /* pPacketIdentifier */ ); + + /** + * @brief PINGREQ packet serializer function. + * @param[out] uint8_t** Where the PINGREQ packet is written. + * @param[out] size_t* Size of the PINGREQ packet. + * + * Default implementation: #AwsIotMqttInternal_SerializePingreq + */ + AwsIotMqttError_t ( * pingreq )( uint8_t ** const /* pPingreqPacket */, + size_t * const /* pPacketSize */ ); + + /** + * @brief DISCONNECT packet serializer function. + * @param[out] uint8_t** Where the DISCONNECT packet is written. + * @param[out] size_t* Size of the DISCONNECT packet. + * + * Default implementation: #AwsIotMqttInternal_SerializeDisconnect + */ + AwsIotMqttError_t ( * disconnect )( uint8_t ** const /* pDisconnectPacket */, + size_t * const /* pPacketSize */ ); + } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ + + struct + { + /** + * @brief CONNACK packet deserializer function. + * @param[in] uint8_t* Pointer to the start of a CONNACK packet. + * @param[in] size_t Length of the data stream. + * @param[out] size_t* The number of bytes in the data stream processed + * by this function. + * + * Default implementation: #AwsIotMqttInternal_DeserializeConnack + */ + AwsIotMqttError_t ( * connack )( const uint8_t * const /* pConnackStart */, + size_t /* dataLength */, + size_t * const /* pBytesProcessed */ ); + + /** + * @brief PUBLISH packet deserializer function. + * @param[in] uint8_t* Pointer to the start of a PUBLISH packet. + * @param[in] size_t Length of the data stream. + * @param[out] AwsIotMqttPublishInfo_t* Where the deserialized PUBLISH will be written. + * @param[out] uint16_t* The packet identifier in the PUBLISH. + * @param[out] size_t* The number of bytes in the data stream processed + * by this function. + * + * Default implementation: #AwsIotMqttInternal_DeserializePublish + */ + AwsIotMqttError_t ( * publish )( const uint8_t * const /* pPublishStart */, + size_t /* dataLength */, + AwsIotMqttPublishInfo_t * const /* pOutput */, + uint16_t * const /* pPacketIdentifier */, + size_t * const /* pBytesProcessed */ ); + + /** + * @brief PUBACK packet deserializer function. + * @param[in] uint8_t* Pointer to the start of a PUBACK packet. + * @param[in] size_t Length of the data stream. + * @param[out] uint16_t* The packet identifier in the PUBACK. + * @param[out] size_t* The number of bytes in the data stream processed + * by this function. + * + * Default implementation: #AwsIotMqttInternal_DeserializePuback + */ + AwsIotMqttError_t ( * puback )( const uint8_t * const /* pPubackStart */, + size_t /* dataLength */, + uint16_t * const /* pPacketIdentifier */, + size_t * const /* pBytesProcessed */ ); + + /** + * @brief SUBACK packet deserializer function. + * @param[in] AwsIotMqttConnection_t The MQTT connection associated with + * the subscription. Rejected topic filters should be removed from this + * connection. + * @param[in] uint8_t* Pointer to the start of a SUBACK packet. + * @param[in] size_t Length of the data stream. + * @param[out] uint16_t* The packet identifier in the SUBACK. + * @param[out] size_t* The number of bytes in the data stream processed + * by this function. + * + * Default implementation: #AwsIotMqttInternal_DeserializeSuback + */ + AwsIotMqttError_t ( * suback )( AwsIotMqttConnection_t /* mqttConnection */, + const uint8_t * const /* pSubackStart */, + size_t /* dataLength */, + uint16_t * const /* pPacketIdentifier */, + size_t * const /* pBytesProcessed */ ); + + /** + * @brief UNSUBACK packet deserializer function. + * @param[in] uint8_t* Pointer to the start of a UNSUBACK packet. + * @param[in] size_t Length of the data stream. + * @param[out] uint16_t* The packet identifier in the UNSUBACK. + * @param[out] size_t* The number of bytes in the data stream processed + * by this function. + * + * Default implementation: #AwsIotMqttInternal_DeserializeUnsuback + */ + AwsIotMqttError_t ( * unsuback )( const uint8_t * const /* pUnsubackStart */, + size_t /* dataLength */, + uint16_t * const /* pPacketIdentifier */, + size_t * const /* pBytesProcessed */ ); + + /** + * @brief PINGRESP packet deserializer function. + * @param[in] uint8_t* Pointer to the start of a PINGRESP packet. + * @param[in] size_t Length of the data stream. + * @param[out] size_t* The number of bytes in the data stream processed + * by this function. + * + * Default implementation: #AwsIotMqttInternal_DeserializePingresp + */ + AwsIotMqttError_t ( * pingresp )( const uint8_t * const /* pPingrespStart */, + size_t /* dataLength */, + size_t * const /* pBytesProcessed */ ); + } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ + + /** + * @brief Get the MQTT packet type from a stream of bytes. + * + * @param[in] uint8_t* pPacket Pointer to the beginning of the byte stream. + * @param[in] size_t Size of the byte stream. + * + * Default implementation: #AwsIotMqttInternal_GetPacketType + */ + uint8_t ( * getPacketType )( const uint8_t * const /* pPacket */, + size_t /* packetSize */ ); + + /** + * @brief Free a packet generated by the serializer. + * + * This function pointer must be set if any other serializer override is set. + * @param[in] uint8_t* The packet to free. + * + * Default implementation: #AwsIotMqttInternal_FreePacket + */ + void ( * freePacket )( uint8_t * /* pPacket */ ); + #endif /* if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +} AwsIotMqttNetIf_t; + +/*------------------------- MQTT defined constants --------------------------*/ + +/** + * @constantspage{mqtt,MQTT library} + * + * @section mqtt_constants_initializers MQTT Initializers + * @brief Provides default values for the data types of the MQTT library. + * + * @snippet this define_mqtt_initializers + * + * All user-facing data types of the MQTT library should be initialized using + * one of the following. + * + * @warning Failing to initialize an MQTT data type with the appropriate initializer + * may result in undefined behavior! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + * AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + * AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + * AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * AwsIotMqttConnection_t connection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + * AwsIotMqttReference_t reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * @endcode + * + * @section mqtt_constants_flags MQTT Function Flags + * @brief Flags that modify the behavior of MQTT library functions. + * - #AWS_IOT_MQTT_FLAG_WAITABLE
+ * @copybrief AWS_IOT_MQTT_FLAG_WAITABLE + * + * Flags should be bitwise-ORed with each other to change the behavior of + * @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, or + * @ref mqtt_function_publish. + * + * @note The values of the flags may change at any time in future versions, but + * their names will remain the same. Additionally, flags will be bitwise-exclusive + * of each other. + */ + +/* @[define_mqtt_initializers] */ +#define AWS_IOT_MQTT_NETIF_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttNetIf_t. */ +/** @brief Initializer for #AwsIotMqttConnectInfo_t. */ +#define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { .awsIotMqttMode = true, \ + .cleanSession = true } +#define AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttPublishInfo_t. */ +#define AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttSubscription_t. */ +#define AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttCallbackInfo_t. */ +#define AWS_IOT_MQTT_CONNECTION_INITIALIZER NULL /**< @brief Initializer for #AwsIotMqttConnection_t. */ +#define AWS_IOT_MQTT_REFERENCE_INITIALIZER NULL /**< @brief Initializer for #AwsIotMqttReference_t. */ +/* @[define_mqtt_initializers] */ + +/** + * @brief Allows the use of @ref mqtt_function_wait for blocking until completion. + * + * This flag is always valid for @ref mqtt_function_subscribe and + * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, + * the parameter [pPublishInfo->QoS](@ref AwsIotMqttPublishInfo_t.QoS) must not be `0`. + * + * An #AwsIotMqttReference_t MUST be provided if this flag is set. Additionally, an + * #AwsIotMqttCallbackInfo_t MUST NOT be provided. + * + * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up + * resources. + */ +#define AWS_IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) + +/*------------------------- MQTT library functions --------------------------*/ + +/** + * @functionspage{mqtt,MQTT library} + * - @functionname{mqtt_function_init} + * - @functionname{mqtt_function_cleanup} + * - @functionname{mqtt_function_receivecallback} + * - @functionname{mqtt_function_connect} + * - @functionname{mqtt_function_connectrestoresession} + * - @functionname{mqtt_function_disconnect} + * - @functionname{mqtt_function_subscribe} + * - @functionname{mqtt_function_timedsubscribe} + * - @functionname{mqtt_function_unsubscribe} + * - @functionname{mqtt_function_timedunsubscribe} + * - @functionname{mqtt_function_publish} + * - @functionname{mqtt_function_timedpublish} + * - @functionname{mqtt_function_wait} + * - @functionname{mqtt_function_strerror} + * - @functionname{mqtt_function_operationtype} + * - @functionname{mqtt_function_issubscribed} + */ + +/** + * @functionpage{AwsIotMqtt_Init,mqtt,init} + * @functionpage{AwsIotMqtt_Cleanup,mqtt,cleanup} + * @functionpage{AwsIotMqtt_ReceiveCallback,mqtt,receivecallback} + * + * @anchor mqtt_function_receivecallback_nopartial + * Sequence Diagram: Processing a network buffer without partial packets + * @image html mqtt_function_receivecallback_nopartial.png width=80% + * + * @anchor mqtt_function_receivecallback_partial + * Sequence Diagram: Processing a network buffer with partial packets + * @image html mqtt_function_receivecallback_partial.png width=80% + * + * @functionpage{AwsIotMqtt_Connect,mqtt,connect} + * @functionpage{AwsIotMqtt_ConnectRestoreSession,mqtt,connectrestoresession} + * @functionpage{AwsIotMqtt_Disconnect,mqtt,disconnect} + * @functionpage{AwsIotMqtt_Subscribe,mqtt,subscribe} + * @functionpage{AwsIotMqtt_TimedSubscribe,mqtt,timedsubscribe} + * @functionpage{AwsIotMqtt_Unsubscribe,mqtt,unsubscribe} + * @functionpage{AwsIotMqtt_TimedUnsubscribe,mqtt,timedunsubscribe} + * @functionpage{AwsIotMqtt_Publish,mqtt,publish} + * @functionpage{AwsIotMqtt_TimedPublish,mqtt,timedpublish} + * @functionpage{AwsIotMqtt_Wait,mqtt,wait} + * @functionpage{AwsIotMqtt_strerror,mqtt,strerror} + * @functionpage{AwsIotMqtt_OperationType,mqtt,operationtype} + * @functionpage{AwsIotMqtt_IsSubscribed,mqtt,issubscribed} + */ + +/** + * @brief One-time initialization function for the MQTT library. + * + * This function performs internal setup of the MQTT library. It must be called + * once (and only once) before calling any other MQTT function. Calling this + * function more than once without first calling @ref mqtt_function_cleanup + * may result in a crash. + * + * @return One of the following: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_INIT_FAILED + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref mqtt_function_cleanup + */ +/* @[declare_mqtt_init] */ +AwsIotMqttError_t AwsIotMqtt_Init( void ); +/* @[declare_mqtt_init] */ + +/** + * @brief One-time deinitialization function for the MQTT library. + * + * This function frees resources taken in @ref mqtt_function_init. It should be + * called after [closing all MQTT connections](@ref mqtt_function_disconnect) to + * clean up the MQTT library. After this function returns, @ref mqtt_function_init + * must be called again before calling any other MQTT function. + * + * @warning No thread-safety guarantees are provided for this function. Do not + * call this function if any MQTT connections are open! + * + * @see @ref mqtt_function_init + */ +/* @[declare_mqtt_cleanup] */ +void AwsIotMqtt_Cleanup( void ); +/* @[declare_mqtt_cleanup] */ + +/** + * @brief Network receive callback for the MQTT library. + * + * This function should be called by the system whenever a stream of MQTT data + * is received from the network. It processes the data stream and decodes any + * MQTT packets it finds. The MQTT library uses #AwsIotMqttNetIf_t for sending + * data and closing network connections. + * + * @attention Remember that this function's input `pReceivedData` is a data + * stream! A data stream may contain any number of different MQTT packets, + * including incomplete MQTT packets. + * + * The input parameter `pReceivedData` must be allocated by the system. The system + * should read data from the network and place the data in `pReceivedData`. Then, + * the system should call this function. + * + * An important concept associated with this function is *buffer ownership*. + * Normally, once `pReceivedData` is passed to this function, it is considered + * property of the MQTT library. The system must keep `pReceivedData` valid + * and in-scope even after this function returns, and must not make any changes + * to `pReceivedData` until the MQTT library calls `freeReceivedData` (which + * transfers ownership of `pReceivedData` back to the system). + * [This sequence diagram](@ref mqtt_function_receivecallback_nopartial) illustrates + * the flow where `pReceivedData` is completely processed. + * + * If `pReceivedData` ends with a partial MQTT packet, the MQTT library will not + * call `freeReceivedData`. In this case, this function will return the actual + * number of bytes processed (from this point onwards called `bytesProcessed`). + * The buffer `pReceivedData` will be returned to the system. The system should + * wait for the rest of the MQTT packet to be received and place the remainder + * of the MQTT packet immediately after the last processed byte in `pReceivedData`. + * The bytes in `pReceivedData` in the range of `[offset, offset+bytesProcessed]` + * should be considered "already processed" and should be discarded. + * [This sequence diagram](@ref mqtt_function_receivecallback_partial) illustrates + * the case where `pReceivedData` contains a partial packet. + * + * If `freeReceivedData` is `NULL`, then ownership of `pReceivedData` will always + * revert to the system as soon as this function returns. In this scenario, the + * MQTT library will copy any data it requires out of `pReceivedData`. Therefore, + * passing `NULL` for `freeReceivedData` may increase memory usage. + * + * @param[in] pMqttConnection A pointer to the MQTT connection handle for which + * the packet was received. + * @param[in] pReceivedData Pointer to the beginning of the data stream. This buffer + * must remain valid and in-scope until the MQTT library calls `freeReceivedData`. + * @param[in] offset The offset (in bytes) into `pReceivedData` where the MQTT library + * begins processing. All bytes from `pReceivedData+offset` to `pReceivedData+dataLength` + * will be processed. Pass `0` to start from the beginning of `pReceivedData`. + * @param[in] dataLength The length of `pReceivedData` in bytes. + * @param[in] freeReceivedData The function that the MQTT library calls when it is + * finished with `pReceivedData`. This function will only be called when all data is + * successfully processed, i.e. when this function returns a value of `dataLength-offset`. + * Pass `NULL` to ignore. + * + * @return + * - `-1` if a protocol violation is encountered. If this function returns `-1`, then + * the network connection is closed (unless a [disconnect function] + * (@ref AwsIotMqttNetIf_t.disconnect) was not provided to @ref mqtt_function_connect). + * `freeReceivedData` is not called if this function returns `-1`. + * - Number of bytes processed otherwise. If the return value is less than `dataLength` + * (but not `-1`), the data stream probably contained a partial MQTT packet. The function + * `freeReceivedData` will only be called if all bytes in the range `pReceivedData+offset` + * to `pReceivedData+dataLength` were successfully processed. + */ +/* @[declare_mqtt_receivecallback] */ +int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, + const void * pReceivedData, + size_t offset, + size_t dataLength, + void ( * freeReceivedData )( void * ) ); +/* @[declare_mqtt_receivecallback] */ + +/** + * @brief Establish a new MQTT connection. + * + * This function opens a connection between a new MQTT client and an MQTT server + * (also called a broker). MQTT connections are established on top of transport + * layer protocols (such as TCP/IP), and optionally, application layer security + * protocols (such as TLS). The MQTT packet that establishes a connection is called + * the MQTT CONNECT packet. After @ref mqtt_function_init, this function must be + * called before any other MQTT library function. + * + * If [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) is `true`, + * this function establishes a clean MQTT session. Subscriptions and unacknowledged + * PUBLISH messages will be discarded when the connection is closed. + * + * If [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) is `false`, + * this function establishes a persistent MQTT session. This function should only + * be used to establish a NEW persistent MQTT session, i.e. a persistent + * session that has no previous subscriptions. The function @ref + * mqtt_function_connectrestoresession should be used to restore an established + * persistent session. The flow for using persistent sessions should be: + * 1. Establish new persistent session by calling @ref mqtt_function_connect with + * [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) `= false`. + * 2. Disconnect the MQTT connection. + * 3. When reconnecting a persistent session, call @ref mqtt_function_connectrestoresession + * with [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) `= false` + * and pass a list of subscriptions used in the persistent session. + * + * This MQTT library is network agnostic, meaning it has no knowledge of the + * underlying network protocol carrying the MQTT packets. It interacts with the + * network through a network abstraction layer, allowing it to be used with many + * different network stacks. The network abstraction layer is established + * per-connection, allowing every #AwsIotMqttConnection_t to use a different network + * stack. The parameter `pNetworkInterface` sets up the network abstraction layer + * for an MQTT connection; see the documentation on #AwsIotMqttNetIf_t for details + * on its members. + * + * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet. + * Its members [are defined by the MQTT spec.](@ref AwsIotMqttConnectInfo_t). The + * `pWillInfo` parameter provides information on a Last Will and Testament (LWT) + * message to be published if the MQTT connection is closed without + * [sending a DISCONNECT packet](@ref mqtt_function_disconnect). Unlike other PUBLISH + * messages, a LWT message payload is limited to 65535 bytes in length. Additionally, + * the retry [interval](@ref AwsIotMqttPublishInfo_t.retryMs) and [limit] + * (@ref AwsIotMqttPublishInfo_t.retryLimit) members of #AwsIotMqttPublishInfo_t + * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL. + * + * Unlike @ref mqtt_function_publish, @ref mqtt_function_subscribe, and + * @ref mqtt_function_unsubscribe, this function is always blocking. Additionally, + * because the MQTT connection acknowledgement packet (CONNACK packet) does not + * contain any information on which CONNECT packet it acknowledges, only one + * CONNECT operation may be in progress at any time. This means that parallel + * threads making calls to @ref mqtt_function_connect will be serialized to send + * their CONNECT packets one-by-one. + * + * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle + * if this function succeeds. + * @param[in] pNetworkInterface The network `send` and `disconnect` functions that + * this MQTT connection will use. + * @param[in] pConnectInfo MQTT connection setup parameters. + * @param[in] pWillInfo Information on a Last Will and Testament message to be + * published if this connection is unexpectedly closed. Optional; pass `NULL` to + * ignore. + * @param[in] timeoutMs If the MQTT server does not accept the connection within + * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #AWS_IOT_MQTT_TIMEOUT + * - #AWS_IOT_MQTT_SERVER_REFUSED + * + * @see + * @ref mqtt_function_connectrestoresession for the function to restore an + * established MQTT persistent session. + * + * Example + * @code{c} + * // An initialized and connected network connection. + * AwsIotNetworkConnection_t pNetworkConnection; + * + * // Parameters to MQTT connect. + * AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + * AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + * AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + * AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * + * // Example using the OpenSSL network implementation. + * networkInterface.pSendContext = pNetworkConnection; + * networkInterface.pDisconnectContext = pNetworkConnection; + * networkInterface.send = AwsIotNetwork_Send; + * networkInterface.disconnect = AwsIotNetwork_CloseConnection; + * + * // Set the members of the connection info (password and username not used). + * connectInfo.cleanSession = true; + * connectInfo.keepAliveSeconds = 30; + * connectInfo.pClientIdentifier = "uniqueclientidentifier"; + * connectInfo.clientIdentifierLength = 22; + * + * // Set the members of the will info (retain and retry not used). + * willInfo.QoS = 1; + * willInfo.pTopicName = "will/topic/name"; + * willInfo.topicNameLength = 15; + * willInfo.pPayload = "MQTT client unexpectedly disconnected."; + * willInfo.payloadLength = 38; + * + * // Call CONNECT with a 5 second block time. Should return + * // AWS_IOT_MQTT_SUCCESS when successful. + * AwsIotMqttError_t result = AwsIotMqtt_Connect( &mqttConnection, + * &networkInterface, + * &connectInfo, + * &willInfo, + * 5000 ); + * + * if( result == AWS_IOT_MQTT_SUCCESS ) + * { + * // Do something with the MQTT connection... + * + * // Clean up and close the MQTT connection once it's no longer needed. + * AwsIotMqtt_Disconnect( mqttConnection, false ); + * } + * @endcode + */ +/* @[declare_mqtt_connect] */ +AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface, + const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + uint64_t timeoutMs ); +/* @[declare_mqtt_connect] */ + +/** + * @brief Establish an MQTT connection and restore the subscriptions of a + * persistent session. + * + * Like @ref mqtt_function_connect, this function establishes an MQTT connection + * by sending an MQTT CONNECT packet. In addition, this function also restores + * the subscription callbacks from a persistent session. + * + * The subscription list passed to this function must contain subscriptions that + * are already present on the MQTT server as part of a persistent session. This + * function does not send an MQTT SUBSCRIBE packet. The parameter + * [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) + * must be `false`. + * + * This function must only be called to restore an established persistent + * session. To create a new persistent session, use @ref mqtt_function_connect. + * + * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle + * if this function succeeds. + * @param[in] pNetworkInterface The network `send` and `disconnect` functions that + * this MQTT connection will use. + * @param[in] pConnectInfo MQTT connection setup parameters. + * @param[in] pWillInfo Information on a Last Will and Testament message to be + * published if this connection is unexpectedly closed. Optional; pass `NULL` to + * ignore. + * @param[in] pSessionSubscriptions MQTT subscriptions contained in the persistent + * session. + * @param[in] sessionSubscriptionsCount The number of persistent session subscriptions. + * @param[in] timeoutMs If the MQTT server does not accept the connection within + * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #AWS_IOT_MQTT_TIMEOUT + * - #AWS_IOT_MQTT_SERVER_REFUSED + * + * @see + * @ref mqtt_function_connect for the function to establish clean MQTT connections + * or new persistent MQTT connections. + */ +/* @[declare_mqtt_connectrestoresession] */ +AwsIotMqttError_t AwsIotMqtt_ConnectRestoreSession( AwsIotMqttConnection_t * pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface, + const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + const AwsIotMqttSubscription_t * const pSessionSubscriptions, + size_t sessionSubscriptionsCount, + uint64_t timeoutMs ); +/* @[declare_mqtt_connectrestoresession] */ + +/** + * @brief Closes an MQTT connection and frees resources. + * + * This function closes an MQTT connection and should only be called once + * the MQTT connection is no longer needed. Its exact behavior depends on the + * `cleanupOnly` parameter. + * + * Normally, `cleanupOnly` should be `false`. This gracefully shuts down an MQTT + * connection by sending an MQTT DISCONNECT packet. Any [disconnect function] + * (@ref AwsIotMqttNetIf_t.disconnect) provided [when the connection was established] + * (@ref mqtt_function_connect) will also be called. Note that because the MQTT server + * will not acknowledge a DISCONNECT packet, the client has no way of knowing if + * the server received the DISCONNECT packet. In the case where the DISCONNECT + * packet is lost in transport, any Last Will and Testament (LWT) message established + * with the connection may be published. However, if the DISCONNECT reaches the + * MQTT server, the LWT message will be discarded and not published. + * + * Should the underlying network connection become unusable, this function should + * be called with `cleanupOnly` set to `true`. In this case, no DISCONNECT packet + * nor [disconnect function](@ref AwsIotMqttNetIf_t.disconnect) will be called. + * This function will only free the resources used by the MQTT connection; it still + * must be called even if the network is offline to avoid leaking resources. + * + * Once this function is called, its parameter `mqttConnection` should no longer + * be used. + * + * @param[in] mqttConnection The MQTT connection to close and clean up. + * @param[in] cleanupOnly Passing `true` will cause this function to only perform + * cleanup of the MQTT connection and not send a DISCONNECT packet. This parameter + * should be `true` if the network goes offline or is otherwise unusable. Otherwise, + * it should be `false`. + */ +/* @[declare_mqtt_disconnect] */ +void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, + bool cleanupOnly ); +/* @[declare_mqtt_disconnect] */ + +/** + * @brief Subscribes to the given array of topic filters and receive an asynchronous + * notification when the subscribe completes. + * + * This function transmits an MQTT SUBSCRIBE packet to the server. A SUBSCRIBE + * packet notifies the server to send any matching PUBLISH messages to this client. + * A single SUBSCRIBE packet may carry more than one topic filter, hence the + * parameters to this function include an array of [subscriptions] + * (@ref AwsIotMqttSubscription_t). + * + * An MQTT subscription has two pieces: + * 1. The subscription topic filter registered with the MQTT server. The MQTT + * SUBSCRIBE packet sent from this client to server notifies the server to send + * messages matching the given topic filters to this client. + * 2. The [callback function](@ref AwsIotMqttCallbackInfo_t.function) that this + * client will invoke when an incoming message is received. The callback function + * notifies applications of an incoming PUBLISH message. + * + * The helper function @ref mqtt_function_issubscribed can be used to check if a + * [callback function](@ref AwsIotMqttCallbackInfo_t.function) is registered for + * a particular topic filter. + * + * To modify an already-registered subscription callback, call this function with + * a new `pSubscriptionList`. Any topic filters in `pSubscriptionList` that already + * have a registered callback will be replaced with the new values in `pSubscriptionList`. + * + * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid + * for subscription QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pSubscribeRef Set to a handle by which this operation may be + * referenced after this function returns. This reference is invalidated once + * the subscription operation completes. + * + * @return This function will return #AWS_IOT_MQTT_STATUS_PENDING upon success. + * @return Upon completion of the subscription (either through an + * #AwsIotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #AWS_IOT_MQTT_SERVER_REFUSED + * @return If this function fails before queuing a subscribe operation, it will return + * one of: + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * + * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. + * @see @ref mqtt_function_unsubscribe for the function that removes subscriptions. + * + * Example + * @code{c} + * #define NUMBER_OF_SUBSCRIPTIONS ... + * + * // Subscription callback function. + * void subscriptionCallback( void * pArgument, AwsIotMqttCallbackParam_t * const pPublish ); + * + * // An initialized and connected MQTT connection. + * AwsIotMqttConnection_t mqttConnection; + * + * // Subscription information. + * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + * AwsIotMqttReference_t lastOperation = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * + * // Set the subscription information. + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * pSubscriptions[ i ].QoS = 1; + * pSubscriptions[ i ].pTopicFilter = "some/topic/filter"; + * pSubscriptions[ i ].topicLength = ( uint16_t ) strlen( pSubscriptions[ i ].pTopicFilter ); + * pSubscriptions[ i ].callback.function = subscriptionCallback; + * } + * + * AwsIotMqttError_t result = AwsIotMqtt_Subscribe( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * AWS_IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); + * + * // Subscribe returns AWS_IOT_MQTT_STATUS_PENDING when successful. Wait up to + * // 5 seconds for the operation to complete. + * if( result == AWS_IOT_MQTT_STATUS_PENDING ) + * { + * result = AwsIotMqtt_Wait( subscriptionRef, 5000 ); + * } + * + * // Check that the subscriptions were successful. + * if( result == AWS_IOT_MQTT_SUCCESS ) + * { + * // Wait for messages on the subscription topic filters... + * + * // Unsubscribe once the subscriptions are no longer needed. + * result = AwsIotMqtt_Unsubscribe( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * AWS_IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); + * + * // UNSUBSCRIBE returns AWS_IOT_MQTT_STATUS_PENDING when successful. + * // Wait up to 5 seconds for the operation to complete. + * if( result == AWS_IOT_MQTT_STATUS_PENDING ) + * { + * result = AwsIotMqtt_Wait( lastOperation, 5000 ); + * } + * } + * // Check which subscriptions were rejected by the server. + * else if( result == AWS_IOT_MQTT_SERVER_REFUSED ) + * { + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * if( AwsIotMqtt_IsSubscribed( mqttConnection, + * pSubscriptions[ i ].pTopicFilter, + * pSubscriptions[ i ].topicFilterLength, + * NULL ) == false ) + * { + * // This subscription was rejected. + * } + * } + * } + * @endcode + */ +/* @[declare_mqtt_subscribe] */ +AwsIotMqttError_t AwsIotMqtt_Subscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pSubscribeRef ); +/* @[declare_mqtt_subscribe] */ + +/** + * @brief Subscribes to the given array of topic filters with a timeout. + * + * This function transmits an MQTT SUBSCRIBE packet to the server, then waits for + * a server response to the packet. Internally, this function is a call to @ref + * mqtt_function_subscribe followed by @ref mqtt_function_wait. See @ref + * mqtt_function_subscribe for more information about the MQTT SUBSCRIBE operation. + * + * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid + * for subscription QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within + * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #AWS_IOT_MQTT_TIMEOUT + * - #AWS_IOT_MQTT_SERVER_REFUSED + */ +/* @[declare_mqtt_timedsubscribe] */ +AwsIotMqttError_t AwsIotMqtt_TimedSubscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ); +/* @[declare_mqtt_timedsubscribe] */ + +/** + * @brief Unsubscribes from the given array of topic filters and receive an asynchronous + * notification when the unsubscribe completes. + * + * This function transmits an MQTT UNSUBSCRIBE packet to the server. An UNSUBSCRIBE + * packet removes registered topic filters from the server. After unsubscribing, + * the server will no longer send messages on these topic filters to the client. + * + * Corresponding [subscription callback functions](@ref AwsIotMqttCallbackInfo_t.function) + * are also removed from the MQTT connection. These subscription callback functions + * will be removed even if the MQTT UNSUBSCRIBE packet fails to send. + * + * @param[in] mqttConnection The MQTT connection used for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pUnsubscribeRef Set to a handle by which this operation may be + * referenced after this function returns. This reference is invalidated once + * the unsubscribe operation completes. + * + * @return This function will return #AWS_IOT_MQTT_STATUS_PENDING upon success. + * @return Upon completion of the unsubscribe (either through an + * #AwsIotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * @return If this function fails before queuing an unsubscribe operation, it will return + * one of: + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * + * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. + * @see @ref mqtt_function_subscribe for the function that adds subscriptions. + */ +/* @[declare_mqtt_unsubscribe] */ +AwsIotMqttError_t AwsIotMqtt_Unsubscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pUnsubscribeRef ); +/* @[declare_mqtt_unsubscribe] */ + +/** + * @brief Unsubscribes from a given array of topic filters with a timeout. + * + * This function transmits an MQTT UNSUBSCRIBE packet to the server, then waits + * for a server response to the packet. Internally, this function is a call to + * @ref mqtt_function_unsubscribe followed by @ref mqtt_function_wait. See @ref + * mqtt_function_unsubscribe for more information about the MQTT UNSUBSCRIBE + * operation. + * + * @param[in] mqttConnection The MQTT connection used for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge the UNSUBSCRIBE within + * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + */ +/* @[declare_mqtt_timedunsubscribe] */ +AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ); +/* @[declare_mqtt_timedunsubscribe] */ + +/** + * @brief Publishes a message to the given topic name and receive an asynchronous + * notification when the publish completes. + * + * This function transmits an MQTT PUBLISH packet to the server. A PUBLISH packet + * contains a payload and a topic name. Any clients with a subscription on a + * topic filter matching the PUBLISH topic name will receive a copy of the + * PUBLISH packet from the server. + * + * If a PUBLISH packet fails to reach the server and it is not a QoS 0 message, + * it will be retransmitted. See #AwsIotMqttPublishInfo_t for a description + * of the retransmission strategy. + * + * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid + * for message QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the publish. + * @param[in] pPublishInfo MQTT publish parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pPublishRef Set to a handle by which this operation may be + * referenced after this function returns. This reference is invalidated once + * the publish operation completes. + * + * @return This function will return #AWS_IOT_MQTT_STATUS_PENDING upon success for + * QoS 1 publishes. For a QoS 0 publish it returns #AWS_IOT_MQTT_SUCCESS upon + * success. + * @return Upon completion of a QoS 1 publish (either through an + * #AwsIotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #AWS_IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref AwsIotMqttPublishInfo_t.retryMs) + * and [pPublishInfo->retryLimit](@ref AwsIotMqttPublishInfo_t.retryLimit) were set). + * @return If this function fails before queuing an publish operation (regardless + * of QoS), it will return one of: + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * + * @note The parameters `pCallbackInfo` and `pPublishRef` should only be used for QoS + * 1 publishes. For QoS 0, they should both be `NULL`. + * + * @see @ref mqtt_function_timedpublish for a blocking variant of this function. + * + * Example + * @code{c} + * // An initialized and connected MQTT connection. + * AwsIotMqttConnection_t mqttConnection; + * + * // Publish information. + * AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * + * // Set the publish information. QoS 0 example (retain not used): + * publishInfo.QoS = 0; + * publishInfo.pTopicName = "some/topic/name"; + * publishInfo.topicNameLength = 15; + * publishInfo.pPayload = "payload"; + * publishInfo.payloadLength = 8; + * + * // QoS 0 publish should return AWS_IOT_MQTT_SUCCESS upon success. + * AwsIotMqttError_t qos0Result = AwsIotMqtt_Publish( mqttConnection, + * &publishInfo, + * 0, + * NULL, + * NULL ); + * + * // QoS 1 with retry example (using same topic name and payload as QoS 0 example): + * AwsIotMqttReference_t qos1Reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * publishInfo.QoS = 1; + * publishInfo.retryMs = 1000; // Retry if no response is received in 1 second. + * publishInfo.retryLimit = 5; // Retry up to 5 times. + * + * // QoS 1 publish should return AWS_IOT_MQTT_STATUS_PENDING upon success. + * AwsIotMqttError_t qos1Result = AwsIotMqtt_Publish( mqttConnection, + * &publishInfo, + * AWS_IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &qos1Reference ); + * + * // Wait up to 5 seconds for the publish to complete. + * if( qos1Result == AWS_IOT_MQTT_STATUS_PENDING ) + * { + * qos1Result = AwsIotMqtt_Wait( qos1Reference, 5000 ); + * } + * @endcode + */ +/* @[declare_mqtt_publish] */ +AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pPublishRef ); +/* @[declare_mqtt_publish] */ + +/** + * @brief Publish a message to the given topic name with a timeout. + * + * This function transmits an MQTT PUBLISH packet to the server, then waits for + * a server response to the packet. Internally, this function is a call to @ref + * mqtt_function_publish followed by @ref mqtt_function_wait. See @ref + * mqtt_function_publish for more information about the MQTT PUBLISH operation. + * + * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid + * for message QoS. + * + * @param[in] mqttConnection The MQTT connection to use for the publish. + * @param[in] pPublishInfo MQTT publish parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge a QoS 1 PUBLISH + * within this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. This parameter + * is ignored for QoS 0 PUBLISH messages. + * + * @return One of the following: + * - #AWS_IOT_MQTT_SUCCESS + * - #AWS_IOT_MQTT_BAD_PARAMETER + * - #AWS_IOT_MQTT_NO_MEMORY + * - #AWS_IOT_MQTT_SEND_ERROR + * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #AWS_IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref AwsIotMqttPublishInfo_t.retryMs) + * and [pPublishInfo->retryLimit](@ref AwsIotMqttPublishInfo_t.retryLimit) were set). + */ +/* @[declare_mqtt_timedpublish] */ +AwsIotMqttError_t AwsIotMqtt_TimedPublish( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint32_t flags, + uint64_t timeoutMs ); +/* @[declare_mqtt_timedpublish] */ + +/** + * @brief Waits for an operation to complete. + * + * This function blocks to wait for a [subscribe](@ref mqtt_function_subscribe), + * [unsubscribe](@ref mqtt_function_unsubscribe), or [publish] + * (@ref mqtt_function_publish) to complete. These operations are by default + * asynchronous; the function calls queue an operation for processing, and a + * callback is invoked once the operation is complete. + * + * To use this function, the flag #AWS_IOT_MQTT_FLAG_WAITABLE must have been + * set in the operation's function call. Additionally, this function must always + * be called with any waitable operation to clean up resources. + * + * Regardless of its return value, this function always clean up resources used + * by the waitable operation. This means `reference` is invalidated as soon as + * this function returns, even if it returns #AWS_IOT_MQTT_TIMEOUT or another error. + * + * @param[in] reference Reference to the operation to wait for. The flag + * #AWS_IOT_MQTT_FLAG_WAITABLE must have been set for this operation. + * @param[in] timeoutMs How long to wait before returning #AWS_IOT_MQTT_TIMEOUT. + * + * @return The return value of this function depends on the MQTT operation associated + * with `reference`. See #AwsIotMqttError_t for possible return values. + * + * Example + * @code{c} + * // Reference and timeout. + * AwsIotMqttReference_t reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * uint64_t timeoutMs = 5000; // 5 seconds + * + * // MQTT operation to wait for. + * AwsIotMqttError_t result = AwsIotMqtt_Publish( mqttConnection, + * &publishInfo, + * AWS_IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &reference ); + * + * // Publish should have returned AWS_IOT_MQTT_STATUS_PENDING. The call to wait + * // returns once the result of the publish is available or the timeout expires. + * result = AwsIotMqtt_Wait( reference, timeoutMs ); + * + * // After the call to wait, the result of the publish is known + * // (not AWS_IOT_MQTT_STATUS_PENDING). + * assert( result != AWS_IOT_MQTT_STATUS_PENDING ); + * @endcode + */ +/* @[declare_mqtt_wait] */ +AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, + uint64_t timeoutMs ); +/* @[declare_mqtt_wait] */ + +/*-------------------------- MQTT helper functions --------------------------*/ + +/** + * @brief Returns a string that describes an #AwsIotMqttError_t. + * + * Like the POSIX's `strerror`, this function returns a string describing a + * return code. In this case, the return code is an MQTT library error code, + * `status`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] status The status to describe. + * + * @return A read-only string that describes `status`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_mqtt_strerror] */ +const char * AwsIotMqtt_strerror( AwsIotMqttError_t status ); +/* @[declare_mqtt_strerror] */ + +/** + * @brief Returns a string that describes an #AwsIotMqttOperationType_t. + * + * This function returns a string describing an MQTT operation type, `operation`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] operation The operation to describe. + * + * @return A read-only string that describes `operation`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_mqtt_operationtype] */ +const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ); +/* @[declare_mqtt_operationtype] */ + +/** + * @brief Check if an MQTT connection has a subscription for a topic filter. + * + * This function checks whether an MQTT connection `mqttConnection` has a + * subscription callback registered for a topic filter `pTopicFilter`. If a + * subscription callback is found, its details are copied into the output parameter + * `pCurrentSubscription`. This subscription callback will be invoked for incoming + * PUBLISH messages on `pTopicFilter`. + * + * The check for a matching subscription is only performed client-side; + * therefore, this function should not be relied upon for perfect accuracy. For + * example, this function may return an incorrect result if the MQTT server + * crashes and drops subscriptions without informing the client. + * + * Note that an MQTT connection's subscriptions might change between the time this + * function checks the subscription list and its caller tests the return value. + * This function certainly should not be used concurrently with any pending SUBSCRIBE + * or UNSUBSCRIBE operations. + * + * One suitable use of this function is to check which subscriptions were rejected + * if @ref mqtt_function_subscribe returns #AWS_IOT_MQTT_SERVER_REFUSED; that return + * code only means that at least one subscription was rejected. + * + * @param[in] mqttConnection The MQTT connection to check. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of `pTopicFilter`. + * @param[out] pCurrentSubscription If a subscription is found, its details are + * copied here. This output parameter is only valid if this function returns `true`. + * Pass `NULL` to ignore. + * + * @return `true` if a subscription was found; `false` otherwise. + * + * @note The subscription QoS is not stored by the MQTT library; therefore, + * `pCurrentSubscription->QoS` will always be set to `0`. + */ +/* @[declare_mqtt_issubscribed] */ +bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, + const char * const pTopicFilter, + uint16_t topicFilterLength, + AwsIotMqttSubscription_t * pCurrentSubscription ); +/* @[declare_mqtt_issubscribed] */ + +#endif /* ifndef _AWS_IOT_MQTT_H_ */ diff --git a/lib/include/aws_iot_queue.h b/lib/include/aws_iot_queue.h new file mode 100644 index 0000000000..7f4b686219 --- /dev/null +++ b/lib/include/aws_iot_queue.h @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_queue.h + * @brief Declares queue and linked list data structures and functions. + */ + +#ifndef _AWS_IOT_QUEUE_H_ +#define _AWS_IOT_QUEUE_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Platform layer includes. */ +#include "platform/aws_iot_threads.h" + +/** + * @brief Calculates the offset of a link within its containing struct. + * + * @param[in] containerType The name of the containing struct type. + * @param[in] linkMember The name of the link member in the containing struct. + * + * Example + * @code{c} + * typedef struct Example + * { + * int member; + * AwsIotLink_t link1; + * AwsIotLink_t link2; + * } Example_t; + * + * // Calculate offset of link1. + * size_t offset = AwsIotLink_Offset( Example_t, link1 ); + * @endcode + */ +#define AwsIotLink_Offset( containerType, linkMember ) \ + ( ( size_t ) &( ( ( containerType * ) 0 )->linkMember ) ) + +/** + * @brief Calculates the starting address of a containing struct. + * + * @param[in] pLink Pointer to a link member. + * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset + * to calculate. + */ +#define AwsIotLink_Container( pLink, linkOffset ) \ + ( ( void * ) ( ( ( uint8_t * ) ( pLink ) ) - linkOffset ) ) + +/** + * @paramstructs{queue,queue} + */ + +/** + * @ingroup queue_datatypes_paramstructs + * @brief Configuration parameters of an #AwsIotQueue_t when using notifications. + * + * @paramfor @ref queue_function_create + * + * Passed to @ref queue_function_create to configure a new notification queue. + */ +typedef struct AwsIotQueueNotifyParams +{ + /** + * @brief Notification routine to run when data is added to the queue. + * + * This notification will run in its own detached thread. May be set to NULL + * to disable notification routines. + */ + AwsIotThreadRoutine_t notifyRoutine; + + /** + * @brief The argument passed to #AwsIotQueueNotifyParams_t.notifyRoutine. + * + * This member is not used by any queue function and is intended for + * application use. + */ + void * pNotifyArgument; +} AwsIotQueueNotifyParams_t; + +/** + * @defgroup queue_datatypes_queuelist Queue and list + * @brief Structures that represent a queue or list. + * + * Most members of these structures are internal and should only be modified + * using [queue or list functions](@ref queue_functions). The exception is the + * `mutex` member, which may be locked as needed to provide thread safety. + */ + +/** + * @ingroup queue_datatypes_queuelist + * @brief Link member placed in structs of a queue or list. + * + * All elements in a queue or list must contain one of these members. The macro + * #AwsIotLink_Offset can be used to calculate the offset of the link member in + * its containing struct. The macro #AwsIotLink_Container can be used to calculate + * the starting address of the containing struct. + */ +typedef struct AwsIotLink +{ + struct AwsIotLink * pPrevious; /**< @brief Pointer to previous link. */ + struct AwsIotLink * pNext; /**< @brief Pointer to next link. */ +} AwsIotLink_t; + +/** + * @ingroup queue_datatypes_queuelist + * @brief Queue structure. + * + * @attention Although the members of #AwsIotQueue_t are visible to applications, + * they should not be accessed directly. + */ +typedef struct AwsIotQueue +{ + AwsIotLink_t * pHead; /**< @brief Pointer to queue's head. */ + AwsIotLink_t * pTail; /**< @brief Pointer to queue's tail. */ + + AwsIotMutex_t mutex; /**< @brief Protects this queue from concurrent use. */ + AwsIotSemaphore_t notifyThreadCount; /**< @brief Limits the number of concurrent notification threads. */ + AwsIotQueueNotifyParams_t params; /**< @brief Queue notification configuration. See #AwsIotQueueNotifyParams_t. */ +} AwsIotQueue_t; + +/** + * @ingroup queue_datatypes_queuelist + * @brief List structure. + * + * @attention Although the members of #AwsIotList_t are visible to applications, only + * #AwsIotList_t.mutex and should be accessed directly. This mutex should be locked + * to provide thread safety for all list functions except @ref list_function_removeallmatches. + */ +typedef struct AwsIotList +{ + AwsIotLink_t * pHead; /**< @brief Pointer to the list's head. */ + AwsIotMutex_t mutex; /**< @brief Protects this list from concurrent use. */ +} AwsIotList_t; + +/** + * @functionspage{queue,queue library} + * + * Queue functions: + * - @functionname{queue_function_create} + * - @functionname{queue_function_destroy} + * - @functionname{queue_function_inserthead} + * - @functionname{queue_function_removetail} + * - @functionname{queue_function_removefirstmatch} + * - @functionname{queue_function_removeallmatches} + * + * List functions: + * - @functionname{list_function_create} + * - @functionname{list_function_destroy} + * - @functionname{list_function_inserthead} + * - @functionname{list_function_insertsorted} + * - @functionname{list_function_findfirstmatch} + * - @functionname{list_function_remove} + * - @functionname{list_function_removeallmatches} + */ + +/** + * @functionpage{AwsIotQueue_Create,queue,create} + * @functionpage{AwsIotQueue_Destroy,queue,destroy} + * @functionpage{AwsIotQueue_InsertHead,queue,inserthead} + * @functionpage{AwsIotQueue_RemoveTail,queue,removetail} + * @functionpage{AwsIotQueue_RemoveFirstMatch,queue,removefirstmatch} + * @functionpage{AwsIotQueue_RemoveAllMatches,queue,removeallmatches} + * @functionpage{AwsIotList_Create,list,create} + * @functionpage{AwsIotList_Destroy,list,destroy} + * @functionpage{AwsIotList_InsertHead,list,inserthead} + * @functionpage{AwsIotList_InsertSorted,list,insertsorted} + * @functionpage{AwsIotList_FindFirstMatch,list,findfirstmatch} + * @functionpage{AwsIotList_Remove,list,remove} + * @functionpage{AwsIotList_RemoveAllMatches,list,removeallmatches} + */ + +/** + * @brief Create a queue. + * + * This function initializes a new queue. It must be called on an uninitialized + * #AwsIotQueue_t before calling any other queue function. This function must + * not be called on an already-initialized #AwsIotQueue_t. + * + * If [pQueueParams->notifyRoutine](@ref AwsIotQueueNotifyParams_t.notifyRoutine) is + * not `NULL`, then this function creates a notification queue. A notification + * queue creates a thread to notify the application that data is available. Up to + * `maxNotifyThreads` simultaneous threads may be created for a single queue. + * + * @param[in] pQueue Pointer to the memory that will hold the new queue. + * @param[in] pNotifyParams Parameters for a notification queue. Optional; pass + * `NULL` to create a queue that does not have notifications. + * @param[in] maxNotifyThreads The maximum number of simultaneous notification + * threads this queue may have. Ignored if [pQueueParams->notifyRoutine] + * (@ref AwsIotQueueNotifyParams_t.notifyRoutine) is `NULL`. This parameter must be + * greater than `0` when [pQueueParams->notifyRoutine] + * (@ref AwsIotQueueNotifyParams_t.notifyRoutine) is not `NULL`. + * + * @return `true` if a queue was successfully created; `false` otherwise. + */ +/* @[declare_queue_create] */ +bool AwsIotQueue_Create( AwsIotQueue_t * const pQueue, + const AwsIotQueueNotifyParams_t * const pNotifyParams, + uint32_t maxNotifyThreads ); +/* @[declare_queue_create] */ + +/** + * @brief Free resources used by a queue. + * + * This function frees resources used by a queue. It must be called on an + * [initialized](@ref queue_function_create) #AwsIotQueue_t. No other queue + * functions should be called on `pQueue` after calling this function (unless + * the queue is re-created). + * + * If `pQueue` is a notification queue, this function will block to wait for all + * active notification threads to exit before destroying the queue. This function + * will also prevent any new notification threads from being created. + * + * @param[in] pQueue The queue to destroy. + */ +/* @[declare_queue_destroy] */ +void AwsIotQueue_Destroy( AwsIotQueue_t * const pQueue ); +/* @[declare_queue_destroy] */ + +/** + * @brief Insert an element at the head of the queue. + * + * This function places a new element in `pQueue`. New elements are always placed + * at the head of `pQueue` (FIFO). Data is inserted by setting the [next] + * (@ref AwsIotLink_t.pNext) and [previous](@ref AwsIotLink_t.pPrevious) pointers + * of a link member; no data is copied. + * + * If `pQueue` is a notification queue, a new notification thread will be created + * (assuming the system has the necessary resources). + * + * @param[in] pQueue The queue that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + * + * @return `true` if the new element was successfully inserted and any requested + * notification was successful. If the maximum number of notification threads + * are active, this function will also return `true`. This function only returns + * `false` if a notification thread couldn't be created, so its return value can + * be ignored for queues with no notification routine. + */ +/* @[declare_queue_inserthead] */ +bool AwsIotQueue_InsertHead( AwsIotQueue_t * const pQueue, + AwsIotLink_t * const pLink ); +/* @[declare_queue_inserthead] */ + +/** + * @brief Removes the element at the tail of the queue. + * + * Removes the oldest element from `pQueue`. + * + * If this function is being called from a notification thread that will exit + * if no data is available in the queue, the parameter `notifyExitIfEmpty` should + * be `true`. Otherwise, `notifyExitIfEmpty` should be `false`. + * + * @param[in] pQueue The queue that holds the element to remove. + * @param[in] linkOffset Offset of link members in elements of `pQueue`. Use + * #AwsIotLink_Offset to calculate. + * @param[in] notifyExitIfEmpty If this parameter is `true`, then this function + * will increment the number of available notification threads and expect the + * calling thread to exit. This parameter should only be `true` if the calling + * thread is a notification thread. + * + * @return Pointer to the removed queue tail; `NULL` if the queue is empty. + */ +/* @[declare_queue_removetail] */ +void * AwsIotQueue_RemoveTail( AwsIotQueue_t * const pQueue, + size_t linkOffset, + bool notifyExitIfEmpty ); +/* @[declare_queue_removetail] */ + +/** + * @brief Remove a single matching element from a queue. + * + * Removes the oldest matching queue element. This function searches starting + * from the queue tail. It will return the first matching element, so it must + * be called multiple times for multiple matching elements. + * + * @param[in] pQueue The queue to search. + * @param[in] linkOffset Offset of link members in elements of `pQueue`. Use + * #AwsIotLink_Offset to calculate. + * @param[in] pArgument If `shouldRemove` is not `NULL`, this value will be passed as the + * first argument to `shouldRemove`. If `shouldRemove` is `NULL`, every element in the queue + * will be compared to this value to find a match. + * @param[in] shouldRemove Function to determine if an element matches. Pass `NULL` to + * search using the address `pArgument`, i.e. `element == pArgument`. + * + * @return Pointer to the removed element; `NULL` if no match was found. + */ +/* @[declare_queue_removefirstmatch] */ +void * AwsIotQueue_RemoveFirstMatch( AwsIotQueue_t * const pQueue, + size_t linkOffset, + void * pArgument, + bool ( * shouldRemove )( void *, void * ) ); +/* @[declare_queue_removefirstmatch] */ + +/** + * @brief Remove all matching elements from a queue. + * + * This function searches the entire queue and removes all matching elements. + * If it's not `NULL`, the `freeElement` function is called on all matching + * elements. + * + * @param[in] pQueue The queue to search. + * @param[in] linkOffset Offset of link members in elements of `pQueue`. Use + * #AwsIotLink_Offset to calculate. + * @param[in] pArgument The first argument passed to `shouldRemove`. + * @param[in] shouldRemove Function used to determine if an element matches. + * Pass `NULL` to remove all elements from the queue. + * @param[in] freeElement Function called on each matching element. Pass + * `NULL` to ignore. + */ +/* @[declare_queue_removeallmatches] */ +void AwsIotQueue_RemoveAllMatches( AwsIotQueue_t * const pQueue, + size_t linkOffset, + void * pArgument, + bool ( * shouldRemove )( void *, void * ), + void ( * freeElement )( void * ) ); +/* @[declare_queue_removeallmatches] */ + +/** + * @brief Create a list. + * + * This function initializes a new list. It must be called on an uninitialized + * #AwsIotList_t before calling any other list function. This function must not + * be called on an already-initialized #AwsIotList_t. + * + * @param[in] pList Pointer to the memory that will hold the new list. + * + * @return `true` if a list was successfully created; `false` otherwise. + */ +/* @[declare_list_create] */ +bool AwsIotList_Create( AwsIotList_t * const pList ); +/* @[declare_list_create] */ + +/** + * @brief Free resources used by a list. + * + * This function frees resources used by a list. It must be called on an [initialized] + * (@ref list_function_create) #AwsIotList_t. No other list functions should be + * called on `pList` after calling this function (unless the list is re-created). + * + * @param[in] pList The list to destroy. + */ +/* @[declare_list_destroy] */ +void AwsIotList_Destroy( AwsIotList_t * const pList ); +/* @[declare_list_destroy] */ + +/** + * @brief Insert an element at the head of the list. + * + * This function places a new element at the head of `pList`. Data is inserted + * by setting the [next](@ref AwsIotLink_t.pNext) and [previous] + * (@ref AwsIotLink_t.pPrevious) pointers of a link member; no data is copied. + * + * @param[in] pList The list that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset + * to calculate. + * + * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) + * locked if thread safety is needed. + */ +/* @[declare_list_inserthead] */ +void AwsIotList_InsertHead( AwsIotList_t * const pList, + AwsIotLink_t * const pLink, + size_t linkOffset ); +/* @[declare_list_inserthead] */ + +/** + * @brief Insert an element in a sorted list. + * + * Places an element into a list by sorting it into order. The function + * `compare` is used to determine where to place the new element. Data is + * inserted by setting the [next](@ref AwsIotLink_t.pNext) and [previous] + * (@ref AwsIotLink_t.pPrevious) pointers of a link member; no data is copied. + * + * @param[in] pList The list that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset + * to calculate. + * @param[in] compare Determines the order of the list. Returns a negative + * value if its first argument is less than its second argument; returns + * zero if its first argument is equal to its second argument; returns a + * positive value if its first argument is greater than its second argument. + * + * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) + * locked if thread safety is needed. + */ +/* @[declare_list_insertsorted] */ +void AwsIotList_InsertSorted( AwsIotList_t * const pList, + AwsIotLink_t * const pLink, + size_t linkOffset, + int ( * compare )( void *, void * ) ); +/* @[declare_list_insertsorted] */ + +/** + * @brief Search a list for the first matching element after the start point. + * + * This function searches from `pStartPoint` to the list tail for the first + * matching element. If a match is found, the matching element is not + * removed from the list. + * + * @param[in] pStartPoint Start point of the search. Pass [pList->pHead] + * (@ref AwsIotList_t.pHead) to search an entire list. + * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset + * to calculate. + * @param[in] pArgument If `compare` is not `NULL`, this value will be passed + * as the first argument to `compare`. If `compare` is `NULL`, every + * element in the list will be compared to this value to find a match. + * @param[in] compare Function to determine if an element matches. Pass `NULL` to + * search using the address `pArgument`, i.e. `element == pArgument`. + * + * @return Pointer to a matching element; `NULL` if no match was found. + * + * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) + * locked if thread safety is needed. + */ +/* @[declare_list_findfirstmatch] */ +void * AwsIotList_FindFirstMatch( AwsIotLink_t * const pStartPoint, + size_t linkOffset, + void * pArgument, + bool ( * compare )( void *, void * ) ); +/* @[declare_list_findfirstmatch] */ + +/** + * @brief Remove a single element from a list. + * + * Remove an element from a list. The given element must be in `pList`; use + * @ref list_function_findfirstmatch to determine if an element is in `pList`. + * + * @param[in] pList The list that holds the container of `pLink`. + * @param[in] pLink The link member of the element to remove. + * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset + * to calculate. + * + * @warning Do not pass a `pLink` that is not in `pList`. + * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) + * locked if thread safety is needed. + */ +/* @[declare_list_remove] */ +void AwsIotList_Remove( AwsIotList_t * const pList, + AwsIotLink_t * const pLink, + size_t linkOffset ); +/* @[declare_list_remove] */ + +/** + * @brief Remove all matching elements from a list. + * + * This function searches an entire list and removes all matching elements. + * If it's not `NULL`, the `freeElement` function is called on all the matching + * elements. + * + * @param[in] pList The list to search. + * @param[in] linkOffset Offset of link members in elements of `pList`. Use + * #AwsIotLink_Offset to calculate. + * @param[in] pArgument The first argument passed to `shouldRemove`. + * @param[in] shouldRemove Function used to determine if an element matches. + * Pass `NULL` to remove all elements from the list. + * @param[in] freeElement Function called on each matching element. Pass + * `NULL` to ignore. + * + * @warning Unlike the other #AwsIotList_t functions, this function provides + * thread safety by locking [pList->mutex](@ref AwsIotList_t.mutex). Therefore, + * locking [pList->mutex](@ref AwsIotList_t.mutex) before calling this function + * will cause a deadlock. + */ +/* @[declare_list_removeallmatches] */ +void AwsIotList_RemoveAllMatches( AwsIotList_t * const pList, + size_t linkOffset, + void * pArgument, + bool ( * shouldRemove )( void *, void * ), + void ( * freeElement )( void * ) ); +/* @[declare_list_removeallmatches] */ + +#endif /* ifndef _AWS_IOT_QUEUE_H_ */ diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h new file mode 100644 index 0000000000..c33abbe53f --- /dev/null +++ b/lib/include/aws_iot_shadow.h @@ -0,0 +1,766 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow.h + * @brief User-facing functions and structs of the Shadow library. + */ + +#ifndef _AWS_IOT_SHADOW_H_ +#define _AWS_IOT_SHADOW_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/*--------------------------- Shadow handle types ---------------------------*/ + +/** + * @handles{shadow,Shadow library} + */ + +/** + * @ingroup shadow_datatypes_handles + * @brief Opaque handle that references an in-progress Shadow operation. + * + * Set as an output parameter of @ref shadow_function_delete, @ref shadow_function_get, + * and @ref shadow_function_update. These functions send a message to the Shadow + * service requesting a Shadow operation; the result of this operation is unknown + * until the Shadow service sends a response. Therefore, this handle serves as a + * reference to Shadow operations awaiting a response from the Shadow service. + * + * This reference will be valid from the successful return of @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update. The reference becomes + * invalid once the [completion callback](@ref AwsIotShadowCallbackInfo_t) is invoked, + * or @ref shadow_function_wait returns. + * + * @initializer{AwsIotShadowReference_t,AWS_IOT_SHADOW_REFERENCE_INITIALIZER} + * + * @see @ref shadow_function_wait and #AWS_IOT_SHADOW_FLAG_WAITABLE for waiting on + * a reference. #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an + * asynchronous notification of completion. + */ +typedef void * AwsIotShadowReference_t; + +/*------------------------- Shadow enumerated types -------------------------*/ + +/** + * @enums{shadow,Shadow library} + */ + +/** + * @ingroup shadow_datatypes_enums + * @brief Return codes of [Shadow functions](@ref shadow_functions). + * + * The function @ref shadow_function_strerror can be used to get a return code's + * description. + * + * The values between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) and 500 + * (#AWS_IOT_SHADOW_SERVER_ERROR) may be returned by the Shadow service when it + * rejects a Shadow operation. See [this page] + * (https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-error-messages.html) + * for more information. + */ +typedef enum AwsIotShadowError +{ + /** + * @brief Shadow operation completed successfully. + * + * Functions that may return this value: + * - @ref shadow_function_init + * - @ref shadow_function_wait + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + * - @ref shadow_function_removepersistentsubscriptions + * + * Will also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + * when successful. + */ + AWS_IOT_SHADOW_SUCCESS, + + /** + * @brief Shadow operation queued, awaiting result. + * + * Functions that may return this value: + * - @ref shadow_function_delete + * - @ref shadow_function_get + * - @ref shadow_function_update + */ + AWS_IOT_SHADOW_STATUS_PENDING, + + /** + * @brief Initialization failed. + * + * Functions that may return this value: + * - @ref shadow_function_init + */ + AWS_IOT_SHADOW_INIT_FAILED, + + /** + * @brief At least one parameter is invalid. + * + * Functions that may return this value: + * - @ref shadow_function_delete and @ref shadow_function_timeddelete + * - @ref shadow_function_get and @ref shadow_function_timedget + * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + */ + AWS_IOT_SHADOW_BAD_PARAMETER, + + /** + * @brief Shadow operation failed because of memory allocation failure. + * + * Functions that may return this value: + * - @ref shadow_function_delete and @ref shadow_function_timeddelete + * - @ref shadow_function_get and @ref shadow_function_timedget + * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + */ + AWS_IOT_SHADOW_NO_MEMORY, + + /** + * @brief Shadow operation failed because of failure in MQTT library. + * + * Check the Shadow library logs for the error code returned by the MQTT + * library. + * + * Functions that may return this value: + * - @ref shadow_function_delete and @ref shadow_function_timeddelete + * - @ref shadow_function_get and @ref shadow_function_timedget + * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + * - @ref shadow_function_removepersistentsubscriptions + */ + AWS_IOT_SHADOW_MQTT_ERROR, + + /** + * @brief Reponse received from Shadow service not understood. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_BAD_RESPONSE, + + /** + * @brief A blocking Shadow operation timed out. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + */ + AWS_IOT_SHADOW_TIMEOUT, + + /** + * @brief Shadow operation rejected: Bad request. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_BAD_REQUEST = 400, + + /** + * @brief Shadow operation rejected: Unauthorized. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_UNAUTHORIZED = 401, + + /** + * @brief Shadow operation rejected: Forbidden. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_FORBIDDEN = 403, + + /** + * @brief Shadow operation rejected: Thing not found. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_NOT_FOUND = 404, + + /** + * @brief Shadow operation rejected: Version conflict. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_CONFLICT = 409, + + /** + * @brief Shadow operation rejected: The payload exceeds the maximum size + * allowed. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_TOO_LARGE = 413, + + /** + * @brief Shadow operation rejected: Unsupported document encoding; supported + * encoding is UTF-8. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_UNSUPPORTED = 415, + + /** + * @brief Shadow operation rejected: The Device Shadow service will generate + * this error message when there are more than 10 in-flight requests. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_TOO_MANY_REQUESTS = 429, + + /** + * @brief Shadow operation rejected: Internal service failure. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_SERVER_ERROR = 500, +} AwsIotShadowError_t; + +/** + * @ingroup shadow_datatypes_enums + * @brief Types of Shadow library callbacks. + * + * One of these values will be placed in #AwsIotShadowCallbackParam_t.callbackType + * to identify the reason for invoking a callback function. + */ +typedef enum AwsIotShadowCallbackType +{ + AWS_IOT_SHADOW_DELETE_COMPLETE, /**< Callback invoked because a [Shadow delete](@ref shadow_function_delete) completed. */ + AWS_IOT_SHADOW_GET_COMPLETE, /**< Callback invoked because a [Shadow get](@ref shadow_function_get) completed. */ + AWS_IOT_SHADOW_UPDATE_COMPLETE, /**< Callback invoked because a [Shadow update](@ref shadow_function_update) completed. */ + AWS_IOT_SHADOW_DELTA_CALLBACK, /**< Callback invoked for an incoming message on a [Shadow delta](@ref shadow_function_setdeltacallback) topic. */ + AWS_IOT_SHADOW_UPDATED_CALLBACK /**< Callback invoked for an incoming message on a [Shadow updated](@ref shadow_function_setupdatedcallback) topic. */ +} AwsIotShadowCallbackType_t; + +/*------------------------- Shadow parameter structs ------------------------*/ + +/** + * @paramstructs{shadow,Shadow} + */ + +/** + * @ingroup shadow_datatypes_paramstructs + * @brief Parameter to a Shadow callback function. + * + * @paramfor Shadow callback functions + * + * The Shadow library passes this struct to a callback function whenever a + * Shadow operation completes or a message is received on a Shadow delta or + * updated topic. + * + * The valid members of this struct are different based on + * #AwsIotShadowCallbackParam_t.callbackType. If the callback type is + * #AWS_IOT_SHADOW_DELETE_COMPLETE, #AWS_IOT_SHADOW_GET_COMPLETE, or + * #AWS_IOT_SHADOW_UPDATE_COMPLETE, then #AwsIotShadowCallbackParam_t.operation + * is valid. Otherwise, if the callback type is #AWS_IOT_SHADOW_DELTA_CALLBACK + * or #AWS_IOT_SHADOW_UPDATED_CALLBACK, then #AwsIotShadowCallbackParam_t.callback + * is valid. + * + * @attention Any pointers in this callback parameter may be freed as soon as the + * [callback function](@ref AwsIotShadowCallbackInfo_t.function) returns. Therefore, + * data must be copied if it is needed after the callback function returns. + * @attention The Shadow library may set strings that are not NULL-terminated. + * + * @see #AwsIotShadowCallbackInfo_t for the signature of a callback function. + */ +typedef struct AwsIotShadowCallbackParam +{ + AwsIotShadowCallbackType_t callbackType; /**< @brief Reason for invoking the Shadow callback function. */ + + const char * pThingName; /**< @brief The Thing Name associated with this Shadow callback. */ + size_t thingNameLength; /**< @brief Length of #AwsIotShadowCallbackParam_t.pThingName. */ + + union + { + /* Valid for completed Shadow operations. */ + struct + { + /* Valid for a completed Shadow GET operation. */ + struct + { + const char * pDocument; /**< @brief Retrieved Shadow document. */ + size_t documentLength; /**< @brief Length of retrieved Shadow document. */ + } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_get). */ + + AwsIotShadowError_t result; /**< @brief Result of Shadow operation, e.g. succeeded or failed. */ + AwsIotShadowReference_t reference; /**< @brief Reference to the Shadow operation that completed. */ + } operation; /**< @brief Information on a completed Shadow operation. */ + + /* Valid for a message on a Shadow delta or updated topic. */ + struct + { + const char * pDocument; /**< @brief Shadow delta or updated document. */ + size_t documentLength; /**< @brief Length of Shadow delta or updated document. */ + } callback; /**< @brief Shadow document from an incoming delta or updated topic. */ + }; +} AwsIotShadowCallbackParam_t; + +/** + * @ingroup shadow_datatypes_paramstructs + * @brief Information on a user-provided Shadow callback function. + * + * @paramfor @ref shadow_function_delete, @ref shadow_function_get, @ref + * shadow_function_update, @ref shadow_function_setdeltacallback, @ref + * shadow_function_setupdatedcallback + * + * Provides a function to be invoked when a Shadow operation completes or when a + * Shadow document is received on a callback topic (delta or updated). + * + * @initializer{AwsIotShadowCallbackInfo_t,AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER} + */ +typedef struct AwsIotShadowCallbackInfo +{ + void * param1; /**< @brief The first parameter to pass to the callback function. */ + + /** + * @brief User-provided callback function signature. + * + * @param[in] void* #AwsIotShadowCallbackInfo_t.param1 + * @param[in] AwsIotShadowCallbackParam_t* Details on the outcome of the Shadow + * operation or an incoming Shadow document. + * + * @see #AwsIotShadowCallbackParam_t for more information on the second parameter. + */ + void ( * function )( void *, + AwsIotShadowCallbackParam_t * const ); +} AwsIotShadowCallbackInfo_t; + +/** + * @ingroup shadow_datatypes_paramstructs + * @brief Information on a Shadow document for @ref shadow_function_get or @ref + * shadow_function_update. + * + * @paramfor @ref shadow_function_get, @ref shadow_function_update + * + * The valid members of this struct are different based on whether this struct + * is passed to @ref shadow_function_get or @ref shadow_function_update. When + * passed to @ref shadow_function_get, the `get` member is valid. When passed to + * @ref shadow_function_update, the `update` member is valid. All other members + * must always be set. + * + * @initializer{AwsIotShadowDocumentInfo_t,AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER} + */ +typedef struct AwsIotShadowDocumentInfo +{ + const char * pThingName; /**< @brief The Thing Name associated with this Shadow document. */ + size_t thingNameLength; /**< @brief Length of #AwsIotShadowDocumentInfo_t.pThingName. */ + + int QoS; /**< @brief QoS when sending a Shadow get or update message. See #AwsIotMqttPublishInfo_t.QoS. */ + int retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #AwsIotMqttPublishInfo_t.retryLimit. */ + uint64_t retryMs; /**< @brief First retry time for a Shadow get or update message. See #AwsIotMqttPublishInfo_t.retryMs. */ + + union + { + /* Valid for Shadow get. */ + struct + { + /** + * @brief Function to allocate memory for an incoming Shadow document. + * + * This only needs to be set if #AWS_IOT_SHADOW_FLAG_WAITABLE is passed to + * @ref shadow_function_get. + */ + void *( *mallocDocument )( size_t ); + } get; /**< @brief Valid members for @ref shadow_function_get. */ + + /* Valid for Shadow update. */ + struct + { + const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ + size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ + } update; /**< @brief Valid members for @ref shadow_function_update. */ + }; +} AwsIotShadowDocumentInfo_t; + +/*------------------------ Shadow defined constants -------------------------*/ + +/** + * @constantspage{shadow,Shadow library} + * + * @section shadow_constants_initializers Shadow Initializers + * @brief Provides default values for the data types of the Shadow library. + * + * @snippet this define_shadow_initializers + * + * All user-facing data types of the Shadow library should be initialized + * using one of the following. + * + * @warning Failing to initialize a Shadow data type with the appropriate + * initializer may result in undefined behavior! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + * AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * @endcode + * + * @section shadow_constants_flags Shadow Function Flags + * @brief Flags that modify the behavior of Shadow library functions. + * + * Flags should be bitwise-ORed with each other to change the behavior of + * Shadow library functions. + * + * The following flags are valid for the Shadow operation functions: + * @ref shadow_function_delete, @ref shadow_function_get, @ref shadow_function_update, + * and their Timed variants. + * - #AWS_IOT_SHADOW_FLAG_WAITABLE
+ * @copybrief AWS_IOT_SHADOW_FLAG_WAITABLE + * - #AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS + * + * The following flags are valid for @ref shadow_function_removepersistentsubscriptions. + * These flags are not valid for the Shadow operation functions. + * - #AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS + * - #AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS + * - #AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS + * + * @note The values of the flags may change at any time in future versions, but + * their names will remain the same. Additionally, flags which may be used at + * the same time will be bitwise-exclusive of each other. + */ + +/* @[define_shadow_initializers] */ +#define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ +#define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ +#define AWS_IOT_SHADOW_REFERENCE_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowReference_t. */ +/* @[define_shadow_initializers] */ + +/** + * @brief Allows the use of @ref shadow_function_wait for blocking until completion. + * + * This flag is only valid if passed to the functions @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update. + * + * An #AwsIotShadowReference_t MUST be provided if this flag is set. + * Additionally, an #AwsIotShadowCallbackInfo_t MUST NOT be provided. + * + * @note If this flag is set, @ref shadow_function_wait MUST be called to + * clean up resources. + */ +#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001 ) + +/** + * @brief Maintain the subscriptions for the Shadow operation topics, even after + * this function returns. + */ +#define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) + +/** + * @brief Remove the persistent subscriptions from a Shadow delete operation. + */ +#define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001 ) + +/** + * @brief Remove the persistent subscriptions from a Shadow get operation. + */ +#define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002 ) + +/** + * @brief Remove the persistent subscriptions from a Shadow update operation. + */ +#define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004 ) + +/*------------------------ Shadow library functions -------------------------*/ + +/** + * @functionspage{shadow,Shadow library} + * - @functionname{shadow_function_init} + * - @functionname{shadow_function_cleanup} + * - @functionname{shadow_function_delete} + * - @functionname{shadow_function_timeddelete} + * - @functionname{shadow_function_get} + * - @functionname{shadow_function_timedget} + * - @functionname{shadow_function_update} + * - @functionname{shadow_function_timedupdate} + * - @functionname{shadow_function_wait} + * - @functionname{shadow_function_setdeltacallback} + * - @functionname{shadow_function_setupdatedcallback} + * - @functionname{shadow_function_removepersistentsubscriptions} + * - @functionname{shadow_function_strerror} + */ + +/** + * @functionpage{AwsIotShadow_Init,shadow,init} + * @functionpage{AwsIotShadow_Cleanup,shadow,cleanup} + * @functionpage{AwsIotShadow_Delete,shadow,delete} + * @functionpage{AwsIotShadow_TimedDelete,shadow,timeddelete} + * @functionpage{AwsIotShadow_Get,shadow,get} + * @functionpage{AwsIotShadow_TimedGet,shadow,timedget} + * @functionpage{AwsIotShadow_Update,shadow,update} + * @functionpage{AwsIotShadow_TimedUpdate,shadow,timedupdate} + * @functionpage{AwsIotShadow_Wait,shadow,wait} + * @functionpage{AwsIotShadow_SetDeltaCallback,shadow,setdeltacallback} + * @functionpage{AwsIotShadow_SetUpdatedCallback,shadow,setupdatedcallback} + * @functionpage{AwsIotShadow_RemovePersistentSubscriptions,shadow,removepersistentsubscriptions} + * @functionpage{AwsIotShadow_strerror,shadow,strerror} + */ + +/** + * @brief One-time initialization function for the Shadow library. + * + * This function performs internal setup of the Shadow library. It must be + * called once (and only once) before calling any other Shadow function. + * Calling this function more than once without first calling @ref + * shadow_function_cleanup may result in a crash. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_INIT_FAILED + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref shadow_function_cleanup + */ +/* @[declare_shadow_init] */ +AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeout ); +/* @[declare_shadow_init] */ + +/** + * @brief One-time deinitialization function for the Shadow library. + * + * This function frees resources taken in @ref shadow_function_init. It should + * be called to clean up the Shadow library. After this function returns, @ref + * shadow_function_init must be called again before calling any other Shadow + * function. + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref shadow_function_init + */ +/* @[declare_shadow_cleanup] */ +void AwsIotShadow_Cleanup( void ); +/* @[declare_shadow_cleanup] */ + +/** + * @brief Delete a Thing Shadow and receive an asynchronous notification when + * the Delete completes. + */ +/* @[declare_shadow_delete] */ +AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + AwsIotShadowReference_t * const pDeleteRef ); +/* @[declare_shadow_delete] */ + +/** + * @brief Delete a Thing Shadow with a timeout. + */ +/* @[declare_shadow_timeddelete] */ +AwsIotShadowError_t AwsIotShadow_TimedDelete( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + uint64_t timeoutMs ); +/* @[declare_shadow_timeddelete] */ + +/** + * @brief Retrieve a Thing Shadow and receive an asynchronous notification when + * the Shadow document is received. + */ +/* @[declare_shadow_get] */ +AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pGetInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + AwsIotShadowReference_t * const pGetRef ); +/* @[declare_shadow_get] */ + +/** + * @brief Retrieve a Thing Shadow with a timeout. + */ +/* @[declare_shadow_timedget] */ +AwsIotShadowError_t AwsIotShadow_TimedGet( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pGetInfo, + uint32_t flags, + uint64_t timeoutMs, + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ); +/* @[declare_shadow_timedget] */ + +/** + * @brief Send a Thing Shadow update and receive an asynchronous notification when + * the Shadow Update completes. + */ +/* @[declare_shadow_update] */ +AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + AwsIotShadowReference_t * const pUpdateRef ); +/* @[declare_shadow_update] */ + +/** + * @brief Send a Thing Shadow update with a timeout. + */ +/* @[declare_shadow_timedupdate] */ +AwsIotShadowError_t AwsIotShadow_TimedUpdate( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + uint32_t flags, + uint64_t timeoutMs ); +/* @[declare_shadow_timedupdate] */ + +/** + * @brief Wait for a Shadow operation to complete. + */ +/* @[declare_shadow_wait] */ +AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, + uint64_t timeoutMs, + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ); +/* @[declare_shadow_wait] */ + +/** + * @brief Set a callback to be invoked when the Thing Shadow "desired" and "reported" + * states differ. + */ +/* @[declare_shadow_setdeltacallback] */ +AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pDeltaCallback ); +/* @[declare_shadow_setdeltacallback] */ + +/** + * @brief Set a callback to be invoked when a Thing Shadow changes. + */ +/* @[declare_shadow_setupdatedcallback] */ +AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pUpdatedCallback ); +/* @[declare_shadow_setupdatedcallback] */ + +/** + * @brief Remove persistent Thing Shadow operation topic subscriptions. + * + * Not safe to call with any in-progress operation. Does not affect callbacks. + */ +/* @[declare_shadow_removepersistentsubscriptions] */ +AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags ); +/* @[declare_shadow_removepersistentsubscriptions] */ + +/*------------------------- Shadow helper functions -------------------------*/ + +/** + * @brief Returns a string that describes an #AwsIotShadowError_t. + */ +/* @[declare_shadow_strerror] */ +const char * AwsIotShadow_strerror( AwsIotShadowError_t status ); +/* @[declare_shadow_strerror] */ + +#endif /* ifndef _AWS_IOT_SHADOW_H_ */ diff --git a/lib/include/platform/aws_iot_clock.h b/lib/include/platform/aws_iot_clock.h new file mode 100644 index 0000000000..1b2c1b6a13 --- /dev/null +++ b/lib/include/platform/aws_iot_clock.h @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_clock.h + * @brief Time-related functions used by the AWS IoT libraries. + */ + +#ifndef _AWS_IOT_CLOCK_H_ +#define _AWS_IOT_CLOCK_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Platform threads include. */ +#include "aws_iot_threads.h" + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent timers. The constant @ref AWS_IOT_TIMER_TYPE + * can be used to change the timer type. + * + * + * This constant will be automatically configured during build and generally + * does not need to be defined. + * + * + * Example
+ * To change the type of #AwsIotTimer_t to `long`: + * @code{c} + * #define AWS_IOT_TIMER_TYPE long + * #include "aws_iot_clock.h" + * @endcode + */ +typedef AWS_IOT_TIMER_TYPE AwsIotTimer_t; + +/** + * @functionspage{platform_clock,platform clock component,Clock} + * - @functionname{platform_clock_function_gettimestring} + * - @functionname{platform_clock_function_gettimems} + * - @functionname{platform_clock_function_timercreate} + * - @functionname{platform_clock_function_timerdestroy} + * - @functionname{platform_clock_function_timerarm} + */ + +/** + * @functionpage{AwsIotClock_GetTimestring,platform_clock,gettimestring} + * @functionpage{AwsIotClock_GetTimeMs,platform_clock,gettimems} + * @functionpage{AwsIotClock_TimerCreate,platform_clock,timercreate} + * @functionpage{AwsIotClock_TimerDestroy,platform_clock,timerdestroy} + * @functionpage{AwsIotClock_TimerArm,platform_clock,timerarm} + */ + +/** + * @brief Generates a human-readable timestring, such as "01 Jan 2018 12:00". + * + * This function uses the system clock to generate a human-readable timestring. + * This timestring is printed by the [logging functions](@ref logging_functions). + * + * @param[out] pBuffer A buffer to store the timestring in. + * @param[in] bufferSize The size of `pBuffer`. + * @param[out] pTimestringLength The actual length of the timestring stored in + * `pBuffer`. + * + * @return `true` if a timestring was successfully generated; `false` otherwise. + * + * @warning The implementation of this function must not call any [logging functions] + * (@ref logging_functions). + * + * Example + * @code{c} + * char timestring[ 32 ]; + * size_t timestringLength = 0; + * + * if( AwsIotClock_GetTimestring( timestring, 32, ×tringLength ) == true ) + * { + * printf( "Timestring: %.*s", timestringLength, timestring ); + * } + * @endcode + */ +/* @[declare_platform_clock_gettimestring] */ +bool AwsIotClock_GetTimestring( char * const pBuffer, + size_t bufferSize, + size_t * const pTimestringLength ); +/* @[declare_platform_clock_gettimestring] */ + +/** + * @brief Returns a nonzero, monotonically-increasing system time in milliseconds. + * + * This function reads a millisecond-resolution system clock. The clock should + * always be monotonically-increasing; therefore, real-time clocks that may be + * set by another process are not suitable for this function's implementation. + * + * @return The value of the system clock. This function is not expected to fail. + * + * Example + * @code{c} + * // Get current time. + * uint64_t currentTime = AwsIotClock_GetTimeMs(); + * @endcode + */ +/* @[declare_platform_clock_gettimems] */ +uint64_t AwsIotClock_GetTimeMs( void ); +/* @[declare_platform_clock_gettimems] */ + +/** + * @brief Create a new timer. + * + * This function creates a new, unarmed timer. It must be called on an uninitialized + * #AwsIotTimer_t. This function must not be called on an already-initialized + * #AwsIotTimer_t. + * + * @param[out] pNewTimer Set to a new timer handle on success. + * @param[in] expirationRoutine The function to run when this timer expires. This + * function should be called in its own detached thread. + * @param[in] pArgument The argument to pass to `expirationRoutine`. + * + * @return `true` if the timer is successfully created; `false` otherwise. + * + * @see @ref platform_clock_function_timerdestroy, @ref platform_clock_function_timerarm + */ +/* @[declare_platform_clock_timercreate] */ +bool AwsIotClock_TimerCreate( AwsIotTimer_t * const pNewTimer, + AwsIotThreadRoutine_t expirationRoutine, + void * pArgument ); +/* @[declare_platform_clock_timercreate] */ + +/** + * @brief Free resources used by a timer. + * + * This function frees resources used by a timer. It must be called on an initialized + * #AwsIotTimer_t. No other timer functions should be called on `pTimer` after calling + * this function (unless the timer is re-created). + * + * This function will stop the `pTimer` if it is armed. + * + * @param[in] pTimer The timer to destroy. + * + * @see @ref platform_clock_function_timercreate, @ref platform_clock_function_timerarm + */ +/* @[declare_platform_clock_timerdestroy] */ +void AwsIotClock_TimerDestroy( AwsIotTimer_t * const pTimer ); +/* @[declare_platform_clock_timerdestroy] */ + +/** + * @brief Arm a timer to expire at the given relative timeout. + * + * This function arms a timer to run its expiration routine at the given time. + * + * If `periodMs` is nonzero, the timer should expire periodically at intervals + * such as: + * - `relativeTimeoutMs` + * - `relativeTimeoutMs + periodMs` + * - `relativeTimeoutMs + 2 * periodMs` + * - Etc. (subject to some jitter). + * + * Setting `periodMs` to `0` arms a one-shot, non-periodic timer. + * + * @param[in] pTimer The timer to arm. + * @param[in] relativeTimeoutMs When the timer should expire, relative to the time + * this function is called. + * @param[in] periodMs How often the timer should expire again after `relativeTimerMs`. + * + * @return `true` if the timer was successfully armed; `false` otherwise. + * + * @see @ref platform_clock_function_timercreate, @ref platform_clock_function_timerdestroy + * + * Example + * @code{c} + * + * void timerExpirationRoutine( void * pArgument ); + * + * void timerExample( void ) + * { + * AwsIotTimer_t timer; + * + * if( AwsIotClock_TimerCreate( &timer, timerExpirationRoutine, NULL ) == true ) + * { + * // Set the timer to periodically expire every 10 seconds. + * if( AwsIotClock_TimerArm( &timer, 10000, 10000 ) == true ) + * { + * // Wait for timer to expire. + * } + * + * AwsIotClock_TimerDestroy( &timer ); + * } + * } + * @endcode + */ +/* @[declare_platform_clock_timerarm] */ +bool AwsIotClock_TimerArm( AwsIotTimer_t * const pTimer, + uint64_t relativeTimeoutMs, + uint64_t periodMs ); +/* @[declare_platform_clock_timerarm] */ + +#endif /* ifndef _AWS_IOT_CLOCK_H_ */ diff --git a/lib/include/platform/aws_iot_network.h b/lib/include/platform/aws_iot_network.h new file mode 100644 index 0000000000..d40d4d4451 --- /dev/null +++ b/lib/include/platform/aws_iot_network.h @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_network.h + * @brief Network-related functions used by the AWS IoT libraries. + */ + +#ifndef _AWS_IOT_NETWORK_H_ +#define _AWS_IOT_NETWORK_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/** + * @ingroup platform_datatypes_handles + * @brief Opaque handle of a network connection. + * + * This type identifies a network connection, which is valid after a successful call + * to @ref platform_network_function_createconnection. A variable of this type is + * passed as the first argument to [network layer functions](@ref platform_network_functions) + * to identify which connection that function acts on. + * + * A call to @ref platform_network_function_closeconnection shuts down the network + * connection, but keeps its resources allocated. A call to + * @ref platform_network_function_destroyconnection makes a connection handle + * invalid. Once @ref platform_network_function_destroyconnection is called, the + * connection handle should no longer be used. + * + * @initializer{AwsIotNetworkConnection_t,AWS_IOT_NETWORK_CONNECTION_INITIALIZER} + */ +typedef void * AwsIotNetworkConnection_t; + +/** + * @ingroup platform_datatypes_handles + * @brief Function pointer for the [MQTT receive callback] + * (@ref mqtt_function_receivecallback). + * + * This function pointer is used to identify the function @ref + * mqtt_function_receivecallback. + */ +typedef int32_t ( * AwsIotMqttReceiveCallback_t )( AwsIotMqttConnection_t *, + const void *, + size_t, + size_t, + void ( * )( void * ) ); + +/** + * @ingroup platform_datatypes_enums + * @brief Return codes for [network functions](@ref platform_network_functions). + */ +typedef enum AwsIotNetworkError +{ + AWS_IOT_NETWORK_SUCCESS = 0, /**< Function successfully completed. */ + AWS_IOT_NETWORK_INIT_FAILED, /**< Initialization failed. Only returned by @ref platform_network_function_init. */ + AWS_IOT_NETWORK_BAD_PARAMETER, /**< At least one parameter was invalid. */ + AWS_IOT_NETWORK_NO_MEMORY, /**< Memory allocation failed. */ + AWS_IOT_NETWORK_DNS_LOOKUP_ERROR, /**< Host name could not be translated.*/ + AWS_IOT_NETWORK_CREDENTIAL_READ_ERROR, /**< Credentials could not be read. */ + AWS_IOT_NETWORK_TLS_LIBRARY_ERROR, /**< An error occurred in the system's TLS library. */ + AWS_IOT_NETWORK_SYSTEM_ERROR /**< An error occurred when calling a system function. */ +} AwsIotNetworkError_t; + +/** + * @ingroup platform_datatypes_paramstructs + * @brief Parameters to @ref platform_network_function_createconnection for setting + * up TLS. + * + * This structure provides @ref platform_network_function_createconnection with the + * necessary information to set up TLS on a TCP connection. + * + * @paramfor @ref platform_network_function_createconnection + * + * @initializer{AwsIotNetworkTlsInfo_t,AWS_IOT_NETWORK_TLS_INFO_INITIALIZER} + */ +typedef struct AwsIotNetworkTlsInfo +{ + /** + * @brief Set this to a non-NULL value to use ALPN. + * + * This string must be NULL-terminated. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char * pAlpnProtos; + + /** + * @brief Set this to a non-zero value to use TLS max fragment length + * negotiation (TLS MFLN). + * + * @note The system's TLS library may have a minimum value for this parameter. + * @ref platform_network_function_createconnection may return an error if + * this parameter is too small. + */ + size_t maxFragmentLength; + + /** + * @brief Disable server name indication (SNI) for a TLS session. + */ + bool disableSni; + + /* Credentials. The exact meaning of these parameters depends on the system. + * For example, on Linux these may be used as the paths to credentials, while + * on FreeRTOS they may be the PEM-encoded credentials themselves. Note that + * some systems may ignore the "length" parameters. */ + const char * pRootCa; /**< @brief Trusted server root certificate. */ + size_t rootCaLength; /**< @brief Length of #AwsIotNetworkTlsInfo_t.pRootCa. */ + const char * pClientCert; /**< @brief Client certificate. */ + size_t clientCertLength; /**< @brief Length of #AwsIotNetworkTlsInfo_t.pClientCert. */ + const char * pPrivateKey; /**< @brief Client certificate private key. */ + size_t privateKeyLength; /**< @brief Length of #AwsIotNetworkTlsInfo_t.pPrivateKey. */ +} AwsIotNetworkTlsInfo_t; + +/** + * @brief Provides a default value for an #AwsIotNetworkConnection_t. + * + * All instances of #AwsIotNetworkConnection_t should be initialized with this + * constant. + * + * @code{c} + * AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + * @endcode + * + * @warning Failing to initialize a network connection with this initializer + * may result in undefined behavior! + * @note This initializer may change at any time in future versions, but its + * names will remain the same. + */ +#define AWS_IOT_NETWORK_CONNECTION_INITIALIZER NULL + +/** + * @brief Provides default values for an #AwsIotNetworkTlsInfo_t. + * + * All instances of #AwsIotNetworkTlsInfo_t should be initialized with this + * constant. In most use cases, the default values should not be changed. + * + * @code{c} + * AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER; + * @endcode + * + * @warning Failing to initialize an #AwsIotNetworkTlsInfo_t with this initializer + * may result in undefined behavior! + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define AWS_IOT_NETWORK_TLS_INFO_INITIALIZER { .pAlpnProtos = "\x0ex-amzn-mqtt-ca" } + +/** + * @functionspage{platform_network,platform network component,Networking} + * - @functionname{platform_network_function_init} + * - @functionname{platform_network_function_cleanup} + * - @functionname{platform_network_function_createconnection} + * - @functionname{platform_network_function_closeconnection} + * - @functionname{platform_network_function_destroyconnection} + * - @functionname{platform_network_function_setmqttreceivecallback} + * - @functionname{platform_network_function_send} + */ + +/** + * @functionpage{AwsIotNetwork_Init,platform_network,init} + * @functionpage{AwsIotNetwork_Cleanup,platform_network,cleanup} + * @functionpage{AwsIotNetwork_CreateConnection,platform_network,createconnection} + * @functionpage{AwsIotNetwork_CloseConnection,platform_network,closeconnection} + * @functionpage{AwsIotNetwork_DestroyConnection,platform_network,destroyconnection} + * @functionpage{AwsIotNetwork_SetMqttReceiveCallback,platform_network,setmqttreceivecallback} + * @functionpage{AwsIotNetwork_Send,platform_network,send} + */ + +/** + * @brief One-time initialization function for the platform networking component. + * + * This function performs internal setup of the platform networking component. + * It must be called once (and only once) before calling any other platform + * networking function. Calling this function more than once without first calling + * @ref platform_network_function_cleanup may result in a crash. + * + * @return #AWS_IOT_NETWORK_SUCCESS or #AWS_IOT_NETWORK_INIT_FAILED. + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref platform_network_function_cleanup + * + * @code{c} + * // Before calling any other network function. + * if( AwsIotNetwork_Init() == AWS_IOT_NETWORK_SUCCESS ) + * { + * // Do something with the network library... + * + * // After the network is no longer needed. + * AwsIotNetwork_Cleanup(); + * } + * else + * { + * // Initialization failed. + * } + * @endcode + */ +/* @[declare_platform_network_init] */ +AwsIotNetworkError_t AwsIotNetwork_Init( void ); +/* @[declare_platform_network_init] */ + +/** + * @brief One-time deinitialization function for the platform networking component. + * + * This function frees resources taken in @ref platform_network_function_init. + * It should be called after [destroying all network connections] + * (@ref platform_network_function_destroyconnection) to clean up the platform + * networking component. After this function returns, @ref platform_network_function_init + * must be called again before calling any other platform networking function. + * + * @warning No thread-safety guarantees are provided for this function. Do not + * call this function if any network connections exist! + * + * @see @ref platform_network_function_init + */ +/* @[declare_platform_network_cleanup] */ +void AwsIotNetwork_Cleanup( void ); +/* @[declare_platform_network_cleanup] */ + +/** + * @brief Open a TCP connection to a remote server with an option for TLS. + * + * This function opens a TCP connection to a server on a given port. If security + * is needed, parameter `pTlsInfo` may be passed to set up TLS on the TCP connection. + * + * @param[out] pNetworkConnection The handle by which this new connection will + * be referenced. + * @param[in] pHostName Host name for connection. Must be null-terminated. + * @param[in] port Port (in host-order) for connection. + * @param[in] pTlsInfo TLS setup parameters. Optional; pass `NULL` for an unsecured + * connection. + * + * @return Any #AwsIotNetworkError_t except #AWS_IOT_NETWORK_INIT_FAILED or + * #AWS_IOT_NETWORK_SYSTEM_ERROR. + * + * @see @ref platform_network_function_closeconnection, + * @ref platform_network_function_destroyconnection + * + * @code{c} + * // Network connection handle and TLS setup parameters. + * AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + * AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER; + * + * // Example server. + * const char * const pHostName = "test.mosquitto.org"; + * uint16_t port = 8883; + * + * // Set the TLS setup credentials. The exact format of these credentials depends + * // on the system. + * tlsInfo.pRootCa = ROOT_CA; + * tlsInfo.rootCaLength = ROOT_CA_LENGTH; + * tlsInfo.pClientCert = CLIENT_CERT; + * tlsInfo.clientCertLength = CLIENT_CERT_LENGTH; + * tlsInfo.pPrivateKey = PRIVATE_KEY; + * tlsInfo.privateKeyLength = PRIVATE_KEY_LENGTH; + * + * // Returns AWS_IOT_NETWORK_SUCCESS and sets networkConnection when the + * // connection is established. + * AwsIotNetworkError_t result = AwsIotNetwork_TcpConnect( &networkConnection, + * pHostName, + * port, + * &tlsInfo ); + * + * // Do something with the connection... + * + * // Close the connection. + * AwsIotNetwork_CloseConnection( networkConnection ); + * + * // Destroy the network connection. This should only be called once the + * // connection is guaranteed to be unused. + * AwsIotNetwork_DestroyConnection( networkConnection ); + * @endcode + */ +/* @[declare_platform_network_createconnection] */ +AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * const pNetworkConnection, + const char * const pHostName, + uint16_t port, + const AwsIotNetworkTlsInfo_t * const pTlsInfo ); +/* @[declare_platform_network_createconnection] */ + +/** + * @brief Close a network connection. + * + * This function closes the TCP socket and cleans up TLS. However, the resources + * for the connection are not released by this function. This allows calls to + * @ref platform_network_function_send (or similar functions) to receive an error + * and handle a closed connection without the risk of crashing. Once it can be + * guaranteed that the network connection won't be used further, it can be destroyed + * with @ref platform_network_function_destroyconnection. + * + * Calling this function also removes any registered receive callback. + * + * @param[in] networkConnection The handle of the connection to close. + * + * @note It must be safe to call this function on an already-closed connection. + * + * @see @ref platform_network_function_createconnection, + * @ref platform_network_function_destroyconnection + */ +/* @[declare_platform_network_closeconnection] */ +void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection ); +/* @[declare_platform_network_closeconnection] */ + +/** + * @brief Free resources in use by a connection. + * + * This function releases the resources used by a closed connection. It should + * be called after @ref platform_network_function_closeconnection. + * + * @param[in] networkConnection The connection to destroy. + * + * @see @ref platform_network_function_createconnection, + * @ref platform_network_function_closeconnection + * + * @warning Using the network connection after calling this function will likely + * result in a crash! + */ +/* @[declare_platform_network_destroyconnection] */ +void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnection ); +/* @[declare_platform_network_destroyconnection] */ + +/** + * @brief Register the [MQTT callback function](@ref mqtt_function_receivecallback) + * to be called when MQTT data is read from the network. + * + * The [MQTT library](@ref mqtt) expects to be asynchronously notified of incoming + * network data. This function creates a thread that will notify the MQTT library + * of incoming data. + * + * @param[in] networkConnection The handle of the connection to receive from. + * @param[in] pMqttConnection Pointer to the MQTT connection to pass to the MQTT + * callback. + * @param[in] receiveCallback @ref mqtt_function_receivecallback + * + * @return #AWS_IOT_NETWORK_SUCCESS or #AWS_IOT_NETWORK_SYSTEM_ERROR. + * + * @see @ref mqtt_function_receivecallback + * + * @code{c} + * // MQTT network receive callback. + * int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, + * const void * pReceivedData, + * size_t offset, + * size_t dataLength, + * void ( * freeReceivedData )( void * ) ); + * + * void setCallback( AwsIotNetworkConnection_t networkConnection, AwsIotMqttConnection_t * pMqttConnection ) + * { + * // Returns AWS_IOT_NETWORK_SUCCESS when the callback is successfully set. + * AwsIotNetworkError_t result = AwsIotNetwork_SetMqttReceiveCallback( networkConnection, + * pMqttConnection, + * AwsIotMqtt_ReceiveCallback ); + * } + * @endcode + */ +/* @[declare_platform_network_setmqttreceivecallback] */ +AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnection_t networkConnection, + AwsIotMqttConnection_t * pMqttConnection, + AwsIotMqttReceiveCallback_t receiveCallback ); +/* @[declare_platform_network_setmqttreceivecallback] */ + +/** + * @brief Sends data over a connection. + * + * Attempts to transmit `messageLength` bytes of `pMessage` across + * `networkConnection`. Returns the number of bytes actually sent, `0` on + * failure. + * + * @param[in] networkConnection The handle of the connection used to send data. + * For compatibility with other network implementations, this is a void*. + * @param[in] pMessage The message to send. + * @param[in] messageLength The length of pMessage. + * + * @return The number of bytes successfully sent, `0` on failure. + */ +/* @[declare_platform_network_send] */ +size_t AwsIotNetwork_Send( void * networkConnection, + const void * const pMessage, + size_t messageLength ); +/* @[declare_platform_network_send] */ + +#endif /* ifndef _AWS_IOT_NETWORK_H_ */ diff --git a/lib/include/platform/aws_iot_static_memory.h b/lib/include/platform/aws_iot_static_memory.h new file mode 100644 index 0000000000..5e291621aa --- /dev/null +++ b/lib/include/platform/aws_iot_static_memory.h @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_static_memory.h + * @brief Functions for managing static buffers. Only used when + * @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* The functions in this file should only exist in static memory only mode, hence + * the check for AWS_IOT_STATIC_MEMORY_ONLY in the double inclusion guard. */ +#if !defined( _AWS_IOT_STATIC_MEMORY_H_ ) && ( AWS_IOT_STATIC_MEMORY_ONLY == 1 ) +#define _AWS_IOT_STATIC_MEMORY_H_ + +/* Standard includes. */ +#include +#include + +/** + * @functionspage{platform_static_memory,platform static memory component,Static Memory} + * - @functionname{platform_static_memory_function_messagebuffersize} + * - @functionname{platform_static_memory_function_mallocmessagebuffer} + * - @functionname{platform_static_memory_function_freemessagebuffer} + * - @functionname{platform_static_memory_function_mallocmqttconnection} + * - @functionname{platform_static_memory_function_freemqttconnection} + * - @functionname{platform_static_memory_function_mallocmqttoperation} + * - @functionname{platform_static_memory_function_freemqttoperation} + * - @functionname{platform_static_memory_function_mallocmqtttimerevent} + * - @functionname{platform_static_memory_function_freemqtttimerevent} + * - @functionname{platform_static_memory_function_mallocmqttsubscription} + * - @functionname{platform_static_memory_function_freemqttsubscription} + * - @functionname{platform_static_memory_function_mallocshadowoperation} + * - @functionname{platform_static_memory_function_freeshadowoperation} + * - @functionname{platform_static_memory_function_mallocshadowsubscription} + * - @functionname{platform_static_memory_function_freeshadowsubscription} + */ + +/*------------------------ Message buffer management ------------------------*/ + +/** + * @functionpage{AwsIot_MessageBufferSize,platform_static_memory,messagebuffersize} + * @functionpage{AwsIot_MallocMessageBuffer,platform_static_memory,mallocmessagebuffer} + * @functionpage{AwsIot_FreeMessageBuffer,platform_static_memory,freemessagebuffer} + */ + +/** + * @brief Get the fixed size of a [message buffer] + * (@ref platform_static_memory_types_messagebuffers). + * + * The size of the message buffers are known at compile time, but it is a [constant] + * (@ref AWS_IOT_MESSAGE_BUFFER_SIZE) that may not be visible to all source files. + * This function allows other source files to know the size of a message buffer. + * + * @return The size, in bytes, of a single message buffer. + */ +/* @[declare_platform_static_memory_messagebuffersize] */ +size_t AwsIot_MessageBufferSize( void ); +/* @[declare_platform_static_memory_messagebuffersize] */ + +/** + * @brief Get an empty [message buffer] + * (@ref platform_static_memory_types_messagebuffers). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for message buffers. + * + * @param[in] size Requested size for a message buffer. + * + * @return Pointer to the start of a message buffer. If the `size` argument is larger + * than the [fixed size of a message buffer](@ref AWS_IOT_MESSAGE_BUFFER_SIZE) + * or no message buffers are available, `NULL` is returned. + */ +/* @[declare_platform_static_memory_mallocmessagebuffer] */ +void * AwsIot_MallocMessageBuffer( size_t size ); +/* @[declare_platform_static_memory_mallocmessagebuffer] */ + +/** + * @brief Free an in-use [message buffer] + * (@ref platform_static_memory_types_messagebuffers). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for message buffers. + * + * @param[in] ptr Pointer to the message buffer to free. + */ +/* @[declare_platform_static_memory_freemessagebuffer] */ +void AwsIot_FreeMessageBuffer( void * ptr ); +/* @[declare_platform_static_memory_freemessagebuffer] */ + +/*----------------------- MQTT connection management ------------------------*/ + +/** + * @functionpage{AwsIot_MallocMqttConnection,platform_static_memory,mallocmqttconnection} + * @functionpage{AwsIot_FreeMqttConnection,platform_static_memory,freemqttconnection} + */ + +/** + * @brief Allocates memory to hold data for a new [MQTT connection] + * (@ref platform_static_memory_types_mqttconnections). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for MQTT connections. + * + * @param[in] size Must be equal to sizeof( #_mqttConnection_t ). + * + * @return Pointer to an MQTT connection. + */ +/* @[declare_platform_static_memory_mallocmqttconnection] */ +void * AwsIot_MallocMqttConnection( size_t size ); +/* @[declare_platform_static_memory_mallocmqttconnection] */ + +/** + * @brief Frees an in-use [MQTT connection] + * (@ref platform_static_memory_types_mqttconnections). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for MQTT connections. + * + * @param[in] ptr Pointer to an active MQTT connection to free. + */ +/* @[declare_platform_static_memory_freemqttconnection] */ +void AwsIot_FreeMqttConnection( void * ptr ); +/* @[declare_platform_static_memory_freemqttconnection] */ + +/*------------------------ MQTT operation management ------------------------*/ + +/** + * @functionpage{AwsIot_MallocMqttOperation,platform_static_memory,mallocmqttoperation} + * @functionpage{AwsIot_FreeMqttOperation,platform_static_memory,freemqttoperation} + */ + +/** + * @brief Allocates memory to hold data for a new [MQTT operation] + * (@ref platform_static_memory_types_mqttoperations). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for MQTT operations. + * + * @param[in] size Must be equal to sizeof( #_mqttOperation_t ). + * + * @return Pointer to an MQTT operation. + */ +/* @[declare_platform_static_memory_mallocmqttoperation] */ +void * AwsIot_MallocMqttOperation( size_t size ); +/* @[declare_platform_static_memory_mallocmqttoperation] */ + +/** + * @brief Frees an in-use [MQTT operation] + * (@ref platform_static_memory_types_mqttoperations). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for MQTT operations. + * + * @param[in] ptr Pointer to an active MQTT operation to free. + */ +/* @[declare_platform_static_memory_freemqttoperation] */ +void AwsIot_FreeMqttOperation( void * ptr ); +/* @[declare_platform_static_memory_freemqttoperation] */ + +/*----------------------- MQTT timer event management -----------------------*/ + +/** + * @functionpage{AwsIot_MallocMqttTimerEvent,platform_static_memory,mallocmqtttimerevent} + * @functionpage{AwsIot_FreeMqttTimerEvent,platform_static_memory,freemqtttimerevent} + */ + +/** + * @brief Allocates memory to hold data for a new [MQTT timer event] + * (@ref platform_static_memory_types_mqtttimerevents). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) for MQTT timer events. + * + * @param[in] size Must be equal to sizeof( #_mqttTimerEvent_t ). + * + * @return Pointer to an MQTT timer event. + */ +/* @[declare_platform_static_memory_mallocmqtttimerevent] */ +void * AwsIot_MallocMqttTimerEvent( size_t size ); +/* @[declare_platform_static_memory_mallocmqtttimerevent] */ + +/** + * @brief Frees an in-use [MQTT timer event] + * (@ref platform_static_memory_types_mqtttimerevents). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for MQTT timer events. + * + * @param[in] ptr Pointer to an active MQTT timer event to free. + */ +/* @[declare_platform_static_memory_freemqtttimerevent] */ +void AwsIot_FreeMqttTimerEvent( void * ptr ); +/* @[declare_platform_static_memory_freemqtttimerevent] */ + +/*---------------------- MQTT subscription management -----------------------*/ + +/** + * @functionpage{AwsIot_MallocMqttSubscription,platform_static_memory,mallocmqttsubscription} + * @functionpage{AwsIot_FreeMqttSubscription,platform_static_memory,freemqttsubscription} + */ + +/** + * @brief Allocates memory to hold data for a new [MQTT subscription] + * (@ref platform_static_memory_types_mqttsubscriptions). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for subscriptions. + * + * @param[in] size Requested size for the subscription. Because each subscription + * contains a different topic, subscriptions will be different sizes. This value + * should be checked to make sure that the statically-allocated subscription + * object is large enough to accommodate the new topic filter. + * + * @return Pointer to an MQTT subscription. If the size argument is larger than + * the fixed size of an MQTT subscription object or no free MQTT subscriptions + * are available, `NULL` is returned. + */ +/* @[declare_platform_static_memory_mallocmqttsubscription] */ +void * AwsIot_MallocMqttSubscription( size_t size ); +/* @[declare_platform_static_memory_mallocmqttsubscription] */ + +/** + * @brief Frees an in-use [MQTT subscription] + * (@ref platform_static_memory_types_mqttsubscriptions). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for MQTT subscriptions. + * + * @param[in] ptr Pointer to an active MQTT subscription to free. + */ +/* @[declare_platform_static_memory_freemqttsubscription] */ +void AwsIot_FreeMqttSubscription( void * ptr ); +/* @[declare_platform_static_memory_freemqttsubscription] */ + +/*---------------------- Shadow operation management ------------------------*/ + +/** + * @functionpage{AwsIot_MallocShadowOperation,platform_static_memory,mallocshadowoperation} + * @functionpage{AwsIot_FreeShadowOperation,platform_static_memory,freeshadowoperation} + */ + +/** + * @brief Allocates memory to hold data for a new [Shadow operation] + * (@ref platform_static_memory_types_shadowoperations). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Shadow operations. + * + * @param[in] size Must be equal to sizeof( #_shadowOperation_t ). + * + * @return Pointer to a Shadow operation. + */ +/* @[declare_platform_static_memory_mallocshadowoperation] */ +void * AwsIot_MallocShadowOperation( size_t size ); +/* @[declare_platform_static_memory_mallocshadowoperation] */ + +/** + * @brief Frees an in-use [Shadow operation] + * (@ref platform_static_memory_types_shadowoperations). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Shadow operations. + * + * @param[in] ptr Pointer to an active Shadow operation to free. + */ +/* @[declare_platform_static_memory_freeshadowoperation] */ +void AwsIot_FreeShadowOperation( void * ptr ); +/* @[declare_platform_static_memory_freeshadowoperation] */ + +/*--------------------- Shadow subscription management ----------------------*/ + +/** + * @functionpage{AwsIot_MallocShadowSubscription,platform_static_memory,mallocshadowsubscription} + * @functionpage{AwsIot_FreeShadowSubscription,platform_static_memory,freeshadowsubscription} + */ + +/** + * @brief Allocates memory to hold data for a new [Shadow subscription] + * (@ref platform_static_memory_types_shadowsubscriptions). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Shadow subscriptions. + * + * @param[in] size Requested size for the subscription. Because each subscription + * contains a different Thing Name, subscriptions will be different sizes. This + * value should be checked to make sure that the statically-allocated subscription + * object is large enough to accommodate the new Thing Name. + * + * @return Pointer to a Shadow subscription. If the size argument is larger than + * the fixed size of a Shadow subscription object or no free Shadow subscriptions + * are available, `NULL` is returned. + */ +/* @[declare_platform_static_memory_mallocshadowsubscription] */ +void * AwsIot_MallocShadowSubscription( size_t size ); +/* @[declare_platform_static_memory_mallocshadowsubscription] */ + +/** + * @brief Frees an in-use [Shadow subscription] + * (@ref platform_static_memory_types_shadowsubscriptions). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Shadow subscriptions. + * + * @param[in] ptr Pointer to an active Shadow subscription to free. + */ +/* @[declare_platform_static_memory_freeshadowsubscription] */ +void AwsIot_FreeShadowSubscription( void * ptr ); +/* @[declare_platform_static_memory_freeshadowsubscription] */ + +#endif /* if !defined( _AWS_IOT_STATIC_MEMORY_H_ ) && ( AWS_IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/include/platform/aws_iot_threads.h b/lib/include/platform/aws_iot_threads.h new file mode 100644 index 0000000000..c82d91d559 --- /dev/null +++ b/lib/include/platform/aws_iot_threads.h @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_threads.h + * @brief Threading and synchronization functions used by the AWS IoT libraries. + */ + +#ifndef _AWS_IOT_THREADS_H_ +#define _AWS_IOT_THREADS_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent mutexes. The constant @ref AWS_IOT_MUTEX_TYPE + * can be used to change the mutex type. + * + * + * This constant will be automatically configured during build and generally + * does not need to be defined. + * + * + * Mutexes should only be released by the threads that take them. + * + * Example
+ * To change the type of #AwsIotMutex_t to `long`: + * @code{c} + * #define AWS_IOT_MUTEX_TYPE long + * #include "aws_iot_threads.h" + * @endcode + */ +typedef AWS_IOT_MUTEX_TYPE AwsIotMutex_t; + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent semaphores. The constant + * @ref AWS_IOT_SEMAPHORE_TYPE be used to change the semaphore type. + * + * + * This constant will be automatically configured during build and generally + * does not need to be defined. + * + * + * Semaphores must be counting, and any thread may take (wait on) or release + * (post to) a semaphore. + * + * Example
+ * To change the type of #AwsIotSemaphore_t to `long`: + * @code{c} + * #define AWS_IOT_SEMAPHORE_TYPE long + * #include "aws_iot_threads.h" + * @endcode + */ +typedef AWS_IOT_SEMAPHORE_TYPE AwsIotSemaphore_t; + +/** + * @brief Thread routine function. + * + * @param[in] void * The argument passed to the @ref + * platform_threads_function_createdetachedthread. For application use. + */ +typedef void ( * AwsIotThreadRoutine_t )( void * ); + +/** + * @functionspage{platform_threads,platform thread management,Thread Management} + * - @functionname{platform_threads_function_createdetachedthread} + * - @functionname{platform_threads_function_mutexcreate} + * - @functionname{platform_threads_function_mutexdestroy} + * - @functionname{platform_threads_function_mutexlock} + * - @functionname{platform_threads_function_mutextrylock} + * - @functionname{platform_threads_function_mutexunlock} + * - @functionname{platform_threads_function_semaphorecreate} + * - @functionname{platform_threads_function_semaphoredestroy} + * - @functionname{platform_threads_function_semaphoregetcount} + * - @functionname{platform_threads_function_semaphorewait} + * - @functionname{platform_threads_function_semaphoretrywait} + * - @functionname{platform_threads_function_semaphoretimedwait} + * - @functionname{platform_threads_function_semaphorepost} + */ + +/** + * @functionpage{AwsIot_CreateDetachedThread,platform_threads,createdetachedthread} + * @functionpage{AwsIotMutex_Create,platform_threads,mutexcreate} + * @functionpage{AwsIotMutex_Destroy,platform_threads,mutexdestroy} + * @functionpage{AwsIotMutex_Lock,platform_threads,mutexlock} + * @functionpage{AwsIotMutex_TryLock,platform_threads,mutextrylock} + * @functionpage{AwsIotMutex_Unlock,platform_threads,mutexunlock} + * @functionpage{AwsIotSemaphore_Create,platform_threads,semaphorecreate} + * @functionpage{AwsIotSemaphore_Destroy,platform_threads,semaphoredestroy} + * @functionpage{AwsIotSemaphore_GetCount,platform_threads,semaphoregetcount} + * @functionpage{AwsIotSemaphore_Wait,platform_threads,semaphorewait} + * @functionpage{AwsIotSemaphore_TryWait,platform_threads,semaphoretrywait} + * @functionpage{AwsIotSemaphore_TimedWait,platform_threads,semaphoretimedwait} + * @functionpage{AwsIotSemaphore_Post,platform_threads,semaphorepost} + */ + +/** + * @brief Create a new detached thread, i.e. a thread that cleans up after itself. + * + * This function creates a new thread. Threads created by this function exit + * upon returning from the thread routine. Any resources taken must be freed + * by the exiting thread. + * + * @param[in] threadRoutine The function this thread should run. + * @param[in] pArgument The argument passed to `threadRoutine`. + * + * @return `true` if the new thread was successfully created; `false` otherwise. + * + * @code{c} + * // Thread routine. + * void threadRoutine( void * pArgument ); + * + * // Run threadRoutine in a detached thread. + * if( AwsIot_CreateDetachedThread( threadRoutine, NULL ) == true ) + * { + * // Success + * } + * else + * { + * // Failure, no thread was created. + * } + * @endcode + */ +/* @[declare_platform_threads_createdetachedthread] */ +bool AwsIot_CreateDetachedThread( AwsIotThreadRoutine_t threadRoutine, + void * pArgument ); +/* @[declare_platform_threads_createdetachedthread] */ + +/** + * @brief Create a new mutex. + * + * This function creates a new, unlocked mutex. It must be called on an uninitialized + * #AwsIotMutex_t. This function must not be called on an already-initialized + * #AwsIotMutex_t. + * + * @param[in] pNewMutex Pointer to the memory that will hold the new mutex. + * + * @return `true` if mutex creation succeeds; `false` otherwise. + * + * @see @ref platform_threads_function_mutexdestroy + * + * **Example** + * @code{c} + * AwsIotMutex_t mutex; + * + * if( AwsIotMutex_Create( &mutex ) == true ) + * { + * // Lock and unlock the mutex... + * + * // Destroy the mutex when it's no longer needed. + * AwsIotMutex_Destroy( &mutex ); + * } + * @endcode + */ +/* @[declare_platform_threads_mutexcreate] */ +bool AwsIotMutex_Create( AwsIotMutex_t * const pNewMutex ); +/* @[declare_platform_threads_mutexcreate] */ + +/** + * @brief Free resources used by a mutex. + * + * This function frees resources used by a mutex. It must be called on an initialized + * #AwsIotMutex_t. No other mutex functions should be called on `pMutex` after calling + * this function (unless the mutex is re-created). + * + * @param[in] pMutex The mutex to destroy. + * + * @warning This function must not be called on a locked mutex. + * @see @ref platform_threads_function_mutexcreate + */ +/* @[declare_platform_threads_mutexdestroy] */ +void AwsIotMutex_Destroy( AwsIotMutex_t * const pMutex ); +/* @[declare_platform_threads_mutexdestroy] */ + +/** + * @brief Lock a mutex. This function should only return when the mutex is locked; + * it is not expected to fail. + * + * This function blocks and waits until a mutex is available. It waits forever + * (deadlocks) if `pMutex` is already locked and never unlocked. + * + * @param[in] pMutex The mutex to lock. + * + * @see @ref platform_threads_function_mutextrylock for a nonblocking lock. + */ +/* @[declare_platform_threads_mutexlock] */ +void AwsIotMutex_Lock( AwsIotMutex_t * const pMutex ); +/* @[declare_platform_threads_mutexlock] */ + +/** + * @brief Attempt to lock a mutex. Return immediately if the mutex is not available. + * + * If `pMutex` is available, this function immediately locks it and returns. + * Otherwise, this function returns without locking `pMutex`. + * + * @param[in] pMutex The mutex to lock. + * + * @return `true` if the mutex was successfully locked; `false` if the mutex was + * not available. + * + * @see @ref platform_threads_function_mutexlock for a blocking lock. + */ +/* @[declare_platform_threads_mutextrylock] */ +bool AwsIotMutex_TryLock( AwsIotMutex_t * const pMutex ); +/* @[declare_platform_threads_mutextrylock] */ + +/** + * @brief Unlock a mutex. This function should only return when the mutex is unlocked; + * it is not expected to fail. + * + * Unlocks a locked mutex. `pMutex` must have been locked by the thread calling + * this function. + * + * @param[in] pMutex The mutex to unlock. + * + * @note This function should not be called on a mutex that is already unlocked. + */ +/* @[declare_platform_threads_mutexunlock] */ +void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ); +/* @[declare_platform_threads_mutexunlock] */ + +/** + * @brief Create a new counting semaphore. + * + * This function creates a new counting semaphore with a given intial and + * maximum value. It must be called on an uninitialized #AwsIotSemaphore_t. + * This function must not be called on an already-initialized #AwsIotSemaphore_t. + * + * @param[in] pNewSemaphore Pointer to the memory that will hold the new semaphore. + * @param[in] initialValue The semaphore should be initialized with this value. + * @param[in] maxValue The maximum value the semaphore will reach. + * + * @return `true` if semaphore creation succeeds; `false` otherwise. + * + * @see @ref platform_threads_function_semaphoredestroy + * + * **Example** + * @code{c} + * AwsIotSemaphore_t sem; + * + * // Create a locked binary semaphore. + * if( AwsIotSemaphore_Create( &sem, 0, 1 ) == true ) + * { + * // Unlock the semaphore. + * AwsIotSemaphore_Post( &sem ); + * + * // Destroy the semaphore when it's no longer needed. + * AwsIotSemaphore_Destroy( &sem ); + * } + * @endcode + */ +/* @[declare_platform_threads_semaphorecreate] */ +bool AwsIotSemaphore_Create( AwsIotSemaphore_t * const pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ); +/* @[declare_platform_threads_semaphorecreate] */ + +/** + * @brief Free resources used by a semaphore. + * + * This function frees resources used by a semaphore. It must be called on an initialized + * #AwsIotSemaphore_t. No other semaphore functions should be called on `pSemaphore` after + * calling this function (unless the semaphore is re-created). + * + * @param[in] pSemaphore The semaphore to destroy. + * + * @warning This function must not be called on a semaphore with waiting threads. + * @see @ref platform_threads_function_semaphorecreate + */ +/* @[declare_platform_threads_semaphoredestroy] */ +void AwsIotSemaphore_Destroy( AwsIotSemaphore_t * const pSemaphore ); +/* @[declare_platform_threads_semaphoredestroy] */ + +/** + * @brief Query the current count of the semaphore. + * + * This function queries a counting semaphore for its current value. A counting + * semaphore's value is always 0 or positive. + * + * @param[in] pSemaphore The semaphore to query. + * + * @return The current count of the semaphore. This function should not fail. + */ +/* @[declare_platform_threads_semaphoregetcount] */ +uint32_t AwsIotSemaphore_GetCount( AwsIotSemaphore_t * const pSemaphore ); +/* @[declare_platform_threads_semaphoregetcount] */ + +/** + * @brief Wait on (lock) a semaphore. This function should only return when the + * semaphore wait succeeds; it is not expected to fail. + * + * This function blocks and waits until a counting semaphore is positive. It + * waits forever (deadlocks) if `pSemaphore` has a count `0` that is never + * [incremented](@ref platform_threads_function_semaphorepost). + * + * @param[in] pSemaphore The semaphore to lock. + * + * @see @ref platform_threads_function_semaphoretrywait for a nonblocking wait; + * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. + */ +/* @[declare_platform_threads_semaphorewait] */ +void AwsIotSemaphore_Wait( AwsIotSemaphore_t * const pSemaphore ); +/* @[declare_platform_threads_semaphorewait] */ + +/** + * @brief Attempt to wait on (lock) a semaphore. Return immediately if the semaphore + * is not available. + * + * If the count of `pSemaphore` is positive, this function immediately decrements + * the semaphore and returns. Otherwise, this function returns without decrementing + * `pSemaphore`. + * + * @param[in] pSemaphore The semaphore to lock. + * + * @return `true` if the semaphore wait succeeded; `false` if the semaphore has + * a count of `0`. + * + * @see @ref platform_threads_function_semaphorewait for a blocking wait; + * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. + */ +/* @[declare_platform_threads_semaphoretrywait] */ +bool AwsIotSemaphore_TryWait( AwsIotSemaphore_t * const pSemaphore ); +/* @[declare_platform_threads_semaphoretrywait] */ + +/** + * @brief Attempt to wait on (lock) a semaphore with a timeout. + * + * This function blocks and waits until a counting semaphore is positive + * or its timeout expires (whichever is sooner). It decrements + * `pSemaphore` and returns `true` if the semaphore is positive at some + * time during the wait. If `pSemaphore` is always `0` during the wait, + * this function returns `false`. + * + * @param[in] pSemaphore The semaphore to lock. + * @param[in] timeoutMs Relative timeout of semaphore lock. This function returns + * false if the semaphore couldn't be locked within this timeout. + * + * @return `true` if the semaphore wait succeeded; `false` if it timed out. + * + * @see @ref platform_threads_function_semaphoretrywait for a nonblocking wait; + * @ref platform_threads_function_semaphorewait for a blocking wait. + */ +/* @[declare_platform_threads_semaphoretimedwait] */ +bool AwsIotSemaphore_TimedWait( AwsIotSemaphore_t * const pSemaphore, + uint64_t timeoutMs ); +/* @[declare_platform_threads_semaphoretimedwait] */ + +/** + * @brief Post to (unlock) a semaphore. This function should only return when the + * semaphore post succeeds; it is not expected to fail. + * + * This function increments the count of a semaphore. Any thread may call this + * function to increment a semaphore's count. + * + * @param[in] pSemaphore The semaphore to unlock. + */ +/* @[declare_platform_threads_semaphorepost] */ +void AwsIotSemaphore_Post( AwsIotSemaphore_t * const pSemaphore ); +/* @[declare_platform_threads_semaphorepost] */ + +#endif /* ifndef _AWS_IOT_THREADS_H_ */ diff --git a/lib/include/private/aws_iot_logging.h b/lib/include/private/aws_iot_logging.h new file mode 100644 index 0000000000..fc6159c475 --- /dev/null +++ b/lib/include/private/aws_iot_logging.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_logging.h + * @brief Generic logging function header file. + * + * Declares the generic logging function and the log levels. This file never + * needs to be included in source code. The header aws_iot_logging_setup.h should + * be included instead. + * + * @see aws_iot_logging_setup.h + */ + +#ifndef _AWS_IOT_LOGGING_H_ +#define _AWS_IOT_LOGGING_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/** + * @constantspage{logging,logging library} + * + * @section logging_constants_levels Log levels + * @brief Log levels for the AWS IoT libraries. + * + * Each library should specify a log level by setting @ref _LIBRARY_LOG_LEVEL. + * All log messages with a level at or below the specified level will be printed + * for that library. + * + * Currently, there are 4 log levels. In the order of lowest to highest, they are: + * - #AWS_IOT_LOG_NONE
+ * @copybrief AWS_IOT_LOG_NONE + * - #AWS_IOT_LOG_ERROR
+ * @copybrief AWS_IOT_LOG_ERROR + * - #AWS_IOT_LOG_WARN
+ * @copybrief AWS_IOT_LOG_WARN + * - #AWS_IOT_LOG_INFO
+ * @copybrief AWS_IOT_LOG_INFO + * - #AWS_IOT_LOG_DEBUG
+ * @copybrief AWS_IOT_LOG_DEBUG + */ + +/** + * @brief No log messages. + * + * Log messages with this level will be silently discarded. When @ref + * _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_NONE, logging is disabled and no [logging functions] + * (@ref logging_functions) can be called. + */ +#define AWS_IOT_LOG_NONE 0 + +/** + * @brief Only critical, unrecoverable errors. + * + * Log messages with this level will be printed when a library encounters an + * error from which it cannot easily recover. + */ +#define AWS_IOT_LOG_ERROR 1 + +/** + * @brief Message about an abnormal but recoverable event. + * + * Log messages with this level will be printed when a library encounters an + * abnormal event that may be indicative of an error. Libraries should continue + * execution after logging a warning. + */ +#define AWS_IOT_LOG_WARN 2 + +/** + * @brief A helpful, informational message. + * + * Log messages with this level may indicate the normal status of a library + * function. They should be used to track how far a program has executed. + */ +#define AWS_IOT_LOG_INFO 3 + +/** + * @brief Detailed and excessive debug information. + * + * Log messages with this level are intended for developers. They may contain + * excessive information such as internal variables, buffers, or other specific + * information. + */ +#define AWS_IOT_LOG_DEBUG 4 + +/** + * @paramstructs{logging,logging} + */ + +/** + * @ingroup logging_datatypes_paramstructs + * @brief Log message configuration struct. + * + * @paramfor @ref logging_function_log, @ref logging_function_loggeneric + * + * By default, log messages print the library name, log level, and a timestring. + * This struct can be passed to @ref logging_function_loggeneric to disable one of + * the above components in the log message. + * + * **Example:** + * + * @code{c} + * AwsIotLogGeneric( AWS_IOT_LOG_DEBUG, "SAMPLE", AWS_IOT_LOG_DEBUG, NULL, "Hello world!" ); + * @endcode + * The code above prints the following message: + * @code + * [DEBUG][SAMPLE][2018-01-01 12:00:00] Hello world! + * @endcode + * + * The timestring can be disabled as follows: + * @code + * AwsIotLogConfig_t logConfig = { .hideLogLevel = false, .hideLibraryName = false, .hideTimestring = true}; + * AwsIotLogGeneric( AWS_IOT_LOG_DEBUG, "SAMPLE", AWS_IOT_LOG_DEBUG, &logConfig, "Hello world!" ); + * @endcode + * The resulting log message will be: + * @code + * [DEBUG][SAMPLE] Hello world! + * @endcode + */ +typedef struct AwsIotLogConfig +{ + bool hideLogLevel; /**< @brief Don't print the log level string for this message. */ + bool hideLibraryName; /**< @brief Don't print the library name for this message. */ + bool hideTimestring; /**< @brief Don't print the timestring for this message. */ +} AwsIotLogConfig_t; + +/** + * @functionspage{logging,logging library} + * + * - @functionname{logging_function_log} + * - @functionname{logging_function_printbuffer} + * - @functionname{logging_function_loggeneric} + * - @functionname{logging_function_genericprintbuffer} + */ + +/** + * @functionpage{AwsIotLogGeneric,logging,loggeneric} + * @functionpage{AwsIotLogGeneric_PrintBuffer,logging,genericprintbuffer} + */ + +/** + * @brief Generic logging function that prints a single message. + * + * This function is the generic logging function shared across all libraries. + * The library-specific logging function @ref logging_function_log is implemented + * using this function. Like @ref logging_function_log, this function is only + * available when @ref _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_NONE. + * + * In most cases, the library-specific logging function @ref logging_function_log + * should be called instead of this function. + * + * @param[in] libraryLogSetting The log level setting of the library, used to + * determine if the log message should be printed. Must be one of the @ref + * logging_constants_levels. + * @param[in] pLibraryName The library name to print. See @ref _LIBRARY_LOG_NAME. + * @param[in] messageLevel The log level of the this message. See @ref _LIBRARY_LOG_LEVEL. + * @param[in] pLogConfig Pointer to a #AwsIotLogConfig_t. Optional; pass `NULL` to ignore. + * @param[in] pFormat Format string for the log message. + * @param[in] ... Arguments for format specification. + * + * @return No return value. On errors, it prints nothing. + */ +/* @[declare_logging_loggeneric] */ +void AwsIotLogGeneric( int libraryLogSetting, + const char * const pLibraryName, + int messageLevel, + const AwsIotLogConfig_t * const pLogConfig, + const char * const pFormat, + ... ); +/* @[declare_logging_loggeneric] */ + +/** + * @brief Generic function to log the contents of a buffer as bytes. + * + * This function is the generic buffer logging function shared across all libraries. + * The library-specific buffer logging function @ref logging_function_printbuffer is + * implemented using this function. Like @ref logging_function_printbuffer, this + * function is only available when @ref _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_DEBUG. + * + * In most cases, the library-specific buffer logging function @ref + * logging_function_printbuffer should be called instead of this function. + * + * @param[in] pLibraryName The library name to print with the log. See @ref _LIBRARY_LOG_NAME. + * @param[in] pHeader A message to print before printing the buffer. + * @param[in] pBuffer The buffer to print. + * @param[in] bufferSize The number of bytes in `pBuffer` to print. + * + * @return No return value. On errors, it prints nothing. + * + * @note To conserve memory, this function only allocates enough memory for a + * single line of output. Therefore, in multithreaded systems, its output may + * appear "fragmented" if other threads are logging simultaneously. + */ +/* @[declare_logging_genericprintbuffer] */ +void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, + const char * const pHeader, + const uint8_t * const pBuffer, + size_t bufferSize ); +/* @[declare_logging_genericprintbuffer] */ + +#endif /* ifndef _AWS_IOT_LOGGING_H_ */ diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h new file mode 100644 index 0000000000..4d0a42ef7b --- /dev/null +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -0,0 +1,911 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_internal.h + * @brief Internal header of MQTT library. This header should not be included in + * typical application code. + */ + +#ifndef _AWS_IOT_MQTT_INTERNAL_H_ +#define _AWS_IOT_MQTT_INTERNAL_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/* Queue include. */ +#include "aws_iot_queue.h" + +/* Platform clock include. */ +#include "platform/aws_iot_clock.h" + +/** + * @def AwsIotMqtt_Assert( expression ) + * @brief Assertion macro for the MQTT library. + * + * Set @ref AWS_IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if AWS_IOT_MQTT_ENABLE_ASSERTS == 1 + #ifndef AwsIotMqtt_Assert + #include + #define AwsIotMqtt_Assert( expression ) assert( expression ) + #endif +#else + #define AwsIotMqtt_Assert( expression ) +#endif + +/* Configure logs for MQTT functions. */ +#ifdef AWS_IOT_LOG_LEVEL_MQTT + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_MQTT +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "MQTT" ) +#include "aws_iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #include "platform/aws_iot_static_memory.h" + + /** + * @brief Allocate an #_mqttConnection_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotMqtt_MallocConnection + #define AwsIotMqtt_MallocConnection AwsIot_MallocMqttConnection + #endif + + /** + * @brief Free an #_mqttConnection_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotMqtt_FreeConnection + #define AwsIotMqtt_FreeConnection AwsIot_FreeMqttConnection + #endif + + /** + * @brief Allocate memory for an MQTT packet. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotMqtt_MallocMessage + #define AwsIotMqtt_MallocMessage AwsIot_MallocMessageBuffer + #endif + + /** + * @brief Free an MQTT packet. This function should have the same signature + * as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotMqtt_FreeMessage + #define AwsIotMqtt_FreeMessage AwsIot_FreeMessageBuffer + #endif + + /** + * @brief Allocate an #_mqttOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotMqtt_MallocOperation + #define AwsIotMqtt_MallocOperation AwsIot_MallocMqttOperation + #endif + + /** + * @brief Free an #_mqttOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotMqtt_FreeOperation + #define AwsIotMqtt_FreeOperation AwsIot_FreeMqttOperation + #endif + + /** + * @brief Allocate an #_mqttSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotMqtt_MallocSubscription + #define AwsIotMqtt_MallocSubscription AwsIot_MallocMqttSubscription + #endif + + /** + * @brief Free an #_mqttSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotMqtt_FreeSubscription + #define AwsIotMqtt_FreeSubscription AwsIot_FreeMqttSubscription + #endif + + /** + * @brief Allocate an #_mqttTimerEvent_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotMqtt_MallocTimerEvent + #define AwsIotMqtt_MallocTimerEvent AwsIot_MallocMqttTimerEvent + #endif + + /** + * @brief Free an #_mqttTimerEvent_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotMqtt_FreeTimerEvent + #define AwsIotMqtt_FreeTimerEvent AwsIot_FreeMqttTimerEvent + #endif +#else /* if AWS_IOT_STATIC_MEMORY_ONLY */ + #include + + #ifndef AwsIotMqtt_MallocConnection + #define AwsIotMqtt_MallocConnection malloc + #endif + + #ifndef AwsIotMqtt_FreeConnection + #define AwsIotMqtt_FreeConnection free + #endif + + #ifndef AwsIotMqtt_MallocMessage + #define AwsIotMqtt_MallocMessage malloc + #endif + + #ifndef AwsIotMqtt_FreeMessage + #define AwsIotMqtt_FreeMessage free + #endif + + #ifndef AwsIotMqtt_MallocOperation + #define AwsIotMqtt_MallocOperation malloc + #endif + + #ifndef AwsIotMqtt_FreeOperation + #define AwsIotMqtt_FreeOperation free + #endif + + #ifndef AwsIotMqtt_MallocSubscription + #define AwsIotMqtt_MallocSubscription malloc + #endif + + #ifndef AwsIotMqtt_FreeSubscription + #define AwsIotMqtt_FreeSubscription free + #endif + + #ifndef AwsIotMqtt_MallocTimerEvent + #define AwsIotMqtt_MallocTimerEvent malloc + #endif + + #ifndef AwsIotMqtt_FreeTimerEvent + #define AwsIotMqtt_FreeTimerEvent free + #endif +#endif /* if AWS_IOT_STATIC_MEMORY_ONLY */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_MQTT_ENABLE_METRICS + #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) +#endif +#ifndef AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES + #define AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) +#endif +#ifndef AWS_IOT_MQTT_MAX_CALLBACK_THREADS + #define AWS_IOT_MQTT_MAX_CALLBACK_THREADS ( 2 ) +#endif +#ifndef AWS_IOT_MQTT_MAX_SEND_THREADS + #define AWS_IOT_MQTT_MAX_SEND_THREADS ( 1 ) +#endif +#ifndef AWS_IOT_MQTT_TEST + #define AWS_IOT_MQTT_TEST ( 0 ) +#endif +#ifndef AWS_IOT_MQTT_RESPONSE_WAIT_MS + #define AWS_IOT_MQTT_RESPONSE_WAIT_MS ( 1000 ) +#endif +#ifndef AWS_IOT_MQTT_RETRY_MS_CEILING + #define AWS_IOT_MQTT_RETRY_MS_CEILING ( 60000 ) +#endif +#ifndef AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS + #define AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ( 100 ) +#endif +/** @endcond */ + +/* + * Constants related to limits defined in AWS Service Limits. + * + * For details, see + * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html + * + * Used to validate parameters if when connecting to an AWS IoT MQTT server. + */ +#define _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ + +/* + * MQTT control packet type and flags. Always the first byte of an MQTT + * packet. + * + * For details, see + * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 + */ +#define _MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define _MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ +#define _MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ +#define _MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ +#define _MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ +#define _MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ +#define _MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ + +/*---------------------- MQTT internal data structures ----------------------*/ + +/** + * @brief The offset of `link` in #_mqttSubscription_t. + */ +#define _SUBSCRIPTION_LINK_OFFSET AwsIotLink_Offset( _mqttSubscription_t, link ) + + /** + * @brief The offset of `link` in #_mqttTimerEvent_t. + */ +#define _TIMER_EVENT_LINK_OFFSET AwsIotLink_Offset( _mqttTimerEvent_t, link ) + +/** + * @brief The offset of `link` in #_mqttOperation_t. + */ +#define _OPERATION_LINK_OFFSET AwsIotLink_Offset( _mqttOperation_t, link ) + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declarations. + */ +struct _mqttSubscription; +struct _mqttConnection; +struct _mqttOperation; +struct _mqttTimerEvent; +/** @endcond */ + +/** + * @brief Represents a subscription stored in an MQTT connection. + */ +typedef struct _mqttSubscription +{ + AwsIotLink_t link; /**< @brief List link member. */ + + int references; /**< @brief How many subscription callbacks are using this subscription. */ + + /** + * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for + * this subscription. + * + * If there are active subscription callbacks, @ref mqtt_function_unsubscribe + * cannot remove this subscription. Instead, it will set this flag, which + * schedules the removal of this subscription once all subscription callbacks + * terminate. + */ + bool unsubscribed; + + struct + { + uint16_t identifier; /**< @brief Packet identifier. */ + size_t order; /**< @brief Order in the packet's list of subscriptions. */ + } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ + + AwsIotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ + + uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ + char pTopicFilter[]; /**< @brief The subscription topic filter. */ +} _mqttSubscription_t; + +/** + * @brief Internal structure to hold data on an MQTT connection. + */ +typedef struct _mqttConnection +{ + bool errorOccurred; /**< @brief Tracks if a protocol violation or other error occurred. */ + bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ + AwsIotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + AwsIotList_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + + AwsIotMutex_t mutex; /**< @brief Prevents concurrent threads from modifying connection data. */ + AwsIotTimer_t timer; /**< @brief Expires when a timer event should be processed. */ + AwsIotList_t timerEventList; /**< @brief List of active timer events. */ + uint16_t keepAliveSeconds; /**< @brief Keep-alive interval. */ + struct _mqttOperation * pPingreqOperation; /**< @brief PINGREQ operation. Only used if keep-alive is active. */ + struct _mqttTimerEvent * pKeepAliveEvent; /**< @brief When to process a keep-alive. Only used if keep-alive is active. */ +} _mqttConnection_t; + +/** + * @brief Internal structure representing a single MQTT operation, such as + * CONNECT, SUBSCRIBE, PUBLISH, etc. + * + * Queues of these structures keeps track of all in-progress MQTT operations. + */ +typedef struct _mqttOperation +{ + /* Pointers to neighboring queue elements. */ + AwsIotLink_t link; /**< @brief Queue link member. */ + + bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ + struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ + + union + { + /* If incomingPublish is false, this struct is valid. */ + struct + { + /* Basic operation information. */ + AwsIotMqttOperationType_t operation; /**< @brief What operation this structure represents. */ + uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ + uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ + + /* Serialized packet and size. */ + uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ + size_t packetSize; /**< @brief Size of `pMqttPacket`. */ + + /* How to notify of an operation's completion. */ + union + { + AwsIotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ + AwsIotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ + } notify; /**< @brief How to notify of this operation's completion. */ + AwsIotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ + + struct _mqttTimerEvent * pPublishRetry; /**< @brief How an operation will be retried. Only used for QoS 1 publishes. */ + }; + + /* If incomingPublish is true, this struct is valid. */ + struct + { + AwsIotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ + struct _mqttOperation * pNextPublish; /**< @brief Pointer to the next PUBLISH in the data stream. */ + const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ + void ( * freeReceivedData )( void * ); /**< @brief Function called to free `pReceivedData`. */ + }; + }; + } _mqttOperation_t; + +/** + * @brief Represents an operation that is subject to a timer. + * + * These events are queued per MQTT connection. They are sorted by their + * expiration time. + */ +typedef struct _mqttTimerEvent +{ + AwsIotLink_t link; /**< @brief List link member. */ + + uint64_t expirationTime; /**< @brief When this event should be processed. */ + struct _mqttOperation * pOperation; /**< @brief The MQTT operation associated with this event. */ + + /* Valid members depend on the operation associated with this event + * (PINGREQ or PUBLISH). The checkPingresp member is valid for PINGREQ, + * and the retry member is valid for PUBLISH. */ + union + { + bool checkPingresp; /**< @brief This keep-alive operation is waiting for PINGRESP. */ + + struct + { + int count; /**< @brief The number of times this message has been retried. */ + int limit; /**< @brief The maximum number of times to retry this message. */ + uint64_t nextPeriod; /**< @brief How long to wait before the next retry. */ + } retry; + }; +} _mqttTimerEvent_t; + +/* Declarations of the MQTT queues for internal MQTT files. */ +extern AwsIotQueue_t _AwsIotMqttSendQueue; +extern AwsIotQueue_t _AwsIotMqttReceiveQueue; +extern AwsIotQueue_t _AwsIotMqttCallbackQueue; + +/*-------------------- MQTT struct validation functions ---------------------*/ + +/** + * @brief Check that an #AwsIotMqttNetIf_t is valid. + * + * @param[in] pNetworkInterface The #AwsIotMqttNetIf_t to validate. + * + * @return `true` if `pNetworkInterface` is valid; `false` otherwise. + */ +bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkInterface ); + +/** + * @brief Check that an #AwsIotMqttConnectInfo_t is valid. + * + * @param[in] pConnectInfo The #AwsIotMqttConnectInfo_t to validate. + * + * @return `true` if `pConnectInfo` is valid; `false` otherwise. + */ +bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo ); + +/** + * @brief Check that an #AwsIotMqttPublishInfo_t is valid. + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pPublishInfo The #AwsIotMqttPublishInfo_t to validate. + * + * @return `true` if `pPublishInfo` is valid; `false` otherwise. + */ +bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, + const AwsIotMqttPublishInfo_t * const pPublishInfo ); + +/** + * @brief Check that an #AwsIotMqttReference_t is valid and waitable. + * + * @param[in] reference The #AwsIotMqttReference_t to validate. + * + * @return `true` if `reference` is valid; `false` otherwise. + */ +bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ); + +/** + * @brief Check that a list of #AwsIotMqttSubscription_t is valid. + * + * @param[in] operation Either #AWS_IOT_MQTT_SUBSCRIBE or #AWS_IOT_MQTT_UNSUBSCRIBE. + * Some parameters are not validated for #AWS_IOT_MQTT_UNSUBSCRIBE. + * @param[in] awsIotMqttMode Specifies if this SUBSCRIBE packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pListStart First element of the list to validate. + * @param[in] listSize Number of elements in the subscription list. + * + * @return `true` if every element in the list is valid; `false` otherwise. + */ +bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t operation, + bool awsIotMqttMode, + const AwsIotMqttSubscription_t * const pListStart, + size_t listSize ); + +/*-------------------- MQTT packet serializer functions ---------------------*/ + +/** + * @brief Initialize the MQTT packet serializer. Called by @ref mqtt_function_init + * when initializing the MQTT library. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_INIT_FAILED. + */ +AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ); + +/** + * @brief Free resources taken by #AwsIotMqttInternal_InitSerialize. + * + * No parameters, no return values. + */ +void AwsIotMqttInternal_CleanupSerialize( void ); + +/** + * @brief Get the MQTT packet type from a stream of bytes. + * + * @param[in] pPacket Pointer to the beginning of the byte stream. + * @param[in] packetSize Size of the byte stream. + * + * @return One of the server-to-client MQTT packet types. + * + * @note This function is only used for incoming packets, and may not work + * correctly for outgoing packets. + */ +uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, + size_t packetSize ); + +/** + * @brief Generate a CONNECT packet from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[in] pWillInfo User-provided Last Will and Testament information. + * @param[out] pConnectPacket Where the CONNECT packet is written. + * @param[out] pPacketSize Size of the packet written to `pConnectPacket`. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + uint8_t ** const pConnectPacket, + size_t * const pPacketSize ); + +/** + * @brief Deserialize a CONNACK packet. + * + * Converts the packet from a stream of bytes to an #AwsIotMqttError_t. Also + * prints out debug log messages about the packet. + * @param[in] pConnackStart Pointer to the start of a CONNACK packet. + * @param[in] dataLength Length of the data stream after `pConnackStart`. + * @param[out] pBytesProcessed The number of bytes in the data stream processed + * by this function. For CONNACK, this is always 4. + * + * @return #AWS_IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; + * #AWS_IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; + * #AWS_IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. + */ +AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const pConnackStart, + size_t dataLength, + size_t * const pBytesProcessed ); + +/** + * @brief Generate a PUBLISH packet from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information. + * @param[out] pPublishPacket Where the PUBLISH packet is written. + * @param[out] pPacketSize Size of the packet written to `pPublishPacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint8_t ** const pPublishPacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ); + +/** + * @brief Set the DUP bit in a QoS 1 PUBLISH packet. + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify. + * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag, + * a new packet identifier is generated and should be written here. This parameter + * is only used when connected to an AWS IoT MQTT server. + * + * @note See #AwsIotMqttPublishInfo_t for caveats with retransmission to the + * AWS IoT MQTT server. + */ +void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, + uint8_t * const pPublishPacket, + uint16_t * const pNewPacketIdentifier ); + +/** + * @brief Deserialize a PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #AwsIotMqttPublishInfo_t and + * extracts the packet identifier. Also prints out debug log messages about the + * packet. + * + * @param[in] pPublishStart Pointer to the start of a PUBLISH packet. + * @param[in] dataLength Length of the data stream after `pPublishStart`. + * @param[out] pOutput Where the deserialized PUBLISH will be written. + * @param[out] pPacketIdentifier The packet identifier in the PUBLISH. + * @param[out] pBytesProcessed The number of bytes in the data stream processed + * by this function. + * + * @return #AWS_IOT_MQTT_SUCCESS if PUBLISH is valid; #AWS_IOT_MQTT_BAD_RESPONSE + * if the PUBLISH packet doesn't follow MQTT spec. + */ +AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const pPublishStart, + size_t dataLength, + AwsIotMqttPublishInfo_t * const pOutput, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ); + +/** + * @brief Generate a PUBACK packet for the given packet identifier. + * + * @param[in] packetIdentifier The packet identifier to place in PUBACK. + * @param[out] pPubackPacket Where the PUBACK packet is written. + * @param[out] pPacketSize Size of the packet written to `pPubackPacket`. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializePuback( uint16_t packetIdentifier, + uint8_t ** const pPubackPacket, + size_t * const pPacketSize ); + +/** + * @brief Deserialize a PUBACK packet. + * + * Converts the packet from a stream of bytes to an #AwsIotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in] pPubackStart Pointer to the start of a PUBACK packet. + * @param[in] dataLength Length of the data stream after `pPubackStart`. + * @param[out] pPacketIdentifier The packet identifier in the PUBACK. + * @param[out] pBytesProcessed The number of bytes in the data stream processed + * by this function. For PUBACK, this is always 4. + * + * @return #AWS_IOT_MQTT_SUCCESS if PUBACK is valid; #AWS_IOT_MQTT_BAD_RESPONSE + * if the PUBACK packet doesn't follow MQTT spec. + */ +AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pPubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ); + +/** + * @brief Generate a SUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pSubscribePacket Where the SUBSCRIBE packet is written. + * @param[out] pPacketSize Size of the packet written to `pSubscribePacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pSubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ); + +/** + * @brief Deserialize a SUBACK packet. + * + * Converts the packet from a stream of bytes to an #AwsIotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in] mqttConnection The MQTT connection associated with the subscription. + * Rejected topic filters are removed from this connection. + * @param[in] pSubackStart Pointer to the start of a SUBACK packet. + * @param[in] dataLength Length of the data stream after `pSubackStart`. + * @param[out] pPacketIdentifier The packet identifier in the SUBACK. + * @param[out] pBytesProcessed The number of bytes in the data stream processed by + * this function. + * + * @return #AWS_IOT_MQTT_SUCCESS if SUBACK is valid; #AWS_IOT_MQTT_BAD_RESPONSE + * if the SUBACK packet doesn't follow MQTT spec. + */ +AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t mqttConnection, + const uint8_t * const pSubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ); + +/** + * @brief Generate an UNSUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions to remove. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pUnsubscribePacket Where the UNSUBSCRIBE packet is written. + * @param[out] pPacketSize Size of the packet written to `pUnsubscribePacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pUnsubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ); + +/** + * @brief Deserialize a UNSUBACK packet. + * + * Converts the packet from a stream of bytes to an #AwsIotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in] pUnsubackStart Pointer to the start of an UNSUBACK packet. + * @param[in] dataLength Length of the data stream after `pUnsubackStart`. + * @param[out] pPacketIdentifier The packet identifier in the UNSUBACK. + * @param[out] pBytesProcessed The number of bytes in the data stream processed by + * this function. For UNSUBACK, this is always 4. + * + * @return #AWS_IOT_MQTT_SUCCESS if UNSUBACK is valid; #AWS_IOT_MQTT_BAD_RESPONSE + * if the UNSUBACK packet doesn't follow MQTT spec. + */ +AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const pUnsubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ); + +/** + * @brief Generate a PINGREQ packet. + * + * @param[out] pPingreqPacket Where the PINGREQ packet is written. + * @param[out] pPacketSize Size of the packet written to `pPingreqPacket`. + * + * @return Always returns #AWS_IOT_MQTT_SUCCESS. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreqPacket, + size_t * const pPacketSize ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * Converts the packet from a stream of bytes to an #AwsIotMqttError_t. Also + * prints out debug log messages about the packet. + * @param[in] pPingrespStart Pointer to the start of a PINGRESP packet. + * @param[in] dataLength Length of the data stream after `pPingrespStart`. + * @param[out] pBytesProcessed The number of bytes in the data stream processed by + * this function. For PINGRESP, this is always 2. + * + * @return #AWS_IOT_MQTT_SUCCESS if PINGRESP is valid; #AWS_IOT_MQTT_BAD_RESPONSE + * if the PINGRESP packet doesn't follow MQTT spec. + */ +AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const pPingrespStart, + size_t dataLength, + size_t * const pBytesProcessed ); + +/** + * @brief Generate a DISCONNECT packet. + * + * @param[out] pDisconnectPacket Where the DISCONNECT packet is written. + * @param[out] pPacketSize Size of the packet written to `pDisconnectPacket`. + * + * @return Always returns #AWS_IOT_MQTT_SUCCESS. + */ +AwsIotMqttError_t AwsIotMqttInternal_SerializeDisconnect( uint8_t ** const pDisconnectPacket, + size_t * const pPacketSize ); + +/** + * @brief Free a packet generated by the serializer. + * + * @param[in] pPacket The packet to free. + */ +void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ); + +/*-------------------- MQTT operation record functions ----------------------*/ + +/** + * @brief Create all the MQTT operation queues. Called by @ref mqtt_function_init + * when initializing the MQTT library. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_INIT_FAILED. + */ +AwsIotMqttError_t AwsIotMqttInternal_CreateQueues( void ); + +/** + * @brief Compare two #_mqttTimerEvent_t by expiration time. + * + * @param[in] pData1 The first #_mqttTimerEvent_t to compare. + * @param[in] pData2 The second #_mqttTimerEvent_t to compare. + * + * @return + * - Negative value if `pData1` is less than `pData2`. + * - Zero if `pData1` is equal to `pData2`. + * - Positive value if `pData1` is greater than `pData2`. + * + * @note The arguments of this function are of type `void*` for compatibility with + * @ref list_function_insertsorted. + */ +int AwsIotMqttInternal_TimerEventCompare( void * pData1, + void * pData2 ); + +/** + * @brief Create a record for a new in-progress MQTT operation. + * + * @param[out] pNewOperation Set to point to the new operation on success. + * @param[in] flags Flags variable passed to a user-facing MQTT function. + * @param[in] pCallbackInfo User-provided callback function and parameter. + * + * @return #AWS_IOT_MQTT_SUCCESS, #AWS_IOT_MQTT_BAD_PARAMETER, or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const pNewOperation, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo ); + +/** + * @brief Free resources used to record an MQTT operation. This is called when + * the operation completes. + * + * @param[in] pData The operation which completed. This parameter is of type + * `void*` for compatibility with @ref queue_function_removeallmatches. + */ +void AwsIotMqttInternal_DestroyOperation( void * pData ); + +/** + * @brief Notify of a completed MQTT operation. + * + * @param[in] pOperation The MQTT operation which completed. + * + * Depending on the parameters passed to a user-facing MQTT function, the + * notification will cause @ref mqtt_function_wait to return or invoke a + * user-provided callback. + */ +void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ); + +/** + * @brief Search a queue of in-progress MQTT operations using an operation name + * and packet identifier. Removes the operation from the queue if found. + * + * Allows operations to be removed from the middle of the queue. + * @param[in] pQueue Which queue to search. + * @param[in] operation The operation type to look for. + * @param[in] pPacketIdentifier A packet identifier to match. Pass NULL to ignore. + * @param[out] pOutput Set to point to the found operation. + * + * @return #AWS_IOT_MQTT_SUCCESS if a corresponding operation was found, otherwise + * #AWS_IOT_MQTT_BAD_PARAMETER if not found. + */ +AwsIotMqttError_t AwsIotMqttInternal_OperationFind( AwsIotQueue_t * const pQueue, + AwsIotMqttOperationType_t operation, + const uint16_t * const pPacketIdentifier, + _mqttOperation_t ** const pOutput ); + +/*------------------- Subscription management functions ---------------------*/ + +/** + * @brief Add an array of subscriptions to the subscription manager. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] subscribePacketIdentifier Packet identifier for the subscriptions' + * SUBSCRIBE packet. + * @param[in] pSubscriptionList The first element in the array. + * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. + * + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. + */ +AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const pMqttConnection, + uint16_t subscribePacketIdentifier, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount ); + +/** + * @brief Process a received PUBLISH from the server, invoking any subscription + * callbacks that have a matching topic filter. + * + * @param[in] pMqttConnection The MQTT connection associated with the received + * PUBLISH. + * @param[in] pCallbackParam The parameter to pass to a PUBLISH callback. + */ +void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnection, + AwsIotMqttCallbackParam_t * const pCallbackParam ); + +/** + * @brief Remove a single subscription from the subscription manager by + * packetIdentifier and order. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] packetIdentifier The packet identifier associated with the subscription's + * SUBSCRIBE packet. + * @param[in] order The order of the subscription in the SUBSCRIBE packet. + * Pass `-1` to ignore order and remove all subscriptions for `packetIdentifier`. + */ +void AwsIotMqttInternal_RemoveSubscriptionByPacket( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier, + long order ); + +/** + * @brief Remove an array of subscriptions from the subscription manager by + * topic filter. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] pSubscriptionList The first element in the array. + * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. + */ +void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * const pMqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount ); + +#endif /* ifndef _AWS_IOT_MQTT_INTERNAL_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h new file mode 100644 index 0000000000..63c56abdf7 --- /dev/null +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow_internal.h + * @brief Internal header of Shadow library. This header should not be included in + * typical application code. + */ + +#ifndef _AWS_IOT_SHADOW_INTERNAL_H_ +#define _AWS_IOT_SHADOW_INTERNAL_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Shadow include. */ +#include "aws_iot_shadow.h" + +/* Queue include. */ +#include "aws_iot_queue.h" + +/** + * @def AwsIotShadow_Assert( expression ) + * @brief Assertion macro for the Shadow library. + * + * Set @ref AWS_IOT_SHADOW_ENABLE_ASSERTS to `1` to enable assertions in the Shadow + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 + #ifndef AwsIotShadow_Assert + #include + #define AwsIotShadow_Assert( expression ) assert( expression ) + #endif +#else + #define AwsIotShadow_Assert( expression ) +#endif + +/* Configure logs for Shadow functions. */ +#ifdef AWS_IOT_LOG_LEVEL_SHADOW + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_SHADOW +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "Shadow" ) +#include "aws_iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #include "platform/aws_iot_static_memory.h" + + /** + * @brief Allocate a #_shadowOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotShadow_MallocOperation + #define AwsIotShadow_MallocOperation AwsIot_MallocShadowOperation + #endif + + /** + * @brief Free a #_shadowOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotShadow_FreeOperation + #define AwsIotShadow_FreeOperation AwsIot_FreeShadowOperation + #endif + + /** + * @brief Allocate a buffer for a short string, used for topic names or client + * tokens. This function should have the same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotShadow_MallocString + #define AwsIotShadow_MallocString AwsIot_MallocMessageBuffer + #endif + + /** + * @brief Free a string. This function should have the same signature as + * [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotShadow_FreeString + #define AwsIotShadow_FreeString AwsIot_FreeMessageBuffer + #endif + + /** + * @brief Allocate a #_shadowSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotShadow_MallocSubscription + #define AwsIotShadow_MallocSubscription AwsIot_MallocShadowSubscription + #endif + + /** + * @brief Free a #_shadowSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotShadow_FreeSubscription + #define AwsIotShadow_FreeSubscription AwsIot_FreeShadowSubscription + #endif +#else /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ + #include + + #ifndef AwsIotShadow_MallocOperation + #define AwsIotShadow_MallocOperation malloc + #endif + + #ifndef AwsIotShadow_FreeOperation + #define AwsIotShadow_FreeOperation free + #endif + + #ifndef AwsIotShadow_MallocString + #define AwsIotShadow_MallocString malloc + #endif + + #ifndef AwsIotShadow_FreeString + #define AwsIotShadow_FreeString free + #endif + + #ifndef AwsIotShadow_MallocSubscription + #define AwsIotShadow_MallocSubscription malloc + #endif + + #ifndef AwsIotShadow_FreeSubscription + #define AwsIotShadow_FreeSubscription free + #endif +#endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + #define AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS ( 5000 ) +#endif +/** @endcond */ + +/** + * @brief The longest Thing Name accepted by the Shadow service, per the [AWS IoT + * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). + */ +#define _MAX_THING_NAME_LENGTH ( 128 ) + +/** + * @brief The number of currently available Shadow operations. + * + * The 3 Shadow operations are DELETE, GET, and UPDATE. + */ +#define _SHADOW_OPERATION_COUNT ( 3 ) + +/** + * @brief The number of currently available Shadow callbacks. + * + * The 2 Shadow callbacks are `update/delta` (AKA "Delta") and `/update/documents` + * (AKA "Updated"). + */ +#define _SHADOW_CALLBACK_COUNT ( 2 ) + +/** + * @brief The common prefix of all Shadow MQTT topics, per the [AWS IoT Shadow + * spec](https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html). + */ +#define _SHADOW_TOPIC_PREFIX "$aws/things/" + +/** + * @brief The length of #_SHADOW_TOPIC_PREFIX. + */ +#define _SHADOW_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_TOPIC_PREFIX ) - 1 ) ) + +/** + * @brief The string representing a Shadow DELETE operation in a Shadow MQTT topic. + */ +#define _SHADOW_DELETE_OPERATION_STRING "/shadow/delete" + +/** + * @brief The length of #_SHADOW_DELETE_OPERATION_STRING. + */ +#define _SHADOW_DELETE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_DELETE_OPERATION_STRING ) - 1 ) ) + +/** + * @brief The string representing a Shadow GET operation in a Shadow MQTT topic. + */ +#define _SHADOW_GET_OPERATION_STRING "/shadow/get" + +/** + * @brief The length of #_SHADOW_GET_OPERATION_STRING. + */ +#define _SHADOW_GET_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_GET_OPERATION_STRING ) - 1 ) ) + +/** + * @brief The string representing a Shadow UPDATE operation in a Shadow MQTT topic. + */ +#define _SHADOW_UPDATE_OPERATION_STRING "/shadow/update" + +/** + * @brief The length of #_SHADOW_UPDATE_OPERATION_STRING. + */ +#define _SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_UPDATE_OPERATION_STRING ) - 1 ) ) + +/** + * @brief The suffix for a Shadow operation "accepted" topic. + */ +#define _SHADOW_ACCEPTED_SUFFIX "/accepted" + +/** + * @brief The length of #_SHADOW_ACCEPTED_SUFFIX. + */ +#define _SHADOW_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_ACCEPTED_SUFFIX ) - 1 ) ) + +/** + * @brief The suffix for a Shadow operation "rejected" topic. + */ +#define _SHADOW_REJECTED_SUFFIX "/rejected" + +/** + * @brief The length of #_SHADOW_REJECTED_SUFFIX. + */ +#define _SHADOW_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_REJECTED_SUFFIX ) - 1 ) ) + +/** + * @brief The suffix for a Shadow delta topic. + */ +#define _SHADOW_DELTA_SUFFIX "/delta" + +/** + * @brief The length of #_SHADOW_DELTA_SUFFIX. + */ +#define _SHADOW_DELTA_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_DELTA_SUFFIX ) - 1 ) ) + +/** + * @brief The suffix for a Shadow updated topic. + */ +#define _SHADOW_UPDATED_SUFFIX "/documents" + +/** + * @brief The length of #_SHADOW_UPDATED_SUFFIX. + */ +#define _SHADOW_UPDATED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_UPDATED_SUFFIX ) - 1 ) ) + +/** + * @brief The length of the longest Shadow suffix. + */ +#define _SHADOW_LONGEST_SUFFIX_LENGTH _SHADOW_UPDATED_SUFFIX_LENGTH + +/** + * @brief The JSON key used to represent client tokens in a Shadow update document. + */ +#define _CLIENT_TOKEN_KEY "clientToken" + +/** + * @brief The length of #_CLIENT_TOKEN_KEY. + */ +#define _CLIENT_TOKEN_KEY_LENGTH ( sizeof( _CLIENT_TOKEN_KEY ) - 1 ) + +/** + * @brief The longest client token accepted by the Shadow service, per AWS IoT + * service limits. + */ +#define _MAX_CLIENT_TOKEN_LENGTH ( 64 ) + +/** + * @brief A flag to represent persistent subscriptions in a Shadow subscriptions + * object. + * + * Its value is negative to distinguish it from valid subscription counts, which + * are 0 or positive. + */ +#define _PERSISTENT_SUBSCRIPTION ( -1 ) + +/*----------------------- Shadow internal data types ------------------------*/ + +/** + * @brief The offset of `link` in #_shadowOperation_t. + */ +#define _SHADOW_OPERATION_LINK_OFFSET AwsIotLink_Offset( _shadowOperation_t, link ) + +/** + * @brief The offset of `link` in #_shadowSubscription_t. + */ +#define _SHADOW_SUBSCRIPTION_LINK_OFFSET AwsIotLink_Offset( _shadowSubscription_t, link ) + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declarations. + */ +struct _shadowOperation; +struct _shadowSubscription; +/** @endcond */ + +/** + * @brief Function pointer representing an MQTT timed operation. + * + * Currently, this is used to represent @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + */ +typedef AwsIotMqttError_t ( * _mqttOperationFunction_t )( AwsIotMqttConnection_t, + const AwsIotMqttSubscription_t * const, + size_t, + uint32_t, + uint64_t ); + +/** + * @brief Function pointer representing an MQTT library callback function. + */ +typedef void ( * _mqttCallbackFunction_t )( void *, + AwsIotMqttCallbackParam_t * const ); + +/** + * @brief Enumerations representing each of the Shadow library's API functions. + */ +typedef enum _shadowOperationType +{ + /* Shadow operations. */ + _SHADOW_DELETE = 0, /**< @ref shadow_function_delete */ + _SHADOW_GET = 1, /**< @ref shadow_function_get */ + _SHADOW_UPDATE = 2, /**< @ref shadow_function_update */ + + /* Shadow callbacks. */ + _SET_DELTA_CALLBACK = 3, /**< @ref shadow_function_setdeltacallback */ + _SET_UPDATED_CALLBACK = 4 /**< @ref shadow_function_setupdatedcallback */ +} _shadowOperationType_t; + +/** + * @brief Enumerations representing each of the Shadow callback functions. + */ +typedef enum _shadowCallbackType +{ + _DELTA_CALLBACK = 0, /**< Delta callback. */ + _UPDATED_CALLBACK = 1 /**< Updated callback. */ +} _shadowCallbackType_t; + +/** + * @brief Enumerations representing each of the statuses that may be parsed + * from a Shadow status topic. + */ +typedef enum _shadowOperationStatus +{ + _SHADOW_ACCEPTED = 0, /**< Shadow operation accepted. */ + _SHADOW_REJECTED = 1, /**< Shadow operation rejected. */ + _UNKNOWN_STATUS = 2 /**< Parsed value matched neither accepted nor rejected. */ +} _shadowOperationStatus_t; + +/** + * @brief Internal structure representing a single Shadow operation (DELETE, + * GET, or UPDATE). + * + * A list of these structures keeps track of all in-progress Shadow operations. + */ +typedef struct _shadowOperation +{ + AwsIotLink_t link; /**< @brief List link member. */ + + /* Basic operation information. */ + _shadowOperationType_t type; /**< @brief Operation type. */ + uint32_t flags; /**< @brief Flags passed to operation API function. */ + AwsIotShadowError_t status; /**< @brief Status of operation. */ + + AwsIotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ + struct _shadowSubscription * pSubscription; /**< @brief Shadow subscriptions object associated with this operation. */ + + union + { + /* Members valid only for a GET operation. */ + struct + { + /** + * @brief Function to allocate memory for an incoming Shadow document. + * + * Only used when the flag #AWS_IOT_SHADOW_FLAG_WAITABLE is set. + */ + void *( *mallocDocument )( size_t ); + + const char * pDocument; /**< @brief Retrieved Shadow document. */ + size_t documentLength; /**< @brief Length of retrieved Shadow document. */ + } get; + + /* Members valid only for an UPDATE operation. */ + struct + { + const char * pClientToken; /**< @brief Client token in update document. */ + size_t clientTokenLength; /**< @brief Length of client token. */ + } update; + }; + + /* How to notify of an operation's completion. */ + union + { + AwsIotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref shadow_function_wait. */ + AwsIotShadowCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ + } notify; /**< @brief How to notify of an operation's completion. */ +} _shadowOperation_t; + +/** + * @brief Represents a Shadow subscriptions object. + * + * These structures are stored in a list. + */ +typedef struct _shadowSubscription +{ + AwsIotLink_t link; /**< @brief List link member. */ + + int references[ _SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ + AwsIotShadowCallbackInfo_t callbacks[ _SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ + + /** + * @brief Buffer allocated for removing Shadow topics. + * + * This buffer is pre-allocated to ensure that memory is available when + * unsubscribing. + */ + char * pTopicBuffer; + + size_t thingNameLength; /**< @brief Length of Thing Name. */ + char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ +} _shadowSubscription_t; + +/* Declarations of names printed in logs. */ +#if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + extern const char * const _pAwsIotShadowOperationNames[]; + extern const char * const _pAwsIotShadowCallbackNames[]; +#endif + +/* Declarations of variables for internal Shadow files. */ +extern uint64_t _AwsIotShadowMqttTimeoutMs; +extern AwsIotList_t _AwsIotShadowPendingOperations; +extern AwsIotList_t _AwsIotShadowSubscriptions; + +/*----------------------- Shadow operation functions ------------------------*/ + +/** + * @brief Create a record for a new in-progress Shadow operation. + * + * @param[out] pNewOperation Set to point to the new operation on success. + * @param[in] operation The type of Shadow operation. + * @param[in] flags Flags variables passed to a user-facing Shadow function. + * @param[in] pCallbackInfo User-provided callback function and parameter. + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY + */ +AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** const pNewOperation, + _shadowOperationType_t operation, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo ); + +/** + * @brief Free resources used to record a Shadow operation. This is called when + * the operation completes. + * + * @param[in] pData The operation which completed. This parameter is of type + * `void*` for compatibility with @ref list_function_removeallmatches. + */ +void AwsIotShadowInternal_DestroyOperation( void * pData ); + +/** + * @brief Fill a buffer with a Shadow topic. + * + * @param[in] type One of: DELETE, GET, UPDATE. + * @param[in] pThingName Thing Name to place in the topic. + * @param[in] thingNameLength Length of `pThingName`. + * @param[out] pTopicBuffer Address of the buffer for the Shadow topic. If the + * pointer at this address is `NULL`, this function will allocate a new buffer; + * otherwise, it will use the provided buffer. + * @param[out] pOperationTopicLength Length of the Shadow operation topic (excluding + * any suffix) placed in `pTopicBuffer`. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must be large enough to accommodate the full Shadow topic, plus + * #_SHADOW_LONGEST_SUFFIX_LENGTH. + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. This function + * will not return #AWS_IOT_SHADOW_NO_MEMORY when a buffer is provided. + */ +AwsIotShadowError_t AwsIotShadowInternal_GenerateShadowTopic( _shadowOperationType_t type, + const char * const pThingName, + size_t thingNameLength, + char ** const pTopicBuffer, + uint16_t * const pOperationTopicLength ); + +/** + * @brief Process a Shadow operation by sending the necessary MQTT packets. + * + * @param[in] mqttConnection The MQTT connection to use. + * @param[in] pThingName Thing Name for the Shadow operation. + * @param[in] thingNameLength Length of `pThingName`. + * @param[in] pOperation Operation data to process. + * @param[in] pDocumentInfo Information on the Shadow document for GET or UPDATE + * operations. + * + * @return #AWS_IOT_SHADOW_STATUS_PENDING on success. On error, one of + * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. + */ +AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + _shadowOperation_t * const pOperation, + const AwsIotShadowDocumentInfo_t * const pDocumentInfo ); + +/** + * @brief Notify of a completed Shadow operation. + * + * @param[in] pOperation The operation which completed. + * + * Depending on the parameters passed to a user-facing Shadow function, the + * notification will cause @ref shadow_function_wait to return or invoke a + * user-provided callback. + */ +void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ); + +/*---------------------- Shadow subscription functions ----------------------*/ + +/** + * @brief Find a Shadow subscription object. Creates a new subscription object + * and adds it to the subscription list if not found. + * + * @param[in] pThingName Thing Name in the subscription object. + * @param[in] thingNameLength Length of `pThingName`. + * + * @return Pointer to a Shadow subscription object, either found or newly + * allocated. Returns `NULL` if no subscription object is found and a new + * subscription object could not be allocated. + * + * @note This function should be called with the subscription list mutex locked. + */ +_shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * const pThingName, + size_t thingNameLength ); + +/** + * @brief Remove a Shadow subscription object from the subscription list if + * unreferenced. + * + * @param[in] pSubscription Subscription object to check. If this object has no + * active references, it is removed from the subscription list. + * @param[out] pRemovedSubscription Removed subscription object, if any. Optional; + * pass `NULL` to ignore. If not `NULL`, this parameter will be set to the removed + * subscription and that subscription will not be destroyed. + * + * @note This function should be called with the subscription list mutex locked. + */ +void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSubscription, + _shadowSubscription_t ** const pRemovedSubscription ); + +/** + * @brief Free resources used for a Shadow subscription object. + * + * @param[in] pData The subscription object to destroy. This parameter is of type + * `void*` for compatibility with @ref list_function_removeallmatches. + */ +void AwsIotShadowInternal_DestroySubscription( void * pData ); + +/** + * @brief Increment the reference count of a Shadow subscriptions object. + * + * Also adds MQTT subscriptions if necessary. + * + * @param[in] pOperation The operation for which the reference count should be + * incremented. + * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if + * subscriptions need to be added. + * @param[in] operationTopicLength The length of the operation topic in `pTopicBuffer`. + * @param[in] callback MQTT callback function for when this operation completes. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must already contain the Shadow operation topic, plus enough space for the + * status suffix. + * + * @return #AWS_IOT_SHADOW_STATUS_PENDING on success. On error, one of + * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. + * + * @note This function should be called with the subscription list mutex locked. + */ +AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + uint16_t operationTopicLength, + _mqttCallbackFunction_t callback ); + +/** + * @brief Decrement the reference count of a Shadow subscriptions object. + * + * Also removed MQTT subscriptions and deletes the subscription object if necessary. + * + * @param[in] pOperation The operation for which the reference count should be + * decremented. + * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if + * subscriptions need to be removed. + * @param[out] pRemovedSubscription Set to point to a removed subscription. + * Optional; pass `NULL` to ignore. If not `NULL`, this function will not destroy + * a removed subscription. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must be large enough to accommodate the full Shadow topic, plus + * #_SHADOW_LONGEST_SUFFIX_LENGTH. + * + * @note This function should be called with the subscription list mutex locked. + */ +void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + _shadowSubscription_t ** const pRemovedSubscription ); + +/*------------------------- Shadow parser functions -------------------------*/ + +/** + * @brief Parse the operation status (accepted or rejected) from a Shadow topic. + * + * @param[in] pTopicName The topic to parse. + * @param[in] topicNameLength The length of `pTopicName`. + * + * @return Any #_shadowOperationStatus_t. + */ +_shadowOperationStatus_t AwsIotShadowInternal_ParseShadowStatus( const char * const pTopicName, + size_t topicNameLength ); + +/** + * @brief Parse the Thing Name from a Shadow topic. + * + * @param[in] pTopicName The topic to parse. + * @param[in] topicNameLength The length of `pTopicName`. + * @param[out] pThingName Set to point to the Thing Name. + * @param[out] pThingNameLength Set to the length of the Thing Name. + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_RESPONSE. + */ +AwsIotShadowError_t AwsIotShadowInternal_ParseThingName( const char * const pTopicName, + uint16_t topicNameLength, + const char ** const pThingName, + size_t * const pThingNameLength ); + +/** + * @brief Parse a Shadow error document. + * + * @param[in] pErrorDocument The error document to parse. + * @param[in] errorDocumentLength The length of `pErrorDocument`. + * + * @return One of the #AwsIotShadowError_t ranging from 400 to 500 on success. + * #AWS_IOT_SHADOW_BAD_RESPONSE on error. + */ +AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const pErrorDocument, + size_t errorDocumentLength ); + +#endif /* ifndef _AWS_IOT_SHADOW_INTERNAL_H_ */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt new file mode 100644 index 0000000000..7990a2d965 --- /dev/null +++ b/lib/source/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# Common libraries source files. +add_library( awsiotcommon SHARED + aws_iot_json_utils.c + aws_iot_logging.c + aws_iot_queue.c ) + +# Library version. +set_target_properties( awsiotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Library public headers. +set_target_properties( awsiotcommon PROPERTIES PUBLIC_HEADER + "../../include/aws_iot_json_utils.h;../../include/aws_iot_logging_setup.h;../../include/aws_iot_queue.h" ) + +# Library private headers. +set_target_properties( awsiotcommon PROPERTIES PRIVATE_HEADER + "../../include/private/aws_iot_logging.h" ) + +# Link required libraries. +target_link_libraries( awsiotcommon awsiotplatform ) + +# Link Unity test framework when building tests. +if( ${AWS_IOT_BUILD_TESTS} ) + target_link_libraries( awsiotcommon unity ) +endif() diff --git a/lib/source/common/aws_iot_json_utils.c b/lib/source/common/aws_iot_json_utils.c new file mode 100644 index 0000000000..a5a0dcf0db --- /dev/null +++ b/lib/source/common/aws_iot_json_utils.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_json_utils.c + * @brief Implements the functions in aws_iot_json_utils.h + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* JSON utilities include. */ +#include "aws_iot_json_utils.h" + +/*-----------------------------------------------------------*/ + +bool AwsIotJsonUtils_FindJsonValue( const char * const pJsonDocument, + size_t jsonDocumentLength, + const char * const pJsonKey, + size_t jsonKeyLength, + const char ** const pJsonValue, + size_t * const pJsonValueLength ) +{ + size_t i = 0; + size_t jsonValueLength = 0; + char openCharacter = '\0', closeCharacter = '\0'; + int nestingLevel = 0; + + /* Ensure the JSON document is long enough to contain the key/value pair. At + * the very least, a JSON key/value pair must contain the key and the 6 + * characters {":""} */ + if( jsonDocumentLength < jsonKeyLength + 6 ) + { + return false; + } + + /* Search the characters in the JSON document for the key. The end of the JSON + * document does not have to be searched once too few characters remain to hold a + * value. */ + while( i < jsonDocumentLength - jsonKeyLength - 5 ) + { + /* If the first character in the key is found and there's an unescaped double + * quote after the key length, do a string compare for the key. */ + if( ( pJsonDocument[ i ] == pJsonKey[ 0 ] ) && + ( pJsonDocument[ i + jsonKeyLength ] == '\"' ) && + ( pJsonDocument[ i + jsonKeyLength - 1 ] != '\\' ) && + ( strncmp( pJsonDocument + i, + pJsonKey, + jsonKeyLength ) == 0 ) ) + { + /* Key found; this is a potential match. */ + + /* Skip the characters in the JSON key and closing double quote. */ + i += jsonKeyLength + 1; + + /* Skip all whitespace characters between the closing " and the : */ + while( pJsonDocument[ i ] == ' ' || + pJsonDocument[ i ] == '\n' || + pJsonDocument[ i ] == '\r' || + pJsonDocument[ i ] == '\t' ) + { + i++; + + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + } + + /* The character immediately following a key (and any whitespace) must be a : + * If it's another character, then this string is a JSON value; skip it. */ + if( pJsonDocument[ i ] != ':' ) + { + continue; + } + else + { + /* Skip the : */ + i++; + } + + /* Skip all whitespace characters between : and the first character in the value. */ + while( pJsonDocument[ i ] == ' ' || + pJsonDocument[ i ] == '\n' || + pJsonDocument[ i ] == '\r' || + pJsonDocument[ i ] == '\t' ) + { + i++; + + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + } + + /* Value found. Set the output parameter. */ + if( pJsonValue != NULL ) + { + *pJsonValue = pJsonDocument + i; + } + + /* Calculate the value's length. */ + switch( pJsonDocument[ i ] ) + { + /* Calculate length of a JSON string. */ + case '\"': + /* Include the length of the opening and closing double quotes. */ + jsonValueLength = 2; + + /* Skip the opening double quote. */ + i++; + + /* Add the length of all characters in the JSON string. */ + while( pJsonDocument[ i ] != '\"' ) + { + /* Ignore escaped double quotes. */ + if( ( pJsonDocument[ i ] == '\\' ) && + ( i + 1 < jsonDocumentLength ) && + ( pJsonDocument[ i + 1 ] == '\"' ) ) + { + /* Skip the characters \" */ + i += 2; + jsonValueLength += 2; + } + else + { + /* Add the length of a single character. */ + i++; + jsonValueLength++; + } + + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + } + + break; + + /* Set the matching opening and closing characters of a JSON object or array. + * The length calculation is performed below. */ + case '{': + openCharacter = '{'; + closeCharacter = '}'; + break; + + case '[': + openCharacter = '['; + closeCharacter = ']'; + break; + + /* Calculate the length of a JSON primitive. */ + default: + + /* Skip the characters in the JSON value. The JSON value ends with a , or } */ + while( pJsonDocument[ i ] != ',' && + pJsonDocument[ i ] != '}' ) + { + /* Any whitespace before a , or } means the JSON document is invalid. */ + if( ( pJsonDocument[ i ] == ' ' ) || + ( pJsonDocument[ i ] == '\n' ) || + ( pJsonDocument[ i ] == '\r' ) || + ( pJsonDocument[ i ] == '\t' ) ) + { + return false; + } + + i++; + jsonValueLength++; + + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + } + + break; + } + + /* Calculate the length of a JSON object or array. */ + if( ( openCharacter != '\0' ) && ( closeCharacter != '\0' ) ) + { + /* Include the length of the opening and closing characters. */ + jsonValueLength = 2; + + /* Skip the opening character. */ + i++; + + /* Add the length of all characters in the JSON object or array. This + * includes the length of nested objects. */ + while( pJsonDocument[ i ] != closeCharacter || + ( pJsonDocument[ i ] == closeCharacter && nestingLevel != 0 ) ) + { + /* An opening character starts a nested object. */ + if( pJsonDocument[ i ] == openCharacter ) + { + nestingLevel++; + } + /* A closing character ends a nested object. */ + else if( pJsonDocument[ i ] == closeCharacter ) + { + nestingLevel--; + } + + i++; + jsonValueLength++; + + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + } + } + + /* JSON value length calculated; set the output parameter. */ + if( pJsonValueLength != NULL ) + { + *pJsonValueLength = jsonValueLength; + } + + return true; + } + + i++; + } + + return false; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/common/aws_iot_logging.c b/lib/source/common/aws_iot_logging.c new file mode 100644 index 0000000000..a7f44ca027 --- /dev/null +++ b/lib/source/common/aws_iot_logging.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_logging.c + * @brief Implementation of logging functions from aws_iot_logging.h + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Platform clock include. */ +#include "platform/aws_iot_clock.h" + +/* AWS IoT library includes. */ +#include "private/aws_iot_logging.h" + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int sprintf( char *, const char *, ... ); +extern int snprintf( char *, size_t, const char *, ... ); +extern int vsnprintf( char *, size_t, const char *, va_list ); +/** @endcond */ + +/*-----------------------------------------------------------*/ + +/* This implementation assumes the following values for the log level constants. + * Ensure that the values have not been modified. */ +#if AWS_IOT_LOG_NONE != 0 + #error "AWS_IOT_LOG_NONE must be 0." +#endif +#if AWS_IOT_LOG_ERROR != 1 + #error "AWS_IOT_LOG_ERROR must be 1." +#endif +#if AWS_IOT_LOG_WARN != 2 + #error "AWS_IOT_LOG_WARN must be 2." +#endif +#if AWS_IOT_LOG_INFO != 3 + #error "AWS_IOT_LOG_INFO must be 3." +#endif +#if AWS_IOT_LOG_DEBUG != 4 + #error "AWS_IOT_LOG_DEBUG must be 4." +#endif + +/** + * @def AwsIotLogging_Puts( message ) + * @brief Function the logging library uses to print a line. + * + * This function can be set by using a define. By default, the standard library + * [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) + * function is used. + */ +#ifndef AwsIotLogging_Puts + /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + */ + extern int puts( const char * ); + /** @endcond */ + + #define AwsIotLogging_Puts puts +#endif + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + /* Static memory allocation header. */ + #include "platform/aws_iot_static_memory.h" + + /** + * @brief Allocate a new logging buffer. This function must have the same + * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotLogging_Malloc + #define AwsIotLogging_Malloc AwsIot_MallocMessageBuffer + #endif + + /** + * @brief Free a logging buffer. This function must have the same signature + * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotLogging_Free + #define AwsIotLogging_Free AwsIot_FreeMessageBuffer + #endif + + /** + * @brief Get the size of a logging buffer. Statically-allocated buffers + * should all have the same size. + */ + #ifndef AwsIotLogging_StaticBufferSize + #define AwsIotLogging_StaticBufferSize AwsIot_MessageBufferSize + #endif +#else /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ + #ifndef AwsIotLogging_Malloc + #include + #define AwsIotLogging_Malloc malloc + #endif + + #ifndef AwsIotLogging_Free + #include + #define AwsIotLogging_Free free + #endif +#endif /* if AWS_IOT_STATIC_MEMORY_ONLY */ + +/** + * @brief A guess of the maximum length of a timestring. + * + * There's no way for this logging library to know the length of a timestring + * before it's generated. Therefore, the logging library will assume a maximum + * length of any timestring it may get. This value should be generous enough + * to accommodate the vast majority of timestrings. + * + * @see @ref platform_clock_function_gettimestring + */ +#define _MAX_TIMESTRING_LENGTH ( 64 ) + +/** + * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate + * `[]` and a null-terminator. + */ +#define _MAX_LOG_LEVEL_LENGTH ( 8 ) + +/** + * @brief How many bytes @ref logging_function_genericprintbuffer should output on + * each line. + */ +#define _BYTES_PER_LINE ( 16 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Lookup table for log levels. + * + * Converts one of the @ref logging_constants_levels to a string. + */ +static const char * const _pLogLevelStrings[ 5 ] = +{ + "", /* AWS_IOT_LOG_NONE */ + "ERROR", /* AWS_IOT_LOG_ERROR */ + "WARN ", /* AWS_IOT_LOG_WARN */ + "INFO ", /* AWS_IOT_LOG_INFO */ + "DEBUG" /* AWS_IOT_LOG_DEBUG */ +}; + +/*-----------------------------------------------------------*/ + +#if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) +static bool _reallocLoggingBuffer( void ** pOldBuffer, + size_t newSize, + size_t oldSize ) +{ + /* Allocate a new, larger buffer. */ + void * pNewBuffer = AwsIotLogging_Malloc( newSize ); + + /* Ensure that memory allocation succeeded. */ + if( pNewBuffer == NULL ) + { + return false; + } + + /* Copy the data from the old buffer to the new buffer. */ + ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); + + /* Free the old buffer and update the pointer. */ + AwsIotLogging_Free( *pOldBuffer ); + *pOldBuffer = pNewBuffer; + + return true; +} +#endif /* if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) */ + +/*-----------------------------------------------------------*/ + +void AwsIotLogGeneric( int libraryLogSetting, + const char * const pLibraryName, + int messageLevel, + const AwsIotLogConfig_t * const pLogConfig, + const char * const pFormat, + ... ) +{ + int requiredMessageSize = 0; + size_t bufferSize = 0, + bufferPosition = 0, timestringLength = 0; + char * pLoggingBuffer = NULL; + va_list args; + + /* If the library's log level setting is lower than the message level, + * return without doing anything. */ + if( ( messageLevel == 0 ) || ( messageLevel > libraryLogSetting ) ) + { + return; + } + + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) + { + /* Add length of log level if requested. */ + bufferSize += _MAX_LOG_LEVEL_LENGTH; + } + + /* Estimate the amount of buffer needed for this log message. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) + { + /* Add size of library name if requested. Add 2 to accomodate "[]". */ + bufferSize += strlen( pLibraryName ) + 2; + } + + if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) + { + /* Add length of timestring if requested. */ + bufferSize += _MAX_TIMESTRING_LENGTH; + } + + /* Add 64 as an initial (arbitrary) guess for the length of the message. */ + bufferSize += 64; + + /* In static memory mode, check that the log message will fit in the a + * static buffer. */ + #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + if( bufferSize >= AwsIotLogging_StaticBufferSize() ) + { + /* If the static buffers are likely too small to fit the log message, + * return. */ + return; + } + + /* Otherwise, update the buffer size to the size of a static buffer. */ + bufferSize = AwsIotLogging_StaticBufferSize(); + #endif + + /* Allocate memory for the logging buffer. */ + pLoggingBuffer = ( char * ) AwsIotLogging_Malloc( bufferSize ); + + if( pLoggingBuffer == NULL ) + { + return; + } + + /* Print the message log level if requested. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) + { + /* Ensure that message level is valid. */ + if( ( messageLevel >= AWS_IOT_LOG_NONE ) && ( messageLevel <= AWS_IOT_LOG_DEBUG ) ) + { + /* Add the log level string to the logging buffer. */ + requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + "[%s]", + _pLogLevelStrings[ messageLevel ] ); + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + AwsIotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Update the buffer position. */ + bufferPosition += ( size_t ) requiredMessageSize; + } + } + + /* Print the library name if requested. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) + { + /* Add the library name to the logging buffer. */ + requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + "[%s]", + pLibraryName ); + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + AwsIotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Update the buffer position. */ + bufferPosition += ( size_t ) requiredMessageSize; + } + + /* Print the timestring if requested. */ + if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) + { + /* Add the opening '[' enclosing the timestring. */ + pLoggingBuffer[ bufferPosition ] = '['; + bufferPosition++; + + /* Generate the timestring and add it to the buffer. */ + if( AwsIotClock_GetTimestring( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + ×tringLength ) == true ) + { + /* If the timestring was successfully generated, add the closing "]". */ + bufferPosition += timestringLength; + pLoggingBuffer[ bufferPosition ] = ']'; + bufferPosition++; + } + else + { + /* Sufficient memory for a timestring should have been allocated. A timestring + * probably failed to generate due to a clock read error; remove the opening '[' + * from the logging buffer. */ + bufferPosition--; + pLoggingBuffer[ bufferPosition ] = '\0'; + } + } + + /* Add a padding space between the last closing ']' and the message, unless + * the logging buffer is empty. */ + if( bufferPosition > 0 ) + { + pLoggingBuffer[ bufferPosition ] = ' '; + bufferPosition++; + } + + va_start( args, pFormat ); + + /* Add the log message to the logging buffer. */ + requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + pFormat, + args ); + + va_end( args ); + + /* If the logging buffer was too small to fit the log message, reallocate + * a larger logging buffer. */ + if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition ) + { + #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + + /* There's no point trying to allocate a larger static buffer. Return + * immediately. */ + AwsIotLogging_Free( pLoggingBuffer ); + + return; + #else + if( _reallocLoggingBuffer( ( void ** ) &pLoggingBuffer, + ( size_t ) requiredMessageSize + bufferPosition + 1, + bufferSize ) == false ) + { + /* If buffer reallocation failed, return. */ + AwsIotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Reallocation successful, update buffer size. */ + bufferSize = ( size_t ) requiredMessageSize + bufferPosition + 1; + + /* Add the log message to the buffer. Now that the buffer has been + * reallocated, this should succeed. */ + va_start( args, pFormat ); + requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + pFormat, + args ); + va_end( args ); + #endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ + } + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + AwsIotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Print the logging buffer to stdout. */ + AwsIotLogging_Puts( pLoggingBuffer ); + + /* Free the logging buffer. */ + AwsIotLogging_Free( pLoggingBuffer ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, + const char * const pHeader, + const uint8_t * const pBuffer, + size_t bufferSize ) +{ + size_t i = 0, offset = 0; + + /* Allocate memory to hold each line of the log message. Since each byte + * of pBuffer is printed in 4 characters (2 digits, a space, and a null- + * terminator), the size of each line is 4 * _BYTES_PER_LINE. */ + char * pMessageBuffer = AwsIotLogging_Malloc( 4 * _BYTES_PER_LINE ); + + /* Exit if no memory is available. */ + if( pMessageBuffer == NULL ) + { + return; + } + + /* Print pHeader before printing pBuffer. */ + if( pHeader != NULL ) + { + AwsIotLogGeneric( AWS_IOT_LOG_DEBUG, + pLibraryName, + AWS_IOT_LOG_DEBUG, + NULL, + pHeader ); + } + + /* Print each byte in pBuffer. */ + for( i = 0; i < bufferSize; i++ ) + { + /* Print a line if _BYTES_PER_LINE is reached. But don't print a line + * at the beginning (when i=0). */ + if( ( i % _BYTES_PER_LINE == 0 ) && ( i != 0 ) ) + { + AwsIotLogging_Puts( pMessageBuffer ); + + /* Reset offset so that pMessageBuffer is filled from the beginning. */ + offset = 0; + } + + /* Print a single byte into pMessageBuffer. This call to sprintf won't + * cause any buffer overflows because it always prints 4 characters per + * invocation and sufficient memory has been allocated. */ + ( void ) sprintf( pMessageBuffer + offset, "%02x ", pBuffer[ i ] ); + + /* Move the offset where the next character is printed. */ + offset += 3; + } + + /* Print the final line of bytes. This line isn't printed by the for-loop above. */ + AwsIotLogging_Puts( pMessageBuffer ); + + /* Free memory used by this function. */ + AwsIotLogging_Free( pMessageBuffer ); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/common/aws_iot_queue.c b/lib/source/common/aws_iot_queue.c new file mode 100644 index 0000000000..7ee9647d3f --- /dev/null +++ b/lib/source/common/aws_iot_queue.c @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_queue.c + * @brief Implements the functions in aws_iot_queue.h + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Queue include. */ +#include "aws_iot_queue.h" + +/** + * @def AwsIotQueue_Assert( expression ) + * @brief Assertion macro for the queue library. + * + * Set @ref AWS_IOT_QUEUE_ENABLE_ASSERTS to `1` to enable assertions in the queue + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if AWS_IOT_QUEUE_ENABLE_ASSERTS == 1 + #ifndef AwsIotQueue_Assert + #include + #define AwsIotQueue_Assert( expression ) assert( expression ) + #endif +#else + #define AwsIotQueue_Assert( expression ) +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Remove a single element from the queue. + * + * This function should be called within a critical section. + * @param[in] pQueue The queue holding the element to remove. + * @param[in] pLink The element to remove. + */ +static void _queueRemove( AwsIotQueue_t * const pQueue, + AwsIotLink_t * const pLink ); + +/*-----------------------------------------------------------*/ + +static void _queueRemove( AwsIotQueue_t * const pQueue, + AwsIotLink_t * const pLink ) +{ + /* This function should not be called on an empty queue. */ + AwsIotQueue_Assert( pQueue->pHead != NULL && pQueue->pTail != NULL ); + + /* Check if the head of the queue is being removed. */ + if( pLink == pQueue->pHead ) + { + /* Queue head should have NULL previous pointer. */ + AwsIotQueue_Assert( pQueue->pHead->pPrevious == NULL ); + + /* Move the queue head. */ + pQueue->pHead = pQueue->pHead->pNext; + + /* Check if the queue is now empty. */ + if( pQueue->pHead == NULL ) + { + /* If the queue is empty, set the tail pointer to NULL also. */ + pQueue->pTail = NULL; + } + else + { + /* If the queue is not empty, set the new head's previous pointer + * to NULL. */ + pQueue->pHead->pPrevious = NULL; + } + } + /* Check if the tail of the queue is being removed. */ + else if( pLink == pQueue->pTail ) + { + /* Queue tail should have NULL next pointer. */ + AwsIotQueue_Assert( pQueue->pTail->pNext == NULL ); + + /* Move the queue tail. */ + pQueue->pTail = pQueue->pTail->pPrevious; + + /* Check if the queue is now empty. */ + if( pQueue->pTail == NULL ) + { + /* If the queue is empty, set the head pointer to NULL also. */ + pQueue->pHead = NULL; + } + else + { + /* If the queue is not empty, set the new tail's next pointer to + * NULL. */ + pQueue->pTail->pNext = NULL; + } + } + /* Otherwise, remove from the middle of the queue. */ + else + { + /* Check that pointers in the next and previous elements are correctly + * set. */ + AwsIotQueue_Assert( pLink->pPrevious->pNext == pLink ); + AwsIotQueue_Assert( pLink->pNext->pPrevious == pLink ); + + /* Reassign the pointers in the next and previous elements. */ + pLink->pPrevious->pNext = pLink->pNext; + pLink->pNext->pPrevious = pLink->pPrevious; + } + + /* Clear the next and previous pointers of pLink. */ + pLink->pNext = NULL; + pLink->pPrevious = NULL; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotQueue_Create( AwsIotQueue_t * const pQueue, + const AwsIotQueueNotifyParams_t * const pNotifyParams, + uint32_t maxNotifyThreads ) +{ + /* For a notification queue, at least 1 notify thread must be allowed. */ + if( ( pNotifyParams != NULL ) && ( maxNotifyThreads == 0 ) ) + { + return false; + } + + /* Clear the queue data. This sets all pointers to NULL. */ + ( void ) memset( pQueue, 0x00, sizeof( AwsIotQueue_t ) ); + + /* Create the queue mutex. */ + if( AwsIotMutex_Create( &( pQueue->mutex ) ) == false ) + { + return false; + } + + /* If a notification routine is set, create the thread count semaphore. */ + if( ( pNotifyParams != NULL ) && ( pNotifyParams->notifyRoutine != NULL ) ) + { + if( AwsIotSemaphore_Create( &( pQueue->notifyThreadCount ), + maxNotifyThreads, + maxNotifyThreads ) == false ) + { + AwsIotMutex_Destroy( &( pQueue->mutex ) ); + + return false; + } + + /* Copy the notify params. */ + pQueue->params = *( pNotifyParams ); + } + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotQueue_Destroy( AwsIotQueue_t * const pQueue ) +{ + /* Clean up notification routine. */ + if( pQueue->params.notifyRoutine != NULL ) + { + AwsIotMutex_Lock( &( pQueue->mutex ) ); + + /* Decrement the count of available notification threads to 0, ensuring + * no more notification threads may be created. This also waits for + * termination of any existing notification threads. */ + while( AwsIotSemaphore_GetCount( &( pQueue->notifyThreadCount ) ) > 0 ) + { + AwsIotMutex_Unlock( &( pQueue->mutex ) ); + AwsIotSemaphore_Wait( &( pQueue->notifyThreadCount ) ); + AwsIotMutex_Lock( &( pQueue->mutex ) ); + } + + AwsIotSemaphore_Destroy( &( pQueue->notifyThreadCount ) ); + AwsIotMutex_Unlock( &( pQueue->mutex ) ); + } + + /* Destroy queue mutex. */ + AwsIotMutex_Destroy( &( pQueue->mutex ) ); + + /* Clear all data in the queue. */ + ( void ) memset( pQueue, 0x00, sizeof( AwsIotQueue_t ) ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotQueue_InsertHead( AwsIotQueue_t * const pQueue, + AwsIotLink_t * const pLink ) +{ + bool status = true; + + AwsIotMutex_Lock( &( pQueue->mutex ) ); + + /* Clear the next and previous pointers of pLink. */ + pLink->pNext = NULL; + pLink->pPrevious = NULL; + + /* Check if the queue is currently empty. */ + if( pQueue->pHead == NULL ) + { + /* If the head pointer is NULL, the tail pointer should also be NULL. */ + AwsIotQueue_Assert( pQueue->pTail == NULL ); + + /* Set the head and tail pointer to the new operation. This places the + * first item in the queue. */ + pQueue->pHead = pLink; + pQueue->pTail = pLink; + } + else + { + /* The tail shouldn't be NULL when the head isn't NULL. */ + AwsIotQueue_Assert( pQueue->pTail != NULL ); + + /* If the queue is not empty, insert the new operation at the head + * of the queue. */ + pLink->pNext = pQueue->pHead; + pQueue->pHead->pPrevious = pLink; + pQueue->pHead = pLink; + } + + /* Create a notification thread if this queue has a notify routine set. */ + if( pQueue->params.notifyRoutine != NULL ) + { + if( ( AwsIotSemaphore_TryWait( &( pQueue->notifyThreadCount ) ) == true ) && + ( AwsIot_CreateDetachedThread( pQueue->params.notifyRoutine, + pQueue->params.pNotifyArgument ) == false ) ) + { + _queueRemove( pQueue, pLink ); + AwsIotSemaphore_Post( &( pQueue->notifyThreadCount ) ); + status = false; + } + } + + AwsIotMutex_Unlock( &( pQueue->mutex ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +void * AwsIotQueue_RemoveTail( AwsIotQueue_t * const pQueue, + size_t linkOffset, + bool notifyExitIfEmpty ) +{ + AwsIotLink_t * pResult = NULL; + + AwsIotMutex_Lock( &( pQueue->mutex ) ); + + /* Check if a message is available. */ + if( pQueue->pTail != NULL ) + { + /* If the queue tail isn't NULL, then head shouldn't be NULL. */ + AwsIotQueue_Assert( pQueue->pHead != NULL ); + + /* Set the pointer to the tail, then remove it from the queue. */ + pResult = pQueue->pTail; + _queueRemove( pQueue, pQueue->pTail ); + } + else + { + /* If this is an exiting notification thread, increment the number of + * available notification threads. */ + if( notifyExitIfEmpty == true ) + { + AwsIotSemaphore_Post( &( pQueue->notifyThreadCount ) ); + } + } + + AwsIotMutex_Unlock( &( pQueue->mutex ) ); + + return AwsIotLink_Container( pResult, linkOffset ); +} + +/*-----------------------------------------------------------*/ + +void * AwsIotQueue_RemoveFirstMatch( AwsIotQueue_t * const pQueue, + size_t linkOffset, + void * pArgument, + bool ( *shouldRemove )( void *, void * ) ) +{ + AwsIotLink_t * pResult = NULL; + void * pLinkContainer = NULL; + + AwsIotMutex_Lock( &( pQueue->mutex ) ); + + /* Iterate through the queue to find the first matching element. */ + for( pResult = pQueue->pTail; pResult != NULL; pResult = pResult->pPrevious ) + { + pLinkContainer = AwsIotLink_Container( pResult, linkOffset ); + + if( ( ( shouldRemove == NULL ) && ( pArgument == pLinkContainer ) ) || + ( ( shouldRemove != NULL ) && ( shouldRemove( pArgument, pLinkContainer ) == true ) ) ) + { + /* If a matching element is found, remove it from the queue. */ + _queueRemove( pQueue, pResult ); + break; + } + } + + AwsIotMutex_Unlock( &( pQueue->mutex ) ); + + /* Element found. */ + if( pResult != NULL ) + { + return pLinkContainer; + } + + /* Element not found. */ + return NULL; +} + +/*-----------------------------------------------------------*/ + +void AwsIotQueue_RemoveAllMatches( AwsIotQueue_t * const pQueue, + size_t linkOffset, + void * pArgument, + bool ( *shouldRemove )( void *, void * ), + void ( *freeElement )( void * ) ) +{ + AwsIotLink_t * i = NULL, * pCurrent = NULL; + void * pLinkContainer = NULL; + + AwsIotMutex_Lock( &( pQueue->mutex ) ); + i = pQueue->pHead; + + /* Iterate through the entire queue. Remove and free all matching elements. */ + while( i != NULL ) + { + /* Save a pointer to the current element and move the iterating pointer. */ + pCurrent = i; + i = i->pNext; + + /* Calculate address of link container. */ + pLinkContainer = AwsIotLink_Container( pCurrent, linkOffset ); + + /* Check if the current queue element should be removed. */ + if( ( shouldRemove == NULL ) || + ( shouldRemove( pArgument, pLinkContainer ) == true ) ) + { + /* Remove the matched element from the queue and free it. */ + _queueRemove( pQueue, pCurrent ); + + if( freeElement != NULL ) + { + freeElement( pLinkContainer ); + } + } + } + + AwsIotMutex_Unlock( &( pQueue->mutex ) ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotList_Create( AwsIotList_t * const pList ) +{ + /* Clear the list data. This sets the head pointer to NULL. */ + ( void ) memset( pList, 0x00, sizeof( AwsIotList_t ) ); + + /* Create the list mutex. */ + if( AwsIotMutex_Create( &( pList->mutex ) ) == false ) + { + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotList_Destroy( AwsIotList_t * const pList ) +{ + /* Destroy list mutex. */ + AwsIotMutex_Destroy( &( pList->mutex ) ); + + /* Clear all data in the list. */ + ( void ) memset( pList, 0x00, sizeof( AwsIotList_t ) ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotList_InsertHead( AwsIotList_t * const pList, + AwsIotLink_t * const pLink, + size_t linkOffset ) +{ + /* Link offset is not used when asserts are off. */ + ( void ) linkOffset; + + /* Clear the next and previous pointers of pLink. */ + pLink->pNext = NULL; + pLink->pPrevious = NULL; + + /* Link container must not already be in the list. */ + AwsIotQueue_Assert( AwsIotList_FindFirstMatch( pList->pHead, + linkOffset, + AwsIotLink_Container( pLink, linkOffset ), + NULL ) == NULL ); + + /* Insert the new node at the list head. */ + if( pList->pHead == NULL ) + { + /* Set new head if list is empty. */ + pList->pHead = pLink; + } + else + { + pLink->pNext = pList->pHead; + pList->pHead->pPrevious = pLink; + pList->pHead = pLink; + } +} + +/*-----------------------------------------------------------*/ + +void AwsIotList_InsertSorted( AwsIotList_t * const pList, + AwsIotLink_t * const pLink, + size_t linkOffset, + int ( *compare )( void *, void * ) ) +{ + AwsIotLink_t * i = NULL; + + /* Clear the next and previous pointers of the link. */ + pLink->pNext = NULL; + pLink->pPrevious = NULL; + + /* Link container must not already be in the list. */ + AwsIotQueue_Assert( AwsIotList_FindFirstMatch( pList->pHead, + linkOffset, + AwsIotLink_Container( pLink, linkOffset ), + NULL ) == NULL ); + + /* Insert at head if list is empty. */ + if( pList->pHead == NULL ) + { + pList->pHead = pLink; + } + /* Check if the head should be replaced. */ + else if( compare( AwsIotLink_Container( pLink, linkOffset ), + AwsIotLink_Container( pList->pHead, linkOffset ) ) <= 0 ) + { + pLink->pNext = pList->pHead; + pList->pHead->pPrevious = pLink; + pList->pHead = pLink; + } + else + { + for( i = pList->pHead; i != NULL; i = i->pNext ) + { + /* Insert at queue tail if there's no next element. */ + if( i->pNext == NULL ) + { + pLink->pPrevious = i; + i->pNext = pLink; + break; + } + /* Check the order of the current and next element. */ + else if( ( compare( AwsIotLink_Container( pLink, linkOffset ), + AwsIotLink_Container( i, linkOffset ) ) >= 0 ) && + ( compare( AwsIotLink_Container( pLink, linkOffset ), + AwsIotLink_Container( i->pNext, linkOffset ) ) < 0 ) ) + { + pLink->pNext = i->pNext; + pLink->pPrevious = i; + + i->pNext = pLink; + pLink->pNext->pPrevious = pLink; + break; + } + } + + /* Ensure that pLink was placed in the list. */ + AwsIotQueue_Assert( ( pLink->pNext != NULL ) || + ( pLink->pPrevious != NULL ) ); + } +} + +/*-----------------------------------------------------------*/ + +void * AwsIotList_FindFirstMatch( AwsIotLink_t * const pStartPoint, + size_t linkOffset, + void * pArgument, + bool ( *compare )( void *, void * ) ) +{ + AwsIotLink_t * i = NULL; + void * pLinkContainer = NULL; + + /* Iterate through the list to find the first matching element. */ + for( i = pStartPoint; i != NULL; i = i->pNext ) + { + pLinkContainer = AwsIotLink_Container( i, linkOffset ); + + if( ( ( compare == NULL ) && ( pArgument == pLinkContainer ) ) || + ( ( compare != NULL ) && ( compare( pArgument, pLinkContainer ) == true ) ) ) + { + return pLinkContainer; + } + } + + /* Element not found. */ + AwsIotQueue_Assert( i == NULL ); + + return NULL; +} + +/*-----------------------------------------------------------*/ + +void AwsIotList_Remove( AwsIotList_t * const pList, + AwsIotLink_t * const pLink, + size_t linkOffset ) +{ + /* Link offset is not used when asserts are off. */ + ( void ) linkOffset; + + /* This function should not be called with an NULL pLink or an empty list. */ + if( ( pList->pHead == NULL ) || ( pLink == NULL ) ) + { + return; + } + + /* pLink must be in pList. */ + AwsIotQueue_Assert( AwsIotList_FindFirstMatch( pList->pHead, + linkOffset, + AwsIotLink_Container( pLink, linkOffset ), + NULL ) != NULL ); + + /* Check if the list head is being removed. */ + if( pLink == pList->pHead ) + { + /* Set the next node's previous pointer to NULL. */ + if( pLink->pNext != NULL ) + { + pLink->pNext->pPrevious = NULL; + } + + /* Update the head pointer. */ + pList->pHead = pLink->pNext; + } + else + { + /* Update the next node's previous pointer. */ + if( pLink->pNext != NULL ) + { + pLink->pNext->pPrevious = pLink->pPrevious; + } + + /* Update the previous node's next pointer. */ + pLink->pPrevious->pNext = pLink->pNext; + } + + /* Clear the next and previous pointers of pLink. */ + pLink->pNext = NULL; + pLink->pPrevious = NULL; +} + +/*-----------------------------------------------------------*/ + +void AwsIotList_RemoveAllMatches( AwsIotList_t * const pList, + size_t linkOffset, + void * pArgument, + bool ( *shouldRemove )( void *, void * ), + void ( *freeElement )( void * ) ) +{ + AwsIotLink_t * i = NULL, * pCurrent = NULL; + void * pLinkContainer = NULL; + + AwsIotMutex_Lock( &( pList->mutex ) ); + i = pList->pHead; + + /* Iterate through the entire list. Remove and free all matching elements. */ + while( i != NULL ) + { + /* Save a pointer to the current element and move the iterating pointer. */ + pCurrent = i; + i = i->pNext; + + /* Calculate address of link container. */ + pLinkContainer = AwsIotLink_Container( pCurrent, linkOffset ); + + /* Check if the current list element should be removed. */ + if( ( shouldRemove == NULL ) || + ( shouldRemove( pArgument, pLinkContainer ) == true ) ) + { + /* Remove the matched element from the list and free it. */ + AwsIotList_Remove( pList, pCurrent, linkOffset ); + + if( freeElement != NULL ) + { + freeElement( pLinkContainer ); + } + } + } + + AwsIotMutex_Unlock( &( pList->mutex ) ); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt new file mode 100644 index 0000000000..a853ef9699 --- /dev/null +++ b/lib/source/mqtt/CMakeLists.txt @@ -0,0 +1,26 @@ +# MQTT library source files. +add_library( awsiotmqtt SHARED + aws_iot_mqtt_api.c + aws_iot_mqtt_operation.c + aws_iot_mqtt_serialize.c + aws_iot_mqtt_subscription.c + aws_iot_mqtt_validate.c ) + +# Library version. +set_target_properties( awsiotmqtt PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Library public headers. +set_target_properties( awsiotmqtt PROPERTIES PUBLIC_HEADER + "../../include/aws_iot_mqtt.h" ) + +# Library private headers. +set_target_properties( awsiotmqtt PROPERTIES PRIVATE_HEADER + "../../include/private/aws_iot_mqtt_internal.h" ) + +# Link required libraries. +target_link_libraries( awsiotmqtt awsiotcommon awsiotplatform ) + +# Link Unity test framework when building tests. +if( ${AWS_IOT_BUILD_TESTS} ) + target_link_libraries( awsiotmqtt unity ) +endif() diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c new file mode 100644 index 0000000000..ec7e896bf7 --- /dev/null +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -0,0 +1,2318 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_api.c + * @brief Implements the user-facing functions of the MQTT library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Validate MQTT configuration settings. */ +#if AWS_IOT_MQTT_ENABLE_ASSERTS != 0 && AWS_IOT_MQTT_ENABLE_ASSERTS != 1 + #error "AWS_IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." +#endif +#if AWS_IOT_MQTT_ENABLE_METRICS != 0 && AWS_IOT_MQTT_ENABLE_METRICS != 1 + #error "AWS_IOT_MQTT_ENABLE_METRICS must be 0 or 1." +#endif +#if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 + #error "AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." +#endif +#if AWS_IOT_MQTT_MAX_CALLBACK_THREADS <= 0 + #error "AWS_IOT_MQTT_MAX_CALLBACK_THREADS cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_MAX_SEND_THREADS <= 0 + #error "AWS_IOT_MQTT_MAX_SEND_THREADS cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_TEST != 0 && AWS_IOT_MQTT_TEST != 1 + #error "AWS_IOT_MQTT_MQTT_TEST must be 0 or 1." +#endif +#if AWS_IOT_MQTT_RESPONSE_WAIT_MS <= 0 + #error "AWS_IOT_MQTT_RESPONSE_WAIT_MS cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_RETRY_MS_CEILING <= 0 + #error "AWS_IOT_MQTT_RETRY_MS_CEILING cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS <= 0 + #error "AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Match MQTT connections by address. + * + * @param[in] pMqttConnection Pointer to an #_mqttConnection_t. + * @param[in] pData Pointer to an #_mqttOperation_t. + * + * @return `true` if the given [pData->pMqttConnection] + * (@ref _mqttOperation_t.pMqttConnection) is equal to `pMqttConnection`; `false` + * otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility with + * @ref queue_function_removeallmatches. + */ +static inline bool _mqttConnection_match( void * pMqttConnection, + void * pData ); + +/** + * @brief Determines if an MQTT subscription is safe to remove based on its + * reference count. + * + * @param[in] pArgument Not used. + * @param[in] pData Pointer to an #_mqttSubscription_t. + * + * @return `true` if the given subscription has no references; `false` otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility with + * @ref list_function_removeallmatches. + */ +static inline bool _mqttSubscription_shouldRemove( void * pArgument, + void * pData ); + +/** + * @brief Process a keep-alive event received from a timer event queue. + * + * @param[in] pMqttConnection The MQTT connection associated with the keep-alive. + * @param[in] pKeepAliveEvent The keep-alive event to process. + * + * @return `true` if the event was successful; false if an error was encountered + * while processing the keep-alive. If this function returns false, the MQTT + * connection should be closed. + */ +static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, + _mqttTimerEvent_t * const pKeepAliveEvent ); + +/** + * @brief Process a PUBLISH retry event received from a timer event queue. + * + * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. + * @param[in] pPublishRetryEvent The PUBLISH retry event to process. + */ +static void _processPublishRetryEvent( bool awsIotMqttMode, + _mqttTimerEvent_t * const pPublishRetryEvent ); + +/** + * @brief Handles timer expirations for an MQTT connection. + * + * This function is invoked when the MQTT connection timer expires. Based on + * pending timer events, it then sends PINGREQ, checks for PINGRESP, or resends + * an unacknowledged QoS 1 PUBLISH. + * + * @param[in] pArgument The MQTT connection for which PINGREQ is sent. + */ +static void _timerThread( void * pArgument ); + +/** + * @brief Creates a new MQTT connection and initializes its members. + * + * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. + * @param[in] pNetworkInterface User-provided network interface for the new + * connection. + * @param[in] keepAliveSeconds User-provided keep-alive interval for the new + * connection. + * + * @return Pointer to a newly-allocated MQTT connection on success; `NULL` on + * failure. + */ +static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, + const AwsIotMqttNetIf_t * const pNetworkInterface, + uint16_t keepAliveSeconds ); + +/** + * @brief Destroys the members of an MQTT connection. + * + * @param[in] pMqttConnection Which connection to destroy. + */ +static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ); + +/** + * @brief The common component of both @ref mqtt_function_connect and @ref + * mqtt_function_connectrestoresession. + * + * See @ref mqtt_function_connectrestoresession for a description of the + * parameters and return values. + */ +static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface, + const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + const AwsIotMqttSubscription_t * const pSessionSubscriptions, + size_t sessionSubscriptionsCount, + uint64_t timeoutMs ); + +/** + * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. + * + * @param[in] pMqttConnection Which connection the PUBACK should be sent over. + * @param[in] packetIdentifier Which packet identifier to include in PUBACK. + */ +static void _sendPuback( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Ensures that only one CONNECT operation is in-progress at any time. + * + * Because CONNACK contains no data about which CONNECT packet it acknowledges, + * only one CONNECT operation may be in-progress at any time. + */ +static AwsIotMutex_t _connectMutex; + +/*-----------------------------------------------------------*/ + +static inline bool _mqttConnection_match( void * pMqttConnection, + void * pData ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + + /* Ignore PINGREQ operations. PINGREQs will be cleaned up with the MQTT + * connection and not here. */ + if( pOperation->operation == AWS_IOT_MQTT_PINGREQ ) + { + return false; + } + + return pMqttConnection == pOperation->pMqttConnection; +} + +/*-----------------------------------------------------------*/ + +static inline bool _mqttSubscription_shouldRemove( void * pArgument, + void * pData ) +{ + _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + /* Reference count must not be negative. */ + AwsIotMqtt_Assert( pSubscription->references >= 0 ); + + /* Check if any subscription callbacks are using this subscription. */ + if( pSubscription->references > 0 ) + { + /* Set the unsubscribed flag, but don't remove the subscription yet. */ + pSubscription->unsubscribed = true; + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, + _mqttTimerEvent_t * const pKeepAliveEvent ) +{ + bool status = true; + + /* Check if the keep-alive is waiting for a PINGRESP. */ + if( pKeepAliveEvent->checkPingresp == false ) + { + /* If keep-alive isn't waiting for PINGRESP, send PINGREQ. */ + AwsIotLogDebug( "Sending PINGREQ." ); + + /* Add the PINGREQ operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pMqttConnection->pPingreqOperation->link ) ) == false ) + { + AwsIotLogWarn( "Failed to add PINGREQ to send queue." ); + status = false; + } + else + { + /* Check for a PINGRESP after AWS_IOT_MQTT_RESPONSE_WAIT_MS. */ + pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + AWS_IOT_MQTT_RESPONSE_WAIT_MS; + pKeepAliveEvent->checkPingresp = true; + } + } + else + { + /* Check that a PINGRESP is immediately available. */ + if( AwsIotMqtt_Wait( pMqttConnection->pPingreqOperation, 0 ) == AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogDebug( "PINGRESP received." ); + + /* The next keep-alive event should send another PINGREQ. */ + pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + + pMqttConnection->keepAliveSeconds * 1000ULL; + pKeepAliveEvent->checkPingresp = false; + } + else + { + /* PINGRESP was not received within AWS_IOT_MQTT_PINGRESP_WAIT_MS. */ + AwsIotLogError( "Timeout waiting on PINGRESP." ); + + /* Set the error flag. The MQTT connection will be closed. */ + pMqttConnection->errorOccurred = true; + + /* Free the keep-alive event and destroy the PINGREQ operation, as they + * will no longer be used by a closed connection. */ + AwsIotMqtt_FreeTimerEvent( pMqttConnection->pKeepAliveEvent ); + pMqttConnection->pKeepAliveEvent = NULL; + + AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); + pMqttConnection->pPingreqOperation = NULL; + + status = false; + } + } + + /* Add the next keep-alive event to the timer event list if the keep-alive + * was successfully processed. */ + if( status == true ) + { + AwsIotMutex_Lock( &( pMqttConnection->timerEventList.mutex ) ); + AwsIotList_InsertSorted( &( pMqttConnection->timerEventList ), + &( pMqttConnection->pKeepAliveEvent->link ), + _TIMER_EVENT_LINK_OFFSET, + AwsIotMqttInternal_TimerEventCompare ); + AwsIotMutex_Unlock( &( pMqttConnection->timerEventList.mutex ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void _processPublishRetryEvent( bool awsIotMqttMode, + _mqttTimerEvent_t * const pPublishRetryEvent ) +{ + /* Check if the PUBLISH operation is still waiting in the receive queue for + * a response. */ + if( AwsIotQueue_RemoveFirstMatch( &_AwsIotMqttReceiveQueue, + _OPERATION_LINK_OFFSET, + pPublishRetryEvent->pOperation, + NULL ) == pPublishRetryEvent->pOperation ) + { + /* Check if the retry limit is reached. */ + if( pPublishRetryEvent->retry.count > pPublishRetryEvent->retry.limit ) + { + AwsIotLogError( "No PUBACK received for PUBLISH %hu after %d retransmissions.", + pPublishRetryEvent->pOperation->packetIdentifier, + pPublishRetryEvent->retry.limit ); + + /* Set a status of "No response to retries" and notify. */ + pPublishRetryEvent->pOperation->status = AWS_IOT_MQTT_RETRY_NO_RESPONSE; + AwsIotMqttInternal_Notify( pPublishRetryEvent->pOperation ); + } + else + { + /* Choose a set DUP function. */ + void ( * publishSetDup )( bool, + uint8_t * const, + uint16_t * const ) = AwsIotMqttInternal_PublishSetDup; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pPublishRetryEvent->pOperation->pMqttConnection->network.serialize.publishSetDup != NULL ) + { + publishSetDup = pPublishRetryEvent->pOperation->pMqttConnection->network.serialize.publishSetDup; + } + #endif + + /* For the AWS IoT MQTT server, AwsIotMqttInternal_PublishSetDup changes the + * packet identifier; this must be done on every retry. For a compliant MQTT + * server, the function sets the DUP flag; this only needs to be done on the + * first retry. */ + if( awsIotMqttMode == true ) + { + if( pPublishRetryEvent->retry.count <= pPublishRetryEvent->retry.limit ) + { + publishSetDup( true, + pPublishRetryEvent->pOperation->pMqttPacket, + &( pPublishRetryEvent->pOperation->packetIdentifier ) ); + } + } + else + { + if( pPublishRetryEvent->retry.count == 1 ) + { + publishSetDup( false, + pPublishRetryEvent->pOperation->pMqttPacket, + &( pPublishRetryEvent->pOperation->packetIdentifier ) ); + } + } + + /* Print debug log messages about this PUBLISH retry. */ + AwsIotLogDebug( "No PUBACK received for PUBLISH %hu. Attempting retransmission" + " %d of %d.", + pPublishRetryEvent->pOperation->packetIdentifier, + pPublishRetryEvent->retry.count, + pPublishRetryEvent->retry.limit ); + + if( pPublishRetryEvent->retry.count < pPublishRetryEvent->retry.limit ) + { + AwsIotLogDebug( "Next retry for PUBLISH %hu in %llu ms.", + pPublishRetryEvent->pOperation->packetIdentifier, + pPublishRetryEvent->retry.nextPeriod ); + } + else + { + AwsIotLogDebug( "Final retry for PUBLISH %hu. Will check in %llu ms " + "for response.", + pPublishRetryEvent->pOperation->packetIdentifier, + AWS_IOT_MQTT_RESPONSE_WAIT_MS ); + } + + /* Add the PUBLISH to the send queue for network transmission. */ + ( void ) AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pPublishRetryEvent->pOperation->link ) ); + } + } +} + +/*-----------------------------------------------------------*/ + +static void _timerThread( void * pArgument ) +{ + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pArgument; + _mqttTimerEvent_t * pTimerEvent = NULL; + + AwsIotLogDebug( "Timer thread started for connection %p.", pMqttConnection ); + + /* Attempt to lock the connection mutex before this thread does anything. + * Return immediately if the mutex couldn't be locked. */ + if( AwsIotMutex_TryLock( &( pMqttConnection->mutex ) ) == false ) + { + AwsIotLogWarn( "Failed to lock connection mutex in timer thread. Exiting." ); + + return; + } + + while( true ) + { + /* Lock the timer list mutex. */ + AwsIotMutex_Lock( &( pMqttConnection->timerEventList.mutex ) ); + + /* Peek the first element in the timer list. */ + pTimerEvent = ( _mqttTimerEvent_t * ) ( pMqttConnection->timerEventList.pHead ); + + if( pTimerEvent != NULL ) + { + /* Check if the first element should be processed now. */ + if( pTimerEvent->expirationTime <= AwsIotClock_GetTimeMs() + AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ) + { + AwsIotList_Remove( &( pMqttConnection->timerEventList ), + pMqttConnection->timerEventList.pHead, + _TIMER_EVENT_LINK_OFFSET ); + } + else + { + /* The first element in the timer queue shouldn't be processed yet. + * Arm the timer for when it should be processed. */ + if( AwsIotClock_TimerArm( &( pMqttConnection->timer ), + pTimerEvent->expirationTime - AwsIotClock_GetTimeMs(), + 0 ) == false ) + { + AwsIotLogWarn( "Failed to re-arm timer for connection %p.", + pMqttConnection ); + } + + pTimerEvent = NULL; + } + } + + /* Unlock the timer list mutex. */ + AwsIotMutex_Unlock( &( pMqttConnection->timerEventList.mutex ) ); + + /* If there are no timer events to process, terminate this thread. */ + if( pTimerEvent == NULL ) + { + AwsIotLogDebug( "No further timer events to process. Exiting timer thread." ); + + break; + } + + AwsIotLogDebug( "Processing timer event for %s.", + AwsIotMqtt_OperationType( pTimerEvent->pOperation->operation ) ); + + /* Process the received timer event. Currently, only PINGREQ and PUBLISH + * operations send timer events. */ + switch( pTimerEvent->pOperation->operation ) + { + case AWS_IOT_MQTT_PINGREQ: + + /* Process the PINGREQ event. If it fails to process, close the MQTT + * connection. */ + if( _processKeepAliveEvent( pMqttConnection, pTimerEvent ) == false ) + { + if( pMqttConnection->network.disconnect != NULL ) + { + pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + } + else + { + AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); + } + } + + break; + + case AWS_IOT_MQTT_PUBLISH_TO_SERVER: + + _processPublishRetryEvent( pMqttConnection->awsIotMqttMode, + pTimerEvent ); + break; + + default: + + /* No other operation may send a timer event. Abort the program + * if this case is reached. */ + AwsIotMqtt_Assert( 0 ); + break; + } + } + + AwsIotMutex_Unlock( &( pMqttConnection->mutex ) ); +} + +/*-----------------------------------------------------------*/ + +static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, + const AwsIotMqttNetIf_t * const pNetworkInterface, + uint16_t keepAliveSeconds ) +{ + _mqttConnection_t * pNewMqttConnection = NULL; + + /* AWS IoT service limits set minimum and maximum values for keep-alive interval. + * Adjust the user-provided keep-alive interval based on these requirements. */ + if( awsIotMqttMode == true ) + { + if( keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; + } + else if( ( keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) || ( keepAliveSeconds == 0 ) ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + } + + /* Allocate memory to store data for the new MQTT connection. */ + pNewMqttConnection = ( _mqttConnection_t * ) + AwsIotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); + + if( pNewMqttConnection == NULL ) + { + AwsIotLogError( "Failed to allocate memory for new MQTT connection." ); + + return NULL; + } + + /* Clear the MQTT connection, then copy the network interface and MQTT server + * mode. */ + ( void ) memset( pNewMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); + pNewMqttConnection->network = *pNetworkInterface; + pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; + + /* Create the new connection's subscription list. */ + if( AwsIotList_Create( &( pNewMqttConnection->subscriptionList ) ) == false ) + { + AwsIotLogError( "Failed to create subscription list for new MQTT connection." ); + AwsIotMqtt_FreeConnection( pNewMqttConnection ); + + return NULL; + } + + /* Create the new connection's timer event list. */ + if( AwsIotList_Create( &( pNewMqttConnection->timerEventList ) ) == false ) + { + AwsIotLogError( "Failed to create timer event list for new MQTT connection" ); + AwsIotList_Destroy( &( pNewMqttConnection->subscriptionList ) ); + AwsIotMqtt_FreeConnection( pNewMqttConnection ); + + return NULL; + } + + /* Create the mutex for a new connection. */ + if( AwsIotMutex_Create( &( pNewMqttConnection->mutex ) ) == false ) + { + AwsIotLogError( "Failed to create mutex for new connection." ); + AwsIotList_Destroy( &( pNewMqttConnection->timerEventList ) ); + AwsIotList_Destroy( &( pNewMqttConnection->subscriptionList ) ); + AwsIotMqtt_FreeConnection( pNewMqttConnection ); + + return NULL; + } + + /* Create the timer for a new connection. */ + if( AwsIotClock_TimerCreate( &( pNewMqttConnection->timer ), + _timerThread, + pNewMqttConnection ) == false ) + { + AwsIotLogError( "Failed to create timer for new connection." ); + AwsIotMutex_Destroy( &( pNewMqttConnection->mutex ) ); + AwsIotList_Destroy( &( pNewMqttConnection->timerEventList ) ); + AwsIotList_Destroy( &( pNewMqttConnection->subscriptionList ) ); + AwsIotMqtt_FreeConnection( pNewMqttConnection ); + + return NULL; + } + + /* Check if keep-alive is active for this connection. */ + if( keepAliveSeconds != 0 ) + { + /* Save the keep-alive interval. */ + pNewMqttConnection->keepAliveSeconds = keepAliveSeconds; + + /* Allocate memory for keep-alive timer event. */ + pNewMqttConnection->pKeepAliveEvent = AwsIotMqtt_MallocTimerEvent( sizeof( _mqttTimerEvent_t ) ); + + if( pNewMqttConnection->pKeepAliveEvent == NULL ) + { + AwsIotLogError( "Failed to allocate keep-alive event for new connection." ); + _destroyMqttConnection( pNewMqttConnection ); + + return NULL; + } + + /* Create PINGREQ operation. */ + if( AwsIotMqttInternal_CreateOperation( &( pNewMqttConnection->pPingreqOperation ), + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to allocate PINGREQ operation for new connection." ); + _destroyMqttConnection( pNewMqttConnection ); + + return NULL; + } + + /* Set the members of the PINGREQ operations. */ + pNewMqttConnection->pPingreqOperation->operation = AWS_IOT_MQTT_PINGREQ; + pNewMqttConnection->pPingreqOperation->pMqttConnection = pNewMqttConnection; + + /* Choose a PINGREQ serializer function. */ + AwsIotMqttError_t ( * serializePingreq )( uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializePingreq; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNewMqttConnection->network.serialize.pingreq != NULL ) + { + serializePingreq = pNewMqttConnection->network.serialize.pingreq; + } + #endif + + if( serializePingreq( &( pNewMqttConnection->pPingreqOperation->pMqttPacket ), + &( pNewMqttConnection->pPingreqOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to serialize PINGREQ packet for new connection." ); + _destroyMqttConnection( pNewMqttConnection ); + + return NULL; + } + + /* Set the members of the keep-alive timer event. */ + ( void ) memset( pNewMqttConnection->pKeepAliveEvent, 0x00, sizeof( _mqttTimerEvent_t ) ); + pNewMqttConnection->pKeepAliveEvent->pOperation = pNewMqttConnection->pPingreqOperation; + pNewMqttConnection->pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + keepAliveSeconds * 1000ULL; + + /* Add the PINGREQ to the timer event list. */ + AwsIotMutex_Lock( &( pNewMqttConnection->timerEventList.mutex ) ); + AwsIotList_InsertSorted( &( pNewMqttConnection->timerEventList ), + &( pNewMqttConnection->pKeepAliveEvent->link ), + _TIMER_EVENT_LINK_OFFSET, + AwsIotMqttInternal_TimerEventCompare ); + AwsIotMutex_Unlock( &( pNewMqttConnection->timerEventList.mutex ) ); + } + + return pNewMqttConnection; +} + +/*-----------------------------------------------------------*/ + +static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) +{ + /* Destroy keep-alive timer event. */ + if( pMqttConnection->pKeepAliveEvent != NULL ) + { + AwsIotMqtt_FreeTimerEvent( pMqttConnection->pKeepAliveEvent ); + pMqttConnection->pKeepAliveEvent = NULL; + } + + /* Destroy keep-alive operation. */ + if( pMqttConnection->pPingreqOperation != NULL ) + { + AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); + + pMqttConnection->pPingreqOperation = NULL; + } + + /* Remove any previous session subscriptions. */ + AwsIotList_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + NULL, + _mqttSubscription_shouldRemove, + AwsIotMqtt_FreeSubscription ); + + /* Destroy timers, mutex, and lists. */ + AwsIotClock_TimerDestroy( &( pMqttConnection->timer ) ); + AwsIotMutex_Destroy( &( pMqttConnection->mutex ) ); + AwsIotList_Destroy( &( pMqttConnection->timerEventList ) ); + AwsIotList_Destroy( &( pMqttConnection->subscriptionList ) ); + AwsIotMqtt_FreeConnection( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface, + const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + const AwsIotMqttSubscription_t * const pSessionSubscriptions, + size_t sessionSubscriptionsCount, + uint64_t timeoutMs ) +{ + AwsIotMqttError_t connectStatus = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pNewMqttConnection = NULL; + _mqttOperation_t * pConnectOperation = NULL; + + /* Default CONNECT serializer function. */ + AwsIotMqttError_t ( * serializeConnect )( const AwsIotMqttConnectInfo_t * const, + const AwsIotMqttPublishInfo_t * const, + uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializeConnect; + + /* Check that the network interface is valid. */ + if( AwsIotMqttInternal_ValidateNetIf( pNetworkInterface ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Choose a CONNECT serializer function. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNetworkInterface->serialize.connect != NULL ) + { + serializeConnect = pNetworkInterface->serialize.connect; + } + #endif + + /* Check that the connection info is valid. */ + if( AwsIotMqttInternal_ValidateConnect( pConnectInfo ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* If will info is provided, check that it is valid. */ + if( pWillInfo != NULL ) + { + if( AwsIotMqttInternal_ValidatePublish( pConnectInfo->awsIotMqttMode, + pWillInfo ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Will message payloads cannot be larger than 65535. This restriction + * applies only to will messages, and not normal PUBLISH messages. */ + if( pWillInfo->payloadLength > UINT16_MAX ) + { + AwsIotLogError( "Will payload cannot be larger than 65535." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + } + + AwsIotLogInfo( "Establishing new MQTT connection." ); + + /* Create a CONNECT operation. */ + connectStatus = AwsIotMqttInternal_CreateOperation( &pConnectOperation, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL ); + + if( connectStatus != AWS_IOT_MQTT_SUCCESS ) + { + return connectStatus; + } + + /* Ensure the members set by operation creation and serialization + * are appropriate for a blocking CONNECT. */ + AwsIotMqtt_Assert( pConnectOperation->pPublishRetry == NULL ); + AwsIotMqtt_Assert( pConnectOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + AwsIotMqtt_Assert( ( pConnectOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) + == AWS_IOT_MQTT_FLAG_WAITABLE ); + + /* Set the operation type. */ + pConnectOperation->operation = AWS_IOT_MQTT_CONNECT; + + /* Allocate memory to store data for the new MQTT connection. */ + pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, + pNetworkInterface, + pConnectInfo->keepAliveSeconds ); + + if( pNewMqttConnection == NULL ) + { + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the MQTT connection. */ + pConnectOperation->pMqttConnection = pNewMqttConnection; + + /* Add previous session subscriptions. */ + if( ( pSessionSubscriptions != NULL ) && ( sessionSubscriptionsCount > 0 ) ) + { + connectStatus = AwsIotMqttInternal_AddSubscriptions( pNewMqttConnection, + 2, + pSessionSubscriptions, + sessionSubscriptionsCount ); + + if( connectStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + _destroyMqttConnection( pNewMqttConnection ); + + return connectStatus; + } + } + + /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ + connectStatus = serializeConnect( pConnectInfo, + pWillInfo, + &( pConnectOperation->pMqttPacket ), + &( pConnectOperation->packetSize ) ); + + if( connectStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + _destroyMqttConnection( pNewMqttConnection ); + + return connectStatus; + } + + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pConnectOperation->packetSize > 0 ); + + /* Set the output parameter so it may be used by the network receive callback. */ + *pMqttConnection = pNewMqttConnection; + + /* Prevent another CONNECT operation from using the network. */ + AwsIotMutex_Lock( &_connectMutex ); + + /* Add the CONNECT operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pConnectOperation->link ) ) == false ) + { + AwsIotLogError( "Failed to add CONNECT to send queue." ); + connectStatus = AWS_IOT_MQTT_NO_MEMORY; + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + } + else + { + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + connectStatus = AwsIotMqtt_Wait( ( AwsIotMqttReference_t ) pConnectOperation, + timeoutMs ); + } + + /* Unlock the CONNECT mutex. */ + AwsIotMutex_Unlock( &_connectMutex ); + + /* Arm the timer for the first keep alive expiration if keep-alive is + * active for this connection. */ + if( ( connectStatus == AWS_IOT_MQTT_SUCCESS ) && + ( pNewMqttConnection->keepAliveSeconds > 0 ) ) + { + AwsIotLogDebug( "Starting new MQTT connection timer." ); + + if( AwsIotClock_TimerArm( &( pNewMqttConnection->timer ), + pNewMqttConnection->pKeepAliveEvent->expirationTime - AwsIotClock_GetTimeMs(), + 0 ) == false ) + { + AwsIotLogError( "Failed to start connection timer for new MQTT connection" ); + + connectStatus = AWS_IOT_MQTT_INIT_FAILED; + } + } + + /* Check the status of the CONNECT operation. */ + if( connectStatus == AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); + } + else + { + /* Otherwise, free resources and log an error. */ + _destroyMqttConnection( pNewMqttConnection ); + *pMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + + AwsIotLogError( "Failed to establish new MQTT connection, error %s.", + AwsIotMqtt_strerror( connectStatus ) ); + } + + return connectStatus; +} + +/*-----------------------------------------------------------*/ + +static void _sendPuback( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier ) +{ + _mqttOperation_t * pPubackOperation = NULL; + + /* Choose a PUBACK serializer function. */ + AwsIotMqttError_t ( * serializePuback )( uint16_t, + uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializePuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.puback != NULL ) + { + serializePuback = pMqttConnection->network.serialize.puback; + } + #endif + + AwsIotLogDebug( "Sending PUBACK for received PUBLISH %hu.", packetIdentifier ); + + /* Create a PUBACK operation. */ + if( AwsIotMqttInternal_CreateOperation( &pPubackOperation, + 0, + NULL ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to create PUBACK operation." ); + + return; + } + + /* Generate a PUBACK packet from the packet identifier. */ + if( serializePuback( packetIdentifier, + &( pPubackOperation->pMqttPacket ), + &( pPubackOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to generate PUBACK packet." ); + AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + + return; + } + + /* Set the remaining members of the PUBACK operation and push it to the + * send queue. */ + pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; + pPubackOperation->pMqttConnection = pMqttConnection; + + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pPubackOperation->link ) ) == false ) + { + AwsIotLogWarn( "Failed to add PUBACK to send queue." ); + AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + } +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_Init( void ) +{ + /* Create CONNECT mutex. */ + if( AwsIotMutex_Create( &_connectMutex ) == false ) + { + AwsIotLogError( "Failed to initialize MQTT library connect mutex." ); + + return AWS_IOT_MQTT_INIT_FAILED; + } + + /* Create MQTT queues. */ + if( AwsIotMqttInternal_CreateQueues() != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to initialize MQTT library queues." ); + + AwsIotMutex_Destroy( &_connectMutex ); + + return AWS_IOT_MQTT_INIT_FAILED; + } + + /* Initialize MQTT serializer. */ + if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to initialize MQTT library serializer. " ); + + AwsIotMutex_Destroy( &_connectMutex ); + AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); + AwsIotQueue_Destroy( &_AwsIotMqttReceiveQueue ); + AwsIotQueue_Destroy( &_AwsIotMqttCallbackQueue ); + + return AWS_IOT_MQTT_INIT_FAILED; + } + + AwsIotLogInfo( "MQTT library successfully initialized." ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqtt_Cleanup() +{ + /* Clean up connect mutex, queues, and serializer. */ + AwsIotMutex_Destroy( &_connectMutex ); + AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); + AwsIotQueue_Destroy( &_AwsIotMqttReceiveQueue ); + AwsIotQueue_Destroy( &_AwsIotMqttCallbackQueue ); + AwsIotMqttInternal_CleanupSerialize(); + + AwsIotLogInfo( "MQTT library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + +int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, + const void * pReceivedData, + size_t offset, + size_t dataLength, + void ( *freeReceivedData )( void * ) ) +{ + size_t bytesProcessed = 0, totalBytesProcessed = 0, remainingDataLength = 0; + _mqttConnection_t * pConnectionInfo = *( ( _mqttConnection_t ** ) ( pMqttConnection ) ); + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + uint16_t packetIdentifier = 0; + const uint8_t * pNextPacket = ( const uint8_t * ) pReceivedData; + _mqttOperation_t * pOperation = NULL, * pFirstPublish = NULL, * pLastPublish = NULL; + + /* Choose a packet type decoder function. */ + uint8_t ( * getPacketType )( const uint8_t * const, + size_t ) = AwsIotMqttInternal_GetPacketType; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.getPacketType != NULL ) + { + getPacketType = pConnectionInfo->network.getPacketType; + } + #endif + + /* Ensure that offset is smaller than dataLength. */ + if( offset >= dataLength ) + { + return 0; + } + + /* Adjust the packet pointer based on the offset. */ + pNextPacket += offset; + remainingDataLength = dataLength - offset; + + /* Process the stream of data until the entire stream is proccessed or an + * incomplete packet is found. */ + while( ( totalBytesProcessed < remainingDataLength ) && ( status != AWS_IOT_MQTT_BAD_RESPONSE ) ) + { + switch( getPacketType( pNextPacket, remainingDataLength - totalBytesProcessed ) ) + { + case _MQTT_PACKET_TYPE_CONNACK: + AwsIotLog_PrintBuffer( "CONNACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the CONNACK. */ + AwsIotMqttError_t ( * deserializeConnack )( const uint8_t * const, + size_t, + size_t * const ) = AwsIotMqttInternal_DeserializeConnack; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.connack != NULL ) + { + deserializeConnack = pConnectionInfo->network.deserialize.connack; + } + #endif + + status = deserializeConnack( pNextPacket, + remainingDataLength - totalBytesProcessed, + &bytesProcessed ); + + /* If a complete CONNACK was deserialized, check if there's an + * in-progress CONNECT operation. */ + if( ( bytesProcessed > 0 ) && + ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, + AWS_IOT_MQTT_CONNECT, + NULL, + &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_PUBLISH: + AwsIotLog_PrintBuffer( "PUBLISH in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Allocate memory to handle the incoming PUBLISH. */ + pOperation = AwsIotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + AwsIotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); + bytesProcessed = 0; + + break; + } + + /* Set the members of the incoming PUBLISH operation. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + pOperation->incomingPublish = true; + pOperation->pMqttConnection = pConnectionInfo; + + /* Deserialize the PUBLISH into an AwsIotMqttPublishInfo_t. */ + AwsIotMqttError_t ( * deserializePublish )( const uint8_t * const, + size_t, + AwsIotMqttPublishInfo_t * const, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializePublish; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.publish != NULL ) + { + deserializePublish = pConnectionInfo->network.deserialize.publish; + } + #endif + + status = deserializePublish( pNextPacket, + remainingDataLength - totalBytesProcessed, + &( pOperation->publishInfo ), + &packetIdentifier, + &bytesProcessed ); + + /* If a complete PUBLISH was deserialized, process it. */ + if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_SUCCESS ) ) + { + /* If a QoS 1 PUBLISH was received, send a PUBACK. */ + if( pOperation->publishInfo.QoS == 1 ) + { + _sendPuback( pConnectionInfo, packetIdentifier ); + } + + /* Change the first and last PUBLISH pointers. */ + if( pFirstPublish == NULL ) + { + pFirstPublish = pOperation; + pLastPublish = pOperation; + } + else + { + pLastPublish->pNextPublish = pOperation; + pLastPublish = pOperation; + } + } + else + { + /* Free the PUBLISH operation here if the PUBLISH packet isn't + * valid. */ + AwsIotMqtt_FreeOperation( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_PUBACK: + AwsIotLog_PrintBuffer( "PUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the PUBACK to get the packet identifier. */ + AwsIotMqttError_t ( * deserializePuback )( const uint8_t * const, + size_t, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializePuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.puback != NULL ) + { + deserializePuback = pConnectionInfo->network.deserialize.puback; + } + #endif + + status = deserializePuback( pNextPacket, + remainingDataLength - totalBytesProcessed, + &packetIdentifier, + &bytesProcessed ); + + /* If a complete PUBACK packet was deserialized, find an in-progress + * PUBLISH with a matching client identifier. */ + if( ( bytesProcessed > 0 ) && + ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, + AWS_IOT_MQTT_PUBLISH_TO_SERVER, + &packetIdentifier, + &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_SUBACK: + AwsIotLog_PrintBuffer( "SUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the SUBACK to get the packet identifier. */ + AwsIotMqttError_t ( * deserializeSuback )( AwsIotMqttConnection_t, + const uint8_t * const, + size_t, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializeSuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.suback != NULL ) + { + deserializeSuback = pConnectionInfo->network.deserialize.suback; + } + #endif + + status = deserializeSuback( pConnectionInfo, + pNextPacket, + remainingDataLength - totalBytesProcessed, + &packetIdentifier, + &bytesProcessed ); + + /* If a complete SUBACK was deserialized, find an in-progress + * SUBACK with a matching client identifier. */ + if( ( bytesProcessed > 0 ) && + ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, + AWS_IOT_MQTT_SUBSCRIBE, + &packetIdentifier, + &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_UNSUBACK: + AwsIotLog_PrintBuffer( "UNSUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the UNSUBACK to get the packet identifier. */ + AwsIotMqttError_t ( * deserializeUnsuback )( const uint8_t * const, + size_t, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializeUnsuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.unsuback != NULL ) + { + deserializeUnsuback = pConnectionInfo->network.deserialize.unsuback; + } + #endif + + status = deserializeUnsuback( pNextPacket, + remainingDataLength - totalBytesProcessed, + &packetIdentifier, + &bytesProcessed ); + + /* If a complete UNSUBACK was deserialized, find an in-progress + * UNSUBACK with a matching client identifier. */ + if( ( bytesProcessed > 0 ) && + ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, + AWS_IOT_MQTT_UNSUBSCRIBE, + &packetIdentifier, + &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_PINGRESP: + AwsIotLog_PrintBuffer( "PINGRESP in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the PINGRESP. */ + AwsIotMqttError_t ( * deserializePingresp )( const uint8_t * const, + size_t, + size_t * const ) = AwsIotMqttInternal_DeserializePingresp; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.pingresp != NULL ) + { + deserializePingresp = pConnectionInfo->network.deserialize.pingresp; + } + #endif + + status = deserializePingresp( pNextPacket, + remainingDataLength - totalBytesProcessed, + &bytesProcessed ); + + /* If a complete PINGRESP was deserialized, check if there's an + * in-progress PINGREQ operation. */ + if( ( bytesProcessed > 0 ) && + ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, + AWS_IOT_MQTT_PINGREQ, + NULL, + &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + + break; + + default: + + /* If an unknown packet is received, stop processing pReceivedData. */ + AwsIotLogError( "Unknown packet type %02x received.", + pNextPacket[ 0 ] ); + + bytesProcessed = 1; + status = AWS_IOT_MQTT_BAD_RESPONSE; + + break; + } + + /* Check if a protocol violation was encountered. */ + if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_BAD_RESPONSE ) ) + { + AwsIotLogError( "MQTT protocol violation encountered. Closing network connection" ); + + /* Clean up any previously allocated incoming PUBLISH operations. */ + while( pFirstPublish != NULL ) + { + pLastPublish = pFirstPublish; + pFirstPublish = pFirstPublish->pNextPublish; + + AwsIotMqtt_FreeOperation( pLastPublish ); + } + + pLastPublish = NULL; + + /* Prevent the PINGRESP send thread from running, then set the error flag. */ + AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + + pConnectionInfo->errorOccurred = true; + + if( pConnectionInfo->network.disconnect != NULL ) + { + pConnectionInfo->network.disconnect( pConnectionInfo->network.pDisconnectContext ); + } + else + { + AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); + } + + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + + return -1; + } + + /* Check if a partial packet was encountered. */ + if( bytesProcessed == 0 ) + { + break; + } + + /* Move the "next packet" pointer and increment the number of bytes processed. */ + pNextPacket += bytesProcessed; + totalBytesProcessed += bytesProcessed; + + /* Number of bytes processed should never exceed remainingDataLength. */ + AwsIotMqtt_Assert( pNextPacket - totalBytesProcessed - offset == pReceivedData ); + AwsIotMqtt_Assert( totalBytesProcessed <= remainingDataLength ); + } + + /* Only free pReceivedData if all bytes were processed and no PUBLISH messages + * were in the data stream. */ + if( ( freeReceivedData != NULL ) && + ( totalBytesProcessed == remainingDataLength ) && + ( pFirstPublish == NULL ) ) + { + AwsIotMqtt_Assert( pLastPublish == NULL ); + + freeReceivedData( ( void * ) pReceivedData ); + } + + /* Add all PUBLISH messages to the callback queue. */ + if( pLastPublish != NULL ) + { + /* If all bytes of the receive buffer were processed, set the function + * to free the receive buffer. */ + if( ( totalBytesProcessed == remainingDataLength ) && ( freeReceivedData != NULL ) ) + { + pLastPublish->pReceivedData = pReceivedData; + pLastPublish->freeReceivedData = freeReceivedData; + } + else + { + /* When some of the receive buffer is unprocessed, the receive buffer is + * given back to the calling function. The MQTT library cannot guarantee + * that the calling function will keep the receive buffer in scope; + * therefore, data in the receive buffer must be copied for the MQTT + * library's use. */ + for( pOperation = pFirstPublish; pOperation != NULL; pOperation = pOperation->pNextPublish ) + { + /* Neither the buffer pointer nor the free function should be set. */ + AwsIotMqtt_Assert( pOperation->pReceivedData == NULL ); + AwsIotMqtt_Assert( pOperation->freeReceivedData == NULL ); + + /* Allocate a new buffer to hold the topic name and payload. */ + pOperation->pReceivedData = AwsIotMqtt_MallocMessage( pOperation->publishInfo.topicNameLength + + pOperation->publishInfo.payloadLength ); + + if( pOperation->pReceivedData != NULL ) + { + /* Copy the topic name and payload. */ + ( void ) memcpy( ( void * ) pOperation->pReceivedData, + pOperation->publishInfo.pTopicName, + pOperation->publishInfo.topicNameLength ); + ( void ) memcpy( ( uint8_t * ) ( pOperation->pReceivedData ) + + pOperation->publishInfo.topicNameLength, + pOperation->publishInfo.pPayload, + pOperation->publishInfo.payloadLength ); + + /* Set the topic name and payload pointers into the new buffer. + * Also set the free function. */ + pOperation->publishInfo.pTopicName = pOperation->pReceivedData; + pOperation->publishInfo.pPayload = ( uint8_t * ) ( pOperation->pReceivedData ) + + pOperation->publishInfo.topicNameLength; + pOperation->freeReceivedData = AwsIotMqtt_FreeMessage; + } + else + { + /* If a new buffer couldn't be allocated, clear the topic name and + * payload pointers so that this PUBLISH message will be ignored. */ + AwsIotLogWarn( "Failed to allocate memory for incoming PUBLISH message." ); + pOperation->publishInfo.pTopicName = NULL; + pOperation->publishInfo.topicNameLength = 0; + pOperation->publishInfo.pPayload = NULL; + pOperation->publishInfo.payloadLength = 0; + } + } + } + + if( AwsIotQueue_InsertHead( &_AwsIotMqttCallbackQueue, + &( pFirstPublish->link ) ) == false ) + { + AwsIotLogWarn( "Failed to add PUBLISH to callback queue." ); + } + } + + return ( int32_t ) totalBytesProcessed; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface, + const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + uint64_t timeoutMs ) +{ + return _connectCommon( pMqttConnection, + pNetworkInterface, + pConnectInfo, + pWillInfo, + NULL, + 0, + timeoutMs ); +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_ConnectRestoreSession( AwsIotMqttConnection_t * pMqttConnection, + const AwsIotMqttNetIf_t * const pNetworkInterface, + const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + const AwsIotMqttSubscription_t * const pSessionSubscriptions, + size_t sessionSubscriptionsCount, + uint64_t timeoutMs ) +{ + /* Check that clean session is false. */ + if( pConnectInfo->cleanSession != false ) + { + AwsIotLogError( "AwsIotMqtt_ConnectRestoreSession must have pConnectInfo->cleanSession == false. " + "AwsIotMqtt_Connect should be used to establish a clean session." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Validate previous session subscriptions. */ + if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pSessionSubscriptions, + sessionSubscriptionsCount ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + return _connectCommon( pMqttConnection, + pNetworkInterface, + pConnectInfo, + pWillInfo, + pSessionSubscriptions, + sessionSubscriptionsCount, + timeoutMs ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, + bool cleanupOnly ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + _mqttOperation_t * pDisconnectOperation = NULL; + + AwsIotLogInfo( "Disconnecting MQTT connection %p.", pMqttConnection ); + + /* Lock the connection mutex to block the timer thread. */ + AwsIotMutex_Lock( &( pMqttConnection->mutex ) ); + + /* Purge all of this connection's subscriptions, operations, and timer events. */ + AwsIotList_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + NULL, + _mqttSubscription_shouldRemove, + AwsIotMqtt_FreeSubscription ); + AwsIotQueue_RemoveAllMatches( &_AwsIotMqttSendQueue, + _OPERATION_LINK_OFFSET, + pMqttConnection, + _mqttConnection_match, + AwsIotMqttInternal_DestroyOperation ); + AwsIotQueue_RemoveAllMatches( &_AwsIotMqttReceiveQueue, + _OPERATION_LINK_OFFSET, + pMqttConnection, + _mqttConnection_match, + AwsIotMqttInternal_DestroyOperation ); + AwsIotList_RemoveAllMatches( &( pMqttConnection->timerEventList ), + _TIMER_EVENT_LINK_OFFSET, + NULL, + NULL, + AwsIotMqtt_FreeTimerEvent ); + + /* Stop the connection timer. */ + AwsIotLogDebug( "Stopping connection timer." ); + AwsIotClock_TimerDestroy( &( pMqttConnection->timer ) ); + + /* Only send a DISCONNECT packet if no error occurred and the "cleanup only" + * option is false. */ + if( ( pMqttConnection->errorOccurred == false ) && ( cleanupOnly == false ) ) + { + /* Create a DISCONNECT operation. This function blocks until the DISCONNECT + * packet is sent, so it sets AWS_IOT_MQTT_FLAG_WAITABLE. */ + status = AwsIotMqttInternal_CreateOperation( &pDisconnectOperation, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL ); + + if( status == AWS_IOT_MQTT_SUCCESS ) + { + /* Ensure that the members set by operation creation and serialization + * are appropriate for a blocking DISCONNECT. */ + AwsIotMqtt_Assert( pDisconnectOperation->pPublishRetry == NULL ); + AwsIotMqtt_Assert( pDisconnectOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + AwsIotMqtt_Assert( ( pDisconnectOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) + == AWS_IOT_MQTT_FLAG_WAITABLE ); + + /* Set the remaining members of the DISCONNECT operation. */ + pDisconnectOperation->operation = AWS_IOT_MQTT_DISCONNECT; + pDisconnectOperation->pMqttConnection = pMqttConnection; + + /* Choose a disconnect serializer. */ + AwsIotMqttError_t ( * serializeDisconnect )( uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializeDisconnect; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.disconnect != NULL ) + { + serializeDisconnect = pMqttConnection->network.serialize.disconnect; + } + #endif + + /* Generate a DISCONNECT packet. */ + status = serializeDisconnect( &( pDisconnectOperation->pMqttPacket ), + &( pDisconnectOperation->packetSize ) ); + } + + if( status == AWS_IOT_MQTT_SUCCESS ) + { + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pDisconnectOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pDisconnectOperation->packetSize > 0 ); + + /* Add the DISCONNECT operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pDisconnectOperation->link ) ) == false ) + { + AwsIotLogWarn( "Failed to add DISCONNECT to send queue." ); + AwsIotMqttInternal_DestroyOperation( pDisconnectOperation ); + } + else + { + /* Wait until the DISCONNECT packet has been transmitted. DISCONNECT + * should always be successful because it does not rely on any incoming + * data. */ + status = AwsIotMqtt_Wait( ( AwsIotMqttReference_t ) pDisconnectOperation, + 0 ); + + /* A wait on DISCONNECT should only ever return SUCCESS or SEND ERROR. */ + AwsIotMqtt_Assert( ( status == AWS_IOT_MQTT_SUCCESS ) || + ( status == AWS_IOT_MQTT_SEND_ERROR ) ); + + AwsIotLogInfo( "MQTT connection %p disconnected.", pMqttConnection ); + } + } + + /* Close the network connection regardless of whether an MQTT DISCONNECT + * packet was sent. */ + if( pMqttConnection->network.disconnect != NULL ) + { + pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + } + else + { + AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); + } + } + + /* Free the memory in use by the keep-alive operation. */ + if( pMqttConnection->pPingreqOperation != NULL ) + { + AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); + } + + /* Destroy this connection's lists and queues. */ + AwsIotList_Destroy( &( pMqttConnection->subscriptionList ) ); + AwsIotList_Destroy( &( pMqttConnection->timerEventList ) ); + + /* Unlock and destroy the connection mutex. */ + AwsIotMutex_Unlock( &( pMqttConnection->mutex ) ); + AwsIotMutex_Destroy( &( pMqttConnection->mutex ) ); + + /* Free the memory used by this connection. */ + AwsIotMqtt_FreeConnection( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_Subscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pSubscribeRef ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pSubscribeOperation = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Default SUBSCRIBE serializer function. */ + AwsIotMqttError_t ( * serializeSubscribe )( const AwsIotMqttSubscription_t * const, + size_t, + uint8_t ** const, + size_t * const, + uint16_t * const ) = AwsIotMqttInternal_SerializeSubscribe; + + /* Choose a SUBSCRIBE serializer function. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.subscribe != NULL ) + { + serializeSubscribe = pMqttConnection->network.serialize.subscribe; + } + #endif + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && + ( pSubscribeRef == NULL ) ) + { + AwsIotLogError( "Reference must be provided for a waitable SUBSCRIBE." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Check that all elements in the subscription list are valid. */ + if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, + pMqttConnection->awsIotMqttMode, + pSubscriptionList, + subscriptionCount ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Create a SUBSCRIBE operation. */ + status = AwsIotMqttInternal_CreateOperation( &pSubscribeOperation, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + return status; + } + + /* Check the SUBSCRIBE operation data and set the remaining members. */ + AwsIotMqtt_Assert( pSubscribeOperation->pPublishRetry == NULL ); + AwsIotMqtt_Assert( pSubscribeOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + pSubscribeOperation->operation = AWS_IOT_MQTT_SUBSCRIBE; + pSubscribeOperation->pMqttConnection = pMqttConnection; + + /* Generate a SUBSCRIBE packet from the subscription list. */ + status = serializeSubscribe( pSubscriptionList, + subscriptionCount, + &( pSubscribeOperation->pMqttPacket ), + &( pSubscribeOperation->packetSize ), + &( pSubscribeOperation->packetIdentifier ) ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pSubscribeOperation ); + + return status; + } + + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pSubscribeOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pSubscribeOperation->packetSize > 0 ); + + /* Add the subscription list to the MQTT connection. */ + status = AwsIotMqttInternal_AddSubscriptions( pMqttConnection, + pSubscribeOperation->packetIdentifier, + pSubscriptionList, + subscriptionCount ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pSubscribeOperation ); + + return status; + } + + /* Set the reference, if provided. This must be set before the subscribe + * is pushed to the network queue to avoid a race condition. */ + if( pSubscribeRef != NULL ) + { + *pSubscribeRef = pSubscribeOperation; + } + + /* Add the SUBSCRIBE operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pSubscribeOperation->link ) ) == false ) + { + AwsIotLogError( "Failed to add SUBSCRIBE to send queue." ); + + AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, + pSubscribeOperation->packetIdentifier, + -1 ); + AwsIotMqttInternal_DestroyOperation( pSubscribeOperation ); + + /* Clear the previously set (and now invalid) reference. */ + if( pSubscribeRef != NULL ) + { + *pSubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + } + + return AWS_IOT_MQTT_NO_MEMORY; + } + + AwsIotLogInfo( "MQTT SUBSCRIBE operation queued." ); + + /* The SUBSCRIBE operation is waiting for a network response. */ + return AWS_IOT_MQTT_STATUS_PENDING; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_TimedSubscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttReference_t subscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + + /* Flags are not used, but the parameter is present for future compatibility. */ + ( void ) flags; + + /* Call the asynchronous SUBSCRIBE function. */ + status = AwsIotMqtt_Subscribe( mqttConnection, + pSubscriptionList, + subscriptionCount, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeRef ); + + /* Wait for the SUBSCRIBE operation to complete. */ + if( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + status = AwsIotMqtt_Wait( subscribeRef, timeoutMs ); + } + + /* Ensure that a status was set. */ + AwsIotMqtt_Assert( status != AWS_IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_Unsubscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pUnsubscribeRef ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pUnsubscribeOperation = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Default UNSUBSCRIBE serializer function. */ + AwsIotMqttError_t ( * serializeUnsubscribe )( const AwsIotMqttSubscription_t * const, + size_t, + uint8_t ** const, + size_t * const, + uint16_t * const ) = AwsIotMqttInternal_SerializeUnsubscribe; + + /* Choose an UNSUBSCRIBE serializer function. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.unsubscribe != NULL ) + { + serializeUnsubscribe = pMqttConnection->network.serialize.unsubscribe; + } + #endif + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && + ( pUnsubscribeRef == NULL ) ) + { + AwsIotLogError( "Reference must be provided for a waitable UNSUBSCRIBE." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Check that all elements in the subscription list are valid. */ + if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_UNSUBSCRIBE, + pMqttConnection->awsIotMqttMode, + pSubscriptionList, + subscriptionCount ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Remove the given subsriptions from the MQTT connection. */ + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, + pSubscriptionList, + subscriptionCount ); + + /* Create an UNSUBSCRIBE operation. */ + status = AwsIotMqttInternal_CreateOperation( &pUnsubscribeOperation, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + return status; + } + + /* Check the UNSUBSCRIBE operation data and set the remaining members. */ + AwsIotMqtt_Assert( pUnsubscribeOperation->pPublishRetry == NULL ); + AwsIotMqtt_Assert( pUnsubscribeOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + pUnsubscribeOperation->operation = AWS_IOT_MQTT_UNSUBSCRIBE; + pUnsubscribeOperation->pMqttConnection = pMqttConnection; + + /* Generate an UNSUBSCRIBE packet from the subscription list. */ + status = serializeUnsubscribe( pSubscriptionList, + subscriptionCount, + &( pUnsubscribeOperation->pMqttPacket ), + &( pUnsubscribeOperation->packetSize ), + &( pUnsubscribeOperation->packetIdentifier ) ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pUnsubscribeOperation ); + + return status; + } + + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pUnsubscribeOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pUnsubscribeOperation->packetSize > 0 ); + + /* Set the reference, if provided. This is set before the unsubscribe + * is pushed to the network queue. */ + if( pUnsubscribeRef != NULL ) + { + *pUnsubscribeRef = pUnsubscribeOperation; + } + + /* Add the UNSUBSCRIBE operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pUnsubscribeOperation->link ) ) == false ) + { + AwsIotLogError( "Failed to add UNSUBSCRIBE to send queue." ); + + AwsIotMqttInternal_DestroyOperation( pUnsubscribeOperation ); + + /* Clear the previously set (and now invalid) reference. */ + if( pUnsubscribeRef != NULL ) + { + *pUnsubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + } + + return AWS_IOT_MQTT_NO_MEMORY; + } + + AwsIotLogInfo( "MQTT UNSUBSCRIBE operation queued." ); + + /* The UNSUBSCRIBE operation is waiting for a network response. */ + return AWS_IOT_MQTT_STATUS_PENDING; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttReference_t unsubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + + /* Flags are not used, but the parameter is present for future compatibility. */ + ( void ) flags; + + /* Call the asynchronous UNSUBSCRIBE function. */ + status = AwsIotMqtt_Unsubscribe( mqttConnection, + pSubscriptionList, + subscriptionCount, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeRef ); + + /* Wait for the UNSUBSCRIBE operation to complete. */ + if( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + status = AwsIotMqtt_Wait( unsubscribeRef, timeoutMs ); + } + + /* Ensure that a status was set. */ + AwsIotMqtt_Assert( status != AWS_IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pPublishRef ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pPublishOperation = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Default PUBLISH serializer function. */ + AwsIotMqttError_t ( * serializePublish )( const AwsIotMqttPublishInfo_t * const, + uint8_t ** const, + size_t * const, + uint16_t * const ) = AwsIotMqttInternal_SerializePublish; + + /* Choose a PUBLISH serializer function. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.publish != NULL ) + { + serializePublish = pMqttConnection->network.serialize.publish; + } + #endif + + /* Check that the PUBLISH information is valid. */ + if( AwsIotMqttInternal_ValidatePublish( pMqttConnection->awsIotMqttMode, + pPublishInfo ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Check that no notification is requested for a QoS 0 publish. */ + if( pPublishInfo->QoS == 0 ) + { + if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) || + ( pCallbackInfo != NULL ) ) + { + AwsIotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + if( pPublishRef != NULL ) + { + AwsIotLogWarn( "Ignoring pPublishRef parameter for QoS 0 publish." ); + } + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && + ( pPublishRef == NULL ) ) + { + AwsIotLogError( "Reference must be provided for a waitable PUBLISH." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Create a PUBLISH operation. */ + status = AwsIotMqttInternal_CreateOperation( &pPublishOperation, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + return status; + } + + /* Check the PUBLISH operation data and set the remaining members. */ + AwsIotMqtt_Assert( pPublishOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + pPublishOperation->operation = AWS_IOT_MQTT_PUBLISH_TO_SERVER; + pPublishOperation->pMqttConnection = pMqttConnection; + + /* Generate a PUBLISH packet from pPublishInfo. */ + status = serializePublish( pPublishInfo, + &( pPublishOperation->pMqttPacket ), + &( pPublishOperation->packetSize ), + &( pPublishOperation->packetIdentifier ) ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pPublishOperation ); + + return status; + } + + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pPublishOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pPublishOperation->packetSize > 0 ); + + if( pPublishInfo->QoS == 0 ) + { + AwsIotMqtt_Assert( pPublishOperation->packetIdentifier == 0 ); + } + else + { + AwsIotMqtt_Assert( pPublishOperation->packetIdentifier != 0 ); + } + + /* Initialize PUBLISH retry for QoS 1 PUBLISH if retryLimit is set. */ + if( ( pPublishInfo->QoS > 0 ) && ( pPublishInfo->retryLimit > 0 ) ) + { + /* Allocate a timer event to handle retries. */ + pPublishOperation->pPublishRetry = AwsIotMqtt_MallocTimerEvent( sizeof( _mqttTimerEvent_t ) ); + + if( pPublishOperation->pPublishRetry == NULL ) + { + AwsIotMqttInternal_DestroyOperation( pPublishOperation ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the members of the retry timer event. */ + ( void ) ( memset( pPublishOperation->pPublishRetry, 0x00, sizeof( _mqttTimerEvent_t ) ) ); + pPublishOperation->pPublishRetry->pOperation = pPublishOperation; + pPublishOperation->pPublishRetry->retry.limit = pPublishInfo->retryLimit; + pPublishOperation->pPublishRetry->retry.nextPeriod = pPublishInfo->retryMs; + } + + /* Set the reference, if provided. This should be set before the publish + * is pushed to the network queue to avoid a race condition. */ + if( ( pPublishInfo->QoS > 0 ) && ( pPublishRef != NULL ) ) + { + *pPublishRef = pPublishOperation; + } + + /* Add the PUBLISH operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pPublishOperation->link ) ) == false ) + { + AwsIotLogError( "Failed to add PUBLISH to send queue." ); + + AwsIotMqttInternal_DestroyOperation( pPublishOperation ); + + /* Clear the previously set (and now invalid) reference. */ + if( ( pPublishInfo->QoS > 0 ) && ( pPublishRef != NULL ) ) + { + *pPublishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + } + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* A QoS 0 PUBLISH is considered successful as soon as it's added to the + * send queue. */ + if( pPublishInfo->QoS == 0 ) + { + return AWS_IOT_MQTT_SUCCESS; + } + + AwsIotLogInfo( "MQTT PUBLISH operation queued." ); + + /* QoS 1 and QoS 2 PUBLISH messages are awaiting responses. */ + return AWS_IOT_MQTT_STATUS_PENDING; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_TimedPublish( AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint32_t flags, + uint64_t timeoutMs ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER, + * pPublishRef = NULL; + + /* Clear the flags. */ + flags = 0; + + /* Set the waitable flag and reference for QoS 1 PUBLISH. */ + if( pPublishInfo->QoS > 0 ) + { + flags = AWS_IOT_MQTT_FLAG_WAITABLE; + pPublishRef = &publishRef; + } + + /* Call the asynchronous PUBLISH function. */ + status = AwsIotMqtt_Publish( mqttConnection, + pPublishInfo, + flags, + NULL, + pPublishRef ); + + /* Wait for a queued QoS 1 PUBLISH to complete. */ + if( ( pPublishInfo->QoS > 0 ) && ( status == AWS_IOT_MQTT_STATUS_PENDING ) ) + { + status = AwsIotMqtt_Wait( publishRef, timeoutMs ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, + uint64_t timeoutMs ) +{ + bool publishRetryActive = false; + uint64_t startTime = 0, currentTime = 0, elapsedTime = 0, remainingMs = timeoutMs; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; + + /* Check reference. */ + if( AwsIotMqttInternal_ValidateReference( reference ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + AwsIotLogInfo( "Waiting for operation %s to complete.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + /* Wait for the operation to be sent once. This wait should return quickly. */ + AwsIotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); + + /* Check any status set by the send thread. Block the receive callback + * during this check by locking the receive queue mutex. */ + AwsIotMutex_Lock( &( _AwsIotMqttReceiveQueue.mutex ) ); + status = pOperation->status; + AwsIotMutex_Unlock( &( _AwsIotMqttReceiveQueue.mutex ) ); + + if( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + /* Check if this operation is a PUBLISH with retry. Block the timer + * thread during this check by locking the connection mutex. */ + if( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) + { + AwsIotMutex_Lock( &( pOperation->pMqttConnection->mutex ) ); + publishRetryActive = ( pOperation->pPublishRetry != NULL ); + AwsIotMutex_Unlock( &( pOperation->pMqttConnection->mutex ) ); + } + + /* Wait for a response to be received from the network. Record when + * the wait begins for a PUBLISH with retry. */ + if( publishRetryActive == true ) + { + startTime = AwsIotClock_GetTimeMs(); + AwsIotMqtt_Assert( startTime > 0 ); + } + + /* All MQTT operations except PUBLISH with retry will have a status after + * the second block on the wait semaphore. PUBLISH with retry may require + * multiple blocks (once more per each retransmission). */ + while( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + /* Only perform the remaining time calculation for PUBLISH with retry. */ + if( publishRetryActive == true ) + { + /* Get current time. */ + currentTime = AwsIotClock_GetTimeMs(); + AwsIotMqtt_Assert( currentTime >= startTime ); + + /* Calculate elapsed time. */ + elapsedTime = currentTime - startTime; + + /* Check for timeout with elapsed time. */ + if( elapsedTime > timeoutMs ) + { + status = AWS_IOT_MQTT_TIMEOUT; + break; + } + + /* Calculate the remaining wait time. */ + remainingMs = timeoutMs - elapsedTime; + } + + /* Block on the wait semaphore. */ + if( AwsIotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + remainingMs ) == false ) + { + /* No status received before timeout. */ + status = AWS_IOT_MQTT_TIMEOUT; + + /* A timed out operation may still be in the receive queue. */ + ( void ) AwsIotQueue_RemoveFirstMatch( &_AwsIotMqttReceiveQueue, + _OPERATION_LINK_OFFSET, + pOperation, + NULL ); + } + else + { + /* For a PUBLISH with retry, block the timer thread before reading a + * status. */ + if( publishRetryActive == true ) + { + AwsIotMutex_Lock( &( pOperation->pMqttConnection->mutex ) ); + } + + /* Successfully received a notification of completion. Retrieve the + * status. */ + status = pOperation->status; + + if( publishRetryActive == true ) + { + AwsIotMutex_Unlock( &( pOperation->pMqttConnection->mutex ) ); + } + } + } + } + else + { + /* If a status was set by the send thread, wait for the send thread to be + * completely done with the operation. */ + AwsIotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); + } + + /* A completed operation should not be in any queue. */ + AwsIotMqtt_Assert( pOperation->link.pNext == NULL ); + AwsIotMqtt_Assert( pOperation->link.pPrevious == NULL ); + + /* Remove any lingering subscriptions if SUBSCRIBE failed. */ + if( ( status != AWS_IOT_MQTT_SUCCESS ) && + ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) ) + { + AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, + pOperation->packetIdentifier, + -1 ); + } + + AwsIotLogInfo( "MQTT operation %s complete with result %s.", + AwsIotMqtt_OperationType( pOperation->operation ), + AwsIotMqtt_strerror( status ) ); + + /* The operation is complete; it can be destroyed. PINGREQ operations are + * destroyed by AwsIotMqtt_Disconnect and not here. If the operation is a + * PINGRESP, reset it. */ + if( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) + { + AwsIotMqttInternal_DestroyOperation( pOperation ); + } + else + { + AwsIotMqtt_Assert( AwsIotSemaphore_GetCount( &( pOperation->notify.waitSemaphore ) ) == 0 ); + pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; + } + + /* A complete operation status (not pending) should be set. */ + AwsIotMqtt_Assert( status != AWS_IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +const char * AwsIotMqtt_strerror( AwsIotMqttError_t status ) +{ + /* The string returned if the parameter is invalid. */ + static const char * pInvalidStatus = "INVALID STATUS"; + /* Lookup table of MQTT statuses. */ + static const char * pStatusNames[] = + { + "SUCCESS", /* AWS_IOT_MQTT_SUCCESS */ + "PENDING", /* AWS_IOT_MQTT_STATUS_PENDING */ + "INIT FAILED", /* AWS_IOT_MQTT_INIT_FAILED */ + "BAD PARAMETER", /* AWS_IOT_MQTT_BAD_PARAMETER */ + "NO MEMORY", /* AWS_IOT_MQTT_NO_MEMORY */ + "NETWORK SEND ERROR", /* AWS_IOT_MQTT_SEND_ERROR */ + "BAD RESPONSE RECEIVED", /* AWS_IOT_MQTT_BAD_RESPONSE */ + "TIMEOUT", /* AWS_IOT_MQTT_TIMEOUT */ + "SERVER REFUSED", /* AWS_IOT_MQTT_SERVER_REFUSED */ + "NO RESPONSE" /* AWS_IOT_MQTT_RETRY_NO_RESPONSE */ + }; + + /* Check that the parameter is valid. */ + if( ( status < 0 ) || + ( status >= ( sizeof( pStatusNames ) / sizeof( pStatusNames[ 0 ] ) ) ) ) + { + return pInvalidStatus; + } + + return pStatusNames[ status ]; +} + +/*-----------------------------------------------------------*/ + +const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ) +{ + /* The string returned if the parameter is invalid. */ + static const char * pInvalidOperation = "INVALID OPERATION"; + /* Lookup table of MQTT operations. */ + static const char * pOperationTypes[] = + { + "CONNECT", /* AWS_IOT_MQTT_CONNECT */ + "PUBLISH", /* AWS_IOT_MQTT_PUBLISH_TO_SERVER */ + "PUBACK", /* AWS_IOT_MQTT_PUBACK */ + "SUBSCRIBE", /* AWS_IOT_MQTT_SUBSCRIBE */ + "UNSUBSCRIBE", /* AWS_IOT_MQTT_UNSUBSCRIBE */ + "PINGREQ", /* AWS_IOT_MQTT_PINGREQ */ + "DISCONNECT" /* AWS_IOT_MQTT_DISCONNECT */ + }; + + /* Check that the parameter is valid. */ + if( ( operation < 0 ) || + ( operation >= ( sizeof( pOperationTypes ) / sizeof( pOperationTypes[ 0 ] ) ) ) ) + { + return pInvalidOperation; + } + + return pOperationTypes[ operation ]; +} + +/*-----------------------------------------------------------*/ + +/* If the MQTT library is being tested, include a file that allows access to + * internal functions and variables. */ +#if AWS_IOT_MQTT_TEST == 1 + #include "aws_iot_test_access_mqtt_api.c" +#endif diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c new file mode 100644 index 0000000000..728b0ca4cf --- /dev/null +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -0,0 +1,693 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_operation.c + * @brief Implements functions that process MQTT operations. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_mqttOperation_match. + */ +typedef struct _operationMatchParam +{ + AwsIotMqttOperationType_t operation; /**< @brief The operation to look for. */ + const uint16_t * pPacketIdentifier; /**< @brief The packet identifier associated with the operation. + * Set to `NULL` to ignore packet identifier. */ +} _operationMatchParam_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Match an MQTT operation by type and packet identifier. + * + * @param[in] pArgument Pointer to an #_operationMatchParam_t. + * @param[in] pData Pointer to an #_mqttOperation_t. + * + * @return `true` if `pData` matches the parameters in `pArgument`; `false` + * otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility + * with @ref queue_function_removefirstmatch. + */ +static inline bool _mqttOperation_match( void * pArgument, + void * pData ); + +/** + * @brief Send data over the network. + * + * Whenever an operation is added to the send queue, a send thread is created. + * This thread processes operations from the send queue until it is empty. The + * number of concurrent send threads is limited by @ref AWS_IOT_MQTT_MAX_SEND_THREADS. + * + * @param[in] pArgument Not used. + */ +static void _sendThread( void * pArgument ); + +/** + * @brief Invoke user-provided callback functions. + * + * Callback functions are invoked in their own thread so they don't block the + * MQTT library's threads. The number of concurrent callback threads is limited + * by @ref AWS_IOT_MQTT_MAX_CALLBACK_THREADS. + * + * @param[in] pArgument Not used. + */ +static void _callbackThread( void * pArgument ); + +/** + * @brief Calculate the timeout and period of the next PUBLISH retry event. + * + * Calculates the next time a PUBLISH should be transmitted and places the + * retry event in a timer event queue. + * + * @param[in] pMqttConnection The MQTT connection associated with the PUBLISH. + * @param[in] pPublishRetry The current PUBLISH retry event. + * + * @return true if the retry event was successfully placed in a timer event queue; + * false otherwise. + */ +static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnection, + _mqttTimerEvent_t * const pPublishRetry ); + +/*-----------------------------------------------------------*/ + +/* + * MQTT operation queues. + */ +AwsIotQueue_t _AwsIotMqttSendQueue = { 0 }; /**< @brief Queues data to be sent on the network. */ +AwsIotQueue_t _AwsIotMqttReceiveQueue = { 0 }; /**< @brief Queues responses received from the network. */ +AwsIotQueue_t _AwsIotMqttCallbackQueue = { 0 }; /**< @brief Queues pending user callbacks. */ + +/*-----------------------------------------------------------*/ + +static inline bool _mqttOperation_match( void * pArgument, + void * pData ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pArgument; + + if( pParam->operation == pOperation->operation ) + { + if( pParam->pPacketIdentifier == NULL ) + { + return true; + } + else + { + return *( pParam->pPacketIdentifier ) == pOperation->packetIdentifier; + } + } + + return false; +} + +/*-----------------------------------------------------------*/ + +static void _sendThread( void * pArgument ) +{ + bool waitableOperation = false; + _mqttOperation_t * pOperation = NULL; + + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + AwsIotLogDebug( "New send thread started." ); + + /* Receive messages from the send queue until the queue is empty. */ + while( true ) + { + AwsIotLogDebug( "Removing oldest operation from MQTT send queue." ); + + /* Get the oldest operation in the MQTT send queue. */ + pOperation = AwsIotQueue_RemoveTail( &_AwsIotMqttSendQueue, + _OPERATION_LINK_OFFSET, + true ); + + /* If no operation was received, terminate the loop. */ + if( pOperation == NULL ) + { + break; + } + + AwsIotLogDebug( "Operation %s received from queue. Sending data over network.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + /* The received operation should be waiting for a status and have a valid packet. */ + AwsIotMqtt_Assert( pOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + AwsIotMqtt_Assert( pOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pOperation->packetSize != 0 ); + + /* Check if this is a waitable operation. */ + waitableOperation = ( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) + == AWS_IOT_MQTT_FLAG_WAITABLE ); + + /* Transmit the MQTT packet from the operation over the network. */ + if( pOperation->pMqttConnection->network.send( pOperation->pMqttConnection->network.pSendContext, + pOperation->pMqttPacket, + pOperation->packetSize ) != pOperation->packetSize ) + { + /* Transmission failed. */ + AwsIotLogError( "Failed to send data for operation %s.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + pOperation->status = AWS_IOT_MQTT_SEND_ERROR; + } + else + { + /* DISCONNECT operations are considered successful upon successful + * transmission. */ + if( pOperation->operation == AWS_IOT_MQTT_DISCONNECT ) + { + pOperation->status = AWS_IOT_MQTT_SUCCESS; + } + /* Calculate the details of the next PUBLISH retry event. */ + else if( pOperation->pPublishRetry != NULL ) + { + /* Only a PUBLISH may have a retry event. */ + AwsIotMqtt_Assert( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + + if( _calculateNextPublishRetry( pOperation->pMqttConnection, + pOperation->pPublishRetry ) == false ) + { + /* PUBLISH retry failed to re-arm connection timer. Retransmission + * will not be sent. */ + pOperation->status = AWS_IOT_MQTT_SEND_ERROR; + } + } + } + + /* Once the MQTT packet has been sent, it may be freed if it will not be + * retransmitted and it's not a PINGREQ. Additionally, the entire operation + * may be destroyed if no notification method is set. */ + if( ( pOperation->pPublishRetry == NULL ) && + ( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) ) + { + /* Choose a free packet function. */ + void ( * freePacket )( uint8_t * ) = AwsIotMqttInternal_FreePacket; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pOperation->pMqttConnection->network.freePacket != NULL ) + { + freePacket = pOperation->pMqttConnection->network.freePacket; + } + #endif + + freePacket( pOperation->pMqttPacket ); + pOperation->pMqttPacket = NULL; + + if( ( waitableOperation == false ) && + ( pOperation->notify.callback.function == NULL ) ) + { + AwsIotMqttInternal_DestroyOperation( pOperation ); + continue; + } + } + + /* If a status was set by this function, notify of completion. */ + if( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) + { + AwsIotMqttInternal_Notify( pOperation ); + } + /* Otherwise, add the operation to the receive queue. */ + else + { + /* This function call will not fail because the receive queue has no + * notification routine. */ + ( void ) AwsIotQueue_InsertHead( &_AwsIotMqttReceiveQueue, + &( pOperation->link ) ); + } + + /* Notify a waitable operation that the send thread is finished. */ + if( waitableOperation == true ) + { + AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + } + } + + AwsIotLogDebug( "Send queue empty. Send thread terminating." ); +} + +/*-----------------------------------------------------------*/ + +static void _callbackThread( void * pArgument ) +{ + _mqttOperation_t * pOperation = NULL, * i = NULL, * pCurrent = NULL; + AwsIotMqttCallbackParam_t callbackParam = { .operation = { 0 } }; + + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + AwsIotLogDebug( "New callback thread started." ); + + while( true ) + { + AwsIotLogDebug( "Removing oldest operation from MQTT callback queue." ); + + /* Get the oldest operation in the MQTT callback queue. */ + pOperation = AwsIotQueue_RemoveTail( &_AwsIotMqttCallbackQueue, + _OPERATION_LINK_OFFSET, + true ); + + /* If no operation was received, terminate the loop. */ + if( pOperation == NULL ) + { + break; + } + + if( pOperation->incomingPublish == true ) + { + /* Set the pointer to the first PUBLISH. */ + i = pOperation; + + /* Process each PUBLISH in the operation. */ + while( i != NULL ) + { + /* Save a pointer to the current PUBLISH and move the iterating + * pointer. */ + pCurrent = i; + i = i->pNextPublish; + + /* Process the current PUBLISH. */ + if( ( pCurrent->publishInfo.pPayload != NULL ) && + ( pCurrent->publishInfo.pTopicName != NULL ) ) + { + callbackParam.message.info = pCurrent->publishInfo; + + AwsIotMqttInternal_ProcessPublish( pCurrent->pMqttConnection, + &callbackParam ); + } + + /* Free any buffers associated with the current PUBLISH message. */ + if( pCurrent->freeReceivedData != NULL ) + { + AwsIotMqtt_Assert( pCurrent->pReceivedData != NULL ); + pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); + } + + /* Free the current PUBLISH. */ + AwsIotMqtt_FreeOperation( pCurrent ); + } + } + else + { + /* Operations with no callback should not have been passed to the + * callback queue. */ + AwsIotMqtt_Assert( pOperation->notify.callback.function != NULL ); + + AwsIotLogDebug( "Operation %s received from queue. Invoking user callback.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + /* Set callback parameters. */ + callbackParam.mqttConnection = pOperation->pMqttConnection; + callbackParam.operation.type = pOperation->operation; + callbackParam.operation.reference = pOperation; + callbackParam.operation.result = pOperation->status; + + /* Invoke user callback function. */ + pOperation->notify.callback.function( pOperation->notify.callback.param1, + &callbackParam ); + + /* Once the user-provided callback returns, the operation can be destroyed. */ + AwsIotMqttInternal_DestroyOperation( pOperation ); + } + } + + AwsIotLogDebug( "Callback queue empty. Callback thread terminating." ); +} + +/*-----------------------------------------------------------*/ + +static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnection, + _mqttTimerEvent_t * const pPublishRetry ) +{ + bool status = true; + _mqttTimerEvent_t * pTimerListHead = NULL; + + /* Check arguments. */ + AwsIotMqtt_Assert( pPublishRetry->pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + AwsIotMqtt_Assert( pPublishRetry->retry.limit > 0 ); + AwsIotMqtt_Assert( pPublishRetry->retry.nextPeriod <= AWS_IOT_MQTT_RETRY_MS_CEILING ); + + /* Increment the number of retries. */ + pPublishRetry->retry.count++; + + /* Calculate when the PUBLISH retry event should expire relative to the current + * time. */ + pPublishRetry->expirationTime = AwsIotClock_GetTimeMs(); + + if( pPublishRetry->retry.count > pPublishRetry->retry.limit ) + { + /* Retry limit reached. Check for a response shortly. */ + pPublishRetry->expirationTime += AWS_IOT_MQTT_RESPONSE_WAIT_MS; + } + else + { + /* Expire at the next retry period. */ + pPublishRetry->expirationTime += pPublishRetry->retry.nextPeriod; + + /* Calculate the next retry period. PUBLISH retries use a truncated exponential + * backoff strategy. */ + if( pPublishRetry->retry.nextPeriod < AWS_IOT_MQTT_RETRY_MS_CEILING ) + { + pPublishRetry->retry.nextPeriod *= 2; + + if( pPublishRetry->retry.nextPeriod > AWS_IOT_MQTT_RETRY_MS_CEILING ) + { + pPublishRetry->retry.nextPeriod = AWS_IOT_MQTT_RETRY_MS_CEILING; + } + } + } + + AwsIotMutex_Lock( &( pMqttConnection->timerEventList.mutex ) ); + + /* Peek the current head of the timer event list. If the PUBLISH retry expires + * sooner, re-arm the timer to expire at the PUBLISH retry's expiration time. */ + pTimerListHead = AwsIotLink_Container( pMqttConnection->timerEventList.pHead, + _TIMER_EVENT_LINK_OFFSET ); + + if( ( pTimerListHead == NULL ) || + ( pTimerListHead->expirationTime > pPublishRetry->expirationTime ) ) + { + status = AwsIotClock_TimerArm( &( pMqttConnection->timer ), + pPublishRetry->expirationTime - AwsIotClock_GetTimeMs(), + 0 ); + + if( status == false ) + { + AwsIotLogError( "Failed to re-arm timer for connection %p.", pMqttConnection ); + } + } + + /* Insert the PUBLISH retry into the timer event list only if the timer + * is guaranteed to expire before its expiration time. */ + if( status == true ) + { + AwsIotList_InsertSorted( &( pMqttConnection->timerEventList ), + &( pPublishRetry->link ), + _TIMER_EVENT_LINK_OFFSET, + AwsIotMqttInternal_TimerEventCompare ); + } + + AwsIotMutex_Unlock( &( pMqttConnection->timerEventList.mutex ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_CreateQueues( void ) +{ + AwsIotQueueNotifyParams_t notifyParams = { 0 }; + + /* Create the send queue. */ + notifyParams.notifyRoutine = _sendThread; + + if( AwsIotQueue_Create( &_AwsIotMqttSendQueue, + ¬ifyParams, + AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) + { + return AWS_IOT_MQTT_INIT_FAILED; + } + + /* Create the receive queue. */ + if( AwsIotQueue_Create( &_AwsIotMqttReceiveQueue, + NULL, + 0 ) == false ) + { + AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); + + return AWS_IOT_MQTT_INIT_FAILED; + } + + /* Create the callback queue. */ + notifyParams.notifyRoutine = _callbackThread; + + if( AwsIotQueue_Create( &_AwsIotMqttCallbackQueue, + ¬ifyParams, + AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) + { + AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); + AwsIotQueue_Destroy( &_AwsIotMqttReceiveQueue ); + + return AWS_IOT_MQTT_INIT_FAILED; + } + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +int AwsIotMqttInternal_TimerEventCompare( void * pData1, + void * pData2 ) +{ + _mqttTimerEvent_t * pTimerEvent1 = ( _mqttTimerEvent_t * ) pData1, + *pTimerEvent2 = ( _mqttTimerEvent_t * ) pData2; + + if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) + { + return -1; + } + + if( pTimerEvent1->expirationTime > pTimerEvent2->expirationTime ) + { + return 1; + } + + return 0; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const pNewOperation, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo ) +{ + _mqttOperation_t * pOperation = NULL; + + AwsIotLogDebug( "Creating new MQTT operation record." ); + + /* If the waitable flag is set, make sure that there's no callback. */ + if( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) + { + if( pCallbackInfo != NULL ) + { + AwsIotLogError( "Callback should not be set for a waitable operation." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + } + + /* Allocate memory for a new operation. */ + pOperation = AwsIotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + AwsIotLogError( "Failed to allocate memory for new MQTT operation." ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) + { + /* The wait semaphore counts up to 2: once for when the send thread completes, + * and once for when the entire operation completes. */ + if( AwsIotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 2 ) == false ) + { + AwsIotLogError( "Failed to create semaphore for waitable MQTT operation." ); + + AwsIotMqtt_FreeOperation( pOperation ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->notify.callback = *pCallbackInfo; + } + } + + /* Initialize the flags and status members of the new operation. */ + pOperation->flags = flags; + pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; + + /* Set the output parameter. */ + *pNewOperation = pOperation; + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_DestroyOperation( void * pData ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + + AwsIotLogDebug( "Destroying operation %s.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + /* If the MQTT packet is still allocated, free it. */ + if( pOperation->pMqttPacket != NULL ) + { + /* Choose a free packet function. */ + void ( * freePacket )( uint8_t * ) = AwsIotMqttInternal_FreePacket; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pOperation->pMqttConnection->network.freePacket != NULL ) + { + freePacket = pOperation->pMqttConnection->network.freePacket; + } + #endif + + freePacket( pOperation->pMqttPacket ); + } + + /* Check if this operation is a PUBLISH. Clean up its retry event if + * necessary. */ + if( ( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) && + ( pOperation->pPublishRetry != NULL ) ) + { + AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerEventList.mutex ) ); + + /* Remove the timer event from the timer event list before freeing it. */ + if( AwsIotList_FindFirstMatch( pOperation->pMqttConnection->timerEventList.pHead, + _TIMER_EVENT_LINK_OFFSET, + pOperation->pPublishRetry, + NULL ) != NULL ) + { + AwsIotList_Remove( &( pOperation->pMqttConnection->timerEventList ), + &( pOperation->pPublishRetry->link ), + _TIMER_EVENT_LINK_OFFSET ); + } + + AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerEventList.mutex ) ); + + AwsIotMqtt_FreeTimerEvent( pOperation->pPublishRetry ); + } + + /* Check if a wait semaphore was created for this operation. */ + if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) + { + AwsIotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + } + + /* Free the memory used to hold operation data. */ + AwsIotMqtt_FreeOperation( pOperation ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) +{ + /* If the operation is waiting, post to its wait semaphore and return. */ + if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) + { + AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + + return; + } + + /* Add the operation to the callback queue if a callback was set. */ + if( pOperation->notify.callback.function != NULL ) + { + if( AwsIotQueue_InsertHead( &_AwsIotMqttCallbackQueue, + &( pOperation->link ) ) != true ) + { + AwsIotLogWarn( "Failed to create new callback thread." ); + } + } + else + { + /* Destroy the operation if no callback was set. */ + AwsIotMqttInternal_DestroyOperation( pOperation ); + } +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_OperationFind( AwsIotQueue_t * const pQueue, + AwsIotMqttOperationType_t operation, + const uint16_t * const pPacketIdentifier, + _mqttOperation_t ** const pOutput ) +{ + _mqttOperation_t * pResult = NULL; + _operationMatchParam_t param = { 0 }; + + AwsIotLogDebug( "Searching for in-progress operation %s in MQTT receive queue.", + AwsIotMqtt_OperationType( operation ) ); + + if( pPacketIdentifier != NULL ) + { + AwsIotLogDebug( "Searching for packet identifier %hu.", *pPacketIdentifier ); + } + + /* Set the search parameters. */ + param.operation = operation; + param.pPacketIdentifier = pPacketIdentifier; + + /* Find the first matching element in the queue. */ + pResult = AwsIotQueue_RemoveFirstMatch( pQueue, + _OPERATION_LINK_OFFSET, + ¶m, + _mqttOperation_match ); + + /* The result will be NULL if no corresponding operation was found in the + * queue. */ + if( pResult == NULL ) + { + AwsIotLogDebug( "In-progress operation %s not found.", + AwsIotMqtt_OperationType( operation ) ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* If a corresponding operation was found, set the output parameter. */ + *pOutput = pResult; + + AwsIotLogDebug( "Found in-progress operation %s.", + AwsIotMqtt_OperationType( operation ) ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c new file mode 100644 index 0000000000..b69b47ddc7 --- /dev/null +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -0,0 +1,1933 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_serialize.c + * @brief Implements functions that generate and decode MQTT network packets. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal includes. */ +#include "private/aws_iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +/* + * Macros for reading the high and low byte of a 2-byte unsigned int. + */ +#define _UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ +#define _UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ + +/** + * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. + * + * @param[in] ptr A uint8_t* that points to the high byte. + */ +#define _UINT16_DECODE( ptr ) \ + ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ + ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) + +/** + * @brief Macro for setting a bit in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to set. + * @param[in] position Which bit to set. + */ +#define _UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01 << position ) ) ) + +/** + * @brief Macro for checking if a bit is set in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to check. + * @param[in] position Which bit to check. + */ +#define _UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01 << position ) ) == ( 0x01 << position ) ) + +/* + * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT + * packet. + */ +#define _MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define _MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define _MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ +#define _MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ +#define _MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define _MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define _MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ + +/* + * Positions of each flag in the first byte of an MQTT PUBLISH packet's + * fixed header. + */ +#define _MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ +#define _MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ +#define _MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ +#define _MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ + +/** + * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. + */ +#define _MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) + +/** + * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT + * packet is this value. + */ +#define _MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) + +/** + * @brief The maximum possible size of a CONNECT packet. + * + * All strings in a CONNECT packet are constrained to 2-byte lengths, giving a + * maximum length smaller than the max "Remaining Length" constant above. + */ +#define _MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) + +/* + * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. + */ +#define _MQTT_PACKET_CONNACK_SIZE ( 4 ) /**< @brief A CONNACK packet is always 4 bytes in size. */ +#define _MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01 ) /**< @brief The "Session Present" bit is always the lowest bit. */ + +/* + * Constants relating to PUBLISH and PUBACK packets, defined by MQTT + * 3.1.1 spec. + */ +#define _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ( 9 ) /**< @brief The size of the smallest valid PUBLISH packet. */ +#define _MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ +#define _MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ + +/* + * Constants relating to SUBACK and UNSUBACK packets, defined by MQTT + * 3.1.1 spec. + */ +#define _MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid SUBACK packet. */ +#define _MQTT_PACKET_UNSUBACK_SIZE ( 4 ) /**< @brief An UNSUBACK packet is always 4 bytes in size. */ +#define _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ + +/* + * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. + */ +#define _MQTT_PACKET_PINGREQ_SIZE ( 2 ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ +#define _MQTT_PACKET_PINGRESP_SIZE ( 2 ) /**< @brief A PINGRESP packet is always 2 bytes in size. */ +#define _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0 ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ + +/* + * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. + */ +#define _MQTT_PACKET_DISCONNECT_SIZE ( 2 ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ + +/* + * Username for metrics with AWS IoT. + */ +#if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 + #ifndef AWS_IOT_SDK_VERSION + #error "AWS_IOT_SDK_VERSION must be defined." + #endif + + #define _AWS_IOT_METRICS_USERNAME ( "?SDK=C&Version=" AWS_IOT_SDK_VERSION ) /**< @brief Specify "C SDK" and SDK version. */ + #define _AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( _AWS_IOT_METRICS_USERNAME ) - 1 ) /**< @brief Length of #_AWS_IOT_METRICS_USERNAME. */ +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Generate and return a 2-byte packet identifier. + * + * This packet identifier will be nonzero. + * + * @return The packet identifier. + */ +static uint16_t _nextPacketIdentifier( void ); + +/** + * @brief Calculate the number of bytes required to encode an MQTT + * "Remaining length" field. + * + * @param[in] length The value of the "Remaining length" to encode. + * + * @return The size of the encoding of length. This is always `1`, `2`, `3`, or `4`. + */ +static size_t _remainingLengthEncodedSize( size_t length ); + +/** + * @brief Encode the "Remaining length" field per MQTT spec. + * + * @param[out] pDestination Where to write the encoded "Remaining length". + * @param[in] length The "Remaining length" to encode. + * + * @return Pointer to the end of the encoded "Remaining length", which is 1-4 + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold the encoded "Remaining length" using + * the function #_remainingLengthEncodedSize to avoid buffer overflows. + */ +static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, + size_t length ); + +/** + * @brief Decodes the "Remaining length" field in an MQTT packet. + * + * @param[in] pSource Pointer to the beginning of remaining length. + * @param[out] pEnd Set to point to the byte after the encoded "Remaining length". + * @param[out] pLength Set to the decoded remaining length. + * + * @return Pointer to the end of the encoded "Remaining length", which is 1-4 + * bytes greater than pSource. + * + * @warning This function does not check the size of `pSource`! + */ +static AwsIotMqttError_t _decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** const pEnd, + size_t * const pLength ); + +/** + * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. + * + * @param[out] pDestination Where to write the encoded string. + * @param[in] source The string to encode. + * @param[in] sourceLength The length of source. + * + * @return Pointer to the end of the encoded string, which is `sourceLength+2` + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer + * overflow. + */ +static uint8_t * _encodeString( uint8_t * pDestination, + const char * const source, + uint16_t sourceLength ); + +/** + * @brief Calculate the size and "Remaining length" of a CONNECT packet generated + * from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information struct. + * @param[in] pWillInfo User-provided Last Will and Testament info struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + */ +static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ); + +/** + * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated + * from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +static bool _publishPacketSize( const AwsIotMqttPublishInfo_t * const pPublishInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ); + +/** + * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE + * packet generated from the given parameters. + * + * @param[in] type Either AWS_IOT_MQTT_SUBSCRIBE or AWS_IOT_MQTT_UNSUBSCRIBE. + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + size_t * const pRemainingLength, + size_t * const pPacketSize ); + +/*-----------------------------------------------------------*/ + +#if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE +/** + * @brief If logging is enabled, define a log configuration that only prints the log + * string. This is used when printing out details of deserialized MQTT packets. + */ +static const AwsIotLogConfig_t _logHideAll = +{ + .hideLibraryName = true, + .hideLogLevel = true, + .hideTimestring = true +}; +#endif + +/** + * @brief Guards access to the packet identifier counter. + * + * Each packet should have a unique packet identifier. This mutex ensures that only + * one thread at a time may read the global packet identifer. + */ +static AwsIotMutex_t _packetIdentifierMutex; + +/*-----------------------------------------------------------*/ + +static uint16_t _nextPacketIdentifier( void ) +{ + static uint16_t nextPacketIdentifier = 1; + uint16_t newPacketIdentifier = 0; + + /* Lock the packet identifier mutex so that only one thread may read and + * modify nextPacketIdentifier. */ + AwsIotMutex_Lock( &_packetIdentifierMutex ); + + /* Read the next packet identifier. */ + newPacketIdentifier = nextPacketIdentifier; + + /* The next packet identifier will be greater by 2. This prevents packet + * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet + * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); + + /* Unlock the packet identifier mutex. */ + AwsIotMutex_Unlock( &_packetIdentifierMutex ); + + return newPacketIdentifier; +} + +/*-----------------------------------------------------------*/ + +static size_t _remainingLengthEncodedSize( size_t length ) +{ + /* length should have already been checked before calling this function. */ + AwsIotMqtt_Assert( length <= _MQTT_MAX_REMAINING_LENGTH ); + + /* Determine how many bytes are needed to encode length. + * The values below are taken from the MQTT 3.1.1 spec. */ + + /* 1 byte is needed to encode lengths between 0 and 127. */ + if( length < 128 ) + { + return 1; + } + + /* 2 bytes are needed to encode lengths between 128 and 16,383. */ + if( length < 16384 ) + { + return 2; + } + + /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ + if( length < 2097152 ) + { + return 3; + } + + /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ + return 4; +} + +/*-----------------------------------------------------------*/ + +static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, + size_t length ) +{ + uint8_t lengthByte = 0, * pLengthEnd = pDestination; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = length % 128; + length = length / 128; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( length > 0 ) + { + _UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( length > 0 ); + + return pLengthEnd; +} + +/*-----------------------------------------------------------*/ + +static AwsIotMqttError_t _decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** const pEnd, + size_t * const pLength ) +{ + uint8_t encodedByte = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152 ) /* 128 ^ 3 */ + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + encodedByte = *pSource; + pSource++; + + remainingLength += ( encodedByte & 0x7F ) * multiplier; + multiplier *= 128; + bytesDecoded++; + } while( ( encodedByte & 0x80 ) != 0 ); + + /* Check that the number of bytes decoded conforms to the spec-compliant + * representation of the remaining length. */ + if( bytesDecoded != _remainingLengthEncodedSize( remainingLength ) ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Valid remaining length should be at most 4 bytes. */ + AwsIotMqtt_Assert( bytesDecoded <= 4 ); + + /* Set the output parameters. */ + if( pLength != NULL ) + { + *pLength = remainingLength; + } + + if( pEnd != NULL ) + { + *pEnd = pSource; + } + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +static uint8_t * _encodeString( uint8_t * pDestination, + const char * const source, + uint16_t sourceLength ) +{ + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pDestination = _UINT16_HIGH_BYTE( sourceLength ); + pDestination++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pDestination = _UINT16_LOW_BYTE( sourceLength ); + pDestination++; + + /* Copy the string into pDestination. */ + ( void ) memcpy( pDestination, source, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pDestination += sourceLength; + + return pDestination; +} + +/*-----------------------------------------------------------*/ + +static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ) +{ + size_t connectPacketSize = 0; + + /* The CONNECT packet will always include a 10-byte variable header. */ + connectPacketSize += 10U; + + /* Add the length of the client identifier if provided. */ + if( pConnectInfo->clientIdentifierLength > 0 ) + { + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); + } + + /* Add the lengths of the will message and topic name if provided. */ + if( pWillInfo != NULL ) + { + connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) + + pWillInfo->payloadLength + sizeof( uint16_t ); + } + + /* Depending on the status of metrics, add the length of the metrics username + * or the user-provided username. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + connectPacketSize += _AWS_IOT_METRICS_USERNAME_LENGTH + sizeof( uint16_t ); + #endif + } + else + { + if( pConnectInfo->pUserName != NULL ) + { + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + } + } + + /* Add the length of the password if provided and not connecting to an AWS + * IoT MQTT server. */ + if( ( pConnectInfo->pPassword != NULL ) && + ( pConnectInfo->awsIotMqttMode == false ) ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + + /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has + * been calculated. Set the output parameter. */ + AwsIotMqtt_Assert( connectPacketSize < _MQTT_PACKET_CONNECT_MAX_SIZE ); + *pRemainingLength = connectPacketSize; + + /* Calculate the full size of the MQTT CONNECT packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. Set + * the pPacketSize output parameter. */ + connectPacketSize += 1 + _remainingLengthEncodedSize( connectPacketSize ); + *pPacketSize = connectPacketSize; + + /* Because of the 2-byte restriction on the length of all strings in a CONNECT + * packet, a CONNECT packet cannot be larger than 327700. */ + AwsIotMqtt_Assert( connectPacketSize < _MQTT_PACKET_CONNECT_MAX_SIZE ); +} + +/*-----------------------------------------------------------*/ + +static bool _publishPacketSize( const AwsIotMqttPublishInfo_t * const pPublishInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ) +{ + size_t publishPacketSize = 0; + + /* The variable header of a PUBLISH packet always contains the topic name. */ + publishPacketSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); + + /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte + * packet identifier. */ + if( pPublishInfo->QoS > 0 ) + { + publishPacketSize += sizeof( uint16_t ); + } + + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. Return if the "Remaining length" exceeds what is allowed + * by MQTT 3.1.1. Otherwise, set the output parameter. */ + publishPacketSize += pPublishInfo->payloadLength; + + if( publishPacketSize > _MQTT_MAX_REMAINING_LENGTH ) + { + return false; + } + + *pRemainingLength = publishPacketSize; + + /* Calculate the full size of the MQTT PUBLISH packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. Set + * the pPacketSize output parameter. */ + publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); + *pPacketSize = publishPacketSize; + + return true; +} + +/*-----------------------------------------------------------*/ + +static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + size_t * const pRemainingLength, + size_t * const pPacketSize ) +{ + size_t i = 0, subscriptionPacketSize = 0; + + /* Only SUBSCRIBE and UNSUBSCRIBE operations should call this function. */ + AwsIotMqtt_Assert( type == AWS_IOT_MQTT_SUBSCRIBE || type == AWS_IOT_MQTT_UNSUBSCRIBE ); + + /* The variable header of a subscription packet consists of a 2-byte packet + * identifier. */ + subscriptionPacketSize += sizeof( uint16_t ); + + /* Sum the lengths of all subscription topic filters; add 1 byte for each + * subscription's QoS if type is AWS_IOT_MQTT_SUBSCRIBE. */ + for( i = 0; i < subscriptionCount; i++ ) + { + /* Add the length of the topic filter. */ + subscriptionPacketSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); + + /* Only SUBSCRIBE packets include the QoS. */ + if( type == AWS_IOT_MQTT_SUBSCRIBE ) + { + subscriptionPacketSize += 1; + } + } + + /* At this point, the "Remaining length" has been calculated. Return if the + * "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, set + * the output parameter.*/ + if( subscriptionPacketSize > _MQTT_MAX_REMAINING_LENGTH ) + { + return false; + } + + *pRemainingLength = subscriptionPacketSize; + + /* Calculate the full size of the subscription packet by adding the size of the + * "Remaining length" field plus 1 byte for the "Packet type" field. Set the + * pPacketSize output parameter. */ + subscriptionPacketSize += 1 + _remainingLengthEncodedSize( subscriptionPacketSize ); + *pPacketSize = subscriptionPacketSize; + + return true; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ) +{ + /* Create the packet identifier mutex. */ + if( AwsIotMutex_Create( &_packetIdentifierMutex ) == false ) + { + return AWS_IOT_MQTT_INIT_FAILED; + } + + /* Call any additional serializer initialization function if serializer + * overrides are enabled. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef AwsIotMqttInternal_InitSerializeAdditional + if( AwsIotMqttInternal_InitSerializeAdditional() == false ) + { + AwsIotMutex_Destroy( &_packetIdentifierMutex ); + + return AWS_IOT_MQTT_INIT_FAILED; + } + #endif + #endif + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_CleanupSerialize( void ) +{ + /* Destroy the packet identifier mutex. */ + AwsIotMutex_Destroy( &_packetIdentifierMutex ); + + /* Call any additional serializer cleanup initialization function is serializer + * overrides are enabled. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef AwsIotMqttInternal_CleanupSerializeAdditional + AwsIotMqttInternal_CleanupSerializeAdditional(); + #endif + #endif +} + +/*-----------------------------------------------------------*/ + +uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, + size_t packetSize ) +{ + uint8_t packetType = 0; + + if( packetSize > 0 ) + { + /* The MQTT packet type is in the first byte of the packet. Mask out the + * lower bits to ignore flags. */ + packetType = *pPacket & 0xf0; + } + + return packetType; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + uint8_t ** const pConnectPacket, + size_t * const pPacketSize ) +{ + uint8_t connectFlags = 0; + size_t remainingLength = 0, connectPacketSize = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. */ + _connectPacketSize( pConnectInfo, + pWillInfo, + &remainingLength, + &connectPacketSize ); + + /* Total size of the connect packet should be larger than the "Remaining length" + * field. */ + AwsIotMqtt_Assert( connectPacketSize > remainingLength ); + + /* Allocate memory to hold the CONNECT packet. */ + pBuffer = AwsIotMqtt_MallocMessage( connectPacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + AwsIotLogError( "Failed to allocate memory for CONNECT packet." ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pConnectPacket = pBuffer; + *pPacketSize = connectPacketSize; + + /* The first byte in the CONNECT packet is the control packet type. */ + *pBuffer = _MQTT_PACKET_TYPE_CONNECT; + pBuffer++; + + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + pBuffer = _encodeString( pBuffer, "MQTT", 4 ); + + /* The MQTT protocol version is the second byte of the variable header. */ + *pBuffer = _MQTT_VERSION_3_1_1; + pBuffer++; + + /* Set the CONNECT flags based on the given parameters. */ + if( pConnectInfo->cleanSession == true ) + { + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_CLEAN ); + } + + /* AWS IoT MQTT servers do not use the MQTT password. */ + if( ( pConnectInfo->pPassword != NULL ) && + ( pConnectInfo->awsIotMqttMode == false ) ) + { + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_PASSWORD ); + } + + /* If metrics are enabled, always set the username flag when connecting + * to an AWS IoT MQTT server. Otherwise, set the username flag only when + * not connecting to an AWS IoT MQTT server. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_USERNAME ); + #endif + } + else + { + if( pConnectInfo->pUserName != NULL ) + { + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_USERNAME ); + } + } + + if( pWillInfo != NULL ) + { + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for will QoS 1 and 2. */ + switch( pWillInfo->QoS ) + { + case 1: + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS1 ); + break; + + case 2: + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS2 ); + break; + + default: + break; + } + + if( pWillInfo->retain == true ) + { + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + } + + *pBuffer = connectFlags; + pBuffer++; + + /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ + *pBuffer = _UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pBuffer + 1 ) = _UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + pBuffer += 2; + + /* Write the client identifier into the CONNECT packet. */ + pBuffer = _encodeString( pBuffer, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + + /* Write the will topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) + { + pBuffer = _encodeString( pBuffer, + pWillInfo->pTopicName, + pWillInfo->topicNameLength ); + + pBuffer = _encodeString( pBuffer, + pWillInfo->pPayload, + ( uint16_t ) pWillInfo->payloadLength ); + } + + /* If metrics are enabled, write the metrics username into the CONNECT packet. + * Otherwise, write the username only when not connecting to an AWS IoT MQTT + * server. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + AwsIotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " + "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); + + pBuffer = _encodeString( pBuffer, + _AWS_IOT_METRICS_USERNAME, + _AWS_IOT_METRICS_USERNAME_LENGTH ); + #endif + } + else + { + if( pConnectInfo->pUserName != NULL ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + } + } + + /* Write the password into the CONNECT packet if provided and this connection + * is not to an AWS IoT MQTT server. */ + if( ( pConnectInfo->pPassword != NULL ) && + ( pConnectInfo->awsIotMqttMode == false ) ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ + AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pConnectPacket ) == connectPacketSize ); + + /* Print out the serialized CONNECT packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const pConnackStart, + size_t dataLength, + size_t * const pBytesProcessed ) +{ + /* If logging is enabled, declare the CONNACK response code strings. The + * fourth byte of CONNACK indexes into this array for the corresponding response. */ + #if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + static const char * pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; + #endif + + /* According to MQTT 3.1.1, CONNACK packets are all 4 bytes in size. If the + * data stream has fewer than 4 bytes, then the CONNACK packet is incomplete. */ + if( dataLength < _MQTT_PACKET_CONNACK_SIZE ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "CONNACK less than %d bytes in size.", + _MQTT_PACKET_CONNACK_SIZE ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* The next 4 bytes will be processed by this function. */ + *pBytesProcessed = _MQTT_PACKET_CONNACK_SIZE; + + /* Check that the control packet type is 0x20. */ + if( pConnackStart[ 0 ] != _MQTT_PACKET_TYPE_CONNACK ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pConnackStart[ 0 ] ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* According to MQTT 3.1.1, the second byte of CONNACK must specify a + * "Remaining length" of 2. */ + if( pConnackStart[ 1 ] != _MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "CONNACK does not have remaining length of %d.", + _MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Check the reserved bits in CONNACK. The high 7 bits of the second byte + * in CONNACK must be 0. */ + if( ( pConnackStart[ 2 ] | 0x01 ) != 0x01 ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "Reserved bits in CONNACK incorrect." ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Determine if the "Session Present" bit it set. This is the lowest bit of + * the second byte in CONNACK. */ + if( ( pConnackStart[ 2 ] & _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit set." ); + + /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pConnackStart[ 3 ] != 0 ) + { + return AWS_IOT_MQTT_BAD_RESPONSE; + } + } + else + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit not set." ); + } + + /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ + if( pConnackStart[ 3 ] > 5 ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK response %hhu is not valid.", + pConnackStart[ 3 ] ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Print the appropriate message for the CONNACK response code if logs are + * enabled. */ + #if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "%s", + pConnackResponses[ pConnackStart[ 3 ] ] ); + #endif + + /* A nonzero CONNACK response code means the connection was refused. */ + if( pConnackStart[ 3 ] > 0 ) + { + return AWS_IOT_MQTT_SERVER_REFUSED; + } + + /* A zero CONNACK response code means the connection was accepted. */ + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint8_t ** const pPublishPacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + uint8_t publishFlags = 0; + uint16_t packetIdentifier = 0; + size_t remainingLength = 0, publishPacketSize = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _publishPacketSize( pPublishInfo, + &remainingLength, + &publishPacketSize ) == false ) + { + AwsIotLogError( "Publish packet remaining length exceeds %d, which is the " + "maximum size allowed by MQTT 3.1.1.", + _MQTT_MAX_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Total size of the publish packet should be larger than the "Remaining length" + * field. */ + AwsIotMqtt_Assert( publishPacketSize > remainingLength ); + + /* Allocate memory to hold the PUBLISH packet. */ + pBuffer = AwsIotMqtt_MallocMessage( publishPacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + AwsIotLogError( "Failed to allocate memory for PUBLISH packet." ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPublishPacket = pBuffer; + *pPacketSize = publishPacketSize; + + /* The first byte of a PUBLISH packet contains the packet type and flags. */ + publishFlags = _MQTT_PACKET_TYPE_PUBLISH; + + if( pPublishInfo->QoS == 1 ) + { + _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ); + } + else if( pPublishInfo->QoS == 2 ) + { + _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS2 ); + } + + if( pPublishInfo->retain == true ) + { + _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); + } + + *pBuffer = publishFlags; + pBuffer++; + + /* The "Remaining length" is encoded from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* The topic name is placed after the "Remaining length". */ + pBuffer = _encodeString( pBuffer, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + + /* A packet identifier is required for QoS 1 and 2 messages. */ + if( pPublishInfo->QoS > 0 ) + { + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + AwsIotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the PUBLISH packet. */ + *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + } + + /* The payload is placed after the packet identifier. */ + if( pPublishInfo->payloadLength > 0 ) + { + ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + pBuffer += pPublishInfo->payloadLength; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ + AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pPublishPacket ) == publishPacketSize ); + + /* Print out the serialized PUBLISH packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, + uint8_t * const pPublishPacket, + uint16_t * const pNewPacketIdentifier ) +{ + const uint8_t * pTopicNameLength = NULL; + uint8_t * pPacketIdentifier = NULL; + uint16_t topicNameLength = 0, newPacketIdentifier = _nextPacketIdentifier(); + + /* For an AWS IoT MQTT server, change the packet identifier. */ + if( awsIotMqttMode == true ) + { + /* Decode the "Remaining length" to find where it ends. Because the + * "Remaining length" was not received from the network, it is "trusted" + * so the return value of this function isn't checked. */ + ( void ) _decodeRemainingLength( pPublishPacket + 1, + &pTopicNameLength, + NULL ); + + /* Decode the topic name length and calculate the address of the packet identifier. */ + topicNameLength = _UINT16_DECODE( pTopicNameLength ); + pPacketIdentifier = ( uint8_t * ) ( pTopicNameLength + topicNameLength + sizeof( uint16_t ) ); + + AwsIotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", + *pPacketIdentifier, + newPacketIdentifier ); + + /* Replace the packet identifier. */ + *pPacketIdentifier = _UINT16_HIGH_BYTE( newPacketIdentifier ); + *( pPacketIdentifier + 1 ) = _UINT16_LOW_BYTE( newPacketIdentifier ); + *pNewPacketIdentifier = newPacketIdentifier; + } + else + { + /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ + _UINT8_SET_BIT( *pPublishPacket, _MQTT_PUBLISH_FLAG_DUP ); + } +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const pPublishStart, + size_t dataLength, + AwsIotMqttPublishInfo_t * const pOutput, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + size_t remainingLength = 0, packetSize = 0; + uint8_t publishFlags = 0; + uint16_t packetIdentifier = 0; + const uint8_t * pVariableHeader = NULL, * pPacketIdentifierHigh = NULL; + + /* Ensure that at least 9 bytes are available. If not, this is an incomplete + * PUBLISH packet. */ + if( dataLength < _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ) + { + AwsIotLogError( "PUBLISH size %lu is smaller than the smallest possible " + "size %d.", + ( unsigned long ) dataLength, + _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Decode the "Remaining length", which is the second byte in PUBLISH. */ + if( _decodeRemainingLength( pPublishStart + 1, + &pVariableHeader, + &remainingLength ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Bad remaining length." ); + + /* If the "Remaining length" couldn't be determined, invalidate the rest + * of the data stream by marking it processed. */ + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length %lu.", ( unsigned long ) remainingLength ); + + /* Calculate the packet size and set the output parameter. */ + packetSize = remainingLength + _remainingLengthEncodedSize( remainingLength ) + 1; + *pBytesProcessed = packetSize; + + /* Ensure that the PUBLISH packet fits within the data stream. If it doesn't, + * then this is a partial packet. */ + if( packetSize > dataLength ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "PUBLISH packet size %lu exceeds data length %lu.", + ( unsigned long ) packetSize, + ( unsigned long ) dataLength ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* The flags are the lower 4 bits of the first byte in PUBLISH. */ + publishFlags = *pPublishStart; + + /* Parse the Retain bit. */ + pOutput->retain = _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Retain bit is %d.", pOutput->retain ); + + /* Check for QoS 2. */ + if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS2 ) == true ) + { + /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ + if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Bad QoS: 3." ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + pOutput->QoS = 2; + } + /* Check for QoS 1. */ + else if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + pOutput->QoS = 1; + } + /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ + else + { + pOutput->QoS = 0; + } + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "QoS is %d.", pOutput->QoS ); + + /* Parse the DUP bit. */ + if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_DUP ) == true ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 1." ); + } + else + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 0." ); + } + + /* Sanity checks for "Remaining length". */ + if( pOutput->QoS == 0 ) + { + /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate + * topic name length (2 bytes) and topic name (at least 1 byte). */ + if( remainingLength < 3 ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "QoS 0 PUBLISH cannot have a remaining length less than 3." ); + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + } + else + { + /* A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 to + * accommodate a packet identifier as well as the topic name length and + * topic name. */ + if( remainingLength < 5 ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + } + + /* Extract the topic name starting from the first byte of the variable header. + * The topic name string starts at byte 3 in the variable header. */ + pOutput->topicNameLength = _UINT16_DECODE( pVariableHeader ); + + /* Sanity checks for topic name length and "Remaining length". */ + if( pOutput->QoS == 0 ) + { + /* Check that the "Remaining length" is at least as large as the variable + * header. */ + if( remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + } + else + { + /* Check that the "Remaining length" is at least as large as the variable + * header. */ + if( remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + } + + /* Parse the topic. */ + pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Topic name length %hu: %.*s", + pOutput->topicNameLength, + pOutput->topicNameLength, + pOutput->pTopicName ); + + /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet + * identifier starts immediately after the topic name. */ + pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); + + if( pOutput->QoS > 0 ) + { + packetIdentifier = _UINT16_DECODE( pPacketIdentifierHigh ); + *pPacketIdentifier = packetIdentifier; + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( packetIdentifier == 0 ) + { + return AWS_IOT_MQTT_BAD_RESPONSE; + } + } + + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain + * a packet identifer, but QoS 0 PUBLISH packets do not. */ + if( pOutput->QoS == 0 ) + { + pOutput->payloadLength = ( uint16_t ) ( remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh; + } + else + { + pOutput->payloadLength = ( uint16_t ) ( remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); + } + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Payload length %hu. Payload:", pOutput->payloadLength ); + AwsIotLog_PrintBuffer( NULL, pOutput->pPayload, pOutput->payloadLength ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializePuback( uint16_t packetIdentifier, + uint8_t ** const pPubackPacket, + size_t * const pPacketSize ) +{ + /* Allocate memory for PUBACK. */ + uint8_t * pBuffer = AwsIotMqtt_MallocMessage( _MQTT_PACKET_PUBACK_SIZE ); + + if( pBuffer == NULL ) + { + AwsIotLogError( "Failed to allocate memory for PUBACK packet" ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPubackPacket = pBuffer; + *pPacketSize = _MQTT_PACKET_PUBACK_SIZE; + + /* Set the 4 bytes in PUBACK. */ + pBuffer[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; + pBuffer[ 1 ] = _MQTT_PACKET_PUBACK_REMAINING_LENGTH; + pBuffer[ 2 ] = _UINT16_HIGH_BYTE( packetIdentifier ); + pBuffer[ 3 ] = _UINT16_LOW_BYTE( packetIdentifier ); + + /* Print out the serialized PUBACK packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, _MQTT_PACKET_PUBACK_SIZE ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pPubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + uint16_t packetIdentifier = 0; + + /* According to MQTT 3.1.1, PUBACK packets are all 4 bytes in size. If the + * data stream has fewer than 4 bytes, then the PUBACK packet is incomplete. */ + if( dataLength < _MQTT_PACKET_PUBACK_SIZE ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "PUBACK less than %d bytes in size.", + _MQTT_PACKET_PUBACK_SIZE ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* The next 4 bytes will be processed by this function. */ + *pBytesProcessed = _MQTT_PACKET_PUBACK_SIZE; + + /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ + packetIdentifier = _UINT16_DECODE( pPubackStart + 2 ); + *pPacketIdentifier = packetIdentifier; + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( packetIdentifier == 0 ) + { + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Check that the control packet type is 0x40. */ + if( pPubackStart[ 0 ] != _MQTT_PACKET_TYPE_PUBACK ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPubackStart[ 0 ] ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Check the "Remaining length" (second byte) of the received PUBACK. */ + if( pPubackStart[ 1 ] != _MQTT_PACKET_PUBACK_REMAINING_LENGTH ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "PUBACK does not have remaining length of %d.", + _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pSubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + size_t i = 0, subscribePacketSize = 0, remainingLength = 0; + uint16_t packetIdentifier = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _subscriptionPacketSize( AWS_IOT_MQTT_SUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &subscribePacketSize ) == false ) + { + AwsIotLogError( "Subscribe packet remaining length exceeds %d, which is the " + "maximum size allowed by MQTT 3.1.1.", + _MQTT_MAX_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + AwsIotMqtt_Assert( subscribePacketSize > remainingLength ); + + /* Allocate memory to hold the SUBSCRIBE packet. */ + pBuffer = AwsIotMqtt_MallocMessage( subscribePacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + AwsIotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pSubscribePacket = pBuffer; + *pPacketSize = subscribePacketSize; + + /* The first byte in SUBSCRIBE is the packet type. */ + *pBuffer = _MQTT_PACKET_TYPE_SUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + AwsIotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the SUBSCRIBE packet. */ + *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter and QoS. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + /* Place the QoS in the SUBSCRIBE packet. */ + *pBuffer = ( uint8_t ) pSubscriptionList[ i ].QoS; + pBuffer++; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ + AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pSubscribePacket ) == subscribePacketSize ); + + /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t mqttConnection, + const uint8_t * const pSubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; + size_t i = 0, remainingLength = 0, packetSize = 0; + uint16_t packetIdentifier = 0; + uint8_t subscriptionStatus = 0; + const uint8_t * pVariableHeader = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Ensure that at least 5 bytes are available. If not, this is an incomplete + * SUBACK packet. */ + if( dataLength < _MQTT_PACKET_SUBACK_MINIMUM_SIZE ) + { + AwsIotLogError( "SUBACK size %lu is smaller than the smallest possible " + "size %d.", + ( unsigned long ) dataLength, + _MQTT_PACKET_SUBACK_MINIMUM_SIZE ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Decode the "Remaining length" in SUBACK, which starts at byte 2. */ + if( _decodeRemainingLength( pSubackStart + 1, + &pVariableHeader, + &remainingLength ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Bad remaining length." ); + + /* If the "Remaining length" couldn't be determined, invalidate the rest + * of the data stream by marking it processed. */ + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length %lu.", ( unsigned long ) remainingLength ); + + /* A SUBACK must have a remaining length of at least 3 to accommodate the + * packet identifer and at least 1 return code. */ + if( remainingLength < 3 ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "SUBACK cannot have a remaining length less than 3." ); + *pBytesProcessed = dataLength; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Calculate the packet size and set the output parameter. */ + packetSize = remainingLength + _remainingLengthEncodedSize( remainingLength ) + 1; + *pBytesProcessed = packetSize; + + /* Ensure that the SUBACK packet fits within the data stream. If it doesn't, + * then this is a partial packet. */ + if( packetSize > dataLength ) + { + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "SUBACK packet size %lu exceeds data length %lu.", + ( unsigned long ) packetSize, + ( unsigned long ) dataLength ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ + packetIdentifier = _UINT16_DECODE( pVariableHeader ); + *pPacketIdentifier = packetIdentifier; + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); + + /* Check that the control packet type is 0x90. */ + if( pSubackStart[ 0 ] != _MQTT_PACKET_TYPE_SUBACK ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pSubackStart[ 0 ] ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Iterate through each status byte in the SUBACK packet. */ + for( i = 0; i < remainingLength - sizeof( uint16_t ); i++ ) + { + /* Read a single status byte in SUBACK. */ + subscriptionStatus = *( pVariableHeader + sizeof( uint16_t ) + i ); + + /* MQTT 3.1.1 defines the following values as status codes. */ + switch( subscriptionStatus ) + { + case 0x00: + case 0x01: + case 0x02: + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); + break; + + case 0x80: + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu refused.", ( unsigned long ) i ); + + /* Remove a rejected subscription from the subscription manager. */ + AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, + packetIdentifier, + ( long ) i ); + + status = AWS_IOT_MQTT_SERVER_REFUSED; + + break; + + default: + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + status = AWS_IOT_MQTT_BAD_RESPONSE; + + break; + } + + /* Stop parsing the subscription statuses if a bad response was received. */ + if( status == AWS_IOT_MQTT_BAD_RESPONSE ) + { + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pUnsubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + size_t i = 0, unsubscribePacketSize = 0, remainingLength = 0; + uint16_t packetIdentifier = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _subscriptionPacketSize( AWS_IOT_MQTT_UNSUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &unsubscribePacketSize ) == false ) + { + AwsIotLogError( "Unsubscribe packet remaining length exceeds %d, which is the " + "maximum size allowed by MQTT 3.1.1.", + _MQTT_MAX_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Total size of the unsubscribe packet should be larger than the "Remaining length" + * field. */ + AwsIotMqtt_Assert( unsubscribePacketSize > remainingLength ); + + /* Allocate memory to hold the UNSUBSCRIBE packet. */ + pBuffer = AwsIotMqtt_MallocMessage( unsubscribePacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + AwsIotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pUnsubscribePacket = pBuffer; + *pPacketSize = unsubscribePacketSize; + + /* The first byte in UNSUBSCRIBE is the packet type. */ + *pBuffer = _MQTT_PACKET_TYPE_UNSUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + AwsIotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the UNSUBSCRIBE packet. */ + *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ + AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pUnsubscribePacket ) == unsubscribePacketSize ); + + /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const pUnsubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + uint16_t packetIdentifier = 0; + + /* According to MQTT 3.1.1, UNSUBACK packets are all 4 bytes in size. If the + * data stream has fewer than 4 bytes, then the UNSUBACK packet is incomplete. */ + if( dataLength < _MQTT_PACKET_UNSUBACK_SIZE ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "UNSUBACK less than %d bytes in size.", + _MQTT_PACKET_UNSUBACK_SIZE ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* The next 4 bytes will be processed by this function. */ + *pBytesProcessed = _MQTT_PACKET_UNSUBACK_SIZE; + + /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ + packetIdentifier = _UINT16_DECODE( pUnsubackStart + 2 ); + *pPacketIdentifier = packetIdentifier; + + /* Packet identifier cannot be 0. */ + if( packetIdentifier == 0 ) + { + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Check that the control packet type is 0xb0. */ + if( pUnsubackStart[ 0 ] != _MQTT_PACKET_TYPE_UNSUBACK ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pUnsubackStart[ 0 ] ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ + if( pUnsubackStart[ 1 ] != _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "UNSUBACK does not have remaining length of %d.", + _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + AwsIotLog( AWS_IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreqPacket, + size_t * const pPacketSize ) +{ + /* PINGREQ packets are always the same. */ + static const uint8_t pPingreq[ _MQTT_PACKET_PINGREQ_SIZE ] = + { + _MQTT_PACKET_TYPE_PINGREQ, + 0x00 + }; + + /* Set the output parameters. */ + *pPingreqPacket = ( uint8_t * ) pPingreq; + *pPacketSize = _MQTT_PACKET_PINGREQ_SIZE; + + /* Print out the PINGREQ packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, _MQTT_PACKET_PINGREQ_SIZE ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const pPingrespStart, + size_t dataLength, + size_t * const pBytesProcessed ) +{ + /* According to MQTT 3.1.1, PINGRESP packets are all 2 bytes in size. If the + * data stream has fewer than 2 bytes, then the PINGRESP packet is incomplete. */ + if( dataLength < _MQTT_PACKET_PINGRESP_SIZE ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "PINGRESP less than %d bytes in size.", + _MQTT_PACKET_PINGRESP_SIZE ); + *pBytesProcessed = 0; + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* The next 2 bytes will be processed by this function. */ + *pBytesProcessed = _MQTT_PACKET_PINGRESP_SIZE; + + /* Check that the control packet type is 0xd0. */ + if( pPingrespStart[ 0 ] != _MQTT_PACKET_TYPE_PINGRESP ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPingrespStart[ 0 ] ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + /* Check the "Remaining length" (second byte) of the received PINGRESP. */ + if( pPingrespStart[ 1 ] != _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + { + AwsIotLog( AWS_IOT_LOG_ERROR, + &_logHideAll, + "PINGRESP does not have remaining length of %d.", + _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + + return AWS_IOT_MQTT_BAD_RESPONSE; + } + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_SerializeDisconnect( uint8_t ** const pDisconnectPacket, + size_t * const pPacketSize ) +{ + /* DISCONNECT packets are always the same. */ + static const uint8_t pDisconnect[ _MQTT_PACKET_DISCONNECT_SIZE ] = + { + _MQTT_PACKET_TYPE_DISCONNECT, + 0x00 + }; + + /* Set the output parameters. */ + *pDisconnectPacket = ( uint8_t * ) pDisconnect; + *pPacketSize = _MQTT_PACKET_DISCONNECT_SIZE; + + /* Print out the DISCONNECT packet for debugging purposes. */ + AwsIotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, _MQTT_PACKET_DISCONNECT_SIZE ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ) +{ + uint8_t packetType = *pPacket; + + /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static + * memory. */ + if( ( packetType != _MQTT_PACKET_TYPE_DISCONNECT ) && + ( packetType != _MQTT_PACKET_TYPE_PINGREQ ) ) + { + AwsIotMqtt_FreeMessage( pPacket ); + } +} + +/*-----------------------------------------------------------*/ + +/* If the MQTT library is being tested, include a file that allows access to + * internal functions and variables. */ +#if AWS_IOT_MQTT_TEST == 1 + #include "aws_iot_test_access_mqtt_serialize.c" +#endif diff --git a/lib/source/mqtt/aws_iot_mqtt_subscription.c b/lib/source/mqtt/aws_iot_mqtt_subscription.c new file mode 100644 index 0000000000..5660ee3655 --- /dev/null +++ b/lib/source/mqtt/aws_iot_mqtt_subscription.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_subscription.c + * @brief Implements functions that manage subscriptions for an MQTT connection. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_topicMatch. + */ +typedef struct _topicMatchParams +{ + const char * pTopicName; /**< @brief The topic name to parse. */ + uint16_t topicNameLength; /**< @brief Length of #_topicMatchParams_t.pTopicName. */ + bool exactMatchOnly; /**< @brief Whether to allow wildcards or require exact matches. */ +} _topicMatchParams_t; + +/** + * @brief First parameter to #_packetMatch. + */ +typedef struct _packetMatchParams +{ + uint16_t packetIdentifier; /**< Packet identifier to match. */ + long order; /**< Order to match. Set to `-1` to ignore. */ +} _packetMatchParams_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Matches a topic name (from a publish) with a topic filter (from a + * subscription). + * + * @param[in] pArgument Pointer to a #_topicMatchParams_t. + * @param[in] pData Pointer to a #_mqttSubscription_t containing the topic filter + * and name to check. + * + * @return `true` if the arguments match the subscription topic filter; `false` + * otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility with + * @ref list_function_findfirstmatch. + */ +static bool _topicMatch( void * pArgument, + void * pData ); + +/** + * @brief Matches a packet identifier and order. + * + * @param[in] pArgument Pointer to a #_packetMatchParams_t. + * @param[in] pData Pointer to a #_mqttSubscription_t containing the packet identifier + * and order to check. + * + * @return `true` if the arguments match the subscription's packet info; `false` + * otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility with + * @ref list_function_findfirstmatch. + */ +static bool _packetMatch( void * pArgument, + void * pData ); + +/*-----------------------------------------------------------*/ + +static bool _topicMatch( void * pArgument, + void * pData ) +{ + uint16_t nameIndex = 0, filterIndex = 0; + _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pArgument; + _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + + /* Extract the relevant strings and lengths from parameters. */ + const char * const pTopicName = pParam->pTopicName; + const char * const pTopicFilter = pSubscription->pTopicFilter; + const uint16_t topicNameLength = pParam->topicNameLength; + const uint16_t topicFilterLength = pSubscription->topicFilterLength; + + /* Check for an exact match. */ + if( topicNameLength == topicFilterLength ) + { + return( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); + } + + /* If the topic lengths are different but an exact match is required, return + * false. */ + if( pParam->exactMatchOnly == true ) + { + return false; + } + + while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) + { + /* Check if the character in the topic name matches the corresponding + * character in the topic filter string. */ + if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) + { + /* Handle special corner cases as documented by the MQTT protocol spec. */ + + /* Filter "sport/#" also matches "sport" since # includes the parent level. */ + if( ( nameIndex == topicNameLength - 1 ) && + ( filterIndex == topicFilterLength - 3 ) && + ( pTopicFilter[ filterIndex + 1 ] == '/' ) && + ( pTopicFilter[ filterIndex + 2 ] == '#' ) ) + { + return true; + } + + /* Filter "sport/+" also matches the "sport/" but not "sport". */ + if( ( nameIndex == topicNameLength - 1 ) && + ( filterIndex == topicFilterLength - 2 ) && + ( pTopicFilter[ filterIndex + 1 ] == '+' ) ) + { + return true; + } + } + else + { + /* Check for wildcards. */ + if( pTopicFilter[ filterIndex ] == '+' ) + { + /* Move topic name index to the end of the current level. + * This is identified by '/'. */ + while( nameIndex < topicNameLength && pTopicName[ nameIndex ] != '/' ) + { + nameIndex++; + } + + /* Increment filter index to skip '/'. */ + filterIndex++; + continue; + } + else if( pTopicFilter[ filterIndex ] == '#' ) + { + /* Subsequent characters don't need to be checked if the for the + * multi-level wildcard. */ + return true; + } + else + { + /* Any character mismatch other than '+' or '#' means the topic + * name does not match the topic filter. */ + return false; + } + } + + /* Increment indexes. */ + nameIndex++; + filterIndex++; + } + + /* If the end of both strings has been reached, they match. */ + if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) + { + return true; + } + + return false; +} + +/*-----------------------------------------------------------*/ + +static bool _packetMatch( void * pArgument, + void * pData ) +{ + bool match = false; + _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pArgument; + _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + + /* Compare packet identifiers. */ + if( pParam->packetIdentifier == pSubscription->packetInfo.identifier ) + { + /* Compare orders if order is not -1. */ + if( pParam->order == -1 ) + { + match = true; + } + else + { + match = ( ( size_t ) pParam->order ) == pSubscription->packetInfo.order; + } + } + + /* If this subscription should be removed, check the reference count. */ + if( match == true ) + { + /* Reference count must not be negative. */ + AwsIotMqtt_Assert( pSubscription->references >= 0 ); + + /* If the reference count is positive, this subscription cannot be + * removed yet because there are subscription callbacks using it. */ + if( pSubscription->references > 0 ) + { + match = false; + + /* Set the unsubscribed flag. The last active subscription callback + * will remove and clean up this subscription. */ + pSubscription->unsubscribed = true; + } + } + + return match; +} + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const pMqttConnection, + uint16_t subscribePacketIdentifier, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount ) +{ + size_t i = 0; + _mqttSubscription_t * pNewSubscription = NULL; + _topicMatchParams_t topicMatchParams = { .exactMatchOnly = true }; + + AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + + for( i = 0; i < subscriptionCount; i++ ) + { + /* Check if this topic filter is already registered. */ + topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; + topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; + pNewSubscription = AwsIotList_FindFirstMatch( pMqttConnection->subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + _topicMatch ); + + if( pNewSubscription != NULL ) + { + /* The lengths of exactly matching topic filters must match. */ + AwsIotMqtt_Assert( pNewSubscription->topicFilterLength == pSubscriptionList[ i ].topicFilterLength ); + + /* Replace the callback and packet info with the new parameters. */ + pNewSubscription->callback = pSubscriptionList[ i ].callback; + pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; + pNewSubscription->packetInfo.order = i; + } + else + { + /* Allocate memory for a new subscription. */ + pNewSubscription = AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + + pSubscriptionList[ i ].topicFilterLength ); + + /* If memory allocation failed, remove all previously added subscriptions. */ + if( pNewSubscription == NULL ) + { + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, + pSubscriptionList, + i ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Clear the new subscription. */ + ( void ) memset( pNewSubscription, + 0x00, + sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); + + /* Set the members of the new subscription and add it to the list. */ + pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; + pNewSubscription->packetInfo.order = i; + pNewSubscription->callback = pSubscriptionList[ i ].callback; + pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; + ( void ) strncpy( pNewSubscription->pTopicFilter, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + AwsIotList_InsertHead( &( pMqttConnection->subscriptionList ), + &( pNewSubscription->link ), + _SUBSCRIPTION_LINK_OFFSET ); + } + } + + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + + return AWS_IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnection, + AwsIotMqttCallbackParam_t * const pCallbackParam ) +{ + _mqttSubscription_t * pSubscription = NULL; + AwsIotLink_t * pStartPoint = NULL; + void * pParam1 = NULL; + + void ( * callbackFunction )( void *, + AwsIotMqttCallbackParam_t * const ) = NULL; + const _topicMatchParams_t topicMatchParams = + { + .pTopicName = pCallbackParam->message.info.pTopicName, + .topicNameLength = pCallbackParam->message.info.topicNameLength, + .exactMatchOnly = false + }; + + /* Prevent any other thread from modifying the subscription list while this + * function is searching. */ + AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + pStartPoint = pMqttConnection->subscriptionList.pHead; + + /* Search the subscription list for all matching subscriptions. */ + while( pStartPoint != NULL ) + { + pSubscription = AwsIotList_FindFirstMatch( pStartPoint, + _SUBSCRIPTION_LINK_OFFSET, + ( void * ) ( &topicMatchParams ), + _topicMatch ); + + /* No subscription found. Exit loop. */ + if( pSubscription == NULL ) + { + break; + } + + /* Subscription validation should not have allowed a NULL callback function. */ + AwsIotMqtt_Assert( pSubscription->callback.function != NULL ); + + /* Increment the subscription's reference count. */ + ( pSubscription->references )++; + + /* Copy the necessary members of the subscription before releasing the + * subscription list mutex. */ + pParam1 = pSubscription->callback.param1; + callbackFunction = pSubscription->callback.function; + + /* Unlock the subscription list mutex. */ + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + + /* Set the members of the callback parameter. */ + pCallbackParam->mqttConnection = pMqttConnection; + pCallbackParam->message.pTopicFilter = pSubscription->pTopicFilter; + pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; + + /* Invoke the subscription callback. */ + callbackFunction( pParam1, pCallbackParam ); + + /* Lock the subscription list mutex to decrement the reference count. */ + AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + + /* Decrement the reference count. It must still be positive. */ + ( pSubscription->references )--; + AwsIotMqtt_Assert( pSubscription->references >= 0 ); + + /* Restart the search at the next subscription. */ + pStartPoint = pSubscription->link.pNext; + + /* Remove this subscription if it has no references and the unsubscribed + * flag is set. */ + if( ( pSubscription->references == 0 ) && ( pSubscription->unsubscribed == true ) ) + { + AwsIotList_Remove( &( pMqttConnection->subscriptionList ), + &( pSubscription->link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotMqtt_FreeSubscription( pSubscription ); + } + } + + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_RemoveSubscriptionByPacket( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier, + long order ) +{ + const _packetMatchParams_t packetMatchParams = + { + .packetIdentifier = packetIdentifier, + .order = order + }; + + AwsIotList_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + ( void * ) &packetMatchParams, + _packetMatch, + AwsIotMqtt_FreeSubscription ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * const pMqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount ) +{ + size_t i = 0; + _mqttSubscription_t * pSubscription = NULL; + _topicMatchParams_t topicMatchParams = { 0 }; + + /* Prevent any other thread from modifying the subscription list while this + * function is running. */ + AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + + /* Find and remove each topic filter from the list. */ + for( i = 0; i < subscriptionCount; i++ ) + { + topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; + topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; + topicMatchParams.exactMatchOnly = true; + + pSubscription = AwsIotList_FindFirstMatch( pMqttConnection->subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + _topicMatch ); + + if( pSubscription != NULL ) + { + /* Reference count must not be negative. */ + AwsIotMqtt_Assert( pSubscription->references >= 0 ); + + /* Check the reference count. This subscription cannot be removed if + * there are subscription callbacks using it. */ + if( pSubscription->references > 0 ) + { + /* Set the unsubscribed flag. The last active subscription callback + * will remove and clean up this subscription. */ + pSubscription->unsubscribed = true; + } + else + { + /* No subscription callbacks are using this subscription. Remove it. */ + AwsIotList_Remove( &( pMqttConnection->subscriptionList ), + &( pSubscription->link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotMqtt_FreeSubscription( pSubscription ); + } + } + } + + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, + const char * const pTopicFilter, + uint16_t topicFilterLength, + AwsIotMqttSubscription_t * pCurrentSubscription ) +{ + bool status = false; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + _mqttSubscription_t * pSubscription = NULL; + _topicMatchParams_t topicMatchParams = + { + .pTopicName = pTopicFilter, + .topicNameLength = topicFilterLength, + .exactMatchOnly = true + }; + + /* Prevent any other thread from modifying the subscription list while this + * function is running. */ + AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + + /* Search for a matching subscription. */ + pSubscription = AwsIotList_FindFirstMatch( pMqttConnection->subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + _topicMatch ); + + /* Check if a matching subscription was found. */ + if( pSubscription != NULL ) + { + /* Copy the matching subscription to the output parameter. */ + if( pCurrentSubscription != NULL ) + { + pCurrentSubscription->pTopicFilter = pTopicFilter; + pCurrentSubscription->topicFilterLength = topicFilterLength; + pCurrentSubscription->QoS = 0; + pCurrentSubscription->callback = pSubscription->callback; + } + + status = true; + } + + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +/* If the MQTT library is being tested, include a file that allows access to + * internal functions and variables. */ +#if AWS_IOT_MQTT_TEST == 1 + #include "aws_iot_test_access_mqtt_subscription.c" +#endif diff --git a/lib/source/mqtt/aws_iot_mqtt_validate.c b/lib/source/mqtt/aws_iot_mqtt_validate.c new file mode 100644 index 0000000000..aaf3c730d5 --- /dev/null +++ b/lib/source/mqtt/aws_iot_mqtt_validate.c @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_mqtt_validate.c + * @brief Implements functions that validate the structs of the MQTT library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkInterface ) +{ + /* Check for NULL. */ + if( pNetworkInterface == NULL ) + { + AwsIotLogError( "Network interface cannot be NULL." ); + + return false; + } + + /* Check for a non-NULL send function. */ + if( pNetworkInterface->send == NULL ) + { + AwsIotLogError( "Network interface send function must be set." ); + + return false; + } + + /* The MQTT 3.1.1 spec suggests disconnecting the client on errors. Check + * that a function has been provided to do this. */ + if( pNetworkInterface->disconnect == NULL ) + { + AwsIotLogWarn( "No disconnect function was provided. The MQTT connection will not be " + "closed on errors, which is against MQTT 3.1.1 specification." ); + } + + /* Check that the freePacket function pointer is set if any other serializer + * override is set. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNetworkInterface->freePacket == NULL ) + { + /* Check serializer overrides. */ + if( ( pNetworkInterface->serialize.connect != NULL ) || + ( pNetworkInterface->serialize.publish != NULL ) || + ( pNetworkInterface->serialize.publishSetDup != NULL ) || + ( pNetworkInterface->serialize.puback != NULL ) || + ( pNetworkInterface->serialize.subscribe != NULL ) || + ( pNetworkInterface->serialize.unsubscribe != NULL ) || + ( pNetworkInterface->serialize.pingreq != NULL ) || + ( pNetworkInterface->serialize.disconnect != NULL ) ) + { + AwsIotLogError( "Network interface must have a freePacket function " + "if a serializer override isn't NULL." ); + + return false; + } + + /* Check deserializer overrides. */ + if( ( pNetworkInterface->deserialize.connack != NULL ) || + ( pNetworkInterface->deserialize.puback != NULL ) || + ( pNetworkInterface->deserialize.publish != NULL ) || + ( pNetworkInterface->deserialize.suback != NULL ) || + ( pNetworkInterface->deserialize.unsuback != NULL ) || + ( pNetworkInterface->deserialize.pingresp != NULL ) ) + { + AwsIotLogError( "Network interface must have a freePacket function " + "if a deserializer override isn't NULL." ); + + return false; + } + } + #endif /* if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo ) +{ + /* Check for NULL. */ + if( pConnectInfo == NULL ) + { + AwsIotLogError( "MQTT connection information cannot be NULL." ); + + return false; + } + + /* Check that a client identifier was set. */ + if( pConnectInfo->pClientIdentifier == NULL ) + { + AwsIotLogError( "Client identifier must be set." ); + + return false; + } + + /* Check for a zero-length client identifier. Zero-length client identifiers + * are not allowed with clean sessions. */ + if( pConnectInfo->clientIdentifierLength == 0 ) + { + AwsIotLogWarn( "A zero-length client identifier was provided." ); + + if( pConnectInfo->cleanSession == true ) + { + AwsIotLogError( "A zero-length client identifier cannot be used with a clean session." ); + + return false; + } + } + + /* In MQTT 3.1.1, servers are not obligated to accept client identifiers longer + * than 23 characters. */ + if( pConnectInfo->clientIdentifierLength > 23 ) + { + AwsIotLogWarn( "A client identifier length of %hu is longer than 23, which is " + "the longest client identifier a server must accept.", + pConnectInfo->clientIdentifierLength ); + } + + /* Check for compatibility with the AWS IoT MQTT service limits. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + /* Check that client identifier is within the service limit. */ + if( pConnectInfo->clientIdentifierLength > _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ) + { + AwsIotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", + _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); + + return false; + } + + /* Check for compliance with AWS IoT keep-alive limits. */ + if( pConnectInfo->keepAliveSeconds == 0 ) + { + AwsIotLogWarn( "AWS IoT does not support disabling keep-alive. Default keep-alive " + "of %d seconds will be used.", + _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + } + else if( pConnectInfo->keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + { + AwsIotLogWarn( "AWS IoT does not support keep-alive intervals less than %d seconds. " + "An interval of %d seconds will be used.", + _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, + _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); + } + else if( pConnectInfo->keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + { + AwsIotLogWarn( "AWS IoT does not support keep-alive intervals greater than %d seconds. " + "An interval of %d seconds will be used.", + _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, + _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + } + } + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, + const AwsIotMqttPublishInfo_t * const pPublishInfo ) +{ + /* Check for NULL. */ + if( pPublishInfo == NULL ) + { + AwsIotLogError( "Publish information cannot be NULL." ); + + return false; + } + + /* Check topic name for NULL or zero-length. */ + if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) + { + AwsIotLogError( "Publish topic name must be set." ); + + return false; + } + + /* Only allow NULL payloads with zero-length. */ + if( ( pPublishInfo->pPayload == NULL ) && ( pPublishInfo->payloadLength != 0 ) ) + { + AwsIotLogError( "Nonzero payload length cannot have a NULL payload." ); + + return false; + } + + /* Check for a valid QoS. */ + if( ( pPublishInfo->QoS < 0 ) || ( pPublishInfo->QoS > 1 ) ) + { + AwsIotLogError( "Publish QoS must be either 0 or 1." ); + + return false; + } + + /* Check the retry parameters. */ + if( pPublishInfo->retryLimit < 0 ) + { + AwsIotLogError( "Publish retry cannot be less than 0." ); + + return false; + } + else if( pPublishInfo->retryLimit > 0 ) + { + if( pPublishInfo->retryMs == 0 ) + { + AwsIotLogError( "Publish retry time must be positive." ); + + return false; + } + } + + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) + { + /* Check for retained message. */ + if( pPublishInfo->retain == true ) + { + AwsIotLogError( "AWS IoT does not support retained publish messages." ); + + return false; + } + + /* Check topic name length. */ + if( pPublishInfo->topicNameLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + AwsIotLogError( "AWS IoT does not support topic names longer than %d bytes.", + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + return false; + } + } + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; + + /* Check for NULL. */ + if( pOperation == NULL ) + { + AwsIotLogError( "Reference cannot be NULL." ); + + return false; + } + + /* Check that reference is waitable. */ + if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) != AWS_IOT_MQTT_FLAG_WAITABLE ) + { + AwsIotLogError( "Reference is not a waitable MQTT operation." ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t operation, + bool awsIotMqttMode, + const AwsIotMqttSubscription_t * const pListStart, + size_t listSize ) +{ + size_t i = 0; + uint16_t j = 0; + const AwsIotMqttSubscription_t * pListElement = NULL; + + /* Operation must be either subscribe or unsubscribe. */ + AwsIotMqtt_Assert( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) || + ( operation == AWS_IOT_MQTT_UNSUBSCRIBE ) ); + + /* Check for empty list. */ + if( ( listSize == 0 ) || ( pListStart == NULL ) ) + { + AwsIotLogError( "Empty subscription list." ); + + return false; + } + + /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ + if( awsIotMqttMode == true ) + { + if( listSize > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + { + AwsIotLogError( "AWS IoT does not support more than %d topic filters per " + "subscription request.", + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); + + return false; + } + } + + for( i = 0; i < listSize; i++ ) + { + pListElement = &( pListStart[ i ] ); + + /* Check for a valid QoS when subscribing. */ + if( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) && + ( ( pListElement->QoS < 0 ) || ( pListElement->QoS > 1 ) ) ) + { + AwsIotLogError( "Subscription QoS must be either 0 or 1." ); + + return false; + } + + /* Check subscription topic filter. */ + if( ( pListElement->pTopicFilter == NULL ) || ( pListElement->topicFilterLength == 0 ) ) + { + AwsIotLogError( "Subscription topic filter must be set." ); + + return false; + } + + /* Check that a callback function is set when subscribing. */ + if( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) && + ( pListElement->callback.function == NULL ) ) + { + AwsIotLogError( "Callback function must be set." ); + + return false; + } + + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) + { + /* Check topic filter length. */ + if( pListElement->topicFilterLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + AwsIotLogError( "AWS IoT does not support topic filters longer than %d bytes.", + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + return false; + } + } + + /* Check that the wildcards '+' and '#' are being used correctly. */ + for( j = 0; j < pListElement->topicFilterLength; j++ ) + { + switch( pListElement->pTopicFilter[ j ] ) + { + /* Check that the single level wildcard '+' is used correctly. */ + case '+': + + /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ + if( ( j > 0 ) && ( pListElement->pTopicFilter[ j - 1 ] != '/' ) ) + { + AwsIotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + return false; + } + + /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ + if( ( j < pListElement->topicFilterLength - 1 ) && + ( pListElement->pTopicFilter[ j + 1 ] != '/' ) ) + { + AwsIotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + return false; + } + + break; + + /* Check that the multi-level wildcard '#' is used correctly. */ + case '#': + + /* '#' must be the last character in the filter. */ + if( j != pListElement->topicFilterLength - 1 ) + { + AwsIotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + return false; + } + + /* Unless '#' is standalone, it must be preceded by '/'. */ + if( ( pListElement->topicFilterLength > 1 ) && + ( pListElement->pTopicFilter[ j - 1 ] != '/' ) ) + { + AwsIotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + return false; + } + + break; + + default: + break; + } + } + } + + return true; +} diff --git a/lib/source/platform/posix/CMakeLists.txt b/lib/source/platform/posix/CMakeLists.txt new file mode 100644 index 0000000000..3c20b39928 --- /dev/null +++ b/lib/source/platform/posix/CMakeLists.txt @@ -0,0 +1,106 @@ +include( CheckCCompilerFlag ) +include( CheckTypeSize ) +include( CheckFunctionExists ) + +# Check that the -lrt flag works. +check_c_compiler_flag( -lrt HAS_C_FLAG_lrt ) + +if( NOT HAS_C_FLAG_lrt ) + message( FATAL_ERROR "Compiler flag -lrt must be supported." ) +endif() + +# Check for POSIX threads. +find_package( Threads REQUIRED ) + +if( NOT ${CMAKE_USE_PTHREADS_INIT} ) + message( FATAL_ERROR "POSIX threads required." ) +endif() + +# Check for required POSIX types. +set( CMAKE_EXTRA_INCLUDE_FILES "semaphore.h" ) +list( APPEND REQUIRED_POSIX_TYPES + pthread_t + pthread_attr_t + pthread_mutex_t + sem_t + timer_t ) + +foreach( POSIX_TYPE ${REQUIRED_POSIX_TYPES} ) + check_type_size( ${POSIX_TYPE} SIZEOF_${POSIX_TYPE} ) + + if( NOT HAVE_SIZEOF_${POSIX_TYPE} ) + message( FATAL_ERROR "Required type ${POSIX_TYPE} not found." ) + endif() +endforeach() + +# Check for some required POSIX functions. This is not intended to be a comprehensive list. +set( CMAKE_REQUIRED_INCLUDES "time.h" ) +set( CMAKE_REQUIRED_LIBRARIES rt Threads::Threads ) +list( APPEND REQUIRED_POSIX_FUNCTIONS + clock_gettime time localtime_r strftime timer_create timer_delete + timer_settime pthread_create pthread_attr_init pthread_attr_setdetachstate + pthread_mutex_init pthread_mutex_lock pthread_mutex_trylock pthread_mutex_unlock + sem_init sem_getvalue sem_wait sem_trywait sem_timedwait sem_post ) + +foreach( POSIX_FUNCTION ${REQUIRED_POSIX_FUNCTIONS} ) + check_function_exists( ${POSIX_FUNCTION} HAVE_${POSIX_FUNCTION} ) + + if( NOT HAVE_${POSIX_FUNCTION} ) + message( FATAL_ERROR "Required function ${POSIX_FUNCTION} not found." ) + endif() +endforeach() + +# Check for OpenSSL. +find_package( OpenSSL ) + +# Minimum supported OpenSSL version is 1.0.2g. +if( ${OPENSSL_FOUND} ) + if( ${OPENSSL_VERSION} STRLESS "1.0.2g" ) + message( FATAL_ERROR "OpenSSL 1.0.2g or later required, found ${OPENSSL_VERSION}." ) + endif() + + # Choose OpenSSL network source file. + set( NETWORK_SOURCE_FILE network/aws_iot_network_openssl.c ) + + # Link OpenSSL. + set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) +endif() + +# Ensure that a compatible TLS library was found. +if( NOT DEFINED TLS_LIBRARY_LINKER_FLAG ) + message( FATAL_ERROR "No compatible TLS library was found." ) +endif() + +# Configure the platform layer for POSIX systems. +set( CONFIG_MUTEX_HEADER "pthread.h" ) +set( CONFIG_MUTEX_TYPE pthread_mutex_t ) +set( CONFIG_SEMAPHORE_HEADER "semaphore.h" ) +set( CONFIG_SEMAPHORE_TYPE sem_t ) +set( CONFIG_TIMER_HEADER "time.h" ) +set( CONFIG_TIMER_TYPE void* ) + +configure_file( ${CMAKE_SOURCE_DIR}/lib/config/aws_iot_config_build.h.in + ${CMAKE_BINARY_DIR}/config/aws_iot_config_build.h ) + +# Platform libraries source files. +add_library( awsiotplatform SHARED + aws_iot_clock_posix.c + aws_iot_threads_posix.c + ${NETWORK_SOURCE_FILE} + static_memory/aws_iot_static_memory_common_posix.c + static_memory/aws_iot_static_memory_mqtt_posix.c + static_memory/aws_iot_static_memory_shadow_posix.c ) + +# Library version. +set_target_properties( awsiotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Library public headers. +set_target_properties( awsiotplatform PROPERTIES PUBLIC_HEADER + "../../include/platform;../../include/platform/network" ) + +# Set configuration file. +target_compile_definitions( awsiotplatform PRIVATE + AWS_IOT_CONFIG_FILE="aws_iot_config_build.h" ) + +# Link required libraries. +target_link_libraries( awsiotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) diff --git a/lib/source/platform/posix/aws_iot_clock_posix.c b/lib/source/platform/posix/aws_iot_clock_posix.c new file mode 100644 index 0000000000..66f0f06c9b --- /dev/null +++ b/lib/source/platform/posix/aws_iot_clock_posix.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_clock_posix.c + * @brief Implementation of the functions in aws_iot_clock.h for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* POSIX includes. Allow the default POSIX headers to be overridden. */ +#ifdef POSIX_ERRNO_HEADER + #include POSIX_ERRNO_HEADER +#else + #include +#endif +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif +#ifdef POSIX_SIGNAL_HEADER + #include POSIX_SIGNAL_HEADER +#else + #include +#endif +#ifdef POSIX_TIME_HEADER + #include POSIX_TIME_HEADER +#else + #include +#endif + +/* Platform clock include. */ +#include "platform/aws_iot_clock.h" + +/* Configure logs for the functions in this file. */ +#ifdef AWS_IOT_LOG_LEVEL_PLATFORM + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_PLATFORM +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "CLOCK" ) +#include "aws_iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#ifndef AwsIotClock_Malloc + #include + + /** + * @brief Memory allocation. This function should have the same signature as + * [malloc.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + */ + #define AwsIotClock_Malloc malloc +#endif +#ifndef AwsIotClock_Free + #include + + /** + * @brief Free memory. This function should have the same signature as + * [free.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + */ + #define AwsIotClock_Free free +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief The format of timestrings printed in logs. + * + * For more information on timestring formats, see [this link.] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) + */ +#define _TIMESTRING_FORMAT ( "%F %R:%S" ) + +/* + * Time conversion constants. + */ +#define _NANOSECONDS_PER_SECOND ( 1000000000 ) /**< @brief Nanoseconds per second. */ +#define _NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ +#define _MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Holds information about an active timer. + */ +typedef struct _timerInfo +{ + timer_t timer; /**< @brief Underlying POSIX timer. */ + void * pArgument; /**< @brief First argument to threadRoutine. */ + AwsIotThreadRoutine_t threadRoutine; /**< @brief Thread function to run on timer expiration. */ +} _timerInfo_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps an AwsIotThreadRoutine_t with a POSIX-compliant one. + * + * @param[in] argument The value passed as `sigevent.sigev_value`. + */ +static void _timerExpirationWrapper( union sigval argument ); + +/** + * @brief Convert a relative timeout in milliseconds to an absolute timeout + * represented as a struct timespec. + * + * This function is not included in aws_iot_clock.h because it's platform-specific. + * But it may be called by other POSIX platform files. + * @param[in] timeoutMs The relative timeout. + * @param[out] pOutput Where to store the resulting `timespec`. + * + * @return `true` if `timeoutMs` was successfully converted; `false` otherwise. + */ +bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, + struct timespec * const pOutput ); + +/*-----------------------------------------------------------*/ + +static void _timerExpirationWrapper( union sigval argument ) +{ + _timerInfo_t * pTimerInfo = ( _timerInfo_t * ) argument.sival_ptr; + + /* Call the wrapped thread routine. */ + pTimerInfo->threadRoutine( pTimerInfo->pArgument ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, + struct timespec * const pOutput ) +{ + struct timespec systemTime = { 0 }; + + if( clock_gettime( CLOCK_REALTIME, &systemTime ) != 0 ) + { + AwsIotLogError( "Failed to read system time. errno=%d", errno ); + + return false; + } + + /* Add the nanoseconds value to the time. */ + systemTime.tv_nsec += ( long ) ( ( timeoutMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); + + /* Check for overflow of nanoseconds value. */ + if( systemTime.tv_nsec >= _NANOSECONDS_PER_SECOND ) + { + systemTime.tv_nsec -= _NANOSECONDS_PER_SECOND; + systemTime.tv_sec++; + } + + /* Add the seconds value to the timeout. */ + systemTime.tv_sec += ( time_t ) ( timeoutMs / _MILLISECONDS_PER_SECOND ); + + /* Set the output parameter. */ + *pOutput = systemTime; + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotClock_GetTimestring( char * const pBuffer, + size_t bufferSize, + size_t * const pTimestringLength ) +{ + const time_t unixTime = time( NULL ); + struct tm localTime = { 0 }; + size_t timestringLength = 0; + + /* localtime_r is the thread-safe variant of localtime. Its return value + * should be the pointer to the localTime struct. */ + if( localtime_r( &unixTime, &localTime ) != &localTime ) + { + return false; + } + + /* Convert the localTime struct to a string. */ + timestringLength = strftime( pBuffer, bufferSize, _TIMESTRING_FORMAT, &localTime ); + + /* Check for error from strftime. */ + if( timestringLength == 0 ) + { + return false; + } + + /* Set the output parameter. */ + *pTimestringLength = timestringLength; + + return true; +} + +/*-----------------------------------------------------------*/ + +uint64_t AwsIotClock_GetTimeMs( void ) +{ + struct timespec currentTime = { 0 }; + + if( clock_gettime( CLOCK_MONOTONIC, ¤tTime ) != 0 ) + { + AwsIotLogWarn( "Failed to read time from CLOCK_MONOTONIC. errno=%d", + errno ); + } + + return ( ( uint64_t ) currentTime.tv_sec ) * 1000ULL + + ( ( uint64_t ) currentTime.tv_nsec ) / 1000000ULL; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotClock_TimerCreate( AwsIotTimer_t * const pNewTimer, + AwsIotThreadRoutine_t expirationRoutine, + void * pArgument ) +{ + _timerInfo_t * pTimerInfo = NULL; + struct sigevent expirationNotification = + { + .sigev_notify = SIGEV_THREAD, + .sigev_signo = 0, + .sigev_value.sival_ptr = NULL, + .sigev_notify_function = _timerExpirationWrapper, + .sigev_notify_attributes = NULL + }; + + AwsIotLogDebug( "Creating new timer %p.", pNewTimer ); + + /* Allocate memory to store the expiration routine and argument. */ + pTimerInfo = AwsIotClock_Malloc( sizeof( _timerInfo_t ) ); + + if( pTimerInfo == NULL ) + { + AwsIotLogError( "Failed to allocate memory for new timer %p.", pNewTimer ); + + return false; + } + + /* Set the timer expiration routine and argument. */ + pTimerInfo->pArgument = pArgument; + pTimerInfo->threadRoutine = expirationRoutine; + + expirationNotification.sigev_value.sival_ptr = pTimerInfo; + + /* Create the underlying POSIX timer. */ + if( timer_create( CLOCK_REALTIME, + &expirationNotification, + &( pTimerInfo->timer ) ) != 0 ) + { + AwsIotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); + + AwsIotClock_Free( pTimerInfo ); + + return false; + } + + /* Set the output parameter. */ + *pNewTimer = pTimerInfo; + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotClock_TimerDestroy( AwsIotTimer_t * const pTimer ) +{ + _timerInfo_t * pTimerInfo = ( _timerInfo_t * ) *pTimer; + + AwsIotLogDebug( "Destroying timer %p.", pTimer ); + + /* Destroy the underlying POSIX timer. */ + if( timer_delete( pTimerInfo->timer ) != 0 ) + { + AwsIotLogWarn( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); + } + + AwsIotClock_Free( pTimerInfo ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotClock_TimerArm( AwsIotTimer_t * const pTimer, + uint64_t relativeTimeoutMs, + uint64_t periodMs ) +{ + _timerInfo_t * pTimerInfo = ( _timerInfo_t * ) *pTimer; + struct itimerspec timerExpiration = + { + .it_value = { 0 }, + .it_interval = { 0 } + }; + + AwsIotLogDebug( "Arming timer %p with timeout %llu and period %llu.", + pTimer, + relativeTimeoutMs, + periodMs ); + + /* Calculate the initial timer expiration. */ + if( AwsIotClock_TimeoutToTimespec( relativeTimeoutMs, + &( timerExpiration.it_value ) ) == false ) + { + AwsIotLogError( "Invalid relative timeout." ); + + return false; + } + + /* Calculate the timer expiration period. */ + if( periodMs > 0 ) + { + timerExpiration.it_interval.tv_sec = ( time_t ) ( periodMs / _MILLISECONDS_PER_SECOND ); + timerExpiration.it_interval.tv_nsec = ( long ) ( ( periodMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); + } + + /* Arm the underlying POSIX timer. */ + if( timer_settime( pTimerInfo->timer, TIMER_ABSTIME, &timerExpiration, NULL ) != 0 ) + { + AwsIotLogError( "Failed to arm timer %p. errno=%d.", pTimer, errno ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/platform/posix/aws_iot_threads_posix.c b/lib/source/platform/posix/aws_iot_threads_posix.c new file mode 100644 index 0000000000..0f5c7cbf6c --- /dev/null +++ b/lib/source/platform/posix/aws_iot_threads_posix.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_threads_posix.c + * @brief Implementation of the functions in aws_iot_threads.h for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* POSIX includes. Allow the default POSIX headers to be overridden. */ +#ifdef POSIX_ERRNO_HEADER + #include POSIX_ERRNO_HEADER +#else + #include +#endif +#ifdef POSIX_LIMITS_HEADER + #include POSIX_LIMITS_HEADER +#else + #include +#endif +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif +#ifdef POSIX_SEMAPHORE_HEADER + #include POSIX_SEMAPHORE_HEADER +#else + #include +#endif + +/* Platform threads include. */ +#include "platform/aws_iot_threads.h" + +/* Configure logs for the functions in this file. */ +#ifdef AWS_IOT_LOG_LEVEL_PLATFORM + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_PLATFORM +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "THREAD" ) +#include "aws_iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#ifndef AwsIotThreads_Malloc + #include + + /** + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define AwsIotThreads_Malloc malloc +#endif +#ifndef AwsIotThreads_Free + #include + + /** + * @brief Free memory. This function should have the same signature as + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define AwsIotThreads_Free free +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps an AwsIotThreadRoutine_t with a POSIX-compliant one. + * + * @param[in] pArgument The value passed as `arg` to `pthread_create`. + * + * @return Always returns `NULL`. + */ +static void * _threadRoutineWrapper( void * pArgument ); + +/* Platform-specific function implemented in aws_iot_clock_posix.c */ +extern bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, + struct timespec * const pOutput ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Holds information about an active detached thread. + */ +typedef struct _threadInfo +{ + void * pArgument; /**< @brief First argument to `threadRoutine`. */ + AwsIotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ +} _threadInfo_t; + +/*-----------------------------------------------------------*/ + +static void * _threadRoutineWrapper( void * pArgument ) +{ + _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; + + /* Run the thread routine. */ + pThreadInfo->threadRoutine( pThreadInfo->pArgument ); + + AwsIotThreads_Free( pThreadInfo ); + + return NULL; +} + +/*-----------------------------------------------------------*/ + +bool AwsIot_CreateDetachedThread( AwsIotThreadRoutine_t threadRoutine, + void * pArgument ) +{ + int posixErrno = 0; + _threadInfo_t * pThreadInfo = NULL; + pthread_t newThread; + pthread_attr_t threadAttributes; + + AwsIotLogDebug( "Creating new thread." ); + + /* Allocate memory for the new thread. */ + pThreadInfo = AwsIotThreads_Malloc( sizeof( _threadInfo_t ) ); + + if( pThreadInfo == NULL ) + { + AwsIotLogError( "Failed to allocate memory for new thread." ); + + return false; + } + + /* Create a new thread attributes object. */ + posixErrno = pthread_attr_init( &threadAttributes ); + + if( posixErrno != 0 ) + { + AwsIotLogError( "Failed to initialize thread attributes. errno=%d.", + posixErrno ); + AwsIotThreads_Free( pThreadInfo ); + + return false; + } + + /* Set the new thread to detached. */ + posixErrno = pthread_attr_setdetachstate( &threadAttributes, + PTHREAD_CREATE_DETACHED ); + + if( posixErrno != 0 ) + { + AwsIotLogError( "Failed to set detached thread attribute. errno=%d.", + posixErrno ); + ( void ) pthread_attr_destroy( &threadAttributes ); + AwsIotThreads_Free( pThreadInfo ); + + return false; + } + + /* Set the thread routine and argument. */ + pThreadInfo->threadRoutine = threadRoutine; + pThreadInfo->pArgument = pArgument; + + /* Create the underlying POSIX thread. */ + posixErrno = pthread_create( &newThread, + &threadAttributes, + _threadRoutineWrapper, + pThreadInfo ); + + if( posixErrno != 0 ) + { + AwsIotLogError( "Failed to create new thread. errno=%d.", posixErrno ); + ( void ) pthread_attr_destroy( &threadAttributes ); + AwsIotThreads_Free( pThreadInfo ); + + return false; + } + + /* Destroy thread attributes object. */ + posixErrno = pthread_attr_destroy( &threadAttributes ); + + if( posixErrno != 0 ) + { + AwsIotLogWarn( "Failed to destroy thread attributes. errno=%d.", + posixErrno ); + } + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMutex_Create( AwsIotMutex_t * pNewMutex ) +{ + AwsIotLogDebug( "Creating new mutex %p.", pNewMutex ); + + int mutexError = pthread_mutex_init( pNewMutex, NULL ); + + if( mutexError != 0 ) + { + AwsIotLogError( "Failed to create new mutex %p. errno=%d.", + pNewMutex, + mutexError ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMutex_Destroy( AwsIotMutex_t * const pMutex ) +{ + AwsIotLogDebug( "Destroying mutex %p.", pMutex ); + + int mutexError = pthread_mutex_destroy( pMutex ); + + if( mutexError != 0 ) + { + AwsIotLogWarn( "Failed to destroy mutex %p. errno=%d.", + pMutex, + mutexError ); + } +} + +/*-----------------------------------------------------------*/ + +void AwsIotMutex_Lock( AwsIotMutex_t * const pMutex ) +{ + AwsIotLogDebug( "Locking mutex %p.", pMutex ); + + int mutexError = pthread_mutex_lock( pMutex ); + + if( mutexError != 0 ) + { + AwsIotLogWarn( "Failed to lock mutex %p. errno=%d.", + pMutex, + mutexError ); + } +} + +/*-----------------------------------------------------------*/ + +bool AwsIotMutex_TryLock( AwsIotMutex_t * const pMutex ) +{ + AwsIotLogDebug( "Attempting to lock mutex %p.", pMutex ); + + int mutexError = pthread_mutex_trylock( pMutex ); + + if( mutexError != 0 ) + { + AwsIotLogDebug( "Mutex mutex %p is not available. errno=%d.", + pMutex, + mutexError ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ) +{ + AwsIotLogDebug( "Unlocking mutex %p.", pMutex ); + + int mutexError = pthread_mutex_unlock( pMutex ); + + if( mutexError != 0 ) + { + AwsIotLogWarn( "Failed to unlock mutex %p. errno=%d.", + pMutex, + mutexError ); + } +} + +/*-----------------------------------------------------------*/ + +bool AwsIotSemaphore_Create( AwsIotSemaphore_t * const pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ) +{ + AwsIotLogDebug( "Creating new semaphore %p.", pNewSemaphore ); + + if( maxValue > ( uint32_t ) SEM_VALUE_MAX ) + { + AwsIotLogError( "%lu is larger than the maximum value a semaphore may" + " have on this system.", maxValue ); + + return false; + } + + if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) + { + AwsIotLogError( "Failed to create new semaphore %p. errno=%d.", + pNewSemaphore, + errno ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +uint32_t AwsIotSemaphore_GetCount( AwsIotSemaphore_t * const pSemaphore ) +{ + int count = 0; + + if( sem_getvalue( pSemaphore, &count ) != 0 ) + { + AwsIotLogWarn( "Failed to query semaphore count of %p. errno=%d.", + pSemaphore, + errno ); + } + + AwsIotLogDebug( "Semaphore %p has count %d.", pSemaphore, count ); + + return ( uint32_t ) count; +} + +/*-----------------------------------------------------------*/ + +void AwsIotSemaphore_Destroy( AwsIotSemaphore_t * const pSemaphore ) +{ + AwsIotLogDebug( "Destroying semaphore %p.", pSemaphore ); + + if( sem_destroy( pSemaphore ) != 0 ) + { + AwsIotLogWarn( "Failed to destroy semaphore %p. errno=%d.", + pSemaphore, + errno ); + } +} + +/*-----------------------------------------------------------*/ + +void AwsIotSemaphore_Wait( AwsIotSemaphore_t * pSemaphore ) +{ + AwsIotLogDebug( "Waiting on semaphore %p.", pSemaphore ); + + if( sem_wait( pSemaphore ) != 0 ) + { + AwsIotLogWarn( "Failed to wait on semaphore %p. errno=%d.", + pSemaphore, + errno ); + } +} + +/*-----------------------------------------------------------*/ + +bool AwsIotSemaphore_TryWait( AwsIotSemaphore_t * const pSemaphore ) +{ + AwsIotLogDebug( "Attempting to wait on semaphore %p.", pSemaphore ); + + if( sem_trywait( pSemaphore ) != 0 ) + { + AwsIotLogDebug( "Semaphore %p is not available. errno=%d.", + pSemaphore, + errno ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotSemaphore_TimedWait( AwsIotSemaphore_t * const pSemaphore, + uint64_t timeoutMs ) +{ + struct timespec timeout = { 0 }; + + AwsIotLogDebug( "Attempting to wait on semaphore %p with timeout %llu.", + pSemaphore, timeoutMs ); + + if( AwsIotClock_TimeoutToTimespec( timeoutMs, &timeout ) == false ) + { + AwsIotLogError( "Invalid timeout." ); + + return false; + } + + if( sem_timedwait( pSemaphore, &timeout ) != 0 ) + { + AwsIotLogDebug( "Semaphore %p is not available. errno=%d.", + pSemaphore, + errno ); + + return false; + } + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotSemaphore_Post( AwsIotSemaphore_t * const pSemaphore ) +{ + AwsIotLogDebug( "Posting to semaphore %p.", pSemaphore ); + + if( sem_post( pSemaphore ) != 0 ) + { + AwsIotLogWarn( "Failed to post to semaphore %p. errno=%d.", + pSemaphore, + errno ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/platform/posix/network/aws_iot_network_openssl.c b/lib/source/platform/posix/network/aws_iot_network_openssl.c new file mode 100644 index 0000000000..349f7f2cc5 --- /dev/null +++ b/lib/source/platform/posix/network/aws_iot_network_openssl.c @@ -0,0 +1,1061 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_network_openssl.c + * @brief Implementation of the network-related functions from aws_iot_network.h + * for systems with OpenSSL on POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include +#include +#include +#include +#include + +/* Sockets includes. */ +#include +#include + +/* OpenSSL includes. */ +#include +#include +#include + +/* Network header include. */ +#include "platform/aws_iot_network.h" + +/* Platform threads include. */ +#include "platform/aws_iot_threads.h" + +/* Configure logs for the functions in this file. */ +#ifdef AWS_IOT_LOG_LEVEL_NETWORK + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_NETWORK +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "NET" ) +#include "aws_iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#ifndef AwsIotNetwork_Malloc + #include + +/** + * @brief Memory allocation. This function should have the same signature as + * [malloc.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + */ + #define AwsIotNetwork_Malloc malloc +#endif +#ifndef AwsIotNetwork_Free + #include + +/** + * @brief Free memory. This function should have the same signature as + * [free.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + */ + #define AwsIotNetwork_Free free +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Statuses for receive threads. + */ +typedef enum _threadStatus +{ + _NONE = 0, /**< Any previously created thread is fully cleaned up. */ + _ACTIVE, /**< A receive thread is currently running. */ + _TERMINATED /**< A receive thread is terminated and needs to be joined. */ +} _threadStatus_t; + +/** + * @brief Connection info, which is pointed to by the connection handle. + * + * Holds data on a single connection. + */ +typedef struct _connectionInfo +{ + int tcpSocket; /**< @brief Socket associated with connection. */ + SSL * pSSLConnectionContext; /**< @brief SSL context for connection. */ + AwsIotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ + _threadStatus_t receiveThreadStatus; /**< @brief Status of the receive thread for this connection. */ + pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ + + AwsIotMqttReceiveCallback_t mqttReceiveCallback; /**< @brief Function to call when MQTT data is received, if any. */ + AwsIotMqttConnection_t * pMqttConnection; /**< @brief The first parameter passed to #_connectionInfo_t.mqttReceiveCallback. */ +} _connectionInfo_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler for SIGPIPE. + * + * Currently, this function just prevents SIGPIPE from terminating the program. + * + * @param[in] signal Should always be SIGPIPE. + */ +static void _sigpipeHandler( int signal ) +{ + ( void ) signal; + + /* This signal handler doesn't do anything, it just prevents SIGPIPE from + * terminating this process. SIGPIPE will cause any receive threads to + * terminate, and any further calls to AwsIotNetwork_Send will fail. */ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Network receive thread. + * + * This thread polls the network socket and reads data when data is available. + * It then invokes the MQTT receive callback, if any. + * + * @param[in] pArgument The connection associated with this receive thread. + */ +static void * _networkReceiveThread( void * pArgument ) +{ + int bytesAvailable = 0, bytesRead = 0; + int32_t bytesProcessed = 0; + _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) pArgument; + void * pReceiveBuffer = NULL; + struct pollfd fileDescriptors = + { + .fd = pConnectionInfo->tcpSocket, + .events = POLLIN, + .revents = 0 + }; + + /* Continuously poll the network socket for events. */ + while( poll( &fileDescriptors, 1, -1 ) == 1 ) + { + /* Prevent this thread from being cancelled while running. But if the + * connection mutex is locked, wait until it is available. */ + if( AwsIotMutex_TryLock( &( pConnectionInfo->mutex ) ) == false ) + { + continue; + } + + /* If an error event is detected, terminate this thread. */ + if( ( fileDescriptors.revents & POLLERR ) || + ( fileDescriptors.revents & POLLHUP ) || + ( fileDescriptors.revents & POLLNVAL ) ) + { + AwsIotLogError( "Detected error on socket %d, receive thread exiting.", + pConnectionInfo->tcpSocket ); + break; + } + + /* Query how many bytes are available to read. The value returned by this + * function may include more than just the data. */ + if( ioctl( pConnectionInfo->tcpSocket, FIONREAD, &bytesAvailable ) != 0 ) + { + AwsIotLogError( "Failed to query bytes available on socket. errno=%d.", + errno ); + + /* Unlock the connection mutex before polling again. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + + continue; + } + + AwsIotLogDebug( "%d bytes available on socket %d.", + bytesAvailable, + pConnectionInfo->tcpSocket ); + + /* If no bytes can be read, the socket is likely closed. Terminate this thread. */ + if( bytesAvailable == 0 ) + { + AwsIotLogInfo( "No data available, terminating receive thread for socket %d.", + pConnectionInfo->tcpSocket ); + break; + } + + /* Allocate memory to hold the received message. */ + pReceiveBuffer = AwsIotNetwork_Malloc( ( size_t ) bytesAvailable ); + + if( pReceiveBuffer == NULL ) + { + AwsIotLogError( "Failed to allocate %d bytes for socket read on %d.", + bytesAvailable, + pConnectionInfo->tcpSocket ); + + /* Unlock the connection mutex before polling again. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + + continue; + } + + /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on + * whether the SSL connection context is set up. */ + if( pConnectionInfo->pSSLConnectionContext != NULL ) + { + bytesRead = SSL_read( pConnectionInfo->pSSLConnectionContext, + pReceiveBuffer, + bytesAvailable ); + } + else + { + bytesRead = ( int ) recv( pConnectionInfo->tcpSocket, + pReceiveBuffer, + ( size_t ) bytesAvailable, + 0 ); + } + + /* Check how many bytes were read. */ + if( bytesRead <= 0 ) + { + AwsIotLogError( "Error reading from socket %d.", + pConnectionInfo->tcpSocket ); + + AwsIotNetwork_Free( pReceiveBuffer ); + break; + } + + AwsIotLogDebug( "Received %d bytes from socket %d.", + bytesRead, + pConnectionInfo->tcpSocket ); + + /* Invoke the callback function. But if there's no callback to invoke, + * terminate this thread. */ + if( pConnectionInfo->mqttReceiveCallback != NULL ) + { + bytesProcessed = pConnectionInfo->mqttReceiveCallback( pConnectionInfo->pMqttConnection, + pReceiveBuffer, + 0, + ( size_t ) bytesRead, + AwsIotNetwork_Free ); + } + else + { + /* Free resources and terminate thread. */ + AwsIotNetwork_Free( pReceiveBuffer ); + + break; + } + + /* Check the return value of the MQTT callback. */ + if( bytesProcessed < 0 ) + { + AwsIotLogError( "Detected MQTT protocol violation. Receive thread for " + "socket %d terminating.", pConnectionInfo->tcpSocket ); + break; + } + + /* Unlock the connection mutex before polling again. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + } + + AwsIotLogDebug( "Network receive thread for socket %d terminating.", + pConnectionInfo->tcpSocket ); + + /* Clear data about the callback. */ + pConnectionInfo->receiveThreadStatus = _TERMINATED; + pConnectionInfo->mqttReceiveCallback = NULL; + pConnectionInfo->pMqttConnection = NULL; + + /* Unlock the connection mutex before exiting. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + + return NULL; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Perform a DNS lookup of a host name and establish a TCP connection. + * + * @param[in] pHostName The host name to look up. + * @param[in] hostNameLength Length of `pHostName`. + * @param[in] port Remote server port (in host byte order) for the TCP connection. + * + * @return A connected TCP socket number; -1 if the DNS lookup failed. + */ +static inline int _dnsLookup( const char * const pHostName, + size_t hostNameLength, + uint16_t port ) +{ + int status = 0, tcpSocket = -1; + const uint16_t netPort = htons( port ); + struct addrinfo * pListHead = NULL, * pAddressInfo = NULL; + struct sockaddr_in * pServer = NULL; + + /* Perform a DNS lookup of pHostName. */ + AwsIotLogDebug( "Performing DNS lookup of %s", pHostName ); + status = getaddrinfo( pHostName, NULL, NULL, &pListHead ); + + if( status != 0 ) + { + AwsIotLogError( "DNS lookup failed. %s.", gai_strerror( status ) ); + + return -1; + } + + AwsIotLogDebug( "Successfully received DNS records." ); + + /* Go through the list of DNS records until a successful connection is made. */ + for( pAddressInfo = pListHead; pAddressInfo != NULL; pAddressInfo = pAddressInfo->ai_next ) + { + /* Open a socket using the information in the DNS record. */ + AwsIotLogDebug( "Creating socket for domain: %d, type: %d, protocol: %d.", + pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); + tcpSocket = socket( pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); + + /* Check if the socket was successfully opened. */ + if( tcpSocket == -1 ) + { + AwsIotLogDebug( "Could not open socket for record at %p. Trying next.", + pAddressInfo ); + continue; + } + + /* Set the port for the connection. */ + AwsIotLogDebug( "Socket creation successful. Attempting connection." ); + pServer = ( struct sockaddr_in * ) ( pAddressInfo->ai_addr ); + pServer->sin_port = netPort; + + /* Attempt to connect. */ + if( connect( tcpSocket, + ( struct sockaddr * ) pServer, + sizeof( struct sockaddr ) ) == -1 ) + { + /* Connect failed; close socket and try next record. */ + if( close( tcpSocket ) != 0 ) + { + AwsIotLogWarn( "Failed to close socket %d. errno=%d.", + tcpSocket, + errno ); + } + + AwsIotLogDebug( "Socket connection failed. Trying next." ); + } + else + { + /* Connection successful; stop searching the list. */ + AwsIotLogDebug( "Socket connection successful." ); + break; + } + } + + freeaddrinfo( pListHead ); + + /* If pAddressInfo is NULL, then the entire list of records was parsed but none + * of them provided a successful connection. */ + if( pAddressInfo == NULL ) + { + AwsIotLogError( "Failed to connect to all retrieved DNS records." ); + + return -1; + } + + return tcpSocket; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Reads credentials from the filesystem. + * + * Uses OpenSSL to import the root CA certificate, client certificate, and + * client certificate private key. + * @param[in] pSSLContext Destination for the imported credentials. + * @param[in] pRootCAPath Path to the root CA certificate. + * @param[in] pClientCertPath Path to the client certificate. + * @param[in] pCertPrivateKeyPath Path to the client certificate private key. + */ +static inline bool _readCredentials( SSL_CTX * pSSLContext, + const char * const pRootCAPath, + const char * const pClientCertPath, + const char * const pCertPrivateKeyPath ) +{ + X509 * pRootCA = NULL; + + /* OpenSSL does not provide a single function for reading and loading certificates + * from files into stores, so the file API must be called. */ + AwsIotLogDebug( "Opening root certificate %s", pRootCAPath ); + FILE * pRootCAFile = fopen( pRootCAPath, "r" ); + + if( pRootCAFile == NULL ) + { + AwsIotLogError( "Failed to open %s", pRootCAPath ); + + return false; + } + + /* Read the root CA into an X509 object, then close its file handle. */ + pRootCA = PEM_read_X509( pRootCAFile, NULL, NULL, NULL ); + + if( fclose( pRootCAFile ) != 0 ) + { + AwsIotLogWarn( "Failed to close file %s", pRootCAPath ); + } + + if( pRootCA == NULL ) + { + AwsIotLogError( "Failed to parse root CA." ); + + return false; + } + + /* Add the root CA to certificate store. */ + if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSSLContext ), + pRootCA ) != 1 ) + { + AwsIotLogError( "Failed to add root CA to certificate store." ); + + return false; + } + + /* Free the root CA object. */ + X509_free( pRootCA ); + AwsIotLogInfo( "Successfully imported root CA." ); + + /* Import the client certificate. */ + if( SSL_CTX_use_certificate_file( pSSLContext, + pClientCertPath, + SSL_FILETYPE_PEM ) != 1 ) + { + AwsIotLogError( "Failed to import client certificate at %s", + pClientCertPath ); + + return false; + } + + AwsIotLogInfo( "Successfully imported client certificate." ); + + /* Import the client certificate private key. */ + if( SSL_CTX_use_PrivateKey_file( pSSLContext, + pCertPrivateKeyPath, + SSL_FILETYPE_PEM ) != 1 ) + { + AwsIotLogError( "Failed to import client certificate private key at %s", + pCertPrivateKeyPath ); + + return false; + } + + AwsIotLogInfo( "Successfully imported client certificate private key." ); + + return true; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Set up TLS on a TCP connection. + * + * @param[in] pConnectionInfo An established TCP connection. + * @param[in] pServerName Remote host name, used for server name indication. + * @param[in] pTlsInfo TLS setup parameters. + * + * @return #AWS_IOT_NETWORK_SUCCESS, #AWS_IOT_NETWORK_BAD_PARAMETER, + * #AWS_IOT_NETWORK_TLS_LIBRARY_ERROR, or #AWS_IOT_NETWORK_CREDENTIAL_READ_ERROR + */ +static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnectionInfo, + const char * const pServerName, + const AwsIotNetworkTlsInfo_t * const pTlsInfo ) +{ + SSL_CTX * pSSLContext = NULL; + AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; + + /* Check credentials. The sizes of credentials are ignored, as the credential + * parameters represent filesystem paths and the OpenSSL APIs for reading + * credential paths do not take a size (NULL-terminated strings expected). */ + if( ( pTlsInfo->pRootCa == NULL ) || + ( pTlsInfo->pClientCert == NULL ) || + ( pTlsInfo->pPrivateKey == NULL ) ) + { + AwsIotLogError( "Bad parameter in TLS setup parameters." ); + + return AWS_IOT_NETWORK_BAD_PARAMETER; + } + + /* Create a new SSL context. */ + #if OPENSSL_VERSION_NUMBER < 0x10100000L + pSSLContext = SSL_CTX_new( TLSv1_2_client_method() ); + #else + pSSLContext = SSL_CTX_new( TLS_client_method() ); + #endif + + if( pSSLContext == NULL ) + { + AwsIotLogError( "Failed to create new SSL context." ); + + return AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The + * mask returned by SSL_CTX_set_mode does not need to be checked. */ + AwsIotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); + ( void ) SSL_CTX_set_mode( pSSLContext, SSL_MODE_AUTO_RETRY ); + + /* Import all credentials. */ + if( _readCredentials( pSSLContext, + pTlsInfo->pRootCa, + pTlsInfo->pClientCert, + pTlsInfo->pPrivateKey ) == false ) + { + SSL_CTX_free( pSSLContext ); + + return AWS_IOT_NETWORK_CREDENTIAL_READ_ERROR; + } + + /* Create a new SSL connection context, then free the SSL context as it's no + * longer needed. */ + pConnectionInfo->pSSLConnectionContext = SSL_new( pSSLContext ); + SSL_CTX_free( pSSLContext ); + + if( pConnectionInfo->pSSLConnectionContext == NULL ) + { + AwsIotLogError( "Failed to create new SSL connection context." ); + + return AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + + /* Enable SSL peer verification. */ + AwsIotLogDebug( "Setting SSL_VERIFY_PEER." ); + SSL_set_verify( pConnectionInfo->pSSLConnectionContext, SSL_VERIFY_PEER, NULL ); + + /* Set the socket for the SSL connection. */ + if( SSL_set_fd( pConnectionInfo->pSSLConnectionContext, + pConnectionInfo->tcpSocket ) != 1 ) + { + AwsIotLogError( "Failed to set SSL socket %d.", pConnectionInfo->tcpSocket ); + status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + + /* Set up ALPN. */ + if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->pAlpnProtos != NULL ) ) + { + AwsIotLogDebug( "Setting ALPN protos." ); + + if( ( SSL_set_alpn_protos( pConnectionInfo->pSSLConnectionContext, + ( const unsigned char * ) pTlsInfo->pAlpnProtos, + ( unsigned int ) strlen( pTlsInfo->pAlpnProtos ) ) != 0 ) ) + { + AwsIotLogError( "Failed to set ALPN protos." ); + status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + } + + /* Set TLS MFLN. */ + if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->maxFragmentLength > 0 ) ) + { + AwsIotLogDebug( "Setting max send fragment length %lu.", + ( unsigned long ) pTlsInfo->maxFragmentLength ); + + /* Set the maximum send fragment length. */ + if( SSL_set_max_send_fragment( pConnectionInfo->pSSLConnectionContext, + ( long ) pTlsInfo->maxFragmentLength ) != 1 ) + { + AwsIotLogError( "Failed to set max send fragment length %lu.", + ( unsigned long ) pTlsInfo->maxFragmentLength ); + status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + else + { + /* In supported versions of OpenSSL, change the size of the read buffer + * to match the maximum fragment length + some extra bytes for overhead. + * Note that OpenSSL ignores this setting if it's smaller than the default. + */ + #if OPENSSL_VERSION_NUMBER > 0x10100000L + SSL_set_default_read_buffer_len( pConnectionInfo->pSSLConnectionContext, + pTlsInfo->maxFragmentLength + + SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); + #endif + } + } + + /* Set server name for SNI. */ + if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->disableSni == false ) ) + { + AwsIotLogDebug( "Setting server name %s for SNI.", pServerName ); + + if( SSL_set_tlsext_host_name( pConnectionInfo->pSSLConnectionContext, + pServerName ) != 1 ) + { + AwsIotLogError( "Failed to set server name %s for SNI.", pServerName ); + status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + } + + /* Perform the TLS handshake. */ + if( status == AWS_IOT_NETWORK_SUCCESS ) + { + if( SSL_connect( pConnectionInfo->pSSLConnectionContext ) != 1 ) + { + AwsIotLogError( "TLS handshake failed." ); + status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + else + { + AwsIotLogInfo( "TLS handshake succeeded." ); + } + } + + /* Verify the peer certificate. */ + if( status == AWS_IOT_NETWORK_SUCCESS ) + { + if( SSL_get_verify_result( pConnectionInfo->pSSLConnectionContext ) != X509_V_OK ) + { + AwsIotLogError( "Peer certificate verification failed." ); + status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + } + else + { + AwsIotLogInfo( "Peer certificate verified. TLS connection established." ); + } + } + + /* Clean up on error. */ + if( status != AWS_IOT_NETWORK_SUCCESS ) + { + SSL_free( pConnectionInfo->pSSLConnectionContext ); + pConnectionInfo->pSSLConnectionContext = NULL; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Cleans up TLS for a connection. + * + * @param[in] pConnectionInfo The TLS connection to clean up. + */ +static inline void _tlsCleanup( _connectionInfo_t * const pConnectionInfo ) +{ + /* Shut down the TLS connection. */ + AwsIotLogInfo( "Shutting down TLS connection." ); + + /* SSL shutdown should be called twice: once to send "close notify" and once + * more to receive the peer's "close notify". */ + if( SSL_shutdown( pConnectionInfo->pSSLConnectionContext ) == 0 ) + { + AwsIotLogDebug( "\"Close notify\" sent. Awaiting peer response." ); + + /* The previous call to SSL_shutdown marks the SSL connection as closed. + * SSL_shutdown must be called again to read the peer response. */ + if( SSL_shutdown( pConnectionInfo->pSSLConnectionContext ) != 1 ) + { + AwsIotLogWarn( "No response from peer." ); + } + else + { + AwsIotLogDebug( "Peer response to \"close notify\" received." ); + } + } + + AwsIotLogInfo( "TLS connection terminated." ); + + /* Free the memory used by the TLS connection. */ + SSL_free( pConnectionInfo->pSSLConnectionContext ); + pConnectionInfo->pSSLConnectionContext = NULL; +} + +/*-----------------------------------------------------------*/ + +AwsIotNetworkError_t AwsIotNetwork_Init( void ) +{ + /* Details on SIGPIPE handling. */ + struct sigaction sigpipeAction; + + ( void ) memset( &sigpipeAction, 0x00, sizeof( struct sigaction ) ); + + /* Set a signal handler for SIGPIPE, which may be raised if the peer closes + * the connection. */ + sigpipeAction.sa_handler = _sigpipeHandler; + + if( sigaction( SIGPIPE, &sigpipeAction, NULL ) != 0 ) + { + AwsIotLogError( "Failed to set SIGPIPE handler. errno=%d.", errno ); + + return AWS_IOT_NETWORK_INIT_FAILED; + } + + /* Per the OpenSSL 1.0.2 docs, the return value of this function is meaningless. + * This function is also deprecated, but it's called to retain compatibility + * with v1.0.2. */ + ( void ) SSL_library_init(); + + AwsIotLogInfo( "Network library initialized." ); + + return AWS_IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotNetwork_Cleanup( void ) +{ + /* Call the necessary OpenSSL functions to prevent memory leaks. */ + #if OPENSSL_VERSION_NUMBER < 0x10100000L + ERR_remove_thread_state( NULL ); + #endif + CRYPTO_cleanup_all_ex_data(); + EVP_cleanup(); + SSL_COMP_free_compression_methods(); + + AwsIotLogInfo( "Network library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + +AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * const pNetworkConnection, + const char * const pHostName, + uint16_t port, + const AwsIotNetworkTlsInfo_t * const pTlsInfo ) +{ + int tcpSocket = -1; + AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; + const size_t hostNameLength = strlen( pHostName ); + _connectionInfo_t * pConnectionInfo = NULL; + + /* Check parameters. */ + if( ( pNetworkConnection == NULL ) || ( pHostName == NULL ) || ( port == 0 ) ) + { + AwsIotLogError( "Bad parameter to AwsIotNetwork_TcpConnect." ); + + return AWS_IOT_NETWORK_BAD_PARAMETER; + } + + /* Allocate memory for the connection context. This will hold information + * about the connection. */ + pConnectionInfo = ( _connectionInfo_t * ) AwsIotNetwork_Malloc( sizeof( _connectionInfo_t ) ); + + if( pConnectionInfo == NULL ) + { + AwsIotLogError( "Failed to allocate memory for connection information." ); + + return AWS_IOT_NETWORK_NO_MEMORY; + } + + /* Create the network connection mutex. */ + if( AwsIotMutex_Create( &( pConnectionInfo->mutex ) ) == false ) + { + AwsIotLogError( "Failed to create connection mutex." ); + AwsIotNetwork_Free( pConnectionInfo ); + + return AWS_IOT_NETWORK_NO_MEMORY; + } + + /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ + tcpSocket = _dnsLookup( pHostName, hostNameLength, port ); + + if( tcpSocket == -1 ) + { + status = AWS_IOT_NETWORK_DNS_LOOKUP_ERROR; + } + + if( status == AWS_IOT_NETWORK_SUCCESS ) + { + /* Initialize the other members of the connection struct. */ + pConnectionInfo->tcpSocket = tcpSocket; + pConnectionInfo->pSSLConnectionContext = NULL; + pConnectionInfo->receiveThreadStatus = _NONE; + pConnectionInfo->mqttReceiveCallback = NULL; + pConnectionInfo->pMqttConnection = NULL; + ( void ) memset( &( pConnectionInfo->receiveThread ), 0x00, sizeof( pthread_t ) ); + + /* Set the output parameter. */ + *pNetworkConnection = pConnectionInfo; + + AwsIotLogInfo( "TCP connection successful." ); + + if( pTlsInfo != NULL ) + { + AwsIotLogInfo( "Setting up TLS." ); + + status = _tlsSetup( pConnectionInfo, pHostName, pTlsInfo ); + } + } + + /* Clean up on error. */ + if( status != AWS_IOT_NETWORK_SUCCESS ) + { + if( tcpSocket != -1 ) + { + ( void ) close( tcpSocket ); + } + + AwsIotNetwork_DestroyConnection( pConnectionInfo ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection ) +{ + int posixError = 0; + _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; + + /* Check parameters. */ + if( pConnectionInfo == NULL ) + { + AwsIotLogError( "Bad parameter to AwsIotNetwork_CloseConnection." ); + + return; + } + + /* Lock the connection mutex to block the receive thread. */ + AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + + /* Check if a network receive thread was created. */ + if( pConnectionInfo->receiveThreadStatus != _NONE ) + { + /* Send a cancellation request to the receive thread if active. */ + if( pConnectionInfo->receiveThreadStatus == _ACTIVE ) + { + posixError = pthread_cancel( pConnectionInfo->receiveThread ); + + if( ( posixError != 0 ) && ( posixError != ESRCH ) ) + { + AwsIotLogWarn( "Failed to send cancellation request to socket %d receive " + "thread during TLS cleanup. errno=%d.", + pConnectionInfo->tcpSocket, + posixError ); + } + } + + /* Join the receive thread. */ + posixError = pthread_join( pConnectionInfo->receiveThread, NULL ); + + if( posixError != 0 ) + { + AwsIotLogWarn( "Failed to join network receive thread for socket %d. errno=%d", + pConnectionInfo->tcpSocket, + posixError ); + } + + /* Clear data about the receive thread. */ + pConnectionInfo->receiveThreadStatus = _NONE; + ( void ) memset( &( pConnectionInfo->receiveThread ), 0x00, sizeof( pthread_t ) ); + } + + /* If TLS was set up for this connection, clean it up. */ + if( pConnectionInfo->pSSLConnectionContext != NULL ) + { + _tlsCleanup( pConnectionInfo ); + } + + /* Close the connection. */ + if( close( pConnectionInfo->tcpSocket ) != 0 ) + { + AwsIotLogWarn( "Could not close socket %d. errno=%d.", + pConnectionInfo->tcpSocket, + errno ); + } + else + { + AwsIotLogInfo( "Connection (socket %d) closed.", + pConnectionInfo->tcpSocket ); + } + + /* Unlock the connection mutex. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnection ) +{ + _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; + + if( pConnectionInfo == NULL ) + { + AwsIotLogError( "Bad parameter to AwsIotNetwork_DestroyConnection." ); + + return; + } + + /* Destroy the connection data mutex. */ + AwsIotMutex_Destroy( &( pConnectionInfo->mutex ) ); + + /* Free memory in use by the connection. */ + AwsIotNetwork_Free( pConnectionInfo ); +} + +/*-----------------------------------------------------------*/ + +AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnection_t networkConnection, + AwsIotMqttConnection_t * pMqttConnection, + AwsIotMqttReceiveCallback_t receiveCallback ) +{ + int posixError = 0; + AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; + _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; + + /* Lock the connection mutex before changing the callback and its parameter. */ + AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + + /* Clean up any previously terminated receive thread. */ + if( pConnectionInfo->receiveThreadStatus == _TERMINATED ) + { + posixError = pthread_join( pConnectionInfo->receiveThread, NULL ); + + if( posixError != 0 ) + { + AwsIotLogWarn( "Failed to join socket %d network receive thread. errno=%d.", + pConnectionInfo->tcpSocket, + posixError ); + + status = AWS_IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pConnectionInfo->receiveThreadStatus = _NONE; + } + } + + /* If the receive thread for this connection hasn't been created, create it now. */ + if( ( pConnectionInfo->receiveThreadStatus == _NONE ) && ( status == AWS_IOT_NETWORK_SUCCESS ) ) + { + /* Update the callback and parameter. */ + pConnectionInfo->mqttReceiveCallback = receiveCallback; + pConnectionInfo->pMqttConnection = pMqttConnection; + + posixError = pthread_create( &pConnectionInfo->receiveThread, + NULL, + _networkReceiveThread, + pConnectionInfo ); + + if( posixError != 0 ) + { + AwsIotLogError( "Failed to create socket %d network receive thread. errno=%d.", + pConnectionInfo->tcpSocket, + posixError ); + status = AWS_IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pConnectionInfo->receiveThreadStatus = _ACTIVE; + } + } + + /* Unlock the connection mutex. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +size_t AwsIotNetwork_Send( void * networkConnection, + const void * const pMessage, + size_t messageLength ) +{ + int bytesSent = 0; + _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; + struct pollfd fileDescriptors = + { + .events = POLLOUT, + .revents = 0 + }; + + /* Check parameters. */ + if( ( pConnectionInfo == NULL ) || ( pMessage == NULL ) || ( messageLength == 0 ) ) + { + AwsIotLogError( "Bad parameter to AwsIotNetwork_Send." ); + + return 0; + } + + /* Send message. */ + AwsIotLogDebug( "Sending %lu bytes over network.", + ( unsigned long ) messageLength ); + + /* Lock the connection mutex to prevent the connection from being closed + * while sending. */ + AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + + /* Set the file descriptor for poll. */ + fileDescriptors.fd = pConnectionInfo->tcpSocket; + + /* Check that it's possible to send data right now. */ + if( poll( &fileDescriptors, 1, 0 ) == 1 ) + { + /* Use OpenSSL's SSL_write() function or the POSIX send() function based on + * whether the SSL connection context is set up. */ + if( pConnectionInfo->pSSLConnectionContext != NULL ) + { + bytesSent = SSL_write( pConnectionInfo->pSSLConnectionContext, + pMessage, + ( int ) messageLength ); + } + else + { + bytesSent = ( int ) send( pConnectionInfo->tcpSocket, + pMessage, + messageLength, + 0 ); + } + } + + /* Unlock the connection mutex. */ + AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + + /* Check for errors. */ + if( bytesSent <= 0 ) + { + AwsIotLogError( "Send failure." ); + + return 0; + } + + if( ( size_t ) bytesSent != messageLength ) + { + AwsIotLogWarn( "Failed to send %lu bytes, %d bytes sent instead.", + ( unsigned long ) messageLength, + bytesSent ); + } + else + { + AwsIotLogDebug( "Successfully sent %lu bytes.", + ( unsigned long ) messageLength ); + } + + return ( size_t ) bytesSent; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c new file mode 100644 index 0000000000..0bff96709e --- /dev/null +++ b/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_static_memory_common_posix.c + * @brief Implementation of common static memory functions in aws_iot_static_memory.h + * for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX include. Allow it to be overridden. */ +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + +/* Static memory include. */ +#include "platform/aws_iot_static_memory.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_MESSAGE_BUFFERS + #define AWS_IOT_MESSAGE_BUFFERS ( 8 ) +#endif +#ifndef AWS_IOT_MESSAGE_BUFFER_SIZE + #define AWS_IOT_MESSAGE_BUFFER_SIZE ( 1024 ) +#endif +/** @endcond */ + +/* Validate static memory configuration settings. */ +#if AWS_IOT_MESSAGE_BUFFERS <= 0 + #error "AWS_IOT_MESSAGE_BUFFERS cannot be 0 or negative." +#endif +#if AWS_IOT_MESSAGE_BUFFER_SIZE <= 0 + #error "AWS_IOT_MESSAGE_BUFFER_SIZE cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Find a free buffer using the "in-use" flags. + * + * If a free buffer is found, this function marks the buffer in-use. This function + * is common to the static memory implementation on POSIX systems. + * + * @param[in] pInUse The "in-use" flags to search. + * @param[in] limit How many flags to check. + * + * @return The index of a free buffer; -1 if no free buffers are available. + */ +int AwsIotStaticMemory_FindFree( bool * const pInUse, + int limit ); + +/** + * @brief Return an "in-use" buffer. + * + * This function is common to the static memory implementation on POSIX systems. + * + * @param[in] ptr Pointer to the buffer to return. + * @param[in] pPool The pool of buffers that the in-use buffer was allocation from. + * @param[in] pInUse The "in-use" flags for pPool. + * @param[in] limit How many buffers (and flags) to check while searching for ptr. + * @param[in] elementSize The size of a single element in pPool. + */ +void AwsIotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Guards access to critical sections. + */ +static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER; + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _pInUseMessageBuffers[ AWS_IOT_MESSAGE_BUFFERS ] = { 0 }; /**< @brief Message buffer in-use flags. */ +static char _pMessageBuffers[ AWS_IOT_MESSAGE_BUFFERS ][ AWS_IOT_MESSAGE_BUFFER_SIZE ] = { { 0 } }; /**< @brief Message buffers. */ + +/*-----------------------------------------------------------*/ + +int AwsIotStaticMemory_FindFree( bool * const pInUse, + int limit ) +{ + int i = 0, freeIndex = -1; + + /* Perform the search for a free buffer in a critical section. */ + if( pthread_mutex_lock( &_mutex ) != 0 ) + { + return -1; + } + + for( i = 0; i < limit; i++ ) + { + if( pInUse[ i ] == false ) + { + /* If a free buffer is found, mark it "in-use" and return its index. */ + pInUse[ i ] = true; + freeIndex = i; + break; + } + } + + /* Exit the critical section. */ + ( void ) pthread_mutex_unlock( &_mutex ); + + return freeIndex; +} + +/*-----------------------------------------------------------*/ + +void AwsIotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ) +{ + int i = 0; + uint8_t * element = NULL; + + /* Clear ptr. */ + ( void ) memset( ptr, 0x00, elementSize ); + + /* Perform a search for ptr to make sure it's part of pPool. This search + * is done in a critical section. */ + if( pthread_mutex_lock( &_mutex ) != 0 ) + { + return; + } + + for( i = 0; i < limit; i++ ) + { + /* Calculate address of the i-th element in pPool. */ + element = ( ( uint8_t * ) pPool ) + elementSize * ( size_t ) i; + + /* Check for a match. */ + if( ( ( void * ) element == ptr ) && + ( pInUse[ i ] == true ) ) + { + pInUse[ i ] = false; + break; + } + } + + /* Exit the critical section. */ + ( void ) pthread_mutex_unlock( &_mutex ); +} + +/*-----------------------------------------------------------*/ + +size_t AwsIot_MessageBufferSize( void ) +{ + return ( size_t ) AWS_IOT_MESSAGE_BUFFER_SIZE; +} + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocMessageBuffer( size_t size ) +{ + int freeIndex = -1; + void * pNewBuffer = NULL; + + /* Check that size is within the fixed message buffer size. */ + if( size <= AWS_IOT_MESSAGE_BUFFER_SIZE ) + { + /* Get the index of a free message buffer. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseMessageBuffers, + AWS_IOT_MESSAGE_BUFFERS ); + + if( freeIndex != -1 ) + { + pNewBuffer = &( _pMessageBuffers[ freeIndex ][ 0 ] ); + } + } + + return pNewBuffer; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeMessageBuffer( void * ptr ) +{ + /* Return the in-use message buffer. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pMessageBuffers, + _pInUseMessageBuffers, + AWS_IOT_MESSAGE_BUFFERS, + AWS_IOT_MESSAGE_BUFFER_SIZE ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c new file mode 100644 index 0000000000..7fd318ad09 --- /dev/null +++ b/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_static_memory_mqtt_posix.c + * @brief Implementation of MQTT static memory functions in aws_iot_static_memory.h + * for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* POSIX include. Allow it to be overridden. */ +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + +/* Static memory include. */ +#include "platform/aws_iot_static_memory.h" + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_MQTT_CONNECTIONS + #define AWS_IOT_MQTT_CONNECTIONS ( 1 ) +#endif +#ifndef AWS_IOT_MQTT_SUBSCRIPTIONS + #define AWS_IOT_MQTT_SUBSCRIPTIONS ( 8 ) +#endif +#ifndef AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS + #define AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) +#endif +/** @endcond */ + +/* Validate static memory configuration settings. */ +#if AWS_IOT_MQTT_CONNECTIONS <= 0 + #error "AWS_IOT_MQTT_CONNECTIONS cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_SUBSCRIPTIONS <= 0 + #error "AWS_IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS <= 0 + #error "AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." +#endif + +/** + * @brief The size of a static memory MQTT subscription. + * + * Since the pTopic member of #_mqttSubscription_t is variable-length, the constant + * #_AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of + * #_mqttSubscription_t.pTopicFilter. + */ +#define _MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in aws_iot_static_memory_common_posix.c + * Because these functions are POSIX-platform-specific, they are not placed in + * a platform header file. */ +extern int AwsIotStaticMemory_FindFree( bool * const pInUse, + int limit ); +extern void AwsIotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _pInUseMqttConnections[ AWS_IOT_MQTT_CONNECTIONS ] = { 0 }; /**< @brief MQTT connection in-use flags. */ +static _mqttConnection_t _pMqttConnections[ AWS_IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ + +static bool _pInUseMqttOperations[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ +static _mqttOperation_t _pMqttOperations[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operations. */ + +static bool _pInUseMqttTimerEvents[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer event in-use flags. */ +static _mqttTimerEvent_t _pMqttTimerEvents[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer events. */ + +static bool _pInUseMqttSubscriptions[ AWS_IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ +static char _pMqttSubscriptions[ AWS_IOT_MQTT_SUBSCRIPTIONS ][ _MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocMqttConnection( size_t size ) +{ + int freeIndex = -1; + void * pNewConnection = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttConnection_t ) ) + { + /* Find a free MQTT connection. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttConnections, + AWS_IOT_MQTT_CONNECTIONS ); + + if( freeIndex != -1 ) + { + pNewConnection = &( _pMqttConnections[ freeIndex ] ); + } + } + + return pNewConnection; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeMqttConnection( void * ptr ) +{ + /* Return the in-use MQTT connection. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pMqttConnections, + _pInUseMqttConnections, + AWS_IOT_MQTT_CONNECTIONS, + sizeof( _mqttConnection_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocMqttOperation( size_t size ) +{ + int freeIndex = -1; + void * pNewOperation = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttOperation_t ) ) + { + /* Find a free MQTT operation. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttOperations, + AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewOperation = &( _pMqttOperations[ freeIndex ] ); + } + } + + return pNewOperation; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeMqttOperation( void * ptr ) +{ + /* Return the in-use MQTT operation. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pMqttOperations, + _pInUseMqttOperations, + AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _mqttOperation_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocMqttTimerEvent( size_t size ) +{ + int freeIndex = -1; + void * pNewTimerEvent = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttTimerEvent_t ) ) + { + /* Find a free MQTT timer event. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttTimerEvents, + AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewTimerEvent = &( _pMqttTimerEvents[ freeIndex ] ); + } + } + + return pNewTimerEvent; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeMqttTimerEvent( void * ptr ) +{ + /* Return the in-use MQTT timer event. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pMqttTimerEvents, + _pInUseMqttTimerEvents, + AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _mqttTimerEvent_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocMqttSubscription( size_t size ) +{ + int freeIndex = -1; + void * pNewSubscription = NULL; + + if( size <= _MQTT_SUBSCRIPTION_SIZE ) + { + /* Get the index of a free MQTT subscription. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttSubscriptions, + AWS_IOT_MQTT_SUBSCRIPTIONS ); + + if( freeIndex != -1 ) + { + pNewSubscription = &( _pMqttSubscriptions[ freeIndex ][ 0 ] ); + } + } + + return pNewSubscription; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeMqttSubscription( void * ptr ) +{ + /* Return the in-use MQTT subscription. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pMqttSubscriptions, + _pInUseMqttSubscriptions, + AWS_IOT_MQTT_SUBSCRIPTIONS, + _MQTT_SUBSCRIPTION_SIZE ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c new file mode 100644 index 0000000000..b810b87b9d --- /dev/null +++ b/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_static_memory_shadow_posix.c + * @brief Implementation of Shadow static memory functions in aws_iot_static_memory.h + * for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* POSIX include. Allow it to be overridden. */ +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + +/* Static memory include. */ +#include "platform/aws_iot_static_memory.h" + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS + #define AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ( 10 ) +#endif +#ifndef AWS_IOT_SHADOW_SUBSCRIPTIONS + #define AWS_IOT_SHADOW_SUBSCRIPTIONS ( 2 ) +#endif +/** @endcond */ + +/* Validate static memory configuration settings. */ +#if AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS <= 0 + #error "AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." +#endif +#if AWS_IOT_SHADOW_SUBSCRIPTIONS <= 0 + #error "AWS_IOT_SHADOW_SUBSCRIPTIONS cannot be 0 or negative." +#endif + +/** + * @brief The size of a static memory Shadow subscription. + * + * Since the pThingName member of #_shadowSubscription_t is variable-length, + * the constant #_MAX_THING_NAME_LENGTH is used for the length of + * #_shadowSubscription_t.pThingName. + */ +#define _SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + _MAX_THING_NAME_LENGTH ) + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in aws_iot_static_memory_common_posix.c + * Because these functions are POSIX-platform-specific, they are not placed in + * a platform header file. */ +extern int AwsIotStaticMemory_FindFree( bool * const pInUse, + int limit ); +extern void AwsIotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief Shadow operation in-use flags. */ +static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief Shadow operations. */ + +static bool _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0 }; /**< @brief Shadow subscription in-use flags. */ +static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ _SHADOW_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Shadow subscriptions. */ + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocShadowOperation( size_t size ) +{ + int freeIndex = -1; + void * pNewOperation = NULL; + + /* Check size argument. */ + if( size == sizeof( _shadowOperation_t ) ) + { + /* Find a free Shadow operation. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseShadowOperations, + AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewOperation = &( _pShadowOperations[ freeIndex ] ); + } + } + + return pNewOperation; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeShadowOperation( void * ptr ) +{ + /* Return the in-use Shadow operation. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pShadowOperations, + _pInUseShadowOperations, + AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _shadowOperation_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * AwsIot_MallocShadowSubscription( size_t size ) +{ + int freeIndex = -1; + void * pNewSubscription = NULL; + + if( size <= _SHADOW_SUBSCRIPTION_SIZE ) + { + /* Get the index of a free Shadow subscription. */ + freeIndex = AwsIotStaticMemory_FindFree( _pInUseShadowSubscriptions, + AWS_IOT_SHADOW_SUBSCRIPTIONS ); + + if( freeIndex != -1 ) + { + pNewSubscription = &( _pShadowSubscriptions[ freeIndex ][ 0 ] ); + } + } + + return pNewSubscription; +} + +/*-----------------------------------------------------------*/ + +void AwsIot_FreeShadowSubscription( void * ptr ) +{ + /* Return the in-use Shadow subscription. */ + AwsIotStaticMemory_ReturnInUse( ptr, + _pShadowSubscriptions, + _pInUseShadowSubscriptions, + AWS_IOT_SHADOW_SUBSCRIPTIONS, + _SHADOW_SUBSCRIPTION_SIZE ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt new file mode 100644 index 0000000000..88c5404e70 --- /dev/null +++ b/lib/source/shadow/CMakeLists.txt @@ -0,0 +1,25 @@ +# Shadow library source files. +add_library( awsiotshadow SHARED + aws_iot_shadow_api.c + aws_iot_shadow_operation.c + aws_iot_shadow_parser.c + aws_iot_shadow_subscription.c ) + +# Library version. +set_target_properties( awsiotshadow PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Library public headers. +set_target_properties( awsiotshadow PROPERTIES PUBLIC_HEADER + "../../include/aws_iot_shadow.h" ) + +# Library private headers. +set_target_properties( awsiotmqtt PROPERTIES PRIVATE_HEADER + "../../include/private/aws_iot_shadow_internal.h" ) + +# Link required libraries. +target_link_libraries( awsiotshadow awsiotcommon awsiotplatform awsiotmqtt ) + +# Link Unity test framework when building tests. +if( ${AWS_IOT_BUILD_TESTS} ) + target_link_libraries( awsiotshadow unity ) +endif() diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c new file mode 100644 index 0000000000..5654a571ab --- /dev/null +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -0,0 +1,1111 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow_api.c + * @brief Implements the user-facing functions of the Shadow library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/* JSON utilities include. */ +#include "aws_iot_json_utils.h" + +/* Validate Shadow configuration settings. */ +#if AWS_IOT_SHADOW_ENABLE_ASSERTS != 0 && AWS_IOT_SHADOW_ENABLE_ASSERTS != 1 + #error "AWS_IOT_SHADOW_ENABLE_ASSERTS must be 0 or 1." +#endif +#if AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS <= 0 + #error "AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Checks Thing Name and flags parameters passed to Shadow API functions. + * + * @param[in] type The Shadow API function type. + * @param[in] pThingName Thing Name passed to Shadow API function. + * @param[in] thingNameLength Length of `pThingName`. + * @param[in] flags Flags passed to Shadow API function. + * @param[in] pCallbackInfo Callback info passed to Shadow API function. + * @param[in] pReference Reference pointer passed to Shadow API function. + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_PARAMETER. + */ +static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + const AwsIotShadowReference_t * const pReference ); + +/** + * @brief Checks document info parameter passed to Shadow API functions. + * + * @param[in] type The Shadow API function type. + * @param[in] flags Flags passed to Shadow API function. + * @param[in] pDocumentInfo Document info passed to Shadow API function. + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_PARAMETER. + */ +static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, + uint32_t flags, + const AwsIotShadowDocumentInfo_t * pDocumentInfo ); + +/** + * @brief Common function for setting Shadow callbacks. + * + * @param[in] mqttConnection The MQTT connection to use. + * @param[in] type Type of Shadow callback. + * @param[in] pThingName Thing Name for Shadow callback. + * @param[in] thingNameLength Length of `pThingName`. + * @param[in] pCallbackInfo Callback information to set. + * + * @return #AWS_IOT_SHADOW_SUCCESS, #AWS_IOT_SHADOW_BAD_PARAMETER, + * #AWS_IOT_SHADOW_NO_MEMORY, or #AWS_IOT_SHADOW_MQTT_ERROR. + */ +static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnection, + _shadowCallbackType_t type, + const char * const pThingName, + size_t thingNameLength, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo ); + +/** + * @brief Change the subscriptions for Shadow callbacks, either by subscribing + * or unsubscribing. + * + * @param[in] mqttConnection The MQTT connection to use. + * @param[in] type Type of Shadow callback. + * @param[in] pSubscription Shadow subscriptions object for callback. + * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + */ +static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t mqttConnection, + _shadowCallbackType_t type, + _shadowSubscription_t * const pSubscription, + _mqttOperationFunction_t mqttOperation ); + +/** + * @brief Common function for incoming Shadow callbacks. + * + * @param[in] type Shadow callback type. + * @param[in] pSubscription Shadow subscriptions object for callback. + * @param[in] pMessage The received Shadow callback document (as an MQTT PUBLISH + * message). + */ +static void _callbackWrapperCommon( _shadowCallbackType_t type, + const _shadowSubscription_t * const pSubscription, + AwsIotMqttCallbackParam_t * const pMessage ); + +/** + * @brief Invoked when a document is received on the Shadow DELTA callback. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). + */ +static void _deltaCallbackWrapper( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ); + +/** + * @brief Invoked when a document is received on the Shadow UPDATED callback. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage The received UPDATED document (as an MQTT PUBLISH message). + */ +static void _updatedCallbackWrapper( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Timeout used for MQTT operations. + */ +uint64_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; + +#if _LIBRARY_LOG_LEVEL > _AWS_IOT_LOG_NONE + +/** + * @brief Printable names for the Shadow callbacks. + */ + const char * const _pAwsIotShadowCallbackNames[] = + { + "DELTA", + "UPDATED" + }; +#endif + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + const AwsIotShadowReference_t * const pReference ) +{ + /* Type is not used when logging is disabled. */ + ( void ) type; + + /* Check Thing Name. */ + if( ( pThingName == NULL ) || ( thingNameLength == 0 ) ) + { + AwsIotLogError( "Thing name for Shadow %s cannot be NULL or have length 0.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + if( thingNameLength > _MAX_THING_NAME_LENGTH ) + { + AwsIotLogError( "Thing Name length of %lu exceeds the maximum allowed" + "length of %d.", + ( unsigned long ) thingNameLength, + _MAX_THING_NAME_LENGTH ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check the waitable operation flag. */ + if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + /* Check that a reference pointer is provided for a waitable operation. */ + if( pReference == NULL ) + { + AwsIotLogError( "Reference must be set for a waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* A callback should not be set for a waitable operation. */ + if( pCallbackInfo != NULL ) + { + AwsIotLogError( "Callback should not be set for a waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + } + + /* A callback info must be passed to a non-waitable GET. */ + if( ( type == _SHADOW_GET ) && + ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) && + ( pCallbackInfo == NULL ) ) + { + AwsIotLogError( "Callback info must be provided for non-waitable Shadow GET." ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check that a callback function is set. */ + if( ( pCallbackInfo != NULL ) && + ( pCallbackInfo->function == NULL ) ) + { + AwsIotLogError( "Callback function must be set for Shadow %s callback.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, + uint32_t flags, + const AwsIotShadowDocumentInfo_t * pDocumentInfo ) +{ + /* This function should only be called for Shadow GET or UPDATE. */ + AwsIotShadow_Assert( ( type == _SHADOW_GET ) || ( type == _SHADOW_UPDATE ) ); + + /* Check QoS. */ + if( ( pDocumentInfo->QoS < 0 ) || ( pDocumentInfo->QoS > 1 ) ) + { + AwsIotLogError( "QoS for Shadow %d must be 0 or 1.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check the retry parameters. */ + if( pDocumentInfo->retryLimit < 0 ) + { + AwsIotLogError( "Retry limit of Shadow %s cannot be less than 0.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + else if( pDocumentInfo->retryLimit > 0 ) + { + if( pDocumentInfo->retryMs == 0 ) + { + AwsIotLogError( "Retry time of Shadow %s must be positive.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + } + + /* Check members relevant to a Shadow GET. */ + if( type == _SHADOW_GET ) + { + /* Check memory allocation function for waitable GET. */ + if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && + ( pDocumentInfo->get.mallocDocument == NULL ) ) + { + AwsIotLogError( "Memory allocation function must be set for waitable Shadow GET." ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + } + /* Check members relevant to a Shadow UPDATE. */ + else + { + /* Check UPDATE document pointer and length. */ + if( ( pDocumentInfo->update.pUpdateDocument == NULL ) || + ( pDocumentInfo->update.updateDocumentLength == 0 ) ) + { + AwsIotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" + " have length 0." ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + } + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnection, + _shadowCallbackType_t type, + const char * const pThingName, + size_t thingNameLength, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + _shadowSubscription_t * pSubscription = NULL; + + /* Check parameters. */ + if( _validateThingNameFlags( type + _SHADOW_OPERATION_COUNT, /* Shadow callbacks are enumerated after the operations. */ + pThingName, + thingNameLength, + 0, + pCallbackInfo, + NULL ) != AWS_IOT_SHADOW_SUCCESS ) + { + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + AwsIotLogDebug( "Processing Shadow %s callback for %.*s.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); + + /* Lock the subscription list mutex to check for an existing subscription + * object. */ + AwsIotMutex_Lock( &( _AwsIotShadowSubscriptions.mutex ) ); + + /* Check for an existing subscription. This function will attempt to allocate + * a new subscription if not found. */ + pSubscription = AwsIotShadowInternal_FindSubscription( pThingName, + thingNameLength ); + + if( pSubscription == NULL ) + { + /* No existing subscription was found, and no new subscription could be + * allocated. */ + status = AWS_IOT_SHADOW_NO_MEMORY; + } + else + { + /* Check for an existing callback. */ + if( pSubscription->callbacks[ type ].function != NULL ) + { + /* Replace existing callback. */ + if( pCallbackInfo != NULL ) + { + AwsIotLogDebug( "Found existing %s callback for %.*s. Replacing callback.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); + + pSubscription->callbacks[ type ] = *pCallbackInfo; + } + /* Remove existing callback. */ + else + { + AwsIotLogDebug( "Removing existing %s callback for %.*s.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); + + /* Unsubscribe, then clear the callback information. */ + ( void ) _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + AwsIotMqtt_TimedUnsubscribe ); + ( void ) memset( &( pSubscription->callbacks[ type ] ), + 0x00, + sizeof( AwsIotShadowCallbackInfo_t ) ); + + /* Check if this subscription object can be removed. */ + AwsIotShadowInternal_RemoveSubscription( pSubscription, NULL ); + } + } + /* No existing callback. */ + else + { + /* Add new callback. */ + if( pCallbackInfo != NULL ) + { + AwsIotLogDebug( "Adding new %s callback for %.*s.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); + + pSubscription->callbacks[ type ] = *pCallbackInfo; + status = _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + AwsIotMqtt_TimedSubscribe ); + } + /* Do nothing; set return value to success. */ + else + { + status = AWS_IOT_SHADOW_SUCCESS; + } + } + } + + AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptions.mutex ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t mqttConnection, + _shadowCallbackType_t type, + _shadowSubscription_t * const pSubscription, + _mqttOperationFunction_t mqttOperation ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + char * pTopicFilter = NULL; + uint16_t operationTopicLength = 0; + + /* Lookup table for Shadow callback suffixes. */ + const char * const pCallbackSuffix[ _SHADOW_CALLBACK_COUNT ] = + { + _SHADOW_DELTA_SUFFIX, /* Delta callback. */ + _SHADOW_UPDATED_SUFFIX /* Updated callback. */ + }; + + /* Lookup table for Shadow callback suffix lengths. */ + const uint16_t pCallbackSuffixLength[ _SHADOW_CALLBACK_COUNT ] = + { + _SHADOW_DELTA_SUFFIX_LENGTH, /* Delta callback. */ + _SHADOW_UPDATED_SUFFIX_LENGTH /* Updated callback. */ + }; + + /* Lookup table for Shadow callback function wrappers. */ + const _mqttCallbackFunction_t pCallbackWrapper[ _SHADOW_CALLBACK_COUNT ] = + { + _deltaCallbackWrapper, /* Delta callback. */ + _updatedCallbackWrapper, /* Updated callback. */ + }; + + /* MQTT operation may only be subscribe or unsubscribe. */ + AwsIotShadow_Assert( ( mqttOperation == AwsIotMqtt_TimedSubscribe ) || + ( mqttOperation == AwsIotMqtt_TimedUnsubscribe ) ); + + /* Use the subscription's topic buffer if available. */ + if( pSubscription->pTopicBuffer != NULL ) + { + pTopicFilter = pSubscription->pTopicBuffer; + } + + /* Generate the prefix portion of the Shadow callback topic filter. Both + * callbacks share the same callback as the Shadow Update operation. */ + if( AwsIotShadowInternal_GenerateShadowTopic( _SHADOW_UPDATE, + pSubscription->pThingName, + pSubscription->thingNameLength, + &pTopicFilter, + &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) + { + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Place the callback suffix in the topic filter. */ + ( void ) memcpy( pTopicFilter + operationTopicLength, + pCallbackSuffix[ type ], + pCallbackSuffixLength[ type ] ); + + AwsIotLogDebug( "%s subscription for %.*s", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", + operationTopicLength + pCallbackSuffixLength[ type ], + pTopicFilter ); + + /* Set the members of the MQTT subscription. */ + subscription.QoS = 1; + subscription.pTopicFilter = pTopicFilter; + subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); + subscription.callback.param1 = ( void * ) pSubscription; + subscription.callback.function = pCallbackWrapper[ type ]; + + /* Call the MQTT operation function. */ + mqttStatus = mqttOperation( mqttConnection, + &subscription, + 1, + 0, + _AwsIotShadowMqttTimeoutMs ); + + /* Check the result of the MQTT operation. */ + if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to %s callback for %.*s %s callback, error %s.", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ], + AwsIotMqtt_strerror( mqttStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + if( mqttStatus == AWS_IOT_MQTT_NO_MEMORY ) + { + status = AWS_IOT_SHADOW_NO_MEMORY; + } + else + { + status = AWS_IOT_SHADOW_MQTT_ERROR; + } + } + else + { + AwsIotLogDebug( "Successfully %s %.*s Shadow %s callback.", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + } + + /* MQTT subscribe should check the subscription topic buffer. */ + if( mqttOperation == AwsIotMqtt_TimedSubscribe ) + { + /* If the current subscription has no topic buffer, assign it the current + * topic buffer. Otherwise, free the current topic buffer. */ + if( pSubscription->pTopicBuffer == NULL ) + { + pSubscription->pTopicBuffer = pTopicFilter; + } + else + { + AwsIotShadow_FreeString( pTopicFilter ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void _callbackWrapperCommon( _shadowCallbackType_t type, + const _shadowSubscription_t * const pSubscription, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + AwsIotShadowCallbackParam_t callbackParam = { 0 }; + + /* Ensure that a callback function is set. */ + AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); + + /* Set the members of the callback param. */ + callbackParam.callbackType = type + _SHADOW_OPERATION_COUNT; /* Shadow callbacks are enumerated after the operations. */ + callbackParam.pThingName = pSubscription->pThingName; + callbackParam.thingNameLength = pSubscription->thingNameLength; + callbackParam.callback.pDocument = pMessage->message.info.pPayload; + callbackParam.callback.documentLength = pMessage->message.info.payloadLength; + + /* Invoke the callback function. */ + pSubscription->callbacks[ type ].function( pSubscription->callbacks[ type ].param1, + &callbackParam ); +} + +/*-----------------------------------------------------------*/ + +static void _deltaCallbackWrapper( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + _callbackWrapperCommon( _DELTA_CALLBACK, pArgument, pMessage ); +} + +/*-----------------------------------------------------------*/ + +static void _updatedCallbackWrapper( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + _callbackWrapperCommon( _UPDATED_CALLBACK, pArgument, pMessage ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) +{ + /* Create the Shadow pending operation list. */ + if( AwsIotList_Create( &_AwsIotShadowPendingOperations ) == false ) + { + AwsIotLogError( "Failed to create Shadow pending operation list." ); + + return AWS_IOT_SHADOW_INIT_FAILED; + } + + /* Create the Shadow subscription list. */ + if( AwsIotList_Create( &_AwsIotShadowSubscriptions ) == false ) + { + AwsIotLogError( "Failed to create Shadow subscription list." ); + AwsIotList_Destroy( &_AwsIotShadowPendingOperations ); + + return AWS_IOT_SHADOW_INIT_FAILED; + } + + /* Save the MQTT timeout option. */ + if( mqttTimeoutMs != 0 ) + { + _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; + } + + AwsIotLogInfo( "Shadow library successfully initialized." ); + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotShadow_Cleanup( void ) +{ + /* Remove and free all items in the Shadow pending operation list, then destroy + * the list. */ + AwsIotList_RemoveAllMatches( &_AwsIotShadowPendingOperations, + _SHADOW_OPERATION_LINK_OFFSET, + NULL, + NULL, + AwsIotShadowInternal_DestroyOperation ); + AwsIotList_Destroy( &_AwsIotShadowPendingOperations ); + + /* Remove and free all items in the Shadow subscription list, then destroy + * the list. */ + AwsIotList_RemoveAllMatches( &_AwsIotShadowSubscriptions, + _SHADOW_SUBSCRIPTION_LINK_OFFSET, + NULL, + NULL, + AwsIotShadowInternal_DestroySubscription ); + AwsIotList_Destroy( &_AwsIotShadowSubscriptions ); + + /* Restore the default MQTT timeout. */ + _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; + + AwsIotLogInfo( "Shadow library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + AwsIotShadowReference_t * const pDeleteRef ) +{ + _shadowOperation_t * pOperation = NULL; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + + /* Validate the Thing Name and flags for Shadow DELETE. */ + if( _validateThingNameFlags( _SHADOW_DELETE, + pThingName, + thingNameLength, + flags, + pCallbackInfo, + pDeleteRef ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* The Thing Name or some flag was invalid. */ + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Allocate a new Shadow operation for DELETE. */ + if( AwsIotShadowInternal_CreateOperation( &pOperation, + _SHADOW_DELETE, + flags, + pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* No memory for a new Shadow operation. */ + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Check the members set by Shadow operation creation. */ + AwsIotShadow_Assert( pOperation != NULL ); + AwsIotShadow_Assert( pOperation->type == _SHADOW_DELETE ); + AwsIotShadow_Assert( pOperation->flags == flags ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + + /* Set the reference if provided. This must be done before the Shadow operation + * is processed. */ + if( pDeleteRef != NULL ) + { + *pDeleteRef = pOperation; + } + + /* Process the Shadow operation. This subscribes to any required topics and + * sends the MQTT message for the Shadow operation. */ + status = AwsIotShadowInternal_ProcessOperation( mqttConnection, + pThingName, + thingNameLength, + pOperation, + NULL ); + + /* If the Shadow operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteRef != NULL ) ) + { + *pDeleteRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_TimedDelete( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + uint64_t timeoutMs ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowReference_t deleteRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; + + /* Call the asynchronous Shadow delete function. */ + status = AwsIotShadow_Delete( mqttConnection, + pThingName, + thingNameLength, + flags, + NULL, + &deleteRef ); + + /* Wait for the Shadow delete operation to complete. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + status = AwsIotShadow_Wait( deleteRef, timeoutMs, NULL, NULL ); + } + + /* Ensure that a status was set. */ + AwsIotShadow_Assert( status != AWS_IOT_SHADOW_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pGetInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + AwsIotShadowReference_t * const pGetRef ) +{ + _shadowOperation_t * pOperation = NULL; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + + /* Validate the Thing Name and flags for Shadow GET. */ + if( _validateThingNameFlags( _SHADOW_GET, + pGetInfo->pThingName, + pGetInfo->thingNameLength, + flags, + pCallbackInfo, + pGetRef ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* The Thing Name or some flag was invalid. */ + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Validate the document info for Shadow GET. */ + if( _validateDocumentInfo( _SHADOW_GET, + flags, + pGetInfo ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* Document info was invalid. */ + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Allocate a new Shadow operation for GET. */ + if( AwsIotShadowInternal_CreateOperation( &pOperation, + _SHADOW_GET, + flags, + pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* No memory for a new Shadow operation. */ + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Check the members set by Shadow operation creation. */ + AwsIotShadow_Assert( pOperation != NULL ); + AwsIotShadow_Assert( pOperation->type == _SHADOW_GET ); + AwsIotShadow_Assert( pOperation->flags == flags ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + + /* Copy the memory allocation function. */ + pOperation->get.mallocDocument = pGetInfo->get.mallocDocument; + + /* Set the reference if provided. This must be done before the Shadow operation + * is processed. */ + if( pGetRef != NULL ) + { + *pGetRef = pOperation; + } + + /* Process the Shadow operation. This subscribes to any required topics and + * sends the MQTT message for the Shadow operation. */ + status = AwsIotShadowInternal_ProcessOperation( mqttConnection, + pGetInfo->pThingName, + pGetInfo->thingNameLength, + pOperation, + pGetInfo ); + + /* If the Shadow operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetRef != NULL ) ) + { + *pGetRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_TimedGet( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pGetInfo, + uint32_t flags, + uint64_t timeoutMs, + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowReference_t getRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; + + /* Call the asynchronous Shadow get function. */ + status = AwsIotShadow_Get( mqttConnection, + pGetInfo, + flags, + NULL, + &getRef ); + + /* Wait for the Shadow get operation to complete. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + status = AwsIotShadow_Wait( getRef, + timeoutMs, + pShadowDocument, + pShadowDocumentLength ); + } + + /* Ensure that a status was set. */ + AwsIotShadow_Assert( status != AWS_IOT_SHADOW_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo, + AwsIotShadowReference_t * const pUpdateRef ) +{ + _shadowOperation_t * pOperation = NULL; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + const char * pClientToken = NULL; + size_t clientTokenLength = 0; + + /* Validate the Thing Name and flags for Shadow UPDATE. */ + if( _validateThingNameFlags( _SHADOW_UPDATE, + pUpdateInfo->pThingName, + pUpdateInfo->thingNameLength, + flags, + pCallbackInfo, + pUpdateRef ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* The Thing Name or some flag was invalid. */ + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Validate the document info for Shadow UPDATE. */ + if( _validateDocumentInfo( _SHADOW_UPDATE, + flags, + pUpdateInfo ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* Document info was invalid. */ + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check UPDATE document for a client token. */ + if( AwsIotJsonUtils_FindJsonValue( pUpdateInfo->update.pUpdateDocument, + pUpdateInfo->update.updateDocumentLength, + _CLIENT_TOKEN_KEY, + _CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ) == false ) + { + AwsIotLogError( "Shadow document for Shadow UPDATE must have a %s key.", + _CLIENT_TOKEN_KEY ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check the client token length. It must be greater than the length of its + * enclosing double quotes (2) and less than the maximum allowed by the Shadow + * service. */ + if( ( clientTokenLength < 2 ) || ( clientTokenLength > _MAX_CLIENT_TOKEN_LENGTH ) ) + { + AwsIotLogError( "Client token length must be between 2 and %d (including " + "enclosing quotes).", _MAX_CLIENT_TOKEN_LENGTH + 2 ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Allocate a new Shadow operation for UPDATE. */ + if( AwsIotShadowInternal_CreateOperation( &pOperation, + _SHADOW_UPDATE, + flags, + pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + { + /* No memory for a new Shadow operation. */ + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Check the members set by Shadow operation creation. */ + AwsIotShadow_Assert( pOperation != NULL ); + AwsIotShadow_Assert( pOperation->type == _SHADOW_UPDATE ); + AwsIotShadow_Assert( pOperation->flags == flags ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + + /* Allocate memory for the client token. */ + pOperation->update.pClientToken = AwsIotShadow_MallocString( clientTokenLength ); + + if( pOperation->update.pClientToken == NULL ) + { + AwsIotLogError( "Failed to allocate memory for Shadow update client token." ); + AwsIotShadowInternal_DestroyOperation( pOperation ); + + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Copy the client token. The client token must be copied in case the application + * frees the buffer containing it. */ + ( void ) memcpy( ( void * ) pOperation->update.pClientToken, + pClientToken, + clientTokenLength ); + pOperation->update.clientTokenLength = clientTokenLength; + + /* Set the reference if provided. This must be done before the Shadow operation + * is processed. */ + if( pUpdateRef != NULL ) + { + *pUpdateRef = pOperation; + } + + /* Process the Shadow operation. This subscribes to any required topics and + * sends the MQTT message for the Shadow operation. */ + status = AwsIotShadowInternal_ProcessOperation( mqttConnection, + pUpdateInfo->pThingName, + pUpdateInfo->thingNameLength, + pOperation, + pUpdateInfo ); + + /* If the Shadow operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateRef != NULL ) ) + { + *pUpdateRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_TimedUpdate( AwsIotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + uint32_t flags, + uint64_t timeoutMs ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowReference_t updateRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; + + /* Call the asynchronous Shadow update function. */ + status = AwsIotShadow_Update( mqttConnection, + pUpdateInfo, + flags, + NULL, + &updateRef ); + + /* Wait for the Shadow update operation to complete. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + status = AwsIotShadow_Wait( updateRef, timeoutMs, NULL, NULL ); + } + + /* Ensure that a status was set. */ + AwsIotShadow_Assert( status != AWS_IOT_SHADOW_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, + uint64_t timeoutMs, + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + _shadowOperation_t * pOperation = ( _shadowOperation_t * ) reference; + + /* Check that reference is set. */ + if( pOperation == NULL ) + { + AwsIotLogError( "Reference cannot be NULL." ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check that reference is waitable. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + { + AwsIotLogError( "Reference is not a waitable Shadow operation." ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + + /* Check that output parameters are set for a Shadow GET. */ + if( pOperation->type == _SHADOW_GET ) + { + if( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) + { + AwsIotLogError( "Output buffer and size pointer must be set for Shadow GET." ); + + return AWS_IOT_SHADOW_BAD_PARAMETER; + } + } + + /* Wait for a response to the Shadow operation. */ + if( AwsIotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + timeoutMs ) == true ) + { + status = pOperation->status; + } + else + { + status = AWS_IOT_SHADOW_TIMEOUT; + } + + /* Remove the completed operation from the pending operation list. */ + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotList_Remove( &_AwsIotShadowPendingOperations, + &( pOperation->link ), + _SHADOW_OPERATION_LINK_OFFSET ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ + AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotShadowInternal_DecrementReferences( pOperation, + pOperation->pSubscription->pTopicBuffer, + NULL ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + + /* Set the output parameters for Shadow GET. */ + if( pOperation->type == _SHADOW_GET ) + { + *pShadowDocument = pOperation->get.pDocument; + *pShadowDocumentLength = pOperation->get.documentLength; + } + + /* Destroy the Shadow operation. */ + AwsIotShadowInternal_DestroyOperation( pOperation ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pDeltaCallback ) +{ + /* Flags are currently not used by this function. */ + ( void ) flags; + + return _setCallbackCommon( mqttConnection, + _DELTA_CALLBACK, + pThingName, + thingNameLength, + pDeltaCallback ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pUpdatedCallback ) +{ + /* Flags are currently not used by this function. */ + ( void ) flags; + + return _setCallbackCommon( mqttConnection, + _UPDATED_CALLBACK, + pThingName, + thingNameLength, + pUpdatedCallback ); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c new file mode 100644 index 0000000000..b49e4ea722 --- /dev/null +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -0,0 +1,870 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow_operation.c + * @brief Implements functions that process Shadow operations. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/* JSON utils include. */ +#include "aws_iot_json_utils.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_shadowOperation_match. + */ +typedef struct _operationMatchParams +{ + _shadowOperationType_t type; /**< @brief DELETE, GET, or UPDATE. */ + const char * pThingName; /**< @brief Thing Name of Shadow operation. */ + size_t thingNameLength; /**< @brief Length of #_operationMatchParams_t.pThingName. */ + const char * pDocument; /**< @brief Shadow UPDATE response document. */ + size_t documentLength; /**< @brief Length of #_operationMatchParams_t.pDocument. */ +} _operationMatchParams_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Match a received Shadow response with a Shadow operation awaiting a + * response. + * + * @param[in] pArgument Pointer to an #_operationMatchParams_t. + * @param[in] pData Pointer to a #_shadowOperation_t to check. + * + * @return `true` if `pData` matches the received response; `false` otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility + * with @ref list_function_findfirstmatch. + */ +static inline bool _shadowOperation_match( void * pArgument, + void * pData ); + +/** + * @brief Common function for processing received Shadow responses. + * + * @param[in] type DELETE, GET, or UPDATE. + * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). + */ +static void _commonOperationCallback( _shadowOperationType_t type, + AwsIotMqttCallbackParam_t * const pMessage ); + +/** + * @brief Invoked when a Shadow response is received for Shadow DELETE. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). + */ +static void _deleteCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ); + +/** + * @brief Invoked when a Shadow response is received for a Shadow GET. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). + */ +static void _getCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ); + +/** + * @brief Process an incoming Shadow document received when a Shadow GET is + * accepted. + * + * @param[in] pOperation The GET operation associated with the incoming Shadow + * document. + * @param[in] pPublishInfo The received Shadow document (as an MQTT PUBLISH + * message). + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. Memory allocation + * only happens for a waitable `pOperation`. + */ +static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOperation, + const AwsIotMqttPublishInfo_t * const pPublishInfo ); + +/** + * @brief Invoked when a Shadow response is received for a Shadow UPDATE. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). + */ +static void _updateCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ); + +/*-----------------------------------------------------------*/ + +#if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + +/** + * @brief Printable names for each of the Shadow operations. + */ + const char * const _pAwsIotShadowOperationNames[] = + { + "DELETE", + "GET", + "UPDATE", + "SET DELTA", + "SET UPDATED" + }; +#endif /* if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE */ + +/** + * @brief List of active Shadow operations awaiting a response from the Shadow + * service. + */ +AwsIotList_t _AwsIotShadowPendingOperations = { 0 }; + +/*-----------------------------------------------------------*/ + +static inline bool _shadowOperation_match( void * pArgument, + void * pData ) +{ + _operationMatchParams_t * const pParam = ( _operationMatchParams_t * ) pArgument; + _shadowOperation_t * const pOperation = ( _shadowOperation_t * ) pData; + _shadowSubscription_t * const pSubscription = pOperation->pSubscription; + const char * pClientToken = NULL; + size_t clientTokenLength = 0; + + /* Check for matching Thing Name and operation type. */ + bool match = ( pOperation->type == pParam->type ) && + ( pParam->thingNameLength == pSubscription->thingNameLength ) && + ( strncmp( pParam->pThingName, + pSubscription->pThingName, + pParam->thingNameLength ) == 0 ); + + /* For a Shadow UPDATE operation, compare the client tokens. */ + if( ( match == true ) && ( pOperation->type == _SHADOW_UPDATE ) ) + { + /* Check document pointers. */ + AwsIotShadow_Assert( pParam->pDocument != NULL ); + AwsIotShadow_Assert( pParam->documentLength > 0 ); + AwsIotShadow_Assert( pOperation->update.pClientToken != NULL ); + AwsIotShadow_Assert( pOperation->update.clientTokenLength > 0 ); + + AwsIotLogDebug( "Verifying client tokens for Shadow UPDATE." ); + + /* Check for the client token in the UPDATE response document. */ + match = AwsIotJsonUtils_FindJsonValue( pParam->pDocument, + pParam->documentLength, + _CLIENT_TOKEN_KEY, + _CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ); + + /* If the UPDATE response document has a client token, check that it + * matches. */ + if( match == true ) + { + match = ( clientTokenLength == pOperation->update.clientTokenLength ) && + ( strncmp( pClientToken, + pOperation->update.pClientToken, + clientTokenLength ) == 0 ); + } + else + { + AwsIotLogWarn( "Received a Shadow UPDATE response with no client token. " + "This is possibly a response to a bad JSON document:\n%.*s", + pParam->documentLength, + pParam->pDocument ); + } + } + + return match; +} + +/*-----------------------------------------------------------*/ + +static void _commonOperationCallback( _shadowOperationType_t type, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + _shadowOperation_t * pOperation = NULL; + _shadowOperationStatus_t status = _UNKNOWN_STATUS; + _operationMatchParams_t param = { 0 }; + uint32_t flags = 0; + + /* Set operation type to search. */ + param.type = type; + + /* Set the response document for a Shadow UPDATE. */ + if( type == _SHADOW_UPDATE ) + { + param.pDocument = pMessage->message.info.pPayload; + param.documentLength = pMessage->message.info.payloadLength; + } + + /* Parse the Thing Name from the MQTT topic name. */ + if( AwsIotShadowInternal_ParseThingName( pMessage->message.info.pTopicName, + pMessage->message.info.topicNameLength, + &( param.pThingName ), + &( param.thingNameLength ) ) != AWS_IOT_SHADOW_SUCCESS ) + { + return; + } + + /* Lock the pending operations list for exclusive access. */ + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); + + /* Search for a matching pending operation. */ + pOperation = AwsIotList_FindFirstMatch( _AwsIotShadowPendingOperations.pHead, + _SHADOW_OPERATION_LINK_OFFSET, + ¶m, + _shadowOperation_match ); + + /* Find and remove the first Shadow operation of the given type. */ + if( pOperation == NULL ) + { + /* Operation is not pending. It may have already been processed. Return + * without doing anything */ + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + + AwsIotLogWarn( "Shadow %s callback received an unknown operation.", + _pAwsIotShadowOperationNames[ type ] ); + + return; + } + else + { + /* Remove a non-waitable operation from the pending operation list. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + { + AwsIotList_Remove( &_AwsIotShadowPendingOperations, + &( pOperation->link ), + _SHADOW_OPERATION_LINK_OFFSET ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + } + } + + /* Check that the Shadow operation type and status. */ + AwsIotShadow_Assert( pOperation->type == type ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + + AwsIotLogDebug( "Received Shadow response on topic %.*s", + pMessage->message.info.topicNameLength, + pMessage->message.info.pTopicName ); + + /* Parse the status from the topic name. */ + status = AwsIotShadowInternal_ParseShadowStatus( pMessage->message.info.pTopicName, + pMessage->message.info.topicNameLength ); + + switch( status ) + { + case _SHADOW_ACCEPTED: + AwsIotLogInfo( "Shadow %s of %.*s was ACCEPTED.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + /* Process the retrieved document for a Shadow GET. Otherwise, set + * status to success. */ + if( type == _SHADOW_GET ) + { + pOperation->status = _processAcceptedGet( pOperation, + &( pMessage->message.info ) ); + } + else + { + pOperation->status = AWS_IOT_SHADOW_SUCCESS; + } + + break; + + case _SHADOW_REJECTED: + AwsIotLogWarn( "Shadow %s of %.*s was REJECTED.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + pOperation->status = AwsIotShadowInternal_ParseErrorDocument( pMessage->message.info.pPayload, + pMessage->message.info.payloadLength ); + break; + + default: + AwsIotLogWarn( "Unknown status for %s of %.*s Shadow. Ignoring message.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + pOperation->status = AWS_IOT_SHADOW_BAD_RESPONSE; + break; + } + + /* Copy the flags from the Shadow operation. The notify function may delete the operation. */ + flags = pOperation->flags; + + /* Notify of operation completion. */ + AwsIotShadowInternal_Notify( pOperation ); + + /* For waitable operations, unlock the pending operation list mutex to signal + * this function's completion. */ + if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + } +} + +/*-----------------------------------------------------------*/ + +static void _deleteCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; + + _commonOperationCallback( _SHADOW_DELETE, pMessage ); +} + +/*-----------------------------------------------------------*/ + +static void _getCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; + + _commonOperationCallback( _SHADOW_GET, pMessage ); +} + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOperation, + const AwsIotMqttPublishInfo_t * const pPublishInfo ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + + /* A non-waitable operation can re-use the pointers from the publish info, + * since those are guaranteed to be in-scope throughout the user callback. + * But a waitable operation must copy the data from the publish info because + * AwsIotShadow_Wait may be called after the MQTT library frees the publish + * info. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + { + pOperation->get.pDocument = pPublishInfo->pPayload; + pOperation->get.documentLength = pPublishInfo->payloadLength; + } + else + { + AwsIotLogDebug( "Allocating new buffer for waitable Shadow GET." ); + + /* Parameter validation should not have allowed a NULL malloc function. */ + AwsIotShadow_Assert( pOperation->get.mallocDocument != NULL ); + + /* Allocate a buffer for the retrieved document. */ + pOperation->get.pDocument = pOperation->get.mallocDocument( pPublishInfo->payloadLength ); + + if( pOperation->get.pDocument == NULL ) + { + AwsIotLogError( "Failed to allocate buffer for retrieved Shadow document." ); + + status = AWS_IOT_SHADOW_NO_MEMORY; + } + else + { + /* Copy the retrieved document. */ + ( void ) memcpy( ( void * ) pOperation->get.pDocument, + pPublishInfo->pPayload, + pPublishInfo->payloadLength ); + pOperation->get.documentLength = pPublishInfo->payloadLength; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void _updateCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; + + _commonOperationCallback( _SHADOW_UPDATE, pMessage ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** const pNewOperation, + _shadowOperationType_t type, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo ) +{ + _shadowOperation_t * pOperation = NULL; + + AwsIotLogDebug( "Creating operation record for Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + + /* Allocate memory for a new Shadow operation. */ + pOperation = AwsIotShadow_MallocOperation( sizeof( _shadowOperation_t ) ); + + if( pOperation == NULL ) + { + AwsIotLogError( "Failed to allocate memory for Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _shadowOperation_t ) ); + + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + if( AwsIotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + { + AwsIotLogError( "Failed to create semaphore for waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + + AwsIotShadow_FreeOperation( pOperation ); + + return AWS_IOT_SHADOW_NO_MEMORY; + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->notify.callback = *pCallbackInfo; + } + } + + /* Set the remaining common members of the Shadow operation. */ + pOperation->type = type; + pOperation->flags = flags; + pOperation->status = AWS_IOT_SHADOW_STATUS_PENDING; + + /* Set the output parameter. */ + *pNewOperation = pOperation; + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotShadowInternal_DestroyOperation( void * pData ) +{ + _shadowOperation_t * pOperation = ( _shadowOperation_t * ) pData; + + /* The Shadow operation pointer must not be NULL. */ + AwsIotShadow_Assert( pOperation != NULL ); + + AwsIotLogDebug( "Destroying Shadow operation %s.", + _pAwsIotShadowOperationNames[ pOperation->type ] ); + + /* Check if a wait semaphore was created for this operation. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + /* Destroy the wait semaphore */ + AwsIotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + } + + /* If this is a Shadow update, free any allocated client token. */ + if( ( pOperation->type == _SHADOW_UPDATE ) && + ( pOperation->update.pClientToken != NULL ) ) + { + AwsIotShadow_Assert( pOperation->update.clientTokenLength > 0 ); + + AwsIotShadow_FreeString( ( void * ) ( pOperation->update.pClientToken ) ); + } + + /* Free the memory used to hold operation data. */ + AwsIotShadow_FreeOperation( pOperation ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadowInternal_GenerateShadowTopic( _shadowOperationType_t type, + const char * const pThingName, + size_t thingNameLength, + char ** const pTopicBuffer, + uint16_t * const pOperationTopicLength ) +{ + uint16_t bufferLength = 0; + uint16_t operationTopicLength = 0; + char * pBuffer = NULL; + + /* Lookup table for Shadow operation strings. */ + const char * const pOperationString[ _SHADOW_OPERATION_COUNT ] = + { + _SHADOW_DELETE_OPERATION_STRING, /* Shadow delete operation. */ + _SHADOW_GET_OPERATION_STRING, /* Shadow get operation. */ + _SHADOW_UPDATE_OPERATION_STRING /* Shadow update operation. */ + }; + + /* Lookup table for Shadow operation string lengths. */ + const uint16_t pOperationStringLength[ _SHADOW_OPERATION_COUNT ] = + { + _SHADOW_DELETE_OPERATION_STRING_LENGTH, /* Shadow delete operation. */ + _SHADOW_GET_OPERATION_STRING_LENGTH, /* Shadow get operation. */ + _SHADOW_UPDATE_OPERATION_STRING_LENGTH /* Shadow update operation. */ + }; + + /* Only Shadow delete, get, and update operation types should be passed to this + * function. */ + AwsIotShadow_Assert( ( type == _SHADOW_DELETE ) || + ( type == _SHADOW_GET ) || + ( type == _SHADOW_UPDATE ) ); + + /* Calculate the required topic buffer length. */ + bufferLength = ( uint16_t ) ( _SHADOW_TOPIC_PREFIX_LENGTH + + thingNameLength + + pOperationStringLength[ type ] + + _SHADOW_LONGEST_SUFFIX_LENGTH ); + + /* Allocate memory for the topic buffer if no topic buffer is given. */ + if( *pTopicBuffer == NULL ) + { + pBuffer = AwsIotShadow_MallocString( ( size_t ) bufferLength ); + + if( pBuffer == NULL ) + { + return AWS_IOT_SHADOW_NO_MEMORY; + } + } + /* Otherwise, use the given topic buffer. */ + else + { + pBuffer = *pTopicBuffer; + } + + /* Copy the Shadow topic prefix into the topic buffer. */ + ( void ) memcpy( pBuffer, _SHADOW_TOPIC_PREFIX, _SHADOW_TOPIC_PREFIX_LENGTH ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + _SHADOW_TOPIC_PREFIX_LENGTH ); + + /* Copy the Thing Name into the topic buffer. */ + ( void ) memcpy( pBuffer + operationTopicLength, pThingName, thingNameLength ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + thingNameLength ); + + /* Copy the Shadow operation string into the topic buffer. */ + ( void ) memcpy( pBuffer + operationTopicLength, + pOperationString[ type ], + pOperationStringLength[ type ] ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + pOperationStringLength[ type ] ); + + /* Ensure that the topic length is in the topic buffer. */ + AwsIotShadow_Assert( operationTopicLength < bufferLength ); + + /* Set the output parameters. */ + if( *pTopicBuffer == NULL ) + { + *pTopicBuffer = pBuffer; + } + + *pOperationTopicLength = operationTopicLength; + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + _shadowOperation_t * const pOperation, + const AwsIotShadowDocumentInfo_t * const pDocumentInfo ) +{ + _shadowSubscription_t * pSubscription = NULL; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotMqttError_t publishStatus = AWS_IOT_MQTT_STATUS_PENDING; + char * pTopicBuffer = NULL; + uint16_t operationTopicLength = 0; + bool freeTopicBuffer = true; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Lookup table for Shadow operation callbacks. */ + const _mqttCallbackFunction_t shadowCallbacks[ _SHADOW_OPERATION_COUNT ] = + { + _deleteCallback, + _getCallback, + _updateCallback + }; + + AwsIotLogDebug( "Processing Shadow operation %s for Thing %.*s.", + _pAwsIotShadowOperationNames[ pOperation->type ], + thingNameLength, + pThingName ); + + /* Set the operation's MQTT connection. */ + pOperation->mqttConnection = mqttConnection; + + /* Generate the operation topic buffer. */ + if( AwsIotShadowInternal_GenerateShadowTopic( pOperation->type, + pThingName, + thingNameLength, + &pTopicBuffer, + &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) + { + AwsIotLogError( "No memory for Shadow operation topic buffer." ); + + AwsIotShadowInternal_DestroyOperation( pOperation ); + + return AWS_IOT_SHADOW_NO_MEMORY; + } + + /* Lock the subscription list mutex for exclusive access. */ + AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + + /* Check for an existing subscription. This function will attempt to allocate + * a new subscription if not found. */ + pSubscription = AwsIotShadowInternal_FindSubscription( pThingName, + thingNameLength ); + + if( pSubscription == NULL ) + { + /* No existing subscription was found, and no new subscription could be + * allocated. */ + status = AWS_IOT_SHADOW_NO_MEMORY; + } + + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* Ensure that the subscription Thing Name matches. */ + AwsIotShadow_Assert( pSubscription != NULL ); + AwsIotShadow_Assert( pSubscription->thingNameLength == thingNameLength ); + AwsIotShadow_Assert( strncmp( pSubscription->pThingName, + pThingName, + thingNameLength ) == 0 ); + + /* Set the subscription object for the Shadow operation. */ + pOperation->pSubscription = pSubscription; + + /* Assign the topic buffer to the subscription to use for unsubscribing if + * the subscription has no topic buffer. */ + if( pSubscription->pTopicBuffer == NULL ) + { + pSubscription->pTopicBuffer = pTopicBuffer; + + /* This function should not free the topic buffer. */ + freeTopicBuffer = false; + } + + /* Increment the reference count for this Shadow operation's + * subscriptions. */ + status = AwsIotShadowInternal_IncrementReferences( pOperation, + pTopicBuffer, + operationTopicLength, + shadowCallbacks[ pOperation->type ] ); + + if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* Failed to add subscriptions for a Shadow operation. The reference + * count was not incremented. Check if this subscription should be + * deleted. */ + AwsIotShadowInternal_RemoveSubscription( pSubscription, NULL ); + } + } + + /* Unlock the Shadow subscription list mutex. */ + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + + /* Check that all memory allocation and subscriptions succeeded. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* Set the operation topic name. */ + publishInfo.pTopicName = pTopicBuffer; + publishInfo.topicNameLength = operationTopicLength; + + AwsIotLogDebug( "Shadow %s message will be published to topic %.*s", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.topicNameLength, + publishInfo.pTopicName ); + + /* Set the document info if this operation is not a Shadow DELETE. */ + if( pOperation->type != _SHADOW_DELETE ) + { + publishInfo.QoS = pDocumentInfo->QoS; + publishInfo.retryLimit = pDocumentInfo->retryLimit; + publishInfo.retryMs = pDocumentInfo->retryMs; + + AwsIotLogDebug( "Shadow %s message will be published at QoS %d with " + "retryLimit %d and retryMs %llu.", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.QoS, + publishInfo.retryLimit, + publishInfo.retryMs ); + } + + /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ + if( pOperation->type == _SHADOW_UPDATE ) + { + publishInfo.pPayload = pDocumentInfo->update.pUpdateDocument; + publishInfo.payloadLength = pDocumentInfo->update.updateDocumentLength; + } + + /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, + * per the Shadow spec. */ + else + { + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; + } + + /* Add Shadow operation to the pending operations list. */ + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotList_InsertHead( &_AwsIotShadowPendingOperations, + &( pOperation->link ), + _SHADOW_OPERATION_LINK_OFFSET ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + + /* Publish to the Shadow topic name. */ + publishStatus = AwsIotMqtt_TimedPublish( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotShadowMqttTimeoutMs ); + + /* Check for errors from the MQTT publish. */ + if( publishStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", + _pAwsIotShadowOperationNames[ pOperation->type ], + thingNameLength, + pThingName, + AwsIotMqtt_strerror( publishStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + if( publishStatus == AWS_IOT_MQTT_NO_MEMORY ) + { + status = AWS_IOT_SHADOW_NO_MEMORY; + } + else + { + status = AWS_IOT_SHADOW_MQTT_ERROR; + } + + /* If the "keep subscriptions" flag is not set, decrement the reference + * count. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + { + AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotShadowInternal_DecrementReferences( pOperation, + pTopicBuffer, + NULL ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + } + + /* Remove Shadow operation from the pending operations list. */ + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotList_Remove( &_AwsIotShadowPendingOperations, + &( pOperation->link ), + _SHADOW_OPERATION_LINK_OFFSET ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + } + else + { + AwsIotLogDebug( "Shadow %s PUBLISH message successfully sent.", + _pAwsIotShadowOperationNames[ pOperation->type ] ); + } + } + + /* Free the topic buffer used by this function if it was not assigned to a + * subscription. */ + if( ( freeTopicBuffer == true ) && ( pTopicBuffer != NULL ) ) + { + AwsIotShadow_FreeString( pTopicBuffer ); + } + + /* Destroy the Shadow operation on failure. */ + if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + { + AwsIotShadowInternal_DestroyOperation( pOperation ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ) +{ + AwsIotShadowCallbackParam_t callbackParam = { 0 }; + _shadowSubscription_t * pSubscription = pOperation->pSubscription, + * pRemovedSubscription; + + /* If the operation is waiting, post to its wait semaphore and return. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + + return; + } + + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ + AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotShadowInternal_DecrementReferences( pOperation, + pSubscription->pTopicBuffer, + &pRemovedSubscription ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + + /* Set the subscription pointer used for the user callback based on whether + * a subscription was removed from the list. */ + if( pRemovedSubscription != NULL ) + { + pSubscription = pRemovedSubscription; + } + + AwsIotShadow_Assert( pSubscription != NULL ); + + /* Invoke the user callback if provided. */ + if( pOperation->notify.callback.function != NULL ) + { + /* Set the common members of the callback parameter. */ + callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; + callbackParam.operation.result = pOperation->status; + callbackParam.operation.reference = pOperation; + callbackParam.pThingName = pSubscription->pThingName; + callbackParam.thingNameLength = pSubscription->thingNameLength; + + /* Set the members of the callback parameter for a received document. */ + if( pOperation->type == _SHADOW_GET ) + { + callbackParam.operation.get.pDocument = pOperation->get.pDocument; + callbackParam.operation.get.documentLength = pOperation->get.documentLength; + } + + pOperation->notify.callback.function( pOperation->notify.callback.param1, + &callbackParam ); + } + + /* Destroy a removed subscription. */ + if( pRemovedSubscription != NULL ) + { + AwsIotShadowInternal_DestroySubscription( pRemovedSubscription ); + } + + AwsIotShadowInternal_DestroyOperation( pOperation ); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c new file mode 100644 index 0000000000..4be9e83880 --- /dev/null +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow_parser.c + * @brief Implements topic name and JSON parsing functions of the Shadow library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/* JSON utilities include. */ +#include "aws_iot_json_utils.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief The JSON key for the error code in a Shadow error document. + */ +#define _ERROR_DOCUMENT_CODE_KEY "code" + +/** + * @brief The length of #_ERROR_DOCUMENT_CODE_KEY. + */ +#define _ERROR_DOCUMENT_CODE_KEY_LENGTH ( sizeof( _ERROR_DOCUMENT_CODE_KEY ) - 1 ) + +/** + * @brief The JSON key for the error message in a Shadow error document. + */ +#define _ERROR_DOCUMENT_MESSAGE_KEY "message" + +/** + * @brief The length of #_ERROR_DOCUMENT_MESSAGE_KEY. + */ +#define _ERROR_DOCUMENT_MESSAGE_KEY_LENGTH ( sizeof( _ERROR_DOCUMENT_MESSAGE_KEY ) - 1 ) + +/** + * @brief The minimum possible length of a Shadow topic name, per the Shadow + * spec. + */ +#define _MINIMUM_SHADOW_TOPIC_NAME_LENGTH \ + ( _SHADOW_TOPIC_PREFIX_LENGTH + \ + ( uint16_t ) sizeof( _SHADOW_GET_OPERATION_STRING ) + \ + ( _SHADOW_ACCEPTED_SUFFIX_LENGTH < _SHADOW_REJECTED_SUFFIX_LENGTH ? \ + _SHADOW_ACCEPTED_SUFFIX_LENGTH : \ + _SHADOW_REJECTED_SUFFIX_LENGTH ) ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Converts a `unsigned long` to an `AwsIotShadowError_t`. + * + * @param[in] code A value between 400 and 500 to convert. + * + * @return A corresponding #AwsIotShadowError_t; #AWS_IOT_SHADOW_BAD_RESPONSE + * if `code` is unknown. + */ +static AwsIotShadowError_t _codeToShadowStatus( unsigned long code ); + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _codeToShadowStatus( unsigned long code ) +{ + /* Convert the Shadow response code to an AwsIotShadowError_t. */ + switch( code ) + { + case 400UL: + + return AWS_IOT_SHADOW_BAD_REQUEST; + + case 401UL: + + return AWS_IOT_SHADOW_UNAUTHORIZED; + + case 403UL: + + return AWS_IOT_SHADOW_FORBIDDEN; + + case 404UL: + + return AWS_IOT_SHADOW_NOT_FOUND; + + case 409UL: + + return AWS_IOT_SHADOW_CONFLICT; + + case 413UL: + + return AWS_IOT_SHADOW_TOO_LARGE; + + case 415UL: + + return AWS_IOT_SHADOW_UNSUPPORTED; + + case 429UL: + + return AWS_IOT_SHADOW_TOO_MANY_REQUESTS; + + case 500UL: + + return AWS_IOT_SHADOW_SERVER_ERROR; + + default: + + return AWS_IOT_SHADOW_BAD_RESPONSE; + } +} + +/*-----------------------------------------------------------*/ + +_shadowOperationStatus_t AwsIotShadowInternal_ParseShadowStatus( const char * const pTopicName, + size_t topicNameLength ) +{ + const char * pSuffixStart = NULL; + + /* Check that the Shadow status topic name is at least as long as the + * "accepted" suffix. */ + if( topicNameLength > _SHADOW_ACCEPTED_SUFFIX_LENGTH ) + { + /* Calculate where the "accepted" suffix should start. */ + pSuffixStart = pTopicName + topicNameLength - _SHADOW_ACCEPTED_SUFFIX_LENGTH; + + /* pSuffixStart must be in pTopicName. */ + AwsIotShadow_Assert( ( pSuffixStart > pTopicName ) && + ( pSuffixStart < pTopicName + topicNameLength ) ); + + /* Check if the end of the Shadow status topic name is "accepted". */ + if( strncmp( pSuffixStart, + _SHADOW_ACCEPTED_SUFFIX, + _SHADOW_ACCEPTED_SUFFIX_LENGTH ) == 0 ) + { + return _SHADOW_ACCEPTED; + } + } + + /* Check that the Shadow status topic name is at least as long as the + * "rejected" suffix. */ + if( topicNameLength > _SHADOW_REJECTED_SUFFIX_LENGTH ) + { + /* Calculate where the "rejected" suffix should start. */ + pSuffixStart = pTopicName + topicNameLength - _SHADOW_REJECTED_SUFFIX_LENGTH; + + /* pSuffixStart must be in pTopicName. */ + AwsIotShadow_Assert( ( pSuffixStart > pTopicName ) && + ( pSuffixStart < pTopicName + topicNameLength ) ); + + /* Check if the end of the Shadow status topic name is "rejected". */ + if( strncmp( pSuffixStart, + _SHADOW_REJECTED_SUFFIX, + _SHADOW_REJECTED_SUFFIX_LENGTH ) == 0 ) + { + return _SHADOW_REJECTED; + } + } + + /* The topic name matched neither "accepted" nor "rejected". */ + return _UNKNOWN_STATUS; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const pErrorDocument, + size_t errorDocumentLength ) +{ + const char * pCode = NULL, * pMessage = NULL; + size_t codeLength = 0, messageLength = 0; + unsigned long code = 0; + + /* Parse the code from the error document. */ + if( AwsIotJsonUtils_FindJsonValue( pErrorDocument, + errorDocumentLength, + _ERROR_DOCUMENT_CODE_KEY, + _ERROR_DOCUMENT_CODE_KEY_LENGTH, + &pCode, + &codeLength ) == false ) + { + /* Error parsing JSON document, or no "code" key was found. */ + AwsIotLogWarn( "Failed to parse code from error document.\n%.*s", + errorDocumentLength, + pErrorDocument ); + + return AWS_IOT_SHADOW_BAD_RESPONSE; + } + + /* Code must be in error document. */ + AwsIotShadow_Assert( ( pCode > pErrorDocument ) && + ( pCode + codeLength < pErrorDocument + errorDocumentLength ) ); + + /* Convert the code to an unsigned integer value. */ + code = strtoul( pCode, NULL, 10 ); + + /* Parse the error message and print it. An error document must always contain + * a message. */ + if( AwsIotJsonUtils_FindJsonValue( pErrorDocument, + errorDocumentLength, + _ERROR_DOCUMENT_MESSAGE_KEY, + _ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, + &pMessage, + &messageLength ) == true ) + { + AwsIotLogWarn( "Code %lu: %.*s.", + code, + messageLength, + pMessage ); + } + else + { + AwsIotLogWarn( "Code %lu; failed to parse message from error document.\n%.*s", + code, + errorDocumentLength, + pErrorDocument ); + + /* An error document must contain a message; if it does not, then it is invalid. */ + return AWS_IOT_SHADOW_BAD_RESPONSE; + } + + return _codeToShadowStatus( code ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadowInternal_ParseThingName( const char * const pTopicName, + uint16_t topicNameLength, + const char ** const pThingName, + size_t * const pThingNameLength ) +{ + const char * pThingNameStart = NULL; + size_t thingNameLength = 0; + + /* Check that the topic name length exceeds the minimum possible length. */ + if( topicNameLength < _MINIMUM_SHADOW_TOPIC_NAME_LENGTH ) + { + return AWS_IOT_SHADOW_BAD_RESPONSE; + } + + /* All Shadow topic names must start with the same prefix. */ + if( strncmp( _SHADOW_TOPIC_PREFIX, + pTopicName, + _SHADOW_TOPIC_PREFIX_LENGTH ) != 0 ) + { + return AWS_IOT_SHADOW_BAD_RESPONSE; + } + + /* The Thing Name starts immediately after the topic prefix. */ + pThingNameStart = pTopicName + _SHADOW_TOPIC_PREFIX_LENGTH; + + /* Calculate the length of the Thing Name. */ + while( ( thingNameLength + _SHADOW_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) && + ( pThingNameStart[ thingNameLength ] != '/' ) ) + { + thingNameLength++; + } + + /* The end of the topic name was reached without finding a '/'. The topic + * name is invalid. */ + if( thingNameLength + _SHADOW_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) + { + return AWS_IOT_SHADOW_BAD_RESPONSE; + } + + /* Set the output parameters. */ + *pThingName = pThingNameStart; + *pThingNameLength = thingNameLength; + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c new file mode 100644 index 0000000000..5599113d78 --- /dev/null +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow_subscription.c + * @brief Implements functions for interacting with the Shadow library's + * subscription list. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_shadowSubscription_compare. + */ +typedef struct _thingName +{ + const char * pThingName; /**< @brief Thing Name to compare. */ + size_t thingNameLength; /**< @brief Length of `pThingName`. */ +} _thingName_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Compare two #_shadowSubscription_t by Thing Name. + * + * @param[in] pArgument Pointer to a #_thingName_t. + * @param[in] pData Pointer to a #_shadowSubscription_t containing the Thing + * Name to check. + * + * @return `true` if the Thing Names match; `false` otherwise. + * + * @note The arguments of this function are of type `void*` for compatibility + * with @ref list_function_findfirstmatch. + */ +static inline bool _shadowSubscription_compare( void * pArgument, + void * pData ); + +/** + * @brief Modify Shadow subscriptions, either by unsubscribing or subscribing. + * + * @param[in] mqttConnection The MQTT connection to use. + * @param[in] pTopicFilter The topic filter to modify. + * @param[in] topicFilterLength The length of `pTopicFilter`. + * @param[in] callback The callback function to execute for an incoming message. + * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or @ref + * mqtt_function_timedunsubscribe. + * + * @return #AWS_IOT_SHADOW_STATUS_PENDING on success; otherwise + * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. + */ +static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t mqttConnection, + const char * const pTopicFilter, + uint16_t topicFilterLength, + _mqttCallbackFunction_t callback, + _mqttOperationFunction_t mqttOperation ); + +/*-----------------------------------------------------------*/ + +/** + * @brief List of active Shadow subscriptions objects. + */ +AwsIotList_t _AwsIotShadowSubscriptions = { 0 }; + +/*-----------------------------------------------------------*/ + +static inline bool _shadowSubscription_compare( void * pArgument, + void * pData ) +{ + const _thingName_t * const pThingName = ( _thingName_t * ) pArgument; + const _shadowSubscription_t * const pSubscription = ( _shadowSubscription_t * ) pData; + + if( pThingName->thingNameLength == pSubscription->thingNameLength ) + { + /* Check for matching Thing Names. */ + return( strncmp( pThingName->pThingName, + pSubscription->pThingName, + pThingName->thingNameLength ) == 0 ); + } + + return false; +} + +/*-----------------------------------------------------------*/ + +static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t mqttConnection, + const char * const pTopicFilter, + uint16_t topicFilterLength, + _mqttCallbackFunction_t callback, + _mqttOperationFunction_t mqttOperation ) +{ + AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* The MQTT operation function pointer must be either Subscribe or Unsubscribe. */ + AwsIotShadow_Assert( ( mqttOperation == AwsIotMqtt_TimedSubscribe ) || + ( mqttOperation == AwsIotMqtt_TimedUnsubscribe ) ); + + /* Per the AWS IoT documentation, Shadow topic subscriptions are QoS 1. */ + subscription.QoS = 1; + + AwsIotLogDebug( "%s Shadow subscription for %.*s", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", + topicFilterLength, + pTopicFilter ); + + /* Set the members of the subscription parameter. */ + subscription.pTopicFilter = pTopicFilter; + subscription.topicFilterLength = topicFilterLength; + subscription.callback.param1 = NULL; + subscription.callback.function = callback; + + /* Call the MQTT operation function. */ + mqttStatus = mqttOperation( mqttConnection, + &subscription, + 1, + 0, + _AwsIotShadowMqttTimeoutMs ); + + /* Check the result of the MQTT operation. */ + if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to %s %.*s, error %s.", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + topicFilterLength, + pTopicFilter, + AwsIotMqtt_strerror( mqttStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + if( mqttStatus == AWS_IOT_MQTT_NO_MEMORY ) + { + return AWS_IOT_SHADOW_NO_MEMORY; + } + + return AWS_IOT_SHADOW_MQTT_ERROR; + } + else + { + AwsIotLogDebug( "Successfully %s %.*s", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + topicFilterLength, + pTopicFilter ); + } + + return AWS_IOT_SHADOW_STATUS_PENDING; +} + +/*-----------------------------------------------------------*/ + +_shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * const pThingName, + size_t thingNameLength ) +{ + _shadowSubscription_t * pSubscription = NULL; + _thingName_t thingName = + { + .pThingName = pThingName, + .thingNameLength = thingNameLength + }; + + /* Search the list for an existing subscription for Thing Name. */ + pSubscription = AwsIotList_FindFirstMatch( _AwsIotShadowSubscriptions.pHead, + _SHADOW_SUBSCRIPTION_LINK_OFFSET, + &thingName, + _shadowSubscription_compare ); + + /* Check if a subscription was found. */ + if( pSubscription == NULL ) + { + /* No subscription found. Allocate a new subscription. */ + pSubscription = AwsIotShadow_MallocSubscription( sizeof( _shadowSubscription_t ) + thingNameLength ); + + if( pSubscription != NULL ) + { + /* Clear the new subscription. */ + ( void ) memset( pSubscription, 0x00, sizeof( _shadowSubscription_t ) + thingNameLength ); + + /* Set the Thing Name length and copy the Thing Name into the new subscription. */ + pSubscription->thingNameLength = thingNameLength; + ( void ) strncpy( pSubscription->pThingName, pThingName, thingNameLength ); + + /* Add the new subscription to the subscription list. */ + AwsIotList_InsertHead( &_AwsIotShadowSubscriptions, + &( pSubscription->link ), + _SHADOW_SUBSCRIPTION_LINK_OFFSET ); + + AwsIotLogDebug( "Created new Shadow subscriptions object for %.*s.", + thingNameLength, + pThingName ); + } + else + { + AwsIotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.", + thingNameLength, + pThingName ); + } + } + else + { + AwsIotLogDebug( "Found existing Shadow subscriptions object for %.*s.", + thingNameLength, + pThingName ); + } + + return pSubscription; +} + +/*-----------------------------------------------------------*/ + +void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSubscription, + _shadowSubscription_t ** const pRemovedSubscription ) +{ + int i = 0; + + AwsIotLogDebug( "Checking if subscription object for %.*s can be removed.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + /* If any Shadow operation's subscription reference count is not 0, then the + * subscription cannot be removed. */ + for( i = 0; i < _SHADOW_OPERATION_COUNT; i++ ) + { + if( pSubscription->references[ i ] > 0 ) + { + AwsIotLogDebug( "Reference count %d for %.*s subscription object. " + "Subscription cannot be removed yet.", + pSubscription->references[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + return; + } + else if( pSubscription->references[ i ] == _PERSISTENT_SUBSCRIPTION ) + { + AwsIotLogDebug( "Subscription object for %.*s has persistent subscriptions. " + "Subscription will not be removed.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + return; + } + } + + /* If any Shadow callbacks are active, then the subscription cannot be removed. */ + for( i = 0; i < _SHADOW_CALLBACK_COUNT; i++ ) + { + if( pSubscription->callbacks[ i ].function != NULL ) + { + AwsIotLogDebug( "Found active Shadow %s callback for %.*s subscription object. " + "Subscription cannot be removed yet.", + _pAwsIotShadowCallbackNames[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + return; + } + } + + /* No Shadow operation subscription references or active Shadow callbacks. + * Remove the subscription object. */ + AwsIotList_Remove( &_AwsIotShadowSubscriptions, + &( pSubscription->link ), + _SHADOW_SUBSCRIPTION_LINK_OFFSET ); + + AwsIotLogDebug( "Removed subscription object for %.*s.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + /* If the caller requested the removed subscription, set the output parameter. + * Otherwise, free the memory used by the subscription. */ + if( pRemovedSubscription != NULL ) + { + *pRemovedSubscription = pSubscription; + } + else + { + AwsIotShadowInternal_DestroySubscription( pSubscription ); + } +} + +/*-----------------------------------------------------------*/ + +void AwsIotShadowInternal_DestroySubscription( void * pData ) +{ + _shadowSubscription_t * pSubscription = ( _shadowSubscription_t * ) pData; + + /* Free the topic buffer. It should not be NULL. */ + AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); + AwsIotShadow_FreeString( pSubscription->pTopicBuffer ); + + /* Free memory used by subscription. */ + AwsIotShadow_FreeSubscription( pSubscription ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + uint16_t operationTopicLength, + _mqttCallbackFunction_t callback ) +{ + uint16_t topicFilterLength = 0; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + const _shadowOperationType_t type = pOperation->type; + _shadowSubscription_t * const pSubscription = pOperation->pSubscription; + + /* Do nothing if this operation has persistent subscriptions. */ + if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) + { + AwsIotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " + "count will not be incremented.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + return AWS_IOT_SHADOW_STATUS_PENDING; + } + + /* When persistent subscriptions are not present, the reference count must + * not be negative. */ + AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); + + /* Check if there are any existing references for this operation. */ + if( pSubscription->references[ type ] == 0 ) + { + /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + _SHADOW_ACCEPTED_SUFFIX, + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + + /* There should not be an active subscription for the accepted topic. */ + AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == false ); + + /* Add a subscription to the Shadow "accepted" topic. */ + status = _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + callback, + AwsIotMqtt_TimedSubscribe ); + + if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + { + return status; + } + + /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + _SHADOW_REJECTED_SUFFIX, + _SHADOW_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_REJECTED_SUFFIX_LENGTH ); + + /* There should not be an active subscription for the rejected topic. */ + AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == false ); + + /* Add a subscription to the Shadow "rejected" topic. */ + status = _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + callback, + AwsIotMqtt_TimedSubscribe ); + + if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* Failed to add subscription to Shadow "rejected" topic. Remove + * subscription for the Shadow "accepted" topic. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + _SHADOW_ACCEPTED_SUFFIX, + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + + ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + callback, + AwsIotMqtt_TimedUnsubscribe ); + + return status; + } + } + + /* Increment the number of subscription references for this operation when + * the keep subscriptions flag is not set. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + { + ( pSubscription->references[ type ] )++; + + AwsIotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName, + pSubscription->references[ type ] ); + } + /* Otherwise, set the persistent subscriptions flag. */ + else + { + pSubscription->references[ type ] = _PERSISTENT_SUBSCRIPTION; + + AwsIotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + _shadowSubscription_t ** const pRemovedSubscription ) +{ + uint16_t topicFilterLength = 0; + const _shadowOperationType_t type = pOperation->type; + _shadowSubscription_t * const pSubscription = pOperation->pSubscription; + uint16_t operationTopicLength = 0; + + /* Do nothing if this Shadow operation has persistent subscriptions. */ + if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) + { + AwsIotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " + "count will not be decremented.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + return; + } + + /* Decrement the number of subscription references for this operation. + * Ensure that it's positive. */ + ( pSubscription->references[ type ] )--; + AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); + + /* Check if the number of references has reached 0. */ + if( pSubscription->references[ type ] == 0 ) + { + AwsIotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowOperationNames[ type ] ); + + /* Subscription must have a topic buffer. */ + AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); + + /* Generate the prefix of the Shadow topic. This function will not + * fail when given a buffer. */ + ( void ) AwsIotShadowInternal_GenerateShadowTopic( ( _shadowOperationType_t ) type, + pSubscription->pThingName, + pSubscription->thingNameLength, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); + + /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + _SHADOW_ACCEPTED_SUFFIX, + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + + /* There should be an active subscription for the accepted topic. */ + AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == true ); + + /* Remove the subscription from the Shadow "accepted" topic. */ + ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL, + AwsIotMqtt_TimedUnsubscribe ); + + /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + _SHADOW_REJECTED_SUFFIX, + _SHADOW_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + + /* There should be an active subscription for the accepted topic. */ + AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == true ); + + /* Remove the subscription from the Shadow "rejected" topic. */ + ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL, + AwsIotMqtt_TimedUnsubscribe ); + } + + /* Check if this subscription should be deleted. */ + AwsIotShadowInternal_RemoveSubscription( pSubscription, + pRemovedSubscription ); +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + uint32_t flags ) +{ + int i = 0; + uint16_t operationTopicLength = 0, topicFilterLength = 0; + AwsIotShadowError_t removeAcceptedStatus = AWS_IOT_SHADOW_STATUS_PENDING, + removeRejectedStatus = AWS_IOT_SHADOW_STATUS_PENDING; + _shadowSubscription_t * pSubscription = NULL; + _thingName_t thingName = + { + .pThingName = pThingName, + .thingNameLength = thingNameLength + }; + + AwsIotLogInfo( "Removing persistent subscriptions for %.*s.", + thingNameLength, + pThingName ); + + AwsIotMutex_Lock( &( _AwsIotShadowSubscriptions.mutex ) ); + + /* Search the list for an existing subscription for Thing Name. */ + pSubscription = AwsIotList_FindFirstMatch( _AwsIotShadowSubscriptions.pHead, + _SHADOW_SUBSCRIPTION_LINK_OFFSET, + &thingName, + _shadowSubscription_compare ); + + /* Unsubscribe from operation subscriptions if found. */ + if( pSubscription != NULL ) + { + AwsIotLogDebug( "Found subscription object for %.*s. Checking for persistent " + "subscriptions to remove.", + thingNameLength, + pThingName ); + + for( i = 0; i < _SHADOW_OPERATION_COUNT; i++ ) + { + if( ( flags & ( 0x1UL << i ) ) != 0 ) + { + AwsIotLogDebug( "Removing %.*s %s persistent subscriptions.", + thingNameLength, + pThingName, + _pAwsIotShadowOperationNames[ i ] ); + + /* Subscription must have a topic buffer. */ + AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); + + if( pSubscription->references[ i ] == _PERSISTENT_SUBSCRIPTION ) + { + /* Generate the prefix of the Shadow topic. This function will not + * fail when given a buffer. */ + ( void ) AwsIotShadowInternal_GenerateShadowTopic( ( _shadowOperationType_t ) i, + pThingName, + thingNameLength, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); + + /* Remove the "accepted" topic. */ + ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, + _SHADOW_ACCEPTED_SUFFIX, + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + + removeAcceptedStatus = _modifyOperationSubscriptions( mqttConnection, + pSubscription->pTopicBuffer, + topicFilterLength, + NULL, + AwsIotMqtt_TimedUnsubscribe ); + + if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + break; + } + + /* Remove the "rejected" topic. */ + ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, + _SHADOW_REJECTED_SUFFIX, + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + + _SHADOW_REJECTED_SUFFIX_LENGTH ); + + removeRejectedStatus = _modifyOperationSubscriptions( mqttConnection, + pSubscription->pTopicBuffer, + topicFilterLength, + NULL, + AwsIotMqtt_TimedUnsubscribe ); + + if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + break; + } + + /* Clear the persistent subscriptions flag. */ + pSubscription->references[ i ] = 0; + } + else + { + AwsIotLogDebug( "%.*s %s does not have persistent subscriptions.", + thingNameLength, + pThingName, + _pAwsIotShadowOperationNames[ i ] ); + } + } + } + } + else + { + AwsIotLogWarn( "No subscription object found for %.*s", + thingNameLength, + pThingName ); + } + + AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptions.mutex ) ); + + /* Check the results of the MQTT unsubscribes. */ + if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + return removeAcceptedStatus; + } + + if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + return removeRejectedStatus; + } + + return AWS_IOT_SHADOW_SUCCESS; +} + +/*-----------------------------------------------------------*/ diff --git a/platform/linux/common/timer.c b/platform/linux/common/timer.c deleted file mode 100644 index d7cc204bd7..0000000000 --- a/platform/linux/common/timer.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file timer.c - * @brief Linux implementation of the timer interface. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include - -#include "timer_platform.h" - -bool has_timer_expired(Timer *timer) { - struct timeval now, res; - gettimeofday(&now, NULL); - timersub(&timer->end_time, &now, &res); - return res.tv_sec < 0 || (res.tv_sec == 0 && res.tv_usec <= 0); -} - -void countdown_ms(Timer *timer, uint32_t timeout) { - struct timeval now; -#ifdef __cplusplus - struct timeval interval = {timeout / 1000, static_cast((timeout % 1000) * 1000)}; -#else - struct timeval interval = {timeout / 1000, (int)((timeout % 1000) * 1000)}; -#endif - gettimeofday(&now, NULL); - timeradd(&now, &interval, &timer->end_time); -} - -uint32_t left_ms(Timer *timer) { - struct timeval now, res; - uint32_t result_ms = 0; - gettimeofday(&now, NULL); - timersub(&timer->end_time, &now, &res); - if(res.tv_sec >= 0) { - result_ms = (uint32_t) (res.tv_sec * 1000 + res.tv_usec / 1000); - } - return result_ms; -} - -void countdown_sec(Timer *timer, uint32_t timeout) { - struct timeval now; - struct timeval interval = {timeout, 0}; - gettimeofday(&now, NULL); - timeradd(&now, &interval, &timer->end_time); -} - -void init_timer(Timer *timer) { - timer->end_time = (struct timeval) {0, 0}; -} - -void delay(unsigned milliseconds) -{ - useconds_t sleepTime = (useconds_t)(milliseconds * 1000); - - usleep(sleepTime); -} - -#ifdef __cplusplus -} -#endif diff --git a/platform/linux/common/timer_platform.h b/platform/linux/common/timer_platform.h deleted file mode 100644 index d381447e05..0000000000 --- a/platform/linux/common/timer_platform.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef SRC_PROTOCOL_MQTT_AWS_IOT_EMBEDDED_CLIENT_WRAPPER_PLATFORM_LINUX_COMMON_TIMER_PLATFORM_H_ -#define SRC_PROTOCOL_MQTT_AWS_IOT_EMBEDDED_CLIENT_WRAPPER_PLATFORM_LINUX_COMMON_TIMER_PLATFORM_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @file timer_platform.h - */ -#include -#include -#include "timer_interface.h" - -/** - * definition of the Timer struct. Platform specific - */ -struct Timer { - struct timeval end_time; -}; - -/** - * @brief Delay (sleep) for the specified number of milliseconds. - * - * @param milliseconds The number of milliseconds to sleep. - */ -void delay(unsigned milliseconds); - -#ifdef __cplusplus -} -#endif - -#endif /* SRC_PROTOCOL_MQTT_AWS_IOT_EMBEDDED_CLIENT_WRAPPER_PLATFORM_LINUX_COMMON_TIMER_PLATFORM_H_ */ diff --git a/platform/linux/mbedtls/network_mbedtls_wrapper.c b/platform/linux/mbedtls/network_mbedtls_wrapper.c deleted file mode 100644 index 7443d174b5..0000000000 --- a/platform/linux/mbedtls/network_mbedtls_wrapper.c +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include - -#include "aws_iot_error.h" -#include "aws_iot_log.h" -#include "network_interface.h" -#include "network_platform.h" - - -/* This is the value used for ssl read timeout */ -#define IOT_SSL_READ_TIMEOUT 10 - -/* This defines the value of the debug buffer that gets allocated. - * The value can be altered based on memory constraints - */ -#ifdef ENABLE_IOT_DEBUG -#define MBEDTLS_DEBUG_BUFFER_SIZE 2048 -#endif - -/* - * This is a function to do further verification if needed on the cert received - */ - -static int _iot_tls_verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) { - char buf[1024]; - ((void) data); - - IOT_DEBUG("\nVerify requested for (Depth %d):\n", depth); - mbedtls_x509_crt_info(buf, sizeof(buf) - 1, "", crt); - IOT_DEBUG("%s", buf); - - if((*flags) == 0) { - IOT_DEBUG(" This certificate has no flags\n"); - } else { - IOT_DEBUG(buf, sizeof(buf), " ! ", *flags); - IOT_DEBUG("%s\n", buf); - } - - return 0; -} - -void _iot_tls_set_connect_params(Network *pNetwork, char *pRootCALocation, char *pDeviceCertLocation, - char *pDevicePrivateKeyLocation, char *pDestinationURL, - uint16_t destinationPort, uint32_t timeout_ms, bool ServerVerificationFlag) { - pNetwork->tlsConnectParams.DestinationPort = destinationPort; - pNetwork->tlsConnectParams.pDestinationURL = pDestinationURL; - pNetwork->tlsConnectParams.pDeviceCertLocation = pDeviceCertLocation; - pNetwork->tlsConnectParams.pDevicePrivateKeyLocation = pDevicePrivateKeyLocation; - pNetwork->tlsConnectParams.pRootCALocation = pRootCALocation; - pNetwork->tlsConnectParams.timeout_ms = timeout_ms; - pNetwork->tlsConnectParams.ServerVerificationFlag = ServerVerificationFlag; -} - -IoT_Error_t iot_tls_init(Network *pNetwork, char *pRootCALocation, char *pDeviceCertLocation, - char *pDevicePrivateKeyLocation, char *pDestinationURL, - uint16_t destinationPort, uint32_t timeout_ms, bool ServerVerificationFlag) { - _iot_tls_set_connect_params(pNetwork, pRootCALocation, pDeviceCertLocation, pDevicePrivateKeyLocation, - pDestinationURL, destinationPort, timeout_ms, ServerVerificationFlag); - - pNetwork->connect = iot_tls_connect; - pNetwork->read = iot_tls_read; - pNetwork->write = iot_tls_write; - pNetwork->disconnect = iot_tls_disconnect; - pNetwork->isConnected = iot_tls_is_connected; - pNetwork->destroy = iot_tls_destroy; - - pNetwork->tlsDataParams.flags = 0; - - return SUCCESS; -} - -IoT_Error_t iot_tls_is_connected(Network *pNetwork) { - /* Use this to add implementation which can check for physical layer disconnect */ - return NETWORK_PHYSICAL_LAYER_CONNECTED; -} - -IoT_Error_t iot_tls_connect(Network *pNetwork, TLSConnectParams *params) { - int ret = 0; - const char *pers = "aws_iot_tls_wrapper"; - TLSDataParams *tlsDataParams = NULL; - char portBuffer[6]; - char vrfy_buf[512]; - const char *alpnProtocols[] = { "x-amzn-mqtt-ca", NULL }; - -#ifdef ENABLE_IOT_DEBUG - unsigned char buf[MBEDTLS_DEBUG_BUFFER_SIZE]; -#endif - - if(NULL == pNetwork) { - return NULL_VALUE_ERROR; - } - - if(NULL != params) { - _iot_tls_set_connect_params(pNetwork, params->pRootCALocation, params->pDeviceCertLocation, - params->pDevicePrivateKeyLocation, params->pDestinationURL, - params->DestinationPort, params->timeout_ms, params->ServerVerificationFlag); - } - - tlsDataParams = &(pNetwork->tlsDataParams); - - mbedtls_net_init(&(tlsDataParams->server_fd)); - mbedtls_ssl_init(&(tlsDataParams->ssl)); - mbedtls_ssl_config_init(&(tlsDataParams->conf)); - mbedtls_ctr_drbg_init(&(tlsDataParams->ctr_drbg)); - mbedtls_x509_crt_init(&(tlsDataParams->cacert)); - mbedtls_x509_crt_init(&(tlsDataParams->clicert)); - mbedtls_pk_init(&(tlsDataParams->pkey)); - - IOT_DEBUG("\n . Seeding the random number generator..."); - mbedtls_entropy_init(&(tlsDataParams->entropy)); - if((ret = mbedtls_ctr_drbg_seed(&(tlsDataParams->ctr_drbg), mbedtls_entropy_func, &(tlsDataParams->entropy), - (const unsigned char *) pers, strlen(pers))) != 0) { - IOT_ERROR(" failed\n ! mbedtls_ctr_drbg_seed returned -0x%x\n", -ret); - return NETWORK_MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED; - } - - IOT_DEBUG(" . Loading the CA root certificate ..."); - ret = mbedtls_x509_crt_parse_file(&(tlsDataParams->cacert), pNetwork->tlsConnectParams.pRootCALocation); - if(ret < 0) { - IOT_ERROR(" failed\n ! mbedtls_x509_crt_parse returned -0x%x while parsing root cert\n\n", -ret); - return NETWORK_X509_ROOT_CRT_PARSE_ERROR; - } - IOT_DEBUG(" ok (%d skipped)\n", ret); - - IOT_DEBUG(" . Loading the client cert. and key..."); - ret = mbedtls_x509_crt_parse_file(&(tlsDataParams->clicert), pNetwork->tlsConnectParams.pDeviceCertLocation); - if(ret != 0) { - IOT_ERROR(" failed\n ! mbedtls_x509_crt_parse returned -0x%x while parsing device cert\n\n", -ret); - return NETWORK_X509_DEVICE_CRT_PARSE_ERROR; - } - - ret = mbedtls_pk_parse_keyfile(&(tlsDataParams->pkey), pNetwork->tlsConnectParams.pDevicePrivateKeyLocation, ""); - if(ret != 0) { - IOT_ERROR(" failed\n ! mbedtls_pk_parse_key returned -0x%x while parsing private key\n\n", -ret); - IOT_DEBUG(" path : %s ", pNetwork->tlsConnectParams.pDevicePrivateKeyLocation); - return NETWORK_PK_PRIVATE_KEY_PARSE_ERROR; - } - IOT_DEBUG(" ok\n"); - snprintf(portBuffer, 6, "%d", pNetwork->tlsConnectParams.DestinationPort); - IOT_DEBUG(" . Connecting to %s/%s...", pNetwork->tlsConnectParams.pDestinationURL, portBuffer); - if((ret = mbedtls_net_connect(&(tlsDataParams->server_fd), pNetwork->tlsConnectParams.pDestinationURL, - portBuffer, MBEDTLS_NET_PROTO_TCP)) != 0) { - IOT_ERROR(" failed\n ! mbedtls_net_connect returned -0x%x\n\n", -ret); - switch(ret) { - case MBEDTLS_ERR_NET_SOCKET_FAILED: - return NETWORK_ERR_NET_SOCKET_FAILED; - case MBEDTLS_ERR_NET_UNKNOWN_HOST: - return NETWORK_ERR_NET_UNKNOWN_HOST; - case MBEDTLS_ERR_NET_CONNECT_FAILED: - default: - return NETWORK_ERR_NET_CONNECT_FAILED; - }; - } - - ret = mbedtls_net_set_block(&(tlsDataParams->server_fd)); - if(ret != 0) { - IOT_ERROR(" failed\n ! net_set_(non)block() returned -0x%x\n\n", -ret); - return SSL_CONNECTION_ERROR; - } IOT_DEBUG(" ok\n"); - - IOT_DEBUG(" . Setting up the SSL/TLS structure..."); - if((ret = mbedtls_ssl_config_defaults(&(tlsDataParams->conf), MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { - IOT_ERROR(" failed\n ! mbedtls_ssl_config_defaults returned -0x%x\n\n", -ret); - return SSL_CONNECTION_ERROR; - } - - mbedtls_ssl_conf_verify(&(tlsDataParams->conf), _iot_tls_verify_cert, NULL); - if(pNetwork->tlsConnectParams.ServerVerificationFlag == true) { - mbedtls_ssl_conf_authmode(&(tlsDataParams->conf), MBEDTLS_SSL_VERIFY_REQUIRED); - } else { - mbedtls_ssl_conf_authmode(&(tlsDataParams->conf), MBEDTLS_SSL_VERIFY_OPTIONAL); - } - mbedtls_ssl_conf_rng(&(tlsDataParams->conf), mbedtls_ctr_drbg_random, &(tlsDataParams->ctr_drbg)); - - mbedtls_ssl_conf_ca_chain(&(tlsDataParams->conf), &(tlsDataParams->cacert), NULL); - if((ret = mbedtls_ssl_conf_own_cert(&(tlsDataParams->conf), &(tlsDataParams->clicert), &(tlsDataParams->pkey))) != - 0) { - IOT_ERROR(" failed\n ! mbedtls_ssl_conf_own_cert returned %d\n\n", ret); - return SSL_CONNECTION_ERROR; - } - - mbedtls_ssl_conf_read_timeout(&(tlsDataParams->conf), pNetwork->tlsConnectParams.timeout_ms); - - /* Use the AWS IoT ALPN extension for MQTT if port 443 is requested. */ - if(443 == pNetwork->tlsConnectParams.DestinationPort) { - if((ret = mbedtls_ssl_conf_alpn_protocols(&(tlsDataParams->conf), alpnProtocols)) != 0) { - IOT_ERROR(" failed\n ! mbedtls_ssl_conf_alpn_protocols returned -0x%x\n\n", -ret); - return SSL_CONNECTION_ERROR; - } - } - - /* Assign the resulting configuration to the SSL context. */ - if((ret = mbedtls_ssl_setup(&(tlsDataParams->ssl), &(tlsDataParams->conf))) != 0) { - IOT_ERROR(" failed\n ! mbedtls_ssl_setup returned -0x%x\n\n", -ret); - return SSL_CONNECTION_ERROR; - } - if((ret = mbedtls_ssl_set_hostname(&(tlsDataParams->ssl), pNetwork->tlsConnectParams.pDestinationURL)) != 0) { - IOT_ERROR(" failed\n ! mbedtls_ssl_set_hostname returned %d\n\n", ret); - return SSL_CONNECTION_ERROR; - } - IOT_DEBUG("\n\nSSL state connect : %d ", tlsDataParams->ssl.state); - mbedtls_ssl_set_bio(&(tlsDataParams->ssl), &(tlsDataParams->server_fd), mbedtls_net_send, NULL, - mbedtls_net_recv_timeout); - IOT_DEBUG(" ok\n"); - - IOT_DEBUG("\n\nSSL state connect : %d ", tlsDataParams->ssl.state); - IOT_DEBUG(" . Performing the SSL/TLS handshake..."); - while((ret = mbedtls_ssl_handshake(&(tlsDataParams->ssl))) != 0) { - if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - IOT_ERROR(" failed\n ! mbedtls_ssl_handshake returned -0x%x\n", -ret); - if(ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { - IOT_ERROR(" Unable to verify the server's certificate. " - "Either it is invalid,\n" - " or you didn't set ca_file or ca_path " - "to an appropriate value.\n" - " Alternatively, you may want to use " - "auth_mode=optional for testing purposes.\n"); - } - return SSL_CONNECTION_ERROR; - } - } - - IOT_DEBUG(" ok\n [ Protocol is %s ]\n [ Ciphersuite is %s ]\n", mbedtls_ssl_get_version(&(tlsDataParams->ssl)), - mbedtls_ssl_get_ciphersuite(&(tlsDataParams->ssl))); - if((ret = mbedtls_ssl_get_record_expansion(&(tlsDataParams->ssl))) >= 0) { - IOT_DEBUG(" [ Record expansion is %d ]\n", ret); - } else { - IOT_DEBUG(" [ Record expansion is unknown (compression) ]\n"); - } - - IOT_DEBUG(" . Verifying peer X.509 certificate..."); - - if(pNetwork->tlsConnectParams.ServerVerificationFlag == true) { - if((tlsDataParams->flags = mbedtls_ssl_get_verify_result(&(tlsDataParams->ssl))) != 0) { - IOT_ERROR(" failed\n"); - mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", tlsDataParams->flags); - IOT_ERROR("%s\n", vrfy_buf); - ret = SSL_CONNECTION_ERROR; - } else { - IOT_DEBUG(" ok\n"); - ret = SUCCESS; - } - } else { - IOT_DEBUG(" Server Verification skipped\n"); - ret = SUCCESS; - } - -#ifdef ENABLE_IOT_DEBUG - if (mbedtls_ssl_get_peer_cert(&(tlsDataParams->ssl)) != NULL) { - IOT_DEBUG(" . Peer certificate information ...\n"); - mbedtls_x509_crt_info((char *) buf, sizeof(buf) - 1, " ", mbedtls_ssl_get_peer_cert(&(tlsDataParams->ssl))); - IOT_DEBUG("%s\n", buf); - } -#endif - - mbedtls_ssl_conf_read_timeout(&(tlsDataParams->conf), IOT_SSL_READ_TIMEOUT); - - return (IoT_Error_t) ret; -} - -IoT_Error_t iot_tls_write(Network *pNetwork, unsigned char *pMsg, size_t len, Timer *timer, size_t *written_len) { - size_t written_so_far; - bool isErrorFlag = false; - int frags; - int ret = 0; - TLSDataParams *tlsDataParams = &(pNetwork->tlsDataParams); - - for(written_so_far = 0, frags = 0; - written_so_far < len && !has_timer_expired(timer); written_so_far += ret, frags++) { - while(!has_timer_expired(timer) && - (ret = mbedtls_ssl_write(&(tlsDataParams->ssl), pMsg + written_so_far, len - written_so_far)) <= 0) { - if(ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - IOT_ERROR(" failed\n ! mbedtls_ssl_write returned -0x%x\n\n", -ret); - /* All other negative return values indicate connection needs to be reset. - * Will be caught in ping request so ignored here */ - isErrorFlag = true; - break; - } - } - if(isErrorFlag) { - break; - } - } - - *written_len = written_so_far; - - if(isErrorFlag) { - return NETWORK_SSL_WRITE_ERROR; - } else if(has_timer_expired(timer) && written_so_far != len) { - return NETWORK_SSL_WRITE_TIMEOUT_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t iot_tls_read(Network *pNetwork, unsigned char *pMsg, size_t len, Timer *timer, size_t *read_len) { - mbedtls_ssl_context *ssl = &(pNetwork->tlsDataParams.ssl); - size_t rxLen = 0; - int ret; - - while (len > 0) { - // This read will timeout after IOT_SSL_READ_TIMEOUT if there's no data to be read - ret = mbedtls_ssl_read(ssl, pMsg, len); - if (ret > 0) { - rxLen += ret; - pMsg += ret; - len -= ret; - } else if (ret == 0 || (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_TIMEOUT)) { - return NETWORK_SSL_READ_ERROR; - } - - // Evaluate timeout after the read to make sure read is done at least once - if (has_timer_expired(timer)) { - break; - } - } - - if (len == 0) { - *read_len = rxLen; - return SUCCESS; - } - - if (rxLen == 0) { - return NETWORK_SSL_NOTHING_TO_READ; - } else { - return NETWORK_SSL_READ_TIMEOUT_ERROR; - } -} - -IoT_Error_t iot_tls_disconnect(Network *pNetwork) { - mbedtls_ssl_context *ssl = &(pNetwork->tlsDataParams.ssl); - int ret = 0; - do { - ret = mbedtls_ssl_close_notify(ssl); - } while(ret == MBEDTLS_ERR_SSL_WANT_WRITE); - - /* All other negative return values indicate connection needs to be reset. - * No further action required since this is disconnect call */ - - return SUCCESS; -} - -IoT_Error_t iot_tls_destroy(Network *pNetwork) { - TLSDataParams *tlsDataParams = &(pNetwork->tlsDataParams); - - mbedtls_net_free(&(tlsDataParams->server_fd)); - - mbedtls_x509_crt_free(&(tlsDataParams->clicert)); - mbedtls_x509_crt_free(&(tlsDataParams->cacert)); - mbedtls_pk_free(&(tlsDataParams->pkey)); - mbedtls_ssl_free(&(tlsDataParams->ssl)); - mbedtls_ssl_config_free(&(tlsDataParams->conf)); - mbedtls_ctr_drbg_free(&(tlsDataParams->ctr_drbg)); - mbedtls_entropy_free(&(tlsDataParams->entropy)); - - return SUCCESS; -} - -#ifdef __cplusplus -} -#endif diff --git a/platform/linux/mbedtls/network_platform.h b/platform/linux/mbedtls/network_platform.h deleted file mode 100644 index c2810a1c81..0000000000 --- a/platform/linux/mbedtls/network_platform.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef IOTSDKC_NETWORK_MBEDTLS_PLATFORM_H_H - -#include "mbedtls/config.h" - -#include "mbedtls/platform.h" -#include "mbedtls/net.h" -#include "mbedtls/ssl.h" -#include "mbedtls/entropy.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/certs.h" -#include "mbedtls/x509.h" -#include "mbedtls/error.h" -#include "mbedtls/debug.h" -#include "mbedtls/timing.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief TLS Connection Parameters - * - * Defines a type containing TLS specific parameters to be passed down to the - * TLS networking layer to create a TLS secured socket. - */ -typedef struct _TLSDataParams { - mbedtls_entropy_context entropy; - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_ssl_context ssl; - mbedtls_ssl_config conf; - uint32_t flags; - mbedtls_x509_crt cacert; - mbedtls_x509_crt clicert; - mbedtls_pk_context pkey; - mbedtls_net_context server_fd; -}TLSDataParams; - -#define IOTSDKC_NETWORK_MBEDTLS_PLATFORM_H_H - -#ifdef __cplusplus -} -#endif - -#endif //IOTSDKC_NETWORK_MBEDTLS_PLATFORM_H_H diff --git a/platform/linux/pthread/threads_platform.h b/platform/linux/pthread/threads_platform.h deleted file mode 100644 index 8a520c6374..0000000000 --- a/platform/linux/pthread/threads_platform.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include "threads_interface.h" -#ifdef _ENABLE_THREAD_SUPPORT_ -#ifndef IOTSDKC_THREADS_PLATFORM_H_H -#define IOTSDKC_THREADS_PLATFORM_H_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** - * @brief Mutex Type - * - * definition of the Mutex struct. Platform specific - * - */ -struct _IoT_Mutex_t { - pthread_mutex_t lock; -}; - -#ifdef __cplusplus -} -#endif - -#endif /* IOTSDKC_THREADS_PLATFORM_H_H */ -#endif /* _ENABLE_THREAD_SUPPORT_ */ - diff --git a/platform/linux/pthread/threads_pthread_wrapper.c b/platform/linux/pthread/threads_pthread_wrapper.c deleted file mode 100644 index 65a310fb0f..0000000000 --- a/platform/linux/pthread/threads_pthread_wrapper.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#include "threads_platform.h" -#ifdef _ENABLE_THREAD_SUPPORT_ - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Initialize the provided mutex - * - * Call this function to initialize the mutex - * - * @param IoT_Mutex_t - pointer to the mutex to be initialized - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_init(IoT_Mutex_t *pMutex) { - if(0 != pthread_mutex_init(&(pMutex->lock), NULL)) { - return MUTEX_INIT_ERROR; - } - - return SUCCESS; -} - -/** - * @brief Lock the provided mutex - * - * Call this function to lock the mutex before performing a state change - * Blocking, thread will block until lock request fails - * - * @param IoT_Mutex_t - pointer to the mutex to be locked - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_lock(IoT_Mutex_t *pMutex) { -int rc = pthread_mutex_lock(&(pMutex->lock)); - if(0 != rc) { - return MUTEX_LOCK_ERROR; - } - - return SUCCESS; -} - -/** - * @brief Try to lock the provided mutex - * - * Call this function to attempt to lock the mutex before performing a state change - * Non-Blocking, immediately returns with failure if lock attempt fails - * - * @param IoT_Mutex_t - pointer to the mutex to be locked - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_trylock(IoT_Mutex_t *pMutex) { -int rc = pthread_mutex_trylock(&(pMutex->lock)); - if(0 != rc) { - return MUTEX_LOCK_ERROR; - } - - return SUCCESS; -} - -/** - * @brief Unlock the provided mutex - * - * Call this function to unlock the mutex before performing a state change - * - * @param IoT_Mutex_t - pointer to the mutex to be unlocked - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_unlock(IoT_Mutex_t *pMutex) { - if(0 != pthread_mutex_unlock(&(pMutex->lock))) { - return MUTEX_UNLOCK_ERROR; - } - - return SUCCESS; -} - -/** - * @brief Destroy the provided mutex - * - * Call this function to destroy the mutex - * - * @param IoT_Mutex_t - pointer to the mutex to be destroyed - * @return IoT_Error_t - error code indicating result of operation - */ -IoT_Error_t aws_iot_thread_mutex_destroy(IoT_Mutex_t *pMutex) { - if(0 != pthread_mutex_destroy(&(pMutex->lock))) { - return MUTEX_DESTROY_ERROR; - } - - return SUCCESS; -} - -#ifdef __cplusplus -} -#endif - -#endif /* _ENABLE_THREAD_SUPPORT_ */ - diff --git a/samples/README.md b/samples/README.md deleted file mode 100644 index 7ac0fa5e36..0000000000 --- a/samples/README.md +++ /dev/null @@ -1,42 +0,0 @@ -## Overview -This folder contains several samples that demonstrate various SDK functions. The Readme file also includes a walk-through of the subscribe publish sample to explain how the SDK is used. The samples are currently provided with Makefiles for building them on linux. For each sample: - - * Explore the makefile. The makefile for each sample provides a reference on how to set up makefiles for client applications - * Explore the example. It connects to AWS IoT platform using MQTT and demonstrates few actions that can be performed by the SDK - * Download certificate authority CA file from [Symantec](https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem) and place in location referenced in the example (certs/) - * Ensure you have [created a thing](https://docs.aws.amazon.com/iot/latest/developerguide/create-thing.html) through your AWS IoT Console with name matching the definition AWS_IOT_MY_THING_NAME in the `aws_iot_config.h` file - * Place device identity cert and private key in locations referenced in the example (certs/) - * Ensure the names of the cert files are the same as in the `aws_iot_config.h` file - * Ensure the certificate has an attached policy which allows the proper permissions for AWS IoT - * Build the example using make (`make`) - * Run sample application (./subscribe_publish_sample or ./shadow_sample). The sample will print status messages to stdout - * All samples are written in C unless otherwise mentioned. The following sample applications are included: - * `subscribe_publish_sample` - a simple pub/sub MQTT example - * `subscribe_publish_cpp_sample` - a simple pub/sub MQTT example written in C++ - * `subscribe_publish_library_sample` - a simple pub/sub MQTT example which builds the SDK as a separate library - * `shadow_sample` - a simple device shadow example using a connected window example - * `shadow_sample_console_echo` - a sample to work with the AWS IoT Console interactive guide - -## Subscribe Publish Sample -This is a simple pub/sub MQTT example. It connects a single MQTT client to the server and subscribes to a test topic. Then it proceeds to publish messages on this topic and yields after each publish to ensure that the message was received. - - * The sample first creates an instance of the AWS_IoT_Client - * The next step is to initialize the client. The aws_iot_mqtt_init API is called for this purpose. The API takes the client instance and an IoT_Client_Init_Params variable to set the initial values for the client. The Init params include values like host URL, port, certificates, disconnect handler etc. - * If the call to the init API was successful, we can proceed to call connect. The API is called aws_iot_mqtt_connect. It takes the client instance and IoT_Client_Connect_Params variable as arguments. The IoT_Client_Connect_Params is optional after the first call to connect as the client retains the original values that were provided to support reconnect. The Connect params include values like Client Id, MQTT Version etc. - * If the connect API call was successful, we can proceed to subscribe and publish on this connect. The connect API call will return an error code, specific to the type of error that occurred, in case the call fails. - * It is important to remember here that there is no dynamic memory allocation in the SDK. Any values that are passed as a pointer to the APIs should not be freed unless they are not required any further. For example, if the variable that stores the certificate path is freed after the init call is made, the connect call will fail. Similarly, if it is freed after the connect API returns success, any future connect calls (including reconnects) will fail. - * The next step for this sample is to subscribe to the test topic. The API to be called for subscribe is aws_iot_mqtt_subscribe. It takes as arguments, the IoT Client instance, topic name, the length of the topic name, QoS, the subscribe callback handler and an optional pointer to some data to be returned to the subscribe handler - * The next step it to call the publish API to send a message on the test topic. The sample sends two different messages, one QoS0 and one QoS1. The - * The publish API takes the client instance, topic name to publish to, topic name length and a variable of type IoT_Publish_Message_Params. The IoT_Publish_Message_Params contains the payload, length of the payload and QoS. - * If the publish API calls are successful, the sample proceeds to call the yield API. The yield API takes the client instance and a timeout value in milliseconds as arguments. - * The yield API is called to let the SDK process any incoming messages. It also periodically sends out the PING request to prevent disconnect and, if enabled, it also performs auto-reconnect and resubscribe. - * The yield API should be called periodically to process the PING request as well as read any messages in the receive buffer. It should be called once at least every TTL/2 time periods to ensure disconnect does not happen. There can only be one yield in progress at a time. Therefore, in multi-threaded scenarios one thread can be a dedicated yield thread while other threads handle other operations. - * The sample sends out messages equal to the value set in publish count unless infinite publishing flag is set - -For further information on each API please read the API documentation. - -## Subscribe Publish Cpp Sample -This is the same sample as above but it is built using a C++ compiler. It demonstrates how the SDK can be used in a C++ program. - -## Subscribe Publish Library Sample -This is also the same code as the Subscribe Publish sample. In this case, the SDK is built as a separate library and then used in the sample program. \ No newline at end of file diff --git a/samples/linux/jobs_sample/Makefile b/samples/linux/jobs_sample/Makefile deleted file mode 100644 index 5fc68e67ab..0000000000 --- a/samples/linux/jobs_sample/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -CC = gcc - -#remove @ for no make command prints -DEBUG = @ - -APP_DIR = . -APP_INCLUDE_DIRS += -I $(APP_DIR) -APP_NAME = jobs_sample -APP_SRC_FILES = $(APP_NAME).c - -#IoT client directory -IOT_CLIENT_DIR = ../../.. - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux/mbedtls -PLATFORM_COMMON_DIR = $(IOT_CLIENT_DIR)/platform/linux/common - -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/sdk_config -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn -IOT_INCLUDE_DIRS += -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_DIR) - -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') - -#TLS - mbedtls -MBEDTLS_DIR = $(IOT_CLIENT_DIR)/external_libs/mbedTLS -TLS_LIB_DIR = $(MBEDTLS_DIR)/library -TLS_INCLUDE_DIR = -I $(MBEDTLS_DIR)/include -EXTERNAL_LIBS += -L$(TLS_LIB_DIR) -LD_FLAG += -Wl,-rpath,$(TLS_LIB_DIR) -LD_FLAG += -ldl $(TLS_LIB_DIR)/libmbedtls.a $(TLS_LIB_DIR)/libmbedcrypto.a $(TLS_LIB_DIR)/libmbedx509.a -lpthread - -#Aggregate all include and src directories -INCLUDE_ALL_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(TLS_INCLUDE_DIR) -INCLUDE_ALL_DIRS += $(APP_INCLUDE_DIRS) - -SRC_FILES += $(APP_SRC_FILES) -SRC_FILES += $(IOT_SRC_FILES) - -# Logging level control -LOG_FLAGS += -DENABLE_IOT_DEBUG -LOG_FLAGS += -DENABLE_IOT_INFO -LOG_FLAGS += -DENABLE_IOT_WARN -LOG_FLAGS += -DENABLE_IOT_ERROR - -COMPILER_FLAGS += $(LOG_FLAGS) -#If the processor is big endian uncomment the compiler flag -#COMPILER_FLAGS += -DREVERSED - -MBED_TLS_MAKE_CMD = $(MAKE) -C $(MBEDTLS_DIR) - -PRE_MAKE_CMD = $(MBED_TLS_MAKE_CMD) -MAKE_CMD = $(CC) $(SRC_FILES) $(COMPILER_FLAGS) -o $(APP_NAME) $(LD_FLAG) $(EXTERNAL_LIBS) $(INCLUDE_ALL_DIRS) - -all: - $(PRE_MAKE_CMD) - $(DEBUG)$(MAKE_CMD) - $(POST_MAKE_CMD) - -clean: - rm -f $(APP_DIR)/$(APP_NAME) - $(MBED_TLS_MAKE_CMD) clean diff --git a/samples/linux/jobs_sample/aws_iot_config.h b/samples/linux/jobs_sample/aws_iot_config.h deleted file mode 100644 index 3238ce1081..0000000000 --- a/samples/linux/jobs_sample/aws_iot_config.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_config.h - * @brief AWS IoT specific configuration file - */ - -#ifndef SRC_JOBS_IOT_JOB_CONFIG_H_ -#define SRC_JOBS_IOT_JOB_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow -#define AWS_IOT_MQTT_PORT 443 ///< default port for MQTT/S -#define AWS_IOT_MQTT_CLIENT_ID "c-sdk-client-id" ///< MQTT client ID should be unique for every device -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" ///< Root CA file name -#define AWS_IOT_CERTIFICATE_FILENAME "cert.pem" ///< device signed certificate file name -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" ///< Device private key filename - -// MQTT PubSub -#ifndef DISABLE_IOT_JOBS -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#else -#define AWS_IOT_MQTT_RX_BUF_LEN 2048 -#endif -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Shadow and Job common configs -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_SIZE_OF_THING_NAME 30 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER 512 ///< Maximum size of the SHADOW buffer to store the received Shadow message -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Job specific configs -#ifndef DISABLE_IOT_JOBS -#define MAX_SIZE_OF_JOB_ID 64 -#define MAX_JOB_JSON_TOKEN_EXPECTED 120 -#define MAX_SIZE_OF_JOB_REQUEST AWS_IOT_MQTT_TX_BUF_LEN - -#define MAX_JOB_TOPIC_LENGTH_WITHOUT_JOB_ID_OR_THING_NAME 40 -#define MAX_JOB_TOPIC_LENGTH_BYTES MAX_JOB_TOPIC_LENGTH_WITHOUT_JOB_ID_OR_THING_NAME + MAX_SIZE_OF_THING_NAME + MAX_SIZE_OF_JOB_ID + 2 -#endif - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#define DISABLE_METRICS false ///< Disable the collection of metrics by setting this to true - -#endif /* SRC_JOBS_IOT_JOB_CONFIG_H_ */ diff --git a/samples/linux/jobs_sample/jobs_sample.c b/samples/linux/jobs_sample/jobs_sample.c deleted file mode 100644 index 7b066f115f..0000000000 --- a/samples/linux/jobs_sample/jobs_sample.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * - * This example takes the parameters from the aws_iot_config.h file and establishes - * a connection to the AWS IoT MQTT Platform. It performs several operations to - * demonstrate the basic capabilities of the AWS IoT Jobs platform. - * - * If all the certs are correct, you should see the list of pending Job Executions - * printed out by the iot_get_pending_callback_handler. If there are any existing pending - * job executions each will be processed one at a time in the iot_next_job_callback_handler. - * After all of the pending jobs have been processed the program will wait for - * notifications for new pending jobs and process them one at a time as they come in. - * - * In the main body you can see how each callback is registered for each corresponding - * Jobs topic. - * - */ -#include -#include -#include -#include -#include -#include - -#include "aws_iot_config.h" -#include "aws_iot_json_utils.h" -#include "aws_iot_log.h" -#include "aws_iot_version.h" -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_jobs_interface.h" - -/** - * @brief Default cert location - */ -static char certDirectory[PATH_MAX + 1] = "../../../certs"; - -/** - * @brief Default MQTT HOST URL is pulled from the aws_iot_config.h - */ -static char HostAddress[255] = AWS_IOT_MQTT_HOST; - -/** - * @brief Default MQTT port is pulled from the aws_iot_config.h - */ -static uint32_t port = AWS_IOT_MQTT_PORT; - -static jsmn_parser jsonParser; -static jsmntok_t jsonTokenStruct[MAX_JSON_TOKEN_EXPECTED]; -static int32_t tokenCount; - -static void iot_get_pending_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("\nJOB_GET_PENDING_TOPIC callback"); - IOT_INFO("topic: %.*s", topicNameLen, topicName); - IOT_INFO("payload: %.*s", (int) params->payloadLen, (char *)params->payload); - - jsmn_init(&jsonParser); - - tokenCount = jsmn_parse(&jsonParser, params->payload, (int) params->payloadLen, jsonTokenStruct, MAX_JSON_TOKEN_EXPECTED); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d", tokenCount); - return; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - IOT_WARN("Top Level is not an object"); - return; - } - - jsmntok_t *jobs; - - jobs = findToken("inProgressJobs", params->payload, jsonTokenStruct); - - if (jobs) { - IOT_INFO("inProgressJobs: %.*s", jobs->end - jobs->start, (char *)params->payload + jobs->start); - } - - jobs = findToken("queuedJobs", params->payload, jsonTokenStruct); - - if (jobs) { - IOT_INFO("queuedJobs: %.*s", jobs->end - jobs->start, (char *)params->payload + jobs->start); - } -} - -static void iot_next_job_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - char topicToPublishUpdate[MAX_JOB_TOPIC_LENGTH_BYTES]; - char messageBuffer[200]; - - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("\nJOB_NOTIFY_NEXT_TOPIC / JOB_DESCRIBE_TOPIC($next) callback"); - IOT_INFO("topic: %.*s", topicNameLen, topicName); - IOT_INFO("payload: %.*s", (int) params->payloadLen, (char *)params->payload); - - jsmn_init(&jsonParser); - - tokenCount = jsmn_parse(&jsonParser, params->payload, (int) params->payloadLen, jsonTokenStruct, MAX_JSON_TOKEN_EXPECTED); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d", tokenCount); - return; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - IOT_WARN("Top Level is not an object"); - return; - } - - jsmntok_t *tokExecution; - - tokExecution = findToken("execution", params->payload, jsonTokenStruct); - - if (tokExecution) { - IOT_INFO("execution: %.*s", tokExecution->end - tokExecution->start, (char *)params->payload + tokExecution->start); - - jsmntok_t *tok; - - tok = findToken("jobId", params->payload, tokExecution); - - if (tok) { - IoT_Error_t rc; - char jobId[MAX_SIZE_OF_JOB_ID + 1]; - AwsIotJobExecutionUpdateRequest updateRequest; - - rc = parseStringValue(jobId, MAX_SIZE_OF_JOB_ID + 1, params->payload, tok); - if(SUCCESS != rc) { - IOT_ERROR("parseStringValue returned error : %d ", rc); - return; - } - - IOT_INFO("jobId: %s", jobId); - - tok = findToken("jobDocument", params->payload, tokExecution); - - /* - * Do your job processing here. - */ - - if (tok) { - IOT_INFO("jobDocument: %.*s", tok->end - tok->start, (char *)params->payload + tok->start); - /* Alternatively if the job still has more steps the status can be set to JOB_EXECUTION_IN_PROGRESS instead */ - updateRequest.status = JOB_EXECUTION_SUCCEEDED; - updateRequest.statusDetails = "{\"exampleDetail\":\"a value appropriate for your successful job\"}"; - } else { - updateRequest.status = JOB_EXECUTION_FAILED; - updateRequest.statusDetails = "{\"failureDetail\":\"Unable to process job document\"}"; - } - - updateRequest.expectedVersion = 0; - updateRequest.executionNumber = 0; - updateRequest.includeJobExecutionState = false; - updateRequest.includeJobDocument = false; - updateRequest.clientToken = NULL; - - rc = aws_iot_jobs_send_update(pClient, QOS0, AWS_IOT_MY_THING_NAME, jobId, &updateRequest, - topicToPublishUpdate, sizeof(topicToPublishUpdate), messageBuffer, sizeof(messageBuffer)); - if(SUCCESS != rc) { - IOT_ERROR("aws_iot_jobs_send_update returned error : %d ", rc); - return; - } - } - } else { - IOT_INFO("execution property not found, nothing to do"); - } -} - -static void iot_update_accepted_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("\nJOB_UPDATE_TOPIC / accepted callback"); - IOT_INFO("topic: %.*s", topicNameLen, topicName); - IOT_INFO("payload: %.*s", (int) params->payloadLen, (char *)params->payload); -} - -static void iot_update_rejected_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("\nJOB_UPDATE_TOPIC / rejected callback"); - IOT_INFO("topic: %.*s", topicNameLen, topicName); - IOT_INFO("payload: %.*s", (int) params->payloadLen, (char *)params->payload); - - /* Do error handling here for when the update was rejected */ -} - -static void disconnectCallbackHandler(AWS_IoT_Client *pClient, void *data) { - IOT_WARN("MQTT Disconnect"); - IoT_Error_t rc = FAILURE; - - if(NULL == pClient) { - return; - } - - IOT_UNUSED(data); - - if(aws_iot_is_autoreconnect_enabled(pClient)) { - IOT_INFO("Auto Reconnect is enabled, Reconnecting attempt will start now"); - } else { - IOT_WARN("Auto Reconnect not enabled. Starting manual reconnect..."); - rc = aws_iot_mqtt_attempt_reconnect(pClient); - if(NETWORK_RECONNECTED == rc) { - IOT_WARN("Manual Reconnect Successful"); - } else { - IOT_WARN("Manual Reconnect Failed - %d", rc); - } - } -} - -int main(int argc, char **argv) { - char rootCA[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - char cPayload[100]; - - IoT_Error_t rc = FAILURE; - - AWS_IoT_Client client; - IoT_Client_Init_Params mqttInitParams = iotClientInitParamsDefault; - IoT_Client_Connect_Params connectParams = iotClientConnectParamsDefault; - - IoT_Publish_Message_Params paramsQOS0; - - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(rootCA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - - IOT_DEBUG("rootCA %s", rootCA); - IOT_DEBUG("clientCRT %s", clientCRT); - IOT_DEBUG("clientKey %s", clientKey); - - mqttInitParams.enableAutoReconnect = false; // We enable this later below - mqttInitParams.pHostURL = HostAddress; - mqttInitParams.port = port; - mqttInitParams.pRootCALocation = rootCA; - mqttInitParams.pDeviceCertLocation = clientCRT; - mqttInitParams.pDevicePrivateKeyLocation = clientKey; - mqttInitParams.mqttCommandTimeout_ms = 20000; - mqttInitParams.tlsHandshakeTimeout_ms = 5000; - mqttInitParams.isSSLHostnameVerify = true; - mqttInitParams.disconnectHandler = disconnectCallbackHandler; - mqttInitParams.disconnectHandlerData = NULL; - - rc = aws_iot_mqtt_init(&client, &mqttInitParams); - if(SUCCESS != rc) { - IOT_ERROR("aws_iot_mqtt_init returned error : %d ", rc); - return rc; - } - - connectParams.keepAliveIntervalInSec = 600; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = AWS_IOT_MQTT_CLIENT_ID; - connectParams.clientIDLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - connectParams.isWillMsgPresent = false; - - IOT_INFO("Connecting..."); - rc = aws_iot_mqtt_connect(&client, &connectParams); - if(SUCCESS != rc) { - IOT_ERROR("Error(%d) connecting to %s:%d", rc, mqttInitParams.pHostURL, mqttInitParams.port); - return rc; - } - /* - * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h - * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL - * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL - */ - rc = aws_iot_mqtt_autoreconnect_set_status(&client, true); - if(SUCCESS != rc) { - IOT_ERROR("Unable to set Auto Reconnect to true - %d", rc); - return rc; - } - - char topicToSubscribeGetPending[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToSubscribeNotifyNext[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToSubscribeGetNext[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToSubscribeUpdateAccepted[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToSubscribeUpdateRejected[MAX_JOB_TOPIC_LENGTH_BYTES]; - - char topicToPublishGetPending[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToPublishGetNext[MAX_JOB_TOPIC_LENGTH_BYTES]; - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, NULL, JOB_GET_PENDING_TOPIC, JOB_WILDCARD_REPLY_TYPE, - iot_get_pending_callback_handler, NULL, topicToSubscribeGetPending, sizeof(topicToSubscribeGetPending)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_GET_PENDING_TOPIC: %d ", rc); - return rc; - } - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, NULL, JOB_NOTIFY_NEXT_TOPIC, JOB_REQUEST_TYPE, - iot_next_job_callback_handler, NULL, topicToSubscribeNotifyNext, sizeof(topicToSubscribeNotifyNext)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_NOTIFY_NEXT_TOPIC: %d ", rc); - return rc; - } - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, JOB_ID_NEXT, JOB_DESCRIBE_TOPIC, JOB_WILDCARD_REPLY_TYPE, - iot_next_job_callback_handler, NULL, topicToSubscribeGetNext, sizeof(topicToSubscribeGetNext)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_DESCRIBE_TOPIC ($next): %d ", rc); - return rc; - } - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, JOB_ID_WILDCARD, JOB_UPDATE_TOPIC, JOB_ACCEPTED_REPLY_TYPE, - iot_update_accepted_callback_handler, NULL, topicToSubscribeUpdateAccepted, sizeof(topicToSubscribeUpdateAccepted)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_UPDATE_TOPIC/accepted: %d ", rc); - return rc; - } - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, JOB_ID_WILDCARD, JOB_UPDATE_TOPIC, JOB_REJECTED_REPLY_TYPE, - iot_update_rejected_callback_handler, NULL, topicToSubscribeUpdateRejected, sizeof(topicToSubscribeUpdateRejected)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_UPDATE_TOPIC/rejected: %d ", rc); - return rc; - } - - paramsQOS0.qos = QOS0; - paramsQOS0.payload = (void *) cPayload; - paramsQOS0.isRetained = 0; - paramsQOS0.payloadLen = strlen(cPayload); - - rc = aws_iot_jobs_send_query(&client, QOS0, AWS_IOT_MY_THING_NAME, NULL, NULL, topicToPublishGetPending, sizeof(topicToPublishGetPending), NULL, 0, JOB_GET_PENDING_TOPIC); - - AwsIotDescribeJobExecutionRequest describeRequest; - describeRequest.executionNumber = 0; - describeRequest.includeJobDocument = true; - describeRequest.clientToken = NULL; - - rc = aws_iot_jobs_describe(&client, QOS0, AWS_IOT_MY_THING_NAME, JOB_ID_NEXT, &describeRequest, topicToPublishGetNext, sizeof(topicToPublishGetNext), NULL, 0); - - while(SUCCESS == rc) { - //Max time the yield function will wait for read messages - rc = aws_iot_mqtt_yield(&client, 50000); - } - - return rc; -} diff --git a/samples/linux/shadow_sample/Makefile b/samples/linux/shadow_sample/Makefile deleted file mode 100644 index 6199b0484d..0000000000 --- a/samples/linux/shadow_sample/Makefile +++ /dev/null @@ -1,71 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -CC = gcc - -#remove @ for no make command prints -DEBUG = @ - -APP_DIR = . -APP_INCLUDE_DIRS += -I $(APP_DIR) -APP_NAME = shadow_sample -APP_SRC_FILES = $(APP_NAME).c - -#IoT client directory -IOT_CLIENT_DIR = ../../.. - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux/mbedtls -PLATFORM_COMMON_DIR = $(IOT_CLIENT_DIR)/platform/linux/common - -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn -IOT_INCLUDE_DIRS += -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_DIR) - -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') - -#TLS - mbedtls -MBEDTLS_DIR = $(IOT_CLIENT_DIR)/external_libs/mbedTLS -TLS_LIB_DIR = $(MBEDTLS_DIR)/library -TLS_INCLUDE_DIR = -I $(MBEDTLS_DIR)/include -EXTERNAL_LIBS += -L$(TLS_LIB_DIR) -LD_FLAG += -Wl,-rpath,$(TLS_LIB_DIR) -LD_FLAG += -ldl $(TLS_LIB_DIR)/libmbedtls.a $(TLS_LIB_DIR)/libmbedcrypto.a $(TLS_LIB_DIR)/libmbedx509.a - - -#Aggregate all include and src directories -INCLUDE_ALL_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(TLS_INCLUDE_DIR) -INCLUDE_ALL_DIRS += $(APP_INCLUDE_DIRS) - -SRC_FILES += $(APP_SRC_FILES) -SRC_FILES += $(IOT_SRC_FILES) - -# Logging level control -LOG_FLAGS += -DENABLE_IOT_DEBUG -LOG_FLAGS += -DENABLE_IOT_INFO -LOG_FLAGS += -DENABLE_IOT_WARN -LOG_FLAGS += -DENABLE_IOT_ERROR - -COMPILER_FLAGS += -g -COMPILER_FLAGS += $(LOG_FLAGS) -#If the processor is big endian uncomment the compiler flag -#COMPILER_FLAGS += -DREVERSED - -MBED_TLS_MAKE_CMD = $(MAKE) -C $(MBEDTLS_DIR) - -PRE_MAKE_CMD = $(MBED_TLS_MAKE_CMD) -MAKE_CMD = $(CC) $(SRC_FILES) $(COMPILER_FLAGS) -o $(APP_NAME) $(LD_FLAG) $(EXTERNAL_LIBS) $(INCLUDE_ALL_DIRS) - -all: - $(PRE_MAKE_CMD) - $(DEBUG)$(MAKE_CMD) - $(POST_MAKE_CMD) - -clean: - rm -f $(APP_DIR)/$(APP_NAME) - $(MBED_TLS_MAKE_CMD) clean diff --git a/samples/linux/shadow_sample/aws_iot_config.h b/samples/linux/shadow_sample/aws_iot_config.h deleted file mode 100644 index 8af3dd9697..0000000000 --- a/samples/linux/shadow_sample/aws_iot_config.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_config.h - * @brief AWS IoT specific configuration file - */ - -#ifndef SRC_SHADOW_IOT_SHADOW_CONFIG_H_ -#define SRC_SHADOW_IOT_SHADOW_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow -#define AWS_IOT_MQTT_PORT 443 ///< default port for MQTT/S -#define AWS_IOT_MQTT_CLIENT_ID "c-sdk-client-id" ///< MQTT client ID should be unique for every device -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" ///< Root CA file name -#define AWS_IOT_CERTIFICATE_FILENAME "cert.pem" ///< device signed certificate file name -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" ///< Device private key filename -// ================================================= - -// MQTT PubSub -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER (AWS_IOT_MQTT_RX_BUF_LEN+1) ///< Maximum size of the SHADOW buffer to store the received Shadow message, including terminating NULL byte. -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SIZE_OF_THING_NAME 20 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#define DISABLE_METRICS false ///< Disable the collection of metrics by setting this to true - -#endif /* SRC_SHADOW_IOT_SHADOW_CONFIG_H_ */ diff --git a/samples/linux/shadow_sample/shadow_sample.c b/samples/linux/shadow_sample/shadow_sample.c deleted file mode 100644 index 2a692c70c2..0000000000 --- a/samples/linux/shadow_sample/shadow_sample.c +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file shadow_sample.c - * @brief A simple connected window example demonstrating the use of Thing Shadow - */ - -#include -#include -#include -#include -#include -#include - -#include "aws_iot_config.h" -#include "aws_iot_log.h" -#include "aws_iot_version.h" -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_shadow_interface.h" - -/*! - * The goal of this sample application is to demonstrate the capabilities of shadow. - * This device(say Connected Window) will open the window of a room based on temperature - * It can report to the Shadow the following parameters: - * 1. temperature of the room (double) - * 2. status of the window (open or close) - * It can act on commands from the cloud. In this case it will open or close the window based on the json object "windowOpen" data[open/close] - * - * The two variables from a device's perspective are double temperature and bool windowOpen - * The device needs to act on only on windowOpen variable, so we will create a primitiveJson_t object with callback - The Json Document in the cloud will be - { - "reported": { - "temperature": 0, - "windowOpen": false - }, - "desired": { - "windowOpen": false - } - } - */ - -#define ROOMTEMPERATURE_UPPERLIMIT 32.0f -#define ROOMTEMPERATURE_LOWERLIMIT 25.0f -#define STARTING_ROOMTEMPERATURE ROOMTEMPERATURE_LOWERLIMIT - -#define MAX_LENGTH_OF_UPDATE_JSON_BUFFER 200 - -static char certDirectory[PATH_MAX + 1] = "../../../certs"; -#define HOST_ADDRESS_SIZE 255 -static char HostAddress[HOST_ADDRESS_SIZE] = AWS_IOT_MQTT_HOST; -static uint32_t port = AWS_IOT_MQTT_PORT; -static uint8_t numPubs = 5; - -static void simulateRoomTemperature(float *pRoomTemperature) { - static float deltaChange; - - if(*pRoomTemperature >= ROOMTEMPERATURE_UPPERLIMIT) { - deltaChange = -0.5f; - } else if(*pRoomTemperature <= ROOMTEMPERATURE_LOWERLIMIT) { - deltaChange = 0.5f; - } - - *pRoomTemperature += deltaChange; -} - -static void ShadowUpdateStatusCallback(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, - const char *pReceivedJsonDocument, void *pContextData) { - IOT_UNUSED(pThingName); - IOT_UNUSED(action); - IOT_UNUSED(pReceivedJsonDocument); - IOT_UNUSED(pContextData); - - if(SHADOW_ACK_TIMEOUT == status) { - IOT_INFO("Update Timeout--"); - } else if(SHADOW_ACK_REJECTED == status) { - IOT_INFO("Update RejectedXX"); - } else if(SHADOW_ACK_ACCEPTED == status) { - IOT_INFO("Update Accepted !!"); - } -} - -static void windowActuate_Callback(const char *pJsonString, uint32_t JsonStringDataLen, jsonStruct_t *pContext) { - IOT_UNUSED(pJsonString); - IOT_UNUSED(JsonStringDataLen); - - if(pContext != NULL) { - IOT_INFO("Delta - Window state changed to %d", *(bool *) (pContext->pData)); - } -} - -static void parseInputArgsForConnectParams(int argc, char **argv) { - int opt; - - while(-1 != (opt = getopt(argc, argv, "h:p:c:n:"))) { - switch(opt) { - case 'h': - strncpy(HostAddress, optarg, HOST_ADDRESS_SIZE); - IOT_DEBUG("Host %s", optarg); - break; - case 'p': - port = atoi(optarg); - IOT_DEBUG("port %s", optarg); - break; - case 'c': - strncpy(certDirectory, optarg, PATH_MAX + 1); - IOT_DEBUG("cert root directory %s", optarg); - break; - case 'n': - numPubs = atoi(optarg); - IOT_DEBUG("num pubs %s", optarg); - break; - case '?': - if(optopt == 'c') { - IOT_ERROR("Option -%c requires an argument.", optopt); - } else if(isprint(optopt)) { - IOT_WARN("Unknown option `-%c'.", optopt); - } else { - IOT_WARN("Unknown option character `\\x%x'.", optopt); - } - break; - default: - IOT_ERROR("ERROR in command line argument parsing"); - break; - } - } - -} - -int main(int argc, char **argv) { - IoT_Error_t rc = FAILURE; - - char JsonDocumentBuffer[MAX_LENGTH_OF_UPDATE_JSON_BUFFER]; - size_t sizeOfJsonDocumentBuffer = sizeof(JsonDocumentBuffer) / sizeof(JsonDocumentBuffer[0]); - float temperature = 0.0; - - bool windowOpen = false; - jsonStruct_t windowActuator; - windowActuator.cb = windowActuate_Callback; - windowActuator.pData = &windowOpen; - windowActuator.dataLength = sizeof(bool); - windowActuator.pKey = "windowOpen"; - windowActuator.type = SHADOW_JSON_BOOL; - - jsonStruct_t temperatureHandler; - temperatureHandler.cb = NULL; - temperatureHandler.pKey = "temperature"; - temperatureHandler.pData = &temperature; - temperatureHandler.dataLength = sizeof(float); - temperatureHandler.type = SHADOW_JSON_FLOAT; - - char rootCA[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - - IOT_INFO("\nAWS IoT SDK Version %d.%d.%d-%s\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG); - - IOT_DEBUG("rootCA %s", rootCA); - IOT_DEBUG("clientCRT %s", clientCRT); - IOT_DEBUG("clientKey %s", clientKey); - - parseInputArgsForConnectParams(argc, argv); - - // generate the paths of the credentials - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(rootCA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - - // initialize the mqtt client - AWS_IoT_Client mqttClient; - - ShadowInitParameters_t sp = ShadowInitParametersDefault; - sp.pHost = HostAddress; - sp.port = port; - sp.pClientCRT = clientCRT; - sp.pClientKey = clientKey; - sp.pRootCA = rootCA; - sp.enableAutoReconnect = false; - sp.disconnectHandler = NULL; - - IOT_INFO("Shadow Init"); - rc = aws_iot_shadow_init(&mqttClient, &sp); - if(SUCCESS != rc) { - IOT_ERROR("Shadow Connection Error"); - return rc; - } - - ShadowConnectParameters_t scp = ShadowConnectParametersDefault; - scp.pMyThingName = AWS_IOT_MY_THING_NAME; - scp.pMqttClientId = AWS_IOT_MQTT_CLIENT_ID; - scp.mqttClientIdLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - - IOT_INFO("Shadow Connect"); - rc = aws_iot_shadow_connect(&mqttClient, &scp); - if(SUCCESS != rc) { - IOT_ERROR("Shadow Connection Error"); - return rc; - } - - /* - * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h - * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL - * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL - */ - rc = aws_iot_shadow_set_autoreconnect_status(&mqttClient, true); - if(SUCCESS != rc) { - IOT_ERROR("Unable to set Auto Reconnect to true - %d", rc); - return rc; - } - - rc = aws_iot_shadow_register_delta(&mqttClient, &windowActuator); - - if(SUCCESS != rc) { - IOT_ERROR("Shadow Register Delta Error"); - } - temperature = STARTING_ROOMTEMPERATURE; - - // loop and publish a change in temperature - while(NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc) { - rc = aws_iot_shadow_yield(&mqttClient, 200); - if(NETWORK_ATTEMPTING_RECONNECT == rc) { - sleep(1); - // If the client is attempting to reconnect we will skip the rest of the loop. - continue; - } - IOT_INFO("\n=======================================================================================\n"); - IOT_INFO("On Device: window state %s", windowOpen ? "true" : "false"); - simulateRoomTemperature(&temperature); - - rc = aws_iot_shadow_init_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer); - if(SUCCESS == rc) { - rc = aws_iot_shadow_add_reported(JsonDocumentBuffer, sizeOfJsonDocumentBuffer, 2, &temperatureHandler, - &windowActuator); - if(SUCCESS == rc) { - rc = aws_iot_finalize_json_document(JsonDocumentBuffer, sizeOfJsonDocumentBuffer); - if(SUCCESS == rc) { - IOT_INFO("Update Shadow: %s", JsonDocumentBuffer); - rc = aws_iot_shadow_update(&mqttClient, AWS_IOT_MY_THING_NAME, JsonDocumentBuffer, - ShadowUpdateStatusCallback, NULL, 4, true); - } - } - } - IOT_INFO("*****************************************************************************************\n"); - sleep(1); - } - - if(SUCCESS != rc) { - IOT_ERROR("An error occurred in the loop %d", rc); - } - - IOT_INFO("Disconnecting"); - rc = aws_iot_shadow_disconnect(&mqttClient); - - if(SUCCESS != rc) { - IOT_ERROR("Disconnect error %d", rc); - } - - return rc; -} diff --git a/samples/linux/shadow_sample_console_echo/Makefile b/samples/linux/shadow_sample_console_echo/Makefile deleted file mode 100644 index 8447901d8f..0000000000 --- a/samples/linux/shadow_sample_console_echo/Makefile +++ /dev/null @@ -1,74 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -CC = gcc - -#remove @ for no make command prints -DEBUG = @ - -APP_DIR = . -APP_INCLUDE_DIRS += -I $(APP_DIR) -APP_NAME = shadow_console_echo -APP_SRC_FILES = $(APP_NAME).c - -#IoT client directory -IOT_CLIENT_DIR = ../../.. - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux/mbedtls -PLATFORM_COMMON_DIR = $(IOT_CLIENT_DIR)/platform/linux/common - -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn -IOT_INCLUDE_DIRS += -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_DIR) - -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') - -#TLS - mbedtls -MBEDTLS_DIR = $(IOT_CLIENT_DIR)/external_libs/mbedTLS -TLS_LIB_DIR = $(MBEDTLS_DIR)/library -TLS_INCLUDE_DIR = -I $(MBEDTLS_DIR)/include -EXTERNAL_LIBS += -L$(TLS_LIB_DIR) -LD_FLAG += -Wl,-rpath,$(TLS_LIB_DIR) -LD_FLAG += -ldl $(TLS_LIB_DIR)/libmbedtls.a $(TLS_LIB_DIR)/libmbedcrypto.a $(TLS_LIB_DIR)/libmbedx509.a - - -#Aggregate all include and src directories -INCLUDE_ALL_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(MQTT_INCLUDE_DIR) -INCLUDE_ALL_DIRS += $(TLS_INCLUDE_DIR) -INCLUDE_ALL_DIRS += $(APP_INCLUDE_DIRS) - -SRC_FILES += $(MQTT_SRC_FILES) -SRC_FILES += $(APP_SRC_FILES) -SRC_FILES += $(IOT_SRC_FILES) - -# Logging level control -LOG_FLAGS += -DENABLE_IOT_DEBUG -LOG_FLAGS += -DENABLE_IOT_INFO -LOG_FLAGS += -DENABLE_IOT_WARN -LOG_FLAGS += -DENABLE_IOT_ERROR - -COMPILER_FLAGS += -g -COMPILER_FLAGS += $(LOG_FLAGS) - -#If the processor is big endian uncomment the compiler flag -#COMPILER_FLAGS += -DREVERSED - -MBED_TLS_MAKE_CMD = $(MAKE) -C $(MBEDTLS_DIR) - -PRE_MAKE_CMD = $(MBED_TLS_MAKE_CMD) -MAKE_CMD = $(CC) $(SRC_FILES) $(COMPILER_FLAGS) -o $(APP_NAME) $(LD_FLAG) $(EXTERNAL_LIBS) $(INCLUDE_ALL_DIRS) - -all: - $(PRE_MAKE_CMD) - $(DEBUG)$(MAKE_CMD) - $(POST_MAKE_CMD) - -clean: - rm -f $(APP_DIR)/$(APP_NAME) - $(MBED_TLS_MAKE_CMD) clean diff --git a/samples/linux/shadow_sample_console_echo/aws_iot_config.h b/samples/linux/shadow_sample_console_echo/aws_iot_config.h deleted file mode 100644 index 8af3dd9697..0000000000 --- a/samples/linux/shadow_sample_console_echo/aws_iot_config.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_config.h - * @brief AWS IoT specific configuration file - */ - -#ifndef SRC_SHADOW_IOT_SHADOW_CONFIG_H_ -#define SRC_SHADOW_IOT_SHADOW_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow -#define AWS_IOT_MQTT_PORT 443 ///< default port for MQTT/S -#define AWS_IOT_MQTT_CLIENT_ID "c-sdk-client-id" ///< MQTT client ID should be unique for every device -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" ///< Root CA file name -#define AWS_IOT_CERTIFICATE_FILENAME "cert.pem" ///< device signed certificate file name -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" ///< Device private key filename -// ================================================= - -// MQTT PubSub -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER (AWS_IOT_MQTT_RX_BUF_LEN+1) ///< Maximum size of the SHADOW buffer to store the received Shadow message, including terminating NULL byte. -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SIZE_OF_THING_NAME 20 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#define DISABLE_METRICS false ///< Disable the collection of metrics by setting this to true - -#endif /* SRC_SHADOW_IOT_SHADOW_CONFIG_H_ */ diff --git a/samples/linux/shadow_sample_console_echo/shadow_console_echo.c b/samples/linux/shadow_sample_console_echo/shadow_console_echo.c deleted file mode 100644 index 181c8d77a6..0000000000 --- a/samples/linux/shadow_sample_console_echo/shadow_console_echo.c +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -#include -#include -#include -#include -#include -#include - -#include "aws_iot_config.h" -#include "aws_iot_log.h" -#include "aws_iot_version.h" -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_shadow_interface.h" - -/** - * @file shadow_console_echo.c - * @brief Echo received Delta message - * - * This application will echo the message received in delta, as reported. - * for example: - * Received Delta message - * { - * "state": { - * "switch": "on" - * } - * } - * This delta message means the desired switch position has changed to "on" - * - * This application will take this delta message and publish it back as the reported message from the device. - * { - * "state": { - * "reported": { - * "switch": "on" - * } - * } - * } - * - * This update message will remove the delta that was created. If this message was not removed then the AWS IoT Thing Shadow is going to always have a delta and keep sending delta any time an update is applied to the Shadow - * This example will not use any of the json builder/helper functions provided in the aws_iot_shadow_json_data.h. - * @note Ensure the buffer sizes in aws_iot_config.h are big enough to receive the delta message. The delta message will also contain the metadata with the timestamps - */ - -static char certDirectory[PATH_MAX + 1] = "../../../certs"; -#define HOST_ADDRESS_SIZE 255 -static char HostAddress[HOST_ADDRESS_SIZE] = AWS_IOT_MQTT_HOST; -static uint32_t port = AWS_IOT_MQTT_PORT; -static bool messageArrivedOnDelta = false; - -/* - * @note The delta message is always sent on the "state" key in the json - * @note Any time messages are bigger than AWS_IOT_MQTT_RX_BUF_LEN the underlying MQTT library will ignore it. The maximum size of the message that can be received is limited to the AWS_IOT_MQTT_RX_BUF_LEN - */ -static char stringToEchoDelta[SHADOW_MAX_SIZE_OF_RX_BUFFER]; - - -/** - * @brief This function builds a full Shadow expected JSON document by putting the data in the reported section - * - * @param pJsonDocument Buffer to be filled up with the JSON data - * @param maxSizeOfJsonDocument maximum size of the buffer that could be used to fill - * @param pReceivedDeltaData This is the data that will be embedded in the reported section of the JSON document - * @param lengthDelta Length of the data - */ -static bool buildJSONForReported(char *pJsonDocument, size_t maxSizeOfJsonDocument, const char *pReceivedDeltaData, uint32_t lengthDelta) { - int32_t ret; - - if (NULL == pJsonDocument) { - return false; - } - - char tempClientTokenBuffer[MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE]; - - if(aws_iot_fill_with_client_token(tempClientTokenBuffer, MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE) != SUCCESS){ - return false; - } - - ret = snprintf(pJsonDocument, maxSizeOfJsonDocument, "{\"state\":{\"reported\":%.*s}, \"clientToken\":\"%s\"}", lengthDelta, pReceivedDeltaData, tempClientTokenBuffer); - - if (ret >= maxSizeOfJsonDocument || ret < 0) { - return false; - } - - return true; -} - -// Helper functions -static void parseInputArgsForConnectParams(int argc, char** argv) { - int opt; - - while (-1 != (opt = getopt(argc, argv, "h:p:c:"))) { - switch (opt) { - case 'h': - strncpy(HostAddress, optarg, HOST_ADDRESS_SIZE); - IOT_DEBUG("Host %s", optarg); - break; - case 'p': - port = atoi(optarg); - IOT_DEBUG("arg %s", optarg); - break; - case 'c': - strncpy(certDirectory, optarg, PATH_MAX + 1); - IOT_DEBUG("cert root directory %s", optarg); - break; - case '?': - if (optopt == 'c') { - IOT_ERROR("Option -%c requires an argument.", optopt); - } else if (isprint(optopt)) { - IOT_WARN("Unknown option `-%c'.", optopt); - } else { - IOT_WARN("Unknown option character `\\x%x'.", optopt); - } - break; - default: - IOT_ERROR("ERROR in command line argument parsing"); - break; - } - } - -} - -// Shadow Callback for receiving the delta -static void DeltaCallback(const char *pJsonValueBuffer, uint32_t valueLength, jsonStruct_t *pJsonStruct_t) { - IOT_UNUSED(pJsonStruct_t); - - IOT_DEBUG("Received Delta message %.*s", valueLength, pJsonValueBuffer); - - if (buildJSONForReported(stringToEchoDelta, SHADOW_MAX_SIZE_OF_RX_BUFFER, pJsonValueBuffer, valueLength)) { - messageArrivedOnDelta = true; - } -} - -static void UpdateStatusCallback(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, - const char *pReceivedJsonDocument, void *pContextData) { - IOT_UNUSED(pThingName); - IOT_UNUSED(action); - IOT_UNUSED(pReceivedJsonDocument); - IOT_UNUSED(pContextData); - - if(SHADOW_ACK_TIMEOUT == status) { - IOT_INFO("Update Timeout--"); - } else if(SHADOW_ACK_REJECTED == status) { - IOT_INFO("Update RejectedXX"); - } else if(SHADOW_ACK_ACCEPTED == status) { - IOT_INFO("Update Accepted !!"); - } -} - -int main(int argc, char** argv) { - IoT_Error_t rc = SUCCESS; - - char rootCA[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - - IOT_INFO("\nAWS IoT SDK Version %d.%d.%d-%s\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG); - - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(rootCA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - - IOT_DEBUG("rootCA %s", rootCA); - IOT_DEBUG("clientCRT %s", clientCRT); - IOT_DEBUG("clientKey %s", clientKey); - - parseInputArgsForConnectParams(argc, argv); - - // initialize the mqtt client - AWS_IoT_Client mqttClient; - - ShadowInitParameters_t sp = ShadowInitParametersDefault; - sp.pHost = AWS_IOT_MQTT_HOST; - sp.port = AWS_IOT_MQTT_PORT; - sp.pClientCRT = clientCRT; - sp.pClientKey = clientKey; - sp.pRootCA = rootCA; - sp.enableAutoReconnect = false; - sp.disconnectHandler = NULL; - - IOT_INFO("Shadow Init"); - rc = aws_iot_shadow_init(&mqttClient, &sp); - if (SUCCESS != rc) { - IOT_ERROR("Shadow Connection Error"); - return rc; - } - - ShadowConnectParameters_t scp = ShadowConnectParametersDefault; - scp.pMyThingName = AWS_IOT_MY_THING_NAME; - scp.pMqttClientId = AWS_IOT_MQTT_CLIENT_ID; - scp.mqttClientIdLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - - IOT_INFO("Shadow Connect"); - rc = aws_iot_shadow_connect(&mqttClient, &scp); - if (SUCCESS != rc) { - IOT_ERROR("Shadow Connection Error"); - return rc; - } - - /* - * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h - * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL - * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL - */ - rc = aws_iot_shadow_set_autoreconnect_status(&mqttClient, true); - if(SUCCESS != rc){ - IOT_ERROR("Unable to set Auto Reconnect to true - %d", rc); - return rc; - } - - jsonStruct_t deltaObject; - deltaObject.pData = stringToEchoDelta; - deltaObject.dataLength = SHADOW_MAX_SIZE_OF_RX_BUFFER; - deltaObject.pKey = "state"; - deltaObject.type = SHADOW_JSON_OBJECT; - deltaObject.cb = DeltaCallback; - - /* - * Register the jsonStruct object - */ - rc = aws_iot_shadow_register_delta(&mqttClient, &deltaObject); - - // Now wait in the loop to receive any message sent from the console - while (NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc) { - /* - * Lets check for the incoming messages for 200 ms. - */ - rc = aws_iot_shadow_yield(&mqttClient, 200); - - if (NETWORK_ATTEMPTING_RECONNECT == rc) { - sleep(1); - // If the client is attempting to reconnect we will skip the rest of the loop. - continue; - } - - if (messageArrivedOnDelta) { - IOT_INFO("\nSending delta message back %s\n", stringToEchoDelta); - rc = aws_iot_shadow_update(&mqttClient, AWS_IOT_MY_THING_NAME, stringToEchoDelta, UpdateStatusCallback, NULL, 2, true); - messageArrivedOnDelta = false; - } - - // sleep for some time in seconds - sleep(1); - } - - if (SUCCESS != rc) { - IOT_ERROR("An error occurred in the loop %d", rc); - } - - IOT_INFO("Disconnecting"); - rc = aws_iot_shadow_disconnect(&mqttClient); - - if (SUCCESS != rc) { - IOT_ERROR("Disconnect error %d", rc); - } - - return rc; -} diff --git a/samples/linux/subscribe_publish_library_sample/Makefile b/samples/linux/subscribe_publish_library_sample/Makefile deleted file mode 100644 index bad284924e..0000000000 --- a/samples/linux/subscribe_publish_library_sample/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -CC = gcc - -#remove @ for no make command prints -DEBUG = @ - -APP_DIR = . -APP_INCLUDE_DIRS += -I $(APP_DIR) -APP_NAME = subscribe_publish_library_sample -APP_SRC_FILES = $(APP_NAME).c - -#IoT client directory -IOT_CLIENT_DIR = ../../.. - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux/mbedtls -PLATFORM_COMMON_DIR = $(IOT_CLIENT_DIR)/platform/linux/common - -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn -IOT_INCLUDE_DIRS += -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_DIR) - -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') - -#TLS - mbedtls -MBEDTLS_DIR = $(IOT_CLIENT_DIR)/external_libs/mbedTLS -TLS_LIB_DIR = $(MBEDTLS_DIR)/library -TLS_INCLUDE_DIR = -I $(MBEDTLS_DIR)/include -EXTERNAL_LIBS += -L$(TLS_LIB_DIR) -LD_FLAG += -Wl,-rpath,$(TLS_LIB_DIR) -LD_FLAG += -ldl $(TLS_LIB_DIR)/libmbedtls.a $(TLS_LIB_DIR)/libmbedcrypto.a $(TLS_LIB_DIR)/libmbedx509.a -lpthread - -#Aggregate all include and src directories -INCLUDE_ALL_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(TLS_INCLUDE_DIR) -INCLUDE_ALL_DIRS += $(APP_INCLUDE_DIRS) - -SRC_FILES += $(IOT_SRC_FILES) - -# Logging level control -LOG_FLAGS += -DENABLE_IOT_DEBUG -LOG_FLAGS += -DENABLE_IOT_INFO -LOG_FLAGS += -DENABLE_IOT_WARN -LOG_FLAGS += -DENABLE_IOT_ERROR - -COMPILER_FLAGS += $(LOG_FLAGS) -#If the processor is big endian uncomment the compiler flag -#COMPILER_FLAGS += -DREVERSED - -MBED_TLS_MAKE_CMD = $(MAKE) -C $(MBEDTLS_DIR) - -PRE_MAKE_CMD = $(MBED_TLS_MAKE_CMD) -MAKE_CMD = $(CC) $(APP_NAME).c $(COMPILER_FLAGS) -o $(APP_NAME) -L. -lAwsIotSdk $(LD_FLAG) $(INCLUDE_ALL_DIRS) - -all: libAwsIotSdk.a - $(PRE_MAKE_CMD) - $(DEBUG)$(MAKE_CMD) - $(POST_MAKE_CMD) - -libAwsIotSdk.a: $(SRC_FILES:.c=.o) - ar rcs $@ $^ - -%.o : %.c - $(CC) -c $< -o $@ $(COMPILER_FLAGS) $(EXTERNAL_LIBS) $(INCLUDE_ALL_DIRS) - -clean: - rm -f $(APP_DIR)/$(APP_NAME) - rm -f $(APP_DIR)/libAwsIotSdk.a - $(MBED_TLS_MAKE_CMD) clean diff --git a/samples/linux/subscribe_publish_library_sample/aws_iot_config.h b/samples/linux/subscribe_publish_library_sample/aws_iot_config.h deleted file mode 100644 index 8af3dd9697..0000000000 --- a/samples/linux/subscribe_publish_library_sample/aws_iot_config.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_config.h - * @brief AWS IoT specific configuration file - */ - -#ifndef SRC_SHADOW_IOT_SHADOW_CONFIG_H_ -#define SRC_SHADOW_IOT_SHADOW_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow -#define AWS_IOT_MQTT_PORT 443 ///< default port for MQTT/S -#define AWS_IOT_MQTT_CLIENT_ID "c-sdk-client-id" ///< MQTT client ID should be unique for every device -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" ///< Root CA file name -#define AWS_IOT_CERTIFICATE_FILENAME "cert.pem" ///< device signed certificate file name -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" ///< Device private key filename -// ================================================= - -// MQTT PubSub -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER (AWS_IOT_MQTT_RX_BUF_LEN+1) ///< Maximum size of the SHADOW buffer to store the received Shadow message, including terminating NULL byte. -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SIZE_OF_THING_NAME 20 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#define DISABLE_METRICS false ///< Disable the collection of metrics by setting this to true - -#endif /* SRC_SHADOW_IOT_SHADOW_CONFIG_H_ */ diff --git a/samples/linux/subscribe_publish_library_sample/subscribe_publish_library_sample.c b/samples/linux/subscribe_publish_library_sample/subscribe_publish_library_sample.c deleted file mode 100644 index 0c3e775e97..0000000000 --- a/samples/linux/subscribe_publish_library_sample/subscribe_publish_library_sample.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file subscribe_publish_library_sample.c - * @brief simple MQTT publish and subscribe on the same topic using the SDK as a library - * - * This example takes the parameters from the aws_iot_config.h file and establishes a connection to the AWS IoT MQTT Platform. - * It subscribes and publishes to the same topic - "sdkTest/sub" - * - * If all the certs are correct, you should see the messages received by the application in a loop. - * - * The application takes in the certificate path, host name , port and the number of times the publish should happen. - * - */ -#include -#include -#include -#include -#include -#include - -#include "aws_iot_config.h" -#include "aws_iot_log.h" -#include "aws_iot_version.h" -#include "aws_iot_mqtt_client_interface.h" - -#define HOST_ADDRESS_SIZE 255 - -/** - * @brief Default cert location - */ -static char certDirectory[PATH_MAX + 1] = "../../../certs"; - -/** - * @brief Default MQTT HOST URL is pulled from the aws_iot_config.h - */ -static char HostAddress[HOST_ADDRESS_SIZE] = AWS_IOT_MQTT_HOST; - -/** - * @brief Default MQTT port is pulled from the aws_iot_config.h - */ -static uint32_t port = AWS_IOT_MQTT_PORT; - -/** - * @brief This parameter will avoid infinite loop of publish and exit the program after certain number of publishes - */ -uint32_t publishCount = 0; - -static void iot_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("Subscribe callback"); - IOT_INFO("%.*s\t%.*s", topicNameLen, topicName, (int) params->payloadLen, (char *) params->payload); -} - -static void disconnectCallbackHandler(AWS_IoT_Client *pClient, void *data) { - IOT_WARN("MQTT Disconnect"); - IoT_Error_t rc = FAILURE; - - if(NULL == pClient) { - return; - } - - IOT_UNUSED(data); - - if(aws_iot_is_autoreconnect_enabled(pClient)) { - IOT_INFO("Auto Reconnect is enabled, Reconnecting attempt will start now"); - } else { - IOT_WARN("Auto Reconnect not enabled. Starting manual reconnect..."); - rc = aws_iot_mqtt_attempt_reconnect(pClient); - if(NETWORK_RECONNECTED == rc) { - IOT_WARN("Manual Reconnect Successful"); - } else { - IOT_WARN("Manual Reconnect Failed - %d", rc); - } - } -} - -static void parseInputArgsForConnectParams(int argc, char **argv) { - int opt; - - while(-1 != (opt = getopt(argc, argv, "h:p:c:x:"))) { - switch(opt) { - case 'h': - strncpy(HostAddress, optarg, HOST_ADDRESS_SIZE); - IOT_DEBUG("Host %s", optarg); - break; - case 'p': - port = atoi(optarg); - IOT_DEBUG("arg %s", optarg); - break; - case 'c': - strncpy(certDirectory, optarg, PATH_MAX + 1); - IOT_DEBUG("cert root directory %s", optarg); - break; - case 'x': - publishCount = atoi(optarg); - IOT_DEBUG("publish %s times\n", optarg); - break; - case '?': - if(optopt == 'c') { - IOT_ERROR("Option -%c requires an argument.", optopt); - } else if(isprint(optopt)) { - IOT_WARN("Unknown option `-%c'.", optopt); - } else { - IOT_WARN("Unknown option character `\\x%x'.", optopt); - } - break; - default: - IOT_ERROR("Error in command line argument parsing"); - break; - } - } - -} - -int main(int argc, char **argv) { - bool infinitePublishFlag = true; - - char rootCA[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - char cPayload[100]; - - int32_t i = 0; - - IoT_Error_t rc = FAILURE; - - AWS_IoT_Client client; - IoT_Client_Init_Params mqttInitParams = iotClientInitParamsDefault; - IoT_Client_Connect_Params connectParams = iotClientConnectParamsDefault; - - IoT_Publish_Message_Params paramsQOS0; - IoT_Publish_Message_Params paramsQOS1; - - parseInputArgsForConnectParams(argc, argv); - - IOT_INFO("\nAWS IoT SDK Version %d.%d.%d-%s\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG); - - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(rootCA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - - IOT_DEBUG("rootCA %s", rootCA); - IOT_DEBUG("clientCRT %s", clientCRT); - IOT_DEBUG("clientKey %s", clientKey); - mqttInitParams.enableAutoReconnect = false; // We enable this later below - mqttInitParams.pHostURL = HostAddress; - mqttInitParams.port = port; - mqttInitParams.pRootCALocation = rootCA; - mqttInitParams.pDeviceCertLocation = clientCRT; - mqttInitParams.pDevicePrivateKeyLocation = clientKey; - mqttInitParams.mqttCommandTimeout_ms = 20000; - mqttInitParams.tlsHandshakeTimeout_ms = 5000; - mqttInitParams.isSSLHostnameVerify = true; - mqttInitParams.disconnectHandler = disconnectCallbackHandler; - mqttInitParams.disconnectHandlerData = NULL; - - rc = aws_iot_mqtt_init(&client, &mqttInitParams); - if(SUCCESS != rc) { - IOT_ERROR("aws_iot_mqtt_init returned error : %d ", rc); - return rc; - } - - connectParams.keepAliveIntervalInSec = 600; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = AWS_IOT_MQTT_CLIENT_ID; - connectParams.clientIDLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - connectParams.isWillMsgPresent = false; - - IOT_INFO("Connecting..."); - rc = aws_iot_mqtt_connect(&client, &connectParams); - if(SUCCESS != rc) { - IOT_ERROR("Error(%d) connecting to %s:%d", rc, mqttInitParams.pHostURL, mqttInitParams.port); - return rc; - } - /* - * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h - * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL - * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL - */ - rc = aws_iot_mqtt_autoreconnect_set_status(&client, true); - if(SUCCESS != rc) { - IOT_ERROR("Unable to set Auto Reconnect to true - %d", rc); - return rc; - } - - IOT_INFO("Subscribing..."); - rc = aws_iot_mqtt_subscribe(&client, "sdkTest/sub", 11, QOS0, iot_subscribe_callback_handler, NULL); - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing : %d ", rc); - return rc; - } - - sprintf(cPayload, "%s : %d ", "hello from SDK", i); - - paramsQOS0.qos = QOS0; - paramsQOS0.payload = (void *) cPayload; - paramsQOS0.isRetained = 0; - - paramsQOS1.qos = QOS1; - paramsQOS1.payload = (void *) cPayload; - paramsQOS1.isRetained = 0; - - if(publishCount != 0) { - infinitePublishFlag = false; - } - - while((NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc) - && (publishCount > 0 || infinitePublishFlag)) { - - //Max time the yield function will wait for read messages - rc = aws_iot_mqtt_yield(&client, 100); - if(NETWORK_ATTEMPTING_RECONNECT == rc) { - // If the client is attempting to reconnect we will skip the rest of the loop. - continue; - } - - IOT_INFO("-->sleep"); - sleep(1); - sprintf(cPayload, "%s : %d ", "hello from SDK QOS0", i++); - paramsQOS0.payloadLen = strlen(cPayload); - rc = aws_iot_mqtt_publish(&client, "sdkTest/sub", 11, ¶msQOS0); - if(publishCount > 0) { - publishCount--; - } - - sprintf(cPayload, "%s : %d ", "hello from SDK QOS1", i++); - paramsQOS1.payloadLen = strlen(cPayload); - rc = aws_iot_mqtt_publish(&client, "sdkTest/sub", 11, ¶msQOS1); - if (rc == MQTT_REQUEST_TIMEOUT_ERROR) { - IOT_WARN("QOS1 publish ack not received.\n"); - rc = SUCCESS; - } - if(publishCount > 0) { - publishCount--; - } - } - - if(SUCCESS != rc) { - IOT_ERROR("An error occurred in the loop.\n"); - } else { - IOT_INFO("Publish done\n"); - } - - return rc; -} diff --git a/samples/linux/subscribe_publish_sample/Makefile b/samples/linux/subscribe_publish_sample/Makefile deleted file mode 100644 index 4b32391064..0000000000 --- a/samples/linux/subscribe_publish_sample/Makefile +++ /dev/null @@ -1,69 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -CC = gcc - -#remove @ for no make command prints -DEBUG = @ - -APP_DIR = . -APP_INCLUDE_DIRS += -I $(APP_DIR) -APP_NAME = subscribe_publish_sample -APP_SRC_FILES = $(APP_NAME).c - -#IoT client directory -IOT_CLIENT_DIR = ../../.. - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux/mbedtls -PLATFORM_COMMON_DIR = $(IOT_CLIENT_DIR)/platform/linux/common - -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn -IOT_INCLUDE_DIRS += -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_DIR) - -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') - -#TLS - mbedtls -MBEDTLS_DIR = $(IOT_CLIENT_DIR)/external_libs/mbedTLS -TLS_LIB_DIR = $(MBEDTLS_DIR)/library -TLS_INCLUDE_DIR = -I $(MBEDTLS_DIR)/include -EXTERNAL_LIBS += -L$(TLS_LIB_DIR) -LD_FLAG += -Wl,-rpath,$(TLS_LIB_DIR) -LD_FLAG += -ldl $(TLS_LIB_DIR)/libmbedtls.a $(TLS_LIB_DIR)/libmbedcrypto.a $(TLS_LIB_DIR)/libmbedx509.a -lpthread - -#Aggregate all include and src directories -INCLUDE_ALL_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(TLS_INCLUDE_DIR) -INCLUDE_ALL_DIRS += $(APP_INCLUDE_DIRS) - -SRC_FILES += $(APP_SRC_FILES) -SRC_FILES += $(IOT_SRC_FILES) - -# Logging level control -LOG_FLAGS += -DENABLE_IOT_DEBUG -LOG_FLAGS += -DENABLE_IOT_INFO -LOG_FLAGS += -DENABLE_IOT_WARN -LOG_FLAGS += -DENABLE_IOT_ERROR - -COMPILER_FLAGS += $(LOG_FLAGS) -#If the processor is big endian uncomment the compiler flag -#COMPILER_FLAGS += -DREVERSED - -MBED_TLS_MAKE_CMD = $(MAKE) -C $(MBEDTLS_DIR) - -PRE_MAKE_CMD = $(MBED_TLS_MAKE_CMD) -MAKE_CMD = $(CC) $(SRC_FILES) $(COMPILER_FLAGS) -o $(APP_NAME) $(LD_FLAG) $(EXTERNAL_LIBS) $(INCLUDE_ALL_DIRS) - -all: - $(PRE_MAKE_CMD) - $(DEBUG)$(MAKE_CMD) - $(POST_MAKE_CMD) - -clean: - rm -f $(APP_DIR)/$(APP_NAME) - $(MBED_TLS_MAKE_CMD) clean diff --git a/samples/linux/subscribe_publish_sample/aws_iot_config.h b/samples/linux/subscribe_publish_sample/aws_iot_config.h deleted file mode 100644 index 8af3dd9697..0000000000 --- a/samples/linux/subscribe_publish_sample/aws_iot_config.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_config.h - * @brief AWS IoT specific configuration file - */ - -#ifndef SRC_SHADOW_IOT_SHADOW_CONFIG_H_ -#define SRC_SHADOW_IOT_SHADOW_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow -#define AWS_IOT_MQTT_PORT 443 ///< default port for MQTT/S -#define AWS_IOT_MQTT_CLIENT_ID "c-sdk-client-id" ///< MQTT client ID should be unique for every device -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" ///< Root CA file name -#define AWS_IOT_CERTIFICATE_FILENAME "cert.pem" ///< device signed certificate file name -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" ///< Device private key filename -// ================================================= - -// MQTT PubSub -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER (AWS_IOT_MQTT_RX_BUF_LEN+1) ///< Maximum size of the SHADOW buffer to store the received Shadow message, including terminating NULL byte. -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SIZE_OF_THING_NAME 20 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#define DISABLE_METRICS false ///< Disable the collection of metrics by setting this to true - -#endif /* SRC_SHADOW_IOT_SHADOW_CONFIG_H_ */ diff --git a/samples/linux/subscribe_publish_sample/subscribe_publish_sample.c b/samples/linux/subscribe_publish_sample/subscribe_publish_sample.c deleted file mode 100644 index ce7a28f9c4..0000000000 --- a/samples/linux/subscribe_publish_sample/subscribe_publish_sample.c +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file subscribe_publish_sample.c - * @brief simple MQTT publish and subscribe on the same topic - * - * This example takes the parameters from the aws_iot_config.h file and establishes a connection to the AWS IoT MQTT Platform. - * It subscribes and publishes to the same topic - "sdkTest/sub" - * - * If all the certs are correct, you should see the messages received by the application in a loop. - * - * The application takes in the certificate path, host name , port and the number of times the publish should happen. - * - */ -#include -#include -#include -#include -#include -#include - -#include "aws_iot_config.h" -#include "aws_iot_log.h" -#include "aws_iot_version.h" -#include "aws_iot_mqtt_client_interface.h" - -#define HOST_ADDRESS_SIZE 255 -/** - * @brief Default cert location - */ -static char certDirectory[PATH_MAX + 1] = "../../../certs"; - -/** - * @brief Default MQTT HOST URL is pulled from the aws_iot_config.h - */ -static char HostAddress[HOST_ADDRESS_SIZE] = AWS_IOT_MQTT_HOST; - -/** - * @brief Default MQTT port is pulled from the aws_iot_config.h - */ -static uint32_t port = AWS_IOT_MQTT_PORT; - -/** - * @brief This parameter will avoid infinite loop of publish and exit the program after certain number of publishes - */ -static uint32_t publishCount = 0; - -static void iot_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("Subscribe callback"); - IOT_INFO("%.*s\t%.*s", topicNameLen, topicName, (int) params->payloadLen, (char *) params->payload); -} - -static void disconnectCallbackHandler(AWS_IoT_Client *pClient, void *data) { - IOT_WARN("MQTT Disconnect"); - IoT_Error_t rc = FAILURE; - - if(NULL == pClient) { - return; - } - - IOT_UNUSED(data); - - if(aws_iot_is_autoreconnect_enabled(pClient)) { - IOT_INFO("Auto Reconnect is enabled, Reconnecting attempt will start now"); - } else { - IOT_WARN("Auto Reconnect not enabled. Starting manual reconnect..."); - rc = aws_iot_mqtt_attempt_reconnect(pClient); - if(NETWORK_RECONNECTED == rc) { - IOT_WARN("Manual Reconnect Successful"); - } else { - IOT_WARN("Manual Reconnect Failed - %d", rc); - } - } -} - -static void parseInputArgsForConnectParams(int argc, char **argv) { - int opt; - - while(-1 != (opt = getopt(argc, argv, "h:p:c:x:"))) { - switch(opt) { - case 'h': - strncpy(HostAddress, optarg, HOST_ADDRESS_SIZE); - IOT_DEBUG("Host %s", optarg); - break; - case 'p': - port = atoi(optarg); - IOT_DEBUG("arg %s", optarg); - break; - case 'c': - strncpy(certDirectory, optarg, PATH_MAX + 1); - IOT_DEBUG("cert root directory %s", optarg); - break; - case 'x': - publishCount = atoi(optarg); - IOT_DEBUG("publish %s times\n", optarg); - break; - case '?': - if(optopt == 'c') { - IOT_ERROR("Option -%c requires an argument.", optopt); - } else if(isprint(optopt)) { - IOT_WARN("Unknown option `-%c'.", optopt); - } else { - IOT_WARN("Unknown option character `\\x%x'.", optopt); - } - break; - default: - IOT_ERROR("Error in command line argument parsing"); - break; - } - } - -} - -int main(int argc, char **argv) { - bool infinitePublishFlag = true; - - char rootCA[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - char cPayload[100]; - - int32_t i = 0; - - IoT_Error_t rc = FAILURE; - - AWS_IoT_Client client; - IoT_Client_Init_Params mqttInitParams = iotClientInitParamsDefault; - IoT_Client_Connect_Params connectParams = iotClientConnectParamsDefault; - - IoT_Publish_Message_Params paramsQOS0; - IoT_Publish_Message_Params paramsQOS1; - - parseInputArgsForConnectParams(argc, argv); - - IOT_INFO("\nAWS IoT SDK Version %d.%d.%d-%s\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG); - - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(rootCA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - - IOT_DEBUG("rootCA %s", rootCA); - IOT_DEBUG("clientCRT %s", clientCRT); - IOT_DEBUG("clientKey %s", clientKey); - mqttInitParams.enableAutoReconnect = false; // We enable this later below - mqttInitParams.pHostURL = HostAddress; - mqttInitParams.port = port; - mqttInitParams.pRootCALocation = rootCA; - mqttInitParams.pDeviceCertLocation = clientCRT; - mqttInitParams.pDevicePrivateKeyLocation = clientKey; - mqttInitParams.mqttCommandTimeout_ms = 20000; - mqttInitParams.tlsHandshakeTimeout_ms = 5000; - mqttInitParams.isSSLHostnameVerify = true; - mqttInitParams.disconnectHandler = disconnectCallbackHandler; - mqttInitParams.disconnectHandlerData = NULL; - - rc = aws_iot_mqtt_init(&client, &mqttInitParams); - if(SUCCESS != rc) { - IOT_ERROR("aws_iot_mqtt_init returned error : %d ", rc); - return rc; - } - - connectParams.keepAliveIntervalInSec = 600; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = AWS_IOT_MQTT_CLIENT_ID; - connectParams.clientIDLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - connectParams.isWillMsgPresent = false; - - IOT_INFO("Connecting..."); - rc = aws_iot_mqtt_connect(&client, &connectParams); - if(SUCCESS != rc) { - IOT_ERROR("Error(%d) connecting to %s:%d", rc, mqttInitParams.pHostURL, mqttInitParams.port); - return rc; - } - /* - * Enable Auto Reconnect functionality. Minimum and Maximum time of Exponential backoff are set in aws_iot_config.h - * #AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL - * #AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL - */ - rc = aws_iot_mqtt_autoreconnect_set_status(&client, true); - if(SUCCESS != rc) { - IOT_ERROR("Unable to set Auto Reconnect to true - %d", rc); - return rc; - } - - IOT_INFO("Subscribing..."); - rc = aws_iot_mqtt_subscribe(&client, "sdkTest/sub", 11, QOS0, iot_subscribe_callback_handler, NULL); - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing : %d ", rc); - return rc; - } - - sprintf(cPayload, "%s : %d ", "hello from SDK", i); - - paramsQOS0.qos = QOS0; - paramsQOS0.payload = (void *) cPayload; - paramsQOS0.isRetained = 0; - - paramsQOS1.qos = QOS1; - paramsQOS1.payload = (void *) cPayload; - paramsQOS1.isRetained = 0; - - if(publishCount != 0) { - infinitePublishFlag = false; - } - - while((NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc) - && (publishCount > 0 || infinitePublishFlag)) { - - //Max time the yield function will wait for read messages - rc = aws_iot_mqtt_yield(&client, 100); - if(NETWORK_ATTEMPTING_RECONNECT == rc) { - // If the client is attempting to reconnect we will skip the rest of the loop. - continue; - } - - IOT_INFO("-->sleep"); - sleep(1); - sprintf(cPayload, "%s : %d ", "hello from SDK QOS0", i++); - paramsQOS0.payloadLen = strlen(cPayload); - rc = aws_iot_mqtt_publish(&client, "sdkTest/sub", 11, ¶msQOS0); - if(publishCount > 0) { - publishCount--; - } - - if(publishCount == 0 && !infinitePublishFlag) { - break; - } - - sprintf(cPayload, "%s : %d ", "hello from SDK QOS1", i++); - paramsQOS1.payloadLen = strlen(cPayload); - rc = aws_iot_mqtt_publish(&client, "sdkTest/sub", 11, ¶msQOS1); - if (rc == MQTT_REQUEST_TIMEOUT_ERROR) { - IOT_WARN("QOS1 publish ack not received.\n"); - rc = SUCCESS; - } - if(publishCount > 0) { - publishCount--; - } - } - - // Wait for all the messages to be received - aws_iot_mqtt_yield(&client, 100); - - if(SUCCESS != rc) { - IOT_ERROR("An error occurred in the loop.\n"); - } else { - IOT_INFO("Publish done\n"); - } - - return rc; -} diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh new file mode 100644 index 0000000000..169729ef62 --- /dev/null +++ b/scripts/build_check_commit.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# Travis CI uses this script to check GitHub commits. It builds the +# demos and tests, then runs the demos and tests against AWS IoT with +# the appropriate credentials. + +# Exit on any nonzero return code. +set -e + +# Set Thing Name. +AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" + +# Create build directory. +mkdir -p build +cd build +rm -rf * + +# Create directory for credentials. +mkdir ../credentials + +# Download Amazon Root CA 1. +wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O ../credentials/AmazonRootCA1.pem + +# Write the client certificate and private key into files. +echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem +echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem + +# Build tests and demos against AWS IoT with ThreadSanitizer. +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -fsanitize=thread" +make + +# Run MQTT tests and demo against AWS IoT. +./bin/aws_iot_tests_mqtt +./bin/aws_iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" + +# Run Shadow tests and demo. +./bin/aws_iot_tests_shadow +./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" + +# Rebuild in static memory mode. +rm -rf * +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_STATIC_MEMORY_ONLY=1 -fsanitize=thread" +make + +# Run MQTT and Shadow tests in static memory mode. +./bin/aws_iot_tests_mqtt +./bin/aws_iot_tests_shadow diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh new file mode 100644 index 0000000000..30d09b0693 --- /dev/null +++ b/scripts/build_check_pr.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# Travis CI uses this script to check GitHub pull requests. It builds the +# demos and tests, then runs the demos and tests that require no credentials. + +# Exit on any nonzero return code. +set -e + +# Set Thing Name. +AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" + +# Create build directory. +mkdir -p build +cd build +rm -rf * + +# Build tests and demos against Mosquitto broker with ThreadSanitizer. +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -fsanitize=thread" +make + +# Run MQTT tests and demos against Mosquitto. +./bin/aws_iot_tests_mqtt +./bin/aws_iot_demo_mqtt -h test.mosquitto.org -p 1883 -n + +# Run the Shadow tests that do not require the network. +./bin/aws_iot_tests_shadow -n + +# Rebuild in static memory mode. +rm -rf * +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DAWS_IOT_STATIC_MEMORY_ONLY=1 -fsanitize=thread" +make + +# Run MQTT tests and no-network Shadow tests in static memory mode. +./bin/aws_iot_tests_mqtt +./bin/aws_iot_tests_shadow -n diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100644 index 0000000000..e30345c95f --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +# Travis CI uses this script to build an submit code coverage results. + +# Exit on any nonzero return code. +set -e + +# Set Thing Name. +AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" + +# Delete everything in the build directory. +mkdir -p build +cd build +rm -rf * + +# Build tests and demos against AWS IoT with gcov. +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO --coverage" +make + +# Run MQTT tests and demo against AWS IoT with code coverage. +./bin/aws_iot_tests_mqtt +./bin/aws_iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" + +# Run Shadow tests and demo with code coverage. +./bin/aws_iot_tests_shadow +./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" + +# Generate code coverage results, excluding Unity test framework. +lcov --directory . --capture --output-file coverage.info +lcov --remove coverage.info '*unity*' --output-file coverage.info + +# Submit the code coverage results. +cd .. +coveralls --lcov-file build/coverage.info diff --git a/scripts/generate_doc.sh b/scripts/generate_doc.sh new file mode 100644 index 0000000000..9cd5f13e53 --- /dev/null +++ b/scripts/generate_doc.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +# Check for doxygen. +command -v doxygen > /dev/null || { echo "Doxygen not found. Exiting."; exit 1; } + +if [ $# -ne 1 ]; then + echo "Usage: ./generate_doc.sh sdk_root_directory" + exit 1; +fi + +# Change to SDK root directory. +cd $1 + +# Create tag directory if needed. +mkdir -p doc/tag + +# Doxygen must be run twice: once to generate tags and once more to link tags. +i=0; while [ $i -le 1 ]; do + # Run for each library config file. + for file in doc/config/*; do + # Ignore directories. + if [ -d $file ]; then + continue + fi + + # Ignore xml files. + if [ ${file##*.} = "xml" ]; then + continue + fi + + # Ignore the common configuration file. + if [ $file = "doc/config/common" ]; then + continue + fi + + # Generate Doxygen tags first. Once tags are generated, generate documentation. + if [ $i -eq 0 ]; then + echo "Generating Doxygen tags for $file..." + doxygen $file 2> /dev/null + else + echo "Generating documentation for $file..." + + # Redirect errors to file when running under Travis CI. + if [ -z "$TRAVIS" ]; then + doxygen $file + else + doxygen $file 2>>doxygen_warnings.txt + fi + fi + done + + i=$(($i+1)); +done + +echo "Documentation written to doc/output" + +# Print any doxygen errors or warnings and exit with a nonzero value. +if [ -s doxygen_warnings.txt ]; then + cat doxygen_warnings.txt + exit 1; +fi diff --git a/src/aws_iot_jobs_interface.c b/src/aws_iot_jobs_interface.c deleted file mode 100644 index 0452d67861..0000000000 --- a/src/aws_iot_jobs_interface.c +++ /dev/null @@ -1,215 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#include "aws_iot_jobs_interface.h" -#include "aws_iot_log.h" -#include "aws_iot_jobs_json.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define CHECK_GENERATE_STRING_RESULT(result, bufferSize) \ - if (result < 0) { \ - return FAILURE; \ - } else if ((unsigned) result >= bufferSize) { \ - return LIMIT_EXCEEDED_ERROR; \ - } - - -IoT_Error_t aws_iot_jobs_subscribe_to_job_messages( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - AwsIotJobExecutionTopicType topicType, - AwsIotJobExecutionTopicReplyType replyType, - pApplicationHandler_t pApplicationHandler, - void *pApplicationHandlerData, - char *topicBuffer, - uint16_t topicBufferSize) -{ - int requiredSize = aws_iot_jobs_get_api_topic(topicBuffer, topicBufferSize, topicType, replyType, thingName, jobId); - CHECK_GENERATE_STRING_RESULT(requiredSize, topicBufferSize); - - return aws_iot_mqtt_subscribe(pClient, topicBuffer, (uint16_t)strlen(topicBuffer), qos, pApplicationHandler, pApplicationHandlerData); -} - -IoT_Error_t aws_iot_jobs_subscribe_to_all_job_messages( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - pApplicationHandler_t pApplicationHandler, - void *pApplicationHandlerData, - char *topicBuffer, - uint16_t topicBufferSize) -{ - return aws_iot_jobs_subscribe_to_job_messages(pClient, qos, thingName, NULL, JOB_WILDCARD_TOPIC, JOB_WILDCARD_REPLY_TYPE, - pApplicationHandler, pApplicationHandlerData, topicBuffer, topicBufferSize); -} - -IoT_Error_t aws_iot_jobs_unsubscribe_from_job_messages( - AWS_IoT_Client *pClient, - char *topicBuffer) -{ - return aws_iot_mqtt_unsubscribe(pClient, topicBuffer, (uint16_t)strlen(topicBuffer)); -} - -IoT_Error_t aws_iot_jobs_send_query( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - const char *clientToken, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize, - AwsIotJobExecutionTopicType topicType) -{ - if (thingName == NULL || topicBuffer == NULL || (clientToken != NULL && messageBuffer == NULL)) { - return NULL_VALUE_ERROR; - } - - int neededSize = aws_iot_jobs_get_api_topic(topicBuffer, topicBufferSize, topicType, JOB_REQUEST_TYPE, thingName, jobId); - CHECK_GENERATE_STRING_RESULT(neededSize, topicBufferSize); - uint16_t topicSize = (uint16_t) neededSize; - - char emptyBuffer[1]; - size_t messageLength; - if (clientToken == NULL) { - messageLength = 0; - messageBuffer = emptyBuffer; - } else { - int serializeResult = aws_iot_jobs_json_serialize_client_token_only_request(messageBuffer, messageBufferSize, clientToken); - CHECK_GENERATE_STRING_RESULT(serializeResult, messageBufferSize); - messageLength = (size_t)serializeResult; - } - - IoT_Publish_Message_Params publishParams; - publishParams.qos = qos; - publishParams.isRetained = 0; - publishParams.isDup = 0; - publishParams.id = 0; - publishParams.payload = messageBuffer; - publishParams.payloadLen = messageLength; - - return aws_iot_mqtt_publish(pClient, topicBuffer, topicSize, &publishParams); -} - -IoT_Error_t aws_iot_jobs_start_next( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const AwsIotStartNextPendingJobExecutionRequest *startNextRequest, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize) -{ - if (thingName == NULL || topicBuffer == NULL || startNextRequest == NULL) { - return NULL_VALUE_ERROR; - } - - int neededSize = aws_iot_jobs_get_api_topic(topicBuffer, topicBufferSize, JOB_START_NEXT_TOPIC, JOB_REQUEST_TYPE, thingName, NULL); - CHECK_GENERATE_STRING_RESULT(neededSize, topicBufferSize); - uint16_t topicSize = (uint16_t) neededSize; - - int serializeResult = aws_iot_jobs_json_serialize_start_next_job_execution_request(messageBuffer, messageBufferSize, startNextRequest); - CHECK_GENERATE_STRING_RESULT(serializeResult, messageBufferSize); - - IoT_Publish_Message_Params publishParams; - publishParams.qos = qos; - publishParams.isRetained = 0; - publishParams.isDup = 0; - publishParams.id = 0; - publishParams.payload = messageBuffer; - publishParams.payloadLen = (size_t) serializeResult; - - return aws_iot_mqtt_publish(pClient, topicBuffer, topicSize, &publishParams); -} - -IoT_Error_t aws_iot_jobs_describe( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - const AwsIotDescribeJobExecutionRequest *describeRequest, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize) -{ - if (thingName == NULL || topicBuffer == NULL || describeRequest == NULL) { - return NULL_VALUE_ERROR; - } - - int neededSize = aws_iot_jobs_get_api_topic(topicBuffer, topicBufferSize, JOB_DESCRIBE_TOPIC, JOB_REQUEST_TYPE, thingName, jobId); - CHECK_GENERATE_STRING_RESULT(neededSize, topicBufferSize); - uint16_t topicSize = (uint16_t) neededSize; - - char emptyBuffer[1]; - size_t messageLength; - if (messageBuffer == NULL) { - messageLength = 0; - messageBuffer = emptyBuffer; - } else { - int serializeResult = aws_iot_jobs_json_serialize_describe_job_execution_request(messageBuffer, messageBufferSize, describeRequest); - CHECK_GENERATE_STRING_RESULT(serializeResult, messageBufferSize); - messageLength = (size_t) serializeResult; - } - - IoT_Publish_Message_Params publishParams; - publishParams.qos = qos; - publishParams.isRetained = 0; - publishParams.isDup = 0; - publishParams.id = 0; - publishParams.payload = messageBuffer; - publishParams.payloadLen = messageLength; - - return aws_iot_mqtt_publish(pClient, topicBuffer, topicSize, &publishParams); -} - -IoT_Error_t aws_iot_jobs_send_update( - AWS_IoT_Client *pClient, QoS qos, - const char *thingName, - const char *jobId, - const AwsIotJobExecutionUpdateRequest *updateRequest, - char *topicBuffer, - uint16_t topicBufferSize, - char *messageBuffer, - size_t messageBufferSize) -{ - if (thingName == NULL || topicBuffer == NULL || jobId == NULL || updateRequest == NULL) { - return NULL_VALUE_ERROR; - } - - int neededSize = aws_iot_jobs_get_api_topic(topicBuffer, topicBufferSize, JOB_UPDATE_TOPIC, JOB_REQUEST_TYPE, thingName, jobId); - CHECK_GENERATE_STRING_RESULT(neededSize, topicBufferSize); - uint16_t topicSize = (uint16_t) neededSize; - - int serializeResult = aws_iot_jobs_json_serialize_update_job_execution_request(messageBuffer, messageBufferSize, updateRequest); - CHECK_GENERATE_STRING_RESULT(serializeResult, messageBufferSize); - - IoT_Publish_Message_Params publishParams; - publishParams.qos = qos; - publishParams.isRetained = 0; - publishParams.isDup = 0; - publishParams.id = 0; - publishParams.payload = messageBuffer; - publishParams.payloadLen = (size_t) serializeResult; - - return aws_iot_mqtt_publish(pClient, topicBuffer, topicSize, &publishParams); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_jobs_json.c b/src/aws_iot_jobs_json.c deleted file mode 100644 index 2213ddd7b4..0000000000 --- a/src/aws_iot_jobs_json.c +++ /dev/null @@ -1,197 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -#define __STDC_FORMAT_MACROS -#include -#include -#include -#include -#include -#include - -#include "jsmn.h" -#include "aws_iot_jobs_json.h" - -struct _SerializeState { - int totalSize; - char *nextPtr; - size_t remaingSize; -}; - -static void _printToBuffer(struct _SerializeState *state, const char *fmt, ...) { - if (state->totalSize == -1) return; - - va_list vl; - va_start(vl, fmt); - int len = vsnprintf(state->nextPtr, state->remaingSize, fmt, vl); - if (len < 0) { - state->totalSize = -1; - } else { - state->totalSize += len; - if (state->nextPtr != NULL) { - if (state->remaingSize > (size_t) len) { - state->remaingSize -= (size_t) len; - state->nextPtr += len; - } else { - state->remaingSize = 0; - state->nextPtr = NULL; - } - } - } - va_end(vl); -} - -static void _printKey(struct _SerializeState *state, bool first, const char *key) { - if (first) { - _printToBuffer(state, "{\"%s\":", key); - } else { - _printToBuffer(state, ",\"%s\":", key); - } -} - -static void _printStringValue(struct _SerializeState *state, const char *value) { - if (value == NULL) { - _printToBuffer(state, "null"); - } else { - _printToBuffer(state, "\"%s\"", value); - } -} - -static void _printLongValue(struct _SerializeState *state, int64_t value) { - _printToBuffer(state, "%lld", value); -} - -static void _printBooleanValue(struct _SerializeState *state, bool value) { - if(value) { - _printToBuffer(state, "true"); - } else { - _printToBuffer(state, "false"); - } -} - -int aws_iot_jobs_json_serialize_update_job_execution_request( - char *requestBuffer, size_t bufferSize, - const AwsIotJobExecutionUpdateRequest *request) -{ - const char *statusStr = aws_iot_jobs_map_status_to_string(request->status); - if (statusStr == NULL) return -1; - if (requestBuffer == NULL) bufferSize = 0; - - struct _SerializeState state = { 0, requestBuffer, bufferSize }; - _printKey(&state, true, "status"); - _printStringValue(&state, statusStr); - if (request->statusDetails != NULL) { - _printKey(&state, false, "statusDetails"); - _printToBuffer(&state, "%s", request->statusDetails); - } - if (request->executionNumber != 0) { - _printKey(&state, false, "executionNumber"); - _printLongValue(&state, request->executionNumber); - } - if (request->expectedVersion != 0) { - _printKey(&state, false, "expectedVersion"); - _printLongValue(&state, request->expectedVersion); - } - if (request->includeJobExecutionState) { - _printKey(&state, false, "includeJobExecutionState"); - _printBooleanValue(&state, request->includeJobExecutionState); - } - if (request->includeJobDocument) { - _printKey(&state, false, "includeJobDocument"); - _printBooleanValue(&state, request->includeJobDocument); - } - if (request->clientToken != NULL) { - _printKey(&state, false, "clientToken"); - _printStringValue(&state, request->clientToken); - } - - _printToBuffer(&state, "}"); - - return state.totalSize; -} - -int aws_iot_jobs_json_serialize_client_token_only_request( - char *requestBuffer, size_t bufferSize, - const char *clientToken) -{ - struct _SerializeState state = { 0, requestBuffer, bufferSize }; - _printKey(&state, true, "clientToken"); - _printStringValue(&state, clientToken); - _printToBuffer(&state, "}"); - - return state.totalSize; -} - -int aws_iot_jobs_json_serialize_describe_job_execution_request( - char *requestBuffer, size_t bufferSize, - const AwsIotDescribeJobExecutionRequest *request) -{ - bool first = true; - - if (requestBuffer == NULL) return 0; - - struct _SerializeState state = { 0, requestBuffer, bufferSize }; - if (request->clientToken != NULL) { - _printKey(&state, first, "clientToken"); - _printStringValue(&state, request->clientToken); - first = false; - } - if (request->executionNumber != 0) { - _printKey(&state, first, "executionNumber"); - _printLongValue(&state, request->executionNumber); - first = false; - } - if (request->includeJobDocument) { - _printKey(&state, first, "includeJobDocument"); - _printBooleanValue(&state, request->includeJobDocument); - } - - _printToBuffer(&state, "}"); - - return state.totalSize; -} - -int aws_iot_jobs_json_serialize_start_next_job_execution_request( - char *requestBuffer, size_t bufferSize, - const AwsIotStartNextPendingJobExecutionRequest *request) -{ - if (requestBuffer == NULL) bufferSize = 0; - struct _SerializeState state = { 0, requestBuffer, bufferSize }; - if (request->statusDetails != NULL) { - _printKey(&state, true, "statusDetails"); - _printToBuffer(&state, "%s", request->statusDetails); - } - if (request->clientToken != NULL) { - if(request->statusDetails != NULL) { - _printKey(&state, false, "clientToken"); - } else { - _printKey(&state, true, "clientToken"); - } - _printStringValue(&state, request->clientToken); - } - if (request->clientToken == NULL && request->statusDetails == NULL) { - _printToBuffer(&state, "{"); - } - _printToBuffer(&state, "}"); - return state.totalSize; -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_jobs_topics.c b/src/aws_iot_jobs_topics.c deleted file mode 100644 index 39c5cba28f..0000000000 --- a/src/aws_iot_jobs_topics.c +++ /dev/null @@ -1,129 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_jobs_topics.h" -#include -#include -#include - -#define BASE_THINGS_TOPIC "$aws/things/" - -#define NOTIFY_OPERATION "notify" -#define NOTIFY_NEXT_OPERATION "notify-next" -#define GET_OPERATION "get" -#define START_NEXT_OPERATION "start-next" -#define WILDCARD_OPERATION "+" -#define UPDATE_OPERATION "update" -#define ACCEPTED_REPLY "accepted" -#define REJECTED_REPLY "rejected" -#define WILDCARD_REPLY "+" - -static const char *_get_operation_for_base_topic(AwsIotJobExecutionTopicType topicType) { - switch (topicType) { - case JOB_UPDATE_TOPIC: - return UPDATE_OPERATION; - case JOB_NOTIFY_TOPIC: - return NOTIFY_OPERATION; - case JOB_NOTIFY_NEXT_TOPIC: - return NOTIFY_NEXT_OPERATION; - case JOB_GET_PENDING_TOPIC: - case JOB_DESCRIBE_TOPIC: - return GET_OPERATION; - case JOB_START_NEXT_TOPIC: - return START_NEXT_OPERATION; - case JOB_WILDCARD_TOPIC: - return WILDCARD_OPERATION; - case JOB_UNRECOGNIZED_TOPIC: - default: - return NULL; - } -} - -static bool _base_topic_requires_job_id(AwsIotJobExecutionTopicType topicType) { - switch (topicType) { - case JOB_UPDATE_TOPIC: - case JOB_DESCRIBE_TOPIC: - return true; - case JOB_NOTIFY_TOPIC: - case JOB_NOTIFY_NEXT_TOPIC: - case JOB_START_NEXT_TOPIC: - case JOB_GET_PENDING_TOPIC: - case JOB_WILDCARD_TOPIC: - case JOB_UNRECOGNIZED_TOPIC: - default: - return false; - } -} - -static const char *_get_suffix_for_topic_type(AwsIotJobExecutionTopicReplyType replyType) { - switch (replyType) { - case JOB_REQUEST_TYPE: - return ""; - break; - case JOB_ACCEPTED_REPLY_TYPE: - return "/" ACCEPTED_REPLY; - break; - case JOB_REJECTED_REPLY_TYPE: - return "/" REJECTED_REPLY; - break; - case JOB_WILDCARD_REPLY_TYPE: - return "/" WILDCARD_REPLY; - break; - case JOB_UNRECOGNIZED_TOPIC_TYPE: - default: - return NULL; - } -} - -int aws_iot_jobs_get_api_topic(char *buffer, size_t bufferSize, - AwsIotJobExecutionTopicType topicType, AwsIotJobExecutionTopicReplyType replyType, - const char* thingName, const char* jobId) -{ - if (thingName == NULL) { - return -1; - } - - if ((topicType == JOB_NOTIFY_TOPIC || topicType == JOB_NOTIFY_NEXT_TOPIC) && replyType != JOB_REQUEST_TYPE) { - return -1; - } - - bool requireJobId = _base_topic_requires_job_id(topicType); - if (jobId == NULL && requireJobId) { - return -1; - } - - const char *operation = _get_operation_for_base_topic(topicType); - if (operation == NULL) { - return -1; - } - - const char *suffix = _get_suffix_for_topic_type(replyType); - - if (requireJobId || (topicType == JOB_WILDCARD_TOPIC && jobId != NULL)) { - return snprintf(buffer, bufferSize, BASE_THINGS_TOPIC "%s/jobs/%s/%s%s", thingName, jobId, operation, suffix); - } else if (topicType == JOB_WILDCARD_TOPIC) { - return snprintf(buffer, bufferSize, BASE_THINGS_TOPIC "%s/jobs/#", thingName); - } else { - return snprintf(buffer, bufferSize, BASE_THINGS_TOPIC "%s/jobs/%s%s", thingName, operation, suffix); - } -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_jobs_types.c b/src/aws_iot_jobs_types.c deleted file mode 100644 index 9dcbf50810..0000000000 --- a/src/aws_iot_jobs_types.c +++ /dev/null @@ -1,73 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "aws_iot_jobs_types.h" - -const char *JOB_EXECUTION_QUEUED_STR = "QUEUED"; -const char *JOB_EXECUTION_IN_PROGRESS_STR = "IN_PROGRESS"; -const char *JOB_EXECUTION_FAILED_STR = "FAILED"; -const char *JOB_EXECUTION_SUCCEEDED_STR = "SUCCEEDED"; -const char *JOB_EXECUTION_CANCELED_STR = "CANCELED"; -const char *JOB_EXECUTION_REJECTED_STR = "REJECTED"; - -JobExecutionStatus aws_iot_jobs_map_string_to_job_status(const char *str) { - if (str == NULL || str[0] == '\0') { - return JOB_EXECUTION_STATUS_NOT_SET; - } else if (strcmp(str, JOB_EXECUTION_QUEUED_STR) == 0) { - return JOB_EXECUTION_QUEUED; - } else if(strcmp(str, JOB_EXECUTION_IN_PROGRESS_STR) == 0) { - return JOB_EXECUTION_IN_PROGRESS; - } else if(strcmp(str, JOB_EXECUTION_FAILED_STR) == 0) { - return JOB_EXECUTION_FAILED; - } else if(strcmp(str, JOB_EXECUTION_SUCCEEDED_STR) == 0) { - return JOB_EXECUTION_SUCCEEDED; - } else if(strcmp(str, JOB_EXECUTION_CANCELED_STR) == 0) { - return JOB_EXECUTION_CANCELED; - } else if(strcmp(str, JOB_EXECUTION_REJECTED_STR) == 0) { - return JOB_EXECUTION_REJECTED; - } else { - return JOB_EXECUTION_UNKNOWN_STATUS; - } -} - -const char *aws_iot_jobs_map_status_to_string(JobExecutionStatus status) { - switch(status) { - case JOB_EXECUTION_QUEUED: - return JOB_EXECUTION_QUEUED_STR; - case JOB_EXECUTION_IN_PROGRESS: - return JOB_EXECUTION_IN_PROGRESS_STR; - case JOB_EXECUTION_FAILED: - return JOB_EXECUTION_FAILED_STR; - case JOB_EXECUTION_SUCCEEDED: - return JOB_EXECUTION_SUCCEEDED_STR; - case JOB_EXECUTION_CANCELED: - return JOB_EXECUTION_CANCELED_STR; - case JOB_EXECUTION_REJECTED: - return JOB_EXECUTION_REJECTED_STR; - case JOB_EXECUTION_STATUS_NOT_SET: - case JOB_EXECUTION_UNKNOWN_STATUS: - default: - return NULL; - } -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_json_utils.c b/src/aws_iot_json_utils.c deleted file mode 100644 index f8485695fd..0000000000 --- a/src/aws_iot_json_utils.c +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_json_utils.c - * @brief Utilities for manipulating JSON - * - * json_utils provides JSON parsing utilities for use with the IoT SDK. - * Underlying JSON parsing relies on the Jasmine JSON parser. - * - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_json_utils.h" - -#include -#include -#include - -#include "aws_iot_log.h" - -int8_t jsoneq(const char *json, jsmntok_t *tok, const char *s) { - if(tok->type == JSMN_STRING) { - if((int) strlen(s) == tok->end - tok->start) { - if(strncmp(json + tok->start, s, (size_t) (tok->end - tok->start)) == 0) { - return 0; - } - } - } - return -1; -} - -IoT_Error_t parseUnsignedInteger32Value(uint32_t *i, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not an integer"); - return JSON_PARSE_ERROR; - } - - if(('-' == (char) (jsonString[token->start])) || (1 != sscanf(jsonString + token->start, "%u", i))) { - IOT_WARN("Token was not an unsigned integer."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseUnsignedInteger16Value(uint16_t *i, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not an integer"); - return JSON_PARSE_ERROR; - } - - if(('-' == (char) (jsonString[token->start])) || (1 != sscanf(jsonString + token->start, "%hu", i))) { - IOT_WARN("Token was not an unsigned integer."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseUnsignedInteger8Value(uint8_t *i, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not an integer"); - return JSON_PARSE_ERROR; - } - - if(('-' == (char) (jsonString[token->start])) || (1 != sscanf(jsonString + token->start, "%hhu", i))) { - IOT_WARN("Token was not an unsigned integer."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseInteger32Value(int32_t *i, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not an integer"); - return JSON_PARSE_ERROR; - } - - if(1 != sscanf(jsonString + token->start, "%i", i)) { - IOT_WARN("Token was not an integer."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseInteger16Value(int16_t *i, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not an integer"); - return JSON_PARSE_ERROR; - } - - if(1 != sscanf(jsonString + token->start, "%hi", i)) { - IOT_WARN("Token was not an integer."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseInteger8Value(int8_t *i, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not an integer"); - return JSON_PARSE_ERROR; - } - - if(1 != sscanf(jsonString + token->start, "%hhi", i)) { - IOT_WARN("Token was not an integer."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseFloatValue(float *f, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not a float."); - return JSON_PARSE_ERROR; - } - - if(1 != sscanf(jsonString + token->start, "%f", f)) { - IOT_WARN("Token was not a float."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseDoubleValue(double *d, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not a double."); - return JSON_PARSE_ERROR; - } - - if(1 != sscanf(jsonString + token->start, "%lf", d)) { - IOT_WARN("Token was not a double."); - return JSON_PARSE_ERROR; - } - - return SUCCESS; -} - -IoT_Error_t parseBooleanValue(bool *b, const char *jsonString, jsmntok_t *token) { - if(token->type != JSMN_PRIMITIVE) { - IOT_WARN("Token was not a primitive."); - return JSON_PARSE_ERROR; - } - if(strncmp(jsonString + token->start, "true", 4) == 0) { - *b = true; - } else if(strncmp(jsonString + token->start, "false", 5) == 0) { - *b = false; - } else { - IOT_WARN("Token was not a bool."); - return JSON_PARSE_ERROR; - } - return SUCCESS; -} - -IoT_Error_t parseStringValue(char *buf, size_t bufLen, const char *jsonString, jsmntok_t *token) { - /* This length does not include a null-terminator. */ - size_t stringLength = (size_t)(token->end - token->start); - - if(token->type != JSMN_STRING) { - IOT_WARN("Token was not a string."); - return JSON_PARSE_ERROR; - } - - if (stringLength+1 > bufLen) { - IOT_WARN("Buffer too small to hold string value."); - return SHADOW_JSON_ERROR; - } - - strncpy(buf, jsonString + token->start, stringLength); - buf[stringLength] = '\0'; - - return SUCCESS; -} - -jsmntok_t *findToken(const char *key, const char *jsonString, jsmntok_t *token) { - jsmntok_t *result = token; - int i; - - if(token->type != JSMN_OBJECT) { - IOT_WARN("Token was not an object."); - return NULL; - } - - if(token->size == 0) { - return NULL; - } - - result = token + 1; - - for (i = 0; i < token->size; i++) { - if (0 == jsoneq(jsonString, result, key)) { - return result + 1; - } - - int propertyEnd = (result + 1)->end; - result += 2; - while (result->start < propertyEnd) - result++; - } - - return NULL; -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_mqtt_client.c b/src/aws_iot_mqtt_client.c deleted file mode 100644 index 5c92dd6d3a..0000000000 --- a/src/aws_iot_mqtt_client.c +++ /dev/null @@ -1,369 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client.c - * @brief MQTT client API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include "aws_iot_log.h" -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_mqtt_client_common_internal.h" -#include "aws_iot_version.h" - -#if !DISABLE_METRICS -#define SDK_METRICS_LEN 25 -#define SDK_METRICS_TEMPLATE "?SDK=C&Version=%d.%d.%d" -static char pUsernameTemp[SDK_METRICS_LEN] = {0}; -#endif - -#ifdef _ENABLE_THREAD_SUPPORT_ -#include "threads_interface.h" -#endif - -const IoT_Client_Init_Params iotClientInitParamsDefault = IoT_Client_Init_Params_initializer; -const IoT_MQTT_Will_Options iotMqttWillOptionsDefault = IoT_MQTT_Will_Options_Initializer; -const IoT_Client_Connect_Params iotClientConnectParamsDefault = IoT_Client_Connect_Params_initializer; - -ClientState aws_iot_mqtt_get_client_state(AWS_IoT_Client *pClient) { - FUNC_ENTRY; - if(NULL == pClient) { - return CLIENT_STATE_INVALID; - } - - FUNC_EXIT_RC(pClient->clientStatus.clientState); -} - -#ifdef _ENABLE_THREAD_SUPPORT_ -IoT_Error_t aws_iot_mqtt_client_lock_mutex(AWS_IoT_Client *pClient, IoT_Mutex_t *pMutex) { - FUNC_ENTRY; - IoT_Error_t threadRc = FAILURE; - - if(NULL == pClient || NULL == pMutex){ - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(false == pClient->clientData.isBlockOnThreadLockEnabled) { - threadRc = aws_iot_thread_mutex_trylock(pMutex); - } else { - threadRc = aws_iot_thread_mutex_lock(pMutex); - /* Should never return Error because the above request blocks until lock is obtained */ - } - - if(SUCCESS != threadRc) { - FUNC_EXIT_RC(threadRc); - } - - FUNC_EXIT_RC(SUCCESS); -} - -IoT_Error_t aws_iot_mqtt_client_unlock_mutex(AWS_IoT_Client *pClient, IoT_Mutex_t *pMutex) { - if(NULL == pClient || NULL == pMutex) { - return NULL_VALUE_ERROR; - } - IOT_UNUSED(pClient); - return aws_iot_thread_mutex_unlock(pMutex); -} -#endif - -IoT_Error_t aws_iot_mqtt_set_client_state(AWS_IoT_Client *pClient, ClientState expectedCurrentState, - ClientState newState) { - IoT_Error_t rc; -#ifdef _ENABLE_THREAD_SUPPORT_ - IoT_Error_t threadRc = FAILURE; -#endif - - FUNC_ENTRY; - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - -#ifdef _ENABLE_THREAD_SUPPORT_ - rc = aws_iot_mqtt_client_lock_mutex(pClient, &(pClient->clientData.state_change_mutex)); - if(SUCCESS != rc) { - return rc; - } -#endif - if(expectedCurrentState == aws_iot_mqtt_get_client_state(pClient)) { - pClient->clientStatus.clientState = newState; - rc = SUCCESS; - } else { - rc = MQTT_UNEXPECTED_CLIENT_STATE_ERROR; - } - -#ifdef _ENABLE_THREAD_SUPPORT_ - threadRc = aws_iot_mqtt_client_unlock_mutex(pClient, &(pClient->clientData.state_change_mutex)); - if(SUCCESS == rc && SUCCESS != threadRc) { - rc = threadRc; - } -#endif - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_mqtt_set_connect_params(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pNewConnectParams) { - FUNC_ENTRY; - if(NULL == pClient || NULL == pNewConnectParams) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - pClient->clientData.options.isWillMsgPresent = pNewConnectParams->isWillMsgPresent; - pClient->clientData.options.MQTTVersion = pNewConnectParams->MQTTVersion; - pClient->clientData.options.pClientID = pNewConnectParams->pClientID; - pClient->clientData.options.clientIDLen = pNewConnectParams->clientIDLen; -#if !DISABLE_METRICS - if (0 == strlen(pUsernameTemp)) { - snprintf(pUsernameTemp, SDK_METRICS_LEN, SDK_METRICS_TEMPLATE, VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); - } - pClient->clientData.options.pUsername = (char*)&pUsernameTemp[0]; - pClient->clientData.options.usernameLen = strlen(pUsernameTemp); -#else - pClient->clientData.options.pUsername = pNewConnectParams->pUsername; - pClient->clientData.options.usernameLen = pNewConnectParams->usernameLen; -#endif - pClient->clientData.options.pPassword = pNewConnectParams->pPassword; - pClient->clientData.options.passwordLen = pNewConnectParams->passwordLen; - pClient->clientData.options.will.pTopicName = pNewConnectParams->will.pTopicName; - pClient->clientData.options.will.topicNameLen = pNewConnectParams->will.topicNameLen; - pClient->clientData.options.will.pMessage = pNewConnectParams->will.pMessage; - pClient->clientData.options.will.msgLen = pNewConnectParams->will.msgLen; - pClient->clientData.options.will.qos = pNewConnectParams->will.qos; - pClient->clientData.options.will.isRetained = pNewConnectParams->will.isRetained; - pClient->clientData.options.keepAliveIntervalInSec = pNewConnectParams->keepAliveIntervalInSec; - pClient->clientData.options.isCleanSession = pNewConnectParams->isCleanSession; - - FUNC_EXIT_RC(SUCCESS); -} - -IoT_Error_t aws_iot_mqtt_free(AWS_IoT_Client *pClient) -{ - IoT_Error_t rc = SUCCESS; - - if (NULL == pClient) { - rc = NULL_VALUE_ERROR; - }else - { - #ifdef _ENABLE_THREAD_SUPPORT_ - if (rc == SUCCESS) - { - rc = aws_iot_thread_mutex_destroy(&(pClient->clientData.state_change_mutex)); - } - - if (rc == SUCCESS) - { - rc = aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_read_mutex)); - }else{ - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_read_mutex)); - } - - if (rc == SUCCESS) - { - rc = aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_write_mutex)); - }else{ - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_write_mutex)); - } - #endif - } - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_mqtt_init(AWS_IoT_Client *pClient, IoT_Client_Init_Params *pInitParams) { - uint32_t i; - IoT_Error_t rc; - IoT_Client_Connect_Params default_options = IoT_Client_Connect_Params_initializer; - - FUNC_ENTRY; - - if(NULL == pClient || NULL == pInitParams || NULL == pInitParams->pHostURL || 0 == pInitParams->port || - NULL == pInitParams->pRootCALocation || NULL == pInitParams->pDevicePrivateKeyLocation || - NULL == pInitParams->pDeviceCertLocation) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - for(i = 0; i < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; ++i) { - pClient->clientData.messageHandlers[i].topicName = NULL; - pClient->clientData.messageHandlers[i].pApplicationHandler = NULL; - pClient->clientData.messageHandlers[i].pApplicationHandlerData = NULL; - pClient->clientData.messageHandlers[i].qos = QOS0; - } - - pClient->clientData.packetTimeoutMs = pInitParams->mqttPacketTimeout_ms; - pClient->clientData.commandTimeoutMs = pInitParams->mqttCommandTimeout_ms; - pClient->clientData.writeBufSize = AWS_IOT_MQTT_TX_BUF_LEN; - pClient->clientData.readBufSize = AWS_IOT_MQTT_RX_BUF_LEN; - pClient->clientData.counterNetworkDisconnected = 0; - pClient->clientData.disconnectHandler = pInitParams->disconnectHandler; - pClient->clientData.disconnectHandlerData = pInitParams->disconnectHandlerData; - pClient->clientData.nextPacketId = 1; - - /* Initialize default connection options */ - rc = aws_iot_mqtt_set_connect_params(pClient, &default_options); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - -#ifdef _ENABLE_THREAD_SUPPORT_ - pClient->clientData.isBlockOnThreadLockEnabled = pInitParams->isBlockOnThreadLockEnabled; - rc = aws_iot_thread_mutex_init(&(pClient->clientData.state_change_mutex)); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - rc = aws_iot_thread_mutex_init(&(pClient->clientData.tls_read_mutex)); - if(SUCCESS != rc) { - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.state_change_mutex)); - FUNC_EXIT_RC(rc); - } - rc = aws_iot_thread_mutex_init(&(pClient->clientData.tls_write_mutex)); - if(SUCCESS != rc) { - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_read_mutex)); - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.state_change_mutex)); - FUNC_EXIT_RC(rc); - } -#endif - - pClient->clientStatus.isPingOutstanding = 0; - pClient->clientStatus.isAutoReconnectEnabled = pInitParams->enableAutoReconnect; - - rc = iot_tls_init(&(pClient->networkStack), pInitParams->pRootCALocation, pInitParams->pDeviceCertLocation, - pInitParams->pDevicePrivateKeyLocation, pInitParams->pHostURL, pInitParams->port, - pInitParams->tlsHandshakeTimeout_ms, pInitParams->isSSLHostnameVerify); - - if(SUCCESS != rc) { - #ifdef _ENABLE_THREAD_SUPPORT_ - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_read_mutex)); - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.state_change_mutex)); - (void)aws_iot_thread_mutex_destroy(&(pClient->clientData.tls_write_mutex)); - #endif - pClient->clientStatus.clientState = CLIENT_STATE_INVALID; - FUNC_EXIT_RC(rc); - } - - init_timer(&(pClient->pingTimer)); - init_timer(&(pClient->reconnectDelayTimer)); - - pClient->clientStatus.clientState = CLIENT_STATE_INITIALIZED; - - FUNC_EXIT_RC(SUCCESS); -} - -uint16_t aws_iot_mqtt_get_next_packet_id(AWS_IoT_Client *pClient) { - return pClient->clientData.nextPacketId = (uint16_t) ((MAX_PACKET_ID == pClient->clientData.nextPacketId) ? 1 : ( - pClient->clientData.nextPacketId + 1)); -} - -bool aws_iot_mqtt_is_client_connected(AWS_IoT_Client *pClient) { - bool isConnected; - - FUNC_ENTRY; - - if(NULL == pClient) { - IOT_WARN(" Client is null! "); - FUNC_EXIT_RC(false); - } - - switch(pClient->clientStatus.clientState) { - case CLIENT_STATE_INVALID: - case CLIENT_STATE_INITIALIZED: - case CLIENT_STATE_CONNECTING: - isConnected = false; - break; - case CLIENT_STATE_CONNECTED_IDLE: - case CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_PUBLISH_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_SUBSCRIBE_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_UNSUBSCRIBE_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_RESUBSCRIBE_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN: - isConnected = true; - break; - case CLIENT_STATE_DISCONNECTING: - case CLIENT_STATE_DISCONNECTED_ERROR: - case CLIENT_STATE_DISCONNECTED_MANUALLY: - case CLIENT_STATE_PENDING_RECONNECT: - default: - isConnected = false; - break; - } - - FUNC_EXIT_RC(isConnected); -} - -bool aws_iot_is_autoreconnect_enabled(AWS_IoT_Client *pClient) { - FUNC_ENTRY; - if(NULL == pClient) { - IOT_WARN(" Client is null! "); - FUNC_EXIT_RC(false); - } - - FUNC_EXIT_RC(pClient->clientStatus.isAutoReconnectEnabled); -} - -IoT_Error_t aws_iot_mqtt_autoreconnect_set_status(AWS_IoT_Client *pClient, bool newStatus) { - FUNC_ENTRY; - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - pClient->clientStatus.isAutoReconnectEnabled = newStatus; - FUNC_EXIT_RC(SUCCESS); -} - -IoT_Error_t aws_iot_mqtt_set_disconnect_handler(AWS_IoT_Client *pClient, iot_disconnect_handler pDisconnectHandler, - void *pDisconnectHandlerData) { - FUNC_ENTRY; - if(NULL == pClient || NULL == pDisconnectHandler) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - pClient->clientData.disconnectHandler = pDisconnectHandler; - pClient->clientData.disconnectHandlerData = pDisconnectHandlerData; - FUNC_EXIT_RC(SUCCESS); -} - -uint32_t aws_iot_mqtt_get_network_disconnected_count(AWS_IoT_Client *pClient) { - return pClient->clientData.counterNetworkDisconnected; -} - -void aws_iot_mqtt_reset_network_disconnected_count(AWS_IoT_Client *pClient) { - pClient->clientData.counterNetworkDisconnected = 0; -} - -#ifdef __cplusplus -} -#endif - diff --git a/src/aws_iot_mqtt_client_common_internal.c b/src/aws_iot_mqtt_client_common_internal.c deleted file mode 100644 index 045abb38c4..0000000000 --- a/src/aws_iot_mqtt_client_common_internal.c +++ /dev/null @@ -1,720 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - * Sergio R. Caprile - non-blocking packet read functions for stream transport - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_common_internal.c - * @brief MQTT client internal API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "aws_iot_mqtt_client_common_internal.h" - -/* Max length of packet header */ -#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4 - -/** - * Encodes the message length according to the MQTT algorithm - * @param buf the buffer into which the encoded data is written - * @param length the length to be encoded - * @return the number of bytes written to buffer - */ -size_t aws_iot_mqtt_internal_write_len_to_buffer(unsigned char *buf, uint32_t length) { - size_t outLen = 0; - unsigned char encodedByte; - - FUNC_ENTRY; - do { - encodedByte = (unsigned char) (length % 128); - length /= 128; - /* if there are more digits to encode, set the top bit of this digit */ - if(length > 0) { - encodedByte |= 0x80; - } - buf[outLen++] = encodedByte; - } while(length > 0); - - FUNC_EXIT_RC(outLen); -} - -/** - * Decodes the message length according to the MQTT algorithm - * @param the buffer containing the message - * @param value the decoded length returned - * @return the number of bytes read from the socket - */ -IoT_Error_t aws_iot_mqtt_internal_decode_remaining_length_from_buffer(unsigned char *buf, uint32_t *decodedLen, - uint32_t *readBytesLen) { - unsigned char encodedByte; - uint32_t multiplier, len; - FUNC_ENTRY; - - multiplier = 1; - len = 0; - *decodedLen = 0; - - do { - if(++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) { - /* bad data */ - FUNC_EXIT_RC(MQTT_DECODE_REMAINING_LENGTH_ERROR); - } - encodedByte = *buf; - buf++; - *decodedLen += (encodedByte & 127) * multiplier; - multiplier *= 128; - } while((encodedByte & 128) != 0); - - *readBytesLen = len; - - FUNC_EXIT_RC(SUCCESS); -} - -uint32_t aws_iot_mqtt_internal_get_final_packet_length_from_remaining_length(uint32_t rem_len) { - rem_len += 1; /* header byte */ - /* now remaining_length field (MQTT 3.1.1 - 2.2.3)*/ - if(rem_len < 128) { - rem_len += 1; - } else if(rem_len < 16384) { - rem_len += 2; - } else if(rem_len < 2097152) { - rem_len += 3; - } else { - rem_len += 4; - } - return rem_len; -} - -/** - * Calculates uint16 packet id from two bytes read from the input buffer - * Checks Endianness at runtime - * - * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned - * @return the value calculated - */ -uint16_t aws_iot_mqtt_internal_read_uint16_t(unsigned char **pptr) { - unsigned char *ptr = *pptr; - uint16_t len = 0; - uint8_t firstByte = (uint8_t) (*ptr); - uint8_t secondByte = (uint8_t) (*(ptr + 1)); - len = (uint16_t) (secondByte + (256 * firstByte)); - - *pptr += 2; - return len; -} - -/** - * Writes an integer as 2 bytes to an output buffer. - * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned - * @param anInt the integer to write - */ -void aws_iot_mqtt_internal_write_uint_16(unsigned char **pptr, uint16_t anInt) { - **pptr = (unsigned char) (anInt / 256); - (*pptr)++; - **pptr = (unsigned char) (anInt % 256); - (*pptr)++; -} - -/** - * Reads one character from the input buffer. - * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned - * @return the character read - */ -unsigned char aws_iot_mqtt_internal_read_char(unsigned char **pptr) { - unsigned char c = **pptr; - (*pptr)++; - return c; -} - -/** - * Writes one character to an output buffer. - * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned - * @param c the character to write - */ -void aws_iot_mqtt_internal_write_char(unsigned char **pptr, unsigned char c) { - **pptr = c; - (*pptr)++; -} - -void aws_iot_mqtt_internal_write_utf8_string(unsigned char **pptr, const char *string, uint16_t stringLen) { - /* Nothing that calls this function will have a stringLen with a size larger than 2 bytes (MQTT 3.1.1 - 1.5.3) */ - aws_iot_mqtt_internal_write_uint_16(pptr, stringLen); - if(stringLen > 0) { - memcpy(*pptr, string, stringLen); - *pptr += stringLen; - } -} - -/** - * Initialize the MQTTHeader structure. Used to ensure that Header bits are - * always initialized using the proper mappings. No Endianness issues here since - * the individual fields are all less than a byte. Also generates no warnings since - * all fields are initialized using hex constants - */ -IoT_Error_t aws_iot_mqtt_internal_init_header(MQTTHeader *pHeader, MessageTypes message_type, - QoS qos, uint8_t dup, uint8_t retained) { - FUNC_ENTRY; - - if(NULL == pHeader) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - /* Set all bits to zero */ - pHeader->byte = 0; - uint8_t type = 0; - switch(message_type) { - case UNKNOWN: - /* Should never happen */ - return FAILURE; - case CONNECT: - type = 0x01; - break; - case CONNACK: - type = 0x02; - break; - case PUBLISH: - type = 0x03; - break; - case PUBACK: - type = 0x04; - break; - case PUBREC: - type = 0x05; - break; - case PUBREL: - type = 0x06; - break; - case PUBCOMP: - type = 0x07; - break; - case SUBSCRIBE: - type = 0x08; - break; - case SUBACK: - type = 0x09; - break; - case UNSUBSCRIBE: - type = 0x0A; - break; - case UNSUBACK: - type = 0x0B; - break; - case PINGREQ: - type = 0x0C; - break; - case PINGRESP: - type = 0x0D; - break; - case DISCONNECT: - type = 0x0E; - break; - default: - /* Should never happen */ - FUNC_EXIT_RC(FAILURE); - } - - pHeader->byte = type << 4; - pHeader->byte |= dup << 3; - - switch(qos) { - case QOS0: - break; - case QOS1: - pHeader->byte |= 1 << 1; - break; - default: - /* Using QOS0 as default */ - break; - } - - pHeader->byte |= (1 == retained) ? 0x01 : 0x00; - - FUNC_EXIT_RC(SUCCESS); -} - -IoT_Error_t aws_iot_mqtt_internal_send_packet(AWS_IoT_Client *pClient, size_t length, Timer *pTimer) { - - size_t sentLen, sent; - IoT_Error_t rc; - - FUNC_ENTRY; - - if(NULL == pClient || NULL == pTimer) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(length >= pClient->clientData.writeBufSize) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - -#ifdef _ENABLE_THREAD_SUPPORT_ - rc = aws_iot_mqtt_client_lock_mutex(pClient, &(pClient->clientData.tls_write_mutex)); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } -#endif - - sentLen = 0; - sent = 0; - - while(sent < length && !has_timer_expired(pTimer)) { - rc = pClient->networkStack.write(&(pClient->networkStack), - &pClient->clientData.writeBuf[sent], - (length - sent), - pTimer, - &sentLen); - if(SUCCESS != rc) { - /* there was an error writing the data */ - break; - } - sent += sentLen; - } - -#ifdef _ENABLE_THREAD_SUPPORT_ - rc = aws_iot_mqtt_client_unlock_mutex(pClient, &(pClient->clientData.tls_write_mutex)); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } -#endif - - if(sent == length) { - /* record the fact that we have successfully sent the packet */ - //countdown_sec(&c->pingTimer, c->clientData.keepAliveInterval); - FUNC_EXIT_RC(SUCCESS); - } - - FUNC_EXIT_RC(rc) -} - -static IoT_Error_t _aws_iot_mqtt_internal_readWrapper( AWS_IoT_Client *pClient, size_t offset, size_t size, Timer *pTimer, size_t * read_len ) { - IoT_Error_t rc; - int byteToRead; - size_t byteRead = 0; - - byteToRead = ( offset + size ) - pClient->clientData.readBufIndex; - - if ( byteToRead > 0 ) - { - rc = pClient->networkStack.read( &( pClient->networkStack ), - pClient->clientData.readBuf + pClient->clientData.readBufIndex, - (size_t)byteToRead, - pTimer, - &byteRead ); - pClient->clientData.readBufIndex += byteRead; - - /* refresh byte to read */ - byteToRead = ( offset + size ) - ((int)pClient->clientData.readBufIndex); - *read_len = size - (size_t)byteToRead; - } - else - { - *read_len = size; - rc = SUCCESS; - } - - - - return rc; -} -static IoT_Error_t _aws_iot_mqtt_internal_decode_packet_remaining_len(AWS_IoT_Client *pClient, size_t * offset, - size_t *rem_len, Timer *pTimer) { - size_t multiplier, len; - IoT_Error_t rc; - size_t read_len; - - FUNC_ENTRY; - - multiplier = 1; - len = 0; - *rem_len = 0; - - do { - if(++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) { - /* bad data */ - FUNC_EXIT_RC(MQTT_DECODE_REMAINING_LENGTH_ERROR); - } - rc = _aws_iot_mqtt_internal_readWrapper( pClient, len, 1, pTimer, &read_len ); - - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - *rem_len += (( pClient->clientData.readBuf[len] & 127) * multiplier); - multiplier *= 128; - } while(( pClient->clientData.readBuf[len] & 128) != 0); - *offset = len + 1; - FUNC_EXIT_RC(rc); -} - -static IoT_Error_t _aws_iot_mqtt_internal_read_packet(AWS_IoT_Client *pClient, Timer *pTimer, uint8_t *pPacketType) { - size_t rem_len, total_bytes_read, bytes_to_be_read, read_len; - IoT_Error_t rc; - size_t offset = 0; - MQTTHeader header = {0}; - Timer packetTimer; - init_timer(&packetTimer); - countdown_ms(&packetTimer, pClient->clientData.packetTimeoutMs); - - rem_len = 0; - total_bytes_read = 0; - bytes_to_be_read = 0; - read_len = 0; - - rc = _aws_iot_mqtt_internal_readWrapper( pClient, offset, 1, pTimer, &read_len ); - /* 1. read the header byte. This has the packet type in it */ - if(NETWORK_SSL_NOTHING_TO_READ == rc) { - return MQTT_NOTHING_TO_READ; - } else if(SUCCESS != rc) { - return rc; - } - - /* 2. read the remaining length. This is variable in itself */ - rc = _aws_iot_mqtt_internal_decode_packet_remaining_len(pClient, &offset, &rem_len, pTimer); - if(SUCCESS != rc) { - return rc; - } - - /* if the buffer is too short then the message will be dropped silently */ - if((rem_len + offset) >= pClient->clientData.readBufSize) { - bytes_to_be_read = pClient->clientData.readBufSize; - do { - rc = pClient->networkStack.read(&(pClient->networkStack), pClient->clientData.readBuf, bytes_to_be_read, - pTimer, &read_len); - if(SUCCESS == rc) { - total_bytes_read += read_len; - if((rem_len - total_bytes_read) >= pClient->clientData.readBufSize) { - bytes_to_be_read = pClient->clientData.readBufSize; - } else { - bytes_to_be_read = rem_len - total_bytes_read; - } - } - } while(total_bytes_read < rem_len && SUCCESS == rc); - - /* Check buffer was correctly emptied, otherwise, return error message. */ - if ( total_bytes_read == rem_len ) - { - aws_iot_mqtt_internal_flushBuffers( pClient ); - return MQTT_RX_BUFFER_TOO_SHORT_ERROR; - } - else - { - return rc; - } - } - - /* 3. read the rest of the buffer using a callback to supply the rest of the data */ - if(rem_len > 0) { - rc = _aws_iot_mqtt_internal_readWrapper( pClient, offset, rem_len, pTimer, &read_len ); - if(SUCCESS != rc || read_len != rem_len) { - return FAILURE; - } - } - - /* Pack has been received, we can flush the buffers for next call. */ - aws_iot_mqtt_internal_flushBuffers( pClient ); - header.byte = pClient->clientData.readBuf[0]; - *pPacketType = MQTT_HEADER_FIELD_TYPE(header.byte); - - FUNC_EXIT_RC(rc); -} - -// assume topic filter and name is in correct format -// # can only be at end -// + and # can only be next to separator -static bool _aws_iot_mqtt_internal_is_topic_matched(char *pTopicFilter, char *pTopicName, uint16_t topicNameLen) { - - char *curf, *curn, *curn_end; - - if(NULL == pTopicFilter || NULL == pTopicName) { - return false; - } - - curf = pTopicFilter; - curn = pTopicName; - curn_end = curn + topicNameLen; - - while(*curf && (curn < curn_end)) { - if(*curn == '/' && *curf != '/') { - break; - } - if(*curf != '+' && *curf != '#' && *curf != *curn) { - break; - } - if(*curf == '+') { - /* skip until we meet the next separator, or end of string */ - char *nextpos = curn + 1; - while(nextpos < curn_end && *nextpos != '/') - nextpos = ++curn + 1; - } else if(*curf == '#') { - /* skip until end of string */ - curn = curn_end - 1; - } - - curf++; - curn++; - }; - - return (curn == curn_end) && (*curf == '\0'); -} - -static IoT_Error_t _aws_iot_mqtt_internal_deliver_message(AWS_IoT_Client *pClient, char *pTopicName, - uint16_t topicNameLen, - IoT_Publish_Message_Params *pMessageParams) { - uint32_t itr; - IoT_Error_t rc; - ClientState clientState; - - FUNC_ENTRY; - - if(NULL == pTopicName) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - /* This function can be called from all MQTT APIs - * But while callback return is in progress, Yield should not be called. - * The state for CB_RETURN accomplishes that, as yield cannot be called while in that state */ - clientState = aws_iot_mqtt_get_client_state(pClient); - aws_iot_mqtt_set_client_state(pClient, clientState, CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN); - - /* Find the right message handler - indexed by topic */ - for(itr = 0; itr < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; ++itr) { - if(NULL != pClient->clientData.messageHandlers[itr].topicName) { - if(((topicNameLen == pClient->clientData.messageHandlers[itr].topicNameLen) - && - (strncmp(pTopicName, (char *) pClient->clientData.messageHandlers[itr].topicName, topicNameLen) == 0)) - || _aws_iot_mqtt_internal_is_topic_matched((char *) pClient->clientData.messageHandlers[itr].topicName, - pTopicName, topicNameLen)) { - if(NULL != pClient->clientData.messageHandlers[itr].pApplicationHandler) { - pClient->clientData.messageHandlers[itr].pApplicationHandler(pClient, pTopicName, topicNameLen, - pMessageParams, - pClient->clientData.messageHandlers[itr].pApplicationHandlerData); - } - } - } - } - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN, clientState); - - FUNC_EXIT_RC(rc); -} - -static IoT_Error_t _aws_iot_mqtt_internal_handle_publish(AWS_IoT_Client *pClient, Timer *pTimer) { - char *topicName; - uint16_t topicNameLen; - uint32_t len; - IoT_Error_t rc; - IoT_Publish_Message_Params msg; - - FUNC_ENTRY; - - topicName = NULL; - topicNameLen = 0; - len = 0; - - rc = aws_iot_mqtt_internal_deserialize_publish(&msg.isDup, &msg.qos, &msg.isRetained, - &msg.id, &topicName, &topicNameLen, - (unsigned char **) &msg.payload, &msg.payloadLen, - pClient->clientData.readBuf, - pClient->clientData.readBufSize); - - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = _aws_iot_mqtt_internal_deliver_message(pClient, topicName, topicNameLen, &msg); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - if(QOS0 == msg.qos) { - /* No further processing required for QoS0 */ - FUNC_EXIT_RC(SUCCESS); - } - - /* Message assumed to be QoS1 since we do not support QoS2 at this time */ - rc = aws_iot_mqtt_internal_serialize_ack(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, - PUBACK, 0, msg.id, &len); - - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = aws_iot_mqtt_internal_send_packet(pClient, len, pTimer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - FUNC_EXIT_RC(SUCCESS); -} - -IoT_Error_t aws_iot_mqtt_internal_cycle_read(AWS_IoT_Client *pClient, Timer *pTimer, uint8_t *pPacketType) { - IoT_Error_t rc; - -#ifdef _ENABLE_THREAD_SUPPORT_ - IoT_Error_t threadRc; -#endif - - if(NULL == pClient || NULL == pTimer) { - return NULL_VALUE_ERROR; - } - -#ifdef _ENABLE_THREAD_SUPPORT_ - threadRc = aws_iot_mqtt_client_lock_mutex(pClient, &(pClient->clientData.tls_read_mutex)); - if(SUCCESS != threadRc) { - FUNC_EXIT_RC(threadRc); - } -#endif - - /* read the socket, see what work is due */ - rc = _aws_iot_mqtt_internal_read_packet(pClient, pTimer, pPacketType); - -#ifdef _ENABLE_THREAD_SUPPORT_ - threadRc = aws_iot_mqtt_client_unlock_mutex(pClient, &(pClient->clientData.tls_read_mutex)); - if(SUCCESS != threadRc && (MQTT_NOTHING_TO_READ == rc || SUCCESS == rc)) { - return threadRc; - } -#endif - - if(MQTT_NOTHING_TO_READ == rc) { - /* Nothing to read, not a cycle failure */ - return SUCCESS; - } else if(SUCCESS != rc) { - return rc; - } - - switch(*pPacketType) { - case CONNACK: - case PUBACK: - case SUBACK: - case UNSUBACK: - /* SDK is blocking, these responses will be forwarded to calling function to process */ - break; - case PUBLISH: { - rc = _aws_iot_mqtt_internal_handle_publish(pClient, pTimer); - break; - } - case PUBREC: - case PUBCOMP: - /* QoS2 not supported at this time */ - break; - case PINGRESP: { - pClient->clientStatus.isPingOutstanding = 0; - countdown_sec(&pClient->pingTimer, pClient->clientData.keepAliveInterval); - break; - } - default: { - /* Either unknown packet type or Failure occurred - * Should not happen */ - rc = MQTT_RX_MESSAGE_PACKET_TYPE_INVALID_ERROR; - break; - } - } - - return rc; -} - -IoT_Error_t aws_iot_mqtt_internal_flushBuffers( AWS_IoT_Client *pClient ) { - pClient->clientData.readBufIndex = 0; - return SUCCESS; -} - -/* only used in single-threaded mode where one command at a time is in process */ -IoT_Error_t aws_iot_mqtt_internal_wait_for_read(AWS_IoT_Client *pClient, uint8_t packetType, Timer *pTimer) { - IoT_Error_t rc; - uint8_t read_packet_type; - - FUNC_ENTRY; - if(NULL == pClient || NULL == pTimer) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - read_packet_type = 0; - do { - if(has_timer_expired(pTimer)) { - /* we timed out */ - rc = MQTT_REQUEST_TIMEOUT_ERROR; - break; - } - rc = aws_iot_mqtt_internal_cycle_read(pClient, pTimer, &read_packet_type); - } while(((SUCCESS == rc) || (MQTT_NOTHING_TO_READ == rc)) && (read_packet_type != packetType)); - - /* If rc is SUCCESS, we have received the expected - * MQTT packet. Otherwise rc tells the error. */ - FUNC_EXIT_RC(rc); -} - -/** - * Serializes a 0-length packet into the supplied buffer, ready for writing to a socket - * @param buf the buffer into which the packet will be serialized - * @param buflen the length in bytes of the supplied buffer, to avoid overruns - * @param packettype the message type - * @param serialized length - * @return IoT_Error_t indicating function execution status - */ -IoT_Error_t aws_iot_mqtt_internal_serialize_zero(unsigned char *pTxBuf, size_t txBufLen, MessageTypes packetType, - size_t *pSerializedLength) { - unsigned char *ptr; - IoT_Error_t rc; - MQTTHeader header = {0}; - - FUNC_ENTRY; - if(NULL == pTxBuf || NULL == pSerializedLength) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - /* Buffer should have at least 2 bytes for the header */ - if(4 > txBufLen) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - - ptr = pTxBuf; - - rc = aws_iot_mqtt_internal_init_header(&header, packetType, QOS0, 0, 0); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* write header */ - aws_iot_mqtt_internal_write_char(&ptr, header.byte); - - /* write remaining length */ - ptr += aws_iot_mqtt_internal_write_len_to_buffer(ptr, 0); - *pSerializedLength = (uint32_t) (ptr - pTxBuf); - - FUNC_EXIT_RC(SUCCESS); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_mqtt_client_connect.c b/src/aws_iot_mqtt_client_connect.c deleted file mode 100644 index 884e2833c5..0000000000 --- a/src/aws_iot_mqtt_client_connect.c +++ /dev/null @@ -1,627 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_connect.c - * @brief MQTT client connect API definition and related functions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#include -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_mqtt_client_common_internal.h" - -typedef union { - uint8_t all; /**< all connect flags */ -#if defined(REVERSED) - struct - { - unsigned int username : 1; /**< 3.1 user name */ - unsigned int password : 1; /**< 3.1 password */ - unsigned int willRetain : 1; /**< will retain setting */ - unsigned int willQoS : 2; /**< will QoS value */ - unsigned int will : 1; /**< will flag */ - unsigned int cleansession : 1; /**< clean session flag */ - unsigned int : 1; /**< unused */ - } bits; -#else - struct { - unsigned int : 1; - /**< unused */ - unsigned int cleansession : 1; - /**< cleansession flag */ - unsigned int will : 1; - /**< will flag */ - unsigned int willQoS : 2; - /**< will QoS value */ - unsigned int willRetain : 1; - /**< will retain setting */ - unsigned int password : 1; - /**< 3.1 password */ - unsigned int username : 1; /**< 3.1 user name */ - } bits; -#endif -} MQTT_Connect_Header_Flags; -/**< connect flags byte */ - -typedef union { - uint8_t all; /**< all connack flags */ -#if defined(REVERSED) - struct - { - unsigned int sessionpresent : 1; /**< session present flag */ - unsigned int : 7; /**< unused */ - } bits; -#else - struct { - unsigned int : 7; - /**< unused */ - unsigned int sessionpresent : 1; /**< session present flag */ - } bits; -#endif -} MQTT_Connack_Header_Flags; -/**< connack flags byte */ - -typedef enum { - CONNACK_CONNECTION_ACCEPTED = 0, - CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR = 1, - CONNACK_IDENTIFIER_REJECTED_ERROR = 2, - CONNACK_SERVER_UNAVAILABLE_ERROR = 3, - CONNACK_BAD_USERDATA_ERROR = 4, - CONNACK_NOT_AUTHORIZED_ERROR = 5 -} MQTT_Connack_Return_Codes; /**< Connect request response codes from server */ - - -/** - * Determines the length of the MQTT connect packet that would be produced using the supplied connect options. - * @param options the options to be used to build the connect packet - * @param the length of buffer needed to contain the serialized version of the packet - * @return IoT_Error_t indicating function execution status - */ -static uint32_t _aws_iot_get_connect_packet_length(IoT_Client_Connect_Params *pConnectParams) { - uint32_t len; - /* Enable when adding further MQTT versions */ - /*size_t len = 0; - switch(pConnectParams->MQTTVersion) { - case MQTT_3_1_1: - len = 10; - break; - }*/ - FUNC_ENTRY; - - len = 10; // Len = 10 for MQTT_3_1_1 - len = len + pConnectParams->clientIDLen + 2; - - if(pConnectParams->isWillMsgPresent) { - len = len + pConnectParams->will.topicNameLen + 2 + pConnectParams->will.msgLen + 2; - } - - if(NULL != pConnectParams->pUsername) { - len = len + pConnectParams->usernameLen + 2; - } - - if(NULL != pConnectParams->pPassword) { - len = len + pConnectParams->passwordLen + 2; - } - - FUNC_EXIT_RC(len); -} - -/** - * Serializes the connect options into the buffer. - * @param buf the buffer into which the packet will be serialized - * @param len the length in bytes of the supplied buffer - * @param options the options to be used to build the connect packet - * @param serialized length - * @return IoT_Error_t indicating function execution status - */ -static IoT_Error_t _aws_iot_mqtt_serialize_connect(unsigned char *pTxBuf, size_t txBufLen, - IoT_Client_Connect_Params *pConnectParams, - size_t *pSerializedLen) { - unsigned char *ptr; - uint32_t len; - IoT_Error_t rc; - MQTTHeader header = {0}; - MQTT_Connect_Header_Flags flags = {0}; - - FUNC_ENTRY; - - if(NULL == pTxBuf || NULL == pConnectParams || NULL == pSerializedLen || - (NULL == pConnectParams->pClientID && 0 != pConnectParams->clientIDLen) || - (NULL != pConnectParams->pClientID && 0 == pConnectParams->clientIDLen)) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - /* Check needed here before we start writing to the Tx buffer */ - switch(pConnectParams->MQTTVersion) { - case MQTT_3_1_1: - break; - default: - return MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR; - } - - ptr = pTxBuf; - len = _aws_iot_get_connect_packet_length(pConnectParams); - if(aws_iot_mqtt_internal_get_final_packet_length_from_remaining_length(len) > txBufLen) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - - rc = aws_iot_mqtt_internal_init_header(&header, CONNECT, QOS0, 0, 0); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - aws_iot_mqtt_internal_write_char(&ptr, header.byte); /* write header */ - - ptr += aws_iot_mqtt_internal_write_len_to_buffer(ptr, len); /* write remaining length */ - - // Enable if adding support for more versions - //if(MQTT_3_1_1 == pConnectParams->MQTTVersion) { - aws_iot_mqtt_internal_write_utf8_string(&ptr, "MQTT", 4); - aws_iot_mqtt_internal_write_char(&ptr, (unsigned char) pConnectParams->MQTTVersion); - //} - - flags.all = 0; - if (pConnectParams->isCleanSession) - { - flags.all |= 1 << 1; - } - - if (pConnectParams->isWillMsgPresent) - { - flags.all |= 1 << 2; - flags.all |= pConnectParams->will.qos << 3; - flags.all |= pConnectParams->will.isRetained << 5; - } - - if(pConnectParams->pPassword) { - flags.all |= 1 << 6; - } - - if(pConnectParams->pUsername) { - flags.all |= 1 << 7; - } - - aws_iot_mqtt_internal_write_char(&ptr, flags.all); - aws_iot_mqtt_internal_write_uint_16(&ptr, pConnectParams->keepAliveIntervalInSec); - - /* If the code have passed the check for incorrect values above, no client id was passed as argument */ - if(NULL == pConnectParams->pClientID) { - aws_iot_mqtt_internal_write_uint_16(&ptr, 0); - } else { - aws_iot_mqtt_internal_write_utf8_string(&ptr, pConnectParams->pClientID, pConnectParams->clientIDLen); - } - - if(pConnectParams->isWillMsgPresent) { - aws_iot_mqtt_internal_write_utf8_string(&ptr, pConnectParams->will.pTopicName, - pConnectParams->will.topicNameLen); - aws_iot_mqtt_internal_write_utf8_string(&ptr, pConnectParams->will.pMessage, pConnectParams->will.msgLen); - } - - if(pConnectParams->pUsername) { - aws_iot_mqtt_internal_write_utf8_string(&ptr, pConnectParams->pUsername, pConnectParams->usernameLen); - } - - if(pConnectParams->pPassword) { - aws_iot_mqtt_internal_write_utf8_string(&ptr, pConnectParams->pPassword, pConnectParams->passwordLen); - } - - *pSerializedLen = (size_t) (ptr - pTxBuf); - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * Deserializes the supplied (wire) buffer into connack data - return code - * @param sessionPresent the session present flag returned (only for MQTT 3.1.1) - * @param connack_rc returned integer value of the connack return code - * @param buf the raw buffer data, of the correct length determined by the remaining length field - * @param buflen the length in bytes of the data in the supplied buffer - * @return IoT_Error_t indicating function execution status - */ -static IoT_Error_t _aws_iot_mqtt_deserialize_connack(unsigned char *pSessionPresent, IoT_Error_t *pConnackRc, - unsigned char *pRxBuf, size_t rxBufLen) { - unsigned char *curdata, *enddata; - unsigned char connack_rc_char; - uint32_t decodedLen, readBytesLen; - IoT_Error_t rc; - MQTT_Connack_Header_Flags flags = {0}; - MQTTHeader header = {0}; - - FUNC_ENTRY; - - if(NULL == pSessionPresent || NULL == pConnackRc || NULL == pRxBuf) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - /* CONNACK header size is fixed at two bytes for fixed and 2 bytes for variable, - * using that as minimum size - * MQTT v3.1.1 Specification 3.2.1 */ - if(4 > rxBufLen) { - FUNC_EXIT_RC(MQTT_RX_BUFFER_TOO_SHORT_ERROR); - } - - curdata = pRxBuf; - enddata = NULL; - decodedLen = 0; - readBytesLen = 0; - - header.byte = aws_iot_mqtt_internal_read_char(&curdata); - if(CONNACK != MQTT_HEADER_FIELD_TYPE(header.byte)) { - FUNC_EXIT_RC(FAILURE); - } - - /* read remaining length */ - rc = aws_iot_mqtt_internal_decode_remaining_length_from_buffer(curdata, &decodedLen, &readBytesLen); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* CONNACK remaining length should always be 2 as per MQTT 3.1.1 spec */ - curdata += (readBytesLen); - enddata = curdata + decodedLen; - if(2 != (enddata - curdata)) { - FUNC_EXIT_RC(MQTT_DECODE_REMAINING_LENGTH_ERROR); - } - - flags.all = aws_iot_mqtt_internal_read_char(&curdata); - *pSessionPresent = flags.bits.sessionpresent; - connack_rc_char = aws_iot_mqtt_internal_read_char(&curdata); - switch(connack_rc_char) { - case CONNACK_CONNECTION_ACCEPTED: - *pConnackRc = MQTT_CONNACK_CONNECTION_ACCEPTED; - break; - case CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR: - *pConnackRc = MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR; - break; - case CONNACK_IDENTIFIER_REJECTED_ERROR: - *pConnackRc = MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR; - break; - case CONNACK_SERVER_UNAVAILABLE_ERROR: - *pConnackRc = MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR; - break; - case CONNACK_BAD_USERDATA_ERROR: - *pConnackRc = MQTT_CONNACK_BAD_USERDATA_ERROR; - break; - case CONNACK_NOT_AUTHORIZED_ERROR: - *pConnackRc = MQTT_CONNACK_NOT_AUTHORIZED_ERROR; - break; - default: - *pConnackRc = MQTT_CONNACK_UNKNOWN_ERROR; - break; - } - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Check if client state is valid for a connect request - * - * Called to check if client state is valid for a connect request - * @param pClient Reference to the IoT Client - * - * @return bool true = state is valid, false = not valid - */ -static bool _aws_iot_mqtt_is_client_state_valid_for_connect(ClientState clientState) { - bool isValid = false; - - switch(clientState) { - case CLIENT_STATE_INVALID: - isValid = false; - break; - case CLIENT_STATE_INITIALIZED: - isValid = true; - break; - case CLIENT_STATE_CONNECTING: - case CLIENT_STATE_CONNECTED_IDLE: - case CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_PUBLISH_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_SUBSCRIBE_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_UNSUBSCRIBE_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_RESUBSCRIBE_IN_PROGRESS: - case CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN: - case CLIENT_STATE_DISCONNECTING: - isValid = false; - break; - case CLIENT_STATE_DISCONNECTED_ERROR: - case CLIENT_STATE_DISCONNECTED_MANUALLY: - case CLIENT_STATE_PENDING_RECONNECT: - isValid = true; - break; - default: - break; - } - - return isValid; -} - -/** - * @brief MQTT Connection Function - * - * Called to establish an MQTT connection with the AWS IoT Service - * This is the internal function which is called by the connect API to perform the operation. - * Not meant to be called directly as it doesn't do validations or client state changes - * - * @param pClient Reference to the IoT Client - * @param pConnectParams Pointer to MQTT connection parameters - * - * @return An IoT Error Type defining successful/failed connection - */ -static IoT_Error_t _aws_iot_mqtt_internal_connect(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pConnectParams) { - Timer connect_timer; - IoT_Error_t connack_rc = FAILURE; - char sessionPresent = 0; - size_t len = 0; - IoT_Error_t rc = FAILURE; - - FUNC_ENTRY; - - if(NULL != pConnectParams) { - /* override default options if new options were supplied */ - rc = aws_iot_mqtt_set_connect_params(pClient, pConnectParams); - if(SUCCESS != rc) { - FUNC_EXIT_RC(MQTT_CONNECTION_ERROR); - } - } - - rc = pClient->networkStack.connect(&(pClient->networkStack), NULL); - if(SUCCESS != rc) { - /* TLS Connect failed, return error */ - FUNC_EXIT_RC(rc); - } - - init_timer(&connect_timer); - countdown_ms(&connect_timer, pClient->clientData.commandTimeoutMs); - - pClient->clientData.keepAliveInterval = pClient->clientData.options.keepAliveIntervalInSec; - rc = _aws_iot_mqtt_serialize_connect(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, - &(pClient->clientData.options), &len); - if(SUCCESS != rc || 0 >= len) { - FUNC_EXIT_RC(rc); - } - - /* send the connect packet */ - rc = aws_iot_mqtt_internal_send_packet(pClient, len, &connect_timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* this will be a blocking call, wait for the CONNACK */ - rc = aws_iot_mqtt_internal_wait_for_read(pClient, CONNACK, &connect_timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* Received CONNACK, check the return code */ - rc = _aws_iot_mqtt_deserialize_connack((unsigned char *) &sessionPresent, &connack_rc, pClient->clientData.readBuf, - pClient->clientData.readBufSize); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - if(MQTT_CONNACK_CONNECTION_ACCEPTED != connack_rc) { - FUNC_EXIT_RC(connack_rc); - } - - pClient->clientStatus.isPingOutstanding = false; - countdown_sec(&pClient->pingTimer, pClient->clientData.keepAliveInterval); - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief MQTT Connection Function - * - * Called to establish an MQTT connection with the AWS IoT Service - * This is the outer function which does the validations and calls the internal connect above - * to perform the actual operation. It is also responsible for client state changes - * - * @param pClient Reference to the IoT Client - * @param pConnectParams Pointer to MQTT connection parameters - * - * @return An IoT Error Type defining successful/failed connection - */ -IoT_Error_t aws_iot_mqtt_connect(AWS_IoT_Client *pClient, IoT_Client_Connect_Params *pConnectParams) { - IoT_Error_t rc, disconRc; - ClientState clientState; - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - aws_iot_mqtt_internal_flushBuffers( pClient ); - clientState = aws_iot_mqtt_get_client_state(pClient); - - if(false == _aws_iot_mqtt_is_client_state_valid_for_connect(clientState)) { - /* Don't send connect packet again if we are already connected - * or in the process of connecting/disconnecting */ - FUNC_EXIT_RC(NETWORK_ALREADY_CONNECTED_ERROR); - } - - aws_iot_mqtt_set_client_state(pClient, clientState, CLIENT_STATE_CONNECTING); - - rc = _aws_iot_mqtt_internal_connect(pClient, pConnectParams); - - if(SUCCESS != rc) { - pClient->networkStack.disconnect(&(pClient->networkStack)); - disconRc = pClient->networkStack.destroy(&(pClient->networkStack)); - if (SUCCESS != disconRc) { - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); - } - aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTING, CLIENT_STATE_DISCONNECTED_ERROR); - } else { - aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTING, CLIENT_STATE_CONNECTED_IDLE); - } - - FUNC_EXIT_RC(rc); -} - -/** - * @brief Disconnect an MQTT Connection - * - * Called to send a disconnect message to the broker. - * This is the internal function which is called by the disconnect API to perform the operation. - * Not meant to be called directly as it doesn't do validations or client state changes - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed send of the disconnect control packet. - */ -static IoT_Error_t _aws_iot_mqtt_internal_disconnect(AWS_IoT_Client *pClient) { - /* We might wait for incomplete incoming publishes to complete */ - Timer timer; - size_t serialized_len = 0; - IoT_Error_t rc; - - FUNC_ENTRY; - - rc = aws_iot_mqtt_internal_serialize_zero(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, - DISCONNECT, - &serialized_len); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - init_timer(&timer); - countdown_ms(&timer, pClient->clientData.commandTimeoutMs); - - /* send the disconnect packet */ - if(serialized_len > 0) { - (void)aws_iot_mqtt_internal_send_packet(pClient, serialized_len, &timer); - } - - /* Clean network stack */ - pClient->networkStack.disconnect(&(pClient->networkStack)); - rc = pClient->networkStack.destroy(&(pClient->networkStack)); - if(0 != rc) { - /* TLS Destroy failed, return error */ - FUNC_EXIT_RC(FAILURE); - } - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Disconnect an MQTT Connection - * - * Called to send a disconnect message to the broker. - * This is the outer function which does the validations and calls the internal disconnect above - * to perform the actual operation. It is also responsible for client state changes - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed send of the disconnect control packet. - */ -IoT_Error_t aws_iot_mqtt_disconnect(AWS_IoT_Client *pClient) { - ClientState clientState; - IoT_Error_t rc; - - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - clientState = aws_iot_mqtt_get_client_state(pClient); - if(!aws_iot_mqtt_is_client_connected(pClient)) { - /* Network is already disconnected. Do nothing */ - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); - } - - rc = aws_iot_mqtt_set_client_state(pClient, clientState, CLIENT_STATE_DISCONNECTING); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = _aws_iot_mqtt_internal_disconnect(pClient); - - if(SUCCESS != rc) { - pClient->clientStatus.clientState = clientState; - } else { - /* If called from Keepalive, this gets set to CLIENT_STATE_DISCONNECTED_ERROR */ - pClient->clientStatus.clientState = CLIENT_STATE_DISCONNECTED_MANUALLY; - } - - FUNC_EXIT_RC(rc); -} - -/** - * @brief MQTT Manual Re-Connection Function - * - * Called to establish an MQTT connection with the AWS IoT Service - * using parameters from the last time a connection was attempted - * Use after disconnect to start the reconnect process manually - * Makes only one reconnect attempt. Sets the client state to - * pending reconnect in case of failure - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed connection - */ -IoT_Error_t aws_iot_mqtt_attempt_reconnect(AWS_IoT_Client *pClient) { - IoT_Error_t rc; - - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(NETWORK_ALREADY_CONNECTED_ERROR); - } - - /* Ignoring return code. failures expected if network is disconnected */ - rc = aws_iot_mqtt_connect(pClient, NULL); - - /* If still disconnected handle disconnect */ - if(CLIENT_STATE_CONNECTED_IDLE != aws_iot_mqtt_get_client_state(pClient)) { - aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_DISCONNECTED_ERROR, CLIENT_STATE_PENDING_RECONNECT); - FUNC_EXIT_RC(NETWORK_ATTEMPTING_RECONNECT); - } - - rc = aws_iot_mqtt_resubscribe(pClient); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - FUNC_EXIT_RC(NETWORK_RECONNECTED); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_mqtt_client_publish.c b/src/aws_iot_mqtt_client_publish.c deleted file mode 100644 index 081a98c2ba..0000000000 --- a/src/aws_iot_mqtt_client_publish.c +++ /dev/null @@ -1,428 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - * Ian Craggs - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=453144 - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_publish.c - * @brief MQTT client publish API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_mqtt_client_common_internal.h" - -/** - * @param stringVar pointer to the String into which the data is to be read - * @param stringLen pointer to variable which has the length of the string - * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned - * @param enddata pointer to the end of the data: do not read beyond - * @return SUCCESS if successful, FAILURE if not - */ -static IoT_Error_t _aws_iot_mqtt_read_string_with_len(char **stringVar, uint16_t *stringLen, - unsigned char **pptr, unsigned char *enddata) { - IoT_Error_t rc = FAILURE; - - FUNC_ENTRY; - /* the first two bytes are the length of the string */ - /* enough length to read the integer? */ - if(enddata - (*pptr) > 1) { - *stringLen = aws_iot_mqtt_internal_read_uint16_t(pptr); /* increments pptr to point past length */ - if(&(*pptr)[*stringLen] <= enddata) { - *stringVar = (char *) *pptr; - *pptr += *stringLen; - rc = SUCCESS; - } - } - - FUNC_EXIT_RC(rc); -} - -/** - * Serializes the supplied publish data into the supplied buffer, ready for sending - * @param pTxBuf the buffer into which the packet will be serialized - * @param txBufLen the length in bytes of the supplied buffer - * @param dup uint8_t - the MQTT dup flag - * @param qos QoS - the MQTT QoS value - * @param retained uint8_t - the MQTT retained flag - * @param packetId uint16_t - the MQTT packet identifier - * @param pTopicName char * - the MQTT topic in the publish - * @param topicNameLen uint16_t - the length of the Topic Name - * @param pPayload byte buffer - the MQTT publish payload - * @param payloadLen size_t - the length of the MQTT payload - * @param pSerializedLen uint32_t - pointer to the variable that stores serialized len - * - * @return An IoT Error Type defining successful/failed call - */ -static IoT_Error_t _aws_iot_mqtt_internal_serialize_publish(unsigned char *pTxBuf, size_t txBufLen, uint8_t dup, - QoS qos, uint8_t retained, uint16_t packetId, - const char *pTopicName, uint16_t topicNameLen, - const unsigned char *pPayload, size_t payloadLen, - uint32_t *pSerializedLen) { - unsigned char *ptr; - uint32_t rem_len; - IoT_Error_t rc; - MQTTHeader header = {0}; - - FUNC_ENTRY; - if(NULL == pTxBuf || NULL == pPayload || NULL == pSerializedLen) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - ptr = pTxBuf; - rem_len = 0; - - rem_len += (uint32_t) (topicNameLen + payloadLen + 2); - if(qos > 0) { - rem_len += 2; /* packetId */ - } - if(aws_iot_mqtt_internal_get_final_packet_length_from_remaining_length(rem_len) > txBufLen) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - - rc = aws_iot_mqtt_internal_init_header(&header, PUBLISH, qos, dup, retained); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - aws_iot_mqtt_internal_write_char(&ptr, header.byte); /* write header */ - - ptr += aws_iot_mqtt_internal_write_len_to_buffer(ptr, rem_len); /* write remaining length */; - - aws_iot_mqtt_internal_write_utf8_string(&ptr, pTopicName, topicNameLen); - - if(qos > 0) { - aws_iot_mqtt_internal_write_uint_16(&ptr, packetId); - } - - memcpy(ptr, pPayload, payloadLen); - ptr += payloadLen; - - *pSerializedLen = (uint32_t) (ptr - pTxBuf); - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * Serializes the ack packet into the supplied buffer. - * @param pTxBuf the buffer into which the packet will be serialized - * @param txBufLen the length in bytes of the supplied buffer - * @param msgType the MQTT packet type - * @param dup the MQTT dup flag - * @param packetId the MQTT packet identifier - * @param pSerializedLen uint32_t - pointer to the variable that stores serialized len - * - * @return An IoT Error Type defining successful/failed call - */ -IoT_Error_t aws_iot_mqtt_internal_serialize_ack(unsigned char *pTxBuf, size_t txBufLen, - MessageTypes msgType, uint8_t dup, uint16_t packetId, - uint32_t *pSerializedLen) { - unsigned char *ptr; - QoS requestQoS; - IoT_Error_t rc; - MQTTHeader header = {0}; - FUNC_ENTRY; - if(NULL == pTxBuf || pSerializedLen == NULL) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - ptr = pTxBuf; - - /* Minimum byte length required by ACK headers is - * 2 for fixed and 2 for variable part */ - if(4 > txBufLen) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - - requestQoS = (PUBREL == msgType) ? QOS1 : QOS0; - rc = aws_iot_mqtt_internal_init_header(&header, msgType, requestQoS, dup, 0); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - aws_iot_mqtt_internal_write_char(&ptr, header.byte); /* write header */ - - ptr += aws_iot_mqtt_internal_write_len_to_buffer(ptr, 2); /* write remaining length */ - aws_iot_mqtt_internal_write_uint_16(&ptr, packetId); - *pSerializedLen = (uint32_t) (ptr - pTxBuf); - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Publish an MQTT message on a topic - * - * Called to publish an MQTT message on a topic. - * @note Call is blocking. In the case of a QoS 0 message the function returns - * after the message was successfully passed to the TLS layer. In the case of QoS 1 - * the function returns after the receipt of the PUBACK control packet. - * This is the internal function which is called by the publish API to perform the operation. - * Not meant to be called directly as it doesn't do validations or client state changes - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to - * @param topicNameLen Length of the topic name - * @param pParams Pointer to Publish Message parameters - * - * @return An IoT Error Type defining successful/failed publish - */ -static IoT_Error_t _aws_iot_mqtt_internal_publish(AWS_IoT_Client *pClient, const char *pTopicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *pParams) { - Timer timer; - uint32_t len = 0; - uint16_t packet_id; - unsigned char dup, type; - IoT_Error_t rc; - - FUNC_ENTRY; - - init_timer(&timer); - countdown_ms(&timer, pClient->clientData.commandTimeoutMs); - - if(QOS1 == pParams->qos) { - pParams->id = aws_iot_mqtt_get_next_packet_id(pClient); - } - - rc = _aws_iot_mqtt_internal_serialize_publish(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, 0, - pParams->qos, pParams->isRetained, pParams->id, pTopicName, - topicNameLen, (unsigned char *) pParams->payload, - pParams->payloadLen, &len); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* send the publish packet */ - rc = aws_iot_mqtt_internal_send_packet(pClient, len, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* Wait for ack if QoS1 */ - if(QOS1 == pParams->qos) { - rc = aws_iot_mqtt_internal_wait_for_read(pClient, PUBACK, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = aws_iot_mqtt_internal_deserialize_ack(&type, &dup, &packet_id, pClient->clientData.readBuf, - pClient->clientData.readBufSize); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - } - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Publish an MQTT message on a topic - * - * Called to publish an MQTT message on a topic. - * @note Call is blocking. In the case of a QoS 0 message the function returns - * after the message was successfully passed to the TLS layer. In the case of QoS 1 - * the function returns after the receipt of the PUBACK control packet. - * This is the outer function which does the validations and calls the internal publish above - * to perform the actual operation. It is also responsible for client state changes - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to - * @param topicNameLen Length of the topic name - * @param pParams Pointer to Publish Message parameters - * - * @return An IoT Error Type defining successful/failed publish - */ -IoT_Error_t aws_iot_mqtt_publish(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *pParams) { - IoT_Error_t rc, pubRc; - ClientState clientState; - - FUNC_ENTRY; - - if(NULL == pClient || NULL == pTopicName || 0 == topicNameLen || NULL == pParams) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(!aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); - } - - clientState = aws_iot_mqtt_get_client_state(pClient); - if(CLIENT_STATE_CONNECTED_IDLE != clientState && CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN != clientState) { - FUNC_EXIT_RC(MQTT_CLIENT_NOT_IDLE_ERROR); - } - - rc = aws_iot_mqtt_set_client_state(pClient, clientState, CLIENT_STATE_CONNECTED_PUBLISH_IN_PROGRESS); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - pubRc = _aws_iot_mqtt_internal_publish(pClient, pTopicName, topicNameLen, pParams); - - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_PUBLISH_IN_PROGRESS, clientState); - if(SUCCESS == pubRc && SUCCESS != rc) { - pubRc = rc; - } - - FUNC_EXIT_RC(pubRc); -} - -/** - * Deserializes the supplied (wire) buffer into publish data - * @param dup returned uint8_t - the MQTT dup flag - * @param qos returned QoS type - the MQTT QoS value - * @param retained returned uint8_t - the MQTT retained flag - * @param pPacketId returned uint16_t - the MQTT packet identifier - * @param pTopicName returned String - the MQTT topic in the publish - * @param topicNameLen returned uint16_t - the length of the MQTT topic in the publish - * @param payload returned byte buffer - the MQTT publish payload - * @param payloadlen returned size_t - the length of the MQTT payload - * @param pRxBuf the raw buffer data, of the correct length determined by the remaining length field - * @param rxBufLen the length in bytes of the data in the supplied buffer - * - * @return An IoT Error Type defining successful/failed call - */ -IoT_Error_t aws_iot_mqtt_internal_deserialize_publish(uint8_t *dup, QoS *qos, - uint8_t *retained, uint16_t *pPacketId, - char **pTopicName, uint16_t *topicNameLen, - unsigned char **payload, size_t *payloadLen, - unsigned char *pRxBuf, size_t rxBufLen) { - unsigned char *curData = pRxBuf; - unsigned char *endData = NULL; - IoT_Error_t rc = FAILURE; - uint32_t decodedLen = 0; - uint32_t readBytesLen = 0; - MQTTHeader header = {0}; - - FUNC_ENTRY; - - if(NULL == dup || NULL == qos || NULL == retained || NULL == pPacketId) { - FUNC_EXIT_RC(FAILURE); - } - - /* Publish header size is at least four bytes. - * Fixed header is two bytes. - * Variable header size depends on QoS And Topic Name. - * QoS level 0 doesn't have a message identifier (0 - 2 bytes) - * Topic Name length fields decide size of topic name field (at least 2 bytes) - * MQTT v3.1.1 Specification 3.3.1 */ - if(4 > rxBufLen) { - FUNC_EXIT_RC(MQTT_RX_BUFFER_TOO_SHORT_ERROR); - } - - header.byte = aws_iot_mqtt_internal_read_char(&curData); - if(PUBLISH != MQTT_HEADER_FIELD_TYPE(header.byte)) { - FUNC_EXIT_RC(FAILURE); - } - - *dup = MQTT_HEADER_FIELD_DUP(header.byte); - *qos = (QoS) MQTT_HEADER_FIELD_QOS(header.byte); - *retained = MQTT_HEADER_FIELD_RETAIN(header.byte); - - /* read remaining length */ - rc = aws_iot_mqtt_internal_decode_remaining_length_from_buffer(curData, &decodedLen, &readBytesLen); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - curData += (readBytesLen); - endData = curData + decodedLen; - - /* do we have enough data to read the protocol version byte? */ - if(SUCCESS != _aws_iot_mqtt_read_string_with_len(pTopicName, topicNameLen, &curData, endData) - || (0 > (endData - curData))) { - FUNC_EXIT_RC(FAILURE); - } - - if(QOS0 != *qos) { - *pPacketId = aws_iot_mqtt_internal_read_uint16_t(&curData); - } - - *payloadLen = (size_t) (endData - curData); - *payload = curData; - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * Deserializes the supplied (wire) buffer into an ack - * @param pPacketType returned integer - the MQTT packet type - * @param dup returned integer - the MQTT dup flag - * @param pPacketId returned integer - the MQTT packet identifier - * @param pRxBuf the raw buffer data, of the correct length determined by the remaining length field - * @param rxBuflen the length in bytes of the data in the supplied buffer - * - * @return An IoT Error Type defining successful/failed call - */ -IoT_Error_t aws_iot_mqtt_internal_deserialize_ack(unsigned char *pPacketType, unsigned char *dup, - uint16_t *pPacketId, unsigned char *pRxBuf, - size_t rxBuflen) { - IoT_Error_t rc = FAILURE; - unsigned char *curdata = pRxBuf; - unsigned char *enddata = NULL; - uint32_t decodedLen = 0; - uint32_t readBytesLen = 0; - MQTTHeader header = {0}; - - FUNC_ENTRY; - - if(NULL == pPacketType || NULL == dup || NULL == pPacketId || NULL == pRxBuf) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - /* PUBACK fixed header size is two bytes, variable header is 2 bytes, MQTT v3.1.1 Specification 3.4.1 */ - if(4 > rxBuflen) { - FUNC_EXIT_RC(MQTT_RX_BUFFER_TOO_SHORT_ERROR); - } - - - header.byte = aws_iot_mqtt_internal_read_char(&curdata); - *dup = MQTT_HEADER_FIELD_DUP(header.byte); - *pPacketType = MQTT_HEADER_FIELD_TYPE(header.byte); - - /* read remaining length */ - rc = aws_iot_mqtt_internal_decode_remaining_length_from_buffer(curdata, &decodedLen, &readBytesLen); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - curdata += (readBytesLen); - enddata = curdata + decodedLen; - - if(enddata - curdata < 2) { - FUNC_EXIT_RC(FAILURE); - } - - *pPacketId = aws_iot_mqtt_internal_read_uint16_t(&curdata); - - FUNC_EXIT_RC(SUCCESS); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_mqtt_client_subscribe.c b/src/aws_iot_mqtt_client_subscribe.c deleted file mode 100644 index 05d9bca50c..0000000000 --- a/src/aws_iot_mqtt_client_subscribe.c +++ /dev/null @@ -1,449 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_subscribe.c - * @brief MQTT client subscribe API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_mqtt_client_common_internal.h" - -/** - * Serializes the supplied subscribe data into the supplied buffer, ready for sending - * @param pTxBuf the buffer into which the packet will be serialized - * @param txBufLen the length in bytes of the supplied buffer - * @param dup unsigned char - the MQTT dup flag - * @param packetId uint16_t - the MQTT packet identifier - * @param topicCount - number of members in the topicFilters and reqQos arrays - * @param pTopicNameList - array of topic filter names - * @param pTopicNameLenList - array of length of topic filter names - * @param pRequestedQoSs - array of requested QoS - * @param pSerializedLen - the length of the serialized data - * - * @return An IoT Error Type defining successful/failed operation - */ -static IoT_Error_t _aws_iot_mqtt_serialize_subscribe(unsigned char *pTxBuf, size_t txBufLen, - unsigned char dup, uint16_t packetId, uint32_t topicCount, - const char **pTopicNameList, uint16_t *pTopicNameLenList, - QoS *pRequestedQoSs, uint32_t *pSerializedLen) { - unsigned char *ptr; - uint32_t itr, rem_len; - IoT_Error_t rc; - MQTTHeader header = {0}; - - FUNC_ENTRY; - if(NULL == pTxBuf || NULL == pSerializedLen) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - ptr = pTxBuf; - rem_len = 2; /* packetId */ - - for(itr = 0; itr < topicCount; ++itr) { - rem_len += (uint32_t) (pTopicNameLenList[itr] + 2 + 1); /* topic + length + req_qos */ - } - - if(aws_iot_mqtt_internal_get_final_packet_length_from_remaining_length(rem_len) > txBufLen) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - - rc = aws_iot_mqtt_internal_init_header(&header, SUBSCRIBE, QOS1, dup, 0); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - /* write header */ - aws_iot_mqtt_internal_write_char(&ptr, header.byte); - - /* write remaining length */ - ptr += aws_iot_mqtt_internal_write_len_to_buffer(ptr, rem_len); - - aws_iot_mqtt_internal_write_uint_16(&ptr, packetId); - - for(itr = 0; itr < topicCount; ++itr) { - aws_iot_mqtt_internal_write_utf8_string(&ptr, pTopicNameList[itr], pTopicNameLenList[itr]); - aws_iot_mqtt_internal_write_char(&ptr, (unsigned char) pRequestedQoSs[itr]); - } - - *pSerializedLen = (uint32_t) (ptr - pTxBuf); - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * Deserializes the supplied (wire) buffer into suback data - * @param pPacketId returned integer - the MQTT packet identifier - * @param maxExpectedQoSCount - the maximum number of members allowed in the grantedQoSs array - * @param pGrantedQoSCount returned uint32_t - number of members in the grantedQoSs array - * @param pGrantedQoSs returned array of QoS type - the granted qualities of service - * @param pRxBuf the raw buffer data, of the correct length determined by the remaining length field - * @param rxBufLen the length in bytes of the data in the supplied buffer - * - * @return An IoT Error Type defining successful/failed operation - */ -static IoT_Error_t _aws_iot_mqtt_deserialize_suback(uint16_t *pPacketId, uint32_t maxExpectedQoSCount, - uint32_t *pGrantedQoSCount, QoS *pGrantedQoSs, - unsigned char *pRxBuf, size_t rxBufLen) { - unsigned char *curData, *endData; - uint32_t decodedLen, readBytesLen; - IoT_Error_t decodeRc; - MQTTHeader header = {0}; - - FUNC_ENTRY; - if(NULL == pPacketId || NULL == pGrantedQoSCount || NULL == pGrantedQoSs) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - curData = pRxBuf; - endData = NULL; - decodeRc = FAILURE; - decodedLen = 0; - readBytesLen = 0; - - /* SUBACK header size is 4 bytes for header and at least one byte for QoS payload - * Need at least a 5 bytes buffer. MQTT3.1.1 specification 3.9 - */ - if(5 > rxBufLen) { - FUNC_EXIT_RC(MQTT_RX_BUFFER_TOO_SHORT_ERROR); - } - - header.byte = aws_iot_mqtt_internal_read_char(&curData); - if(SUBACK != MQTT_HEADER_FIELD_TYPE(header.byte)) { - FUNC_EXIT_RC(FAILURE); - } - - /* read remaining length */ - decodeRc = aws_iot_mqtt_internal_decode_remaining_length_from_buffer(curData, &decodedLen, &readBytesLen); - if(SUCCESS != decodeRc) { - FUNC_EXIT_RC(decodeRc); - } - - curData += (readBytesLen); - endData = curData + decodedLen; - if(endData - curData < 2) { - FUNC_EXIT_RC(FAILURE); - } - - *pPacketId = aws_iot_mqtt_internal_read_uint16_t(&curData); - - *pGrantedQoSCount = 0; - while(curData < endData) { - if(*pGrantedQoSCount > maxExpectedQoSCount) { - FUNC_EXIT_RC(FAILURE); - } - pGrantedQoSs[(*pGrantedQoSCount)++] = (QoS) aws_iot_mqtt_internal_read_char(&curData); - } - - FUNC_EXIT_RC(SUCCESS); -} - -/* Returns MAX_MESSAGE_HANDLERS value if no free index is available */ -static uint32_t _aws_iot_mqtt_get_free_message_handler_index(AWS_IoT_Client *pClient) { - uint32_t itr; - - FUNC_ENTRY; - - for(itr = 0; itr < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; itr++) { - if(pClient->clientData.messageHandlers[itr].topicName == NULL) { - break; - } - } - - FUNC_EXIT_RC(itr); -} - -/** - * @brief Subscribe to an MQTT topic. - * - * Called to send a subscribe message to the broker requesting a subscription - * to an MQTT topic. This is the internal function which is called by the - * subscribe API to perform the operation. Not meant to be called directly as - * it doesn't do validations or client state changes - * @note Call is blocking. The call returns after the receipt of the SUBACK control packet. - * @warning pTopicName and pApplicationHandlerData need to be static in memory. - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to. pTopicName needs to be static in memory since - * no malloc are performed by the SDK - * @param topicNameLen Length of the topic name - * @param pApplicationHandler_t Reference to the handler function for this subscription - * @param pApplicationHandlerData Point to data passed to the callback. - * pApplicationHandlerData also needs to be static in memory since no malloc are performed by the SDK - * - * @return An IoT Error Type defining successful/failed subscription - */ -static IoT_Error_t _aws_iot_mqtt_internal_subscribe(AWS_IoT_Client *pClient, const char *pTopicName, - uint16_t topicNameLen, QoS qos, - pApplicationHandler_t pApplicationHandler, - void *pApplicationHandlerData) { - uint16_t txPacketId, rxPacketId; - uint32_t serializedLen, indexOfFreeMessageHandler, count; - IoT_Error_t rc; - Timer timer; - QoS grantedQoS[3] = {QOS0, QOS0, QOS0}; - - FUNC_ENTRY; - init_timer(&timer); - countdown_ms(&timer, pClient->clientData.commandTimeoutMs); - - serializedLen = 0; - count = 0; - txPacketId = aws_iot_mqtt_get_next_packet_id(pClient); - rxPacketId = 0; - - rc = _aws_iot_mqtt_serialize_subscribe(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, 0, - txPacketId, 1, &pTopicName, &topicNameLen, &qos, &serializedLen); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - indexOfFreeMessageHandler = _aws_iot_mqtt_get_free_message_handler_index(pClient); - if(AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS <= indexOfFreeMessageHandler) { - FUNC_EXIT_RC(MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR); - } - - /* send the subscribe packet */ - rc = aws_iot_mqtt_internal_send_packet(pClient, serializedLen, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* wait for suback */ - rc = aws_iot_mqtt_internal_wait_for_read(pClient, SUBACK, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* Granted QoS can be 0, 1 or 2 */ - rc = _aws_iot_mqtt_deserialize_suback(&rxPacketId, 1, &count, grantedQoS, pClient->clientData.readBuf, - pClient->clientData.readBufSize); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* TODO : Figure out how to test this before activating this check */ - //if(txPacketId != rxPacketId) { - /* Different SUBACK received than expected. Return error - * This can cause issues if the request timeout value is too small */ - // return RX_MESSAGE_INVALID_ERROR; - //} - - pClient->clientData.messageHandlers[indexOfFreeMessageHandler].topicName = - pTopicName; - pClient->clientData.messageHandlers[indexOfFreeMessageHandler].topicNameLen = - topicNameLen; - pClient->clientData.messageHandlers[indexOfFreeMessageHandler].pApplicationHandler = - pApplicationHandler; - pClient->clientData.messageHandlers[indexOfFreeMessageHandler].pApplicationHandlerData = - pApplicationHandlerData; - pClient->clientData.messageHandlers[indexOfFreeMessageHandler].qos = qos; - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Subscribe to an MQTT topic. - * - * Called to send a subscribe message to the broker requesting a subscription - * to an MQTT topic. This is the outer function which does the validations and - * calls the internal subscribe above to perform the actual operation. - * It is also responsible for client state changes - * @note Call is blocking. The call returns after the receipt of the SUBACK control packet. - * @warning pTopicName and pApplicationHandlerData need to be static in memory. - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to. pTopicName needs to be static in memory since - * no malloc are performed by the SDK - * @param topicNameLen Length of the topic name - * @param pApplicationHandler_t Reference to the handler function for this subscription - * @param pApplicationHandlerData Point to data passed to the callback. - * pApplicationHandlerData also needs to be static in memory since no malloc are performed by the SDK - * - * @return An IoT Error Type defining successful/failed subscription - */ -IoT_Error_t aws_iot_mqtt_subscribe(AWS_IoT_Client *pClient, const char *pTopicName, uint16_t topicNameLen, - QoS qos, pApplicationHandler_t pApplicationHandler, void *pApplicationHandlerData) { - ClientState clientState; - IoT_Error_t rc, subRc; - - FUNC_ENTRY; - - if(NULL == pClient || NULL == pTopicName || NULL == pApplicationHandler) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(!aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); - } - - clientState = aws_iot_mqtt_get_client_state(pClient); - if(CLIENT_STATE_CONNECTED_IDLE != clientState && CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN != clientState) { - FUNC_EXIT_RC(MQTT_CLIENT_NOT_IDLE_ERROR); - } - - rc = aws_iot_mqtt_set_client_state(pClient, clientState, CLIENT_STATE_CONNECTED_SUBSCRIBE_IN_PROGRESS); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - subRc = _aws_iot_mqtt_internal_subscribe(pClient, pTopicName, topicNameLen, qos, - pApplicationHandler, pApplicationHandlerData); - - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_SUBSCRIBE_IN_PROGRESS, clientState); - if(SUCCESS == subRc && SUCCESS != rc) { - subRc = rc; - } - - FUNC_EXIT_RC(subRc); -} - -/** - * @brief Subscribe to an MQTT topic. - * - * Called to send a subscribe message to the broker requesting a subscription - * to an MQTT topic. - * This is the internal function which is called by the resubscribe API to perform the operation. - * Not meant to be called directly as it doesn't do validations or client state changes - * @note Call is blocking. The call returns after the receipt of the SUBACK control packet. - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed subscription - */ -static IoT_Error_t _aws_iot_mqtt_internal_resubscribe(AWS_IoT_Client *pClient) { - uint16_t packetId; - uint32_t len, count, existingSubCount, itr; - IoT_Error_t rc; - Timer timer; - QoS grantedQoS[3] = {QOS0, QOS0, QOS0}; - - FUNC_ENTRY; - - packetId = 0; - len = 0; - count = 0; - existingSubCount = _aws_iot_mqtt_get_free_message_handler_index(pClient); - - for(itr = 0; itr < existingSubCount; itr++) { - if(pClient->clientData.messageHandlers[itr].topicName == NULL) { - continue; - } - - init_timer(&timer); - countdown_ms(&timer, pClient->clientData.commandTimeoutMs); - - rc = _aws_iot_mqtt_serialize_subscribe(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, 0, - aws_iot_mqtt_get_next_packet_id(pClient), 1, - &(pClient->clientData.messageHandlers[itr].topicName), - &(pClient->clientData.messageHandlers[itr].topicNameLen), - &(pClient->clientData.messageHandlers[itr].qos), &len); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* send the subscribe packet */ - rc = aws_iot_mqtt_internal_send_packet(pClient, len, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* wait for suback */ - rc = aws_iot_mqtt_internal_wait_for_read(pClient, SUBACK, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* Granted QoS can be 0, 1 or 2 */ - rc = _aws_iot_mqtt_deserialize_suback(&packetId, 1, &count, grantedQoS, pClient->clientData.readBuf, - pClient->clientData.readBufSize); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - } - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Subscribe to an MQTT topic. - * - * Called to send a subscribe message to the broker requesting a subscription - * to an MQTT topic. - * This is the outer function which does the validations and calls the internal resubscribe above - * to perform the actual operation. It is also responsible for client state changes - * @note Call is blocking. The call returns after the receipt of the SUBACK control packet. - * - * @param pClient Reference to the IoT Client - * - * @return An IoT Error Type defining successful/failed subscription - */ -IoT_Error_t aws_iot_mqtt_resubscribe(AWS_IoT_Client *pClient) { - IoT_Error_t rc, resubRc; - - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(false == aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); - } - - if(CLIENT_STATE_CONNECTED_IDLE != aws_iot_mqtt_get_client_state(pClient)) { - FUNC_EXIT_RC(MQTT_CLIENT_NOT_IDLE_ERROR); - } - - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_IDLE, - CLIENT_STATE_CONNECTED_RESUBSCRIBE_IN_PROGRESS); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - resubRc = _aws_iot_mqtt_internal_resubscribe(pClient); - - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_RESUBSCRIBE_IN_PROGRESS, - CLIENT_STATE_CONNECTED_IDLE); - if(SUCCESS == resubRc && SUCCESS != rc) { - resubRc = rc; - } - - FUNC_EXIT_RC(resubRc); -} - -#ifdef __cplusplus -} -#endif - diff --git a/src/aws_iot_mqtt_client_unsubscribe.c b/src/aws_iot_mqtt_client_unsubscribe.c deleted file mode 100644 index ccde6e9b88..0000000000 --- a/src/aws_iot_mqtt_client_unsubscribe.c +++ /dev/null @@ -1,250 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_unsubscribe.c - * @brief MQTT client unsubscribe API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_mqtt_client_common_internal.h" - -/** - * Serializes the supplied unsubscribe data into the supplied buffer, ready for sending - * @param pTxBuf the raw buffer data, of the correct length determined by the remaining length field - * @param txBufLen the length in bytes of the data in the supplied buffer - * @param dup integer - the MQTT dup flag - * @param packetId integer - the MQTT packet identifier - * @param count - number of members in the topicFilters array - * @param pTopicNameList - array of topic filter names - * @param pTopicNameLenList - array of length of topic filter names in pTopicNameList - * @param pSerializedLen - the length of the serialized data - * @return IoT_Error_t indicating function execution status - */ -static IoT_Error_t _aws_iot_mqtt_serialize_unsubscribe(unsigned char *pTxBuf, size_t txBufLen, - uint8_t dup, uint16_t packetId, - uint32_t count, const char **pTopicNameList, - uint16_t *pTopicNameLenList, uint32_t *pSerializedLen) { - unsigned char *ptr = pTxBuf; - uint32_t i = 0; - uint32_t rem_len = 2; /* packetId */ - IoT_Error_t rc; - MQTTHeader header = {0}; - - FUNC_ENTRY; - - for(i = 0; i < count; ++i) { - rem_len += (uint32_t) (pTopicNameLenList[i] + 2); /* topic + length */ - } - - if(aws_iot_mqtt_internal_get_final_packet_length_from_remaining_length(rem_len) > txBufLen) { - FUNC_EXIT_RC(MQTT_TX_BUFFER_TOO_SHORT_ERROR); - } - - rc = aws_iot_mqtt_internal_init_header(&header, UNSUBSCRIBE, QOS1, dup, 0); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - aws_iot_mqtt_internal_write_char(&ptr, header.byte); /* write header */ - - ptr += aws_iot_mqtt_internal_write_len_to_buffer(ptr, rem_len); /* write remaining length */ - - aws_iot_mqtt_internal_write_uint_16(&ptr, packetId); - - for(i = 0; i < count; ++i) { - aws_iot_mqtt_internal_write_utf8_string(&ptr, pTopicNameList[i], pTopicNameLenList[i]); - } - - *pSerializedLen = (uint32_t) (ptr - pTxBuf); - - FUNC_EXIT_RC(SUCCESS); -} - - -/** - * Deserializes the supplied (wire) buffer into unsuback data - * @param pPacketId returned integer - the MQTT packet identifier - * @param pRxBuf the raw buffer data, of the correct length determined by the remaining length field - * @param rxBufLen the length in bytes of the data in the supplied buffer - * @return IoT_Error_t indicating function execution status - */ -static IoT_Error_t _aws_iot_mqtt_deserialize_unsuback(uint16_t *pPacketId, unsigned char *pRxBuf, size_t rxBufLen) { - unsigned char type = 0; - unsigned char dup = 0; - IoT_Error_t rc; - - FUNC_ENTRY; - - rc = aws_iot_mqtt_internal_deserialize_ack(&type, &dup, pPacketId, pRxBuf, rxBufLen); - if(SUCCESS == rc && UNSUBACK != type) { - rc = FAILURE; - } - - FUNC_EXIT_RC(rc); -} - -/** - * @brief Unsubscribe to an MQTT topic. - * - * Called to send an unsubscribe message to the broker requesting removal of a subscription - * to an MQTT topic. - * @note Call is blocking. The call returns after the receipt of the UNSUBACK control packet. - * This is the internal function which is called by the unsubscribe API to perform the operation. - * Not meant to be called directly as it doesn't do validations or client state changes - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to - * @param topicNameLen Length of the topic name - * - * @return An IoT Error Type defining successful/failed unsubscribe call - */ -static IoT_Error_t _aws_iot_mqtt_internal_unsubscribe(AWS_IoT_Client *pClient, const char *pTopicFilter, - uint16_t topicFilterLen) { - /* No NULL checks because this is a static internal function */ - - Timer timer; - - uint16_t packet_id; - uint32_t serializedLen = 0; - uint32_t i = 0; - IoT_Error_t rc; - bool subscriptionExists = false; - - FUNC_ENTRY; - - /* Remove from message handler array */ - for(i = 0; i < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; ++i) { - if(pClient->clientData.messageHandlers[i].topicName != NULL && - (strcmp(pClient->clientData.messageHandlers[i].topicName, pTopicFilter) == 0)) { - subscriptionExists = true; - break; - } - } - - if(false == subscriptionExists) { - FUNC_EXIT_RC(FAILURE); - } - - init_timer(&timer); - countdown_ms(&timer, pClient->clientData.commandTimeoutMs); - - rc = _aws_iot_mqtt_serialize_unsubscribe(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, 0, - aws_iot_mqtt_get_next_packet_id(pClient), 1, &pTopicFilter, - &topicFilterLen, &serializedLen); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* send the unsubscribe packet */ - rc = aws_iot_mqtt_internal_send_packet(pClient, serializedLen, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = aws_iot_mqtt_internal_wait_for_read(pClient, UNSUBACK, &timer); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = _aws_iot_mqtt_deserialize_unsuback(&packet_id, pClient->clientData.readBuf, pClient->clientData.readBufSize); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* Remove from message handler array */ - for(i = 0; i < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; ++i) { - if(pClient->clientData.messageHandlers[i].topicName != NULL && - (strcmp(pClient->clientData.messageHandlers[i].topicName, pTopicFilter) == 0)) { - pClient->clientData.messageHandlers[i].topicName = NULL; - /* We don't want to break here, in case the same topic is registered - * with 2 callbacks. Unlikely scenario */ - } - } - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Unsubscribe to an MQTT topic. - * - * Called to send an unsubscribe message to the broker requesting removal of a subscription - * to an MQTT topic. - * @note Call is blocking. The call returns after the receipt of the UNSUBACK control packet. - * This is the outer function which does the validations and calls the internal unsubscribe above - * to perform the actual operation. It is also responsible for client state changes - * - * @param pClient Reference to the IoT Client - * @param pTopicName Topic Name to publish to - * @param topicNameLen Length of the topic name - * - * @return An IoT Error Type defining successful/failed unsubscribe call - */ -IoT_Error_t aws_iot_mqtt_unsubscribe(AWS_IoT_Client *pClient, const char *pTopicFilter, uint16_t topicFilterLen) { - IoT_Error_t rc, unsubRc; - ClientState clientState; - - if(NULL == pClient || NULL == pTopicFilter) { - return NULL_VALUE_ERROR; - } - - if(!aws_iot_mqtt_is_client_connected(pClient)) { - return NETWORK_DISCONNECTED_ERROR; - } - - clientState = aws_iot_mqtt_get_client_state(pClient); - if(CLIENT_STATE_CONNECTED_IDLE != clientState && CLIENT_STATE_CONNECTED_WAIT_FOR_CB_RETURN != clientState) { - return MQTT_CLIENT_NOT_IDLE_ERROR; - } - - rc = aws_iot_mqtt_set_client_state(pClient, clientState, CLIENT_STATE_CONNECTED_UNSUBSCRIBE_IN_PROGRESS); - if(SUCCESS != rc) { - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_UNSUBSCRIBE_IN_PROGRESS, clientState); - return rc; - } - - unsubRc = _aws_iot_mqtt_internal_unsubscribe(pClient, pTopicFilter, topicFilterLen); - - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_UNSUBSCRIBE_IN_PROGRESS, clientState); - if(SUCCESS == unsubRc && SUCCESS != rc) { - unsubRc = rc; - } - - return unsubRc; -} - -#ifdef __cplusplus -} -#endif - diff --git a/src/aws_iot_mqtt_client_yield.c b/src/aws_iot_mqtt_client_yield.c deleted file mode 100644 index b4feff8129..0000000000 --- a/src/aws_iot_mqtt_client_yield.c +++ /dev/null @@ -1,311 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -// Based on Eclipse Paho. -/******************************************************************************* - * Copyright (c) 2014 IBM Corp. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * and the Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation - *******************************************************************************/ - -/** - * @file aws_iot_mqtt_client_yield.c - * @brief MQTT client yield API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_mqtt_client_common_internal.h" - -/** - * This is for the case when the aws_iot_mqtt_internal_send_packet Fails. - */ -static void _aws_iot_mqtt_force_client_disconnect(AWS_IoT_Client *pClient) { - pClient->clientStatus.clientState = CLIENT_STATE_DISCONNECTED_ERROR; - pClient->networkStack.disconnect(&(pClient->networkStack)); - pClient->networkStack.destroy(&(pClient->networkStack)); -} - -static IoT_Error_t _aws_iot_mqtt_handle_disconnect(AWS_IoT_Client *pClient) { - IoT_Error_t rc; - - FUNC_ENTRY; - - rc = aws_iot_mqtt_disconnect(pClient); - if(rc != SUCCESS) { - // If the aws_iot_mqtt_internal_send_packet prevents us from sending a disconnect packet then we have to clean the stack - _aws_iot_mqtt_force_client_disconnect(pClient); - } - - if(NULL != pClient->clientData.disconnectHandler) { - pClient->clientData.disconnectHandler(pClient, pClient->clientData.disconnectHandlerData); - } - - /* Reset to 0 since this was not a manual disconnect */ - pClient->clientStatus.clientState = CLIENT_STATE_DISCONNECTED_ERROR; - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); -} - - -static IoT_Error_t _aws_iot_mqtt_handle_reconnect(AWS_IoT_Client *pClient) { - IoT_Error_t rc; - - FUNC_ENTRY; - - if(!has_timer_expired(&(pClient->reconnectDelayTimer))) { - /* Timer has not expired. Not time to attempt reconnect yet. - * Return attempting reconnect */ - FUNC_EXIT_RC(NETWORK_ATTEMPTING_RECONNECT); - } - - rc = NETWORK_PHYSICAL_LAYER_DISCONNECTED; - if(NULL != pClient->networkStack.isConnected) { - rc = pClient->networkStack.isConnected(&(pClient->networkStack)); - } - - if(NETWORK_PHYSICAL_LAYER_CONNECTED == rc) { - rc = aws_iot_mqtt_attempt_reconnect(pClient); - if(NETWORK_RECONNECTED == rc) { - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_IDLE, - CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - FUNC_EXIT_RC(NETWORK_RECONNECTED); - } - } - - pClient->clientData.currentReconnectWaitInterval *= 2; - - if(AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL < pClient->clientData.currentReconnectWaitInterval) { - FUNC_EXIT_RC(NETWORK_RECONNECT_TIMED_OUT_ERROR); - } - countdown_ms(&(pClient->reconnectDelayTimer), pClient->clientData.currentReconnectWaitInterval); - FUNC_EXIT_RC(rc); -} - -static IoT_Error_t _aws_iot_mqtt_keep_alive(AWS_IoT_Client *pClient) { - IoT_Error_t rc = SUCCESS; - Timer timer; - size_t serialized_len; - - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(0 == pClient->clientData.keepAliveInterval) { - FUNC_EXIT_RC(SUCCESS); - } - - if(!has_timer_expired(&pClient->pingTimer)) { - FUNC_EXIT_RC(SUCCESS); - } - - if(pClient->clientStatus.isPingOutstanding) { - rc = _aws_iot_mqtt_handle_disconnect(pClient); - FUNC_EXIT_RC(rc); - } - - /* there is no ping outstanding - send one */ - init_timer(&timer); - - countdown_ms(&timer, pClient->clientData.commandTimeoutMs); - serialized_len = 0; - rc = aws_iot_mqtt_internal_serialize_zero(pClient->clientData.writeBuf, pClient->clientData.writeBufSize, - PINGREQ, &serialized_len); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - /* send the ping packet */ - rc = aws_iot_mqtt_internal_send_packet(pClient, serialized_len, &timer); - if(SUCCESS != rc) { - //If sending a PING fails we can no longer determine if we are connected. In this case we decide we are disconnected and begin reconnection attempts - rc = _aws_iot_mqtt_handle_disconnect(pClient); - FUNC_EXIT_RC(rc); - } - - pClient->clientStatus.isPingOutstanding = true; - /* start a timer to wait for PINGRESP from server */ - countdown_sec(&pClient->pingTimer, pClient->clientData.keepAliveInterval); - - FUNC_EXIT_RC(SUCCESS); -} - -/** - * @brief Yield to the MQTT client - * - * Called to yield the current thread to the underlying MQTT client. This time is used by - * the MQTT client to manage PING requests to monitor the health of the TCP connection as - * well as periodically check the socket receive buffer for subscribe messages. Yield() - * must be called at a rate faster than the keepalive interval. It must also be called - * at a rate faster than the incoming message rate as this is the only way the client receives - * processing time to manage incoming messages. - * This is the internal function which is called by the yield API to perform the operation. - * Not meant to be called directly as it doesn't do validations or client state changes - * - * @param pClient Reference to the IoT Client - * @param timeout_ms Maximum number of milliseconds to pass thread execution to the client. - * - * @return An IoT Error Type defining successful/failed client processing. - * If this call results in an error it is likely the MQTT connection has dropped. - * iot_is_mqtt_connected can be called to confirm. - */ - -static IoT_Error_t _aws_iot_mqtt_internal_yield(AWS_IoT_Client *pClient, uint32_t timeout_ms) { - IoT_Error_t yieldRc = SUCCESS; - - uint8_t packet_type; - ClientState clientState; - Timer timer; - init_timer(&timer); - countdown_ms(&timer, timeout_ms); - - FUNC_ENTRY; - - // evaluate timeout at the end of the loop to make sure the actual yield runs at least once - do { - clientState = aws_iot_mqtt_get_client_state(pClient); - if(CLIENT_STATE_PENDING_RECONNECT == clientState) { - if(AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL < pClient->clientData.currentReconnectWaitInterval) { - yieldRc = NETWORK_RECONNECT_TIMED_OUT_ERROR; - break; - } - yieldRc = _aws_iot_mqtt_handle_reconnect(pClient); - /* Network reconnect attempted, check if yield timer expired before - * doing anything else */ - continue; - } - - yieldRc = aws_iot_mqtt_internal_cycle_read(pClient, &timer, &packet_type); - if(SUCCESS == yieldRc) { - yieldRc = _aws_iot_mqtt_keep_alive(pClient); - } else { - // SSL read and write errors are terminal, connection must be closed and retried - if(NETWORK_SSL_READ_ERROR == yieldRc || NETWORK_SSL_WRITE_ERROR == yieldRc || NETWORK_SSL_WRITE_TIMEOUT_ERROR == yieldRc) { - yieldRc = _aws_iot_mqtt_handle_disconnect(pClient); - } - } - - if(NETWORK_DISCONNECTED_ERROR == yieldRc) { - pClient->clientData.counterNetworkDisconnected++; - if(1 == pClient->clientStatus.isAutoReconnectEnabled) { - yieldRc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_DISCONNECTED_ERROR, - CLIENT_STATE_PENDING_RECONNECT); - if(SUCCESS != yieldRc) { - FUNC_EXIT_RC(yieldRc); - } - - pClient->clientData.currentReconnectWaitInterval = AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL; - countdown_ms(&(pClient->reconnectDelayTimer), pClient->clientData.currentReconnectWaitInterval); - /* Depending on timer values, it is possible that yield timer has expired - * Set to rc to attempting reconnect to inform client that autoreconnect - * attempt has started */ - yieldRc = NETWORK_ATTEMPTING_RECONNECT; - } else { - break; - } - } else if(SUCCESS != yieldRc) { - break; - } - } while(!has_timer_expired(&timer)); - - FUNC_EXIT_RC(yieldRc); -} - -/** - * @brief Yield to the MQTT client - * - * Called to yield the current thread to the underlying MQTT client. This time is used by - * the MQTT client to manage PING requests to monitor the health of the TCP connection as - * well as periodically check the socket receive buffer for subscribe messages. Yield() - * must be called at a rate faster than the keepalive interval. It must also be called - * at a rate faster than the incoming message rate as this is the only way the client receives - * processing time to manage incoming messages. - * This is the outer function which does the validations and calls the internal yield above - * to perform the actual operation. It is also responsible for client state changes - * - * @param pClient Reference to the IoT Client - * @param timeout_ms Maximum number of milliseconds to pass thread execution to the client. - * - * @return An IoT Error Type defining successful/failed client processing. - * If this call results in an error it is likely the MQTT connection has dropped. - * iot_is_mqtt_connected can be called to confirm. - */ -IoT_Error_t aws_iot_mqtt_yield(AWS_IoT_Client *pClient, uint32_t timeout_ms) { - IoT_Error_t rc, yieldRc; - ClientState clientState; - - if(NULL == pClient || 0 == timeout_ms) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - clientState = aws_iot_mqtt_get_client_state(pClient); - /* Check if network was manually disconnected */ - if(CLIENT_STATE_DISCONNECTED_MANUALLY == clientState) { - FUNC_EXIT_RC(NETWORK_MANUALLY_DISCONNECTED); - } - - /* If we are in the pending reconnect state, skip other checks. - * Pending reconnect state is only set when auto-reconnect is enabled */ - if(CLIENT_STATE_PENDING_RECONNECT != clientState) { - /* Check if network is disconnected and auto-reconnect is not enabled */ - if(!aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR); - } - - /* Check if client is idle, if not another operation is in progress and we should return */ - if(CLIENT_STATE_CONNECTED_IDLE != clientState) { - FUNC_EXIT_RC(MQTT_CLIENT_NOT_IDLE_ERROR); - } - - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_IDLE, - CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - } - - yieldRc = _aws_iot_mqtt_internal_yield(pClient, timeout_ms); - - if(NETWORK_DISCONNECTED_ERROR != yieldRc && NETWORK_ATTEMPTING_RECONNECT != yieldRc) { - rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS, - CLIENT_STATE_CONNECTED_IDLE); - if(SUCCESS == yieldRc && SUCCESS != rc) { - yieldRc = rc; - } - } - - FUNC_EXIT_RC(yieldRc); -} - -#ifdef __cplusplus -} -#endif - diff --git a/src/aws_iot_shadow.c b/src/aws_iot_shadow.c deleted file mode 100644 index 5c69edb05d..0000000000 --- a/src/aws_iot_shadow.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_shadow.c - * @brief Shadow client API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_shadow_interface.h" -#include "aws_iot_error.h" -#include "aws_iot_log.h" -#include "aws_iot_shadow_actions.h" -#include "aws_iot_shadow_json.h" -#include "aws_iot_shadow_key.h" -#include "aws_iot_shadow_records.h" - -const ShadowInitParameters_t ShadowInitParametersDefault = {(char *) AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, NULL, NULL, - NULL, false, NULL}; - -const ShadowConnectParameters_t ShadowConnectParametersDefault = {(char *) AWS_IOT_MY_THING_NAME, - (char *) AWS_IOT_MQTT_CLIENT_ID, 0, NULL}; - -static char deleteAcceptedTopic[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - -void aws_iot_shadow_reset_last_received_version(void) { - shadowJsonVersionNum = 0; -} - -uint32_t aws_iot_shadow_get_last_received_version(void) { - return shadowJsonVersionNum; -} - -void aws_iot_shadow_enable_discard_old_delta_msgs(void) { - shadowDiscardOldDeltaFlag = true; -} - -void aws_iot_shadow_disable_discard_old_delta_msgs(void) { - shadowDiscardOldDeltaFlag = false; -} - -IoT_Error_t aws_iot_shadow_free(AWS_IoT_Client *pClient) -{ - IoT_Error_t rc; - - if (NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - rc = aws_iot_mqtt_free(pClient); - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_shadow_init(AWS_IoT_Client *pClient, ShadowInitParameters_t *pParams) { - IoT_Client_Init_Params mqttInitParams = IoT_Client_Init_Params_initializer; - IoT_Error_t rc; - - FUNC_ENTRY; - - if(NULL == pClient || NULL == pParams) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - mqttInitParams.enableAutoReconnect = pParams->enableAutoReconnect; - mqttInitParams.pHostURL = pParams->pHost; - mqttInitParams.port = pParams->port; - mqttInitParams.pRootCALocation = pParams->pRootCA; - mqttInitParams.pDeviceCertLocation = pParams->pClientCRT; - mqttInitParams.pDevicePrivateKeyLocation = pParams->pClientKey; - mqttInitParams.mqttPacketTimeout_ms = 5000; - mqttInitParams.mqttCommandTimeout_ms = 20000; - mqttInitParams.tlsHandshakeTimeout_ms = 5000; - mqttInitParams.isSSLHostnameVerify = true; - mqttInitParams.disconnectHandler = pParams->disconnectHandler; - - rc = aws_iot_mqtt_init(pClient, &mqttInitParams); - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - resetClientTokenSequenceNum(); - aws_iot_shadow_reset_last_received_version(); - initDeltaTokens(); - - FUNC_EXIT_RC(SUCCESS); -} - -IoT_Error_t aws_iot_shadow_connect(AWS_IoT_Client *pClient, ShadowConnectParameters_t *pParams) { - IoT_Error_t rc = SUCCESS; - uint16_t deleteAcceptedTopicLen; - IoT_Client_Connect_Params ConnectParams = iotClientConnectParamsDefault; - - FUNC_ENTRY; - - if(NULL == pClient || NULL == pParams || NULL == pParams->pMqttClientId) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - snprintf(myThingName, MAX_SIZE_OF_THING_NAME, "%s", pParams->pMyThingName); - snprintf(mqttClientID, MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES, "%s", pParams->pMqttClientId); - - ConnectParams.keepAliveIntervalInSec = 600; // NOTE: Temporary fix - ConnectParams.MQTTVersion = MQTT_3_1_1; - ConnectParams.isCleanSession = true; - ConnectParams.isWillMsgPresent = false; - ConnectParams.pClientID = pParams->pMqttClientId; - ConnectParams.clientIDLen = pParams->mqttClientIdLen; - ConnectParams.pPassword = NULL; - ConnectParams.pUsername = NULL; - - rc = aws_iot_mqtt_connect(pClient, &ConnectParams); - - if(SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - initializeRecords(pClient); - - if(NULL != pParams->deleteActionHandler) { - snprintf(deleteAcceptedTopic, MAX_SHADOW_TOPIC_LENGTH_BYTES, - "$aws/things/%s/shadow/delete/accepted", myThingName); - deleteAcceptedTopicLen = (uint16_t) strlen(deleteAcceptedTopic); - rc = aws_iot_mqtt_subscribe(pClient, deleteAcceptedTopic, deleteAcceptedTopicLen, QOS1, - pParams->deleteActionHandler, (void *) myThingName); - } - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_shadow_register_delta(AWS_IoT_Client *pMqttClient, jsonStruct_t *pStruct) { - if(NULL == pMqttClient || NULL == pStruct) { - return NULL_VALUE_ERROR; - } - - if(!aws_iot_mqtt_is_client_connected(pMqttClient)) { - return MQTT_CONNECTION_ERROR; - } - - return registerJsonTokenOnDelta(pStruct); -} - -IoT_Error_t aws_iot_shadow_yield(AWS_IoT_Client *pClient, uint32_t timeout) { - if(NULL == pClient) { - return NULL_VALUE_ERROR; - } - - HandleExpiredResponseCallbacks(); - return aws_iot_mqtt_yield(pClient, timeout); -} - -IoT_Error_t aws_iot_shadow_disconnect(AWS_IoT_Client *pClient) { - return aws_iot_mqtt_disconnect(pClient); -} - -IoT_Error_t aws_iot_shadow_update(AWS_IoT_Client *pClient, const char *pThingName, char *pJsonString, - fpActionCallback_t callback, void *pContextData, uint8_t timeout_seconds, - bool isPersistentSubscribe) { - IoT_Error_t rc; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(!aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(MQTT_CONNECTION_ERROR); - } - - rc = aws_iot_shadow_internal_action(pThingName, SHADOW_UPDATE, pJsonString, strlen(pJsonString), callback, pContextData, - timeout_seconds, isPersistentSubscribe); - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_shadow_delete(AWS_IoT_Client *pClient, const char *pThingName, fpActionCallback_t callback, - void *pContextData, uint8_t timeout_seconds, bool isPersistentSubscribe) { - char deleteRequestJsonBuf[MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE]; - IoT_Error_t rc; - - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(!aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(MQTT_CONNECTION_ERROR); - } - - rc = aws_iot_shadow_internal_delete_request_json(deleteRequestJsonBuf, MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE ); - if ( SUCCESS != rc ) { - FUNC_EXIT_RC( rc ); - } - - rc = aws_iot_shadow_internal_action(pThingName, SHADOW_DELETE, deleteRequestJsonBuf, MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE, callback, pContextData, - timeout_seconds, isPersistentSubscribe); - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_shadow_get(AWS_IoT_Client *pClient, const char *pThingName, fpActionCallback_t callback, - void *pContextData, uint8_t timeout_seconds, bool isPersistentSubscribe) { - char getRequestJsonBuf[MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE]; - IoT_Error_t rc; - - FUNC_ENTRY; - - if(NULL == pClient) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - if(!aws_iot_mqtt_is_client_connected(pClient)) { - FUNC_EXIT_RC(MQTT_CONNECTION_ERROR); - } - - rc = aws_iot_shadow_internal_get_request_json(getRequestJsonBuf, MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE ); - if (SUCCESS != rc) { - FUNC_EXIT_RC(rc); - } - - rc = aws_iot_shadow_internal_action(pThingName, SHADOW_GET, getRequestJsonBuf, MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE, callback, pContextData, - timeout_seconds, isPersistentSubscribe); - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_shadow_set_autoreconnect_status(AWS_IoT_Client *pClient, bool newStatus) { - return aws_iot_mqtt_autoreconnect_set_status(pClient, newStatus); -} - -#ifdef __cplusplus -} -#endif - diff --git a/src/aws_iot_shadow_actions.c b/src/aws_iot_shadow_actions.c deleted file mode 100644 index 95b81c56f3..0000000000 --- a/src/aws_iot_shadow_actions.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_shadow_actions.c - * @brief Shadow client Action API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_shadow_actions.h" - -#include "aws_iot_log.h" -#include "aws_iot_shadow_json.h" -#include "aws_iot_shadow_records.h" -#include "aws_iot_config.h" - -IoT_Error_t aws_iot_shadow_internal_action(const char *pThingName, ShadowActions_t action, - const char *pJsonDocumentToBeSent, size_t jsonSize, fpActionCallback_t callback, - void *pCallbackContext, uint32_t timeout_seconds, bool isSticky) { - IoT_Error_t ret_val = SUCCESS; - bool isClientTokenPresent = false; - bool isAckWaitListFree = false; - uint8_t indexAckWaitList; - char extractedClientToken[MAX_SIZE_CLIENT_ID_WITH_SEQUENCE]; - - FUNC_ENTRY; - - if(NULL == pThingName || NULL == pJsonDocumentToBeSent) { - FUNC_EXIT_RC(NULL_VALUE_ERROR); - } - - isClientTokenPresent = extractClientToken(pJsonDocumentToBeSent, jsonSize, extractedClientToken, MAX_SIZE_CLIENT_ID_WITH_SEQUENCE ); - - if(isClientTokenPresent && (NULL != callback)) { - if(getNextFreeIndexOfAckWaitList(&indexAckWaitList)) { - isAckWaitListFree = true; - } - - if(isAckWaitListFree) { - if(!isSubscriptionPresent(pThingName, action)) { - ret_val = subscribeToShadowActionAcks(pThingName, action, isSticky); - } else { - incrementSubscriptionCnt(pThingName, action, isSticky); - } - } - else { - ret_val = FAILURE; - } - } - - if(SUCCESS == ret_val) { - ret_val = publishToShadowAction(pThingName, action, pJsonDocumentToBeSent); - } - - if(isClientTokenPresent && (NULL != callback) && (SUCCESS == ret_val) && isAckWaitListFree) { - addToAckWaitList(indexAckWaitList, pThingName, action, extractedClientToken, callback, pCallbackContext, - timeout_seconds); - } - - FUNC_EXIT_RC(ret_val); -} - -#ifdef __cplusplus -} -#endif diff --git a/src/aws_iot_shadow_json.c b/src/aws_iot_shadow_json.c deleted file mode 100644 index b1d7778361..0000000000 --- a/src/aws_iot_shadow_json.c +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_shadow_json.c - * @brief Shadow client JSON parsing API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_shadow_json.h" - -#include -#include - -#include "aws_iot_json_utils.h" -#include "aws_iot_log.h" -#include "aws_iot_shadow_key.h" -#include "aws_iot_config.h" - -extern char mqttClientID[MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES]; -#define AWS_IOT_SHADOW_CLIENT_TOKEN_KEY "{\"clientToken\":\"" -static uint32_t clientTokenNum = 0; - -//helper functions -static IoT_Error_t convertDataToString(char *pStringBuffer, size_t maxSizoStringBuffer, JsonPrimitiveType type, - void *pData); - -void resetClientTokenSequenceNum(void) { - clientTokenNum = 0; -} - -static IoT_Error_t emptyJsonWithClientToken(char *pBuffer, size_t bufferSize) { - - IoT_Error_t rc = SUCCESS; - size_t dataLenInBuffer = 0; - - if(pBuffer != NULL) - { - dataLenInBuffer = (size_t)snprintf(pBuffer, bufferSize, AWS_IOT_SHADOW_CLIENT_TOKEN_KEY); - }else - { - IOT_ERROR("NULL buffer in emptyJsonWithClientToken\n"); - rc = FAILURE; - } - - if(rc == SUCCESS) - { - if ( dataLenInBuffer < bufferSize ) - { - dataLenInBuffer += (size_t)snprintf(pBuffer + dataLenInBuffer, bufferSize - dataLenInBuffer, "%s-%d", mqttClientID, ( int )clientTokenNum++); - } - else - { - rc = FAILURE; - IOT_ERROR("Supplied buffer too small to create JSON file\n"); - } - } - - if(rc == SUCCESS) - { - if ( dataLenInBuffer < bufferSize ) - { - dataLenInBuffer += (size_t)snprintf( pBuffer + dataLenInBuffer, bufferSize - dataLenInBuffer, "\"}" ); - if ( dataLenInBuffer > bufferSize ) - { - rc = FAILURE; - IOT_ERROR( "Supplied buffer too small to create JSON file\n" ); - } - } - else - { - rc = FAILURE; - IOT_ERROR( "Supplied buffer too small to create JSON file\n" ); - } - } - - FUNC_EXIT_RC(rc); -} - -IoT_Error_t aws_iot_shadow_internal_get_request_json(char *pBuffer, size_t bufferSize) { - return emptyJsonWithClientToken( pBuffer, bufferSize); -} - -IoT_Error_t aws_iot_shadow_internal_delete_request_json(char *pBuffer, size_t bufferSize ) { - return emptyJsonWithClientToken( pBuffer, bufferSize); -} - -static inline IoT_Error_t checkReturnValueOfSnPrintf(int32_t snPrintfReturn, size_t maxSizeOfJsonDocument) { - if(snPrintfReturn < 0) { - return SHADOW_JSON_ERROR; - } else if((size_t) snPrintfReturn >= maxSizeOfJsonDocument) { - return SHADOW_JSON_BUFFER_TRUNCATED; - } - return SUCCESS; -} - -IoT_Error_t aws_iot_shadow_init_json_document(char *pJsonDocument, size_t maxSizeOfJsonDocument) { - - IoT_Error_t ret_val = SUCCESS; - int32_t snPrintfReturn = 0; - - if(pJsonDocument == NULL) { - return NULL_VALUE_ERROR; - } - snPrintfReturn = snprintf(pJsonDocument, maxSizeOfJsonDocument, "{\"state\":{"); - - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, maxSizeOfJsonDocument); - - return ret_val; - -} - -IoT_Error_t aws_iot_shadow_add_desired(char *pJsonDocument, size_t maxSizeOfJsonDocument, uint8_t count, ...) { - IoT_Error_t ret_val = SUCCESS; - size_t tempSize = 0; - int8_t i; - size_t remSizeOfJsonBuffer = maxSizeOfJsonDocument; - int32_t snPrintfReturn = 0; - va_list pArgs; - jsonStruct_t *pTemporary = NULL; - va_start(pArgs, count); - - if(pJsonDocument == NULL) { - va_end(pArgs); - return NULL_VALUE_ERROR; - } - - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - va_end(pArgs); - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, "\"desired\":{"); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - - if(ret_val != SUCCESS) { - va_end(pArgs); - return ret_val; - } - - for(i = 0; i < count; i++) { - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - va_end(pArgs); - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - pTemporary = va_arg (pArgs, jsonStruct_t *); - if(pTemporary != NULL) { - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, "\"%s\":", - pTemporary->pKey); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - if(ret_val != SUCCESS) { - va_end(pArgs); - return ret_val; - } - if(pTemporary->pKey != NULL && pTemporary->pData != NULL) { - ret_val = convertDataToString(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, - pTemporary->type, pTemporary->pData); - } else { - va_end(pArgs); - return NULL_VALUE_ERROR; - } - if(ret_val != SUCCESS) { - va_end(pArgs); - return ret_val; - } - } else { - va_end(pArgs); - return NULL_VALUE_ERROR; - } - } - - va_end(pArgs); - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument) - 1, remSizeOfJsonBuffer, "},"); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - return ret_val; -} - -IoT_Error_t aws_iot_shadow_add_reported(char *pJsonDocument, size_t maxSizeOfJsonDocument, uint8_t count, ...) { - IoT_Error_t ret_val = SUCCESS; - - int8_t i; - size_t remSizeOfJsonBuffer = maxSizeOfJsonDocument; - int32_t snPrintfReturn = 0; - size_t tempSize = 0; - jsonStruct_t *pTemporary; - va_list pArgs; - va_start(pArgs, count); - - if(pJsonDocument == NULL) { - va_end(pArgs); - return NULL_VALUE_ERROR; - } - - - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - va_end(pArgs); - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, "\"reported\":{"); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - - if(ret_val != SUCCESS) { - va_end(pArgs); - return ret_val; - } - - for(i = 0; i < count; i++) { - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - va_end(pArgs); - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - - pTemporary = va_arg (pArgs, jsonStruct_t *); - if(pTemporary != NULL) { - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, "\"%s\":", - pTemporary->pKey); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - if(ret_val != SUCCESS) { - va_end(pArgs); - return ret_val; - } - if(pTemporary->pKey != NULL && pTemporary->pData != NULL) { - ret_val = convertDataToString(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, - pTemporary->type, pTemporary->pData); - } else { - va_end(pArgs); - return NULL_VALUE_ERROR; - } - if(ret_val != SUCCESS) { - va_end(pArgs); - return ret_val; - } - } else { - va_end(pArgs); - return NULL_VALUE_ERROR; - } - } - - va_end(pArgs); - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument) - 1, remSizeOfJsonBuffer, "},"); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - return ret_val; -} - - -int32_t FillWithClientTokenSize(char *pBufferToBeUpdatedWithClientToken, size_t maxSizeOfJsonDocument) { - int32_t snPrintfReturn; - snPrintfReturn = snprintf(pBufferToBeUpdatedWithClientToken, maxSizeOfJsonDocument, "%s-%d", mqttClientID, - (int) clientTokenNum++); - - return snPrintfReturn; -} - -IoT_Error_t aws_iot_fill_with_client_token(char *pBufferToBeUpdatedWithClientToken, size_t maxSizeOfJsonDocument) { - - int32_t snPrintfRet = 0; - snPrintfRet = FillWithClientTokenSize(pBufferToBeUpdatedWithClientToken, maxSizeOfJsonDocument); - return checkReturnValueOfSnPrintf(snPrintfRet, maxSizeOfJsonDocument); - -} - -IoT_Error_t aws_iot_finalize_json_document(char *pJsonDocument, size_t maxSizeOfJsonDocument) { - size_t remSizeOfJsonBuffer = maxSizeOfJsonDocument; - int32_t snPrintfReturn = 0; - size_t tempSize = 0; - IoT_Error_t ret_val = SUCCESS; - - if(pJsonDocument == NULL) { - return NULL_VALUE_ERROR; - } - - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - - // strlen(ShadowTxBuffer) - 1 is to ensure we remove the last ,(comma) that was added - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument) - 1, remSizeOfJsonBuffer, "}, \"%s\":\"", - SHADOW_CLIENT_TOKEN_STRING); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - - if(ret_val != SUCCESS) { - return ret_val; - } - // refactor this XXX repeated code - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - - - snPrintfReturn = FillWithClientTokenSize(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - - if(ret_val != SUCCESS) { - return ret_val; - } - tempSize = maxSizeOfJsonDocument - strlen(pJsonDocument); - if(tempSize <= 1) { - return SHADOW_JSON_ERROR; - } - remSizeOfJsonBuffer = tempSize; - - - snPrintfReturn = snprintf(pJsonDocument + strlen(pJsonDocument), remSizeOfJsonBuffer, "\"}"); - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, remSizeOfJsonBuffer); - - return ret_val; -} - -static IoT_Error_t convertDataToString(char *pStringBuffer, size_t maxSizoStringBuffer, JsonPrimitiveType type, - void *pData) { - int32_t snPrintfReturn = 0; - IoT_Error_t ret_val = SUCCESS; - - if(maxSizoStringBuffer == 0) { - return SHADOW_JSON_ERROR; - } - - if(type == SHADOW_JSON_INT32) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%i,", *(int32_t *) (pData)); - } else if(type == SHADOW_JSON_INT16) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%hi,", *(int16_t *) (pData)); - } else if(type == SHADOW_JSON_INT8) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%hhi,", *(int8_t *) (pData)); - } else if(type == SHADOW_JSON_UINT32) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%u,", *(uint32_t *) (pData)); - } else if(type == SHADOW_JSON_UINT16) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%hu,", *(uint16_t *) (pData)); - } else if(type == SHADOW_JSON_UINT8) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%hhu,", *(uint8_t *) (pData)); - } else if(type == SHADOW_JSON_DOUBLE) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%f,", *(double *) (pData)); - } else if(type == SHADOW_JSON_FLOAT) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%f,", *(float *) (pData)); - } else if(type == SHADOW_JSON_BOOL) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%s,", *(bool *) (pData) ? "true" : "false"); - } else if(type == SHADOW_JSON_STRING) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "\"%s\",", (char *) (pData)); - } else if(type == SHADOW_JSON_OBJECT) { - snPrintfReturn = snprintf(pStringBuffer, maxSizoStringBuffer, "%s,", (char *) (pData)); - } - - - ret_val = checkReturnValueOfSnPrintf(snPrintfReturn, maxSizoStringBuffer); - - return ret_val; -} - -static jsmn_parser shadowJsonParser; -static jsmntok_t jsonTokenStruct[MAX_JSON_TOKEN_EXPECTED]; - -bool isJsonValidAndParse(const char *pJsonDocument, size_t jsonSize, void *pJsonHandler, int32_t *pTokenCount) { - int32_t tokenCount; - - IOT_UNUSED(pJsonHandler); - - jsmn_init(&shadowJsonParser); - - tokenCount = jsmn_parse(&shadowJsonParser, pJsonDocument, jsonSize, jsonTokenStruct, - sizeof(jsonTokenStruct) / sizeof(jsonTokenStruct[0])); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d\n", tokenCount); - return false; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - IOT_WARN("Top Level is not an object\n"); - return false; - } - - *pTokenCount = tokenCount; - - return true; -} - -static IoT_Error_t UpdateValueIfNoObject(const char *pJsonString, jsonStruct_t *pDataStruct, jsmntok_t token) { - IoT_Error_t ret_val = SHADOW_JSON_ERROR; - if(pDataStruct->type == SHADOW_JSON_BOOL && pDataStruct->dataLength >= sizeof(bool)) { - ret_val = parseBooleanValue((bool *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_INT32 && pDataStruct->dataLength >= sizeof(int32_t)) { - ret_val = parseInteger32Value((int32_t *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_INT16 && pDataStruct->dataLength >= sizeof(int16_t)) { - ret_val = parseInteger16Value((int16_t *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_INT8 && pDataStruct->dataLength >= sizeof(int8_t)) { - ret_val = parseInteger8Value((int8_t *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_UINT32 && pDataStruct->dataLength >= sizeof(uint32_t)) { - ret_val = parseUnsignedInteger32Value((uint32_t *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_UINT16 && pDataStruct->dataLength >= sizeof(uint16_t)) { - ret_val = parseUnsignedInteger16Value((uint16_t *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_UINT8 && pDataStruct->dataLength >= sizeof(uint8_t)) { - ret_val = parseUnsignedInteger8Value((uint8_t *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_FLOAT && pDataStruct->dataLength >= sizeof(float)) { - ret_val = parseFloatValue((float *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_DOUBLE && pDataStruct->dataLength >= sizeof(double)) { - ret_val = parseDoubleValue((double *) pDataStruct->pData, pJsonString, &token); - } else if(pDataStruct->type == SHADOW_JSON_STRING) { - ret_val = parseStringValue((char *) pDataStruct->pData, pDataStruct->dataLength, pJsonString, &token); - } - - return ret_val; -} - -bool isJsonKeyMatchingAndUpdateValue(const char *pJsonDocument, void *pJsonHandler, int32_t tokenCount, - jsonStruct_t *pDataStruct, uint32_t *pDataLength, int32_t *pDataPosition) { - int32_t i, metadataEnd; - uint32_t dataLength; - jsmntok_t dataToken; - - IOT_UNUSED(pJsonHandler); - - for(i = 1; i < tokenCount; ) { - if(jsoneq(pJsonDocument, &(jsonTokenStruct[i]), pDataStruct->pKey) == 0) { - dataToken = jsonTokenStruct[i + 1]; - dataLength = (uint32_t) (dataToken.end - dataToken.start); - UpdateValueIfNoObject(pJsonDocument, pDataStruct, dataToken); - *pDataPosition = dataToken.start; - *pDataLength = dataLength; - return true; - } else if(jsoneq(pJsonDocument, &(jsonTokenStruct[i]), "metadata") == 0) { - /* Sanity check: must not be at the last key in the json object. */ - if(i >= tokenCount-2) - { - return false; - } - - /* Record where the metadata object ends. */ - metadataEnd = jsonTokenStruct[i+1].end; - - /* Skip past the "metadata" key and jsmn object element. */ - i+= 2; - - /* Skip past every key inside "metadata". Keys inside "metadata" have - * have an end character before the end of the metadata object. - */ - while(jsonTokenStruct[i].end < metadataEnd) - { - i++; - } - } - else - { - /* Only increment the loop counter if the current object doesn't - * match AND isn't "metadata". - */ - i++; - } - } - return false; -} - -bool isReceivedJsonValid(const char *pJsonDocument, size_t jsonSize ) { - int32_t tokenCount; - - jsmn_init(&shadowJsonParser); - - tokenCount = jsmn_parse(&shadowJsonParser, pJsonDocument, jsonSize, jsonTokenStruct, - sizeof(jsonTokenStruct) / sizeof(jsonTokenStruct[0])); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d\n", tokenCount); - return false; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - return false; - } - - return true; -} - -bool extractClientToken(const char *pJsonDocument, size_t jsonSize, char *pExtractedClientToken, size_t clientTokenSize) { - int32_t tokenCount, i; - size_t length; - jsmntok_t ClientJsonToken; - jsmn_init(&shadowJsonParser); - - tokenCount = jsmn_parse(&shadowJsonParser, pJsonDocument, jsonSize, jsonTokenStruct, - sizeof(jsonTokenStruct) / sizeof(jsonTokenStruct[0])); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d\n", tokenCount); - return false; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - return false; - } - - for(i = 1; i < tokenCount; i++) { - if(jsoneq(pJsonDocument, &jsonTokenStruct[i], SHADOW_CLIENT_TOKEN_STRING) == 0) { - ClientJsonToken = jsonTokenStruct[i + 1]; - length = (size_t) (ClientJsonToken.end - ClientJsonToken.start); - if (clientTokenSize >= length + 1) - { - strncpy( pExtractedClientToken, pJsonDocument + ClientJsonToken.start, length); - pExtractedClientToken[length] = '\0'; - return true; - }else{ - IOT_WARN( "Token size %zu too small for string %zu \n", clientTokenSize, length); - return false; - } - } - } - - return false; -} - -bool extractVersionNumber(const char *pJsonDocument, void *pJsonHandler, int32_t tokenCount, uint32_t *pVersionNumber) { - int32_t i; - IoT_Error_t ret_val = SUCCESS; - - IOT_UNUSED(pJsonHandler); - - for(i = 1; i < tokenCount; i++) { - if(jsoneq(pJsonDocument, &(jsonTokenStruct[i]), SHADOW_VERSION_STRING) == 0) { - ret_val = parseUnsignedInteger32Value(pVersionNumber, pJsonDocument, &jsonTokenStruct[i + 1]); - if(ret_val == SUCCESS) { - return true; - } - } - } - return false; -} - -#ifdef __cplusplus -} -#endif - diff --git a/src/aws_iot_shadow_records.c b/src/aws_iot_shadow_records.c deleted file mode 100644 index d3a2c77202..0000000000 --- a/src/aws_iot_shadow_records.c +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file aws_iot_mqtt_client_subscribe.c - * @brief MQTT client subscribe API definitions - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "aws_iot_shadow_records.h" - -#include -#include - -#include "timer_interface.h" -#include "aws_iot_json_utils.h" -#include "aws_iot_log.h" -#include "aws_iot_shadow_json.h" -#include "aws_iot_config.h" - -typedef struct { - char clientTokenID[MAX_SIZE_CLIENT_ID_WITH_SEQUENCE]; - char thingName[MAX_SIZE_OF_THING_NAME]; - ShadowActions_t action; - fpActionCallback_t callback; - void *pCallbackContext; - bool isFree; - Timer timer; -} ToBeReceivedAckRecord_t; - -typedef struct { - const char *pKey; - void *pStruct; - jsonStructCallback_t callback; - bool isFree; -} JsonTokenTable_t; - -typedef struct { - char Topic[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - uint8_t count; - bool isFree; - bool isSticky; -} SubscriptionRecord_t; - -typedef enum { - SHADOW_ACCEPTED, SHADOW_REJECTED, SHADOW_ACTION -} ShadowAckTopicTypes_t; - -ToBeReceivedAckRecord_t AckWaitList[MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME]; - -AWS_IoT_Client *pMqttClient; - -char myThingName[MAX_SIZE_OF_THING_NAME]; -char mqttClientID[MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES]; - -char shadowDeltaTopic[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - -#define MAX_TOPICS_AT_ANY_GIVEN_TIME 2*MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME -SubscriptionRecord_t SubscriptionList[MAX_TOPICS_AT_ANY_GIVEN_TIME]; - -#define SUBSCRIBE_SETTLING_TIME 2 -char shadowRxBuf[SHADOW_MAX_SIZE_OF_RX_BUFFER]; - -static JsonTokenTable_t tokenTable[MAX_JSON_TOKEN_EXPECTED]; -static uint32_t tokenTableIndex = 0; -static bool deltaTopicSubscribedFlag = false; -uint32_t shadowJsonVersionNum = 0; -bool shadowDiscardOldDeltaFlag = true; - -// local helper functions -static void AckStatusCallback(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData); - -static void shadow_delta_callback(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData); - -static void topicNameFromThingAndAction(char *pTopic, const char *pThingName, ShadowActions_t action, - ShadowAckTopicTypes_t ackType); - -static int16_t getNextFreeIndexOfSubscriptionList(void); - -static void unsubscribeFromAcceptedAndRejected(uint8_t index); - -void initDeltaTokens(void) { - uint32_t i; - for(i = 0; i < MAX_JSON_TOKEN_EXPECTED; i++) { - tokenTable[i].isFree = true; - } - tokenTableIndex = 0; - deltaTopicSubscribedFlag = false; -} - -IoT_Error_t registerJsonTokenOnDelta(jsonStruct_t *pStruct) { - - IoT_Error_t rc = SUCCESS; - - if(!deltaTopicSubscribedFlag) { - snprintf(shadowDeltaTopic, MAX_SHADOW_TOPIC_LENGTH_BYTES, "$aws/things/%s/shadow/update/delta", myThingName); - rc = aws_iot_mqtt_subscribe(pMqttClient, shadowDeltaTopic, (uint16_t) strlen(shadowDeltaTopic), QOS0, - shadow_delta_callback, NULL); - deltaTopicSubscribedFlag = true; - } - - if(tokenTableIndex >= MAX_JSON_TOKEN_EXPECTED) { - return FAILURE; - } - - tokenTable[tokenTableIndex].pKey = pStruct->pKey; - tokenTable[tokenTableIndex].callback = pStruct->cb; - tokenTable[tokenTableIndex].pStruct = pStruct; - tokenTable[tokenTableIndex].isFree = false; - tokenTableIndex++; - - return rc; -} - -static int16_t getNextFreeIndexOfSubscriptionList(void) { - uint8_t i; - for(i = 0; i < MAX_TOPICS_AT_ANY_GIVEN_TIME; i++) { - if(SubscriptionList[i].isFree) { - SubscriptionList[i].isFree = false; - return i; - } - } - return -1; -} - -static void topicNameFromThingAndAction(char *pTopic, const char *pThingName, ShadowActions_t action, - ShadowAckTopicTypes_t ackType) { - - char actionBuf[10]; - char ackTypeBuf[10]; - - if(SHADOW_GET == action) { - strncpy(actionBuf, "get", 10); - } else if(SHADOW_UPDATE == action) { - strncpy(actionBuf, "update", 10); - } else if(SHADOW_DELETE == action) { - strncpy(actionBuf, "delete", 10); - } - - if(SHADOW_ACCEPTED == ackType) { - strncpy(ackTypeBuf, "accepted", 10); - } else if(SHADOW_REJECTED == ackType) { - strncpy(ackTypeBuf, "rejected", 10); - } - - if(SHADOW_ACTION == ackType) { - snprintf(pTopic, MAX_SHADOW_TOPIC_LENGTH_BYTES, "$aws/things/%s/shadow/%s", pThingName, actionBuf); - } else { - snprintf(pTopic, MAX_SHADOW_TOPIC_LENGTH_BYTES, "$aws/things/%s/shadow/%s/%s", pThingName, actionBuf, - ackTypeBuf); - } -} - -static bool isValidShadowVersionUpdate(const char *pTopicName) { - if(strstr(pTopicName, myThingName) != NULL && - ((strstr(pTopicName, "get/accepted") != NULL) || - (strstr(pTopicName, "delta") != NULL))) { - return true; - } - return false; -} - -static void AckStatusCallback(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - int32_t tokenCount; - uint8_t i; - void *pJsonHandler = NULL; - char temporaryClientToken[MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE]; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - if(params->payloadLen >= SHADOW_MAX_SIZE_OF_RX_BUFFER) { - IOT_WARN("Payload larger than RX Buffer"); - return; - } - - memcpy(shadowRxBuf, params->payload, params->payloadLen); - shadowRxBuf[params->payloadLen] = '\0'; // jsmn_parse relies on a string - - if(!isJsonValidAndParse(shadowRxBuf, SHADOW_MAX_SIZE_OF_RX_BUFFER, pJsonHandler, &tokenCount)) { - IOT_WARN("Received JSON is not valid"); - return; - } - - if(isValidShadowVersionUpdate(topicName)) { - uint32_t tempVersionNumber = 0; - if(extractVersionNumber(shadowRxBuf, pJsonHandler, tokenCount, &tempVersionNumber)) { - if(tempVersionNumber > shadowJsonVersionNum) { - shadowJsonVersionNum = tempVersionNumber; - } - } - } - - if(extractClientToken(shadowRxBuf, SHADOW_MAX_SIZE_OF_RX_BUFFER, temporaryClientToken, MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE)) { - for(i = 0; i < MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME; i++) { - if(!AckWaitList[i].isFree) { - if(strcmp(AckWaitList[i].clientTokenID, temporaryClientToken) == 0) { - Shadow_Ack_Status_t status = SHADOW_ACK_REJECTED; - if(strstr(topicName, "accepted") != NULL) { - status = SHADOW_ACK_ACCEPTED; - } else if(strstr(topicName, "rejected") != NULL) { - status = SHADOW_ACK_REJECTED; - } - if(status == SHADOW_ACK_ACCEPTED || status == SHADOW_ACK_REJECTED) { - if(AckWaitList[i].callback != NULL) { - AckWaitList[i].callback(AckWaitList[i].thingName, AckWaitList[i].action, status, - shadowRxBuf, AckWaitList[i].pCallbackContext); - } - unsubscribeFromAcceptedAndRejected(i); - AckWaitList[i].isFree = true; - return; - } - } - } - } - } -} - -static int16_t findIndexOfSubscriptionList(const char *pTopic) { - uint8_t i; - for(i = 0; i < MAX_TOPICS_AT_ANY_GIVEN_TIME; i++) { - if(!SubscriptionList[i].isFree) { - if((strcmp(pTopic, SubscriptionList[i].Topic) == 0)) { - return i; - } - } - } - return -1; -} - -static void unsubscribeFromAcceptedAndRejected(uint8_t index) { - - char TemporaryTopicNameAccepted[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - char TemporaryTopicNameRejected[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - IoT_Error_t ret_val = SUCCESS; - - int16_t indexSubList; - - topicNameFromThingAndAction(TemporaryTopicNameAccepted, AckWaitList[index].thingName, AckWaitList[index].action, - SHADOW_ACCEPTED); - topicNameFromThingAndAction(TemporaryTopicNameRejected, AckWaitList[index].thingName, AckWaitList[index].action, - SHADOW_REJECTED); - - indexSubList = findIndexOfSubscriptionList(TemporaryTopicNameAccepted); - if((indexSubList >= 0)) { - if(!SubscriptionList[indexSubList].isSticky && (SubscriptionList[indexSubList].count == 1)) { - ret_val = aws_iot_mqtt_unsubscribe(pMqttClient, TemporaryTopicNameAccepted, - (uint16_t) strlen(TemporaryTopicNameAccepted)); - if(ret_val == SUCCESS) { - SubscriptionList[indexSubList].isFree = true; - } - } else if(SubscriptionList[indexSubList].count > 1) { - SubscriptionList[indexSubList].count--; - } - } - - indexSubList = findIndexOfSubscriptionList(TemporaryTopicNameRejected); - if((indexSubList >= 0)) { - if(!SubscriptionList[indexSubList].isSticky && (SubscriptionList[indexSubList].count == 1)) { - ret_val = aws_iot_mqtt_unsubscribe(pMqttClient, TemporaryTopicNameRejected, - (uint16_t) strlen(TemporaryTopicNameRejected)); - if(ret_val == SUCCESS) { - SubscriptionList[indexSubList].isFree = true; - } - } else if(SubscriptionList[indexSubList].count > 1) { - SubscriptionList[indexSubList].count--; - } - } -} - -void initializeRecords(AWS_IoT_Client *pClient) { - uint8_t i; - for(i = 0; i < MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME; i++) { - AckWaitList[i].isFree = true; - } - for(i = 0; i < MAX_TOPICS_AT_ANY_GIVEN_TIME; i++) { - SubscriptionList[i].isFree = true; - SubscriptionList[i].count = 0; - SubscriptionList[i].isSticky = false; - } - - pMqttClient = pClient; -} - -bool isSubscriptionPresent(const char *pThingName, ShadowActions_t action) { - - uint8_t i = 0; - bool isAcceptedPresent = false; - bool isRejectedPresent = false; - char TemporaryTopicNameAccepted[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - char TemporaryTopicNameRejected[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - - topicNameFromThingAndAction(TemporaryTopicNameAccepted, pThingName, action, SHADOW_ACCEPTED); - topicNameFromThingAndAction(TemporaryTopicNameRejected, pThingName, action, SHADOW_REJECTED); - - for(i = 0; i < MAX_TOPICS_AT_ANY_GIVEN_TIME; i++) { - if(!SubscriptionList[i].isFree) { - if((strcmp(TemporaryTopicNameAccepted, SubscriptionList[i].Topic) == 0)) { - isAcceptedPresent = true; - } else if((strcmp(TemporaryTopicNameRejected, SubscriptionList[i].Topic) == 0)) { - isRejectedPresent = true; - } - } - } - - if(isRejectedPresent && isAcceptedPresent) { - return true; - } - - return false; -} - -IoT_Error_t subscribeToShadowActionAcks(const char *pThingName, ShadowActions_t action, bool isSticky) { - IoT_Error_t ret_val = SUCCESS; - - bool clearBothEntriesFromList = true; - int16_t indexAcceptedSubList = 0; - int16_t indexRejectedSubList = 0; - Timer subSettlingtimer; - indexAcceptedSubList = getNextFreeIndexOfSubscriptionList(); - indexRejectedSubList = getNextFreeIndexOfSubscriptionList(); - - if(indexAcceptedSubList >= 0 && indexRejectedSubList >= 0) { - topicNameFromThingAndAction(SubscriptionList[indexAcceptedSubList].Topic, pThingName, action, SHADOW_ACCEPTED); - ret_val = aws_iot_mqtt_subscribe(pMqttClient, SubscriptionList[indexAcceptedSubList].Topic, - (uint16_t) strlen(SubscriptionList[indexAcceptedSubList].Topic), QOS0, - AckStatusCallback, NULL); - if(ret_val == SUCCESS) { - SubscriptionList[indexAcceptedSubList].count = 1; - SubscriptionList[indexAcceptedSubList].isSticky = isSticky; - topicNameFromThingAndAction(SubscriptionList[indexRejectedSubList].Topic, pThingName, action, - SHADOW_REJECTED); - ret_val = aws_iot_mqtt_subscribe(pMqttClient, SubscriptionList[indexRejectedSubList].Topic, - (uint16_t) strlen(SubscriptionList[indexRejectedSubList].Topic), QOS0, - AckStatusCallback, NULL); - if(ret_val == SUCCESS) { - SubscriptionList[indexRejectedSubList].count = 1; - SubscriptionList[indexRejectedSubList].isSticky = isSticky; - clearBothEntriesFromList = false; - - // wait for SUBSCRIBE_SETTLING_TIME seconds to let the subscription take effect - init_timer(&subSettlingtimer); - countdown_sec(&subSettlingtimer, SUBSCRIBE_SETTLING_TIME); - while(!has_timer_expired(&subSettlingtimer)); - - } - } - } - - if(clearBothEntriesFromList) { - if(indexAcceptedSubList >= 0) { - SubscriptionList[indexAcceptedSubList].isFree = true; - - if(SubscriptionList[indexAcceptedSubList].count == 1) { - aws_iot_mqtt_unsubscribe(pMqttClient, SubscriptionList[indexAcceptedSubList].Topic, - (uint16_t) strlen(SubscriptionList[indexAcceptedSubList].Topic)); - } - } - if(indexRejectedSubList >= 0) { - SubscriptionList[indexRejectedSubList].isFree = true; - } - - } - - return ret_val; -} - -void incrementSubscriptionCnt(const char *pThingName, ShadowActions_t action, bool isSticky) { - char TemporaryTopicNameAccepted[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - char TemporaryTopicNameRejected[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - uint8_t i; - topicNameFromThingAndAction(TemporaryTopicNameAccepted, pThingName, action, SHADOW_ACCEPTED); - topicNameFromThingAndAction(TemporaryTopicNameRejected, pThingName, action, SHADOW_REJECTED); - - for(i = 0; i < MAX_TOPICS_AT_ANY_GIVEN_TIME; i++) { - if(!SubscriptionList[i].isFree) { - if((strcmp(TemporaryTopicNameAccepted, SubscriptionList[i].Topic) == 0) - || (strcmp(TemporaryTopicNameRejected, SubscriptionList[i].Topic) == 0)) { - SubscriptionList[i].count++; - SubscriptionList[i].isSticky = isSticky; - } - } - } -} - -IoT_Error_t publishToShadowAction(const char *pThingName, ShadowActions_t action, const char *pJsonDocumentToBeSent) { - IoT_Error_t ret_val = SUCCESS; - char TemporaryTopicName[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - IoT_Publish_Message_Params msgParams; - - if(NULL == pThingName || NULL == pJsonDocumentToBeSent) { - return NULL_VALUE_ERROR; - } - - topicNameFromThingAndAction(TemporaryTopicName, pThingName, action, SHADOW_ACTION); - - msgParams.qos = QOS0; - msgParams.isRetained = 0; - msgParams.payloadLen = strlen(pJsonDocumentToBeSent); - msgParams.payload = (char *) pJsonDocumentToBeSent; - ret_val = aws_iot_mqtt_publish(pMqttClient, TemporaryTopicName, (uint16_t) strlen(TemporaryTopicName), &msgParams); - - return ret_val; -} - -bool getNextFreeIndexOfAckWaitList(uint8_t *pIndex) { - uint8_t i; - bool rc = false; - - if(NULL == pIndex) { - return false; - } - - for(i = 0; i < MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME; i++) { - if(AckWaitList[i].isFree) { - *pIndex = i; - rc = true; - break; - } - } - - return rc; -} - -void addToAckWaitList(uint8_t indexAckWaitList, const char *pThingName, ShadowActions_t action, - const char *pExtractedClientToken, fpActionCallback_t callback, void *pCallbackContext, - uint32_t timeout_seconds) { - AckWaitList[indexAckWaitList].callback = callback; - memcpy(AckWaitList[indexAckWaitList].clientTokenID, pExtractedClientToken, MAX_SIZE_CLIENT_ID_WITH_SEQUENCE); - memcpy(AckWaitList[indexAckWaitList].thingName, pThingName, MAX_SIZE_OF_THING_NAME); - AckWaitList[indexAckWaitList].pCallbackContext = pCallbackContext; - AckWaitList[indexAckWaitList].action = action; - init_timer(&(AckWaitList[indexAckWaitList].timer)); - countdown_sec(&(AckWaitList[indexAckWaitList].timer), timeout_seconds); - AckWaitList[indexAckWaitList].isFree = false; -} - -void HandleExpiredResponseCallbacks(void) { - uint8_t i; - for(i = 0; i < MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME; i++) { - if(!AckWaitList[i].isFree) { - if(has_timer_expired(&(AckWaitList[i].timer))) { - if(AckWaitList[i].callback != NULL) { - AckWaitList[i].callback(AckWaitList[i].thingName, AckWaitList[i].action, SHADOW_ACK_TIMEOUT, - shadowRxBuf, AckWaitList[i].pCallbackContext); - } - AckWaitList[i].isFree = true; - unsubscribeFromAcceptedAndRejected(i); - } - } - } -} - -static void shadow_delta_callback(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData) { - int32_t tokenCount; - uint32_t i = 0; - void *pJsonHandler = NULL; - int32_t DataPosition; - uint32_t dataLength; - uint32_t tempVersionNumber = 0; - - FUNC_ENTRY; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - if(params->payloadLen >= SHADOW_MAX_SIZE_OF_RX_BUFFER) { - IOT_WARN("Payload larger than RX Buffer"); - return; - } - - memcpy(shadowRxBuf, params->payload, params->payloadLen); - shadowRxBuf[params->payloadLen] = '\0'; // jsmn_parse relies on a string - - if(!isJsonValidAndParse(shadowRxBuf, SHADOW_MAX_SIZE_OF_RX_BUFFER, pJsonHandler, &tokenCount)) { - IOT_WARN("Received JSON is not valid"); - return; - } - - if(shadowDiscardOldDeltaFlag) { - if(extractVersionNumber(shadowRxBuf, pJsonHandler, tokenCount, &tempVersionNumber)) { - if(tempVersionNumber > shadowJsonVersionNum) { - shadowJsonVersionNum = tempVersionNumber; - } else { - IOT_WARN("Old Delta Message received - Ignoring rx: %d local: %d", tempVersionNumber, - shadowJsonVersionNum); - return; - } - } - } - - for(i = 0; i < tokenTableIndex; i++) { - if(!tokenTable[i].isFree) { - if(isJsonKeyMatchingAndUpdateValue(shadowRxBuf, pJsonHandler, tokenCount, - (jsonStruct_t *) tokenTable[i].pStruct, &dataLength, &DataPosition)) { - if(tokenTable[i].callback != NULL) { - tokenTable[i].callback(shadowRxBuf + DataPosition, dataLength, - (jsonStruct_t *) tokenTable[i].pStruct); - } - } - } - } -} - -#ifdef __cplusplus -} -#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..45b6c9474b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +# Tests are currently only supported on Linux. +if( NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + message( FATAL_ERROR "Currently, tests must run on Linux. Detected system: ${CMAKE_SYSTEM_NAME}." ) +endif() + +# MQTT tests. +add_subdirectory( mqtt ) + +# Shadow tests. +add_subdirectory( shadow ) diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 3625bae683..0000000000 --- a/tests/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Tests -This folder contains tests to verify SDK functionality. These have been tested to work with Linux but haven't been ported to any specific platform. For additional information about porting the Device SDK for embedded C onto additional platforms please refer to the [PortingGuide](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/PortingGuide.md/). -A description for each folder is given below - -## integration -This folder contains integration tests that run directly against the server. For further information on how to run these tests check out the [Integration Test README](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/tests/integration/README.md/). - -## unit -This folder contains unit tests that test SDK functionality against a Mock TLS layer. They are built using the CppUTest testing framework. For further information on how to run these tests check out the [Unit Test README](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/tests/unit/README.md/). \ No newline at end of file diff --git a/tests/aws_iot_tests_config.h b/tests/aws_iot_tests_config.h new file mode 100644 index 0000000000..0d1d4f11aa --- /dev/null +++ b/tests/aws_iot_tests_config.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* This file contains configuration settings for the tests. Currently, the tests + * must run on POSIX systems. */ + +#ifndef _AWS_IOT_TESTS_CONFIG_H_ +#define _AWS_IOT_TESTS_CONFIG_H_ + +/* Test framework include. */ +#include "unity_fixture_malloc_overrides.h" + +/* MQTT server endpoints used for the tests. */ +#if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 + /* Mosquitto test server. */ + #define AWS_IOT_TEST_SECURED_CONNECTION ( 0 ) + #define AWS_IOT_TEST_SERVER "test.mosquitto.org" + #define AWS_IOT_TEST_PORT ( 1883 ) +#else + /* AWS IoT MQTT server. */ + #define AWS_IOT_TEST_SECURED_CONNECTION ( 1 ) + + /* AWS IoT endpoint and credentials. */ + #ifndef AWS_IOT_TEST_SERVER + #define AWS_IOT_TEST_SERVER "" + #endif + #ifndef AWS_IOT_TEST_PORT + #define AWS_IOT_TEST_PORT ( 443 ) + #endif + #ifndef AWS_IOT_TEST_ROOT_CA + #define AWS_IOT_TEST_ROOT_CA "" + #endif + #ifndef AWS_IOT_TEST_CLIENT_CERT + #define AWS_IOT_TEST_CLIENT_CERT "" + #endif + #ifndef AWS_IOT_TEST_PRIVATE_KEY + #define AWS_IOT_TEST_PRIVATE_KEY "" + #endif +#endif /* if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 */ + +/* Shadow tests configuration. */ +#ifndef AWS_IOT_TEST_SHADOW_THING_NAME + #define AWS_IOT_TEST_SHADOW_THING_NAME "" +#endif + +/* Queue library configuration. */ +#define AWS_IOT_QUEUE_ENABLE_ASSERTS ( 1 ) + +/* Shadow library configuration. */ +#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) + +/* MQTT library configuration. */ +#define AWS_IOT_MQTT_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_MQTT_ENABLE_METRICS ( 0 ) +#define AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) +#define AWS_IOT_MQTT_TEST ( 1 ) + +/* Memory allocation function configuration. Note that these functions will not + * be affected by AWS_IOT_STATIC_MEMORY_ONLY. */ +#define AwsIotNetwork_Malloc unity_malloc_mt +#define AwsIotNetwork_Free unity_free_mt +#define AwsIotClock_Malloc unity_malloc_mt +#define AwsIotClock_Free unity_free_mt +#define AwsIotThreads_Malloc unity_malloc_mt +#define AwsIotThreads_Free unity_free_mt +#define AwsIotLogging_Malloc unity_malloc_mt +#define AwsIotLogging_Free unity_free_mt +/* #define AwsIotLogging_StaticBufferSize */ +#define AwsIotTest_Malloc unity_malloc_mt +#define AwsIotTest_Free unity_free_mt + +/* Static memory resource settings for the tests. These values must be large + * enough to support the stress tests. */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #define AWS_IOT_MQTT_CONNECTIONS ( 2 ) + #define AWS_IOT_MQTT_SUBSCRIPTIONS ( 80 ) +#endif + +/* Memory allocation function configuration for libraries affected by + * AWS_IOT_STATIC_MEMORY_ONLY. */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 0 + #define AwsIotMqtt_MallocConnection unity_malloc_mt + #define AwsIotMqtt_FreeConnection unity_free_mt + #define AwsIotMqtt_MallocMessage unity_malloc_mt + #define AwsIotMqtt_FreeMessage unity_free_mt + #define AwsIotMqtt_MallocOperation unity_malloc_mt + #define AwsIotMqtt_FreeOperation unity_free_mt + #define AwsIotMqtt_MallocSubscription unity_malloc_mt + #define AwsIotMqtt_FreeSubscription unity_free_mt + #define AwsIotMqtt_MallocTimerEvent unity_malloc_mt + #define AwsIotMqtt_FreeTimerEvent unity_free_mt + #define AwsIotShadow_MallocOperation unity_malloc_mt + #define AwsIotShadow_FreeOperation unity_free_mt + #define AwsIotShadow_MallocString unity_malloc_mt + #define AwsIotShadow_FreeString unity_free_mt + #define AwsIotShadow_MallocSubscription unity_malloc_mt + #define AwsIotShadow_FreeSubscription unity_free_mt +#endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 0 */ + +#endif /* ifndef _AWS_IOT_TESTS_CONFIG_H_ */ diff --git a/tests/aws_iot_tests_network.c b/tests/aws_iot_tests_network.c new file mode 100644 index 0000000000..53d1d8b01d --- /dev/null +++ b/tests/aws_iot_tests_network.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_network.c + * @brief Common network function implementations for the tests. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/* Platform layer includes. */ +#include "platform/aws_iot_network.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Network interface setup function for the tests. + * + * Creates a global network connection to be used by the tests. + * @return true if setup succeeded; false otherwise. + * + * @see #AwsIotTest_NetworkCleanup + */ +bool AwsIotTest_NetworkSetup( void ); + +/** + * @brief Network interface cleanup function for the tests. + * + * @see #AwsIotTest_NetworkSetup + */ +void AwsIotTest_NetworkCleanup( void ); + +/** + * @brief Network interface connect function for the tests. + * + * Creates a new network connection for use with MQTT. + * + * @param[out] pNewConnection The handle by which this new connection will be referenced. + * @param[in] pMqttConnection The MQTT connection associated with the new network connection. + * + * @return true if a new network connection was successfully created; false otherwise. + */ +bool AwsIotTest_NetworkConnect( void ** const pNewConnection, + AwsIotMqttConnection_t * pMqttConnection ); + +/** + * @brief Network interface close connection function for the tests. + * + * @param[in] pDisconnectContext The connection to close. Pass NULL to close + * the global network connection created by #AwsIotTest_NetworkSetup. + */ +void AwsIotTest_NetworkClose( void * pDisconnectContext ); + +/** + * @brief Network interface cleanup function for the tests. + * + * @param[in] pNetworkConnection The connection to destroy. + */ +void AwsIotTest_NetworkDestroy( void * pNetworkConnection ); + +/*-----------------------------------------------------------*/ + +/** + * @brief The network connection shared among the tests. + */ +static AwsIotNetworkConnection_t _networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + +/** + * @brief The MQTT network interface shared among the tests. + */ +AwsIotMqttNetIf_t _AwsIotTestNetworkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + +/** + * @brief The MQTT connection shared among the tests. + */ +AwsIotMqttConnection_t _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + +/*-----------------------------------------------------------*/ + +bool AwsIotTest_NetworkSetup( void ) +{ + /* Initialize the network library. */ + if( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) + { + return false; + } + + if( AwsIotTest_NetworkConnect( &_networkConnection, + &_AwsIotTestMqttConnection ) == false ) + { + AwsIotNetwork_Cleanup(); + + return false; + } + + /* Set the members of the network interface. */ + _AwsIotTestNetworkInterface.pDisconnectContext = NULL; + _AwsIotTestNetworkInterface.disconnect = AwsIotTest_NetworkClose; + _AwsIotTestNetworkInterface.pSendContext = ( void * ) _networkConnection; + _AwsIotTestNetworkInterface.send = AwsIotNetwork_Send; + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotTest_NetworkCleanup( void ) +{ + /* Close the TCP connection to the server. */ + if( _networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + { + AwsIotTest_NetworkClose( NULL ); + AwsIotTest_NetworkDestroy( _networkConnection ); + _networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + } + + /* Clean up the network library. */ + AwsIotNetwork_Cleanup(); + + /* Clear the network interface. */ + ( void ) memset( &_AwsIotTestNetworkInterface, 0x00, sizeof( AwsIotMqttNetIf_t ) ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotTest_NetworkConnect( void ** const pNewConnection, + AwsIotMqttConnection_t * pMqttConnection ) +{ + AwsIotNetworkConnection_t newConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + AwsIotNetworkTlsInfo_t * pTlsInfo = NULL; + + /* Set up TLS if the endpoint is secured. These tests should always use ALPN. */ + #if AWS_IOT_TEST_SECURED_CONNECTION == 1 + AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER; + pTlsInfo = &tlsInfo; + + /* Set credentials for secured connection. Lengths are optional on some + * platforms and only set if needed. */ + tlsInfo.pRootCa = AWS_IOT_TEST_ROOT_CA; + tlsInfo.pClientCert = AWS_IOT_TEST_CLIENT_CERT; + tlsInfo.pPrivateKey = AWS_IOT_TEST_PRIVATE_KEY; + + #ifdef AWS_IOT_TEST_ROOT_CA_LENGTH + tlsInfo.rootCaLength = AWS_IOT_TEST_ROOT_CA_LENGTH; + #endif + + #ifdef AWS_IOT_TEST_CLIENT_CERT_LENGTH + tlsInfo.clientCertLength = AWS_IOT_TEST_CLIENT_CERT_LENGTH; + #endif + + #ifdef AWS_IOT_TEST_PRIVATE_KEY_LENGTH + tlsInfo.privateKeyLength = AWS_IOT_TEST_PRIVATE_KEY_LENGTH; + #endif + #endif /* if AWS_IOT_TEST_SECURED_CONNECTION == 0 */ + + /* Open a connection to the test server. */ + if( AwsIotNetwork_CreateConnection( &newConnection, + AWS_IOT_TEST_SERVER, + AWS_IOT_TEST_PORT, + pTlsInfo ) != AWS_IOT_NETWORK_SUCCESS ) + { + return false; + } + + /* Set the MQTT receive callback. */ + if( AwsIotNetwork_SetMqttReceiveCallback( newConnection, + pMqttConnection, + AwsIotMqtt_ReceiveCallback ) != AWS_IOT_NETWORK_SUCCESS ) + { + AwsIotNetwork_CloseConnection( newConnection ); + AwsIotNetwork_DestroyConnection( newConnection ); + *pNewConnection = NULL; + + return false; + } + + /* Set the output parameter. */ + *pNewConnection = ( void * ) newConnection; + + return true; +} + +/*-----------------------------------------------------------*/ + +void AwsIotTest_NetworkClose( void * pDisconnectContext ) +{ + AwsIotNetworkConnection_t networkConnection = ( AwsIotNetworkConnection_t ) pDisconnectContext; + + /* Close the provided network handle; if that is uninitialized, close the + * global network handle. */ + if( ( networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) && + ( networkConnection != _networkConnection ) ) + { + AwsIotNetwork_CloseConnection( networkConnection ); + } + else if( _networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + { + AwsIotNetwork_CloseConnection( _networkConnection ); + } +} + +/*-----------------------------------------------------------*/ + +void AwsIotTest_NetworkDestroy( void * pNetworkConnection ) +{ + AwsIotNetworkConnection_t networkConnection = ( AwsIotNetworkConnection_t ) pNetworkConnection; + + if( ( networkConnection != NULL ) && + ( networkConnection != _networkConnection ) ) + { + /* Wrap the network interface's destroy function. */ + AwsIotNetwork_DestroyConnection( ( AwsIotNetworkConnection_t ) pNetworkConnection ); + } + else + { + if( _networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + { + AwsIotNetwork_DestroyConnection( ( AwsIotNetworkConnection_t ) _networkConnection ); + _networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + } + } +} + +/*-----------------------------------------------------------*/ diff --git a/tests/integration/Makefile b/tests/integration/Makefile deleted file mode 100644 index db9f60a04b..0000000000 --- a/tests/integration/Makefile +++ /dev/null @@ -1,107 +0,0 @@ -#This target is to ensure accidental execution of Makefile as a bash script will not execute commands like rm in unexpected directories and exit gracefully. -.prevent_execution: - exit 0 - -CC = gcc -RM = rm - -DEBUG = - -#IoT client directory -IOT_CLIENT_DIR = ../.. - -APP_DIR = $(IOT_CLIENT_DIR)/tests/integration -APP_NAME = integration_tests_mbedtls -MT_APP_NAME = integration_tests_mbedtls_mt -APP_SRC_FILES = $(shell find $(APP_DIR)/src/ -name '*.c') -MT_APP_SRC_FILES = $(shell find $(APP_DIR)/multithreadingTest/ -name '*.c') -APP_INCLUDE_DIRS = -I $(APP_DIR)/include - -PLATFORM_DIR = $(IOT_CLIENT_DIR)/platform/linux - -#MbedTLS directory -TEMP_MBEDTLS_SRC_DIR = $(IOT_CLIENT_DIR)/external_libs/mbedTLS -TLS_LIB_DIR = $(TEMP_MBEDTLS_SRC_DIR)/library -TLS_INCLUDE_DIR = -I $(TEMP_MBEDTLS_SRC_DIR)/include - -EXTERNAL_LIBS += -L$(TLS_LIB_DIR) -LD_FLAG += -Wl,-rpath,$(TLS_LIB_DIR) -LD_FLAG += -ldl $(TLS_LIB_DIR)/libmbedtls.a $(TLS_LIB_DIR)/libmbedcrypto.a $(TLS_LIB_DIR)/libmbedx509.a -lpthread - -# Logging level control -#LOG_FLAGS += -DENABLE_IOT_DEBUG -#LOG_FLAGS += -DENABLE_IOT_TRACE -#LOG_FLAGS += -DENABLE_IOT_INFO -LOG_FLAGS += -DENABLE_IOT_WARN -LOG_FLAGS += -DENABLE_IOT_ERROR -COMPILER_FLAGS += $(LOG_FLAGS) - -#IoT client directory -PLATFORM_COMMON_DIR = $(PLATFORM_DIR)/common -PLATFORM_THREAD_DIR = $(PLATFORM_DIR)/pthread -PLATFORM_NETWORK_DIR = $(PLATFORM_DIR)/mbedtls - -IOT_INCLUDE_DIRS = -I $(PLATFORM_COMMON_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_THREAD_DIR) -IOT_INCLUDE_DIRS += -I $(PLATFORM_NETWORK_DIR) -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/include -IOT_INCLUDE_DIRS += -I $(IOT_CLIENT_DIR)/external_libs/jsmn - -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/src/ -name '*.c') -IOT_SRC_FILES += $(shell find $(IOT_CLIENT_DIR)/external_libs/jsmn/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_NETWORK_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_COMMON_DIR)/ -name '*.c') -IOT_SRC_FILES += $(shell find $(PLATFORM_THREAD_DIR)/ -name '*.c') - -#Aggregate all include and src directories -INCLUDE_ALL_DIRS += $(IOT_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(APP_INCLUDE_DIRS) -INCLUDE_ALL_DIRS += $(TLS_INCLUDE_DIR) - -SRC_FILES += $(APP_SRC_FILES) -SRC_FILES += $(IOT_SRC_FILES) - -MT_SRC_FILES += $(MT_APP_SRC_FILES) -MT_SRC_FILES += $(IOT_SRC_FILES) - -COMPILER_FLAGS += -g -COMPILER_FLAGS += $(LOG_FLAGS) -PRE_MAKE_CMDS += cd $(TEMP_MBEDTLS_SRC_DIR) && make - -MAKE_CMD = $(CC) $(SRC_FILES) $(COMPILER_FLAGS) -g3 -D_ENABLE_THREAD_SUPPORT_ -o $(APP_DIR)/$(APP_NAME) $(EXTERNAL_LIBS) $(LD_FLAG) $(INCLUDE_ALL_DIRS); -MAKE_MT_CMD = $(CC) $(MT_SRC_FILES) $(COMPILER_FLAGS) -g3 -D_ENABLE_THREAD_SUPPORT_ -o $(APP_DIR)/$(MT_APP_NAME) $(EXTERNAL_LIBS) $(LD_FLAG) $(INCLUDE_ALL_DIRS); - -ifeq ($(CODE_SIZE_ENABLE),Y) -POST_MAKE_CMDS += $(CC) -c $(SRC_FILES) $(INCLUDE_ALL_DIRS) -fstack-usage; -POST_MAKE_CMDS += (size --format=Berkeley *.o > $(APP_NAME)_size_info.txt); -POST_MAKE_CMDS += (cat *.su >> $(APP_NAME)_stack_usage.txt); -POST_MAKE_CMDS += ($(RM) *.o); -POST_MAKE_CMDS += ($(RM) *.su); -CLEAN_CMD += ($(RM) -f $(APP_NAME)_size_info.txt); -CLEAN_CMD += ($(RM) -f $(APP_NAME)_stack_usage.txt); -endif - -all: - $(PRE_MAKE_CMDS) - $(DEBUG)$(MAKE_CMD) - $(DEBUG)$(MAKE_MT_CMD) - ./$(APP_NAME) - ./$(MT_APP_NAME) - $(POST_MAKE_CMDS) - -app: - $(PRE_MAKE_CMDS) - $(DEBUG)$(MAKE_CMD) - $(DEBUG)$(MAKE_MT_CMD) - -tests: - ./$(APP_NAME) - ./$(MT_APP_NAME) - $(POST_MAKE_CMDS) - -clean: - $(RM) -f $(APP_DIR)/$(APP_NAME) - $(RM) -f $(APP_DIR)/$(MT_APP_NAME) - $(CLEAN_CMD) - -ALL_TARGETS_CLEAN += test-integration-assert-clean diff --git a/tests/integration/README.md b/tests/integration/README.md deleted file mode 100644 index b3ea04b25a..0000000000 --- a/tests/integration/README.md +++ /dev/null @@ -1,44 +0,0 @@ -## Integration Tests -This folder contains integration tests to verify Embedded C SDK functionality. These have been tested to work with Linux but haven't been ported to any specific platform. For additional information about porting the Device SDK for embedded C onto additional platforms please refer to the [PortingGuide](https://github.com/aws/aws-iot-device-sdk-embedded-c/blob/master/PortingGuide.md/). -To run these tests, follow the below steps: - - * Place device identity cert and private key in locations referenced in the `certs` folder - * Download certificate authority CA file from [Symantec](https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem) and place in `certs` folder - * Ensure the names of the cert files are the same as in the `aws_iot_config.h` file - * Ensure the certificate has an attached policy which allows the proper permissions for AWS IoT - * Update the Host endpoint in the `aws_iot_config.h` file - * Build the example using make. (''make''). The tests will run automatically as a part of the build process - * For more detailed Debug output, enable the IOT_DEBUG flag in `Logging level control` section of the Makefile. IOT_TRACE can be enabled as well for very detailed information on what functions are being executed - * More information on the each test is below - -### Integration test configuration -For all the tests below, there is additional configuration in the `integ_tests_config.h`. The configuration options are explained below: - - * PUBLISH_COUNT - Number of messages to publish in each publish thread - * MAX_PUB_THREAD_COUNT - Maximum number of threads to create for the multi-threading test - * RX_RECEIVE_PERCENTAGE - Minimum percentage of messages that must be received back by the yield thread. This is here ONLY because sometimes the yield thread doesn't get scheduled before the publish thread when it is created. In every other case, 100% messages should be received - * CONNECT_MAX_ATTEMPT_COUNT - Max number of initial connect retries - * THREAD_SLEEP_INTERVAL_USEC - Interval that each thread sleeps for - * INTEGRATION_TEST_TOPIC - Test topic to publish on - * INTEGRATION_TEST_CLIENT_ID - Client ID to be used for single client tests - * INTEGRATION_TEST_CLIENT_ID_PUB, INTEGRATION_TEST_CLIENT_ID_SUB - Client IDs to be used for multiple client tests - -### Test 1 - Basic Connectivity Test -This test verifies basic connectivity with the server. It creates one client instance and connects to the server. It subscribes to the Integration Test topic. Then it creates two threads, one publish thread and one yield thread. The publish thread publishes `PUBLISH_COUNT` messages on the test topic and the yield thread receives them. Once all the messages are published, the program waits for 1 sec to ensure all the messages have sufficient time to be received. -The test ends with the program verifying that all the messages were received and no other errors occurred. - -### Test 2 - Multiple Client Connectivity Test -This test verifies usage of multiple clients in the same application. It creates two client instances and both connect to the server. One client instance subscribes to the Integration Test topic. The other client instance publishes `PUBLISH_COUNT` messages on the test topic and the yield instance receives them. Once all the messages are published, the program waits for 1 sec to ensure all the messages have sufficient time to be received. -The test ends with the program verifying that all the messages were received and no other errors occurred. - -### Test 3 - Auto Reconnect Test -This test verifies Auto-reconnect functionality. It creates one client instance. Then it performs 3 separate tests - - * Tests the disconnect handler by causing a TLS layer disconnect. - * Tests manual reconnect API by causing a TLS layer disconnect with auto-reconnect disabled - * Lastly, it tests the Auto-reconnect API by enabling auto-reconnect. It renames the rootCA file to a different name to prevent immediate reconnect, verifies the error codes are returned properly and that the reconnect algorithm is running. Once that check is satisfied, it renames the rootCA file back to the original names and verifies the client was able to reconnect. - -### Test 4 - Multi-threading Validation Test -This test is used to validate thread-safe operations. This creates on client instance, one yield thread, one thread to test subscribe/unsubscribe behavior and MAX_PUB_THREAD_COUNT number of publish threads. Then it proceeds to publish PUBLISH_COUNT messages on the test topic from each publish thread. The subscribe/unsubscribe thread runs in the background constantly subscribing and unsubscribing to a second test topic. The yield threads records which messages were received. - -The test verifies whether all the messages that were published were received or not. It also checks for errors that could occur in multi-threaded scenarios. The test has been run with 10 threads sending 500 messages each and verified to be working fine. It can be used as a reference testing application to validate whether your use case will work with multi-threading enabled. diff --git a/tests/integration/include/aws_iot_config.h b/tests/integration/include/aws_iot_config.h deleted file mode 100644 index 4683023cee..0000000000 --- a/tests/integration/include/aws_iot_config.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef SRC_SHADOW_IOT_SHADOW_CONFIG_H_ -#define SRC_SHADOW_IOT_SHADOW_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "" ///< Customer specific MQTT HOST. The same will be used for Thing Shadow -#define AWS_IOT_MQTT_PORT 443 ///< default port for MQTT/S -#define AWS_IOT_MQTT_CLIENT_ID "c-sdk-client-id" ///< MQTT client ID should be unique for every device -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" ///< Thing Name of the Shadow this device is associated with -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" ///< Root CA file name -#define AWS_IOT_CERTIFICATE_FILENAME "cert.pem" ///< device signed certificate file name -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" ///< Device private key filename - -// MQTT PubSub -#ifndef DISABLE_IOT_JOBS -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#else -#define AWS_IOT_MQTT_RX_BUF_LEN 2048 -#endif -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Shadow and Job common configs -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_SIZE_OF_THING_NAME 30 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER 512 ///< Maximum size of the SHADOW buffer to store the received Shadow message -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Job specific configs -#ifndef DISABLE_IOT_JOBS -#define MAX_SIZE_OF_JOB_ID 64 -#define MAX_JOB_JSON_TOKEN_EXPECTED 120 -#define MAX_SIZE_OF_JOB_REQUEST AWS_IOT_MQTT_TX_BUF_LEN - -#define MAX_JOB_TOPIC_LENGTH_WITHOUT_JOB_ID_OR_THING_NAME 40 -#define MAX_JOB_TOPIC_LENGTH_BYTES MAX_JOB_TOPIC_LENGTH_WITHOUT_JOB_ID_OR_THING_NAME + MAX_SIZE_OF_THING_NAME + MAX_SIZE_OF_JOB_ID + 2 -#endif - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#define DISABLE_METRICS false ///< Disable the collection of metrics by setting this to true - -#endif /* SRC_SHADOW_IOT_SHADOW_CONFIG_H_ */ diff --git a/tests/integration/include/aws_iot_integ_tests_config.h b/tests/integration/include/aws_iot_integ_tests_config.h deleted file mode 100644 index a109338067..0000000000 --- a/tests/integration/include/aws_iot_integ_tests_config.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2010-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -#ifndef TESTS_INTEGRATION_INTEG_TESTS_CONFIG_H_ -#define TESTS_INTEGRATION_INTEG_TESTS_CONFIG_H_ - -/* Number of messages to publish in each publish thread */ -#define PUBLISH_COUNT 100 - -/* Maximum number of threads to create for the multi-threading test */ -#define MAX_PUB_THREAD_COUNT 3 - -/* Minimum percentage of messages that must be received back by the yield thread. - * This is here ONLY because sometimes the yield thread doesn't get scheduled before the publish - * thread when it is created. In every other case, 100% messages should be received. */ -#define RX_RECEIVE_PERCENTAGE 99.0f - -/* Max number of initial connect retries */ -#define CONNECT_MAX_ATTEMPT_COUNT 3 - -/* Interval that each thread sleeps for */ -#define THREAD_SLEEP_INTERVAL_USEC 500000 - -/* Test topic to publish on */ -#define INTEGRATION_TEST_TOPIC "Tests/Integration/EmbeddedC" - -/* Client ID to be used for single client tests */ -#define INTEGRATION_TEST_CLIENT_ID "EMB_C_SDK_INTEG_TESTER" - -/* Client IDs to be used for multiple client tests */ -#define INTEGRATION_TEST_CLIENT_ID_PUB "EMB_C_SDK_INTEG_TESTER_PUB" -#define INTEGRATION_TEST_CLIENT_ID_SUB "EMB_C_SDK_INTEG_TESTER_SUB" - -#endif /* TESTS_INTEGRATION_INTEG_TESTS_CONFIG_H_ */ diff --git a/tests/integration/include/aws_iot_test_integration_common.h b/tests/integration/include/aws_iot_test_integration_common.h deleted file mode 100644 index 39da671ce0..0000000000 --- a/tests/integration/include/aws_iot_test_integration_common.h +++ /dev/null @@ -1,43 +0,0 @@ - -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_test_integration_common.h - * @brief Integration Test common header - */ - -#ifndef TESTS_INTEGRATION_H_ -#define TESTS_INTEGRATION_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_log.h" -#include "aws_iot_integ_tests_config.h" -#include "aws_iot_config.h" - -int aws_iot_mqtt_tests_basic_connectivity(); -int aws_iot_mqtt_tests_multiple_clients(); -int aws_iot_mqtt_tests_auto_reconnect(); - -#endif /* TESTS_INTEGRATION_COMMON_H_ */ diff --git a/tests/integration/multithreadingTest/aws_iot_test_multithreading_validation.c b/tests/integration/multithreadingTest/aws_iot_test_multithreading_validation.c deleted file mode 100644 index 032661d4fe..0000000000 --- a/tests/integration/multithreadingTest/aws_iot_test_multithreading_validation.c +++ /dev/null @@ -1,362 +0,0 @@ -/* - * multithreadedTest.c - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_log.h" - -#include "aws_iot_integ_tests_config.h" -#include "aws_iot_config.h" - -#define BUFFER_SIZE 100 - -static bool terminate_yield_thread; -static bool terminate_subUnsub_thread; - -static unsigned int countArray[MAX_PUB_THREAD_COUNT][PUBLISH_COUNT]; -static unsigned int rxMsgBufferTooBigCounter; -static unsigned int rxUnexpectedNumberCounter; -static unsigned int rePublishCount; -static unsigned int wrongYieldCount; -static unsigned int threadStatus[MAX_PUB_THREAD_COUNT]; - -typedef struct ThreadData { - int threadId; - AWS_IoT_Client *client; -} ThreadData; - -static void aws_iot_mqtt_tests_message_aggregator(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData) { - char tempBuf[BUFFER_SIZE]; - char *temp = NULL; - char *next_token; - unsigned int tempRow = 0, tempCol = 0; - - IoT_Error_t rc; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - if(BUFFER_SIZE >= params->payloadLen) { - snprintf(tempBuf, params->payloadLen, params->payload); - printf("\n Message received : %s", tempBuf); - temp = strtok_r(tempBuf, " ,:", &next_token); - temp = strtok_r(NULL, " ,:", &next_token); - if(NULL == temp) { - return; - } - tempRow = atoi(temp); - temp = strtok_r(NULL, " ,:", &next_token); - temp = strtok_r(NULL, " ,:", &next_token); - - if(NULL == temp) { - return; - } - - tempCol = atoi(temp); - - if(((tempRow - 1) < MAX_PUB_THREAD_COUNT) && (tempCol < PUBLISH_COUNT)) { - countArray[tempRow - 1][tempCol]++; - } else { - IOT_ERROR(" \nUnexpected Thread : %d, Message : %d ", tempRow, tempCol); - rxUnexpectedNumberCounter++; - } - rc = aws_iot_mqtt_yield(pClient, 10); - if(MQTT_CLIENT_NOT_IDLE_ERROR != rc) { - IOT_ERROR("\n Yield succeeded in callback!!! Client state : %d Rc : %d\n", - aws_iot_mqtt_get_client_state(pClient), rc); - wrongYieldCount++; - } - } else { - rxMsgBufferTooBigCounter++; - } -} - -static void aws_iot_mqtt_tests_disconnect_callback_handler(AWS_IoT_Client *pClient, void *param) { - IOT_UNUSED(pClient); - IOT_UNUSED(param); -} - -static IoT_Error_t aws_iot_mqtt_tests_subscribe_to_test_topic(AWS_IoT_Client *pClient, QoS qos, struct timeval *pSubscribeTime) { - IoT_Error_t rc = SUCCESS; - struct timeval start, end; - - gettimeofday(&start, NULL); - rc = aws_iot_mqtt_subscribe(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), qos, - aws_iot_mqtt_tests_message_aggregator, NULL); - IOT_DEBUG(" Sub response : %d\n", rc); - gettimeofday(&end, NULL); - - timersub(&end, &start, pSubscribeTime); - - return rc; -} - -static void *aws_iot_mqtt_tests_yield_thread_runner(void *ptr) { - IoT_Error_t rc = SUCCESS; - AWS_IoT_Client *pClient = (AWS_IoT_Client *) ptr; - while(SUCCESS == rc && false == terminate_yield_thread) { - do { - usleep(THREAD_SLEEP_INTERVAL_USEC); - //DEBUG("\n Yielding \n"); - rc = aws_iot_mqtt_yield(pClient, 100); - } while(MQTT_CLIENT_NOT_IDLE_ERROR == rc); - - if(SUCCESS != rc) { - IOT_ERROR("\nYield Returned : %d ", rc); - } - } - - return NULL; -} - -static void *aws_iot_mqtt_tests_publish_thread_runner(void *ptr) { - int itr = 0; - char cPayload[100]; - IoT_Publish_Message_Params params; - IoT_Error_t rc = SUCCESS; - ThreadData *threadData = (ThreadData *) ptr; - AWS_IoT_Client *pClient = threadData->client; - int threadId = threadData->threadId; - - for(itr = 0; itr < PUBLISH_COUNT; itr++) { - snprintf(cPayload, 100, "%s_Thread : %d, Msg : %d", AWS_IOT_MY_THING_NAME, threadId, itr); - printf("\nMsg being published: %s \n", cPayload); - params.payload = (void *) cPayload; - params.payloadLen = strlen(cPayload) + 1; - params.qos = QOS1; - params.isRetained = 0; - - do { - rc = aws_iot_mqtt_publish(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), ¶ms); - usleep(THREAD_SLEEP_INTERVAL_USEC); - } while(MUTEX_LOCK_ERROR == rc || MQTT_CLIENT_NOT_IDLE_ERROR == rc); - if(SUCCESS != rc) { - IOT_WARN("\nFailed attempt 1 Publishing Thread : %d, Msg : %d, cs : %d --> %d\n ", threadId, itr, rc, - aws_iot_mqtt_get_client_state(pClient)); - do { - rc = aws_iot_mqtt_publish(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), ¶ms); - usleep(THREAD_SLEEP_INTERVAL_USEC); - } while(MUTEX_LOCK_ERROR == rc || MQTT_CLIENT_NOT_IDLE_ERROR == rc); - rePublishCount++; - if(SUCCESS != rc) { - IOT_ERROR("\nFailed attempt 2 Publishing Thread : %d, Msg : %d, cs : %d --> %d Second Attempt \n", threadId, - itr, rc, aws_iot_mqtt_get_client_state(pClient)); - } - } - } - threadStatus[threadId - 1] = 1; - return 0; - -} - -static void *aws_iot_mqtt_tests_sub_unsub_thread_runner(void *ptr) { - IoT_Error_t rc = SUCCESS; - AWS_IoT_Client *pClient = (AWS_IoT_Client *) ptr; - char testTopic[50]; - snprintf(testTopic, 50, "%s_temp", INTEGRATION_TEST_TOPIC); - while(SUCCESS == rc && false == terminate_subUnsub_thread) { - do { - usleep(THREAD_SLEEP_INTERVAL_USEC); - rc = aws_iot_mqtt_subscribe(pClient, testTopic, strlen(testTopic), QOS1, - aws_iot_mqtt_tests_message_aggregator, NULL); - } while(MQTT_CLIENT_NOT_IDLE_ERROR == rc); - - if(SUCCESS != rc) { - IOT_ERROR("Subscribe Returned : %d ", rc); - } - - do { - usleep(THREAD_SLEEP_INTERVAL_USEC); - rc = aws_iot_mqtt_unsubscribe(pClient, testTopic, strlen(testTopic)); - } while(MQTT_CLIENT_NOT_IDLE_ERROR == rc); - - if(SUCCESS != rc) { - IOT_ERROR("Unsubscribe Returned : %d ", rc); - } - } - - return NULL; -} - -int aws_iot_mqtt_tests_multi_threading_validation() { - pthread_t publish_thread[MAX_PUB_THREAD_COUNT], yield_thread, sub_unsub_thread; - char certDirectory[15] = "../../certs"; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - char root_CA[PATH_MAX + 1]; - - char clientId[50]; - IoT_Client_Init_Params initParams = IoT_Client_Init_Params_initializer; - IoT_Client_Connect_Params connectParams; - int threadId[MAX_PUB_THREAD_COUNT]; - int pubThreadReturn[MAX_PUB_THREAD_COUNT]; - int yieldThreadReturn = 0, subUnsubThreadReturn = 0; - float percentOfRxMsg = 0.0; - int finishedThreadCount = 0; - IoT_Error_t rc = SUCCESS; - int i, rxMsgCount = 0, j = 0; - struct timeval subscribeTopic; - unsigned int connectCounter = 0; - int test_result = 0; - ThreadData threadData[MAX_PUB_THREAD_COUNT]; - AWS_IoT_Client client; - terminate_yield_thread = false; - rxMsgBufferTooBigCounter = 0; - rxUnexpectedNumberCounter = 0; - rePublishCount = 0; - wrongYieldCount = 0; - for(j = 0; j < MAX_PUB_THREAD_COUNT; j++) { - threadId[j] = j + 1; - threadStatus[j] = 0; - for(i = 0; i < PUBLISH_COUNT; i++) { - countArray[j][i] = 0; - } - } - - printf("\nConnecting Client "); - do { - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(root_CA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - srand((unsigned int)time(NULL)); - snprintf(clientId, 50, "%s_%d", INTEGRATION_TEST_CLIENT_ID, rand() % 10000); - - IOT_DEBUG(" Root CA Path : %s\n clientCRT : %s\n clientKey : %s\n", root_CA, clientCRT, clientKey); - initParams.pHostURL = AWS_IOT_MQTT_HOST; - initParams.port = AWS_IOT_MQTT_PORT; - initParams.pRootCALocation = root_CA; - initParams.pDeviceCertLocation = clientCRT; - initParams.pDevicePrivateKeyLocation = clientKey; - initParams.mqttCommandTimeout_ms = 10000; - initParams.tlsHandshakeTimeout_ms = 10000; - initParams.disconnectHandler = aws_iot_mqtt_tests_disconnect_callback_handler; - initParams.enableAutoReconnect = false; - initParams.isBlockOnThreadLockEnabled = true; - aws_iot_mqtt_init(&client, &initParams); - - connectParams.keepAliveIntervalInSec = 10; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = (char *)&clientId; - connectParams.clientIDLen = strlen(clientId); - connectParams.isWillMsgPresent = false; - connectParams.pUsername = NULL; - connectParams.usernameLen = 0; - connectParams.pPassword = NULL; - connectParams.passwordLen = 0; - - rc = aws_iot_mqtt_connect(&client, &connectParams); - if(SUCCESS != rc) { - IOT_ERROR("ERROR Connecting %d\n", rc); - return -1; - } - - connectCounter++; - } while(SUCCESS != rc && connectCounter < CONNECT_MAX_ATTEMPT_COUNT); - - if(SUCCESS == rc) { - printf("\n## Connect Success.\n"); - } else { - IOT_ERROR("## Connect Failed. error code %d\n", rc); - return -1; - } - - aws_iot_mqtt_tests_subscribe_to_test_topic(&client, QOS1, &subscribeTopic); - - printf("\nRunning Test! "); - - yieldThreadReturn = pthread_create(&yield_thread, NULL, aws_iot_mqtt_tests_yield_thread_runner, &client); - subUnsubThreadReturn = pthread_create(&sub_unsub_thread, NULL, aws_iot_mqtt_tests_sub_unsub_thread_runner, &client); - - for(i = 0; i < MAX_PUB_THREAD_COUNT; i++) { - threadData[i].client = &client; - threadData[i].threadId = threadId[i]; - pubThreadReturn[i] = pthread_create(&publish_thread[i], NULL, aws_iot_mqtt_tests_publish_thread_runner, - &threadData[i]); - } - - /* Wait until all publish threads have finished */ - do { - finishedThreadCount = 0; - for(i = 0; i < MAX_PUB_THREAD_COUNT; i++) { finishedThreadCount += threadStatus[i]; } - printf("\nFinished thread count : %d \n", finishedThreadCount); - sleep(1); - } while(finishedThreadCount < MAX_PUB_THREAD_COUNT); - - printf("\nFinished publishing!!"); - - terminate_yield_thread = true; - terminate_subUnsub_thread = true; - pthread_join(yield_thread, NULL); - pthread_join(sub_unsub_thread, NULL); - - for(i = 0; i < MAX_PUB_THREAD_COUNT; i++) { - pthread_join(publish_thread[i], NULL); - } - - /* Not using pthread_join because all threads should have terminated gracefully at this point. If they haven't, - * which should not be possible, something below will fail. */ - - printf("\n\nCalculating Results!! \n\n"); - for(i = 0; i < PUBLISH_COUNT; i++) { - for(j = 0; j < MAX_PUB_THREAD_COUNT; j++) { - if(countArray[j][i] > 0) { - rxMsgCount++; - } - } - } - - printf("\n\nResult : \n"); - percentOfRxMsg = (float) rxMsgCount * 100 / (PUBLISH_COUNT * MAX_PUB_THREAD_COUNT); - if(RX_RECEIVE_PERCENTAGE <= percentOfRxMsg && 0 == rxMsgBufferTooBigCounter && 0 == rxUnexpectedNumberCounter && - 0 == wrongYieldCount) { - printf("\nSuccess: %f \%\n", percentOfRxMsg); - printf("Published Messages: %d , Received Messages: %d \n", PUBLISH_COUNT * MAX_PUB_THREAD_COUNT, rxMsgCount); - printf("QoS 1 re publish count %d\n", rePublishCount); - printf("Connection Attempts %d\n", connectCounter); - printf("Yield count without error during callback %d\n", wrongYieldCount); - test_result = 0; - } else { - printf("\nFailure: %f\n", percentOfRxMsg); - printf("\"Received message was too big than anything sent\" count: %d\n", rxMsgBufferTooBigCounter); - printf("\"The number received is out of the range\" count: %d\n", rxUnexpectedNumberCounter); - printf("Yield count without error during callback %d\n", wrongYieldCount); - test_result = -2; - } - aws_iot_mqtt_disconnect(&client); - return test_result; -} - -int main() { - printf("\n\n"); - printf("******************************************************************\n"); - printf("* Starting MQTT Version 3.1.1 Multithreading Validation Test *\n"); - printf("******************************************************************\n"); - int rc = aws_iot_mqtt_tests_multi_threading_validation(); - if(0 != rc) { - printf("\n*******************************************************************\n"); - printf("*MQTT Version 3.1.1 Multithreading Validation Test FAILED! RC : %d \n", rc); - printf("*******************************************************************\n"); - return 1; - } - - printf("******************************************************************\n"); - printf("* MQTT Version 3.1.1 Multithreading Validation Test SUCCESS!! *\n"); - printf("******************************************************************\n"); - - return 0; -} diff --git a/tests/integration/src/aws_iot_test_auto_reconnect.c b/tests/integration/src/aws_iot_test_auto_reconnect.c deleted file mode 100644 index 267d9dc050..0000000000 --- a/tests/integration/src/aws_iot_test_auto_reconnect.c +++ /dev/null @@ -1,236 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_test_auto_reconnect.c - * @brief Integration Test for automatic reconnect - */ - -#include "aws_iot_test_integration_common.h" - -static char ModifiedPathBuffer[PATH_MAX + 1]; -char root_CA[PATH_MAX + 1]; - -bool terminate_yield_with_rc_thread = false; -IoT_Error_t yieldRC; -bool captureYieldReturnCode = false; -char * dummyLocation = "dummyLocation"; -char * savedLocation; -/** - * This function renames the rootCA.crt file to a temporary name to cause connect failure - */ -void aws_iot_mqtt_tests_block_tls_connect(AWS_IoT_Client *pClient) { - savedLocation = pClient->networkStack.tlsConnectParams.pRootCALocation; - pClient->networkStack.tlsConnectParams.pRootCALocation = dummyLocation; -} - -/** - * Always ensure this function is called after block_tls_connect - */ -void aws_iot_mqtt_tests_unblock_tls_connect(AWS_IoT_Client *pClient) { - pClient->networkStack.tlsConnectParams.pRootCALocation = savedLocation; -} - -void *aws_iot_mqtt_tests_yield_with_rc(void *ptr) { - IoT_Error_t rc = SUCCESS; - - struct timeval start, end, result; - static int cntr = 0; - AWS_IoT_Client *pClient = (AWS_IoT_Client *) ptr; - - while(terminate_yield_with_rc_thread == false - && (NETWORK_ATTEMPTING_RECONNECT == rc || NETWORK_RECONNECTED == rc || SUCCESS == rc)) { - usleep(500000); - printf(" Client state : %d ", aws_iot_mqtt_get_client_state(pClient)); - rc = aws_iot_mqtt_yield(pClient, 100); - printf("yield rc %d\n", rc); - if(captureYieldReturnCode && SUCCESS != rc) { - printf("yield rc capture %d\n", rc); - captureYieldReturnCode = false; - yieldRC = rc; - } - } - - return NULL; -} - -unsigned int disconnectedCounter = 0; - -void aws_iot_mqtt_tests_disconnect_callback_handler(AWS_IoT_Client *pClient, void *param) { - disconnectedCounter++; -} - -int aws_iot_mqtt_tests_auto_reconnect() { - pthread_t reconnectTester_thread, yield_thread; - int yieldThreadReturn = 0; - int test_result = 0; - - char certDirectory[15] = "../../certs"; - char CurrentWD[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char clientId[50]; - AWS_IoT_Client client; - - IoT_Error_t rc = SUCCESS; - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(root_CA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - srand((unsigned int) time(NULL)); - snprintf(clientId, 50, "%s_%d", INTEGRATION_TEST_CLIENT_ID, rand() % 10000); - - printf(" Root CA Path : %s\n clientCRT : %s\n clientKey : %s\n", root_CA, clientCRT, clientKey); - IoT_Client_Init_Params initParams = IoT_Client_Init_Params_initializer; - initParams.pHostURL = AWS_IOT_MQTT_HOST; - initParams.port = AWS_IOT_MQTT_PORT; - initParams.pRootCALocation = root_CA; - initParams.pDeviceCertLocation = clientCRT; - initParams.pDevicePrivateKeyLocation = clientKey; - initParams.mqttCommandTimeout_ms = 20000; - initParams.tlsHandshakeTimeout_ms = 5000; - initParams.disconnectHandler = aws_iot_mqtt_tests_disconnect_callback_handler; - initParams.enableAutoReconnect = false; - aws_iot_mqtt_init(&client, &initParams); - - IoT_Client_Connect_Params connectParams; - connectParams.keepAliveIntervalInSec = 5; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = (char *) &clientId; - connectParams.clientIDLen = strlen(clientId); - connectParams.isWillMsgPresent = 0; - connectParams.pUsername = NULL; - connectParams.usernameLen = 0; - connectParams.pPassword = NULL; - connectParams.passwordLen = 0; - - rc = aws_iot_mqtt_connect(&client, &connectParams); - if(rc != SUCCESS) { - printf("ERROR Connecting %d\n", rc); - return -1; - } - - yieldThreadReturn = pthread_create(&yield_thread, NULL, aws_iot_mqtt_tests_yield_with_rc, &client); - - /* - * Test disconnect handler - */ - printf("1. Test Disconnect Handler\n"); - aws_iot_mqtt_tests_block_tls_connect(&client); - iot_tls_disconnect(&(client.networkStack)); - sleep(connectParams.keepAliveIntervalInSec + 1); - if(disconnectedCounter == 1) { - printf("Success invoking Disconnect Handler\n"); - } else { - aws_iot_mqtt_tests_unblock_tls_connect(&client); - printf("Failure to invoke Disconnect Handler\n"); - return -1; - } - - terminate_yield_with_rc_thread = true; - pthread_join(yield_thread, NULL); - aws_iot_mqtt_tests_unblock_tls_connect(&client); - - /* - * Manual Reconnect Test - */ - printf("2. Test Manual Reconnect, Current Client state : %d \n", aws_iot_mqtt_get_client_state(&client)); - rc = aws_iot_mqtt_attempt_reconnect(&client); - if(rc != NETWORK_RECONNECTED) { - printf("ERROR reconnecting manually %d\n", rc); - return -4; - } - terminate_yield_with_rc_thread = false; - yieldThreadReturn = pthread_create(&yield_thread, NULL, aws_iot_mqtt_tests_yield_with_rc, &client); - - yieldRC = FAILURE; - captureYieldReturnCode = true; - - // ensure atleast 1 cycle of yield is executed to get the yield status to SUCCESS - sleep(1); - if(!captureYieldReturnCode) { - if(yieldRC == NETWORK_ATTEMPTING_RECONNECT) { - printf("Success reconnecting manually\n"); - } else { - printf("Failure to reconnect manually\n"); - return -3; - } - } - terminate_yield_with_rc_thread = true; - pthread_join(yield_thread, NULL); - - /* - * Auto Reconnect Test - */ - - printf("3. Test Auto_reconnect \n"); - - rc = aws_iot_mqtt_autoreconnect_set_status(&client, true); - if(rc != SUCCESS) { - printf("Error: Failed to enable auto-reconnect %d \n", rc); - } - - yieldRC = FAILURE; - captureYieldReturnCode = true; - - // Disconnect - aws_iot_mqtt_tests_block_tls_connect(&client); - iot_tls_disconnect(&(client.networkStack)); - - terminate_yield_with_rc_thread = false; - yieldThreadReturn = pthread_create(&yield_thread, NULL, aws_iot_mqtt_tests_yield_with_rc, &client); - - sleep(connectParams.keepAliveIntervalInSec + 1); - if(!captureYieldReturnCode) { - if(yieldRC == NETWORK_ATTEMPTING_RECONNECT) { - printf("Success attempting reconnect\n"); - } else { - printf("Failure to attempt to reconnect\n"); - return -6; - } - } - if(disconnectedCounter == 2) { - printf("Success: disconnect handler invoked on enabling auto-reconnect\n"); - } else { - printf("Failure: disconnect handler not invoked on enabling auto-reconnect : %d\n", disconnectedCounter); - return -7; - } - aws_iot_mqtt_tests_unblock_tls_connect(&client); - sleep(connectParams.keepAliveIntervalInSec + 1); - captureYieldReturnCode = true; - sleep(connectParams.keepAliveIntervalInSec + 1); - if(!captureYieldReturnCode) { - if(yieldRC == SUCCESS) { - printf("Success attempting reconnect\n"); - } else { - printf("Failure to attempt to reconnect\n"); - return -6; - } - } - - terminate_yield_with_rc_thread = true; - pthread_join(yield_thread, NULL); - - if(true == aws_iot_mqtt_is_client_connected(&client)) { - printf("Success: is Mqtt connected api\n"); - } else { - printf("Failure: is Mqtt Connected api\n"); - return -7; - } - - rc = aws_iot_mqtt_disconnect(&client); - return rc; -} diff --git a/tests/integration/src/aws_iot_test_basic_connectivity.c b/tests/integration/src/aws_iot_test_basic_connectivity.c deleted file mode 100644 index 8d08980ee6..0000000000 --- a/tests/integration/src/aws_iot_test_basic_connectivity.c +++ /dev/null @@ -1,281 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_test_basic_connectivity.c - * @brief Integration Test for basic client connectivity - */ - -#include "aws_iot_test_integration_common.h" - -#define BUFFER_SIZE 100 - -static bool terminate_yield_thread; - -static unsigned int countArray[PUBLISH_COUNT]; -static unsigned int rxMsgBufferTooBigCounter; -static unsigned int rxUnexpectedNumberCounter; -static unsigned int rePublishCount; -static unsigned int wrongYieldCount; - -typedef struct ThreadData { - AWS_IoT_Client *client; - int threadId; -} ThreadData; - -static void aws_iot_mqtt_tests_message_aggregator(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData) { - char tempBuf[BUFFER_SIZE]; - char *next_token; - char *temp = NULL; - unsigned int tempRow = 0, tempCol = 0; - IoT_Error_t rc; - - if(params->payloadLen <= BUFFER_SIZE) { - snprintf(tempBuf, params->payloadLen, params->payload); - printf("\nMsg received : %s", tempBuf); - temp = strtok_r(tempBuf, " ,:", &next_token); - temp = strtok_r(NULL, " ,:", &next_token); - if(NULL == temp) { - return; - } - tempRow = atoi(temp); - temp = strtok_r(NULL, " ,:", &next_token); - temp = strtok_r(NULL, " ,:", &next_token); - if(NULL == temp) { - return; - } - - tempCol = atoi(temp); - - if(tempCol > 0 && tempCol <= PUBLISH_COUNT) { - countArray[tempCol - 1]++; - } else { - IOT_WARN(" \n Thread : %d, Msg : %d ", tempRow, tempCol); - rxUnexpectedNumberCounter++; - } - rc = aws_iot_mqtt_yield(pClient, 10); - if(MQTT_CLIENT_NOT_IDLE_ERROR != rc) { - IOT_ERROR("\n Yield succeeded in callback!!! Client state : %d Rc : %d\n", - aws_iot_mqtt_get_client_state(pClient), rc); - wrongYieldCount++; - } - } else { - rxMsgBufferTooBigCounter++; - } -} - -static void aws_iot_mqtt_tests_disconnect_callback_handler(AWS_IoT_Client *pClient, void *param) { -} - -static IoT_Error_t aws_iot_mqtt_tests_subscribe_to_test_topic(AWS_IoT_Client *pClient, QoS qos, - struct timeval *pSubscribeTime) { - IoT_Error_t rc = SUCCESS; - struct timeval start, end; - - gettimeofday(&start, NULL); - rc = aws_iot_mqtt_subscribe(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), qos, - aws_iot_mqtt_tests_message_aggregator, NULL); - IOT_DEBUG("Sub response : %d\n", rc); - gettimeofday(&end, NULL); - - timersub(&end, &start, pSubscribeTime); - - return rc; -} - -static void *aws_iot_mqtt_tests_yield_thread_runner(void *ptr) { - IoT_Error_t rc = SUCCESS; - AWS_IoT_Client *pClient = (AWS_IoT_Client *) ptr; - while(SUCCESS == rc && terminate_yield_thread == false) { - do { - usleep(THREAD_SLEEP_INTERVAL_USEC); - rc = aws_iot_mqtt_yield(pClient, 100); - } while(MQTT_CLIENT_NOT_IDLE_ERROR == rc); // Client is busy, wait to get lock - - if(SUCCESS != rc) { - IOT_DEBUG("\nYield Returned : %d ", rc); - } - } - - return NULL; -} - -static void *aws_iot_mqtt_tests_publish_thread_runner(void *ptr) { - int i = 0; - char cPayload[100]; - IoT_Publish_Message_Params params; - IoT_Error_t rc = SUCCESS; - ThreadData *threadData = (ThreadData *) ptr; - AWS_IoT_Client *pClient = threadData->client; - int threadId = threadData->threadId; - - for(i = 0; i < PUBLISH_COUNT; i++) { - snprintf(cPayload, 100, "%s_Thread : %d, Msg : %d", AWS_IOT_MY_THING_NAME, threadId, i + 1); - printf("\nMsg being published: %s \n", cPayload); - params.payload = (void *) cPayload; - params.payloadLen = strlen(cPayload) + 1; - params.qos = QOS1; - params.isRetained = 0; - - do { - rc = aws_iot_mqtt_publish(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), ¶ms); - usleep(THREAD_SLEEP_INTERVAL_USEC); - } while(MUTEX_LOCK_ERROR == rc || MQTT_CLIENT_NOT_IDLE_ERROR == rc); - if(rc != SUCCESS) { - IOT_WARN("Error Publishing #%d --> %d\n ", i, rc); - do { - rc = aws_iot_mqtt_publish(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), ¶ms); - usleep(THREAD_SLEEP_INTERVAL_USEC); - } while(MUTEX_LOCK_ERROR == rc || MQTT_CLIENT_NOT_IDLE_ERROR == rc); - rePublishCount++; - if(rc != SUCCESS) { - IOT_ERROR("Error Publishing #%d --> %d Second Attempt \n", i, rc); - } - } - } - return 0; -} - -int aws_iot_mqtt_tests_basic_connectivity() { - pthread_t publish_thread, yield_thread; - char certDirectory[15] = "../../certs"; - char clientCRT[PATH_MAX + 1]; - char root_CA[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - char clientId[50]; - IoT_Client_Init_Params initParams; - IoT_Client_Connect_Params connectParams; - int pubThreadReturn; - int yieldThreadReturn = 0; - float percentOfRxMsg = 0.0; - IoT_Error_t rc = SUCCESS; - int i, rxMsgCount = 0, j = 0; - struct timeval connectTime, subscribeTopic; - struct timeval start, end; - unsigned int connectCounter = 0; - int test_result = 0; - ThreadData threadData; - AWS_IoT_Client client; - - terminate_yield_thread = false; - - rxMsgBufferTooBigCounter = 0; - rxUnexpectedNumberCounter = 0; - rePublishCount = 0; - wrongYieldCount = 0; - for(i = 0; i < PUBLISH_COUNT; i++) { - countArray[i] = 0; - } - - IOT_DEBUG("\nConnecting Client "); - do { - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(root_CA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - srand((unsigned int)time(NULL)); - snprintf(clientId, 50, "%s_%d", INTEGRATION_TEST_CLIENT_ID, rand() % 10000); - - printf("\n\nClient ID : %s \n", clientId); - - IOT_DEBUG("Root CA Path : %s\n clientCRT : %s\n clientKey : %s\n", root_CA, clientCRT, clientKey); - initParams.pHostURL = AWS_IOT_MQTT_HOST; - initParams.port = AWS_IOT_MQTT_PORT; - initParams.pRootCALocation = root_CA; - initParams.pDeviceCertLocation = clientCRT; - initParams.pDevicePrivateKeyLocation = clientKey; - initParams.mqttCommandTimeout_ms = 10000; - initParams.tlsHandshakeTimeout_ms = 10000; - initParams.mqttPacketTimeout_ms = 5000; - initParams.isSSLHostnameVerify = true; - initParams.disconnectHandlerData = NULL; - initParams.isBlockOnThreadLockEnabled = true; - initParams.disconnectHandler = aws_iot_mqtt_tests_disconnect_callback_handler; - initParams.enableAutoReconnect = false; - aws_iot_mqtt_init(&client, &initParams); - - connectParams.keepAliveIntervalInSec = 10; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = (char *)&clientId; - connectParams.clientIDLen = strlen(clientId); - connectParams.isWillMsgPresent = false; - connectParams.pUsername = NULL; - connectParams.usernameLen = 0; - connectParams.pPassword = NULL; - connectParams.passwordLen = 0; - - gettimeofday(&start, NULL); - rc = aws_iot_mqtt_connect(&client, &connectParams); - gettimeofday(&end, NULL); - timersub(&end, &start, &connectTime); - - connectCounter++; - } while(rc != SUCCESS && connectCounter < CONNECT_MAX_ATTEMPT_COUNT); - - if(SUCCESS == rc) { - IOT_DEBUG("## Connect Success. Time sec: %d, usec: %d\n", connectTime.tv_sec, connectTime.tv_usec); - } else { - IOT_ERROR("## Connect Failed. error code %d\n", rc); - return -1; - } - - aws_iot_mqtt_tests_subscribe_to_test_topic(&client, QOS1, &subscribeTopic); - - yieldThreadReturn = pthread_create(&yield_thread, NULL, aws_iot_mqtt_tests_yield_thread_runner, &client); - sleep(1); - - threadData.client = &client; - threadData.threadId = 1; - pubThreadReturn = pthread_create(&publish_thread, NULL, aws_iot_mqtt_tests_publish_thread_runner, &threadData); - - pthread_join(publish_thread, NULL); - // This sleep is to ensure that the last publish message has enough time to be received by us - sleep(1); - - terminate_yield_thread = true; - pthread_join(yield_thread, NULL); - - /* Not using pthread_join because all threads should have terminated gracefully at this point. If they haven't, - * which should not be possible, something below will fail. */ - - for(i = 0; i < PUBLISH_COUNT; i++) { - if(countArray[i] > 0) { - rxMsgCount++; - } - } - - IOT_DEBUG("\n\nResult : \n"); - percentOfRxMsg = (float) rxMsgCount * 100 / PUBLISH_COUNT; - if(percentOfRxMsg >= RX_RECEIVE_PERCENTAGE && rxMsgBufferTooBigCounter == 0 && rxUnexpectedNumberCounter == 0 && - wrongYieldCount == 0) { - IOT_DEBUG("\n\nSuccess: %f \%\n", percentOfRxMsg); - IOT_DEBUG("Published Messages: %d , Received Messages: %d \n", PUBLISH_COUNT, rxMsgCount); - IOT_DEBUG("QoS 1 re publish count %d\n", rePublishCount); - IOT_DEBUG("Connection Attempts %d\n", connectCounter); - IOT_DEBUG("Yield count without error during callback %d\n", wrongYieldCount); - test_result = 0; - } else { - IOT_ERROR("\n\nFailure: %f\n", percentOfRxMsg); - IOT_ERROR("\"Received message was too big than anything sent\" count: %d\n", rxMsgBufferTooBigCounter); - IOT_ERROR("\"The number received is out of the range\" count: %d\n", rxUnexpectedNumberCounter); - IOT_ERROR("Yield count without error during callback %d\n", wrongYieldCount); - test_result = -2; - } - aws_iot_mqtt_disconnect(&client); - return test_result; -} diff --git a/tests/integration/src/aws_iot_test_integration_runner.c b/tests/integration/src/aws_iot_test_integration_runner.c deleted file mode 100644 index 61ec00fabc..0000000000 --- a/tests/integration/src/aws_iot_test_integration_runner.c +++ /dev/null @@ -1,91 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_test_integration_runner.c - * @brief Integration Test runner - */ - -#include "aws_iot_test_integration_common.h" - -int main() { - int rc = 0; - - printf("\n\n"); - printf("*************************************************************************************************\n"); - printf("* Starting TEST 1 MQTT Version 3.1.1 Basic Subscribe QoS 1 Publish QoS 1 with Single Client *\n"); - printf("*************************************************************************************************\n"); - rc = aws_iot_mqtt_tests_basic_connectivity(); - if(0 != rc) { - printf("\n********************************************************************************************************\n"); - printf("* TEST 1 MQTT Version 3.1.1 Basic Subscribe QoS 1 Publish QoS 1 with Single Client FAILED! RC : %4d *\n", rc); - printf("********************************************************************************************************\n"); - return 1; - } - printf("\n*************************************************************************************************\n"); - printf("* TEST 1 MQTT Version 3.1.1 Basic Subscribe QoS 1 Publish QoS 1 with Single Client SUCCESS!! *\n"); - printf("*************************************************************************************************\n"); - - printf("\n\n"); - printf("************************************************************************************************************\n"); - printf("* Starting TEST 2 MQTT Version 3.1.1 Multithreaded Subscribe QoS 1 Publish QoS 1 with Multiple Clients *\n"); - printf("************************************************************************************************************\n"); - rc = aws_iot_mqtt_tests_multiple_clients(); - if(0 != rc) { - printf("\n*******************************************************************************************************************\n"); - printf("* TEST 2 MQTT Version 3.1.1 Multithreaded Subscribe QoS 1 Publish QoS 1 with Multiple Clients FAILED! RC : %4d *\n", rc); - printf("*******************************************************************************************************************\n"); - return 1; - } - printf("\n*************************************************************************************************************\n"); - printf("* TEST 2 MQTT Version 3.1.1 Multithreaded Subscribe QoS 1 Publish QoS 1 with Multiple Clients SUCCESS!! *\n"); - printf("*************************************************************************************************************\n"); - - printf("\n\n"); - printf("*********************************************************\n"); - printf("* Starting TEST 3 MQTT Version 3.1.1 Auto Reconnect *\n"); - printf("*********************************************************\n"); - rc = aws_iot_mqtt_tests_auto_reconnect(); - if(0 != rc) { - printf("\n***************************************************************\n"); - printf("* TEST 3 MQTT Version 3.1.1 Auto Reconnect FAILED! RC : %4d *\n", rc); - printf("***************************************************************\n"); - return 1; - } - printf("\n**********************************************************\n"); - printf("* TEST 3 MQTT Version 3.1.1 Auto Reconnect SUCCESS!! *\n"); - printf("**********************************************************\n"); - -#ifndef DISABLE_IOT_JOBS -#ifndef DISABLE_IOT_JOBS_INTERFACE - printf("\n\n"); - printf("*************************************************************************************************\n"); - printf("* Starting TEST 4 Jobs API Test *\n"); - printf("*************************************************************************************************\n"); - rc = aws_iot_jobs_basic_test(); - if(0 != rc) { - printf("\n********************************************************************************************************\n"); - printf("* TEST 4 Jobs API Test FAILED! RC : %4d *\n", rc); - printf("********************************************************************************************************\n"); - return 1; - } - printf("\n*************************************************************************************************\n"); - printf("* TEST 4 Jobs API Test SUCCESS!! *\n"); - printf("*************************************************************************************************\n"); -#endif -#endif - - return 0; -} diff --git a/tests/integration/src/aws_iot_test_jobs_api.c b/tests/integration/src/aws_iot_test_jobs_api.c deleted file mode 100644 index 4974eb857a..0000000000 --- a/tests/integration/src/aws_iot_test_jobs_api.c +++ /dev/null @@ -1,284 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#ifndef DISABLE_IOT_JOBS -#ifndef DISABLE_IOT_JOBS_INTERFACE - -#include "aws_iot_test_integration_common.h" -#include -#include -#include -#include -#include - -static AWS_IoT_Client client; - -static jsmn_parser jsonParser; -static jsmntok_t jsonTokenStruct[MAX_JSON_TOKEN_EXPECTED]; -static int32_t tokenCount; - -static void aws_iot_mqtt_tests_disconnect_callback_handler(AWS_IoT_Client *pClient, void *param) { -} - -void iot_get_pending_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - IoT_Error_t rc = SUCCESS; - char topicToPublishGetNext[MAX_JOB_TOPIC_LENGTH_BYTES]; - - IOT_UNUSED(pData); - IOT_UNUSED(pClient); - IOT_INFO("\nJOB_GET_PENDING_TOPIC callback"); - IOT_INFO("topic: %.*s", topicNameLen, topicName); - IOT_INFO("payload: %.*s", (int) params->payloadLen, (char *)params->payload); - - jsmn_init(&jsonParser); - - tokenCount = jsmn_parse(&jsonParser, params->payload, (int) params->payloadLen, jsonTokenStruct, MAX_JSON_TOKEN_EXPECTED); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d", tokenCount); - return; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - IOT_WARN("Top Level is not an object"); - return; - } - - jsmntok_t *jobs; - - jobs = findToken("inProgressJobs", params->payload, jsonTokenStruct); - - if (jobs) { - IOT_INFO("inProgressJobs: %.*s", jobs->end - jobs->start, (char *)params->payload + jobs->start); - } - - jobs = findToken("queuedJobs", params->payload, jsonTokenStruct); - - if (jobs) { - IOT_INFO("queuedJobs: %.*s", jobs->end - jobs->start, (char *)params->payload + jobs->start); - } - - AwsIotDescribeJobExecutionRequest describeRequest; - describeRequest.executionNumber = 0; - describeRequest.includeJobDocument = true; - describeRequest.clientToken = NULL; - - rc = aws_iot_jobs_describe(&client, QOS0, AWS_IOT_MY_THING_NAME, JOB_ID_NEXT, &describeRequest, topicToPublishGetNext, sizeof(topicToPublishGetNext), NULL, 0); -} - -void iot_next_job_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - char topicToPublishUpdate[MAX_JOB_TOPIC_LENGTH_BYTES]; - char messageBuffer[200]; - - IOT_UNUSED(pClient); - IOT_INFO("\nJOB_NOTIFY_NEXT_TOPIC / JOB_DESCRIBE_TOPIC($next) callback"); - IOT_INFO("topic: %.*s", topicNameLen, topicName); - IOT_INFO("payload: %.*s", (int) params->payloadLen, (char *)params->payload); - - jsmn_init(&jsonParser); - - tokenCount = jsmn_parse(&jsonParser, params->payload, (int) params->payloadLen, jsonTokenStruct, MAX_JSON_TOKEN_EXPECTED); - - if(tokenCount < 0) { - IOT_WARN("Failed to parse JSON: %d", tokenCount); - return; - } - - /* Assume the top-level element is an object */ - if(tokenCount < 1 || jsonTokenStruct[0].type != JSMN_OBJECT) { - IOT_WARN("Top Level is not an object"); - return; - } - - jsmntok_t *tokExecution; - - tokExecution = findToken("execution", params->payload, jsonTokenStruct); - - if (tokExecution) { - IOT_INFO("execution: %.*s", tokExecution->end - tokExecution->start, (char *)params->payload + tokExecution->start); - - jsmntok_t *tok; - - tok = findToken("jobId", params->payload, tokExecution); - - if (tok) { - IoT_Error_t rc; - char jobId[MAX_SIZE_OF_JOB_ID + 1]; - AwsIotJobExecutionUpdateRequest updateRequest; - - rc = parseStringValue(jobId, MAX_SIZE_OF_JOB_ID + 1, params->payload, tok); - if(SUCCESS != rc) { - IOT_ERROR("parseStringValue returned error : %d ", rc); - return; - } - - IOT_INFO("jobId: %s", jobId); - - tok = findToken("jobDocument", params->payload, tokExecution); - - /* - * Do your job processing here. - */ - - if (tok) { - IOT_INFO("jobDocument: %.*s", tok->end - tok->start, (char *)params->payload + tok->start); - /* Alternatively if the job still has more steps the status can be set to JOB_EXECUTION_IN_PROGRESS instead */ - updateRequest.status = JOB_EXECUTION_SUCCEEDED; - updateRequest.statusDetails = "{\"exampleDetail\":\"a value appropriate for your successful job\"}"; - } else { - updateRequest.status = JOB_EXECUTION_FAILED; - updateRequest.statusDetails = "{\"failureDetail\":\"Unable to process job document\"}"; - } - - updateRequest.expectedVersion = 0; - updateRequest.executionNumber = 0; - updateRequest.includeJobExecutionState = false; - updateRequest.includeJobDocument = false; - updateRequest.clientToken = NULL; - - rc = aws_iot_jobs_send_update(pClient, QOS0, AWS_IOT_MY_THING_NAME, jobId, &updateRequest, - topicToPublishUpdate, sizeof(topicToPublishUpdate), messageBuffer, sizeof(messageBuffer)); - } - } else { - IOT_INFO("execution property not found, nothing to do, jobs integration test complete"); - - bool *callbackDone = (bool *) pData; - *callbackDone = true; - } -} - -int aws_iot_jobs_basic_test() { - char certDirectory[15] = "../../certs"; - char clientCRT[PATH_MAX + 1]; - char root_CA[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - char CurrentWD[PATH_MAX + 1]; - char clientId[50]; - char cPayload[100]; - IoT_Client_Init_Params initParams = IoT_Client_Init_Params_initializer; - IoT_Client_Connect_Params connectParams; - IoT_Publish_Message_Params paramsQOS0; - IoT_Error_t rc = SUCCESS; - struct timeval connectTime, waitCallBackTime; - struct timeval start, end; - unsigned int connectCounter = 0; - - IOT_DEBUG("\nConnecting Client "); - do { - getcwd(CurrentWD, sizeof(CurrentWD)); - snprintf(root_CA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - srand((unsigned int)time(NULL)); - snprintf(clientId, 50, "%s_%d", INTEGRATION_TEST_CLIENT_ID, rand() % 10000); - - printf("\n\nClient ID : %s \n", clientId); - - IOT_DEBUG("Root CA Path : %s\n clientCRT : %s\n clientKey : %s\n", root_CA, clientCRT, clientKey); - initParams.pHostURL = AWS_IOT_MQTT_HOST; - initParams.port = AWS_IOT_MQTT_PORT; - initParams.pRootCALocation = root_CA; - initParams.pDeviceCertLocation = clientCRT; - initParams.pDevicePrivateKeyLocation = clientKey; - initParams.mqttCommandTimeout_ms = 10000; - initParams.tlsHandshakeTimeout_ms = 10000; - initParams.disconnectHandler = aws_iot_mqtt_tests_disconnect_callback_handler; - initParams.enableAutoReconnect = false; - aws_iot_mqtt_init(&client, &initParams); - - connectParams.keepAliveIntervalInSec = 10; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = (char *)&clientId; - connectParams.clientIDLen = strlen(clientId); - connectParams.isWillMsgPresent = false; - connectParams.pUsername = NULL; - connectParams.usernameLen = 0; - connectParams.pPassword = NULL; - connectParams.passwordLen = 0; - - gettimeofday(&start, NULL); - rc = aws_iot_mqtt_connect(&client, &connectParams); - gettimeofday(&end, NULL); - timersub(&end, &start, &connectTime); - - connectCounter++; - } while(rc != SUCCESS && connectCounter < CONNECT_MAX_ATTEMPT_COUNT); - - if(SUCCESS == rc) { - IOT_DEBUG("## Connect Success. Time sec: %ld, usec: %ld\n", (long int)connectTime.tv_sec, (long int)connectTime.tv_usec); - } else { - IOT_ERROR("## Connect Failed. error code %d\n", rc); - return -1; - } - - bool callbackDone = false; - char topicToSubscribeGetPending[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToSubscribeNotifyNext[MAX_JOB_TOPIC_LENGTH_BYTES]; - char topicToSubscribeGetNext[MAX_JOB_TOPIC_LENGTH_BYTES]; - - char topicToPublishGetPending[MAX_JOB_TOPIC_LENGTH_BYTES]; - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, NULL, JOB_GET_PENDING_TOPIC, JOB_WILDCARD_REPLY_TYPE, - iot_get_pending_callback_handler, &callbackDone, topicToSubscribeGetPending, sizeof(topicToSubscribeGetPending)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_GET_PENDING_TOPIC: %d ", rc); - return rc; - } - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, NULL, JOB_NOTIFY_NEXT_TOPIC, JOB_REQUEST_TYPE, - iot_next_job_callback_handler, &callbackDone, topicToSubscribeNotifyNext, sizeof(topicToSubscribeNotifyNext)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_NOTIFY_NEXT_TOPIC: %d ", rc); - return rc; - } - - rc = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, AWS_IOT_MY_THING_NAME, JOB_ID_NEXT, JOB_DESCRIBE_TOPIC, JOB_WILDCARD_REPLY_TYPE, - iot_next_job_callback_handler, &callbackDone, topicToSubscribeGetNext, sizeof(topicToSubscribeGetNext)); - - if(SUCCESS != rc) { - IOT_ERROR("Error subscribing JOB_DESCRIBE_TOPIC ($next): %d ", rc); - return rc; - } - - paramsQOS0.qos = QOS0; - paramsQOS0.payload = (void *) cPayload; - paramsQOS0.isRetained = 0; - paramsQOS0.payloadLen = strlen(cPayload); - - rc = aws_iot_jobs_send_query(&client, QOS0, AWS_IOT_MY_THING_NAME, NULL, NULL, topicToPublishGetPending, sizeof(topicToPublishGetPending), NULL, 0, JOB_GET_PENDING_TOPIC); - gettimeofday(&start, NULL); - while (!callbackDone) { - aws_iot_mqtt_yield(&client, 5000); - - gettimeofday(&end, NULL); - timersub(&end, &start, &waitCallBackTime); - - if(waitCallBackTime.tv_sec > 10) break; - } - - return 0; -} - -#endif -#endif diff --git a/tests/integration/src/aws_iot_test_multiple_clients.c b/tests/integration/src/aws_iot_test_multiple_clients.c deleted file mode 100644 index 3ea85aae47..0000000000 --- a/tests/integration/src/aws_iot_test_multiple_clients.c +++ /dev/null @@ -1,286 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_test_multiple_clients.c - * @brief Integration Test for multiple clients from the same application - */ - -#include "aws_iot_test_integration_common.h" - - -#define MAX_ERROR_DISPLAY 50 -static bool terminate_yield_thread; - -static unsigned int countArray[PUBLISH_COUNT]; -static unsigned int rxMsgBufferTooBigCounter; -static unsigned int rxUnexpectedNumberCounter; -static unsigned int rePublishCount; - -static void aws_iot_mqtt_tests_message_aggregator(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - char tempBuf[10]; - unsigned int tempInt = 0; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - if(10 >= params->payloadLen) { - snprintf(tempBuf, params->payloadLen, params->payload); - printf("\nMsg received : %s", tempBuf); - tempInt = atoi(tempBuf); - if(0 < tempInt && PUBLISH_COUNT >= tempInt) { - countArray[tempInt - 1]++; - } else { - rxUnexpectedNumberCounter++; - } - } else { - if( params->payloadLen > MAX_ERROR_DISPLAY) - { - ((char *)params->payload)[MAX_ERROR_DISPLAY-1] = '\0'; - IOT_ERROR("\nWrong Msg received : %s", (char *)params->payload); - }else - { - IOT_ERROR("\nWrong Msg received : %s", (char *)params->payload); - } - rxMsgBufferTooBigCounter++; - } -} - -static void aws_iot_mqtt_tests_disconnect_callback_handler(AWS_IoT_Client *pClient, void *param) { - IOT_UNUSED(pClient); - IOT_UNUSED(param); -} - -static IoT_Error_t aws_iot_mqtt_tests_connect_client_to_service(AWS_IoT_Client *pClient, struct timeval *pConnectTime, - char *clientId, char *rootCA, char *clientCRT, - char *clientKey) { - IoT_Client_Init_Params initParams; - IoT_Client_Connect_Params connectParams; - IoT_Error_t rc; - struct timeval start, end; - - initParams.pHostURL = AWS_IOT_MQTT_HOST; - initParams.port = AWS_IOT_MQTT_PORT; - initParams.pRootCALocation = rootCA; - initParams.pDeviceCertLocation = clientCRT; - initParams.pDevicePrivateKeyLocation = clientKey; - initParams.mqttCommandTimeout_ms = 5000; - initParams.tlsHandshakeTimeout_ms = 2000; - initParams.mqttPacketTimeout_ms = 5000; - initParams.isSSLHostnameVerify = true; - initParams.disconnectHandlerData = NULL; - initParams.isBlockOnThreadLockEnabled = true; - initParams.disconnectHandler = aws_iot_mqtt_tests_disconnect_callback_handler; - initParams.enableAutoReconnect = false; - rc = aws_iot_mqtt_init(pClient, &initParams); - printf("\n Init response : %d", rc); - - printf("\nRoot CA Path : %s\nClientCRT : %s\nClientKey : %s \nClient ID : %s", rootCA, clientCRT, - clientKey, clientId); - connectParams.keepAliveIntervalInSec = 5; - connectParams.isCleanSession = true; - connectParams.MQTTVersion = MQTT_3_1_1; - connectParams.pClientID = clientId; - connectParams.clientIDLen = strlen(clientId); - connectParams.isWillMsgPresent = 0; - connectParams.pUsername = NULL; - connectParams.usernameLen = 0; - connectParams.pPassword = NULL; - connectParams.passwordLen = 0; - - gettimeofday(&start, NULL); - rc = aws_iot_mqtt_connect(pClient, &connectParams); - printf("\nConnect response : %d ", rc); - gettimeofday(&end, NULL); - timersub(&end, &start, pConnectTime); - - return rc; -} - -static IoT_Error_t aws_iot_mqtt_tests_subscribe_to_test_topic(AWS_IoT_Client *pClient, QoS qos, struct timeval *pSubscribeTime) { - IoT_Error_t rc = SUCCESS; - struct timeval start, end; - - gettimeofday(&start, NULL); - rc = aws_iot_mqtt_subscribe(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), qos, - aws_iot_mqtt_tests_message_aggregator, NULL); - printf("\nSub response : %d\n", rc); - gettimeofday(&end, NULL); - - timersub(&end, &start, pSubscribeTime); - - return rc; -} - -static void *aws_iot_mqtt_tests_yield_thread_runner(void *ptr) { - IoT_Error_t rc = SUCCESS; - AWS_IoT_Client *pClient = (AWS_IoT_Client *) ptr; - while(SUCCESS == rc && false == terminate_yield_thread) { - do { - usleep(THREAD_SLEEP_INTERVAL_USEC); - rc = aws_iot_mqtt_yield(pClient, 100); - } while(MQTT_CLIENT_NOT_IDLE_ERROR == rc); - - if(SUCCESS != rc) { - IOT_ERROR("\nYield Returned : %d ", rc); - } - } - - return NULL; -} - -static void *aws_iot_mqtt_tests_publish_thread_runner(void *ptr) { - int itr = 0; - char cPayload[10]; - IoT_Publish_Message_Params params; - IoT_Error_t rc = SUCCESS; - AWS_IoT_Client *pClient = (AWS_IoT_Client *) ptr; - - for(itr = 0; itr < PUBLISH_COUNT; itr++) { - sprintf(cPayload, "%d", itr + 1); - params.payload = (void *) cPayload; - params.payloadLen = strlen(cPayload) + 1; - params.qos = QOS1; - params.isRetained = 0; - rc = aws_iot_mqtt_publish(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), ¶ms); - printf("\n Publishing %s", cPayload); - if(SUCCESS != rc) { - printf("Error Publishing #%d --> %d\n ", itr, rc); - usleep(300000); - rc = aws_iot_mqtt_publish(pClient, INTEGRATION_TEST_TOPIC, strlen(INTEGRATION_TEST_TOPIC), ¶ms); - rePublishCount++; - if(SUCCESS != rc) { - printf("Error Publishing #%d --> %d Second Attempt \n", itr, rc); - } - } - usleep(300000); - } - - return NULL; -} - - -int aws_iot_mqtt_tests_multiple_clients() { - char certDirectory[15] = "../../certs"; - char CurrentWD[PATH_MAX + 1]; - char rootCA[PATH_MAX + 1]; - char clientCRT[PATH_MAX + 1]; - char clientKey[PATH_MAX + 1]; - - char subClientId[50]; - char pubClientId[50]; - - int itr = 0; - int rxMsgCount = 0; - int test_result = 0; - int pubThreadReturn = 0; - int yieldThreadReturn = 0; - unsigned int connectCounter = 0; - float percentOfRxMsg = 0.0; - - IoT_Error_t rc = SUCCESS; - pthread_t yield_thread; - pthread_t publish_thread; - struct timeval connectTime; - struct timeval subscribeTopic; - - AWS_IoT_Client pubClient; - AWS_IoT_Client subClient; - - terminate_yield_thread = false; - rxMsgBufferTooBigCounter = 0; - rxUnexpectedNumberCounter = 0; - rePublishCount = 0; - - srand((unsigned int)time(NULL)); - snprintf(subClientId, 50, "%s_%d", INTEGRATION_TEST_CLIENT_ID_SUB, rand() % 10000); - snprintf(pubClientId, 50, "%s_%d", INTEGRATION_TEST_CLIENT_ID_PUB, rand() % 10000); - - getcwd(CurrentWD, sizeof(CurrentWD)); - - snprintf(rootCA, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_ROOT_CA_FILENAME); - snprintf(clientCRT, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_CERTIFICATE_FILENAME); - snprintf(clientKey, PATH_MAX + 1, "%s/%s/%s", CurrentWD, certDirectory, AWS_IOT_PRIVATE_KEY_FILENAME); - - for(itr = 0; itr < PUBLISH_COUNT; itr++) { - countArray[itr] = 0; - } - - printf(" \n Connecting Pub Client "); - do { - rc = aws_iot_mqtt_tests_connect_client_to_service(&pubClient, &connectTime, pubClientId, rootCA, - clientCRT, clientKey); - connectCounter++; - } while(SUCCESS != rc && CONNECT_MAX_ATTEMPT_COUNT > connectCounter); - - if(SUCCESS == rc) { - printf("\n## Connect Success. Time sec: %ld, usec: %ld\n", (long int)connectTime.tv_sec, (long int)connectTime.tv_usec); - } else { - printf("\n## Connect Failed. error code %d\n", rc); - return -1; - } - - printf("\n Connecting Sub Client "); - do { - rc = aws_iot_mqtt_tests_connect_client_to_service(&subClient, &connectTime, subClientId, rootCA, - clientCRT, clientKey); - connectCounter++; - } while(SUCCESS != rc && connectCounter < CONNECT_MAX_ATTEMPT_COUNT); - - if(SUCCESS == rc) { - printf("## Connect Success. Time sec: %ld, usec: %ld\n", (long int)connectTime.tv_sec, (long int)connectTime.tv_usec); - } else { - printf("## Connect Failed. error code %d\n", rc); - return -1; - } - - aws_iot_mqtt_tests_subscribe_to_test_topic(&subClient, QOS1, &subscribeTopic); - - yieldThreadReturn = pthread_create(&yield_thread, NULL, aws_iot_mqtt_tests_yield_thread_runner, &subClient); - pubThreadReturn = pthread_create(&publish_thread, NULL, aws_iot_mqtt_tests_publish_thread_runner, &pubClient); - - pthread_join(publish_thread, NULL); - - /* Kill yield thread */ - terminate_yield_thread = true; - pthread_join(yield_thread, NULL); - - aws_iot_mqtt_disconnect(&pubClient); - aws_iot_mqtt_disconnect(&subClient); - - for(itr = 0; itr < PUBLISH_COUNT; itr++) { - if(countArray[itr] > 0) { - rxMsgCount++; - } - } - - percentOfRxMsg = (float) rxMsgCount * 100 / PUBLISH_COUNT; - if(percentOfRxMsg >= RX_RECEIVE_PERCENTAGE && rxMsgBufferTooBigCounter == 0 && rxUnexpectedNumberCounter == 0) { - printf("\nSuccess: %f \%\n", percentOfRxMsg); - printf("Published Messages: %d , Received Messages: %d \n", PUBLISH_COUNT, rxMsgCount); - printf("QoS 1 re publish count %d\n", rePublishCount); - printf("Connection Attempts %d\n", connectCounter); - test_result = 0; - } else { - printf("\nFailure: %f\n", percentOfRxMsg); - printf("\"Received message was too big than anything sent\" count: %d\n", rxMsgBufferTooBigCounter); - printf("\"The number received is out of the range\" count: %d\n", rxUnexpectedNumberCounter); - test_result = -2; - } - return test_result; -} diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt new file mode 100644 index 0000000000..8927d38bfc --- /dev/null +++ b/tests/mqtt/CMakeLists.txt @@ -0,0 +1,13 @@ +# MQTT tests executable. +add_executable( aws_iot_tests_mqtt + aws_iot_tests_mqtt.c + ${CMAKE_SOURCE_DIR}/tests/aws_iot_tests_network.c + unit/aws_iot_tests_mqtt_api.c + unit/aws_iot_tests_mqtt_receive.c + unit/aws_iot_tests_mqtt_subscription.c + unit/aws_iot_tests_mqtt_validate.c + system/aws_iot_tests_mqtt_system.c + system/aws_iot_tests_mqtt_stress.c ) + +# MQTT tests library dependencies. +target_link_libraries( aws_iot_tests_mqtt awsiotcommon awsiotplatform awsiotmqtt unity ) diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt.h b/tests/mqtt/access/aws_iot_test_access_mqtt.h new file mode 100644 index 0000000000..d922a759e7 --- /dev/null +++ b/tests/mqtt/access/aws_iot_test_access_mqtt.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_test_access_mqtt.h + * @brief Declares the functions that provide access to the internal functions + * and variables of the MQTT library. + */ + +#ifndef _AWS_IOT_TEST_ACCESS_MQTT_H_ +#define _AWS_IOT_TEST_ACCESS_MQTT_H_ + +/*--------------------------- aws_iot_mqtt_api.c ---------------------------*/ + +/** + * @brief Test access function for #_createMqttConnection. + * + * @see #_createMqttConnection. + */ +_mqttConnection_t * AwsIotTestMqtt_createMqttConnection( bool awsIotMqttMode, + const AwsIotMqttNetIf_t * const pNetworkInterface, + uint16_t keepAliveSeconds ); + +/** + * @brief Test access function for #_destroyMqttConnection. + * + * @see #_destroyMqttConnection. + */ +void AwsIotTestMqtt_destroyMqttConnection( _mqttConnection_t * const pMqttConnection ); + +/*------------------------- aws_iot_mqtt_serialize.c ------------------------*/ + +/* + * Macros for reading the high and low byte of a 2-byte unsigned int. + */ +#define _UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ +#define _UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ + +/** + * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. + * + * @param[in] ptr A uint8_t* that points to the high byte. + */ +#define _UINT16_DECODE( ptr ) \ + ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ + ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) + +/** + * @brief Test access function for #_decodeRemainingLength. + * + * @see #_decodeRemainingLength. + */ +AwsIotMqttError_t AwsIotTestMqtt_decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** const pEnd, + size_t * const pLength ); + +/*----------------------- aws_iot_mqtt_subscription.c -----------------------*/ + +/* Internal data structures of aws_iot_mqtt_subscription.c, redefined for the tests. */ +typedef struct _topicMatchParams +{ + const char * pTopicName; + uint16_t topicNameLength; + bool exactMatchOnly; +} _topicMatchParams_t; +typedef struct _packetMatchParams +{ + uint16_t packetIdentifier; + long order; +} _packetMatchParams_t; + +/** + * @brief Test access function for #_topicMatch. + * + * @see #_topicMatch. + */ +bool AwsIotTestMqtt_topicMatch( void * pArgument, + void * pData ); + +/** + * @brief Test access function for #_packetMatch. + * + * @see #_packetMatch. + */ +bool AwsIotTestMqtt_packetMatch( void * pArgument, + void * pData ); + +#endif /* ifndef _AWS_IOT_TEST_ACCESS_MQTT_H_ */ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_api.c b/tests/mqtt/access/aws_iot_test_access_mqtt_api.c new file mode 100644 index 0000000000..bfce784476 --- /dev/null +++ b/tests/mqtt/access/aws_iot_test_access_mqtt_api.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_test_access_mqtt_api.c + * @brief Provides access to the internal functions and variables of + * aws_iot_mqtt_api.c + * + * This file should only be included at the bottom of aws_iot_mqtt_api.c and never + * compiled by itself. + */ + +/*-----------------------------------------------------------*/ + +_mqttConnection_t * AwsIotTestMqtt_createMqttConnection( bool awsIotMqttMode, + const AwsIotMqttNetIf_t * const pNetworkInterface, + uint16_t keepAliveSeconds ) +{ + return _createMqttConnection( awsIotMqttMode, pNetworkInterface, keepAliveSeconds ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotTestMqtt_destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) +{ + _destroyMqttConnection( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c b/tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c new file mode 100644 index 0000000000..9191aa3b52 --- /dev/null +++ b/tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_test_access_mqtt_serialize.c + * @brief Provides access to the internal functions and variables of + * aws_iot_mqtt_serialize.c + * + * This file should only be included at the bottom of aws_iot_mqtt_serialize.c + * and never compiled by itself. + */ + +/*-----------------------------------------------------------*/ + +AwsIotMqttError_t AwsIotTestMqtt_decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** const pEnd, + size_t * const pLength ) +{ + return _decodeRemainingLength( pSource, pEnd, pLength ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c b/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c new file mode 100644 index 0000000000..9febbcd188 --- /dev/null +++ b/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_test_access_mqtt_subscription.c + * @brief Provides access to the internal functions and variables of + * aws_iot_mqtt_subscription.c + * + * This file should only be included at the bottom of aws_iot_mqtt_subscription.c + * and never compiled by itself. + */ + +/*-----------------------------------------------------------*/ + +bool AwsIotTestMqtt_topicMatch( void * pArgument, + void * pData ) +{ + return _topicMatch( pArgument, pData ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotTestMqtt_packetMatch( void * pArgument, + void * pData ) +{ + return _packetMatch( pArgument, pData ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/aws_iot_tests_mqtt.c b/tests/mqtt/aws_iot_tests_mqtt.c new file mode 100644 index 0000000000..2531d7269b --- /dev/null +++ b/tests/mqtt/aws_iot_tests_mqtt.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt.c + * @brief Test runner for the MQTT tests on POSIX systems. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler. Terminates the tests if called. + */ +static void _signalHandler( int signum ) +{ + /* Immediately terminate the tests if this signal handler is called. */ + if( signum == SIGSEGV ) + { + printf( "\nSegmentation fault.\n" ); + exit( EXIT_FAILURE ); + } + else if( signum == SIGABRT ) + { + printf( "\nAssertion failed.\n" ); + exit( EXIT_FAILURE ); + } +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + struct sigaction signalAction; + + /* Set a signal handler for segmentation faults and assertion failures. */ + ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); + signalAction.sa_handler = _signalHandler; + + if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) + { + return -1; + } + + if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + { + return -1; + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Run short tests. */ + RUN_TEST_GROUP( MQTT_Unit_Subscription ); + RUN_TEST_GROUP( MQTT_Unit_Validate ); + RUN_TEST_GROUP( MQTT_Unit_Receive ); + RUN_TEST_GROUP( MQTT_Unit_API ); + RUN_TEST_GROUP( MQTT_System ); + + /* The stress tests may take several minutes to run. Only run them if the + * -l command line argument was given. */ + if( getopt( argc, argv, "l" ) != -1 ) + { + RUN_TEST_GROUP( MQTT_Stress ); + } + + /* Return the number of test failures. This will cause a non-zero exit code + * if any test fails. */ + return UNITY_END(); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c new file mode 100644 index 0000000000..6e7bd3b7f4 --- /dev/null +++ b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt_stress.c + * @brief Stress tests for the AWS IoT MQTT library. + * + * The tests in this file run far longer than other tests, and may easily fail + * due to poor network conditions. For best results, these tests should be run + * on a stable local network (not the Internet). + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* POSIX includes. */ +#include + +/* MQTT include. */ +#include "aws_iot_mqtt.h" + +/* Platform layer include. */ +#include "platform/aws_iot_clock.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* The tests in this file run for a long time, so set up logging to track their + * progress. */ +#define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_INFO +#define _LIBRARY_LOG_NAME ( "MQTT_STRESS" ) +#include "aws_iot_logging_setup.h" + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + + /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER + #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef AWS_IOT_TEST_MQTT_TIMEOUT_MS + #define AWS_IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) +#endif +#ifndef AWS_IOT_TEST_MQTT_TOPIC_PREFIX + #define AWS_IOT_TEST_MQTT_TOPIC_PREFIX "awsiotmqtttest" +#endif +#ifndef AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S + #if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 + #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 5 ) + #else + #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) + #endif +#endif +#ifndef AWS_IOT_TEST_MQTT_RETRY_MS + #define AWS_IOT_TEST_MQTT_RETRY_MS ( 350 ) +#endif +#ifndef AWS_IOT_TEST_MQTT_RETRY_LIMIT + #define AWS_IOT_TEST_MQTT_RETRY_LIMIT ( 32 ) +#endif +#ifndef AWS_IOT_TEST_MQTT_DECONGEST_S + #define AWS_IOT_TEST_MQTT_DECONGEST_S ( 30 ) +#endif +#ifndef AWS_IOT_TEST_MQTT_THREADS + #define AWS_IOT_TEST_MQTT_THREADS ( 16 ) +#endif +#ifndef AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD + #ifdef AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS + #define AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ) + #else + #define AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( 100 ) + #endif +#endif +/** @endcond */ + +/** + * @brief Number of test topic names. + */ +#define _TEST_TOPIC_NAME_COUNT ( 8 ) + +/** + * @brief The maximum number of PUBLISH messages that will be received by a + * single test. + */ +#define _MAX_RECEIVED_PUBLISH ( AWS_IOT_TEST_MQTT_THREADS * AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) + +/** + * @brief The maximum length of an MQTT client identifier. + */ +#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Parameter 1 of #_publishThread. + */ +typedef struct _publishParams +{ + int threadNumber; /**< @brief ID number of this publish thread. */ + long publishPeriodNs; /**< @brief How long to wait (in nanoseconds) between each publish. */ + unsigned publishLimit; /**< @brief How many publishes this thread will send. */ + AwsIotMqttError_t status; /**< @brief Final status of this publish thread. */ +} _publishParams_t; + +/*-----------------------------------------------------------*/ + +/* Network functions used by the tests, declared and implemented in one of + * the test network function files. */ +extern bool AwsIotTest_NetworkSetup( void ); +extern void AwsIotTest_NetworkCleanup( void ); + +/* Extern declarations of default serializer functions. The internal MQTT header cannot + * be included by this file. */ +extern AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreqPacket, + size_t * const pPacketSize ); +extern void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ); + +/* Network variables used by the tests, declared in one of the test network + * function files. */ +extern AwsIotMqttNetIf_t _AwsIotTestNetworkInterface; +extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; + +/*-----------------------------------------------------------*/ + +/** + * @brief Filler text to publish. + */ +static const char _pSamplePayload[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" + " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" + " culpa qui officia deserunt mollit anim id est laborum."; + +/** + * @brief Length of #_pSamplePayload. + */ +static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; + +/** + * @brief Topic names used in the stress tests. + * + * For convenience, all topic names are the same length. + */ +static const char * const _pTopicNames[ _TEST_TOPIC_NAME_COUNT ] = +{ + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/1", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/2", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/3", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/4", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/5", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/6", + AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/7" +}; + +/** + * @brief Length of topic names used in the stress tests. + * + * For convenience, all topic names are the same length. + */ +static const uint16_t _topicNameLength = ( uint16_t ) sizeof( AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0" ) - 1; + +/** + * @brief Counts how many subscriptions were received for each test. + * + * Used in conjunction with #_publishReceived. + */ +static AwsIotSemaphore_t receivedPublishCounter; + +/** + * @brief Buffer holding the client identifier used for the tests. + */ +static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + +/** + * @brief Tracks whether the PINGREQ serializer override was called. + */ +static bool _pingreqOverrideCalled = false; + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for PINGREQ. + */ +static AwsIotMqttError_t _serializePingreq( uint8_t ** const pPingreqPacket, + size_t * const pPacketSize ) +{ + _pingreqOverrideCalled = true; + + return AwsIotMqttInternal_SerializePingreq( pPingreqPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Checks that #_AwsIotTestMqttConnection is still usable by sending a PUBLISH. + * + * @return The result of the PUBLISH. + */ +static AwsIotMqttError_t _checkConnection( void ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + + /* Set the publish info. */ + publishInfo.QoS = 1; + publishInfo.pTopicName = _pTopicNames[ 0 ]; + publishInfo.topicNameLength = _topicNameLength; + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryMs = AWS_IOT_TEST_MQTT_RETRY_MS; + publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; + + /* Send a PUBLISH. */ + status = AwsIotMqtt_Publish( _AwsIotTestMqttConnection, + &publishInfo, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishRef ); + + if( status != AWS_IOT_MQTT_STATUS_PENDING ) + { + return status; + } + + /* Return the result of the PUBLISH. */ + return AwsIotMqtt_Wait( publishRef, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscription callback function. + */ +static void _publishReceived( void * pArgument, + AwsIotMqttCallbackParam_t * const pPublish ) +{ + ( void ) pArgument; + + /* Increment the received PUBLISH counter if the received message matches + * what was published. */ + if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && + ( strncmp( _pSamplePayload, pPublish->message.info.pPayload, _samplePayloadLength ) == 0 ) && + ( pPublish->message.info.topicNameLength == _topicNameLength ) && + ( pPublish->message.info.QoS == 1 ) ) + { + AwsIotSemaphore_Post( &receivedPublishCounter ); + } + else + { + AwsIotLogWarn( "Received an unknown message on subscription %.*s: %.*s", + pPublish->message.info.topicNameLength, pPublish->message.info.pTopicName, + pPublish->message.info.payloadLength, pPublish->message.info.pPayload ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Callback function that blocks for a long time. + */ +static void _blockingCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const param ) +{ + AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + const unsigned blockTime = 5 * AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + + ( void ) param; + + AwsIotLogInfo( "Callback blocking for %u seconds.", blockTime ); + sleep( blockTime ); + AwsIotSemaphore_Post( pWaitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Periodically sends PUBLISH messages. + * + * @param[in] pArgument Pointer to a #_publishParams_t. + */ +static void * _publishThread( void * pArgument ) +{ + unsigned i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _publishParams_t * pParams = ( _publishParams_t * ) pArgument; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + const struct timespec sleepTime = + { + .tv_sec = 0, + .tv_nsec = pParams->publishPeriodNs + }; + + /* Set the publish info. */ + publishInfo.QoS = 1; + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.topicNameLength = _topicNameLength; + publishInfo.retryMs = AWS_IOT_TEST_MQTT_RETRY_MS; + publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; + + for( i = 0; i < pParams->publishLimit; ) + { + /* Choose a topic name. */ + publishInfo.pTopicName = _pTopicNames[ i % _TEST_TOPIC_NAME_COUNT ]; + + /* PUBLISH the message. */ + status = AwsIotMqtt_Publish( _AwsIotTestMqttConnection, + &publishInfo, + 0, + NULL, + NULL ); + + /* The stress tests may exhaust all memory available to the MQTT library. + * If no memory is available, wait some time for resources to be released. */ + if( status == AWS_IOT_MQTT_NO_MEMORY ) + { + AwsIotLogInfo( "Thread %d: no memory available on PUBLISH %d." + " Sleeping for %d seconds.", + pParams->threadNumber, + i, + AWS_IOT_TEST_MQTT_DECONGEST_S ); + sleep( AWS_IOT_TEST_MQTT_DECONGEST_S ); + continue; + } + /* If the PUBLISH failed, exit this thread. */ + else if( status != AWS_IOT_MQTT_STATUS_PENDING ) + { + AwsIotLogError( "Thread %d encountered error %d publishing message %d.", + status, i ); + break; + } + + /* Only increment the loop counter if the PUBLISH succeeded. */ + i++; + + /* Occasionally print an update on how many PUBLISH messages this thread + * has sent. */ + if( ( i % 25 == 0 ) || + ( i == pParams->publishLimit ) ) + { + AwsIotLogInfo( "Thread %d has published %d of %d messages.", + pParams->threadNumber, i, pParams->publishLimit ); + } + + /* Sleep until the next PUBLISH should be sent. */ + if( nanosleep( &sleepTime, NULL ) != 0 ) + { + AwsIotLogError( "Error in nanosleep." ); + status = AWS_IOT_MQTT_BAD_RESPONSE; + break; + } + } + + /* Set the thread's last status. */ + pParams->status = status; + + return NULL; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT stress tests. + */ +TEST_GROUP( MQTT_Stress ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT stress tests. + */ +TEST_SETUP( MQTT_Stress ) +{ + int i = 0; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + const AwsIotLogConfig_t logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; + + /* Clear the PINGREQ override flag. */ + _pingreqOverrideCalled = false; + + /* Empty log message to log a new line. */ + AwsIotLog( AWS_IOT_LOG_INFO, &logHideAll, " " ); + + /* Create the publish counter semaphore. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &receivedPublishCounter, + 0, + _MAX_RECEIVED_PUBLISH ) ); + + /* Set the serializer overrides. */ + _AwsIotTestNetworkInterface.serialize.pingreq = _serializePingreq; + _AwsIotTestNetworkInterface.freePacket = AwsIotMqttInternal_FreePacket; + + /* Set up the network stack. */ + if( AwsIotTest_NetworkSetup() == false ) + { + TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + } + + /* Initialize the MQTT library. */ + if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } + + /* Generate a new, unique client identifier based on the time. */ + ( void ) snprintf( _pClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "aws%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + + /* Set the members of the connect info. */ + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + /* Set the members of the subscriptions */ + for( i = 0; i < _TEST_TOPIC_NAME_COUNT; i++ ) + { + pSubscriptions[ i ].pTopicFilter = _pTopicNames[ i ]; + pSubscriptions[ i ].topicFilterLength = _topicNameLength; + pSubscriptions[ i ].QoS = 1; + pSubscriptions[ i ].callback.function = _publishReceived; + } + + /* Establish the MQTT connection. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + NULL, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) ); + + /* Subscribe to the test topic filters. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotMqtt_TimedSubscribe( _AwsIotTestMqttConnection, + pSubscriptions, + _TEST_TOPIC_NAME_COUNT, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT stress tests. + */ +TEST_TEAR_DOWN( MQTT_Stress ) +{ + /* Destroy the PUBLISH counter semaphore. */ + AwsIotSemaphore_Destroy( &receivedPublishCounter ); + + /* Disconnect the MQTT connection. Unsubscribe is not called; the subscriptions + * should be cleaned up by Disconnect. */ + if( _AwsIotTestMqttConnection != AWS_IOT_MQTT_CONNECTION_INITIALIZER ) + { + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + } + + /* Clean up the network stack. */ + AwsIotTest_NetworkCleanup(); + + /* Clean up the MQTT library. */ + AwsIotMqtt_Cleanup(); + _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT stress tests. + */ +TEST_GROUP_RUNNER( MQTT_Stress ) +{ + RUN_TEST_CASE( MQTT_Stress, KeepAlive ); + RUN_TEST_CASE( MQTT_Stress, BlockingCallback ); + RUN_TEST_CASE( MQTT_Stress, ClientClosesConnection ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that keep-alive keeps an idle connection open. + */ +TEST( MQTT_Stress, KeepAlive ) +{ + const unsigned sleepTime = 5 * AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + + /* Send no MQTT packets for a long time. The keep-alive must be used to keep + * the connection open. */ + AwsIotLogInfo( "KeepAlive test sleeping for %u seconds.", sleepTime ); + sleep( sleepTime ); + + /* Send a PUBLISH to verify that the connection is still usable. */ + AwsIotLogInfo( "KeepAlive test checking MQTT connection." ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); + + /* Check that the PINGREQ override was used. */ + TEST_ASSERT_EQUAL( true, _pingreqOverrideCalled ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that the MQTT connection is not closed if a user callback blocks. + */ +TEST( MQTT_Stress, BlockingCallback ) +{ + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + AwsIotSemaphore_t waitSem; + + callbackInfo.function = _blockingCallback; + callbackInfo.param1 = &waitSem; + + publishInfo.QoS = 1; + publishInfo.pTopicName = _pTopicNames[ 0 ]; + publishInfo.topicNameLength = _topicNameLength; + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryMs = AWS_IOT_TEST_MQTT_RETRY_MS; + publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; + + /* Create the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Call a function that will invoke the blocking callback. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_STATUS_PENDING, + AwsIotMqtt_Publish( _AwsIotTestMqttConnection, + &publishInfo, + 0, + &callbackInfo, + NULL ) ); + + /* Wait for the callback to return, then check that the connection is + * still usable. */ + AwsIotSemaphore_Wait( &waitSem ); + AwsIotLogInfo( "BlockingCallback test checking MQTT connection." ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); + } + + AwsIotSemaphore_Destroy( &waitSem ); + + /* Check that the PINGREQ override was used. */ + TEST_ASSERT_EQUAL( true, _pingreqOverrideCalled ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test the behavior of the MQTT library when the network connection is + * closed by the client. + */ +TEST( MQTT_Stress, ClientClosesConnection ) +{ + int i = 0, threadsCreated = 0, threadsJoined = 0; + pthread_t publishThreads[ AWS_IOT_TEST_MQTT_THREADS ] = { 0 }; + _publishParams_t publishThreadParams[ AWS_IOT_TEST_MQTT_THREADS ] = { 0 }; + + /* Set the parameters for each thread. */ + for( i = 0; i < AWS_IOT_TEST_MQTT_THREADS; i++ ) + { + publishThreadParams[ i ].threadNumber = i; + publishThreadParams[ i ].publishPeriodNs = 500000000; + publishThreadParams[ i ].publishLimit = AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD; + } + + AwsIotLogInfo( "ClientClosesConnection test creating threads." ); + + /* Spawn the threads for the test. */ + for( i = 0; i < AWS_IOT_TEST_MQTT_THREADS; i++ ) + { + if( pthread_create( &( publishThreads[ i ] ), + NULL, + _publishThread, + &( publishThreadParams[ i ] ) ) != 0 ) + { + break; + } + } + + /* Record how many threads were created. */ + threadsCreated = i; + + /* Wait for all created threads to finish. */ + for( i = 0; i < threadsCreated; i++ ) + { + if( pthread_join( publishThreads[ i ], NULL ) == 0 ) + { + threadsJoined++; + } + } +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c new file mode 100644 index 0000000000..6ac1af13fe --- /dev/null +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt_system.c + * @brief Full system tests for the AWS IoT MQTT library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/aws_iot_clock.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int snprintf( char *, + size_t, + const char *, + ... ); +/** @endcond */ + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef AWS_IOT_TEST_MQTT_TIMEOUT_MS + #define AWS_IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) +#endif +#ifndef AWS_IOT_TEST_MQTT_TOPIC_PREFIX + #define AWS_IOT_TEST_MQTT_TOPIC_PREFIX "awsiotmqtttest" +#endif +/** @endcond */ + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + + /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER + #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/** + * @brief The maximum length of an MQTT client identifier. + */ +#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Parameter 1 of #_operationComplete. + */ +typedef struct _operationCompleteParams +{ + AwsIotMqttOperationType_t expectedOperation; /**< @brief Expected completed operation. */ + AwsIotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ + AwsIotMqttReference_t reference; /**< @brief Reference to expected completed operation. */ +} _operationCompleteParams_t; + +/*-----------------------------------------------------------*/ + +/* Network functions used by the tests, declared and implemented in one of + * the test network function files. */ +extern bool AwsIotTest_NetworkSetup( void ); +extern void AwsIotTest_NetworkCleanup( void ); +extern bool AwsIotTest_NetworkConnect( void ** const pNewConnection, + AwsIotMqttConnection_t * pMqttConnection ); +extern void AwsIotTest_NetworkClose( void * pDisconnectContext ); +extern void AwsIotTest_NetworkDestroy( void * pConnection ); + +/* Network variables used by the tests, declared in one of the test network + * function files. */ +extern AwsIotMqttNetIf_t _AwsIotTestNetworkInterface; +extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; + +/*-----------------------------------------------------------*/ + +/** + * @brief Filler text to publish. + */ +static const char _pSamplePayload[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" + " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" + " culpa qui officia deserunt mollit anim id est laborum."; + +/** + * @brief Length of #_pSamplePayload. + */ +static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; + +/** + * @brief Buffer holding the client identifier used for the tests. + */ +static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + +/* + * Track if the serializer overrides were called for a test. + */ +static bool _freePacketOverride = false; /**< @brief Tracks if #_freePacket was called. */ +static bool _connectSerializerOverride = false; /**< @brief Tracks if #_connectSerializerOverride was called. */ +static bool _publishSerializerOverride = false; /**< @brief Tracks if #_publishSerializerOverride was called. */ +static bool _pubackSerializerOverride = false; /**< @brief Tracks if #_pubackSerializerOverride was called. */ +static bool _subscribeSerializerOverride = false; /**< @brief Tracks if #_subscribeSerializerOverride was called. */ +static bool _unsubscribeSerializerOverride = false; /**< @brief Tracks if #_unsubscribeSerializerOverride was called. */ +static bool _disconnectSerializerOverride = false; /**< @brief Tracks if #_disconnectSerializerOverride was called. */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Packet free function override + */ +static void _freePacket( uint8_t * pPacket ) +{ + _freePacketOverride = true; + + AwsIotMqttInternal_FreePacket( pPacket ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for CONNECT. + */ +static AwsIotMqttError_t _serializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, + const AwsIotMqttPublishInfo_t * const pWillInfo, + uint8_t ** const pConnectPacket, + size_t * const pPacketSize ) +{ + _connectSerializerOverride = true; + + return AwsIotMqttInternal_SerializeConnect( pConnectInfo, + pWillInfo, + pConnectPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for PUBLISH. + */ +static AwsIotMqttError_t _serializePublish( const AwsIotMqttPublishInfo_t * const pPublishInfo, + uint8_t ** const pPublishPacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + _publishSerializerOverride = true; + + return AwsIotMqttInternal_SerializePublish( pPublishInfo, + pPublishPacket, + pPacketSize, + pPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for PUBACK. + */ +static AwsIotMqttError_t _serializePuback( uint16_t packetIdentifier, + uint8_t ** const pPubackPacket, + size_t * const pPacketSize ) +{ + _pubackSerializerOverride = true; + + return AwsIotMqttInternal_SerializePuback( packetIdentifier, + pPubackPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for SUBSCRIBE. + */ +static AwsIotMqttError_t _serializeSubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pSubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + _subscribeSerializerOverride = true; + + return AwsIotMqttInternal_SerializeSubscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for UNSUBSCRIBE. + */ +static AwsIotMqttError_t _serializeUnsubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint8_t ** const pSubscribePacket, + size_t * const pPacketSize, + uint16_t * const pPacketIdentifier ) +{ + _unsubscribeSerializerOverride = true; + + return AwsIotMqttInternal_SerializeUnsubscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for DISCONNECT. + */ +static AwsIotMqttError_t _serializeDisconnect( uint8_t ** const pDisconnectPacket, + size_t * const pPacketSize ) +{ + _disconnectSerializerOverride = true; + + return AwsIotMqttInternal_SerializeDisconnect( pDisconnectPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscription callback function. Checks for valid parameters and unblocks + * the main test thread. + */ +static void _publishReceived( void * pArgument, + AwsIotMqttCallbackParam_t * const pPublish ) +{ + AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + + /* If the received messages matches what was sent, unblock the waiting thread. */ + if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && + ( strncmp( pPublish->message.info.pPayload, + _pSamplePayload, + _samplePayloadLength ) == 0 ) ) + { + AwsIotSemaphore_Post( pWaitSem ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Operation completion callback function. Checks for valid parameters + * and unblocks the main test thread. + */ +static void _operationComplete( void * pArgument, + AwsIotMqttCallbackParam_t * const pOperation ) +{ + _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; + + /* If the operation information matches the parameters and the operation was + * successful, unblock the waiting thread. */ + if( ( pParams->expectedOperation == pOperation->operation.type ) && + ( pParams->reference == pOperation->operation.reference ) && + ( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) ) + { + AwsIotSemaphore_Post( &( pParams->waitSem ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Run the subscribe-publish-wait tests at various QoS. + */ +static void _subscribePublishWait( int QoS ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttNetIf_t networkInterface = _AwsIotTestNetworkInterface; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotSemaphore_t waitSem; + + /* Set the serializer overrides. */ + networkInterface.freePacket = _freePacket; + networkInterface.serialize.connect = _serializeConnect; + networkInterface.serialize.publish = _serializePublish; + networkInterface.serialize.puback = _serializePuback; + networkInterface.serialize.subscribe = _serializeSubscribe; + networkInterface.serialize.unsubscribe = _serializeUnsubscribe; + networkInterface.serialize.disconnect = _serializeDisconnect; + + /* Create the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the members of the MQTT connection info. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + /* Establish the MQTT connection. */ + status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &networkInterface, + &connectInfo, + NULL, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Set the members of the subscription. */ + subscription.QoS = QoS; + subscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.function = _publishReceived; + subscription.callback.param1 = &waitSem; + + /* Subscribe to the test topic filter using the blocking SUBSCRIBE + * function. */ + status = AwsIotMqtt_TimedSubscribe( _AwsIotTestMqttConnection, + &subscription, + 1, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Set the members of the publish info. */ + publishInfo.QoS = QoS; + publishInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + + /* Publish the message. */ + status = AwsIotMqtt_TimedPublish( _AwsIotTestMqttConnection, + &publishInfo, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + + /* Wait for the message to be received. */ + if( AwsIotSemaphore_TimedWait( &waitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); + } + + /* Unsubscribe from the test topic filter. */ + status = AwsIotMqtt_TimedUnsubscribe( _AwsIotTestMqttConnection, + &subscription, + 1, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + } + + /* Close the MQTT connection. */ + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + } + + AwsIotSemaphore_Destroy( &waitSem ); + + /* Check that the serializer overrides were called. */ + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, _freePacketOverride ); + TEST_ASSERT_EQUAL_INT( true, _connectSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _publishSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _subscribeSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _unsubscribeSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _disconnectSerializerOverride ); + + if( QoS == 1 ) + { + TEST_ASSERT_EQUAL_INT( true, _pubackSerializerOverride ); + } + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT system tests. + */ +TEST_GROUP( MQTT_System ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT system tests. + */ +TEST_SETUP( MQTT_System ) +{ + /* Clear the serializer override flags. */ + _freePacketOverride = false; + _connectSerializerOverride = false; + _publishSerializerOverride = false; + _pubackSerializerOverride = false; + _subscribeSerializerOverride = false; + _unsubscribeSerializerOverride = false; + _disconnectSerializerOverride = false; + + /* Set up the network stack. */ + if( AwsIotTest_NetworkSetup() == false ) + { + TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + } + + /* Initialize the MQTT library. */ + if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } + + /* Generate a new, unique client identifier based on the time. */ + ( void ) snprintf( _pClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "aws%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT system tests. + */ +TEST_TEAR_DOWN( MQTT_System ) +{ + /* Clean up the network stack. */ + AwsIotTest_NetworkCleanup(); + + /* Clean up the MQTT library. */ + AwsIotMqtt_Cleanup(); + _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT system tests. + */ +TEST_GROUP_RUNNER( MQTT_System ) +{ + RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS0 ); + RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS1 ); + RUN_TEST_CASE( MQTT_System, SubscribePublishAsync ); + RUN_TEST_CASE( MQTT_System, LastWillAndTestament ); + + /* Persistent sessions are currently unsupported by AWS IoT. */ + #if _AWS_IOT_MQTT_SERVER == false + RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); + #endif +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscribe-publish-wait (QoS 0). + */ +TEST( MQTT_System, SubscribePublishWaitQoS0 ) +{ + _subscribePublishWait( 0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscribe-publish-wait (QoS 1). + */ +TEST( MQTT_System, SubscribePublishWaitQoS1 ) +{ + _subscribePublishWait( 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscribe-publish asynchronous (QoS 1). + */ +TEST( MQTT_System, SubscribePublishAsync ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + _operationCompleteParams_t callbackParam = { 0 }; + AwsIotSemaphore_t publishWaitSem; + + /* Initialize members of the operation callback info. */ + callbackInfo.function = _operationComplete; + callbackInfo.param1 = &callbackParam; + + /* Initialize members of the subscription. */ + subscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.function = _publishReceived; + subscription.callback.param1 = &publishWaitSem; + + /* Initialize members of the connect info. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + /* Initialize members of the publish info. */ + publishInfo.QoS = 1; + publishInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + + /* Create the wait semaphore for operations. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Create the wait semaphore for subscriptions. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &publishWaitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Establish the MQTT connection. */ + status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + NULL, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Subscribe to the test topic filter. */ + callbackParam.expectedOperation = AWS_IOT_MQTT_SUBSCRIBE; + status = AwsIotMqtt_Subscribe( _AwsIotTestMqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for SUBSCRIBE to complete." ); + } + + /* Publish the message. */ + callbackParam.expectedOperation = AWS_IOT_MQTT_PUBLISH_TO_SERVER; + status = AwsIotMqtt_Publish( _AwsIotTestMqttConnection, + &publishInfo, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for PUBLISH to complete." ); + } + + /* Wait for the message to be received. */ + if( AwsIotSemaphore_TimedWait( &publishWaitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); + } + + /* Unsubscribe from the test topic filter. */ + callbackParam.expectedOperation = AWS_IOT_MQTT_UNSUBSCRIBE; + status = AwsIotMqtt_Unsubscribe( _AwsIotTestMqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for UNSUBSCRIBE to complete." ); + } + } + + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + } + + AwsIotSemaphore_Destroy( &publishWaitSem ); + } + + AwsIotSemaphore_Destroy( &( callbackParam.waitSem ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that a LWT is published if an MQTT connection is unexpectedly closed. + */ +TEST( MQTT_System, LastWillAndTestament ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttNetIf_t lwtNetIf = _AwsIotTestNetworkInterface; + char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + AwsIotMqttConnection_t lwtListener = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + AwsIotMqttConnectInfo_t lwtConnectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER, + connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttSubscription_t willSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + void * pLwtListenerConnection = NULL; + AwsIotSemaphore_t waitSem; + + /* Create the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + + /* Generate client identifier for LWT listener. */ + ( void ) snprintf( pLwtListenerClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "awslwt%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + + if( TEST_PROTECT() ) + { + /* Establish an independent MQTT over TCP connection to receive a Last + * Will and Testament message. */ + TEST_ASSERT_EQUAL( true, + AwsIotTest_NetworkConnect( &pLwtListenerConnection, + &lwtListener ) ); + + if( TEST_PROTECT() ) + { + lwtNetIf.pDisconnectContext = pLwtListenerConnection; + lwtNetIf.pSendContext = pLwtListenerConnection; + lwtConnectInfo.cleanSession = true; + lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; + lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); + + status = AwsIotMqtt_Connect( &lwtListener, + &lwtNetIf, + &lwtConnectInfo, + NULL, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Register a subscription for the LWT. */ + willSubscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); + willSubscription.callback.function = _publishReceived; + willSubscription.callback.param1 = &waitSem; + + status = AwsIotMqtt_TimedSubscribe( lwtListener, + &willSubscription, + 1, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Create a connection that requests the LWT. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + willInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); + willInfo.pPayload = _pSamplePayload; + willInfo.payloadLength = _samplePayloadLength; + + status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + &willInfo, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Abruptly close the MQTT connection. This should cause the LWT + * to be sent to the LWT listener. */ + AwsIotTest_NetworkClose( NULL ); + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, true ); + AwsIotTest_NetworkDestroy( NULL ); + + /* Check that the LWT was received. */ + if( AwsIotSemaphore_TimedWait( &waitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); + } + } + + AwsIotMqtt_Disconnect( lwtListener, false ); + AwsIotTest_NetworkDestroy( pLwtListenerConnection ); + pLwtListenerConnection = NULL; + } + + if( pLwtListenerConnection != NULL ) + { + AwsIotTest_NetworkClose( pLwtListenerConnection ); + AwsIotTest_NetworkDestroy( pLwtListenerConnection ); + } + } + + AwsIotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that subscriptions from a previous session are restored. + */ +TEST( MQTT_System, RestorePreviousSession ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotSemaphore_t waitSem; + + /* Create the wait semaphore for operations. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + + /* Set the members of the connection info for a persistent session. */ + connectInfo.cleanSession = false; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + if( TEST_PROTECT() ) + { + /* Establish a persistent MQTT connection. */ + status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + NULL, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Add a subscription. */ + subscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.param1 = &waitSem; + subscription.callback.function = _publishReceived; + + status = AwsIotMqtt_TimedSubscribe( _AwsIotTestMqttConnection, + &subscription, + 1, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Disconnect the MQTT connection and clean up network connection. */ + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + AwsIotTest_NetworkCleanup(); + + /* Re-establish the network connection. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotTest_NetworkSetup() ); + + /* Re-establish the MQTT connection with a previous session. */ + connectInfo.cleanSession = false; + status = AwsIotMqtt_ConnectRestoreSession( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + NULL, + &subscription, + 1, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Publish a message to the subscription added in the previous session. */ + publishInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + + status = AwsIotMqtt_TimedPublish( _AwsIotTestMqttConnection, + &publishInfo, + 0, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Wait for the message to be received. */ + if( AwsIotSemaphore_TimedWait( &waitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for message." ); + } + + /* Disconnect the MQTT connection. */ + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + AwsIotTest_NetworkCleanup(); + } + + AwsIotSemaphore_Destroy( &waitSem ); + + if( TEST_PROTECT() ) + { + /* Re-establish the network connection. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotTest_NetworkSetup() ); + + /* After this test is finished, establish one more connection with a clean + * session to clean up persistent sessions on the MQTT server created by this + * test. */ + connectInfo.cleanSession = true; + status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + NULL, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c new file mode 100644 index 0000000000..ba563de69c --- /dev/null +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -0,0 +1,961 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt_api.c + * @brief Tests for the user-facing API functions (declared in aws_iot_mqtt.h). + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* POSIX includes. */ +#ifdef POSIX_UNISTD_HEADER + #include POSIX_UNISTD_HEADER +#else + #include +#endif + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/aws_iot_clock.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* MQTT test access include. */ +#include "aws_iot_test_access_mqtt.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + + /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER + #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/** + * @brief Timeout to use for the tests. This can be short, but should allow time + * for other threads to run. + */ +#define _TIMEOUT_MS ( 400 ) + +/* + * Client identifier and length to use for the MQTT API tests. + */ +#define _CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define _CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( _CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ + +/* + * Will topic name and length to use for the MQTT API tests. + */ +#define _TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define _TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ + +/** + * @brief A non-NULL function pointer to use for subscription callback. This + * "function" should cause a crash if actually called. + */ +#define _SUBSCRIPTION_CALLBACK \ + ( ( void ( * )( void *, \ + AwsIotMqttCallbackParam_t * const ) ) 0x01 ) + +/** + * @brief How many times #TEST_MQTT_Unit_API_DisconnectMallocFail_ will test + * malloc failures. + * + * The DISCONNECT function provides no mechanism to wait on its successful + * completion. Therefore, this function simply uses the value below as an estimate + * for the maximum number of times DISCONNECT will use malloc. + */ +#define _DISCONNECT_MALLOC_LIMIT ( 20 ) + +/* + * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. + */ +#define _DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ +#define _DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ +#define _DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. + * Duplicates are sent using an exponential backoff strategy. */ +/** @brief The minimum amount of time the test can take. */ +#define _DUP_CHECK_MINIMUM_WAIT \ + ( _DUP_CHECK_RETRY_MS + \ + 2 * _DUP_CHECK_RETRY_MS + \ + 4 * _DUP_CHECK_RETRY_MS + \ + AWS_IOT_MQTT_RESPONSE_WAIT_MS ) + +/** + * @brief Tracks whether #_publishSetDup has been called. + */ +static bool _publishSetDupCalled = false; + +/*-----------------------------------------------------------*/ + +/** + * @brief PUBLISH set DUP function override. + */ +static void _publishSetDup( bool awsIotMqttMode, + uint8_t * const pPublishPacket, + uint16_t * const pNewPacketIdentifier ) +{ + _publishSetDupCalled = true; + + AwsIotMqttInternal_PublishSetDup( awsIotMqttMode, + pPublishPacket, + pNewPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A send function that always "succeeds". + */ +static size_t _sendSuccess( void * pSendContext, + const void * const pMessage, + size_t messageLength ) +{ + ( void ) pSendContext; + ( void ) pMessage; + + /* This function just returns the message length to simulate a successful + * send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief This send function checks that a duplicate outgoing message differs from + * the original. + */ +static size_t _dupChecker( void * pSendContext, + const void * const pMessage, + size_t messageLength ) +{ + static int runCount = 0; + static bool status = true; + bool * pDupCheckResult = ( bool * ) pSendContext; + uint8_t publishFlags = *( ( uint8_t * ) pMessage ); + + /* Declare the remaining variables required to check packet identifier + * for the AWS IoT MQTT server. */ + #if _AWS_IOT_MQTT_SERVER == true + static uint16_t lastPacketIdentifier = 0; + uint16_t currentPacketIdentifier = 0; + size_t bytesProcessed = 0; + AwsIotMqttPublishInfo_t publishInfo = { 0 }; + #endif + + /* Ignore any MQTT packet that's not a PUBLISH. */ + if( ( publishFlags & 0xf0 ) != _MQTT_PACKET_TYPE_PUBLISH ) + { + return messageLength; + } + + runCount++; + + /* Check how many times this function has been called. */ + if( runCount == 1 ) + { + #if _AWS_IOT_MQTT_SERVER == true + /* Deserialize the PUBLISH to read the packet identifier. */ + if( AwsIotMqttInternal_DeserializePublish( pMessage, + messageLength, + &publishInfo, + &lastPacketIdentifier, + &bytesProcessed ) != AWS_IOT_MQTT_SUCCESS ) + { + status = false; + } + else + { + /* Ensure that all bytes of the PUBLISH were processed. */ + status = ( bytesProcessed == messageLength ); + } + #else /* if _AWS_IOT_MQTT_SERVER == true */ + /* DUP flag should not be set on this function's first run. */ + if( ( publishFlags & 0x08 ) == 0x08 ) + { + status = false; + } + #endif /* if _AWS_IOT_MQTT_SERVER == true */ + } + else + { + /* Only check the packet again if the previous run checks passed. */ + if( status == true ) + { + #if _AWS_IOT_MQTT_SERVER == true + /* Deserialize the PUBLISH to read the packet identifier. */ + if( AwsIotMqttInternal_DeserializePublish( pMessage, + messageLength, + &publishInfo, + ¤tPacketIdentifier, + &bytesProcessed ) != AWS_IOT_MQTT_SUCCESS ) + { + status = false; + } + else + { + /* Ensure that all bytes of the PUBLISH were processed. */ + status = ( bytesProcessed == messageLength ); + + /* Check that the packet identifier is different. */ + if( status == true ) + { + status = ( currentPacketIdentifier != lastPacketIdentifier ); + lastPacketIdentifier = currentPacketIdentifier; + } + } + #else /* if _AWS_IOT_MQTT_SERVER == true */ + /* DUP flag should be set when this function runs again. */ + if( ( publishFlags & 0x08 ) != 0x08 ) + { + status = false; + } + #endif /* if _AWS_IOT_MQTT_SERVER == true */ + } + + /* Write the check result on the last expected run of this function. */ + if( runCount == _DUP_CHECK_RETRY_LIMIT ) + { + *pDupCheckResult = status; + } + } + + /* Return the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A disconnect function that only reports whether it was invoked. + */ +static void _disconnect( void * pDisconnectContext ) +{ + bool * pDisconnectInvoked = ( bool * ) pDisconnectContext; + + /* This function just sets a flag testifying that it was invoked. */ + if( pDisconnectInvoked != NULL ) + { + *pDisconnectInvoked = true; + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT API tests. + */ +TEST_GROUP( MQTT_Unit_API ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT API tests. + */ +TEST_SETUP( MQTT_Unit_API ) +{ + _publishSetDupCalled = false; + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT API tests. + */ +TEST_TEAR_DOWN( MQTT_Unit_API ) +{ + AwsIotMqtt_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT API tests. + */ +TEST_GROUP_RUNNER( MQTT_Unit_API ) +{ + RUN_TEST_CASE( MQTT_Unit_API, ConnectParameters ); + RUN_TEST_CASE( MQTT_Unit_API, ConnectMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, ConnectRestoreSessionMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, DisconnectMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0Parameters ); + RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0MallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, PublishQoS1 ); + RUN_TEST_CASE( MQTT_Unit_API, PublishDuplicates ); + RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); + RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, UnsubscribeMallocFail ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect with various + * invalid parameters. + */ +TEST( MQTT_Unit_API, ConnectParameters ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Check that the network interface is validated. */ + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + NULL, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + networkInterface.send = _sendSuccess; + + /* Check that the connection info is validated. */ + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + NULL, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + + /* AwsIotMqtt_ConnectRestoreSession with bad subscription. */ + connectInfo.cleanSession = false; + status = AwsIotMqtt_ConnectRestoreSession( &mqttConnection, + &networkInterface, + &connectInfo, + NULL, + &subscription, + 1, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + /* AwsIotMqtt_ConnectRestoreSession with clean session. */ + connectInfo.cleanSession = true; + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + status = AwsIotMqtt_ConnectRestoreSession( &mqttConnection, + &networkInterface, + &connectInfo, + &willInfo, + &subscription, + 1, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + /* Check that the will info is validated when it's provided. */ + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + &willInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + willInfo.pTopicName = _TEST_TOPIC_NAME; + willInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + /* Check that a will message longer than 65535 is not allowed. */ + willInfo.pPayload = ""; + willInfo.payloadLength = 65536; + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + &willInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + willInfo.payloadLength = 0; + + /* Check that passing a wait time of 0 returns immediately. */ + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + &willInfo, + 0 ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, ConnectMallocFail ) +{ + int i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Initialize parameters. */ + networkInterface.send = _sendSuccess; + connectInfo.keepAliveSeconds = 100; + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call CONNECT. Memory allocation will fail at various times during + * this call. */ + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + NULL, + _TIMEOUT_MS ); + + /* If the return value is timeout, then all memory allocation succeeded + * and the loop can exit. The expected return value is timeout (and not + * success) because the receive callback is never invoked. */ + if( status == AWS_IOT_MQTT_TIMEOUT ) + { + break; + } + + /* If the return value isn't timeout, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connectrestoresession + * when memory allocation fails at various points. + */ +TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) +{ + int i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Initialize parameters. */ + networkInterface.send = _sendSuccess; + connectInfo.cleanSession = false; + connectInfo.keepAliveSeconds = 100; + connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call CONNECT with a previous session. Memory allocation will fail at + * various times during this call. */ + status = AwsIotMqtt_ConnectRestoreSession( &mqttConnection, + &networkInterface, + &connectInfo, + NULL, + &subscription, + 1, + _TIMEOUT_MS ); + + /* If the return value is timeout, then all memory allocation succeeded + * and the loop can exit. The expected return value is timeout (and not + * success) because the receive callback is never invoked. */ + if( status == AWS_IOT_MQTT_TIMEOUT ) + { + break; + } + + /* If the return value isn't timeout, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_disconnect when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, DisconnectMallocFail ) +{ + int i = 0; + bool disconnectInvoked = false; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + _mqttConnection_t * pMqttConnection = NULL; + + /* Set the members of the network interface. */ + networkInterface.pDisconnectContext = &disconnectInvoked; + networkInterface.send = _sendSuccess; + networkInterface.disconnect = _disconnect; + + for( i = 0; i < _DISCONNECT_MALLOC_LIMIT; i++ ) + { + /* Allow unlimited use of malloc during connection initialization. */ + UnityMalloc_MakeMallocFailAfterCount( -1 ); + + /* Create a new MQTT connection. */ + pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); + + /* Set malloc to eventually fail. */ + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call DISCONNECT; this function should always perform cleanup regardless + * of memory allocation errors. */ + AwsIotMqtt_Disconnect( pMqttConnection, false ); + TEST_ASSERT_EQUAL_INT( true, disconnectInvoked ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) with various + * valid and invalid parameters. + */ +TEST( MQTT_Unit_API, PublishQoS0Parameters ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t mqttConnection = { 0 }; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttReference_t publishReference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Set a send function for the MQTT connection. */ + mqttConnection.network.send = _sendSuccess; + + /* Check that the publish info is validated. */ + status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + /* Check that a QoS 0 publish is refused if a notification is requested. */ + status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, AWS_IOT_MQTT_FLAG_WAITABLE, NULL, &publishReference ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, &callbackInfo, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + /* If valid parameters are passed, QoS 0 publish should always return success. */ + status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, 0, &publishReference ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Allow time for the send thread to run and clean up the PUBLISH. QoS 0 + * PUBLISH provides no mechanism to wait on completion, so sleep is used. */ + sleep( 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, PublishQoS0MallocFail ) +{ + int i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t mqttConnection = { 0 }; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Set a send function for the MQTT connection; also set the necessary + * members of publish info. */ + mqttConnection.network.send = _sendSuccess; + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call PUBLISH. Memory allocation will fail at various times during + * this call. */ + status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, NULL, NULL ); + + /* Once PUBLISH succeeds, the loop can exit. */ + if( status == AWS_IOT_MQTT_SUCCESS ) + { + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + } + + /* Wait for any pending QoS 0 publishes to clean up. */ + sleep( 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publish (QoS 1) with various + * invalid parameters. Also tests the behavior of @ref mqtt_function_publish + * (QoS 1) when memory allocation fails at various points. + */ +TEST( MQTT_Unit_API, PublishQoS1 ) +{ + int i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = NULL; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.send = _sendSuccess; + pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); + + /* Set the publish info. */ + publishInfo.QoS = 1; + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + if( TEST_PROTECT() ) + { + /* Setting the waitable flag with no reference is not allowed. */ + status = AwsIotMqtt_Publish( pMqttConnection, + &publishInfo, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + /* Setting both the waitable flag and callback info is not allowed. */ + status = AwsIotMqtt_Publish( pMqttConnection, + &publishInfo, + AWS_IOT_MQTT_FLAG_WAITABLE, + &callbackInfo, + &publishRef ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + /* Check QoS 1 PUBLISH behavior with malloc failures. */ + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call PUBLISH. Memory allocation will fail at various times during + * this call. */ + status = AwsIotMqtt_Publish( pMqttConnection, + &publishInfo, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishRef ); + + /* If the PUBLISH succeeded, the loop can exit after waiting for the QoS + * 1 PUBLISH to be cleaned up. */ + if( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, AwsIotMqtt_Wait( publishRef, _TIMEOUT_MS ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + } + } + + /* Clean up MQTT connection. */ + AwsIotTestMqtt_destroyMqttConnection( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that duplicate QoS 1 PUBLISH packets are different from the + * original. + * + * For non-AWS IoT MQTT servers, checks that the DUP flag is set. For + * AWS IoT MQTT servers, checks that the packet identifier is different. + */ +TEST( MQTT_Unit_API, PublishDuplicates ) +{ + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + _mqttConnection_t * pMqttConnection = NULL; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + volatile bool dupCheckResult = false; + uint64_t startTime = 0; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.pSendContext = ( void * ) &dupCheckResult; + networkInterface.send = _dupChecker; + networkInterface.serialize.publishSetDup = _publishSetDup; + pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); + + /* Set the publish info. */ + publishInfo.QoS = 1; + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + publishInfo.pPayload = "test"; + publishInfo.payloadLength = 4; + publishInfo.retryMs = _DUP_CHECK_RETRY_MS; + publishInfo.retryLimit = _DUP_CHECK_RETRY_LIMIT; + + startTime = AwsIotClock_GetTimeMs(); + + if( TEST_PROTECT() ) + { + /* Send a PUBLISH with retransmissions enabled. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_STATUS_PENDING, + AwsIotMqtt_Publish( pMqttConnection, + &publishInfo, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishRef ) ); + + /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is + * expected. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_RETRY_NO_RESPONSE, + AwsIotMqtt_Wait( publishRef, _DUP_CHECK_TIMEOUT ) ); + + /* Check the result of the DUP check. */ + TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); + + /* Check that at least the minimum wait time elapsed. */ + TEST_ASSERT_TRUE( startTime + _DUP_CHECK_MINIMUM_WAIT <= AwsIotClock_GetTimeMs() ); + } + + /* Clean up MQTT connection. */ + AwsIotTestMqtt_destroyMqttConnection( pMqttConnection ); + + /* Check that the set DUP override was called. */ + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, _publishSetDupCalled ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_subscribe and + * @ref mqtt_function_unsubscribe with various invalid parameters. + */ +TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t mqttConnection = { 0 }; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttReference_t reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + + /* Check that subscription info is validated. */ + status = AwsIotMqtt_Subscribe( &mqttConnection, + &subscription, + 1, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + status = AwsIotMqtt_Unsubscribe( &mqttConnection, + &subscription, + 1, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + /* A reference must be provided for a waitable SUBSCRIBE. */ + status = AwsIotMqtt_Subscribe( &mqttConnection, + &subscription, + 1, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + + status = AwsIotMqtt_Unsubscribe( &mqttConnection, + &subscription, + 1, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_subscribe when memory allocation + * fails at various points. + */ +TEST( MQTT_Unit_API, SubscribeMallocFail ) +{ + int i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t mqttConnection = { 0 }; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttReference_t subscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + + /* Set the necessary members of the MQTT connection and subscription. */ + mqttConnection.network.send = _sendSuccess; + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + TEST_ASSERT_EQUAL_INT( true, + AwsIotList_Create( &( mqttConnection.subscriptionList ) ) ); + + if( TEST_PROTECT() ) + { + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call SUBSCRIBE. Memory allocation will fail at various times during + * this call. */ + status = AwsIotMqtt_Subscribe( &mqttConnection, + &subscription, + 1, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeRef ); + + /* If the SUBSCRIBE succeeded, the loop can exit after waiting for + * the SUBSCRIBE to be cleaned up. */ + if( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, AwsIotMqtt_Wait( subscribeRef, _TIMEOUT_MS ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + } + + /* No lingering subscriptions should be in the MQTT connection. */ + TEST_ASSERT_EQUAL_PTR( NULL, mqttConnection.subscriptionList.pHead ); + } + + AwsIotList_RemoveAllMatches( &( mqttConnection.subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + NULL, + NULL, + AwsIotMqtt_FreeSubscription ); + AwsIotList_Destroy( &( mqttConnection.subscriptionList ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_unsubscribe when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, UnsubscribeMallocFail ) +{ + int i = 0; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t mqttConnection = { 0 }; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttReference_t unsubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + + /* Set the necessary members of the MQTT connection and subscription. */ + mqttConnection.network.send = _sendSuccess; + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + TEST_ASSERT_EQUAL_INT( true, + AwsIotList_Create( &( mqttConnection.subscriptionList ) ) ); + + if( TEST_PROTECT() ) + { + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call UNSUBSCRIBE. Memory allocation will fail at various times during + * this call. */ + status = AwsIotMqtt_Unsubscribe( &mqttConnection, + &subscription, + 1, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeRef ); + + /* If the UNSUBSCRIBE succeeded, the loop can exit after waiting for + * the UNSUBSCRIBE to be cleaned up. */ + if( status == AWS_IOT_MQTT_STATUS_PENDING ) + { + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, AwsIotMqtt_Wait( unsubscribeRef, _TIMEOUT_MS ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + } + + /* No lingering subscriptions should be in the MQTT connection. */ + TEST_ASSERT_EQUAL_PTR( NULL, mqttConnection.subscriptionList.pHead ); + } + + AwsIotList_RemoveAllMatches( &( mqttConnection.subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + NULL, + NULL, + AwsIotMqtt_FreeSubscription ); + AwsIotList_Destroy( &( mqttConnection.subscriptionList ) ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c new file mode 100644 index 0000000000..5bc0d95b9a --- /dev/null +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -0,0 +1,1723 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt_receive.c + * @brief Tests for the function @ref mqtt_function_receivecallback. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Platform layer includes. */ +#include "platform/aws_iot_threads.h" + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* MQTT test access include. */ +#include "aws_iot_test_access_mqtt.h" + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + + /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER + #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default malloc and free functions in dynamic memory mode. + */ +#if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) + #ifndef AwsIotTest_Malloc + #include + #define AwsIotTest_Malloc malloc + #endif + #ifndef AwsIotTest_Free + #include + #define AwsIotTest_Free free + #endif +#endif /* if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) */ +/** @endcond */ + +/** @brief Default CONNACK packet for the receive tests. */ +static const uint8_t _pConnackTemplate[] = { 0x20, 0x02, 0x00, 0x00 }; +/** @brief Default PUBLISH packet for the receive tests. */ +static const uint8_t _pPublishTemplate[] = +{ + 0x30, 0x8d, 0x02, 0x00, 0x0b, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; +/** @brief Default PUBACK packet for the receive tests. */ +static const uint8_t _pPubackTemplate[] = { 0x40, 0x02, 0x00, 0x01 }; +/** @brief Default SUBACK packet for the receive tests. */ +static const uint8_t _pSubackTemplate[] = { 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02 }; +/** @brief Default UNSUBACK packet for the receive tests. */ +static const uint8_t _pUnsubackTemplate[] = { 0xb0, 0x02, 0x00, 0x01 }; +/** @brief Default PINGRESP packet for the receive tests. */ +static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; + +/*-----------------------------------------------------------*/ + +/** + * @brief Topic name and filter used in the tests. + */ +#define _TEST_TOPIC_NAME "/test/topic" + +/** + * @brief Length of #_TEST_TOPIC_NAME. + */ +#define _TEST_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( _TEST_TOPIC_NAME ) - 1 ) ) + +/** + * @brief Timeout for waiting on a PUBLISH callback. + */ +#define _PUBLISH_CALLBACK_TIMEOUT ( 1000 ) + +/** + * @brief Size of data stream in #TEST_MQTT_Unit_Receive_DataStream_. + */ +#define _DATA_STREAM_SIZE \ + ( sizeof( _pConnackTemplate ) + \ + sizeof( _pSubackTemplate ) + \ + sizeof( _pPublishTemplate ) + \ + sizeof( _pUnsubackTemplate ) + \ + sizeof( _pPingrespTemplate ) ) + +/** + * @brief Number of PUBLISH messages in the stream for #TEST_MQTT_Unit_Receive_PublishStream_ + * and #TEST_MQTT_Unit_Receive_PublishInvalidStream_. + */ +#define _PUBLISH_STREAM_COUNT ( 3 ) + +/** + * @brief Declare a buffer holding a packet and its size. + */ +#if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ + uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ + const size_t sizeName = sizeof( pTemplate ); \ + ( void ) memcpy( bufferName, pTemplate, sizeName ); \ + TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); +#else + #define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ + uint8_t * bufferName = _mallocWrapper( sizeof( pTemplate ) ); \ + TEST_ASSERT_NOT_EQUAL( NULL, bufferName ); \ + const size_t sizeName = sizeof( pTemplate ); \ + ( void ) memcpy( bufferName, pTemplate, sizeName ); +#endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @brief Initializer for operations in the tests. + */ +#define _INITIALIZE_OPERATION( name ) \ + { \ + .link = { 0 }, .operation = name, .pMqttConnection = _pMqttConnection, \ + .flags = AWS_IOT_MQTT_FLAG_WAITABLE, .packetIdentifier = 1, \ + .pMqttPacket = NULL, .packetSize = 0, .notify = { .callback = { 0 } }, \ + .status = AWS_IOT_MQTT_STATUS_PENDING, .pPublishRetry = NULL \ + } + +/*-----------------------------------------------------------*/ + +/** + * @brief The MQTT connection shared by all the tests. + */ +static _mqttConnection_t * _pMqttConnection = NULL; + +/** + * @brief Synchronizes malloc and free, which may be called from different threads. + */ +static AwsIotSemaphore_t _mallocSemaphore; + +/** + * @brief Tracks whether a deserializer override was called for a test. + */ +static bool _deserializeOverrideCalled = false; + +/** + * @brief Tracks whether #_getPacketType has been called. + */ +static bool _getPacketTypeCalled = false; + +/*-----------------------------------------------------------*/ + +/** + * @brief Wrapper for AwsIotTest_Malloc. + */ +static void * _mallocWrapper( size_t size ) +{ + void * pBuffer = NULL; + + ( void ) size; + + #if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) + pBuffer = AwsIotTest_Malloc( size ); + + /* Decrement the malloc semaphore. */ + if( pBuffer != NULL ) + #endif + { + if( ( AwsIotSemaphore_TryWait( &_mallocSemaphore ) == true ) && + ( AwsIotSemaphore_GetCount( &_mallocSemaphore ) > 0 ) ) + { + /* If the malloc semaphore value isn't what's expected, return NULL. */ + AwsIotTest_Free( pBuffer ); + pBuffer = NULL; + } + } + + return pBuffer; +} + + +/*-----------------------------------------------------------*/ + +/** + * @brief Get packet type function override. + */ +static uint8_t _getPacketType( const uint8_t * const pPacket, + size_t packetSize ) +{ + _getPacketTypeCalled = true; + + return AwsIotMqttInternal_GetPacketType( pPacket, packetSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Wrapper for AwsIotTest_Free. + */ +static void _freeWrapper( void * ptr ) +{ + /* This function should do nothing in static memory mode. */ + #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + ( void ) ptr; + #else + AwsIotTest_Free( ptr ); + #endif + + AwsIotSemaphore_Post( &_mallocSemaphore ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Deserializer override for CONNACK. + */ +static AwsIotMqttError_t _deserializeConnack( const uint8_t * const pConnackStart, + size_t dataLength, + size_t * const pBytesProcessed ) +{ + _deserializeOverrideCalled = true; + + return AwsIotMqttInternal_DeserializeConnack( pConnackStart, + dataLength, + pBytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Deserializer override for PUBLISH. + */ +static AwsIotMqttError_t _deserializePublish( const uint8_t * const pPublishStart, + size_t dataLength, + AwsIotMqttPublishInfo_t * const pOutput, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + _deserializeOverrideCalled = true; + + return AwsIotMqttInternal_DeserializePublish( pPublishStart, + dataLength, + pOutput, + pPacketIdentifier, + pBytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Deserializer override for PUBACK. + */ +static AwsIotMqttError_t _deserializePuback( const uint8_t * const pPubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + _deserializeOverrideCalled = true; + + return AwsIotMqttInternal_DeserializePuback( pPubackStart, + dataLength, + pPacketIdentifier, + pBytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Deserializer override for SUBACK. + */ +static AwsIotMqttError_t _deserializeSuback( AwsIotMqttConnection_t mqttConnection, + const uint8_t * const pSubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + _deserializeOverrideCalled = true; + + return AwsIotMqttInternal_DeserializeSuback( mqttConnection, + pSubackStart, + dataLength, + pPacketIdentifier, + pBytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Deserializer override for UNSUBACK. + */ +static AwsIotMqttError_t _deserializeUnsuback( const uint8_t * const pUnsubackStart, + size_t dataLength, + uint16_t * const pPacketIdentifier, + size_t * const pBytesProcessed ) +{ + _deserializeOverrideCalled = true; + + return AwsIotMqttInternal_DeserializeUnsuback( pUnsubackStart, + dataLength, + pPacketIdentifier, + pBytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Deserializer override for PINGRESP. + */ +static AwsIotMqttError_t _deserializePingresp( const uint8_t * const pPingrespStart, + size_t dataLength, + size_t * const pBytesProcessed ) +{ + _deserializeOverrideCalled = true; + + return AwsIotMqttInternal_DeserializePingresp( pPingrespStart, + dataLength, + pBytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Reset the status of an #_mqttOperation_t and pushing it to the receive queue. + */ +static void _operationResetAndPush( _mqttOperation_t * const pOperation ) +{ + pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotQueue_InsertHead( &_AwsIotMqttReceiveQueue, &( pOperation->link ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Process a non-PUBLISH buffer and check the result. + */ +static bool _processBuffer( const _mqttOperation_t * const pOperation, + const uint8_t * pBuffer, + size_t bufferSize, + int32_t expectedBytesProcessed, + AwsIotMqttError_t expectedResult ) +{ + /* Call the receive callback on pBuffer. */ + int32_t bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pBuffer, + 0, + bufferSize, + _freeWrapper ); + + /* Free pBuffer if the receive callback wasn't expected to free it. */ + if( expectedBytesProcessed <= 0 ) + { + _freeWrapper( ( void * ) pBuffer ); + } + + /* Check results against expected values. */ + return ( expectedBytesProcessed == bytesProcessed ) && + ( expectedResult == pOperation->status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Process a PUBLISH message and check the result. + */ +static bool _processPublish( const uint8_t * const pPublish, + size_t publishSize, + int32_t expectedBytesProcessed, + uint32_t expectedInvokeCount ) +{ + AwsIotSemaphore_t invokeCount; + uint32_t finalInvokeCount = 0, i = 0; + int32_t bytesProcessed = 0; + bool waitResult = true; + + /* Create a semaphore that counts how many times the publish callback is invoked. */ + if( AwsIotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) + { + return false; + } + + /* Set the subscription parameter. */ + if( _pMqttConnection->subscriptionList.pHead != NULL ) + { + ( ( _mqttSubscription_t * ) ( _pMqttConnection->subscriptionList.pHead ) )->callback.param1 = &invokeCount; + } + + /* Call the receive callback on pPublish. */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pPublish, + 0, + publishSize, + _freeWrapper ); + + /* Check how many times the publish callback is invoked. */ + for( i = 0; i < expectedInvokeCount; i++ ) + { + waitResult = AwsIotSemaphore_TimedWait( &invokeCount, + _PUBLISH_CALLBACK_TIMEOUT ); + + if( waitResult == false ) + { + break; + } + } + + /* Ensure that the invoke count semaphore has a value of 0, then destroy the + * semaphore. */ + finalInvokeCount = AwsIotSemaphore_GetCount( &invokeCount ); + AwsIotSemaphore_Destroy( &invokeCount ); + + /* Check results against expected values. */ + return ( finalInvokeCount == 0 ) && + ( expectedBytesProcessed == bytesProcessed ) && + ( waitResult == true ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Called when a PUBLISH message is "received". + */ +static void _publishCallback( void * param1, + AwsIotMqttCallbackParam_t * const pPublish ) +{ + AwsIotSemaphore_t * pInvokeCount = ( AwsIotSemaphore_t * ) param1; + + /* QoS 2 is valid for these tests, but currently unsupported by the MQTT library. + * Change the QoS to 0 so that QoS validation passes. */ + pPublish->message.info.QoS = 0; + + /* Check that the parameters to this function are valid. */ + if( ( AwsIotMqttInternal_ValidatePublish( _AWS_IOT_MQTT_SERVER, + &( pPublish->message.info ) ) == true ) && + ( pPublish->message.info.topicNameLength == _TEST_TOPIC_LENGTH ) && + ( strncmp( _TEST_TOPIC_NAME, pPublish->message.info.pTopicName, _TEST_TOPIC_LENGTH ) == 0 ) ) + { + AwsIotSemaphore_Post( pInvokeCount ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A PUBACK serializer function that does nothing, but always returns failure. + * + * Prevents any tests on QoS 1 PUBLISH packets from sending any PUBACKS. + */ +static AwsIotMqttError_t _serializePuback( uint16_t packetIdentifier, + uint8_t ** const pPubackPacket, + size_t * const pPacketSize ) +{ + ( void ) packetIdentifier; + ( void ) pPubackPacket; + ( void ) pPacketSize; + + return AWS_IOT_MQTT_NO_MEMORY; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT Receive tests. + */ +TEST_GROUP( MQTT_Unit_Receive ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT Receive tests. + */ +TEST_SETUP( MQTT_Unit_Receive ) +{ + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Set the deserializer overrides. */ + networkInterface.deserialize.connack = _deserializeConnack; + networkInterface.deserialize.publish = _deserializePublish; + networkInterface.deserialize.puback = _deserializePuback; + networkInterface.deserialize.suback = _deserializeSuback; + networkInterface.deserialize.unsuback = _deserializeUnsuback; + networkInterface.deserialize.pingresp = _deserializePingresp; + networkInterface.getPacketType = _getPacketType; + + /* Create the memory allocation semaphore. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &_mallocSemaphore, + 1, + 1 ) ); + + /* Initialize the MQTT library. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); + + /* Initialize the MQTT connection used by the tests. */ + _pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_EQUAL( NULL, _pMqttConnection ); + + /* Set the members of the subscription. */ + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_LENGTH; + subscription.callback.function = _publishCallback; + + /* Add the subscription to the MQTT connection. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqttInternal_AddSubscriptions( _pMqttConnection, + 1, + &subscription, + 1 ) ); + + /* Clear the deserialize override called flag. */ + _deserializeOverrideCalled = false; + _getPacketTypeCalled = false; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT Receive tests. + */ +TEST_TEAR_DOWN( MQTT_Unit_Receive ) +{ + /* Clean up resources taken in test setup. */ + AwsIotList_RemoveAllMatches( &( _pMqttConnection->subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + NULL, + NULL, + AwsIotMqtt_FreeSubscription ); + AwsIotTestMqtt_destroyMqttConnection( _pMqttConnection ); + AwsIotMqtt_Cleanup(); + AwsIotSemaphore_Destroy( &_mallocSemaphore ); + _pMqttConnection = NULL; + + /* Check that the tests used a deserializer override. */ + TEST_ASSERT_EQUAL_INT( true, _deserializeOverrideCalled ); + TEST_ASSERT_EQUAL_INT( true, _getPacketTypeCalled ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT Receive tests. + */ +TEST_GROUP_RUNNER( MQTT_Unit_Receive ) +{ + RUN_TEST_CASE( MQTT_Unit_Receive, DecodeRemainingLength ); + RUN_TEST_CASE( MQTT_Unit_Receive, InvalidPacket ); + RUN_TEST_CASE( MQTT_Unit_Receive, DataStream ); + RUN_TEST_CASE( MQTT_Unit_Receive, ConnackValid ); + RUN_TEST_CASE( MQTT_Unit_Receive, ConnackInvalid ); + RUN_TEST_CASE( MQTT_Unit_Receive, PublishValid ); + RUN_TEST_CASE( MQTT_Unit_Receive, PublishInvalid ); + RUN_TEST_CASE( MQTT_Unit_Receive, PublishStream ); + RUN_TEST_CASE( MQTT_Unit_Receive, PublishInvalidStream ); + RUN_TEST_CASE( MQTT_Unit_Receive, PubackValid ); + RUN_TEST_CASE( MQTT_Unit_Receive, PubackInvalid ); + RUN_TEST_CASE( MQTT_Unit_Receive, SubackValid ); + RUN_TEST_CASE( MQTT_Unit_Receive, SubackInvalid ); + RUN_TEST_CASE( MQTT_Unit_Receive, UnsubackValid ); + RUN_TEST_CASE( MQTT_Unit_Receive, UnsubackInvalid ); + RUN_TEST_CASE( MQTT_Unit_Receive, Pingresp ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the function #_decodeRemainingLength. + */ +TEST( MQTT_Unit_Receive, DecodeRemainingLength ) +{ + const uint8_t * pEnd = NULL; + size_t decodedLength = 0; + + /* Decode 0, which has a 1-byte representation. */ + { + uint8_t pRemainingLength[ 4 ] = { 0 }; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); + TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 1 ); + TEST_ASSERT_EQUAL( 0, decodedLength ); + } + + /* Decode 129, which has a 2-byte representation. */ + { + uint8_t pRemainingLength[ 4 ] = { 0x81, 0x01, 0x00, 0x00 }; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); + TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 2 ); + TEST_ASSERT_EQUAL( 129, decodedLength ); + } + + /* Decode 16,386, which has a 3-byte representation. */ + { + uint8_t pRemainingLength[ 4 ] = { 0x82, 0x80, 0x01, 0x00 }; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); + TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 3 ); + TEST_ASSERT_EQUAL( 16386, decodedLength ); + } + + /* Decode 268,435,455, which has a 4-byte representation. This value is the + * largest representable value. */ + { + uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x7f }; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); + TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 4 ); + TEST_ASSERT_EQUAL( 268435455, decodedLength ); + } + + /* Attempt to decode an invalid value where all continuation bits are set. */ + { + uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x8f }; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, + AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); + } + + /* Attempt to decode a 4-byte representation of 0. According to the spec, + * this representation is not valid. */ + { + uint8_t pRemainingLength[ 4 ] = { 0x80, 0x80, 0x80, 0x00 }; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, + AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); + } + + /* Test tear down for this test group checks that deserializer overrides + * were called. However, this test does not use any deserializer overrides; + * set these values to true so that the checks pass. */ + _deserializeOverrideCalled = true; + _getPacketTypeCalled = true; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with an + * invalid control packet type. + */ +TEST( MQTT_Unit_Receive, InvalidPacket ) +{ + int32_t bytesProcessed = 0; + uint8_t invalidPacket = 0xf0; + + /* Processing a control packet 0xf is a protocol violation. */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + &invalidPacket, + 0, + sizeof( uint8_t ), + NULL ); + TEST_ASSERT_EQUAL( -1, bytesProcessed ); + + /* This test should not have called any deserializer. Set the deserialize + * override called flag to true so that the check for it passes. */ + TEST_ASSERT_EQUAL_INT( false, _deserializeOverrideCalled ); + _deserializeOverrideCalled = true; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with an stream + * of data (instead of discrete packets). + */ +TEST( MQTT_Unit_Receive, DataStream ) +{ + size_t copyOffset = 0, processOffset = 0; + int32_t bytesProcessed = 0; + + /* Allocate the data stream depending on the memory allocation mode. */ + #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + uint8_t pDataStream[ _DATA_STREAM_SIZE ] = { 0 }; + TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); + #else + uint8_t * pDataStream = _mallocWrapper( _DATA_STREAM_SIZE ); + TEST_ASSERT_NOT_EQUAL( NULL, pDataStream ); + #endif + + /* Construct the data stream by placing a CONNACK, SUBACK, PUBLISH, UNSUBACK, + * and PINGRESP in it. */ + ( void ) memcpy( pDataStream, _pConnackTemplate, sizeof( _pConnackTemplate ) ); + copyOffset += sizeof( _pConnackTemplate ); + ( void ) memcpy( pDataStream + copyOffset, _pSubackTemplate, sizeof( _pSubackTemplate ) ); + copyOffset += sizeof( _pSubackTemplate ); + ( void ) memcpy( pDataStream + copyOffset, _pPublishTemplate, sizeof( _pPublishTemplate ) ); + copyOffset += sizeof( _pPublishTemplate ); + ( void ) memcpy( pDataStream + copyOffset, _pUnsubackTemplate, sizeof( _pUnsubackTemplate ) ); + copyOffset += sizeof( _pUnsubackTemplate ); + ( void ) memcpy( pDataStream + copyOffset, _pPingrespTemplate, sizeof( _pPingrespTemplate ) ); + TEST_ASSERT_EQUAL( _DATA_STREAM_SIZE, copyOffset + sizeof( _pPingrespTemplate ) ); + + /* Passing an offset greater than dataLength should not process anything. */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pDataStream, + 5, + 4, + _freeWrapper ); + TEST_ASSERT_EQUAL( 0, bytesProcessed ); + + /* The first call to process 64 bytes should only process the CONNACK and + * SUBACK. */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pDataStream, + 0, + processOffset + 64, + _freeWrapper ); + TEST_ASSERT_EQUAL( 11, bytesProcessed ); + processOffset += ( size_t ) bytesProcessed; + + /* A second call to process 64 bytes should not process anything, as the + * PUBLISH is incomplete. */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pDataStream, + processOffset, + processOffset + 64, + _freeWrapper ); + TEST_ASSERT_EQUAL( 0, bytesProcessed ); + + /* A call to process 273 bytes should process the PUBLISH packet (272 bytes). */ + TEST_ASSERT_EQUAL_INT( true, _processPublish( pDataStream + processOffset, + 273, + 272, + 1 ) ); + processOffset += 272; + + /* A call to process 5 bytes should only process the UNSUBACK (4 bytes). */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pDataStream, + processOffset, + processOffset + 5, + _freeWrapper ); + TEST_ASSERT_EQUAL( 4, bytesProcessed ); + processOffset += ( size_t ) bytesProcessed; + + /* Process the last 2 bytes (PINGRESP). */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pDataStream, + processOffset, + processOffset + 2, + _freeWrapper ); + TEST_ASSERT_EQUAL( 2, bytesProcessed ); + + /* Wait for the buffer to be freed. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + + /* Check that the entire buffer was processed. */ + TEST_ASSERT_EQUAL( _DATA_STREAM_SIZE, processOffset + ( size_t ) bytesProcessed ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a spec-compliant + * CONNACK. + */ +TEST( MQTT_Unit_Receive, ConnackValid ) +{ + uint8_t i = 0; + _mqttOperation_t connect = _INITIALIZE_OPERATION( AWS_IOT_MQTT_CONNECT ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( connect.notify.waitSemaphore ), + 0, + 10 ) ); + + /* Even though no CONNECT is in the receive queue, 4 bytes should still be + * processed (should not crash). */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + ( int32_t ) connackSize, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Process a valid, successful CONNACK with no SP flag. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + ( int32_t ) connackSize, + AWS_IOT_MQTT_SUCCESS ) ); + } + + /* Process a valid, successful CONNACK with SP flag set. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + pConnack[ 2 ] = 0x01; + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + ( int32_t ) connackSize, + AWS_IOT_MQTT_SUCCESS ) ); + } + + /* Check each of the CONNACK failure codes, which range from 1 to 5. */ + for( i = 0x01; i < 0x06; i++ ) + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + + /* Set the CONNECT state and code. */ + _operationResetAndPush( &connect ); + pConnack[ 3 ] = i; + + /* Push a CONNECT operation to the queue and process a CONNACK. */ + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + ( int32_t ) connackSize, + AWS_IOT_MQTT_SERVER_REFUSED ) ); + } + + AwsIotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a + * CONNACK that doesn't comply to MQTT spec. + */ +TEST( MQTT_Unit_Receive, ConnackInvalid ) +{ + _mqttOperation_t connect = _INITIALIZE_OPERATION( AWS_IOT_MQTT_CONNECT ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( connect.notify.waitSemaphore ), + 0, + 10 ) ); + + /* An incomplete CONNACK should not be processed, and no status should be set. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize - 1, + 0, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* The CONNACK control packet type must be 0x20. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + pConnack[ 0 ] = 0x21; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* A CONNACK must have a remaining length of 2. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + pConnack[ 1 ] = 0x03; + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* The reserved bits in CONNACK must be 0. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + pConnack[ 2 ] = 0x80; + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* The fourth byte of CONNACK must be 0 if the SP flag is set. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + pConnack[ 2 ] = 0x01; + pConnack[ 3 ] = 0x01; + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* CONNACK return codes cannot be above 5. */ + { + _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + pConnack[ 3 ] = 0x06; + _operationResetAndPush( &connect ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, + pConnack, + connackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + AwsIotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a spec-compliant + * PUBLISH. + */ +TEST( MQTT_Unit_Receive, PublishValid ) +{ + /* Set the PUBACK serializer function. This serializer function always returns + * failure to prevent any PUBACK packets from actually being sent. */ + _pMqttConnection->network.serialize.puback = _serializePuback; + + /* Process a valid QoS 0 PUBLISH. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + ( int32_t ) publishSize, + 1 ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + } + + /* Process a valid QoS 1 PUBLISH. Prevent an attempt to send PUBACK by + * making no memory available for the PUBACK. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 0 ] = 0x32; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + ( int32_t ) publishSize, + 1 ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + } + + /* Process a valid QoS 2 PUBLISH. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 0 ] = 0x34; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + ( int32_t ) publishSize, + 1 ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + } + + /* Process a valid PUBLISH with DUP flag set. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 0 ] = 0x38; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + ( int32_t ) publishSize, + 1 ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + } + + /* Remove the subscription. Even though there is no matching subscription, + * all bytes of the PUBLISH should still be processed (should not crash). */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + AwsIotMqtt_FreeSubscription( _pMqttConnection->subscriptionList.pHead ); + _pMqttConnection->subscriptionList.pHead = NULL; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + ( int32_t ) publishSize, + 0 ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a PUBLIS + * that doesn't comply to MQTT spec. + */ +TEST( MQTT_Unit_Receive, PublishInvalid ) +{ + /* Attempting to process a packet smaller than 9 bytes should result in no + * bytes processed. 9 bytes is the minimum size of a PUBLISH. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + 8, + 0, + 0 ) ); + _freeWrapper( pPublish ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } + + /* The PUBLISH cannot have a QoS of 3. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 0 ] = 0x36; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + -1, + 0 ) ); + _freeWrapper( pPublish ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } + + /* Attempt to process a PUBLISH with an invalid "Remaining length". */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 1 ] = 0xff; + pPublish[ 2 ] = 0xff; + pPublish[ 3 ] = 0xff; + pPublish[ 4 ] = 0xff; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + -1, + 0 ) ); + _freeWrapper( pPublish ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } + + /* Attempt to process a PUBLISH larger than the size of the data stream. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 2 ] = 0x52; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + 0, + 0 ) ); + _freeWrapper( pPublish ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } + + /* Attempt to process a PUBLISH with a "Remaining length" smaller than the + * spec allows. */ + { + /* QoS 0. */ + _DECLARE_PACKET( _pPublishTemplate, pPublish0, publish0Size ); + pPublish0[ 1 ] = 0x02; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish0, + publish0Size, + -1, + 0 ) ); + _freeWrapper( pPublish0 ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* QoS 1. */ + _DECLARE_PACKET( _pPublishTemplate, pPublish1, publish1Size ); + pPublish1[ 0 ] = 0x32; + pPublish1[ 1 ] = 0x04; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish1, + publish1Size, + -1, + 0 ) ); + _freeWrapper( pPublish1 ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } + + /* Attempt to process a PUBLISH with a "Remaining length" smaller than the + * end of the variable header. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 1 ] = 0x0a; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + -1, + 0 ) ); + _freeWrapper( pPublish ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } + + /* Attempt to process a PUBLISH with packet identifier 0. */ + { + _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + pPublish[ 0 ] = 0x32; + pPublish[ 17 ] = 0x00; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, + publishSize, + -1, + 0 ) ); + _freeWrapper( pPublish ); + TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a stream + * of incoming PUBLISH messages. + */ +TEST( MQTT_Unit_Receive, PublishStream ) +{ + size_t i = 0; + + /* Allocate the data stream depending on the memory allocation mode. */ + #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + uint8_t pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ] = { 0 }; + TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); + #else + uint8_t * pPublishStream = _mallocWrapper( _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ); + TEST_ASSERT_NOT_EQUAL( NULL, pPublishStream ); + #endif + + /* Fill the data stream with PUBLISH messages. */ + for( i = 0; i < _PUBLISH_STREAM_COUNT; i++ ) + { + ( void ) memcpy( pPublishStream + i * sizeof( _pPublishTemplate ), + _pPublishTemplate, + sizeof( _pPublishTemplate ) ); + } + + /* Process a stream that contains one complete and one partial PUBLISH message. */ + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, + sizeof( _pPublishTemplate ) + 1, + sizeof( _pPublishTemplate ), + 1 ) ); + TEST_ASSERT_EQUAL_INT( false, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + + /* Process the complete stream of PUBLISH messages. */ + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, + _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ), + _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ), + _PUBLISH_STREAM_COUNT ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a stream + * of incoming PUBLISH messages that is invalid. + */ +TEST( MQTT_Unit_Receive, PublishInvalidStream ) +{ + size_t i = 0; + + /* Allocate the data stream depending on the memory allocation mode. */ + #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + uint8_t pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1 ] = { 0 }; + TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); + #else + uint8_t * pPublishStream = _mallocWrapper( _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1 ); + TEST_ASSERT_NOT_EQUAL( NULL, pPublishStream ); + #endif + + /* Fill the data stream with PUBLISH messages. */ + for( i = 0; i < _PUBLISH_STREAM_COUNT; i++ ) + { + ( void ) memcpy( pPublishStream + i * sizeof( _pPublishTemplate ), + _pPublishTemplate, + sizeof( _pPublishTemplate ) ); + } + + /* Place an invalid byte at the end of the stream. */ + pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ] = 0xff; + + /* Process the stream of PUBLISH messages. */ + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, + _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1, + -1, + 0 ) ); + TEST_ASSERT_EQUAL_INT( false, AwsIotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); + _freeWrapper( pPublishStream ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a + * spec-compliant PUBACK. + */ +TEST( MQTT_Unit_Receive, PubackValid ) +{ + _mqttOperation_t publish = _INITIALIZE_OPERATION( AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( publish.notify.waitSemaphore ), + 0, + 10 ) ); + + /* Even though no PUBLISH is in the receive queue, 4 bytes should still be + * processed (should not crash). */ + { + _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, + pPuback, + pubackSize, + ( int32_t ) pubackSize, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Process a valid PUBACK. */ + { + _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + _operationResetAndPush( &publish ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, + pPuback, + pubackSize, + ( int32_t ) pubackSize, + AWS_IOT_MQTT_SUCCESS ) ); + } + + AwsIotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a PUBACK + * that doesn't comply to MQTT spec. + */ +TEST( MQTT_Unit_Receive, PubackInvalid ) +{ + _mqttOperation_t publish = _INITIALIZE_OPERATION( AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( publish.notify.waitSemaphore ), + 0, + 10 ) ); + + /* An incomplete PUBACK should not be processed, and no status should be set. */ + { + _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + _operationResetAndPush( &publish ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, + pPuback, + pubackSize - 1, + 0, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* The PUBACK control packet type must be 0x40. */ + { + _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + pPuback[ 0 ] = 0x41; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, + pPuback, + pubackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* A PUBACK must have a remaining length of 2. */ + { + _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + pPuback[ 1 ] = 0x03; + _operationResetAndPush( &publish ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, + pPuback, + pubackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* The packet identifier in PUBACK cannot be 0. No status should be set if + * packet identifier 0 is received. */ + { + _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + pPuback[ 3 ] = 0x00; + _operationResetAndPush( &publish ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, + pPuback, + pubackSize, + -1, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + AwsIotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a + * spec-compliant SUBACK. + */ +TEST( MQTT_Unit_Receive, SubackValid ) +{ + _mqttSubscription_t * pNewSubscription = NULL; + _mqttOperation_t subscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_SUBSCRIBE ); + AwsIotMqttSubscription_t pSubscriptions[ 2 ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( subscribe.notify.waitSemaphore ), + 0, + 10 ) ); + + /* Add 2 additional subscriptions to the MQTT connection. */ + pSubscriptions[ 0 ].QoS = 1; + pSubscriptions[ 0 ].callback.function = _publishCallback; + pSubscriptions[ 0 ].pTopicFilter = _TEST_TOPIC_NAME "1"; + pSubscriptions[ 0 ].topicFilterLength = _TEST_TOPIC_LENGTH + 1; + + pSubscriptions[ 1 ].QoS = 1; + pSubscriptions[ 1 ].callback.function = _publishCallback; + pSubscriptions[ 1 ].pTopicFilter = _TEST_TOPIC_NAME "2"; + pSubscriptions[ 1 ].topicFilterLength = _TEST_TOPIC_LENGTH + 1; + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqttInternal_AddSubscriptions( _pMqttConnection, + 1, + pSubscriptions, + 2 ) ); + + /* Set orders 2 and 1 for the new subscriptions. */ + pNewSubscription = AwsIotLink_Container( _pMqttConnection->subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET ); + pNewSubscription->packetInfo.order = 2; + + pNewSubscription = AwsIotLink_Container( pNewSubscription->link.pNext, + _SUBSCRIPTION_LINK_OFFSET ); + pNewSubscription->packetInfo.order = 1; + + /* Even though no SUBSCRIBE is in the receive queue, all bytes of the SUBACK + * should still be processed (should not crash). */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + ( int32_t ) subackSize, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Process a valid SUBACK where all subscriptions are successful. */ + { + AwsIotMqttSubscription_t currentSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + _operationResetAndPush( &subscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + ( int32_t ) subackSize, + AWS_IOT_MQTT_SUCCESS ) ); + + /* Test the subscription check function. QoS is not tested. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotMqtt_IsSubscribed( _pMqttConnection, + pSubscriptions[ 0 ].pTopicFilter, + pSubscriptions[ 0 ].topicFilterLength, + ¤tSubscription ) ); + currentSubscription.QoS = pSubscriptions[ 0 ].QoS; + TEST_ASSERT_EQUAL_MEMORY( &pSubscriptions[ 0 ], + ¤tSubscription, + sizeof( AwsIotMqttSubscription_t ) ); + } + + /* Process a valid SUBACK where some subscriptions were rejected. */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + pSuback[ 4 ] = 0x80; + pSuback[ 6 ] = 0x80; + _operationResetAndPush( &subscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + ( int32_t ) subackSize, + AWS_IOT_MQTT_SERVER_REFUSED ) ); + + /* Check that rejected subscriptions were removed from the subscription + * list. */ + TEST_ASSERT_EQUAL_INT( false, AwsIotMqtt_IsSubscribed( _pMqttConnection, + _TEST_TOPIC_NAME, + _TEST_TOPIC_LENGTH, + NULL ) ); + TEST_ASSERT_EQUAL_INT( false, AwsIotMqtt_IsSubscribed( _pMqttConnection, + pSubscriptions[ 1 ].pTopicFilter, + pSubscriptions[ 1 ].topicFilterLength, + NULL ) ); + } + + AwsIotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a + * SUBACK that doesn't comply to MQTT spec. + */ +TEST( MQTT_Unit_Receive, SubackInvalid ) +{ + _mqttOperation_t subscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_SUBSCRIBE ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( subscribe.notify.waitSemaphore ), + 0, + 10 ) ); + + /* Attempting to process a packet smaller than 5 bytes should result in no + * bytes processed. 5 bytes is the minimum size of a SUBACK. */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + _operationResetAndPush( &subscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + 4, + 0, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Attempt to process a SUBACK with an invalid "Remaining length". */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + pSuback[ 1 ] = 0xff; + pSuback[ 2 ] = 0xff; + pSuback[ 3 ] = 0xff; + pSuback[ 4 ] = 0xff; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + -1, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Attempt to process a SUBACK larger than the size of the data stream. */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + pSuback[ 1 ] = 0x52; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + 0, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Attempt to process a SUBACK with a "Remaining length" smaller than the + * spec allows. */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + pSuback[ 1 ] = 0x02; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + -1, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Attempt to process a SUBACK with a bad return code. */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + pSuback[ 6 ] = 0xff; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* The SUBACK control packet type must be 0x90. */ + { + _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + pSuback[ 0 ] = 0x91; + _operationResetAndPush( &subscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, + pSuback, + subackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + AwsIotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a + * spec-compliant UNSUBACK. + */ +TEST( MQTT_Unit_Receive, UnsubackValid ) +{ + _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_UNSUBSCRIBE ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), + 0, + 10 ) ); + + /* Even though no UNSUBSCRIBE is in the receive queue, 4 bytes should still be + * processed (should not crash). */ + { + _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, + pUnsuback, + unsubackSize, + ( int32_t ) unsubackSize, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Process a valid UNSUBACK. */ + { + _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + _operationResetAndPush( &unsubscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, + pUnsuback, + unsubackSize, + ( int32_t ) unsubackSize, + AWS_IOT_MQTT_SUCCESS ) ); + } + + AwsIotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with an + * UNSUBACK that doesn't comply to MQTT spec. + */ +TEST( MQTT_Unit_Receive, UnsubackInvalid ) +{ + _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_UNSUBSCRIBE ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), + 0, + 10 ) ); + + /* An incomplete UNSUBACK should not be processed, and no status should be set. */ + { + _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + _operationResetAndPush( &unsubscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, + pUnsuback, + unsubackSize - 1, + 0, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* The UNSUBACK control packet type must be 0xb0. */ + { + _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + pUnsuback[ 0 ] = 0xb1; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, + pUnsuback, + unsubackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* An UNSUBACK must have a remaining length of 2. */ + { + _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + pUnsuback[ 1 ] = 0x03; + _operationResetAndPush( &unsubscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, + pUnsuback, + unsubackSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* The packet identifier in UNSUBACK cannot be 0. No status should be set if + * packet identifier 0 is received. */ + { + _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + pUnsuback[ 3 ] = 0x00; + _operationResetAndPush( &unsubscribe ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, + pUnsuback, + unsubackSize, + -1, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + AwsIotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback when receiving + * a PINGRESP packet (both compliant and non-compliant packets). + */ +TEST( MQTT_Unit_Receive, Pingresp ) +{ + _mqttOperation_t pingreq = _INITIALIZE_OPERATION( AWS_IOT_MQTT_PINGREQ ); + + /* Create the wait semaphore so notifications don't crash. The value of + * this semaphore will not be checked, so the maxValue argument is arbitrary. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( pingreq.notify.waitSemaphore ), + 0, + 10 ) ); + + /* Even though no PINGREQ is in the receive queue, 2 bytes should still be + * processed (should not crash). */ + { + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + pPingresp, + pingrespSize, + ( int32_t ) pingrespSize, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* Process a valid PINGRESP. */ + { + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + _operationResetAndPush( &pingreq ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + pPingresp, + pingrespSize, + ( int32_t ) pingrespSize, + AWS_IOT_MQTT_SUCCESS ) ); + } + + /* An incomplete PINGRESP should not be processed, and no status should be set. */ + { + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + _operationResetAndPush( &pingreq ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + pPingresp, + pingrespSize - 1, + 0, + AWS_IOT_MQTT_STATUS_PENDING ) ); + } + + /* A PINGRESP should have a remaining length of 0. */ + { + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + pPingresp[ 1 ] = 0x01; + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + pPingresp, + pingrespSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + /* The PINGRESP control packet type must be 0xd0. */ + { + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + pPingresp[ 0 ] = 0xd1; + _operationResetAndPush( &pingreq ); + TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + pPingresp, + pingrespSize, + -1, + AWS_IOT_MQTT_BAD_RESPONSE ) ); + } + + AwsIotSemaphore_Destroy( &( pingreq.notify.waitSemaphore ) ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c new file mode 100644 index 0000000000..6ae00c2e63 --- /dev/null +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c @@ -0,0 +1,907 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt_subscription.c + * @brief Tests for the functions in aws_iot_mqtt_subscription.c + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* POSIX includes. */ +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* MQTT test access include. */ +#include "aws_iot_test_access_mqtt.h" + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int snprintf( char *, size_t, const char *, ... ); +/** @endcond */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + + /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER + #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/* + * Constants relating to the test subscription list. + */ +#define _LIST_ITEM_COUNT ( 10 ) /**< @brief Number of subscriptions. */ +#define _TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ +#define _TEST_TOPIC_FILTER_LENGTH ( sizeof( _TEST_TOPIC_FILTER_FORMAT ) + 1 ) /**< @brief Maximum length of each topic filter. */ + +/* + * Constants relating to the multithreaded subscription test. + */ +#define _MT_THREAD_COUNT ( 8 ) /**< @brief Number of threads. */ +#define _MT_TOPIC_FILTER_FORMAT ( "/%p-%lu" ) /**< @brief Format of each topic filter. */ +#define _MT_TOPIC_FILTER_LENGTH ( 16 ) /**< @brief Maximum length of each topic filter. */ + +/** + * @brief A non-NULL function pointer to use for subscription callback. This + * "function" should cause a crash if actually called. + */ +#define _CALLBACK_FUNCTION \ + ( ( void ( * )( void *, \ + AwsIotMqttCallbackParam_t * const ) ) 0x1 ) + +/** + * @brief All test topic filters in the tests #TEST_MQTT_Unit_Subscription_TopicFilterMatchTrue_ + * and #TEST_MQTT_Unit_Subscription_TopicFilterMatchFalse_ should be shorter than this + * length. + */ +#define _TOPIC_FILTER_MATCH_MAX_LENGTH ( 32 ) + +/** + * @brief Macro to check a single topic name against a topic filter. + * + * @param[in] topicNameString The topic name to check. + * @param[in] topicFilterString The topic filter to check. + * @param[in] exactMatch Whether an exact match is required. If this is false, + * wildcard matching is allowed. + * @param[in] expectedResult Whether the topic name and filter are expected to match. + * + * @note This macro may only be used when a #_mqttSubscription_t pointer named pTopicFilter + * is in scope. + */ +#define _TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ + { \ + _topicMatchParams_t _topicMatchParams = { 0 }; \ + _topicMatchParams.pTopicName = topicNameString; \ + _topicMatchParams.topicNameLength = ( uint16_t ) strlen( _topicMatchParams.pTopicName ); \ + _topicMatchParams.exactMatchOnly = exactMatch; \ + \ + pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ + _TOPIC_FILTER_MATCH_MAX_LENGTH, \ + topicFilterString ); \ + \ + TEST_ASSERT_EQUAL_INT( expectedResult, \ + AwsIotTestMqtt_topicMatch( &_topicMatchParams, pTopicFilter ) ); \ + } + +/*-----------------------------------------------------------*/ + +/** + * @brief The MQTT connection shared by all tests. + */ +static _mqttConnection_t _connection = { 0 }; + +/** + * @brief Synchronizes threads in the multithreaded test. + */ +static pthread_barrier_t _mtTestBarrier; + +/*-----------------------------------------------------------*/ + +/** + * @brief Places dummy subscriptions in the subscription list of #_connection. + */ +static void _populateList( void ) +{ + size_t i = 0; + _mqttSubscription_t * pSubscription = NULL; + + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + pSubscription = AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); + TEST_ASSERT_NOT_NULL( pSubscription ); + + ( void ) memset( pSubscription, 0x00, sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); + pSubscription->packetInfo.identifier = 1; + pSubscription->packetInfo.order = i; + pSubscription->callback.function = _CALLBACK_FUNCTION; + pSubscription->topicFilterLength = ( uint16_t ) snprintf( pSubscription->pTopicFilter, + _TEST_TOPIC_FILTER_LENGTH, + _TEST_TOPIC_FILTER_FORMAT, + ( unsigned long ) i ); + + AwsIotList_InsertHead( &( _connection.subscriptionList ), + &( pSubscription->link), + _SUBSCRIPTION_LINK_OFFSET ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A subscription callback function that only reports whether it was invoked. + */ +static void _publishCallback( void * pArgument, + AwsIotMqttCallbackParam_t * const pPublish ) +{ + uint16_t i = 0; + bool * pCallbackInvoked = ( bool * ) pArgument; + + /* Notify the caller that this callback was invoked. */ + *pCallbackInvoked = true; + + /* If the topic filter doesn't match the topic name, ensure that the topic + * filter contains a wildcard. */ + if( pPublish->message.topicFilterLength != pPublish->message.info.topicNameLength ) + { + for( i = 0; i < pPublish->message.topicFilterLength; i++ ) + { + if( ( pPublish->message.pTopicFilter[ i ] == '+' ) || + ( pPublish->message.pTopicFilter[ i ] == '#' ) ) + { + break; + } + } + + TEST_ASSERT_LESS_THAN_UINT16( pPublish->message.topicFilterLength, i ); + } + + /* Ensure that the MQTT connection was set correctly. */ + TEST_ASSERT_EQUAL_PTR( pPublish->mqttConnection, &_connection ); + + /* Ensure that publish info is valid. */ + TEST_ASSERT_EQUAL_INT( true, + AwsIotMqttInternal_ValidatePublish( _AWS_IOT_MQTT_SERVER, + &( pPublish->message.info ) ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Thread routing of the multithreaded test. + */ +static void * _multithreadTestThread( void * pArgument ) +{ + size_t i = 0; + int barrierResult = 0; + bool * pThreadResult = ( bool * ) pArgument; + char pTopicFilters[ _LIST_ITEM_COUNT ][ _MT_TOPIC_FILTER_LENGTH ] = { { 0 } }; + AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + /* Synchronize with the other threads before starting the test. */ + barrierResult = pthread_barrier_wait( &_mtTestBarrier ); + + if( ( barrierResult != 0 ) && ( barrierResult != PTHREAD_BARRIER_SERIAL_THREAD ) ) + { + *pThreadResult = false; + + return NULL; + } + + /* Add items to the subscription list. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + subscription[ i ].callback.function = _CALLBACK_FUNCTION; + subscription[ i ].pTopicFilter = pTopicFilters[ i ]; + subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], + _MT_TOPIC_FILTER_LENGTH, + _MT_TOPIC_FILTER_FORMAT, + &i, + ( unsigned long ) i ); + + if( AwsIotMqttInternal_AddSubscriptions( &_connection, + 1, + &( subscription[ i ] ), + 1 ) != AWS_IOT_MQTT_SUCCESS ) + { + *pThreadResult = false; + + return NULL; + } + } + + /* Remove the previously added items from the list. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, + &( subscription[ i ] ), + 1 ); + } + + *pThreadResult = true; + + return NULL; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT subscription tests. + */ +TEST_GROUP( MQTT_Unit_Subscription ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT API tests. + */ +TEST_SETUP( MQTT_Unit_Subscription ) +{ + TEST_ASSERT_EQUAL_INT( true, + AwsIotList_Create( &( _connection.subscriptionList ) ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT API tests. + */ +TEST_TEAR_DOWN( MQTT_Unit_Subscription ) +{ + AwsIotList_RemoveAllMatches( &( _connection.subscriptionList ), + _SUBSCRIPTION_LINK_OFFSET, + NULL, + NULL, + AwsIotMqtt_FreeSubscription ); + AwsIotList_Destroy( &( _connection.subscriptionList ) ); + ( void ) memset( &_connection, 0x00, sizeof( _mqttConnection_t ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT API tests. + */ +TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) +{ + RUN_TEST_CASE( MQTT_Unit_Subscription, ListInsertRemove ); + RUN_TEST_CASE( MQTT_Unit_Subscription, ListFindByTopicFilter ); + RUN_TEST_CASE( MQTT_Unit_Subscription, ListFindByPacket ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddDuplicate ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddMallocFail ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionMultithreaded ); + RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublish ); + RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublishMultiple ); + RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchTrue ); + RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchFalse ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests simple insertion and removal of elements from the subscription list. + */ +TEST( MQTT_Unit_Subscription, ListInsertRemove ) +{ + _mqttSubscription_t node1 = { 0 }; + _mqttSubscription_t node2 = { 0 }; + _mqttSubscription_t node3 = { 0 }; + + AwsIotList_InsertHead( &( _connection.subscriptionList ), + &( node1.link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotList_InsertHead( &( _connection.subscriptionList ), + &( node2.link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotList_InsertHead( &( _connection.subscriptionList ), + &( node3.link ), + _SUBSCRIPTION_LINK_OFFSET ); + + AwsIotList_Remove( &( _connection.subscriptionList ), + &( node1.link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotList_Remove( &( _connection.subscriptionList ), + &( node2.link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotList_Remove( &( _connection.subscriptionList ), + &( node3.link ), + _SUBSCRIPTION_LINK_OFFSET ); + + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests searching the subscription list using a topic filter. + */ +TEST( MQTT_Unit_Subscription, ListFindByTopicFilter ) +{ + _mqttSubscription_t * pSubscription = NULL; + _topicMatchParams_t topicMatchParams = { 0 }; + + topicMatchParams.pTopicName = "/test0"; + topicMatchParams.topicNameLength = 6; + + /* On empty list. */ + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + AwsIotTestMqtt_topicMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + + _populateList(); + + /* Topic filter present. */ + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + AwsIotTestMqtt_topicMatch ); + TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); + + /* Topic filter not present. */ + topicMatchParams.pTopicName = "/notpresent"; + topicMatchParams.topicNameLength = 11; + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + AwsIotTestMqtt_topicMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests searching the subscription list using a packet identifier. + */ +TEST( MQTT_Unit_Subscription, ListFindByPacket ) +{ + _mqttSubscription_t * pSubscription = 0; + _packetMatchParams_t packetMatchParams = { 0 }; + + packetMatchParams.packetIdentifier = 1; + packetMatchParams.order = 0; + + /* On empty list. */ + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &packetMatchParams, + AwsIotTestMqtt_packetMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + + _populateList(); + + /* Packet and order present. */ + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &packetMatchParams, + AwsIotTestMqtt_packetMatch ); + TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); + + /* Packet present, order not present. */ + packetMatchParams.order = _LIST_ITEM_COUNT; + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &packetMatchParams, + AwsIotTestMqtt_packetMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + + /* Packet not present, order present. */ + packetMatchParams.packetIdentifier = 0; + packetMatchParams.order = 0; + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &packetMatchParams, + AwsIotTestMqtt_packetMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + + /* Packet and order not present. */ + packetMatchParams.packetIdentifier = 0; + packetMatchParams.order = _LIST_ITEM_COUNT; + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &packetMatchParams, + AwsIotTestMqtt_packetMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests removing subscriptions by packet identifier. + */ +TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) +{ + long i = 0; + + /* On empty list (should not crash). */ + AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, + 1, + 0 ); + + _populateList(); + + /* Remove all subscriptions by packet one-by-one. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, + 1, + i ); + } + + /* List should be empty. */ + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + + /* Remove all subscriptions for a packet one-shot. */ + _populateList(); + AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, + 1, + -1 ); + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests removing subscriptions by a topic filter. + */ +TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) +{ + size_t i = 0; + char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; + AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + /* On empty list (should not crash). */ + subscription[ 0 ].pTopicFilter = "/topic"; + subscription[ 0 ].topicFilterLength = 6; + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, + &( subscription[ 0 ] ), + 1 ); + + _populateList(); + subscription[ 0 ].pTopicFilter = pTopicFilters[ 0 ]; + + /* Removal one-by-one. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + subscription[ 0 ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ 0 ], + _TEST_TOPIC_FILTER_LENGTH, + _TEST_TOPIC_FILTER_FORMAT, + ( unsigned long ) i ); + + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, + &( subscription[ 0 ] ), + 1 ); + } + + /* List should be empty. */ + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + + /* Refill the list. */ + _populateList(); + TEST_ASSERT_NOT_EQUAL( NULL, _connection.subscriptionList.pHead ); + + /* Removal all at once. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + subscription[ i ].pTopicFilter = pTopicFilters[ i ]; + subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], + _TEST_TOPIC_FILTER_LENGTH, + _TEST_TOPIC_FILTER_FORMAT, + ( unsigned long ) i ); + } + + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, + subscription, + _LIST_ITEM_COUNT ); + + /* List should be empty. */ + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests adding duplicate subscriptions. + */ +TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) +{ + size_t i = 0; + _mqttSubscription_t * pSubscription = NULL; + _topicMatchParams_t topicMatchParams = { 0 }; + char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + /* Set valid values in the subscription list. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + subscription[ i ].callback.function = _CALLBACK_FUNCTION; + subscription[ i ].pTopicFilter = pTopicFilters[ i ]; + subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], + _TEST_TOPIC_FILTER_LENGTH, + _TEST_TOPIC_FILTER_FORMAT, + ( unsigned long ) i ); + } + + /* Add all subscriptions to the list. */ + status = AwsIotMqttInternal_AddSubscriptions( &_connection, + 1, + subscription, + _LIST_ITEM_COUNT ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Change the callback information, but not the topic filter. */ + subscription[ 1 ].callback.function = _publishCallback; + subscription[ 1 ].callback.param1 = &_connection; + + /* Add the duplicate subscription. */ + status = AwsIotMqttInternal_AddSubscriptions( &_connection, + 3, + &( subscription[ 1 ] ), + 1 ); + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + + /* Find the subscription that was just modified. */ + topicMatchParams.pTopicName = "/test1"; + topicMatchParams.topicNameLength = 6; + topicMatchParams.exactMatchOnly = true; + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + AwsIotTestMqtt_topicMatch ); + TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); + + /* Check that the information was changed. */ + TEST_ASSERT_EQUAL_UINT16( 3, pSubscription->packetInfo.identifier ); + TEST_ASSERT_EQUAL( 0, pSubscription->packetInfo.order ); + TEST_ASSERT_EQUAL_PTR( _publishCallback, pSubscription->callback.function ); + TEST_ASSERT_EQUAL_PTR( &_connection, pSubscription->callback.param1 ); + + /* Check that a duplicate entry wasn't created. */ + AwsIotList_Remove( &( _connection.subscriptionList ), + &( pSubscription->link ), + _SUBSCRIPTION_LINK_OFFSET ); + AwsIotMqtt_FreeSubscription( pSubscription ); + pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, + _SUBSCRIPTION_LINK_OFFSET, + &topicMatchParams, + AwsIotTestMqtt_topicMatch ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests adding subscriptions when memory allocation fails at various points. + */ +TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) +{ + size_t i = 0; + char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + /* Set valid values in the subscription list. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + subscription[ i ].callback.function = _CALLBACK_FUNCTION; + subscription[ i ].pTopicFilter = pTopicFilters[ i ]; + subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], + _TEST_TOPIC_FILTER_LENGTH, + _TEST_TOPIC_FILTER_FORMAT, + ( unsigned long ) i ); + } + + /* Set malloc to fail at various points. */ + for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( ( int ) i ); + + status = AwsIotMqttInternal_AddSubscriptions( &_connection, + 1, + subscription, + _LIST_ITEM_COUNT ); + + if( status == AWS_IOT_MQTT_SUCCESS ) + { + break; + } + + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests adding and removing subscriptions in a multithreaded environment. + */ +TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) +{ + int i = 0, threadsCreated = 0, barrierReturn = 0; + volatile int threadsJoined = 0; + pthread_t testThreads[ _MT_THREAD_COUNT ] = { 0 }; + bool threadResults[ _MT_THREAD_COUNT ] = { 0 }; + + /* Create the synchronization barrier. */ + TEST_ASSERT_EQUAL_INT( 0, pthread_barrier_init( &_mtTestBarrier, + NULL, + _MT_THREAD_COUNT + 1 ) ); + + /* Spawn threads for the test. */ + for( i = 0; i < _MT_THREAD_COUNT; i++ ) + { + if( pthread_create( &( testThreads[ i ] ), + NULL, + _multithreadTestThread, + &( threadResults[ i ] ) ) != 0 ) + { + break; + } + } + + /* Record how many threads were created. */ + threadsCreated = i; + + /* Synchronize with the test threads. */ + barrierReturn = pthread_barrier_wait( &_mtTestBarrier ); + + /* Wait for all created threads to finish. */ + for( i = 0; i < threadsCreated; i++ ) + { + if( pthread_join( testThreads[ i ], NULL ) == 0 ) + { + threadsJoined++; + } + } + + if( TEST_PROTECT() ) + { + /* Check the results of barrier wait and thread create/join. */ + TEST_ASSERT_TRUE( barrierReturn == 0 || barrierReturn == PTHREAD_BARRIER_SERIAL_THREAD ); + TEST_ASSERT_EQUAL_INT( threadsCreated, threadsJoined ); + TEST_ASSERT_EQUAL_INT( threadsCreated, _MT_THREAD_COUNT ); + + /* Check the results of the test threads. */ + for( i = 0; i < _MT_THREAD_COUNT; i++ ) + { + TEST_ASSERT_EQUAL_INT( true, threadResults[ i ] ); + } + + /* The subscription list should be empty. */ + TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + } + + /* Destroy the synchronization barrier. */ + if( pthread_barrier_destroy( &_mtTestBarrier ) != 0 ) + { + TEST_FAIL_MESSAGE( "Failed to destroy barrier" ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests invoking subscription callbacks with PUBLISH messages. + */ +TEST( MQTT_Unit_Subscription, ProcessPublish ) +{ + bool callbackInvoked = false; + AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + + /* Set the subscription and corresponding publish info. */ + subscription.pTopicFilter = "/test"; + subscription.topicFilterLength = 5; + subscription.callback.function = _publishCallback; + subscription.callback.param1 = &callbackInvoked; + + callbackParam.message.info.pTopicName = "/test"; + callbackParam.message.info.topicNameLength = 5; + callbackParam.message.info.pPayload = ""; + callbackParam.message.info.payloadLength = 0; + + /* Add the subscription. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotMqttInternal_AddSubscriptions( &_connection, + 1, + &subscription, + 1 ) ); + + /* Find the subscription and invoke its callback. */ + AwsIotMqttInternal_ProcessPublish( &_connection, + &callbackParam ); + + /* Check that the callback was invoked. */ + TEST_ASSERT_EQUAL_INT( true, callbackInvoked ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that all matching subscription callbacks are invoked for a + * PUBLISH. + */ +TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) +{ + bool callbackInvoked[ 3 ] = { false }; + AwsIotMqttSubscription_t subscription[ 3 ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + AwsIotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + + /* Set the subscription info. */ + subscription[ 0 ].pTopicFilter = "/test"; + subscription[ 0 ].topicFilterLength = 5; + subscription[ 0 ].callback.function = _publishCallback; + subscription[ 0 ].callback.param1 = &( callbackInvoked[ 0 ] ); + + subscription[ 1 ].pTopicFilter = "/+"; + subscription[ 1 ].topicFilterLength = 2; + subscription[ 1 ].callback.function = _publishCallback; + subscription[ 1 ].callback.param1 = &( callbackInvoked[ 1 ] ); + + subscription[ 2 ].pTopicFilter = "/#"; + subscription[ 2 ].topicFilterLength = 2; + subscription[ 2 ].callback.function = _publishCallback; + subscription[ 2 ].callback.param1 = &( callbackInvoked[ 2 ] ); + + /* Create a PUBLISH that matches all 3 subscriptions. */ + callbackParam.message.info.pTopicName = "/test"; + callbackParam.message.info.topicNameLength = 5; + callbackParam.message.info.pPayload = ""; + callbackParam.message.info.payloadLength = 0; + + /* Add the subscriptions. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, + AwsIotMqttInternal_AddSubscriptions( &_connection, + 1, + &( subscription[ 0 ] ), + 3 ) ); + + /* Invoke subscription callbacks. */ + AwsIotMqttInternal_ProcessPublish( &_connection, + &callbackParam ); + + /* Check that all 3 callbacks were invoked. */ + TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 0 ] ); + TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 1 ] ); + TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 2 ] ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests result of matching topic filters and topic names. + */ +TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) +{ + _mqttSubscription_t * pTopicFilter = + AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); + + TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); + + if( TEST_PROTECT() ) + { + /* Exact matching. */ + _TEST_TOPIC_MATCH( "/exact", "/exact", true, true ); + _TEST_TOPIC_MATCH( "/exact", "/exact", false, true ); + + /* Topic level wildcard matching. */ + _TEST_TOPIC_MATCH( "/aws", "/+", false, true ); + _TEST_TOPIC_MATCH( "/aws/iot", "/aws/+", false, true ); + _TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/shadow", false, true ); + _TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/+", false, true ); + _TEST_TOPIC_MATCH( "aws/", "aws/+", false, true ); + _TEST_TOPIC_MATCH( "/aws", "+/+", false, true ); + _TEST_TOPIC_MATCH( "aws//iot", "aws/+/iot", false, true ); + _TEST_TOPIC_MATCH( "aws//iot", "aws//+", false, true ); + _TEST_TOPIC_MATCH( "aws///iot", "aws/+/+/iot", false, true ); + + /* Multi level wildcard matching. */ + _TEST_TOPIC_MATCH( "/aws/iot/shadow", "#", false, true ); + _TEST_TOPIC_MATCH( "aws/iot/shadow", "#", false, true ); + _TEST_TOPIC_MATCH( "/aws/iot/shadow", "/#", false, true ); + _TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/iot/#", false, true ); + _TEST_TOPIC_MATCH( "aws/iot/shadow/thing", "aws/iot/#", false, true ); + _TEST_TOPIC_MATCH( "aws", "aws/#", false, true ); + + /* Both topic level and multi level wildcard. */ + _TEST_TOPIC_MATCH( "aws/iot/shadow/thing/temp", "aws/+/shadow/#", false, true ); + } + + AwsIotMqtt_FreeSubscription( pTopicFilter ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests result of matching topic filters and topic names that don't + * match. + */ +TEST( MQTT_Unit_Subscription, TopicFilterMatchFalse ) +{ + _mqttSubscription_t * pTopicFilter = + AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); + + TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); + + if( TEST_PROTECT() ) + { + /* Topic filter longer than filter name. */ + _TEST_TOPIC_MATCH( "/short", "/toolong", true, false ); + _TEST_TOPIC_MATCH( "/short", "/toolong", false, false ); + + /* Case mismatch. */ + _TEST_TOPIC_MATCH( "/exact", "/eXaCt", true, false ); + _TEST_TOPIC_MATCH( "/exact", "/ExAcT", false, false ); + + /* Substrings should not match. */ + _TEST_TOPIC_MATCH( "aws/", "aws/iot", true, false ); + _TEST_TOPIC_MATCH( "aws/", "aws/iot", false, false ); + + /* Topic level wildcard matching. */ + _TEST_TOPIC_MATCH( "aws", "aws/", false, false ); + _TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+", false, false ); + _TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+/thing", false, false ); + _TEST_TOPIC_MATCH( "/aws", "+", false, false ); + + /* Multi level wildcard matching. */ + _TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/#", false, false ); + _TEST_TOPIC_MATCH( "aws/iot", "/#", false, false ); + + /* Both topic level and multi level wildcard. */ + _TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/+/#", false, false ); + } + + AwsIotMqtt_FreeSubscription( pTopicFilter ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c b/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c new file mode 100644 index 0000000000..a275f3b692 --- /dev/null +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_mqtt_validate.c + * @brief Tests for the functions in aws_iot_mqtt_validate.c + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Length of the subscription array used in + * #TEST_MQTT_Unit_Validate_ValidateSubscriptionList_. + */ +#define _SUBSCRIPTION_COUNT ( _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + +/** + * @brief A non-NULL function pointer. + */ +#define _FUNCTION_POINTER ( ( void * ) 0x1 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT validate tests. + */ +TEST_GROUP( MQTT_Unit_Validate ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT validate tests. + */ +TEST_SETUP( MQTT_Unit_Validate ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT validate tests. + */ +TEST_TEAR_DOWN( MQTT_Unit_Validate ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT validate tests. + */ +TEST_GROUP_RUNNER( MQTT_Unit_Validate ) +{ + RUN_TEST_CASE( MQTT_Unit_Validate, ValidateNetIf ); + RUN_TEST_CASE( MQTT_Unit_Validate, ValidateConnectInfo ); + RUN_TEST_CASE( MQTT_Unit_Validate, ValidatePublish ); + RUN_TEST_CASE( MQTT_Unit_Validate, ValidateReference ); + RUN_TEST_CASE( MQTT_Unit_Validate, ValidateSubscriptionList ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test validation of an #AwsIotMqttNetIf_t. + */ +TEST( MQTT_Unit_Validate, ValidateNetIf ) +{ + bool validateStatus = false; + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + + /* NULL parameter. */ + validateStatus = AwsIotMqttInternal_ValidateNetIf( NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Uninitialized parameter. */ + validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Uninitialized disconnect function is allowed. */ + networkInterface.send = _FUNCTION_POINTER; + validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* Check serializer override function pointers. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + /* No freePacket function with serializer. */ + networkInterface.serialize.disconnect = _FUNCTION_POINTER; + validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + networkInterface.serialize.disconnect = NULL; + + /* No freePacket function with deserializer. */ + networkInterface.deserialize.pingresp = _FUNCTION_POINTER; + validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* freePacket function pointer set. */ + networkInterface.freePacket = _FUNCTION_POINTER; + validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + #endif /* if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test validation of an #AwsIotMqttConnectInfo_t. + */ +TEST( MQTT_Unit_Validate, ValidateConnectInfo ) +{ + bool validateStatus = false; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* NULL parameter. */ + validateStatus = AwsIotMqttInternal_ValidateConnect( NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Uninitialized parameter. */ + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Zero-length client identifier with clean session. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = ""; + connectInfo.clientIdentifierLength = 0; + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Client identifier longer than 23 characters. */ + connectInfo.pClientIdentifier = "longlongclientidentifier"; + connectInfo.clientIdentifierLength = 24; + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* AWS IoT MQTT service limit tests. */ + #if AWS_IOT_MQTT_MODE == 1 + /* Client identifier too long. */ + connectInfo.clientIdentifierLength = _AWS_IOT_MQTT_SERVER_MAX_CLIENTID + 1; + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + connectInfo.clientIdentifierLength = 24; + + /* Keep-alive disabled. */ + connectInfo.keepAliveSeconds = 0; + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* Keep-alive too small. */ + connectInfo.keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE - 1; + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* Keep-alive too large. */ + connectInfo.keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE + 1; + validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + #endif /* if AWS_IOT_MQTT_MODE == 1 */ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test validation of an #AwsIotMqttPublishInfo_t. + */ +TEST( MQTT_Unit_Validate, ValidatePublish ) +{ + bool validateStatus = false; + AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* NULL parameter. */ + validateStatus = AwsIotMqttInternal_ValidatePublish( false, NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Zero-length topic name. */ + publishInfo.pTopicName = ""; + publishInfo.topicNameLength = 0; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + publishInfo.pTopicName = "/test"; + publishInfo.topicNameLength = 5; + + /* Zero-length/NULL payload. */ + publishInfo.pPayload = NULL; + publishInfo.payloadLength = 0; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* NULL payload only allowed with length 0. */ + publishInfo.pPayload = NULL; + publishInfo.payloadLength = 1; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.payloadLength = 0; + + /* Negative QoS or QoS > 2. */ + publishInfo.QoS = -1; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.QoS = 3; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.QoS = 0; + + /* Negative retry limit. */ + publishInfo.retryLimit = -1; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Positive retry limit with no period. */ + publishInfo.retryLimit = 1; + publishInfo.retryMs = 0; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Positive retry limit with positive period. */ + publishInfo.retryLimit = 1; + publishInfo.retryMs = 1; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* Retry limit 0. */ + publishInfo.retryLimit = 0; + validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* AWS IoT MQTT service limit tests. */ + + /* QoS 2. */ + publishInfo.QoS = 2; + validateStatus = AwsIotMqttInternal_ValidatePublish( true, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.QoS = 0; + + /* Retained message. */ + publishInfo.retain = true; + validateStatus = AwsIotMqttInternal_ValidatePublish( true, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.retain = false; + + /* Topic name too long. */ + publishInfo.topicNameLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; + validateStatus = AwsIotMqttInternal_ValidatePublish( true, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test validation of an #AwsIotMqttReference_t. + */ +TEST( MQTT_Unit_Validate, ValidateReference ) +{ + bool validateStatus = false; + _mqttOperation_t reference = { 0 }; + + /* NULL parameter. */ + validateStatus = AwsIotMqttInternal_ValidateReference( NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Non-waitable reference. */ + reference.flags = 0; + validateStatus = AwsIotMqttInternal_ValidateReference( &reference ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Waitable (valid) reference. */ + reference.flags = AWS_IOT_MQTT_FLAG_WAITABLE; + validateStatus = AwsIotMqttInternal_ValidateReference( &reference ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test validation of a list of #AwsIotMqttSubscription_t. + */ +TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) +{ + size_t i = 0; + bool validateStatus = false; + AwsIotMqttSubscription_t pSubscriptions[ _SUBSCRIPTION_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + /* NULL parameter. */ + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, NULL, 1 ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Zero parameter. */ + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, 0 ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Uninitialized subscriptions. */ + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Initialize all subscriptions to valid values. */ + for( i = 0; i < _SUBSCRIPTION_COUNT; i++ ) + { + pSubscriptions[ i ].pTopicFilter = "/test"; + pSubscriptions[ i ].topicFilterLength = 5; + pSubscriptions[ i ].callback.function = _FUNCTION_POINTER; + } + + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* One subscription with invalid QoS. */ + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].QoS = -1; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].QoS = 3; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* QoS is not validated for UNSUBSCRIBE. */ + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].QoS = 0; + + /* One subscription with no callback. */ + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].callback.function = NULL; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* Callback is not validated for UNSUBSCRIBE. */ + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].callback.function = _FUNCTION_POINTER; + + /* Valid subscription filters. */ + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 1; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/#"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+/"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/+/+/+"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 7; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* Invalid subscription filters. */ + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/#"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a#"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a+"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+a"; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* AWS IoT MQTT service limit tests. */ + + /* Too many subscriptions. */ + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, + true, + pSubscriptions, + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE + 1 ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* QoS 2. */ + pSubscriptions[ 0 ].QoS = 2; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, + true, + pSubscriptions, + _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + pSubscriptions[ 0 ].QoS = 0; + + /* Topic filter too long. */ + pSubscriptions[ 0 ].topicFilterLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; + validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, + true, + pSubscriptions, + _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt new file mode 100644 index 0000000000..2d81da8c49 --- /dev/null +++ b/tests/shadow/CMakeLists.txt @@ -0,0 +1,10 @@ +# Shadow tests executable. +add_executable( aws_iot_tests_shadow + aws_iot_tests_shadow.c + ${CMAKE_SOURCE_DIR}/tests/aws_iot_tests_network.c + unit/aws_iot_tests_shadow_api.c + unit/aws_iot_tests_shadow_parser.c + system/aws_iot_tests_shadow_system.c ) + +# Shadow tests library dependencies. +target_link_libraries( aws_iot_tests_shadow awsiotcommon awsiotplatform awsiotmqtt awsiotshadow unity ) diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c new file mode 100644 index 0000000000..204f571b00 --- /dev/null +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_shadow.c + * @brief Test runner for the Shadow tests on POSIX systems. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler. Terminates the tests if called. + */ +static void _signalHandler( int signum ) +{ + /* Immediately terminate the tests if this signal handler is called. */ + if( signum == SIGSEGV ) + { + printf( "\nSegmentation fault.\n" ); + exit( EXIT_FAILURE ); + } + else if( signum == SIGABRT ) + { + printf( "\nAssertion failed.\n" ); + exit( EXIT_FAILURE ); + } +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + struct sigaction signalAction; + + /* Set a signal handler for segmentation faults and assertion failures. */ + ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); + signalAction.sa_handler = _signalHandler; + + if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) + { + return -1; + } + + if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + { + return -1; + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Run tests that do not require the network. */ + RUN_TEST_GROUP( Shadow_Unit_Parser ); + RUN_TEST_GROUP( Shadow_Unit_API ); + + /* Disable the Shadow tests that require the network if the -n command line + * option is set. */ + if( getopt( argc, argv, "n" ) == -1 ) + { + RUN_TEST_GROUP( Shadow_System ); + } + + /* The stress tests may take several minutes to run. Only run them if the + * -l command line argument was given. */ + if( getopt( argc, argv, "l" ) != -1 ) + { + } + + /* Return the number of test failures. This will cause a non-zero exit code + * if any test fails. */ + return UNITY_END(); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c new file mode 100644 index 0000000000..1f0b0121ae --- /dev/null +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_shadow_system.c + * @brief Full system tests for the AWS IoT Shadow library. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/* JSON utilities include. */ +#include "aws_iot_json_utils.h" + +/* Platform layer includes. */ +#include "platform/aws_iot_clock.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* Require Shadow library asserts to be enabled for these tests. The Shadow + * assert function is used to abort the tests on failure from the Shadow operation + * complete callback. */ +#if AWS_IOT_SHADOW_ENABLE_ASSERTS == 0 + #error "Shadow API unit tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." +#endif + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int snprintf( char *, + size_t, + const char *, + ... ); +/** @endcond */ + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef AWS_IOT_TEST_MQTT_TOPIC_PREFIX + #define AWS_IOT_TEST_MQTT_TOPIC_PREFIX "awsiotmqtttest" +#endif +#ifndef AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S + #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) +#endif +#ifndef AWS_IOT_TEST_SHADOW_TIMEOUT + #define AWS_IOT_TEST_SHADOW_TIMEOUT ( 5000 ) +#endif +/** @endcond */ + +/* Thing Name must be defined for these tests. */ +#ifndef AWS_IOT_TEST_SHADOW_THING_NAME + #error "Please define AWS_IOT_TEST_SHADOW_THING_NAME." +#endif + +/** + * @brief The maximum length of an MQTT client identifier. + */ +#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) + +/** + * @brief The length of @ref AWS_IOT_TEST_SHADOW_THING_NAME. + */ +#define _THING_NAME_LENGTH ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ) + +/** + * @brief The Shadow document used for these tests. + */ +#define _TEST_SHADOW_DOCUMENT "{\"state\":{\"reported\":{\"key\":\"value\"}},\"clientToken\":\"shadowtest\"}" + +/** + * @brief The length of #_TEST_SHADOW_DOCUMENT. + */ +#define _TEST_SHADOW_DOCUMENT_LENGTH ( sizeof( _TEST_SHADOW_DOCUMENT ) - 1 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Parameter 1 of #_operationComplete. + */ +typedef struct _operationCompleteParams +{ + AwsIotShadowCallbackType_t expectedType; /**< @brief Expected callback type. */ + AwsIotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ + AwsIotShadowReference_t reference; /**< @brief Reference to expected completed operation. */ +} _operationCompleteParams_t; + +/*-----------------------------------------------------------*/ + +/* Network functions used by the tests, declared and implemented in one of + * the test network function files. */ +extern bool AwsIotTest_NetworkSetup( void ); +extern void AwsIotTest_NetworkCleanup( void ); +extern bool AwsIotTest_NetworkConnect( void ** const pNewConnection, + AwsIotMqttConnection_t * pMqttConnection ); +extern void AwsIotTest_NetworkClose( void * pDisconnectContext ); +extern void AwsIotTest_NetworkDestroy( void * pConnection ); + +/* Network variables used by the tests, declared in one of the test network + * function files. */ +extern AwsIotMqttNetIf_t _AwsIotTestNetworkInterface; +extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; + +/*-----------------------------------------------------------*/ + +/** + * @brief Buffer holding the MQTT client identifier used for the tests. + */ +static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + +/*-----------------------------------------------------------*/ + +/** + * @brief Shadow operation completion callback function. Checks parameters + * and unblocks the main test thread. + */ +static void _operationComplete( void * pArgument, + AwsIotShadowCallbackParam_t * const pOperation ) +{ + _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + /* Check parameters against received operation information. */ + AwsIotShadow_Assert( pOperation->callbackType == pParams->expectedType ); + AwsIotShadow_Assert( pOperation->operation.result == AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadow_Assert( pOperation->operation.reference == pParams->reference ); + AwsIotShadow_Assert( pOperation->thingNameLength == _THING_NAME_LENGTH ); + AwsIotShadow_Assert( strncmp( pOperation->pThingName, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH ) == 0 ); + + /* Check the retrieved Shadow document. */ + if( pOperation->callbackType == AWS_IOT_SHADOW_GET_COMPLETE ) + { + AwsIotShadow_Assert( pOperation->operation.get.documentLength > 0 ); + + AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pOperation->operation.get.pDocument, + pOperation->operation.get.documentLength, + "key", + 3, + &pJsonValue, + &jsonValueLength ) == true ); + AwsIotShadow_Assert( jsonValueLength == 7 ); + AwsIotShadow_Assert( strncmp( pJsonValue, "\"value\"", 7 ) == 0 ); + } + + /* Unblock the main test thread. */ + AwsIotSemaphore_Post( &( pParams->waitSem ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Shadow delta callback. Checks parameters and unblocks the main test + * thread. + */ +static void _deltaCallback( void * pArgument, + AwsIotShadowCallbackParam_t * const pCallback ) +{ + AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + const char * pValue = NULL, * pClientToken = NULL; + size_t valueLength = 0, clientTokenLength = 0; + + /* Check callback type. */ + AwsIotShadow_Assert( pCallback->callbackType == AWS_IOT_SHADOW_DELTA_CALLBACK ); + + /* Check delta document state. */ + AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "key", + 3, + &pValue, + &valueLength ) == true ); + AwsIotShadow_Assert( valueLength == 4 ); + AwsIotShadow_Assert( strncmp( pValue, "true", valueLength ) == 0 ); + + /* Check delta document client token. */ + AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "clientToken", + 11, + &pClientToken, + &clientTokenLength ) ); + AwsIotShadow_Assert( clientTokenLength == 12 ); + AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); + + /* Unblock the main test thread. */ + AwsIotSemaphore_Post( pWaitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Shadow updated callback. Checks parameters and unblocks the main test + * thread. + */ +static void _updatedCallback( void * pArgument, + AwsIotShadowCallbackParam_t * const pCallback ) +{ + AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + const char * pPrevious = NULL, * pCurrent = NULL, * pClientToken = NULL; + size_t previousStateLength = 0, currentStateLength = 0, clientTokenLength = 0; + + /* Check updated document previous state. */ + AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "previous", + 8, + &pPrevious, + &previousStateLength ) == true ); + AwsIotShadow_Assert( previousStateLength > 0 ); + AwsIotShadow_Assert( strncmp( "{\"state\":{},\"metadata\":{},", + pPrevious, + 26 ) == 0 ); + + /* Check updated document current state. */ + AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "current", + 7, + &pCurrent, + ¤tStateLength ) == true ); + AwsIotShadow_Assert( currentStateLength > 0 ); + AwsIotShadow_Assert( strncmp( "{\"state\":{\"desired\":{\"key\":true}}", + pCurrent, + 33 ) == 0 ); + + /* Check updated document client token. */ + AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "clientToken", + 11, + &pClientToken, + &clientTokenLength ) ); + AwsIotShadow_Assert( clientTokenLength == 12 ); + AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); + + /* Unblock the main test thread. */ + AwsIotSemaphore_Post( pWaitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Run the Update-Get-Delete asynchronous tests at various QoS. + */ +static void _updateGetDeleteAsync( int QoS ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + _operationCompleteParams_t callbackParam = { 0 }; + + /* Initialize the members of the operation callback info. */ + callbackInfo.param1 = &callbackParam; + callbackInfo.function = _operationComplete; + + /* Initialize the common members of the Shadow document info. */ + documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; + documentInfo.thingNameLength = _THING_NAME_LENGTH; + documentInfo.QoS = QoS; + + /* Create the wait semaphore for operations. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the expected callback type to UPDATE. */ + callbackParam.expectedType = AWS_IOT_SHADOW_UPDATE_COMPLETE; + + /* Set the members of the Shadow document info for UPDATE. */ + documentInfo.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; + documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; + + /* Create a new Shadow document. */ + status = AwsIotShadow_Update( _AwsIotTestMqttConnection, + &documentInfo, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting to update Shadow document." ); + } + + /* Set the expected callback type to GET. */ + callbackParam.expectedType = AWS_IOT_SHADOW_GET_COMPLETE; + + /* Retrieve the Shadow document. */ + status = AwsIotShadow_Get( _AwsIotTestMqttConnection, + &documentInfo, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_STATUS_PENDING, status ); + + if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting to retrieve Shadow document." ); + } + + /* Set the expected callback type to DELETE. */ + callbackParam.expectedType = AWS_IOT_SHADOW_DELETE_COMPLETE; + + /* Delete the Shadow document. */ + status = AwsIotShadow_Delete( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting to delete Shadow document." ); + } + } + + AwsIotSemaphore_Destroy( &( callbackParam.waitSem ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Run the Update-Get-Delete blocking tests at various QoS. + */ +static void _updateGetDeleteBlocking( int QoS ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + const char * pShadowDocument = NULL, * pJsonValue = NULL; + size_t shadowDocumentLength = 0, jsonValueLength = 0; + + /* Initialize the common members of the Shadow document info. */ + documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; + documentInfo.thingNameLength = _THING_NAME_LENGTH; + documentInfo.QoS = QoS; + + /* Set the members of the Shadow document info for UPDATE. */ + documentInfo.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; + documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; + + /* Create a new Shadow document. */ + status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + &documentInfo, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Set the members of the Shadow document info for GET. */ + documentInfo.get.mallocDocument = AwsIotTest_Malloc; + + /* Retrieve the Shadow document. */ + status = AwsIotShadow_TimedGet( _AwsIotTestMqttConnection, + &documentInfo, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT, + &pShadowDocument, + &shadowDocumentLength ); + TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Check the retrieved Shadow document. */ + TEST_ASSERT_GREATER_THAN( 0, shadowDocumentLength ); + TEST_ASSERT_NOT_NULL( pShadowDocument ); + TEST_ASSERT_EQUAL_INT( true, AwsIotJsonUtils_FindJsonValue( pShadowDocument, + shadowDocumentLength, + "key", + 3, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 7, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "\"value\"", pJsonValue, jsonValueLength ); + + /* Free the retrieved Shadow document. */ + AwsIotTest_Free( ( void * ) pShadowDocument ); + + /* Delete the Shadow document. */ + status = AwsIotShadow_TimedDelete( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Shadow system tests. + */ +TEST_GROUP( Shadow_System ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Shadow system tests. + */ +TEST_SETUP( Shadow_System ) +{ + int clientIdentifierLength = 0; + AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + + /* Set up the network stack. */ + if( AwsIotTest_NetworkSetup() == false ) + { + TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + } + + /* Initialize the MQTT library. */ + if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } + + /* Generate a new, unique client identifier based on the time. */ + clientIdentifierLength = snprintf( _pClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "aws%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + + if( clientIdentifierLength <= 0 ) + { + TEST_FAIL_MESSAGE( "Failed to generate MQTT client identifier." ); + } + + /* Set the members of the connect info. */ + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) clientIdentifierLength; + connectInfo.keepAliveSeconds = AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + + /* Establish an MQTT connection. */ + if( AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + NULL, + AWS_IOT_TEST_SHADOW_TIMEOUT ) != AWS_IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); + } + + /* Initialize the Shadow library. */ + if( AwsIotShadow_Init( 0 ) != AWS_IOT_SHADOW_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize Shadow library." ); + } + + /* Delete any existing Shadow so all tests start with no Shadow. */ + status = AwsIotShadow_TimedDelete( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + + /* Acceptable statuses are SUCCESS and NOT FOUND. Both of these statuses allow + * the tests to start with no Shadow. */ + if( ( status != AWS_IOT_SHADOW_SUCCESS ) && ( status != AWS_IOT_SHADOW_NOT_FOUND ) ) + { + TEST_FAIL_MESSAGE( "Failed to delete shadow in test set up." ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Shadow system tests. + */ +TEST_TEAR_DOWN( Shadow_System ) +{ + /* Disconnect the MQTT connection. */ + AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + + /* Clean up the Shadow library. */ + AwsIotShadow_Cleanup(); + + /* Clean up the network stack. */ + AwsIotTest_NetworkCleanup(); + + /* Clean up the MQTT library. */ + AwsIotMqtt_Cleanup(); + _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Shadow system tests. + */ +TEST_GROUP_RUNNER( Shadow_System ) +{ + RUN_TEST_CASE( Shadow_System, UpdateGetDeleteAsyncQoS0 ); + RUN_TEST_CASE( Shadow_System, UpdateGetDeleteAsyncQoS1 ); + RUN_TEST_CASE( Shadow_System, UpdateGetDeleteBlockingQoS0 ); + RUN_TEST_CASE( Shadow_System, UpdateGetDeleteBlockingQoS1 ); + RUN_TEST_CASE( Shadow_System, DeltaCallback ); + RUN_TEST_CASE( Shadow_System, UpdatedCallback ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Update-Get-Delete asynchronous (QoS 0). + */ +TEST( Shadow_System, UpdateGetDeleteAsyncQoS0 ) +{ + _updateGetDeleteAsync( 0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Update-Get-Delete asynchronous (QoS 1). + */ +TEST( Shadow_System, UpdateGetDeleteAsyncQoS1 ) +{ + _updateGetDeleteAsync( 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Update-Get-Delete blocking (QoS 0). + */ +TEST( Shadow_System, UpdateGetDeleteBlockingQoS0 ) +{ + _updateGetDeleteBlocking( 0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Update-Get-Delete blocking (QoS 1). + */ +TEST( Shadow_System, UpdateGetDeleteBlockingQoS1 ) +{ + _updateGetDeleteBlocking( 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the Shadow delta callback. + */ +TEST( Shadow_System, DeltaCallback ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotSemaphore_t waitSem; + + /* Create a semaphore to wait on. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + + /* Set the delta callback information. */ + deltaCallback.param1 = &waitSem; + deltaCallback.function = _deltaCallback; + + /* Set a desired state in the Update document. */ + updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; + updateDocument.thingNameLength = _THING_NAME_LENGTH; + updateDocument.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; + updateDocument.update.updateDocumentLength = 65; + + if( TEST_PROTECT() ) + { + /* Set the delta callback. */ + status = AwsIotShadow_SetDeltaCallback( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + &deltaCallback ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Create a Shadow document with a desired state. */ + status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + &updateDocument, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Set a different reported state in the Update document. */ + updateDocument.update.pUpdateDocument = "{\"state\": {\"reported\": {\"key\": false}}, \"clientToken\":\"shadowtest\"}"; + updateDocument.update.updateDocumentLength = 67; + + /* Create a Shadow document with a reported state. */ + status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + &updateDocument, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Block on the wait semaphore until the delta callback is invoked. */ + if( AwsIotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for delta callback." ); + } + + /* Remove the delta callback. */ + status = AwsIotShadow_SetDeltaCallback( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Remove persistent subscriptions for Shadow Update. */ + status = AwsIotShadow_RemovePersistentSubscriptions( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + } + + AwsIotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ + +TEST( Shadow_System, UpdatedCallback ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowCallbackInfo_t updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotSemaphore_t waitSem; + + /* Create a semaphore to wait on. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + + /* Set the delta callback information. */ + updatedCallback.param1 = &waitSem; + updatedCallback.function = _updatedCallback; + + /* Set a desired state in the Update document. */ + updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; + updateDocument.thingNameLength = _THING_NAME_LENGTH; + updateDocument.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; + updateDocument.update.updateDocumentLength = 65; + + if( TEST_PROTECT() ) + { + /* Set the updated callback. */ + status = AwsIotShadow_SetUpdatedCallback( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + &updatedCallback ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Create a Shadow document. */ + status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + &updateDocument, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + + /* Block on the wait semaphore until the updated callback is invoked. */ + if( AwsIotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for updated callback." ); + } + + /* Remove the updated callback. */ + status = AwsIotShadow_SetUpdatedCallback( _AwsIotTestMqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + _THING_NAME_LENGTH, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); + } + + AwsIotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c new file mode 100644 index 0000000000..9190e57f62 --- /dev/null +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_shadow_api.c + * @brief Tests for the user-facing API functions (declared in aws_iot_shadwo.h). + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* POSIX includes. */ +#ifdef POSIX_UNISTD_HEADER + #include POSIX_UNISTD_HEADER +#else + #include +#endif + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/* Undefine logging configuration set in Shadow internal header. */ +#undef _LIBRARY_LOG_NAME +#undef _LIBRARY_LOG_LEVEL + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Undefine logging configuration set in MQTT internal header. */ +#undef _LIBRARY_LOG_NAME +#undef _LIBRARY_LOG_LEVEL + +/* Platform layer includes. */ +#include "platform/aws_iot_clock.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* MQTT test access include. */ +#include "aws_iot_test_access_mqtt.h" + +/* Require Shadow library asserts to be enabled for these tests. The Shadow + * assert function is used to abort the tests on failure from the MQTT send + * or receive threads. */ +#if AWS_IOT_SHADOW_ENABLE_ASSERTS == 0 + #error "Shadow API unit tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief The Thing Name shared among all the tests. + */ +#define _TEST_THING_NAME "TestThingName" + +/** + * @brief The length of #_TEST_THING_NAME. + */ +#define _TEST_THING_NAME_LENGTH ( sizeof( _TEST_THING_NAME ) - 1 ) + +/** + * @brief A delay that simulates the time required for an MQTT packet to be sent + * to the server and for the server to send a response. + */ +#define _NETWORK_ROUND_TRIP_TIME_MS ( 250 ) + +/** + * @brief The maximum size of any MQTT acknowledgement packet (e.g. SUBACK, + * PUBACK, UNSUBACK) used in these tests. + */ +#define _ACKNOWLEDGEMENT_PACKET_SIZE ( 5 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief The MQTT connection object shared among all the tests. + */ +static _mqttConnection_t * _pMqttConnection = NULL; + +/** + * @brief Timer used to simulate a response from the network. + */ +static AwsIotTimer_t _receiveTimer; + +/** + * @brief Synchronizes the MQTT send and receive threads in these tests. + */ +static AwsIotMutex_t _lastPacketMutex; + +/** + * @brief The type of the last packet sent by the send thread. + * + * Must be one of: PUBLISH, SUBSCRIBE, UNSUBSCRIBE. + */ +static uint8_t _lastPacketType = 0; + +/** + * @brief The packet identifier of the last packet send by the send thread. + */ +static uint16_t _lastPacketIdentifier = 0; + +/*-----------------------------------------------------------*/ + +/** + * @brief Invokes the MQTT receive callback to simulate a response received from + * the network. + */ +static void _receiveThread( void * pArgument ) +{ + uint8_t pReceivedData[ _ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; + size_t receivedDataLength = 0; + int32_t bytesProcessed = 0; + + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + /* Lock mutex to read and process the last packet sent. */ + AwsIotMutex_Lock( &_lastPacketMutex ); + + /* Ensure that the last packet type and identifier were set. */ + AwsIotShadow_Assert( _lastPacketType != 0 ); + AwsIotShadow_Assert( _lastPacketIdentifier != 0 ); + + /* Set the packet identifier in the ACK packet. */ + pReceivedData[ 2 ] = _UINT16_HIGH_BYTE( _lastPacketIdentifier ); + pReceivedData[ 3 ] = _UINT16_LOW_BYTE( _lastPacketIdentifier ); + + /* Create the corresponding ACK packet based on the last packet type. */ + switch( _lastPacketType ) + { + case _MQTT_PACKET_TYPE_PUBLISH: + + pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; + pReceivedData[ 1 ] = 2; + receivedDataLength = 4; + break; + + case _MQTT_PACKET_TYPE_SUBSCRIBE: + + pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_SUBACK; + pReceivedData[ 1 ] = 3; + pReceivedData[ 4 ] = 1; + receivedDataLength = 5; + break; + + case _MQTT_PACKET_TYPE_UNSUBSCRIBE: + + pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_UNSUBACK; + pReceivedData[ 1 ] = 2; + receivedDataLength = 4; + break; + + default: + + /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and + * UNSUBSCRIBE. Abort if any other packet is found. */ + AwsIotShadow_Assert( 0 ); + } + + /* Call the MQTT receive callback to process the ACK packet. */ + bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + pReceivedData, + 0, + receivedDataLength, + NULL ); + AwsIotShadow_Assert( bytesProcessed == ( int32_t ) receivedDataLength ); + + AwsIotMutex_Unlock( &_lastPacketMutex ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A send function that always "succeeds". It also sets the receive + * timer to respond with an ACK when necessary. + */ +static size_t _sendSuccess( void * pSendContext, + const void * const pMessage, + size_t messageLength ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + const uint8_t * const pPacket = ( const uint8_t * const ) pMessage; + const uint8_t * pPacketIdentifier = NULL; + AwsIotMqttPublishInfo_t decodedPublish = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + size_t publishBytesProcessed = 0; + + /* Ignore the send context. */ + ( void ) pSendContext; + + /* Lock the mutex to modify the information on the last packet sent. */ + AwsIotMutex_Lock( &_lastPacketMutex ); + + /* Set the last packet type based on the outgoing message. */ + switch( AwsIotMqttInternal_GetPacketType( pMessage, messageLength ) ) + { + case ( _MQTT_PACKET_TYPE_PUBLISH & 0xf0 ): + + /* Only set the last packet type to PUBLISH for QoS 1. */ + if( ( ( *pPacket & 0x06 ) >> 1 ) == 1 ) + { + _lastPacketType = _MQTT_PACKET_TYPE_PUBLISH; + } + else + { + _lastPacketType = 0; + _lastPacketIdentifier = 0; + } + + break; + + case ( _MQTT_PACKET_TYPE_SUBSCRIBE & 0xf0 ): + _lastPacketType = _MQTT_PACKET_TYPE_SUBSCRIBE; + break; + + case ( _MQTT_PACKET_TYPE_UNSUBSCRIBE & 0xf0 ): + _lastPacketType = _MQTT_PACKET_TYPE_UNSUBSCRIBE; + break; + + default: + + /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and + * UNSUBSCRIBE. Abort if any other packet is found. */ + AwsIotShadow_Assert( 0 ); + } + + /* Check if a network response is needed. */ + if( _lastPacketType != 0 ) + { + /* Decode the remaining length. */ + if( _lastPacketType != _MQTT_PACKET_TYPE_PUBLISH ) + { + status = AwsIotTestMqtt_decodeRemainingLength( pPacket + 1, + &pPacketIdentifier, + NULL ); + + /* Save the packet identifier as the last packet identifier. */ + _lastPacketIdentifier = _UINT16_DECODE( pPacketIdentifier ); + } + else + { + status = AwsIotMqttInternal_DeserializePublish( pMessage, + messageLength, + &decodedPublish, + &_lastPacketIdentifier, + &publishBytesProcessed ); + + AwsIotShadow_Assert( publishBytesProcessed == messageLength ); + } + + AwsIotShadow_Assert( status == AWS_IOT_MQTT_SUCCESS ); + + /* Set the receive thread to run after a "network round-trip". */ + AwsIotClock_TimerArm( &_receiveTimer, + _NETWORK_ROUND_TRIP_TIME_MS, + 0 ); + } + + AwsIotMutex_Unlock( &_lastPacketMutex ); + + /* Return the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Shadow API tests. + */ +TEST_GROUP( Shadow_Unit_API ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Shadow API tests. + */ +TEST_SETUP( Shadow_Unit_API ) +{ + AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + + /* Clear the last packet type and identifier. */ + _lastPacketType = 0; + _lastPacketIdentifier = 0; + + /* Create the mutex that synchronizes the receive callback and send thread. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &_lastPacketMutex ) ); + + /* Create the receive thread timer. */ + AwsIotClock_TimerCreate( &_receiveTimer, + _receiveThread, + NULL ); + + /* Initialize the MQTT library. */ + TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); + + /* Set the network interface send function. */ + networkInterface.send = _sendSuccess; + + /* Initialize the MQTT connection object to use for the Shadow tests. */ + _pMqttConnection = AwsIotTestMqtt_createMqttConnection( false, + &networkInterface, + 0 ); + + /* Initialize the Shadow library. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Shadow API tests. + */ +TEST_TEAR_DOWN( Shadow_Unit_API ) +{ + /* Clean up the Shadow library. */ + AwsIotShadow_Cleanup(); + + /* Clean up the MQTT connection object. */ + AwsIotTestMqtt_destroyMqttConnection( _pMqttConnection ); + + /* Clean up the MQTT library. */ + AwsIotMqtt_Cleanup(); + + /* Destroy the receive thread timer. */ + AwsIotClock_TimerDestroy( &_receiveTimer ); + + /* Wait for the receive thread to finish and release the last packet mutex. */ + AwsIotMutex_Lock( &_lastPacketMutex ); + + /* Destroy the last packet mutex. */ + AwsIotMutex_Unlock( &_lastPacketMutex ); + AwsIotMutex_Destroy( &_lastPacketMutex ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Shadow API tests. + */ +TEST_GROUP_RUNNER( Shadow_Unit_API ) +{ + RUN_TEST_CASE( Shadow_Unit_API, Init ); + RUN_TEST_CASE( Shadow_Unit_API, OperationInvalidParameters ); + RUN_TEST_CASE( Shadow_Unit_API, DocumentInvalidParameters ); + RUN_TEST_CASE( Shadow_Unit_API, WaitInvalidParameters ); + RUN_TEST_CASE( Shadow_Unit_API, DeleteMallocFail ); + RUN_TEST_CASE( Shadow_Unit_API, GetMallocFail ); + RUN_TEST_CASE( Shadow_Unit_API, UpdateMallocFail ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the function @ref shadow_function_init. + */ +TEST( Shadow_Unit_API, Init ) +{ + /* Check that test set up set the default value. */ + TEST_ASSERT_EQUAL_UINT64( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); + + /* The Shadow library was already initialized by test set up. Clean it up + * before running this test. */ + AwsIotShadow_Cleanup(); + + /* Set a MQTT timeout. */ + AwsIotShadow_Init( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1 ); + TEST_ASSERT_EQUAL_UINT64( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotShadowMqttTimeoutMs ); + + /* Cleanup should restore the default MQTT timeout. */ + AwsIotShadow_Cleanup(); + TEST_ASSERT_EQUAL_UINT64( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); + + /* Initialize the Shadow library for test clean up. */ + AwsIotShadow_Init( 0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Shadow operation functions with various + * invalid parameters. + */ +TEST( Shadow_Unit_API, OperationInvalidParameters ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + + /* Missing Thing Name. */ + status = AwsIotShadow_Delete( _pMqttConnection, + NULL, + 0, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + status = AwsIotShadow_Update( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* Thing Name too long. */ + status = AwsIotShadow_Delete( _pMqttConnection, + _TEST_THING_NAME, + _MAX_THING_NAME_LENGTH + 1, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* No reference with waitable operation. */ + status = AwsIotShadow_Delete( _pMqttConnection, + _TEST_THING_NAME, + _TEST_THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* Both callback and waitable flag set. */ + status = AwsIotShadow_Delete( _pMqttConnection, + _TEST_THING_NAME, + _TEST_THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_WAITABLE, + &callbackInfo, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* No callback for non-waitable GET. */ + documentInfo.pThingName = _TEST_THING_NAME; + documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* Callback function not set. */ + status = AwsIotShadow_Delete( _pMqttConnection, + _TEST_THING_NAME, + _TEST_THING_NAME_LENGTH, + 0, + &callbackInfo, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Shadow operation functions with an invalid + * document info parameter. + */ +TEST( Shadow_Unit_API, DocumentInvalidParameters ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + + /* Missing Thing Name. */ + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + documentInfo.pThingName = _TEST_THING_NAME; + documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + + /* Invalid QoS. */ + documentInfo.QoS = 3; + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + documentInfo.QoS = 0; + + /* Invalid retry parameters. */ + documentInfo.retryLimit = -1; + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + documentInfo.retryLimit = 1; + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + documentInfo.retryLimit = 0; + + /* Waitable Shadow get with no memory allocation function. */ + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* Update with no document. */ + status = AwsIotShadow_Update( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* Update with no client token. */ + documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}}"; + documentInfo.update.updateDocumentLength = 29; + status = AwsIotShadow_Update( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* Client token too long. */ + documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}},\"clientToken\": " + "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; + documentInfo.update.updateDocumentLength = 146; + status = AwsIotShadow_Update( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref shadow_function_wait with various + * invalid parameters. + */ +TEST( Shadow_Unit_API, WaitInvalidParameters ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + _shadowOperation_t operation = { 0 }; + + /* NULL reference. */ + status = AwsIotShadow_Wait( NULL, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* No waitable flag set. */ + status = AwsIotShadow_Wait( &operation, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + + /* NULL output parameters for Shadow GET. */ + operation.flags = AWS_IOT_SHADOW_FLAG_WAITABLE; + operation.type = _SHADOW_GET; + status = AwsIotShadow_Wait( &operation, 0, NULL, NULL ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref shadow_function_delete when memory + * allocation fails at various points. + */ +TEST( Shadow_Unit_API, DeleteMallocFail ) +{ + int i = 0; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Shadow DELETE. Memory allocation will fail at various times + * during this call. */ + status = AwsIotShadow_Delete( _pMqttConnection, + _TEST_THING_NAME, + _TEST_THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + + /* Once the Shadow DELETE call succeeds, wait for it to complete. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* No response will be received from the network, so the Shadow DELETE + * is expected to time out. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, + AwsIotShadow_Wait( reference, 0, NULL, NULL ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } + + /* Wait for any pending QoS 0 publishes to clean up. */ + sleep( 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref shadow_function_get when memory + * allocation fails at various points. + */ +TEST( Shadow_Unit_API, GetMallocFail ) +{ + int i = 0; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + const char * pRetrievedDocument = NULL; + size_t retrievedDocumentSize = 0; + + /* Set the members of the document info. */ + documentInfo.pThingName = _TEST_THING_NAME; + documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + documentInfo.QoS = 1; + documentInfo.get.mallocDocument = AwsIotTest_Malloc; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Shadow GET. Memory allocation will fail at various times + * during this call. */ + status = AwsIotShadow_Get( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + + /* Once the Shadow GET call succeeds, wait for it to complete. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* No response will be received from the network, so the Shadow GET + * is expected to time out. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, + AwsIotShadow_Wait( reference, + 0, + &pRetrievedDocument, + &retrievedDocumentSize ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } +} + +/*-----------------------------------------------------------*/ + +TEST( Shadow_Unit_API, UpdateMallocFail ) +{ + int i = 0; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + + /* Set the members of the document info. */ + documentInfo.pThingName = _TEST_THING_NAME; + documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + documentInfo.QoS = 1; + documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}},\"clientToken\":\"TEST\"}"; + documentInfo.update.updateDocumentLength = 50; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Shadow UPDATE. Memory allocation will fail at various times + * during this call. */ + status = AwsIotShadow_Update( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &reference ); + + /* Once the Shadow UPDATE call succeeds, wait for it to complete. */ + if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + { + /* No response will be received from the network, so the Shadow UPDATE + * is expected to time out. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, + AwsIotShadow_Wait( reference, + 0, + NULL, + NULL ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c new file mode 100644 index 0000000000..4e14e897d5 --- /dev/null +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_shadow_parser.c + * @brief Tests for the Shadow topic name and JSON parser functions. + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int vsnprintf( char *, + size_t, + const char *, + va_list ); +/** @endcond */ + +/* Shadow internal include. */ +#include "private/aws_iot_shadow_internal.h" + +/* JSON utilities include. */ +#include "aws_iot_json_utils.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief The size of the buffers allocated for holding Shadow error documents. + */ +#define _ERROR_DOCUMENT_BUFFER_SIZE ( 128 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Wrapper for parsing JSON documents and checking the result. + */ +static void _parseJson( bool expectedResult, + const char * const pJsonDocument, + size_t jsonDocumentLength, + const char * const pJsonKey, + const char * const pExpectedJsonValue, + size_t expectedJsonValueLength ) +{ + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + TEST_ASSERT_EQUAL_INT( expectedResult, + AwsIotJsonUtils_FindJsonValue( pJsonDocument, + jsonDocumentLength, + pJsonKey, + strlen( pJsonKey ), + &pJsonValue, + &jsonValueLength ) ); + + if( expectedResult == true ) + { + TEST_ASSERT_EQUAL( expectedJsonValueLength, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( pExpectedJsonValue, pJsonValue, jsonValueLength ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Wrapper for generating and parsing error documents. + */ +static void _generateParseErrorDocument( char * const pErrorDocument, + AwsIotShadowError_t expectedCode, + const char * const pFormat, + ... ) +{ + int errorDocumentLength = 0; + va_list arguments; + + /* Generate an error document. */ + va_start( arguments, pFormat ); + errorDocumentLength = vsnprintf( pErrorDocument, + _ERROR_DOCUMENT_BUFFER_SIZE, + pFormat, + arguments ); + va_end( arguments ); + + /* Check for errors from vsnprintf. */ + TEST_ASSERT_GREATER_THAN_INT( 0, errorDocumentLength ); + TEST_ASSERT_LESS_THAN_INT( _ERROR_DOCUMENT_BUFFER_SIZE, errorDocumentLength ); + + /* Parse the error document and check the result. */ + TEST_ASSERT_EQUAL( expectedCode, + AwsIotShadowInternal_ParseErrorDocument( pErrorDocument, + ( size_t ) errorDocumentLength ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Wrapper for parsing Shadow Thing Names and checking the result. + */ +static void _parseThingName( const char * const pTopicName, + AwsIotShadowError_t expectedResult, + const char * const pExpectedThingName ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + uint16_t topicNameLength = ( uint16_t ) strlen( pTopicName ); + const char * pThingName = NULL; + size_t thingNameLength = 0; + + status = AwsIotShadowInternal_ParseThingName( pTopicName, + topicNameLength, + &pThingName, + &thingNameLength ); + TEST_ASSERT_EQUAL( expectedResult, status ); + + if( expectedResult == AWS_IOT_SHADOW_SUCCESS ) + { + TEST_ASSERT_EQUAL( strlen( pExpectedThingName ), thingNameLength ); + TEST_ASSERT_EQUAL_STRING_LEN( pExpectedThingName, pThingName, thingNameLength ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Shadow parser tests. + */ +TEST_GROUP( Shadow_Unit_Parser ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Shadow parser tests. + */ +TEST_SETUP( Shadow_Unit_Parser ) +{ + /* Initialize the Shadow library. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Shadow parser tests. + */ +TEST_TEAR_DOWN( Shadow_Unit_Parser ) +{ + /* Clean up the Shadow library. */ + AwsIotShadow_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Shadow parser tests. + */ +TEST_GROUP_RUNNER( Shadow_Unit_Parser ) +{ + RUN_TEST_CASE( Shadow_Unit_Parser, StatusValid ); + RUN_TEST_CASE( Shadow_Unit_Parser, StatusInvalid ); + RUN_TEST_CASE( Shadow_Unit_Parser, JsonValid ); + RUN_TEST_CASE( Shadow_Unit_Parser, JsonInvalid ); + RUN_TEST_CASE( Shadow_Unit_Parser, ErrorDocument ); + RUN_TEST_CASE( Shadow_Unit_Parser, ErrorDocumentInvalid ); + RUN_TEST_CASE( Shadow_Unit_Parser, ThingName ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing Shadow status from a valid topic name. + */ +TEST( Shadow_Unit_Parser, StatusValid ) +{ + _shadowOperationStatus_t status = _UNKNOWN_STATUS; + + /* Parse "accepted" status. */ + status = AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/accepted", + 39 ); + TEST_ASSERT_EQUAL( _SHADOW_ACCEPTED, status ); + + /* Parse "rejected" status. */ + status = AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/rejected", + 39 ); + TEST_ASSERT_EQUAL( _SHADOW_REJECTED, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing Shadow status from invalid topic names. + */ +TEST( Shadow_Unit_Parser, StatusInvalid ) +{ + /* Topic too short. */ + TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + AwsIotShadowInternal_ParseShadowStatus( "accepted", + 8 ) ); + + /* Topic missing last character. */ + TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/accepte", + 38 ) ); + + /* Topic missing level separator. */ + TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadowaccepted", + 38 ) ); + + /* Topic suffix isn't "accepted" or "rejected". */ + TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/unknown", + 38 ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing valid JSON documents. + */ +TEST( Shadow_Unit_Parser, JsonValid ) +{ + /* Parse JSON document with string, int, bool, and null. */ + { + const char pJsonDocument[ 81 ] = "{\"name\" \n\r:\n \"John Smith\", \"age\" :\n\r 30, \n \"isAlive\" : true, \r \"spouse\":null}"; + size_t jsonDocumentLength = 81; + + _parseJson( true, pJsonDocument, jsonDocumentLength, "name", "\"John Smith\"", 12 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "age", "30", 2 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "isAlive", "true", 4 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "spouse", "null", 4 ); + + /* Attempt to find a key not in the JSON document. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "address", NULL, 0 ); + } + + /* Parse JSON document with objects and arrays. */ + { + const char pJsonDocument[ 90 ] = "{\"object\" : { \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}}"; + size_t jsonDocumentLength = 90; + + _parseJson( true, pJsonDocument, jsonDocumentLength, "object", "{ \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}", 76 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "nestedObject", "{ \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}", 57 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "array", "[[1,2,3],[1,2,3],[1,2,3]]", 25 ); + } + + /* JSON document with escape sequences. */ + { + const char pJsonDocument[ 40 ] = "{\"key\": \"value\", \"ke\\\"y2\": \"\\\"value\\\"\"}"; + size_t jsonDocumentLength = 40; + + /* Attempt to find a JSON key that is actually a value. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "value", NULL, 0 ); + + /* Attempt to find a JSON key that is a substring of a key. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "ke", NULL, 0 ); + + /* Find a key and string that contain escaped quotes. */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "ke\\\"y2", "\"\\\"value\\\"\"", 11 ); + } + + /* Short JSON document. */ + { + const char pJsonDocument[ 16 ] = "{\"key\":\"value\"}"; + size_t jsonDocumentLength = 16; + + /* Attempt to find a key longer than the JSON document. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "longlonglonglongkey", NULL, 0 ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that parsing invalid JSON documents does not read out-of-bounds + * memory. + */ +TEST( Shadow_Unit_Parser, JsonInvalid ) +{ + /* JSON key not followed by a : */ + { + const char pJsonDocument[ 15 ] = "{\"string\" "; + size_t jsonDocumentLength = 15; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* JSON value not followed by a , */ + { + const char pJsonDocument[ 29 ] = "{\"int\": 10 \"string\": \"hello\"}"; + size_t jsonDocumentLength = 29; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); + } + + /* JSON key with no value. */ + { + const char pJsonDocument[ 17 ] = "{\"string\": "; + size_t jsonDocumentLength = 17; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* Unterminated JSON primitive. */ + { + const char pJsonDocument[ 11 ] = "{\"int\":1000"; + size_t jsonDocumentLength = 11; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); + } + + /* Unterminated JSON string (ending is an escaped quote). */ + { + const char pJsonDocument[ 14 ] = "{\"string\": \"\\\""; + size_t jsonDocumentLength = 14; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* Unterminated JSON string (ending is not a quote). */ + { + const char pJsonDocument[ 14 ] = "{\"string\": \" \\"; + size_t jsonDocumentLength = 14; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* Unterminated JSON object. */ + { + const char pJsonDocument[ 41 ] = "{\"object\": {\"key\": { \"nestedKey\":\"value\"}"; + size_t jsonDocumentLength = 41; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "object", NULL, 0 ); + } + + /* Unterminated JSON array. */ + { + const char pJsonDocument[ 26 ] = "{\"array\": [[1,2,3],[1,2,3]"; + size_t jsonDocumentLength = 26; + + _parseJson( false, pJsonDocument, jsonDocumentLength, "array", NULL, 0 ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing valid Shadow error documents. + */ +TEST( Shadow_Unit_Parser, ErrorDocument ) +{ + size_t i = 0; + char pErrorDocument[ _ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; + AwsIotShadowError_t pValidErrorCodes[] = + { + AWS_IOT_SHADOW_BAD_REQUEST, + AWS_IOT_SHADOW_UNAUTHORIZED, + AWS_IOT_SHADOW_FORBIDDEN, + AWS_IOT_SHADOW_NOT_FOUND, + AWS_IOT_SHADOW_CONFLICT, + AWS_IOT_SHADOW_TOO_LARGE, + AWS_IOT_SHADOW_UNSUPPORTED, + AWS_IOT_SHADOW_TOO_MANY_REQUESTS, + AWS_IOT_SHADOW_SERVER_ERROR + }; + + /* Generate an error document for every valid error code and parse it. */ + for( i = 0; i < ( sizeof( pValidErrorCodes ) / sizeof( pValidErrorCodes[ 0 ] ) ); i++ ) + { + _generateParseErrorDocument( pErrorDocument, + pValidErrorCodes[ i ], + "{\"code\": %d, \"message\": \"%s\"}", + ( int ) pValidErrorCodes[ i ], + "Test" ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing invalid Shadow error documents. + */ +TEST( Shadow_Unit_Parser, ErrorDocumentInvalid ) +{ + char pErrorDocument[ _ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; + + /* Parse an error document with an unknown code. */ + _generateParseErrorDocument( pErrorDocument, + AWS_IOT_SHADOW_BAD_RESPONSE, + "{\"code\": %d, \"message\": \"%s\"}", + -1, + "Test" ); + + /* Parse an error document missing the "code" key. */ + _generateParseErrorDocument( pErrorDocument, + AWS_IOT_SHADOW_BAD_RESPONSE, + "{\"message\": \"Test\"}" ); + + /* Parse an error document missing the "message" key. */ + _generateParseErrorDocument( pErrorDocument, + AWS_IOT_SHADOW_BAD_RESPONSE, + "{\"code\": 400}" ); + + /* Parse a malformed error document where the code is unterminated. */ + _generateParseErrorDocument( pErrorDocument, + AWS_IOT_SHADOW_BAD_RESPONSE, + "{\"code\": 400" ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing both valid and invalid Shadow topics. + */ +TEST( Shadow_Unit_Parser, ThingName ) +{ + /* Valid operation topic. */ + _parseThingName( "$aws/things/TEST/shadow/get/accepted", + AWS_IOT_SHADOW_SUCCESS, + "TEST" ); + + /* Valid callback topic. */ + _parseThingName( "$aws/things/TEST/shadow/update/delta", + AWS_IOT_SHADOW_SUCCESS, + "TEST" ); + + /* Topic too short. */ + _parseThingName( "$aws/things/TEST/", + AWS_IOT_SHADOW_BAD_RESPONSE, + "TEST" ); + + /* Incorrect prefix. */ + _parseThingName( "$awsshadow/TEST/shadow/update/accepted", + AWS_IOT_SHADOW_BAD_RESPONSE, + "TEST" ); + + /* Thing Name unterminated. */ + _parseThingName( "$aws/things/TESTTESTTESTTESTTESTTEST", + AWS_IOT_SHADOW_BAD_RESPONSE, + "TESTTESTTESTTESTTESTTEST" ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/unit/README.md b/tests/unit/README.md deleted file mode 100644 index a58e2e4757..0000000000 --- a/tests/unit/README.md +++ /dev/null @@ -1,12 +0,0 @@ -## Unit Tests -This folder contains unit tests to verify Embedded C SDK functionality. These have been tested to work with Linux using CppUTest as the testing framework. -CppUTest is not provided along with this code. It needs to be separately downloaded. These tests have been verified to work with CppUTest v3.6, which can be found [here](https://github.com/cpputest/cpputest/tree/v3.6). -Each test contains a comment describing what is being tested. The Tests can be run using the Makefile provided in the root folder for the SDK. There are a total of 187 tests. - -To run these tests, follow the below steps: - - * Copy the code for CppUTest v3.6 from github to external_libs/CppUTest - * Navigate to SDK Root folder - * run `make run-unit-tests` - -This will run all unit tests and generate coverage report in the build_output folder. The report can be viewed by opening /build_output/generated-coverage/index.html in a browser. \ No newline at end of file diff --git a/tests/unit/include/aws_iot_config.h b/tests/unit/include/aws_iot_config.h deleted file mode 100644 index b75d6e3b40..0000000000 --- a/tests/unit/include/aws_iot_config.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_config.h - * @brief IoT Client Unit Testing - IoT Config - */ - -#ifndef IOT_TESTS_UNIT_CONFIG_H_ -#define IOT_TESTS_UNIT_CONFIG_H_ - -// Get from console -// ================================================= -#define AWS_IOT_MQTT_HOST "localhost" -#define AWS_IOT_MQTT_PORT 443 -#define AWS_IOT_MQTT_CLIENT_ID "C-SDK_UnitTestClient" -#define AWS_IOT_MY_THING_NAME "C-SDK_UnitTestThing" -#define AWS_IOT_ROOT_CA_FILENAME "rootCA.crt" -#define AWS_IOT_CERTIFICATE_FILENAME "cert.crt" -#define AWS_IOT_PRIVATE_KEY_FILENAME "privkey.pem" -// ================================================= - - -// MQTT PubSub -#ifndef DISABLE_IOT_JOBS -#define AWS_IOT_MQTT_RX_BUF_LEN 512 ///< Any message that comes into the device should be less than this buffer size. If a received message is bigger than this buffer size the message will be dropped. -#else -#define AWS_IOT_MQTT_RX_BUF_LEN 2048 -#endif -#define AWS_IOT_MQTT_TX_BUF_LEN 512 ///< Any time a message is sent out through the MQTT layer. The message is copied into this buffer anytime a publish is done. This will also be used in the case of Thing Shadow -#define AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS 5 ///< Maximum number of topic filters the MQTT client can handle at any given time. This should be increased appropriately when using Thing Shadow - -// Shadow and Job common configs -#define MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES 80 ///< Maximum size of the Unique Client Id. For More info on the Client Id refer \ref response "Acknowledgments" -#define MAX_SIZE_CLIENT_ID_WITH_SEQUENCE MAX_SIZE_OF_UNIQUE_CLIENT_ID_BYTES + 10 ///< This is size of the extra sequence number that will be appended to the Unique client Id -#define MAX_SIZE_CLIENT_TOKEN_CLIENT_SEQUENCE MAX_SIZE_CLIENT_ID_WITH_SEQUENCE + 20 ///< This is size of the the total clientToken key and value pair in the JSON -#define MAX_SIZE_OF_THING_NAME 30 ///< The Thing Name should not be bigger than this value. Modify this if the Thing Name needs to be bigger - -// Thing Shadow specific configs -#define SHADOW_MAX_SIZE_OF_RX_BUFFER 512 ///< Maximum size of the SHADOW buffer to store the received Shadow message -#define MAX_ACKS_TO_COMEIN_AT_ANY_GIVEN_TIME 10 ///< At Any given time we will wait for this many responses. This will correlate to the rate at which the shadow actions are requested -#define MAX_THINGNAME_HANDLED_AT_ANY_GIVEN_TIME 10 ///< We could perform shadow action on any thing Name and this is maximum Thing Names we can act on at any given time -#define MAX_JSON_TOKEN_EXPECTED 120 ///< These are the max tokens that is expected to be in the Shadow JSON document. Include the metadata that gets published -#define MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME 60 ///< All shadow actions have to be published or subscribed to a topic which is of the format $aws/things/{thingName}/shadow/update/accepted. This refers to the size of the topic without the Thing Name -#define MAX_SHADOW_TOPIC_LENGTH_BYTES MAX_SHADOW_TOPIC_LENGTH_WITHOUT_THINGNAME + MAX_SIZE_OF_THING_NAME ///< This size includes the length of topic with Thing Name - -// Job specific configs -#ifndef DISABLE_IOT_JOBS -#define MAX_SIZE_OF_JOB_ID 64 -#define MAX_JOB_JSON_TOKEN_EXPECTED 120 -#define MAX_SIZE_OF_JOB_REQUEST AWS_IOT_MQTT_TX_BUF_LEN - -#define MAX_JOB_TOPIC_LENGTH_WITHOUT_JOB_ID_OR_THING_NAME 40 -#define MAX_JOB_TOPIC_LENGTH_BYTES MAX_JOB_TOPIC_LENGTH_WITHOUT_JOB_ID_OR_THING_NAME + MAX_SIZE_OF_THING_NAME + MAX_SIZE_OF_JOB_ID + 2 -#endif - -// Auto Reconnect specific config -#define AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL 1000 ///< Minimum time before the First reconnect attempt is made as part of the exponential back-off algorithm -#define AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL 128000 ///< Maximum time interval after which exponential back-off will stop attempting to reconnect. - -#endif /* IOT_TESTS_UNIT_CONFIG_H_ */ diff --git a/tests/unit/include/aws_iot_tests_unit_helper_functions.h b/tests/unit/include/aws_iot_tests_unit_helper_functions.h deleted file mode 100644 index a7d3f80a1e..0000000000 --- a/tests/unit/include/aws_iot_tests_unit_helper_functions.h +++ /dev/null @@ -1,100 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_helper_functions.h - * @brief IoT Client Unit Testing - Helper Functions - */ - -#ifndef IOT_TESTS_UNIT_HELPER_FUNCTIONS_H_ -#define IOT_TESTS_UNIT_HELPER_FUNCTIONS_H_ - -#include -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_jobs_topics.h" - -typedef struct { - unsigned char PacketType; - unsigned int RemainingLength; - unsigned int ProtocolLength; - unsigned char ProtocolName[4]; - unsigned int ProtocolLevel; - unsigned char ConnectFlag; - unsigned int KeepAlive; -} ConnectBufferProofread; - -void ResetInvalidParameters(void); - -void InitMQTTParamsSetup(IoT_Client_Init_Params *params, char *pHost, uint16_t port, bool enableAutoReconnect, - iot_disconnect_handler disconnectHandler); - -void ConnectMQTTParamsSetup(IoT_Client_Connect_Params *params, char *pClientID, uint16_t clientIDLen); - -void ConnectMQTTParamsSetup_Detailed(IoT_Client_Connect_Params *params, char *pClientID, uint16_t clientIDLen, - QoS qos, bool isCleanSession, bool isWillMsgPresent, char *pWillTopicName, - uint16_t willTopicNameLen, char *pWillMessage, uint16_t willMsgLen, - char *pUsername, uint16_t userNameLen, char *pPassword, - uint16_t passwordLen); - -void printBuffer(unsigned char *buffer, size_t len); - -void setTLSRxBufferForConnack(IoT_Client_Connect_Params *params, unsigned char sessionPresent, - unsigned char connackResponseCode); - -void setTLSRxBufferForPuback(void); - -void setTLSRxBufferForSuback(char *topicName, size_t topicNameLen, QoS qos, IoT_Publish_Message_Params params); - -void setTLSRxBufferForDoubleSuback(char *topicName, size_t topicNameLen, QoS qos, IoT_Publish_Message_Params params); - -void setTLSRxBufferForSubFail(void); - -void setTLSRxBufferWithMsgOnSubscribedTopic(char *topicName, size_t topicNameLen, QoS qos, - IoT_Publish_Message_Params params, char *pMsg); - -void setTLSRxBufferForUnsuback(void); - -void setTLSRxBufferForPingresp(void); - -void setTLSRxBufferForConnackAndSuback(IoT_Client_Connect_Params *conParams, unsigned char sessionPresent, - char *topicName, size_t topicNameLen, QoS qos); - -unsigned char isLastTLSTxMessagePuback(void); - -unsigned char isLastTLSTxMessagePingreq(void); - -unsigned char isLastTLSTxMessageDisconnect(void); - -void setTLSRxBufferDelay(int seconds, int microseconds); - -void ResetTLSBuffer(void); - -unsigned char generateMultipleSubTopics(char *des, int boundary); - -void encodeRemainingLength(unsigned char *buf, size_t *st, size_t length); - -unsigned char *connectTxBufferHeaderParser(ConnectBufferProofread *params, unsigned char *buf); - -bool isConnectTxBufFlagCorrect(IoT_Client_Connect_Params *settings, ConnectBufferProofread *readRes); - -bool isConnectTxBufPayloadCorrect(IoT_Client_Connect_Params *settings, unsigned char *payloadBuf); - -void printPrfrdParams(ConnectBufferProofread *params); - -const char *getJobTopicTypeName(AwsIotJobExecutionTopicType topicType); - -const char *getJobReplyTypeName(AwsIotJobExecutionTopicReplyType replyType); - -#endif /* IOT_TESTS_UNIT_HELPER_FUNCTIONS_H_ */ diff --git a/tests/unit/include/aws_iot_tests_unit_shadow_helper.h b/tests/unit/include/aws_iot_tests_unit_shadow_helper.h deleted file mode 100644 index 455a387b30..0000000000 --- a/tests/unit/include/aws_iot_tests_unit_shadow_helper.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_helper.h - * @brief IoT Client Unit Testing - Shadow Helper functions - */ - -#ifndef IOT_TESTS_UNIT_SHADOW_HELPER_FUNCTIONS_H_ -#define IOT_TESTS_UNIT_SHADOW_HELPER_FUNCTIONS_H_ - -#define AWS_THINGS_TOPIC "$aws/things/" -#define SHADOW_TOPIC "/shadow/" -#define ACCEPTED_TOPIC "/accepted" -#define REJECTED_TOPIC "/rejected" -#define UPDATE_TOPIC "update" -#define GET_TOPIC "get" -#define DELETE_TOPIC "delete" - - -#define GET_ACCEPTED_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC GET_TOPIC ACCEPTED_TOPIC -#define GET_REJECTED_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC GET_TOPIC REJECTED_TOPIC -#define GET_PUB_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC GET_TOPIC - -#define DELETE_ACCEPTED_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC DELETE_TOPIC ACCEPTED_TOPIC -#define DELETE_REJECTED_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC DELETE_TOPIC REJECTED_TOPIC - -#define UPDATE_ACCEPTED_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC UPDATE_TOPIC ACCEPTED_TOPIC -#define UPDATE_REJECTED_TOPIC AWS_THINGS_TOPIC AWS_IOT_MY_THING_NAME SHADOW_TOPIC UPDATE_TOPIC REJECTED_TOPIC - -#endif /* IOT_TESTS_UNIT_SHADOW_HELPER_FUNCTIONS_H_ */ diff --git a/tests/unit/src/aws_iot_tests_unit_common_tests.cpp b/tests/unit/src/aws_iot_tests_unit_common_tests.cpp deleted file mode 100644 index 449647ecc6..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_common_tests.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_common_tests.cpp - * @brief IoT Client Unit Testing - Common Tests - */ - -#include -#include - -TEST_GROUP_C(CommonTests){ - TEST_GROUP_C_SETUP_WRAPPER(CommonTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(CommonTests) -}; - -TEST_GROUP_C_WRAPPER(CommonTests, NullClientGetState) -TEST_GROUP_C_WRAPPER(CommonTests, NullClientSetAutoreconnect) - -TEST_GROUP_C_WRAPPER(CommonTests, UnexpectedAckFiltering) -TEST_GROUP_C_WRAPPER(CommonTests, BigMQTTRxMessageIgnore) -TEST_GROUP_C_WRAPPER(CommonTests, BigMQTTRxMessageReadNextMessage) diff --git a/tests/unit/src/aws_iot_tests_unit_common_tests_helper.c b/tests/unit/src/aws_iot_tests_unit_common_tests_helper.c deleted file mode 100644 index 9d16042116..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_common_tests_helper.c +++ /dev/null @@ -1,186 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_common_tests_helper.h - * @brief IoT Client Unit Testing - Common Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_log.h" -#include "aws_iot_tests_unit_helper_functions.h" - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static IoT_Publish_Message_Params testPubMsgParams; -static AWS_IoT_Client iotClient; - -static char subTopic[10] = "sdk/Test"; -static uint16_t subTopicLen = 8; -char cPayload[100]; - -char cbBuffer[AWS_IOT_MQTT_TX_BUF_LEN + 2]; - -static void iot_tests_unit_common_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, IoT_Publish_Message_Params *params, - void *pData) { - char *tmp = params->payload; - unsigned int i; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - for(i = 0; i < params->payloadLen; i++) { - cbBuffer[i] = tmp[i]; - } -} - -TEST_GROUP_C_SETUP(CommonTests) { - ResetTLSBuffer(); - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.mqttCommandTimeout_ms = 2000; - IoT_Error_t rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - IOT_DEBUG("\n\nMQTT Status State : %d, RC : %d\n\n", aws_iot_mqtt_get_client_state(&iotClient), rc); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - snprintf(cPayload, 100, "%s : %d ", "hello from SDK", 0); - testPubMsgParams.payload = (void *) cPayload; - testPubMsgParams.payloadLen = strlen(cPayload); - - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(CommonTests) { - /* Clean up. Not checking return code here because this is common to all tests. - * A test might have already caused a disconnect by this point. - */ - IoT_Error_t rc = aws_iot_mqtt_disconnect(&iotClient); - IOT_UNUSED(rc); -} - -TEST_C(CommonTests, NullClientGetState) { - ClientState cs = aws_iot_mqtt_get_client_state(NULL); - CHECK_EQUAL_C_INT(CLIENT_STATE_INVALID, cs); -} - -TEST_C(CommonTests, NullClientSetAutoreconnect) { - IoT_Error_t rc = aws_iot_mqtt_autoreconnect_set_status(NULL, true); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -// Unexpected Ack section -TEST_C(CommonTests, UnexpectedAckFiltering) { - IoT_Error_t rc = FAILURE; - - IOT_DEBUG("\n-->Running CommonTests - Unexpected Ack Filtering\n"); - // Assume we are connected and have not done anything yet - // Connack - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - // Puback - setTLSRxBufferForPuback(); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - // Suback: OoS1 - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - // Suback: QoS0 - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - // Unsuback - setTLSRxBufferForUnsuback(); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); -} - -TEST_C(CommonTests, BigMQTTRxMessageIgnore) { - uint32_t i = 0; - IoT_Error_t rc = FAILURE; - char expectedCallbackString[AWS_IOT_MQTT_RX_BUF_LEN + 2]; - - IOT_DEBUG("\n-->Running CommonTests - Ignore Large Incoming Message \n"); - - setTLSRxBufferForSuback("limitTest/topic1", 16, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "limitTest/topic1", 16, QOS0, iot_tests_unit_common_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - for(i = 0; i < AWS_IOT_MQTT_RX_BUF_LEN; i++) { - expectedCallbackString[i] = 'X'; - } - expectedCallbackString[i + 1] = '\0'; - - setTLSRxBufferWithMsgOnSubscribedTopic("limitTest/topic1", 16, QOS0, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(MQTT_RX_BUFFER_TOO_SHORT_ERROR, rc); -} - -/** - * - * On receiving a big message into the TLS buffer the MQTT client should flush it out, otherwise it can cause undefined behavior. - */ -TEST_C(CommonTests, BigMQTTRxMessageReadNextMessage) { - uint32_t i = 0; - IoT_Error_t rc = FAILURE; - char expectedCallbackString[AWS_IOT_MQTT_RX_BUF_LEN + 2]; - - IOT_DEBUG("\n-->Running CommonTests - Clear Buffer when large message received and continue reading \n"); - - setTLSRxBufferForSuback("limitTest/topic1", 16, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "limitTest/topic1", 16, QOS0, iot_tests_unit_common_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - for(i = 0; i < AWS_IOT_MQTT_RX_BUF_LEN; i++) { - expectedCallbackString[i] = 'X'; - } - expectedCallbackString[i + 1] = '\0'; - - setTLSRxBufferWithMsgOnSubscribedTopic("limitTest/topic1", 16, QOS0, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(MQTT_RX_BUFFER_TOO_SHORT_ERROR, rc); - - ResetTLSBuffer(); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - expectedCallbackString[3] = '\0'; - setTLSRxBufferWithMsgOnSubscribedTopic("limitTest/topic1", 16, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(rc, SUCCESS); - CHECK_EQUAL_C_STRING("XXX", cbBuffer); -} diff --git a/tests/unit/src/aws_iot_tests_unit_connect.cpp b/tests/unit/src/aws_iot_tests_unit_connect.cpp deleted file mode 100644 index bfb3823b2e..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_connect.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_connect.cpp - * @brief IoT Client Unit Testing - Connect API Tests - */ - -#include -#include - -TEST_GROUP_C(ConnectTests){ - TEST_GROUP_C_SETUP_WRAPPER(ConnectTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(ConnectTests) -}; - -/* B:1 - Init with Null/empty client instance */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullClientInit) -/* B:2 - Connect with Null/empty client instance */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullClientConnect) -/* B:3 - Connect with Null/Empty endpoint */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullHost) -/* B:4 - Connect with Null/Empty port */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullPort) -/* B:5 - Connect with Null/Empty root CA path */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullRootCAPath) -/* B:6 - Connect with Null/Empty Client certificate path */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullClientCertificate) -/* B:7 - Connect with Null/Empty private key Path */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullPrivateKeyPath) -/* B:8 - Connect with Null/Empty client ID */ -TEST_GROUP_C_WRAPPER(ConnectTests, NullClientID) -/* B:9 - Connect with invalid Endpoint */ -TEST_GROUP_C_WRAPPER(ConnectTests, InvalidEndpoint) -/* B:10 - Connect with invalid correct endpoint but invalid port */ -TEST_GROUP_C_WRAPPER(ConnectTests, InvalidPort) -/* B:11 - Connect with invalid Root CA path */ -TEST_GROUP_C_WRAPPER(ConnectTests, InvalidRootCAPath) -/* B:12 - Connect with invalid Client certificate path */ -TEST_GROUP_C_WRAPPER(ConnectTests, InvalidClientCertPath) -/* B:13 - Connect with invalid private key path */ -TEST_GROUP_C_WRAPPER(ConnectTests, InvalidPrivateKeyPath) -/* B:14 - Connect, no response timeout */ -TEST_GROUP_C_WRAPPER(ConnectTests, NoResponseTimeout) -/* B:15 - Connect, connack malformed, too large */ -TEST_GROUP_C_WRAPPER(ConnectTests, ConnackTooLarge) -/* B:16 - Connect, connack malformed, fixed header corrupted */ -TEST_GROUP_C_WRAPPER(ConnectTests, FixedHeaderCorrupted) -/* B:17 - Connect, connack malformed, invalid remaining length */ -TEST_GROUP_C_WRAPPER(ConnectTests, InvalidRemainingLength) -/* B:18 - Connect, connack returned error, unacceptable protocol version */ -TEST_GROUP_C_WRAPPER(ConnectTests, UnacceptableProtocolVersion) -/* B:19 - Connect, connack returned error, identifier rejected */ -TEST_GROUP_C_WRAPPER(ConnectTests, IndentifierRejected) -/* B:20 - Connect, connack returned error, Server unavailable */ -TEST_GROUP_C_WRAPPER(ConnectTests, ServerUnavailable) -/* B:21 - Connect, connack returned error, bad user name or password */ -TEST_GROUP_C_WRAPPER(ConnectTests, BadUserNameOrPassword) -/* B:22 - Connect, connack returned error, not authorized */ -TEST_GROUP_C_WRAPPER(ConnectTests, NotAuthorized) -/* B:23 - Connect, connack return after half command timeout delay, success */ -TEST_GROUP_C_WRAPPER(ConnectTests, SuccessAfterDelayedConnack) -/* B:24 - Connect, connack returned success */ -TEST_GROUP_C_WRAPPER(ConnectTests, ConnectSuccess) -/* B:25 - Connect, flag settings and parameters are recorded in buffer */ -TEST_GROUP_C_WRAPPER(ConnectTests, FlagSettingsAndParamsAreRecordedIntoBuf) -/* B:26 - Connect attempt, Disconnect, Manually reconnect */ -TEST_GROUP_C_WRAPPER(ConnectTests, ConnectDisconnectConnect) -/* B:27 - Connect attempt, Clean session, Subscribe */ -TEST_GROUP_C_WRAPPER(ConnectTests, cleanSessionInitSubscribers) -/* B:28 - Connect attempt, power cycle with clean session false */ -TEST_GROUP_C_WRAPPER(ConnectTests, PowerCycleWithCleanSessionFalse) diff --git a/tests/unit/src/aws_iot_tests_unit_connect_helper.c b/tests/unit/src/aws_iot_tests_unit_connect_helper.c deleted file mode 100644 index 0aea77ceb0..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_connect_helper.c +++ /dev/null @@ -1,720 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_connect_helper.c - * @brief IoT Client Unit Testing - Connect API Tests Helper - */ - -#include -#include -#include -#include -#include - -#include "aws_iot_tests_unit_mock_tls_params.h" -#include "aws_iot_tests_unit_helper_functions.h" - -#include "aws_iot_log.h" - -static bool unitTestIsMqttConnected = false; - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static AWS_IoT_Client iotClient; - -static IoT_Publish_Message_Params testPubMsgParams; -static ConnectBufferProofread prfrdParams; - -static char subTopic1[12] = "sdk/Topic1"; -static char subTopic2[12] = "sdk/Topic2"; - -#define NO_MSG_XXXX "XXXX" -static char CallbackMsgStringclean[100] = NO_MSG_XXXX; - -static void iot_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - char *tmp = params->payload; - unsigned int i; - - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgStringclean[i] = tmp[i]; - } -} - -TEST_GROUP_C_SETUP(ConnectTests) { - IoT_Error_t rc = SUCCESS; - unitTestIsMqttConnected = false; - rc = aws_iot_mqtt_disconnect(&iotClient); - ResetTLSBuffer(); - ResetInvalidParameters(); -} - -TEST_GROUP_C_TEARDOWN(ConnectTests) { - /* Clean up. Not checking return code here because this is common to all tests. - * A test might have already caused a disconnect by this point. - */ - IoT_Error_t rc = aws_iot_mqtt_disconnect(&iotClient); - IOT_UNUSED(rc); -} - -/* B:1 - Init with Null/empty client instance */ -TEST_C(ConnectTests, NullClientInit) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:1 - Init with Null/empty client instance \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(NULL, &initParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - B:1 - Init with Null/empty client instance \n"); -} - -/* B:2 - Connect with Null/empty client instance */ -TEST_C(ConnectTests, NullClientConnect) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:2 - Connect with Null/empty client instance \n"); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(NULL, &connectParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - B:2 - Connect with Null/empty client instance \n"); -} - -/* B:3 - Connect with Null/Empty endpoint */ -TEST_C(ConnectTests, NullHost) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:3 - Connect with Null/Empty endpoint \n"); - - InitMQTTParamsSetup(&initParams, NULL, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:3 - Connect with Null/Empty endpoint \n"); -} - -/* B:4 - Connect with Null/Empty port */ -TEST_C(ConnectTests, NullPort) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:4 - Connect with Null/Empty port \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, 0, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:4 - Connect with Null/Empty port \n"); -} - -/* B:5 - Connect with Null/Empty root CA path */ -TEST_C(ConnectTests, NullRootCAPath) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:6 - Connect with Null/Empty root CA path \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.pRootCALocation = NULL; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - B:6 - Connect with Null/Empty root CA path \n"); -} - -/* B:6 - Connect with Null/Empty Client certificate path */ -TEST_C(ConnectTests, NullClientCertificate) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:7 - Connect with Null/Empty Client certificate path \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.pDeviceCertLocation = NULL; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - B:7 - Connect with Null/Empty Client certificate path \n"); -} - -/* B:7 - Connect with Null/Empty private key Path */ -TEST_C(ConnectTests, NullPrivateKeyPath) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:8 - Connect with Null/Empty private key Path \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.pDevicePrivateKeyLocation = NULL; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - B:8 - Connect with Null/Empty private key Path \n"); -} - -/* B:8 - Connect with Null/Empty client ID */ -TEST_C(ConnectTests, NullClientID) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:8 - Connect with Null/Empty client ID \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - setTLSRxBufferForConnack(&connectParams, 0, 0); - - /* If no client id is passed but a length was passed, return error */ - ConnectMQTTParamsSetup(&connectParams, NULL, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - /* If client id is passed but 0 length was passed, return error */ - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - /* If client id is NULL and length is 0 then request succeeds */ - ConnectMQTTParamsSetup(&connectParams, NULL, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - B:8 - Connect with Null/Empty client ID \n"); -} - -/* B:9 - Connect with invalid Endpoint */ -TEST_C(ConnectTests, InvalidEndpoint) { - IoT_Error_t rc = SUCCESS; - char invalidEndPoint[20]; - snprintf(invalidEndPoint, 20, "invalid"); - - IOT_DEBUG("-->Running Connect Tests - B:9 - Connect with invalid Endpoint \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - invalidEndpointFilter = invalidEndPoint; - initParams.pHostURL = invalidEndpointFilter; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:9 - Connect with invalid Endpoint \n"); -} - -/* B:10 - Connect with invalid correct endpoint but invalid port */ -TEST_C(ConnectTests, InvalidPort) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:10 - Connect with invalid correct endpoint but invalid port \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - invalidPortFilter = 1234; - initParams.port = invalidPortFilter; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:10 - Connect with invalid correct endpoint but invalid port \n"); -} - -/* B:11 - Connect with invalid Root CA path */ -TEST_C(ConnectTests, InvalidRootCAPath) { - IoT_Error_t rc = SUCCESS; - char invalidRootCAPath[20]; - snprintf(invalidRootCAPath, 20, "invalid"); - - IOT_DEBUG("-->Running Connect Tests - B:11 - Connect with invalid Root CA path \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - invalidRootCAPathFilter = invalidRootCAPath; - initParams.pRootCALocation = invalidRootCAPathFilter; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:11 - Connect with invalid Root CA path \n"); -} - -/* B:12 - Connect with invalid Client certificate path */ -TEST_C(ConnectTests, InvalidClientCertPath) { - IoT_Error_t rc = SUCCESS; - char invalidCertPath[20]; - snprintf(invalidCertPath, 20, "invalid"); - - IOT_DEBUG("-->Running Connect Tests - B:12 - Connect with invalid Client certificate path \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - invalidCertPathFilter = invalidCertPath; - initParams.pDeviceCertLocation = invalidCertPathFilter; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:12 - Connect with invalid Client certificate path \n"); -} - -/* B:13 - Connect with invalid private key path */ -TEST_C(ConnectTests, InvalidPrivateKeyPath) { - IoT_Error_t rc = SUCCESS; - char invalidPrivKeyPath[20]; - snprintf(invalidPrivKeyPath, 20, "invalid"); - - IOT_DEBUG("-->Running Connect Tests - B:13 - Connect with invalid private key path \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - invalidPrivKeyPathFilter = invalidPrivKeyPath; - initParams.pDevicePrivateKeyLocation = invalidPrivKeyPathFilter; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:13 - Connect with invalid private key path \n"); -} - -/* B:14 - Connect, no response timeout */ -TEST_C(ConnectTests, NoResponseTimeout) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:14 - Connect, no response timeout \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - - IOT_DEBUG("-->Success - B:14 - Connect, no response timeout \n"); -} - -/* B:15 - Connect, connack malformed, too large */ -TEST_C(ConnectTests, ConnackTooLarge) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:15 - Connect, connack malformed, too large \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - RxBuffer.pBuffer[1] = (char) (0x15); /* Set remaining length to a larger than expected value */ - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:15 - Connect, connack malformed, too large \n"); -} - -/* B:16 - Connect, connack malformed, fixed header corrupted */ -TEST_C(ConnectTests, FixedHeaderCorrupted) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:16 - Connect, connack malformed, fixed header corrupted \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - RxBuffer.pBuffer[0] = (char) (0x00); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_C(SUCCESS != rc); - - IOT_DEBUG("-->Success - B:16 - Connect, connack malformed, fixed header corrupted \n"); -} - -/* B:17 - Connect, connack malformed, invalid remaining length */ -TEST_C(ConnectTests, InvalidRemainingLength) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:17 - Connect, connack malformed, invalid remaining length \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - RxBuffer.pBuffer[1] = (char) (0x00); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_DECODE_REMAINING_LENGTH_ERROR, rc); - - IOT_DEBUG("-->Success - B:17 - Connect, connack malformed, invalid remaining length \n"); -} - -/* B:18 - Connect, connack returned error, unacceptable protocol version */ -TEST_C(ConnectTests, UnacceptableProtocolVersion) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:18 - Connect, connack returned error, unacceptable protocol version \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.MQTTVersion = 7; - setTLSRxBufferForConnack(&connectParams, 0, 1); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_CONNACK_UNACCEPTABLE_PROTOCOL_VERSION_ERROR, rc); - - IOT_DEBUG("-->Success - B:18 - Connect, connack returned error, unacceptable protocol version \n"); -} - -/* B:19 - Connect, connack returned error, identifier rejected */ -TEST_C(ConnectTests, IndentifierRejected) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:19 - Connect, connack returned error, identifier rejected \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 2); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_CONNACK_IDENTIFIER_REJECTED_ERROR, rc); - - IOT_DEBUG("-->Success - B:19 - Connect, connack returned error, identifier rejected \n"); -} - -/* B:20 - Connect, connack returned error, Server unavailable */ -TEST_C(ConnectTests, ServerUnavailable) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:20 - Connect, connack returned error, Server unavailable \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 3); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_CONNACK_SERVER_UNAVAILABLE_ERROR, rc); - - IOT_DEBUG("-->Success - B:20 - Connect, connack returned error, Server unavailable \n"); -} - -/* B:21 - Connect, connack returned error, bad user name or password */ -TEST_C(ConnectTests, BadUserNameOrPassword) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:21 - Connect, connack returned error, bad user name or password \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 4); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_CONNACK_BAD_USERDATA_ERROR, rc); - - IOT_DEBUG("-->Success - B:21 - Connect, connack returned error, bad user name or password \n"); -} - -/* B:22 - Connect, connack returned error, not authorized */ -TEST_C(ConnectTests, NotAuthorized) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("\n-->Running Connect Tests - B:22 - Connect, connack returned error, not authorized \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 5); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(MQTT_CONNACK_NOT_AUTHORIZED_ERROR, rc); - - IOT_DEBUG("\n-->Success - B:22 - Connect, connack returned error, not authorized \n"); -} - -/* B:23 - Connect, connack return after half command timeout delay, success */ -TEST_C(ConnectTests, SuccessAfterDelayedConnack) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:23 - Connect, connack return after half command timeout delay, success \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - setTLSRxBufferDelay(0, (int) initParams.mqttCommandTimeout_ms/2); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - B:23 - Connect, connack return after half command timeout delay, success \n"); -} - -/* B:24 - Connect, connack returned success */ -TEST_C(ConnectTests, ConnectSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:24 - Connect, connack returned success \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - B:24 - Connect, connack returned success \n"); -} - -/* B:25 - Connect, flag settings and parameters are recorded in buffer */ -TEST_C(ConnectTests, FlagSettingsAndParamsAreRecordedIntoBuf) { - IoT_Error_t rc = SUCCESS; - unsigned char *currPayload = NULL; - - IOT_DEBUG("-->Running Connect Tests - B:25 - Connect, flag settings and parameters are recorded in buffer \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - ConnectMQTTParamsSetup_Detailed(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID), - QOS1, false, true, "willTopicName", (uint16_t) strlen("willTopicName"), "willMsg", - (uint16_t) strlen("willMsg"), NULL, 0, NULL, 0); - connectParams.keepAliveIntervalInSec = (1 << 16) - 1; - setTLSRxBufferForConnack(&connectParams, 0, 0); - - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - currPayload = connectTxBufferHeaderParser(&prfrdParams, TxBuffer.pBuffer); - CHECK_C(true == isConnectTxBufFlagCorrect(&connectParams, &prfrdParams)); - CHECK_C(true == isConnectTxBufPayloadCorrect(&connectParams, currPayload)); - - IOT_DEBUG("-->Success - B:25 - Connect, flag settings and parameters are recorded in buffer \n"); -} - -/* B:26 - Connect attempt, Disconnect, Manually reconnect */ -TEST_C(ConnectTests, ConnectDisconnectConnect) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Connect Tests - B:26 - Connect attempt, Disconnect, Manually reconnect \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - - // connect - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - // check the is_connected call - unitTestIsMqttConnected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(true, unitTestIsMqttConnected); - - // disconnect - rc = aws_iot_mqtt_disconnect(&iotClient); - CHECK_EQUAL_C_INT(SUCCESS, rc); - // check the is_connected call - unitTestIsMqttConnected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(false, unitTestIsMqttConnected); - - ResetTLSBuffer(); - setTLSRxBufferForConnack(&connectParams, 0, 0); - - // connect - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - // check the is_connected call - unitTestIsMqttConnected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(true, unitTestIsMqttConnected); - - IOT_DEBUG("-->Success - B:26 - Connect attempt, Disconnect, Manually reconnect \n"); -} - -/* B:27 - Connect attempt, Clean session, Subscribe - * connect with clean session true and subscribe to a topic1, set msg to topic1 and ensure it is received - * connect cs false, set msg to topic1 and nothing should come in, Sub to topic2 and check if msg is received - * connect cs false and send msg to topic2 and should be received - * cs true and everything should be clean again - */ -TEST_C(ConnectTests, cleanSessionInitSubscribers) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[] = "msg topic"; - - IOT_DEBUG("-->Running Connect Tests - B:27 - Connect attempt, Clean session, Subscribe \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - ResetTLSBuffer(); - - //1. connect with clean session true and - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.isCleanSession = true; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - //1. subscribe to a topic1 and - testPubMsgParams.payload = expectedCallbackString; - testPubMsgParams.payloadLen = (uint16_t) strlen(expectedCallbackString); - setTLSRxBufferForSuback(subTopic1, strlen(subTopic1), QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic1, (uint16_t) strlen(subTopic1), QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - //1. receive message - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic1, strlen(subTopic1), QOS0, testPubMsgParams, - expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgStringclean); - - ResetTLSBuffer(); - rc = aws_iot_mqtt_disconnect(&iotClient); - - //2. connect cs false and - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.isCleanSession = false; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - //3. set msg to topic1 and should receive the topic1 message - snprintf(CallbackMsgStringclean, 100, NO_MSG_XXXX); - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic1, strlen(subTopic1), QOS0, testPubMsgParams, - expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgStringclean); - - ResetTLSBuffer(); - //4. ,sub to topic2 - snprintf(CallbackMsgStringclean, 100, NO_MSG_XXXX); - setTLSRxBufferForSuback(subTopic1, strlen(subTopic1), QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic2, (uint16_t) strlen(subTopic2), QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - //5. and check if topic 2 msg is received - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic2, strlen(subTopic2), QOS0, testPubMsgParams, - expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgStringclean); - - rc = aws_iot_mqtt_disconnect(&iotClient); - - //6. connect cs false and - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.isCleanSession = false; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - //7. set msg to topic2 and - snprintf(CallbackMsgStringclean, 100, NO_MSG_XXXX); - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic2, strlen(subTopic2), QOS0, testPubMsgParams, - expectedCallbackString); - //8. should be received - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgStringclean); - - IOT_DEBUG("-->Success - B:27 - Connect attempt, Clean session, Subscribe \n"); - -} - -/* B:28 - Connect attempt, power cycle with clean session false - * This test is to ensure we can initialize the subscribe table in mqtt even when connecting with CS = false - * currently the AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS is set to 5 - */ -TEST_C(ConnectTests, PowerCycleWithCleanSessionFalse) { - IoT_Error_t rc = SUCCESS; - int itr = 0; - char subTestTopic[12]; - uint16_t subTestTopicLen = 0; - - IOT_DEBUG("-->Running Connect Tests - B:28 - Connect attempt, power cycle with clean session false \n"); - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - ResetTLSBuffer(); - - //1. connect with clean session false and - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.isCleanSession = true; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - //2. subscribe to max number of topics - for(itr = 0; itr < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; itr++) { - snprintf(subTestTopic, 12, "sdk/topic%d", itr + 1); - subTestTopicLen = (uint16_t) strlen(subTestTopic); - setTLSRxBufferForSuback(subTestTopic, subTestTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTestTopic, subTestTopicLen, QOS0, iot_subscribe_callback_handler, - NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - //3. Subscribe to one more topic. Should return error - snprintf(subTestTopic, 12, "sdk/topic%d", itr + 1); - subTestTopicLen = (uint16_t) strlen(subTestTopic); - setTLSRxBufferForSuback(subTestTopic, subTestTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTestTopic, subTestTopicLen, QOS0, iot_subscribe_callback_handler, - NULL); - CHECK_EQUAL_C_INT(MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR, rc); - - IOT_DEBUG("-->Success - B:28 - Connect attempt, power cycle with clean session false \n"); -} diff --git a/tests/unit/src/aws_iot_tests_unit_disconnect.cpp b/tests/unit/src/aws_iot_tests_unit_disconnect.cpp deleted file mode 100644 index 09edca39b7..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_disconnect.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_disconnect.cpp - * @brief IoT Client Unit Testing - Disconnect API Tests - */ - -#include -#include - -TEST_GROUP_C(DisconnectTests){ - TEST_GROUP_C_SETUP_WRAPPER(DisconnectTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(DisconnectTests) -}; - -/* F:1 - Disconnect with Null/empty client instance */ -TEST_GROUP_C_WRAPPER(DisconnectTests, NullClientDisconnect) -/* F:2 - Set Disconnect Handler with Null/empty Client */ -TEST_GROUP_C_WRAPPER(DisconnectTests, NullClientSetDisconnectHandler) -/* F:3 - Call Set Disconnect handler with Null handler */ -TEST_GROUP_C_WRAPPER(DisconnectTests, SetDisconnectHandlerNullHandler) -/* F:4 - Disconnect attempt, not connected */ -TEST_GROUP_C_WRAPPER(DisconnectTests, disconnectNotConnected) -/* F:5 - Disconnect success */ -TEST_GROUP_C_WRAPPER(DisconnectTests, disconnectNoAckSuccess) -/* F:6 - Disconnect, Handler invoked on disconnect */ -TEST_GROUP_C_WRAPPER(DisconnectTests, HandlerInvokedOnDisconnect) -/* F:7 - Disconnect, with set handler and invoked on disconnect */ -TEST_GROUP_C_WRAPPER(DisconnectTests, SetHandlerAndInvokedOnDisconnect) diff --git a/tests/unit/src/aws_iot_tests_unit_disconnect_helper.c b/tests/unit/src/aws_iot_tests_unit_disconnect_helper.c deleted file mode 100644 index 681862b437..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_disconnect_helper.c +++ /dev/null @@ -1,244 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_disconnect_helper.c - * @brief IoT Client Unit Testing - Disconnect Tests helper - */ - -#include -#include -#include - -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_log.h" - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static AWS_IoT_Client iotClient; - -static bool handlerInvoked = false; - -void disconnectTestHandler(AWS_IoT_Client *pClient, void *disconHandlerParam) { - IOT_UNUSED(pClient); - IOT_UNUSED(disconHandlerParam); - - handlerInvoked = true; -} - -TEST_GROUP_C_SETUP(DisconnectTests) { - IoT_Error_t rc; - ResetTLSBuffer(); - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, true, disconnectTestHandler); - initParams.mqttCommandTimeout_ms = 2000; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.keepAliveIntervalInSec = 5; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - IOT_DEBUG("MQTT Status State : %d, RC : %d\n\n", aws_iot_mqtt_get_client_state(&iotClient), rc); - - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(DisconnectTests) { } - - -/* F:1 - Disconnect with Null/empty client instance */ -TEST_C(DisconnectTests, NullClientDisconnect) { - IoT_Error_t rc = aws_iot_mqtt_disconnect(NULL); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* F:2 - Set Disconnect Handler with Null/empty Client */ -TEST_C(DisconnectTests, NullClientSetDisconnectHandler) { - IoT_Error_t rc = aws_iot_mqtt_set_disconnect_handler(NULL, disconnectTestHandler, NULL); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* F:3 - Call Set Disconnect handler with Null handler */ -TEST_C(DisconnectTests, SetDisconnectHandlerNullHandler) { - IoT_Error_t rc = aws_iot_mqtt_set_disconnect_handler(&iotClient, NULL, NULL); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* F:4 - Disconnect attempt, not connected */ -TEST_C(DisconnectTests, disconnectNotConnected) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Disconnect Tests - F:4 - Disconnect attempt, not connected \n"); - - /* First make sure client is disconnected */ - rc = aws_iot_mqtt_disconnect(&iotClient); - - /* Check client is disconnected */ - CHECK_EQUAL_C_INT(false, aws_iot_mqtt_is_client_connected(&iotClient)); - - /* Now call disconnect again */ - rc = aws_iot_mqtt_disconnect(&iotClient); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); - - IOT_DEBUG("-->Success - F:4 - Disconnect attempt, not connected \n"); -} - -/* F:5 - Disconnect success */ -TEST_C(DisconnectTests, disconnectNoAckSuccess) { - IoT_Error_t rc = SUCCESS; - rc = aws_iot_mqtt_disconnect(&iotClient); - CHECK_EQUAL_C_INT(SUCCESS, rc); -} - -/* F:6 - Disconnect, Handler invoked on disconnect */ -TEST_C(DisconnectTests, HandlerInvokedOnDisconnect) { - bool connected = false; - bool currentAutoReconnectStatus = false; - int i; - int j; - int attempt = 3; - uint32_t dcCount = 0; - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Disconnect Tests - F:6 - Disconnect, Handler invoked on disconnect \n"); - - handlerInvoked = false; - - IOT_DEBUG("Current Keep Alive Interval is set to %d sec.\n", connectParams.keepAliveIntervalInSec); - currentAutoReconnectStatus = aws_iot_is_autoreconnect_enabled(&iotClient); - - connected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(1, connected); - - aws_iot_mqtt_autoreconnect_set_status(&iotClient, false); - - // 3 cycles of half keep alive time expiring - // verify a ping request is sent and give a ping response - for(i = 0; i < attempt; i++) { - /* Set TLS buffer for ping response */ - ResetTLSBuffer(); - setTLSRxBufferForPingresp(); - for(j = 0; j <= connectParams.keepAliveIntervalInSec; j++) { - sleep(1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePingreq()); - } - - // keepalive() waits for 1/2 of keepalive time after sending ping request - // to receive a pingresponse before determining the connection is not alive - // wait for keepalive time and then yield() - sleep(connectParams.keepAliveIntervalInSec); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessageDisconnect()); - - connected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(0, connected); - - CHECK_EQUAL_C_INT(true, handlerInvoked); - - dcCount = aws_iot_mqtt_get_network_disconnected_count(&iotClient); - CHECK_C(1 == dcCount); - - aws_iot_mqtt_reset_network_disconnected_count(&iotClient); - - dcCount = aws_iot_mqtt_get_network_disconnected_count(&iotClient); - CHECK_C(0 == dcCount); - - ResetTLSBuffer(); - aws_iot_mqtt_autoreconnect_set_status(&iotClient, currentAutoReconnectStatus); - - IOT_DEBUG("-->Success - F:6 - Disconnect, Handler invoked on disconnect \n"); -} - - -/* F:7 - Disconnect, with set handler and invoked on disconnect */ -TEST_C(DisconnectTests, SetHandlerAndInvokedOnDisconnect) { - bool connected = false; - bool currentAutoReconnectStatus = false; - int i; - int j; - int attempt = 3; - uint32_t dcCount = 0; - IoT_Error_t rc = SUCCESS; - IOT_DEBUG("-->Running Disconnect Tests - F:7 - Disconnect, with set handler and invoked on disconnect \n"); - - handlerInvoked = false; - InitMQTTParamsSetup(&initParams, "localhost", AWS_IOT_MQTT_PORT, false, NULL); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - connectParams.keepAliveIntervalInSec = 5; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - aws_iot_mqtt_set_disconnect_handler(&iotClient, disconnectTestHandler, NULL); - aws_iot_mqtt_autoreconnect_set_status(&iotClient, true); - - IOT_DEBUG("Current Keep Alive Interval is set to %d sec.\n", connectParams.keepAliveIntervalInSec); - currentAutoReconnectStatus = aws_iot_is_autoreconnect_enabled(&iotClient); - - connected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(1, connected); - - aws_iot_mqtt_autoreconnect_set_status(&iotClient, false); - - // 3 cycles of keep alive time expiring - // verify a ping request is sent and give a ping response - for(i = 0; i < attempt; i++) { - /* Set TLS buffer for ping response */ - ResetTLSBuffer(); - setTLSRxBufferForPingresp(); - for(j = 0; j <= connectParams.keepAliveIntervalInSec; j++) { - sleep(1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePingreq()); - } - ResetTLSBuffer(); - - // keepalive() waits for 1/2 of keepalive time after sending ping request - // to receive a pingresponse before determining the connection is not alive - // wait for keepalive time and then yield() - sleep(connectParams.keepAliveIntervalInSec); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessageDisconnect()); - - connected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(0, connected); - - CHECK_EQUAL_C_INT(true, handlerInvoked); - - dcCount = aws_iot_mqtt_get_network_disconnected_count(&iotClient); - CHECK_C(1 == dcCount); - - aws_iot_mqtt_reset_network_disconnected_count(&iotClient); - - dcCount = aws_iot_mqtt_get_network_disconnected_count(&iotClient); - CHECK_C(0 == dcCount); - - ResetTLSBuffer(); - aws_iot_mqtt_autoreconnect_set_status(&iotClient, currentAutoReconnectStatus); - - IOT_DEBUG("-->Success - F:7 - Disconnect, with set handler and invoked on disconnect \n"); -} - diff --git a/tests/unit/src/aws_iot_tests_unit_helper_functions.c b/tests/unit/src/aws_iot_tests_unit_helper_functions.c deleted file mode 100644 index 428bc29374..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_helper_functions.c +++ /dev/null @@ -1,563 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_helper_functions.c - * @brief IoT Client Unit Testing - Helper Functions - */ - -#include -#include -#include "aws_iot_mqtt_client.h" -#include "aws_iot_tests_unit_mock_tls_params.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_version.h" - -#if !DISABLE_METRICS -#define SDK_METRICS_LEN 25 -#define SDK_METRICS_TEMPLATE "?SDK=C&Version=%d.%d.%d" -static char pUsernameTemp[SDK_METRICS_LEN] = {0}; -#endif - -#define CONNACK_SUBACK_PACKET_SIZE 9 -#define PUBACK_PACKET_SIZE 4 -#define SUBACK_PACKET_SIZE 5 -#define UNSUBACK_PACKET_SIZE 4 -#define PINGRESP_PACKET_SIZE 2 - -void ResetInvalidParameters(void) { - invalidEndpointFilter = NULL; - invalidRootCAPathFilter = NULL; - invalidCertPathFilter = NULL; - invalidPrivKeyPathFilter = NULL; - invalidPortFilter = 0; -} - -void InitMQTTParamsSetup(IoT_Client_Init_Params *params, char *pHost, uint16_t port, bool enableAutoReconnect, - iot_disconnect_handler disconnectHandler) { - params->pHostURL = pHost; - params->port = port; - params->mqttCommandTimeout_ms = 5000; - params->tlsHandshakeTimeout_ms = 5000; - params->enableAutoReconnect = enableAutoReconnect; - params->disconnectHandler = disconnectHandler; - params->disconnectHandlerData = NULL; - params->isSSLHostnameVerify = true; - params->pDeviceCertLocation = AWS_IOT_ROOT_CA_FILENAME; - params->pDevicePrivateKeyLocation = AWS_IOT_CERTIFICATE_FILENAME; - params->pRootCALocation = AWS_IOT_PRIVATE_KEY_FILENAME; -} - -void ConnectMQTTParamsSetup(IoT_Client_Connect_Params *params, char *pClientID, uint16_t clientIDLen) { - params->keepAliveIntervalInSec = 10; - params->isCleanSession = 1; - params->MQTTVersion = MQTT_3_1_1; - params->pClientID = pClientID; - params->clientIDLen = clientIDLen; - params->isWillMsgPresent = false; - params->pUsername = NULL; - params->usernameLen = 0; - params->pPassword = NULL; - params->passwordLen = 0; -} - -void ConnectMQTTParamsSetup_Detailed(IoT_Client_Connect_Params *params, char *pClientID, uint16_t clientIDLen, QoS qos, - bool isCleanSession, bool isWillMsgPresent, char *pWillTopicName, - uint16_t willTopicNameLen, char *pWillMessage, uint16_t willMsgLen, - char *pUsername, uint16_t userNameLen, char *pPassword, uint16_t passwordLen) { - params->keepAliveIntervalInSec = 10; - params->isCleanSession = isCleanSession; - params->MQTTVersion = MQTT_3_1_1; - params->pClientID = pClientID; - params->clientIDLen = clientIDLen; - params->pUsername = pUsername; - params->usernameLen = userNameLen; - params->pPassword = pPassword; - params->passwordLen = passwordLen; - params->isWillMsgPresent = isWillMsgPresent; - params->will.pMessage = pWillMessage; - params->will.msgLen = willMsgLen; - params->will.pTopicName = pWillTopicName; - params->will.topicNameLen = willTopicNameLen; - params->will.qos = qos; - params->will.isRetained = false; -} - -void printBuffer(unsigned char *buffer, size_t len) { - size_t i; - printf("\n--\n"); - for(i = 0; i < len; i++) { - printf("%d: %c, %d\n", (uint32_t)i, buffer[i], buffer[i]); - } - printf("\n--\n"); -} - -#define CONNACK_PACKET_SIZE 4 - -void setTLSRxBufferForConnack(IoT_Client_Connect_Params *params, unsigned char sessionPresent, - unsigned char connackResponseCode) { - RxBuffer.NoMsgFlag = false; - - if(params->isCleanSession) { - sessionPresent = 0; - } - - RxBuffer.pBuffer[0] = (unsigned char) (0x20); - RxBuffer.pBuffer[1] = (unsigned char) (0x02); - RxBuffer.pBuffer[2] = sessionPresent; - RxBuffer.pBuffer[3] = connackResponseCode; - - RxBuffer.len = CONNACK_PACKET_SIZE; - RxIndex = 0; -} - -void setTLSRxBufferForConnackAndSuback(IoT_Client_Connect_Params *conParams, unsigned char sessionPresent, - char *topicName, size_t topicNameLen, QoS qos) { - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - - RxBuffer.NoMsgFlag = false; - - if(conParams->isCleanSession) { - sessionPresent = 0; - } - - RxBuffer.pBuffer[0] = (unsigned char) (0x20); - RxBuffer.pBuffer[1] = (unsigned char) (0x02); - RxBuffer.pBuffer[2] = sessionPresent; - RxBuffer.pBuffer[3] = (unsigned char) (0x0); - - RxBuffer.pBuffer[4] = (unsigned char) (0x90); - RxBuffer.pBuffer[5] = (unsigned char) (0x2 + 1); - // Variable header - packet identifier - RxBuffer.pBuffer[6] = (unsigned char) (2); - RxBuffer.pBuffer[7] = (unsigned char) (0); - // payload - RxBuffer.pBuffer[8] = (unsigned char) (qos); - - RxBuffer.len = CONNACK_SUBACK_PACKET_SIZE; - RxIndex = 0; -} - -void setTLSRxBufferForPuback(void) { - size_t i; - - RxBuffer.NoMsgFlag = true; - RxBuffer.len = PUBACK_PACKET_SIZE; - RxIndex = 0; - - for(i = 0; i < RxBuffer.BufMaxSize; i++) { - RxBuffer.pBuffer[i] = 0; - } - - RxBuffer.pBuffer[0] = (unsigned char) (0x40); - RxBuffer.pBuffer[1] = (unsigned char) (0x02); - RxBuffer.pBuffer[2] = (unsigned char) (0x02); - RxBuffer.pBuffer[3] = (unsigned char) (0x00); - RxBuffer.NoMsgFlag = false; -} - -void setTLSRxBufferForSubFail(void) { - RxBuffer.NoMsgFlag = false; - RxBuffer.pBuffer[0] = (unsigned char) (0x90); - RxBuffer.pBuffer[1] = (unsigned char) (0x2 + 1); - // Variable header - packet identifier - RxBuffer.pBuffer[2] = (unsigned char) (2); - RxBuffer.pBuffer[3] = (unsigned char) (0); - // payload - RxBuffer.pBuffer[4] = (unsigned char) (128); - - RxBuffer.len = SUBACK_PACKET_SIZE; - RxIndex = 0; -} - -void setTLSRxBufferForDoubleSuback(char *topicName, size_t topicNameLen, QoS qos, IoT_Publish_Message_Params params) { - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(params); - - RxBuffer.NoMsgFlag = false; - RxBuffer.pBuffer[0] = (unsigned char) (0x90); - RxBuffer.pBuffer[1] = (unsigned char) (0x2 + 1); - // Variable header - packet identifier - RxBuffer.pBuffer[2] = (unsigned char) (2); - RxBuffer.pBuffer[3] = (unsigned char) (0); - // payload - RxBuffer.pBuffer[4] = (unsigned char) (qos); - - RxBuffer.pBuffer[5] = (unsigned char) (0x90); - RxBuffer.pBuffer[6] = (unsigned char) (0x2 + 1); - // Variable header - packet identifier - RxBuffer.pBuffer[7] = (unsigned char) (2); - RxBuffer.pBuffer[8] = (unsigned char) (0); - // payload - RxBuffer.pBuffer[9] = (unsigned char) (qos); - - RxBuffer.len = SUBACK_PACKET_SIZE * 2; - RxIndex = 0; -} - -void setTLSRxBufferForSuback(char *topicName, size_t topicNameLen, QoS qos, IoT_Publish_Message_Params params) { - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(params); - - RxBuffer.NoMsgFlag = false; - RxBuffer.pBuffer[0] = (unsigned char) (0x90); - RxBuffer.pBuffer[1] = (unsigned char) (0x2 + 1); - // Variable header - packet identifier - RxBuffer.pBuffer[2] = (unsigned char) (2); - RxBuffer.pBuffer[3] = (unsigned char) (0); - // payload - RxBuffer.pBuffer[4] = (unsigned char) (qos); - - RxBuffer.len = SUBACK_PACKET_SIZE; - RxIndex = 0; -} - -void setTLSRxBufferForUnsuback(void) { - RxBuffer.NoMsgFlag = false; - RxBuffer.pBuffer[0] = (unsigned char) (0xB0); - RxBuffer.pBuffer[1] = (unsigned char) (0x02); - // Variable header - packet identifier - RxBuffer.pBuffer[2] = (unsigned char) (2); - RxBuffer.pBuffer[3] = (unsigned char) (0); - // No payload - RxBuffer.len = UNSUBACK_PACKET_SIZE; - RxIndex = 0; -} - -void setTLSRxBufferForPingresp(void) { - RxBuffer.NoMsgFlag = false; - RxBuffer.pBuffer[0] = (unsigned char) (0xD0); - RxBuffer.pBuffer[1] = (unsigned char) (0x00); - RxBuffer.len = PINGRESP_PACKET_SIZE; - RxIndex = 0; -} - -void ResetTLSBuffer(void) { - size_t i; - RxBuffer.len = 0; - RxBuffer.NoMsgFlag = true; - - for(i = 0; i < RxBuffer.BufMaxSize; i++) { - RxBuffer.pBuffer[i] = 0; - } - - RxIndex = 0; - RxBuffer.expiry_time.tv_sec = 0; - RxBuffer.expiry_time.tv_usec = 0; - TxBuffer.len = 0; - for(i = 0; i < TxBuffer.BufMaxSize; i++) { - TxBuffer.pBuffer[i] = 0; - } -} - -void setTLSRxBufferDelay(int seconds, int microseconds) { - struct timeval now, duration, result; - duration.tv_sec = seconds; - duration.tv_usec = microseconds; - - gettimeofday(&now, NULL); - timeradd(&now, &duration, &result); - RxBuffer.expiry_time.tv_sec = result.tv_sec; - RxBuffer.expiry_time.tv_usec = result.tv_usec; -} - -void setTLSRxBufferWithMsgOnSubscribedTopic(char *topicName, size_t topicNameLen, QoS qos, - IoT_Publish_Message_Params params, char *pMsg) { - size_t VariableLen = topicNameLen + 2 + 2; - size_t i = 0, cursor = 0, packetIdStartLoc = 0, payloadStartLoc = 0, VarHeaderStartLoc = 0; - size_t PayloadLen = strlen(pMsg) + 1; - - RxBuffer.NoMsgFlag = false; - RxBuffer.pBuffer[0] = (unsigned char) (0x30 | ((params.qos << 1) & 0xF));// QoS1 - cursor++; // Move the cursor - - // Remaining Length - // Translate the Remaining Length into packet bytes - encodeRemainingLength(RxBuffer.pBuffer, &cursor, VariableLen + PayloadLen); - - VarHeaderStartLoc = cursor - 1; - // Variable header - RxBuffer.pBuffer[VarHeaderStartLoc + 1] = (unsigned char) ((topicNameLen & 0xFF00) >> 8); - RxBuffer.pBuffer[VarHeaderStartLoc + 2] = (unsigned char) (topicNameLen & 0xFF); - for(i = 0; i < topicNameLen; i++) { - RxBuffer.pBuffer[VarHeaderStartLoc + 3 + i] = (unsigned char) topicName[i]; - } - - packetIdStartLoc = VarHeaderStartLoc + topicNameLen + 2; - payloadStartLoc = (packetIdStartLoc + 1); - - if(QOS0 != qos) { - // packet id only for QoS 1 or 2 - RxBuffer.pBuffer[packetIdStartLoc + 1] = 2; - RxBuffer.pBuffer[packetIdStartLoc + 2] = 3; - payloadStartLoc = packetIdStartLoc + 3; - } - - // payload - for(i = 0; i < PayloadLen; i++) { - RxBuffer.pBuffer[payloadStartLoc + i] = (unsigned char) pMsg[i]; - } - - RxBuffer.len = VariableLen + PayloadLen + 2; // 2 for fixed header - RxIndex = 0; - //printBuffer(RxBuffer.pBuffer, RxBuffer.len); -} - -unsigned char isLastTLSTxMessagePuback() { - return (unsigned char) (TxBuffer.pBuffer[0] == 0x40 ? 1 : 0); -} - -unsigned char isLastTLSTxMessagePingreq() { - return (unsigned char) (TxBuffer.pBuffer[0] == 0xC0 ? 1 : 0); -} - -unsigned char isLastTLSTxMessageDisconnect() { - return (unsigned char) (TxBuffer.pBuffer[0] == 0xE0 ? 1 : 0); -} - -unsigned char generateMultipleSubTopics(char *des, int boundary) { - int i; - int currLen = 0; - char *op1 = des; - unsigned char ret = (unsigned char) (des == NULL ? 0 : 1); - while(*op1 != '\0') { - currLen++; - op1++; - } - // Save 1 byte for terminator '\0' - for(i = 0; i < boundary - currLen - 1; i++) { - //printf("%d\n", i); - strcat(des, "a"); - } - return ret; -} - -void encodeRemainingLength(unsigned char *buf, size_t *st, size_t length) { - unsigned char c; - // watch out for the type of length, could be over flow. Limits = 256MB - // No boundary check for 256MB!! - do { - c = (unsigned char) (length % 128); - length /= 128; - if(length > 0) c |= (unsigned char) (0x80); // If there is still another byte following - buf[(*st)++] = c; - } while(length > 0); - // At this point, *st should be the next position for a new part of data in the packet -} - -unsigned char *connectTxBufferHeaderParser(ConnectBufferProofread *params, unsigned char *buf) { - unsigned char *op = buf; - // Get packet type - unsigned char *ele1 = op; - unsigned int multiplier = 1; - int cnt = 0; - unsigned char *ele2; - unsigned int x; - unsigned char *op2; - params->PacketType = *ele1; - op++; - // Get remaining length (length bytes more than 4 bytes are ignored) - params->RemainingLength = 0; - do { - ele2 = op; - params->RemainingLength += ((unsigned int) (*ele2 & (0x7F)) * multiplier); - multiplier *= 128; - cnt++; - op++; - } while((*ele2 & (0x80)) != 0 && cnt < 4); - // At this point, op should be updated to the start address of the next chunk of information - // Get protocol length - params->ProtocolLength = 0; - params->ProtocolLength += (256 * (unsigned int) (*op++)); - params->ProtocolLength += (unsigned int) (*op++); - // Get protocol name - for(x = 0; x < params->ProtocolLength; x++) { - params->ProtocolName[x] = *op; - op++; - } - // Get protocol level - params->ProtocolLevel = (unsigned int) (*op++); - // Get connect flags - params->ConnectFlag = (*op++); - // Get keepalive - op2 = op; - params->KeepAlive = 0; - params->KeepAlive += (256 * (unsigned int) (*op2++)); // get rid of the sign bit - op++; - params->KeepAlive += (unsigned int) (*op2++); - op++; - - return op; -} - -bool isConnectTxBufFlagCorrect(IoT_Client_Connect_Params *settings, ConnectBufferProofread *readRes) { - bool ret = true; - int i; - unsigned char myByte[8]; // Construct our own connect flag byte according to the settings -#if !DISABLE_METRICS - myByte[0] = (unsigned char) (1); // User Name Flag -#else - myByte[0] = (unsigned char) (settings->pUsername == NULL ? 0 : 1); // User Name Flag -#endif - myByte[1] = (unsigned char) (settings->pPassword == NULL ? 0 : 1); // Password Flag - myByte[2] = 0; // Will Retain - // QoS - if(QOS1 == settings->will.qos) { - myByte[3] = 0; - myByte[4] = 1; - } else { // default QoS is QOS0 - myByte[3] = 0; - myByte[4] = 0; - } - // - myByte[5] = (unsigned char) settings->isWillMsgPresent; // Will Flag - myByte[6] = (unsigned char) settings->isCleanSession; // Clean Session - myByte[7] = 0; // Retained - // - for(i = 0; i < 8; i++) { - if(myByte[i] != (unsigned char) (((readRes->ConnectFlag) >> (7 - i)) & 0x01)) { - printf("ex %x ac %x\n", (unsigned char) (((readRes->ConnectFlag) >> (7 - i)) & 0x01) + '0', myByte[i]); - ret = false; - break; - } - } - return ret; -} - -bool isConnectTxBufPayloadCorrect(IoT_Client_Connect_Params *settings, unsigned char *payloadBuf) { - // Construct our own payload according to the settings to see if the real one matches with it - unsigned int ClientIDLen = (unsigned int) strlen(settings->pClientID); - unsigned int WillTopicLen = (unsigned int) strlen(settings->will.pTopicName); - unsigned int WillMsgLen = (unsigned int) strlen(settings->will.pMessage); -#if !DISABLE_METRICS - if (0 == strlen(pUsernameTemp)) { - snprintf(pUsernameTemp, SDK_METRICS_LEN, SDK_METRICS_TEMPLATE, - VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); - } - unsigned int UsernameLen = (unsigned int)strlen(pUsernameTemp); -#else - unsigned int UsernameLen = (unsigned int) settings->usernameLen; -#endif - unsigned int PasswordLen = (unsigned int) settings->passwordLen; - unsigned int myPayloadLen = ClientIDLen + 2 + WillTopicLen + 2 + WillMsgLen + UsernameLen + 2 + PasswordLen; - char *myPayload = (char *) malloc(sizeof(char) * (myPayloadLen + 1)); // reserve 1 byte for '\0' - // Construction starts... - unsigned int i; - bool ret = false; - char *op = myPayload; - *op = (char) (ClientIDLen & 0x0FF00); // MSB There is a writeInt inside paho... MQTTString.lenstring.len != 0 - op++; - *op = (char) (ClientIDLen & 0x00FF); // LSB - op++; - // ClientID - for(i = 0; i < ClientIDLen; i++) { - *op = settings->pClientID[i]; - op++; - } - if(true == settings->isWillMsgPresent) { - // WillTopic - for(i = 0; i < WillTopicLen; i++) { - *op = settings->will.pTopicName[i]; - op++; - } - // WillMsg Len - *op = (char) (WillMsgLen & 0x0FF00); // MSB - op++; - *op = (char) (WillMsgLen & 0x00FF); // LSB - op++; - // WillMsg - for(i = 0; i < WillMsgLen; i++) { - *op = settings->will.pMessage[i]; - op++; - } - } - // Username -#if !DISABLE_METRICS - for(i = 0; i < strlen(pUsernameTemp); i++) - { - *op = pUsernameTemp[i]; - op++; - } -#else - if(NULL != settings->pUsername) { - for(i = 0; i < UsernameLen; i++) { - *op = settings->pUsername[i]; - op++; - } - } -#endif - // PasswordLen + Password - if(NULL != settings->pPassword) { - *op = (char) (PasswordLen & 0x0FF00); // MSB - op++; - *op = (char) (PasswordLen & 0x00FF); // LSB - op++; - // - for(i = 0; i < PasswordLen; i++) { - *op = settings->pPassword[i]; - op++; - } - } - // - *op = '\0'; - ret = strcmp(myPayload, (const char *)payloadBuf) == 0 ? true : false; - free(myPayload); - return ret; -} - -void printPrfrdParams(ConnectBufferProofread *params) { - unsigned int i; - printf("\n----------------\n"); - printf("PacketType: %x\n", params->PacketType); - printf("RemainingLength: %u\n", params->RemainingLength); - printf("ProtocolLength: %u\n", params->ProtocolLength); - printf("ProtocolName: "); - for(i = 0; i < params->ProtocolLength; i++) { - printf("%c", params->ProtocolName[i]); - } - printf("\n"); - printf("ProtocolLevel: %u\n", params->ProtocolLevel); - printf("ConnectFlag: %x\n", params->ConnectFlag); - printf("KeepAliveInterval: %u\n", params->KeepAlive); - printf("----------------\n"); -} - -const char *getJobTopicTypeName(AwsIotJobExecutionTopicType topicType) { - switch (topicType) { - case JOB_UPDATE_TOPIC: return "JOB_UPDATE_TOPIC"; - case JOB_NOTIFY_TOPIC: return "JOB_NOTIFY_TOPIC"; - case JOB_NOTIFY_NEXT_TOPIC: return "JOB_NOTIFY_NEXT_TOPIC"; - case JOB_GET_PENDING_TOPIC: return "JOB_GET_PENDING_TOPIC"; - case JOB_DESCRIBE_TOPIC: return "JOB_DESCRIBE_TOPIC"; - case JOB_START_NEXT_TOPIC: return "JOB_START_NEXT_TOPIC"; - case JOB_WILDCARD_TOPIC: return "JOB_WILDCARD_TOPIC"; - case JOB_UNRECOGNIZED_TOPIC: return "JOB_UNRECOGNIZED_TOPIC"; - default: return "invalid"; - } -} - -const char *getJobReplyTypeName(AwsIotJobExecutionTopicReplyType replyType) { - switch (replyType) { - case JOB_REQUEST_TYPE: return "JOB_REQUEST_TYPE"; - case JOB_ACCEPTED_REPLY_TYPE: return "JOB_ACCEPTED_REPLY_TYPE"; - case JOB_REJECTED_REPLY_TYPE: return "JOB_REJECTED_REPLY_TYPE"; - case JOB_WILDCARD_REPLY_TYPE: return "JOB_WILDCARD_REPLY_TYPE"; - case JOB_UNRECOGNIZED_TOPIC_TYPE: return "JOB_UNRECOGNIZED_TOPIC_TYPE"; - default: return "invalid"; - } -} diff --git a/tests/unit/src/aws_iot_tests_unit_jobs.cpp b/tests/unit/src/aws_iot_tests_unit_jobs.cpp deleted file mode 100644 index edbf8cee4e..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_jobs.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#include -#include - - -TEST_GROUP_C(JobsTypesTests) { - TEST_GROUP_C_SETUP_WRAPPER(JobsTypesTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(JobsTypesTests) -}; - -TEST_GROUP_C_WRAPPER(JobsTypesTests, StringToStatus) -TEST_GROUP_C_WRAPPER(JobsTypesTests, StatusToString) - -TEST_GROUP_C(JobsJsonTests) { - TEST_GROUP_C_SETUP_WRAPPER(JobsJsonTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(JobsJsonTests) -}; - -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeUpdateRequest) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeUpdateRequestWithNullBuffer) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeUpdateRequestWithTooSmallBuffer) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeDescribeJobExecutionRequest) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeDescribeJobExecutionRequestWithNullBuffer) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeDescribeJobExecutionRequestWithTooSmallBuffer) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeStartNextRequest) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeStartNextRequestWithNullBuffer) -TEST_GROUP_C_WRAPPER(JobsJsonTests, SerializeStartNextRequestWithTooSmallBuffer) - -TEST_GROUP_C(JobsTopicsTests) { - TEST_GROUP_C_SETUP_WRAPPER(JobsTopicsTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(JobsTopicsTests) -}; - -TEST_GROUP_C_WRAPPER(JobsTopicsTests, GenerateValidTopics) -TEST_GROUP_C_WRAPPER(JobsTopicsTests, GenerateWithMissingJobId) -TEST_GROUP_C_WRAPPER(JobsTopicsTests, GenerateWithInvalidTopicOrReplyType) -TEST_GROUP_C_WRAPPER(JobsTopicsTests, GenerateWithInvalidCombinations) - -TEST_GROUP_C(JobsInterfaceTest) { - TEST_GROUP_C_SETUP_WRAPPER(JobsInterfaceTest) - TEST_GROUP_C_TEARDOWN_WRAPPER(JobsInterfaceTest) -}; - -TEST_GROUP_C_WRAPPER(JobsInterfaceTest, TestSubscribeAndUnsubscribe) -TEST_GROUP_C_WRAPPER(JobsInterfaceTest, TestSendQuery) -TEST_GROUP_C_WRAPPER(JobsInterfaceTest, TestSendUpdate) diff --git a/tests/unit/src/aws_iot_tests_unit_jobs_interface.c b/tests/unit/src/aws_iot_tests_unit_jobs_interface.c deleted file mode 100644 index c19a38d884..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_jobs_interface.c +++ /dev/null @@ -1,337 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#include - -#include "aws_iot_tests_unit_mock_tls_params.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_config.h" -#include -#include -#include - -static AWS_IoT_Client client; -static IoT_Client_Connect_Params connectParams; -static IoT_Client_Init_Params mqttInitParams; - -static const char *THING_NAME = "T1"; -static const char *JOB_ID = "J1"; - -static int CALLBACK_DATA = 5; - -static const char *expectedTopic; -static const void *expectedData; -static size_t expectedDataLen; - -static int callbackCount = 0; - -static bool expectError; -static bool expectedJsonContext; - - -static void testCallback( - AWS_IoT_Client *pClient, - char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) -{ - callbackCount++; - - CHECK_C(pData == &CALLBACK_DATA); - CHECK_C(pClient == &client); - - CHECK_EQUAL_C_INT((int)strlen(expectedTopic), (int)topicNameLen); - CHECK_C(strncmp(expectedTopic, topicName, (size_t) topicNameLen) == 0); - - CHECK_C(memcmp(expectedData, params->payload, expectedDataLen) == 0); -} - -TEST_GROUP_C_SETUP(JobsInterfaceTest) { - IoT_Error_t ret_val = SUCCESS; - - InitMQTTParamsSetup(&mqttInitParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - ret_val = aws_iot_mqtt_init(&client, &mqttInitParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - ConnectMQTTParamsSetup(&connectParams, (char *) AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - ret_val = aws_iot_mqtt_connect(&client, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - ResetTLSBuffer(); - - /* Ensure that old data can't be used */ - lastSubscribeMsgLen = 0; - lastUnsubscribeMsgLen = 0; - lastPublishMessageTopicLen = 0; - lastPublishMessagePayloadLen = 0; - - callbackCount = 0; - expectedTopic = NULL; - expectedData = NULL; - expectedDataLen = 0; - - expectError = false; - expectedJsonContext = false; -} - -TEST_GROUP_C_TEARDOWN(JobsInterfaceTest) { - IoT_Error_t rc = aws_iot_mqtt_disconnect(&client); - IOT_UNUSED(rc); -} - -static void publishTestMessage(AwsIotJobExecutionTopicType topicType, bool withJobId) { - char replyTopic[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - AwsIotJobExecutionTopicReplyType replyType; - char * message; - - if (topicType == JOB_NOTIFY_TOPIC) { - replyType = JOB_REQUEST_TYPE; - message = "{\"jobs\":{\"IN_PROGRESS\":[{}]},\"timestamp\":5}"; - } else if (topicType == JOB_NOTIFY_NEXT_TOPIC) { - replyType = JOB_REQUEST_TYPE; - message = "{\"execution\":{\"jobId\":\"J1\",\"status\":\"IN_PROGRESS\",\"queuedAt\":50,\"versionNumber\":20},\"timestamp\":5}"; - } - else { - replyType = JOB_REJECTED_REPLY_TYPE; - message = "{\"code\":\"foo\",\"message\":\"bar\", \"clientToken\":\"baz\",\"timestamp\":6}"; - } - - const char *passedJobId = NULL; - if (withJobId) { - if (topicType == JOB_WILDCARD_TOPIC) { - topicType = JOB_DESCRIBE_TOPIC; - } - passedJobId = JOB_ID; - } - - int replyTopicLen = aws_iot_jobs_get_api_topic( - replyTopic, MAX_JOB_TOPIC_LENGTH_BYTES + 1, - topicType, replyType, THING_NAME, passedJobId); - - expectedData = message; - expectedDataLen = strlen(message) + 1; - expectedTopic = replyTopic; - - IoT_Publish_Message_Params params; - params.payload = message; - params.payloadLen = strlen(message); - params.qos = QOS0; - - int preSendCount = callbackCount; - - setTLSRxBufferWithMsgOnSubscribedTopic(replyTopic, (size_t)replyTopicLen, QOS0, params, message); - IoT_Error_t error = aws_iot_mqtt_yield(&client, 100); - - CHECK_EQUAL_C_INT(SUCCESS, error); - CHECK_EQUAL_C_INT(preSendCount + 1, callbackCount); -} - -static void testSubscribeAndUnsubscribe(AwsIotJobExecutionTopicType topicType, bool withJobId) { - char expectedTopic[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - char topicBuffer[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - - IOT_DEBUG("\n-->Running Jobs Interface Tests - test subscribe/unsubscribe %s %s \n", getJobTopicTypeName(topicType), (withJobId ? "(w/ job id)" : "")); - - AwsIotJobExecutionTopicReplyType replyType; - if (topicType != JOB_NOTIFY_TOPIC && topicType != JOB_NOTIFY_NEXT_TOPIC) { - replyType = JOB_WILDCARD_REPLY_TYPE; - } else { - replyType = JOB_REQUEST_TYPE; - } - - const char *passedJobId = NULL; - if (withJobId) passedJobId = JOB_ID; - - int expectedTopicLength = aws_iot_jobs_get_api_topic( - expectedTopic, MAX_JOB_TOPIC_LENGTH_BYTES + 1, - topicType, replyType, - THING_NAME, passedJobId); - - CHECK_C(expectedTopicLength > 0 && expectedTopicLength < MAX_JOB_TOPIC_LENGTH_BYTES); - - /* Clear to make sure old data doesn't cause the test to pass when we shouldn't */ - lastSubscribeMsgLen = 0; - lastPublishMessageTopicLen = 0; - - IoT_Publish_Message_Params unused; - - setTLSRxBufferForSuback(expectedTopic, (size_t)expectedTopicLength, QOS0, unused); - - IoT_Error_t iotError; - if (topicType == JOB_WILDCARD_TOPIC && !withJobId) { - iotError = aws_iot_jobs_subscribe_to_all_job_messages( - &client, QOS0, THING_NAME, testCallback, &CALLBACK_DATA, topicBuffer, sizeof(topicBuffer)); - } else { - iotError = aws_iot_jobs_subscribe_to_job_messages( - &client, QOS0, THING_NAME, passedJobId, topicType, replyType, testCallback, &CALLBACK_DATA, topicBuffer, sizeof(topicBuffer)); - } - - CHECK_EQUAL_C_INT(SUCCESS, iotError); - - CHECK_EQUAL_C_INT(expectedTopicLength, (int)strlen(topicBuffer)); - CHECK_EQUAL_C_INT(expectedTopicLength, (int)lastSubscribeMsgLen); - CHECK_EQUAL_C_STRING(expectedTopic, topicBuffer); - CHECK_EQUAL_C_STRING(expectedTopic, LastSubscribeMessage); - - publishTestMessage(topicType, withJobId); - - LastUnsubscribeMessage[0] = 0; - lastUnsubscribeMsgLen = 0; - - setTLSRxBufferForUnsuback(); - iotError = aws_iot_jobs_unsubscribe_from_job_messages(&client, topicBuffer); - - CHECK_EQUAL_C_INT(SUCCESS, iotError); - - CHECK_EQUAL_C_STRING(expectedTopic, LastUnsubscribeMessage); - - IOT_DEBUG("-->Success - test subscribe/unsubscribe %s %s \n", getJobTopicTypeName(topicType), (withJobId ? "(w/ job id)" : "")); -} - -TEST_C(JobsInterfaceTest, TestSubscribeAndUnsubscribe) { - testSubscribeAndUnsubscribe(JOB_NOTIFY_TOPIC, false); - testSubscribeAndUnsubscribe(JOB_NOTIFY_NEXT_TOPIC, false); - testSubscribeAndUnsubscribe(JOB_DESCRIBE_TOPIC, true); - testSubscribeAndUnsubscribe(JOB_GET_PENDING_TOPIC, false); - testSubscribeAndUnsubscribe(JOB_UPDATE_TOPIC, true); - testSubscribeAndUnsubscribe(JOB_WILDCARD_TOPIC, true); - testSubscribeAndUnsubscribe(JOB_WILDCARD_TOPIC, false); - testSubscribeAndUnsubscribe(JOB_START_NEXT_TOPIC, false); -} - -static void testSendQuery(bool withJobId, bool withClientToken, AwsIotJobExecutionTopicType topicType) { - char expectedTopic[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - char topicBuffer[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - char messageBuffer[MAX_SIZE_OF_JOB_REQUEST + 1]; - - IOT_DEBUG("\n-->Running Jobs Interface Tests - test send query %s %s \n", getJobTopicTypeName(topicType), (withClientToken ? "(w/ clientToken)" : "")); - - char *clientToken = NULL; - char *bufferToPass = NULL; - size_t bufferLenToPass = 0; - if (withClientToken) { - clientToken = "FooBar"; - bufferToPass = messageBuffer; - bufferLenToPass = MAX_SIZE_OF_JOB_REQUEST + 1; - } - - const char *passedJobId = NULL; - if (withJobId) passedJobId = JOB_ID; - - int expectedTopicLength = aws_iot_jobs_get_api_topic( - expectedTopic, MAX_JOB_TOPIC_LENGTH_BYTES + 1, - topicType, JOB_REQUEST_TYPE, - THING_NAME, passedJobId); - - CHECK_C(expectedTopicLength > 0 && expectedTopicLength <= MAX_JOB_TOPIC_LENGTH_BYTES); - - LastPublishMessageTopic[0] = 0; - lastPublishMessageTopicLen = 0; - LastPublishMessagePayload[0] = 0; - lastPublishMessagePayloadLen = 0; - - setTLSRxBufferForPuback(); - IoT_Error_t iotError; - - if (topicType == JOB_DESCRIBE_TOPIC) { - AwsIotDescribeJobExecutionRequest request; - request.executionNumber = 0; - request.includeJobDocument = false; - request.clientToken = clientToken; - - iotError = aws_iot_jobs_describe( - &client, QOS0, THING_NAME, passedJobId, &request, topicBuffer, sizeof(topicBuffer), bufferToPass, bufferLenToPass); - } else if (topicType == JOB_START_NEXT_TOPIC) { - AwsIotStartNextPendingJobExecutionRequest request; - request.statusDetails = NULL; - request.clientToken = clientToken; - - iotError = aws_iot_jobs_start_next( - &client, QOS0, THING_NAME, &request, topicBuffer, sizeof(topicBuffer), messageBuffer, sizeof(messageBuffer)); - } else { - iotError = aws_iot_jobs_send_query( - &client, QOS0, THING_NAME, passedJobId, clientToken, topicBuffer, sizeof(topicBuffer), bufferToPass, bufferLenToPass, topicType); - } - - CHECK_EQUAL_C_INT(SUCCESS, iotError); - CHECK_EQUAL_C_INT(expectedTopicLength, (int)lastPublishMessageTopicLen); - CHECK_EQUAL_C_STRING(expectedTopic, LastPublishMessageTopic); - - if (withClientToken) { - CHECK_EQUAL_C_INT((int)strlen(LastPublishMessagePayload), (int)lastPublishMessagePayloadLen); - CHECK_EQUAL_C_STRING("{\"clientToken\":\"FooBar\"}", LastPublishMessagePayload); - } else if (topicType == JOB_START_NEXT_TOPIC) { - CHECK_EQUAL_C_INT(2, (int)lastPublishMessagePayloadLen); - CHECK_EQUAL_C_STRING("{}", LastPublishMessagePayload); - } else { - CHECK_EQUAL_C_INT(0, (int)lastPublishMessagePayloadLen); - } - - IOT_DEBUG("-->Success - test send query %s %s \n", getJobTopicTypeName(topicType), (withClientToken ? "(w/ clientToken)" : "")); -} - -TEST_C(JobsInterfaceTest, TestSendQuery) { - int token; - for (token = 0; token < 2; ++token) { - testSendQuery(false, token == 0, JOB_GET_PENDING_TOPIC); - testSendQuery(true, token == 0, JOB_DESCRIBE_TOPIC); - testSendQuery(false, token == 0, JOB_START_NEXT_TOPIC); - } -} - -TEST_C(JobsInterfaceTest, TestSendUpdate) { - AwsIotJobExecutionUpdateRequest updateRequest; - updateRequest.clientToken = "FooBar"; - updateRequest.status = JOB_EXECUTION_SUCCEEDED; - updateRequest.expectedVersion = 1; - updateRequest.executionNumber = 0; - updateRequest.statusDetails = NULL; - updateRequest.includeJobExecutionState = false; - updateRequest.includeJobDocument = false; - - char expectedTopic[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - char topicBuffer[MAX_JOB_TOPIC_LENGTH_BYTES + 1]; - char messageBuffer[AWS_IOT_MQTT_TX_BUF_LEN + 1]; - - IOT_DEBUG("\n-->Running Jobs Interface Tests - test send update \n"); - - int expectedTopicLength = aws_iot_jobs_get_api_topic( - expectedTopic, MAX_JOB_TOPIC_LENGTH_BYTES + 1, - JOB_UPDATE_TOPIC, JOB_REQUEST_TYPE, - THING_NAME, JOB_ID); - - CHECK_C(expectedTopicLength > 0 && expectedTopicLength <= MAX_JOB_TOPIC_LENGTH_BYTES); - - LastPublishMessageTopic[0] = 0; - lastPublishMessageTopicLen = 0; - LastPublishMessagePayload[0] = 0; - lastPublishMessagePayloadLen = 0; - - setTLSRxBufferForPuback(); - IoT_Error_t iotError; - iotError = aws_iot_jobs_send_update( - &client, QOS0, THING_NAME, JOB_ID, &updateRequest, - topicBuffer, sizeof(topicBuffer), - messageBuffer, sizeof(messageBuffer)); - - CHECK_EQUAL_C_INT(SUCCESS, iotError); - - CHECK_EQUAL_C_INT(expectedTopicLength, (int)lastPublishMessageTopicLen); - CHECK_EQUAL_C_STRING(expectedTopic, LastPublishMessageTopic); - - CHECK_EQUAL_C_STRING("{\"status\":\"SUCCEEDED\",\"expectedVersion\":1,\"clientToken\":\"FooBar\"}", LastPublishMessagePayload); - - IOT_DEBUG("-->Success - test send update \n"); -} diff --git a/tests/unit/src/aws_iot_tests_unit_jobs_json.c b/tests/unit/src/aws_iot_tests_unit_jobs_json.c deleted file mode 100644 index 5a04554e1b..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_jobs_json.c +++ /dev/null @@ -1,244 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define NUM_TOKENS 50 - -static jsmntok_t tokens[NUM_TOKENS]; - -TEST_GROUP_C_SETUP(JobsJsonTests) { -} - -TEST_GROUP_C_TEARDOWN(JobsJsonTests) { -} - -static const char *defaultUpdateRequestSerialized = - "{\"status\":\"IN_PROGRESS\",\"statusDetails\":{\"Step\":\"1\",\"StepStatus\":\"Foo\"}," - "\"executionNumber\":2,\"expectedVersion\":1,\"includeJobExecutionState\":true,\"includeJobDocument\":true,\"clientToken\":\"1234\"}"; - -static void fillDefaultUpdateRequest(AwsIotJobExecutionUpdateRequest *request) -{ - request->status = JOB_EXECUTION_IN_PROGRESS; - request->statusDetails = "{\"Step\":\"1\",\"StepStatus\":\"Foo\"}"; - request->expectedVersion = 1; - request->executionNumber = 2; - request->includeJobExecutionState = true; - request->includeJobDocument = true; - request->clientToken = "1234"; -} - -TEST_C(JobsJsonTests, SerializeUpdateRequest) { - AwsIotJobExecutionUpdateRequest updateRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize update request \n"); - - fillDefaultUpdateRequest(&updateRequest); - - char buffer[256]; - int charsUsed = aws_iot_jobs_json_serialize_update_job_execution_request(buffer, sizeof(buffer), &updateRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultUpdateRequestSerialized), charsUsed); - CHECK_EQUAL_C_STRING(defaultUpdateRequestSerialized, buffer); - - jsmn_parser parser; - jsmn_init(&parser); - - int num_parsed_tokens = jsmn_parse(&parser, buffer, (size_t)charsUsed, tokens, NUM_TOKENS); - CHECK_C(num_parsed_tokens > 0); - - IOT_DEBUG("-->Success - serialize update request \n"); -} - -TEST_C(JobsJsonTests, SerializeUpdateRequestWithNullBuffer) { - AwsIotJobExecutionUpdateRequest updateRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize update request w/ null buffer \n"); - - fillDefaultUpdateRequest(&updateRequest); - - int charsUsed = aws_iot_jobs_json_serialize_update_job_execution_request(NULL, 0, &updateRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultUpdateRequestSerialized), charsUsed); - - IOT_DEBUG("-->Success - serialize update request w/ null buffer \n"); -} - -TEST_C(JobsJsonTests, SerializeUpdateRequestWithTooSmallBuffer) { - AwsIotJobExecutionUpdateRequest updateRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize update request w/ too small buffer \n"); - - fillDefaultUpdateRequest(&updateRequest); - - char buffer[11]; - // A check character which shouldn't be overwritten as we pass a length of 10 - buffer[10] = -1; - - int charsUsed = aws_iot_jobs_json_serialize_update_job_execution_request(buffer, 10, &updateRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultUpdateRequestSerialized), charsUsed); - - CHECK_EQUAL_C_CHAR(-1, buffer[10]); - CHECK_EQUAL_C_CHAR(0, buffer[9]); - // 9 because the 10th is \0 - CHECK_C(strncmp(defaultUpdateRequestSerialized, buffer, 9) == 0); - - IOT_DEBUG("-->Success - serialize update request w/ too small buffer \n"); -} - -static void fillDefaultDescribeJobExecutionRequest( - AwsIotDescribeJobExecutionRequest *request) -{ - request->executionNumber = 1; - request->includeJobDocument = true; - request->clientToken = "1234"; -} - -static const char *defaultDescribeJobExecutionRequestSerialized = - "{\"clientToken\":\"1234\",\"executionNumber\":1,\"includeJobDocument\":true}"; - -TEST_C(JobsJsonTests, SerializeDescribeJobExecutionRequest) { - AwsIotDescribeJobExecutionRequest describeJobExecutionRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize describe job execution request \n"); - - fillDefaultDescribeJobExecutionRequest(&describeJobExecutionRequest); - - char buffer[256]; - int charsUsed = aws_iot_jobs_json_serialize_describe_job_execution_request(buffer, 256, &describeJobExecutionRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultDescribeJobExecutionRequestSerialized), charsUsed); - - CHECK_EQUAL_C_STRING(defaultDescribeJobExecutionRequestSerialized, buffer); - - jsmn_parser parser; - jsmn_init(&parser); - - int num_parsed_tokens = jsmn_parse(&parser, buffer, (size_t)charsUsed, tokens, NUM_TOKENS); - CHECK_C(num_parsed_tokens > 0); - - IOT_DEBUG("-->Success - serialize describe job execution request \n"); -} - -TEST_C(JobsJsonTests, SerializeDescribeJobExecutionRequestWithNullBuffer) { - AwsIotDescribeJobExecutionRequest describeJobExecutionRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize describe job execution request w/ null buffer \n"); - - fillDefaultDescribeJobExecutionRequest(&describeJobExecutionRequest); - - int charsUsed = aws_iot_jobs_json_serialize_describe_job_execution_request(NULL, 0, &describeJobExecutionRequest); - CHECK_EQUAL_C_INT(0, charsUsed); - - IOT_DEBUG("-->Success - serialize describe job execution request w/ null buffer \n"); -} - -TEST_C(JobsJsonTests, SerializeDescribeJobExecutionRequestWithTooSmallBuffer) { - AwsIotDescribeJobExecutionRequest describeJobExecutionRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize describe job execution request w/ too small buffer \n"); - - fillDefaultDescribeJobExecutionRequest(&describeJobExecutionRequest); - - char buffer[11]; - // A check character which shouldn't be overwritten as we pass a length of 10 - buffer[10] = -1; - - int charsUsed = aws_iot_jobs_json_serialize_describe_job_execution_request(buffer, 10, &describeJobExecutionRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultDescribeJobExecutionRequestSerialized), charsUsed); - - CHECK_EQUAL_C_CHAR(-1, buffer[10]); - CHECK_EQUAL_C_CHAR(0, buffer[9]); - // 9 because the 10th is \0 - CHECK_C(strncmp(defaultDescribeJobExecutionRequestSerialized, buffer, 9) == 0); - - IOT_DEBUG("-->Success - serialize describe job execution request w/ too small buffer \n"); -} - -static void fillDefaultStartNextRequest(AwsIotStartNextPendingJobExecutionRequest *request) -{ - request->statusDetails = "{\"Step\":\"1\",\"StepStatus\":\"Foo\"}"; - request->clientToken = "1234"; -} - -static const char *defaultStartNextRequestSerialized = - "{\"statusDetails\":{\"Step\":\"1\",\"StepStatus\":\"Foo\"},\"clientToken\":\"1234\"}"; - -TEST_C(JobsJsonTests, SerializeStartNextRequest) { - AwsIotStartNextPendingJobExecutionRequest startNextRequest; - fillDefaultStartNextRequest(&startNextRequest); - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize start next request \n"); - - char buffer[256]; - int charsUsed = aws_iot_jobs_json_serialize_start_next_job_execution_request(buffer, 256, &startNextRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultStartNextRequestSerialized), charsUsed); - - CHECK_EQUAL_C_STRING(defaultStartNextRequestSerialized, buffer); - jsmn_parser parser; - jsmn_init(&parser); - - int num_parsed_tokens = jsmn_parse(&parser, buffer, (size_t)charsUsed, tokens, NUM_TOKENS); - CHECK_C(num_parsed_tokens > 0); - - IOT_DEBUG("-->Success - serialize start next request \n"); -} - -TEST_C(JobsJsonTests, SerializeStartNextRequestWithNullBuffer) { - AwsIotStartNextPendingJobExecutionRequest startNextRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize start next request w/ null buffer \n"); - - fillDefaultStartNextRequest(&startNextRequest); - - int charsUsed = aws_iot_jobs_json_serialize_start_next_job_execution_request(NULL, 0, &startNextRequest); - CHECK_EQUAL_C_INT((int)strlen(defaultStartNextRequestSerialized), charsUsed); - - IOT_DEBUG("-->Success - serialize start next request w/ null buffer \n"); -} - -TEST_C(JobsJsonTests, SerializeStartNextRequestWithTooSmallBuffer) { - AwsIotStartNextPendingJobExecutionRequest startNextRequest; - - IOT_DEBUG("\n-->Running Jobs Json Tests - serialize start next request w/ too small buffer \n"); - - fillDefaultStartNextRequest(&startNextRequest); - - char buffer[11]; - // A check character which shouldn't be overwritten as we pass a length of 10 - buffer[10] = -1; - - int charsUsed = aws_iot_jobs_json_serialize_start_next_job_execution_request(buffer, 10, &startNextRequest); - CHECK_EQUAL_C_INT((int) strlen(defaultStartNextRequestSerialized), charsUsed); - - CHECK_EQUAL_C_CHAR(-1, buffer[10]); - CHECK_EQUAL_C_CHAR(0, buffer[9]); - // 9 because the 10th is \0 - CHECK_C(strncmp(defaultStartNextRequestSerialized, buffer, 9) == 0); - - IOT_DEBUG("-->Success - serialize start next request w/ too small buffer \n"); -} - -#ifdef __cplusplus -} -#endif diff --git a/tests/unit/src/aws_iot_tests_unit_jobs_topics.c b/tests/unit/src/aws_iot_tests_unit_jobs_topics.c deleted file mode 100644 index b6c6ace085..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_jobs_topics.c +++ /dev/null @@ -1,161 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#include "aws_iot_jobs_topics.h" -#include -#include -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -TEST_GROUP_C_SETUP(JobsTopicsTests) { -} - -TEST_GROUP_C_TEARDOWN(JobsTopicsTests) { -} - -#define TEST_THING_NAME "TestThing" -#define TEST_TOPIC_PREFIX "$aws/things/" TEST_THING_NAME "/jobs" -#define TEST_JOB_ID "TestJob" -#define TEST_NEXT_JOB_ID "$next" -#define TEST_TOPIC_W_JOB_PREFIX TEST_TOPIC_PREFIX "/" TEST_JOB_ID - -static int generateTopicForTestThingAndJob( - char *buffer, size_t bufferSize, - AwsIotJobExecutionTopicType topicType, AwsIotJobExecutionTopicReplyType replyType) -{ - return aws_iot_jobs_get_api_topic( - buffer, bufferSize, topicType, replyType, TEST_THING_NAME, TEST_JOB_ID); -} - -static int generateTopicForTestThing( - char *buffer, size_t bufferSize, - AwsIotJobExecutionTopicType topicType, AwsIotJobExecutionTopicReplyType replyType) -{ - return aws_iot_jobs_get_api_topic( - buffer, bufferSize, topicType, replyType, TEST_THING_NAME, NULL); -} - -static void testGenerateValidApiTopic( - AwsIotJobExecutionTopicType topicType, AwsIotJobExecutionTopicReplyType replyType, - bool includeThingName, const char *expectedOutput) -{ - int requestedSize; - int expectedSize = (int)strlen(expectedOutput); - - IOT_DEBUG("\n-->Running Jobs Topics Tests - generate valid topics %s w/ reply type \n", getJobTopicTypeName(topicType), getJobReplyTypeName(replyType)); - - if (includeThingName) { - requestedSize = generateTopicForTestThingAndJob(NULL, 0, topicType, replyType); - CHECK_EQUAL_C_INT(expectedSize, requestedSize); - } else { - requestedSize = generateTopicForTestThing(NULL, 0, topicType, replyType); - CHECK_EQUAL_C_INT(expectedSize, requestedSize); - } - - CHECK_C(requestedSize > 3); - char *buffer = (char *) malloc((size_t)(requestedSize + 1) * sizeof(char)); - buffer[3] = -1; - - if (includeThingName) { - requestedSize = generateTopicForTestThingAndJob(buffer, 3, topicType, replyType); - } else { - requestedSize = generateTopicForTestThing(buffer, 3, topicType, replyType); - } - CHECK_EQUAL_C_INT(expectedSize, requestedSize); - - CHECK_EQUAL_C_CHAR(expectedOutput[0], buffer[0]); - CHECK_EQUAL_C_CHAR(expectedOutput[1], buffer[1]); - CHECK_EQUAL_C_CHAR('\0', buffer[2]); - CHECK_EQUAL_C_CHAR(-1, buffer[3]); - - if (includeThingName) { - requestedSize = generateTopicForTestThingAndJob(buffer, (size_t)requestedSize + 1, topicType, replyType); - } else { - requestedSize = generateTopicForTestThing(buffer, (size_t)requestedSize + 1, topicType, replyType); - } - - CHECK_EQUAL_C_INT(expectedSize, requestedSize); - CHECK_EQUAL_C_STRING(expectedOutput, buffer); - - free(buffer); - - IOT_DEBUG("-->Success - generate valid topics %s w/ reply type \n", getJobTopicTypeName(topicType), getJobReplyTypeName(replyType)); -} - -static void testGenerateValidApiTopicWithAllReplyTypes( - AwsIotJobExecutionTopicType topicType, bool includeThingName, - const char *expectedBaseTopic) -{ - char buffer[1024]; - - testGenerateValidApiTopic( - topicType, JOB_REQUEST_TYPE, includeThingName, expectedBaseTopic); - - snprintf(buffer, 1024, "%s/accepted", expectedBaseTopic); - testGenerateValidApiTopic( - topicType, JOB_ACCEPTED_REPLY_TYPE, includeThingName, buffer); - - snprintf(buffer, 1024, "%s/rejected", expectedBaseTopic); - testGenerateValidApiTopic( - topicType, JOB_REJECTED_REPLY_TYPE, includeThingName, buffer); - - snprintf(buffer, 1024, "%s/+", expectedBaseTopic); - testGenerateValidApiTopic( - topicType, JOB_WILDCARD_REPLY_TYPE, includeThingName, buffer); - -} - -TEST_C(JobsTopicsTests, GenerateValidTopics) { - testGenerateValidApiTopicWithAllReplyTypes(JOB_UPDATE_TOPIC, true, TEST_TOPIC_W_JOB_PREFIX "/update"); - testGenerateValidApiTopicWithAllReplyTypes(JOB_DESCRIBE_TOPIC, true, TEST_TOPIC_W_JOB_PREFIX "/get"); - testGenerateValidApiTopicWithAllReplyTypes(JOB_WILDCARD_TOPIC, true, TEST_TOPIC_W_JOB_PREFIX "/+"); - testGenerateValidApiTopic(JOB_WILDCARD_TOPIC, JOB_REQUEST_TYPE, false, TEST_TOPIC_PREFIX "/#"); - testGenerateValidApiTopic(JOB_NOTIFY_TOPIC, JOB_REQUEST_TYPE, false, TEST_TOPIC_PREFIX "/notify"); - testGenerateValidApiTopic(JOB_NOTIFY_NEXT_TOPIC, JOB_REQUEST_TYPE, false, TEST_TOPIC_PREFIX "/notify-next"); - testGenerateValidApiTopic(JOB_START_NEXT_TOPIC, JOB_REQUEST_TYPE, false, TEST_TOPIC_PREFIX "/start-next"); -} - -TEST_C(JobsTopicsTests, GenerateWithMissingJobId) { - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_UPDATE_TOPIC, JOB_REQUEST_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_DESCRIBE_TOPIC, JOB_REQUEST_TYPE)); -} - -TEST_C(JobsTopicsTests, GenerateWithInvalidTopicOrReplyType) { - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_UNRECOGNIZED_TOPIC, JOB_REQUEST_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, -1, JOB_REQUEST_TYPE)); - - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_DESCRIBE_TOPIC, JOB_UNRECOGNIZED_TOPIC_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_DESCRIBE_TOPIC, -1)); -} - -TEST_C(JobsTopicsTests, GenerateWithInvalidCombinations) { - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_NOTIFY_TOPIC, JOB_ACCEPTED_REPLY_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_NOTIFY_TOPIC, JOB_REJECTED_REPLY_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_NOTIFY_TOPIC, JOB_WILDCARD_REPLY_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_NOTIFY_NEXT_TOPIC, JOB_ACCEPTED_REPLY_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_NOTIFY_NEXT_TOPIC, JOB_REJECTED_REPLY_TYPE)); - CHECK_EQUAL_C_INT(-1, generateTopicForTestThing(NULL, 0, JOB_NOTIFY_NEXT_TOPIC, JOB_WILDCARD_REPLY_TYPE)); -} - -#ifdef __cplusplus -} -#endif diff --git a/tests/unit/src/aws_iot_tests_unit_jobs_types.c b/tests/unit/src/aws_iot_tests_unit_jobs_types.c deleted file mode 100644 index 60dc8c9d39..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_jobs_types.c +++ /dev/null @@ -1,70 +0,0 @@ -/* -* Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -TEST_GROUP_C_SETUP(JobsTypesTests) { -} - -TEST_GROUP_C_TEARDOWN(JobsTypesTests) { -} - -TEST_C(JobsTypesTests, StringToStatus) { - char emptyString = '\0'; - - IOT_DEBUG("\n-->Running Jobs Types Tests - string to status \n"); - - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_STATUS_NOT_SET, (int)aws_iot_jobs_map_string_to_job_status(NULL)); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_STATUS_NOT_SET, (int)aws_iot_jobs_map_string_to_job_status(&emptyString)); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_QUEUED, (int)aws_iot_jobs_map_string_to_job_status("QUEUED")); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_IN_PROGRESS, (int)aws_iot_jobs_map_string_to_job_status("IN_PROGRESS")); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_FAILED, (int)aws_iot_jobs_map_string_to_job_status("FAILED")); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_SUCCEEDED, (int)aws_iot_jobs_map_string_to_job_status("SUCCEEDED")); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_CANCELED, (int)aws_iot_jobs_map_string_to_job_status("CANCELED")); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_REJECTED, (int)aws_iot_jobs_map_string_to_job_status("REJECTED")); - CHECK_EQUAL_C_INT((int)JOB_EXECUTION_UNKNOWN_STATUS, (int)aws_iot_jobs_map_string_to_job_status("invalidStatus")); - - IOT_DEBUG("-->Success - string to status \n"); -} - -TEST_C(JobsTypesTests, StatusToString) { - IOT_DEBUG("\n-->Running Jobs Types Tests - status to string \n"); - - CHECK_EQUAL_C_STRING("QUEUED", aws_iot_jobs_map_status_to_string(JOB_EXECUTION_QUEUED)); - CHECK_EQUAL_C_STRING("IN_PROGRESS", aws_iot_jobs_map_status_to_string(JOB_EXECUTION_IN_PROGRESS)); - CHECK_EQUAL_C_STRING("FAILED", aws_iot_jobs_map_status_to_string(JOB_EXECUTION_FAILED)); - CHECK_EQUAL_C_STRING("SUCCEEDED", aws_iot_jobs_map_status_to_string(JOB_EXECUTION_SUCCEEDED)); - CHECK_EQUAL_C_STRING("CANCELED", aws_iot_jobs_map_status_to_string(JOB_EXECUTION_CANCELED)); - CHECK_EQUAL_C_STRING("REJECTED", aws_iot_jobs_map_status_to_string(JOB_EXECUTION_REJECTED)); - CHECK_EQUAL_C_STRING(NULL, aws_iot_jobs_map_status_to_string(JOB_EXECUTION_STATUS_NOT_SET)); - CHECK_EQUAL_C_STRING(NULL, aws_iot_jobs_map_status_to_string(JOB_EXECUTION_UNKNOWN_STATUS)); - - IOT_DEBUG("-->Success - status to string \n"); -} - -#ifdef __cplusplus -} -#endif diff --git a/tests/unit/src/aws_iot_tests_unit_json_utils.cpp b/tests/unit/src/aws_iot_tests_unit_json_utils.cpp deleted file mode 100644 index f1fe2e33b3..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_json_utils.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_json_utils.cpp - * @brief IoT Client Unit Testing - JSON Utility Tests - */ - -#include -#include - -TEST_GROUP_C(JsonUtils) { - TEST_GROUP_C_SETUP_WRAPPER(JsonUtils) - TEST_GROUP_C_TEARDOWN_WRAPPER(JsonUtils) -}; - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseStringBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseStringLongerStringIsValid) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseStringWithBufferTooSmall) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseStringEmptyStringIsValid) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseStringErrorOnInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseStringErrorOnBoolean) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseBooleanTrue) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseBooleanFalse) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseBooleanErrorOnString) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseBooleanErrorOnInvalidJson) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleNumberWithNoDecimal) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleSmallDouble) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleErrorOnString) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleErrorOnJsonObject) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseDoubleNegativeNumber) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatNumberWithNoDecimal) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatSmallFloat) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatErrorOnString) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatErrorOnJsonObject) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseFloatNegativeNumber) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseIntegerBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseIntegerLargeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseIntegerNegativeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseIntegerErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseIntegerErrorOnString) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger16bitBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger16bitLargeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger16bitNegativeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger16bitErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger16bitErrorOnString) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger8bitBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger8bitLargeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger8bitNegativeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger8bitErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseInteger8bitErrorOnString) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedIntegerBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedIntegerLargeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedIntegerErrorOnNegativeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedIntegerErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedIntegerErrorOnString) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger16bitBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger16bitLargeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger16bitErrorOnNegativeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger16bitErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger16bitErrorOnString) - -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger8bitBasic) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger8bitLargeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger8bitErrorOnNegativeInteger) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger8bitErrorOnBoolean) -TEST_GROUP_C_WRAPPER(JsonUtils, ParseUnsignedInteger8bitErrorOnString) diff --git a/tests/unit/src/aws_iot_tests_unit_json_utils_helper.c b/tests/unit/src/aws_iot_tests_unit_json_utils_helper.c deleted file mode 100644 index 864d379d73..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_json_utils_helper.c +++ /dev/null @@ -1,834 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_json_utils_helper.c - * @brief IoT Client Unit Testing - JSON Utility Tests helper - */ - -#include -#include -#include -#include - -#include "aws_iot_json_utils.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_log.h" - -#define STRING_BUFFER_LENGTH (50) - -static IoT_Error_t rc = SUCCESS; - -static jsmn_parser test_parser; -static jsmntok_t t[128]; - -TEST_GROUP_C_SETUP(JsonUtils) { - jsmn_init(&test_parser); -} - -TEST_GROUP_C_TEARDOWN(JsonUtils) { - -} - -TEST_C(JsonUtils, ParseStringBasic) { - int r; - const char *json = "{\"x\":\"test1\"}"; - char parsedString[STRING_BUFFER_LENGTH]; - - IOT_DEBUG("\n-->Running Json Utils Tests - Basic String Parsing \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseStringValue(parsedString, STRING_BUFFER_LENGTH, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING("test1", parsedString); -} - -TEST_C(JsonUtils, ParseStringLongerStringIsValid) { - int r; - const char *json = "{\"x\":\"this is a longer string for test 2\"}"; - char parsedString[STRING_BUFFER_LENGTH]; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse long string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseStringValue(parsedString, STRING_BUFFER_LENGTH, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING("this is a longer string for test 2", parsedString); -} - -/* Test that parsing a string doesn't overflow the given buffer. */ -TEST_C(JsonUtils, ParseStringWithBufferTooSmall) { - static const char* const pJsonString = "{\"key\":\"This value is longer than JSON_BUFFER_LENGTH, which should be 50.\"}"; - char parsedString[STRING_BUFFER_LENGTH] = {0}; - int jsmnReturn, i; - - IOT_DEBUG("\n-->Running Json Utils Tests - String parsing with buffer too small. \n"); - - jsmnReturn = jsmn_parse(&test_parser, pJsonString, strlen(pJsonString), t, sizeof(t)/sizeof(t[0])); - CHECK_EQUAL_C_INT(3, jsmnReturn); - - rc = parseStringValue(parsedString, STRING_BUFFER_LENGTH, pJsonString, t + 2); - - CHECK_EQUAL_C_INT(SHADOW_JSON_ERROR, rc); - - /* Ensure there was no attempt to write to a buffer that's too small. */ - for(i = 0; i < STRING_BUFFER_LENGTH; i++) - { - CHECK_EQUAL_C_CHAR(0, parsedString[i]); - } -} - -TEST_C(JsonUtils, ParseStringEmptyStringIsValid) { int r; - const char *json = "{\"x\":\"\"}"; - char parsedString[STRING_BUFFER_LENGTH]; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse empty string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseStringValue(parsedString, STRING_BUFFER_LENGTH, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING("", parsedString); -} - -TEST_C(JsonUtils, ParseStringErrorOnInteger) { - int r; - const char *json = "{\"x\":3}"; - char parsedString[STRING_BUFFER_LENGTH]; - - IOT_DEBUG("\n-->Running Json Utils Tests - parse integer as string returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseStringValue(parsedString, STRING_BUFFER_LENGTH, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseStringErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - char parsedString[STRING_BUFFER_LENGTH]; - - IOT_DEBUG("\n-->Running Json Utils Tests - parse boolean as string returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseStringValue(parsedString, STRING_BUFFER_LENGTH, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseBooleanTrue) { - int r; - const char *json = "{\"x\":true}"; - bool parsedBool; - - IOT_DEBUG("\n-->Running Json Utils Tests - parse boolean with true value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseBooleanValue(&parsedBool, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(1, (int) parsedBool); -} - -TEST_C(JsonUtils, ParseBooleanFalse) { - int r; - const char *json = "{\"x\":false}"; - bool parsedBool; - - IOT_DEBUG("\n-->Running Json Utils Tests - parse boolean with false value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseBooleanValue(&parsedBool, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(0, (int) parsedBool); -} - -TEST_C(JsonUtils, ParseBooleanErrorOnString) { - int r; - const char *json = "{\"x\":\"not a bool\"}"; - bool parsedBool; - - IOT_DEBUG("\n-->Running Json Utils Tests - parse string as boolean returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseBooleanValue(&parsedBool, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseBooleanErrorOnInvalidJson) { - int r; - const char *json = "{\"x\":frue}"; // Invalid - bool parsedBool; - - IOT_DEBUG("\n-->Running Json Utils Tests - parse boolean returns error with invalid json \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseBooleanValue(&parsedBool, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseDoubleBasic) { - int r; - const char *json = "{\"x\":20.5}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse double test \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_REAL(20.5, parsedDouble, 0.0); -} - -TEST_C(JsonUtils, ParseDoubleNumberWithNoDecimal) { - int r; - const char *json = "{\"x\":100}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse double number with no decimal \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_REAL(100, parsedDouble, 0.0); -} - -TEST_C(JsonUtils, ParseDoubleSmallDouble) { - int r; - const char *json = "{\"x\":0.000004}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse small double value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_REAL(0.000004, parsedDouble, 0.0); -} - -TEST_C(JsonUtils, ParseDoubleErrorOnString) { - int r; - const char *json = "{\"x\":\"20.5\"}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse string as double returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseDoubleErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse boolean as double returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseDoubleErrorOnJsonObject) { - int r; - const char *json = "{\"x\":{}}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse json object as double returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseDoubleNegativeNumber) { - int r; - const char *json = "{\"x\":-56.78}"; - double parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse negative double value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseDoubleValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_REAL(-56.78, parsedDouble, 0.0); -} - -TEST_C(JsonUtils, ParseFloatBasic) { - int r; - const char *json = "{\"x\":20.5}"; - float parsedFloat; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse float test \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedFloat, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_REAL(20.5, parsedFloat, 0.0); -} - -TEST_C(JsonUtils, ParseFloatNumberWithNoDecimal) { - int r; - const char *json = "{\"x\":100}"; - float parsedFloat; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse float number with no decimal \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedFloat, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_REAL(100, parsedFloat, 0.0); -} - -TEST_C(JsonUtils, ParseFloatSmallFloat) { - int r; - const char *json = "{\"x\":0.0004}"; - float parsedFloat; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse small float value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedFloat, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_C(0.0004f == parsedFloat); -} - -TEST_C(JsonUtils, ParseFloatErrorOnString) { - int r; - const char *json = "{\"x\":\"20.5\"}"; - float parsedFloat; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse string as float returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedFloat, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseFloatErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - float parsedFloat; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse boolean as float returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedFloat, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseFloatErrorOnJsonObject) { - int r; - const char *json = "{\"x\":{}}"; - float parsedDouble; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse json object as float returns error \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedDouble, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseFloatNegativeNumber) { - int r; - const char *json = "{\"x\":-56.78}"; - float parsedFloat; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse negative float value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseFloatValue(&parsedFloat, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_C(-56.78f == parsedFloat); -} - -TEST_C(JsonUtils, ParseIntegerBasic) { - int r; - const char *json = "{\"x\":1}"; - int32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 32 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(1, parsedInteger); -} - -TEST_C(JsonUtils, ParseIntegerLargeInteger) { - int r; - const char *json = "{\"x\":2147483647}"; - int32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse large 32 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(2147483647, parsedInteger); -} - -TEST_C(JsonUtils, ParseIntegerNegativeInteger) { - int r; - const char *json = "{\"x\":-308}"; - int32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse negative 32 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(-308, parsedInteger); -} - -TEST_C(JsonUtils, ParseIntegerErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - int32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 32 bit integer returns error with boolean \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseIntegerErrorOnString) { - int r; - const char *json = "{\"x\":\"45\"}"; - int32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 32 bit integer returns error on string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseInteger16bitBasic) { - int r; - const char *json = "{\"x\":1}"; - int16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 16 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(1, parsedInteger); -} - -TEST_C(JsonUtils, ParseInteger16bitLargeInteger) { - int r; - const char *json = "{\"x\":32767}"; - int16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse large 16 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(32767, parsedInteger); -} - -TEST_C(JsonUtils, ParseInteger16bitNegativeInteger) { - int r; - const char *json = "{\"x\":-308}"; - int16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse negative 16 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(-308, parsedInteger); -} - -TEST_C(JsonUtils, ParseInteger16bitErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - int16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 16 bit integer returns error with boolean \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseInteger16bitErrorOnString) { - int r; - const char *json = "{\"x\":\"45\"}"; - int16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 16 bit integer returns error on string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseInteger8bitBasic) { - int r; - const char *json = "{\"x\":1}"; - int8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 8 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(1, parsedInteger); -} - -TEST_C(JsonUtils, ParseInteger8bitLargeInteger) { - int r; - const char *json = "{\"x\":127}"; - int8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse large 8 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(127, parsedInteger); -} - -TEST_C(JsonUtils, ParseInteger8bitNegativeInteger) { - int r; - const char *json = "{\"x\":-30}"; - int8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse negative 8 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(-30, parsedInteger); -} - -TEST_C(JsonUtils, ParseInteger8bitErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - int8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 8 bit integer returns error with boolean \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseInteger8bitErrorOnString) { - int r; - const char *json = "{\"x\":\"45\"}"; - int8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse 8 bit integer returns error on string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedIntegerBasic) { - int r; - const char *json = "{\"x\":1}"; - uint32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 32 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_C(1 == parsedInteger); -} - -TEST_C(JsonUtils, ParseUnsignedIntegerLargeInteger) { - int r; - const char *json = "{\"x\":2147483647}"; - uint32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse large unsigned 32 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_C(2147483647 == parsedInteger); -} - -TEST_C(JsonUtils, ParseUnsignedIntegerErrorOnNegativeInteger) { - int r; - const char *json = "{\"x\":-308}"; - uint32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 32 bit integer returns error with negative value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedIntegerErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - uint32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 32 bit integer returns error with boolean \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedIntegerErrorOnString) { - int r; - const char *json = "{\"x\":\"45\"}"; - uint32_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 32 bit integer returns error on string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger32Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedInteger16bitBasic) { - int r; - const char *json = "{\"x\":1}"; - uint16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 16 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(1, parsedInteger); -} - -TEST_C(JsonUtils, ParseUnsignedInteger16bitLargeInteger) { - int r; - const char *json = "{\"x\":65535}"; - uint16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse large unsigned 16 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(65535, parsedInteger); -} - -TEST_C(JsonUtils, ParseUnsignedInteger16bitErrorOnNegativeInteger) { - int r; - const char *json = "{\"x\":-308}"; - uint16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 16 bit integer returns error on negative value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedInteger16bitErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - uint16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 16 bit integer returns error with boolean \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedInteger16bitErrorOnString) { - int r; - const char *json = "{\"x\":\"45\"}"; - uint16_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 16 bit integer returns error on string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger16Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedInteger8bitBasic) { - int r; - const char *json = "{\"x\":1}"; - uint8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 8 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(1, parsedInteger); -} - -TEST_C(JsonUtils, ParseUnsignedInteger8bitLargeInteger) { - int r; - const char *json = "{\"x\":255}"; - uint8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse large unsigned 8 bit integer \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(255, parsedInteger); -} - -TEST_C(JsonUtils, ParseUnsignedInteger8bitErrorOnNegativeInteger) { - int r; - const char *json = "{\"x\":-30}"; - uint8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 8 bit integer returns error on negative value \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedInteger8bitErrorOnBoolean) { - int r; - const char *json = "{\"x\":true}"; - uint8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 8 bit integer returns error with boolean \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} - -TEST_C(JsonUtils, ParseUnsignedInteger8bitErrorOnString) { - int r; - const char *json = "{\"x\":\"45\"}"; - uint8_t parsedInteger; - - IOT_DEBUG("\n-->Running Json Utils Tests - Parse unsigned 8 bit integer returns error on string \n"); - - r = jsmn_parse(&test_parser, json, strlen(json), t, sizeof(t) / sizeof(t[0])); - rc = parseUnsignedInteger8Value(&parsedInteger, json, t + 2); - - CHECK_EQUAL_C_INT(3, r); - CHECK_EQUAL_C_INT(JSON_PARSE_ERROR, rc); -} diff --git a/tests/unit/src/aws_iot_tests_unit_publish.cpp b/tests/unit/src/aws_iot_tests_unit_publish.cpp deleted file mode 100644 index da188e0fef..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_publish.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_publish.cpp - * @brief IoT Client Unit Testing - Publish API Tests - */ - -#include -#include - -TEST_GROUP_C(PublishTests) { - TEST_GROUP_C_SETUP_WRAPPER(PublishTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(PublishTests) -}; - -/* E:1 - Publish with Null/empty client instance */ -TEST_GROUP_C_WRAPPER(PublishTests, PublishNullClient) -/* E:2 - Publish with Null/empty Topic Name */ -TEST_GROUP_C_WRAPPER(PublishTests, PublishNullTopic) -/* E:3 - Publish with Null/empty payload */ -TEST_GROUP_C_WRAPPER(PublishTests, PublishNullParams) -/* E:4 - Publish with network disconnected */ -TEST_GROUP_C_WRAPPER(PublishTests, PublishNetworkDisconnected) -/* E:5 - Publish with QoS2 */ -/* Not required here, enum value doesn't exist for QoS2 */ -/* E:6 - Publish with QoS1 send success, Puback not received */ -TEST_GROUP_C_WRAPPER(PublishTests, publishQoS1FailureToReceivePuback) -/* E:7 - Publish with QoS1 send success, Delayed Puback received after command timeout */ -TEST_GROUP_C_WRAPPER(PublishTests, publishQoS1FailureDelayedPuback) -/* E:8 - Publish with send success, Delayed Puback received before command timeout */ -TEST_GROUP_C_WRAPPER(PublishTests, publishQoS1Success10msDelayedPuback) -/* E:9 - Publish QoS0 success */ -TEST_GROUP_C_WRAPPER(PublishTests, publishQoS0NoPubackSuccess) -/* E:10 - Publish with QoS1 send success, Puback received */ -TEST_GROUP_C_WRAPPER(PublishTests, publishQoS1Success) diff --git a/tests/unit/src/aws_iot_tests_unit_publish_helper.c b/tests/unit/src/aws_iot_tests_unit_publish_helper.c deleted file mode 100644 index 2f2c6406c0..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_publish_helper.c +++ /dev/null @@ -1,205 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_publish_helper.c - * @brief IoT Client Unit Testing - Publish API Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_log.h" - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static IoT_Publish_Message_Params testPubMsgParams; -static char subTopic[10] = "sdk/Test"; -static uint16_t subTopicLen = 8; - -static AWS_IoT_Client iotClient; -char cPayload[100]; - -TEST_GROUP_C_SETUP(PublishTests) { - IoT_Error_t rc = SUCCESS; - ResetTLSBuffer(); - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.mqttCommandTimeout_ms = 2000; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - IOT_DEBUG("MQTT Status State : %d, RC : %d\n\n", aws_iot_mqtt_get_client_state(&iotClient), rc); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - sprintf(cPayload, "%s : %d ", "hello from SDK", 0); - testPubMsgParams.payload = (void *) cPayload; - testPubMsgParams.payloadLen = strlen(cPayload); - - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(PublishTests) { } - -/* E:1 - Publish with Null/empty client instance */ -TEST_C(PublishTests, PublishNullClient) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:1 - Publish with Null/empty client instance \n"); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - testPubMsgParams.payload = "Message"; - testPubMsgParams.payloadLen = 7; - - rc = aws_iot_mqtt_publish(NULL, "sdkTest/Sub", 11, &testPubMsgParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - E:1 - Publish with Null/empty client instance \n"); -} - -/* E:2 - Publish with Null/empty Topic Name */ -TEST_C(PublishTests, PublishNullTopic) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:2 - Publish with Null/empty Topic Name \n"); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - testPubMsgParams.payload = "Message"; - testPubMsgParams.payloadLen = 7; - - rc = aws_iot_mqtt_publish(&iotClient, NULL, 11, &testPubMsgParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - rc = aws_iot_mqtt_publish(&iotClient, "sdkTest/Sub", 0, &testPubMsgParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - E:2 - Publish with Null/empty Topic Name \n"); -} - -/* E:3 - Publish with Null/empty payload */ -TEST_C(PublishTests, PublishNullParams) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:3 - Publish with Null/empty payload \n"); - - rc = aws_iot_mqtt_publish(&iotClient, "sdkTest/Sub", 11, NULL); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - testPubMsgParams.payload = NULL; - testPubMsgParams.payloadLen = 0; - - rc = aws_iot_mqtt_publish(&iotClient, "sdkTest/Sub", 11, &testPubMsgParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); - - IOT_DEBUG("-->Success - E:3 - Publish with Null/empty payload \n"); -} - -/* E:4 - Publish with network disconnected */ -TEST_C(PublishTests, PublishNetworkDisconnected) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:4 - Publish with network disconnected \n"); - - /* Ensure network is disconnected */ - rc = aws_iot_mqtt_disconnect(&iotClient); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - testPubMsgParams.payload = NULL; - testPubMsgParams.payloadLen = 0; - - rc = aws_iot_mqtt_publish(&iotClient, "sdkTest/Sub", 11, &testPubMsgParams); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); - - IOT_DEBUG("-->Success - E:4 - Publish with network disconnected \n"); -} - -/* E:6 - Publish with QoS1 send success, Puback not received */ -TEST_C(PublishTests, publishQoS1FailureToReceivePuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:6 - Publish with QoS1 send success, Puback not received \n"); - - rc = aws_iot_mqtt_publish(&iotClient, subTopic, subTopicLen, &testPubMsgParams); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - - IOT_DEBUG("-->Success - E:6 - Publish with QoS1 send success, Puback not received \n"); -} - -/* E:7 - Publish with QoS1 send success, Delayed Puback received after command timeout */ -TEST_C(PublishTests, publishQoS1FailureDelayedPuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:7 - Publish with QoS1 send success, Delayed Puback received after command timeout \n"); - - setTLSRxBufferDelay(10, 0); - setTLSRxBufferForPuback(); - rc = aws_iot_mqtt_publish(&iotClient, subTopic, subTopicLen, &testPubMsgParams); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - - IOT_DEBUG("-->Success - E:7 - Publish with QoS1 send success, Delayed Puback received after command timeout \n"); -} - -/* E:8 - Publish with send success, Delayed Puback received before command timeout */ -TEST_C(PublishTests, publishQoS1Success10msDelayedPuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:8 - Publish with send success, Delayed Puback received before command timeout \n"); - - ResetTLSBuffer(); - setTLSRxBufferDelay(0, (int) iotClient.clientData.commandTimeoutMs/2); - setTLSRxBufferForPuback(); - rc = aws_iot_mqtt_publish(&iotClient, subTopic, subTopicLen, &testPubMsgParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - E:8 - Publish with send success, Delayed Puback received before command timeout \n"); -} - -/* E:9 - Publish QoS0 success */ -TEST_C(PublishTests, publishQoS0NoPubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:9 - Publish QoS0 success \n"); - - testPubMsgParams.qos = QOS0; // switch to a Qos0 PUB - rc = aws_iot_mqtt_publish(&iotClient, subTopic, subTopicLen, &testPubMsgParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - E:9 - Publish QoS0 success \n"); -} - -/* E:10 - Publish with QoS1 send success, Puback received */ -TEST_C(PublishTests, publishQoS1Success) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Publish Tests - E:10 - Publish with QoS1 send success, Puback received \n"); - - setTLSRxBufferForPuback(); - rc = aws_iot_mqtt_publish(&iotClient, subTopic, subTopicLen, &testPubMsgParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - E:10 - Publish with QoS1 send success, Puback received \n"); -} diff --git a/tests/unit/src/aws_iot_tests_unit_runner.cpp b/tests/unit/src/aws_iot_tests_unit_runner.cpp deleted file mode 100644 index ba1f5cb481..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_runner.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_runner.cpp - * @brief IoT Client Unit Testing - Runner - */ - -#include - -int main(int ac, char **argv) { - return CommandLineTestRunner::RunAllTests(ac, argv); -} diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_action.cpp b/tests/unit/src/aws_iot_tests_unit_shadow_action.cpp deleted file mode 100644 index 32f1be2e12..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_action.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_action.cpp - * @brief IoT Client Unit Testing - Shadow Action Tests - */ - -#include -#include - -TEST_GROUP_C(ShadowActionTests) { - TEST_GROUP_C_SETUP_WRAPPER(ShadowActionTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(ShadowActionTests) -}; - -TEST_GROUP_C_WRAPPER(ShadowActionTests, GetTheFullJSONDocument) -TEST_GROUP_C_WRAPPER(ShadowActionTests, DeleteTheJSONDocument) -TEST_GROUP_C_WRAPPER(ShadowActionTests, UpdateTheJSONDocument) -TEST_GROUP_C_WRAPPER(ShadowActionTests, GetTheFullJSONDocumentTimeout) -TEST_GROUP_C_WRAPPER(ShadowActionTests, SubscribeToAcceptedRejectedOnGet) -TEST_GROUP_C_WRAPPER(ShadowActionTests, UnSubscribeToAcceptedRejectedOnGetResponse) -TEST_GROUP_C_WRAPPER(ShadowActionTests, UnSubscribeToAcceptedRejectedOnGetTimeout) -TEST_GROUP_C_WRAPPER(ShadowActionTests, UnSubscribeToAcceptedRejectedOnGetTimeoutWithSticky) -TEST_GROUP_C_WRAPPER(ShadowActionTests, WrongTokenInGetResponse) -TEST_GROUP_C_WRAPPER(ShadowActionTests, NoTokenInGetResponse) -TEST_GROUP_C_WRAPPER(ShadowActionTests, InvalidInboundJSONInGetResponse) -TEST_GROUP_C_WRAPPER(ShadowActionTests, AcceptedSubFailsGetRequest) -TEST_GROUP_C_WRAPPER(ShadowActionTests, RejectedSubFailsGetRequest) -TEST_GROUP_C_WRAPPER(ShadowActionTests, PublishFailsGetRequest) -TEST_GROUP_C_WRAPPER(ShadowActionTests, GetVersionFromAckStatus) -TEST_GROUP_C_WRAPPER(ShadowActionTests, StickyNonStickyNeverConflict) -TEST_GROUP_C_WRAPPER(ShadowActionTests, ACKWaitingMoreThanAllowed) -TEST_GROUP_C_WRAPPER(ShadowActionTests, InboundDataTooBigForBuffer) -TEST_GROUP_C_WRAPPER(ShadowActionTests, NoClientTokenForShadowAction) -TEST_GROUP_C_WRAPPER(ShadowActionTests, NoCallbackForShadowAction) -TEST_GROUP_C_WRAPPER(ShadowActionTests, GetAndDeleteRequest) -TEST_GROUP_C_WRAPPER(ShadowActionTests, ExtractClientToken) -TEST_GROUP_C_WRAPPER(ShadowActionTests, IsReceivedJsonValid) diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_action_helper.c b/tests/unit/src/aws_iot_tests_unit_shadow_action_helper.c deleted file mode 100644 index 86e603197d..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_action_helper.c +++ /dev/null @@ -1,944 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_action_helper.c - * @brief IoT Client Unit Testing - Shadow Action API Tests Helper - */ - -#include -#include -#include -#include -#include - -#include "aws_iot_tests_unit_mock_tls_params.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_tests_unit_shadow_helper.h" - -#include "aws_iot_shadow_interface.h" -#include "aws_iot_shadow_actions.h" -#include "aws_iot_shadow_json.h" -#include "aws_iot_log.h" - -#define SIZE_OF_UPDATE_DOCUMENT 200 -#define TEST_JSON_RESPONSE_FULL_DOCUMENT "{\"state\":{\"reported\":{\"sensor1\":98}}, \"clientToken\":\"" AWS_IOT_MQTT_CLIENT_ID "-0\"}" -#define TEST_JSON_RESPONSE_DELETE_DOCUMENT "{\"version\":2,\"timestamp\":1443473857,\"clientToken\":\"" AWS_IOT_MQTT_CLIENT_ID "-0\"}" -#define TEST_JSON_RESPONSE_UPDATE_DOCUMENT "{\"state\":{\"reported\":{\"doubleData\":4.090800,\"floatData\":3.445000}}, \"clientToken\":\"" AWS_IOT_MQTT_CLIENT_ID "-0\"}" -#define TEST_JSON_SIZE 120 -static AWS_IoT_Client client; -static IoT_Client_Connect_Params connectParams; -static IoT_Publish_Message_Params testPubMsgParams; -static ShadowInitParameters_t shadowInitParams; -static ShadowConnectParameters_t shadowConnectParams; - -static Shadow_Ack_Status_t ackStatusRx; -static char jsonFullDocument[200]; -static ShadowActions_t actionRx; - -static void topicNameFromThingAndAction(char *pTopic, const char *pThingName, ShadowActions_t action) { - char actionBuf[10]; - - if(SHADOW_GET == action) { - strcpy(actionBuf, "get"); - } else if(SHADOW_UPDATE == action) { - strcpy(actionBuf, "update"); - } else if(SHADOW_DELETE == action) { - strcpy(actionBuf, "delete"); - } - - snprintf(pTopic, 100, "$aws/things/%s/shadow/%s", pThingName, actionBuf); -} - -TEST_GROUP_C_SETUP(ShadowActionTests) { - IoT_Error_t ret_val = SUCCESS; - char cPayload[100]; - char topic[120]; - - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.pClientCRT = AWS_IOT_CERTIFICATE_FILENAME; - shadowInitParams.pRootCA = AWS_IOT_ROOT_CA_FILENAME; - shadowInitParams.pClientKey = AWS_IOT_PRIVATE_KEY_FILENAME; - shadowInitParams.disconnectHandler = NULL; - shadowInitParams.enableAutoReconnect = false; - ret_val = aws_iot_shadow_init(&client, &shadowInitParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - shadowConnectParams.pMyThingName = AWS_IOT_MY_THING_NAME; - shadowConnectParams.pMqttClientId = AWS_IOT_MQTT_CLIENT_ID; - shadowConnectParams.mqttClientIdLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - ret_val = aws_iot_shadow_connect(&client, &shadowConnectParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - setTLSRxBufferForPuback(); - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - snprintf(cPayload, 100, "%s : %d ", "hello from SDK", 0); - testPubMsgParams.payload = (void *) cPayload; - testPubMsgParams.payloadLen = strlen(cPayload) + 1; - topicNameFromThingAndAction(topic, AWS_IOT_MY_THING_NAME, SHADOW_GET); - setTLSRxBufferForDoubleSuback(topic, strlen(topic), QOS1, testPubMsgParams); -} - -TEST_GROUP_C_TEARDOWN(ShadowActionTests) { - /* Clean up. Not checking return code here because this is common to all tests. - * A test might have already caused a disconnect by this point. - */ - IoT_Error_t rc = aws_iot_shadow_disconnect(&client); - IOT_UNUSED(rc); -} - -static void actionCallback(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, - const char *pReceivedJsonDocument, void *pContextData) { - IOT_UNUSED(pThingName); - IOT_UNUSED(pContextData); - IOT_DEBUG("%s", pReceivedJsonDocument); - actionRx = action; - ackStatusRx = status; - if(SHADOW_ACK_TIMEOUT != status) { - strcpy(jsonFullDocument, pReceivedJsonDocument); - } -} - -// Happy path for Get, Update, Delete -TEST_C(ShadowActionTests, GetTheFullJSONDocument) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Get full json document \n"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - ResetTLSBuffer(); - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_FULL_DOCUMENT, jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_GET, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_ACCEPTED, ackStatusRx); - - IOT_DEBUG("-->Success - Get full json document \n"); -} - -TEST_C(ShadowActionTests, DeleteTheJSONDocument) { - IoT_Error_t ret_val = SUCCESS; - IoT_Publish_Message_Params params; - char deleteRequestJson[TEST_JSON_SIZE]; - - IOT_DEBUG("-->Running Shadow Action Tests - Delete json document \n"); - - aws_iot_shadow_internal_delete_request_json(deleteRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_DELETE, deleteRequestJson, TEST_JSON_SIZE, actionCallback, - NULL, 4, false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_DELETE_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_DELETE_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(DELETE_ACCEPTED_TOPIC, strlen(DELETE_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_DELETE_DOCUMENT, jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_DELETE, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_ACCEPTED, ackStatusRx); - - IOT_DEBUG("-->Success - Delete json document \n"); -} - -TEST_C(ShadowActionTests, UpdateTheJSONDocument) { - IoT_Error_t ret_val = SUCCESS; - char updateRequestJson[SIZE_OF_UPDATE_DOCUMENT]; - char expectedUpdateRequestJson[SIZE_OF_UPDATE_DOCUMENT]; - double doubleData = 4.0908f; - float floatData = 3.445f; - bool boolData = true; - jsonStruct_t dataFloatHandler; - jsonStruct_t dataDoubleHandler; - jsonStruct_t dataBoolHandler; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Update json document \n"); - - dataFloatHandler.cb = NULL; - dataFloatHandler.pData = &floatData; - dataFloatHandler.dataLength = sizeof(float); - dataFloatHandler.pKey = "floatData"; - dataFloatHandler.type = SHADOW_JSON_FLOAT; - - dataDoubleHandler.cb = NULL; - dataDoubleHandler.pData = &doubleData; - dataDoubleHandler.dataLength = sizeof(double); - dataDoubleHandler.pKey = "doubleData"; - dataDoubleHandler.type = SHADOW_JSON_DOUBLE; - - dataBoolHandler.cb = NULL; - dataBoolHandler.pData = &boolData; - dataBoolHandler.dataLength = sizeof(bool); - dataBoolHandler.pKey = "boolData"; - dataBoolHandler.type = SHADOW_JSON_BOOL; - - ret_val = aws_iot_shadow_init_json_document(updateRequestJson, SIZE_OF_UPDATE_DOCUMENT); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - ret_val = aws_iot_shadow_add_reported(updateRequestJson, SIZE_OF_UPDATE_DOCUMENT, 2, &dataDoubleHandler, - &dataFloatHandler); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - ret_val = aws_iot_shadow_add_desired(updateRequestJson, SIZE_OF_UPDATE_DOCUMENT, 1, &dataBoolHandler); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - ret_val = aws_iot_finalize_json_document(updateRequestJson, SIZE_OF_UPDATE_DOCUMENT); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - snprintf(expectedUpdateRequestJson, SIZE_OF_UPDATE_DOCUMENT, - "{\"state\":{\"reported\":{\"doubleData\":4.090800,\"floatData\":3.445000},\"desired\":{\"boolData\":true}}, \"clientToken\":\"%s-0\"}", - AWS_IOT_MQTT_CLIENT_ID); - CHECK_EQUAL_C_STRING(expectedUpdateRequestJson, updateRequestJson); - - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_UPDATE, updateRequestJson, SIZE_OF_UPDATE_DOCUMENT, actionCallback, - NULL, 4, false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - ResetTLSBuffer(); - snprintf(jsonFullDocument, 200, "%s", ""); - params.payloadLen = strlen(TEST_JSON_RESPONSE_UPDATE_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_UPDATE_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(UPDATE_ACCEPTED_TOPIC, strlen(UPDATE_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_UPDATE_DOCUMENT, jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_UPDATE, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_ACCEPTED, ackStatusRx); - - IOT_DEBUG("-->Success - Update json document \n"); -} - -TEST_C(ShadowActionTests, GetTheFullJSONDocumentTimeout) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Get full json document timeout \n"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - sleep(4 + 1); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_FULL_DOCUMENT, jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_GET, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_TIMEOUT, ackStatusRx); - - IOT_DEBUG("-->Success - Get full json document timeout \n"); -} - -// Subscribe and UnSubscribe on reception of thing names. Will perform the test with Get operation -TEST_C(ShadowActionTests, SubscribeToAcceptedRejectedOnGet) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - - uint8_t firstByte, secondByte; - uint16_t topicNameLen; - char topicName[128] = "test"; - - IOT_DEBUG("-->Running Shadow Action Tests - Subscribe to get/accepted and get/rejected \n"); - - lastSubscribeMsgLen = 11; - snprintf(LastSubscribeMessage, lastSubscribeMsgLen, "No Message"); - secondLastSubscribeMsgLen = 11; - snprintf(SecondLastSubscribeMessage, secondLastSubscribeMsgLen, "No Message"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - CHECK_EQUAL_C_STRING(GET_REJECTED_TOPIC, LastSubscribeMessage); - CHECK_EQUAL_C_STRING(GET_ACCEPTED_TOPIC, SecondLastSubscribeMessage); - - firstByte = (uint8_t)(TxBuffer.pBuffer[2]); - secondByte = (uint8_t)(TxBuffer.pBuffer[3]); - topicNameLen = (uint16_t) (secondByte + (256 * firstByte)); - - snprintf(topicName, topicNameLen + 1u, "%s", &(TxBuffer.pBuffer[4])); // Added one for null character - - // Verify publish happens - CHECK_EQUAL_C_STRING(GET_PUB_TOPIC, topicName); - - IOT_DEBUG("-->Success - Subscribe to get/accepted and get/rejected \n"); -} - -TEST_C(ShadowActionTests, UnSubscribeToAcceptedRejectedOnGetResponse) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - unsubscribe to get/accepted and get/rejected on response \n"); - - lastSubscribeMsgLen = 11; - snprintf(LastSubscribeMessage, lastSubscribeMsgLen, "No Message"); - secondLastSubscribeMsgLen = 11; - snprintf(SecondLastSubscribeMessage, secondLastSubscribeMsgLen, "No Message"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - CHECK_EQUAL_C_STRING(GET_REJECTED_TOPIC, LastSubscribeMessage); - CHECK_EQUAL_C_STRING(GET_ACCEPTED_TOPIC, SecondLastSubscribeMessage); - - IOT_DEBUG("-->Success - unsubscribe to get/accepted and get/rejected on response \n"); -} - -TEST_C(ShadowActionTests, UnSubscribeToAcceptedRejectedOnGetTimeout) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Unsubscribe to get/accepted and get/rejected on get timeout \n"); - - snprintf(jsonFullDocument, 200, "aa"); - lastSubscribeMsgLen = 11; - snprintf(LastSubscribeMessage, lastSubscribeMsgLen, "No Message"); - secondLastSubscribeMsgLen = 11; - snprintf(SecondLastSubscribeMessage, secondLastSubscribeMsgLen, "No Message"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - sleep(4 + 1); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("aa", jsonFullDocument); - - CHECK_EQUAL_C_STRING(GET_REJECTED_TOPIC, LastSubscribeMessage); - CHECK_EQUAL_C_STRING(GET_ACCEPTED_TOPIC, SecondLastSubscribeMessage); - - IOT_DEBUG("-->Success - Unsubscribe to get/accepted and get/rejected on get timeout \n"); -} - - -TEST_C(ShadowActionTests, UnSubscribeToAcceptedRejectedOnGetTimeoutWithSticky) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - No unsubscribe to get/accepted and get/rejected on get timeout with a sticky subscription \n"); - - snprintf(jsonFullDocument, 200, "timeout"); - lastSubscribeMsgLen = 11; - snprintf(LastSubscribeMessage, lastSubscribeMsgLen, "No Message"); - secondLastSubscribeMsgLen = 11; - snprintf(SecondLastSubscribeMessage, secondLastSubscribeMsgLen, "No Message"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - true); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - sleep(4 + 1); - - ResetTLSBuffer(); - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("timeout", jsonFullDocument); - - CHECK_EQUAL_C_STRING(GET_REJECTED_TOPIC, LastSubscribeMessage); - CHECK_EQUAL_C_STRING(GET_ACCEPTED_TOPIC, SecondLastSubscribeMessage); - - IOT_DEBUG("-->Success - No unsubscribe to get/accepted and get/rejected on get timeout with a sticky subscription \n"); -} - -#define TEST_JSON_RESPONSE_FULL_DOCUMENT_WITH_VERSION(num) "{\"state\":{\"reported\":{\"sensor1\":98}}, \"clientToken\":\"" AWS_IOT_MQTT_CLIENT_ID "-0\",\"version\":" #num "}" - -TEST_C(ShadowActionTests, GetVersionFromAckStatus) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - IoT_Publish_Message_Params params2; - - IOT_DEBUG("-->Running Shadow Action Tests - Get version from Ack status \n"); - - snprintf(jsonFullDocument, 200, "timeout"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - true); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - ResetTLSBuffer(); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT_WITH_VERSION(1); - params.payloadLen = strlen(params.payload); - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_C(1u == aws_iot_shadow_get_last_received_version()); - - ResetTLSBuffer(); - params2.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT_WITH_VERSION(132387); - params2.payloadLen = strlen(params2.payload); - params2.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params2, - params2.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_C(132387u == aws_iot_shadow_get_last_received_version()); - - IOT_DEBUG("-->Success - Get version from Ack status \n"); -} - -#define TEST_JSON_RESPONSE_FULL_DOCUMENT_ALWAYS_WRONG_TOKEN "{\"state\":{\"reported\":{\"sensor1\":98}}, \"clientToken\":\"TroubleToken1234\"}" - -TEST_C(ShadowActionTests, WrongTokenInGetResponse) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Wrong token in get response \n"); - - snprintf(jsonFullDocument, 200, "timeout"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - sleep(4 + 1); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT_ALWAYS_WRONG_TOKEN); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT_ALWAYS_WRONG_TOKEN; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("timeout", jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_GET, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_TIMEOUT, ackStatusRx); - - IOT_DEBUG("-->Success - Wrong token in get response \n"); -} - -#define TEST_JSON_RESPONSE_FULL_DOCUMENT_NO_TOKEN "{\"state\":{\"reported\":{\"sensor1\":98}}}" - -TEST_C(ShadowActionTests, NoTokenInGetResponse) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - No token in get response \n"); - - snprintf(jsonFullDocument, 200, "timeout"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - sleep(4 + 1); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT_NO_TOKEN); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT_NO_TOKEN; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("timeout", jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_GET, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_TIMEOUT, ackStatusRx); - - IOT_DEBUG("-->Success - No token in get response \n"); -} - -TEST_C(ShadowActionTests, InvalidInboundJSONInGetResponse) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Invalid inbound json in get response \n"); - - snprintf(jsonFullDocument, 200, "NOT_VISITED"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - params.payloadLen = strlen("{\"state\":{{"); - params.payload = "{\"state\":{{"; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("NOT_VISITED", jsonFullDocument); - - IOT_DEBUG("-->Success - Invalid inbound json in get response \n"); -} - -TEST_C(ShadowActionTests, AcceptedSubFailsGetRequest) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Accepted sub fails get request \n"); - - snprintf(jsonFullDocument, 200, "NOT_SENT"); - - ResetTLSBuffer(); - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, ret_val); // Should never subscribe and publish - - ResetTLSBuffer(); - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("NOT_SENT", jsonFullDocument); // Never called callback - - IOT_DEBUG("-->Success - Accepted sub fails get request \n"); -} - -TEST_C(ShadowActionTests, RejectedSubFailsGetRequest) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Rejected sub fails get request \n"); - - snprintf(jsonFullDocument, 200, "NOT_SENT"); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferForSuback(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params); - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, ret_val); // Should never subscribe and publish - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(GET_REJECTED_TOPIC, strlen(GET_REJECTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("NOT_SENT", jsonFullDocument); // Never called callback - - IOT_DEBUG("-->Success - Rejected sub fails get request \n"); -} - -TEST_C(ShadowActionTests, PublishFailsGetRequest) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - publish fails on get request \n"); - - snprintf(jsonFullDocument, 200, "NOT_SENT"); - - ResetTLSBuffer(); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, ret_val); // Should never subscribe and publish - - ResetTLSBuffer(); - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING("NOT_SENT", jsonFullDocument); // Never called callback - - IOT_DEBUG("-->Success - publish fails on get request \n"); -} - -TEST_C(ShadowActionTests, StickyNonStickyNeverConflict) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - IOT_DEBUG("-->Running Shadow Action Tests - Sticky and non-sticky subscriptions do not conflict \n"); - - lastSubscribeMsgLen = 11; - snprintf(LastSubscribeMessage, lastSubscribeMsgLen, "No Message"); - secondLastSubscribeMsgLen = 11; - snprintf(SecondLastSubscribeMessage, secondLastSubscribeMsgLen, "No Message"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - true); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_FULL_DOCUMENT, jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_GET, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_ACCEPTED, ackStatusRx); - - lastSubscribeMsgLen = 11; - snprintf(LastSubscribeMessage, lastSubscribeMsgLen, "No Message"); - secondLastSubscribeMsgLen = 11; - snprintf(SecondLastSubscribeMessage, secondLastSubscribeMsgLen, "No Message"); - - // Non-sticky shadow get, same thing name. Should never unsub since they are sticky - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - ResetTLSBuffer(); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_FULL_DOCUMENT, jsonFullDocument); - CHECK_EQUAL_C_INT(SHADOW_GET, actionRx); - CHECK_EQUAL_C_INT(SHADOW_ACK_ACCEPTED, ackStatusRx); - - CHECK_EQUAL_C_STRING("No Message", LastSubscribeMessage); - CHECK_EQUAL_C_STRING("No Message", SecondLastSubscribeMessage); - - IOT_DEBUG("-->Success - Sticky and non-sticky subscriptions do not conflict \n"); - -} - -TEST_C(ShadowActionTests, ACKWaitingMoreThanAllowed) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - - IOT_DEBUG("-->Running Shadow Action Tests - Ack waiting more than allowed wait time \n"); - - // 1st - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 2nd - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 3rd - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 4th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 5th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 6th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 7th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 8th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 9th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 10th - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // 11th - // Should return some error code, since we are running out of ACK space - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, - 100, false); // 100 sec to timeout - CHECK_EQUAL_C_INT(FAILURE, ret_val); - - IOT_DEBUG("-->Success - Ack waiting more than allowed wait time \n"); -} - -TEST_C(ShadowActionTests, InboundDataTooBigForBuffer) { - uint32_t i = 0; - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - char expectedCallbackString[AWS_IOT_MQTT_RX_BUF_LEN + 2]; - - IOT_DEBUG("-->Running Shadow Action Tests - Inbound data too big for buffer \n"); - - snprintf(jsonFullDocument, 200, "NOT_VISITED"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - for(i = 0; i < AWS_IOT_MQTT_RX_BUF_LEN; i++) { - expectedCallbackString[i] = 'X'; - } - expectedCallbackString[i + 1] = '\0'; - - params.payloadLen = strlen(expectedCallbackString); - params.payload = expectedCallbackString; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - CHECK_EQUAL_C_INT(MQTT_RX_BUFFER_TOO_SHORT_ERROR, ret_val); - CHECK_EQUAL_C_STRING("NOT_VISITED", jsonFullDocument); - - IOT_DEBUG("-->Success - Inbound data too big for buffer \n"); -} - -#define TEST_JSON_RESPONSE_NO_TOKEN "{\"state\":{\"reported\":{\"sensor1\":98}}}" - -TEST_C(ShadowActionTests, NoClientTokenForShadowAction) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - uint8_t firstByte, secondByte; - uint16_t topicNameLen; - char topicName[128] = "test"; - - IOT_DEBUG("-->Running Shadow Action Tests - No client token for shadow action \n"); - - snprintf(getRequestJson, TEST_JSON_SIZE, "{}"); - snprintf(jsonFullDocument, 200, "NOT_VISITED"); - - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, actionCallback, NULL, 4, - false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_NO_TOKEN); - params.payload = TEST_JSON_RESPONSE_NO_TOKEN; - params.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // Should never subscribe to accepted/rejected topics since we have no token to track the response - CHECK_EQUAL_C_STRING("NOT_VISITED", jsonFullDocument); - - firstByte = (uint8_t)(TxBuffer.pBuffer[2]); - secondByte = (uint8_t)(TxBuffer.pBuffer[3]); - topicNameLen = (uint16_t) (secondByte + (256 * firstByte)); - - snprintf(topicName, topicNameLen + 1u, "%s", &(TxBuffer.pBuffer[4])); // Added one for null character - - // Verify publish happens - CHECK_EQUAL_C_STRING(GET_PUB_TOPIC, topicName); - - IOT_DEBUG("-->Success - No client token for shadow action \n"); -} - -TEST_C(ShadowActionTests, IsReceivedJsonValid) -{ - bool ret_val; - char getRequestJson[TEST_JSON_SIZE]; - - IOT_DEBUG("-->Running Shadow Action Tests - IsReceivedJsonValid \n"); - - snprintf(getRequestJson, TEST_JSON_SIZE, TEST_JSON_RESPONSE_FULL_DOCUMENT); - - //Test by cutting the JSON document - ret_val = isReceivedJsonValid(getRequestJson, 3); - CHECK_EQUAL_C_INT(false, ret_val); - - //Happy path - ret_val = isReceivedJsonValid(getRequestJson, TEST_JSON_SIZE); - CHECK_EQUAL_C_INT(true, ret_val); - - IOT_DEBUG("-->Success - IsReceivedJsonValid"); -} - -TEST_C(ShadowActionTests, ExtractClientToken) -{ - bool ret_val; - char getRequestJson[TEST_JSON_SIZE]; - char extractedClientToken[MAX_SIZE_CLIENT_ID_WITH_SEQUENCE]; - - IOT_DEBUG("-->Running Shadow Action Tests - ExtractClientToken \n"); - - //Try JSON with no token - snprintf(getRequestJson, TEST_JSON_SIZE, "{}"); - ret_val = extractClientToken(getRequestJson, TEST_JSON_SIZE, extractedClientToken, MAX_SIZE_CLIENT_ID_WITH_SEQUENCE ); - CHECK_EQUAL_C_INT(false, ret_val); - - //Try JSON with token but not enough memory - snprintf(getRequestJson, TEST_JSON_SIZE, TEST_JSON_RESPONSE_FULL_DOCUMENT); - ret_val = extractClientToken(getRequestJson, TEST_JSON_SIZE, extractedClientToken, 1 ); - CHECK_EQUAL_C_INT(false, ret_val); - - //Happy path - ret_val = extractClientToken(getRequestJson, TEST_JSON_SIZE, extractedClientToken, MAX_SIZE_CLIENT_ID_WITH_SEQUENCE ); - CHECK_EQUAL_C_INT(true, ret_val); - - IOT_DEBUG("-->Success - ExtractClientToken"); -} - -TEST_C(ShadowActionTests, GetAndDeleteRequest) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - - IOT_DEBUG("-->Running Shadow Action Tests - GetAndDeleteRequest \n"); - - ret_val = aws_iot_shadow_internal_get_request_json(NULL, TEST_JSON_SIZE); - CHECK_EQUAL_C_INT(FAILURE, ret_val); - - ret_val = aws_iot_shadow_internal_get_request_json(getRequestJson, 1); - CHECK_EQUAL_C_INT(FAILURE, ret_val); - - ret_val = aws_iot_shadow_internal_delete_request_json(NULL, TEST_JSON_SIZE); - CHECK_EQUAL_C_INT(FAILURE, ret_val); - - ret_val = aws_iot_shadow_internal_delete_request_json(getRequestJson, 1); - CHECK_EQUAL_C_INT(FAILURE, ret_val); - - IOT_DEBUG("-->Success - GetAndDeleteRequest"); -} - -TEST_C(ShadowActionTests, NoCallbackForShadowAction) { - IoT_Error_t ret_val = SUCCESS; - char getRequestJson[TEST_JSON_SIZE]; - IoT_Publish_Message_Params params; - - uint8_t firstByte, secondByte; - uint16_t topicNameLen; - char topicName[128] = "test"; - - IOT_DEBUG("-->Running Shadow Action Tests - No callback for shadow action \n"); - - snprintf(jsonFullDocument, 200, "NOT_VISITED"); - - aws_iot_shadow_internal_get_request_json(getRequestJson, TEST_JSON_SIZE); - ret_val = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_GET, getRequestJson, TEST_JSON_SIZE, NULL, NULL, 4, false); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - params.payloadLen = strlen(TEST_JSON_RESPONSE_FULL_DOCUMENT); - params.payload = TEST_JSON_RESPONSE_FULL_DOCUMENT; - setTLSRxBufferWithMsgOnSubscribedTopic(GET_ACCEPTED_TOPIC, strlen(GET_ACCEPTED_TOPIC), QOS0, params, - params.payload); - ret_val = aws_iot_shadow_yield(&client, 200); - - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - // Should never subscribe to accepted/rejected topics since we have no callback to track the response - CHECK_EQUAL_C_STRING("NOT_VISITED", jsonFullDocument); - - firstByte = (uint8_t)(TxBuffer.pBuffer[2]); - secondByte = (uint8_t)(TxBuffer.pBuffer[3]); - topicNameLen = (uint16_t) (secondByte + (256 * firstByte)); - - snprintf(topicName, topicNameLen + 1u, "%s", &(TxBuffer.pBuffer[4])); // Added one for null character - - // Verify publish happens - CHECK_EQUAL_C_STRING(GET_PUB_TOPIC, topicName); - - IOT_DEBUG("-->Success - No callback for shadow action"); -} diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_delta.cpp b/tests/unit/src/aws_iot_tests_unit_shadow_delta.cpp deleted file mode 100644 index db9c0b25dd..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_delta.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_delta.cpp - * @brief IoT Client Unit Testing - Shadow Delta Tests - */ - -#include -#include - -TEST_GROUP_C(ShadowDeltaTest){ - TEST_GROUP_C_SETUP_WRAPPER(ShadowDeltaTest) - TEST_GROUP_C_TEARDOWN_WRAPPER(ShadowDeltaTest) -}; - -TEST_GROUP_C_WRAPPER(ShadowDeltaTest, registerDeltaSuccess) -TEST_GROUP_C_WRAPPER(ShadowDeltaTest, registerDeltaInt) -TEST_GROUP_C_WRAPPER(ShadowDeltaTest, registerDeltaIntNoCallback) -TEST_GROUP_C_WRAPPER(ShadowDeltaTest, DeltaNestedObject) -TEST_GROUP_C_WRAPPER(ShadowDeltaTest, DeltaVersionIgnoreOldVersion) diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_delta_helper.c b/tests/unit/src/aws_iot_tests_unit_shadow_delta_helper.c deleted file mode 100644 index 1e6cc2a0ac..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_delta_helper.c +++ /dev/null @@ -1,324 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_delta_helper.c - * @brief IoT Client Unit Testing - Shadow Delta Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_shadow_interface.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_log.h" - -static AWS_IoT_Client client; -static IoT_Client_Connect_Params connectParams; -static ShadowInitParameters_t shadowInitParams; -static ShadowConnectParameters_t shadowConnectParams; - -static char receivedNestedObject[100] = ""; -static char sentNestedObjectData[100] = "{\"sensor1\":23}"; -static char shadowDeltaTopic[MAX_SHADOW_TOPIC_LENGTH_BYTES]; - -#define SHADOW_DELTA_UPDATE "$aws/things/%s/shadow/update/delta" - -#undef AWS_IOT_MY_THING_NAME -#define AWS_IOT_MY_THING_NAME "AWS-IoT-C-SDK" - -void genericCallback(const char *pJsonStringData, uint32_t JsonStringDataLen, jsonStruct_t *pContext) { - printf("\nkey[%s]==Data[%.*s]\n", pContext->pKey, JsonStringDataLen, pJsonStringData); -} - -void nestedObjectCallback(const char *pJsonStringData, uint32_t JsonStringDataLen, jsonStruct_t *pContext) { - printf("\nkey[%s]==Data[%.*s]\n", pContext->pKey, JsonStringDataLen, pJsonStringData); - snprintf(receivedNestedObject, 100, "%.*s", JsonStringDataLen, pJsonStringData); -} - -TEST_GROUP_C_SETUP(ShadowDeltaTest) { - IoT_Error_t ret_val = SUCCESS; - - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.pClientCRT = AWS_IOT_CERTIFICATE_FILENAME; - shadowInitParams.pRootCA = AWS_IOT_ROOT_CA_FILENAME; - shadowInitParams.pClientKey = AWS_IOT_PRIVATE_KEY_FILENAME; - shadowInitParams.disconnectHandler = NULL; - shadowInitParams.enableAutoReconnect = false; - ret_val = aws_iot_shadow_init(&client, &shadowInitParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - shadowConnectParams.pMyThingName = AWS_IOT_MY_THING_NAME; - shadowConnectParams.pMqttClientId = AWS_IOT_MQTT_CLIENT_ID; - shadowConnectParams.mqttClientIdLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - ret_val = aws_iot_shadow_connect(&client, &shadowConnectParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - snprintf(shadowDeltaTopic, MAX_SHADOW_TOPIC_LENGTH_BYTES, SHADOW_DELTA_UPDATE, AWS_IOT_MY_THING_NAME); -} - -TEST_GROUP_C_TEARDOWN(ShadowDeltaTest) { - -} - -TEST_C(ShadowDeltaTest, registerDeltaSuccess) { - jsonStruct_t windowHandler; - char deltaJSONString[] = "{\"state\":{\"delta\":{\"window\":true}},\"version\":1}"; - bool windowOpenData = false; - IoT_Publish_Message_Params params; - IoT_Error_t ret_val = SUCCESS; - - IOT_DEBUG("\n-->Running Shadow Delta Tests - Register delta success \n"); - - windowHandler.cb = genericCallback; - windowHandler.pKey = "window"; - windowHandler.type = SHADOW_JSON_BOOL; - windowHandler.pData = &windowOpenData; - windowHandler.dataLength = sizeof(bool); - - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferForSuback(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params); - - ret_val = aws_iot_shadow_register_delta(&client, &windowHandler); - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - ret_val = aws_iot_shadow_yield(&client, 3000); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - CHECK_EQUAL_C_INT(true, windowOpenData); -} - - -TEST_C(ShadowDeltaTest, registerDeltaInt) { - IoT_Error_t ret_val = SUCCESS; - jsonStruct_t intHandler; - int32_t intData = 0; - char deltaJSONString[] = "{\"state\":{\"delta\":{\"length\":23}},\"version\":1}"; - IoT_Publish_Message_Params params; - - IOT_DEBUG("\n-->Running Shadow Delta Tests - Register delta integer \n"); - - intHandler.cb = genericCallback; - intHandler.pKey = "length"; - intHandler.type = SHADOW_JSON_INT32; - intHandler.pData = &intData; - intHandler.dataLength = sizeof(int32_t); - - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferForSuback(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params); - - ret_val = aws_iot_shadow_register_delta(&client, &intHandler); - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 3000); - CHECK_EQUAL_C_INT(23, intData); -} - -TEST_C(ShadowDeltaTest, registerDeltaIntNoCallback) { - IoT_Error_t ret_val = SUCCESS; - jsonStruct_t intHandler; - int32_t intData = 0; - char deltaJSONString[] = "{\"state\":{\"delta\":{\"length_nocb\":23}},\"version\":1}"; - IoT_Publish_Message_Params params; - - IOT_DEBUG("\n-->Running Shadow Delta Tests - Register delta integer with no callback \n"); - - intHandler.cb = NULL; - intHandler.pKey = "length_nocb"; - intHandler.type = SHADOW_JSON_INT32; - intHandler.pData = &intData; - intHandler.dataLength = sizeof(int32_t); - - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferForSuback(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params); - - ret_val = aws_iot_shadow_register_delta(&client, &intHandler); - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 3000); - CHECK_EQUAL_C_INT(23, intData); -} - -TEST_C(ShadowDeltaTest, DeltaNestedObject) { - IoT_Error_t ret_val = SUCCESS; - IoT_Publish_Message_Params params; - jsonStruct_t nestedObjectHandler; - char nestedObject[100]; - char deltaJSONString[100]; - - printf("\n-->Running Shadow Delta Tests - Delta received with nested object \n"); - - nestedObjectHandler.cb = nestedObjectCallback; - nestedObjectHandler.pKey = "sensors"; - nestedObjectHandler.type = SHADOW_JSON_OBJECT; - nestedObjectHandler.pData = &nestedObject; - nestedObjectHandler.dataLength = 100; - - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":1}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferForSuback(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params); - - ret_val = aws_iot_shadow_register_delta(&client, &nestedObjectHandler); - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 3000); - CHECK_EQUAL_C_STRING(sentNestedObjectData, receivedNestedObject); -} - - -// Send back to back version and ensure a wrong version is ignored with old message enabled -TEST_C(ShadowDeltaTest, DeltaVersionIgnoreOldVersion) { - IoT_Error_t ret_val = SUCCESS; - char deltaJSONString[100]; - jsonStruct_t nestedObjectHandler; - char nestedObject[100]; - IoT_Publish_Message_Params params; - - printf("\n-->Running Shadow Delta Tests - delta received, old version ignored \n"); - - nestedObjectHandler.cb = nestedObjectCallback; - nestedObjectHandler.pKey = "sensors"; - nestedObjectHandler.type = SHADOW_JSON_OBJECT; - nestedObjectHandler.pData = &nestedObject; - nestedObjectHandler.dataLength = 100; - - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":1}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferForSuback(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params); - - ret_val = aws_iot_shadow_register_delta(&client, &nestedObjectHandler); - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(sentNestedObjectData, receivedNestedObject); - - snprintf(receivedNestedObject, 100, " "); - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":2}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(sentNestedObjectData, receivedNestedObject); - - snprintf(receivedNestedObject, 100, " "); - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":2}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(" ", receivedNestedObject); - - snprintf(receivedNestedObject, 100, " "); - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":3}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(sentNestedObjectData, receivedNestedObject); - - aws_iot_shadow_reset_last_received_version(); - - snprintf(receivedNestedObject, 100, " "); - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":3}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(sentNestedObjectData, receivedNestedObject); - - snprintf(receivedNestedObject, 100, " "); - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":3}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(" ", receivedNestedObject); - - aws_iot_shadow_disable_discard_old_delta_msgs(); - - snprintf(receivedNestedObject, 100, " "); - snprintf(deltaJSONString, 100, "{\"state\":{\"delta\":{\"%s\":%s}},\"version\":3}", nestedObjectHandler.pKey, - sentNestedObjectData); - params.payloadLen = strlen(deltaJSONString); - params.payload = deltaJSONString; - params.qos = QOS0; - - ResetTLSBuffer(); - setTLSRxBufferWithMsgOnSubscribedTopic(shadowDeltaTopic, strlen(shadowDeltaTopic), QOS0, params, params.payload); - - aws_iot_shadow_yield(&client, 100); - CHECK_EQUAL_C_STRING(sentNestedObjectData, receivedNestedObject); -} diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_json_builder.cpp b/tests/unit/src/aws_iot_tests_unit_shadow_json_builder.cpp deleted file mode 100644 index 8feb144e57..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_json_builder.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_json_builder.cpp - * @brief IoT Client Unit Testing - Shadow JSON Builder Tests - */ - -#include -#include - -TEST_GROUP_C(ShadowJsonBuilderTests){ - TEST_GROUP_C_SETUP_WRAPPER(ShadowJsonBuilderTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(ShadowJsonBuilderTests) -}; - -TEST_GROUP_C_WRAPPER(ShadowJsonBuilderTests, UpdateTheJSONDocumentBuilder) -TEST_GROUP_C_WRAPPER(ShadowJsonBuilderTests, PassingNullValue) -TEST_GROUP_C_WRAPPER(ShadowJsonBuilderTests, SmallBuffer) diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_json_builder_helper.c b/tests/unit/src/aws_iot_tests_unit_shadow_json_builder_helper.c deleted file mode 100644 index 3541341b5e..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_json_builder_helper.c +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_json_builder_helper.c - * @brief IoT Client Unit Testing - Shadow JSON Builder Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_shadow_actions.h" -#include "aws_iot_log.h" -#include "aws_iot_tests_unit_helper_functions.h" - -static jsonStruct_t dataFloatHandler; -static jsonStruct_t dataDoubleHandler; -static double doubleData = 4.0908f; -static float floatData = 3.445f; -static AWS_IoT_Client iotClient; -static IoT_Client_Connect_Params connectParams; -static ShadowInitParameters_t shadowInitParams; -static ShadowConnectParameters_t shadowConnectParams; - -TEST_GROUP_C_SETUP(ShadowJsonBuilderTests) { - IoT_Error_t ret_val; - - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.pClientCRT = AWS_IOT_CERTIFICATE_FILENAME; - shadowInitParams.pRootCA = AWS_IOT_ROOT_CA_FILENAME; - shadowInitParams.pClientKey = AWS_IOT_PRIVATE_KEY_FILENAME; - shadowInitParams.disconnectHandler = NULL; - shadowInitParams.enableAutoReconnect = false; - ret_val = aws_iot_shadow_init(&iotClient, &shadowInitParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - shadowConnectParams.pMyThingName = AWS_IOT_MY_THING_NAME; - shadowConnectParams.pMqttClientId = AWS_IOT_MQTT_CLIENT_ID; - shadowConnectParams.mqttClientIdLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - ret_val = aws_iot_shadow_connect(&iotClient, &shadowConnectParams); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - dataFloatHandler.cb = NULL; - dataFloatHandler.pData = &floatData; - dataFloatHandler.pKey = "floatData"; - dataFloatHandler.type = SHADOW_JSON_FLOAT; - dataFloatHandler.dataLength = sizeof(float); - - dataDoubleHandler.cb = NULL; - dataDoubleHandler.pData = &doubleData; - dataDoubleHandler.pKey = "doubleData"; - dataDoubleHandler.type = SHADOW_JSON_DOUBLE; - dataDoubleHandler.dataLength = sizeof(double); -} - -TEST_GROUP_C_TEARDOWN(ShadowJsonBuilderTests) { - /* Clean up. Not checking return code here because this is common to all tests. - * A test might have already caused a disconnect by this point. - */ - IoT_Error_t rc = aws_iot_mqtt_disconnect(&iotClient); - IOT_UNUSED(rc); -} - -#define TEST_JSON_RESPONSE_UPDATE_DOCUMENT "{\"state\":{\"reported\":{\"doubleData\":4.090800,\"floatData\":3.445000}}, \"clientToken\":\"" AWS_IOT_MQTT_CLIENT_ID "-0\"}" - -#define SIZE_OF_UPFATE_BUF 200 - -TEST_C(ShadowJsonBuilderTests, UpdateTheJSONDocumentBuilder) { - IoT_Error_t ret_val; - char updateRequestJson[SIZE_OF_UPFATE_BUF]; - size_t jsonBufSize = sizeof(updateRequestJson) / sizeof(updateRequestJson[0]); - - IOT_DEBUG("\n-->Running Shadow Json Builder Tests - Update the Json document builder \n"); - - ret_val = aws_iot_shadow_init_json_document(updateRequestJson, jsonBufSize); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - ret_val = aws_iot_shadow_add_reported(updateRequestJson, jsonBufSize, 2, &dataDoubleHandler, &dataFloatHandler); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - ret_val = aws_iot_finalize_json_document(updateRequestJson, jsonBufSize); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - - CHECK_EQUAL_C_STRING(TEST_JSON_RESPONSE_UPDATE_DOCUMENT, updateRequestJson); -} - -TEST_C(ShadowJsonBuilderTests, PassingNullValue) { - IoT_Error_t ret_val; - - IOT_DEBUG("\n-->Running Shadow Json Builder Tests - Passing Null value to Shadow json builder \n"); - - ret_val = aws_iot_shadow_init_json_document(NULL, SIZE_OF_UPFATE_BUF); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, ret_val); - ret_val = aws_iot_shadow_add_reported(NULL, SIZE_OF_UPFATE_BUF, 2, &dataDoubleHandler, &dataFloatHandler); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, ret_val); - ret_val = aws_iot_shadow_add_desired(NULL, SIZE_OF_UPFATE_BUF, 2, &dataDoubleHandler, &dataFloatHandler); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, ret_val); - ret_val = aws_iot_finalize_json_document(NULL, SIZE_OF_UPFATE_BUF); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, ret_val); -} - -TEST_C(ShadowJsonBuilderTests, SmallBuffer) { - IoT_Error_t ret_val; - char updateRequestJson[14]; - size_t jsonBufSize = sizeof(updateRequestJson) / sizeof(updateRequestJson[0]); - - IOT_DEBUG("\n-->Running Shadow Json Builder Tests - Json Buffer is too small \n"); - - ret_val = aws_iot_shadow_init_json_document(updateRequestJson, jsonBufSize); - CHECK_EQUAL_C_INT(SUCCESS, ret_val); - ret_val = aws_iot_shadow_add_reported(updateRequestJson, jsonBufSize, 2, &dataDoubleHandler, &dataFloatHandler); - CHECK_EQUAL_C_INT(SHADOW_JSON_BUFFER_TRUNCATED, ret_val); - ret_val = aws_iot_shadow_add_desired(updateRequestJson, jsonBufSize, 2, &dataDoubleHandler, &dataFloatHandler); - CHECK_EQUAL_C_INT(SHADOW_JSON_ERROR, ret_val); - ret_val = aws_iot_finalize_json_document(updateRequestJson, jsonBufSize); - CHECK_EQUAL_C_INT(SHADOW_JSON_ERROR, ret_val); -} diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_null_fields.cpp b/tests/unit/src/aws_iot_tests_unit_shadow_null_fields.cpp deleted file mode 100644 index dd4d02a6b3..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_null_fields.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_null_fields.cpp - * @brief IoT Client Unit Testing - Shadow APIs NULL field Tests - */ - -#include -#include - -TEST_GROUP_C(ShadowNullFields){ - TEST_GROUP_C_SETUP_WRAPPER(ShadowNullFields) - TEST_GROUP_C_TEARDOWN_WRAPPER(ShadowNullFields) -}; - -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientInit) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientConnect) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullHost) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullPort) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientID) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullUpdateDocument) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientYield) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientDisconnect) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientShadowGet) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientShadowUpdate) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientShadowDelete) -TEST_GROUP_C_WRAPPER(ShadowNullFields, NullClientSetAutoreconnect) diff --git a/tests/unit/src/aws_iot_tests_unit_shadow_null_fields_helper.c b/tests/unit/src/aws_iot_tests_unit_shadow_null_fields_helper.c deleted file mode 100644 index 0848bd0a52..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_shadow_null_fields_helper.c +++ /dev/null @@ -1,151 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_shadow_null_fields_helper.c - * @brief IoT Client Unit Testing - Shadow APIs NULL field Tests helper - */ - -#include -#include -#include -#include - -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_shadow_interface.h" -#include "aws_iot_shadow_actions.h" -#include "aws_iot_log.h" - -static AWS_IoT_Client client; -static ShadowInitParameters_t shadowInitParams; -static ShadowConnectParameters_t shadowConnectParams; - -static Shadow_Ack_Status_t ackStatusRx; -static ShadowActions_t actionRx; -static char jsonFullDocument[200]; - -void actionCallbackNullTest(const char *pThingName, ShadowActions_t action, Shadow_Ack_Status_t status, - const char *pReceivedJsonDocument, void *pContextData) { - IOT_UNUSED(pThingName); - IOT_UNUSED(pContextData); - IOT_DEBUG("%s", pReceivedJsonDocument); - actionRx = action; - ackStatusRx = status; - if(status != SHADOW_ACK_TIMEOUT) { - strcpy(jsonFullDocument, pReceivedJsonDocument); - } -} - -TEST_GROUP_C_SETUP(ShadowNullFields) { - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(ShadowNullFields) { } - -TEST_C(ShadowNullFields, NullHost) { - shadowInitParams.pHost = NULL; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.disconnectHandler = NULL; - IoT_Error_t rc = aws_iot_shadow_init(&client, &shadowInitParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullPort) { - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = 0; - shadowInitParams.disconnectHandler = NULL; - IoT_Error_t rc = aws_iot_shadow_init(&client, &shadowInitParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientID) { - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.pClientCRT = AWS_IOT_CERTIFICATE_FILENAME; - shadowInitParams.pRootCA = AWS_IOT_ROOT_CA_FILENAME; - shadowInitParams.pClientKey = AWS_IOT_PRIVATE_KEY_FILENAME; - shadowInitParams.disconnectHandler = NULL; - shadowInitParams.enableAutoReconnect = false; - IoT_Error_t rc = aws_iot_shadow_init(&client, &shadowInitParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - shadowConnectParams.pMyThingName = AWS_IOT_MY_THING_NAME; - shadowConnectParams.pMqttClientId = NULL; - rc = aws_iot_shadow_connect(&client, &shadowConnectParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientInit) { - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.disconnectHandler = NULL; - IoT_Error_t rc = aws_iot_shadow_init(NULL, &shadowInitParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientConnect) { - shadowInitParams.pHost = AWS_IOT_MQTT_HOST; - shadowInitParams.port = AWS_IOT_MQTT_PORT; - shadowInitParams.pClientCRT = AWS_IOT_CERTIFICATE_FILENAME; - shadowInitParams.pRootCA = AWS_IOT_ROOT_CA_FILENAME; - shadowInitParams.pClientKey = AWS_IOT_PRIVATE_KEY_FILENAME; - shadowInitParams.disconnectHandler = NULL; - shadowInitParams.enableAutoReconnect = false; - IoT_Error_t rc = aws_iot_shadow_init(&client, &shadowInitParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - shadowConnectParams.pMyThingName = AWS_IOT_MY_THING_NAME; - shadowConnectParams.pMqttClientId = AWS_IOT_MQTT_CLIENT_ID; - shadowConnectParams.mqttClientIdLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID); - rc = aws_iot_shadow_connect(NULL, &shadowConnectParams); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullUpdateDocument) { - IoT_Error_t rc = aws_iot_shadow_internal_action(AWS_IOT_MY_THING_NAME, SHADOW_UPDATE, NULL, 0, actionCallbackNullTest, - NULL, 4, false); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientYield) { - IoT_Error_t rc = aws_iot_shadow_yield(NULL, 1000); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientDisconnect) { - IoT_Error_t rc = aws_iot_shadow_disconnect(NULL); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientShadowGet) { - IoT_Error_t rc = aws_iot_shadow_get(NULL, AWS_IOT_MY_THING_NAME, actionCallbackNullTest, NULL, 100, true); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientShadowUpdate) { - IoT_Error_t rc = aws_iot_shadow_update(NULL, AWS_IOT_MY_THING_NAME, jsonFullDocument, - actionCallbackNullTest, NULL, 100, true); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientShadowDelete) { - IoT_Error_t rc = aws_iot_shadow_delete(NULL, AWS_IOT_MY_THING_NAME, actionCallbackNullTest, NULL, 100, true); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -TEST_C(ShadowNullFields, NullClientSetAutoreconnect) { - IoT_Error_t rc = aws_iot_shadow_set_autoreconnect_status(NULL, true); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} diff --git a/tests/unit/src/aws_iot_tests_unit_subscribe.cpp b/tests/unit/src/aws_iot_tests_unit_subscribe.cpp deleted file mode 100644 index 205cc076ad..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_subscribe.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_subscribe.cpp - * @brief IoT Client Unit Testing - Subscribe API Tests - */ - -#include -#include - -TEST_GROUP_C(SubscribeTests){ - TEST_GROUP_C_SETUP_WRAPPER(SubscribeTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(SubscribeTests) -}; - -/* C:1 - Subscribe with Null/empty Client Instance */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubscribeNullClient) -/* C:2 - Subscribe with Null/empty Topic Name */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubscribeNullTopic) -/* C:3 - Subscribe with Null client callback */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubscribeNullSubscribeHandler) -/* C:4 - Subscribe with Null client callback data */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubscribeNullSubscribeHandlerData) -/* C:5 - Subscribe with no connection */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubscribeNoConnection) -/* C:6 - Subscribe QoS2, error */ -/* Not required, QoS enum doesn't have value for QoS2 */ - -/* C:7 - Subscribe attempt, QoS0, no response timeout */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS0FailureOnNoSuback) -/* C:8 - Subscribe attempt, QoS1, no response timeout */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS1FailureOnNoSuback) - -/* C:9 - Subscribe QoS0 success, suback received */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS0Success) -/* C:10 - Subscribe QoS1 success, suback received */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS1Success) - -/* C:11 - Subscribe, QoS0, delayed suback, success */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS0WithDelayedSubackSuccess) -/* C:12 - Subscribe, QoS1, delayed suback, success */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS1WithDelayedSubackSuccess) - -/* C:13 - Subscribe QoS0 success, no puback sent on message */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS0MsgReceivedAndNoPubackSent) -/* C:14 - Subscribe QoS1 success, puback sent on message */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeQoS1MsgReceivedAndSendPuback) - -/* C:15 - Subscribe, malformed response */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeMalformedResponse) - -/* C:16 - Subscribe, multiple topics, messages on each topic */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubscribeToMultipleTopicsSuccess) -/* C:17 - Subscribe, max topics, messages on each topic */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubcribeToMaxAllowedTopicsSuccess) -/* C:18 - Subscribe, max topics, another subscribe */ -TEST_GROUP_C_WRAPPER(SubscribeTests, SubcribeToMaxPlusOneAllowedTopicsFailure) - -/* C:19 - Subscribe, '#' not last character in topic name, Failure */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeTopicWithHashkeyAllSubTopicSuccess) -/* C:20 - Subscribe with '#', subscribed to all subtopics */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeTopicHashkeyMustBeTheLastFail) -/* C:21 - Subscribe with '+' as wildcard success */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeTopicWithPluskeySuccess) -/* C:22 - Subscribe with '+' as last character in topic name, Success */ -TEST_GROUP_C_WRAPPER(SubscribeTests, subscribeTopicPluskeyComesLastSuccess) diff --git a/tests/unit/src/aws_iot_tests_unit_subscribe_helper.c b/tests/unit/src/aws_iot_tests_unit_subscribe_helper.c deleted file mode 100644 index d710f41d61..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_subscribe_helper.c +++ /dev/null @@ -1,606 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_subscribe_helper.c - * @brief IoT Client Unit Testing - Subscribe API Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_log.h" - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static IoT_Publish_Message_Params testPubMsgParams; -static char subTopic[10] = "sdk/Test"; -static uint16_t subTopicLen = 8; - -static AWS_IoT_Client iotClient; -static char CallbackMsgString[100]; -char cPayload[100]; - -static char CallbackMsgString1[100] = {"XXXX"}; -static char CallbackMsgString2[100] = {"XXXX"}; -static char CallbackMsgString3[100] = {"XXXX"}; -static char CallbackMsgString4[100] = {"XXXX"}; -static char CallbackMsgString5[100] = {"XXXX"}; -static char CallbackMsgString6[100] = {"XXXX"}; - -static void iot_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString[i] = tmp[i]; - } -} - -static void iot_subscribe_callback_handler1(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - printf("callback topic %s\n", topicName); - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString1[i] = tmp[i]; - } -} - -static void iot_subscribe_callback_handler2(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString2[i] = tmp[i]; - } -} - -static void iot_subscribe_callback_handler3(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString3[i] = tmp[i]; - } -} - -static void iot_subscribe_callback_handler4(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString4[i] = tmp[i]; - } -} - -static void iot_subscribe_callback_handler5(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString5[i] = tmp[i]; - } -} - -static void iot_subscribe_callback_handler6(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString6[i] = tmp[i]; - } -} - -TEST_GROUP_C_SETUP(SubscribeTests) { - IoT_Error_t rc; - ResetTLSBuffer(); - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.mqttCommandTimeout_ms = 2000; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - IOT_DEBUG("MQTT Status State : %d, RC : %d\n\n", aws_iot_mqtt_get_client_state(&iotClient), rc); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - snprintf(cPayload, 100, "%s : %d ", "hello from SDK", 0); - testPubMsgParams.payload = (void *) cPayload; - testPubMsgParams.payloadLen = strlen(cPayload); - - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(SubscribeTests) { } - - - -/* C:1 - Subscribe with Null/empty Client Instance */ -TEST_C(SubscribeTests, SubscribeNullClient) { - IoT_Error_t rc = aws_iot_mqtt_subscribe(NULL, "sdkTest/Sub", 11, QOS1, iot_subscribe_callback_handler, &iotClient); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* C:2 - Subscribe with Null/empty Topic Name */ -TEST_C(SubscribeTests, SubscribeNullTopic) { - IoT_Error_t rc = aws_iot_mqtt_subscribe(&iotClient, NULL, 11, QOS1, iot_subscribe_callback_handler, &iotClient); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* C:3 - Subscribe with Null client callback */ -TEST_C(SubscribeTests, SubscribeNullSubscribeHandler) { - IoT_Error_t rc = aws_iot_mqtt_subscribe(&iotClient, "sdkTest/Sub", 11, QOS1, NULL, &iotClient); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} -/* C:4 - Subscribe with Null client callback data */ -TEST_C(SubscribeTests, SubscribeNullSubscribeHandlerData) { - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - IoT_Error_t rc = aws_iot_mqtt_subscribe(&iotClient, "sdkTest/Sub", 11, QOS1, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); -} -/* C:5 - Subscribe with no connection */ -TEST_C(SubscribeTests, SubscribeNoConnection) { - /* Disconnect first */ - IoT_Error_t rc = aws_iot_mqtt_disconnect(&iotClient); - - rc = aws_iot_mqtt_subscribe(&iotClient, "sdkTest/Sub", 11, QOS1, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); -} -/* C:6 - Subscribe QoS2, error */ -/* Not required, QoS enum doesn't have value for QoS2 */ - -/* C:7 - Subscribe attempt, QoS0, no response timeout */ -TEST_C(SubscribeTests, subscribeQoS0FailureOnNoSuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:7 - Subscribe attempt, QoS0, no response timeout \n"); - - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - - IOT_DEBUG("-->Success - C:7 - Subscribe attempt, QoS0, no response timeout \n"); -} -/* C:8 - Subscribe attempt, QoS1, no response timeout */ -TEST_C(SubscribeTests, subscribeQoS1FailureOnNoSuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:8 - Subscribe attempt, QoS1, no response timeout \n"); - - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - - IOT_DEBUG("-->Success - C:8 - Subscribe attempt, QoS1, no response timeout \n"); -} - -/* C:9 - Subscribe QoS0 success, suback received */ -TEST_C(SubscribeTests, subscribeQoS0Success) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:9 - Subscribe QoS0 success, suback received \n"); - - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - C:9 - Subscribe QoS0 success, suback received \n"); -} - -/* C:10 - Subscribe QoS1 success, suback received */ -TEST_C(SubscribeTests, subscribeQoS1Success) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:10 - Subscribe QoS1 success, suback received \n"); - - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - C:10 - Subscribe QoS1 success, suback received \n"); -} - -/* C:11 - Subscribe, QoS0, delayed suback, success */ -TEST_C(SubscribeTests, subscribeQoS0WithDelayedSubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:11 - Subscribe, QoS0, delayed suback, success \n"); - - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - setTLSRxBufferDelay(0, (int) iotClient.clientData.commandTimeoutMs/2); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - C:11 - Subscribe, QoS0, delayed suback, success \n"); -} - -/* C:12 - Subscribe, QoS1, delayed suback, success */ -TEST_C(SubscribeTests, subscribeQoS1WithDelayedSubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:12 - Subscribe, QoS1, delayed suback, success \n"); - - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - setTLSRxBufferDelay(0, (int) iotClient.clientData.commandTimeoutMs/2); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - C:12 - Subscribe, QoS1, delayed suback, success \n"); -} - -/* C:13 - Subscribe QoS0 success, no puback sent on message */ -TEST_C(SubscribeTests, subscribeQoS0MsgReceivedAndNoPubackSent) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[100] = "Test msg - unit test"; - - IOT_DEBUG("-->Running Subscribe Tests - C:13 - Subscribe QoS0 success, no puback sent on message \n"); - - ResetTLSBuffer(); - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, NULL); - if(SUCCESS == rc) { - testPubMsgParams.qos = QOS0; - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic, subTopicLen, QOS0, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - if(SUCCESS == rc) { - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - } - } - CHECK_EQUAL_C_INT(0, isLastTLSTxMessagePuback()); - - IOT_DEBUG("-->Success - C:13 - Subscribe QoS0 success, no puback sent on message \n"); -} - -/* C:14 - Subscribe QoS1 success, puback sent on message */ -TEST_C(SubscribeTests, subscribeQoS1MsgReceivedAndSendPuback) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[] = "0xA5A5A3"; - - IOT_DEBUG("-->Running Subscribe Tests - C:14 - Subscribe QoS1 success, puback sent on message \n"); - - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, NULL); - if(SUCCESS == rc) { - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic, subTopicLen, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - if(SUCCESS == rc) { - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - } - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - } - - IOT_DEBUG("-->Success - C:14 - Subscribe QoS1 success, puback sent on message \n"); -} - -/* C:15 - Subscribe, malformed response */ -TEST_C(SubscribeTests, subscribeMalformedResponse) {} - -/* C:16 - Subscribe, multiple topics, messages on each topic */ -TEST_C(SubscribeTests, SubscribeToMultipleTopicsSuccess) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[] = "0xA5A5A3"; - - IOT_DEBUG("-->Running Subscribe Tests - C:16 - Subscribe, multiple topics, messages on each topic \n"); - - setTLSRxBufferForSuback("sdk/Test1", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test1", 9, QOS1, iot_subscribe_callback_handler1, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test2", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test2", 9, QOS1, iot_subscribe_callback_handler2, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test1", 9, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString1); - - IOT_DEBUG("-->Success - C:16 - Subscribe, multiple topics, messages on each topic \n"); -} -/* C:17 - Subscribe, max topics, messages on each topic */ -TEST_C(SubscribeTests, SubcribeToMaxAllowedTopicsSuccess) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[] = "topics sdk/Test1"; - char expectedCallbackString2[] = "topics sdk/Test2"; - char expectedCallbackString3[] = "topics sdk/Test3"; - char expectedCallbackString4[] = "topics sdk/Test4"; - char expectedCallbackString5[] = "topics sdk/Test5"; - - IOT_DEBUG("-->Running Subscribe Tests - C:17 - Subscribe, max topics, messages on each topic \n"); - - setTLSRxBufferForSuback("sdk/Test1", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test1", 9, QOS1, iot_subscribe_callback_handler1, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test2", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test2", 9, QOS1, iot_subscribe_callback_handler2, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test3", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test3", 9, QOS1, iot_subscribe_callback_handler3, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test4", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test4", 9, QOS1, iot_subscribe_callback_handler4, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test5", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test5", 9, QOS1, iot_subscribe_callback_handler5, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test1", 9, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test2", 9, QOS1, testPubMsgParams, expectedCallbackString2); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test3", 9, QOS1, testPubMsgParams, expectedCallbackString3); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test4", 9, QOS1, testPubMsgParams, expectedCallbackString4); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test5", 9, QOS1, testPubMsgParams, expectedCallbackString5); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString1); - CHECK_EQUAL_C_STRING(expectedCallbackString2, CallbackMsgString2); - CHECK_EQUAL_C_STRING(expectedCallbackString3, CallbackMsgString3); - CHECK_EQUAL_C_STRING(expectedCallbackString4, CallbackMsgString4); - CHECK_EQUAL_C_STRING(expectedCallbackString5, CallbackMsgString5); - - IOT_DEBUG("-->Success - C:17 - Subscribe, max topics, messages on each topic \n"); -} -/* C:18 - Subscribe, max topics, another subscribe */ -TEST_C(SubscribeTests, SubcribeToMaxPlusOneAllowedTopicsFailure) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Subscribe Tests - C:18 - Subscribe, max topics, another subscribe \n"); - - setTLSRxBufferForSuback("sdk/Test1", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test1", 9, QOS1, iot_subscribe_callback_handler1, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test2", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test2", 9, QOS1, iot_subscribe_callback_handler2, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test3", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test3", 9, QOS1, iot_subscribe_callback_handler3, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test4", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test4", 9, QOS1, iot_subscribe_callback_handler4, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test5", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test5", 9, QOS1, iot_subscribe_callback_handler5, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - setTLSRxBufferForSuback("sdk/Test6", 9, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test6", 9, QOS1, iot_subscribe_callback_handler6, NULL); - CHECK_EQUAL_C_INT(MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR, rc); - - IOT_DEBUG("-->Success - C:18 - Subscribe, max topics, another subscribe \n"); -} - -/* C:19 - Subscribe, '#' not last character in topic name, Failure */ -TEST_C(SubscribeTests, subscribeTopicWithHashkeyAllSubTopicSuccess) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[100] = "New message: sub/sub, Hashkey"; - - IOT_DEBUG("-->Running Subscribe Tests - C:19 - Subscribe, '#' not last character in topic name, Failure \n"); - - // Set up the subscribed topic, including '#' - setTLSRxBufferForSuback("sdk/Test/#", strlen("sdk/Test/#"), QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test/#", strlen("sdk/Test/#"), QOS1, - iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - // Now provide a published message from a sub topic - IOT_DEBUG("[Matching '#'] Checking first sub topic message, with '#' be the last..\n"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/sub/sub", strlen("sdk/Test/sub/sub"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - - // Re-initialize Rx Tx Buffer - ResetTLSBuffer(); - - // Now provide another message from a different sub topic - IOT_DEBUG("[Matching '#'] Checking second sub topic message, with '#' be the last...\n"); - snprintf(expectedCallbackString, 100, "New message: sub2, Hashkey"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/sub2", strlen("sdk/Test/sub2"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - - IOT_DEBUG("-->Success - C:19 - Subscribe, '#' not last character in topic name, Failure \n"); -} -/* C:20 - Subscribe with '#', subscribed to all subtopics */ -TEST_C(SubscribeTests, subscribeTopicHashkeyMustBeTheLastFail) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[100] = "New message: foo1/sub, Hashkey"; - - IOT_DEBUG("-->Running Subscribe Tests - C:20 - Subscribe with '#', subscribed to all subtopics \n"); - - // Set up the subscribed topic, with '#' in the middle - // Topic directory not permitted, should fail - setTLSRxBufferForSuback("sdk/#/sub", strlen("sdk/#/sub"), QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/#/sub", strlen("sdk/#/sub"), QOS1, - iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - // Now provide a published message from a sub directoy with this sub topic - IOT_DEBUG("[Matching '#'] Checking sub topic message, with '#' in the middle...\n"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/foo1/sub", strlen("sdk/Test/foo1/sub"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING("NOT_VISITED", CallbackMsgString); - - IOT_DEBUG("-->Success - C:20 - Subscribe with '#', subscribed to all subtopics \n"); -} -/* C:21 - Subscribe with '+' as wildcard success */ -TEST_C(SubscribeTests, subscribeTopicWithPluskeySuccess) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[100] = "New message: 1/sub, Pluskey"; - - IOT_DEBUG("-->Running Subscribe Tests - C:21 - Subscribe with '+' as wildcard success \n"); - - // Set up the subscribed topic, including '+' - setTLSRxBufferForSuback("sdk/Test/+/sub", strlen("sdk/Test/+/sub"), QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test/+/sub", strlen("sdk/Test/+/sub"), QOS1, - iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - // Now provide a published message from a sub topic - IOT_DEBUG("[Matching '+'] Checking first sub topic message, with '+' in the middle...\n"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/1/sub", strlen("sdk/Test/1/sub"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - - // Re-initialize Rx Tx Buffer - ResetTLSBuffer(); - - // Now provide another message from a different sub topic - IOT_DEBUG("[Matching '+'] Checking second sub topic message, with '+' in the middle...\n"); - snprintf(expectedCallbackString, 100, "New message: 2/sub, Pluskey"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/2/sub", strlen("sdk/Test/2/sub"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - - IOT_DEBUG("-->Success - C:21 - Subscribe with '+' as wildcard success \n"); -} -/* C:22 - Subscribe with '+' as last character in topic name, Success */ -TEST_C(SubscribeTests, subscribeTopicPluskeyComesLastSuccess) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[100] = "New message: foo1, Pluskey"; - - IOT_DEBUG("-->Running Subscribe Tests - C:22 - Subscribe with '+' as last character in topic name, Success \n"); - - // Set up the subscribed topic, with '+' comes the last - setTLSRxBufferForSuback("sdk/Test/+", strlen("sdk/Test/+"), QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "sdk/Test/+", strlen("sdk/Test/+"), QOS1, - iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - // Now provide a published message from a single layer of sub directroy - IOT_DEBUG("[Matching '+'] Checking first sub topic message, with '+' be the last...\n"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/foo1", strlen("sdk/Test/foo1"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - - // Re-initialize Rx Tx Buffer - ResetTLSBuffer(); - - // Now provide a published message from another single layer of sub directroy - IOT_DEBUG("[Matching '+'] Checking second sub topic message, with '+' be the last...\n"); - snprintf(expectedCallbackString, 100, "New message: foo2, Pluskey"); - setTLSRxBufferWithMsgOnSubscribedTopic("sdk/Test/foo2", strlen("sdk/Test/foo2"), QOS1, testPubMsgParams, - expectedCallbackString); - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - - IOT_DEBUG("-->Success - C:22 - Subscribe with '+' as last character in topic name, Success \n"); -} diff --git a/tests/unit/src/aws_iot_tests_unit_unsubscribe.cpp b/tests/unit/src/aws_iot_tests_unit_unsubscribe.cpp deleted file mode 100644 index bedea1f380..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_unsubscribe.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_unsubscribe.cpp - * @brief IoT Client Unit Testing - Unsubscribe API Tests - */ - -#include -#include - -TEST_GROUP_C(UnsubscribeTests){ - TEST_GROUP_C_SETUP_WRAPPER(UnsubscribeTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(UnsubscribeTests) -}; - -/* D:1 - Unsubscribe with Null/empty client instance */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, UnsubscribeNullClient) -/* D:2 - Unsubscribe with Null/empty topic name */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, UnsubscribeNullTopic) -/* D:3 - Unsubscribe, Not subscribed to topic */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, UnsubscribeNotSubscribed) - -/* D:4 - Unsubscribe, QoS0, No response, timeout */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, unsubscribeQoS0FailureOnNoUnsuback) -/* D:5 - Unsubscribe, QoS1, No response, timeout */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, unsubscribeQoS1FailureOnNoUnsuback) - -/* D:6 - Unsubscribe, QoS0, success */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, unsubscribeQoS0WithUnsubackSuccess) -/* D:7 - Unsubscribe, QoS0, half command timeout delayed unsuback, success */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, unsubscribeQoS0WithDelayedUnsubackSuccess) -/* D:8 - Unsubscribe, QoS1, success */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, unsubscribeQoS1WithUnsubackSuccess) -/* D:9 - Unsubscribe, QoS1, half command timeout delayed unsuback, success */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, unsubscribeQoS1WithDelayedUnsubackSuccess) - -/* D:10 - Unsubscribe, success, message on topic ignored */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, MsgAfterUnsubscribe) -/* D:11 - Unsubscribe after max topics reached */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, MaxTopicsSubscription) -/* D:12 - Repeated Subscribe and Unsubscribe */ -TEST_GROUP_C_WRAPPER(UnsubscribeTests, RepeatedSubUnSub) diff --git a/tests/unit/src/aws_iot_tests_unit_unsubscribe_helper.c b/tests/unit/src/aws_iot_tests_unit_unsubscribe_helper.c deleted file mode 100644 index a4d245378a..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_unsubscribe_helper.c +++ /dev/null @@ -1,343 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_unsubscribe_helper.c - * @brief IoT Client Unit Testing - Unsubscribe API Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_mqtt_client_interface.h" -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_log.h" - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static IoT_Publish_Message_Params testPubMsgParams; -static char subTopic[10] = "sdk/Test"; -static uint16_t subTopicLen = 8; - -static AWS_IoT_Client iotClient; -static char CallbackMsgString[100]; -char cPayload[100]; - -static void iot_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - if(NULL == pClient || NULL == topicName || 0 == topicNameLen) { - return; - } - - IOT_UNUSED(pData); - - char *tmp = params->payload; - unsigned int i; - - for(i = 0; i < (params->payloadLen); i++) { - CallbackMsgString[i] = tmp[i]; - } -} - -TEST_GROUP_C_SETUP(UnsubscribeTests) { - IoT_Error_t rc = SUCCESS; - ResetTLSBuffer(); - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, NULL); - initParams.mqttCommandTimeout_ms = 2000; - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ConnectMQTTParamsSetup(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID)); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - IOT_DEBUG("MQTT Status State : %d, RC : %d\n\n", aws_iot_mqtt_get_client_state(&iotClient), rc); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - snprintf(cPayload, 100, "%s : %d ", "hello from SDK", 0); - testPubMsgParams.payload = (void *) cPayload; - testPubMsgParams.payloadLen = strlen(cPayload); - - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(UnsubscribeTests) { } - -/* D:1 - Unsubscribe with Null/empty client instance */ -TEST_C(UnsubscribeTests, UnsubscribeNullClient) { - IoT_Error_t rc = aws_iot_mqtt_unsubscribe(NULL, "sdkTest/Sub", 11); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* D:2 - Unsubscribe with Null/empty topic name */ -TEST_C(UnsubscribeTests, UnsubscribeNullTopic) { - IoT_Error_t rc = aws_iot_mqtt_unsubscribe(&iotClient, NULL, 11); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* D:3 - Unsubscribe, Not subscribed to topic */ -TEST_C(UnsubscribeTests, UnsubscribeNotSubscribed) { - IoT_Error_t rc = aws_iot_mqtt_unsubscribe(&iotClient, "sdkTest/Sub", 11); - CHECK_EQUAL_C_INT(FAILURE, rc); -} - -/* D:4 - Unsubscribe, QoS0, No response, timeout */ -TEST_C(UnsubscribeTests, unsubscribeQoS0FailureOnNoUnsuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:4 - Unsubscribe, QoS0, No response, timeout \n"); - - // First, subscribe to a topic - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, - NULL); - if(SUCCESS == rc) { - // Then, unsubscribe - rc = aws_iot_mqtt_unsubscribe(&iotClient, subTopic, (uint16_t) strlen(subTopic)); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - } - - IOT_DEBUG("-->Success - D:4 - Unsubscribe, QoS0, No response, timeout \n"); -} - -/* D:5 - Unsubscribe, QoS1, No response, timeout */ -TEST_C(UnsubscribeTests, unsubscribeQoS1FailureOnNoUnsuback) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:5 - Unsubscribe, QoS1, No response, timeout \n"); - - // First, subscribe to a topic - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, - NULL); - if(SUCCESS == rc) { - // Then, unsubscribe - rc = aws_iot_mqtt_unsubscribe(&iotClient, subTopic, (uint16_t) strlen(subTopic)); - CHECK_EQUAL_C_INT(MQTT_REQUEST_TIMEOUT_ERROR, rc); - } - - IOT_DEBUG("-->Success - D:5 - Unsubscribe, QoS1, No response, timeout \n"); -} - -/* D:6 - Unsubscribe, QoS0, success */ -TEST_C(UnsubscribeTests, unsubscribeQoS0WithUnsubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:6 - Unsubscribe, QoS0, success \n"); - - // First, subscribe to a topic - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, - NULL); - if(SUCCESS == rc) { - // Then, unsubscribe - setTLSRxBufferForUnsuback(); - rc = aws_iot_mqtt_unsubscribe(&iotClient, subTopic, (uint16_t) strlen(subTopic)); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - IOT_DEBUG("-->Success - D:6 - Unsubscribe, QoS0, success \n"); -} - -/* D:7 - Unsubscribe, QoS0, half command timeout delayed unsuback, success */ -TEST_C(UnsubscribeTests, unsubscribeQoS0WithDelayedUnsubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:7 - Unsubscribe, QoS0, half command timeout delayed unsuback, success \n"); - - // First, subscribe to a topic - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_subscribe_callback_handler, - NULL); - if(SUCCESS == rc) { - // Then, unsubscribe - setTLSRxBufferForUnsuback(); - setTLSRxBufferDelay(0, (int) iotClient.clientData.commandTimeoutMs/2); - rc = aws_iot_mqtt_unsubscribe(&iotClient, subTopic, (uint16_t) strlen(subTopic)); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - IOT_DEBUG("-->Success - D:7 - Unsubscribe, QoS0, half command timeout delayed unsuback, success \n"); -} - -/* D:8 - Unsubscribe, QoS1, success */ -TEST_C(UnsubscribeTests, unsubscribeQoS1WithUnsubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:8 - Unsubscribe, QoS1, success \n"); - - // First, subscribe to a topic - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, - NULL); - if(SUCCESS == rc) { - // Then, unsubscribe - setTLSRxBufferForUnsuback(); - rc = aws_iot_mqtt_unsubscribe(&iotClient, subTopic, (uint16_t) strlen(subTopic)); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - IOT_DEBUG("-->Success - D:8 - Unsubscribe, QoS1, success \n"); -} - -/* D:9 - Unsubscribe, QoS1, half command timeout delayed unsuback, success */ -TEST_C(UnsubscribeTests, unsubscribeQoS1WithDelayedUnsubackSuccess) { - IoT_Error_t rc = SUCCESS; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:9 - Unsubscribe, QoS1, half command timeout delayed unsuback, success \n"); - - // First, subscribe to a topic - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, iot_subscribe_callback_handler, - NULL); - if(SUCCESS == rc) { - // Then, unsubscribe - setTLSRxBufferForUnsuback(); - setTLSRxBufferDelay(0, (int) iotClient.clientData.commandTimeoutMs/2); - rc = aws_iot_mqtt_unsubscribe(&iotClient, subTopic, (uint16_t) strlen(subTopic)); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - IOT_DEBUG("-->Success - D:9 - Unsubscribe, QoS1, half command timeout delayed unsuback, success \n"); -} - -/* D:10 - Unsubscribe, success, message on topic ignored - * 1. Subscribe to topic 1 - * 2. Send message and receive it - * 3. Unsubscribe to topic 1 - * 4. Should not receive message - */ -TEST_C(UnsubscribeTests, MsgAfterUnsubscribe) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[100]; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:10 - Unsubscribe, success, message on topic ignored \n"); - - // 1. - setTLSRxBufferForSuback("topic1", 6, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, "topic1", 6, QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - // 2. - snprintf(expectedCallbackString, 100, "Message for topic1"); - setTLSRxBufferWithMsgOnSubscribedTopic("topic1", 6, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - - //3. - setTLSRxBufferForUnsuback(); - rc = aws_iot_mqtt_unsubscribe(&iotClient, "topic1", 6); - CHECK_EQUAL_C_INT(SUCCESS, rc); - //reset the string - snprintf(CallbackMsgString, 100, " "); - - // 4. - // Have a new message published to that topic coming in - snprintf(expectedCallbackString, 100, "Message after unsubscribe"); - setTLSRxBufferWithMsgOnSubscribedTopic("topic1", 6, QOS0, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - // No new msg was received - CHECK_EQUAL_C_STRING(" ", CallbackMsgString); - - IOT_DEBUG("-->Success - D:10 - Unsubscribe, success, message on topic ignored \n"); -} - -/* D:11 - Unsubscribe after max topics reached - * 1. Subscribe to max topics + 1 fail for last subscription - * 2. Unsubscribe from one topic - * 3. Subscribe again and should have no error - * 4. Receive msg test - last subscribed topic - */ -TEST_C(UnsubscribeTests, MaxTopicsSubscription) { - IoT_Error_t rc = SUCCESS; - int i = 0; - char topics[AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS + 1][10]; - char expectedCallbackString[] = "Message after subscribe - topic[i]"; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:11 - Unsubscribe after max topics reached \n"); - - // 1. - for(i = 0; i < AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; i++) { - snprintf(topics[i], 10, "topic-%d", i); - setTLSRxBufferForSuback(topics[i], strlen(topics[i]), QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, topics[i], (uint16_t) strlen(topics[i]), QOS0, iot_subscribe_callback_handler, - NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - snprintf(topics[i], 10, "topic-%d", i); - setTLSRxBufferForSuback(topics[i], strlen(topics[i]), QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, topics[i], (uint16_t) strlen(topics[i]), QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(MQTT_MAX_SUBSCRIPTIONS_REACHED_ERROR, rc); - - // 2. - setTLSRxBufferForUnsuback(); - rc = aws_iot_mqtt_unsubscribe(&iotClient, topics[i - 1], (uint16_t) strlen(topics[i - 1])); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - //3. - setTLSRxBufferForSuback(topics[i], strlen(topics[i]), QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, topics[i], (uint16_t) strlen(topics[i]), QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - //4. - setTLSRxBufferWithMsgOnSubscribedTopic(topics[i], strlen(topics[i]), QOS1, testPubMsgParams, - expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - - IOT_DEBUG("-->Success - D:11 - Unsubscribe after max topics reached \n"); -} - -/* D:12 - Repeated Subscribe and Unsubscribe - * 1. subscribe and unsubscribe for more than the max subscribed topic - * 2. ensure every time the subscribed topic msg is received - */ -TEST_C(UnsubscribeTests, RepeatedSubUnSub) { - IoT_Error_t rc = SUCCESS; - int i = 0; - char expectedCallbackString[100]; - char topics[3 * AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS][10]; - - IOT_DEBUG("-->Running Unsubscribe Tests - D:12 - Repeated Subscribe and Unsubscribe \n"); - - - for(i = 0; i < 3 * AWS_IOT_MQTT_NUM_SUBSCRIBE_HANDLERS; i++) { - //1. - snprintf(topics[i], 10, "topic-%d", i); - setTLSRxBufferForSuback(topics[i], 10, QOS0, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, topics[i], 10, QOS0, iot_subscribe_callback_handler, NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - snprintf(expectedCallbackString, 10, "message##%d", i); - testPubMsgParams.payload = (void *) expectedCallbackString; - testPubMsgParams.payloadLen = strlen(expectedCallbackString); - setTLSRxBufferWithMsgOnSubscribedTopic(topics[i], strlen(topics[i]), QOS1, testPubMsgParams, - expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - - //2. - setTLSRxBufferForUnsuback(); - rc = aws_iot_mqtt_unsubscribe(&iotClient, topics[i], (uint16_t) strlen(topics[i])); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - IOT_DEBUG("-->Success - D:12 - Repeated Subscribe and Unsubscribe \n"); -} diff --git a/tests/unit/src/aws_iot_tests_unit_yield.cpp b/tests/unit/src/aws_iot_tests_unit_yield.cpp deleted file mode 100644 index 8eeb203f0e..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_yield.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_yield.cpp - * @brief IoT Client Unit Testing - Yield API Tests - */ - -#include -#include - -TEST_GROUP_C(YieldTests){ - TEST_GROUP_C_SETUP_WRAPPER(YieldTests) - TEST_GROUP_C_TEARDOWN_WRAPPER(YieldTests) -}; - -/* G:1 - Yield with Null/empty Client Instance */ -TEST_GROUP_C_WRAPPER(YieldTests, NullClientYield) -/* G:2 - Yield with zero yield timeout */ -TEST_GROUP_C_WRAPPER(YieldTests, ZeroTimeoutYield) -/* G:3 - Yield, network disconnected, never connected */ -TEST_GROUP_C_WRAPPER(YieldTests, YieldNetworkDisconnectedNeverConnected) -/* G:4 - Yield, network disconnected, disconnected manually */ -TEST_GROUP_C_WRAPPER(YieldTests, YieldNetworkDisconnectedDisconnectedManually) -/* G:5 - Yield, network connected, yield called while in subscribe application callback */ -TEST_GROUP_C_WRAPPER(YieldTests, YieldInSubscribeCallback) -/* G:6 - Yield, network disconnected, ping timeout, auto-reconnect disabled */ -TEST_GROUP_C_WRAPPER(YieldTests, disconnectNoAutoReconnect) - -/* G:7 - Yield, network connected, no incoming messages */ -TEST_GROUP_C_WRAPPER(YieldTests, YieldSuccessNoMessages) -/* G:8 - Yield, network connected, ping request/response */ -TEST_GROUP_C_WRAPPER(YieldTests, PingRequestPingResponse) - -/* G:9 - Yield, disconnected, Auto-reconnect timed-out */ -TEST_GROUP_C_WRAPPER(YieldTests, disconnectAutoReconnectTimeout) -/* G:10 - Yield, disconnected, Auto-reconnect successful */ -TEST_GROUP_C_WRAPPER(YieldTests, disconnectAutoReconnectSuccess) -/* G:11 - Yield, disconnected, Manual reconnect */ -TEST_GROUP_C_WRAPPER(YieldTests, disconnectManualAutoReconnect) -/* G:12 - Yield, resubscribe to all topics on reconnect */ -TEST_GROUP_C_WRAPPER(YieldTests, resubscribeSuccessfulReconnect) diff --git a/tests/unit/src/aws_iot_tests_unit_yield_helper.c b/tests/unit/src/aws_iot_tests_unit_yield_helper.c deleted file mode 100644 index ca0ea33901..0000000000 --- a/tests/unit/src/aws_iot_tests_unit_yield_helper.c +++ /dev/null @@ -1,452 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_yield_helper.c - * @brief IoT Client Unit Testing - Yield API Tests Helper - */ - -#include -#include -#include - -#include "aws_iot_tests_unit_helper_functions.h" -#include "aws_iot_tests_unit_mock_tls_params.h" -#include "aws_iot_log.h" - -static IoT_Client_Init_Params initParams; -static IoT_Client_Connect_Params connectParams; -static AWS_IoT_Client iotClient; -static IoT_Publish_Message_Params testPubMsgParams; - -static ConnectBufferProofread prfrdParams; -static char CallbackMsgString[100]; -static char subTopic[10] = "sdk/Test"; -static uint16_t subTopicLen = 8; - -static bool dcHandlerInvoked = false; - -static void iot_tests_unit_acr_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - char *tmp = params->payload; - unsigned int i; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - for(i = 0; i < params->payloadLen; i++) { - CallbackMsgString[i] = tmp[i]; - } -} - -static void iot_tests_unit_yield_test_subscribe_callback_handler(AWS_IoT_Client *pClient, char *topicName, - uint16_t topicNameLen, - IoT_Publish_Message_Params *params, void *pData) { - char *tmp = params->payload; - unsigned int i; - IoT_Error_t rc = SUCCESS; - - IOT_UNUSED(pClient); - IOT_UNUSED(topicName); - IOT_UNUSED(topicNameLen); - IOT_UNUSED(pData); - - rc = aws_iot_mqtt_yield(pClient, 1000); - CHECK_EQUAL_C_INT(MQTT_CLIENT_NOT_IDLE_ERROR, rc); - - for(i = 0; i < params->payloadLen; i++) { - CallbackMsgString[i] = tmp[i]; - } -} - -void iot_tests_unit_disconnect_handler(AWS_IoT_Client *pClient, void *disconParam) { - IOT_UNUSED(pClient); - IOT_UNUSED(disconParam); - dcHandlerInvoked = true; -} - - -TEST_GROUP_C_SETUP(YieldTests) { - IoT_Error_t rc = SUCCESS; - dcHandlerInvoked = false; - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, iot_tests_unit_disconnect_handler); - rc = aws_iot_mqtt_init(&iotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - - ConnectMQTTParamsSetup_Detailed(&connectParams, AWS_IOT_MQTT_CLIENT_ID, (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID), - QOS1, false, true, "willTopicName", (uint16_t) strlen("willTopicName"), "willMsg", - (uint16_t) strlen("willMsg"), NULL, 0, NULL, 0); - connectParams.keepAliveIntervalInSec = 5; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_connect(&iotClient, &connectParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - rc = aws_iot_mqtt_autoreconnect_set_status(&iotClient, false); - CHECK_EQUAL_C_INT(SUCCESS, rc); - ResetTLSBuffer(); -} - -TEST_GROUP_C_TEARDOWN(YieldTests) { - /* Clean up. Not checking return code here because this is common to all tests. - * A test might have already caused a disconnect by this point. - */ - IoT_Error_t rc = aws_iot_mqtt_disconnect(&iotClient); - IOT_UNUSED(rc); -} - -/* G:1 - Yield with Null/empty Client Instance */ -TEST_C(YieldTests, NullClientYield) { - IoT_Error_t rc = aws_iot_mqtt_yield(NULL, 1000); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} - -/* G:2 - Yield with zero yield timeout */ - -TEST_C(YieldTests, ZeroTimeoutYield) { - IoT_Error_t rc = aws_iot_mqtt_yield(&iotClient, 0); - CHECK_EQUAL_C_INT(NULL_VALUE_ERROR, rc); -} -/* G:3 - Yield, network disconnected, never connected */ - -TEST_C(YieldTests, YieldNetworkDisconnectedNeverConnected) { - AWS_IoT_Client tempIotClient; - IoT_Error_t rc; - - InitMQTTParamsSetup(&initParams, AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, false, iot_tests_unit_disconnect_handler); - rc = aws_iot_mqtt_init(&tempIotClient, &initParams); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - rc = aws_iot_mqtt_yield(&tempIotClient, 1000); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); -} - -/* G:4 - Yield, network disconnected, disconnected manually */ -TEST_C(YieldTests, YieldNetworkDisconnectedDisconnectedManually) { - IoT_Error_t rc = aws_iot_mqtt_disconnect(&iotClient); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - CHECK_EQUAL_C_INT(NETWORK_MANUALLY_DISCONNECTED, rc); -} - -/* G:5 - Yield, network connected, yield called while in subscribe application callback */ -TEST_C(YieldTests, YieldInSubscribeCallback) { - IoT_Error_t rc = SUCCESS; - char expectedCallbackString[] = "0xA5A5A3"; - - IOT_DEBUG("-->Running Yield Tests - G:5 - Yield, network connected, yield called while in subscribe application callback \n"); - - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS1, - iot_tests_unit_yield_test_subscribe_callback_handler, NULL); - if(SUCCESS == rc) { - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic, subTopicLen, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 1000); - if(SUCCESS == rc) { - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - } - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePuback()); - } - - IOT_DEBUG("-->Success - G:5 - Yield, network connected, yield called while in subscribe application callback \n"); -} - -/* G:6 - Yield, network disconnected, ping timeout, auto-reconnect disabled */ -TEST_C(YieldTests, disconnectNoAutoReconnect) { - IoT_Error_t rc = FAILURE; - - IOT_DEBUG("-->Running Yield Tests - G:6 - Yield, network disconnected, ping timeout, auto-reconnect disabled \n"); - - rc = aws_iot_mqtt_autoreconnect_set_status(&iotClient, true); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - CHECK_EQUAL_C_INT(true, aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, aws_iot_is_autoreconnect_enabled(&iotClient)); - - /* Disable Autoreconnect, then let ping request time out and call yield */ - aws_iot_mqtt_autoreconnect_set_status(&iotClient, false); - sleep((uint16_t)(iotClient.clientData.keepAliveInterval)); - - ResetTLSBuffer(); - - /* Sleep for keep alive interval to allow the first ping to be sent out */ - sleep(iotClient.clientData.keepAliveInterval); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(true, isLastTLSTxMessagePingreq()); - - /* Let ping request time out and call yield */ - sleep(iotClient.clientData.keepAliveInterval + 1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessageDisconnect()); - CHECK_EQUAL_C_INT(0, aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, dcHandlerInvoked); - - IOT_DEBUG("-->Success - G:6 - Yield, network disconnected, ping timeout, auto-reconnect disabled \n"); -} - -/* G:7 - Yield, network connected, no incoming messages */ -TEST_C(YieldTests, YieldSuccessNoMessages) { - IoT_Error_t rc; - int i; - - IOT_DEBUG("-->Running Yield Tests - G:7 - Yield, network connected, no incoming messages \n"); - - for(i = 0; i < 100; i++) { - CallbackMsgString[i] = 'x'; - } - - rc = aws_iot_mqtt_yield(&iotClient, 1000); - if(SUCCESS == rc) { - /* Check no messages were received */ - for(i = 0; i < 100; i++) { - if('x' != CallbackMsgString[i]) { - rc = FAILURE; - } - } - } - - CHECK_EQUAL_C_INT(SUCCESS, rc); - - IOT_DEBUG("-->Success - G:7 - Yield, network connected, no incoming messages \n"); -} - -/* G:8 - Yield, network connected, ping request/response */ -TEST_C(YieldTests, PingRequestPingResponse) { - IoT_Error_t rc = SUCCESS; - int i = 0; - int j = 0; - int attempt = 3; - - IOT_DEBUG("-->Running Yield Tests - G:8 - Yield, network connected, ping request/response \n"); - IOT_DEBUG("Current Keep Alive Interval is set to %d sec.\n", iotClient.clientData.keepAliveInterval); - - for(i = 0; i < attempt; i++) { - IOT_DEBUG("[Round_%d/Total_%d] Waiting for %d sec...\n", i + 1, attempt, iotClient.clientData.keepAliveInterval); - /* Set TLS buffer for ping response */ - ResetTLSBuffer(); - setTLSRxBufferForPingresp(); - - for(j = 0; j <= iotClient.clientData.keepAliveInterval; j++) { - sleep(1); - IOT_DEBUG("[Waited %d secs]", j + 1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - } - - /* Check whether ping was processed correctly and new Ping request was generated */ - CHECK_EQUAL_C_INT(1, isLastTLSTxMessagePingreq()); - } - - IOT_DEBUG("-->Success - G:8 - Yield, network connected, ping request/response \n"); -} - -/* G:9 - Yield, disconnected, Auto-reconnect timed-out */ -TEST_C(YieldTests, disconnectAutoReconnectTimeout) { - IoT_Error_t rc = FAILURE; - - IOT_DEBUG("-->Running Yield Tests - G:9 - Yield, disconnected, Auto-reconnect timed-out \n"); - - rc = aws_iot_mqtt_autoreconnect_set_status(&iotClient, true); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - CHECK_EQUAL_C_INT(true, aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, aws_iot_is_autoreconnect_enabled(&iotClient)); - - ResetTLSBuffer(); - - /* Sleep for keep alive interval to allow the first ping to be sent out */ - sleep(iotClient.clientData.keepAliveInterval); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(true, isLastTLSTxMessagePingreq()); - - /* Let ping request time out and call yield */ - sleep(iotClient.clientData.keepAliveInterval + 1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_ATTEMPTING_RECONNECT, rc); - CHECK_EQUAL_C_INT(0, aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, dcHandlerInvoked); - - IOT_DEBUG("-->Success - G:9 - Yield, disconnected, Auto-reconnect timed-out \n"); -} - -/* G:10 - Yield, disconnected, Auto-reconnect successful */ -TEST_C(YieldTests, disconnectAutoReconnectSuccess) { - IoT_Error_t rc = FAILURE; - unsigned char *currPayload = NULL; - - IOT_DEBUG("-->Running Yield Tests - G:10 - Yield, disconnected, Auto-reconnect successful \n"); - - rc = aws_iot_mqtt_autoreconnect_set_status(&iotClient, true); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - CHECK_EQUAL_C_INT(true, aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, aws_iot_is_autoreconnect_enabled(&iotClient)); - - /* Sleep for keep alive interval to allow the first ping to be sent out */ - sleep(iotClient.clientData.keepAliveInterval); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(true, isLastTLSTxMessagePingreq()); - - /* Let ping request time out and call yield */ - sleep(iotClient.clientData.keepAliveInterval + 1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_ATTEMPTING_RECONNECT, rc); - - sleep(2); /* Default min reconnect delay is 1 sec */ - printf("\nWakeup"); - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - currPayload = connectTxBufferHeaderParser(&prfrdParams, TxBuffer.pBuffer); - CHECK_C(true == isConnectTxBufFlagCorrect(&connectParams, &prfrdParams)); - CHECK_C(true == isConnectTxBufPayloadCorrect(&connectParams, currPayload)); - CHECK_EQUAL_C_INT(true, aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, dcHandlerInvoked); - - IOT_DEBUG("-->Success - G:10 - Yield, disconnected, Auto-reconnect successful \n"); -} - -/* G:11 - Yield, disconnected, Manual reconnect */ -TEST_C(YieldTests, disconnectManualAutoReconnect) { - IoT_Error_t rc = FAILURE; - unsigned char *currPayload = NULL; - - IOT_DEBUG("-->Running Yield Tests - G:11 - Yield, disconnected, Manual reconnect \n"); - - CHECK_C(aws_iot_mqtt_is_client_connected(&iotClient)); - - /* Disable Autoreconnect, then let ping request time out and call yield */ - aws_iot_mqtt_autoreconnect_set_status(&iotClient, false); - CHECK_C(!aws_iot_is_autoreconnect_enabled(&iotClient)); - - /* Sleep for keep alive interval to allow the first ping to be sent out */ - sleep(iotClient.clientData.keepAliveInterval); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(true, isLastTLSTxMessagePingreq()); - - /* Let ping request time out and call yield */ - sleep(iotClient.clientData.keepAliveInterval + 1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_DISCONNECTED_ERROR, rc); - CHECK_EQUAL_C_INT(1, isLastTLSTxMessageDisconnect()); - CHECK_C(!aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(true, dcHandlerInvoked); - - dcHandlerInvoked = false; - setTLSRxBufferForConnack(&connectParams, 0, 0); - rc = aws_iot_mqtt_attempt_reconnect(&iotClient); - CHECK_EQUAL_C_INT(NETWORK_RECONNECTED, rc); - - currPayload = connectTxBufferHeaderParser(&prfrdParams, TxBuffer.pBuffer); - CHECK_C(true == isConnectTxBufFlagCorrect(&connectParams, &prfrdParams)); - CHECK_C(true == isConnectTxBufPayloadCorrect(&connectParams, currPayload)); - CHECK_C(aws_iot_mqtt_is_client_connected(&iotClient)); - CHECK_EQUAL_C_INT(false, dcHandlerInvoked); - - IOT_DEBUG("-->Success - G:11 - Yield, disconnected, Manual reconnect \n"); -} - -/* G:12 - Yield, resubscribe to all topics on reconnect */ -TEST_C(YieldTests, resubscribeSuccessfulReconnect) { - IoT_Error_t rc = FAILURE; - char cPayload[100]; - bool connected = false; - bool autoReconnectEnabled = false; - char expectedCallbackString[100]; - - IOT_DEBUG("-->Running Yield Tests - G:12 - Yield, resubscribe to all topics on reconnect \n"); - - rc = aws_iot_mqtt_autoreconnect_set_status(&iotClient, true); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - snprintf(CallbackMsgString, 100, "NOT_VISITED"); - - testPubMsgParams.qos = QOS1; - testPubMsgParams.isRetained = 0; - snprintf(cPayload, 100, "%s : %d ", "hello from SDK", 0); - testPubMsgParams.payload = (void *) cPayload; - testPubMsgParams.payloadLen = strlen(cPayload); - - connected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(1, connected); - - ResetTLSBuffer(); - - /* Subscribe to a topic */ - setTLSRxBufferForSuback(subTopic, subTopicLen, QOS1, testPubMsgParams); - rc = aws_iot_mqtt_subscribe(&iotClient, subTopic, subTopicLen, QOS0, iot_tests_unit_acr_subscribe_callback_handler, - NULL); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - ResetTLSBuffer(); - - /* Check subscribe */ - snprintf(expectedCallbackString, 100, "Message for %s", subTopic); - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic, subTopicLen, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - - ResetTLSBuffer(); - - autoReconnectEnabled = aws_iot_is_autoreconnect_enabled(&iotClient); - CHECK_EQUAL_C_INT(1, autoReconnectEnabled); - - /* Sleep for keep alive interval to allow the first ping to be sent out */ - sleep(iotClient.clientData.keepAliveInterval); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_INT(true, isLastTLSTxMessagePingreq()); - - /* Let ping request time out and call yield */ - sleep(iotClient.clientData.keepAliveInterval + 1); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(NETWORK_ATTEMPTING_RECONNECT, rc); - - sleep(2); /* Default min reconnect delay is 1 sec */ - - ResetTLSBuffer(); - setTLSRxBufferForConnackAndSuback(&connectParams, 0, subTopic, subTopicLen, QOS1); - - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - - /* Test if reconnect worked */ - connected = aws_iot_mqtt_is_client_connected(&iotClient); - CHECK_EQUAL_C_INT(true, connected); - - ResetTLSBuffer(); - - /* Check subscribe */ - snprintf(expectedCallbackString, 100, "Message for %s after resub", subTopic); - setTLSRxBufferWithMsgOnSubscribedTopic(subTopic, subTopicLen, QOS1, testPubMsgParams, expectedCallbackString); - rc = aws_iot_mqtt_yield(&iotClient, 100); - CHECK_EQUAL_C_INT(SUCCESS, rc); - CHECK_EQUAL_C_STRING(expectedCallbackString, CallbackMsgString); - CHECK_EQUAL_C_INT(true, dcHandlerInvoked); - - IOT_DEBUG("-->Success - G:12 - Yield, resubscribe to all topics on reconnect \n"); -} diff --git a/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls.c b/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls.c deleted file mode 100644 index 950b5a1580..0000000000 --- a/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls.c +++ /dev/null @@ -1,202 +0,0 @@ -#include -#include -#include - -#include "network_interface.h" -#include "aws_iot_tests_unit_mock_tls_params.h" - - -void _iot_tls_set_connect_params(Network *pNetwork, char *pRootCALocation, char *pDeviceCertLocation, - char *pDevicePrivateKeyLocation, char *pDestinationURL, - uint16_t destinationPort, uint32_t timeout_ms, bool ServerVerificationFlag) { - pNetwork->tlsConnectParams.DestinationPort = destinationPort; - pNetwork->tlsConnectParams.pDestinationURL = pDestinationURL; - pNetwork->tlsConnectParams.pDeviceCertLocation = pDeviceCertLocation; - pNetwork->tlsConnectParams.pDevicePrivateKeyLocation = pDevicePrivateKeyLocation; - pNetwork->tlsConnectParams.pRootCALocation = pRootCALocation; - pNetwork->tlsConnectParams.timeout_ms = timeout_ms; - pNetwork->tlsConnectParams.ServerVerificationFlag = ServerVerificationFlag; -} - -IoT_Error_t iot_tls_init(Network *pNetwork, char *pRootCALocation, char *pDeviceCertLocation, - char *pDevicePrivateKeyLocation, char *pDestinationURL, - uint16_t destinationPort, uint32_t timeout_ms, bool ServerVerificationFlag) { - _iot_tls_set_connect_params(pNetwork, pRootCALocation, pDeviceCertLocation, pDevicePrivateKeyLocation, - pDestinationURL, destinationPort, timeout_ms, ServerVerificationFlag); - - pNetwork->connect = iot_tls_connect; - pNetwork->read = iot_tls_read; - pNetwork->write = iot_tls_write; - pNetwork->disconnect = iot_tls_disconnect; - pNetwork->isConnected = iot_tls_is_connected; - pNetwork->destroy = iot_tls_destroy; - - return SUCCESS; -} - -IoT_Error_t iot_tls_connect(Network *pNetwork, TLSConnectParams *params) { - IOT_UNUSED(pNetwork); - - if(NULL != params) { - _iot_tls_set_connect_params(pNetwork, params->pRootCALocation, params->pDeviceCertLocation, - params->pDevicePrivateKeyLocation, params->pDestinationURL, params->DestinationPort, - params->timeout_ms, params->ServerVerificationFlag); - } - - if(NULL != invalidEndpointFilter && 0 == strcmp(invalidEndpointFilter, pNetwork->tlsConnectParams.pDestinationURL)) { - return NETWORK_ERR_NET_UNKNOWN_HOST; - } - - if(invalidPortFilter == pNetwork->tlsConnectParams.DestinationPort) { - return NETWORK_ERR_NET_CONNECT_FAILED; - } - - if(NULL != invalidRootCAPathFilter && 0 == strcmp(invalidRootCAPathFilter, pNetwork->tlsConnectParams.pRootCALocation)) { - return NETWORK_ERR_NET_CONNECT_FAILED; - } - - if(NULL != invalidCertPathFilter && 0 == strcmp(invalidCertPathFilter, pNetwork->tlsConnectParams.pDeviceCertLocation)) { - return NETWORK_ERR_NET_CONNECT_FAILED; - } - - if(NULL != invalidPrivKeyPathFilter && 0 == strcmp(invalidPrivKeyPathFilter, pNetwork->tlsConnectParams.pDevicePrivateKeyLocation)) { - return NETWORK_ERR_NET_CONNECT_FAILED; - } - return SUCCESS; -} - -IoT_Error_t iot_tls_is_connected(Network *pNetwork) { - IOT_UNUSED(pNetwork); - - /* Use this to add implementation which can check for physical layer disconnect */ - return NETWORK_PHYSICAL_LAYER_CONNECTED; -} - -static size_t iot_tls_mqtt_read_variable_length_int(const unsigned char *buffer, size_t startPos) { - size_t result = 0; - size_t pos = startPos; - size_t multiplier = 1; - do { - result = (buffer[pos] & 0x7f) * multiplier; - multiplier *= 0x80; - pos++; - } while ((buffer[pos - 1] & 0x80) && pos - startPos < 4); - - return result; -} - -static size_t iot_tls_mqtt_get_end_of_variable_length_int(const unsigned char *buffer, size_t startPos) { - size_t pos = startPos; - while ((buffer[pos] & 0x80) && pos - startPos < 3) pos++; - pos++; - - return pos; -} - -static uint16_t iot_tls_mqtt_get_fixed_uint16_from_message(const unsigned char *msgBuffer, size_t startPos) { - uint8_t firstByte = (uint8_t)(msgBuffer[startPos]); - uint8_t secondByte = (uint8_t)(msgBuffer[startPos + 1]); - return (uint16_t) (secondByte + (256 * firstByte)); -} - -static uint16_t iot_tls_mqtt_copy_string_from_message(char *dest, const unsigned char *msgBuffer, size_t startPos) { - uint16_t length = iot_tls_mqtt_get_fixed_uint16_from_message(msgBuffer, startPos); - - snprintf(dest, length + 1u, "%s", &(msgBuffer[startPos + 2])); // Added one for null character - return length; -} - -IoT_Error_t iot_tls_write(Network *pNetwork, unsigned char *pMsg, size_t len, Timer *timer, size_t *written_len) { - size_t i = 0; - uint8_t firstPacketByte; - size_t mqttPacketLength; - size_t variableHeaderStart; - IOT_UNUSED(pNetwork); - IOT_UNUSED(timer); - - for(i = 0; (i < len) && left_ms(timer) > 0; i++) { - TxBuffer.pBuffer[i] = pMsg[i]; - } - TxBuffer.len = len; - *written_len = len; - - mqttPacketLength = iot_tls_mqtt_read_variable_length_int(TxBuffer.pBuffer, 1); - variableHeaderStart = iot_tls_mqtt_get_end_of_variable_length_int(TxBuffer.pBuffer, 1); - - firstPacketByte = TxBuffer.pBuffer[0]; - /* Save last two subscribed topics */ - if((firstPacketByte == 0x82 ? true : false)) { - snprintf(SecondLastSubscribeMessage, lastSubscribeMsgLen + 1u, "%s", LastSubscribeMessage); - secondLastSubscribeMsgLen = lastSubscribeMsgLen; - - lastSubscribeMsgLen = iot_tls_mqtt_copy_string_from_message( - LastSubscribeMessage, TxBuffer.pBuffer, variableHeaderStart + 2); - } else if (firstPacketByte == 0xA2) { - lastUnsubscribeMsgLen = iot_tls_mqtt_copy_string_from_message( - LastUnsubscribeMessage, TxBuffer.pBuffer, variableHeaderStart + 2); - } else if ((firstPacketByte & 0x30) == 0x30) { - QoS qos = (QoS) (firstPacketByte & 0x6 >> 1); - - lastPublishMessageTopicLen = - iot_tls_mqtt_copy_string_from_message(LastPublishMessageTopic, TxBuffer.pBuffer, variableHeaderStart); - - size_t payloadStart = variableHeaderStart + 2 + lastPublishMessageTopicLen; - - if (qos > QOS0) { - payloadStart += 2; - } - - lastPublishMessagePayloadLen = mqttPacketLength - payloadStart + 2; /* + 2 as the first two bytes don't count towards the length */ - memcpy(LastPublishMessagePayload, TxBuffer.pBuffer + payloadStart, lastPublishMessagePayloadLen); - LastPublishMessagePayload[lastPublishMessagePayloadLen] = 0; - } - - return SUCCESS; -} - -static unsigned char isTimerExpired(struct timeval target_time) { - unsigned char ret_val = 0; - struct timeval now, result; - - if(target_time.tv_sec != 0 || target_time.tv_usec != 0) { - gettimeofday(&now, NULL); - timersub(&(target_time), &now, &result); - if(result.tv_sec < 0 || (result.tv_sec == 0 && result.tv_usec <= 0)) { - ret_val = 1; - } - } else { - ret_val = 1; - } - return ret_val; -} - -IoT_Error_t iot_tls_read(Network *pNetwork, unsigned char *pMsg, size_t len, Timer *pTimer, size_t *read_len) { - IOT_UNUSED(pNetwork); - IOT_UNUSED(pTimer); - - if(RxIndex > TLSMaxBufferSize - 1) { - RxIndex = TLSMaxBufferSize - 1; - } - - if(RxBuffer.len <= RxIndex || !isTimerExpired(RxBuffer.expiry_time)) { - return NETWORK_SSL_NOTHING_TO_READ; - } - - if((false == RxBuffer.NoMsgFlag) && (RxIndex < RxBuffer.len)) { - memcpy(pMsg, &(RxBuffer.pBuffer[RxIndex]), len); - RxIndex += len; - *read_len = len; - } - - return SUCCESS; -} - -IoT_Error_t iot_tls_disconnect(Network *pNetwork) { - IOT_UNUSED(pNetwork); - return SUCCESS; -} - -IoT_Error_t iot_tls_destroy(Network *pNetwork) { - IOT_UNUSED(pNetwork); - return SUCCESS; -} diff --git a/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.c b/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.c deleted file mode 100644 index 7523cdca50..0000000000 --- a/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.c +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_mock_tls_params.c - * @brief IoT Client Unit Testing Mock TLS Params - */ - -#include "aws_iot_tests_unit_mock_tls_params.h" - -unsigned char RxBuf[TLSMaxBufferSize]; -unsigned char TxBuf[TLSMaxBufferSize]; -char LastSubscribeMessage[TLSMaxBufferSize]; -size_t lastSubscribeMsgLen; -char SecondLastSubscribeMessage[TLSMaxBufferSize]; -size_t secondLastSubscribeMsgLen; - -char LastUnsubscribeMessage[TLSMaxBufferSize]; -size_t lastUnsubscribeMsgLen; - -char LastPublishMessageTopic[TLSMaxBufferSize]; -size_t lastPublishMessageTopicLen; -char LastPublishMessagePayload[TLSMaxBufferSize]; -size_t lastPublishMessagePayloadLen; - -TlsBuffer RxBuffer = {.pBuffer = RxBuf,.len = 512, .NoMsgFlag=1, .expiry_time = {0, 0}, .BufMaxSize = TLSMaxBufferSize}; -TlsBuffer TxBuffer = {.pBuffer = TxBuf,.len = 512, .NoMsgFlag=1, .expiry_time = {0, 0}, .BufMaxSize = TLSMaxBufferSize}; - -size_t RxIndex = 0; - -char *invalidEndpointFilter; -char *invalidRootCAPathFilter; -char *invalidCertPathFilter; -char *invalidPrivKeyPathFilter; -uint16_t invalidPortFilter; diff --git a/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.h b/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.h deleted file mode 100644 index cfaad235ba..0000000000 --- a/tests/unit/tls_mock/aws_iot_tests_unit_mock_tls_params.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. -* -* Licensed under the Apache License, Version 2.0 (the "License"). -* You may not use this file except in compliance with the License. -* A copy of the License is located at -* -* http://aws.amazon.com/apache2.0 -* -* or in the "license" file accompanying this file. This file is distributed -* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -* express or implied. See the License for the specific language governing -* permissions and limitations under the License. -*/ - -/** - * @file aws_iot_tests_unit_mock_tls_params.h - * @brief IoT Client Unit Testing Mock TLS Params - */ - -#ifndef UNITTESTS_MOCKS_TLS_PARAMS_H_ -#define UNITTESTS_MOCKS_TLS_PARAMS_H_ - -#include -#include -#include -#include -#include - -#define TLSMaxBufferSize 2560 -#define NO_MSG_LENGTH_MENTIONED -1 - -typedef struct { - unsigned char *pBuffer; - size_t len; - bool NoMsgFlag; - struct timeval expiry_time; - size_t BufMaxSize; -} TlsBuffer; - - -extern TlsBuffer RxBuffer; -extern TlsBuffer TxBuffer; - -extern size_t RxIndex; -extern unsigned char RxBuf[TLSMaxBufferSize]; -extern unsigned char TxBuf[TLSMaxBufferSize]; -extern char LastSubscribeMessage[TLSMaxBufferSize]; -extern size_t lastSubscribeMsgLen; -extern char SecondLastSubscribeMessage[TLSMaxBufferSize]; -extern size_t secondLastSubscribeMsgLen; - -extern char LastUnsubscribeMessage[TLSMaxBufferSize]; -extern size_t lastUnsubscribeMsgLen; - -extern char LastPublishMessageTopic[TLSMaxBufferSize]; -extern size_t lastPublishMessageTopicLen; -extern char LastPublishMessagePayload[TLSMaxBufferSize]; -extern size_t lastPublishMessagePayloadLen; - -extern char hostAddress[512]; -extern uint16_t port; -extern uint32_t handshakeTimeout_ms; - -extern char *invalidEndpointFilter; -extern char *invalidRootCAPathFilter; -extern char *invalidCertPathFilter; -extern char *invalidPrivKeyPathFilter; -extern uint16_t invalidPortFilter; - -#endif /* UNITTESTS_MOCKS_TLS_PARAMS_H_ */ diff --git a/tests/unit/tls_mock/network_platform.h b/tests/unit/tls_mock/network_platform.h deleted file mode 100644 index 1bbec842f5..0000000000 --- a/tests/unit/tls_mock/network_platform.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Created by chaurah on 3/22/16. -// - -#ifndef IOTSDKC_NETWORK_MBEDTLS_PLATFORM_H_H - -/** - * @brief TLS Connection Parameters - * - * Defines a type containing TLS specific parameters to be passed down to the - * TLS networking layer to create a TLS secured socket. - */ -typedef struct _TLSDataParams { - uint32_t flags; -}TLSDataParams; - -#define IOTSDKC_NETWORK_MBEDTLS_PLATFORM_H_H - -#endif //IOTSDKC_NETWORK_MBEDTLS_PLATFORM_H_H diff --git a/tests/unity/CMakeLists.txt b/tests/unity/CMakeLists.txt new file mode 100644 index 0000000000..8517118e24 --- /dev/null +++ b/tests/unity/CMakeLists.txt @@ -0,0 +1,11 @@ +# Unity test framework. +add_library( unity SHARED + unity.c + fixture/unity_fixture.c + fixture/unity_memory_mt.c ) + +set_target_properties( unity PROPERTIES PUBLIC_HEADER + "unity.h;fixture/unity_fixture.h;fixture/unity_fixture_malloc_overrides.h" ) + +set_target_properties( unity PROPERTIES PRIVATE_HEADER + "unity_internals.h;fixture/unity_fixture_internals.h" ) diff --git a/tests/unity/fixture/unity_fixture.c b/tests/unity/fixture/unity_fixture.c new file mode 100644 index 0000000000..c2f3ca34f9 --- /dev/null +++ b/tests/unity/fixture/unity_fixture.c @@ -0,0 +1,444 @@ +/* Copyright (c) 2010 James Grenning and Contributed to Unity Project + * ========================================== + * Unity Project - A Test Framework for C + * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams + * [Released under MIT License. Please refer to license.txt for details] + * ========================================== */ + +#include "unity_fixture.h" +#include "unity_internals.h" +#include + +/* For thread safety. */ +#include + +struct UNITY_FIXTURE_T UnityFixture; + +/* If you decide to use the function pointer approach. + * Build with -D UNITY_OUTPUT_CHAR=outputChar and include + * int (*outputChar)(int) = putchar; */ + +#if !defined(UNITY_WEAK_ATTRIBUTE) && !defined(UNITY_WEAK_PRAGMA) +void setUp(void) { /*does nothing*/ } +void tearDown(void) { /*does nothing*/ } +#endif + +static void announceTestRun(unsigned int runNumber) +{ + UnityPrint("Unity test run "); + UnityPrintNumberUnsigned(runNumber+1); + UnityPrint(" of "); + UnityPrintNumberUnsigned(UnityFixture.RepeatCount); + UNITY_PRINT_EOL(); +} + +int UnityMain(int argc, const char* argv[], void (*runAllTests)(void)) +{ + int result = UnityGetCommandLineOptions(argc, argv); + unsigned int r; + if (result != 0) + return result; + + for (r = 0; r < UnityFixture.RepeatCount; r++) + { + UnityBegin(argv[0]); + announceTestRun(r); + runAllTests(); + if (!UnityFixture.Verbose) UNITY_PRINT_EOL(); + UnityEnd(); + } + + return (int)Unity.TestFailures; +} + +static int selected(const char* filter, const char* name) +{ + if (filter == 0) + return 1; + return strstr(name, filter) ? 1 : 0; +} + +static int testSelected(const char* test) +{ + return selected(UnityFixture.NameFilter, test); +} + +static int groupSelected(const char* group) +{ + return selected(UnityFixture.GroupFilter, group); +} + +void UnityTestRunner(unityfunction* setup, + unityfunction* testBody, + unityfunction* teardown, + const char* printableName, + const char* group, + const char* name, + const char* file, + unsigned int line) +{ + if (testSelected(name) && groupSelected(group)) + { + Unity.TestFile = file; + Unity.CurrentTestName = printableName; + Unity.CurrentTestLineNumber = line; + if (!UnityFixture.Verbose) + UNITY_OUTPUT_CHAR('.'); + else + { + UnityPrint(printableName); + #ifndef UNITY_REPEAT_TEST_NAME + Unity.CurrentTestName = NULL; + #endif + } + + Unity.NumberOfTests++; + UnityMalloc_StartTest(); + UnityPointer_Init(); + + if (TEST_PROTECT()) + { + setup(); + testBody(); + } + if (TEST_PROTECT()) + { + teardown(); + } + if (TEST_PROTECT()) + { + UnityPointer_UndoAllSets(); + if (!Unity.CurrentTestFailed) + UnityMalloc_EndTest(); + } + UnityConcludeFixtureTest(); + } +} + +void UnityIgnoreTest(const char* printableName, const char* group, const char* name) +{ + if (testSelected(name) && groupSelected(group)) + { + Unity.NumberOfTests++; + Unity.TestIgnores++; + if (!UnityFixture.Verbose) + UNITY_OUTPUT_CHAR('!'); + else + { + UnityPrint(printableName); + UNITY_PRINT_EOL(); + } + } +} + + +/*------------------------------------------------- */ +/* Malloc and free stuff */ +#define MALLOC_DONT_FAIL -1 +static int malloc_count; +static int malloc_fail_countdown = MALLOC_DONT_FAIL; +extern pthread_mutex_t CriticalSectionMutex; + +void UnityMalloc_StartTest(void) +{ + pthread_mutex_lock(&CriticalSectionMutex); + malloc_count = 0; + malloc_fail_countdown = MALLOC_DONT_FAIL; + pthread_mutex_unlock(&CriticalSectionMutex); +} + +void UnityMalloc_EndTest(void) +{ + pthread_mutex_lock(&CriticalSectionMutex); + malloc_fail_countdown = MALLOC_DONT_FAIL; + if (malloc_count != 0) + { + pthread_mutex_unlock(&CriticalSectionMutex); + UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "This test leaks!"); + } + + pthread_mutex_unlock(&CriticalSectionMutex); +} + +void UnityMalloc_MakeMallocFailAfterCount(int countdown) +{ + pthread_mutex_lock(&CriticalSectionMutex); + malloc_fail_countdown = countdown; + pthread_mutex_unlock(&CriticalSectionMutex); +} + +/* These definitions are always included from unity_fixture_malloc_overrides.h */ +/* We undef to use them or avoid conflict with per the C standard */ +#undef malloc +#undef free +#undef calloc +#undef realloc + +#ifdef UNITY_EXCLUDE_STDLIB_MALLOC +static unsigned char unity_heap[UNITY_INTERNAL_HEAP_SIZE_BYTES]; +static size_t heap_index; +#else +#include +#endif + +typedef struct GuardBytes +{ + size_t size; + size_t guard_space; +} Guard; + + +static const char end[] = "END"; + +void* unity_malloc(size_t size) +{ + char* mem; + Guard* guard; + size_t total_size = size + sizeof(Guard) + sizeof(end); + + if (malloc_fail_countdown != MALLOC_DONT_FAIL) + { + if (malloc_fail_countdown == 0) + return NULL; + malloc_fail_countdown--; + } + + if (size == 0) return NULL; +#ifdef UNITY_EXCLUDE_STDLIB_MALLOC + if (heap_index + total_size > UNITY_INTERNAL_HEAP_SIZE_BYTES) + { + guard = NULL; + } + else + { + guard = (Guard*)&unity_heap[heap_index]; + heap_index += total_size; + } +#else + guard = (Guard*)UNITY_FIXTURE_MALLOC(total_size); +#endif + if (guard == NULL) return NULL; + malloc_count++; + guard->size = size; + guard->guard_space = 0; + mem = (char*)&(guard[1]); + memcpy(&mem[size], end, sizeof(end)); + + return (void*)mem; +} + +static int isOverrun(void* mem) +{ + Guard* guard = (Guard*)mem; + char* memAsChar = (char*)mem; + guard--; + + return guard->guard_space != 0 || strcmp(&memAsChar[guard->size], end) != 0; +} + +static void release_memory(void* mem) +{ + Guard* guard = (Guard*)mem; + guard--; + + malloc_count--; +#ifdef UNITY_EXCLUDE_STDLIB_MALLOC + if (mem == unity_heap + heap_index - guard->size - sizeof(end)) + { + heap_index -= (guard->size + sizeof(Guard) + sizeof(end)); + } +#else + UNITY_FIXTURE_FREE(guard); +#endif +} + +void unity_free(void* mem) +{ + int overrun; + + if (mem == NULL) + { + return; + } + + overrun = isOverrun(mem); + release_memory(mem); + if (overrun) + { + UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "Buffer overrun detected during free()"); + } +} + +void* unity_calloc(size_t num, size_t size) +{ + void* mem = unity_malloc(num * size); + if (mem == NULL) return NULL; + memset(mem, 0, num * size); + return mem; +} + +void* unity_realloc(void* oldMem, size_t size) +{ + Guard* guard = (Guard*)oldMem; + void* newMem; + + if (oldMem == NULL) return unity_malloc(size); + + guard--; + if (isOverrun(oldMem)) + { + release_memory(oldMem); + UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "Buffer overrun detected during realloc()"); + } + + if (size == 0) + { + release_memory(oldMem); + return NULL; + } + + if (guard->size >= size) return oldMem; + +#ifdef UNITY_EXCLUDE_STDLIB_MALLOC /* Optimization if memory is expandable */ + if (oldMem == unity_heap + heap_index - guard->size - sizeof(end) && + heap_index + size - guard->size <= UNITY_INTERNAL_HEAP_SIZE_BYTES) + { + release_memory(oldMem); /* Not thread-safe, like unity_heap generally */ + return unity_malloc(size); /* No memcpy since data is in place */ + } +#endif + newMem = unity_malloc(size); + if (newMem == NULL) return NULL; /* Do not release old memory */ + memcpy(newMem, oldMem, guard->size); + release_memory(oldMem); + return newMem; +} + + +/*-------------------------------------------------------- */ +/*Automatic pointer restoration functions */ +struct PointerPair +{ + void** pointer; + void* old_value; +}; + +static struct PointerPair pointer_store[UNITY_MAX_POINTERS]; +static int pointer_index = 0; + +void UnityPointer_Init(void) +{ + pointer_index = 0; +} + +void UnityPointer_Set(void** pointer, void* newValue, UNITY_LINE_TYPE line) +{ + if (pointer_index >= UNITY_MAX_POINTERS) + { + UNITY_TEST_FAIL(line, "Too many pointers set"); + } + else + { + pointer_store[pointer_index].pointer = pointer; + pointer_store[pointer_index].old_value = *pointer; + *pointer = newValue; + pointer_index++; + } +} + +void UnityPointer_UndoAllSets(void) +{ + while (pointer_index > 0) + { + pointer_index--; + *(pointer_store[pointer_index].pointer) = + pointer_store[pointer_index].old_value; + } +} + +int UnityGetCommandLineOptions(int argc, const char* argv[]) +{ + int i; + UnityFixture.Verbose = 0; + UnityFixture.GroupFilter = 0; + UnityFixture.NameFilter = 0; + UnityFixture.RepeatCount = 1; + + if (argc == 1) + return 0; + + for (i = 1; i < argc; ) + { + if (strcmp(argv[i], "-v") == 0) + { + UnityFixture.Verbose = 1; + i++; + } + else if (strcmp(argv[i], "-g") == 0) + { + i++; + if (i >= argc) + return 1; + UnityFixture.GroupFilter = argv[i]; + i++; + } + else if (strcmp(argv[i], "-n") == 0) + { + i++; + if (i >= argc) + return 1; + UnityFixture.NameFilter = argv[i]; + i++; + } + else if (strcmp(argv[i], "-r") == 0) + { + UnityFixture.RepeatCount = 2; + i++; + if (i < argc) + { + if (*(argv[i]) >= '0' && *(argv[i]) <= '9') + { + unsigned int digit = 0; + UnityFixture.RepeatCount = 0; + while (argv[i][digit] >= '0' && argv[i][digit] <= '9') + { + UnityFixture.RepeatCount *= 10; + UnityFixture.RepeatCount += (unsigned int)argv[i][digit++] - '0'; + } + i++; + } + } + } + else + { + /* ignore unknown parameter */ + i++; + } + } + return 0; +} + +void UnityConcludeFixtureTest(void) +{ + if (Unity.CurrentTestIgnored) + { + Unity.TestIgnores++; + UNITY_PRINT_EOL(); + } + else if (!Unity.CurrentTestFailed) + { + if (UnityFixture.Verbose) + { + UnityPrint(" PASS"); + UNITY_PRINT_EOL(); + } + } + else /* Unity.CurrentTestFailed */ + { + Unity.TestFailures++; + UNITY_PRINT_EOL(); + } + + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; +} diff --git a/tests/unity/fixture/unity_fixture.h b/tests/unity/fixture/unity_fixture.h new file mode 100644 index 0000000000..2dcf473c7f --- /dev/null +++ b/tests/unity/fixture/unity_fixture.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2010 James Grenning and Contributed to Unity Project + * ========================================== + * Unity Project - A Test Framework for C + * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams + * [Released under MIT License. Please refer to license.txt for details] + * ========================================== */ + +#ifndef UNITY_FIXTURE_H_ +#define UNITY_FIXTURE_H_ + +#include "unity.h" +#include "unity_internals.h" +#include "unity_fixture_malloc_overrides.h" +#include "unity_fixture_internals.h" + +int UnityMain(int argc, const char* argv[], void (*runAllTests)(void)); + + +#define TEST_GROUP(group)\ + static const char* TEST_GROUP_##group = #group + +#define TEST_SETUP(group) void TEST_##group##_SETUP(void);\ + void TEST_##group##_SETUP(void) + +#define TEST_TEAR_DOWN(group) void TEST_##group##_TEAR_DOWN(void);\ + void TEST_##group##_TEAR_DOWN(void) + + +#define TEST(group, name) \ + void TEST_##group##_##name##_(void);\ + void TEST_##group##_##name##_run(void);\ + void TEST_##group##_##name##_run(void)\ + {\ + UnityTestRunner(TEST_##group##_SETUP,\ + TEST_##group##_##name##_,\ + TEST_##group##_TEAR_DOWN,\ + "TEST(" #group ", " #name ")",\ + TEST_GROUP_##group, #name,\ + __FILE__, __LINE__);\ + }\ + void TEST_##group##_##name##_(void) + +#define IGNORE_TEST(group, name) \ + void TEST_##group##_##name##_(void);\ + void TEST_##group##_##name##_run(void);\ + void TEST_##group##_##name##_run(void)\ + {\ + UnityIgnoreTest("IGNORE_TEST(" #group ", " #name ")", TEST_GROUP_##group, #name);\ + }\ + void TEST_##group##_##name##_(void) + +/* Call this for each test, insider the group runner */ +#define RUN_TEST_CASE(group, name) \ + { void TEST_##group##_##name##_run(void);\ + TEST_##group##_##name##_run(); } + +/* This goes at the bottom of each test file or in a separate c file */ +#define TEST_GROUP_RUNNER(group)\ + void TEST_##group##_GROUP_RUNNER(void);\ + void TEST_##group##_GROUP_RUNNER(void) + +/* Call this from main */ +#define RUN_TEST_GROUP(group)\ + { void TEST_##group##_GROUP_RUNNER(void);\ + TEST_##group##_GROUP_RUNNER(); } + +/* CppUTest Compatibility Macros */ +#ifndef UNITY_EXCLUDE_CPPUTEST_ASSERTS +/* Sets a pointer and automatically restores it to its old value after teardown */ +#define UT_PTR_SET(ptr, newPointerValue) UnityPointer_Set((void**)&(ptr), (void*)(newPointerValue), __LINE__) +#define TEST_ASSERT_POINTERS_EQUAL(expected, actual) TEST_ASSERT_EQUAL_PTR((expected), (actual)) +#define TEST_ASSERT_BYTES_EQUAL(expected, actual) TEST_ASSERT_EQUAL_HEX8(0xff & (expected), 0xff & (actual)) +#define FAIL(message) TEST_FAIL_MESSAGE((message)) +#define CHECK(condition) TEST_ASSERT_TRUE((condition)) +#define LONGS_EQUAL(expected, actual) TEST_ASSERT_EQUAL_INT((expected), (actual)) +#define STRCMP_EQUAL(expected, actual) TEST_ASSERT_EQUAL_STRING((expected), (actual)) +#define DOUBLES_EQUAL(expected, actual, delta) TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual)) +#endif + +/* You must compile with malloc replacement, as defined in unity_fixture_malloc_overrides.h */ +void UnityMalloc_MakeMallocFailAfterCount(int countdown); + +#endif /* UNITY_FIXTURE_H_ */ diff --git a/tests/unity/fixture/unity_fixture_internals.h b/tests/unity/fixture/unity_fixture_internals.h new file mode 100644 index 0000000000..00cee88307 --- /dev/null +++ b/tests/unity/fixture/unity_fixture_internals.h @@ -0,0 +1,51 @@ +/* Copyright (c) 2010 James Grenning and Contributed to Unity Project + * ========================================== + * Unity Project - A Test Framework for C + * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams + * [Released under MIT License. Please refer to license.txt for details] + * ========================================== */ + +#ifndef UNITY_FIXTURE_INTERNALS_H_ +#define UNITY_FIXTURE_INTERNALS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct UNITY_FIXTURE_T +{ + int Verbose; + unsigned int RepeatCount; + const char* NameFilter; + const char* GroupFilter; +}; +extern struct UNITY_FIXTURE_T UnityFixture; + +typedef void unityfunction(void); +void UnityTestRunner(unityfunction* setup, + unityfunction* testBody, + unityfunction* teardown, + const char* printableName, + const char* group, + const char* name, + const char* file, unsigned int line); + +void UnityIgnoreTest(const char* printableName, const char* group, const char* name); +void UnityMalloc_StartTest(void); +void UnityMalloc_EndTest(void); +int UnityGetCommandLineOptions(int argc, const char* argv[]); +void UnityConcludeFixtureTest(void); + +void UnityPointer_Set(void** pointer, void* newValue, UNITY_LINE_TYPE line); +void UnityPointer_UndoAllSets(void); +void UnityPointer_Init(void); +#ifndef UNITY_MAX_POINTERS +#define UNITY_MAX_POINTERS 5 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UNITY_FIXTURE_INTERNALS_H_ */ diff --git a/tests/unity/fixture/unity_fixture_malloc_overrides.h b/tests/unity/fixture/unity_fixture_malloc_overrides.h new file mode 100644 index 0000000000..aa9696cf73 --- /dev/null +++ b/tests/unity/fixture/unity_fixture_malloc_overrides.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2010 James Grenning and Contributed to Unity Project + * ========================================== + * Unity Project - A Test Framework for C + * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams + * [Released under MIT License. Please refer to license.txt for details] + * ========================================== */ + +#ifndef UNITY_FIXTURE_MALLOC_OVERRIDES_H_ +#define UNITY_FIXTURE_MALLOC_OVERRIDES_H_ + +#include + +#ifdef UNITY_EXCLUDE_STDLIB_MALLOC +/* Define this macro to remove the use of stdlib.h, malloc, and free. + * Many embedded systems do not have a heap or malloc/free by default. + * This internal unity_malloc() provides allocated memory deterministically from + * the end of an array only, unity_free() only releases from end-of-array, + * blocks are not coalesced, and memory not freed in LIFO order is stranded. */ + #ifndef UNITY_INTERNAL_HEAP_SIZE_BYTES + #define UNITY_INTERNAL_HEAP_SIZE_BYTES 4096 + #endif +#endif + +/* These functions are used by the Unity Fixture to allocate and release memory + * on the heap and can be overridden with platform-specific implementations. + * For example, when using FreeRTOS UNITY_FIXTURE_MALLOC becomes pvPortMalloc() + * and UNITY_FIXTURE_FREE becomes vPortFree(). */ +#if !defined(UNITY_FIXTURE_MALLOC) || !defined(UNITY_FIXTURE_FREE) + #include + #define UNITY_FIXTURE_MALLOC(size) malloc(size) + #define UNITY_FIXTURE_FREE(ptr) free(ptr) +#else + extern void* UNITY_FIXTURE_MALLOC(size_t size); + extern void UNITY_FIXTURE_FREE(void* ptr); +#endif + +#define malloc unity_malloc +#define calloc unity_calloc +#define realloc unity_realloc +#define free unity_free + +void* unity_malloc(size_t size); +void* unity_calloc(size_t num, size_t size); +void* unity_realloc(void * oldMem, size_t size); +void unity_free(void * mem); + +/* Thread-safety wrappers for the unity memory functions. */ +void* unity_malloc_mt(size_t size); +void* unity_calloc_mt(size_t num, size_t size); +void* unity_realloc_mt(void * oldMem, size_t size); +void unity_free_mt(void * mem); + +#endif /* UNITY_FIXTURE_MALLOC_OVERRIDES_H_ */ diff --git a/tests/unity/fixture/unity_memory_mt.c b/tests/unity/fixture/unity_memory_mt.c new file mode 100644 index 0000000000..319bdd1a36 --- /dev/null +++ b/tests/unity/fixture/unity_memory_mt.c @@ -0,0 +1,67 @@ +/* Wrappers that make the unity memory functions thread-safe. Implemented for + * POSIX systems. */ + +#include "unity_fixture_malloc_overrides.h" +#include + +pthread_mutex_t CriticalSectionMutex = PTHREAD_MUTEX_INITIALIZER; + +void* unity_malloc_mt(size_t size) +{ + void* mem = NULL; + + if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + + mem = unity_malloc(size); + + if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) + { + unity_free(mem); + mem = NULL; + } + + return mem; +} + +void* unity_calloc_mt(size_t num, size_t size) +{ + void* mem = NULL; + + if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + + mem = unity_calloc(num, size); + + if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) + { + unity_free(mem); + mem = NULL; + } + + return mem; +} + +void* unity_realloc_mt(void * oldMem, size_t size) +{ + void* mem = NULL; + + if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + + mem = unity_realloc(oldMem, size); + + if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) + { + unity_free(mem); + mem = NULL; + } + + return mem; +} + +void unity_free_mt(void * mem) +{ + pthread_mutex_lock(&CriticalSectionMutex); + + unity_free(mem); + + pthread_mutex_unlock(&CriticalSectionMutex); +} diff --git a/tests/unity/unity.c b/tests/unity/unity.c new file mode 100644 index 0000000000..44a8003222 --- /dev/null +++ b/tests/unity/unity.c @@ -0,0 +1,1570 @@ +/* ========================================================================= + Unity Project - A Test Framework for C + Copyright (c) 2007-14 Mike Karlesky, Mark VanderVoord, Greg Williams + [Released under MIT License. Please refer to license.txt for details] +============================================================================ */ + +#define UNITY_INCLUDE_SETUP_STUBS +#include "unity.h" +#include + +/* If omitted from header, declare overrideable prototypes here so they're ready for use */ +#ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION +void UNITY_OUTPUT_CHAR(int); +#endif + +/* Helpful macros for us to use here in Assert functions */ +#define UNITY_FAIL_AND_BAIL { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } +#define UNITY_IGNORE_AND_BAIL { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } +#define RETURN_IF_FAIL_OR_IGNORE if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) return + +struct UNITY_STORAGE_T Unity; + +#ifdef UNITY_OUTPUT_COLOR +static const char UnityStrOk[] = "\033[42mOK\033[00m"; +static const char UnityStrPass[] = "\033[42mPASS\033[00m"; +static const char UnityStrFail[] = "\033[41mFAIL\033[00m"; +static const char UnityStrIgnore[] = "\033[43mIGNORE\033[00m"; +#else +static const char UnityStrOk[] = "OK"; +static const char UnityStrPass[] = "PASS"; +static const char UnityStrFail[] = "FAIL"; +static const char UnityStrIgnore[] = "IGNORE"; +#endif +static const char UnityStrNull[] = "NULL"; +static const char UnityStrSpacer[] = ". "; +static const char UnityStrExpected[] = " Expected "; +static const char UnityStrWas[] = " Was "; +static const char UnityStrGt[] = " to be greater than "; +static const char UnityStrLt[] = " to be less than "; +static const char UnityStrOrEqual[] = "or equal to "; +static const char UnityStrElement[] = " Element "; +static const char UnityStrByte[] = " Byte "; +static const char UnityStrMemory[] = " Memory Mismatch."; +static const char UnityStrDelta[] = " Values Not Within Delta "; +static const char UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; +static const char UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; +static const char UnityStrNullPointerForActual[] = " Actual pointer was NULL"; +#ifndef UNITY_EXCLUDE_FLOAT +static const char UnityStrNot[] = "Not "; +static const char UnityStrInf[] = "Infinity"; +static const char UnityStrNegInf[] = "Negative Infinity"; +static const char UnityStrNaN[] = "NaN"; +static const char UnityStrDet[] = "Determinate"; +static const char UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; +#endif +const char UnityStrErrFloat[] = "Unity Floating Point Disabled"; +const char UnityStrErrDouble[] = "Unity Double Precision Disabled"; +const char UnityStrErr64[] = "Unity 64-bit Support Disabled"; +static const char UnityStrBreaker[] = "-----------------------"; +static const char UnityStrResultsTests[] = " Tests "; +static const char UnityStrResultsFailures[] = " Failures "; +static const char UnityStrResultsIgnored[] = " Ignored "; +static const char UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; +static const char UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; + +/*----------------------------------------------- + * Pretty Printers & Test Result Output Handlers + *-----------------------------------------------*/ + +void UnityPrint(const char* string) +{ + const char* pch = string; + + if (pch != NULL) + { + while (*pch) + { + /* printable characters plus CR & LF are printed */ + if ((*pch <= 126) && (*pch >= 32)) + { + UNITY_OUTPUT_CHAR(*pch); + } + /* write escaped carriage returns */ + else if (*pch == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (*pch == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } +#ifdef UNITY_OUTPUT_COLOR + /* print ANSI escape code */ + else if (*pch == 27 && *(pch + 1) == '[') + { + while (*pch && *pch != 'm') + { + UNITY_OUTPUT_CHAR(*pch); + pch++; + } + UNITY_OUTPUT_CHAR('m'); + } +#endif + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)*pch, 2); + } + pch++; + } + } +} + +void UnityPrintLen(const char* string, const UNITY_UINT32 length) +{ + const char* pch = string; + + if (pch != NULL) + { + while (*pch && (UNITY_UINT32)(pch - string) < length) + { + /* printable characters plus CR & LF are printed */ + if ((*pch <= 126) && (*pch >= 32)) + { + UNITY_OUTPUT_CHAR(*pch); + } + /* write escaped carriage returns */ + else if (*pch == 13) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('r'); + } + /* write escaped line feeds */ + else if (*pch == 10) + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('n'); + } + /* unprintable characters are shown as codes */ + else + { + UNITY_OUTPUT_CHAR('\\'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)*pch, 2); + } + pch++; + } + } +} + +/*-----------------------------------------------*/ +void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style) +{ + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + UnityPrintNumber(number); + } + else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT) + { + UnityPrintNumberUnsigned((UNITY_UINT)number); + } + else + { + UNITY_OUTPUT_CHAR('0'); + UNITY_OUTPUT_CHAR('x'); + UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2)); + } +} + +/*-----------------------------------------------*/ +void UnityPrintNumber(const UNITY_INT number_to_print) +{ + UNITY_UINT number = (UNITY_UINT)number_to_print; + + if (number_to_print < 0) + { + /* A negative number, including MIN negative */ + UNITY_OUTPUT_CHAR('-'); + number = (UNITY_UINT)(-number_to_print); + } + UnityPrintNumberUnsigned(number); +} + +/*----------------------------------------------- + * basically do an itoa using as little ram as possible */ +void UnityPrintNumberUnsigned(const UNITY_UINT number) +{ + UNITY_UINT divisor = 1; + + /* figure out initial divisor */ + while (number / divisor > 9) + { + divisor *= 10; + } + + /* now mod and print, then divide divisor */ + do + { + UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10))); + divisor /= 10; + } while (divisor > 0); +} + +/*-----------------------------------------------*/ +void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print) +{ + int nibble; + char nibbles = nibbles_to_print; + if ((unsigned)nibbles > (2 * sizeof(number))) + nibbles = 2 * sizeof(number); + + while (nibbles > 0) + { + nibbles--; + nibble = (int)(number >> (nibbles * 4)) & 0x0F; + if (nibble <= 9) + { + UNITY_OUTPUT_CHAR((char)('0' + nibble)); + } + else + { + UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble)); + } + } +} + +/*-----------------------------------------------*/ +void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number) +{ + UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1); + UNITY_INT32 i; + + for (i = 0; i < UNITY_INT_WIDTH; i++) + { + if (current_bit & mask) + { + if (current_bit & number) + { + UNITY_OUTPUT_CHAR('1'); + } + else + { + UNITY_OUTPUT_CHAR('0'); + } + } + else + { + UNITY_OUTPUT_CHAR('X'); + } + current_bit = current_bit >> 1; + } +} + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +/* This function prints a floating-point value in a format similar to + * printf("%.6g"). It can work with either single- or double-precision, + * but for simplicity, it prints only 6 significant digits in either case. + * Printing more than 6 digits accurately is hard (at least in the single- + * precision case) and isn't attempted here. */ +void UnityPrintFloat(const UNITY_DOUBLE input_number) +{ + UNITY_DOUBLE number = input_number; + + /* print minus sign (including for negative zero) */ + if (number < 0.0f || (number == 0.0f && 1.0f / number < 0.0f)) + { + UNITY_OUTPUT_CHAR('-'); + number = -number; + } + + /* handle zero, NaN, and +/- infinity */ + if (number == 0.0f) UnityPrint("0"); + else if (isnan(number)) UnityPrint("nan"); + else if (isinf(number)) UnityPrint("inf"); + else + { + int exponent = 0; + int decimals, digits; + UNITY_INT32 n; + char buf[16]; + + /* scale up or down by powers of 10 */ + while (number < 100000.0f / 1e6f) { number *= 1e6f; exponent -= 6; } + while (number < 100000.0f) { number *= 10.0f; exponent--; } + while (number > 1000000.0f * 1e6f) { number /= 1e6f; exponent += 6; } + while (number > 1000000.0f) { number /= 10.0f; exponent++; } + + /* round to nearest integer */ + n = ((UNITY_INT32)(number + number) + 1) / 2; + if (n > 999999) + { + n = 100000; + exponent++; + } + + /* determine where to place decimal point */ + decimals = (exponent <= 0 && exponent >= -9) ? -exponent : 5; + exponent += decimals; + + /* truncate trailing zeroes after decimal point */ + while (decimals > 0 && n % 10 == 0) + { + n /= 10; + decimals--; + } + + /* build up buffer in reverse order */ + digits = 0; + while (n != 0 || digits < decimals + 1) + { + buf[digits++] = (char)('0' + n % 10); + n /= 10; + } + while (digits > 0) + { + if(digits == decimals) UNITY_OUTPUT_CHAR('.'); + UNITY_OUTPUT_CHAR(buf[--digits]); + } + + /* print exponent if needed */ + if (exponent != 0) + { + UNITY_OUTPUT_CHAR('e'); + + if(exponent < 0) + { + UNITY_OUTPUT_CHAR('-'); + exponent = -exponent; + } + else + { + UNITY_OUTPUT_CHAR('+'); + } + + digits = 0; + while (exponent != 0 || digits < 2) + { + buf[digits++] = (char)('0' + exponent % 10); + exponent /= 10; + } + while (digits > 0) + { + UNITY_OUTPUT_CHAR(buf[--digits]); + } + } + } +} +#endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */ + +/*-----------------------------------------------*/ +static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line) +{ + UnityPrint(file); + UNITY_OUTPUT_CHAR(':'); + UnityPrintNumber((UNITY_INT)line); + UNITY_OUTPUT_CHAR(':'); + UnityPrint(Unity.CurrentTestName); + UNITY_OUTPUT_CHAR(':'); +} + +/*-----------------------------------------------*/ +static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line) +{ + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrFail); + UNITY_OUTPUT_CHAR(':'); +} + +/*-----------------------------------------------*/ +void UnityConcludeTest(void) +{ + if (Unity.CurrentTestIgnored) + { + Unity.TestIgnores++; + } + else if (!Unity.CurrentTestFailed) + { + UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber); + UnityPrint(UnityStrPass); + } + else + { + Unity.TestFailures++; + } + + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; + UNITY_PRINT_EOL(); + UNITY_FLUSH_CALL(); +} + +/*-----------------------------------------------*/ +static void UnityAddMsgIfSpecified(const char* msg) +{ + if (msg) + { + UnityPrint(UnityStrSpacer); +#ifndef UNITY_EXCLUDE_DETAILS + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) + { + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); + } + UnityPrint(UnityStrSpacer); + } +#endif + UnityPrint(msg); + } +} + +/*-----------------------------------------------*/ +static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual) +{ + UnityPrint(UnityStrExpected); + if (expected != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrint(expected); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } + UnityPrint(UnityStrWas); + if (actual != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrint(actual); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } +} + +/*-----------------------------------------------*/ +static void UnityPrintExpectedAndActualStringsLen(const char* expected, + const char* actual, + const UNITY_UINT32 length) +{ + UnityPrint(UnityStrExpected); + if (expected != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrintLen(expected, length); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } + UnityPrint(UnityStrWas); + if (actual != NULL) + { + UNITY_OUTPUT_CHAR('\''); + UnityPrintLen(actual, length); + UNITY_OUTPUT_CHAR('\''); + } + else + { + UnityPrint(UnityStrNull); + } +} + +/*----------------------------------------------- + * Assertion & Control Helpers + *-----------------------------------------------*/ + +static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_LINE_TYPE lineNumber, + const char* msg) +{ + if (expected == actual) return 0; /* Both are NULL or same pointer */ + + /* print and return true if just expected is NULL */ + if (expected == NULL) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrNullPointerForExpected); + UnityAddMsgIfSpecified(msg); + return 1; + } + + /* print and return true if just actual is NULL */ + if (actual == NULL) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrNullPointerForActual); + UnityAddMsgIfSpecified(msg); + return 1; + } + + return 0; /* return false if neither is NULL */ +} + +/*----------------------------------------------- + * Assertion Functions + *-----------------------------------------------*/ + +void UnityAssertBits(const UNITY_INT mask, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if ((mask & expected) != (mask & actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected); + UnityPrint(UnityStrWas); + UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualNumber(const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (expected != actual) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expected, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, + const UNITY_INT actual, + const UNITY_COMPARISON_T compare, + const char *msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + int failed = 0; + RETURN_IF_FAIL_OR_IGNORE; + + if (threshold == actual && compare & UNITY_EQUAL_TO) return; + if (threshold == actual) failed = 1; + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (actual > threshold && compare & UNITY_SMALLER_THAN) failed = 1; + if (actual < threshold && compare & UNITY_GREATER_THAN) failed = 1; + } + else /* UINT or HEX */ + { + if ((UNITY_UINT)actual > (UNITY_UINT)threshold && compare & UNITY_SMALLER_THAN) failed = 1; + if ((UNITY_UINT)actual < (UNITY_UINT)threshold && compare & UNITY_GREATER_THAN) failed = 1; + } + + if (failed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(actual, style); + if (compare & UNITY_GREATER_THAN) UnityPrint(UnityStrGt); + if (compare & UNITY_SMALLER_THAN) UnityPrint(UnityStrLt); + if (compare & UNITY_EQUAL_TO) UnityPrint(UnityStrOrEqual); + UnityPrintNumberByStyle(threshold, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#define UnityPrintPointlessAndBail() \ +{ \ + UnityTestResultsFailBegin(lineNumber); \ + UnityPrint(UnityStrPointless); \ + UnityAddMsgIfSpecified(msg); \ + UNITY_FAIL_AND_BAIL; } + +/*-----------------------------------------------*/ +void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + unsigned int length = style & 0xF; + + RETURN_IF_FAIL_OR_IGNORE; + + if (num_elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) return; /* Both are NULL or same pointer */ + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + UNITY_FAIL_AND_BAIL; + + while ((elements > 0) && elements--) + { + UNITY_INT expect_val; + UNITY_INT actual_val; + switch (length) + { + case 1: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; + break; + case 2: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; + break; +#ifdef UNITY_SUPPORT_64 + case 8: + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; + break; +#endif + default: /* length 4 bytes */ + expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; + actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; + length = 4; + break; + } + + if (expect_val != actual_val) + { + if (style & UNITY_DISPLAY_RANGE_UINT && length < sizeof(expect_val)) + { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ + UNITY_INT mask = 1; + mask = (mask << 8 * length) - 1; + expect_val &= mask; + actual_val &= mask; + } + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expect_val, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual_val, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expected = (UNITY_INTERNAL_PTR)(length + (const char*)expected); + } + actual = (UNITY_INTERNAL_PTR)(length + (const char*)actual); + } +} + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_FLOAT +/* Wrap this define in a function with variable types as float or double */ +#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ + if (isinf(expected) && isinf(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ + if (UNITY_NAN_CHECK) return 1; \ + (diff) = (actual) - (expected); \ + if ((diff) < 0) (diff) = -(diff); \ + if ((delta) < 0) (delta) = -(delta); \ + return !(isnan(diff) || isinf(diff) || ((diff) > (delta))) + /* This first part of this condition will catch any NaN or Infinite values */ +#ifndef UNITY_NAN_NOT_EQUAL_NAN + #define UNITY_NAN_CHECK isnan(expected) && isnan(actual) +#else + #define UNITY_NAN_CHECK 0 +#endif + +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ + { \ + UnityPrint(UnityStrExpected); \ + UnityPrintFloat(expected); \ + UnityPrint(UnityStrWas); \ + UnityPrintFloat(actual); } +#else + #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ + UnityPrint(UnityStrDelta) +#endif /* UNITY_EXCLUDE_FLOAT_PRINT */ + +static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual) +{ + UNITY_FLOAT diff; + UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); +} + +void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) return; /* Both are NULL or same pointer */ + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + UNITY_FAIL_AND_BAIL; + + while (elements--) + { + if (!UnityFloatsWithin(*ptr_expected * UNITY_FLOAT_PRECISION, *ptr_expected, *ptr_actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + ptr_expected++; + } + ptr_actual++; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatsWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + + if (!UnityFloatsWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertFloatSpecial(const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style) +{ + const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; + UNITY_INT should_be_trait = ((UNITY_INT)style & 1); + UNITY_INT is_trait = !should_be_trait; + UNITY_INT trait_index = (UNITY_INT)(style >> 1); + + RETURN_IF_FAIL_OR_IGNORE; + + switch (style) + { + case UNITY_FLOAT_IS_INF: + case UNITY_FLOAT_IS_NOT_INF: + is_trait = isinf(actual) && (actual > 0); + break; + case UNITY_FLOAT_IS_NEG_INF: + case UNITY_FLOAT_IS_NOT_NEG_INF: + is_trait = isinf(actual) && (actual < 0); + break; + + case UNITY_FLOAT_IS_NAN: + case UNITY_FLOAT_IS_NOT_NAN: + is_trait = isnan(actual) ? 1 : 0; + break; + + case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ + case UNITY_FLOAT_IS_NOT_DET: + is_trait = !isinf(actual) && !isnan(actual); + break; + + default: + trait_index = 0; + trait_names[0] = UnityStrInvalidFloatTrait; + break; + } + + if (is_trait != should_be_trait) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + if (!should_be_trait) + UnityPrint(UnityStrNot); + UnityPrint(trait_names[trait_index]); + UnityPrint(UnityStrWas); +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + UnityPrintFloat((UNITY_DOUBLE)actual); +#else + if (should_be_trait) + UnityPrint(UnityStrNot); + UnityPrint(trait_names[trait_index]); +#endif + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#endif /* not UNITY_EXCLUDE_FLOAT */ + +/*-----------------------------------------------*/ +#ifndef UNITY_EXCLUDE_DOUBLE +static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual) +{ + UNITY_DOUBLE diff; + UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); +} + +void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 elements = num_elements; + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; + + RETURN_IF_FAIL_OR_IGNORE; + + if (elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) return; /* Both are NULL or same pointer */ + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + UNITY_FAIL_AND_BAIL; + + while (elements--) + { + if (!UnityDoublesWithin(*ptr_expected * UNITY_DOUBLE_PRECISION, *ptr_expected, *ptr_actual)) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + if (flags == UNITY_ARRAY_TO_ARRAY) + { + ptr_expected++; + } + ptr_actual++; + } +} + +/*-----------------------------------------------*/ +void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if (!UnityDoublesWithin(delta, expected, actual)) + { + UnityTestResultsFailBegin(lineNumber); + UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ + +void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style) +{ + const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; + UNITY_INT should_be_trait = ((UNITY_INT)style & 1); + UNITY_INT is_trait = !should_be_trait; + UNITY_INT trait_index = (UNITY_INT)(style >> 1); + + RETURN_IF_FAIL_OR_IGNORE; + + switch (style) + { + case UNITY_FLOAT_IS_INF: + case UNITY_FLOAT_IS_NOT_INF: + is_trait = isinf(actual) && (actual > 0); + break; + case UNITY_FLOAT_IS_NEG_INF: + case UNITY_FLOAT_IS_NOT_NEG_INF: + is_trait = isinf(actual) && (actual < 0); + break; + + case UNITY_FLOAT_IS_NAN: + case UNITY_FLOAT_IS_NOT_NAN: + is_trait = isnan(actual) ? 1 : 0; + break; + + case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ + case UNITY_FLOAT_IS_NOT_DET: + is_trait = !isinf(actual) && !isnan(actual); + break; + + default: + trait_index = 0; + trait_names[0] = UnityStrInvalidFloatTrait; + break; + } + + if (is_trait != should_be_trait) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrExpected); + if (!should_be_trait) + UnityPrint(UnityStrNot); + UnityPrint(trait_names[trait_index]); + UnityPrint(UnityStrWas); +#ifndef UNITY_EXCLUDE_FLOAT_PRINT + UnityPrintFloat(actual); +#else + if (should_be_trait) + UnityPrint(UnityStrNot); + UnityPrint(trait_names[trait_index]); +#endif + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +#endif /* not UNITY_EXCLUDE_DOUBLE */ + +/*-----------------------------------------------*/ +void UnityAssertNumbersWithin(const UNITY_UINT delta, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style) +{ + RETURN_IF_FAIL_OR_IGNORE; + + if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) + { + if (actual > expected) + Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(actual - expected) > delta); + else + Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(expected - actual) > delta); + } + else + { + if ((UNITY_UINT)actual > (UNITY_UINT)expected) + Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(actual - expected) > delta); + else + Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(expected - actual) > delta); + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrDelta); + UnityPrintNumberByStyle((UNITY_INT)delta, style); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(expected, style); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(actual, style); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualString(const char* expected, + const char* actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + UNITY_UINT32 i; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if both pointers not null compare the strings */ + if (expected && actual) + { + for (i = 0; expected[i] || actual[i]; i++) + { + if (expected[i] != actual[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expected != actual) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrintExpectedAndActualStrings(expected, actual); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualStringLen(const char* expected, + const char* actual, + const UNITY_UINT32 length, + const char* msg, + const UNITY_LINE_TYPE lineNumber) +{ + UNITY_UINT32 i; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if both pointers not null compare the strings */ + if (expected && actual) + { + for (i = 0; (i < length) && (expected[i] || actual[i]); i++) + { + if (expected[i] != actual[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expected != actual) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrintExpectedAndActualStringsLen(expected, actual, length); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } +} + +/*-----------------------------------------------*/ +void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, + const char** actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_UINT32 i = 0; + UNITY_UINT32 j = 0; + const char* expd = NULL; + const char* act = NULL; + + RETURN_IF_FAIL_OR_IGNORE; + + /* if no elements, it's an error */ + if (num_elements == 0) + { + UnityPrintPointlessAndBail(); + } + + if ((const void*)expected == (const void*)actual) + { + return; /* Both are NULL or same pointer */ + } + + if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) + { + UNITY_FAIL_AND_BAIL; + } + + if (flags != UNITY_ARRAY_TO_ARRAY) + { + expd = (const char*)expected; + } + + do + { + act = actual[j]; + if (flags == UNITY_ARRAY_TO_ARRAY) + { + expd = ((const char* const*)expected)[j]; + } + + /* if both pointers not null compare the strings */ + if (expd && act) + { + for (i = 0; expd[i] || act[i]; i++) + { + if (expd[i] != act[i]) + { + Unity.CurrentTestFailed = 1; + break; + } + } + } + else + { /* handle case of one pointers being null (if both null, test should pass) */ + if (expd != act) + { + Unity.CurrentTestFailed = 1; + } + } + + if (Unity.CurrentTestFailed) + { + UnityTestResultsFailBegin(lineNumber); + if (num_elements > 1) + { + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(j); + } + UnityPrintExpectedAndActualStrings(expd, act); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + } while (++j < num_elements); +} + +/*-----------------------------------------------*/ +void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 length, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags) +{ + UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; + UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual; + UNITY_UINT32 elements = num_elements; + UNITY_UINT32 bytes; + + RETURN_IF_FAIL_OR_IGNORE; + + if ((elements == 0) || (length == 0)) + { + UnityPrintPointlessAndBail(); + } + + if (expected == actual) return; /* Both are NULL or same pointer */ + if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) + UNITY_FAIL_AND_BAIL; + + while (elements--) + { + bytes = length; + while (bytes--) + { + if (*ptr_exp != *ptr_act) + { + UnityTestResultsFailBegin(lineNumber); + UnityPrint(UnityStrMemory); + if (num_elements > 1) + { + UnityPrint(UnityStrElement); + UnityPrintNumberUnsigned(num_elements - elements - 1); + } + UnityPrint(UnityStrByte); + UnityPrintNumberUnsigned(length - bytes - 1); + UnityPrint(UnityStrExpected); + UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8); + UnityPrint(UnityStrWas); + UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8); + UnityAddMsgIfSpecified(msg); + UNITY_FAIL_AND_BAIL; + } + ptr_exp++; + ptr_act++; + } + if (flags == UNITY_ARRAY_TO_VAL) + { + ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; + } + } +} + +/*-----------------------------------------------*/ + +static union +{ + UNITY_INT8 i8; + UNITY_INT16 i16; + UNITY_INT32 i32; +#ifdef UNITY_SUPPORT_64 + UNITY_INT64 i64; +#endif +#ifndef UNITY_EXCLUDE_FLOAT + float f; +#endif +#ifndef UNITY_EXCLUDE_DOUBLE + double d; +#endif +} UnityQuickCompare; + +UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size) +{ + switch(size) + { + case 1: + UnityQuickCompare.i8 = (UNITY_INT8)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8); + + case 2: + UnityQuickCompare.i16 = (UNITY_INT16)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16); + +#ifdef UNITY_SUPPORT_64 + case 8: + UnityQuickCompare.i64 = (UNITY_INT64)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64); +#endif + default: /* 4 bytes */ + UnityQuickCompare.i32 = (UNITY_INT32)num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32); + } +} + +#ifndef UNITY_EXCLUDE_FLOAT +UNITY_INTERNAL_PTR UnityFloatToPtr(const float num) +{ + UnityQuickCompare.f = num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f); +} +#endif + +#ifndef UNITY_EXCLUDE_DOUBLE +UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) +{ + UnityQuickCompare.d = num; + return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d); +} +#endif + +/*----------------------------------------------- + * Control Functions + *-----------------------------------------------*/ + +void UnityFail(const char* msg, const UNITY_LINE_TYPE line) +{ + RETURN_IF_FAIL_OR_IGNORE; + + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrFail); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + +#ifndef UNITY_EXCLUDE_DETAILS + if (Unity.CurrentDetail1) + { + UnityPrint(UnityStrDetail1Name); + UnityPrint(Unity.CurrentDetail1); + if (Unity.CurrentDetail2) + { + UnityPrint(UnityStrDetail2Name); + UnityPrint(Unity.CurrentDetail2); + } + UnityPrint(UnityStrSpacer); + } +#endif + if (msg[0] != ' ') + { + UNITY_OUTPUT_CHAR(' '); + } + UnityPrint(msg); + } + + UNITY_FAIL_AND_BAIL; +} + +/*-----------------------------------------------*/ +void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line) +{ + RETURN_IF_FAIL_OR_IGNORE; + + UnityTestResultsBegin(Unity.TestFile, line); + UnityPrint(UnityStrIgnore); + if (msg != NULL) + { + UNITY_OUTPUT_CHAR(':'); + UNITY_OUTPUT_CHAR(' '); + UnityPrint(msg); + } + UNITY_IGNORE_AND_BAIL; +} + +/*-----------------------------------------------*/ +void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum) +{ + Unity.CurrentTestName = FuncName; + Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum; + Unity.NumberOfTests++; + UNITY_CLR_DETAILS(); + if (TEST_PROTECT()) + { + setUp(); + Func(); + } + if (TEST_PROTECT()) + { + tearDown(); + } + UnityConcludeTest(); +} + +/*-----------------------------------------------*/ +void UnityBegin(const char* filename) +{ + Unity.TestFile = filename; + Unity.CurrentTestName = NULL; + Unity.CurrentTestLineNumber = 0; + Unity.NumberOfTests = 0; + Unity.TestFailures = 0; + Unity.TestIgnores = 0; + Unity.CurrentTestFailed = 0; + Unity.CurrentTestIgnored = 0; + + UNITY_CLR_DETAILS(); + UNITY_OUTPUT_START(); +} + +/*-----------------------------------------------*/ +int UnityEnd(void) +{ + UNITY_PRINT_EOL(); + UnityPrint(UnityStrBreaker); + UNITY_PRINT_EOL(); + UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests)); + UnityPrint(UnityStrResultsTests); + UnityPrintNumber((UNITY_INT)(Unity.TestFailures)); + UnityPrint(UnityStrResultsFailures); + UnityPrintNumber((UNITY_INT)(Unity.TestIgnores)); + UnityPrint(UnityStrResultsIgnored); + UNITY_PRINT_EOL(); + if (Unity.TestFailures == 0U) + { + UnityPrint(UnityStrOk); + } + else + { + UnityPrint(UnityStrFail); +#ifdef UNITY_DIFFERENTIATE_FINAL_FAIL + UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D'); +#endif + } + UNITY_PRINT_EOL(); + UNITY_FLUSH_CALL(); + UNITY_OUTPUT_COMPLETE(); + return (int)(Unity.TestFailures); +} + +/*----------------------------------------------- + * Command Line Argument Support + *-----------------------------------------------*/ +#ifdef UNITY_USE_COMMAND_LINE_ARGS + +char* UnityOptionIncludeNamed = NULL; +char* UnityOptionExcludeNamed = NULL; +int UnityVerbosity = 1; + +int UnityParseOptions(int argc, char** argv) +{ + UnityOptionIncludeNamed = NULL; + UnityOptionExcludeNamed = NULL; + + for (int i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + switch (argv[i][1]) + { + case 'l': /* list tests */ + return -1; + case 'n': /* include tests with name including this string */ + case 'f': /* an alias for -n */ + if (argv[i][2] == '=') + UnityOptionIncludeNamed = &argv[i][3]; + else if (++i < argc) + UnityOptionIncludeNamed = argv[i]; + else + { + UnityPrint("ERROR: No Test String to Include Matches For"); + UNITY_PRINT_EOL(); + return 1; + } + break; + case 'q': /* quiet */ + UnityVerbosity = 0; + break; + case 'v': /* verbose */ + UnityVerbosity = 2; + break; + case 'x': /* exclude tests with name including this string */ + if (argv[i][2] == '=') + UnityOptionExcludeNamed = &argv[i][3]; + else if (++i < argc) + UnityOptionExcludeNamed = argv[i]; + else + { + UnityPrint("ERROR: No Test String to Exclude Matches For"); + UNITY_PRINT_EOL(); + return 1; + } + break; + default: + UnityPrint("ERROR: Unknown Option "); + UNITY_OUTPUT_CHAR(argv[i][1]); + UNITY_PRINT_EOL(); + return 1; + } + } + } + + return 0; +} + +int IsStringInBiggerString(const char* longstring, const char* shortstring) +{ + const char* lptr = longstring; + const char* sptr = shortstring; + const char* lnext = lptr; + + if (*sptr == '*') + return 1; + + while (*lptr) + { + lnext = lptr + 1; + + /* If they current bytes match, go on to the next bytes */ + while (*lptr && *sptr && (*lptr == *sptr)) + { + lptr++; + sptr++; + + /* We're done if we match the entire string or up to a wildcard */ + if (*sptr == '*') + return 1; + if (*sptr == ',') + return 1; + if (*sptr == '"') + return 1; + if (*sptr == '\'') + return 1; + if (*sptr == ':') + return 2; + if (*sptr == 0) + return 1; + } + + /* Otherwise we start in the long pointer 1 character further and try again */ + lptr = lnext; + sptr = shortstring; + } + return 0; +} + +int UnityStringArgumentMatches(const char* str) +{ + int retval; + const char* ptr1; + const char* ptr2; + const char* ptrf; + + /* Go through the options and get the substrings for matching one at a time */ + ptr1 = str; + while (ptr1[0] != 0) + { + if ((ptr1[0] == '"') || (ptr1[0] == '\'')) + ptr1++; + + /* look for the start of the next partial */ + ptr2 = ptr1; + ptrf = 0; + do + { + ptr2++; + if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')) + ptrf = &ptr2[1]; + } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')); + while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ','))) + ptr2++; + + /* done if complete filename match */ + retval = IsStringInBiggerString(Unity.TestFile, ptr1); + if (retval == 1) + return retval; + + /* done if testname match after filename partial match */ + if ((retval == 2) && (ptrf != 0)) + { + if (IsStringInBiggerString(Unity.CurrentTestName, ptrf)) + return 1; + } + + /* done if complete testname match */ + if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1) + return 1; + + ptr1 = ptr2; + } + + /* we couldn't find a match for any substrings */ + return 0; +} + +int UnityTestMatches(void) +{ + /* Check if this test name matches the included test pattern */ + int retval; + if (UnityOptionIncludeNamed) + { + retval = UnityStringArgumentMatches(UnityOptionIncludeNamed); + } + else + retval = 1; + + /* Check if this test name matches the excluded test pattern */ + if (UnityOptionExcludeNamed) + { + if (UnityStringArgumentMatches(UnityOptionExcludeNamed)) + retval = 0; + } + return retval; +} + +#endif /* UNITY_USE_COMMAND_LINE_ARGS */ +/*-----------------------------------------------*/ diff --git a/tests/unity/unity.h b/tests/unity/unity.h new file mode 100644 index 0000000000..a0c301d25a --- /dev/null +++ b/tests/unity/unity.h @@ -0,0 +1,503 @@ +/* ========================================== + Unity Project - A Test Framework for C + Copyright (c) 2007-14 Mike Karlesky, Mark VanderVoord, Greg Williams + [Released under MIT License. Please refer to license.txt for details] +========================================== */ + +#ifndef UNITY_FRAMEWORK_H +#define UNITY_FRAMEWORK_H +#define UNITY + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "unity_internals.h" + +/*------------------------------------------------------- + * Test Setup / Teardown + *-------------------------------------------------------*/ + +/* These functions are intended to be called before and after each test. */ +void setUp(void); +void tearDown(void); + +/* These functions are intended to be called at the beginning and end of an + * entire test suite. suiteTearDown() is passed the number of tests that + * failed, and its return value becomes the exit code of main(). */ +void suiteSetUp(void); +int suiteTearDown(int num_failures); + +/* If the compiler supports it, the following block provides stub + * implementations of the above functions as weak symbols. Note that on + * some platforms (MinGW for example), weak function implementations need + * to be in the same translation unit they are called from. This can be + * achieved by defining UNITY_INCLUDE_SETUP_STUBS before including unity.h. */ +#ifdef UNITY_INCLUDE_SETUP_STUBS + #ifdef UNITY_WEAK_ATTRIBUTE + UNITY_WEAK_ATTRIBUTE void setUp(void) { } + UNITY_WEAK_ATTRIBUTE void tearDown(void) { } + UNITY_WEAK_ATTRIBUTE void suiteSetUp(void) { } + UNITY_WEAK_ATTRIBUTE int suiteTearDown(int num_failures) { return num_failures; } + #elif defined(UNITY_WEAK_PRAGMA) + #pragma weak setUp + void setUp(void) { } + #pragma weak tearDown + void tearDown(void) { } + #pragma weak suiteSetUp + void suiteSetUp(void) { } + #pragma weak suiteTearDown + int suiteTearDown(int num_failures) { return num_failures; } + #endif +#endif + +/*------------------------------------------------------- + * Configuration Options + *------------------------------------------------------- + * All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above. + + * Integers/longs/pointers + * - Unity attempts to automatically discover your integer sizes + * - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in + * - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in + * - If you cannot use the automatic methods above, you can force Unity by using these options: + * - define UNITY_SUPPORT_64 + * - set UNITY_INT_WIDTH + * - set UNITY_LONG_WIDTH + * - set UNITY_POINTER_WIDTH + + * Floats + * - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons + * - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT + * - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats + * - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons + * - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default) + * - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE + * - define UNITY_DOUBLE_TYPE to specify something other than double + * - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors + + * Output + * - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired + * - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure + + * Optimization + * - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge + * - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests. + + * Test Cases + * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script + + * Parameterized Tests + * - you'll want to create a define of TEST_CASE(...) which basically evaluates to nothing + + * Tests with Arguments + * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity + + *------------------------------------------------------- + * Basic Fail and Ignore + *-------------------------------------------------------*/ + +#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message)) +#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL) +#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message)) +#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL) +#define TEST_ONLY() + +/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails. + * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */ +#define TEST_PASS() TEST_ABORT() + +/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out + * which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */ +#define TEST_FILE(a) + +/*------------------------------------------------------- + * Test Asserts (simple) + *-------------------------------------------------------*/ + +/* Boolean */ +#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE") +#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE") +#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE") +#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE") +#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL") +#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL") + +/* Integers (of all sizes) */ +#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") +#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, NULL) +#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, NULL) +#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, NULL) +#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, NULL) + +/* Integer Greater Than/ Less Than (of all sizes) */ +#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) + +#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) + +/* Integer Ranges (of all sizes) */ +#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL) + +/* Structs and Strings */ +#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL) + +/* Arrays */ +#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL) + +/* Arrays Compared To Single Value */ +#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL) + +/* Floating Point (If Enabled) */ +#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL) + +/* Double (If Enabled) */ +#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) +#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL) +#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL) + +/*------------------------------------------------------- + * Test Asserts (with additional messages) + *-------------------------------------------------------*/ + +/* Boolean */ +#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) +#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) +#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) +#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) +#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message)) +#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message)) + +/* Integers (of all sizes) */ +#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) +#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message)) +#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) +#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message)) + +/* Integer Greater Than/ Less Than (of all sizes) */ +#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) + +#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) +#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) + +/* Integer Ranges (of all sizes) */ +#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message)) + +/* Structs and Strings */ +#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message)) + +/* Arrays */ +#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message)) + +/* Arrays Compared To Single Value*/ +#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message)) + +/* Floating Point (If Enabled) */ +#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message)) + +/* Double (If Enabled) */ +#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) +#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message)) +#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message)) + +/* end of UNITY_FRAMEWORK_H */ +#ifdef __cplusplus +} +#endif +#endif diff --git a/tests/unity/unity_internals.h b/tests/unity/unity_internals.h new file mode 100644 index 0000000000..415a29b0cc --- /dev/null +++ b/tests/unity/unity_internals.h @@ -0,0 +1,872 @@ +/* ========================================== + Unity Project - A Test Framework for C + Copyright (c) 2007-14 Mike Karlesky, Mark VanderVoord, Greg Williams + [Released under MIT License. Please refer to license.txt for details] +========================================== */ + +#ifndef UNITY_INTERNALS_H +#define UNITY_INTERNALS_H + +#ifdef UNITY_INCLUDE_CONFIG_H +#include "unity_config.h" +#endif + +#ifndef UNITY_EXCLUDE_SETJMP_H +#include +#endif + +#ifndef UNITY_EXCLUDE_MATH_H +#include +#endif + +/* Unity Attempts to Auto-Detect Integer Types + * Attempt 1: UINT_MAX, ULONG_MAX in , or default to 32 bits + * Attempt 2: UINTPTR_MAX in , or default to same size as long + * The user may override any of these derived constants: + * UNITY_INT_WIDTH, UNITY_LONG_WIDTH, UNITY_POINTER_WIDTH */ +#ifndef UNITY_EXCLUDE_STDINT_H +#include +#endif + +#ifndef UNITY_EXCLUDE_LIMITS_H +#include +#endif + +/*------------------------------------------------------- + * Guess Widths If Not Specified + *-------------------------------------------------------*/ + +/* Determine the size of an int, if not already specified. + * We cannot use sizeof(int), because it is not yet defined + * at this stage in the translation of the C program. + * Therefore, infer it from UINT_MAX if possible. */ +#ifndef UNITY_INT_WIDTH + #ifdef UINT_MAX + #if (UINT_MAX == 0xFFFF) + #define UNITY_INT_WIDTH (16) + #elif (UINT_MAX == 0xFFFFFFFF) + #define UNITY_INT_WIDTH (32) + #elif (UINT_MAX == 0xFFFFFFFFFFFFFFFF) + #define UNITY_INT_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_INT_WIDTH (32) + #endif /* UINT_MAX */ +#endif + +/* Determine the size of a long, if not already specified. */ +#ifndef UNITY_LONG_WIDTH + #ifdef ULONG_MAX + #if (ULONG_MAX == 0xFFFF) + #define UNITY_LONG_WIDTH (16) + #elif (ULONG_MAX == 0xFFFFFFFF) + #define UNITY_LONG_WIDTH (32) + #elif (ULONG_MAX == 0xFFFFFFFFFFFFFFFF) + #define UNITY_LONG_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_LONG_WIDTH (32) + #endif /* ULONG_MAX */ +#endif + +/* Determine the size of a pointer, if not already specified. */ +#ifndef UNITY_POINTER_WIDTH + #ifdef UINTPTR_MAX + #if (UINTPTR_MAX <= 0xFFFF) + #define UNITY_POINTER_WIDTH (16) + #elif (UINTPTR_MAX <= 0xFFFFFFFF) + #define UNITY_POINTER_WIDTH (32) + #elif (UINTPTR_MAX <= 0xFFFFFFFFFFFFFFFF) + #define UNITY_POINTER_WIDTH (64) + #endif + #else /* Set to default */ + #define UNITY_POINTER_WIDTH UNITY_LONG_WIDTH + #endif /* UINTPTR_MAX */ +#endif + +/*------------------------------------------------------- + * Int Support (Define types based on detected sizes) + *-------------------------------------------------------*/ + +#if (UNITY_INT_WIDTH == 32) + typedef unsigned char UNITY_UINT8; + typedef unsigned short UNITY_UINT16; + typedef unsigned int UNITY_UINT32; + typedef signed char UNITY_INT8; + typedef signed short UNITY_INT16; + typedef signed int UNITY_INT32; +#elif (UNITY_INT_WIDTH == 16) + typedef unsigned char UNITY_UINT8; + typedef unsigned int UNITY_UINT16; + typedef unsigned long UNITY_UINT32; + typedef signed char UNITY_INT8; + typedef signed int UNITY_INT16; + typedef signed long UNITY_INT32; +#else + #error Invalid UNITY_INT_WIDTH specified! (16 or 32 are supported) +#endif + +/*------------------------------------------------------- + * 64-bit Support + *-------------------------------------------------------*/ + +#ifndef UNITY_SUPPORT_64 + #if UNITY_LONG_WIDTH == 64 || UNITY_POINTER_WIDTH == 64 + #define UNITY_SUPPORT_64 + #endif +#endif + +#ifndef UNITY_SUPPORT_64 + /* No 64-bit Support */ + typedef UNITY_UINT32 UNITY_UINT; + typedef UNITY_INT32 UNITY_INT; +#else + + /* 64-bit Support */ + #if (UNITY_LONG_WIDTH == 32) + typedef unsigned long long UNITY_UINT64; + typedef signed long long UNITY_INT64; + #elif (UNITY_LONG_WIDTH == 64) + typedef unsigned long UNITY_UINT64; + typedef signed long UNITY_INT64; + #else + #error Invalid UNITY_LONG_WIDTH specified! (32 or 64 are supported) + #endif + typedef UNITY_UINT64 UNITY_UINT; + typedef UNITY_INT64 UNITY_INT; + +#endif + +/*------------------------------------------------------- + * Pointer Support + *-------------------------------------------------------*/ + +#if (UNITY_POINTER_WIDTH == 32) +#define UNITY_PTR_TO_INT UNITY_INT32 +#define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX32 +#elif (UNITY_POINTER_WIDTH == 64) +#define UNITY_PTR_TO_INT UNITY_INT64 +#define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX64 +#elif (UNITY_POINTER_WIDTH == 16) +#define UNITY_PTR_TO_INT UNITY_INT16 +#define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX16 +#else + #error Invalid UNITY_POINTER_WIDTH specified! (16, 32 or 64 are supported) +#endif + +#ifndef UNITY_PTR_ATTRIBUTE +#define UNITY_PTR_ATTRIBUTE +#endif + +#ifndef UNITY_INTERNAL_PTR +#define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void* +#endif + +/*------------------------------------------------------- + * Float Support + *-------------------------------------------------------*/ + +#ifdef UNITY_EXCLUDE_FLOAT + +/* No Floating Point Support */ +#ifndef UNITY_EXCLUDE_DOUBLE +#define UNITY_EXCLUDE_DOUBLE /* Remove double when excluding float support */ +#endif +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +#define UNITY_EXCLUDE_FLOAT_PRINT +#endif + +#else + +/* Floating Point Support */ +#ifndef UNITY_FLOAT_PRECISION +#define UNITY_FLOAT_PRECISION (0.00001f) +#endif +#ifndef UNITY_FLOAT_TYPE +#define UNITY_FLOAT_TYPE float +#endif +typedef UNITY_FLOAT_TYPE UNITY_FLOAT; + +/* isinf & isnan macros should be provided by math.h */ +#ifndef isinf +/* The value of Inf - Inf is NaN */ +#define isinf(n) (isnan((n) - (n)) && !isnan(n)) +#endif + +#ifndef isnan +/* NaN is the only floating point value that does NOT equal itself. + * Therefore if n != n, then it is NaN. */ +#define isnan(n) ((n != n) ? 1 : 0) +#endif + +#endif + +/*------------------------------------------------------- + * Double Float Support + *-------------------------------------------------------*/ + +/* unlike float, we DON'T include by default */ +#if defined(UNITY_EXCLUDE_DOUBLE) || !defined(UNITY_INCLUDE_DOUBLE) + + /* No Floating Point Support */ + #ifndef UNITY_EXCLUDE_DOUBLE + #define UNITY_EXCLUDE_DOUBLE + #else + #undef UNITY_INCLUDE_DOUBLE + #endif + + #ifndef UNITY_EXCLUDE_FLOAT + #ifndef UNITY_DOUBLE_TYPE + #define UNITY_DOUBLE_TYPE double + #endif + typedef UNITY_FLOAT UNITY_DOUBLE; + /* For parameter in UnityPrintFloat(UNITY_DOUBLE), which aliases to double or float */ + #endif + +#else + + /* Double Floating Point Support */ + #ifndef UNITY_DOUBLE_PRECISION + #define UNITY_DOUBLE_PRECISION (1e-12) + #endif + + #ifndef UNITY_DOUBLE_TYPE + #define UNITY_DOUBLE_TYPE double + #endif + typedef UNITY_DOUBLE_TYPE UNITY_DOUBLE; + +#endif + +/*------------------------------------------------------- + * Output Method: stdout (DEFAULT) + *-------------------------------------------------------*/ +#ifndef UNITY_OUTPUT_CHAR + /* Default to using putchar, which is defined in stdio.h */ + #include + #define UNITY_OUTPUT_CHAR(a) (void)putchar(a) +#else + /* If defined as something else, make sure we declare it here so it's ready for use */ + #ifdef UNITY_OUTPUT_CHAR_HEADER_DECLARATION + extern void UNITY_OUTPUT_CHAR_HEADER_DECLARATION; + #endif +#endif + +#ifndef UNITY_OUTPUT_FLUSH + #ifdef UNITY_USE_FLUSH_STDOUT + /* We want to use the stdout flush utility */ + #include + #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) + #else + /* We've specified nothing, therefore flush should just be ignored */ + #define UNITY_OUTPUT_FLUSH() + #endif +#else + /* If defined as something else, make sure we declare it here so it's ready for use */ + #ifdef UNITY_OUTPUT_FLUSH_HEADER_DECLARATION + extern void UNITY_OUTPUT_FLUSH_HEADER_DECLARATION; + #endif +#endif + +#ifndef UNITY_OUTPUT_FLUSH +#define UNITY_FLUSH_CALL() +#else +#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() +#endif + +#ifndef UNITY_PRINT_EOL +#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') +#endif + +#ifndef UNITY_OUTPUT_START +#define UNITY_OUTPUT_START() +#endif + +#ifndef UNITY_OUTPUT_COMPLETE +#define UNITY_OUTPUT_COMPLETE() +#endif + +/*------------------------------------------------------- + * Footprint + *-------------------------------------------------------*/ + +#ifndef UNITY_LINE_TYPE +#define UNITY_LINE_TYPE UNITY_UINT +#endif + +#ifndef UNITY_COUNTER_TYPE +#define UNITY_COUNTER_TYPE UNITY_UINT +#endif + +/*------------------------------------------------------- + * Language Features Available + *-------------------------------------------------------*/ +#if !defined(UNITY_WEAK_ATTRIBUTE) && !defined(UNITY_WEAK_PRAGMA) +# if defined(__GNUC__) || defined(__ghs__) /* __GNUC__ includes clang */ +# if !(defined(__WIN32__) && defined(__clang__)) && !defined(__TMS470__) +# define UNITY_WEAK_ATTRIBUTE __attribute__((weak)) +# endif +# endif +#endif + +#ifdef UNITY_NO_WEAK +# undef UNITY_WEAK_ATTRIBUTE +# undef UNITY_WEAK_PRAGMA +#endif + + +/*------------------------------------------------------- + * Internal Structs Needed + *-------------------------------------------------------*/ + +typedef void (*UnityTestFunction)(void); + +#define UNITY_DISPLAY_RANGE_INT (0x10) +#define UNITY_DISPLAY_RANGE_UINT (0x20) +#define UNITY_DISPLAY_RANGE_HEX (0x40) + +typedef enum +{ +UNITY_DISPLAY_STYLE_INT = sizeof(int)+ UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT8 = 1 + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT16 = 2 + UNITY_DISPLAY_RANGE_INT, + UNITY_DISPLAY_STYLE_INT32 = 4 + UNITY_DISPLAY_RANGE_INT, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_INT64 = 8 + UNITY_DISPLAY_RANGE_INT, +#endif + +UNITY_DISPLAY_STYLE_UINT = sizeof(unsigned) + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT8 = 1 + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT16 = 2 + UNITY_DISPLAY_RANGE_UINT, + UNITY_DISPLAY_STYLE_UINT32 = 4 + UNITY_DISPLAY_RANGE_UINT, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_UINT64 = 8 + UNITY_DISPLAY_RANGE_UINT, +#endif + + UNITY_DISPLAY_STYLE_HEX8 = 1 + UNITY_DISPLAY_RANGE_HEX, + UNITY_DISPLAY_STYLE_HEX16 = 2 + UNITY_DISPLAY_RANGE_HEX, + UNITY_DISPLAY_STYLE_HEX32 = 4 + UNITY_DISPLAY_RANGE_HEX, +#ifdef UNITY_SUPPORT_64 + UNITY_DISPLAY_STYLE_HEX64 = 8 + UNITY_DISPLAY_RANGE_HEX, +#endif + + UNITY_DISPLAY_STYLE_UNKNOWN +} UNITY_DISPLAY_STYLE_T; + +typedef enum +{ + UNITY_EQUAL_TO = 1, + UNITY_GREATER_THAN = 2, + UNITY_GREATER_OR_EQUAL = 2 + UNITY_EQUAL_TO, + UNITY_SMALLER_THAN = 4, + UNITY_SMALLER_OR_EQUAL = 4 + UNITY_EQUAL_TO +} UNITY_COMPARISON_T; + +#ifndef UNITY_EXCLUDE_FLOAT +typedef enum UNITY_FLOAT_TRAIT +{ + UNITY_FLOAT_IS_NOT_INF = 0, + UNITY_FLOAT_IS_INF, + UNITY_FLOAT_IS_NOT_NEG_INF, + UNITY_FLOAT_IS_NEG_INF, + UNITY_FLOAT_IS_NOT_NAN, + UNITY_FLOAT_IS_NAN, + UNITY_FLOAT_IS_NOT_DET, + UNITY_FLOAT_IS_DET, + UNITY_FLOAT_INVALID_TRAIT +} UNITY_FLOAT_TRAIT_T; +#endif + +typedef enum +{ + UNITY_ARRAY_TO_VAL = 0, + UNITY_ARRAY_TO_ARRAY +} UNITY_FLAGS_T; + +struct UNITY_STORAGE_T +{ + const char* TestFile; + const char* CurrentTestName; +#ifndef UNITY_EXCLUDE_DETAILS + const char* CurrentDetail1; + const char* CurrentDetail2; +#endif + UNITY_LINE_TYPE CurrentTestLineNumber; + UNITY_COUNTER_TYPE NumberOfTests; + UNITY_COUNTER_TYPE TestFailures; + UNITY_COUNTER_TYPE TestIgnores; + UNITY_COUNTER_TYPE CurrentTestFailed; + UNITY_COUNTER_TYPE CurrentTestIgnored; +#ifndef UNITY_EXCLUDE_SETJMP_H + jmp_buf AbortFrame; +#endif +}; + +extern struct UNITY_STORAGE_T Unity; + +/*------------------------------------------------------- + * Test Suite Management + *-------------------------------------------------------*/ + +void UnityBegin(const char* filename); +int UnityEnd(void); +void UnityConcludeTest(void); +void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum); + +/*------------------------------------------------------- + * Details Support + *-------------------------------------------------------*/ + +#ifdef UNITY_EXCLUDE_DETAILS +#define UNITY_CLR_DETAILS() +#define UNITY_SET_DETAIL(d1) +#define UNITY_SET_DETAILS(d1,d2) +#else +#define UNITY_CLR_DETAILS() { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } +#define UNITY_SET_DETAIL(d1) { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } +#define UNITY_SET_DETAILS(d1,d2) { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } + +#ifndef UNITY_DETAIL1_NAME +#define UNITY_DETAIL1_NAME "Function" +#endif + +#ifndef UNITY_DETAIL2_NAME +#define UNITY_DETAIL2_NAME "Argument" +#endif +#endif + +/*------------------------------------------------------- + * Test Output + *-------------------------------------------------------*/ + +void UnityPrint(const char* string); +void UnityPrintLen(const char* string, const UNITY_UINT32 length); +void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number); +void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style); +void UnityPrintNumber(const UNITY_INT number_to_print); +void UnityPrintNumberUnsigned(const UNITY_UINT number); +void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print); + +#ifndef UNITY_EXCLUDE_FLOAT_PRINT +void UnityPrintFloat(const UNITY_DOUBLE input_number); +#endif + +/*------------------------------------------------------- + * Test Assertion Functions + *------------------------------------------------------- + * Use the macros below this section instead of calling + * these directly. The macros have a consistent naming + * convention and will pull in file and line information + * for you. */ + +void UnityAssertEqualNumber(const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, + const UNITY_INT actual, + const UNITY_COMPARISON_T compare, + const char *msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style, + const UNITY_FLAGS_T flags); + +void UnityAssertBits(const UNITY_INT mask, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualString(const char* expected, + const char* actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualStringLen(const char* expected, + const char* actual, + const UNITY_UINT32 length, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualStringArray( UNITY_INTERNAL_PTR expected, + const char** actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertEqualMemory( UNITY_INTERNAL_PTR expected, + UNITY_INTERNAL_PTR actual, + const UNITY_UINT32 length, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertNumbersWithin(const UNITY_UINT delta, + const UNITY_INT expected, + const UNITY_INT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_DISPLAY_STYLE_T style); + +void UnityFail(const char* msg, const UNITY_LINE_TYPE line); + +void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line); + +#ifndef UNITY_EXCLUDE_FLOAT +void UnityAssertFloatsWithin(const UNITY_FLOAT delta, + const UNITY_FLOAT expected, + const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, + UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertFloatSpecial(const UNITY_FLOAT actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style); +#endif + +#ifndef UNITY_EXCLUDE_DOUBLE +void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, + const UNITY_DOUBLE expected, + const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber); + +void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, + UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, + const UNITY_UINT32 num_elements, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLAGS_T flags); + +void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, + const char* msg, + const UNITY_LINE_TYPE lineNumber, + const UNITY_FLOAT_TRAIT_T style); +#endif + +/*------------------------------------------------------- + * Helpers + *-------------------------------------------------------*/ + +UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size); +#ifndef UNITY_EXCLUDE_FLOAT +UNITY_INTERNAL_PTR UnityFloatToPtr(const float num); +#endif +#ifndef UNITY_EXCLUDE_DOUBLE +UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num); +#endif + +/*------------------------------------------------------- + * Error Strings We Might Need + *-------------------------------------------------------*/ + +extern const char UnityStrErrFloat[]; +extern const char UnityStrErrDouble[]; +extern const char UnityStrErr64[]; + +/*------------------------------------------------------- + * Test Running Macros + *-------------------------------------------------------*/ + +#ifndef UNITY_EXCLUDE_SETJMP_H +#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0) +#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) +#else +#define TEST_PROTECT() 1 +#define TEST_ABORT() return +#endif + +/* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */ +#ifndef RUN_TEST +#ifdef __STDC_VERSION__ +#if __STDC_VERSION__ >= 199901L +#define RUN_TEST(...) UnityDefaultTestRun(RUN_TEST_FIRST(__VA_ARGS__), RUN_TEST_SECOND(__VA_ARGS__)) +#define RUN_TEST_FIRST(...) RUN_TEST_FIRST_HELPER(__VA_ARGS__, throwaway) +#define RUN_TEST_FIRST_HELPER(first, ...) (first), #first +#define RUN_TEST_SECOND(...) RUN_TEST_SECOND_HELPER(__VA_ARGS__, __LINE__, throwaway) +#define RUN_TEST_SECOND_HELPER(first, second, ...) (second) +#endif +#endif +#endif + +/* If we can't do the tricky version, we'll just have to require them to always include the line number */ +#ifndef RUN_TEST +#ifdef CMOCK +#define RUN_TEST(func, num) UnityDefaultTestRun(func, #func, num) +#else +#define RUN_TEST(func) UnityDefaultTestRun(func, #func, __LINE__) +#endif +#endif + +#define TEST_LINE_NUM (Unity.CurrentTestLineNumber) +#define TEST_IS_IGNORED (Unity.CurrentTestIgnored) +#define UNITY_NEW_TEST(a) \ + Unity.CurrentTestName = (a); \ + Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)(__LINE__); \ + Unity.NumberOfTests++; + +#ifndef UNITY_BEGIN +#define UNITY_BEGIN() UnityBegin(__FILE__) +#endif + +#ifndef UNITY_END +#define UNITY_END() UnityEnd() +#endif + +/*----------------------------------------------- + * Command Line Argument Support + *-----------------------------------------------*/ + +#ifdef UNITY_USE_COMMAND_LINE_ARGS +int UnityParseOptions(int argc, char** argv); +int UnityTestMatches(void); +#endif + +/*------------------------------------------------------- + * Basic Fail and Ignore + *-------------------------------------------------------*/ + +#define UNITY_TEST_FAIL(line, message) UnityFail( (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_IGNORE(line, message) UnityIgnore( (message), (UNITY_LINE_TYPE)(line)) + +/*------------------------------------------------------- + * Test Asserts + *-------------------------------------------------------*/ + +#define UNITY_TEST_ASSERT(condition, line, message) if (condition) {} else {UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), (message));} +#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (UNITY_LINE_TYPE)(line), (message)) + +#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_EQUAL_INT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_EQUAL_INT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_EQUAL_UINT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_EQUAL_UINT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_EQUAL_HEX8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_EQUAL_HEX16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_EQUAL_HEX32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) +#define UNITY_TEST_ASSERT_BITS(mask, expected, actual, line, message) UnityAssertBits((UNITY_INT)(mask), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line)) + +#define UNITY_TEST_ASSERT_GREATER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) + +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) + +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) + +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) + +#define UNITY_TEST_ASSERT_INT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) +#define UNITY_TEST_ASSERT_INT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) +#define UNITY_TEST_ASSERT_INT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) +#define UNITY_TEST_ASSERT_INT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) +#define UNITY_TEST_ASSERT_UINT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) +#define UNITY_TEST_ASSERT_UINT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) +#define UNITY_TEST_ASSERT_UINT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) +#define UNITY_TEST_ASSERT_UINT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) +#define UNITY_TEST_ASSERT_HEX8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) +#define UNITY_TEST_ASSERT_HEX16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) +#define UNITY_TEST_ASSERT_HEX32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) + +#define UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_PTR_TO_INT)(expected), (UNITY_PTR_TO_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER) +#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, message) UnityAssertEqualString((const char*)(expected), (const char*)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, message) UnityAssertEqualStringLen((const char*)(expected), (const char*)(actual), (UNITY_UINT32)(len), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_MEMORY(expected, actual, len, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), 1, (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) + +#define UNITY_TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) + +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), sizeof(int)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), sizeof(unsigned int)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT16)(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT32)(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_PTR_TO_INT) (expected), sizeof(int*)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) + +#ifdef UNITY_SUPPORT_64 +#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)expected, 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT64)expected, 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)expected, 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) +#else +#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) +#endif + +#ifdef UNITY_EXCLUDE_FLOAT +#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) +#else +#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) +#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray((UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray(UnityFloatToPtr(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) +#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) +#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) +#endif + +#ifdef UNITY_EXCLUDE_DOUBLE +#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) +#else +#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)line) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual, (UNITY_LINE_TYPE)(line), message) +#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray((UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)line, UNITY_ARRAY_TO_ARRAY) +#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray(UnityDoubleToPtr(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)line, UNITY_ARRAY_TO_VAL) +#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) +#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) +#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) +#endif + +/* End of UNITY_INTERNALS_H */ +#endif From 2e909c9ed4b290e447498b66d5c486fffd198e05 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 13 Dec 2018 10:13:51 -0800 Subject: [PATCH 002/844] Add CI and coverage badges to README. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4a9b953756..b53eb4b1f9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ **[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/index.html)** +[![Build Status](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C.svg?branch=v4_beta)](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) +[![Coverage Status](https://coveralls.io/repos/github/aws/aws-iot-device-sdk-embedded-C/badge.svg?branch=v4_beta)](https://coveralls.io/github/aws/aws-iot-device-sdk-embedded-C?branch=v4_beta) + The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be build into firmware along with application code. This library, currently a Beta release, will supersede both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. From 111ba5ab8b1e8b6cbcc6371e2bfbcd8b9117048a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 13 Dec 2018 17:10:08 -0800 Subject: [PATCH 003/844] Make MQTT client identifier configurable in tests (#249) --- demos/aws_iot_demo_mqtt.c | 22 +++++---- doc/lib/mqtt.txt | 20 ++++++++- scripts/build_check_commit.sh | 4 +- scripts/build_check_pr.sh | 3 -- scripts/coverage.sh | 2 +- tests/mqtt/system/aws_iot_tests_mqtt_stress.c | 23 +++++++--- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 45 ++++++++++++++----- .../system/aws_iot_tests_shadow_system.c | 34 ++------------ 8 files changed, 89 insertions(+), 64 deletions(-) diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c index 82ce61e810..86b4aba873 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/aws_iot_demo_mqtt.c @@ -62,6 +62,9 @@ extern int snprintf( char *, * * Provide default values for undefined configuration settings. */ +#ifndef AWS_IOT_DEMO_MQTT_TOPIC_PREFIX + #define AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "awsiotdemo" +#endif #ifndef AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) #endif @@ -89,9 +92,10 @@ extern int snprintf( char *, /** * @brief The longest client identifier that an MQTT server must accept (as defined - * by the MQTT 3.1.1 spec) is 23 characters. + * by the MQTT 3.1.1 spec) is 23 characters. Add 1 to include the length of the NULL + * terminator. */ -#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) +#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) /** * @brief The keep-alive interval used for this demo. @@ -111,7 +115,7 @@ extern int snprintf( char *, * The MQTT server will publish a message to this topic name if this client is * unexpectedly disconnected. */ -#define _WILL_TOPIC_NAME _CLIENT_IDENTIFIER_PREFIX "/will" +#define _WILL_TOPIC_NAME AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/will" /** * @brief The length of #_WILL_TOPIC_NAME. @@ -138,7 +142,7 @@ extern int snprintf( char *, * * For convenience, all topic filters are the same length. */ -#define _TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( _CLIENT_IDENTIFIER_PREFIX "/topic/1" ) - 1 ) ) +#define _TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1" ) - 1 ) ) /** * @brief Format string of the PUBLISH messages in this demo. @@ -165,7 +169,7 @@ extern int snprintf( char *, * @brief The topic name on which acknowledgement messages for incoming publishes * should be published. */ -#define _ACKNOWLEDGEMENT_TOPIC_NAME _CLIENT_IDENTIFIER_PREFIX "/acknowledgements" +#define _ACKNOWLEDGEMENT_TOPIC_NAME AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/acknowledgements" /** * @brief The length of #_ACKNOWLEDGEMENT_TOPIC_NAME. @@ -366,10 +370,10 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, AwsIotSemaphore_t publishesReceived; const char * pTopicFilters[ _TOPIC_FILTER_COUNT ] = { - _CLIENT_IDENTIFIER_PREFIX "/topic/1", - _CLIENT_IDENTIFIER_PREFIX "/topic/2", - _CLIENT_IDENTIFIER_PREFIX "/topic/3", - _CLIENT_IDENTIFIER_PREFIX "/topic/4", + AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", + AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", + AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/3", + AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/4", }; /* Set the common members of the connection info. */ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 1b14cef64f..fafc65b009 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -107,6 +107,14 @@ The current MQTT tests use the [Unity test framework](http://www.throwtheswitch. /** @configpage{mqtt_tests,MQTT tests,Test,tests} +@section AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER +@brief The MQTT client identifier to use for the tests. + +No two clients may connect using the same client identifier simultaneously. If this setting is undefined, the tests will generate a unique client identifier to use. + +@configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
+@configdefault The tests will generate a unique client identifier if this setting is undefined. + @section AWS_IOT_TEST_MQTT_MOSQUITTO @brief Test the MQTT library against the [public Mosquitto test server](https://test.mosquitto.org/). @@ -127,9 +135,9 @@ This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait @configdefault `5000` @section AWS_IOT_TEST_MQTT_TOPIC_PREFIX -@brief The string prepended to topic filters and client identifiers. +@brief The string prepended to topic filters. -This string will be prepended as the common part of topic filters and client identifiers as a way to differentiate MQTT traffic generated by the tests. +This string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the tests. @configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
@configdefault `"awsiotmqtttest"` @@ -200,6 +208,14 @@ The MQTT client identifier may also be set with [the command line option -i](@re @configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
@configdefault The demo will generate a unique client identifier if this setting is undefined. +@section AWS_IOT_DEMO_MQTT_TOPIC_PREFIX +@brief The string prepended to topic filters in the demo. + +The string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the demo. + +@configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
+@configdefault `"awsiotmqttdemo"` + @section AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE @brief The number of messages published in each burst. diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index 169729ef62..51e09893b5 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -26,7 +26,7 @@ echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem # Build tests and demos against AWS IoT with ThreadSanitizer. -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -fsanitize=thread" +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" make # Run MQTT tests and demo against AWS IoT. @@ -39,7 +39,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_STATIC_MEMORY_ONLY=1 -fsanitize=thread" +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" make # Run MQTT and Shadow tests in static memory mode. diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh index 30d09b0693..0e2ad3ad01 100644 --- a/scripts/build_check_pr.sh +++ b/scripts/build_check_pr.sh @@ -6,9 +6,6 @@ # Exit on any nonzero return code. set -e -# Set Thing Name. -AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" - # Create build directory. mkdir -p build cd build diff --git a/scripts/coverage.sh b/scripts/coverage.sh index e30345c95f..5ad47bf40b 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -14,7 +14,7 @@ cd build rm -rf * # Build tests and demos against AWS IoT with gcov. -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO --coverage" +cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" --coverage" make # Run MQTT tests and demo against AWS IoT with code coverage. diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c index 6e7bd3b7f4..22a0644ae6 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c @@ -122,7 +122,11 @@ /** * @brief The maximum length of an MQTT client identifier. */ -#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) +#ifdef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER ) ) +#else + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) +#endif /*-----------------------------------------------------------*/ @@ -444,11 +448,18 @@ TEST_SETUP( MQTT_Stress ) TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } - /* Generate a new, unique client identifier based on the time. */ - ( void ) snprintf( _pClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, - "aws%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + /* Generate a new, unique client identifier based on the time if no client + * identifier is defined. Otherwise, copy the provided client identifier. */ + #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER + ( void ) snprintf( _pClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "aws%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + #else + ( void ) strncpy( _pClientIdentifier, + AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER, + _CLIENT_IDENTIFIER_MAX_LENGTH ); + #endif /* Set the members of the connect info. */ connectInfo.cleanSession = true; diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 6ac1af13fe..78870c570e 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -85,8 +85,18 @@ extern int snprintf( char *, /** * @brief The maximum length of an MQTT client identifier. + * + * When @ref AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER is defined, this value must + * accommodate the length of @ref AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER plus 4 + * to accommodate the Last Will and Testament test. Otherwise, this value is + * set to 24, which is the longest client identifier length an MQTT server is + * obligated to accept plus a NULL terminator. */ -#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) +#ifdef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) +#else + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) +#endif /*-----------------------------------------------------------*/ @@ -456,11 +466,18 @@ TEST_SETUP( MQTT_System ) TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } - /* Generate a new, unique client identifier based on the time. */ - ( void ) snprintf( _pClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, - "aws%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + /* Generate a new, unique client identifier based on the time if no client + * identifier is defined. Otherwise, copy the provided client identifier. */ + #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER + ( void ) snprintf( _pClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "aws%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + #else + ( void ) strncpy( _pClientIdentifier, + AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER, + _CLIENT_IDENTIFIER_MAX_LENGTH ); + #endif } /*-----------------------------------------------------------*/ @@ -655,11 +672,17 @@ TEST( MQTT_System, LastWillAndTestament ) /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); - /* Generate client identifier for LWT listener. */ - ( void ) snprintf( pLwtListenerClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, - "awslwt%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + /* Generate a client identifier for LWT listener. */ + #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER + ( void ) snprintf( pLwtListenerClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "awslwt%llu", + ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + #else + ( void ) strncpy( pLwtListenerClientIdentifier, + AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER "LWT", + _CLIENT_IDENTIFIER_MAX_LENGTH ); + #endif if( TEST_PROTECT() ) { diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 1f0b0121ae..2bd068eef7 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -72,9 +72,6 @@ extern int snprintf( char *, * * Provide default values of test configuration constants. */ -#ifndef AWS_IOT_TEST_MQTT_TOPIC_PREFIX - #define AWS_IOT_TEST_MQTT_TOPIC_PREFIX "awsiotmqtttest" -#endif #ifndef AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) #endif @@ -88,11 +85,6 @@ extern int snprintf( char *, #error "Please define AWS_IOT_TEST_SHADOW_THING_NAME." #endif -/** - * @brief The maximum length of an MQTT client identifier. - */ -#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 23 ) - /** * @brief The length of @ref AWS_IOT_TEST_SHADOW_THING_NAME. */ @@ -138,13 +130,6 @@ extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; /*-----------------------------------------------------------*/ -/** - * @brief Buffer holding the MQTT client identifier used for the tests. - */ -static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - -/*-----------------------------------------------------------*/ - /** * @brief Shadow operation completion callback function. Checks parameters * and unblocks the main test thread. @@ -437,7 +422,6 @@ TEST_GROUP( Shadow_System ); */ TEST_SETUP( Shadow_System ) { - int clientIdentifierLength = 0; AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -453,20 +437,10 @@ TEST_SETUP( Shadow_System ) TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } - /* Generate a new, unique client identifier based on the time. */ - clientIdentifierLength = snprintf( _pClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, - "aws%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); - - if( clientIdentifierLength <= 0 ) - { - TEST_FAIL_MESSAGE( "Failed to generate MQTT client identifier." ); - } - - /* Set the members of the connect info. */ - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) clientIdentifierLength; + /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT + * client identifier. */ + connectInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; + connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; /* Establish an MQTT connection. */ From 9a3645d16ed610de4b6c33f7fac642103f7a809e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:59:25 -0800 Subject: [PATCH 004/844] Fix function name in documentation. (#250) --- lib/include/platform/aws_iot_network.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/include/platform/aws_iot_network.h b/lib/include/platform/aws_iot_network.h index d40d4d4451..6508bca76e 100644 --- a/lib/include/platform/aws_iot_network.h +++ b/lib/include/platform/aws_iot_network.h @@ -285,10 +285,10 @@ void AwsIotNetwork_Cleanup( void ); * * // Returns AWS_IOT_NETWORK_SUCCESS and sets networkConnection when the * // connection is established. - * AwsIotNetworkError_t result = AwsIotNetwork_TcpConnect( &networkConnection, - * pHostName, - * port, - * &tlsInfo ); + * AwsIotNetworkError_t result = AwsIotNetwork_CreateConnection( &networkConnection, + * pHostName, + * port, + * &tlsInfo ); * * // Do something with the connection... * From aebcfe768da679c5fc76d3ea1abc43e76f4193e4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 18 Dec 2018 11:17:31 -0800 Subject: [PATCH 005/844] Combine MQTT subscribe and unsubscribe functions. (#251) --- lib/source/mqtt/aws_iot_mqtt_api.c | 534 +++++++++++++++-------------- 1 file changed, 271 insertions(+), 263 deletions(-) diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index ec7e896bf7..7015df4895 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -168,6 +168,21 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio size_t sessionSubscriptionsCount, uint64_t timeoutMs ); +/** + * @brief The common component of both @ref mqtt_function_subscribe and @ref + * mqtt_function_unsubscribe. + * + * See @ref mqtt_function_subscribe or @ref mqtt_function_unsubscribe for a + * description of the parameters and return values. + */ +static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operation, + AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pSubscriptionRef ); + /** * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. * @@ -874,6 +889,171 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio /*-----------------------------------------------------------*/ +static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operation, + AwsIotMqttConnection_t mqttConnection, + const AwsIotMqttSubscription_t * const pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const AwsIotMqttCallbackInfo_t * const pCallbackInfo, + AwsIotMqttReference_t * const pSubscriptionRef ) +{ + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pSubscriptionOperation = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Subscription serializer function. */ + AwsIotMqttError_t ( * serializeSubscription )( const AwsIotMqttSubscription_t * const, + size_t, + uint8_t ** const, + size_t * const, + uint16_t * const ) = NULL; + + /* This function should only be called for subscribe or unsubscribe. */ + AwsIotMqtt_Assert( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) || + ( operation == AWS_IOT_MQTT_UNSUBSCRIBE ) ); + + /* Choose a subscription serialize function. */ + if( operation == AWS_IOT_MQTT_SUBSCRIBE ) + { + serializeSubscription = AwsIotMqttInternal_SerializeSubscribe; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.subscribe != NULL ) + { + serializeSubscription = pMqttConnection->network.serialize.subscribe; + } + #endif + } + else + { + serializeSubscription = AwsIotMqttInternal_SerializeUnsubscribe; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.unsubscribe != NULL ) + { + serializeSubscription = pMqttConnection->network.serialize.unsubscribe; + } + #endif + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && + ( pSubscriptionRef == NULL ) ) + { + AwsIotLogError( "Reference must be provided for a waitable %s.", + AwsIotMqtt_OperationType( operation ) ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Check that all elements in the subscription list are valid. */ + if( AwsIotMqttInternal_ValidateSubscriptionList( operation, + pMqttConnection->awsIotMqttMode, + pSubscriptionList, + subscriptionCount ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ + if( operation == AWS_IOT_MQTT_UNSUBSCRIBE ) + { + AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, + pSubscriptionList, + subscriptionCount ); + } + + /* Create a subscription operation. */ + status = AwsIotMqttInternal_CreateOperation( &pSubscriptionOperation, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + return status; + } + + /* Check the subscription operation data and set the remaining members. */ + AwsIotMqtt_Assert( pSubscriptionOperation->pPublishRetry == NULL ); + AwsIotMqtt_Assert( pSubscriptionOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + pSubscriptionOperation->operation = operation; + pSubscriptionOperation->pMqttConnection = pMqttConnection; + + /* Generate a subscription packet from the subscription list. */ + status = serializeSubscription( pSubscriptionList, + subscriptionCount, + &( pSubscriptionOperation->pMqttPacket ), + &( pSubscriptionOperation->packetSize ), + &( pSubscriptionOperation->packetIdentifier ) ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pSubscriptionOperation ); + + return status; + } + + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pSubscriptionOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pSubscriptionOperation->packetSize > 0 ); + + /* Add the subscription list for a SUBSCRIBE. */ + if( operation == AWS_IOT_MQTT_SUBSCRIBE ) + { + status = AwsIotMqttInternal_AddSubscriptions( pMqttConnection, + pSubscriptionOperation->packetIdentifier, + pSubscriptionList, + subscriptionCount ); + + if( status != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pSubscriptionOperation ); + + return status; + } + } + + /* Set the reference, if provided. This must be set before the subscription + * is pushed to the network queue to avoid a race condition. */ + if( pSubscriptionRef != NULL ) + { + *pSubscriptionRef = pSubscriptionOperation; + } + + /* Add the subscription operation to the send queue. */ + if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, + &( pSubscriptionOperation->link ) ) == false ) + { + AwsIotLogError( "Failed to add %s to send queue.", + AwsIotMqtt_OperationType( operation ) ); + + if( operation == AWS_IOT_MQTT_SUBSCRIBE ) + { + AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, + pSubscriptionOperation->packetIdentifier, + -1 ); + } + + AwsIotMqttInternal_DestroyOperation( pSubscriptionOperation ); + + /* Clear the previously set (and now invalid) reference. */ + if( pSubscriptionRef != NULL ) + { + *pSubscriptionRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + } + + return AWS_IOT_MQTT_NO_MEMORY; + } + + AwsIotLogInfo( "MQTT %s operation queued.", + AwsIotMqtt_OperationType( operation ) ); + + /* The subscription operation is waiting for a network response. */ + return AWS_IOT_MQTT_STATUS_PENDING; +} + +/*-----------------------------------------------------------*/ + static void _sendPuback( _mqttConnection_t * const pMqttConnection, uint16_t packetIdentifier ) { @@ -1607,121 +1787,13 @@ AwsIotMqttError_t AwsIotMqtt_Subscribe( AwsIotMqttConnection_t mqttConnection, const AwsIotMqttCallbackInfo_t * const pCallbackInfo, AwsIotMqttReference_t * const pSubscribeRef ) { - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pSubscribeOperation = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; - - /* Default SUBSCRIBE serializer function. */ - AwsIotMqttError_t ( * serializeSubscribe )( const AwsIotMqttSubscription_t * const, - size_t, - uint8_t ** const, - size_t * const, - uint16_t * const ) = AwsIotMqttInternal_SerializeSubscribe; - - /* Choose a SUBSCRIBE serializer function. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.subscribe != NULL ) - { - serializeSubscribe = pMqttConnection->network.serialize.subscribe; - } - #endif - - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && - ( pSubscribeRef == NULL ) ) - { - AwsIotLogError( "Reference must be provided for a waitable SUBSCRIBE." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Check that all elements in the subscription list are valid. */ - if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, - pMqttConnection->awsIotMqttMode, - pSubscriptionList, - subscriptionCount ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Create a SUBSCRIBE operation. */ - status = AwsIotMqttInternal_CreateOperation( &pSubscribeOperation, - flags, - pCallbackInfo ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - return status; - } - - /* Check the SUBSCRIBE operation data and set the remaining members. */ - AwsIotMqtt_Assert( pSubscribeOperation->pPublishRetry == NULL ); - AwsIotMqtt_Assert( pSubscribeOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - pSubscribeOperation->operation = AWS_IOT_MQTT_SUBSCRIBE; - pSubscribeOperation->pMqttConnection = pMqttConnection; - - /* Generate a SUBSCRIBE packet from the subscription list. */ - status = serializeSubscribe( pSubscriptionList, - subscriptionCount, - &( pSubscribeOperation->pMqttPacket ), - &( pSubscribeOperation->packetSize ), - &( pSubscribeOperation->packetIdentifier ) ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pSubscribeOperation ); - - return status; - } - - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pSubscribeOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pSubscribeOperation->packetSize > 0 ); - - /* Add the subscription list to the MQTT connection. */ - status = AwsIotMqttInternal_AddSubscriptions( pMqttConnection, - pSubscribeOperation->packetIdentifier, - pSubscriptionList, - subscriptionCount ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pSubscribeOperation ); - - return status; - } - - /* Set the reference, if provided. This must be set before the subscribe - * is pushed to the network queue to avoid a race condition. */ - if( pSubscribeRef != NULL ) - { - *pSubscribeRef = pSubscribeOperation; - } - - /* Add the SUBSCRIBE operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pSubscribeOperation->link ) ) == false ) - { - AwsIotLogError( "Failed to add SUBSCRIBE to send queue." ); - - AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, - pSubscribeOperation->packetIdentifier, - -1 ); - AwsIotMqttInternal_DestroyOperation( pSubscribeOperation ); - - /* Clear the previously set (and now invalid) reference. */ - if( pSubscribeRef != NULL ) - { - *pSubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - } - - return AWS_IOT_MQTT_NO_MEMORY; - } - - AwsIotLogInfo( "MQTT SUBSCRIBE operation queued." ); - - /* The SUBSCRIBE operation is waiting for a network response. */ - return AWS_IOT_MQTT_STATUS_PENDING; + return _subscriptionCommon( AWS_IOT_MQTT_SUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pSubscribeRef ); } /*-----------------------------------------------------------*/ @@ -1767,110 +1839,13 @@ AwsIotMqttError_t AwsIotMqtt_Unsubscribe( AwsIotMqttConnection_t mqttConnection, const AwsIotMqttCallbackInfo_t * const pCallbackInfo, AwsIotMqttReference_t * const pUnsubscribeRef ) { - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pUnsubscribeOperation = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; - - /* Default UNSUBSCRIBE serializer function. */ - AwsIotMqttError_t ( * serializeUnsubscribe )( const AwsIotMqttSubscription_t * const, - size_t, - uint8_t ** const, - size_t * const, - uint16_t * const ) = AwsIotMqttInternal_SerializeUnsubscribe; - - /* Choose an UNSUBSCRIBE serializer function. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.unsubscribe != NULL ) - { - serializeUnsubscribe = pMqttConnection->network.serialize.unsubscribe; - } - #endif - - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && - ( pUnsubscribeRef == NULL ) ) - { - AwsIotLogError( "Reference must be provided for a waitable UNSUBSCRIBE." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Check that all elements in the subscription list are valid. */ - if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_UNSUBSCRIBE, - pMqttConnection->awsIotMqttMode, - pSubscriptionList, - subscriptionCount ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Remove the given subsriptions from the MQTT connection. */ - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, - pSubscriptionList, - subscriptionCount ); - - /* Create an UNSUBSCRIBE operation. */ - status = AwsIotMqttInternal_CreateOperation( &pUnsubscribeOperation, - flags, - pCallbackInfo ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - return status; - } - - /* Check the UNSUBSCRIBE operation data and set the remaining members. */ - AwsIotMqtt_Assert( pUnsubscribeOperation->pPublishRetry == NULL ); - AwsIotMqtt_Assert( pUnsubscribeOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - pUnsubscribeOperation->operation = AWS_IOT_MQTT_UNSUBSCRIBE; - pUnsubscribeOperation->pMqttConnection = pMqttConnection; - - /* Generate an UNSUBSCRIBE packet from the subscription list. */ - status = serializeUnsubscribe( pSubscriptionList, - subscriptionCount, - &( pUnsubscribeOperation->pMqttPacket ), - &( pUnsubscribeOperation->packetSize ), - &( pUnsubscribeOperation->packetIdentifier ) ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pUnsubscribeOperation ); - - return status; - } - - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pUnsubscribeOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pUnsubscribeOperation->packetSize > 0 ); - - /* Set the reference, if provided. This is set before the unsubscribe - * is pushed to the network queue. */ - if( pUnsubscribeRef != NULL ) - { - *pUnsubscribeRef = pUnsubscribeOperation; - } - - /* Add the UNSUBSCRIBE operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pUnsubscribeOperation->link ) ) == false ) - { - AwsIotLogError( "Failed to add UNSUBSCRIBE to send queue." ); - - AwsIotMqttInternal_DestroyOperation( pUnsubscribeOperation ); - - /* Clear the previously set (and now invalid) reference. */ - if( pUnsubscribeRef != NULL ) - { - *pUnsubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - } - - return AWS_IOT_MQTT_NO_MEMORY; - } - - AwsIotLogInfo( "MQTT UNSUBSCRIBE operation queued." ); - - /* The UNSUBSCRIBE operation is waiting for a network response. */ - return AWS_IOT_MQTT_STATUS_PENDING; + return _subscriptionCommon( AWS_IOT_MQTT_UNSUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pUnsubscribeRef ); } /*-----------------------------------------------------------*/ @@ -2254,59 +2229,92 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, const char * AwsIotMqtt_strerror( AwsIotMqttError_t status ) { - /* The string returned if the parameter is invalid. */ - static const char * pInvalidStatus = "INVALID STATUS"; - /* Lookup table of MQTT statuses. */ - static const char * pStatusNames[] = - { - "SUCCESS", /* AWS_IOT_MQTT_SUCCESS */ - "PENDING", /* AWS_IOT_MQTT_STATUS_PENDING */ - "INIT FAILED", /* AWS_IOT_MQTT_INIT_FAILED */ - "BAD PARAMETER", /* AWS_IOT_MQTT_BAD_PARAMETER */ - "NO MEMORY", /* AWS_IOT_MQTT_NO_MEMORY */ - "NETWORK SEND ERROR", /* AWS_IOT_MQTT_SEND_ERROR */ - "BAD RESPONSE RECEIVED", /* AWS_IOT_MQTT_BAD_RESPONSE */ - "TIMEOUT", /* AWS_IOT_MQTT_TIMEOUT */ - "SERVER REFUSED", /* AWS_IOT_MQTT_SERVER_REFUSED */ - "NO RESPONSE" /* AWS_IOT_MQTT_RETRY_NO_RESPONSE */ - }; - - /* Check that the parameter is valid. */ - if( ( status < 0 ) || - ( status >= ( sizeof( pStatusNames ) / sizeof( pStatusNames[ 0 ] ) ) ) ) - { - return pInvalidStatus; - } - - return pStatusNames[ status ]; + switch( status ) + { + case AWS_IOT_MQTT_SUCCESS: + + return "SUCCESS"; + + case AWS_IOT_MQTT_STATUS_PENDING: + + return "PENDING"; + + case AWS_IOT_MQTT_INIT_FAILED: + + return "INITIALIZATION FAILED"; + + case AWS_IOT_MQTT_BAD_PARAMETER: + + return "BAD PARAMETER"; + + case AWS_IOT_MQTT_NO_MEMORY: + + return "NO MEMORY"; + + case AWS_IOT_MQTT_SEND_ERROR: + + return "NETWORK SEND ERROR"; + + case AWS_IOT_MQTT_BAD_RESPONSE: + + return "BAD RESPONSE RECEIVED"; + + case AWS_IOT_MQTT_TIMEOUT: + + return "TIMEOUT"; + + case AWS_IOT_MQTT_SERVER_REFUSED: + + return "SERVER REFUSED"; + + case AWS_IOT_MQTT_RETRY_NO_RESPONSE: + + return "NO RESPONSE"; + + default: + + return "INVALID STATUS"; + } } /*-----------------------------------------------------------*/ const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ) { - /* The string returned if the parameter is invalid. */ - static const char * pInvalidOperation = "INVALID OPERATION"; - /* Lookup table of MQTT operations. */ - static const char * pOperationTypes[] = + switch( operation ) { - "CONNECT", /* AWS_IOT_MQTT_CONNECT */ - "PUBLISH", /* AWS_IOT_MQTT_PUBLISH_TO_SERVER */ - "PUBACK", /* AWS_IOT_MQTT_PUBACK */ - "SUBSCRIBE", /* AWS_IOT_MQTT_SUBSCRIBE */ - "UNSUBSCRIBE", /* AWS_IOT_MQTT_UNSUBSCRIBE */ - "PINGREQ", /* AWS_IOT_MQTT_PINGREQ */ - "DISCONNECT" /* AWS_IOT_MQTT_DISCONNECT */ - }; + case AWS_IOT_MQTT_CONNECT: - /* Check that the parameter is valid. */ - if( ( operation < 0 ) || - ( operation >= ( sizeof( pOperationTypes ) / sizeof( pOperationTypes[ 0 ] ) ) ) ) - { - return pInvalidOperation; - } + return "CONNECT"; + + case AWS_IOT_MQTT_PUBLISH_TO_SERVER: + + return "PUBLISH"; + + case AWS_IOT_MQTT_PUBACK: + + return "PUBACK"; + + case AWS_IOT_MQTT_SUBSCRIBE: + + return "SUBSCRIBE"; - return pOperationTypes[ operation ]; + case AWS_IOT_MQTT_UNSUBSCRIBE: + + return "UNSUBSCRIBE"; + + case AWS_IOT_MQTT_PINGREQ: + + return "PINGREQ"; + + case AWS_IOT_MQTT_DISCONNECT: + + return "DISCONNECT"; + + default: + + return "INVALID OPERATION"; + } } /*-----------------------------------------------------------*/ From 9c528b4c378c945e7d40faf1a04f0a84827931cf Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 18 Dec 2018 13:02:48 -0800 Subject: [PATCH 006/844] Move MQTT subscription cleanup from Wait to Notify. (#252) --- lib/source/mqtt/aws_iot_mqtt_api.c | 8 +++++--- lib/source/mqtt/aws_iot_mqtt_operation.c | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index 7015df4895..fcb6437f95 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -2193,9 +2193,11 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, AwsIotMqtt_Assert( pOperation->link.pNext == NULL ); AwsIotMqtt_Assert( pOperation->link.pPrevious == NULL ); - /* Remove any lingering subscriptions if SUBSCRIBE failed. */ - if( ( status != AWS_IOT_MQTT_SUCCESS ) && - ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) ) + /* Remove any lingering subscriptions from a timed out SUBSCRIBE. If + * a SUBSCRIBE fails for any other reason, its subscription have already + * been removed. */ + if( ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) && + ( status == AWS_IOT_MQTT_TIMEOUT ) ) { AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, pOperation->packetIdentifier, diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index 728b0ca4cf..c96a0c1251 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -619,6 +619,17 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) { + /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected + * subscriptions are removed by the deserializer, so not removed here. */ + if( ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) && + ( pOperation->status != AWS_IOT_MQTT_SUCCESS ) && + ( pOperation->status != AWS_IOT_MQTT_SERVER_REFUSED ) ) + { + AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, + pOperation->packetIdentifier, + -1 ); + } + /* If the operation is waiting, post to its wait semaphore and return. */ if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) { From 141787c917d159572a5d7c3be409d197382ae8b2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 8 Jan 2019 09:52:05 -0800 Subject: [PATCH 007/844] Update documentation in Shadow (#255) Documentation for functions `AwsIotShadow_Wait`, `AwsIotShadow_strerror`, and `AwsIotShadow_SetDeltaCallback`. --- doc/config/shadow | 3 +- doc/lib/mqtt.txt | 2 +- doc/lib/shadow.txt | 43 ++++ lib/include/aws_iot_mqtt.h | 11 +- lib/include/aws_iot_shadow.h | 293 ++++++++++++++++++++++++- lib/source/shadow/aws_iot_shadow_api.c | 83 ++++++- 6 files changed, 427 insertions(+), 8 deletions(-) diff --git a/doc/config/shadow b/doc/config/shadow index 877596c05d..71afa0790c 100644 --- a/doc/config/shadow +++ b/doc/config/shadow @@ -25,4 +25,5 @@ FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt TAGFILES = doc/tag/main.tag=../main \ doc/tag/mqtt.tag=../mqtt \ doc/tag/queue.tag=../queue \ - doc/tag/logging.tag=../logging + doc/tag/logging.tag=../logging \ + doc/tag/platform.tag=../platform diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index fafc65b009..dc52b8fcd9 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -52,7 +52,7 @@ digraph mqtt_dependencies @enddot Currently, the MQTT library has the following dependencies: -- The queue library for maintains the data structures for managing in-progress MQTT operations. +- The queue library for maintaining the data structures for managing in-progress MQTT operations. - The logging library may be used if @ref AWS_IOT_LOG_LEVEL_MQTT is not @ref AWS_IOT_LOG_NONE. - The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index cb68264be3..804591ed04 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -6,6 +6,49 @@ > A device's shadow is a JSON document that is used to store and retrieve current state information for a device. The Device Shadow service maintains a shadow for each device you connect to AWS IoT. You can use the shadow to get and set the state of a device over MQTT or HTTP, regardless of whether the device is connected to the Internet. Each device's shadow is uniquely identified by the name of the corresponding thing. Description of Device Shadows from [AWS IoT documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html)
+ +Thing Shadows are the always-available device state in the AWS cloud. They are stored as JSON documents, and available via AWS even if the associated device goes offline. Common use cases for Thing Shadows include backing up device state, or sending commands to devices. + +This library provides an API for interacting with AWS IoT Thing Shadows. Features of this library include: +- Both fully asynchronous and blocking API functions. +- API functions for modifying Thing Shadows and for registering notifications of a Thing Shadow change. + +@dependencies{shadow,Shadow library} +@dot "Shadow direct dependencies" +digraph shadow_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph + { + shadow[label="Shadow", fillcolor="#cc00ccff"]; + mqtt[label="MQTT", fillcolor="#cc00ccff", URL="@ref mqtt"]; + } + subgraph + { + node[fillcolor="#aed8a9ff"]; + rank = same; + queue[label="Queue", URL="@ref queue"]; + logging[label="Logging", URL="@ref logging"]; + } + subgraph + { + rank = same; + platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; + } + shadow -> mqtt; + shadow -> queue; + shadow -> logging [label=" if logging enabled", style="dashed"]; + shadow -> platform_static_memory [label=" if static memory only", style="dashed"]; +} +@enddot + +Currently, the Shadow library has the following dependencies: +- The MQTT library for sending the messages that interact with the Thing Shadow service. See [this page](@ref mqtt_dependencies) for the dependencies of the MQTT library, which are not shown in the graph above. +- The queue library for maintaining the data structures for managing in-progress Shadow operations. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_SHADOW is not @ref AWS_IOT_LOG_NONE. + +In addition to the components above, the Shadow library also depends on C standard library headers. */ /** diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h index bd1c9e5caa..ee022dde9d 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/aws_iot_mqtt.h @@ -1705,11 +1705,14 @@ AwsIotMqttError_t AwsIotMqtt_TimedPublish( AwsIotMqttConnection_t mqttConnection * * // Publish should have returned AWS_IOT_MQTT_STATUS_PENDING. The call to wait * // returns once the result of the publish is available or the timeout expires. - * result = AwsIotMqtt_Wait( reference, timeoutMs ); + * if( result == AWS_IOT_MQTT_STATUS_PENDING ) + * { + * result = AwsIotMqtt_Wait( reference, timeoutMs ); * - * // After the call to wait, the result of the publish is known - * // (not AWS_IOT_MQTT_STATUS_PENDING). - * assert( result != AWS_IOT_MQTT_STATUS_PENDING ); + * // After the call to wait, the result of the publish is known + * // (not AWS_IOT_MQTT_STATUS_PENDING). + * assert( result != AWS_IOT_MQTT_STATUS_PENDING ); + * } * @endcode */ /* @[declare_mqtt_wait] */ diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index c33abbe53f..91047721c6 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -185,6 +185,8 @@ typedef enum AwsIotShadowError * - @ref shadow_function_timedget * - @ref shadow_function_timedupdate * - @ref shadow_function_wait + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback */ AWS_IOT_SHADOW_TIMEOUT, @@ -550,21 +552,73 @@ typedef struct AwsIotShadowDocumentInfo /** * @brief Maintain the subscriptions for the Shadow operation topics, even after * this function returns. + * + * This flag is only valid if passed to the functions @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update. + * + * The Shadow service reports results of Shadow operations by publishing + * messages to MQTT topics. By default, the functions @ref shadow_function_delete, + * @ref shadow_function_get, and @ref shadow_function_update subscribe to the + * necessary topics, wait for the Shadow service to publish the result of the + * Shadow operation, then unsubscribe from those topics. This workflow is suitable + * for infrequent Shadow operations, but is inefficient for frequent, periodic + * Shadow operations (where subscriptions for the Shadow operation topics would be + * constantly added and removed). + * + * This flag causes @ref shadow_function_delete, @ref shadow_function_get, or + * @ref shadow_function_update to maintain Shadow operation topic subscriptions, + * even after the function returns. These subscriptions may then be used by a + * future call to the same function. + * + * This flags only needs to be set once, after which subscriptions are maintained + * and reused for a specific Thing Name and Shadow function. The function @ref + * shadow_function_removepersistentsubscriptions may be used to remove + * subscriptions maintained by this flag. */ #define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) /** * @brief Remove the persistent subscriptions from a Shadow delete operation. + * + * This flag is only valid if passed to the function @ref + * shadow_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref shadow_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref shadow_function_delete. + * + * @warning Do not call @ref shadow_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Shadow delete operations. */ #define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001 ) /** * @brief Remove the persistent subscriptions from a Shadow get operation. + * + * This flag is only valid if passed to the function @ref + * shadow_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref shadow_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref shadow_function_get. + * + * @warning Do not call @ref shadow_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Shadow get operations. */ #define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002 ) /** * @brief Remove the persistent subscriptions from a Shadow update operation. + * + * This flag is only valid if passed to the function @ref + * shadow_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref shadow_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref shadow_function_update. + * + * @warning Do not call @ref shadow_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Shadow update operations. */ #define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004 ) @@ -711,6 +765,117 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( AwsIotMqttConnection_t mqttConnect /** * @brief Wait for a Shadow operation to complete. + * + * This function blocks to wait for a [delete](@ref shadow_function_delete), + * [get](@ref shadow_function_get), or [update](@ref shadow_function_update) to + * complete. These operations are by default asynchronous; the function calls + * queue an operation for processing, and a callback is invoked once the operation + * is complete. + * + * To use this function, the flag #AWS_IOT_SHADOW_FLAG_WAITABLE must have been + * set in the operation's function call. Additionally, this function must always + * be called with any waitable operation to clean up resources. + * + * Regardless of its return value, this function always clean up resources used + * by the waitable operation. This means `reference` is invalidated as soon as + * this function returns, even if it returns #AWS_IOT_SHADOW_TIMEOUT or another + * error. + * + * @param[in] reference Reference to the Shadow operation to wait for. The flag + * #AWS_IOT_SHADOW_FLAG_WAITABLE must have been set for this operation. + * @param[in] timeoutMs How long to wait before returning #AWS_IOT_SHADOW_TIMEOUT. + * @param[out] pShadowDocument A pointer to a buffer containing the Shadow document + * retrieved by a [Shadow get](@ref shadow_function_get) is placed here. The buffer + * was allocated with the function #AwsIotShadowDocumentInfo_t.mallocDocument passed + * to @ref shadow_function_get. This parameter is only valid for a [Shadow get] + * (@ref shadow_function_get) and ignored for other Shadow operations. This output + * parameter is only valid if this function returns #AWS_IOT_SHADOW_SUCCESS. + * @param[out] pShadowDocumentLength The length of the Shadow document in + * `pShadowDocument` is placed here. This parameter is only valid for a [Shadow get] + * (@ref shadow_function_get) and ignored for other Shadow operations. This output + * parameter is only valid if this function returns #AWS_IOT_SHADOW_SUCCESS. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - #AWS_IOT_SHADOW_TIMEOUT + * - A Shadow service rejected reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) + * + * Example 1 (Shadow Update) + * @code{c} + * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; + * AwsIotShadowDocumentInfo_t updateInfo = { ... }; + * + * // Reference and timeout. + * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * uint64_t timeout = 5000; // 5 seconds + * + * // Shadow update operation. + * result = AwsIotShadow_Update( mqttConnection, + * &updateInfo, + * AWS_IOT_SHADOW_FLAG_WAITABLE, + * NULL, + * &reference ); + * + * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait + * // returns once the result of the update is available or the timeout expires. + * if( result == AWS_IOT_SHADOW_STATUS_PENDING ) + * { + * // The last two parameters are ignored for a Shadow update. + * result = AwsIotShadow_Wait( reference, timeout, NULL, NULL ); + * + * // After the call to wait, the result of the update is known + * // (not AWS_IOT_SHADOW_STATUS_PENDING). + * assert( result != AWS_IOT_SHADOW_STATUS_PENDING ); + * } + * @endcode + * + * Example 2 (Shadow Get) + * @code{c} + * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; + * AwsIotShadowDocumentInfo_t getInfo = { ... }; + * + * // Reference and timeout. + * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * uint64_t timeout = 5000; // 5 seconds + * + * // Buffer pointer and size for retrieved Shadow document. + * const char * pShadowDocument = NULL; + * size_t documentLength = 0; + * + * // Buffer allocation function must be set for a waitable Shadow get. + * getInfo.get.mallocDocument = malloc; + * + * // Shadow get operation. + * result = AwsIotShadow_Get( mqttConnection, + * &getInfo, + * AWS_IOT_SHADOW_FLAG_WAITABLE, + * NULL, + * &reference ); + * + * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait + * // returns once the result of the get is available or the timeout expires. + * if( result == AWS_IOT_SHADOW_STATUS_PENDING ) + * { + * // The last two parameters must be set for a Shadow get. + * result = AwsIotShadow_Wait( reference, timeout, &pShadowDocument, &documentLength ); + * + * // After the call to wait, the result of the get is known + * // (not AWS_IOT_SHADOW_STATUS_PENDING). + * assert( result != AWS_IOT_SHADOW_STATUS_PENDING ); + * + * // The retrieved Shadow document is only valid for a successful Shadow get. + * if( result == AWS_IOT_SHADOW_SUCCESS ) + * { + * // Do something with the Shadow document... + * + * // Free the Shadow document when finished. + * free( pShadowDocument ); + * } + * } + * @endcode */ /* @[declare_shadow_wait] */ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, @@ -720,8 +885,121 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, /* @[declare_shadow_wait] */ /** - * @brief Set a callback to be invoked when the Thing Shadow "desired" and "reported" + * @brief Set a callback to be invoked when the Thing Shadow `desired` and `reported` * states differ. + * + * A Thing Shadow contains `reported` and `desired` states, meant to represent + * the current device status and some desired status, respectively. When the + * `reported` and `desired` states differ, the Thing Shadow service generates a + * delta document and publishes it to the topic `update/delta`. Devices + * with a subscription for this topic will receive the delta document and may act + * based on the different `reported` and `desired` states. See [this page] + * (https://docs.aws.amazon.com/iot/latest/developerguide/using-device-shadows.html#delta-state) + * for more information about using delta documents. + * + * A delta callback may be invoked whenever a delta document is generated. + * Each Thing may have a single delta callback set. This function modifies the delta + * callback for a specific Thing depending on the `pDeltaCallback` parameter and + * the presence of any existing delta callback: + * - When no existing delta callback exists for a specific Thing, a new delta + * callback is added. + * - If there is an existing delta callback and `pDeltaCallback` is not `NULL`, then + * the existing callback function and parameter are replaced with `pDeltaCallback`. + * - If there is an existing subscription and `pDeltaCallback` is `NULL`, then the + * delta callback is removed. + * + * This function is always blocking; it may block for up to the default MQTT + * timeout. This timeout is set as a parameter to @ref shadow_function_init, + * and defaults to @ref AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS if not set. If + * this function's underlying MQTT operations fail to complete within this + * timeout, then this function returns #AWS_IOT_SHADOW_TIMEOUT. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription to + * `update/delta`. + * @param[in] pThingName The subscription to `update/delta` will be added for + * this Thing Name. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags This parameter is for future-compatibility. Currently, flags + * are not supported for this function and this parameter is ignored. + * @param[in] pDeltaCallback Callback function and to invoke for incoming delta + * documents. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_TIMEOUT + * + * @return This function always returns #AWS_IOT_SHADOW_SUCCESS when replacing or + * removing existing delta callbacks. + * + * @see @ref shadow_function_setupdatedcallback for the function to register + * callbacks for all Shadow updates. + * + * Example + * @code{c} + * #define _THING_NAME "Test_device" + * #define _THING_NAME_LENGTH ( sizeof( _THING_NAME ) - 1 ) + * + * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; + * AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + * + * // _deltaCallbackFunction will be invoked when a delta document is received. + * deltaCallback.function = _deltaCallbackFunction; + * + * // Set the delta callback for the Thing "Test_device". + * result = AwsIotShadow_SetDeltaCallback( mqttConnection, + * _THING_NAME, + * _THING_NAME_LENGTH, + * 0, + * &deltaCallback ); + * + * // Check if callback was successfully set. + * if( result == AWS_IOT_SHADOW_SUCCESS ) + * { + * AwsIotShadowDocumentInfo_t updateInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + * + * // Set the Thing Name for Shadow update. + * updateInfo.pThingName = _THING_NAME; + * updateInfo.thingNameLength = _THING_NAME_LENGTH; + * + * // Set the Shadow document to send. This document has different "reported" + * // and "desired" states. It represents a scenario where a device is currently + * // off, but is being ordered to turn on. + * updateInfo.update.pUpdateDocument = + * "{" + * "\"state\": {" + * "\"reported\": { \"deviceOn\": false }," + * "\"desired\": { \"deviceOn\": true }" + * "}" + * "}"; + * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); + * + * // Send the Shadow document with different "reported" and desired states. + * result = AwsIotShadow_TimedUpdate( mqttConnection, + * &updateInfo, + * 0, + * 0, + * 5000 ); + * + * // After the update is successfully sent, the function _deltaCallbackFunction + * // will be invoked once the Shadow service generates and sends a delta document. + * // The delta document will contain the different "deviceOn" states, as well as + * // metadata. + * + * // Once the delta callback is no longer needed, it may be removed by passing + * // NULL as pDeltaCallback. + * result = AwsIotShadow_SetDeltaCallback( mqttConnection, + * _THING_NAME, + * _THING_NAME_LENGTH, + * 0, + * NULL ); + * + * // The return value from removing a delta callback should always be success. + * assert( result == AWS_IOT_SHADOW_SUCCESS ); + * } + * @endcode */ /* @[declare_shadow_setdeltacallback] */ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttConnection, @@ -758,6 +1036,19 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec /** * @brief Returns a string that describes an #AwsIotShadowError_t. + * + * Like POSIX's `strerror`, this function returns a string describing a return + * code. In this case, the return code is a Shadow library error code, `status`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] status The status to describe. + * + * @return A read-only string that describes `status`. + * + * @warning The string returned by this function must never be modified. */ /* @[declare_shadow_strerror] */ const char * AwsIotShadow_strerror( AwsIotShadowError_t status ); diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 5654a571ab..9d26221071 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -1060,7 +1060,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); /* Set the output parameters for Shadow GET. */ - if( pOperation->type == _SHADOW_GET ) + if( ( pOperation->type == _SHADOW_GET ) && + ( status == AWS_IOT_SHADOW_SUCCESS ) ) { *pShadowDocument = pOperation->get.pDocument; *pShadowDocumentLength = pOperation->get.documentLength; @@ -1109,3 +1110,83 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( AwsIotMqttConnection_t mqtt } /*-----------------------------------------------------------*/ + +const char * AwsIotShadow_strerror( AwsIotShadowError_t status ) +{ + switch( status ) + { + case AWS_IOT_SHADOW_SUCCESS: + + return "SUCCESS"; + + case AWS_IOT_SHADOW_STATUS_PENDING: + + return "STATUS PENDING"; + + case AWS_IOT_SHADOW_INIT_FAILED: + + return "INITIALIZATION FAILED"; + + case AWS_IOT_SHADOW_BAD_PARAMETER: + + return "BAD PARAMETER"; + + case AWS_IOT_SHADOW_NO_MEMORY: + + return "NO MEMORY"; + + case AWS_IOT_SHADOW_MQTT_ERROR: + + return "MQTT LIBRARY ERROR"; + + case AWS_IOT_SHADOW_BAD_RESPONSE: + + return "BAD RESPONSE RECEIVED"; + + case AWS_IOT_SHADOW_TIMEOUT: + + return "TIMEOUT"; + + case AWS_IOT_SHADOW_BAD_REQUEST: + + return "REJECTED: 400 BAD REQUEST"; + + case AWS_IOT_SHADOW_UNAUTHORIZED: + + return "REJECTED: 401 UNAUTHORIZED"; + + case AWS_IOT_SHADOW_FORBIDDEN: + + return "REJECTED: 403 FORBIDDEN"; + + case AWS_IOT_SHADOW_NOT_FOUND: + + return "REJECTED: 404 NOT FOUND"; + + case AWS_IOT_SHADOW_CONFLICT: + + return "REJECTED: 409 VERSION CONFLICT"; + + case AWS_IOT_SHADOW_TOO_LARGE: + + return "REJECTED: 413 PAYLOAD TOO LARGE"; + + case AWS_IOT_SHADOW_UNSUPPORTED: + + return "REJECTED: 415 UNSUPPORTED ENCODING"; + + case AWS_IOT_SHADOW_TOO_MANY_REQUESTS: + + return "REJECTED: 429 TOO MANY REQUESTS"; + + case AWS_IOT_SHADOW_SERVER_ERROR: + + return "500 SERVER ERROR"; + + default: + + return "INVALID STATUS"; + } +} + +/*-----------------------------------------------------------*/ From 0b47f26074d4aea9d32df6a6865bdb193139fed2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 21 Jan 2019 09:31:58 -0800 Subject: [PATCH 008/844] Replace list/queue with linear containers. (#257) --- demos/aws_iot_demo_config.h | 4 +- demos/aws_iot_demo_mqtt.c | 4 + doc/config/layout_main.xml | 2 +- doc/config/{queue => linear_containers} | 16 +- doc/config/mqtt | 2 +- doc/config/shadow | 4 +- doc/lib/linear_containers.txt | 28 + doc/lib/mqtt.txt | 31 +- doc/lib/queue.txt | 63 -- doc/lib/shadow.txt | 4 +- lib/include/aws_iot_queue.h | 498 ---------- lib/include/iot_linear_containers.h | 888 ++++++++++++++++++ lib/include/private/aws_iot_mqtt_internal.h | 117 +-- lib/include/private/aws_iot_shadow_internal.h | 30 +- lib/source/common/CMakeLists.txt | 5 +- lib/source/common/aws_iot_queue.c | 620 ------------ lib/source/mqtt/aws_iot_mqtt_api.c | 571 +++++------ lib/source/mqtt/aws_iot_mqtt_operation.c | 706 +++++++------- lib/source/mqtt/aws_iot_mqtt_subscription.c | 133 ++- .../posix/network/aws_iot_network_openssl.c | 5 +- lib/source/shadow/aws_iot_shadow_api.c | 64 +- lib/source/shadow/aws_iot_shadow_operation.c | 82 +- .../shadow/aws_iot_shadow_subscription.c | 76 +- tests/CMakeLists.txt | 3 + tests/aws_iot_tests_config.h | 4 +- tests/common/CMakeLists.txt | 7 + tests/common/iot_tests_common.c | 100 ++ .../common/unit/iot_tests_linear_containers.c | 103 ++ tests/mqtt/access/aws_iot_test_access_mqtt.h | 8 +- .../aws_iot_test_access_mqtt_subscription.c | 12 +- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 6 +- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 28 +- tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 45 +- .../unit/aws_iot_tests_mqtt_subscription.c | 202 ++-- 34 files changed, 2224 insertions(+), 2247 deletions(-) rename doc/config/{queue => linear_containers} (53%) create mode 100644 doc/lib/linear_containers.txt delete mode 100644 doc/lib/queue.txt delete mode 100644 lib/include/aws_iot_queue.h create mode 100644 lib/include/iot_linear_containers.h delete mode 100644 lib/source/common/aws_iot_queue.c create mode 100644 tests/common/CMakeLists.txt create mode 100644 tests/common/iot_tests_common.c create mode 100644 tests/common/unit/iot_tests_linear_containers.c diff --git a/demos/aws_iot_demo_config.h b/demos/aws_iot_demo_config.h index 133dcdee95..4eef6c16a0 100644 --- a/demos/aws_iot_demo_config.h +++ b/demos/aws_iot_demo_config.h @@ -43,8 +43,8 @@ #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) -/* Enable asserts in queues and MQTT. */ -#define AWS_IOT_QUEUE_ENABLE_ASSERTS ( 1 ) +/* Enable asserts in linear containers and MQTT. */ +#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_MQTT_ENABLE_ASSERTS ( 1 ) /* Library logging configuration. */ diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c index 86b4aba873..e7f3d37683 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/aws_iot_demo_mqtt.c @@ -201,6 +201,10 @@ static void _operationCompleteCallback( void * param1, { intptr_t publishCount = ( intptr_t ) param1; + /* Silence warnings about unused variables. publishCount will not be used if + * logging is disabled. */ + ( void ) publishCount; + /* Print the status of the completed operation. A PUBLISH operation is * successful when transmitted over the network. */ if( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) diff --git a/doc/config/layout_main.xml b/doc/config/layout_main.xml index f45cd386a2..52a2403711 100644 --- a/doc/config/layout_main.xml +++ b/doc/config/layout_main.xml @@ -9,7 +9,7 @@ - + diff --git a/doc/config/queue b/doc/config/linear_containers similarity index 53% rename from doc/config/queue rename to doc/config/linear_containers index cd9ebe3f27..ca333fe113 100644 --- a/doc/config/queue +++ b/doc/config/linear_containers @@ -3,23 +3,21 @@ @INCLUDE = common # Basic project information. -PROJECT_NAME = "Queue" -PROJECT_BRIEF = "Queues and lists" +PROJECT_NAME = "Linear Containers" +PROJECT_BRIEF = "Linked lists and Queues" # Library documentation output directory. -HTML_OUTPUT = queue +HTML_OUTPUT = linear_containers # Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/queue.tag +GENERATE_TAGFILE = doc/tag/linear_containers.tag # Directories containing library source code. INPUT = doc/lib \ - lib/include/ \ - lib/source/common + lib/include/ # Library file names. -FILE_PATTERNS = *queue*.c *queue*.h *queue*.txt +FILE_PATTERNS = *linear_containers*.h *linear_containers*.txt # External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/platform.tag=../platform +TAGFILES = doc/tag/main.tag=../main diff --git a/doc/config/mqtt b/doc/config/mqtt index 60e34d035c..631cdc9dc3 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -29,6 +29,6 @@ FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt aws_iot_tests_network.c # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ - doc/tag/queue.tag=../queue \ doc/tag/logging.tag=../logging \ doc/tag/platform.tag=../platform \ + doc/tag/linear_containers.tag=../linear_containers diff --git a/doc/config/shadow b/doc/config/shadow index 71afa0790c..01b984ef3a 100644 --- a/doc/config/shadow +++ b/doc/config/shadow @@ -24,6 +24,6 @@ FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ doc/tag/mqtt.tag=../mqtt \ - doc/tag/queue.tag=../queue \ doc/tag/logging.tag=../logging \ - doc/tag/platform.tag=../platform + doc/tag/platform.tag=../platform \ + doc/tag/linear_containers.tag=../linear_containers diff --git a/doc/lib/linear_containers.txt b/doc/lib/linear_containers.txt new file mode 100644 index 0000000000..3ec2a990fd --- /dev/null +++ b/doc/lib/linear_containers.txt @@ -0,0 +1,28 @@ +/** +@mainpage +@anchor linear_containers +@brief Linked list and queue data structures and functions.

+ +This library provides linear containers, such as linked lists and queues. Linked lists and queues may hold any data type that contain an #IotLink_t member. By default, these containers do not provide thread-safety. + +Currently, linear containers are implemented with `static inline` functions and have no dependencies other than C standard library types. +*/ + +/** +@configpage{linear_containers,linear containers library} + +@section IOT_CONTAINERS_ENABLE_ASSERTS +@brief Set this to `1` to perform sanity checks when using linear containers. + +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotContainers_Assert can be defined to set the assert function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. + +@configpossible `0` (asserts disabled) or `1` (asserts enabled)
+@configrecommended `1` when debugging; `0` in production code.
+@configdefault `0` + +@section IotContainers_Assert +@brief Assertion function used when @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`. + +@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
+@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`; otherwise, nothing. +*/ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index dc52b8fcd9..7d20ddc9d1 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -28,7 +28,7 @@ digraph mqtt_dependencies { node[fillcolor="#aed8a9ff"]; rank = same; - queue[label="Queue", URL="@ref queue"]; + linear_containers[label="List/Queue", URL="@ref linear_containers"]; logging[label="Logging", URL="@ref logging"]; } subgraph @@ -39,13 +39,12 @@ digraph mqtt_dependencies platform_network[label="Network", fillcolor="#e89025ff", URL="@ref platform_network"]; platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; } - mqtt -> queue; + mqtt -> linear_containers; mqtt -> logging [label=" if logging enabled", style="dashed"]; mqtt -> platform_threads; mqtt -> platform_clock; mqtt -> platform_network; mqtt -> platform_static_memory [label=" if static memory only", style="dashed"]; - queue -> platform_threads; logging -> platform_clock; logging -> platform_static_memory [label=" if static memory only", style="dashed"]; } @@ -63,17 +62,6 @@ In addition to the components above, the MQTT library also depends on C standard @page mqtt_design Design @brief Architecture behind the MQTT library. -This library uses threads to process outgoing and incoming MQTT operations, and queues to store MQTT operations waiting to be processed. -- [MQTT API functions](@ref mqtt_functions) only enqueue a waiting operation on the Send Queue. API functions return after successfully enqueuing an operation and do not block to wait for the result of the operation. -- Send threads are created when operations arrive in the send queue. These threads remove operations from the send queue in FIFO order and transmit MQTT packets across the network. After transmitting the packet, send threads add operations awaiting responses from the server to the Receive Queue. The setting @ref AWS_IOT_MQTT_MAX_SEND_THREADS controls the maximum number of simultaneous send threads. -- When a response is received from the server, the system's network stack invokes the [MQTT receive callback](@ref mqtt_function_receivecallback). This callback parses the server response, matches the incoming data with an operation in the receive queue, and enqueues the operation in the Callback Queue. -- Callback threads are created when operations arrive in the callback queue. These threads handle notification of an MQTT operation's result. The notification is handled by a separate thread (not the [receive callback](@ref mqtt_function_receivecallback)) to allow [callback functions](@ref AwsIotMqttCallbackInfo_t.function) to block without slowing down the receive callback. The setting @ref AWS_IOT_MQTT_MAX_CALLBACK_THREADS controls the maximum number of simultaneous callback threads. -- Periodically, timer threads will be created to send keep-alive packets and retransmit unacknowledged QoS 1 PUBLISH operations. - -Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the MQTT library on-the-go on some systems, while other systems may use an always-allocated thread pool. - -The sequence diagram below illustrates the workflow described above. The application thread is able to continue executing while the MQTT library processes the operation. - @image html mqtt_design_typicaloperation.png width=100% */ @@ -273,24 +261,15 @@ void AwsIotMqttInternal_CleanupSerializeAdditional( void ); @configrecommended The default value is strongly recommended.
@configdefault The default value of this setting depends on the platform. For example, when running on FreeRTOS with BLE support, this setting will be automatically enabled. Conversely, when running on Linux, this setting will be disabled. -@section AWS_IOT_MQTT_MAX_CALLBACK_THREADS -@brief Set the maximum number of simultaneous callback threads. +@section AWS_IOT_MQTT_MAX_THREADS +@brief Set the maximum number of simultaneous threads the MQTT library may spawn. -Callback threads process notifications from completed MQTT operations or incoming PUBLISH messages. Incoming notifications are queued, then removed from the queue and processed by any available callback threads. User callback functions are invoked from the callback threads. This means that user callback functions should avoid blocking, since blocking will make a callback thread unavailable and cause the queue of notifications to grow. A situation where all callback threads are blocked should certainly be avoided. +Threads process MQTT operations, such as sending MQTT packets or invoking callbacks for completed operations. This setting limits the maximum number of threads the MQTT library may have active at any time. @configpossible Any positive integer.
@configrecommended In most use cases, this value can be `1` or `2`. In high-throughput use cases with many MQTT operations or incoming PUBLISH messages, this value can be increased for better performance.
@configdefault `2` -@section AWS_IOT_MQTT_MAX_SEND_THREADS -@brief Set the number of simultaneous send threads. - -Send threads process outgoing network packets. Packets are queued, then removed from the queue and sent over the network by any available send threads. - -@configpossible Any positive integer
-@configrecommended `1`. Unless the [network interface send function](@ref AwsIotMqttNetIf_t.send) supports parallel sends, any value greater than `1` will not provide any performance benefit.
-@configdefault `1` - @section AWS_IOT_LOG_LEVEL_MQTT @brief Set the log level of the MQTT library. diff --git a/doc/lib/queue.txt b/doc/lib/queue.txt deleted file mode 100644 index c5d7d3a6f7..0000000000 --- a/doc/lib/queue.txt +++ /dev/null @@ -1,63 +0,0 @@ -/** -@mainpage -@anchor queue -@brief Queue and linked list data structures and functions.

- -This library provides queue and linked list data structures which other libraries use for organizing data. Features of this library include: -- Double-ended notification queue that may also function as a doubly-linked list. -- Simple doubly-linked list. -- Queues and lists may hold any data types. - -@section queue_vs_list Queue vs List -@brief Comparison between #AwsIotQueue_t and #AwsIotList_t. - -This library provides two similar data structures, #AwsIotQueue_t (queue) and #AwsIotList_t (list). - -#AwsIotQueue_t is a double-ended "notify queue" that can create threads to process queued data. It may also be searched linearly, allowing it to function as a doubly-linked list. All functions that operate on an #AwsIotQueue_t provide thread safety. - -#AwsIotList_t implements a simpler doubly-linked list for storing data. It requires less memory than an #AwsIotQueue_t. However, unlike #AwsIotQueue_t, most of its functions do not provide thread safety. Only @ref list_function_removeallmatches provides thread safety. For all other list functions, #AwsIotList_t.mutex must be locked before the function call if thread safety is required. - -A comparison of these two data structures is below. -@anchor queue_vs_list_table | Queue | List ---------------------------- | ------------------------------------ | ---- -Thread safety | All queue functions are thread safe. | Only @ref list_function_removeallmatches is thread safe.
#AwsIotList_t.mutex must be locked before calling any other function if thread safety is required. -Data insertion | Data may only be [inserted at the head.](@ref queue_function_inserthead) | Data may be inserted [at the head](@ref list_function_inserthead) or [sorted into place.](@ref list_function_insertsorted) -Data searching | No in-place searches. Data can only be [search-and-removed.](@ref queue_function_removefirstmatch) | [Search function](@ref list_function_findfirstmatch) does not remove data. -Data removal | [Remove tail (FIFO)](@ref queue_function_removetail), [search and remove first matching data](@ref queue_function_removefirstmatch), or [search and remove all matching data](@ref queue_function_removeallmatches). | Remove data after [searching](@ref list_function_findfirstmatch) or [search and remove all matching data.](@ref list_function_removeallmatches) -Other | [Can provide notifications](@ref AwsIotQueueNotifyParams_t.notifyRoutine) when data is inserted. | Uses less memory than queue. - -@dependencies{queue,queue library} -@dot "Queue direct dependencies" -digraph queue_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - queue[label="Queue", fillcolor="#aed8a9ff"]; - platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; - standard_library[label="Standard library\nstdbool, stddef, stdint, string", fillcolor="#d15555ff"]; - queue -> platform_threads; - queue -> standard_library; -} -@enddot - -Currently, the queue library has the following dependencies: -- The [platform thread management component](@ref platform_threads) for creating notification threads when data is [inserted into a queue](@ref queue_function_inserthead). The queue library also depends on the mutexes provided by this component. -*/ - -/** -@configpage{queue,queue library} - -@section AWS_IOT_QUEUE_ENABLE_ASSERTS -@brief Set this to `1` to perform sanity checks when using queues and lists. - -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotQueue_Assert can be defined to set the assert function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. - -@configpossible `0` (asserts disabled) or `1` (asserts enabled)
-@configrecommended `1` when debugging; `0` in production code.
-@configdefault `0` - -@section AwsIotQueue_Assert -@brief Assertion function used when @ref AWS_IOT_QUEUE_ENABLE_ASSERTS is `1`. - -@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_QUEUE_ENABLE_ASSERTS is `1`; otherwise, nothing. -*/ diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 804591ed04..fbebc7e48f 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -28,7 +28,7 @@ digraph shadow_dependencies { node[fillcolor="#aed8a9ff"]; rank = same; - queue[label="Queue", URL="@ref queue"]; + linear_containers[label="List/Queue", URL="@ref linear_containers"]; logging[label="Logging", URL="@ref logging"]; } subgraph @@ -37,7 +37,7 @@ digraph shadow_dependencies platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; } shadow -> mqtt; - shadow -> queue; + shadow -> linear_containers; shadow -> logging [label=" if logging enabled", style="dashed"]; shadow -> platform_static_memory [label=" if static memory only", style="dashed"]; } diff --git a/lib/include/aws_iot_queue.h b/lib/include/aws_iot_queue.h deleted file mode 100644 index 7f4b686219..0000000000 --- a/lib/include/aws_iot_queue.h +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_queue.h - * @brief Declares queue and linked list data structures and functions. - */ - -#ifndef _AWS_IOT_QUEUE_H_ -#define _AWS_IOT_QUEUE_H_ - -/* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include -#include - -/* Platform layer includes. */ -#include "platform/aws_iot_threads.h" - -/** - * @brief Calculates the offset of a link within its containing struct. - * - * @param[in] containerType The name of the containing struct type. - * @param[in] linkMember The name of the link member in the containing struct. - * - * Example - * @code{c} - * typedef struct Example - * { - * int member; - * AwsIotLink_t link1; - * AwsIotLink_t link2; - * } Example_t; - * - * // Calculate offset of link1. - * size_t offset = AwsIotLink_Offset( Example_t, link1 ); - * @endcode - */ -#define AwsIotLink_Offset( containerType, linkMember ) \ - ( ( size_t ) &( ( ( containerType * ) 0 )->linkMember ) ) - -/** - * @brief Calculates the starting address of a containing struct. - * - * @param[in] pLink Pointer to a link member. - * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset - * to calculate. - */ -#define AwsIotLink_Container( pLink, linkOffset ) \ - ( ( void * ) ( ( ( uint8_t * ) ( pLink ) ) - linkOffset ) ) - -/** - * @paramstructs{queue,queue} - */ - -/** - * @ingroup queue_datatypes_paramstructs - * @brief Configuration parameters of an #AwsIotQueue_t when using notifications. - * - * @paramfor @ref queue_function_create - * - * Passed to @ref queue_function_create to configure a new notification queue. - */ -typedef struct AwsIotQueueNotifyParams -{ - /** - * @brief Notification routine to run when data is added to the queue. - * - * This notification will run in its own detached thread. May be set to NULL - * to disable notification routines. - */ - AwsIotThreadRoutine_t notifyRoutine; - - /** - * @brief The argument passed to #AwsIotQueueNotifyParams_t.notifyRoutine. - * - * This member is not used by any queue function and is intended for - * application use. - */ - void * pNotifyArgument; -} AwsIotQueueNotifyParams_t; - -/** - * @defgroup queue_datatypes_queuelist Queue and list - * @brief Structures that represent a queue or list. - * - * Most members of these structures are internal and should only be modified - * using [queue or list functions](@ref queue_functions). The exception is the - * `mutex` member, which may be locked as needed to provide thread safety. - */ - -/** - * @ingroup queue_datatypes_queuelist - * @brief Link member placed in structs of a queue or list. - * - * All elements in a queue or list must contain one of these members. The macro - * #AwsIotLink_Offset can be used to calculate the offset of the link member in - * its containing struct. The macro #AwsIotLink_Container can be used to calculate - * the starting address of the containing struct. - */ -typedef struct AwsIotLink -{ - struct AwsIotLink * pPrevious; /**< @brief Pointer to previous link. */ - struct AwsIotLink * pNext; /**< @brief Pointer to next link. */ -} AwsIotLink_t; - -/** - * @ingroup queue_datatypes_queuelist - * @brief Queue structure. - * - * @attention Although the members of #AwsIotQueue_t are visible to applications, - * they should not be accessed directly. - */ -typedef struct AwsIotQueue -{ - AwsIotLink_t * pHead; /**< @brief Pointer to queue's head. */ - AwsIotLink_t * pTail; /**< @brief Pointer to queue's tail. */ - - AwsIotMutex_t mutex; /**< @brief Protects this queue from concurrent use. */ - AwsIotSemaphore_t notifyThreadCount; /**< @brief Limits the number of concurrent notification threads. */ - AwsIotQueueNotifyParams_t params; /**< @brief Queue notification configuration. See #AwsIotQueueNotifyParams_t. */ -} AwsIotQueue_t; - -/** - * @ingroup queue_datatypes_queuelist - * @brief List structure. - * - * @attention Although the members of #AwsIotList_t are visible to applications, only - * #AwsIotList_t.mutex and should be accessed directly. This mutex should be locked - * to provide thread safety for all list functions except @ref list_function_removeallmatches. - */ -typedef struct AwsIotList -{ - AwsIotLink_t * pHead; /**< @brief Pointer to the list's head. */ - AwsIotMutex_t mutex; /**< @brief Protects this list from concurrent use. */ -} AwsIotList_t; - -/** - * @functionspage{queue,queue library} - * - * Queue functions: - * - @functionname{queue_function_create} - * - @functionname{queue_function_destroy} - * - @functionname{queue_function_inserthead} - * - @functionname{queue_function_removetail} - * - @functionname{queue_function_removefirstmatch} - * - @functionname{queue_function_removeallmatches} - * - * List functions: - * - @functionname{list_function_create} - * - @functionname{list_function_destroy} - * - @functionname{list_function_inserthead} - * - @functionname{list_function_insertsorted} - * - @functionname{list_function_findfirstmatch} - * - @functionname{list_function_remove} - * - @functionname{list_function_removeallmatches} - */ - -/** - * @functionpage{AwsIotQueue_Create,queue,create} - * @functionpage{AwsIotQueue_Destroy,queue,destroy} - * @functionpage{AwsIotQueue_InsertHead,queue,inserthead} - * @functionpage{AwsIotQueue_RemoveTail,queue,removetail} - * @functionpage{AwsIotQueue_RemoveFirstMatch,queue,removefirstmatch} - * @functionpage{AwsIotQueue_RemoveAllMatches,queue,removeallmatches} - * @functionpage{AwsIotList_Create,list,create} - * @functionpage{AwsIotList_Destroy,list,destroy} - * @functionpage{AwsIotList_InsertHead,list,inserthead} - * @functionpage{AwsIotList_InsertSorted,list,insertsorted} - * @functionpage{AwsIotList_FindFirstMatch,list,findfirstmatch} - * @functionpage{AwsIotList_Remove,list,remove} - * @functionpage{AwsIotList_RemoveAllMatches,list,removeallmatches} - */ - -/** - * @brief Create a queue. - * - * This function initializes a new queue. It must be called on an uninitialized - * #AwsIotQueue_t before calling any other queue function. This function must - * not be called on an already-initialized #AwsIotQueue_t. - * - * If [pQueueParams->notifyRoutine](@ref AwsIotQueueNotifyParams_t.notifyRoutine) is - * not `NULL`, then this function creates a notification queue. A notification - * queue creates a thread to notify the application that data is available. Up to - * `maxNotifyThreads` simultaneous threads may be created for a single queue. - * - * @param[in] pQueue Pointer to the memory that will hold the new queue. - * @param[in] pNotifyParams Parameters for a notification queue. Optional; pass - * `NULL` to create a queue that does not have notifications. - * @param[in] maxNotifyThreads The maximum number of simultaneous notification - * threads this queue may have. Ignored if [pQueueParams->notifyRoutine] - * (@ref AwsIotQueueNotifyParams_t.notifyRoutine) is `NULL`. This parameter must be - * greater than `0` when [pQueueParams->notifyRoutine] - * (@ref AwsIotQueueNotifyParams_t.notifyRoutine) is not `NULL`. - * - * @return `true` if a queue was successfully created; `false` otherwise. - */ -/* @[declare_queue_create] */ -bool AwsIotQueue_Create( AwsIotQueue_t * const pQueue, - const AwsIotQueueNotifyParams_t * const pNotifyParams, - uint32_t maxNotifyThreads ); -/* @[declare_queue_create] */ - -/** - * @brief Free resources used by a queue. - * - * This function frees resources used by a queue. It must be called on an - * [initialized](@ref queue_function_create) #AwsIotQueue_t. No other queue - * functions should be called on `pQueue` after calling this function (unless - * the queue is re-created). - * - * If `pQueue` is a notification queue, this function will block to wait for all - * active notification threads to exit before destroying the queue. This function - * will also prevent any new notification threads from being created. - * - * @param[in] pQueue The queue to destroy. - */ -/* @[declare_queue_destroy] */ -void AwsIotQueue_Destroy( AwsIotQueue_t * const pQueue ); -/* @[declare_queue_destroy] */ - -/** - * @brief Insert an element at the head of the queue. - * - * This function places a new element in `pQueue`. New elements are always placed - * at the head of `pQueue` (FIFO). Data is inserted by setting the [next] - * (@ref AwsIotLink_t.pNext) and [previous](@ref AwsIotLink_t.pPrevious) pointers - * of a link member; no data is copied. - * - * If `pQueue` is a notification queue, a new notification thread will be created - * (assuming the system has the necessary resources). - * - * @param[in] pQueue The queue that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - * - * @return `true` if the new element was successfully inserted and any requested - * notification was successful. If the maximum number of notification threads - * are active, this function will also return `true`. This function only returns - * `false` if a notification thread couldn't be created, so its return value can - * be ignored for queues with no notification routine. - */ -/* @[declare_queue_inserthead] */ -bool AwsIotQueue_InsertHead( AwsIotQueue_t * const pQueue, - AwsIotLink_t * const pLink ); -/* @[declare_queue_inserthead] */ - -/** - * @brief Removes the element at the tail of the queue. - * - * Removes the oldest element from `pQueue`. - * - * If this function is being called from a notification thread that will exit - * if no data is available in the queue, the parameter `notifyExitIfEmpty` should - * be `true`. Otherwise, `notifyExitIfEmpty` should be `false`. - * - * @param[in] pQueue The queue that holds the element to remove. - * @param[in] linkOffset Offset of link members in elements of `pQueue`. Use - * #AwsIotLink_Offset to calculate. - * @param[in] notifyExitIfEmpty If this parameter is `true`, then this function - * will increment the number of available notification threads and expect the - * calling thread to exit. This parameter should only be `true` if the calling - * thread is a notification thread. - * - * @return Pointer to the removed queue tail; `NULL` if the queue is empty. - */ -/* @[declare_queue_removetail] */ -void * AwsIotQueue_RemoveTail( AwsIotQueue_t * const pQueue, - size_t linkOffset, - bool notifyExitIfEmpty ); -/* @[declare_queue_removetail] */ - -/** - * @brief Remove a single matching element from a queue. - * - * Removes the oldest matching queue element. This function searches starting - * from the queue tail. It will return the first matching element, so it must - * be called multiple times for multiple matching elements. - * - * @param[in] pQueue The queue to search. - * @param[in] linkOffset Offset of link members in elements of `pQueue`. Use - * #AwsIotLink_Offset to calculate. - * @param[in] pArgument If `shouldRemove` is not `NULL`, this value will be passed as the - * first argument to `shouldRemove`. If `shouldRemove` is `NULL`, every element in the queue - * will be compared to this value to find a match. - * @param[in] shouldRemove Function to determine if an element matches. Pass `NULL` to - * search using the address `pArgument`, i.e. `element == pArgument`. - * - * @return Pointer to the removed element; `NULL` if no match was found. - */ -/* @[declare_queue_removefirstmatch] */ -void * AwsIotQueue_RemoveFirstMatch( AwsIotQueue_t * const pQueue, - size_t linkOffset, - void * pArgument, - bool ( * shouldRemove )( void *, void * ) ); -/* @[declare_queue_removefirstmatch] */ - -/** - * @brief Remove all matching elements from a queue. - * - * This function searches the entire queue and removes all matching elements. - * If it's not `NULL`, the `freeElement` function is called on all matching - * elements. - * - * @param[in] pQueue The queue to search. - * @param[in] linkOffset Offset of link members in elements of `pQueue`. Use - * #AwsIotLink_Offset to calculate. - * @param[in] pArgument The first argument passed to `shouldRemove`. - * @param[in] shouldRemove Function used to determine if an element matches. - * Pass `NULL` to remove all elements from the queue. - * @param[in] freeElement Function called on each matching element. Pass - * `NULL` to ignore. - */ -/* @[declare_queue_removeallmatches] */ -void AwsIotQueue_RemoveAllMatches( AwsIotQueue_t * const pQueue, - size_t linkOffset, - void * pArgument, - bool ( * shouldRemove )( void *, void * ), - void ( * freeElement )( void * ) ); -/* @[declare_queue_removeallmatches] */ - -/** - * @brief Create a list. - * - * This function initializes a new list. It must be called on an uninitialized - * #AwsIotList_t before calling any other list function. This function must not - * be called on an already-initialized #AwsIotList_t. - * - * @param[in] pList Pointer to the memory that will hold the new list. - * - * @return `true` if a list was successfully created; `false` otherwise. - */ -/* @[declare_list_create] */ -bool AwsIotList_Create( AwsIotList_t * const pList ); -/* @[declare_list_create] */ - -/** - * @brief Free resources used by a list. - * - * This function frees resources used by a list. It must be called on an [initialized] - * (@ref list_function_create) #AwsIotList_t. No other list functions should be - * called on `pList` after calling this function (unless the list is re-created). - * - * @param[in] pList The list to destroy. - */ -/* @[declare_list_destroy] */ -void AwsIotList_Destroy( AwsIotList_t * const pList ); -/* @[declare_list_destroy] */ - -/** - * @brief Insert an element at the head of the list. - * - * This function places a new element at the head of `pList`. Data is inserted - * by setting the [next](@ref AwsIotLink_t.pNext) and [previous] - * (@ref AwsIotLink_t.pPrevious) pointers of a link member; no data is copied. - * - * @param[in] pList The list that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset - * to calculate. - * - * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) - * locked if thread safety is needed. - */ -/* @[declare_list_inserthead] */ -void AwsIotList_InsertHead( AwsIotList_t * const pList, - AwsIotLink_t * const pLink, - size_t linkOffset ); -/* @[declare_list_inserthead] */ - -/** - * @brief Insert an element in a sorted list. - * - * Places an element into a list by sorting it into order. The function - * `compare` is used to determine where to place the new element. Data is - * inserted by setting the [next](@ref AwsIotLink_t.pNext) and [previous] - * (@ref AwsIotLink_t.pPrevious) pointers of a link member; no data is copied. - * - * @param[in] pList The list that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset - * to calculate. - * @param[in] compare Determines the order of the list. Returns a negative - * value if its first argument is less than its second argument; returns - * zero if its first argument is equal to its second argument; returns a - * positive value if its first argument is greater than its second argument. - * - * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) - * locked if thread safety is needed. - */ -/* @[declare_list_insertsorted] */ -void AwsIotList_InsertSorted( AwsIotList_t * const pList, - AwsIotLink_t * const pLink, - size_t linkOffset, - int ( * compare )( void *, void * ) ); -/* @[declare_list_insertsorted] */ - -/** - * @brief Search a list for the first matching element after the start point. - * - * This function searches from `pStartPoint` to the list tail for the first - * matching element. If a match is found, the matching element is not - * removed from the list. - * - * @param[in] pStartPoint Start point of the search. Pass [pList->pHead] - * (@ref AwsIotList_t.pHead) to search an entire list. - * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset - * to calculate. - * @param[in] pArgument If `compare` is not `NULL`, this value will be passed - * as the first argument to `compare`. If `compare` is `NULL`, every - * element in the list will be compared to this value to find a match. - * @param[in] compare Function to determine if an element matches. Pass `NULL` to - * search using the address `pArgument`, i.e. `element == pArgument`. - * - * @return Pointer to a matching element; `NULL` if no match was found. - * - * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) - * locked if thread safety is needed. - */ -/* @[declare_list_findfirstmatch] */ -void * AwsIotList_FindFirstMatch( AwsIotLink_t * const pStartPoint, - size_t linkOffset, - void * pArgument, - bool ( * compare )( void *, void * ) ); -/* @[declare_list_findfirstmatch] */ - -/** - * @brief Remove a single element from a list. - * - * Remove an element from a list. The given element must be in `pList`; use - * @ref list_function_findfirstmatch to determine if an element is in `pList`. - * - * @param[in] pList The list that holds the container of `pLink`. - * @param[in] pLink The link member of the element to remove. - * @param[in] linkOffset Offset of `pLink` in its container. Use #AwsIotLink_Offset - * to calculate. - * - * @warning Do not pass a `pLink` that is not in `pList`. - * @note This function should be called with [pList->mutex](@ref AwsIotList_t.mutex) - * locked if thread safety is needed. - */ -/* @[declare_list_remove] */ -void AwsIotList_Remove( AwsIotList_t * const pList, - AwsIotLink_t * const pLink, - size_t linkOffset ); -/* @[declare_list_remove] */ - -/** - * @brief Remove all matching elements from a list. - * - * This function searches an entire list and removes all matching elements. - * If it's not `NULL`, the `freeElement` function is called on all the matching - * elements. - * - * @param[in] pList The list to search. - * @param[in] linkOffset Offset of link members in elements of `pList`. Use - * #AwsIotLink_Offset to calculate. - * @param[in] pArgument The first argument passed to `shouldRemove`. - * @param[in] shouldRemove Function used to determine if an element matches. - * Pass `NULL` to remove all elements from the list. - * @param[in] freeElement Function called on each matching element. Pass - * `NULL` to ignore. - * - * @warning Unlike the other #AwsIotList_t functions, this function provides - * thread safety by locking [pList->mutex](@ref AwsIotList_t.mutex). Therefore, - * locking [pList->mutex](@ref AwsIotList_t.mutex) before calling this function - * will cause a deadlock. - */ -/* @[declare_list_removeallmatches] */ -void AwsIotList_RemoveAllMatches( AwsIotList_t * const pList, - size_t linkOffset, - void * pArgument, - bool ( * shouldRemove )( void *, void * ), - void ( * freeElement )( void * ) ); -/* @[declare_list_removeallmatches] */ - -#endif /* ifndef _AWS_IOT_QUEUE_H_ */ diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h new file mode 100644 index 0000000000..037a60fa12 --- /dev/null +++ b/lib/include/iot_linear_containers.h @@ -0,0 +1,888 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_linear_containers.h + * @brief Declares and implements doubly-linked lists and queues. + */ + +#ifndef _IOT_LINEAR_CONTAINERS_H_ +#define _IOT_LINEAR_CONTAINERS_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/** + * @defgroup linear_containers_datatypes_listqueue List and queue + * @brief Structures that represent a list or queue. + */ + +/** + * @ingroup linear_containers_datatypes_listqueue + * @brief Link member placed in structs of a list or queue. + * + * All elements in a list or queue must contain one of these members. The macro + * #IotLink_Container can be used to calculate the starting address of the + * link's container. + */ +typedef struct IotLink +{ + struct IotLink * pPrevious; /**< @brief Pointer to the previous element. */ + struct IotLink * pNext; /**< @brief Pointer to the next element. */ +} IotLink_t; + +/** + * @ingroup linear_containers_datatypes_listqueue + * @brief Represents a doubly-linked list. + */ +typedef IotLink_t IotListDouble_t; + +/** + * @ingroup linear_containers_datatypes_listqueue + * @brief Represents a queue. + */ +typedef IotLink_t IotQueue_t; + +/** + * @constantspage{linear_containers,linear containers library} + * + * @section linear_containers_constants_initializers Linear Containers Initializers + * @brief Provides default values for initializing the linear containers data types. + * + * @snippet this define_linear_containers_initializers + * + * All user-facing data types of the linear containers library should be initialized + * using one of the following. + * + * @warning Failure to initialize a linear containers data type with the appropriate + * initializer may result in a runtime error! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + */ +/* @[define_linear_containers_initializers] */ +#define IOT_LINK_INITIALIZER { 0 } /**< @brief Initializer for an #IotLink_t. */ +#define IOT_LIST_DOUBLE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotListDouble_t. */ +#define IOT_QUEUE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotQueue_t. */ +/* @[define_linear_containers_initializers] */ + +/** + * @def IotContainers_Assert( expression ) + * @brief Assertion macro for the linear containers library. + * + * Set @ref IOT_CONTAINERS_ENABLE_ASSERTS to `1` to enable assertions in the linear + * containers library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_CONTAINERS_ENABLE_ASSERTS == 1 + #ifndef IotContainers_Assert + #include + #define IotContainers_Assert( expression ) assert( expression ) + #endif +#else + #define IotContainers_Assert( expression ) +#endif + +/** + * @brief Calculates the starting address of a containing struct. + * + * @param[in] type Type of the containing struct. + * @param[in] pLink Pointer to a link member. + * @param[in] linkName Name of the #IotLink_t in the containing struct. + */ +#define IotLink_Container( type, pLink, linkName ) \ + ( ( type * ) ( ( ( uint8_t * ) ( pLink ) ) - offsetof( type, linkName ) ) ) + +/** + * @functionspage{linear_containers,linear containers library} + * - @functionname{linear_containers_function_link_islinked} + * - @functionname{linear_containers_function_list_double_create} + * - @functionname{linear_containers_function_list_double_count} + * - @functionname{linear_containers_function_list_double_isempty} + * - @functionname{linear_containers_function_list_double_peekhead} + * - @functionname{linear_containers_function_list_double_peektail} + * - @functionname{linear_containers_function_list_double_inserthead} + * - @functionname{linear_containers_function_list_double_inserttail} + * - @functionname{linear_containers_function_list_double_insertbefore} + * - @functionname{linear_containers_function_list_double_insertafter} + * - @functionname{linear_containers_function_list_double_insertsorted} + * - @functionname{linear_containers_function_list_double_remove} + * - @functionname{linear_containers_function_list_double_removehead} + * - @functionname{linear_containers_function_list_double_removetail} + * - @functionname{linear_containers_function_list_double_removeall} + * - @functionname{linear_containers_function_list_double_findfirstmatch} + * - @functionname{linear_containers_function_list_double_removefirstmatch} + * - @functionname{linear_containers_function_list_double_removeallmatches} + * - @functionname{linear_containers_function_queue_create} + * - @functionname{linear_containers_function_queue_count} + * - @functionname{linear_containers_function_queue_isempty} + * - @functionname{linear_containers_function_queue_peek} + * - @functionname{linear_containers_function_queue_enqueue} + * - @functionname{linear_containers_function_queue_dequeue} + * - @functionname{linear_containers_function_queue_remove} + * - @functionname{linear_containers_function_queue_removeall} + * - @functionname{linear_containers_function_queue_removeallmatches} + */ + +/** + * @functionpage{IotLink_IsLinked,linear_containers,link_islinked} + * @functionpage{IotListDouble_Create,linear_containers,list_double_create} + * @functionpage{IotListDouble_Count,linear_containers,list_double_count} + * @functionpage{IotListDouble_IsEmpty,linear_containers,list_double_isempty} + * @functionpage{IotListDouble_PeekHead,linear_containers,list_double_peekhead} + * @functionpage{IotListDouble_PeekTail,linear_containers,list_double_peektail} + * @functionpage{IotListDouble_InsertHead,linear_containers,list_double_inserthead} + * @functionpage{IotListDouble_InsertTail,linear_containers,list_double_inserttail} + * @functionpage{IotListDouble_InsertBefore,linear_containers,list_double_insertbefore} + * @functionpage{IotListDouble_InsertAfter,linear_containers,list_double_insertafter} + * @functionpage{IotListDouble_InsertSorted,linear_containers,list_double_insertsorted} + * @functionpage{IotListDouble_Remove,linear_containers,list_double_remove} + * @functionpage{IotListDouble_RemoveHead,linear_containers,list_double_removehead} + * @functionpage{IotListDouble_RemoveTail,linear_containers,list_double_removetail} + * @functionpage{IotListDouble_RemoveAll,linear_containers,list_double_removeall} + * @functionpage{IotListDouble_FindFirstMatch,linear_containers,list_double_findfirstmatch} + * @functionpage{IotListDouble_RemoveFirstMatch,linear_containers,list_double_removefirstmatch} + * @functionpage{IotListDouble_RemoveAllMatches,linear_containers,list_double_removeallmatches} + * @functionpage{IotQueue_Create,linear_containers,queue_create} + * @functionpage{IotQueue_Count,linear_containers,queue_count} + * @functionpage{IotQueue_IsEmpty,linear_containers,queue_isempty} + * @functionpage{IotQueue_Peek,linear_containers,queue_peek} + * @functionpage{IotQueue_Enqueue,linear_containers,queue_enqueue} + * @functionpage{IotQueue_Dequeue,linear_containers,queue_dequeue} + * @functionpage{IotQueue_Remove,linear_containers,queue_remove} + * @functionpage{IotQueue_RemoveAll,linear_containers,queue_removeall} + * @functionpage{IotQueue_RemoveAllMatches,linear_containers,queue_removeallmatches} + */ + +/** + * @brief Check if an #IotLink_t is linked in a list or queue. + * + * @param[in] pLink The link to check. + * + * @return `true` if `pCurrent` is linked in a list or queue; `false` otherwise. + */ +/* @[declare_linear_containers_link_islinked] */ +static inline bool IotLink_IsLinked( const IotLink_t * const pLink ) +/* @[declare_linear_containers_link_islinked] */ +{ + bool isLinked = false; + + if( pLink != NULL ) + { + isLinked = ( pLink->pNext != NULL ) && ( pLink->pPrevious != NULL ); + } + + return isLinked; +} + +/** + * @brief Create a new doubly-linked list. + * + * This function initializes a new doubly-linked list. It must be called on an + * uninitialized #IotListDouble_t before calling any other doubly-linked list + * function. This function must not be called on an already-initialized + * #IotListDouble_t. + * + * This function will not fail. The function @ref linear_containers_function_list_double_removeall + * may be called to destroy a list. + * + * @param[in] pList Pointer to the memory that will hold the new doubly-linked list. + */ +/* @[declare_linear_containers_list_double_create] */ +static inline void IotListDouble_Create( IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_create] */ +{ + /* This function must not be called with a NULL parameter. */ + IotContainers_Assert( pList != NULL ); + + /* An empty list is a link pointing to itself. */ + pList->pPrevious = pList; + pList->pNext = pList; +} + +/** + * @brief Return the number of elements contained in an #IotListDouble_t. + * + * @param[in] pList The doubly-linked list with the elements to count. + * + * @return The number of elements in the doubly-linked list. + */ +/* @[declare_linear_containers_list_double_count] */ +static inline size_t IotListDouble_Count( const IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_count] */ +{ + size_t count = 0; + + if( pList != NULL ) + { + /* Get the list head. */ + const IotLink_t * pCurrent = pList->pNext; + + /* Iterate through the list to count the elements. */ + while( pCurrent != pList ) + { + count++; + pCurrent = pCurrent->pNext; + } + } + + return count; +} + +/** + * @brief Check if a doubly-linked list is empty. + * + * @param[in] pList The doubly-linked list to check. + * + * @return `true` if the list is empty; `false` otherwise. + */ +/* @[declare_linear_containers_list_double_isempty] */ +static inline bool IotListDouble_IsEmpty( const IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_isempty] */ +{ + /* An empty list is NULL link, or a link pointing to itself. */ + return( ( pList == NULL ) || ( pList->pNext == pList ) ); +} + +/** + * @brief Return an #IotLink_t representing the first element in a doubly-linked list + * without removing it. + * + * @param[in] pList The list to peek. + * + * @return Pointer to an #IotLink_t representing the element at the head of the + * list; `NULL` if the list is empty. The macro #IotLink_Container may be used to + * determine the address of the link's container. + */ +/* @[declare_linear_containers_list_double_peekhead] */ +static inline IotLink_t * IotListDouble_PeekHead( const IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_peekhead] */ +{ + IotLink_t * pHead = NULL; + + if( pList != NULL ) + { + if( IotListDouble_IsEmpty( pList ) == false ) + { + pHead = pList->pNext; + } + } + + return pHead; +} + +/** + * @brief Return an #IotLink_t representing the last element in a doubly-linked + * list without removing it. + * + * @param[in] pList The list to peek. + * + * @return Pointer to an #IotLink_t representing the element at the tail of the + * list; `NULL` if the list is empty. The macro #IotLink_Container may be used to + * determine the address of the link's container. + */ +/* @[declare_linear_containers_list_double_peektail] */ +static inline IotLink_t * IotListDouble_PeekTail( const IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_peektail] */ +{ + IotLink_t * pTail = NULL; + + if( pList != NULL ) + { + if( IotListDouble_IsEmpty( pList ) == false ) + { + pTail = pList->pPrevious; + } + } + + return pTail; +} + +/** + * @brief Insert an element at the head of a doubly-linked list. + * + * @param[in] pList The doubly-linked list that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + */ +/* @[declare_linear_containers_list_double_inserthead] */ +static inline void IotListDouble_InsertHead( IotListDouble_t * const pList, + IotLink_t * const pLink ) +/* @[declare_linear_containers_list_double_inserthead] */ +{ + /* This function must not be called with NULL parameters. */ + IotContainers_Assert( pList != NULL ); + IotContainers_Assert( pLink != NULL ); + + /* Save current list head. */ + IotLink_t * pHead = pList->pNext; + + /* Place new element before list head. */ + pLink->pNext = pHead; + pLink->pPrevious = pList; + + /* Assign new list head. */ + pHead->pPrevious = pLink; + pList->pNext = pLink; +} + +/** + * @brief Insert an element at the tail of a doubly-linked list. + * + * @param[in] pList The double-linked list that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + */ +/* @[declare_linear_containers_list_double_inserttail] */ +static inline void IotListDouble_InsertTail( IotListDouble_t * const pList, + IotLink_t * const pLink ) +/* @[declare_linear_containers_list_double_inserttail] */ +{ + /* This function must not be called with NULL parameters. */ + IotContainers_Assert( pList != NULL ); + IotContainers_Assert( pLink != NULL ); + + /* Save current list tail. */ + IotLink_t * pTail = pList->pPrevious; + + pLink->pNext = pList; + pLink->pPrevious = pTail; + + pList->pPrevious = pLink; + pTail->pNext = pLink; +} + +/** + * @brief Insert an element before another element in a doubly-linked list. + * + * @param[in] pElement The new element will be placed before this element. + * @param[in] pLink Pointer to the new element's link member. + */ +/* @[declare_linear_containers_list_double_insertbefore] */ +static inline void IotListDouble_InsertBefore( IotLink_t * const pElement, + IotLink_t * const pLink ) +/* @[declare_linear_containers_list_double_insertbefore] */ +{ + IotListDouble_InsertTail( pElement, pLink ); +} + +/** + * @brief Insert an element after another element in a doubly-linked list. + * + * @param[in] pElement The new element will be placed after this element. + * @param[in] pLink Pointer to the new element's link member. + */ +/* @[declare_linear_containers_list_double_insertafter] */ +static inline void IotListDouble_InsertAfter( IotLink_t * const pElement, + IotLink_t * const pLink ) +/* @[declare_linear_containers_list_double_insertafter] */ +{ + IotListDouble_InsertHead( pElement, pLink ); +} + +/** + * @brief Insert an element in a sorted doubly-linked list. + * + * Places an element into a list by sorting it into order. The function + * `compare` is used to determine where to place the new element. + * + * @param[in] pList The list that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + * @param[in] compare Determines the order of the list. Returns a negative + * value if its first argument is less than its second argument; returns + * zero if its first argument is equal to its second argument; returns a + * positive value if its first argument is greater than its second argument. + * The parameters to this function are #IotLink_t, so the macro #IotLink_Container + * may be used to determine the address of the link's container. + */ +/* @[declare_linear_containers_list_double_insertsorted] */ +static inline void IotListDouble_InsertSorted( IotListDouble_t * const pList, + IotLink_t * const pLink, + int ( *compare )( const IotLink_t * const, const IotLink_t * const ) ) +/* @[declare_linear_containers_list_double_insertsorted] */ +{ + /* This function must not be called with NULL parameters. */ + IotContainers_Assert( pList != NULL ); + IotContainers_Assert( pLink != NULL ); + IotContainers_Assert( compare != NULL ); + + /* Insert at head for empty list. */ + if( IotListDouble_IsEmpty( pList ) == true ) + { + IotListDouble_InsertHead( pList, pLink ); + } + else + { + bool inserted = false; + IotLink_t * pCurrent = pList->pNext; + + /* Iterate through the list to find the correct position. */ + while( pCurrent != pList ) + { + /* Comparing for '<' preserves the order of insertion. */ + if( compare( pLink, pCurrent ) < 0 ) + { + IotListDouble_InsertBefore( pCurrent, pLink ); + inserted = true; + + break; + } + + pCurrent = pCurrent->pNext; + } + + /* New element is greater than all elements in list. Insert at tail. */ + if( inserted == false ) + { + IotListDouble_InsertTail( pList, pLink ); + } + } +} + +/** + * @brief Remove a single element from a doubly-linked list. + * + * @param[in] pLink The element to remove. + */ +/* @[declare_linear_containers_list_double_remove] */ +static inline void IotListDouble_Remove( IotLink_t * const pLink ) +/* @[declare_linear_containers_list_double_remove] */ +{ + /* This function must not be called with a NULL parameter. */ + IotContainers_Assert( pLink != NULL ); + + /* This function must be called on a linked element. */ + IotContainers_Assert( IotLink_IsLinked( pLink ) == true ); + + pLink->pPrevious->pNext = pLink->pNext; + pLink->pNext->pPrevious = pLink->pPrevious; + pLink->pPrevious = NULL; + pLink->pNext = NULL; +} + +/** + * @brief Remove the element at the head of a doubly-linked list. + * + * @param[in] pList The doubly-linked list that holds the element to remove. + * + * @return Pointer to an #IotLink_t representing the removed list head; `NULL` + * if the list is empty. The macro #IotLink_Container may be used to determine + * the address of the link's container. + */ +/* @[declare_linear_containers_list_double_removehead] */ +static inline IotLink_t * IotListDouble_RemoveHead( IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_removehead] */ +{ + IotLink_t * pHead = NULL; + + if( IotListDouble_IsEmpty( pList ) == false ) + { + pHead = pList->pNext; + IotListDouble_Remove( pHead ); + } + + return pHead; +} + +/** + * @brief Remove the element at the tail of a doubly-linked list. + * + * @param[in] pList The doubly-linked list that holds the element to remove. + * + * @return Pointer to an #IotLink_t representing the removed list tail; `NULL` + * if the list is empty. The macro #IotLink_Container may be used to determine + * the address of the link's container. + */ +/* @[declare_linear_containers_list_double_removetail] */ +static inline IotLink_t * IotListDouble_RemoveTail( IotListDouble_t * const pList ) +/* @[declare_linear_containers_list_double_removetail] */ +{ + IotLink_t * pTail = NULL; + + if( IotListDouble_IsEmpty( pList ) == false ) + { + pTail = pList->pPrevious; + IotListDouble_Remove( pTail ); + } + + return pTail; +} + +/** + * @brief Remove all elements in a doubly-linked list. + * + * @param[in] pList The list to empty. + * @param[in] freeElement A function to free memory used by each removed list + * element. Optional; pass `NULL` to ignore. + * @param[in] linkOffset Offset in bytes of a link member in its container, used + * to calculate the pointer to pass to `freeElement`. This value should be calculated + * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` + * or its value is `0`. + */ +/* @[declare_linear_containers_list_double_removeall] */ +static inline void IotListDouble_RemoveAll( IotListDouble_t * const pList, + void ( *freeElement )( void * ), + size_t linkOffset ) +/* @[declare_linear_containers_list_double_removeall] */ +{ + /* This function must not be called with a NULL pList parameter. */ + IotContainers_Assert( pList != NULL ); + + /* Get the list head. */ + IotLink_t * pCurrent = pList->pNext; + + /* Iterate through the list and remove all elements. */ + while( pCurrent != pList ) + { + /* Save a pointer to the next list element. */ + IotLink_t * pNext = pCurrent->pNext; + + /* Remove and free the current list element. */ + IotListDouble_Remove( pCurrent ); + + if( freeElement != NULL ) + { + freeElement( ( ( uint8_t * ) pCurrent ) - linkOffset ); + } + + /* Move the iterating pointer to the next list element. */ + pCurrent = pNext; + } +} + +/** + * @brief Search a doubly-linked list for the first matching element. + * + * If a match is found, the matching element is not removed from the list. + * See @ref linear_containers_function_list_double_removefirstmatch for the function + * that searches and removes. + * + * @param[in] pList The doubly-linked list to search. + * @param[in] pStartPoint An element in `pList`. Only elements between this one and + * the list tail are checked. Pass `NULL` to search from the beginning of the list. + * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to + * search using the address `pMatch`, i.e. `element == pMatch`. + * @param[in] pMatch If `isMatch` is `NULL`, each element in the list is compared + * to this address to find a match. Otherwise, it is passed as the second argument + * to `isMatch`. + * + * @return Pointer to an #IotLink_t representing the first matched element; `NULL` + * if no match is found. The macro #IotLink_Container may be used to determine the + * address of the link's container. + */ +/* @[declare_linear_containers_list_double_findfirstmatch] */ +static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * const pList, + const IotLink_t * const pStartPoint, + bool ( *isMatch )( const IotLink_t *, void * ), + void * pMatch ) +/* @[declare_linear_containers_list_double_findfirstmatch] */ +{ + /* The const must be cast away to match this function's return value. Nevertheless, + * this function will respect the const-ness of pStartPoint. */ + IotLink_t * pCurrent = ( IotLink_t * ) pStartPoint; + + /* This function must not be called with a NULL pList parameter. */ + IotContainers_Assert( pList != NULL ); + + /* Search starting from list head if no start point is given. */ + if( pStartPoint == NULL ) + { + pCurrent = pList->pNext; + } + + /* Iterate through the list to search for matches. */ + while( pCurrent != pList ) + { + /* Call isMatch if provided. Otherwise, compare pointers. */ + if( isMatch != NULL ) + { + if( isMatch( pCurrent, pMatch ) == true ) + { + return pCurrent; + } + } + else + { + if( pCurrent == pMatch ) + { + return pCurrent; + } + } + + pCurrent = pCurrent->pNext; + } + + /* No match found, return NULL. */ + return NULL; +} + +/** + * @brief Search a doubly-linked list for the first matching element and remove + * it. + * + * An #IotLink_t may be passed as `pList` to start searching after the head of a + * doubly-linked list. + * + * @param[in] pList The doubly-linked list to search. + * @param[in] pStartPoint An element in `pList`. Only elements between this one and + * the list tail are checked. Pass `NULL` to search from the beginning of the list. + * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to + * search using the address `pMatch`, i.e. `element == pMatch`. + * @param[in] pMatch If `isMatch` is `NULL`, each element in the list is compared + * to this address to find a match. Otherwise, it is passed as the second argument + * to `isMatch`. + * + * @return Pointer to an #IotLink_t representing the matched and removed element; + * `NULL` if no match is found. The macro #IotLink_Container may be used to determine + * the address of the link's container. + */ +/* @[declare_linear_containers_list_double_removefirstmatch] */ +static inline IotLink_t * IotListDouble_RemoveFirstMatch( IotListDouble_t * const pList, + const IotLink_t * const pStartPoint, + bool ( *isMatch )( const IotLink_t *, void * ), + void * pMatch ) +/* @[declare_linear_containers_list_double_removefirstmatch] */ +{ + IotLink_t * pMatchedElement = IotListDouble_FindFirstMatch( pList, + pStartPoint, + isMatch, + pMatch ); + + if( pMatchedElement != NULL ) + { + IotListDouble_Remove( pMatchedElement ); + } + + return pMatchedElement; +} + +/** + * @brief Remove all matching elements from a doubly-linked list. + * + * @param[in] pList The doubly-linked list to search. + * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to + * search using the address `pMatch`, i.e. `element == pMatch`. + * @param[in] pMatch If `isMatch` is `NULL`, each element in the list is compared + * to this address to find a match. Otherwise, it is passed as the second argument + * to `isMatch`. + * @param[in] freeElement A function to free memory used by each removed list + * element. Optional; pass `NULL` to ignore. + * @param[in] linkOffset Offset in bytes of a link member in its container, used + * to calculate the pointer to pass to `freeElement`. This value should be calculated + * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` + * or its value is `0`. + */ +/* @[declare_linear_containers_list_double_removeallmatches] */ +static inline void IotListDouble_RemoveAllMatches( IotListDouble_t * const pList, + bool ( *isMatch )( const IotLink_t *, void * ), + void * pMatch, + void ( *freeElement )( void * ), + size_t linkOffset ) +/* @[declare_linear_containers_list_double_removeallmatches] */ +{ + IotLink_t * pMatchedElement = NULL, * pNextElement = NULL; + + /* Search the list for all matching elements. */ + do + { + pMatchedElement = IotListDouble_FindFirstMatch( pList, + pMatchedElement, + isMatch, + pMatch ); + + if( pMatchedElement != NULL ) + { + /* Save pointer to next element. */ + pNextElement = pMatchedElement->pNext; + + /* Match found; remove and free. */ + IotListDouble_Remove( pMatchedElement ); + + if( freeElement != NULL ) + { + freeElement( ( ( uint8_t * ) pMatchedElement ) - linkOffset ); + } + + /* Continue search from next element. */ + pMatchedElement = pNextElement; + } + } while( pMatchedElement != NULL ); +} + +/** + * @brief Create a new queue. + * + * This function initializes a new queue. It must be called on an uninitialized + * #IotQueue_t before calling any other queue function. This function must not be + * called on an already-initialized #IotQueue_t. + * + * This function will not fail. + * + * @param[in] pQueue Pointer to the memory that will hold the new queue. + */ +/* @[declare_linear_containers_queue_create] */ +static inline void IotQueue_Create( IotQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_create] */ +{ + IotListDouble_Create( pQueue ); +} + +/** + * @brief Return the number of elements contained in an #IotQueue_t. + * + * @param[in] pQueue The queue with the elements to count. + * + * @return The number of items elements in the queue. + */ +/* @[declare_linear_containers_queue_count] */ +static inline size_t IotQueue_Count( const IotQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_count] */ +{ + return IotListDouble_Count( pQueue ); +} + +/** + * @brief Check if a queue is empty. + * + * @param[in] pQueue The queue to check. + * + * @return `true` if the queue is empty; `false` otherwise. + * + */ +/* @[declare_linear_containers_queue_isempty] */ +static inline bool IotQueue_IsEmpty( const IotQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_isempty] */ +{ + return IotListDouble_IsEmpty( pQueue ); +} + +/** + * @brief Return an #IotLink_t representing the element at the front of the queue + * without removing it. + * + * @param[in] pQueue The queue to peek. + * + * @return Pointer to an #IotLink_t representing the element at the head of the + * queue; `NULL` if the queue is empty. The macro #IotLink_Container may be used + * to determine the address of the link's container. + */ +/* @[declare_linear_containers_queue_peek] */ +static inline IotLink_t * IotQueue_Peek( const IotQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_peek] */ +{ + return IotListDouble_PeekHead( pQueue ); +} + +/** + * @brief Add an element to the queue. + * + * @param[in] pQueue The queue that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + */ +/* @[declare_linear_containers_queue_enqueue] */ +static inline void IotQueue_Enqueue( IotQueue_t * const pQueue, + IotLink_t * const pLink ) +/* @[declare_linear_containers_queue_enqueue] */ +{ + IotListDouble_InsertTail( pQueue, pLink ); +} + +/** + * @brief Remove the oldest element in the queue. + * + * @param[in] pQueue The queue that holds the element to remove. + * + * @return Pointer to an #IotLink_t representing the removed queue element; `NULL` + * if the queue is empty. The macro #IotLink_Container may be used to determine + * the address of the link's container. + */ +/* @[declare_linear_containers_queue_dequeue] */ +static inline IotLink_t * IotQueue_Dequeue( IotQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_dequeue] */ +{ + return IotListDouble_RemoveHead( pQueue ); +} + +/** + * @brief Remove a single element from a queue. + * + * @param[in] pLink The element to remove. + */ +/* @[declare_linear_containers_queue_remove] */ +static inline void IotQueue_Remove( IotLink_t * const pLink ) +/* @[declare_linear_containers_queue_remove] */ +{ + IotListDouble_Remove( pLink ); +} + +/** + * @brief Remove all elements in a queue. + * + * @param[in] pQueue The queue to empty. + * @param[in] freeElement A function to free memory used by each removed queue + * element. Optional; pass `NULL` to ignore. + * @param[in] linkOffset Offset in bytes of a link member in its container, used + * to calculate the pointer to pass to `freeElement`. This value should be calculated + * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` + * or its value is `0`. + */ +/* @[declare_linear_containers_queue_removeall] */ +static inline void IotQueue_RemoveAll( IotQueue_t * const pQueue, + void ( * freeElement )( void * ), + size_t linkOffset ) +/* @[declare_linear_containers_queue_removeall] */ +{ + return IotListDouble_RemoveAll( pQueue, freeElement, linkOffset ); +} + +/** + * @brief Remove all matching elements from a queue. + * + * @param[in] pQueue The queue to search. + * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to + * search using the address `pMatch`, i.e. `element == pMatch`. + * @param[in] pMatch If `isMatch` is `NULL`, each element in the queue is compared + * to this address to find a match. Otherwise, it is passed as the second argument + * to `isMatch`. + * @param[in] freeElement A function to free memory used by each removed queue + * element. Optional; pass `NULL` to ignore. + * @param[in] linkOffset Offset in bytes of a link member in its container, used + * to calculate the pointer to pass to `freeElement`. This value should be calculated + * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` + * or its value is `0`. + */ +/* @[declare_linear_containers_queue_removeallmatches] */ +static inline void IotQueue_RemoveAllMatches( IotQueue_t * const pQueue, + bool( *isMatch )( const IotLink_t *, void * ), + void * pMatch, + void( *freeElement )( void * ), + size_t linkOffset ) +/* @[declare_linear_containers_queue_removeallmatches] */ +{ + IotListDouble_RemoveAllMatches( pQueue, isMatch, pMatch, freeElement, linkOffset ); +} + +#endif /* _IOT_LINEAR_CONTAINERS_H_ */ diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index 4d0a42ef7b..251726572e 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -36,8 +36,8 @@ /* MQTT include. */ #include "aws_iot_mqtt.h" -/* Queue include. */ -#include "aws_iot_queue.h" +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" /* Platform clock include. */ #include "platform/aws_iot_clock.h" @@ -226,11 +226,8 @@ #ifndef AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES #define AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) #endif -#ifndef AWS_IOT_MQTT_MAX_CALLBACK_THREADS - #define AWS_IOT_MQTT_MAX_CALLBACK_THREADS ( 2 ) -#endif -#ifndef AWS_IOT_MQTT_MAX_SEND_THREADS - #define AWS_IOT_MQTT_MAX_SEND_THREADS ( 1 ) +#ifndef AWS_IOT_MQTT_MAX_THREADS + #define AWS_IOT_MQTT_MAX_THREADS ( 2 ) #endif #ifndef AWS_IOT_MQTT_TEST #define AWS_IOT_MQTT_TEST ( 0 ) @@ -281,21 +278,6 @@ /*---------------------- MQTT internal data structures ----------------------*/ -/** - * @brief The offset of `link` in #_mqttSubscription_t. - */ -#define _SUBSCRIPTION_LINK_OFFSET AwsIotLink_Offset( _mqttSubscription_t, link ) - - /** - * @brief The offset of `link` in #_mqttTimerEvent_t. - */ -#define _TIMER_EVENT_LINK_OFFSET AwsIotLink_Offset( _mqttTimerEvent_t, link ) - -/** - * @brief The offset of `link` in #_mqttOperation_t. - */ -#define _OPERATION_LINK_OFFSET AwsIotLink_Offset( _mqttOperation_t, link ) - /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -313,7 +295,7 @@ struct _mqttTimerEvent; */ typedef struct _mqttSubscription { - AwsIotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ int references; /**< @brief How many subscription callbacks are using this subscription. */ @@ -348,11 +330,12 @@ typedef struct _mqttConnection bool errorOccurred; /**< @brief Tracks if a protocol violation or other error occurred. */ bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ AwsIotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ - AwsIotList_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + AwsIotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the #_mqttConnection_t.subscriptionList. */ - AwsIotMutex_t mutex; /**< @brief Prevents concurrent threads from modifying connection data. */ AwsIotTimer_t timer; /**< @brief Expires when a timer event should be processed. */ - AwsIotList_t timerEventList; /**< @brief List of active timer events. */ + AwsIotMutex_t timerMutex; /**< @brief Prevents concurrent access from timer thread and protects timer event list. */ + IotListDouble_t timerEventList; /**< @brief List of active timer events. */ uint16_t keepAliveSeconds; /**< @brief Keep-alive interval. */ struct _mqttOperation * pPingreqOperation; /**< @brief PINGREQ operation. Only used if keep-alive is active. */ struct _mqttTimerEvent * pKeepAliveEvent; /**< @brief When to process a keep-alive. Only used if keep-alive is active. */ @@ -367,7 +350,7 @@ typedef struct _mqttConnection typedef struct _mqttOperation { /* Pointers to neighboring queue elements. */ - AwsIotLink_t link; /**< @brief Queue link member. */ + IotLink_t link; /**< @brief List link member. */ bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ @@ -416,7 +399,7 @@ typedef struct _mqttOperation */ typedef struct _mqttTimerEvent { - AwsIotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ uint64_t expirationTime; /**< @brief When this event should be processed. */ struct _mqttOperation * pOperation; /**< @brief The MQTT operation associated with this event. */ @@ -437,10 +420,13 @@ typedef struct _mqttTimerEvent }; } _mqttTimerEvent_t; -/* Declarations of the MQTT queues for internal MQTT files. */ -extern AwsIotQueue_t _AwsIotMqttSendQueue; -extern AwsIotQueue_t _AwsIotMqttReceiveQueue; -extern AwsIotQueue_t _AwsIotMqttCallbackQueue; +/* Declarations of the structures keeping track of MQTT operations for internal + * files. */ +extern IotQueue_t _IotMqttPendingProcessing; +extern IotListDouble_t _IotMqttPendingResponse; +extern AwsIotMutex_t _IotMqttPendingProcessingMutex; +extern AwsIotMutex_t _IotMqttPendingResponseMutex; +extern AwsIotSemaphore_t _IotMqttAvailableThreads; /*-------------------- MQTT struct validation functions ---------------------*/ @@ -777,30 +763,19 @@ void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ); /*-------------------- MQTT operation record functions ----------------------*/ -/** - * @brief Create all the MQTT operation queues. Called by @ref mqtt_function_init - * when initializing the MQTT library. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_INIT_FAILED. - */ -AwsIotMqttError_t AwsIotMqttInternal_CreateQueues( void ); - /** * @brief Compare two #_mqttTimerEvent_t by expiration time. * - * @param[in] pData1 The first #_mqttTimerEvent_t to compare. - * @param[in] pData2 The second #_mqttTimerEvent_t to compare. + * @param[in] pTimerEventLink1 The link member of the first #_mqttTimerEvent_t to compare. + * @param[in] pTimerEventLink2 The link member of the second #_mqttTimerEvent_t to compare. * * @return - * - Negative value if `pData1` is less than `pData2`. - * - Zero if `pData1` is equal to `pData2`. - * - Positive value if `pData1` is greater than `pData2`. - * - * @note The arguments of this function are of type `void*` for compatibility with - * @ref list_function_insertsorted. + * - Negative value if the first timer event is less than the second timer event. + * - Zero if the two timer events are equal. + * - Positive value if the first timer event is greater than the second timer event. */ -int AwsIotMqttInternal_TimerEventCompare( void * pData1, - void * pData2 ); +int AwsIotMqttInternal_TimerEventCompare( const IotLink_t * const pTimerEventLink1, + const IotLink_t * const pTimerEventLink2 ); /** * @brief Create a record for a new in-progress MQTT operation. @@ -820,38 +795,42 @@ AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const * the operation completes. * * @param[in] pData The operation which completed. This parameter is of type - * `void*` for compatibility with @ref queue_function_removeallmatches. + * `void*` to match the signature of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ void AwsIotMqttInternal_DestroyOperation( void * pData ); /** - * @brief Notify of a completed MQTT operation. + * @brief Enqueue an MQTT operation for processing. * - * @param[in] pOperation The MQTT operation which completed. + * @param[in] pOperation The MQTT operation to enqueue. * - * Depending on the parameters passed to a user-facing MQTT function, the - * notification will cause @ref mqtt_function_wait to return or invoke a - * user-provided callback. + * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. */ -void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ); +AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation ); /** - * @brief Search a queue of in-progress MQTT operations using an operation name - * and packet identifier. Removes the operation from the queue if found. + * @brief Search the list of MQTT operations pending responses using an operation + * name and packet identifier. Removes a matching operation from the list if found. * - * Allows operations to be removed from the middle of the queue. - * @param[in] pQueue Which queue to search. * @param[in] operation The operation type to look for. - * @param[in] pPacketIdentifier A packet identifier to match. Pass NULL to ignore. - * @param[out] pOutput Set to point to the found operation. + * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore. + * + * @return Pointer to any matching operation; `NULL` if no match was found. + */ +_mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t operation, + const uint16_t * const pPacketIdentifier ); + +/** + * @brief Notify of a completed MQTT operation. + * + * @param[in] pOperation The MQTT operation which completed. * - * @return #AWS_IOT_MQTT_SUCCESS if a corresponding operation was found, otherwise - * #AWS_IOT_MQTT_BAD_PARAMETER if not found. + * Depending on the parameters passed to a user-facing MQTT function, the + * notification will cause @ref mqtt_function_wait to return or invoke a + * user-provided callback. */ -AwsIotMqttError_t AwsIotMqttInternal_OperationFind( AwsIotQueue_t * const pQueue, - AwsIotMqttOperationType_t operation, - const uint16_t * const pPacketIdentifier, - _mqttOperation_t ** const pOutput ); +void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ); /*------------------- Subscription management functions ---------------------*/ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 63c56abdf7..2e2bc45535 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -36,8 +36,8 @@ /* Shadow include. */ #include "aws_iot_shadow.h" -/* Queue include. */ -#include "aws_iot_queue.h" +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" /** * @def AwsIotShadow_Assert( expression ) @@ -304,16 +304,6 @@ /*----------------------- Shadow internal data types ------------------------*/ -/** - * @brief The offset of `link` in #_shadowOperation_t. - */ -#define _SHADOW_OPERATION_LINK_OFFSET AwsIotLink_Offset( _shadowOperation_t, link ) - -/** - * @brief The offset of `link` in #_shadowSubscription_t. - */ -#define _SHADOW_SUBSCRIPTION_LINK_OFFSET AwsIotLink_Offset( _shadowSubscription_t, link ) - /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -385,7 +375,7 @@ typedef enum _shadowOperationStatus */ typedef struct _shadowOperation { - AwsIotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ /* Basic operation information. */ _shadowOperationType_t type; /**< @brief Operation type. */ @@ -434,7 +424,7 @@ typedef struct _shadowOperation */ typedef struct _shadowSubscription { - AwsIotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ int references[ _SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ AwsIotShadowCallbackInfo_t callbacks[ _SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ @@ -459,8 +449,10 @@ typedef struct _shadowSubscription /* Declarations of variables for internal Shadow files. */ extern uint64_t _AwsIotShadowMqttTimeoutMs; -extern AwsIotList_t _AwsIotShadowPendingOperations; -extern AwsIotList_t _AwsIotShadowSubscriptions; +extern IotListDouble_t _AwsIotShadowPendingOperations; +extern IotListDouble_t _AwsIotShadowSubscriptions; +extern AwsIotMutex_t _AwsIotShadowPendingOperationsMutex; +extern AwsIotMutex_t _AwsIotShadowSubscriptionsMutex; /*----------------------- Shadow operation functions ------------------------*/ @@ -484,7 +476,8 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** * the operation completes. * * @param[in] pData The operation which completed. This parameter is of type - * `void*` for compatibility with @ref list_function_removeallmatches. + * `void*` to match the signature of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ void AwsIotShadowInternal_DestroyOperation( void * pData ); @@ -580,7 +573,8 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub * @brief Free resources used for a Shadow subscription object. * * @param[in] pData The subscription object to destroy. This parameter is of type - * `void*` for compatibility with @ref list_function_removeallmatches. + * `void*` to match the signature of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ void AwsIotShadowInternal_DestroySubscription( void * pData ); diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 7990a2d965..c637c9543c 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,15 +1,14 @@ # Common libraries source files. add_library( awsiotcommon SHARED aws_iot_json_utils.c - aws_iot_logging.c - aws_iot_queue.c ) + aws_iot_logging.c ) # Library version. set_target_properties( awsiotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) # Library public headers. set_target_properties( awsiotcommon PROPERTIES PUBLIC_HEADER - "../../include/aws_iot_json_utils.h;../../include/aws_iot_logging_setup.h;../../include/aws_iot_queue.h" ) + "../../include/aws_iot_json_utils.h;../../include/aws_iot_logging_setup.h" ) # Library private headers. set_target_properties( awsiotcommon PROPERTIES PRIVATE_HEADER diff --git a/lib/source/common/aws_iot_queue.c b/lib/source/common/aws_iot_queue.c deleted file mode 100644 index 7ee9647d3f..0000000000 --- a/lib/source/common/aws_iot_queue.c +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_queue.c - * @brief Implements the functions in aws_iot_queue.h - */ - -/* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include -#include - -/* Queue include. */ -#include "aws_iot_queue.h" - -/** - * @def AwsIotQueue_Assert( expression ) - * @brief Assertion macro for the queue library. - * - * Set @ref AWS_IOT_QUEUE_ENABLE_ASSERTS to `1` to enable assertions in the queue - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if AWS_IOT_QUEUE_ENABLE_ASSERTS == 1 - #ifndef AwsIotQueue_Assert - #include - #define AwsIotQueue_Assert( expression ) assert( expression ) - #endif -#else - #define AwsIotQueue_Assert( expression ) -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Remove a single element from the queue. - * - * This function should be called within a critical section. - * @param[in] pQueue The queue holding the element to remove. - * @param[in] pLink The element to remove. - */ -static void _queueRemove( AwsIotQueue_t * const pQueue, - AwsIotLink_t * const pLink ); - -/*-----------------------------------------------------------*/ - -static void _queueRemove( AwsIotQueue_t * const pQueue, - AwsIotLink_t * const pLink ) -{ - /* This function should not be called on an empty queue. */ - AwsIotQueue_Assert( pQueue->pHead != NULL && pQueue->pTail != NULL ); - - /* Check if the head of the queue is being removed. */ - if( pLink == pQueue->pHead ) - { - /* Queue head should have NULL previous pointer. */ - AwsIotQueue_Assert( pQueue->pHead->pPrevious == NULL ); - - /* Move the queue head. */ - pQueue->pHead = pQueue->pHead->pNext; - - /* Check if the queue is now empty. */ - if( pQueue->pHead == NULL ) - { - /* If the queue is empty, set the tail pointer to NULL also. */ - pQueue->pTail = NULL; - } - else - { - /* If the queue is not empty, set the new head's previous pointer - * to NULL. */ - pQueue->pHead->pPrevious = NULL; - } - } - /* Check if the tail of the queue is being removed. */ - else if( pLink == pQueue->pTail ) - { - /* Queue tail should have NULL next pointer. */ - AwsIotQueue_Assert( pQueue->pTail->pNext == NULL ); - - /* Move the queue tail. */ - pQueue->pTail = pQueue->pTail->pPrevious; - - /* Check if the queue is now empty. */ - if( pQueue->pTail == NULL ) - { - /* If the queue is empty, set the head pointer to NULL also. */ - pQueue->pHead = NULL; - } - else - { - /* If the queue is not empty, set the new tail's next pointer to - * NULL. */ - pQueue->pTail->pNext = NULL; - } - } - /* Otherwise, remove from the middle of the queue. */ - else - { - /* Check that pointers in the next and previous elements are correctly - * set. */ - AwsIotQueue_Assert( pLink->pPrevious->pNext == pLink ); - AwsIotQueue_Assert( pLink->pNext->pPrevious == pLink ); - - /* Reassign the pointers in the next and previous elements. */ - pLink->pPrevious->pNext = pLink->pNext; - pLink->pNext->pPrevious = pLink->pPrevious; - } - - /* Clear the next and previous pointers of pLink. */ - pLink->pNext = NULL; - pLink->pPrevious = NULL; -} - -/*-----------------------------------------------------------*/ - -bool AwsIotQueue_Create( AwsIotQueue_t * const pQueue, - const AwsIotQueueNotifyParams_t * const pNotifyParams, - uint32_t maxNotifyThreads ) -{ - /* For a notification queue, at least 1 notify thread must be allowed. */ - if( ( pNotifyParams != NULL ) && ( maxNotifyThreads == 0 ) ) - { - return false; - } - - /* Clear the queue data. This sets all pointers to NULL. */ - ( void ) memset( pQueue, 0x00, sizeof( AwsIotQueue_t ) ); - - /* Create the queue mutex. */ - if( AwsIotMutex_Create( &( pQueue->mutex ) ) == false ) - { - return false; - } - - /* If a notification routine is set, create the thread count semaphore. */ - if( ( pNotifyParams != NULL ) && ( pNotifyParams->notifyRoutine != NULL ) ) - { - if( AwsIotSemaphore_Create( &( pQueue->notifyThreadCount ), - maxNotifyThreads, - maxNotifyThreads ) == false ) - { - AwsIotMutex_Destroy( &( pQueue->mutex ) ); - - return false; - } - - /* Copy the notify params. */ - pQueue->params = *( pNotifyParams ); - } - - return true; -} - -/*-----------------------------------------------------------*/ - -void AwsIotQueue_Destroy( AwsIotQueue_t * const pQueue ) -{ - /* Clean up notification routine. */ - if( pQueue->params.notifyRoutine != NULL ) - { - AwsIotMutex_Lock( &( pQueue->mutex ) ); - - /* Decrement the count of available notification threads to 0, ensuring - * no more notification threads may be created. This also waits for - * termination of any existing notification threads. */ - while( AwsIotSemaphore_GetCount( &( pQueue->notifyThreadCount ) ) > 0 ) - { - AwsIotMutex_Unlock( &( pQueue->mutex ) ); - AwsIotSemaphore_Wait( &( pQueue->notifyThreadCount ) ); - AwsIotMutex_Lock( &( pQueue->mutex ) ); - } - - AwsIotSemaphore_Destroy( &( pQueue->notifyThreadCount ) ); - AwsIotMutex_Unlock( &( pQueue->mutex ) ); - } - - /* Destroy queue mutex. */ - AwsIotMutex_Destroy( &( pQueue->mutex ) ); - - /* Clear all data in the queue. */ - ( void ) memset( pQueue, 0x00, sizeof( AwsIotQueue_t ) ); -} - -/*-----------------------------------------------------------*/ - -bool AwsIotQueue_InsertHead( AwsIotQueue_t * const pQueue, - AwsIotLink_t * const pLink ) -{ - bool status = true; - - AwsIotMutex_Lock( &( pQueue->mutex ) ); - - /* Clear the next and previous pointers of pLink. */ - pLink->pNext = NULL; - pLink->pPrevious = NULL; - - /* Check if the queue is currently empty. */ - if( pQueue->pHead == NULL ) - { - /* If the head pointer is NULL, the tail pointer should also be NULL. */ - AwsIotQueue_Assert( pQueue->pTail == NULL ); - - /* Set the head and tail pointer to the new operation. This places the - * first item in the queue. */ - pQueue->pHead = pLink; - pQueue->pTail = pLink; - } - else - { - /* The tail shouldn't be NULL when the head isn't NULL. */ - AwsIotQueue_Assert( pQueue->pTail != NULL ); - - /* If the queue is not empty, insert the new operation at the head - * of the queue. */ - pLink->pNext = pQueue->pHead; - pQueue->pHead->pPrevious = pLink; - pQueue->pHead = pLink; - } - - /* Create a notification thread if this queue has a notify routine set. */ - if( pQueue->params.notifyRoutine != NULL ) - { - if( ( AwsIotSemaphore_TryWait( &( pQueue->notifyThreadCount ) ) == true ) && - ( AwsIot_CreateDetachedThread( pQueue->params.notifyRoutine, - pQueue->params.pNotifyArgument ) == false ) ) - { - _queueRemove( pQueue, pLink ); - AwsIotSemaphore_Post( &( pQueue->notifyThreadCount ) ); - status = false; - } - } - - AwsIotMutex_Unlock( &( pQueue->mutex ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -void * AwsIotQueue_RemoveTail( AwsIotQueue_t * const pQueue, - size_t linkOffset, - bool notifyExitIfEmpty ) -{ - AwsIotLink_t * pResult = NULL; - - AwsIotMutex_Lock( &( pQueue->mutex ) ); - - /* Check if a message is available. */ - if( pQueue->pTail != NULL ) - { - /* If the queue tail isn't NULL, then head shouldn't be NULL. */ - AwsIotQueue_Assert( pQueue->pHead != NULL ); - - /* Set the pointer to the tail, then remove it from the queue. */ - pResult = pQueue->pTail; - _queueRemove( pQueue, pQueue->pTail ); - } - else - { - /* If this is an exiting notification thread, increment the number of - * available notification threads. */ - if( notifyExitIfEmpty == true ) - { - AwsIotSemaphore_Post( &( pQueue->notifyThreadCount ) ); - } - } - - AwsIotMutex_Unlock( &( pQueue->mutex ) ); - - return AwsIotLink_Container( pResult, linkOffset ); -} - -/*-----------------------------------------------------------*/ - -void * AwsIotQueue_RemoveFirstMatch( AwsIotQueue_t * const pQueue, - size_t linkOffset, - void * pArgument, - bool ( *shouldRemove )( void *, void * ) ) -{ - AwsIotLink_t * pResult = NULL; - void * pLinkContainer = NULL; - - AwsIotMutex_Lock( &( pQueue->mutex ) ); - - /* Iterate through the queue to find the first matching element. */ - for( pResult = pQueue->pTail; pResult != NULL; pResult = pResult->pPrevious ) - { - pLinkContainer = AwsIotLink_Container( pResult, linkOffset ); - - if( ( ( shouldRemove == NULL ) && ( pArgument == pLinkContainer ) ) || - ( ( shouldRemove != NULL ) && ( shouldRemove( pArgument, pLinkContainer ) == true ) ) ) - { - /* If a matching element is found, remove it from the queue. */ - _queueRemove( pQueue, pResult ); - break; - } - } - - AwsIotMutex_Unlock( &( pQueue->mutex ) ); - - /* Element found. */ - if( pResult != NULL ) - { - return pLinkContainer; - } - - /* Element not found. */ - return NULL; -} - -/*-----------------------------------------------------------*/ - -void AwsIotQueue_RemoveAllMatches( AwsIotQueue_t * const pQueue, - size_t linkOffset, - void * pArgument, - bool ( *shouldRemove )( void *, void * ), - void ( *freeElement )( void * ) ) -{ - AwsIotLink_t * i = NULL, * pCurrent = NULL; - void * pLinkContainer = NULL; - - AwsIotMutex_Lock( &( pQueue->mutex ) ); - i = pQueue->pHead; - - /* Iterate through the entire queue. Remove and free all matching elements. */ - while( i != NULL ) - { - /* Save a pointer to the current element and move the iterating pointer. */ - pCurrent = i; - i = i->pNext; - - /* Calculate address of link container. */ - pLinkContainer = AwsIotLink_Container( pCurrent, linkOffset ); - - /* Check if the current queue element should be removed. */ - if( ( shouldRemove == NULL ) || - ( shouldRemove( pArgument, pLinkContainer ) == true ) ) - { - /* Remove the matched element from the queue and free it. */ - _queueRemove( pQueue, pCurrent ); - - if( freeElement != NULL ) - { - freeElement( pLinkContainer ); - } - } - } - - AwsIotMutex_Unlock( &( pQueue->mutex ) ); -} - -/*-----------------------------------------------------------*/ - -bool AwsIotList_Create( AwsIotList_t * const pList ) -{ - /* Clear the list data. This sets the head pointer to NULL. */ - ( void ) memset( pList, 0x00, sizeof( AwsIotList_t ) ); - - /* Create the list mutex. */ - if( AwsIotMutex_Create( &( pList->mutex ) ) == false ) - { - return false; - } - - return true; -} - -/*-----------------------------------------------------------*/ - -void AwsIotList_Destroy( AwsIotList_t * const pList ) -{ - /* Destroy list mutex. */ - AwsIotMutex_Destroy( &( pList->mutex ) ); - - /* Clear all data in the list. */ - ( void ) memset( pList, 0x00, sizeof( AwsIotList_t ) ); -} - -/*-----------------------------------------------------------*/ - -void AwsIotList_InsertHead( AwsIotList_t * const pList, - AwsIotLink_t * const pLink, - size_t linkOffset ) -{ - /* Link offset is not used when asserts are off. */ - ( void ) linkOffset; - - /* Clear the next and previous pointers of pLink. */ - pLink->pNext = NULL; - pLink->pPrevious = NULL; - - /* Link container must not already be in the list. */ - AwsIotQueue_Assert( AwsIotList_FindFirstMatch( pList->pHead, - linkOffset, - AwsIotLink_Container( pLink, linkOffset ), - NULL ) == NULL ); - - /* Insert the new node at the list head. */ - if( pList->pHead == NULL ) - { - /* Set new head if list is empty. */ - pList->pHead = pLink; - } - else - { - pLink->pNext = pList->pHead; - pList->pHead->pPrevious = pLink; - pList->pHead = pLink; - } -} - -/*-----------------------------------------------------------*/ - -void AwsIotList_InsertSorted( AwsIotList_t * const pList, - AwsIotLink_t * const pLink, - size_t linkOffset, - int ( *compare )( void *, void * ) ) -{ - AwsIotLink_t * i = NULL; - - /* Clear the next and previous pointers of the link. */ - pLink->pNext = NULL; - pLink->pPrevious = NULL; - - /* Link container must not already be in the list. */ - AwsIotQueue_Assert( AwsIotList_FindFirstMatch( pList->pHead, - linkOffset, - AwsIotLink_Container( pLink, linkOffset ), - NULL ) == NULL ); - - /* Insert at head if list is empty. */ - if( pList->pHead == NULL ) - { - pList->pHead = pLink; - } - /* Check if the head should be replaced. */ - else if( compare( AwsIotLink_Container( pLink, linkOffset ), - AwsIotLink_Container( pList->pHead, linkOffset ) ) <= 0 ) - { - pLink->pNext = pList->pHead; - pList->pHead->pPrevious = pLink; - pList->pHead = pLink; - } - else - { - for( i = pList->pHead; i != NULL; i = i->pNext ) - { - /* Insert at queue tail if there's no next element. */ - if( i->pNext == NULL ) - { - pLink->pPrevious = i; - i->pNext = pLink; - break; - } - /* Check the order of the current and next element. */ - else if( ( compare( AwsIotLink_Container( pLink, linkOffset ), - AwsIotLink_Container( i, linkOffset ) ) >= 0 ) && - ( compare( AwsIotLink_Container( pLink, linkOffset ), - AwsIotLink_Container( i->pNext, linkOffset ) ) < 0 ) ) - { - pLink->pNext = i->pNext; - pLink->pPrevious = i; - - i->pNext = pLink; - pLink->pNext->pPrevious = pLink; - break; - } - } - - /* Ensure that pLink was placed in the list. */ - AwsIotQueue_Assert( ( pLink->pNext != NULL ) || - ( pLink->pPrevious != NULL ) ); - } -} - -/*-----------------------------------------------------------*/ - -void * AwsIotList_FindFirstMatch( AwsIotLink_t * const pStartPoint, - size_t linkOffset, - void * pArgument, - bool ( *compare )( void *, void * ) ) -{ - AwsIotLink_t * i = NULL; - void * pLinkContainer = NULL; - - /* Iterate through the list to find the first matching element. */ - for( i = pStartPoint; i != NULL; i = i->pNext ) - { - pLinkContainer = AwsIotLink_Container( i, linkOffset ); - - if( ( ( compare == NULL ) && ( pArgument == pLinkContainer ) ) || - ( ( compare != NULL ) && ( compare( pArgument, pLinkContainer ) == true ) ) ) - { - return pLinkContainer; - } - } - - /* Element not found. */ - AwsIotQueue_Assert( i == NULL ); - - return NULL; -} - -/*-----------------------------------------------------------*/ - -void AwsIotList_Remove( AwsIotList_t * const pList, - AwsIotLink_t * const pLink, - size_t linkOffset ) -{ - /* Link offset is not used when asserts are off. */ - ( void ) linkOffset; - - /* This function should not be called with an NULL pLink or an empty list. */ - if( ( pList->pHead == NULL ) || ( pLink == NULL ) ) - { - return; - } - - /* pLink must be in pList. */ - AwsIotQueue_Assert( AwsIotList_FindFirstMatch( pList->pHead, - linkOffset, - AwsIotLink_Container( pLink, linkOffset ), - NULL ) != NULL ); - - /* Check if the list head is being removed. */ - if( pLink == pList->pHead ) - { - /* Set the next node's previous pointer to NULL. */ - if( pLink->pNext != NULL ) - { - pLink->pNext->pPrevious = NULL; - } - - /* Update the head pointer. */ - pList->pHead = pLink->pNext; - } - else - { - /* Update the next node's previous pointer. */ - if( pLink->pNext != NULL ) - { - pLink->pNext->pPrevious = pLink->pPrevious; - } - - /* Update the previous node's next pointer. */ - pLink->pPrevious->pNext = pLink->pNext; - } - - /* Clear the next and previous pointers of pLink. */ - pLink->pNext = NULL; - pLink->pPrevious = NULL; -} - -/*-----------------------------------------------------------*/ - -void AwsIotList_RemoveAllMatches( AwsIotList_t * const pList, - size_t linkOffset, - void * pArgument, - bool ( *shouldRemove )( void *, void * ), - void ( *freeElement )( void * ) ) -{ - AwsIotLink_t * i = NULL, * pCurrent = NULL; - void * pLinkContainer = NULL; - - AwsIotMutex_Lock( &( pList->mutex ) ); - i = pList->pHead; - - /* Iterate through the entire list. Remove and free all matching elements. */ - while( i != NULL ) - { - /* Save a pointer to the current element and move the iterating pointer. */ - pCurrent = i; - i = i->pNext; - - /* Calculate address of link container. */ - pLinkContainer = AwsIotLink_Container( pCurrent, linkOffset ); - - /* Check if the current list element should be removed. */ - if( ( shouldRemove == NULL ) || - ( shouldRemove( pArgument, pLinkContainer ) == true ) ) - { - /* Remove the matched element from the list and free it. */ - AwsIotList_Remove( pList, pCurrent, linkOffset ); - - if( freeElement != NULL ) - { - freeElement( pLinkContainer ); - } - } - } - - AwsIotMutex_Unlock( &( pList->mutex ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index fcb6437f95..227195528d 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -45,11 +45,8 @@ #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 #error "AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." #endif -#if AWS_IOT_MQTT_MAX_CALLBACK_THREADS <= 0 - #error "AWS_IOT_MQTT_MAX_CALLBACK_THREADS cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_MAX_SEND_THREADS <= 0 - #error "AWS_IOT_MQTT_MAX_SEND_THREADS cannot be 0 or negative." +#if AWS_IOT_MQTT_MAX_THREADS <= 0 + #error "AWS_IOT_MQTT_MAX_THREADS cannot be 0 or negative." #endif #if AWS_IOT_MQTT_TEST != 0 && AWS_IOT_MQTT_TEST != 1 #error "AWS_IOT_MQTT_MQTT_TEST must be 0 or 1." @@ -67,35 +64,29 @@ /*-----------------------------------------------------------*/ /** - * @brief Match MQTT connections by address. + * @brief Match #_mqttOperation_t by their associated MQTT connection. * - * @param[in] pMqttConnection Pointer to an #_mqttConnection_t. - * @param[in] pData Pointer to an #_mqttOperation_t. + * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. + * @param[in] pMatch Pointer to an #_mqttConnection_t. * - * @return `true` if the given [pData->pMqttConnection] - * (@ref _mqttOperation_t.pMqttConnection) is equal to `pMqttConnection`; `false` + * @return `true` if the [connection associated with the given operation] + * (@ref _mqttOperation_t.pMqttConnection) is equal to `pMatch`; `false` * otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility with - * @ref queue_function_removeallmatches. */ -static inline bool _mqttConnection_match( void * pMqttConnection, - void * pData ); +static bool _mqttOperation_matchConnection( const IotLink_t * pOperationLink, + void * pMatch ); /** * @brief Determines if an MQTT subscription is safe to remove based on its * reference count. * - * @param[in] pArgument Not used. - * @param[in] pData Pointer to an #_mqttSubscription_t. + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Not used. * * @return `true` if the given subscription has no references; `false` otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility with - * @ref list_function_removeallmatches. */ -static inline bool _mqttSubscription_shouldRemove( void * pArgument, - void * pData ); +static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, + void * pMatch ); /** * @brief Process a keep-alive event received from a timer event queue. @@ -204,30 +195,36 @@ static AwsIotMutex_t _connectMutex; /*-----------------------------------------------------------*/ -static inline bool _mqttConnection_match( void * pMqttConnection, - void * pData ) +static bool _mqttOperation_matchConnection( const IotLink_t * pOperationLink, + void * pMatch ) { - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + bool match = false; + _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, + pOperationLink, + link ); /* Ignore PINGREQ operations. PINGREQs will be cleaned up with the MQTT * connection and not here. */ - if( pOperation->operation == AWS_IOT_MQTT_PINGREQ ) + if( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) { - return false; + match = ( pMatch == pOperation->pMqttConnection ); } - return pMqttConnection == pOperation->pMqttConnection; + return match; } /*-----------------------------------------------------------*/ -static inline bool _mqttSubscription_shouldRemove( void * pArgument, - void * pData ) +static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, + void * pMatch ) { - _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + bool match = false; + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); /* Silence warnings about unused parameters. */ - ( void ) pArgument; + ( void ) pMatch; /* Reference count must not be negative. */ AwsIotMqtt_Assert( pSubscription->references >= 0 ); @@ -235,13 +232,16 @@ static inline bool _mqttSubscription_shouldRemove( void * pArgument, /* Check if any subscription callbacks are using this subscription. */ if( pSubscription->references > 0 ) { - /* Set the unsubscribed flag, but don't remove the subscription yet. */ + /* Set the unsubscribed flag, but do not remove the subscription yet. */ pSubscription->unsubscribed = true; - - return false; + } + else + { + /* No references for this subscription; it can be removed. */ + match = true; } - return true; + return match; } /*-----------------------------------------------------------*/ @@ -257,9 +257,8 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, /* If keep-alive isn't waiting for PINGRESP, send PINGREQ. */ AwsIotLogDebug( "Sending PINGREQ." ); - /* Add the PINGREQ operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pMqttConnection->pPingreqOperation->link ) ) == false ) + /* Add the PINGREQ operation to the pending operations queue. */ + if( AwsIotMqttInternal_EnqueueOperation( pMqttConnection->pPingreqOperation ) != AWS_IOT_MQTT_SUCCESS ) { AwsIotLogWarn( "Failed to add PINGREQ to send queue." ); status = false; @@ -307,12 +306,9 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, * was successfully processed. */ if( status == true ) { - AwsIotMutex_Lock( &( pMqttConnection->timerEventList.mutex ) ); - AwsIotList_InsertSorted( &( pMqttConnection->timerEventList ), - &( pMqttConnection->pKeepAliveEvent->link ), - _TIMER_EVENT_LINK_OFFSET, - AwsIotMqttInternal_TimerEventCompare ); - AwsIotMutex_Unlock( &( pMqttConnection->timerEventList.mutex ) ); + IotListDouble_InsertSorted( &( pMqttConnection->timerEventList ), + &( pMqttConnection->pKeepAliveEvent->link ), + AwsIotMqttInternal_TimerEventCompare ); } return status; @@ -323,23 +319,27 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, static void _processPublishRetryEvent( bool awsIotMqttMode, _mqttTimerEvent_t * const pPublishRetryEvent ) { - /* Check if the PUBLISH operation is still waiting in the receive queue for - * a response. */ - if( AwsIotQueue_RemoveFirstMatch( &_AwsIotMqttReceiveQueue, - _OPERATION_LINK_OFFSET, - pPublishRetryEvent->pOperation, - NULL ) == pPublishRetryEvent->pOperation ) + _mqttOperation_t * pOperation = pPublishRetryEvent->pOperation; + + /* This function should only be called for PUBLISH operations with retry. */ + AwsIotMqtt_Assert( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + AwsIotMqtt_Assert( pOperation->pPublishRetry == pPublishRetryEvent ); + AwsIotMqtt_Assert( pPublishRetryEvent->retry.limit > 0 ); + + /* Check if the PUBLISH operation is still waiting for a response. */ + if( AwsIotMqttInternal_FindOperation( pOperation->operation, + &( pOperation->packetIdentifier ) ) == pOperation ) { /* Check if the retry limit is reached. */ if( pPublishRetryEvent->retry.count > pPublishRetryEvent->retry.limit ) { AwsIotLogError( "No PUBACK received for PUBLISH %hu after %d retransmissions.", - pPublishRetryEvent->pOperation->packetIdentifier, + pOperation->packetIdentifier, pPublishRetryEvent->retry.limit ); /* Set a status of "No response to retries" and notify. */ - pPublishRetryEvent->pOperation->status = AWS_IOT_MQTT_RETRY_NO_RESPONSE; - AwsIotMqttInternal_Notify( pPublishRetryEvent->pOperation ); + pOperation->status = AWS_IOT_MQTT_RETRY_NO_RESPONSE; + AwsIotMqttInternal_Notify( pOperation ); } else { @@ -349,9 +349,9 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, uint16_t * const ) = AwsIotMqttInternal_PublishSetDup; #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pPublishRetryEvent->pOperation->pMqttConnection->network.serialize.publishSetDup != NULL ) + if( pOperation->pMqttConnection->network.serialize.publishSetDup != NULL ) { - publishSetDup = pPublishRetryEvent->pOperation->pMqttConnection->network.serialize.publishSetDup; + publishSetDup = pOperation->pMqttConnection->network.serialize.publishSetDup; } #endif @@ -364,8 +364,8 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, if( pPublishRetryEvent->retry.count <= pPublishRetryEvent->retry.limit ) { publishSetDup( true, - pPublishRetryEvent->pOperation->pMqttPacket, - &( pPublishRetryEvent->pOperation->packetIdentifier ) ); + pOperation->pMqttPacket, + &( pOperation->packetIdentifier ) ); } } else @@ -373,35 +373,37 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, if( pPublishRetryEvent->retry.count == 1 ) { publishSetDup( false, - pPublishRetryEvent->pOperation->pMqttPacket, - &( pPublishRetryEvent->pOperation->packetIdentifier ) ); + pOperation->pMqttPacket, + &( pOperation->packetIdentifier ) ); } } /* Print debug log messages about this PUBLISH retry. */ AwsIotLogDebug( "No PUBACK received for PUBLISH %hu. Attempting retransmission" " %d of %d.", - pPublishRetryEvent->pOperation->packetIdentifier, + pOperation->packetIdentifier, pPublishRetryEvent->retry.count, pPublishRetryEvent->retry.limit ); if( pPublishRetryEvent->retry.count < pPublishRetryEvent->retry.limit ) { AwsIotLogDebug( "Next retry for PUBLISH %hu in %llu ms.", - pPublishRetryEvent->pOperation->packetIdentifier, + pOperation->packetIdentifier, pPublishRetryEvent->retry.nextPeriod ); } else { AwsIotLogDebug( "Final retry for PUBLISH %hu. Will check in %llu ms " "for response.", - pPublishRetryEvent->pOperation->packetIdentifier, + pOperation->packetIdentifier, AWS_IOT_MQTT_RESPONSE_WAIT_MS ); } - /* Add the PUBLISH to the send queue for network transmission. */ - ( void ) AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pPublishRetryEvent->pOperation->link ) ); + /* Add the PUBLISH to the pending operations queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pOperation ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to add PUBLISH retry to pending operations queue." ); + } } } } @@ -415,31 +417,29 @@ static void _timerThread( void * pArgument ) AwsIotLogDebug( "Timer thread started for connection %p.", pMqttConnection ); - /* Attempt to lock the connection mutex before this thread does anything. + /* Attempt to lock the timer mutex before this thread does anything. * Return immediately if the mutex couldn't be locked. */ - if( AwsIotMutex_TryLock( &( pMqttConnection->mutex ) ) == false ) + if( AwsIotMutex_TryLock( &( pMqttConnection->timerMutex ) ) == false ) { - AwsIotLogWarn( "Failed to lock connection mutex in timer thread. Exiting." ); + AwsIotLogWarn( "Failed to lock connection timer mutex in timer thread. Exiting." ); return; } while( true ) { - /* Lock the timer list mutex. */ - AwsIotMutex_Lock( &( pMqttConnection->timerEventList.mutex ) ); - - /* Peek the first element in the timer list. */ - pTimerEvent = ( _mqttTimerEvent_t * ) ( pMqttConnection->timerEventList.pHead ); + /* Peek the first event in the timer event list. */ + pTimerEvent = IotLink_Container( _mqttTimerEvent_t, + IotListDouble_PeekHead( &( pMqttConnection->timerEventList ) ), + link ); if( pTimerEvent != NULL ) { - /* Check if the first element should be processed now. */ + /* Check if the first event should be processed now. */ if( pTimerEvent->expirationTime <= AwsIotClock_GetTimeMs() + AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ) { - AwsIotList_Remove( &( pMqttConnection->timerEventList ), - pMqttConnection->timerEventList.pHead, - _TIMER_EVENT_LINK_OFFSET ); + /* Remove the timer event for immediate processing. */ + IotListDouble_Remove( &( pTimerEvent->link ) ); } else { @@ -457,9 +457,6 @@ static void _timerThread( void * pArgument ) } } - /* Unlock the timer list mutex. */ - AwsIotMutex_Unlock( &( pMqttConnection->timerEventList.mutex ) ); - /* If there are no timer events to process, terminate this thread. */ if( pTimerEvent == NULL ) { @@ -508,7 +505,7 @@ static void _timerThread( void * pArgument ) } } - AwsIotMutex_Unlock( &( pMqttConnection->mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->timerMutex ) ); } /*-----------------------------------------------------------*/ @@ -550,45 +547,36 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, pNewMqttConnection->network = *pNetworkInterface; pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; - /* Create the new connection's subscription list. */ - if( AwsIotList_Create( &( pNewMqttConnection->subscriptionList ) ) == false ) + /* Create the timer mutex for a new connection. */ + if( AwsIotMutex_Create( &( pNewMqttConnection->timerMutex ) ) == false ) { - AwsIotLogError( "Failed to create subscription list for new MQTT connection." ); + AwsIotLogError( "Failed to create timer mutex for new connection." ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); return NULL; } - /* Create the new connection's timer event list. */ - if( AwsIotList_Create( &( pNewMqttConnection->timerEventList ) ) == false ) + if( AwsIotMutex_Create( &( pNewMqttConnection->subscriptionMutex ) ) == false ) { - AwsIotLogError( "Failed to create timer event list for new MQTT connection" ); - AwsIotList_Destroy( &( pNewMqttConnection->subscriptionList ) ); + AwsIotLogError( "Failed to create subscription mutex for new connection." ); + AwsIotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); return NULL; } - /* Create the mutex for a new connection. */ - if( AwsIotMutex_Create( &( pNewMqttConnection->mutex ) ) == false ) - { - AwsIotLogError( "Failed to create mutex for new connection." ); - AwsIotList_Destroy( &( pNewMqttConnection->timerEventList ) ); - AwsIotList_Destroy( &( pNewMqttConnection->subscriptionList ) ); - AwsIotMqtt_FreeConnection( pNewMqttConnection ); - - return NULL; - } + /* Create the new connection's subscription and timer event lists. */ + IotListDouble_Create( &( pNewMqttConnection->subscriptionList ) ); + IotListDouble_Create( &( pNewMqttConnection->timerEventList ) ); - /* Create the timer for a new connection. */ + /* Create the timer mutex for a new connection. */ if( AwsIotClock_TimerCreate( &( pNewMqttConnection->timer ), _timerThread, pNewMqttConnection ) == false ) { AwsIotLogError( "Failed to create timer for new connection." ); - AwsIotMutex_Destroy( &( pNewMqttConnection->mutex ) ); - AwsIotList_Destroy( &( pNewMqttConnection->timerEventList ) ); - AwsIotList_Destroy( &( pNewMqttConnection->subscriptionList ) ); + AwsIotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); + AwsIotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); return NULL; @@ -652,12 +640,9 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, pNewMqttConnection->pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + keepAliveSeconds * 1000ULL; /* Add the PINGREQ to the timer event list. */ - AwsIotMutex_Lock( &( pNewMqttConnection->timerEventList.mutex ) ); - AwsIotList_InsertSorted( &( pNewMqttConnection->timerEventList ), - &( pNewMqttConnection->pKeepAliveEvent->link ), - _TIMER_EVENT_LINK_OFFSET, - AwsIotMqttInternal_TimerEventCompare ); - AwsIotMutex_Unlock( &( pNewMqttConnection->timerEventList.mutex ) ); + IotListDouble_InsertSorted( &( pNewMqttConnection->timerEventList ), + &( pNewMqttConnection->pKeepAliveEvent->link ), + AwsIotMqttInternal_TimerEventCompare ); } return pNewMqttConnection; @@ -678,22 +663,20 @@ static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) if( pMqttConnection->pPingreqOperation != NULL ) { AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); - pMqttConnection->pPingreqOperation = NULL; } /* Remove any previous session subscriptions. */ - AwsIotList_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - NULL, - _mqttSubscription_shouldRemove, - AwsIotMqtt_FreeSubscription ); + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotListDouble_RemoveAll( &( pMqttConnection->subscriptionList ), + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - /* Destroy timers, mutex, and lists. */ + /* Destroy timer and mutexes. */ AwsIotClock_TimerDestroy( &( pMqttConnection->timer ) ); - AwsIotMutex_Destroy( &( pMqttConnection->mutex ) ); - AwsIotList_Destroy( &( pMqttConnection->timerEventList ) ); - AwsIotList_Destroy( &( pMqttConnection->subscriptionList ) ); + AwsIotMutex_Destroy( &( pMqttConnection->timerMutex ) ); + AwsIotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); AwsIotMqtt_FreeConnection( pMqttConnection ); } @@ -834,11 +817,10 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio /* Prevent another CONNECT operation from using the network. */ AwsIotMutex_Lock( &_connectMutex ); - /* Add the CONNECT operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pConnectOperation->link ) ) == false ) + /* Add the CONNECT operation to the pending operations queue. */ + if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to add CONNECT to send queue." ); + AwsIotLogError( "Failed to add CONNECT to pending operations queue." ); connectStatus = AWS_IOT_MQTT_NO_MEMORY; AwsIotMqttInternal_DestroyOperation( pConnectOperation ); } @@ -1020,11 +1002,10 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio *pSubscriptionRef = pSubscriptionOperation; } - /* Add the subscription operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pSubscriptionOperation->link ) ) == false ) + /* Add the subscription operation to the pending operations queue. */ + if( AwsIotMqttInternal_EnqueueOperation( pSubscriptionOperation ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to add %s to send queue.", + AwsIotLogError( "Failed to add %s to pending operations queue.", AwsIotMqtt_OperationType( operation ) ); if( operation == AWS_IOT_MQTT_SUBSCRIBE ) @@ -1095,14 +1076,13 @@ static void _sendPuback( _mqttConnection_t * const pMqttConnection, } /* Set the remaining members of the PUBACK operation and push it to the - * send queue. */ + * pending operations queue. */ pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; pPubackOperation->pMqttConnection = pMqttConnection; - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pPubackOperation->link ) ) == false ) + if( AwsIotMqttInternal_EnqueueOperation( pPubackOperation ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add PUBACK to send queue." ); + AwsIotLogWarn( "Failed to add PUBACK to pending operations queue." ); AwsIotMqttInternal_DestroyOperation( pPubackOperation ); } } @@ -1111,51 +1091,114 @@ static void _sendPuback( _mqttConnection_t * const pMqttConnection, AwsIotMqttError_t AwsIotMqtt_Init( void ) { - /* Create CONNECT mutex. */ - if( AwsIotMutex_Create( &_connectMutex ) == false ) + AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; + + /* Create mutex protecting list of operations pending processing. */ + if( AwsIotMutex_Create( &( _IotMqttPendingProcessingMutex ) ) == false ) + { + AwsIotLogError( "Failed to initialize MQTT library pending processing mutex." ); + status = AWS_IOT_MQTT_INIT_FAILED; + } + + /* Create mutex protecting list of operations pending network responses. */ + if( status == AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to initialize MQTT library connect mutex." ); + if( AwsIotMutex_Create( &( _IotMqttPendingResponseMutex ) ) == false ) + { + AwsIotLogError( "Failed to initialize MQTT library pending response mutex." ); + AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); - return AWS_IOT_MQTT_INIT_FAILED; + status = AWS_IOT_MQTT_INIT_FAILED; + } } - /* Create MQTT queues. */ - if( AwsIotMqttInternal_CreateQueues() != AWS_IOT_MQTT_SUCCESS ) + /* Create CONNECT mutex. */ + if( status == AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to initialize MQTT library queues." ); + if( AwsIotMutex_Create( &( _connectMutex ) ) == false ) + { + AwsIotLogError( "Failed to initialize MQTT library connect mutex." ); + AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); - AwsIotMutex_Destroy( &_connectMutex ); + status = AWS_IOT_MQTT_INIT_FAILED; + } + } - return AWS_IOT_MQTT_INIT_FAILED; + /* Create semaphore that counts active MQTT library threads. */ + if( status == AWS_IOT_MQTT_SUCCESS ) + { + if( AwsIotSemaphore_Create( &( _IotMqttAvailableThreads ), + AWS_IOT_MQTT_MAX_THREADS, + AWS_IOT_MQTT_MAX_THREADS ) == false ) + { + AwsIotLogError( "Failed to initialize record of active MQTT library threads." ); + AwsIotMutex_Destroy( &( _connectMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); + + status = AWS_IOT_MQTT_INIT_FAILED; + } } /* Initialize MQTT serializer. */ - if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) + if( status == AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to initialize MQTT library serializer. " ); - - AwsIotMutex_Destroy( &_connectMutex ); - AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); - AwsIotQueue_Destroy( &_AwsIotMqttReceiveQueue ); - AwsIotQueue_Destroy( &_AwsIotMqttCallbackQueue ); + if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogError( "Failed to initialize MQTT library serializer. " ); + AwsIotSemaphore_Destroy( &( _IotMqttAvailableThreads ) ); + AwsIotMutex_Destroy( &( _connectMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); - return AWS_IOT_MQTT_INIT_FAILED; + status = AWS_IOT_MQTT_INIT_FAILED; + } } - AwsIotLogInfo( "MQTT library successfully initialized." ); + /* Create MQTT library linear containers. */ + if( status == AWS_IOT_MQTT_SUCCESS ) + { + IotQueue_Create( &( _IotMqttPendingProcessing ) ); + IotListDouble_Create( &( _IotMqttPendingResponse ) ); + + AwsIotLogInfo( "MQTT library successfully initialized." ); + } - return AWS_IOT_MQTT_SUCCESS; + return status; } /*-----------------------------------------------------------*/ void AwsIotMqtt_Cleanup() { - /* Clean up connect mutex, queues, and serializer. */ - AwsIotMutex_Destroy( &_connectMutex ); - AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); - AwsIotQueue_Destroy( &_AwsIotMqttReceiveQueue ); - AwsIotQueue_Destroy( &_AwsIotMqttCallbackQueue ); + /* Wait for termination of any active MQTT library threads. */ + AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + + while( AwsIotSemaphore_GetCount( &( _IotMqttAvailableThreads ) ) > 0 ) + { + AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotSemaphore_Wait( &( _IotMqttAvailableThreads ) ); + AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + } + + AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + + /* This API requires all MQTT connections to be terminated. If the MQTT library + * linear containers are not empty, there is an active MQTT connection. The + * library cannot be safely shut down. */ + AwsIotMqtt_Assert( IotQueue_IsEmpty( &( _IotMqttPendingProcessing ) ) == true ); + AwsIotMqtt_Assert( IotListDouble_IsEmpty( &( _IotMqttPendingResponse ) ) == true ); + + /* Clean up MQTT library mutexes. */ + AwsIotMutex_Destroy( &( _connectMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); + + /* Clean up active thread counter. */ + AwsIotSemaphore_Destroy( &( _IotMqttAvailableThreads ) ); + + /* Clean up MQTT serializer. */ AwsIotMqttInternal_CleanupSerialize(); AwsIotLogInfo( "MQTT library cleanup done." ); @@ -1224,14 +1267,15 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, /* If a complete CONNACK was deserialized, check if there's an * in-progress CONNECT operation. */ - if( ( bytesProcessed > 0 ) && - ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, - AWS_IOT_MQTT_CONNECT, - NULL, - &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + if( bytesProcessed > 0 ) { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_CONNECT, NULL ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } } break; @@ -1328,14 +1372,15 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, /* If a complete PUBACK packet was deserialized, find an in-progress * PUBLISH with a matching client identifier. */ - if( ( bytesProcessed > 0 ) && - ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, - AWS_IOT_MQTT_PUBLISH_TO_SERVER, - &packetIdentifier, - &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + if( bytesProcessed > 0 ) { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PUBLISH_TO_SERVER, &packetIdentifier ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } } break; @@ -1365,14 +1410,15 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, /* If a complete SUBACK was deserialized, find an in-progress * SUBACK with a matching client identifier. */ - if( ( bytesProcessed > 0 ) && - ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, - AWS_IOT_MQTT_SUBSCRIBE, - &packetIdentifier, - &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + if( bytesProcessed > 0 ) { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_SUBSCRIBE, &packetIdentifier ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } } break; @@ -1400,14 +1446,15 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, /* If a complete UNSUBACK was deserialized, find an in-progress * UNSUBACK with a matching client identifier. */ - if( ( bytesProcessed > 0 ) && - ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, - AWS_IOT_MQTT_UNSUBSCRIBE, - &packetIdentifier, - &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + if( bytesProcessed > 0 ) { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_UNSUBSCRIBE, &packetIdentifier ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } } break; @@ -1433,14 +1480,15 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, /* If a complete PINGRESP was deserialized, check if there's an * in-progress PINGREQ operation. */ - if( ( bytesProcessed > 0 ) && - ( AwsIotMqttInternal_OperationFind( &_AwsIotMqttReceiveQueue, - AWS_IOT_MQTT_PINGREQ, - NULL, - &pOperation ) == AWS_IOT_MQTT_SUCCESS ) ) + if( bytesProcessed > 0 ) { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PINGREQ, NULL ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } } break; @@ -1473,8 +1521,8 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, pLastPublish = NULL; - /* Prevent the PINGRESP send thread from running, then set the error flag. */ - AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + /* Prevent the timer thread from running, then set the error flag. */ + AwsIotMutex_Lock( &( pConnectionInfo->timerMutex ) ); pConnectionInfo->errorOccurred = true; @@ -1487,7 +1535,7 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); } - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + AwsIotMutex_Unlock( &( pConnectionInfo->timerMutex ) ); return -1; } @@ -1518,7 +1566,7 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, freeReceivedData( ( void * ) pReceivedData ); } - /* Add all PUBLISH messages to the callback queue. */ + /* Add all PUBLISH messages to the pending operations queue. */ if( pLastPublish != NULL ) { /* If all bytes of the receive buffer were processed, set the function @@ -1576,10 +1624,9 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, } } - if( AwsIotQueue_InsertHead( &_AwsIotMqttCallbackQueue, - &( pFirstPublish->link ) ) == false ) + if( AwsIotMqttInternal_EnqueueOperation( pFirstPublish ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add PUBLISH to callback queue." ); + AwsIotLogWarn( "Failed to add PUBLISH to pending operations queue." ); } } @@ -1651,30 +1698,32 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotLogInfo( "Disconnecting MQTT connection %p.", pMqttConnection ); - /* Lock the connection mutex to block the timer thread. */ - AwsIotMutex_Lock( &( pMqttConnection->mutex ) ); - - /* Purge all of this connection's subscriptions, operations, and timer events. */ - AwsIotList_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - NULL, - _mqttSubscription_shouldRemove, - AwsIotMqtt_FreeSubscription ); - AwsIotQueue_RemoveAllMatches( &_AwsIotMqttSendQueue, - _OPERATION_LINK_OFFSET, - pMqttConnection, - _mqttConnection_match, - AwsIotMqttInternal_DestroyOperation ); - AwsIotQueue_RemoveAllMatches( &_AwsIotMqttReceiveQueue, - _OPERATION_LINK_OFFSET, - pMqttConnection, - _mqttConnection_match, - AwsIotMqttInternal_DestroyOperation ); - AwsIotList_RemoveAllMatches( &( pMqttConnection->timerEventList ), - _TIMER_EVENT_LINK_OFFSET, - NULL, - NULL, - AwsIotMqtt_FreeTimerEvent ); + /* Purge all of this connection's subscriptions. */ + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _mqttSubscription_shouldRemove, + NULL, + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + /* Lock the connection timer mutex to block the timer thread. */ + AwsIotMutex_Lock( &( pMqttConnection->timerMutex ) ); + + /* Purge all of this connection's operations, and timer events. */ + IotQueue_RemoveAllMatches( &( _IotMqttPendingProcessing ), + _mqttOperation_matchConnection, + pMqttConnection, + AwsIotMqttInternal_DestroyOperation, + offsetof( _mqttOperation_t, link ) ); + IotListDouble_RemoveAllMatches( &( _IotMqttPendingProcessing ), + _mqttOperation_matchConnection, + pMqttConnection, + AwsIotMqttInternal_DestroyOperation, + offsetof( _mqttOperation_t, link ) ); + IotListDouble_RemoveAll( &( pMqttConnection->timerEventList ), + AwsIotMqtt_FreeTimerEvent, + offsetof( _mqttTimerEvent_t, link ) ); /* Stop the connection timer. */ AwsIotLogDebug( "Stopping connection timer." ); @@ -1725,11 +1774,10 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotMqtt_Assert( pDisconnectOperation->pMqttPacket != NULL ); AwsIotMqtt_Assert( pDisconnectOperation->packetSize > 0 ); - /* Add the DISCONNECT operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pDisconnectOperation->link ) ) == false ) + /* Add the DISCONNECT operation to the pending operations queue. */ + if( AwsIotMqttInternal_EnqueueOperation( pDisconnectOperation ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add DISCONNECT to send queue." ); + AwsIotLogWarn( "Failed to add DISCONNECT to pending operations queue." ); AwsIotMqttInternal_DestroyOperation( pDisconnectOperation ); } else @@ -1747,17 +1795,6 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotLogInfo( "MQTT connection %p disconnected.", pMqttConnection ); } } - - /* Close the network connection regardless of whether an MQTT DISCONNECT - * packet was sent. */ - if( pMqttConnection->network.disconnect != NULL ) - { - pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); - } - else - { - AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); - } } /* Free the memory in use by the keep-alive operation. */ @@ -1766,13 +1803,23 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); } - /* Destroy this connection's lists and queues. */ - AwsIotList_Destroy( &( pMqttConnection->subscriptionList ) ); - AwsIotList_Destroy( &( pMqttConnection->timerEventList ) ); + /* Unlock the connection timer mutex. */ + AwsIotMutex_Unlock( &( pMqttConnection->timerMutex ) ); + + /* Close the network connection regardless of whether an MQTT DISCONNECT + * packet was sent. */ + if( pMqttConnection->network.disconnect != NULL ) + { + pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + } + else + { + AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); + } - /* Unlock and destroy the connection mutex. */ - AwsIotMutex_Unlock( &( pMqttConnection->mutex ) ); - AwsIotMutex_Destroy( &( pMqttConnection->mutex ) ); + /* Destroy the MQTT connection's mutexes. */ + AwsIotMutex_Destroy( &( pMqttConnection->timerMutex ) ); + AwsIotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); /* Free the memory used by this connection. */ AwsIotMqtt_FreeConnection( pMqttConnection ); @@ -2003,17 +2050,16 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, } /* Set the reference, if provided. This should be set before the publish - * is pushed to the network queue to avoid a race condition. */ + * is pushed to the pending operations queue to avoid a race condition. */ if( ( pPublishInfo->QoS > 0 ) && ( pPublishRef != NULL ) ) { *pPublishRef = pPublishOperation; } - /* Add the PUBLISH operation to the send queue. */ - if( AwsIotQueue_InsertHead( &_AwsIotMqttSendQueue, - &( pPublishOperation->link ) ) == false ) + /* Add the PUBLISH operation to the pending operations queue. */ + if( AwsIotMqttInternal_EnqueueOperation( pPublishOperation ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to add PUBLISH to send queue." ); + AwsIotLogError( "Failed to add PUBLISH to pending operations queue." ); AwsIotMqttInternal_DestroyOperation( pPublishOperation ); @@ -2027,7 +2073,7 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, } /* A QoS 0 PUBLISH is considered successful as soon as it's added to the - * send queue. */ + * pending operations queue. */ if( pPublishInfo->QoS == 0 ) { return AWS_IOT_MQTT_SUCCESS; @@ -2099,10 +2145,10 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, AwsIotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); /* Check any status set by the send thread. Block the receive callback - * during this check by locking the receive queue mutex. */ - AwsIotMutex_Lock( &( _AwsIotMqttReceiveQueue.mutex ) ); + * during this check by locking the mutex for operations pending responses. */ + AwsIotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); status = pOperation->status; - AwsIotMutex_Unlock( &( _AwsIotMqttReceiveQueue.mutex ) ); + AwsIotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); if( status == AWS_IOT_MQTT_STATUS_PENDING ) { @@ -2110,9 +2156,9 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, * thread during this check by locking the connection mutex. */ if( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) { - AwsIotMutex_Lock( &( pOperation->pMqttConnection->mutex ) ); + AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); publishRetryActive = ( pOperation->pPublishRetry != NULL ); - AwsIotMutex_Unlock( &( pOperation->pMqttConnection->mutex ) ); + AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); } /* Wait for a response to be received from the network. Record when @@ -2156,11 +2202,11 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, /* No status received before timeout. */ status = AWS_IOT_MQTT_TIMEOUT; - /* A timed out operation may still be in the receive queue. */ - ( void ) AwsIotQueue_RemoveFirstMatch( &_AwsIotMqttReceiveQueue, - _OPERATION_LINK_OFFSET, - pOperation, - NULL ); + /* A timed out operation may still pending a network response. */ + ( void ) IotListDouble_RemoveFirstMatch( &( _IotMqttPendingResponse ), + NULL, + NULL, + pOperation ); } else { @@ -2168,7 +2214,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, * status. */ if( publishRetryActive == true ) { - AwsIotMutex_Lock( &( pOperation->pMqttConnection->mutex ) ); + AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); } /* Successfully received a notification of completion. Retrieve the @@ -2177,7 +2223,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, if( publishRetryActive == true ) { - AwsIotMutex_Unlock( &( pOperation->pMqttConnection->mutex ) ); + AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); } } } @@ -2189,9 +2235,8 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, AwsIotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); } - /* A completed operation should not be in any queue. */ - AwsIotMqtt_Assert( pOperation->link.pNext == NULL ); - AwsIotMqtt_Assert( pOperation->link.pPrevious == NULL ); + /* A completed operation should not be linked. */ + AwsIotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); /* Remove any lingering subscriptions from a timed out SUBSCRIBE. If * a SUBSCRIBE fails for any other reason, its subscription have already diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index c96a0c1251..6fbe092a9e 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -52,39 +52,14 @@ typedef struct _operationMatchParam /** * @brief Match an MQTT operation by type and packet identifier. * - * @param[in] pArgument Pointer to an #_operationMatchParam_t. - * @param[in] pData Pointer to an #_mqttOperation_t. + * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. + * @param[in] pMatch Pointer to an #_operationMatchParam_t. * - * @return `true` if `pData` matches the parameters in `pArgument`; `false` + * @return `true` if the operation matches the parameters in `pArgument`; `false` * otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility - * with @ref queue_function_removefirstmatch. - */ -static inline bool _mqttOperation_match( void * pArgument, - void * pData ); - -/** - * @brief Send data over the network. - * - * Whenever an operation is added to the send queue, a send thread is created. - * This thread processes operations from the send queue until it is empty. The - * number of concurrent send threads is limited by @ref AWS_IOT_MQTT_MAX_SEND_THREADS. - * - * @param[in] pArgument Not used. - */ -static void _sendThread( void * pArgument ); - -/** - * @brief Invoke user-provided callback functions. - * - * Callback functions are invoked in their own thread so they don't block the - * MQTT library's threads. The number of concurrent callback threads is limited - * by @ref AWS_IOT_MQTT_MAX_CALLBACK_THREADS. - * - * @param[in] pArgument Not used. */ -static void _callbackThread( void * pArgument ); +static bool _mqttOperation_match( const IotLink_t * pOperationLink, + void * pMatch ); /** * @brief Calculate the timeout and period of the next PUBLISH retry event. @@ -101,250 +76,80 @@ static void _callbackThread( void * pArgument ); static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnection, _mqttTimerEvent_t * const pPublishRetry ); -/*-----------------------------------------------------------*/ - -/* - * MQTT operation queues. +/** + * @brief Send the packet associated with an MQTT operation. + * + * Transmits the MQTT packet from a pending operation to the MQTT server, then + * places the operation in the list of MQTT operations awaiting responses from + * the server. + * + * @param[in] pOperation The MQTT operation with the packet to send. */ -AwsIotQueue_t _AwsIotMqttSendQueue = { 0 }; /**< @brief Queues data to be sent on the network. */ -AwsIotQueue_t _AwsIotMqttReceiveQueue = { 0 }; /**< @brief Queues responses received from the network. */ -AwsIotQueue_t _AwsIotMqttCallbackQueue = { 0 }; /**< @brief Queues pending user callbacks. */ +static void _invokeSend( _mqttOperation_t * const pOperation ); -/*-----------------------------------------------------------*/ - -static inline bool _mqttOperation_match( void * pArgument, - void * pData ) -{ - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; - _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pArgument; - - if( pParam->operation == pOperation->operation ) - { - if( pParam->pPacketIdentifier == NULL ) - { - return true; - } - else - { - return *( pParam->pPacketIdentifier ) == pOperation->packetIdentifier; - } - } +/** + * @brief Invoke user-provided callback functions. + * + * Invokes callback functions associated with either a completed MQTT operation + * or an incoming PUBLISH message. + * + * @param[in] pOperation The MQTT operation with the callback to invoke. + */ +static void _invokeCallback( _mqttOperation_t * const pOperation ); - return false; -} +/** + * @brief Process an MQTT operation. + * + * This function is a thread routine that either sends an MQTT packet for + * in-progress operations, notifies of an operation's completion, or notifies + * of an incoming PUBLISH message. + * + * @param[in] pArgument Not used. + */ +static void _processOperation( void * pArgument ); /*-----------------------------------------------------------*/ -static void _sendThread( void * pArgument ) -{ - bool waitableOperation = false; - _mqttOperation_t * pOperation = NULL; - - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - AwsIotLogDebug( "New send thread started." ); - - /* Receive messages from the send queue until the queue is empty. */ - while( true ) - { - AwsIotLogDebug( "Removing oldest operation from MQTT send queue." ); - - /* Get the oldest operation in the MQTT send queue. */ - pOperation = AwsIotQueue_RemoveTail( &_AwsIotMqttSendQueue, - _OPERATION_LINK_OFFSET, - true ); - - /* If no operation was received, terminate the loop. */ - if( pOperation == NULL ) - { - break; - } - - AwsIotLogDebug( "Operation %s received from queue. Sending data over network.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - /* The received operation should be waiting for a status and have a valid packet. */ - AwsIotMqtt_Assert( pOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - AwsIotMqtt_Assert( pOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pOperation->packetSize != 0 ); - - /* Check if this is a waitable operation. */ - waitableOperation = ( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) - == AWS_IOT_MQTT_FLAG_WAITABLE ); - - /* Transmit the MQTT packet from the operation over the network. */ - if( pOperation->pMqttConnection->network.send( pOperation->pMqttConnection->network.pSendContext, - pOperation->pMqttPacket, - pOperation->packetSize ) != pOperation->packetSize ) - { - /* Transmission failed. */ - AwsIotLogError( "Failed to send data for operation %s.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - pOperation->status = AWS_IOT_MQTT_SEND_ERROR; - } - else - { - /* DISCONNECT operations are considered successful upon successful - * transmission. */ - if( pOperation->operation == AWS_IOT_MQTT_DISCONNECT ) - { - pOperation->status = AWS_IOT_MQTT_SUCCESS; - } - /* Calculate the details of the next PUBLISH retry event. */ - else if( pOperation->pPublishRetry != NULL ) - { - /* Only a PUBLISH may have a retry event. */ - AwsIotMqtt_Assert( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); - - if( _calculateNextPublishRetry( pOperation->pMqttConnection, - pOperation->pPublishRetry ) == false ) - { - /* PUBLISH retry failed to re-arm connection timer. Retransmission - * will not be sent. */ - pOperation->status = AWS_IOT_MQTT_SEND_ERROR; - } - } - } - - /* Once the MQTT packet has been sent, it may be freed if it will not be - * retransmitted and it's not a PINGREQ. Additionally, the entire operation - * may be destroyed if no notification method is set. */ - if( ( pOperation->pPublishRetry == NULL ) && - ( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) ) - { - /* Choose a free packet function. */ - void ( * freePacket )( uint8_t * ) = AwsIotMqttInternal_FreePacket; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pOperation->pMqttConnection->network.freePacket != NULL ) - { - freePacket = pOperation->pMqttConnection->network.freePacket; - } - #endif - - freePacket( pOperation->pMqttPacket ); - pOperation->pMqttPacket = NULL; - - if( ( waitableOperation == false ) && - ( pOperation->notify.callback.function == NULL ) ) - { - AwsIotMqttInternal_DestroyOperation( pOperation ); - continue; - } - } - - /* If a status was set by this function, notify of completion. */ - if( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) - { - AwsIotMqttInternal_Notify( pOperation ); - } - /* Otherwise, add the operation to the receive queue. */ - else - { - /* This function call will not fail because the receive queue has no - * notification routine. */ - ( void ) AwsIotQueue_InsertHead( &_AwsIotMqttReceiveQueue, - &( pOperation->link ) ); - } - - /* Notify a waitable operation that the send thread is finished. */ - if( waitableOperation == true ) - { - AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - } - } +/** + * Linear containers for in-progress MQTT operations. + */ +IotQueue_t _IotMqttPendingProcessing = { 0 }; /**< @brief Queue of MQTT operations that are waiting to be processed (i.e. sent or parsed). */ +IotListDouble_t _IotMqttPendingResponse = { 0 }; /**< @brief List of MQTT operations awaiting a response from the MQTT server. */ +AwsIotMutex_t _IotMqttPendingProcessingMutex; /**< @brief Protects #_IotMqttPendingProcessing from concurrent access. */ +AwsIotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ - AwsIotLogDebug( "Send queue empty. Send thread terminating." ); -} +/** + * @brief Maintains a count of threads currently available for the MQTT library + * and provides a mechanism to wait for active threads to finish. + */ +AwsIotSemaphore_t _IotMqttAvailableThreads; /*-----------------------------------------------------------*/ -static void _callbackThread( void * pArgument ) +static bool _mqttOperation_match( const IotLink_t * pOperationLink, + void * pMatch ) { - _mqttOperation_t * pOperation = NULL, * i = NULL, * pCurrent = NULL; - AwsIotMqttCallbackParam_t callbackParam = { .operation = { 0 } }; - - /* Silence warnings about unused parameters. */ - ( void ) pArgument; + bool match = false; + _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, + pOperationLink, + link ); + _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; - AwsIotLogDebug( "New callback thread started." ); - - while( true ) + /* Check for matching operations. */ + if( pParam->operation == pOperation->operation ) { - AwsIotLogDebug( "Removing oldest operation from MQTT callback queue." ); - - /* Get the oldest operation in the MQTT callback queue. */ - pOperation = AwsIotQueue_RemoveTail( &_AwsIotMqttCallbackQueue, - _OPERATION_LINK_OFFSET, - true ); - - /* If no operation was received, terminate the loop. */ - if( pOperation == NULL ) - { - break; - } - - if( pOperation->incomingPublish == true ) + /* Check for matching packet identifiers. */ + if( pParam->pPacketIdentifier == NULL ) { - /* Set the pointer to the first PUBLISH. */ - i = pOperation; - - /* Process each PUBLISH in the operation. */ - while( i != NULL ) - { - /* Save a pointer to the current PUBLISH and move the iterating - * pointer. */ - pCurrent = i; - i = i->pNextPublish; - - /* Process the current PUBLISH. */ - if( ( pCurrent->publishInfo.pPayload != NULL ) && - ( pCurrent->publishInfo.pTopicName != NULL ) ) - { - callbackParam.message.info = pCurrent->publishInfo; - - AwsIotMqttInternal_ProcessPublish( pCurrent->pMqttConnection, - &callbackParam ); - } - - /* Free any buffers associated with the current PUBLISH message. */ - if( pCurrent->freeReceivedData != NULL ) - { - AwsIotMqtt_Assert( pCurrent->pReceivedData != NULL ); - pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); - } - - /* Free the current PUBLISH. */ - AwsIotMqtt_FreeOperation( pCurrent ); - } + match = true; } else { - /* Operations with no callback should not have been passed to the - * callback queue. */ - AwsIotMqtt_Assert( pOperation->notify.callback.function != NULL ); - - AwsIotLogDebug( "Operation %s received from queue. Invoking user callback.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - /* Set callback parameters. */ - callbackParam.mqttConnection = pOperation->pMqttConnection; - callbackParam.operation.type = pOperation->operation; - callbackParam.operation.reference = pOperation; - callbackParam.operation.result = pOperation->status; - - /* Invoke user callback function. */ - pOperation->notify.callback.function( pOperation->notify.callback.param1, - &callbackParam ); - - /* Once the user-provided callback returns, the operation can be destroyed. */ - AwsIotMqttInternal_DestroyOperation( pOperation ); + match = ( *( pParam->pPacketIdentifier ) == pOperation->packetIdentifier ); } } - AwsIotLogDebug( "Callback queue empty. Callback thread terminating." ); + return match; } /*-----------------------------------------------------------*/ @@ -390,12 +195,14 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio } } - AwsIotMutex_Lock( &( pMqttConnection->timerEventList.mutex ) ); + /* Lock the connection timer mutex to block the timer thread. */ + AwsIotMutex_Lock( &( pMqttConnection->timerMutex ) ); /* Peek the current head of the timer event list. If the PUBLISH retry expires * sooner, re-arm the timer to expire at the PUBLISH retry's expiration time. */ - pTimerListHead = AwsIotLink_Container( pMqttConnection->timerEventList.pHead, - _TIMER_EVENT_LINK_OFFSET ); + pTimerListHead = IotLink_Container( _mqttTimerEvent_t, + IotListDouble_PeekHead( &( pMqttConnection->timerEventList ) ), + link ); if( ( pTimerListHead == NULL ) || ( pTimerListHead->expirationTime > pPublishRetry->expirationTime ) ) @@ -414,66 +221,244 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio * is guaranteed to expire before its expiration time. */ if( status == true ) { - AwsIotList_InsertSorted( &( pMqttConnection->timerEventList ), - &( pPublishRetry->link ), - _TIMER_EVENT_LINK_OFFSET, - AwsIotMqttInternal_TimerEventCompare ); + IotListDouble_InsertSorted( &( pMqttConnection->timerEventList ), + &( pPublishRetry->link ), + AwsIotMqttInternal_TimerEventCompare ); } - AwsIotMutex_Unlock( &( pMqttConnection->timerEventList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->timerMutex ) ); return status; } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_CreateQueues( void ) +static void _invokeSend( _mqttOperation_t * const pOperation ) { - AwsIotQueueNotifyParams_t notifyParams = { 0 }; + bool waitableOperation = false; - /* Create the send queue. */ - notifyParams.notifyRoutine = _sendThread; + AwsIotLogDebug( "Operation %s received from queue. Sending data over network.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + /* The received operation should be waiting for a status and have a valid packet. */ + AwsIotMqtt_Assert( pOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + AwsIotMqtt_Assert( pOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pOperation->packetSize != 0 ); + + /* Check if this is a waitable operation. */ + waitableOperation = ( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) + == AWS_IOT_MQTT_FLAG_WAITABLE ); - if( AwsIotQueue_Create( &_AwsIotMqttSendQueue, - ¬ifyParams, - AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) + /* Transmit the MQTT packet from the operation over the network. */ + if( pOperation->pMqttConnection->network.send( pOperation->pMqttConnection->network.pSendContext, + pOperation->pMqttPacket, + pOperation->packetSize ) != pOperation->packetSize ) { - return AWS_IOT_MQTT_INIT_FAILED; + /* Transmission failed. */ + AwsIotLogError( "Failed to send data for operation %s.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + pOperation->status = AWS_IOT_MQTT_SEND_ERROR; + } + else + { + /* DISCONNECT operations are considered successful upon successful + * transmission. */ + if( pOperation->operation == AWS_IOT_MQTT_DISCONNECT ) + { + pOperation->status = AWS_IOT_MQTT_SUCCESS; + } + /* Calculate the details of the next PUBLISH retry event. */ + else if( pOperation->pPublishRetry != NULL ) + { + /* Only a PUBLISH may have a retry event. */ + AwsIotMqtt_Assert( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + + if( _calculateNextPublishRetry( pOperation->pMqttConnection, + pOperation->pPublishRetry ) == false ) + { + /* PUBLISH retry failed to re-arm connection timer. Retransmission + * will not be sent. */ + pOperation->status = AWS_IOT_MQTT_SEND_ERROR; + } + } } - /* Create the receive queue. */ - if( AwsIotQueue_Create( &_AwsIotMqttReceiveQueue, - NULL, - 0 ) == false ) + /* Once the MQTT packet has been sent, it may be freed if it will not be + * retransmitted and it's not a PINGREQ. Additionally, the entire operation + * may be destroyed if no notification method is set. */ + if( ( pOperation->pPublishRetry == NULL ) && + ( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) ) { - AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); + /* Choose a free packet function. */ + void ( * freePacket )( uint8_t * ) = AwsIotMqttInternal_FreePacket; - return AWS_IOT_MQTT_INIT_FAILED; + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pOperation->pMqttConnection->network.freePacket != NULL ) + { + freePacket = pOperation->pMqttConnection->network.freePacket; + } + #endif + + freePacket( pOperation->pMqttPacket ); + pOperation->pMqttPacket = NULL; + + if( ( waitableOperation == false ) && + ( pOperation->notify.callback.function == NULL ) ) + { + AwsIotMqttInternal_DestroyOperation( pOperation ); + + return; + } } - /* Create the callback queue. */ - notifyParams.notifyRoutine = _callbackThread; + /* If a status was set by this function, notify of completion. */ + if( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) + { + AwsIotMqttInternal_Notify( pOperation ); + } + /* Otherwise, add operation to the list of MQTT operations pending responses. */ + else + { + AwsIotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); + IotListDouble_InsertTail( &( _IotMqttPendingResponse ), &( pOperation->link ) ); + AwsIotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); + } - if( AwsIotQueue_Create( &_AwsIotMqttCallbackQueue, - ¬ifyParams, - AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) + /* Notify a waitable operation that it has been sent. */ + if( waitableOperation == true ) { - AwsIotQueue_Destroy( &_AwsIotMqttSendQueue ); - AwsIotQueue_Destroy( &_AwsIotMqttReceiveQueue ); + AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + } +} + +/*-----------------------------------------------------------*/ + +static void _invokeCallback( _mqttOperation_t * const pOperation ) +{ + _mqttOperation_t * i = NULL, * pCurrent = NULL; + AwsIotMqttCallbackParam_t callbackParam = { .operation = { 0 } }; + + if( pOperation->incomingPublish == true ) + { + /* Set the pointer to the first PUBLISH. */ + i = pOperation; + + /* Process each PUBLISH in the operation. */ + while( i != NULL ) + { + /* Save a pointer to the current PUBLISH and move the iterating + * pointer. */ + pCurrent = i; + i = i->pNextPublish; + + /* Process the current PUBLISH. */ + if( ( pCurrent->publishInfo.pPayload != NULL ) && + ( pCurrent->publishInfo.pTopicName != NULL ) ) + { + callbackParam.message.info = pCurrent->publishInfo; + + AwsIotMqttInternal_ProcessPublish( pCurrent->pMqttConnection, + &callbackParam ); + } + + /* Free any buffers associated with the current PUBLISH message. */ + if( pCurrent->freeReceivedData != NULL ) + { + AwsIotMqtt_Assert( pCurrent->pReceivedData != NULL ); + pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); + } - return AWS_IOT_MQTT_INIT_FAILED; + /* Free the current PUBLISH. */ + AwsIotMqtt_FreeOperation( pCurrent ); + } } + else + { + /* Operations with no callback should not have been passed. */ + AwsIotMqtt_Assert( pOperation->notify.callback.function != NULL ); - return AWS_IOT_MQTT_SUCCESS; + AwsIotLogDebug( "Operation %s received from queue. Invoking user callback.", + AwsIotMqtt_OperationType( pOperation->operation ) ); + + /* Set callback parameters. */ + callbackParam.mqttConnection = pOperation->pMqttConnection; + callbackParam.operation.type = pOperation->operation; + callbackParam.operation.reference = pOperation; + callbackParam.operation.result = pOperation->status; + + /* Invoke user callback function. */ + pOperation->notify.callback.function( pOperation->notify.callback.param1, + &callbackParam ); + + /* Once the user-provided callback returns, the operation can be destroyed. */ + AwsIotMqttInternal_DestroyOperation( pOperation ); + } } /*-----------------------------------------------------------*/ -int AwsIotMqttInternal_TimerEventCompare( void * pData1, - void * pData2 ) +static void _processOperation( void * pArgument ) { - _mqttTimerEvent_t * pTimerEvent1 = ( _mqttTimerEvent_t * ) pData1, - *pTimerEvent2 = ( _mqttTimerEvent_t * ) pData2; + _mqttOperation_t * pOperation = NULL; + + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + AwsIotLogDebug( "New thread for processing MQTT operations started." ); + + while( true ) + { + AwsIotLogDebug( "Removing oldest operation from MQTT pending operations." ); + + /* Remove the oldest operation from the queue of pending MQTT operations. */ + AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + + pOperation = IotLink_Container( _mqttOperation_t, + IotQueue_Dequeue( &( _IotMqttPendingProcessing ) ), + link ); + + /* If no operation was received, this thread will terminate. Increment + * number of available threads. */ + if( pOperation == NULL ) + { + AwsIotSemaphore_Post( &( _IotMqttAvailableThreads ) ); + } + + AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + + /* Terminate thread if no operation was received. */ + if( pOperation == NULL ) + { + break; + } + + /* Invoke user callbacks for incoming PUBLISH messages or completed + * operations. Otherwise, send the operation's MQTT packet. */ + if( ( pOperation->incomingPublish == true ) || ( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) ) + { + _invokeCallback( pOperation ); + } + else + { + _invokeSend( pOperation ); + } + } + + AwsIotLogDebug( "No more pending operations. Terminating MQTT processing thread." ); +} + +/*-----------------------------------------------------------*/ + +int AwsIotMqttInternal_TimerEventCompare( const IotLink_t * const pTimerEventLink1, + const IotLink_t * const pTimerEventLink2 ) +{ + const _mqttTimerEvent_t * pTimerEvent1 = IotLink_Container( _mqttTimerEvent_t, + pTimerEventLink1, + link ); + const _mqttTimerEvent_t * pTimerEvent2 = IotLink_Container( _mqttTimerEvent_t, + pTimerEventLink2, + link ); if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) { @@ -587,20 +572,17 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) if( ( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) && ( pOperation->pPublishRetry != NULL ) ) { - AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerEventList.mutex ) ); + AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); - /* Remove the timer event from the timer event list before freeing it. */ - if( AwsIotList_FindFirstMatch( pOperation->pMqttConnection->timerEventList.pHead, - _TIMER_EVENT_LINK_OFFSET, - pOperation->pPublishRetry, - NULL ) != NULL ) - { - AwsIotList_Remove( &( pOperation->pMqttConnection->timerEventList ), - &( pOperation->pPublishRetry->link ), - _TIMER_EVENT_LINK_OFFSET ); - } + /* Remove the timer event from the timer event list before freeing it. + * The return value of this function is not checked because it always + * equals the publish retry event or is NULL. */ + ( void ) IotListDouble_RemoveFirstMatch( &( pOperation->pMqttConnection->timerEventList ), + NULL, + NULL, + pOperation->pPublishRetry ); - AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerEventList.mutex ) ); + AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); AwsIotMqtt_FreeTimerEvent( pOperation->pPublishRetry ); } @@ -617,54 +599,47 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) +AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation ) { - /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected - * subscriptions are removed by the deserializer, so not removed here. */ - if( ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) && - ( pOperation->status != AWS_IOT_MQTT_SUCCESS ) && - ( pOperation->status != AWS_IOT_MQTT_SERVER_REFUSED ) ) - { - AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, - pOperation->packetIdentifier, - -1 ); - } + AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; - /* If the operation is waiting, post to its wait semaphore and return. */ - if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) - { - AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + /* The given operation must not already be queued. */ + AwsIotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); - return; - } + /* Lock mutex for exclusive access to pending operations list. */ + AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); - /* Add the operation to the callback queue if a callback was set. */ - if( pOperation->notify.callback.function != NULL ) + /* Add operation to pending operations queue. */ + IotQueue_Enqueue( &( _IotMqttPendingProcessing ), &( pOperation->link ) ); + + /* Check if a new thread can be created. */ + if( AwsIotSemaphore_TryWait( &( _IotMqttAvailableThreads ) ) == true ) { - if( AwsIotQueue_InsertHead( &_AwsIotMqttCallbackQueue, - &( pOperation->link ) ) != true ) + /* Create new thread. */ + if( AwsIot_CreateDetachedThread( _processOperation, NULL ) == false ) { - AwsIotLogWarn( "Failed to create new callback thread." ); + /* New thread could not be created. Remove enqueued operation and + * report error. */ + IotQueue_Remove( &( pOperation->link ) ); + AwsIotSemaphore_Post( &( _IotMqttAvailableThreads ) ); + status = AWS_IOT_MQTT_NO_MEMORY; } } - else - { - /* Destroy the operation if no callback was set. */ - AwsIotMqttInternal_DestroyOperation( pOperation ); - } + + AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + + return status; } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_OperationFind( AwsIotQueue_t * const pQueue, - AwsIotMqttOperationType_t operation, - const uint16_t * const pPacketIdentifier, - _mqttOperation_t ** const pOutput ) +_mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t operation, + const uint16_t * const pPacketIdentifier ) { _mqttOperation_t * pResult = NULL; _operationMatchParam_t param = { 0 }; - AwsIotLogDebug( "Searching for in-progress operation %s in MQTT receive queue.", + AwsIotLogDebug( "Searching for in-progress operation %s in MQTT operations pending response.", AwsIotMqtt_OperationType( operation ) ); if( pPacketIdentifier != NULL ) @@ -676,29 +651,68 @@ AwsIotMqttError_t AwsIotMqttInternal_OperationFind( AwsIotQueue_t * const pQueue param.operation = operation; param.pPacketIdentifier = pPacketIdentifier; - /* Find the first matching element in the queue. */ - pResult = AwsIotQueue_RemoveFirstMatch( pQueue, - _OPERATION_LINK_OFFSET, - ¶m, - _mqttOperation_match ); + /* Find the first matching element in the list. */ + AwsIotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); + pResult = IotLink_Container( _mqttOperation_t, + IotListDouble_RemoveFirstMatch( &( _IotMqttPendingResponse ), + NULL, + _mqttOperation_match, + ¶m ), + link ); + AwsIotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); /* The result will be NULL if no corresponding operation was found in the - * queue. */ + * list. */ if( pResult == NULL ) { AwsIotLogDebug( "In-progress operation %s not found.", AwsIotMqtt_OperationType( operation ) ); - - return AWS_IOT_MQTT_BAD_PARAMETER; + } + else + { + AwsIotLogDebug( "Found in-progress operation %s.", + AwsIotMqtt_OperationType( operation ) ); } - /* If a corresponding operation was found, set the output parameter. */ - *pOutput = pResult; + return pResult; +} - AwsIotLogDebug( "Found in-progress operation %s.", - AwsIotMqtt_OperationType( operation ) ); +/*-----------------------------------------------------------*/ - return AWS_IOT_MQTT_SUCCESS; +void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) +{ + /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected + * subscriptions are removed by the deserializer, so not removed here. */ + if( ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) && + ( pOperation->status != AWS_IOT_MQTT_SUCCESS ) && + ( pOperation->status != AWS_IOT_MQTT_SERVER_REFUSED ) ) + { + AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, + pOperation->packetIdentifier, + -1 ); + } + + /* If the operation is waitable, post to its wait semaphore and return. + * Otherwise, enqueue it for callback. */ + if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) + { + AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + } + else + { + if( pOperation->notify.callback.function != NULL ) + { + if( AwsIotMqttInternal_EnqueueOperation( pOperation ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to create new callback thread." ); + } + } + else + { + /* Destroy the operation if no callback was set. */ + AwsIotMqttInternal_DestroyOperation( pOperation ); + } + } } /*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/aws_iot_mqtt_subscription.c b/lib/source/mqtt/aws_iot_mqtt_subscription.c index 5660ee3655..e79bf47176 100644 --- a/lib/source/mqtt/aws_iot_mqtt_subscription.c +++ b/lib/source/mqtt/aws_iot_mqtt_subscription.c @@ -63,43 +63,37 @@ typedef struct _packetMatchParams * @brief Matches a topic name (from a publish) with a topic filter (from a * subscription). * - * @param[in] pArgument Pointer to a #_topicMatchParams_t. - * @param[in] pData Pointer to a #_mqttSubscription_t containing the topic filter - * and name to check. + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Pointer to a #_topicMatchParams_t. * * @return `true` if the arguments match the subscription topic filter; `false` * otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility with - * @ref list_function_findfirstmatch. */ -static bool _topicMatch( void * pArgument, - void * pData ); +static bool _topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); /** * @brief Matches a packet identifier and order. * - * @param[in] pArgument Pointer to a #_packetMatchParams_t. - * @param[in] pData Pointer to a #_mqttSubscription_t containing the packet identifier - * and order to check. + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Pointer to a #_packetMatchParams_t. * * @return `true` if the arguments match the subscription's packet info; `false` * otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility with - * @ref list_function_findfirstmatch. */ -static bool _packetMatch( void * pArgument, - void * pData ); +static bool _packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); /*-----------------------------------------------------------*/ -static bool _topicMatch( void * pArgument, - void * pData ) +static bool _topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) { uint16_t nameIndex = 0, filterIndex = 0; - _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pArgument; - _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; /* Extract the relevant strings and lengths from parameters. */ const char * const pTopicName = pParam->pTopicName; @@ -191,12 +185,14 @@ static bool _topicMatch( void * pArgument, /*-----------------------------------------------------------*/ -static bool _packetMatch( void * pArgument, - void * pData ) +static bool _packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) { bool match = false; - _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pArgument; - _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pMatch; /* Compare packet identifiers. */ if( pParam->packetIdentifier == pSubscription->packetInfo.identifier ) @@ -244,17 +240,19 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const _mqttSubscription_t * pNewSubscription = NULL; _topicMatchParams_t topicMatchParams = { .exactMatchOnly = true }; - AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); for( i = 0; i < subscriptionCount; i++ ) { /* Check if this topic filter is already registered. */ topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; - pNewSubscription = AwsIotList_FindFirstMatch( pMqttConnection->subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - _topicMatch ); + pNewSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ), + link ); if( pNewSubscription != NULL ) { @@ -275,7 +273,7 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const /* If memory allocation failed, remove all previously added subscriptions. */ if( pNewSubscription == NULL ) { - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, pSubscriptionList, i ); @@ -297,13 +295,12 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const pSubscriptionList[ i ].pTopicFilter, pSubscriptionList[ i ].topicFilterLength ); - AwsIotList_InsertHead( &( pMqttConnection->subscriptionList ), - &( pNewSubscription->link ), - _SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), + &( pNewSubscription->link ) ); } } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); return AWS_IOT_MQTT_SUCCESS; } @@ -314,7 +311,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio AwsIotMqttCallbackParam_t * const pCallbackParam ) { _mqttSubscription_t * pSubscription = NULL; - AwsIotLink_t * pStartPoint = NULL; + IotLink_t * pStartPoint = NULL; void * pParam1 = NULL; void ( * callbackFunction )( void *, @@ -328,16 +325,18 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio /* Prevent any other thread from modifying the subscription list while this * function is searching. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); - pStartPoint = pMqttConnection->subscriptionList.pHead; + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + pStartPoint = IotListDouble_PeekHead( &( pMqttConnection->subscriptionList ) ); /* Search the subscription list for all matching subscriptions. */ while( pStartPoint != NULL ) { - pSubscription = AwsIotList_FindFirstMatch( pStartPoint, - _SUBSCRIPTION_LINK_OFFSET, - ( void * ) ( &topicMatchParams ), - _topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + pStartPoint, + _topicMatch, + ( void * ) ( &topicMatchParams ) ), + link ); /* No subscription found. Exit loop. */ if( pSubscription == NULL ) @@ -357,7 +356,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio callbackFunction = pSubscription->callback.function; /* Unlock the subscription list mutex. */ - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); /* Set the members of the callback parameter. */ pCallbackParam->mqttConnection = pMqttConnection; @@ -368,7 +367,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio callbackFunction( pParam1, pCallbackParam ); /* Lock the subscription list mutex to decrement the reference count. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Decrement the reference count. It must still be positive. */ ( pSubscription->references )--; @@ -381,14 +380,12 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio * flag is set. */ if( ( pSubscription->references == 0 ) && ( pSubscription->unsubscribed == true ) ) { - AwsIotList_Remove( &( pMqttConnection->subscriptionList ), - &( pSubscription->link ), - _SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_Remove( &( pSubscription->link ) ); AwsIotMqtt_FreeSubscription( pSubscription ); } } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -403,11 +400,11 @@ void AwsIotMqttInternal_RemoveSubscriptionByPacket( _mqttConnection_t * const pM .order = order }; - AwsIotList_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - ( void * ) &packetMatchParams, - _packetMatch, - AwsIotMqtt_FreeSubscription ); + IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _packetMatch, + ( void * ) ( &packetMatchParams ), + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); } /*-----------------------------------------------------------*/ @@ -422,7 +419,7 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con /* Prevent any other thread from modifying the subscription list while this * function is running. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Find and remove each topic filter from the list. */ for( i = 0; i < subscriptionCount; i++ ) @@ -431,10 +428,12 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; topicMatchParams.exactMatchOnly = true; - pSubscription = AwsIotList_FindFirstMatch( pMqttConnection->subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - _topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ), + link ); if( pSubscription != NULL ) { @@ -452,15 +451,13 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con else { /* No subscription callbacks are using this subscription. Remove it. */ - AwsIotList_Remove( &( pMqttConnection->subscriptionList ), - &( pSubscription->link ), - _SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_Remove( &( pSubscription->link ) ); AwsIotMqtt_FreeSubscription( pSubscription ); } } } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -482,13 +479,15 @@ bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, /* Prevent any other thread from modifying the subscription list while this * function is running. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Search for a matching subscription. */ - pSubscription = AwsIotList_FindFirstMatch( pMqttConnection->subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - _topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ), + link ); /* Check if a matching subscription was found. */ if( pSubscription != NULL ) @@ -505,7 +504,7 @@ bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, status = true; } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionList.mutex ) ); + AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); return status; } diff --git a/lib/source/platform/posix/network/aws_iot_network_openssl.c b/lib/source/platform/posix/network/aws_iot_network_openssl.c index 349f7f2cc5..ecc865dc69 100644 --- a/lib/source/platform/posix/network/aws_iot_network_openssl.c +++ b/lib/source/platform/posix/network/aws_iot_network_openssl.c @@ -305,13 +305,11 @@ static void * _networkReceiveThread( void * pArgument ) * @brief Perform a DNS lookup of a host name and establish a TCP connection. * * @param[in] pHostName The host name to look up. - * @param[in] hostNameLength Length of `pHostName`. * @param[in] port Remote server port (in host byte order) for the TCP connection. * * @return A connected TCP socket number; -1 if the DNS lookup failed. */ static inline int _dnsLookup( const char * const pHostName, - size_t hostNameLength, uint16_t port ) { int status = 0, tcpSocket = -1; @@ -747,7 +745,6 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * { int tcpSocket = -1; AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; - const size_t hostNameLength = strlen( pHostName ); _connectionInfo_t * pConnectionInfo = NULL; /* Check parameters. */ @@ -779,7 +776,7 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * } /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ - tcpSocket = _dnsLookup( pHostName, hostNameLength, port ); + tcpSocket = _dnsLookup( pHostName, port ); if( tcpSocket == -1 ) { diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 9d26221071..1956d75495 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -333,7 +333,7 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec /* Lock the subscription list mutex to check for an existing subscription * object. */ - AwsIotMutex_Lock( &( _AwsIotShadowSubscriptions.mutex ) ); + AwsIotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ @@ -407,7 +407,7 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec } } - AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptions.mutex ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); return status; } @@ -581,23 +581,27 @@ static void _updatedCallbackWrapper( void * pArgument, AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) { - /* Create the Shadow pending operation list. */ - if( AwsIotList_Create( &_AwsIotShadowPendingOperations ) == false ) + /* Create the Shadow pending operation list mutex. */ + if( AwsIotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ) ) == false ) { AwsIotLogError( "Failed to create Shadow pending operation list." ); return AWS_IOT_SHADOW_INIT_FAILED; } - /* Create the Shadow subscription list. */ - if( AwsIotList_Create( &_AwsIotShadowSubscriptions ) == false ) + /* Create the Shadow subscription list mutex. */ + if( AwsIotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ) ) == false ) { AwsIotLogError( "Failed to create Shadow subscription list." ); - AwsIotList_Destroy( &_AwsIotShadowPendingOperations ); + AwsIotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); return AWS_IOT_SHADOW_INIT_FAILED; } + /* Create Shadow linear containers. */ + IotListDouble_Create( &( _AwsIotShadowPendingOperations ) ); + IotListDouble_Create( &( _AwsIotShadowSubscriptions ) ); + /* Save the MQTT timeout option. */ if( mqttTimeoutMs != 0 ) { @@ -613,23 +617,23 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) void AwsIotShadow_Cleanup( void ) { - /* Remove and free all items in the Shadow pending operation list, then destroy - * the list. */ - AwsIotList_RemoveAllMatches( &_AwsIotShadowPendingOperations, - _SHADOW_OPERATION_LINK_OFFSET, - NULL, - NULL, - AwsIotShadowInternal_DestroyOperation ); - AwsIotList_Destroy( &_AwsIotShadowPendingOperations ); - - /* Remove and free all items in the Shadow subscription list, then destroy - * the list. */ - AwsIotList_RemoveAllMatches( &_AwsIotShadowSubscriptions, - _SHADOW_SUBSCRIPTION_LINK_OFFSET, - NULL, - NULL, - AwsIotShadowInternal_DestroySubscription ); - AwsIotList_Destroy( &_AwsIotShadowSubscriptions ); + /* Remove and free all items in the Shadow pending operation list. */ + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), + AwsIotShadowInternal_DestroyOperation, + offsetof( _shadowOperation_t, link ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + + /* Remove and free all items in the Shadow subscription list. */ + AwsIotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotListDouble_RemoveAll( &( _AwsIotShadowSubscriptions ), + AwsIotShadowInternal_DestroySubscription, + offsetof( _shadowSubscription_t, link ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + + /* Destroy Shadow library mutexes. */ + AwsIotMutex_Destroy( &( _AwsIotShadowPendingOperationsMutex ) ); + AwsIotMutex_Destroy( &( _AwsIotShadowSubscriptionsMutex ) ); /* Restore the default MQTT timeout. */ _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; @@ -1045,19 +1049,17 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, } /* Remove the completed operation from the pending operation list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); - AwsIotList_Remove( &_AwsIotShadowPendingOperations, - &( pOperation->link ), - _SHADOW_OPERATION_LINK_OFFSET ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_Remove( &( pOperation->link ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ - AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); AwsIotShadowInternal_DecrementReferences( pOperation, pOperation->pSubscription->pTopicBuffer, NULL ); - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the output parameters for Shadow GET. */ if( ( pOperation->type == _SHADOW_GET ) && diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index b49e4ea722..2b268763f4 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -58,16 +58,14 @@ typedef struct _operationMatchParams * @brief Match a received Shadow response with a Shadow operation awaiting a * response. * - * @param[in] pArgument Pointer to an #_operationMatchParams_t. - * @param[in] pData Pointer to a #_shadowOperation_t to check. + * @param[in] pOperationLink Pointer to the link member of the #_shadowOperation_t + * to check. + * @param[in] pMatch Pointer to an #_operationMatchParams_t. * - * @return `true` if `pData` matches the received response; `false` otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility - * with @ref list_function_findfirstmatch. + * @return `true` if `pMatch` matches the received response; `false` otherwise. */ -static inline bool _shadowOperation_match( void * pArgument, - void * pData ); +static bool _shadowOperation_match( const IotLink_t * pOperationLink, + void * pMatch ); /** * @brief Common function for processing received Shadow responses. @@ -141,15 +139,22 @@ static void _updateCallback( void * pArgument, * @brief List of active Shadow operations awaiting a response from the Shadow * service. */ -AwsIotList_t _AwsIotShadowPendingOperations = { 0 }; +IotListDouble_t _AwsIotShadowPendingOperations = { 0 }; + +/** + * @brief Protects #_AwsIotShadowPendingOperations from concurrent access. + */ +AwsIotMutex_t _AwsIotShadowPendingOperationsMutex; /*-----------------------------------------------------------*/ -static inline bool _shadowOperation_match( void * pArgument, - void * pData ) +static bool _shadowOperation_match( const IotLink_t * pOperationLink, + void * pMatch ) { - _operationMatchParams_t * const pParam = ( _operationMatchParams_t * ) pArgument; - _shadowOperation_t * const pOperation = ( _shadowOperation_t * ) pData; + _shadowOperation_t * const pOperation = IotLink_Container( _shadowOperation_t, + pOperationLink, + link ); + _operationMatchParams_t * const pParam = ( _operationMatchParams_t * ) pMatch; _shadowSubscription_t * const pSubscription = pOperation->pSubscription; const char * pClientToken = NULL; size_t clientTokenLength = 0; @@ -231,20 +236,22 @@ static void _commonOperationCallback( _shadowOperationType_t type, } /* Lock the pending operations list for exclusive access. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Search for a matching pending operation. */ - pOperation = AwsIotList_FindFirstMatch( _AwsIotShadowPendingOperations.pHead, - _SHADOW_OPERATION_LINK_OFFSET, - ¶m, - _shadowOperation_match ); + pOperation = IotLink_Container( _shadowOperation_t, + IotListDouble_FindFirstMatch( &( _AwsIotShadowPendingOperations ), + NULL, + _shadowOperation_match, + ¶m ), + link ); /* Find and remove the first Shadow operation of the given type. */ if( pOperation == NULL ) { /* Operation is not pending. It may have already been processed. Return * without doing anything */ - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); AwsIotLogWarn( "Shadow %s callback received an unknown operation.", _pAwsIotShadowOperationNames[ type ] ); @@ -256,10 +263,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, /* Remove a non-waitable operation from the pending operation list. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { - AwsIotList_Remove( &_AwsIotShadowPendingOperations, - &( pOperation->link ), - _SHADOW_OPERATION_LINK_OFFSET ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + IotListDouble_Remove( &( pOperation->link ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } } @@ -327,7 +332,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, * this function's completion. */ if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } } @@ -634,7 +639,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } /* Lock the subscription list mutex for exclusive access. */ - AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ @@ -687,7 +692,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } /* Unlock the Shadow subscription list mutex. */ - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Check that all memory allocation and subscriptions succeeded. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -732,11 +737,10 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } /* Add Shadow operation to the pending operations list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); - AwsIotList_InsertHead( &_AwsIotShadowPendingOperations, - &( pOperation->link ), - _SHADOW_OPERATION_LINK_OFFSET ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), + &( pOperation->link ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Publish to the Shadow topic name. */ publishStatus = AwsIotMqtt_TimedPublish( pOperation->mqttConnection, @@ -767,19 +771,17 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ * count. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) { - AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); AwsIotShadowInternal_DecrementReferences( pOperation, pTopicBuffer, NULL ); - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); } /* Remove Shadow operation from the pending operations list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperations.mutex ) ); - AwsIotList_Remove( &_AwsIotShadowPendingOperations, - &( pOperation->link ), - _SHADOW_OPERATION_LINK_OFFSET ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperations.mutex ) ); + AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_Remove( &( pOperation->link ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } else { @@ -822,11 +824,11 @@ void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ) /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ - AwsIotMutex_Lock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); AwsIotShadowInternal_DecrementReferences( pOperation, pSubscription->pTopicBuffer, &pRemovedSubscription ); - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptions.mutex ); + AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the subscription pointer used for the user callback based on whether * a subscription was removed from the list. */ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 5599113d78..361e43af8a 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -39,7 +39,7 @@ /*-----------------------------------------------------------*/ /** - * @brief First parameter to #_shadowSubscription_compare. + * @brief First parameter to #_shadowSubscription_match. */ typedef struct _thingName { @@ -50,19 +50,16 @@ typedef struct _thingName /*-----------------------------------------------------------*/ /** - * @brief Compare two #_shadowSubscription_t by Thing Name. + * @brief Match two #_shadowSubscription_t by Thing Name. * - * @param[in] pArgument Pointer to a #_thingName_t. - * @param[in] pData Pointer to a #_shadowSubscription_t containing the Thing - * Name to check. + * @param[in] pSubscriptionLink Pointer to the link member of a #_shadowSubscription_t + * containing the Thing Name to check. + * @param[in] pMatch Pointer to a #_thingName_t. * * @return `true` if the Thing Names match; `false` otherwise. - * - * @note The arguments of this function are of type `void*` for compatibility - * with @ref list_function_findfirstmatch. */ -static inline bool _shadowSubscription_compare( void * pArgument, - void * pData ); +static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, + void * pMatch ); /** * @brief Modify Shadow subscriptions, either by unsubscribing or subscribing. @@ -88,25 +85,33 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t /** * @brief List of active Shadow subscriptions objects. */ -AwsIotList_t _AwsIotShadowSubscriptions = { 0 }; +IotListDouble_t _AwsIotShadowSubscriptions = { 0 }; + +/** + * @brief Protects #_AwsIotShadowSubscriptions from concurrent access. + */ +AwsIotMutex_t _AwsIotShadowSubscriptionsMutex; /*-----------------------------------------------------------*/ -static inline bool _shadowSubscription_compare( void * pArgument, - void * pData ) +static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, + void * pMatch ) { - const _thingName_t * const pThingName = ( _thingName_t * ) pArgument; - const _shadowSubscription_t * const pSubscription = ( _shadowSubscription_t * ) pData; + bool match = false; + const _shadowSubscription_t * const pSubscription = IotLink_Container( _shadowSubscription_t, + pSubscriptionLink, + link ); + const _thingName_t * const pThingName = ( _thingName_t * ) pMatch; if( pThingName->thingNameLength == pSubscription->thingNameLength ) { /* Check for matching Thing Names. */ - return( strncmp( pThingName->pThingName, - pSubscription->pThingName, - pThingName->thingNameLength ) == 0 ); + match = ( strncmp( pThingName->pThingName, + pSubscription->pThingName, + pThingName->thingNameLength ) == 0 ); } - return false; + return match; } /*-----------------------------------------------------------*/ @@ -186,10 +191,12 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons }; /* Search the list for an existing subscription for Thing Name. */ - pSubscription = AwsIotList_FindFirstMatch( _AwsIotShadowSubscriptions.pHead, - _SHADOW_SUBSCRIPTION_LINK_OFFSET, - &thingName, - _shadowSubscription_compare ); + pSubscription = IotLink_Container( _shadowSubscription_t, + IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), + NULL, + _shadowSubscription_match, + &thingName ), + link ); /* Check if a subscription was found. */ if( pSubscription == NULL ) @@ -207,9 +214,8 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons ( void ) strncpy( pSubscription->pThingName, pThingName, thingNameLength ); /* Add the new subscription to the subscription list. */ - AwsIotList_InsertHead( &_AwsIotShadowSubscriptions, - &( pSubscription->link ), - _SHADOW_SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ), + &( pSubscription->link ) ); AwsIotLogDebug( "Created new Shadow subscriptions object for %.*s.", thingNameLength, @@ -285,9 +291,7 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub /* No Shadow operation subscription references or active Shadow callbacks. * Remove the subscription object. */ - AwsIotList_Remove( &_AwsIotShadowSubscriptions, - &( pSubscription->link ), - _SHADOW_SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_Remove( &( pSubscription->link ) ); AwsIotLogDebug( "Removed subscription object for %.*s.", pSubscription->thingNameLength, @@ -551,13 +555,15 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec thingNameLength, pThingName ); - AwsIotMutex_Lock( &( _AwsIotShadowSubscriptions.mutex ) ); + AwsIotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Search the list for an existing subscription for Thing Name. */ - pSubscription = AwsIotList_FindFirstMatch( _AwsIotShadowSubscriptions.pHead, - _SHADOW_SUBSCRIPTION_LINK_OFFSET, - &thingName, - _shadowSubscription_compare ); + pSubscription = IotLink_Container( _shadowSubscription_t, + IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), + NULL, + _shadowSubscription_match, + &thingName ), + link ); /* Unsubscribe from operation subscriptions if found. */ if( pSubscription != NULL ) @@ -644,7 +650,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec pThingName ); } - AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptions.mutex ) ); + AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Check the results of the MQTT unsubscribes. */ if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 45b6c9474b..036da7a3f1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,9 @@ if( NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) message( FATAL_ERROR "Currently, tests must run on Linux. Detected system: ${CMAKE_SYSTEM_NAME}." ) endif() +# Common tests. +add_subdirectory( common ) + # MQTT tests. add_subdirectory( mqtt ) diff --git a/tests/aws_iot_tests_config.h b/tests/aws_iot_tests_config.h index 0d1d4f11aa..73c6cad31d 100644 --- a/tests/aws_iot_tests_config.h +++ b/tests/aws_iot_tests_config.h @@ -61,8 +61,8 @@ #define AWS_IOT_TEST_SHADOW_THING_NAME "" #endif -/* Queue library configuration. */ -#define AWS_IOT_QUEUE_ENABLE_ASSERTS ( 1 ) +/* Linear containers library configuration. */ +#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) /* Shadow library configuration. */ #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt new file mode 100644 index 0000000000..8460f99f52 --- /dev/null +++ b/tests/common/CMakeLists.txt @@ -0,0 +1,7 @@ +# Common tests executable. +add_executable( iot_tests_common + iot_tests_common.c + unit/iot_tests_linear_containers.c ) + +# Common tests library dependencies. +target_link_libraries( iot_tests_common unity ) diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c new file mode 100644 index 0000000000..df9dbae977 --- /dev/null +++ b/tests/common/iot_tests_common.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_common.c + * @brief Test runner for the common tests (linear containers, task pool, etc.) + * on POSIX systems. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler. Terminates the tests if called. + */ +static void _signalHandler( int signum ) +{ + /* Immediately terminate the tests if this signal handler is called. */ + if( signum == SIGSEGV ) + { + printf( "\nSegmentation fault.\n" ); + exit( EXIT_FAILURE ); + } + else if( signum == SIGABRT ) + { + printf( "\nAssertion failed.\n" ); + exit( EXIT_FAILURE ); + } +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + struct sigaction signalAction; + + /* Silence warnings about unused parameters. */ + ( void ) argc; + ( void ) argv; + + /* Set a signal handler for segmentation faults and assertion failures. */ + ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); + signalAction.sa_handler = _signalHandler; + + if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) + { + return -1; + } + + if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + { + return -1; + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Run linear containers tests. */ + RUN_TEST_GROUP( Common_Unit_Linear_Containers ); + + /* Return the number of test failures. This will cause a non-zero exit code + * if any test fails. */ + return UNITY_END(); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/common/unit/iot_tests_linear_containers.c b/tests/common/unit/iot_tests_linear_containers.c new file mode 100644 index 0000000000..57cd6e4991 --- /dev/null +++ b/tests/common/unit/iot_tests_linear_containers.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_linear_containers.c + * @brief Tests for linear containers (lists and queues). + */ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Linear containers include. */ +#include "iot_linear_containers.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for linear containers tests. + */ +TEST_GROUP( Common_Unit_Linear_Containers ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for linear containers tests. + */ +TEST_SETUP( Common_Unit_Linear_Containers ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for linear containers. + */ +TEST_TEAR_DOWN( Common_Unit_Linear_Containers ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for linear containers. + */ +TEST_GROUP_RUNNER( Common_Unit_Linear_Containers ) +{ + RUN_TEST_CASE( Common_Unit_Linear_Containers, ListQueueEmpty ); +} + +/*-----------------------------------------------------------*/ + +TEST( Common_Unit_Linear_Containers, ListQueueEmpty ) +{ + IotListDouble_t list = IOT_LIST_DOUBLE_INITIALIZER; + IotQueue_t queue = IOT_QUEUE_INITIALIZER; + + /* Create an empty list. */ + IotListDouble_Create( &list ); + + /* Check appropriate return values for an empty list. */ + TEST_ASSERT_EQUAL( 0, IotListDouble_Count( &list ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &list ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_PeekHead( &list ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_PeekTail( &list ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveHead( &list ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveTail( &list ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_FindFirstMatch( &list, NULL, NULL, 0 ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveFirstMatch( &list, NULL, NULL, 0 ) ); + + /* Create an empty queue. */ + IotQueue_Create( &queue ); + + /* Check appropriate return values for an empty queue. */ + TEST_ASSERT_EQUAL( 0, IotQueue_Count( &queue ) ); + TEST_ASSERT_EQUAL_INT( true, IotQueue_IsEmpty( &queue ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotQueue_Peek( &queue ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotQueue_Dequeue( &queue ) ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt.h b/tests/mqtt/access/aws_iot_test_access_mqtt.h index d922a759e7..964c1551b1 100644 --- a/tests/mqtt/access/aws_iot_test_access_mqtt.h +++ b/tests/mqtt/access/aws_iot_test_access_mqtt.h @@ -92,15 +92,15 @@ typedef struct _packetMatchParams * * @see #_topicMatch. */ -bool AwsIotTestMqtt_topicMatch( void * pArgument, - void * pData ); +bool AwsIotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); /** * @brief Test access function for #_packetMatch. * * @see #_packetMatch. */ -bool AwsIotTestMqtt_packetMatch( void * pArgument, - void * pData ); +bool AwsIotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); #endif /* ifndef _AWS_IOT_TEST_ACCESS_MQTT_H_ */ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c b/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c index 9febbcd188..b720b3ece9 100644 --- a/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c +++ b/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c @@ -30,18 +30,18 @@ /*-----------------------------------------------------------*/ -bool AwsIotTestMqtt_topicMatch( void * pArgument, - void * pData ) +bool AwsIotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) { - return _topicMatch( pArgument, pData ); + return _topicMatch( pSubscriptionLink, pMatch ); } /*-----------------------------------------------------------*/ -bool AwsIotTestMqtt_packetMatch( void * pArgument, - void * pData ) +bool AwsIotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) { - return _packetMatch( pArgument, pData ); + return _packetMatch( pSubscriptionLink, pMatch ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 78870c570e..40c6cd68b7 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -506,11 +506,7 @@ TEST_GROUP_RUNNER( MQTT_System ) RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS1 ); RUN_TEST_CASE( MQTT_System, SubscribePublishAsync ); RUN_TEST_CASE( MQTT_System, LastWillAndTestament ); - - /* Persistent sessions are currently unsupported by AWS IoT. */ - #if _AWS_IOT_MQTT_SERVER == false - RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); - #endif + RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index ba563de69c..035dcb8fc8 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -852,8 +852,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - TEST_ASSERT_EQUAL_INT( true, - AwsIotList_Create( &( mqttConnection.subscriptionList ) ) ); + IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) { @@ -884,15 +883,12 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) } /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_PTR( NULL, mqttConnection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( mqttConnection.subscriptionList ) ) ); } - AwsIotList_RemoveAllMatches( &( mqttConnection.subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - NULL, - NULL, - AwsIotMqtt_FreeSubscription ); - AwsIotList_Destroy( &( mqttConnection.subscriptionList ) ); + IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); } /*-----------------------------------------------------------*/ @@ -915,8 +911,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - TEST_ASSERT_EQUAL_INT( true, - AwsIotList_Create( &( mqttConnection.subscriptionList ) ) ); + IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) { @@ -947,15 +942,12 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) } /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_PTR( NULL, mqttConnection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( mqttConnection.subscriptionList ) ) ); } - AwsIotList_RemoveAllMatches( &( mqttConnection.subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - NULL, - NULL, - AwsIotMqtt_FreeSubscription ); - AwsIotList_Destroy( &( mqttConnection.subscriptionList ) ); + IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c index 5bc0d95b9a..bf0ab5968e 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -362,12 +362,13 @@ static AwsIotMqttError_t _deserializePingresp( const uint8_t * const pPingrespSt /*-----------------------------------------------------------*/ /** - * @brief Reset the status of an #_mqttOperation_t and pushing it to the receive queue. + * @brief Reset the status of an #_mqttOperation_t and push it to the queue of + * MQTT operations awaiting network response. */ static void _operationResetAndPush( _mqttOperation_t * const pOperation ) { pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotQueue_InsertHead( &_AwsIotMqttReceiveQueue, &( pOperation->link ) ); + IotQueue_Enqueue( &( _IotMqttPendingResponse ), &( pOperation->link ) ); } /*-----------------------------------------------------------*/ @@ -421,9 +422,12 @@ static bool _processPublish( const uint8_t * const pPublish, } /* Set the subscription parameter. */ - if( _pMqttConnection->subscriptionList.pHead != NULL ) + if( IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) == false ) { - ( ( _mqttSubscription_t * ) ( _pMqttConnection->subscriptionList.pHead ) )->callback.param1 = &invokeCount; + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ), + link ); + pSubscription->callback.param1 = &invokeCount; } /* Call the receive callback on pPublish. */ @@ -562,11 +566,6 @@ TEST_SETUP( MQTT_Unit_Receive ) TEST_TEAR_DOWN( MQTT_Unit_Receive ) { /* Clean up resources taken in test setup. */ - AwsIotList_RemoveAllMatches( &( _pMqttConnection->subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - NULL, - NULL, - AwsIotMqtt_FreeSubscription ); AwsIotTestMqtt_destroyMqttConnection( _pMqttConnection ); AwsIotMqtt_Cleanup(); AwsIotSemaphore_Destroy( &_mallocSemaphore ); @@ -1031,8 +1030,10 @@ TEST( MQTT_Unit_Receive, PublishValid ) * all bytes of the PUBLISH should still be processed (should not crash). */ { _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - AwsIotMqtt_FreeSubscription( _pMqttConnection->subscriptionList.pHead ); - _pMqttConnection->subscriptionList.pHead = NULL; + IotListDouble_RemoveAll( &( _pMqttConnection->subscriptionList ), + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, ( int32_t ) publishSize, @@ -1341,6 +1342,12 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) AWS_IOT_MQTT_STATUS_PENDING ) ); } + /* Remove unprocessed PUBLISH if present. */ + if( IotLink_IsLinked( &( publish.link ) ) == true ) + { + IotQueue_Remove( &( publish.link ) ); + } + AwsIotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); } @@ -1379,12 +1386,14 @@ TEST( MQTT_Unit_Receive, SubackValid ) 2 ) ); /* Set orders 2 and 1 for the new subscriptions. */ - pNewSubscription = AwsIotLink_Container( _pMqttConnection->subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET ); + pNewSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ), + link ); pNewSubscription->packetInfo.order = 2; - pNewSubscription = AwsIotLink_Container( pNewSubscription->link.pNext, - _SUBSCRIPTION_LINK_OFFSET ); + pNewSubscription = IotLink_Container( _mqttSubscription_t, + pNewSubscription->link.pNext, + link ); pNewSubscription->packetInfo.order = 1; /* Even though no SUBSCRIBE is in the receive queue, all bytes of the SUBACK @@ -1642,6 +1651,12 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) AWS_IOT_MQTT_STATUS_PENDING ) ); } + /* Remove unprocessed UNSUBSCRIBE if present. */ + if( IotLink_IsLinked( &( unsubscribe.link ) ) == true ) + { + IotQueue_Remove( &( unsubscribe.link ) ); + } + AwsIotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); } diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c index 6ae00c2e63..40ef3b6d8c 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c @@ -55,7 +55,10 @@ * Including stdio.h also brings in unwanted (and conflicting) symbols on some * platforms. Therefore, any functions in stdio.h needed in this file have an * extern declaration here. */ -extern int snprintf( char *, size_t, const char *, ... ); +extern int snprintf( char *, + size_t, + const char *, + ... ); /** @endcond */ /*-----------------------------------------------------------*/ @@ -68,7 +71,7 @@ extern int snprintf( char *, size_t, const char *, ... ); #else #define _AWS_IOT_MQTT_SERVER false - /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif @@ -114,19 +117,19 @@ extern int snprintf( char *, size_t, const char *, ... ); * @note This macro may only be used when a #_mqttSubscription_t pointer named pTopicFilter * is in scope. */ -#define _TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ - { \ - _topicMatchParams_t _topicMatchParams = { 0 }; \ - _topicMatchParams.pTopicName = topicNameString; \ - _topicMatchParams.topicNameLength = ( uint16_t ) strlen( _topicMatchParams.pTopicName ); \ - _topicMatchParams.exactMatchOnly = exactMatch; \ - \ - pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ - _TOPIC_FILTER_MATCH_MAX_LENGTH, \ - topicFilterString ); \ - \ - TEST_ASSERT_EQUAL_INT( expectedResult, \ - AwsIotTestMqtt_topicMatch( &_topicMatchParams, pTopicFilter ) ); \ +#define _TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ + { \ + _topicMatchParams_t _topicMatchParams = { 0 }; \ + _topicMatchParams.pTopicName = topicNameString; \ + _topicMatchParams.topicNameLength = ( uint16_t ) strlen( _topicMatchParams.pTopicName ); \ + _topicMatchParams.exactMatchOnly = exactMatch; \ + \ + pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ + _TOPIC_FILTER_MATCH_MAX_LENGTH, \ + topicFilterString ); \ + \ + TEST_ASSERT_EQUAL_INT( expectedResult, \ + AwsIotTestMqtt_topicMatch( &( pTopicFilter->link ), &_topicMatchParams ) ); \ } /*-----------------------------------------------------------*/ @@ -165,9 +168,8 @@ static void _populateList( void ) _TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); - AwsIotList_InsertHead( &( _connection.subscriptionList ), - &( pSubscription->link), - _SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_InsertHead( &( _connection.subscriptionList ), + &( pSubscription->link ) ); } } @@ -282,8 +284,7 @@ TEST_GROUP( MQTT_Unit_Subscription ); */ TEST_SETUP( MQTT_Unit_Subscription ) { - TEST_ASSERT_EQUAL_INT( true, - AwsIotList_Create( &( _connection.subscriptionList ) ) ); + IotListDouble_Create( &( _connection.subscriptionList ) ); } /*-----------------------------------------------------------*/ @@ -293,12 +294,10 @@ TEST_SETUP( MQTT_Unit_Subscription ) */ TEST_TEAR_DOWN( MQTT_Unit_Subscription ) { - AwsIotList_RemoveAllMatches( &( _connection.subscriptionList ), - _SUBSCRIPTION_LINK_OFFSET, - NULL, - NULL, - AwsIotMqtt_FreeSubscription ); - AwsIotList_Destroy( &( _connection.subscriptionList ) ); + IotListDouble_RemoveAll( &( _connection.subscriptionList ), + AwsIotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); + ( void ) memset( &_connection, 0x00, sizeof( _mqttConnection_t ) ); } @@ -334,27 +333,18 @@ TEST( MQTT_Unit_Subscription, ListInsertRemove ) _mqttSubscription_t node2 = { 0 }; _mqttSubscription_t node3 = { 0 }; - AwsIotList_InsertHead( &( _connection.subscriptionList ), - &( node1.link ), - _SUBSCRIPTION_LINK_OFFSET ); - AwsIotList_InsertHead( &( _connection.subscriptionList ), - &( node2.link ), - _SUBSCRIPTION_LINK_OFFSET ); - AwsIotList_InsertHead( &( _connection.subscriptionList ), - &( node3.link ), - _SUBSCRIPTION_LINK_OFFSET ); - - AwsIotList_Remove( &( _connection.subscriptionList ), - &( node1.link ), - _SUBSCRIPTION_LINK_OFFSET ); - AwsIotList_Remove( &( _connection.subscriptionList ), - &( node2.link ), - _SUBSCRIPTION_LINK_OFFSET ); - AwsIotList_Remove( &( _connection.subscriptionList ), - &( node3.link ), - _SUBSCRIPTION_LINK_OFFSET ); - - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + IotListDouble_InsertHead( &( _connection.subscriptionList ), + &( node1.link ) ); + IotListDouble_InsertHead( &( _connection.subscriptionList ), + &( node2.link ) ); + IotListDouble_InsertHead( &( _connection.subscriptionList ), + &( node3.link ) ); + + IotListDouble_Remove( &( node1.link ) ); + IotListDouble_Remove( &( node2.link ) ); + IotListDouble_Remove( &( node3.link ) ); + + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); } /*-----------------------------------------------------------*/ @@ -371,28 +361,34 @@ TEST( MQTT_Unit_Subscription, ListFindByTopicFilter ) topicMatchParams.topicNameLength = 6; /* On empty list. */ - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - AwsIotTestMqtt_topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_topicMatch, + &topicMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); _populateList(); /* Topic filter present. */ - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - AwsIotTestMqtt_topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_topicMatch, + &topicMatchParams ), + link ); TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Topic filter not present. */ topicMatchParams.pTopicName = "/notpresent"; topicMatchParams.topicNameLength = 11; - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - AwsIotTestMqtt_topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_topicMatch, + &topicMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); } @@ -410,45 +406,55 @@ TEST( MQTT_Unit_Subscription, ListFindByPacket ) packetMatchParams.order = 0; /* On empty list. */ - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &packetMatchParams, - AwsIotTestMqtt_packetMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_packetMatch, + &packetMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); _populateList(); /* Packet and order present. */ - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &packetMatchParams, - AwsIotTestMqtt_packetMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_packetMatch, + &packetMatchParams ), + link ); TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Packet present, order not present. */ packetMatchParams.order = _LIST_ITEM_COUNT; - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &packetMatchParams, - AwsIotTestMqtt_packetMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_packetMatch, + &packetMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); /* Packet not present, order present. */ packetMatchParams.packetIdentifier = 0; packetMatchParams.order = 0; - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &packetMatchParams, - AwsIotTestMqtt_packetMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_packetMatch, + &packetMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); /* Packet and order not present. */ packetMatchParams.packetIdentifier = 0; packetMatchParams.order = _LIST_ITEM_COUNT; - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &packetMatchParams, - AwsIotTestMqtt_packetMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_packetMatch, + &packetMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); } @@ -477,14 +483,14 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) } /* List should be empty. */ - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); /* Remove all subscriptions for a packet one-shot. */ _populateList(); AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, 1, -1 ); - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); } /*-----------------------------------------------------------*/ @@ -522,11 +528,11 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) } /* List should be empty. */ - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); /* Refill the list. */ _populateList(); - TEST_ASSERT_NOT_EQUAL( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( false, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); /* Removal all at once. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) @@ -543,7 +549,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) _LIST_ITEM_COUNT ); /* List should be empty. */ - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); } /*-----------------------------------------------------------*/ @@ -593,10 +599,12 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) topicMatchParams.pTopicName = "/test1"; topicMatchParams.topicNameLength = 6; topicMatchParams.exactMatchOnly = true; - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - AwsIotTestMqtt_topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_topicMatch, + &topicMatchParams ), + link ); TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Check that the information was changed. */ @@ -606,14 +614,14 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) TEST_ASSERT_EQUAL_PTR( &_connection, pSubscription->callback.param1 ); /* Check that a duplicate entry wasn't created. */ - AwsIotList_Remove( &( _connection.subscriptionList ), - &( pSubscription->link ), - _SUBSCRIPTION_LINK_OFFSET ); + IotListDouble_Remove( &( pSubscription->link ) ); AwsIotMqtt_FreeSubscription( pSubscription ); - pSubscription = AwsIotList_FindFirstMatch( _connection.subscriptionList.pHead, - _SUBSCRIPTION_LINK_OFFSET, - &topicMatchParams, - AwsIotTestMqtt_topicMatch ); + pSubscription = IotLink_Container( _mqttSubscription_t, + IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), + NULL, + AwsIotTestMqtt_topicMatch, + &topicMatchParams ), + link ); TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); } @@ -656,7 +664,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) } TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); } } @@ -718,7 +726,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) } /* The subscription list should be empty. */ - TEST_ASSERT_EQUAL_PTR( NULL, _connection.subscriptionList.pHead ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); } /* Destroy the synchronization barrier. */ From 2ac1803d8d73558a17980ea58b2b1c86d6848fa4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 22 Jan 2019 09:28:50 -0800 Subject: [PATCH 009/844] Revert send/callback to separate threads. (#258) --- doc/lib/mqtt.txt | 15 +- lib/include/iot_linear_containers.h | 2 +- lib/include/private/aws_iot_mqtt_internal.h | 31 +- lib/source/mqtt/aws_iot_mqtt_api.c | 282 ++++++++++-------- lib/source/mqtt/aws_iot_mqtt_operation.c | 79 ++--- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 4 + .../unit/aws_iot_tests_mqtt_subscription.c | 2 + tests/shadow/unit/aws_iot_tests_shadow_api.c | 6 +- 8 files changed, 256 insertions(+), 165 deletions(-) diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 7d20ddc9d1..3cd409b0cf 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -261,15 +261,24 @@ void AwsIotMqttInternal_CleanupSerializeAdditional( void ); @configrecommended The default value is strongly recommended.
@configdefault The default value of this setting depends on the platform. For example, when running on FreeRTOS with BLE support, this setting will be automatically enabled. Conversely, when running on Linux, this setting will be disabled. -@section AWS_IOT_MQTT_MAX_THREADS -@brief Set the maximum number of simultaneous threads the MQTT library may spawn. +@section AWS_IOT_MQTT_MAX_CALLBACK_THREADS +@brief Set the maximum number of simultaneous callback threads. -Threads process MQTT operations, such as sending MQTT packets or invoking callbacks for completed operations. This setting limits the maximum number of threads the MQTT library may have active at any time. +Callback threads process notifications from completed MQTT operations or incoming PUBLISH messages. Incoming notifications are queued, then removed from the queue and processed by any available callback threads. User callback functions are invoked from the callback threads. This means that user callback functions should avoid blocking, since blocking will make a callback thread unavailable and cause the queue of notifications to grow. A situation where all callback threads are blocked should certainly be avoided. @configpossible Any positive integer.
@configrecommended In most use cases, this value can be `1` or `2`. In high-throughput use cases with many MQTT operations or incoming PUBLISH messages, this value can be increased for better performance.
@configdefault `2` +@section AWS_IOT_MQTT_MAX_SEND_THREADS +@brief Set the number of simultaneous send threads. + +Send threads process outgoing network packets. Packets are queued, then removed from the queue and sent over the network by any available send threads. + +@configpossible Any positive integer
+@configrecommended `1`. Unless the [network interface send function](@ref AwsIotMqttNetIf_t.send) supports parallel sends, any value greater than `1` will not provide any performance benefit.
+@configdefault `1` + @section AWS_IOT_LOG_LEVEL_MQTT @brief Set the log level of the MQTT library. diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 037a60fa12..7a8b54b906 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -855,7 +855,7 @@ static inline void IotQueue_RemoveAll( IotQueue_t * const pQueue, size_t linkOffset ) /* @[declare_linear_containers_queue_removeall] */ { - return IotListDouble_RemoveAll( pQueue, freeElement, linkOffset ); + IotListDouble_RemoveAll( pQueue, freeElement, linkOffset ); } /** diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index 251726572e..9839cfc070 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -226,8 +226,11 @@ #ifndef AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES #define AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) #endif -#ifndef AWS_IOT_MQTT_MAX_THREADS - #define AWS_IOT_MQTT_MAX_THREADS ( 2 ) +#ifndef AWS_IOT_MQTT_MAX_CALLBACK_THREADS + #define AWS_IOT_MQTT_MAX_CALLBACK_THREADS ( 2 ) +#endif +#ifndef AWS_IOT_MQTT_MAX_SEND_THREADS + #define AWS_IOT_MQTT_MAX_SEND_THREADS ( 1 ) #endif #ifndef AWS_IOT_MQTT_TEST #define AWS_IOT_MQTT_TEST ( 0 ) @@ -420,13 +423,27 @@ typedef struct _mqttTimerEvent }; } _mqttTimerEvent_t; +/** + * @brief Holds waiting MQTT operations and manages threads that process them. + */ +typedef struct _mqttOperationQueue +{ + IotQueue_t queue; /**< @brief Queue of waiting MQTT operations. */ + + /** + * @brief Maintains a count of threads currently available to process this + * queue and provides a mechanism to wait for active callback threads to finish. + */ + AwsIotSemaphore_t availableThreads; +} _mqttOperationQueue_t; + /* Declarations of the structures keeping track of MQTT operations for internal * files. */ -extern IotQueue_t _IotMqttPendingProcessing; +extern _mqttOperationQueue_t _IotMqttCallback; +extern _mqttOperationQueue_t _IotMqttSend; +extern AwsIotMutex_t _IotMqttQueueMutex; extern IotListDouble_t _IotMqttPendingResponse; -extern AwsIotMutex_t _IotMqttPendingProcessingMutex; extern AwsIotMutex_t _IotMqttPendingResponseMutex; -extern AwsIotSemaphore_t _IotMqttAvailableThreads; /*-------------------- MQTT struct validation functions ---------------------*/ @@ -804,10 +821,12 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ); * @brief Enqueue an MQTT operation for processing. * * @param[in] pOperation The MQTT operation to enqueue. + * @param[in] pQueue The address of either #_IotMqttCallback or #_IotMqttSend. * * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. */ -AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation ); +AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation, + _mqttOperationQueue_t * const pQueue ); /** * @brief Search the list of MQTT operations pending responses using an operation diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index 227195528d..40db0ae6e4 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -45,8 +45,11 @@ #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 #error "AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." #endif -#if AWS_IOT_MQTT_MAX_THREADS <= 0 - #error "AWS_IOT_MQTT_MAX_THREADS cannot be 0 or negative." +#if AWS_IOT_MQTT_MAX_CALLBACK_THREADS <= 0 + #error "AWS_IOT_MQTT_MAX_CALLBACK_THREADS cannot be 0 or negative." +#endif +#if AWS_IOT_MQTT_MAX_SEND_THREADS <= 0 + #error "AWS_IOT_MQTT_MAX_SEND_THREADS cannot be 0 or negative." #endif #if AWS_IOT_MQTT_TEST != 0 && AWS_IOT_MQTT_TEST != 1 #error "AWS_IOT_MQTT_MQTT_TEST must be 0 or 1." @@ -110,6 +113,15 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, static void _processPublishRetryEvent( bool awsIotMqttMode, _mqttTimerEvent_t * const pPublishRetryEvent ); +/** + * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. + * + * @param[in] pMqttConnection Which connection the PUBACK should be sent over. + * @param[in] packetIdentifier Which packet identifier to include in PUBACK. + */ +static void _sendPuback( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier ); + /** * @brief Handles timer expirations for an MQTT connection. * @@ -174,15 +186,6 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio const AwsIotMqttCallbackInfo_t * const pCallbackInfo, AwsIotMqttReference_t * const pSubscriptionRef ); -/** - * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. - * - * @param[in] pMqttConnection Which connection the PUBACK should be sent over. - * @param[in] packetIdentifier Which packet identifier to include in PUBACK. - */ -static void _sendPuback( _mqttConnection_t * const pMqttConnection, - uint16_t packetIdentifier ); - /*-----------------------------------------------------------*/ /** @@ -257,10 +260,11 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, /* If keep-alive isn't waiting for PINGRESP, send PINGREQ. */ AwsIotLogDebug( "Sending PINGREQ." ); - /* Add the PINGREQ operation to the pending operations queue. */ - if( AwsIotMqttInternal_EnqueueOperation( pMqttConnection->pPingreqOperation ) != AWS_IOT_MQTT_SUCCESS ) + /* Add the PINGREQ operation to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pMqttConnection->pPingreqOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add PINGREQ to send queue." ); + AwsIotLogWarn( "Failed to enqueue PINGREQ for sending." ); status = false; } else @@ -399,10 +403,11 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, AWS_IOT_MQTT_RESPONSE_WAIT_MS ); } - /* Add the PUBLISH to the pending operations queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pOperation ) != AWS_IOT_MQTT_SUCCESS ) + /* Add the PUBLISH to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add PUBLISH retry to pending operations queue." ); + AwsIotLogWarn( "Failed to enqueue PUBLISH retry for sending." ); } } } @@ -410,6 +415,61 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, /*-----------------------------------------------------------*/ +static void _sendPuback( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier ) +{ + _mqttOperation_t * pPubackOperation = NULL; + + /* Choose a PUBACK serializer function. */ + AwsIotMqttError_t ( * serializePuback )( uint16_t, + uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializePuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.puback != NULL ) + { + serializePuback = pMqttConnection->network.serialize.puback; + } + #endif + + AwsIotLogDebug( "Sending PUBACK for received PUBLISH %hu.", packetIdentifier ); + + /* Create a PUBACK operation. */ + if( AwsIotMqttInternal_CreateOperation( &pPubackOperation, + 0, + NULL ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to create PUBACK operation." ); + + return; + } + + /* Generate a PUBACK packet from the packet identifier. */ + if( serializePuback( packetIdentifier, + &( pPubackOperation->pMqttPacket ), + &( pPubackOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to generate PUBACK packet." ); + AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + + return; + } + + /* Set the remaining members of the PUBACK operation and push it to the + * send queue for network transmission. */ + pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; + pPubackOperation->pMqttConnection = pMqttConnection; + + if( AwsIotMqttInternal_EnqueueOperation( pPubackOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotLogWarn( "Failed to enqueue PUBACK for sending." ); + AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + } +} + +/*-----------------------------------------------------------*/ + static void _timerThread( void * pArgument ) { _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pArgument; @@ -817,10 +877,11 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio /* Prevent another CONNECT operation from using the network. */ AwsIotMutex_Lock( &_connectMutex ); - /* Add the CONNECT operation to the pending operations queue. */ - if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation ) != AWS_IOT_MQTT_SUCCESS ) + /* Add the CONNECT operation to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to add CONNECT to pending operations queue." ); + AwsIotLogError( "Failed to enqueue CONNECT for sending." ); connectStatus = AWS_IOT_MQTT_NO_MEMORY; AwsIotMqttInternal_DestroyOperation( pConnectOperation ); } @@ -1002,10 +1063,11 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio *pSubscriptionRef = pSubscriptionOperation; } - /* Add the subscription operation to the pending operations queue. */ - if( AwsIotMqttInternal_EnqueueOperation( pSubscriptionOperation ) != AWS_IOT_MQTT_SUCCESS ) + /* Add the subscription operation to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pSubscriptionOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to add %s to pending operations queue.", + AwsIotLogError( "Failed to enqueue %s for sending.", AwsIotMqtt_OperationType( operation ) ); if( operation == AWS_IOT_MQTT_SUBSCRIBE ) @@ -1035,68 +1097,14 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio /*-----------------------------------------------------------*/ -static void _sendPuback( _mqttConnection_t * const pMqttConnection, - uint16_t packetIdentifier ) -{ - _mqttOperation_t * pPubackOperation = NULL; - - /* Choose a PUBACK serializer function. */ - AwsIotMqttError_t ( * serializePuback )( uint16_t, - uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializePuback; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.puback != NULL ) - { - serializePuback = pMqttConnection->network.serialize.puback; - } - #endif - - AwsIotLogDebug( "Sending PUBACK for received PUBLISH %hu.", packetIdentifier ); - - /* Create a PUBACK operation. */ - if( AwsIotMqttInternal_CreateOperation( &pPubackOperation, - 0, - NULL ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to create PUBACK operation." ); - - return; - } - - /* Generate a PUBACK packet from the packet identifier. */ - if( serializePuback( packetIdentifier, - &( pPubackOperation->pMqttPacket ), - &( pPubackOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to generate PUBACK packet." ); - AwsIotMqttInternal_DestroyOperation( pPubackOperation ); - - return; - } - - /* Set the remaining members of the PUBACK operation and push it to the - * pending operations queue. */ - pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; - pPubackOperation->pMqttConnection = pMqttConnection; - - if( AwsIotMqttInternal_EnqueueOperation( pPubackOperation ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to add PUBACK to pending operations queue." ); - AwsIotMqttInternal_DestroyOperation( pPubackOperation ); - } -} - -/*-----------------------------------------------------------*/ - AwsIotMqttError_t AwsIotMqtt_Init( void ) { AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; - /* Create mutex protecting list of operations pending processing. */ - if( AwsIotMutex_Create( &( _IotMqttPendingProcessingMutex ) ) == false ) + /* Create mutex protecting MQTT operation queues. */ + if( AwsIotMutex_Create( &( _IotMqttQueueMutex ) ) == false ) { - AwsIotLogError( "Failed to initialize MQTT library pending processing mutex." ); + AwsIotLogError( "Failed to initialize MQTT operation queue mutex." ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1106,7 +1114,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) if( AwsIotMutex_Create( &( _IotMqttPendingResponseMutex ) ) == false ) { AwsIotLogError( "Failed to initialize MQTT library pending response mutex." ); - AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1119,25 +1127,44 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) { AwsIotLogError( "Failed to initialize MQTT library connect mutex." ); AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; } } - /* Create semaphore that counts active MQTT library threads. */ + /* Create semaphores that count active MQTT library threads. */ if( status == AWS_IOT_MQTT_SUCCESS ) { - if( AwsIotSemaphore_Create( &( _IotMqttAvailableThreads ), - AWS_IOT_MQTT_MAX_THREADS, - AWS_IOT_MQTT_MAX_THREADS ) == false ) + /* Create semaphore that counts active callback threads. */ + if( AwsIotSemaphore_Create( &( _IotMqttCallback.availableThreads ), + AWS_IOT_MQTT_MAX_CALLBACK_THREADS, + AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) + { + AwsIotLogError( "Failed to initialize record of active MQTT callback threads." ); + status = AWS_IOT_MQTT_INIT_FAILED; + } + else + { + /* Create semaphore that counts active send threads. */ + if( AwsIotSemaphore_Create( &( _IotMqttSend.availableThreads ), + AWS_IOT_MQTT_MAX_SEND_THREADS, + AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) + { + AwsIotLogError( "Failed to initialize record of active MQTT send threads." ); + status = AWS_IOT_MQTT_INIT_FAILED; + + AwsIotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); + } + } + + /* Destroy previously created mutexes if thread counter semaphores could + * not be created. */ + if( status == AWS_IOT_MQTT_INIT_FAILED ) { - AwsIotLogError( "Failed to initialize record of active MQTT library threads." ); AwsIotMutex_Destroy( &( _connectMutex ) ); AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); - - status = AWS_IOT_MQTT_INIT_FAILED; + AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); } } @@ -1147,10 +1174,11 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) { AwsIotLogError( "Failed to initialize MQTT library serializer. " ); - AwsIotSemaphore_Destroy( &( _IotMqttAvailableThreads ) ); + AwsIotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); + AwsIotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); AwsIotMutex_Destroy( &( _connectMutex ) ); AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1159,7 +1187,8 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) /* Create MQTT library linear containers. */ if( status == AWS_IOT_MQTT_SUCCESS ) { - IotQueue_Create( &( _IotMqttPendingProcessing ) ); + IotQueue_Create( &( _IotMqttCallback.queue ) ); + IotQueue_Create( &( _IotMqttSend.queue ) ); IotListDouble_Create( &( _IotMqttPendingResponse ) ); AwsIotLogInfo( "MQTT library successfully initialized." ); @@ -1173,30 +1202,39 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) void AwsIotMqtt_Cleanup() { /* Wait for termination of any active MQTT library threads. */ - AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + + while( AwsIotSemaphore_GetCount( &( _IotMqttCallback.availableThreads ) ) > 0 ) + { + AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); + AwsIotSemaphore_Wait( &( _IotMqttCallback.availableThreads ) ); + AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + } - while( AwsIotSemaphore_GetCount( &( _IotMqttAvailableThreads ) ) > 0 ) + while( AwsIotSemaphore_GetCount( &( _IotMqttSend.availableThreads ) ) > 0 ) { - AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); - AwsIotSemaphore_Wait( &( _IotMqttAvailableThreads ) ); - AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); + AwsIotSemaphore_Wait( &( _IotMqttSend.availableThreads ) ); + AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); } - AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); /* This API requires all MQTT connections to be terminated. If the MQTT library - * linear containers are not empty, there is an active MQTT connection. The + * linear containers are not empty, there is an active MQTT connection and the * library cannot be safely shut down. */ - AwsIotMqtt_Assert( IotQueue_IsEmpty( &( _IotMqttPendingProcessing ) ) == true ); + AwsIotMqtt_Assert( IotQueue_IsEmpty( &( _IotMqttCallback.queue ) ) == true ); + AwsIotMqtt_Assert( IotQueue_IsEmpty( &( _IotMqttSend.queue ) ) == true ); AwsIotMqtt_Assert( IotListDouble_IsEmpty( &( _IotMqttPendingResponse ) ) == true ); /* Clean up MQTT library mutexes. */ AwsIotMutex_Destroy( &( _connectMutex ) ); AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); - /* Clean up active thread counter. */ - AwsIotSemaphore_Destroy( &( _IotMqttAvailableThreads ) ); + /* Clean up thread counter semaphores. */ + AwsIotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); + AwsIotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); /* Clean up MQTT serializer. */ AwsIotMqttInternal_CleanupSerialize(); @@ -1624,9 +1662,10 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, } } - if( AwsIotMqttInternal_EnqueueOperation( pFirstPublish ) != AWS_IOT_MQTT_SUCCESS ) + if( AwsIotMqttInternal_EnqueueOperation( pFirstPublish, + &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add PUBLISH to pending operations queue." ); + AwsIotLogWarn( "Failed to enqueue incoming PUBLISH for callback." ); } } @@ -1710,17 +1749,22 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, /* Lock the connection timer mutex to block the timer thread. */ AwsIotMutex_Lock( &( pMqttConnection->timerMutex ) ); - /* Purge all of this connection's operations, and timer events. */ - IotQueue_RemoveAllMatches( &( _IotMqttPendingProcessing ), + /* Purge all of this connection's pending operations and timer events. */ + IotQueue_RemoveAllMatches( &( _IotMqttSend.queue ), _mqttOperation_matchConnection, pMqttConnection, AwsIotMqttInternal_DestroyOperation, offsetof( _mqttOperation_t, link ) ); - IotListDouble_RemoveAllMatches( &( _IotMqttPendingProcessing ), + IotListDouble_RemoveAllMatches( &( _IotMqttPendingResponse ), _mqttOperation_matchConnection, pMqttConnection, AwsIotMqttInternal_DestroyOperation, offsetof( _mqttOperation_t, link ) ); + IotQueue_RemoveAllMatches( &( _IotMqttCallback.queue ), + _mqttOperation_matchConnection, + pMqttConnection, + AwsIotMqttInternal_DestroyOperation, + offsetof( _mqttOperation_t, link ) ); IotListDouble_RemoveAll( &( pMqttConnection->timerEventList ), AwsIotMqtt_FreeTimerEvent, offsetof( _mqttTimerEvent_t, link ) ); @@ -1774,10 +1818,11 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotMqtt_Assert( pDisconnectOperation->pMqttPacket != NULL ); AwsIotMqtt_Assert( pDisconnectOperation->packetSize > 0 ); - /* Add the DISCONNECT operation to the pending operations queue. */ - if( AwsIotMqttInternal_EnqueueOperation( pDisconnectOperation ) != AWS_IOT_MQTT_SUCCESS ) + /* Add the DISCONNECT operation to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pDisconnectOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to add DISCONNECT to pending operations queue." ); + AwsIotLogWarn( "Failed to enqueue DISCONNECT for sending." ); AwsIotMqttInternal_DestroyOperation( pDisconnectOperation ); } else @@ -2050,16 +2095,17 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, } /* Set the reference, if provided. This should be set before the publish - * is pushed to the pending operations queue to avoid a race condition. */ + * is pushed to the send queue to avoid a race condition. */ if( ( pPublishInfo->QoS > 0 ) && ( pPublishRef != NULL ) ) { *pPublishRef = pPublishOperation; } - /* Add the PUBLISH operation to the pending operations queue. */ - if( AwsIotMqttInternal_EnqueueOperation( pPublishOperation ) != AWS_IOT_MQTT_SUCCESS ) + /* Add the PUBLISH operation to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pPublishOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to add PUBLISH to pending operations queue." ); + AwsIotLogError( "Failed to enqueue PUBLISH for sending." ); AwsIotMqttInternal_DestroyOperation( pPublishOperation ); @@ -2072,8 +2118,8 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, return AWS_IOT_MQTT_NO_MEMORY; } - /* A QoS 0 PUBLISH is considered successful as soon as it's added to the - * pending operations queue. */ + /* A QoS 0 PUBLISH is considered successful as soon as it is added to the + * send queue. */ if( pPublishInfo->QoS == 0 ) { return AWS_IOT_MQTT_SUCCESS; diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index 6fbe092a9e..7fc101f052 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -104,26 +104,24 @@ static void _invokeCallback( _mqttOperation_t * const pOperation ); * in-progress operations, notifies of an operation's completion, or notifies * of an incoming PUBLISH message. * - * @param[in] pArgument Not used. + * @param[in] pArgument The address of either #_IotMqttCallback (to invoke a user + * callback) or #_IotMqttSend (to send an MQTT packet). */ static void _processOperation( void * pArgument ); /*-----------------------------------------------------------*/ /** - * Linear containers for in-progress MQTT operations. + * Queues of MQTT operations waiting to be processed. */ -IotQueue_t _IotMqttPendingProcessing = { 0 }; /**< @brief Queue of MQTT operations that are waiting to be processed (i.e. sent or parsed). */ +_mqttOperationQueue_t _IotMqttCallback = { 0 }; /**< @brief Queue of MQTT operations waiting for their callback to be invoked. */ +_mqttOperationQueue_t _IotMqttSend = { 0 }; /**< @brief Queue of MQTT operations waiting to be sent. */ +AwsIotMutex_t _IotMqttQueueMutex; /**< @brief Protects both #_IotMqttSend and #_IotMqttCallback from concurrent access. */ + +/* List of MQTT operations awaiting a network response. */ IotListDouble_t _IotMqttPendingResponse = { 0 }; /**< @brief List of MQTT operations awaiting a response from the MQTT server. */ -AwsIotMutex_t _IotMqttPendingProcessingMutex; /**< @brief Protects #_IotMqttPendingProcessing from concurrent access. */ AwsIotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ -/** - * @brief Maintains a count of threads currently available for the MQTT library - * and provides a mechanism to wait for active threads to finish. - */ -AwsIotSemaphore_t _IotMqttAvailableThreads; - /*-----------------------------------------------------------*/ static bool _mqttOperation_match( const IotLink_t * pOperationLink, @@ -240,11 +238,6 @@ static void _invokeSend( _mqttOperation_t * const pOperation ) AwsIotLogDebug( "Operation %s received from queue. Sending data over network.", AwsIotMqtt_OperationType( pOperation->operation ) ); - /* The received operation should be waiting for a status and have a valid packet. */ - AwsIotMqtt_Assert( pOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - AwsIotMqtt_Assert( pOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pOperation->packetSize != 0 ); - /* Check if this is a waitable operation. */ waitableOperation = ( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ); @@ -401,9 +394,11 @@ static void _invokeCallback( _mqttOperation_t * const pOperation ) static void _processOperation( void * pArgument ) { _mqttOperation_t * pOperation = NULL; + _mqttOperationQueue_t * pQueue = ( _mqttOperationQueue_t * ) pArgument; - /* Silence warnings about unused parameters. */ - ( void ) pArgument; + /* There are only two valid values for this function's argument. */ + AwsIotMqtt_Assert( ( pQueue == &( _IotMqttCallback ) ) || + ( pQueue == &( _IotMqttSend ) ) ); AwsIotLogDebug( "New thread for processing MQTT operations started." ); @@ -412,20 +407,20 @@ static void _processOperation( void * pArgument ) AwsIotLogDebug( "Removing oldest operation from MQTT pending operations." ); /* Remove the oldest operation from the queue of pending MQTT operations. */ - AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); pOperation = IotLink_Container( _mqttOperation_t, - IotQueue_Dequeue( &( _IotMqttPendingProcessing ) ), + IotQueue_Dequeue( &( pQueue->queue ) ), link ); /* If no operation was received, this thread will terminate. Increment * number of available threads. */ if( pOperation == NULL ) { - AwsIotSemaphore_Post( &( _IotMqttAvailableThreads ) ); + AwsIotSemaphore_Post( &( pQueue->availableThreads ) ); } - AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); /* Terminate thread if no operation was received. */ if( pOperation == NULL ) @@ -433,14 +428,23 @@ static void _processOperation( void * pArgument ) break; } - /* Invoke user callbacks for incoming PUBLISH messages or completed - * operations. Otherwise, send the operation's MQTT packet. */ - if( ( pOperation->incomingPublish == true ) || ( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) ) + /* Run thread routine based on given queue. */ + if( pQueue == &( _IotMqttCallback ) ) { + /* Only incoming PUBLISH messages and completed operations should invoke + * the callback routine. */ + AwsIotMqtt_Assert( ( pOperation->incomingPublish == true ) || + ( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) ); + _invokeCallback( pOperation ); } else { + /* Only operations with an allocated packet should be sent. */ + AwsIotMqtt_Assert( pOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + AwsIotMqtt_Assert( pOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pOperation->packetSize != 0 ); + _invokeSend( pOperation ); } } @@ -599,34 +603,40 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation ) +AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation, + _mqttOperationQueue_t * const pQueue ) { AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; /* The given operation must not already be queued. */ AwsIotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); - /* Lock mutex for exclusive access to pending operations list. */ - AwsIotMutex_Lock( &( _IotMqttPendingProcessingMutex ) ); + /* Check that the queue argument is either the callback queue or send queue. */ + AwsIotMqtt_Assert( ( pQueue == &( _IotMqttCallback ) ) || + ( pQueue == &( _IotMqttSend ) ) ); + + /* Lock mutex for exclusive access to queues. */ + AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); - /* Add operation to pending operations queue. */ - IotQueue_Enqueue( &( _IotMqttPendingProcessing ), &( pOperation->link ) ); + /* Add operation to queue. */ + IotQueue_Enqueue( &( pQueue->queue ), &( pOperation->link ) ); /* Check if a new thread can be created. */ - if( AwsIotSemaphore_TryWait( &( _IotMqttAvailableThreads ) ) == true ) + if( AwsIotSemaphore_TryWait( &( pQueue->availableThreads ) ) == true ) { /* Create new thread. */ - if( AwsIot_CreateDetachedThread( _processOperation, NULL ) == false ) + if( AwsIot_CreateDetachedThread( _processOperation, + pQueue ) == false ) { /* New thread could not be created. Remove enqueued operation and * report error. */ IotQueue_Remove( &( pOperation->link ) ); - AwsIotSemaphore_Post( &( _IotMqttAvailableThreads ) ); + AwsIotSemaphore_Post( &( pQueue->availableThreads ) ); status = AWS_IOT_MQTT_NO_MEMORY; } } - AwsIotMutex_Unlock( &( _IotMqttPendingProcessingMutex ) ); + AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); return status; } @@ -702,7 +712,8 @@ void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) { if( pOperation->notify.callback.function != NULL ) { - if( AwsIotMqttInternal_EnqueueOperation( pOperation ) != AWS_IOT_MQTT_SUCCESS ) + if( AwsIotMqttInternal_EnqueueOperation( pOperation, + &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) { AwsIotLogWarn( "Failed to create new callback thread." ); } diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index 035dcb8fc8..d33573ed33 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -852,6 +852,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; + TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) @@ -889,6 +890,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); + AwsIotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -911,6 +913,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; + TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) @@ -948,6 +951,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); + AwsIotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c index 40ef3b6d8c..e7895d2d59 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c @@ -284,6 +284,7 @@ TEST_GROUP( MQTT_Unit_Subscription ); */ TEST_SETUP( MQTT_Unit_Subscription ) { + TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &( _connection.subscriptionMutex ) ) ); IotListDouble_Create( &( _connection.subscriptionList ) ); } @@ -297,6 +298,7 @@ TEST_TEAR_DOWN( MQTT_Unit_Subscription ) IotListDouble_RemoveAll( &( _connection.subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); + AwsIotMutex_Destroy( &( _connection.subscriptionMutex ) ); ( void ) memset( &_connection, 0x00, sizeof( _mqttConnection_t ) ); } diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 9190e57f62..6e96c249a0 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -378,7 +378,7 @@ TEST_GROUP_RUNNER( Shadow_Unit_API ) TEST( Shadow_Unit_API, Init ) { /* Check that test set up set the default value. */ - TEST_ASSERT_EQUAL_UINT64( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); /* The Shadow library was already initialized by test set up. Clean it up * before running this test. */ @@ -386,11 +386,11 @@ TEST( Shadow_Unit_API, Init ) /* Set a MQTT timeout. */ AwsIotShadow_Init( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1 ); - TEST_ASSERT_EQUAL_UINT64( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotShadowMqttTimeoutMs ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotShadowMqttTimeoutMs ); /* Cleanup should restore the default MQTT timeout. */ AwsIotShadow_Cleanup(); - TEST_ASSERT_EQUAL_UINT64( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); /* Initialize the Shadow library for test clean up. */ AwsIotShadow_Init( 0 ); From a98f8f7ed0b2e49c29f4e9df44e13ea61d094fe4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 22 Jan 2019 14:39:24 -0800 Subject: [PATCH 010/844] Fix size of minimal PUBLISH packet. (#259) --- lib/source/mqtt/aws_iot_mqtt_serialize.c | 4 ++-- tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c index b69b47ddc7..bdb120b1d5 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -119,7 +119,7 @@ * Constants relating to PUBLISH and PUBACK packets, defined by MQTT * 3.1.1 spec. */ -#define _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ( 9 ) /**< @brief The size of the smallest valid PUBLISH packet. */ +#define _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid PUBLISH packet. */ #define _MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ #define _MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ @@ -1122,7 +1122,7 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p uint16_t packetIdentifier = 0; const uint8_t * pVariableHeader = NULL, * pPacketIdentifierHigh = NULL; - /* Ensure that at least 9 bytes are available. If not, this is an incomplete + /* Ensure that at least 5 bytes are available. If not, this is an incomplete * PUBLISH packet. */ if( dataLength < _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ) { diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c index bf0ab5968e..9b8f86c695 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -1051,12 +1051,12 @@ TEST( MQTT_Unit_Receive, PublishValid ) */ TEST( MQTT_Unit_Receive, PublishInvalid ) { - /* Attempting to process a packet smaller than 9 bytes should result in no - * bytes processed. 9 bytes is the minimum size of a PUBLISH. */ + /* Attempting to process a packet smaller than 5 bytes should result in no + * bytes processed. 5 bytes is the minimum size of a PUBLISH. */ { _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - 8, + 4, 0, 0 ) ); _freeWrapper( pPublish ); From 1e2251add06cdc7cb27d625038ee66c61b1c722a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 23 Jan 2019 11:32:27 -0800 Subject: [PATCH 011/844] Rename config define to IOT_CONFIG_FILE. (#260) --- CMakeLists.txt | 18 +++---- demos/aws_iot_demo.h | 4 +- demos/aws_iot_demo_mqtt.c | 4 +- demos/aws_iot_demo_shadow.c | 4 +- ...ws_iot_demo_config.h => iot_demo_config.h} | 13 +++-- demos/posix/aws_iot_demo_common_posix.c | 4 +- demos/posix/aws_iot_demo_mqtt_posix.c | 4 +- demos/posix/aws_iot_demo_posix.h | 2 +- demos/posix/aws_iot_demo_shadow_posix.c | 4 +- doc/config/common | 2 +- doc/guide/building.txt | 10 ++-- doc/guide/style.txt | 6 +-- doc/mainpage.txt | 4 +- lib/config/aws_iot_config_build.h.in | 51 ------------------- lib/include/aws_iot_logging_setup.h | 4 +- lib/include/aws_iot_mqtt.h | 4 +- lib/include/aws_iot_shadow.h | 4 +- lib/include/iot_linear_containers.h | 4 +- lib/include/platform/aws_iot_clock.h | 4 +- lib/include/platform/aws_iot_network.h | 4 +- lib/include/platform/aws_iot_static_memory.h | 4 +- lib/include/platform/aws_iot_threads.h | 8 +-- lib/include/private/aws_iot_logging.h | 4 +- lib/include/private/aws_iot_mqtt_internal.h | 4 +- lib/include/private/aws_iot_shadow_internal.h | 4 +- lib/source/common/CMakeLists.txt | 2 +- lib/source/common/aws_iot_json_utils.c | 4 +- lib/source/common/aws_iot_logging.c | 4 +- lib/source/mqtt/CMakeLists.txt | 2 +- lib/source/mqtt/aws_iot_mqtt_api.c | 4 +- lib/source/mqtt/aws_iot_mqtt_operation.c | 4 +- lib/source/mqtt/aws_iot_mqtt_serialize.c | 10 ++-- lib/source/mqtt/aws_iot_mqtt_subscription.c | 4 +- lib/source/mqtt/aws_iot_mqtt_validate.c | 4 +- lib/source/platform/posix/CMakeLists.txt | 15 ------ .../platform/posix/aws_iot_clock_posix.c | 4 +- .../platform/posix/aws_iot_threads_posix.c | 4 +- .../posix/network/aws_iot_network_openssl.c | 4 +- .../aws_iot_static_memory_common_posix.c | 4 +- .../aws_iot_static_memory_mqtt_posix.c | 4 +- .../aws_iot_static_memory_shadow_posix.c | 4 +- lib/source/shadow/CMakeLists.txt | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 4 +- lib/source/shadow/aws_iot_shadow_operation.c | 4 +- lib/source/shadow/aws_iot_shadow_parser.c | 4 +- .../shadow/aws_iot_shadow_subscription.c | 4 +- scripts/build_check_commit.sh | 4 +- scripts/build_check_pr.sh | 4 +- scripts/coverage.sh | 2 +- tests/aws_iot_tests_network.c | 4 +- .../common/unit/iot_tests_linear_containers.c | 4 +- ..._iot_tests_config.h => iot_tests_config.h} | 13 +++-- tests/mqtt/system/aws_iot_tests_mqtt_stress.c | 11 +++- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 4 +- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 4 +- tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 4 +- .../unit/aws_iot_tests_mqtt_subscription.c | 4 +- tests/mqtt/unit/aws_iot_tests_mqtt_validate.c | 4 +- .../system/aws_iot_tests_shadow_system.c | 4 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 4 +- .../shadow/unit/aws_iot_tests_shadow_parser.c | 4 +- 61 files changed, 148 insertions(+), 199 deletions(-) rename demos/{aws_iot_demo_config.h => iot_demo_config.h} (91%) delete mode 100644 lib/config/aws_iot_config_build.h.in rename tests/{aws_iot_tests_config.h => iot_tests_config.h} (95%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d4138b46c..417d2e5e9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required( VERSION 3.5.0 ) project( AwsIotDeviceSdkC VERSION 4.0.0 LANGUAGES C ) -add_definitions( -DAWS_IOT_SDK_VERSION="${PROJECT_VERSION}" ) +add_definitions( -DIOT_SDK_VERSION="${PROJECT_VERSION}" ) # Use C99. set( CMAKE_C_STANDARD 99 ) @@ -28,14 +28,11 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # SDK include path. include_directories( ${PROJECT_SOURCE_DIR}/lib/include ) -# CMake configure_file output. -include_directories( ${PROJECT_BINARY_DIR}/config ) - # Demo include path. include_directories( ${PROJECT_SOURCE_DIR}/demos ) # Use either the demo or test configuration file. -if( ${AWS_IOT_BUILD_TESTS} ) +if( ${IOT_BUILD_TESTS} ) # Tests include directories. include_directories( ${PROJECT_SOURCE_DIR}/tests tests/unity @@ -43,23 +40,20 @@ if( ${AWS_IOT_BUILD_TESTS} ) tests/mqtt/access ) # Tests config file. - add_definitions( -DAWS_IOT_USER_CONFIG_FILE="aws_iot_tests_config.h" ) + add_definitions( -DIOT_CONFIG_FILE="iot_tests_config.h" ) # Build unity test framework. add_subdirectory( tests/unity ) else() - add_definitions( -DAWS_IOT_USER_CONFIG_FILE="aws_iot_demo_config.h" ) + add_definitions( -DIOT_CONFIG_FILE="iot_demo_config.h" ) endif() # Platform libraries. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( lib/source/platform/posix ) - - # Set the configuration file for all subsequent libraries. - add_definitions( -DAWS_IOT_CONFIG_FILE="aws_iot_config_build.h" ) endif() -# Common libraries (queue, logging, etc.) +# Common libraries (linear containers, logging, etc.) add_subdirectory( lib/source/common ) # MQTT library. @@ -74,6 +68,6 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) endif() # Test executables. -if( ${AWS_IOT_BUILD_TESTS} ) +if( ${IOT_BUILD_TESTS} ) add_subdirectory( tests/ ) endif() diff --git a/demos/aws_iot_demo.h b/demos/aws_iot_demo.h index d541decff3..24da7905b9 100644 --- a/demos/aws_iot_demo.h +++ b/demos/aws_iot_demo.h @@ -28,8 +28,8 @@ #define _AWS_IOT_DEMO_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c index e7f3d37683..8fe74457ba 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/aws_iot_demo_mqtt.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 2424884157..08bf8296b5 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/demos/aws_iot_demo_config.h b/demos/iot_demo_config.h similarity index 91% rename from demos/aws_iot_demo_config.h rename to demos/iot_demo_config.h index 4eef6c16a0..4d4abe5c96 100644 --- a/demos/aws_iot_demo_config.h +++ b/demos/iot_demo_config.h @@ -21,8 +21,8 @@ /* This file contains configuration settings for the demos. */ -#ifndef _AWS_IOT_DEMO_CONFIG_H_ -#define _AWS_IOT_DEMO_CONFIG_H_ +#ifndef _IOT_DEMO_CONFIG_H_ +#define _IOT_DEMO_CONFIG_H_ /* Server endpoints used for the demos. May be overridden with command line * options. */ @@ -55,4 +55,11 @@ #define AWS_IOT_LOG_LEVEL_SHADOW AWS_IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_DEMO AWS_IOT_LOG_INFO -#endif /* ifndef _AWS_IOT_DEMO_CONFIG_H_ */ +#include +#include + +#define AWS_IOT_MUTEX_TYPE pthread_mutex_t +#define AWS_IOT_SEMAPHORE_TYPE sem_t +#define AWS_IOT_TIMER_TYPE void* + +#endif /* ifndef _IOT_DEMO_CONFIG_H_ */ diff --git a/demos/posix/aws_iot_demo_common_posix.c b/demos/posix/aws_iot_demo_common_posix.c index 9d2c34ac44..be1f7b3687 100644 --- a/demos/posix/aws_iot_demo_common_posix.c +++ b/demos/posix/aws_iot_demo_common_posix.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/aws_iot_demo_mqtt_posix.c index e8923d9718..7e634e9ba3 100644 --- a/demos/posix/aws_iot_demo_mqtt_posix.c +++ b/demos/posix/aws_iot_demo_mqtt_posix.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/demos/posix/aws_iot_demo_posix.h b/demos/posix/aws_iot_demo_posix.h index 7275b8d1d2..1a83737991 100644 --- a/demos/posix/aws_iot_demo_posix.h +++ b/demos/posix/aws_iot_demo_posix.h @@ -33,7 +33,7 @@ * Each demo will use one of these structs to hold its arguments. * * The default values of this struct may be set using compile-time constants, - * either through a [config file](@ref AWS_IOT_CONFIG_FILE) or a compiler option + * either through a [config file](@ref IOT_CONFIG_FILE) or a compiler option * like `-D`. * * The default values may be overridden using command line arguments. If a default diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index a97fbebe1a..682ae60c6e 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/doc/config/common b/doc/config/common index d8c012316d..6759b8e8e8 100644 --- a/doc/config/common +++ b/doc/config/common @@ -71,7 +71,7 @@ EXCLUDE_SYMBOLS += "TEST_*_run" ALIASES += dependencies{2}="@section \1_dependencies Dependencies^^@brief Dependencies of the \2.^^^^" # Alias for starting a configuration settings page. -ALIASES += describeconfig="Configuration settings are C proprocessor constants. They can be set with a @c #`define` in an @ref AWS_IOT_CONFIG_FILE or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." +ALIASES += describeconfig="Configuration settings are C proprocessor constants. They can be set with a @c #`define` in an @ref IOT_CONFIG_FILE or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." ALIASES += configpage{2}="@page \1_config Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^@par configpagemarker" ALIASES += configpage{4}="@page \1_config \3 Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^^^The settings on this page only affect the [\2](@ref \1). In addition to the settings on this page, them \2 will also be affected by [settings that affect all \4](@ref global_\4_config).^^@par configpagemarker" ALIASES += globalconfigpage{3}="@page global_\1_config Global \2 Configuration^^^^@describeconfig^^@brief Configuration settings that affect all \3.^^@par configpagemarker" diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 0844cd72db..ebc095d39e 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -19,7 +19,7 @@ In addition, credentials and Things for AWS IoT must be provisioned before runni @section building_demo Building the demo applications @brief How to build the demo applications. -Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/aws_iot_demo_config.h`. Any undefined settings will use a default value when possible. +Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/iot_demo_config.h`. Any undefined settings will use a default value when possible. The demo applications build with CMake. @@ -38,7 +38,7 @@ make Demo application executables will be placed in a `bin` directory of the CMake build directory, i.e. `build/bin` in the example above. The executables will be named `aws_iot_demo_library`. For example, the MQTT demo application will be named `aws_iot_demo_mqtt`. -Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/aws_iot_demo_config.h` is recommended over setting them using CMake. +Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/iot_demo_config.h` is recommended over setting them using CMake. @code{sh} # Example: Set AWS_IOT_STATIC_MEMORY_ONLY using CMake cmake .. -DCMAKE_C_FLAGS=-DAWS_IOT_STATIC_MEMORY_ONLY=1 @@ -103,16 +103,16 @@ See @ref AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER (MQTT demo only) or @ref AWS_IOT_DE @section building_tests Building and running the tests @brief How to build and run the SDK tests. -Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/aws_iot_tests_config.h`. +Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_tests_config.h`. -Unlike the demos, the tests are currently only supported on POSIX systems that support all of the POSIX prerequisites. They are built by passing the option `AWS_IOT_BUILD_TESTS=1` to CMake. +Unlike the demos, the tests are currently only supported on POSIX systems that support all of the POSIX prerequisites. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. @code{sh} # Create build directory and change to build directory. mkdir build cd build # Configure build using CMake. -cmake .. -DAWS_IOT_BUILD_TESTS=1 +cmake .. -DIOT_BUILD_TESTS=1 # Build both the demos and tests. make diff --git a/doc/guide/style.txt b/doc/guide/style.txt index b30f490996..ad0817af8e 100644 --- a/doc/guide/style.txt +++ b/doc/guide/style.txt @@ -23,7 +23,7 @@ The coding style aims to produce code that is readable and easy to debug. All li @code{c} /*-----------------------------------------------------------*/ @endcode -- All files must include @ref AWS_IOT_CONFIG_FILE at the top of the file before any other includes. +- All files must include @ref IOT_CONFIG_FILE at the top of the file before any other includes. - `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. See @ref guide_developer_styleguide_codingstyle_example for a more complete example. @@ -37,8 +37,8 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Included headers are at the top of the file. The config file include is always first. */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE /* Lines between #ifdef/#endif are indented. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE /* Lines between #ifdef/#endif are indented. */ #endif /* Standard includes are immediately after the config file. They are sorted alphabetically. diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 26495f1ccf..9a60d76a7a 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -14,10 +14,10 @@ Design goals of this SDK include: /** @globalconfigpage{library,Library,libraries} -@section AWS_IOT_CONFIG_FILE +@section IOT_CONFIG_FILE @brief Configuration header for the AWS IoT libraries. -Define a file to be included in all library source files before any other files. Unlike other settings, this one must be set using a compiler option, such as `-D` for gcc. The option should specify a filename that is in the include path; for example, `-DAWS_IOT_CONFIG_FILE="aws_iot_config.h"` (quotes may need to be escaped for some compilers). +Define a file to be included in all library source files before any other files. Unlike other settings, this one must be set using a compiler option, such as `-D` for gcc. The option should specify a filename that is in the include path; for example, `-DIOT_CONFIG_FILE="iot_config.h"` (quotes may need to be escaped for some compilers). @configpossible A string representing a file name. This file must be in the compiler's include path.
@configdefault None. A configuration header will not be used if this value is undefined. diff --git a/lib/config/aws_iot_config_build.h.in b/lib/config/aws_iot_config_build.h.in deleted file mode 100644 index 1f29d0264b..0000000000 --- a/lib/config/aws_iot_config_build.h.in +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* This file is used by CMake to configure the libraries during build. */ - -#ifndef _AWS_IOT_CONFIG_BUILD_H_ -#define _AWS_IOT_CONFIG_BUILD_H_ - -/* Include any provided user config file, which may override any options in this - * file. */ -#ifdef AWS_IOT_USER_CONFIG_FILE - #include AWS_IOT_USER_CONFIG_FILE -#endif - -/* Mutex type. */ -#ifndef AWS_IOT_MUTEX_TYPE - #include <@CONFIG_MUTEX_HEADER@> - #define AWS_IOT_MUTEX_TYPE @CONFIG_MUTEX_TYPE@ -#endif - -/* Semaphore type. */ -#ifndef AWS_IOT_SEMAPHORE_TYPE - #include <@CONFIG_SEMAPHORE_HEADER@> - #define AWS_IOT_SEMAPHORE_TYPE @CONFIG_SEMAPHORE_TYPE@ -#endif - -/* Timer type. */ -#ifndef AWS_IOT_TIMER_TYPE - #include <@CONFIG_TIMER_HEADER@> - #define AWS_IOT_TIMER_TYPE @CONFIG_TIMER_TYPE@ -#endif - -#endif diff --git a/lib/include/aws_iot_logging_setup.h b/lib/include/aws_iot_logging_setup.h index 8f70c5ea9c..93d6a190cc 100644 --- a/lib/include/aws_iot_logging_setup.h +++ b/lib/include/aws_iot_logging_setup.h @@ -28,8 +28,8 @@ #define _AWS_IOT_LOGGING_SETUP_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Logging include. Because it's included here, aws_iot_logging.h never needs diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h index ee022dde9d..117b64bf3d 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/aws_iot_mqtt.h @@ -28,8 +28,8 @@ #define _AWS_IOT_MQTT_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 91047721c6..2f145a783c 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -28,8 +28,8 @@ #define _AWS_IOT_SHADOW_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* MQTT include. */ diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 7a8b54b906..e1845287af 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -28,8 +28,8 @@ #define _IOT_LINEAR_CONTAINERS_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/include/platform/aws_iot_clock.h b/lib/include/platform/aws_iot_clock.h index 1b2c1b6a13..ce8f232c45 100644 --- a/lib/include/platform/aws_iot_clock.h +++ b/lib/include/platform/aws_iot_clock.h @@ -28,8 +28,8 @@ #define _AWS_IOT_CLOCK_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/include/platform/aws_iot_network.h b/lib/include/platform/aws_iot_network.h index 6508bca76e..68ad4d55bf 100644 --- a/lib/include/platform/aws_iot_network.h +++ b/lib/include/platform/aws_iot_network.h @@ -28,8 +28,8 @@ #define _AWS_IOT_NETWORK_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/include/platform/aws_iot_static_memory.h b/lib/include/platform/aws_iot_static_memory.h index 5e291621aa..dbcc720d60 100644 --- a/lib/include/platform/aws_iot_static_memory.h +++ b/lib/include/platform/aws_iot_static_memory.h @@ -26,8 +26,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* The functions in this file should only exist in static memory only mode, hence diff --git a/lib/include/platform/aws_iot_threads.h b/lib/include/platform/aws_iot_threads.h index c82d91d559..ad9c39ed15 100644 --- a/lib/include/platform/aws_iot_threads.h +++ b/lib/include/platform/aws_iot_threads.h @@ -28,8 +28,8 @@ #define _AWS_IOT_THREADS_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ @@ -165,7 +165,7 @@ bool AwsIot_CreateDetachedThread( AwsIotThreadRoutine_t threadRoutine, * * @see @ref platform_threads_function_mutexdestroy * - * **Example** + * Example * @code{c} * AwsIotMutex_t mutex; * @@ -260,7 +260,7 @@ void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ); * * @see @ref platform_threads_function_semaphoredestroy * - * **Example** + * Example * @code{c} * AwsIotSemaphore_t sem; * diff --git a/lib/include/private/aws_iot_logging.h b/lib/include/private/aws_iot_logging.h index fc6159c475..26eb5345ce 100644 --- a/lib/include/private/aws_iot_logging.h +++ b/lib/include/private/aws_iot_logging.h @@ -34,8 +34,8 @@ #define _AWS_IOT_LOGGING_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index 9839cfc070..441ea48853 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -29,8 +29,8 @@ #define _AWS_IOT_MQTT_INTERNAL_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* MQTT include. */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 2e2bc45535..dedab0d9c6 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -29,8 +29,8 @@ #define _AWS_IOT_SHADOW_INTERNAL_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Shadow include. */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index c637c9543c..704e047d62 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -18,6 +18,6 @@ set_target_properties( awsiotcommon PROPERTIES PRIVATE_HEADER target_link_libraries( awsiotcommon awsiotplatform ) # Link Unity test framework when building tests. -if( ${AWS_IOT_BUILD_TESTS} ) +if( ${IOT_BUILD_TESTS} ) target_link_libraries( awsiotcommon unity ) endif() diff --git a/lib/source/common/aws_iot_json_utils.c b/lib/source/common/aws_iot_json_utils.c index a5a0dcf0db..d442e6dc48 100644 --- a/lib/source/common/aws_iot_json_utils.c +++ b/lib/source/common/aws_iot_json_utils.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/common/aws_iot_logging.c b/lib/source/common/aws_iot_logging.c index a7f44ca027..97451ddd3d 100644 --- a/lib/source/common/aws_iot_logging.c +++ b/lib/source/common/aws_iot_logging.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index a853ef9699..f5ea96e5c0 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -21,6 +21,6 @@ set_target_properties( awsiotmqtt PROPERTIES PRIVATE_HEADER target_link_libraries( awsiotmqtt awsiotcommon awsiotplatform ) # Link Unity test framework when building tests. -if( ${AWS_IOT_BUILD_TESTS} ) +if( ${IOT_BUILD_TESTS} ) target_link_libraries( awsiotmqtt unity ) endif() diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index 40db0ae6e4..f78f2e637d 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index 7fc101f052..bdbb543690 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c index bdb120b1d5..fc5d7a4d43 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ @@ -147,11 +147,11 @@ * Username for metrics with AWS IoT. */ #if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 - #ifndef AWS_IOT_SDK_VERSION - #error "AWS_IOT_SDK_VERSION must be defined." + #ifndef IOT_SDK_VERSION + #error "IOT_SDK_VERSION must be defined." #endif - #define _AWS_IOT_METRICS_USERNAME ( "?SDK=C&Version=" AWS_IOT_SDK_VERSION ) /**< @brief Specify "C SDK" and SDK version. */ + #define _AWS_IOT_METRICS_USERNAME ( "?SDK=C&Version=" IOT_SDK_VERSION ) /**< @brief Specify "C SDK" and SDK version. */ #define _AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( _AWS_IOT_METRICS_USERNAME ) - 1 ) /**< @brief Length of #_AWS_IOT_METRICS_USERNAME. */ #endif diff --git a/lib/source/mqtt/aws_iot_mqtt_subscription.c b/lib/source/mqtt/aws_iot_mqtt_subscription.c index e79bf47176..0497bb4058 100644 --- a/lib/source/mqtt/aws_iot_mqtt_subscription.c +++ b/lib/source/mqtt/aws_iot_mqtt_subscription.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/mqtt/aws_iot_mqtt_validate.c b/lib/source/mqtt/aws_iot_mqtt_validate.c index aaf3c730d5..f6fe533c44 100644 --- a/lib/source/mqtt/aws_iot_mqtt_validate.c +++ b/lib/source/mqtt/aws_iot_mqtt_validate.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* MQTT internal include. */ diff --git a/lib/source/platform/posix/CMakeLists.txt b/lib/source/platform/posix/CMakeLists.txt index 3c20b39928..9ad216ed15 100644 --- a/lib/source/platform/posix/CMakeLists.txt +++ b/lib/source/platform/posix/CMakeLists.txt @@ -71,17 +71,6 @@ if( NOT DEFINED TLS_LIBRARY_LINKER_FLAG ) message( FATAL_ERROR "No compatible TLS library was found." ) endif() -# Configure the platform layer for POSIX systems. -set( CONFIG_MUTEX_HEADER "pthread.h" ) -set( CONFIG_MUTEX_TYPE pthread_mutex_t ) -set( CONFIG_SEMAPHORE_HEADER "semaphore.h" ) -set( CONFIG_SEMAPHORE_TYPE sem_t ) -set( CONFIG_TIMER_HEADER "time.h" ) -set( CONFIG_TIMER_TYPE void* ) - -configure_file( ${CMAKE_SOURCE_DIR}/lib/config/aws_iot_config_build.h.in - ${CMAKE_BINARY_DIR}/config/aws_iot_config_build.h ) - # Platform libraries source files. add_library( awsiotplatform SHARED aws_iot_clock_posix.c @@ -98,9 +87,5 @@ set_target_properties( awsiotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) set_target_properties( awsiotplatform PROPERTIES PUBLIC_HEADER "../../include/platform;../../include/platform/network" ) -# Set configuration file. -target_compile_definitions( awsiotplatform PRIVATE - AWS_IOT_CONFIG_FILE="aws_iot_config_build.h" ) - # Link required libraries. target_link_libraries( awsiotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) diff --git a/lib/source/platform/posix/aws_iot_clock_posix.c b/lib/source/platform/posix/aws_iot_clock_posix.c index 66f0f06c9b..ec1c1eeaf2 100644 --- a/lib/source/platform/posix/aws_iot_clock_posix.c +++ b/lib/source/platform/posix/aws_iot_clock_posix.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* POSIX includes. Allow the default POSIX headers to be overridden. */ diff --git a/lib/source/platform/posix/aws_iot_threads_posix.c b/lib/source/platform/posix/aws_iot_threads_posix.c index 0f5c7cbf6c..246f327df8 100644 --- a/lib/source/platform/posix/aws_iot_threads_posix.c +++ b/lib/source/platform/posix/aws_iot_threads_posix.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* POSIX includes. Allow the default POSIX headers to be overridden. */ diff --git a/lib/source/platform/posix/network/aws_iot_network_openssl.c b/lib/source/platform/posix/network/aws_iot_network_openssl.c index ecc865dc69..6136b44c53 100644 --- a/lib/source/platform/posix/network/aws_iot_network_openssl.c +++ b/lib/source/platform/posix/network/aws_iot_network_openssl.c @@ -26,8 +26,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c index 0bff96709e..79dc8329ee 100644 --- a/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c +++ b/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c @@ -26,8 +26,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* This file should only be compiled if dynamic memory allocation is forbidden. */ diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c index 7fd318ad09..da561051d4 100644 --- a/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c +++ b/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c @@ -26,8 +26,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* This file should only be compiled if dynamic memory allocation is forbidden. */ diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c index b810b87b9d..bebbc7dbac 100644 --- a/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c +++ b/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c @@ -26,8 +26,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* This file should only be compiled if dynamic memory allocation is forbidden. */ diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index 88c5404e70..3877a1654f 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -20,6 +20,6 @@ set_target_properties( awsiotmqtt PROPERTIES PRIVATE_HEADER target_link_libraries( awsiotshadow awsiotcommon awsiotplatform awsiotmqtt ) # Link Unity test framework when building tests. -if( ${AWS_IOT_BUILD_TESTS} ) +if( ${IOT_BUILD_TESTS} ) target_link_libraries( awsiotshadow unity ) endif() diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 1956d75495..781080e69d 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 2b268763f4..99ad8341ca 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index 4be9e83880..e9a7ba9658 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 361e43af8a..eae63a1d49 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -26,8 +26,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index 51e09893b5..9f47da7a49 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -26,7 +26,7 @@ echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem # Build tests and demos against AWS IoT with ThreadSanitizer. -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" make # Run MQTT tests and demo against AWS IoT. @@ -39,7 +39,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" make # Run MQTT and Shadow tests in static memory mode. diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh index 0e2ad3ad01..b5ac168d1f 100644 --- a/scripts/build_check_pr.sh +++ b/scripts/build_check_pr.sh @@ -12,7 +12,7 @@ cd build rm -rf * # Build tests and demos against Mosquitto broker with ThreadSanitizer. -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -fsanitize=thread" make # Run MQTT tests and demos against Mosquitto. @@ -24,7 +24,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DAWS_IOT_STATIC_MEMORY_ONLY=1 -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DAWS_IOT_STATIC_MEMORY_ONLY=1 -fsanitize=thread" make # Run MQTT tests and no-network Shadow tests in static memory mode. diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 5ad47bf40b..6dba994620 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -14,7 +14,7 @@ cd build rm -rf * # Build tests and demos against AWS IoT with gcov. -cmake .. -DAWS_IOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" --coverage" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" --coverage" make # Run MQTT tests and demo against AWS IoT with code coverage. diff --git a/tests/aws_iot_tests_network.c b/tests/aws_iot_tests_network.c index 53d1d8b01d..53d05c62bd 100644 --- a/tests/aws_iot_tests_network.c +++ b/tests/aws_iot_tests_network.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/common/unit/iot_tests_linear_containers.c b/tests/common/unit/iot_tests_linear_containers.c index 57cd6e4991..1d70c4992e 100644 --- a/tests/common/unit/iot_tests_linear_containers.c +++ b/tests/common/unit/iot_tests_linear_containers.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Linear containers include. */ diff --git a/tests/aws_iot_tests_config.h b/tests/iot_tests_config.h similarity index 95% rename from tests/aws_iot_tests_config.h rename to tests/iot_tests_config.h index 73c6cad31d..65accc64ec 100644 --- a/tests/aws_iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -22,8 +22,8 @@ /* This file contains configuration settings for the tests. Currently, the tests * must run on POSIX systems. */ -#ifndef _AWS_IOT_TESTS_CONFIG_H_ -#define _AWS_IOT_TESTS_CONFIG_H_ +#ifndef _IOT_TESTS_CONFIG_H_ +#define _IOT_TESTS_CONFIG_H_ /* Test framework include. */ #include "unity_fixture_malloc_overrides.h" @@ -115,4 +115,11 @@ #define AwsIotShadow_FreeSubscription unity_free_mt #endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 0 */ -#endif /* ifndef _AWS_IOT_TESTS_CONFIG_H_ */ +#include +#include + +#define AWS_IOT_MUTEX_TYPE pthread_mutex_t +#define AWS_IOT_SEMAPHORE_TYPE sem_t +#define AWS_IOT_TIMER_TYPE void* + +#endif /* ifndef _IOT_TESTS_CONFIG_H_ */ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c index 22a0644ae6..703da1f04c 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c @@ -29,8 +29,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ @@ -43,6 +43,13 @@ /* MQTT include. */ #include "aws_iot_mqtt.h" +/* POSIX includes. */ +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + /* Platform layer include. */ #include "platform/aws_iot_clock.h" diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 40c6cd68b7..03bc7fc3be 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index d33573ed33..7712014f8f 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c index 9b8f86c695..bf5f088d44 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c index e7895d2d59..874e631416 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c b/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c index a275f3b692..0c71c41597 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* MQTT internal include. */ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 2bd068eef7..133003761f 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 6e96c249a0..22e8a4e911 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 4e14e897d5..af6fbcbb6c 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -25,8 +25,8 @@ */ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ From f506fae6f58febf6a947c425deabfcf808807db9 Mon Sep 17 00:00:00 2001 From: "Mark R. Tuttle" Date: Tue, 22 Jan 2019 12:55:35 -0500 Subject: [PATCH 012/844] CBMC proofs of memory safety for MQTT packet parsers (#261) --- cbmc/README.md | 18 ++ .../DeserializeConnack_harness.c | 17 ++ cbmc/proofs/DeserializeConnack/Makefile | 10 + .../proofs/DeserializeConnack/cbmc-batch.yaml | 4 + .../DeserializePingresp_harness.c | 17 ++ cbmc/proofs/DeserializePingresp/Makefile | 10 + .../DeserializePingresp/cbmc-batch.yaml | 4 + .../DeserializePuback_harness.c | 19 ++ cbmc/proofs/DeserializePuback/Makefile | 10 + cbmc/proofs/DeserializePuback/cbmc-batch.yaml | 4 + .../DeserializePublish_harness.c | 21 ++ cbmc/proofs/DeserializePublish/Makefile | 10 + .../proofs/DeserializePublish/cbmc-batch.yaml | 4 + .../DeserializeSuback_harness.c | 23 +++ cbmc/proofs/DeserializeSuback/Makefile | 19 ++ cbmc/proofs/DeserializeSuback/cbmc-batch.yaml | 4 + .../DeserializeUnsuback_harness.c | 19 ++ cbmc/proofs/DeserializeUnsuback/Makefile | 10 + .../DeserializeUnsuback/cbmc-batch.yaml | 4 + cbmc/proofs/Makefile.common | 189 ++++++++++++++++++ 20 files changed, 416 insertions(+) create mode 100644 cbmc/README.md create mode 100644 cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c create mode 100644 cbmc/proofs/DeserializeConnack/Makefile create mode 100644 cbmc/proofs/DeserializeConnack/cbmc-batch.yaml create mode 100644 cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c create mode 100644 cbmc/proofs/DeserializePingresp/Makefile create mode 100644 cbmc/proofs/DeserializePingresp/cbmc-batch.yaml create mode 100644 cbmc/proofs/DeserializePuback/DeserializePuback_harness.c create mode 100644 cbmc/proofs/DeserializePuback/Makefile create mode 100644 cbmc/proofs/DeserializePuback/cbmc-batch.yaml create mode 100644 cbmc/proofs/DeserializePublish/DeserializePublish_harness.c create mode 100644 cbmc/proofs/DeserializePublish/Makefile create mode 100644 cbmc/proofs/DeserializePublish/cbmc-batch.yaml create mode 100644 cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c create mode 100644 cbmc/proofs/DeserializeSuback/Makefile create mode 100644 cbmc/proofs/DeserializeSuback/cbmc-batch.yaml create mode 100644 cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c create mode 100644 cbmc/proofs/DeserializeUnsuback/Makefile create mode 100644 cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml create mode 100644 cbmc/proofs/Makefile.common diff --git a/cbmc/README.md b/cbmc/README.md new file mode 100644 index 0000000000..f08364436b --- /dev/null +++ b/cbmc/README.md @@ -0,0 +1,18 @@ +# MQTT memory safety proofs + +This directory contains CBMC proofs of memory safety of MQTT entry points. [CBMC](http://www.cprover.org/cbmc/) is a bounded model checker for C available from the GitHub [repository](https://github.com/diffblue/cbmc). Each proof is in a separate subdirectory of proofs: + +* DeserializeConnack: AwsIotMqttInternal_DeserializeConnack is memory safe assuming: + * We abstract the AwsIotLogGeneric logging function +* DeserializePingresp: AwsIotMqttInternal_DeserializePingresp is memory safe assuming: + * We abstract the AwsIotLogGeneric logging function +* DeserializePuback: AwsIotMqttInternal_DeserializePuback is memory safe assuming: + * We abstract the AwsIotLogGeneric logging function +* DeserializePublish: AwsIotMqttInternal_DeserializePublish is memory safe assuming: + * We abstract the AwsIotLogGeneric logging function +* DeserializeSuback: AwsIotMqttInternal_DeserializeSuback is memory safe assuming: + * We abstract the AwsIotLogGeneric logging function + * We abstract the AwsIotMqttInternal_RemoveSubscriptionByPacket function + * We bound the length of the buffer being parsed +* DeserializeUnsuback: AwsIotMqttInternal_DeserializeUnsuback is memory safe assuming: + * We abstract the AwsIotLogGeneric logging function diff --git a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c new file mode 100644 index 0000000000..b06f9559fc --- /dev/null +++ b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c @@ -0,0 +1,17 @@ +#include IOT_CONFIG_FILE +#include "private/aws_iot_mqtt_internal.h" + +#include + +/*-----------------------------------------------------------*/ + +void harness() +{ + size_t dataLength; + uint8_t * pConnackStart = malloc( sizeof( uint8_t ) * dataLength ); + size_t bytesProcessed; + + AwsIotMqttInternal_DeserializeConnack( pConnackStart, + dataLength, + &bytesProcessed ); +} diff --git a/cbmc/proofs/DeserializeConnack/Makefile b/cbmc/proofs/DeserializeConnack/Makefile new file mode 100644 index 0000000000..f3fb57dfbe --- /dev/null +++ b/cbmc/proofs/DeserializeConnack/Makefile @@ -0,0 +1,10 @@ +ENTRY=DeserializeConnack + +ABSTRACTIONS = \ + --remove-function-body AwsIotLogGeneric \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializeConnack/cbmc-batch.yaml b/cbmc/proofs/DeserializeConnack/cbmc-batch.yaml new file mode 100644 index 0000000000..c35e2a5af1 --- /dev/null +++ b/cbmc/proofs/DeserializeConnack/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' +goto: DeserializeConnack.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c new file mode 100644 index 0000000000..26c7274fc7 --- /dev/null +++ b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c @@ -0,0 +1,17 @@ +#include IOT_CONFIG_FILE +#include "private/aws_iot_mqtt_internal.h" + +#include + +/*-----------------------------------------------------------*/ + +void harness() +{ + size_t dataLength; + uint8_t * pPingrespStart = malloc( sizeof( uint8_t ) * dataLength ); + size_t bytesProcessed; + + AwsIotMqttInternal_DeserializePingresp( pPingrespStart, + dataLength, + &bytesProcessed ); +} diff --git a/cbmc/proofs/DeserializePingresp/Makefile b/cbmc/proofs/DeserializePingresp/Makefile new file mode 100644 index 0000000000..c2cfae7174 --- /dev/null +++ b/cbmc/proofs/DeserializePingresp/Makefile @@ -0,0 +1,10 @@ +ENTRY=DeserializePingresp + +ABSTRACTIONS = \ + --remove-function-body AwsIotLogGeneric \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializePingresp/cbmc-batch.yaml b/cbmc/proofs/DeserializePingresp/cbmc-batch.yaml new file mode 100644 index 0000000000..505ba4001b --- /dev/null +++ b/cbmc/proofs/DeserializePingresp/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' +goto: DeserializePingresp.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c new file mode 100644 index 0000000000..b260d4d73f --- /dev/null +++ b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c @@ -0,0 +1,19 @@ +#include IOT_CONFIG_FILE +#include "private/aws_iot_mqtt_internal.h" + +#include + +/*-----------------------------------------------------------*/ + +void harness() +{ + size_t dataLength; + uint8_t * pPubackStart = malloc( sizeof( uint8_t ) * dataLength ); + uint16_t packetIdentifier; + size_t bytesProcessed; + + AwsIotMqttInternal_DeserializePuback( pPubackStart, + dataLength, + &packetIdentifier, + &bytesProcessed ); +} diff --git a/cbmc/proofs/DeserializePuback/Makefile b/cbmc/proofs/DeserializePuback/Makefile new file mode 100644 index 0000000000..026b0ee7d8 --- /dev/null +++ b/cbmc/proofs/DeserializePuback/Makefile @@ -0,0 +1,10 @@ +ENTRY=DeserializePuback + +ABSTRACTIONS = \ + --remove-function-body AwsIotLogGeneric \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializePuback/cbmc-batch.yaml b/cbmc/proofs/DeserializePuback/cbmc-batch.yaml new file mode 100644 index 0000000000..ad66a6fd49 --- /dev/null +++ b/cbmc/proofs/DeserializePuback/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' +goto: DeserializePuback.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c new file mode 100644 index 0000000000..4c606eaea0 --- /dev/null +++ b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c @@ -0,0 +1,21 @@ +#include IOT_CONFIG_FILE +#include "private/aws_iot_mqtt_internal.h" + +#include + +/*-----------------------------------------------------------*/ + +void harness() +{ + size_t dataLength; + uint8_t * pPublishStart = malloc( sizeof( uint8_t ) * dataLength ); + AwsIotMqttPublishInfo_t output; + uint16_t packetIdentifier; + size_t bytesProcessed; + + AwsIotMqttInternal_DeserializePublish( pPublishStart, + dataLength, + &output, + &packetIdentifier, + &bytesProcessed ); +} diff --git a/cbmc/proofs/DeserializePublish/Makefile b/cbmc/proofs/DeserializePublish/Makefile new file mode 100644 index 0000000000..fd1ff9b4ef --- /dev/null +++ b/cbmc/proofs/DeserializePublish/Makefile @@ -0,0 +1,10 @@ +ENTRY=DeserializePublish + +ABSTRACTIONS = \ + --remove-function-body AwsIotLogGeneric \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializePublish/cbmc-batch.yaml b/cbmc/proofs/DeserializePublish/cbmc-batch.yaml new file mode 100644 index 0000000000..0dd0fe5712 --- /dev/null +++ b/cbmc/proofs/DeserializePublish/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' +goto: DeserializePublish.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c new file mode 100644 index 0000000000..ed42d5b00a --- /dev/null +++ b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c @@ -0,0 +1,23 @@ +#include IOT_CONFIG_FILE +#include "private/aws_iot_mqtt_internal.h" + +#include + +/*-----------------------------------------------------------*/ + +void harness() +{ + AwsIotMqttConnection_t mqttConnection; + size_t dataLength; + uint8_t * pSubackStart = malloc( sizeof( uint8_t ) * dataLength ); + uint16_t packetIdentifier; + size_t bytesProcessed; + + __CPROVER_assume( dataLength <= BUFFER_SIZE ); + + AwsIotMqttInternal_DeserializeSuback( mqttConnection, + pSubackStart, + dataLength, + &packetIdentifier, + &bytesProcessed ); +} diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/DeserializeSuback/Makefile new file mode 100644 index 0000000000..b4f1c7619c --- /dev/null +++ b/cbmc/proofs/DeserializeSuback/Makefile @@ -0,0 +1,19 @@ +ENTRY=DeserializeSuback + +BUFFER_SIZE = 100 + +ABSTRACTIONS = \ + --remove-function-body AwsIotLogGeneric \ + --remove-function-body AwsIotMqttInternal_RemoveSubscriptionByPacket \ + +UNWINDING = \ + --unwindset AwsIotMqttInternal_DeserializeSuback.0:$(BUFFER_SIZE) \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_subscription.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + +DEF = -DBUFFER_SIZE=$(BUFFER_SIZE) + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml new file mode 100644 index 0000000000..e98f729bd3 --- /dev/null +++ b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--unwindset;AwsIotMqttInternal_DeserializeSuback.0:200;--bounds-check;--pointer-check;--unwinding-assertions=' +goto: DeserializeSuback.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c new file mode 100644 index 0000000000..fa09ef72d7 --- /dev/null +++ b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c @@ -0,0 +1,19 @@ +#include IOT_CONFIG_FILE +#include "private/aws_iot_mqtt_internal.h" + +#include + +/*-----------------------------------------------------------*/ + +void harness() +{ + size_t dataLength; + uint8_t * pUnsubackStart = malloc( sizeof( uint8_t ) * dataLength ); + uint16_t packetIdentifier; + size_t bytesProcessed; + + AwsIotMqttInternal_DeserializeUnsuback( pUnsubackStart, + dataLength, + &packetIdentifier, + &bytesProcessed ); +} diff --git a/cbmc/proofs/DeserializeUnsuback/Makefile b/cbmc/proofs/DeserializeUnsuback/Makefile new file mode 100644 index 0000000000..87868a233a --- /dev/null +++ b/cbmc/proofs/DeserializeUnsuback/Makefile @@ -0,0 +1,10 @@ +ENTRY=DeserializeUnsuback + +ABSTRACTIONS = \ + --remove-function-body AwsIotLogGeneric \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml b/cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml new file mode 100644 index 0000000000..34bda60043 --- /dev/null +++ b/cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' +goto: DeserializeUnsuback.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common new file mode 100644 index 0000000000..c4023454e8 --- /dev/null +++ b/cbmc/proofs/Makefile.common @@ -0,0 +1,189 @@ +SHELL=/bin/bash + +default: report + +################################################################ +# Define source location and cbmc binaries + +# MQTT source directory relative to proof subdirectories. +MQTT ?= $(abspath ../../..) + +GOTO_CC ?= goto-cc +GOTO_INSTRUMENT ?= goto-instrument +GOTO_ANALYZER ?= goto-analyzer +BATCH ?= cbmc-batch +VIEWER ?= cbmc-viewer + +################################################################ +# Build goto binary for cbmc +# Build goto binaries with options taken from top-level cmake + +INC = \ + -I$(MQTT)/lib/include \ + -I$(MQTT)/demos \ + +DEF += \ + -DIOT_CONFIG_FILE=\"iot_demo_config.h\" \ + -DIOT_SDK_VERSION=\"4.0.0\" \ + -Dawsiotmqtt_EXPORTS \ + +CFLAGS += $(CFLAGS2) $(INC) $(DEF) + +%.goto : %.c + $(GOTO_CC) -o $@ $(CFLAGS) $< + +$(MQTT)/build: + (mkdir -p $(MQTT)/build; cd $(MQTT)/build; cmake ..) 2>&1 \ + | tee $(ENTRY)0.txt \ + ; exit $${PIPESTATUS[0]} + +$(ENTRY)1.goto: $(MQTT)/build $(OBJS) + $(GOTO_CC) --function harness -o $@ $(OBJS) 2>&1 \ + | tee $(ENTRY)1.txt \ + ; exit $${PIPESTATUS[0]} + +$(ENTRY)2.goto: $(ENTRY)1.goto + $(GOTO_INSTRUMENT) \ + $(ABSTRACTIONS) \ + --drop-unused-functions \ + --slice-global-inits $< $@ 2>&1 \ + | tee $(ENTRY)2.txt \ + ; exit $${PIPESTATUS[0]} + +$(ENTRY).goto: $(ENTRY)2.goto + cp $< $@ + +################################################################ +# Run cbmc and build html report + +CBMCFLAGS += \ + $(UNWINDING) \ + --bounds-check \ + --pointer-check \ + --unwinding-assertions \ + +goto: $(ENTRY).goto + +cbmc.txt: $(ENTRY).goto + cbmc $(CBMCFLAGS) --trace $< 2>&1 | tee $@ + +property.xml: $(ENTRY).goto + cbmc $(CBMCFLAGS) --show-properties --xml-ui $< 2>&1 > $@ + +coverage.xml: $(ENTRY).goto + cbmc $(filter-out --unwinding-assertions,$(CBMCFLAGS)) \ + --cover location --xml-ui $< 2>&1 > $@ + +cbmc: cbmc.txt + +property: property.xml + +coverage: coverage.xml + +report: cbmc.txt property.xml coverage.xml + $(VIEWER) \ + --goto $(ENTRY).goto \ + --srcdir $(MQTT) \ + --blddir $(MQTT) \ + --htmldir html \ + --srcexclude "(./verification|./tests|./tools|./lib/third_party)" \ + --result cbmc.txt \ + --property property.xml \ + --block coverage.xml + +clean: + $(RM) $(OBJS) $(ENTRY).goto + $(RM) $(ENTRY)[0-9].goto $(ENTRY)[0-9].txt + $(RM) cbmc.txt property.xml coverage.xml TAGS + $(RM) *~ \#* + +veryclean: clean + $(RM) -r html + +.PHONY: cbmc property coverage report clean veryclean + +################################################################ +# Run cbmc under cbmc-batch + +BATCH ?= cbmc-batch +WS ?= ws +JOBOS ?= ubuntu16 + +define encode_options + '=$(shell echo $(1) | sed 's/ ,/ /g' | sed 's/ /;/g')=' +endef + +PROPMEM ?= 64000 +COVMEM ?= 64000 +CBMCPKG ?= cbmc +BATCHPKG ?= cbmc-batch +VIEWERPKG ?= cbmc-viewer + +SRC_ROOT ?= $(MQTT) +SRC_TARFILE ?= s3://cbmc/mqtt.tar.gz + +BATCHFLAGS ?= \ + --srcdir $(MQTT) \ + --wsdir $(WS) \ + --jobprefix $(ENTRY) \ + --no-build \ + --goto $(ENTRY).goto \ + --cbmcflags $(call encode_options,$(CBMCFLAGS)) \ + --property-memory $(PROPMEM) \ + --coverage-memory $(COVMEM) \ + --cbmcpkg $(CBMCPKG) \ + --batchpkg $(BATCHPKG) \ + --viewerpkg $(VIEWERPKG) \ + --no-copysrc \ + --srctarfile $(SRC_TARFILE) \ + --blddir $(MQTT) \ + --jobos $(JOBOS) \ + +define yaml_encode_options + "$(shell echo $(1) | sed 's/ ,/ /g' | sed 's/ /;/g')" +endef + +$(ENTRY).yaml: $(ENTRY).goto Makefile + echo 'jobos: $(JOBOS)' > $@ + echo 'cbmcpkg: $(CBMCPKG)' >> $@ + echo 'batchpkg: $(BATCHPKG)' >> $@ + echo 'viewerpkg: $(VIEWERPKG)' >> $@ + echo 'goto: $(ENTRY).goto' >> $@ + echo 'build: false' >> $@ + echo 'cbmcflags: $(call yaml_encode_options,$(CBMCFLAGS))' >> $@ + echo 'property_memory: $(PROPMEM)' >> $@ + echo 'coverage_memory: $(COVMEM)' >> $@ + echo 'expected: "SUCCESSFUL"' >> $@ + +launch: $(ENTRY).goto Makefile + mkdir -p $(WS) + cp $(ENTRY).goto $(WS) + $(BATCH) $(BATCHFLAGS) + +launch-clean: + for d in $(ENTRY)*; do \ + if [ -d $$d ]; then \ + for f in $$d.json $$d.yaml Makefile-$$d; do \ + if [ -f $$f ]; then mv $$f $$d; fi \ + done\ + fi \ + done + $(RM) Makefile-$(ENTRY)-[0-9]*-[0-9]* + $(RM) $(ENTRY)-[0-9]*-[0-9]*.json $(ENTRY)-[0-9]*-[0-9]*.yaml + $(RM) -r $(WS) + +launch-veryclean: launch-clean + $(RM) -r $(ENTRY)-[0-9]*-[0-9]* + +################################################################ +# Build configuration file to run cbmc under cbmc-batch in CI + +cbmc-batch.yaml: Makefile ../Makefile.common + @echo "Building $@" + @$(RM) $@ + @echo "jobos: $(JOBOS)" >> $@ + @echo "cbmcflags: $(call encode_options,$(CBMCFLAGS))" >> $@ + @echo "goto: $(ENTRY).goto" >> $@ + @echo "expected: SUCCESSFUL" >> $@ + +################################################################ From 229ac79e05965deee835cd9ad838467001ced1e2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 24 Jan 2019 13:19:49 -0800 Subject: [PATCH 013/844] Rename platform clock and threads to remove AWS prefix (#262) --- CMakeLists.txt | 1 + demos/aws_iot_demo_mqtt.c | 28 +- demos/aws_iot_demo_shadow.c | 4 +- demos/iot_demo_config.h | 9 +- doc/config/platform | 1 + doc/lib/platform.txt | 36 +-- lib/include/aws_iot_mqtt.h | 3 - .../platform/{aws_iot_clock.h => iot_clock.h} | 84 +++--- .../{aws_iot_threads.h => iot_threads.h} | 149 ++++------ .../platform/types/iot_platform_types.h | 109 ++++++++ .../platform/types/iot_platform_types_posix.h | 62 +++++ lib/include/private/aws_iot_mqtt_internal.h | 158 +++++------ lib/include/private/aws_iot_shadow_internal.h | 15 +- lib/source/common/aws_iot_logging.c | 93 ++++--- lib/source/mqtt/aws_iot_mqtt_api.c | 176 ++++++------ lib/source/mqtt/aws_iot_mqtt_operation.c | 58 ++-- lib/source/mqtt/aws_iot_mqtt_serialize.c | 15 +- lib/source/mqtt/aws_iot_mqtt_subscription.c | 25 +- lib/source/platform/posix/CMakeLists.txt | 4 +- ...ws_iot_clock_posix.c => iot_clock_posix.c} | 221 ++++++--------- ...ot_threads_posix.c => iot_threads_posix.c} | 263 ++++++++++-------- .../posix/network/aws_iot_network_openssl.c | 30 +- lib/source/shadow/aws_iot_shadow_api.c | 37 +-- lib/source/shadow/aws_iot_shadow_operation.c | 39 +-- .../shadow/aws_iot_shadow_subscription.c | 9 +- tests/iot_tests_config.h | 15 +- tests/mqtt/system/aws_iot_tests_mqtt_stress.c | 35 +-- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 73 ++--- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 29 +- tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 162 +++++------ .../unit/aws_iot_tests_mqtt_subscription.c | 7 +- .../system/aws_iot_tests_shadow_system.c | 52 ++-- tests/shadow/unit/aws_iot_tests_shadow_api.c | 37 +-- 33 files changed, 1068 insertions(+), 971 deletions(-) rename lib/include/platform/{aws_iot_clock.h => iot_clock.h} (70%) rename lib/include/platform/{aws_iot_threads.h => iot_threads.h} (69%) create mode 100644 lib/include/platform/types/iot_platform_types.h create mode 100644 lib/include/platform/types/iot_platform_types_posix.h rename lib/source/platform/posix/{aws_iot_clock_posix.c => iot_clock_posix.c} (54%) rename lib/source/platform/posix/{aws_iot_threads_posix.c => iot_threads_posix.c} (59%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 417d2e5e9b..25d60f9fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ endif() # Platform libraries. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + add_definitions( -DIOT_SYSTEM_TYPES_FILE="platform/types/iot_platform_types_posix.h" ) add_subdirectory( lib/source/platform/posix ) endif() diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c index 8fe74457ba..235411ef77 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/aws_iot_demo_mqtt.c @@ -37,8 +37,8 @@ #include "aws_iot_demo.h" /* Platform layer includes. */ -#include "platform/aws_iot_clock.h" -#include "platform/aws_iot_threads.h" +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* MQTT include. */ #include "aws_iot_mqtt.h" @@ -240,7 +240,7 @@ static void _mqttSubscriptionCallback( void * param1, { int acknowledgementLength = 0; size_t messageNumberIndex = 0, messageNumberLength = 1; - AwsIotSemaphore_t * pPublishesReceived = ( AwsIotSemaphore_t * ) param1; + IotSemaphore_t * pPublishesReceived = ( IotSemaphore_t * ) param1; const char * pPayload = pPublish->message.info.pPayload; char pAcknowledgementMessage[ _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; AwsIotMqttPublishInfo_t acknowledgementInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; @@ -335,7 +335,7 @@ static void _mqttSubscriptionCallback( void * param1, } /* Increment the number of PUBLISH messages received. */ - AwsIotSemaphore_Post( pPublishesReceived ); + IotSemaphore_Post( pPublishesReceived ); } /*-----------------------------------------------------------*/ @@ -371,7 +371,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; AwsIotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; AwsIotMqttCallbackInfo_t publishComplete = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; - AwsIotSemaphore_t publishesReceived; + IotSemaphore_t publishesReceived; const char * pTopicFilters[ _TOPIC_FILTER_COUNT ] = { AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", @@ -408,7 +408,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, status = snprintf( pClientIdentifierBuffer, _CLIENT_IDENTIFIER_MAX_LENGTH, _CLIENT_IDENTIFIER_PREFIX "%lu", - ( long unsigned int ) AwsIotClock_GetTimeMs() ); + ( long unsigned int ) IotClock_GetTimeMs() ); /* Check for errors from snprintf. */ if( status < 0 ) @@ -522,9 +522,9 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, publishInfo.retryLimit = _PUBLISH_RETRY_LIMIT; /* Create the semaphore that counts received PUBLISH messages.*/ - if( AwsIotSemaphore_Create( &publishesReceived, - 0, - AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) + if( IotSemaphore_Create( &publishesReceived, + 0, + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) { for( publishCount = 0; publishCount < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; @@ -592,8 +592,8 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { - if( AwsIotSemaphore_TimedWait( &publishesReceived, - _MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &publishesReceived, + _MQTT_TIMEOUT_MS ) == false ) { AwsIotLogError( "Timed out waiting for incoming PUBLISH messages." ); status = -1; @@ -620,8 +620,8 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { - if( AwsIotSemaphore_TimedWait( &publishesReceived, - _MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &publishesReceived, + _MQTT_TIMEOUT_MS ) == false ) { AwsIotLogError( "Timed out waiting for incoming PUBLISH messages." ); status = -1; @@ -633,7 +633,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, } /* Destroy the received message counter. */ - AwsIotSemaphore_Destroy( &publishesReceived ); + IotSemaphore_Destroy( &publishesReceived ); } else { diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 08bf8296b5..6fd49b80db 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -37,8 +37,8 @@ #include "aws_iot_demo.h" /* Platform layer includes. */ -#include "platform/aws_iot_clock.h" -#include "platform/aws_iot_threads.h" +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* Shadow include. */ #include "aws_iot_shadow.h" diff --git a/demos/iot_demo_config.h b/demos/iot_demo_config.h index 4d4abe5c96..7d391daa36 100644 --- a/demos/iot_demo_config.h +++ b/demos/iot_demo_config.h @@ -55,11 +55,8 @@ #define AWS_IOT_LOG_LEVEL_SHADOW AWS_IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_DEMO AWS_IOT_LOG_INFO -#include -#include - -#define AWS_IOT_MUTEX_TYPE pthread_mutex_t -#define AWS_IOT_SEMAPHORE_TYPE sem_t -#define AWS_IOT_TIMER_TYPE void* +/* The build system will choose the appropriate system types file for the platform + * layer based on the host operating system. */ +#include IOT_SYSTEM_TYPES_FILE #endif /* ifndef _IOT_DEMO_CONFIG_H_ */ diff --git a/doc/config/platform b/doc/config/platform index 46132c8c1d..5d55b1b49a 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -15,6 +15,7 @@ GENERATE_TAGFILE = doc/tag/platform.tag # Directories containing library source code. INPUT = doc/lib \ lib/include/platform \ + lib/include/platform/types \ lib/source/platform/posix \ lib/source/platform/posix/static_memory \ lib/source/platform/posix/network diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 5577d8af32..7b23eb8ca3 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -162,11 +162,11 @@ Currently, the platform static memory component has the following dependencies: /** @page platform_threads Thread Management -@brief @copybrief aws_iot_threads.h +@brief @copybrief iot_threads.h The platform thread management component provides other libraries with functions relating to threading and synchronization. It interfaces directly with the operating system to provide: - A function to create new threads. -- Synchronization mechanisms such as [mutexes](@ref AwsIotMutex_t) and [counting semaphores](@ref AwsIotSemaphore_t). +- Synchronization mechanisms such as [mutexes](@ref IotMutex_t) and [counting semaphores](@ref IotSemaphore_t). @dependencies{platform_threads,platform thread management component} @dot "Thread management direct dependencies" @@ -215,30 +215,6 @@ This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the [platform networkin @configpossible One of the @ref logging_constants_levels.
@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. -@section AWS_IOT_MUTEX_TYPE -@brief Set the type used to represent mutexes. - -@copydetails AwsIotMutex_t - -@configpossible Any C data type.
-@configdefault No default value is provided, but this setting will be automatically configured during build. - -@section AWS_IOT_SEMAPHORE_TYPE -@brief Set the type used to represent semaphores. - -@copydetails AwsIotSemaphore_t - -@configpossible Any C data type.
-@configdefault No default value is provided, but this setting will be automatically configured during build. - -@section AWS_IOT_TIMER_TYPE -@brief Set the type used to represent timers. - -@copydetails AwsIotTimer_t - -@configpossible Any C data type.
-@configdefault No default value is provided, but this setting will be automatically configured during build. - @section AWS_IOT_MESSAGE_BUFFERS @brief The number of statically-allocated [message buffers](@ref platform_static_memory_types_messagebuffers). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. @@ -298,11 +274,10 @@ This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the [platform networkin @section platform_config_memory Memory allocation @brief Memory allocation function overrides for the platform layer. -Some platform layers are not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. Currently, the following platform clock implementations require memory allocation: -- [POSIX](@ref aws_iot_clock_posix.c)
+Some platform layers are not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. Currently, the following platform implementations require memory allocation: +- POSIX
This implementation is not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - - #AwsIotClock_Malloc and #AwsIotClock_Free - - #AwsIotThreads_Malloc and #AwsIotThreads_Free. + - #IotThreads_Malloc and #IotThreads_Free. - #AwsIotNetwork_Malloc and #AwsIotNetwork_Free. @section platform_config_posixheaders POSIX headers @@ -317,6 +292,7 @@ Standard name | Setting [signal.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html) | `POSIX_SIGNAL_HEADER` [semaphore.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html) | `POSIX_SEMAPHORE_HEADER` [time.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html) | `POSIX_TIME_HEADER` +[sys/types.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html) | `POSIX_TYPES_HEADER` Example:
To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HEADER` setting should be defined. diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h index 117b64bf3d..7f97cea252 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/aws_iot_mqtt.h @@ -37,9 +37,6 @@ #include #include -/* Platform threads include. */ -#include "platform/aws_iot_threads.h" - /*---------------------------- MQTT handle types ----------------------------*/ /** diff --git a/lib/include/platform/aws_iot_clock.h b/lib/include/platform/iot_clock.h similarity index 70% rename from lib/include/platform/aws_iot_clock.h rename to lib/include/platform/iot_clock.h index ce8f232c45..c7938fc958 100644 --- a/lib/include/platform/aws_iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -20,12 +20,12 @@ */ /** - * @file aws_iot_clock.h - * @brief Time-related functions used by the AWS IoT libraries. + * @file iot_clock.h + * @brief Time-related functions used by libraries in this SDK. */ -#ifndef _AWS_IOT_CLOCK_H_ -#define _AWS_IOT_CLOCK_H_ +#ifndef _IOT_CLOCK_H_ +#define _IOT_CLOCK_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -33,29 +33,12 @@ #endif /* Standard includes. */ +#include #include +#include -/* Platform threads include. */ -#include "aws_iot_threads.h" - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent timers. The constant @ref AWS_IOT_TIMER_TYPE - * can be used to change the timer type. - * - * - * This constant will be automatically configured during build and generally - * does not need to be defined. - * - * - * Example
- * To change the type of #AwsIotTimer_t to `long`: - * @code{c} - * #define AWS_IOT_TIMER_TYPE long - * #include "aws_iot_clock.h" - * @endcode - */ -typedef AWS_IOT_TIMER_TYPE AwsIotTimer_t; +/* Platform layer types include. */ +#include "platform/types/iot_platform_types.h" /** * @functionspage{platform_clock,platform clock component,Clock} @@ -67,11 +50,11 @@ typedef AWS_IOT_TIMER_TYPE AwsIotTimer_t; */ /** - * @functionpage{AwsIotClock_GetTimestring,platform_clock,gettimestring} - * @functionpage{AwsIotClock_GetTimeMs,platform_clock,gettimems} - * @functionpage{AwsIotClock_TimerCreate,platform_clock,timercreate} - * @functionpage{AwsIotClock_TimerDestroy,platform_clock,timerdestroy} - * @functionpage{AwsIotClock_TimerArm,platform_clock,timerarm} + * @functionpage{IotClock_GetTimestring,platform_clock,gettimestring} + * @functionpage{IotClock_GetTimeMs,platform_clock,gettimems} + * @functionpage{IotClock_TimerCreate,platform_clock,timercreate} + * @functionpage{IotClock_TimerDestroy,platform_clock,timerdestroy} + * @functionpage{IotClock_TimerArm,platform_clock,timerarm} */ /** @@ -95,16 +78,16 @@ typedef AWS_IOT_TIMER_TYPE AwsIotTimer_t; * char timestring[ 32 ]; * size_t timestringLength = 0; * - * if( AwsIotClock_GetTimestring( timestring, 32, ×tringLength ) == true ) + * if( IotClock_GetTimestring( timestring, 32, ×tringLength ) == true ) * { * printf( "Timestring: %.*s", timestringLength, timestring ); * } * @endcode */ /* @[declare_platform_clock_gettimestring] */ -bool AwsIotClock_GetTimestring( char * const pBuffer, - size_t bufferSize, - size_t * const pTimestringLength ); +bool IotClock_GetTimestring( char * const pBuffer, + size_t bufferSize, + size_t * const pTimestringLength ); /* @[declare_platform_clock_gettimestring] */ /** @@ -119,19 +102,18 @@ bool AwsIotClock_GetTimestring( char * const pBuffer, * Example * @code{c} * // Get current time. - * uint64_t currentTime = AwsIotClock_GetTimeMs(); + * uint64_t currentTime = IotClock_GetTimeMs(); * @endcode */ /* @[declare_platform_clock_gettimems] */ -uint64_t AwsIotClock_GetTimeMs( void ); +uint64_t IotClock_GetTimeMs( void ); /* @[declare_platform_clock_gettimems] */ /** * @brief Create a new timer. * * This function creates a new, unarmed timer. It must be called on an uninitialized - * #AwsIotTimer_t. This function must not be called on an already-initialized - * #AwsIotTimer_t. + * #IotTimer_t. This function must not be called on an already-initialized #IotTimer_t. * * @param[out] pNewTimer Set to a new timer handle on success. * @param[in] expirationRoutine The function to run when this timer expires. This @@ -143,16 +125,16 @@ uint64_t AwsIotClock_GetTimeMs( void ); * @see @ref platform_clock_function_timerdestroy, @ref platform_clock_function_timerarm */ /* @[declare_platform_clock_timercreate] */ -bool AwsIotClock_TimerCreate( AwsIotTimer_t * const pNewTimer, - AwsIotThreadRoutine_t expirationRoutine, - void * pArgument ); +bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, + IotThreadRoutine_t expirationRoutine, + void * pArgument ); /* @[declare_platform_clock_timercreate] */ /** * @brief Free resources used by a timer. * * This function frees resources used by a timer. It must be called on an initialized - * #AwsIotTimer_t. No other timer functions should be called on `pTimer` after calling + * #IotTimer_t. No other timer functions should be called on `pTimer` after calling * this function (unless the timer is re-created). * * This function will stop the `pTimer` if it is armed. @@ -162,7 +144,7 @@ bool AwsIotClock_TimerCreate( AwsIotTimer_t * const pNewTimer, * @see @ref platform_clock_function_timercreate, @ref platform_clock_function_timerarm */ /* @[declare_platform_clock_timerdestroy] */ -void AwsIotClock_TimerDestroy( AwsIotTimer_t * const pTimer ); +void IotClock_TimerDestroy( IotTimer_t * const pTimer ); /* @[declare_platform_clock_timerdestroy] */ /** @@ -195,25 +177,25 @@ void AwsIotClock_TimerDestroy( AwsIotTimer_t * const pTimer ); * * void timerExample( void ) * { - * AwsIotTimer_t timer; + * IotTimer_t timer; * - * if( AwsIotClock_TimerCreate( &timer, timerExpirationRoutine, NULL ) == true ) + * if( IotClock_TimerCreate( &timer, timerExpirationRoutine, NULL ) == true ) * { * // Set the timer to periodically expire every 10 seconds. - * if( AwsIotClock_TimerArm( &timer, 10000, 10000 ) == true ) + * if( IotClock_TimerArm( &timer, 10000, 10000 ) == true ) * { * // Wait for timer to expire. * } * - * AwsIotClock_TimerDestroy( &timer ); + * IotClock_TimerDestroy( &timer ); * } * } * @endcode */ /* @[declare_platform_clock_timerarm] */ -bool AwsIotClock_TimerArm( AwsIotTimer_t * const pTimer, - uint64_t relativeTimeoutMs, - uint64_t periodMs ); +bool IotClock_TimerArm( IotTimer_t * const pTimer, + uint64_t relativeTimeoutMs, + uint64_t periodMs ); /* @[declare_platform_clock_timerarm] */ -#endif /* ifndef _AWS_IOT_CLOCK_H_ */ +#endif /* ifndef _IOT_CLOCK_H_ */ diff --git a/lib/include/platform/aws_iot_threads.h b/lib/include/platform/iot_threads.h similarity index 69% rename from lib/include/platform/aws_iot_threads.h rename to lib/include/platform/iot_threads.h index ad9c39ed15..c259445160 100644 --- a/lib/include/platform/aws_iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -20,12 +20,12 @@ */ /** - * @file aws_iot_threads.h - * @brief Threading and synchronization functions used by the AWS IoT libraries. + * @file iot_threads.h + * @brief Threading and synchronization functions used by libraries in this SDK. */ -#ifndef _AWS_IOT_THREADS_H_ -#define _AWS_IOT_THREADS_H_ +#ifndef _IOT_THREADS_H_ +#define _IOT_THREADS_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -36,56 +36,8 @@ #include #include -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent mutexes. The constant @ref AWS_IOT_MUTEX_TYPE - * can be used to change the mutex type. - * - * - * This constant will be automatically configured during build and generally - * does not need to be defined. - * - * - * Mutexes should only be released by the threads that take them. - * - * Example
- * To change the type of #AwsIotMutex_t to `long`: - * @code{c} - * #define AWS_IOT_MUTEX_TYPE long - * #include "aws_iot_threads.h" - * @endcode - */ -typedef AWS_IOT_MUTEX_TYPE AwsIotMutex_t; - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent semaphores. The constant - * @ref AWS_IOT_SEMAPHORE_TYPE be used to change the semaphore type. - * - * - * This constant will be automatically configured during build and generally - * does not need to be defined. - * - * - * Semaphores must be counting, and any thread may take (wait on) or release - * (post to) a semaphore. - * - * Example
- * To change the type of #AwsIotSemaphore_t to `long`: - * @code{c} - * #define AWS_IOT_SEMAPHORE_TYPE long - * #include "aws_iot_threads.h" - * @endcode - */ -typedef AWS_IOT_SEMAPHORE_TYPE AwsIotSemaphore_t; - -/** - * @brief Thread routine function. - * - * @param[in] void * The argument passed to the @ref - * platform_threads_function_createdetachedthread. For application use. - */ -typedef void ( * AwsIotThreadRoutine_t )( void * ); +/* Platform layer types include. */ +#include "platform/types/iot_platform_types.h" /** * @functionspage{platform_threads,platform thread management,Thread Management} @@ -105,19 +57,19 @@ typedef void ( * AwsIotThreadRoutine_t )( void * ); */ /** - * @functionpage{AwsIot_CreateDetachedThread,platform_threads,createdetachedthread} - * @functionpage{AwsIotMutex_Create,platform_threads,mutexcreate} - * @functionpage{AwsIotMutex_Destroy,platform_threads,mutexdestroy} - * @functionpage{AwsIotMutex_Lock,platform_threads,mutexlock} - * @functionpage{AwsIotMutex_TryLock,platform_threads,mutextrylock} - * @functionpage{AwsIotMutex_Unlock,platform_threads,mutexunlock} - * @functionpage{AwsIotSemaphore_Create,platform_threads,semaphorecreate} - * @functionpage{AwsIotSemaphore_Destroy,platform_threads,semaphoredestroy} - * @functionpage{AwsIotSemaphore_GetCount,platform_threads,semaphoregetcount} - * @functionpage{AwsIotSemaphore_Wait,platform_threads,semaphorewait} - * @functionpage{AwsIotSemaphore_TryWait,platform_threads,semaphoretrywait} - * @functionpage{AwsIotSemaphore_TimedWait,platform_threads,semaphoretimedwait} - * @functionpage{AwsIotSemaphore_Post,platform_threads,semaphorepost} + * @functionpage{Iot_CreateDetachedThread,platform_threads,createdetachedthread} + * @functionpage{IotMutex_Create,platform_threads,mutexcreate} + * @functionpage{IotMutex_Destroy,platform_threads,mutexdestroy} + * @functionpage{IotMutex_Lock,platform_threads,mutexlock} + * @functionpage{IotMutex_TryLock,platform_threads,mutextrylock} + * @functionpage{IotMutex_Unlock,platform_threads,mutexunlock} + * @functionpage{IotSemaphore_Create,platform_threads,semaphorecreate} + * @functionpage{IotSemaphore_Destroy,platform_threads,semaphoredestroy} + * @functionpage{IotSemaphore_GetCount,platform_threads,semaphoregetcount} + * @functionpage{IotSemaphore_Wait,platform_threads,semaphorewait} + * @functionpage{IotSemaphore_TryWait,platform_threads,semaphoretrywait} + * @functionpage{IotSemaphore_TimedWait,platform_threads,semaphoretimedwait} + * @functionpage{IotSemaphore_Post,platform_threads,semaphorepost} */ /** @@ -137,7 +89,7 @@ typedef void ( * AwsIotThreadRoutine_t )( void * ); * void threadRoutine( void * pArgument ); * * // Run threadRoutine in a detached thread. - * if( AwsIot_CreateDetachedThread( threadRoutine, NULL ) == true ) + * if( Iot_CreateDetachedThread( threadRoutine, NULL ) == true ) * { * // Success * } @@ -148,16 +100,15 @@ typedef void ( * AwsIotThreadRoutine_t )( void * ); * @endcode */ /* @[declare_platform_threads_createdetachedthread] */ -bool AwsIot_CreateDetachedThread( AwsIotThreadRoutine_t threadRoutine, - void * pArgument ); +bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, + void * pArgument ); /* @[declare_platform_threads_createdetachedthread] */ /** * @brief Create a new mutex. * * This function creates a new, unlocked mutex. It must be called on an uninitialized - * #AwsIotMutex_t. This function must not be called on an already-initialized - * #AwsIotMutex_t. + * #IotMutex_t. This function must not be called on an already-initialized #IotMutex_t. * * @param[in] pNewMutex Pointer to the memory that will hold the new mutex. * @@ -167,26 +118,26 @@ bool AwsIot_CreateDetachedThread( AwsIotThreadRoutine_t threadRoutine, * * Example * @code{c} - * AwsIotMutex_t mutex; + * IotMutex_t mutex; * - * if( AwsIotMutex_Create( &mutex ) == true ) + * if( IotMutex_Create( &mutex ) == true ) * { * // Lock and unlock the mutex... * * // Destroy the mutex when it's no longer needed. - * AwsIotMutex_Destroy( &mutex ); + * IotMutex_Destroy( &mutex ); * } * @endcode */ /* @[declare_platform_threads_mutexcreate] */ -bool AwsIotMutex_Create( AwsIotMutex_t * const pNewMutex ); +bool IotMutex_Create( IotMutex_t * const pNewMutex ); /* @[declare_platform_threads_mutexcreate] */ /** * @brief Free resources used by a mutex. * * This function frees resources used by a mutex. It must be called on an initialized - * #AwsIotMutex_t. No other mutex functions should be called on `pMutex` after calling + * #IotMutex_t. No other mutex functions should be called on `pMutex` after calling * this function (unless the mutex is re-created). * * @param[in] pMutex The mutex to destroy. @@ -195,7 +146,7 @@ bool AwsIotMutex_Create( AwsIotMutex_t * const pNewMutex ); * @see @ref platform_threads_function_mutexcreate */ /* @[declare_platform_threads_mutexdestroy] */ -void AwsIotMutex_Destroy( AwsIotMutex_t * const pMutex ); +void IotMutex_Destroy( IotMutex_t * const pMutex ); /* @[declare_platform_threads_mutexdestroy] */ /** @@ -210,7 +161,7 @@ void AwsIotMutex_Destroy( AwsIotMutex_t * const pMutex ); * @see @ref platform_threads_function_mutextrylock for a nonblocking lock. */ /* @[declare_platform_threads_mutexlock] */ -void AwsIotMutex_Lock( AwsIotMutex_t * const pMutex ); +void IotMutex_Lock( IotMutex_t * const pMutex ); /* @[declare_platform_threads_mutexlock] */ /** @@ -227,7 +178,7 @@ void AwsIotMutex_Lock( AwsIotMutex_t * const pMutex ); * @see @ref platform_threads_function_mutexlock for a blocking lock. */ /* @[declare_platform_threads_mutextrylock] */ -bool AwsIotMutex_TryLock( AwsIotMutex_t * const pMutex ); +bool IotMutex_TryLock( IotMutex_t * const pMutex ); /* @[declare_platform_threads_mutextrylock] */ /** @@ -242,15 +193,15 @@ bool AwsIotMutex_TryLock( AwsIotMutex_t * const pMutex ); * @note This function should not be called on a mutex that is already unlocked. */ /* @[declare_platform_threads_mutexunlock] */ -void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ); +void IotMutex_Unlock( IotMutex_t * const pMutex ); /* @[declare_platform_threads_mutexunlock] */ /** * @brief Create a new counting semaphore. * * This function creates a new counting semaphore with a given intial and - * maximum value. It must be called on an uninitialized #AwsIotSemaphore_t. - * This function must not be called on an already-initialized #AwsIotSemaphore_t. + * maximum value. It must be called on an uninitialized #IotSemaphore_t. + * This function must not be called on an already-initialized #IotSemaphore_t. * * @param[in] pNewSemaphore Pointer to the memory that will hold the new semaphore. * @param[in] initialValue The semaphore should be initialized with this value. @@ -262,30 +213,30 @@ void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ); * * Example * @code{c} - * AwsIotSemaphore_t sem; + * IotSemaphore_t sem; * * // Create a locked binary semaphore. - * if( AwsIotSemaphore_Create( &sem, 0, 1 ) == true ) + * if( IotSemaphore_Create( &sem, 0, 1 ) == true ) * { * // Unlock the semaphore. - * AwsIotSemaphore_Post( &sem ); + * IotSemaphore_Post( &sem ); * * // Destroy the semaphore when it's no longer needed. - * AwsIotSemaphore_Destroy( &sem ); + * IotSemaphore_Destroy( &sem ); * } * @endcode */ /* @[declare_platform_threads_semaphorecreate] */ -bool AwsIotSemaphore_Create( AwsIotSemaphore_t * const pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ); +bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ); /* @[declare_platform_threads_semaphorecreate] */ /** * @brief Free resources used by a semaphore. * * This function frees resources used by a semaphore. It must be called on an initialized - * #AwsIotSemaphore_t. No other semaphore functions should be called on `pSemaphore` after + * #IotSemaphore_t. No other semaphore functions should be called on `pSemaphore` after * calling this function (unless the semaphore is re-created). * * @param[in] pSemaphore The semaphore to destroy. @@ -294,7 +245,7 @@ bool AwsIotSemaphore_Create( AwsIotSemaphore_t * const pNewSemaphore, * @see @ref platform_threads_function_semaphorecreate */ /* @[declare_platform_threads_semaphoredestroy] */ -void AwsIotSemaphore_Destroy( AwsIotSemaphore_t * const pSemaphore ); +void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ); /* @[declare_platform_threads_semaphoredestroy] */ /** @@ -308,7 +259,7 @@ void AwsIotSemaphore_Destroy( AwsIotSemaphore_t * const pSemaphore ); * @return The current count of the semaphore. This function should not fail. */ /* @[declare_platform_threads_semaphoregetcount] */ -uint32_t AwsIotSemaphore_GetCount( AwsIotSemaphore_t * const pSemaphore ); +uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ); /* @[declare_platform_threads_semaphoregetcount] */ /** @@ -325,7 +276,7 @@ uint32_t AwsIotSemaphore_GetCount( AwsIotSemaphore_t * const pSemaphore ); * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. */ /* @[declare_platform_threads_semaphorewait] */ -void AwsIotSemaphore_Wait( AwsIotSemaphore_t * const pSemaphore ); +void IotSemaphore_Wait( IotSemaphore_t * const pSemaphore ); /* @[declare_platform_threads_semaphorewait] */ /** @@ -345,7 +296,7 @@ void AwsIotSemaphore_Wait( AwsIotSemaphore_t * const pSemaphore ); * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. */ /* @[declare_platform_threads_semaphoretrywait] */ -bool AwsIotSemaphore_TryWait( AwsIotSemaphore_t * const pSemaphore ); +bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ); /* @[declare_platform_threads_semaphoretrywait] */ /** @@ -367,8 +318,8 @@ bool AwsIotSemaphore_TryWait( AwsIotSemaphore_t * const pSemaphore ); * @ref platform_threads_function_semaphorewait for a blocking wait. */ /* @[declare_platform_threads_semaphoretimedwait] */ -bool AwsIotSemaphore_TimedWait( AwsIotSemaphore_t * const pSemaphore, - uint64_t timeoutMs ); +bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, + uint64_t timeoutMs ); /* @[declare_platform_threads_semaphoretimedwait] */ /** @@ -381,7 +332,7 @@ bool AwsIotSemaphore_TimedWait( AwsIotSemaphore_t * const pSemaphore, * @param[in] pSemaphore The semaphore to unlock. */ /* @[declare_platform_threads_semaphorepost] */ -void AwsIotSemaphore_Post( AwsIotSemaphore_t * const pSemaphore ); +void IotSemaphore_Post( IotSemaphore_t * const pSemaphore ); /* @[declare_platform_threads_semaphorepost] */ -#endif /* ifndef _AWS_IOT_THREADS_H_ */ +#endif /* ifndef _IOT_THREADS_H_ */ diff --git a/lib/include/platform/types/iot_platform_types.h b/lib/include/platform/types/iot_platform_types.h new file mode 100644 index 0000000000..b1cb4f21b2 --- /dev/null +++ b/lib/include/platform/types/iot_platform_types.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_platform_types.h + * @brief Types of the platform layer. + */ + +#ifndef _IOT_PLATFORM_TYPES_H_ +#define _IOT_PLATFORM_TYPES_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/*------------------------- Thread management types -------------------------*/ + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent mutexes, configured with the type + * `_IotSystemMutex_t`. + * + * + * `_IotSystemMutex_t` will be automatically configured during build and generally + * does not need to be defined. + * + * + * Mutexes should only be released by the threads that take them. + * + * Example
+ * To change the type of #IotMutex_t to `long`: + * @code{c} + * typedef long _IotSystemMutex_t; + * #include "iot_threads.h" + * @endcode + */ +typedef _IotSystemMutex_t IotMutex_t; + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent semaphores, configured with the type + * `_IotSystemSemaphore_t`. + * + * + * `_IotSystemSemaphore_t` will be automatically configured during build and + * generally does not need to be defined. + * + * + * Semaphores must be counting, and any thread may take (wait on) or release + * (post to) a semaphore. + * + * Example
+ * To change the type of #IotSemaphore_t to `long`: + * @code{c} + * typedef long _IotSystemSemaphore_t; + * #include "iot_threads.h" + * @endcode + */ +typedef _IotSystemSemaphore_t IotSemaphore_t; + +/** + * @brief Thread routine function. + * + * @param[in] void * The argument passed to the @ref + * platform_threads_function_createdetachedthread. For application use. + */ +typedef void ( * IotThreadRoutine_t )( void * ); + +/*-------------------------- Clock and timer types --------------------------*/ + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent timers, configured with the type + * `_IotSystemTimer_t`. + * + * + * `_IotSystemTimer_t` will be automatically configured during build and generally + * does not need to be defined. + * + * + * Example
+ * To change the type of #IotTimer_t to `long`: + * @code{c} + * typedef long _IotSystemTimer_t; + * #include "iot_clock.h" + * @endcode + */ +typedef _IotSystemTimer_t IotTimer_t; + +#endif /* ifndef _IOT_PLATFORM_TYPES_H_ */ diff --git a/lib/include/platform/types/iot_platform_types_posix.h b/lib/include/platform/types/iot_platform_types_posix.h new file mode 100644 index 0000000000..52bd80f8d5 --- /dev/null +++ b/lib/include/platform/types/iot_platform_types_posix.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_platform_types_posix.h + * @brief Definitions of platform layer types on POSIX systems. + */ + +#ifndef _IOT_PLATFORM_TYPES_POSIX_H_ +#define _IOT_PLATFORM_TYPES_POSIX_H_ + +/* POSIX includes. Allow the default POSIX headers to be overridden. */ +#ifdef POSIX_TYPES_HEADER + #include POSIX_TYPES_HEADER +#else + #include +#endif +#ifdef POSIX_SEMAPHORE_HEADER + #include POSIX_SEMAPHORE_HEADER +#else + #include +#endif + +/** + * @brief The native mutex type on POSIX systems. + */ +typedef pthread_mutex_t _IotSystemMutex_t; + +/** + * @brief The native semaphore type on POSIX systems. + */ +typedef sem_t _IotSystemSemaphore_t; + +/** + * @brief Represents an #IotTimer_t on POSIX systems. + */ +typedef struct _IotSystemTimer +{ + timer_t timer; /**< @brief Underlying POSIX timer. */ + void * pArgument; /**< @brief First argument to threadRoutine. */ + void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ +} _IotSystemTimer_t; + +#endif /* ifndef _IOT_PLATFORM_TYPES_POSIX_H_ */ diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index 441ea48853..ef96d66408 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -33,14 +33,14 @@ #include IOT_CONFIG_FILE #endif -/* MQTT include. */ -#include "aws_iot_mqtt.h" - /* Linear containers (lists and queues) include. */ #include "iot_linear_containers.h" -/* Platform clock include. */ -#include "platform/aws_iot_clock.h" +/* Platform layer types include. */ +#include "platform/types/iot_platform_types.h" + +/* MQTT include. */ +#include "aws_iot_mqtt.h" /** * @def AwsIotMqtt_Assert( expression ) @@ -81,92 +81,92 @@ #if AWS_IOT_STATIC_MEMORY_ONLY == 1 #include "platform/aws_iot_static_memory.h" - /** - * @brief Allocate an #_mqttConnection_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate an #_mqttConnection_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotMqtt_MallocConnection #define AwsIotMqtt_MallocConnection AwsIot_MallocMqttConnection #endif - /** - * @brief Free an #_mqttConnection_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free an #_mqttConnection_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotMqtt_FreeConnection #define AwsIotMqtt_FreeConnection AwsIot_FreeMqttConnection #endif - /** - * @brief Allocate memory for an MQTT packet. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate memory for an MQTT packet. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotMqtt_MallocMessage #define AwsIotMqtt_MallocMessage AwsIot_MallocMessageBuffer #endif - /** - * @brief Free an MQTT packet. This function should have the same signature - * as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free an MQTT packet. This function should have the same signature + * as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotMqtt_FreeMessage #define AwsIotMqtt_FreeMessage AwsIot_FreeMessageBuffer #endif - /** - * @brief Allocate an #_mqttOperation_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate an #_mqttOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotMqtt_MallocOperation #define AwsIotMqtt_MallocOperation AwsIot_MallocMqttOperation #endif - /** - * @brief Free an #_mqttOperation_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free an #_mqttOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotMqtt_FreeOperation #define AwsIotMqtt_FreeOperation AwsIot_FreeMqttOperation #endif - /** - * @brief Allocate an #_mqttSubscription_t. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate an #_mqttSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotMqtt_MallocSubscription #define AwsIotMqtt_MallocSubscription AwsIot_MallocMqttSubscription #endif - /** - * @brief Free an #_mqttSubscription_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free an #_mqttSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotMqtt_FreeSubscription #define AwsIotMqtt_FreeSubscription AwsIot_FreeMqttSubscription #endif - /** - * @brief Allocate an #_mqttTimerEvent_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate an #_mqttTimerEvent_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotMqtt_MallocTimerEvent #define AwsIotMqtt_MallocTimerEvent AwsIot_MallocMqttTimerEvent #endif - /** - * @brief Free an #_mqttTimerEvent_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free an #_mqttTimerEvent_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotMqtt_FreeTimerEvent #define AwsIotMqtt_FreeTimerEvent AwsIot_FreeMqttTimerEvent #endif @@ -298,9 +298,9 @@ struct _mqttTimerEvent; */ typedef struct _mqttSubscription { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - int references; /**< @brief How many subscription callbacks are using this subscription. */ + int references; /**< @brief How many subscription callbacks are using this subscription. */ /** * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for @@ -330,18 +330,18 @@ typedef struct _mqttSubscription */ typedef struct _mqttConnection { - bool errorOccurred; /**< @brief Tracks if a protocol violation or other error occurred. */ - bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ - AwsIotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - AwsIotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the #_mqttConnection_t.subscriptionList. */ - - AwsIotTimer_t timer; /**< @brief Expires when a timer event should be processed. */ - AwsIotMutex_t timerMutex; /**< @brief Prevents concurrent access from timer thread and protects timer event list. */ - IotListDouble_t timerEventList; /**< @brief List of active timer events. */ - uint16_t keepAliveSeconds; /**< @brief Keep-alive interval. */ - struct _mqttOperation * pPingreqOperation; /**< @brief PINGREQ operation. Only used if keep-alive is active. */ - struct _mqttTimerEvent * pKeepAliveEvent; /**< @brief When to process a keep-alive. Only used if keep-alive is active. */ + bool errorOccurred; /**< @brief Tracks if a protocol violation or other error occurred. */ + bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ + AwsIotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the #_mqttConnection_t.subscriptionList. */ + + IotTimer_t timer; /**< @brief Expires when a timer event should be processed. */ + IotMutex_t timerMutex; /**< @brief Prevents concurrent access from timer thread and protects timer event list. */ + IotListDouble_t timerEventList; /**< @brief List of active timer events. */ + uint16_t keepAliveSeconds; /**< @brief Keep-alive interval. */ + struct _mqttOperation * pPingreqOperation; /**< @brief PINGREQ operation. Only used if keep-alive is active. */ + struct _mqttTimerEvent * pKeepAliveEvent; /**< @brief When to process a keep-alive. Only used if keep-alive is active. */ } _mqttConnection_t; /** @@ -353,7 +353,7 @@ typedef struct _mqttConnection typedef struct _mqttOperation { /* Pointers to neighboring queue elements. */ - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ @@ -375,10 +375,10 @@ typedef struct _mqttOperation /* How to notify of an operation's completion. */ union { - AwsIotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ - AwsIotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ - } notify; /**< @brief How to notify of this operation's completion. */ - AwsIotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ + IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ + AwsIotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ + } notify; /**< @brief How to notify of this operation's completion. */ + AwsIotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ struct _mqttTimerEvent * pPublishRetry; /**< @brief How an operation will be retried. Only used for QoS 1 publishes. */ }; @@ -392,7 +392,7 @@ typedef struct _mqttOperation void ( * freeReceivedData )( void * ); /**< @brief Function called to free `pReceivedData`. */ }; }; - } _mqttOperation_t; +} _mqttOperation_t; /** * @brief Represents an operation that is subject to a timer. @@ -402,7 +402,7 @@ typedef struct _mqttOperation */ typedef struct _mqttTimerEvent { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ uint64_t expirationTime; /**< @brief When this event should be processed. */ struct _mqttOperation * pOperation; /**< @brief The MQTT operation associated with this event. */ @@ -434,16 +434,16 @@ typedef struct _mqttOperationQueue * @brief Maintains a count of threads currently available to process this * queue and provides a mechanism to wait for active callback threads to finish. */ - AwsIotSemaphore_t availableThreads; + IotSemaphore_t availableThreads; } _mqttOperationQueue_t; /* Declarations of the structures keeping track of MQTT operations for internal * files. */ extern _mqttOperationQueue_t _IotMqttCallback; extern _mqttOperationQueue_t _IotMqttSend; -extern AwsIotMutex_t _IotMqttQueueMutex; +extern IotMutex_t _IotMqttQueueMutex; extern IotListDouble_t _IotMqttPendingResponse; -extern AwsIotMutex_t _IotMqttPendingResponseMutex; +extern IotMutex_t _IotMqttPendingResponseMutex; /*-------------------- MQTT struct validation functions ---------------------*/ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index dedab0d9c6..f7d7f8955a 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -33,12 +33,15 @@ #include IOT_CONFIG_FILE #endif -/* Shadow include. */ -#include "aws_iot_shadow.h" - /* Linear containers (lists and queues) include. */ #include "iot_linear_containers.h" +/* Platform layer types include. */ +#include "platform/types/iot_platform_types.h" + +/* Shadow include. */ +#include "aws_iot_shadow.h" + /** * @def AwsIotShadow_Assert( expression ) * @brief Assertion macro for the Shadow library. @@ -412,7 +415,7 @@ typedef struct _shadowOperation /* How to notify of an operation's completion. */ union { - AwsIotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref shadow_function_wait. */ + IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref shadow_function_wait. */ AwsIotShadowCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ } notify; /**< @brief How to notify of an operation's completion. */ } _shadowOperation_t; @@ -451,8 +454,8 @@ typedef struct _shadowSubscription extern uint64_t _AwsIotShadowMqttTimeoutMs; extern IotListDouble_t _AwsIotShadowPendingOperations; extern IotListDouble_t _AwsIotShadowSubscriptions; -extern AwsIotMutex_t _AwsIotShadowPendingOperationsMutex; -extern AwsIotMutex_t _AwsIotShadowSubscriptionsMutex; +extern IotMutex_t _AwsIotShadowPendingOperationsMutex; +extern IotMutex_t _AwsIotShadowSubscriptionsMutex; /*----------------------- Shadow operation functions ------------------------*/ diff --git a/lib/source/common/aws_iot_logging.c b/lib/source/common/aws_iot_logging.c index 97451ddd3d..c2385fe77d 100644 --- a/lib/source/common/aws_iot_logging.c +++ b/lib/source/common/aws_iot_logging.c @@ -34,9 +34,9 @@ #include /* Platform clock include. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_clock.h" -/* AWS IoT library includes. */ +/* Logging includes. */ #include "private/aws_iot_logging.h" /** @@ -46,9 +46,17 @@ * Including stdio.h also brings in unwanted (and conflicting) symbols on some * platforms. Therefore, any functions in stdio.h needed in this file have an * extern declaration here. */ -extern int sprintf( char *, const char *, ... ); -extern int snprintf( char *, size_t, const char *, ... ); -extern int vsnprintf( char *, size_t, const char *, va_list ); +extern int sprintf( char *, + const char *, + ... ); +extern int snprintf( char *, + size_t, + const char *, + ... ); +extern int vsnprintf( char *, + size_t, + const char *, + va_list ); /** @endcond */ /*-----------------------------------------------------------*/ @@ -80,10 +88,11 @@ extern int vsnprintf( char *, size_t, const char *, va_list ); * function is used. */ #ifndef AwsIotLogging_Puts - /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + */ extern int puts( const char * ); /** @endcond */ @@ -98,26 +107,26 @@ extern int vsnprintf( char *, size_t, const char *, va_list ); /* Static memory allocation header. */ #include "platform/aws_iot_static_memory.h" - /** - * @brief Allocate a new logging buffer. This function must have the same - * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate a new logging buffer. This function must have the same + * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotLogging_Malloc #define AwsIotLogging_Malloc AwsIot_MallocMessageBuffer #endif - /** - * @brief Free a logging buffer. This function must have the same signature - * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free a logging buffer. This function must have the same signature + * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotLogging_Free #define AwsIotLogging_Free AwsIot_FreeMessageBuffer #endif - /** - * @brief Get the size of a logging buffer. Statically-allocated buffers - * should all have the same size. - */ +/** + * @brief Get the size of a logging buffer. Statically-allocated buffers + * should all have the same size. + */ #ifndef AwsIotLogging_StaticBufferSize #define AwsIotLogging_StaticBufferSize AwsIot_MessageBufferSize #endif @@ -176,28 +185,28 @@ static const char * const _pLogLevelStrings[ 5 ] = /*-----------------------------------------------------------*/ #if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) -static bool _reallocLoggingBuffer( void ** pOldBuffer, - size_t newSize, - size_t oldSize ) -{ - /* Allocate a new, larger buffer. */ - void * pNewBuffer = AwsIotLogging_Malloc( newSize ); - - /* Ensure that memory allocation succeeded. */ - if( pNewBuffer == NULL ) + static bool _reallocLoggingBuffer( void ** pOldBuffer, + size_t newSize, + size_t oldSize ) { - return false; - } + /* Allocate a new, larger buffer. */ + void * pNewBuffer = AwsIotLogging_Malloc( newSize ); - /* Copy the data from the old buffer to the new buffer. */ - ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); + /* Ensure that memory allocation succeeded. */ + if( pNewBuffer == NULL ) + { + return false; + } - /* Free the old buffer and update the pointer. */ - AwsIotLogging_Free( *pOldBuffer ); - *pOldBuffer = pNewBuffer; + /* Copy the data from the old buffer to the new buffer. */ + ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); - return true; -} + /* Free the old buffer and update the pointer. */ + AwsIotLogging_Free( *pOldBuffer ); + *pOldBuffer = pNewBuffer; + + return true; + } #endif /* if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) */ /*-----------------------------------------------------------*/ @@ -320,9 +329,9 @@ void AwsIotLogGeneric( int libraryLogSetting, bufferPosition++; /* Generate the timestring and add it to the buffer. */ - if( AwsIotClock_GetTimestring( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - ×tringLength ) == true ) + if( IotClock_GetTimestring( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + ×tringLength ) == true ) { /* If the timestring was successfully generated, add the closing "]". */ bufferPosition += timestringLength; diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index f78f2e637d..d5235c1912 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -35,6 +35,10 @@ /* MQTT internal include. */ #include "private/aws_iot_mqtt_internal.h" +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + /* Validate MQTT configuration settings. */ #if AWS_IOT_MQTT_ENABLE_ASSERTS != 0 && AWS_IOT_MQTT_ENABLE_ASSERTS != 1 #error "AWS_IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." @@ -194,7 +198,7 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio * Because CONNACK contains no data about which CONNECT packet it acknowledges, * only one CONNECT operation may be in-progress at any time. */ -static AwsIotMutex_t _connectMutex; +static IotMutex_t _connectMutex; /*-----------------------------------------------------------*/ @@ -270,7 +274,7 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, else { /* Check for a PINGRESP after AWS_IOT_MQTT_RESPONSE_WAIT_MS. */ - pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + AWS_IOT_MQTT_RESPONSE_WAIT_MS; + pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + AWS_IOT_MQTT_RESPONSE_WAIT_MS; pKeepAliveEvent->checkPingresp = true; } } @@ -282,7 +286,7 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, AwsIotLogDebug( "PINGRESP received." ); /* The next keep-alive event should send another PINGREQ. */ - pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + + pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + pMqttConnection->keepAliveSeconds * 1000ULL; pKeepAliveEvent->checkPingresp = false; } @@ -479,7 +483,7 @@ static void _timerThread( void * pArgument ) /* Attempt to lock the timer mutex before this thread does anything. * Return immediately if the mutex couldn't be locked. */ - if( AwsIotMutex_TryLock( &( pMqttConnection->timerMutex ) ) == false ) + if( IotMutex_TryLock( &( pMqttConnection->timerMutex ) ) == false ) { AwsIotLogWarn( "Failed to lock connection timer mutex in timer thread. Exiting." ); @@ -496,7 +500,7 @@ static void _timerThread( void * pArgument ) if( pTimerEvent != NULL ) { /* Check if the first event should be processed now. */ - if( pTimerEvent->expirationTime <= AwsIotClock_GetTimeMs() + AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ) + if( pTimerEvent->expirationTime <= IotClock_GetTimeMs() + AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ) { /* Remove the timer event for immediate processing. */ IotListDouble_Remove( &( pTimerEvent->link ) ); @@ -505,9 +509,9 @@ static void _timerThread( void * pArgument ) { /* The first element in the timer queue shouldn't be processed yet. * Arm the timer for when it should be processed. */ - if( AwsIotClock_TimerArm( &( pMqttConnection->timer ), - pTimerEvent->expirationTime - AwsIotClock_GetTimeMs(), - 0 ) == false ) + if( IotClock_TimerArm( &( pMqttConnection->timer ), + pTimerEvent->expirationTime - IotClock_GetTimeMs(), + 0 ) == false ) { AwsIotLogWarn( "Failed to re-arm timer for connection %p.", pMqttConnection ); @@ -565,7 +569,7 @@ static void _timerThread( void * pArgument ) } } - AwsIotMutex_Unlock( &( pMqttConnection->timerMutex ) ); + IotMutex_Unlock( &( pMqttConnection->timerMutex ) ); } /*-----------------------------------------------------------*/ @@ -608,7 +612,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; /* Create the timer mutex for a new connection. */ - if( AwsIotMutex_Create( &( pNewMqttConnection->timerMutex ) ) == false ) + if( IotMutex_Create( &( pNewMqttConnection->timerMutex ) ) == false ) { AwsIotLogError( "Failed to create timer mutex for new connection." ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); @@ -616,10 +620,10 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, return NULL; } - if( AwsIotMutex_Create( &( pNewMqttConnection->subscriptionMutex ) ) == false ) + if( IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ) ) == false ) { AwsIotLogError( "Failed to create subscription mutex for new connection." ); - AwsIotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); + IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); return NULL; @@ -630,13 +634,13 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, IotListDouble_Create( &( pNewMqttConnection->timerEventList ) ); /* Create the timer mutex for a new connection. */ - if( AwsIotClock_TimerCreate( &( pNewMqttConnection->timer ), - _timerThread, - pNewMqttConnection ) == false ) + if( IotClock_TimerCreate( &( pNewMqttConnection->timer ), + _timerThread, + pNewMqttConnection ) == false ) { AwsIotLogError( "Failed to create timer for new connection." ); - AwsIotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); - AwsIotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); + IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); + IotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); return NULL; @@ -697,7 +701,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, /* Set the members of the keep-alive timer event. */ ( void ) memset( pNewMqttConnection->pKeepAliveEvent, 0x00, sizeof( _mqttTimerEvent_t ) ); pNewMqttConnection->pKeepAliveEvent->pOperation = pNewMqttConnection->pPingreqOperation; - pNewMqttConnection->pKeepAliveEvent->expirationTime = AwsIotClock_GetTimeMs() + keepAliveSeconds * 1000ULL; + pNewMqttConnection->pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + keepAliveSeconds * 1000ULL; /* Add the PINGREQ to the timer event list. */ IotListDouble_InsertSorted( &( pNewMqttConnection->timerEventList ), @@ -727,16 +731,16 @@ static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) } /* Remove any previous session subscriptions. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); IotListDouble_RemoveAll( &( pMqttConnection->subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); /* Destroy timer and mutexes. */ - AwsIotClock_TimerDestroy( &( pMqttConnection->timer ) ); - AwsIotMutex_Destroy( &( pMqttConnection->timerMutex ) ); - AwsIotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); + IotClock_TimerDestroy( &( pMqttConnection->timer ) ); + IotMutex_Destroy( &( pMqttConnection->timerMutex ) ); + IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); AwsIotMqtt_FreeConnection( pMqttConnection ); } @@ -875,7 +879,7 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio *pMqttConnection = pNewMqttConnection; /* Prevent another CONNECT operation from using the network. */ - AwsIotMutex_Lock( &_connectMutex ); + IotMutex_Lock( &_connectMutex ); /* Add the CONNECT operation to the send queue for network transmission. */ if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation, @@ -893,7 +897,7 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio } /* Unlock the CONNECT mutex. */ - AwsIotMutex_Unlock( &_connectMutex ); + IotMutex_Unlock( &_connectMutex ); /* Arm the timer for the first keep alive expiration if keep-alive is * active for this connection. */ @@ -902,9 +906,9 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio { AwsIotLogDebug( "Starting new MQTT connection timer." ); - if( AwsIotClock_TimerArm( &( pNewMqttConnection->timer ), - pNewMqttConnection->pKeepAliveEvent->expirationTime - AwsIotClock_GetTimeMs(), - 0 ) == false ) + if( IotClock_TimerArm( &( pNewMqttConnection->timer ), + pNewMqttConnection->pKeepAliveEvent->expirationTime - IotClock_GetTimeMs(), + 0 ) == false ) { AwsIotLogError( "Failed to start connection timer for new MQTT connection" ); @@ -1102,7 +1106,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; /* Create mutex protecting MQTT operation queues. */ - if( AwsIotMutex_Create( &( _IotMqttQueueMutex ) ) == false ) + if( IotMutex_Create( &( _IotMqttQueueMutex ) ) == false ) { AwsIotLogError( "Failed to initialize MQTT operation queue mutex." ); status = AWS_IOT_MQTT_INIT_FAILED; @@ -1111,10 +1115,10 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) /* Create mutex protecting list of operations pending network responses. */ if( status == AWS_IOT_MQTT_SUCCESS ) { - if( AwsIotMutex_Create( &( _IotMqttPendingResponseMutex ) ) == false ) + if( IotMutex_Create( &( _IotMqttPendingResponseMutex ) ) == false ) { AwsIotLogError( "Failed to initialize MQTT library pending response mutex." ); - AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); + IotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1123,11 +1127,11 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) /* Create CONNECT mutex. */ if( status == AWS_IOT_MQTT_SUCCESS ) { - if( AwsIotMutex_Create( &( _connectMutex ) ) == false ) + if( IotMutex_Create( &( _connectMutex ) ) == false ) { AwsIotLogError( "Failed to initialize MQTT library connect mutex." ); - AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); + IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1137,9 +1141,9 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) if( status == AWS_IOT_MQTT_SUCCESS ) { /* Create semaphore that counts active callback threads. */ - if( AwsIotSemaphore_Create( &( _IotMqttCallback.availableThreads ), - AWS_IOT_MQTT_MAX_CALLBACK_THREADS, - AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) + if( IotSemaphore_Create( &( _IotMqttCallback.availableThreads ), + AWS_IOT_MQTT_MAX_CALLBACK_THREADS, + AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) { AwsIotLogError( "Failed to initialize record of active MQTT callback threads." ); status = AWS_IOT_MQTT_INIT_FAILED; @@ -1147,14 +1151,14 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) else { /* Create semaphore that counts active send threads. */ - if( AwsIotSemaphore_Create( &( _IotMqttSend.availableThreads ), - AWS_IOT_MQTT_MAX_SEND_THREADS, - AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) + if( IotSemaphore_Create( &( _IotMqttSend.availableThreads ), + AWS_IOT_MQTT_MAX_SEND_THREADS, + AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) { AwsIotLogError( "Failed to initialize record of active MQTT send threads." ); status = AWS_IOT_MQTT_INIT_FAILED; - AwsIotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); + IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); } } @@ -1162,9 +1166,9 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) * not be created. */ if( status == AWS_IOT_MQTT_INIT_FAILED ) { - AwsIotMutex_Destroy( &( _connectMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); + IotMutex_Destroy( &( _connectMutex ) ); + IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Destroy( &( _IotMqttQueueMutex ) ); } } @@ -1174,11 +1178,11 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) { AwsIotLogError( "Failed to initialize MQTT library serializer. " ); - AwsIotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); - AwsIotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); - AwsIotMutex_Destroy( &( _connectMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); + IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); + IotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); + IotMutex_Destroy( &( _connectMutex ) ); + IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1202,23 +1206,23 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) void AwsIotMqtt_Cleanup() { /* Wait for termination of any active MQTT library threads. */ - AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + IotMutex_Lock( &( _IotMqttQueueMutex ) ); - while( AwsIotSemaphore_GetCount( &( _IotMqttCallback.availableThreads ) ) > 0 ) + while( IotSemaphore_GetCount( &( _IotMqttCallback.availableThreads ) ) > 0 ) { - AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); - AwsIotSemaphore_Wait( &( _IotMqttCallback.availableThreads ) ); - AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); + IotSemaphore_Wait( &( _IotMqttCallback.availableThreads ) ); + IotMutex_Lock( &( _IotMqttQueueMutex ) ); } - while( AwsIotSemaphore_GetCount( &( _IotMqttSend.availableThreads ) ) > 0 ) + while( IotSemaphore_GetCount( &( _IotMqttSend.availableThreads ) ) > 0 ) { - AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); - AwsIotSemaphore_Wait( &( _IotMqttSend.availableThreads ) ); - AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); + IotSemaphore_Wait( &( _IotMqttSend.availableThreads ) ); + IotMutex_Lock( &( _IotMqttQueueMutex ) ); } - AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); /* This API requires all MQTT connections to be terminated. If the MQTT library * linear containers are not empty, there is an active MQTT connection and the @@ -1228,13 +1232,13 @@ void AwsIotMqtt_Cleanup() AwsIotMqtt_Assert( IotListDouble_IsEmpty( &( _IotMqttPendingResponse ) ) == true ); /* Clean up MQTT library mutexes. */ - AwsIotMutex_Destroy( &( _connectMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - AwsIotMutex_Destroy( &( _IotMqttQueueMutex ) ); + IotMutex_Destroy( &( _connectMutex ) ); + IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Destroy( &( _IotMqttQueueMutex ) ); /* Clean up thread counter semaphores. */ - AwsIotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); - AwsIotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); + IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); + IotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); /* Clean up MQTT serializer. */ AwsIotMqttInternal_CleanupSerialize(); @@ -1560,7 +1564,7 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, pLastPublish = NULL; /* Prevent the timer thread from running, then set the error flag. */ - AwsIotMutex_Lock( &( pConnectionInfo->timerMutex ) ); + IotMutex_Lock( &( pConnectionInfo->timerMutex ) ); pConnectionInfo->errorOccurred = true; @@ -1573,7 +1577,7 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); } - AwsIotMutex_Unlock( &( pConnectionInfo->timerMutex ) ); + IotMutex_Unlock( &( pConnectionInfo->timerMutex ) ); return -1; } @@ -1738,16 +1742,16 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotLogInfo( "Disconnecting MQTT connection %p.", pMqttConnection ); /* Purge all of this connection's subscriptions. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), _mqttSubscription_shouldRemove, NULL, AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); /* Lock the connection timer mutex to block the timer thread. */ - AwsIotMutex_Lock( &( pMqttConnection->timerMutex ) ); + IotMutex_Lock( &( pMqttConnection->timerMutex ) ); /* Purge all of this connection's pending operations and timer events. */ IotQueue_RemoveAllMatches( &( _IotMqttSend.queue ), @@ -1771,7 +1775,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, /* Stop the connection timer. */ AwsIotLogDebug( "Stopping connection timer." ); - AwsIotClock_TimerDestroy( &( pMqttConnection->timer ) ); + IotClock_TimerDestroy( &( pMqttConnection->timer ) ); /* Only send a DISCONNECT packet if no error occurred and the "cleanup only" * option is false. */ @@ -1849,7 +1853,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, } /* Unlock the connection timer mutex. */ - AwsIotMutex_Unlock( &( pMqttConnection->timerMutex ) ); + IotMutex_Unlock( &( pMqttConnection->timerMutex ) ); /* Close the network connection regardless of whether an MQTT DISCONNECT * packet was sent. */ @@ -1863,8 +1867,8 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, } /* Destroy the MQTT connection's mutexes. */ - AwsIotMutex_Destroy( &( pMqttConnection->timerMutex ) ); - AwsIotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Destroy( &( pMqttConnection->timerMutex ) ); + IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); /* Free the memory used by this connection. */ AwsIotMqtt_FreeConnection( pMqttConnection ); @@ -2188,13 +2192,13 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, AwsIotMqtt_OperationType( pOperation->operation ) ); /* Wait for the operation to be sent once. This wait should return quickly. */ - AwsIotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); /* Check any status set by the send thread. Block the receive callback * during this check by locking the mutex for operations pending responses. */ - AwsIotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); status = pOperation->status; - AwsIotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); if( status == AWS_IOT_MQTT_STATUS_PENDING ) { @@ -2202,16 +2206,16 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, * thread during this check by locking the connection mutex. */ if( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) { - AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); + IotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); publishRetryActive = ( pOperation->pPublishRetry != NULL ); - AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); + IotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); } /* Wait for a response to be received from the network. Record when * the wait begins for a PUBLISH with retry. */ if( publishRetryActive == true ) { - startTime = AwsIotClock_GetTimeMs(); + startTime = IotClock_GetTimeMs(); AwsIotMqtt_Assert( startTime > 0 ); } @@ -2224,7 +2228,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, if( publishRetryActive == true ) { /* Get current time. */ - currentTime = AwsIotClock_GetTimeMs(); + currentTime = IotClock_GetTimeMs(); AwsIotMqtt_Assert( currentTime >= startTime ); /* Calculate elapsed time. */ @@ -2242,8 +2246,8 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, } /* Block on the wait semaphore. */ - if( AwsIotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), - remainingMs ) == false ) + if( IotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + remainingMs ) == false ) { /* No status received before timeout. */ status = AWS_IOT_MQTT_TIMEOUT; @@ -2260,7 +2264,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, * status. */ if( publishRetryActive == true ) { - AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); + IotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); } /* Successfully received a notification of completion. Retrieve the @@ -2269,7 +2273,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, if( publishRetryActive == true ) { - AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); + IotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); } } } @@ -2278,7 +2282,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, { /* If a status was set by the send thread, wait for the send thread to be * completely done with the operation. */ - AwsIotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); } /* A completed operation should not be linked. */ @@ -2308,7 +2312,7 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, } else { - AwsIotMqtt_Assert( AwsIotSemaphore_GetCount( &( pOperation->notify.waitSemaphore ) ) == 0 ); + AwsIotMqtt_Assert( IotSemaphore_GetCount( &( pOperation->notify.waitSemaphore ) ) == 0 ); pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; } diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index bdbb543690..d94c2c7fec 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -35,6 +35,10 @@ /* MQTT internal include. */ #include "private/aws_iot_mqtt_internal.h" +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + /*-----------------------------------------------------------*/ /** @@ -116,11 +120,11 @@ static void _processOperation( void * pArgument ); */ _mqttOperationQueue_t _IotMqttCallback = { 0 }; /**< @brief Queue of MQTT operations waiting for their callback to be invoked. */ _mqttOperationQueue_t _IotMqttSend = { 0 }; /**< @brief Queue of MQTT operations waiting to be sent. */ -AwsIotMutex_t _IotMqttQueueMutex; /**< @brief Protects both #_IotMqttSend and #_IotMqttCallback from concurrent access. */ +IotMutex_t _IotMqttQueueMutex; /**< @brief Protects both #_IotMqttSend and #_IotMqttCallback from concurrent access. */ /* List of MQTT operations awaiting a network response. */ IotListDouble_t _IotMqttPendingResponse = { 0 }; /**< @brief List of MQTT operations awaiting a response from the MQTT server. */ -AwsIotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ +IotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ /*-----------------------------------------------------------*/ @@ -168,7 +172,7 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio /* Calculate when the PUBLISH retry event should expire relative to the current * time. */ - pPublishRetry->expirationTime = AwsIotClock_GetTimeMs(); + pPublishRetry->expirationTime = IotClock_GetTimeMs(); if( pPublishRetry->retry.count > pPublishRetry->retry.limit ) { @@ -194,7 +198,7 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio } /* Lock the connection timer mutex to block the timer thread. */ - AwsIotMutex_Lock( &( pMqttConnection->timerMutex ) ); + IotMutex_Lock( &( pMqttConnection->timerMutex ) ); /* Peek the current head of the timer event list. If the PUBLISH retry expires * sooner, re-arm the timer to expire at the PUBLISH retry's expiration time. */ @@ -205,9 +209,9 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio if( ( pTimerListHead == NULL ) || ( pTimerListHead->expirationTime > pPublishRetry->expirationTime ) ) { - status = AwsIotClock_TimerArm( &( pMqttConnection->timer ), - pPublishRetry->expirationTime - AwsIotClock_GetTimeMs(), - 0 ); + status = IotClock_TimerArm( &( pMqttConnection->timer ), + pPublishRetry->expirationTime - IotClock_GetTimeMs(), + 0 ); if( status == false ) { @@ -224,7 +228,7 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio AwsIotMqttInternal_TimerEventCompare ); } - AwsIotMutex_Unlock( &( pMqttConnection->timerMutex ) ); + IotMutex_Unlock( &( pMqttConnection->timerMutex ) ); return status; } @@ -313,15 +317,15 @@ static void _invokeSend( _mqttOperation_t * const pOperation ) /* Otherwise, add operation to the list of MQTT operations pending responses. */ else { - AwsIotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); IotListDouble_InsertTail( &( _IotMqttPendingResponse ), &( pOperation->link ) ); - AwsIotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); } /* Notify a waitable operation that it has been sent. */ if( waitableOperation == true ) { - AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); } } @@ -407,7 +411,7 @@ static void _processOperation( void * pArgument ) AwsIotLogDebug( "Removing oldest operation from MQTT pending operations." ); /* Remove the oldest operation from the queue of pending MQTT operations. */ - AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + IotMutex_Lock( &( _IotMqttQueueMutex ) ); pOperation = IotLink_Container( _mqttOperation_t, IotQueue_Dequeue( &( pQueue->queue ) ), @@ -417,10 +421,10 @@ static void _processOperation( void * pArgument ) * number of available threads. */ if( pOperation == NULL ) { - AwsIotSemaphore_Post( &( pQueue->availableThreads ) ); + IotSemaphore_Post( &( pQueue->availableThreads ) ); } - AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); /* Terminate thread if no operation was received. */ if( pOperation == NULL ) @@ -517,7 +521,7 @@ AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const { /* The wait semaphore counts up to 2: once for when the send thread completes, * and once for when the entire operation completes. */ - if( AwsIotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 2 ) == false ) + if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 2 ) == false ) { AwsIotLogError( "Failed to create semaphore for waitable MQTT operation." ); @@ -576,7 +580,7 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) if( ( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) && ( pOperation->pPublishRetry != NULL ) ) { - AwsIotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); + IotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); /* Remove the timer event from the timer event list before freeing it. * The return value of this function is not checked because it always @@ -586,7 +590,7 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) NULL, pOperation->pPublishRetry ); - AwsIotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); + IotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); AwsIotMqtt_FreeTimerEvent( pOperation->pPublishRetry ); } @@ -594,7 +598,7 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) /* Check if a wait semaphore was created for this operation. */ if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) { - AwsIotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); } /* Free the memory used to hold operation data. */ @@ -616,27 +620,27 @@ AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const ( pQueue == &( _IotMqttSend ) ) ); /* Lock mutex for exclusive access to queues. */ - AwsIotMutex_Lock( &( _IotMqttQueueMutex ) ); + IotMutex_Lock( &( _IotMqttQueueMutex ) ); /* Add operation to queue. */ IotQueue_Enqueue( &( pQueue->queue ), &( pOperation->link ) ); /* Check if a new thread can be created. */ - if( AwsIotSemaphore_TryWait( &( pQueue->availableThreads ) ) == true ) + if( IotSemaphore_TryWait( &( pQueue->availableThreads ) ) == true ) { /* Create new thread. */ - if( AwsIot_CreateDetachedThread( _processOperation, - pQueue ) == false ) + if( Iot_CreateDetachedThread( _processOperation, + pQueue ) == false ) { /* New thread could not be created. Remove enqueued operation and * report error. */ IotQueue_Remove( &( pOperation->link ) ); - AwsIotSemaphore_Post( &( pQueue->availableThreads ) ); + IotSemaphore_Post( &( pQueue->availableThreads ) ); status = AWS_IOT_MQTT_NO_MEMORY; } } - AwsIotMutex_Unlock( &( _IotMqttQueueMutex ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); return status; } @@ -662,14 +666,14 @@ _mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t o param.pPacketIdentifier = pPacketIdentifier; /* Find the first matching element in the list. */ - AwsIotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); pResult = IotLink_Container( _mqttOperation_t, IotListDouble_RemoveFirstMatch( &( _IotMqttPendingResponse ), NULL, _mqttOperation_match, ¶m ), link ); - AwsIotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); + IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); /* The result will be NULL if no corresponding operation was found in the * list. */ @@ -706,7 +710,7 @@ void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) * Otherwise, enqueue it for callback. */ if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) { - AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); } else { diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c index fc5d7a4d43..938d1aaf50 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -35,6 +35,9 @@ /* MQTT internal includes. */ #include "private/aws_iot_mqtt_internal.h" +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /*-----------------------------------------------------------*/ /* @@ -295,7 +298,7 @@ static const AwsIotLogConfig_t _logHideAll = * Each packet should have a unique packet identifier. This mutex ensures that only * one thread at a time may read the global packet identifer. */ -static AwsIotMutex_t _packetIdentifierMutex; +static IotMutex_t _packetIdentifierMutex; /*-----------------------------------------------------------*/ @@ -306,7 +309,7 @@ static uint16_t _nextPacketIdentifier( void ) /* Lock the packet identifier mutex so that only one thread may read and * modify nextPacketIdentifier. */ - AwsIotMutex_Lock( &_packetIdentifierMutex ); + IotMutex_Lock( &_packetIdentifierMutex ); /* Read the next packet identifier. */ newPacketIdentifier = nextPacketIdentifier; @@ -317,7 +320,7 @@ static uint16_t _nextPacketIdentifier( void ) nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); /* Unlock the packet identifier mutex. */ - AwsIotMutex_Unlock( &_packetIdentifierMutex ); + IotMutex_Unlock( &_packetIdentifierMutex ); return newPacketIdentifier; } @@ -612,7 +615,7 @@ static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ) { /* Create the packet identifier mutex. */ - if( AwsIotMutex_Create( &_packetIdentifierMutex ) == false ) + if( IotMutex_Create( &_packetIdentifierMutex ) == false ) { return AWS_IOT_MQTT_INIT_FAILED; } @@ -623,7 +626,7 @@ AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ) #ifdef AwsIotMqttInternal_InitSerializeAdditional if( AwsIotMqttInternal_InitSerializeAdditional() == false ) { - AwsIotMutex_Destroy( &_packetIdentifierMutex ); + IotMutex_Destroy( &_packetIdentifierMutex ); return AWS_IOT_MQTT_INIT_FAILED; } @@ -638,7 +641,7 @@ AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ) void AwsIotMqttInternal_CleanupSerialize( void ) { /* Destroy the packet identifier mutex. */ - AwsIotMutex_Destroy( &_packetIdentifierMutex ); + IotMutex_Destroy( &_packetIdentifierMutex ); /* Call any additional serializer cleanup initialization function is serializer * overrides are enabled. */ diff --git a/lib/source/mqtt/aws_iot_mqtt_subscription.c b/lib/source/mqtt/aws_iot_mqtt_subscription.c index 0497bb4058..f36e7a0807 100644 --- a/lib/source/mqtt/aws_iot_mqtt_subscription.c +++ b/lib/source/mqtt/aws_iot_mqtt_subscription.c @@ -36,6 +36,9 @@ /* MQTT internal include. */ #include "private/aws_iot_mqtt_internal.h" +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /*-----------------------------------------------------------*/ /** @@ -240,7 +243,7 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const _mqttSubscription_t * pNewSubscription = NULL; _topicMatchParams_t topicMatchParams = { .exactMatchOnly = true }; - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); for( i = 0; i < subscriptionCount; i++ ) { @@ -273,7 +276,7 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const /* If memory allocation failed, remove all previously added subscriptions. */ if( pNewSubscription == NULL ) { - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, pSubscriptionList, i ); @@ -300,7 +303,7 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const } } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); return AWS_IOT_MQTT_SUCCESS; } @@ -325,7 +328,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio /* Prevent any other thread from modifying the subscription list while this * function is searching. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); pStartPoint = IotListDouble_PeekHead( &( pMqttConnection->subscriptionList ) ); /* Search the subscription list for all matching subscriptions. */ @@ -356,7 +359,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio callbackFunction = pSubscription->callback.function; /* Unlock the subscription list mutex. */ - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); /* Set the members of the callback parameter. */ pCallbackParam->mqttConnection = pMqttConnection; @@ -367,7 +370,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio callbackFunction( pParam1, pCallbackParam ); /* Lock the subscription list mutex to decrement the reference count. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Decrement the reference count. It must still be positive. */ ( pSubscription->references )--; @@ -385,7 +388,7 @@ void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnectio } } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -419,7 +422,7 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con /* Prevent any other thread from modifying the subscription list while this * function is running. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Find and remove each topic filter from the list. */ for( i = 0; i < subscriptionCount; i++ ) @@ -457,7 +460,7 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con } } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -479,7 +482,7 @@ bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, /* Prevent any other thread from modifying the subscription list while this * function is running. */ - AwsIotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Search for a matching subscription. */ pSubscription = IotLink_Container( _mqttSubscription_t, @@ -504,7 +507,7 @@ bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, status = true; } - AwsIotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); return status; } diff --git a/lib/source/platform/posix/CMakeLists.txt b/lib/source/platform/posix/CMakeLists.txt index 9ad216ed15..7f4356af3f 100644 --- a/lib/source/platform/posix/CMakeLists.txt +++ b/lib/source/platform/posix/CMakeLists.txt @@ -73,8 +73,8 @@ endif() # Platform libraries source files. add_library( awsiotplatform SHARED - aws_iot_clock_posix.c - aws_iot_threads_posix.c + iot_clock_posix.c + iot_threads_posix.c ${NETWORK_SOURCE_FILE} static_memory/aws_iot_static_memory_common_posix.c static_memory/aws_iot_static_memory_mqtt_posix.c diff --git a/lib/source/platform/posix/aws_iot_clock_posix.c b/lib/source/platform/posix/iot_clock_posix.c similarity index 54% rename from lib/source/platform/posix/aws_iot_clock_posix.c rename to lib/source/platform/posix/iot_clock_posix.c index ec1c1eeaf2..72d594d3a2 100644 --- a/lib/source/platform/posix/aws_iot_clock_posix.c +++ b/lib/source/platform/posix/iot_clock_posix.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_clock_posix.c - * @brief Implementation of the functions in aws_iot_clock.h for POSIX systems. + * @file iot_clock_posix.c + * @brief Implementation of the functions in iot_clock.h for POSIX systems. */ /* Build using a config header, if provided. */ @@ -52,7 +52,7 @@ #endif /* Platform clock include. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_clock.h" /* Configure logs for the functions in this file. */ #ifdef AWS_IOT_LOG_LEVEL_PLATFORM @@ -68,29 +68,6 @@ #define _LIBRARY_LOG_NAME ( "CLOCK" ) #include "aws_iot_logging_setup.h" -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#ifndef AwsIotClock_Malloc - #include - - /** - * @brief Memory allocation. This function should have the same signature as - * [malloc.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - */ - #define AwsIotClock_Malloc malloc -#endif -#ifndef AwsIotClock_Free - #include - - /** - * @brief Free memory. This function should have the same signature as - * [free.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - */ - #define AwsIotClock_Free free -#endif - /*-----------------------------------------------------------*/ /** @@ -99,7 +76,7 @@ * For more information on timestring formats, see [this link.] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) */ -#define _TIMESTRING_FORMAT ( "%F %R:%S" ) +#define _TIMESTRING_FORMAT ( "%F %R:%S" ) /* * Time conversion constants. @@ -111,19 +88,7 @@ /*-----------------------------------------------------------*/ /** - * @brief Holds information about an active timer. - */ -typedef struct _timerInfo -{ - timer_t timer; /**< @brief Underlying POSIX timer. */ - void * pArgument; /**< @brief First argument to threadRoutine. */ - AwsIotThreadRoutine_t threadRoutine; /**< @brief Thread function to run on timer expiration. */ -} _timerInfo_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an AwsIotThreadRoutine_t with a POSIX-compliant one. + * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. * * @param[in] argument The value passed as `sigevent.sigev_value`. */ @@ -133,65 +98,69 @@ static void _timerExpirationWrapper( union sigval argument ); * @brief Convert a relative timeout in milliseconds to an absolute timeout * represented as a struct timespec. * - * This function is not included in aws_iot_clock.h because it's platform-specific. + * This function is not included in iot_clock.h because it's platform-specific. * But it may be called by other POSIX platform files. * @param[in] timeoutMs The relative timeout. * @param[out] pOutput Where to store the resulting `timespec`. * * @return `true` if `timeoutMs` was successfully converted; `false` otherwise. */ -bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, - struct timespec * const pOutput ); +bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, + struct timespec * const pOutput ); /*-----------------------------------------------------------*/ static void _timerExpirationWrapper( union sigval argument ) { - _timerInfo_t * pTimerInfo = ( _timerInfo_t * ) argument.sival_ptr; + IotTimer_t * pTimer = ( IotTimer_t * ) argument.sival_ptr; /* Call the wrapped thread routine. */ - pTimerInfo->threadRoutine( pTimerInfo->pArgument ); + pTimer->threadRoutine( pTimer->pArgument ); } /*-----------------------------------------------------------*/ -bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, - struct timespec * const pOutput ) +bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, + struct timespec * const pOutput ) { + bool status = true; struct timespec systemTime = { 0 }; - if( clock_gettime( CLOCK_REALTIME, &systemTime ) != 0 ) + if( clock_gettime( CLOCK_REALTIME, &systemTime ) == 0 ) { - AwsIotLogError( "Failed to read system time. errno=%d", errno ); + /* Add the nanoseconds value to the time. */ + systemTime.tv_nsec += ( long ) ( ( timeoutMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); - return false; - } + /* Check for overflow of nanoseconds value. */ + if( systemTime.tv_nsec >= _NANOSECONDS_PER_SECOND ) + { + systemTime.tv_nsec -= _NANOSECONDS_PER_SECOND; + systemTime.tv_sec++; + } - /* Add the nanoseconds value to the time. */ - systemTime.tv_nsec += ( long ) ( ( timeoutMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); + /* Add the seconds value to the timeout. */ + systemTime.tv_sec += ( time_t ) ( timeoutMs / _MILLISECONDS_PER_SECOND ); - /* Check for overflow of nanoseconds value. */ - if( systemTime.tv_nsec >= _NANOSECONDS_PER_SECOND ) - { - systemTime.tv_nsec -= _NANOSECONDS_PER_SECOND; - systemTime.tv_sec++; + /* Set the output parameter. */ + *pOutput = systemTime; } + else + { + AwsIotLogError( "Failed to read system time. errno=%d", errno ); - /* Add the seconds value to the timeout. */ - systemTime.tv_sec += ( time_t ) ( timeoutMs / _MILLISECONDS_PER_SECOND ); - - /* Set the output parameter. */ - *pOutput = systemTime; + status = false; + } - return true; + return status; } /*-----------------------------------------------------------*/ -bool AwsIotClock_GetTimestring( char * const pBuffer, - size_t bufferSize, - size_t * const pTimestringLength ) +bool IotClock_GetTimestring( char * const pBuffer, + size_t bufferSize, + size_t * const pTimestringLength ) { + bool status = true; const time_t unixTime = time( NULL ); struct tm localTime = { 0 }; size_t timestringLength = 0; @@ -200,27 +169,32 @@ bool AwsIotClock_GetTimestring( char * const pBuffer, * should be the pointer to the localTime struct. */ if( localtime_r( &unixTime, &localTime ) != &localTime ) { - return false; + status = false; } - /* Convert the localTime struct to a string. */ - timestringLength = strftime( pBuffer, bufferSize, _TIMESTRING_FORMAT, &localTime ); - - /* Check for error from strftime. */ - if( timestringLength == 0 ) + if( status == true ) { - return false; + /* Convert the localTime struct to a string. */ + timestringLength = strftime( pBuffer, bufferSize, _TIMESTRING_FORMAT, &localTime ); + + /* Check for error from strftime. */ + if( timestringLength == 0 ) + { + status = false; + } + else + { + /* Set the output parameter. */ + *pTimestringLength = timestringLength; + } } - /* Set the output parameter. */ - *pTimestringLength = timestringLength; - - return true; + return status; } /*-----------------------------------------------------------*/ -uint64_t AwsIotClock_GetTimeMs( void ) +uint64_t IotClock_GetTimeMs( void ) { struct timespec currentTime = { 0 }; @@ -236,80 +210,58 @@ uint64_t AwsIotClock_GetTimeMs( void ) /*-----------------------------------------------------------*/ -bool AwsIotClock_TimerCreate( AwsIotTimer_t * const pNewTimer, - AwsIotThreadRoutine_t expirationRoutine, - void * pArgument ) +bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, + IotThreadRoutine_t expirationRoutine, + void * pArgument ) { - _timerInfo_t * pTimerInfo = NULL; + bool status = true; struct sigevent expirationNotification = { .sigev_notify = SIGEV_THREAD, .sigev_signo = 0, - .sigev_value.sival_ptr = NULL, + .sigev_value.sival_ptr = pNewTimer, .sigev_notify_function = _timerExpirationWrapper, .sigev_notify_attributes = NULL }; AwsIotLogDebug( "Creating new timer %p.", pNewTimer ); - /* Allocate memory to store the expiration routine and argument. */ - pTimerInfo = AwsIotClock_Malloc( sizeof( _timerInfo_t ) ); - - if( pTimerInfo == NULL ) - { - AwsIotLogError( "Failed to allocate memory for new timer %p.", pNewTimer ); - - return false; - } - /* Set the timer expiration routine and argument. */ - pTimerInfo->pArgument = pArgument; - pTimerInfo->threadRoutine = expirationRoutine; - - expirationNotification.sigev_value.sival_ptr = pTimerInfo; + pNewTimer->threadRoutine = expirationRoutine; + pNewTimer->pArgument = pArgument; /* Create the underlying POSIX timer. */ if( timer_create( CLOCK_REALTIME, &expirationNotification, - &( pTimerInfo->timer ) ) != 0 ) + &( pNewTimer->timer ) ) != 0 ) { AwsIotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); - - AwsIotClock_Free( pTimerInfo ); - - return false; + status = false; } - /* Set the output parameter. */ - *pNewTimer = pTimerInfo; - - return true; + return status; } /*-----------------------------------------------------------*/ -void AwsIotClock_TimerDestroy( AwsIotTimer_t * const pTimer ) +void IotClock_TimerDestroy( IotTimer_t * const pTimer ) { - _timerInfo_t * pTimerInfo = ( _timerInfo_t * ) *pTimer; - AwsIotLogDebug( "Destroying timer %p.", pTimer ); /* Destroy the underlying POSIX timer. */ - if( timer_delete( pTimerInfo->timer ) != 0 ) + if( timer_delete( pTimer->timer ) != 0 ) { AwsIotLogWarn( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); } - - AwsIotClock_Free( pTimerInfo ); } /*-----------------------------------------------------------*/ -bool AwsIotClock_TimerArm( AwsIotTimer_t * const pTimer, - uint64_t relativeTimeoutMs, - uint64_t periodMs ) +bool IotClock_TimerArm( IotTimer_t * const pTimer, + uint64_t relativeTimeoutMs, + uint64_t periodMs ) { - _timerInfo_t * pTimerInfo = ( _timerInfo_t * ) *pTimer; + bool status = true; struct itimerspec timerExpiration = { .it_value = { 0 }, @@ -322,30 +274,33 @@ bool AwsIotClock_TimerArm( AwsIotTimer_t * const pTimer, periodMs ); /* Calculate the initial timer expiration. */ - if( AwsIotClock_TimeoutToTimespec( relativeTimeoutMs, - &( timerExpiration.it_value ) ) == false ) + if( IotClock_TimeoutToTimespec( relativeTimeoutMs, + &( timerExpiration.it_value ) ) == false ) { AwsIotLogError( "Invalid relative timeout." ); - return false; + status = false; } - /* Calculate the timer expiration period. */ - if( periodMs > 0 ) + if( status == true ) { - timerExpiration.it_interval.tv_sec = ( time_t ) ( periodMs / _MILLISECONDS_PER_SECOND ); - timerExpiration.it_interval.tv_nsec = ( long ) ( ( periodMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); - } - - /* Arm the underlying POSIX timer. */ - if( timer_settime( pTimerInfo->timer, TIMER_ABSTIME, &timerExpiration, NULL ) != 0 ) - { - AwsIotLogError( "Failed to arm timer %p. errno=%d.", pTimer, errno ); - - return false; + /* Calculate the timer expiration period. */ + if( periodMs > 0 ) + { + timerExpiration.it_interval.tv_sec = ( time_t ) ( periodMs / _MILLISECONDS_PER_SECOND ); + timerExpiration.it_interval.tv_nsec = ( long ) ( ( periodMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); + } + + /* Arm the underlying POSIX timer. */ + if( timer_settime( pTimer->timer, TIMER_ABSTIME, &timerExpiration, NULL ) != 0 ) + { + AwsIotLogError( "Failed to arm timer %p. errno=%d.", pTimer, errno ); + + status = false; + } } - return true; + return status; } /*-----------------------------------------------------------*/ diff --git a/lib/source/platform/posix/aws_iot_threads_posix.c b/lib/source/platform/posix/iot_threads_posix.c similarity index 59% rename from lib/source/platform/posix/aws_iot_threads_posix.c rename to lib/source/platform/posix/iot_threads_posix.c index 246f327df8..8ff996cd72 100644 --- a/lib/source/platform/posix/aws_iot_threads_posix.c +++ b/lib/source/platform/posix/iot_threads_posix.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_threads_posix.c - * @brief Implementation of the functions in aws_iot_threads.h for POSIX systems. + * @file iot_threads_posix.c + * @brief Implementation of the functions in iot_threads.h for POSIX systems. */ /* Build using a config header, if provided. */ @@ -52,7 +52,7 @@ #endif /* Platform threads include. */ -#include "platform/aws_iot_threads.h" +#include "platform/iot_threads.h" /* Configure logs for the functions in this file. */ #ifdef AWS_IOT_LOG_LEVEL_PLATFORM @@ -69,32 +69,31 @@ #include "aws_iot_logging_setup.h" /* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. + * Provide default values for undefined memory allocation functions. */ -#ifndef AwsIotThreads_Malloc +#ifndef IotThreads_Malloc #include - /** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define AwsIotThreads_Malloc malloc +/** + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define IotThreads_Malloc malloc #endif -#ifndef AwsIotThreads_Free +#ifndef IotThreads_Free #include - /** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define AwsIotThreads_Free free +/** + * @brief Free memory. This function should have the same signature as + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define IotThreads_Free free #endif /*-----------------------------------------------------------*/ /** - * @brief Wraps an AwsIotThreadRoutine_t with a POSIX-compliant one. + * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. * * @param[in] pArgument The value passed as `arg` to `pthread_create`. * @@ -102,9 +101,9 @@ */ static void * _threadRoutineWrapper( void * pArgument ); -/* Platform-specific function implemented in aws_iot_clock_posix.c */ -extern bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, - struct timespec * const pOutput ); +/* Platform-specific function implemented in iot_clock_posix.c */ +extern bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, + struct timespec * const pOutput ); /*-----------------------------------------------------------*/ @@ -113,8 +112,8 @@ extern bool AwsIotClock_TimeoutToTimespec( uint64_t timeoutMs, */ typedef struct _threadInfo { - void * pArgument; /**< @brief First argument to `threadRoutine`. */ - AwsIotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ + void * pArgument; /**< @brief First argument to `threadRoutine`. */ + IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ } _threadInfo_t; /*-----------------------------------------------------------*/ @@ -123,97 +122,116 @@ static void * _threadRoutineWrapper( void * pArgument ) { _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; - /* Run the thread routine. */ - pThreadInfo->threadRoutine( pThreadInfo->pArgument ); + /* Read thread routine and argument, then free thread info. */ + IotThreadRoutine_t threadRoutine = pThreadInfo->threadRoutine; + void * pThreadRoutineArgument = pThreadInfo->pArgument; + + IotThreads_Free( pThreadInfo ); - AwsIotThreads_Free( pThreadInfo ); + /* Run the thread routine. */ + threadRoutine( pThreadRoutineArgument ); return NULL; } /*-----------------------------------------------------------*/ -bool AwsIot_CreateDetachedThread( AwsIotThreadRoutine_t threadRoutine, - void * pArgument ) +bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, + void * pArgument ) { + bool status = true; int posixErrno = 0; - _threadInfo_t * pThreadInfo = NULL; pthread_t newThread; pthread_attr_t threadAttributes; AwsIotLogDebug( "Creating new thread." ); /* Allocate memory for the new thread. */ - pThreadInfo = AwsIotThreads_Malloc( sizeof( _threadInfo_t ) ); + _threadInfo_t * pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); if( pThreadInfo == NULL ) { AwsIotLogError( "Failed to allocate memory for new thread." ); - return false; + status = false; } - /* Create a new thread attributes object. */ - posixErrno = pthread_attr_init( &threadAttributes ); - - if( posixErrno != 0 ) + /* Set up thread attributes object. */ + if( status == true ) { - AwsIotLogError( "Failed to initialize thread attributes. errno=%d.", - posixErrno ); - AwsIotThreads_Free( pThreadInfo ); - - return false; + /* Create a new thread attributes object. */ + posixErrno = pthread_attr_init( &threadAttributes ); + + if( posixErrno != 0 ) + { + AwsIotLogError( "Failed to initialize thread attributes. errno=%d.", + posixErrno ); + + status = false; + } + else + { + /* Set the new thread to detached. */ + posixErrno = pthread_attr_setdetachstate( &threadAttributes, + PTHREAD_CREATE_DETACHED ); + + if( posixErrno != 0 ) + { + AwsIotLogError( "Failed to set detached thread attribute. errno=%d.", + posixErrno ); + ( void ) pthread_attr_destroy( &threadAttributes ); + + status = false; + } + } } - /* Set the new thread to detached. */ - posixErrno = pthread_attr_setdetachstate( &threadAttributes, - PTHREAD_CREATE_DETACHED ); - - if( posixErrno != 0 ) - { - AwsIotLogError( "Failed to set detached thread attribute. errno=%d.", - posixErrno ); - ( void ) pthread_attr_destroy( &threadAttributes ); - AwsIotThreads_Free( pThreadInfo ); - - return false; - } - - /* Set the thread routine and argument. */ - pThreadInfo->threadRoutine = threadRoutine; - pThreadInfo->pArgument = pArgument; - - /* Create the underlying POSIX thread. */ - posixErrno = pthread_create( &newThread, - &threadAttributes, - _threadRoutineWrapper, - pThreadInfo ); - - if( posixErrno != 0 ) + if( status == true ) { - AwsIotLogError( "Failed to create new thread. errno=%d.", posixErrno ); - ( void ) pthread_attr_destroy( &threadAttributes ); - AwsIotThreads_Free( pThreadInfo ); - - return false; + /* Set the thread routine and argument. */ + pThreadInfo->threadRoutine = threadRoutine; + pThreadInfo->pArgument = pArgument; + + /* Create the underlying POSIX thread. */ + posixErrno = pthread_create( &newThread, + &threadAttributes, + _threadRoutineWrapper, + pThreadInfo ); + + if( posixErrno != 0 ) + { + AwsIotLogError( "Failed to create new thread. errno=%d.", posixErrno ); + + status = false; + } + + /* Destroy thread attributes object. */ + posixErrno = pthread_attr_destroy( &threadAttributes ); + + if( posixErrno != 0 ) + { + AwsIotLogWarn( "Failed to destroy thread attributes. errno=%d.", + posixErrno ); + } } - /* Destroy thread attributes object. */ - posixErrno = pthread_attr_destroy( &threadAttributes ); - - if( posixErrno != 0 ) + if( status == false ) { - AwsIotLogWarn( "Failed to destroy thread attributes. errno=%d.", - posixErrno ); + if( pThreadInfo != NULL ) + { + IotThreads_Free( pThreadInfo ); + } } - return true; + return status; } /*-----------------------------------------------------------*/ -bool AwsIotMutex_Create( AwsIotMutex_t * pNewMutex ) +bool IotMutex_Create( IotMutex_t * pNewMutex ) { + bool status = true; + AwsIotLogDebug( "Creating new mutex %p.", pNewMutex ); int mutexError = pthread_mutex_init( pNewMutex, NULL ); @@ -224,15 +242,15 @@ bool AwsIotMutex_Create( AwsIotMutex_t * pNewMutex ) pNewMutex, mutexError ); - return false; + status = false; } - return true; + return status; } /*-----------------------------------------------------------*/ -void AwsIotMutex_Destroy( AwsIotMutex_t * const pMutex ) +void IotMutex_Destroy( IotMutex_t * const pMutex ) { AwsIotLogDebug( "Destroying mutex %p.", pMutex ); @@ -248,7 +266,7 @@ void AwsIotMutex_Destroy( AwsIotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -void AwsIotMutex_Lock( AwsIotMutex_t * const pMutex ) +void IotMutex_Lock( IotMutex_t * const pMutex ) { AwsIotLogDebug( "Locking mutex %p.", pMutex ); @@ -264,8 +282,10 @@ void AwsIotMutex_Lock( AwsIotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -bool AwsIotMutex_TryLock( AwsIotMutex_t * const pMutex ) +bool IotMutex_TryLock( IotMutex_t * const pMutex ) { + bool status = true; + AwsIotLogDebug( "Attempting to lock mutex %p.", pMutex ); int mutexError = pthread_mutex_trylock( pMutex ); @@ -276,15 +296,15 @@ bool AwsIotMutex_TryLock( AwsIotMutex_t * const pMutex ) pMutex, mutexError ); - return false; + status = false; } - return true; + return status; } /*-----------------------------------------------------------*/ -void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ) +void IotMutex_Unlock( IotMutex_t * const pMutex ) { AwsIotLogDebug( "Unlocking mutex %p.", pMutex ); @@ -300,10 +320,12 @@ void AwsIotMutex_Unlock( AwsIotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -bool AwsIotSemaphore_Create( AwsIotSemaphore_t * const pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ) +bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ) { + bool status = true; + AwsIotLogDebug( "Creating new semaphore %p.", pNewSemaphore ); if( maxValue > ( uint32_t ) SEM_VALUE_MAX ) @@ -311,24 +333,26 @@ bool AwsIotSemaphore_Create( AwsIotSemaphore_t * const pNewSemaphore, AwsIotLogError( "%lu is larger than the maximum value a semaphore may" " have on this system.", maxValue ); - return false; + status = false; } - - if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) + else { - AwsIotLogError( "Failed to create new semaphore %p. errno=%d.", - pNewSemaphore, - errno ); - - return false; + if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) + { + AwsIotLogError( "Failed to create new semaphore %p. errno=%d.", + pNewSemaphore, + errno ); + + status = false; + } } - return true; + return status; } /*-----------------------------------------------------------*/ -uint32_t AwsIotSemaphore_GetCount( AwsIotSemaphore_t * const pSemaphore ) +uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ) { int count = 0; @@ -346,7 +370,7 @@ uint32_t AwsIotSemaphore_GetCount( AwsIotSemaphore_t * const pSemaphore ) /*-----------------------------------------------------------*/ -void AwsIotSemaphore_Destroy( AwsIotSemaphore_t * const pSemaphore ) +void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ) { AwsIotLogDebug( "Destroying semaphore %p.", pSemaphore ); @@ -360,7 +384,7 @@ void AwsIotSemaphore_Destroy( AwsIotSemaphore_t * const pSemaphore ) /*-----------------------------------------------------------*/ -void AwsIotSemaphore_Wait( AwsIotSemaphore_t * pSemaphore ) +void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) { AwsIotLogDebug( "Waiting on semaphore %p.", pSemaphore ); @@ -374,8 +398,10 @@ void AwsIotSemaphore_Wait( AwsIotSemaphore_t * pSemaphore ) /*-----------------------------------------------------------*/ -bool AwsIotSemaphore_TryWait( AwsIotSemaphore_t * const pSemaphore ) +bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ) { + bool status = true; + AwsIotLogDebug( "Attempting to wait on semaphore %p.", pSemaphore ); if( sem_trywait( pSemaphore ) != 0 ) @@ -384,44 +410,47 @@ bool AwsIotSemaphore_TryWait( AwsIotSemaphore_t * const pSemaphore ) pSemaphore, errno ); - return false; + status = false; } - return true; + return status; } /*-----------------------------------------------------------*/ -bool AwsIotSemaphore_TimedWait( AwsIotSemaphore_t * const pSemaphore, - uint64_t timeoutMs ) +bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, + uint64_t timeoutMs ) { + bool status = true; struct timespec timeout = { 0 }; AwsIotLogDebug( "Attempting to wait on semaphore %p with timeout %llu.", pSemaphore, timeoutMs ); - if( AwsIotClock_TimeoutToTimespec( timeoutMs, &timeout ) == false ) + if( IotClock_TimeoutToTimespec( timeoutMs, &timeout ) == false ) { AwsIotLogError( "Invalid timeout." ); - return false; + status = false; } - - if( sem_timedwait( pSemaphore, &timeout ) != 0 ) + else { - AwsIotLogDebug( "Semaphore %p is not available. errno=%d.", - pSemaphore, - errno ); - - return false; + if( sem_timedwait( pSemaphore, &timeout ) != 0 ) + { + AwsIotLogDebug( "Semaphore %p is not available. errno=%d.", + pSemaphore, + errno ); + + status = false; + } } - return true; + return status; } /*-----------------------------------------------------------*/ -void AwsIotSemaphore_Post( AwsIotSemaphore_t * const pSemaphore ) +void IotSemaphore_Post( IotSemaphore_t * const pSemaphore ) { AwsIotLogDebug( "Posting to semaphore %p.", pSemaphore ); diff --git a/lib/source/platform/posix/network/aws_iot_network_openssl.c b/lib/source/platform/posix/network/aws_iot_network_openssl.c index 6136b44c53..02f052f40f 100644 --- a/lib/source/platform/posix/network/aws_iot_network_openssl.c +++ b/lib/source/platform/posix/network/aws_iot_network_openssl.c @@ -56,7 +56,7 @@ #include "platform/aws_iot_network.h" /* Platform threads include. */ -#include "platform/aws_iot_threads.h" +#include "platform/iot_threads.h" /* Configure logs for the functions in this file. */ #ifdef AWS_IOT_LOG_LEVEL_NETWORK @@ -116,7 +116,7 @@ typedef struct _connectionInfo { int tcpSocket; /**< @brief Socket associated with connection. */ SSL * pSSLConnectionContext; /**< @brief SSL context for connection. */ - AwsIotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ + IotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ _threadStatus_t receiveThreadStatus; /**< @brief Status of the receive thread for this connection. */ pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ @@ -170,7 +170,7 @@ static void * _networkReceiveThread( void * pArgument ) { /* Prevent this thread from being cancelled while running. But if the * connection mutex is locked, wait until it is available. */ - if( AwsIotMutex_TryLock( &( pConnectionInfo->mutex ) ) == false ) + if( IotMutex_TryLock( &( pConnectionInfo->mutex ) ) == false ) { continue; } @@ -193,7 +193,7 @@ static void * _networkReceiveThread( void * pArgument ) errno ); /* Unlock the connection mutex before polling again. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); continue; } @@ -220,7 +220,7 @@ static void * _networkReceiveThread( void * pArgument ) pConnectionInfo->tcpSocket ); /* Unlock the connection mutex before polling again. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); continue; } @@ -282,7 +282,7 @@ static void * _networkReceiveThread( void * pArgument ) } /* Unlock the connection mutex before polling again. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); } AwsIotLogDebug( "Network receive thread for socket %d terminating.", @@ -294,7 +294,7 @@ static void * _networkReceiveThread( void * pArgument ) pConnectionInfo->pMqttConnection = NULL; /* Unlock the connection mutex before exiting. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); return NULL; } @@ -767,7 +767,7 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * } /* Create the network connection mutex. */ - if( AwsIotMutex_Create( &( pConnectionInfo->mutex ) ) == false ) + if( IotMutex_Create( &( pConnectionInfo->mutex ) ) == false ) { AwsIotLogError( "Failed to create connection mutex." ); AwsIotNetwork_Free( pConnectionInfo ); @@ -836,7 +836,7 @@ void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection } /* Lock the connection mutex to block the receive thread. */ - AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + IotMutex_Lock( &( pConnectionInfo->mutex ) ); /* Check if a network receive thread was created. */ if( pConnectionInfo->receiveThreadStatus != _NONE ) @@ -890,7 +890,7 @@ void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection } /* Unlock the connection mutex. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); } /*-----------------------------------------------------------*/ @@ -907,7 +907,7 @@ void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnectio } /* Destroy the connection data mutex. */ - AwsIotMutex_Destroy( &( pConnectionInfo->mutex ) ); + IotMutex_Destroy( &( pConnectionInfo->mutex ) ); /* Free memory in use by the connection. */ AwsIotNetwork_Free( pConnectionInfo ); @@ -924,7 +924,7 @@ AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnecti _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; /* Lock the connection mutex before changing the callback and its parameter. */ - AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + IotMutex_Lock( &( pConnectionInfo->mutex ) ); /* Clean up any previously terminated receive thread. */ if( pConnectionInfo->receiveThreadStatus == _TERMINATED ) @@ -971,7 +971,7 @@ AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnecti } /* Unlock the connection mutex. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); return status; } @@ -1004,7 +1004,7 @@ size_t AwsIotNetwork_Send( void * networkConnection, /* Lock the connection mutex to prevent the connection from being closed * while sending. */ - AwsIotMutex_Lock( &( pConnectionInfo->mutex ) ); + IotMutex_Lock( &( pConnectionInfo->mutex ) ); /* Set the file descriptor for poll. */ fileDescriptors.fd = pConnectionInfo->tcpSocket; @@ -1030,7 +1030,7 @@ size_t AwsIotNetwork_Send( void * networkConnection, } /* Unlock the connection mutex. */ - AwsIotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pConnectionInfo->mutex ) ); /* Check for errors. */ if( bytesSent <= 0 ) diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 781080e69d..ac4439aefc 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -35,6 +35,9 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /* JSON utilities include. */ #include "aws_iot_json_utils.h" @@ -333,7 +336,7 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec /* Lock the subscription list mutex to check for an existing subscription * object. */ - AwsIotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ @@ -407,7 +410,7 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec } } - AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); return status; } @@ -582,7 +585,7 @@ static void _updatedCallbackWrapper( void * pArgument, AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) { /* Create the Shadow pending operation list mutex. */ - if( AwsIotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ) ) == false ) + if( IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ) ) == false ) { AwsIotLogError( "Failed to create Shadow pending operation list." ); @@ -590,10 +593,10 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) } /* Create the Shadow subscription list mutex. */ - if( AwsIotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ) ) == false ) + if( IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ) ) == false ) { AwsIotLogError( "Failed to create Shadow subscription list." ); - AwsIotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); + IotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); return AWS_IOT_SHADOW_INIT_FAILED; } @@ -618,22 +621,22 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) void AwsIotShadow_Cleanup( void ) { /* Remove and free all items in the Shadow pending operation list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), AwsIotShadowInternal_DestroyOperation, offsetof( _shadowOperation_t, link ) ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Remove and free all items in the Shadow subscription list. */ - AwsIotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); IotListDouble_RemoveAll( &( _AwsIotShadowSubscriptions ), AwsIotShadowInternal_DestroySubscription, offsetof( _shadowSubscription_t, link ) ); - AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Destroy Shadow library mutexes. */ - AwsIotMutex_Destroy( &( _AwsIotShadowPendingOperationsMutex ) ); - AwsIotMutex_Destroy( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Destroy( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Destroy( &( _AwsIotShadowSubscriptionsMutex ) ); /* Restore the default MQTT timeout. */ _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; @@ -1038,8 +1041,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, } /* Wait for a response to the Shadow operation. */ - if( AwsIotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), - timeoutMs ) == true ) + if( IotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + timeoutMs ) == true ) { status = pOperation->status; } @@ -1049,17 +1052,17 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, } /* Remove the completed operation from the pending operation list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); IotListDouble_Remove( &( pOperation->link ) ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ - AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); AwsIotShadowInternal_DecrementReferences( pOperation, pOperation->pSubscription->pTopicBuffer, NULL ); - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the output parameters for Shadow GET. */ if( ( pOperation->type == _SHADOW_GET ) && diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 99ad8341ca..952147edf4 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -35,6 +35,9 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /* JSON utils include. */ #include "aws_iot_json_utils.h" @@ -144,7 +147,7 @@ IotListDouble_t _AwsIotShadowPendingOperations = { 0 }; /** * @brief Protects #_AwsIotShadowPendingOperations from concurrent access. */ -AwsIotMutex_t _AwsIotShadowPendingOperationsMutex; +IotMutex_t _AwsIotShadowPendingOperationsMutex; /*-----------------------------------------------------------*/ @@ -236,7 +239,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, } /* Lock the pending operations list for exclusive access. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Search for a matching pending operation. */ pOperation = IotLink_Container( _shadowOperation_t, @@ -251,7 +254,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, { /* Operation is not pending. It may have already been processed. Return * without doing anything */ - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); AwsIotLogWarn( "Shadow %s callback received an unknown operation.", _pAwsIotShadowOperationNames[ type ] ); @@ -264,7 +267,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { IotListDouble_Remove( &( pOperation->link ) ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } } @@ -332,7 +335,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, * this function's completion. */ if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } } @@ -445,7 +448,7 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** * wait on. */ if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { - if( AwsIotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) { AwsIotLogError( "Failed to create semaphore for waitable Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); @@ -492,7 +495,7 @@ void AwsIotShadowInternal_DestroyOperation( void * pData ) if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { /* Destroy the wait semaphore */ - AwsIotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); } /* If this is a Shadow update, free any allocated client token. */ @@ -639,7 +642,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } /* Lock the subscription list mutex for exclusive access. */ - AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ @@ -692,7 +695,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } /* Unlock the Shadow subscription list mutex. */ - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Check that all memory allocation and subscriptions succeeded. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -737,10 +740,10 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } /* Add Shadow operation to the pending operations list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), &( pOperation->link ) ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Publish to the Shadow topic name. */ publishStatus = AwsIotMqtt_TimedPublish( pOperation->mqttConnection, @@ -771,17 +774,17 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ * count. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) { - AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); AwsIotShadowInternal_DecrementReferences( pOperation, pTopicBuffer, NULL ); - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); } /* Remove Shadow operation from the pending operations list. */ - AwsIotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); IotListDouble_Remove( &( pOperation->link ) ); - AwsIotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } else { @@ -817,18 +820,18 @@ void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ) /* If the operation is waiting, post to its wait semaphore and return. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { - AwsIotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); return; } /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ - AwsIotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); AwsIotShadowInternal_DecrementReferences( pOperation, pSubscription->pTopicBuffer, &pRemovedSubscription ); - AwsIotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the subscription pointer used for the user callback based on whether * a subscription was removed from the list. */ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index eae63a1d49..e021341684 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -36,6 +36,9 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /*-----------------------------------------------------------*/ /** @@ -90,7 +93,7 @@ IotListDouble_t _AwsIotShadowSubscriptions = { 0 }; /** * @brief Protects #_AwsIotShadowSubscriptions from concurrent access. */ -AwsIotMutex_t _AwsIotShadowSubscriptionsMutex; +IotMutex_t _AwsIotShadowSubscriptionsMutex; /*-----------------------------------------------------------*/ @@ -555,7 +558,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec thingNameLength, pThingName ); - AwsIotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Search the list for an existing subscription for Thing Name. */ pSubscription = IotLink_Container( _shadowSubscription_t, @@ -650,7 +653,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec pThingName ); } - AwsIotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Check the results of the MQTT unsubscribes. */ if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 65accc64ec..e311b51e62 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -77,10 +77,8 @@ * be affected by AWS_IOT_STATIC_MEMORY_ONLY. */ #define AwsIotNetwork_Malloc unity_malloc_mt #define AwsIotNetwork_Free unity_free_mt -#define AwsIotClock_Malloc unity_malloc_mt -#define AwsIotClock_Free unity_free_mt -#define AwsIotThreads_Malloc unity_malloc_mt -#define AwsIotThreads_Free unity_free_mt +#define IotThreads_Malloc unity_malloc_mt +#define IotThreads_Free unity_free_mt #define AwsIotLogging_Malloc unity_malloc_mt #define AwsIotLogging_Free unity_free_mt /* #define AwsIotLogging_StaticBufferSize */ @@ -115,11 +113,8 @@ #define AwsIotShadow_FreeSubscription unity_free_mt #endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 0 */ -#include -#include - -#define AWS_IOT_MUTEX_TYPE pthread_mutex_t -#define AWS_IOT_SEMAPHORE_TYPE sem_t -#define AWS_IOT_TIMER_TYPE void* +/* The build system will choose the appropriate system types file for the platform + * layer based on the host operating system. */ +#include IOT_SYSTEM_TYPES_FILE #endif /* ifndef _IOT_TESTS_CONFIG_H_ */ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c index 703da1f04c..fe4821e742 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c @@ -51,7 +51,8 @@ #endif /* Platform layer include. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -70,7 +71,7 @@ #else #define _AWS_IOT_MQTT_SERVER false - /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif @@ -118,13 +119,13 @@ /** * @brief Number of test topic names. */ -#define _TEST_TOPIC_NAME_COUNT ( 8 ) +#define _TEST_TOPIC_NAME_COUNT ( 8 ) /** * @brief The maximum number of PUBLISH messages that will be received by a * single test. */ -#define _MAX_RECEIVED_PUBLISH ( AWS_IOT_TEST_MQTT_THREADS * AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) +#define _MAX_RECEIVED_PUBLISH ( AWS_IOT_TEST_MQTT_THREADS * AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) /** * @brief The maximum length of an MQTT client identifier. @@ -213,7 +214,7 @@ static const uint16_t _topicNameLength = ( uint16_t ) sizeof( AWS_IOT_TEST_MQTT_ * * Used in conjunction with #_publishReceived. */ -static AwsIotSemaphore_t receivedPublishCounter; +static IotSemaphore_t receivedPublishCounter; /** * @brief Buffer holding the client identifier used for the tests. @@ -295,7 +296,7 @@ static void _publishReceived( void * pArgument, ( pPublish->message.info.topicNameLength == _topicNameLength ) && ( pPublish->message.info.QoS == 1 ) ) { - AwsIotSemaphore_Post( &receivedPublishCounter ); + IotSemaphore_Post( &receivedPublishCounter ); } else { @@ -313,14 +314,14 @@ static void _publishReceived( void * pArgument, static void _blockingCallback( void * pArgument, AwsIotMqttCallbackParam_t * const param ) { - AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; const unsigned blockTime = 5 * AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; ( void ) param; AwsIotLogInfo( "Callback blocking for %u seconds.", blockTime ); sleep( blockTime ); - AwsIotSemaphore_Post( pWaitSem ); + IotSemaphore_Post( pWaitSem ); } /*-----------------------------------------------------------*/ @@ -435,9 +436,9 @@ TEST_SETUP( MQTT_Stress ) AwsIotLog( AWS_IOT_LOG_INFO, &logHideAll, " " ); /* Create the publish counter semaphore. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &receivedPublishCounter, - 0, - _MAX_RECEIVED_PUBLISH ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &receivedPublishCounter, + 0, + _MAX_RECEIVED_PUBLISH ) ); /* Set the serializer overrides. */ _AwsIotTestNetworkInterface.serialize.pingreq = _serializePingreq; @@ -461,7 +462,7 @@ TEST_SETUP( MQTT_Stress ) ( void ) snprintf( _pClientIdentifier, _CLIENT_IDENTIFIER_MAX_LENGTH, "aws%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( _pClientIdentifier, AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER, @@ -508,7 +509,7 @@ TEST_SETUP( MQTT_Stress ) TEST_TEAR_DOWN( MQTT_Stress ) { /* Destroy the PUBLISH counter semaphore. */ - AwsIotSemaphore_Destroy( &receivedPublishCounter ); + IotSemaphore_Destroy( &receivedPublishCounter ); /* Disconnect the MQTT connection. Unsubscribe is not called; the subscriptions * should be cleaned up by Disconnect. */ @@ -568,7 +569,7 @@ TEST( MQTT_Stress, BlockingCallback ) { AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; - AwsIotSemaphore_t waitSem; + IotSemaphore_t waitSem; callbackInfo.function = _blockingCallback; callbackInfo.param1 = &waitSem; @@ -582,7 +583,7 @@ TEST( MQTT_Stress, BlockingCallback ) publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); if( TEST_PROTECT() ) { @@ -596,12 +597,12 @@ TEST( MQTT_Stress, BlockingCallback ) /* Wait for the callback to return, then check that the connection is * still usable. */ - AwsIotSemaphore_Wait( &waitSem ); + IotSemaphore_Wait( &waitSem ); AwsIotLogInfo( "BlockingCallback test checking MQTT connection." ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); } - AwsIotSemaphore_Destroy( &waitSem ); + IotSemaphore_Destroy( &waitSem ); /* Check that the PINGREQ override was used. */ TEST_ASSERT_EQUAL( true, _pingreqOverrideCalled ); diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 03bc7fc3be..b2dc2b89b9 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -36,7 +36,8 @@ #include "private/aws_iot_mqtt_internal.h" /* Platform layer includes. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -78,7 +79,7 @@ extern int snprintf( char *, #else #define _AWS_IOT_MQTT_SERVER false - /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif @@ -106,7 +107,7 @@ extern int snprintf( char *, typedef struct _operationCompleteParams { AwsIotMqttOperationType_t expectedOperation; /**< @brief Expected completed operation. */ - AwsIotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ + IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ AwsIotMqttReference_t reference; /**< @brief Reference to expected completed operation. */ } _operationCompleteParams_t; @@ -287,7 +288,7 @@ static AwsIotMqttError_t _serializeDisconnect( uint8_t ** const pDisconnectPacke static void _publishReceived( void * pArgument, AwsIotMqttCallbackParam_t * const pPublish ) { - AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; /* If the received messages matches what was sent, unblock the waiting thread. */ if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && @@ -295,7 +296,7 @@ static void _publishReceived( void * pArgument, _pSamplePayload, _samplePayloadLength ) == 0 ) ) { - AwsIotSemaphore_Post( pWaitSem ); + IotSemaphore_Post( pWaitSem ); } } @@ -316,7 +317,7 @@ static void _operationComplete( void * pArgument, ( pParams->reference == pOperation->operation.reference ) && ( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) ) { - AwsIotSemaphore_Post( &( pParams->waitSem ) ); + IotSemaphore_Post( &( pParams->waitSem ) ); } } @@ -332,7 +333,7 @@ static void _subscribePublishWait( int QoS ) AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotSemaphore_t waitSem; + IotSemaphore_t waitSem; /* Set the serializer overrides. */ networkInterface.freePacket = _freePacket; @@ -344,7 +345,7 @@ static void _subscribePublishWait( int QoS ) networkInterface.serialize.disconnect = _serializeDisconnect; /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); if( TEST_PROTECT() ) { @@ -393,8 +394,8 @@ static void _subscribePublishWait( int QoS ) AWS_IOT_TEST_MQTT_TIMEOUT_MS ); /* Wait for the message to be received. */ - if( AwsIotSemaphore_TimedWait( &waitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &waitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); } @@ -412,7 +413,7 @@ static void _subscribePublishWait( int QoS ) AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); } - AwsIotSemaphore_Destroy( &waitSem ); + IotSemaphore_Destroy( &waitSem ); /* Check that the serializer overrides were called. */ if( TEST_PROTECT() ) @@ -472,7 +473,7 @@ TEST_SETUP( MQTT_System ) ( void ) snprintf( _pClientIdentifier, _CLIENT_IDENTIFIER_MAX_LENGTH, "aws%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( _pClientIdentifier, AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER, @@ -542,7 +543,7 @@ TEST( MQTT_System, SubscribePublishAsync ) AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; _operationCompleteParams_t callbackParam = { 0 }; - AwsIotSemaphore_t publishWaitSem; + IotSemaphore_t publishWaitSem; /* Initialize members of the operation callback info. */ callbackInfo.function = _operationComplete; @@ -567,12 +568,12 @@ TEST( MQTT_System, SubscribePublishAsync ) publishInfo.payloadLength = _samplePayloadLength; /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); if( TEST_PROTECT() ) { /* Create the wait semaphore for subscriptions. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &publishWaitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &publishWaitSem, 0, 1 ) ); if( TEST_PROTECT() ) { @@ -595,8 +596,8 @@ TEST( MQTT_System, SubscribePublishAsync ) &callbackInfo, &( callbackParam.reference ) ); - if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for SUBSCRIBE to complete." ); } @@ -609,15 +610,15 @@ TEST( MQTT_System, SubscribePublishAsync ) &callbackInfo, &( callbackParam.reference ) ); - if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for PUBLISH to complete." ); } /* Wait for the message to be received. */ - if( AwsIotSemaphore_TimedWait( &publishWaitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &publishWaitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); } @@ -631,8 +632,8 @@ TEST( MQTT_System, SubscribePublishAsync ) &callbackInfo, &( callbackParam.reference ) ); - if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for UNSUBSCRIBE to complete." ); } @@ -641,10 +642,10 @@ TEST( MQTT_System, SubscribePublishAsync ) AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); } - AwsIotSemaphore_Destroy( &publishWaitSem ); + IotSemaphore_Destroy( &publishWaitSem ); } - AwsIotSemaphore_Destroy( &( callbackParam.waitSem ) ); + IotSemaphore_Destroy( &( callbackParam.waitSem ) ); } /*-----------------------------------------------------------*/ @@ -663,17 +664,17 @@ TEST( MQTT_System, LastWillAndTestament ) AwsIotMqttSubscription_t willSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; void * pLwtListenerConnection = NULL; - AwsIotSemaphore_t waitSem; + IotSemaphore_t waitSem; /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); /* Generate a client identifier for LWT listener. */ #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER ( void ) snprintf( pLwtListenerClientIdentifier, _CLIENT_IDENTIFIER_MAX_LENGTH, "awslwt%llu", - ( long long unsigned int ) AwsIotClock_GetTimeMs() ); + ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( pLwtListenerClientIdentifier, AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER "LWT", @@ -742,8 +743,8 @@ TEST( MQTT_System, LastWillAndTestament ) AwsIotTest_NetworkDestroy( NULL ); /* Check that the LWT was received. */ - if( AwsIotSemaphore_TimedWait( &waitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &waitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); } @@ -761,7 +762,7 @@ TEST( MQTT_System, LastWillAndTestament ) } } - AwsIotSemaphore_Destroy( &waitSem ); + IotSemaphore_Destroy( &waitSem ); } /*-----------------------------------------------------------*/ @@ -775,10 +776,10 @@ TEST( MQTT_System, RestorePreviousSession ) AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotSemaphore_t waitSem; + IotSemaphore_t waitSem; /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); /* Set the members of the connection info for a persistent session. */ connectInfo.cleanSession = false; @@ -839,8 +840,8 @@ TEST( MQTT_System, RestorePreviousSession ) TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); /* Wait for the message to be received. */ - if( AwsIotSemaphore_TimedWait( &waitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( &waitSem, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for message." ); } @@ -850,7 +851,7 @@ TEST( MQTT_System, RestorePreviousSession ) AwsIotTest_NetworkCleanup(); } - AwsIotSemaphore_Destroy( &waitSem ); + IotSemaphore_Destroy( &waitSem ); if( TEST_PROTECT() ) { diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index 7712014f8f..cab7656a26 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -43,7 +43,8 @@ #include "private/aws_iot_mqtt_internal.h" /* Platform layer includes. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -61,7 +62,7 @@ #else #define _AWS_IOT_MQTT_SERVER false - /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif @@ -538,8 +539,8 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) /* Create a new MQTT connection. */ pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); + &networkInterface, + 0 ); TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); /* Set malloc to eventually fail. */ @@ -652,8 +653,8 @@ TEST( MQTT_Unit_API, PublishQoS1 ) /* Set the members of the network interface and create a new MQTT connection. */ networkInterface.send = _sendSuccess; pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); + &networkInterface, + 0 ); TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); /* Set the publish info. */ @@ -733,8 +734,8 @@ TEST( MQTT_Unit_API, PublishDuplicates ) networkInterface.send = _dupChecker; networkInterface.serialize.publishSetDup = _publishSetDup; pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); + &networkInterface, + 0 ); TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); /* Set the publish info. */ @@ -746,7 +747,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) publishInfo.retryMs = _DUP_CHECK_RETRY_MS; publishInfo.retryLimit = _DUP_CHECK_RETRY_LIMIT; - startTime = AwsIotClock_GetTimeMs(); + startTime = IotClock_GetTimeMs(); if( TEST_PROTECT() ) { @@ -767,7 +768,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); /* Check that at least the minimum wait time elapsed. */ - TEST_ASSERT_TRUE( startTime + _DUP_CHECK_MINIMUM_WAIT <= AwsIotClock_GetTimeMs() ); + TEST_ASSERT_TRUE( startTime + _DUP_CHECK_MINIMUM_WAIT <= IotClock_GetTimeMs() ); } /* Clean up MQTT connection. */ @@ -852,7 +853,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) @@ -890,7 +891,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); - AwsIotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); + IotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -913,7 +914,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) @@ -951,7 +952,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); - AwsIotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); + IotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c index bf5f088d44..6aa4588378 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -33,7 +33,7 @@ #include /* Platform layer includes. */ -#include "platform/aws_iot_threads.h" +#include "platform/iot_threads.h" /* MQTT internal include. */ #include "private/aws_iot_mqtt_internal.h" @@ -52,7 +52,7 @@ #else #define _AWS_IOT_MQTT_SERVER false - /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif @@ -178,7 +178,7 @@ static _mqttConnection_t * _pMqttConnection = NULL; /** * @brief Synchronizes malloc and free, which may be called from different threads. */ -static AwsIotSemaphore_t _mallocSemaphore; +static IotSemaphore_t _mallocSemaphore; /** * @brief Tracks whether a deserializer override was called for a test. @@ -208,8 +208,8 @@ static void * _mallocWrapper( size_t size ) if( pBuffer != NULL ) #endif { - if( ( AwsIotSemaphore_TryWait( &_mallocSemaphore ) == true ) && - ( AwsIotSemaphore_GetCount( &_mallocSemaphore ) > 0 ) ) + if( ( IotSemaphore_TryWait( &_mallocSemaphore ) == true ) && + ( IotSemaphore_GetCount( &_mallocSemaphore ) > 0 ) ) { /* If the malloc semaphore value isn't what's expected, return NULL. */ AwsIotTest_Free( pBuffer ); @@ -248,7 +248,7 @@ static void _freeWrapper( void * ptr ) AwsIotTest_Free( ptr ); #endif - AwsIotSemaphore_Post( &_mallocSemaphore ); + IotSemaphore_Post( &_mallocSemaphore ); } /*-----------------------------------------------------------*/ @@ -410,13 +410,13 @@ static bool _processPublish( const uint8_t * const pPublish, int32_t expectedBytesProcessed, uint32_t expectedInvokeCount ) { - AwsIotSemaphore_t invokeCount; + IotSemaphore_t invokeCount; uint32_t finalInvokeCount = 0, i = 0; int32_t bytesProcessed = 0; bool waitResult = true; /* Create a semaphore that counts how many times the publish callback is invoked. */ - if( AwsIotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) + if( IotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) { return false; } @@ -440,8 +440,8 @@ static bool _processPublish( const uint8_t * const pPublish, /* Check how many times the publish callback is invoked. */ for( i = 0; i < expectedInvokeCount; i++ ) { - waitResult = AwsIotSemaphore_TimedWait( &invokeCount, - _PUBLISH_CALLBACK_TIMEOUT ); + waitResult = IotSemaphore_TimedWait( &invokeCount, + _PUBLISH_CALLBACK_TIMEOUT ); if( waitResult == false ) { @@ -451,8 +451,8 @@ static bool _processPublish( const uint8_t * const pPublish, /* Ensure that the invoke count semaphore has a value of 0, then destroy the * semaphore. */ - finalInvokeCount = AwsIotSemaphore_GetCount( &invokeCount ); - AwsIotSemaphore_Destroy( &invokeCount ); + finalInvokeCount = IotSemaphore_GetCount( &invokeCount ); + IotSemaphore_Destroy( &invokeCount ); /* Check results against expected values. */ return ( finalInvokeCount == 0 ) && @@ -468,7 +468,7 @@ static bool _processPublish( const uint8_t * const pPublish, static void _publishCallback( void * param1, AwsIotMqttCallbackParam_t * const pPublish ) { - AwsIotSemaphore_t * pInvokeCount = ( AwsIotSemaphore_t * ) param1; + IotSemaphore_t * pInvokeCount = ( IotSemaphore_t * ) param1; /* QoS 2 is valid for these tests, but currently unsupported by the MQTT library. * Change the QoS to 0 so that QoS validation passes. */ @@ -480,7 +480,7 @@ static void _publishCallback( void * param1, ( pPublish->message.info.topicNameLength == _TEST_TOPIC_LENGTH ) && ( strncmp( _TEST_TOPIC_NAME, pPublish->message.info.pTopicName, _TEST_TOPIC_LENGTH ) == 0 ) ) { - AwsIotSemaphore_Post( pInvokeCount ); + IotSemaphore_Post( pInvokeCount ); } } @@ -529,9 +529,9 @@ TEST_SETUP( MQTT_Unit_Receive ) networkInterface.getPacketType = _getPacketType; /* Create the memory allocation semaphore. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &_mallocSemaphore, - 1, - 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &_mallocSemaphore, + 1, + 1 ) ); /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); @@ -568,7 +568,7 @@ TEST_TEAR_DOWN( MQTT_Unit_Receive ) /* Clean up resources taken in test setup. */ AwsIotTestMqtt_destroyMqttConnection( _pMqttConnection ); AwsIotMqtt_Cleanup(); - AwsIotSemaphore_Destroy( &_mallocSemaphore ); + IotSemaphore_Destroy( &_mallocSemaphore ); _pMqttConnection = NULL; /* Check that the tests used a deserializer override. */ @@ -672,7 +672,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) /* Attempt to decode a 4-byte representation of 0. According to the spec, * this representation is not valid. */ - { + { uint8_t pRemainingLength[ 4 ] = { 0x80, 0x80, 0x80, 0x00 }; TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, @@ -798,8 +798,8 @@ TEST( MQTT_Unit_Receive, DataStream ) TEST_ASSERT_EQUAL( 2, bytesProcessed ); /* Wait for the buffer to be freed. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); /* Check that the entire buffer was processed. */ TEST_ASSERT_EQUAL( _DATA_STREAM_SIZE, processOffset + ( size_t ) bytesProcessed ); @@ -818,9 +818,9 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( connect.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.notify.waitSemaphore ), + 0, + 10 ) ); /* Even though no CONNECT is in the receive queue, 4 bytes should still be * processed (should not crash). */ @@ -873,7 +873,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) AWS_IOT_MQTT_SERVER_REFUSED ) ); } - AwsIotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -888,9 +888,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( connect.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.notify.waitSemaphore ), + 0, + 10 ) ); /* An incomplete CONNACK should not be processed, and no status should be set. */ { @@ -963,7 +963,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) AWS_IOT_MQTT_BAD_RESPONSE ) ); } - AwsIotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -985,8 +985,8 @@ TEST( MQTT_Unit_Receive, PublishValid ) publishSize, ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Process a valid QoS 1 PUBLISH. Prevent an attempt to send PUBACK by @@ -998,8 +998,8 @@ TEST( MQTT_Unit_Receive, PublishValid ) publishSize, ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Process a valid QoS 2 PUBLISH. */ @@ -1010,8 +1010,8 @@ TEST( MQTT_Unit_Receive, PublishValid ) publishSize, ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Process a valid PUBLISH with DUP flag set. */ @@ -1022,8 +1022,8 @@ TEST( MQTT_Unit_Receive, PublishValid ) publishSize, ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Remove the subscription. Even though there is no matching subscription, @@ -1038,8 +1038,8 @@ TEST( MQTT_Unit_Receive, PublishValid ) publishSize, ( int32_t ) publishSize, 0 ) ); - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); } } @@ -1060,7 +1060,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) 0, 0 ) ); _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } /* The PUBLISH cannot have a QoS of 3. */ @@ -1072,7 +1072,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) -1, 0 ) ); _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } /* Attempt to process a PUBLISH with an invalid "Remaining length". */ @@ -1087,7 +1087,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) -1, 0 ) ); _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } /* Attempt to process a PUBLISH larger than the size of the data stream. */ @@ -1099,7 +1099,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) 0, 0 ) ); _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } /* Attempt to process a PUBLISH with a "Remaining length" smaller than the @@ -1113,7 +1113,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) -1, 0 ) ); _freeWrapper( pPublish0 ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); /* QoS 1. */ _DECLARE_PACKET( _pPublishTemplate, pPublish1, publish1Size ); @@ -1124,7 +1124,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) -1, 0 ) ); _freeWrapper( pPublish1 ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } /* Attempt to process a PUBLISH with a "Remaining length" smaller than the @@ -1137,7 +1137,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) -1, 0 ) ); _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } /* Attempt to process a PUBLISH with packet identifier 0. */ @@ -1150,7 +1150,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) -1, 0 ) ); _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, AwsIotSemaphore_GetCount( &_mallocSemaphore ) ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); } } @@ -1186,16 +1186,16 @@ TEST( MQTT_Unit_Receive, PublishStream ) sizeof( _pPublishTemplate ) + 1, sizeof( _pPublishTemplate ), 1 ) ); - TEST_ASSERT_EQUAL_INT( false, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( false, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); /* Process the complete stream of PUBLISH messages. */ TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ), _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ), _PUBLISH_STREAM_COUNT ) ); - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); } /*-----------------------------------------------------------*/ @@ -1233,8 +1233,8 @@ TEST( MQTT_Unit_Receive, PublishInvalidStream ) _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1, -1, 0 ) ); - TEST_ASSERT_EQUAL_INT( false, AwsIotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); + TEST_ASSERT_EQUAL_INT( false, IotSemaphore_TimedWait( &_mallocSemaphore, + _PUBLISH_CALLBACK_TIMEOUT ) ); _freeWrapper( pPublishStream ); } @@ -1250,9 +1250,9 @@ TEST( MQTT_Unit_Receive, PubackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( publish.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.notify.waitSemaphore ), + 0, + 10 ) ); /* Even though no PUBLISH is in the receive queue, 4 bytes should still be * processed (should not crash). */ @@ -1276,7 +1276,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) AWS_IOT_MQTT_SUCCESS ) ); } - AwsIotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1291,9 +1291,9 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( publish.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.notify.waitSemaphore ), + 0, + 10 ) ); /* An incomplete PUBACK should not be processed, and no status should be set. */ { @@ -1348,7 +1348,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) IotQueue_Remove( &( publish.link ) ); } - AwsIotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1365,9 +1365,9 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( subscribe.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.notify.waitSemaphore ), + 0, + 10 ) ); /* Add 2 additional subscriptions to the MQTT connection. */ pSubscriptions[ 0 ].QoS = 1; @@ -1453,7 +1453,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) NULL ) ); } - AwsIotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1468,9 +1468,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( subscribe.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.notify.waitSemaphore ), + 0, + 10 ) ); /* Attempting to process a packet smaller than 5 bytes should result in no * bytes processed. 5 bytes is the minimum size of a SUBACK. */ @@ -1544,7 +1544,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) AWS_IOT_MQTT_BAD_RESPONSE ) ); } - AwsIotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1559,9 +1559,9 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), + 0, + 10 ) ); /* Even though no UNSUBSCRIBE is in the receive queue, 4 bytes should still be * processed (should not crash). */ @@ -1585,7 +1585,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) AWS_IOT_MQTT_SUCCESS ) ); } - AwsIotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1600,9 +1600,9 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), + 0, + 10 ) ); /* An incomplete UNSUBACK should not be processed, and no status should be set. */ { @@ -1657,7 +1657,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) IotQueue_Remove( &( unsubscribe.link ) ); } - AwsIotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1672,9 +1672,9 @@ TEST( MQTT_Unit_Receive, Pingresp ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( pingreq.notify.waitSemaphore ), - 0, - 10 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pingreq.notify.waitSemaphore ), + 0, + 10 ) ); /* Even though no PINGREQ is in the receive queue, 2 bytes should still be * processed (should not crash). */ @@ -1732,7 +1732,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) AWS_IOT_MQTT_BAD_RESPONSE ) ); } - AwsIotSemaphore_Destroy( &( pingreq.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( pingreq.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c index 874e631416..318c77ef40 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c @@ -32,6 +32,9 @@ /* Standard includes. */ #include +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /* MQTT internal include. */ #include "private/aws_iot_mqtt_internal.h" @@ -284,7 +287,7 @@ TEST_GROUP( MQTT_Unit_Subscription ); */ TEST_SETUP( MQTT_Unit_Subscription ) { - TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &( _connection.subscriptionMutex ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( _connection.subscriptionMutex ) ) ); IotListDouble_Create( &( _connection.subscriptionList ) ); } @@ -298,7 +301,7 @@ TEST_TEAR_DOWN( MQTT_Unit_Subscription ) IotListDouble_RemoveAll( &( _connection.subscriptionList ), AwsIotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); - AwsIotMutex_Destroy( &( _connection.subscriptionMutex ) ); + IotMutex_Destroy( &( _connection.subscriptionMutex ) ); ( void ) memset( &_connection, 0x00, sizeof( _mqttConnection_t ) ); } diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 133003761f..0ba72f361f 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -39,7 +39,7 @@ #include "aws_iot_json_utils.h" /* Platform layer includes. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_threads.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -88,17 +88,17 @@ extern int snprintf( char *, /** * @brief The length of @ref AWS_IOT_TEST_SHADOW_THING_NAME. */ -#define _THING_NAME_LENGTH ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ) +#define _THING_NAME_LENGTH ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ) /** * @brief The Shadow document used for these tests. */ -#define _TEST_SHADOW_DOCUMENT "{\"state\":{\"reported\":{\"key\":\"value\"}},\"clientToken\":\"shadowtest\"}" +#define _TEST_SHADOW_DOCUMENT "{\"state\":{\"reported\":{\"key\":\"value\"}},\"clientToken\":\"shadowtest\"}" /** * @brief The length of #_TEST_SHADOW_DOCUMENT. */ -#define _TEST_SHADOW_DOCUMENT_LENGTH ( sizeof( _TEST_SHADOW_DOCUMENT ) - 1 ) +#define _TEST_SHADOW_DOCUMENT_LENGTH ( sizeof( _TEST_SHADOW_DOCUMENT ) - 1 ) /*-----------------------------------------------------------*/ @@ -108,7 +108,7 @@ extern int snprintf( char *, typedef struct _operationCompleteParams { AwsIotShadowCallbackType_t expectedType; /**< @brief Expected callback type. */ - AwsIotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ + IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ AwsIotShadowReference_t reference; /**< @brief Reference to expected completed operation. */ } _operationCompleteParams_t; @@ -166,7 +166,7 @@ static void _operationComplete( void * pArgument, } /* Unblock the main test thread. */ - AwsIotSemaphore_Post( &( pParams->waitSem ) ); + IotSemaphore_Post( &( pParams->waitSem ) ); } /*-----------------------------------------------------------*/ @@ -178,7 +178,7 @@ static void _operationComplete( void * pArgument, static void _deltaCallback( void * pArgument, AwsIotShadowCallbackParam_t * const pCallback ) { - AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; const char * pValue = NULL, * pClientToken = NULL; size_t valueLength = 0, clientTokenLength = 0; @@ -206,7 +206,7 @@ static void _deltaCallback( void * pArgument, AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); /* Unblock the main test thread. */ - AwsIotSemaphore_Post( pWaitSem ); + IotSemaphore_Post( pWaitSem ); } /*-----------------------------------------------------------*/ @@ -218,7 +218,7 @@ static void _deltaCallback( void * pArgument, static void _updatedCallback( void * pArgument, AwsIotShadowCallbackParam_t * const pCallback ) { - AwsIotSemaphore_t * pWaitSem = ( AwsIotSemaphore_t * ) pArgument; + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; const char * pPrevious = NULL, * pCurrent = NULL, * pClientToken = NULL; size_t previousStateLength = 0, currentStateLength = 0, clientTokenLength = 0; @@ -257,7 +257,7 @@ static void _updatedCallback( void * pArgument, AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); /* Unblock the main test thread. */ - AwsIotSemaphore_Post( pWaitSem ); + IotSemaphore_Post( pWaitSem ); } /*-----------------------------------------------------------*/ @@ -282,7 +282,7 @@ static void _updateGetDeleteAsync( int QoS ) documentInfo.QoS = QoS; /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); if( TEST_PROTECT() ) { @@ -300,8 +300,8 @@ static void _updateGetDeleteAsync( int QoS ) &callbackInfo, &( callbackParam.reference ) ); - if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting to update Shadow document." ); } @@ -317,8 +317,8 @@ static void _updateGetDeleteAsync( int QoS ) &( callbackParam.reference ) ); TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_STATUS_PENDING, status ); - if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting to retrieve Shadow document." ); } @@ -334,14 +334,14 @@ static void _updateGetDeleteAsync( int QoS ) &callbackInfo, &( callbackParam.reference ) ); - if( AwsIotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting to delete Shadow document." ); } } - AwsIotSemaphore_Destroy( &( callbackParam.waitSem ) ); + IotSemaphore_Destroy( &( callbackParam.waitSem ) ); } /*-----------------------------------------------------------*/ @@ -560,10 +560,10 @@ TEST( Shadow_System, DeltaCallback ) AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotSemaphore_t waitSem; + IotSemaphore_t waitSem; /* Create a semaphore to wait on. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); /* Set the delta callback information. */ deltaCallback.param1 = &waitSem; @@ -604,7 +604,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Block on the wait semaphore until the delta callback is invoked. */ - if( AwsIotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for delta callback." ); } @@ -625,7 +625,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); } - AwsIotSemaphore_Destroy( &waitSem ); + IotSemaphore_Destroy( &waitSem ); } /*-----------------------------------------------------------*/ @@ -635,10 +635,10 @@ TEST( Shadow_System, UpdatedCallback ) AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowCallbackInfo_t updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotSemaphore_t waitSem; + IotSemaphore_t waitSem; /* Create a semaphore to wait on. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); /* Set the delta callback information. */ updatedCallback.param1 = &waitSem; @@ -668,7 +668,7 @@ TEST( Shadow_System, UpdatedCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Block on the wait semaphore until the updated callback is invoked. */ - if( AwsIotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) + if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) { TEST_FAIL_MESSAGE( "Timed out waiting for updated callback." ); } @@ -682,7 +682,7 @@ TEST( Shadow_System, UpdatedCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); } - AwsIotSemaphore_Destroy( &waitSem ); + IotSemaphore_Destroy( &waitSem ); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 22e8a4e911..905b8e5a32 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -54,7 +54,8 @@ #undef _LIBRARY_LOG_LEVEL /* Platform layer includes. */ -#include "platform/aws_iot_clock.h" +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -103,12 +104,12 @@ static _mqttConnection_t * _pMqttConnection = NULL; /** * @brief Timer used to simulate a response from the network. */ -static AwsIotTimer_t _receiveTimer; +static IotTimer_t _receiveTimer; /** * @brief Synchronizes the MQTT send and receive threads in these tests. */ -static AwsIotMutex_t _lastPacketMutex; +static IotMutex_t _lastPacketMutex; /** * @brief The type of the last packet sent by the send thread. @@ -138,7 +139,7 @@ static void _receiveThread( void * pArgument ) ( void ) pArgument; /* Lock mutex to read and process the last packet sent. */ - AwsIotMutex_Lock( &_lastPacketMutex ); + IotMutex_Lock( &_lastPacketMutex ); /* Ensure that the last packet type and identifier were set. */ AwsIotShadow_Assert( _lastPacketType != 0 ); @@ -188,7 +189,7 @@ static void _receiveThread( void * pArgument ) NULL ); AwsIotShadow_Assert( bytesProcessed == ( int32_t ) receivedDataLength ); - AwsIotMutex_Unlock( &_lastPacketMutex ); + IotMutex_Unlock( &_lastPacketMutex ); } /*-----------------------------------------------------------*/ @@ -211,7 +212,7 @@ static size_t _sendSuccess( void * pSendContext, ( void ) pSendContext; /* Lock the mutex to modify the information on the last packet sent. */ - AwsIotMutex_Lock( &_lastPacketMutex ); + IotMutex_Lock( &_lastPacketMutex ); /* Set the last packet type based on the outgoing message. */ switch( AwsIotMqttInternal_GetPacketType( pMessage, messageLength ) ) @@ -273,12 +274,12 @@ static size_t _sendSuccess( void * pSendContext, AwsIotShadow_Assert( status == AWS_IOT_MQTT_SUCCESS ); /* Set the receive thread to run after a "network round-trip". */ - AwsIotClock_TimerArm( &_receiveTimer, - _NETWORK_ROUND_TRIP_TIME_MS, - 0 ); + IotClock_TimerArm( &_receiveTimer, + _NETWORK_ROUND_TRIP_TIME_MS, + 0 ); } - AwsIotMutex_Unlock( &_lastPacketMutex ); + IotMutex_Unlock( &_lastPacketMutex ); /* Return the message length to simulate a successful send. */ return messageLength; @@ -305,12 +306,12 @@ TEST_SETUP( Shadow_Unit_API ) _lastPacketIdentifier = 0; /* Create the mutex that synchronizes the receive callback and send thread. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotMutex_Create( &_lastPacketMutex ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &_lastPacketMutex ) ); /* Create the receive thread timer. */ - AwsIotClock_TimerCreate( &_receiveTimer, - _receiveThread, - NULL ); + IotClock_TimerCreate( &_receiveTimer, + _receiveThread, + NULL ); /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); @@ -344,14 +345,14 @@ TEST_TEAR_DOWN( Shadow_Unit_API ) AwsIotMqtt_Cleanup(); /* Destroy the receive thread timer. */ - AwsIotClock_TimerDestroy( &_receiveTimer ); + IotClock_TimerDestroy( &_receiveTimer ); /* Wait for the receive thread to finish and release the last packet mutex. */ - AwsIotMutex_Lock( &_lastPacketMutex ); + IotMutex_Lock( &_lastPacketMutex ); /* Destroy the last packet mutex. */ - AwsIotMutex_Unlock( &_lastPacketMutex ); - AwsIotMutex_Destroy( &_lastPacketMutex ); + IotMutex_Unlock( &_lastPacketMutex ); + IotMutex_Destroy( &_lastPacketMutex ); } /*-----------------------------------------------------------*/ From 23bc66f43303ba406c918484c0430b9909017665 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Fri, 25 Jan 2019 12:12:29 -0500 Subject: [PATCH 014/844] CBMC: Added IOT_SYSTEM_TYPES_FILE from CMakeLists.txt to CBMC build (#263) --- cbmc/proofs/Makefile.common | 1 + 1 file changed, 1 insertion(+) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index c4023454e8..cd467edff1 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -25,6 +25,7 @@ INC = \ DEF += \ -DIOT_CONFIG_FILE=\"iot_demo_config.h\" \ -DIOT_SDK_VERSION=\"4.0.0\" \ + -DIOT_SYSTEM_TYPES_FILE=\"platform/types/iot_platform_types_posix.h\" \ -Dawsiotmqtt_EXPORTS \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) From 4d2f529c32810906d07034fa7d8cbfcdb9a03efc Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 25 Jan 2019 16:39:59 -0800 Subject: [PATCH 015/844] Directory reorganization and common init. (#264) --- CMakeLists.txt | 9 +- cbmc/proofs/Makefile.common | 3 +- demos/posix/CMakeLists.txt | 4 +- demos/posix/aws_iot_demo_mqtt_posix.c | 34 ++- demos/posix/aws_iot_demo_shadow_posix.c | 30 +- doc/config/common | 2 +- doc/config/layout_main.xml | 7 +- doc/config/logging | 1 + doc/config/mqtt | 1 + doc/config/platform | 11 +- doc/config/shadow | 1 + doc/config/static_memory | 26 ++ doc/guide/building.txt | 4 +- doc/lib/logging.txt | 8 +- doc/lib/mqtt.txt | 10 +- doc/lib/platform.txt | 128 +-------- doc/lib/shadow.txt | 10 +- doc/lib/static_memory.txt | 117 ++++++++ doc/mainpage.txt | 18 +- lib/include/aws_iot_logging_setup.h | 2 +- lib/include/iot_common.h | 65 +++++ lib/include/platform/iot_clock.h | 2 +- lib/include/platform/iot_threads.h | 2 +- lib/include/private/aws_iot_mqtt_internal.h | 30 +- lib/include/private/aws_iot_shadow_internal.h | 14 +- .../iot_static_memory.h} | 239 +++++++++------- .../{platform => }/types/iot_platform_types.h | 0 lib/source/common/CMakeLists.txt | 22 +- lib/source/common/aws_iot_logging.c | 24 +- lib/source/common/iot_common.c | 81 ++++++ .../aws_iot_static_memory_shadow.c} | 57 ++-- .../static_memory/iot_static_memory_common.c} | 123 ++++---- .../static_memory/iot_static_memory_mqtt.c | 263 +++++++++++++++++ lib/source/mqtt/CMakeLists.txt | 16 +- .../aws_iot_static_memory_mqtt_posix.c | 264 ------------------ lib/source/shadow/CMakeLists.txt | 10 +- .../include/posix}/iot_platform_types_posix.h | 0 .../source}/posix/CMakeLists.txt | 17 +- .../source}/posix/iot_clock_posix.c | 0 .../source}/posix/iot_threads_posix.c | 2 +- .../posix/network/iot_network_openssl.c | 6 +- scripts/build_check_commit.sh | 7 +- scripts/build_check_pr.sh | 7 +- tests/common/CMakeLists.txt | 2 +- tests/common/iot_tests_common.c | 12 + tests/iot_tests_config.h | 14 +- tests/mqtt/CMakeLists.txt | 2 +- tests/mqtt/aws_iot_tests_mqtt.c | 12 + tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 18 +- tests/shadow/CMakeLists.txt | 2 +- tests/shadow/aws_iot_tests_shadow.c | 12 + tests/unity/CMakeLists.txt | 6 - 52 files changed, 1024 insertions(+), 733 deletions(-) create mode 100644 doc/config/static_memory create mode 100644 doc/lib/static_memory.txt create mode 100644 lib/include/iot_common.h rename lib/include/{platform/aws_iot_static_memory.h => private/iot_static_memory.h} (52%) rename lib/include/{platform => }/types/iot_platform_types.h (100%) create mode 100644 lib/source/common/iot_common.c rename lib/source/{platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c => common/static_memory/aws_iot_static_memory_shadow.c} (73%) rename lib/source/{platform/posix/static_memory/aws_iot_static_memory_common_posix.c => common/static_memory/iot_static_memory_common.c} (61%) create mode 100644 lib/source/common/static_memory/iot_static_memory_mqtt.c delete mode 100644 lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c rename {lib/include/platform/types => platform/include/posix}/iot_platform_types_posix.h (100%) rename {lib/source/platform => platform/source}/posix/CMakeLists.txt (78%) rename {lib/source/platform => platform/source}/posix/iot_clock_posix.c (100%) rename {lib/source/platform => platform/source}/posix/iot_threads_posix.c (99%) rename lib/source/platform/posix/network/aws_iot_network_openssl.c => platform/source/posix/network/iot_network_openssl.c (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 25d60f9fd3..0ec01c7b8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,8 +25,9 @@ endif() set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) -# SDK include path. -include_directories( ${PROJECT_SOURCE_DIR}/lib/include ) +# SDK include paths. +include_directories( ${PROJECT_SOURCE_DIR}/lib/include + ${PROJECT_SOURCE_DIR}/platform/include ) # Demo include path. include_directories( ${PROJECT_SOURCE_DIR}/demos ) @@ -50,8 +51,8 @@ endif() # Platform libraries. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - add_definitions( -DIOT_SYSTEM_TYPES_FILE="platform/types/iot_platform_types_posix.h" ) - add_subdirectory( lib/source/platform/posix ) + add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) + add_subdirectory( platform/source/posix ) endif() # Common libraries (linear containers, logging, etc.) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index cd467edff1..61b5f59338 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -20,12 +20,13 @@ VIEWER ?= cbmc-viewer INC = \ -I$(MQTT)/lib/include \ + -I$(MQTT)/platform/include \ -I$(MQTT)/demos \ DEF += \ -DIOT_CONFIG_FILE=\"iot_demo_config.h\" \ -DIOT_SDK_VERSION=\"4.0.0\" \ - -DIOT_SYSTEM_TYPES_FILE=\"platform/types/iot_platform_types_posix.h\" \ + -DIOT_SYSTEM_TYPES_FILE=\"posix/iot_platform_types_posix.h\" \ -Dawsiotmqtt_EXPORTS \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) diff --git a/demos/posix/CMakeLists.txt b/demos/posix/CMakeLists.txt index f89bf79499..6d2611c696 100644 --- a/demos/posix/CMakeLists.txt +++ b/demos/posix/CMakeLists.txt @@ -14,7 +14,7 @@ target_include_directories( aws_iot_demo_mqtt ${DEMO_COMMON_INCLUDE_FILES} ) # MQTT demo library dependencies. -target_link_libraries( aws_iot_demo_mqtt awsiotplatform awsiotmqtt ) +target_link_libraries( aws_iot_demo_mqtt iotplatform iotmqtt ) # Shadow demo source files. add_executable( aws_iot_demo_shadow @@ -28,4 +28,4 @@ target_include_directories( aws_iot_demo_mqtt ${DEMO_COMMON_INCLUDE_FILES} ) # Shadow demo library dependencies. -target_link_libraries( aws_iot_demo_shadow awsiotplatform awsiotmqtt awsiotshadow ) +target_link_libraries( aws_iot_demo_shadow iotplatform iotmqtt awsiotshadow ) diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/aws_iot_demo_mqtt_posix.c index 7e634e9ba3..b81d2ffe97 100644 --- a/demos/posix/aws_iot_demo_mqtt_posix.c +++ b/demos/posix/aws_iot_demo_mqtt_posix.c @@ -34,6 +34,9 @@ #include #include +/* Common libraries include. */ +#include "iot_common.h" + /* Common demo includes. */ #include "aws_iot_demo.h" #include "aws_iot_demo_posix.h" @@ -46,6 +49,7 @@ int main( int argc, char ** argv ) { + bool commonInitialized = false; int status = 0; AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; @@ -69,11 +73,25 @@ int main( int argc, status = -1; } - /* Initialize the network. */ - if( ( status == 0 ) && - ( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) ) + /* Initialize the common libraries and network. */ + if( status == 0 ) { - status = -1; + if( IotCommon_Init() == false ) + { + status = -1; + } + else + { + if( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) + { + IotCommon_Cleanup(); + status = -1; + } + else + { + commonInitialized = true; + } + } } if( status == 0 ) @@ -156,8 +174,12 @@ int main( int argc, AwsIotNetwork_DestroyConnection( networkConnection ); } - /* Clean up the network. */ - AwsIotNetwork_Cleanup(); + /* Clean up the common libraries and network. */ + if( commonInitialized == true ) + { + IotCommon_Cleanup(); + AwsIotNetwork_Cleanup(); + } /* Log the demo status. */ if( status == 0 ) diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index 682ae60c6e..1065573cbb 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -37,6 +37,9 @@ /* Shadow include. */ #include "aws_iot_shadow.h" +/* Common libraries include. */ +#include "iot_common.h" + /* Common demo includes. */ #include "aws_iot_demo.h" #include "aws_iot_demo_posix.h" @@ -49,6 +52,7 @@ int main( int argc, char ** argv ) { + bool commonInitialized = false; int status = 0; AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; @@ -73,10 +77,24 @@ int main( int argc, } /* Initialize the network. */ - if( ( status == 0 ) && - ( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) ) + if( status == 0 ) { - status = -1; + if( IotCommon_Init() == false ) + { + status = -1; + } + else + { + if( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) + { + IotCommon_Cleanup(); + status = -1; + } + else + { + commonInitialized = true; + } + } } /* Thing Name must be set for this demo. */ @@ -167,7 +185,11 @@ int main( int argc, } /* Clean up the network. */ - AwsIotNetwork_Cleanup(); + if( commonInitialized == true ) + { + IotCommon_Cleanup(); + AwsIotNetwork_Cleanup(); + } /* Log the demo status. */ if( status == 0 ) diff --git a/doc/config/common b/doc/config/common index 6759b8e8e8..b2ffd7c2aa 100644 --- a/doc/config/common +++ b/doc/config/common @@ -21,7 +21,7 @@ QUIET = YES # Define the following preprocessor constants when generating documentation. PREDEFINED = "DOXYGEN=1" \ - "AWS_IOT_STATIC_MEMORY_ONLY=1" \ + "IOT_STATIC_MEMORY_ONLY=1" \ "_LIBRARY_LOG_LEVEL=AWS_IOT_LOG_DEBUG" \ "_LIBRARY_LOG_NAME=\"DOXYGEN\"" diff --git a/doc/config/layout_main.xml b/doc/config/layout_main.xml index 52a2403711..a5090fcaa0 100644 --- a/doc/config/layout_main.xml +++ b/doc/config/layout_main.xml @@ -9,8 +9,11 @@ - - + + + + + diff --git a/doc/config/logging b/doc/config/logging index 14a4a4677e..c8b301ef79 100644 --- a/doc/config/logging +++ b/doc/config/logging @@ -23,4 +23,5 @@ FILE_PATTERNS = *logging*.c *logging*.h *logging*.txt # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ + doc/tag/static_memory.tag=../static_memory \ doc/tag/platform.tag=../platform diff --git a/doc/config/mqtt b/doc/config/mqtt index 631cdc9dc3..9e7ac4669a 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -30,5 +30,6 @@ FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt aws_iot_tests_network.c # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ doc/tag/logging.tag=../logging \ + doc/tag/static_memory.tag=../static_memory \ doc/tag/platform.tag=../platform \ doc/tag/linear_containers.tag=../linear_containers diff --git a/doc/config/platform b/doc/config/platform index 5d55b1b49a..69e44ec848 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -15,13 +15,14 @@ GENERATE_TAGFILE = doc/tag/platform.tag # Directories containing library source code. INPUT = doc/lib \ lib/include/platform \ - lib/include/platform/types \ - lib/source/platform/posix \ - lib/source/platform/posix/static_memory \ - lib/source/platform/posix/network + lib/include/types # Library file names. -FILE_PATTERNS = *.c *.h *platform*.txt +FILE_PATTERNS = platform.txt \ + aws_iot_network.h \ + iot_clock.h \ + iot_threads.h \ + iot_platform_types.h # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ diff --git a/doc/config/shadow b/doc/config/shadow index 01b984ef3a..63e3fe2110 100644 --- a/doc/config/shadow +++ b/doc/config/shadow @@ -25,5 +25,6 @@ FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt TAGFILES = doc/tag/main.tag=../main \ doc/tag/mqtt.tag=../mqtt \ doc/tag/logging.tag=../logging \ + doc/tag/static_memory.tag=../static_memory \ doc/tag/platform.tag=../platform \ doc/tag/linear_containers.tag=../linear_containers diff --git a/doc/config/static_memory b/doc/config/static_memory new file mode 100644 index 0000000000..ece30d90b8 --- /dev/null +++ b/doc/config/static_memory @@ -0,0 +1,26 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Static Memory" +PROJECT_BRIEF = "Statically-allocated buffer pools" + +# Library documentation output directory. +HTML_OUTPUT = static_memory + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/static_memory.tag + +# Directories containing library source code. +INPUT = doc/lib \ + lib/include/private \ + lib/source/common/static_memory + +# Library file names. +FILE_PATTERNS = *static_memory*.h *static_memory*.c *static_memory*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/mqtt.tag=../mqtt \ + doc/tag/shadow.tag=../shadow diff --git a/doc/guide/building.txt b/doc/guide/building.txt index ebc095d39e..9b235d990f 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -40,8 +40,8 @@ Demo application executables will be placed in a `bin` directory of the CMake bu Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/iot_demo_config.h` is recommended over setting them using CMake. @code{sh} -# Example: Set AWS_IOT_STATIC_MEMORY_ONLY using CMake -cmake .. -DCMAKE_C_FLAGS=-DAWS_IOT_STATIC_MEMORY_ONLY=1 +# Example: Set IOT_STATIC_MEMORY_ONLY using CMake +cmake .. -DCMAKE_C_FLAGS=-DIOT_STATIC_MEMORY_ONLY=1 @endcode By default, CMake may build in `Release` configuration. To enable debugging symbols, set `CMAKE_BUILD_TYPE` to `Debug`. diff --git a/doc/lib/logging.txt b/doc/lib/logging.txt index b236c8a246..1d69b3bd2d 100644 --- a/doc/lib/logging.txt +++ b/doc/lib/logging.txt @@ -20,18 +20,18 @@ digraph logging_dependencies { rank = same; platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; - platform_static_memory[label="Static memory", fillcolor="#e89025ff" URL="@ref platform_static_memory"]; + static_memory[label="Static memory", fillcolor="#e89025ff" URL="@ref static_memory"]; } standard_library[label="Standard library\nstdarg, stdbool, stddef,\nstdint, stdio, string", fillcolor="#d15555ff"]; logging -> platform_clock; - logging -> platform_static_memory [style="dashed", label=" if static memory only"]; + logging -> static_memory [style="dashed", label=" if static memory only"]; logging -> standard_library; } @enddot Currently, the logging library has the following dependencies: - The [platform clock component](@ref platform_clock) for [generating the timestring](@ref platform_clock_function_gettimestring) to print in log messages. -- When @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, the logging library depends on the [platform static memory component](@ref platform_static_memory) to allocate buffers for log messages. When @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`, the logging library will default to using the standard library's [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) functions. The logging library's memory allocation functions [may always be overridden](@ref logging_config_memory). Note that the logging library will silently discard logs if it fails to allocate memory for the message. +- When @ref IOT_STATIC_MEMORY_ONLY is `1`, the logging library depends on the [platform static memory component](@ref static_memory) to allocate buffers for log messages. When @ref IOT_STATIC_MEMORY_ONLY is `0`, the logging library will default to using the standard library's [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) functions. The logging library's memory allocation functions [may always be overridden](@ref logging_config_memory). Note that the logging library will silently discard logs if it fails to allocate memory for the message. @section logging_setup_use Setup and use @brief How to set up and use the logging library. @@ -106,7 +106,7 @@ Although this function is supposed to return an `int`, the logging library does @configdefault Standard library [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function. @section logging_config_memory Memory allocation -@brief If @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the logging library. +@brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the logging library. - #AwsIotLogging_Malloc
@copybrief AwsIotLogging_Malloc - #AwsIotLogging_Free
diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 3cd409b0cf..618bebe7f1 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -30,6 +30,7 @@ digraph mqtt_dependencies rank = same; linear_containers[label="List/Queue", URL="@ref linear_containers"]; logging[label="Logging", URL="@ref logging"]; + static_memory[label="Static memory", URL="@ref static_memory"]; } subgraph { @@ -37,16 +38,15 @@ digraph mqtt_dependencies platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; platform_network[label="Network", fillcolor="#e89025ff", URL="@ref platform_network"]; - platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; } mqtt -> linear_containers; mqtt -> logging [label=" if logging enabled", style="dashed"]; mqtt -> platform_threads; mqtt -> platform_clock; mqtt -> platform_network; - mqtt -> platform_static_memory [label=" if static memory only", style="dashed"]; + mqtt -> static_memory [label=" if static memory only", style="dashed"]; logging -> platform_clock; - logging -> platform_static_memory [label=" if static memory only", style="dashed"]; + logging -> static_memory [label=" if static memory only", style="dashed"]; } @enddot @@ -180,7 +180,7 @@ The number of threads to spawn for the stress tests. Up to @ref AWS_IOT_TEST_MQT Up to @ref AWS_IOT_TEST_MQTT_THREADS `*` @ref AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. @configpossible Any non-negative integer.
-@configdefault When @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, this value defaults to @ref AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. Otherwise, it defaults to `100`. +@configdefault When @ref IOT_STATIC_MEMORY_ONLY is `1`, this value defaults to @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. Otherwise, it defaults to `100`. */ /** @@ -349,7 +349,7 @@ If @ref AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS is `100`, then both `Event 1` and @configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. @section mqtt_config_memory Memory allocation -@brief If @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the MQTT library. +@brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the MQTT library. - #AwsIotMqtt_MallocConnection
@copybrief AwsIotMqtt_MallocConnection - #AwsIotMqtt_FreeConnection
diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 7b23eb8ca3..3ffa40a09d 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -6,8 +6,6 @@ All system calls (including networking) used in the AWS IoT libraries go through a lightweight platform layer. The functions of the platform layer are intended to be easily implementable on a wide variety of operating systems. The current platform layer has the following components: - @ref platform_clock
@copybrief platform_clock -- @ref platform_static_memory
- @copybrief platform_static_memory - @ref platform_threads
@copybrief platform_threads - @ref platform_network
@@ -17,14 +15,13 @@ All of the functions in each component will need to be implemented to port the l Component | Supported platforms --------- | ------------------- @ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) -@ref platform_static_memory | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_network | [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) */ /** @page platform_clock Clock -@brief @copybrief aws_iot_clock.h +@brief @copybrief iot_clock.h The platform clock component provides other libraries with functions relating to timers and clocks. It interfaces directly with the operation system to provide: - Clocks for reading the current time. @@ -103,63 +100,6 @@ Currently, the networking component has the following dependencies: - The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. */ -/** -@page platform_static_memory Static Memory -@brief @copybrief aws_iot_static_memory.h - -The platform static memory component provides statically-allocated buffers that are used instead of dynamic memory allocation when @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`. Using static memory only does not guarantee that memory allocation will always succeed; it's possible for all statically-allocated buffers to be in-use. However, static memory only can guarantee the availability of at least a certain amount of resources. Because space must be reserved for statically-allocated buffers, binaries compiled with static memory only will be larger. - -@section platform_static_memory_types Types of buffers -@brief The types of statically-allocated buffers provided by the static memory component. - -@subsection platform_static_memory_types_messagebuffers Message buffers -Message buffers are fixed-size buffers used for strings, such as log messages or bytes transmitted over a network. Their size and number can be configured with the constants @ref AWS_IOT_MESSAGE_BUFFERS (number) and @ref AWS_IOT_MESSAGE_BUFFER_SIZE (size of each message buffer). Message buffers may be used by any library, and are analogous to the generic buffers allocated by [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) (though all message buffers are the same size). - -@subsection platform_static_memory_mqtt MQTT static buffers -@brief Statically-allocated buffers used by the [MQTT library](@ref mqtt). - -@subsubsection platform_static_memory_types_mqttconnections Connections -MQTT connections correspond to a statically-allocated MQTT session between a single client and server. In static memory mode, the number of simultaneous, active MQTT connections is controlled by the constant @ref AWS_IOT_MQTT_CONNECTIONS. - -@subsubsection platform_static_memory_types_mqttoperations Operations -MQTT operations correspond to in-progress requests between a client and the MQTT server, such as CONNECT, PUBLISH, or publish acknowledgement (PUBACK). In static memory mode, the number of simultaneous, active MQTT operations is controlled by the constant @ref AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. - -@subsubsection platform_static_memory_types_mqtttimerevents Timer events -MQTT timer events store data on operations that require responses within a certain timeout: PINGREQ and QoS 1 PUBLISH. In static memory mode, the number of simultaneous, active MQTT timer events is controlled by the constant @ref AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. - -@subsubsection platform_static_memory_types_mqttsubscriptions Subscriptions -MQTT subscriptions store records on callbacks registered for MQTT topic filters. In static memory mode, the number of simultaneous, active MQTT subscriptions (across all connections) is controlled by the constant @ref AWS_IOT_MQTT_SUBSCRIPTIONS. - -@subsection platform_static_memory_shadow Shadow static buffers -@brief Statically-allocated buffers used by the [Shadow library](@ref shadow). - -@subsubsection platform_static_memory_types_shadowoperations Operations -Shadow operations correspond to in-progress Shadow [DELETE](@ref shadow_function_delete), [GET](@ref shadow_function_get), or [UPDATE](@ref shadow_function_update) operations. In static memory mode, the number of simultaneous, active Shadow operations is controlled by the constant @ref AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS. - -@subsubsection platform_static_memory_types_shadowsubscriptions Subscriptions -A Shadow subscriptions object groups MQTT subscriptions for a Thing. Shadow operations require pairs of subscriptions to determine their status (i.e. `/accepted` or `/rejected`); these subscriptions (as well as subscriptions for Shadow callbacks) are tracked by a Shadow subscriptions object. In static memory mode, the number of simultaneous, active Shadow subscriptions is controlled by the constant @ref AWS_IOT_SHADOW_SUBSCRIPTIONS. - -@dependencies{platform_static_memory,platform static memory component} -@dot "Static memory direct dependencies" -digraph static_memory_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - platform_static_memory[label="Static memory", fillcolor="#e89025ff"]; - subgraph - { - rank = same; - operating_system[label="Operating system", fillcolor="#999999ff"] - standard_library[label="Standard library\nstdbool, stddef, string", fillcolor="#d15555ff"]; - } - platform_static_memory -> operating_system; - platform_static_memory -> standard_library; -} -@enddot - -Currently, the platform static memory component has the following dependencies: -- The operating system must provide a mechanism to implement critical sections. -*/ - /** @page platform_threads Thread Management @brief @copybrief iot_threads.h @@ -215,70 +155,14 @@ This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the [platform networkin @configpossible One of the @ref logging_constants_levels.
@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. -@section AWS_IOT_MESSAGE_BUFFERS -@brief The number of statically-allocated [message buffers](@ref platform_static_memory_types_messagebuffers). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see @ref platform_static_memory_types_messagebuffers - -@configpossible Any positive integer.
-@configdefault `8` - -@section AWS_IOT_MESSAGE_BUFFER_SIZE -@brief The size (in bytes) of each statically-allocated [message buffer](@ref platform_static_memory_types_messagebuffers). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see @ref platform_static_memory_types_messagebuffers - -@configpossible Any positive integer.
-@configdefault `1024` - -@section AWS_IOT_MQTT_CONNECTIONS -@brief The number of statically-allocated [MQTT connections](@ref platform_static_memory_types_mqttconnections). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see [MQTT connections](@ref platform_static_memory_types_mqttconnections) - -@configpossible Any positive integer.
-@configdefault `1` - -@section AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS -@brief The number of statically-allocated [MQTT operations](@ref platform_static_memory_types_mqttoperations). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see [In-progress MQTT operations](@ref platform_static_memory_types_mqttoperations) - -@configpossible Any positive integer.
-@configdefault `10` - -@section AWS_IOT_MQTT_SUBSCRIPTIONS -@brief The number of statically-allocated [MQTT subscriptions](@ref platform_static_memory_types_mqttsubscriptions). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see [MQTT subscriptions](@ref platform_static_memory_types_mqttsubscriptions) - -@configpossible Any positive integer.
-@configdefault `8` - -@section AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS -@brief The number of statically-allocated [Shadow operations](@ref platform_static_memory_types_shadowoperations). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see [In-progress Shadow operations](@ref platform_static_memory_types_shadowoperations) - -@configpossible Any positive integer.
-@configdefault `10` - -@section AWS_IOT_SHADOW_SUBSCRIPTIONS -@brief The number of statically-allocated [Shadow subscriptions](@ref platform_static_memory_types_shadowsubscriptions). This setting has no effect if @ref AWS_IOT_STATIC_MEMORY_ONLY is `0`. - -@see [Shadow subscriptions objects](@ref platform_static_memory_types_shadowsubscriptions) - -@configpossible Any positive integer.
-@configdefault `2` - @section platform_config_memory Memory allocation @brief Memory allocation function overrides for the platform layer. -Some platform layers are not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. Currently, the following platform implementations require memory allocation: +Some platform layers are not affected by @ref IOT_STATIC_MEMORY_ONLY. Currently, the following platform implementations require memory allocation: - POSIX
- This implementation is not affected by @ref AWS_IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - - #IotThreads_Malloc and #IotThreads_Free. - - #AwsIotNetwork_Malloc and #AwsIotNetwork_Free. + This implementation is not affected by @ref IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + - `IotThreads_Malloc` and `IotThreads_Free`. + - `AwsIotNetwork_Malloc` and `AwsIotNetwork_Free`. @section platform_config_posixheaders POSIX headers @brief The POSIX platform layer allows the standard POSIX header includes to be overridden. Overrides only affect the POSIX platform layer. @@ -317,8 +201,6 @@ To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HE @copybrief platform_clock_functions - @subpage platform_network_functions
@copybrief platform_network_functions -- @subpage platform_static_memory_functions
- @copybrief platform_static_memory - @subpage platform_threads_functions
@copybrief platform_threads_functions */ diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index fbebc7e48f..a34dbdf0ab 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -30,16 +30,12 @@ digraph shadow_dependencies rank = same; linear_containers[label="List/Queue", URL="@ref linear_containers"]; logging[label="Logging", URL="@ref logging"]; - } - subgraph - { - rank = same; - platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; + static_memory[label="Static memory", URL="@ref static_memory"]; } shadow -> mqtt; shadow -> linear_containers; shadow -> logging [label=" if logging enabled", style="dashed"]; - shadow -> platform_static_memory [label=" if static memory only", style="dashed"]; + shadow -> static_memory [label=" if static memory only", style="dashed"]; } @enddot @@ -92,7 +88,7 @@ Log messages from the Shadow library at or below this setting will be printed. @configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`; otherwise, nothing. @section shadow_config_memory Memory allocation -@brief If @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the Shadow library. +@brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the Shadow library. - #AwsIotShadow_MallocOperation
@copybrief AwsIotShadow_MallocOperation - #AwsIotShadow_FreeOperation
diff --git a/doc/lib/static_memory.txt b/doc/lib/static_memory.txt new file mode 100644 index 0000000000..987ba7b5d6 --- /dev/null +++ b/doc/lib/static_memory.txt @@ -0,0 +1,117 @@ +/** +@mainpage +@anchor static_memory +@brief @copybrief iot_static_memory.h

+ +The static memory component provides statically-allocated buffers that are used instead of dynamic memory allocation when @ref IOT_STATIC_MEMORY_ONLY is `1`. Using static memory only does not guarantee that memory allocation will always succeed; it's possible for all statically-allocated buffers to be in-use. However, static memory only can guarantee the availability of at least a certain amount of resources. Because space must be reserved for statically-allocated buffers, binaries compiled with static memory only will be larger. + +@section static_memory_types Types of buffers +@brief The types of statically-allocated buffers provided by the static memory component. + +@subsection static_memory_types_messagebuffers Message buffers +Message buffers are fixed-size buffers used for strings, such as log messages or bytes transmitted over a network. Their size and number can be configured with the constants @ref IOT_MESSAGE_BUFFERS (number) and @ref IOT_MESSAGE_BUFFER_SIZE (size of each message buffer). Message buffers may be used by any library, and are analogous to the generic buffers allocated by [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) (though all message buffers are the same size). + +@subsection static_memory_mqtt MQTT static buffers +@brief Statically-allocated buffers used by the [MQTT library](@ref mqtt). + +@subsubsection static_memory_types_mqttconnections Connections +MQTT connections correspond to a statically-allocated MQTT session between a single client and server. In static memory mode, the number of simultaneous, active MQTT connections is controlled by the constant @ref IOT_MQTT_CONNECTIONS. + +@subsubsection static_memory_types_mqttoperations Operations +MQTT operations correspond to in-progress requests between a client and the MQTT server, such as CONNECT, PUBLISH, or publish acknowledgement (PUBACK). In static memory mode, the number of simultaneous, active MQTT operations is controlled by the constant @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. + +@subsubsection static_memory_types_mqtttimerevents Timer events +MQTT timer events store data on operations that require responses within a certain timeout: PINGREQ and QoS 1 PUBLISH. In static memory mode, the number of simultaneous, active MQTT timer events is controlled by the constant @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. + +@subsubsection static_memory_types_mqttsubscriptions Subscriptions +MQTT subscriptions store records on callbacks registered for MQTT topic filters. In static memory mode, the number of simultaneous, active MQTT subscriptions (across all connections) is controlled by the constant @ref IOT_MQTT_SUBSCRIPTIONS. + +@subsection static_memory_shadow Shadow static buffers +@brief Statically-allocated buffers used by the [Shadow library](@ref shadow). + +@subsubsection static_memory_types_shadowoperations Operations +Shadow operations correspond to in-progress Shadow [DELETE](@ref shadow_function_delete), [GET](@ref shadow_function_get), or [UPDATE](@ref shadow_function_update) operations. In static memory mode, the number of simultaneous, active Shadow operations is controlled by the constant @ref AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS. + +@subsubsection static_memory_types_shadowsubscriptions Subscriptions +A Shadow subscriptions object groups MQTT subscriptions for a Thing. Shadow operations require pairs of subscriptions to determine their status (i.e. `/accepted` or `/rejected`); these subscriptions (as well as subscriptions for Shadow callbacks) are tracked by a Shadow subscriptions object. In static memory mode, the number of simultaneous, active Shadow subscriptions is controlled by the constant @ref AWS_IOT_SHADOW_SUBSCRIPTIONS. + +@dependencies{static_memory,static memory component} +@dot "Static memory direct dependencies" +digraph static_memory_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + static_memory[label="Static memory", fillcolor="#aed8a9ff"]; + subgraph + { + rank = same; + operating_system[label="Operating system", fillcolor="#999999ff"] + standard_library[label="Standard library\nstdbool, stddef, string", fillcolor="#d15555ff"]; + } + static_memory -> operating_system; + static_memory -> standard_library; +} +@enddot + +Currently, the static memory component has the following dependencies: +- The operating system must provide a mechanism to implement critical sections. +*/ + +/** +@configpage{static_memory,statically-allocated buffer pools} + +@section IOT_MESSAGE_BUFFERS +@brief The number of statically-allocated [message buffers](@ref static_memory_types_messagebuffers). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see @ref static_memory_types_messagebuffers + +@configpossible Any positive integer.
+@configdefault `8` + +@section IOT_MESSAGE_BUFFER_SIZE +@brief The size (in bytes) of each statically-allocated [message buffer](@ref static_memory_types_messagebuffers). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see @ref static_memory_types_messagebuffers + +@configpossible Any positive integer.
+@configdefault `1024` + +@section IOT_MQTT_CONNECTIONS +@brief The number of statically-allocated [MQTT connections](@ref static_memory_types_mqttconnections). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see [MQTT connections](@ref static_memory_types_mqttconnections) + +@configpossible Any positive integer.
+@configdefault `1` + +@section IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS +@brief The number of statically-allocated [MQTT operations](@ref static_memory_types_mqttoperations). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see [In-progress MQTT operations](@ref static_memory_types_mqttoperations) + +@configpossible Any positive integer.
+@configdefault `10` + +@section IOT_MQTT_SUBSCRIPTIONS +@brief The number of statically-allocated [MQTT subscriptions](@ref static_memory_types_mqttsubscriptions). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see [MQTT subscriptions](@ref static_memory_types_mqttsubscriptions) + +@configpossible Any positive integer.
+@configdefault `8` + +@section AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS +@brief The number of statically-allocated [Shadow operations](@ref static_memory_types_shadowoperations). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see [In-progress Shadow operations](@ref static_memory_types_shadowoperations) + +@configpossible Any positive integer.
+@configdefault `10` + +@section AWS_IOT_SHADOW_SUBSCRIPTIONS +@brief The number of statically-allocated [Shadow subscriptions](@ref static_memory_types_shadowsubscriptions). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. + +@see [Shadow subscriptions objects](@ref static_memory_types_shadowsubscriptions) + +@configpossible Any positive integer.
+@configdefault `2` +*/ diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 9a60d76a7a..b6f87517a0 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -11,6 +11,18 @@ Design goals of this SDK include: All system calls go through a lightweight portability layer to allow this SDK to be easily ported to many systems. See [platform layer](../platform/index.html) for a list of functions that must be ported. */ +/** +@page global_config Global Configuration + +The following pages describe configuration settings that affect multiple components of this SDK. +- @subpage global_library_config
+ Settings that affect all libraries. +- @subpage global_demos_config
+ Settings that affect all demos. +- @subpage global_tests_config
+ Settings that affect all tests. +*/ + /** @globalconfigpage{library,Library,libraries} @@ -30,8 +42,8 @@ Any libraries that do not define a log level will use this setting. If a both a @configpossible One of the @ref logging_constants_levels.
@configdefault #AWS_IOT_LOG_NONE -@section AWS_IOT_STATIC_MEMORY_ONLY -@brief Set this to `1` to disable the usage of dynamic memory allocation ([malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html)) throughout the AWS IoT libraries. +@section IOT_STATIC_MEMORY_ONLY +@brief Set this to `1` to disable the usage of dynamic memory allocation ([malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html)) throughout the libraries in this SDK. When dynamic memory allocation is disabled, all of the memory allocation functions used in the libraries must be re-implemented. See each library's Configuration/Memory allocation section for a list of functions that must be re-implemented for that library. @@ -39,7 +51,7 @@ When dynamic memory allocation is disabled, all of the memory allocation functio @configrecommended `0`
@configdefault `0` -@attention This constant only affects the AWS IoT libraries. It has no effect on system calls or third-party libraries; it may also have no effect on [platform layer] (@ref platform) implementations. +@attention This settings has no effect on system calls or third-party libraries; it may also have no effect on [platform layer] (@ref platform) implementations. */ /** diff --git a/lib/include/aws_iot_logging_setup.h b/lib/include/aws_iot_logging_setup.h index 93d6a190cc..a0d7d75871 100644 --- a/lib/include/aws_iot_logging_setup.h +++ b/lib/include/aws_iot_logging_setup.h @@ -111,7 +111,7 @@ * @see @ref logging_function_genericprintbuffer for the generic (not library-specific) * buffer logging function. * - * **Example** + * Example * @code{c} * const uint8_t pBuffer[] = { 0x00, 0x01, 0x02, 0x03 }; * diff --git a/lib/include/iot_common.h b/lib/include/iot_common.h new file mode 100644 index 0000000000..251c28edc2 --- /dev/null +++ b/lib/include/iot_common.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_common.h + * @brief Provides function signatures for intialization and cleanup of common + * libraries. + */ + +#ifndef _IOT_COMMON_H_ +#define _IOT_COMMON_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/** + * @brief One-time initialization function for all common libraries. + * + * This function initializes common libraries, such as static memory and task + * pool. It must be called once (and only once) before calling any other + * function in this SDK. Calling this function more than once without first + * calling `IotCommon_Cleanup` may result in a crash. + * + * @return `true` if initialization succeeded; `false` otherwise. Logs may be + * printed in case of failure. + * + * @warning No thread-safety guarantees are provided for this function. + */ +bool IotCommon_Init( void ); + +/** + * @brief One-time deinitialization function for all common libraries. + * + * This function frees resources taken in `IotCommon_Init`. No other function + * in this SDK may be called after calling this function unless `IotCommon_Init` + * is called again. + * + * @warning No thread-safety guarantees are provided for this function. + */ +void IotCommon_Cleanup( void ); + +#endif diff --git a/lib/include/platform/iot_clock.h b/lib/include/platform/iot_clock.h index c7938fc958..e580029f19 100644 --- a/lib/include/platform/iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -38,7 +38,7 @@ #include /* Platform layer types include. */ -#include "platform/types/iot_platform_types.h" +#include "types/iot_platform_types.h" /** * @functionspage{platform_clock,platform clock component,Clock} diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index c259445160..ca7d042fec 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -37,7 +37,7 @@ #include /* Platform layer types include. */ -#include "platform/types/iot_platform_types.h" +#include "types/iot_platform_types.h" /** * @functionspage{platform_threads,platform thread management,Thread Management} diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index ef96d66408..7d26328200 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -37,7 +37,7 @@ #include "iot_linear_containers.h" /* Platform layer types include. */ -#include "platform/types/iot_platform_types.h" +#include "types/iot_platform_types.h" /* MQTT include. */ #include "aws_iot_mqtt.h" @@ -78,8 +78,8 @@ * Provide default values for undefined memory allocation functions based on * the usage of dynamic memory allocation. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 - #include "platform/aws_iot_static_memory.h" +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" /** * @brief Allocate an #_mqttConnection_t. This function should have the same @@ -87,7 +87,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotMqtt_MallocConnection - #define AwsIotMqtt_MallocConnection AwsIot_MallocMqttConnection + #define AwsIotMqtt_MallocConnection Iot_MallocMqttConnection #endif /** @@ -96,7 +96,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotMqtt_FreeConnection - #define AwsIotMqtt_FreeConnection AwsIot_FreeMqttConnection + #define AwsIotMqtt_FreeConnection Iot_FreeMqttConnection #endif /** @@ -105,7 +105,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotMqtt_MallocMessage - #define AwsIotMqtt_MallocMessage AwsIot_MallocMessageBuffer + #define AwsIotMqtt_MallocMessage Iot_MallocMessageBuffer #endif /** @@ -114,7 +114,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotMqtt_FreeMessage - #define AwsIotMqtt_FreeMessage AwsIot_FreeMessageBuffer + #define AwsIotMqtt_FreeMessage Iot_FreeMessageBuffer #endif /** @@ -123,7 +123,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotMqtt_MallocOperation - #define AwsIotMqtt_MallocOperation AwsIot_MallocMqttOperation + #define AwsIotMqtt_MallocOperation Iot_MallocMqttOperation #endif /** @@ -132,7 +132,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotMqtt_FreeOperation - #define AwsIotMqtt_FreeOperation AwsIot_FreeMqttOperation + #define AwsIotMqtt_FreeOperation Iot_FreeMqttOperation #endif /** @@ -141,7 +141,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotMqtt_MallocSubscription - #define AwsIotMqtt_MallocSubscription AwsIot_MallocMqttSubscription + #define AwsIotMqtt_MallocSubscription Iot_MallocMqttSubscription #endif /** @@ -150,7 +150,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotMqtt_FreeSubscription - #define AwsIotMqtt_FreeSubscription AwsIot_FreeMqttSubscription + #define AwsIotMqtt_FreeSubscription Iot_FreeMqttSubscription #endif /** @@ -159,7 +159,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotMqtt_MallocTimerEvent - #define AwsIotMqtt_MallocTimerEvent AwsIot_MallocMqttTimerEvent + #define AwsIotMqtt_MallocTimerEvent Iot_MallocMqttTimerEvent #endif /** @@ -168,9 +168,9 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotMqtt_FreeTimerEvent - #define AwsIotMqtt_FreeTimerEvent AwsIot_FreeMqttTimerEvent + #define AwsIotMqtt_FreeTimerEvent Iot_FreeMqttTimerEvent #endif -#else /* if AWS_IOT_STATIC_MEMORY_ONLY */ +#else /* if IOT_STATIC_MEMORY_ONLY */ #include #ifndef AwsIotMqtt_MallocConnection @@ -212,7 +212,7 @@ #ifndef AwsIotMqtt_FreeTimerEvent #define AwsIotMqtt_FreeTimerEvent free #endif -#endif /* if AWS_IOT_STATIC_MEMORY_ONLY */ +#endif /* if IOT_STATIC_MEMORY_ONLY */ /** * @cond DOXYGEN_IGNORE diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index f7d7f8955a..622f5c841b 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -37,7 +37,7 @@ #include "iot_linear_containers.h" /* Platform layer types include. */ -#include "platform/types/iot_platform_types.h" +#include "types/iot_platform_types.h" /* Shadow include. */ #include "aws_iot_shadow.h" @@ -78,8 +78,8 @@ * Provide default values for undefined memory allocation functions based on * the usage of dynamic memory allocation. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 - #include "platform/aws_iot_static_memory.h" +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" /** * @brief Allocate a #_shadowOperation_t. This function should have the same @@ -105,7 +105,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotShadow_MallocString - #define AwsIotShadow_MallocString AwsIot_MallocMessageBuffer + #define AwsIotShadow_MallocString Iot_MallocMessageBuffer #endif /** @@ -114,7 +114,7 @@ * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotShadow_FreeString - #define AwsIotShadow_FreeString AwsIot_FreeMessageBuffer + #define AwsIotShadow_FreeString Iot_FreeMessageBuffer #endif /** @@ -134,7 +134,7 @@ #ifndef AwsIotShadow_FreeSubscription #define AwsIotShadow_FreeSubscription AwsIot_FreeShadowSubscription #endif -#else /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ #include #ifndef AwsIotShadow_MallocOperation @@ -160,7 +160,7 @@ #ifndef AwsIotShadow_FreeSubscription #define AwsIotShadow_FreeSubscription free #endif -#endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /** * @cond DOXYGEN_IGNORE diff --git a/lib/include/platform/aws_iot_static_memory.h b/lib/include/private/iot_static_memory.h similarity index 52% rename from lib/include/platform/aws_iot_static_memory.h rename to lib/include/private/iot_static_memory.h index dbcc720d60..8feba84898 100644 --- a/lib/include/platform/aws_iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -20,9 +20,9 @@ */ /** - * @file aws_iot_static_memory.h + * @file iot_static_memory.h * @brief Functions for managing static buffers. Only used when - * @ref AWS_IOT_STATIC_MEMORY_ONLY is `1`. + * @ref IOT_STATIC_MEMORY_ONLY is `1`. */ /* Build using a config header, if provided. */ @@ -31,58 +31,107 @@ #endif /* The functions in this file should only exist in static memory only mode, hence - * the check for AWS_IOT_STATIC_MEMORY_ONLY in the double inclusion guard. */ -#if !defined( _AWS_IOT_STATIC_MEMORY_H_ ) && ( AWS_IOT_STATIC_MEMORY_ONLY == 1 ) -#define _AWS_IOT_STATIC_MEMORY_H_ + * the check for IOT_STATIC_MEMORY_ONLY in the double inclusion guard. */ +#if !defined( _IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) +#define _IOT_STATIC_MEMORY_H_ /* Standard includes. */ #include #include /** - * @functionspage{platform_static_memory,platform static memory component,Static Memory} - * - @functionname{platform_static_memory_function_messagebuffersize} - * - @functionname{platform_static_memory_function_mallocmessagebuffer} - * - @functionname{platform_static_memory_function_freemessagebuffer} - * - @functionname{platform_static_memory_function_mallocmqttconnection} - * - @functionname{platform_static_memory_function_freemqttconnection} - * - @functionname{platform_static_memory_function_mallocmqttoperation} - * - @functionname{platform_static_memory_function_freemqttoperation} - * - @functionname{platform_static_memory_function_mallocmqtttimerevent} - * - @functionname{platform_static_memory_function_freemqtttimerevent} - * - @functionname{platform_static_memory_function_mallocmqttsubscription} - * - @functionname{platform_static_memory_function_freemqttsubscription} - * - @functionname{platform_static_memory_function_mallocshadowoperation} - * - @functionname{platform_static_memory_function_freeshadowoperation} - * - @functionname{platform_static_memory_function_mallocshadowsubscription} - * - @functionname{platform_static_memory_function_freeshadowsubscription} + * @functionspage{static_memory,static memory component} + * - @functionname{static_memory_function_init} + * - @functionname{static_memory_function_cleanup} + * - @functionname{static_memory_function_messagebuffersize} + * - @functionname{static_memory_function_mallocmessagebuffer} + * - @functionname{static_memory_function_freemessagebuffer} + * - @functionname{static_memory_function_mallocmqttconnection} + * - @functionname{static_memory_function_freemqttconnection} + * - @functionname{static_memory_function_mallocmqttoperation} + * - @functionname{static_memory_function_freemqttoperation} + * - @functionname{static_memory_function_mallocmqtttimerevent} + * - @functionname{static_memory_function_freemqtttimerevent} + * - @functionname{static_memory_function_mallocmqttsubscription} + * - @functionname{static_memory_function_freemqttsubscription} + * - @functionname{static_memory_function_mallocshadowoperation} + * - @functionname{static_memory_function_freeshadowoperation} + * - @functionname{static_memory_function_mallocshadowsubscription} + * - @functionname{static_memory_function_freeshadowsubscription} */ +/*----------------------- Initialization and cleanup ------------------------*/ + +/** + * @functionpage{IotStaticMemory_Init,static_memory,init} + * @functionpage{IotStaticMemory_Cleanup,static_memory,cleanup} + */ + +/** + * @brief One-time initialization function for static memory. + * + * This function performs internal setup of static memory. It must be called + * once (and only once) before calling any other static memory function. + * Calling this function more than once without first calling + * @ref static_memory_function_cleanup may result in a crash. + * + * @return `true` if initialization succeeded; `false` otherwise. + * + * @attention This function is called by `IotCommon_Init` and does not need to be + * called by itself. + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see static_memory_function_cleanup + */ +/* @[declare_static_memory_init] */ +bool IotStaticMemory_Init( void ); +/* @[declare_static_memory_init] */ + +/** + * @brief One-time deinitialization function for static memory. + * + * This function frees resources taken in @ref static_memory_function_init. + * It should be called after to clean up static memory. After this function + * returns, @ref static_memory_function_init must be called again before + * calling any other static memory function. + * + * @attention This function is called by `IotCommon_Cleanup` and does not need + * to be called by itself. + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see static_memory_function_init + */ +/* @[declare_static_memory_cleanup] */ +void IotStaticMemory_Cleanup( void ); +/* @[declare_static_memory_cleanup] */ + /*------------------------ Message buffer management ------------------------*/ /** - * @functionpage{AwsIot_MessageBufferSize,platform_static_memory,messagebuffersize} - * @functionpage{AwsIot_MallocMessageBuffer,platform_static_memory,mallocmessagebuffer} - * @functionpage{AwsIot_FreeMessageBuffer,platform_static_memory,freemessagebuffer} + * @functionpage{Iot_MessageBufferSize,static_memory,messagebuffersize} + * @functionpage{Iot_MallocMessageBuffer,static_memory,mallocmessagebuffer} + * @functionpage{Iot_FreeMessageBuffer,static_memory,freemessagebuffer} */ /** * @brief Get the fixed size of a [message buffer] - * (@ref platform_static_memory_types_messagebuffers). + * (@ref static_memory_types_messagebuffers). * * The size of the message buffers are known at compile time, but it is a [constant] - * (@ref AWS_IOT_MESSAGE_BUFFER_SIZE) that may not be visible to all source files. + * (@ref IOT_MESSAGE_BUFFER_SIZE) that may not be visible to all source files. * This function allows other source files to know the size of a message buffer. * * @return The size, in bytes, of a single message buffer. */ -/* @[declare_platform_static_memory_messagebuffersize] */ -size_t AwsIot_MessageBufferSize( void ); -/* @[declare_platform_static_memory_messagebuffersize] */ +/* @[declare_static_memory_messagebuffersize] */ +size_t Iot_MessageBufferSize( void ); +/* @[declare_static_memory_messagebuffersize] */ /** * @brief Get an empty [message buffer] - * (@ref platform_static_memory_types_messagebuffers). + * (@ref static_memory_types_messagebuffers). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -91,16 +140,16 @@ size_t AwsIot_MessageBufferSize( void ); * @param[in] size Requested size for a message buffer. * * @return Pointer to the start of a message buffer. If the `size` argument is larger - * than the [fixed size of a message buffer](@ref AWS_IOT_MESSAGE_BUFFER_SIZE) + * than the [fixed size of a message buffer](@ref IOT_MESSAGE_BUFFER_SIZE) * or no message buffers are available, `NULL` is returned. */ -/* @[declare_platform_static_memory_mallocmessagebuffer] */ -void * AwsIot_MallocMessageBuffer( size_t size ); -/* @[declare_platform_static_memory_mallocmessagebuffer] */ +/* @[declare_static_memory_mallocmessagebuffer] */ +void * Iot_MallocMessageBuffer( size_t size ); +/* @[declare_static_memory_mallocmessagebuffer] */ /** * @brief Free an in-use [message buffer] - * (@ref platform_static_memory_types_messagebuffers). + * (@ref static_memory_types_messagebuffers). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -108,20 +157,20 @@ void * AwsIot_MallocMessageBuffer( size_t size ); * * @param[in] ptr Pointer to the message buffer to free. */ -/* @[declare_platform_static_memory_freemessagebuffer] */ -void AwsIot_FreeMessageBuffer( void * ptr ); -/* @[declare_platform_static_memory_freemessagebuffer] */ +/* @[declare_static_memory_freemessagebuffer] */ +void Iot_FreeMessageBuffer( void * ptr ); +/* @[declare_static_memory_freemessagebuffer] */ /*----------------------- MQTT connection management ------------------------*/ /** - * @functionpage{AwsIot_MallocMqttConnection,platform_static_memory,mallocmqttconnection} - * @functionpage{AwsIot_FreeMqttConnection,platform_static_memory,freemqttconnection} + * @functionpage{Iot_MallocMqttConnection,static_memory,mallocmqttconnection} + * @functionpage{Iot_FreeMqttConnection,static_memory,freemqttconnection} */ /** * @brief Allocates memory to hold data for a new [MQTT connection] - * (@ref platform_static_memory_types_mqttconnections). + * (@ref static_memory_types_mqttconnections). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -131,13 +180,13 @@ void AwsIot_FreeMessageBuffer( void * ptr ); * * @return Pointer to an MQTT connection. */ -/* @[declare_platform_static_memory_mallocmqttconnection] */ -void * AwsIot_MallocMqttConnection( size_t size ); -/* @[declare_platform_static_memory_mallocmqttconnection] */ +/* @[declare_static_memory_mallocmqttconnection] */ +void * Iot_MallocMqttConnection( size_t size ); +/* @[declare_static_memory_mallocmqttconnection] */ /** * @brief Frees an in-use [MQTT connection] - * (@ref platform_static_memory_types_mqttconnections). + * (@ref static_memory_types_mqttconnections). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -145,20 +194,20 @@ void * AwsIot_MallocMqttConnection( size_t size ); * * @param[in] ptr Pointer to an active MQTT connection to free. */ -/* @[declare_platform_static_memory_freemqttconnection] */ -void AwsIot_FreeMqttConnection( void * ptr ); -/* @[declare_platform_static_memory_freemqttconnection] */ +/* @[declare_static_memory_freemqttconnection] */ +void Iot_FreeMqttConnection( void * ptr ); +/* @[declare_static_memory_freemqttconnection] */ /*------------------------ MQTT operation management ------------------------*/ /** - * @functionpage{AwsIot_MallocMqttOperation,platform_static_memory,mallocmqttoperation} - * @functionpage{AwsIot_FreeMqttOperation,platform_static_memory,freemqttoperation} + * @functionpage{Iot_MallocMqttOperation,static_memory,mallocmqttoperation} + * @functionpage{Iot_FreeMqttOperation,static_memory,freemqttoperation} */ /** * @brief Allocates memory to hold data for a new [MQTT operation] - * (@ref platform_static_memory_types_mqttoperations). + * (@ref static_memory_types_mqttoperations). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -168,13 +217,13 @@ void AwsIot_FreeMqttConnection( void * ptr ); * * @return Pointer to an MQTT operation. */ -/* @[declare_platform_static_memory_mallocmqttoperation] */ -void * AwsIot_MallocMqttOperation( size_t size ); -/* @[declare_platform_static_memory_mallocmqttoperation] */ +/* @[declare_static_memory_mallocmqttoperation] */ +void * Iot_MallocMqttOperation( size_t size ); +/* @[declare_static_memory_mallocmqttoperation] */ /** * @brief Frees an in-use [MQTT operation] - * (@ref platform_static_memory_types_mqttoperations). + * (@ref static_memory_types_mqttoperations). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -182,20 +231,20 @@ void * AwsIot_MallocMqttOperation( size_t size ); * * @param[in] ptr Pointer to an active MQTT operation to free. */ -/* @[declare_platform_static_memory_freemqttoperation] */ -void AwsIot_FreeMqttOperation( void * ptr ); -/* @[declare_platform_static_memory_freemqttoperation] */ +/* @[declare_static_memory_freemqttoperation] */ +void Iot_FreeMqttOperation( void * ptr ); +/* @[declare_static_memory_freemqttoperation] */ /*----------------------- MQTT timer event management -----------------------*/ /** - * @functionpage{AwsIot_MallocMqttTimerEvent,platform_static_memory,mallocmqtttimerevent} - * @functionpage{AwsIot_FreeMqttTimerEvent,platform_static_memory,freemqtttimerevent} + * @functionpage{Iot_MallocMqttTimerEvent,static_memory,mallocmqtttimerevent} + * @functionpage{Iot_FreeMqttTimerEvent,static_memory,freemqtttimerevent} */ /** * @brief Allocates memory to hold data for a new [MQTT timer event] - * (@ref platform_static_memory_types_mqtttimerevents). + * (@ref static_memory_types_mqtttimerevents). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) for MQTT timer events. @@ -204,13 +253,13 @@ void AwsIot_FreeMqttOperation( void * ptr ); * * @return Pointer to an MQTT timer event. */ -/* @[declare_platform_static_memory_mallocmqtttimerevent] */ -void * AwsIot_MallocMqttTimerEvent( size_t size ); -/* @[declare_platform_static_memory_mallocmqtttimerevent] */ +/* @[declare_static_memory_mallocmqtttimerevent] */ +void * Iot_MallocMqttTimerEvent( size_t size ); +/* @[declare_static_memory_mallocmqtttimerevent] */ /** * @brief Frees an in-use [MQTT timer event] - * (@ref platform_static_memory_types_mqtttimerevents). + * (@ref static_memory_types_mqtttimerevents). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -218,20 +267,20 @@ void * AwsIot_MallocMqttTimerEvent( size_t size ); * * @param[in] ptr Pointer to an active MQTT timer event to free. */ -/* @[declare_platform_static_memory_freemqtttimerevent] */ -void AwsIot_FreeMqttTimerEvent( void * ptr ); -/* @[declare_platform_static_memory_freemqtttimerevent] */ +/* @[declare_static_memory_freemqtttimerevent] */ +void Iot_FreeMqttTimerEvent( void * ptr ); +/* @[declare_static_memory_freemqtttimerevent] */ /*---------------------- MQTT subscription management -----------------------*/ /** - * @functionpage{AwsIot_MallocMqttSubscription,platform_static_memory,mallocmqttsubscription} - * @functionpage{AwsIot_FreeMqttSubscription,platform_static_memory,freemqttsubscription} + * @functionpage{Iot_MallocMqttSubscription,static_memory,mallocmqttsubscription} + * @functionpage{Iot_FreeMqttSubscription,static_memory,freemqttsubscription} */ /** * @brief Allocates memory to hold data for a new [MQTT subscription] - * (@ref platform_static_memory_types_mqttsubscriptions). + * (@ref static_memory_types_mqttsubscriptions). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -246,13 +295,13 @@ void AwsIot_FreeMqttTimerEvent( void * ptr ); * the fixed size of an MQTT subscription object or no free MQTT subscriptions * are available, `NULL` is returned. */ -/* @[declare_platform_static_memory_mallocmqttsubscription] */ -void * AwsIot_MallocMqttSubscription( size_t size ); -/* @[declare_platform_static_memory_mallocmqttsubscription] */ +/* @[declare_static_memory_mallocmqttsubscription] */ +void * Iot_MallocMqttSubscription( size_t size ); +/* @[declare_static_memory_mallocmqttsubscription] */ /** * @brief Frees an in-use [MQTT subscription] - * (@ref platform_static_memory_types_mqttsubscriptions). + * (@ref static_memory_types_mqttsubscriptions). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -260,20 +309,20 @@ void * AwsIot_MallocMqttSubscription( size_t size ); * * @param[in] ptr Pointer to an active MQTT subscription to free. */ -/* @[declare_platform_static_memory_freemqttsubscription] */ -void AwsIot_FreeMqttSubscription( void * ptr ); -/* @[declare_platform_static_memory_freemqttsubscription] */ +/* @[declare_static_memory_freemqttsubscription] */ +void Iot_FreeMqttSubscription( void * ptr ); +/* @[declare_static_memory_freemqttsubscription] */ /*---------------------- Shadow operation management ------------------------*/ /** - * @functionpage{AwsIot_MallocShadowOperation,platform_static_memory,mallocshadowoperation} - * @functionpage{AwsIot_FreeShadowOperation,platform_static_memory,freeshadowoperation} + * @functionpage{AwsIot_MallocShadowOperation,static_memory,mallocshadowoperation} + * @functionpage{AwsIot_FreeShadowOperation,static_memory,freeshadowoperation} */ /** * @brief Allocates memory to hold data for a new [Shadow operation] - * (@ref platform_static_memory_types_shadowoperations). + * (@ref static_memory_types_shadowoperations). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -283,13 +332,13 @@ void AwsIot_FreeMqttSubscription( void * ptr ); * * @return Pointer to a Shadow operation. */ -/* @[declare_platform_static_memory_mallocshadowoperation] */ +/* @[declare_static_memory_mallocshadowoperation] */ void * AwsIot_MallocShadowOperation( size_t size ); -/* @[declare_platform_static_memory_mallocshadowoperation] */ +/* @[declare_static_memory_mallocshadowoperation] */ /** * @brief Frees an in-use [Shadow operation] - * (@ref platform_static_memory_types_shadowoperations). + * (@ref static_memory_types_shadowoperations). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -297,20 +346,20 @@ void * AwsIot_MallocShadowOperation( size_t size ); * * @param[in] ptr Pointer to an active Shadow operation to free. */ -/* @[declare_platform_static_memory_freeshadowoperation] */ +/* @[declare_static_memory_freeshadowoperation] */ void AwsIot_FreeShadowOperation( void * ptr ); -/* @[declare_platform_static_memory_freeshadowoperation] */ +/* @[declare_static_memory_freeshadowoperation] */ /*--------------------- Shadow subscription management ----------------------*/ /** - * @functionpage{AwsIot_MallocShadowSubscription,platform_static_memory,mallocshadowsubscription} - * @functionpage{AwsIot_FreeShadowSubscription,platform_static_memory,freeshadowsubscription} + * @functionpage{AwsIot_MallocShadowSubscription,static_memory,mallocshadowsubscription} + * @functionpage{AwsIot_FreeShadowSubscription,static_memory,freeshadowsubscription} */ /** * @brief Allocates memory to hold data for a new [Shadow subscription] - * (@ref platform_static_memory_types_shadowsubscriptions). + * (@ref static_memory_types_shadowsubscriptions). * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -325,13 +374,13 @@ void AwsIot_FreeShadowOperation( void * ptr ); * the fixed size of a Shadow subscription object or no free Shadow subscriptions * are available, `NULL` is returned. */ -/* @[declare_platform_static_memory_mallocshadowsubscription] */ +/* @[declare_static_memory_mallocshadowsubscription] */ void * AwsIot_MallocShadowSubscription( size_t size ); -/* @[declare_platform_static_memory_mallocshadowsubscription] */ +/* @[declare_static_memory_mallocshadowsubscription] */ /** * @brief Frees an in-use [Shadow subscription] - * (@ref platform_static_memory_types_shadowsubscriptions). + * (@ref static_memory_types_shadowsubscriptions). * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -339,8 +388,8 @@ void * AwsIot_MallocShadowSubscription( size_t size ); * * @param[in] ptr Pointer to an active Shadow subscription to free. */ -/* @[declare_platform_static_memory_freeshadowsubscription] */ +/* @[declare_static_memory_freeshadowsubscription] */ void AwsIot_FreeShadowSubscription( void * ptr ); -/* @[declare_platform_static_memory_freeshadowsubscription] */ +/* @[declare_static_memory_freeshadowsubscription] */ -#endif /* if !defined( _AWS_IOT_STATIC_MEMORY_H_ ) && ( AWS_IOT_STATIC_MEMORY_ONLY == 1 ) */ +#endif /* if !defined( _IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/include/platform/types/iot_platform_types.h b/lib/include/types/iot_platform_types.h similarity index 100% rename from lib/include/platform/types/iot_platform_types.h rename to lib/include/types/iot_platform_types.h diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 704e047d62..6c53250e10 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,23 +1,19 @@ # Common libraries source files. -add_library( awsiotcommon SHARED +add_library( iotcommon SHARED + iot_common.c aws_iot_json_utils.c - aws_iot_logging.c ) + aws_iot_logging.c + static_memory/iot_static_memory_common.c + static_memory/iot_static_memory_mqtt.c + static_memory/aws_iot_static_memory_shadow.c ) # Library version. -set_target_properties( awsiotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) - -# Library public headers. -set_target_properties( awsiotcommon PROPERTIES PUBLIC_HEADER - "../../include/aws_iot_json_utils.h;../../include/aws_iot_logging_setup.h" ) - -# Library private headers. -set_target_properties( awsiotcommon PROPERTIES PRIVATE_HEADER - "../../include/private/aws_iot_logging.h" ) +set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( awsiotcommon awsiotplatform ) +target_link_libraries( iotcommon iotplatform ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotcommon unity ) + target_link_libraries( iotcommon unity ) endif() diff --git a/lib/source/common/aws_iot_logging.c b/lib/source/common/aws_iot_logging.c index c2385fe77d..461db46602 100644 --- a/lib/source/common/aws_iot_logging.c +++ b/lib/source/common/aws_iot_logging.c @@ -103,16 +103,16 @@ extern int vsnprintf( char *, * Provide default values for undefined memory allocation functions based on * the usage of dynamic memory allocation. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 +#if IOT_STATIC_MEMORY_ONLY == 1 /* Static memory allocation header. */ - #include "platform/aws_iot_static_memory.h" + #include "private/iot_static_memory.h" /** * @brief Allocate a new logging buffer. This function must have the same * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #ifndef AwsIotLogging_Malloc - #define AwsIotLogging_Malloc AwsIot_MallocMessageBuffer + #define AwsIotLogging_Malloc Iot_MallocMessageBuffer #endif /** @@ -120,7 +120,7 @@ extern int vsnprintf( char *, * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #ifndef AwsIotLogging_Free - #define AwsIotLogging_Free AwsIot_FreeMessageBuffer + #define AwsIotLogging_Free Iot_FreeMessageBuffer #endif /** @@ -128,9 +128,9 @@ extern int vsnprintf( char *, * should all have the same size. */ #ifndef AwsIotLogging_StaticBufferSize - #define AwsIotLogging_StaticBufferSize AwsIot_MessageBufferSize + #define AwsIotLogging_StaticBufferSize Iot_MessageBufferSize #endif -#else /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ #ifndef AwsIotLogging_Malloc #include #define AwsIotLogging_Malloc malloc @@ -140,7 +140,7 @@ extern int vsnprintf( char *, #include #define AwsIotLogging_Free free #endif -#endif /* if AWS_IOT_STATIC_MEMORY_ONLY */ +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /** * @brief A guess of the maximum length of a timestring. @@ -184,7 +184,7 @@ static const char * const _pLogLevelStrings[ 5 ] = /*-----------------------------------------------------------*/ -#if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) +#if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) static bool _reallocLoggingBuffer( void ** pOldBuffer, size_t newSize, size_t oldSize ) @@ -207,7 +207,7 @@ static const char * const _pLogLevelStrings[ 5 ] = return true; } -#endif /* if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) */ +#endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ /*-----------------------------------------------------------*/ @@ -255,7 +255,7 @@ void AwsIotLogGeneric( int libraryLogSetting, /* In static memory mode, check that the log message will fit in the a * static buffer. */ - #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #if IOT_STATIC_MEMORY_ONLY == 1 if( bufferSize >= AwsIotLogging_StaticBufferSize() ) { /* If the static buffers are likely too small to fit the log message, @@ -370,7 +370,7 @@ void AwsIotLogGeneric( int libraryLogSetting, * a larger logging buffer. */ if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition ) { - #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #if IOT_STATIC_MEMORY_ONLY == 1 /* There's no point trying to allocate a larger static buffer. Return * immediately. */ @@ -399,7 +399,7 @@ void AwsIotLogGeneric( int libraryLogSetting, pFormat, args ); va_end( args ); - #endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ + #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ } /* Check for encoding errors. */ diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c new file mode 100644 index 0000000000..58cbb7827b --- /dev/null +++ b/lib/source/common/iot_common.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Common include. */ +#include "iot_common.h" + +/* Static memory include (if dynamic memory allocation is disabled). */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" +#endif + +/* Configure logs for the functions in this file. */ +#ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL +#else + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE +#endif + +#define _LIBRARY_LOG_NAME ( "COMMON" ) +#include "aws_iot_logging_setup.h" + +/*-----------------------------------------------------------*/ + +bool IotCommon_Init( void ) +{ + bool status = true; + + /* Initialize static memory if dynamic memory allocation is disabled. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + status = IotStaticMemory_Init(); + + if( status == false ) + { + AwsIotLogError( "Failed to initialize static memory." ); + } + #endif + + if( status == true ) + { + AwsIotLogInfo( "Common libraries successfully initialized." ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotCommon_Cleanup( void ) +{ + /* Cleanup static memory if dynamic memory allocation is disabled. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + IotStaticMemory_Cleanup(); + #endif + + AwsIotLogInfo( "Common libraries cleanup done." ); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c similarity index 73% rename from lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c rename to lib/source/common/static_memory/aws_iot_static_memory_shadow.c index bebbc7dbac..d7f7b5d2bd 100644 --- a/lib/source/platform/posix/static_memory/aws_iot_static_memory_shadow_posix.c +++ b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c @@ -20,9 +20,8 @@ */ /** - * @file aws_iot_static_memory_shadow_posix.c - * @brief Implementation of Shadow static memory functions in aws_iot_static_memory.h - * for POSIX systems. + * @file aws_iot_static_memory_shadow.c + * @brief Implementation of Shadow static memory functions in iot_static_memory.h */ /* Build using a config header, if provided. */ @@ -31,7 +30,7 @@ #endif /* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 +#if IOT_STATIC_MEMORY_ONLY == 1 /* Standard includes. */ #include @@ -46,7 +45,7 @@ #endif /* Static memory include. */ -#include "platform/aws_iot_static_memory.h" +#include "private/iot_static_memory.h" /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" @@ -86,16 +85,16 @@ /*-----------------------------------------------------------*/ -/* Extern declarations of common static memory functions in aws_iot_static_memory_common_posix.c - * Because these functions are POSIX-platform-specific, they are not placed in - * a platform header file. */ -extern int AwsIotStaticMemory_FindFree( bool * const pInUse, - int limit ); -extern void AwsIotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); +/* Extern declarations of common static memory functions in iot_static_memory_common.c + * Because these functions are specific to this static memory implementation, they are + * not placed in the common static memory header file. */ +extern int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); +extern void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); /*-----------------------------------------------------------*/ @@ -119,8 +118,8 @@ void * AwsIot_MallocShadowOperation( size_t size ) if( size == sizeof( _shadowOperation_t ) ) { /* Find a free Shadow operation. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseShadowOperations, - AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ); + freeIndex = IotStaticMemory_FindFree( _pInUseShadowOperations, + AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ); if( freeIndex != -1 ) { @@ -136,11 +135,11 @@ void * AwsIot_MallocShadowOperation( size_t size ) void AwsIot_FreeShadowOperation( void * ptr ) { /* Return the in-use Shadow operation. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pShadowOperations, - _pInUseShadowOperations, - AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _shadowOperation_t ) ); + IotStaticMemory_ReturnInUse( ptr, + _pShadowOperations, + _pInUseShadowOperations, + AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _shadowOperation_t ) ); } /*-----------------------------------------------------------*/ @@ -153,8 +152,8 @@ void * AwsIot_MallocShadowSubscription( size_t size ) if( size <= _SHADOW_SUBSCRIPTION_SIZE ) { /* Get the index of a free Shadow subscription. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseShadowSubscriptions, - AWS_IOT_SHADOW_SUBSCRIPTIONS ); + freeIndex = IotStaticMemory_FindFree( _pInUseShadowSubscriptions, + AWS_IOT_SHADOW_SUBSCRIPTIONS ); if( freeIndex != -1 ) { @@ -170,11 +169,11 @@ void * AwsIot_MallocShadowSubscription( size_t size ) void AwsIot_FreeShadowSubscription( void * ptr ) { /* Return the in-use Shadow subscription. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pShadowSubscriptions, - _pInUseShadowSubscriptions, - AWS_IOT_SHADOW_SUBSCRIPTIONS, - _SHADOW_SUBSCRIPTION_SIZE ); + IotStaticMemory_ReturnInUse( ptr, + _pShadowSubscriptions, + _pInUseShadowSubscriptions, + AWS_IOT_SHADOW_SUBSCRIPTIONS, + _SHADOW_SUBSCRIPTION_SIZE ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c b/lib/source/common/static_memory/iot_static_memory_common.c similarity index 61% rename from lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c rename to lib/source/common/static_memory/iot_static_memory_common.c index 79dc8329ee..0a48c912ce 100644 --- a/lib/source/platform/posix/static_memory/aws_iot_static_memory_common_posix.c +++ b/lib/source/common/static_memory/iot_static_memory_common.c @@ -20,9 +20,8 @@ */ /** - * @file aws_iot_static_memory_common_posix.c - * @brief Implementation of common static memory functions in aws_iot_static_memory.h - * for POSIX systems. + * @file iot_static_memory_common.c + * @brief Implementation of common static memory functions in iot_static_memory.h */ /* Build using a config header, if provided. */ @@ -31,7 +30,7 @@ #endif /* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 +#if IOT_STATIC_MEMORY_ONLY == 1 /* Standard includes. */ #include @@ -39,15 +38,11 @@ #include #include -/* POSIX include. Allow it to be overridden. */ -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif +/* Platform layer includes. */ +#include "platform/iot_threads.h" /* Static memory include. */ -#include "platform/aws_iot_static_memory.h" +#include "private/iot_static_memory.h" /*-----------------------------------------------------------*/ @@ -57,20 +52,20 @@ * * Provide default values for undefined configuration constants. */ -#ifndef AWS_IOT_MESSAGE_BUFFERS - #define AWS_IOT_MESSAGE_BUFFERS ( 8 ) +#ifndef IOT_MESSAGE_BUFFERS + #define IOT_MESSAGE_BUFFERS ( 8 ) #endif -#ifndef AWS_IOT_MESSAGE_BUFFER_SIZE - #define AWS_IOT_MESSAGE_BUFFER_SIZE ( 1024 ) +#ifndef IOT_MESSAGE_BUFFER_SIZE + #define IOT_MESSAGE_BUFFER_SIZE ( 1024 ) #endif /** @endcond */ /* Validate static memory configuration settings. */ -#if AWS_IOT_MESSAGE_BUFFERS <= 0 - #error "AWS_IOT_MESSAGE_BUFFERS cannot be 0 or negative." +#if IOT_MESSAGE_BUFFERS <= 0 + #error "IOT_MESSAGE_BUFFERS cannot be 0 or negative." #endif -#if AWS_IOT_MESSAGE_BUFFER_SIZE <= 0 - #error "AWS_IOT_MESSAGE_BUFFER_SIZE cannot be 0 or negative." +#if IOT_MESSAGE_BUFFER_SIZE <= 0 + #error "IOT_MESSAGE_BUFFER_SIZE cannot be 0 or negative." #endif /*-----------------------------------------------------------*/ @@ -79,20 +74,20 @@ * @brief Find a free buffer using the "in-use" flags. * * If a free buffer is found, this function marks the buffer in-use. This function - * is common to the static memory implementation on POSIX systems. + * is common to the static memory implementation. * * @param[in] pInUse The "in-use" flags to search. * @param[in] limit How many flags to check. * * @return The index of a free buffer; -1 if no free buffers are available. */ -int AwsIotStaticMemory_FindFree( bool * const pInUse, - int limit ); +int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); /** * @brief Return an "in-use" buffer. * - * This function is common to the static memory implementation on POSIX systems. + * This function is common to the static memory implementation. * * @param[in] ptr Pointer to the buffer to return. * @param[in] pPool The pool of buffers that the in-use buffer was allocation from. @@ -100,37 +95,34 @@ int AwsIotStaticMemory_FindFree( bool * const pInUse, * @param[in] limit How many buffers (and flags) to check while searching for ptr. * @param[in] elementSize The size of a single element in pPool. */ -void AwsIotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); +void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); /*-----------------------------------------------------------*/ /** * @brief Guards access to critical sections. */ -static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER; +static IotMutex_t _mutex; /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseMessageBuffers[ AWS_IOT_MESSAGE_BUFFERS ] = { 0 }; /**< @brief Message buffer in-use flags. */ -static char _pMessageBuffers[ AWS_IOT_MESSAGE_BUFFERS ][ AWS_IOT_MESSAGE_BUFFER_SIZE ] = { { 0 } }; /**< @brief Message buffers. */ +static bool _pInUseMessageBuffers[ IOT_MESSAGE_BUFFERS ] = { 0 }; /**< @brief Message buffer in-use flags. */ +static char _pMessageBuffers[ IOT_MESSAGE_BUFFERS ][ IOT_MESSAGE_BUFFER_SIZE ] = { { 0 } }; /**< @brief Message buffers. */ /*-----------------------------------------------------------*/ -int AwsIotStaticMemory_FindFree( bool * const pInUse, - int limit ) +int IotStaticMemory_FindFree( bool * const pInUse, + int limit ) { int i = 0, freeIndex = -1; /* Perform the search for a free buffer in a critical section. */ - if( pthread_mutex_lock( &_mutex ) != 0 ) - { - return -1; - } + IotMutex_Lock( &( _mutex ) ); for( i = 0; i < limit; i++ ) { @@ -144,18 +136,18 @@ int AwsIotStaticMemory_FindFree( bool * const pInUse, } /* Exit the critical section. */ - ( void ) pthread_mutex_unlock( &_mutex ); + IotMutex_Unlock( &( _mutex ) ); return freeIndex; } /*-----------------------------------------------------------*/ -void AwsIotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ) +void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ) { int i = 0; uint8_t * element = NULL; @@ -165,10 +157,7 @@ void AwsIotStaticMemory_ReturnInUse( void * ptr, /* Perform a search for ptr to make sure it's part of pPool. This search * is done in a critical section. */ - if( pthread_mutex_lock( &_mutex ) != 0 ) - { - return; - } + IotMutex_Lock( &( _mutex ) ); for( i = 0; i < limit; i++ ) { @@ -185,29 +174,43 @@ void AwsIotStaticMemory_ReturnInUse( void * ptr, } /* Exit the critical section. */ - ( void ) pthread_mutex_unlock( &_mutex ); + IotMutex_Unlock( &( _mutex ) ); +} + +/*-----------------------------------------------------------*/ + +bool IotStaticMemory_Init( void ) +{ + return IotMutex_Create( &( _mutex ) ); +} + +/*-----------------------------------------------------------*/ + +void IotStaticMemory_Cleanup( void ) +{ + IotMutex_Destroy( &( _mutex ) ); } /*-----------------------------------------------------------*/ -size_t AwsIot_MessageBufferSize( void ) +size_t Iot_MessageBufferSize( void ) { - return ( size_t ) AWS_IOT_MESSAGE_BUFFER_SIZE; + return ( size_t ) IOT_MESSAGE_BUFFER_SIZE; } /*-----------------------------------------------------------*/ -void * AwsIot_MallocMessageBuffer( size_t size ) +void * Iot_MallocMessageBuffer( size_t size ) { int freeIndex = -1; void * pNewBuffer = NULL; /* Check that size is within the fixed message buffer size. */ - if( size <= AWS_IOT_MESSAGE_BUFFER_SIZE ) + if( size <= IOT_MESSAGE_BUFFER_SIZE ) { /* Get the index of a free message buffer. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseMessageBuffers, - AWS_IOT_MESSAGE_BUFFERS ); + freeIndex = IotStaticMemory_FindFree( _pInUseMessageBuffers, + IOT_MESSAGE_BUFFERS ); if( freeIndex != -1 ) { @@ -220,14 +223,14 @@ void * AwsIot_MallocMessageBuffer( size_t size ) /*-----------------------------------------------------------*/ -void AwsIot_FreeMessageBuffer( void * ptr ) +void Iot_FreeMessageBuffer( void * ptr ) { /* Return the in-use message buffer. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pMessageBuffers, - _pInUseMessageBuffers, - AWS_IOT_MESSAGE_BUFFERS, - AWS_IOT_MESSAGE_BUFFER_SIZE ); + IotStaticMemory_ReturnInUse( ptr, + _pMessageBuffers, + _pInUseMessageBuffers, + IOT_MESSAGE_BUFFERS, + IOT_MESSAGE_BUFFER_SIZE ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/common/static_memory/iot_static_memory_mqtt.c b/lib/source/common/static_memory/iot_static_memory_mqtt.c new file mode 100644 index 0000000000..4f2677e5cb --- /dev/null +++ b/lib/source/common/static_memory/iot_static_memory_mqtt.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_static_memory_mqtt.c + * @brief Implementation of MQTT static memory functions in iot_static_memory.h + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* POSIX include. Allow it to be overridden. */ +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + +/* Static memory include. */ +#include "private/iot_static_memory.h" + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef IOT_MQTT_CONNECTIONS + #define IOT_MQTT_CONNECTIONS ( 1 ) +#endif +#ifndef IOT_MQTT_SUBSCRIPTIONS + #define IOT_MQTT_SUBSCRIPTIONS ( 8 ) +#endif +#ifndef IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS + #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) +#endif +/** @endcond */ + +/* Validate static memory configuration settings. */ +#if IOT_MQTT_CONNECTIONS <= 0 + #error "IOT_MQTT_CONNECTIONS cannot be 0 or negative." +#endif +#if IOT_MQTT_SUBSCRIPTIONS <= 0 + #error "IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." +#endif +#if IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS <= 0 + #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." +#endif + +/** + * @brief The size of a static memory MQTT subscription. + * + * Since the pTopic member of #_mqttSubscription_t is variable-length, the constant + * #_AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of + * #_mqttSubscription_t.pTopicFilter. + */ +#define _MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in iot_static_memory_common.c + * Because these functions are specific to this static memory implementation, they are + * not placed in the common static memory header file. */ +extern int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); +extern void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ] = { 0 }; /**< @brief MQTT connection in-use flags. */ +static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ + +static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ +static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operations. */ + +static bool _pInUseMqttTimerEvents[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer event in-use flags. */ +static _mqttTimerEvent_t _pMqttTimerEvents[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer events. */ + +static bool _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ +static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ _MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ + +/*-----------------------------------------------------------*/ + +void * Iot_MallocMqttConnection( size_t size ) +{ + int freeIndex = -1; + void * pNewConnection = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttConnection_t ) ) + { + /* Find a free MQTT connection. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttConnections, + IOT_MQTT_CONNECTIONS ); + + if( freeIndex != -1 ) + { + pNewConnection = &( _pMqttConnections[ freeIndex ] ); + } + } + + return pNewConnection; +} + +/*-----------------------------------------------------------*/ + +void Iot_FreeMqttConnection( void * ptr ) +{ + /* Return the in-use MQTT connection. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttConnections, + _pInUseMqttConnections, + IOT_MQTT_CONNECTIONS, + sizeof( _mqttConnection_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * Iot_MallocMqttOperation( size_t size ) +{ + int freeIndex = -1; + void * pNewOperation = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttOperation_t ) ) + { + /* Find a free MQTT operation. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttOperations, + IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewOperation = &( _pMqttOperations[ freeIndex ] ); + } + } + + return pNewOperation; +} + +/*-----------------------------------------------------------*/ + +void Iot_FreeMqttOperation( void * ptr ) +{ + /* Return the in-use MQTT operation. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttOperations, + _pInUseMqttOperations, + IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _mqttOperation_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * Iot_MallocMqttTimerEvent( size_t size ) +{ + int freeIndex = -1; + void * pNewTimerEvent = NULL; + + /* Check size argument. */ + if( size == sizeof( _mqttTimerEvent_t ) ) + { + /* Find a free MQTT timer event. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttTimerEvents, + IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewTimerEvent = &( _pMqttTimerEvents[ freeIndex ] ); + } + } + + return pNewTimerEvent; +} + +/*-----------------------------------------------------------*/ + +void Iot_FreeMqttTimerEvent( void * ptr ) +{ + /* Return the in-use MQTT timer event. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttTimerEvents, + _pInUseMqttTimerEvents, + IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _mqttTimerEvent_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * Iot_MallocMqttSubscription( size_t size ) +{ + int freeIndex = -1; + void * pNewSubscription = NULL; + + if( size <= _MQTT_SUBSCRIPTION_SIZE ) + { + /* Get the index of a free MQTT subscription. */ + freeIndex = IotStaticMemory_FindFree( _pInUseMqttSubscriptions, + IOT_MQTT_SUBSCRIPTIONS ); + + if( freeIndex != -1 ) + { + pNewSubscription = &( _pMqttSubscriptions[ freeIndex ][ 0 ] ); + } + } + + return pNewSubscription; +} + +/*-----------------------------------------------------------*/ + +void Iot_FreeMqttSubscription( void * ptr ) +{ + /* Return the in-use MQTT subscription. */ + IotStaticMemory_ReturnInUse( ptr, + _pMqttSubscriptions, + _pInUseMqttSubscriptions, + IOT_MQTT_SUBSCRIPTIONS, + _MQTT_SUBSCRIPTION_SIZE ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index f5ea96e5c0..093def535a 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,5 +1,5 @@ # MQTT library source files. -add_library( awsiotmqtt SHARED +add_library( iotmqtt SHARED aws_iot_mqtt_api.c aws_iot_mqtt_operation.c aws_iot_mqtt_serialize.c @@ -7,20 +7,12 @@ add_library( awsiotmqtt SHARED aws_iot_mqtt_validate.c ) # Library version. -set_target_properties( awsiotmqtt PROPERTIES VERSION ${PROJECT_VERSION} ) - -# Library public headers. -set_target_properties( awsiotmqtt PROPERTIES PUBLIC_HEADER - "../../include/aws_iot_mqtt.h" ) - -# Library private headers. -set_target_properties( awsiotmqtt PROPERTIES PRIVATE_HEADER - "../../include/private/aws_iot_mqtt_internal.h" ) +set_target_properties( iotmqtt PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( awsiotmqtt awsiotcommon awsiotplatform ) +target_link_libraries( iotmqtt iotcommon iotplatform ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotmqtt unity ) + target_link_libraries( iotmqtt unity ) endif() diff --git a/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c b/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c deleted file mode 100644 index da561051d4..0000000000 --- a/lib/source/platform/posix/static_memory/aws_iot_static_memory_mqtt_posix.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_static_memory_mqtt_posix.c - * @brief Implementation of MQTT static memory functions in aws_iot_static_memory.h - * for POSIX systems. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include -#include - -/* POSIX include. Allow it to be overridden. */ -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif - -/* Static memory include. */ -#include "platform/aws_iot_static_memory.h" - -/* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_MQTT_CONNECTIONS - #define AWS_IOT_MQTT_CONNECTIONS ( 1 ) -#endif -#ifndef AWS_IOT_MQTT_SUBSCRIPTIONS - #define AWS_IOT_MQTT_SUBSCRIPTIONS ( 8 ) -#endif -#ifndef AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS - #define AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) -#endif -/** @endcond */ - -/* Validate static memory configuration settings. */ -#if AWS_IOT_MQTT_CONNECTIONS <= 0 - #error "AWS_IOT_MQTT_CONNECTIONS cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_SUBSCRIPTIONS <= 0 - #error "AWS_IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS <= 0 - #error "AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." -#endif - -/** - * @brief The size of a static memory MQTT subscription. - * - * Since the pTopic member of #_mqttSubscription_t is variable-length, the constant - * #_AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of - * #_mqttSubscription_t.pTopicFilter. - */ -#define _MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - -/*-----------------------------------------------------------*/ - -/* Extern declarations of common static memory functions in aws_iot_static_memory_common_posix.c - * Because these functions are POSIX-platform-specific, they are not placed in - * a platform header file. */ -extern int AwsIotStaticMemory_FindFree( bool * const pInUse, - int limit ); -extern void AwsIotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static bool _pInUseMqttConnections[ AWS_IOT_MQTT_CONNECTIONS ] = { 0 }; /**< @brief MQTT connection in-use flags. */ -static _mqttConnection_t _pMqttConnections[ AWS_IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ - -static bool _pInUseMqttOperations[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ -static _mqttOperation_t _pMqttOperations[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operations. */ - -static bool _pInUseMqttTimerEvents[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer event in-use flags. */ -static _mqttTimerEvent_t _pMqttTimerEvents[ AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer events. */ - -static bool _pInUseMqttSubscriptions[ AWS_IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ -static char _pMqttSubscriptions[ AWS_IOT_MQTT_SUBSCRIPTIONS ][ _MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ - -/*-----------------------------------------------------------*/ - -void * AwsIot_MallocMqttConnection( size_t size ) -{ - int freeIndex = -1; - void * pNewConnection = NULL; - - /* Check size argument. */ - if( size == sizeof( _mqttConnection_t ) ) - { - /* Find a free MQTT connection. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttConnections, - AWS_IOT_MQTT_CONNECTIONS ); - - if( freeIndex != -1 ) - { - pNewConnection = &( _pMqttConnections[ freeIndex ] ); - } - } - - return pNewConnection; -} - -/*-----------------------------------------------------------*/ - -void AwsIot_FreeMqttConnection( void * ptr ) -{ - /* Return the in-use MQTT connection. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pMqttConnections, - _pInUseMqttConnections, - AWS_IOT_MQTT_CONNECTIONS, - sizeof( _mqttConnection_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * AwsIot_MallocMqttOperation( size_t size ) -{ - int freeIndex = -1; - void * pNewOperation = NULL; - - /* Check size argument. */ - if( size == sizeof( _mqttOperation_t ) ) - { - /* Find a free MQTT operation. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttOperations, - AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); - - if( freeIndex != -1 ) - { - pNewOperation = &( _pMqttOperations[ freeIndex ] ); - } - } - - return pNewOperation; -} - -/*-----------------------------------------------------------*/ - -void AwsIot_FreeMqttOperation( void * ptr ) -{ - /* Return the in-use MQTT operation. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pMqttOperations, - _pInUseMqttOperations, - AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _mqttOperation_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * AwsIot_MallocMqttTimerEvent( size_t size ) -{ - int freeIndex = -1; - void * pNewTimerEvent = NULL; - - /* Check size argument. */ - if( size == sizeof( _mqttTimerEvent_t ) ) - { - /* Find a free MQTT timer event. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttTimerEvents, - AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); - - if( freeIndex != -1 ) - { - pNewTimerEvent = &( _pMqttTimerEvents[ freeIndex ] ); - } - } - - return pNewTimerEvent; -} - -/*-----------------------------------------------------------*/ - -void AwsIot_FreeMqttTimerEvent( void * ptr ) -{ - /* Return the in-use MQTT timer event. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pMqttTimerEvents, - _pInUseMqttTimerEvents, - AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _mqttTimerEvent_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * AwsIot_MallocMqttSubscription( size_t size ) -{ - int freeIndex = -1; - void * pNewSubscription = NULL; - - if( size <= _MQTT_SUBSCRIPTION_SIZE ) - { - /* Get the index of a free MQTT subscription. */ - freeIndex = AwsIotStaticMemory_FindFree( _pInUseMqttSubscriptions, - AWS_IOT_MQTT_SUBSCRIPTIONS ); - - if( freeIndex != -1 ) - { - pNewSubscription = &( _pMqttSubscriptions[ freeIndex ][ 0 ] ); - } - } - - return pNewSubscription; -} - -/*-----------------------------------------------------------*/ - -void AwsIot_FreeMqttSubscription( void * ptr ) -{ - /* Return the in-use MQTT subscription. */ - AwsIotStaticMemory_ReturnInUse( ptr, - _pMqttSubscriptions, - _pInUseMqttSubscriptions, - AWS_IOT_MQTT_SUBSCRIPTIONS, - _MQTT_SUBSCRIPTION_SIZE ); -} - -/*-----------------------------------------------------------*/ - -#endif diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index 3877a1654f..10536b1dab 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -8,16 +8,8 @@ add_library( awsiotshadow SHARED # Library version. set_target_properties( awsiotshadow PROPERTIES VERSION ${PROJECT_VERSION} ) -# Library public headers. -set_target_properties( awsiotshadow PROPERTIES PUBLIC_HEADER - "../../include/aws_iot_shadow.h" ) - -# Library private headers. -set_target_properties( awsiotmqtt PROPERTIES PRIVATE_HEADER - "../../include/private/aws_iot_shadow_internal.h" ) - # Link required libraries. -target_link_libraries( awsiotshadow awsiotcommon awsiotplatform awsiotmqtt ) +target_link_libraries( awsiotshadow iotcommon iotplatform iotmqtt ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/lib/include/platform/types/iot_platform_types_posix.h b/platform/include/posix/iot_platform_types_posix.h similarity index 100% rename from lib/include/platform/types/iot_platform_types_posix.h rename to platform/include/posix/iot_platform_types_posix.h diff --git a/lib/source/platform/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt similarity index 78% rename from lib/source/platform/posix/CMakeLists.txt rename to platform/source/posix/CMakeLists.txt index 7f4356af3f..5eab107348 100644 --- a/lib/source/platform/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -60,7 +60,7 @@ if( ${OPENSSL_FOUND} ) endif() # Choose OpenSSL network source file. - set( NETWORK_SOURCE_FILE network/aws_iot_network_openssl.c ) + set( NETWORK_SOURCE_FILE network/iot_network_openssl.c ) # Link OpenSSL. set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) @@ -72,20 +72,13 @@ if( NOT DEFINED TLS_LIBRARY_LINKER_FLAG ) endif() # Platform libraries source files. -add_library( awsiotplatform SHARED +add_library( iotplatform SHARED iot_clock_posix.c iot_threads_posix.c - ${NETWORK_SOURCE_FILE} - static_memory/aws_iot_static_memory_common_posix.c - static_memory/aws_iot_static_memory_mqtt_posix.c - static_memory/aws_iot_static_memory_shadow_posix.c ) + ${NETWORK_SOURCE_FILE} ) # Library version. -set_target_properties( awsiotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) - -# Library public headers. -set_target_properties( awsiotplatform PROPERTIES PUBLIC_HEADER - "../../include/platform;../../include/platform/network" ) +set_target_properties( iotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( awsiotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) +target_link_libraries( iotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) diff --git a/lib/source/platform/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c similarity index 100% rename from lib/source/platform/posix/iot_clock_posix.c rename to platform/source/posix/iot_clock_posix.c diff --git a/lib/source/platform/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c similarity index 99% rename from lib/source/platform/posix/iot_threads_posix.c rename to platform/source/posix/iot_threads_posix.c index 8ff996cd72..58a65f8e1b 100644 --- a/lib/source/platform/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -228,7 +228,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, /*-----------------------------------------------------------*/ -bool IotMutex_Create( IotMutex_t * pNewMutex ) +bool IotMutex_Create( IotMutex_t * const pNewMutex ) { bool status = true; diff --git a/lib/source/platform/posix/network/aws_iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c similarity index 99% rename from lib/source/platform/posix/network/aws_iot_network_openssl.c rename to platform/source/posix/network/iot_network_openssl.c index 02f052f40f..f985bdbe40 100644 --- a/lib/source/platform/posix/network/aws_iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -20,9 +20,9 @@ */ /** - * @file aws_iot_network_openssl.c - * @brief Implementation of the network-related functions from aws_iot_network.h - * for systems with OpenSSL on POSIX systems. + * @file iot_network_openssl.c + * @brief Implementation of the network interface functions in iot_network.h + * for POSIX systems with OpenSSL. */ /* Build using a config header, if provided. */ diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index 9f47da7a49..d124cc7c9d 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -10,6 +10,9 @@ set -e # Set Thing Name. AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" +# Additional options for compilation. +COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" + # Create build directory. mkdir -p build cd build @@ -26,7 +29,7 @@ echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem # Build tests and demos against AWS IoT with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run MQTT tests and demo against AWS IoT. @@ -39,7 +42,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run MQTT and Shadow tests in static memory mode. diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh index b5ac168d1f..20e55abbf1 100644 --- a/scripts/build_check_pr.sh +++ b/scripts/build_check_pr.sh @@ -6,13 +6,16 @@ # Exit on any nonzero return code. set -e +# Additional options for compilation. +COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" + # Create build directory. mkdir -p build cd build rm -rf * # Build tests and demos against Mosquitto broker with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" make # Run MQTT tests and demos against Mosquitto. @@ -24,7 +27,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DAWS_IOT_STATIC_MEMORY_ONLY=1 -fsanitize=thread" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DIOT_STATIC_MEMORY_ONLY=1 $COMPILER_OPTIONS" make # Run MQTT tests and no-network Shadow tests in static memory mode. diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 8460f99f52..9ca8aa774f 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -4,4 +4,4 @@ add_executable( iot_tests_common unit/iot_tests_linear_containers.c ) # Common tests library dependencies. -target_link_libraries( iot_tests_common unity ) +target_link_libraries( iot_tests_common unity iotcommon ) diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index df9dbae977..ce857d48e9 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -34,6 +34,9 @@ /* POSIX includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -82,6 +85,12 @@ int main( int argc, return -1; } + /* Initialize the common libraries before running the tests. */ + if( IotCommon_Init() == false ) + { + return -1; + } + /* Unity setup. */ UnityFixture.Verbose = 1; UnityFixture.RepeatCount = 1; @@ -92,6 +101,9 @@ int main( int argc, /* Run linear containers tests. */ RUN_TEST_GROUP( Common_Unit_Linear_Containers ); + /* Clean up common libraries. */ + IotCommon_Cleanup(); + /* Return the number of test failures. This will cause a non-zero exit code * if any test fails. */ return UNITY_END(); diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index e311b51e62..1ecef38825 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -74,7 +74,7 @@ #define AWS_IOT_MQTT_TEST ( 1 ) /* Memory allocation function configuration. Note that these functions will not - * be affected by AWS_IOT_STATIC_MEMORY_ONLY. */ + * be affected by IOT_STATIC_MEMORY_ONLY. */ #define AwsIotNetwork_Malloc unity_malloc_mt #define AwsIotNetwork_Free unity_free_mt #define IotThreads_Malloc unity_malloc_mt @@ -87,14 +87,14 @@ /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 - #define AWS_IOT_MQTT_CONNECTIONS ( 2 ) - #define AWS_IOT_MQTT_SUBSCRIPTIONS ( 80 ) +#if IOT_STATIC_MEMORY_ONLY == 1 + #define IOT_MQTT_CONNECTIONS ( 2 ) + #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) #endif /* Memory allocation function configuration for libraries affected by - * AWS_IOT_STATIC_MEMORY_ONLY. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 0 + * IOT_STATIC_MEMORY_ONLY. */ +#if IOT_STATIC_MEMORY_ONLY == 0 #define AwsIotMqtt_MallocConnection unity_malloc_mt #define AwsIotMqtt_FreeConnection unity_free_mt #define AwsIotMqtt_MallocMessage unity_malloc_mt @@ -111,7 +111,7 @@ #define AwsIotShadow_FreeString unity_free_mt #define AwsIotShadow_MallocSubscription unity_malloc_mt #define AwsIotShadow_FreeSubscription unity_free_mt -#endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 0 */ +#endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index 8927d38bfc..7868c88557 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -10,4 +10,4 @@ add_executable( aws_iot_tests_mqtt system/aws_iot_tests_mqtt_stress.c ) # MQTT tests library dependencies. -target_link_libraries( aws_iot_tests_mqtt awsiotcommon awsiotplatform awsiotmqtt unity ) +target_link_libraries( aws_iot_tests_mqtt iotcommon iotplatform iotmqtt unity ) diff --git a/tests/mqtt/aws_iot_tests_mqtt.c b/tests/mqtt/aws_iot_tests_mqtt.c index 2531d7269b..c5d1c22e17 100644 --- a/tests/mqtt/aws_iot_tests_mqtt.c +++ b/tests/mqtt/aws_iot_tests_mqtt.c @@ -33,6 +33,9 @@ /* POSIX includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -77,6 +80,12 @@ int main( int argc, return -1; } + /* Initialize the common libraries before running the tests. */ + if( IotCommon_Init() == false ) + { + return -1; + } + /* Unity setup. */ UnityFixture.Verbose = 1; UnityFixture.RepeatCount = 1; @@ -98,6 +107,9 @@ int main( int argc, RUN_TEST_GROUP( MQTT_Stress ); } + /* Clean up common libraries. */ + IotCommon_Cleanup(); + /* Return the number of test failures. This will cause a non-zero exit code * if any test fails. */ return UNITY_END(); diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c index 6aa4588378..d6454197b0 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -63,7 +63,7 @@ * * Provide default malloc and free functions in dynamic memory mode. */ -#if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) +#if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) #ifndef AwsIotTest_Malloc #include #define AwsIotTest_Malloc malloc @@ -72,7 +72,7 @@ #include #define AwsIotTest_Free free #endif -#endif /* if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) */ +#endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ /** @endcond */ /** @brief Default CONNACK packet for the receive tests. */ @@ -143,7 +143,7 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Declare a buffer holding a packet and its size. */ -#if AWS_IOT_STATIC_MEMORY_ONLY == 1 +#if IOT_STATIC_MEMORY_ONLY == 1 #define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ const size_t sizeName = sizeof( pTemplate ); \ @@ -155,7 +155,7 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; TEST_ASSERT_NOT_EQUAL( NULL, bufferName ); \ const size_t sizeName = sizeof( pTemplate ); \ ( void ) memcpy( bufferName, pTemplate, sizeName ); -#endif /* if AWS_IOT_STATIC_MEMORY_ONLY == 1 */ +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /** * @brief Initializer for operations in the tests. @@ -201,7 +201,7 @@ static void * _mallocWrapper( size_t size ) ( void ) size; - #if !defined( AWS_IOT_STATIC_MEMORY_ONLY ) || ( AWS_IOT_STATIC_MEMORY_ONLY == 0 ) + #if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) pBuffer = AwsIotTest_Malloc( size ); /* Decrement the malloc semaphore. */ @@ -242,7 +242,7 @@ static uint8_t _getPacketType( const uint8_t * const pPacket, static void _freeWrapper( void * ptr ) { /* This function should do nothing in static memory mode. */ - #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #if IOT_STATIC_MEMORY_ONLY == 1 ( void ) ptr; #else AwsIotTest_Free( ptr ); @@ -725,7 +725,7 @@ TEST( MQTT_Unit_Receive, DataStream ) int32_t bytesProcessed = 0; /* Allocate the data stream depending on the memory allocation mode. */ - #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #if IOT_STATIC_MEMORY_ONLY == 1 uint8_t pDataStream[ _DATA_STREAM_SIZE ] = { 0 }; TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); #else @@ -1165,7 +1165,7 @@ TEST( MQTT_Unit_Receive, PublishStream ) size_t i = 0; /* Allocate the data stream depending on the memory allocation mode. */ - #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #if IOT_STATIC_MEMORY_ONLY == 1 uint8_t pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ] = { 0 }; TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); #else @@ -1209,7 +1209,7 @@ TEST( MQTT_Unit_Receive, PublishInvalidStream ) size_t i = 0; /* Allocate the data stream depending on the memory allocation mode. */ - #if AWS_IOT_STATIC_MEMORY_ONLY == 1 + #if IOT_STATIC_MEMORY_ONLY == 1 uint8_t pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1 ] = { 0 }; TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); #else diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index 2d81da8c49..de587b1281 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -7,4 +7,4 @@ add_executable( aws_iot_tests_shadow system/aws_iot_tests_shadow_system.c ) # Shadow tests library dependencies. -target_link_libraries( aws_iot_tests_shadow awsiotcommon awsiotplatform awsiotmqtt awsiotshadow unity ) +target_link_libraries( aws_iot_tests_shadow iotcommon iotplatform iotmqtt awsiotshadow unity ) diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index 204f571b00..488c9ad0a3 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -33,6 +33,9 @@ /* POSIX includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -77,6 +80,12 @@ int main( int argc, return -1; } + /* Initialize the common libraries before running the tests. */ + if( IotCommon_Init() == false ) + { + return -1; + } + /* Unity setup. */ UnityFixture.Verbose = 1; UnityFixture.RepeatCount = 1; @@ -101,6 +110,9 @@ int main( int argc, { } + /* Clean up common libraries. */ + IotCommon_Cleanup(); + /* Return the number of test failures. This will cause a non-zero exit code * if any test fails. */ return UNITY_END(); diff --git a/tests/unity/CMakeLists.txt b/tests/unity/CMakeLists.txt index 8517118e24..dec7e4cd5a 100644 --- a/tests/unity/CMakeLists.txt +++ b/tests/unity/CMakeLists.txt @@ -3,9 +3,3 @@ add_library( unity SHARED unity.c fixture/unity_fixture.c fixture/unity_memory_mt.c ) - -set_target_properties( unity PROPERTIES PUBLIC_HEADER - "unity.h;fixture/unity_fixture.h;fixture/unity_fixture_malloc_overrides.h" ) - -set_target_properties( unity PROPERTIES PRIVATE_HEADER - "unity_internals.h;fixture/unity_fixture_internals.h" ) From 58ea2407e2830b6be62f65fae82af640ef73237f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 30 Jan 2019 11:12:13 -0800 Subject: [PATCH 016/844] Move MQTT receive callback; add mutexes around queues. (#265) Also renames the remaining of the common directories. --- cbmc/README.md | 12 +- cbmc/proofs/DeserializeConnack/Makefile | 2 +- cbmc/proofs/DeserializePingresp/Makefile | 2 +- cbmc/proofs/DeserializePuback/Makefile | 2 +- cbmc/proofs/DeserializePublish/Makefile | 2 +- cbmc/proofs/DeserializeSuback/Makefile | 2 +- cbmc/proofs/DeserializeUnsuback/Makefile | 2 +- demos/aws_iot_demo.h | 8 +- demos/aws_iot_demo_mqtt.c | 124 ++-- demos/aws_iot_demo_shadow.c | 12 +- demos/iot_demo_config.h | 12 +- demos/posix/aws_iot_demo_common_posix.c | 38 +- demos/posix/aws_iot_demo_mqtt_posix.c | 4 +- demos/posix/aws_iot_demo_shadow_posix.c | 6 +- doc/config/common | 2 +- doc/guide/style.txt | 10 +- doc/lib/logging.txt | 46 +- doc/lib/mqtt.txt | 4 +- doc/lib/platform.txt | 24 +- doc/lib/shadow.txt | 4 +- doc/mainpage.txt | 4 +- ...{aws_iot_json_utils.h => iot_json_utils.h} | 20 +- ...ot_logging_setup.h => iot_logging_setup.h} | 144 ++-- lib/include/private/aws_iot_mqtt_internal.h | 8 +- lib/include/private/aws_iot_shadow_internal.h | 10 +- .../{aws_iot_logging.h => iot_logging.h} | 98 +-- lib/source/common/CMakeLists.txt | 4 +- lib/source/common/iot_common.c | 14 +- ...{aws_iot_json_utils.c => iot_json_utils.c} | 18 +- .../{aws_iot_logging.c => iot_logging.c} | 148 ++-- lib/source/mqtt/CMakeLists.txt | 1 + lib/source/mqtt/aws_iot_mqtt_api.c | 655 +++--------------- lib/source/mqtt/aws_iot_mqtt_operation.c | 56 +- lib/source/mqtt/aws_iot_mqtt_serialize.c | 382 +++++----- lib/source/mqtt/aws_iot_mqtt_validate.c | 114 +-- lib/source/mqtt/iot_mqtt_network.c | 537 ++++++++++++++ lib/source/shadow/aws_iot_shadow_api.c | 144 ++-- lib/source/shadow/aws_iot_shadow_operation.c | 124 ++-- lib/source/shadow/aws_iot_shadow_parser.c | 48 +- .../shadow/aws_iot_shadow_subscription.c | 166 ++--- platform/source/posix/iot_clock_posix.c | 38 +- platform/source/posix/iot_threads_posix.c | 135 ++-- .../posix/network/iot_network_openssl.c | 248 +++---- scripts/build_check_commit.sh | 4 +- scripts/coverage.sh | 2 +- scripts/generate_doc.sh | 4 +- tests/iot_tests_config.h | 10 +- tests/mqtt/system/aws_iot_tests_mqtt_stress.c | 44 +- .../system/aws_iot_tests_shadow_system.c | 86 +-- .../shadow/unit/aws_iot_tests_shadow_parser.c | 14 +- 50 files changed, 1828 insertions(+), 1770 deletions(-) rename lib/include/{aws_iot_json_utils.h => iot_json_utils.h} (70%) rename lib/include/{aws_iot_logging_setup.h => iot_logging_setup.h} (53%) rename lib/include/private/{aws_iot_logging.h => iot_logging.h} (71%) rename lib/source/common/{aws_iot_json_utils.c => iot_json_utils.c} (94%) rename lib/source/common/{aws_iot_logging.c => iot_logging.c} (80%) create mode 100644 lib/source/mqtt/iot_mqtt_network.c diff --git a/cbmc/README.md b/cbmc/README.md index f08364436b..724554c079 100644 --- a/cbmc/README.md +++ b/cbmc/README.md @@ -3,16 +3,16 @@ This directory contains CBMC proofs of memory safety of MQTT entry points. [CBMC](http://www.cprover.org/cbmc/) is a bounded model checker for C available from the GitHub [repository](https://github.com/diffblue/cbmc). Each proof is in a separate subdirectory of proofs: * DeserializeConnack: AwsIotMqttInternal_DeserializeConnack is memory safe assuming: - * We abstract the AwsIotLogGeneric logging function + * We abstract the IotLog_Generic logging function * DeserializePingresp: AwsIotMqttInternal_DeserializePingresp is memory safe assuming: - * We abstract the AwsIotLogGeneric logging function + * We abstract the IotLog_Generic logging function * DeserializePuback: AwsIotMqttInternal_DeserializePuback is memory safe assuming: - * We abstract the AwsIotLogGeneric logging function + * We abstract the IotLog_Generic logging function * DeserializePublish: AwsIotMqttInternal_DeserializePublish is memory safe assuming: - * We abstract the AwsIotLogGeneric logging function + * We abstract the IotLog_Generic logging function * DeserializeSuback: AwsIotMqttInternal_DeserializeSuback is memory safe assuming: - * We abstract the AwsIotLogGeneric logging function + * We abstract the IotLog_Generic logging function * We abstract the AwsIotMqttInternal_RemoveSubscriptionByPacket function * We bound the length of the buffer being parsed * DeserializeUnsuback: AwsIotMqttInternal_DeserializeUnsuback is memory safe assuming: - * We abstract the AwsIotLogGeneric logging function + * We abstract the IotLog_Generic logging function diff --git a/cbmc/proofs/DeserializeConnack/Makefile b/cbmc/proofs/DeserializeConnack/Makefile index f3fb57dfbe..2301d6a297 100644 --- a/cbmc/proofs/DeserializeConnack/Makefile +++ b/cbmc/proofs/DeserializeConnack/Makefile @@ -1,7 +1,7 @@ ENTRY=DeserializeConnack ABSTRACTIONS = \ - --remove-function-body AwsIotLogGeneric \ + --remove-function-body IotLog_Generic \ OBJS = \ $(ENTRY)_harness.goto \ diff --git a/cbmc/proofs/DeserializePingresp/Makefile b/cbmc/proofs/DeserializePingresp/Makefile index c2cfae7174..df0640dda5 100644 --- a/cbmc/proofs/DeserializePingresp/Makefile +++ b/cbmc/proofs/DeserializePingresp/Makefile @@ -1,7 +1,7 @@ ENTRY=DeserializePingresp ABSTRACTIONS = \ - --remove-function-body AwsIotLogGeneric \ + --remove-function-body IotLog_Generic \ OBJS = \ $(ENTRY)_harness.goto \ diff --git a/cbmc/proofs/DeserializePuback/Makefile b/cbmc/proofs/DeserializePuback/Makefile index 026b0ee7d8..e234f26227 100644 --- a/cbmc/proofs/DeserializePuback/Makefile +++ b/cbmc/proofs/DeserializePuback/Makefile @@ -1,7 +1,7 @@ ENTRY=DeserializePuback ABSTRACTIONS = \ - --remove-function-body AwsIotLogGeneric \ + --remove-function-body IotLog_Generic \ OBJS = \ $(ENTRY)_harness.goto \ diff --git a/cbmc/proofs/DeserializePublish/Makefile b/cbmc/proofs/DeserializePublish/Makefile index fd1ff9b4ef..4b9f1fe707 100644 --- a/cbmc/proofs/DeserializePublish/Makefile +++ b/cbmc/proofs/DeserializePublish/Makefile @@ -1,7 +1,7 @@ ENTRY=DeserializePublish ABSTRACTIONS = \ - --remove-function-body AwsIotLogGeneric \ + --remove-function-body IotLog_Generic \ OBJS = \ $(ENTRY)_harness.goto \ diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/DeserializeSuback/Makefile index b4f1c7619c..b1979b6bef 100644 --- a/cbmc/proofs/DeserializeSuback/Makefile +++ b/cbmc/proofs/DeserializeSuback/Makefile @@ -3,7 +3,7 @@ ENTRY=DeserializeSuback BUFFER_SIZE = 100 ABSTRACTIONS = \ - --remove-function-body AwsIotLogGeneric \ + --remove-function-body IotLog_Generic \ --remove-function-body AwsIotMqttInternal_RemoveSubscriptionByPacket \ UNWINDING = \ diff --git a/cbmc/proofs/DeserializeUnsuback/Makefile b/cbmc/proofs/DeserializeUnsuback/Makefile index 87868a233a..4826339630 100644 --- a/cbmc/proofs/DeserializeUnsuback/Makefile +++ b/cbmc/proofs/DeserializeUnsuback/Makefile @@ -1,7 +1,7 @@ ENTRY=DeserializeUnsuback ABSTRACTIONS = \ - --remove-function-body AwsIotLogGeneric \ + --remove-function-body IotLog_Generic \ OBJS = \ $(ENTRY)_harness.goto \ diff --git a/demos/aws_iot_demo.h b/demos/aws_iot_demo.h index 24da7905b9..d7a0ce753d 100644 --- a/demos/aws_iot_demo.h +++ b/demos/aws_iot_demo.h @@ -43,15 +43,15 @@ #ifdef AWS_IOT_LOG_LEVEL_DEMO #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEMO #else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif #define _LIBRARY_LOG_NAME ( "DEMO" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /*----------------------------- Demo functions ------------------------------*/ diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c index 235411ef77..bb8dc29ab6 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/aws_iot_demo_mqtt.c @@ -209,16 +209,16 @@ static void _operationCompleteCallback( void * param1, * successful when transmitted over the network. */ if( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogInfo( "MQTT %s %d successfully sent.", - AwsIotMqtt_OperationType( pOperation->operation.type ), - ( int ) publishCount ); + IotLogInfo( "MQTT %s %d successfully sent.", + AwsIotMqtt_OperationType( pOperation->operation.type ), + ( int ) publishCount ); } else { - AwsIotLogError( "MQTT %s %d could not be sent. Error %s.", - AwsIotMqtt_OperationType( pOperation->operation.type ), - ( int ) publishCount, - AwsIotMqtt_strerror( pOperation->operation.result ) ); + IotLogError( "MQTT %s %d could not be sent. Error %s.", + AwsIotMqtt_OperationType( pOperation->operation.type ), + ( int ) publishCount, + AwsIotMqtt_strerror( pOperation->operation.result ) ); } } @@ -246,20 +246,20 @@ static void _mqttSubscriptionCallback( void * param1, AwsIotMqttPublishInfo_t acknowledgementInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Print information about the incoming PUBLISH message. */ - AwsIotLogInfo( "Incoming PUBLISH received:\n" - "Subscription topic filter: %.*s\n" - "Publish topic name: %.*s\n" - "Publish retain flag: %d\n" - "Publish QoS: %d\n" - "Publish payload: %.*s", - pPublish->message.topicFilterLength, - pPublish->message.pTopicFilter, - pPublish->message.info.topicNameLength, - pPublish->message.info.pTopicName, - pPublish->message.info.retain, - pPublish->message.info.QoS, - pPublish->message.info.payloadLength, - pPayload ); + IotLogInfo( "Incoming PUBLISH received:\n" + "Subscription topic filter: %.*s\n" + "Publish topic name: %.*s\n" + "Publish retain flag: %d\n" + "Publish QoS: %d\n" + "Publish payload: %.*s", + pPublish->message.topicFilterLength, + pPublish->message.pTopicFilter, + pPublish->message.info.topicNameLength, + pPublish->message.info.pTopicName, + pPublish->message.info.retain, + pPublish->message.info.QoS, + pPublish->message.info.payloadLength, + pPayload ); /* Find the message number inside of the PUBLISH message. */ for( messageNumberIndex = 0; messageNumberIndex < pPublish->message.info.payloadLength; messageNumberIndex++ ) @@ -294,9 +294,9 @@ static void _mqttSubscriptionCallback( void * param1, /* Check for errors from snprintf. */ if( acknowledgementLength < 0 ) { - AwsIotLogWarn( "Failed to generate acknowledgement message for PUBLISH *.*s.", - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); + IotLogWarn( "Failed to generate acknowledgement message for PUBLISH *.*s.", + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); } else { @@ -321,15 +321,15 @@ static void _mqttSubscriptionCallback( void * param1, NULL, NULL ) == AWS_IOT_MQTT_STATUS_PENDING ) { - AwsIotLogInfo( "Acknowledgment message for PUBLISH %.*s will be sent.", - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); + IotLogInfo( "Acknowledgment message for PUBLISH %.*s will be sent.", + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); } else { - AwsIotLogWarn( "Acknowledgment message for PUBLISH %.*s will NOT be sent.", - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); + IotLogWarn( "Acknowledgment message for PUBLISH %.*s will NOT be sent.", + ( int ) messageNumberLength, + pPayload + messageNumberIndex ); } } } @@ -413,7 +413,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, /* Check for errors from snprintf. */ if( status < 0 ) { - AwsIotLogError( "Failed to generate unique client identifier for demo." ); + IotLogError( "Failed to generate unique client identifier for demo." ); status = -1; } else @@ -428,10 +428,10 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, if( status == 0 ) { - AwsIotLogInfo( "MQTT demo client identifier is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); + IotLogInfo( "MQTT demo client identifier is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); /* Establish the MQTT connection. */ mqttStatus = AwsIotMqtt_Connect( pMqttConnection, @@ -442,8 +442,8 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "MQTT CONNECT returned error %s.", - AwsIotMqtt_strerror( mqttStatus ) ); + IotLogError( "MQTT CONNECT returned error %s.", + AwsIotMqtt_strerror( mqttStatus ) ); status = -1; } @@ -473,7 +473,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, switch( mqttStatus ) { case AWS_IOT_MQTT_SUCCESS: - AwsIotLogInfo( "All demo topic filter subscriptions accepted." ); + IotLogInfo( "All demo topic filter subscriptions accepted." ); break; case AWS_IOT_MQTT_SERVER_REFUSED: @@ -486,15 +486,15 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, pSubscriptions[ i ].topicFilterLength, NULL ) == true ) { - AwsIotLogInfo( "Topic filter %.*s was accepted.", - pSubscriptions[ i ].topicFilterLength, - pSubscriptions[ i ].pTopicFilter ); + IotLogInfo( "Topic filter %.*s was accepted.", + pSubscriptions[ i ].topicFilterLength, + pSubscriptions[ i ].pTopicFilter ); } else { - AwsIotLogInfo( "Topic filter %.*s was rejected.", - pSubscriptions[ i ].topicFilterLength, - pSubscriptions[ i ].pTopicFilter ); + IotLogInfo( "Topic filter %.*s was rejected.", + pSubscriptions[ i ].topicFilterLength, + pSubscriptions[ i ].pTopicFilter ); } } @@ -532,9 +532,9 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, { if( publishCount % AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) { - AwsIotLogInfo( "Publishing messages %d to %d.", - publishCount, - publishCount + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); + IotLogInfo( "Publishing messages %d to %d.", + publishCount, + publishCount + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); } /* Pass the PUBLISH number to the operation complete callback. */ @@ -552,8 +552,8 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, /* Check for errors from snprintf. */ if( status < 0 ) { - AwsIotLogError( "Failed to generate MQTT PUBLISH payload for PUBLISH %d.", - ( int ) publishCount ); + IotLogError( "Failed to generate MQTT PUBLISH payload for PUBLISH %d.", + ( int ) publishCount ); status = -1; break; @@ -573,9 +573,9 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, if( mqttStatus != AWS_IOT_MQTT_STATUS_PENDING ) { - AwsIotLogError( "MQTT PUBLISH %d returned error %s.", - ( int ) publishCount, - AwsIotMqtt_strerror( mqttStatus ) ); + IotLogError( "MQTT PUBLISH %d returned error %s.", + ( int ) publishCount, + AwsIotMqtt_strerror( mqttStatus ) ); status = -1; break; } @@ -587,22 +587,22 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, if( ( publishCount > 0 ) && ( publishCount % AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) { - AwsIotLogInfo( "Waiting for %d publishes to be received.", - AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + IotLogInfo( "Waiting for %d publishes to be received.", + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { if( IotSemaphore_TimedWait( &publishesReceived, _MQTT_TIMEOUT_MS ) == false ) { - AwsIotLogError( "Timed out waiting for incoming PUBLISH messages." ); + IotLogError( "Timed out waiting for incoming PUBLISH messages." ); status = -1; break; } } - AwsIotLogInfo( "%d publishes received.", - AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + IotLogInfo( "%d publishes received.", + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); } /* Stop publishing if there was an error. */ @@ -616,20 +616,20 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, * should also wait for all previously published messages. */ if( status == 0 ) { - AwsIotLogInfo( "Waiting for all publishes to be received." ); + IotLogInfo( "Waiting for all publishes to be received." ); for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { if( IotSemaphore_TimedWait( &publishesReceived, _MQTT_TIMEOUT_MS ) == false ) { - AwsIotLogError( "Timed out waiting for incoming PUBLISH messages." ); + IotLogError( "Timed out waiting for incoming PUBLISH messages." ); status = -1; break; } } - AwsIotLogInfo( "All publishes received." ); + IotLogInfo( "All publishes received." ); } /* Destroy the received message counter. */ @@ -653,8 +653,8 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "MQTT UNSUBSCRIBE returned error %s.", - AwsIotMqtt_strerror( mqttStatus ) ); + IotLogError( "MQTT UNSUBSCRIBE returned error %s.", + AwsIotMqtt_strerror( mqttStatus ) ); status = -1; } } diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 6fd49b80db..34844f36eb 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -101,10 +101,10 @@ int AwsIotDemo_RunShadowDemo( const char * const pThingName, connectInfo.pClientIdentifier = pThingName; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pThingName ); - AwsIotLogInfo( "Shadow Thing Name is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); + IotLogInfo( "Shadow Thing Name is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); /* Establish the MQTT connection. */ mqttStatus = AwsIotMqtt_Connect( pMqttConnection, @@ -115,8 +115,8 @@ int AwsIotDemo_RunShadowDemo( const char * const pThingName, if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "MQTT CONNECT returned error %s.", - AwsIotMqtt_strerror( mqttStatus ) ); + IotLogError( "MQTT CONNECT returned error %s.", + AwsIotMqtt_strerror( mqttStatus ) ); status = -1; } diff --git a/demos/iot_demo_config.h b/demos/iot_demo_config.h index 7d391daa36..ae37ce7021 100644 --- a/demos/iot_demo_config.h +++ b/demos/iot_demo_config.h @@ -48,12 +48,12 @@ #define AWS_IOT_MQTT_ENABLE_ASSERTS ( 1 ) /* Library logging configuration. */ -#define AWS_IOT_LOG_LEVEL_GLOBAL AWS_IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_PLATFORM AWS_IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_NETWORK AWS_IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_MQTT AWS_IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_SHADOW AWS_IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_DEMO AWS_IOT_LOG_INFO +#define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO +#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO +#define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_MQTT IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_DEMO IOT_LOG_INFO /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ diff --git a/demos/posix/aws_iot_demo_common_posix.c b/demos/posix/aws_iot_demo_common_posix.c index be1f7b3687..dd63869c87 100644 --- a/demos/posix/aws_iot_demo_common_posix.c +++ b/demos/posix/aws_iot_demo_common_posix.c @@ -81,7 +81,7 @@ bool AwsIotDemo_ParseArguments( int argc, pArguments->pPrivateKeyPath = AWS_IOT_DEMO_PRIVATE_KEY; #endif - AwsIotLogInfo( "Parsing command line arguments." ); + IotLogInfo( "Parsing command line arguments." ); /* Silence any error or warning messages printed by the system. The demos * will use the logging library instead. */ @@ -122,7 +122,7 @@ bool AwsIotDemo_ParseArguments( int argc, /* Check that port is valid. */ if( ( port == 0 ) || ( port > UINT16_MAX ) ) { - AwsIotLogWarn( "Ignoring invalid port '%lu'.", port ); + IotLogWarn( "Ignoring invalid port '%lu'.", port ); } else { @@ -153,14 +153,14 @@ bool AwsIotDemo_ParseArguments( int argc, /* Unknown argument. */ case ( int ) ( '?' ): - AwsIotLogWarn( "Ignoring unknown argument '-%c'.", ( char ) optopt ); + IotLogWarn( "Ignoring unknown argument '-%c'.", ( char ) optopt ); break; /* Argument known, but missing value. */ case ( int ) ( ':' ): - AwsIotLogWarn( "Ignoring invalid argument '-%c'. Option '-%c' requires a value.", - ( char ) optopt, - ( char ) optopt ); + IotLogWarn( "Ignoring invalid argument '-%c'. Option '-%c' requires a value.", + ( char ) optopt, + ( char ) optopt ); break; /* The default case should not be executed. */ @@ -174,7 +174,7 @@ bool AwsIotDemo_ParseArguments( int argc, if( ( pArguments->pHostName == NULL ) || ( strlen( pArguments->pHostName ) == 0 ) ) { - AwsIotLogError( "MQTT server not set. Exiting." ); + IotLogError( "MQTT server not set. Exiting." ); return false; } @@ -182,7 +182,7 @@ bool AwsIotDemo_ParseArguments( int argc, /* Check that a server port was set. */ if( pArguments->port == 0 ) { - AwsIotLogError( "MQTT server port not set. Exiting." ); + IotLogError( "MQTT server port not set. Exiting." ); return false; } @@ -194,7 +194,7 @@ bool AwsIotDemo_ParseArguments( int argc, if( ( pArguments->pRootCaPath == NULL ) || ( strlen( pArguments->pRootCaPath ) == 0 ) ) { - AwsIotLogError( "Root CA path not set. Exiting." ); + IotLogError( "Root CA path not set. Exiting." ); return false; } @@ -203,7 +203,7 @@ bool AwsIotDemo_ParseArguments( int argc, if( ( pArguments->pClientCertPath == NULL ) || ( strlen( pArguments->pClientCertPath ) == 0 ) ) { - AwsIotLogError( "Client certificate path not set. Exiting." ); + IotLogError( "Client certificate path not set. Exiting." ); return false; } @@ -212,21 +212,21 @@ bool AwsIotDemo_ParseArguments( int argc, if( ( pArguments->pPrivateKeyPath == NULL ) || ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) { - AwsIotLogError( "Client certificate private key not set. Exiting." ); + IotLogError( "Client certificate private key not set. Exiting." ); return false; } } - AwsIotLogInfo( "Command line arguments successfully parsed." ); + IotLogInfo( "Command line arguments successfully parsed." ); - AwsIotLogDebug( "AWS IoT MQTT mode: %s", pArguments->awsIotMqttMode == true ? "true" : "false" ); - AwsIotLogDebug( "Secured connection: %s", pArguments->securedConnection == true ? "true" : "false" ); - AwsIotLogDebug( "Host: %s", pArguments->pHostName ); - AwsIotLogDebug( "Port: %hu", pArguments->port ); - AwsIotLogDebug( "Root CA: %s", pArguments->pRootCaPath ); - AwsIotLogDebug( "Client certificate: %s", pArguments->pClientCertPath ); - AwsIotLogDebug( "Private key: %s", pArguments->pPrivateKeyPath ); + IotLogDebug( "AWS IoT MQTT mode: %s", pArguments->awsIotMqttMode == true ? "true" : "false" ); + IotLogDebug( "Secured connection: %s", pArguments->securedConnection == true ? "true" : "false" ); + IotLogDebug( "Host: %s", pArguments->pHostName ); + IotLogDebug( "Port: %hu", pArguments->port ); + IotLogDebug( "Root CA: %s", pArguments->pRootCaPath ); + IotLogDebug( "Client certificate: %s", pArguments->pClientCertPath ); + IotLogDebug( "Private key: %s", pArguments->pPrivateKeyPath ); return true; } diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/aws_iot_demo_mqtt_posix.c index b81d2ffe97..134d3d3cd4 100644 --- a/demos/posix/aws_iot_demo_mqtt_posix.c +++ b/demos/posix/aws_iot_demo_mqtt_posix.c @@ -184,11 +184,11 @@ int main( int argc, /* Log the demo status. */ if( status == 0 ) { - AwsIotLogInfo( "Demo completed successfully." ); + IotLogInfo( "Demo completed successfully." ); } else { - AwsIotLogError( "Error running demo, status %d.", status ); + IotLogError( "Error running demo, status %d.", status ); } return status; diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index 1065573cbb..ac7b250c07 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -100,7 +100,7 @@ int main( int argc, /* Thing Name must be set for this demo. */ if( demoArguments.pIdentifier == NULL ) { - AwsIotLogError( "Thing Name must be set for Shadow demo." ); + IotLogError( "Thing Name must be set for Shadow demo." ); status = -1; } @@ -194,11 +194,11 @@ int main( int argc, /* Log the demo status. */ if( status == 0 ) { - AwsIotLogInfo( "Demo completed successfully." ); + IotLogInfo( "Demo completed successfully." ); } else { - AwsIotLogError( "Error running demo, status %d.", status ); + IotLogError( "Error running demo, status %d.", status ); } return status; diff --git a/doc/config/common b/doc/config/common index b2ffd7c2aa..0053df88d8 100644 --- a/doc/config/common +++ b/doc/config/common @@ -22,7 +22,7 @@ QUIET = YES # Define the following preprocessor constants when generating documentation. PREDEFINED = "DOXYGEN=1" \ "IOT_STATIC_MEMORY_ONLY=1" \ - "_LIBRARY_LOG_LEVEL=AWS_IOT_LOG_DEBUG" \ + "_LIBRARY_LOG_LEVEL=IOT_LOG_DEBUG" \ "_LIBRARY_LOG_NAME=\"DOXYGEN\"" # Ignore the constants used for setting log levels and names. diff --git a/doc/guide/style.txt b/doc/guide/style.txt index ad0817af8e..4ff21fd0b4 100644 --- a/doc/guide/style.txt +++ b/doc/guide/style.txt @@ -128,13 +128,13 @@ static bool _libraryStaticFunction( void * pArgument, int * pLocalPointer = ( int * ) pArgument; /* All functions make generous use of the logging library. */ - AwsIotLogInfo( "Performing calculation..." ); + IotLogInfo( "Performing calculation..." ); /* Checking parameters at the beginning of functions and returning on bad * parameter values is encouraged. */ if( ( pArgument == NULL ) || ( argumentLength == 0 ) ) /* Note the parentheses and spacing in if statements */ { - AwsIotLogError( "Bad parameters." ); + IotLogError( "Bad parameters." ); return false; } @@ -143,15 +143,15 @@ static bool _libraryStaticFunction( void * pArgument, { localVariable += AwsIotLibrary_ExternalFunction( pArgument ); - AwsIotLogDebug( "Current value is %d.", localVariable ); + IotLogDebug( "Current value is %d.", localVariable ); } if( localVariable < 0 ) { - AwsIotLogWarn( "Failed to calculate positive value." ); + IotLogWarn( "Failed to calculate positive value." ); } - AwsIotLogInfo( "Calculation done." ); + IotLogInfo( "Calculation done." ); return true; } diff --git a/doc/lib/logging.txt b/doc/lib/logging.txt index 1d69b3bd2d..20c3299eb6 100644 --- a/doc/lib/logging.txt +++ b/doc/lib/logging.txt @@ -5,8 +5,8 @@ This library allows other libraries to generate and print log messages, which can aid in debugging. Log messages are printed by passing strings to one of the [logging functions]( @ref logging_functions). The features of this library include: - [Configurable levels](@ref logging_constants_levels) for log messages and libraries. -- Automatic printing of log level, library name, and time with every message. The information printed with each message [may be customized](@ref AwsIotLogConfig_t). -- A [function to print the contents of buffers](@ref logging_function_printbuffer) when using the [debug](@ref AWS_IOT_LOG_DEBUG) log level. This allows the contents of memory to be easily examined. +- Automatic printing of log level, library name, and time with every message. The information printed with each message [may be customized](@ref IotLogConfig_t). +- A [function to print the contents of buffers](@ref logging_function_printbuffer) when using the [debug](@ref IOT_LOG_DEBUG) log level. This allows the contents of memory to be easily examined. @dependencies{logging,logging library} @@ -36,28 +36,28 @@ Currently, the logging library has the following dependencies: @section logging_setup_use Setup and use @brief How to set up and use the logging library. -The file aws_iot_logging_setup.h should be included to configure logging for a single source file. Before including aws_iot_logging_setup.h, the constants @ref _LIBRARY_LOG_LEVEL and @ref _LIBRARY_LOG_NAME must be defined. +The file iot_logging_setup.h should be included to configure logging for a single source file. Before including iot_logging_setup.h, the constants @ref _LIBRARY_LOG_LEVEL and @ref _LIBRARY_LOG_NAME must be defined. -For example, to configure all the "SAMPLE" library to print all messages below the [info](@ref AWS_IOT_LOG_INFO) log level: +For example, to configure all the "SAMPLE" library to print all messages below the [info](@ref IOT_LOG_INFO) log level: @code{c} // Print log messages up to the "info" level. -#define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_INFO +#define _LIBRARY_LOG_LEVEL IOT_LOG_INFO // Print library name "SAMPLE". #define _LIBRARY_LOG_NAME "SAMPLE" // Including this header defines the logging macros using _LIBRARY_LOG_LEVEL and // _LIBRARY_LOG_NAME. -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" int main( void ) { - // After including aws_iot_logging_setup.h, the logging functions can be used. - AwsIotLogError( "Error." ); // Will be printed because AWS_IOT_LOG_ERROR <= _LIBRARY_LOG_LEVEL - AwsIotLogWarn( "Warning. " ); // Will be printed because AWS_IOT_LOG_WARN <= _LIBRARY_LOG_LEVEL - AwsIotLogInfo( "Info." ); // Will be printed because AWS_IOT_LOG_INFO <= _LIBRARY_LOG_LEVEL - AwsIotLogDebug( "Debug." ); // Will not be printed because AWS_IOT_LOG_DEBUG > _LIBRARY_LOG_LEVEL + // After including iot_logging_setup.h, the logging functions can be used. + IotLogError( "Error." ); // Will be printed because IOT_LOG_ERROR <= _LIBRARY_LOG_LEVEL + IotLogWarn( "Warning. " ); // Will be printed because IOT_LOG_WARN <= _LIBRARY_LOG_LEVEL + IotLogInfo( "Info." ); // Will be printed because IOT_LOG_INFO <= _LIBRARY_LOG_LEVEL + IotLogDebug( "Debug." ); // Will not be printed because IOT_LOG_DEBUG > _LIBRARY_LOG_LEVEL return 0; } @@ -80,22 +80,22 @@ The code above will print something like the following: Sets the log level for a single source file. A log message will only be printed if its level is less than this setting. -Both this constant and @ref _LIBRARY_LOG_NAME must be defined before including aws_iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. +Both this constant and @ref _LIBRARY_LOG_NAME must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. @configpossible One of the @ref logging_constants_levels. -@note This value must be defined if aws_iot_logging_setup.h is included. The library will not provide a default value for this setting. +@note This value must be defined if iot_logging_setup.h is included. The library will not provide a default value for this setting. @section _LIBRARY_LOG_NAME @brief The library name printed in log messages. -Sets the library name for a single source file. By default, all log messages contain the library name. The library name may be disabled for a single log message by setting passing an #AwsIotLogConfig_t with [hideLibraryName](@ref AwsIotLogConfig_t.hideLibraryName) set to `true`. +Sets the library name for a single source file. By default, all log messages contain the library name. The library name may be disabled for a single log message by setting passing an #IotLogConfig_t with [hideLibraryName](@ref IotLogConfig_t.hideLibraryName) set to `true`. -Both this constant and @ref _LIBRARY_LOG_LEVEL must be defined before including aws_iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. +Both this constant and @ref _LIBRARY_LOG_LEVEL must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. @configpossible Any string. -@note This value must be defined if aws_iot_logging_setup.h is included. The library will not provide a default value for this setting. +@note This value must be defined if iot_logging_setup.h is included. The library will not provide a default value for this setting. -@section AwsIotLogging_Puts +@section IotLogging_Puts @brief Logging library output function. The logging library calls this function to print strings. Like the standard library's [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function, this function should write a newline after the string, as log messages may not end with a newline. This setting provided the flexibility to log over different channels. For example, if no `stdout` is available, this function may be set to log over other channels such as UART or a network. @@ -107,12 +107,12 @@ Although this function is supposed to return an `int`, the logging library does @section logging_config_memory Memory allocation @brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the logging library. -- #AwsIotLogging_Malloc
- @copybrief AwsIotLogging_Malloc -- #AwsIotLogging_Free
- @copybrief AwsIotLogging_Free -- #AwsIotLogging_StaticBufferSize
- @copybrief AwsIotLogging_StaticBufferSize +- #IotLogging_Malloc
+ @copybrief IotLogging_Malloc +- #IotLogging_Free
+ @copybrief IotLogging_Free +- #IotLogging_StaticBufferSize
+ @copybrief IotLogging_StaticBufferSize Note that the logging library will silently discard logs if it fails to allocate memory for the message. */ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 618bebe7f1..7f2efca146 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -52,7 +52,7 @@ digraph mqtt_dependencies Currently, the MQTT library has the following dependencies: - The queue library for maintaining the data structures for managing in-progress MQTT operations. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_MQTT is not @ref AWS_IOT_LOG_NONE. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_MQTT is not #IOT_LOG_NONE. - The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. In addition to the components above, the MQTT library also depends on C standard library headers. @@ -285,7 +285,7 @@ Send threads process outgoing network packets. Packets are queued, then removed Log messages from the MQTT library at or below this setting will be printed. @configpossible One of the @ref logging_constants_levels.
-@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. +@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. @section AWS_IOT_MQTT_RESPONSE_WAIT_MS @brief A "reasonable amount of time" to wait for keep-alive responses from the MQTT server. diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 3ffa40a09d..73db2a72bd 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -3,7 +3,7 @@ @anchor platform @brief The platform layer provides portability across different operating systems.

-All system calls (including networking) used in the AWS IoT libraries go through a lightweight platform layer. The functions of the platform layer are intended to be easily implementable on a wide variety of operating systems. The current platform layer has the following components: +All system calls (including networking) used in this SDK's libraries go through a lightweight platform layer. The functions of the platform layer are intended to be easily implementable on a wide variety of operating systems. The current platform layer has the following components: - @ref platform_clock
@copybrief platform_clock - @ref platform_threads
@@ -53,12 +53,12 @@ digraph clock_dependencies Currently, the platform clock component has the following dependencies: - The operating system must provide the necessary APIs to implement the clock component's functions. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. +- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. */ /** @page platform_network Networking -@brief @copybrief aws_iot_network.h +@brief @copybrief iot_network.h The platform networking component provides other libraries with functions for managing network connections. It interfaces directly with the operating system to provide: - Functions for opening and closing network connections; function for sending and receiving data. @@ -97,7 +97,7 @@ digraph network_dependencies Currently, the networking component has the following dependencies: - The operating system must provide the necessary networking APIs, such as a sockets API. - A third-party security library is needed to encrypt secured connections. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. +- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not @ref IOT_LOG_NONE. */ /** @@ -133,27 +133,27 @@ digraph threads_dependencies Currently, the platform thread management component has the following dependencies: - The operating system must provide the necessary APIs to implement the [thread management functions](@ref platform_threads_functions). -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_PLATFORM is not @ref AWS_IOT_LOG_NONE. +- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. */ /** @configpage{platform,platform layer} -@section AWS_IOT_LOG_LEVEL_PLATFORM +@section IOT_LOG_LEVEL_PLATFORM @brief Set the log level of all platform components except the [networking component](@ref platform_network). -This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the platform layer components that it affects. All log messages with a level at or below this setting will be printed. The [platform networking component](@ref platform_network) is generally more verbose than others, so its logging is controlled separately by @ref AWS_IOT_LOG_LEVEL_NETWORK. +This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the platform layer components that it affects. All log messages with a level at or below this setting will be printed. The [platform networking component](@ref platform_network) is generally more verbose than others, so its logging is controlled separately by @ref IOT_LOG_LEVEL_NETWORK. @configpossible One of the @ref logging_constants_levels.
-@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. +@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. -@section AWS_IOT_LOG_LEVEL_NETWORK +@section IOT_LOG_LEVEL_NETWORK @brief Set the log level of the [platform networking component](@ref platform_network). -This setting overrides @ref AWS_IOT_LOG_LEVEL_GLOBAL for the [platform networking component](@ref platform_network). All log messages with a level at or below this setting will be printed. See @ref AWS_IOT_LOG_LEVEL_PLATFORM to set the log level of other platform components. +This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the [platform networking component](@ref platform_network). All log messages with a level at or below this setting will be printed. See @ref IOT_LOG_LEVEL_PLATFORM to set the log level of other platform components. @configpossible One of the @ref logging_constants_levels.
-@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. +@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. @section platform_config_memory Memory allocation @brief Memory allocation function overrides for the platform layer. @@ -162,7 +162,7 @@ Some platform layers are not affected by @ref IOT_STATIC_MEMORY_ONLY. Currently, - POSIX
This implementation is not affected by @ref IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - `IotThreads_Malloc` and `IotThreads_Free`. - - `AwsIotNetwork_Malloc` and `AwsIotNetwork_Free`. + - `IotNetwork_Malloc` and `IotNetwork_Free`. @section platform_config_posixheaders POSIX headers @brief The POSIX platform layer allows the standard POSIX header includes to be overridden. Overrides only affect the POSIX platform layer. diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index a34dbdf0ab..ce16650387 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -42,7 +42,7 @@ digraph shadow_dependencies Currently, the Shadow library has the following dependencies: - The MQTT library for sending the messages that interact with the Thing Shadow service. See [this page](@ref mqtt_dependencies) for the dependencies of the MQTT library, which are not shown in the graph above. - The queue library for maintaining the data structures for managing in-progress Shadow operations. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_SHADOW is not @ref AWS_IOT_LOG_NONE. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_SHADOW is not #IOT_LOG_NONE. In addition to the components above, the Shadow library also depends on C standard library headers. */ @@ -79,7 +79,7 @@ If the `mqttTimeout` argument of @ref shadow_function_init is `0`, the Shadow li Log messages from the Shadow library at or below this setting will be printed. @configpossible One of the @ref logging_constants_levels.
-@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. +@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. @section AwsIotShadow_Assert @brief Assertion function used when @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`. diff --git a/doc/mainpage.txt b/doc/mainpage.txt index b6f87517a0..93981bd33d 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -34,13 +34,13 @@ Define a file to be included in all library source files before any other files. @configpossible A string representing a file name. This file must be in the compiler's include path.
@configdefault None. A configuration header will not be used if this value is undefined. -@section AWS_IOT_LOG_LEVEL_GLOBAL +@section IOT_LOG_LEVEL_GLOBAL @brief Set a default log level. Any libraries that do not define a log level will use this setting. If a both a global log level and a library log level are defined, the library log level overrides the global one. @configpossible One of the @ref logging_constants_levels.
-@configdefault #AWS_IOT_LOG_NONE +@configdefault @ref IOT_LOG_NONE @section IOT_STATIC_MEMORY_ONLY @brief Set this to `1` to disable the usage of dynamic memory allocation ([malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html)) throughout the libraries in this SDK. diff --git a/lib/include/aws_iot_json_utils.h b/lib/include/iot_json_utils.h similarity index 70% rename from lib/include/aws_iot_json_utils.h rename to lib/include/iot_json_utils.h index a15365540c..4f0160e38c 100644 --- a/lib/include/aws_iot_json_utils.h +++ b/lib/include/iot_json_utils.h @@ -20,22 +20,22 @@ */ /** - * @file aws_iot_json_utils.h + * @file iot_json_utils.h * @brief Declares JSON utility functions. */ -#ifndef _AWS_IOT_JSON_UTILS_H_ -#define _AWS_IOT_JSON_UTILS_H_ +#ifndef _IOT_JSON_UTILS_H_ +#define _IOT_JSON_UTILS_H_ /* Standard includes. */ #include #include -bool AwsIotJsonUtils_FindJsonValue( const char * const pJsonDocument, - size_t jsonDocumentLength, - const char * const pJsonKey, - size_t jsonKeyLength, - const char ** const pJsonValue, - size_t * const pJsonValueLength ); +bool IotJsonUtils_FindJsonValue( const char * const pJsonDocument, + size_t jsonDocumentLength, + const char * const pJsonKey, + size_t jsonKeyLength, + const char ** const pJsonValue, + size_t * const pJsonValueLength ); -#endif /* ifndef _AWS_IOT_JSON_UTILS_H_ */ +#endif /* ifndef _IOT_JSON_UTILS_H_ */ diff --git a/lib/include/aws_iot_logging_setup.h b/lib/include/iot_logging_setup.h similarity index 53% rename from lib/include/aws_iot_logging_setup.h rename to lib/include/iot_logging_setup.h index a0d7d75871..08b3ece91b 100644 --- a/lib/include/aws_iot_logging_setup.h +++ b/lib/include/iot_logging_setup.h @@ -20,78 +20,78 @@ */ /** - * @file aws_iot_logging_setup.h - * @brief Defines the logging macro #AwsIotLog. + * @file iot_logging_setup.h + * @brief Defines the logging macro #IotLog. */ -#ifndef _AWS_IOT_LOGGING_SETUP_H_ -#define _AWS_IOT_LOGGING_SETUP_H_ +#ifndef _IOT_LOGGING_SETUP_H_ +#define _IOT_LOGGING_SETUP_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE #include IOT_CONFIG_FILE #endif -/* Logging include. Because it's included here, aws_iot_logging.h never needs +/* Logging include. Because it's included here, iot_logging.h never needs * to be included in source. */ -#include "private/aws_iot_logging.h" +#include "private/iot_logging.h" /** - * @functionpage{AwsIotLog,logging,log} - * @functionpage{AwsIotLog_PrintBuffer,logging,printbuffer} + * @functionpage{IotLog,logging,log} + * @functionpage{IotLog_PrintBuffer,logging,printbuffer} */ /** - * @def AwsIotLog( messageLevel, pLogConfig, ... ) + * @def IotLog( messageLevel, pLogConfig, ... ) * @brief Logging function for a specific library. In most cases, this is the * logging function to call. * * This function prints a single log message. It is available when @ref - * _LIBRARY_LOG_LEVEL is not #AWS_IOT_LOG_NONE. Log messages automatically + * _LIBRARY_LOG_LEVEL is not #IOT_LOG_NONE. Log messages automatically * include the [log level](@ref logging_constants_levels), [library name] - * (@ref _LIBRARY_LOG_NAME), and time. An optional @ref AwsIotLogConfig_t may + * (@ref _LIBRARY_LOG_NAME), and time. An optional @ref IotLogConfig_t may * be passed to this function to hide information for a single log message. * * The logging library must be set up before this function may be called. See * @ref logging_setup_use for more information. * * This logging function also has the following abbreviated forms that can be used - * when an #AwsIotLogConfig_t isn't needed. + * when an #IotLogConfig_t isn't needed. * - * Name | Equivalent to - * ---- | ------------- - * #AwsIotLogError | @code{c} AwsIotLog( AWS_IOT_LOG_ERROR, NULL, ... ) @endcode - * #AwsIotLogWarn | @code{c} AwsIotLog( AWS_IOT_LOG_WARN, NULL, ... ) @endcode - * #AwsIotLogInfo | @code{c} AwsIotLog( AWS_IOT_LOG_INFO, NULL, ... ) @endcode - * #AwsIotLogDebug | @code{c} AwsIotLog( AWS_IOT_LOG_DEBUG, NULL, ... ) @endcode + * Name | Equivalent to + * ---- | ------------- + * #IotLogError | @code{c} IotLog( IOT_LOG_ERROR, NULL, ... ) @endcode + * #IotLogWarn | @code{c} IotLog( IOT_LOG_WARN, NULL, ... ) @endcode + * #IotLogInfo | @code{c} IotLog( IOT_LOG_INFO, NULL, ... ) @endcode + * #IotLogDebug | @code{c} IotLog( IOT_LOG_DEBUG, NULL, ... ) @endcode * * @param[in] messageLevel Log level of this message. Must be one of the * @ref logging_constants_levels. - * @param[in] pLogConfig Pointer to an #AwsIotLogConfig_t. Optional; pass `NULL` + * @param[in] pLogConfig Pointer to an #IotLogConfig_t. Optional; pass `NULL` * to ignore. * @param[in] ... Message and format specification. * * @return No return value. On errors, it prints nothing. * * @note This function may be implemented as a macro. - * @see @ref logging_function_loggeneric for the generic (not library-specific) + * @see @ref logging_function_generic for the generic (not library-specific) * logging function. */ /** - * @def AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) + * @def IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) * @brief Log the contents of buffer as bytes. Only available when @ref - * _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_DEBUG. + * _LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. * * This function prints the bytes located at a given memory address. It is * intended for debugging only, and is therefore only available when @ref - * _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_DEBUG. + * _LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. * * Log messages printed by this function always include the [log level] * (@ref logging_constants_levels), [library name](@ref _LIBRARY_LOG_NAME), * and time. In addition, this function may print an optional header `pHeader` * before it prints the contents of the buffer. This function does not have an - * #AwsIotLogConfig_t parameter. + * #IotLogConfig_t parameter. * * The logging library must be set up before this function may be called. See * @ref logging_setup_use for more information. @@ -115,9 +115,9 @@ * @code{c} * const uint8_t pBuffer[] = { 0x00, 0x01, 0x02, 0x03 }; * - * AwsIotLog_PrintBuffer( "This buffer contains:", - * pBuffer, - * 4 ); + * IotLog_PrintBuffer( "This buffer contains:", + * pBuffer, + * 4 ); * @endcode * The code above prints something like the following: * @code{c} @@ -127,95 +127,95 @@ */ /** - * @def AwsIotLogError( ... ) - * @brief Abbreviated logging macro for level #AWS_IOT_LOG_ERROR. + * @def IotLogError( ... ) + * @brief Abbreviated logging macro for level #IOT_LOG_ERROR. * * Equivalent to: * @code{c} - * AwsIotLog( AWS_IOT_LOG_ERROR, NULL, ... ) + * IotLog( IOT_LOG_ERROR, NULL, ... ) * @endcode */ /** - * @def AwsIotLogWarn( ... ) - * @brief Abbreviated logging macro for level #AWS_IOT_LOG_WARN. + * @def IotLogWarn( ... ) + * @brief Abbreviated logging macro for level #IOT_LOG_WARN. * * Equivalent to: * @code{c} - * AwsIotLog( AWS_IOT_LOG_WARN, NULL, ... ) + * IotLog( IOT_LOG_WARN, NULL, ... ) * @endcode */ /** - * @def AwsIotLogInfo( ... ) - * @brief Abbreviated logging macro for level #AWS_IOT_LOG_INFO. + * @def IotLogInfo( ... ) + * @brief Abbreviated logging macro for level #IOT_LOG_INFO. * * Equivalent to: * @code{c} - * AwsIotLog( AWS_IOT_LOG_INFO, NULL, ... ) + * IotLog( IOT_LOG_INFO, NULL, ... ) * @endcode */ /** - * @def AwsIotLogDebug( ... ) - * @brief Abbreviated logging macro for level #AWS_IOT_LOG_DEBUG. + * @def IotLogDebug( ... ) + * @brief Abbreviated logging macro for level #IOT_LOG_DEBUG. * * Equivalent to: * @code{c} - * AwsIotLog( AWS_IOT_LOG_DEBUG, NULL, ... ) + * IotLog( IOT_LOG_DEBUG, NULL, ... ) * @endcode */ /* Check that _LIBRARY_LOG_LEVEL is defined and has a valid value. */ #if !defined( _LIBRARY_LOG_LEVEL ) || \ - ( _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_NONE && \ - _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_ERROR && \ - _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_WARN && \ - _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_INFO && \ - _LIBRARY_LOG_LEVEL != AWS_IOT_LOG_DEBUG ) - #error "Please define _LIBRARY_LOG_LEVEL as either AWS_IOT_LOG_NONE, AWS_IOT_LOG_ERROR, AWS_IOT_LOG_WARN, AWS_IOT_LOG_INFO, or AWS_IOT_LOG_DEBUG." + ( _LIBRARY_LOG_LEVEL != IOT_LOG_NONE && \ + _LIBRARY_LOG_LEVEL != IOT_LOG_ERROR && \ + _LIBRARY_LOG_LEVEL != IOT_LOG_WARN && \ + _LIBRARY_LOG_LEVEL != IOT_LOG_INFO && \ + _LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) + #error "Please define _LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." /* Check that _LIBRARY_LOG_NAME is defined and has a valid value. */ #elif !defined( _LIBRARY_LOG_NAME ) #error "Please define _LIBRARY_LOG_NAME." #else - /* Define AwsIotLog if the log level is greater than "none". */ - #if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE - #define AwsIotLog( messageLevel, pLogConfig, ... ) \ - AwsIotLogGeneric( _LIBRARY_LOG_LEVEL, \ - _LIBRARY_LOG_NAME, \ - messageLevel, \ - pLogConfig, \ - __VA_ARGS__ ) + /* Define IotLog if the log level is greater than "none". */ + #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #define IotLog( messageLevel, pLogConfig, ... ) \ + IotLog_Generic( _LIBRARY_LOG_LEVEL, \ + _LIBRARY_LOG_NAME, \ + messageLevel, \ + pLogConfig, \ + __VA_ARGS__ ) /* Define the abbreviated logging macros. */ - #define AwsIotLogError( ... ) AwsIotLog( AWS_IOT_LOG_ERROR, NULL, __VA_ARGS__ ) - #define AwsIotLogWarn( ... ) AwsIotLog( AWS_IOT_LOG_WARN, NULL, __VA_ARGS__ ) - #define AwsIotLogInfo( ... ) AwsIotLog( AWS_IOT_LOG_INFO, NULL, __VA_ARGS__ ) - #define AwsIotLogDebug( ... ) AwsIotLog( AWS_IOT_LOG_DEBUG, NULL, __VA_ARGS__ ) + #define IotLogError( ... ) IotLog( IOT_LOG_ERROR, NULL, __VA_ARGS__ ) + #define IotLogWarn( ... ) IotLog( IOT_LOG_WARN, NULL, __VA_ARGS__ ) + #define IotLogInfo( ... ) IotLog( IOT_LOG_INFO, NULL, __VA_ARGS__ ) + #define IotLogDebug( ... ) IotLog( IOT_LOG_DEBUG, NULL, __VA_ARGS__ ) /* If log level is DEBUG, enable the function to print buffers. */ - #if _LIBRARY_LOG_LEVEL >= AWS_IOT_LOG_DEBUG - #define AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) \ - AwsIotLogGeneric_PrintBuffer( _LIBRARY_LOG_NAME, \ - pHeader, \ - pBuffer, \ - bufferSize ) + #if _LIBRARY_LOG_LEVEL >= IOT_LOG_DEBUG + #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) \ + IotLog_GenericPrintBuffer( _LIBRARY_LOG_NAME, \ + pHeader, \ + pBuffer, \ + bufferSize ) #else - #define AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) + #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) #endif - /* Remove references to AwsIotLog from the source code if logging is disabled. */ + /* Remove references to IotLog from the source code if logging is disabled. */ #else /* @[declare_logging_log] */ - #define AwsIotLog( messageLevel, pLogConfig, ... ) + #define IotLog( messageLevel, pLogConfig, ... ) /* @[declare_logging_log] */ /* @[declare_logging_printbuffer] */ - #define AwsIotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) + #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) /* @[declare_logging_printbuffer] */ - #define AwsIotLogError( ... ) - #define AwsIotLogWarn( ... ) - #define AwsIotLogInfo( ... ) - #define AwsIotLogDebug( ... ) + #define IotLogError( ... ) + #define IotLogWarn( ... ) + #define IotLogInfo( ... ) + #define IotLogDebug( ... ) #endif #endif -#endif /* ifndef _AWS_IOT_LOGGING_SETUP_H_ */ +#endif /* ifndef _IOT_LOGGING_SETUP_H_ */ diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index 7d26328200..3f77e35bf2 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -64,15 +64,15 @@ #ifdef AWS_IOT_LOG_LEVEL_MQTT #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_MQTT #else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif #define _LIBRARY_LOG_NAME ( "MQTT" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /* * Provide default values for undefined memory allocation functions based on diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 622f5c841b..8bf988461c 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -64,15 +64,15 @@ #ifdef AWS_IOT_LOG_LEVEL_SHADOW #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_SHADOW #else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif #define _LIBRARY_LOG_NAME ( "Shadow" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /* * Provide default values for undefined memory allocation functions based on @@ -445,7 +445,7 @@ typedef struct _shadowSubscription } _shadowSubscription_t; /* Declarations of names printed in logs. */ -#if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE +#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE extern const char * const _pAwsIotShadowOperationNames[]; extern const char * const _pAwsIotShadowCallbackNames[]; #endif diff --git a/lib/include/private/aws_iot_logging.h b/lib/include/private/iot_logging.h similarity index 71% rename from lib/include/private/aws_iot_logging.h rename to lib/include/private/iot_logging.h index 26eb5345ce..64dc06aab1 100644 --- a/lib/include/private/aws_iot_logging.h +++ b/lib/include/private/iot_logging.h @@ -20,18 +20,18 @@ */ /** - * @file aws_iot_logging.h + * @file iot_logging.h * @brief Generic logging function header file. * * Declares the generic logging function and the log levels. This file never - * needs to be included in source code. The header aws_iot_logging_setup.h should + * needs to be included in source code. The header iot_logging_setup.h should * be included instead. * - * @see aws_iot_logging_setup.h + * @see iot_logging_setup.h */ -#ifndef _AWS_IOT_LOGGING_H_ -#define _AWS_IOT_LOGGING_H_ +#ifndef _IOT_LOGGING_H_ +#define _IOT_LOGGING_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -47,33 +47,33 @@ * @constantspage{logging,logging library} * * @section logging_constants_levels Log levels - * @brief Log levels for the AWS IoT libraries. + * @brief Log levels for the libraries in this SDK. * * Each library should specify a log level by setting @ref _LIBRARY_LOG_LEVEL. * All log messages with a level at or below the specified level will be printed * for that library. * * Currently, there are 4 log levels. In the order of lowest to highest, they are: - * - #AWS_IOT_LOG_NONE
- * @copybrief AWS_IOT_LOG_NONE - * - #AWS_IOT_LOG_ERROR
- * @copybrief AWS_IOT_LOG_ERROR - * - #AWS_IOT_LOG_WARN
- * @copybrief AWS_IOT_LOG_WARN - * - #AWS_IOT_LOG_INFO
- * @copybrief AWS_IOT_LOG_INFO - * - #AWS_IOT_LOG_DEBUG
- * @copybrief AWS_IOT_LOG_DEBUG + * - #IOT_LOG_NONE
+ * @copybrief IOT_LOG_NONE + * - #IOT_LOG_ERROR
+ * @copybrief IOT_LOG_ERROR + * - #IOT_LOG_WARN
+ * @copybrief IOT_LOG_WARN + * - #IOT_LOG_INFO
+ * @copybrief IOT_LOG_INFO + * - #IOT_LOG_DEBUG
+ * @copybrief IOT_LOG_DEBUG */ /** * @brief No log messages. * * Log messages with this level will be silently discarded. When @ref - * _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_NONE, logging is disabled and no [logging functions] + * _LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no [logging functions] * (@ref logging_functions) can be called. */ -#define AWS_IOT_LOG_NONE 0 +#define IOT_LOG_NONE 0 /** * @brief Only critical, unrecoverable errors. @@ -81,7 +81,7 @@ * Log messages with this level will be printed when a library encounters an * error from which it cannot easily recover. */ -#define AWS_IOT_LOG_ERROR 1 +#define IOT_LOG_ERROR 1 /** * @brief Message about an abnormal but recoverable event. @@ -90,7 +90,7 @@ * abnormal event that may be indicative of an error. Libraries should continue * execution after logging a warning. */ -#define AWS_IOT_LOG_WARN 2 +#define IOT_LOG_WARN 2 /** * @brief A helpful, informational message. @@ -98,7 +98,7 @@ * Log messages with this level may indicate the normal status of a library * function. They should be used to track how far a program has executed. */ -#define AWS_IOT_LOG_INFO 3 +#define IOT_LOG_INFO 3 /** * @brief Detailed and excessive debug information. @@ -107,7 +107,7 @@ * excessive information such as internal variables, buffers, or other specific * information. */ -#define AWS_IOT_LOG_DEBUG 4 +#define IOT_LOG_DEBUG 4 /** * @paramstructs{logging,logging} @@ -117,16 +117,16 @@ * @ingroup logging_datatypes_paramstructs * @brief Log message configuration struct. * - * @paramfor @ref logging_function_log, @ref logging_function_loggeneric + * @paramfor @ref logging_function_log, @ref logging_function_generic * * By default, log messages print the library name, log level, and a timestring. - * This struct can be passed to @ref logging_function_loggeneric to disable one of + * This struct can be passed to @ref logging_function_generic to disable one of * the above components in the log message. * - * **Example:** + * Example: * * @code{c} - * AwsIotLogGeneric( AWS_IOT_LOG_DEBUG, "SAMPLE", AWS_IOT_LOG_DEBUG, NULL, "Hello world!" ); + * IotLog_Generic( IOT_LOG_DEBUG, "SAMPLE", IOT_LOG_DEBUG, NULL, "Hello world!" ); * @endcode * The code above prints the following message: * @code @@ -135,33 +135,33 @@ * * The timestring can be disabled as follows: * @code - * AwsIotLogConfig_t logConfig = { .hideLogLevel = false, .hideLibraryName = false, .hideTimestring = true}; - * AwsIotLogGeneric( AWS_IOT_LOG_DEBUG, "SAMPLE", AWS_IOT_LOG_DEBUG, &logConfig, "Hello world!" ); + * IotLogConfig_t logConfig = { .hideLogLevel = false, .hideLibraryName = false, .hideTimestring = true}; + * IotLog_Generic( IOT_LOG_DEBUG, "SAMPLE", IOT_LOG_DEBUG, &logConfig, "Hello world!" ); * @endcode * The resulting log message will be: * @code * [DEBUG][SAMPLE] Hello world! * @endcode */ -typedef struct AwsIotLogConfig +typedef struct IotLogConfig { bool hideLogLevel; /**< @brief Don't print the log level string for this message. */ bool hideLibraryName; /**< @brief Don't print the library name for this message. */ bool hideTimestring; /**< @brief Don't print the timestring for this message. */ -} AwsIotLogConfig_t; +} IotLogConfig_t; /** * @functionspage{logging,logging library} * * - @functionname{logging_function_log} * - @functionname{logging_function_printbuffer} - * - @functionname{logging_function_loggeneric} + * - @functionname{logging_function_generic} * - @functionname{logging_function_genericprintbuffer} */ /** - * @functionpage{AwsIotLogGeneric,logging,loggeneric} - * @functionpage{AwsIotLogGeneric_PrintBuffer,logging,genericprintbuffer} + * @functionpage{IotLog_Generic,logging,generic} + * @functionpage{IotLog_PrintBuffer,logging,genericprintbuffer} */ /** @@ -170,7 +170,7 @@ typedef struct AwsIotLogConfig * This function is the generic logging function shared across all libraries. * The library-specific logging function @ref logging_function_log is implemented * using this function. Like @ref logging_function_log, this function is only - * available when @ref _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_NONE. + * available when @ref _LIBRARY_LOG_LEVEL is #IOT_LOG_NONE. * * In most cases, the library-specific logging function @ref logging_function_log * should be called instead of this function. @@ -180,20 +180,20 @@ typedef struct AwsIotLogConfig * logging_constants_levels. * @param[in] pLibraryName The library name to print. See @ref _LIBRARY_LOG_NAME. * @param[in] messageLevel The log level of the this message. See @ref _LIBRARY_LOG_LEVEL. - * @param[in] pLogConfig Pointer to a #AwsIotLogConfig_t. Optional; pass `NULL` to ignore. + * @param[in] pLogConfig Pointer to a #IotLogConfig_t. Optional; pass `NULL` to ignore. * @param[in] pFormat Format string for the log message. * @param[in] ... Arguments for format specification. * * @return No return value. On errors, it prints nothing. */ -/* @[declare_logging_loggeneric] */ -void AwsIotLogGeneric( int libraryLogSetting, - const char * const pLibraryName, - int messageLevel, - const AwsIotLogConfig_t * const pLogConfig, - const char * const pFormat, - ... ); -/* @[declare_logging_loggeneric] */ +/* @[declare_logging_generic] */ +void IotLog_Generic( int libraryLogSetting, + const char * const pLibraryName, + int messageLevel, + const IotLogConfig_t * const pLogConfig, + const char * const pFormat, + ... ); +/* @[declare_logging_generic] */ /** * @brief Generic function to log the contents of a buffer as bytes. @@ -201,7 +201,7 @@ void AwsIotLogGeneric( int libraryLogSetting, * This function is the generic buffer logging function shared across all libraries. * The library-specific buffer logging function @ref logging_function_printbuffer is * implemented using this function. Like @ref logging_function_printbuffer, this - * function is only available when @ref _LIBRARY_LOG_LEVEL is #AWS_IOT_LOG_DEBUG. + * function is only available when @ref _LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. * * In most cases, the library-specific buffer logging function @ref * logging_function_printbuffer should be called instead of this function. @@ -218,10 +218,10 @@ void AwsIotLogGeneric( int libraryLogSetting, * appear "fragmented" if other threads are logging simultaneously. */ /* @[declare_logging_genericprintbuffer] */ -void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, - const char * const pHeader, - const uint8_t * const pBuffer, - size_t bufferSize ); +void IotLog_GenericPrintBuffer( const char * const pLibraryName, + const char * const pHeader, + const uint8_t * const pBuffer, + size_t bufferSize ); /* @[declare_logging_genericprintbuffer] */ -#endif /* ifndef _AWS_IOT_LOGGING_H_ */ +#endif /* ifndef _IOT_LOGGING_H_ */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 6c53250e10..94516afb8f 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,8 +1,8 @@ # Common libraries source files. add_library( iotcommon SHARED iot_common.c - aws_iot_json_utils.c - aws_iot_logging.c + iot_json_utils.c + iot_logging.c static_memory/iot_static_memory_common.c static_memory/iot_static_memory_mqtt.c static_memory/aws_iot_static_memory_shadow.c ) diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c index 58cbb7827b..29d6bb4a59 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_common.c @@ -33,14 +33,14 @@ #endif /* Configure logs for the functions in this file. */ -#ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL +#ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #define _LIBRARY_LOG_NAME ( "COMMON" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /*-----------------------------------------------------------*/ @@ -54,13 +54,13 @@ bool IotCommon_Init( void ) if( status == false ) { - AwsIotLogError( "Failed to initialize static memory." ); + IotLogError( "Failed to initialize static memory." ); } #endif if( status == true ) { - AwsIotLogInfo( "Common libraries successfully initialized." ); + IotLogInfo( "Common libraries successfully initialized." ); } return status; @@ -75,7 +75,7 @@ void IotCommon_Cleanup( void ) IotStaticMemory_Cleanup(); #endif - AwsIotLogInfo( "Common libraries cleanup done." ); + IotLogInfo( "Common libraries cleanup done." ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/common/aws_iot_json_utils.c b/lib/source/common/iot_json_utils.c similarity index 94% rename from lib/source/common/aws_iot_json_utils.c rename to lib/source/common/iot_json_utils.c index d442e6dc48..a0aacedab6 100644 --- a/lib/source/common/aws_iot_json_utils.c +++ b/lib/source/common/iot_json_utils.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_json_utils.c - * @brief Implements the functions in aws_iot_json_utils.h + * @file iot_json_utils.c + * @brief Implements the functions in iot_json_utils.h */ /* Build using a config header, if provided. */ @@ -33,16 +33,16 @@ #include /* JSON utilities include. */ -#include "aws_iot_json_utils.h" +#include "iot_json_utils.h" /*-----------------------------------------------------------*/ -bool AwsIotJsonUtils_FindJsonValue( const char * const pJsonDocument, - size_t jsonDocumentLength, - const char * const pJsonKey, - size_t jsonKeyLength, - const char ** const pJsonValue, - size_t * const pJsonValueLength ) +bool IotJsonUtils_FindJsonValue( const char * const pJsonDocument, + size_t jsonDocumentLength, + const char * const pJsonKey, + size_t jsonKeyLength, + const char ** const pJsonValue, + size_t * const pJsonValueLength ) { size_t i = 0; size_t jsonValueLength = 0; diff --git a/lib/source/common/aws_iot_logging.c b/lib/source/common/iot_logging.c similarity index 80% rename from lib/source/common/aws_iot_logging.c rename to lib/source/common/iot_logging.c index 461db46602..21fc60b6f2 100644 --- a/lib/source/common/aws_iot_logging.c +++ b/lib/source/common/iot_logging.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_logging.c - * @brief Implementation of logging functions from aws_iot_logging.h + * @file iot_logging.c + * @brief Implementation of logging functions from iot_logging.h */ /* Build using a config header, if provided. */ @@ -37,7 +37,7 @@ #include "platform/iot_clock.h" /* Logging includes. */ -#include "private/aws_iot_logging.h" +#include "private/iot_logging.h" /** * @cond DOXYGEN_IGNORE @@ -63,40 +63,40 @@ extern int vsnprintf( char *, /* This implementation assumes the following values for the log level constants. * Ensure that the values have not been modified. */ -#if AWS_IOT_LOG_NONE != 0 - #error "AWS_IOT_LOG_NONE must be 0." +#if IOT_LOG_NONE != 0 + #error "IOT_LOG_NONE must be 0." #endif -#if AWS_IOT_LOG_ERROR != 1 - #error "AWS_IOT_LOG_ERROR must be 1." +#if IOT_LOG_ERROR != 1 + #error "IOT_LOG_ERROR must be 1." #endif -#if AWS_IOT_LOG_WARN != 2 - #error "AWS_IOT_LOG_WARN must be 2." +#if IOT_LOG_WARN != 2 + #error "IOT_LOG_WARN must be 2." #endif -#if AWS_IOT_LOG_INFO != 3 - #error "AWS_IOT_LOG_INFO must be 3." +#if IOT_LOG_INFO != 3 + #error "IOT_LOG_INFO must be 3." #endif -#if AWS_IOT_LOG_DEBUG != 4 - #error "AWS_IOT_LOG_DEBUG must be 4." +#if IOT_LOG_DEBUG != 4 + #error "IOT_LOG_DEBUG must be 4." #endif /** - * @def AwsIotLogging_Puts( message ) + * @def IotLogging_Puts( message ) * @brief Function the logging library uses to print a line. * * This function can be set by using a define. By default, the standard library * [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) * function is used. */ -#ifndef AwsIotLogging_Puts +#ifndef IotLogging_Puts /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. */ - extern int puts( const char * ); - /** @endcond */ +extern int puts( const char * ); +/** @endcond */ - #define AwsIotLogging_Puts puts + #define IotLogging_Puts puts #endif /* @@ -111,34 +111,34 @@ extern int vsnprintf( char *, * @brief Allocate a new logging buffer. This function must have the same * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef AwsIotLogging_Malloc - #define AwsIotLogging_Malloc Iot_MallocMessageBuffer + #ifndef IotLogging_Malloc + #define IotLogging_Malloc Iot_MallocMessageBuffer #endif /** * @brief Free a logging buffer. This function must have the same signature * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef AwsIotLogging_Free - #define AwsIotLogging_Free Iot_FreeMessageBuffer + #ifndef IotLogging_Free + #define IotLogging_Free Iot_FreeMessageBuffer #endif /** * @brief Get the size of a logging buffer. Statically-allocated buffers * should all have the same size. */ - #ifndef AwsIotLogging_StaticBufferSize - #define AwsIotLogging_StaticBufferSize Iot_MessageBufferSize + #ifndef IotLogging_StaticBufferSize + #define IotLogging_StaticBufferSize Iot_MessageBufferSize #endif #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #ifndef AwsIotLogging_Malloc + #ifndef IotLogging_Malloc #include - #define AwsIotLogging_Malloc malloc + #define IotLogging_Malloc malloc #endif - #ifndef AwsIotLogging_Free + #ifndef IotLogging_Free #include - #define AwsIotLogging_Free free + #define IotLogging_Free free #endif #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ @@ -175,11 +175,11 @@ extern int vsnprintf( char *, */ static const char * const _pLogLevelStrings[ 5 ] = { - "", /* AWS_IOT_LOG_NONE */ - "ERROR", /* AWS_IOT_LOG_ERROR */ - "WARN ", /* AWS_IOT_LOG_WARN */ - "INFO ", /* AWS_IOT_LOG_INFO */ - "DEBUG" /* AWS_IOT_LOG_DEBUG */ + "", /* IOT_LOG_NONE */ + "ERROR", /* IOT_LOG_ERROR */ + "WARN ", /* IOT_LOG_WARN */ + "INFO ", /* IOT_LOG_INFO */ + "DEBUG" /* IOT_LOG_DEBUG */ }; /*-----------------------------------------------------------*/ @@ -189,34 +189,36 @@ static const char * const _pLogLevelStrings[ 5 ] = size_t newSize, size_t oldSize ) { + bool status = false; + /* Allocate a new, larger buffer. */ - void * pNewBuffer = AwsIotLogging_Malloc( newSize ); + void * pNewBuffer = IotLogging_Malloc( newSize ); /* Ensure that memory allocation succeeded. */ - if( pNewBuffer == NULL ) + if( pNewBuffer != NULL ) { - return false; - } + /* Copy the data from the old buffer to the new buffer. */ + ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); - /* Copy the data from the old buffer to the new buffer. */ - ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); + /* Free the old buffer and update the pointer. */ + IotLogging_Free( *pOldBuffer ); + *pOldBuffer = pNewBuffer; - /* Free the old buffer and update the pointer. */ - AwsIotLogging_Free( *pOldBuffer ); - *pOldBuffer = pNewBuffer; + status = true; + } - return true; + return status; } #endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ /*-----------------------------------------------------------*/ -void AwsIotLogGeneric( int libraryLogSetting, - const char * const pLibraryName, - int messageLevel, - const AwsIotLogConfig_t * const pLogConfig, - const char * const pFormat, - ... ) +void IotLog_Generic( int libraryLogSetting, + const char * const pLibraryName, + int messageLevel, + const IotLogConfig_t * const pLogConfig, + const char * const pFormat, + ... ) { int requiredMessageSize = 0; size_t bufferSize = 0, @@ -256,7 +258,7 @@ void AwsIotLogGeneric( int libraryLogSetting, /* In static memory mode, check that the log message will fit in the a * static buffer. */ #if IOT_STATIC_MEMORY_ONLY == 1 - if( bufferSize >= AwsIotLogging_StaticBufferSize() ) + if( bufferSize >= IotLogging_StaticBufferSize() ) { /* If the static buffers are likely too small to fit the log message, * return. */ @@ -264,11 +266,11 @@ void AwsIotLogGeneric( int libraryLogSetting, } /* Otherwise, update the buffer size to the size of a static buffer. */ - bufferSize = AwsIotLogging_StaticBufferSize(); + bufferSize = IotLogging_StaticBufferSize(); #endif /* Allocate memory for the logging buffer. */ - pLoggingBuffer = ( char * ) AwsIotLogging_Malloc( bufferSize ); + pLoggingBuffer = ( char * ) IotLogging_Malloc( bufferSize ); if( pLoggingBuffer == NULL ) { @@ -279,7 +281,7 @@ void AwsIotLogGeneric( int libraryLogSetting, if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) { /* Ensure that message level is valid. */ - if( ( messageLevel >= AWS_IOT_LOG_NONE ) && ( messageLevel <= AWS_IOT_LOG_DEBUG ) ) + if( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ) { /* Add the log level string to the logging buffer. */ requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, @@ -290,7 +292,7 @@ void AwsIotLogGeneric( int libraryLogSetting, /* Check for encoding errors. */ if( requiredMessageSize <= 0 ) { - AwsIotLogging_Free( pLoggingBuffer ); + IotLogging_Free( pLoggingBuffer ); return; } @@ -312,7 +314,7 @@ void AwsIotLogGeneric( int libraryLogSetting, /* Check for encoding errors. */ if( requiredMessageSize <= 0 ) { - AwsIotLogging_Free( pLoggingBuffer ); + IotLogging_Free( pLoggingBuffer ); return; } @@ -374,7 +376,7 @@ void AwsIotLogGeneric( int libraryLogSetting, /* There's no point trying to allocate a larger static buffer. Return * immediately. */ - AwsIotLogging_Free( pLoggingBuffer ); + IotLogging_Free( pLoggingBuffer ); return; #else @@ -383,7 +385,7 @@ void AwsIotLogGeneric( int libraryLogSetting, bufferSize ) == false ) { /* If buffer reallocation failed, return. */ - AwsIotLogging_Free( pLoggingBuffer ); + IotLogging_Free( pLoggingBuffer ); return; } @@ -405,31 +407,31 @@ void AwsIotLogGeneric( int libraryLogSetting, /* Check for encoding errors. */ if( requiredMessageSize <= 0 ) { - AwsIotLogging_Free( pLoggingBuffer ); + IotLogging_Free( pLoggingBuffer ); return; } /* Print the logging buffer to stdout. */ - AwsIotLogging_Puts( pLoggingBuffer ); + IotLogging_Puts( pLoggingBuffer ); /* Free the logging buffer. */ - AwsIotLogging_Free( pLoggingBuffer ); + IotLogging_Free( pLoggingBuffer ); } /*-----------------------------------------------------------*/ -void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, - const char * const pHeader, - const uint8_t * const pBuffer, - size_t bufferSize ) +void IotLog_GenericPrintBuffer( const char * const pLibraryName, + const char * const pHeader, + const uint8_t * const pBuffer, + size_t bufferSize ) { size_t i = 0, offset = 0; /* Allocate memory to hold each line of the log message. Since each byte * of pBuffer is printed in 4 characters (2 digits, a space, and a null- * terminator), the size of each line is 4 * _BYTES_PER_LINE. */ - char * pMessageBuffer = AwsIotLogging_Malloc( 4 * _BYTES_PER_LINE ); + char * pMessageBuffer = IotLogging_Malloc( 4 * _BYTES_PER_LINE ); /* Exit if no memory is available. */ if( pMessageBuffer == NULL ) @@ -440,11 +442,11 @@ void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, /* Print pHeader before printing pBuffer. */ if( pHeader != NULL ) { - AwsIotLogGeneric( AWS_IOT_LOG_DEBUG, - pLibraryName, - AWS_IOT_LOG_DEBUG, - NULL, - pHeader ); + IotLog_Generic( IOT_LOG_DEBUG, + pLibraryName, + IOT_LOG_DEBUG, + NULL, + pHeader ); } /* Print each byte in pBuffer. */ @@ -454,7 +456,7 @@ void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, * at the beginning (when i=0). */ if( ( i % _BYTES_PER_LINE == 0 ) && ( i != 0 ) ) { - AwsIotLogging_Puts( pMessageBuffer ); + IotLogging_Puts( pMessageBuffer ); /* Reset offset so that pMessageBuffer is filled from the beginning. */ offset = 0; @@ -470,10 +472,10 @@ void AwsIotLogGeneric_PrintBuffer( const char * const pLibraryName, } /* Print the final line of bytes. This line isn't printed by the for-loop above. */ - AwsIotLogging_Puts( pMessageBuffer ); + IotLogging_Puts( pMessageBuffer ); /* Free memory used by this function. */ - AwsIotLogging_Free( pMessageBuffer ); + IotLogging_Free( pMessageBuffer ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index 093def535a..f383368a0f 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,6 +1,7 @@ # MQTT library source files. add_library( iotmqtt SHARED aws_iot_mqtt_api.c + iot_mqtt_network.c aws_iot_mqtt_operation.c aws_iot_mqtt_serialize.c aws_iot_mqtt_subscription.c diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index d5235c1912..c945bd9ee7 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -21,7 +21,7 @@ /** * @file aws_iot_mqtt_api.c - * @brief Implements the user-facing functions of the MQTT library. + * @brief Implements most user-facing functions of the MQTT library. */ /* Build using a config header, if provided. */ @@ -117,15 +117,6 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, static void _processPublishRetryEvent( bool awsIotMqttMode, _mqttTimerEvent_t * const pPublishRetryEvent ); -/** - * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. - * - * @param[in] pMqttConnection Which connection the PUBACK should be sent over. - * @param[in] packetIdentifier Which packet identifier to include in PUBACK. - */ -static void _sendPuback( _mqttConnection_t * const pMqttConnection, - uint16_t packetIdentifier ); - /** * @brief Handles timer expirations for an MQTT connection. * @@ -262,13 +253,13 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, if( pKeepAliveEvent->checkPingresp == false ) { /* If keep-alive isn't waiting for PINGRESP, send PINGREQ. */ - AwsIotLogDebug( "Sending PINGREQ." ); + IotLogDebug( "Sending PINGREQ." ); /* Add the PINGREQ operation to the send queue for network transmission. */ if( AwsIotMqttInternal_EnqueueOperation( pMqttConnection->pPingreqOperation, &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to enqueue PINGREQ for sending." ); + IotLogWarn( "Failed to enqueue PINGREQ for sending." ); status = false; } else @@ -283,7 +274,7 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, /* Check that a PINGRESP is immediately available. */ if( AwsIotMqtt_Wait( pMqttConnection->pPingreqOperation, 0 ) == AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogDebug( "PINGRESP received." ); + IotLogDebug( "PINGRESP received." ); /* The next keep-alive event should send another PINGREQ. */ pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + @@ -293,7 +284,7 @@ static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, else { /* PINGRESP was not received within AWS_IOT_MQTT_PINGRESP_WAIT_MS. */ - AwsIotLogError( "Timeout waiting on PINGRESP." ); + IotLogError( "Timeout waiting on PINGRESP." ); /* Set the error flag. The MQTT connection will be closed. */ pMqttConnection->errorOccurred = true; @@ -341,9 +332,9 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, /* Check if the retry limit is reached. */ if( pPublishRetryEvent->retry.count > pPublishRetryEvent->retry.limit ) { - AwsIotLogError( "No PUBACK received for PUBLISH %hu after %d retransmissions.", - pOperation->packetIdentifier, - pPublishRetryEvent->retry.limit ); + IotLogError( "No PUBACK received for PUBLISH %hu after %d retransmissions.", + pOperation->packetIdentifier, + pPublishRetryEvent->retry.limit ); /* Set a status of "No response to retries" and notify. */ pOperation->status = AWS_IOT_MQTT_RETRY_NO_RESPONSE; @@ -387,31 +378,31 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, } /* Print debug log messages about this PUBLISH retry. */ - AwsIotLogDebug( "No PUBACK received for PUBLISH %hu. Attempting retransmission" - " %d of %d.", - pOperation->packetIdentifier, - pPublishRetryEvent->retry.count, - pPublishRetryEvent->retry.limit ); + IotLogDebug( "No PUBACK received for PUBLISH %hu. Attempting retransmission" + " %d of %d.", + pOperation->packetIdentifier, + pPublishRetryEvent->retry.count, + pPublishRetryEvent->retry.limit ); if( pPublishRetryEvent->retry.count < pPublishRetryEvent->retry.limit ) { - AwsIotLogDebug( "Next retry for PUBLISH %hu in %llu ms.", - pOperation->packetIdentifier, - pPublishRetryEvent->retry.nextPeriod ); + IotLogDebug( "Next retry for PUBLISH %hu in %llu ms.", + pOperation->packetIdentifier, + pPublishRetryEvent->retry.nextPeriod ); } else { - AwsIotLogDebug( "Final retry for PUBLISH %hu. Will check in %llu ms " - "for response.", - pOperation->packetIdentifier, - AWS_IOT_MQTT_RESPONSE_WAIT_MS ); + IotLogDebug( "Final retry for PUBLISH %hu. Will check in %llu ms " + "for response.", + pOperation->packetIdentifier, + AWS_IOT_MQTT_RESPONSE_WAIT_MS ); } /* Add the PUBLISH to the send queue for network transmission. */ if( AwsIotMqttInternal_EnqueueOperation( pOperation, &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to enqueue PUBLISH retry for sending." ); + IotLogWarn( "Failed to enqueue PUBLISH retry for sending." ); } } } @@ -419,73 +410,18 @@ static void _processPublishRetryEvent( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -static void _sendPuback( _mqttConnection_t * const pMqttConnection, - uint16_t packetIdentifier ) -{ - _mqttOperation_t * pPubackOperation = NULL; - - /* Choose a PUBACK serializer function. */ - AwsIotMqttError_t ( * serializePuback )( uint16_t, - uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializePuback; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.puback != NULL ) - { - serializePuback = pMqttConnection->network.serialize.puback; - } - #endif - - AwsIotLogDebug( "Sending PUBACK for received PUBLISH %hu.", packetIdentifier ); - - /* Create a PUBACK operation. */ - if( AwsIotMqttInternal_CreateOperation( &pPubackOperation, - 0, - NULL ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to create PUBACK operation." ); - - return; - } - - /* Generate a PUBACK packet from the packet identifier. */ - if( serializePuback( packetIdentifier, - &( pPubackOperation->pMqttPacket ), - &( pPubackOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to generate PUBACK packet." ); - AwsIotMqttInternal_DestroyOperation( pPubackOperation ); - - return; - } - - /* Set the remaining members of the PUBACK operation and push it to the - * send queue for network transmission. */ - pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; - pPubackOperation->pMqttConnection = pMqttConnection; - - if( AwsIotMqttInternal_EnqueueOperation( pPubackOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to enqueue PUBACK for sending." ); - AwsIotMqttInternal_DestroyOperation( pPubackOperation ); - } -} - -/*-----------------------------------------------------------*/ - static void _timerThread( void * pArgument ) { _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pArgument; _mqttTimerEvent_t * pTimerEvent = NULL; - AwsIotLogDebug( "Timer thread started for connection %p.", pMqttConnection ); + IotLogDebug( "Timer thread started for connection %p.", pMqttConnection ); /* Attempt to lock the timer mutex before this thread does anything. * Return immediately if the mutex couldn't be locked. */ if( IotMutex_TryLock( &( pMqttConnection->timerMutex ) ) == false ) { - AwsIotLogWarn( "Failed to lock connection timer mutex in timer thread. Exiting." ); + IotLogWarn( "Failed to lock connection timer mutex in timer thread. Exiting." ); return; } @@ -513,8 +449,8 @@ static void _timerThread( void * pArgument ) pTimerEvent->expirationTime - IotClock_GetTimeMs(), 0 ) == false ) { - AwsIotLogWarn( "Failed to re-arm timer for connection %p.", - pMqttConnection ); + IotLogWarn( "Failed to re-arm timer for connection %p.", + pMqttConnection ); } pTimerEvent = NULL; @@ -524,13 +460,13 @@ static void _timerThread( void * pArgument ) /* If there are no timer events to process, terminate this thread. */ if( pTimerEvent == NULL ) { - AwsIotLogDebug( "No further timer events to process. Exiting timer thread." ); + IotLogDebug( "No further timer events to process. Exiting timer thread." ); break; } - AwsIotLogDebug( "Processing timer event for %s.", - AwsIotMqtt_OperationType( pTimerEvent->pOperation->operation ) ); + IotLogDebug( "Processing timer event for %s.", + AwsIotMqtt_OperationType( pTimerEvent->pOperation->operation ) ); /* Process the received timer event. Currently, only PINGREQ and PUBLISH * operations send timer events. */ @@ -548,7 +484,7 @@ static void _timerThread( void * pArgument ) } else { - AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); + IotLogWarn( "No disconnect function was set. Network connection not closed." ); } } @@ -600,7 +536,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, if( pNewMqttConnection == NULL ) { - AwsIotLogError( "Failed to allocate memory for new MQTT connection." ); + IotLogError( "Failed to allocate memory for new MQTT connection." ); return NULL; } @@ -614,7 +550,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, /* Create the timer mutex for a new connection. */ if( IotMutex_Create( &( pNewMqttConnection->timerMutex ) ) == false ) { - AwsIotLogError( "Failed to create timer mutex for new connection." ); + IotLogError( "Failed to create timer mutex for new connection." ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); return NULL; @@ -622,7 +558,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, if( IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ) ) == false ) { - AwsIotLogError( "Failed to create subscription mutex for new connection." ); + IotLogError( "Failed to create subscription mutex for new connection." ); IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); @@ -638,7 +574,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, _timerThread, pNewMqttConnection ) == false ) { - AwsIotLogError( "Failed to create timer for new connection." ); + IotLogError( "Failed to create timer for new connection." ); IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); IotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); @@ -657,7 +593,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, if( pNewMqttConnection->pKeepAliveEvent == NULL ) { - AwsIotLogError( "Failed to allocate keep-alive event for new connection." ); + IotLogError( "Failed to allocate keep-alive event for new connection." ); _destroyMqttConnection( pNewMqttConnection ); return NULL; @@ -668,7 +604,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, AWS_IOT_MQTT_FLAG_WAITABLE, NULL ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to allocate PINGREQ operation for new connection." ); + IotLogError( "Failed to allocate PINGREQ operation for new connection." ); _destroyMqttConnection( pNewMqttConnection ); return NULL; @@ -692,7 +628,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, if( serializePingreq( &( pNewMqttConnection->pPingreqOperation->pMqttPacket ), &( pNewMqttConnection->pPingreqOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to serialize PINGREQ packet for new connection." ); + IotLogError( "Failed to serialize PINGREQ packet for new connection." ); _destroyMqttConnection( pNewMqttConnection ); return NULL; @@ -797,13 +733,13 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio * applies only to will messages, and not normal PUBLISH messages. */ if( pWillInfo->payloadLength > UINT16_MAX ) { - AwsIotLogError( "Will payload cannot be larger than 65535." ); + IotLogError( "Will payload cannot be larger than 65535." ); return AWS_IOT_MQTT_BAD_PARAMETER; } } - AwsIotLogInfo( "Establishing new MQTT connection." ); + IotLogInfo( "Establishing new MQTT connection." ); /* Create a CONNECT operation. */ connectStatus = AwsIotMqttInternal_CreateOperation( &pConnectOperation, @@ -885,7 +821,7 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation, &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to enqueue CONNECT for sending." ); + IotLogError( "Failed to enqueue CONNECT for sending." ); connectStatus = AWS_IOT_MQTT_NO_MEMORY; AwsIotMqttInternal_DestroyOperation( pConnectOperation ); } @@ -904,13 +840,13 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio if( ( connectStatus == AWS_IOT_MQTT_SUCCESS ) && ( pNewMqttConnection->keepAliveSeconds > 0 ) ) { - AwsIotLogDebug( "Starting new MQTT connection timer." ); + IotLogDebug( "Starting new MQTT connection timer." ); if( IotClock_TimerArm( &( pNewMqttConnection->timer ), pNewMqttConnection->pKeepAliveEvent->expirationTime - IotClock_GetTimeMs(), 0 ) == false ) { - AwsIotLogError( "Failed to start connection timer for new MQTT connection" ); + IotLogError( "Failed to start connection timer for new MQTT connection" ); connectStatus = AWS_IOT_MQTT_INIT_FAILED; } @@ -919,7 +855,7 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio /* Check the status of the CONNECT operation. */ if( connectStatus == AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); + IotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); } else { @@ -927,8 +863,8 @@ static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnectio _destroyMqttConnection( pNewMqttConnection ); *pMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotLogError( "Failed to establish new MQTT connection, error %s.", - AwsIotMqtt_strerror( connectStatus ) ); + IotLogError( "Failed to establish new MQTT connection, error %s.", + AwsIotMqtt_strerror( connectStatus ) ); } return connectStatus; @@ -987,8 +923,8 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && ( pSubscriptionRef == NULL ) ) { - AwsIotLogError( "Reference must be provided for a waitable %s.", - AwsIotMqtt_OperationType( operation ) ); + IotLogError( "Reference must be provided for a waitable %s.", + AwsIotMqtt_OperationType( operation ) ); return AWS_IOT_MQTT_BAD_PARAMETER; } @@ -1071,8 +1007,8 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio if( AwsIotMqttInternal_EnqueueOperation( pSubscriptionOperation, &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to enqueue %s for sending.", - AwsIotMqtt_OperationType( operation ) ); + IotLogError( "Failed to enqueue %s for sending.", + AwsIotMqtt_OperationType( operation ) ); if( operation == AWS_IOT_MQTT_SUBSCRIBE ) { @@ -1092,8 +1028,8 @@ static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operatio return AWS_IOT_MQTT_NO_MEMORY; } - AwsIotLogInfo( "MQTT %s operation queued.", - AwsIotMqtt_OperationType( operation ) ); + IotLogInfo( "MQTT %s operation queued.", + AwsIotMqtt_OperationType( operation ) ); /* The subscription operation is waiting for a network response. */ return AWS_IOT_MQTT_STATUS_PENDING; @@ -1108,7 +1044,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) /* Create mutex protecting MQTT operation queues. */ if( IotMutex_Create( &( _IotMqttQueueMutex ) ) == false ) { - AwsIotLogError( "Failed to initialize MQTT operation queue mutex." ); + IotLogError( "Failed to initialize MQTT operation queue mutex." ); status = AWS_IOT_MQTT_INIT_FAILED; } @@ -1117,7 +1053,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) { if( IotMutex_Create( &( _IotMqttPendingResponseMutex ) ) == false ) { - AwsIotLogError( "Failed to initialize MQTT library pending response mutex." ); + IotLogError( "Failed to initialize MQTT library pending response mutex." ); IotMutex_Destroy( &( _IotMqttQueueMutex ) ); status = AWS_IOT_MQTT_INIT_FAILED; @@ -1129,7 +1065,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) { if( IotMutex_Create( &( _connectMutex ) ) == false ) { - AwsIotLogError( "Failed to initialize MQTT library connect mutex." ); + IotLogError( "Failed to initialize MQTT library connect mutex." ); IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); IotMutex_Destroy( &( _IotMqttQueueMutex ) ); @@ -1145,7 +1081,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) AWS_IOT_MQTT_MAX_CALLBACK_THREADS, AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) { - AwsIotLogError( "Failed to initialize record of active MQTT callback threads." ); + IotLogError( "Failed to initialize record of active MQTT callback threads." ); status = AWS_IOT_MQTT_INIT_FAILED; } else @@ -1155,7 +1091,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) AWS_IOT_MQTT_MAX_SEND_THREADS, AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) { - AwsIotLogError( "Failed to initialize record of active MQTT send threads." ); + IotLogError( "Failed to initialize record of active MQTT send threads." ); status = AWS_IOT_MQTT_INIT_FAILED; IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); @@ -1177,7 +1113,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) { if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to initialize MQTT library serializer. " ); + IotLogError( "Failed to initialize MQTT library serializer. " ); IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); IotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); IotMutex_Destroy( &( _connectMutex ) ); @@ -1195,7 +1131,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) IotQueue_Create( &( _IotMqttSend.queue ) ); IotListDouble_Create( &( _IotMqttPendingResponse ) ); - AwsIotLogInfo( "MQTT library successfully initialized." ); + IotLogInfo( "MQTT library successfully initialized." ); } return status; @@ -1243,437 +1179,7 @@ void AwsIotMqtt_Cleanup() /* Clean up MQTT serializer. */ AwsIotMqttInternal_CleanupSerialize(); - AwsIotLogInfo( "MQTT library cleanup done." ); -} - -/*-----------------------------------------------------------*/ - -int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, - const void * pReceivedData, - size_t offset, - size_t dataLength, - void ( *freeReceivedData )( void * ) ) -{ - size_t bytesProcessed = 0, totalBytesProcessed = 0, remainingDataLength = 0; - _mqttConnection_t * pConnectionInfo = *( ( _mqttConnection_t ** ) ( pMqttConnection ) ); - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - uint16_t packetIdentifier = 0; - const uint8_t * pNextPacket = ( const uint8_t * ) pReceivedData; - _mqttOperation_t * pOperation = NULL, * pFirstPublish = NULL, * pLastPublish = NULL; - - /* Choose a packet type decoder function. */ - uint8_t ( * getPacketType )( const uint8_t * const, - size_t ) = AwsIotMqttInternal_GetPacketType; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.getPacketType != NULL ) - { - getPacketType = pConnectionInfo->network.getPacketType; - } - #endif - - /* Ensure that offset is smaller than dataLength. */ - if( offset >= dataLength ) - { - return 0; - } - - /* Adjust the packet pointer based on the offset. */ - pNextPacket += offset; - remainingDataLength = dataLength - offset; - - /* Process the stream of data until the entire stream is proccessed or an - * incomplete packet is found. */ - while( ( totalBytesProcessed < remainingDataLength ) && ( status != AWS_IOT_MQTT_BAD_RESPONSE ) ) - { - switch( getPacketType( pNextPacket, remainingDataLength - totalBytesProcessed ) ) - { - case _MQTT_PACKET_TYPE_CONNACK: - AwsIotLog_PrintBuffer( "CONNACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the CONNACK. */ - AwsIotMqttError_t ( * deserializeConnack )( const uint8_t * const, - size_t, - size_t * const ) = AwsIotMqttInternal_DeserializeConnack; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.connack != NULL ) - { - deserializeConnack = pConnectionInfo->network.deserialize.connack; - } - #endif - - status = deserializeConnack( pNextPacket, - remainingDataLength - totalBytesProcessed, - &bytesProcessed ); - - /* If a complete CONNACK was deserialized, check if there's an - * in-progress CONNECT operation. */ - if( bytesProcessed > 0 ) - { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_CONNECT, NULL ); - - if( pOperation != NULL ) - { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); - } - } - - break; - - case _MQTT_PACKET_TYPE_PUBLISH: - AwsIotLog_PrintBuffer( "PUBLISH in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Allocate memory to handle the incoming PUBLISH. */ - pOperation = AwsIotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - if( pOperation == NULL ) - { - AwsIotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); - bytesProcessed = 0; - - break; - } - - /* Set the members of the incoming PUBLISH operation. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - pOperation->incomingPublish = true; - pOperation->pMqttConnection = pConnectionInfo; - - /* Deserialize the PUBLISH into an AwsIotMqttPublishInfo_t. */ - AwsIotMqttError_t ( * deserializePublish )( const uint8_t * const, - size_t, - AwsIotMqttPublishInfo_t * const, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializePublish; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.publish != NULL ) - { - deserializePublish = pConnectionInfo->network.deserialize.publish; - } - #endif - - status = deserializePublish( pNextPacket, - remainingDataLength - totalBytesProcessed, - &( pOperation->publishInfo ), - &packetIdentifier, - &bytesProcessed ); - - /* If a complete PUBLISH was deserialized, process it. */ - if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_SUCCESS ) ) - { - /* If a QoS 1 PUBLISH was received, send a PUBACK. */ - if( pOperation->publishInfo.QoS == 1 ) - { - _sendPuback( pConnectionInfo, packetIdentifier ); - } - - /* Change the first and last PUBLISH pointers. */ - if( pFirstPublish == NULL ) - { - pFirstPublish = pOperation; - pLastPublish = pOperation; - } - else - { - pLastPublish->pNextPublish = pOperation; - pLastPublish = pOperation; - } - } - else - { - /* Free the PUBLISH operation here if the PUBLISH packet isn't - * valid. */ - AwsIotMqtt_FreeOperation( pOperation ); - } - - break; - - case _MQTT_PACKET_TYPE_PUBACK: - AwsIotLog_PrintBuffer( "PUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the PUBACK to get the packet identifier. */ - AwsIotMqttError_t ( * deserializePuback )( const uint8_t * const, - size_t, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializePuback; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.puback != NULL ) - { - deserializePuback = pConnectionInfo->network.deserialize.puback; - } - #endif - - status = deserializePuback( pNextPacket, - remainingDataLength - totalBytesProcessed, - &packetIdentifier, - &bytesProcessed ); - - /* If a complete PUBACK packet was deserialized, find an in-progress - * PUBLISH with a matching client identifier. */ - if( bytesProcessed > 0 ) - { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PUBLISH_TO_SERVER, &packetIdentifier ); - - if( pOperation != NULL ) - { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); - } - } - - break; - - case _MQTT_PACKET_TYPE_SUBACK: - AwsIotLog_PrintBuffer( "SUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the SUBACK to get the packet identifier. */ - AwsIotMqttError_t ( * deserializeSuback )( AwsIotMqttConnection_t, - const uint8_t * const, - size_t, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializeSuback; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.suback != NULL ) - { - deserializeSuback = pConnectionInfo->network.deserialize.suback; - } - #endif - - status = deserializeSuback( pConnectionInfo, - pNextPacket, - remainingDataLength - totalBytesProcessed, - &packetIdentifier, - &bytesProcessed ); - - /* If a complete SUBACK was deserialized, find an in-progress - * SUBACK with a matching client identifier. */ - if( bytesProcessed > 0 ) - { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_SUBSCRIBE, &packetIdentifier ); - - if( pOperation != NULL ) - { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); - } - } - - break; - - case _MQTT_PACKET_TYPE_UNSUBACK: - AwsIotLog_PrintBuffer( "UNSUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the UNSUBACK to get the packet identifier. */ - AwsIotMqttError_t ( * deserializeUnsuback )( const uint8_t * const, - size_t, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializeUnsuback; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.unsuback != NULL ) - { - deserializeUnsuback = pConnectionInfo->network.deserialize.unsuback; - } - #endif - - status = deserializeUnsuback( pNextPacket, - remainingDataLength - totalBytesProcessed, - &packetIdentifier, - &bytesProcessed ); - - /* If a complete UNSUBACK was deserialized, find an in-progress - * UNSUBACK with a matching client identifier. */ - if( bytesProcessed > 0 ) - { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_UNSUBSCRIBE, &packetIdentifier ); - - if( pOperation != NULL ) - { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); - } - } - - break; - - case _MQTT_PACKET_TYPE_PINGRESP: - AwsIotLog_PrintBuffer( "PINGRESP in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the PINGRESP. */ - AwsIotMqttError_t ( * deserializePingresp )( const uint8_t * const, - size_t, - size_t * const ) = AwsIotMqttInternal_DeserializePingresp; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.pingresp != NULL ) - { - deserializePingresp = pConnectionInfo->network.deserialize.pingresp; - } - #endif - - status = deserializePingresp( pNextPacket, - remainingDataLength - totalBytesProcessed, - &bytesProcessed ); - - /* If a complete PINGRESP was deserialized, check if there's an - * in-progress PINGREQ operation. */ - if( bytesProcessed > 0 ) - { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PINGREQ, NULL ); - - if( pOperation != NULL ) - { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); - } - } - - break; - - default: - - /* If an unknown packet is received, stop processing pReceivedData. */ - AwsIotLogError( "Unknown packet type %02x received.", - pNextPacket[ 0 ] ); - - bytesProcessed = 1; - status = AWS_IOT_MQTT_BAD_RESPONSE; - - break; - } - - /* Check if a protocol violation was encountered. */ - if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_BAD_RESPONSE ) ) - { - AwsIotLogError( "MQTT protocol violation encountered. Closing network connection" ); - - /* Clean up any previously allocated incoming PUBLISH operations. */ - while( pFirstPublish != NULL ) - { - pLastPublish = pFirstPublish; - pFirstPublish = pFirstPublish->pNextPublish; - - AwsIotMqtt_FreeOperation( pLastPublish ); - } - - pLastPublish = NULL; - - /* Prevent the timer thread from running, then set the error flag. */ - IotMutex_Lock( &( pConnectionInfo->timerMutex ) ); - - pConnectionInfo->errorOccurred = true; - - if( pConnectionInfo->network.disconnect != NULL ) - { - pConnectionInfo->network.disconnect( pConnectionInfo->network.pDisconnectContext ); - } - else - { - AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); - } - - IotMutex_Unlock( &( pConnectionInfo->timerMutex ) ); - - return -1; - } - - /* Check if a partial packet was encountered. */ - if( bytesProcessed == 0 ) - { - break; - } - - /* Move the "next packet" pointer and increment the number of bytes processed. */ - pNextPacket += bytesProcessed; - totalBytesProcessed += bytesProcessed; - - /* Number of bytes processed should never exceed remainingDataLength. */ - AwsIotMqtt_Assert( pNextPacket - totalBytesProcessed - offset == pReceivedData ); - AwsIotMqtt_Assert( totalBytesProcessed <= remainingDataLength ); - } - - /* Only free pReceivedData if all bytes were processed and no PUBLISH messages - * were in the data stream. */ - if( ( freeReceivedData != NULL ) && - ( totalBytesProcessed == remainingDataLength ) && - ( pFirstPublish == NULL ) ) - { - AwsIotMqtt_Assert( pLastPublish == NULL ); - - freeReceivedData( ( void * ) pReceivedData ); - } - - /* Add all PUBLISH messages to the pending operations queue. */ - if( pLastPublish != NULL ) - { - /* If all bytes of the receive buffer were processed, set the function - * to free the receive buffer. */ - if( ( totalBytesProcessed == remainingDataLength ) && ( freeReceivedData != NULL ) ) - { - pLastPublish->pReceivedData = pReceivedData; - pLastPublish->freeReceivedData = freeReceivedData; - } - else - { - /* When some of the receive buffer is unprocessed, the receive buffer is - * given back to the calling function. The MQTT library cannot guarantee - * that the calling function will keep the receive buffer in scope; - * therefore, data in the receive buffer must be copied for the MQTT - * library's use. */ - for( pOperation = pFirstPublish; pOperation != NULL; pOperation = pOperation->pNextPublish ) - { - /* Neither the buffer pointer nor the free function should be set. */ - AwsIotMqtt_Assert( pOperation->pReceivedData == NULL ); - AwsIotMqtt_Assert( pOperation->freeReceivedData == NULL ); - - /* Allocate a new buffer to hold the topic name and payload. */ - pOperation->pReceivedData = AwsIotMqtt_MallocMessage( pOperation->publishInfo.topicNameLength + - pOperation->publishInfo.payloadLength ); - - if( pOperation->pReceivedData != NULL ) - { - /* Copy the topic name and payload. */ - ( void ) memcpy( ( void * ) pOperation->pReceivedData, - pOperation->publishInfo.pTopicName, - pOperation->publishInfo.topicNameLength ); - ( void ) memcpy( ( uint8_t * ) ( pOperation->pReceivedData ) + - pOperation->publishInfo.topicNameLength, - pOperation->publishInfo.pPayload, - pOperation->publishInfo.payloadLength ); - - /* Set the topic name and payload pointers into the new buffer. - * Also set the free function. */ - pOperation->publishInfo.pTopicName = pOperation->pReceivedData; - pOperation->publishInfo.pPayload = ( uint8_t * ) ( pOperation->pReceivedData ) + - pOperation->publishInfo.topicNameLength; - pOperation->freeReceivedData = AwsIotMqtt_FreeMessage; - } - else - { - /* If a new buffer couldn't be allocated, clear the topic name and - * payload pointers so that this PUBLISH message will be ignored. */ - AwsIotLogWarn( "Failed to allocate memory for incoming PUBLISH message." ); - pOperation->publishInfo.pTopicName = NULL; - pOperation->publishInfo.topicNameLength = 0; - pOperation->publishInfo.pPayload = NULL; - pOperation->publishInfo.payloadLength = 0; - } - } - } - - if( AwsIotMqttInternal_EnqueueOperation( pFirstPublish, - &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotLogWarn( "Failed to enqueue incoming PUBLISH for callback." ); - } - } - - return ( int32_t ) totalBytesProcessed; + IotLogInfo( "MQTT library cleanup done." ); } /*-----------------------------------------------------------*/ @@ -1706,8 +1212,8 @@ AwsIotMqttError_t AwsIotMqtt_ConnectRestoreSession( AwsIotMqttConnection_t * pMq /* Check that clean session is false. */ if( pConnectInfo->cleanSession != false ) { - AwsIotLogError( "AwsIotMqtt_ConnectRestoreSession must have pConnectInfo->cleanSession == false. " - "AwsIotMqtt_Connect should be used to establish a clean session." ); + IotLogError( "AwsIotMqtt_ConnectRestoreSession must have pConnectInfo->cleanSession == false. " + "AwsIotMqtt_Connect should be used to establish a clean session." ); return AWS_IOT_MQTT_BAD_PARAMETER; } @@ -1739,7 +1245,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; _mqttOperation_t * pDisconnectOperation = NULL; - AwsIotLogInfo( "Disconnecting MQTT connection %p.", pMqttConnection ); + IotLogInfo( "Disconnecting MQTT connection %p.", pMqttConnection ); /* Purge all of this connection's subscriptions. */ IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); @@ -1754,27 +1260,36 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, IotMutex_Lock( &( pMqttConnection->timerMutex ) ); /* Purge all of this connection's pending operations and timer events. */ + IotMutex_Lock( &( _IotMqttQueueMutex ) ); IotQueue_RemoveAllMatches( &( _IotMqttSend.queue ), _mqttOperation_matchConnection, pMqttConnection, AwsIotMqttInternal_DestroyOperation, offsetof( _mqttOperation_t, link ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); + + IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); IotListDouble_RemoveAllMatches( &( _IotMqttPendingResponse ), _mqttOperation_matchConnection, pMqttConnection, AwsIotMqttInternal_DestroyOperation, offsetof( _mqttOperation_t, link ) ); + IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); + + IotMutex_Lock( &( _IotMqttQueueMutex ) ); IotQueue_RemoveAllMatches( &( _IotMqttCallback.queue ), _mqttOperation_matchConnection, pMqttConnection, AwsIotMqttInternal_DestroyOperation, offsetof( _mqttOperation_t, link ) ); + IotMutex_Unlock( &( _IotMqttQueueMutex ) ); + IotListDouble_RemoveAll( &( pMqttConnection->timerEventList ), AwsIotMqtt_FreeTimerEvent, offsetof( _mqttTimerEvent_t, link ) ); /* Stop the connection timer. */ - AwsIotLogDebug( "Stopping connection timer." ); + IotLogDebug( "Stopping connection timer." ); IotClock_TimerDestroy( &( pMqttConnection->timer ) ); /* Only send a DISCONNECT packet if no error occurred and the "cleanup only" @@ -1826,7 +1341,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, if( AwsIotMqttInternal_EnqueueOperation( pDisconnectOperation, &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to enqueue DISCONNECT for sending." ); + IotLogWarn( "Failed to enqueue DISCONNECT for sending." ); AwsIotMqttInternal_DestroyOperation( pDisconnectOperation ); } else @@ -1841,7 +1356,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, AwsIotMqtt_Assert( ( status == AWS_IOT_MQTT_SUCCESS ) || ( status == AWS_IOT_MQTT_SEND_ERROR ) ); - AwsIotLogInfo( "MQTT connection %p disconnected.", pMqttConnection ); + IotLogInfo( "MQTT connection %p disconnected.", pMqttConnection ); } } } @@ -1863,7 +1378,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, } else { - AwsIotLogWarn( "No disconnect function was set. Network connection not closed." ); + IotLogWarn( "No disconnect function was set. Network connection not closed." ); } /* Destroy the MQTT connection's mutexes. */ @@ -2017,14 +1532,14 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) || ( pCallbackInfo != NULL ) ) { - AwsIotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); return AWS_IOT_MQTT_BAD_PARAMETER; } if( pPublishRef != NULL ) { - AwsIotLogWarn( "Ignoring pPublishRef parameter for QoS 0 publish." ); + IotLogWarn( "Ignoring pPublishRef parameter for QoS 0 publish." ); } } @@ -2032,7 +1547,7 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && ( pPublishRef == NULL ) ) { - AwsIotLogError( "Reference must be provided for a waitable PUBLISH." ); + IotLogError( "Reference must be provided for a waitable PUBLISH." ); return AWS_IOT_MQTT_BAD_PARAMETER; } @@ -2109,7 +1624,7 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, if( AwsIotMqttInternal_EnqueueOperation( pPublishOperation, &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to enqueue PUBLISH for sending." ); + IotLogError( "Failed to enqueue PUBLISH for sending." ); AwsIotMqttInternal_DestroyOperation( pPublishOperation ); @@ -2129,7 +1644,7 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, return AWS_IOT_MQTT_SUCCESS; } - AwsIotLogInfo( "MQTT PUBLISH operation queued." ); + IotLogInfo( "MQTT PUBLISH operation queued." ); /* QoS 1 and QoS 2 PUBLISH messages are awaiting responses. */ return AWS_IOT_MQTT_STATUS_PENDING; @@ -2188,8 +1703,8 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, return AWS_IOT_MQTT_BAD_PARAMETER; } - AwsIotLogInfo( "Waiting for operation %s to complete.", - AwsIotMqtt_OperationType( pOperation->operation ) ); + IotLogInfo( "Waiting for operation %s to complete.", + AwsIotMqtt_OperationType( pOperation->operation ) ); /* Wait for the operation to be sent once. This wait should return quickly. */ IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); @@ -2253,10 +1768,12 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, status = AWS_IOT_MQTT_TIMEOUT; /* A timed out operation may still pending a network response. */ + IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); ( void ) IotListDouble_RemoveFirstMatch( &( _IotMqttPendingResponse ), NULL, NULL, pOperation ); + IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); } else { @@ -2299,9 +1816,9 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, -1 ); } - AwsIotLogInfo( "MQTT operation %s complete with result %s.", - AwsIotMqtt_OperationType( pOperation->operation ), - AwsIotMqtt_strerror( status ) ); + IotLogInfo( "MQTT operation %s complete with result %s.", + AwsIotMqtt_OperationType( pOperation->operation ), + AwsIotMqtt_strerror( status ) ); /* The operation is complete; it can be destroyed. PINGREQ operations are * destroyed by AwsIotMqtt_Disconnect and not here. If the operation is a diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index d94c2c7fec..b92e13a223 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -118,13 +118,13 @@ static void _processOperation( void * pArgument ); /** * Queues of MQTT operations waiting to be processed. */ -_mqttOperationQueue_t _IotMqttCallback = { 0 }; /**< @brief Queue of MQTT operations waiting for their callback to be invoked. */ -_mqttOperationQueue_t _IotMqttSend = { 0 }; /**< @brief Queue of MQTT operations waiting to be sent. */ -IotMutex_t _IotMqttQueueMutex; /**< @brief Protects both #_IotMqttSend and #_IotMqttCallback from concurrent access. */ +_mqttOperationQueue_t _IotMqttCallback; /**< @brief Queue of MQTT operations waiting for their callback to be invoked. */ +_mqttOperationQueue_t _IotMqttSend; /**< @brief Queue of MQTT operations waiting to be sent. */ +IotMutex_t _IotMqttQueueMutex; /**< @brief Protects both #_IotMqttSend and #_IotMqttCallback from concurrent access. */ /* List of MQTT operations awaiting a network response. */ -IotListDouble_t _IotMqttPendingResponse = { 0 }; /**< @brief List of MQTT operations awaiting a response from the MQTT server. */ -IotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ +IotListDouble_t _IotMqttPendingResponse; /**< @brief List of MQTT operations awaiting a response from the MQTT server. */ +IotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ /*-----------------------------------------------------------*/ @@ -215,7 +215,7 @@ static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnectio if( status == false ) { - AwsIotLogError( "Failed to re-arm timer for connection %p.", pMqttConnection ); + IotLogError( "Failed to re-arm timer for connection %p.", pMqttConnection ); } } @@ -239,8 +239,8 @@ static void _invokeSend( _mqttOperation_t * const pOperation ) { bool waitableOperation = false; - AwsIotLogDebug( "Operation %s received from queue. Sending data over network.", - AwsIotMqtt_OperationType( pOperation->operation ) ); + IotLogDebug( "Operation %s received from queue. Sending data over network.", + AwsIotMqtt_OperationType( pOperation->operation ) ); /* Check if this is a waitable operation. */ waitableOperation = ( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) @@ -252,8 +252,8 @@ static void _invokeSend( _mqttOperation_t * const pOperation ) pOperation->packetSize ) != pOperation->packetSize ) { /* Transmission failed. */ - AwsIotLogError( "Failed to send data for operation %s.", - AwsIotMqtt_OperationType( pOperation->operation ) ); + IotLogError( "Failed to send data for operation %s.", + AwsIotMqtt_OperationType( pOperation->operation ) ); pOperation->status = AWS_IOT_MQTT_SEND_ERROR; } @@ -375,8 +375,8 @@ static void _invokeCallback( _mqttOperation_t * const pOperation ) /* Operations with no callback should not have been passed. */ AwsIotMqtt_Assert( pOperation->notify.callback.function != NULL ); - AwsIotLogDebug( "Operation %s received from queue. Invoking user callback.", - AwsIotMqtt_OperationType( pOperation->operation ) ); + IotLogDebug( "Operation %s received from queue. Invoking user callback.", + AwsIotMqtt_OperationType( pOperation->operation ) ); /* Set callback parameters. */ callbackParam.mqttConnection = pOperation->pMqttConnection; @@ -404,11 +404,11 @@ static void _processOperation( void * pArgument ) AwsIotMqtt_Assert( ( pQueue == &( _IotMqttCallback ) ) || ( pQueue == &( _IotMqttSend ) ) ); - AwsIotLogDebug( "New thread for processing MQTT operations started." ); + IotLogDebug( "New thread for processing MQTT operations started." ); while( true ) { - AwsIotLogDebug( "Removing oldest operation from MQTT pending operations." ); + IotLogDebug( "Removing oldest operation from MQTT pending operations." ); /* Remove the oldest operation from the queue of pending MQTT operations. */ IotMutex_Lock( &( _IotMqttQueueMutex ) ); @@ -453,7 +453,7 @@ static void _processOperation( void * pArgument ) } } - AwsIotLogDebug( "No more pending operations. Terminating MQTT processing thread." ); + IotLogDebug( "No more pending operations. Terminating MQTT processing thread." ); } /*-----------------------------------------------------------*/ @@ -489,14 +489,14 @@ AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const { _mqttOperation_t * pOperation = NULL; - AwsIotLogDebug( "Creating new MQTT operation record." ); + IotLogDebug( "Creating new MQTT operation record." ); /* If the waitable flag is set, make sure that there's no callback. */ if( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) { if( pCallbackInfo != NULL ) { - AwsIotLogError( "Callback should not be set for a waitable operation." ); + IotLogError( "Callback should not be set for a waitable operation." ); return AWS_IOT_MQTT_BAD_PARAMETER; } @@ -507,7 +507,7 @@ AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const if( pOperation == NULL ) { - AwsIotLogError( "Failed to allocate memory for new MQTT operation." ); + IotLogError( "Failed to allocate memory for new MQTT operation." ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -523,7 +523,7 @@ AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const * and once for when the entire operation completes. */ if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 2 ) == false ) { - AwsIotLogError( "Failed to create semaphore for waitable MQTT operation." ); + IotLogError( "Failed to create semaphore for waitable MQTT operation." ); AwsIotMqtt_FreeOperation( pOperation ); @@ -556,8 +556,8 @@ void AwsIotMqttInternal_DestroyOperation( void * pData ) { _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; - AwsIotLogDebug( "Destroying operation %s.", - AwsIotMqtt_OperationType( pOperation->operation ) ); + IotLogDebug( "Destroying operation %s.", + AwsIotMqtt_OperationType( pOperation->operation ) ); /* If the MQTT packet is still allocated, free it. */ if( pOperation->pMqttPacket != NULL ) @@ -653,12 +653,12 @@ _mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t o _mqttOperation_t * pResult = NULL; _operationMatchParam_t param = { 0 }; - AwsIotLogDebug( "Searching for in-progress operation %s in MQTT operations pending response.", + IotLogDebug( "Searching for in-progress operation %s in MQTT operations pending response.", AwsIotMqtt_OperationType( operation ) ); if( pPacketIdentifier != NULL ) { - AwsIotLogDebug( "Searching for packet identifier %hu.", *pPacketIdentifier ); + IotLogDebug( "Searching for packet identifier %hu.", *pPacketIdentifier ); } /* Set the search parameters. */ @@ -679,13 +679,13 @@ _mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t o * list. */ if( pResult == NULL ) { - AwsIotLogDebug( "In-progress operation %s not found.", - AwsIotMqtt_OperationType( operation ) ); + IotLogDebug( "In-progress operation %s not found.", + AwsIotMqtt_OperationType( operation ) ); } else { - AwsIotLogDebug( "Found in-progress operation %s.", - AwsIotMqtt_OperationType( operation ) ); + IotLogDebug( "Found in-progress operation %s.", + AwsIotMqtt_OperationType( operation ) ); } return pResult; @@ -719,7 +719,7 @@ void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) if( AwsIotMqttInternal_EnqueueOperation( pOperation, &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogWarn( "Failed to create new callback thread." ); + IotLogWarn( "Failed to create new callback thread." ); } } else diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c index 938d1aaf50..0ceeac12d1 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -279,12 +279,12 @@ static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, /*-----------------------------------------------------------*/ -#if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE +#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE /** * @brief If logging is enabled, define a log configuration that only prints the log * string. This is used when printing out details of deserialized MQTT packets. */ -static const AwsIotLogConfig_t _logHideAll = +static const IotLogConfig_t _logHideAll = { .hideLibraryName = true, .hideLogLevel = true, @@ -696,7 +696,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { - AwsIotLogError( "Failed to allocate memory for CONNECT packet." ); + IotLogError( "Failed to allocate memory for CONNECT packet." ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -808,8 +808,8 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - AwsIotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " - "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); + IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " + "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); pBuffer = _encodeString( pBuffer, _AWS_IOT_METRICS_USERNAME, @@ -841,7 +841,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pConnectPacket ) == connectPacketSize ); /* Print out the serialized CONNECT packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); + IotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); return AWS_IOT_MQTT_SUCCESS; } @@ -854,7 +854,7 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p { /* If logging is enabled, declare the CONNACK response code strings. The * fourth byte of CONNACK indexes into this array for the corresponding response. */ - #if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE + #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE static const char * pConnackResponses[ 6 ] = { "Connection accepted.", /* 0 */ @@ -870,10 +870,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p * data stream has fewer than 4 bytes, then the CONNACK packet is incomplete. */ if( dataLength < _MQTT_PACKET_CONNACK_SIZE ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "CONNACK less than %d bytes in size.", - _MQTT_PACKET_CONNACK_SIZE ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "CONNACK less than %d bytes in size.", + _MQTT_PACKET_CONNACK_SIZE ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -885,10 +885,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p /* Check that the control packet type is 0x20. */ if( pConnackStart[ 0 ] != _MQTT_PACKET_TYPE_CONNACK ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pConnackStart[ 0 ] ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pConnackStart[ 0 ] ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -897,10 +897,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p * "Remaining length" of 2. */ if( pConnackStart[ 1 ] != _MQTT_PACKET_CONNACK_REMAINING_LENGTH ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "CONNACK does not have remaining length of %d.", - _MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "CONNACK does not have remaining length of %d.", + _MQTT_PACKET_CONNACK_REMAINING_LENGTH ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -909,9 +909,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p * in CONNACK must be 0. */ if( ( pConnackStart[ 2 ] | 0x01 ) != 0x01 ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "Reserved bits in CONNACK incorrect." ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Reserved bits in CONNACK incorrect." ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -921,9 +921,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p if( ( pConnackStart[ 2 ] & _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) == _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit set." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit set." ); /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the * "Session Present" bit is set. */ @@ -934,29 +934,29 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p } else { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit not set." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit not set." ); } /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ if( pConnackStart[ 3 ] > 5 ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK response %hhu is not valid.", - pConnackStart[ 3 ] ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK response %hhu is not valid.", + pConnackStart[ 3 ] ); return AWS_IOT_MQTT_BAD_RESPONSE; } /* Print the appropriate message for the CONNACK response code if logs are * enabled. */ - #if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "%s", - pConnackResponses[ pConnackStart[ 3 ] ] ); + #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "%s", + pConnackResponses[ pConnackStart[ 3 ] ] ); #endif /* A nonzero CONNACK response code means the connection was refused. */ @@ -987,9 +987,9 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn &remainingLength, &publishPacketSize ) == false ) { - AwsIotLogError( "Publish packet remaining length exceeds %d, which is the " - "maximum size allowed by MQTT 3.1.1.", - _MQTT_MAX_REMAINING_LENGTH ); + IotLogError( "Publish packet remaining length exceeds %d, which is the " + "maximum size allowed by MQTT 3.1.1.", + _MQTT_MAX_REMAINING_LENGTH ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1004,7 +1004,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { - AwsIotLogError( "Failed to allocate memory for PUBLISH packet." ); + IotLogError( "Failed to allocate memory for PUBLISH packet." ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1067,7 +1067,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pPublishPacket ) == publishPacketSize ); /* Print out the serialized PUBLISH packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); + IotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); return AWS_IOT_MQTT_SUCCESS; } @@ -1096,9 +1096,9 @@ void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, topicNameLength = _UINT16_DECODE( pTopicNameLength ); pPacketIdentifier = ( uint8_t * ) ( pTopicNameLength + topicNameLength + sizeof( uint16_t ) ); - AwsIotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", - *pPacketIdentifier, - newPacketIdentifier ); + IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", + *pPacketIdentifier, + newPacketIdentifier ); /* Replace the packet identifier. */ *pPacketIdentifier = _UINT16_HIGH_BYTE( newPacketIdentifier ); @@ -1129,10 +1129,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * PUBLISH packet. */ if( dataLength < _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ) { - AwsIotLogError( "PUBLISH size %lu is smaller than the smallest possible " - "size %d.", - ( unsigned long ) dataLength, - _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ); + IotLogError( "PUBLISH size %lu is smaller than the smallest possible " + "size %d.", + ( unsigned long ) dataLength, + _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1143,9 +1143,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p &pVariableHeader, &remainingLength ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Bad remaining length." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad remaining length." ); /* If the "Remaining length" couldn't be determined, invalidate the rest * of the data stream by marking it processed. */ @@ -1154,9 +1154,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p return AWS_IOT_MQTT_BAD_RESPONSE; } - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length %lu.", ( unsigned long ) remainingLength ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length %lu.", ( unsigned long ) remainingLength ); /* Calculate the packet size and set the output parameter. */ packetSize = remainingLength + _remainingLengthEncodedSize( remainingLength ) + 1; @@ -1166,11 +1166,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * then this is a partial packet. */ if( packetSize > dataLength ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "PUBLISH packet size %lu exceeds data length %lu.", - ( unsigned long ) packetSize, - ( unsigned long ) dataLength ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "PUBLISH packet size %lu exceeds data length %lu.", + ( unsigned long ) packetSize, + ( unsigned long ) dataLength ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1182,9 +1182,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p /* Parse the Retain bit. */ pOutput->retain = _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Retain bit is %d.", pOutput->retain ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Retain bit is %d.", pOutput->retain ); /* Check for QoS 2. */ if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS2 ) == true ) @@ -1192,9 +1192,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ) == true ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Bad QoS: 3." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad QoS: 3." ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1212,22 +1212,22 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p pOutput->QoS = 0; } - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "QoS is %d.", pOutput->QoS ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS is %d.", pOutput->QoS ); /* Parse the DUP bit. */ if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_DUP ) == true ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "DUP is 1." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 1." ); } else { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "DUP is 0." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 0." ); } /* Sanity checks for "Remaining length". */ @@ -1237,9 +1237,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * topic name length (2 bytes) and topic name (at least 1 byte). */ if( remainingLength < 3 ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "QoS 0 PUBLISH cannot have a remaining length less than 3." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 0 PUBLISH cannot have a remaining length less than 3." ); *pBytesProcessed = dataLength; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1252,9 +1252,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * topic name. */ if( remainingLength < 5 ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); *pBytesProcessed = dataLength; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1272,9 +1272,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * header. */ if( remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length cannot be less than variable header length." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); *pBytesProcessed = dataLength; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1286,9 +1286,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * header. */ if( remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length cannot be less than variable header length." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); *pBytesProcessed = dataLength; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1298,12 +1298,12 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p /* Parse the topic. */ pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Topic name length %hu: %.*s", - pOutput->topicNameLength, - pOutput->topicNameLength, - pOutput->pTopicName ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic name length %hu: %.*s", + pOutput->topicNameLength, + pOutput->topicNameLength, + pOutput->pTopicName ); /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet * identifier starts immediately after the topic name. */ @@ -1314,9 +1314,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p packetIdentifier = _UINT16_DECODE( pPacketIdentifierHigh ); *pPacketIdentifier = packetIdentifier; - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); /* Packet identifier cannot be 0. */ if( packetIdentifier == 0 ) @@ -1338,10 +1338,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Payload length %hu. Payload:", pOutput->payloadLength ); - AwsIotLog_PrintBuffer( NULL, pOutput->pPayload, pOutput->payloadLength ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Payload length %hu. Payload:", pOutput->payloadLength ); + IotLog_PrintBuffer( NULL, pOutput->pPayload, pOutput->payloadLength ); return AWS_IOT_MQTT_SUCCESS; } @@ -1357,7 +1357,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePuback( uint16_t packetIdentifier, if( pBuffer == NULL ) { - AwsIotLogError( "Failed to allocate memory for PUBACK packet" ); + IotLogError( "Failed to allocate memory for PUBACK packet" ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1373,7 +1373,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePuback( uint16_t packetIdentifier, pBuffer[ 3 ] = _UINT16_LOW_BYTE( packetIdentifier ); /* Print out the serialized PUBACK packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, _MQTT_PACKET_PUBACK_SIZE ); + IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, _MQTT_PACKET_PUBACK_SIZE ); return AWS_IOT_MQTT_SUCCESS; } @@ -1391,10 +1391,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP * data stream has fewer than 4 bytes, then the PUBACK packet is incomplete. */ if( dataLength < _MQTT_PACKET_PUBACK_SIZE ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "PUBACK less than %d bytes in size.", - _MQTT_PACKET_PUBACK_SIZE ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PUBACK less than %d bytes in size.", + _MQTT_PACKET_PUBACK_SIZE ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1407,9 +1407,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP packetIdentifier = _UINT16_DECODE( pPubackStart + 2 ); *pPacketIdentifier = packetIdentifier; - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); /* Packet identifier cannot be 0. */ if( packetIdentifier == 0 ) @@ -1420,10 +1420,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP /* Check that the control packet type is 0x40. */ if( pPubackStart[ 0 ] != _MQTT_PACKET_TYPE_PUBACK ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pPubackStart[ 0 ] ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPubackStart[ 0 ] ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1431,10 +1431,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP /* Check the "Remaining length" (second byte) of the received PUBACK. */ if( pPubackStart[ 1 ] != _MQTT_PACKET_PUBACK_REMAINING_LENGTH ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "PUBACK does not have remaining length of %d.", - _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PUBACK does not have remaining length of %d.", + _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1462,9 +1462,9 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscri &remainingLength, &subscribePacketSize ) == false ) { - AwsIotLogError( "Subscribe packet remaining length exceeds %d, which is the " - "maximum size allowed by MQTT 3.1.1.", - _MQTT_MAX_REMAINING_LENGTH ); + IotLogError( "Subscribe packet remaining length exceeds %d, which is the " + "maximum size allowed by MQTT 3.1.1.", + _MQTT_MAX_REMAINING_LENGTH ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1479,7 +1479,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscri /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { - AwsIotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); + IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1522,7 +1522,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscri AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pSubscribePacket ) == subscribePacketSize ); /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); + IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); return AWS_IOT_MQTT_SUCCESS; } @@ -1546,10 +1546,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m * SUBACK packet. */ if( dataLength < _MQTT_PACKET_SUBACK_MINIMUM_SIZE ) { - AwsIotLogError( "SUBACK size %lu is smaller than the smallest possible " - "size %d.", - ( unsigned long ) dataLength, - _MQTT_PACKET_SUBACK_MINIMUM_SIZE ); + IotLogError( "SUBACK size %lu is smaller than the smallest possible " + "size %d.", + ( unsigned long ) dataLength, + _MQTT_PACKET_SUBACK_MINIMUM_SIZE ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1560,9 +1560,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m &pVariableHeader, &remainingLength ) != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Bad remaining length." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad remaining length." ); /* If the "Remaining length" couldn't be determined, invalidate the rest * of the data stream by marking it processed. */ @@ -1571,17 +1571,17 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m return AWS_IOT_MQTT_BAD_RESPONSE; } - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length %lu.", ( unsigned long ) remainingLength ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length %lu.", ( unsigned long ) remainingLength ); /* A SUBACK must have a remaining length of at least 3 to accommodate the * packet identifer and at least 1 return code. */ if( remainingLength < 3 ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "SUBACK cannot have a remaining length less than 3." ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "SUBACK cannot have a remaining length less than 3." ); *pBytesProcessed = dataLength; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1595,11 +1595,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m * then this is a partial packet. */ if( packetSize > dataLength ) { - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "SUBACK packet size %lu exceeds data length %lu.", - ( unsigned long ) packetSize, - ( unsigned long ) dataLength ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "SUBACK packet size %lu exceeds data length %lu.", + ( unsigned long ) packetSize, + ( unsigned long ) dataLength ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1609,17 +1609,17 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m packetIdentifier = _UINT16_DECODE( pVariableHeader ); *pPacketIdentifier = packetIdentifier; - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); /* Check that the control packet type is 0x90. */ if( pSubackStart[ 0 ] != _MQTT_PACKET_TYPE_SUBACK ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pSubackStart[ 0 ] ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pSubackStart[ 0 ] ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1636,16 +1636,16 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m case 0x00: case 0x01: case 0x02: - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); break; case 0x80: - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu refused.", ( unsigned long ) i ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu refused.", ( unsigned long ) i ); /* Remove a rejected subscription from the subscription manager. */ AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, @@ -1657,9 +1657,9 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m break; default: - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); status = AWS_IOT_MQTT_BAD_RESPONSE; @@ -1696,9 +1696,9 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubsc &remainingLength, &unsubscribePacketSize ) == false ) { - AwsIotLogError( "Unsubscribe packet remaining length exceeds %d, which is the " - "maximum size allowed by MQTT 3.1.1.", - _MQTT_MAX_REMAINING_LENGTH ); + IotLogError( "Unsubscribe packet remaining length exceeds %d, which is the " + "maximum size allowed by MQTT 3.1.1.", + _MQTT_MAX_REMAINING_LENGTH ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1713,7 +1713,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubsc /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { - AwsIotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); + IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); return AWS_IOT_MQTT_NO_MEMORY; } @@ -1752,7 +1752,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubsc AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pUnsubscribePacket ) == unsubscribePacketSize ); /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); + IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); return AWS_IOT_MQTT_SUCCESS; } @@ -1770,10 +1770,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const * data stream has fewer than 4 bytes, then the UNSUBACK packet is incomplete. */ if( dataLength < _MQTT_PACKET_UNSUBACK_SIZE ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "UNSUBACK less than %d bytes in size.", - _MQTT_PACKET_UNSUBACK_SIZE ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "UNSUBACK less than %d bytes in size.", + _MQTT_PACKET_UNSUBACK_SIZE ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1795,10 +1795,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const /* Check that the control packet type is 0xb0. */ if( pUnsubackStart[ 0 ] != _MQTT_PACKET_TYPE_UNSUBACK ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pUnsubackStart[ 0 ] ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pUnsubackStart[ 0 ] ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1806,17 +1806,17 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ if( pUnsubackStart[ 1 ] != _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "UNSUBACK does not have remaining length of %d.", - _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "UNSUBACK does not have remaining length of %d.", + _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); return AWS_IOT_MQTT_BAD_RESPONSE; } - AwsIotLog( AWS_IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", packetIdentifier ); return AWS_IOT_MQTT_SUCCESS; } @@ -1838,7 +1838,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreq *pPacketSize = _MQTT_PACKET_PINGREQ_SIZE; /* Print out the PINGREQ packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, _MQTT_PACKET_PINGREQ_SIZE ); + IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, _MQTT_PACKET_PINGREQ_SIZE ); return AWS_IOT_MQTT_SUCCESS; } @@ -1853,10 +1853,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const * data stream has fewer than 2 bytes, then the PINGRESP packet is incomplete. */ if( dataLength < _MQTT_PACKET_PINGRESP_SIZE ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "PINGRESP less than %d bytes in size.", - _MQTT_PACKET_PINGRESP_SIZE ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PINGRESP less than %d bytes in size.", + _MQTT_PACKET_PINGRESP_SIZE ); *pBytesProcessed = 0; return AWS_IOT_MQTT_BAD_RESPONSE; @@ -1868,10 +1868,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const /* Check that the control packet type is 0xd0. */ if( pPingrespStart[ 0 ] != _MQTT_PACKET_TYPE_PINGRESP ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pPingrespStart[ 0 ] ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPingrespStart[ 0 ] ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1879,10 +1879,10 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const /* Check the "Remaining length" (second byte) of the received PINGRESP. */ if( pPingrespStart[ 1 ] != _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { - AwsIotLog( AWS_IOT_LOG_ERROR, - &_logHideAll, - "PINGRESP does not have remaining length of %d.", - _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PINGRESP does not have remaining length of %d.", + _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); return AWS_IOT_MQTT_BAD_RESPONSE; } @@ -1907,7 +1907,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeDisconnect( uint8_t ** const pDisc *pPacketSize = _MQTT_PACKET_DISCONNECT_SIZE; /* Print out the DISCONNECT packet for debugging purposes. */ - AwsIotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, _MQTT_PACKET_DISCONNECT_SIZE ); + IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, _MQTT_PACKET_DISCONNECT_SIZE ); return AWS_IOT_MQTT_SUCCESS; } diff --git a/lib/source/mqtt/aws_iot_mqtt_validate.c b/lib/source/mqtt/aws_iot_mqtt_validate.c index f6fe533c44..41424e9771 100644 --- a/lib/source/mqtt/aws_iot_mqtt_validate.c +++ b/lib/source/mqtt/aws_iot_mqtt_validate.c @@ -39,7 +39,7 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI /* Check for NULL. */ if( pNetworkInterface == NULL ) { - AwsIotLogError( "Network interface cannot be NULL." ); + IotLogError( "Network interface cannot be NULL." ); return false; } @@ -47,7 +47,7 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI /* Check for a non-NULL send function. */ if( pNetworkInterface->send == NULL ) { - AwsIotLogError( "Network interface send function must be set." ); + IotLogError( "Network interface send function must be set." ); return false; } @@ -56,8 +56,8 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI * that a function has been provided to do this. */ if( pNetworkInterface->disconnect == NULL ) { - AwsIotLogWarn( "No disconnect function was provided. The MQTT connection will not be " - "closed on errors, which is against MQTT 3.1.1 specification." ); + IotLogWarn( "No disconnect function was provided. The MQTT connection will not be " + "closed on errors, which is against MQTT 3.1.1 specification." ); } /* Check that the freePacket function pointer is set if any other serializer @@ -75,8 +75,8 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI ( pNetworkInterface->serialize.pingreq != NULL ) || ( pNetworkInterface->serialize.disconnect != NULL ) ) { - AwsIotLogError( "Network interface must have a freePacket function " - "if a serializer override isn't NULL." ); + IotLogError( "Network interface must have a freePacket function " + "if a serializer override isn't NULL." ); return false; } @@ -89,8 +89,8 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI ( pNetworkInterface->deserialize.unsuback != NULL ) || ( pNetworkInterface->deserialize.pingresp != NULL ) ) { - AwsIotLogError( "Network interface must have a freePacket function " - "if a deserializer override isn't NULL." ); + IotLogError( "Network interface must have a freePacket function " + "if a deserializer override isn't NULL." ); return false; } @@ -107,7 +107,7 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p /* Check for NULL. */ if( pConnectInfo == NULL ) { - AwsIotLogError( "MQTT connection information cannot be NULL." ); + IotLogError( "MQTT connection information cannot be NULL." ); return false; } @@ -115,7 +115,7 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p /* Check that a client identifier was set. */ if( pConnectInfo->pClientIdentifier == NULL ) { - AwsIotLogError( "Client identifier must be set." ); + IotLogError( "Client identifier must be set." ); return false; } @@ -124,11 +124,11 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p * are not allowed with clean sessions. */ if( pConnectInfo->clientIdentifierLength == 0 ) { - AwsIotLogWarn( "A zero-length client identifier was provided." ); + IotLogWarn( "A zero-length client identifier was provided." ); if( pConnectInfo->cleanSession == true ) { - AwsIotLogError( "A zero-length client identifier cannot be used with a clean session." ); + IotLogError( "A zero-length client identifier cannot be used with a clean session." ); return false; } @@ -138,9 +138,9 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p * than 23 characters. */ if( pConnectInfo->clientIdentifierLength > 23 ) { - AwsIotLogWarn( "A client identifier length of %hu is longer than 23, which is " - "the longest client identifier a server must accept.", - pConnectInfo->clientIdentifierLength ); + IotLogWarn( "A client identifier length of %hu is longer than 23, which is " + "the longest client identifier a server must accept.", + pConnectInfo->clientIdentifierLength ); } /* Check for compatibility with the AWS IoT MQTT service limits. */ @@ -149,8 +149,8 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p /* Check that client identifier is within the service limit. */ if( pConnectInfo->clientIdentifierLength > _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ) { - AwsIotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", - _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); + IotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", + _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); return false; } @@ -158,23 +158,23 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p /* Check for compliance with AWS IoT keep-alive limits. */ if( pConnectInfo->keepAliveSeconds == 0 ) { - AwsIotLogWarn( "AWS IoT does not support disabling keep-alive. Default keep-alive " - "of %d seconds will be used.", - _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + IotLogWarn( "AWS IoT does not support disabling keep-alive. Default keep-alive " + "of %d seconds will be used.", + _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); } else if( pConnectInfo->keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) { - AwsIotLogWarn( "AWS IoT does not support keep-alive intervals less than %d seconds. " - "An interval of %d seconds will be used.", - _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, - _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); + IotLogWarn( "AWS IoT does not support keep-alive intervals less than %d seconds. " + "An interval of %d seconds will be used.", + _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, + _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); } else if( pConnectInfo->keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) { - AwsIotLogWarn( "AWS IoT does not support keep-alive intervals greater than %d seconds. " - "An interval of %d seconds will be used.", - _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, - _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + IotLogWarn( "AWS IoT does not support keep-alive intervals greater than %d seconds. " + "An interval of %d seconds will be used.", + _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, + _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); } } @@ -189,7 +189,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Check for NULL. */ if( pPublishInfo == NULL ) { - AwsIotLogError( "Publish information cannot be NULL." ); + IotLogError( "Publish information cannot be NULL." ); return false; } @@ -197,7 +197,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Check topic name for NULL or zero-length. */ if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) { - AwsIotLogError( "Publish topic name must be set." ); + IotLogError( "Publish topic name must be set." ); return false; } @@ -205,7 +205,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Only allow NULL payloads with zero-length. */ if( ( pPublishInfo->pPayload == NULL ) && ( pPublishInfo->payloadLength != 0 ) ) { - AwsIotLogError( "Nonzero payload length cannot have a NULL payload." ); + IotLogError( "Nonzero payload length cannot have a NULL payload." ); return false; } @@ -213,7 +213,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Check for a valid QoS. */ if( ( pPublishInfo->QoS < 0 ) || ( pPublishInfo->QoS > 1 ) ) { - AwsIotLogError( "Publish QoS must be either 0 or 1." ); + IotLogError( "Publish QoS must be either 0 or 1." ); return false; } @@ -221,7 +221,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Check the retry parameters. */ if( pPublishInfo->retryLimit < 0 ) { - AwsIotLogError( "Publish retry cannot be less than 0." ); + IotLogError( "Publish retry cannot be less than 0." ); return false; } @@ -229,7 +229,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, { if( pPublishInfo->retryMs == 0 ) { - AwsIotLogError( "Publish retry time must be positive." ); + IotLogError( "Publish retry time must be positive." ); return false; } @@ -241,7 +241,7 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Check for retained message. */ if( pPublishInfo->retain == true ) { - AwsIotLogError( "AWS IoT does not support retained publish messages." ); + IotLogError( "AWS IoT does not support retained publish messages." ); return false; } @@ -249,8 +249,8 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, /* Check topic name length. */ if( pPublishInfo->topicNameLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) { - AwsIotLogError( "AWS IoT does not support topic names longer than %d bytes.", - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + IotLogError( "AWS IoT does not support topic names longer than %d bytes.", + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); return false; } @@ -268,7 +268,7 @@ bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ) /* Check for NULL. */ if( pOperation == NULL ) { - AwsIotLogError( "Reference cannot be NULL." ); + IotLogError( "Reference cannot be NULL." ); return false; } @@ -276,7 +276,7 @@ bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ) /* Check that reference is waitable. */ if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) != AWS_IOT_MQTT_FLAG_WAITABLE ) { - AwsIotLogError( "Reference is not a waitable MQTT operation." ); + IotLogError( "Reference is not a waitable MQTT operation." ); return false; } @@ -302,7 +302,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper /* Check for empty list. */ if( ( listSize == 0 ) || ( pListStart == NULL ) ) { - AwsIotLogError( "Empty subscription list." ); + IotLogError( "Empty subscription list." ); return false; } @@ -312,7 +312,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper { if( listSize > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) { - AwsIotLogError( "AWS IoT does not support more than %d topic filters per " + IotLogError( "AWS IoT does not support more than %d topic filters per " "subscription request.", _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); @@ -328,7 +328,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper if( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) && ( ( pListElement->QoS < 0 ) || ( pListElement->QoS > 1 ) ) ) { - AwsIotLogError( "Subscription QoS must be either 0 or 1." ); + IotLogError( "Subscription QoS must be either 0 or 1." ); return false; } @@ -336,7 +336,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper /* Check subscription topic filter. */ if( ( pListElement->pTopicFilter == NULL ) || ( pListElement->topicFilterLength == 0 ) ) { - AwsIotLogError( "Subscription topic filter must be set." ); + IotLogError( "Subscription topic filter must be set." ); return false; } @@ -345,7 +345,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper if( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) && ( pListElement->callback.function == NULL ) ) { - AwsIotLogError( "Callback function must be set." ); + IotLogError( "Callback function must be set." ); return false; } @@ -356,7 +356,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper /* Check topic filter length. */ if( pListElement->topicFilterLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) { - AwsIotLogError( "AWS IoT does not support topic filters longer than %d bytes.", + IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); return false; @@ -374,9 +374,9 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ if( ( j > 0 ) && ( pListElement->pTopicFilter[ j - 1 ] != '/' ) ) { - AwsIotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); + IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); return false; } @@ -385,9 +385,9 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper if( ( j < pListElement->topicFilterLength - 1 ) && ( pListElement->pTopicFilter[ j + 1 ] != '/' ) ) { - AwsIotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); + IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); return false; } @@ -400,9 +400,9 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper /* '#' must be the last character in the filter. */ if( j != pListElement->topicFilterLength - 1 ) { - AwsIotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); + IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); return false; } @@ -411,9 +411,9 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper if( ( pListElement->topicFilterLength > 1 ) && ( pListElement->pTopicFilter[ j - 1 ] != '/' ) ) { - AwsIotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); + IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); return false; } diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c new file mode 100644 index 0000000000..d3b2af5a4e --- /dev/null +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_network.c + * @brief Implements functions involving transport layer connections. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/aws_iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_threads.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. + * + * @param[in] pMqttConnection Which connection the PUBACK should be sent over. + * @param[in] packetIdentifier Which packet identifier to include in PUBACK. + */ +static void _sendPuback( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier ); + +/*-----------------------------------------------------------*/ + +static void _sendPuback( _mqttConnection_t * const pMqttConnection, + uint16_t packetIdentifier ) +{ + _mqttOperation_t * pPubackOperation = NULL; + + /* Choose a PUBACK serializer function. */ + AwsIotMqttError_t ( * serializePuback )( uint16_t, + uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializePuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.puback != NULL ) + { + serializePuback = pMqttConnection->network.serialize.puback; + } + #endif + + IotLogDebug( "Sending PUBACK for received PUBLISH %hu.", packetIdentifier ); + + /* Create a PUBACK operation. */ + if( AwsIotMqttInternal_CreateOperation( &pPubackOperation, + 0, + NULL ) != AWS_IOT_MQTT_SUCCESS ) + { + IotLogWarn( "Failed to create PUBACK operation." ); + + return; + } + + /* Generate a PUBACK packet from the packet identifier. */ + if( serializePuback( packetIdentifier, + &( pPubackOperation->pMqttPacket ), + &( pPubackOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) + { + IotLogWarn( "Failed to generate PUBACK packet." ); + AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + + return; + } + + /* Set the remaining members of the PUBACK operation and push it to the + * send queue for network transmission. */ + pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; + pPubackOperation->pMqttConnection = pMqttConnection; + + if( AwsIotMqttInternal_EnqueueOperation( pPubackOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) + { + IotLogWarn( "Failed to enqueue PUBACK for sending." ); + AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + } +} + +/*-----------------------------------------------------------*/ + +int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, + const void * pReceivedData, + size_t offset, + size_t dataLength, + void ( *freeReceivedData )( void * ) ) +{ + size_t bytesProcessed = 0, totalBytesProcessed = 0, remainingDataLength = 0; + _mqttConnection_t * pConnectionInfo = *( ( _mqttConnection_t ** ) ( pMqttConnection ) ); + AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + uint16_t packetIdentifier = 0; + const uint8_t * pNextPacket = ( const uint8_t * ) pReceivedData; + _mqttOperation_t * pOperation = NULL, * pFirstPublish = NULL, * pLastPublish = NULL; + + /* Choose a packet type decoder function. */ + uint8_t ( * getPacketType )( const uint8_t * const, + size_t ) = AwsIotMqttInternal_GetPacketType; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.getPacketType != NULL ) + { + getPacketType = pConnectionInfo->network.getPacketType; + } + #endif + + /* Ensure that offset is smaller than dataLength. */ + if( offset >= dataLength ) + { + return 0; + } + + /* Adjust the packet pointer based on the offset. */ + pNextPacket += offset; + remainingDataLength = dataLength - offset; + + /* Process the stream of data until the entire stream is proccessed or an + * incomplete packet is found. */ + while( ( totalBytesProcessed < remainingDataLength ) && ( status != AWS_IOT_MQTT_BAD_RESPONSE ) ) + { + switch( getPacketType( pNextPacket, remainingDataLength - totalBytesProcessed ) ) + { + case _MQTT_PACKET_TYPE_CONNACK: + IotLog_PrintBuffer( "CONNACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the CONNACK. */ + AwsIotMqttError_t ( * deserializeConnack )( const uint8_t * const, + size_t, + size_t * const ) = AwsIotMqttInternal_DeserializeConnack; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.connack != NULL ) + { + deserializeConnack = pConnectionInfo->network.deserialize.connack; + } + #endif + + status = deserializeConnack( pNextPacket, + remainingDataLength - totalBytesProcessed, + &bytesProcessed ); + + /* If a complete CONNACK was deserialized, check if there's an + * in-progress CONNECT operation. */ + if( bytesProcessed > 0 ) + { + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_CONNECT, NULL ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + } + + break; + + case _MQTT_PACKET_TYPE_PUBLISH: + IotLog_PrintBuffer( "PUBLISH in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Allocate memory to handle the incoming PUBLISH. */ + pOperation = AwsIotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); + bytesProcessed = 0; + + break; + } + + /* Set the members of the incoming PUBLISH operation. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + pOperation->incomingPublish = true; + pOperation->pMqttConnection = pConnectionInfo; + + /* Deserialize the PUBLISH into an AwsIotMqttPublishInfo_t. */ + AwsIotMqttError_t ( * deserializePublish )( const uint8_t * const, + size_t, + AwsIotMqttPublishInfo_t * const, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializePublish; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.publish != NULL ) + { + deserializePublish = pConnectionInfo->network.deserialize.publish; + } + #endif + + status = deserializePublish( pNextPacket, + remainingDataLength - totalBytesProcessed, + &( pOperation->publishInfo ), + &packetIdentifier, + &bytesProcessed ); + + /* If a complete PUBLISH was deserialized, process it. */ + if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_SUCCESS ) ) + { + /* If a QoS 1 PUBLISH was received, send a PUBACK. */ + if( pOperation->publishInfo.QoS == 1 ) + { + _sendPuback( pConnectionInfo, packetIdentifier ); + } + + /* Change the first and last PUBLISH pointers. */ + if( pFirstPublish == NULL ) + { + pFirstPublish = pOperation; + pLastPublish = pOperation; + } + else + { + pLastPublish->pNextPublish = pOperation; + pLastPublish = pOperation; + } + } + else + { + /* Free the PUBLISH operation here if the PUBLISH packet isn't + * valid. */ + AwsIotMqtt_FreeOperation( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_PUBACK: + IotLog_PrintBuffer( "PUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the PUBACK to get the packet identifier. */ + AwsIotMqttError_t ( * deserializePuback )( const uint8_t * const, + size_t, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializePuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.puback != NULL ) + { + deserializePuback = pConnectionInfo->network.deserialize.puback; + } + #endif + + status = deserializePuback( pNextPacket, + remainingDataLength - totalBytesProcessed, + &packetIdentifier, + &bytesProcessed ); + + /* If a complete PUBACK packet was deserialized, find an in-progress + * PUBLISH with a matching client identifier. */ + if( bytesProcessed > 0 ) + { + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PUBLISH_TO_SERVER, &packetIdentifier ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + } + + break; + + case _MQTT_PACKET_TYPE_SUBACK: + IotLog_PrintBuffer( "SUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the SUBACK to get the packet identifier. */ + AwsIotMqttError_t ( * deserializeSuback )( AwsIotMqttConnection_t, + const uint8_t * const, + size_t, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializeSuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.suback != NULL ) + { + deserializeSuback = pConnectionInfo->network.deserialize.suback; + } + #endif + + status = deserializeSuback( pConnectionInfo, + pNextPacket, + remainingDataLength - totalBytesProcessed, + &packetIdentifier, + &bytesProcessed ); + + /* If a complete SUBACK was deserialized, find an in-progress + * SUBACK with a matching client identifier. */ + if( bytesProcessed > 0 ) + { + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_SUBSCRIBE, &packetIdentifier ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + } + + break; + + case _MQTT_PACKET_TYPE_UNSUBACK: + IotLog_PrintBuffer( "UNSUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the UNSUBACK to get the packet identifier. */ + AwsIotMqttError_t ( * deserializeUnsuback )( const uint8_t * const, + size_t, + uint16_t * const, + size_t * const ) = AwsIotMqttInternal_DeserializeUnsuback; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.unsuback != NULL ) + { + deserializeUnsuback = pConnectionInfo->network.deserialize.unsuback; + } + #endif + + status = deserializeUnsuback( pNextPacket, + remainingDataLength - totalBytesProcessed, + &packetIdentifier, + &bytesProcessed ); + + /* If a complete UNSUBACK was deserialized, find an in-progress + * UNSUBACK with a matching client identifier. */ + if( bytesProcessed > 0 ) + { + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_UNSUBSCRIBE, &packetIdentifier ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + } + + break; + + case _MQTT_PACKET_TYPE_PINGRESP: + IotLog_PrintBuffer( "PINGRESP in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + + /* Deserialize the PINGRESP. */ + AwsIotMqttError_t ( * deserializePingresp )( const uint8_t * const, + size_t, + size_t * const ) = AwsIotMqttInternal_DeserializePingresp; + + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pConnectionInfo->network.deserialize.pingresp != NULL ) + { + deserializePingresp = pConnectionInfo->network.deserialize.pingresp; + } + #endif + + status = deserializePingresp( pNextPacket, + remainingDataLength - totalBytesProcessed, + &bytesProcessed ); + + /* If a complete PINGRESP was deserialized, check if there's an + * in-progress PINGREQ operation. */ + if( bytesProcessed > 0 ) + { + pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PINGREQ, NULL ); + + if( pOperation != NULL ) + { + pOperation->status = status; + AwsIotMqttInternal_Notify( pOperation ); + } + } + + break; + + default: + + /* If an unknown packet is received, stop processing pReceivedData. */ + IotLogError( "Unknown packet type %02x received.", + pNextPacket[ 0 ] ); + + bytesProcessed = 1; + status = AWS_IOT_MQTT_BAD_RESPONSE; + + break; + } + + /* Check if a protocol violation was encountered. */ + if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_BAD_RESPONSE ) ) + { + IotLogError( "MQTT protocol violation encountered. Closing network connection" ); + + /* Clean up any previously allocated incoming PUBLISH operations. */ + while( pFirstPublish != NULL ) + { + pLastPublish = pFirstPublish; + pFirstPublish = pFirstPublish->pNextPublish; + + AwsIotMqtt_FreeOperation( pLastPublish ); + } + + pLastPublish = NULL; + + /* Prevent the timer thread from running, then set the error flag. */ + IotMutex_Lock( &( pConnectionInfo->timerMutex ) ); + + pConnectionInfo->errorOccurred = true; + + if( pConnectionInfo->network.disconnect != NULL ) + { + pConnectionInfo->network.disconnect( pConnectionInfo->network.pDisconnectContext ); + } + else + { + IotLogWarn( "No disconnect function was set. Network connection not closed." ); + } + + IotMutex_Unlock( &( pConnectionInfo->timerMutex ) ); + + return -1; + } + + /* Check if a partial packet was encountered. */ + if( bytesProcessed == 0 ) + { + break; + } + + /* Move the "next packet" pointer and increment the number of bytes processed. */ + pNextPacket += bytesProcessed; + totalBytesProcessed += bytesProcessed; + + /* Number of bytes processed should never exceed remainingDataLength. */ + AwsIotMqtt_Assert( pNextPacket - totalBytesProcessed - offset == pReceivedData ); + AwsIotMqtt_Assert( totalBytesProcessed <= remainingDataLength ); + } + + /* Only free pReceivedData if all bytes were processed and no PUBLISH messages + * were in the data stream. */ + if( ( freeReceivedData != NULL ) && + ( totalBytesProcessed == remainingDataLength ) && + ( pFirstPublish == NULL ) ) + { + AwsIotMqtt_Assert( pLastPublish == NULL ); + + freeReceivedData( ( void * ) pReceivedData ); + } + + /* Add all PUBLISH messages to the pending operations queue. */ + if( pLastPublish != NULL ) + { + /* If all bytes of the receive buffer were processed, set the function + * to free the receive buffer. */ + if( ( totalBytesProcessed == remainingDataLength ) && ( freeReceivedData != NULL ) ) + { + pLastPublish->pReceivedData = pReceivedData; + pLastPublish->freeReceivedData = freeReceivedData; + } + else + { + /* When some of the receive buffer is unprocessed, the receive buffer is + * given back to the calling function. The MQTT library cannot guarantee + * that the calling function will keep the receive buffer in scope; + * therefore, data in the receive buffer must be copied for the MQTT + * library's use. */ + for( pOperation = pFirstPublish; pOperation != NULL; pOperation = pOperation->pNextPublish ) + { + /* Neither the buffer pointer nor the free function should be set. */ + AwsIotMqtt_Assert( pOperation->pReceivedData == NULL ); + AwsIotMqtt_Assert( pOperation->freeReceivedData == NULL ); + + /* Allocate a new buffer to hold the topic name and payload. */ + pOperation->pReceivedData = AwsIotMqtt_MallocMessage( pOperation->publishInfo.topicNameLength + + pOperation->publishInfo.payloadLength ); + + if( pOperation->pReceivedData != NULL ) + { + /* Copy the topic name and payload. */ + ( void ) memcpy( ( void * ) pOperation->pReceivedData, + pOperation->publishInfo.pTopicName, + pOperation->publishInfo.topicNameLength ); + ( void ) memcpy( ( uint8_t * ) ( pOperation->pReceivedData ) + + pOperation->publishInfo.topicNameLength, + pOperation->publishInfo.pPayload, + pOperation->publishInfo.payloadLength ); + + /* Set the topic name and payload pointers into the new buffer. + * Also set the free function. */ + pOperation->publishInfo.pTopicName = pOperation->pReceivedData; + pOperation->publishInfo.pPayload = ( uint8_t * ) ( pOperation->pReceivedData ) + + pOperation->publishInfo.topicNameLength; + pOperation->freeReceivedData = AwsIotMqtt_FreeMessage; + } + else + { + /* If a new buffer couldn't be allocated, clear the topic name and + * payload pointers so that this PUBLISH message will be ignored. */ + IotLogWarn( "Failed to allocate memory for incoming PUBLISH message." ); + pOperation->publishInfo.pTopicName = NULL; + pOperation->publishInfo.topicNameLength = 0; + pOperation->publishInfo.pPayload = NULL; + pOperation->publishInfo.payloadLength = 0; + } + } + } + + if( AwsIotMqttInternal_EnqueueOperation( pFirstPublish, + &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) + { + IotLogWarn( "Failed to enqueue incoming PUBLISH for callback." ); + } + } + + return ( int32_t ) totalBytesProcessed; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index ac4439aefc..05f4d51ddd 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -39,7 +39,7 @@ #include "platform/iot_threads.h" /* JSON utilities include. */ -#include "aws_iot_json_utils.h" +#include "iot_json_utils.h" /* Validate Shadow configuration settings. */ #if AWS_IOT_SHADOW_ENABLE_ASSERTS != 0 && AWS_IOT_SHADOW_ENABLE_ASSERTS != 1 @@ -153,7 +153,7 @@ static void _updatedCallbackWrapper( void * pArgument, */ uint64_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; -#if _LIBRARY_LOG_LEVEL > _AWS_IOT_LOG_NONE +#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE /** * @brief Printable names for the Shadow callbacks. @@ -180,18 +180,18 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, /* Check Thing Name. */ if( ( pThingName == NULL ) || ( thingNameLength == 0 ) ) { - AwsIotLogError( "Thing name for Shadow %s cannot be NULL or have length 0.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Thing name for Shadow %s cannot be NULL or have length 0.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } if( thingNameLength > _MAX_THING_NAME_LENGTH ) { - AwsIotLogError( "Thing Name length of %lu exceeds the maximum allowed" - "length of %d.", - ( unsigned long ) thingNameLength, - _MAX_THING_NAME_LENGTH ); + IotLogError( "Thing Name length of %lu exceeds the maximum allowed" + "length of %d.", + ( unsigned long ) thingNameLength, + _MAX_THING_NAME_LENGTH ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -202,8 +202,8 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, /* Check that a reference pointer is provided for a waitable operation. */ if( pReference == NULL ) { - AwsIotLogError( "Reference must be set for a waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Reference must be set for a waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -211,8 +211,8 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, /* A callback should not be set for a waitable operation. */ if( pCallbackInfo != NULL ) { - AwsIotLogError( "Callback should not be set for a waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Callback should not be set for a waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -223,7 +223,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) && ( pCallbackInfo == NULL ) ) { - AwsIotLogError( "Callback info must be provided for non-waitable Shadow GET." ); + IotLogError( "Callback info must be provided for non-waitable Shadow GET." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -232,8 +232,8 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, if( ( pCallbackInfo != NULL ) && ( pCallbackInfo->function == NULL ) ) { - AwsIotLogError( "Callback function must be set for Shadow %s callback.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Callback function must be set for Shadow %s callback.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -253,8 +253,8 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, /* Check QoS. */ if( ( pDocumentInfo->QoS < 0 ) || ( pDocumentInfo->QoS > 1 ) ) { - AwsIotLogError( "QoS for Shadow %d must be 0 or 1.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "QoS for Shadow %d must be 0 or 1.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -262,8 +262,8 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, /* Check the retry parameters. */ if( pDocumentInfo->retryLimit < 0 ) { - AwsIotLogError( "Retry limit of Shadow %s cannot be less than 0.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Retry limit of Shadow %s cannot be less than 0.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -271,8 +271,8 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, { if( pDocumentInfo->retryMs == 0 ) { - AwsIotLogError( "Retry time of Shadow %s must be positive.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Retry time of Shadow %s must be positive.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -285,7 +285,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && ( pDocumentInfo->get.mallocDocument == NULL ) ) { - AwsIotLogError( "Memory allocation function must be set for waitable Shadow GET." ); + IotLogError( "Memory allocation function must be set for waitable Shadow GET." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -297,7 +297,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, if( ( pDocumentInfo->update.pUpdateDocument == NULL ) || ( pDocumentInfo->update.updateDocumentLength == 0 ) ) { - AwsIotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" + IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" " have length 0." ); return AWS_IOT_SHADOW_BAD_PARAMETER; @@ -329,10 +329,10 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec return AWS_IOT_SHADOW_BAD_PARAMETER; } - AwsIotLogDebug( "Processing Shadow %s callback for %.*s.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogDebug( "Processing Shadow %s callback for %.*s.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); /* Lock the subscription list mutex to check for an existing subscription * object. */ @@ -357,20 +357,20 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec /* Replace existing callback. */ if( pCallbackInfo != NULL ) { - AwsIotLogDebug( "Found existing %s callback for %.*s. Replacing callback.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogDebug( "Found existing %s callback for %.*s. Replacing callback.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); pSubscription->callbacks[ type ] = *pCallbackInfo; } /* Remove existing callback. */ else { - AwsIotLogDebug( "Removing existing %s callback for %.*s.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogDebug( "Removing existing %s callback for %.*s.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); /* Unsubscribe, then clear the callback information. */ ( void ) _modifyCallbackSubscriptions( mqttConnection, @@ -391,10 +391,10 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec /* Add new callback. */ if( pCallbackInfo != NULL ) { - AwsIotLogDebug( "Adding new %s callback for %.*s.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogDebug( "Adding new %s callback for %.*s.", + _pAwsIotShadowCallbackNames[ type ], + thingNameLength, + pThingName ); pSubscription->callbacks[ type ] = *pCallbackInfo; status = _modifyCallbackSubscriptions( mqttConnection, @@ -475,10 +475,10 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t pCallbackSuffix[ type ], pCallbackSuffixLength[ type ] ); - AwsIotLogDebug( "%s subscription for %.*s", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", - operationTopicLength + pCallbackSuffixLength[ type ], - pTopicFilter ); + IotLogDebug( "%s subscription for %.*s", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", + operationTopicLength + pCallbackSuffixLength[ type ], + pTopicFilter ); /* Set the members of the MQTT subscription. */ subscription.QoS = 1; @@ -497,12 +497,12 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t /* Check the result of the MQTT operation. */ if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ], - AwsIotMqtt_strerror( mqttStatus ) ); + IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ], + AwsIotMqtt_strerror( mqttStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ if( mqttStatus == AWS_IOT_MQTT_NO_MEMORY ) @@ -516,11 +516,11 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t } else { - AwsIotLogDebug( "Successfully %s %.*s Shadow %s callback.", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ] ); + IotLogDebug( "Successfully %s %.*s Shadow %s callback.", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ] ); } /* MQTT subscribe should check the subscription topic buffer. */ @@ -587,7 +587,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) /* Create the Shadow pending operation list mutex. */ if( IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ) ) == false ) { - AwsIotLogError( "Failed to create Shadow pending operation list." ); + IotLogError( "Failed to create Shadow pending operation list." ); return AWS_IOT_SHADOW_INIT_FAILED; } @@ -595,7 +595,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) /* Create the Shadow subscription list mutex. */ if( IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ) ) == false ) { - AwsIotLogError( "Failed to create Shadow subscription list." ); + IotLogError( "Failed to create Shadow subscription list." ); IotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); return AWS_IOT_SHADOW_INIT_FAILED; @@ -611,7 +611,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; } - AwsIotLogInfo( "Shadow library successfully initialized." ); + IotLogInfo( "Shadow library successfully initialized." ); return AWS_IOT_SHADOW_SUCCESS; } @@ -641,7 +641,7 @@ void AwsIotShadow_Cleanup( void ) /* Restore the default MQTT timeout. */ _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; - AwsIotLogInfo( "Shadow library cleanup done." ); + IotLogInfo( "Shadow library cleanup done." ); } /*-----------------------------------------------------------*/ @@ -889,15 +889,15 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, } /* Check UPDATE document for a client token. */ - if( AwsIotJsonUtils_FindJsonValue( pUpdateInfo->update.pUpdateDocument, - pUpdateInfo->update.updateDocumentLength, - _CLIENT_TOKEN_KEY, - _CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ) == false ) + if( IotJsonUtils_FindJsonValue( pUpdateInfo->update.pUpdateDocument, + pUpdateInfo->update.updateDocumentLength, + _CLIENT_TOKEN_KEY, + _CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ) == false ) { - AwsIotLogError( "Shadow document for Shadow UPDATE must have a %s key.", - _CLIENT_TOKEN_KEY ); + IotLogError( "Shadow document for Shadow UPDATE must have a %s key.", + _CLIENT_TOKEN_KEY ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -907,8 +907,8 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, * service. */ if( ( clientTokenLength < 2 ) || ( clientTokenLength > _MAX_CLIENT_TOKEN_LENGTH ) ) { - AwsIotLogError( "Client token length must be between 2 and %d (including " - "enclosing quotes).", _MAX_CLIENT_TOKEN_LENGTH + 2 ); + IotLogError( "Client token length must be between 2 and %d (including " + "enclosing quotes).", _MAX_CLIENT_TOKEN_LENGTH + 2 ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -934,7 +934,7 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, if( pOperation->update.pClientToken == NULL ) { - AwsIotLogError( "Failed to allocate memory for Shadow update client token." ); + IotLogError( "Failed to allocate memory for Shadow update client token." ); AwsIotShadowInternal_DestroyOperation( pOperation ); return AWS_IOT_SHADOW_NO_MEMORY; @@ -1016,7 +1016,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, /* Check that reference is set. */ if( pOperation == NULL ) { - AwsIotLogError( "Reference cannot be NULL." ); + IotLogError( "Reference cannot be NULL." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -1024,7 +1024,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, /* Check that reference is waitable. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { - AwsIotLogError( "Reference is not a waitable Shadow operation." ); + IotLogError( "Reference is not a waitable Shadow operation." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -1034,7 +1034,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, { if( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) { - AwsIotLogError( "Output buffer and size pointer must be set for Shadow GET." ); + IotLogError( "Output buffer and size pointer must be set for Shadow GET." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 952147edf4..ef1bdd4614 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -39,7 +39,7 @@ #include "platform/iot_threads.h" /* JSON utils include. */ -#include "aws_iot_json_utils.h" +#include "iot_json_utils.h" /*-----------------------------------------------------------*/ @@ -123,7 +123,7 @@ static void _updateCallback( void * pArgument, /*-----------------------------------------------------------*/ -#if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE +#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE /** * @brief Printable names for each of the Shadow operations. @@ -136,7 +136,7 @@ static void _updateCallback( void * pArgument, "SET DELTA", "SET UPDATED" }; -#endif /* if _LIBRARY_LOG_LEVEL > AWS_IOT_LOG_NONE */ +#endif /* if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ /** * @brief List of active Shadow operations awaiting a response from the Shadow @@ -178,15 +178,15 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, AwsIotShadow_Assert( pOperation->update.pClientToken != NULL ); AwsIotShadow_Assert( pOperation->update.clientTokenLength > 0 ); - AwsIotLogDebug( "Verifying client tokens for Shadow UPDATE." ); + IotLogDebug( "Verifying client tokens for Shadow UPDATE." ); /* Check for the client token in the UPDATE response document. */ - match = AwsIotJsonUtils_FindJsonValue( pParam->pDocument, - pParam->documentLength, - _CLIENT_TOKEN_KEY, - _CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ); + match = IotJsonUtils_FindJsonValue( pParam->pDocument, + pParam->documentLength, + _CLIENT_TOKEN_KEY, + _CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ); /* If the UPDATE response document has a client token, check that it * matches. */ @@ -199,10 +199,10 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, } else { - AwsIotLogWarn( "Received a Shadow UPDATE response with no client token. " - "This is possibly a response to a bad JSON document:\n%.*s", - pParam->documentLength, - pParam->pDocument ); + IotLogWarn( "Received a Shadow UPDATE response with no client token. " + "This is possibly a response to a bad JSON document:\n%.*s", + pParam->documentLength, + pParam->pDocument ); } } @@ -256,8 +256,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, * without doing anything */ IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - AwsIotLogWarn( "Shadow %s callback received an unknown operation.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogWarn( "Shadow %s callback received an unknown operation.", + _pAwsIotShadowOperationNames[ type ] ); return; } @@ -275,9 +275,9 @@ static void _commonOperationCallback( _shadowOperationType_t type, AwsIotShadow_Assert( pOperation->type == type ); AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - AwsIotLogDebug( "Received Shadow response on topic %.*s", - pMessage->message.info.topicNameLength, - pMessage->message.info.pTopicName ); + IotLogDebug( "Received Shadow response on topic %.*s", + pMessage->message.info.topicNameLength, + pMessage->message.info.pTopicName ); /* Parse the status from the topic name. */ status = AwsIotShadowInternal_ParseShadowStatus( pMessage->message.info.pTopicName, @@ -286,10 +286,10 @@ static void _commonOperationCallback( _shadowOperationType_t type, switch( status ) { case _SHADOW_ACCEPTED: - AwsIotLogInfo( "Shadow %s of %.*s was ACCEPTED.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); + IotLogInfo( "Shadow %s of %.*s was ACCEPTED.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); /* Process the retrieved document for a Shadow GET. Otherwise, set * status to success. */ @@ -306,20 +306,20 @@ static void _commonOperationCallback( _shadowOperationType_t type, break; case _SHADOW_REJECTED: - AwsIotLogWarn( "Shadow %s of %.*s was REJECTED.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); + IotLogWarn( "Shadow %s of %.*s was REJECTED.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); pOperation->status = AwsIotShadowInternal_ParseErrorDocument( pMessage->message.info.pPayload, pMessage->message.info.payloadLength ); break; default: - AwsIotLogWarn( "Unknown status for %s of %.*s Shadow. Ignoring message.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); + IotLogWarn( "Unknown status for %s of %.*s Shadow. Ignoring message.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); pOperation->status = AWS_IOT_SHADOW_BAD_RESPONSE; break; @@ -380,7 +380,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOper } else { - AwsIotLogDebug( "Allocating new buffer for waitable Shadow GET." ); + IotLogDebug( "Allocating new buffer for waitable Shadow GET." ); /* Parameter validation should not have allowed a NULL malloc function. */ AwsIotShadow_Assert( pOperation->get.mallocDocument != NULL ); @@ -390,7 +390,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOper if( pOperation->get.pDocument == NULL ) { - AwsIotLogError( "Failed to allocate buffer for retrieved Shadow document." ); + IotLogError( "Failed to allocate buffer for retrieved Shadow document." ); status = AWS_IOT_SHADOW_NO_MEMORY; } @@ -427,16 +427,16 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** { _shadowOperation_t * pOperation = NULL; - AwsIotLogDebug( "Creating operation record for Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogDebug( "Creating operation record for Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); /* Allocate memory for a new Shadow operation. */ pOperation = AwsIotShadow_MallocOperation( sizeof( _shadowOperation_t ) ); if( pOperation == NULL ) { - AwsIotLogError( "Failed to allocate memory for Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + IotLogError( "Failed to allocate memory for Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); return AWS_IOT_SHADOW_NO_MEMORY; } @@ -450,7 +450,7 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** { if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) { - AwsIotLogError( "Failed to create semaphore for waitable Shadow %s.", + IotLogError( "Failed to create semaphore for waitable Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); AwsIotShadow_FreeOperation( pOperation ); @@ -488,8 +488,8 @@ void AwsIotShadowInternal_DestroyOperation( void * pData ) /* The Shadow operation pointer must not be NULL. */ AwsIotShadow_Assert( pOperation != NULL ); - AwsIotLogDebug( "Destroying Shadow operation %s.", - _pAwsIotShadowOperationNames[ pOperation->type ] ); + IotLogDebug( "Destroying Shadow operation %s.", + _pAwsIotShadowOperationNames[ pOperation->type ] ); /* Check if a wait semaphore was created for this operation. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) @@ -619,10 +619,10 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ _updateCallback }; - AwsIotLogDebug( "Processing Shadow operation %s for Thing %.*s.", - _pAwsIotShadowOperationNames[ pOperation->type ], - thingNameLength, - pThingName ); + IotLogDebug( "Processing Shadow operation %s for Thing %.*s.", + _pAwsIotShadowOperationNames[ pOperation->type ], + thingNameLength, + pThingName ); /* Set the operation's MQTT connection. */ pOperation->mqttConnection = mqttConnection; @@ -634,7 +634,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ &pTopicBuffer, &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) { - AwsIotLogError( "No memory for Shadow operation topic buffer." ); + IotLogError( "No memory for Shadow operation topic buffer." ); AwsIotShadowInternal_DestroyOperation( pOperation ); @@ -704,10 +704,10 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ publishInfo.pTopicName = pTopicBuffer; publishInfo.topicNameLength = operationTopicLength; - AwsIotLogDebug( "Shadow %s message will be published to topic %.*s", - _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.topicNameLength, - publishInfo.pTopicName ); + IotLogDebug( "Shadow %s message will be published to topic %.*s", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.topicNameLength, + publishInfo.pTopicName ); /* Set the document info if this operation is not a Shadow DELETE. */ if( pOperation->type != _SHADOW_DELETE ) @@ -716,12 +716,12 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ publishInfo.retryLimit = pDocumentInfo->retryLimit; publishInfo.retryMs = pDocumentInfo->retryMs; - AwsIotLogDebug( "Shadow %s message will be published at QoS %d with " - "retryLimit %d and retryMs %llu.", - _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.QoS, - publishInfo.retryLimit, - publishInfo.retryMs ); + IotLogDebug( "Shadow %s message will be published at QoS %d with " + "retryLimit %d and retryMs %llu.", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.QoS, + publishInfo.retryLimit, + publishInfo.retryMs ); } /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ @@ -754,11 +754,11 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ /* Check for errors from the MQTT publish. */ if( publishStatus != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", - _pAwsIotShadowOperationNames[ pOperation->type ], - thingNameLength, - pThingName, - AwsIotMqtt_strerror( publishStatus ) ); + IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", + _pAwsIotShadowOperationNames[ pOperation->type ], + thingNameLength, + pThingName, + AwsIotMqtt_strerror( publishStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ if( publishStatus == AWS_IOT_MQTT_NO_MEMORY ) @@ -788,8 +788,8 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ } else { - AwsIotLogDebug( "Shadow %s PUBLISH message successfully sent.", - _pAwsIotShadowOperationNames[ pOperation->type ] ); + IotLogDebug( "Shadow %s PUBLISH message successfully sent.", + _pAwsIotShadowOperationNames[ pOperation->type ] ); } } diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index e9a7ba9658..4df4c61156 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -37,7 +37,7 @@ #include "private/aws_iot_shadow_internal.h" /* JSON utilities include. */ -#include "aws_iot_json_utils.h" +#include "iot_json_utils.h" /*-----------------------------------------------------------*/ @@ -194,17 +194,17 @@ AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const unsigned long code = 0; /* Parse the code from the error document. */ - if( AwsIotJsonUtils_FindJsonValue( pErrorDocument, - errorDocumentLength, - _ERROR_DOCUMENT_CODE_KEY, - _ERROR_DOCUMENT_CODE_KEY_LENGTH, - &pCode, - &codeLength ) == false ) + if( IotJsonUtils_FindJsonValue( pErrorDocument, + errorDocumentLength, + _ERROR_DOCUMENT_CODE_KEY, + _ERROR_DOCUMENT_CODE_KEY_LENGTH, + &pCode, + &codeLength ) == false ) { /* Error parsing JSON document, or no "code" key was found. */ - AwsIotLogWarn( "Failed to parse code from error document.\n%.*s", - errorDocumentLength, - pErrorDocument ); + IotLogWarn( "Failed to parse code from error document.\n%.*s", + errorDocumentLength, + pErrorDocument ); return AWS_IOT_SHADOW_BAD_RESPONSE; } @@ -218,24 +218,24 @@ AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const /* Parse the error message and print it. An error document must always contain * a message. */ - if( AwsIotJsonUtils_FindJsonValue( pErrorDocument, - errorDocumentLength, - _ERROR_DOCUMENT_MESSAGE_KEY, - _ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, - &pMessage, - &messageLength ) == true ) + if( IotJsonUtils_FindJsonValue( pErrorDocument, + errorDocumentLength, + _ERROR_DOCUMENT_MESSAGE_KEY, + _ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, + &pMessage, + &messageLength ) == true ) { - AwsIotLogWarn( "Code %lu: %.*s.", - code, - messageLength, - pMessage ); + IotLogWarn( "Code %lu: %.*s.", + code, + messageLength, + pMessage ); } else { - AwsIotLogWarn( "Code %lu; failed to parse message from error document.\n%.*s", - code, - errorDocumentLength, - pErrorDocument ); + IotLogWarn( "Code %lu; failed to parse message from error document.\n%.*s", + code, + errorDocumentLength, + pErrorDocument ); /* An error document must contain a message; if it does not, then it is invalid. */ return AWS_IOT_SHADOW_BAD_RESPONSE; diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index e021341684..05787fdee3 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -135,10 +135,10 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t /* Per the AWS IoT documentation, Shadow topic subscriptions are QoS 1. */ subscription.QoS = 1; - AwsIotLogDebug( "%s Shadow subscription for %.*s", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", - topicFilterLength, - pTopicFilter ); + IotLogDebug( "%s Shadow subscription for %.*s", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", + topicFilterLength, + pTopicFilter ); /* Set the members of the subscription parameter. */ subscription.pTopicFilter = pTopicFilter; @@ -156,11 +156,11 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t /* Check the result of the MQTT operation. */ if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) { - AwsIotLogError( "Failed to %s %.*s, error %s.", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", - topicFilterLength, - pTopicFilter, - AwsIotMqtt_strerror( mqttStatus ) ); + IotLogError( "Failed to %s %.*s, error %s.", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + topicFilterLength, + pTopicFilter, + AwsIotMqtt_strerror( mqttStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ if( mqttStatus == AWS_IOT_MQTT_NO_MEMORY ) @@ -172,10 +172,10 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t } else { - AwsIotLogDebug( "Successfully %s %.*s", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", - topicFilterLength, - pTopicFilter ); + IotLogDebug( "Successfully %s %.*s", + mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + topicFilterLength, + pTopicFilter ); } return AWS_IOT_SHADOW_STATUS_PENDING; @@ -220,22 +220,22 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ), &( pSubscription->link ) ); - AwsIotLogDebug( "Created new Shadow subscriptions object for %.*s.", - thingNameLength, - pThingName ); + IotLogDebug( "Created new Shadow subscriptions object for %.*s.", + thingNameLength, + pThingName ); } else { - AwsIotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.", - thingNameLength, - pThingName ); + IotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.", + thingNameLength, + pThingName ); } } else { - AwsIotLogDebug( "Found existing Shadow subscriptions object for %.*s.", - thingNameLength, - pThingName ); + IotLogDebug( "Found existing Shadow subscriptions object for %.*s.", + thingNameLength, + pThingName ); } return pSubscription; @@ -248,9 +248,9 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub { int i = 0; - AwsIotLogDebug( "Checking if subscription object for %.*s can be removed.", - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Checking if subscription object for %.*s can be removed.", + pSubscription->thingNameLength, + pSubscription->pThingName ); /* If any Shadow operation's subscription reference count is not 0, then the * subscription cannot be removed. */ @@ -258,20 +258,20 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub { if( pSubscription->references[ i ] > 0 ) { - AwsIotLogDebug( "Reference count %d for %.*s subscription object. " - "Subscription cannot be removed yet.", - pSubscription->references[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Reference count %d for %.*s subscription object. " + "Subscription cannot be removed yet.", + pSubscription->references[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); return; } else if( pSubscription->references[ i ] == _PERSISTENT_SUBSCRIPTION ) { - AwsIotLogDebug( "Subscription object for %.*s has persistent subscriptions. " - "Subscription will not be removed.", - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " + "Subscription will not be removed.", + pSubscription->thingNameLength, + pSubscription->pThingName ); return; } @@ -282,11 +282,11 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub { if( pSubscription->callbacks[ i ].function != NULL ) { - AwsIotLogDebug( "Found active Shadow %s callback for %.*s subscription object. " - "Subscription cannot be removed yet.", - _pAwsIotShadowCallbackNames[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Found active Shadow %s callback for %.*s subscription object. " + "Subscription cannot be removed yet.", + _pAwsIotShadowCallbackNames[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); return; } @@ -296,9 +296,9 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub * Remove the subscription object. */ IotListDouble_Remove( &( pSubscription->link ) ); - AwsIotLogDebug( "Removed subscription object for %.*s.", - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Removed subscription object for %.*s.", + pSubscription->thingNameLength, + pSubscription->pThingName ); /* If the caller requested the removed subscription, set the output parameter. * Otherwise, free the memory used by the subscription. */ @@ -341,11 +341,11 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t /* Do nothing if this operation has persistent subscriptions. */ if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) { - AwsIotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " - "count will not be incremented.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " + "count will not be incremented.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); return AWS_IOT_SHADOW_STATUS_PENDING; } @@ -425,21 +425,21 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t { ( pSubscription->references[ type ] )++; - AwsIotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName, - pSubscription->references[ type ] ); + IotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName, + pSubscription->references[ type ] ); } /* Otherwise, set the persistent subscriptions flag. */ else { pSubscription->references[ type ] = _PERSISTENT_SUBSCRIPTION; - AwsIotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); } return status; @@ -459,11 +459,11 @@ void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOpera /* Do nothing if this Shadow operation has persistent subscriptions. */ if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) { - AwsIotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " - "count will not be decremented.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " + "count will not be decremented.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); return; } @@ -476,10 +476,10 @@ void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOpera /* Check if the number of references has reached 0. */ if( pSubscription->references[ type ] == 0 ) { - AwsIotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowOperationNames[ type ] ); + IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowOperationNames[ type ] ); /* Subscription must have a topic buffer. */ AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); @@ -554,9 +554,9 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec .thingNameLength = thingNameLength }; - AwsIotLogInfo( "Removing persistent subscriptions for %.*s.", - thingNameLength, - pThingName ); + IotLogInfo( "Removing persistent subscriptions for %.*s.", + thingNameLength, + pThingName ); IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); @@ -571,19 +571,19 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec /* Unsubscribe from operation subscriptions if found. */ if( pSubscription != NULL ) { - AwsIotLogDebug( "Found subscription object for %.*s. Checking for persistent " - "subscriptions to remove.", - thingNameLength, - pThingName ); + IotLogDebug( "Found subscription object for %.*s. Checking for persistent " + "subscriptions to remove.", + thingNameLength, + pThingName ); for( i = 0; i < _SHADOW_OPERATION_COUNT; i++ ) { if( ( flags & ( 0x1UL << i ) ) != 0 ) { - AwsIotLogDebug( "Removing %.*s %s persistent subscriptions.", - thingNameLength, - pThingName, - _pAwsIotShadowOperationNames[ i ] ); + IotLogDebug( "Removing %.*s %s persistent subscriptions.", + thingNameLength, + pThingName, + _pAwsIotShadowOperationNames[ i ] ); /* Subscription must have a topic buffer. */ AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); @@ -638,19 +638,19 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec } else { - AwsIotLogDebug( "%.*s %s does not have persistent subscriptions.", - thingNameLength, - pThingName, - _pAwsIotShadowOperationNames[ i ] ); + IotLogDebug( "%.*s %s does not have persistent subscriptions.", + thingNameLength, + pThingName, + _pAwsIotShadowOperationNames[ i ] ); } } } } else { - AwsIotLogWarn( "No subscription object found for %.*s", - thingNameLength, - pThingName ); + IotLogWarn( "No subscription object found for %.*s", + thingNameLength, + pThingName ); } IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index 72d594d3a2..b5e0b65053 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -55,18 +55,18 @@ #include "platform/iot_clock.h" /* Configure logs for the functions in this file. */ -#ifdef AWS_IOT_LOG_LEVEL_PLATFORM - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_PLATFORM +#ifdef IOT_LOG_LEVEL_PLATFORM + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM #else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif #define _LIBRARY_LOG_NAME ( "CLOCK" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /*-----------------------------------------------------------*/ @@ -146,7 +146,7 @@ bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, } else { - AwsIotLogError( "Failed to read system time. errno=%d", errno ); + IotLogError( "Failed to read system time. errno=%d", errno ); status = false; } @@ -200,8 +200,8 @@ uint64_t IotClock_GetTimeMs( void ) if( clock_gettime( CLOCK_MONOTONIC, ¤tTime ) != 0 ) { - AwsIotLogWarn( "Failed to read time from CLOCK_MONOTONIC. errno=%d", - errno ); + IotLogWarn( "Failed to read time from CLOCK_MONOTONIC. errno=%d", + errno ); } return ( ( uint64_t ) currentTime.tv_sec ) * 1000ULL + @@ -224,7 +224,7 @@ bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, .sigev_notify_attributes = NULL }; - AwsIotLogDebug( "Creating new timer %p.", pNewTimer ); + IotLogDebug( "Creating new timer %p.", pNewTimer ); /* Set the timer expiration routine and argument. */ pNewTimer->threadRoutine = expirationRoutine; @@ -235,7 +235,7 @@ bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, &expirationNotification, &( pNewTimer->timer ) ) != 0 ) { - AwsIotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); + IotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); status = false; } @@ -246,12 +246,12 @@ bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, void IotClock_TimerDestroy( IotTimer_t * const pTimer ) { - AwsIotLogDebug( "Destroying timer %p.", pTimer ); + IotLogDebug( "Destroying timer %p.", pTimer ); /* Destroy the underlying POSIX timer. */ if( timer_delete( pTimer->timer ) != 0 ) { - AwsIotLogWarn( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); + IotLogWarn( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); } } @@ -268,16 +268,16 @@ bool IotClock_TimerArm( IotTimer_t * const pTimer, .it_interval = { 0 } }; - AwsIotLogDebug( "Arming timer %p with timeout %llu and period %llu.", - pTimer, - relativeTimeoutMs, - periodMs ); + IotLogDebug( "Arming timer %p with timeout %llu and period %llu.", + pTimer, + relativeTimeoutMs, + periodMs ); /* Calculate the initial timer expiration. */ if( IotClock_TimeoutToTimespec( relativeTimeoutMs, &( timerExpiration.it_value ) ) == false ) { - AwsIotLogError( "Invalid relative timeout." ); + IotLogError( "Invalid relative timeout." ); status = false; } @@ -294,7 +294,7 @@ bool IotClock_TimerArm( IotTimer_t * const pTimer, /* Arm the underlying POSIX timer. */ if( timer_settime( pTimer->timer, TIMER_ABSTIME, &timerExpiration, NULL ) != 0 ) { - AwsIotLogError( "Failed to arm timer %p. errno=%d.", pTimer, errno ); + IotLogError( "Failed to arm timer %p. errno=%d.", pTimer, errno ); status = false; } diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 58a65f8e1b..39ab0cdeab 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -55,18 +55,18 @@ #include "platform/iot_threads.h" /* Configure logs for the functions in this file. */ -#ifdef AWS_IOT_LOG_LEVEL_PLATFORM - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_PLATFORM +#ifdef IOT_LOG_LEVEL_PLATFORM + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM #else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif #define _LIBRARY_LOG_NAME ( "THREAD" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /* * Provide default values for undefined memory allocation functions. @@ -144,14 +144,14 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, pthread_t newThread; pthread_attr_t threadAttributes; - AwsIotLogDebug( "Creating new thread." ); + IotLogDebug( "Creating new thread." ); /* Allocate memory for the new thread. */ _threadInfo_t * pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); if( pThreadInfo == NULL ) { - AwsIotLogError( "Failed to allocate memory for new thread." ); + IotLogError( "Failed to allocate memory for new thread." ); status = false; } @@ -164,8 +164,8 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, if( posixErrno != 0 ) { - AwsIotLogError( "Failed to initialize thread attributes. errno=%d.", - posixErrno ); + IotLogError( "Failed to initialize thread attributes. errno=%d.", + posixErrno ); status = false; } @@ -177,8 +177,8 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, if( posixErrno != 0 ) { - AwsIotLogError( "Failed to set detached thread attribute. errno=%d.", - posixErrno ); + IotLogError( "Failed to set detached thread attribute. errno=%d.", + posixErrno ); ( void ) pthread_attr_destroy( &threadAttributes ); status = false; @@ -200,7 +200,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, if( posixErrno != 0 ) { - AwsIotLogError( "Failed to create new thread. errno=%d.", posixErrno ); + IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); status = false; } @@ -210,8 +210,8 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, if( posixErrno != 0 ) { - AwsIotLogWarn( "Failed to destroy thread attributes. errno=%d.", - posixErrno ); + IotLogWarn( "Failed to destroy thread attributes. errno=%d.", + posixErrno ); } } @@ -232,15 +232,15 @@ bool IotMutex_Create( IotMutex_t * const pNewMutex ) { bool status = true; - AwsIotLogDebug( "Creating new mutex %p.", pNewMutex ); + IotLogDebug( "Creating new mutex %p.", pNewMutex ); int mutexError = pthread_mutex_init( pNewMutex, NULL ); if( mutexError != 0 ) { - AwsIotLogError( "Failed to create new mutex %p. errno=%d.", - pNewMutex, - mutexError ); + IotLogError( "Failed to create new mutex %p. errno=%d.", + pNewMutex, + mutexError ); status = false; } @@ -252,15 +252,15 @@ bool IotMutex_Create( IotMutex_t * const pNewMutex ) void IotMutex_Destroy( IotMutex_t * const pMutex ) { - AwsIotLogDebug( "Destroying mutex %p.", pMutex ); + IotLogDebug( "Destroying mutex %p.", pMutex ); int mutexError = pthread_mutex_destroy( pMutex ); if( mutexError != 0 ) { - AwsIotLogWarn( "Failed to destroy mutex %p. errno=%d.", - pMutex, - mutexError ); + IotLogWarn( "Failed to destroy mutex %p. errno=%d.", + pMutex, + mutexError ); } } @@ -268,15 +268,15 @@ void IotMutex_Destroy( IotMutex_t * const pMutex ) void IotMutex_Lock( IotMutex_t * const pMutex ) { - AwsIotLogDebug( "Locking mutex %p.", pMutex ); + IotLogDebug( "Locking mutex %p.", pMutex ); int mutexError = pthread_mutex_lock( pMutex ); if( mutexError != 0 ) { - AwsIotLogWarn( "Failed to lock mutex %p. errno=%d.", - pMutex, - mutexError ); + IotLogWarn( "Failed to lock mutex %p. errno=%d.", + pMutex, + mutexError ); } } @@ -286,15 +286,15 @@ bool IotMutex_TryLock( IotMutex_t * const pMutex ) { bool status = true; - AwsIotLogDebug( "Attempting to lock mutex %p.", pMutex ); + IotLogDebug( "Attempting to lock mutex %p.", pMutex ); int mutexError = pthread_mutex_trylock( pMutex ); if( mutexError != 0 ) { - AwsIotLogDebug( "Mutex mutex %p is not available. errno=%d.", - pMutex, - mutexError ); + IotLogDebug( "Mutex mutex %p is not available. errno=%d.", + pMutex, + mutexError ); status = false; } @@ -306,15 +306,15 @@ bool IotMutex_TryLock( IotMutex_t * const pMutex ) void IotMutex_Unlock( IotMutex_t * const pMutex ) { - AwsIotLogDebug( "Unlocking mutex %p.", pMutex ); + IotLogDebug( "Unlocking mutex %p.", pMutex ); int mutexError = pthread_mutex_unlock( pMutex ); if( mutexError != 0 ) { - AwsIotLogWarn( "Failed to unlock mutex %p. errno=%d.", - pMutex, - mutexError ); + IotLogWarn( "Failed to unlock mutex %p. errno=%d.", + pMutex, + mutexError ); } } @@ -326,12 +326,12 @@ bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, { bool status = true; - AwsIotLogDebug( "Creating new semaphore %p.", pNewSemaphore ); + IotLogDebug( "Creating new semaphore %p.", pNewSemaphore ); if( maxValue > ( uint32_t ) SEM_VALUE_MAX ) { - AwsIotLogError( "%lu is larger than the maximum value a semaphore may" - " have on this system.", maxValue ); + IotLogError( "%lu is larger than the maximum value a semaphore may" + " have on this system.", maxValue ); status = false; } @@ -339,9 +339,9 @@ bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, { if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) { - AwsIotLogError( "Failed to create new semaphore %p. errno=%d.", - pNewSemaphore, - errno ); + IotLogError( "Failed to create new semaphore %p. errno=%d.", + pNewSemaphore, + errno ); status = false; } @@ -358,12 +358,12 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ) if( sem_getvalue( pSemaphore, &count ) != 0 ) { - AwsIotLogWarn( "Failed to query semaphore count of %p. errno=%d.", - pSemaphore, - errno ); + IotLogWarn( "Failed to query semaphore count of %p. errno=%d.", + pSemaphore, + errno ); } - AwsIotLogDebug( "Semaphore %p has count %d.", pSemaphore, count ); + IotLogDebug( "Semaphore %p has count %d.", pSemaphore, count ); return ( uint32_t ) count; } @@ -372,13 +372,13 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ) void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ) { - AwsIotLogDebug( "Destroying semaphore %p.", pSemaphore ); + IotLogDebug( "Destroying semaphore %p.", pSemaphore ); if( sem_destroy( pSemaphore ) != 0 ) { - AwsIotLogWarn( "Failed to destroy semaphore %p. errno=%d.", - pSemaphore, - errno ); + IotLogWarn( "Failed to destroy semaphore %p. errno=%d.", + pSemaphore, + errno ); } } @@ -386,13 +386,13 @@ void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ) void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) { - AwsIotLogDebug( "Waiting on semaphore %p.", pSemaphore ); + IotLogDebug( "Waiting on semaphore %p.", pSemaphore ); if( sem_wait( pSemaphore ) != 0 ) { - AwsIotLogWarn( "Failed to wait on semaphore %p. errno=%d.", - pSemaphore, - errno ); + IotLogWarn( "Failed to wait on semaphore %p. errno=%d.", + pSemaphore, + errno ); } } @@ -402,13 +402,13 @@ bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ) { bool status = true; - AwsIotLogDebug( "Attempting to wait on semaphore %p.", pSemaphore ); + IotLogDebug( "Attempting to wait on semaphore %p.", pSemaphore ); if( sem_trywait( pSemaphore ) != 0 ) { - AwsIotLogDebug( "Semaphore %p is not available. errno=%d.", - pSemaphore, - errno ); + IotLogDebug( "Semaphore %p is not available. errno=%d.", + pSemaphore, + errno ); status = false; } @@ -424,12 +424,13 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, bool status = true; struct timespec timeout = { 0 }; - AwsIotLogDebug( "Attempting to wait on semaphore %p with timeout %llu.", - pSemaphore, timeoutMs ); + IotLogDebug( "Attempting to wait on semaphore %p with timeout %llu.", + pSemaphore, + timeoutMs ); if( IotClock_TimeoutToTimespec( timeoutMs, &timeout ) == false ) { - AwsIotLogError( "Invalid timeout." ); + IotLogError( "Invalid timeout." ); status = false; } @@ -437,9 +438,9 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, { if( sem_timedwait( pSemaphore, &timeout ) != 0 ) { - AwsIotLogDebug( "Semaphore %p is not available. errno=%d.", - pSemaphore, - errno ); + IotLogDebug( "Semaphore %p is not available. errno=%d.", + pSemaphore, + errno ); status = false; } @@ -452,13 +453,13 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, void IotSemaphore_Post( IotSemaphore_t * const pSemaphore ) { - AwsIotLogDebug( "Posting to semaphore %p.", pSemaphore ); + IotLogDebug( "Posting to semaphore %p.", pSemaphore ); if( sem_post( pSemaphore ) != 0 ) { - AwsIotLogWarn( "Failed to post to semaphore %p. errno=%d.", - pSemaphore, - errno ); + IotLogWarn( "Failed to post to semaphore %p. errno=%d.", + pSemaphore, + errno ); } } diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index f985bdbe40..cfacc29a7b 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -59,40 +59,40 @@ #include "platform/iot_threads.h" /* Configure logs for the functions in this file. */ -#ifdef AWS_IOT_LOG_LEVEL_NETWORK - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_NETWORK +#ifdef IOT_LOG_LEVEL_NETWORK + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK #else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_GLOBAL + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_NONE + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif #define _LIBRARY_LOG_NAME ( "NET" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /* * Provide default values for undefined memory allocation functions based on * the usage of dynamic memory allocation. */ -#ifndef AwsIotNetwork_Malloc +#ifndef IotNetwork_Malloc #include /** * @brief Memory allocation. This function should have the same signature as * [malloc.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) */ - #define AwsIotNetwork_Malloc malloc + #define IotNetwork_Malloc malloc #endif -#ifndef AwsIotNetwork_Free +#ifndef IotNetwork_Free #include /** * @brief Free memory. This function should have the same signature as * [free.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) */ - #define AwsIotNetwork_Free free + #define IotNetwork_Free free #endif /*-----------------------------------------------------------*/ @@ -180,8 +180,8 @@ static void * _networkReceiveThread( void * pArgument ) ( fileDescriptors.revents & POLLHUP ) || ( fileDescriptors.revents & POLLNVAL ) ) { - AwsIotLogError( "Detected error on socket %d, receive thread exiting.", - pConnectionInfo->tcpSocket ); + IotLogError( "Detected error on socket %d, receive thread exiting.", + pConnectionInfo->tcpSocket ); break; } @@ -189,8 +189,8 @@ static void * _networkReceiveThread( void * pArgument ) * function may include more than just the data. */ if( ioctl( pConnectionInfo->tcpSocket, FIONREAD, &bytesAvailable ) != 0 ) { - AwsIotLogError( "Failed to query bytes available on socket. errno=%d.", - errno ); + IotLogError( "Failed to query bytes available on socket. errno=%d.", + errno ); /* Unlock the connection mutex before polling again. */ IotMutex_Unlock( &( pConnectionInfo->mutex ) ); @@ -198,26 +198,26 @@ static void * _networkReceiveThread( void * pArgument ) continue; } - AwsIotLogDebug( "%d bytes available on socket %d.", - bytesAvailable, - pConnectionInfo->tcpSocket ); + IotLogDebug( "%d bytes available on socket %d.", + bytesAvailable, + pConnectionInfo->tcpSocket ); /* If no bytes can be read, the socket is likely closed. Terminate this thread. */ if( bytesAvailable == 0 ) { - AwsIotLogInfo( "No data available, terminating receive thread for socket %d.", - pConnectionInfo->tcpSocket ); + IotLogInfo( "No data available, terminating receive thread for socket %d.", + pConnectionInfo->tcpSocket ); break; } /* Allocate memory to hold the received message. */ - pReceiveBuffer = AwsIotNetwork_Malloc( ( size_t ) bytesAvailable ); + pReceiveBuffer = IotNetwork_Malloc( ( size_t ) bytesAvailable ); if( pReceiveBuffer == NULL ) { - AwsIotLogError( "Failed to allocate %d bytes for socket read on %d.", - bytesAvailable, - pConnectionInfo->tcpSocket ); + IotLogError( "Failed to allocate %d bytes for socket read on %d.", + bytesAvailable, + pConnectionInfo->tcpSocket ); /* Unlock the connection mutex before polling again. */ IotMutex_Unlock( &( pConnectionInfo->mutex ) ); @@ -244,16 +244,16 @@ static void * _networkReceiveThread( void * pArgument ) /* Check how many bytes were read. */ if( bytesRead <= 0 ) { - AwsIotLogError( "Error reading from socket %d.", - pConnectionInfo->tcpSocket ); + IotLogError( "Error reading from socket %d.", + pConnectionInfo->tcpSocket ); - AwsIotNetwork_Free( pReceiveBuffer ); + IotNetwork_Free( pReceiveBuffer ); break; } - AwsIotLogDebug( "Received %d bytes from socket %d.", - bytesRead, - pConnectionInfo->tcpSocket ); + IotLogDebug( "Received %d bytes from socket %d.", + bytesRead, + pConnectionInfo->tcpSocket ); /* Invoke the callback function. But if there's no callback to invoke, * terminate this thread. */ @@ -263,12 +263,12 @@ static void * _networkReceiveThread( void * pArgument ) pReceiveBuffer, 0, ( size_t ) bytesRead, - AwsIotNetwork_Free ); + IotNetwork_Free ); } else { /* Free resources and terminate thread. */ - AwsIotNetwork_Free( pReceiveBuffer ); + IotNetwork_Free( pReceiveBuffer ); break; } @@ -276,8 +276,8 @@ static void * _networkReceiveThread( void * pArgument ) /* Check the return value of the MQTT callback. */ if( bytesProcessed < 0 ) { - AwsIotLogError( "Detected MQTT protocol violation. Receive thread for " - "socket %d terminating.", pConnectionInfo->tcpSocket ); + IotLogError( "Detected MQTT protocol violation. Receive thread for " + "socket %d terminating.", pConnectionInfo->tcpSocket ); break; } @@ -285,8 +285,8 @@ static void * _networkReceiveThread( void * pArgument ) IotMutex_Unlock( &( pConnectionInfo->mutex ) ); } - AwsIotLogDebug( "Network receive thread for socket %d terminating.", - pConnectionInfo->tcpSocket ); + IotLogDebug( "Network receive thread for socket %d terminating.", + pConnectionInfo->tcpSocket ); /* Clear data about the callback. */ pConnectionInfo->receiveThreadStatus = _TERMINATED; @@ -318,36 +318,36 @@ static inline int _dnsLookup( const char * const pHostName, struct sockaddr_in * pServer = NULL; /* Perform a DNS lookup of pHostName. */ - AwsIotLogDebug( "Performing DNS lookup of %s", pHostName ); + IotLogDebug( "Performing DNS lookup of %s", pHostName ); status = getaddrinfo( pHostName, NULL, NULL, &pListHead ); if( status != 0 ) { - AwsIotLogError( "DNS lookup failed. %s.", gai_strerror( status ) ); + IotLogError( "DNS lookup failed. %s.", gai_strerror( status ) ); return -1; } - AwsIotLogDebug( "Successfully received DNS records." ); + IotLogDebug( "Successfully received DNS records." ); /* Go through the list of DNS records until a successful connection is made. */ for( pAddressInfo = pListHead; pAddressInfo != NULL; pAddressInfo = pAddressInfo->ai_next ) { /* Open a socket using the information in the DNS record. */ - AwsIotLogDebug( "Creating socket for domain: %d, type: %d, protocol: %d.", - pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); + IotLogDebug( "Creating socket for domain: %d, type: %d, protocol: %d.", + pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); tcpSocket = socket( pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); /* Check if the socket was successfully opened. */ if( tcpSocket == -1 ) { - AwsIotLogDebug( "Could not open socket for record at %p. Trying next.", - pAddressInfo ); + IotLogDebug( "Could not open socket for record at %p. Trying next.", + pAddressInfo ); continue; } /* Set the port for the connection. */ - AwsIotLogDebug( "Socket creation successful. Attempting connection." ); + IotLogDebug( "Socket creation successful. Attempting connection." ); pServer = ( struct sockaddr_in * ) ( pAddressInfo->ai_addr ); pServer->sin_port = netPort; @@ -359,17 +359,17 @@ static inline int _dnsLookup( const char * const pHostName, /* Connect failed; close socket and try next record. */ if( close( tcpSocket ) != 0 ) { - AwsIotLogWarn( "Failed to close socket %d. errno=%d.", - tcpSocket, - errno ); + IotLogWarn( "Failed to close socket %d. errno=%d.", + tcpSocket, + errno ); } - AwsIotLogDebug( "Socket connection failed. Trying next." ); + IotLogDebug( "Socket connection failed. Trying next." ); } else { /* Connection successful; stop searching the list. */ - AwsIotLogDebug( "Socket connection successful." ); + IotLogDebug( "Socket connection successful." ); break; } } @@ -380,7 +380,7 @@ static inline int _dnsLookup( const char * const pHostName, * of them provided a successful connection. */ if( pAddressInfo == NULL ) { - AwsIotLogError( "Failed to connect to all retrieved DNS records." ); + IotLogError( "Failed to connect to all retrieved DNS records." ); return -1; } @@ -409,12 +409,12 @@ static inline bool _readCredentials( SSL_CTX * pSSLContext, /* OpenSSL does not provide a single function for reading and loading certificates * from files into stores, so the file API must be called. */ - AwsIotLogDebug( "Opening root certificate %s", pRootCAPath ); + IotLogDebug( "Opening root certificate %s", pRootCAPath ); FILE * pRootCAFile = fopen( pRootCAPath, "r" ); if( pRootCAFile == NULL ) { - AwsIotLogError( "Failed to open %s", pRootCAPath ); + IotLogError( "Failed to open %s", pRootCAPath ); return false; } @@ -424,12 +424,12 @@ static inline bool _readCredentials( SSL_CTX * pSSLContext, if( fclose( pRootCAFile ) != 0 ) { - AwsIotLogWarn( "Failed to close file %s", pRootCAPath ); + IotLogWarn( "Failed to close file %s", pRootCAPath ); } if( pRootCA == NULL ) { - AwsIotLogError( "Failed to parse root CA." ); + IotLogError( "Failed to parse root CA." ); return false; } @@ -438,40 +438,40 @@ static inline bool _readCredentials( SSL_CTX * pSSLContext, if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSSLContext ), pRootCA ) != 1 ) { - AwsIotLogError( "Failed to add root CA to certificate store." ); + IotLogError( "Failed to add root CA to certificate store." ); return false; } /* Free the root CA object. */ X509_free( pRootCA ); - AwsIotLogInfo( "Successfully imported root CA." ); + IotLogInfo( "Successfully imported root CA." ); /* Import the client certificate. */ if( SSL_CTX_use_certificate_file( pSSLContext, pClientCertPath, SSL_FILETYPE_PEM ) != 1 ) { - AwsIotLogError( "Failed to import client certificate at %s", - pClientCertPath ); + IotLogError( "Failed to import client certificate at %s", + pClientCertPath ); return false; } - AwsIotLogInfo( "Successfully imported client certificate." ); + IotLogInfo( "Successfully imported client certificate." ); /* Import the client certificate private key. */ if( SSL_CTX_use_PrivateKey_file( pSSLContext, pCertPrivateKeyPath, SSL_FILETYPE_PEM ) != 1 ) { - AwsIotLogError( "Failed to import client certificate private key at %s", - pCertPrivateKeyPath ); + IotLogError( "Failed to import client certificate private key at %s", + pCertPrivateKeyPath ); return false; } - AwsIotLogInfo( "Successfully imported client certificate private key." ); + IotLogInfo( "Successfully imported client certificate private key." ); return true; } @@ -502,7 +502,7 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect ( pTlsInfo->pClientCert == NULL ) || ( pTlsInfo->pPrivateKey == NULL ) ) { - AwsIotLogError( "Bad parameter in TLS setup parameters." ); + IotLogError( "Bad parameter in TLS setup parameters." ); return AWS_IOT_NETWORK_BAD_PARAMETER; } @@ -516,14 +516,14 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect if( pSSLContext == NULL ) { - AwsIotLogError( "Failed to create new SSL context." ); + IotLogError( "Failed to create new SSL context." ); return AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The * mask returned by SSL_CTX_set_mode does not need to be checked. */ - AwsIotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); + IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); ( void ) SSL_CTX_set_mode( pSSLContext, SSL_MODE_AUTO_RETRY ); /* Import all credentials. */ @@ -544,33 +544,33 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect if( pConnectionInfo->pSSLConnectionContext == NULL ) { - AwsIotLogError( "Failed to create new SSL connection context." ); + IotLogError( "Failed to create new SSL connection context." ); return AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } /* Enable SSL peer verification. */ - AwsIotLogDebug( "Setting SSL_VERIFY_PEER." ); + IotLogDebug( "Setting SSL_VERIFY_PEER." ); SSL_set_verify( pConnectionInfo->pSSLConnectionContext, SSL_VERIFY_PEER, NULL ); /* Set the socket for the SSL connection. */ if( SSL_set_fd( pConnectionInfo->pSSLConnectionContext, pConnectionInfo->tcpSocket ) != 1 ) { - AwsIotLogError( "Failed to set SSL socket %d.", pConnectionInfo->tcpSocket ); + IotLogError( "Failed to set SSL socket %d.", pConnectionInfo->tcpSocket ); status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } /* Set up ALPN. */ if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->pAlpnProtos != NULL ) ) { - AwsIotLogDebug( "Setting ALPN protos." ); + IotLogDebug( "Setting ALPN protos." ); if( ( SSL_set_alpn_protos( pConnectionInfo->pSSLConnectionContext, ( const unsigned char * ) pTlsInfo->pAlpnProtos, ( unsigned int ) strlen( pTlsInfo->pAlpnProtos ) ) != 0 ) ) { - AwsIotLogError( "Failed to set ALPN protos." ); + IotLogError( "Failed to set ALPN protos." ); status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } } @@ -578,15 +578,15 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect /* Set TLS MFLN. */ if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->maxFragmentLength > 0 ) ) { - AwsIotLogDebug( "Setting max send fragment length %lu.", - ( unsigned long ) pTlsInfo->maxFragmentLength ); + IotLogDebug( "Setting max send fragment length %lu.", + ( unsigned long ) pTlsInfo->maxFragmentLength ); /* Set the maximum send fragment length. */ if( SSL_set_max_send_fragment( pConnectionInfo->pSSLConnectionContext, ( long ) pTlsInfo->maxFragmentLength ) != 1 ) { - AwsIotLogError( "Failed to set max send fragment length %lu.", - ( unsigned long ) pTlsInfo->maxFragmentLength ); + IotLogError( "Failed to set max send fragment length %lu.", + ( unsigned long ) pTlsInfo->maxFragmentLength ); status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } else @@ -606,12 +606,12 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect /* Set server name for SNI. */ if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->disableSni == false ) ) { - AwsIotLogDebug( "Setting server name %s for SNI.", pServerName ); + IotLogDebug( "Setting server name %s for SNI.", pServerName ); if( SSL_set_tlsext_host_name( pConnectionInfo->pSSLConnectionContext, pServerName ) != 1 ) { - AwsIotLogError( "Failed to set server name %s for SNI.", pServerName ); + IotLogError( "Failed to set server name %s for SNI.", pServerName ); status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } } @@ -621,12 +621,12 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect { if( SSL_connect( pConnectionInfo->pSSLConnectionContext ) != 1 ) { - AwsIotLogError( "TLS handshake failed." ); + IotLogError( "TLS handshake failed." ); status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } else { - AwsIotLogInfo( "TLS handshake succeeded." ); + IotLogInfo( "TLS handshake succeeded." ); } } @@ -635,12 +635,12 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect { if( SSL_get_verify_result( pConnectionInfo->pSSLConnectionContext ) != X509_V_OK ) { - AwsIotLogError( "Peer certificate verification failed." ); + IotLogError( "Peer certificate verification failed." ); status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; } else { - AwsIotLogInfo( "Peer certificate verified. TLS connection established." ); + IotLogInfo( "Peer certificate verified. TLS connection established." ); } } @@ -664,27 +664,27 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect static inline void _tlsCleanup( _connectionInfo_t * const pConnectionInfo ) { /* Shut down the TLS connection. */ - AwsIotLogInfo( "Shutting down TLS connection." ); + IotLogInfo( "Shutting down TLS connection." ); /* SSL shutdown should be called twice: once to send "close notify" and once * more to receive the peer's "close notify". */ if( SSL_shutdown( pConnectionInfo->pSSLConnectionContext ) == 0 ) { - AwsIotLogDebug( "\"Close notify\" sent. Awaiting peer response." ); + IotLogDebug( "\"Close notify\" sent. Awaiting peer response." ); /* The previous call to SSL_shutdown marks the SSL connection as closed. * SSL_shutdown must be called again to read the peer response. */ if( SSL_shutdown( pConnectionInfo->pSSLConnectionContext ) != 1 ) { - AwsIotLogWarn( "No response from peer." ); + IotLogWarn( "No response from peer." ); } else { - AwsIotLogDebug( "Peer response to \"close notify\" received." ); + IotLogDebug( "Peer response to \"close notify\" received." ); } } - AwsIotLogInfo( "TLS connection terminated." ); + IotLogInfo( "TLS connection terminated." ); /* Free the memory used by the TLS connection. */ SSL_free( pConnectionInfo->pSSLConnectionContext ); @@ -706,7 +706,7 @@ AwsIotNetworkError_t AwsIotNetwork_Init( void ) if( sigaction( SIGPIPE, &sigpipeAction, NULL ) != 0 ) { - AwsIotLogError( "Failed to set SIGPIPE handler. errno=%d.", errno ); + IotLogError( "Failed to set SIGPIPE handler. errno=%d.", errno ); return AWS_IOT_NETWORK_INIT_FAILED; } @@ -716,7 +716,7 @@ AwsIotNetworkError_t AwsIotNetwork_Init( void ) * with v1.0.2. */ ( void ) SSL_library_init(); - AwsIotLogInfo( "Network library initialized." ); + IotLogInfo( "Network library initialized." ); return AWS_IOT_NETWORK_SUCCESS; } @@ -733,7 +733,7 @@ void AwsIotNetwork_Cleanup( void ) EVP_cleanup(); SSL_COMP_free_compression_methods(); - AwsIotLogInfo( "Network library cleanup done." ); + IotLogInfo( "Network library cleanup done." ); } /*-----------------------------------------------------------*/ @@ -750,18 +750,18 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * /* Check parameters. */ if( ( pNetworkConnection == NULL ) || ( pHostName == NULL ) || ( port == 0 ) ) { - AwsIotLogError( "Bad parameter to AwsIotNetwork_TcpConnect." ); + IotLogError( "Bad parameter to AwsIotNetwork_TcpConnect." ); return AWS_IOT_NETWORK_BAD_PARAMETER; } /* Allocate memory for the connection context. This will hold information * about the connection. */ - pConnectionInfo = ( _connectionInfo_t * ) AwsIotNetwork_Malloc( sizeof( _connectionInfo_t ) ); + pConnectionInfo = ( _connectionInfo_t * ) IotNetwork_Malloc( sizeof( _connectionInfo_t ) ); if( pConnectionInfo == NULL ) { - AwsIotLogError( "Failed to allocate memory for connection information." ); + IotLogError( "Failed to allocate memory for connection information." ); return AWS_IOT_NETWORK_NO_MEMORY; } @@ -769,8 +769,8 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * /* Create the network connection mutex. */ if( IotMutex_Create( &( pConnectionInfo->mutex ) ) == false ) { - AwsIotLogError( "Failed to create connection mutex." ); - AwsIotNetwork_Free( pConnectionInfo ); + IotLogError( "Failed to create connection mutex." ); + IotNetwork_Free( pConnectionInfo ); return AWS_IOT_NETWORK_NO_MEMORY; } @@ -796,11 +796,11 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * /* Set the output parameter. */ *pNetworkConnection = pConnectionInfo; - AwsIotLogInfo( "TCP connection successful." ); + IotLogInfo( "TCP connection successful." ); if( pTlsInfo != NULL ) { - AwsIotLogInfo( "Setting up TLS." ); + IotLogInfo( "Setting up TLS." ); status = _tlsSetup( pConnectionInfo, pHostName, pTlsInfo ); } @@ -830,7 +830,7 @@ void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection /* Check parameters. */ if( pConnectionInfo == NULL ) { - AwsIotLogError( "Bad parameter to AwsIotNetwork_CloseConnection." ); + IotLogError( "Bad parameter to AwsIotNetwork_CloseConnection." ); return; } @@ -848,10 +848,10 @@ void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection if( ( posixError != 0 ) && ( posixError != ESRCH ) ) { - AwsIotLogWarn( "Failed to send cancellation request to socket %d receive " - "thread during TLS cleanup. errno=%d.", - pConnectionInfo->tcpSocket, - posixError ); + IotLogWarn( "Failed to send cancellation request to socket %d receive " + "thread during TLS cleanup. errno=%d.", + pConnectionInfo->tcpSocket, + posixError ); } } @@ -860,9 +860,9 @@ void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection if( posixError != 0 ) { - AwsIotLogWarn( "Failed to join network receive thread for socket %d. errno=%d", - pConnectionInfo->tcpSocket, - posixError ); + IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d", + pConnectionInfo->tcpSocket, + posixError ); } /* Clear data about the receive thread. */ @@ -879,14 +879,14 @@ void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection /* Close the connection. */ if( close( pConnectionInfo->tcpSocket ) != 0 ) { - AwsIotLogWarn( "Could not close socket %d. errno=%d.", - pConnectionInfo->tcpSocket, - errno ); + IotLogWarn( "Could not close socket %d. errno=%d.", + pConnectionInfo->tcpSocket, + errno ); } else { - AwsIotLogInfo( "Connection (socket %d) closed.", - pConnectionInfo->tcpSocket ); + IotLogInfo( "Connection (socket %d) closed.", + pConnectionInfo->tcpSocket ); } /* Unlock the connection mutex. */ @@ -901,7 +901,7 @@ void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnectio if( pConnectionInfo == NULL ) { - AwsIotLogError( "Bad parameter to AwsIotNetwork_DestroyConnection." ); + IotLogError( "Bad parameter to AwsIotNetwork_DestroyConnection." ); return; } @@ -910,7 +910,7 @@ void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnectio IotMutex_Destroy( &( pConnectionInfo->mutex ) ); /* Free memory in use by the connection. */ - AwsIotNetwork_Free( pConnectionInfo ); + IotNetwork_Free( pConnectionInfo ); } /*-----------------------------------------------------------*/ @@ -933,9 +933,9 @@ AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnecti if( posixError != 0 ) { - AwsIotLogWarn( "Failed to join socket %d network receive thread. errno=%d.", - pConnectionInfo->tcpSocket, - posixError ); + IotLogWarn( "Failed to join socket %d network receive thread. errno=%d.", + pConnectionInfo->tcpSocket, + posixError ); status = AWS_IOT_NETWORK_SYSTEM_ERROR; } @@ -959,9 +959,9 @@ AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnecti if( posixError != 0 ) { - AwsIotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pConnectionInfo->tcpSocket, - posixError ); + IotLogError( "Failed to create socket %d network receive thread. errno=%d.", + pConnectionInfo->tcpSocket, + posixError ); status = AWS_IOT_NETWORK_SYSTEM_ERROR; } else @@ -993,14 +993,14 @@ size_t AwsIotNetwork_Send( void * networkConnection, /* Check parameters. */ if( ( pConnectionInfo == NULL ) || ( pMessage == NULL ) || ( messageLength == 0 ) ) { - AwsIotLogError( "Bad parameter to AwsIotNetwork_Send." ); + IotLogError( "Bad parameter to AwsIotNetwork_Send." ); return 0; } /* Send message. */ - AwsIotLogDebug( "Sending %lu bytes over network.", - ( unsigned long ) messageLength ); + IotLogDebug( "Sending %lu bytes over network.", + ( unsigned long ) messageLength ); /* Lock the connection mutex to prevent the connection from being closed * while sending. */ @@ -1035,21 +1035,21 @@ size_t AwsIotNetwork_Send( void * networkConnection, /* Check for errors. */ if( bytesSent <= 0 ) { - AwsIotLogError( "Send failure." ); + IotLogError( "Send failure." ); return 0; } if( ( size_t ) bytesSent != messageLength ) { - AwsIotLogWarn( "Failed to send %lu bytes, %d bytes sent instead.", - ( unsigned long ) messageLength, - bytesSent ); + IotLogWarn( "Failed to send %lu bytes, %d bytes sent instead.", + ( unsigned long ) messageLength, + bytesSent ); } else { - AwsIotLogDebug( "Successfully sent %lu bytes.", - ( unsigned long ) messageLength ); + IotLogDebug( "Successfully sent %lu bytes.", + ( unsigned long ) messageLength ); } return ( size_t ) bytesSent; diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index d124cc7c9d..e3fb0de410 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -29,7 +29,7 @@ echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem # Build tests and demos against AWS IoT with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run MQTT tests and demo against AWS IoT. @@ -42,7 +42,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run MQTT and Shadow tests in static memory mode. diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 6dba994620..ac5c9b6390 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -14,7 +14,7 @@ cd build rm -rf * # Build tests and demos against AWS IoT with gcov. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=AWS_IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" --coverage" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" --coverage" make # Run MQTT tests and demo against AWS IoT with code coverage. diff --git a/scripts/generate_doc.sh b/scripts/generate_doc.sh index 9cd5f13e53..b1a7aff38a 100644 --- a/scripts/generate_doc.sh +++ b/scripts/generate_doc.sh @@ -5,7 +5,7 @@ command -v doxygen > /dev/null || { echo "Doxygen not found. Exiting."; exit 1; if [ $# -ne 1 ]; then echo "Usage: ./generate_doc.sh sdk_root_directory" - exit 1; + exit 1 fi # Change to SDK root directory. @@ -57,5 +57,5 @@ echo "Documentation written to doc/output" # Print any doxygen errors or warnings and exit with a nonzero value. if [ -s doxygen_warnings.txt ]; then cat doxygen_warnings.txt - exit 1; + exit 1 fi diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 1ecef38825..c2a62c3d50 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -75,13 +75,13 @@ /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define AwsIotNetwork_Malloc unity_malloc_mt -#define AwsIotNetwork_Free unity_free_mt +#define IotNetwork_Malloc unity_malloc_mt +#define IotNetwork_Free unity_free_mt #define IotThreads_Malloc unity_malloc_mt #define IotThreads_Free unity_free_mt -#define AwsIotLogging_Malloc unity_malloc_mt -#define AwsIotLogging_Free unity_free_mt -/* #define AwsIotLogging_StaticBufferSize */ +#define IotLogging_Malloc unity_malloc_mt +#define IotLogging_Free unity_free_mt +/* #define IotLogging_StaticBufferSize */ #define AwsIotTest_Malloc unity_malloc_mt #define AwsIotTest_Free unity_free_mt diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c index fe4821e742..9cd8b73c7c 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c @@ -59,9 +59,9 @@ /* The tests in this file run for a long time, so set up logging to track their * progress. */ -#define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_INFO +#define _LIBRARY_LOG_LEVEL IOT_LOG_INFO #define _LIBRARY_LOG_NAME ( "MQTT_STRESS" ) -#include "aws_iot_logging_setup.h" +#include "iot_logging_setup.h" /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). @@ -300,9 +300,9 @@ static void _publishReceived( void * pArgument, } else { - AwsIotLogWarn( "Received an unknown message on subscription %.*s: %.*s", - pPublish->message.info.topicNameLength, pPublish->message.info.pTopicName, - pPublish->message.info.payloadLength, pPublish->message.info.pPayload ); + IotLogWarn( "Received an unknown message on subscription %.*s: %.*s", + pPublish->message.info.topicNameLength, pPublish->message.info.pTopicName, + pPublish->message.info.payloadLength, pPublish->message.info.pPayload ); } } @@ -319,7 +319,7 @@ static void _blockingCallback( void * pArgument, ( void ) param; - AwsIotLogInfo( "Callback blocking for %u seconds.", blockTime ); + IotLogInfo( "Callback blocking for %u seconds.", blockTime ); sleep( blockTime ); IotSemaphore_Post( pWaitSem ); } @@ -367,19 +367,19 @@ static void * _publishThread( void * pArgument ) * If no memory is available, wait some time for resources to be released. */ if( status == AWS_IOT_MQTT_NO_MEMORY ) { - AwsIotLogInfo( "Thread %d: no memory available on PUBLISH %d." - " Sleeping for %d seconds.", - pParams->threadNumber, - i, - AWS_IOT_TEST_MQTT_DECONGEST_S ); + IotLogInfo( "Thread %d: no memory available on PUBLISH %d." + " Sleeping for %d seconds.", + pParams->threadNumber, + i, + AWS_IOT_TEST_MQTT_DECONGEST_S ); sleep( AWS_IOT_TEST_MQTT_DECONGEST_S ); continue; } /* If the PUBLISH failed, exit this thread. */ else if( status != AWS_IOT_MQTT_STATUS_PENDING ) { - AwsIotLogError( "Thread %d encountered error %d publishing message %d.", - status, i ); + IotLogError( "Thread %d encountered error %d publishing message %d.", + status, i ); break; } @@ -391,14 +391,14 @@ static void * _publishThread( void * pArgument ) if( ( i % 25 == 0 ) || ( i == pParams->publishLimit ) ) { - AwsIotLogInfo( "Thread %d has published %d of %d messages.", - pParams->threadNumber, i, pParams->publishLimit ); + IotLogInfo( "Thread %d has published %d of %d messages.", + pParams->threadNumber, i, pParams->publishLimit ); } /* Sleep until the next PUBLISH should be sent. */ if( nanosleep( &sleepTime, NULL ) != 0 ) { - AwsIotLogError( "Error in nanosleep." ); + IotLogError( "Error in nanosleep." ); status = AWS_IOT_MQTT_BAD_RESPONSE; break; } @@ -427,13 +427,13 @@ TEST_SETUP( MQTT_Stress ) int i = 0; AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - const AwsIotLogConfig_t logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; + const IotLogConfig_t logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; /* Clear the PINGREQ override flag. */ _pingreqOverrideCalled = false; /* Empty log message to log a new line. */ - AwsIotLog( AWS_IOT_LOG_INFO, &logHideAll, " " ); + IotLog( IOT_LOG_INFO, &logHideAll, " " ); /* Create the publish counter semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &receivedPublishCounter, @@ -549,11 +549,11 @@ TEST( MQTT_Stress, KeepAlive ) /* Send no MQTT packets for a long time. The keep-alive must be used to keep * the connection open. */ - AwsIotLogInfo( "KeepAlive test sleeping for %u seconds.", sleepTime ); + IotLogInfo( "KeepAlive test sleeping for %u seconds.", sleepTime ); sleep( sleepTime ); /* Send a PUBLISH to verify that the connection is still usable. */ - AwsIotLogInfo( "KeepAlive test checking MQTT connection." ); + IotLogInfo( "KeepAlive test checking MQTT connection." ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); /* Check that the PINGREQ override was used. */ @@ -598,7 +598,7 @@ TEST( MQTT_Stress, BlockingCallback ) /* Wait for the callback to return, then check that the connection is * still usable. */ IotSemaphore_Wait( &waitSem ); - AwsIotLogInfo( "BlockingCallback test checking MQTT connection." ); + IotLogInfo( "BlockingCallback test checking MQTT connection." ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); } @@ -628,7 +628,7 @@ TEST( MQTT_Stress, ClientClosesConnection ) publishThreadParams[ i ].publishLimit = AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD; } - AwsIotLogInfo( "ClientClosesConnection test creating threads." ); + IotLogInfo( "ClientClosesConnection test creating threads." ); /* Spawn the threads for the test. */ for( i = 0; i < AWS_IOT_TEST_MQTT_THREADS; i++ ) diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 0ba72f361f..ed68c5ae17 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -36,7 +36,7 @@ #include "private/aws_iot_shadow_internal.h" /* JSON utilities include. */ -#include "aws_iot_json_utils.h" +#include "iot_json_utils.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -155,12 +155,12 @@ static void _operationComplete( void * pArgument, { AwsIotShadow_Assert( pOperation->operation.get.documentLength > 0 ); - AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pOperation->operation.get.pDocument, - pOperation->operation.get.documentLength, - "key", - 3, - &pJsonValue, - &jsonValueLength ) == true ); + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pOperation->operation.get.pDocument, + pOperation->operation.get.documentLength, + "key", + 3, + &pJsonValue, + &jsonValueLength ) == true ); AwsIotShadow_Assert( jsonValueLength == 7 ); AwsIotShadow_Assert( strncmp( pJsonValue, "\"value\"", 7 ) == 0 ); } @@ -186,22 +186,22 @@ static void _deltaCallback( void * pArgument, AwsIotShadow_Assert( pCallback->callbackType == AWS_IOT_SHADOW_DELTA_CALLBACK ); /* Check delta document state. */ - AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, - "key", - 3, - &pValue, - &valueLength ) == true ); + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "key", + 3, + &pValue, + &valueLength ) == true ); AwsIotShadow_Assert( valueLength == 4 ); AwsIotShadow_Assert( strncmp( pValue, "true", valueLength ) == 0 ); /* Check delta document client token. */ - AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, - "clientToken", - 11, - &pClientToken, - &clientTokenLength ) ); + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "clientToken", + 11, + &pClientToken, + &clientTokenLength ) ); AwsIotShadow_Assert( clientTokenLength == 12 ); AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); @@ -223,36 +223,36 @@ static void _updatedCallback( void * pArgument, size_t previousStateLength = 0, currentStateLength = 0, clientTokenLength = 0; /* Check updated document previous state. */ - AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, - "previous", - 8, - &pPrevious, - &previousStateLength ) == true ); + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "previous", + 8, + &pPrevious, + &previousStateLength ) == true ); AwsIotShadow_Assert( previousStateLength > 0 ); AwsIotShadow_Assert( strncmp( "{\"state\":{},\"metadata\":{},", pPrevious, 26 ) == 0 ); /* Check updated document current state. */ - AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, - "current", - 7, - &pCurrent, - ¤tStateLength ) == true ); + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "current", + 7, + &pCurrent, + ¤tStateLength ) == true ); AwsIotShadow_Assert( currentStateLength > 0 ); AwsIotShadow_Assert( strncmp( "{\"state\":{\"desired\":{\"key\":true}}", pCurrent, 33 ) == 0 ); /* Check updated document client token. */ - AwsIotShadow_Assert( AwsIotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, - "clientToken", - 11, - &pClientToken, - &clientTokenLength ) ); + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, + pCallback->callback.documentLength, + "clientToken", + 11, + &pClientToken, + &clientTokenLength ) ); AwsIotShadow_Assert( clientTokenLength == 12 ); AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); @@ -387,12 +387,12 @@ static void _updateGetDeleteBlocking( int QoS ) /* Check the retrieved Shadow document. */ TEST_ASSERT_GREATER_THAN( 0, shadowDocumentLength ); TEST_ASSERT_NOT_NULL( pShadowDocument ); - TEST_ASSERT_EQUAL_INT( true, AwsIotJsonUtils_FindJsonValue( pShadowDocument, - shadowDocumentLength, - "key", - 3, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( pShadowDocument, + shadowDocumentLength, + "key", + 3, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 7, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"value\"", pJsonValue, jsonValueLength ); diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index af6fbcbb6c..eb4a1fb5ff 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -50,7 +50,7 @@ extern int vsnprintf( char *, #include "private/aws_iot_shadow_internal.h" /* JSON utilities include. */ -#include "aws_iot_json_utils.h" +#include "iot_json_utils.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -78,12 +78,12 @@ static void _parseJson( bool expectedResult, size_t jsonValueLength = 0; TEST_ASSERT_EQUAL_INT( expectedResult, - AwsIotJsonUtils_FindJsonValue( pJsonDocument, - jsonDocumentLength, - pJsonKey, - strlen( pJsonKey ), - &pJsonValue, - &jsonValueLength ) ); + IotJsonUtils_FindJsonValue( pJsonDocument, + jsonDocumentLength, + pJsonKey, + strlen( pJsonKey ), + &pJsonValue, + &jsonValueLength ) ); if( expectedResult == true ) { From ce1e896ea450972b1bfdc8dad2d5f430f557b06f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 5 Feb 2019 17:18:42 -0800 Subject: [PATCH 017/844] Add priority and stack size to thread creation. (#267) --- lib/include/platform/iot_threads.h | 27 ++++- lib/source/mqtt/aws_iot_mqtt_operation.c | 4 +- platform/source/posix/iot_threads_posix.c | 125 ++++++++++++++++++++-- 3 files changed, 145 insertions(+), 11 deletions(-) diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index ca7d042fec..e0c16f125b 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -39,6 +39,16 @@ /* Platform layer types include. */ #include "types/iot_platform_types.h" +/** + * @brief A value representing the system default for new thread priority. + */ +#define IOT_THREAD_DEFAULT_PRIORITY 0 + +/** + * @brief A value representhing the system default for new thread stack size. + */ +#define IOT_THREAD_DEFAULT_STACK_SIZE 0 + /** * @functionspage{platform_threads,platform thread management,Thread Management} * - @functionname{platform_threads_function_createdetachedthread} @@ -81,6 +91,12 @@ * * @param[in] threadRoutine The function this thread should run. * @param[in] pArgument The argument passed to `threadRoutine`. + * @param[in] priority Represents the priority of the new thread, as defined by + * the system. The value #IOT_THREAD_DEFAULT_PRIORITY (i.e. `0`) must be used to + * represent the system default for thread priority. + * @param[in] stackSize Represents the stack size of the new thread, as defined + * by the system. The value #IOT_THREAD_DEFAULT_STACK_SIZE (i.e. `0`) must be used + * to represent the system default for stack size. * * @return `true` if the new thread was successfully created; `false` otherwise. * @@ -88,8 +104,11 @@ * // Thread routine. * void threadRoutine( void * pArgument ); * - * // Run threadRoutine in a detached thread. - * if( Iot_CreateDetachedThread( threadRoutine, NULL ) == true ) + * // Run threadRoutine in a detached thread, using default priority and stack size. + * if( Iot_CreateDetachedThread( threadRoutine, + * NULL, + * IOT_THREAD_DEFAULT_PRIORITY, + * IOT_THREAD_DEFAULT_STACK_SIZE ) == true ) * { * // Success * } @@ -101,7 +120,9 @@ */ /* @[declare_platform_threads_createdetachedthread] */ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument ); + void * pArgument, + int32_t priority, + size_t stackSize ); /* @[declare_platform_threads_createdetachedthread] */ /** diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index b92e13a223..b2fa69c259 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -630,7 +630,9 @@ AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const { /* Create new thread. */ if( Iot_CreateDetachedThread( _processOperation, - pQueue ) == false ) + pQueue, + IOT_THREAD_DEFAULT_PRIORITY, + IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) { /* New thread could not be created. Remove enqueued operation and * report error. */ diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 39ab0cdeab..0e9c40a7b2 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -137,23 +137,75 @@ static void * _threadRoutineWrapper( void * pArgument ) /*-----------------------------------------------------------*/ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument ) + void * pArgument, + int32_t priority, + size_t stackSize ) { bool status = true; int posixErrno = 0; + _threadInfo_t * pThreadInfo = NULL; pthread_t newThread; pthread_attr_t threadAttributes; IotLogDebug( "Creating new thread." ); - /* Allocate memory for the new thread. */ - _threadInfo_t * pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); + /* Check priority if a non-default scheduling priority is given. */ + if( priority != IOT_THREAD_DEFAULT_PRIORITY ) + { + if( priority < 0 ) + { + IotLogError( "Priority %ld is not valid for a new thread.", + ( long int ) priority ); + status = false; + } + else if( ( int ) priority < sched_get_priority_min( SCHED_RR ) ) + { + IotLogError( "Priority %ld is less than minimum allowed %d.", + sched_get_priority_min( SCHED_RR ) ); + status = false; + } + else if( ( int ) priority > sched_get_priority_max( SCHED_RR ) ) + { + IotLogError( "Priority %ld is greater than maximum allowed %d.", + sched_get_priority_max( SCHED_RR ) ); + status = false; + } + else + { + IotLogInfo( "New thread will have priority %ld with SCHED_RR policy.", + ( long int ) priority ); + } + } - if( pThreadInfo == NULL ) + /* Check stack size if a non-default value is given. */ + if( status == true ) { - IotLogError( "Failed to allocate memory for new thread." ); + /* Adjust stack size based on system minimum. */ + if( stackSize != IOT_THREAD_DEFAULT_STACK_SIZE ) + { + if( stackSize < PTHREAD_STACK_MIN ) + { + IotLogWarn( "Stack size of %lu is smaller than system minimum %d." + " System minimum will be used instead.", + ( unsigned long ) stackSize, + PTHREAD_STACK_MIN ); - status = false; + stackSize = PTHREAD_STACK_MIN; + } + } + } + + if( status == true ) + { + /* Allocate memory for the new thread. */ + pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); + + if( pThreadInfo == NULL ) + { + IotLogError( "Failed to allocate memory for new thread." ); + + status = false; + } } /* Set up thread attributes object. */ @@ -179,10 +231,69 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, { IotLogError( "Failed to set detached thread attribute. errno=%d.", posixErrno ); - ( void ) pthread_attr_destroy( &threadAttributes ); status = false; } + + /* Set the stack size if given. */ + if( status == true ) + { + if( stackSize != IOT_THREAD_DEFAULT_STACK_SIZE ) + { + posixErrno = pthread_attr_setstacksize( &threadAttributes, + stackSize ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to set stack size %lu.", ( unsigned long ) stackSize ); + + status = false; + } + } + } + + /* Set scheduler policy and priority if given. */ + if( status == true ) + { + if( priority != IOT_THREAD_DEFAULT_PRIORITY ) + { + struct sched_param schedulerParam = + { + .sched_priority = ( int ) priority + }; + + /* Set scheduler policy SCHED_RR. */ + posixErrno = pthread_attr_setschedpolicy( &threadAttributes, + SCHED_RR ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to set scheduling policy SCHED_RR." ); + + status = false; + } + + /* Set scheduler priority. */ + if( status == true ) + { + posixErrno = pthread_attr_setschedparam( &threadAttributes, + &schedulerParam ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to set scheduling priority %d.", + schedulerParam.sched_priority ); + + status = false; + } + } + } + } + + if( status == false ) + { + ( void ) pthread_attr_destroy( &threadAttributes ); + } } } From d823168082f146a093f43fab273bd73b74618f04 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 6 Feb 2019 08:55:15 -0800 Subject: [PATCH 018/844] Remove separate persistent session MQTT Connect. (#268) --- demos/aws_iot_demo_mqtt.c | 2 +- demos/aws_iot_demo_shadow.c | 1 - lib/include/aws_iot_mqtt.h | 268 ++++++----- lib/include/private/aws_iot_mqtt_internal.h | 2 - lib/source/mqtt/aws_iot_mqtt_api.c | 422 ++++++++---------- lib/source/mqtt/aws_iot_mqtt_serialize.c | 27 +- lib/source/mqtt/aws_iot_mqtt_validate.c | 12 + tests/mqtt/system/aws_iot_tests_mqtt_stress.c | 1 - tests/mqtt/system/aws_iot_tests_mqtt_system.c | 22 +- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 57 +-- .../system/aws_iot_tests_shadow_system.c | 1 - 11 files changed, 368 insertions(+), 447 deletions(-) diff --git a/demos/aws_iot_demo_mqtt.c b/demos/aws_iot_demo_mqtt.c index bb8dc29ab6..da6dbd7f54 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/aws_iot_demo_mqtt.c @@ -384,6 +384,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, connectInfo.awsIotMqttMode = awsIotMqttMode; connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + connectInfo.pWillInfo = &willInfo; /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects @@ -437,7 +438,6 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, mqttStatus = AwsIotMqtt_Connect( pMqttConnection, pNetworkInterface, &connectInfo, - &willInfo, _MQTT_TIMEOUT_MS ); if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 34844f36eb..3faa4e4f3a 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -110,7 +110,6 @@ int AwsIotDemo_RunShadowDemo( const char * const pThingName, mqttStatus = AwsIotMqtt_Connect( pMqttConnection, pNetworkInterface, &connectInfo, - NULL, _TIMEOUT_MS ); if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h index 7f97cea252..1458f9dcdd 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/aws_iot_mqtt.h @@ -260,61 +260,6 @@ typedef enum AwsIotMqttOperationType * @paramstructs{mqtt,MQTT} */ -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a new MQTT connection. - * - * @paramfor @ref mqtt_function_connect - * - * Passed as an argument to @ref mqtt_function_connect. The members of this struct - * (except for `awsIotMqttMode`) correspond to the content of an [MQTT CONNECT packet.] - * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) - * - * @initializer{AwsIotMqttConnectInfo_t,AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER} - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - */ -typedef struct AwsIotMqttConnectInfo -{ - /** - * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. - * - * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] - * (https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt) - * When this member is `true`, the MQTT library will accommodate these - * differences. This setting should be `false` when communicating with a - * fully-compliant MQTT broker. - * - * @attention This setting MUST be `true` when using the AWS IoT MQTT - * server; it MUST be `false` otherwise. - * @note Currently, @ref AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER sets this - * this member to `true`. - */ - bool awsIotMqttMode; - - /** - * @brief Whether this connection is a clean session. - * - * If this parameter is `false`, the MQTT connection should be established with - * @ref mqtt_function_connect. Otherwise, if this parameter is `true`, the MQTT - * connection should be established with @ref mqtt_function_connectrestoresession. - */ - bool cleanSession; - - uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ - - const char * pClientIdentifier; /**< @brief MQTT client identifier. */ - uint16_t clientIdentifierLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pClientIdentifier. */ - - /* These credentials are not used by AWS IoT and may be ignored if - * awsIotMqttMode is true. */ - const char * pUserName; /**< @brief Username for MQTT connection. */ - uint16_t userNameLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pUserName. */ - const char * pPassword; /**< @brief Password for MQTT connection. */ - uint16_t passwordLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pPassword. */ -} AwsIotMqttConnectInfo_t; - /** * @ingroup mqtt_datatypes_paramstructs * @brief Information on a PUBLISH message. @@ -536,6 +481,123 @@ typedef struct AwsIotMqttSubscription * See #AwsIotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. */ } AwsIotMqttSubscription_t; +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a new MQTT connection. + * + * @paramfor @ref mqtt_function_connect + * + * Passed as an argument to @ref mqtt_function_connect. Most members of this struct + * correspond to the content of an [MQTT CONNECT packet.] + * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) + * + * @initializer{AwsIotMqttConnectInfo_t,AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + */ +typedef struct AwsIotMqttConnectInfo +{ + /** + * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. + * + * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] + * (https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt) + * When this member is `true`, the MQTT library will accommodate these + * differences. This setting should be `false` when communicating with a + * fully-compliant MQTT broker. + * + * @attention This setting MUST be `true` when using the AWS IoT MQTT + * server; it MUST be `false` otherwise. + * @note Currently, @ref AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER sets this + * this member to `true`. + */ + bool awsIotMqttMode; + + /** + * @brief Whether this connection is a clean session. + * + * MQTT servers can maintain and topic filter subscriptions and unacknowledged + * PUBLISH messages. These form part of an MQTT session, which is identified by + * the [client identifier](@ref AwsIotMqttConnectInfo_t.pClientIdentifier). + * + * Setting this value to `true` establishes a clean session, which causes + * the MQTT server to discard any previous session data for a client identifier. + * When the client disconnects, the server discards all session data. If this + * value is `true`, #AwsIotMqttConnectInfo_t.pPreviousSubscriptions and + * #AwsIotMqttConnectInfo_t.previousSubscriptionCount are ignored. + * + * Setting this value to `false` does one of the following: + * - If no previous session exists, the MQTT server will create a new + * persistent session. The server may maintain subscriptions and + * unacknowledged PUBLISH messages after a client disconnects, to be restored + * once the same client identifier reconnects. + * - If a previous session exists, the MQTT server restores all of the session's + * subscriptions for the client identifier and may immediately transmit any + * unacknowledged PUBLISH packets to the client. + * + * When a client with a persistent session disconnects, the MQTT server + * continues to maintain all subscriptions and unacknowledged PUBLISH messages. + * The client must also remember the session subscriptions to restore them + * upon reconnecting. #AwsIotMqttConnectInfo_t.pPreviousSubscriptions + * and #AwsIotMqttConnectInfo_t.previousSubscriptionCount are used to + * restore a previous session's subscriptions client-side. + */ + bool cleanSession; + + /** + * @brief An array of MQTT subscriptions present in a previous session, if any. + * + * Pointer to the start of an array of subscriptions present a previous session, + * if any. These subscriptions will be immediately restored upon reconnecting. + * + * This member is ignored if it is `NULL` or #AwsIotMqttConnectInfo_t.cleanSession + * is `true`. If this member is not `NULL`, #AwsIotMqttConnectInfo_t.previousSubscriptionCount + * must be nonzero. + */ + const AwsIotMqttSubscription_t * pPreviousSubscriptions; + + /** + * @brief The number of MQTT subscriptions present in a previous session, if any. + * + * Number of subscriptions contained in the array + * #AwsIotMqttConnectInfo_t.pPreviousSubscriptions. + * + * This value is ignored if #AwsIotMqttConnectInfo_t.pPreviousSubscriptions + * is `NULL` or #AwsIotMqttConnectInfo_t.cleanSession is `true`. If + * #AwsIotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value + * must be nonzero. + */ + size_t previousSubscriptionCount; + + /** + * @brief A message to publish if the new MQTT connection is unexpectedly closed. + * + * A Last Will and Testament (LWT) message may be published if this connection is + * closed without sending an MQTT DISCONNECT packet. This pointer should be set to + * an #AwsIotMqttPublishInfo_t representing any LWT message to publish. If an LWT + * is not needed, this member must be set to `NULL`. + * + * Unlike other PUBLISH messages, an LWT message is limited to 65535 bytes in + * length. Additionally, [pWillInfo->retryMs](@ref AwsIotMqttPublishInfo_t.retryMs) + * and [pWillInfo->retryLimit](@ref AwsIotMqttPublishInfo_t.retryLimit) will + * be ignored. + */ + const AwsIotMqttPublishInfo_t * pWillInfo; + + uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ + + const char * pClientIdentifier; /**< @brief MQTT client identifier. */ + uint16_t clientIdentifierLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pClientIdentifier. */ + + /* These credentials are not used by AWS IoT and may be ignored if + * awsIotMqttMode is true. */ + const char * pUserName; /**< @brief Username for MQTT connection. */ + uint16_t userNameLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pUserName. */ + const char * pPassword; /**< @brief Password for MQTT connection. */ + uint16_t passwordLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pPassword. */ +} AwsIotMqttConnectInfo_t; + /** * @ingroup mqtt_datatypes_paramstructs * @brief Provides the network functions for sending data and closing connections. @@ -606,14 +668,12 @@ typedef struct AwsIotMqttNetIf /** * @brief CONNECT packet serializer function. * @param[in] AwsIotMqttConnectInfo_t* User-provided CONNECT information. - * @param[in] AwsIotMqttPublishInfo_t* User-provided Last Will and Testament information. * @param[out] uint8_t** Where the CONNECT packet is written. * @param[out] size_t* Size of the CONNECT packet. * * Default implementation: #AwsIotMqttInternal_SerializeConnect */ AwsIotMqttError_t ( * connect )( const AwsIotMqttConnectInfo_t * const /* pConnectInfo */, - const AwsIotMqttPublishInfo_t * const /* pWillInfo */, uint8_t ** const /* pConnectPacket */, size_t * const /* pPacketSize */ ); @@ -905,7 +965,6 @@ typedef struct AwsIotMqttNetIf * - @functionname{mqtt_function_cleanup} * - @functionname{mqtt_function_receivecallback} * - @functionname{mqtt_function_connect} - * - @functionname{mqtt_function_connectrestoresession} * - @functionname{mqtt_function_disconnect} * - @functionname{mqtt_function_subscribe} * - @functionname{mqtt_function_timedsubscribe} @@ -933,7 +992,6 @@ typedef struct AwsIotMqttNetIf * @image html mqtt_function_receivecallback_partial.png width=80% * * @functionpage{AwsIotMqtt_Connect,mqtt,connect} - * @functionpage{AwsIotMqtt_ConnectRestoreSession,mqtt,connectrestoresession} * @functionpage{AwsIotMqtt_Disconnect,mqtt,disconnect} * @functionpage{AwsIotMqtt_Subscribe,mqtt,subscribe} * @functionpage{AwsIotMqtt_TimedSubscribe,mqtt,timedsubscribe} @@ -1071,17 +1129,12 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, * PUBLISH messages will be discarded when the connection is closed. * * If [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) is `false`, - * this function establishes a persistent MQTT session. This function should only - * be used to establish a NEW persistent MQTT session, i.e. a persistent - * session that has no previous subscriptions. The function @ref - * mqtt_function_connectrestoresession should be used to restore an established - * persistent session. The flow for using persistent sessions should be: - * 1. Establish new persistent session by calling @ref mqtt_function_connect with - * [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) `= false`. - * 2. Disconnect the MQTT connection. - * 3. When reconnecting a persistent session, call @ref mqtt_function_connectrestoresession - * with [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) `= false` - * and pass a list of subscriptions used in the persistent session. + * this function establishes (or re-establishes) a persistent MQTT session. The parameters + * [pConnectInfo->pPreviousSubscriptions](@ref AwsIotMqttConnectInfo_t.pPreviousSubscriptions) + * and [pConnectInfo->previousSubscriptionCount](@ref AwsIotMqttConnectInfo_t.previousSubscriptionCount) + * may be used to restore subscriptions present in a re-established persistent session. + * Any restored subscriptions MUST have been present in the persistent session; + * this function does not send an MQTT SUBSCRIBE packet! * * This MQTT library is network agnostic, meaning it has no knowledge of the * underlying network protocol carrying the MQTT packets. It interacts with the @@ -1093,10 +1146,11 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, * on its members. * * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet. - * Its members [are defined by the MQTT spec.](@ref AwsIotMqttConnectInfo_t). The - * `pWillInfo` parameter provides information on a Last Will and Testament (LWT) - * message to be published if the MQTT connection is closed without - * [sending a DISCONNECT packet](@ref mqtt_function_disconnect). Unlike other PUBLISH + * Most members [are defined by the MQTT spec.](@ref AwsIotMqttConnectInfo_t). The + * [pConnectInfo->pWillInfo](@ref AwsIotMqttConnectInfo_t.pWillInfo) member provides + * information on a Last Will and Testament (LWT) message to be published if the + * MQTT connection is closed without [sending a DISCONNECT packet] + * (@ref mqtt_function_disconnect). Unlike other PUBLISH * messages, a LWT message payload is limited to 65535 bytes in length. Additionally, * the retry [interval](@ref AwsIotMqttPublishInfo_t.retryMs) and [limit] * (@ref AwsIotMqttPublishInfo_t.retryLimit) members of #AwsIotMqttPublishInfo_t @@ -1115,9 +1169,6 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, * @param[in] pNetworkInterface The network `send` and `disconnect` functions that * this MQTT connection will use. * @param[in] pConnectInfo MQTT connection setup parameters. - * @param[in] pWillInfo Information on a Last Will and Testament message to be - * published if this connection is unexpectedly closed. Optional; pass `NULL` to - * ignore. * @param[in] timeoutMs If the MQTT server does not accept the connection within * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. * @@ -1130,10 +1181,6 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, * - #AWS_IOT_MQTT_TIMEOUT * - #AWS_IOT_MQTT_SERVER_REFUSED * - * @see - * @ref mqtt_function_connectrestoresession for the function to restore an - * established MQTT persistent session. - * * Example * @code{c} * // An initialized and connected network connection. @@ -1164,12 +1211,14 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, * willInfo.pPayload = "MQTT client unexpectedly disconnected."; * willInfo.payloadLength = 38; * + * // Set the pointer to the will info. + * connectInfo.pWillInfo = &willInfo; + * * // Call CONNECT with a 5 second block time. Should return * // AWS_IOT_MQTT_SUCCESS when successful. * AwsIotMqttError_t result = AwsIotMqtt_Connect( &mqttConnection, * &networkInterface, * &connectInfo, - * &willInfo, * 5000 ); * * if( result == AWS_IOT_MQTT_SUCCESS ) @@ -1185,64 +1234,9 @@ int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, const AwsIotMqttNetIf_t * const pNetworkInterface, const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, uint64_t timeoutMs ); /* @[declare_mqtt_connect] */ -/** - * @brief Establish an MQTT connection and restore the subscriptions of a - * persistent session. - * - * Like @ref mqtt_function_connect, this function establishes an MQTT connection - * by sending an MQTT CONNECT packet. In addition, this function also restores - * the subscription callbacks from a persistent session. - * - * The subscription list passed to this function must contain subscriptions that - * are already present on the MQTT server as part of a persistent session. This - * function does not send an MQTT SUBSCRIBE packet. The parameter - * [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) - * must be `false`. - * - * This function must only be called to restore an established persistent - * session. To create a new persistent session, use @ref mqtt_function_connect. - * - * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle - * if this function succeeds. - * @param[in] pNetworkInterface The network `send` and `disconnect` functions that - * this MQTT connection will use. - * @param[in] pConnectInfo MQTT connection setup parameters. - * @param[in] pWillInfo Information on a Last Will and Testament message to be - * published if this connection is unexpectedly closed. Optional; pass `NULL` to - * ignore. - * @param[in] pSessionSubscriptions MQTT subscriptions contained in the persistent - * session. - * @param[in] sessionSubscriptionsCount The number of persistent session subscriptions. - * @param[in] timeoutMs If the MQTT server does not accept the connection within - * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. - * - * @return One of the following: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE - * - #AWS_IOT_MQTT_TIMEOUT - * - #AWS_IOT_MQTT_SERVER_REFUSED - * - * @see - * @ref mqtt_function_connect for the function to establish clean MQTT connections - * or new persistent MQTT connections. - */ -/* @[declare_mqtt_connectrestoresession] */ -AwsIotMqttError_t AwsIotMqtt_ConnectRestoreSession( AwsIotMqttConnection_t * pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface, - const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, - const AwsIotMqttSubscription_t * const pSessionSubscriptions, - size_t sessionSubscriptionsCount, - uint64_t timeoutMs ); -/* @[declare_mqtt_connectrestoresession] */ - /** * @brief Closes an MQTT connection and frees resources. * diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h index 3f77e35bf2..81a707ca77 100644 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ b/lib/include/private/aws_iot_mqtt_internal.h @@ -538,14 +538,12 @@ uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, * @brief Generate a CONNECT packet from the given parameters. * * @param[in] pConnectInfo User-provided CONNECT information. - * @param[in] pWillInfo User-provided Last Will and Testament information. * @param[out] pConnectPacket Where the CONNECT packet is written. * @param[out] pPacketSize Size of the packet written to `pConnectPacket`. * * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. */ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, uint8_t ** const pConnectPacket, size_t * const pPacketSize ); diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index c945bd9ee7..0f38fe3b69 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -151,21 +151,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, */ static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ); -/** - * @brief The common component of both @ref mqtt_function_connect and @ref - * mqtt_function_connectrestoresession. - * - * See @ref mqtt_function_connectrestoresession for a description of the - * parameters and return values. - */ -static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface, - const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, - const AwsIotMqttSubscription_t * const pSessionSubscriptions, - size_t sessionSubscriptionsCount, - uint64_t timeoutMs ); - /** * @brief The common component of both @ref mqtt_function_subscribe and @ref * mqtt_function_unsubscribe. @@ -682,196 +667,6 @@ static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) /*-----------------------------------------------------------*/ -static AwsIotMqttError_t _connectCommon( AwsIotMqttConnection_t * pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface, - const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, - const AwsIotMqttSubscription_t * const pSessionSubscriptions, - size_t sessionSubscriptionsCount, - uint64_t timeoutMs ) -{ - AwsIotMqttError_t connectStatus = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pNewMqttConnection = NULL; - _mqttOperation_t * pConnectOperation = NULL; - - /* Default CONNECT serializer function. */ - AwsIotMqttError_t ( * serializeConnect )( const AwsIotMqttConnectInfo_t * const, - const AwsIotMqttPublishInfo_t * const, - uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializeConnect; - - /* Check that the network interface is valid. */ - if( AwsIotMqttInternal_ValidateNetIf( pNetworkInterface ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Choose a CONNECT serializer function. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNetworkInterface->serialize.connect != NULL ) - { - serializeConnect = pNetworkInterface->serialize.connect; - } - #endif - - /* Check that the connection info is valid. */ - if( AwsIotMqttInternal_ValidateConnect( pConnectInfo ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* If will info is provided, check that it is valid. */ - if( pWillInfo != NULL ) - { - if( AwsIotMqttInternal_ValidatePublish( pConnectInfo->awsIotMqttMode, - pWillInfo ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Will message payloads cannot be larger than 65535. This restriction - * applies only to will messages, and not normal PUBLISH messages. */ - if( pWillInfo->payloadLength > UINT16_MAX ) - { - IotLogError( "Will payload cannot be larger than 65535." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - } - - IotLogInfo( "Establishing new MQTT connection." ); - - /* Create a CONNECT operation. */ - connectStatus = AwsIotMqttInternal_CreateOperation( &pConnectOperation, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL ); - - if( connectStatus != AWS_IOT_MQTT_SUCCESS ) - { - return connectStatus; - } - - /* Ensure the members set by operation creation and serialization - * are appropriate for a blocking CONNECT. */ - AwsIotMqtt_Assert( pConnectOperation->pPublishRetry == NULL ); - AwsIotMqtt_Assert( pConnectOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - AwsIotMqtt_Assert( ( pConnectOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) - == AWS_IOT_MQTT_FLAG_WAITABLE ); - - /* Set the operation type. */ - pConnectOperation->operation = AWS_IOT_MQTT_CONNECT; - - /* Allocate memory to store data for the new MQTT connection. */ - pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, - pNetworkInterface, - pConnectInfo->keepAliveSeconds ); - - if( pNewMqttConnection == NULL ) - { - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - - return AWS_IOT_MQTT_NO_MEMORY; - } - - /* Set the MQTT connection. */ - pConnectOperation->pMqttConnection = pNewMqttConnection; - - /* Add previous session subscriptions. */ - if( ( pSessionSubscriptions != NULL ) && ( sessionSubscriptionsCount > 0 ) ) - { - connectStatus = AwsIotMqttInternal_AddSubscriptions( pNewMqttConnection, - 2, - pSessionSubscriptions, - sessionSubscriptionsCount ); - - if( connectStatus != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - _destroyMqttConnection( pNewMqttConnection ); - - return connectStatus; - } - } - - /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ - connectStatus = serializeConnect( pConnectInfo, - pWillInfo, - &( pConnectOperation->pMqttPacket ), - &( pConnectOperation->packetSize ) ); - - if( connectStatus != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - _destroyMqttConnection( pNewMqttConnection ); - - return connectStatus; - } - - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pConnectOperation->packetSize > 0 ); - - /* Set the output parameter so it may be used by the network receive callback. */ - *pMqttConnection = pNewMqttConnection; - - /* Prevent another CONNECT operation from using the network. */ - IotMutex_Lock( &_connectMutex ); - - /* Add the CONNECT operation to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to enqueue CONNECT for sending." ); - connectStatus = AWS_IOT_MQTT_NO_MEMORY; - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - } - else - { - /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - connectStatus = AwsIotMqtt_Wait( ( AwsIotMqttReference_t ) pConnectOperation, - timeoutMs ); - } - - /* Unlock the CONNECT mutex. */ - IotMutex_Unlock( &_connectMutex ); - - /* Arm the timer for the first keep alive expiration if keep-alive is - * active for this connection. */ - if( ( connectStatus == AWS_IOT_MQTT_SUCCESS ) && - ( pNewMqttConnection->keepAliveSeconds > 0 ) ) - { - IotLogDebug( "Starting new MQTT connection timer." ); - - if( IotClock_TimerArm( &( pNewMqttConnection->timer ), - pNewMqttConnection->pKeepAliveEvent->expirationTime - IotClock_GetTimeMs(), - 0 ) == false ) - { - IotLogError( "Failed to start connection timer for new MQTT connection" ); - - connectStatus = AWS_IOT_MQTT_INIT_FAILED; - } - } - - /* Check the status of the CONNECT operation. */ - if( connectStatus == AWS_IOT_MQTT_SUCCESS ) - { - IotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); - } - else - { - /* Otherwise, free resources and log an error. */ - _destroyMqttConnection( pNewMqttConnection ); - *pMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - - IotLogError( "Failed to establish new MQTT connection, error %s.", - AwsIotMqtt_strerror( connectStatus ) ); - } - - return connectStatus; -} - -/*-----------------------------------------------------------*/ - static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operation, AwsIotMqttConnection_t mqttConnection, const AwsIotMqttSubscription_t * const pSubscriptionList, @@ -1187,53 +982,200 @@ void AwsIotMqtt_Cleanup() AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, const AwsIotMqttNetIf_t * const pNetworkInterface, const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, uint64_t timeoutMs ) { - return _connectCommon( pMqttConnection, - pNetworkInterface, - pConnectInfo, - pWillInfo, - NULL, - 0, - timeoutMs ); -} + AwsIotMqttError_t connectStatus = AWS_IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pNewMqttConnection = NULL; + _mqttOperation_t * pConnectOperation = NULL; -/*-----------------------------------------------------------*/ + /* Default CONNECT serializer function. */ + AwsIotMqttError_t ( * serializeConnect )( const AwsIotMqttConnectInfo_t * const, + uint8_t ** const, + size_t * const ) = AwsIotMqttInternal_SerializeConnect; -AwsIotMqttError_t AwsIotMqtt_ConnectRestoreSession( AwsIotMqttConnection_t * pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface, - const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, - const AwsIotMqttSubscription_t * const pSessionSubscriptions, - size_t sessionSubscriptionsCount, - uint64_t timeoutMs ) -{ - /* Check that clean session is false. */ - if( pConnectInfo->cleanSession != false ) + /* Check that the network interface is valid. */ + if( AwsIotMqttInternal_ValidateNetIf( pNetworkInterface ) == false ) { - IotLogError( "AwsIotMqtt_ConnectRestoreSession must have pConnectInfo->cleanSession == false. " - "AwsIotMqtt_Connect should be used to establish a clean session." ); - return AWS_IOT_MQTT_BAD_PARAMETER; } - /* Validate previous session subscriptions. */ - if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pSessionSubscriptions, - sessionSubscriptionsCount ) == false ) + /* Choose a CONNECT serializer function. */ + #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNetworkInterface->serialize.connect != NULL ) + { + serializeConnect = pNetworkInterface->serialize.connect; + } + #endif + + /* Check that the connection info is valid. */ + if( AwsIotMqttInternal_ValidateConnect( pConnectInfo ) == false ) { return AWS_IOT_MQTT_BAD_PARAMETER; } - return _connectCommon( pMqttConnection, - pNetworkInterface, - pConnectInfo, - pWillInfo, - pSessionSubscriptions, - sessionSubscriptionsCount, - timeoutMs ); + /* If will info is provided, check that it is valid. */ + if( pConnectInfo->pWillInfo != NULL ) + { + if( AwsIotMqttInternal_ValidatePublish( pConnectInfo->awsIotMqttMode, + pConnectInfo->pWillInfo ) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + + /* Will message payloads cannot be larger than 65535. This restriction + * applies only to will messages, and not normal PUBLISH messages. */ + if( pConnectInfo->pWillInfo->payloadLength > UINT16_MAX ) + { + IotLogError( "Will payload cannot be larger than 65535." ); + + return AWS_IOT_MQTT_BAD_PARAMETER; + } + } + + /* If previous subscriptions are provided, check that they are valid. */ + if( ( pConnectInfo->cleanSession == false ) && + ( pConnectInfo->pPreviousSubscriptions != NULL ) ) + { + if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount) == false ) + { + return AWS_IOT_MQTT_BAD_PARAMETER; + } + } + + IotLogInfo( "Establishing new MQTT connection." ); + + /* Create a CONNECT operation. */ + connectStatus = AwsIotMqttInternal_CreateOperation( &pConnectOperation, + AWS_IOT_MQTT_FLAG_WAITABLE, + NULL ); + + if( connectStatus != AWS_IOT_MQTT_SUCCESS ) + { + return connectStatus; + } + + /* Ensure the members set by operation creation and serialization + * are appropriate for a blocking CONNECT. */ + AwsIotMqtt_Assert( pConnectOperation->pPublishRetry == NULL ); + AwsIotMqtt_Assert( pConnectOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); + AwsIotMqtt_Assert( ( pConnectOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) + == AWS_IOT_MQTT_FLAG_WAITABLE ); + + /* Set the operation type. */ + pConnectOperation->operation = AWS_IOT_MQTT_CONNECT; + + /* Allocate memory to store data for the new MQTT connection. */ + pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, + pNetworkInterface, + pConnectInfo->keepAliveSeconds ); + + if( pNewMqttConnection == NULL ) + { + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + + return AWS_IOT_MQTT_NO_MEMORY; + } + + /* Set the MQTT connection. */ + pConnectOperation->pMqttConnection = pNewMqttConnection; + + /* Add previous session subscriptions. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + /* Previous subscription count should have been validated as nonzero. */ + AwsIotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); + + connectStatus = AwsIotMqttInternal_AddSubscriptions( pNewMqttConnection, + 2, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ); + + if( connectStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + _destroyMqttConnection( pNewMqttConnection ); + + return connectStatus; + } + } + + /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ + connectStatus = serializeConnect( pConnectInfo, + &( pConnectOperation->pMqttPacket ), + &( pConnectOperation->packetSize ) ); + + if( connectStatus != AWS_IOT_MQTT_SUCCESS ) + { + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + _destroyMqttConnection( pNewMqttConnection ); + + return connectStatus; + } + + /* Check the serialized MQTT packet. */ + AwsIotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); + AwsIotMqtt_Assert( pConnectOperation->packetSize > 0 ); + + /* Set the output parameter so it may be used by the network receive callback. */ + *pMqttConnection = pNewMqttConnection; + + /* Prevent another CONNECT operation from using the network. */ + IotMutex_Lock( &_connectMutex ); + + /* Add the CONNECT operation to the send queue for network transmission. */ + if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation, + &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to enqueue CONNECT for sending." ); + connectStatus = AWS_IOT_MQTT_NO_MEMORY; + AwsIotMqttInternal_DestroyOperation( pConnectOperation ); + } + else + { + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + connectStatus = AwsIotMqtt_Wait( ( AwsIotMqttReference_t ) pConnectOperation, + timeoutMs ); + } + + /* Unlock the CONNECT mutex. */ + IotMutex_Unlock( &_connectMutex ); + + /* Arm the timer for the first keep alive expiration if keep-alive is + * active for this connection. */ + if( ( connectStatus == AWS_IOT_MQTT_SUCCESS ) && + ( pNewMqttConnection->keepAliveSeconds > 0 ) ) + { + IotLogDebug( "Starting new MQTT connection timer." ); + + if( IotClock_TimerArm( &( pNewMqttConnection->timer ), + pNewMqttConnection->pKeepAliveEvent->expirationTime - IotClock_GetTimeMs(), + 0 ) == false ) + { + IotLogError( "Failed to start connection timer for new MQTT connection" ); + + connectStatus = AWS_IOT_MQTT_INIT_FAILED; + } + } + + /* Check the status of the CONNECT operation. */ + if( connectStatus == AWS_IOT_MQTT_SUCCESS ) + { + IotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); + } + else + { + /* Otherwise, free resources and log an error. */ + _destroyMqttConnection( pNewMqttConnection ); + *pMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + + IotLogError( "Failed to establish new MQTT connection, error %s.", + AwsIotMqtt_strerror( connectStatus ) ); + } + + return connectStatus; } /*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c index 0ceeac12d1..6ced1f177d 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -234,12 +234,10 @@ static uint8_t * _encodeString( uint8_t * pDestination, * from the given parameters. * * @param[in] pConnectInfo User-provided CONNECT information struct. - * @param[in] pWillInfo User-provided Last Will and Testament info struct. * @param[out] pRemainingLength Output for calculated "Remaining length" field. * @param[out] pPacketSize Output for calculated total packet size. */ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, size_t * const pPacketSize ); @@ -459,7 +457,6 @@ static uint8_t * _encodeString( uint8_t * pDestination, /*-----------------------------------------------------------*/ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, size_t * const pPacketSize ) { @@ -475,10 +472,10 @@ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectIn } /* Add the lengths of the will message and topic name if provided. */ - if( pWillInfo != NULL ) + if( pConnectInfo->pWillInfo != NULL ) { - connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) + - pWillInfo->payloadLength + sizeof( uint16_t ); + connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + + pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); } /* Depending on the status of metrics, add the length of the metrics username @@ -672,7 +669,6 @@ uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, /*-----------------------------------------------------------*/ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, uint8_t ** const pConnectPacket, size_t * const pPacketSize ) { @@ -682,7 +678,6 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn /* Calculate the "Remaining length" field and total packet size. */ _connectPacketSize( pConnectInfo, - pWillInfo, &remainingLength, &connectPacketSize ); @@ -752,12 +747,12 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn } } - if( pWillInfo != NULL ) + if( pConnectInfo->pWillInfo != NULL ) { _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL ); /* Flags only need to be changed for will QoS 1 and 2. */ - switch( pWillInfo->QoS ) + switch( pConnectInfo->pWillInfo->QoS ) { case 1: _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS1 ); @@ -771,7 +766,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn break; } - if( pWillInfo->retain == true ) + if( pConnectInfo->pWillInfo->retain == true ) { _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_RETAIN ); } @@ -791,15 +786,15 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn pConnectInfo->clientIdentifierLength ); /* Write the will topic name and message into the CONNECT packet if provided. */ - if( pWillInfo != NULL ) + if( pConnectInfo->pWillInfo != NULL ) { pBuffer = _encodeString( pBuffer, - pWillInfo->pTopicName, - pWillInfo->topicNameLength ); + pConnectInfo->pWillInfo->pTopicName, + pConnectInfo->pWillInfo->topicNameLength ); pBuffer = _encodeString( pBuffer, - pWillInfo->pPayload, - ( uint16_t ) pWillInfo->payloadLength ); + pConnectInfo->pWillInfo->pPayload, + ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); } /* If metrics are enabled, write the metrics username into the CONNECT packet. diff --git a/lib/source/mqtt/aws_iot_mqtt_validate.c b/lib/source/mqtt/aws_iot_mqtt_validate.c index 41424e9771..1408931293 100644 --- a/lib/source/mqtt/aws_iot_mqtt_validate.c +++ b/lib/source/mqtt/aws_iot_mqtt_validate.c @@ -134,6 +134,18 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p } } + /* Check that the number of persistent session subscriptions is valid. */ + if( pConnectInfo->cleanSession == false ) + { + if( ( pConnectInfo->pPreviousSubscriptions != NULL ) && + ( pConnectInfo->previousSubscriptionCount == 0 ) ) + { + IotLogError( "Previous subscription count cannot be 0." ); + + return false; + } + } + /* In MQTT 3.1.1, servers are not obligated to accept client identifiers longer * than 23 characters. */ if( pConnectInfo->clientIdentifierLength > 23 ) diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c index 9cd8b73c7c..e4c9565c5c 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_stress.c @@ -489,7 +489,6 @@ TEST_SETUP( MQTT_Stress ) AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &_AwsIotTestNetworkInterface, &connectInfo, - NULL, AWS_IOT_TEST_MQTT_TIMEOUT_MS ) ); /* Subscribe to the test topic filters. */ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index b2dc2b89b9..2de7cf6c6d 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -179,14 +179,12 @@ static void _freePacket( uint8_t * pPacket ) * @brief Serializer override for CONNECT. */ static AwsIotMqttError_t _serializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, - const AwsIotMqttPublishInfo_t * const pWillInfo, uint8_t ** const pConnectPacket, size_t * const pPacketSize ) { _connectSerializerOverride = true; return AwsIotMqttInternal_SerializeConnect( pConnectInfo, - pWillInfo, pConnectPacket, pPacketSize ); } @@ -358,7 +356,6 @@ static void _subscribePublishWait( int QoS ) status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &networkInterface, &connectInfo, - NULL, AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); @@ -581,7 +578,6 @@ TEST( MQTT_System, SubscribePublishAsync ) status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &_AwsIotTestNetworkInterface, &connectInfo, - NULL, AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); @@ -700,7 +696,6 @@ TEST( MQTT_System, LastWillAndTestament ) status = AwsIotMqtt_Connect( &lwtListener, &lwtNetIf, &lwtConnectInfo, - NULL, AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); @@ -723,6 +718,7 @@ TEST( MQTT_System, LastWillAndTestament ) connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + connectInfo.pWillInfo = &willInfo; willInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); @@ -732,7 +728,6 @@ TEST( MQTT_System, LastWillAndTestament ) status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &_AwsIotTestNetworkInterface, &connectInfo, - &willInfo, AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); @@ -792,7 +787,6 @@ TEST( MQTT_System, RestorePreviousSession ) status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &_AwsIotTestNetworkInterface, &connectInfo, - NULL, AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); @@ -818,13 +812,12 @@ TEST( MQTT_System, RestorePreviousSession ) /* Re-establish the MQTT connection with a previous session. */ connectInfo.cleanSession = false; - status = AwsIotMqtt_ConnectRestoreSession( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - NULL, - &subscription, - 1, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + connectInfo.pPreviousSubscriptions = &subscription; + connectInfo.previousSubscriptionCount = 1; + status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, + &_AwsIotTestNetworkInterface, + &connectInfo, + AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ @@ -865,7 +858,6 @@ TEST( MQTT_System, RestorePreviousSession ) status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &_AwsIotTestNetworkInterface, &connectInfo, - NULL, AWS_IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index cab7656a26..afbb57fb03 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -343,7 +343,6 @@ TEST( MQTT_Unit_API, ConnectParameters ) status = AwsIotMqtt_Connect( &mqttConnection, &networkInterface, &connectInfo, - NULL, _TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); networkInterface.send = _sendSuccess; @@ -352,43 +351,38 @@ TEST( MQTT_Unit_API, ConnectParameters ) status = AwsIotMqtt_Connect( &mqttConnection, &networkInterface, &connectInfo, - NULL, _TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; - /* AwsIotMqtt_ConnectRestoreSession with bad subscription. */ + /* Connect with bad previous session subscription. */ connectInfo.cleanSession = false; - status = AwsIotMqtt_ConnectRestoreSession( &mqttConnection, - &networkInterface, - &connectInfo, - NULL, - &subscription, - 1, - _TIMEOUT_MS ); + connectInfo.pPreviousSubscriptions = &subscription; + connectInfo.previousSubscriptionCount = 1; + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - /* AwsIotMqtt_ConnectRestoreSession with clean session. */ - connectInfo.cleanSession = true; + /* Connect with bad subscription count. */ + connectInfo.previousSubscriptionCount = 0; subscription.pTopicFilter = _TEST_TOPIC_NAME; subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - status = AwsIotMqtt_ConnectRestoreSession( &mqttConnection, - &networkInterface, - &connectInfo, - &willInfo, - &subscription, - 1, - _TIMEOUT_MS ); + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); + connectInfo.previousSubscriptionCount = 1; /* Check that the will info is validated when it's provided. */ + connectInfo.pWillInfo = &willInfo; status = AwsIotMqtt_Connect( &mqttConnection, &networkInterface, &connectInfo, - &willInfo, _TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); willInfo.pTopicName = _TEST_TOPIC_NAME; @@ -400,7 +394,6 @@ TEST( MQTT_Unit_API, ConnectParameters ) status = AwsIotMqtt_Connect( &mqttConnection, &networkInterface, &connectInfo, - &willInfo, _TIMEOUT_MS ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); willInfo.payloadLength = 0; @@ -409,7 +402,6 @@ TEST( MQTT_Unit_API, ConnectParameters ) status = AwsIotMqtt_Connect( &mqttConnection, &networkInterface, &connectInfo, - &willInfo, 0 ); TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, status ); } @@ -444,7 +436,6 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) status = AwsIotMqtt_Connect( &mqttConnection, &networkInterface, &connectInfo, - NULL, _TIMEOUT_MS ); /* If the return value is timeout, then all memory allocation succeeded @@ -464,8 +455,8 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_connectrestoresession - * when memory allocation fails at various points. + * @brief Tests the behavior of @ref mqtt_function_connect when memory + * allocation fails at various points for a persistent session. */ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) { @@ -486,19 +477,19 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; + connectInfo.pPreviousSubscriptions = &subscription; + connectInfo.previousSubscriptionCount = 1; + for( i = 0; ; i++ ) { UnityMalloc_MakeMallocFailAfterCount( i ); /* Call CONNECT with a previous session. Memory allocation will fail at * various times during this call. */ - status = AwsIotMqtt_ConnectRestoreSession( &mqttConnection, - &networkInterface, - &connectInfo, - NULL, - &subscription, - 1, - _TIMEOUT_MS ); + status = AwsIotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); /* If the return value is timeout, then all memory allocation succeeded * and the loop can exit. The expected return value is timeout (and not diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index ed68c5ae17..0a1c0a299a 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -447,7 +447,6 @@ TEST_SETUP( Shadow_System ) if( AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, &_AwsIotTestNetworkInterface, &connectInfo, - NULL, AWS_IOT_TEST_SHADOW_TIMEOUT ) != AWS_IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); From d38b9e26b1e2c4db9d6fc2f210e3893e9474a0a4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 6 Feb 2019 13:09:36 -0800 Subject: [PATCH 019/844] Reimplement platform network for more portability. (#270) --- demos/posix/aws_iot_demo_mqtt_posix.c | 66 +- demos/posix/aws_iot_demo_shadow_posix.c | 79 +- doc/config/platform | 2 +- doc/lib/platform.txt | 8 +- lib/include/aws_iot_mqtt.h | 18 +- lib/include/platform/aws_iot_network.h | 411 ------- lib/include/platform/iot_network.h | 302 +++++ lib/source/mqtt/aws_iot_mqtt_operation.c | 2 +- lib/source/mqtt/iot_mqtt_network.c | 14 +- platform/include/posix/iot_network_openssl.h | 266 ++++ .../posix/network/iot_network_openssl.c | 1078 ++++++++++------- tests/aws_iot_tests_network.c | 124 +- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 43 +- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 10 +- tests/mqtt/unit/aws_iot_tests_mqtt_receive.c | 24 +- .../system/aws_iot_tests_shadow_system.c | 2 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 10 +- 17 files changed, 1396 insertions(+), 1063 deletions(-) delete mode 100644 lib/include/platform/aws_iot_network.h create mode 100644 lib/include/platform/iot_network.h create mode 100644 platform/include/posix/iot_network_openssl.h diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/aws_iot_demo_mqtt_posix.c index 134d3d3cd4..0956fdc206 100644 --- a/demos/posix/aws_iot_demo_mqtt_posix.c +++ b/demos/posix/aws_iot_demo_mqtt_posix.c @@ -41,19 +41,20 @@ #include "aws_iot_demo.h" #include "aws_iot_demo_posix.h" -/* Platform layer include. */ -#include "platform/aws_iot_network.h" +/* POSIX+OpenSSL network include. */ +#include "posix/iot_network_openssl.h" /*-----------------------------------------------------------*/ int main( int argc, char ** argv ) { - bool commonInitialized = false; + bool commonInitialized = false, networkConnectionCreated = false; int status = 0; AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; - AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; - AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER, * pTlsInfo = NULL; + IotNetworkConnectionOpenssl_t networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; + IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; + IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, * pCredentials = NULL; AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; @@ -82,7 +83,7 @@ int main( int argc, } else { - if( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) { IotCommon_Cleanup(); status = -1; @@ -99,39 +100,46 @@ int main( int argc, /* Set the TLS connection information for secured connections. */ if( demoArguments.securedConnection == true ) { - pTlsInfo = &tlsInfo; + pCredentials = &credentials; - /* By default AWS_IOT_NETWORK_TLS_INFO_INITIALIZER enables ALPN. ALPN + /* By default AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER enables ALPN. ALPN * must be used with port 443; disable ALPN if another port is being used. */ if( demoArguments.port != 443 ) { - tlsInfo.pAlpnProtos = NULL; + credentials.pAlpnProtos = NULL; } /* Set the paths to the credentials. Lengths of credential paths are * ignored by the POSIX platform layer, so they are not set. */ - tlsInfo.pRootCa = demoArguments.pRootCaPath; - tlsInfo.pClientCert = demoArguments.pClientCertPath; - tlsInfo.pPrivateKey = demoArguments.pPrivateKeyPath; + credentials.pRootCaPath = demoArguments.pRootCaPath; + credentials.pClientCertPath = demoArguments.pClientCertPath; + credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; } + /* Set server info. */ + serverInfo.pHostName = demoArguments.pHostName; + serverInfo.port = demoArguments.port; + /* Establish a TCP connection to the MQTT server. */ - if( AwsIotNetwork_CreateConnection( &networkConnection, - demoArguments.pHostName, - demoArguments.port, - pTlsInfo ) != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_Create( &serverInfo, + pCredentials, + &networkConnection ) != IOT_NETWORK_SUCCESS ) { status = -1; } + else + { + networkConnectionCreated = true; + } } if( status == 0 ) { /* Set the MQTT receive callback for a network connection. This receive * callback processes MQTT data from the network. */ - if( AwsIotNetwork_SetMqttReceiveCallback( networkConnection, - &mqttConnection, - AwsIotMqtt_ReceiveCallback ) != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, + AwsIotMqtt_ReceiveCallback, + &mqttConnection ) != IOT_NETWORK_SUCCESS ) { status = -1; } @@ -140,10 +148,10 @@ int main( int argc, if( status == 0 ) { /* Set the members of the network interface used by the MQTT connection. */ - networkInterface.pDisconnectContext = ( void * ) networkConnection; - networkInterface.pSendContext = ( void * ) networkConnection; - networkInterface.disconnect = AwsIotNetwork_CloseConnection; - networkInterface.send = AwsIotNetwork_Send; + networkInterface.pDisconnectContext = ( void * ) &networkConnection; + networkInterface.pSendContext = ( void * ) &networkConnection; + networkInterface.disconnect = IotNetworkOpenssl_Close; + networkInterface.send = IotNetworkOpenssl_Send; /* Initialize the MQTT library. */ if( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) @@ -164,21 +172,21 @@ int main( int argc, } /* Close and destroy the network connection (if it was established). */ - if( networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + if( networkConnectionCreated == true ) { - /* Note that the MQTT library may have called AwsIotNetwork_CloseConnection. - * However, AwsIotNetwork_CloseConnection is safe to call on a closed connection. + /* Note that the MQTT library may have already closed the connection. + * However, the network close function is safe to call on a closed connection. * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. */ - AwsIotNetwork_CloseConnection( networkConnection ); - AwsIotNetwork_DestroyConnection( networkConnection ); + IotNetworkOpenssl_Close( &networkConnection ); + IotNetworkOpenssl_Destroy( &networkConnection ); } /* Clean up the common libraries and network. */ if( commonInitialized == true ) { IotCommon_Cleanup(); - AwsIotNetwork_Cleanup(); + IotNetworkOpenssl_Cleanup(); } /* Log the demo status. */ diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index ac7b250c07..3454e768d3 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -44,19 +44,20 @@ #include "aws_iot_demo.h" #include "aws_iot_demo_posix.h" -/* Platform layer include. */ -#include "platform/aws_iot_network.h" +/* POSIX+OpenSSL network include. */ +#include "posix/iot_network_openssl.h" /*-----------------------------------------------------------*/ int main( int argc, char ** argv ) { - bool commonInitialized = false; + bool commonInitialized = false, networkConnectionCreated = false; int status = 0; AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; - AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; - AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER, * pTlsInfo = NULL; + IotNetworkConnectionOpenssl_t networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; + IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; + IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, *pCredentials = NULL; AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; @@ -85,7 +86,7 @@ int main( int argc, } else { - if( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) { IotCommon_Cleanup(); status = -1; @@ -109,26 +110,25 @@ int main( int argc, { /* Set the TLS connection information for secured connections. Thing * Shadow is specific to AWS IoT, so it always requires a secured connection. */ - pTlsInfo = &tlsInfo; + pCredentials = &credentials; /* By default AWS_IOT_NETWORK_TLS_INFO_INITIALIZER enables ALPN. ALPN * must be used with port 443; disable ALPN if another port is being used. */ if( demoArguments.port != 443 ) { - tlsInfo.pAlpnProtos = NULL; + credentials.pAlpnProtos = NULL; } /* Set the paths to the credentials. Lengths of credential paths are * ignored by the POSIX platform layer, so they are not set. */ - tlsInfo.pRootCa = demoArguments.pRootCaPath; - tlsInfo.pClientCert = demoArguments.pClientCertPath; - tlsInfo.pPrivateKey = demoArguments.pPrivateKeyPath; + credentials.pRootCaPath = demoArguments.pRootCaPath; + credentials.pClientCertPath = demoArguments.pClientCertPath; + credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; /* Establish a TCP connection to the MQTT server. */ - if( AwsIotNetwork_CreateConnection( &networkConnection, - demoArguments.pHostName, - demoArguments.port, - pTlsInfo ) != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_Create( &serverInfo, + pCredentials, + &networkConnection ) != IOT_NETWORK_SUCCESS ) { status = -1; } @@ -138,9 +138,9 @@ int main( int argc, { /* Set the MQTT receive callback for a network connection. This receive * callback processes MQTT data from the network. */ - if( AwsIotNetwork_SetMqttReceiveCallback( networkConnection, - &mqttConnection, - AwsIotMqtt_ReceiveCallback ) != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, + AwsIotMqtt_ReceiveCallback, + &mqttConnection ) == IOT_NETWORK_SUCCESS ) { status = -1; } @@ -149,22 +149,29 @@ int main( int argc, if( status == 0 ) { /* Set the members of the network interface used by the MQTT connection. */ - networkInterface.pDisconnectContext = ( void * ) networkConnection; - networkInterface.pSendContext = ( void * ) networkConnection; - networkInterface.disconnect = AwsIotNetwork_CloseConnection; - networkInterface.send = AwsIotNetwork_Send; + networkInterface.pDisconnectContext = ( void * ) &networkConnection; + networkInterface.pSendContext = ( void * ) &networkConnection; + networkInterface.disconnect = IotNetworkOpenssl_Close; + networkInterface.send = IotNetworkOpenssl_Send; /* Initialize the MQTT library and Shadow library. */ - if( ( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) && - ( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ) ) + if( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) { - /* Run the Shadow demo. */ - status = AwsIotDemo_RunShadowDemo( demoArguments.pIdentifier, - &mqttConnection, - &networkInterface ); + if( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ) + { + /* Run the Shadow demo. */ + status = AwsIotDemo_RunShadowDemo( demoArguments.pIdentifier, + &mqttConnection, + &networkInterface ); + + /* Clean up the MQTT library and Shadow library. */ + AwsIotShadow_Cleanup(); + } + else + { + status = -1; + } - /* Clean up the MQTT library and Shadow library. */ - AwsIotShadow_Cleanup(); AwsIotMqtt_Cleanup(); } else @@ -174,21 +181,21 @@ int main( int argc, } /* Close and destroy the network connection (if it was established). */ - if( networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + if( networkConnectionCreated == true ) { - /* Note that the MQTT library may have called AwsIotNetwork_CloseConnection. - * However, AwsIotNetwork_CloseConnection is safe to call on a closed connection. + /* Note that the MQTT library may have already closed the connection. + * However, the network close function is safe to call on a closed connection. * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. */ - AwsIotNetwork_CloseConnection( networkConnection ); - AwsIotNetwork_DestroyConnection( networkConnection ); + IotNetworkOpenssl_Close( &networkConnection ); + IotNetworkOpenssl_Destroy( &networkConnection ); } /* Clean up the network. */ if( commonInitialized == true ) { IotCommon_Cleanup(); - AwsIotNetwork_Cleanup(); + IotNetworkOpenssl_Cleanup(); } /* Log the demo status. */ diff --git a/doc/config/platform b/doc/config/platform index 69e44ec848..c26f141ccd 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -19,7 +19,7 @@ INPUT = doc/lib \ # Library file names. FILE_PATTERNS = platform.txt \ - aws_iot_network.h \ + iot_network.h \ iot_clock.h \ iot_threads.h \ iot_platform_types.h diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 73db2a72bd..2057d45121 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -60,9 +60,7 @@ Currently, the platform clock component has the following dependencies: @page platform_network Networking @brief @copybrief iot_network.h -The platform networking component provides other libraries with functions for managing network connections. It interfaces directly with the operating system to provide: -- Functions for opening and closing network connections; function for sending and receiving data. -- Functions for securing network connections. +The platform networking component provides other libraries with an abstraction for interacting with the network through an #IotNetworkInterface_t. Libraries that require the network will request an #IotNetworkInterface_t as a parameter and use those function pointers to access the network. This allows libraries to use different network stacks simultaneously. @dependencies{platform_network,platform networking component} @dot "Networking direct dependencies" @@ -75,7 +73,6 @@ digraph network_dependencies { rank = same; platform_network[label="Networking", fillcolor="#e89025ff"]; - platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; } subgraph { @@ -85,7 +82,6 @@ digraph network_dependencies security_library[label="Security library", fillcolor="#999999ff"]; standard_library[label="Standard library", fillcolor="#d15555ff"]; } - platform_network -> platform_threads; platform_network -> operating_system; platform_network -> security_library [label=" secured connection", style="dashed"]; platform_network -> standard_library; @@ -94,7 +90,7 @@ digraph network_dependencies } @enddot -Currently, the networking component has the following dependencies: +Functions should be implemented against the system's network stack to match the signatures given in an #IotNetworkInterface_t. - The operating system must provide the necessary networking APIs, such as a sockets API. - A third-party security library is needed to encrypt secured connections. - The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not @ref IOT_LOG_NONE. diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h index 1458f9dcdd..c60e5a1a70 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/aws_iot_mqtt.h @@ -37,6 +37,9 @@ #include #include +/* Platform network include. */ +#include "platform/iot_network.h" + /*---------------------------- MQTT handle types ----------------------------*/ /** @@ -627,7 +630,7 @@ typedef struct AwsIotMqttNetIf * @return Number of bytes successfully sent, 0 on failure. */ size_t ( * send )( void *, - const void * const, + const uint8_t * const, size_t ); /** @@ -642,7 +645,7 @@ typedef struct AwsIotMqttNetIf * must be closed in certain conditions; if this function is not provided, the * MQTT library is noncompliant. */ - void ( * disconnect )( void * ); + IotNetworkError_t ( * disconnect )( void * ); /* * In addition to providing the network send and disconnect functions, this @@ -1085,12 +1088,14 @@ void AwsIotMqtt_Cleanup( void ); * * @param[in] pMqttConnection A pointer to the MQTT connection handle for which * the packet was received. + * @param[in] pConnection The network connection associated with the MQTT connection, + * passed by the network stack. * @param[in] pReceivedData Pointer to the beginning of the data stream. This buffer * must remain valid and in-scope until the MQTT library calls `freeReceivedData`. + * @param[in] dataLength The length of `pReceivedData` in bytes. * @param[in] offset The offset (in bytes) into `pReceivedData` where the MQTT library * begins processing. All bytes from `pReceivedData+offset` to `pReceivedData+dataLength` * will be processed. Pass `0` to start from the beginning of `pReceivedData`. - * @param[in] dataLength The length of `pReceivedData` in bytes. * @param[in] freeReceivedData The function that the MQTT library calls when it is * finished with `pReceivedData`. This function will only be called when all data is * successfully processed, i.e. when this function returns a value of `dataLength-offset`. @@ -1107,10 +1112,11 @@ void AwsIotMqtt_Cleanup( void ); * to `pReceivedData+dataLength` were successfully processed. */ /* @[declare_mqtt_receivecallback] */ -int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, - const void * pReceivedData, - size_t offset, +int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, + void * pConnection, + const uint8_t * pReceivedData, size_t dataLength, + size_t offset, void ( * freeReceivedData )( void * ) ); /* @[declare_mqtt_receivecallback] */ diff --git a/lib/include/platform/aws_iot_network.h b/lib/include/platform/aws_iot_network.h deleted file mode 100644 index 68ad4d55bf..0000000000 --- a/lib/include/platform/aws_iot_network.h +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_network.h - * @brief Network-related functions used by the AWS IoT libraries. - */ - -#ifndef _AWS_IOT_NETWORK_H_ -#define _AWS_IOT_NETWORK_H_ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include -#include - -/* MQTT include. */ -#include "aws_iot_mqtt.h" - -/** - * @ingroup platform_datatypes_handles - * @brief Opaque handle of a network connection. - * - * This type identifies a network connection, which is valid after a successful call - * to @ref platform_network_function_createconnection. A variable of this type is - * passed as the first argument to [network layer functions](@ref platform_network_functions) - * to identify which connection that function acts on. - * - * A call to @ref platform_network_function_closeconnection shuts down the network - * connection, but keeps its resources allocated. A call to - * @ref platform_network_function_destroyconnection makes a connection handle - * invalid. Once @ref platform_network_function_destroyconnection is called, the - * connection handle should no longer be used. - * - * @initializer{AwsIotNetworkConnection_t,AWS_IOT_NETWORK_CONNECTION_INITIALIZER} - */ -typedef void * AwsIotNetworkConnection_t; - -/** - * @ingroup platform_datatypes_handles - * @brief Function pointer for the [MQTT receive callback] - * (@ref mqtt_function_receivecallback). - * - * This function pointer is used to identify the function @ref - * mqtt_function_receivecallback. - */ -typedef int32_t ( * AwsIotMqttReceiveCallback_t )( AwsIotMqttConnection_t *, - const void *, - size_t, - size_t, - void ( * )( void * ) ); - -/** - * @ingroup platform_datatypes_enums - * @brief Return codes for [network functions](@ref platform_network_functions). - */ -typedef enum AwsIotNetworkError -{ - AWS_IOT_NETWORK_SUCCESS = 0, /**< Function successfully completed. */ - AWS_IOT_NETWORK_INIT_FAILED, /**< Initialization failed. Only returned by @ref platform_network_function_init. */ - AWS_IOT_NETWORK_BAD_PARAMETER, /**< At least one parameter was invalid. */ - AWS_IOT_NETWORK_NO_MEMORY, /**< Memory allocation failed. */ - AWS_IOT_NETWORK_DNS_LOOKUP_ERROR, /**< Host name could not be translated.*/ - AWS_IOT_NETWORK_CREDENTIAL_READ_ERROR, /**< Credentials could not be read. */ - AWS_IOT_NETWORK_TLS_LIBRARY_ERROR, /**< An error occurred in the system's TLS library. */ - AWS_IOT_NETWORK_SYSTEM_ERROR /**< An error occurred when calling a system function. */ -} AwsIotNetworkError_t; - -/** - * @ingroup platform_datatypes_paramstructs - * @brief Parameters to @ref platform_network_function_createconnection for setting - * up TLS. - * - * This structure provides @ref platform_network_function_createconnection with the - * necessary information to set up TLS on a TCP connection. - * - * @paramfor @ref platform_network_function_createconnection - * - * @initializer{AwsIotNetworkTlsInfo_t,AWS_IOT_NETWORK_TLS_INFO_INITIALIZER} - */ -typedef struct AwsIotNetworkTlsInfo -{ - /** - * @brief Set this to a non-NULL value to use ALPN. - * - * This string must be NULL-terminated. - * - * See [this link] - * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) - * for more information. - */ - const char * pAlpnProtos; - - /** - * @brief Set this to a non-zero value to use TLS max fragment length - * negotiation (TLS MFLN). - * - * @note The system's TLS library may have a minimum value for this parameter. - * @ref platform_network_function_createconnection may return an error if - * this parameter is too small. - */ - size_t maxFragmentLength; - - /** - * @brief Disable server name indication (SNI) for a TLS session. - */ - bool disableSni; - - /* Credentials. The exact meaning of these parameters depends on the system. - * For example, on Linux these may be used as the paths to credentials, while - * on FreeRTOS they may be the PEM-encoded credentials themselves. Note that - * some systems may ignore the "length" parameters. */ - const char * pRootCa; /**< @brief Trusted server root certificate. */ - size_t rootCaLength; /**< @brief Length of #AwsIotNetworkTlsInfo_t.pRootCa. */ - const char * pClientCert; /**< @brief Client certificate. */ - size_t clientCertLength; /**< @brief Length of #AwsIotNetworkTlsInfo_t.pClientCert. */ - const char * pPrivateKey; /**< @brief Client certificate private key. */ - size_t privateKeyLength; /**< @brief Length of #AwsIotNetworkTlsInfo_t.pPrivateKey. */ -} AwsIotNetworkTlsInfo_t; - -/** - * @brief Provides a default value for an #AwsIotNetworkConnection_t. - * - * All instances of #AwsIotNetworkConnection_t should be initialized with this - * constant. - * - * @code{c} - * AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; - * @endcode - * - * @warning Failing to initialize a network connection with this initializer - * may result in undefined behavior! - * @note This initializer may change at any time in future versions, but its - * names will remain the same. - */ -#define AWS_IOT_NETWORK_CONNECTION_INITIALIZER NULL - -/** - * @brief Provides default values for an #AwsIotNetworkTlsInfo_t. - * - * All instances of #AwsIotNetworkTlsInfo_t should be initialized with this - * constant. In most use cases, the default values should not be changed. - * - * @code{c} - * AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER; - * @endcode - * - * @warning Failing to initialize an #AwsIotNetworkTlsInfo_t with this initializer - * may result in undefined behavior! - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define AWS_IOT_NETWORK_TLS_INFO_INITIALIZER { .pAlpnProtos = "\x0ex-amzn-mqtt-ca" } - -/** - * @functionspage{platform_network,platform network component,Networking} - * - @functionname{platform_network_function_init} - * - @functionname{platform_network_function_cleanup} - * - @functionname{platform_network_function_createconnection} - * - @functionname{platform_network_function_closeconnection} - * - @functionname{platform_network_function_destroyconnection} - * - @functionname{platform_network_function_setmqttreceivecallback} - * - @functionname{platform_network_function_send} - */ - -/** - * @functionpage{AwsIotNetwork_Init,platform_network,init} - * @functionpage{AwsIotNetwork_Cleanup,platform_network,cleanup} - * @functionpage{AwsIotNetwork_CreateConnection,platform_network,createconnection} - * @functionpage{AwsIotNetwork_CloseConnection,platform_network,closeconnection} - * @functionpage{AwsIotNetwork_DestroyConnection,platform_network,destroyconnection} - * @functionpage{AwsIotNetwork_SetMqttReceiveCallback,platform_network,setmqttreceivecallback} - * @functionpage{AwsIotNetwork_Send,platform_network,send} - */ - -/** - * @brief One-time initialization function for the platform networking component. - * - * This function performs internal setup of the platform networking component. - * It must be called once (and only once) before calling any other platform - * networking function. Calling this function more than once without first calling - * @ref platform_network_function_cleanup may result in a crash. - * - * @return #AWS_IOT_NETWORK_SUCCESS or #AWS_IOT_NETWORK_INIT_FAILED. - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see @ref platform_network_function_cleanup - * - * @code{c} - * // Before calling any other network function. - * if( AwsIotNetwork_Init() == AWS_IOT_NETWORK_SUCCESS ) - * { - * // Do something with the network library... - * - * // After the network is no longer needed. - * AwsIotNetwork_Cleanup(); - * } - * else - * { - * // Initialization failed. - * } - * @endcode - */ -/* @[declare_platform_network_init] */ -AwsIotNetworkError_t AwsIotNetwork_Init( void ); -/* @[declare_platform_network_init] */ - -/** - * @brief One-time deinitialization function for the platform networking component. - * - * This function frees resources taken in @ref platform_network_function_init. - * It should be called after [destroying all network connections] - * (@ref platform_network_function_destroyconnection) to clean up the platform - * networking component. After this function returns, @ref platform_network_function_init - * must be called again before calling any other platform networking function. - * - * @warning No thread-safety guarantees are provided for this function. Do not - * call this function if any network connections exist! - * - * @see @ref platform_network_function_init - */ -/* @[declare_platform_network_cleanup] */ -void AwsIotNetwork_Cleanup( void ); -/* @[declare_platform_network_cleanup] */ - -/** - * @brief Open a TCP connection to a remote server with an option for TLS. - * - * This function opens a TCP connection to a server on a given port. If security - * is needed, parameter `pTlsInfo` may be passed to set up TLS on the TCP connection. - * - * @param[out] pNetworkConnection The handle by which this new connection will - * be referenced. - * @param[in] pHostName Host name for connection. Must be null-terminated. - * @param[in] port Port (in host-order) for connection. - * @param[in] pTlsInfo TLS setup parameters. Optional; pass `NULL` for an unsecured - * connection. - * - * @return Any #AwsIotNetworkError_t except #AWS_IOT_NETWORK_INIT_FAILED or - * #AWS_IOT_NETWORK_SYSTEM_ERROR. - * - * @see @ref platform_network_function_closeconnection, - * @ref platform_network_function_destroyconnection - * - * @code{c} - * // Network connection handle and TLS setup parameters. - * AwsIotNetworkConnection_t networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; - * AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER; - * - * // Example server. - * const char * const pHostName = "test.mosquitto.org"; - * uint16_t port = 8883; - * - * // Set the TLS setup credentials. The exact format of these credentials depends - * // on the system. - * tlsInfo.pRootCa = ROOT_CA; - * tlsInfo.rootCaLength = ROOT_CA_LENGTH; - * tlsInfo.pClientCert = CLIENT_CERT; - * tlsInfo.clientCertLength = CLIENT_CERT_LENGTH; - * tlsInfo.pPrivateKey = PRIVATE_KEY; - * tlsInfo.privateKeyLength = PRIVATE_KEY_LENGTH; - * - * // Returns AWS_IOT_NETWORK_SUCCESS and sets networkConnection when the - * // connection is established. - * AwsIotNetworkError_t result = AwsIotNetwork_CreateConnection( &networkConnection, - * pHostName, - * port, - * &tlsInfo ); - * - * // Do something with the connection... - * - * // Close the connection. - * AwsIotNetwork_CloseConnection( networkConnection ); - * - * // Destroy the network connection. This should only be called once the - * // connection is guaranteed to be unused. - * AwsIotNetwork_DestroyConnection( networkConnection ); - * @endcode - */ -/* @[declare_platform_network_createconnection] */ -AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * const pNetworkConnection, - const char * const pHostName, - uint16_t port, - const AwsIotNetworkTlsInfo_t * const pTlsInfo ); -/* @[declare_platform_network_createconnection] */ - -/** - * @brief Close a network connection. - * - * This function closes the TCP socket and cleans up TLS. However, the resources - * for the connection are not released by this function. This allows calls to - * @ref platform_network_function_send (or similar functions) to receive an error - * and handle a closed connection without the risk of crashing. Once it can be - * guaranteed that the network connection won't be used further, it can be destroyed - * with @ref platform_network_function_destroyconnection. - * - * Calling this function also removes any registered receive callback. - * - * @param[in] networkConnection The handle of the connection to close. - * - * @note It must be safe to call this function on an already-closed connection. - * - * @see @ref platform_network_function_createconnection, - * @ref platform_network_function_destroyconnection - */ -/* @[declare_platform_network_closeconnection] */ -void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection ); -/* @[declare_platform_network_closeconnection] */ - -/** - * @brief Free resources in use by a connection. - * - * This function releases the resources used by a closed connection. It should - * be called after @ref platform_network_function_closeconnection. - * - * @param[in] networkConnection The connection to destroy. - * - * @see @ref platform_network_function_createconnection, - * @ref platform_network_function_closeconnection - * - * @warning Using the network connection after calling this function will likely - * result in a crash! - */ -/* @[declare_platform_network_destroyconnection] */ -void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnection ); -/* @[declare_platform_network_destroyconnection] */ - -/** - * @brief Register the [MQTT callback function](@ref mqtt_function_receivecallback) - * to be called when MQTT data is read from the network. - * - * The [MQTT library](@ref mqtt) expects to be asynchronously notified of incoming - * network data. This function creates a thread that will notify the MQTT library - * of incoming data. - * - * @param[in] networkConnection The handle of the connection to receive from. - * @param[in] pMqttConnection Pointer to the MQTT connection to pass to the MQTT - * callback. - * @param[in] receiveCallback @ref mqtt_function_receivecallback - * - * @return #AWS_IOT_NETWORK_SUCCESS or #AWS_IOT_NETWORK_SYSTEM_ERROR. - * - * @see @ref mqtt_function_receivecallback - * - * @code{c} - * // MQTT network receive callback. - * int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, - * const void * pReceivedData, - * size_t offset, - * size_t dataLength, - * void ( * freeReceivedData )( void * ) ); - * - * void setCallback( AwsIotNetworkConnection_t networkConnection, AwsIotMqttConnection_t * pMqttConnection ) - * { - * // Returns AWS_IOT_NETWORK_SUCCESS when the callback is successfully set. - * AwsIotNetworkError_t result = AwsIotNetwork_SetMqttReceiveCallback( networkConnection, - * pMqttConnection, - * AwsIotMqtt_ReceiveCallback ); - * } - * @endcode - */ -/* @[declare_platform_network_setmqttreceivecallback] */ -AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnection_t networkConnection, - AwsIotMqttConnection_t * pMqttConnection, - AwsIotMqttReceiveCallback_t receiveCallback ); -/* @[declare_platform_network_setmqttreceivecallback] */ - -/** - * @brief Sends data over a connection. - * - * Attempts to transmit `messageLength` bytes of `pMessage` across - * `networkConnection`. Returns the number of bytes actually sent, `0` on - * failure. - * - * @param[in] networkConnection The handle of the connection used to send data. - * For compatibility with other network implementations, this is a void*. - * @param[in] pMessage The message to send. - * @param[in] messageLength The length of pMessage. - * - * @return The number of bytes successfully sent, `0` on failure. - */ -/* @[declare_platform_network_send] */ -size_t AwsIotNetwork_Send( void * networkConnection, - const void * const pMessage, - size_t messageLength ); -/* @[declare_platform_network_send] */ - -#endif /* ifndef _AWS_IOT_NETWORK_H_ */ diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h new file mode 100644 index 0000000000..1a4735b662 --- /dev/null +++ b/lib/include/platform/iot_network.h @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_network.h + * @brief Abstraction of network functions used by libraries in this SDK. + */ + +#ifndef _IOT_NETWORK_H_ +#define _IOT_NETWORK_H_ + +/* Standard includes. */ +#include + +/** + * @ingroup platform_datatypes_enums + * @brief Return codes for [network functions](@ref platform_network_functions). + */ +typedef enum IotNetworkError +{ + IOT_NETWORK_SUCCESS = 0, /**< Function successfully completed. */ + IOT_NETWORK_FAILURE, /**< Generic failure not covered by other values. */ + IOT_NETWORK_BAD_PARAMETER, /**< At least one parameter was invalid. */ + IOT_NETWORK_NO_MEMORY, /**< Memory allocation failed. */ + IOT_NETWORK_SYSTEM_ERROR /**< An error occurred when calling a system API. */ +} IotNetworkError_t; + +/** + * @page platform_network_functions Networking + * @brief Functions of the network abstraction component. + * + * The network abstraction component does not declare functions, but uses function + * pointers instead. This allows multiple network stacks to be used at the same time. + * Libraries that require the network will request an #IotNetworkInterface_t + * parameter. The members of the #IotNetworkInterface_t will be called whenever + * the library interacts with the network. + * + * The following function pointers are associated with an #IotNetworkInterface_t. + * Together, they represent a network stack. + * - @functionname{platform_network_function_create} + * - @functionname{platform_network_function_setreceivecallback} + * - @functionname{platform_network_function_send} + * - @functionname{platform_network_function_receive} + * - @functionname{platform_network_function_close} + * - @functionname{platform_network_function_closecallback} + * - @functionname{platform_network_function_destroy} + * - @functionname{platform_network_function_receivecallback} + */ + +/** + * @functionpage{IotNetworkInterface_t::create,platform_network,create} + * @functionpage{IotNetworkInterface_t::setReceiveCallback,platform_network,setreceivecallback} + * @functionpage{IotNetworkInterface_t::send,platform_network,send} + * @functionpage{IotNetworkInterface_t::receive,platform_network,receive} + * @functionpage{IotNetworkInterface_t::close,platform_network,close} + * @functionpage{IotNetworkInterface_t::closeCallback,platform_network,closecallback} + * @functionpage{IotNetworkInterface_t::destroy,platform_network,destroy} + * @functionpage{IotNetworkReceiveCallback_t,platform_network,receivecallback} + */ + +/** + * @brief Provide an asynchronous notification of incoming network data. + * + * A function with this signature may be set with @ref platform_network_function_setreceivecallback + * to be invoked when data is available on the network. + * + * An important concept associated with this function is buffer ownership. + * Normally, once `pReceivedData` is passed to this function, it is considered + * property of the library that set the receive callback. The network stack + * must keep `pReceivedData` valid and in-scope even after this function returns, + * and must not make any changes to `pReceivedData` until `freeReceivedData` is called + * (which transfers ownership of `pReceivedData` back to the network stack). + * + * If `freeReceivedData` is `NULL`, then ownership of `pReceivedData` will always + * revert to the network stack as soon as the library's callback function returns. + * In this scenario, the library must copy any data it requires out of `pReceivedData`. + * Therefore, passing `NULL` for `freeReceivedData` may increase memory usage. + * + * @param[in] pContext The third argument passed to @ref platform_network_function_setreceivecallback. + * @param[in] pConnection The connection on which data was received, defined by + * the network stack. + * @param[in] pReceivedData Buffer holding data received from the network. This buffer + * must remain valid and in-scope until `freeReceivedData` is called if this function + * returns a value of `dataLength-offset`. + * @param[in] receivedDataLength The length of the buffer `pReceivedData`. + * @param[in] offset The offset (in bytes) into `pReceivedData` where the received data + * begins. The received data ranges from `pReceivedData+offset` to `pReceivedData+dataLength`. + * Pass `0` to start from the beginning of `pReceivedData`. + * @param[in] freeReceivedData The function to call when finished with `pReceivedData`. + * Its parameter must be `pReceivedData`. Pass `NULL` to ignore. + * + * @return + * The network stack must respond to this function's return value as follows: + * - This function returns `-1` to indicate a fatal, unrecoverable error. The network + * connection should no longer be used. The receive callback will not call `freeReceivedData` + * if it returns `-1`. + * - This function returns a positive value less than `dataLength-offset` to indicate + * that the received data was incomplete in some way. The network stack should keep + * the data in `pReceivedBuffer` and wait more data to arrive. The receive callback should + * be called again with this additional data. The receive callback will not call + * `freeReceivedData` in this case. + * - This function returns `dataLength-offset` to indicate all received data was + * processed. The buffer `pReceivedData` must remain valid and in-scope until + * `freeReceivedData` is called. + * + * @attention The function @ref platform_network_function_receive must not be + * called if a receive callback is active! + */ +/* @[declare_platform_network_receivecallback] */ +typedef int32_t ( * IotNetworkReceiveCallback_t )( void * pContext, + void * pConnection, + const uint8_t * pReceivedData, + size_t receivedDataLength, + size_t offset, + void ( * freeReceivedData )( void * ) ); +/* @[declare_platform_network_receivecallback] */ + +/** + * @ingroup platform_datatypes_paramstructs + * @brief Represents the functions of a network stack. + * + * Functions that match these signatures should be implemented against a system's + * network stack. See the `platform` directory for existing implementations. + */ +typedef struct IotNetworkInterface +{ + /** + * @brief Create a new network connection. + * + * This function allocates resources and establishes a new network connection. + * @param[in] pConnectionInfo Represents information needed to set up the + * new connection, defined by the network stack. + * @param[in] pCredentialInfo Represents information needed to secure the + * new connection, defined by the network stack. + * @param[out] pConnection Set to represent a new connection, defined by the + * network stack. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + */ + /* @[declare_platform_network_create] */ + IotNetworkError_t ( * create )( void * pConnectionInfo, + void * pCredentialInfo, + void * const pConnection ); + /* @[declare_platform_network_create] */ + + /** + * @brief Register an @ref platform_network_function_receivecallback. + * + * Sets an @ref platform_network_function_receivecallback to be called + * asynchronously when data arrives on the network. The network stack + * should invoke this function "as if" it were the thread routine of a + * detached thread. + * + * Each network connection may only have one receive callback at any time. + * If this function is called for a connection that already has a receive + * callback, the existing callback should be replaced. If the `receiveCallback` + * parameter is `NULL`, any existing receive callback should be removed. In + * addition, @ref platform_network_function_close is expected to remove any + * active receive callbacks. + * + * @param[in] pConnection The connection to associate with the receive callback. + * @param[in] receiveCallback The function to invoke for incoming network data. + * @param[in] pContext A value to pass as the first parameter to the receive callback. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @attention @ref platform_network_function_receive must not be called if + * a receive callback is set! + * + * @see platform_network_function_receivecallback + */ + /* @[declare_platform_network_setreceivecallback] */ + IotNetworkError_t ( * setReceiveCallback )( void * pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ); + /* @[declare_platform_network_setreceivecallback] */ + + /** + * @brief Send data over a return connection. + * + * Attempts to transmit `messageLength` bytes of `pMessage` across the + * connection represented by `pConnection`. Returns the number of bytes + * actually sent, `0` on failure. + * + * @param[in] pConnection The connection used to send data, defined by the + * network stack. + * @param[in] pMessage The message to send. + * @param[in] messageLength The length of `pMessage`. + * + * @return The number of bytes successfully sent, `0` on failure. + */ + /* @[declare_platform_network_send] */ + size_t ( * send )( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ); + /* @[declare_platform_network_send] */ + + /** + * @brief Block and wait for incoming network data. + * + * Wait for a message of size `bytesRequested` to arrive on the network and + * place it in `pBuffer`. + * + * @param[in] pConnection The connection to wait on, defined by the network + * stack. + * @param[out] pBuffer Where to place the incoming network data. This buffer + * must be at least `bytesRequested` in size. + * @param[in] bytesRequested How many bytes to wait for. `pBuffer` must be at + * least this size. + * + * @return The number of bytes successfully received. This should be + * `bytesRequested` when successful. Any other value may indicate an error. + * + * @attention This function must not be called if a + * [receive callback](@ref platform_network_function_receivecallback) is active! + */ + /* @[declare_platform_network_receive] */ + size_t ( * receive )( void * pConnection, + uint8_t * const pBuffer, + size_t bytesRequested ); + /* @[declare_platform_network_receive] */ + + /** + * @brief Close a network connection. + * + * This function closes the connection, but does not release the resources + * used by the connection. This allows calls to other networking functions + * to return an error and handle a closed connection without the risk of + * crashing. Once it can be guaranteed that `pConnection` will no longer be + * used, the connection can be destroyed with @ref platform_network_function_destroy. + * + * In addition to closing the connection, this function should also remove + * any active [receive callback](@ref platform_network_function_receivecallback). + * + * @param[in] pConnection The network connection to close, defined by the + * network stack. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @note It must be safe to call this function on an already-closed connection. + */ + /* @[declare_platform_network_close] */ + IotNetworkError_t ( * close )( void * pConnection ); + /* @[declare_platform_network_close] */ + + /** + * @brief Callback function to invoke after @ref platform_network_function_close + * is called. + * + * @ref platform_network_function_close instructs the underlying network stack to + * close a connection. In constrast, this function can be used to inform a higher-level + * application that a connection was closed. + * + * @param[in] reason Meant to convey the reason a connection was closed; meaningful + * values should be defined by the library closing the connection. + * @param[in] pContext A context defined by the library closing the connection. + * @param[in] pConnection The connection that was closed. + */ + /* @[declare_platform_network_closecallback] */ + void ( * closeCallback )( int32_t reason, + void * pContext, + void * pConnection ); + /* @[declare_platform_network_closecallback] */ + + /** + * @brief Free resources used by a network connection. + * + * This function releases the resources of a closed connection. It should be + * called after @ref platform_network_function_close. + * + * @param[in] pConnection The network connection to destroy, defined by + * the network stack. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @attention No function should be called on the network connection after + * calling this function. + */ + /* @[declare_platform_network_destroy] */ + IotNetworkError_t ( * destroy )( void * pConnection ); + /* @[declare_platform_network_destroy] */ +} IotNetworkInterface_t; + +#endif /* ifndef _IOT_NETWORK_H_ */ diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c index b2fa69c259..8cd4316018 100644 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ b/lib/source/mqtt/aws_iot_mqtt_operation.c @@ -362,7 +362,7 @@ static void _invokeCallback( _mqttOperation_t * const pOperation ) /* Free any buffers associated with the current PUBLISH message. */ if( pCurrent->freeReceivedData != NULL ) { - AwsIotMqtt_Assert( pCurrent->pReceivedData != NULL ); + AwsIotMqtt_Assert( pCurrent->freeReceivedData != NULL ); pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); } diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index d3b2af5a4e..88e0a74993 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -106,19 +106,23 @@ static void _sendPuback( _mqttConnection_t * const pMqttConnection, /*-----------------------------------------------------------*/ -int32_t AwsIotMqtt_ReceiveCallback( AwsIotMqttConnection_t * pMqttConnection, - const void * pReceivedData, - size_t offset, +int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, + void * pConnection, + const uint8_t * pReceivedData, size_t dataLength, - void ( *freeReceivedData )( void * ) ) + size_t offset, + void( *freeReceivedData )( void * ) ) { size_t bytesProcessed = 0, totalBytesProcessed = 0, remainingDataLength = 0; _mqttConnection_t * pConnectionInfo = *( ( _mqttConnection_t ** ) ( pMqttConnection ) ); AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; uint16_t packetIdentifier = 0; - const uint8_t * pNextPacket = ( const uint8_t * ) pReceivedData; + const uint8_t * pNextPacket = pReceivedData; _mqttOperation_t * pOperation = NULL, * pFirstPublish = NULL, * pLastPublish = NULL; + /* Network connection parameter is ignored. */ + ( void ) pConnection; + /* Choose a packet type decoder function. */ uint8_t ( * getPacketType )( const uint8_t * const, size_t ) = AwsIotMqttInternal_GetPacketType; diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h new file mode 100644 index 0000000000..d6d51be351 --- /dev/null +++ b/platform/include/posix/iot_network_openssl.h @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_network_openssl.h + * @brief Declares the network stack functions specified in iot_network.h for + * POSIX systems with OpenSSL. + */ + +#ifndef _IOT_NETWORK_OPENSSL_H_ +#define _IOT_NETWORK_OPENSSL_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* POSIX types include. */ +#ifdef POSIX_TYPES_HEADER + #include POSIX_TYPES_HEADER +#else + #include +#endif + +/* OpenSSL types include. */ +#include + +/* Platform types include. */ +#include "types/iot_platform_types.h" + +/* Platform network include. */ +#include "platform/iot_network.h" + +/** + * @brief Represents a network connection that uses OpenSSL. + * + * All instances of #IotNetworkConnectionOpenssl_t should be initialized with + * #IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER. + * + * @attention The members of this struct are intended to be opaque and may change + * at any time. This struct should only be passed as the connection handle to the + * functions declared in this file. Do not directly modify its members! + */ +typedef struct IotNetworkConnectionOpenssl +{ + int socket; /**< @brief Socket associated with this connection. */ + SSL * pSslContext; /**< @brief SSL context for connection. */ + IotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ + + /** @brief Status of the receive thread for this connection. */ + enum + { + _NONE = 0, _ACTIVE, _TERMINATED + } receiveThreadStatus; + pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ + + IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ + void * pCallbackContext; /**< @brief The first parameter to pass to the receive callback. */ +} IotNetworkConnectionOpenssl_t; + +/** + * @brief Information on the remote server for connection setup. + * + * Passed to #IotNetworkOpenssl_Create as `pConnectionInfo`. + * + * All instances of #IotNetworkServerInfoOpenssl_t should be initialized with + * #IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER. + */ +typedef struct IotNetworkServerInfoOpenssl +{ + const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ + uint16_t port; /**< @brief Server port in host-order. */ +} IotNetworkServerInfoOpenssl_t; + +/** + * @brief Contains the credentials necessary for connection setup with OpenSSL. + * + * Passed to #IotNetworkOpenssl_Create as `pCredentialInfo`. + * + * All instances of #IotNetworkCredentialsOpenssl should be initialized with either + * #AWS_IOT_NETWORK_CREDENTIALS_OPENSSL (for connections to AWS IoT) or + * #IOT_NETWORK_CREDENTIALS_OPENSSL (for other connections). + */ +typedef struct IotNetworkCredentialsOpenssl +{ + /** + * @brief Set this to a non-NULL value to use ALPN. + * + * This string must be NULL-terminated. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char * pAlpnProtos; + + /** + * @brief Set this to a non-zero value to use TLS max fragment length + * negotiation (TLS MFLN). + * + * @note OpenSSL may have a minimum value for this parameter; + * #IotNetworkOpenssl_Create may return an error if this parameter is too small. + */ + size_t maxFragmentLength; + + /** + * @brief Disable server name indication (SNI) for a TLS session. + */ + bool disableSni; + + /* These paths must be NULL-terminated. */ + const char * pRootCaPath; /**< @brief Filesystem path of a trusted server root certificate. */ + const char * pClientCertPath; /**< @brief Filesystem path of the client certificate. */ + const char * pPrivateKeyPath; /**< @brief Filesystem path of the client certificate's private key. */ +} IotNetworkCredentialsOpenssl_t; + +/** + * @brief Provides a default value for an #IotNetworkConnectionOpenssl_t. + * + * All instances of #IotNetworkConnectionOpenssl_t should be initialized with + * this constant. + * + * @warning Failing to initialize an #IotNetworkConnectionOpenssl_t with this + * initializer may result in undefined behavior! + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER { 0 } + +/** + * @brief Provides a default value for an #IotNetworkServerInfoOpenssl_t. + * + * All instances of #IotNetworkServerInfoOpenssl_t should be initialized with + * this constant. + * + * @warning Failing to initialize an #IotNetworkServerInfoOpenssl_t with this + * initializer may result in undefined behavior! + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER { 0 } + +/** + * @brief Initialize an #IotNetworkCredentialsOpenssl_t for AWS IoT with ALPN enabled. + * + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER \ + { \ + .pAlpnProtos = "\x0ex-amzn-mqtt-ca" \ + } + +/** + * @brief Generic initializer for an #IotNetworkCredentialsOpenssl_t. + * + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER { 0 } + +/** + * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions + * declared in this file. + */ +#define IOT_NETWORK_INTERFACE_OPENSSL ( &( _IotNetworkOpenssl ) ) + +/** + * @brief One-time initialization function for this network stack. + * + * This function performs internal setup of this network stack. It must be + * called once (and only once) before calling any other function in this network + * stack. Calling this function more than once without first calling + * #IotNetworkOpenssl_Cleanup may result in a crash. + * + * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_FAILURE. + * + * @warning No thread-safety guarantees are provided for this function. + */ +IotNetworkError_t IotNetworkOpenssl_Init( void ); + +/** + * @brief One-time deinitialization function for this network stack. + * + * This function frees resources taken in #IotNetworkOpenssl_Init. It should be + * called after destroying all network connections to clean up this network + * stack. After this function returns, #IotNetworkOpenssl_Init must be called + * again before calling any other function in this network stack. + * + * @warning No thread-safety guarantees are provided for this function. Do not + * call this function if any network connections exist! + */ +void IotNetworkOpenssl_Cleanup( void ); + +/** + * @brief An implementation of #IotNetworkInterface_t::create for POSIX systems + * with OpenSSL. + */ +IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, + void * pCredentialInfo, + void * const pConnection ); + +/** + * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for + * POSIX systems with OpenSSL. + */ +IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ); + +/** + * @brief An implementation of #IotNetworkInterface_t::send for POSIX systems + * with OpenSSL. + */ +size_t IotNetworkOpenssl_Send( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ); + +/** + * @brief An implementation of #IotNetworkInterface_t::receive for POSIX systems + * with OpenSSL. + */ +size_t IotNetworkOpenssl_Receive( void * pConnection, + uint8_t * const pBuffer, + size_t bytesRequested ); + +/** + * @brief An implementation of #IotNetworkInterface_t::close for POSIX systems + * with OpenSSL. + */ +IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ); + +/** + * @brief An implementation of #IotNetworkInterface_t::destroy for POSIX systems + * with OpenSSL. + */ +IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Declaration of a network interface struct using the functions in this file. + */ +extern const IotNetworkInterface_t _IotNetworkOpenssl; +/** @endcond */ + +#endif /* ifndef _IOT_NETWORK_OPENSSL_H_ */ diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index cfacc29a7b..4c63de13a5 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -52,8 +52,8 @@ #include #include -/* Network header include. */ -#include "platform/aws_iot_network.h" +/* POSIX+OpenSSL network include. */ +#include "posix/iot_network_openssl.h" /* Platform threads include. */ #include "platform/iot_threads.h" @@ -98,49 +98,17 @@ /*-----------------------------------------------------------*/ /** - * @brief Statuses for receive threads. + * @brief An #IotNetworkInterface_t that uses the functions in this file. */ -typedef enum _threadStatus +const IotNetworkInterface_t _IotNetworkOpenssl = { - _NONE = 0, /**< Any previously created thread is fully cleaned up. */ - _ACTIVE, /**< A receive thread is currently running. */ - _TERMINATED /**< A receive thread is terminated and needs to be joined. */ -} _threadStatus_t; - -/** - * @brief Connection info, which is pointed to by the connection handle. - * - * Holds data on a single connection. - */ -typedef struct _connectionInfo -{ - int tcpSocket; /**< @brief Socket associated with connection. */ - SSL * pSSLConnectionContext; /**< @brief SSL context for connection. */ - IotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ - _threadStatus_t receiveThreadStatus; /**< @brief Status of the receive thread for this connection. */ - pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ - - AwsIotMqttReceiveCallback_t mqttReceiveCallback; /**< @brief Function to call when MQTT data is received, if any. */ - AwsIotMqttConnection_t * pMqttConnection; /**< @brief The first parameter passed to #_connectionInfo_t.mqttReceiveCallback. */ -} _connectionInfo_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Signal handler for SIGPIPE. - * - * Currently, this function just prevents SIGPIPE from terminating the program. - * - * @param[in] signal Should always be SIGPIPE. - */ -static void _sigpipeHandler( int signal ) -{ - ( void ) signal; - - /* This signal handler doesn't do anything, it just prevents SIGPIPE from - * terminating this process. SIGPIPE will cause any receive threads to - * terminate, and any further calls to AwsIotNetwork_Send will fail. */ -} + .create = IotNetworkOpenssl_Create, + .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .send = IotNetworkOpenssl_Send, + .receive = IotNetworkOpenssl_Receive, + .close = IotNetworkOpenssl_Close, + .destroy = IotNetworkOpenssl_Destroy +}; /*-----------------------------------------------------------*/ @@ -148,7 +116,7 @@ static void _sigpipeHandler( int signal ) * @brief Network receive thread. * * This thread polls the network socket and reads data when data is available. - * It then invokes the MQTT receive callback, if any. + * It then invokes the receive callback, if any. * * @param[in] pArgument The connection associated with this receive thread. */ @@ -156,57 +124,61 @@ static void * _networkReceiveThread( void * pArgument ) { int bytesAvailable = 0, bytesRead = 0; int32_t bytesProcessed = 0; - _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) pArgument; - void * pReceiveBuffer = NULL; - struct pollfd fileDescriptors = + uint8_t * pReceiveBuffer = NULL; + struct pollfd fileDescriptor = { - .fd = pConnectionInfo->tcpSocket, .events = POLLIN, .revents = 0 }; + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pArgument; + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = pNetworkConnection->socket; + /* Continuously poll the network socket for events. */ - while( poll( &fileDescriptors, 1, -1 ) == 1 ) + while( poll( &fileDescriptor, 1, -1 ) == 1 ) { /* Prevent this thread from being cancelled while running. But if the * connection mutex is locked, wait until it is available. */ - if( IotMutex_TryLock( &( pConnectionInfo->mutex ) ) == false ) + if( IotMutex_TryLock( &( pNetworkConnection->mutex ) ) == false ) { continue; } /* If an error event is detected, terminate this thread. */ - if( ( fileDescriptors.revents & POLLERR ) || - ( fileDescriptors.revents & POLLHUP ) || - ( fileDescriptors.revents & POLLNVAL ) ) + if( ( fileDescriptor.revents & POLLERR ) || + ( fileDescriptor.revents & POLLHUP ) || + ( fileDescriptor.revents & POLLNVAL ) ) { IotLogError( "Detected error on socket %d, receive thread exiting.", - pConnectionInfo->tcpSocket ); + pNetworkConnection->socket ); break; } /* Query how many bytes are available to read. The value returned by this * function may include more than just the data. */ - if( ioctl( pConnectionInfo->tcpSocket, FIONREAD, &bytesAvailable ) != 0 ) + if( ioctl( pNetworkConnection->socket, FIONREAD, &bytesAvailable ) != 0 ) { IotLogError( "Failed to query bytes available on socket. errno=%d.", errno ); /* Unlock the connection mutex before polling again. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); continue; } IotLogDebug( "%d bytes available on socket %d.", bytesAvailable, - pConnectionInfo->tcpSocket ); + pNetworkConnection->socket ); /* If no bytes can be read, the socket is likely closed. Terminate this thread. */ if( bytesAvailable == 0 ) { IotLogInfo( "No data available, terminating receive thread for socket %d.", - pConnectionInfo->tcpSocket ); + pNetworkConnection->socket ); break; } @@ -217,25 +189,24 @@ static void * _networkReceiveThread( void * pArgument ) { IotLogError( "Failed to allocate %d bytes for socket read on %d.", bytesAvailable, - pConnectionInfo->tcpSocket ); - - /* Unlock the connection mutex before polling again. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); + pNetworkConnection->socket ); - continue; + /* Terminate on memory allocation failure to prevent unread incoming + * data from accumulating. */ + break; } /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on * whether the SSL connection context is set up. */ - if( pConnectionInfo->pSSLConnectionContext != NULL ) + if( pNetworkConnection->pSslContext != NULL ) { - bytesRead = SSL_read( pConnectionInfo->pSSLConnectionContext, + bytesRead = SSL_read( pNetworkConnection->pSslContext, pReceiveBuffer, bytesAvailable ); } else { - bytesRead = ( int ) recv( pConnectionInfo->tcpSocket, + bytesRead = ( int ) recv( pNetworkConnection->socket, pReceiveBuffer, ( size_t ) bytesAvailable, 0 ); @@ -245,7 +216,7 @@ static void * _networkReceiveThread( void * pArgument ) if( bytesRead <= 0 ) { IotLogError( "Error reading from socket %d.", - pConnectionInfo->tcpSocket ); + pNetworkConnection->socket ); IotNetwork_Free( pReceiveBuffer ); break; @@ -253,17 +224,18 @@ static void * _networkReceiveThread( void * pArgument ) IotLogDebug( "Received %d bytes from socket %d.", bytesRead, - pConnectionInfo->tcpSocket ); + pNetworkConnection->socket ); /* Invoke the callback function. But if there's no callback to invoke, * terminate this thread. */ - if( pConnectionInfo->mqttReceiveCallback != NULL ) + if( pNetworkConnection->receiveCallback != NULL ) { - bytesProcessed = pConnectionInfo->mqttReceiveCallback( pConnectionInfo->pMqttConnection, - pReceiveBuffer, - 0, - ( size_t ) bytesRead, - IotNetwork_Free ); + bytesProcessed = pNetworkConnection->receiveCallback( pNetworkConnection->pCallbackContext, + pNetworkConnection, + pReceiveBuffer, + ( size_t ) bytesRead, + 0, + IotNetwork_Free ); } else { @@ -273,28 +245,41 @@ static void * _networkReceiveThread( void * pArgument ) break; } - /* Check the return value of the MQTT callback. */ - if( bytesProcessed < 0 ) + /* Free resources and terminate thread if some data was unprocessed. */ + if( bytesProcessed != ( int32_t ) bytesRead ) { - IotLogError( "Detected MQTT protocol violation. Receive thread for " - "socket %d terminating.", pConnectionInfo->tcpSocket ); + if( bytesProcessed < 0 ) + { + IotLogError( "Receive callback for socket %d returned error." + "Receive thread terminating.", pNetworkConnection->socket ); + } + else + { + /* This implementation should never read incomplete packets + * because it is able to allocate buffers large enough for + * entire streams. */ + IotLogError( "Incomplete data received from network on socket %d.", + pNetworkConnection->socket ); + } + + IotNetwork_Free( pReceiveBuffer ); break; } /* Unlock the connection mutex before polling again. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); } IotLogDebug( "Network receive thread for socket %d terminating.", - pConnectionInfo->tcpSocket ); + pNetworkConnection->socket ); /* Clear data about the callback. */ - pConnectionInfo->receiveThreadStatus = _TERMINATED; - pConnectionInfo->mqttReceiveCallback = NULL; - pConnectionInfo->pMqttConnection = NULL; + pNetworkConnection->receiveThreadStatus = _TERMINATED; + pNetworkConnection->receiveCallback = NULL; + pNetworkConnection->pCallbackContext = NULL; /* Unlock the connection mutex before exiting. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); return NULL; } @@ -304,22 +289,20 @@ static void * _networkReceiveThread( void * pArgument ) /** * @brief Perform a DNS lookup of a host name and establish a TCP connection. * - * @param[in] pHostName The host name to look up. - * @param[in] port Remote server port (in host byte order) for the TCP connection. + * @param[in] pServerInfo Server host name and port. * - * @return A connected TCP socket number; -1 if the DNS lookup failed. + * @return A connected TCP socket number; `-1` if the DNS lookup failed. */ -static inline int _dnsLookup( const char * const pHostName, - uint16_t port ) +static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServerInfo ) { int status = 0, tcpSocket = -1; - const uint16_t netPort = htons( port ); + const uint16_t netPort = htons( pServerInfo->port ); struct addrinfo * pListHead = NULL, * pAddressInfo = NULL; struct sockaddr_in * pServer = NULL; - /* Perform a DNS lookup of pHostName. */ - IotLogDebug( "Performing DNS lookup of %s", pHostName ); - status = getaddrinfo( pHostName, NULL, NULL, &pListHead ); + /* Perform a DNS lookup of host name. */ + IotLogDebug( "Performing DNS lookup of %s", pServerInfo->pHostName ); + status = getaddrinfo( pServerInfo->pHostName, NULL, NULL, &pListHead ); if( status != 0 ) { @@ -382,7 +365,7 @@ static inline int _dnsLookup( const char * const pHostName, { IotLogError( "Failed to connect to all retrieved DNS records." ); - return -1; + tcpSocket = -1; } return tcpSocket; @@ -399,81 +382,102 @@ static inline int _dnsLookup( const char * const pHostName, * @param[in] pRootCAPath Path to the root CA certificate. * @param[in] pClientCertPath Path to the client certificate. * @param[in] pCertPrivateKeyPath Path to the client certificate private key. + * + * @return `true` if all credentials were successfully read; `false` otherwise. */ static inline bool _readCredentials( SSL_CTX * pSSLContext, const char * const pRootCAPath, const char * const pClientCertPath, const char * const pCertPrivateKeyPath ) { - X509 * pRootCA = NULL; + bool status = true; + X509 * pRootCa = NULL; /* OpenSSL does not provide a single function for reading and loading certificates * from files into stores, so the file API must be called. */ IotLogDebug( "Opening root certificate %s", pRootCAPath ); - FILE * pRootCAFile = fopen( pRootCAPath, "r" ); + FILE * pRootCaFile = fopen( pRootCAPath, "r" ); - if( pRootCAFile == NULL ) + if( pRootCaFile == NULL ) { IotLogError( "Failed to open %s", pRootCAPath ); - return false; + status = false; } /* Read the root CA into an X509 object, then close its file handle. */ - pRootCA = PEM_read_X509( pRootCAFile, NULL, NULL, NULL ); - - if( fclose( pRootCAFile ) != 0 ) + if( status == true ) { - IotLogWarn( "Failed to close file %s", pRootCAPath ); - } + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - if( pRootCA == NULL ) - { - IotLogError( "Failed to parse root CA." ); + if( fclose( pRootCaFile ) != 0 ) + { + IotLogWarn( "Failed to close file %s", pRootCAPath ); + } - return false; - } + if( pRootCa == NULL ) + { + IotLogError( "Failed to parse root CA." ); - /* Add the root CA to certificate store. */ - if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSSLContext ), - pRootCA ) != 1 ) - { - IotLogError( "Failed to add root CA to certificate store." ); + status = false; + } + else + { + /* Add the root CA to certificate store. */ + if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSSLContext ), + pRootCa ) != 1 ) + { + IotLogError( "Failed to add root CA to certificate store." ); - return false; - } + status = false; + } + else + { + IotLogInfo( "Successfully imported root CA." ); + } - /* Free the root CA object. */ - X509_free( pRootCA ); - IotLogInfo( "Successfully imported root CA." ); + /* Free the root CA object. */ + X509_free( pRootCa ); + } + } /* Import the client certificate. */ - if( SSL_CTX_use_certificate_file( pSSLContext, - pClientCertPath, - SSL_FILETYPE_PEM ) != 1 ) + if( status == true ) { - IotLogError( "Failed to import client certificate at %s", - pClientCertPath ); + if( SSL_CTX_use_certificate_file( pSSLContext, + pClientCertPath, + SSL_FILETYPE_PEM ) != 1 ) + { + IotLogError( "Failed to import client certificate at %s", + pClientCertPath ); - return false; + status = false; + } + else + { + IotLogInfo( "Successfully imported client certificate." ); + } } - IotLogInfo( "Successfully imported client certificate." ); - /* Import the client certificate private key. */ - if( SSL_CTX_use_PrivateKey_file( pSSLContext, - pCertPrivateKeyPath, - SSL_FILETYPE_PEM ) != 1 ) + if( status == true ) { - IotLogError( "Failed to import client certificate private key at %s", - pCertPrivateKeyPath ); + if( SSL_CTX_use_PrivateKey_file( pSSLContext, + pCertPrivateKeyPath, + SSL_FILETYPE_PEM ) != 1 ) + { + IotLogError( "Failed to import client certificate private key at %s", + pCertPrivateKeyPath ); - return false; + status = false; + } + else + { + IotLogInfo( "Successfully imported client certificate private key." ); + } } - IotLogInfo( "Successfully imported client certificate private key." ); - - return true; + return status; } /*-----------------------------------------------------------*/ @@ -481,31 +485,18 @@ static inline bool _readCredentials( SSL_CTX * pSSLContext, /** * @brief Set up TLS on a TCP connection. * - * @param[in] pConnectionInfo An established TCP connection. + * @param[in] pNetworkConnection An established TCP connection. * @param[in] pServerName Remote host name, used for server name indication. - * @param[in] pTlsInfo TLS setup parameters. + * @param[in] pCredentials TLS setup parameters. * - * @return #AWS_IOT_NETWORK_SUCCESS, #AWS_IOT_NETWORK_BAD_PARAMETER, - * #AWS_IOT_NETWORK_TLS_LIBRARY_ERROR, or #AWS_IOT_NETWORK_CREDENTIAL_READ_ERROR + * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. */ -static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnectionInfo, - const char * const pServerName, - const AwsIotNetworkTlsInfo_t * const pTlsInfo ) +static inline IotNetworkError_t _tlsSetup( IotNetworkConnectionOpenssl_t * const pNetworkConnection, + const char * const pServerName, + const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials ) { SSL_CTX * pSSLContext = NULL; - AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; - - /* Check credentials. The sizes of credentials are ignored, as the credential - * parameters represent filesystem paths and the OpenSSL APIs for reading - * credential paths do not take a size (NULL-terminated strings expected). */ - if( ( pTlsInfo->pRootCa == NULL ) || - ( pTlsInfo->pClientCert == NULL ) || - ( pTlsInfo->pPrivateKey == NULL ) ) - { - IotLogError( "Bad parameter in TLS setup parameters." ); - - return AWS_IOT_NETWORK_BAD_PARAMETER; - } + IotNetworkError_t status = IOT_NETWORK_SUCCESS; /* Create a new SSL context. */ #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -518,139 +509,152 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect { IotLogError( "Failed to create new SSL context." ); - return AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + status = IOT_NETWORK_SYSTEM_ERROR; } - - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The - * mask returned by SSL_CTX_set_mode does not need to be checked. */ - IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); - ( void ) SSL_CTX_set_mode( pSSLContext, SSL_MODE_AUTO_RETRY ); - - /* Import all credentials. */ - if( _readCredentials( pSSLContext, - pTlsInfo->pRootCa, - pTlsInfo->pClientCert, - pTlsInfo->pPrivateKey ) == false ) - { - SSL_CTX_free( pSSLContext ); - - return AWS_IOT_NETWORK_CREDENTIAL_READ_ERROR; - } - - /* Create a new SSL connection context, then free the SSL context as it's no - * longer needed. */ - pConnectionInfo->pSSLConnectionContext = SSL_new( pSSLContext ); - SSL_CTX_free( pSSLContext ); - - if( pConnectionInfo->pSSLConnectionContext == NULL ) + else { - IotLogError( "Failed to create new SSL connection context." ); + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The + * mask returned by SSL_CTX_set_mode does not need to be checked. */ + IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); + ( void ) SSL_CTX_set_mode( pSSLContext, SSL_MODE_AUTO_RETRY ); + + /* Import all credentials. */ + if( _readCredentials( pSSLContext, + pOpensslCredentials->pRootCaPath, + pOpensslCredentials->pClientCertPath, + pOpensslCredentials->pPrivateKeyPath ) == false ) + { + status = IOT_NETWORK_FAILURE; + } + else + { + /* Create a new SSL connection context */ + pNetworkConnection->pSslContext = SSL_new( pSSLContext ); - return AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; - } + if( pNetworkConnection->pSslContext == NULL ) + { + IotLogError( "Failed to create new SSL connection context." ); - /* Enable SSL peer verification. */ - IotLogDebug( "Setting SSL_VERIFY_PEER." ); - SSL_set_verify( pConnectionInfo->pSSLConnectionContext, SSL_VERIFY_PEER, NULL ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + } - /* Set the socket for the SSL connection. */ - if( SSL_set_fd( pConnectionInfo->pSSLConnectionContext, - pConnectionInfo->tcpSocket ) != 1 ) - { - IotLogError( "Failed to set SSL socket %d.", pConnectionInfo->tcpSocket ); - status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + SSL_CTX_free( pSSLContext ); } - /* Set up ALPN. */ - if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->pAlpnProtos != NULL ) ) + if( status == IOT_NETWORK_SUCCESS ) { - IotLogDebug( "Setting ALPN protos." ); + /* Enable SSL peer verification. */ + IotLogDebug( "Setting SSL_VERIFY_PEER." ); + SSL_set_verify( pNetworkConnection->pSslContext, SSL_VERIFY_PEER, NULL ); - if( ( SSL_set_alpn_protos( pConnectionInfo->pSSLConnectionContext, - ( const unsigned char * ) pTlsInfo->pAlpnProtos, - ( unsigned int ) strlen( pTlsInfo->pAlpnProtos ) ) != 0 ) ) + /* Set the socket for the SSL connection. */ + if( SSL_set_fd( pNetworkConnection->pSslContext, + pNetworkConnection->socket ) != 1 ) { - IotLogError( "Failed to set ALPN protos." ); - status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + IotLogError( "Failed to set SSL socket %d.", pNetworkConnection->socket ); + status = IOT_NETWORK_SYSTEM_ERROR; } - } - - /* Set TLS MFLN. */ - if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->maxFragmentLength > 0 ) ) - { - IotLogDebug( "Setting max send fragment length %lu.", - ( unsigned long ) pTlsInfo->maxFragmentLength ); - /* Set the maximum send fragment length. */ - if( SSL_set_max_send_fragment( pConnectionInfo->pSSLConnectionContext, - ( long ) pTlsInfo->maxFragmentLength ) != 1 ) - { - IotLogError( "Failed to set max send fragment length %lu.", - ( unsigned long ) pTlsInfo->maxFragmentLength ); - status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; - } - else + /* Set up ALPN if requested. */ + if( status == IOT_NETWORK_SUCCESS ) { - /* In supported versions of OpenSSL, change the size of the read buffer - * to match the maximum fragment length + some extra bytes for overhead. - * Note that OpenSSL ignores this setting if it's smaller than the default. - */ - #if OPENSSL_VERSION_NUMBER > 0x10100000L - SSL_set_default_read_buffer_len( pConnectionInfo->pSSLConnectionContext, - pTlsInfo->maxFragmentLength + - SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); - #endif + if( pOpensslCredentials->pAlpnProtos != NULL ) + { + IotLogDebug( "Setting ALPN protos." ); + + if( ( SSL_set_alpn_protos( pNetworkConnection->pSslContext, + ( const unsigned char * ) pOpensslCredentials->pAlpnProtos, + ( unsigned int ) strlen( pOpensslCredentials->pAlpnProtos ) ) != 0 ) ) + { + IotLogError( "Failed to set ALPN protos." ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + } } - } - - /* Set server name for SNI. */ - if( ( status == AWS_IOT_NETWORK_SUCCESS ) && ( pTlsInfo->disableSni == false ) ) - { - IotLogDebug( "Setting server name %s for SNI.", pServerName ); - if( SSL_set_tlsext_host_name( pConnectionInfo->pSSLConnectionContext, - pServerName ) != 1 ) + /* Set TLS MFLN if requested. */ + if( status == IOT_NETWORK_SUCCESS ) { - IotLogError( "Failed to set server name %s for SNI.", pServerName ); - status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + if( pOpensslCredentials->maxFragmentLength > 0 ) + { + IotLogDebug( "Setting max send fragment length %lu.", + ( unsigned long ) pOpensslCredentials->maxFragmentLength ); + + /* Set the maximum send fragment length. */ + if( SSL_set_max_send_fragment( pNetworkConnection->pSslContext, + ( long ) pOpensslCredentials->maxFragmentLength ) != 1 ) + { + IotLogError( "Failed to set max send fragment length %lu.", + ( unsigned long ) pOpensslCredentials->maxFragmentLength ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + /* In supported versions of OpenSSL, change the size of the read buffer + * to match the maximum fragment length + some extra bytes for overhead. + * Note that OpenSSL ignores this setting if it's smaller than the default. + */ + #if OPENSSL_VERSION_NUMBER > 0x10100000L + SSL_set_default_read_buffer_len( pNetworkConnection->pSslContext, + pOpensslCredentials->maxFragmentLength + + SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); + #endif + } + } } - } - /* Perform the TLS handshake. */ - if( status == AWS_IOT_NETWORK_SUCCESS ) - { - if( SSL_connect( pConnectionInfo->pSSLConnectionContext ) != 1 ) + if( status == IOT_NETWORK_SUCCESS ) { - IotLogError( "TLS handshake failed." ); - status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + if( pOpensslCredentials->disableSni == false ) + { + IotLogDebug( "Setting server name %s for SNI.", pServerName ); + + if( SSL_set_tlsext_host_name( pNetworkConnection->pSslContext, + pServerName ) != 1 ) + { + IotLogError( "Failed to set server name %s for SNI.", pServerName ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + } } - else + + /* Perform the TLS handshake. */ + if( status == IOT_NETWORK_SUCCESS ) { - IotLogInfo( "TLS handshake succeeded." ); + if( SSL_connect( pNetworkConnection->pSslContext ) != 1 ) + { + IotLogError( "TLS handshake failed." ); + status = IOT_NETWORK_FAILURE; + } + else + { + IotLogInfo( "TLS handshake succeeded." ); + } } - } - /* Verify the peer certificate. */ - if( status == AWS_IOT_NETWORK_SUCCESS ) - { - if( SSL_get_verify_result( pConnectionInfo->pSSLConnectionContext ) != X509_V_OK ) + /* Verify the peer certificate. */ + if( status == IOT_NETWORK_SUCCESS ) { - IotLogError( "Peer certificate verification failed." ); - status = AWS_IOT_NETWORK_TLS_LIBRARY_ERROR; + if( SSL_get_verify_result( pNetworkConnection->pSslContext ) != X509_V_OK ) + { + IotLogError( "Peer certificate verification failed." ); + status = IOT_NETWORK_FAILURE; + } + else + { + IotLogInfo( "Peer certificate verified. TLS connection established." ); + } } - else + + /* Clean up on error. */ + if( status != IOT_NETWORK_SUCCESS ) { - IotLogInfo( "Peer certificate verified. TLS connection established." ); + SSL_free( pNetworkConnection->pSslContext ); + pNetworkConnection->pSslContext = NULL; } } - /* Clean up on error. */ - if( status != AWS_IOT_NETWORK_SUCCESS ) - { - SSL_free( pConnectionInfo->pSSLConnectionContext ); - pConnectionInfo->pSSLConnectionContext = NULL; - } - return status; } @@ -659,22 +663,22 @@ static inline AwsIotNetworkError_t _tlsSetup( _connectionInfo_t * const pConnect /** * @brief Cleans up TLS for a connection. * - * @param[in] pConnectionInfo The TLS connection to clean up. + * @param[in] pNetworkConnection The TLS connection to clean up. */ -static inline void _tlsCleanup( _connectionInfo_t * const pConnectionInfo ) +static inline void _tlsCleanup( IotNetworkConnectionOpenssl_t * const pNetworkConnection ) { /* Shut down the TLS connection. */ IotLogInfo( "Shutting down TLS connection." ); /* SSL shutdown should be called twice: once to send "close notify" and once * more to receive the peer's "close notify". */ - if( SSL_shutdown( pConnectionInfo->pSSLConnectionContext ) == 0 ) + if( SSL_shutdown( pNetworkConnection->pSslContext ) == 0 ) { IotLogDebug( "\"Close notify\" sent. Awaiting peer response." ); /* The previous call to SSL_shutdown marks the SSL connection as closed. * SSL_shutdown must be called again to read the peer response. */ - if( SSL_shutdown( pConnectionInfo->pSSLConnectionContext ) != 1 ) + if( SSL_shutdown( pNetworkConnection->pSslContext ) != 1 ) { IotLogWarn( "No response from peer." ); } @@ -687,43 +691,110 @@ static inline void _tlsCleanup( _connectionInfo_t * const pConnectionInfo ) IotLogInfo( "TLS connection terminated." ); /* Free the memory used by the TLS connection. */ - SSL_free( pConnectionInfo->pSSLConnectionContext ); - pConnectionInfo->pSSLConnectionContext = NULL; + SSL_free( pNetworkConnection->pSslContext ); + pNetworkConnection->pSslContext = NULL; } /*-----------------------------------------------------------*/ -AwsIotNetworkError_t AwsIotNetwork_Init( void ) +/** + * @brief Cleans up the receive thread for a connection. + * + * @param[in] pNetworkConnection The network connection with the receive thread + * to clean up. + * + * This function must be called with the network connection mutex locked. + * + * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_SYSTEM_ERROR. + */ +static IotNetworkError_t _cancelReceiveThread( IotNetworkConnectionOpenssl_t * const pNetworkConnection ) { + int posixError = 0; + IotNetworkError_t status = IOT_NETWORK_SUCCESS; + + /* Check if a network receive thread was created. */ + if( pNetworkConnection->receiveThreadStatus != _NONE ) + { + /* Send a cancellation request to the receive thread if active. */ + if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) + { + posixError = pthread_cancel( pNetworkConnection->receiveThread ); + + if( ( posixError != 0 ) && ( posixError != ESRCH ) ) + { + IotLogWarn( "Failed to send cancellation request to socket %d receive " + "thread. errno=%d.", + pNetworkConnection->socket, + posixError ); + + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pNetworkConnection->receiveThreadStatus = _TERMINATED; + } + } + + if( pNetworkConnection->receiveThreadStatus == _TERMINATED ) + { + /* Join the receive thread. */ + posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + + if( posixError != 0 ) + { + IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d.", + pNetworkConnection->socket, + posixError ); + } + + /* Clear data about the receive thread and callback. */ + pNetworkConnection->receiveThreadStatus = _NONE; + ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); + pNetworkConnection->receiveCallback = NULL; + pNetworkConnection->pCallbackContext = NULL; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkOpenssl_Init( void ) +{ + IotNetworkError_t status = IOT_NETWORK_SUCCESS; + /* Details on SIGPIPE handling. */ struct sigaction sigpipeAction; ( void ) memset( &sigpipeAction, 0x00, sizeof( struct sigaction ) ); - /* Set a signal handler for SIGPIPE, which may be raised if the peer closes - * the connection. */ - sigpipeAction.sa_handler = _sigpipeHandler; + /* Ignore SIGPIPE, which may be raised if the peer closes the connection. */ + sigpipeAction.sa_handler = SIG_IGN; if( sigaction( SIGPIPE, &sigpipeAction, NULL ) != 0 ) { IotLogError( "Failed to set SIGPIPE handler. errno=%d.", errno ); - return AWS_IOT_NETWORK_INIT_FAILED; + status = IOT_NETWORK_FAILURE; } - /* Per the OpenSSL 1.0.2 docs, the return value of this function is meaningless. - * This function is also deprecated, but it's called to retain compatibility - * with v1.0.2. */ - ( void ) SSL_library_init(); + if( status == IOT_NETWORK_SUCCESS ) + { + /* Per the OpenSSL docs, the return value of this function is meaningless. + * This function is also deprecated, but it's called to retain compatibility + * with v1.0.2. */ + ( void ) SSL_library_init(); - IotLogInfo( "Network library initialized." ); + IotLogInfo( "Network library initialized." ); + } - return AWS_IOT_NETWORK_SUCCESS; + return status; } /*-----------------------------------------------------------*/ -void AwsIotNetwork_Cleanup( void ) +void IotNetworkOpenssl_Cleanup( void ) { /* Call the necessary OpenSSL functions to prevent memory leaks. */ #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -738,83 +809,92 @@ void AwsIotNetwork_Cleanup( void ) /*-----------------------------------------------------------*/ -AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * const pNetworkConnection, - const char * const pHostName, - uint16_t port, - const AwsIotNetworkTlsInfo_t * const pTlsInfo ) +IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, + void * pCredentialInfo, + void * const pConnection ) { int tcpSocket = -1; - AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; - _connectionInfo_t * pConnectionInfo = NULL; + IotNetworkError_t status = IOT_NETWORK_SUCCESS; - /* Check parameters. */ - if( ( pNetworkConnection == NULL ) || ( pHostName == NULL ) || ( port == 0 ) ) + /* Cast function parameters to correct types. */ + const IotNetworkServerInfoOpenssl_t * const pServerInfo = pConnectionInfo; + const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials = pCredentialInfo; + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; + + /* Check output parameter. */ + if( pNetworkConnection == NULL ) { - IotLogError( "Bad parameter to AwsIotNetwork_TcpConnect." ); + IotLogError( "Output parameter for connection create cannot be NULL." ); - return AWS_IOT_NETWORK_BAD_PARAMETER; + return IOT_NETWORK_BAD_PARAMETER; } - /* Allocate memory for the connection context. This will hold information - * about the connection. */ - pConnectionInfo = ( _connectionInfo_t * ) IotNetwork_Malloc( sizeof( _connectionInfo_t ) ); + /* Check server info. */ + if( ( pServerInfo == NULL ) || + ( pServerInfo->pHostName == NULL ) || + ( pServerInfo->port == 0 ) ) + { + IotLogError( "Missing server information." ); + + return IOT_NETWORK_BAD_PARAMETER; + } - if( pConnectionInfo == NULL ) + /* Check credentials parameter if given. */ + if( pOpensslCredentials != NULL ) { - IotLogError( "Failed to allocate memory for connection information." ); + if( ( pOpensslCredentials->pRootCaPath == NULL ) || + ( pOpensslCredentials->pClientCertPath == NULL ) || + ( pOpensslCredentials->pPrivateKeyPath == NULL ) ) + { + IotLogError( "Missing credential information." ); - return AWS_IOT_NETWORK_NO_MEMORY; + return IOT_NETWORK_BAD_PARAMETER; + } } + /* Clear connection data. */ + ( void ) memset( pNetworkConnection, 0x00, sizeof( IotNetworkConnectionOpenssl_t ) ); + /* Create the network connection mutex. */ - if( IotMutex_Create( &( pConnectionInfo->mutex ) ) == false ) + if( IotMutex_Create( &( pNetworkConnection->mutex ) ) == false ) { - IotLogError( "Failed to create connection mutex." ); - IotNetwork_Free( pConnectionInfo ); + IotLogError( "Failed to create network connection mutex." ); - return AWS_IOT_NETWORK_NO_MEMORY; + return IOT_NETWORK_NO_MEMORY; } /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ - tcpSocket = _dnsLookup( pHostName, port ); + tcpSocket = _dnsLookup( pServerInfo ); if( tcpSocket == -1 ) { - status = AWS_IOT_NETWORK_DNS_LOOKUP_ERROR; + status = IOT_NETWORK_FAILURE; } - - if( status == AWS_IOT_NETWORK_SUCCESS ) + else { - /* Initialize the other members of the connection struct. */ - pConnectionInfo->tcpSocket = tcpSocket; - pConnectionInfo->pSSLConnectionContext = NULL; - pConnectionInfo->receiveThreadStatus = _NONE; - pConnectionInfo->mqttReceiveCallback = NULL; - pConnectionInfo->pMqttConnection = NULL; - ( void ) memset( &( pConnectionInfo->receiveThread ), 0x00, sizeof( pthread_t ) ); - - /* Set the output parameter. */ - *pNetworkConnection = pConnectionInfo; - IotLogInfo( "TCP connection successful." ); - if( pTlsInfo != NULL ) + /* Set the socket in the network connection. */ + pNetworkConnection->socket = tcpSocket; + + /* Set up TLS if credentials are provided. */ + if( pOpensslCredentials != NULL ) { IotLogInfo( "Setting up TLS." ); - status = _tlsSetup( pConnectionInfo, pHostName, pTlsInfo ); + status = _tlsSetup( pNetworkConnection, pServerInfo->pHostName, pOpensslCredentials ); } } /* Clean up on error. */ - if( status != AWS_IOT_NETWORK_SUCCESS ) + if( status != IOT_NETWORK_SUCCESS ) { if( tcpSocket != -1 ) { ( void ) close( tcpSocket ); } - AwsIotNetwork_DestroyConnection( pConnectionInfo ); + IotNetworkOpenssl_Destroy( pNetworkConnection ); } return status; @@ -822,237 +902,303 @@ AwsIotNetworkError_t AwsIotNetwork_CreateConnection( AwsIotNetworkConnection_t * /*-----------------------------------------------------------*/ -void AwsIotNetwork_CloseConnection( AwsIotNetworkConnection_t networkConnection ) +IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ) { int posixError = 0; - _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; + IotNetworkError_t status = IOT_NETWORK_SUCCESS; - /* Check parameters. */ - if( pConnectionInfo == NULL ) - { - IotLogError( "Bad parameter to AwsIotNetwork_CloseConnection." ); + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - return; - } + /* Lock the connection mutex before changing the callback and its parameter. */ + IotMutex_Lock( &( pNetworkConnection->mutex ) ); - /* Lock the connection mutex to block the receive thread. */ - IotMutex_Lock( &( pConnectionInfo->mutex ) ); + /* Cancel any active receive thread for this connection. */ + status = _cancelReceiveThread( pNetworkConnection ); - /* Check if a network receive thread was created. */ - if( pConnectionInfo->receiveThreadStatus != _NONE ) + if( status == IOT_NETWORK_SUCCESS ) { - /* Send a cancellation request to the receive thread if active. */ - if( pConnectionInfo->receiveThreadStatus == _ACTIVE ) + /* Create a new receive thread if a callback is given. */ + if( receiveCallback != NULL ) { - posixError = pthread_cancel( pConnectionInfo->receiveThread ); + /* Update the callback and parameter. */ + pNetworkConnection->receiveCallback = receiveCallback; + pNetworkConnection->pCallbackContext = pContext; - if( ( posixError != 0 ) && ( posixError != ESRCH ) ) + posixError = pthread_create( &pNetworkConnection->receiveThread, + NULL, + _networkReceiveThread, + pNetworkConnection ); + + if( posixError != 0 ) { - IotLogWarn( "Failed to send cancellation request to socket %d receive " - "thread during TLS cleanup. errno=%d.", - pConnectionInfo->tcpSocket, - posixError ); + IotLogError( "Failed to create socket %d network receive thread. errno=%d.", + pNetworkConnection->socket, + posixError ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pNetworkConnection->receiveThreadStatus = _ACTIVE; } } + } + + /* Unlock the connection mutex. */ + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - /* Join the receive thread. */ - posixError = pthread_join( pConnectionInfo->receiveThread, NULL ); + return status; +} - if( posixError != 0 ) - { - IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d", - pConnectionInfo->tcpSocket, - posixError ); - } +/*-----------------------------------------------------------*/ - /* Clear data about the receive thread. */ - pConnectionInfo->receiveThreadStatus = _NONE; - ( void ) memset( &( pConnectionInfo->receiveThread ), 0x00, sizeof( pthread_t ) ); - } +size_t IotNetworkOpenssl_Send( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ) +{ + int bytesSent = 0; + struct pollfd fileDescriptor = + { + .events = POLLOUT, + .revents = 0 + }; - /* If TLS was set up for this connection, clean it up. */ - if( pConnectionInfo->pSSLConnectionContext != NULL ) + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; + + /* Check parameters. */ + if( ( pNetworkConnection == NULL ) || ( pMessage == NULL ) || ( messageLength == 0 ) ) { - _tlsCleanup( pConnectionInfo ); + IotLogError( "Bad parameter to network connection send." ); + + return 0; } - /* Close the connection. */ - if( close( pConnectionInfo->tcpSocket ) != 0 ) + IotLogDebug( "Sending %lu bytes over socket %d.", + ( unsigned long ) messageLength, + pNetworkConnection->socket ); + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = pNetworkConnection->socket; + + /* Lock the connection mutex to prevent the connection from being closed + * while sending. */ + IotMutex_Lock( &( pNetworkConnection->mutex ) ); + + /* Check that it's possible to send data right now. */ + if( poll( &fileDescriptor, 1, 0 ) == 1 ) { - IotLogWarn( "Could not close socket %d. errno=%d.", - pConnectionInfo->tcpSocket, - errno ); + /* Use OpenSSL's SSL_write() function or the POSIX send() function based on + * whether the SSL connection context is set up. */ + if( pNetworkConnection->pSslContext != NULL ) + { + bytesSent = SSL_write( pNetworkConnection->pSslContext, + pMessage, + ( int ) messageLength ); + } + else + { + bytesSent = ( int ) send( pNetworkConnection->socket, + pMessage, + messageLength, + 0 ); + } } else { - IotLogInfo( "Connection (socket %d) closed.", - pConnectionInfo->tcpSocket ); + IotLogError( "Not possible to send on socket %d.", pNetworkConnection->socket ); } /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); -} + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); -/*-----------------------------------------------------------*/ - -void AwsIotNetwork_DestroyConnection( AwsIotNetworkConnection_t networkConnection ) -{ - _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; - - if( pConnectionInfo == NULL ) + /* Log the number of bytes sent. */ + if( bytesSent <= 0 ) { - IotLogError( "Bad parameter to AwsIotNetwork_DestroyConnection." ); + IotLogError( "Send failure." ); - return; + bytesSent = 0; + } + else if( ( size_t ) bytesSent != messageLength ) + { + IotLogWarn( "Failed to send %lu bytes, %d bytes sent instead.", + ( unsigned long ) messageLength, + bytesSent ); + } + else + { + IotLogDebug( "Successfully sent %lu bytes.", + ( unsigned long ) messageLength ); } - /* Destroy the connection data mutex. */ - IotMutex_Destroy( &( pConnectionInfo->mutex ) ); - - /* Free memory in use by the connection. */ - IotNetwork_Free( pConnectionInfo ); + return ( size_t ) bytesSent; } /*-----------------------------------------------------------*/ -AwsIotNetworkError_t AwsIotNetwork_SetMqttReceiveCallback( AwsIotNetworkConnection_t networkConnection, - AwsIotMqttConnection_t * pMqttConnection, - AwsIotMqttReceiveCallback_t receiveCallback ) +size_t IotNetworkOpenssl_Receive( void * pConnection, + uint8_t * const pBuffer, + size_t bytesRequested ) { - int posixError = 0; - AwsIotNetworkError_t status = AWS_IOT_NETWORK_SUCCESS; - _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; + int bytesRead = 0; + size_t totalBytesRead = 0; + struct pollfd fileDescriptor = + { + .events = POLLIN | POLLPRI, + .revents = 0 + }; - /* Lock the connection mutex before changing the callback and its parameter. */ - IotMutex_Lock( &( pConnectionInfo->mutex ) ); + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - /* Clean up any previously terminated receive thread. */ - if( pConnectionInfo->receiveThreadStatus == _TERMINATED ) + /* Check parameters. */ + if( ( pNetworkConnection == NULL ) || ( pBuffer == NULL ) || ( bytesRequested == 0 ) ) { - posixError = pthread_join( pConnectionInfo->receiveThread, NULL ); + IotLogError( "Bad parameter to network connection receive." ); - if( posixError != 0 ) + return 0; + } + + IotLogDebug( "Blocking to wait for %lu bytes on socket %d.", + ( unsigned long ) bytesRequested, + pNetworkConnection->socket ); + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = pNetworkConnection->socket; + + /* Lock the connection mutex to prevent the connection from being closed + * while receiving. */ + IotMutex_Lock( &( pNetworkConnection->mutex ) ); + + /* Continuously block on the socket for available data. */ + while( poll( &fileDescriptor, 1, -1 ) == 1 ) + { + /* If an error event is detected, stop receiving. */ + if( ( fileDescriptor.revents & POLLERR ) || + ( fileDescriptor.revents & POLLHUP ) || + ( fileDescriptor.revents & POLLNVAL ) ) { - IotLogWarn( "Failed to join socket %d network receive thread. errno=%d.", - pConnectionInfo->tcpSocket, - posixError ); + IotLogError( "Error receiving on socket %d after %lu bytes;" + " stopping blocking receive.", + ( unsigned long ) totalBytesRead, + pNetworkConnection->socket ); + break; + } - status = AWS_IOT_NETWORK_SYSTEM_ERROR; + /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on + * whether the SSL connection context is set up. */ + if( pNetworkConnection->pSslContext != NULL ) + { + bytesRead = SSL_read( pNetworkConnection->pSslContext, + pBuffer + totalBytesRead, + ( int ) ( bytesRequested - totalBytesRead ) ); } else { - pConnectionInfo->receiveThreadStatus = _NONE; + bytesRead = ( int ) recv( pNetworkConnection->socket, + pBuffer + totalBytesRead, + bytesRequested - totalBytesRead, + 0 ); } - } - - /* If the receive thread for this connection hasn't been created, create it now. */ - if( ( pConnectionInfo->receiveThreadStatus == _NONE ) && ( status == AWS_IOT_NETWORK_SUCCESS ) ) - { - /* Update the callback and parameter. */ - pConnectionInfo->mqttReceiveCallback = receiveCallback; - pConnectionInfo->pMqttConnection = pMqttConnection; - - posixError = pthread_create( &pConnectionInfo->receiveThread, - NULL, - _networkReceiveThread, - pConnectionInfo ); - if( posixError != 0 ) + /* Check for an error during receive. */ + if( bytesRead <= 0 ) { - IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pConnectionInfo->tcpSocket, - posixError ); - status = AWS_IOT_NETWORK_SYSTEM_ERROR; + IotLogError( "Error receiving from socket %d after receiving %lu bytes", + pNetworkConnection->socket, + ( unsigned long ) totalBytesRead ); + break; } - else + + totalBytesRead += ( size_t ) bytesRead; + + /* Check if any more data needs to be read. */ + if( totalBytesRead == bytesRequested ) { - pConnectionInfo->receiveThreadStatus = _ACTIVE; + IotLogDebug( "Successfully received %lu bytes.", + ( unsigned long ) bytesRequested ); + + break; } } /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - return status; + return totalBytesRead; } /*-----------------------------------------------------------*/ -size_t AwsIotNetwork_Send( void * networkConnection, - const void * const pMessage, - size_t messageLength ) +IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) { - int bytesSent = 0; - _connectionInfo_t * pConnectionInfo = ( _connectionInfo_t * ) networkConnection; - struct pollfd fileDescriptors = - { - .events = POLLOUT, - .revents = 0 - }; + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - /* Check parameters. */ - if( ( pConnectionInfo == NULL ) || ( pMessage == NULL ) || ( messageLength == 0 ) ) + /* Check parameter. */ + if( pNetworkConnection == NULL ) { - IotLogError( "Bad parameter to AwsIotNetwork_Send." ); + IotLogError( "NULL parameter to network connection close." ); - return 0; + return IOT_NETWORK_BAD_PARAMETER; } - /* Send message. */ - IotLogDebug( "Sending %lu bytes over network.", - ( unsigned long ) messageLength ); + /* Lock the connection mutex to block the receive thread. */ + IotMutex_Lock( &( pNetworkConnection->mutex ) ); - /* Lock the connection mutex to prevent the connection from being closed - * while sending. */ - IotMutex_Lock( &( pConnectionInfo->mutex ) ); + /* Cancel any receive thread for this connection. The return value is not + * checked because the socket will be closed anyways. */ + ( void ) _cancelReceiveThread( pNetworkConnection ); - /* Set the file descriptor for poll. */ - fileDescriptors.fd = pConnectionInfo->tcpSocket; + /* If TLS was set up for this connection, clean it up. */ + if( pNetworkConnection->pSslContext != NULL ) + { + _tlsCleanup( pNetworkConnection ); + } - /* Check that it's possible to send data right now. */ - if( poll( &fileDescriptors, 1, 0 ) == 1 ) + /* Close the connection. */ + if( close( pNetworkConnection->socket ) != 0 ) { - /* Use OpenSSL's SSL_write() function or the POSIX send() function based on - * whether the SSL connection context is set up. */ - if( pConnectionInfo->pSSLConnectionContext != NULL ) - { - bytesSent = SSL_write( pConnectionInfo->pSSLConnectionContext, - pMessage, - ( int ) messageLength ); - } - else - { - bytesSent = ( int ) send( pConnectionInfo->tcpSocket, - pMessage, - messageLength, - 0 ); - } + IotLogWarn( "Could not close socket %d. errno=%d.", + pNetworkConnection->socket, + errno ); + } + else + { + IotLogInfo( "Connection (socket %d) closed.", + pNetworkConnection->socket ); } /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pConnectionInfo->mutex ) ); + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - /* Check for errors. */ - if( bytesSent <= 0 ) - { - IotLogError( "Send failure." ); + return IOT_NETWORK_SUCCESS; +} - return 0; - } +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) +{ + IotNetworkError_t status = IOT_NETWORK_SUCCESS; + + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - if( ( size_t ) bytesSent != messageLength ) + if( pNetworkConnection == NULL ) { - IotLogWarn( "Failed to send %lu bytes, %d bytes sent instead.", - ( unsigned long ) messageLength, - bytesSent ); + IotLogError( "NULL parameter to network connection destroy." ); + + status = IOT_NETWORK_BAD_PARAMETER; } else { - IotLogDebug( "Successfully sent %lu bytes.", - ( unsigned long ) messageLength ); + /* Clean up the connection by destroying its mutex. */ + IotMutex_Destroy( &( pNetworkConnection->mutex ) ); } - return ( size_t ) bytesSent; + return status; } /*-----------------------------------------------------------*/ diff --git a/tests/aws_iot_tests_network.c b/tests/aws_iot_tests_network.c index 53d05c62bd..66f7a45b9a 100644 --- a/tests/aws_iot_tests_network.c +++ b/tests/aws_iot_tests_network.c @@ -36,8 +36,8 @@ /* MQTT include. */ #include "aws_iot_mqtt.h" -/* Platform layer includes. */ -#include "platform/aws_iot_network.h" +/* POSIX+OpenSSL network include. */ +#include "posix/iot_network_openssl.h" /*-----------------------------------------------------------*/ @@ -68,7 +68,7 @@ void AwsIotTest_NetworkCleanup( void ); * * @return true if a new network connection was successfully created; false otherwise. */ -bool AwsIotTest_NetworkConnect( void ** const pNewConnection, +bool AwsIotTest_NetworkConnect( void * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ); /** @@ -76,8 +76,10 @@ bool AwsIotTest_NetworkConnect( void ** const pNewConnection, * * @param[in] pDisconnectContext The connection to close. Pass NULL to close * the global network connection created by #AwsIotTest_NetworkSetup. + * + * @return Always returns #IOT_NETWORK_SUCCESS. */ -void AwsIotTest_NetworkClose( void * pDisconnectContext ); +IotNetworkError_t AwsIotTest_NetworkClose( void * pDisconnectContext ); /** * @brief Network interface cleanup function for the tests. @@ -88,10 +90,15 @@ void AwsIotTest_NetworkDestroy( void * pNetworkConnection ); /*-----------------------------------------------------------*/ +/** + * @brief Flag that tracks if the network connection is created. + */ +static bool _networkConnectionCreated = false; + /** * @brief The network connection shared among the tests. */ -static AwsIotNetworkConnection_t _networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; +static IotNetworkConnectionOpenssl_t _networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; /** * @brief The MQTT network interface shared among the tests. @@ -108,7 +115,7 @@ AwsIotMqttConnection_t _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITI bool AwsIotTest_NetworkSetup( void ) { /* Initialize the network library. */ - if( AwsIotNetwork_Init() != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) { return false; } @@ -116,7 +123,7 @@ bool AwsIotTest_NetworkSetup( void ) if( AwsIotTest_NetworkConnect( &_networkConnection, &_AwsIotTestMqttConnection ) == false ) { - AwsIotNetwork_Cleanup(); + IotNetworkOpenssl_Cleanup(); return false; } @@ -124,8 +131,10 @@ bool AwsIotTest_NetworkSetup( void ) /* Set the members of the network interface. */ _AwsIotTestNetworkInterface.pDisconnectContext = NULL; _AwsIotTestNetworkInterface.disconnect = AwsIotTest_NetworkClose; - _AwsIotTestNetworkInterface.pSendContext = ( void * ) _networkConnection; - _AwsIotTestNetworkInterface.send = AwsIotNetwork_Send; + _AwsIotTestNetworkInterface.pSendContext = ( void * ) &_networkConnection; + _AwsIotTestNetworkInterface.send = IotNetworkOpenssl_Send; + + _networkConnectionCreated = true; return true; } @@ -135,15 +144,15 @@ bool AwsIotTest_NetworkSetup( void ) void AwsIotTest_NetworkCleanup( void ) { /* Close the TCP connection to the server. */ - if( _networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + if( _networkConnectionCreated == true ) { AwsIotTest_NetworkClose( NULL ); - AwsIotTest_NetworkDestroy( _networkConnection ); - _networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + AwsIotTest_NetworkDestroy( &_networkConnection ); + _networkConnectionCreated = false; } /* Clean up the network library. */ - AwsIotNetwork_Cleanup(); + IotNetworkOpenssl_Cleanup(); /* Clear the network interface. */ ( void ) memset( &_AwsIotTestNetworkInterface, 0x00, sizeof( AwsIotMqttNetIf_t ) ); @@ -151,100 +160,85 @@ void AwsIotTest_NetworkCleanup( void ) /*-----------------------------------------------------------*/ -bool AwsIotTest_NetworkConnect( void ** const pNewConnection, +bool AwsIotTest_NetworkConnect( void * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ) { - AwsIotNetworkConnection_t newConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; - AwsIotNetworkTlsInfo_t * pTlsInfo = NULL; + IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; + IotNetworkCredentialsOpenssl_t * pCredentials = NULL; + + serverInfo.pHostName = AWS_IOT_TEST_SERVER; + serverInfo.port = AWS_IOT_TEST_PORT; /* Set up TLS if the endpoint is secured. These tests should always use ALPN. */ #if AWS_IOT_TEST_SECURED_CONNECTION == 1 - AwsIotNetworkTlsInfo_t tlsInfo = AWS_IOT_NETWORK_TLS_INFO_INITIALIZER; - pTlsInfo = &tlsInfo; + IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER; + pCredentials = &credentials; - /* Set credentials for secured connection. Lengths are optional on some - * platforms and only set if needed. */ - tlsInfo.pRootCa = AWS_IOT_TEST_ROOT_CA; - tlsInfo.pClientCert = AWS_IOT_TEST_CLIENT_CERT; - tlsInfo.pPrivateKey = AWS_IOT_TEST_PRIVATE_KEY; - - #ifdef AWS_IOT_TEST_ROOT_CA_LENGTH - tlsInfo.rootCaLength = AWS_IOT_TEST_ROOT_CA_LENGTH; - #endif - - #ifdef AWS_IOT_TEST_CLIENT_CERT_LENGTH - tlsInfo.clientCertLength = AWS_IOT_TEST_CLIENT_CERT_LENGTH; - #endif - - #ifdef AWS_IOT_TEST_PRIVATE_KEY_LENGTH - tlsInfo.privateKeyLength = AWS_IOT_TEST_PRIVATE_KEY_LENGTH; - #endif - #endif /* if AWS_IOT_TEST_SECURED_CONNECTION == 0 */ + /* Set credentials for secured connection. */ + credentials.pRootCaPath = AWS_IOT_TEST_ROOT_CA; + credentials.pClientCertPath = AWS_IOT_TEST_CLIENT_CERT; + credentials.pPrivateKeyPath = AWS_IOT_TEST_PRIVATE_KEY; + #endif /* if AWS_IOT_TEST_SECURED_CONNECTION == 1 */ /* Open a connection to the test server. */ - if( AwsIotNetwork_CreateConnection( &newConnection, - AWS_IOT_TEST_SERVER, - AWS_IOT_TEST_PORT, - pTlsInfo ) != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_Create( &serverInfo, + pCredentials, + pNewConnection ) != IOT_NETWORK_SUCCESS ) { return false; } /* Set the MQTT receive callback. */ - if( AwsIotNetwork_SetMqttReceiveCallback( newConnection, - pMqttConnection, - AwsIotMqtt_ReceiveCallback ) != AWS_IOT_NETWORK_SUCCESS ) + if( IotNetworkOpenssl_SetReceiveCallback( pNewConnection, + AwsIotMqtt_ReceiveCallback, + pMqttConnection) != IOT_NETWORK_SUCCESS ) { - AwsIotNetwork_CloseConnection( newConnection ); - AwsIotNetwork_DestroyConnection( newConnection ); - *pNewConnection = NULL; + IotNetworkOpenssl_Close( pNewConnection ); + IotNetworkOpenssl_Destroy( pNewConnection ); return false; } - /* Set the output parameter. */ - *pNewConnection = ( void * ) newConnection; - return true; } /*-----------------------------------------------------------*/ -void AwsIotTest_NetworkClose( void * pDisconnectContext ) +IotNetworkError_t AwsIotTest_NetworkClose( void * pDisconnectContext ) { - AwsIotNetworkConnection_t networkConnection = ( AwsIotNetworkConnection_t ) pDisconnectContext; + IotNetworkConnectionOpenssl_t * pNetworkConnection = ( IotNetworkConnectionOpenssl_t * ) pDisconnectContext; - /* Close the provided network handle; if that is uninitialized, close the + /* Close the provided network handle; if that is NULL, close the * global network handle. */ - if( ( networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) && - ( networkConnection != _networkConnection ) ) + if( ( pNetworkConnection != NULL ) && + ( pNetworkConnection != &_networkConnection ) ) { - AwsIotNetwork_CloseConnection( networkConnection ); + IotNetworkOpenssl_Close( pNetworkConnection ); } - else if( _networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + else if( _networkConnectionCreated == true ) { - AwsIotNetwork_CloseConnection( _networkConnection ); + IotNetworkOpenssl_Close( &_networkConnection ); } + + return IOT_NETWORK_SUCCESS; } /*-----------------------------------------------------------*/ void AwsIotTest_NetworkDestroy( void * pNetworkConnection ) { - AwsIotNetworkConnection_t networkConnection = ( AwsIotNetworkConnection_t ) pNetworkConnection; - - if( ( networkConnection != NULL ) && - ( networkConnection != _networkConnection ) ) + if( ( pNetworkConnection != NULL ) && + ( pNetworkConnection != &_networkConnection ) ) { /* Wrap the network interface's destroy function. */ - AwsIotNetwork_DestroyConnection( ( AwsIotNetworkConnection_t ) pNetworkConnection ); + IotNetworkOpenssl_Destroy( pNetworkConnection ); } else { - if( _networkConnection != AWS_IOT_NETWORK_CONNECTION_INITIALIZER ) + if( _networkConnectionCreated == true ) { - AwsIotNetwork_DestroyConnection( ( AwsIotNetworkConnection_t ) _networkConnection ); - _networkConnection = AWS_IOT_NETWORK_CONNECTION_INITIALIZER; + IotNetworkOpenssl_Destroy( &_networkConnection ); + _networkConnectionCreated = false; } } } diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 2de7cf6c6d..1474fe0283 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -39,6 +39,9 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* POSIX+OpenSSL network include. */ +#include "posix/iot_network_openssl.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -117,7 +120,7 @@ typedef struct _operationCompleteParams * the test network function files. */ extern bool AwsIotTest_NetworkSetup( void ); extern void AwsIotTest_NetworkCleanup( void ); -extern bool AwsIotTest_NetworkConnect( void ** const pNewConnection, +extern bool AwsIotTest_NetworkConnect( void * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ); extern void AwsIotTest_NetworkClose( void * pDisconnectContext ); extern void AwsIotTest_NetworkDestroy( void * pConnection ); @@ -452,18 +455,18 @@ TEST_SETUP( MQTT_System ) _unsubscribeSerializerOverride = false; _disconnectSerializerOverride = false; - /* Set up the network stack. */ - if( AwsIotTest_NetworkSetup() == false ) - { - TEST_FAIL_MESSAGE( "Failed to set up network connection." ); - } - /* Initialize the MQTT library. */ if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } + /* Set up the network stack. */ + if( AwsIotTest_NetworkSetup() == false ) + { + TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + } + /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER @@ -485,12 +488,12 @@ TEST_SETUP( MQTT_System ) */ TEST_TEAR_DOWN( MQTT_System ) { - /* Clean up the network stack. */ - AwsIotTest_NetworkCleanup(); - /* Clean up the MQTT library. */ AwsIotMqtt_Cleanup(); _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + + /* Clean up the network stack. */ + AwsIotTest_NetworkCleanup(); } /*-----------------------------------------------------------*/ @@ -651,6 +654,7 @@ TEST( MQTT_System, SubscribePublishAsync ) */ TEST( MQTT_System, LastWillAndTestament ) { + bool lwtListenerCreated = false; AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; AwsIotMqttNetIf_t lwtNetIf = _AwsIotTestNetworkInterface; char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; @@ -659,7 +663,7 @@ TEST( MQTT_System, LastWillAndTestament ) connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotMqttSubscription_t willSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - void * pLwtListenerConnection = NULL; + IotNetworkConnectionOpenssl_t lwtListenerConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; IotSemaphore_t waitSem; /* Create the wait semaphore. */ @@ -682,13 +686,14 @@ TEST( MQTT_System, LastWillAndTestament ) /* Establish an independent MQTT over TCP connection to receive a Last * Will and Testament message. */ TEST_ASSERT_EQUAL( true, - AwsIotTest_NetworkConnect( &pLwtListenerConnection, + AwsIotTest_NetworkConnect( &lwtListenerConnection, &lwtListener ) ); + lwtListenerCreated = true; if( TEST_PROTECT() ) { - lwtNetIf.pDisconnectContext = pLwtListenerConnection; - lwtNetIf.pSendContext = pLwtListenerConnection; + lwtNetIf.pDisconnectContext = &lwtListenerConnection; + lwtNetIf.pSendContext = &lwtListenerConnection; lwtConnectInfo.cleanSession = true; lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); @@ -746,14 +751,14 @@ TEST( MQTT_System, LastWillAndTestament ) } AwsIotMqtt_Disconnect( lwtListener, false ); - AwsIotTest_NetworkDestroy( pLwtListenerConnection ); - pLwtListenerConnection = NULL; + AwsIotTest_NetworkDestroy( &lwtListenerConnection ); + lwtListenerCreated = false; } - if( pLwtListenerConnection != NULL ) + if( lwtListenerCreated == true ) { - AwsIotTest_NetworkClose( pLwtListenerConnection ); - AwsIotTest_NetworkDestroy( pLwtListenerConnection ); + AwsIotTest_NetworkClose( &lwtListenerConnection ); + AwsIotTest_NetworkDestroy( &lwtListenerConnection ); } } diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index afbb57fb03..6f859e10b0 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -144,7 +144,7 @@ static void _publishSetDup( bool awsIotMqttMode, * @brief A send function that always "succeeds". */ static size_t _sendSuccess( void * pSendContext, - const void * const pMessage, + const uint8_t * const pMessage, size_t messageLength ) { ( void ) pSendContext; @@ -162,13 +162,13 @@ static size_t _sendSuccess( void * pSendContext, * the original. */ static size_t _dupChecker( void * pSendContext, - const void * const pMessage, + const uint8_t * const pMessage, size_t messageLength ) { static int runCount = 0; static bool status = true; bool * pDupCheckResult = ( bool * ) pSendContext; - uint8_t publishFlags = *( ( uint8_t * ) pMessage ); + uint8_t publishFlags = *pMessage; /* Declare the remaining variables required to check packet identifier * for the AWS IoT MQTT server. */ @@ -265,7 +265,7 @@ static size_t _dupChecker( void * pSendContext, /** * @brief A disconnect function that only reports whether it was invoked. */ -static void _disconnect( void * pDisconnectContext ) +static IotNetworkError_t _disconnect( void * pDisconnectContext ) { bool * pDisconnectInvoked = ( bool * ) pDisconnectContext; @@ -274,6 +274,8 @@ static void _disconnect( void * pDisconnectContext ) { *pDisconnectInvoked = true; } + + return IOT_NETWORK_SUCCESS; } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c index d6454197b0..78f2b612eb 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c @@ -384,9 +384,10 @@ static bool _processBuffer( const _mqttOperation_t * const pOperation, { /* Call the receive callback on pBuffer. */ int32_t bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pBuffer, - 0, bufferSize, + 0, _freeWrapper ); /* Free pBuffer if the receive callback wasn't expected to free it. */ @@ -432,9 +433,10 @@ static bool _processPublish( const uint8_t * const pPublish, /* Call the receive callback on pPublish. */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pPublish, - 0, publishSize, + 0, _freeWrapper ); /* Check how many times the publish callback is invoked. */ @@ -701,9 +703,10 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) /* Processing a control packet 0xf is a protocol violation. */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, &invalidPacket, - 0, sizeof( uint8_t ), + 0, NULL ); TEST_ASSERT_EQUAL( -1, bytesProcessed ); @@ -748,18 +751,20 @@ TEST( MQTT_Unit_Receive, DataStream ) /* Passing an offset greater than dataLength should not process anything. */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pDataStream, - 5, 4, + 5, _freeWrapper ); TEST_ASSERT_EQUAL( 0, bytesProcessed ); /* The first call to process 64 bytes should only process the CONNACK and * SUBACK. */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pDataStream, - 0, processOffset + 64, + 0, _freeWrapper ); TEST_ASSERT_EQUAL( 11, bytesProcessed ); processOffset += ( size_t ) bytesProcessed; @@ -767,9 +772,10 @@ TEST( MQTT_Unit_Receive, DataStream ) /* A second call to process 64 bytes should not process anything, as the * PUBLISH is incomplete. */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pDataStream, - processOffset, processOffset + 64, + processOffset, _freeWrapper ); TEST_ASSERT_EQUAL( 0, bytesProcessed ); @@ -782,18 +788,20 @@ TEST( MQTT_Unit_Receive, DataStream ) /* A call to process 5 bytes should only process the UNSUBACK (4 bytes). */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pDataStream, - processOffset, processOffset + 5, + processOffset, _freeWrapper ); TEST_ASSERT_EQUAL( 4, bytesProcessed ); processOffset += ( size_t ) bytesProcessed; /* Process the last 2 bytes (PINGRESP). */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pDataStream, - processOffset, processOffset + 2, + processOffset, _freeWrapper ); TEST_ASSERT_EQUAL( 2, bytesProcessed ); diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 0a1c0a299a..ac227a5412 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -118,7 +118,7 @@ typedef struct _operationCompleteParams * the test network function files. */ extern bool AwsIotTest_NetworkSetup( void ); extern void AwsIotTest_NetworkCleanup( void ); -extern bool AwsIotTest_NetworkConnect( void ** const pNewConnection, +extern bool AwsIotTest_NetworkConnect( void * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ); extern void AwsIotTest_NetworkClose( void * pDisconnectContext ); extern void AwsIotTest_NetworkDestroy( void * pConnection ); diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 905b8e5a32..e0ed622974 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -183,9 +183,10 @@ static void _receiveThread( void * pArgument ) /* Call the MQTT receive callback to process the ACK packet. */ bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, + NULL, pReceivedData, - 0, receivedDataLength, + 0, NULL ); AwsIotShadow_Assert( bytesProcessed == ( int32_t ) receivedDataLength ); @@ -199,11 +200,10 @@ static void _receiveThread( void * pArgument ) * timer to respond with an ACK when necessary. */ static size_t _sendSuccess( void * pSendContext, - const void * const pMessage, + const uint8_t * const pMessage, size_t messageLength ) { AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - const uint8_t * const pPacket = ( const uint8_t * const ) pMessage; const uint8_t * pPacketIdentifier = NULL; AwsIotMqttPublishInfo_t decodedPublish = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; size_t publishBytesProcessed = 0; @@ -220,7 +220,7 @@ static size_t _sendSuccess( void * pSendContext, case ( _MQTT_PACKET_TYPE_PUBLISH & 0xf0 ): /* Only set the last packet type to PUBLISH for QoS 1. */ - if( ( ( *pPacket & 0x06 ) >> 1 ) == 1 ) + if( ( ( *pMessage & 0x06 ) >> 1 ) == 1 ) { _lastPacketType = _MQTT_PACKET_TYPE_PUBLISH; } @@ -253,7 +253,7 @@ static size_t _sendSuccess( void * pSendContext, /* Decode the remaining length. */ if( _lastPacketType != _MQTT_PACKET_TYPE_PUBLISH ) { - status = AwsIotTestMqtt_decodeRemainingLength( pPacket + 1, + status = AwsIotTestMqtt_decodeRemainingLength( pMessage + 1, &pPacketIdentifier, NULL ); From 79970766028975f62b60112ba72a7957f11c489a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 6 Feb 2019 14:24:50 -0800 Subject: [PATCH 020/844] Add recursive mutex option to platform. (#271) --- demos/posix/aws_iot_demo_shadow_posix.c | 6 ++- lib/include/platform/iot_threads.h | 8 ++- .../static_memory/iot_static_memory_common.c | 2 +- lib/source/mqtt/aws_iot_mqtt_api.c | 10 ++-- lib/source/mqtt/aws_iot_mqtt_serialize.c | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 4 +- platform/source/posix/iot_threads_posix.c | 49 +++++++++++++++++-- .../posix/network/iot_network_openssl.c | 2 +- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 5 ++ tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 4 +- .../unit/aws_iot_tests_mqtt_subscription.c | 12 +++-- tests/mqtt/unit/aws_iot_tests_mqtt_validate.c | 7 ++- tests/shadow/unit/aws_iot_tests_shadow_api.c | 7 ++- 13 files changed, 93 insertions(+), 25 deletions(-) diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index 3454e768d3..60e0345d32 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -125,6 +125,10 @@ int main( int argc, credentials.pClientCertPath = demoArguments.pClientCertPath; credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; + /* Set server info. */ + serverInfo.pHostName = demoArguments.pHostName; + serverInfo.port = demoArguments.port; + /* Establish a TCP connection to the MQTT server. */ if( IotNetworkOpenssl_Create( &serverInfo, pCredentials, @@ -140,7 +144,7 @@ int main( int argc, * callback processes MQTT data from the network. */ if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, AwsIotMqtt_ReceiveCallback, - &mqttConnection ) == IOT_NETWORK_SUCCESS ) + &mqttConnection ) != IOT_NETWORK_SUCCESS ) { status = -1; } diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index e0c16f125b..759ac5cd1a 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -132,6 +132,9 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, * #IotMutex_t. This function must not be called on an already-initialized #IotMutex_t. * * @param[in] pNewMutex Pointer to the memory that will hold the new mutex. + * @param[in] recursive Set to `true` to create a recursive mutex, i.e. a mutex that + * may be locked multiple times by the same thread. If the system does not support + * recursive mutexes, this function should do nothing and return `false`. * * @return `true` if mutex creation succeeds; `false` otherwise. * @@ -141,7 +144,8 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, * @code{c} * IotMutex_t mutex; * - * if( IotMutex_Create( &mutex ) == true ) + * // Create non-recursive mutex. + * if( IotMutex_Create( &mutex, false ) == true ) * { * // Lock and unlock the mutex... * @@ -151,7 +155,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, * @endcode */ /* @[declare_platform_threads_mutexcreate] */ -bool IotMutex_Create( IotMutex_t * const pNewMutex ); +bool IotMutex_Create( IotMutex_t * const pNewMutex, bool recursive ); /* @[declare_platform_threads_mutexcreate] */ /** diff --git a/lib/source/common/static_memory/iot_static_memory_common.c b/lib/source/common/static_memory/iot_static_memory_common.c index 0a48c912ce..80c5abc7a3 100644 --- a/lib/source/common/static_memory/iot_static_memory_common.c +++ b/lib/source/common/static_memory/iot_static_memory_common.c @@ -181,7 +181,7 @@ void IotStaticMemory_ReturnInUse( void * ptr, bool IotStaticMemory_Init( void ) { - return IotMutex_Create( &( _mutex ) ); + return IotMutex_Create( &( _mutex ), false ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index 0f38fe3b69..e462ff46d1 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -533,7 +533,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; /* Create the timer mutex for a new connection. */ - if( IotMutex_Create( &( pNewMqttConnection->timerMutex ) ) == false ) + if( IotMutex_Create( &( pNewMqttConnection->timerMutex ), true ) == false ) { IotLogError( "Failed to create timer mutex for new connection." ); AwsIotMqtt_FreeConnection( pNewMqttConnection ); @@ -541,7 +541,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, return NULL; } - if( IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ) ) == false ) + if( IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ), false ) == false ) { IotLogError( "Failed to create subscription mutex for new connection." ); IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); @@ -837,7 +837,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; /* Create mutex protecting MQTT operation queues. */ - if( IotMutex_Create( &( _IotMqttQueueMutex ) ) == false ) + if( IotMutex_Create( &( _IotMqttQueueMutex ), false ) == false ) { IotLogError( "Failed to initialize MQTT operation queue mutex." ); status = AWS_IOT_MQTT_INIT_FAILED; @@ -846,7 +846,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) /* Create mutex protecting list of operations pending network responses. */ if( status == AWS_IOT_MQTT_SUCCESS ) { - if( IotMutex_Create( &( _IotMqttPendingResponseMutex ) ) == false ) + if( IotMutex_Create( &( _IotMqttPendingResponseMutex ), false ) == false ) { IotLogError( "Failed to initialize MQTT library pending response mutex." ); IotMutex_Destroy( &( _IotMqttQueueMutex ) ); @@ -858,7 +858,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ) /* Create CONNECT mutex. */ if( status == AWS_IOT_MQTT_SUCCESS ) { - if( IotMutex_Create( &( _connectMutex ) ) == false ) + if( IotMutex_Create( &( _connectMutex ), false ) == false ) { IotLogError( "Failed to initialize MQTT library connect mutex." ); IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/aws_iot_mqtt_serialize.c index 6ced1f177d..9b68534200 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/aws_iot_mqtt_serialize.c @@ -612,7 +612,7 @@ static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ) { /* Create the packet identifier mutex. */ - if( IotMutex_Create( &_packetIdentifierMutex ) == false ) + if( IotMutex_Create( &_packetIdentifierMutex, false ) == false ) { return AWS_IOT_MQTT_INIT_FAILED; } diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 05f4d51ddd..d5e8964cd5 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -585,7 +585,7 @@ static void _updatedCallbackWrapper( void * pArgument, AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) { /* Create the Shadow pending operation list mutex. */ - if( IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ) ) == false ) + if( IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ), false ) == false ) { IotLogError( "Failed to create Shadow pending operation list." ); @@ -593,7 +593,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) } /* Create the Shadow subscription list mutex. */ - if( IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ) ) == false ) + if( IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ), false ) == false ) { IotLogError( "Failed to create Shadow subscription list." ); IotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 0e9c40a7b2..b52090cf1f 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -339,13 +339,50 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, /*-----------------------------------------------------------*/ -bool IotMutex_Create( IotMutex_t * const pNewMutex ) +bool IotMutex_Create( IotMutex_t * const pNewMutex, bool recursive ) { bool status = true; + int mutexError = 0; + pthread_mutexattr_t mutexAttributes, *pMutexAttributes = NULL; - IotLogDebug( "Creating new mutex %p.", pNewMutex ); + if( recursive == true ) + { + IotLogDebug( "Creating new recursive mutex %p.", pNewMutex ); + + /* Create new mutex attributes object. */ + mutexError = pthread_mutexattr_init( &mutexAttributes ); + + if( mutexError != 0 ) + { + IotLogError( "Failed to initialize mutex attributes. errno=%d.", + mutexError ); + + status = false; + } + + if( status == true ) + { + pMutexAttributes = &mutexAttributes; - int mutexError = pthread_mutex_init( pNewMutex, NULL ); + /* Set recursive mutex type. */ + mutexError = pthread_mutexattr_settype( &mutexAttributes, + PTHREAD_MUTEX_RECURSIVE ); + + if( mutexError != 0 ) + { + IotLogError( "Failed to set recursive mutex type. errno=%d.", + mutexError ); + + status = false; + } + } + } + else + { + IotLogDebug( "Creating new mutex %p.", pNewMutex ); + } + + mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); if( mutexError != 0 ) { @@ -356,6 +393,12 @@ bool IotMutex_Create( IotMutex_t * const pNewMutex ) status = false; } + /* Destroy any created mutex attributes. */ + if( pMutexAttributes != NULL ) + { + ( void ) pthread_mutexattr_destroy( &mutexAttributes ); + } + return status; } diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 4c63de13a5..b73691acac 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -856,7 +856,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, ( void ) memset( pNetworkConnection, 0x00, sizeof( IotNetworkConnectionOpenssl_t ) ); /* Create the network connection mutex. */ - if( IotMutex_Create( &( pNetworkConnection->mutex ) ) == false ) + if( IotMutex_Create( &( pNetworkConnection->mutex ), false ) == false ) { IotLogError( "Failed to create network connection mutex." ); diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 1474fe0283..273d0573d0 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -848,6 +848,11 @@ TEST( MQTT_System, RestorePreviousSession ) AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); AwsIotTest_NetworkCleanup(); } + else + { + /* Close network connection on test failure. */ + AwsIotTest_NetworkClose( NULL ); + } IotSemaphore_Destroy( &waitSem ); diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index 6f859e10b0..04d9a5ecc5 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -846,7 +846,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ), false ) ); IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) @@ -907,7 +907,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ), false ) ); IotListDouble_Create( &( mqttConnection.subscriptionList ) ); if( TEST_PROTECT() ) diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c index 318c77ef40..c8dbd4b264 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c @@ -287,7 +287,7 @@ TEST_GROUP( MQTT_Unit_Subscription ); */ TEST_SETUP( MQTT_Unit_Subscription ) { - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( _connection.subscriptionMutex ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( _connection.subscriptionMutex ), false ) ); IotListDouble_Create( &( _connection.subscriptionList ) ); } @@ -334,9 +334,13 @@ TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) */ TEST( MQTT_Unit_Subscription, ListInsertRemove ) { - _mqttSubscription_t node1 = { 0 }; - _mqttSubscription_t node2 = { 0 }; - _mqttSubscription_t node3 = { 0 }; + _mqttSubscription_t node1; + _mqttSubscription_t node2; + _mqttSubscription_t node3; + + ( void ) memset( &node1, 0x00, sizeof( _mqttSubscription_t ) ); + ( void ) memset( &node2, 0x00, sizeof( _mqttSubscription_t ) ); + ( void ) memset( &node3, 0x00, sizeof( _mqttSubscription_t ) ); IotListDouble_InsertHead( &( _connection.subscriptionList ), &( node1.link ) ); diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c b/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c index 0c71c41597..127c60f9b7 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c @@ -29,6 +29,9 @@ #include IOT_CONFIG_FILE #endif +/* Standard includes. */ +#include + /* MQTT internal include. */ #include "private/aws_iot_mqtt_internal.h" @@ -281,7 +284,9 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) TEST( MQTT_Unit_Validate, ValidateReference ) { bool validateStatus = false; - _mqttOperation_t reference = { 0 }; + _mqttOperation_t reference; + + ( void ) memset( &reference, 0x00, sizeof( _mqttOperation_t ) ); /* NULL parameter. */ validateStatus = AwsIotMqttInternal_ValidateReference( NULL ); diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index e0ed622974..489e17294e 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -31,6 +31,7 @@ /* Standard includes. */ #include +#include /* POSIX includes. */ #ifdef POSIX_UNISTD_HEADER @@ -306,7 +307,7 @@ TEST_SETUP( Shadow_Unit_API ) _lastPacketIdentifier = 0; /* Create the mutex that synchronizes the receive callback and send thread. */ - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &_lastPacketMutex ) ); + TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &_lastPacketMutex, false ) ); /* Create the receive thread timer. */ IotClock_TimerCreate( &_receiveTimer, @@ -570,7 +571,9 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) TEST( Shadow_Unit_API, WaitInvalidParameters ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - _shadowOperation_t operation = { 0 }; + _shadowOperation_t operation; + + ( void ) memset( &operation, 0x00, sizeof( _shadowOperation_t ) ); /* NULL reference. */ status = AwsIotShadow_Wait( NULL, 0, NULL, NULL ); From 56d1ff6a1801245e910052daece70e1c6aaaf82a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 6 Feb 2019 16:19:26 -0800 Subject: [PATCH 021/844] Add network close callback. (#272) --- demos/posix/aws_iot_demo_mqtt_posix.c | 2 +- demos/posix/aws_iot_demo_shadow_posix.c | 2 +- lib/include/aws_iot_mqtt.h | 3 +- lib/include/platform/iot_network.h | 76 +++++++++++++------ lib/source/mqtt/aws_iot_mqtt_api.c | 4 +- lib/source/mqtt/iot_mqtt_network.c | 2 +- platform/include/posix/iot_network_openssl.h | 14 +++- .../posix/network/iot_network_openssl.c | 46 +++++++++-- tests/aws_iot_tests_network.c | 19 +++-- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 9 ++- tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 5 +- .../system/aws_iot_tests_shadow_system.c | 4 - 12 files changed, 135 insertions(+), 51 deletions(-) diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/aws_iot_demo_mqtt_posix.c index 0956fdc206..d7555a8f97 100644 --- a/demos/posix/aws_iot_demo_mqtt_posix.c +++ b/demos/posix/aws_iot_demo_mqtt_posix.c @@ -178,7 +178,7 @@ int main( int argc, * However, the network close function is safe to call on a closed connection. * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. */ - IotNetworkOpenssl_Close( &networkConnection ); + IotNetworkOpenssl_Close( 0, &networkConnection ); IotNetworkOpenssl_Destroy( &networkConnection ); } diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index 60e0345d32..8bd2f24321 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -191,7 +191,7 @@ int main( int argc, * However, the network close function is safe to call on a closed connection. * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. */ - IotNetworkOpenssl_Close( &networkConnection ); + IotNetworkOpenssl_Close( 0, &networkConnection ); IotNetworkOpenssl_Destroy( &networkConnection ); } diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/aws_iot_mqtt.h index c60e5a1a70..6a86f75489 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/aws_iot_mqtt.h @@ -639,13 +639,14 @@ typedef struct AwsIotMqttNetIf * If this function is not provided, the network connection will not be closed * by the MQTT library. * + * @param[in] int32_t Currently unused. * @param[in] void * #AwsIotMqttNetIf_t.pDisconnectContext * * @note Optional; set to `NULL` to ignore. The MQTT spec states that connections * must be closed in certain conditions; if this function is not provided, the * MQTT library is noncompliant. */ - IotNetworkError_t ( * disconnect )( void * ); + IotNetworkError_t ( * disconnect )( int32_t, void * ); /* * In addition to providing the network send and disconnect functions, this diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index 1a4735b662..fbb31aed35 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -57,23 +57,25 @@ typedef enum IotNetworkError * Together, they represent a network stack. * - @functionname{platform_network_function_create} * - @functionname{platform_network_function_setreceivecallback} + * - @functionname{platform_network_function_setclosecallback} * - @functionname{platform_network_function_send} * - @functionname{platform_network_function_receive} * - @functionname{platform_network_function_close} - * - @functionname{platform_network_function_closecallback} * - @functionname{platform_network_function_destroy} * - @functionname{platform_network_function_receivecallback} + * - @functionname{platform_network_function_closecallback} */ /** * @functionpage{IotNetworkInterface_t::create,platform_network,create} * @functionpage{IotNetworkInterface_t::setReceiveCallback,platform_network,setreceivecallback} + * @functionpage{IotNetworkInterface_t::setCloseCallback,platform_network,setclosecallback} * @functionpage{IotNetworkInterface_t::send,platform_network,send} * @functionpage{IotNetworkInterface_t::receive,platform_network,receive} * @functionpage{IotNetworkInterface_t::close,platform_network,close} - * @functionpage{IotNetworkInterface_t::closeCallback,platform_network,closecallback} * @functionpage{IotNetworkInterface_t::destroy,platform_network,destroy} * @functionpage{IotNetworkReceiveCallback_t,platform_network,receivecallback} + * @functionpage{IotNetworkCloseCallback_t,platform_network,closecallback} */ /** @@ -133,6 +135,28 @@ typedef int32_t ( * IotNetworkReceiveCallback_t )( void * pContext, void ( * freeReceivedData )( void * ) ); /* @[declare_platform_network_receivecallback] */ +/** + * @brief Callback function to invoke after @ref platform_network_function_close + * is called. + * + * A function with this signature may be set with @ref platform_network_function_setclosecallback + * to be invoked when the network connection is closed. + * + * @ref platform_network_function_close instructs the underlying network stack to + * close a connection. In constrast, this function can be used to inform a higher-level + * application that a connection was closed. + * + * @param[in] reason Meant to convey the reason a connection was closed; meaningful + * values should be defined by the library closing the connection. + * @param[in] pContext A context defined by the library closing the connection. + * @param[in] pConnection The connection that was closed. + */ +/* @[declare_platform_network_closecallback] */ +typedef void ( * IotNetworkCloseCallback_t )( int32_t reason, + void * pContext, + void * pConnection ); +/* @[declare_platform_network_closecallback] */ + /** * @ingroup platform_datatypes_paramstructs * @brief Represents the functions of a network stack. @@ -193,6 +217,30 @@ typedef struct IotNetworkInterface void * pContext ); /* @[declare_platform_network_setreceivecallback] */ + /** + * @brief Register an @ref platform_network_function_closecallback. + * + * Sets an @ref platform_network_function_closecallback to be called when + * this network connection is closed. This function can be used to notify + * higher-level applications of network connection closure. + * + * Each network connection may only have one close callback at any time. + * If this function is called for a connection that already has a close + * callback, the existing callback should be replaced. If the `closeCallback` + * parameter is `NULL`, any existing close callback should be removed. + * + * @param[in] pConnection The connection to associate with the close callback. + * @param[in] closeCallback The function to invoke when the network is closed. + * @param[in] pContext A value to pass as a parameter to the close callback. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + */ + /* @[declare_platform_network_setclosecallback] */ + IotNetworkError_t ( * setCloseCallback )( void * pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ); + /* @[declare_platform_network_setclosecallback] */ + /** * @brief Send data over a return connection. * @@ -250,6 +298,8 @@ typedef struct IotNetworkInterface * In addition to closing the connection, this function should also remove * any active [receive callback](@ref platform_network_function_receivecallback). * + * @param[in] reason The reason for connection closure, defined by a higher-level + * library. It will be passed to @ref platform_network_function_closecallback. * @param[in] pConnection The network connection to close, defined by the * network stack. * @@ -258,28 +308,10 @@ typedef struct IotNetworkInterface * @note It must be safe to call this function on an already-closed connection. */ /* @[declare_platform_network_close] */ - IotNetworkError_t ( * close )( void * pConnection ); + IotNetworkError_t ( * close )( int32_t reason, + void * pConnection ); /* @[declare_platform_network_close] */ - /** - * @brief Callback function to invoke after @ref platform_network_function_close - * is called. - * - * @ref platform_network_function_close instructs the underlying network stack to - * close a connection. In constrast, this function can be used to inform a higher-level - * application that a connection was closed. - * - * @param[in] reason Meant to convey the reason a connection was closed; meaningful - * values should be defined by the library closing the connection. - * @param[in] pContext A context defined by the library closing the connection. - * @param[in] pConnection The connection that was closed. - */ - /* @[declare_platform_network_closecallback] */ - void ( * closeCallback )( int32_t reason, - void * pContext, - void * pConnection ); - /* @[declare_platform_network_closecallback] */ - /** * @brief Free resources used by a network connection. * diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c index e462ff46d1..c43195fc5b 100644 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ b/lib/source/mqtt/aws_iot_mqtt_api.c @@ -465,7 +465,7 @@ static void _timerThread( void * pArgument ) { if( pMqttConnection->network.disconnect != NULL ) { - pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + pMqttConnection->network.disconnect( 0, pMqttConnection->network.pDisconnectContext ); } else { @@ -1316,7 +1316,7 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, * packet was sent. */ if( pMqttConnection->network.disconnect != NULL ) { - pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + pMqttConnection->network.disconnect( 0, pMqttConnection->network.pDisconnectContext ); } else { diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 88e0a74993..5a57e5e4f8 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -432,7 +432,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, if( pConnectionInfo->network.disconnect != NULL ) { - pConnectionInfo->network.disconnect( pConnectionInfo->network.pDisconnectContext ); + pConnectionInfo->network.disconnect( 0, pConnectionInfo->network.pDisconnectContext ); } else { diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index d6d51be351..064ef854c5 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -73,7 +73,9 @@ typedef struct IotNetworkConnectionOpenssl pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ - void * pCallbackContext; /**< @brief The first parameter to pass to the receive callback. */ + void * pReceiveContext; /**< @brief The context for the receive callback. */ + IotNetworkCloseCallback_t closeCallback; /**< @brief Network close callback, if any. */ + void * pCloseContext; /**< @brief The context for the close callback. */ } IotNetworkConnectionOpenssl_t; /** @@ -226,6 +228,14 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ); +/** + * @brief An implementation of #IotNetworkInterface_t::setCloseCallback for + * POSIX systems with OpenSSL. + */ +IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( void * pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ); + /** * @brief An implementation of #IotNetworkInterface_t::send for POSIX systems * with OpenSSL. @@ -246,7 +256,7 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, * @brief An implementation of #IotNetworkInterface_t::close for POSIX systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ); +IotNetworkError_t IotNetworkOpenssl_Close( int32_t reason, void * pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::destroy for POSIX systems diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index b73691acac..7103f45f28 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -104,6 +104,7 @@ const IotNetworkInterface_t _IotNetworkOpenssl = { .create = IotNetworkOpenssl_Create, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .setCloseCallback = IotNetworkOpenssl_SetCloseCallback, .send = IotNetworkOpenssl_Send, .receive = IotNetworkOpenssl_Receive, .close = IotNetworkOpenssl_Close, @@ -230,7 +231,7 @@ static void * _networkReceiveThread( void * pArgument ) * terminate this thread. */ if( pNetworkConnection->receiveCallback != NULL ) { - bytesProcessed = pNetworkConnection->receiveCallback( pNetworkConnection->pCallbackContext, + bytesProcessed = pNetworkConnection->receiveCallback( pNetworkConnection->pReceiveContext, pNetworkConnection, pReceiveBuffer, ( size_t ) bytesRead, @@ -276,7 +277,7 @@ static void * _networkReceiveThread( void * pArgument ) /* Clear data about the callback. */ pNetworkConnection->receiveThreadStatus = _TERMINATED; pNetworkConnection->receiveCallback = NULL; - pNetworkConnection->pCallbackContext = NULL; + pNetworkConnection->pReceiveContext = NULL; /* Unlock the connection mutex before exiting. */ IotMutex_Unlock( &( pNetworkConnection->mutex ) ); @@ -751,7 +752,7 @@ static IotNetworkError_t _cancelReceiveThread( IotNetworkConnectionOpenssl_t * c pNetworkConnection->receiveThreadStatus = _NONE; ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); pNetworkConnection->receiveCallback = NULL; - pNetworkConnection->pCallbackContext = NULL; + pNetworkConnection->pReceiveContext = NULL; } } @@ -925,7 +926,7 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, { /* Update the callback and parameter. */ pNetworkConnection->receiveCallback = receiveCallback; - pNetworkConnection->pCallbackContext = pContext; + pNetworkConnection->pReceiveContext = pContext; posixError = pthread_create( &pNetworkConnection->receiveThread, NULL, @@ -954,6 +955,27 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, /*-----------------------------------------------------------*/ +IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( void * pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ) +{ + /* Cast function parameter to correct type. */ + IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; + + /* Lock the mutex to prevent a concurrent call to close. */ + IotMutex_Lock( &( pNetworkConnection->mutex ) ); + + /* Replace the existing close callback. */ + pNetworkConnection->closeCallback = closeCallback; + pNetworkConnection->pCloseContext = pContext; + + IotMutex_Unlock( &( pNetworkConnection->mutex ) ); + + return IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + size_t IotNetworkOpenssl_Send( void * pConnection, const uint8_t * pMessage, size_t messageLength ) @@ -1132,8 +1154,12 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) +IotNetworkError_t IotNetworkOpenssl_Close( int32_t reason, + void * pConnection ) { + IotNetworkCloseCallback_t closeCallback = NULL; + void * pCloseContext = NULL; + /* Cast function parameter to correct type. */ IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; @@ -1171,9 +1197,19 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) pNetworkConnection->socket ); } + /* Copy close callback and context to invoke after mutex unlock. */ + closeCallback = pNetworkConnection->closeCallback; + pCloseContext = pNetworkConnection->pCloseContext; + /* Unlock the connection mutex. */ IotMutex_Unlock( &( pNetworkConnection->mutex ) ); + /* Invoke close callback. */ + if( closeCallback != NULL ) + { + closeCallback( reason, pCloseContext, pNetworkConnection ); + } + return IOT_NETWORK_SUCCESS; } diff --git a/tests/aws_iot_tests_network.c b/tests/aws_iot_tests_network.c index 66f7a45b9a..c32a951c7d 100644 --- a/tests/aws_iot_tests_network.c +++ b/tests/aws_iot_tests_network.c @@ -74,12 +74,14 @@ bool AwsIotTest_NetworkConnect( void * const pNewConnection, /** * @brief Network interface close connection function for the tests. * + * @param[in] reason Currently unused. * @param[in] pDisconnectContext The connection to close. Pass NULL to close * the global network connection created by #AwsIotTest_NetworkSetup. * * @return Always returns #IOT_NETWORK_SUCCESS. */ -IotNetworkError_t AwsIotTest_NetworkClose( void * pDisconnectContext ); +IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, + void * pDisconnectContext ); /** * @brief Network interface cleanup function for the tests. @@ -146,7 +148,7 @@ void AwsIotTest_NetworkCleanup( void ) /* Close the TCP connection to the server. */ if( _networkConnectionCreated == true ) { - AwsIotTest_NetworkClose( NULL ); + AwsIotTest_NetworkClose( 0, NULL ); AwsIotTest_NetworkDestroy( &_networkConnection ); _networkConnectionCreated = false; } @@ -191,9 +193,9 @@ bool AwsIotTest_NetworkConnect( void * const pNewConnection, /* Set the MQTT receive callback. */ if( IotNetworkOpenssl_SetReceiveCallback( pNewConnection, AwsIotMqtt_ReceiveCallback, - pMqttConnection) != IOT_NETWORK_SUCCESS ) + pMqttConnection ) != IOT_NETWORK_SUCCESS ) { - IotNetworkOpenssl_Close( pNewConnection ); + IotNetworkOpenssl_Close( 0, pNewConnection ); IotNetworkOpenssl_Destroy( pNewConnection ); return false; @@ -204,20 +206,23 @@ bool AwsIotTest_NetworkConnect( void * const pNewConnection, /*-----------------------------------------------------------*/ -IotNetworkError_t AwsIotTest_NetworkClose( void * pDisconnectContext ) +IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, + void * pDisconnectContext ) { IotNetworkConnectionOpenssl_t * pNetworkConnection = ( IotNetworkConnectionOpenssl_t * ) pDisconnectContext; + ( void ) reason; + /* Close the provided network handle; if that is NULL, close the * global network handle. */ if( ( pNetworkConnection != NULL ) && ( pNetworkConnection != &_networkConnection ) ) { - IotNetworkOpenssl_Close( pNetworkConnection ); + IotNetworkOpenssl_Close( 0, pNetworkConnection ); } else if( _networkConnectionCreated == true ) { - IotNetworkOpenssl_Close( &_networkConnection ); + IotNetworkOpenssl_Close( 0, &_networkConnection ); } return IOT_NETWORK_SUCCESS; diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 273d0573d0..10617bde9b 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -122,7 +122,8 @@ extern bool AwsIotTest_NetworkSetup( void ); extern void AwsIotTest_NetworkCleanup( void ); extern bool AwsIotTest_NetworkConnect( void * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ); -extern void AwsIotTest_NetworkClose( void * pDisconnectContext ); +extern void AwsIotTest_NetworkClose( int32_t reason, + void * pDisconnectContext ); extern void AwsIotTest_NetworkDestroy( void * pConnection ); /* Network variables used by the tests, declared in one of the test network @@ -738,7 +739,7 @@ TEST( MQTT_System, LastWillAndTestament ) /* Abruptly close the MQTT connection. This should cause the LWT * to be sent to the LWT listener. */ - AwsIotTest_NetworkClose( NULL ); + AwsIotTest_NetworkClose( 0, NULL ); AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, true ); AwsIotTest_NetworkDestroy( NULL ); @@ -757,7 +758,7 @@ TEST( MQTT_System, LastWillAndTestament ) if( lwtListenerCreated == true ) { - AwsIotTest_NetworkClose( &lwtListenerConnection ); + AwsIotTest_NetworkClose( 0, &lwtListenerConnection ); AwsIotTest_NetworkDestroy( &lwtListenerConnection ); } } @@ -851,7 +852,7 @@ TEST( MQTT_System, RestorePreviousSession ) else { /* Close network connection on test failure. */ - AwsIotTest_NetworkClose( NULL ); + AwsIotTest_NetworkClose( 0, NULL ); } IotSemaphore_Destroy( &waitSem ); diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c index 04d9a5ecc5..79c67e63cb 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c @@ -265,10 +265,13 @@ static size_t _dupChecker( void * pSendContext, /** * @brief A disconnect function that only reports whether it was invoked. */ -static IotNetworkError_t _disconnect( void * pDisconnectContext ) +static IotNetworkError_t _disconnect( int32_t reason, + void * pDisconnectContext ) { bool * pDisconnectInvoked = ( bool * ) pDisconnectContext; + ( void ) reason; + /* This function just sets a flag testifying that it was invoked. */ if( pDisconnectInvoked != NULL ) { diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index ac227a5412..e750122335 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -118,10 +118,6 @@ typedef struct _operationCompleteParams * the test network function files. */ extern bool AwsIotTest_NetworkSetup( void ); extern void AwsIotTest_NetworkCleanup( void ); -extern bool AwsIotTest_NetworkConnect( void * const pNewConnection, - AwsIotMqttConnection_t * pMqttConnection ); -extern void AwsIotTest_NetworkClose( void * pDisconnectContext ); -extern void AwsIotTest_NetworkDestroy( void * pConnection ); /* Network variables used by the tests, declared in one of the test network * function files. */ From 9bfeef89b68d8bd3e2b0f7bd917d75d41491c131 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 7 Feb 2019 09:54:07 -0800 Subject: [PATCH 022/844] Add iot_tests_common to CI. (#273) --- scripts/build_check_commit.sh | 6 ++++++ scripts/build_check_pr.sh | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index e3fb0de410..231aaa5878 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -32,6 +32,9 @@ echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make +# Run common tests (no network required). +./bin/iot_tests_common + # Run MQTT tests and demo against AWS IoT. ./bin/aws_iot_tests_mqtt ./bin/aws_iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" @@ -45,6 +48,9 @@ rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make +# Run common tests in static memory mode (no network required). +./bin/iot_tests_common + # Run MQTT and Shadow tests in static memory mode. ./bin/aws_iot_tests_mqtt ./bin/aws_iot_tests_shadow diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh index 20e55abbf1..87e6a01e19 100644 --- a/scripts/build_check_pr.sh +++ b/scripts/build_check_pr.sh @@ -18,6 +18,9 @@ rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" make +# Run common tests (no network required). +./bin/iot_tests_common + # Run MQTT tests and demos against Mosquitto. ./bin/aws_iot_tests_mqtt ./bin/aws_iot_demo_mqtt -h test.mosquitto.org -p 1883 -n @@ -30,6 +33,9 @@ rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DIOT_STATIC_MEMORY_ONLY=1 $COMPILER_OPTIONS" make +# Run common tests in static memory mode (no network required). +./bin/iot_tests_common + # Run MQTT tests and no-network Shadow tests in static memory mode. ./bin/aws_iot_tests_mqtt ./bin/aws_iot_tests_shadow -n From 93d590d06b6a0287fb0bb9dc0ea9197709e5e37f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 7 Feb 2019 13:43:29 -0800 Subject: [PATCH 023/844] Use generic network interface in tests. (#274) --- tests/aws_iot_tests_network.c | 72 +++++++++---------- tests/iot_tests_config.h | 62 ++++++++++++---- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 12 ++-- 3 files changed, 88 insertions(+), 58 deletions(-) diff --git a/tests/aws_iot_tests_network.c b/tests/aws_iot_tests_network.c index c32a951c7d..a7e3ee2b4b 100644 --- a/tests/aws_iot_tests_network.c +++ b/tests/aws_iot_tests_network.c @@ -36,8 +36,8 @@ /* MQTT include. */ #include "aws_iot_mqtt.h" -/* POSIX+OpenSSL network include. */ -#include "posix/iot_network_openssl.h" +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER /*-----------------------------------------------------------*/ @@ -68,20 +68,20 @@ void AwsIotTest_NetworkCleanup( void ); * * @return true if a new network connection was successfully created; false otherwise. */ -bool AwsIotTest_NetworkConnect( void * const pNewConnection, +bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ); /** * @brief Network interface close connection function for the tests. * * @param[in] reason Currently unused. - * @param[in] pDisconnectContext The connection to close. Pass NULL to close + * @param[in] pNetworkConnection The connection to close. Pass NULL to close * the global network connection created by #AwsIotTest_NetworkSetup. * * @return Always returns #IOT_NETWORK_SUCCESS. */ IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, - void * pDisconnectContext ); + void * pNetworkConnection ); /** * @brief Network interface cleanup function for the tests. @@ -100,7 +100,12 @@ static bool _networkConnectionCreated = false; /** * @brief The network connection shared among the tests. */ -static IotNetworkConnectionOpenssl_t _networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; +static IotTestNetworkConnection_t _networkConnection = IOT_TEST_NETWORK_CONNECTION_INITIALIZER; + +/** + * @brief Network interface to use in the tests. + */ +static const IotNetworkInterface_t * const _pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; /** * @brief The MQTT network interface shared among the tests. @@ -117,7 +122,7 @@ AwsIotMqttConnection_t _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITI bool AwsIotTest_NetworkSetup( void ) { /* Initialize the network library. */ - if( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) { return false; } @@ -125,7 +130,7 @@ bool AwsIotTest_NetworkSetup( void ) if( AwsIotTest_NetworkConnect( &_networkConnection, &_AwsIotTestMqttConnection ) == false ) { - IotNetworkOpenssl_Cleanup(); + IotTestNetwork_Cleanup(); return false; } @@ -134,7 +139,7 @@ bool AwsIotTest_NetworkSetup( void ) _AwsIotTestNetworkInterface.pDisconnectContext = NULL; _AwsIotTestNetworkInterface.disconnect = AwsIotTest_NetworkClose; _AwsIotTestNetworkInterface.pSendContext = ( void * ) &_networkConnection; - _AwsIotTestNetworkInterface.send = IotNetworkOpenssl_Send; + _AwsIotTestNetworkInterface.send = _pNetworkInterface->send; _networkConnectionCreated = true; @@ -154,7 +159,7 @@ void AwsIotTest_NetworkCleanup( void ) } /* Clean up the network library. */ - IotNetworkOpenssl_Cleanup(); + IotTestNetwork_Cleanup(); /* Clear the network interface. */ ( void ) memset( &_AwsIotTestNetworkInterface, 0x00, sizeof( AwsIotMqttNetIf_t ) ); @@ -162,41 +167,33 @@ void AwsIotTest_NetworkCleanup( void ) /*-----------------------------------------------------------*/ -bool AwsIotTest_NetworkConnect( void * const pNewConnection, +bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ) { - IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; - IotNetworkCredentialsOpenssl_t * pCredentials = NULL; - - serverInfo.pHostName = AWS_IOT_TEST_SERVER; - serverInfo.port = AWS_IOT_TEST_PORT; + IotTestNetworkServerInfo_t serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + IotTestNetworkCredentials_t * pCredentials = NULL; /* Set up TLS if the endpoint is secured. These tests should always use ALPN. */ #if AWS_IOT_TEST_SECURED_CONNECTION == 1 - IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER; + IotTestNetworkCredentials_t credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; pCredentials = &credentials; - - /* Set credentials for secured connection. */ - credentials.pRootCaPath = AWS_IOT_TEST_ROOT_CA; - credentials.pClientCertPath = AWS_IOT_TEST_CLIENT_CERT; - credentials.pPrivateKeyPath = AWS_IOT_TEST_PRIVATE_KEY; - #endif /* if AWS_IOT_TEST_SECURED_CONNECTION == 1 */ + #endif /* Open a connection to the test server. */ - if( IotNetworkOpenssl_Create( &serverInfo, - pCredentials, - pNewConnection ) != IOT_NETWORK_SUCCESS ) + if( _pNetworkInterface->create( &serverInfo, + pCredentials, + pNewConnection ) != IOT_NETWORK_SUCCESS ) { return false; } /* Set the MQTT receive callback. */ - if( IotNetworkOpenssl_SetReceiveCallback( pNewConnection, - AwsIotMqtt_ReceiveCallback, - pMqttConnection ) != IOT_NETWORK_SUCCESS ) + if( _pNetworkInterface->setReceiveCallback( pNewConnection, + AwsIotMqtt_ReceiveCallback, + pMqttConnection ) != IOT_NETWORK_SUCCESS ) { - IotNetworkOpenssl_Close( 0, pNewConnection ); - IotNetworkOpenssl_Destroy( pNewConnection ); + _pNetworkInterface->close( 0, pNewConnection ); + _pNetworkInterface->destroy( pNewConnection ); return false; } @@ -207,10 +204,9 @@ bool AwsIotTest_NetworkConnect( void * const pNewConnection, /*-----------------------------------------------------------*/ IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, - void * pDisconnectContext ) + void * pNetworkConnection ) { - IotNetworkConnectionOpenssl_t * pNetworkConnection = ( IotNetworkConnectionOpenssl_t * ) pDisconnectContext; - + /* Ignore the reason for closure. */ ( void ) reason; /* Close the provided network handle; if that is NULL, close the @@ -218,11 +214,11 @@ IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, if( ( pNetworkConnection != NULL ) && ( pNetworkConnection != &_networkConnection ) ) { - IotNetworkOpenssl_Close( 0, pNetworkConnection ); + _pNetworkInterface->close( 0, pNetworkConnection ); } else if( _networkConnectionCreated == true ) { - IotNetworkOpenssl_Close( 0, &_networkConnection ); + _pNetworkInterface->close( 0, &_networkConnection ); } return IOT_NETWORK_SUCCESS; @@ -236,13 +232,13 @@ void AwsIotTest_NetworkDestroy( void * pNetworkConnection ) ( pNetworkConnection != &_networkConnection ) ) { /* Wrap the network interface's destroy function. */ - IotNetworkOpenssl_Destroy( pNetworkConnection ); + _pNetworkInterface->destroy( pNetworkConnection ); } else { if( _networkConnectionCreated == true ) { - IotNetworkOpenssl_Destroy( &_networkConnection ); + _pNetworkInterface->destroy( &_networkConnection ); _networkConnectionCreated = false; } } diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index c2a62c3d50..89e85403a8 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -40,25 +40,25 @@ /* AWS IoT endpoint and credentials. */ #ifndef AWS_IOT_TEST_SERVER - #define AWS_IOT_TEST_SERVER "" + #define AWS_IOT_TEST_SERVER "" #endif #ifndef AWS_IOT_TEST_PORT - #define AWS_IOT_TEST_PORT ( 443 ) + #define AWS_IOT_TEST_PORT ( 443 ) #endif #ifndef AWS_IOT_TEST_ROOT_CA - #define AWS_IOT_TEST_ROOT_CA "" + #define AWS_IOT_TEST_ROOT_CA "" #endif #ifndef AWS_IOT_TEST_CLIENT_CERT - #define AWS_IOT_TEST_CLIENT_CERT "" + #define AWS_IOT_TEST_CLIENT_CERT "" #endif #ifndef AWS_IOT_TEST_PRIVATE_KEY - #define AWS_IOT_TEST_PRIVATE_KEY "" + #define AWS_IOT_TEST_PRIVATE_KEY "" #endif #endif /* if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 */ /* Shadow tests configuration. */ #ifndef AWS_IOT_TEST_SHADOW_THING_NAME - #define AWS_IOT_TEST_SHADOW_THING_NAME "" + #define AWS_IOT_TEST_SHADOW_THING_NAME "" #endif /* Linear containers library configuration. */ @@ -75,15 +75,15 @@ /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define IotNetwork_Malloc unity_malloc_mt -#define IotNetwork_Free unity_free_mt -#define IotThreads_Malloc unity_malloc_mt -#define IotThreads_Free unity_free_mt -#define IotLogging_Malloc unity_malloc_mt -#define IotLogging_Free unity_free_mt +#define IotNetwork_Malloc unity_malloc_mt +#define IotNetwork_Free unity_free_mt +#define IotThreads_Malloc unity_malloc_mt +#define IotThreads_Free unity_free_mt +#define IotLogging_Malloc unity_malloc_mt +#define IotLogging_Free unity_free_mt /* #define IotLogging_StaticBufferSize */ -#define AwsIotTest_Malloc unity_malloc_mt -#define AwsIotTest_Free unity_free_mt +#define AwsIotTest_Malloc unity_malloc_mt +#define AwsIotTest_Free unity_free_mt /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ @@ -113,6 +113,40 @@ #define AwsIotShadow_FreeSubscription unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ +/* Network header to include in the tests. */ +#define IOT_TEST_NETWORK_HEADER "posix/iot_network_openssl.h" + +/* Network types to use in the tests. These are forward declarations. */ +typedef struct IotNetworkConnectionOpenssl IotTestNetworkConnection_t; +typedef struct IotNetworkServerInfoOpenssl IotTestNetworkServerInfo_t; +typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; + +/* Initializers for the tests' network types. */ +#define IOT_TEST_NETWORK_CONNECTION_INITIALIZER IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER +#define IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER \ + { \ + .pHostName = AWS_IOT_TEST_SERVER, \ + .port = AWS_IOT_TEST_PORT \ + } +#if AWS_IOT_TEST_SECURED_CONNECTION == 1 + #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER \ + { \ + .pAlpnProtos = "\x0ex-amzn-mqtt-ca", \ + .pRootCaPath = AWS_IOT_TEST_ROOT_CA, \ + .pClientCertPath = AWS_IOT_TEST_CLIENT_CERT, \ + .pPrivateKeyPath = AWS_IOT_TEST_PRIVATE_KEY \ + } +#else + #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER +#endif + +/* Network interface to use in the tests. */ +#define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL + +/* Network initialization and cleanup functions to use in the tests. */ +#define IotTestNetwork_Init IotNetworkOpenssl_Init +#define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup + /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ #include IOT_SYSTEM_TYPES_FILE diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c index 10617bde9b..b9bd3e7a3a 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ b/tests/mqtt/system/aws_iot_tests_mqtt_system.c @@ -39,8 +39,8 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" -/* POSIX+OpenSSL network include. */ -#include "posix/iot_network_openssl.h" +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER /* Test framework includes. */ #include "unity_fixture.h" @@ -120,10 +120,10 @@ typedef struct _operationCompleteParams * the test network function files. */ extern bool AwsIotTest_NetworkSetup( void ); extern void AwsIotTest_NetworkCleanup( void ); -extern bool AwsIotTest_NetworkConnect( void * const pNewConnection, +extern bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnection, AwsIotMqttConnection_t * pMqttConnection ); -extern void AwsIotTest_NetworkClose( int32_t reason, - void * pDisconnectContext ); +extern IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, + void * pNetworkConnection ); extern void AwsIotTest_NetworkDestroy( void * pConnection ); /* Network variables used by the tests, declared in one of the test network @@ -664,7 +664,7 @@ TEST( MQTT_System, LastWillAndTestament ) connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotMqttSubscription_t willSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotNetworkConnectionOpenssl_t lwtListenerConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; + IotTestNetworkConnection_t lwtListenerConnection = IOT_TEST_NETWORK_CONNECTION_INITIALIZER; IotSemaphore_t waitSem; /* Create the wait semaphore. */ From 7082a92da53b2653dd006a611fb69fd22ad8bcb5 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 21 Feb 2019 16:38:49 -0800 Subject: [PATCH 024/844] Update MQTT threading model. (#279) --- cbmc/README.md | 14 +- .../DeserializeConnack_harness.c | 8 +- cbmc/proofs/DeserializeConnack/Makefile | 2 +- .../DeserializePingresp_harness.c | 8 +- cbmc/proofs/DeserializePingresp/Makefile | 2 +- .../DeserializePuback_harness.c | 10 +- cbmc/proofs/DeserializePuback/Makefile | 2 +- .../DeserializePublish_harness.c | 14 +- cbmc/proofs/DeserializePublish/Makefile | 2 +- .../DeserializeSuback_harness.c | 14 +- cbmc/proofs/DeserializeSuback/Makefile | 8 +- .../DeserializeUnsuback_harness.c | 10 +- cbmc/proofs/DeserializeUnsuback/Makefile | 2 +- cbmc/proofs/Makefile.common | 2 +- demos/aws_iot_demo_shadow.c | 28 +- demos/{aws_iot_demo.h => iot_demo.h} | 28 +- demos/iot_demo_config.h | 34 +- .../{aws_iot_demo_mqtt.c => iot_demo_mqtt.c} | 182 +- demos/posix/CMakeLists.txt | 14 +- demos/posix/aws_iot_demo_shadow_posix.c | 34 +- ...common_posix.c => iot_demo_common_posix.c} | 36 +- ...emo_mqtt_posix.c => iot_demo_mqtt_posix.c} | 40 +- ...{aws_iot_demo_posix.h => iot_demo_posix.h} | 30 +- doc/config/mqtt | 2 +- doc/config/taskpool | 33 + doc/guide/building.txt | 18 +- doc/guide/style.txt | 145 +- doc/lib/mqtt.txt | 178 +- doc/lib/static_memory.txt | 3 - doc/lib/taskpool.txt | 191 ++ doc/mainpage.txt | 52 +- .../taskpool_design_typicaloperation.png | Bin 0 -> 70318 bytes .../taskpool_design_typicaloperation.pu | 77 + lib/include/aws_iot_shadow.h | 28 +- lib/include/iot_linear_containers.h | 4 +- lib/include/{aws_iot_mqtt.h => iot_mqtt.h} | 991 +++++---- lib/include/iot_taskpool.h | 490 +++++ lib/include/platform/iot_network.h | 55 +- lib/include/platform/iot_threads.h | 10 - lib/include/private/aws_iot_mqtt_internal.h | 907 -------- lib/include/private/aws_iot_shadow_internal.h | 158 +- lib/include/private/iot_error.h | 115 + lib/include/private/iot_mqtt_internal.h | 938 ++++++++ lib/include/private/iot_static_memory.h | 107 +- lib/include/private/iot_taskpool_internal.h | 220 ++ lib/include/types/iot_platform_types.h | 10 + lib/include/types/iot_taskpool_types.h | 363 ++++ lib/source/common/CMakeLists.txt | 2 + lib/source/common/iot_common.c | 23 +- lib/source/common/iot_taskpool.c | 1553 ++++++++++++++ .../aws_iot_static_memory_shadow.c | 7 - .../static_memory/iot_static_memory_mqtt.c | 47 +- .../iot_static_memory_taskpool.c | 149 ++ lib/source/mqtt/CMakeLists.txt | 10 +- lib/source/mqtt/aws_iot_mqtt_api.c | 1882 ---------------- lib/source/mqtt/aws_iot_mqtt_operation.c | 735 ------- lib/source/mqtt/iot_mqtt_api.c | 1884 +++++++++++++++++ lib/source/mqtt/iot_mqtt_network.c | 379 ++-- lib/source/mqtt/iot_mqtt_operation.c | 1299 ++++++++++++ ..._mqtt_serialize.c => iot_mqtt_serialize.c} | 1030 ++++++--- ...subscription.c => iot_mqtt_subscription.c} | 413 ++-- ...ot_mqtt_validate.c => iot_mqtt_validate.c} | 438 +++- lib/source/shadow/aws_iot_shadow_api.c | 170 +- lib/source/shadow/aws_iot_shadow_operation.c | 157 +- lib/source/shadow/aws_iot_shadow_parser.c | 16 +- .../shadow/aws_iot_shadow_subscription.c | 163 +- platform/include/posix/iot_network_openssl.h | 12 +- .../posix/network/iot_network_openssl.c | 111 +- scripts/build_check_commit.sh | 10 +- scripts/build_check_pr.sh | 10 +- scripts/coverage.sh | 14 +- tests/common/CMakeLists.txt | 3 +- tests/common/iot_tests_common.c | 26 +- tests/common/unit/iot_tests_taskpool.c | 1426 +++++++++++++ tests/iot_tests_config.h | 110 +- ...ot_tests_network.c => iot_tests_network.c} | 72 +- tests/mqtt/CMakeLists.txt | 20 +- ...t_access_mqtt.h => iot_test_access_mqtt.h} | 45 +- ..._mqtt_api.c => iot_test_access_mqtt_api.c} | 19 +- ...ize.c => iot_test_access_mqtt_serialize.c} | 12 +- ....c => iot_test_access_mqtt_subscription.c} | 14 +- ...{aws_iot_tests_mqtt.c => iot_tests_mqtt.c} | 22 +- tests/mqtt/system/aws_iot_tests_mqtt_system.c | 879 -------- ..._mqtt_stress.c => iot_tests_mqtt_stress.c} | 273 +-- tests/mqtt/system/iot_tests_mqtt_system.c | 1182 +++++++++++ tests/mqtt/unit/aws_iot_tests_mqtt_api.c | 954 --------- tests/mqtt/unit/iot_tests_mqtt_api.c | 1384 ++++++++++++ ...qtt_receive.c => iot_tests_mqtt_receive.c} | 538 ++--- ...iption.c => iot_tests_mqtt_subscription.c} | 570 +++-- ...t_validate.c => iot_tests_mqtt_validate.c} | 231 +- tests/shadow/CMakeLists.txt | 2 +- tests/shadow/aws_iot_tests_shadow.c | 20 +- .../system/aws_iot_tests_shadow_system.c | 110 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 72 +- .../shadow/unit/aws_iot_tests_shadow_parser.c | 36 +- 95 files changed, 15552 insertions(+), 8601 deletions(-) rename demos/{aws_iot_demo.h => iot_demo.h} (72%) rename demos/{aws_iot_demo_mqtt.c => iot_demo_mqtt.c} (78%) rename demos/posix/{aws_iot_demo_common_posix.c => iot_demo_common_posix.c} (89%) rename demos/posix/{aws_iot_demo_mqtt_posix.c => iot_demo_mqtt_posix.c} (83%) rename demos/posix/{aws_iot_demo_posix.h => iot_demo_posix.h} (81%) create mode 100644 doc/config/taskpool create mode 100644 doc/lib/taskpool.txt create mode 100644 doc/plantuml/images/taskpool_design_typicaloperation.png create mode 100644 doc/plantuml/taskpool_design_typicaloperation.pu rename lib/include/{aws_iot_mqtt.h => iot_mqtt.h} (63%) create mode 100644 lib/include/iot_taskpool.h delete mode 100644 lib/include/private/aws_iot_mqtt_internal.h create mode 100644 lib/include/private/iot_error.h create mode 100644 lib/include/private/iot_mqtt_internal.h create mode 100644 lib/include/private/iot_taskpool_internal.h create mode 100644 lib/include/types/iot_taskpool_types.h create mode 100644 lib/source/common/iot_taskpool.c create mode 100644 lib/source/common/static_memory/iot_static_memory_taskpool.c delete mode 100644 lib/source/mqtt/aws_iot_mqtt_api.c delete mode 100644 lib/source/mqtt/aws_iot_mqtt_operation.c create mode 100644 lib/source/mqtt/iot_mqtt_api.c create mode 100644 lib/source/mqtt/iot_mqtt_operation.c rename lib/source/mqtt/{aws_iot_mqtt_serialize.c => iot_mqtt_serialize.c} (69%) rename lib/source/mqtt/{aws_iot_mqtt_subscription.c => iot_mqtt_subscription.c} (52%) rename lib/source/mqtt/{aws_iot_mqtt_validate.c => iot_mqtt_validate.c} (52%) create mode 100644 tests/common/unit/iot_tests_taskpool.c rename tests/{aws_iot_tests_network.c => iot_tests_network.c} (72%) rename tests/mqtt/access/{aws_iot_test_access_mqtt.h => iot_test_access_mqtt.h} (63%) rename tests/mqtt/access/{aws_iot_test_access_mqtt_api.c => iot_test_access_mqtt_api.c} (70%) rename tests/mqtt/access/{aws_iot_test_access_mqtt_serialize.c => iot_test_access_mqtt_serialize.c} (78%) rename tests/mqtt/access/{aws_iot_test_access_mqtt_subscription.c => iot_test_access_mqtt_subscription.c} (79%) rename tests/mqtt/{aws_iot_tests_mqtt.c => iot_tests_mqtt.c} (91%) delete mode 100644 tests/mqtt/system/aws_iot_tests_mqtt_system.c rename tests/mqtt/system/{aws_iot_tests_mqtt_stress.c => iot_tests_mqtt_stress.c} (65%) create mode 100644 tests/mqtt/system/iot_tests_mqtt_system.c delete mode 100644 tests/mqtt/unit/aws_iot_tests_mqtt_api.c create mode 100644 tests/mqtt/unit/iot_tests_mqtt_api.c rename tests/mqtt/unit/{aws_iot_tests_mqtt_receive.c => iot_tests_mqtt_receive.c} (76%) rename tests/mqtt/unit/{aws_iot_tests_mqtt_subscription.c => iot_tests_mqtt_subscription.c} (58%) rename tests/mqtt/unit/{aws_iot_tests_mqtt_validate.c => iot_tests_mqtt_validate.c} (57%) diff --git a/cbmc/README.md b/cbmc/README.md index 724554c079..ed527dd78b 100644 --- a/cbmc/README.md +++ b/cbmc/README.md @@ -2,17 +2,17 @@ This directory contains CBMC proofs of memory safety of MQTT entry points. [CBMC](http://www.cprover.org/cbmc/) is a bounded model checker for C available from the GitHub [repository](https://github.com/diffblue/cbmc). Each proof is in a separate subdirectory of proofs: -* DeserializeConnack: AwsIotMqttInternal_DeserializeConnack is memory safe assuming: +* DeserializeConnack: _IotMqtt_DeserializeConnack is memory safe assuming: * We abstract the IotLog_Generic logging function -* DeserializePingresp: AwsIotMqttInternal_DeserializePingresp is memory safe assuming: +* DeserializePingresp: _IotMqtt_DeserializePingresp is memory safe assuming: * We abstract the IotLog_Generic logging function -* DeserializePuback: AwsIotMqttInternal_DeserializePuback is memory safe assuming: +* DeserializePuback: _IotMqtt_DeserializePuback is memory safe assuming: * We abstract the IotLog_Generic logging function -* DeserializePublish: AwsIotMqttInternal_DeserializePublish is memory safe assuming: +* DeserializePublish: _IotMqtt_DeserializePublish is memory safe assuming: * We abstract the IotLog_Generic logging function -* DeserializeSuback: AwsIotMqttInternal_DeserializeSuback is memory safe assuming: +* DeserializeSuback: _IotMqtt_DeserializeSuback is memory safe assuming: * We abstract the IotLog_Generic logging function - * We abstract the AwsIotMqttInternal_RemoveSubscriptionByPacket function + * We abstract the _IotMqtt_RemoveSubscriptionByPacket function * We bound the length of the buffer being parsed -* DeserializeUnsuback: AwsIotMqttInternal_DeserializeUnsuback is memory safe assuming: +* DeserializeUnsuback: _IotMqtt_DeserializeUnsuback is memory safe assuming: * We abstract the IotLog_Generic logging function diff --git a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c index b06f9559fc..90b7ba13dc 100644 --- a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c +++ b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c @@ -1,5 +1,5 @@ #include IOT_CONFIG_FILE -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" #include @@ -11,7 +11,7 @@ void harness() uint8_t * pConnackStart = malloc( sizeof( uint8_t ) * dataLength ); size_t bytesProcessed; - AwsIotMqttInternal_DeserializeConnack( pConnackStart, - dataLength, - &bytesProcessed ); + _IotMqtt_DeserializeConnack( pConnackStart, + dataLength, + &bytesProcessed ); } diff --git a/cbmc/proofs/DeserializeConnack/Makefile b/cbmc/proofs/DeserializeConnack/Makefile index 2301d6a297..2860fd43e0 100644 --- a/cbmc/proofs/DeserializeConnack/Makefile +++ b/cbmc/proofs/DeserializeConnack/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c index 26c7274fc7..cf683607bd 100644 --- a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c +++ b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c @@ -1,5 +1,5 @@ #include IOT_CONFIG_FILE -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" #include @@ -11,7 +11,7 @@ void harness() uint8_t * pPingrespStart = malloc( sizeof( uint8_t ) * dataLength ); size_t bytesProcessed; - AwsIotMqttInternal_DeserializePingresp( pPingrespStart, - dataLength, - &bytesProcessed ); + _IotMqtt_DeserializePingresp( pPingrespStart, + dataLength, + &bytesProcessed ); } diff --git a/cbmc/proofs/DeserializePingresp/Makefile b/cbmc/proofs/DeserializePingresp/Makefile index df0640dda5..6389a45b7a 100644 --- a/cbmc/proofs/DeserializePingresp/Makefile +++ b/cbmc/proofs/DeserializePingresp/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c index b260d4d73f..6d28136572 100644 --- a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c +++ b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c @@ -1,5 +1,5 @@ #include IOT_CONFIG_FILE -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" #include @@ -12,8 +12,8 @@ void harness() uint16_t packetIdentifier; size_t bytesProcessed; - AwsIotMqttInternal_DeserializePuback( pPubackStart, - dataLength, - &packetIdentifier, - &bytesProcessed ); + _IotMqtt_DeserializePuback( pPubackStart, + dataLength, + &packetIdentifier, + &bytesProcessed ); } diff --git a/cbmc/proofs/DeserializePuback/Makefile b/cbmc/proofs/DeserializePuback/Makefile index e234f26227..f16423b336 100644 --- a/cbmc/proofs/DeserializePuback/Makefile +++ b/cbmc/proofs/DeserializePuback/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c index 4c606eaea0..ad8e9253d0 100644 --- a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c +++ b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c @@ -1,5 +1,5 @@ #include IOT_CONFIG_FILE -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" #include @@ -9,13 +9,13 @@ void harness() { size_t dataLength; uint8_t * pPublishStart = malloc( sizeof( uint8_t ) * dataLength ); - AwsIotMqttPublishInfo_t output; + IotMqttPublishInfo_t output; uint16_t packetIdentifier; size_t bytesProcessed; - AwsIotMqttInternal_DeserializePublish( pPublishStart, - dataLength, - &output, - &packetIdentifier, - &bytesProcessed ); + _IotMqtt_DeserializePublish( pPublishStart, + dataLength, + &output, + &packetIdentifier, + &bytesProcessed ); } diff --git a/cbmc/proofs/DeserializePublish/Makefile b/cbmc/proofs/DeserializePublish/Makefile index 4b9f1fe707..2e8c084b9c 100644 --- a/cbmc/proofs/DeserializePublish/Makefile +++ b/cbmc/proofs/DeserializePublish/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c index ed42d5b00a..6b710c9778 100644 --- a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c +++ b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c @@ -1,5 +1,5 @@ #include IOT_CONFIG_FILE -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" #include @@ -7,7 +7,7 @@ void harness() { - AwsIotMqttConnection_t mqttConnection; + IotMqttConnection_t mqttConnection; size_t dataLength; uint8_t * pSubackStart = malloc( sizeof( uint8_t ) * dataLength ); uint16_t packetIdentifier; @@ -15,9 +15,9 @@ void harness() __CPROVER_assume( dataLength <= BUFFER_SIZE ); - AwsIotMqttInternal_DeserializeSuback( mqttConnection, - pSubackStart, - dataLength, - &packetIdentifier, - &bytesProcessed ); + _IotMqtt_DeserializeSuback( mqttConnection, + pSubackStart, + dataLength, + &packetIdentifier, + &bytesProcessed ); } diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/DeserializeSuback/Makefile index b1979b6bef..8c61e7e4aa 100644 --- a/cbmc/proofs/DeserializeSuback/Makefile +++ b/cbmc/proofs/DeserializeSuback/Makefile @@ -4,15 +4,15 @@ BUFFER_SIZE = 100 ABSTRACTIONS = \ --remove-function-body IotLog_Generic \ - --remove-function-body AwsIotMqttInternal_RemoveSubscriptionByPacket \ + --remove-function-body _IotMqtt_RemoveSubscriptionByPacket \ UNWINDING = \ - --unwindset AwsIotMqttInternal_DeserializeSuback.0:$(BUFFER_SIZE) \ + --unwindset _IotMqtt_DeserializeSuback.0:$(BUFFER_SIZE) \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_subscription.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_subscription.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ DEF = -DBUFFER_SIZE=$(BUFFER_SIZE) diff --git a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c index fa09ef72d7..76765c58f4 100644 --- a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c +++ b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c @@ -1,5 +1,5 @@ #include IOT_CONFIG_FILE -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" #include @@ -12,8 +12,8 @@ void harness() uint16_t packetIdentifier; size_t bytesProcessed; - AwsIotMqttInternal_DeserializeUnsuback( pUnsubackStart, - dataLength, - &packetIdentifier, - &bytesProcessed ); + _IotMqtt_DeserializeUnsuback( pUnsubackStart, + dataLength, + &packetIdentifier, + &bytesProcessed ); } diff --git a/cbmc/proofs/DeserializeUnsuback/Makefile b/cbmc/proofs/DeserializeUnsuback/Makefile index 4826339630..be9f045857 100644 --- a/cbmc/proofs/DeserializeUnsuback/Makefile +++ b/cbmc/proofs/DeserializeUnsuback/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/aws_iot_mqtt_serialize.goto \ + $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 61b5f59338..a9c03227bc 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -27,7 +27,7 @@ DEF += \ -DIOT_CONFIG_FILE=\"iot_demo_config.h\" \ -DIOT_SDK_VERSION=\"4.0.0\" \ -DIOT_SYSTEM_TYPES_FILE=\"posix/iot_platform_types_posix.h\" \ - -Dawsiotmqtt_EXPORTS \ + -Diotmqtt_EXPORTS \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 3faa4e4f3a..a341fb6d54 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -34,7 +34,7 @@ #include /* Common demo include. */ -#include "aws_iot_demo.h" +#include "iot_demo.h" /* Platform layer includes. */ #include "platform/iot_clock.h" @@ -77,7 +77,7 @@ extern int snprintf( char *, * been established. * @param[in] pThingName NULL-terminated Thing Name to use for this demo. * @param[in] pMqttConnection Pointer to the MQTT connection to use. This MQTT - * connection must be initialized to AWS_IOT_MQTT_CONNECTION_INITIALIZER. + * connection must be initialized to IOT_MQTT_CONNECTION_INITIALIZER. * @param[in] pNetworkInterface Pointer to an MQTT network interface to use. * All necessary members of the network interface should be set before calling * this function. @@ -85,12 +85,12 @@ extern int snprintf( char *, * @return 0 if the demo completes successfully; -1 if some part of it fails. */ int AwsIotDemo_RunShadowDemo( const char * const pThingName, - AwsIotMqttConnection_t * const pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface ) + IotMqttConnection_t * const pMqttConnection, + const IotMqttNetIf_t * const pNetworkInterface ) { int status = 0; - AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; /* Set the common members of the connection info. */ connectInfo.awsIotMqttMode = true; @@ -107,23 +107,23 @@ int AwsIotDemo_RunShadowDemo( const char * const pThingName, connectInfo.clientIdentifierLength ); /* Establish the MQTT connection. */ - mqttStatus = AwsIotMqtt_Connect( pMqttConnection, - pNetworkInterface, - &connectInfo, - _TIMEOUT_MS ); + mqttStatus = IotMqtt_Connect( pMqttConnection, + pNetworkInterface, + &connectInfo, + _TIMEOUT_MS ); - if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "MQTT CONNECT returned error %s.", - AwsIotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( mqttStatus ) ); status = -1; } /* Disconnect the MQTT connection if it was established. */ - if( *pMqttConnection != AWS_IOT_MQTT_CONNECTION_INITIALIZER ) + if( *pMqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) { - AwsIotMqtt_Disconnect( *pMqttConnection, false ); + IotMqtt_Disconnect( *pMqttConnection, false ); } return status; diff --git a/demos/aws_iot_demo.h b/demos/iot_demo.h similarity index 72% rename from demos/aws_iot_demo.h rename to demos/iot_demo.h index d7a0ce753d..8ecf071648 100644 --- a/demos/aws_iot_demo.h +++ b/demos/iot_demo.h @@ -20,12 +20,12 @@ */ /** - * @file aws_iot_demo.h + * @file iot_demo.h * @brief Declares the platform-independent demo functions. */ -#ifndef _AWS_IOT_DEMO_H_ -#define _AWS_IOT_DEMO_H_ +#ifndef _IOT_DEMO_H_ +#define _IOT_DEMO_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -37,11 +37,11 @@ #include /* MQTT include. */ -#include "aws_iot_mqtt.h" +#include "iot_mqtt.h" /* Configure logs for the demos. */ -#ifdef AWS_IOT_LOG_LEVEL_DEMO - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEMO +#ifdef IOT_LOG_LEVEL_DEMO + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_DEMO #else #ifdef IOT_LOG_LEVEL_GLOBAL #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL @@ -55,15 +55,15 @@ /*----------------------------- Demo functions ------------------------------*/ -/* See aws_iot_demo_mqtt.c for documentation of this function. */ -int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, - const char * const pClientIdentifier, - AwsIotMqttConnection_t * const pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface ); +/* See iot_demo_mqtt.c for documentation of this function. */ +int IotDemo_RunMqttDemo( bool awsIotMqttMode, + const char * const pClientIdentifier, + IotMqttConnection_t * const pMqttConnection, + const IotMqttNetIf_t * const pNetworkInterface ); /* See aws_iot_demo_shadow.c for documentation of this function. */ int AwsIotDemo_RunShadowDemo( const char * const pThingName, - AwsIotMqttConnection_t * const pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface ); + IotMqttConnection_t * const pMqttConnection, + const IotMqttNetIf_t * const pNetworkInterface ); -#endif /* ifndef _AWS_IOT_DEMO_H_ */ +#endif /* ifndef _IOT_DEMO_H_ */ diff --git a/demos/iot_demo_config.h b/demos/iot_demo_config.h index ae37ce7021..b88e9de9d3 100644 --- a/demos/iot_demo_config.h +++ b/demos/iot_demo_config.h @@ -26,34 +26,34 @@ /* Server endpoints used for the demos. May be overridden with command line * options. */ -#define AWS_IOT_DEMO_SECURED_CONNECTION ( true ) -#define AWS_IOT_DEMO_SERVER "" -#define AWS_IOT_DEMO_PORT ( 443 ) +#define IOT_DEMO_SECURED_CONNECTION ( true ) +#define IOT_DEMO_SERVER "" +#define IOT_DEMO_PORT ( 443 ) /* Credential paths. May be overridden with command line options. */ -#define AWS_IOT_DEMO_ROOT_CA "" -#define AWS_IOT_DEMO_CLIENT_CERT "" -#define AWS_IOT_DEMO_PRIVATE_KEY "" +#define IOT_DEMO_ROOT_CA "" +#define IOT_DEMO_CLIENT_CERT "" +#define IOT_DEMO_PRIVATE_KEY "" /* Default Thing Name to use for AWS IoT demos. */ /* #define AWS_IOT_DEMO_THING_NAME "" */ /* MQTT demo configuration. */ -/* #define AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER "" */ -#define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) -#define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) +/* #define IOT_DEMO_MQTT_CLIENT_IDENTIFIER "" */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) +#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Enable asserts in linear containers and MQTT. */ -#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_MQTT_ENABLE_ASSERTS ( 1 ) +#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) +#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) /* Library logging configuration. */ -#define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO -#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO -#define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_MQTT IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_DEMO IOT_LOG_INFO +#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO +#define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO +#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO +#define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO +#define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ diff --git a/demos/aws_iot_demo_mqtt.c b/demos/iot_demo_mqtt.c similarity index 78% rename from demos/aws_iot_demo_mqtt.c rename to demos/iot_demo_mqtt.c index da6dbd7f54..8a07c4ec0b 100644 --- a/demos/aws_iot_demo_mqtt.c +++ b/demos/iot_demo_mqtt.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_demo_mqtt.c + * @file iot_demo_mqtt.c * @brief Demonstrates usage of the MQTT library. */ @@ -34,14 +34,14 @@ #include /* Common demo include. */ -#include "aws_iot_demo.h" +#include "iot_demo.h" /* Platform layer includes. */ #include "platform/iot_clock.h" #include "platform/iot_threads.h" /* MQTT include. */ -#include "aws_iot_mqtt.h" +#include "iot_mqtt.h" /** * @cond DOXYGEN_IGNORE @@ -62,23 +62,23 @@ extern int snprintf( char *, * * Provide default values for undefined configuration settings. */ -#ifndef AWS_IOT_DEMO_MQTT_TOPIC_PREFIX - #define AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "awsiotdemo" +#ifndef IOT_DEMO_MQTT_TOPIC_PREFIX + #define IOT_DEMO_MQTT_TOPIC_PREFIX "iotdemo" #endif -#ifndef AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) +#ifndef IOT_DEMO_MQTT_PUBLISH_BURST_SIZE + #define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) #endif -#ifndef AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT - #define AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) +#ifndef IOT_DEMO_MQTT_PUBLISH_BURST_COUNT + #define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) #endif /** @endcond */ /* Validate MQTT demo configuration settings. */ -#if AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE <= 0 - #error "AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE cannot be 0 or negative." +#if IOT_DEMO_MQTT_PUBLISH_BURST_SIZE <= 0 + #error "IOT_DEMO_MQTT_PUBLISH_BURST_SIZE cannot be 0 or negative." #endif -#if AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT <= 0 - #error "AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT cannot be 0 or negative." +#if IOT_DEMO_MQTT_PUBLISH_BURST_COUNT <= 0 + #error "IOT_DEMO_MQTT_PUBLISH_BURST_COUNT cannot be 0 or negative." #endif /** @@ -88,7 +88,7 @@ extern int snprintf( char *, * This prefix is also used to generate topic names and topic filters used in this * demo. */ -#define _CLIENT_IDENTIFIER_PREFIX "awsiotdemo" +#define _CLIENT_IDENTIFIER_PREFIX "iotdemo" /** * @brief The longest client identifier that an MQTT server must accept (as defined @@ -115,7 +115,7 @@ extern int snprintf( char *, * The MQTT server will publish a message to this topic name if this client is * unexpectedly disconnected. */ -#define _WILL_TOPIC_NAME AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/will" +#define _WILL_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/will" /** * @brief The length of #_WILL_TOPIC_NAME. @@ -142,7 +142,7 @@ extern int snprintf( char *, * * For convenience, all topic filters are the same length. */ -#define _TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1" ) - 1 ) ) +#define _TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1" ) - 1 ) ) /** * @brief Format string of the PUBLISH messages in this demo. @@ -169,7 +169,7 @@ extern int snprintf( char *, * @brief The topic name on which acknowledgement messages for incoming publishes * should be published. */ -#define _ACKNOWLEDGEMENT_TOPIC_NAME AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/acknowledgements" +#define _ACKNOWLEDGEMENT_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/acknowledgements" /** * @brief The length of #_ACKNOWLEDGEMENT_TOPIC_NAME. @@ -197,7 +197,7 @@ extern int snprintf( char *, * MQTT library. */ static void _operationCompleteCallback( void * param1, - AwsIotMqttCallbackParam_t * const pOperation ) + IotMqttCallbackParam_t * const pOperation ) { intptr_t publishCount = ( intptr_t ) param1; @@ -207,18 +207,18 @@ static void _operationCompleteCallback( void * param1, /* Print the status of the completed operation. A PUBLISH operation is * successful when transmitted over the network. */ - if( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) + if( pOperation->operation.result == IOT_MQTT_SUCCESS ) { IotLogInfo( "MQTT %s %d successfully sent.", - AwsIotMqtt_OperationType( pOperation->operation.type ), + IotMqtt_OperationType( pOperation->operation.type ), ( int ) publishCount ); } else { IotLogError( "MQTT %s %d could not be sent. Error %s.", - AwsIotMqtt_OperationType( pOperation->operation.type ), + IotMqtt_OperationType( pOperation->operation.type ), ( int ) publishCount, - AwsIotMqtt_strerror( pOperation->operation.result ) ); + IotMqtt_strerror( pOperation->operation.result ) ); } } @@ -236,14 +236,14 @@ static void _operationCompleteCallback( void * param1, * the MQTT library. */ static void _mqttSubscriptionCallback( void * param1, - AwsIotMqttCallbackParam_t * const pPublish ) + IotMqttCallbackParam_t * const pPublish ) { int acknowledgementLength = 0; size_t messageNumberIndex = 0, messageNumberLength = 1; IotSemaphore_t * pPublishesReceived = ( IotSemaphore_t * ) param1; const char * pPayload = pPublish->message.info.pPayload; char pAcknowledgementMessage[ _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; - AwsIotMqttPublishInfo_t acknowledgementInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttPublishInfo_t acknowledgementInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Print information about the incoming PUBLISH message. */ IotLogInfo( "Incoming PUBLISH received:\n" @@ -257,7 +257,7 @@ static void _mqttSubscriptionCallback( void * param1, pPublish->message.info.topicNameLength, pPublish->message.info.pTopicName, pPublish->message.info.retain, - pPublish->message.info.QoS, + pPublish->message.info.qos, pPublish->message.info.payloadLength, pPayload ); @@ -301,7 +301,7 @@ static void _mqttSubscriptionCallback( void * param1, else { /* Set the members of the publish info for the acknowledgement message. */ - acknowledgementInfo.QoS = 1; + acknowledgementInfo.qos = IOT_MQTT_QOS_1; acknowledgementInfo.pTopicName = _ACKNOWLEDGEMENT_TOPIC_NAME; acknowledgementInfo.topicNameLength = _ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH; acknowledgementInfo.pPayload = pAcknowledgementMessage; @@ -311,15 +311,15 @@ static void _mqttSubscriptionCallback( void * param1, /* Send the acknowledgement for the received message. This demo program * will not be notified on the status of the acknowledgement because - * neither a callback nor AWS_IOT_MQTT_FLAG_WAITABLE is set. However, + * neither a callback nor IOT_MQTT_FLAG_WAITABLE is set. However, * the MQTT library will still guarantee at-least-once delivery (subject * to the retransmission strategy) because the acknowledgement message is * sent at QoS 1. */ - if( AwsIotMqtt_Publish( pPublish->mqttConnection, - &acknowledgementInfo, - 0, - NULL, - NULL ) == AWS_IOT_MQTT_STATUS_PENDING ) + if( IotMqtt_Publish( pPublish->mqttConnection, + &acknowledgementInfo, + 0, + NULL, + NULL ) == IOT_MQTT_STATUS_PENDING ) { IotLogInfo( "Acknowledgment message for PUBLISH %.*s will be sent.", ( int ) messageNumberLength, @@ -349,35 +349,35 @@ static void _mqttSubscriptionCallback( void * param1, * MQTT server. Set this to false if using another MQTT server. * @param[in] pClientIdentifier NULL-terminated MQTT client identifier. * @param[in] pMqttConnection Pointer to the MQTT connection to use. This MQTT - * connection must be initialized to AWS_IOT_MQTT_CONNECTION_INITIALIZER. + * connection must be initialized to IOT_MQTT_CONNECTION_INITIALIZER. * @param[in] pNetworkInterface Pointer to an MQTT network interface to use. * All necessary members of the network interface should be set before calling * this function. * * @return 0 if the demo completes successfully; -1 if some part of it fails. */ -int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, - const char * const pClientIdentifier, - AwsIotMqttConnection_t * const pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface ) +int IotDemo_RunMqttDemo( bool awsIotMqttMode, + const char * const pClientIdentifier, + IotMqttConnection_t * const pMqttConnection, + const IotMqttNetIf_t * const pNetworkInterface ) { int status = 0, i = 0; intptr_t publishCount = 0; char pClientIdentifierBuffer[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; char pPublishPayload[ _PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; - AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER, - publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - AwsIotMqttCallbackInfo_t publishComplete = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER, + publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttCallbackInfo_t publishComplete = IOT_MQTT_CALLBACK_INFO_INITIALIZER; IotSemaphore_t publishesReceived; const char * pTopicFilters[ _TOPIC_FILTER_COUNT ] = { - AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", - AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", - AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/3", - AWS_IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/4", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/3", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/4", }; /* Set the common members of the connection info. */ @@ -435,15 +435,15 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, connectInfo.clientIdentifierLength ); /* Establish the MQTT connection. */ - mqttStatus = AwsIotMqtt_Connect( pMqttConnection, - pNetworkInterface, - &connectInfo, - _MQTT_TIMEOUT_MS ); + mqttStatus = IotMqtt_Connect( pMqttConnection, + pNetworkInterface, + &connectInfo, + _MQTT_TIMEOUT_MS ); - if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "MQTT CONNECT returned error %s.", - AwsIotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( mqttStatus ) ); status = -1; } @@ -454,7 +454,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, /* Set the members of the subscription list. */ for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) { - pSubscriptions[ i ].QoS = 1; + pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; pSubscriptions[ i ].topicFilterLength = _TOPIC_FILTER_LENGTH; pSubscriptions[ i ].callback.param1 = &publishesReceived; @@ -464,27 +464,27 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, /* Subscribe to all the topic filters in the subscription list. The * blocking SUBSCRIBE function is used because the demo should not * continue until SUBSCRIBE completes. */ - mqttStatus = AwsIotMqtt_TimedSubscribe( *pMqttConnection, - pSubscriptions, - _TOPIC_FILTER_COUNT, - 0, - _MQTT_TIMEOUT_MS ); + mqttStatus = IotMqtt_TimedSubscribe( *pMqttConnection, + pSubscriptions, + _TOPIC_FILTER_COUNT, + 0, + _MQTT_TIMEOUT_MS ); switch( mqttStatus ) { - case AWS_IOT_MQTT_SUCCESS: + case IOT_MQTT_SUCCESS: IotLogInfo( "All demo topic filter subscriptions accepted." ); break; - case AWS_IOT_MQTT_SERVER_REFUSED: + case IOT_MQTT_SERVER_REFUSED: /* Check which subscriptions were rejected before exiting the demo. */ for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) { - if( AwsIotMqtt_IsSubscribed( *pMqttConnection, - pSubscriptions[ i ].pTopicFilter, - pSubscriptions[ i ].topicFilterLength, - NULL ) == true ) + if( IotMqtt_IsSubscribed( *pMqttConnection, + pSubscriptions[ i ].pTopicFilter, + pSubscriptions[ i ].topicFilterLength, + NULL ) == true ) { IotLogInfo( "Topic filter %.*s was accepted.", pSubscriptions[ i ].topicFilterLength, @@ -515,7 +515,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, publishComplete.function = _operationCompleteCallback; /* Set the common members of the publish info. */ - publishInfo.QoS = 1; + publishInfo.qos = IOT_MQTT_QOS_1; publishInfo.topicNameLength = _TOPIC_FILTER_LENGTH; publishInfo.pPayload = pPublishPayload; publishInfo.retryMs = _PUBLISH_RETRY_MS; @@ -524,17 +524,17 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, /* Create the semaphore that counts received PUBLISH messages.*/ if( IotSemaphore_Create( &publishesReceived, 0, - AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) { for( publishCount = 0; - publishCount < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; + publishCount < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; publishCount++ ) { - if( publishCount % AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) + if( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) { IotLogInfo( "Publishing messages %d to %d.", publishCount, - publishCount + AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); + publishCount + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); } /* Pass the PUBLISH number to the operation complete callback. */ @@ -565,17 +565,17 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, } /* PUBLISH a message. */ - mqttStatus = AwsIotMqtt_Publish( *pMqttConnection, - &publishInfo, - 0, - &publishComplete, - NULL ); + mqttStatus = IotMqtt_Publish( *pMqttConnection, + &publishInfo, + 0, + &publishComplete, + NULL ); - if( mqttStatus != AWS_IOT_MQTT_STATUS_PENDING ) + if( mqttStatus != IOT_MQTT_STATUS_PENDING ) { IotLogError( "MQTT PUBLISH %d returned error %s.", ( int ) publishCount, - AwsIotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( mqttStatus ) ); status = -1; break; } @@ -585,12 +585,12 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, * may be received out-of-order, especially if a message was * dropped and had to be retried. */ if( ( publishCount > 0 ) && - ( publishCount % AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) + ( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) { IotLogInfo( "Waiting for %d publishes to be received.", - AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); - for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) + for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { if( IotSemaphore_TimedWait( &publishesReceived, _MQTT_TIMEOUT_MS ) == false ) @@ -602,7 +602,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, } IotLogInfo( "%d publishes received.", - AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); } /* Stop publishing if there was an error. */ @@ -618,7 +618,7 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, { IotLogInfo( "Waiting for all publishes to be received." ); - for( i = 0; i < AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) + for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { if( IotSemaphore_TimedWait( &publishesReceived, _MQTT_TIMEOUT_MS ) == false ) @@ -645,24 +645,24 @@ int AwsIotDemo_RunMqttDemo( bool awsIotMqttMode, if( status == 0 ) { /* Unsubscribe from all demo topic filters. */ - mqttStatus = AwsIotMqtt_TimedUnsubscribe( *pMqttConnection, - pSubscriptions, - _TOPIC_FILTER_COUNT, - 0, - _MQTT_TIMEOUT_MS ); + mqttStatus = IotMqtt_TimedUnsubscribe( *pMqttConnection, + pSubscriptions, + _TOPIC_FILTER_COUNT, + 0, + _MQTT_TIMEOUT_MS ); - if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "MQTT UNSUBSCRIBE returned error %s.", - AwsIotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( mqttStatus ) ); status = -1; } } /* Disconnect the MQTT connection if it was established. */ - if( *pMqttConnection != AWS_IOT_MQTT_CONNECTION_INITIALIZER ) + if( *pMqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) { - AwsIotMqtt_Disconnect( *pMqttConnection, false ); + IotMqtt_Disconnect( *pMqttConnection, false ); } return status; diff --git a/demos/posix/CMakeLists.txt b/demos/posix/CMakeLists.txt index 6d2611c696..36d3848f79 100644 --- a/demos/posix/CMakeLists.txt +++ b/demos/posix/CMakeLists.txt @@ -1,20 +1,20 @@ # Common demo files. set( DEMO_COMMON_INCLUDE_FILES ".;${CMAKE_SOURCE_DIR}/demos" ) -set( DEMO_COMMON_SOURCE_FILES aws_iot_demo_common_posix.c ) +set( DEMO_COMMON_SOURCE_FILES iot_demo_common_posix.c ) # MQTT demo source files. -add_executable( aws_iot_demo_mqtt - ${CMAKE_SOURCE_DIR}/demos/aws_iot_demo_mqtt.c - aws_iot_demo_mqtt_posix.c +add_executable( iot_demo_mqtt + ${CMAKE_SOURCE_DIR}/demos/iot_demo_mqtt.c + iot_demo_mqtt_posix.c ${DEMO_COMMON_SOURCE_FILES} ) # MQTT demo include files. -target_include_directories( aws_iot_demo_mqtt +target_include_directories( iot_demo_mqtt PRIVATE ${DEMO_COMMON_INCLUDE_FILES} ) # MQTT demo library dependencies. -target_link_libraries( aws_iot_demo_mqtt iotplatform iotmqtt ) +target_link_libraries( iot_demo_mqtt iotplatform iotmqtt ) # Shadow demo source files. add_executable( aws_iot_demo_shadow @@ -23,7 +23,7 @@ add_executable( aws_iot_demo_shadow ${DEMO_COMMON_SOURCE_FILES} ) # Shadow demo include files. -target_include_directories( aws_iot_demo_mqtt +target_include_directories( iot_demo_mqtt PRIVATE ${DEMO_COMMON_INCLUDE_FILES} ) diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c index 8bd2f24321..870755e0a4 100644 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ b/demos/posix/aws_iot_demo_shadow_posix.c @@ -41,8 +41,8 @@ #include "iot_common.h" /* Common demo includes. */ -#include "aws_iot_demo.h" -#include "aws_iot_demo_posix.h" +#include "iot_demo.h" +#include "iot_demo_posix.h" /* POSIX+OpenSSL network include. */ #include "posix/iot_network_openssl.h" @@ -54,25 +54,25 @@ int main( int argc, { bool commonInitialized = false, networkConnectionCreated = false; int status = 0; - AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; + IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; IotNetworkConnectionOpenssl_t networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; - IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, *pCredentials = NULL; - AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, * pCredentials = NULL; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; /* This function parses arguments and establishes the network connection * before running the Shadow demo. */ /* Set the default Thing Name. */ - #ifdef AWS_IOT_DEMO_THING_NAME - demoArguments.pIdentifier = AWS_IOT_DEMO_THING_NAME; + #ifdef IOT_DEMO_THING_NAME + demoArguments.pIdentifier = IOT_DEMO_THING_NAME; #endif /* Parse any command line arguments. */ - if( AwsIotDemo_ParseArguments( argc, - argv, - &demoArguments ) == false ) + if( IotDemo_ParseArguments( argc, + argv, + &demoArguments ) == false ) { status = -1; } @@ -112,7 +112,7 @@ int main( int argc, * Shadow is specific to AWS IoT, so it always requires a secured connection. */ pCredentials = &credentials; - /* By default AWS_IOT_NETWORK_TLS_INFO_INITIALIZER enables ALPN. ALPN + /* By default AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER enables ALPN. ALPN * must be used with port 443; disable ALPN if another port is being used. */ if( demoArguments.port != 443 ) { @@ -143,7 +143,7 @@ int main( int argc, /* Set the MQTT receive callback for a network connection. This receive * callback processes MQTT data from the network. */ if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, - AwsIotMqtt_ReceiveCallback, + IotMqtt_ReceiveCallback, &mqttConnection ) != IOT_NETWORK_SUCCESS ) { status = -1; @@ -159,7 +159,7 @@ int main( int argc, networkInterface.send = IotNetworkOpenssl_Send; /* Initialize the MQTT library and Shadow library. */ - if( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) + if( IotMqtt_Init() == IOT_MQTT_SUCCESS ) { if( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ) { @@ -176,7 +176,7 @@ int main( int argc, status = -1; } - AwsIotMqtt_Cleanup(); + IotMqtt_Cleanup(); } else { @@ -189,9 +189,9 @@ int main( int argc, { /* Note that the MQTT library may have already closed the connection. * However, the network close function is safe to call on a closed connection. - * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. + * On the other hand, the destroy connection function must only be called ONCE. */ - IotNetworkOpenssl_Close( 0, &networkConnection ); + IotNetworkOpenssl_Close( &networkConnection ); IotNetworkOpenssl_Destroy( &networkConnection ); } diff --git a/demos/posix/aws_iot_demo_common_posix.c b/demos/posix/iot_demo_common_posix.c similarity index 89% rename from demos/posix/aws_iot_demo_common_posix.c rename to demos/posix/iot_demo_common_posix.c index dd63869c87..b4c109e33a 100644 --- a/demos/posix/aws_iot_demo_common_posix.c +++ b/demos/posix/iot_demo_common_posix.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_mqtt_demo_common_posix.c + * @file iot_mqtt_demo_common_posix.c * @brief Implements the common demo functions for POSIX systems. */ @@ -36,14 +36,14 @@ #include /* Common demo includes. */ -#include "aws_iot_demo.h" -#include "aws_iot_demo_posix.h" +#include "iot_demo.h" +#include "iot_demo_posix.h" /*-----------------------------------------------------------*/ -bool AwsIotDemo_ParseArguments( int argc, - char ** argv, - AwsIotDemoArguments_t * const pArguments ) +bool IotDemo_ParseArguments( int argc, + char ** argv, + IotDemoArguments_t * const pArguments ) { int option = 0; unsigned long int port = 0; @@ -52,33 +52,33 @@ bool AwsIotDemo_ParseArguments( int argc, pArguments->awsIotMqttMode = true; /* Set default secured connection status if defined. */ - #ifdef AWS_IOT_DEMO_SECURED_CONNECTION - pArguments->securedConnection = AWS_IOT_DEMO_SECURED_CONNECTION; + #ifdef IOT_DEMO_SECURED_CONNECTION + pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; #endif /* Set default MQTT server if defined. */ - #ifdef AWS_IOT_DEMO_SERVER - pArguments->pHostName = AWS_IOT_DEMO_SERVER; + #ifdef IOT_DEMO_SERVER + pArguments->pHostName = IOT_DEMO_SERVER; #endif /* Set default MQTT server port if defined. */ - #ifdef AWS_IOT_DEMO_PORT - pArguments->port = AWS_IOT_DEMO_PORT; + #ifdef IOT_DEMO_PORT + pArguments->port = IOT_DEMO_PORT; #endif /* Set default root CA path if defined. */ - #ifdef AWS_IOT_DEMO_ROOT_CA - pArguments->pRootCaPath = AWS_IOT_DEMO_ROOT_CA; + #ifdef IOT_DEMO_ROOT_CA + pArguments->pRootCaPath = IOT_DEMO_ROOT_CA; #endif /* Set default client certificate path if defined. */ - #ifdef AWS_IOT_DEMO_CLIENT_CERT - pArguments->pClientCertPath = AWS_IOT_DEMO_CLIENT_CERT; + #ifdef IOT_DEMO_CLIENT_CERT + pArguments->pClientCertPath = IOT_DEMO_CLIENT_CERT; #endif /* Set default client certificate private key path if defined. */ - #ifdef AWS_IOT_DEMO_PRIVATE_KEY - pArguments->pPrivateKeyPath = AWS_IOT_DEMO_PRIVATE_KEY; + #ifdef IOT_DEMO_PRIVATE_KEY + pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif IotLogInfo( "Parsing command line arguments." ); diff --git a/demos/posix/aws_iot_demo_mqtt_posix.c b/demos/posix/iot_demo_mqtt_posix.c similarity index 83% rename from demos/posix/aws_iot_demo_mqtt_posix.c rename to demos/posix/iot_demo_mqtt_posix.c index d7555a8f97..c4ff61d2ee 100644 --- a/demos/posix/aws_iot_demo_mqtt_posix.c +++ b/demos/posix/iot_demo_mqtt_posix.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_demo_mqtt_posix.c + * @file iot_demo_mqtt_posix.c * @brief Runs the MQTT demo on POSIX systems. */ @@ -38,8 +38,8 @@ #include "iot_common.h" /* Common demo includes. */ -#include "aws_iot_demo.h" -#include "aws_iot_demo_posix.h" +#include "iot_demo.h" +#include "iot_demo_posix.h" /* POSIX+OpenSSL network include. */ #include "posix/iot_network_openssl.h" @@ -51,25 +51,25 @@ int main( int argc, { bool commonInitialized = false, networkConnectionCreated = false; int status = 0; - AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; + IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; IotNetworkConnectionOpenssl_t networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, * pCredentials = NULL; - AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; /* This function parses arguments and establishes the network connection * before running the MQTT demo. */ /* Set default client identifier. */ - #ifdef AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER - demoArguments.pIdentifier = AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER; + #ifdef IOT_DEMO_MQTT_CLIENT_IDENTIFIER + demoArguments.pIdentifier = IOT_DEMO_MQTT_CLIENT_IDENTIFIER; #endif /* Parse any command line arguments. */ - if( AwsIotDemo_ParseArguments( argc, - argv, - &demoArguments ) == false ) + if( IotDemo_ParseArguments( argc, + argv, + &demoArguments ) == false ) { status = -1; } @@ -138,7 +138,7 @@ int main( int argc, /* Set the MQTT receive callback for a network connection. This receive * callback processes MQTT data from the network. */ if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, - AwsIotMqtt_ReceiveCallback, + IotMqtt_ReceiveCallback, &mqttConnection ) != IOT_NETWORK_SUCCESS ) { status = -1; @@ -154,16 +154,16 @@ int main( int argc, networkInterface.send = IotNetworkOpenssl_Send; /* Initialize the MQTT library. */ - if( AwsIotMqtt_Init() == AWS_IOT_MQTT_SUCCESS ) + if( IotMqtt_Init() == IOT_MQTT_SUCCESS ) { /* Run the MQTT demo. */ - status = AwsIotDemo_RunMqttDemo( demoArguments.awsIotMqttMode, - demoArguments.pIdentifier, - &mqttConnection, - &networkInterface ); + status = IotDemo_RunMqttDemo( demoArguments.awsIotMqttMode, + demoArguments.pIdentifier, + &mqttConnection, + &networkInterface ); /* Clean up the MQTT library. */ - AwsIotMqtt_Cleanup(); + IotMqtt_Cleanup(); } else { @@ -176,9 +176,9 @@ int main( int argc, { /* Note that the MQTT library may have already closed the connection. * However, the network close function is safe to call on a closed connection. - * On the other hand, AwsIotNetwork_DestroyConnection must only be called ONCE. + * On the other hand, the destroy connection function must only be called ONCE. */ - IotNetworkOpenssl_Close( 0, &networkConnection ); + IotNetworkOpenssl_Close( &networkConnection ); IotNetworkOpenssl_Destroy( &networkConnection ); } diff --git a/demos/posix/aws_iot_demo_posix.h b/demos/posix/iot_demo_posix.h similarity index 81% rename from demos/posix/aws_iot_demo_posix.h rename to demos/posix/iot_demo_posix.h index 1a83737991..5a2e0b520a 100644 --- a/demos/posix/aws_iot_demo_posix.h +++ b/demos/posix/iot_demo_posix.h @@ -20,12 +20,12 @@ */ /** - * @file aws_iot_demo_posix.h + * @file iot_demo_posix.h * @brief Declares the POSIX-specific demo functions. */ -#ifndef _AWS_IOT_DEMO_POSIX_H_ -#define _AWS_IOT_DEMO_POSIX_H_ +#ifndef _IOT_DEMO_POSIX_H_ +#define _IOT_DEMO_POSIX_H_ /** * @brief Holds the arguments for a single demo. @@ -39,9 +39,9 @@ * The default values may be overridden using command line arguments. If a default * value was not set, then a valid value must be set using a command line argument. * - * @initializer{AwsIotDemoArguments_t,AWS_IOT_DEMO_ARGUMENTS_INITIALIZER} + * @initializer{IotDemoArguments_t,IOT_DEMO_ARGUMENTS_INITIALIZER} */ -typedef struct AwsIotDemoArguments +typedef struct IotDemoArguments { bool awsIotMqttMode; /**< @brief Whether the demo is using the AWS IoT MQTT server. */ bool securedConnection; /**< @brief Whether to secure the network connection with TLS. */ @@ -54,24 +54,24 @@ typedef struct AwsIotDemoArguments const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ -} AwsIotDemoArguments_t; +} IotDemoArguments_t; /** - * @brief Provides default values for an #AwsIotDemoArguments_t. + * @brief Provides default values for an #IotDemoArguments_t. * - * All instances of #AwsIotDemoArguments_t should be initialized with this + * All instances of #IotDemoArguments_t should be initialized with this * constant. * * @code{c} - * AwsIotDemoArguments_t demoArguments = AWS_IOT_DEMO_ARGUMENTS_INITIALIZER; + * IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; * @endcode * - * @warning Failing to initialize an #AwsIotDemoArguments_t with this initializer + * @warning Failing to initialize an #IotDemoArguments_t with this initializer * may result in undefined behavior! * @note This initializer may change at any time in future versions, but its * names will remain the same. */ -#define AWS_IOT_DEMO_ARGUMENTS_INITIALIZER { 0 } +#define IOT_DEMO_ARGUMENTS_INITIALIZER { 0 } /** * @brief Parses command line arguments. @@ -87,8 +87,8 @@ typedef struct AwsIotDemoArguments * were set; `false` otherwise. If this function returns `false`, the demo program * should exit. */ -bool AwsIotDemo_ParseArguments( int argc, - char ** argv, - AwsIotDemoArguments_t * const pArguments ); +bool IotDemo_ParseArguments( int argc, + char ** argv, + IotDemoArguments_t * const pArguments ); -#endif /* ifndef _AWS_IOT_DEMO_POSIX_H_ */ +#endif /* ifndef _IOT_DEMO_POSIX_H_ */ diff --git a/doc/config/mqtt b/doc/config/mqtt index 9e7ac4669a..2283e0dca1 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -25,7 +25,7 @@ INPUT = doc \ tests/mqtt/system # Library file names. -FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt aws_iot_tests_network.c +FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt iot_tests_network.c # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ diff --git a/doc/config/taskpool b/doc/config/taskpool new file mode 100644 index 0000000000..7b66be5dbc --- /dev/null +++ b/doc/config/taskpool @@ -0,0 +1,33 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "TASKPOOL" +PROJECT_BRIEF = "Task pool library" + +# Library documentation output directory. +HTML_OUTPUT = taskpool + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/taskpool.tag + +# Directories containing library source code. +INPUT = doc \ + doc/lib \ + lib/include \ + lib/include/private \ + lib/source/common \ + demos/ \ + tests/ \ + tests/taskpool/unit \ + tests/taskpool/system + +# Library file names. +FILE_PATTERNS = *taskpool*.c *taskpool*.h *taskpool*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/queue.tag=../queue \ + doc/tag/logging.tag=../logging \ + doc/tag/platform.tag=../platform \ diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 9b235d990f..763485526e 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -36,7 +36,7 @@ cmake .. make @endcode -Demo application executables will be placed in a `bin` directory of the CMake build directory, i.e. `build/bin` in the example above. The executables will be named `aws_iot_demo_library`. For example, the MQTT demo application will be named `aws_iot_demo_mqtt`. +Demo application executables will be placed in a `bin` directory of the CMake build directory, i.e. `build/bin` in the example above. The executables will be named `aws_iot_demo_library` or `iot_demo_library`, depending on whether the demo is specific to AWS IoT. For example, the MQTT demo application, which may be used with any MQTT server, will be named `iot_demo_mqtt`. The Thing Shadow demo, which is specific to AWS IoT, will be named `aws_iot_demo_shadow`. Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/iot_demo_config.h` is recommended over setting them using CMake. @code{sh} @@ -59,7 +59,7 @@ Demo applications accept the following command line options. @subsection demo_optionn -n @brief Disable AWS IoT MQTT mode. -By default, the demo applications expect to run with an AWS IoT MQTT server. Passing this option disables [AWS IoT MQTT mode](@ref AwsIotMqttConnectInfo_t.awsIotMqttMode) and treats the remote MQTT server as a fully-compliant MQTT server. This option should not have an argument. +By default, the demo applications expect to run with an AWS IoT MQTT server. Passing this option disables [AWS IoT MQTT mode](@ref IotMqttConnectInfo_t.awsIotMqttMode) and treats the remote MQTT server as a fully-compliant MQTT server. This option should not have an argument. Because Thing Shadows are specific to AWS IoT, this option is ignored by the [Shadow demo](@ref shadow_demo). @@ -68,7 +68,7 @@ Because Thing Shadows are specific to AWS IoT, this option is ignored by the [Sh Neither `-s` nor `-u` should have an argument, and only one of the two should be used at a time. -See @ref AWS_IOT_DEMO_SECURED_CONNECTION for the compile-time default setting of this option. +See @ref IOT_DEMO_SECURED_CONNECTION for the compile-time default setting of this option. Because Thing Shadows are specific to AWS IoT (which requires secured connections), this option is ignored by the [Shadow demo](@ref shadow_demo). @@ -77,28 +77,28 @@ Because Thing Shadows are specific to AWS IoT (which requires secured connection Must be followed by a host name. -See @ref AWS_IOT_DEMO_SERVER for the compile-time default setting of this option. +See @ref IOT_DEMO_SERVER for the compile-time default setting of this option. @subsection demo_optionp -p port @brief Remote port for demo application. Must be followed by a port in the range of `[1,65535]`. -See @ref AWS_IOT_DEMO_PORT for the compile-time default setting of this option. +See @ref IOT_DEMO_PORT for the compile-time default setting of this option. @subsection demo_optionr -r rootCA, -c clientCert, -k privateKey @brief Paths to trusted server root certificate, client certificate, and client certificate private key, respectively. Must be followed by a filesystem path to the respective PEM-encoded credential. -See @ref AWS_IOT_DEMO_ROOT_CA, @ref AWS_IOT_DEMO_CLIENT_CERT, and @ref AWS_IOT_DEMO_PRIVATE_KEY for the compile-time default settings of these options. +See @ref IOT_DEMO_ROOT_CA, @ref IOT_DEMO_CLIENT_CERT, and @ref IOT_DEMO_PRIVATE_KEY for the compile-time default settings of these options. @subsection demo_optioni -i identifier @brief MQTT client identifier OR Thing Name. Must be followed by a string representing either an MQTT client identifier (MQTT demo only) or a Thing Name (all other demos). Because AWS IoT recommends that MQTT client identifier and Thing Name match, demos will use the Thing Name as the MQTT client identifier when possible. -See @ref AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER (MQTT demo only) or @ref AWS_IOT_DEMO_THING_NAME for the compile-time default settings of this option. +See @ref IOT_DEMO_MQTT_CLIENT_IDENTIFIER (MQTT demo only) or @ref AWS_IOT_DEMO_THING_NAME for the compile-time default settings of this option. @section building_tests Building and running the tests @brief How to build and run the SDK tests. @@ -118,7 +118,7 @@ cmake .. -DIOT_BUILD_TESTS=1 make @endcode -Test executables will be placed in a `bin` directory of the CMake build directory (i.e. `build/bin`) alongside the demo applications. They will be named `aws_iot_tests_library`. For example, the MQTT tests executable will be named `aws_iot_tests_mqtt`. +Test executables will be placed in a `bin` directory of the CMake build directory (i.e. `build/bin`) alongside the demo applications. They will be named `aws_iot_tests_library` or `iot_tests_library`, depending on whether the tests are specific to AWS IoT. For example, the MQTT tests application, which may be used with any MQTT server, will be named `iot_tests_mqtt`. The Thing Shadow tests, which are specific to AWS IoT, will be named `aws_iot_tests_shadow`. -By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. +By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. Individual tests may support other command line options; see the test application files for more information. */ diff --git a/doc/guide/style.txt b/doc/guide/style.txt index 4ff21fd0b4..0a05cae3a4 100644 --- a/doc/guide/style.txt +++ b/doc/guide/style.txt @@ -2,20 +2,24 @@ @page guide_developer_styleguide Style Guide @brief Guide for the coding style used in this SDK. -The goal of this style guide is to enforce a readable and consistent coding style across the entire SDK. +The goal of this style guide is to encourage a readable and consistent coding style across the entire SDK. @section guide_developer_styleguide_codingstyle Coding Style @brief The coding style used in this SDK. -The coding style aims to produce code that is readable and easy to debug. All library code follows these general rules: +The coding style aims to produce code that is readable and easy to debug. An example is provided in @ref guide_developer_styleguide_codingstyle_example. + +@subsection guide_developer_styleguide_codingstyle_generalguidelines General guidelines +@brief General guidelines for library style. - Libraries should only use features from [C99](https://en.wikipedia.org/wiki/C99) and earlier. + - Libraries should [log](@ref logging) extensively. - Code should be well-commented. - Only `/*` and @c *`/` should be used to start and end comments. - All comments end with a period. - Only spaces should be used for indenting. A single indent is 4 spaces. No tab characters should be used. - A parenthesis is usually followed by a space (see @ref guide_developer_styleguide_codingstyle_example). -- All lines of code should be less than 80 characters long, although longer lines are permitted if necessary. +- Lines of code should be less than 80 characters long, although longer lines are permitted. - All local variables should be declared at the top of a function. - All global variables should be declared at the top of a file. - Variables are always initialized. @@ -26,7 +30,12 @@ The coding style aims to produce code that is readable and easy to debug. All li - All files must include @ref IOT_CONFIG_FILE at the top of the file before any other includes. - `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. -See @ref guide_developer_styleguide_codingstyle_example for a more complete example. +@subsection guide_developer_styleguide_codingstyle_typeguidelines Type guidelines +@brief Guidelines for variable types. +- Only fixed-width integer types should be used. Exceptions for `bool` and types required by third-party APIs. +- The default integer in the libraries should be 32 bits wide, i.e. `int32_t` or `uint32_t`. +- Sizes and lengths should be represented with `size_t`, and Boolean variables with `bool`. +- The portable format specifiers in `` should be used for logging fixed-width integers. @subsection guide_developer_styleguide_codingstyle_example Example File @brief An example file that follows the coding style rules. @@ -38,7 +47,7 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE /* Lines between #ifdef/#endif are indented. */ +#include IOT_CONFIG_FILE #endif /* Standard includes are immediately after the config file. They are sorted alphabetically. @@ -46,25 +55,28 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Standard includes. */ #include -#include +#include #include +#include #include /* Library internal headers are included next. They use quotes "" around the file name. */ /* Library internal include. */ -#include "private/aws_iot_library_internal.h" +#include "private/iot_library_internal.h" /* Library application-facing headers are included last. They use quotes "" around the file name. */ /* Library include. */ -#include "aws_iot_library.h" +#include "iot_library.h" /*-----------------------------------------------------------*/ /* Defined constants follow the included headers. */ -#define _LIBRARY_CONSTANT ( 10 ) /* When possible, parentheses () should be placed around contant values */ +/* When possible, parentheses () should be placed around contant values and a type + * should be specified. */ +#define _LIBRARY_CONSTANT ( ( int32_t ) 10 ) #define _LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ { \ /* Function-like macros are surrounded by curly braces {}. */ @@ -78,19 +90,19 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Forward declarations are used only when necessary. They are placed before all * other typedefs. */ -typedef int _type_t; +typedef int32_t _type_t; typedef struct _structType /* Structs are named along with the typedef. */ { - int member; + int32_t member; union /* Anonymous structs/unions are permitted only inside of other structs. */ { - int a; - int b; + int32_t a; + int32_t b; }; - int variableLengthMember[]; /* Variable length arrays (a C99 feature) are permitted. */ + int32_t variableLengthMember[]; /* Variable length arrays (a C99 feature) are permitted. */ } _structType_t; /*-----------------------------------------------------------*/ @@ -102,7 +114,7 @@ static bool _libraryStaticFunction( void * pArgument, /* External function declarations should be used sparingly (using an internal * header file to declare functions is preferred). */ -extern int AwsIotLibrary_ExternalFunction( void * pArgument ); +extern int32_t IotLibrary_ExternalFunction( void * pArgument ); /*-----------------------------------------------------------*/ @@ -124,8 +136,8 @@ static bool _libraryStaticFunction( void * pArgument, /* All local variables are declared at the top of the function. Variables are * always initialized. */ size_t i = 0; - int localVariable = 0; - int * pLocalPointer = ( int * ) pArgument; + int32_t localVariable = 0; + int32_t * pLocalPointer = ( int32_t * ) pArgument; /* All functions make generous use of the logging library. */ IotLogInfo( "Performing calculation..." ); @@ -141,9 +153,10 @@ static bool _libraryStaticFunction( void * pArgument, for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ { - localVariable += AwsIotLibrary_ExternalFunction( pArgument ); + localVariable += IotLibrary_ExternalFunction( pArgument ); - IotLogDebug( "Current value is %d.", localVariable ); + /* Use portable format specifier for fixed-width integer. */ + IotLogDebug( "Current value is " PRId32 ".", localVariable ); } if( localVariable < 0 ) @@ -161,7 +174,7 @@ static bool _libraryStaticFunction( void * pArgument, /* Implementations of application-facing functions are at the bottom of the file. */ -bool AwsIotLibrary_ApplicationFunction( void ) /* Functions with no arguments have void in their argument list. */ +bool IotLibrary_ApplicationFunction( void ) /* Functions with no arguments have void in their argument list. */ { _LIBRARY_FUNCTION_MACRO( _globalArray ); @@ -177,89 +190,91 @@ bool AwsIotLibrary_ApplicationFunction( void ) /* Functions with no arguments ha @brief Naming convention used in this SDK. The naming convention aims to differentiate this SDK's files, variables, and functions to avoid name collisions. In general: -- The first characters of all publicly visible names should identify the name as part of this AWS IoT SDK.
- Example: Names starting with `aws_iot_` or `AwsIot`. -- Words in names should be ordered with the most general word first and the most specific word last.
- Example: `aws_iot_mqtt_api.c` identifies a file as part of the general MQTT library. `AwsIotMqttInternal_ValidateNetIf` identifies a function as part of the Internal component of the general MQTT library. +- The first characters of all publicly visible names should identify the name as part of this SDK.
+ Example: For general-purpose libraries (such as MQTT), names start with `iot_` or `Iot`.
+ Example: For libraries specific to AWS IoT (such as Shadow), names start with `aws_iot_` or `AwsIot`. +- Words in names should be ordered with the most general word first and the most specific word last. Names for internal use begin with `_`.
+ Example: `iot_mqtt_api.c` identifies a file as part of the general MQTT library. `_IotMqtt_ValidateConnect` identifies a function as part of the Internal component of the general MQTT library. - Names should avoid using abbreviations. @subsection guide_developer_styleguide_naming_definedconstantsandenumvalues Defined constants and enum values @brief Naming convention for constants set using preprocessor @c #`define` and enum values. @formattable{defined constants and enum values} -@formattableentry{`AWS_IOT_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in application-facing library header files,`AWS_IOT_MQTT_SUCCESS` (aws_iot_mqtt.h)} -@formattableentry{`AWS_IOT_DEMO_`LIBRARY`_`DESCRIPTION
`AWS_IOT_TEST_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in demos and tests,`AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE`
`AWS_IOT_TEST_MQTT_THREADS`} -@formattableentry{`_`DESCRIPTION,Internal constants and enum values,`_MQTT_PACKET_TYPE_CONNECT` (aws_iot_mqtt_internal.h)} +@formattableentry{`IOT_`LIBRARY`_`DESCRIPTION
`AWS_IOT_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in application-facing library header files,`IOT_MQTT_SUCCESS` (iot_mqtt.h)
`AWS_IOT_SHADOW_SUCCESS` (aws_iot_shadow.h)} +@formattableentry{`IOT_DEMO_`DESCRIPTION
`IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests,`IOT_DEMO_MQTT_PUBLISH_BURST_SIZE`
`IOT_TEST_MQTT_THREADS`} +@formattableentry{`AWS_IOT_DEMO_`DESCRIPTION
`AWS_IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests specific to AWS IoT,`AWS_IOT_DEMO_THING_NAME`
`AWS_IOT_TEST_SHADOW_THING_NAME`} +@formattableentry{`_`DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal namesin AWS-specific libraries,`_MQTT_PACKET_TYPE_CONNECT`
(iot_mqtt_internal.h)
`_SHADOW_OPERATION_COUNT`
(aws_iot_shadow_internal.h)} -Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `AWS_IOT_`, while names intended for internal use must begin with only `_`. +Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `IOT_` or `AWS_IOT_`, while names intended for internal use must begin with only `_`. @subsection guide_developer_styleguide_naming_files Files @brief Naming convention for files. @formattable{files} -@formattableentry{`aws_iot_`library`_`description`.extension`,General library file,`aws_iot_mqtt_api.c`} -@formattableentry{`aws_iot_`library`_internal.h`,Internal library header,`aws_iot_mqtt_internal.h`} -@formattableentry{`aws_iot_demo_`library`.c`,Library demo source,`aws_iot_demo_mqtt.c`} -@formattableentry{`aws_iot_tests_`library`_`description`.c`,Library test source,`aws_iot_tests_mqtt_api.c`} +@formattableentry{`iot_`library`_`description`.extension`
`aws_iot_`library`_`description`.extension`,General library file,`iot_mqtt_api.c`
`aws_iot_shadow_api.c`} +@formattableentry{`iot_`library`_internal.h`
`aws_iot_`library`_internal.h`,Internal library header,`iot_mqtt_internal.h`
`aws_iot_shadow_internal.h`} +@formattableentry{`iot_demo_`library`.c`
`aws_iot_demo_`library`.c`,Library demo source,`iot_demo_mqtt.c`
`aws_iot_demo_shadow.c`} +@formattableentry{`iot_tests_`library`_`description`.c`
`aws_iot_tests_`library`_`description`.c`,Library test source,`iot_tests_mqtt_api.c`
`aws_iot_tests_shadow_api.c`} -File names contain only lowercase letters and underscores. All file names should start with `aws_iot_` and be named according to their purpose. For example: -- `aws_iot_mqtt_api.c`: A file in the MQTT library that implements the MQTT API functions. -- `aws_iot_demo_mqtt.c`: A file in the Demos for the MQTT library. -- `aws_iot_demo_mqtt_posix.c`: A file in the Demos for the MQTT library on POSIX systems. -- `aws_iot_tests_mqtt_api.c`: A file in the Tests for the MQTT library. Since the tests currently only run on POSIX systems, test file names do not use the `_posix` suffix. +File names contain only lowercase letters and underscores. All file names should start with `iot_` or `aws_iot_` and be named according to their purpose. For example: +- `iot_mqtt_api.c`: A file in the MQTT library that implements the MQTT API functions. +- `iot_demo_mqtt.c`: A file in the Demos for the MQTT library. +- `iot_demo_mqtt_posix.c`: A file in the Demos for the MQTT library on POSIX systems. +- `iot_tests_mqtt_api.c`: A file in the Tests for the MQTT library. Since the tests currently only run on POSIX systems, test file names do not use the `_posix` suffix. Library file names should use one or two words to describe the functions implemented in that file. For example: -- `aws_iot_mqtt_serialize.c`: Implements the MQTT library's packet serialization and deserialization functions. -- `aws_iot_clock_posix.c`: Implements the platform clock component for POSIX systems. +- `iot_mqtt_serialize.c`: Implements the MQTT library's packet serialization and deserialization functions. +- `iot_clock_posix.c`: Implements the platform clock component for POSIX systems. Declarations of internal functions, structures, macros, etc. of a library should be placed in a header file with an `_internal` suffix. The `_internal` header file should go in the `lib/include/private` directory. For example: -- `aws_iot_mqtt_internal.h`: Declares the MQTT library's internal functions, structures, macros, etc. +- `iot_mqtt_internal.h`: Declares the MQTT library's internal functions, structures, macros, etc. -File names for tests and demos should all begin with `aws_iot_demo_` and `aws_iot_tests_`, respectively. The names should then specify the library being demoed or or tested; for example, the files names of the MQTT library's demos and tests start with `aws_iot_demo_mqtt_` and `aws_iot_tests_mqtt_`. Additionally, test file names should describe what tests are implemented in the file, such as `aws_iot_tests_mqtt_api.c` for a file containing tests for the MQTT library API functions. +File names for tests and demos should all begin with `iot_demo_` and `iot_tests_`, respectively. The names should then specify the library being demoed or or tested; for example, the files names of the MQTT library's demos and tests start with `iot_demo_mqtt_` and `iot_tests_mqtt_`. Additionally, test file names should describe what tests are implemented in the file, such as `iot_tests_mqtt_api.c` for a file containing tests for the MQTT library API functions. @subsection guide_developer_styleguide_naming_functions Functions (and function-like macros) @brief Naming convention of functions and function-like macros. @formattable{functions} -@formattableentry{`AwsIot`Library`_`Description,Externally-visible library function,`AwsIotMqtt_Publish`} -@formattableentry{`AwsIot`Library`Internal_`Description,Internal (but not `static`) library function,`AwsIotMqttInternal_ValidateNetIf`} -@formattableentry{`AwsIotTest`Library`_`Description,Library test-only function,`AwsIotTest_NetworkConnect`
`AwsIotTestMqtt_createMqttConnection`} -@formattableentry{`AwsIotDemo_`Library,Library demo function,`AwsIotDemo_Mqtt`} -@formattableentry{`_`description,`static` function,`_createMqttConnection`} -@formattableentry{`AwsIotTest`Library`_`accessedFunction,Test access function,`AwsIotTestMqtt_createMqttConnection`} +@formattableentry{`Iot`Library`_`Description
`AwsIot`Library`_`Description,Externally-visible library function,`IotMqtt_Publish`
`AwsIotShadow_Update`} +@formattableentry{`_Iot`Library`_`Description
`_AwsIot`Library`_`Description,Internal (but not `static`) library function,`_IotMqtt_ValidateNetIf`
`_AwsIotShadow_ParseShadowStatus`} +@formattableentry{`IotTest`Library`_`Description,Function only used in tests,`IotTest_NetworkConnect`
`IotTestMqtt_createMqttConnection`} +@formattableentry{`_`description,`static` function (never uses `Aws` prefix),`_createMqttConnection`
`_codeToShadowStatus`} +@formattableentry{`IotTest`Library`_`accessedFunction,Test access function,`IotTestMqtt_createMqttConnection`} -Externally visible (i.e. not `static`) functions are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case) and must begin with `AwsIot`. Function names should then specify their library name; followed by an underscore; followed by a brief description of what the function does. Internal library functions that are not `static` should have the word `Internal` after the library name. For example: -- `AwsIotMqtt_Publish`: This function is part of the MQTT library. It Publishes an MQTT message. -- `AwsIotMqttInternal_ValidateNetIf`: This function is internal to the MQTT library, but not `static`. It validates an `AwsIotMqttNetIf_t`. +Externally visible (i.e. not `static`) functions are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case) and must begin with `Iot` or `AwsIot` (public API functions); or `_Iot` or `_AwsIot` (internal functions). Function names should then specify their library name; followed by an underscore; followed by a brief description of what the function does. For example: +- `IotMqtt_Publish`: This function is part of the public MQTT API. It Publishes an MQTT message. +- `_IotMqtt_ValidateNetIf`: This function is internal to the MQTT library, but not `static`. It validates an `IotMqttNetIf_t`. -Functions not visible outside their source file (i.e. `static` functions) have names that are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and begin with an underscore. These function names do not contain the library name or `AwsIot`. For example: -- `_createMqttConnection`: A `static` function in `aws_iot_mqtt_api.c`. +Functions not visible outside their source file (i.e. `static` functions) have names that are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and begin with an underscore. These function names do not contain the library name or `Aws`. For example: +- `_createMqttConnection`: A `static` function in `iot_mqtt_api.c`. +- `_codeToShadowStatus`: A `static` function in `aws_iot_shadow_parser.c`. -Functions that are specific to the demos and tests begin with `AwsIotDemo` and `AwsIotTest`, respectively. Test access functions begin with `AwsIotTest`; followed by the library name; followed by an underscore; followed by the function that the test function accesses. Since the accessed function is always `static`, the accessed function will be [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). +Test access functions begin with `IotTest`; followed by the library name; followed by an underscore; followed by the function that the test function accesses. Since the accessed function is always `static`, the accessed function will be [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). @subsection guide_developer_styleguide_naming_types Types @brief Naming conventions of library `typedef` types. @formattable{types} -@formattableentry{`AwsIot`LibraryDescription`_t`,General types in application-facing library header files,`AwsIotMqttConnection_t` (aws_iot_mqtt.h)} -@formattableentry{`AwsIot`LibraryFunction`Info_t`,Application-facing parameter structure,`AwsIotMqttPublishInfo_t`
(Parameter structure to `AwsIotMqtt_Publish`)} -@formattableentry{`_`libraryDescription`_t`,Type in an `internal` header,`_mqttOperation_t` (aws_iot_mqtt_internal.h)} -@formattableentry{`_`description`_t`,Internal type in source file,`_topicMatchParams_t` (aws_iot_mqtt_subscription.c)} +@formattableentry{`Iot`LibraryDescription`_t`
`AwsIot`LibraryDescription`_t`,General types in application-facing library header files,`IotMqttError_t` (iot_mqtt.h)
`AwsIotShadowError_t` (aws_iot_shadow.h)} +@formattableentry{`Iot`LibraryFunction`Info_t`
`AwsIot`LibraryFunction`Info_t`,Application-facing parameter structure,`IotMqttPublishInfo_t`
(Parameter structure to `IotMqtt_Publish`)
`AwsIotShadowDocumentInfo_t`
(Parameter structure for Shadow documents)} +@formattableentry{`_`libraryDescription`_t`,Type in an `internal` header,`_mqttOperation_t` (iot_mqtt_internal.h)
`_shadowOperation_t` (aws_iot_shadow_internal.h)} +@formattableentry{`_`description`_t`,Internal type in source file,`_topicMatchParams_t` (iot_mqtt_subscription.c)} -Types intended for use in applications are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `AwsIot` and end with `_t`. Parameter structures must indicate their associated function: for example, `AwsIotMqttPublishInfo_t` is passed as a parameter to `AwsIotMqtt_Publish`. +Types intended for use in applications are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `Iot` or `AwsIot` and end with `_t`. Parameter structures should indicate their associated function: for example, `IotMqttPublishInfo_t` is passed as a parameter to `IotMqtt_Publish`. Types intended for internal library use defined in a header file are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `_`, followed by the library name, and end with `_t`. Internal types defined in a library source file must start with `_`, end with `_t`, and not include the library name. `struct` typedefs should always be named along with the `typedef`. The struct name should be identical to the `typedef` name, but without the `_t` at the end. For example: @code{c} -typedef struct AwsIotLibraryStruct +typedef struct IotLibraryStruct { - int member; -} AwsIotLibraryStruct_t; + int32_t member; +} IotLibraryStruct_t; typedef struct _libraryInternalStruct { - int member; + int32_t member; } _libraryInternalStruct_t; @endcode @@ -271,12 +286,12 @@ A `struct` may contain anonymous `struct` or `union` members. @formattable{variables} @formattableentry{variableDescription,General local variable,`startTime`} @formattableentry{`p`VariableDescription,Local variable pointers and arrays (including strings),`pSubscriptionList`} -@formattableentry{`_`variableDescription,Global variable that is `static`,`_connectMutex` (aws_iot_mqtt_api.c)
`_pSamplePayload` (string)} -@formattableentry{`_AwsIot`LibraryDescription,Global variable that is NOT `static`,`_AwsIotMqttSendQueue` (aws_iot_mqtt_operation.c)
`_pAwsIotSamplePayload` (string)} +@formattableentry{`_`variableDescription,Global variable that is `static`,`_connectMutex` (iot_mqtt_api.c)
`_pSamplePayload` (string)} +@formattableentry{`_Iot`LibraryDescription
`_AwsIot`LibraryDescription,Global variable that is NOT `static`,`_IotMqttTaskPool` (iot_mqtt_operation.c)
`_pIotSamplePayload` (string)} Local variable names are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and consist only of a description of the variable. Names like `i` or `j` are acceptable for loop counters, but all other variables should have a descriptive name. -Global variable names always start with a `_`. Global variables that are `static` consist of only the description in [lowerCamelCase](https://en.wikipedia.org/wiki/Camel_case). Global variables that are not static consist of the library name and the description in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case); for example: `_AwsIotLibraryNonStaticGlobalVariable`. +Global variable names always start with a `_`. Global variables that are `static` consist of only the description in [lowerCamelCase](https://en.wikipedia.org/wiki/Camel_case). Global variables that are not static consist of the library name and the description in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case); for example: `_IotLibraryNonStaticGlobalVariable`. All pointers, arrays, and strings (both global and local) start with `p` (local) or `_p` (global). */ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 7f2efca146..3bcffdf5f8 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -52,7 +52,7 @@ digraph mqtt_dependencies Currently, the MQTT library has the following dependencies: - The queue library for maintaining the data structures for managing in-progress MQTT operations. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_MQTT is not #IOT_LOG_NONE. +- The logging library may be used if @ref IOT_LOG_LEVEL_MQTT is not #IOT_LOG_NONE. - The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. In addition to the components above, the MQTT library also depends on C standard library headers. @@ -69,13 +69,13 @@ In addition to the components above, the MQTT library also depends on C standard @page mqtt_demo Demo @brief The MQTT demo demonstrates usage of the MQTT library. -The MQTT demo demonstrates the subscribe-publish workflow of MQTT. After subscribing to multiple topic filters, it publishes [bursts](@ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) of data to various topic names. The demo then waits for all messages in a burst to be received on a topic filter. As each message arrives, the demo publishes an acknowledgement message back to the MQTT server. It repeats this cycle @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times. +The MQTT demo demonstrates the subscribe-publish workflow of MQTT. After subscribing to multiple topic filters, it publishes [bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) of data to various topic names. The demo then waits for all messages in a burst to be received on a topic filter. As each message arrives, the demo publishes an acknowledgement message back to the MQTT server. It repeats this cycle @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times. @image html mqtt_demo.png "MQTT Demo Workflow" width=80% See @subpage mqtt_demo_config for configuration settings that change the behavior of the demo. -The main MQTT demo file is aws_iot_demo_mqtt.c, which contains platform-independent code. See @ref building_demo for instructions on building the MQTT demo. +The main MQTT demo file is iot_demo_mqtt.c, which contains platform-independent code. See @ref building_demo for instructions on building the MQTT demo. */ /** @@ -95,7 +95,7 @@ The current MQTT tests use the [Unity test framework](http://www.throwtheswitch. /** @configpage{mqtt_tests,MQTT tests,Test,tests} -@section AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER +@section IOT_TEST_MQTT_CLIENT_IDENTIFIER @brief The MQTT client identifier to use for the tests. No two clients may connect using the same client identifier simultaneously. If this setting is undefined, the tests will generate a unique client identifier to use. @@ -103,7 +103,7 @@ No two clients may connect using the same client identifier simultaneously. If t @configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
@configdefault The tests will generate a unique client identifier if this setting is undefined. -@section AWS_IOT_TEST_MQTT_MOSQUITTO +@section IOT_TEST_MQTT_MOSQUITTO @brief Test the MQTT library against the [public Mosquitto test server](https://test.mosquitto.org/). When this setting is `1`, the MQTT tests will be built to test against an unsecured [public Mosquitto test server](https://test.mosquitto.org/). This allows the MQTT library to be tested against a fully-compliant MQTT server. Because the connection is unsecured, no credentials are needed for testing against Mosquitto. @@ -111,9 +111,9 @@ When this setting is `1`, the MQTT tests will be built to test against an unsecu @configpossible `0` (use AWS IoT MQTT server) or `1` (use public Mosquitto server)
@configdefault `0` -
The settings below only affect the [system](@ref aws_iot_tests_mqtt_system.c) and [stress](@ref aws_iot_tests_mqtt_stress.c) tests.
+
The settings below only affect the [system](@ref iot_tests_mqtt_system.c) and [stress](@ref iot_tests_mqtt_stress.c) tests.
-@section AWS_IOT_TEST_MQTT_TIMEOUT_MS +@section IOT_TEST_MQTT_TIMEOUT_MS @brief Timeout in milliseconds for MQTT operations. This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait (and similar functions requiring a timeout). Ensure that this value is large enough to accommodate delays caused by the network. @@ -122,17 +122,17 @@ This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait @configrecommended This setting should be at least `1000`.
@configdefault `5000` -@section AWS_IOT_TEST_MQTT_TOPIC_PREFIX +@section IOT_TEST_MQTT_TOPIC_PREFIX @brief The string prepended to topic filters. This string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the tests. @configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
-@configdefault `"awsiotmqtttest"` +@configdefault `"iotmqtttest"` -
The settings below only affect the [stress](@ref aws_iot_tests_mqtt_stress.c) tests.
+
The settings below only affect the [stress](@ref iot_tests_mqtt_stress.c) tests.
-@section AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S +@section IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S @brief The keep-alive interval for the stress tests. MQTT PINGREQ packets will be sent at this interval. @@ -141,8 +141,8 @@ MQTT PINGREQ packets will be sent at this interval. @configrecommended This value should be the shortest keep-alive interval supported by the connection.
@configdefault `30` when using the AWS IoT MQTT server; `5` otherwise -@section AWS_IOT_TEST_MQTT_RETRY_MS -@brief The value of #AwsIotMqttPublishInfo_t.retryMs used in the stress tests. +@section IOT_TEST_MQTT_RETRY_MS +@brief The value of #IotMqttPublishInfo_t.retryMs used in the stress tests. Any lost publish messages will be retransmitted at this time. @@ -150,34 +150,34 @@ Any lost publish messages will be retransmitted at this time. @configrecommended This setting should be at least `250` to avoid excessive network congestion caused by retransmissions.
@configdefault `350` -@section AWS_IOT_TEST_MQTT_RETRY_LIMIT -@brief The value of #AwsIotMqttPublishInfo_t.retryLimit used in the stress tests. +@section IOT_TEST_MQTT_RETRY_LIMIT +@brief The value of #IotMqttPublishInfo_t.retryLimit used in the stress tests. Any lost publish messages will be retried up to this limit. @configpossible Any positive integer.
@configdefault `32` -@section AWS_IOT_TEST_MQTT_DECONGEST_S +@section IOT_TEST_MQTT_DECONGEST_S @brief The time to wait for resources to be freed. -Should an MQTT operation fail due to insufficient resources (such as a return value of @ref AWS_IOT_MQTT_NO_MEMORY), the test thread will sleep for this amount to time before retrying to wait for resources to be freed. Larger values for this setting provide a higher change for a successful retry, but will cause the tests to run longer. +Should an MQTT operation fail due to insufficient resources (such as a return value of @ref IOT_MQTT_NO_MEMORY), the test thread will sleep for this amount to time before retrying to wait for resources to be freed. Larger values for this setting provide a higher change for a successful retry, but will cause the tests to run longer. @configpossible Any positive integer.
@configdefault `30` -@section AWS_IOT_TEST_MQTT_THREADS +@section IOT_TEST_MQTT_THREADS @brief The number of threads to use in the stress tests. -The number of threads to spawn for the stress tests. Up to @ref AWS_IOT_TEST_MQTT_THREADS `*` @ref AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. +The number of threads to spawn for the stress tests. Up to @ref IOT_TEST_MQTT_THREADS `*` @ref IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. @configpossible Any positive integer.
@configdefault `16` -@section AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD +@section IOT_TEST_MQTT_PUBLISHES_PER_THREAD @brief The number of publish messages each thread in the stress test should send. -Up to @ref AWS_IOT_TEST_MQTT_THREADS `*` @ref AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. +Up to @ref IOT_TEST_MQTT_THREADS `*` @ref IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. @configpossible Any non-negative integer.
@configdefault When @ref IOT_STATIC_MEMORY_ONLY is `1`, this value defaults to @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. Otherwise, it defaults to `100`. @@ -186,7 +186,7 @@ Up to @ref AWS_IOT_TEST_MQTT_THREADS `*` @ref AWS_IOT_TEST_MQTT_PUBLISHES_PER_TH /** @configpage{mqtt_demo,MQTT demo,Demo,demos} -@section AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER +@section IOT_DEMO_MQTT_CLIENT_IDENTIFIER @brief The MQTT client identifier to use for the demo. No two clients may connect using the same client identifier simultaneously. @@ -196,28 +196,28 @@ The MQTT client identifier may also be set with [the command line option -i](@re @configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
@configdefault The demo will generate a unique client identifier if this setting is undefined. -@section AWS_IOT_DEMO_MQTT_TOPIC_PREFIX +@section IOT_DEMO_MQTT_TOPIC_PREFIX @brief The string prepended to topic filters in the demo. The string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the demo. @configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
-@configdefault `"awsiotmqttdemo"` +@configdefault `"iotmqttdemo"` -@section AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE +@section IOT_DEMO_MQTT_PUBLISH_BURST_SIZE @brief The number of messages published in each burst. -Messages in a burst are rapidly published. After a complete burst is published, the demo waits for the messages to be received on a subscription topic filter. This value may be increased for higher throughput, at the expense of an increased chance of dropped messages. The MQTT demo publishes a total of @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. +Messages in a burst are rapidly published. After a complete burst is published, the demo waits for the messages to be received on a subscription topic filter. This value may be increased for higher throughput, at the expense of an increased chance of dropped messages. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. @configpossible Any positive integer.
@configdefault `10` -@section AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT -@brief The number of [publish bursts](@ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) in this demo. +@section IOT_DEMO_MQTT_PUBLISH_BURST_COUNT +@brief The number of [publish bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) in this demo. -Each burst will rapidly publish @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE messages. After publishing, the demo will wait for the published messages to be received on a subscription topic filter. +Each burst will rapidly publish @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE messages. After publishing, the demo will wait for the published messages to be received on a subscription topic filter. -This setting can be increased for a longer demo. The MQTT demo publishes a total of @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. +This setting can be increased for a longer demo. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. @configpossible Any positive integer.
@configdefault `10` @@ -226,10 +226,10 @@ This setting can be increased for a longer demo. The MQTT demo publishes a total /** @configpage{mqtt,MQTT library} -@section AWS_IOT_MQTT_ENABLE_ASSERTS +@section IOT_MQTT_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the MQTT library. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotMqtt_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotMqtt_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
@@ -243,43 +243,25 @@ Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SD @configpossible `0` (metrics reporting disabled) or `1` (metrics reporting enabled)
@configrecommended `1`
@configdefault `1` -@note This setting is only in effect for [MQTT connections with AWS IoT](@ref AwsIotMqttConnectInfo_t.awsIotMqttMode). Metrics are reported through [the MQTT username.](@ref AwsIotMqttConnectInfo_t.pUserName) The MQTT library may overwrite any provided user name if metrics are enabled. +@note This setting is only in effect for [MQTT connections with AWS IoT](@ref IotMqttConnectInfo_t.awsIotMqttMode). Metrics are reported through [the MQTT username.](@ref IotMqttConnectInfo_t.pUserName) The MQTT library may overwrite any provided user name if metrics are enabled. -@section AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES +@section IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES @brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. -Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #AwsIotMqttNetIf_t for a list of functions that can be overridden. If this setting is `1`, the serializer [initialization](@ref AwsIotMqttInternal_InitSerialize) and [cleanup](@ref AwsIotMqttInternal_CleanupSerialize) functions may be extended by defining `AwsIotMqttInternal_InitSerializeAdditional` and `AwsIotMqttInternal_CleanupSerializeAdditional`. These functions will be called along with the default serializer initialization and cleanup functions. They must have the following signatures: +Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #IotMqttNetIf_t for a list of functions that can be overridden. If this setting is `1`, the serializer [initialization](@ref _IotMqtt_InitSerialize) and [cleanup](@ref _IotMqtt_CleanupSerialize) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default serializer initialization and cleanup functions. They must have the following signatures: @code{c} #include // Returns true on success and false on failure. -bool AwsIotMqttInternal_InitSerializeAdditional( void ); -void AwsIotMqttInternal_CleanupSerializeAdditional( void ); +bool _IotMqtt_InitSerializeAdditional( void ); +void _IotMqtt_CleanupSerializeAdditional( void ); @endcode @configpossible `0` (serializer overrides disabled) or `1` (serializer overrides enabled)
@configrecommended The default value is strongly recommended.
@configdefault The default value of this setting depends on the platform. For example, when running on FreeRTOS with BLE support, this setting will be automatically enabled. Conversely, when running on Linux, this setting will be disabled. -@section AWS_IOT_MQTT_MAX_CALLBACK_THREADS -@brief Set the maximum number of simultaneous callback threads. - -Callback threads process notifications from completed MQTT operations or incoming PUBLISH messages. Incoming notifications are queued, then removed from the queue and processed by any available callback threads. User callback functions are invoked from the callback threads. This means that user callback functions should avoid blocking, since blocking will make a callback thread unavailable and cause the queue of notifications to grow. A situation where all callback threads are blocked should certainly be avoided. - -@configpossible Any positive integer.
-@configrecommended In most use cases, this value can be `1` or `2`. In high-throughput use cases with many MQTT operations or incoming PUBLISH messages, this value can be increased for better performance.
-@configdefault `2` - -@section AWS_IOT_MQTT_MAX_SEND_THREADS -@brief Set the number of simultaneous send threads. - -Send threads process outgoing network packets. Packets are queued, then removed from the queue and sent over the network by any available send threads. - -@configpossible Any positive integer
-@configrecommended `1`. Unless the [network interface send function](@ref AwsIotMqttNetIf_t.send) supports parallel sends, any value greater than `1` will not provide any performance benefit.
-@configdefault `1` - -@section AWS_IOT_LOG_LEVEL_MQTT +@section IOT_LOG_LEVEL_MQTT @brief Set the log level of the MQTT library. Log messages from the MQTT library at or below this setting will be printed. @@ -287,25 +269,25 @@ Log messages from the MQTT library at or below this setting will be printed. @configpossible One of the @ref logging_constants_levels.
@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. -@section AWS_IOT_MQTT_RESPONSE_WAIT_MS +@section IOT_MQTT_RESPONSE_WAIT_MS @brief A "reasonable amount of time" to wait for keep-alive responses from the MQTT server. -The MQTT spec states that if a response to a keep-alive request is not received within a "reasonable amount of time", the network connection should be closed. Since the meaning of "reasonable" depends heavily on use case, the amount of time to wait for keep-alive responses is defined by this setting. This setting is also used as the amount of time to wait for a final PUBACK to a QoS 1 PUBLISH message before returning #AWS_IOT_MQTT_RETRY_NO_RESPONSE. See #AwsIotMqttPublishInfo_t for a description of QoS 1 PUBLISH retries. +The MQTT spec states that if a response to a keep-alive request is not received within a "reasonable amount of time", the network connection should be closed. Since the meaning of "reasonable" depends heavily on use case, the amount of time to wait for keep-alive responses is defined by this setting. This setting is also used as the amount of time to wait for a final PUBACK to a QoS 1 PUBLISH message before returning #IOT_MQTT_RETRY_NO_RESPONSE. See #IotMqttPublishInfo_t for a description of QoS 1 PUBLISH retries. @configpossible Any positive integer.
@configdefault `1000` -@section AWS_IOT_MQTT_RETRY_MS_CEILING -@brief Controls the maximum [retry interval](@ref AwsIotMqttPublishInfo_t.retryMs) of QoS 1 PUBLISH retransmissions. +@section IOT_MQTT_RETRY_MS_CEILING +@brief Controls the maximum [retry interval](@ref IotMqttPublishInfo_t.retryMs) of QoS 1 PUBLISH retransmissions. -QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. The interval of time between retransmissions increases exponentially until @ref AWS_IOT_MQTT_RETRY_MS_CEILING (or the [retransmission limit)](@ref AwsIotMqttPublishInfo_t.retryLimit) is reached, then increases by @ref AWS_IOT_MQTT_RETRY_MS_CEILING until the [retransmission limit](@ref AwsIotMqttPublishInfo_t.retryLimit) is reached. +QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. The interval of time between retransmissions increases exponentially until @ref IOT_MQTT_RETRY_MS_CEILING (or the [retransmission limit)](@ref IotMqttPublishInfo_t.retryLimit) is reached, then increases by @ref IOT_MQTT_RETRY_MS_CEILING until the [retransmission limit](@ref IotMqttPublishInfo_t.retryLimit) is reached. -@see #AwsIotMqttPublishInfo_t for a detailed description of the QoS 1 PUBLISH retransmission strategy. +@see #IotMqttPublishInfo_t for a detailed description of the QoS 1 PUBLISH retransmission strategy. @configpossible Any positive integer.
@configdefault `60000` -@section AWS_IOT_MQTT_TEST +@section IOT_MQTT_TEST @brief Set this to `1` to enable test access for the MQTT library. This setting is used by the [MQTT tests](@ref mqtt_tests) to access the internal `static` functions and variables of the MQTT library. When `1`, it enables the @c \#include directives at the bottom of the [MQTT library source files](@ref lib/source/mqtt). These @c \#include directives include the test access files (located in `tests/mqtt/access`) that interact with the library's internal symbols. Unless the MQTT library is being tested, this setting should be `0`. @@ -314,60 +296,28 @@ This setting is used by the [MQTT tests](@ref mqtt_tests) to access the internal @configrecommended `0`
@configdefault `0` -@section AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS -@brief Set the threshold for processing MQTT timer events in the future. - -MQTT timer events (such as keep-alive or PUBLISH retry) are sorted by their expiration time and place in a queue. A timer thread processes the events in this queue as each event's expiration time passes. To conserve memory, all MQTT connections (and therefore, all timer events for an MQTT connection) share a single system timer. The purpose of this setting is to avoid having an MQTT connection's timer expire too often. It allows the timer thread to process timer events before their expiration time if their expiration time is in the near future. - -Consider the following timer queue: -@dot -graph timer_event_example -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - subgraph - { - rank = same; - event1[label="Event 1\nExpiration time = NOW"]; - event2[label="Event 2\nExpiration time = NOW + 50"]; - event3[label="Event 3\nExpiration time = NOW + 150"]; - } - event1 -- event2; - event2 -- event3; -} -@enddot - -If @ref AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS is `100`, then both `Event 1` and `Event 2` will be processed now because their expiration times are within `100` of the current time. Because `Event 3` has an expiration time greater than `100` in the future, it will not be processed now. - -@configpossible Any non-negative integer.
-@configrecommended This value should be small to avoid premature processing of timer events. In almost no use case should this value be greater than `1000`.
-@configdefault `100` - -@section AwsIotMqtt_Assert -@brief Assertion function used when @ref AWS_IOT_MQTT_ENABLE_ASSERTS is `1`. +@section IotMqtt_Assert +@brief Assertion function used when @ref IOT_MQTT_ENABLE_ASSERTS is `1`. @configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. +@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. @section mqtt_config_memory Memory allocation @brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the MQTT library. -- #AwsIotMqtt_MallocConnection
- @copybrief AwsIotMqtt_MallocConnection -- #AwsIotMqtt_FreeConnection
- @copybrief AwsIotMqtt_FreeConnection -- #AwsIotMqtt_MallocMessage
- @copybrief AwsIotMqtt_MallocMessage -- #AwsIotMqtt_FreeMessage
- @copybrief AwsIotMqtt_FreeMessage -- #AwsIotMqtt_MallocOperation
- @copybrief AwsIotMqtt_MallocOperation -- #AwsIotMqtt_FreeOperation
- @copybrief AwsIotMqtt_FreeOperation -- #AwsIotMqtt_MallocSubscription
- @copybrief AwsIotMqtt_MallocSubscription -- #AwsIotMqtt_FreeSubscription
- @copybrief AwsIotMqtt_FreeSubscription -- #AwsIotMqtt_MallocTimerEvent
- @copybrief AwsIotMqtt_MallocTimerEvent -- #AwsIotMqtt_FreeTimerEvent
- @copybrief AwsIotMqtt_FreeTimerEvent +- #IotMqtt_MallocConnection
+ @copybrief IotMqtt_MallocConnection +- #IotMqtt_FreeConnection
+ @copybrief IotMqtt_FreeConnection +- #IotMqtt_MallocMessage
+ @copybrief IotMqtt_MallocMessage +- #IotMqtt_FreeMessage
+ @copybrief IotMqtt_FreeMessage +- #IotMqtt_MallocOperation
+ @copybrief IotMqtt_MallocOperation +- #IotMqtt_FreeOperation
+ @copybrief IotMqtt_FreeOperation +- #IotMqtt_MallocSubscription
+ @copybrief IotMqtt_MallocSubscription +- #IotMqtt_FreeSubscription
+ @copybrief IotMqtt_FreeSubscription */ diff --git a/doc/lib/static_memory.txt b/doc/lib/static_memory.txt index 987ba7b5d6..060e6cc615 100644 --- a/doc/lib/static_memory.txt +++ b/doc/lib/static_memory.txt @@ -20,9 +20,6 @@ MQTT connections correspond to a statically-allocated MQTT session between a sin @subsubsection static_memory_types_mqttoperations Operations MQTT operations correspond to in-progress requests between a client and the MQTT server, such as CONNECT, PUBLISH, or publish acknowledgement (PUBACK). In static memory mode, the number of simultaneous, active MQTT operations is controlled by the constant @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. -@subsubsection static_memory_types_mqtttimerevents Timer events -MQTT timer events store data on operations that require responses within a certain timeout: PINGREQ and QoS 1 PUBLISH. In static memory mode, the number of simultaneous, active MQTT timer events is controlled by the constant @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. - @subsubsection static_memory_types_mqttsubscriptions Subscriptions MQTT subscriptions store records on callbacks registered for MQTT topic filters. In static memory mode, the number of simultaneous, active MQTT subscriptions (across all connections) is controlled by the constant @ref IOT_MQTT_SUBSCRIPTIONS. diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt new file mode 100644 index 0000000000..207b7ed1b9 --- /dev/null +++ b/doc/lib/taskpool.txt @@ -0,0 +1,191 @@ +/** +@mainpage +@anchor taskpool +@brief Task pool library. + +> A task pool is an adaptive set of threads that can grow and shrink to execute a user-provided callback through a user-defined job that can be scheduled with a non-blocking call. The design principles are to minimize the memory footprint while allowing asynchrnous execution. The adaptive behavior allows serving jobs in a timely way, without allocating system resources for the entire duration of the application. The user does not have to worry about synchronization and thread management. + +Features of this library include: +- Both fully asynchronous and blocking API functions. +- Scalable performance and footprint. The [configuration settings](@ref taskpool_config) allow this library to be tailored to a system's resources. +- Customizable caching for low memory allocation overhead. + +@dependencies{taskpool,task pool library} +@dot "Task pool direct dependencies" +digraph taskpool_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph + { + taskpool[label="Task pool", fillcolor="#cc00ccff"]; + } + subgraph + { + node[fillcolor="#aed8a9ff"]; + rank = same; + singlelinkedlist[label="SingleLinkedList", URL="@ref singlelinkedlist"]; + queue[label="Queue", URL="@ref queue"]; + logging[label="Logging", URL="@ref logging"]; + } + subgraph + { + rank = same; + platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; + platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"];\ + platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; + } + taskpool -> queue; + taskpool -> logging [label=" if logging enabled", style="dashed"]; + taskpool -> platform_threads; + queue -> platform_threads; + logging -> platform_clock; + logging -> platform_static_memory [label=" if static memory only", style="dashed"]; +} +@enddot + +Currently, the task pool library has the following dependencies: +- The linear containers (list/queue) library for maintaining the data structures for scheduled and in-progress task pool operations. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_TASKPOOL is not @ref AWS_IOT_LOG_NONE. +- The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. + +In addition to the components above, the task pool library may also depend on C standard library headers. +*/ + +/** +@page taskpool_design Design +@brief Architecture behind the task pool library. + +This library uses a user-specified set of threads to process jobs speficied by the user as a callback and a context. User jobs are queued through a non-blocking call and processed asynchronously in the order they are received. +- [Task pool API functions](@ref taskpool_functions) Provides a set of functions to enqueue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully enqueuing an operation. +- Worker threads in the task pool are woken up when operations arrive in the dispatch queue. These threads remove operations from the dispatch queue in FIFO order and execute the user-provided callback. After executing the user callback, the task pool threads try and execute any remaining jobs in the dispatch queue. The task pool tries and execute a user job as soon as it is received and if there are no threads available it will try and create one, up to the maximum number of allowed threads. The user can specificy the minimum and maximum number of threads allowed when creating the task pool. +- The user can try and cancel a job after the task has been scheduled. Cancellation is only allowed before the task enters execution. Wating on a completed task returns immediately. + +Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the task pool library on-the-go on some systems, while other systems may use an always-allocated thread pool. + +The sequence diagram below illustrates the workflow described above. The application thread is able to continue executing while the task pool library processes the operation. + +@image html taskpool_design_typicaloperation.png width=100% +*/ + +/** +@page taskpool_demo Demo +@brief The task pool demo demonstrates usage of the task pool library. + +The task pool demo demonstrates the create-schedule workflow of task pool. After creating multiple jobs, it schedules all jobs and wait on their completion, or try and cancel them. + +@image html taskpool_demo.png "task pool Demo Workflow" width=80% + +See @subpage taskpool_demo_config for configuration settings that change the behavior of the demo. + +The main task pool demo file, aws_iot_demo_taskpool.c, contains platform-independent code. See the following guides for running the task pool demo on various platforms. +- @subpage demo_posix
+ @copybrief demo_posix +*/ + +/** +@page taskpool_tests Tests +@brief Tests written for the task pool library. + +The task pool tests are divided into the following subdirectories: +- `system`: task pool system and stress tests. Stress tests may run for a long time, so they are not run unless the [-l option is passed to the test executable](@ref taskpool_tests_optionl). +- `unit`: task pool unit tests. These tests do not require a network connection. + +See @subpage taskpool_tests_config for configuration settings that change the behavior of the tests. + +The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. See @subpage taskpool_tests_running for a guide on running the tests. +*/ + +/** +@configpage{taskpool_tests,task pool tests,Test,tests} + +
The settings below only affect the [stress](@ref aws_iot_tests_taskpool_stress.c) tests.
+ +*/ + +/** +@page taskpool_tests_running Running task pool tests +@brief Guide for running the task pool tests. + +The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. + +@pre The tests have the following system dependencies: +- POSIX threads, mutexes, and semaphores (link with `-lpthread`). + +@section taskpool_tests_building Building the tests +@brief How to build the task pool tests. + +Before building the task pool tests, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [test](@ref global_tests_config) settings, as well as settings for specific libraries and demos. + +All tests specify their configuration settings in `tests/aws_iot_tests_config.h`. Any undefined settings will use a default value when possible. + +The task pool tests build with a POSIX Makefile. A single Makefile will build all the task pool tests in both dynamic memory and static memory only modes. See @ref AWS_IOT_STATIC_MEMORY_ONLY for more information about static memory only mode. The Makefile also builds tests against AWS IoT and a [public Mosquitto test server](https://test.mosquitto.org/). + +The Makefile for building the task pool tests is located in `tests/taskpool`. To build the tests: +@code +cd tests/task_pool +make +@endcode + +The Makefile will generate four test executables: +- `aws_iot_tests_taskpool_aws`: Tests against AWS IoT in dynamic memory mode. + +Run `make clean` to remove all test executables. + +@section taskpool_tests_commandlineoptions Command line options +@brief Command line options of the task pool test executables. + +All task pool test executables accept the following command line options. + +@subsection taskpool_tests_optionl -l +@brief Enable long-running tests. + +By default, the task pool test executables don't run the stress tests, which may take a long time to run and require an extremely stable network connection. Passing `-l` enables the long-running tests, which will run after all of the default tests finish. +*/ + +/** +@configpage{taskpool_demo,task pool demo,Demo,demos} + +@section AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER +@brief The task pool client identifier to use for the demo. + +*/ + +/** +@configpage{taskpool,task pool library} + +@section AWS_IOT_TASKPOOL_THREADS_STACK_MIN +@brief Set this to minimum stack depth to be enforced at runtime. + +@section AWS_IOT_TASKPOOL_THREADS_STACK_SIZE +@brief Set this to the default stack depth used by the task pool initializers. + +@section AWS_IOT_TASKPOOL_PRIORITY +@brief Set this to the default task priority used by the task pool initializers. + +@section AWS_IOT_TASKPOOL_ENABLE_ASSERTS +@brief Set this to `1` to perform sanity checks when using the task pool library. + +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotTaskPool_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. + +@configpossible `0` (asserts disabled) or `1` (asserts enabled)
+@configrecommended `1` when debugging; `0` in production code.
+@configdefault `0` + +@section AWS_IOT_LOG_LEVEL_TASKPOOL +@brief Set the log level of the task pool library. + +Log messages from the task pool library at or below this setting will be printed. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. + +@section AWS_IOT_TASKPOOL_TEST +@brief Set this to `1` to enable test access for the task pool library. + +This setting is used by the [task pool tests](@ref taskpool_tests) to access the internal `static` functions and variables of the task pool library. When `1`, it enables the @c \#include directives at the bottom of the [task pool library source files](@ref lib/source/taskpool). These @c \#include directives include the test access files (located in `tests/taskpool/access`) that interact with the library's internal symbols. Unless the task pool library is being tested, this setting should be `0`. + +@configpossible `0` (test access disabled) or `1` (test access enabled)
+@configrecommended `0`
+@configdefault `0` +*/ diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 93981bd33d..05cebbced2 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -27,7 +27,7 @@ The following pages describe configuration settings that affect multiple compone @globalconfigpage{library,Library,libraries} @section IOT_CONFIG_FILE -@brief Configuration header for the AWS IoT libraries. +@brief Configuration header for the libraries in this SDK. Define a file to be included in all library source files before any other files. Unlike other settings, this one must be set using a compiler option, such as `-D` for gcc. The option should specify a filename that is in the include path; for example, `-DIOT_CONFIG_FILE="iot_config.h"` (quotes may need to be escaped for some compilers). @@ -57,10 +57,10 @@ When dynamic memory allocation is disabled, all of the memory allocation functio /** @globalconfigpage{demos,Demo,demos} -@section AWS_IOT_DEMO_SECURED_CONNECTION +@section IOT_DEMO_SECURED_CONNECTION @brief Determines if the demos default to a TLS-secured connection with the remote host. -When this setting is `true`, all demo applications will use a TLS-secured connection by default. The default credentials for connections are @ref AWS_IOT_DEMO_ROOT_CA, @ref AWS_IOT_DEMO_CLIENT_CERT, and @ref AWS_IOT_DEMO_PRIVATE_KEY. Any connection with AWS IoT must be secured. +When this setting is `true`, all demo applications will use a TLS-secured connection by default. The default credentials for connections are @ref IOT_DEMO_ROOT_CA, @ref IOT_DEMO_CLIENT_CERT, and @ref IOT_DEMO_PRIVATE_KEY. Any connection with AWS IoT must be secured. In demo applications, the default secured connection setting can be overridden using the command line options `-s` or `-u`. Neither of these command line options have an argument, and only one of the two should be used at a time. Passing `-s` will cause the demo application to use a secured connection, and passing `-u` will cause the demo application to use an unsecured connection. @@ -68,7 +68,7 @@ In demo applications, the default secured connection setting can be overridden u @configrecommended When using the demo applications with AWS IoT, connections must be secured. Therefore, the recommended value for this setting is `true`.
@configdefault If this setting is undefined and neither `-s` nor `-u` are passed as command line options, then any connections will be unsecured. -@section AWS_IOT_DEMO_SERVER +@section IOT_DEMO_SERVER @brief The default remote host to use in the demos. All demo applications will open TCP connections to this host. When using the demos with AWS IoT, this should be set to the AWS account's custom IoT endpoint. This custom endpoint can be found under the Settings tab of the AWS IoT console. It has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`, where `ABCDEFG1234567` is a placeholder subdomain and `us-east-2` is the region. @@ -82,10 +82,10 @@ In demo applications, the default server can be overridden using the command lin - `192.168.1.1` - `localhost` -@section AWS_IOT_DEMO_PORT +@section IOT_DEMO_PORT @brief The default remote port to use in the demos. -All demo applications will open TCP connections to @ref AWS_IOT_DEMO_SERVER on this port. When using the demos with AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). +All demo applications will open TCP connections to @ref IOT_DEMO_SERVER on this port. When using the demos with AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). In demo applications, the default server port may be overridden using the command line option `-p`. The command line option will override this setting. @@ -94,10 +94,10 @@ In demo applications, the default server port may be overridden using the comman @configpossible Any positive integer below `65536`.
@configrecommended When using the demo applications with AWS IoT, port `443` is recommended. -@section AWS_IOT_DEMO_ROOT_CA +@section IOT_DEMO_ROOT_CA @brief The path to the default trusted server root certificate to use in the demos. -A trusted server root certificate only needs to be set for [secured connections](@ref AWS_IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. +A trusted server root certificate only needs to be set for [secured connections](@ref IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. In demo applications, the default trusted server root certificate can be overridden using the command line option `-r`. The command line option will override this setting. @@ -106,10 +106,10 @@ In demo applications, the default trusted server root certificate can be overrid @configpossible Any string representing a filesystem path to a PEM-encoded trusted server root certificate.
@configrecommended When using the demo applications with AWS IoT, one of [these root certificates](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication) should be used. -@section AWS_IOT_DEMO_CLIENT_CERT +@section IOT_DEMO_CLIENT_CERT @brief The path to the default client certificate to use in the demos. -A client certificate only needs to be set for [secured connections](@ref AWS_IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. +A client certificate only needs to be set for [secured connections](@ref IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. In demo applications, the default client certificate can be overridden with the command line option `-c`. The command line option will override this setting. @@ -117,16 +117,16 @@ In demo applications, the default client certificate can be overridden with the @configpossible Any string representing a filesystem path to a PEM-encoded client certificate. -@section AWS_IOT_DEMO_PRIVATE_KEY +@section IOT_DEMO_PRIVATE_KEY @brief The path to the default client certificate private key to use in the demos. -A client certificate private key only needs to be set for [secured connections](@ref AWS_IOT_DEMO_SECURED_CONNECTION). This private key must match the [client certificate](@ref AWS_IOT_DEMO_CLIENT_CERT). +A client certificate private key only needs to be set for [secured connections](@ref IOT_DEMO_SECURED_CONNECTION). This private key must match the [client certificate](@ref IOT_DEMO_CLIENT_CERT). In demo applications, the default client certificate private key can be overridden with the command line option `-k`. The command line option will override this setting. No default value is provided for this setting. If this setting is undefined and no command line option `-k` is given to a demo application using a secured connection, the demo will fail. -@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref AWS_IOT_DEMO_CLIENT_CERT). +@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref IOT_DEMO_CLIENT_CERT). @section AWS_IOT_DEMO_THING_NAME @brief Default Thing Name used for demos specific to AWS IoT. @@ -137,7 +137,7 @@ In demo applications, the default Thing Name can be overridden with the command No default value is provided for this setting. For demos requiring a Thing Name (such as Shadow) this setting must be defined (or a command line option `-i` given to the demo application); otherwise, the demo will fail. -This setting does not affect the MQTT demo (which is not specific to AWS IoT); see @ref AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER for the equivalent setting for the MQTT demo. +This setting does not affect the MQTT demo (which is not specific to AWS IoT); see @ref IOT_DEMO_MQTT_CLIENT_IDENTIFIER for the equivalent setting for the MQTT demo. @configpossible Any string representing an AWS IoT Thing Name. */ @@ -145,16 +145,16 @@ This setting does not affect the MQTT demo (which is not specific to AWS IoT); s /** @globalconfigpage{tests,Test,tests} -@section AWS_IOT_TEST_SECURED_CONNECTION +@section IOT_TEST_SECURED_CONNECTION @brief Determines if the tests use a TLS-secured connection with the remote host. -When this setting is `true`, all test applications will use a TLS-secured connection. The credentials for connections are @ref AWS_IOT_TEST_ROOT_CA, @ref AWS_IOT_TEST_CLIENT_CERT, and @ref AWS_IOT_TEST_PRIVATE_KEY. Any connection with AWS IoT must be secured. +When this setting is `true`, all test applications will use a TLS-secured connection. The credentials for connections are @ref IOT_TEST_ROOT_CA, @ref IOT_TEST_CLIENT_CERT, and @ref IOT_TEST_PRIVATE_KEY. Any connection with AWS IoT must be secured. @configpossible `true` (secured connection) or `false` (unsecured connection)
@configrecommended When testing against AWS IoT, connections must be secured. Therefore, the recommended value for this setting is `true`.
@configdefault `true` -@section AWS_IOT_TEST_SERVER +@section IOT_TEST_SERVER @brief The remote host to use in the tests. Tests using the network will open TCP connections to this host. When testing against AWS IoT, this should be set to the AWS account's custom IoT endpoint. This custom endpoint can be found under the Settings tab of the AWS IoT console. It has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`, where `ABCDEFG1234567` is a placeholder subdomain and `us-east-2` is the region. @@ -166,41 +166,41 @@ Tests using the network will open TCP connections to this host. When testing aga - `192.168.1.1` - `localhost` -@section AWS_IOT_TEST_PORT +@section IOT_TEST_PORT @brief The remote port to use in the tests. -Tests using the network will open TCP connections to @ref AWS_IOT_TEST_SERVER on this port. When testing against AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). +Tests using the network will open TCP connections to @ref IOT_TEST_SERVER on this port. When testing against AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). No default value is provided for this setting. If this setting is undefined, the tests will fail to compile. @configpossible Any positive integer below `65536`.
@configrecommended When testing against AWS IoT, port `443` is recommended. -@section AWS_IOT_TEST_ROOT_CA +@section IOT_TEST_ROOT_CA @brief The path to the trusted server root certificate to use in the tests. -A trusted server root certificate only needs to be set for [secured connections](@ref AWS_IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. +A trusted server root certificate only needs to be set for [secured connections](@ref IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. @configpossible Any string representing a filesystem path to a PEM-encoded trusted server root certificate.
@configrecommended When testing against AWS IoT, one of [these root certificates](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication) should be used. -@section AWS_IOT_TEST_CLIENT_CERT +@section IOT_TEST_CLIENT_CERT @brief The path to the client certificate to use in the tests. -A client certificate only needs to be set for [secured connections](@ref AWS_IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. +A client certificate only needs to be set for [secured connections](@ref IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. @configpossible Any string representing a filesystem path to a PEM-encoded client certificate. -@section AWS_IOT_TEST_PRIVATE_KEY +@section IOT_TEST_PRIVATE_KEY @brief The path to the client certificate private key to use in the tests. -A client certificate private key only needs to be set for [secured connections](@ref AWS_IOT_TEST_SECURED_CONNECTION). This private key must match the [client certificate](@ref AWS_IOT_TEST_CLIENT_CERT). +A client certificate private key only needs to be set for [secured connections](@ref IOT_TEST_SECURED_CONNECTION). This private key must match the [client certificate](@ref IOT_TEST_CLIENT_CERT). No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. -@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref AWS_IOT_TEST_CLIENT_CERT). +@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref IOT_TEST_CLIENT_CERT). */ diff --git a/doc/plantuml/images/taskpool_design_typicaloperation.png b/doc/plantuml/images/taskpool_design_typicaloperation.png new file mode 100644 index 0000000000000000000000000000000000000000..87e85e407bcefe1327a4ee34a8d073bcb3e164b7 GIT binary patch literal 70318 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
KLl~ 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 literal 0 HcmV?d00001 diff --git a/doc/plantuml/taskpool_design_typicaloperation.pu b/doc/plantuml/taskpool_design_typicaloperation.pu new file mode 100644 index 0000000000..ab06d2e6d1 --- /dev/null +++ b/doc/plantuml/taskpool_design_typicaloperation.pu @@ -0,0 +1,77 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica +autonumber + +participant "Application" as app +participant "User Callback" as callback +participant "Job" as job + +participant "Task Pool public API" as TP +participant "Dispatch queue" as queue +participant "Worker Threads" as workers + +box "Task Pool" #LightBlue + participant TP + participant queue + participant workers +end box + +== Create Task Pool == + +activate app + +app -[#blue]> TP: AwsIotTaskPool_Create: create a Task +TP -> queue: Initialize dispatch queue +activate queue +TP -> workers: Create minimum number of worker threads +activate workers +TP --[#blue]> app +activate TP +workers -> workers: Wait on incoming jobs + +== Use Task Pool == + +loop Application loop: Create and schedule jobs + app -[#blue]> TP: AwsIotTaskPool_CreateJob: create a job + TP --[#blue]> app + activate job + note left: job status: //ready// + + app -[#blue]> TP: AwsIotTaskPool_Schedule: schedule a job + TP -> queue: Queue job + TP -> TP: Grow pool up to maximum threads, if all threads are busy + TP -> workers: Signal incoming job + TP --[#blue]> app + note left: job status: //scheduled// + + loop Outer dispatch loop: Wait on incoming jobs + workers -> queue: Dequeue next job + loop Inner dispatch loop: Execute any queue jobs in order + workers -[#green]> job: Invoke user callback + note left: job status: //executing// + job -[#green]> callback: Invoke + activate callback + callback -[#blue]> TP: AwsIotTaskPool_DestroyJob: destroy job + TP --[#blue]> callback + note left: job status: //completed// + deactivate job + deactivate callback + workers -> workers: Move to next job + end + workers -> workers: Wait on incoming jobs + end + +end + +== Destroy Task Pool == + + app -[#blue]> TP: AwsIotTaskPool_Destroy: destroy the task pool + TP -> workers: Shutdown worker threads + deactivate workers + TP -> queue: Destroy all jobs in the dispatch queue + deactivate queue + TP --[#blue]> app + deactivate TP + +@enduml diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 2f145a783c..a868d50df7 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -33,7 +33,7 @@ #endif /* MQTT include. */ -#include "aws_iot_mqtt.h" +#include "iot_mqtt.h" /*--------------------------- Shadow handle types ---------------------------*/ @@ -102,7 +102,7 @@ typedef enum AwsIotShadowError * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) * when successful. */ - AWS_IOT_SHADOW_SUCCESS, + AWS_IOT_SHADOW_SUCCESS = 0, /** * @brief Shadow operation queued, awaiting result. @@ -449,9 +449,9 @@ typedef struct AwsIotShadowDocumentInfo const char * pThingName; /**< @brief The Thing Name associated with this Shadow document. */ size_t thingNameLength; /**< @brief Length of #AwsIotShadowDocumentInfo_t.pThingName. */ - int QoS; /**< @brief QoS when sending a Shadow get or update message. See #AwsIotMqttPublishInfo_t.QoS. */ - int retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #AwsIotMqttPublishInfo_t.retryLimit. */ - uint64_t retryMs; /**< @brief First retry time for a Shadow get or update message. See #AwsIotMqttPublishInfo_t.retryMs. */ + IotMqttQos_t qos; /**< @brief QoS when sending a Shadow get or update message. See #IotMqttPublishInfo_t.qos. */ + uint32_t retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #IotMqttPublishInfo_t.retryLimit. */ + uint64_t retryMs; /**< @brief First retry time for a Shadow get or update message. See IotMqttPublishInfo_t.retryMs. */ union { @@ -698,7 +698,7 @@ void AwsIotShadow_Cleanup( void ); * the Delete completes. */ /* @[declare_shadow_delete] */ -AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -710,7 +710,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, * @brief Delete a Thing Shadow with a timeout. */ /* @[declare_shadow_timeddelete] */ -AwsIotShadowError_t AwsIotShadow_TimedDelete( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -722,7 +722,7 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( AwsIotMqttConnection_t mqttConnect * the Shadow document is received. */ /* @[declare_shadow_get] */ -AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pGetInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * const pCallbackInfo, @@ -733,7 +733,7 @@ AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, * @brief Retrieve a Thing Shadow with a timeout. */ /* @[declare_shadow_timedget] */ -AwsIotShadowError_t AwsIotShadow_TimedGet( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pGetInfo, uint32_t flags, uint64_t timeoutMs, @@ -746,7 +746,7 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( AwsIotMqttConnection_t mqttConnection * the Shadow Update completes. */ /* @[declare_shadow_update] */ -AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pUpdateInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * const pCallbackInfo, @@ -757,7 +757,7 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, * @brief Send a Thing Shadow update with a timeout. */ /* @[declare_shadow_timedupdate] */ -AwsIotShadowError_t AwsIotShadow_TimedUpdate( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pUpdateInfo, uint32_t flags, uint64_t timeoutMs ); @@ -1002,7 +1002,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, * @endcode */ /* @[declare_shadow_setdeltacallback] */ -AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -1013,7 +1013,7 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttCo * @brief Set a callback to be invoked when a Thing Shadow changes. */ /* @[declare_shadow_setupdatedcallback] */ -AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -1026,7 +1026,7 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( AwsIotMqttConnection_t mqtt * Not safe to call with any in-progress operation. Does not affect callbacks. */ /* @[declare_shadow_removepersistentsubscriptions] */ -AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags ); diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index e1845287af..03a0bd666b 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -876,9 +876,9 @@ static inline void IotQueue_RemoveAll( IotQueue_t * const pQueue, */ /* @[declare_linear_containers_queue_removeallmatches] */ static inline void IotQueue_RemoveAllMatches( IotQueue_t * const pQueue, - bool( *isMatch )( const IotLink_t *, void * ), + bool ( * isMatch )( const IotLink_t *, void * ), void * pMatch, - void( *freeElement )( void * ), + void ( * freeElement )( void * ), size_t linkOffset ) /* @[declare_linear_containers_queue_removeallmatches] */ { diff --git a/lib/include/aws_iot_mqtt.h b/lib/include/iot_mqtt.h similarity index 63% rename from lib/include/aws_iot_mqtt.h rename to lib/include/iot_mqtt.h index 6a86f75489..8eb00fa0c6 100644 --- a/lib/include/aws_iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -20,12 +20,12 @@ */ /** - * @file aws_iot_mqtt.h + * @file iot_mqtt.h * @brief User-facing functions and structs of the MQTT 3.1.1 library. */ -#ifndef _AWS_IOT_MQTT_H_ -#define _AWS_IOT_MQTT_H_ +#ifndef _IOT_MQTT_H_ +#define _IOT_MQTT_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -59,9 +59,9 @@ * @ref mqtt_function_disconnect returns, the connection handle should no longer * be used. * - * @initializer{AwsIotMqttConnection_t,AWS_IOT_MQTT_CONNECTION_INITIALIZER} + * @initializer{IotMqttConnection_t,IOT_MQTT_CONNECTION_INITIALIZER} */ -typedef void * AwsIotMqttConnection_t; +typedef void * IotMqttConnection_t; /** * @ingroup mqtt_datatypes_handles @@ -74,16 +74,16 @@ typedef void * AwsIotMqttConnection_t; * * This reference will be valid from the successful return of @ref mqtt_function_publish, * @ref mqtt_function_subscribe, or @ref mqtt_function_unsubscribe. The reference becomes - * invalid once the [completion callback](@ref AwsIotMqttCallbackInfo_t) is invoked, or + * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or * @ref mqtt_function_wait returns. * - * @initializer{AwsIotMqttReference_t,AWS_IOT_MQTT_REFERENCE_INITIALIZER} + * @initializer{IotMqttReference_t,IOT_MQTT_REFERENCE_INITIALIZER} * - * @see @ref mqtt_function_wait and #AWS_IOT_MQTT_FLAG_WAITABLE for waiting on a reference. - * #AwsIotMqttCallbackInfo_t and #AwsIotMqttCallbackParam_t for an asynchronous notification + * @see @ref mqtt_function_wait and #IOT_MQTT_FLAG_WAITABLE for waiting on a reference. + * #IotMqttCallbackInfo_t and #IotMqttCallbackParam_t for an asynchronous notification * of completion. */ -typedef void * AwsIotMqttReference_t; +typedef void * IotMqttReference_t; /*-------------------------- MQTT enumerated types --------------------------*/ @@ -98,7 +98,7 @@ typedef void * AwsIotMqttReference_t; * The function @ref mqtt_function_strerror can be used to get a return code's * description. */ -typedef enum AwsIotMqttError +typedef enum IotMqttError { /** * @brief MQTT operation completed successfully. @@ -112,9 +112,9 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_timedpublish * * Will also be the value of an operation completion callback's - * #AwsIotMqttCallbackParam_t.result when successful. + * #IotMqttCallbackParam_t.result when successful. */ - AWS_IOT_MQTT_SUCCESS = 0, + IOT_MQTT_SUCCESS = 0, /** * @brief MQTT operation queued, awaiting result. @@ -124,7 +124,7 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_unsubscribe * - @ref mqtt_function_publish with QoS 1 parameter */ - AWS_IOT_MQTT_STATUS_PENDING, + IOT_MQTT_STATUS_PENDING, /** * @brief Initialization failed. @@ -132,7 +132,7 @@ typedef enum AwsIotMqttError * Functions that may return this value: * - @ref mqtt_function_init */ - AWS_IOT_MQTT_INIT_FAILED, + IOT_MQTT_INIT_FAILED, /** * @brief At least one parameter is invalid. @@ -144,7 +144,7 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish * - @ref mqtt_function_wait */ - AWS_IOT_MQTT_BAD_PARAMETER, + IOT_MQTT_BAD_PARAMETER, /** * @brief MQTT operation failed because of memory allocation failure. @@ -155,10 +155,12 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish */ - AWS_IOT_MQTT_NO_MEMORY, + IOT_MQTT_NO_MEMORY, /** - * @brief MQTT packet could not be transmitted on the network. + * @brief MQTT operation failed because the network was unusable. + * + * This return value may indicate that the network is disconnected. * * Functions that may return this value: * - @ref mqtt_function_connect @@ -168,9 +170,20 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_timedpublish * * May also be the value of an operation completion callback's - * #AwsIotMqttCallbackParam_t.result. + * #IotMqttCallbackParam_t.result. */ - AWS_IOT_MQTT_SEND_ERROR, + IOT_MQTT_NETWORK_ERROR, + + /** + * @brief MQTT operation could not be scheduled, i.e. enqueued for sending. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + */ + IOT_MQTT_SCHEDULING_ERROR, /** * @brief MQTT response packet received from the network is malformed. @@ -183,13 +196,13 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_timedpublish * * May also be the value of an operation completion callback's - * #AwsIotMqttCallbackParam_t.result. + * #IotMqttCallbackParam_t.result. * * @note If this value is received, the network connection has been closed - * (unless a [disconnect function](@ref AwsIotMqttNetIf_t.disconnect) was not + * (unless a [disconnect function](@ref IotMqttNetIf_t.disconnect) was not * provided to @ref mqtt_function_connect). */ - AWS_IOT_MQTT_BAD_RESPONSE, + IOT_MQTT_BAD_RESPONSE, /** * @brief A blocking MQTT operation timed out. @@ -201,19 +214,19 @@ typedef enum AwsIotMqttError * - @ref mqtt_function_timedunsubscribe * - @ref mqtt_function_timedpublish */ - AWS_IOT_MQTT_TIMEOUT, + IOT_MQTT_TIMEOUT, /** * @brief A CONNECT or at least one subscription was refused by the server. * * Functions that may return this value: * - @ref mqtt_function_connect - * - @ref mqtt_function_wait, but only when its #AwsIotMqttReference_t parameter + * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter * is associated with a SUBSCRIBE operation. * - @ref mqtt_function_timedsubscribe * * May also be the value of an operation completion callback's - * #AwsIotMqttCallbackParam_t.result for a SUBSCRIBE. + * #IotMqttCallbackParam_t.result for a SUBSCRIBE. * * @note If this value is returned and multiple subscriptions were passed to * @ref mqtt_function_subscribe (or @ref mqtt_function_timedsubscribe), it's @@ -222,22 +235,22 @@ typedef enum AwsIotMqttError * mqtt_function_issubscribed can be used to determine which subscriptions * were accepted or rejected. */ - AWS_IOT_MQTT_SERVER_REFUSED, + IOT_MQTT_SERVER_REFUSED, /** * @brief A QoS 1 PUBLISH received no response and [the retry limit] - * (#AwsIotMqttPublishInfo_t.retryLimit) was reached. + * (#IotMqttPublishInfo_t.retryLimit) was reached. * * Functions that may return this value: - * - @ref mqtt_function_wait, but only when its #AwsIotMqttReference_t parameter + * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter * is associated with a QoS 1 PUBLISH operation * - @ref mqtt_function_timedpublish * * May also be the value of an operation completion callback's - * #AwsIotMqttCallbackParam_t.result for a QoS 1 PUBLISH. + * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. */ - AWS_IOT_MQTT_RETRY_NO_RESPONSE -} AwsIotMqttError_t; + IOT_MQTT_RETRY_NO_RESPONSE +} IotMqttError_t; /** * @ingroup mqtt_datatypes_enums @@ -246,16 +259,38 @@ typedef enum AwsIotMqttError * The function @ref mqtt_function_operationtype can be used to get an operation * type's description. */ -typedef enum AwsIotMqttOperationType +typedef enum IotMqttOperationType { - AWS_IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ - AWS_IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ - AWS_IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ - AWS_IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ - AWS_IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ - AWS_IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ - AWS_IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ -} AwsIotMqttOperationType_t; + IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ + IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ + IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ + IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ + IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ + IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ + IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ +} IotMqttOperationType_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief Quality of service levels for MQTT PUBLISH messages. + * + * All MQTT PUBLISH messages, including Last Will and Testament and messages + * received on subscription filters, have an associated Quality of Service, + * which defines any delivery guarantees for that message. + * - QoS 0 messages will be delivered at most once. This is a "best effort" + * transmission with no retransmissions. + * - QoS 1 messages will be delivered at least once. See #IotMqttPublishInfo_t + * for the retransmission strategy this library uses to redeliver messages + * assumed to be lost. + * + * @attention QoS 2 is not supported by this library and should not be used. + */ +typedef enum IotMqttQos +{ + IOT_MQTT_QOS_0 = 0, /**< Delivery at most once. */ + IOT_MQTT_QOS_1 = 1, /**< Delivery at least once. See #IotMqttPublishInfo_t for client-side retry strategy. */ + IOT_MQTT_QOS_2 = 2 /**< Delivery exactly once. Unsupported, but enumerated for completeness. */ +} IotMqttQos_t; /*------------------------- MQTT parameter structs --------------------------*/ @@ -272,31 +307,31 @@ typedef enum AwsIotMqttOperationType * Passed to @ref mqtt_function_publish as the message to publish and @ref * mqtt_function_connect as the Last Will and Testament (LWT) message. * - * @initializer{AwsIotMqttPublishInfo_t,AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER} + * @initializer{IotMqttPublishInfo_t,IOT_MQTT_PUBLISH_INFO_INITIALIZER} * - * #AwsIotMqttPublishInfo_t.retryMs and #AwsIotMqttPublishInfo_t.retryLimit are only + * #IotMqttPublishInfo_t.retryMs and #IotMqttPublishInfo_t.retryLimit are only * relevant to QoS 1 PUBLISH messages. They are ignored for QoS 0 PUBLISH * messages and LWT messages. These members control retransmissions of QoS 1 * messages under the following rules: - * - Retransmission is disabled when #AwsIotMqttPublishInfo_t.retryLimit is 0. + * - Retransmission is disabled when #IotMqttPublishInfo_t.retryLimit is 0. * After sending the PUBLISH, the library will wait indefinitely for a PUBACK. - * - If #AwsIotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes - * that do not receive a PUBACK within #AwsIotMqttPublishInfo_t.retryMs will be - * retransmitted, up to #AwsIotMqttPublishInfo_t.retryLimit times. + * - If #IotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes + * that do not receive a PUBACK within #IotMqttPublishInfo_t.retryMs will be + * retransmitted, up to #IotMqttPublishInfo_t.retryLimit times. * * Retransmission follows a truncated exponential backoff strategy. The constant - * @ref AWS_IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. + * @ref IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. * - * After #AwsIotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT - * library will wait @ref AWS_IOT_MQTT_RESPONSE_WAIT_MS before a final check + * After #IotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT + * library will wait @ref IOT_MQTT_RESPONSE_WAIT_MS before a final check * for a PUBACK. If no PUBACK was received within this time, the QoS 1 PUBLISH - * fails with the code #AWS_IOT_MQTT_RETRY_NO_RESPONSE. + * fails with the code #IOT_MQTT_RETRY_NO_RESPONSE. * * @note The lengths of the strings in this struct should not include the NULL * terminator. Strings in this struct do not need to be NULL-terminated. * * @note The AWS IoT MQTT server does not support the DUP bit. When - * [using this library with the AWS IoT MQTT server](@ref AwsIotMqttConnectInfo_t.awsIotMqttMode), + * [using this library with the AWS IoT MQTT server](@ref IotMqttConnectInfo_t.awsIotMqttMode), * retransmissions will instead be sent with a new packet identifier in the PUBLISH * packet. This is a nonstandard workaround. Note that this workaround has some * flaws, including the following: @@ -311,9 +346,9 @@ typedef enum AwsIotMqttOperationType * Example * * Consider a situation where - * - @ref AWS_IOT_MQTT_RETRY_MS_CEILING is 60000 - * - #AwsIotMqttPublishInfo_t.retryMs is 2000 - * - #AwsIotMqttPublishInfo_t.retryLimit is 20 + * - @ref IOT_MQTT_RETRY_MS_CEILING is 60000 + * - #IotMqttPublishInfo_t.retryMs is 2000 + * - #IotMqttPublishInfo_t.retryLimit is 20 * * A PUBLISH message will be retransmitted at the following times after the initial * transmission if no PUBACK is received: @@ -325,22 +360,22 @@ typedef enum AwsIotMqttOperationType * - 122000 ms, 182000 ms, 242000 ms... (every 60000 ms until 20 transmissions have been sent) * * After the 20th retransmission, the MQTT library will wait - * @ref AWS_IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. + * @ref IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. */ -typedef struct AwsIotMqttPublishInfo +typedef struct IotMqttPublishInfo { - int QoS; /**< @brief QoS of message. Must be 0 or 1. */ + IotMqttQos_t qos; /**< @brief QoS of message. Must be 0 or 1. */ bool retain; /**< @brief MQTT message retain flag. */ const char * pTopicName; /**< @brief Topic name of PUBLISH. */ - uint16_t topicNameLength; /**< @brief Length of #AwsIotMqttPublishInfo_t.pTopicName. */ + uint16_t topicNameLength; /**< @brief Length of #IotMqttPublishInfo_t.pTopicName. */ const void * pPayload; /**< @brief Payload of PUBLISH. */ - size_t payloadLength; /**< @brief Length of #AwsIotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ + size_t payloadLength; /**< @brief Length of #IotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ uint64_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ - int retryLimit; /**< @brief How many times to attempt retransmission. */ -} AwsIotMqttPublishInfo_t; + uint32_t retryLimit; /**< @brief How many times to attempt retransmission. */ +} IotMqttPublishInfo_t; /** * @ingroup mqtt_datatypes_paramstructs @@ -364,14 +399,14 @@ typedef struct AwsIotMqttPublishInfo * callback function returns, so it must be copied if it is needed at a later time. * * @attention Any pointers in this callback parameter may be freed as soon as - * the [callback function](@ref AwsIotMqttCallbackInfo_t.function) returns. + * the [callback function](@ref IotMqttCallbackInfo_t.function) returns. * Therefore, data must be copied if it is needed after the callback function * returns. * @attention The MQTT library may set strings that are not NULL-terminated. * - * @see #AwsIotMqttCallbackInfo_t for the signature of a callback function. + * @see #IotMqttCallbackInfo_t for the signature of a callback function. */ -typedef struct AwsIotMqttCallbackParam +typedef struct IotMqttCallbackParam { /** * @brief The MQTT connection associated with this completed operation or @@ -381,16 +416,16 @@ typedef struct AwsIotMqttCallbackParam * However, blocking function calls (including @ref mqtt_function_wait) are * not recommended (though still safe). */ - AwsIotMqttConnection_t mqttConnection; + IotMqttConnection_t mqttConnection; union { /* Valid for completed operations. */ struct { - AwsIotMqttOperationType_t type; /**< @brief Type of operation that completed. */ - AwsIotMqttReference_t reference; /**< @brief Reference to the operation that completed. */ - AwsIotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ + IotMqttOperationType_t type; /**< @brief Type of operation that completed. */ + IotMqttReference_t reference; /**< @brief Reference to the operation that completed. */ + IotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ } operation; /* Valid for incoming PUBLISH messages. */ @@ -398,22 +433,22 @@ typedef struct AwsIotMqttCallbackParam { const char * pTopicFilter; /**< @brief Topic filter that matched the message. */ uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ - AwsIotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ + IotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ } message; }; -} AwsIotMqttCallbackParam_t; +} IotMqttCallbackParam_t; /** * @ingroup mqtt_datatypes_paramstructs * @brief Information on a user-provided MQTT callback function. * * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, - * and @ref mqtt_function_publish. Cannot be used with #AWS_IOT_MQTT_FLAG_WAITABLE. + * and @ref mqtt_function_publish. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. * * Provides a function to be invoked when an operation completes or when a * server-to-client PUBLISH is received. * - * @initializer{AwsIotMqttCallbackInfo_t,AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER} + * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} * * Below is an example for receiving an asynchronous notification on operation * completion. See @ref mqtt_function_subscribe for an example of using this struct @@ -421,40 +456,40 @@ typedef struct AwsIotMqttCallbackParam * * @code{c} * // Operation completion callback. - * void operationComplete( void * pArgument, AwsIotMqttCallbackParam_t * const pOperation ); + * void operationComplete( void * pArgument, IotMqttCallbackParam_t * pOperation ); * * // Callback information. - * AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; * callbackInfo.function = operationComplete; * * // Operation to wait for. - * AwsIotMqttError_t result = AwsIotMqtt_Publish( mqttConnection, - * &publishInfo, - * 0, - * &callbackInfo, - * &reference ); + * IotMqttError_t result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * 0, + * &callbackInfo, + * &reference ); * - * // Publish should have returned AWS_IOT_MQTT_STATUS_PENDING. Once a response + * // Publish should have returned IOT_MQTT_STATUS_PENDING. Once a response * // is received, operationComplete is executed with the actual status passed * // in pOperation. * @endcode */ -typedef struct AwsIotMqttCallbackInfo +typedef struct IotMqttCallbackInfo { void * param1; /**< @brief The first parameter to pass to the callback function. */ /** * @brief User-provided callback function signature. * - * @param[in] void * #AwsIotMqttCallbackInfo_t.param1 - * @param[in] AwsIotMqttCallbackParam_t * Details on the outcome of the MQTT operation + * @param[in] void * #IotMqttCallbackInfo_t.param1 + * @param[in] IotMqttCallbackParam_t * Details on the outcome of the MQTT operation * or an incoming MQTT PUBLISH. * - * @see #AwsIotMqttCallbackParam_t for more information on the second parameter. + * @see #IotMqttCallbackParam_t for more information on the second parameter. */ void ( * function )( void *, - AwsIotMqttCallbackParam_t * const ); -} AwsIotMqttCallbackInfo_t; + IotMqttCallbackParam_t * ); +} IotMqttCallbackInfo_t; /** * @ingroup mqtt_datatypes_paramstructs @@ -463,26 +498,34 @@ typedef struct AwsIotMqttCallbackInfo * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe * * An array of these is passed to @ref mqtt_function_subscribe and @ref - * mqtt_function_unsubscribe. However, #AwsIotMqttSubscription_t.callback and - * and #AwsIotMqttSubscription_t.QoS are ignored by @ref mqtt_function_unsubscribe. + * mqtt_function_unsubscribe. However, #IotMqttSubscription_t.callback and + * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribe. * - * @initializer{AwsIotMqttSubscription_t,AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER} + * @initializer{IotMqttSubscription_t,IOT_MQTT_SUBSCRIPTION_INITIALIZER} * * @note The lengths of the strings in this struct should not include the NULL * terminator. Strings in this struct do not need to be NULL-terminated. - * @see #AwsIotMqttCallbackInfo_t for details on setting a callback function. + * @see #IotMqttCallbackInfo_t for details on setting a callback function. */ -typedef struct AwsIotMqttSubscription +typedef struct IotMqttSubscription { - int QoS; /**< @brief QoS of messages delivered on subscription. - * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe */ + /** + * @brief QoS of messages delivered on subscription. + * + * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe. + */ + IotMqttQos_t qos; const char * pTopicFilter; /**< @brief Topic filter of subscription. */ - uint16_t topicFilterLength; /**< @brief Length of #AwsIotMqttSubscription_t.pTopicFilter. */ + uint16_t topicFilterLength; /**< @brief Length of #IotMqttSubscription_t.pTopicFilter. */ - AwsIotMqttCallbackInfo_t callback; /**< @brief Callback to invoke when a message is received. - * See #AwsIotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. */ -} AwsIotMqttSubscription_t; + /** + * @brief Callback to invoke when a message is received. + * + * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. + */ + IotMqttCallbackInfo_t callback; +} IotMqttSubscription_t; /** * @ingroup mqtt_datatypes_paramstructs @@ -494,25 +537,25 @@ typedef struct AwsIotMqttSubscription * correspond to the content of an [MQTT CONNECT packet.] * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) * - * @initializer{AwsIotMqttConnectInfo_t,AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER} + * @initializer{IotMqttConnectInfo_t,IOT_MQTT_CONNECT_INFO_INITIALIZER} * * @note The lengths of the strings in this struct should not include the NULL * terminator. Strings in this struct do not need to be NULL-terminated. */ -typedef struct AwsIotMqttConnectInfo +typedef struct IotMqttConnectInfo { /** * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. * * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] - * (https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt) + * (https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html) * When this member is `true`, the MQTT library will accommodate these * differences. This setting should be `false` when communicating with a * fully-compliant MQTT broker. * * @attention This setting MUST be `true` when using the AWS IoT MQTT * server; it MUST be `false` otherwise. - * @note Currently, @ref AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER sets this + * @note Currently, @ref IOT_MQTT_CONNECT_INFO_INITIALIZER sets this * this member to `true`. */ bool awsIotMqttMode; @@ -522,13 +565,13 @@ typedef struct AwsIotMqttConnectInfo * * MQTT servers can maintain and topic filter subscriptions and unacknowledged * PUBLISH messages. These form part of an MQTT session, which is identified by - * the [client identifier](@ref AwsIotMqttConnectInfo_t.pClientIdentifier). + * the [client identifier](@ref IotMqttConnectInfo_t.pClientIdentifier). * * Setting this value to `true` establishes a clean session, which causes * the MQTT server to discard any previous session data for a client identifier. * When the client disconnects, the server discards all session data. If this - * value is `true`, #AwsIotMqttConnectInfo_t.pPreviousSubscriptions and - * #AwsIotMqttConnectInfo_t.previousSubscriptionCount are ignored. + * value is `true`, #IotMqttConnectInfo_t.pPreviousSubscriptions and + * #IotMqttConnectInfo_t.previousSubscriptionCount are ignored. * * Setting this value to `false` does one of the following: * - If no previous session exists, the MQTT server will create a new @@ -542,9 +585,9 @@ typedef struct AwsIotMqttConnectInfo * When a client with a persistent session disconnects, the MQTT server * continues to maintain all subscriptions and unacknowledged PUBLISH messages. * The client must also remember the session subscriptions to restore them - * upon reconnecting. #AwsIotMqttConnectInfo_t.pPreviousSubscriptions - * and #AwsIotMqttConnectInfo_t.previousSubscriptionCount are used to - * restore a previous session's subscriptions client-side. + * upon reconnecting. #IotMqttConnectInfo_t.pPreviousSubscriptions and + * #IotMqttConnectInfo_t.previousSubscriptionCount are used to restore a + * previous session's subscriptions client-side. */ bool cleanSession; @@ -554,21 +597,21 @@ typedef struct AwsIotMqttConnectInfo * Pointer to the start of an array of subscriptions present a previous session, * if any. These subscriptions will be immediately restored upon reconnecting. * - * This member is ignored if it is `NULL` or #AwsIotMqttConnectInfo_t.cleanSession - * is `true`. If this member is not `NULL`, #AwsIotMqttConnectInfo_t.previousSubscriptionCount + * This member is ignored if it is `NULL` or #IotMqttConnectInfo_t.cleanSession + * is `true`. If this member is not `NULL`, #IotMqttConnectInfo_t.previousSubscriptionCount * must be nonzero. */ - const AwsIotMqttSubscription_t * pPreviousSubscriptions; + const IotMqttSubscription_t * pPreviousSubscriptions; /** * @brief The number of MQTT subscriptions present in a previous session, if any. * * Number of subscriptions contained in the array - * #AwsIotMqttConnectInfo_t.pPreviousSubscriptions. + * #IotMqttConnectInfo_t.pPreviousSubscriptions. * - * This value is ignored if #AwsIotMqttConnectInfo_t.pPreviousSubscriptions - * is `NULL` or #AwsIotMqttConnectInfo_t.cleanSession is `true`. If - * #AwsIotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value + * This value is ignored if #IotMqttConnectInfo_t.pPreviousSubscriptions + * is `NULL` or #IotMqttConnectInfo_t.cleanSession is `true`. If + * #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value * must be nonzero. */ size_t previousSubscriptionCount; @@ -578,28 +621,28 @@ typedef struct AwsIotMqttConnectInfo * * A Last Will and Testament (LWT) message may be published if this connection is * closed without sending an MQTT DISCONNECT packet. This pointer should be set to - * an #AwsIotMqttPublishInfo_t representing any LWT message to publish. If an LWT + * an #IotMqttPublishInfo_t representing any LWT message to publish. If an LWT * is not needed, this member must be set to `NULL`. * * Unlike other PUBLISH messages, an LWT message is limited to 65535 bytes in - * length. Additionally, [pWillInfo->retryMs](@ref AwsIotMqttPublishInfo_t.retryMs) - * and [pWillInfo->retryLimit](@ref AwsIotMqttPublishInfo_t.retryLimit) will + * length. Additionally, [pWillInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pWillInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) will * be ignored. */ - const AwsIotMqttPublishInfo_t * pWillInfo; + const IotMqttPublishInfo_t * pWillInfo; uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ const char * pClientIdentifier; /**< @brief MQTT client identifier. */ - uint16_t clientIdentifierLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pClientIdentifier. */ + uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ /* These credentials are not used by AWS IoT and may be ignored if * awsIotMqttMode is true. */ const char * pUserName; /**< @brief Username for MQTT connection. */ - uint16_t userNameLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pUserName. */ + uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ - uint16_t passwordLength; /**< @brief Length of #AwsIotMqttConnectInfo_t.pPassword. */ -} AwsIotMqttConnectInfo_t; + uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ +} IotMqttConnectInfo_t; /** * @ingroup mqtt_datatypes_paramstructs @@ -613,24 +656,24 @@ typedef struct AwsIotMqttConnectInfo * @ref mqtt_function_connect, the function @ref mqtt_function_receivecallback * should be called to process data received from the network. * - * @initializer{AwsIotMqttNetIf_t,AWS_IOT_MQTT_NETIF_INITIALIZER} + * @initializer{IotMqttNetIf_t,IOT_MQTT_NETIF_INITIALIZER} */ -typedef struct AwsIotMqttNetIf +typedef struct IotMqttNetIf { - void * pSendContext; /**< Passed as the first argument to #AwsIotMqttNetIf_t.send. */ - void * pDisconnectContext; /**< Passed as the first argument to #AwsIotMqttNetIf_t.disconnect. */ + void * pSendContext; /**< Passed as the first argument to #IotMqttNetIf_t.send. */ + void * pDisconnectContext; /**< Passed as the first argument to #IotMqttNetIf_t.disconnect. */ /** * @brief Function that sends data on the network. * - * @param[in] void * #AwsIotMqttNetIf_t.pSendContext - * @param[in] const void * const Pointer to the data to send. + * @param[in] void * #IotMqttNetIf_t.pSendContext + * @param[in] const void * Pointer to the data to send. * @param[in] size_t Size of the data to send. * * @return Number of bytes successfully sent, 0 on failure. */ size_t ( * send )( void *, - const uint8_t * const, + const uint8_t *, size_t ); /** @@ -639,61 +682,60 @@ typedef struct AwsIotMqttNetIf * If this function is not provided, the network connection will not be closed * by the MQTT library. * - * @param[in] int32_t Currently unused. - * @param[in] void * #AwsIotMqttNetIf_t.pDisconnectContext + * @param[in] void * #IotMqttNetIf_t.pDisconnectContext * * @note Optional; set to `NULL` to ignore. The MQTT spec states that connections * must be closed in certain conditions; if this function is not provided, the * MQTT library is noncompliant. */ - IotNetworkError_t ( * disconnect )( int32_t, void * ); + IotNetworkError_t ( * disconnect )( void * ); /* * In addition to providing the network send and disconnect functions, this * struct also allows the MQTT serialization and deserialization functions * to be overridden for an MQTT connection. The compile-time setting - * AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 1 to enable this - * functionality. See the AwsIotMqttNetIf_t.serialize and - * AwsIotMqttNetIf_t.deserialize members for a list of functions that can be + * IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 1 to enable this + * functionality. See the IotMqttNetIf_t.serialize and + * IotMqttNetIf_t.deserialize members for a list of functions that can be * overridden. In addition, the functions for freeing packets and determining * the packet type can also be overridden. If - * AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is 1, the serializer initialization + * IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is 1, the serializer initialization * and cleanup functions may be extended. See documentation of - * AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. + * IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. * * If any function pointers that are NULL (the default value set by - * AWS_IOT_MQTT_NETIF_INITIALIZER), then the default implementation of that + * IOT_MQTT_NETIF_INITIALIZER), then the default implementation of that * function will be used. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 struct { /** * @brief CONNECT packet serializer function. - * @param[in] AwsIotMqttConnectInfo_t* User-provided CONNECT information. + * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. * @param[out] uint8_t** Where the CONNECT packet is written. * @param[out] size_t* Size of the CONNECT packet. * - * Default implementation: #AwsIotMqttInternal_SerializeConnect + * Default implementation: #_IotMqtt_SerializeConnect */ - AwsIotMqttError_t ( * connect )( const AwsIotMqttConnectInfo_t * const /* pConnectInfo */, - uint8_t ** const /* pConnectPacket */, - size_t * const /* pPacketSize */ ); + IotMqttError_t ( * connect )( const IotMqttConnectInfo_t * /* pConnectInfo */, + uint8_t ** /* pConnectPacket */, + size_t * /* pPacketSize */ ); /** * @brief PUBLISH packet serializer function. - * @param[in] AwsIotMqttPublishInfo_t* User-provided PUBLISH information. + * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. * @param[out] uint8_t** Where the PUBLISH packet is written. * @param[out] size_t* Size of the PUBLISH packet. * @param[out] uint16_t* The packet identifier generated for this PUBLISH. * - * Default implementation: #AwsIotMqttInternal_SerializePublish + * Default implementation: #_IotMqtt_SerializePublish */ - AwsIotMqttError_t ( * publish )( const AwsIotMqttPublishInfo_t * const /* pPublishInfo */, - uint8_t ** const /* pPublishPacket */, - size_t * const /* pPacketSize */, - uint16_t * const /* pPacketIdentifier */ ); + IotMqttError_t ( * publish )( const IotMqttPublishInfo_t * /* pPublishInfo */, + uint8_t ** /* pPublishPacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); /** * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. @@ -702,11 +744,11 @@ typedef struct AwsIotMqttNetIf * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). * - * Default implementation: #AwsIotMqttInternal_PublishSetDup + * Default implementation: #_IotMqtt_PublishSetDup */ void ( * publishSetDup )( bool /* awsIotMqttMode */, - uint8_t * const /* pPublishPacket */, - uint16_t * const /* pNewPacketIdentifier */ ); + uint8_t * /* pPublishPacket */, + uint16_t * /* pNewPacketIdentifier */ ); /** * @brief PUBACK packet serializer function. @@ -714,63 +756,63 @@ typedef struct AwsIotMqttNetIf * @param[out] uint8_t** Where the PUBACK packet is written. * @param[out] size_t* Size of the PUBACK packet. * - * Default implementation: #AwsIotMqttInternal_SerializePuback + * Default implementation: #_IotMqtt_SerializePuback */ - AwsIotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, - uint8_t ** const /* pPubackPacket */, - size_t * const /* pPacketSize */ ); + IotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, + uint8_t ** /* pPubackPacket */, + size_t * /* pPacketSize */ ); /** * @brief SUBSCRIBE packet serializer function. - * @param[in] AwsIotMqttSubscription_t* User-provided array of subscriptions. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. * @param[in] size_t Number of elements in the subscription array. * @param[out] uint8_t** Where the SUBSCRIBE packet is written. * @param[out] size_t* Size of the SUBSCRIBE packet. * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. * - * Default implementation: #AwsIotMqttInternal_SerializeSubscribe + * Default implementation: #_IotMqtt_SerializeSubscribe */ - AwsIotMqttError_t ( * subscribe )( const AwsIotMqttSubscription_t * const /* pSubscriptionList */, - size_t /* subscriptionCount */, - uint8_t ** const /* pSubscribePacket */, - size_t * const /* pPacketSize */, - uint16_t * const /* pPacketIdentifier */ ); + IotMqttError_t ( * subscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** /* pSubscribePacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); /** * @brief UNSUBSCRIBE packet serializer function. - * @param[in] AwsIotMqttSubscription_t* User-provided array of subscriptions to remove. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions to remove. * @param[in] size_t Number of elements in the subscription array. * @param[out] uint8_t** Where the UNSUBSCRIBE packet is written. * @param[out] size_t* Size of the UNSUBSCRIBE packet. * @param[out] uint16_t* The packet identifier generated for this UNSUBSCRIBE. * - * Default implementation: #AwsIotMqttInternal_SerializeUnsubscribe + * Default implementation: #_IotMqtt_SerializeUnsubscribe */ - AwsIotMqttError_t ( * unsubscribe )( const AwsIotMqttSubscription_t * const /* pSubscriptionList */, - size_t /* subscriptionCount */, - uint8_t ** const /* pUnsubscribePacket */, - size_t * const /* pPacketSize */, - uint16_t * const /* pPacketIdentifier */ ); + IotMqttError_t ( * unsubscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** /* pUnsubscribePacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); /** * @brief PINGREQ packet serializer function. * @param[out] uint8_t** Where the PINGREQ packet is written. * @param[out] size_t* Size of the PINGREQ packet. * - * Default implementation: #AwsIotMqttInternal_SerializePingreq + * Default implementation: #_IotMqtt_SerializePingreq */ - AwsIotMqttError_t ( * pingreq )( uint8_t ** const /* pPingreqPacket */, - size_t * const /* pPacketSize */ ); + IotMqttError_t ( * pingreq )( uint8_t ** /* pPingreqPacket */, + size_t * /* pPacketSize */ ); /** * @brief DISCONNECT packet serializer function. * @param[out] uint8_t** Where the DISCONNECT packet is written. * @param[out] size_t* Size of the DISCONNECT packet. * - * Default implementation: #AwsIotMqttInternal_SerializeDisconnect + * Default implementation: #_IotMqtt_SerializeDisconnect */ - AwsIotMqttError_t ( * disconnect )( uint8_t ** const /* pDisconnectPacket */, - size_t * const /* pPacketSize */ ); + IotMqttError_t ( * disconnect )( uint8_t ** /* pDisconnectPacket */, + size_t * /* pPacketSize */ ); } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ struct @@ -782,28 +824,28 @@ typedef struct AwsIotMqttNetIf * @param[out] size_t* The number of bytes in the data stream processed * by this function. * - * Default implementation: #AwsIotMqttInternal_DeserializeConnack + * Default implementation: #_IotMqtt_DeserializeConnack */ - AwsIotMqttError_t ( * connack )( const uint8_t * const /* pConnackStart */, - size_t /* dataLength */, - size_t * const /* pBytesProcessed */ ); + IotMqttError_t ( * connack )( const uint8_t * /* pConnackStart */, + size_t /* dataLength */, + size_t * /* pBytesProcessed */ ); /** * @brief PUBLISH packet deserializer function. * @param[in] uint8_t* Pointer to the start of a PUBLISH packet. * @param[in] size_t Length of the data stream. - * @param[out] AwsIotMqttPublishInfo_t* Where the deserialized PUBLISH will be written. + * @param[out] IotMqttPublishInfo_t* Where the deserialized PUBLISH will be written. * @param[out] uint16_t* The packet identifier in the PUBLISH. * @param[out] size_t* The number of bytes in the data stream processed * by this function. * - * Default implementation: #AwsIotMqttInternal_DeserializePublish + * Default implementation: #_IotMqtt_DeserializePublish */ - AwsIotMqttError_t ( * publish )( const uint8_t * const /* pPublishStart */, - size_t /* dataLength */, - AwsIotMqttPublishInfo_t * const /* pOutput */, - uint16_t * const /* pPacketIdentifier */, - size_t * const /* pBytesProcessed */ ); + IotMqttError_t ( * publish )( const uint8_t * /* pPublishStart */, + size_t /* dataLength */, + IotMqttPublishInfo_t * /* pOutput */, + uint16_t * /* pPacketIdentifier */, + size_t * /* pBytesProcessed */ ); /** * @brief PUBACK packet deserializer function. @@ -813,16 +855,16 @@ typedef struct AwsIotMqttNetIf * @param[out] size_t* The number of bytes in the data stream processed * by this function. * - * Default implementation: #AwsIotMqttInternal_DeserializePuback + * Default implementation: #_IotMqtt_DeserializePuback */ - AwsIotMqttError_t ( * puback )( const uint8_t * const /* pPubackStart */, - size_t /* dataLength */, - uint16_t * const /* pPacketIdentifier */, - size_t * const /* pBytesProcessed */ ); + IotMqttError_t ( * puback )( const uint8_t * /* pPubackStart */, + size_t /* dataLength */, + uint16_t * /* pPacketIdentifier */, + size_t * /* pBytesProcessed */ ); /** * @brief SUBACK packet deserializer function. - * @param[in] AwsIotMqttConnection_t The MQTT connection associated with + * @param[in] IotMqttConnection_t The MQTT connection associated with * the subscription. Rejected topic filters should be removed from this * connection. * @param[in] uint8_t* Pointer to the start of a SUBACK packet. @@ -831,13 +873,13 @@ typedef struct AwsIotMqttNetIf * @param[out] size_t* The number of bytes in the data stream processed * by this function. * - * Default implementation: #AwsIotMqttInternal_DeserializeSuback + * Default implementation: #_IotMqtt_DeserializeSuback */ - AwsIotMqttError_t ( * suback )( AwsIotMqttConnection_t /* mqttConnection */, - const uint8_t * const /* pSubackStart */, - size_t /* dataLength */, - uint16_t * const /* pPacketIdentifier */, - size_t * const /* pBytesProcessed */ ); + IotMqttError_t ( * suback )( IotMqttConnection_t /* mqttConnection */, + const uint8_t * /* pSubackStart */, + size_t /* dataLength */, + uint16_t * /* pPacketIdentifier */, + size_t * /* pBytesProcessed */ ); /** * @brief UNSUBACK packet deserializer function. @@ -847,12 +889,12 @@ typedef struct AwsIotMqttNetIf * @param[out] size_t* The number of bytes in the data stream processed * by this function. * - * Default implementation: #AwsIotMqttInternal_DeserializeUnsuback + * Default implementation: #_IotMqtt_DeserializeUnsuback */ - AwsIotMqttError_t ( * unsuback )( const uint8_t * const /* pUnsubackStart */, - size_t /* dataLength */, - uint16_t * const /* pPacketIdentifier */, - size_t * const /* pBytesProcessed */ ); + IotMqttError_t ( * unsuback )( const uint8_t * /* pUnsubackStart */, + size_t /* dataLength */, + uint16_t * /* pPacketIdentifier */, + size_t * /* pBytesProcessed */ ); /** * @brief PINGRESP packet deserializer function. @@ -861,11 +903,11 @@ typedef struct AwsIotMqttNetIf * @param[out] size_t* The number of bytes in the data stream processed * by this function. * - * Default implementation: #AwsIotMqttInternal_DeserializePingresp + * Default implementation: #_IotMqtt_DeserializePingresp */ - AwsIotMqttError_t ( * pingresp )( const uint8_t * const /* pPingrespStart */, - size_t /* dataLength */, - size_t * const /* pBytesProcessed */ ); + IotMqttError_t ( * pingresp )( const uint8_t * /* pPingrespStart */, + size_t /* dataLength */, + size_t * /* pBytesProcessed */ ); } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ /** @@ -874,9 +916,9 @@ typedef struct AwsIotMqttNetIf * @param[in] uint8_t* pPacket Pointer to the beginning of the byte stream. * @param[in] size_t Size of the byte stream. * - * Default implementation: #AwsIotMqttInternal_GetPacketType + * Default implementation: #_IotMqtt_GetPacketType */ - uint8_t ( * getPacketType )( const uint8_t * const /* pPacket */, + uint8_t ( * getPacketType )( const uint8_t * /* pPacket */, size_t /* packetSize */ ); /** @@ -885,11 +927,11 @@ typedef struct AwsIotMqttNetIf * This function pointer must be set if any other serializer override is set. * @param[in] uint8_t* The packet to free. * - * Default implementation: #AwsIotMqttInternal_FreePacket + * Default implementation: #_IotMqtt_FreePacket */ void ( * freePacket )( uint8_t * /* pPacket */ ); - #endif /* if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -} AwsIotMqttNetIf_t; + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +} IotMqttNetIf_t; /*------------------------- MQTT defined constants --------------------------*/ @@ -911,19 +953,19 @@ typedef struct AwsIotMqttNetIf * * Example * @code{c} - * AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - * AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - * AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - * AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - * AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; - * AwsIotMqttConnection_t connection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - * AwsIotMqttReference_t reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * IotMqttConnection_t connection = IOT_MQTT_CONNECTION_INITIALIZER; + * IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; * @endcode * * @section mqtt_constants_flags MQTT Function Flags * @brief Flags that modify the behavior of MQTT library functions. - * - #AWS_IOT_MQTT_FLAG_WAITABLE
- * @copybrief AWS_IOT_MQTT_FLAG_WAITABLE + * - #IOT_MQTT_FLAG_WAITABLE
+ * @copybrief IOT_MQTT_FLAG_WAITABLE * * Flags should be bitwise-ORed with each other to change the behavior of * @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, or @@ -935,15 +977,21 @@ typedef struct AwsIotMqttNetIf */ /* @[define_mqtt_initializers] */ -#define AWS_IOT_MQTT_NETIF_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttNetIf_t. */ -/** @brief Initializer for #AwsIotMqttConnectInfo_t. */ -#define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { .awsIotMqttMode = true, \ - .cleanSession = true } -#define AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttPublishInfo_t. */ -#define AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttSubscription_t. */ -#define AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotMqttCallbackInfo_t. */ -#define AWS_IOT_MQTT_CONNECTION_INITIALIZER NULL /**< @brief Initializer for #AwsIotMqttConnection_t. */ -#define AWS_IOT_MQTT_REFERENCE_INITIALIZER NULL /**< @brief Initializer for #AwsIotMqttReference_t. */ +/** @brief Initializer for #IotMqttNetIf_t. */ +#define IOT_MQTT_NETIF_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttConnectInfo_t. */ +#define IOT_MQTT_CONNECT_INFO_INITIALIZER { .awsIotMqttMode = true, \ + .cleanSession = true } +/** @brief Initializer for #IotMqttPublishInfo_t. */ +#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttSubscription_t. */ +#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttCallbackInfo_t. */ +#define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttConnection_t. */ +#define IOT_MQTT_CONNECTION_INITIALIZER NULL +/** @brief Initializer for #IotMqttReference_t. */ +#define IOT_MQTT_REFERENCE_INITIALIZER NULL /* @[define_mqtt_initializers] */ /** @@ -951,15 +999,15 @@ typedef struct AwsIotMqttNetIf * * This flag is always valid for @ref mqtt_function_subscribe and * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, - * the parameter [pPublishInfo->QoS](@ref AwsIotMqttPublishInfo_t.QoS) must not be `0`. + * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. * - * An #AwsIotMqttReference_t MUST be provided if this flag is set. Additionally, an - * #AwsIotMqttCallbackInfo_t MUST NOT be provided. + * An #IotMqttReference_t MUST be provided if this flag is set. Additionally, an + * #IotMqttCallbackInfo_t MUST NOT be provided. * * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up * resources. */ -#define AWS_IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) +#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) /*------------------------- MQTT library functions --------------------------*/ @@ -983,9 +1031,9 @@ typedef struct AwsIotMqttNetIf */ /** - * @functionpage{AwsIotMqtt_Init,mqtt,init} - * @functionpage{AwsIotMqtt_Cleanup,mqtt,cleanup} - * @functionpage{AwsIotMqtt_ReceiveCallback,mqtt,receivecallback} + * @functionpage{IotMqtt_Init,mqtt,init} + * @functionpage{IotMqtt_Cleanup,mqtt,cleanup} + * @functionpage{IotMqtt_ReceiveCallback,mqtt,receivecallback} * * @anchor mqtt_function_receivecallback_nopartial * Sequence Diagram: Processing a network buffer without partial packets @@ -995,38 +1043,38 @@ typedef struct AwsIotMqttNetIf * Sequence Diagram: Processing a network buffer with partial packets * @image html mqtt_function_receivecallback_partial.png width=80% * - * @functionpage{AwsIotMqtt_Connect,mqtt,connect} - * @functionpage{AwsIotMqtt_Disconnect,mqtt,disconnect} - * @functionpage{AwsIotMqtt_Subscribe,mqtt,subscribe} - * @functionpage{AwsIotMqtt_TimedSubscribe,mqtt,timedsubscribe} - * @functionpage{AwsIotMqtt_Unsubscribe,mqtt,unsubscribe} - * @functionpage{AwsIotMqtt_TimedUnsubscribe,mqtt,timedunsubscribe} - * @functionpage{AwsIotMqtt_Publish,mqtt,publish} - * @functionpage{AwsIotMqtt_TimedPublish,mqtt,timedpublish} - * @functionpage{AwsIotMqtt_Wait,mqtt,wait} - * @functionpage{AwsIotMqtt_strerror,mqtt,strerror} - * @functionpage{AwsIotMqtt_OperationType,mqtt,operationtype} - * @functionpage{AwsIotMqtt_IsSubscribed,mqtt,issubscribed} + * @functionpage{IotMqtt_Connect,mqtt,connect} + * @functionpage{IotMqtt_Disconnect,mqtt,disconnect} + * @functionpage{IotMqtt_Subscribe,mqtt,subscribe} + * @functionpage{IotMqtt_TimedSubscribe,mqtt,timedsubscribe} + * @functionpage{IotMqtt_Unsubscribe,mqtt,unsubscribe} + * @functionpage{IotMqtt_TimedUnsubscribe,mqtt,timedunsubscribe} + * @functionpage{IotMqtt_Publish,mqtt,publish} + * @functionpage{IotMqtt_TimedPublish,mqtt,timedpublish} + * @functionpage{IotMqtt_Wait,mqtt,wait} + * @functionpage{IotMqtt_strerror,mqtt,strerror} + * @functionpage{IotMqtt_OperationType,mqtt,operationtype} + * @functionpage{IotMqtt_IsSubscribed,mqtt,issubscribed} */ /** * @brief One-time initialization function for the MQTT library. * - * This function performs internal setup of the MQTT library. It must be called + * This function performs setup of the MQTT library. It must be called * once (and only once) before calling any other MQTT function. Calling this * function more than once without first calling @ref mqtt_function_cleanup * may result in a crash. * * @return One of the following: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_INIT_FAILED * * @warning No thread-safety guarantees are provided for this function. * * @see @ref mqtt_function_cleanup */ /* @[declare_mqtt_init] */ -AwsIotMqttError_t AwsIotMqtt_Init( void ); +IotMqttError_t IotMqtt_Init( void ); /* @[declare_mqtt_init] */ /** @@ -1043,7 +1091,7 @@ AwsIotMqttError_t AwsIotMqtt_Init( void ); * @see @ref mqtt_function_init */ /* @[declare_mqtt_cleanup] */ -void AwsIotMqtt_Cleanup( void ); +void IotMqtt_Cleanup( void ); /* @[declare_mqtt_cleanup] */ /** @@ -1051,7 +1099,7 @@ void AwsIotMqtt_Cleanup( void ); * * This function should be called by the system whenever a stream of MQTT data * is received from the network. It processes the data stream and decodes any - * MQTT packets it finds. The MQTT library uses #AwsIotMqttNetIf_t for sending + * MQTT packets it finds. The MQTT library uses #IotMqttNetIf_t for sending * data and closing network connections. * * @attention Remember that this function's input `pReceivedData` is a data @@ -1105,7 +1153,7 @@ void AwsIotMqtt_Cleanup( void ); * @return * - `-1` if a protocol violation is encountered. If this function returns `-1`, then * the network connection is closed (unless a [disconnect function] - * (@ref AwsIotMqttNetIf_t.disconnect) was not provided to @ref mqtt_function_connect). + * (@ref IotMqttNetIf_t.disconnect) was not provided to @ref mqtt_function_connect). * `freeReceivedData` is not called if this function returns `-1`. * - Number of bytes processed otherwise. If the return value is less than `dataLength` * (but not `-1`), the data stream probably contained a partial MQTT packet. The function @@ -1113,12 +1161,12 @@ void AwsIotMqtt_Cleanup( void ); * to `pReceivedData+dataLength` were successfully processed. */ /* @[declare_mqtt_receivecallback] */ -int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, - void * pConnection, - const uint8_t * pReceivedData, - size_t dataLength, - size_t offset, - void ( * freeReceivedData )( void * ) ); +int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, + void * pConnection, + const uint8_t * pReceivedData, + size_t dataLength, + size_t offset, + void ( * freeReceivedData )( void * ) ); /* @[declare_mqtt_receivecallback] */ /** @@ -1131,14 +1179,14 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * the MQTT CONNECT packet. After @ref mqtt_function_init, this function must be * called before any other MQTT library function. * - * If [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) is `true`, + * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `true`, * this function establishes a clean MQTT session. Subscriptions and unacknowledged * PUBLISH messages will be discarded when the connection is closed. * - * If [pConnectInfo->cleanSession](@ref AwsIotMqttConnectInfo_t.cleanSession) is `false`, + * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `false`, * this function establishes (or re-establishes) a persistent MQTT session. The parameters - * [pConnectInfo->pPreviousSubscriptions](@ref AwsIotMqttConnectInfo_t.pPreviousSubscriptions) - * and [pConnectInfo->previousSubscriptionCount](@ref AwsIotMqttConnectInfo_t.previousSubscriptionCount) + * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions) + * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) * may be used to restore subscriptions present in a re-established persistent session. * Any restored subscriptions MUST have been present in the persistent session; * this function does not send an MQTT SUBSCRIBE packet! @@ -1147,20 +1195,20 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * underlying network protocol carrying the MQTT packets. It interacts with the * network through a network abstraction layer, allowing it to be used with many * different network stacks. The network abstraction layer is established - * per-connection, allowing every #AwsIotMqttConnection_t to use a different network + * per-connection, allowing every #IotMqttConnection_t to use a different network * stack. The parameter `pNetworkInterface` sets up the network abstraction layer - * for an MQTT connection; see the documentation on #AwsIotMqttNetIf_t for details + * for an MQTT connection; see the documentation on #IotMqttNetIf_t for details * on its members. * * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet. - * Most members [are defined by the MQTT spec.](@ref AwsIotMqttConnectInfo_t). The - * [pConnectInfo->pWillInfo](@ref AwsIotMqttConnectInfo_t.pWillInfo) member provides + * Most members [are defined by the MQTT spec.](@ref IotMqttConnectInfo_t). The + * [pConnectInfo->pWillInfo](@ref IotMqttConnectInfo_t.pWillInfo) member provides * information on a Last Will and Testament (LWT) message to be published if the * MQTT connection is closed without [sending a DISCONNECT packet] * (@ref mqtt_function_disconnect). Unlike other PUBLISH * messages, a LWT message payload is limited to 65535 bytes in length. Additionally, - * the retry [interval](@ref AwsIotMqttPublishInfo_t.retryMs) and [limit] - * (@ref AwsIotMqttPublishInfo_t.retryLimit) members of #AwsIotMqttPublishInfo_t + * the retry [interval](@ref IotMqttPublishInfo_t.retryMs) and [limit] + * (@ref IotMqttPublishInfo_t.retryLimit) members of #IotMqttPublishInfo_t * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL. * * Unlike @ref mqtt_function_publish, @ref mqtt_function_subscribe, and @@ -1177,33 +1225,34 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * this MQTT connection will use. * @param[in] pConnectInfo MQTT connection setup parameters. * @param[in] timeoutMs If the MQTT server does not accept the connection within - * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * this timeout, this function returns #IOT_MQTT_TIMEOUT. * * @return One of the following: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE - * - #AWS_IOT_MQTT_TIMEOUT - * - #AWS_IOT_MQTT_SERVER_REFUSED + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_TIMEOUT + * - #IOT_MQTT_SERVER_REFUSED * * Example * @code{c} * // An initialized and connected network connection. - * AwsIotNetworkConnection_t pNetworkConnection; + * IotNetworkConnection_t pNetworkConnection; * * // Parameters to MQTT connect. - * AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - * AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - * AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - * AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + * IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; * - * // Example using the OpenSSL network implementation. + * // Example using a generic network implementation. * networkInterface.pSendContext = pNetworkConnection; * networkInterface.pDisconnectContext = pNetworkConnection; - * networkInterface.send = AwsIotNetwork_Send; - * networkInterface.disconnect = AwsIotNetwork_CloseConnection; + * networkInterface.send = IotNetwork_Send; + * networkInterface.disconnect = IotNetwork_CloseConnection; * * // Set the members of the connection info (password and username not used). * connectInfo.cleanSession = true; @@ -1212,7 +1261,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * connectInfo.clientIdentifierLength = 22; * * // Set the members of the will info (retain and retry not used). - * willInfo.QoS = 1; + * willInfo.qos = IOT_MQTT_QOS_1; * willInfo.pTopicName = "will/topic/name"; * willInfo.topicNameLength = 15; * willInfo.pPayload = "MQTT client unexpectedly disconnected."; @@ -1222,26 +1271,26 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * connectInfo.pWillInfo = &willInfo; * * // Call CONNECT with a 5 second block time. Should return - * // AWS_IOT_MQTT_SUCCESS when successful. - * AwsIotMqttError_t result = AwsIotMqtt_Connect( &mqttConnection, - * &networkInterface, - * &connectInfo, - * 5000 ); + * // IOT_MQTT_SUCCESS when successful. + * IotMqttError_t result = IotMqtt_Connect( &mqttConnection, + * &networkInterface, + * &connectInfo, + * 5000 ); * - * if( result == AWS_IOT_MQTT_SUCCESS ) + * if( result == IOT_MQTT_SUCCESS ) * { * // Do something with the MQTT connection... * * // Clean up and close the MQTT connection once it's no longer needed. - * AwsIotMqtt_Disconnect( mqttConnection, false ); + * IotMqtt_Disconnect( mqttConnection, false ); * } * @endcode */ /* @[declare_mqtt_connect] */ -AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface, - const AwsIotMqttConnectInfo_t * const pConnectInfo, - uint64_t timeoutMs ); +IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, + const IotMqttNetIf_t * pNetworkInterface, + const IotMqttConnectInfo_t * pConnectInfo, + uint64_t timeoutMs ); /* @[declare_mqtt_connect] */ /** @@ -1253,7 +1302,7 @@ AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, * * Normally, `cleanupOnly` should be `false`. This gracefully shuts down an MQTT * connection by sending an MQTT DISCONNECT packet. Any [disconnect function] - * (@ref AwsIotMqttNetIf_t.disconnect) provided [when the connection was established] + * (@ref IotMqttNetIf_t.disconnect) provided [when the connection was established] * (@ref mqtt_function_connect) will also be called. Note that because the MQTT server * will not acknowledge a DISCONNECT packet, the client has no way of knowing if * the server received the DISCONNECT packet. In the case where the DISCONNECT @@ -1263,7 +1312,7 @@ AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, * * Should the underlying network connection become unusable, this function should * be called with `cleanupOnly` set to `true`. In this case, no DISCONNECT packet - * nor [disconnect function](@ref AwsIotMqttNetIf_t.disconnect) will be called. + * nor [disconnect function](@ref IotMqttNetIf_t.disconnect) will be called. * This function will only free the resources used by the MQTT connection; it still * must be called even if the network is offline to avoid leaking resources. * @@ -1277,8 +1326,8 @@ AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, * it should be `false`. */ /* @[declare_mqtt_disconnect] */ -void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, - bool cleanupOnly ); +void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, + bool cleanupOnly ); /* @[declare_mqtt_disconnect] */ /** @@ -1289,18 +1338,18 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, * packet notifies the server to send any matching PUBLISH messages to this client. * A single SUBSCRIBE packet may carry more than one topic filter, hence the * parameters to this function include an array of [subscriptions] - * (@ref AwsIotMqttSubscription_t). + * (@ref IotMqttSubscription_t). * * An MQTT subscription has two pieces: * 1. The subscription topic filter registered with the MQTT server. The MQTT * SUBSCRIBE packet sent from this client to server notifies the server to send * messages matching the given topic filters to this client. - * 2. The [callback function](@ref AwsIotMqttCallbackInfo_t.function) that this + * 2. The [callback function](@ref IotMqttCallbackInfo_t.function) that this * client will invoke when an incoming message is received. The callback function * notifies applications of an incoming PUBLISH message. * * The helper function @ref mqtt_function_issubscribed can be used to check if a - * [callback function](@ref AwsIotMqttCallbackInfo_t.function) is registered for + * [callback function](@ref IotMqttCallbackInfo_t.function) is registered for * a particular topic filter. * * To modify an already-registered subscription callback, call this function with @@ -1320,17 +1369,18 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, * referenced after this function returns. This reference is invalidated once * the subscription operation completes. * - * @return This function will return #AWS_IOT_MQTT_STATUS_PENDING upon success. + * @return This function will return #IOT_MQTT_STATUS_PENDING upon success. * @return Upon completion of the subscription (either through an - * #AwsIotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE - * - #AWS_IOT_MQTT_SERVER_REFUSED + * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_SERVER_REFUSED * @return If this function fails before queuing a subscribe operation, it will return * one of: - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY * * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. * @see @ref mqtt_function_unsubscribe for the function that removes subscriptions. @@ -1340,67 +1390,67 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, * #define NUMBER_OF_SUBSCRIPTIONS ... * * // Subscription callback function. - * void subscriptionCallback( void * pArgument, AwsIotMqttCallbackParam_t * const pPublish ); + * void subscriptionCallback( void * pArgument, IotMqttCallbackParam_t * pPublish ); * * // An initialized and connected MQTT connection. - * AwsIotMqttConnection_t mqttConnection; + * IotMqttConnection_t mqttConnection; * * // Subscription information. - * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - * AwsIotMqttReference_t lastOperation = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + * IotMqttReference_t lastOperation = IOT_MQTT_REFERENCE_INITIALIZER; * * // Set the subscription information. * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) * { - * pSubscriptions[ i ].QoS = 1; + * pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; * pSubscriptions[ i ].pTopicFilter = "some/topic/filter"; * pSubscriptions[ i ].topicLength = ( uint16_t ) strlen( pSubscriptions[ i ].pTopicFilter ); * pSubscriptions[ i ].callback.function = subscriptionCallback; * } * - * AwsIotMqttError_t result = AwsIotMqtt_Subscribe( mqttConnection, - * pSubscriptions, - * NUMBER_OF_SUBSCRIPTIONS, - * AWS_IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &lastOperation ); + * IotMqttError_t result = IotMqtt_Subscribe( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); * - * // Subscribe returns AWS_IOT_MQTT_STATUS_PENDING when successful. Wait up to + * // Subscribe returns IOT_MQTT_STATUS_PENDING when successful. Wait up to * // 5 seconds for the operation to complete. - * if( result == AWS_IOT_MQTT_STATUS_PENDING ) + * if( result == IOT_MQTT_STATUS_PENDING ) * { - * result = AwsIotMqtt_Wait( subscriptionRef, 5000 ); + * result = IotMqtt_Wait( subscriptionRef, 5000 ); * } * * // Check that the subscriptions were successful. - * if( result == AWS_IOT_MQTT_SUCCESS ) + * if( result == IOT_MQTT_SUCCESS ) * { * // Wait for messages on the subscription topic filters... * * // Unsubscribe once the subscriptions are no longer needed. - * result = AwsIotMqtt_Unsubscribe( mqttConnection, - * pSubscriptions, - * NUMBER_OF_SUBSCRIPTIONS, - * AWS_IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &lastOperation ); - * - * // UNSUBSCRIBE returns AWS_IOT_MQTT_STATUS_PENDING when successful. + * result = IotMqtt_Unsubscribe( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); + * + * // UNSUBSCRIBE returns IOT_MQTT_STATUS_PENDING when successful. * // Wait up to 5 seconds for the operation to complete. - * if( result == AWS_IOT_MQTT_STATUS_PENDING ) + * if( result == IOT_MQTT_STATUS_PENDING ) * { - * result = AwsIotMqtt_Wait( lastOperation, 5000 ); + * result = IotMqtt_Wait( lastOperation, 5000 ); * } * } * // Check which subscriptions were rejected by the server. - * else if( result == AWS_IOT_MQTT_SERVER_REFUSED ) + * else if( result == IOT_MQTT_SERVER_REFUSED ) * { * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) * { - * if( AwsIotMqtt_IsSubscribed( mqttConnection, - * pSubscriptions[ i ].pTopicFilter, - * pSubscriptions[ i ].topicFilterLength, - * NULL ) == false ) + * if( IotMqtt_IsSubscribed( mqttConnection, + * pSubscriptions[ i ].pTopicFilter, + * pSubscriptions[ i ].topicFilterLength, + * NULL ) == false ) * { * // This subscription was rejected. * } @@ -1409,12 +1459,12 @@ void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, * @endcode */ /* @[declare_mqtt_subscribe] */ -AwsIotMqttError_t AwsIotMqtt_Subscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pSubscribeRef ); +IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pSubscribeRef ); /* @[declare_mqtt_subscribe] */ /** @@ -1436,23 +1486,24 @@ AwsIotMqttError_t AwsIotMqtt_Subscribe( AwsIotMqttConnection_t mqttConnection, * Currently, flags are ignored by this function; this parameter is for * future-compatibility. * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within - * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * this timeout, this function returns #IOT_MQTT_TIMEOUT. * * @return One of the following: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE - * - #AWS_IOT_MQTT_TIMEOUT - * - #AWS_IOT_MQTT_SERVER_REFUSED + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_TIMEOUT + * - #IOT_MQTT_SERVER_REFUSED */ /* @[declare_mqtt_timedsubscribe] */ -AwsIotMqttError_t AwsIotMqtt_TimedSubscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint64_t timeoutMs ); +IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ); /* @[declare_mqtt_timedsubscribe] */ /** @@ -1463,7 +1514,7 @@ AwsIotMqttError_t AwsIotMqtt_TimedSubscribe( AwsIotMqttConnection_t mqttConnecti * packet removes registered topic filters from the server. After unsubscribing, * the server will no longer send messages on these topic filters to the client. * - * Corresponding [subscription callback functions](@ref AwsIotMqttCallbackInfo_t.function) + * Corresponding [subscription callback functions](@ref IotMqttCallbackInfo_t.function) * are also removed from the MQTT connection. These subscription callback functions * will be removed even if the MQTT UNSUBSCRIBE packet fails to send. * @@ -1477,27 +1528,28 @@ AwsIotMqttError_t AwsIotMqtt_TimedSubscribe( AwsIotMqttConnection_t mqttConnecti * referenced after this function returns. This reference is invalidated once * the unsubscribe operation completes. * - * @return This function will return #AWS_IOT_MQTT_STATUS_PENDING upon success. + * @return This function will return #IOT_MQTT_STATUS_PENDING upon success. * @return Upon completion of the unsubscribe (either through an - * #AwsIotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE + * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE * @return If this function fails before queuing an unsubscribe operation, it will return * one of: - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY * * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. * @see @ref mqtt_function_subscribe for the function that adds subscriptions. */ /* @[declare_mqtt_unsubscribe] */ -AwsIotMqttError_t AwsIotMqtt_Unsubscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pUnsubscribeRef ); +IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pUnsubscribeRef ); /* @[declare_mqtt_unsubscribe] */ /** @@ -1517,21 +1569,22 @@ AwsIotMqttError_t AwsIotMqtt_Unsubscribe( AwsIotMqttConnection_t mqttConnection, * Currently, flags are ignored by this function; this parameter is for * future-compatibility. * @param[in] timeoutMs If the MQTT server does not acknowledge the UNSUBSCRIBE within - * this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. + * this timeout, this function returns #IOT_MQTT_TIMEOUT. * * @return One of the following: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE */ /* @[declare_mqtt_timedunsubscribe] */ -AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint64_t timeoutMs ); +IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ); /* @[declare_mqtt_timedunsubscribe] */ /** @@ -1544,7 +1597,7 @@ AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnec * PUBLISH packet from the server. * * If a PUBLISH packet fails to reach the server and it is not a QoS 0 message, - * it will be retransmitted. See #AwsIotMqttPublishInfo_t for a description + * it will be retransmitted. See #IotMqttPublishInfo_t for a description * of the retransmission strategy. * * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid @@ -1558,20 +1611,21 @@ AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnec * referenced after this function returns. This reference is invalidated once * the publish operation completes. * - * @return This function will return #AWS_IOT_MQTT_STATUS_PENDING upon success for - * QoS 1 publishes. For a QoS 0 publish it returns #AWS_IOT_MQTT_SUCCESS upon + * @return This function will return #IOT_MQTT_STATUS_PENDING upon success for + * QoS 1 publishes. For a QoS 0 publish it returns #IOT_MQTT_SUCCESS upon * success. * @return Upon completion of a QoS 1 publish (either through an - * #AwsIotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE - * - #AWS_IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref AwsIotMqttPublishInfo_t.retryMs) - * and [pPublishInfo->retryLimit](@ref AwsIotMqttPublishInfo_t.retryLimit) were set). + * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). * @return If this function fails before queuing an publish operation (regardless * of QoS), it will return one of: - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY * * @note The parameters `pCallbackInfo` and `pPublishRef` should only be used for QoS * 1 publishes. For QoS 0, they should both be `NULL`. @@ -1581,51 +1635,51 @@ AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnec * Example * @code{c} * // An initialized and connected MQTT connection. - * AwsIotMqttConnection_t mqttConnection; + * IotMqttConnection_t mqttConnection; * * // Publish information. - * AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; * * // Set the publish information. QoS 0 example (retain not used): - * publishInfo.QoS = 0; + * publishInfo.qos = IOT_MQTT_QOS_0; * publishInfo.pTopicName = "some/topic/name"; * publishInfo.topicNameLength = 15; * publishInfo.pPayload = "payload"; * publishInfo.payloadLength = 8; * - * // QoS 0 publish should return AWS_IOT_MQTT_SUCCESS upon success. - * AwsIotMqttError_t qos0Result = AwsIotMqtt_Publish( mqttConnection, - * &publishInfo, - * 0, - * NULL, - * NULL ); + * // QoS 0 publish should return IOT_MQTT_SUCCESS upon success. + * IotMqttError_t qos0Result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * 0, + * NULL, + * NULL ); * * // QoS 1 with retry example (using same topic name and payload as QoS 0 example): - * AwsIotMqttReference_t qos1Reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - * publishInfo.QoS = 1; + * IotMqttReference_t qos1Reference = IOT_MQTT_REFERENCE_INITIALIZER; + * publishInfo.qos = IOT_MQTT_QOS_1; * publishInfo.retryMs = 1000; // Retry if no response is received in 1 second. * publishInfo.retryLimit = 5; // Retry up to 5 times. * - * // QoS 1 publish should return AWS_IOT_MQTT_STATUS_PENDING upon success. - * AwsIotMqttError_t qos1Result = AwsIotMqtt_Publish( mqttConnection, - * &publishInfo, - * AWS_IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &qos1Reference ); + * // QoS 1 publish should return IOT_MQTT_STATUS_PENDING upon success. + * IotMqttError_t qos1Result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &qos1Reference ); * * // Wait up to 5 seconds for the publish to complete. - * if( qos1Result == AWS_IOT_MQTT_STATUS_PENDING ) + * if( qos1Result == IOT_MQTT_STATUS_PENDING ) * { - * qos1Result = AwsIotMqtt_Wait( qos1Reference, 5000 ); + * qos1Result = IotMqtt_Wait( qos1Reference, 5000 ); * } * @endcode */ /* @[declare_mqtt_publish] */ -AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pPublishRef ); +IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pPublishRef ); /* @[declare_mqtt_publish] */ /** @@ -1645,23 +1699,24 @@ AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, * Currently, flags are ignored by this function; this parameter is for * future-compatibility. * @param[in] timeoutMs If the MQTT server does not acknowledge a QoS 1 PUBLISH - * within this timeout, this function returns #AWS_IOT_MQTT_TIMEOUT. This parameter + * within this timeout, this function returns #IOT_MQTT_TIMEOUT. This parameter * is ignored for QoS 0 PUBLISH messages. * * @return One of the following: - * - #AWS_IOT_MQTT_SUCCESS - * - #AWS_IOT_MQTT_BAD_PARAMETER - * - #AWS_IOT_MQTT_NO_MEMORY - * - #AWS_IOT_MQTT_SEND_ERROR - * - #AWS_IOT_MQTT_BAD_RESPONSE - * - #AWS_IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref AwsIotMqttPublishInfo_t.retryMs) - * and [pPublishInfo->retryLimit](@ref AwsIotMqttPublishInfo_t.retryLimit) were set). + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). */ /* @[declare_mqtt_timedpublish] */ -AwsIotMqttError_t AwsIotMqtt_TimedPublish( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint32_t flags, - uint64_t timeoutMs ); +IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + uint64_t timeoutMs ); /* @[declare_mqtt_timedpublish] */ /** @@ -1673,55 +1728,55 @@ AwsIotMqttError_t AwsIotMqtt_TimedPublish( AwsIotMqttConnection_t mqttConnection * asynchronous; the function calls queue an operation for processing, and a * callback is invoked once the operation is complete. * - * To use this function, the flag #AWS_IOT_MQTT_FLAG_WAITABLE must have been + * To use this function, the flag #IOT_MQTT_FLAG_WAITABLE must have been * set in the operation's function call. Additionally, this function must always * be called with any waitable operation to clean up resources. * * Regardless of its return value, this function always clean up resources used * by the waitable operation. This means `reference` is invalidated as soon as - * this function returns, even if it returns #AWS_IOT_MQTT_TIMEOUT or another error. + * this function returns, even if it returns #IOT_MQTT_TIMEOUT or another error. * * @param[in] reference Reference to the operation to wait for. The flag - * #AWS_IOT_MQTT_FLAG_WAITABLE must have been set for this operation. - * @param[in] timeoutMs How long to wait before returning #AWS_IOT_MQTT_TIMEOUT. + * #IOT_MQTT_FLAG_WAITABLE must have been set for this operation. + * @param[in] timeoutMs How long to wait before returning #IOT_MQTT_TIMEOUT. * * @return The return value of this function depends on the MQTT operation associated - * with `reference`. See #AwsIotMqttError_t for possible return values. + * with `reference`. See #IotMqttError_t for possible return values. * * Example * @code{c} * // Reference and timeout. - * AwsIotMqttReference_t reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + * IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; * uint64_t timeoutMs = 5000; // 5 seconds * * // MQTT operation to wait for. - * AwsIotMqttError_t result = AwsIotMqtt_Publish( mqttConnection, - * &publishInfo, - * AWS_IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &reference ); + * IotMqttError_t result = IotMqtt_Publish( mqttConnection, + * &publishInfo, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &reference ); * - * // Publish should have returned AWS_IOT_MQTT_STATUS_PENDING. The call to wait + * // Publish should have returned IOT_MQTT_STATUS_PENDING. The call to wait * // returns once the result of the publish is available or the timeout expires. - * if( result == AWS_IOT_MQTT_STATUS_PENDING ) + * if( result == IOT_MQTT_STATUS_PENDING ) * { - * result = AwsIotMqtt_Wait( reference, timeoutMs ); + * result = IotMqtt_Wait( reference, timeoutMs ); * * // After the call to wait, the result of the publish is known - * // (not AWS_IOT_MQTT_STATUS_PENDING). - * assert( result != AWS_IOT_MQTT_STATUS_PENDING ); + * // (not IOT_MQTT_STATUS_PENDING). + * assert( result != IOT_MQTT_STATUS_PENDING ); * } * @endcode */ /* @[declare_mqtt_wait] */ -AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, - uint64_t timeoutMs ); +IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, + uint64_t timeoutMs ); /* @[declare_mqtt_wait] */ /*-------------------------- MQTT helper functions --------------------------*/ /** - * @brief Returns a string that describes an #AwsIotMqttError_t. + * @brief Returns a string that describes an #IotMqttError_t. * * Like the POSIX's `strerror`, this function returns a string describing a * return code. In this case, the return code is an MQTT library error code, @@ -1738,11 +1793,11 @@ AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, * @warning The string returned by this function must never be modified. */ /* @[declare_mqtt_strerror] */ -const char * AwsIotMqtt_strerror( AwsIotMqttError_t status ); +const char * IotMqtt_strerror( IotMqttError_t status ); /* @[declare_mqtt_strerror] */ /** - * @brief Returns a string that describes an #AwsIotMqttOperationType_t. + * @brief Returns a string that describes an #IotMqttOperationType_t. * * This function returns a string describing an MQTT operation type, `operation`. * @@ -1757,7 +1812,7 @@ const char * AwsIotMqtt_strerror( AwsIotMqttError_t status ); * @warning The string returned by this function must never be modified. */ /* @[declare_mqtt_operationtype] */ -const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ); +const char * IotMqtt_OperationType( IotMqttOperationType_t operation ); /* @[declare_mqtt_operationtype] */ /** @@ -1780,7 +1835,7 @@ const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ); * or UNSUBSCRIBE operations. * * One suitable use of this function is to check which subscriptions were rejected - * if @ref mqtt_function_subscribe returns #AWS_IOT_MQTT_SERVER_REFUSED; that return + * if @ref mqtt_function_subscribe returns #IOT_MQTT_SERVER_REFUSED; that return * code only means that at least one subscription was rejected. * * @param[in] mqttConnection The MQTT connection to check. @@ -1793,13 +1848,13 @@ const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ); * @return `true` if a subscription was found; `false` otherwise. * * @note The subscription QoS is not stored by the MQTT library; therefore, - * `pCurrentSubscription->QoS` will always be set to `0`. + * `pCurrentSubscription->qos` will always be set to #IOT_MQTT_QOS_0. */ /* @[declare_mqtt_issubscribed] */ -bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, - const char * const pTopicFilter, - uint16_t topicFilterLength, - AwsIotMqttSubscription_t * pCurrentSubscription ); +bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, + const char * pTopicFilter, + uint16_t topicFilterLength, + IotMqttSubscription_t * pCurrentSubscription ); /* @[declare_mqtt_issubscribed] */ -#endif /* ifndef _AWS_IOT_MQTT_H_ */ +#endif /* ifndef _IOT_MQTT_H_ */ diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h new file mode 100644 index 0000000000..129c844882 --- /dev/null +++ b/lib/include/iot_taskpool.h @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_taskpool.h + * @brief User-facing functions of the task pool library. + */ + +#ifndef _IOT_TASKPOOL_H_ +#define _IOT_TASKPOOL_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/* Task pool types. */ +#include "types/iot_taskpool_types.h" + +/*------------------------- TASKPOOL defined constants --------------------------*/ + +/** + * @functionspage{taskpool,task pool library} + * - @functionname{taskpool_function_createsystemtaskpool} + * - @functionname{taskpool_function_getsystemtaskpool} + * - @functionname{taskpool_function_create} + * - @functionname{taskpool_function_destroy} + * - @functionname{taskpool_function_setmaxthreads} + * - @functionname{taskpool_function_createjob} + * - @functionname{taskpool_function_createrecyclablejob} + * - @functionname{taskpool_function_destroyjob} + * - @functionname{taskpool_function_recyclejob} + * - @functionname{taskpool_function_schedule} + * - @functionname{taskpool_function_scheduledeferred} + * - @functionname{taskpool_function_getstatus} + * - @functionname{taskpool_function_trycancel} + */ + +/** + * @functionpage{IotTaskPool_CreateSystemTaskPool,taskpool,createsystemtaskpool} + * @functionpage{IotTaskPool_GetSystemTaskPool,taskpool,getsystemtaskpool} + * @functionpage{IotTaskPool_Create,taskpool,create} + * @functionpage{IotTaskPool_Destroy,taskpool,destroy} + * @functionpage{IotTaskPool_SetMaxThreads,taskpool,setmaxthreads} + * @functionpage{IotTaskPool_CreateJob,taskpool,createjob} + * @functionpage{IotTaskPool_CreateJob,taskpool,createrecyclablejob} + * @functionpage{IotTaskPool_DestroyJob,taskpool,destroyjob} + * @functionpage{IotTaskPool_RecycleJob,taskpool,recyclejob} + * @functionpage{IotTaskPool_Schedule,taskpool,schedule} + * @functionpage{IotTaskPool_ScheduleDeferred,taskpool,scheduledeferred} + * @functionpage{IotTaskPool_GetStatus,taskpool,getstatus} + * @functionpage{IotTaskPool_TryCancel,taskpool,trycancel} + */ + +/** + * @brief Creates the one single instance of the system task pool. + * + * This function should be called once by the application to initialize the one single instance of the system task pool. + * An application should initialize the system task pool early in the boot sequence, before initializing any other library + * that uses the system task pool. such as MQTT, Shadow, Defernder, etc.. An application should also initialize the system + * task pool before posting any jobs. Early initialization it typically easy to accomplish by creating the system task pool + * before the scheduler is started. + * + * This function does not allocate memory to hold the task pool data structures and state, but it + * may allocate memory to hold the dependent entities and data structures, e.g. the threads of the task + * pool. The system task pool handle is recoverable for later use by calling (@ref taskpool_function_getsystemtaskpool) or + * the shortcut @ref IOT_TASKPOOL_SYSTEM_TASKPOOL. + * + * @param[in] pInfo A pointer to the task pool initialization data. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_NO_MEMORY + * + * @warning This function should be called only once. Calling this function more that once will result in + * undefined behavior. + * + */ +/* @[declare_taskpool_createsystemtaskpool] */ +IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ); +/* @[declare_taskpool_createsystemtaskpool] */ + +/** + * @brief Retrieves the one and only instance of a system task pool + * + * This function retrieves the sytem task pool created with (@ref taskpool_function_createsystemtaskpool), and it is functionally + * equivalent to using the shortcut @ref AWS_IOT_TASKPOOL_SYSTEM_TASKPOOL. + * + * @return The system task pool handle. + * + * @warning This function should be called after creating the system task pool with @ref IotTaskPool_CreateSystemTaskPool. + * Calling this function before creating the system task pool may return a pointer to an uninitialized task pool, NULL, or otherwise + * fail with undefined behaviour. + * + */ +/* @[declare_taskpool_getsystemtaskpool] */ +IotTaskPool_t * IotTaskPool_GetSystemTaskPool(); +/* @[declare_taskpool_getsystemtaskpool] */ + +/** + * @brief Creates one instance of a task pool. + * + * This function should be called by the user to initialiaze one instance of a task + * pool. The task pool instance will be created around the storage pointed to by the `pTaskPool` + * parameter. This function will create the minimum number of threads requested by the user + * through an instance of the #IotTaskPoolInfo_t type specified with the `pInfo` parameter. + * This function does not allocate memory to hold the task pool data structures and state, but it + * may allocates memory to hold the dependent data structures, e.g. the threads of the task + * pool. + * + * @param[in] pInfo A pointer to the task pool initialization data. + * @param[in,out] pTaskPool A pointer to the task pool storage to be initialized. + * The pointer `pTaskPool` will hold a valid handle only if (@ref taskpool_function_create) + * completes succesfully. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_NO_MEMORY + * + */ +/* @[declare_taskpool_create] */ +IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, + IotTaskPool_t * const pTaskPool ); +/* @[declare_taskpool_create] */ + +/** + * @brief Destroys a task pool instance and collects all memory associated with a task pool and its + * satellite data structures. + * + * This function should be called to destroy one instance of a task pool previously created with a call + * to @ref taskpool_function_create or @ref taskpool_function_createsystemtaskpool. + * Calling this fuction release all underlying resources. After calling this function, any job scheduled but not yet executed + * will be cancelled and destroyed, as if the user called @ref AwsIotTaskPool_DestroyJob on every job + * previously scheduled. + * The `pTaskPool` instance will no longer be valid after this function returns. + * + * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create or + * @ref taskpool_function_createsystemtaskpool. The `pTaskPool` instance will no longer be valid after this function returns. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * + */ +/* @[declare_taskpool_destroy] */ +IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ); +/* @[declare_taskpool_destroy] */ + +/** + * @brief Sets the maximum number of threads for one instance of a task pool. + * + * This function sets the maximum number of threads for the task pool + * pointed to by `pTaskPool`. + * + * If the number of currently active threads in the task pool is greater than `maxThreads`, this + * function causes the task pool to shrink the number of active threads. + * + * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with + * a call to @ref taskpool_function_create. + * @param[in] maxThreads The maximum number of threads for the task pool. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + */ +/* @[declare_taskpool_setmaxthreads] */ +IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, + uint32_t maxThreads ); +/* @[declare_taskpool_setmaxthreads] */ + +/** + * @brief Creates a job for the task pool around a user-provided storage. + * + * This function may allocate memory to hold the state for a job. + * + * @param[in] userCallback A user-specified callback for the job. + * @param[in] pUserContext A user-specified context for the callback. + * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this + * function returns succesfully. This handle can be used to inspect the job status with + * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * + * + */ +/* @[declare_taskpool_createjob] */ +IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallback, + void * const pUserContext, + IotTaskPoolJob_t * const pJob ); +/* @[declare_taskpool_createjob] */ + +/** + * brief Creates a job for the task pool by allocating the job dynamically. + * + * A recyclable job does not need to be allocated twice, but it can rather be reused through + * subsequent calls to @ref AwsIotTaskPool_CreateRecyclableJob. + * + * @param[in] pTaskPool A handle to the task pool for which to create a recyclable job. + * @param[in] userCallback A user-specified callback for the job. + * @param[in] pUserContext A user-specified context for the callback. + * @param[out] ppJob A pointer to an instance of @ref AwsIotTaskPoolJob_t that will be initialized when this + * function returns succesfully. This handle can be used to inspect the job status with + * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #AWS_IOT_TASKPOOL_BAD_PARAMETER + * - #AWS_IOT_TASKPOOL_NO_MEMORY + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + * @note This function will not allocate memory. + * + * @warning A recyclable job should be recycled with a call to @ref AwsIotTaskPool_RecycleJob rather than destroyed. + * + */ +/* @[declare_taskpool_createrecyclablejob] */ +IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskPool, + const IotTaskPoolRoutine_t userCallback, + void * const pUserContext, + IotTaskPoolJob_t ** const ppJob ); +/* @[declare_taskpool_createrecyclablejob] */ + +/** + * @brief This function uninitializes a job. + * + * This function will destroy a job created with @ref AwsIotTaskPool_CreateJob or @ref AwsIotTaskPool_CreateRecyclableJob. + * A job should not be destroyed twice. A job that was previously scheduled but has not completed yet should not be destroyed, + * but rather the application should attempt to cancel it first by calling @ref AwsIotTaskPool_TryCancel. + * An attempt to destroy a job that was scheduled but not yet executed or canceled, may result in a + * @ref AWS_IOT_TASKPOOL_ILLEGAL_OPERATION error. + * + * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create. + * @param[in] pJob A handle to a job that was create with a call to @ref IotTaskPool_CreateJob. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + * @warning The task pool will try and prevent destroying jobs that are currently queued for execution, but does + * not enforce strict ordering of operations. It is up to the user to make sure @ref IotTaskPool_DestroyJob is not called + * our of order. + * + */ +/* @[declare_taskpool_destroyjob] */ +IotTaskPoolError_t IotTaskPool_DestroyJob( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ); +/* @[declare_taskpool_destroyjob] */ + +/** + * @brief Rrecycles a job into the task pool job cache. + * + * This function will try and recycle the job into the task pool cache. If the cache is at the upper limit, + * the job memory is destroyed as if the user called @ref AwsIotTaskPool_DestroyJob. The job should be recycled into + * the task pool instance from where it was allocated. + * Failure to do so will yield undefined results. A job should not be recycled twice. A job + * that was previously scheduled but not completed or canceled cannot be safely recycled. An attempt to do so will result + * in an @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. + * + * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create. + * @param[out] pJob A pointer to a job that was create with a call to @ref IotTaskPool_CreateJob. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_ILLEGAL_OPERATION + * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + * @warning The `pTaskPool` used in this function should be the same + * used to create the job pointed to by `pJob`, or the results will be undefined. + * + * @warning Attempting to call this function on a statically allocated job will result in @ref IOT_TASKPOOL_ILLEGAL_OPERATION + * error. + * + * @warning This function should be used to recycle a job in the task pool cache when after the job executed. + * Failing to call either this function or @ref IotTaskPool_DestroyJob will result is a memory leak. Statically + * alloted jobs do not need to be recycled or destroyed. + * + */ +/* @[declare_taskpool_recyclejob] */ +IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ); +/* @[declare_taskpool_recyclejob] */ + +/** + * @brief This function schedules a job created with @ref taskpool_function_createjob or @ref taskpool_function_createrecyclablejob + * against the task pool pointed to by `pTaskPool`. + * + * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool + * library. + * + * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. + * a call to @ref taskpool_function_create. + * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_ILLEGAL_OPERATION + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + * + * @note This function will not allocate memory, so it is guaranteed to succeed if the paramters are correct and the task pool + * was correctly initialized, and not yet destroyed. + * + * @warning The `pTaskPool` used in this function should be the same used to create the job pointed to by `pJob`, or the + * results will be undefined. + * + * Example + * @code{c} + * // An example of a user context to pass to a callback through a task pool thread. + * typedef struct JobUserContext + * { + * uint32_t counter; + * } JobUserContext_t; + * + * // An example of a user callback to invoke through a task pool thread. + * static void ExecutionCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, void * context ) + * { + * ( void )pTaskPool; + * ( void )pJob; + * + * JobUserContext_t * pUserContext = ( JobUserContext_t * )context; + * + * pUserContext->counter++; + * + * // Destroy the job. + * IotTaskPool_DestroyJob( pTaskPool, pJob ); + * } + * + * void TaskPoolExample( ) + * { + * JobUserContext_t userContext = { 0 }; + * IotTaskPoolJob_t job; + * IotTaskPool_t * pTaskPool; + * + * // Configure the task pool to hold at least two threads and three at the maximum. + * // Provide proper stack size and priority per the application needs. + * + * const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = 512, .priority = 0 }; + * + * // Create a task pool. + * IotTaskPool_Create( &tpInfo, &pTaskPool ); + * + * // Statically allocate one job, schedule it. + * IotTaskPool_CreateJob( &ExecutionCb, &userContext, &job ); + * + * IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( pTaskPool, &job ); + * + * switch ( errorSchedule ) + * { + * case IOT_TASKPOOL_SUCCESS: + * break; + * case IOT_TASKPOOL_BAD_PARAMETER: // Invalid parameters, such as a NULL handle, can trigger this condition. + * case IOT_TASKPOOL_ILLEGAL_OPERATION: // Scheduling a job that was previously scheduled or destroyed could trigger this condition. + * case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: // Scheduling a job after trying to destroy the task pool could trigger this operation. + * // ASSERT + * break; + * default: + * // ASSERT + * } + * + * // + * // ... Perform other operations ... + * // + * + * + * IotTaskPool_DestroyJob( pTaskPool, pJob ); + * IotTaskPool_Destroy( pTaskPool ); + * } + * @endcode + */ +/* @[declare_taskpool_schedule] */ +IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ); +/* @[declare_taskpool_schedule] */ + +/** + * @brief This function schedules a job created with @ref taskpool_function_createjob against the task pool + * pointed to by `pTaskPool` to be executed after a userdefined time interval. + * + * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool + * library. + * + * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. + * a call to @ref taskpool_function_create. + * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_ILLEGAL_OPERATION + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + * + * @note This function will not allocate memory. + * + * @warning The `pTaskPool` used in this function should be the same + * used to create the job pointed to by `pJob`, or the results will be undefined. + * + */ +/* @[declare_taskpool_scheduledeferred] */ +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + uint64_t timeMs ); +/* @[declare_taskpool_scheduledeferred] */ + +/** + * @brief This function retrieves the current status of a job. + * + * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with + * a call to @ref taskpool_function_create or @ref taskpool_function_createsystemtaskpool. + * @param[in] pJob The job to cancel. + * @param[out] pStatus The status of the job at the time of cancellation. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * + * @warning This function is not thread safe and the job status returned in `pStatus` may be invalid by the time + * the calling thread has a chance to inspect it. + */ +/* @[declare_taskpool_getstatus] */ +IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, + const IotTaskPoolJob_t * const pJob, + IotTaskPoolJobStatus_t * const pStatus ); +/* @[declare_taskpool_getstatus] */ + +/** + * @brief This function tries to cancel a job that was previously scheduled with @ref IotTaskPool_Schedule. + * + * A job can be canceled only if it is not yet executing, i.e. if its status is + * @ref IOT_TASKPOOL_STATUS_READY or @ref IOT_TASKPOOL_STATUS_SCHEDULED. Calling + * @ref IotTaskPool_TryCancel on a job whose status is @ref IOT_TASKPOOL_STATUS_EXECUTING, + * or @AWS_IOT_TASKPOOL_STATUS_CANCELED will yield a @AWS_IOT_TASKPOOL_CANCEL_FAILED return result. + * + * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with + * a call to @ref taskpool_function_create. + * @param[in] pJob The job to cancel. + * @param[out] pStatus The status of the job at the time of cancellation. + * + * @return One of the following: + * - #IOT_TASKPOOL_SUCCESS + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * - #IOT_TASKPOOL_CANCEL_FAILED + * + * @warning The `pTaskPool` used in this function should be the same + * used to create the job pointed to by `pJob`, or the results will be undefined. + * + */ +/* @[declare_taskpool_trycancel] */ +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + IotTaskPoolJobStatus_t * const pStatus ); +/* @[declare_taskpool_trycancel] */ + +const char * IotTaskPool_strerror( IotTaskPoolError_t status ); + +#endif /* ifndef _IOT_TASKPOOL_H_ */ diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index fbb31aed35..c7debee0c9 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -57,25 +57,21 @@ typedef enum IotNetworkError * Together, they represent a network stack. * - @functionname{platform_network_function_create} * - @functionname{platform_network_function_setreceivecallback} - * - @functionname{platform_network_function_setclosecallback} * - @functionname{platform_network_function_send} * - @functionname{platform_network_function_receive} * - @functionname{platform_network_function_close} * - @functionname{platform_network_function_destroy} * - @functionname{platform_network_function_receivecallback} - * - @functionname{platform_network_function_closecallback} */ /** * @functionpage{IotNetworkInterface_t::create,platform_network,create} * @functionpage{IotNetworkInterface_t::setReceiveCallback,platform_network,setreceivecallback} - * @functionpage{IotNetworkInterface_t::setCloseCallback,platform_network,setclosecallback} * @functionpage{IotNetworkInterface_t::send,platform_network,send} * @functionpage{IotNetworkInterface_t::receive,platform_network,receive} * @functionpage{IotNetworkInterface_t::close,platform_network,close} * @functionpage{IotNetworkInterface_t::destroy,platform_network,destroy} * @functionpage{IotNetworkReceiveCallback_t,platform_network,receivecallback} - * @functionpage{IotNetworkCloseCallback_t,platform_network,closecallback} */ /** @@ -135,28 +131,6 @@ typedef int32_t ( * IotNetworkReceiveCallback_t )( void * pContext, void ( * freeReceivedData )( void * ) ); /* @[declare_platform_network_receivecallback] */ -/** - * @brief Callback function to invoke after @ref platform_network_function_close - * is called. - * - * A function with this signature may be set with @ref platform_network_function_setclosecallback - * to be invoked when the network connection is closed. - * - * @ref platform_network_function_close instructs the underlying network stack to - * close a connection. In constrast, this function can be used to inform a higher-level - * application that a connection was closed. - * - * @param[in] reason Meant to convey the reason a connection was closed; meaningful - * values should be defined by the library closing the connection. - * @param[in] pContext A context defined by the library closing the connection. - * @param[in] pConnection The connection that was closed. - */ -/* @[declare_platform_network_closecallback] */ -typedef void ( * IotNetworkCloseCallback_t )( int32_t reason, - void * pContext, - void * pConnection ); -/* @[declare_platform_network_closecallback] */ - /** * @ingroup platform_datatypes_paramstructs * @brief Represents the functions of a network stack. @@ -217,30 +191,6 @@ typedef struct IotNetworkInterface void * pContext ); /* @[declare_platform_network_setreceivecallback] */ - /** - * @brief Register an @ref platform_network_function_closecallback. - * - * Sets an @ref platform_network_function_closecallback to be called when - * this network connection is closed. This function can be used to notify - * higher-level applications of network connection closure. - * - * Each network connection may only have one close callback at any time. - * If this function is called for a connection that already has a close - * callback, the existing callback should be replaced. If the `closeCallback` - * parameter is `NULL`, any existing close callback should be removed. - * - * @param[in] pConnection The connection to associate with the close callback. - * @param[in] closeCallback The function to invoke when the network is closed. - * @param[in] pContext A value to pass as a parameter to the close callback. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - */ - /* @[declare_platform_network_setclosecallback] */ - IotNetworkError_t ( * setCloseCallback )( void * pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ); - /* @[declare_platform_network_setclosecallback] */ - /** * @brief Send data over a return connection. * @@ -298,8 +248,6 @@ typedef struct IotNetworkInterface * In addition to closing the connection, this function should also remove * any active [receive callback](@ref platform_network_function_receivecallback). * - * @param[in] reason The reason for connection closure, defined by a higher-level - * library. It will be passed to @ref platform_network_function_closecallback. * @param[in] pConnection The network connection to close, defined by the * network stack. * @@ -308,8 +256,7 @@ typedef struct IotNetworkInterface * @note It must be safe to call this function on an already-closed connection. */ /* @[declare_platform_network_close] */ - IotNetworkError_t ( * close )( int32_t reason, - void * pConnection ); + IotNetworkError_t ( * close )( void * pConnection ); /* @[declare_platform_network_close] */ /** diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index 759ac5cd1a..a578644d92 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -39,16 +39,6 @@ /* Platform layer types include. */ #include "types/iot_platform_types.h" -/** - * @brief A value representing the system default for new thread priority. - */ -#define IOT_THREAD_DEFAULT_PRIORITY 0 - -/** - * @brief A value representhing the system default for new thread stack size. - */ -#define IOT_THREAD_DEFAULT_STACK_SIZE 0 - /** * @functionspage{platform_threads,platform thread management,Thread Management} * - @functionname{platform_threads_function_createdetachedthread} diff --git a/lib/include/private/aws_iot_mqtt_internal.h b/lib/include/private/aws_iot_mqtt_internal.h deleted file mode 100644 index 81a707ca77..0000000000 --- a/lib/include/private/aws_iot_mqtt_internal.h +++ /dev/null @@ -1,907 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_mqtt_internal.h - * @brief Internal header of MQTT library. This header should not be included in - * typical application code. - */ - -#ifndef _AWS_IOT_MQTT_INTERNAL_H_ -#define _AWS_IOT_MQTT_INTERNAL_H_ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Linear containers (lists and queues) include. */ -#include "iot_linear_containers.h" - -/* Platform layer types include. */ -#include "types/iot_platform_types.h" - -/* MQTT include. */ -#include "aws_iot_mqtt.h" - -/** - * @def AwsIotMqtt_Assert( expression ) - * @brief Assertion macro for the MQTT library. - * - * Set @ref AWS_IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if AWS_IOT_MQTT_ENABLE_ASSERTS == 1 - #ifndef AwsIotMqtt_Assert - #include - #define AwsIotMqtt_Assert( expression ) assert( expression ) - #endif -#else - #define AwsIotMqtt_Assert( expression ) -#endif - -/* Configure logs for MQTT functions. */ -#ifdef AWS_IOT_LOG_LEVEL_MQTT - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_MQTT -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define _LIBRARY_LOG_NAME ( "MQTT" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" - -/** - * @brief Allocate an #_mqttConnection_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef AwsIotMqtt_MallocConnection - #define AwsIotMqtt_MallocConnection Iot_MallocMqttConnection - #endif - -/** - * @brief Free an #_mqttConnection_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef AwsIotMqtt_FreeConnection - #define AwsIotMqtt_FreeConnection Iot_FreeMqttConnection - #endif - -/** - * @brief Allocate memory for an MQTT packet. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef AwsIotMqtt_MallocMessage - #define AwsIotMqtt_MallocMessage Iot_MallocMessageBuffer - #endif - -/** - * @brief Free an MQTT packet. This function should have the same signature - * as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef AwsIotMqtt_FreeMessage - #define AwsIotMqtt_FreeMessage Iot_FreeMessageBuffer - #endif - -/** - * @brief Allocate an #_mqttOperation_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef AwsIotMqtt_MallocOperation - #define AwsIotMqtt_MallocOperation Iot_MallocMqttOperation - #endif - -/** - * @brief Free an #_mqttOperation_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef AwsIotMqtt_FreeOperation - #define AwsIotMqtt_FreeOperation Iot_FreeMqttOperation - #endif - -/** - * @brief Allocate an #_mqttSubscription_t. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef AwsIotMqtt_MallocSubscription - #define AwsIotMqtt_MallocSubscription Iot_MallocMqttSubscription - #endif - -/** - * @brief Free an #_mqttSubscription_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef AwsIotMqtt_FreeSubscription - #define AwsIotMqtt_FreeSubscription Iot_FreeMqttSubscription - #endif - -/** - * @brief Allocate an #_mqttTimerEvent_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef AwsIotMqtt_MallocTimerEvent - #define AwsIotMqtt_MallocTimerEvent Iot_MallocMqttTimerEvent - #endif - -/** - * @brief Free an #_mqttTimerEvent_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef AwsIotMqtt_FreeTimerEvent - #define AwsIotMqtt_FreeTimerEvent Iot_FreeMqttTimerEvent - #endif -#else /* if IOT_STATIC_MEMORY_ONLY */ - #include - - #ifndef AwsIotMqtt_MallocConnection - #define AwsIotMqtt_MallocConnection malloc - #endif - - #ifndef AwsIotMqtt_FreeConnection - #define AwsIotMqtt_FreeConnection free - #endif - - #ifndef AwsIotMqtt_MallocMessage - #define AwsIotMqtt_MallocMessage malloc - #endif - - #ifndef AwsIotMqtt_FreeMessage - #define AwsIotMqtt_FreeMessage free - #endif - - #ifndef AwsIotMqtt_MallocOperation - #define AwsIotMqtt_MallocOperation malloc - #endif - - #ifndef AwsIotMqtt_FreeOperation - #define AwsIotMqtt_FreeOperation free - #endif - - #ifndef AwsIotMqtt_MallocSubscription - #define AwsIotMqtt_MallocSubscription malloc - #endif - - #ifndef AwsIotMqtt_FreeSubscription - #define AwsIotMqtt_FreeSubscription free - #endif - - #ifndef AwsIotMqtt_MallocTimerEvent - #define AwsIotMqtt_MallocTimerEvent malloc - #endif - - #ifndef AwsIotMqtt_FreeTimerEvent - #define AwsIotMqtt_FreeTimerEvent free - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_MQTT_ENABLE_METRICS - #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) -#endif -#ifndef AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES - #define AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) -#endif -#ifndef AWS_IOT_MQTT_MAX_CALLBACK_THREADS - #define AWS_IOT_MQTT_MAX_CALLBACK_THREADS ( 2 ) -#endif -#ifndef AWS_IOT_MQTT_MAX_SEND_THREADS - #define AWS_IOT_MQTT_MAX_SEND_THREADS ( 1 ) -#endif -#ifndef AWS_IOT_MQTT_TEST - #define AWS_IOT_MQTT_TEST ( 0 ) -#endif -#ifndef AWS_IOT_MQTT_RESPONSE_WAIT_MS - #define AWS_IOT_MQTT_RESPONSE_WAIT_MS ( 1000 ) -#endif -#ifndef AWS_IOT_MQTT_RETRY_MS_CEILING - #define AWS_IOT_MQTT_RETRY_MS_CEILING ( 60000 ) -#endif -#ifndef AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS - #define AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ( 100 ) -#endif -/** @endcond */ - -/* - * Constants related to limits defined in AWS Service Limits. - * - * For details, see - * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html - * - * Used to validate parameters if when connecting to an AWS IoT MQTT server. - */ -#define _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ - -/* - * MQTT control packet type and flags. Always the first byte of an MQTT - * packet. - * - * For details, see - * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 - */ -#define _MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ -#define _MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ -#define _MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ -#define _MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ -#define _MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ -#define _MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ -#define _MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ - -/*---------------------- MQTT internal data structures ----------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declarations. - */ -struct _mqttSubscription; -struct _mqttConnection; -struct _mqttOperation; -struct _mqttTimerEvent; -/** @endcond */ - -/** - * @brief Represents a subscription stored in an MQTT connection. - */ -typedef struct _mqttSubscription -{ - IotLink_t link; /**< @brief List link member. */ - - int references; /**< @brief How many subscription callbacks are using this subscription. */ - - /** - * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for - * this subscription. - * - * If there are active subscription callbacks, @ref mqtt_function_unsubscribe - * cannot remove this subscription. Instead, it will set this flag, which - * schedules the removal of this subscription once all subscription callbacks - * terminate. - */ - bool unsubscribed; - - struct - { - uint16_t identifier; /**< @brief Packet identifier. */ - size_t order; /**< @brief Order in the packet's list of subscriptions. */ - } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ - - AwsIotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ - - uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ - char pTopicFilter[]; /**< @brief The subscription topic filter. */ -} _mqttSubscription_t; - -/** - * @brief Internal structure to hold data on an MQTT connection. - */ -typedef struct _mqttConnection -{ - bool errorOccurred; /**< @brief Tracks if a protocol violation or other error occurred. */ - bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ - AwsIotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the #_mqttConnection_t.subscriptionList. */ - - IotTimer_t timer; /**< @brief Expires when a timer event should be processed. */ - IotMutex_t timerMutex; /**< @brief Prevents concurrent access from timer thread and protects timer event list. */ - IotListDouble_t timerEventList; /**< @brief List of active timer events. */ - uint16_t keepAliveSeconds; /**< @brief Keep-alive interval. */ - struct _mqttOperation * pPingreqOperation; /**< @brief PINGREQ operation. Only used if keep-alive is active. */ - struct _mqttTimerEvent * pKeepAliveEvent; /**< @brief When to process a keep-alive. Only used if keep-alive is active. */ -} _mqttConnection_t; - -/** - * @brief Internal structure representing a single MQTT operation, such as - * CONNECT, SUBSCRIBE, PUBLISH, etc. - * - * Queues of these structures keeps track of all in-progress MQTT operations. - */ -typedef struct _mqttOperation -{ - /* Pointers to neighboring queue elements. */ - IotLink_t link; /**< @brief List link member. */ - - bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ - struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ - - union - { - /* If incomingPublish is false, this struct is valid. */ - struct - { - /* Basic operation information. */ - AwsIotMqttOperationType_t operation; /**< @brief What operation this structure represents. */ - uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ - uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ - - /* Serialized packet and size. */ - uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ - size_t packetSize; /**< @brief Size of `pMqttPacket`. */ - - /* How to notify of an operation's completion. */ - union - { - IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ - AwsIotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ - } notify; /**< @brief How to notify of this operation's completion. */ - AwsIotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ - - struct _mqttTimerEvent * pPublishRetry; /**< @brief How an operation will be retried. Only used for QoS 1 publishes. */ - }; - - /* If incomingPublish is true, this struct is valid. */ - struct - { - AwsIotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ - struct _mqttOperation * pNextPublish; /**< @brief Pointer to the next PUBLISH in the data stream. */ - const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ - void ( * freeReceivedData )( void * ); /**< @brief Function called to free `pReceivedData`. */ - }; - }; -} _mqttOperation_t; - -/** - * @brief Represents an operation that is subject to a timer. - * - * These events are queued per MQTT connection. They are sorted by their - * expiration time. - */ -typedef struct _mqttTimerEvent -{ - IotLink_t link; /**< @brief List link member. */ - - uint64_t expirationTime; /**< @brief When this event should be processed. */ - struct _mqttOperation * pOperation; /**< @brief The MQTT operation associated with this event. */ - - /* Valid members depend on the operation associated with this event - * (PINGREQ or PUBLISH). The checkPingresp member is valid for PINGREQ, - * and the retry member is valid for PUBLISH. */ - union - { - bool checkPingresp; /**< @brief This keep-alive operation is waiting for PINGRESP. */ - - struct - { - int count; /**< @brief The number of times this message has been retried. */ - int limit; /**< @brief The maximum number of times to retry this message. */ - uint64_t nextPeriod; /**< @brief How long to wait before the next retry. */ - } retry; - }; -} _mqttTimerEvent_t; - -/** - * @brief Holds waiting MQTT operations and manages threads that process them. - */ -typedef struct _mqttOperationQueue -{ - IotQueue_t queue; /**< @brief Queue of waiting MQTT operations. */ - - /** - * @brief Maintains a count of threads currently available to process this - * queue and provides a mechanism to wait for active callback threads to finish. - */ - IotSemaphore_t availableThreads; -} _mqttOperationQueue_t; - -/* Declarations of the structures keeping track of MQTT operations for internal - * files. */ -extern _mqttOperationQueue_t _IotMqttCallback; -extern _mqttOperationQueue_t _IotMqttSend; -extern IotMutex_t _IotMqttQueueMutex; -extern IotListDouble_t _IotMqttPendingResponse; -extern IotMutex_t _IotMqttPendingResponseMutex; - -/*-------------------- MQTT struct validation functions ---------------------*/ - -/** - * @brief Check that an #AwsIotMqttNetIf_t is valid. - * - * @param[in] pNetworkInterface The #AwsIotMqttNetIf_t to validate. - * - * @return `true` if `pNetworkInterface` is valid; `false` otherwise. - */ -bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkInterface ); - -/** - * @brief Check that an #AwsIotMqttConnectInfo_t is valid. - * - * @param[in] pConnectInfo The #AwsIotMqttConnectInfo_t to validate. - * - * @return `true` if `pConnectInfo` is valid; `false` otherwise. - */ -bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo ); - -/** - * @brief Check that an #AwsIotMqttPublishInfo_t is valid. - * - * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. - * @param[in] pPublishInfo The #AwsIotMqttPublishInfo_t to validate. - * - * @return `true` if `pPublishInfo` is valid; `false` otherwise. - */ -bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, - const AwsIotMqttPublishInfo_t * const pPublishInfo ); - -/** - * @brief Check that an #AwsIotMqttReference_t is valid and waitable. - * - * @param[in] reference The #AwsIotMqttReference_t to validate. - * - * @return `true` if `reference` is valid; `false` otherwise. - */ -bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ); - -/** - * @brief Check that a list of #AwsIotMqttSubscription_t is valid. - * - * @param[in] operation Either #AWS_IOT_MQTT_SUBSCRIBE or #AWS_IOT_MQTT_UNSUBSCRIBE. - * Some parameters are not validated for #AWS_IOT_MQTT_UNSUBSCRIBE. - * @param[in] awsIotMqttMode Specifies if this SUBSCRIBE packet is being sent to - * an AWS IoT MQTT server. - * @param[in] pListStart First element of the list to validate. - * @param[in] listSize Number of elements in the subscription list. - * - * @return `true` if every element in the list is valid; `false` otherwise. - */ -bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t operation, - bool awsIotMqttMode, - const AwsIotMqttSubscription_t * const pListStart, - size_t listSize ); - -/*-------------------- MQTT packet serializer functions ---------------------*/ - -/** - * @brief Initialize the MQTT packet serializer. Called by @ref mqtt_function_init - * when initializing the MQTT library. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_INIT_FAILED. - */ -AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ); - -/** - * @brief Free resources taken by #AwsIotMqttInternal_InitSerialize. - * - * No parameters, no return values. - */ -void AwsIotMqttInternal_CleanupSerialize( void ); - -/** - * @brief Get the MQTT packet type from a stream of bytes. - * - * @param[in] pPacket Pointer to the beginning of the byte stream. - * @param[in] packetSize Size of the byte stream. - * - * @return One of the server-to-client MQTT packet types. - * - * @note This function is only used for incoming packets, and may not work - * correctly for outgoing packets. - */ -uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, - size_t packetSize ); - -/** - * @brief Generate a CONNECT packet from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[out] pConnectPacket Where the CONNECT packet is written. - * @param[out] pPacketSize Size of the packet written to `pConnectPacket`. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, - uint8_t ** const pConnectPacket, - size_t * const pPacketSize ); - -/** - * @brief Deserialize a CONNACK packet. - * - * Converts the packet from a stream of bytes to an #AwsIotMqttError_t. Also - * prints out debug log messages about the packet. - * @param[in] pConnackStart Pointer to the start of a CONNACK packet. - * @param[in] dataLength Length of the data stream after `pConnackStart`. - * @param[out] pBytesProcessed The number of bytes in the data stream processed - * by this function. For CONNACK, this is always 4. - * - * @return #AWS_IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; - * #AWS_IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; - * #AWS_IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. - */ -AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const pConnackStart, - size_t dataLength, - size_t * const pBytesProcessed ); - -/** - * @brief Generate a PUBLISH packet from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information. - * @param[out] pPublishPacket Where the PUBLISH packet is written. - * @param[out] pPacketSize Size of the packet written to `pPublishPacket`. - * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint8_t ** const pPublishPacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ); - -/** - * @brief Set the DUP bit in a QoS 1 PUBLISH packet. - * - * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. - * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify. - * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag, - * a new packet identifier is generated and should be written here. This parameter - * is only used when connected to an AWS IoT MQTT server. - * - * @note See #AwsIotMqttPublishInfo_t for caveats with retransmission to the - * AWS IoT MQTT server. - */ -void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, - uint8_t * const pPublishPacket, - uint16_t * const pNewPacketIdentifier ); - -/** - * @brief Deserialize a PUBLISH packet received from the server. - * - * Converts the packet from a stream of bytes to an #AwsIotMqttPublishInfo_t and - * extracts the packet identifier. Also prints out debug log messages about the - * packet. - * - * @param[in] pPublishStart Pointer to the start of a PUBLISH packet. - * @param[in] dataLength Length of the data stream after `pPublishStart`. - * @param[out] pOutput Where the deserialized PUBLISH will be written. - * @param[out] pPacketIdentifier The packet identifier in the PUBLISH. - * @param[out] pBytesProcessed The number of bytes in the data stream processed - * by this function. - * - * @return #AWS_IOT_MQTT_SUCCESS if PUBLISH is valid; #AWS_IOT_MQTT_BAD_RESPONSE - * if the PUBLISH packet doesn't follow MQTT spec. - */ -AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const pPublishStart, - size_t dataLength, - AwsIotMqttPublishInfo_t * const pOutput, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ); - -/** - * @brief Generate a PUBACK packet for the given packet identifier. - * - * @param[in] packetIdentifier The packet identifier to place in PUBACK. - * @param[out] pPubackPacket Where the PUBACK packet is written. - * @param[out] pPacketSize Size of the packet written to `pPubackPacket`. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializePuback( uint16_t packetIdentifier, - uint8_t ** const pPubackPacket, - size_t * const pPacketSize ); - -/** - * @brief Deserialize a PUBACK packet. - * - * Converts the packet from a stream of bytes to an #AwsIotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in] pPubackStart Pointer to the start of a PUBACK packet. - * @param[in] dataLength Length of the data stream after `pPubackStart`. - * @param[out] pPacketIdentifier The packet identifier in the PUBACK. - * @param[out] pBytesProcessed The number of bytes in the data stream processed - * by this function. For PUBACK, this is always 4. - * - * @return #AWS_IOT_MQTT_SUCCESS if PUBACK is valid; #AWS_IOT_MQTT_BAD_RESPONSE - * if the PUBACK packet doesn't follow MQTT spec. - */ -AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pPubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ); - -/** - * @brief Generate a SUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pSubscribePacket Where the SUBSCRIBE packet is written. - * @param[out] pPacketSize Size of the packet written to `pSubscribePacket`. - * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint8_t ** const pSubscribePacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ); - -/** - * @brief Deserialize a SUBACK packet. - * - * Converts the packet from a stream of bytes to an #AwsIotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in] mqttConnection The MQTT connection associated with the subscription. - * Rejected topic filters are removed from this connection. - * @param[in] pSubackStart Pointer to the start of a SUBACK packet. - * @param[in] dataLength Length of the data stream after `pSubackStart`. - * @param[out] pPacketIdentifier The packet identifier in the SUBACK. - * @param[out] pBytesProcessed The number of bytes in the data stream processed by - * this function. - * - * @return #AWS_IOT_MQTT_SUCCESS if SUBACK is valid; #AWS_IOT_MQTT_BAD_RESPONSE - * if the SUBACK packet doesn't follow MQTT spec. - */ -AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t mqttConnection, - const uint8_t * const pSubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ); - -/** - * @brief Generate an UNSUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions to remove. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pUnsubscribePacket Where the UNSUBSCRIBE packet is written. - * @param[out] pPacketSize Size of the packet written to `pUnsubscribePacket`. - * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint8_t ** const pUnsubscribePacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ); - -/** - * @brief Deserialize a UNSUBACK packet. - * - * Converts the packet from a stream of bytes to an #AwsIotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in] pUnsubackStart Pointer to the start of an UNSUBACK packet. - * @param[in] dataLength Length of the data stream after `pUnsubackStart`. - * @param[out] pPacketIdentifier The packet identifier in the UNSUBACK. - * @param[out] pBytesProcessed The number of bytes in the data stream processed by - * this function. For UNSUBACK, this is always 4. - * - * @return #AWS_IOT_MQTT_SUCCESS if UNSUBACK is valid; #AWS_IOT_MQTT_BAD_RESPONSE - * if the UNSUBACK packet doesn't follow MQTT spec. - */ -AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const pUnsubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ); - -/** - * @brief Generate a PINGREQ packet. - * - * @param[out] pPingreqPacket Where the PINGREQ packet is written. - * @param[out] pPacketSize Size of the packet written to `pPingreqPacket`. - * - * @return Always returns #AWS_IOT_MQTT_SUCCESS. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreqPacket, - size_t * const pPacketSize ); - -/** - * @brief Deserialize a PINGRESP packet. - * - * Converts the packet from a stream of bytes to an #AwsIotMqttError_t. Also - * prints out debug log messages about the packet. - * @param[in] pPingrespStart Pointer to the start of a PINGRESP packet. - * @param[in] dataLength Length of the data stream after `pPingrespStart`. - * @param[out] pBytesProcessed The number of bytes in the data stream processed by - * this function. For PINGRESP, this is always 2. - * - * @return #AWS_IOT_MQTT_SUCCESS if PINGRESP is valid; #AWS_IOT_MQTT_BAD_RESPONSE - * if the PINGRESP packet doesn't follow MQTT spec. - */ -AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const pPingrespStart, - size_t dataLength, - size_t * const pBytesProcessed ); - -/** - * @brief Generate a DISCONNECT packet. - * - * @param[out] pDisconnectPacket Where the DISCONNECT packet is written. - * @param[out] pPacketSize Size of the packet written to `pDisconnectPacket`. - * - * @return Always returns #AWS_IOT_MQTT_SUCCESS. - */ -AwsIotMqttError_t AwsIotMqttInternal_SerializeDisconnect( uint8_t ** const pDisconnectPacket, - size_t * const pPacketSize ); - -/** - * @brief Free a packet generated by the serializer. - * - * @param[in] pPacket The packet to free. - */ -void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ); - -/*-------------------- MQTT operation record functions ----------------------*/ - -/** - * @brief Compare two #_mqttTimerEvent_t by expiration time. - * - * @param[in] pTimerEventLink1 The link member of the first #_mqttTimerEvent_t to compare. - * @param[in] pTimerEventLink2 The link member of the second #_mqttTimerEvent_t to compare. - * - * @return - * - Negative value if the first timer event is less than the second timer event. - * - Zero if the two timer events are equal. - * - Positive value if the first timer event is greater than the second timer event. - */ -int AwsIotMqttInternal_TimerEventCompare( const IotLink_t * const pTimerEventLink1, - const IotLink_t * const pTimerEventLink2 ); - -/** - * @brief Create a record for a new in-progress MQTT operation. - * - * @param[out] pNewOperation Set to point to the new operation on success. - * @param[in] flags Flags variable passed to a user-facing MQTT function. - * @param[in] pCallbackInfo User-provided callback function and parameter. - * - * @return #AWS_IOT_MQTT_SUCCESS, #AWS_IOT_MQTT_BAD_PARAMETER, or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const pNewOperation, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo ); - -/** - * @brief Free resources used to record an MQTT operation. This is called when - * the operation completes. - * - * @param[in] pData The operation which completed. This parameter is of type - * `void*` to match the signature of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -void AwsIotMqttInternal_DestroyOperation( void * pData ); - -/** - * @brief Enqueue an MQTT operation for processing. - * - * @param[in] pOperation The MQTT operation to enqueue. - * @param[in] pQueue The address of either #_IotMqttCallback or #_IotMqttSend. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation, - _mqttOperationQueue_t * const pQueue ); - -/** - * @brief Search the list of MQTT operations pending responses using an operation - * name and packet identifier. Removes a matching operation from the list if found. - * - * @param[in] operation The operation type to look for. - * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore. - * - * @return Pointer to any matching operation; `NULL` if no match was found. - */ -_mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t operation, - const uint16_t * const pPacketIdentifier ); - -/** - * @brief Notify of a completed MQTT operation. - * - * @param[in] pOperation The MQTT operation which completed. - * - * Depending on the parameters passed to a user-facing MQTT function, the - * notification will cause @ref mqtt_function_wait to return or invoke a - * user-provided callback. - */ -void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ); - -/*------------------- Subscription management functions ---------------------*/ - -/** - * @brief Add an array of subscriptions to the subscription manager. - * - * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. - * @param[in] subscribePacketIdentifier Packet identifier for the subscriptions' - * SUBSCRIBE packet. - * @param[in] pSubscriptionList The first element in the array. - * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. - * - * @return #AWS_IOT_MQTT_SUCCESS or #AWS_IOT_MQTT_NO_MEMORY. - */ -AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const pMqttConnection, - uint16_t subscribePacketIdentifier, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount ); - -/** - * @brief Process a received PUBLISH from the server, invoking any subscription - * callbacks that have a matching topic filter. - * - * @param[in] pMqttConnection The MQTT connection associated with the received - * PUBLISH. - * @param[in] pCallbackParam The parameter to pass to a PUBLISH callback. - */ -void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnection, - AwsIotMqttCallbackParam_t * const pCallbackParam ); - -/** - * @brief Remove a single subscription from the subscription manager by - * packetIdentifier and order. - * - * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. - * @param[in] packetIdentifier The packet identifier associated with the subscription's - * SUBSCRIBE packet. - * @param[in] order The order of the subscription in the SUBSCRIBE packet. - * Pass `-1` to ignore order and remove all subscriptions for `packetIdentifier`. - */ -void AwsIotMqttInternal_RemoveSubscriptionByPacket( _mqttConnection_t * const pMqttConnection, - uint16_t packetIdentifier, - long order ); - -/** - * @brief Remove an array of subscriptions from the subscription manager by - * topic filter. - * - * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. - * @param[in] pSubscriptionList The first element in the array. - * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. - */ -void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * const pMqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount ); - -#endif /* ifndef _AWS_IOT_MQTT_INTERNAL_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 8bf988461c..c3d18be4f9 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -81,56 +81,56 @@ #if IOT_STATIC_MEMORY_ONLY == 1 #include "private/iot_static_memory.h" - /** - * @brief Allocate a #_shadowOperation_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate a #_shadowOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotShadow_MallocOperation #define AwsIotShadow_MallocOperation AwsIot_MallocShadowOperation #endif - /** - * @brief Free a #_shadowOperation_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free a #_shadowOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotShadow_FreeOperation #define AwsIotShadow_FreeOperation AwsIot_FreeShadowOperation #endif - /** - * @brief Allocate a buffer for a short string, used for topic names or client - * tokens. This function should have the same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate a buffer for a short string, used for topic names or client + * tokens. This function should have the same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotShadow_MallocString #define AwsIotShadow_MallocString Iot_MallocMessageBuffer #endif - /** - * @brief Free a string. This function should have the same signature as - * [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free a string. This function should have the same signature as + * [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotShadow_FreeString #define AwsIotShadow_FreeString Iot_FreeMessageBuffer #endif - /** - * @brief Allocate a #_shadowSubscription_t. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ +/** + * @brief Allocate a #_shadowSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ #ifndef AwsIotShadow_MallocSubscription #define AwsIotShadow_MallocSubscription AwsIot_MallocShadowSubscription #endif - /** - * @brief Free a #_shadowSubscription_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ +/** + * @brief Free a #_shadowSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ #ifndef AwsIotShadow_FreeSubscription #define AwsIotShadow_FreeSubscription AwsIot_FreeShadowSubscription #endif @@ -323,17 +323,17 @@ struct _shadowSubscription; * Currently, this is used to represent @ref mqtt_function_timedsubscribe or * @ref mqtt_function_timedunsubscribe. */ -typedef AwsIotMqttError_t ( * _mqttOperationFunction_t )( AwsIotMqttConnection_t, - const AwsIotMqttSubscription_t * const, - size_t, - uint32_t, - uint64_t ); +typedef IotMqttError_t ( * _mqttOperationFunction_t )( IotMqttConnection_t, + const IotMqttSubscription_t * const, + size_t, + uint32_t, + uint64_t ); /** * @brief Function pointer representing an MQTT library callback function. */ typedef void ( * _mqttCallbackFunction_t )( void *, - AwsIotMqttCallbackParam_t * const ); + IotMqttCallbackParam_t * const ); /** * @brief Enumerations representing each of the Shadow library's API functions. @@ -378,14 +378,14 @@ typedef enum _shadowOperationStatus */ typedef struct _shadowOperation { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ /* Basic operation information. */ - _shadowOperationType_t type; /**< @brief Operation type. */ - uint32_t flags; /**< @brief Flags passed to operation API function. */ - AwsIotShadowError_t status; /**< @brief Status of operation. */ + _shadowOperationType_t type; /**< @brief Operation type. */ + uint32_t flags; /**< @brief Flags passed to operation API function. */ + AwsIotShadowError_t status; /**< @brief Status of operation. */ - AwsIotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ + IotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ struct _shadowSubscription * pSubscription; /**< @brief Shadow subscriptions object associated with this operation. */ union @@ -427,9 +427,9 @@ typedef struct _shadowOperation */ typedef struct _shadowSubscription { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - int references[ _SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ + int32_t references[ _SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ AwsIotShadowCallbackInfo_t callbacks[ _SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ /** @@ -469,10 +469,10 @@ extern IotMutex_t _AwsIotShadowSubscriptionsMutex; * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY */ -AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** const pNewOperation, - _shadowOperationType_t operation, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo ); +AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** const pNewOperation, + _shadowOperationType_t operation, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo ); /** * @brief Free resources used to record a Shadow operation. This is called when @@ -482,7 +482,7 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** * `void*` to match the signature of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ -void AwsIotShadowInternal_DestroyOperation( void * pData ); +void _AwsIotShadow_DestroyOperation( void * pData ); /** * @brief Fill a buffer with a Shadow topic. @@ -503,11 +503,11 @@ void AwsIotShadowInternal_DestroyOperation( void * pData ); * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. This function * will not return #AWS_IOT_SHADOW_NO_MEMORY when a buffer is provided. */ -AwsIotShadowError_t AwsIotShadowInternal_GenerateShadowTopic( _shadowOperationType_t type, - const char * const pThingName, - size_t thingNameLength, - char ** const pTopicBuffer, - uint16_t * const pOperationTopicLength ); +AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t type, + const char * const pThingName, + size_t thingNameLength, + char ** const pTopicBuffer, + uint16_t * const pOperationTopicLength ); /** * @brief Process a Shadow operation by sending the necessary MQTT packets. @@ -522,11 +522,11 @@ AwsIotShadowError_t AwsIotShadowInternal_GenerateShadowTopic( _shadowOperationTy * @return #AWS_IOT_SHADOW_STATUS_PENDING on success. On error, one of * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. */ -AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_t mqttConnection, - const char * const pThingName, - size_t thingNameLength, - _shadowOperation_t * const pOperation, - const AwsIotShadowDocumentInfo_t * const pDocumentInfo ); +AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + _shadowOperation_t * const pOperation, + const AwsIotShadowDocumentInfo_t * const pDocumentInfo ); /** * @brief Notify of a completed Shadow operation. @@ -537,7 +537,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ * notification will cause @ref shadow_function_wait to return or invoke a * user-provided callback. */ -void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ); +void _AwsIotShadow_Notify( _shadowOperation_t * const pOperation ); /*---------------------- Shadow subscription functions ----------------------*/ @@ -554,8 +554,8 @@ void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ); * * @note This function should be called with the subscription list mutex locked. */ -_shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * const pThingName, - size_t thingNameLength ); +_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * const pThingName, + size_t thingNameLength ); /** * @brief Remove a Shadow subscription object from the subscription list if @@ -569,8 +569,8 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons * * @note This function should be called with the subscription list mutex locked. */ -void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSubscription, - _shadowSubscription_t ** const pRemovedSubscription ); +void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * const pSubscription, + _shadowSubscription_t ** const pRemovedSubscription ); /** * @brief Free resources used for a Shadow subscription object. @@ -579,7 +579,7 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub * `void*` to match the signature of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ -void AwsIotShadowInternal_DestroySubscription( void * pData ); +void _AwsIotShadow_DestroySubscription( void * pData ); /** * @brief Increment the reference count of a Shadow subscriptions object. @@ -602,10 +602,10 @@ void AwsIotShadowInternal_DestroySubscription( void * pData ); * * @note This function should be called with the subscription list mutex locked. */ -AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, - uint16_t operationTopicLength, - _mqttCallbackFunction_t callback ); +AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + uint16_t operationTopicLength, + _mqttCallbackFunction_t callback ); /** * @brief Decrement the reference count of a Shadow subscriptions object. @@ -626,9 +626,9 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t * * @note This function should be called with the subscription list mutex locked. */ -void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, - _shadowSubscription_t ** const pRemovedSubscription ); +void _AwsIotShadow_DecrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + _shadowSubscription_t ** const pRemovedSubscription ); /*------------------------- Shadow parser functions -------------------------*/ @@ -640,8 +640,8 @@ void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOpera * * @return Any #_shadowOperationStatus_t. */ -_shadowOperationStatus_t AwsIotShadowInternal_ParseShadowStatus( const char * const pTopicName, - size_t topicNameLength ); +_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * const pTopicName, + size_t topicNameLength ); /** * @brief Parse the Thing Name from a Shadow topic. @@ -653,10 +653,10 @@ _shadowOperationStatus_t AwsIotShadowInternal_ParseShadowStatus( const char * co * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_RESPONSE. */ -AwsIotShadowError_t AwsIotShadowInternal_ParseThingName( const char * const pTopicName, - uint16_t topicNameLength, - const char ** const pThingName, - size_t * const pThingNameLength ); +AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * const pTopicName, + uint16_t topicNameLength, + const char ** const pThingName, + size_t * const pThingNameLength ); /** * @brief Parse a Shadow error document. @@ -667,7 +667,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ParseThingName( const char * const pTop * @return One of the #AwsIotShadowError_t ranging from 400 to 500 on success. * #AWS_IOT_SHADOW_BAD_RESPONSE on error. */ -AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const pErrorDocument, - size_t errorDocumentLength ); +AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * const pErrorDocument, + size_t errorDocumentLength ); #endif /* ifndef _AWS_IOT_SHADOW_INTERNAL_H_ */ diff --git a/lib/include/private/iot_error.h b/lib/include/private/iot_error.h new file mode 100644 index 0000000000..b9129c73ab --- /dev/null +++ b/lib/include/private/iot_error.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_error.h + * @brief Provides macros for error checking and function cleanup. + * + * The macros in this file are generic. They may be customized by each library + * by setting the library prefix. + */ + +#ifndef _IOT_ERROR_H_ +#define _IOT_ERROR_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/** + * @brief Declare the status variable and an initial value. + * + * This macro should be at the beginning of any functions that use cleanup sections. + * + * @param[in] statusType The type of the status variable for this function. + * @param[in] initialValue The initial value to assign to the status variable. + */ +#define _IOT_FUNCTION_ENTRY( statusType, initialValue ) statusType status = initialValue + +/** + * @brief Declares the label that begins a cleanup section. + * + * This macro should be placed at the end of a function and followed by + * #_IOT_FUNCTION_CLEANUP_END. + */ +#define _IOT_FUNCTION_CLEANUP_BEGIN() iotCleanup: + +/** + * @brief Declares the end of a cleanup section. + * + * This macro should be placed at the end of a function and preceded by + * #_IOT_FUNCTION_CLEANUP_BEGIN. + */ +#define _IOT_FUNCTION_CLEANUP_END() return status + +/** + * @brief Declares an empty cleanup section. + * + * This macro should be placed at the end of a function to exit on error if no + * cleanup is required. + */ +#define _IOT_FUNCTION_EXIT_NO_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN(); _IOT_FUNCTION_CLEANUP_END() + +/** + * @brief Jump to the cleanup section. + */ +#define _IOT_GOTO_CLEANUP() goto iotCleanup + +/** + * @brief Assign a value to the status variable and jump to the cleanup section. + * + * @param[in] statusValue The value to assign to the status variable. + */ +#define _IOT_SET_AND_GOTO_CLEANUP( statusValue ) { status = ( statusValue ); _IOT_GOTO_CLEANUP(); } + +/** + * @brief Jump to the cleanup section if a condition is `false`. + * + * This macro may be used in place of `assert` to exit a function is a condition + * is `false`. + * + * @param[in] condition The condition to check. + */ +#define _IOT_GOTO_CLEANUP_IF_FALSE( condition ) { if( ( condition ) == false ) { _IOT_GOTO_CLEANUP(); } } + +/** + * @brief Assign a value to the status variable and jump to the cleanup section + * if a condition is `false`. + * + * @param[in] statusValue The value to assign to the status variable. + * @param[in] condition The condition to check. + */ +#define _IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( statusValue, condition ) \ + if( ( condition ) == false ) \ + _IOT_SET_AND_GOTO_CLEANUP( statusValue ) + +/** + * @brief Check a condition; if `false`, assign the "Bad parameter" status value + * and jump to the cleanup section. + * + * @param[in] libraryPrefix The library prefix of the status variable. + * @param[in] condition The condition to check. + */ +#define _IOT_VALIDATE_PARAMETER( libraryPrefix, condition ) \ + _IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( libraryPrefix ## _BAD_PARAMETER, condition ) + +#endif /* ifndef _IOT_ERROR_H_ */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h new file mode 100644 index 0000000000..9c0776650a --- /dev/null +++ b/lib/include/private/iot_mqtt_internal.h @@ -0,0 +1,938 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_internal.h + * @brief Internal header of MQTT library. This header should not be included in + * typical application code. + */ + +#ifndef _IOT_MQTT_INTERNAL_H_ +#define _IOT_MQTT_INTERNAL_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" + +/* Platform layer types include. */ +#include "types/iot_platform_types.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/* Task pool include. */ +#include "iot_taskpool.h" + +/** + * @def IotMqtt_Assert( expression ) + * @brief Assertion macro for the MQTT library. + * + * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_MQTT_ENABLE_ASSERTS == 1 + #ifndef IotMqtt_Assert + #include + #define IotMqtt_Assert( expression ) assert( expression ) + #endif +#else + #define IotMqtt_Assert( expression ) +#endif + +/* Configure logs for MQTT functions. */ +#ifdef IOT_LOG_LEVEL_MQTT + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "MQTT" ) +#include "iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Allocate an #_mqttConnection_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotMqtt_MallocConnection + #define IotMqtt_MallocConnection Iot_MallocMqttConnection + #endif + +/** + * @brief Free an #_mqttConnection_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotMqtt_FreeConnection + #define IotMqtt_FreeConnection Iot_FreeMqttConnection + #endif + +/** + * @brief Allocate memory for an MQTT packet. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotMqtt_MallocMessage + #define IotMqtt_MallocMessage Iot_MallocMessageBuffer + #endif + +/** + * @brief Free an MQTT packet. This function should have the same signature + * as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotMqtt_FreeMessage + #define IotMqtt_FreeMessage Iot_FreeMessageBuffer + #endif + +/** + * @brief Allocate an #_mqttOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotMqtt_MallocOperation + #define IotMqtt_MallocOperation Iot_MallocMqttOperation + #endif + +/** + * @brief Free an #_mqttOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotMqtt_FreeOperation + #define IotMqtt_FreeOperation Iot_FreeMqttOperation + #endif + +/** + * @brief Allocate an #_mqttSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotMqtt_MallocSubscription + #define IotMqtt_MallocSubscription Iot_MallocMqttSubscription + #endif + +/** + * @brief Free an #_mqttSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotMqtt_FreeSubscription + #define IotMqtt_FreeSubscription Iot_FreeMqttSubscription + #endif +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ + #include + + #ifndef IotMqtt_MallocConnection + #define IotMqtt_MallocConnection malloc + #endif + + #ifndef IotMqtt_FreeConnection + #define IotMqtt_FreeConnection free + #endif + + #ifndef IotMqtt_MallocMessage + #define IotMqtt_MallocMessage malloc + #endif + + #ifndef IotMqtt_FreeMessage + #define IotMqtt_FreeMessage free + #endif + + #ifndef IotMqtt_MallocOperation + #define IotMqtt_MallocOperation malloc + #endif + + #ifndef IotMqtt_FreeOperation + #define IotMqtt_FreeOperation free + #endif + + #ifndef IotMqtt_MallocSubscription + #define IotMqtt_MallocSubscription malloc + #endif + + #ifndef IotMqtt_FreeSubscription + #define IotMqtt_FreeSubscription free + #endif +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_MQTT_ENABLE_METRICS + #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) +#endif +#ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES + #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) +#endif +#ifndef IOT_MQTT_TEST + #define IOT_MQTT_TEST ( 0 ) +#endif +#ifndef IOT_MQTT_RESPONSE_WAIT_MS + #define IOT_MQTT_RESPONSE_WAIT_MS ( 1000 ) +#endif +#ifndef IOT_MQTT_RETRY_MS_CEILING + #define IOT_MQTT_RETRY_MS_CEILING ( 60000 ) +#endif +/** @endcond */ + +/** + * @brief Marks the empty statement of an `else` branch. + * + * Does nothing, but allows test coverage to detect branches not taken. By default, + * this is defined to nothing. When running code coverage testing, this is defined + * to an assembly NOP. + */ +#ifndef _EMPTY_ELSE_MARKER + #define _EMPTY_ELSE_MARKER +#endif + +/* + * Constants related to limits defined in AWS Service Limits. + * + * For details, see + * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html + * + * Used to validate parameters if when connecting to an AWS IoT MQTT server. + */ +#define _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ +#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ + +/* + * MQTT control packet type and flags. Always the first byte of an MQTT + * packet. + * + * For details, see + * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 + */ +#define _MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define _MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ +#define _MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ +#define _MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ +#define _MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ +#define _MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ +#define _MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ +#define _MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ + +/*---------------------- MQTT internal data structures ----------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declarations. + */ +struct _mqttSubscription; +struct _mqttConnection; +struct _mqttOperation; +struct _mqttTimerEvent; +/** @endcond */ + +/** + * @brief Represents a subscription stored in an MQTT connection. + */ +typedef struct _mqttSubscription +{ + IotLink_t link; /**< @brief List link member. */ + + int32_t references; /**< @brief How many subscription callbacks are using this subscription. */ + + /** + * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for + * this subscription. + * + * If there are active subscription callbacks, @ref mqtt_function_unsubscribe + * cannot remove this subscription. Instead, it will set this flag, which + * schedules the removal of this subscription once all subscription callbacks + * terminate. + */ + bool unsubscribed; + + struct + { + uint16_t identifier; /**< @brief Packet identifier. */ + size_t order; /**< @brief Order in the packet's list of subscriptions. */ + } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ + + IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ + + uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ + char pTopicFilter[]; /**< @brief The subscription topic filter. */ +} _mqttSubscription_t; + +/** + * @brief Internal structure to hold data on an MQTT connection. + */ +typedef struct _mqttConnection +{ + bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ + IotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + + bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ + IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ + int32_t references; /**< @brief Counts callbacks and operations using this connection. */ + IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ + IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ + + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to #_mqttConnection_t.subscriptionList. */ + + bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ + uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ + uint64_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ + IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ + uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ + size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ +} _mqttConnection_t; + +/** + * @brief Internal structure representing a single MQTT operation, such as + * CONNECT, SUBSCRIBE, PUBLISH, etc. + * + * Queues of these structures keeps track of all in-progress MQTT operations. + */ +typedef struct _mqttOperation +{ + /* Pointers to neighboring queue elements. */ + IotLink_t link; /**< @brief List link member. */ + + bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ + struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ + + IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ + + union + { + /* If incomingPublish is false, this struct is valid. */ + struct + { + /* Basic operation information. */ + int32_t jobReference; /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */ + IotMqttOperationType_t operation; /**< @brief What operation this structure represents. */ + uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ + uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ + + /* Serialized packet and size. */ + uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ + size_t packetSize; /**< @brief Size of `pMqttPacket`. */ + + /* How to notify of an operation's completion. */ + union + { + IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ + IotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ + } notify; /**< @brief How to notify of this operation's completion. */ + IotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ + + struct + { + uint32_t count; + uint32_t limit; + uint64_t nextPeriod; + } retry; + }; + + /* If incomingPublish is true, this struct is valid. */ + struct + { + IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ + struct _mqttOperation * pNextPublish; /**< @brief Pointer to the next PUBLISH in the data stream. */ + const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ + void ( * freeReceivedData )( void * ); /**< @brief Function called to free `pReceivedData`. */ + }; + }; +} _mqttOperation_t; + +/* Declaration of the MQTT task pool for internal files. */ +extern IotTaskPool_t _IotMqttTaskPool; + +/*-------------------- MQTT struct validation functions ---------------------*/ + +/** + * @brief Check that an #IotMqttNetIf_t is valid. + * + * @param[in] pNetworkInterface The #IotMqttNetIf_t to validate. + * + * @return `true` if `pNetworkInterface` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateNetIf( const IotMqttNetIf_t * pNetworkInterface ); + +/** + * @brief Check that an #IotMqttConnectInfo_t is valid. + * + * @param[in] pConnectInfo The #IotMqttConnectInfo_t to validate. + * + * @return `true` if `pConnectInfo` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ); + +/** + * @brief Check that an #IotMqttPublishInfo_t is valid. + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. + * + * @return `true` if `pPublishInfo` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pPublishInfo ); + +/** + * @brief Check that an #IotMqttReference_t is valid and waitable. + * + * @param[in] reference The #IotMqttReference_t to validate. + * + * @return `true` if `reference` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateReference( IotMqttReference_t reference ); + +/** + * @brief Check that a list of #IotMqttSubscription_t is valid. + * + * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. + * Some parameters are not validated for #IOT_MQTT_UNSUBSCRIBE. + * @param[in] awsIotMqttMode Specifies if this SUBSCRIBE packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pListStart First element of the list to validate. + * @param[in] listSize Number of elements in the subscription list. + * + * @return `true` if every element in the list is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, + bool awsIotMqttMode, + const IotMqttSubscription_t * pListStart, + size_t listSize ); + +/*-------------------- MQTT packet serializer functions ---------------------*/ + +/** + * @brief Initialize the MQTT packet serializer. Called by @ref mqtt_function_init + * when initializing the MQTT library. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_INIT_FAILED. + */ +IotMqttError_t _IotMqtt_InitSerialize( void ); + +/** + * @brief Free resources taken by #_IotMqtt_InitSerialize. + * + * No parameters, no return values. + */ +void _IotMqtt_CleanupSerialize( void ); + +/** + * @brief Get the MQTT packet type from a stream of bytes. + * + * @param[in] pPacket Pointer to the beginning of the byte stream. + * @param[in] packetSize Size of the byte stream. + * + * @return One of the server-to-client MQTT packet types. + * + * @note This function is only used for incoming packets, and may not work + * correctly for outgoing packets. + */ +uint8_t _IotMqtt_GetPacketType( const uint8_t * pPacket, + size_t packetSize ); + +/** + * @brief Generate a CONNECT packet from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[out] pConnectPacket Where the CONNECT packet is written. + * @param[out] pPacketSize Size of the packet written to `pConnectPacket`. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ); + +/** + * @brief Deserialize a CONNACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t. Also + * prints out debug log messages about the packet. + * @param[in] pConnackStart Pointer to the start of a CONNACK packet. + * @param[in] dataLength Length of the data stream after `pConnackStart`. + * @param[out] pBytesProcessed The number of bytes in the data stream processed + * by this function. For CONNACK, this is always 4. + * + * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; + * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; + * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, + size_t dataLength, + size_t * pBytesProcessed ); + +/** + * @brief Generate a PUBLISH packet from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information. + * @param[out] pPublishPacket Where the PUBLISH packet is written. + * @param[out] pPacketSize Size of the packet written to `pPublishPacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); + +/** + * @brief Set the DUP bit in a QoS 1 PUBLISH packet. + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify. + * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag, + * a new packet identifier is generated and should be written here. This parameter + * is only used when connected to an AWS IoT MQTT server. + * + * @note See #IotMqttPublishInfo_t for caveats with retransmission to the + * AWS IoT MQTT server. + */ +void _IotMqtt_PublishSetDup( bool awsIotMqttMode, + uint8_t * pPublishPacket, + uint16_t * pNewPacketIdentifier ); + +/** + * @brief Deserialize a PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #IotMqttPublishInfo_t and + * extracts the packet identifier. Also prints out debug log messages about the + * packet. + * + * @param[in] pPublishStart Pointer to the start of a PUBLISH packet. + * @param[in] dataLength Length of the data stream after `pPublishStart`. + * @param[out] pOutput Where the deserialized PUBLISH will be written. + * @param[out] pPacketIdentifier The packet identifier in the PUBLISH. + * @param[out] pBytesProcessed The number of bytes in the data stream processed + * by this function. + * + * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE + * if the PUBLISH packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, + size_t dataLength, + IotMqttPublishInfo_t * pOutput, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ); + +/** + * @brief Generate a PUBACK packet for the given packet identifier. + * + * @param[in] packetIdentifier The packet identifier to place in PUBACK. + * @param[out] pPubackPacket Where the PUBACK packet is written. + * @param[out] pPacketSize Size of the packet written to `pPubackPacket`. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ); + +/** + * @brief Deserialize a PUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in] pPubackStart Pointer to the start of a PUBACK packet. + * @param[in] dataLength Length of the data stream after `pPubackStart`. + * @param[out] pPacketIdentifier The packet identifier in the PUBACK. + * @param[out] pBytesProcessed The number of bytes in the data stream processed + * by this function. For PUBACK, this is always 4. + * + * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the PUBACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializePuback( const uint8_t * pPubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ); + +/** + * @brief Generate a SUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pSubscribePacket Where the SUBSCRIBE packet is written. + * @param[out] pPacketSize Size of the packet written to `pSubscribePacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a SUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in] mqttConnection The MQTT connection associated with the subscription. + * Rejected topic filters are removed from this connection. + * @param[in] pSubackStart Pointer to the start of a SUBACK packet. + * @param[in] dataLength Length of the data stream after `pSubackStart`. + * @param[out] pPacketIdentifier The packet identifier in the SUBACK. + * @param[out] pBytesProcessed The number of bytes in the data stream processed by + * this function. + * + * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the SUBACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, + const uint8_t * pSubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ); + +/** + * @brief Generate an UNSUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions to remove. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pUnsubscribePacket Where the UNSUBSCRIBE packet is written. + * @param[out] pPacketSize Size of the packet written to `pUnsubscribePacket`. + * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pUnsubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a UNSUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in] pUnsubackStart Pointer to the start of an UNSUBACK packet. + * @param[in] dataLength Length of the data stream after `pUnsubackStart`. + * @param[out] pPacketIdentifier The packet identifier in the UNSUBACK. + * @param[out] pBytesProcessed The number of bytes in the data stream processed by + * this function. For UNSUBACK, this is always 4. + * + * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the UNSUBACK packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ); + +/** + * @brief Generate a PINGREQ packet. + * + * @param[out] pPingreqPacket Where the PINGREQ packet is written. + * @param[out] pPacketSize Size of the packet written to `pPingreqPacket`. + * + * @return Always returns #IOT_MQTT_SUCCESS. + */ +IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t. Also + * prints out debug log messages about the packet. + * @param[in] pPingrespStart Pointer to the start of a PINGRESP packet. + * @param[in] dataLength Length of the data stream after `pPingrespStart`. + * @param[out] pBytesProcessed The number of bytes in the data stream processed by + * this function. For PINGRESP, this is always 2. + * + * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE + * if the PINGRESP packet doesn't follow MQTT spec. + */ +IotMqttError_t _IotMqtt_DeserializePingresp( const uint8_t * pPingrespStart, + size_t dataLength, + size_t * pBytesProcessed ); + +/** + * @brief Generate a DISCONNECT packet. + * + * @param[out] pDisconnectPacket Where the DISCONNECT packet is written. + * @param[out] pPacketSize Size of the packet written to `pDisconnectPacket`. + * + * @return Always returns #IOT_MQTT_SUCCESS. + */ +IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ); + +/** + * @brief Free a packet generated by the serializer. + * + * @param[in] pPacket The packet to free. + */ +void _IotMqtt_FreePacket( uint8_t * pPacket ); + +/*-------------------- MQTT operation record functions ----------------------*/ + +/** + * @brief Create a record for a new in-progress MQTT operation. + * + * @param[in] pMqttConnection The MQTT connection to associate with the operation. + * @param[in] flags Flags variable passed to a user-facing MQTT function. + * @param[in] pCallbackInfo User-provided callback function and parameter. + * @param[out] pNewOperation Set to point to the new operation on success. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_PARAMETER, or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + _mqttOperation_t ** pNewOperation ); + +/** + * @brief Decrement the job reference count of an MQTT operation and optionally + * cancel its job. + * + * Checks if the operation may be destroyed afterwards. + * + * @param[in] pOperation The MQTT operation with the job to cancel. + * @param[in] cancelJob Whether to attempt cancellation of the operation's job. + * + * @return `true` if the the operation may be safely destroyed; `false` otherwise. + */ +bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, + bool cancelJob ); + +/** + * @brief Free resources used to record an MQTT operation. This is called when + * the operation completes. + * + * @param[in] pOperation The operation which completed. + */ +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); + +/** + * @brief Task pool routine for processing an MQTT connection's keep-alive. + * + * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pKeepAliveJob Pointer the an MQTT connection's keep-alive job. + * @param[in] pContext Pointer to an MQTT connection, passed as an opaque context. + */ +void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pKeepAliveJob, + void * pContext ); + +/** + * @brief Task pool routine for processing an incoming PUBLISH message. + * + * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pPublishJob Pointer to the incoming PUBLISH operation's job. + * @param[in] pContext Pointer to the incoming PUBLISH operation, passed as an + * opaque context. + */ +void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pPublishJob, + void * pContext ); + +/** + * @brief Task pool routine for processing an MQTT operation to send. + * + * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pSendJob Pointer to an operation's job. + * @param[in] pContext Pointer to the operation to send, passed as an opaque + * context. + */ +void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pSendJob, + void * pContext ); + +/** + * @brief Task pool routine for processing a completed MQTT operation. + * + * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pOperationJob Pointer to the completed operation's job. + * @param[in] pContext Pointer to the completed operation, passed as an opaque + * context. + */ +void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pOperationJob, + void * pContext ); + +/** + * @brief Schedule an operation for immediate processing. + * + * @param[in] pOperation The operation to schedule. + * @param[in] jobRoutine The routine to run for the job. Must be either + * #_IotMqtt_ProcessSend, #_IotMqtt_ProcessCompletedOperation, or + * #_IotMqtt_ProcessIncomingPublish. + * @param[in] delay A delay before the operation job should be executed. Pass + * `0` to execute ASAP. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_SCHEDULING_ERROR. + */ +IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, + IotTaskPoolRoutine_t jobRoutine, + uint64_t delay ); + +/** + * @brief Search a list of MQTT operations pending responses using an operation + * name and packet identifier. Removes a matching operation from the list if found. + * + * @param[in] pMqttConnection The connection associated with the operation. + * @param[in] operation The operation type to look for. + * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore. + * + * @return Pointer to any matching operation; `NULL` if no match was found. + */ +_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, + IotMqttOperationType_t operation, + const uint16_t * pPacketIdentifier ); + +/** + * @brief Notify of a completed MQTT operation. + * + * @param[in] pOperation The MQTT operation which completed. + * + * Depending on the parameters passed to a user-facing MQTT function, the + * notification will cause @ref mqtt_function_wait to return or invoke a + * user-provided callback. + */ +void _IotMqtt_Notify( _mqttOperation_t * pOperation ); + +/*----------------- MQTT subscription management functions ------------------*/ + +/** + * @brief Add an array of subscriptions to the subscription manager. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] subscribePacketIdentifier Packet identifier for the subscriptions' + * SUBSCRIBE packet. + * @param[in] pSubscriptionList The first element in the array. + * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, + uint16_t subscribePacketIdentifier, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ); + +/** + * @brief Process a received PUBLISH from the server, invoking any subscription + * callbacks that have a matching topic filter. + * + * @param[in] pMqttConnection The MQTT connection associated with the received + * PUBLISH. + * @param[in] pCallbackParam The parameter to pass to a PUBLISH callback. + */ +void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, + IotMqttCallbackParam_t * pCallbackParam ); + +/** + * @brief Remove a single subscription from the subscription manager by + * packetIdentifier and order. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] packetIdentifier The packet identifier associated with the subscription's + * SUBSCRIBE packet. + * @param[in] order The order of the subscription in the SUBSCRIBE packet. + * Pass `-1` to ignore order and remove all subscriptions for `packetIdentifier`. + */ +void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier, + int32_t order ); + +/** + * @brief Remove an array of subscriptions from the subscription manager by + * topic filter. + * + * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. + * @param[in] pSubscriptionList The first element in the array. + * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. + */ +void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ); + +/*------------------ MQTT connection management functions -------------------*/ + +/** + * @brief Attempt to increment the reference count of an MQTT connection. + * + * @param[in] pMqttConnection The referenced MQTT connection. + * + * @return `true` if the reference count was incremented; `false` otherwise. The + * reference count will not be incremented for a disconnected connection. + */ +bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ); + +/** + * @brief Decrement the reference count of an MQTT connection. + * + * Also destroys an unreferenced MQTT connection. + * + * @param[in] pMqttConnection The referenced MQTT connection. + */ +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ); + +/** + * @brief Closes the network connection associated with an MQTT connection. + * + * A network disconnect function must be set in the network interface for the + * network connection to be closed. + * + * @param[in] pMqttConnection The MQTT connection with the network connection + * to close. + */ +void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ); + +#endif /* ifndef _IOT_MQTT_INTERNAL_H_ */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 8feba84898..402c0fb7e1 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -45,13 +45,15 @@ * - @functionname{static_memory_function_cleanup} * - @functionname{static_memory_function_messagebuffersize} * - @functionname{static_memory_function_mallocmessagebuffer} + * - @functionname{static_memory_function_malloctaskpooljob} + * - @functionname{static_memory_function_freetaskpooljob} + * - @functionname{static_memory_function_malloctaskpooltimerevent} + * - @functionname{static_memory_function_freetaskpooltimerevent} * - @functionname{static_memory_function_freemessagebuffer} * - @functionname{static_memory_function_mallocmqttconnection} * - @functionname{static_memory_function_freemqttconnection} * - @functionname{static_memory_function_mallocmqttoperation} * - @functionname{static_memory_function_freemqttoperation} - * - @functionname{static_memory_function_mallocmqtttimerevent} - * - @functionname{static_memory_function_freemqtttimerevent} * - @functionname{static_memory_function_mallocmqttsubscription} * - @functionname{static_memory_function_freemqttsubscription} * - @functionname{static_memory_function_mallocshadowoperation} @@ -161,6 +163,71 @@ void * Iot_MallocMessageBuffer( size_t size ); void Iot_FreeMessageBuffer( void * ptr ); /* @[declare_static_memory_freemessagebuffer] */ +/*------------------- Task pool job and event management --------------------*/ + +/** + * @functionpage{Iot_MallocTaskPoolJob,static_memory,malloctaskpooljob} + * @functionpage{Iot_FreeTaskPoolJob,static_memory,freetaskpooljob} + * @functionpage{Iot_MallocTaskPoolTimerEvent,static_memory,malloctaskpooltimerevent} + * @functionpage{Iot_FreeTaskPoolTimerEvent,static_memory,freetaskpooltimerevent} + */ + +/** + * @brief Allocates memory to hold data for a new task pool job. + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for task pool jobs. + * + * @param[in] size Must be equal to the size of a task pool job. + * + * @return Pointer to a task pool job. + */ +/* @[declare_static_memory_malloctaskpooljob] */ +void * Iot_MallocTaskPoolJob( size_t size ); +/* @[declare_static_memory_malloctaskpooljob] */ + +/** + * @brief Frees an in-use task pool job. + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for task pool jobs. + * + * @param[in] ptr Pointer to an active task pool job to free. + */ +/* @[declare_static_memory_freetaskpooljob] */ +void Iot_FreeTaskPoolJob( void * ptr ); +/* @[declare_static_memory_freetaskpooljob] */ + +/** + * @brief Allocates memory to hold data for a new task pool timer event. + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for task pool timer events. + * + * @param[in] size Must be equal to the size of a task pool timer event. + * + * @return Pointer to a task pool timer event. + */ +/* @[declare_static_memory_malloctaskpooltimerevent] */ +void * Iot_MallocTaskPoolTimerEvent( size_t size ); +/* @[declare_static_memory_malloctaskpooltimerevent] */ + +/** + * @brief Frees an in-use task pool timer event. + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for task pool timer events. + * + * @param[in] ptr Pointer to an active task pool timer event to free. + */ +/* @[declare_static_memory_freetaskpooltimerevent] */ +void Iot_FreeTaskPoolTimerEvent( void * ptr ); +/* @[declare_static_memory_freetaskpooltimerevent] */ + /*----------------------- MQTT connection management ------------------------*/ /** @@ -235,42 +302,6 @@ void * Iot_MallocMqttOperation( size_t size ); void Iot_FreeMqttOperation( void * ptr ); /* @[declare_static_memory_freemqttoperation] */ -/*----------------------- MQTT timer event management -----------------------*/ - -/** - * @functionpage{Iot_MallocMqttTimerEvent,static_memory,mallocmqtttimerevent} - * @functionpage{Iot_FreeMqttTimerEvent,static_memory,freemqtttimerevent} - */ - -/** - * @brief Allocates memory to hold data for a new [MQTT timer event] - * (@ref static_memory_types_mqtttimerevents). - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) for MQTT timer events. - * - * @param[in] size Must be equal to sizeof( #_mqttTimerEvent_t ). - * - * @return Pointer to an MQTT timer event. - */ -/* @[declare_static_memory_mallocmqtttimerevent] */ -void * Iot_MallocMqttTimerEvent( size_t size ); -/* @[declare_static_memory_mallocmqtttimerevent] */ - -/** - * @brief Frees an in-use [MQTT timer event] - * (@ref static_memory_types_mqtttimerevents). - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for MQTT timer events. - * - * @param[in] ptr Pointer to an active MQTT timer event to free. - */ -/* @[declare_static_memory_freemqtttimerevent] */ -void Iot_FreeMqttTimerEvent( void * ptr ); -/* @[declare_static_memory_freemqtttimerevent] */ - /*---------------------- MQTT subscription management -----------------------*/ /** diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h new file mode 100644 index 0000000000..1015475363 --- /dev/null +++ b/lib/include/private/iot_taskpool_internal.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_taskpool_internal.h + * @brief Internal header of task pool library. This header should not be included in + * typical application code. + */ + +#ifndef _IOT_TASKPOOL_INTERNAL_H_ +#define _IOT_TASKPOOL_INTERNAL_H_ + +/* Task pool include. */ +#include "private/iot_error.h" +#include "iot_taskpool.h" + +/* Establish a few convenience macros to handle errors in a standard way. */ + +/** + * @brief Every public API return an enumeration value with an undelying value of 0 in case of success. + */ +#define _TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) + +/** + * @brief Every public API returns an enumeration value with an undelying value different than 0 in case of success. + */ +#define _TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) + +/** + * @brief Jump to the cleanup area. + */ +#define _TASKPOOL_GOTO_CLEANUP() _IOT_GOTO_CLEANUP() + + /** + * @brief Declare the storage for the error status variable. + */ +#define _TASKPOOL_FUNCTION_ENTRY( result ) _IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) + +#define _TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL() return status + +/** + * @brief Check error and leave in case of failure. + */ +#define _TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( _TASKPOOL_FAILED( status = ( expr ) ) ) { _IOT_GOTO_CLEANUP(); } } + +/** + * @brief Set error and leave. + */ +#define _TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) _IOT_SET_AND_GOTO_CLEANUP( expr ) + +/** + * @brief Initialize error and declare start of cleanup area. + */ +#define _TASKPOOL_FUNCTION_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN( ) + +/** + * @brief Initialize error and declare end of cleanup area. + */ +#define _TASKPOOL_FUNCTION_CLEANUP_END() _IOT_FUNCTION_CLEANUP_END() + +/** + * @brief Create an empty cleanup area. + */ +#define _TASKPOOL_NO_FUNCTION_CLEANUP() _IOT_FUNCTION_EXIT_NO_CLEANUP() + +/** + * @brief Exit if an argument is NULL. + */ +#define _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) + +/** + * @brief Exit if an argument is NULL. + */ +#define _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) + + +/** + * @def IotTaskPool_Assert( expression ) + * @brief Assertion macro for the Task pool library. + * + * Set @ref IOT_TASKPOOL_ENABLE_ASSERTS to `1` to enable assertions in the Task pool + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_TASKPOOL_ENABLE_ASSERTS == 1 + #ifndef IotTaskPool_Assert + #include + #define IotTaskPool_Assert( expression ) assert( expression ) + #endif +#else + #define IotTaskPool_Assert( expression ) +#endif + +/* Configure logs for TASKPOOL functions. */ +#ifdef IOT_LOG_LEVEL_TASKPOOL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_TASKPOOL +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "TASKPOOL" ) +#include "iot_logging_setup.h" + +/** + * @brief Overridable allocator and deallocator: provide default values for undefined memory + * allocation functions based on the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Overridable allocator. + */ + #ifndef IotTaskPool_MallocJob + #define IotTaskPool_MallocJob Iot_MallocTaskPoolJob + #endif + + #ifndef IotTaskPool_MallocTimerEvent + #define IotTaskPool_MallocTimerEvent Iot_MallocTaskPoolTimerEvent + #endif + +/** + * @brief Overridable deallocator. + */ + #ifndef IotTaskPool_FreeJob + #define IotTaskPool_FreeJob Iot_FreeTaskPoolJob + #endif + + #ifndef IotTaskPool_FreeTimerEvent + #define IotTaskPool_FreeTimerEvent Iot_FreeTaskPoolTimerEvent + #endif + +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ + #include + +/** + * @brief Overridable allocator. + */ + #ifndef IotTaskPool_MallocJob + #define IotTaskPool_MallocJob malloc + #endif + + #ifndef IotTaskPool_MallocTimerEvent + #define IotTaskPool_MallocTimerEvent malloc + #endif + +/** + * @brief Overridable deallocator. + */ + #ifndef IotTaskPool_FreeJob + #define IotTaskPool_FreeJob free + #endif + + #ifndef IotTaskPool_FreeTimerEvent + #define IotTaskPool_FreeTimerEvent free + #endif + +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef IOT_TASKPOOL_JOBS_RECYCLE_LIMIT + #define IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ( 32 ) +#endif +/** @endcond */ + +/* ---------------------------------------------------------------------------------------------- */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * A few macros to manage task pool status. + */ +#define IOT_TASKPOOL_STATUS_MASK 0x0F /* Lower 4 bits reserved for status (IotTaskPoolJobStatus_t). */ +#define IOT_TASKPOOL_FLAGS_MASK 0xF0 /* Upper 4 bits reserved for flags. */ +#define IOT_TASK_POOL_INTERNAL_STATIC 0x80 /* Flag to mark a job as user-allocated. */ +/** @endcond */ + +/** + * @brief Represents an operation that is subject to a timer. + * + * These events are queued per MQTT connection. They are sorted by their + * expiration time. + */ +typedef struct _taskPoolTimerEvent +{ + IotLink_t link; /**< @brief List link member. */ + uint64_t expirationTime; /**< @brief When this event should be processed. */ + IotTaskPoolJob_t * pJob; /**< @brief The task pool job associated with this event. */ +} _taskPoolTimerEvent_t; + +#endif /* ifndef _IOT_TASKPOOL_INTERNAL_H_ */ diff --git a/lib/include/types/iot_platform_types.h b/lib/include/types/iot_platform_types.h index b1cb4f21b2..fbc59c065b 100644 --- a/lib/include/types/iot_platform_types.h +++ b/lib/include/types/iot_platform_types.h @@ -34,6 +34,16 @@ /*------------------------- Thread management types -------------------------*/ +/** + * @brief A value representing the system default for new thread priority. + */ +#define IOT_THREAD_DEFAULT_PRIORITY 0 + +/** + * @brief A value representhing the system default for new thread stack size. + */ +#define IOT_THREAD_DEFAULT_STACK_SIZE 0 + /** * @ingroup platform_datatypes_handles * @brief The type used to represent mutexes, configured with the type diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h new file mode 100644 index 0000000000..2821272949 --- /dev/null +++ b/lib/include/types/iot_taskpool_types.h @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_taskpool_types.h + * @brief Types of the task pool. + */ + +#ifndef _IOT_TASKPOOL_TYPES_H_ +#define _IOT_TASKPOOL_TYPES_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include + +/* Platform types includes. */ +#include "types/iot_platform_types.h" + +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" + +/*-------------------------- Task pool enumerated types --------------------------*/ + +/** + * @enums{taskpool,Task pool library} + */ + +/** + * @ingroup taskpool_datatypes_enums + * @brief Return codes of [task pool functions](@ref taskpool_functions). + */ +typedef enum IotTaskPoolError +{ + /** + * @brief Task pool operation completed successfully. + * + * Functions that may return this value: + * - @ref taskpool_function_createsystemtaskpool + * - @ref taskpool_function_create + * - @ref taskpool_function_destroy + * - @ref taskpool_function_setmaxthreads + * - @ref taskpool_function_createjob + * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_recyclejob + * - @ref taskpool_function_schedule + * - @ref taskpool_function_scheduledeferred + * - @ref taskpool_function_getstatus + * - @ref taskpool_function_trycancel + * + */ + IOT_TASKPOOL_SUCCESS = 0, + + /** + * @brief Task pool operation failed because at laest one parameter is invalid. + * + * Functions that may return this value: + * - @ref taskpool_function_createsystemtaskpool + * - @ref taskpool_function_create + * - @ref taskpool_function_destroy + * - @ref taskpool_function_setmaxthreads + * - @ref taskpool_function_createjob + * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_recyclejob + * - @ref taskpool_function_schedule + * - @ref taskpool_function_scheduledeferred + * - @ref taskpool_function_getstatus + * - @ref taskpool_function_trycancel + * + */ + IOT_TASKPOOL_BAD_PARAMETER, + + /** + * @brief Task pool operation failed because it is illegal. + * + * Functions that may return this value: + * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_recyclejob + * - @ref taskpool_function_schedule + * - @ref taskpool_function_scheduledeferred + * - @ref taskpool_function_trycancel + * + */ + IOT_TASKPOOL_ILLEGAL_OPERATION, + + /** + * @brief Task pool operation failed because allocating memory failed. + * + * Functions that may return this value: + * - @ref taskpool_function_createsystemtaskpool + * - @ref taskpool_function_create + * - @ref taskpool_function_setmaxthreads + * - @ref taskpool_function_createrecyclablejob + * - @ref taskpool_function_scheduledeferred + * - @ref taskpool_function_getstatus + * + */ + IOT_TASKPOOL_NO_MEMORY, + + /** + * @brief Task pool operation failed because of an invalid parameter. + * + * Functions that may return this value: + * - @ref taskpool_function_setmaxthreads + * - @ref taskpool_function_createrecyclablejob + * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_recyclejob + * - @ref taskpool_function_schedule + * - @ref taskpool_function_scheduledeferred + * - @ref taskpool_function_getstatus + * - @ref taskpool_function_trycancel + * + */ + IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS, + + /** + * @brief Task pool cancellation failed. + * + * Functions that may return this value: + * - @ref taskpool_function_trycancel + * + */ + IOT_TASKPOOL_CANCEL_FAILED, +} IotTaskPoolError_t; + +/** + * @enums{taskpool,Task pool library} + */ + +/** + * @ingroup taskpool_datatypes_enums + * @brief Status codes of [task pool Job](@ref IotTaskPoolJob_t). + * + */ +typedef enum IotTaskPoolJobStatus +{ + /** + * @brief Job is ready to be scheduled. + * + */ + IOT_TASKPOOL_STATUS_READY = 0, + + /** + * @brief Job has been queued for execution. + * + */ + IOT_TASKPOOL_STATUS_SCHEDULED, + + /** + * @brief Job has been scheduled for deferred execution. + * + */ + IOT_TASKPOOL_STATUS_DEFERRED, + + /** + * @brief Job is executing. + * + */ + IOT_TASKPOOL_STATUS_EXECUTING, + + /** + * @brief Job has been canceled before executing. + * + */ + IOT_TASKPOOL_STATUS_CANCELED, + + /** + * @brief Job status is undefined. + * + */ + IOT_TASKPOOL_STATUS_UNDEFINED, +} IotTaskPoolJobStatus_t; + +/** + * @cond DOXYGEN_IGNORE + * @brief Forward declarations for task pool types. + * + */ +struct IotTaskPool; +struct IotTaskPoolJob; +/** @endcond */ + +/*---------------------------- Task pool function pointer types ----------------------------*/ + +/** + * @functionpointers{taskpool,task pool library} + */ + +/** + * @ingroup taskpool_datatypes_functionpointers + * @brief Callback type for a user callback. + * + * This type identifies the user callback signature to execute a task pool job. This callback will be invoked + * by the task pool threads with the `pUserContext` parameter, as speficied by the user when + * calling @ref IotTaskPool_Schedule. + * + */ +typedef void ( * IotTaskPoolRoutine_t )( struct IotTaskPool * pTaskPool, + struct IotTaskPoolJob * pJob, + void * pUserContext ); + +/*------------------------- Task pool parameter structs --------------------------*/ + +/** + * @paramstructs{taskpool,task pool} + */ + +/** + * @ingroup taskpool_datatypes_paramstructs + * @brief Initialization information to create one task pool instance. + * + * @paramfor @ref taskpool_function_createsystemtaskpool @ref taskpool_function_create. + * + * Passed as an argument to @ref taskpool_function_create. + * + * @initializer{IotTaskPoolInfo_t,IOT_TASKPOOL_INFO_INITIALIZER} + */ +typedef struct IotTaskPoolInfo +{ + /** + * @brief Specifies the operating parameters for a task pool. + * + * @attention #IotTaskPoolInfo_t.minThreads MUST be at least 1. + * #IotTaskPoolInfo_t.maxThreads MUST be greater or equal to #IotTaskPoolInfo_t.minThreads. + * If the minimum number of threads is same as the maximum, then the task pool will not try and grow the + * number of worker threads at run time. + */ + + uint32_t minThreads; /**< @brief Minimum number of threads in a task pool. These threads will be created when the task pool is first created with @ref taskpool_function_create. */ + uint32_t maxThreads; /**< @brief Maximum number of threads in a task pool. A task pool may try and grow the number of active threads up to #AwsIotTaskPoolInfo_t.maxThreads. */ + uint32_t stackSize; /**< @brief Stack size for every task pool thread. The stack size for each thread is fixed after the task pool is created and cannot be changed. */ + uint32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ +} IotTaskPoolInfo_t; + +/*------------------------- Task pool handles structs --------------------------*/ + +/** + * @ingroup taskpool_datatypes_types + * @brief Task pool jobs cache. + * + * @warning This is a system-level data type that should not be modified. + * + */ +typedef struct IotTaskPoolCache +{ + IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ + uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ +} IotTaskPoolCache_t; + + +/** + * @ingroup taskpool_datatypes_types + * @brief The task pool data structure keeps track of the internal state and the signals for the dispatcher threads. + * The task pool is a thread safe data structure. + * + * @warning This is a system-level data type that should not be modified. + * + */ +typedef struct IotTaskPool +{ + IotQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ + IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ + IotTaskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ + uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ + uint32_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ + uint32_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ + uint32_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ + size_t stackSize; /**< @brief The stack size for all task pool threads. */ + int32_t priority; /**< @brief The priority for all task pool threads. */ + IotSemaphore_t dispatchSignal; /**< @brief The synchronization object on which threads are waiting for incoming jobs. */ + IotSemaphore_t startStopSignal; /**< @brief The synchronization object for threads to signal start and stop condition. */ + IotTimer_t timer; /**< @brief The timer for deferred jobs. */ + IotMutex_t lock; /**< @brief The lock to protect the task pool data structure access. */ +} IotTaskPool_t; + +/** + * @ingroup taskpool_datatypes_types + * @brief The job data structure keeps track of the user callback and context, as well as the status of the job. + * + * @warning This is a system-level data type that should not be modified. + * + */ +typedef struct IotTaskPoolJob +{ + IotTaskPoolRoutine_t userCallback; /**< @brief The user provided callback. */ + void * pUserContext; /**< @brief The user provided context. */ + IotLink_t link; /**< @brief The link to insert the job in the dispatch queue. */ + IotTaskPoolJobStatus_t status; /**< @brief The status for the job. */ +} IotTaskPoolJob_t; + +/*------------------------- TASKPOOL defined constants --------------------------*/ + +/** + * @constantspage{taskpool,task pool library} + * + * @section taskpool_constants_initializers task pool Initializers + * @brief Provides default values for initializing the data types of the task pool library. + * + * @snippet this define_taskpool_initializers + * + * All user-facing data types of the task pool library can be initialized using + * one of the following. + * + * @warning Failure to initialize a task pool data type with the appropriate initializer + * may result in a runtime error! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * + * IotTaskPool_t * pTaskPool; + * + * const IotTaskPoolInfo_t tpInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; + * + * IotTaskPoolError_t error = IotTaskPool_Create( &tpInfo, &pTaskPool ); + * + * // Use the task pool + * // ... + * + * @endcode + * + */ +/* @[define_taskpool_initializers] */ +#define IOT_TASKPOOL_INFO_INITIALIZER_SMALL { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a small #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a medium #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_LARGE { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a large #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_XLARGE { .minThreads = 2, .maxThreads = 4, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a very large #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM /**< @brief Initializer for a typical #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INITIALIZER { 0 } /**< @brief Initializer for a #IotTaskPoolJob_t. */ +/* @[define_taskpool_initializers] */ + +/** + * @brief Allows the use of the handle to the system task pool. + * + * @warning The task pool handle is not valid unless @ref taskpool_function_createsystemtaskpool is + * called before the handle is used. + */ +#define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) + +#endif /* ifndef _IOT_TASKPOOL_TYPES_H_ */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 94516afb8f..5507670b73 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -3,7 +3,9 @@ add_library( iotcommon SHARED iot_common.c iot_json_utils.c iot_logging.c + iot_taskpool.c static_memory/iot_static_memory_common.c + static_memory/iot_static_memory_taskpool.c static_memory/iot_static_memory_mqtt.c static_memory/aws_iot_static_memory_shadow.c ) diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c index 29d6bb4a59..dbb506cc48 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_common.c @@ -27,10 +27,11 @@ /* Common include. */ #include "iot_common.h" +/* Task pool include. */ +#include "iot_taskpool.h" + /* Static memory include (if dynamic memory allocation is disabled). */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" -#endif +#include "private/iot_static_memory.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_GLOBAL @@ -48,6 +49,19 @@ bool IotCommon_Init( void ) { bool status = true; + /* Create system task pool. */ + if( status == true ) + { + IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; + + if( IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) != IOT_TASKPOOL_SUCCESS ) + { + IotLogError( "Failed to create system task pool." ); + + status = false; + } + } + /* Initialize static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 status = IotStaticMemory_Init(); @@ -55,6 +69,7 @@ bool IotCommon_Init( void ) if( status == false ) { IotLogError( "Failed to initialize static memory." ); + IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); } #endif @@ -75,6 +90,8 @@ void IotCommon_Cleanup( void ) IotStaticMemory_Cleanup(); #endif + IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); + IotLogInfo( "Common libraries cleanup done." ); } diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c new file mode 100644 index 0000000000..78e022da35 --- /dev/null +++ b/lib/source/common/iot_taskpool.c @@ -0,0 +1,1553 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_taskpool.c + * @brief Implements the task pool functions in iot_taskpool.h + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* Platform layer includes. */ +#include "platform/iot_threads.h" +#include "platform/iot_clock.h" + +/* Task pool internal include. */ +#include "private/iot_taskpool_internal.h" + +/** + * @brief Enter a critical section by locking a mutex. + * + */ +#define _TASKPOOL_ENTER_CRITICAL_SECTION IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) + +/** + * @brief Try entering a critical section by trying and lock a mutex. + * + */ +#define _TASKPOOL_TRY_ENTER_CRITICAL_SECTION IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) + +/** + * @brief Exit a critical section by unlocking a mutex. + * + */ +#define _TASKPOOL_EXIT_CRITICAL_SECTION IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) + +/** + * @brief Maximum semaphore value for wait operations. + */ +#define _TASKPOOL_MAX_SEM_VALUE 0xFFFF + +/** + * @brief Reschedule delay in milliseconds for deferred jobs. + */ +#define _TASKPOOL_JOB_RESCHEDULE_DELAY_MS 10 + +/* ---------------------------------------------------------------------------------------------- */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * @brief The system task pool handle for all libraries to use. + * User application can use the system task pool as well knowing that the usage will be shared with + * the system libraries as well. The system task pool needs to be initialized before any library is used or + * before any code that posts jobs to the task pool runs. + */ +IotTaskPool_t _IotSystemTaskPool = { 0 }; + +/** @endcond */ + +/* ---------------------------------------------------------------------------------------------- */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + */ + +/* -------------- Convenience functions to create/recycle/destroy jobs -------------- */ + +/** + * @brief Initializes one instance of a Task pool cache. + * + * @param[in] pCache The pre-allocated instance of the cache to initialize. + */ +static void _initJobsCache( IotTaskPoolCache_t * const pCache ); + +/** + * @brief Extracts and initializes one instance of a job from the cache or, if there is none available, it allocates and initialized a new one. + * + * @param[in] pCache The instance of the cache to extract the job from. + */ +static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache ); + +/** + * Recycles one instance of a job into the cache or, if the cache is full, it destroys it. + * + * @param[in] pCache The instance of the cache to recycle the job into. + * @param[in] pJob The job to recycle. + * + */ +static void _recycleJob( IotTaskPoolCache_t * const pCache, + IotTaskPoolJob_t * const pJob ); + +/** + * Destroys one instance of a job. + * + * @param[in] pJob The job to destroy. + * + */ +static void _destroyJob( IotTaskPoolJob_t * const pJob ); + +/* -------------- The worker thread procedure for a task pool thread -------------- */ + +/** + * The procedure for a task pool worker thread. + * + * @param[in] pUserContext The user context. + * + */ +static void _taskPoolWorker( void * pUserContext ); + +/* -------------- Convenience functions to handle timer events -------------- */ + +/** + * Comparer for the time list. + * + * param[in] pTimerEventLink1 The link to the first timer event. + * param[in] pTimerEventLink1 The link to the first timer event. + */ +static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, + const IotLink_t * const pTimerEventLink2 ); + +static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, + _taskPoolTimerEvent_t * const pFirstTimerEvent ); + +static void _timerThread( void * pArgument ); + +/* -------------- Convenience functions to create/initialize/destroy the task pool -------------- */ + +/** + * Initializes a pre-allocated instance of a task pool. + * + * @param[in] pInfo The initialization information for the task pool. + * @param[in] pTaskPool The pre-allocated instance of the task pool to initialize. + * + */ +static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, + IotTaskPool_t * const pTaskPool ); + +/** + * Initializes a pre-allocated instance of a task pool. + * + * @param[in] pInfo The initialization information for the task pool. + * @param[in] pTaskPoolBuffer A storage to build the task pool when staic allocation is chosen. + * @param[out] pTaskPool The handle to the created task pool. + * + */ +static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, + IotTaskPool_t * const ppTaskPool ); + +/** + * Destroys one instance of a task pool. + * + * @param[in] pTaskPool The task pool to destroy. + * + */ +static void _destroyTaskPool( IotTaskPool_t * const pTaskPool ); + +/** + * Check for the exit condition. + * + * @param[in] pTaskPool The task pool to destroy. + * + */ +static bool _IsShutdownStarted( const IotTaskPool_t * const pTaskPool ); + +/** + * Set the exit condition. + * + * @param[in] pTaskPool The task pool to destroy. + * + */ +static void _signalShutdown( IotTaskPool_t * const pTaskPool, + uint32_t threads ); + +/** + * Places a job in the dispatch queue. + * + * @param[in] pTaskPool The task pool to scheduel the job with. + * @param[in] pJob The job to schedule. + * + */ +static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ); + +/** + * Matches a deferred job in the timer queue with its timer event wrapper. + * + * @param[in] pLink A pointer to the timer event link in the timer queue. + * @param[in] pMatch A pointer to the job to match. + * + */ +static bool _matchJobByPointer( const IotLink_t * pLink, + void * pMatch ); + +/** + * Tries to cancel a job. + * + * @param[in] pTaskPool The task pool to cancel an operation against. + * @param[in] pJob The job to cancel. + * @param[out] pStatus The status of the job at the time of cancellation. + * + */ +static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + IotTaskPoolJobStatus_t * const pStatus ); + +/** + * Try to safely cancel and/or remove a job from the cache when the user calls API out of order. + * + * @param[in] pTaskPool The task pool to safely extract a job from. + * @param[in] pJob The job to extract. + * + */ +static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + bool checkExecutionInProgress ); +/** @endcond */ + +/* ---------------------------------------------------------------------------------------------- */ + +IotTaskPool_t * IotTaskPool_GetSystemTaskPool() +{ + return &_IotSystemTaskPool; +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Create( pInfo, &_IotSystemTaskPool ) ); + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, + IotTaskPool_t * const pTaskPool ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + + _TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTaskPool ) ); + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + uint32_t count; + + /* Track how many threads the task pool owns. */ + uint32_t activeThreads; + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + + /* Destroying the task pool should be safe, and therefore we will grab the task pool lock. + * No worker thread or application thread should access any data structure + * in the task pool while the task pool is being destroyed. */ + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + IotLink_t * pItemLink; + + /* Record how many active threads in the task pool. */ + activeThreads = pTaskPool->activeThreads; + + /* Destroying a Task pool happens in six (6) stages: First, (1) we clear the job queue and (2) the timer queue. + * Then (3) we clear the jobs cache. We will then (4) wait for all worker threads to signal exit, + * before (5) setting the exit condition and wake up all active worker threads. Finally (6) destroying + * all task pool data structures and release the associated memory. + */ + + /* (1) Clear the job queue. */ + do + { + pItemLink = NULL; + + pItemLink = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + + if( pItemLink != NULL ) + { + IotTaskPoolJob_t * pJob = IotLink_Container( IotTaskPoolJob_t, pItemLink, link ); + + _destroyJob( pJob ); + } + } while( pItemLink ); + + /* (2) Clear the timer queue. */ + do + { + pItemLink = NULL; + + pItemLink = IotListDouble_RemoveHead( &pTaskPool->timerEventsList ); + + if( pItemLink != NULL ) + { + _taskPoolTimerEvent_t * pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pItemLink, link ); + + _destroyJob( pTimerEvent->pJob ); + + IotTaskPool_FreeTimerEvent( pTimerEvent ); + } + } while( pItemLink ); + + /* (3) Clear the job cache. */ + do + { + pItemLink = NULL; + + pItemLink = IotListDouble_RemoveHead( &pTaskPool->jobsCache.freeList ); + + if( pItemLink != NULL ) + { + IotTaskPoolJob_t * pJob = IotLink_Container( IotTaskPoolJob_t, pItemLink, link ); + + _destroyJob( pJob ); + } + } while( pItemLink ); + + /* (4) Set the exit condition. */ + _signalShutdown( pTaskPool, activeThreads ); + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + /* (5) Wait for all active threads to reach the end of their life-span. */ + for( count = 0; count < activeThreads; ++count ) + { + IotSemaphore_Wait( &pTaskPool->startStopSignal ); + } + + IotTaskPool_Assert( pTaskPool->activeThreads == 0 ); + IotTaskPool_Assert( IotSemaphore_GetCount( &pTaskPool->startStopSignal ) == 0 ); + + /* (6) Destroy all signaling objects. */ + _destroyTaskPool( pTaskPool ); + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, + uint32_t maxThreads ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < 1 ); + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + uint32_t currentMaxThreads = pTaskPool->maxThreads; + + /* Reset the max threads counter. */ + pTaskPool->maxThreads = maxThreads; + + /* If the number of maximum threads in the pool is set to be smaller than the current value, + * then we need to signal all redundant to exit. + */ + if( maxThreads < currentMaxThreads ) + { + uint32_t count = currentMaxThreads - maxThreads; + + while( count-- > 0 ) + { + IotSemaphore_Post( &pTaskPool->dispatchSignal ); + } + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallback, + void * const pUserContext, + IotTaskPoolJob_t * const pJob ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + + /* Build a job around the user-provided storage. */ + pJob->link.pNext = NULL; + pJob->link.pPrevious = NULL; + pJob->userCallback = userCallback; + pJob->pUserContext = pUserContext; + pJob->status = IOT_TASKPOOL_STATUS_READY | IOT_TASK_POOL_INTERNAL_STATIC; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskPool, + const IotTaskPoolRoutine_t userCallback, + void * const pUserContext, + IotTaskPoolJob_t ** const ppJob ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); + + { + IotTaskPoolJob_t * pTempJob = NULL; + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + pTempJob = _fetchOrAllocateJob( &pTaskPool->jobsCache ); + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + /* Initialize all members. */ + if( pTempJob == NULL ) + { + IotLogInfo( "Failed to allocate a job." ); + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + + pTempJob->link.pNext = NULL; + pTempJob->link.pPrevious = NULL; + pTempJob->userCallback = userCallback; + pTempJob->pUserContext = pUserContext; + pTempJob->status = IOT_TASKPOOL_STATUS_READY; + + *ppJob = pTempJob; + } + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_DestroyJob( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + else + { + status = _trySafeExtraction( pTaskPool, pJob, false ); + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + if( _TASKPOOL_SUCCEEDED( status ) ) + { + /* At this point, the job must not be in any queue or list. */ + IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); + + _destroyJob( pJob ); + } + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + /* Do not recycle statically allocated jobs. */ + if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ) + { + status = _trySafeExtraction( pTaskPool, pJob, false ); + } + else + { + IotLogWarn( "Attempt to recycle a statically allocated job." ); + + status = IOT_TASKPOOL_ILLEGAL_OPERATION; + } + + /* If all safety checks completed, proceed. */ + if( _TASKPOOL_SUCCEEDED( status ) ) + { + _recycleJob( &pTaskPool->jobsCache, pJob ); + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + else + { + status = _trySafeExtraction( pTaskPool, pJob, true ); + } + + /* If all safety checks completed, proceed. */ + if( _TASKPOOL_SUCCEEDED( status ) ) + { + status = _scheduleInternal( pTaskPool, pJob ); + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + uint64_t timeMs ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + + if( timeMs == 0 ) + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob ) ); + } + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + /* If all safety checks completed, proceed. */ + if( _TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, true ) ) ) + { + _taskPoolTimerEvent_t * pTimerEvent = IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); + + if( pTimerEvent == NULL ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + IotLink_t * pTimerEventLink; + + uint64_t now = IotClock_GetTimeMs(); + + pTimerEvent->link.pNext = NULL; + pTimerEvent->link.pPrevious = NULL; + pTimerEvent->expirationTime = now + timeMs; + pTimerEvent->pJob = pJob; + + /* Append the timer event to the timer list. */ + IotListDouble_InsertSorted( &pTaskPool->timerEventsList, &pTimerEvent->link, _timerEventCompare ); + + /* Update the job status to 'scheduled'. */ + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_DEFERRED; + + /* Peek the first event in the timer event list. There must be at least one, + * since we just inserted it. */ + pTimerEventLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); + IotTaskPool_Assert( pTimerEventLink != NULL ); + + /* If the event we inserted is at the front of the queue, then + * we need to reschedule the underlying timer. */ + if( pTimerEventLink == &pTimerEvent->link ) + { + pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ); + + _rescheduleDeferredJobsTimer( &pTaskPool->timer, pTimerEvent ); + } + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, + const IotTaskPoolJob_t * const pJob, + IotTaskPoolJobStatus_t * const pStatus ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Bail out early if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + *pStatus = ( pJob->status & IOT_TASKPOOL_STATUS_MASK ); + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + IotTaskPoolJobStatus_t * const pStatus ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + + if( pStatus != NULL ) + { + *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; + } + + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Check again if this task pool is shutting down. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + } + + status = _tryCancelInternal( pTaskPool, pJob, pStatus ); + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +const char * IotTaskPool_strerror( IotTaskPoolError_t status ) +{ + const char * pMessage = NULL; + + switch( status ) + { + case IOT_TASKPOOL_SUCCESS: + pMessage = "SUCCESS"; + break; + case IOT_TASKPOOL_BAD_PARAMETER: + pMessage = "BAD PARAMETER"; + break; + case IOT_TASKPOOL_ILLEGAL_OPERATION: + pMessage = "ILLEGAL OPERATION"; + break; + case IOT_TASKPOOL_NO_MEMORY: + pMessage = "NO MEMORY"; + break; + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + pMessage = "SHUTDOWN IN PROGRESS"; + break; + case IOT_TASKPOOL_CANCEL_FAILED: + pMessage = "CANCEL FAILED"; + break; + default: + pMessage = "INVALID STATUS"; + break; + } + + return pMessage; +} + +/* ---------------------------------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------------------------------- */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + */ +static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, + IotTaskPool_t * const pTaskPool ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + uint32_t count; + uint32_t threadsCreated = 0; + + /* Check input values for consistency. */ + /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < 1 ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < 1 ); + + /* Initialize all internal data structure prior to creating all threads. */ + _TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); + + /* Create the timer mutex for a new connection. */ + if( IotClock_TimerCreate( &( pTaskPool->timer ), _timerThread, pTaskPool ) == false ) + { + IotLogError( "Failed to create timer for task pool." ); + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + + IotTaskPool_Assert( pInfo->minThreads == pTaskPool->minThreads ); + IotTaskPool_Assert( pInfo->maxThreads == pTaskPool->maxThreads ); + + /* The task pool will initialize the minimum number of threads reqeusted by the user upon start. */ + /* When a thread is created, it will signal a semaphore to signify that it is about to wait on incoming */ + /* jobs. A thread can be woken up for exit or for new jobs only at that point in time. */ + /* The exit condition is setting the maximum number of threads to 0. */ + + /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ + for( ; threadsCreated < pTaskPool->minThreads; ) + { + /* Create one thread. */ + if( Iot_CreateDetachedThread( _taskPoolWorker, + pTaskPool, + pTaskPool->priority, + pTaskPool->stackSize ) == false ) + { + IotLogError( "Could not create worker thread! Exiting..." ); + + /* If creating one thread fails, set error condition and exit the loop. */ + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + + /* Upon successful thread creation, increase the number of active threads. */ + pTaskPool->activeThreads++; + + ++threadsCreated; + } + + _TASKPOOL_FUNCTION_CLEANUP(); + + /* Wait for threads to be ready to wait on the condition, so that threads are actually able to receive messages. */ + for( count = 0; count < threadsCreated; ++count ) + { + IotSemaphore_Wait( &pTaskPool->startStopSignal ); + } + + /* In case of failure, wait on the created threads to exit. */ + if( _TASKPOOL_FAILED( status ) ) + { + /* Set the exit condition for the newly created threads. */ + _signalShutdown( pTaskPool, threadsCreated ); + + /* Signal all threads to exit. */ + for( count = 0; count < threadsCreated; ++count ) + { + IotSemaphore_Wait( &pTaskPool->startStopSignal ); + } + + _destroyTaskPool( pTaskPool ); + } + + _TASKPOOL_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, + IotTaskPool_t * const pTaskPool ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + bool semStartStopInit = false; + bool lockInit = false; + bool semDispatchInit = false; + + /* Zero out all data structures. */ + memset( ( void * ) pTaskPool, 0x00, sizeof( IotTaskPool_t ) ); + + /* Initialize a job data structures that require no de-initialization. + * All other data structures carry a value of 'NULL' before initailization. + */ + IotQueue_Create( &pTaskPool->dispatchQueue ); + IotListDouble_Create( &pTaskPool->timerEventsList ); + + pTaskPool->minThreads = pInfo->minThreads; + pTaskPool->maxThreads = pInfo->maxThreads; + pTaskPool->stackSize = pInfo->stackSize; + pTaskPool->priority = pInfo->priority; + + _initJobsCache( &pTaskPool->jobsCache ); + + /* Initialize the semaphore to ensure all threads have started. */ + if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, _TASKPOOL_MAX_SEM_VALUE ) ) + { + semStartStopInit = true; + + if( IotMutex_Create( &pTaskPool->lock, true ) ) + { + lockInit = true; + + /* Initialize the semaphore for waiting for incoming work. */ + if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, _TASKPOOL_MAX_SEM_VALUE ) ) + { + semDispatchInit = true; + } + else + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + } + else + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + } + else + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + + _TASKPOOL_FUNCTION_CLEANUP(); + + if( _TASKPOOL_FAILED( status ) ) + { + if( semStartStopInit ) + { + IotSemaphore_Destroy( &pTaskPool->startStopSignal ); + } + + if( lockInit ) + { + IotMutex_Destroy( &pTaskPool->lock ); + } + + if( semDispatchInit ) + { + IotSemaphore_Destroy( &pTaskPool->dispatchSignal ); + } + } + + _TASKPOOL_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +static void _destroyTaskPool( IotTaskPool_t * const pTaskPool ) +{ + IotClock_TimerDestroy( &pTaskPool->timer ); + IotSemaphore_Destroy( &pTaskPool->dispatchSignal ); + IotSemaphore_Destroy( &pTaskPool->startStopSignal ); + IotMutex_Destroy( &pTaskPool->lock ); +} + +/* ---------------------------------------------------------------------------------------------- */ + +static void _taskPoolWorker( void * pUserContext ) +{ + /* Extract pTaskPool pointer from context. */ + IotTaskPool_Assert( pUserContext != NULL ); + IotTaskPool_t * pTaskPool = ( IotTaskPool_t * ) pUserContext; + + /* Signal that this worker completed initialization and it is ready to receive notifications. */ + IotSemaphore_Post( &pTaskPool->startStopSignal ); + + /* OUTER LOOP: it controls the lifetiem of the worker thread: exit condition for a worker thread + * is setting maxThreads to zero. A worker thread is running until the maximum number of allowed + * threads is not zero and the active threads are less than the maximum number of allowed threads. + */ + for( ; ; ) + { + IotLink_t * pFirst = NULL; + IotTaskPoolJob_t * pJob = NULL; + bool shouldExit = false; + + /* Wait on incoming notifications... */ + IotSemaphore_Wait( &pTaskPool->dispatchSignal ); + + /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified, + * or before waiting for incoming notifications. + */ + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* If the exit condition is verified, update the number of active threads and exit the loop. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + shouldExit = true; + + IotLogDebug( "Worker thread exiting because exit condition was set." ); + } + else if( pTaskPool->activeThreads > pTaskPool->maxThreads ) + { + shouldExit = true; + + IotLogDebug( "Worker thread exiting because maximum quota was exceeded." ); + } + + /* Check if thread should exit. */ + if( shouldExit ) + { + /* Decrease the number of active threads. */ + pTaskPool->activeThreads--; + + _TASKPOOL_EXIT_CRITICAL_SECTION; + + /* Signal that this worker is exiting. */ + IotSemaphore_Post( &pTaskPool->startStopSignal ); + + /* Abandon the OUTER LOOP. */ + break; + } + + /* Dequeue the first job in FIFO order. */ + pFirst = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + + /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */ + if( pFirst != NULL ) + { + /* Extract the job from its link. */ + pJob = IotLink_Container( IotTaskPoolJob_t, pFirst, link ); + + /* Update status to 'executing'. */ + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_EXECUTING; + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + + /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */ + while( pJob != NULL ) + { + /* Record callback, so job can be re-used in the callback. */ + IotTaskPoolRoutine_t userCallback = pJob->userCallback; + + /* Process the job by invoking the associated callback with the user context. + * This task pool thread will not be available until the user callback returns. + */ + { + IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); + + userCallback( pTaskPool, pJob, pJob->pUserContext ); + + /* This job is finished, clear its pointer. */ + pJob = NULL; + } + + /* Acquire the lock before updating the job status. */ + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ + pTaskPool->activeJobs--; + + /* Try and dequeue the next job in the dispatch queue. */ + IotLink_t * pItem = NULL; + + /* Dequeue the next job from the dispatch queue. */ + pItem = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + + /* If there is no job left in the dispatch queue, update the worker status and leave. */ + if( pItem == NULL ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ + break; + } + else + { + pJob = IotLink_Container( IotTaskPoolJob_t, pItem, link ); + } + + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_EXECUTING; + } + _TASKPOOL_EXIT_CRITICAL_SECTION; + } + } +} + +/* ---------------------------------------------------------------------------------------------- */ + +static void _initJobsCache( IotTaskPoolCache_t * const pCache ) +{ + IotQueue_Create( &pCache->freeList ); + + pCache->freeCount = 0; +} + +/*-----------------------------------------------------------*/ + +static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache ) +{ + IotTaskPoolJob_t * pJob = NULL; + IotLink_t * pLink = IotListDouble_RemoveHead( &( pCache->freeList ) ); + + if( pLink != NULL ) + { + pJob = IotLink_Container( IotTaskPoolJob_t, pLink, link ); + } + + /* If there is no available job in the cache, then allocate one. */ + if( pJob == NULL ) + { + pJob = ( IotTaskPoolJob_t * ) IotTaskPool_MallocJob( sizeof( IotTaskPoolJob_t ) ); + + if( pJob != NULL ) + { + memset( pJob, 0x00, sizeof( IotTaskPoolJob_t ) ); + } + else + { + IotLogInfo( "Failed to allocate job." ); + } + } + /* If there was a job in the cache, then make sure we keep the counters up-to-date. */ + else + { + IotTaskPool_Assert( pCache->freeCount > 0 ); + + pCache->freeCount--; + } + + return pJob; +} + +/*-----------------------------------------------------------*/ + +static void _recycleJob( IotTaskPoolCache_t * const pCache, + IotTaskPoolJob_t * const pJob ) +{ + /* We should never try and recycling a job that is linked into some queue. */ + IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); + + /* We will recycle the job if there is space in the cache. */ + if( pCache->freeCount < IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ) + { + /* Destroy user data, for added safety&security. */ + pJob->userCallback = NULL; + pJob->pUserContext = NULL; + + /* Reset the status for added debuggability. */ + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_UNDEFINED; + + IotListDouble_InsertTail( &pCache->freeList, &pJob->link ); + + pCache->freeCount++; + + IotTaskPool_Assert( pCache->freeCount >= 1 ); + } + else + { + _destroyJob( pJob ); + } +} + +/*-----------------------------------------------------------*/ + +static void _destroyJob( IotTaskPoolJob_t * const pJob ) +{ + /* Destroy user data, for added safety&security. */ + pJob->userCallback = NULL; + pJob->pUserContext = NULL; + + /* Reset the status for added debuggability. */ + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_UNDEFINED; + + /* Only dispose of dynamically allocated jobs. */ + if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ) + { + IotTaskPool_FreeJob( pJob ); + } +} + +/* ---------------------------------------------------------------------------------------------- */ + +static bool _IsShutdownStarted( const IotTaskPool_t * const pTaskPool ) +{ + return( pTaskPool->maxThreads == 0 ); +} + +/*-----------------------------------------------------------*/ + +static void _signalShutdown( IotTaskPool_t * const pTaskPool, + uint32_t threads ) +{ + uint32_t count; + + /* Set the exit condition. */ + pTaskPool->maxThreads = 0; + + /* Broadcast to all active threads to wake-up. Active threads do check the exit condition right after wakein up. */ + for( count = 0; count < threads; ++count ) + { + IotSemaphore_Post( &pTaskPool->dispatchSignal ); + } +} + +/* ---------------------------------------------------------------------------------------------- */ + +static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + /* Update the job status to 'scheduled'. */ + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_SCHEDULED; + + /* Append the job to the dispatch queue. */ + IotQueue_Enqueue( &pTaskPool->dispatchQueue, &pJob->link ); + + /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ + pTaskPool->activeJobs++; + + /* If all threads are busy, try and create a new one. Failing to create a new thread + * only has performance implications on correctly exeuting th scheduled job. + */ + uint32_t activeThreads = pTaskPool->activeThreads; + + if( activeThreads == pTaskPool->activeJobs ) + { + /* Grow the task pool up to the maximum number of threads indicated by the user. + * Growing the taskpool can safely fail, the existing threads will eventually pick up + * the job sometimes later. */ + if( activeThreads < pTaskPool->maxThreads ) + { + IotLogInfo( "Growing a Task pool with a new worker thread..." ); + + if( Iot_CreateDetachedThread( _taskPoolWorker, + pTaskPool, + pTaskPool->priority, + pTaskPool->stackSize ) ) + { + IotSemaphore_Wait( &pTaskPool->startStopSignal ); + + pTaskPool->activeThreads++; + } + else + { + /* Failure to create a worker thread does not hinder functional correctness, but rather just responsiveness. */ + IotLogWarn( "Task pool failed to create a worker thread." ); + } + } + } + + /* Signal a worker to pick up the job. */ + IotSemaphore_Post( &pTaskPool->dispatchSignal ); + + _TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL(); +} + +/*-----------------------------------------------------------*/ + +static bool _matchJobByPointer( const IotLink_t * pLink, + void * pMatch ) +{ + const IotTaskPoolJob_t * pJob = ( IotTaskPoolJob_t * ) pMatch; + + const _taskPoolTimerEvent_t * pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); + + if( pJob == pTimerEvent->pJob ) + { + return true; + } + + return false; +} + +/*-----------------------------------------------------------*/ + +static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + IotTaskPoolJobStatus_t * const pStatus ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + bool cancelable = false; + + /* We can only cancel jobs that are either 'ready' (waiting to be scheduled). 'deferred', or 'scheduled'. */ + + IotTaskPoolJobStatus_t currentStatus = ( pJob->status & IOT_TASKPOOL_STATUS_MASK ); + + switch( currentStatus ) + { + case IOT_TASKPOOL_STATUS_READY: + case IOT_TASKPOOL_STATUS_DEFERRED: + case IOT_TASKPOOL_STATUS_SCHEDULED: + cancelable = true; + break; + + case IOT_TASKPOOL_STATUS_EXECUTING: + case IOT_TASKPOOL_STATUS_CANCELED: + IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." ); + break; + + default: + IotLogError( "Attempt to cancel a job with an undefined state." ); + break; + } + + /* Update the returned status to the current status of the job. */ + if( pStatus != NULL ) + { + *pStatus = currentStatus; + } + + if( cancelable == false ) + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_CANCEL_FAILED ); + } + else + { + /* Update the status of the job. */ + pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; + pJob->status |= IOT_TASKPOOL_STATUS_CANCELED; + + /* If the job is cancelable and its current status is 'scheduled' then unlink it from the dispatch + * queue and signal any waiting threads. */ + if( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) + { + /* A scheduled work items must be in the dispatch queue. */ + IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) ); + + IotQueue_Remove( &pJob->link ); + } + + /* If the job current status is 'deferred' then the job has to be pending + * in the timeouts queue. */ + else if( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED ) + { + /* Find the timer event associated with the current job. There MUST be one, hence assert if not. */ + IotLink_t * pTimerEventLink = IotListDouble_FindFirstMatch( &pTaskPool->timerEventsList, NULL, _matchJobByPointer, pJob ); + IotTaskPool_Assert( pTimerEventLink != NULL ); + + if( pTimerEventLink != NULL ) + { + bool shouldReschedule = false; + + /* If the job being cancelled was at the head of the timeouts queue, then we need to reschedule the timer + * with the next job timeout */ + IotLink_t * pHeadLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); + + if( pHeadLink == pTimerEventLink ) + { + shouldReschedule = true; + } + + /* Remove the timer event associated with the canceled job and free the associated memory. */ + IotListDouble_Remove( pTimerEventLink ); + IotTaskPool_FreeTimerEvent( IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ) ); + + if( shouldReschedule ) + { + IotLink_t * pNextTimerEventLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); + + if( pNextTimerEventLink != NULL ) + { + _rescheduleDeferredJobsTimer( &pTaskPool->timer, IotLink_Container( _taskPoolTimerEvent_t, pNextTimerEventLink, link ) ); + } + } + } + } + } + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob, + bool checkExecutionInProgress ) +{ + _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolJobStatus_t jobStatus = pJob->status & IOT_TASKPOOL_STATUS_MASK; + + /* if the job is executing, we cannot touch it. */ + if( checkExecutionInProgress && ( jobStatus == IOT_TASKPOOL_STATUS_EXECUTING ) ) + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); + } + /* Do not destroy a job in the dispatch queue or the timer queue without cancelling first. */ + else if( ( jobStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) || ( jobStatus == IOT_TASKPOOL_STATUS_DEFERRED ) ) + { + IotTaskPoolJobStatus_t statusAtCancellation; + + /* Cancellation can fail, e.g. if a job is being executed when we are trying to cancel it. */ + jobStatus = _tryCancelInternal( pTaskPool, pJob, &statusAtCancellation ); + + switch( jobStatus ) + { + case IOT_TASKPOOL_SUCCESS: + break; + + case IOT_TASKPOOL_CANCEL_FAILED: + IotLogWarn( "Removing a scheduled job failed because the job could not be canceled." ); + jobStatus = IOT_TASKPOOL_ILLEGAL_OPERATION; + break; + + default: + break; + } + } + else if( IotLink_IsLinked( &pJob->link ) ) + { + /* If the job is not in the dispatch or timer queue, it must be in the cache. */ + IotTaskPool_Assert( ( pJob->jobStatus & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ); + + IotListDouble_Remove( &pJob->link ); + } + + _TASKPOOL_NO_FUNCTION_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, + const IotLink_t * const pTimerEventLink2 ) +{ + const _taskPoolTimerEvent_t * pTimerEvent1 = IotLink_Container( _taskPoolTimerEvent_t, + pTimerEventLink1, + link ); + const _taskPoolTimerEvent_t * pTimerEvent2 = IotLink_Container( _taskPoolTimerEvent_t, + pTimerEventLink2, + link ); + + if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) + { + return -1; + } + + if( pTimerEvent1->expirationTime > pTimerEvent2->expirationTime ) + { + return 1; + } + + return 0; +} + +/*-----------------------------------------------------------*/ + +static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, + _taskPoolTimerEvent_t * const pFirstTimerEvent ) +{ + uint64_t delta = 0; + uint64_t now = IotClock_GetTimeMs(); + + if( pFirstTimerEvent->expirationTime > now ) + { + delta = pFirstTimerEvent->expirationTime - now; + } + + if( delta < _TASKPOOL_JOB_RESCHEDULE_DELAY_MS ) + { + delta = _TASKPOOL_JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ + } + + IotTaskPool_Assert( delta > 0 ); + + if( IotClock_TimerArm( pTimer, delta, 0 ) == false ) + { + IotLogWarn( "Failed to re-arm timer for task pool" ); + } +} + +/*-----------------------------------------------------------*/ + +static void _timerThread( void * pArgument ) +{ + IotTaskPool_t * pTaskPool = ( IotTaskPool_t * ) pArgument; + _taskPoolTimerEvent_t * pTimerEvent = NULL; + + IotLogDebug( "Timer thread started for task pool %p.", pTaskPool ); + + /* Attempt to lock the timer mutex. Return immediately if the mutex cannot be locked. + * If this mutex cannot be locked it means that another thread is manipulating the + * timeouts list, and will reset the timer to fire again, although it will be late. + */ + if( _TASKPOOL_TRY_ENTER_CRITICAL_SECTION == false ) + { + IotLogWarn( "Failed to lock timer mutex in timer thread. Rescheduling deferred job." ); + + IotClock_TimerArm( &pTaskPool->timer, _TASKPOOL_JOB_RESCHEDULE_DELAY_MS, 0 ); + + return; + } + else + { + /* Check again for shutdown and bail out early in case. */ + if( _IsShutdownStarted( pTaskPool ) ) + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + return; + } + + /* Dispatch all deferred job whose timer expired, then reset the timer for the next + * job down the line. */ + while( true ) + { + /* Peek the first event in the timer event list. */ + IotLink_t * pLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); + + /* Check if the timer misfired for any reason. */ + if( pLink != NULL ) + { + /* Record the current time. */ + uint64_t now = IotClock_GetTimeMs(); + + /* Extract the job from its envelope. */ + pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); + + /* Check if the first event should be processed now. */ + if( pTimerEvent->expirationTime <= now ) + { + /* Remove the timer event for immediate processing. */ + IotListDouble_Remove( &( pTimerEvent->link ) ); + } + else + { + /* The first element in the timer queue shouldn't be processed yet. + * Arm the timer for when it should be processed and leave altogether. */ + _rescheduleDeferredJobsTimer( &pTaskPool->timer, pTimerEvent ); + + break; + } + } + /* If there are no timer events to process, terminate this thread. */ + else + { + IotLogDebug( "No further timer events to process. Exiting timer thread." ); + + break; + } + + IotLogDebug( "Scheduling job from timer event." ); + + /* Queue the job associated with the received timer event. */ + _scheduleInternal( pTaskPool, pTimerEvent->pJob ); + + /* Free the timer event. */ + IotTaskPool_FreeTimerEvent( pTimerEvent ); + } + } + + _TASKPOOL_EXIT_CRITICAL_SECTION; +} + +/** @endcond */ diff --git a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c index d7f7b5d2bd..9a2e417d34 100644 --- a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c +++ b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c @@ -37,13 +37,6 @@ #include #include -/* POSIX include. Allow it to be overridden. */ -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif - /* Static memory include. */ #include "private/iot_static_memory.h" diff --git a/lib/source/common/static_memory/iot_static_memory_mqtt.c b/lib/source/common/static_memory/iot_static_memory_mqtt.c index 4f2677e5cb..cebe4e7bd5 100644 --- a/lib/source/common/static_memory/iot_static_memory_mqtt.c +++ b/lib/source/common/static_memory/iot_static_memory_mqtt.c @@ -37,18 +37,11 @@ #include #include -/* POSIX include. Allow it to be overridden. */ -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif - /* Static memory include. */ #include "private/iot_static_memory.h" /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /*-----------------------------------------------------------*/ @@ -113,9 +106,6 @@ static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ] = { { 0 } }; static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operations. */ -static bool _pInUseMqttTimerEvents[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer event in-use flags. */ -static _mqttTimerEvent_t _pMqttTimerEvents[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT timer events. */ - static bool _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ _MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ @@ -191,41 +181,6 @@ void Iot_FreeMqttOperation( void * ptr ) /*-----------------------------------------------------------*/ -void * Iot_MallocMqttTimerEvent( size_t size ) -{ - int freeIndex = -1; - void * pNewTimerEvent = NULL; - - /* Check size argument. */ - if( size == sizeof( _mqttTimerEvent_t ) ) - { - /* Find a free MQTT timer event. */ - freeIndex = IotStaticMemory_FindFree( _pInUseMqttTimerEvents, - IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); - - if( freeIndex != -1 ) - { - pNewTimerEvent = &( _pMqttTimerEvents[ freeIndex ] ); - } - } - - return pNewTimerEvent; -} - -/*-----------------------------------------------------------*/ - -void Iot_FreeMqttTimerEvent( void * ptr ) -{ - /* Return the in-use MQTT timer event. */ - IotStaticMemory_ReturnInUse( ptr, - _pMqttTimerEvents, - _pInUseMqttTimerEvents, - IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _mqttTimerEvent_t ) ); -} - -/*-----------------------------------------------------------*/ - void * Iot_MallocMqttSubscription( size_t size ) { int freeIndex = -1; diff --git a/lib/source/common/static_memory/iot_static_memory_taskpool.c b/lib/source/common/static_memory/iot_static_memory_taskpool.c new file mode 100644 index 0000000000..42f93e115b --- /dev/null +++ b/lib/source/common/static_memory/iot_static_memory_taskpool.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_static_memory_taskpool.c + * @brief Implementation of task pool static memory functions in iot_static_memory.h + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* Static memory include. */ +#include "private/iot_static_memory.h" + +/* Task pool internal include. */ +#include "private/iot_taskpool_internal.h" + +/*-----------------------------------------------------------*/ + +/* Validate static memory configuration settings. */ +#if IOT_TASKPOOL_JOBS_RECYCLE_LIMIT <= 0 + #error "IOT_TASKPOOL_JOBS_RECYCLE_LIMIT cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in iot_static_memory_common.c + * Because these functions are specific to this static memory implementation, they are + * not placed in the common static memory header file. */ +extern int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); +extern void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool jobs in-use flags. */ +static IotTaskPoolJob_t _pTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; /**< @brief Task pool jobs. */ + +static bool _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer event in-use flags. */ +static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer events. */ + +/*-----------------------------------------------------------*/ + +void * Iot_MallocTaskPoolJob( size_t size ) +{ + int freeIndex = -1; + void * pNewJob = NULL; + + /* Check size argument. */ + if( size == sizeof( IotTaskPoolJob_t ) ) + { + /* Find a free task pool job. */ + freeIndex = IotStaticMemory_FindFree( _pInUseTaskPoolJobs, + IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ); + + if( freeIndex != -1 ) + { + pNewJob = &( _pTaskPoolJobs[ freeIndex ] ); + } + } + + return pNewJob; +} + +/*-----------------------------------------------------------*/ + +void Iot_FreeTaskPoolJob( void * ptr ) +{ + /* Return the in-use task pool job. */ + IotStaticMemory_ReturnInUse( ptr, + _pTaskPoolJobs, + _pInUseTaskPoolJobs, + IOT_TASKPOOL_JOBS_RECYCLE_LIMIT, + sizeof( IotTaskPoolJob_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * Iot_MallocTaskPoolTimerEvent( size_t size ) +{ + int freeIndex = -1; + void * pNewTimerEvent = NULL; + + /* Check size argument. */ + if( size == sizeof( _taskPoolTimerEvent_t ) ) + { + /* Find a free task pool timer event. */ + freeIndex = IotStaticMemory_FindFree( _pInUseTaskPoolTimerEvents, + IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ); + + if( freeIndex != -1 ) + { + pNewTimerEvent = &( _pTaskPoolTimerEvents[ freeIndex ] ); + } + } + + return pNewTimerEvent; +} + +/*-----------------------------------------------------------*/ + +void Iot_FreeTaskPoolTimerEvent( void * ptr ) +{ + /* Return the in-use task pool timer event. */ + IotStaticMemory_ReturnInUse( ptr, + _pTaskPoolTimerEvents, + _pInUseTaskPoolTimerEvents, + IOT_TASKPOOL_JOBS_RECYCLE_LIMIT, + sizeof( _taskPoolTimerEvent_t ) ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index f383368a0f..b7646a52ad 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,11 +1,11 @@ # MQTT library source files. add_library( iotmqtt SHARED - aws_iot_mqtt_api.c + iot_mqtt_api.c iot_mqtt_network.c - aws_iot_mqtt_operation.c - aws_iot_mqtt_serialize.c - aws_iot_mqtt_subscription.c - aws_iot_mqtt_validate.c ) + iot_mqtt_operation.c + iot_mqtt_serialize.c + iot_mqtt_subscription.c + iot_mqtt_validate.c ) # Library version. set_target_properties( iotmqtt PROPERTIES VERSION ${PROJECT_VERSION} ) diff --git a/lib/source/mqtt/aws_iot_mqtt_api.c b/lib/source/mqtt/aws_iot_mqtt_api.c deleted file mode 100644 index c43195fc5b..0000000000 --- a/lib/source/mqtt/aws_iot_mqtt_api.c +++ /dev/null @@ -1,1882 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_mqtt_api.c - * @brief Implements most user-facing functions of the MQTT library. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Validate MQTT configuration settings. */ -#if AWS_IOT_MQTT_ENABLE_ASSERTS != 0 && AWS_IOT_MQTT_ENABLE_ASSERTS != 1 - #error "AWS_IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." -#endif -#if AWS_IOT_MQTT_ENABLE_METRICS != 0 && AWS_IOT_MQTT_ENABLE_METRICS != 1 - #error "AWS_IOT_MQTT_ENABLE_METRICS must be 0 or 1." -#endif -#if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 - #error "AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." -#endif -#if AWS_IOT_MQTT_MAX_CALLBACK_THREADS <= 0 - #error "AWS_IOT_MQTT_MAX_CALLBACK_THREADS cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_MAX_SEND_THREADS <= 0 - #error "AWS_IOT_MQTT_MAX_SEND_THREADS cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_TEST != 0 && AWS_IOT_MQTT_TEST != 1 - #error "AWS_IOT_MQTT_MQTT_TEST must be 0 or 1." -#endif -#if AWS_IOT_MQTT_RESPONSE_WAIT_MS <= 0 - #error "AWS_IOT_MQTT_RESPONSE_WAIT_MS cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_RETRY_MS_CEILING <= 0 - #error "AWS_IOT_MQTT_RETRY_MS_CEILING cannot be 0 or negative." -#endif -#if AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS <= 0 - #error "AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS cannot be 0 or negative." -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Match #_mqttOperation_t by their associated MQTT connection. - * - * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. - * @param[in] pMatch Pointer to an #_mqttConnection_t. - * - * @return `true` if the [connection associated with the given operation] - * (@ref _mqttOperation_t.pMqttConnection) is equal to `pMatch`; `false` - * otherwise. - */ -static bool _mqttOperation_matchConnection( const IotLink_t * pOperationLink, - void * pMatch ); - -/** - * @brief Determines if an MQTT subscription is safe to remove based on its - * reference count. - * - * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. - * @param[in] pMatch Not used. - * - * @return `true` if the given subscription has no references; `false` otherwise. - */ -static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -/** - * @brief Process a keep-alive event received from a timer event queue. - * - * @param[in] pMqttConnection The MQTT connection associated with the keep-alive. - * @param[in] pKeepAliveEvent The keep-alive event to process. - * - * @return `true` if the event was successful; false if an error was encountered - * while processing the keep-alive. If this function returns false, the MQTT - * connection should be closed. - */ -static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, - _mqttTimerEvent_t * const pKeepAliveEvent ); - -/** - * @brief Process a PUBLISH retry event received from a timer event queue. - * - * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. - * @param[in] pPublishRetryEvent The PUBLISH retry event to process. - */ -static void _processPublishRetryEvent( bool awsIotMqttMode, - _mqttTimerEvent_t * const pPublishRetryEvent ); - -/** - * @brief Handles timer expirations for an MQTT connection. - * - * This function is invoked when the MQTT connection timer expires. Based on - * pending timer events, it then sends PINGREQ, checks for PINGRESP, or resends - * an unacknowledged QoS 1 PUBLISH. - * - * @param[in] pArgument The MQTT connection for which PINGREQ is sent. - */ -static void _timerThread( void * pArgument ); - -/** - * @brief Creates a new MQTT connection and initializes its members. - * - * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. - * @param[in] pNetworkInterface User-provided network interface for the new - * connection. - * @param[in] keepAliveSeconds User-provided keep-alive interval for the new - * connection. - * - * @return Pointer to a newly-allocated MQTT connection on success; `NULL` on - * failure. - */ -static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, - const AwsIotMqttNetIf_t * const pNetworkInterface, - uint16_t keepAliveSeconds ); - -/** - * @brief Destroys the members of an MQTT connection. - * - * @param[in] pMqttConnection Which connection to destroy. - */ -static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ); - -/** - * @brief The common component of both @ref mqtt_function_subscribe and @ref - * mqtt_function_unsubscribe. - * - * See @ref mqtt_function_subscribe or @ref mqtt_function_unsubscribe for a - * description of the parameters and return values. - */ -static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operation, - AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pSubscriptionRef ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Ensures that only one CONNECT operation is in-progress at any time. - * - * Because CONNACK contains no data about which CONNECT packet it acknowledges, - * only one CONNECT operation may be in-progress at any time. - */ -static IotMutex_t _connectMutex; - -/*-----------------------------------------------------------*/ - -static bool _mqttOperation_matchConnection( const IotLink_t * pOperationLink, - void * pMatch ) -{ - bool match = false; - _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, - pOperationLink, - link ); - - /* Ignore PINGREQ operations. PINGREQs will be cleaned up with the MQTT - * connection and not here. */ - if( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) - { - match = ( pMatch == pOperation->pMqttConnection ); - } - - return match; -} - -/*-----------------------------------------------------------*/ - -static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, - void * pMatch ) -{ - bool match = false; - _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - pSubscriptionLink, - link ); - - /* Silence warnings about unused parameters. */ - ( void ) pMatch; - - /* Reference count must not be negative. */ - AwsIotMqtt_Assert( pSubscription->references >= 0 ); - - /* Check if any subscription callbacks are using this subscription. */ - if( pSubscription->references > 0 ) - { - /* Set the unsubscribed flag, but do not remove the subscription yet. */ - pSubscription->unsubscribed = true; - } - else - { - /* No references for this subscription; it can be removed. */ - match = true; - } - - return match; -} - -/*-----------------------------------------------------------*/ - -static bool _processKeepAliveEvent( _mqttConnection_t * const pMqttConnection, - _mqttTimerEvent_t * const pKeepAliveEvent ) -{ - bool status = true; - - /* Check if the keep-alive is waiting for a PINGRESP. */ - if( pKeepAliveEvent->checkPingresp == false ) - { - /* If keep-alive isn't waiting for PINGRESP, send PINGREQ. */ - IotLogDebug( "Sending PINGREQ." ); - - /* Add the PINGREQ operation to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pMqttConnection->pPingreqOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to enqueue PINGREQ for sending." ); - status = false; - } - else - { - /* Check for a PINGRESP after AWS_IOT_MQTT_RESPONSE_WAIT_MS. */ - pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + AWS_IOT_MQTT_RESPONSE_WAIT_MS; - pKeepAliveEvent->checkPingresp = true; - } - } - else - { - /* Check that a PINGRESP is immediately available. */ - if( AwsIotMqtt_Wait( pMqttConnection->pPingreqOperation, 0 ) == AWS_IOT_MQTT_SUCCESS ) - { - IotLogDebug( "PINGRESP received." ); - - /* The next keep-alive event should send another PINGREQ. */ - pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + - pMqttConnection->keepAliveSeconds * 1000ULL; - pKeepAliveEvent->checkPingresp = false; - } - else - { - /* PINGRESP was not received within AWS_IOT_MQTT_PINGRESP_WAIT_MS. */ - IotLogError( "Timeout waiting on PINGRESP." ); - - /* Set the error flag. The MQTT connection will be closed. */ - pMqttConnection->errorOccurred = true; - - /* Free the keep-alive event and destroy the PINGREQ operation, as they - * will no longer be used by a closed connection. */ - AwsIotMqtt_FreeTimerEvent( pMqttConnection->pKeepAliveEvent ); - pMqttConnection->pKeepAliveEvent = NULL; - - AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); - pMqttConnection->pPingreqOperation = NULL; - - status = false; - } - } - - /* Add the next keep-alive event to the timer event list if the keep-alive - * was successfully processed. */ - if( status == true ) - { - IotListDouble_InsertSorted( &( pMqttConnection->timerEventList ), - &( pMqttConnection->pKeepAliveEvent->link ), - AwsIotMqttInternal_TimerEventCompare ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _processPublishRetryEvent( bool awsIotMqttMode, - _mqttTimerEvent_t * const pPublishRetryEvent ) -{ - _mqttOperation_t * pOperation = pPublishRetryEvent->pOperation; - - /* This function should only be called for PUBLISH operations with retry. */ - AwsIotMqtt_Assert( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); - AwsIotMqtt_Assert( pOperation->pPublishRetry == pPublishRetryEvent ); - AwsIotMqtt_Assert( pPublishRetryEvent->retry.limit > 0 ); - - /* Check if the PUBLISH operation is still waiting for a response. */ - if( AwsIotMqttInternal_FindOperation( pOperation->operation, - &( pOperation->packetIdentifier ) ) == pOperation ) - { - /* Check if the retry limit is reached. */ - if( pPublishRetryEvent->retry.count > pPublishRetryEvent->retry.limit ) - { - IotLogError( "No PUBACK received for PUBLISH %hu after %d retransmissions.", - pOperation->packetIdentifier, - pPublishRetryEvent->retry.limit ); - - /* Set a status of "No response to retries" and notify. */ - pOperation->status = AWS_IOT_MQTT_RETRY_NO_RESPONSE; - AwsIotMqttInternal_Notify( pOperation ); - } - else - { - /* Choose a set DUP function. */ - void ( * publishSetDup )( bool, - uint8_t * const, - uint16_t * const ) = AwsIotMqttInternal_PublishSetDup; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pOperation->pMqttConnection->network.serialize.publishSetDup != NULL ) - { - publishSetDup = pOperation->pMqttConnection->network.serialize.publishSetDup; - } - #endif - - /* For the AWS IoT MQTT server, AwsIotMqttInternal_PublishSetDup changes the - * packet identifier; this must be done on every retry. For a compliant MQTT - * server, the function sets the DUP flag; this only needs to be done on the - * first retry. */ - if( awsIotMqttMode == true ) - { - if( pPublishRetryEvent->retry.count <= pPublishRetryEvent->retry.limit ) - { - publishSetDup( true, - pOperation->pMqttPacket, - &( pOperation->packetIdentifier ) ); - } - } - else - { - if( pPublishRetryEvent->retry.count == 1 ) - { - publishSetDup( false, - pOperation->pMqttPacket, - &( pOperation->packetIdentifier ) ); - } - } - - /* Print debug log messages about this PUBLISH retry. */ - IotLogDebug( "No PUBACK received for PUBLISH %hu. Attempting retransmission" - " %d of %d.", - pOperation->packetIdentifier, - pPublishRetryEvent->retry.count, - pPublishRetryEvent->retry.limit ); - - if( pPublishRetryEvent->retry.count < pPublishRetryEvent->retry.limit ) - { - IotLogDebug( "Next retry for PUBLISH %hu in %llu ms.", - pOperation->packetIdentifier, - pPublishRetryEvent->retry.nextPeriod ); - } - else - { - IotLogDebug( "Final retry for PUBLISH %hu. Will check in %llu ms " - "for response.", - pOperation->packetIdentifier, - AWS_IOT_MQTT_RESPONSE_WAIT_MS ); - } - - /* Add the PUBLISH to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to enqueue PUBLISH retry for sending." ); - } - } - } -} - -/*-----------------------------------------------------------*/ - -static void _timerThread( void * pArgument ) -{ - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pArgument; - _mqttTimerEvent_t * pTimerEvent = NULL; - - IotLogDebug( "Timer thread started for connection %p.", pMqttConnection ); - - /* Attempt to lock the timer mutex before this thread does anything. - * Return immediately if the mutex couldn't be locked. */ - if( IotMutex_TryLock( &( pMqttConnection->timerMutex ) ) == false ) - { - IotLogWarn( "Failed to lock connection timer mutex in timer thread. Exiting." ); - - return; - } - - while( true ) - { - /* Peek the first event in the timer event list. */ - pTimerEvent = IotLink_Container( _mqttTimerEvent_t, - IotListDouble_PeekHead( &( pMqttConnection->timerEventList ) ), - link ); - - if( pTimerEvent != NULL ) - { - /* Check if the first event should be processed now. */ - if( pTimerEvent->expirationTime <= IotClock_GetTimeMs() + AWS_IOT_MQTT_TIMER_EVENT_THRESHOLD_MS ) - { - /* Remove the timer event for immediate processing. */ - IotListDouble_Remove( &( pTimerEvent->link ) ); - } - else - { - /* The first element in the timer queue shouldn't be processed yet. - * Arm the timer for when it should be processed. */ - if( IotClock_TimerArm( &( pMqttConnection->timer ), - pTimerEvent->expirationTime - IotClock_GetTimeMs(), - 0 ) == false ) - { - IotLogWarn( "Failed to re-arm timer for connection %p.", - pMqttConnection ); - } - - pTimerEvent = NULL; - } - } - - /* If there are no timer events to process, terminate this thread. */ - if( pTimerEvent == NULL ) - { - IotLogDebug( "No further timer events to process. Exiting timer thread." ); - - break; - } - - IotLogDebug( "Processing timer event for %s.", - AwsIotMqtt_OperationType( pTimerEvent->pOperation->operation ) ); - - /* Process the received timer event. Currently, only PINGREQ and PUBLISH - * operations send timer events. */ - switch( pTimerEvent->pOperation->operation ) - { - case AWS_IOT_MQTT_PINGREQ: - - /* Process the PINGREQ event. If it fails to process, close the MQTT - * connection. */ - if( _processKeepAliveEvent( pMqttConnection, pTimerEvent ) == false ) - { - if( pMqttConnection->network.disconnect != NULL ) - { - pMqttConnection->network.disconnect( 0, pMqttConnection->network.pDisconnectContext ); - } - else - { - IotLogWarn( "No disconnect function was set. Network connection not closed." ); - } - } - - break; - - case AWS_IOT_MQTT_PUBLISH_TO_SERVER: - - _processPublishRetryEvent( pMqttConnection->awsIotMqttMode, - pTimerEvent ); - break; - - default: - - /* No other operation may send a timer event. Abort the program - * if this case is reached. */ - AwsIotMqtt_Assert( 0 ); - break; - } - } - - IotMutex_Unlock( &( pMqttConnection->timerMutex ) ); -} - -/*-----------------------------------------------------------*/ - -static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, - const AwsIotMqttNetIf_t * const pNetworkInterface, - uint16_t keepAliveSeconds ) -{ - _mqttConnection_t * pNewMqttConnection = NULL; - - /* AWS IoT service limits set minimum and maximum values for keep-alive interval. - * Adjust the user-provided keep-alive interval based on these requirements. */ - if( awsIotMqttMode == true ) - { - if( keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) - { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; - } - else if( ( keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) || ( keepAliveSeconds == 0 ) ) - { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; - } - } - - /* Allocate memory to store data for the new MQTT connection. */ - pNewMqttConnection = ( _mqttConnection_t * ) - AwsIotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); - - if( pNewMqttConnection == NULL ) - { - IotLogError( "Failed to allocate memory for new MQTT connection." ); - - return NULL; - } - - /* Clear the MQTT connection, then copy the network interface and MQTT server - * mode. */ - ( void ) memset( pNewMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); - pNewMqttConnection->network = *pNetworkInterface; - pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; - - /* Create the timer mutex for a new connection. */ - if( IotMutex_Create( &( pNewMqttConnection->timerMutex ), true ) == false ) - { - IotLogError( "Failed to create timer mutex for new connection." ); - AwsIotMqtt_FreeConnection( pNewMqttConnection ); - - return NULL; - } - - if( IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ), false ) == false ) - { - IotLogError( "Failed to create subscription mutex for new connection." ); - IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); - AwsIotMqtt_FreeConnection( pNewMqttConnection ); - - return NULL; - } - - /* Create the new connection's subscription and timer event lists. */ - IotListDouble_Create( &( pNewMqttConnection->subscriptionList ) ); - IotListDouble_Create( &( pNewMqttConnection->timerEventList ) ); - - /* Create the timer mutex for a new connection. */ - if( IotClock_TimerCreate( &( pNewMqttConnection->timer ), - _timerThread, - pNewMqttConnection ) == false ) - { - IotLogError( "Failed to create timer for new connection." ); - IotMutex_Destroy( &( pNewMqttConnection->timerMutex ) ); - IotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); - AwsIotMqtt_FreeConnection( pNewMqttConnection ); - - return NULL; - } - - /* Check if keep-alive is active for this connection. */ - if( keepAliveSeconds != 0 ) - { - /* Save the keep-alive interval. */ - pNewMqttConnection->keepAliveSeconds = keepAliveSeconds; - - /* Allocate memory for keep-alive timer event. */ - pNewMqttConnection->pKeepAliveEvent = AwsIotMqtt_MallocTimerEvent( sizeof( _mqttTimerEvent_t ) ); - - if( pNewMqttConnection->pKeepAliveEvent == NULL ) - { - IotLogError( "Failed to allocate keep-alive event for new connection." ); - _destroyMqttConnection( pNewMqttConnection ); - - return NULL; - } - - /* Create PINGREQ operation. */ - if( AwsIotMqttInternal_CreateOperation( &( pNewMqttConnection->pPingreqOperation ), - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to allocate PINGREQ operation for new connection." ); - _destroyMqttConnection( pNewMqttConnection ); - - return NULL; - } - - /* Set the members of the PINGREQ operations. */ - pNewMqttConnection->pPingreqOperation->operation = AWS_IOT_MQTT_PINGREQ; - pNewMqttConnection->pPingreqOperation->pMqttConnection = pNewMqttConnection; - - /* Choose a PINGREQ serializer function. */ - AwsIotMqttError_t ( * serializePingreq )( uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializePingreq; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNewMqttConnection->network.serialize.pingreq != NULL ) - { - serializePingreq = pNewMqttConnection->network.serialize.pingreq; - } - #endif - - if( serializePingreq( &( pNewMqttConnection->pPingreqOperation->pMqttPacket ), - &( pNewMqttConnection->pPingreqOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to serialize PINGREQ packet for new connection." ); - _destroyMqttConnection( pNewMqttConnection ); - - return NULL; - } - - /* Set the members of the keep-alive timer event. */ - ( void ) memset( pNewMqttConnection->pKeepAliveEvent, 0x00, sizeof( _mqttTimerEvent_t ) ); - pNewMqttConnection->pKeepAliveEvent->pOperation = pNewMqttConnection->pPingreqOperation; - pNewMqttConnection->pKeepAliveEvent->expirationTime = IotClock_GetTimeMs() + keepAliveSeconds * 1000ULL; - - /* Add the PINGREQ to the timer event list. */ - IotListDouble_InsertSorted( &( pNewMqttConnection->timerEventList ), - &( pNewMqttConnection->pKeepAliveEvent->link ), - AwsIotMqttInternal_TimerEventCompare ); - } - - return pNewMqttConnection; -} - -/*-----------------------------------------------------------*/ - -static void _destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) -{ - /* Destroy keep-alive timer event. */ - if( pMqttConnection->pKeepAliveEvent != NULL ) - { - AwsIotMqtt_FreeTimerEvent( pMqttConnection->pKeepAliveEvent ); - pMqttConnection->pKeepAliveEvent = NULL; - } - - /* Destroy keep-alive operation. */ - if( pMqttConnection->pPingreqOperation != NULL ) - { - AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); - pMqttConnection->pPingreqOperation = NULL; - } - - /* Remove any previous session subscriptions. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - IotListDouble_RemoveAll( &( pMqttConnection->subscriptionList ), - AwsIotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - /* Destroy timer and mutexes. */ - IotClock_TimerDestroy( &( pMqttConnection->timer ) ); - IotMutex_Destroy( &( pMqttConnection->timerMutex ) ); - IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); - AwsIotMqtt_FreeConnection( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -static AwsIotMqttError_t _subscriptionCommon( AwsIotMqttOperationType_t operation, - AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pSubscriptionRef ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pSubscriptionOperation = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; - - /* Subscription serializer function. */ - AwsIotMqttError_t ( * serializeSubscription )( const AwsIotMqttSubscription_t * const, - size_t, - uint8_t ** const, - size_t * const, - uint16_t * const ) = NULL; - - /* This function should only be called for subscribe or unsubscribe. */ - AwsIotMqtt_Assert( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) || - ( operation == AWS_IOT_MQTT_UNSUBSCRIBE ) ); - - /* Choose a subscription serialize function. */ - if( operation == AWS_IOT_MQTT_SUBSCRIBE ) - { - serializeSubscription = AwsIotMqttInternal_SerializeSubscribe; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.subscribe != NULL ) - { - serializeSubscription = pMqttConnection->network.serialize.subscribe; - } - #endif - } - else - { - serializeSubscription = AwsIotMqttInternal_SerializeUnsubscribe; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.unsubscribe != NULL ) - { - serializeSubscription = pMqttConnection->network.serialize.unsubscribe; - } - #endif - } - - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && - ( pSubscriptionRef == NULL ) ) - { - IotLogError( "Reference must be provided for a waitable %s.", - AwsIotMqtt_OperationType( operation ) ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Check that all elements in the subscription list are valid. */ - if( AwsIotMqttInternal_ValidateSubscriptionList( operation, - pMqttConnection->awsIotMqttMode, - pSubscriptionList, - subscriptionCount ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ - if( operation == AWS_IOT_MQTT_UNSUBSCRIBE ) - { - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, - pSubscriptionList, - subscriptionCount ); - } - - /* Create a subscription operation. */ - status = AwsIotMqttInternal_CreateOperation( &pSubscriptionOperation, - flags, - pCallbackInfo ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - return status; - } - - /* Check the subscription operation data and set the remaining members. */ - AwsIotMqtt_Assert( pSubscriptionOperation->pPublishRetry == NULL ); - AwsIotMqtt_Assert( pSubscriptionOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - pSubscriptionOperation->operation = operation; - pSubscriptionOperation->pMqttConnection = pMqttConnection; - - /* Generate a subscription packet from the subscription list. */ - status = serializeSubscription( pSubscriptionList, - subscriptionCount, - &( pSubscriptionOperation->pMqttPacket ), - &( pSubscriptionOperation->packetSize ), - &( pSubscriptionOperation->packetIdentifier ) ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pSubscriptionOperation ); - - return status; - } - - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pSubscriptionOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pSubscriptionOperation->packetSize > 0 ); - - /* Add the subscription list for a SUBSCRIBE. */ - if( operation == AWS_IOT_MQTT_SUBSCRIBE ) - { - status = AwsIotMqttInternal_AddSubscriptions( pMqttConnection, - pSubscriptionOperation->packetIdentifier, - pSubscriptionList, - subscriptionCount ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pSubscriptionOperation ); - - return status; - } - } - - /* Set the reference, if provided. This must be set before the subscription - * is pushed to the network queue to avoid a race condition. */ - if( pSubscriptionRef != NULL ) - { - *pSubscriptionRef = pSubscriptionOperation; - } - - /* Add the subscription operation to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pSubscriptionOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to enqueue %s for sending.", - AwsIotMqtt_OperationType( operation ) ); - - if( operation == AWS_IOT_MQTT_SUBSCRIBE ) - { - AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, - pSubscriptionOperation->packetIdentifier, - -1 ); - } - - AwsIotMqttInternal_DestroyOperation( pSubscriptionOperation ); - - /* Clear the previously set (and now invalid) reference. */ - if( pSubscriptionRef != NULL ) - { - *pSubscriptionRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - } - - return AWS_IOT_MQTT_NO_MEMORY; - } - - IotLogInfo( "MQTT %s operation queued.", - AwsIotMqtt_OperationType( operation ) ); - - /* The subscription operation is waiting for a network response. */ - return AWS_IOT_MQTT_STATUS_PENDING; -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_Init( void ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; - - /* Create mutex protecting MQTT operation queues. */ - if( IotMutex_Create( &( _IotMqttQueueMutex ), false ) == false ) - { - IotLogError( "Failed to initialize MQTT operation queue mutex." ); - status = AWS_IOT_MQTT_INIT_FAILED; - } - - /* Create mutex protecting list of operations pending network responses. */ - if( status == AWS_IOT_MQTT_SUCCESS ) - { - if( IotMutex_Create( &( _IotMqttPendingResponseMutex ), false ) == false ) - { - IotLogError( "Failed to initialize MQTT library pending response mutex." ); - IotMutex_Destroy( &( _IotMqttQueueMutex ) ); - - status = AWS_IOT_MQTT_INIT_FAILED; - } - } - - /* Create CONNECT mutex. */ - if( status == AWS_IOT_MQTT_SUCCESS ) - { - if( IotMutex_Create( &( _connectMutex ), false ) == false ) - { - IotLogError( "Failed to initialize MQTT library connect mutex." ); - IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - IotMutex_Destroy( &( _IotMqttQueueMutex ) ); - - status = AWS_IOT_MQTT_INIT_FAILED; - } - } - - /* Create semaphores that count active MQTT library threads. */ - if( status == AWS_IOT_MQTT_SUCCESS ) - { - /* Create semaphore that counts active callback threads. */ - if( IotSemaphore_Create( &( _IotMqttCallback.availableThreads ), - AWS_IOT_MQTT_MAX_CALLBACK_THREADS, - AWS_IOT_MQTT_MAX_CALLBACK_THREADS ) == false ) - { - IotLogError( "Failed to initialize record of active MQTT callback threads." ); - status = AWS_IOT_MQTT_INIT_FAILED; - } - else - { - /* Create semaphore that counts active send threads. */ - if( IotSemaphore_Create( &( _IotMqttSend.availableThreads ), - AWS_IOT_MQTT_MAX_SEND_THREADS, - AWS_IOT_MQTT_MAX_SEND_THREADS ) == false ) - { - IotLogError( "Failed to initialize record of active MQTT send threads." ); - status = AWS_IOT_MQTT_INIT_FAILED; - - IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); - } - } - - /* Destroy previously created mutexes if thread counter semaphores could - * not be created. */ - if( status == AWS_IOT_MQTT_INIT_FAILED ) - { - IotMutex_Destroy( &( _connectMutex ) ); - IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - IotMutex_Destroy( &( _IotMqttQueueMutex ) ); - } - } - - /* Initialize MQTT serializer. */ - if( status == AWS_IOT_MQTT_SUCCESS ) - { - if( AwsIotMqttInternal_InitSerialize() != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to initialize MQTT library serializer. " ); - IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); - IotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); - IotMutex_Destroy( &( _connectMutex ) ); - IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - IotMutex_Destroy( &( _IotMqttQueueMutex ) ); - - status = AWS_IOT_MQTT_INIT_FAILED; - } - } - - /* Create MQTT library linear containers. */ - if( status == AWS_IOT_MQTT_SUCCESS ) - { - IotQueue_Create( &( _IotMqttCallback.queue ) ); - IotQueue_Create( &( _IotMqttSend.queue ) ); - IotListDouble_Create( &( _IotMqttPendingResponse ) ); - - IotLogInfo( "MQTT library successfully initialized." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void AwsIotMqtt_Cleanup() -{ - /* Wait for termination of any active MQTT library threads. */ - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - - while( IotSemaphore_GetCount( &( _IotMqttCallback.availableThreads ) ) > 0 ) - { - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - IotSemaphore_Wait( &( _IotMqttCallback.availableThreads ) ); - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - } - - while( IotSemaphore_GetCount( &( _IotMqttSend.availableThreads ) ) > 0 ) - { - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - IotSemaphore_Wait( &( _IotMqttSend.availableThreads ) ); - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - } - - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - - /* This API requires all MQTT connections to be terminated. If the MQTT library - * linear containers are not empty, there is an active MQTT connection and the - * library cannot be safely shut down. */ - AwsIotMqtt_Assert( IotQueue_IsEmpty( &( _IotMqttCallback.queue ) ) == true ); - AwsIotMqtt_Assert( IotQueue_IsEmpty( &( _IotMqttSend.queue ) ) == true ); - AwsIotMqtt_Assert( IotListDouble_IsEmpty( &( _IotMqttPendingResponse ) ) == true ); - - /* Clean up MQTT library mutexes. */ - IotMutex_Destroy( &( _connectMutex ) ); - IotMutex_Destroy( &( _IotMqttPendingResponseMutex ) ); - IotMutex_Destroy( &( _IotMqttQueueMutex ) ); - - /* Clean up thread counter semaphores. */ - IotSemaphore_Destroy( &( _IotMqttCallback.availableThreads ) ); - IotSemaphore_Destroy( &( _IotMqttSend.availableThreads ) ); - - /* Clean up MQTT serializer. */ - AwsIotMqttInternal_CleanupSerialize(); - - IotLogInfo( "MQTT library cleanup done." ); -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_Connect( AwsIotMqttConnection_t * pMqttConnection, - const AwsIotMqttNetIf_t * const pNetworkInterface, - const AwsIotMqttConnectInfo_t * const pConnectInfo, - uint64_t timeoutMs ) -{ - AwsIotMqttError_t connectStatus = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pNewMqttConnection = NULL; - _mqttOperation_t * pConnectOperation = NULL; - - /* Default CONNECT serializer function. */ - AwsIotMqttError_t ( * serializeConnect )( const AwsIotMqttConnectInfo_t * const, - uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializeConnect; - - /* Check that the network interface is valid. */ - if( AwsIotMqttInternal_ValidateNetIf( pNetworkInterface ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Choose a CONNECT serializer function. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNetworkInterface->serialize.connect != NULL ) - { - serializeConnect = pNetworkInterface->serialize.connect; - } - #endif - - /* Check that the connection info is valid. */ - if( AwsIotMqttInternal_ValidateConnect( pConnectInfo ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* If will info is provided, check that it is valid. */ - if( pConnectInfo->pWillInfo != NULL ) - { - if( AwsIotMqttInternal_ValidatePublish( pConnectInfo->awsIotMqttMode, - pConnectInfo->pWillInfo ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Will message payloads cannot be larger than 65535. This restriction - * applies only to will messages, and not normal PUBLISH messages. */ - if( pConnectInfo->pWillInfo->payloadLength > UINT16_MAX ) - { - IotLogError( "Will payload cannot be larger than 65535." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - } - - /* If previous subscriptions are provided, check that they are valid. */ - if( ( pConnectInfo->cleanSession == false ) && - ( pConnectInfo->pPreviousSubscriptions != NULL ) ) - { - if( AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - } - - IotLogInfo( "Establishing new MQTT connection." ); - - /* Create a CONNECT operation. */ - connectStatus = AwsIotMqttInternal_CreateOperation( &pConnectOperation, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL ); - - if( connectStatus != AWS_IOT_MQTT_SUCCESS ) - { - return connectStatus; - } - - /* Ensure the members set by operation creation and serialization - * are appropriate for a blocking CONNECT. */ - AwsIotMqtt_Assert( pConnectOperation->pPublishRetry == NULL ); - AwsIotMqtt_Assert( pConnectOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - AwsIotMqtt_Assert( ( pConnectOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) - == AWS_IOT_MQTT_FLAG_WAITABLE ); - - /* Set the operation type. */ - pConnectOperation->operation = AWS_IOT_MQTT_CONNECT; - - /* Allocate memory to store data for the new MQTT connection. */ - pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, - pNetworkInterface, - pConnectInfo->keepAliveSeconds ); - - if( pNewMqttConnection == NULL ) - { - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - - return AWS_IOT_MQTT_NO_MEMORY; - } - - /* Set the MQTT connection. */ - pConnectOperation->pMqttConnection = pNewMqttConnection; - - /* Add previous session subscriptions. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) - { - /* Previous subscription count should have been validated as nonzero. */ - AwsIotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); - - connectStatus = AwsIotMqttInternal_AddSubscriptions( pNewMqttConnection, - 2, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ); - - if( connectStatus != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - _destroyMqttConnection( pNewMqttConnection ); - - return connectStatus; - } - } - - /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ - connectStatus = serializeConnect( pConnectInfo, - &( pConnectOperation->pMqttPacket ), - &( pConnectOperation->packetSize ) ); - - if( connectStatus != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - _destroyMqttConnection( pNewMqttConnection ); - - return connectStatus; - } - - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pConnectOperation->packetSize > 0 ); - - /* Set the output parameter so it may be used by the network receive callback. */ - *pMqttConnection = pNewMqttConnection; - - /* Prevent another CONNECT operation from using the network. */ - IotMutex_Lock( &_connectMutex ); - - /* Add the CONNECT operation to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pConnectOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to enqueue CONNECT for sending." ); - connectStatus = AWS_IOT_MQTT_NO_MEMORY; - AwsIotMqttInternal_DestroyOperation( pConnectOperation ); - } - else - { - /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - connectStatus = AwsIotMqtt_Wait( ( AwsIotMqttReference_t ) pConnectOperation, - timeoutMs ); - } - - /* Unlock the CONNECT mutex. */ - IotMutex_Unlock( &_connectMutex ); - - /* Arm the timer for the first keep alive expiration if keep-alive is - * active for this connection. */ - if( ( connectStatus == AWS_IOT_MQTT_SUCCESS ) && - ( pNewMqttConnection->keepAliveSeconds > 0 ) ) - { - IotLogDebug( "Starting new MQTT connection timer." ); - - if( IotClock_TimerArm( &( pNewMqttConnection->timer ), - pNewMqttConnection->pKeepAliveEvent->expirationTime - IotClock_GetTimeMs(), - 0 ) == false ) - { - IotLogError( "Failed to start connection timer for new MQTT connection" ); - - connectStatus = AWS_IOT_MQTT_INIT_FAILED; - } - } - - /* Check the status of the CONNECT operation. */ - if( connectStatus == AWS_IOT_MQTT_SUCCESS ) - { - IotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); - } - else - { - /* Otherwise, free resources and log an error. */ - _destroyMqttConnection( pNewMqttConnection ); - *pMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - - IotLogError( "Failed to establish new MQTT connection, error %s.", - AwsIotMqtt_strerror( connectStatus ) ); - } - - return connectStatus; -} - -/*-----------------------------------------------------------*/ - -void AwsIotMqtt_Disconnect( AwsIotMqttConnection_t mqttConnection, - bool cleanupOnly ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; - _mqttOperation_t * pDisconnectOperation = NULL; - - IotLogInfo( "Disconnecting MQTT connection %p.", pMqttConnection ); - - /* Purge all of this connection's subscriptions. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _mqttSubscription_shouldRemove, - NULL, - AwsIotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - /* Lock the connection timer mutex to block the timer thread. */ - IotMutex_Lock( &( pMqttConnection->timerMutex ) ); - - /* Purge all of this connection's pending operations and timer events. */ - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - IotQueue_RemoveAllMatches( &( _IotMqttSend.queue ), - _mqttOperation_matchConnection, - pMqttConnection, - AwsIotMqttInternal_DestroyOperation, - offsetof( _mqttOperation_t, link ) ); - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - - IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); - IotListDouble_RemoveAllMatches( &( _IotMqttPendingResponse ), - _mqttOperation_matchConnection, - pMqttConnection, - AwsIotMqttInternal_DestroyOperation, - offsetof( _mqttOperation_t, link ) ); - IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); - - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - IotQueue_RemoveAllMatches( &( _IotMqttCallback.queue ), - _mqttOperation_matchConnection, - pMqttConnection, - AwsIotMqttInternal_DestroyOperation, - offsetof( _mqttOperation_t, link ) ); - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - - IotListDouble_RemoveAll( &( pMqttConnection->timerEventList ), - AwsIotMqtt_FreeTimerEvent, - offsetof( _mqttTimerEvent_t, link ) ); - - /* Stop the connection timer. */ - IotLogDebug( "Stopping connection timer." ); - IotClock_TimerDestroy( &( pMqttConnection->timer ) ); - - /* Only send a DISCONNECT packet if no error occurred and the "cleanup only" - * option is false. */ - if( ( pMqttConnection->errorOccurred == false ) && ( cleanupOnly == false ) ) - { - /* Create a DISCONNECT operation. This function blocks until the DISCONNECT - * packet is sent, so it sets AWS_IOT_MQTT_FLAG_WAITABLE. */ - status = AwsIotMqttInternal_CreateOperation( &pDisconnectOperation, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL ); - - if( status == AWS_IOT_MQTT_SUCCESS ) - { - /* Ensure that the members set by operation creation and serialization - * are appropriate for a blocking DISCONNECT. */ - AwsIotMqtt_Assert( pDisconnectOperation->pPublishRetry == NULL ); - AwsIotMqtt_Assert( pDisconnectOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - AwsIotMqtt_Assert( ( pDisconnectOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) - == AWS_IOT_MQTT_FLAG_WAITABLE ); - - /* Set the remaining members of the DISCONNECT operation. */ - pDisconnectOperation->operation = AWS_IOT_MQTT_DISCONNECT; - pDisconnectOperation->pMqttConnection = pMqttConnection; - - /* Choose a disconnect serializer. */ - AwsIotMqttError_t ( * serializeDisconnect )( uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializeDisconnect; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.disconnect != NULL ) - { - serializeDisconnect = pMqttConnection->network.serialize.disconnect; - } - #endif - - /* Generate a DISCONNECT packet. */ - status = serializeDisconnect( &( pDisconnectOperation->pMqttPacket ), - &( pDisconnectOperation->packetSize ) ); - } - - if( status == AWS_IOT_MQTT_SUCCESS ) - { - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pDisconnectOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pDisconnectOperation->packetSize > 0 ); - - /* Add the DISCONNECT operation to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pDisconnectOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to enqueue DISCONNECT for sending." ); - AwsIotMqttInternal_DestroyOperation( pDisconnectOperation ); - } - else - { - /* Wait until the DISCONNECT packet has been transmitted. DISCONNECT - * should always be successful because it does not rely on any incoming - * data. */ - status = AwsIotMqtt_Wait( ( AwsIotMqttReference_t ) pDisconnectOperation, - 0 ); - - /* A wait on DISCONNECT should only ever return SUCCESS or SEND ERROR. */ - AwsIotMqtt_Assert( ( status == AWS_IOT_MQTT_SUCCESS ) || - ( status == AWS_IOT_MQTT_SEND_ERROR ) ); - - IotLogInfo( "MQTT connection %p disconnected.", pMqttConnection ); - } - } - } - - /* Free the memory in use by the keep-alive operation. */ - if( pMqttConnection->pPingreqOperation != NULL ) - { - AwsIotMqttInternal_DestroyOperation( pMqttConnection->pPingreqOperation ); - } - - /* Unlock the connection timer mutex. */ - IotMutex_Unlock( &( pMqttConnection->timerMutex ) ); - - /* Close the network connection regardless of whether an MQTT DISCONNECT - * packet was sent. */ - if( pMqttConnection->network.disconnect != NULL ) - { - pMqttConnection->network.disconnect( 0, pMqttConnection->network.pDisconnectContext ); - } - else - { - IotLogWarn( "No disconnect function was set. Network connection not closed." ); - } - - /* Destroy the MQTT connection's mutexes. */ - IotMutex_Destroy( &( pMqttConnection->timerMutex ) ); - IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); - - /* Free the memory used by this connection. */ - AwsIotMqtt_FreeConnection( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_Subscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pSubscribeRef ) -{ - return _subscriptionCommon( AWS_IOT_MQTT_SUBSCRIBE, - mqttConnection, - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pSubscribeRef ); -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_TimedSubscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint64_t timeoutMs ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttReference_t subscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - - /* Flags are not used, but the parameter is present for future compatibility. */ - ( void ) flags; - - /* Call the asynchronous SUBSCRIBE function. */ - status = AwsIotMqtt_Subscribe( mqttConnection, - pSubscriptionList, - subscriptionCount, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeRef ); - - /* Wait for the SUBSCRIBE operation to complete. */ - if( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - status = AwsIotMqtt_Wait( subscribeRef, timeoutMs ); - } - - /* Ensure that a status was set. */ - AwsIotMqtt_Assert( status != AWS_IOT_MQTT_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_Unsubscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pUnsubscribeRef ) -{ - return _subscriptionCommon( AWS_IOT_MQTT_UNSUBSCRIBE, - mqttConnection, - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pUnsubscribeRef ); -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_TimedUnsubscribe( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint64_t timeoutMs ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttReference_t unsubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - - /* Flags are not used, but the parameter is present for future compatibility. */ - ( void ) flags; - - /* Call the asynchronous UNSUBSCRIBE function. */ - status = AwsIotMqtt_Unsubscribe( mqttConnection, - pSubscriptionList, - subscriptionCount, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeRef ); - - /* Wait for the UNSUBSCRIBE operation to complete. */ - if( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - status = AwsIotMqtt_Wait( unsubscribeRef, timeoutMs ); - } - - /* Ensure that a status was set. */ - AwsIotMqtt_Assert( status != AWS_IOT_MQTT_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_Publish( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo, - AwsIotMqttReference_t * const pPublishRef ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pPublishOperation = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; - - /* Default PUBLISH serializer function. */ - AwsIotMqttError_t ( * serializePublish )( const AwsIotMqttPublishInfo_t * const, - uint8_t ** const, - size_t * const, - uint16_t * const ) = AwsIotMqttInternal_SerializePublish; - - /* Choose a PUBLISH serializer function. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.publish != NULL ) - { - serializePublish = pMqttConnection->network.serialize.publish; - } - #endif - - /* Check that the PUBLISH information is valid. */ - if( AwsIotMqttInternal_ValidatePublish( pMqttConnection->awsIotMqttMode, - pPublishInfo ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Check that no notification is requested for a QoS 0 publish. */ - if( pPublishInfo->QoS == 0 ) - { - if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) || - ( pCallbackInfo != NULL ) ) - { - IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - if( pPublishRef != NULL ) - { - IotLogWarn( "Ignoring pPublishRef parameter for QoS 0 publish." ); - } - } - - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) && - ( pPublishRef == NULL ) ) - { - IotLogError( "Reference must be provided for a waitable PUBLISH." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - /* Create a PUBLISH operation. */ - status = AwsIotMqttInternal_CreateOperation( &pPublishOperation, - flags, - pCallbackInfo ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - return status; - } - - /* Check the PUBLISH operation data and set the remaining members. */ - AwsIotMqtt_Assert( pPublishOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - pPublishOperation->operation = AWS_IOT_MQTT_PUBLISH_TO_SERVER; - pPublishOperation->pMqttConnection = pMqttConnection; - - /* Generate a PUBLISH packet from pPublishInfo. */ - status = serializePublish( pPublishInfo, - &( pPublishOperation->pMqttPacket ), - &( pPublishOperation->packetSize ), - &( pPublishOperation->packetIdentifier ) ); - - if( status != AWS_IOT_MQTT_SUCCESS ) - { - AwsIotMqttInternal_DestroyOperation( pPublishOperation ); - - return status; - } - - /* Check the serialized MQTT packet. */ - AwsIotMqtt_Assert( pPublishOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pPublishOperation->packetSize > 0 ); - - if( pPublishInfo->QoS == 0 ) - { - AwsIotMqtt_Assert( pPublishOperation->packetIdentifier == 0 ); - } - else - { - AwsIotMqtt_Assert( pPublishOperation->packetIdentifier != 0 ); - } - - /* Initialize PUBLISH retry for QoS 1 PUBLISH if retryLimit is set. */ - if( ( pPublishInfo->QoS > 0 ) && ( pPublishInfo->retryLimit > 0 ) ) - { - /* Allocate a timer event to handle retries. */ - pPublishOperation->pPublishRetry = AwsIotMqtt_MallocTimerEvent( sizeof( _mqttTimerEvent_t ) ); - - if( pPublishOperation->pPublishRetry == NULL ) - { - AwsIotMqttInternal_DestroyOperation( pPublishOperation ); - - return AWS_IOT_MQTT_NO_MEMORY; - } - - /* Set the members of the retry timer event. */ - ( void ) ( memset( pPublishOperation->pPublishRetry, 0x00, sizeof( _mqttTimerEvent_t ) ) ); - pPublishOperation->pPublishRetry->pOperation = pPublishOperation; - pPublishOperation->pPublishRetry->retry.limit = pPublishInfo->retryLimit; - pPublishOperation->pPublishRetry->retry.nextPeriod = pPublishInfo->retryMs; - } - - /* Set the reference, if provided. This should be set before the publish - * is pushed to the send queue to avoid a race condition. */ - if( ( pPublishInfo->QoS > 0 ) && ( pPublishRef != NULL ) ) - { - *pPublishRef = pPublishOperation; - } - - /* Add the PUBLISH operation to the send queue for network transmission. */ - if( AwsIotMqttInternal_EnqueueOperation( pPublishOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to enqueue PUBLISH for sending." ); - - AwsIotMqttInternal_DestroyOperation( pPublishOperation ); - - /* Clear the previously set (and now invalid) reference. */ - if( ( pPublishInfo->QoS > 0 ) && ( pPublishRef != NULL ) ) - { - *pPublishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - } - - return AWS_IOT_MQTT_NO_MEMORY; - } - - /* A QoS 0 PUBLISH is considered successful as soon as it is added to the - * send queue. */ - if( pPublishInfo->QoS == 0 ) - { - return AWS_IOT_MQTT_SUCCESS; - } - - IotLogInfo( "MQTT PUBLISH operation queued." ); - - /* QoS 1 and QoS 2 PUBLISH messages are awaiting responses. */ - return AWS_IOT_MQTT_STATUS_PENDING; -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_TimedPublish( AwsIotMqttConnection_t mqttConnection, - const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint32_t flags, - uint64_t timeoutMs ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER, - * pPublishRef = NULL; - - /* Clear the flags. */ - flags = 0; - - /* Set the waitable flag and reference for QoS 1 PUBLISH. */ - if( pPublishInfo->QoS > 0 ) - { - flags = AWS_IOT_MQTT_FLAG_WAITABLE; - pPublishRef = &publishRef; - } - - /* Call the asynchronous PUBLISH function. */ - status = AwsIotMqtt_Publish( mqttConnection, - pPublishInfo, - flags, - NULL, - pPublishRef ); - - /* Wait for a queued QoS 1 PUBLISH to complete. */ - if( ( pPublishInfo->QoS > 0 ) && ( status == AWS_IOT_MQTT_STATUS_PENDING ) ) - { - status = AwsIotMqtt_Wait( publishRef, timeoutMs ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqtt_Wait( AwsIotMqttReference_t reference, - uint64_t timeoutMs ) -{ - bool publishRetryActive = false; - uint64_t startTime = 0, currentTime = 0, elapsedTime = 0, remainingMs = timeoutMs; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; - - /* Check reference. */ - if( AwsIotMqttInternal_ValidateReference( reference ) == false ) - { - return AWS_IOT_MQTT_BAD_PARAMETER; - } - - IotLogInfo( "Waiting for operation %s to complete.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - /* Wait for the operation to be sent once. This wait should return quickly. */ - IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); - - /* Check any status set by the send thread. Block the receive callback - * during this check by locking the mutex for operations pending responses. */ - IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); - status = pOperation->status; - IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); - - if( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - /* Check if this operation is a PUBLISH with retry. Block the timer - * thread during this check by locking the connection mutex. */ - if( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) - { - IotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); - publishRetryActive = ( pOperation->pPublishRetry != NULL ); - IotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); - } - - /* Wait for a response to be received from the network. Record when - * the wait begins for a PUBLISH with retry. */ - if( publishRetryActive == true ) - { - startTime = IotClock_GetTimeMs(); - AwsIotMqtt_Assert( startTime > 0 ); - } - - /* All MQTT operations except PUBLISH with retry will have a status after - * the second block on the wait semaphore. PUBLISH with retry may require - * multiple blocks (once more per each retransmission). */ - while( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - /* Only perform the remaining time calculation for PUBLISH with retry. */ - if( publishRetryActive == true ) - { - /* Get current time. */ - currentTime = IotClock_GetTimeMs(); - AwsIotMqtt_Assert( currentTime >= startTime ); - - /* Calculate elapsed time. */ - elapsedTime = currentTime - startTime; - - /* Check for timeout with elapsed time. */ - if( elapsedTime > timeoutMs ) - { - status = AWS_IOT_MQTT_TIMEOUT; - break; - } - - /* Calculate the remaining wait time. */ - remainingMs = timeoutMs - elapsedTime; - } - - /* Block on the wait semaphore. */ - if( IotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), - remainingMs ) == false ) - { - /* No status received before timeout. */ - status = AWS_IOT_MQTT_TIMEOUT; - - /* A timed out operation may still pending a network response. */ - IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); - ( void ) IotListDouble_RemoveFirstMatch( &( _IotMqttPendingResponse ), - NULL, - NULL, - pOperation ); - IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); - } - else - { - /* For a PUBLISH with retry, block the timer thread before reading a - * status. */ - if( publishRetryActive == true ) - { - IotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); - } - - /* Successfully received a notification of completion. Retrieve the - * status. */ - status = pOperation->status; - - if( publishRetryActive == true ) - { - IotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); - } - } - } - } - else - { - /* If a status was set by the send thread, wait for the send thread to be - * completely done with the operation. */ - IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); - } - - /* A completed operation should not be linked. */ - AwsIotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); - - /* Remove any lingering subscriptions from a timed out SUBSCRIBE. If - * a SUBSCRIBE fails for any other reason, its subscription have already - * been removed. */ - if( ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) && - ( status == AWS_IOT_MQTT_TIMEOUT ) ) - { - AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, - pOperation->packetIdentifier, - -1 ); - } - - IotLogInfo( "MQTT operation %s complete with result %s.", - AwsIotMqtt_OperationType( pOperation->operation ), - AwsIotMqtt_strerror( status ) ); - - /* The operation is complete; it can be destroyed. PINGREQ operations are - * destroyed by AwsIotMqtt_Disconnect and not here. If the operation is a - * PINGRESP, reset it. */ - if( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) - { - AwsIotMqttInternal_DestroyOperation( pOperation ); - } - else - { - AwsIotMqtt_Assert( IotSemaphore_GetCount( &( pOperation->notify.waitSemaphore ) ) == 0 ); - pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; - } - - /* A complete operation status (not pending) should be set. */ - AwsIotMqtt_Assert( status != AWS_IOT_MQTT_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotMqtt_strerror( AwsIotMqttError_t status ) -{ - switch( status ) - { - case AWS_IOT_MQTT_SUCCESS: - - return "SUCCESS"; - - case AWS_IOT_MQTT_STATUS_PENDING: - - return "PENDING"; - - case AWS_IOT_MQTT_INIT_FAILED: - - return "INITIALIZATION FAILED"; - - case AWS_IOT_MQTT_BAD_PARAMETER: - - return "BAD PARAMETER"; - - case AWS_IOT_MQTT_NO_MEMORY: - - return "NO MEMORY"; - - case AWS_IOT_MQTT_SEND_ERROR: - - return "NETWORK SEND ERROR"; - - case AWS_IOT_MQTT_BAD_RESPONSE: - - return "BAD RESPONSE RECEIVED"; - - case AWS_IOT_MQTT_TIMEOUT: - - return "TIMEOUT"; - - case AWS_IOT_MQTT_SERVER_REFUSED: - - return "SERVER REFUSED"; - - case AWS_IOT_MQTT_RETRY_NO_RESPONSE: - - return "NO RESPONSE"; - - default: - - return "INVALID STATUS"; - } -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotMqtt_OperationType( AwsIotMqttOperationType_t operation ) -{ - switch( operation ) - { - case AWS_IOT_MQTT_CONNECT: - - return "CONNECT"; - - case AWS_IOT_MQTT_PUBLISH_TO_SERVER: - - return "PUBLISH"; - - case AWS_IOT_MQTT_PUBACK: - - return "PUBACK"; - - case AWS_IOT_MQTT_SUBSCRIBE: - - return "SUBSCRIBE"; - - case AWS_IOT_MQTT_UNSUBSCRIBE: - - return "UNSUBSCRIBE"; - - case AWS_IOT_MQTT_PINGREQ: - - return "PINGREQ"; - - case AWS_IOT_MQTT_DISCONNECT: - - return "DISCONNECT"; - - default: - - return "INVALID OPERATION"; - } -} - -/*-----------------------------------------------------------*/ - -/* If the MQTT library is being tested, include a file that allows access to - * internal functions and variables. */ -#if AWS_IOT_MQTT_TEST == 1 - #include "aws_iot_test_access_mqtt_api.c" -#endif diff --git a/lib/source/mqtt/aws_iot_mqtt_operation.c b/lib/source/mqtt/aws_iot_mqtt_operation.c deleted file mode 100644 index 8cd4316018..0000000000 --- a/lib/source/mqtt/aws_iot_mqtt_operation.c +++ /dev/null @@ -1,735 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_mqtt_operation.c - * @brief Implements functions that process MQTT operations. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief First parameter to #_mqttOperation_match. - */ -typedef struct _operationMatchParam -{ - AwsIotMqttOperationType_t operation; /**< @brief The operation to look for. */ - const uint16_t * pPacketIdentifier; /**< @brief The packet identifier associated with the operation. - * Set to `NULL` to ignore packet identifier. */ -} _operationMatchParam_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Match an MQTT operation by type and packet identifier. - * - * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. - * @param[in] pMatch Pointer to an #_operationMatchParam_t. - * - * @return `true` if the operation matches the parameters in `pArgument`; `false` - * otherwise. - */ -static bool _mqttOperation_match( const IotLink_t * pOperationLink, - void * pMatch ); - -/** - * @brief Calculate the timeout and period of the next PUBLISH retry event. - * - * Calculates the next time a PUBLISH should be transmitted and places the - * retry event in a timer event queue. - * - * @param[in] pMqttConnection The MQTT connection associated with the PUBLISH. - * @param[in] pPublishRetry The current PUBLISH retry event. - * - * @return true if the retry event was successfully placed in a timer event queue; - * false otherwise. - */ -static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnection, - _mqttTimerEvent_t * const pPublishRetry ); - -/** - * @brief Send the packet associated with an MQTT operation. - * - * Transmits the MQTT packet from a pending operation to the MQTT server, then - * places the operation in the list of MQTT operations awaiting responses from - * the server. - * - * @param[in] pOperation The MQTT operation with the packet to send. - */ -static void _invokeSend( _mqttOperation_t * const pOperation ); - -/** - * @brief Invoke user-provided callback functions. - * - * Invokes callback functions associated with either a completed MQTT operation - * or an incoming PUBLISH message. - * - * @param[in] pOperation The MQTT operation with the callback to invoke. - */ -static void _invokeCallback( _mqttOperation_t * const pOperation ); - -/** - * @brief Process an MQTT operation. - * - * This function is a thread routine that either sends an MQTT packet for - * in-progress operations, notifies of an operation's completion, or notifies - * of an incoming PUBLISH message. - * - * @param[in] pArgument The address of either #_IotMqttCallback (to invoke a user - * callback) or #_IotMqttSend (to send an MQTT packet). - */ -static void _processOperation( void * pArgument ); - -/*-----------------------------------------------------------*/ - -/** - * Queues of MQTT operations waiting to be processed. - */ -_mqttOperationQueue_t _IotMqttCallback; /**< @brief Queue of MQTT operations waiting for their callback to be invoked. */ -_mqttOperationQueue_t _IotMqttSend; /**< @brief Queue of MQTT operations waiting to be sent. */ -IotMutex_t _IotMqttQueueMutex; /**< @brief Protects both #_IotMqttSend and #_IotMqttCallback from concurrent access. */ - -/* List of MQTT operations awaiting a network response. */ -IotListDouble_t _IotMqttPendingResponse; /**< @brief List of MQTT operations awaiting a response from the MQTT server. */ -IotMutex_t _IotMqttPendingResponseMutex; /**< @brief Protects #_IotMqttPendingResponse from concurrent access. */ - -/*-----------------------------------------------------------*/ - -static bool _mqttOperation_match( const IotLink_t * pOperationLink, - void * pMatch ) -{ - bool match = false; - _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, - pOperationLink, - link ); - _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; - - /* Check for matching operations. */ - if( pParam->operation == pOperation->operation ) - { - /* Check for matching packet identifiers. */ - if( pParam->pPacketIdentifier == NULL ) - { - match = true; - } - else - { - match = ( *( pParam->pPacketIdentifier ) == pOperation->packetIdentifier ); - } - } - - return match; -} - -/*-----------------------------------------------------------*/ - -static bool _calculateNextPublishRetry( _mqttConnection_t * const pMqttConnection, - _mqttTimerEvent_t * const pPublishRetry ) -{ - bool status = true; - _mqttTimerEvent_t * pTimerListHead = NULL; - - /* Check arguments. */ - AwsIotMqtt_Assert( pPublishRetry->pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); - AwsIotMqtt_Assert( pPublishRetry->retry.limit > 0 ); - AwsIotMqtt_Assert( pPublishRetry->retry.nextPeriod <= AWS_IOT_MQTT_RETRY_MS_CEILING ); - - /* Increment the number of retries. */ - pPublishRetry->retry.count++; - - /* Calculate when the PUBLISH retry event should expire relative to the current - * time. */ - pPublishRetry->expirationTime = IotClock_GetTimeMs(); - - if( pPublishRetry->retry.count > pPublishRetry->retry.limit ) - { - /* Retry limit reached. Check for a response shortly. */ - pPublishRetry->expirationTime += AWS_IOT_MQTT_RESPONSE_WAIT_MS; - } - else - { - /* Expire at the next retry period. */ - pPublishRetry->expirationTime += pPublishRetry->retry.nextPeriod; - - /* Calculate the next retry period. PUBLISH retries use a truncated exponential - * backoff strategy. */ - if( pPublishRetry->retry.nextPeriod < AWS_IOT_MQTT_RETRY_MS_CEILING ) - { - pPublishRetry->retry.nextPeriod *= 2; - - if( pPublishRetry->retry.nextPeriod > AWS_IOT_MQTT_RETRY_MS_CEILING ) - { - pPublishRetry->retry.nextPeriod = AWS_IOT_MQTT_RETRY_MS_CEILING; - } - } - } - - /* Lock the connection timer mutex to block the timer thread. */ - IotMutex_Lock( &( pMqttConnection->timerMutex ) ); - - /* Peek the current head of the timer event list. If the PUBLISH retry expires - * sooner, re-arm the timer to expire at the PUBLISH retry's expiration time. */ - pTimerListHead = IotLink_Container( _mqttTimerEvent_t, - IotListDouble_PeekHead( &( pMqttConnection->timerEventList ) ), - link ); - - if( ( pTimerListHead == NULL ) || - ( pTimerListHead->expirationTime > pPublishRetry->expirationTime ) ) - { - status = IotClock_TimerArm( &( pMqttConnection->timer ), - pPublishRetry->expirationTime - IotClock_GetTimeMs(), - 0 ); - - if( status == false ) - { - IotLogError( "Failed to re-arm timer for connection %p.", pMqttConnection ); - } - } - - /* Insert the PUBLISH retry into the timer event list only if the timer - * is guaranteed to expire before its expiration time. */ - if( status == true ) - { - IotListDouble_InsertSorted( &( pMqttConnection->timerEventList ), - &( pPublishRetry->link ), - AwsIotMqttInternal_TimerEventCompare ); - } - - IotMutex_Unlock( &( pMqttConnection->timerMutex ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _invokeSend( _mqttOperation_t * const pOperation ) -{ - bool waitableOperation = false; - - IotLogDebug( "Operation %s received from queue. Sending data over network.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - /* Check if this is a waitable operation. */ - waitableOperation = ( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) - == AWS_IOT_MQTT_FLAG_WAITABLE ); - - /* Transmit the MQTT packet from the operation over the network. */ - if( pOperation->pMqttConnection->network.send( pOperation->pMqttConnection->network.pSendContext, - pOperation->pMqttPacket, - pOperation->packetSize ) != pOperation->packetSize ) - { - /* Transmission failed. */ - IotLogError( "Failed to send data for operation %s.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - pOperation->status = AWS_IOT_MQTT_SEND_ERROR; - } - else - { - /* DISCONNECT operations are considered successful upon successful - * transmission. */ - if( pOperation->operation == AWS_IOT_MQTT_DISCONNECT ) - { - pOperation->status = AWS_IOT_MQTT_SUCCESS; - } - /* Calculate the details of the next PUBLISH retry event. */ - else if( pOperation->pPublishRetry != NULL ) - { - /* Only a PUBLISH may have a retry event. */ - AwsIotMqtt_Assert( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ); - - if( _calculateNextPublishRetry( pOperation->pMqttConnection, - pOperation->pPublishRetry ) == false ) - { - /* PUBLISH retry failed to re-arm connection timer. Retransmission - * will not be sent. */ - pOperation->status = AWS_IOT_MQTT_SEND_ERROR; - } - } - } - - /* Once the MQTT packet has been sent, it may be freed if it will not be - * retransmitted and it's not a PINGREQ. Additionally, the entire operation - * may be destroyed if no notification method is set. */ - if( ( pOperation->pPublishRetry == NULL ) && - ( pOperation->operation != AWS_IOT_MQTT_PINGREQ ) ) - { - /* Choose a free packet function. */ - void ( * freePacket )( uint8_t * ) = AwsIotMqttInternal_FreePacket; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pOperation->pMqttConnection->network.freePacket != NULL ) - { - freePacket = pOperation->pMqttConnection->network.freePacket; - } - #endif - - freePacket( pOperation->pMqttPacket ); - pOperation->pMqttPacket = NULL; - - if( ( waitableOperation == false ) && - ( pOperation->notify.callback.function == NULL ) ) - { - AwsIotMqttInternal_DestroyOperation( pOperation ); - - return; - } - } - - /* If a status was set by this function, notify of completion. */ - if( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) - { - AwsIotMqttInternal_Notify( pOperation ); - } - /* Otherwise, add operation to the list of MQTT operations pending responses. */ - else - { - IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); - IotListDouble_InsertTail( &( _IotMqttPendingResponse ), &( pOperation->link ) ); - IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); - } - - /* Notify a waitable operation that it has been sent. */ - if( waitableOperation == true ) - { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - } -} - -/*-----------------------------------------------------------*/ - -static void _invokeCallback( _mqttOperation_t * const pOperation ) -{ - _mqttOperation_t * i = NULL, * pCurrent = NULL; - AwsIotMqttCallbackParam_t callbackParam = { .operation = { 0 } }; - - if( pOperation->incomingPublish == true ) - { - /* Set the pointer to the first PUBLISH. */ - i = pOperation; - - /* Process each PUBLISH in the operation. */ - while( i != NULL ) - { - /* Save a pointer to the current PUBLISH and move the iterating - * pointer. */ - pCurrent = i; - i = i->pNextPublish; - - /* Process the current PUBLISH. */ - if( ( pCurrent->publishInfo.pPayload != NULL ) && - ( pCurrent->publishInfo.pTopicName != NULL ) ) - { - callbackParam.message.info = pCurrent->publishInfo; - - AwsIotMqttInternal_ProcessPublish( pCurrent->pMqttConnection, - &callbackParam ); - } - - /* Free any buffers associated with the current PUBLISH message. */ - if( pCurrent->freeReceivedData != NULL ) - { - AwsIotMqtt_Assert( pCurrent->freeReceivedData != NULL ); - pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); - } - - /* Free the current PUBLISH. */ - AwsIotMqtt_FreeOperation( pCurrent ); - } - } - else - { - /* Operations with no callback should not have been passed. */ - AwsIotMqtt_Assert( pOperation->notify.callback.function != NULL ); - - IotLogDebug( "Operation %s received from queue. Invoking user callback.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - /* Set callback parameters. */ - callbackParam.mqttConnection = pOperation->pMqttConnection; - callbackParam.operation.type = pOperation->operation; - callbackParam.operation.reference = pOperation; - callbackParam.operation.result = pOperation->status; - - /* Invoke user callback function. */ - pOperation->notify.callback.function( pOperation->notify.callback.param1, - &callbackParam ); - - /* Once the user-provided callback returns, the operation can be destroyed. */ - AwsIotMqttInternal_DestroyOperation( pOperation ); - } -} - -/*-----------------------------------------------------------*/ - -static void _processOperation( void * pArgument ) -{ - _mqttOperation_t * pOperation = NULL; - _mqttOperationQueue_t * pQueue = ( _mqttOperationQueue_t * ) pArgument; - - /* There are only two valid values for this function's argument. */ - AwsIotMqtt_Assert( ( pQueue == &( _IotMqttCallback ) ) || - ( pQueue == &( _IotMqttSend ) ) ); - - IotLogDebug( "New thread for processing MQTT operations started." ); - - while( true ) - { - IotLogDebug( "Removing oldest operation from MQTT pending operations." ); - - /* Remove the oldest operation from the queue of pending MQTT operations. */ - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - - pOperation = IotLink_Container( _mqttOperation_t, - IotQueue_Dequeue( &( pQueue->queue ) ), - link ); - - /* If no operation was received, this thread will terminate. Increment - * number of available threads. */ - if( pOperation == NULL ) - { - IotSemaphore_Post( &( pQueue->availableThreads ) ); - } - - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - - /* Terminate thread if no operation was received. */ - if( pOperation == NULL ) - { - break; - } - - /* Run thread routine based on given queue. */ - if( pQueue == &( _IotMqttCallback ) ) - { - /* Only incoming PUBLISH messages and completed operations should invoke - * the callback routine. */ - AwsIotMqtt_Assert( ( pOperation->incomingPublish == true ) || - ( pOperation->status != AWS_IOT_MQTT_STATUS_PENDING ) ); - - _invokeCallback( pOperation ); - } - else - { - /* Only operations with an allocated packet should be sent. */ - AwsIotMqtt_Assert( pOperation->status == AWS_IOT_MQTT_STATUS_PENDING ); - AwsIotMqtt_Assert( pOperation->pMqttPacket != NULL ); - AwsIotMqtt_Assert( pOperation->packetSize != 0 ); - - _invokeSend( pOperation ); - } - } - - IotLogDebug( "No more pending operations. Terminating MQTT processing thread." ); -} - -/*-----------------------------------------------------------*/ - -int AwsIotMqttInternal_TimerEventCompare( const IotLink_t * const pTimerEventLink1, - const IotLink_t * const pTimerEventLink2 ) -{ - const _mqttTimerEvent_t * pTimerEvent1 = IotLink_Container( _mqttTimerEvent_t, - pTimerEventLink1, - link ); - const _mqttTimerEvent_t * pTimerEvent2 = IotLink_Container( _mqttTimerEvent_t, - pTimerEventLink2, - link ); - - if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) - { - return -1; - } - - if( pTimerEvent1->expirationTime > pTimerEvent2->expirationTime ) - { - return 1; - } - - return 0; -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqttInternal_CreateOperation( _mqttOperation_t ** const pNewOperation, - uint32_t flags, - const AwsIotMqttCallbackInfo_t * const pCallbackInfo ) -{ - _mqttOperation_t * pOperation = NULL; - - IotLogDebug( "Creating new MQTT operation record." ); - - /* If the waitable flag is set, make sure that there's no callback. */ - if( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) - { - if( pCallbackInfo != NULL ) - { - IotLogError( "Callback should not be set for a waitable operation." ); - - return AWS_IOT_MQTT_BAD_PARAMETER; - } - } - - /* Allocate memory for a new operation. */ - pOperation = AwsIotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - if( pOperation == NULL ) - { - IotLogError( "Failed to allocate memory for new MQTT operation." ); - - return AWS_IOT_MQTT_NO_MEMORY; - } - - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( ( flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) - { - /* The wait semaphore counts up to 2: once for when the send thread completes, - * and once for when the entire operation completes. */ - if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 2 ) == false ) - { - IotLogError( "Failed to create semaphore for waitable MQTT operation." ); - - AwsIotMqtt_FreeOperation( pOperation ); - - return AWS_IOT_MQTT_NO_MEMORY; - } - } - else - { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) - { - pOperation->notify.callback = *pCallbackInfo; - } - } - - /* Initialize the flags and status members of the new operation. */ - pOperation->flags = flags; - pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; - - /* Set the output parameter. */ - *pNewOperation = pOperation; - - return AWS_IOT_MQTT_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -void AwsIotMqttInternal_DestroyOperation( void * pData ) -{ - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; - - IotLogDebug( "Destroying operation %s.", - AwsIotMqtt_OperationType( pOperation->operation ) ); - - /* If the MQTT packet is still allocated, free it. */ - if( pOperation->pMqttPacket != NULL ) - { - /* Choose a free packet function. */ - void ( * freePacket )( uint8_t * ) = AwsIotMqttInternal_FreePacket; - - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pOperation->pMqttConnection->network.freePacket != NULL ) - { - freePacket = pOperation->pMqttConnection->network.freePacket; - } - #endif - - freePacket( pOperation->pMqttPacket ); - } - - /* Check if this operation is a PUBLISH. Clean up its retry event if - * necessary. */ - if( ( pOperation->operation == AWS_IOT_MQTT_PUBLISH_TO_SERVER ) && - ( pOperation->pPublishRetry != NULL ) ) - { - IotMutex_Lock( &( pOperation->pMqttConnection->timerMutex ) ); - - /* Remove the timer event from the timer event list before freeing it. - * The return value of this function is not checked because it always - * equals the publish retry event or is NULL. */ - ( void ) IotListDouble_RemoveFirstMatch( &( pOperation->pMqttConnection->timerEventList ), - NULL, - NULL, - pOperation->pPublishRetry ); - - IotMutex_Unlock( &( pOperation->pMqttConnection->timerMutex ) ); - - AwsIotMqtt_FreeTimerEvent( pOperation->pPublishRetry ); - } - - /* Check if a wait semaphore was created for this operation. */ - if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) - { - IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); - } - - /* Free the memory used to hold operation data. */ - AwsIotMqtt_FreeOperation( pOperation ); -} - -/*-----------------------------------------------------------*/ - -AwsIotMqttError_t AwsIotMqttInternal_EnqueueOperation( _mqttOperation_t * const pOperation, - _mqttOperationQueue_t * const pQueue ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; - - /* The given operation must not already be queued. */ - AwsIotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); - - /* Check that the queue argument is either the callback queue or send queue. */ - AwsIotMqtt_Assert( ( pQueue == &( _IotMqttCallback ) ) || - ( pQueue == &( _IotMqttSend ) ) ); - - /* Lock mutex for exclusive access to queues. */ - IotMutex_Lock( &( _IotMqttQueueMutex ) ); - - /* Add operation to queue. */ - IotQueue_Enqueue( &( pQueue->queue ), &( pOperation->link ) ); - - /* Check if a new thread can be created. */ - if( IotSemaphore_TryWait( &( pQueue->availableThreads ) ) == true ) - { - /* Create new thread. */ - if( Iot_CreateDetachedThread( _processOperation, - pQueue, - IOT_THREAD_DEFAULT_PRIORITY, - IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) - { - /* New thread could not be created. Remove enqueued operation and - * report error. */ - IotQueue_Remove( &( pOperation->link ) ); - IotSemaphore_Post( &( pQueue->availableThreads ) ); - status = AWS_IOT_MQTT_NO_MEMORY; - } - } - - IotMutex_Unlock( &( _IotMqttQueueMutex ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -_mqttOperation_t * AwsIotMqttInternal_FindOperation( AwsIotMqttOperationType_t operation, - const uint16_t * const pPacketIdentifier ) -{ - _mqttOperation_t * pResult = NULL; - _operationMatchParam_t param = { 0 }; - - IotLogDebug( "Searching for in-progress operation %s in MQTT operations pending response.", - AwsIotMqtt_OperationType( operation ) ); - - if( pPacketIdentifier != NULL ) - { - IotLogDebug( "Searching for packet identifier %hu.", *pPacketIdentifier ); - } - - /* Set the search parameters. */ - param.operation = operation; - param.pPacketIdentifier = pPacketIdentifier; - - /* Find the first matching element in the list. */ - IotMutex_Lock( &( _IotMqttPendingResponseMutex ) ); - pResult = IotLink_Container( _mqttOperation_t, - IotListDouble_RemoveFirstMatch( &( _IotMqttPendingResponse ), - NULL, - _mqttOperation_match, - ¶m ), - link ); - IotMutex_Unlock( &( _IotMqttPendingResponseMutex ) ); - - /* The result will be NULL if no corresponding operation was found in the - * list. */ - if( pResult == NULL ) - { - IotLogDebug( "In-progress operation %s not found.", - AwsIotMqtt_OperationType( operation ) ); - } - else - { - IotLogDebug( "Found in-progress operation %s.", - AwsIotMqtt_OperationType( operation ) ); - } - - return pResult; -} - -/*-----------------------------------------------------------*/ - -void AwsIotMqttInternal_Notify( _mqttOperation_t * const pOperation ) -{ - /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected - * subscriptions are removed by the deserializer, so not removed here. */ - if( ( pOperation->operation == AWS_IOT_MQTT_SUBSCRIBE ) && - ( pOperation->status != AWS_IOT_MQTT_SUCCESS ) && - ( pOperation->status != AWS_IOT_MQTT_SERVER_REFUSED ) ) - { - AwsIotMqttInternal_RemoveSubscriptionByPacket( pOperation->pMqttConnection, - pOperation->packetIdentifier, - -1 ); - } - - /* If the operation is waitable, post to its wait semaphore and return. - * Otherwise, enqueue it for callback. */ - if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) == AWS_IOT_MQTT_FLAG_WAITABLE ) - { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - } - else - { - if( pOperation->notify.callback.function != NULL ) - { - if( AwsIotMqttInternal_EnqueueOperation( pOperation, - &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to create new callback thread." ); - } - } - else - { - /* Destroy the operation if no callback was set. */ - AwsIotMqttInternal_DestroyOperation( pOperation ); - } - } -} - -/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c new file mode 100644 index 0000000000..3b95bb5261 --- /dev/null +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -0,0 +1,1884 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_api.c + * @brief Implements most user-facing functions of the MQTT library. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/* Validate MQTT configuration settings. */ +#if IOT_MQTT_ENABLE_ASSERTS != 0 && IOT_MQTT_ENABLE_ASSERTS != 1 + #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." +#endif +#if IOT_MQTT_ENABLE_METRICS != 0 && IOT_MQTT_ENABLE_METRICS != 1 + #error "IOT_MQTT_ENABLE_METRICS must be 0 or 1." +#endif +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 + #error "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." +#endif +#if IOT_MQTT_TEST != 0 && IOT_MQTT_TEST != 1 + #error "IOT_MQTT_MQTT_TEST must be 0 or 1." +#endif +#if IOT_MQTT_RESPONSE_WAIT_MS <= 0 + #error "IOT_MQTT_RESPONSE_WAIT_MS cannot be 0 or negative." +#endif +#if IOT_MQTT_RETRY_MS_CEILING <= 0 + #error "IOT_MQTT_RETRY_MS_CEILING cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Determines if an MQTT subscription is safe to remove based on its + * reference count. + * + * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. + * @param[in] pMatch Not used. + * + * @return `true` if the given subscription has no references; `false` otherwise. + */ +static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, + void * pMatch ); + +/** + * @brief Decrement the reference count of an MQTT operation and attempt to + * destroy it. + * + * @param[in] pData The operation data to destroy. This parameter is of type + * `void*` for compatibility with [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ +static void _mqttOperation_tryDestroy( void * pData ); + +/** + * @brief Create a keep-alive job for an MQTT connection. + * + * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT + * server. + * @param[in] pMqttConnection The MQTT connection associated with the keep-alive. + * @param[in] keepAliveSeconds User-provided keep-alive interval. + * + * @return `true` if the keep-alive job was successfully created; `false` otherwise. + */ +static bool _createKeepAliveJob( bool awsIotMqttMode, + _mqttConnection_t * pMqttConnection, + uint16_t keepAliveSeconds ); + +/** + * @brief Creates a new MQTT connection and initializes its members. + * + * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. + * @param[in] pNetworkInterface User-provided network interface for the new + * connection. + * @param[in] keepAliveSeconds User-provided keep-alive interval for the new connection. + * + * @return Pointer to a newly-allocated MQTT connection on success; `NULL` on + * failure. + */ +static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, + const IotMqttNetIf_t * pNetworkInterface, + uint16_t keepAliveSeconds ); + +/** + * @brief Destroys the members of an MQTT connection. + * + * @param[in] pMqttConnection Which connection to destroy. + */ +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); + +/** + * @brief The common component of both @ref mqtt_function_subscribe and @ref + * mqtt_function_unsubscribe. + * + * See @ref mqtt_function_subscribe or @ref mqtt_function_unsubscribe for a + * description of the parameters and return values. + */ +static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pSubscriptionRef ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Ensures that only one CONNECT operation is in-progress at any time. + * + * Because CONNACK contains no data about which CONNECT packet it acknowledges, + * only one CONNECT operation may be in-progress at any time. + */ +static IotMutex_t _connectMutex; + +/*-----------------------------------------------------------*/ + +static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, + void * pMatch ) +{ + bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + + /* Silence warnings about unused parameters. */ + ( void ) pMatch; + + /* Reference count must not be negative. */ + IotMqtt_Assert( pSubscription->references >= 0 ); + + /* Check if any subscription callbacks are using this subscription. */ + if( pSubscription->references > 0 ) + { + /* Set the unsubscribed flag, but do not remove the subscription yet. */ + pSubscription->unsubscribed = true; + } + else + { + /* No references for this subscription; it can be removed. */ + match = true; + } + + return match; +} + +/*-----------------------------------------------------------*/ + +static void _mqttOperation_tryDestroy( void * pData ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + + /* Decrement reference count and destroy operation if possible. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +static bool _createKeepAliveJob( bool awsIotMqttMode, + _mqttConnection_t * pMqttConnection, + uint16_t keepAliveSeconds ) +{ + bool status = true; + IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; + IotTaskPoolError_t jobStatus = IOT_TASKPOOL_SUCCESS; + + /* Default PINGREQ serializer function. */ + IotMqttError_t ( * serializePingreq )( uint8_t **, + size_t * ) = _IotMqtt_SerializePingreq; + + /* AWS IoT service limits set minimum and maximum values for keep-alive interval. + * Adjust the user-provided keep-alive interval based on these requirements. */ + if( awsIotMqttMode == true ) + { + if( keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; + } + else if( keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + else if( keepAliveSeconds == 0 ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Convert the keep-alive interval to milliseconds. */ + pMqttConnection->keepAliveMs = keepAliveSeconds * 1000; + pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; + + /* Choose a PINGREQ serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.pingreq != NULL ) + { + serializePingreq = pMqttConnection->network.serialize.pingreq; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + + /* Generate a PINGREQ packet. */ + serializeStatus = serializePingreq( &( pMqttConnection->pPingreqPacket ), + &( pMqttConnection->pingreqPacketSize ) ); + + if( serializeStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to allocate PINGREQ packet for new connection." ); + + status = false; + } + else + { + /* Create the task pool job that processes keep-alive. */ + jobStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, + pMqttConnection, + &( pMqttConnection->keepAliveJob ) ); + + /* Task pool job creation for a pre-allocated job should never fail. + * Abort the program if it does. */ + if( jobStatus != IOT_TASKPOOL_SUCCESS ) + { + IotLogError( "Failed to create keep-alive job for new connection." ); + + IotMqtt_Assert( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Keep-alive references its MQTT connection, so increment reference. */ + ( pMqttConnection->references )++; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, + const IotMqttNetIf_t * pNetworkInterface, + uint16_t keepAliveSeconds ) +{ + _IOT_FUNCTION_ENTRY( bool, true ); + bool referencesMutexCreated = false, subscriptionMutexCreated = false; + _mqttConnection_t * pNewMqttConnection = NULL; + + /* Allocate memory to store data for the new MQTT connection. */ + pNewMqttConnection = ( _mqttConnection_t * ) + IotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); + + if( pNewMqttConnection == NULL ) + { + IotLogError( "Failed to allocate memory for new MQTT connection." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Clear the MQTT connection, then copy the MQTT server mode and network + * interface. */ + ( void ) memset( pNewMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); + pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; + pNewMqttConnection->network = *pNetworkInterface; + + /* Start a new MQTT connection with a reference count of 1. */ + pNewMqttConnection->references = 1; + + /* Create the references mutex for a new connection. It is a recursive mutex. */ + referencesMutexCreated = IotMutex_Create( &( pNewMqttConnection->referencesMutex ), true ); + + if( referencesMutexCreated == false ) + { + IotLogError( "Failed to create references mutex for new connection." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Create the subscription mutex for a new connection. */ + subscriptionMutexCreated = IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ), false ); + + if( subscriptionMutexCreated == false ) + { + IotLogError( "Failed to create subscription mutex for new connection." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Create the new connection's subscription and operation lists. */ + IotListDouble_Create( &( pNewMqttConnection->subscriptionList ) ); + IotListDouble_Create( &( pNewMqttConnection->pendingProcessing ) ); + IotListDouble_Create( &( pNewMqttConnection->pendingResponse ) ); + + /* Check if keep-alive is active for this connection. */ + if( keepAliveSeconds != 0 ) + { + if( _createKeepAliveJob( awsIotMqttMode, + pNewMqttConnection, + keepAliveSeconds ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Clean up mutexes and connection if this function failed. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status == false ) + { + if( subscriptionMutexCreated == true ) + { + IotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( referencesMutexCreated == true ) + { + IotMutex_Destroy( &( pNewMqttConnection->referencesMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pNewMqttConnection != NULL ) + { + IotMqtt_FreeConnection( pNewMqttConnection ); + + pNewMqttConnection = NULL; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + return pNewMqttConnection; +} + +/*-----------------------------------------------------------*/ + +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) +{ + /* Clean up keep-alive if still allocated. */ + if( pMqttConnection->keepAliveMs != 0 ) + { + IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); + + _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); + IotTaskPool_DestroyJob( &( _IotMqttTaskPool ), + &( pMqttConnection->keepAliveJob ) ); + + /* Clear data about the keep-alive. */ + pMqttConnection->keepAliveMs = 0; + pMqttConnection->pPingreqPacket = NULL; + pMqttConnection->pingreqPacketSize = 0; + + /* Decrement reference count. */ + pMqttConnection->references--; + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* A connection to be destroyed should have no keep-alive and at most 1 + * reference. */ + IotMqtt_Assert( pMqttConnection->references <= 1 ); + IotMqtt_Assert( pMqttConnection->keepAliveMs == 0 ); + IotMqtt_Assert( pMqttConnection->pPingreqPacket == NULL ); + IotMqtt_Assert( pMqttConnection->pingreqPacketSize == 0 ); + + /* Remove all subscriptions. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), + _mqttSubscription_shouldRemove, + NULL, + IotMqtt_FreeSubscription, + offsetof( _mqttSubscription_t, link ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + /* Destroy mutexes and free connection. */ + IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); + IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); + IotMqtt_FreeConnection( pMqttConnection ); + + IotLogDebug( "(MQTT connection %p) Connection destroyed.", pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pSubscriptionRef ) +{ + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + _mqttOperation_t * pSubscriptionOperation = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Subscription serializer function. */ + IotMqttError_t ( * serializeSubscription )( const IotMqttSubscription_t *, + size_t, + uint8_t **, + size_t *, + uint16_t * ) = NULL; + + /* This function should only be called for subscribe or unsubscribe. */ + IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || + ( operation == IOT_MQTT_UNSUBSCRIBE ) ); + + /* Check that all elements in the subscription list are valid. */ + if( _IotMqtt_ValidateSubscriptionList( operation, + pMqttConnection->awsIotMqttMode, + pSubscriptionList, + subscriptionCount ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + if( pSubscriptionRef == NULL ) + { + IotLogError( "Reference must be provided for a waitable %s.", + IotMqtt_OperationType( operation ) ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Choose a subscription serialize function. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + serializeSubscription = _IotMqtt_SerializeSubscribe; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.subscribe != NULL ) + { + serializeSubscription = pMqttConnection->network.serialize.subscribe; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + } + else + { + serializeSubscription = _IotMqtt_SerializeUnsubscribe; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.unsubscribe != NULL ) + { + serializeSubscription = pMqttConnection->network.serialize.unsubscribe; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + } + + /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ + if( operation == IOT_MQTT_UNSUBSCRIBE ) + { + _IotMqtt_RemoveSubscriptionByTopicFilter( pMqttConnection, + pSubscriptionList, + subscriptionCount ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Create a subscription operation. */ + status = _IotMqtt_CreateOperation( pMqttConnection, + flags, + pCallbackInfo, + &pSubscriptionOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + + /* Check the subscription operation data and set the operation type. */ + IotMqtt_Assert( pSubscriptionOperation->status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( pSubscriptionOperation->retry.limit == 0 ); + pSubscriptionOperation->operation = operation; + + /* Generate a subscription packet from the subscription list. */ + status = serializeSubscription( pSubscriptionList, + subscriptionCount, + &( pSubscriptionOperation->pMqttPacket ), + &( pSubscriptionOperation->packetSize ), + &( pSubscriptionOperation->packetIdentifier ) ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pSubscriptionOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pSubscriptionOperation->packetSize > 0 ); + + /* Add the subscription list for a SUBSCRIBE. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + status = _IotMqtt_AddSubscriptions( pMqttConnection, + pSubscriptionOperation->packetIdentifier, + pSubscriptionList, + subscriptionCount ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + } + + /* Set the reference, if provided. */ + if( pSubscriptionRef != NULL ) + { + *pSubscriptionRef = pSubscriptionOperation; + } + + /* Schedule the subscription operation for network transmission. */ + status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", + pMqttConnection, + IotMqtt_OperationType( operation ) ); + + if( operation == IOT_MQTT_SUBSCRIBE ) + { + _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, + pSubscriptionOperation->packetIdentifier, + -1 ); + } + + /* Clear the previously set (and now invalid) reference. */ + if( pSubscriptionRef != NULL ) + { + *pSubscriptionRef = IOT_MQTT_REFERENCE_INITIALIZER; + } + + _IOT_GOTO_CLEANUP(); + } + + /* Clean up if this function failed. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( pSubscriptionOperation != NULL ) + { + _IotMqtt_DestroyOperation( pSubscriptionOperation ); + } + } + else + { + status = IOT_MQTT_STATUS_PENDING; + + IotLogInfo( "(MQTT connection %p) %s operation scheduled.", + pMqttConnection, + IotMqtt_OperationType( operation ) ); + } + + _IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ) +{ + bool disconnected = false; + + /* Lock the mutex protecting the reference count. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Reference count must not be negative. */ + IotMqtt_Assert( pMqttConnection->references >= 0 ); + + /* Read connection status. */ + disconnected = pMqttConnection->disconnected; + + /* Increment the connection's reference count if it is not disconnected. */ + if( disconnected == false ) + { + ( pMqttConnection->references )++; + IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", + pMqttConnection, + ( long int ) pMqttConnection->references - 1, + ( long int ) pMqttConnection->references ); + } + else + { + IotLogWarn( "(MQTT connection %p) Attempt to use closed connection.", pMqttConnection ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + return( disconnected == false ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ) +{ + bool destroyConnection = false; + + /* Lock the mutex protecting the reference count. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Decrement reference count. It must not be negative. */ + ( pMqttConnection->references )--; + IotMqtt_Assert( pMqttConnection->references >= 0 ); + + IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", + pMqttConnection, + ( long int ) pMqttConnection->references + 1, + ( long int ) pMqttConnection->references ); + + /* Check if this connection may be destroyed. */ + if( pMqttConnection->references == 0 ) + { + destroyConnection = true; + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Destroy an unreferenced MQTT connection. */ + if( destroyConnection == true ) + { + IotLogDebug( "(MQTT connection %p) Connection will be destroyed now.", + pMqttConnection ); + _destroyMqttConnection( pMqttConnection ); + } + else + { + _EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Init( void ) +{ + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + bool taskPoolCreated = false, connectMutexCreated = false; + const IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM; + + /* Create MQTT library task pool. */ + if( IotTaskPool_Create( &taskPoolInfo, &_IotMqttTaskPool ) != IOT_TASKPOOL_SUCCESS ) + { + IotLogError( "Failed to initialize MQTT library task pool." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + taskPoolCreated = true; + } + + /* Create CONNECT mutex. */ + connectMutexCreated = IotMutex_Create( &( _connectMutex ), false ); + + if( connectMutexCreated == false ) + { + IotLogError( "Failed to initialize MQTT library connect mutex." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Initialize MQTT serializer. */ + if( _IotMqtt_InitSerialize() != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to initialize MQTT library serializer. " ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotLogInfo( "MQTT library successfully initialized." ); + + /* Clean up if this function failed. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( taskPoolCreated == true ) + { + IotTaskPool_Destroy( &( _IotMqttTaskPool ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( connectMutexCreated == true ) + { + IotMutex_Destroy( &( _connectMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + _IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_Cleanup() +{ + /* Clean up MQTT library task pool. */ + IotTaskPool_Destroy( &_IotMqttTaskPool ); + + /* Clean up MQTT serializer. */ + _IotMqtt_CleanupSerialize(); + + /* Clean up CONNECT mutex. */ + IotMutex_Destroy( &( _connectMutex ) ); + + IotLogInfo( "MQTT library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, + const IotMqttNetIf_t * pNetworkInterface, + const IotMqttConnectInfo_t * pConnectInfo, + uint64_t timeoutMs ) +{ + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + _mqttConnection_t * pNewMqttConnection = NULL; + _mqttOperation_t * pConnectOperation = NULL; + + /* Default CONNECT serializer function. */ + IotMqttError_t ( * serializeConnect )( const IotMqttConnectInfo_t *, + uint8_t **, + size_t * ) = _IotMqtt_SerializeConnect; + + /* Validate network interface and connect info. */ + if( _IotMqtt_ValidateNetIf( pNetworkInterface ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* If will info is provided, check that it is valid. */ + if( pConnectInfo->pWillInfo != NULL ) + { + if( _IotMqtt_ValidatePublish( pConnectInfo->awsIotMqttMode, + pConnectInfo->pWillInfo ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else if( pConnectInfo->pWillInfo->payloadLength > UINT16_MAX ) + { + /* Will message payloads cannot be larger than 65535. This restriction + * applies only to will messages, and not normal PUBLISH messages. */ + IotLogError( "Will payload cannot be larger than 65535." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* If previous subscriptions are provided, check that they are valid. */ + if( pConnectInfo->cleanSession == false ) + { + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Choose a CONNECT serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNetworkInterface->serialize.connect != NULL ) + { + serializeConnect = pNetworkInterface->serialize.connect; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + + IotLogInfo( "Establishing new MQTT connection." ); + + /* Allocate memory to store data for the new MQTT connection. */ + pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, + pNetworkInterface, + pConnectInfo->keepAliveSeconds ); + + if( pNewMqttConnection == NULL ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Create a CONNECT operation. */ + status = _IotMqtt_CreateOperation( pNewMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pConnectOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Ensure the members set by operation creation and serialization + * are appropriate for a blocking CONNECT. */ + IotMqtt_Assert( pConnectOperation->status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pConnectOperation->flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pConnectOperation->retry.limit == 0 ); + + /* Set the operation type. */ + pConnectOperation->operation = IOT_MQTT_CONNECT; + + /* Add previous session subscriptions. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + /* Previous subscription count should have been validated as nonzero. */ + IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); + + status = _IotMqtt_AddSubscriptions( pNewMqttConnection, + 2, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ + status = serializeConnect( pConnectInfo, + &( pConnectOperation->pMqttPacket ), + &( pConnectOperation->packetSize ) ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pConnectOperation->packetSize > 0 ); + + /* Set the output parameter so it may be used by the network receive callback. */ + *pMqttConnection = pNewMqttConnection; + + /* Prevent another CONNECT operation from using the network. */ + IotMutex_Lock( &_connectMutex ); + + /* Add the CONNECT operation to the send queue for network transmission. */ + status = _IotMqtt_ScheduleOperation( pConnectOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to enqueue CONNECT for sending." ); + } + else + { + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + status = IotMqtt_Wait( ( IotMqttReference_t ) pConnectOperation, + timeoutMs ); + + /* The call to wait cleans up the CONNECT operation, so set the pointer + * to NULL. */ + pConnectOperation = NULL; + } + + /* Unlock the CONNECT mutex. */ + IotMutex_Unlock( &_connectMutex ); + + /* When a connection is successfully established, schedule keep-alive job. */ + if( status == IOT_MQTT_SUCCESS ) + { + /* Check if a keep-alive job should be scheduled. */ + if( pNewMqttConnection->keepAliveMs != 0 ) + { + IotLogDebug( "Scheduling first MQTT keep-alive job." ); + + taskPoolStatus = IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), + &( pNewMqttConnection->keepAliveJob ), + pNewMqttConnection->nextKeepAliveMs ); + + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SCHEDULING_ERROR ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to establish new MQTT connection, error %s.", + IotMqtt_strerror( status ) ); + + if( pConnectOperation != NULL ) + { + _IotMqtt_DestroyOperation( pConnectOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pNewMqttConnection != NULL ) + { + _destroyMqttConnection( pNewMqttConnection ); + *pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + IotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); + } + + _IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, + bool cleanupOnly ) +{ + bool disconnected = false; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + _mqttOperation_t * pDisconnectOperation = NULL; + + IotLogInfo( "(MQTT connection %p) Disconnecting connection.", pMqttConnection ); + + /* Read the connection status. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + disconnected = pMqttConnection->disconnected; + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" + * option is false. */ + if( disconnected == false ) + { + if( cleanupOnly == false ) + { + /* Create a DISCONNECT operation. This function blocks until the DISCONNECT + * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ + status = _IotMqtt_CreateOperation( pMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pDisconnectOperation ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Ensure that the members set by operation creation and serialization + * are appropriate for a blocking DISCONNECT. */ + IotMqtt_Assert( pDisconnectOperation->status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pDisconnectOperation->flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pDisconnectOperation->retry.limit == 0 ); + + /* Set the operation type. */ + pDisconnectOperation->operation = IOT_MQTT_DISCONNECT; + + /* Choose a disconnect serializer. */ + IotMqttError_t ( * serializeDisconnect )( uint8_t **, + size_t * ) = _IotMqtt_SerializeDisconnect; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.disconnect != NULL ) + { + serializeDisconnect = pMqttConnection->network.serialize.disconnect; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + + /* Generate a DISCONNECT packet. */ + status = serializeDisconnect( &( pDisconnectOperation->pMqttPacket ), + &( pDisconnectOperation->packetSize ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pDisconnectOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pDisconnectOperation->packetSize > 0 ); + + /* Schedule the DISCONNECT operation for network transmission. */ + if( _IotMqtt_ScheduleOperation( pDisconnectOperation, + _IotMqtt_ProcessSend, + 0 ) != IOT_MQTT_SUCCESS ) + { + IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", + pMqttConnection ); + _IotMqtt_DestroyOperation( pDisconnectOperation ); + } + else + { + /* Wait a short time for the DISCONNECT packet to be transmitted. */ + status = IotMqtt_Wait( ( IotMqttReference_t ) pDisconnectOperation, + IOT_MQTT_RESPONSE_WAIT_MS ); + + /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, + * or NETWORK ERROR. */ + if( status == IOT_MQTT_SUCCESS ) + { + IotLogInfo( "(MQTT connection %p) Connection disconnected.", pMqttConnection ); + } + else + { + IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || + ( status == IOT_MQTT_NETWORK_ERROR ) ); + + IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", + pMqttConnection, + IotMqtt_strerror( status ) ); + } + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Close the underlying network connection. This also cleans up keep-alive. */ + _IotMqtt_CloseNetworkConnection( pMqttConnection ); + + /* Check if the connection may be destroyed. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* At this point, the connection should be marked disconnected. */ + IotMqtt_Assert( pMqttConnection->disconnected == true ); + + /* Attempt cancel and destroy each operation in the connection's lists. */ + IotListDouble_RemoveAll( &( pMqttConnection->pendingProcessing ), + _mqttOperation_tryDestroy, + offsetof( _mqttOperation_t, link ) ); + + IotListDouble_RemoveAll( &( pMqttConnection->pendingResponse ), + _mqttOperation_tryDestroy, + offsetof( _mqttOperation_t, link ) ); + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Decrement the connection reference count and destroy it if possible. */ + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pSubscribeRef ) +{ + return _subscriptionCommon( IOT_MQTT_SUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pSubscribeRef ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttReference_t subscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + + /* Flags are not used, but the parameter is present for future compatibility. */ + ( void ) flags; + + /* Call the asynchronous SUBSCRIBE function. */ + status = IotMqtt_Subscribe( mqttConnection, + pSubscriptionList, + subscriptionCount, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeRef ); + + /* Wait for the SUBSCRIBE operation to complete. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( subscribeRef, timeoutMs ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Ensure that a status was set. */ + IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pUnsubscribeRef ) +{ + return _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pUnsubscribeRef ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint64_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + + /* Flags are not used, but the parameter is present for future compatibility. */ + ( void ) flags; + + /* Call the asynchronous UNSUBSCRIBE function. */ + status = IotMqtt_Unsubscribe( mqttConnection, + pSubscriptionList, + subscriptionCount, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeRef ); + + /* Wait for the UNSUBSCRIBE operation to complete. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( unsubscribeRef, timeoutMs ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Ensure that a status was set. */ + IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttReference_t * pPublishRef ) +{ + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + _mqttOperation_t * pPublishOperation = NULL; + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + + /* Default PUBLISH serializer function. */ + IotMqttError_t ( * serializePublish )( const IotMqttPublishInfo_t *, + uint8_t **, + size_t *, + uint16_t * ) = _IotMqtt_SerializePublish; + + /* Check that the PUBLISH information is valid. */ + if( _IotMqtt_ValidatePublish( pMqttConnection->awsIotMqttMode, + pPublishInfo ) == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check that no notification is requested for a QoS 0 publish. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_0 ) + { + if( pCallbackInfo != NULL ) + { + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) + { + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pPublishRef != NULL ) + { + IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + if( pPublishRef == NULL ) + { + IotLogError( "Reference must be provided for a waitable PUBLISH." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Create a PUBLISH operation. */ + status = _IotMqtt_CreateOperation( pMqttConnection, + flags, + pCallbackInfo, + &pPublishOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check the PUBLISH operation data and set the operation type. */ + IotMqtt_Assert( pPublishOperation->status == IOT_MQTT_STATUS_PENDING ); + pPublishOperation->operation = IOT_MQTT_PUBLISH_TO_SERVER; + + /* Choose a PUBLISH serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.publish != NULL ) + { + serializePublish = pMqttConnection->network.serialize.publish; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + + /* Generate a PUBLISH packet from pPublishInfo. */ + status = serializePublish( pPublishInfo, + &( pPublishOperation->pMqttPacket ), + &( pPublishOperation->packetSize ), + &( pPublishOperation->packetIdentifier ) ); + + if( status != IOT_MQTT_SUCCESS ) + { + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pPublishOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pPublishOperation->packetSize > 0 ); + + /* Initialize PUBLISH retry if retryLimit is set. */ + if( pPublishInfo->retryLimit > 0 ) + { + /* A QoS 0 PUBLISH may not be retried. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + pPublishOperation->retry.limit = pPublishInfo->retryLimit; + pPublishOperation->retry.nextPeriod = pPublishInfo->retryMs; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Set the reference, if provided. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + if( pPublishRef != NULL ) + { + *pPublishRef = pPublishOperation; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Add the PUBLISH operation to the send queue for network transmission. */ + status = _IotMqtt_ScheduleOperation( pPublishOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", + pMqttConnection ); + + /* Clear the previously set (and now invalid) reference. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + if( pPublishRef != NULL ) + { + *pPublishRef = IOT_MQTT_REFERENCE_INITIALIZER; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Clean up the PUBLISH operation if this function fails. Otherwise, set the + * appropriate return code based on QoS. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( pPublishOperation != NULL ) + { + _IotMqtt_DestroyOperation( pPublishOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + { + status = IOT_MQTT_STATUS_PENDING; + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotLogInfo( "(MQTT connection %p) MQTT PUBLISH operation queued.", + pMqttConnection ); + } + + _IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + uint64_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER, + * pPublishRef = NULL; + + /* Clear the flags. */ + flags = 0; + + /* Set the waitable flag and reference for QoS 1 PUBLISH. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + flags = IOT_MQTT_FLAG_WAITABLE; + pPublishRef = &publishRef; + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Call the asynchronous PUBLISH function. */ + status = IotMqtt_Publish( mqttConnection, + pPublishInfo, + flags, + NULL, + pPublishRef ); + + /* Wait for a queued QoS 1 PUBLISH to complete. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( publishRef, timeoutMs ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, + uint64_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Validate the given reference. */ + if( _IotMqtt_ValidateReference( reference ) == false ) + { + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check the MQTT connection status. */ + if( status == IOT_MQTT_SUCCESS ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( pMqttConnection->disconnected == true ) + { + IotLogError( "(MQTT connection %p, %s operation %p) MQTT connection is closed. " + "Operation cannot be waited on.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + + status = IOT_MQTT_NETWORK_ERROR; + } + else + { + IotLogInfo( "(MQTT connection %p, %s operation %p) Waiting for operation completion.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Only wait on an operation if the MQTT connection is active. */ + if( status == IOT_MQTT_SUCCESS ) + { + if( IotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + timeoutMs ) == false ) + { + status = IOT_MQTT_TIMEOUT; + + /* Attempt to cancel the job of the timed out operation. */ + ( void ) _IotMqtt_DecrementOperationReferences( pOperation, true ); + + /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ + if( pOperation->operation == IOT_MQTT_SUBSCRIBE ) + { + IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" + " subscriptions of timed-out SUBSCRIBE." + pMqttConnection, + pOperation ); + + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, + pOperation->packetIdentifier, + -1 ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + /* Retrieve the status of the completed operation. */ + status = pOperation->status; + } + + IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", + pMqttConnection + IotMqtt_OperationType( pOperation->operation ), + pOperation, + IotMqtt_strerror( status ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Wait is finished; decrement operation reference count. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +const char * IotMqtt_strerror( IotMqttError_t status ) +{ + const char * pMessage = NULL; + + switch( status ) + { + case IOT_MQTT_SUCCESS: + pMessage = "SUCCESS"; + break; + + case IOT_MQTT_STATUS_PENDING: + pMessage = "PENDING"; + break; + + case IOT_MQTT_INIT_FAILED: + pMessage = "INITIALIZATION FAILED"; + break; + + case IOT_MQTT_BAD_PARAMETER: + pMessage = "BAD PARAMETER"; + break; + + case IOT_MQTT_NO_MEMORY: + pMessage = "NO MEMORY"; + break; + + case IOT_MQTT_NETWORK_ERROR: + pMessage = "NETWORK ERROR"; + break; + + case IOT_MQTT_SCHEDULING_ERROR: + pMessage = "SCHEDULING ERROR"; + break; + + case IOT_MQTT_BAD_RESPONSE: + pMessage = "BAD RESPONSE RECEIVED"; + break; + + case IOT_MQTT_TIMEOUT: + pMessage = "TIMEOUT"; + break; + + case IOT_MQTT_SERVER_REFUSED: + pMessage = "SERVER REFUSED"; + break; + + case IOT_MQTT_RETRY_NO_RESPONSE: + pMessage = "NO RESPONSE"; + break; + + default: + pMessage = "INVALID STATUS"; + break; + } + + return pMessage; +} + +/*-----------------------------------------------------------*/ + +const char * IotMqtt_OperationType( IotMqttOperationType_t operation ) +{ + const char * pMessage = NULL; + + switch( operation ) + { + case IOT_MQTT_CONNECT: + pMessage = "CONNECT"; + break; + + case IOT_MQTT_PUBLISH_TO_SERVER: + pMessage = "PUBLISH"; + break; + + case IOT_MQTT_PUBACK: + pMessage = "PUBACK"; + break; + + case IOT_MQTT_SUBSCRIBE: + pMessage = "SUBSCRIBE"; + break; + + case IOT_MQTT_UNSUBSCRIBE: + pMessage = "UNSUBSCRIBE"; + break; + + case IOT_MQTT_PINGREQ: + pMessage = "PINGREQ"; + break; + + case IOT_MQTT_DISCONNECT: + pMessage = "DISCONNECT"; + break; + + default: + pMessage = "INVALID OPERATION"; + break; + } + + return pMessage; +} + +/*-----------------------------------------------------------*/ + +/* If the MQTT library is being tested, include a file that allows access to + * internal functions and variables. */ +#if IOT_MQTT_TEST == 1 + #include "iot_test_access_mqtt_api.c" +#endif diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 5a57e5e4f8..a0aa035767 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -33,7 +33,7 @@ #include /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -46,76 +46,111 @@ * @param[in] pMqttConnection Which connection the PUBACK should be sent over. * @param[in] packetIdentifier Which packet identifier to include in PUBACK. */ -static void _sendPuback( _mqttConnection_t * const pMqttConnection, +static void _sendPuback( _mqttConnection_t * pMqttConnection, uint16_t packetIdentifier ); /*-----------------------------------------------------------*/ -static void _sendPuback( _mqttConnection_t * const pMqttConnection, +static void _sendPuback( _mqttConnection_t * pMqttConnection, uint16_t packetIdentifier ) { - _mqttOperation_t * pPubackOperation = NULL; + IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; + uint8_t * pPuback = NULL; + size_t pubackSize = 0, bytesSent = 0; + + /* Default PUBACK serializer and free packet functions. */ + IotMqttError_t ( * serializePuback )( uint16_t, + uint8_t **, + size_t * ) = _IotMqtt_SerializePuback; + void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; + + /* Increment the reference count for the MQTT connection. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) + { + IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); - /* Choose a PUBACK serializer function. */ - AwsIotMqttError_t ( * serializePuback )( uint16_t, - uint8_t ** const, - size_t * const ) = AwsIotMqttInternal_SerializePuback; + /* Choose PUBACK serializer and free packet functions. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.puback != NULL ) + { + serializePuback = pMqttConnection->network.serialize.puback; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.freePacket != NULL ) + { + freePacket = pMqttConnection->network.freePacket; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.puback != NULL ) + /* Generate a PUBACK packet from the packet identifier. */ + serializeStatus = serializePuback( packetIdentifier, + &pPuback, + &pubackSize ); + + if( serializeStatus != IOT_MQTT_SUCCESS ) { - serializePuback = pMqttConnection->network.serialize.puback; + IotLogWarn( "(MQTT connection %p) Failed to generate PUBACK packet for " + "received PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); } - #endif - - IotLogDebug( "Sending PUBACK for received PUBLISH %hu.", packetIdentifier ); - - /* Create a PUBACK operation. */ - if( AwsIotMqttInternal_CreateOperation( &pPubackOperation, - 0, - NULL ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to create PUBACK operation." ); + else + { + bytesSent = pMqttConnection->network.send( pMqttConnection->network.pSendContext, + pPuback, + pubackSize ); - return; - } + if( bytesSent != pubackSize ) + { + IotLogWarn( "(MQTT connection %p) Failed to send PUBACK for received" + " PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); + } + else + { + IotLogDebug( "(MQTT connection %p) PUBACK for received PUBLISH %hu sent.", + pMqttConnection, + packetIdentifier ); + } - /* Generate a PUBACK packet from the packet identifier. */ - if( serializePuback( packetIdentifier, - &( pPubackOperation->pMqttPacket ), - &( pPubackOperation->packetSize ) ) != AWS_IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to generate PUBACK packet." ); - AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + freePacket( pPuback ); + } - return; + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } - - /* Set the remaining members of the PUBACK operation and push it to the - * send queue for network transmission. */ - pPubackOperation->operation = AWS_IOT_MQTT_PUBACK; - pPubackOperation->pMqttConnection = pMqttConnection; - - if( AwsIotMqttInternal_EnqueueOperation( pPubackOperation, - &( _IotMqttSend ) ) != AWS_IOT_MQTT_SUCCESS ) + else { - IotLogWarn( "Failed to enqueue PUBACK for sending." ); - AwsIotMqttInternal_DestroyOperation( pPubackOperation ); + IotLogWarn( "(MQTT connection %p) Connection is closed, PUBACK for received" + " PUBLISH %hu will not be sent.", + pMqttConnection, + packetIdentifier ); } } /*-----------------------------------------------------------*/ -int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, - void * pConnection, - const uint8_t * pReceivedData, - size_t dataLength, - size_t offset, - void( *freeReceivedData )( void * ) ) +int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, + void * pConnection, + const uint8_t * pReceivedData, + size_t dataLength, + size_t offset, + void ( *freeReceivedData )( void * ) ) { size_t bytesProcessed = 0, totalBytesProcessed = 0, remainingDataLength = 0; _mqttConnection_t * pConnectionInfo = *( ( _mqttConnection_t ** ) ( pMqttConnection ) ); - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; uint16_t packetIdentifier = 0; const uint8_t * pNextPacket = pReceivedData; _mqttOperation_t * pOperation = NULL, * pFirstPublish = NULL, * pLastPublish = NULL; @@ -124,10 +159,10 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, ( void ) pConnection; /* Choose a packet type decoder function. */ - uint8_t ( * getPacketType )( const uint8_t * const, - size_t ) = AwsIotMqttInternal_GetPacketType; + uint8_t ( * getPacketType )( const uint8_t *, + size_t ) = _IotMqtt_GetPacketType; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.getPacketType != NULL ) { getPacketType = pConnectionInfo->network.getPacketType; @@ -146,7 +181,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, /* Process the stream of data until the entire stream is proccessed or an * incomplete packet is found. */ - while( ( totalBytesProcessed < remainingDataLength ) && ( status != AWS_IOT_MQTT_BAD_RESPONSE ) ) + while( ( totalBytesProcessed < remainingDataLength ) && ( status != IOT_MQTT_BAD_RESPONSE ) ) { switch( getPacketType( pNextPacket, remainingDataLength - totalBytesProcessed ) ) { @@ -154,11 +189,11 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, IotLog_PrintBuffer( "CONNACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); /* Deserialize the CONNACK. */ - AwsIotMqttError_t ( * deserializeConnack )( const uint8_t * const, - size_t, - size_t * const ) = AwsIotMqttInternal_DeserializeConnack; + IotMqttError_t ( * deserializeConnack )( const uint8_t *, + size_t, + size_t * ) = _IotMqtt_DeserializeConnack; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.deserialize.connack != NULL ) { deserializeConnack = pConnectionInfo->network.deserialize.connack; @@ -173,12 +208,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * in-progress CONNECT operation. */ if( bytesProcessed > 0 ) { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_CONNECT, NULL ); + pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_CONNECT, NULL ); if( pOperation != NULL ) { pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + _IotMqtt_Notify( pOperation ); } } @@ -188,7 +223,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, IotLog_PrintBuffer( "PUBLISH in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); /* Allocate memory to handle the incoming PUBLISH. */ - pOperation = AwsIotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); if( pOperation == NULL ) { @@ -203,14 +238,14 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, pOperation->incomingPublish = true; pOperation->pMqttConnection = pConnectionInfo; - /* Deserialize the PUBLISH into an AwsIotMqttPublishInfo_t. */ - AwsIotMqttError_t ( * deserializePublish )( const uint8_t * const, - size_t, - AwsIotMqttPublishInfo_t * const, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializePublish; + /* Deserialize the PUBLISH into an IotMqttPublishInfo_t. */ + IotMqttError_t ( * deserializePublish )( const uint8_t *, + size_t, + IotMqttPublishInfo_t *, + uint16_t *, + size_t * ) = _IotMqtt_DeserializePublish; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.deserialize.publish != NULL ) { deserializePublish = pConnectionInfo->network.deserialize.publish; @@ -224,10 +259,10 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, &bytesProcessed ); /* If a complete PUBLISH was deserialized, process it. */ - if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_SUCCESS ) ) + if( ( bytesProcessed > 0 ) && ( status == IOT_MQTT_SUCCESS ) ) { /* If a QoS 1 PUBLISH was received, send a PUBACK. */ - if( pOperation->publishInfo.QoS == 1 ) + if( pOperation->publishInfo.qos == IOT_MQTT_QOS_1 ) { _sendPuback( pConnectionInfo, packetIdentifier ); } @@ -248,7 +283,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, { /* Free the PUBLISH operation here if the PUBLISH packet isn't * valid. */ - AwsIotMqtt_FreeOperation( pOperation ); + IotMqtt_FreeOperation( pOperation ); } break; @@ -257,12 +292,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, IotLog_PrintBuffer( "PUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); /* Deserialize the PUBACK to get the packet identifier. */ - AwsIotMqttError_t ( * deserializePuback )( const uint8_t * const, - size_t, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializePuback; + IotMqttError_t ( * deserializePuback )( const uint8_t *, + size_t, + uint16_t *, + size_t * ) = _IotMqtt_DeserializePuback; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.deserialize.puback != NULL ) { deserializePuback = pConnectionInfo->network.deserialize.puback; @@ -278,12 +313,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * PUBLISH with a matching client identifier. */ if( bytesProcessed > 0 ) { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PUBLISH_TO_SERVER, &packetIdentifier ); + pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_PUBLISH_TO_SERVER, &packetIdentifier ); if( pOperation != NULL ) { pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + _IotMqtt_Notify( pOperation ); } } @@ -293,13 +328,13 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, IotLog_PrintBuffer( "SUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); /* Deserialize the SUBACK to get the packet identifier. */ - AwsIotMqttError_t ( * deserializeSuback )( AwsIotMqttConnection_t, - const uint8_t * const, - size_t, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializeSuback; + IotMqttError_t ( * deserializeSuback )( IotMqttConnection_t, + const uint8_t *, + size_t, + uint16_t *, + size_t * ) = _IotMqtt_DeserializeSuback; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.deserialize.suback != NULL ) { deserializeSuback = pConnectionInfo->network.deserialize.suback; @@ -316,12 +351,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * SUBACK with a matching client identifier. */ if( bytesProcessed > 0 ) { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_SUBSCRIBE, &packetIdentifier ); + pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_SUBSCRIBE, &packetIdentifier ); if( pOperation != NULL ) { pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + _IotMqtt_Notify( pOperation ); } } @@ -331,12 +366,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, IotLog_PrintBuffer( "UNSUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); /* Deserialize the UNSUBACK to get the packet identifier. */ - AwsIotMqttError_t ( * deserializeUnsuback )( const uint8_t * const, - size_t, - uint16_t * const, - size_t * const ) = AwsIotMqttInternal_DeserializeUnsuback; + IotMqttError_t ( * deserializeUnsuback )( const uint8_t *, + size_t, + uint16_t *, + size_t * ) = _IotMqtt_DeserializeUnsuback; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.deserialize.unsuback != NULL ) { deserializeUnsuback = pConnectionInfo->network.deserialize.unsuback; @@ -352,12 +387,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, * UNSUBACK with a matching client identifier. */ if( bytesProcessed > 0 ) { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_UNSUBSCRIBE, &packetIdentifier ); + pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_UNSUBSCRIBE, &packetIdentifier ); if( pOperation != NULL ) { pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + _IotMqtt_Notify( pOperation ); } } @@ -367,11 +402,11 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, IotLog_PrintBuffer( "PINGRESP in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); /* Deserialize the PINGRESP. */ - AwsIotMqttError_t ( * deserializePingresp )( const uint8_t * const, - size_t, - size_t * const ) = AwsIotMqttInternal_DeserializePingresp; + IotMqttError_t ( * deserializePingresp )( const uint8_t *, + size_t, + size_t * ) = _IotMqtt_DeserializePingresp; - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pConnectionInfo->network.deserialize.pingresp != NULL ) { deserializePingresp = pConnectionInfo->network.deserialize.pingresp; @@ -382,16 +417,29 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, remainingDataLength - totalBytesProcessed, &bytesProcessed ); - /* If a complete PINGRESP was deserialized, check if there's an - * in-progress PINGREQ operation. */ + /* If a complete PINGRESP was successfully deserialized, clear the keep-alive + * failure flag. */ if( bytesProcessed > 0 ) { - pOperation = AwsIotMqttInternal_FindOperation( AWS_IOT_MQTT_PINGREQ, NULL ); - - if( pOperation != NULL ) + if( status == IOT_MQTT_SUCCESS ) { - pOperation->status = status; - AwsIotMqttInternal_Notify( pOperation ); + IotMutex_Lock( &( pConnectionInfo->referencesMutex ) ); + + if( pConnectionInfo->keepAliveFailure == false ) + { + IotLogWarn( "Unexpected PINGRESP received." ); + } + else + { + pConnectionInfo->keepAliveFailure = false; + } + + IotMutex_Unlock( &( pConnectionInfo->referencesMutex ) ); + } + else + { + IotLogError( "Failed to process PINGRESP, status %s.", + IotMqtt_strerror( status ) ); } } @@ -401,16 +449,16 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, /* If an unknown packet is received, stop processing pReceivedData. */ IotLogError( "Unknown packet type %02x received.", - pNextPacket[ 0 ] ); + pNextPacket[ 0 ] ); bytesProcessed = 1; - status = AWS_IOT_MQTT_BAD_RESPONSE; + status = IOT_MQTT_BAD_RESPONSE; break; } /* Check if a protocol violation was encountered. */ - if( ( bytesProcessed > 0 ) && ( status == AWS_IOT_MQTT_BAD_RESPONSE ) ) + if( ( bytesProcessed > 0 ) && ( status == IOT_MQTT_BAD_RESPONSE ) ) { IotLogError( "MQTT protocol violation encountered. Closing network connection" ); @@ -420,26 +468,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, pLastPublish = pFirstPublish; pFirstPublish = pFirstPublish->pNextPublish; - AwsIotMqtt_FreeOperation( pLastPublish ); + IotMqtt_FreeOperation( pLastPublish ); } pLastPublish = NULL; - /* Prevent the timer thread from running, then set the error flag. */ - IotMutex_Lock( &( pConnectionInfo->timerMutex ) ); - - pConnectionInfo->errorOccurred = true; - - if( pConnectionInfo->network.disconnect != NULL ) - { - pConnectionInfo->network.disconnect( 0, pConnectionInfo->network.pDisconnectContext ); - } - else - { - IotLogWarn( "No disconnect function was set. Network connection not closed." ); - } - - IotMutex_Unlock( &( pConnectionInfo->timerMutex ) ); + _IotMqtt_CloseNetworkConnection( pConnectionInfo ); return -1; } @@ -455,8 +489,8 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, totalBytesProcessed += bytesProcessed; /* Number of bytes processed should never exceed remainingDataLength. */ - AwsIotMqtt_Assert( pNextPacket - totalBytesProcessed - offset == pReceivedData ); - AwsIotMqtt_Assert( totalBytesProcessed <= remainingDataLength ); + IotMqtt_Assert( pNextPacket - totalBytesProcessed - offset == pReceivedData ); + IotMqtt_Assert( totalBytesProcessed <= remainingDataLength ); } /* Only free pReceivedData if all bytes were processed and no PUBLISH messages @@ -465,7 +499,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, ( totalBytesProcessed == remainingDataLength ) && ( pFirstPublish == NULL ) ) { - AwsIotMqtt_Assert( pLastPublish == NULL ); + IotMqtt_Assert( pLastPublish == NULL ); freeReceivedData( ( void * ) pReceivedData ); } @@ -490,12 +524,12 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, for( pOperation = pFirstPublish; pOperation != NULL; pOperation = pOperation->pNextPublish ) { /* Neither the buffer pointer nor the free function should be set. */ - AwsIotMqtt_Assert( pOperation->pReceivedData == NULL ); - AwsIotMqtt_Assert( pOperation->freeReceivedData == NULL ); + IotMqtt_Assert( pOperation->pReceivedData == NULL ); + IotMqtt_Assert( pOperation->freeReceivedData == NULL ); /* Allocate a new buffer to hold the topic name and payload. */ - pOperation->pReceivedData = AwsIotMqtt_MallocMessage( pOperation->publishInfo.topicNameLength + - pOperation->publishInfo.payloadLength ); + pOperation->pReceivedData = IotMqtt_MallocMessage( pOperation->publishInfo.topicNameLength + + pOperation->publishInfo.payloadLength ); if( pOperation->pReceivedData != NULL ) { @@ -513,7 +547,7 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, pOperation->publishInfo.pTopicName = pOperation->pReceivedData; pOperation->publishInfo.pPayload = ( uint8_t * ) ( pOperation->pReceivedData ) + pOperation->publishInfo.topicNameLength; - pOperation->freeReceivedData = AwsIotMqtt_FreeMessage; + pOperation->freeReceivedData = IotMqtt_FreeMessage; } else { @@ -528,10 +562,11 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, } } - if( AwsIotMqttInternal_EnqueueOperation( pFirstPublish, - &( _IotMqttCallback ) ) != AWS_IOT_MQTT_SUCCESS ) + if( _IotMqtt_ScheduleOperation( pFirstPublish, + _IotMqtt_ProcessIncomingPublish, + 0 ) != IOT_MQTT_SUCCESS ) { - IotLogWarn( "Failed to enqueue incoming PUBLISH for callback." ); + IotLogWarn( "Failed to schedule incoming PUBLISH callback." ); } } @@ -539,3 +574,83 @@ int32_t AwsIotMqtt_ReceiveCallback( void * pMqttConnection, } /*-----------------------------------------------------------*/ + +void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) +{ + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + + /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pMqttConnection->disconnected = true; + pMqttConnection->keepAliveFailure = true; + + if( pMqttConnection->keepAliveMs != 0 ) + { + /* Keep-alive must have a PINGREQ allocated. */ + IotMqtt_Assert( pMqttConnection->pPingreqPacket != NULL ); + IotMqtt_Assert( pMqttConnection->pingreqPacketSize != 0 ); + + /* PINGREQ provides a reference to the connection, so reference count must + * be nonzero. */ + IotMqtt_Assert( pMqttConnection->references > 0 ); + + /* Attempt to cancel the keep-alive job. */ + taskPoolStatus = IotTaskPool_TryCancel( &( _IotMqttTaskPool ), + &( pMqttConnection->keepAliveJob ), + NULL ); + + /* If the keep-alive job was not canceled, it must be already executing. + * Any other return value is invalid. */ + IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || + ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); + + /* Clean up keep-alive if its job was successfully canceled. Otherwise, + * the executing keep-alive job will clean up itself. */ + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + /* Clean up PINGREQ packet and job. */ + _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); + IotTaskPool_DestroyJob( &( _IotMqttTaskPool ), + &( pMqttConnection->keepAliveJob ) ); + + /* Clear data about the keep-alive. */ + pMqttConnection->keepAliveMs = 0; + pMqttConnection->pPingreqPacket = NULL; + pMqttConnection->pingreqPacketSize = 0; + + /* Keep-alive is cleaned up; decrement reference count. Since this + * function must be followed with a call to DISCONNECT, a check to + * destroy the connection is not done here. */ + pMqttConnection->references--; + + IotLogDebug( "(MQTT connection %p) Keep-alive job canceled and cleaned up.", + pMqttConnection ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Close the network connection regardless of whether an MQTT DISCONNECT + * packet was sent. */ + if( pMqttConnection->network.disconnect != NULL ) + { + pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + + IotLogInfo( "(MQTT connection %p) Network connection closed.", pMqttConnection ); + } + else + { + IotLogWarn( "(MQTT connection %p) No disconnect function was set. Network connection" + " not closed.", pMqttConnection ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c new file mode 100644 index 0000000000..37734a985f --- /dev/null +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -0,0 +1,1299 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_operation.c + * @brief Implements functions that process MQTT operations. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief First parameter to #_mqttOperation_match. + */ +typedef struct _operationMatchParam +{ + IotMqttOperationType_t operation; /**< @brief The operation to look for. */ + const uint16_t * pPacketIdentifier; /**< @brief The packet identifier associated with the operation. + * Set to `NULL` to ignore packet identifier. */ +} _operationMatchParam_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Match an MQTT operation by type and packet identifier. + * + * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. + * @param[in] pMatch Pointer to an #_operationMatchParam_t. + * + * @return `true` if the operation matches the parameters in `pArgument`; `false` + * otherwise. + */ +static bool _mqttOperation_match( const IotLink_t * pOperationLink, + void * pMatch ); + +/** + * @brief Check if an operation with retry has exceeded its retry limit. + * + * If a PUBLISH operation is available for retry, this function also sets any + * necessary DUP flags. + * + * @param[in] pOperation The operation to check. + * + * @return `true` if the operation may be retried; `false` otherwise. + */ +static bool _checkRetryLimit( _mqttOperation_t * pOperation ); + +/** + * @brief Schedule the next send of an operation with retry. + * + * @param[in] pOperation The operation to schedule. + * + * @return `true` if the reschedule succeeded; `false` otherwise. + */ +static bool _scheduleNextRetry( _mqttOperation_t * pOperation ); + +/*-----------------------------------------------------------*/ + +/** + * @brief The task pool that processes MQTT operations. + */ +IotTaskPool_t _IotMqttTaskPool = { 0 }; + +/*-----------------------------------------------------------*/ + +static bool _mqttOperation_match( const IotLink_t * pOperationLink, + void * pMatch ) +{ + bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pOperationLink != NULL ); + + _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, + pOperationLink, + link ); + _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; + + /* Check for matching operations. */ + if( pParam->operation == pOperation->operation ) + { + /* Check for matching packet identifiers. */ + if( pParam->pPacketIdentifier == NULL ) + { + match = true; + } + else + { + match = ( *( pParam->pPacketIdentifier ) == pOperation->packetIdentifier ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + return match; +} + +/*-----------------------------------------------------------*/ + +static bool _checkRetryLimit( _mqttOperation_t * pOperation ) +{ + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + bool status = true; + + /* Choose a set DUP function. */ + void ( * publishSetDup )( bool, + uint8_t *, + uint16_t * ) = _IotMqtt_PublishSetDup; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.serialize.publishSetDup != NULL ) + { + publishSetDup = pMqttConnection->network.serialize.publishSetDup; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + + /* Only PUBLISH may be retried. */ + IotMqtt_Assert( pOperation->operation == IOT_MQTT_PUBLISH_TO_SERVER ); + + /* Check if the retry limit is exhausted. */ + if( pOperation->retry.count > pOperation->retry.limit ) + { + /* The retry count may be at most one more than the retry limit, which + * accounts for the final check for a PUBACK. */ + IotMqtt_Assert( pOperation->retry.count == pOperation->retry.limit + 1 ); + + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", + pMqttConnection, + pOperation, + pOperation->retry.limit ); + + status = false; + } + /* Check if this is the first retry. */ + else if( pOperation->retry.count == 1 ) + { + /* Always set the DUP flag on the first retry. */ + publishSetDup( pMqttConnection->awsIotMqttMode, + pOperation->pMqttPacket, + &( pOperation->packetIdentifier ) ); + } + else + { + /* In AWS IoT MQTT mode, the DUP flag (really a change to the packet + * identifier) must be reset on every retry. */ + if( pMqttConnection->awsIotMqttMode == true ) + { + publishSetDup( pMqttConnection->awsIotMqttMode, + pOperation->pMqttPacket, + &( pOperation->packetIdentifier ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) +{ + bool firstRetry = false; + uint64_t scheduleDelay = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* This function should never be called with retry count greater than + * retry limit. */ + IotMqtt_Assert( pOperation->retry.count <= pOperation->retry.limit ); + + /* Increment the retry count. */ + ( pOperation->retry.count )++; + + /* Check for a response shortly for the final retry. Otherwise, calculate the + * next retry period. */ + if( pOperation->retry.count > pOperation->retry.limit ) + { + scheduleDelay = IOT_MQTT_RESPONSE_WAIT_MS; + + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Final retry was sent. Will check " + "for response in %d ms.", + pMqttConnection, + pOperation, + IOT_MQTT_RESPONSE_WAIT_MS ); + } + else + { + scheduleDelay = pOperation->retry.nextPeriod; + + /* Double the retry peiod, subject to a ceiling value. */ + pOperation->retry.nextPeriod *= 2; + + if( pOperation->retry.nextPeriod > IOT_MQTT_RETRY_MS_CEILING ) + { + pOperation->retry.nextPeriod = IOT_MQTT_RETRY_MS_CEILING; + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %llu ms.", + pMqttConnection, + pOperation, + ( unsigned long ) pOperation->retry.count, + ( unsigned long ) pOperation->retry.limit, + ( unsigned long long ) scheduleDelay ); + + /* Check if this is the first retry. */ + firstRetry = ( pOperation->retry.count == 1 ); + + /* On the first retry, the PUBLISH will be moved from the pending processing + * list to the pending responses list. Lock the connection references mutex + * to manipulate the lists. */ + if( firstRetry == true ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + + /* Reschedule the PUBLISH for another send. */ + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + scheduleDelay ); + + /* Check for successful reschedule. */ + if( status == IOT_MQTT_SUCCESS ) + { + /* Move a successfully rescheduled PUBLISH from the pending processing + * list to the pending responses list on the first retry. */ + if( firstRetry == true ) + { + /* Operation must be linked. */ + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) ); + + /* Transfer to pending response list. */ + IotListDouble_Remove( &( pOperation->link ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), + &( pOperation->link ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + + /* The references mutex only needs to be unlocked on the first retry, since + * only the first retry manipulates the connection lists. */ + if( firstRetry == true ) + { + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + return( status == IOT_MQTT_SUCCESS ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + _mqttOperation_t ** pNewOperation ) +{ + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + bool decrementOnError = false; + _mqttOperation_t * pOperation = NULL; + bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); + + /* If the waitable flag is set, make sure that there's no callback. */ + if( waitable == true ) + { + if( pCallbackInfo != NULL ) + { + IotLogError( "Callback should not be set for a waitable operation." ); + + return IOT_MQTT_BAD_PARAMETER; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotLogDebug( "(MQTT connection %p) Creating new operation record.", + pMqttConnection ); + + /* Increment the reference count for the MQTT connection when creating a new + * operation. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == false ) + { + IotLogError( "(MQTT connection %p) New operation record cannot be created" + " for a closed connection", + pMqttConnection ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + } + else + { + /* Reference count will need to be decremented on error. */ + decrementOnError = true; + } + + /* Allocate memory for a new operation. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + IotLogError( "(MQTT connection %p) Failed to allocate memory for new " + "operation record.", + pMqttConnection ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + + /* Initialize the some members of the new operation. */ + pOperation->pMqttConnection = pMqttConnection; + pOperation->jobReference = 1; + pOperation->flags = flags; + pOperation->status = IOT_MQTT_STATUS_PENDING; + } + + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( waitable == true ) + { + /* Create a semaphore to wait on for a waitable operation. */ + if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + { + IotLogError( "(MQTT connection %p) Failed to create semaphore for " + "waitable operation.", + pMqttConnection ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + /* A waitable operation is created with an additional reference for the + * Wait function. */ + ( pOperation->jobReference )++; + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->notify.callback = *pCallbackInfo; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + + /* Add this operation to the MQTT connection's operation list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Set the output parameter. */ + *pNewOperation = pOperation; + + /* Clean up operation and decrement reference count if this function failed. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_MQTT_SUCCESS ) + { + if( decrementOnError == true ) + { + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pOperation != NULL ) + { + IotMqtt_FreeOperation( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + + _IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, + bool cancelJob ) +{ + bool destroyOperation = false; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Attempt to cancel the operation's job. */ + if( cancelJob == true ) + { + taskPoolStatus = IotTaskPool_TryCancel( &( _IotMqttTaskPool ), + &( pOperation->job ), + NULL ); + + /* If the operation's job was not canceled, it must be already executing. + * Any other return value is invalid. */ + IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || + ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); + + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Job canceled.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Decrement job reference count. */ + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pOperation->jobReference--; + + IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" + " from %ld to %ld.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation, + pOperation->jobReference + 1, + pOperation->jobReference ); + + /* The job reference count must be 0 or 1 after the decrement. */ + IotMqtt_Assert( ( pOperation->jobReference == 0 ) || + ( pOperation->jobReference == 1 ) ); + + /* This operation may be destroyed if its reference count is 0. */ + if( pOperation->jobReference == 0 ) + { + destroyOperation = true; + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + return destroyOperation; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) +{ + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Default free packet function. */ + void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; + + IotLogDebug( "(MQTT connection %p, %s operation %p) Destroying operation.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + + /* The job reference count must be between 0 and 2. */ + IotMqtt_Assert( ( pOperation->jobReference >= 0 ) && + ( pOperation->jobReference <= 2 ) ); + + /* Jobs to be destroyed should be removed from the MQTT connection's + * lists. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Removed operation from connection lists.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation, + pMqttConnection ); + + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Operation was not present in connection lists.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Free any allocated MQTT packet. */ + if( pOperation->pMqttPacket != NULL ) + { + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->network.freePacket != NULL ) + { + freePacket = pMqttConnection->network.freePacket; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + + freePacket( pOperation->pMqttPacket ); + + IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + else + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Operation has no allocated MQTT packet.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + + /* Check if a wait semaphore was created for this operation. */ + if( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + + IotLogDebug( "(MQTT connection %p, %s operation %p) Wait semaphore destroyed.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + + /* Free the memory used to hold operation data. */ + IotMqtt_FreeOperation( pOperation ); + + /* Decrement the MQTT connection's reference count after destroying an + * operation. */ + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pKeepAliveJob, + void * pContext ) +{ + bool status = true; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + size_t bytesSent = 0; + + /* Retrieve the MQTT connection from the context. */ + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; + + /* Check parameters. */ + IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pKeepAliveJob == &( pMqttConnection->keepAliveJob ) ); + + /* Check that keep-alive interval is valid. The MQTT spec states its maximum + * value is 65,535 seconds. */ + IotMqtt_Assert( pMqttConnection->keepAliveMs <= 65535000 ); + + /* Only two values are valid for the next keep alive job delay. */ + IotMqtt_Assert( ( pMqttConnection->nextKeepAliveMs == pMqttConnection->keepAliveMs ) || + ( pMqttConnection->nextKeepAliveMs == IOT_MQTT_RESPONSE_WAIT_MS ) ); + + IotLogDebug( "(MQTT connection %p) Keep-alive job started.", pMqttConnection ); + + /* Re-create the keep-alive job for rescheduling. This should never fail. */ + taskPoolStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, + pContext, + pKeepAliveJob ); + IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); + + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Determine whether to send a PINGREQ or check for PINGRESP. */ + if( pMqttConnection->nextKeepAliveMs == pMqttConnection->keepAliveMs ) + { + IotLogDebug( "(MQTT connection %p) Sending PINGREQ.", pMqttConnection ); + + /* Because PINGREQ may be used to keep the MQTT connection alive, it is + * more important than other operations. Bypass the queue of jobs for + * operations by directly sending the PINGREQ in this job. */ + bytesSent = pMqttConnection->network.send( pMqttConnection->network.pSendContext, + pMqttConnection->pPingreqPacket, + pMqttConnection->pingreqPacketSize ); + + if( bytesSent != pMqttConnection->pingreqPacketSize ) + { + IotLogError( "(MQTT connection %p) Failed to send PINGREQ.", pMqttConnection ); + status = false; + } + else + { + /* Assume the keep-alive will fail. The network receive callback will + * clear the failure flag upon receiving a PINGRESP. */ + pMqttConnection->keepAliveFailure = true; + + /* Schedule a check for PINGRESP. */ + pMqttConnection->nextKeepAliveMs = IOT_MQTT_RESPONSE_WAIT_MS; + + IotLogDebug( "(MQTT connection %p) PINGREQ sent. Scheduling check for PINGRESP in %d ms.", + pMqttConnection, + IOT_MQTT_RESPONSE_WAIT_MS ); + } + } + else + { + IotLogDebug( "(MQTT connection %p) Checking for PINGRESP.", pMqttConnection ); + + if( pMqttConnection->keepAliveFailure == false ) + { + IotLogDebug( "(MQTT connection %p) PINGRESP was received.", pMqttConnection ); + + /* PINGRESP was received. Schedule the next PINGREQ transmission. */ + pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; + } + else + { + IotLogError( "(MQTT connection %p) Failed to receive PINGRESP within %d ms.", + pMqttConnection, + IOT_MQTT_RESPONSE_WAIT_MS ); + + /* The network receive callback did not clear the failure flag. */ + status = false; + } + } + + /* When a PINGREQ is successfully sent, reschedule this job to check for a + * response shortly. */ + if( status == true ) + { + taskPoolStatus = IotTaskPool_ScheduleDeferred( pTaskPool, + pKeepAliveJob, + pMqttConnection->nextKeepAliveMs ); + + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p) Next keep-alive job in %d ms.", + pMqttConnection, + IOT_MQTT_RESPONSE_WAIT_MS ); + } + else + { + IotLogError( "(MQTT connection %p) Failed to reschedule keep-alive job, error %s.", + pMqttConnection, + IotTaskPool_strerror( taskPoolStatus ) ); + + status = false; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Close the connection on failures. */ + if( status == false ) + { + _IotMqtt_CloseNetworkConnection( pMqttConnection ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pPublishJob, + void * pContext ) +{ + _mqttOperation_t * pCurrent = NULL, * pNext = pContext; + IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + + /* Check parameters. The task pool and job parameter is not used when asserts + * are disabled. */ + ( void ) pTaskPool; + ( void ) pPublishJob; + IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pNext->incomingPublish == true ); + IotMqtt_Assert( pPublishJob == &( pNext->job ) ); + + /* Process each linked incoming PUBLISH. */ + while( pNext != NULL ) + { + /* Save a pointer to the current PUBLISH and move the iterating + * pointer. */ + pCurrent = pNext; + pNext = pNext->pNextPublish; + + /* Process the current PUBLISH. */ + if( pCurrent->publishInfo.pPayload != NULL ) + { + if( pCurrent->publishInfo.pTopicName != NULL ) + { + callbackParam.message.info = pCurrent->publishInfo; + + _IotMqtt_InvokeSubscriptionCallback( pCurrent->pMqttConnection, + &callbackParam ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Free any buffers associated with the current PUBLISH message. */ + if( pCurrent->freeReceivedData != NULL ) + { + IotMqtt_Assert( pCurrent->pReceivedData != NULL ); + pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Free the current PUBLISH. */ + IotMqtt_FreeOperation( pCurrent ); + } +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pSendJob, + void * pContext ) +{ + size_t bytesSent = 0; + bool destroyOperation = false, waitable = false, networkPending = false; + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Check parameters. The task pool and job parameter is not used when asserts + * are disabled. */ + ( void ) pTaskPool; + ( void ) pSendJob; + IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pSendJob == &( pOperation->job ) ); + + /* The given operation must have an allocated packet and be waiting for a status. */ + IotMqtt_Assert( pOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->packetSize != 0 ); + IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); + + /* Check if this operation is waitable. */ + waitable = ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Check PUBLISH retry counts and limits. */ + if( pOperation->retry.limit > 0 ) + { + if( _checkRetryLimit( pOperation ) == false ) + { + pOperation->status = IOT_MQTT_RETRY_NO_RESPONSE; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Send an operation that is waiting for a response. */ + if( pOperation->status == IOT_MQTT_STATUS_PENDING ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Sending MQTT packet.", + pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + + /* Transmit the MQTT packet from the operation over the network. */ + bytesSent = pMqttConnection->network.send( pMqttConnection->network.pSendContext, + pOperation->pMqttPacket, + pOperation->packetSize ); + + /* Check transmission status. */ + if( bytesSent != pOperation->packetSize ) + { + pOperation->status = IOT_MQTT_NETWORK_ERROR; + } + else + { + /* DISCONNECT operations are considered successful upon successful + * transmission. In addition, non-waitable operations with no callback + * may also be considered successful. */ + if( pOperation->operation == IOT_MQTT_DISCONNECT ) + { + pOperation->status = IOT_MQTT_SUCCESS; + } + else if( waitable == false ) + { + if( pOperation->notify.callback.function == NULL ) + { + pOperation->status = IOT_MQTT_SUCCESS; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check if this operation requires further processing. */ + if( pOperation->status == IOT_MQTT_STATUS_PENDING ) + { + /* Check if this operation should be scheduled for retransmission. */ + if( pOperation->retry.limit > 0 ) + { + if( _scheduleNextRetry( pOperation ) == false ) + { + pOperation->status = IOT_MQTT_SCHEDULING_ERROR; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Decrement reference count to signal completion of send job. Check + * if the operation should be destroyed. */ + if( waitable == true ) + { + destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* If the operation should not be destroyed, transfer it from the + * pending processing to the pending response list. Do not transfer + * operations with retries. */ + if( destroyOperation == false ) + { + /* Operation must be linked. */ + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) ); + + /* Transfer to pending response list. */ + IotListDouble_Remove( &( pOperation->link ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), + &( pOperation->link ) ); + + /* This operation is now awaiting a response from the network. */ + networkPending = true; + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Destroy the operation or notify of completion if necessary. */ + if( destroyOperation == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + /* Do not check the operation status if a network response is pending, + * since a network response could modify the status. */ + if( networkPending == false ) + { + /* Notify of operation completion if this job set a status. */ + if( pOperation->status != IOT_MQTT_STATUS_PENDING ) + { + _IotMqtt_Notify( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pOperationJob, + void * pContext ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; + IotMqttCallbackParam_t callbackParam = { .operation = { 0 } }; + + /* Check parameters. The task pool and job parameter is not used when asserts + * are disabled. */ + ( void ) pTaskPool; + ( void ) pOperationJob; + IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pOperationJob == &( pOperation->job ) ); + + /* The operation's callback function and status must be set. */ + IotMqtt_Assert( pOperation->notify.callback.function != NULL ); + IotMqtt_Assert( pOperation->status != IOT_MQTT_STATUS_PENDING ); + + callbackParam.mqttConnection = pOperation->pMqttConnection; + callbackParam.operation.type = pOperation->operation; + callbackParam.operation.reference = pOperation; + callbackParam.operation.result = pOperation->status; + + /* Invoke the user callback function. */ + pOperation->notify.callback.function( pOperation->notify.callback.param1, + &callbackParam ); + + /* Attempt to destroy the operation once the user callback returns. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, + IotTaskPoolRoutine_t jobRoutine, + uint64_t delay ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + + /* Check that job routine is valid. */ + IotMqtt_Assert( ( jobRoutine == _IotMqtt_ProcessSend ) || + ( jobRoutine == _IotMqtt_ProcessCompletedOperation ) || + ( jobRoutine == _IotMqtt_ProcessIncomingPublish ) ); + + /* Creating a new job should never fail when parameters are valid. */ + taskPoolStatus = IotTaskPool_CreateJob( jobRoutine, + pOperation, + &( pOperation->job ) ); + IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); + + /* Schedule the new job with a delay. */ + taskPoolStatus = IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), + &( pOperation->job ), + delay ); + + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + /* Scheduling a newly-created job should never be invalid or illegal. */ + IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_BAD_PARAMETER ); + IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_ILLEGAL_OPERATION ); + + IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule operation job, error %s.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation, + IotTaskPool_strerror( taskPoolStatus ) ); + + status = IOT_MQTT_SCHEDULING_ERROR; + } + else + { + _EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, + IotMqttOperationType_t operation, + const uint16_t * pPacketIdentifier ) +{ + bool waitable = false; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + _mqttOperation_t * pResult = NULL; + IotLink_t * pResultLink = NULL; + _operationMatchParam_t param = { 0 }; + + if( pPacketIdentifier != NULL ) + { + IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response " + "with packet identifier %hu.", + pMqttConnection, + IotMqtt_OperationType( operation ), + *pPacketIdentifier ); + } + else + { + IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response.", + pMqttConnection, + IotMqtt_OperationType( operation ) ); + } + + /* Set the search parameters. */ + param.operation = operation; + param.pPacketIdentifier = pPacketIdentifier; + + /* Find and remove the first matching element in the list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pResultLink = IotListDouble_RemoveFirstMatch( &( pMqttConnection->pendingResponse ), + NULL, + _mqttOperation_match, + ¶m ); + + /* Check if a match was found. */ + if( pResultLink != NULL ) + { + /* Get operation pointer and check if it is waitable. */ + pResult = IotLink_Container( _mqttOperation_t, pResultLink, link ); + waitable = ( pResult->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Check if the matched operation is a PUBLISH with retry. If it is, cancel + * the retry job. */ + if( pResult->retry.limit > 0 ) + { + taskPoolStatus = IotTaskPool_TryCancel( &( _IotMqttTaskPool ), + &( pResult->job ), + NULL ); + + /* If the retry job could not be canceled, then it is currently + * executing. Ignore the operation. */ + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + pResult = NULL; + } + else + { + /* Check job reference counts. A waitable operation should have a + * count of 2; a non-waitable operation should have a count of 1. */ + IotMqtt_Assert( pResult->jobReference == ( 1 + ( waitable == true ) ) ); + } + } + else + { + /* An operation with no retry in the pending responses list should + * always have a job reference of 1. */ + IotMqtt_Assert( pResult->jobReference == 1 ); + + /* Increment job references of a waitable operation to prevent Wait from + * destroying this operation if it times out. */ + if( waitable == true ) + { + ( pResult->jobReference )++; + + IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", + pMqttConnection, + IotMqtt_OperationType( operation ), + pResult, + ( long int ) ( pResult->jobReference - 1 ), + ( long int ) ( pResult->jobReference ) ); + } + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + if( pResult != NULL ) + { + IotLogDebug( "(MQTT connection %p) Found operation %s.", + pMqttConnection, + IotMqtt_OperationType( operation ) ); + } + else + { + IotLogDebug( "(MQTT connection %p) Operation %s not found.", + pMqttConnection, + IotMqtt_OperationType( operation ) ); + } + + return pResult; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_Notify( _mqttOperation_t * pOperation ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Check if operation is waitable. */ + bool waitable = ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected + * subscriptions are removed by the deserializer, so not removed here. */ + if( pOperation->operation == IOT_MQTT_SUBSCRIBE ) + { + switch( pOperation->status ) + { + case IOT_MQTT_SUCCESS: + break; + + case IOT_MQTT_SERVER_REFUSED: + break; + + default: + _IotMqtt_RemoveSubscriptionByPacket( pOperation->pMqttConnection, + pOperation->packetIdentifier, + -1 ); + break; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check if a callback function is set. */ + if( pOperation->notify.callback.function != NULL ) + { + /* A waitable operation may not have a callback. */ + IotMqtt_Assert( waitable == false ); + + /* Non-waitable operation should have job reference of 1. */ + IotMqtt_Assert( pOperation->jobReference == 1 ); + + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Schedule an invocation of the callback. */ + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessCompletedOperation, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + + /* A completed operation should not be linked. */ + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); + + /* Place the scheduled operation back in the list of operations pending + * processing. */ + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + } + else + { + IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + /* For a waitable operation, post to the wait semaphore. */ + if( waitable == true ) + { + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + + IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " + "notified of completion.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Decrement reference count of operations with no callback. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/aws_iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c similarity index 69% rename from lib/source/mqtt/aws_iot_mqtt_serialize.c rename to lib/source/mqtt/iot_mqtt_serialize.c index 9b68534200..52f078763d 100644 --- a/lib/source/mqtt/aws_iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_mqtt_serialize.c + * @file iot_mqtt_serialize.c * @brief Implements functions that generate and decode MQTT network packets. */ @@ -32,8 +32,11 @@ /* Standard includes. */ #include +/* Error handling include. */ +#include "private/iot_error.h" + /* MQTT internal includes. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -192,7 +195,7 @@ static size_t _remainingLengthEncodedSize( size_t length ); * `pDestination` is large enough to hold the encoded "Remaining length" using * the function #_remainingLengthEncodedSize to avoid buffer overflows. */ -static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, +static uint8_t * _encodeRemainingLength( uint8_t * pDestination, size_t length ); /** @@ -207,9 +210,9 @@ static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, * * @warning This function does not check the size of `pSource`! */ -static AwsIotMqttError_t _decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** const pEnd, - size_t * const pLength ); +static IotMqttError_t _decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** pEnd, + size_t * pLength ); /** * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. @@ -226,7 +229,7 @@ static AwsIotMqttError_t _decodeRemainingLength( const uint8_t * pSource, * overflow. */ static uint8_t * _encodeString( uint8_t * pDestination, - const char * const source, + const char * source, uint16_t sourceLength ); /** @@ -236,10 +239,13 @@ static uint8_t * _encodeString( uint8_t * pDestination, * @param[in] pConnectInfo User-provided CONNECT information struct. * @param[out] pRemainingLength Output for calculated "Remaining length" field. * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. */ -static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ); +static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); /** * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated @@ -252,15 +258,15 @@ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectIn * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` * otherwise. If this function returns `false`, the output parameters should be ignored. */ -static bool _publishPacketSize( const AwsIotMqttPublishInfo_t * const pPublishInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ); +static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); /** * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE * packet generated from the given parameters. * - * @param[in] type Either AWS_IOT_MQTT_SUBSCRIBE or AWS_IOT_MQTT_UNSUBSCRIBE. + * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. * @param[in] pSubscriptionList User-provided array of subscriptions. * @param[in] subscriptionCount Size of `pSubscriptionList`. * @param[out] pRemainingLength Output for calculated "Remaining length" field. @@ -269,25 +275,26 @@ static bool _publishPacketSize( const AwsIotMqttPublishInfo_t * const pPublishIn * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` * otherwise. If this function returns `false`, the output parameters should be ignored. */ -static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, - const AwsIotMqttSubscription_t * const pSubscriptionList, +static bool _subscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, - size_t * const pRemainingLength, - size_t * const pPacketSize ); + size_t * pRemainingLength, + size_t * pPacketSize ); /*-----------------------------------------------------------*/ #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE + /** * @brief If logging is enabled, define a log configuration that only prints the log * string. This is used when printing out details of deserialized MQTT packets. */ -static const IotLogConfig_t _logHideAll = -{ - .hideLibraryName = true, - .hideLogLevel = true, - .hideTimestring = true -}; + static const IotLogConfig_t _logHideAll = + { + .hideLibraryName = true, + .hideLogLevel = true, + .hideTimestring = true + }; #endif /** @@ -307,7 +314,7 @@ static uint16_t _nextPacketIdentifier( void ) /* Lock the packet identifier mutex so that only one thread may read and * modify nextPacketIdentifier. */ - IotMutex_Lock( &_packetIdentifierMutex ); + IotMutex_Lock( &( _packetIdentifierMutex ) ); /* Read the next packet identifier. */ newPacketIdentifier = nextPacketIdentifier; @@ -318,7 +325,7 @@ static uint16_t _nextPacketIdentifier( void ) nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); /* Unlock the packet identifier mutex. */ - IotMutex_Unlock( &_packetIdentifierMutex ); + IotMutex_Unlock( &( _packetIdentifierMutex ) ); return newPacketIdentifier; } @@ -327,8 +334,10 @@ static uint16_t _nextPacketIdentifier( void ) static size_t _remainingLengthEncodedSize( size_t length ) { + size_t encodedSize = 0; + /* length should have already been checked before calling this function. */ - AwsIotMqtt_Assert( length <= _MQTT_MAX_REMAINING_LENGTH ); + IotMqtt_Assert( length <= _MQTT_MAX_REMAINING_LENGTH ); /* Determine how many bytes are needed to encode length. * The values below are taken from the MQTT 3.1.1 spec. */ @@ -336,28 +345,30 @@ static size_t _remainingLengthEncodedSize( size_t length ) /* 1 byte is needed to encode lengths between 0 and 127. */ if( length < 128 ) { - return 1; + encodedSize = 1; } - /* 2 bytes are needed to encode lengths between 128 and 16,383. */ - if( length < 16384 ) + else if( length < 16384 ) { - return 2; + encodedSize = 2; } - /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ - if( length < 2097152 ) + else if( length < 2097152 ) { - return 3; + encodedSize = 3; } - /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ - return 4; + else + { + encodedSize = 4; + } + + return encodedSize; } /*-----------------------------------------------------------*/ -static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, +static uint8_t * _encodeRemainingLength( uint8_t * pDestination, size_t length ) { uint8_t lengthByte = 0, * pLengthEnd = pDestination; @@ -373,6 +384,10 @@ static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, { _UINT8_SET_BIT( lengthByte, 7 ); } + else + { + _EMPTY_ELSE_MARKER; + } /* Output a single encoded byte. */ *pLengthEnd = lengthByte; @@ -384,57 +399,78 @@ static uint8_t * _encodeRemainingLength( uint8_t * const pDestination, /*-----------------------------------------------------------*/ -static AwsIotMqttError_t _decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** const pEnd, - size_t * const pLength ) +static IotMqttError_t _decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** pEnd, + size_t * const pLength ) { + IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t encodedByte = 0; - size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { if( multiplier > 2097152 ) /* 128 ^ 3 */ { - return AWS_IOT_MQTT_BAD_PARAMETER; + status = IOT_MQTT_BAD_PARAMETER; + break; } + else + { + encodedByte = *pSource; + pSource++; - encodedByte = *pSource; - pSource++; - - remainingLength += ( encodedByte & 0x7F ) * multiplier; - multiplier *= 128; - bytesDecoded++; + remainingLength += ( encodedByte & 0x7F ) * multiplier; + multiplier *= 128; + bytesDecoded++; + } } while( ( encodedByte & 0x80 ) != 0 ); - /* Check that the number of bytes decoded conforms to the spec-compliant - * representation of the remaining length. */ - if( bytesDecoded != _remainingLengthEncodedSize( remainingLength ) ) + if( status == IOT_MQTT_SUCCESS ) { - return AWS_IOT_MQTT_BAD_PARAMETER; - } + expectedSize = _remainingLengthEncodedSize( remainingLength ); - /* Valid remaining length should be at most 4 bytes. */ - AwsIotMqtt_Assert( bytesDecoded <= 4 ); + if( bytesDecoded != expectedSize ) + { + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Valid remaining length should be at most 4 bytes. */ + IotMqtt_Assert( bytesDecoded <= 4 ); - /* Set the output parameters. */ - if( pLength != NULL ) - { - *pLength = remainingLength; - } + /* Set the output parameters. */ + if( pLength != NULL ) + { + *pLength = remainingLength; + } + else + { + _EMPTY_ELSE_MARKER; + } - if( pEnd != NULL ) + if( pEnd != NULL ) + { + *pEnd = pSource; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + } + else { - *pEnd = pSource; + _EMPTY_ELSE_MARKER; } - return AWS_IOT_MQTT_SUCCESS; + return status; } /*-----------------------------------------------------------*/ static uint8_t * _encodeString( uint8_t * pDestination, - const char * const source, + const char * source, uint16_t sourceLength ) { /* The first byte of a UTF-8 string is the high byte of the string length. */ @@ -456,11 +492,12 @@ static uint8_t * _encodeString( uint8_t * pDestination, /*-----------------------------------------------------------*/ -static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ) +static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) { - size_t connectPacketSize = 0; + bool status = true; + size_t connectPacketSize = 0, remainingLength = 0; /* The CONNECT packet will always include a 10-byte variable header. */ connectPacketSize += 10U; @@ -470,6 +507,10 @@ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectIn { connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); } + else + { + _EMPTY_ELSE_MARKER; + } /* Add the lengths of the will message and topic name if provided. */ if( pConnectInfo->pWillInfo != NULL ) @@ -477,6 +518,10 @@ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectIn connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); } + else + { + _EMPTY_ELSE_MARKER; + } /* Depending on the status of metrics, add the length of the metrics username * or the user-provided username. */ @@ -488,42 +533,56 @@ static void _connectPacketSize( const AwsIotMqttConnectInfo_t * const pConnectIn } else { + /* Add the lengths of the username and password if provided and not + * connecting to an AWS IoT MQTT server. */ if( pConnectInfo->pUserName != NULL ) { connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); } - } + else + { + _EMPTY_ELSE_MARKER; + } - /* Add the length of the password if provided and not connecting to an AWS - * IoT MQTT server. */ - if( ( pConnectInfo->pPassword != NULL ) && - ( pConnectInfo->awsIotMqttMode == false ) ) - { - connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + if( pConnectInfo->pPassword != NULL ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + else + { + _EMPTY_ELSE_MARKER; + } } /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has - * been calculated. Set the output parameter. */ - AwsIotMqtt_Assert( connectPacketSize < _MQTT_PACKET_CONNECT_MAX_SIZE ); - *pRemainingLength = connectPacketSize; + * been calculated. */ + remainingLength = connectPacketSize; /* Calculate the full size of the MQTT CONNECT packet by adding the size of - * the "Remaining Length" field plus 1 byte for the "Packet Type" field. Set - * the pPacketSize output parameter. */ + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ connectPacketSize += 1 + _remainingLengthEncodedSize( connectPacketSize ); - *pPacketSize = connectPacketSize; - /* Because of the 2-byte restriction on the length of all strings in a CONNECT - * packet, a CONNECT packet cannot be larger than 327700. */ - AwsIotMqtt_Assert( connectPacketSize < _MQTT_PACKET_CONNECT_MAX_SIZE ); + /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ + if( connectPacketSize > _MQTT_PACKET_CONNECT_MAX_SIZE ) + { + status = false; + } + else + { + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; + } + + return status; } /*-----------------------------------------------------------*/ -static bool _publishPacketSize( const AwsIotMqttPublishInfo_t * const pPublishInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ) +static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) { + bool status = true; size_t publishPacketSize = 0; /* The variable header of a PUBLISH packet always contains the topic name. */ @@ -531,128 +590,171 @@ static bool _publishPacketSize( const AwsIotMqttPublishInfo_t * const pPublishIn /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte * packet identifier. */ - if( pPublishInfo->QoS > 0 ) + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) { publishPacketSize += sizeof( uint16_t ); } + else + { + _EMPTY_ELSE_MARKER; + } /* Add the length of the PUBLISH payload. At this point, the "Remaining length" - * has been calculated. Return if the "Remaining length" exceeds what is allowed - * by MQTT 3.1.1. Otherwise, set the output parameter. */ + * has been calculated. Return error if the "Remaining length" exceeds what is + * allowed by MQTT 3.1.1. Otherwise, set the output parameter. */ publishPacketSize += pPublishInfo->payloadLength; if( publishPacketSize > _MQTT_MAX_REMAINING_LENGTH ) { - return false; + status = false; } + else + { + *pRemainingLength = publishPacketSize; - *pRemainingLength = publishPacketSize; - - /* Calculate the full size of the MQTT PUBLISH packet by adding the size of - * the "Remaining Length" field plus 1 byte for the "Packet Type" field. Set - * the pPacketSize output parameter. */ - publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); - *pPacketSize = publishPacketSize; + /* Calculate the full size of the MQTT PUBLISH packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. Set + * the pPacketSize output parameter. */ + publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); + *pPacketSize = publishPacketSize; + } - return true; + return status; } /*-----------------------------------------------------------*/ -static bool _subscriptionPacketSize( AwsIotMqttOperationType_t type, - const AwsIotMqttSubscription_t * const pSubscriptionList, +static bool _subscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, - size_t * const pRemainingLength, - size_t * const pPacketSize ) + size_t * pRemainingLength, + size_t * pPacketSize ) { + bool status = true; size_t i = 0, subscriptionPacketSize = 0; /* Only SUBSCRIBE and UNSUBSCRIBE operations should call this function. */ - AwsIotMqtt_Assert( type == AWS_IOT_MQTT_SUBSCRIBE || type == AWS_IOT_MQTT_UNSUBSCRIBE ); + IotMqtt_Assert( ( type == IOT_MQTT_SUBSCRIBE ) || ( type == IOT_MQTT_UNSUBSCRIBE ) ); /* The variable header of a subscription packet consists of a 2-byte packet * identifier. */ subscriptionPacketSize += sizeof( uint16_t ); /* Sum the lengths of all subscription topic filters; add 1 byte for each - * subscription's QoS if type is AWS_IOT_MQTT_SUBSCRIBE. */ + * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ for( i = 0; i < subscriptionCount; i++ ) { /* Add the length of the topic filter. */ subscriptionPacketSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); /* Only SUBSCRIBE packets include the QoS. */ - if( type == AWS_IOT_MQTT_SUBSCRIBE ) + if( type == IOT_MQTT_SUBSCRIBE ) { subscriptionPacketSize += 1; } + else + { + _EMPTY_ELSE_MARKER; + } } - /* At this point, the "Remaining length" has been calculated. Return if the - * "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, set - * the output parameter.*/ + /* At this point, the "Remaining length" has been calculated. Return error + * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, + * set the output parameter.*/ if( subscriptionPacketSize > _MQTT_MAX_REMAINING_LENGTH ) { - return false; + status = false; } + else + { + *pRemainingLength = subscriptionPacketSize; - *pRemainingLength = subscriptionPacketSize; - - /* Calculate the full size of the subscription packet by adding the size of the - * "Remaining length" field plus 1 byte for the "Packet type" field. Set the - * pPacketSize output parameter. */ - subscriptionPacketSize += 1 + _remainingLengthEncodedSize( subscriptionPacketSize ); - *pPacketSize = subscriptionPacketSize; + /* Calculate the full size of the subscription packet by adding the size of the + * "Remaining length" field plus 1 byte for the "Packet type" field. Set the + * pPacketSize output parameter. */ + subscriptionPacketSize += 1 + _remainingLengthEncodedSize( subscriptionPacketSize ); + *pPacketSize = subscriptionPacketSize; + } - return true; + return status; } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_InitSerialize( void ) +IotMqttError_t _IotMqtt_InitSerialize( void ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + bool packetMutexCreated = false; + /* Create the packet identifier mutex. */ - if( IotMutex_Create( &_packetIdentifierMutex, false ) == false ) + packetMutexCreated = IotMutex_Create( &( _packetIdentifierMutex ), false ); + + if( packetMutexCreated == false ) { - return AWS_IOT_MQTT_INIT_FAILED; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Call any additional serializer initialization function if serializer * overrides are enabled. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef AwsIotMqttInternal_InitSerializeAdditional - if( AwsIotMqttInternal_InitSerializeAdditional() == false ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_InitSerializeAdditional + if( _IotMqtt_InitSerializeAdditional() == false ) { - IotMutex_Destroy( &_packetIdentifierMutex ); - - return AWS_IOT_MQTT_INIT_FAILED; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + _EMPTY_ELSE_MARKER; } #endif #endif - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status != IOT_MQTT_SUCCESS ) + { + if( packetMutexCreated == true ) + { + IotMutex_Destroy( &( _packetIdentifierMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_CleanupSerialize( void ) +void _IotMqtt_CleanupSerialize( void ) { /* Destroy the packet identifier mutex. */ - IotMutex_Destroy( &_packetIdentifierMutex ); + IotMutex_Destroy( &( _packetIdentifierMutex ) ); /* Call any additional serializer cleanup initialization function is serializer * overrides are enabled. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef AwsIotMqttInternal_CleanupSerializeAdditional - AwsIotMqttInternal_CleanupSerializeAdditional(); + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_CleanupSerializeAdditional + _IotMqtt_CleanupSerializeAdditional(); #endif #endif } /*-----------------------------------------------------------*/ -uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, - size_t packetSize ) +uint8_t _IotMqtt_GetPacketType( const uint8_t * pPacket, + size_t packetSize ) { uint8_t packetType = 0; @@ -662,38 +764,57 @@ uint8_t AwsIotMqttInternal_GetPacketType( const uint8_t * const pPacket, * lower bits to ignore flags. */ packetType = *pPacket & 0xf0; } + else + { + _EMPTY_ELSE_MARKER; + } return packetType; } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, - uint8_t ** const pConnectPacket, - size_t * const pPacketSize ) +IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint8_t connectFlags = 0; size_t remainingLength = 0, connectPacketSize = 0; uint8_t * pBuffer = NULL; - /* Calculate the "Remaining length" field and total packet size. */ - _connectPacketSize( pConnectInfo, - &remainingLength, - &connectPacketSize ); + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _connectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) + { + IotLogError( "Connect packet length exceeds %lu, which is the maximum" + " size allowed by MQTT 3.1.1.", + _MQTT_PACKET_CONNECT_MAX_SIZE ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; + } /* Total size of the connect packet should be larger than the "Remaining length" * field. */ - AwsIotMqtt_Assert( connectPacketSize > remainingLength ); + IotMqtt_Assert( connectPacketSize > remainingLength ); /* Allocate memory to hold the CONNECT packet. */ - pBuffer = AwsIotMqtt_MallocMessage( connectPacketSize ); + pBuffer = IotMqtt_MallocMessage( connectPacketSize ); /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { IotLogError( "Failed to allocate memory for CONNECT packet." ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -722,43 +843,55 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn { _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_CLEAN ); } - - /* AWS IoT MQTT servers do not use the MQTT password. */ - if( ( pConnectInfo->pPassword != NULL ) && - ( pConnectInfo->awsIotMqttMode == false ) ) + else { - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_PASSWORD ); + _EMPTY_ELSE_MARKER; } - /* If metrics are enabled, always set the username flag when connecting - * to an AWS IoT MQTT server. Otherwise, set the username flag only when - * not connecting to an AWS IoT MQTT server. */ + /* Username and password depend on MQTT mode. */ if( pConnectInfo->awsIotMqttMode == true ) { + /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server + * never uses a password. */ #if AWS_IOT_MQTT_ENABLE_METRICS == 1 _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_USERNAME ); #endif } else { + /* Set the flags for username and password if provided. */ if( pConnectInfo->pUserName != NULL ) { _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_USERNAME ); } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pConnectInfo->pPassword != NULL ) + { + _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_PASSWORD ); + } + else + { + _EMPTY_ELSE_MARKER; + } } + /* Set will flag if an LWT is provided. */ if( pConnectInfo->pWillInfo != NULL ) { _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL ); /* Flags only need to be changed for will QoS 1 and 2. */ - switch( pConnectInfo->pWillInfo->QoS ) + switch( pConnectInfo->pWillInfo->qos ) { - case 1: + case IOT_MQTT_QOS_1: _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS1 ); break; - case 2: + case IOT_MQTT_QOS_2: _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS2 ); break; @@ -770,6 +903,14 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn { _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_RETAIN ); } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } *pBuffer = connectFlags; @@ -796,10 +937,14 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn pConnectInfo->pWillInfo->pPayload, ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); } + else + { + _EMPTY_ELSE_MARKER; + } /* If metrics are enabled, write the metrics username into the CONNECT packet. - * Otherwise, write the username only when not connecting to an AWS IoT MQTT - * server. */ + * Otherwise, write the username and password only when not connecting to an + * AWS IoT MQTT server. */ if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 @@ -819,46 +964,53 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeConnect( const AwsIotMqttConnectIn pConnectInfo->pUserName, pConnectInfo->userNameLength ); } - } + else + { + _EMPTY_ELSE_MARKER; + } - /* Write the password into the CONNECT packet if provided and this connection - * is not to an AWS IoT MQTT server. */ - if( ( pConnectInfo->pPassword != NULL ) && - ( pConnectInfo->awsIotMqttMode == false ) ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pPassword, - pConnectInfo->passwordLength ); + if( pConnectInfo->pPassword != NULL ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + } + else + { + _EMPTY_ELSE_MARKER; + } } /* Ensure that the difference between the end and beginning of the buffer * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ - AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pConnectPacket ) == connectPacketSize ); + IotMqtt_Assert( ( size_t ) ( pBuffer - *pConnectPacket ) == connectPacketSize ); /* Print out the serialized CONNECT packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const pConnackStart, - size_t dataLength, - size_t * const pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, + size_t dataLength, + size_t * pBytesProcessed ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + /* If logging is enabled, declare the CONNACK response code strings. The * fourth byte of CONNACK indexes into this array for the corresponding response. */ #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE - static const char * pConnackResponses[ 6 ] = - { - "Connection accepted.", /* 0 */ - "Connection refused: unacceptable protocol version.", /* 1 */ - "Connection refused: identifier rejected.", /* 2 */ - "Connection refused: server unavailable", /* 3 */ - "Connection refused: bad user name or password.", /* 4 */ - "Connection refused: not authorized." /* 5 */ - }; + static const char * pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; #endif /* According to MQTT 3.1.1, CONNACK packets are all 4 bytes in size. If the @@ -871,7 +1023,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p _MQTT_PACKET_CONNACK_SIZE ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* The next 4 bytes will be processed by this function. */ @@ -885,7 +1041,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p "Bad control packet type 0x%02x.", pConnackStart[ 0 ] ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* According to MQTT 3.1.1, the second byte of CONNACK must specify a @@ -897,7 +1057,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p "CONNACK does not have remaining length of %d.", _MQTT_PACKET_CONNACK_REMAINING_LENGTH ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check the reserved bits in CONNACK. The high 7 bits of the second byte @@ -908,7 +1072,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p &_logHideAll, "Reserved bits in CONNACK incorrect." ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Determine if the "Session Present" bit it set. This is the lowest bit of @@ -924,7 +1092,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p * "Session Present" bit is set. */ if( pConnackStart[ 3 ] != 0 ) { - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } } else @@ -942,7 +1114,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p "CONNACK response %hhu is not valid.", pConnackStart[ 3 ] ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Print the appropriate message for the CONNACK response code if logs are @@ -957,20 +1133,24 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeConnack( const uint8_t * const p /* A nonzero CONNACK response code means the connection was refused. */ if( pConnackStart[ 3 ] > 0 ) { - return AWS_IOT_MQTT_SERVER_REFUSED; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); + } + else + { + _EMPTY_ELSE_MARKER; } - /* A zero CONNACK response code means the connection was accepted. */ - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint8_t ** const pPublishPacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ) +IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint8_t publishFlags = 0; uint16_t packetIdentifier = 0; size_t remainingLength = 0, publishPacketSize = 0; @@ -978,30 +1158,36 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _publishPacketSize( pPublishInfo, - &remainingLength, - &publishPacketSize ) == false ) + if( _publishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) { - IotLogError( "Publish packet remaining length exceeds %d, which is the " + IotLogError( "Publish packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", _MQTT_MAX_REMAINING_LENGTH ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Total size of the publish packet should be larger than the "Remaining length" * field. */ - AwsIotMqtt_Assert( publishPacketSize > remainingLength ); + IotMqtt_Assert( publishPacketSize > remainingLength ); /* Allocate memory to hold the PUBLISH packet. */ - pBuffer = AwsIotMqtt_MallocMessage( publishPacketSize ); + pBuffer = IotMqtt_MallocMessage( publishPacketSize ); /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { IotLogError( "Failed to allocate memory for PUBLISH packet." ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -1011,19 +1197,27 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn /* The first byte of a PUBLISH packet contains the packet type and flags. */ publishFlags = _MQTT_PACKET_TYPE_PUBLISH; - if( pPublishInfo->QoS == 1 ) + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ); } - else if( pPublishInfo->QoS == 2 ) + else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) { _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS2 ); } + else + { + _EMPTY_ELSE_MARKER; + } if( pPublishInfo->retain == true ) { _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); } + else + { + _EMPTY_ELSE_MARKER; + } *pBuffer = publishFlags; pBuffer++; @@ -1037,18 +1231,22 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn pPublishInfo->topicNameLength ); /* A packet identifier is required for QoS 1 and 2 messages. */ - if( pPublishInfo->QoS > 0 ) + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) { /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); *pPacketIdentifier = packetIdentifier; - AwsIotMqtt_Assert( packetIdentifier != 0 ); + IotMqtt_Assert( packetIdentifier != 0 ); /* Place the packet identifier into the PUBLISH packet. */ *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); pBuffer += 2; } + else + { + _EMPTY_ELSE_MARKER; + } /* The payload is placed after the packet identifier. */ if( pPublishInfo->payloadLength > 0 ) @@ -1056,22 +1254,26 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePublish( const AwsIotMqttPublishIn ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); pBuffer += pPublishInfo->payloadLength; } + else + { + _EMPTY_ELSE_MARKER; + } /* Ensure that the difference between the end and beginning of the buffer * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ - AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pPublishPacket ) == publishPacketSize ); + IotMqtt_Assert( ( size_t ) ( pBuffer - *pPublishPacket ) == publishPacketSize ); /* Print out the serialized PUBLISH packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, - uint8_t * const pPublishPacket, - uint16_t * const pNewPacketIdentifier ) +void _IotMqtt_PublishSetDup( bool awsIotMqttMode, + uint8_t * pPublishPacket, + uint16_t * pNewPacketIdentifier ) { const uint8_t * pTopicNameLength = NULL; uint8_t * pPacketIdentifier = NULL; @@ -1092,7 +1294,7 @@ void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, pPacketIdentifier = ( uint8_t * ) ( pTopicNameLength + topicNameLength + sizeof( uint16_t ) ); IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", - *pPacketIdentifier, + _UINT16_DECODE( pPacketIdentifier ), newPacketIdentifier ); /* Replace the packet identifier. */ @@ -1104,17 +1306,20 @@ void AwsIotMqttInternal_PublishSetDup( bool awsIotMqttMode, { /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ _UINT8_SET_BIT( *pPublishPacket, _MQTT_PUBLISH_FLAG_DUP ); + + IotLogDebug( "PUBLISH DUP flag set." ); } } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const pPublishStart, - size_t dataLength, - AwsIotMqttPublishInfo_t * const pOutput, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, + size_t dataLength, + IotMqttPublishInfo_t * pOutput, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t remainingLength = 0, packetSize = 0; uint8_t publishFlags = 0; uint16_t packetIdentifier = 0; @@ -1130,13 +1335,17 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Decode the "Remaining length", which is the second byte in PUBLISH. */ - if( _decodeRemainingLength( pPublishStart + 1, - &pVariableHeader, - &remainingLength ) != AWS_IOT_MQTT_SUCCESS ) + status = _decodeRemainingLength( pPublishStart + 1, &pVariableHeader, &remainingLength ); + + if( status != IOT_MQTT_SUCCESS ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1146,7 +1355,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * of the data stream by marking it processed. */ *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } IotLog( IOT_LOG_DEBUG, @@ -1168,7 +1381,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p ( unsigned long ) dataLength ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* The flags are the lower 4 bits of the first byte in PUBLISH. */ @@ -1191,25 +1408,29 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p &_logHideAll, "Bad QoS: 3." ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } - pOutput->QoS = 2; + pOutput->qos = IOT_MQTT_QOS_2; } /* Check for QoS 1. */ else if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ) == true ) { - pOutput->QoS = 1; + pOutput->qos = IOT_MQTT_QOS_1; } /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ else { - pOutput->QoS = 0; + pOutput->qos = IOT_MQTT_QOS_0; } IotLog( IOT_LOG_DEBUG, &_logHideAll, - "QoS is %d.", pOutput->QoS ); + "QoS is %d.", pOutput->qos ); /* Parse the DUP bit. */ if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_DUP ) == true ) @@ -1226,7 +1447,7 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p } /* Sanity checks for "Remaining length". */ - if( pOutput->QoS == 0 ) + if( pOutput->qos == IOT_MQTT_QOS_0 ) { /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate * topic name length (2 bytes) and topic name (at least 1 byte). */ @@ -1237,7 +1458,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p "QoS 0 PUBLISH cannot have a remaining length less than 3." ); *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } } else @@ -1252,7 +1477,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } } @@ -1261,7 +1490,7 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p pOutput->topicNameLength = _UINT16_DECODE( pVariableHeader ); /* Sanity checks for topic name length and "Remaining length". */ - if( pOutput->QoS == 0 ) + if( pOutput->qos == IOT_MQTT_QOS_0 ) { /* Check that the "Remaining length" is at least as large as the variable * header. */ @@ -1272,7 +1501,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p "Remaining length cannot be less than variable header length." ); *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } } else @@ -1286,7 +1519,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p "Remaining length cannot be less than variable header length." ); *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } } @@ -1304,7 +1541,7 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p * identifier starts immediately after the topic name. */ pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); - if( pOutput->QoS > 0 ) + if( pOutput->qos > IOT_MQTT_QOS_0 ) { packetIdentifier = _UINT16_DECODE( pPacketIdentifierHigh ); *pPacketIdentifier = packetIdentifier; @@ -1316,13 +1553,21 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p /* Packet identifier cannot be 0. */ if( packetIdentifier == 0 ) { - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain * a packet identifer, but QoS 0 PUBLISH packets do not. */ - if( pOutput->QoS == 0 ) + if( pOutput->qos == IOT_MQTT_QOS_0 ) { pOutput->payloadLength = ( uint16_t ) ( remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh; @@ -1338,48 +1583,53 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePublish( const uint8_t * const p "Payload length %hu. Payload:", pOutput->payloadLength ); IotLog_PrintBuffer( NULL, pOutput->pPayload, pOutput->payloadLength ); - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializePuback( uint16_t packetIdentifier, - uint8_t ** const pPubackPacket, - size_t * const pPacketSize ) +IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) { + IotMqttError_t status = IOT_MQTT_SUCCESS; + /* Allocate memory for PUBACK. */ - uint8_t * pBuffer = AwsIotMqtt_MallocMessage( _MQTT_PACKET_PUBACK_SIZE ); + uint8_t * pBuffer = IotMqtt_MallocMessage( _MQTT_PACKET_PUBACK_SIZE ); if( pBuffer == NULL ) { IotLogError( "Failed to allocate memory for PUBACK packet" ); - return AWS_IOT_MQTT_NO_MEMORY; + status = IOT_MQTT_NO_MEMORY; } + else + { + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPubackPacket = pBuffer; + *pPacketSize = _MQTT_PACKET_PUBACK_SIZE; - /* Set the output parameters. The remainder of this function always succeeds. */ - *pPubackPacket = pBuffer; - *pPacketSize = _MQTT_PACKET_PUBACK_SIZE; - - /* Set the 4 bytes in PUBACK. */ - pBuffer[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; - pBuffer[ 1 ] = _MQTT_PACKET_PUBACK_REMAINING_LENGTH; - pBuffer[ 2 ] = _UINT16_HIGH_BYTE( packetIdentifier ); - pBuffer[ 3 ] = _UINT16_LOW_BYTE( packetIdentifier ); + /* Set the 4 bytes in PUBACK. */ + pBuffer[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; + pBuffer[ 1 ] = _MQTT_PACKET_PUBACK_REMAINING_LENGTH; + pBuffer[ 2 ] = _UINT16_HIGH_BYTE( packetIdentifier ); + pBuffer[ 3 ] = _UINT16_LOW_BYTE( packetIdentifier ); - /* Print out the serialized PUBACK packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, _MQTT_PACKET_PUBACK_SIZE ); + /* Print out the serialized PUBACK packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, _MQTT_PACKET_PUBACK_SIZE ); + } - return AWS_IOT_MQTT_SUCCESS; + return status; } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pPubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializePuback( const uint8_t * pPubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint16_t packetIdentifier = 0; /* According to MQTT 3.1.1, PUBACK packets are all 4 bytes in size. If the @@ -1392,7 +1642,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP _MQTT_PACKET_PUBACK_SIZE ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* The next 4 bytes will be processed by this function. */ @@ -1409,7 +1663,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP /* Packet identifier cannot be 0. */ if( packetIdentifier == 0 ) { - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check that the control packet type is 0x40. */ @@ -1420,7 +1678,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP "Bad control packet type 0x%02x.", pPubackStart[ 0 ] ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check the "Remaining length" (second byte) of the received PUBACK. */ @@ -1431,52 +1693,65 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePuback( const uint8_t * const pP "PUBACK does not have remaining length of %d.", _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint8_t ** const pSubscribePacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ) +IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t i = 0, subscribePacketSize = 0, remainingLength = 0; uint16_t packetIdentifier = 0; uint8_t * pBuffer = NULL; /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _subscriptionPacketSize( AWS_IOT_MQTT_SUBSCRIBE, + if( _subscriptionPacketSize( IOT_MQTT_SUBSCRIBE, pSubscriptionList, subscriptionCount, &remainingLength, &subscribePacketSize ) == false ) { - IotLogError( "Subscribe packet remaining length exceeds %d, which is the " + IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", _MQTT_MAX_REMAINING_LENGTH ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Total size of the subscribe packet should be larger than the "Remaining length" * field. */ - AwsIotMqtt_Assert( subscribePacketSize > remainingLength ); + IotMqtt_Assert( subscribePacketSize > remainingLength ); /* Allocate memory to hold the SUBSCRIBE packet. */ - pBuffer = AwsIotMqtt_MallocMessage( subscribePacketSize ); + pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -1493,7 +1768,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscri /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); *pPacketIdentifier = packetIdentifier; - AwsIotMqtt_Assert( packetIdentifier != 0 ); + IotMqtt_Assert( packetIdentifier != 0 ); /* Place the packet identifier into the SUBSCRIBE packet. */ *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); @@ -1508,29 +1783,29 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeSubscribe( const AwsIotMqttSubscri pSubscriptionList[ i ].topicFilterLength ); /* Place the QoS in the SUBSCRIBE packet. */ - *pBuffer = ( uint8_t ) pSubscriptionList[ i ].QoS; + *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); pBuffer++; } /* Ensure that the difference between the end and beginning of the buffer * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ - AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pSubscribePacket ) == subscribePacketSize ); + IotMqtt_Assert( ( size_t ) ( pBuffer - *pSubscribePacket ) == subscribePacketSize ); /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t mqttConnection, - const uint8_t * const pSubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, + const uint8_t * pSubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { - AwsIotMqttError_t status = AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t i = 0, remainingLength = 0, packetSize = 0; uint16_t packetIdentifier = 0; uint8_t subscriptionStatus = 0; @@ -1547,13 +1822,17 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m _MQTT_PACKET_SUBACK_MINIMUM_SIZE ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Decode the "Remaining length" in SUBACK, which starts at byte 2. */ if( _decodeRemainingLength( pSubackStart + 1, &pVariableHeader, - &remainingLength ) != AWS_IOT_MQTT_SUCCESS ) + &remainingLength ) != IOT_MQTT_SUCCESS ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1563,7 +1842,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m * of the data stream by marking it processed. */ *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } IotLog( IOT_LOG_DEBUG, @@ -1579,7 +1862,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m "SUBACK cannot have a remaining length less than 3." ); *pBytesProcessed = dataLength; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Calculate the packet size and set the output parameter. */ @@ -1597,7 +1884,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m ( unsigned long ) dataLength ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ @@ -1616,7 +1907,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m "Bad control packet type 0x%02x.", pSubackStart[ 0 ] ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Iterate through each status byte in the SUBACK packet. */ @@ -1643,11 +1938,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m "Topic filter %lu refused.", ( unsigned long ) i ); /* Remove a rejected subscription from the subscription manager. */ - AwsIotMqttInternal_RemoveSubscriptionByPacket( pMqttConnection, - packetIdentifier, - ( long ) i ); + _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, + packetIdentifier, + ( long ) i ); - status = AWS_IOT_MQTT_SERVER_REFUSED; + status = IOT_MQTT_SERVER_REFUSED; break; @@ -1656,61 +1951,74 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeSuback( AwsIotMqttConnection_t m &_logHideAll, "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); - status = AWS_IOT_MQTT_BAD_RESPONSE; + status = IOT_MQTT_BAD_RESPONSE; break; } /* Stop parsing the subscription statuses if a bad response was received. */ - if( status == AWS_IOT_MQTT_BAD_RESPONSE ) + if( status == IOT_MQTT_BAD_RESPONSE ) { break; } + else + { + _EMPTY_ELSE_MARKER; + } } - return status; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint8_t ** const pUnsubscribePacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ) +IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pUnsubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t i = 0, unsubscribePacketSize = 0, remainingLength = 0; uint16_t packetIdentifier = 0; uint8_t * pBuffer = NULL; /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _subscriptionPacketSize( AWS_IOT_MQTT_UNSUBSCRIBE, + if( _subscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, pSubscriptionList, subscriptionCount, &remainingLength, &unsubscribePacketSize ) == false ) { - IotLogError( "Unsubscribe packet remaining length exceeds %d, which is the " + IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", _MQTT_MAX_REMAINING_LENGTH ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Total size of the unsubscribe packet should be larger than the "Remaining length" * field. */ - AwsIotMqtt_Assert( unsubscribePacketSize > remainingLength ); + IotMqtt_Assert( unsubscribePacketSize > remainingLength ); /* Allocate memory to hold the UNSUBSCRIBE packet. */ - pBuffer = AwsIotMqtt_MallocMessage( unsubscribePacketSize ); + pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); /* Check that sufficient memory was allocated. */ if( pBuffer == NULL ) { IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - return AWS_IOT_MQTT_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -1727,7 +2035,7 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubsc /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); *pPacketIdentifier = packetIdentifier; - AwsIotMqtt_Assert( packetIdentifier != 0 ); + IotMqtt_Assert( packetIdentifier != 0 ); /* Place the packet identifier into the UNSUBSCRIBE packet. */ *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); @@ -1744,21 +2052,22 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeUnsubscribe( const AwsIotMqttSubsc /* Ensure that the difference between the end and beginning of the buffer * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ - AwsIotMqtt_Assert( ( size_t ) ( pBuffer - *pUnsubscribePacket ) == unsubscribePacketSize ); + IotMqtt_Assert( ( size_t ) ( pBuffer - *pUnsubscribePacket ) == unsubscribePacketSize ); /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const pUnsubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint16_t packetIdentifier = 0; /* According to MQTT 3.1.1, UNSUBACK packets are all 4 bytes in size. If the @@ -1771,7 +2080,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const _MQTT_PACKET_UNSUBACK_SIZE ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* The next 4 bytes will be processed by this function. */ @@ -1784,7 +2097,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const /* Packet identifier cannot be 0. */ if( packetIdentifier == 0 ) { - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check that the control packet type is 0xb0. */ @@ -1795,7 +2112,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const "Bad control packet type 0x%02x.", pUnsubackStart[ 0 ] ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ @@ -1806,20 +2127,24 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializeUnsuback( const uint8_t * const "UNSUBACK does not have remaining length of %d.", _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } IotLog( IOT_LOG_DEBUG, &_logHideAll, "Packet identifier %hu.", packetIdentifier ); - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreqPacket, - size_t * const pPacketSize ) +IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ) { /* PINGREQ packets are always the same. */ static const uint8_t pPingreq[ _MQTT_PACKET_PINGREQ_SIZE ] = @@ -1835,15 +2160,17 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreq /* Print out the PINGREQ packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, _MQTT_PACKET_PINGREQ_SIZE ); - return AWS_IOT_MQTT_SUCCESS; + return IOT_MQTT_SUCCESS; } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const pPingrespStart, - size_t dataLength, - size_t * const pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializePingresp( const uint8_t * pPingrespStart, + size_t dataLength, + size_t * pBytesProcessed ) { + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + /* According to MQTT 3.1.1, PINGRESP packets are all 2 bytes in size. If the * data stream has fewer than 2 bytes, then the PINGRESP packet is incomplete. */ if( dataLength < _MQTT_PACKET_PINGRESP_SIZE ) @@ -1854,7 +2181,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const _MQTT_PACKET_PINGRESP_SIZE ); *pBytesProcessed = 0; - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* The next 2 bytes will be processed by this function. */ @@ -1868,7 +2199,11 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const "Bad control packet type 0x%02x.", pPingrespStart[ 0 ] ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check the "Remaining length" (second byte) of the received PINGRESP. */ @@ -1879,16 +2214,20 @@ AwsIotMqttError_t AwsIotMqttInternal_DeserializePingresp( const uint8_t * const "PINGRESP does not have remaining length of %d.", _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); - return AWS_IOT_MQTT_BAD_RESPONSE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; } - return AWS_IOT_MQTT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_SerializeDisconnect( uint8_t ** const pDisconnectPacket, - size_t * const pPacketSize ) +IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ) { /* DISCONNECT packets are always the same. */ static const uint8_t pDisconnect[ _MQTT_PACKET_DISCONNECT_SIZE ] = @@ -1904,28 +2243,39 @@ AwsIotMqttError_t AwsIotMqttInternal_SerializeDisconnect( uint8_t ** const pDisc /* Print out the DISCONNECT packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, _MQTT_PACKET_DISCONNECT_SIZE ); - return AWS_IOT_MQTT_SUCCESS; + return IOT_MQTT_SUCCESS; } /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ) +void _IotMqtt_FreePacket( uint8_t * pPacket ) { uint8_t packetType = *pPacket; /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static * memory. */ - if( ( packetType != _MQTT_PACKET_TYPE_DISCONNECT ) && - ( packetType != _MQTT_PACKET_TYPE_PINGREQ ) ) + if( packetType != _MQTT_PACKET_TYPE_DISCONNECT ) { - AwsIotMqtt_FreeMessage( pPacket ); + if( packetType != _MQTT_PACKET_TYPE_PINGREQ ) + { + IotMqtt_FreeMessage( pPacket ); + } + else + { + _EMPTY_ELSE_MARKER; + } } + else + { + _EMPTY_ELSE_MARKER; + } + } /*-----------------------------------------------------------*/ /* If the MQTT library is being tested, include a file that allows access to * internal functions and variables. */ -#if AWS_IOT_MQTT_TEST == 1 - #include "aws_iot_test_access_mqtt_serialize.c" +#if IOT_MQTT_TEST == 1 + #include "iot_test_access_mqtt_serialize.c" #endif diff --git a/lib/source/mqtt/aws_iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c similarity index 52% rename from lib/source/mqtt/aws_iot_mqtt_subscription.c rename to lib/source/mqtt/iot_mqtt_subscription.c index f36e7a0807..c6574242e7 100644 --- a/lib/source/mqtt/aws_iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_mqtt_subscription.c + * @file iot_mqtt_subscription.c * @brief Implements functions that manage subscriptions for an MQTT connection. */ @@ -33,8 +33,11 @@ #include #include +/* Error handling include. */ +#include "private/iot_error.h" + /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -57,7 +60,7 @@ typedef struct _topicMatchParams typedef struct _packetMatchParams { uint16_t packetIdentifier; /**< Packet identifier to match. */ - long order; /**< Order to match. Set to `-1` to ignore. */ + int32_t order; /**< Order to match. Set to `-1` to ignore. */ } _packetMatchParams_t; /*-----------------------------------------------------------*/ @@ -92,29 +95,45 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, static bool _topicMatch( const IotLink_t * pSubscriptionLink, void * pMatch ) { + _IOT_FUNCTION_ENTRY( bool, false ); uint16_t nameIndex = 0, filterIndex = 0; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; /* Extract the relevant strings and lengths from parameters. */ - const char * const pTopicName = pParam->pTopicName; - const char * const pTopicFilter = pSubscription->pTopicFilter; + const char * pTopicName = pParam->pTopicName; + const char * pTopicFilter = pSubscription->pTopicFilter; const uint16_t topicNameLength = pParam->topicNameLength; const uint16_t topicFilterLength = pSubscription->topicFilterLength; /* Check for an exact match. */ if( topicNameLength == topicFilterLength ) { - return( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); + status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); + + _IOT_GOTO_CLEANUP(); + } + else + { + _EMPTY_ELSE_MARKER; } /* If the topic lengths are different but an exact match is required, return * false. */ if( pParam->exactMatchOnly == true ) { - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) @@ -126,20 +145,58 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, /* Handle special corner cases as documented by the MQTT protocol spec. */ /* Filter "sport/#" also matches "sport" since # includes the parent level. */ - if( ( nameIndex == topicNameLength - 1 ) && - ( filterIndex == topicFilterLength - 3 ) && - ( pTopicFilter[ filterIndex + 1 ] == '/' ) && - ( pTopicFilter[ filterIndex + 2 ] == '#' ) ) + if( nameIndex == topicNameLength - 1 ) { - return true; + if( filterIndex == topicFilterLength - 3 ) + { + if( pTopicFilter[ filterIndex + 1 ] == '/' ) + { + if( pTopicFilter[ filterIndex + 2 ] == '#' ) + { + _IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } /* Filter "sport/+" also matches the "sport/" but not "sport". */ - if( ( nameIndex == topicNameLength - 1 ) && - ( filterIndex == topicFilterLength - 2 ) && - ( pTopicFilter[ filterIndex + 1 ] == '+' ) ) + if( nameIndex == topicNameLength - 1 ) { - return true; + if( filterIndex == topicFilterLength - 2 ) + { + if( pTopicFilter[ filterIndex + 1 ] == '+' ) + { + _IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } } else @@ -162,13 +219,13 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { /* Subsequent characters don't need to be checked if the for the * multi-level wildcard. */ - return true; + _IOT_SET_AND_GOTO_CLEANUP( true ); } else { /* Any character mismatch other than '+' or '#' means the topic * name does not match the topic filter. */ - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); } } @@ -180,10 +237,14 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, /* If the end of both strings has been reached, they match. */ if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) { - return true; + _IOT_SET_AND_GOTO_CLEANUP( true ); + } + else + { + _EMPTY_ELSE_MARKER; } - return false; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -192,6 +253,11 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, void * pMatch ) { bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); @@ -215,7 +281,7 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, if( match == true ) { /* Reference count must not be negative. */ - AwsIotMqtt_Assert( pSubscription->references >= 0 ); + IotMqtt_Assert( pSubscription->references >= 0 ); /* If the reference count is positive, this subscription cannot be * removed yet because there are subscription callbacks using it. */ @@ -227,6 +293,14 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, * will remove and clean up this subscription. */ pSubscription->unsubscribed = true; } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } return match; @@ -234,13 +308,15 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const pMqttConnection, - uint16_t subscribePacketIdentifier, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount ) +IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, + uint16_t subscribePacketIdentifier, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ) { + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t i = 0; _mqttSubscription_t * pNewSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { .exactMatchOnly = true }; IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); @@ -250,17 +326,17 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const /* Check if this topic filter is already registered. */ topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; - pNewSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ), - link ); - - if( pNewSubscription != NULL ) + pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); + + if( pSubscriptionLink != NULL ) { + pNewSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + /* The lengths of exactly matching topic filters must match. */ - AwsIotMqtt_Assert( pNewSubscription->topicFilterLength == pSubscriptionList[ i ].topicFilterLength ); + IotMqtt_Assert( pNewSubscription->topicFilterLength == pSubscriptionList[ i ].topicFilterLength ); /* Replace the callback and packet info with the new parameters. */ pNewSubscription->callback = pSubscriptionList[ i ].callback; @@ -270,132 +346,165 @@ AwsIotMqttError_t AwsIotMqttInternal_AddSubscriptions( _mqttConnection_t * const else { /* Allocate memory for a new subscription. */ - pNewSubscription = AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + - pSubscriptionList[ i ].topicFilterLength ); + pNewSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + + pSubscriptionList[ i ].topicFilterLength ); - /* If memory allocation failed, remove all previously added subscriptions. */ if( pNewSubscription == NULL ) { - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( pMqttConnection, - pSubscriptionList, - i ); - - return AWS_IOT_MQTT_NO_MEMORY; + status = IOT_MQTT_NO_MEMORY; + break; + } + else + { + /* Clear the new subscription. */ + ( void ) memset( pNewSubscription, + 0x00, + sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); + + /* Set the members of the new subscription and add it to the list. */ + pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; + pNewSubscription->packetInfo.order = i; + pNewSubscription->callback = pSubscriptionList[ i ].callback; + pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; + ( void ) strncpy( pNewSubscription->pTopicFilter, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), + &( pNewSubscription->link ) ); } - - /* Clear the new subscription. */ - ( void ) memset( pNewSubscription, - 0x00, - sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); - - /* Set the members of the new subscription and add it to the list. */ - pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; - pNewSubscription->packetInfo.order = i; - pNewSubscription->callback = pSubscriptionList[ i ].callback; - pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; - ( void ) strncpy( pNewSubscription->pTopicFilter, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); - - IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), - &( pNewSubscription->link ) ); } } IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - return AWS_IOT_MQTT_SUCCESS; + /* If memory allocation failed, remove all previously added subscriptions. */ + if( status != IOT_MQTT_SUCCESS ) + { + _IotMqtt_RemoveSubscriptionByTopicFilter( pMqttConnection, + pSubscriptionList, + i ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + return status; } /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_ProcessPublish( _mqttConnection_t * const pMqttConnection, - AwsIotMqttCallbackParam_t * const pCallbackParam ) +void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, + IotMqttCallbackParam_t * pCallbackParam ) { _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pStartPoint = NULL; + IotLink_t * pCurrentLink = NULL, * pNextLink = NULL; void * pParam1 = NULL; void ( * callbackFunction )( void *, - AwsIotMqttCallbackParam_t * const ) = NULL; - const _topicMatchParams_t topicMatchParams = + IotMqttCallbackParam_t * ) = NULL; + _topicMatchParams_t topicMatchParams = { .pTopicName = pCallbackParam->message.info.pTopicName, .topicNameLength = pCallbackParam->message.info.topicNameLength, .exactMatchOnly = false }; - /* Prevent any other thread from modifying the subscription list while this - * function is searching. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - pStartPoint = IotListDouble_PeekHead( &( pMqttConnection->subscriptionList ) ); - - /* Search the subscription list for all matching subscriptions. */ - while( pStartPoint != NULL ) + /* Increment MQTT connection reference count to mark it as being used for + * subscription callbacks. Do not proceed if the reference count could not + * be incremented; in this case, the connection is likely closed. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) { - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - pStartPoint, - _topicMatch, - ( void * ) ( &topicMatchParams ) ), - link ); - - /* No subscription found. Exit loop. */ - if( pSubscription == NULL ) + /* Prevent any other thread from modifying the subscription list while this + * function is searching. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + + /* Search the subscription list for all matching subscriptions starting at + * the list head. */ + while( true ) { - break; - } + pCurrentLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + pCurrentLink, + _topicMatch, + &topicMatchParams ); - /* Subscription validation should not have allowed a NULL callback function. */ - AwsIotMqtt_Assert( pSubscription->callback.function != NULL ); + /* No subscription found. Exit loop. */ + if( pCurrentLink == NULL ) + { + break; + } + else + { + _EMPTY_ELSE_MARKER; + } - /* Increment the subscription's reference count. */ - ( pSubscription->references )++; + /* Subscription found. Calculate pointer to subscription object. */ + pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); - /* Copy the necessary members of the subscription before releasing the - * subscription list mutex. */ - pParam1 = pSubscription->callback.param1; - callbackFunction = pSubscription->callback.function; + /* Subscription validation should not have allowed a NULL callback function. */ + IotMqtt_Assert( pSubscription->callback.function != NULL ); - /* Unlock the subscription list mutex. */ - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + /* Increment the subscription's reference count. */ + ( pSubscription->references )++; - /* Set the members of the callback parameter. */ - pCallbackParam->mqttConnection = pMqttConnection; - pCallbackParam->message.pTopicFilter = pSubscription->pTopicFilter; - pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; + /* Copy the necessary members of the subscription before releasing the + * subscription list mutex. */ + pParam1 = pSubscription->callback.param1; + callbackFunction = pSubscription->callback.function; - /* Invoke the subscription callback. */ - callbackFunction( pParam1, pCallbackParam ); + /* Unlock the subscription list mutex. */ + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - /* Lock the subscription list mutex to decrement the reference count. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + /* Set the members of the callback parameter. */ + pCallbackParam->mqttConnection = pMqttConnection; + pCallbackParam->message.pTopicFilter = pSubscription->pTopicFilter; + pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; - /* Decrement the reference count. It must still be positive. */ - ( pSubscription->references )--; - AwsIotMqtt_Assert( pSubscription->references >= 0 ); + /* Invoke the subscription callback. */ + callbackFunction( pParam1, pCallbackParam ); - /* Restart the search at the next subscription. */ - pStartPoint = pSubscription->link.pNext; + /* Lock the subscription list mutex to decrement the reference count. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - /* Remove this subscription if it has no references and the unsubscribed - * flag is set. */ - if( ( pSubscription->references == 0 ) && ( pSubscription->unsubscribed == true ) ) - { - IotListDouble_Remove( &( pSubscription->link ) ); - AwsIotMqtt_FreeSubscription( pSubscription ); + /* Decrement the reference count. It must still be positive. */ + ( pSubscription->references )--; + IotMqtt_Assert( pSubscription->references >= 0 ); + + /* Save the pointer to the next link in case this subscription is freed. */ + pNextLink = pCurrentLink->pNext; + + /* Remove this subscription if it has no references and the unsubscribed + * flag is set. */ + if( ( pSubscription->references == 0 ) && ( pSubscription->unsubscribed == true ) ) + { + IotListDouble_Remove( &( pSubscription->link ) ); + IotMqtt_FreeSubscription( pSubscription ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Move current link pointer. */ + pCurrentLink = pNextLink; } - } - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + } + else + { + _EMPTY_ELSE_MARKER; + } } /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_RemoveSubscriptionByPacket( _mqttConnection_t * const pMqttConnection, - uint16_t packetIdentifier, - long order ) +void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier, + int32_t order ) { const _packetMatchParams_t packetMatchParams = { @@ -406,18 +515,19 @@ void AwsIotMqttInternal_RemoveSubscriptionByPacket( _mqttConnection_t * const pM IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), _packetMatch, ( void * ) ( &packetMatchParams ), - AwsIotMqtt_FreeSubscription, + IotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); } /*-----------------------------------------------------------*/ -void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * const pMqttConnection, - const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount ) +void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ) { size_t i = 0; _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { 0 }; /* Prevent any other thread from modifying the subscription list while this @@ -431,17 +541,17 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; topicMatchParams.exactMatchOnly = true; - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); - if( pSubscription != NULL ) + if( pSubscriptionLink != NULL ) { + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + /* Reference count must not be negative. */ - AwsIotMqtt_Assert( pSubscription->references >= 0 ); + IotMqtt_Assert( pSubscription->references >= 0 ); /* Check the reference count. This subscription cannot be removed if * there are subscription callbacks using it. */ @@ -455,9 +565,13 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con { /* No subscription callbacks are using this subscription. Remove it. */ IotListDouble_Remove( &( pSubscription->link ) ); - AwsIotMqtt_FreeSubscription( pSubscription ); + IotMqtt_FreeSubscription( pSubscription ); } } + else + { + _EMPTY_ELSE_MARKER; + } } IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); @@ -465,14 +579,15 @@ void AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( _mqttConnection_t * con /*-----------------------------------------------------------*/ -bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, - const char * const pTopicFilter, - uint16_t topicFilterLength, - AwsIotMqttSubscription_t * pCurrentSubscription ) +bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, + const char * pTopicFilter, + uint16_t topicFilterLength, + IotMqttSubscription_t * pCurrentSubscription ) { bool status = false; _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { .pTopicName = pTopicFilter, @@ -485,27 +600,35 @@ bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); /* Search for a matching subscription. */ - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); /* Check if a matching subscription was found. */ - if( pSubscription != NULL ) + if( pSubscriptionLink != NULL ) { + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + /* Copy the matching subscription to the output parameter. */ if( pCurrentSubscription != NULL ) { pCurrentSubscription->pTopicFilter = pTopicFilter; pCurrentSubscription->topicFilterLength = topicFilterLength; - pCurrentSubscription->QoS = 0; + pCurrentSubscription->qos = IOT_MQTT_QOS_0; pCurrentSubscription->callback = pSubscription->callback; } + else + { + _EMPTY_ELSE_MARKER; + } status = true; } + else + { + _EMPTY_ELSE_MARKER; + } IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); @@ -516,6 +639,6 @@ bool AwsIotMqtt_IsSubscribed( AwsIotMqttConnection_t mqttConnection, /* If the MQTT library is being tested, include a file that allows access to * internal functions and variables. */ -#if AWS_IOT_MQTT_TEST == 1 - #include "aws_iot_test_access_mqtt_subscription.c" +#if IOT_MQTT_TEST == 1 + #include "iot_test_access_mqtt_subscription.c" #endif diff --git a/lib/source/mqtt/aws_iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c similarity index 52% rename from lib/source/mqtt/aws_iot_mqtt_validate.c rename to lib/source/mqtt/iot_mqtt_validate.c index 1408931293..5f6f621173 100644 --- a/lib/source/mqtt/aws_iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_mqtt_validate.c + * @file iot_mqtt_validate.c * @brief Implements functions that validate the structs of the MQTT library. */ @@ -29,19 +29,28 @@ #include IOT_CONFIG_FILE #endif +/* Error handling include. */ +#include "private/iot_error.h" + /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /*-----------------------------------------------------------*/ -bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkInterface ) +bool _IotMqtt_ValidateNetIf( const IotMqttNetIf_t * pNetworkInterface ) { + _IOT_FUNCTION_ENTRY( bool, true ); + /* Check for NULL. */ if( pNetworkInterface == NULL ) { IotLogError( "Network interface cannot be NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check for a non-NULL send function. */ @@ -49,7 +58,11 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI { IotLogError( "Network interface send function must be set." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* The MQTT 3.1.1 spec suggests disconnecting the client on errors. Check @@ -59,10 +72,14 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI IotLogWarn( "No disconnect function was provided. The MQTT connection will not be " "closed on errors, which is against MQTT 3.1.1 specification." ); } + else + { + _EMPTY_ELSE_MARKER; + } /* Check that the freePacket function pointer is set if any other serializer * override is set. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pNetworkInterface->freePacket == NULL ) { /* Check serializer overrides. */ @@ -78,7 +95,11 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI IotLogError( "Network interface must have a freePacket function " "if a serializer override isn't NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check deserializer overrides. */ @@ -92,24 +113,38 @@ bool AwsIotMqttInternal_ValidateNetIf( const AwsIotMqttNetIf_t * const pNetworkI IotLogError( "Network interface must have a freePacket function " "if a deserializer override isn't NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } } - #endif /* if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - return true; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo ) +bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { + _IOT_FUNCTION_ENTRY( bool, true ); + /* Check for NULL. */ if( pConnectInfo == NULL ) { IotLogError( "MQTT connection information cannot be NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check that a client identifier was set. */ @@ -117,7 +152,11 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p { IotLogError( "Client identifier must be set." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check for a zero-length client identifier. Zero-length client identifiers @@ -130,21 +169,43 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p { IotLogError( "A zero-length client identifier cannot be used with a clean session." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } /* Check that the number of persistent session subscriptions is valid. */ if( pConnectInfo->cleanSession == false ) { - if( ( pConnectInfo->pPreviousSubscriptions != NULL ) && - ( pConnectInfo->previousSubscriptionCount == 0 ) ) + if( pConnectInfo->pPreviousSubscriptions != NULL ) { - IotLogError( "Previous subscription count cannot be 0." ); + if( pConnectInfo->previousSubscriptionCount == 0 ) + { + IotLogError( "Previous subscription count cannot be 0." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } /* In MQTT 3.1.1, servers are not obligated to accept client identifiers longer * than 23 characters. */ @@ -154,6 +215,10 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p "the longest client identifier a server must accept.", pConnectInfo->clientIdentifierLength ); } + else + { + _EMPTY_ELSE_MARKER; + } /* Check for compatibility with the AWS IoT MQTT service limits. */ if( pConnectInfo->awsIotMqttMode == true ) @@ -164,7 +229,11 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p IotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check for compliance with AWS IoT keep-alive limits. */ @@ -188,63 +257,114 @@ bool AwsIotMqttInternal_ValidateConnect( const AwsIotMqttConnectInfo_t * const p _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } - return true; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, - const AwsIotMqttPublishInfo_t * const pPublishInfo ) +bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pPublishInfo ) { + _IOT_FUNCTION_ENTRY( bool, true ); + /* Check for NULL. */ if( pPublishInfo == NULL ) { IotLogError( "Publish information cannot be NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check topic name for NULL or zero-length. */ - if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) + if( pPublishInfo->pTopicName == NULL ) { IotLogError( "Publish topic name must be set." ); - - return false; + } + else + { + _EMPTY_ELSE_MARKER; } - /* Only allow NULL payloads with zero-length. */ - if( ( pPublishInfo->pPayload == NULL ) && ( pPublishInfo->payloadLength != 0 ) ) + if( pPublishInfo->topicNameLength == 0 ) { - IotLogError( "Nonzero payload length cannot have a NULL payload." ); + IotLogError( "Publish topic name length cannot be 0." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } - /* Check for a valid QoS. */ - if( ( pPublishInfo->QoS < 0 ) || ( pPublishInfo->QoS > 1 ) ) + /* Only allow NULL payloads with zero length. */ + if( pPublishInfo->pPayload == NULL ) { - IotLogError( "Publish QoS must be either 0 or 1." ); + if( pPublishInfo->payloadLength != 0 ) + { + IotLogError( "Nonzero payload length cannot have a NULL payload." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } - /* Check the retry parameters. */ - if( pPublishInfo->retryLimit < 0 ) + /* Check for a valid QoS. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - IotLogError( "Publish retry cannot be less than 0." ); + if( pPublishInfo->qos != IOT_MQTT_QOS_1 ) + { + IotLogError( "Publish QoS must be either 0 or 1." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } } - else if( pPublishInfo->retryLimit > 0 ) + else + { + _EMPTY_ELSE_MARKER; + } + + /* Check the retry parameters. */ + if( pPublishInfo->retryLimit > 0 ) { if( pPublishInfo->retryMs == 0 ) { IotLogError( "Publish retry time must be positive." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } /* Check for compatibility with AWS IoT MQTT server. */ @@ -255,7 +375,11 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, { IotLogError( "AWS IoT does not support retained publish messages." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check topic name length. */ @@ -264,17 +388,26 @@ bool AwsIotMqttInternal_ValidatePublish( bool awsIotMqttMode, IotLogError( "AWS IoT does not support topic names longer than %d bytes.", _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } - return true; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ) +bool _IotMqtt_ValidateReference( IotMqttReference_t reference ) { + _IOT_FUNCTION_ENTRY( bool, true ); _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; /* Check for NULL. */ @@ -282,41 +415,65 @@ bool AwsIotMqttInternal_ValidateReference( AwsIotMqttReference_t reference ) { IotLogError( "Reference cannot be NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check that reference is waitable. */ - if( ( pOperation->flags & AWS_IOT_MQTT_FLAG_WAITABLE ) != AWS_IOT_MQTT_FLAG_WAITABLE ) + if( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) { IotLogError( "Reference is not a waitable MQTT operation." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } - return true; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t operation, - bool awsIotMqttMode, - const AwsIotMqttSubscription_t * const pListStart, - size_t listSize ) +bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, + bool awsIotMqttMode, + const IotMqttSubscription_t * pListStart, + size_t listSize ) { + _IOT_FUNCTION_ENTRY( bool, true ); size_t i = 0; uint16_t j = 0; - const AwsIotMqttSubscription_t * pListElement = NULL; + const IotMqttSubscription_t * pListElement = NULL; /* Operation must be either subscribe or unsubscribe. */ - AwsIotMqtt_Assert( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) || - ( operation == AWS_IOT_MQTT_UNSUBSCRIBE ) ); + IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || + ( operation == IOT_MQTT_UNSUBSCRIBE ) ); /* Check for empty list. */ - if( ( listSize == 0 ) || ( pListStart == NULL ) ) + if( pListStart == NULL ) + { + IotLogError( "Subscription list pointer cannot be NULL." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( listSize == 0 ) { IotLogError( "Empty subscription list." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ @@ -325,41 +482,83 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper if( listSize > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) { IotLogError( "AWS IoT does not support more than %d topic filters per " - "subscription request.", - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); + "subscription request.", + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } for( i = 0; i < listSize; i++ ) { pListElement = &( pListStart[ i ] ); - /* Check for a valid QoS when subscribing. */ - if( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) && - ( ( pListElement->QoS < 0 ) || ( pListElement->QoS > 1 ) ) ) + /* Check for a valid QoS and callback function when subscribing. */ + if( operation == IOT_MQTT_SUBSCRIBE ) { - IotLogError( "Subscription QoS must be either 0 or 1." ); + if( pListElement->qos != IOT_MQTT_QOS_0 ) + { + if( pListElement->qos != IOT_MQTT_QOS_1 ) + { + IotLogError( "Subscription QoS must be either 0 or 1." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pListElement->callback.function == NULL ) + { + IotLogError( "Callback function must be set." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } /* Check subscription topic filter. */ - if( ( pListElement->pTopicFilter == NULL ) || ( pListElement->topicFilterLength == 0 ) ) + if( pListElement->pTopicFilter == NULL ) { - IotLogError( "Subscription topic filter must be set." ); + IotLogError( "Subscription topic filter cannot be NULL." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } - /* Check that a callback function is set when subscribing. */ - if( ( operation == AWS_IOT_MQTT_SUBSCRIBE ) && - ( pListElement->callback.function == NULL ) ) + if( pListElement->topicFilterLength == 0 ) { - IotLogError( "Callback function must be set." ); + IotLogError( "Subscription topic filter length cannot be 0." ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Check for compatibility with AWS IoT MQTT server. */ @@ -369,11 +568,19 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper if( pListElement->topicFilterLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) { IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } /* Check that the wildcards '+' and '#' are being used correctly. */ for( j = 0; j < pListElement->topicFilterLength; j++ ) @@ -384,24 +591,45 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper case '+': /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ - if( ( j > 0 ) && ( pListElement->pTopicFilter[ j - 1 ] != '/' ) ) + if( j > 0 ) { - IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - return false; + if( pListElement->pTopicFilter[ j - 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ - if( ( j < pListElement->topicFilterLength - 1 ) && - ( pListElement->pTopicFilter[ j + 1 ] != '/' ) ) + if( j < pListElement->topicFilterLength - 1 ) { - IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - return false; + if( pListElement->pTopicFilter[ j + 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } break; @@ -416,18 +644,32 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper pListElement->topicFilterLength, pListElement->pTopicFilter ); - return false; + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; } /* Unless '#' is standalone, it must be preceded by '/'. */ - if( ( pListElement->topicFilterLength > 1 ) && - ( pListElement->pTopicFilter[ j - 1 ] != '/' ) ) + if( pListElement->topicFilterLength > 1 ) { - IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - return false; + if( pListElement->pTopicFilter[ j - 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", + pListElement->topicFilterLength, + pListElement->pTopicFilter ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; } break; @@ -438,5 +680,7 @@ bool AwsIotMqttInternal_ValidateSubscriptionList( AwsIotMqttOperationType_t oper } } - return true; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index d5e8964cd5..9e8586eedf 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -95,7 +95,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, * @return #AWS_IOT_SHADOW_SUCCESS, #AWS_IOT_SHADOW_BAD_PARAMETER, * #AWS_IOT_SHADOW_NO_MEMORY, or #AWS_IOT_SHADOW_MQTT_ERROR. */ -static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnection, +static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, const char * const pThingName, size_t thingNameLength, @@ -111,7 +111,7 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or * @ref mqtt_function_timedunsubscribe. */ -static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t mqttConnection, +static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, _shadowSubscription_t * const pSubscription, _mqttOperationFunction_t mqttOperation ); @@ -126,7 +126,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t */ static void _callbackWrapperCommon( _shadowCallbackType_t type, const _shadowSubscription_t * const pSubscription, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /** * @brief Invoked when a document is received on the Shadow DELTA callback. @@ -135,7 +135,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). */ static void _deltaCallbackWrapper( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /** * @brief Invoked when a document is received on the Shadow UPDATED callback. @@ -144,7 +144,7 @@ static void _deltaCallbackWrapper( void * pArgument, * @param[in] pMessage The received UPDATED document (as an MQTT PUBLISH message). */ static void _updatedCallbackWrapper( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /*-----------------------------------------------------------*/ @@ -251,23 +251,19 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, AwsIotShadow_Assert( ( type == _SHADOW_GET ) || ( type == _SHADOW_UPDATE ) ); /* Check QoS. */ - if( ( pDocumentInfo->QoS < 0 ) || ( pDocumentInfo->QoS > 1 ) ) + if( pDocumentInfo->qos != IOT_MQTT_QOS_0 ) { - IotLogError( "QoS for Shadow %d must be 0 or 1.", - _pAwsIotShadowOperationNames[ type ] ); + if( pDocumentInfo->qos != IOT_MQTT_QOS_1 ) + { + IotLogError( "QoS for Shadow %d must be 0 or 1.", + _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + return AWS_IOT_SHADOW_BAD_PARAMETER; + } } /* Check the retry parameters. */ - if( pDocumentInfo->retryLimit < 0 ) - { - IotLogError( "Retry limit of Shadow %s cannot be less than 0.", - _pAwsIotShadowOperationNames[ type ] ); - - return AWS_IOT_SHADOW_BAD_PARAMETER; - } - else if( pDocumentInfo->retryLimit > 0 ) + if( pDocumentInfo->retryLimit > 0 ) { if( pDocumentInfo->retryMs == 0 ) { @@ -298,7 +294,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, ( pDocumentInfo->update.updateDocumentLength == 0 ) ) { IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" - " have length 0." ); + " have length 0." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -309,7 +305,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, /*-----------------------------------------------------------*/ -static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnection, +static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, const char * const pThingName, size_t thingNameLength, @@ -340,8 +336,8 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ - pSubscription = AwsIotShadowInternal_FindSubscription( pThingName, - thingNameLength ); + pSubscription = _AwsIotShadow_FindSubscription( pThingName, + thingNameLength ); if( pSubscription == NULL ) { @@ -376,13 +372,13 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec ( void ) _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, - AwsIotMqtt_TimedUnsubscribe ); + IotMqtt_TimedUnsubscribe ); ( void ) memset( &( pSubscription->callbacks[ type ] ), 0x00, sizeof( AwsIotShadowCallbackInfo_t ) ); /* Check if this subscription object can be removed. */ - AwsIotShadowInternal_RemoveSubscription( pSubscription, NULL ); + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); } } /* No existing callback. */ @@ -400,7 +396,7 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec status = _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, - AwsIotMqtt_TimedSubscribe ); + IotMqtt_TimedSubscribe ); } /* Do nothing; set return value to success. */ else @@ -417,14 +413,14 @@ static AwsIotShadowError_t _setCallbackCommon( AwsIotMqttConnection_t mqttConnec /*-----------------------------------------------------------*/ -static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t mqttConnection, +static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, _shadowSubscription_t * const pSubscription, _mqttOperationFunction_t mqttOperation ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; char * pTopicFilter = NULL; uint16_t operationTopicLength = 0; @@ -450,8 +446,8 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t }; /* MQTT operation may only be subscribe or unsubscribe. */ - AwsIotShadow_Assert( ( mqttOperation == AwsIotMqtt_TimedSubscribe ) || - ( mqttOperation == AwsIotMqtt_TimedUnsubscribe ) ); + AwsIotShadow_Assert( ( mqttOperation == IotMqtt_TimedSubscribe ) || + ( mqttOperation == IotMqtt_TimedUnsubscribe ) ); /* Use the subscription's topic buffer if available. */ if( pSubscription->pTopicBuffer != NULL ) @@ -461,11 +457,11 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t /* Generate the prefix portion of the Shadow callback topic filter. Both * callbacks share the same callback as the Shadow Update operation. */ - if( AwsIotShadowInternal_GenerateShadowTopic( _SHADOW_UPDATE, - pSubscription->pThingName, - pSubscription->thingNameLength, - &pTopicFilter, - &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) + if( _AwsIotShadow_GenerateShadowTopic( _SHADOW_UPDATE, + pSubscription->pThingName, + pSubscription->thingNameLength, + &pTopicFilter, + &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) { return AWS_IOT_SHADOW_NO_MEMORY; } @@ -476,12 +472,12 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t pCallbackSuffixLength[ type ] ); IotLogDebug( "%s subscription for %.*s", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", + mqttOperation == IotMqtt_TimedSubscribe ? "Adding" : "Removing", operationTopicLength + pCallbackSuffixLength[ type ], pTopicFilter ); /* Set the members of the MQTT subscription. */ - subscription.QoS = 1; + subscription.qos = IOT_MQTT_QOS_1; subscription.pTopicFilter = pTopicFilter; subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); subscription.callback.param1 = ( void * ) pSubscription; @@ -495,17 +491,17 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t _AwsIotShadowMqttTimeoutMs ); /* Check the result of the MQTT operation. */ - if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", pSubscription->thingNameLength, pSubscription->pThingName, _pAwsIotShadowCallbackNames[ type ], - AwsIotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( mqttStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( mqttStatus == AWS_IOT_MQTT_NO_MEMORY ) + if( mqttStatus == IOT_MQTT_NO_MEMORY ) { status = AWS_IOT_SHADOW_NO_MEMORY; } @@ -517,14 +513,14 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t else { IotLogDebug( "Successfully %s %.*s Shadow %s callback.", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", pSubscription->thingNameLength, pSubscription->pThingName, _pAwsIotShadowCallbackNames[ type ] ); } /* MQTT subscribe should check the subscription topic buffer. */ - if( mqttOperation == AwsIotMqtt_TimedSubscribe ) + if( mqttOperation == IotMqtt_TimedSubscribe ) { /* If the current subscription has no topic buffer, assign it the current * topic buffer. Otherwise, free the current topic buffer. */ @@ -545,7 +541,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( AwsIotMqttConnection_t static void _callbackWrapperCommon( _shadowCallbackType_t type, const _shadowSubscription_t * const pSubscription, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { AwsIotShadowCallbackParam_t callbackParam = { 0 }; @@ -567,7 +563,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, /*-----------------------------------------------------------*/ static void _deltaCallbackWrapper( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { _callbackWrapperCommon( _DELTA_CALLBACK, pArgument, pMessage ); } @@ -575,7 +571,7 @@ static void _deltaCallbackWrapper( void * pArgument, /*-----------------------------------------------------------*/ static void _updatedCallbackWrapper( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { _callbackWrapperCommon( _UPDATED_CALLBACK, pArgument, pMessage ); } @@ -623,14 +619,14 @@ void AwsIotShadow_Cleanup( void ) /* Remove and free all items in the Shadow pending operation list. */ IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), - AwsIotShadowInternal_DestroyOperation, + _AwsIotShadow_DestroyOperation, offsetof( _shadowOperation_t, link ) ); IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Remove and free all items in the Shadow subscription list. */ IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); IotListDouble_RemoveAll( &( _AwsIotShadowSubscriptions ), - AwsIotShadowInternal_DestroySubscription, + _AwsIotShadow_DestroySubscription, offsetof( _shadowSubscription_t, link ) ); IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); @@ -646,7 +642,7 @@ void AwsIotShadow_Cleanup( void ) /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -669,10 +665,10 @@ AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, } /* Allocate a new Shadow operation for DELETE. */ - if( AwsIotShadowInternal_CreateOperation( &pOperation, - _SHADOW_DELETE, - flags, - pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + if( _AwsIotShadow_CreateOperation( &pOperation, + _SHADOW_DELETE, + flags, + pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) { /* No memory for a new Shadow operation. */ return AWS_IOT_SHADOW_NO_MEMORY; @@ -693,11 +689,11 @@ AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, /* Process the Shadow operation. This subscribes to any required topics and * sends the MQTT message for the Shadow operation. */ - status = AwsIotShadowInternal_ProcessOperation( mqttConnection, - pThingName, - thingNameLength, - pOperation, - NULL ); + status = _AwsIotShadow_ProcessOperation( mqttConnection, + pThingName, + thingNameLength, + pOperation, + NULL ); /* If the Shadow operation failed, clear the now invalid reference. */ if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteRef != NULL ) ) @@ -710,7 +706,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( AwsIotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_TimedDelete( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -744,7 +740,7 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( AwsIotMqttConnection_t mqttConnect /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pGetInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * const pCallbackInfo, @@ -775,10 +771,10 @@ AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, } /* Allocate a new Shadow operation for GET. */ - if( AwsIotShadowInternal_CreateOperation( &pOperation, - _SHADOW_GET, - flags, - pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + if( _AwsIotShadow_CreateOperation( &pOperation, + _SHADOW_GET, + flags, + pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) { /* No memory for a new Shadow operation. */ return AWS_IOT_SHADOW_NO_MEMORY; @@ -802,11 +798,11 @@ AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, /* Process the Shadow operation. This subscribes to any required topics and * sends the MQTT message for the Shadow operation. */ - status = AwsIotShadowInternal_ProcessOperation( mqttConnection, - pGetInfo->pThingName, - pGetInfo->thingNameLength, - pOperation, - pGetInfo ); + status = _AwsIotShadow_ProcessOperation( mqttConnection, + pGetInfo->pThingName, + pGetInfo->thingNameLength, + pOperation, + pGetInfo ); /* If the Shadow operation failed, clear the now invalid reference. */ if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetRef != NULL ) ) @@ -819,7 +815,7 @@ AwsIotShadowError_t AwsIotShadow_Get( AwsIotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_TimedGet( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pGetInfo, uint32_t flags, uint64_t timeoutMs, @@ -856,7 +852,7 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( AwsIotMqttConnection_t mqttConnection /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pUpdateInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * const pCallbackInfo, @@ -914,10 +910,10 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, } /* Allocate a new Shadow operation for UPDATE. */ - if( AwsIotShadowInternal_CreateOperation( &pOperation, - _SHADOW_UPDATE, - flags, - pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + if( _AwsIotShadow_CreateOperation( &pOperation, + _SHADOW_UPDATE, + flags, + pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) { /* No memory for a new Shadow operation. */ return AWS_IOT_SHADOW_NO_MEMORY; @@ -935,7 +931,7 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, if( pOperation->update.pClientToken == NULL ) { IotLogError( "Failed to allocate memory for Shadow update client token." ); - AwsIotShadowInternal_DestroyOperation( pOperation ); + _AwsIotShadow_DestroyOperation( pOperation ); return AWS_IOT_SHADOW_NO_MEMORY; } @@ -956,11 +952,11 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, /* Process the Shadow operation. This subscribes to any required topics and * sends the MQTT message for the Shadow operation. */ - status = AwsIotShadowInternal_ProcessOperation( mqttConnection, - pUpdateInfo->pThingName, - pUpdateInfo->thingNameLength, - pOperation, - pUpdateInfo ); + status = _AwsIotShadow_ProcessOperation( mqttConnection, + pUpdateInfo->pThingName, + pUpdateInfo->thingNameLength, + pOperation, + pUpdateInfo ); /* If the Shadow operation failed, clear the now invalid reference. */ if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateRef != NULL ) ) @@ -973,7 +969,7 @@ AwsIotShadowError_t AwsIotShadow_Update( AwsIotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_TimedUpdate( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * const pUpdateInfo, uint32_t flags, uint64_t timeoutMs ) @@ -1059,9 +1055,9 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - AwsIotShadowInternal_DecrementReferences( pOperation, - pOperation->pSubscription->pTopicBuffer, - NULL ); + _AwsIotShadow_DecrementReferences( pOperation, + pOperation->pSubscription->pTopicBuffer, + NULL ); IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the output parameters for Shadow GET. */ @@ -1073,14 +1069,14 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, } /* Destroy the Shadow operation. */ - AwsIotShadowInternal_DestroyOperation( pOperation ); + _AwsIotShadow_DestroyOperation( pOperation ); return status; } /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, @@ -1098,7 +1094,7 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( AwsIotMqttConnection_t mqttCo /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags, diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index ef1bdd4614..de28da6c8b 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -77,7 +77,7 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _commonOperationCallback( _shadowOperationType_t type, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /** * @brief Invoked when a Shadow response is received for Shadow DELETE. @@ -86,7 +86,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _deleteCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /** * @brief Invoked when a Shadow response is received for a Shadow GET. @@ -95,7 +95,7 @@ static void _deleteCallback( void * pArgument, * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _getCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /** * @brief Process an incoming Shadow document received when a Shadow GET is @@ -110,7 +110,7 @@ static void _getCallback( void * pArgument, * only happens for a waitable `pOperation`. */ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOperation, - const AwsIotMqttPublishInfo_t * const pPublishInfo ); + const IotMqttPublishInfo_t * const pPublishInfo ); /** * @brief Invoked when a Shadow response is received for a Shadow UPDATE. @@ -119,7 +119,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOper * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _updateCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * const pMessage ); /*-----------------------------------------------------------*/ @@ -154,6 +154,10 @@ IotMutex_t _AwsIotShadowPendingOperationsMutex; static bool _shadowOperation_match( const IotLink_t * pOperationLink, void * pMatch ) { + /* Because this function is called from a container function, the given link + * must never be NULL. */ + AwsIotShadow_Assert( pOperationLink != NULL ); + _shadowOperation_t * const pOperation = IotLink_Container( _shadowOperation_t, pOperationLink, link ); @@ -212,9 +216,10 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, /*-----------------------------------------------------------*/ static void _commonOperationCallback( _shadowOperationType_t type, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { _shadowOperation_t * pOperation = NULL; + IotLink_t * pOperationLink = NULL; _shadowOperationStatus_t status = _UNKNOWN_STATUS; _operationMatchParams_t param = { 0 }; uint32_t flags = 0; @@ -230,10 +235,10 @@ static void _commonOperationCallback( _shadowOperationType_t type, } /* Parse the Thing Name from the MQTT topic name. */ - if( AwsIotShadowInternal_ParseThingName( pMessage->message.info.pTopicName, - pMessage->message.info.topicNameLength, - &( param.pThingName ), - &( param.thingNameLength ) ) != AWS_IOT_SHADOW_SUCCESS ) + if( _AwsIotShadow_ParseThingName( pMessage->message.info.pTopicName, + pMessage->message.info.topicNameLength, + &( param.pThingName ), + &( param.thingNameLength ) ) != AWS_IOT_SHADOW_SUCCESS ) { return; } @@ -242,15 +247,13 @@ static void _commonOperationCallback( _shadowOperationType_t type, IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Search for a matching pending operation. */ - pOperation = IotLink_Container( _shadowOperation_t, - IotListDouble_FindFirstMatch( &( _AwsIotShadowPendingOperations ), - NULL, - _shadowOperation_match, - ¶m ), - link ); + pOperationLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowPendingOperations ), + NULL, + _shadowOperation_match, + ¶m ); /* Find and remove the first Shadow operation of the given type. */ - if( pOperation == NULL ) + if( pOperationLink == NULL ) { /* Operation is not pending. It may have already been processed. Return * without doing anything */ @@ -263,6 +266,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, } else { + pOperation = IotLink_Container( _shadowOperation_t, pOperationLink, link ); + /* Remove a non-waitable operation from the pending operation list. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { @@ -280,8 +285,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, pMessage->message.info.pTopicName ); /* Parse the status from the topic name. */ - status = AwsIotShadowInternal_ParseShadowStatus( pMessage->message.info.pTopicName, - pMessage->message.info.topicNameLength ); + status = _AwsIotShadow_ParseShadowStatus( pMessage->message.info.pTopicName, + pMessage->message.info.topicNameLength ); switch( status ) { @@ -311,8 +316,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, pOperation->pSubscription->thingNameLength, pOperation->pSubscription->pThingName ); - pOperation->status = AwsIotShadowInternal_ParseErrorDocument( pMessage->message.info.pPayload, - pMessage->message.info.payloadLength ); + pOperation->status = _AwsIotShadow_ParseErrorDocument( pMessage->message.info.pPayload, + pMessage->message.info.payloadLength ); break; default: @@ -329,7 +334,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, flags = pOperation->flags; /* Notify of operation completion. */ - AwsIotShadowInternal_Notify( pOperation ); + _AwsIotShadow_Notify( pOperation ); /* For waitable operations, unlock the pending operation list mutex to signal * this function's completion. */ @@ -342,7 +347,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, /*-----------------------------------------------------------*/ static void _deleteCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; @@ -353,7 +358,7 @@ static void _deleteCallback( void * pArgument, /*-----------------------------------------------------------*/ static void _getCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; @@ -364,7 +369,7 @@ static void _getCallback( void * pArgument, /*-----------------------------------------------------------*/ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOperation, - const AwsIotMqttPublishInfo_t * const pPublishInfo ) + const IotMqttPublishInfo_t * const pPublishInfo ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; @@ -410,7 +415,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOper /*-----------------------------------------------------------*/ static void _updateCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * const pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; @@ -420,10 +425,10 @@ static void _updateCallback( void * pArgument, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** const pNewOperation, - _shadowOperationType_t type, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo ) +AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** const pNewOperation, + _shadowOperationType_t type, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * const pCallbackInfo ) { _shadowOperation_t * pOperation = NULL; @@ -451,7 +456,7 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) { IotLogError( "Failed to create semaphore for waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + _pAwsIotShadowOperationNames[ type ] ); AwsIotShadow_FreeOperation( pOperation ); @@ -481,7 +486,7 @@ AwsIotShadowError_t AwsIotShadowInternal_CreateOperation( _shadowOperation_t ** /*-----------------------------------------------------------*/ -void AwsIotShadowInternal_DestroyOperation( void * pData ) +void _AwsIotShadow_DestroyOperation( void * pData ) { _shadowOperation_t * pOperation = ( _shadowOperation_t * ) pData; @@ -513,11 +518,11 @@ void AwsIotShadowInternal_DestroyOperation( void * pData ) /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadowInternal_GenerateShadowTopic( _shadowOperationType_t type, - const char * const pThingName, - size_t thingNameLength, - char ** const pTopicBuffer, - uint16_t * const pOperationTopicLength ) +AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t type, + const char * const pThingName, + size_t thingNameLength, + char ** const pTopicBuffer, + uint16_t * const pOperationTopicLength ) { uint16_t bufferLength = 0; uint16_t operationTopicLength = 0; @@ -597,19 +602,19 @@ AwsIotShadowError_t AwsIotShadowInternal_GenerateShadowTopic( _shadowOperationTy /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_t mqttConnection, - const char * const pThingName, - size_t thingNameLength, - _shadowOperation_t * const pOperation, - const AwsIotShadowDocumentInfo_t * const pDocumentInfo ) +AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength, + _shadowOperation_t * const pOperation, + const AwsIotShadowDocumentInfo_t * const pDocumentInfo ) { _shadowSubscription_t * pSubscription = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotMqttError_t publishStatus = AWS_IOT_MQTT_STATUS_PENDING; + IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; char * pTopicBuffer = NULL; uint16_t operationTopicLength = 0; bool freeTopicBuffer = true; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Lookup table for Shadow operation callbacks. */ const _mqttCallbackFunction_t shadowCallbacks[ _SHADOW_OPERATION_COUNT ] = @@ -628,15 +633,15 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ pOperation->mqttConnection = mqttConnection; /* Generate the operation topic buffer. */ - if( AwsIotShadowInternal_GenerateShadowTopic( pOperation->type, - pThingName, - thingNameLength, - &pTopicBuffer, - &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) + if( _AwsIotShadow_GenerateShadowTopic( pOperation->type, + pThingName, + thingNameLength, + &pTopicBuffer, + &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) { IotLogError( "No memory for Shadow operation topic buffer." ); - AwsIotShadowInternal_DestroyOperation( pOperation ); + _AwsIotShadow_DestroyOperation( pOperation ); return AWS_IOT_SHADOW_NO_MEMORY; } @@ -646,8 +651,8 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ - pSubscription = AwsIotShadowInternal_FindSubscription( pThingName, - thingNameLength ); + pSubscription = _AwsIotShadow_FindSubscription( pThingName, + thingNameLength ); if( pSubscription == NULL ) { @@ -680,17 +685,17 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ /* Increment the reference count for this Shadow operation's * subscriptions. */ - status = AwsIotShadowInternal_IncrementReferences( pOperation, - pTopicBuffer, - operationTopicLength, - shadowCallbacks[ pOperation->type ] ); + status = _AwsIotShadow_IncrementReferences( pOperation, + pTopicBuffer, + operationTopicLength, + shadowCallbacks[ pOperation->type ] ); if( status != AWS_IOT_SHADOW_STATUS_PENDING ) { /* Failed to add subscriptions for a Shadow operation. The reference * count was not incremented. Check if this subscription should be * deleted. */ - AwsIotShadowInternal_RemoveSubscription( pSubscription, NULL ); + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); } } @@ -712,14 +717,14 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ /* Set the document info if this operation is not a Shadow DELETE. */ if( pOperation->type != _SHADOW_DELETE ) { - publishInfo.QoS = pDocumentInfo->QoS; + publishInfo.qos = pDocumentInfo->qos; publishInfo.retryLimit = pDocumentInfo->retryLimit; publishInfo.retryMs = pDocumentInfo->retryMs; IotLogDebug( "Shadow %s message will be published at QoS %d with " "retryLimit %d and retryMs %llu.", _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.QoS, + publishInfo.qos, publishInfo.retryLimit, publishInfo.retryMs ); } @@ -746,22 +751,22 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Publish to the Shadow topic name. */ - publishStatus = AwsIotMqtt_TimedPublish( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotShadowMqttTimeoutMs ); + publishStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotShadowMqttTimeoutMs ); /* Check for errors from the MQTT publish. */ - if( publishStatus != AWS_IOT_MQTT_SUCCESS ) + if( publishStatus != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", _pAwsIotShadowOperationNames[ pOperation->type ], thingNameLength, pThingName, - AwsIotMqtt_strerror( publishStatus ) ); + IotMqtt_strerror( publishStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( publishStatus == AWS_IOT_MQTT_NO_MEMORY ) + if( publishStatus == IOT_MQTT_NO_MEMORY ) { status = AWS_IOT_SHADOW_NO_MEMORY; } @@ -775,9 +780,9 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) { IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - AwsIotShadowInternal_DecrementReferences( pOperation, - pTopicBuffer, - NULL ); + _AwsIotShadow_DecrementReferences( pOperation, + pTopicBuffer, + NULL ); IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); } @@ -803,7 +808,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ /* Destroy the Shadow operation on failure. */ if( status != AWS_IOT_SHADOW_STATUS_PENDING ) { - AwsIotShadowInternal_DestroyOperation( pOperation ); + _AwsIotShadow_DestroyOperation( pOperation ); } return status; @@ -811,7 +816,7 @@ AwsIotShadowError_t AwsIotShadowInternal_ProcessOperation( AwsIotMqttConnection_ /*-----------------------------------------------------------*/ -void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ) +void _AwsIotShadow_Notify( _shadowOperation_t * const pOperation ) { AwsIotShadowCallbackParam_t callbackParam = { 0 }; _shadowSubscription_t * pSubscription = pOperation->pSubscription, @@ -828,9 +833,9 @@ void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ) /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - AwsIotShadowInternal_DecrementReferences( pOperation, - pSubscription->pTopicBuffer, - &pRemovedSubscription ); + _AwsIotShadow_DecrementReferences( pOperation, + pSubscription->pTopicBuffer, + &pRemovedSubscription ); IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the subscription pointer used for the user callback based on whether @@ -866,10 +871,10 @@ void AwsIotShadowInternal_Notify( _shadowOperation_t * const pOperation ) /* Destroy a removed subscription. */ if( pRemovedSubscription != NULL ) { - AwsIotShadowInternal_DestroySubscription( pRemovedSubscription ); + _AwsIotShadow_DestroySubscription( pRemovedSubscription ); } - AwsIotShadowInternal_DestroyOperation( pOperation ); + _AwsIotShadow_DestroyOperation( pOperation ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index 4df4c61156..9a12b42cb7 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -135,8 +135,8 @@ static AwsIotShadowError_t _codeToShadowStatus( unsigned long code ) /*-----------------------------------------------------------*/ -_shadowOperationStatus_t AwsIotShadowInternal_ParseShadowStatus( const char * const pTopicName, - size_t topicNameLength ) +_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * const pTopicName, + size_t topicNameLength ) { const char * pSuffixStart = NULL; @@ -186,8 +186,8 @@ _shadowOperationStatus_t AwsIotShadowInternal_ParseShadowStatus( const char * co /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const pErrorDocument, - size_t errorDocumentLength ) +AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * const pErrorDocument, + size_t errorDocumentLength ) { const char * pCode = NULL, * pMessage = NULL; size_t codeLength = 0, messageLength = 0; @@ -246,10 +246,10 @@ AwsIotShadowError_t AwsIotShadowInternal_ParseErrorDocument( const char * const /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadowInternal_ParseThingName( const char * const pTopicName, - uint16_t topicNameLength, - const char ** const pThingName, - size_t * const pThingNameLength ) +AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * const pTopicName, + uint16_t topicNameLength, + const char ** const pThingName, + size_t * const pThingNameLength ) { const char * pThingNameStart = NULL; size_t thingNameLength = 0; diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 05787fdee3..afedac2c51 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -77,7 +77,7 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, * @return #AWS_IOT_SHADOW_STATUS_PENDING on success; otherwise * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. */ -static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t mqttConnection, +static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mqttConnection, const char * const pTopicFilter, uint16_t topicFilterLength, _mqttCallbackFunction_t callback, @@ -101,6 +101,11 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, void * pMatch ) { bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + AwsIotShadow_Assert( pSubscriptionLink != NULL ); + const _shadowSubscription_t * const pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); @@ -119,24 +124,24 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ -static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t mqttConnection, +static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mqttConnection, const char * const pTopicFilter, uint16_t topicFilterLength, _mqttCallbackFunction_t callback, _mqttOperationFunction_t mqttOperation ) { - AwsIotMqttError_t mqttStatus = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; /* The MQTT operation function pointer must be either Subscribe or Unsubscribe. */ - AwsIotShadow_Assert( ( mqttOperation == AwsIotMqtt_TimedSubscribe ) || - ( mqttOperation == AwsIotMqtt_TimedUnsubscribe ) ); + AwsIotShadow_Assert( ( mqttOperation == IotMqtt_TimedSubscribe ) || + ( mqttOperation == IotMqtt_TimedUnsubscribe ) ); /* Per the AWS IoT documentation, Shadow topic subscriptions are QoS 1. */ - subscription.QoS = 1; + subscription.qos = IOT_MQTT_QOS_1; IotLogDebug( "%s Shadow subscription for %.*s", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "Adding" : "Removing", + mqttOperation == IotMqtt_TimedSubscribe ? "Adding" : "Removing", topicFilterLength, pTopicFilter ); @@ -154,16 +159,16 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t _AwsIotShadowMqttTimeoutMs ); /* Check the result of the MQTT operation. */ - if( mqttStatus != AWS_IOT_MQTT_SUCCESS ) + if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to %s %.*s, error %s.", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", topicFilterLength, pTopicFilter, - AwsIotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( mqttStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( mqttStatus == AWS_IOT_MQTT_NO_MEMORY ) + if( mqttStatus == IOT_MQTT_NO_MEMORY ) { return AWS_IOT_SHADOW_NO_MEMORY; } @@ -173,7 +178,7 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t else { IotLogDebug( "Successfully %s %.*s", - mqttOperation == AwsIotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", topicFilterLength, pTopicFilter ); } @@ -183,10 +188,11 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( AwsIotMqttConnection_t /*-----------------------------------------------------------*/ -_shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * const pThingName, - size_t thingNameLength ) +_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * const pThingName, + size_t thingNameLength ) { _shadowSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _thingName_t thingName = { .pThingName = pThingName, @@ -194,15 +200,13 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons }; /* Search the list for an existing subscription for Thing Name. */ - pSubscription = IotLink_Container( _shadowSubscription_t, - IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), - NULL, - _shadowSubscription_match, - &thingName ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), + NULL, + _shadowSubscription_match, + &thingName ); /* Check if a subscription was found. */ - if( pSubscription == NULL ) + if( pSubscriptionLink == NULL ) { /* No subscription found. Allocate a new subscription. */ pSubscription = AwsIotShadow_MallocSubscription( sizeof( _shadowSubscription_t ) + thingNameLength ); @@ -236,6 +240,8 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons IotLogDebug( "Found existing Shadow subscriptions object for %.*s.", thingNameLength, pThingName ); + + pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); } return pSubscription; @@ -243,8 +249,8 @@ _shadowSubscription_t * AwsIotShadowInternal_FindSubscription( const char * cons /*-----------------------------------------------------------*/ -void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSubscription, - _shadowSubscription_t ** const pRemovedSubscription ) +void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * const pSubscription, + _shadowSubscription_t ** const pRemovedSubscription ) { int i = 0; @@ -258,9 +264,9 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub { if( pSubscription->references[ i ] > 0 ) { - IotLogDebug( "Reference count %d for %.*s subscription object. " + IotLogDebug( "Reference count %ld for %.*s subscription object. " "Subscription cannot be removed yet.", - pSubscription->references[ i ], + ( long int ) pSubscription->references[ i ], pSubscription->thingNameLength, pSubscription->pThingName ); @@ -308,13 +314,13 @@ void AwsIotShadowInternal_RemoveSubscription( _shadowSubscription_t * const pSub } else { - AwsIotShadowInternal_DestroySubscription( pSubscription ); + _AwsIotShadow_DestroySubscription( pSubscription ); } } /*-----------------------------------------------------------*/ -void AwsIotShadowInternal_DestroySubscription( void * pData ) +void _AwsIotShadow_DestroySubscription( void * pData ) { _shadowSubscription_t * pSubscription = ( _shadowSubscription_t * ) pData; @@ -328,10 +334,10 @@ void AwsIotShadowInternal_DestroySubscription( void * pData ) /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, - uint16_t operationTopicLength, - _mqttCallbackFunction_t callback ) +AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + uint16_t operationTopicLength, + _mqttCallbackFunction_t callback ) { uint16_t topicFilterLength = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -364,17 +370,17 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); /* There should not be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == false ); + AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == false ); /* Add a subscription to the Shadow "accepted" topic. */ status = _modifyOperationSubscriptions( pOperation->mqttConnection, pTopicBuffer, topicFilterLength, callback, - AwsIotMqtt_TimedSubscribe ); + IotMqtt_TimedSubscribe ); if( status != AWS_IOT_SHADOW_STATUS_PENDING ) { @@ -388,17 +394,17 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_REJECTED_SUFFIX_LENGTH ); /* There should not be an active subscription for the rejected topic. */ - AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == false ); + AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == false ); /* Add a subscription to the Shadow "rejected" topic. */ status = _modifyOperationSubscriptions( pOperation->mqttConnection, pTopicBuffer, topicFilterLength, callback, - AwsIotMqtt_TimedSubscribe ); + IotMqtt_TimedSubscribe ); if( status != AWS_IOT_SHADOW_STATUS_PENDING ) { @@ -413,7 +419,7 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t pTopicBuffer, topicFilterLength, callback, - AwsIotMqtt_TimedUnsubscribe ); + IotMqtt_TimedUnsubscribe ); return status; } @@ -447,9 +453,9 @@ AwsIotShadowError_t AwsIotShadowInternal_IncrementReferences( _shadowOperation_t /*-----------------------------------------------------------*/ -void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, - _shadowSubscription_t ** const pRemovedSubscription ) +void _AwsIotShadow_DecrementReferences( _shadowOperation_t * const pOperation, + char * const pTopicBuffer, + _shadowSubscription_t ** const pRemovedSubscription ) { uint16_t topicFilterLength = 0; const _shadowOperationType_t type = pOperation->type; @@ -486,11 +492,11 @@ void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOpera /* Generate the prefix of the Shadow topic. This function will not * fail when given a buffer. */ - ( void ) AwsIotShadowInternal_GenerateShadowTopic( ( _shadowOperationType_t ) type, - pSubscription->pThingName, - pSubscription->thingNameLength, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); + ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) type, + pSubscription->pThingName, + pSubscription->thingNameLength, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, @@ -499,17 +505,17 @@ void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOpera topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); /* There should be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == true ); + AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == true ); /* Remove the subscription from the Shadow "accepted" topic. */ ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, pTopicBuffer, topicFilterLength, NULL, - AwsIotMqtt_TimedUnsubscribe ); + IotMqtt_TimedUnsubscribe ); /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, @@ -518,27 +524,27 @@ void AwsIotShadowInternal_DecrementReferences( _shadowOperation_t * const pOpera topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); /* There should be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( AwsIotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == true ); + AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == true ); /* Remove the subscription from the Shadow "rejected" topic. */ ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, pTopicBuffer, topicFilterLength, NULL, - AwsIotMqtt_TimedUnsubscribe ); + IotMqtt_TimedUnsubscribe ); } /* Check if this subscription should be deleted. */ - AwsIotShadowInternal_RemoveSubscription( pSubscription, - pRemovedSubscription ); + _AwsIotShadow_RemoveSubscription( pSubscription, + pRemovedSubscription ); } /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnection_t mqttConnection, +AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, const char * const pThingName, size_t thingNameLength, uint32_t flags ) @@ -548,6 +554,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec AwsIotShadowError_t removeAcceptedStatus = AWS_IOT_SHADOW_STATUS_PENDING, removeRejectedStatus = AWS_IOT_SHADOW_STATUS_PENDING; _shadowSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _thingName_t thingName = { .pThingName = pThingName, @@ -561,21 +568,21 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); /* Search the list for an existing subscription for Thing Name. */ - pSubscription = IotLink_Container( _shadowSubscription_t, - IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), - NULL, - _shadowSubscription_match, - &thingName ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), + NULL, + _shadowSubscription_match, + &thingName ); /* Unsubscribe from operation subscriptions if found. */ - if( pSubscription != NULL ) + if( pSubscriptionLink != NULL ) { IotLogDebug( "Found subscription object for %.*s. Checking for persistent " "subscriptions to remove.", thingNameLength, pThingName ); + pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); + for( i = 0; i < _SHADOW_OPERATION_COUNT; i++ ) { if( ( flags & ( 0x1UL << i ) ) != 0 ) @@ -592,11 +599,11 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec { /* Generate the prefix of the Shadow topic. This function will not * fail when given a buffer. */ - ( void ) AwsIotShadowInternal_GenerateShadowTopic( ( _shadowOperationType_t ) i, - pThingName, - thingNameLength, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); + ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) i, + pThingName, + thingNameLength, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); /* Remove the "accepted" topic. */ ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, @@ -608,7 +615,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec pSubscription->pTopicBuffer, topicFilterLength, NULL, - AwsIotMqtt_TimedUnsubscribe ); + IotMqtt_TimedUnsubscribe ); if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) { @@ -626,7 +633,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( AwsIotMqttConnec pSubscription->pTopicBuffer, topicFilterLength, NULL, - AwsIotMqtt_TimedUnsubscribe ); + IotMqtt_TimedUnsubscribe ); if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) { diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 064ef854c5..1407f2e0cd 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -74,8 +74,6 @@ typedef struct IotNetworkConnectionOpenssl IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ void * pReceiveContext; /**< @brief The context for the receive callback. */ - IotNetworkCloseCallback_t closeCallback; /**< @brief Network close callback, if any. */ - void * pCloseContext; /**< @brief The context for the close callback. */ } IotNetworkConnectionOpenssl_t; /** @@ -228,14 +226,6 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ); -/** - * @brief An implementation of #IotNetworkInterface_t::setCloseCallback for - * POSIX systems with OpenSSL. - */ -IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( void * pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ); - /** * @brief An implementation of #IotNetworkInterface_t::send for POSIX systems * with OpenSSL. @@ -256,7 +246,7 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, * @brief An implementation of #IotNetworkInterface_t::close for POSIX systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Close( int32_t reason, void * pConnection ); +IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::destroy for POSIX systems diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 7103f45f28..353c29acb5 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -104,7 +104,6 @@ const IotNetworkInterface_t _IotNetworkOpenssl = { .create = IotNetworkOpenssl_Create, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, - .setCloseCallback = IotNetworkOpenssl_SetCloseCallback, .send = IotNetworkOpenssl_Send, .receive = IotNetworkOpenssl_Receive, .close = IotNetworkOpenssl_Close, @@ -713,46 +712,57 @@ static IotNetworkError_t _cancelReceiveThread( IotNetworkConnectionOpenssl_t * c int posixError = 0; IotNetworkError_t status = IOT_NETWORK_SUCCESS; - /* Check if a network receive thread was created. */ - if( pNetworkConnection->receiveThreadStatus != _NONE ) + /* Do nothing if this thread is attempting to cancel itself. */ + if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) { - /* Send a cancellation request to the receive thread if active. */ - if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) + if( pNetworkConnection->receiveThread == pthread_self() ) { - posixError = pthread_cancel( pNetworkConnection->receiveThread ); + status = IOT_NETWORK_FAILURE; + } + } - if( ( posixError != 0 ) && ( posixError != ESRCH ) ) + if( status == IOT_NETWORK_SUCCESS ) + { + if( pNetworkConnection->receiveThreadStatus != _NONE ) + { + /* Send a cancellation request to the receive thread if active. */ + if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) { - IotLogWarn( "Failed to send cancellation request to socket %d receive " - "thread. errno=%d.", - pNetworkConnection->socket, - posixError ); + posixError = pthread_cancel( pNetworkConnection->receiveThread ); - status = IOT_NETWORK_SYSTEM_ERROR; + if( ( posixError != 0 ) && ( posixError != ESRCH ) ) + { + IotLogWarn( "Failed to send cancellation request to socket %d receive " + "thread. errno=%d.", + pNetworkConnection->socket, + posixError ); + + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pNetworkConnection->receiveThreadStatus = _TERMINATED; + } } - else + + if( pNetworkConnection->receiveThreadStatus == _TERMINATED ) { - pNetworkConnection->receiveThreadStatus = _TERMINATED; - } - } + /* Join the receive thread. */ + posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); - if( pNetworkConnection->receiveThreadStatus == _TERMINATED ) - { - /* Join the receive thread. */ - posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + if( posixError != 0 ) + { + IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d.", + pNetworkConnection->socket, + posixError ); + } - if( posixError != 0 ) - { - IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d.", - pNetworkConnection->socket, - posixError ); + /* Clear data about the receive thread and callback. */ + pNetworkConnection->receiveThreadStatus = _NONE; + ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); + pNetworkConnection->receiveCallback = NULL; + pNetworkConnection->pReceiveContext = NULL; } - - /* Clear data about the receive thread and callback. */ - pNetworkConnection->receiveThreadStatus = _NONE; - ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); - pNetworkConnection->receiveCallback = NULL; - pNetworkConnection->pReceiveContext = NULL; } } @@ -857,7 +867,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, ( void ) memset( pNetworkConnection, 0x00, sizeof( IotNetworkConnectionOpenssl_t ) ); /* Create the network connection mutex. */ - if( IotMutex_Create( &( pNetworkConnection->mutex ), false ) == false ) + if( IotMutex_Create( &( pNetworkConnection->mutex ), true ) == false ) { IotLogError( "Failed to create network connection mutex." ); @@ -955,27 +965,6 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( void * pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ) -{ - /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - - /* Lock the mutex to prevent a concurrent call to close. */ - IotMutex_Lock( &( pNetworkConnection->mutex ) ); - - /* Replace the existing close callback. */ - pNetworkConnection->closeCallback = closeCallback; - pNetworkConnection->pCloseContext = pContext; - - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - size_t IotNetworkOpenssl_Send( void * pConnection, const uint8_t * pMessage, size_t messageLength ) @@ -1154,12 +1143,8 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Close( int32_t reason, - void * pConnection ) +IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) { - IotNetworkCloseCallback_t closeCallback = NULL; - void * pCloseContext = NULL; - /* Cast function parameter to correct type. */ IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; @@ -1197,19 +1182,9 @@ IotNetworkError_t IotNetworkOpenssl_Close( int32_t reason, pNetworkConnection->socket ); } - /* Copy close callback and context to invoke after mutex unlock. */ - closeCallback = pNetworkConnection->closeCallback; - pCloseContext = pNetworkConnection->pCloseContext; - /* Unlock the connection mutex. */ IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - /* Invoke close callback. */ - if( closeCallback != NULL ) - { - closeCallback( reason, pCloseContext, pNetworkConnection ); - } - return IOT_NETWORK_SUCCESS; } diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index 231aaa5878..1970241974 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -29,15 +29,15 @@ echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem # Build tests and demos against AWS IoT with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run common tests (no network required). ./bin/iot_tests_common # Run MQTT tests and demo against AWS IoT. -./bin/aws_iot_tests_mqtt -./bin/aws_iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" +./bin/iot_tests_mqtt +./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" # Run Shadow tests and demo. ./bin/aws_iot_tests_shadow @@ -45,12 +45,12 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run common tests in static memory mode (no network required). ./bin/iot_tests_common # Run MQTT and Shadow tests in static memory mode. -./bin/aws_iot_tests_mqtt +./bin/iot_tests_mqtt ./bin/aws_iot_tests_shadow diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh index 87e6a01e19..293bdeb4a6 100644 --- a/scripts/build_check_pr.sh +++ b/scripts/build_check_pr.sh @@ -15,27 +15,27 @@ cd build rm -rf * # Build tests and demos against Mosquitto broker with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" make # Run common tests (no network required). ./bin/iot_tests_common # Run MQTT tests and demos against Mosquitto. -./bin/aws_iot_tests_mqtt -./bin/aws_iot_demo_mqtt -h test.mosquitto.org -p 1883 -n +./bin/iot_tests_mqtt +./bin/iot_demo_mqtt -h test.mosquitto.org -p 1883 -n # Run the Shadow tests that do not require the network. ./bin/aws_iot_tests_shadow -n # Rebuild in static memory mode. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_MQTT_MOSQUITTO=1 -DIOT_STATIC_MEMORY_ONLY=1 $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_STATIC_MEMORY_ONLY=1 $COMPILER_OPTIONS" make # Run common tests in static memory mode (no network required). ./bin/iot_tests_common # Run MQTT tests and no-network Shadow tests in static memory mode. -./bin/aws_iot_tests_mqtt +./bin/iot_tests_mqtt ./bin/aws_iot_tests_shadow -n diff --git a/scripts/coverage.sh b/scripts/coverage.sh index ac5c9b6390..de094946c0 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -14,20 +14,22 @@ cd build rm -rf * # Build tests and demos against AWS IoT with gcov. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DAWS_IOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DAWS_IOT_TEST_PORT=443 -DAWS_IOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DAWS_IOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DAWS_IOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DAWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" --coverage" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" make # Run MQTT tests and demo against AWS IoT with code coverage. -./bin/aws_iot_tests_mqtt -./bin/aws_iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" +./bin/iot_tests_mqtt 2&> /dev/null +./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" 2&> /dev/null # Run Shadow tests and demo with code coverage. -./bin/aws_iot_tests_shadow -./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" +./bin/aws_iot_tests_shadow 2&> /dev/null +./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" 2&> /dev/null -# Generate code coverage results, excluding Unity test framework. +# Generate code coverage results, excluding Unity test framework, demo files, and tests files. lcov --directory . --capture --output-file coverage.info lcov --remove coverage.info '*unity*' --output-file coverage.info +lcov --remove coverage.info '*demo*' --output-file coverage.info +lcov --remove coverage.info '*tests*' --output-file coverage.info # Submit the code coverage results. cd .. diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 9ca8aa774f..6c457a5b56 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -1,7 +1,8 @@ # Common tests executable. add_executable( iot_tests_common iot_tests_common.c - unit/iot_tests_linear_containers.c ) + unit/iot_tests_linear_containers.c + unit/iot_tests_taskpool.c ) # Common tests library dependencies. target_link_libraries( iot_tests_common unity iotcommon ) diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index ce857d48e9..a7a5bf2fd6 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -51,12 +51,12 @@ static void _signalHandler( int signum ) if( signum == SIGSEGV ) { printf( "\nSegmentation fault.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } else if( signum == SIGABRT ) { printf( "\nAssertion failed.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } } @@ -77,18 +77,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return -1; + return EXIT_FAILURE; } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return -1; - } - - /* Initialize the common libraries before running the tests. */ - if( IotCommon_Init() == false ) - { - return -1; + return EXIT_FAILURE; } /* Unity setup. */ @@ -100,13 +94,15 @@ int main( int argc, /* Run linear containers tests. */ RUN_TEST_GROUP( Common_Unit_Linear_Containers ); + RUN_TEST_GROUP( Common_Unit_Task_Pool ); - /* Clean up common libraries. */ - IotCommon_Cleanup(); + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + return EXIT_FAILURE; + } - /* Return the number of test failures. This will cause a non-zero exit code - * if any test fails. */ - return UNITY_END(); + return EXIT_SUCCESS; } /*-----------------------------------------------------------*/ diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c new file mode 100644 index 0000000000..0f04d4e65b --- /dev/null +++ b/tests/common/unit/iot_tests_taskpool.c @@ -0,0 +1,1426 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_taskpool.c + * @brief Tests for task pool. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Platform layer includes. */ +#include "platform/iot_threads.h" + +/* MQTT internal include. */ +#include "private/iot_taskpool_internal.h" + +/* Task pool include. */ +#include "iot_taskpool.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief A simple user context to prove all callbacks are called. + */ +typedef struct JobUserContext +{ + IotMutex_t lock; /**< @brief Protection from concurrent updates. */ + uint32_t counter; /**< @brief A counter to keep track of callback invokations. */ +} JobUserContext_t; + +/** + * @brief A simple user context to prove the taskpool grows as expected. + */ +typedef struct JobBlockingUserContext +{ + IotSemaphore_t signal; /**< @brief A synch object to signal. */ + IotSemaphore_t block; /**< @brief A synch object to wait on. */ +} JobBlockingUserContext_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for task pool tests. + */ +TEST_GROUP( Common_Unit_Task_Pool ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for task pool tests. + */ +TEST_SETUP( Common_Unit_Task_Pool ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for task pool tests. + */ +TEST_TEAR_DOWN( Common_Unit_Task_Pool ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for task pool. + */ +TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) +{ + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroy ); + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateJobError ); + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasksError ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_Grow ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllRecyclableThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReScheduleDeferred ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_CancelTasks ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Number of iterations for each test loop. + */ +#ifndef _TASKPOOL_TEST_ITERATIONS + #define _TASKPOOL_TEST_ITERATIONS ( 200 ) +#endif + +/** + * @brief Define the stress job max duration time (emulated duration). + */ +#ifndef _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX + #define _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX ( 55 ) +#endif + +/** + * @brief A global delay to wait for threads to exit or such... + */ +static struct itimerspec _TEST_DELAY_50MS = +{ + .it_value.tv_sec = 0, + .it_value.tv_nsec = ( 50000000L ), /* 50ms */ + .it_interval = { 0 } +}; + +/* ---------------------------------------------------------- */ + +/** + * @brief A function that emulates some work in the task pool execution by sleeping. + */ +static void EmulateWork() +{ + int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX ) ); + + TEST_ASSERT_TRUE( duration_in_nsec <= 999999999 ); + + struct timespec delay = + { + .tv_sec = 0, + .tv_nsec = duration_in_nsec + }; + + int error = clock_nanosleep( CLOCK_MONOTONIC, 0, &delay, NULL ); + + TEST_ASSERT_TRUE( error == 0 ); +} + +/** + * @brief A function that emulates some work in the task pool execution by sleeping. + */ +static void EmulateWorkLong() +{ + int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX ) ); + + TEST_ASSERT_TRUE( duration_in_nsec <= 999999999 ); + + /* Emulate at least 10 seconds worth of work. */ + struct timespec delay = + { + .tv_sec = 2, + .tv_nsec = duration_in_nsec + }; + + int error = clock_nanosleep( CLOCK_MONOTONIC, 0, &delay, NULL ); + + TEST_ASSERT_TRUE( error == 0 ); +} + +/** + * @brief A callback that recycles its job. + */ +static void ExecutionWithDestroyCb( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * context ) +{ + JobUserContext_t * pUserContext; + IotTaskPoolJobStatus_t status; + + TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + + TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + + EmulateWork(); + + pUserContext = ( JobUserContext_t * ) context; + + IotMutex_Lock( &pUserContext->lock ); + pUserContext->counter++; + IotMutex_Unlock( &pUserContext->lock ); + + TEST_ASSERT( IotTaskPool_DestroyJob( pTaskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); +} + +/** + * @brief A callback that does not recycle its job. + */ +static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * context ) +{ + JobUserContext_t * pUserContext; + IotTaskPoolJobStatus_t status; + + TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + + TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + + EmulateWork(); + + pUserContext = ( JobUserContext_t * ) context; + + IotMutex_Lock( &pUserContext->lock ); + pUserContext->counter++; + IotMutex_Unlock( &pUserContext->lock ); +} + +/** + * @brief A callback that blocks. + */ +static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * context ) +{ + JobBlockingUserContext_t * pUserContext; + IotTaskPoolJobStatus_t status; + + TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + + TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + + pUserContext = ( JobBlockingUserContext_t * ) context; + + /* Signal that the vallback has been called. */ + IotSemaphore_Post( &pUserContext->signal ); + + /* This callback will emulate a blocking wait, for the sole purpose of stealing a task pool + * thread and test that the taskpool can actually grow as expected. */ + IotSemaphore_Wait( &pUserContext->block ); +} + +/** + * @brief A callback that recycles its job. + */ +static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * context ) +{ + JobUserContext_t * pUserContext; + IotTaskPoolJobStatus_t status; + + TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + + TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + + EmulateWork(); + + pUserContext = ( JobUserContext_t * ) context; + + IotMutex_Lock( &pUserContext->lock ); + pUserContext->counter++; + IotMutex_Unlock( &pUserContext->lock ); + + IotTaskPool_RecycleJob( pTaskPool, pJob ); +} + +/** + * @brief A callback that takes a long time and does not recycle its job. + */ +static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * context ) +{ + JobUserContext_t * pUserContext; + IotTaskPoolJobStatus_t status; + + TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + + TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + + EmulateWorkLong(); + + pUserContext = ( JobUserContext_t * ) context; + + IotMutex_Lock( &pUserContext->lock ); + pUserContext->counter++; + IotMutex_Unlock( &pUserContext->lock ); +} + +/* ---------------------------------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------------------------------- */ + +/** + * @brief Number of legal task pool initialization configurations. + */ +#define LEGAL_INFOS 3 + +/** + * @brief Number of illegal task pool initialization configurations. + */ +#define ILLEGAL_INFOS 3 + +/** + * @brief Legal initialization configurations. + */ +IotTaskPoolInfo_t tpInfoLegal[ LEGAL_INFOS ] = +{ + { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, + { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, + { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +}; + +/** + * @brief Illegal initialization configurations. + */ +IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = +{ + { .minThreads = 0, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, + { .minThreads = 1, .maxThreads = 0, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, + { .minThreads = 2, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +}; + +/*-----------------------------------------------------------*/ + +/** + * @brief Test task pool dynamic memory creation and destruction, with both legal and illegal information. + */ +TEST( Common_Unit_Task_Pool, CreateDestroy ) +{ + uint32_t count; + IotTaskPool_t taskPool; + + for( count = 0; count < LEGAL_INFOS; ++count ) + { + TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ count ], &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + } + + for( count = 0; count < ILLEGAL_INFOS; ++count ) + { + TEST_ASSERT( IotTaskPool_Create( &tpInfoIllegal[ count ], &taskPool ) == IOT_TASKPOOL_BAD_PARAMETER ); + } + + TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ 0 ], NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Create( NULL, &taskPool ) == IOT_TASKPOOL_BAD_PARAMETER ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test task pool job static and dynamic memory creation with bogus parameters. + */ +TEST( Common_Unit_Task_Pool, CreateJobError ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Non-recyclable jobs. */ + { + IotTaskPoolJob_t job; + + /* NULL callback. */ + TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL job handle. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithDestroyCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + } + + /* Recyclable jobs. */ + { + IotTaskPoolJob_t * pJob = NULL; + /* NULL callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, NULL, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL engine handle. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( NULL, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL job handle. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + } + + IotTaskPool_Destroy( &taskPool ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test task pool job static and dynamic memory creation with bogus parameters. + */ +TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Recyclable jobs. */ + { + uint32_t count, jobLimit; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + jobLimit = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + jobLimit = 2 * IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * pJobs[ 2 * _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + #endif + + for( count = 0; count < jobLimit; ++count ) + { + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( pJobs[ count ] != NULL ); + } + + for( count = 0; count < jobLimit; ++count ) + { + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + } + + IotTaskPool_Destroy( &taskPool ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a job with bad parameters. + */ +TEST( Common_Unit_Task_Pool, ScheduleTasksError ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + IotTaskPool_Create( &tpInfo, &taskPool ); + + IotTaskPoolJob_t job; + + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + + /* NULL Task Pool Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL Work item Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* Destroy the job, so we do not leak it. */ + TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPool_Destroy( &taskPool ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a job with bad parameters. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + IotTaskPool_Create( &tpInfo, &taskPool ); + + IotTaskPoolJob_t * pJob; + + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + + /* NULL Task Pool Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL Work item Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* Recycle the job, so we do not leak it. */ + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPool_Destroy( &taskPool ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that the taskpool actually grows the number of tasks as expected. + */ +TEST( Common_Unit_Task_Pool, TaskPool_Grow ) +{ +#define _NUMBER_OF_JOBS 4 + + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = _NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobBlockingUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, _NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, _NUMBER_OF_JOBS ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated job, schedule one, then wait. */ + { + uint32_t count; + IotTaskPoolJob_t jobs[ _NUMBER_OF_JOBS ]; + + /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ + for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + + for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + { + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + + count = 0; + + while( true ) + { + /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ + IotSemaphore_Wait( &userContext.signal ); + + ++count; + + if( count == _NUMBER_OF_JOBS ) + { + break; + } + } + + /* Signal all taskpool threads to exit. */ + for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + { + IotSemaphore_Post( &userContext.block ); + } + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotSemaphore_Destroy( &userContext.signal ); + IotSemaphore_Destroy( &userContext.block ); + +#undef _NUMBER_OF_JOBS +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated job, schedule one, then wait. */ + { + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t job; + + for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &job ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + TEST_ASSERT( false ); + break; + + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + + /* Ensure callback actually executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT( userContext.counter == scheduled ); + + TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Since jobs were build from a static buffer and scheduled one-by-one, we + * should have received all callbacks. + */ + TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated job, schedule one, then wait. */ + { + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t job; + + for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &job, 10 + ( rand() % 50 ) ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + TEST_ASSERT( false ); + break; + + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + + /* Ensure callback actually executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT( userContext.counter == scheduled ); + + TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Since jobs were build from a static buffer and scheduled one-by-one, we + * should have received all callbacks. + */ + TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a set of recyclable jobs: dynamic allocation, sequential execution. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneRecyclableThenWait ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Dynamically allocated job, schedule one, then wait. */ + { + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t * pJob; + + for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + { + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, pJob ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + + /* Ensure callback actually executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT( userContext.counter == scheduled ); + } + + TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + + /* Since jobs were build from a static buffer and scheduled one-by-one, we + * should have received all callbacks. + */ + TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a set of jobs: static allocation, bulk execution. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated jobs, schedule all, then wait all. */ + { + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { { 0 } }; + + for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ] ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + /* Wait until callback is executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT_TRUE( userContext.counter == scheduled ); + + for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + { + TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a set of jobs: static allocation, bulk execution. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllRecyclableThenWait ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated jobs, schedule all, then wait all. */ + { + uint32_t count, maxJobs; + uint32_t scheduled = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = _TASKPOOL_TEST_ITERATIONS; + IotTaskPoolJob_t * tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + #endif + + for( count = 0; count < maxJobs; ++count ) + { + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, tpJobs[ count ] ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + /* Wait until callback is executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT_TRUE( userContext.counter == scheduled ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling a set of deferred jobs. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated jobs, schedule all, then wait all. */ + { + uint32_t count, maxJobs; + uint32_t scheduled = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = _TASKPOOL_TEST_ITERATIONS; + IotTaskPoolJob_t * tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + #endif + + for( count = 0; count < maxJobs; ++count ) + { + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + + IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + /* Wait until callback is executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT_TRUE( userContext.counter == scheduled ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling and re-scheduling (without canceling first) a set of jobs. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ) +{ + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated jobs, schedule all, then wait all. */ + { + uint32_t count, maxJobs; + uint32_t scheduled = 0, rescheduled = 0, failedReschudule = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = 10; + IotTaskPoolJob_t tpJobs[ 10 ] = { 0 }; + #endif + + /* Create all jobs. */ + for( count = 0; count < maxJobs; ++count ) + { + /* Shedule the job to be recycled in the callback. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Schedule all jobs. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorSchedule; + + /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ + errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ] ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + /* Give a chance to some jobs to start execution. */ + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + /* Reschedule all. Some will fail to be rescheduled... */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorReSchedule; + + errorReSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ] ); + + switch( errorReSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + rescheduled++; + break; + + case IOT_TASKPOOL_ILLEGAL_OPERATION: + /* Job already executed. */ + failedReschudule++; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + TEST_ASSERT_TRUE( ( rescheduled + failedReschudule ) == scheduled ); + + /* Wait until callback is executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT_TRUE( userContext.counter == scheduled ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling and re-scheduling (without canceling first) a set of deferred jobs. + */ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReScheduleDeferred ) +{ +#define _ONE_HOUR_IN_MS ( 60 * 60 * 1000 ) + + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated jobs, schedule all, then wait all. */ + { + uint32_t count, maxJobs; + uint32_t scheduled = 0, rescheduled = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = _TASKPOOL_TEST_ITERATIONS; + IotTaskPoolJob_t tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + #endif + + /* Schedule all jobs. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorSchedule; + + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + + /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], _ONE_HOUR_IN_MS ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + /* Reschedule all. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorReSchedule; + + errorReSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], 10 + ( rand() % 500 ) ); + + switch( errorReSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++rescheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + TEST_ASSERT_TRUE( rescheduled == scheduled ); + + /* Wait until callback is executed. */ + while( true ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); + } + + TEST_ASSERT_TRUE( userContext.counter == scheduled ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); + +#undef _ONE_HOUR_IN_MS +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test scheduling and canceling jobs. + */ +TEST( Common_Unit_Task_Pool, TaskPool_CancelTasks ) +{ + uint32_t count, maxJobs; + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + uint32_t canceled = 0; + uint32_t scheduled = 0; + + JobUserContext_t userContext = { 0 }; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = _TASKPOOL_TEST_ITERATIONS; + IotTaskPoolJob_t jobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + #endif + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Create and schedule loop. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorSchedule; + + IotTaskPoolError_t errorCreate = IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobs[ count ] ); + + switch( errorCreate ) + { + case IOT_TASKPOOL_SUCCESS: + break; + + case IOT_TASKPOOL_NO_MEMORY: /* OK. */ + continue; + + case IOT_TASKPOOL_BAD_PARAMETER: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &jobs[ count ], 10 + ( rand() % 20 ) ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + /* Cancellation loop. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t error; + IotTaskPoolJobStatus_t statusAtCancellation = IOT_TASKPOOL_STATUS_READY; + IotTaskPoolJobStatus_t statusAfterCancellation = IOT_TASKPOOL_STATUS_READY; + + error = IotTaskPool_TryCancel( &taskPool, &jobs[ count ], &statusAtCancellation ); + + switch( error ) + { + case IOT_TASKPOOL_SUCCESS: + canceled++; + + TEST_ASSERT( + ( statusAtCancellation == IOT_TASKPOOL_STATUS_READY ) || + ( statusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ) || + ( statusAtCancellation == IOT_TASKPOOL_STATUS_SCHEDULED ) || + ( statusAtCancellation == IOT_TASKPOOL_STATUS_CANCELED ) + ); + + TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( statusAfterCancellation == IOT_TASKPOOL_STATUS_CANCELED ); + break; + + case IOT_TASKPOOL_CANCEL_FAILED: + TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_EXECUTING ) ); + TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_EXECUTING ) ); + break; + + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + /* This must be a test issue. */ + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + break; + } + } + + /* Wait until callback is executed. */ + while( ( scheduled - canceled ) != userContext.counter ) + { + ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + } + + TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); + + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPool_DestroyJob( &taskPool, &jobs[ count ] ); + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 89e85403a8..40cfd57dfd 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -29,32 +29,32 @@ #include "unity_fixture_malloc_overrides.h" /* MQTT server endpoints used for the tests. */ -#if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 +#if IOT_TEST_MQTT_MOSQUITTO == 1 /* Mosquitto test server. */ - #define AWS_IOT_TEST_SECURED_CONNECTION ( 0 ) - #define AWS_IOT_TEST_SERVER "test.mosquitto.org" - #define AWS_IOT_TEST_PORT ( 1883 ) + #define IOT_TEST_SECURED_CONNECTION ( 0 ) + #define IOT_TEST_SERVER "test.mosquitto.org" + #define IOT_TEST_PORT ( 1883 ) #else /* AWS IoT MQTT server. */ - #define AWS_IOT_TEST_SECURED_CONNECTION ( 1 ) + #define IOT_TEST_SECURED_CONNECTION ( 1 ) /* AWS IoT endpoint and credentials. */ - #ifndef AWS_IOT_TEST_SERVER - #define AWS_IOT_TEST_SERVER "" + #ifndef IOT_TEST_SERVER + #define IOT_TEST_SERVER "" #endif - #ifndef AWS_IOT_TEST_PORT - #define AWS_IOT_TEST_PORT ( 443 ) + #ifndef IOT_TEST_PORT + #define IOT_TEST_PORT ( 443 ) #endif - #ifndef AWS_IOT_TEST_ROOT_CA - #define AWS_IOT_TEST_ROOT_CA "" + #ifndef IOT_TEST_ROOT_CA + #define IOT_TEST_ROOT_CA "" #endif - #ifndef AWS_IOT_TEST_CLIENT_CERT - #define AWS_IOT_TEST_CLIENT_CERT "" + #ifndef IOT_TEST_CLIENT_CERT + #define IOT_TEST_CLIENT_CERT "" #endif - #ifndef AWS_IOT_TEST_PRIVATE_KEY - #define AWS_IOT_TEST_PRIVATE_KEY "" + #ifndef IOT_TEST_PRIVATE_KEY + #define IOT_TEST_PRIVATE_KEY "" #endif -#endif /* if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 */ +#endif /* if IOT_TEST_MQTT_MOSQUITTO == 1 */ /* Shadow tests configuration. */ #ifndef AWS_IOT_TEST_SHADOW_THING_NAME @@ -62,49 +62,59 @@ #endif /* Linear containers library configuration. */ -#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) +#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) +/* MQTT library configuration. */ +#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) +#define IOT_MQTT_ENABLE_METRICS ( 0 ) +#define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) + +#define IOT_MQTT_TEST ( 1 ) /* Shadow library configuration. */ -#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) -/* MQTT library configuration. */ -#define AWS_IOT_MQTT_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_MQTT_ENABLE_METRICS ( 0 ) -#define AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) -#define AWS_IOT_MQTT_TEST ( 1 ) +/* Define the empty else marker if test coverage is enabled. */ +#if IOT_TEST_COVERAGE == 1 + #define _EMPTY_ELSE_MARKER asm volatile ( "nop" ) +#endif /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define IotNetwork_Malloc unity_malloc_mt -#define IotNetwork_Free unity_free_mt -#define IotThreads_Malloc unity_malloc_mt -#define IotThreads_Free unity_free_mt -#define IotLogging_Malloc unity_malloc_mt -#define IotLogging_Free unity_free_mt +#define IotNetwork_Malloc unity_malloc_mt +#define IotNetwork_Free unity_free_mt +#define IotThreads_Malloc unity_malloc_mt +#define IotThreads_Free unity_free_mt +#define IotLogging_Malloc unity_malloc_mt +#define IotLogging_Free unity_free_mt /* #define IotLogging_StaticBufferSize */ -#define AwsIotTest_Malloc unity_malloc_mt -#define AwsIotTest_Free unity_free_mt +#define IotTest_Malloc unity_malloc_mt +#define IotTest_Free unity_free_mt /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #define IOT_MQTT_CONNECTIONS ( 2 ) - #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) + #define IOT_MQTT_CONNECTIONS ( 2 ) + #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) + #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) #endif /* Memory allocation function configuration for libraries affected by * IOT_STATIC_MEMORY_ONLY. */ #if IOT_STATIC_MEMORY_ONLY == 0 - #define AwsIotMqtt_MallocConnection unity_malloc_mt - #define AwsIotMqtt_FreeConnection unity_free_mt - #define AwsIotMqtt_MallocMessage unity_malloc_mt - #define AwsIotMqtt_FreeMessage unity_free_mt - #define AwsIotMqtt_MallocOperation unity_malloc_mt - #define AwsIotMqtt_FreeOperation unity_free_mt - #define AwsIotMqtt_MallocSubscription unity_malloc_mt - #define AwsIotMqtt_FreeSubscription unity_free_mt - #define AwsIotMqtt_MallocTimerEvent unity_malloc_mt - #define AwsIotMqtt_FreeTimerEvent unity_free_mt + #define IotTaskPool_MallocJob unity_malloc_mt + #define IotTaskPool_FreeJob unity_free_mt + #define IotTaskPool_MallocTimerEvent unity_malloc_mt + #define IotTaskPool_FreeTimerEvent unity_free_mt + #define IotMqtt_MallocConnection unity_malloc_mt + #define IotMqtt_FreeConnection unity_free_mt + #define IotMqtt_MallocMessage unity_malloc_mt + #define IotMqtt_FreeMessage unity_free_mt + #define IotMqtt_MallocOperation unity_malloc_mt + #define IotMqtt_FreeOperation unity_free_mt + #define IotMqtt_MallocSubscription unity_malloc_mt + #define IotMqtt_FreeSubscription unity_free_mt + #define IotMqtt_MallocTimerEvent unity_malloc_mt + #define IotMqtt_FreeTimerEvent unity_free_mt #define AwsIotShadow_MallocOperation unity_malloc_mt #define AwsIotShadow_FreeOperation unity_free_mt #define AwsIotShadow_MallocString unity_malloc_mt @@ -122,19 +132,19 @@ typedef struct IotNetworkServerInfoOpenssl IotTestNetworkServerInfo_t; typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; /* Initializers for the tests' network types. */ -#define IOT_TEST_NETWORK_CONNECTION_INITIALIZER IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER +#define IOT_TEST_NETWORK_CONNECTION_INITIALIZER IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER #define IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER \ { \ - .pHostName = AWS_IOT_TEST_SERVER, \ - .port = AWS_IOT_TEST_PORT \ + .pHostName = IOT_TEST_SERVER, \ + .port = IOT_TEST_PORT \ } -#if AWS_IOT_TEST_SECURED_CONNECTION == 1 +#if IOT_TEST_SECURED_CONNECTION == 1 #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER \ { \ .pAlpnProtos = "\x0ex-amzn-mqtt-ca", \ - .pRootCaPath = AWS_IOT_TEST_ROOT_CA, \ - .pClientCertPath = AWS_IOT_TEST_CLIENT_CERT, \ - .pPrivateKeyPath = AWS_IOT_TEST_PRIVATE_KEY \ + .pRootCaPath = IOT_TEST_ROOT_CA, \ + .pClientCertPath = IOT_TEST_CLIENT_CERT, \ + .pPrivateKeyPath = IOT_TEST_PRIVATE_KEY \ } #else #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER diff --git a/tests/aws_iot_tests_network.c b/tests/iot_tests_network.c similarity index 72% rename from tests/aws_iot_tests_network.c rename to tests/iot_tests_network.c index a7e3ee2b4b..e8e788fa08 100644 --- a/tests/aws_iot_tests_network.c +++ b/tests/iot_tests_network.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_tests_network.c + * @file iot_tests_network.c * @brief Common network function implementations for the tests. */ @@ -34,7 +34,7 @@ #include /* MQTT include. */ -#include "aws_iot_mqtt.h" +#include "iot_mqtt.h" /* Test network header include. */ #include IOT_TEST_NETWORK_HEADER @@ -47,16 +47,16 @@ * Creates a global network connection to be used by the tests. * @return true if setup succeeded; false otherwise. * - * @see #AwsIotTest_NetworkCleanup + * @see #IotTest_NetworkCleanup */ -bool AwsIotTest_NetworkSetup( void ); +bool IotTest_NetworkSetup( void ); /** * @brief Network interface cleanup function for the tests. * - * @see #AwsIotTest_NetworkSetup + * @see #IotTest_NetworkSetup */ -void AwsIotTest_NetworkCleanup( void ); +void IotTest_NetworkCleanup( void ); /** * @brief Network interface connect function for the tests. @@ -68,27 +68,25 @@ void AwsIotTest_NetworkCleanup( void ); * * @return true if a new network connection was successfully created; false otherwise. */ -bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnection, - AwsIotMqttConnection_t * pMqttConnection ); +bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, + IotMqttConnection_t * pMqttConnection ); /** * @brief Network interface close connection function for the tests. * - * @param[in] reason Currently unused. * @param[in] pNetworkConnection The connection to close. Pass NULL to close - * the global network connection created by #AwsIotTest_NetworkSetup. + * the global network connection created by #IotTest_NetworkSetup. * * @return Always returns #IOT_NETWORK_SUCCESS. */ -IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, - void * pNetworkConnection ); +IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ); /** * @brief Network interface cleanup function for the tests. * * @param[in] pNetworkConnection The connection to destroy. */ -void AwsIotTest_NetworkDestroy( void * pNetworkConnection ); +void IotTest_NetworkDestroy( void * pNetworkConnection ); /*-----------------------------------------------------------*/ @@ -110,16 +108,16 @@ static const IotNetworkInterface_t * const _pNetworkInterface = IOT_TEST_NETWORK /** * @brief The MQTT network interface shared among the tests. */ -AwsIotMqttNetIf_t _AwsIotTestNetworkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; +IotMqttNetIf_t _IotTestNetworkInterface = IOT_MQTT_NETIF_INITIALIZER; /** * @brief The MQTT connection shared among the tests. */ -AwsIotMqttConnection_t _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; +IotMqttConnection_t _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*-----------------------------------------------------------*/ -bool AwsIotTest_NetworkSetup( void ) +bool IotTest_NetworkSetup( void ) { /* Initialize the network library. */ if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) @@ -127,8 +125,8 @@ bool AwsIotTest_NetworkSetup( void ) return false; } - if( AwsIotTest_NetworkConnect( &_networkConnection, - &_AwsIotTestMqttConnection ) == false ) + if( IotTest_NetworkConnect( &_networkConnection, + &_IotTestMqttConnection ) == false ) { IotTestNetwork_Cleanup(); @@ -136,10 +134,10 @@ bool AwsIotTest_NetworkSetup( void ) } /* Set the members of the network interface. */ - _AwsIotTestNetworkInterface.pDisconnectContext = NULL; - _AwsIotTestNetworkInterface.disconnect = AwsIotTest_NetworkClose; - _AwsIotTestNetworkInterface.pSendContext = ( void * ) &_networkConnection; - _AwsIotTestNetworkInterface.send = _pNetworkInterface->send; + _IotTestNetworkInterface.pDisconnectContext = NULL; + _IotTestNetworkInterface.disconnect = IotTest_NetworkClose; + _IotTestNetworkInterface.pSendContext = ( void * ) &_networkConnection; + _IotTestNetworkInterface.send = _pNetworkInterface->send; _networkConnectionCreated = true; @@ -148,13 +146,13 @@ bool AwsIotTest_NetworkSetup( void ) /*-----------------------------------------------------------*/ -void AwsIotTest_NetworkCleanup( void ) +void IotTest_NetworkCleanup( void ) { /* Close the TCP connection to the server. */ if( _networkConnectionCreated == true ) { - AwsIotTest_NetworkClose( 0, NULL ); - AwsIotTest_NetworkDestroy( &_networkConnection ); + IotTest_NetworkClose( NULL ); + IotTest_NetworkDestroy( &_networkConnection ); _networkConnectionCreated = false; } @@ -162,19 +160,19 @@ void AwsIotTest_NetworkCleanup( void ) IotTestNetwork_Cleanup(); /* Clear the network interface. */ - ( void ) memset( &_AwsIotTestNetworkInterface, 0x00, sizeof( AwsIotMqttNetIf_t ) ); + ( void ) memset( &_IotTestNetworkInterface, 0x00, sizeof( IotMqttNetIf_t ) ); } /*-----------------------------------------------------------*/ -bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnection, - AwsIotMqttConnection_t * pMqttConnection ) +bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, + IotMqttConnection_t * pMqttConnection ) { IotTestNetworkServerInfo_t serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; IotTestNetworkCredentials_t * pCredentials = NULL; /* Set up TLS if the endpoint is secured. These tests should always use ALPN. */ - #if AWS_IOT_TEST_SECURED_CONNECTION == 1 + #if IOT_TEST_SECURED_CONNECTION == 1 IotTestNetworkCredentials_t credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; pCredentials = &credentials; #endif @@ -189,10 +187,10 @@ bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnectio /* Set the MQTT receive callback. */ if( _pNetworkInterface->setReceiveCallback( pNewConnection, - AwsIotMqtt_ReceiveCallback, + IotMqtt_ReceiveCallback, pMqttConnection ) != IOT_NETWORK_SUCCESS ) { - _pNetworkInterface->close( 0, pNewConnection ); + _pNetworkInterface->close( pNewConnection ); _pNetworkInterface->destroy( pNewConnection ); return false; @@ -203,22 +201,18 @@ bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnectio /*-----------------------------------------------------------*/ -IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, - void * pNetworkConnection ) +IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ) { - /* Ignore the reason for closure. */ - ( void ) reason; - /* Close the provided network handle; if that is NULL, close the * global network handle. */ if( ( pNetworkConnection != NULL ) && ( pNetworkConnection != &_networkConnection ) ) { - _pNetworkInterface->close( 0, pNetworkConnection ); + _pNetworkInterface->close( pNetworkConnection ); } else if( _networkConnectionCreated == true ) { - _pNetworkInterface->close( 0, &_networkConnection ); + _pNetworkInterface->close( &_networkConnection ); } return IOT_NETWORK_SUCCESS; @@ -226,7 +220,7 @@ IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, /*-----------------------------------------------------------*/ -void AwsIotTest_NetworkDestroy( void * pNetworkConnection ) +void IotTest_NetworkDestroy( void * pNetworkConnection ) { if( ( pNetworkConnection != NULL ) && ( pNetworkConnection != &_networkConnection ) ) diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index 7868c88557..eedc6d0923 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -1,13 +1,13 @@ # MQTT tests executable. -add_executable( aws_iot_tests_mqtt - aws_iot_tests_mqtt.c - ${CMAKE_SOURCE_DIR}/tests/aws_iot_tests_network.c - unit/aws_iot_tests_mqtt_api.c - unit/aws_iot_tests_mqtt_receive.c - unit/aws_iot_tests_mqtt_subscription.c - unit/aws_iot_tests_mqtt_validate.c - system/aws_iot_tests_mqtt_system.c - system/aws_iot_tests_mqtt_stress.c ) +add_executable( iot_tests_mqtt + iot_tests_mqtt.c + ${CMAKE_SOURCE_DIR}/tests/iot_tests_network.c + unit/iot_tests_mqtt_api.c + unit/iot_tests_mqtt_receive.c + unit/iot_tests_mqtt_subscription.c + unit/iot_tests_mqtt_validate.c + system/iot_tests_mqtt_system.c + system/iot_tests_mqtt_stress.c ) # MQTT tests library dependencies. -target_link_libraries( aws_iot_tests_mqtt iotcommon iotplatform iotmqtt unity ) +target_link_libraries( iot_tests_mqtt iotcommon iotplatform iotmqtt unity ) diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt.h b/tests/mqtt/access/iot_test_access_mqtt.h similarity index 63% rename from tests/mqtt/access/aws_iot_test_access_mqtt.h rename to tests/mqtt/access/iot_test_access_mqtt.h index 964c1551b1..4843875a5f 100644 --- a/tests/mqtt/access/aws_iot_test_access_mqtt.h +++ b/tests/mqtt/access/iot_test_access_mqtt.h @@ -20,33 +20,26 @@ */ /** - * @file aws_iot_test_access_mqtt.h + * @file iot_test_access_mqtt.h * @brief Declares the functions that provide access to the internal functions * and variables of the MQTT library. */ -#ifndef _AWS_IOT_TEST_ACCESS_MQTT_H_ -#define _AWS_IOT_TEST_ACCESS_MQTT_H_ +#ifndef _IOT_TEST_ACCESS_MQTT_H_ +#define _IOT_TEST_ACCESS_MQTT_H_ -/*--------------------------- aws_iot_mqtt_api.c ---------------------------*/ +/*--------------------------- iot_mqtt_api.c ---------------------------*/ /** * @brief Test access function for #_createMqttConnection. * * @see #_createMqttConnection. */ -_mqttConnection_t * AwsIotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const AwsIotMqttNetIf_t * const pNetworkInterface, - uint16_t keepAliveSeconds ); +_mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, + const IotMqttNetIf_t * pNetworkInterface, + uint16_t keepAliveSeconds ); -/** - * @brief Test access function for #_destroyMqttConnection. - * - * @see #_destroyMqttConnection. - */ -void AwsIotTestMqtt_destroyMqttConnection( _mqttConnection_t * const pMqttConnection ); - -/*------------------------- aws_iot_mqtt_serialize.c ------------------------*/ +/*------------------------- iot_mqtt_serialize.c ------------------------*/ /* * Macros for reading the high and low byte of a 2-byte unsigned int. @@ -68,13 +61,13 @@ void AwsIotTestMqtt_destroyMqttConnection( _mqttConnection_t * const pMqttConnec * * @see #_decodeRemainingLength. */ -AwsIotMqttError_t AwsIotTestMqtt_decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** const pEnd, - size_t * const pLength ); +IotMqttError_t IotTestMqtt_decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** pEnd, + size_t * pLength ); -/*----------------------- aws_iot_mqtt_subscription.c -----------------------*/ +/*----------------------- iot_mqtt_subscription.c -----------------------*/ -/* Internal data structures of aws_iot_mqtt_subscription.c, redefined for the tests. */ +/* Internal data structures of iot_mqtt_subscription.c, redefined for the tests. */ typedef struct _topicMatchParams { const char * pTopicName; @@ -84,7 +77,7 @@ typedef struct _topicMatchParams typedef struct _packetMatchParams { uint16_t packetIdentifier; - long order; + int32_t order; } _packetMatchParams_t; /** @@ -92,15 +85,15 @@ typedef struct _packetMatchParams * * @see #_topicMatch. */ -bool AwsIotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ); +bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); /** * @brief Test access function for #_packetMatch. * * @see #_packetMatch. */ -bool AwsIotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ); +bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); -#endif /* ifndef _AWS_IOT_TEST_ACCESS_MQTT_H_ */ +#endif /* ifndef _IOT_TEST_ACCESS_MQTT_H_ */ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_api.c b/tests/mqtt/access/iot_test_access_mqtt_api.c similarity index 70% rename from tests/mqtt/access/aws_iot_test_access_mqtt_api.c rename to tests/mqtt/access/iot_test_access_mqtt_api.c index bfce784476..aca4fc8f7e 100644 --- a/tests/mqtt/access/aws_iot_test_access_mqtt_api.c +++ b/tests/mqtt/access/iot_test_access_mqtt_api.c @@ -20,28 +20,21 @@ */ /** - * @file aws_iot_test_access_mqtt_api.c + * @file iot_test_access_mqtt_api.c * @brief Provides access to the internal functions and variables of - * aws_iot_mqtt_api.c + * iot_mqtt_api.c * - * This file should only be included at the bottom of aws_iot_mqtt_api.c and never + * This file should only be included at the bottom of iot_mqtt_api.c and never * compiled by itself. */ /*-----------------------------------------------------------*/ -_mqttConnection_t * AwsIotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const AwsIotMqttNetIf_t * const pNetworkInterface, - uint16_t keepAliveSeconds ) +_mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, + const IotMqttNetIf_t * pNetworkInterface, + uint16_t keepAliveSeconds ) { return _createMqttConnection( awsIotMqttMode, pNetworkInterface, keepAliveSeconds ); } /*-----------------------------------------------------------*/ - -void AwsIotTestMqtt_destroyMqttConnection( _mqttConnection_t * const pMqttConnection ) -{ - _destroyMqttConnection( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c b/tests/mqtt/access/iot_test_access_mqtt_serialize.c similarity index 78% rename from tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c rename to tests/mqtt/access/iot_test_access_mqtt_serialize.c index 9191aa3b52..8199777230 100644 --- a/tests/mqtt/access/aws_iot_test_access_mqtt_serialize.c +++ b/tests/mqtt/access/iot_test_access_mqtt_serialize.c @@ -20,19 +20,19 @@ */ /** - * @file aws_iot_test_access_mqtt_serialize.c + * @file iot_test_access_mqtt_serialize.c * @brief Provides access to the internal functions and variables of - * aws_iot_mqtt_serialize.c + * iot_mqtt_serialize.c * - * This file should only be included at the bottom of aws_iot_mqtt_serialize.c + * This file should only be included at the bottom of iot_mqtt_serialize.c * and never compiled by itself. */ /*-----------------------------------------------------------*/ -AwsIotMqttError_t AwsIotTestMqtt_decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** const pEnd, - size_t * const pLength ) +IotMqttError_t IotTestMqtt_decodeRemainingLength( const uint8_t * pSource, + const uint8_t ** pEnd, + size_t * pLength ) { return _decodeRemainingLength( pSource, pEnd, pLength ); } diff --git a/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c b/tests/mqtt/access/iot_test_access_mqtt_subscription.c similarity index 79% rename from tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c rename to tests/mqtt/access/iot_test_access_mqtt_subscription.c index b720b3ece9..55cee5e850 100644 --- a/tests/mqtt/access/aws_iot_test_access_mqtt_subscription.c +++ b/tests/mqtt/access/iot_test_access_mqtt_subscription.c @@ -20,26 +20,26 @@ */ /** - * @file aws_iot_test_access_mqtt_subscription.c + * @file iot_test_access_mqtt_subscription.c * @brief Provides access to the internal functions and variables of - * aws_iot_mqtt_subscription.c + * iot_mqtt_subscription.c * - * This file should only be included at the bottom of aws_iot_mqtt_subscription.c + * This file should only be included at the bottom of iot_mqtt_subscription.c * and never compiled by itself. */ /*-----------------------------------------------------------*/ -bool AwsIotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ) +bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) { return _topicMatch( pSubscriptionLink, pMatch ); } /*-----------------------------------------------------------*/ -bool AwsIotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ) +bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) { return _packetMatch( pSubscriptionLink, pMatch ); } diff --git a/tests/mqtt/aws_iot_tests_mqtt.c b/tests/mqtt/iot_tests_mqtt.c similarity index 91% rename from tests/mqtt/aws_iot_tests_mqtt.c rename to tests/mqtt/iot_tests_mqtt.c index c5d1c22e17..4466dc748a 100644 --- a/tests/mqtt/aws_iot_tests_mqtt.c +++ b/tests/mqtt/iot_tests_mqtt.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_tests_mqtt.c + * @file iot_tests_mqtt.c * @brief Test runner for the MQTT tests on POSIX systems. */ @@ -50,12 +50,12 @@ static void _signalHandler( int signum ) if( signum == SIGSEGV ) { printf( "\nSegmentation fault.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } else if( signum == SIGABRT ) { printf( "\nAssertion failed.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } } @@ -72,18 +72,18 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return -1; + return EXIT_FAILURE; } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return -1; + return EXIT_FAILURE; } /* Initialize the common libraries before running the tests. */ if( IotCommon_Init() == false ) { - return -1; + return EXIT_FAILURE; } /* Unity setup. */ @@ -110,9 +110,13 @@ int main( int argc, /* Clean up common libraries. */ IotCommon_Cleanup(); - /* Return the number of test failures. This will cause a non-zero exit code - * if any test fails. */ - return UNITY_END(); + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_system.c b/tests/mqtt/system/aws_iot_tests_mqtt_system.c deleted file mode 100644 index b9bd3e7a3a..0000000000 --- a/tests/mqtt/system/aws_iot_tests_mqtt_system.c +++ /dev/null @@ -1,879 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_mqtt_system.c - * @brief Full system tests for the AWS IoT MQTT library. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* Test framework includes. */ -#include "unity_fixture.h" - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int snprintf( char *, - size_t, - const char *, - ... ); -/** @endcond */ - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef AWS_IOT_TEST_MQTT_TIMEOUT_MS - #define AWS_IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) -#endif -#ifndef AWS_IOT_TEST_MQTT_TOPIC_PREFIX - #define AWS_IOT_TEST_MQTT_TOPIC_PREFIX "awsiotmqtttest" -#endif -/** @endcond */ - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true -#else - #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER - #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } -#endif - -/** - * @brief The maximum length of an MQTT client identifier. - * - * When @ref AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER is defined, this value must - * accommodate the length of @ref AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER plus 4 - * to accommodate the Last Will and Testament test. Otherwise, this value is - * set to 24, which is the longest client identifier length an MQTT server is - * obligated to accept plus a NULL terminator. - */ -#ifdef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) -#else - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Parameter 1 of #_operationComplete. - */ -typedef struct _operationCompleteParams -{ - AwsIotMqttOperationType_t expectedOperation; /**< @brief Expected completed operation. */ - IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ - AwsIotMqttReference_t reference; /**< @brief Reference to expected completed operation. */ -} _operationCompleteParams_t; - -/*-----------------------------------------------------------*/ - -/* Network functions used by the tests, declared and implemented in one of - * the test network function files. */ -extern bool AwsIotTest_NetworkSetup( void ); -extern void AwsIotTest_NetworkCleanup( void ); -extern bool AwsIotTest_NetworkConnect( IotTestNetworkConnection_t * const pNewConnection, - AwsIotMqttConnection_t * pMqttConnection ); -extern IotNetworkError_t AwsIotTest_NetworkClose( int32_t reason, - void * pNetworkConnection ); -extern void AwsIotTest_NetworkDestroy( void * pConnection ); - -/* Network variables used by the tests, declared in one of the test network - * function files. */ -extern AwsIotMqttNetIf_t _AwsIotTestNetworkInterface; -extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; - -/*-----------------------------------------------------------*/ - -/** - * @brief Filler text to publish. - */ -static const char _pSamplePayload[] = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " - "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " - "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" - " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" - " culpa qui officia deserunt mollit anim id est laborum."; - -/** - * @brief Length of #_pSamplePayload. - */ -static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; - -/** - * @brief Buffer holding the client identifier used for the tests. - */ -static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - -/* - * Track if the serializer overrides were called for a test. - */ -static bool _freePacketOverride = false; /**< @brief Tracks if #_freePacket was called. */ -static bool _connectSerializerOverride = false; /**< @brief Tracks if #_connectSerializerOverride was called. */ -static bool _publishSerializerOverride = false; /**< @brief Tracks if #_publishSerializerOverride was called. */ -static bool _pubackSerializerOverride = false; /**< @brief Tracks if #_pubackSerializerOverride was called. */ -static bool _subscribeSerializerOverride = false; /**< @brief Tracks if #_subscribeSerializerOverride was called. */ -static bool _unsubscribeSerializerOverride = false; /**< @brief Tracks if #_unsubscribeSerializerOverride was called. */ -static bool _disconnectSerializerOverride = false; /**< @brief Tracks if #_disconnectSerializerOverride was called. */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Packet free function override - */ -static void _freePacket( uint8_t * pPacket ) -{ - _freePacketOverride = true; - - AwsIotMqttInternal_FreePacket( pPacket ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for CONNECT. - */ -static AwsIotMqttError_t _serializeConnect( const AwsIotMqttConnectInfo_t * const pConnectInfo, - uint8_t ** const pConnectPacket, - size_t * const pPacketSize ) -{ - _connectSerializerOverride = true; - - return AwsIotMqttInternal_SerializeConnect( pConnectInfo, - pConnectPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PUBLISH. - */ -static AwsIotMqttError_t _serializePublish( const AwsIotMqttPublishInfo_t * const pPublishInfo, - uint8_t ** const pPublishPacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ) -{ - _publishSerializerOverride = true; - - return AwsIotMqttInternal_SerializePublish( pPublishInfo, - pPublishPacket, - pPacketSize, - pPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PUBACK. - */ -static AwsIotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** const pPubackPacket, - size_t * const pPacketSize ) -{ - _pubackSerializerOverride = true; - - return AwsIotMqttInternal_SerializePuback( packetIdentifier, - pPubackPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for SUBSCRIBE. - */ -static AwsIotMqttError_t _serializeSubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint8_t ** const pSubscribePacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ) -{ - _subscribeSerializerOverride = true; - - return AwsIotMqttInternal_SerializeSubscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for UNSUBSCRIBE. - */ -static AwsIotMqttError_t _serializeUnsubscribe( const AwsIotMqttSubscription_t * const pSubscriptionList, - size_t subscriptionCount, - uint8_t ** const pSubscribePacket, - size_t * const pPacketSize, - uint16_t * const pPacketIdentifier ) -{ - _unsubscribeSerializerOverride = true; - - return AwsIotMqttInternal_SerializeUnsubscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for DISCONNECT. - */ -static AwsIotMqttError_t _serializeDisconnect( uint8_t ** const pDisconnectPacket, - size_t * const pPacketSize ) -{ - _disconnectSerializerOverride = true; - - return AwsIotMqttInternal_SerializeDisconnect( pDisconnectPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscription callback function. Checks for valid parameters and unblocks - * the main test thread. - */ -static void _publishReceived( void * pArgument, - AwsIotMqttCallbackParam_t * const pPublish ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - - /* If the received messages matches what was sent, unblock the waiting thread. */ - if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && - ( strncmp( pPublish->message.info.pPayload, - _pSamplePayload, - _samplePayloadLength ) == 0 ) ) - { - IotSemaphore_Post( pWaitSem ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Operation completion callback function. Checks for valid parameters - * and unblocks the main test thread. - */ -static void _operationComplete( void * pArgument, - AwsIotMqttCallbackParam_t * const pOperation ) -{ - _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; - - /* If the operation information matches the parameters and the operation was - * successful, unblock the waiting thread. */ - if( ( pParams->expectedOperation == pOperation->operation.type ) && - ( pParams->reference == pOperation->operation.reference ) && - ( pOperation->operation.result == AWS_IOT_MQTT_SUCCESS ) ) - { - IotSemaphore_Post( &( pParams->waitSem ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Run the subscribe-publish-wait tests at various QoS. - */ -static void _subscribePublishWait( int QoS ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttNetIf_t networkInterface = _AwsIotTestNetworkInterface; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* Set the serializer overrides. */ - networkInterface.freePacket = _freePacket; - networkInterface.serialize.connect = _serializeConnect; - networkInterface.serialize.publish = _serializePublish; - networkInterface.serialize.puback = _serializePuback; - networkInterface.serialize.subscribe = _serializeSubscribe; - networkInterface.serialize.unsubscribe = _serializeUnsubscribe; - networkInterface.serialize.disconnect = _serializeDisconnect; - - /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Set the members of the MQTT connection info. */ - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - /* Establish the MQTT connection. */ - status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &networkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Set the members of the subscription. */ - subscription.QoS = QoS; - subscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = _publishReceived; - subscription.callback.param1 = &waitSem; - - /* Subscribe to the test topic filter using the blocking SUBSCRIBE - * function. */ - status = AwsIotMqtt_TimedSubscribe( _AwsIotTestMqttConnection, - &subscription, - 1, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Set the members of the publish info. */ - publishInfo.QoS = QoS; - publishInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - - /* Publish the message. */ - status = AwsIotMqtt_TimedPublish( _AwsIotTestMqttConnection, - &publishInfo, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - - /* Wait for the message to be received. */ - if( IotSemaphore_TimedWait( &waitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); - } - - /* Unsubscribe from the test topic filter. */ - status = AwsIotMqtt_TimedUnsubscribe( _AwsIotTestMqttConnection, - &subscription, - 1, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - } - - /* Close the MQTT connection. */ - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); - } - - IotSemaphore_Destroy( &waitSem ); - - /* Check that the serializer overrides were called. */ - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, _freePacketOverride ); - TEST_ASSERT_EQUAL_INT( true, _connectSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _publishSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _subscribeSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _unsubscribeSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _disconnectSerializerOverride ); - - if( QoS == 1 ) - { - TEST_ASSERT_EQUAL_INT( true, _pubackSerializerOverride ); - } - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT system tests. - */ -TEST_GROUP( MQTT_System ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT system tests. - */ -TEST_SETUP( MQTT_System ) -{ - /* Clear the serializer override flags. */ - _freePacketOverride = false; - _connectSerializerOverride = false; - _publishSerializerOverride = false; - _pubackSerializerOverride = false; - _subscribeSerializerOverride = false; - _unsubscribeSerializerOverride = false; - _disconnectSerializerOverride = false; - - /* Initialize the MQTT library. */ - if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - - /* Set up the network stack. */ - if( AwsIotTest_NetworkSetup() == false ) - { - TEST_FAIL_MESSAGE( "Failed to set up network connection." ); - } - - /* Generate a new, unique client identifier based on the time if no client - * identifier is defined. Otherwise, copy the provided client identifier. */ - #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER - ( void ) snprintf( _pClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, - "aws%llu", - ( long long unsigned int ) IotClock_GetTimeMs() ); - #else - ( void ) strncpy( _pClientIdentifier, - AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER, - _CLIENT_IDENTIFIER_MAX_LENGTH ); - #endif -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT system tests. - */ -TEST_TEAR_DOWN( MQTT_System ) -{ - /* Clean up the MQTT library. */ - AwsIotMqtt_Cleanup(); - _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - - /* Clean up the network stack. */ - AwsIotTest_NetworkCleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT system tests. - */ -TEST_GROUP_RUNNER( MQTT_System ) -{ - RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS0 ); - RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS1 ); - RUN_TEST_CASE( MQTT_System, SubscribePublishAsync ); - RUN_TEST_CASE( MQTT_System, LastWillAndTestament ); - RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscribe-publish-wait (QoS 0). - */ -TEST( MQTT_System, SubscribePublishWaitQoS0 ) -{ - _subscribePublishWait( 0 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscribe-publish-wait (QoS 1). - */ -TEST( MQTT_System, SubscribePublishWaitQoS1 ) -{ - _subscribePublishWait( 1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscribe-publish asynchronous (QoS 1). - */ -TEST( MQTT_System, SubscribePublishAsync ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { 0 }; - IotSemaphore_t publishWaitSem; - - /* Initialize members of the operation callback info. */ - callbackInfo.function = _operationComplete; - callbackInfo.param1 = &callbackParam; - - /* Initialize members of the subscription. */ - subscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = _publishReceived; - subscription.callback.param1 = &publishWaitSem; - - /* Initialize members of the connect info. */ - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - /* Initialize members of the publish info. */ - publishInfo.QoS = 1; - publishInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - - /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Create the wait semaphore for subscriptions. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &publishWaitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Establish the MQTT connection. */ - status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Subscribe to the test topic filter. */ - callbackParam.expectedOperation = AWS_IOT_MQTT_SUBSCRIBE; - status = AwsIotMqtt_Subscribe( _AwsIotTestMqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - &( callbackParam.reference ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for SUBSCRIBE to complete." ); - } - - /* Publish the message. */ - callbackParam.expectedOperation = AWS_IOT_MQTT_PUBLISH_TO_SERVER; - status = AwsIotMqtt_Publish( _AwsIotTestMqttConnection, - &publishInfo, - 0, - &callbackInfo, - &( callbackParam.reference ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for PUBLISH to complete." ); - } - - /* Wait for the message to be received. */ - if( IotSemaphore_TimedWait( &publishWaitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); - } - - /* Unsubscribe from the test topic filter. */ - callbackParam.expectedOperation = AWS_IOT_MQTT_UNSUBSCRIBE; - status = AwsIotMqtt_Unsubscribe( _AwsIotTestMqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - &( callbackParam.reference ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for UNSUBSCRIBE to complete." ); - } - } - - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); - } - - IotSemaphore_Destroy( &publishWaitSem ); - } - - IotSemaphore_Destroy( &( callbackParam.waitSem ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that a LWT is published if an MQTT connection is unexpectedly closed. - */ -TEST( MQTT_System, LastWillAndTestament ) -{ - bool lwtListenerCreated = false; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttNetIf_t lwtNetIf = _AwsIotTestNetworkInterface; - char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - AwsIotMqttConnection_t lwtListener = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotMqttConnectInfo_t lwtConnectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER, - connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttSubscription_t willSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotTestNetworkConnection_t lwtListenerConnection = IOT_TEST_NETWORK_CONNECTION_INITIALIZER; - IotSemaphore_t waitSem; - - /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - /* Generate a client identifier for LWT listener. */ - #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER - ( void ) snprintf( pLwtListenerClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, - "awslwt%llu", - ( long long unsigned int ) IotClock_GetTimeMs() ); - #else - ( void ) strncpy( pLwtListenerClientIdentifier, - AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER "LWT", - _CLIENT_IDENTIFIER_MAX_LENGTH ); - #endif - - if( TEST_PROTECT() ) - { - /* Establish an independent MQTT over TCP connection to receive a Last - * Will and Testament message. */ - TEST_ASSERT_EQUAL( true, - AwsIotTest_NetworkConnect( &lwtListenerConnection, - &lwtListener ) ); - lwtListenerCreated = true; - - if( TEST_PROTECT() ) - { - lwtNetIf.pDisconnectContext = &lwtListenerConnection; - lwtNetIf.pSendContext = &lwtListenerConnection; - lwtConnectInfo.cleanSession = true; - lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; - lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); - - status = AwsIotMqtt_Connect( &lwtListener, - &lwtNetIf, - &lwtConnectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Register a subscription for the LWT. */ - willSubscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; - willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); - willSubscription.callback.function = _publishReceived; - willSubscription.callback.param1 = &waitSem; - - status = AwsIotMqtt_TimedSubscribe( lwtListener, - &willSubscription, - 1, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Create a connection that requests the LWT. */ - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - connectInfo.pWillInfo = &willInfo; - - willInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; - willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); - willInfo.pPayload = _pSamplePayload; - willInfo.payloadLength = _samplePayloadLength; - - status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Abruptly close the MQTT connection. This should cause the LWT - * to be sent to the LWT listener. */ - AwsIotTest_NetworkClose( 0, NULL ); - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, true ); - AwsIotTest_NetworkDestroy( NULL ); - - /* Check that the LWT was received. */ - if( IotSemaphore_TimedWait( &waitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); - } - } - - AwsIotMqtt_Disconnect( lwtListener, false ); - AwsIotTest_NetworkDestroy( &lwtListenerConnection ); - lwtListenerCreated = false; - } - - if( lwtListenerCreated == true ) - { - AwsIotTest_NetworkClose( 0, &lwtListenerConnection ); - AwsIotTest_NetworkDestroy( &lwtListenerConnection ); - } - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that subscriptions from a previous session are restored. - */ -TEST( MQTT_System, RestorePreviousSession ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - /* Set the members of the connection info for a persistent session. */ - connectInfo.cleanSession = false; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - if( TEST_PROTECT() ) - { - /* Establish a persistent MQTT connection. */ - status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Add a subscription. */ - subscription.pTopicFilter = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.param1 = &waitSem; - subscription.callback.function = _publishReceived; - - status = AwsIotMqtt_TimedSubscribe( _AwsIotTestMqttConnection, - &subscription, - 1, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Disconnect the MQTT connection and clean up network connection. */ - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); - AwsIotTest_NetworkCleanup(); - - /* Re-establish the network connection. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotTest_NetworkSetup() ); - - /* Re-establish the MQTT connection with a previous session. */ - connectInfo.cleanSession = false; - connectInfo.pPreviousSubscriptions = &subscription; - connectInfo.previousSubscriptionCount = 1; - status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Publish a message to the subscription added in the previous session. */ - publishInfo.pTopicName = AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - - status = AwsIotMqtt_TimedPublish( _AwsIotTestMqttConnection, - &publishInfo, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Wait for the message to be received. */ - if( IotSemaphore_TimedWait( &waitSem, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for message." ); - } - - /* Disconnect the MQTT connection. */ - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); - AwsIotTest_NetworkCleanup(); - } - else - { - /* Close network connection on test failure. */ - AwsIotTest_NetworkClose( 0, NULL ); - } - - IotSemaphore_Destroy( &waitSem ); - - if( TEST_PROTECT() ) - { - /* Re-establish the network connection. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotTest_NetworkSetup() ); - - /* After this test is finished, establish one more connection with a clean - * session to clean up persistent sessions on the MQTT server created by this - * test. */ - connectInfo.cleanSession = true; - status = AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c similarity index 65% rename from tests/mqtt/system/aws_iot_tests_mqtt_stress.c rename to tests/mqtt/system/iot_tests_mqtt_stress.c index e4c9565c5c..1b2d8ac730 100644 --- a/tests/mqtt/system/aws_iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_tests_mqtt_stress.c - * @brief Stress tests for the AWS IoT MQTT library. + * @file iot_tests_mqtt_stress.c + * @brief Stress tests for the MQTT library. * * The tests in this file run far longer than other tests, and may easily fail * due to poor network conditions. For best results, these tests should be run @@ -35,13 +35,12 @@ /* Standard includes. */ #include -#include /* POSIX includes. */ #include /* MQTT include. */ -#include "aws_iot_mqtt.h" +#include "iot_mqtt.h" /* POSIX includes. */ #ifdef POSIX_PTHREAD_HEADER @@ -49,6 +48,11 @@ #else #include #endif +#ifdef POSIX_UNISTD_HEADER + #include POSIX_UNISTD_HEADER +#else + #include +#endif /* Platform layer include. */ #include "platform/iot_clock.h" @@ -66,14 +70,14 @@ /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ -#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER - #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } + #undef IOT_MQTT_CONNECT_INFO_INITIALIZER + #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /** @@ -82,36 +86,36 @@ * * Provide default values of test configuration constants. */ -#ifndef AWS_IOT_TEST_MQTT_TIMEOUT_MS - #define AWS_IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) +#ifndef IOT_TEST_MQTT_TIMEOUT_MS + #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) #endif -#ifndef AWS_IOT_TEST_MQTT_TOPIC_PREFIX - #define AWS_IOT_TEST_MQTT_TOPIC_PREFIX "awsiotmqtttest" +#ifndef IOT_TEST_MQTT_TOPIC_PREFIX + #define IOT_TEST_MQTT_TOPIC_PREFIX "iotmqtttest" #endif -#ifndef AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S - #if AWS_IOT_TEST_MQTT_MOSQUITTO == 1 - #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 5 ) +#ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S + #if IOT_TEST_MQTT_MOSQUITTO == 1 + #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 5 ) #else - #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) + #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) #endif #endif -#ifndef AWS_IOT_TEST_MQTT_RETRY_MS - #define AWS_IOT_TEST_MQTT_RETRY_MS ( 350 ) +#ifndef IOT_TEST_MQTT_RETRY_MS + #define IOT_TEST_MQTT_RETRY_MS ( 350 ) #endif -#ifndef AWS_IOT_TEST_MQTT_RETRY_LIMIT - #define AWS_IOT_TEST_MQTT_RETRY_LIMIT ( 32 ) +#ifndef IOT_TEST_MQTT_RETRY_LIMIT + #define IOT_TEST_MQTT_RETRY_LIMIT ( 32 ) #endif -#ifndef AWS_IOT_TEST_MQTT_DECONGEST_S - #define AWS_IOT_TEST_MQTT_DECONGEST_S ( 30 ) +#ifndef IOT_TEST_MQTT_DECONGEST_S + #define IOT_TEST_MQTT_DECONGEST_S ( 30 ) #endif -#ifndef AWS_IOT_TEST_MQTT_THREADS - #define AWS_IOT_TEST_MQTT_THREADS ( 16 ) +#ifndef IOT_TEST_MQTT_THREADS + #define IOT_TEST_MQTT_THREADS ( 16 ) #endif -#ifndef AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD - #ifdef AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS - #define AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( AWS_IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ) +#ifndef IOT_TEST_MQTT_PUBLISHES_PER_THREAD + #ifdef IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS + #define IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ) #else - #define AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( 100 ) + #define IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( 100 ) #endif #endif /** @endcond */ @@ -125,13 +129,13 @@ * @brief The maximum number of PUBLISH messages that will be received by a * single test. */ -#define _MAX_RECEIVED_PUBLISH ( AWS_IOT_TEST_MQTT_THREADS * AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) +#define _MAX_RECEIVED_PUBLISH ( IOT_TEST_MQTT_THREADS * IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) /** * @brief The maximum length of an MQTT client identifier. */ -#ifdef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER ) ) +#ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) ) #else #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) #endif @@ -143,29 +147,29 @@ */ typedef struct _publishParams { - int threadNumber; /**< @brief ID number of this publish thread. */ - long publishPeriodNs; /**< @brief How long to wait (in nanoseconds) between each publish. */ - unsigned publishLimit; /**< @brief How many publishes this thread will send. */ - AwsIotMqttError_t status; /**< @brief Final status of this publish thread. */ + int threadNumber; /**< @brief ID number of this publish thread. */ + long publishPeriodNs; /**< @brief How long to wait (in nanoseconds) between each publish. */ + unsigned publishLimit; /**< @brief How many publishes this thread will send. */ + IotMqttError_t status; /**< @brief Final status of this publish thread. */ } _publishParams_t; /*-----------------------------------------------------------*/ /* Network functions used by the tests, declared and implemented in one of * the test network function files. */ -extern bool AwsIotTest_NetworkSetup( void ); -extern void AwsIotTest_NetworkCleanup( void ); +extern bool IotTest_NetworkSetup( void ); +extern void IotTest_NetworkCleanup( void ); /* Extern declarations of default serializer functions. The internal MQTT header cannot * be included by this file. */ -extern AwsIotMqttError_t AwsIotMqttInternal_SerializePingreq( uint8_t ** const pPingreqPacket, - size_t * const pPacketSize ); -extern void AwsIotMqttInternal_FreePacket( uint8_t * pPacket ); +extern IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ); +extern void _IotMqtt_FreePacket( uint8_t * pPacket ); /* Network variables used by the tests, declared in one of the test network * function files. */ -extern AwsIotMqttNetIf_t _AwsIotTestNetworkInterface; -extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; +extern IotMqttNetIf_t _IotTestNetworkInterface; +extern IotMqttConnection_t _IotTestMqttConnection; /*-----------------------------------------------------------*/ @@ -192,14 +196,14 @@ static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; */ static const char * const _pTopicNames[ _TEST_TOPIC_NAME_COUNT ] = { - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/1", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/2", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/3", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/4", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/5", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/6", - AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/7" + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/1", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/2", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/3", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/4", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/5", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/6", + IOT_TEST_MQTT_TOPIC_PREFIX "/stress/7" }; /** @@ -207,7 +211,7 @@ static const char * const _pTopicNames[ _TEST_TOPIC_NAME_COUNT ] = * * For convenience, all topic names are the same length. */ -static const uint16_t _topicNameLength = ( uint16_t ) sizeof( AWS_IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0" ) - 1; +static const uint16_t _topicNameLength = ( uint16_t ) sizeof( IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0" ) - 1; /** * @brief Counts how many subscriptions were received for each test. @@ -231,52 +235,51 @@ static bool _pingreqOverrideCalled = false; /** * @brief Serializer override for PINGREQ. */ -static AwsIotMqttError_t _serializePingreq( uint8_t ** const pPingreqPacket, - size_t * const pPacketSize ) +static IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ) { _pingreqOverrideCalled = true; - return AwsIotMqttInternal_SerializePingreq( pPingreqPacket, - pPacketSize ); + return _IotMqtt_SerializePingreq( pPingreqPacket, pPacketSize ); } /*-----------------------------------------------------------*/ /** - * @brief Checks that #_AwsIotTestMqttConnection is still usable by sending a PUBLISH. + * @brief Checks that #_IotTestMqttConnection is still usable by sending a PUBLISH. * * @return The result of the PUBLISH. */ -static AwsIotMqttError_t _checkConnection( void ) +static IotMqttError_t _checkConnection( void ) { - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; /* Set the publish info. */ - publishInfo.QoS = 1; + publishInfo.qos = IOT_MQTT_QOS_1; publishInfo.pTopicName = _pTopicNames[ 0 ]; publishInfo.topicNameLength = _topicNameLength; publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryMs = AWS_IOT_TEST_MQTT_RETRY_MS; - publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; + publishInfo.retryMs = IOT_TEST_MQTT_RETRY_MS; + publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; /* Send a PUBLISH. */ - status = AwsIotMqtt_Publish( _AwsIotTestMqttConnection, - &publishInfo, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishRef ); + status = IotMqtt_Publish( _IotTestMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishRef ); - if( status != AWS_IOT_MQTT_STATUS_PENDING ) + if( status != IOT_MQTT_STATUS_PENDING ) { return status; } /* Return the result of the PUBLISH. */ - return AwsIotMqtt_Wait( publishRef, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ); + return IotMqtt_Wait( publishRef, + IOT_TEST_MQTT_TIMEOUT_MS ); } /*-----------------------------------------------------------*/ @@ -285,7 +288,7 @@ static AwsIotMqttError_t _checkConnection( void ) * @brief Subscription callback function. */ static void _publishReceived( void * pArgument, - AwsIotMqttCallbackParam_t * const pPublish ) + IotMqttCallbackParam_t * pPublish ) { ( void ) pArgument; @@ -294,7 +297,7 @@ static void _publishReceived( void * pArgument, if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && ( strncmp( _pSamplePayload, pPublish->message.info.pPayload, _samplePayloadLength ) == 0 ) && ( pPublish->message.info.topicNameLength == _topicNameLength ) && - ( pPublish->message.info.QoS == 1 ) ) + ( pPublish->message.info.qos == IOT_MQTT_QOS_1 ) ) { IotSemaphore_Post( &receivedPublishCounter ); } @@ -312,10 +315,10 @@ static void _publishReceived( void * pArgument, * @brief Callback function that blocks for a long time. */ static void _blockingCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const param ) + IotMqttCallbackParam_t * param ) { IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - const unsigned blockTime = 5 * AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + const unsigned blockTime = 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; ( void ) param; @@ -334,9 +337,9 @@ static void _blockingCallback( void * pArgument, static void * _publishThread( void * pArgument ) { unsigned i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _publishParams_t * pParams = ( _publishParams_t * ) pArgument; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; const struct timespec sleepTime = { .tv_sec = 0, @@ -344,12 +347,12 @@ static void * _publishThread( void * pArgument ) }; /* Set the publish info. */ - publishInfo.QoS = 1; + publishInfo.qos = IOT_MQTT_QOS_1; publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; publishInfo.topicNameLength = _topicNameLength; - publishInfo.retryMs = AWS_IOT_TEST_MQTT_RETRY_MS; - publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; + publishInfo.retryMs = IOT_TEST_MQTT_RETRY_MS; + publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; for( i = 0; i < pParams->publishLimit; ) { @@ -357,26 +360,26 @@ static void * _publishThread( void * pArgument ) publishInfo.pTopicName = _pTopicNames[ i % _TEST_TOPIC_NAME_COUNT ]; /* PUBLISH the message. */ - status = AwsIotMqtt_Publish( _AwsIotTestMqttConnection, - &publishInfo, - 0, - NULL, - NULL ); + status = IotMqtt_Publish( _IotTestMqttConnection, + &publishInfo, + 0, + NULL, + NULL ); /* The stress tests may exhaust all memory available to the MQTT library. * If no memory is available, wait some time for resources to be released. */ - if( status == AWS_IOT_MQTT_NO_MEMORY ) + if( status == IOT_MQTT_NO_MEMORY ) { IotLogInfo( "Thread %d: no memory available on PUBLISH %d." " Sleeping for %d seconds.", pParams->threadNumber, i, - AWS_IOT_TEST_MQTT_DECONGEST_S ); - sleep( AWS_IOT_TEST_MQTT_DECONGEST_S ); + IOT_TEST_MQTT_DECONGEST_S ); + sleep( IOT_TEST_MQTT_DECONGEST_S ); continue; } /* If the PUBLISH failed, exit this thread. */ - else if( status != AWS_IOT_MQTT_STATUS_PENDING ) + else if( status != IOT_MQTT_STATUS_PENDING ) { IotLogError( "Thread %d encountered error %d publishing message %d.", status, i ); @@ -399,7 +402,7 @@ static void * _publishThread( void * pArgument ) if( nanosleep( &sleepTime, NULL ) != 0 ) { IotLogError( "Error in nanosleep." ); - status = AWS_IOT_MQTT_BAD_RESPONSE; + status = IOT_MQTT_BAD_RESPONSE; break; } } @@ -425,8 +428,8 @@ TEST_GROUP( MQTT_Stress ); TEST_SETUP( MQTT_Stress ) { int i = 0; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; const IotLogConfig_t logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; /* Clear the PINGREQ override flag. */ @@ -441,37 +444,37 @@ TEST_SETUP( MQTT_Stress ) _MAX_RECEIVED_PUBLISH ) ); /* Set the serializer overrides. */ - _AwsIotTestNetworkInterface.serialize.pingreq = _serializePingreq; - _AwsIotTestNetworkInterface.freePacket = AwsIotMqttInternal_FreePacket; + _IotTestNetworkInterface.serialize.pingreq = _serializePingreq; + _IotTestNetworkInterface.freePacket = _IotMqtt_FreePacket; /* Set up the network stack. */ - if( AwsIotTest_NetworkSetup() == false ) + if( IotTest_NetworkSetup() == false ) { TEST_FAIL_MESSAGE( "Failed to set up network connection." ); } /* Initialize the MQTT library. */ - if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ - #ifndef AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER + #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER ( void ) snprintf( _pClientIdentifier, _CLIENT_IDENTIFIER_MAX_LENGTH, - "aws%llu", + "iot%llu", ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( _pClientIdentifier, - AWS_IOT_TEST_MQTT_CLIENT_IDENTIFIER, + IOT_TEST_MQTT_CLIENT_IDENTIFIER, _CLIENT_IDENTIFIER_MAX_LENGTH ); #endif /* Set the members of the connect info. */ connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -480,24 +483,24 @@ TEST_SETUP( MQTT_Stress ) { pSubscriptions[ i ].pTopicFilter = _pTopicNames[ i ]; pSubscriptions[ i ].topicFilterLength = _topicNameLength; - pSubscriptions[ i ].QoS = 1; + pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; pSubscriptions[ i ].callback.function = _publishReceived; } /* Establish the MQTT connection. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ) ); /* Subscribe to the test topic filters. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotMqtt_TimedSubscribe( _AwsIotTestMqttConnection, - pSubscriptions, - _TEST_TOPIC_NAME_COUNT, - 0, - AWS_IOT_TEST_MQTT_TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + IotMqtt_TimedSubscribe( _IotTestMqttConnection, + pSubscriptions, + _TEST_TOPIC_NAME_COUNT, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ) ); } /*-----------------------------------------------------------*/ @@ -512,17 +515,17 @@ TEST_TEAR_DOWN( MQTT_Stress ) /* Disconnect the MQTT connection. Unsubscribe is not called; the subscriptions * should be cleaned up by Disconnect. */ - if( _AwsIotTestMqttConnection != AWS_IOT_MQTT_CONNECTION_INITIALIZER ) + if( _IotTestMqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) { - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, false ); } /* Clean up the network stack. */ - AwsIotTest_NetworkCleanup(); + IotTest_NetworkCleanup(); /* Clean up the MQTT library. */ - AwsIotMqtt_Cleanup(); - _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + IotMqtt_Cleanup(); + _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ @@ -544,7 +547,7 @@ TEST_GROUP_RUNNER( MQTT_Stress ) */ TEST( MQTT_Stress, KeepAlive ) { - const unsigned sleepTime = 5 * AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + const unsigned sleepTime = 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; /* Send no MQTT packets for a long time. The keep-alive must be used to keep * the connection open. */ @@ -553,7 +556,7 @@ TEST( MQTT_Stress, KeepAlive ) /* Send a PUBLISH to verify that the connection is still usable. */ IotLogInfo( "KeepAlive test checking MQTT connection." ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _checkConnection() ); /* Check that the PINGREQ override was used. */ TEST_ASSERT_EQUAL( true, _pingreqOverrideCalled ); @@ -566,20 +569,20 @@ TEST( MQTT_Stress, KeepAlive ) */ TEST( MQTT_Stress, BlockingCallback ) { - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; IotSemaphore_t waitSem; callbackInfo.function = _blockingCallback; callbackInfo.param1 = &waitSem; - publishInfo.QoS = 1; + publishInfo.qos = IOT_MQTT_QOS_1; publishInfo.pTopicName = _pTopicNames[ 0 ]; publishInfo.topicNameLength = _topicNameLength; publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryMs = AWS_IOT_TEST_MQTT_RETRY_MS; - publishInfo.retryLimit = AWS_IOT_TEST_MQTT_RETRY_LIMIT; + publishInfo.retryMs = IOT_TEST_MQTT_RETRY_MS; + publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -587,18 +590,18 @@ TEST( MQTT_Stress, BlockingCallback ) if( TEST_PROTECT() ) { /* Call a function that will invoke the blocking callback. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_STATUS_PENDING, - AwsIotMqtt_Publish( _AwsIotTestMqttConnection, - &publishInfo, - 0, - &callbackInfo, - NULL ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, + IotMqtt_Publish( _IotTestMqttConnection, + &publishInfo, + 0, + &callbackInfo, + NULL ) ); /* Wait for the callback to return, then check that the connection is * still usable. */ IotSemaphore_Wait( &waitSem ); IotLogInfo( "BlockingCallback test checking MQTT connection." ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, _checkConnection() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _checkConnection() ); } IotSemaphore_Destroy( &waitSem ); @@ -616,21 +619,21 @@ TEST( MQTT_Stress, BlockingCallback ) TEST( MQTT_Stress, ClientClosesConnection ) { int i = 0, threadsCreated = 0, threadsJoined = 0; - pthread_t publishThreads[ AWS_IOT_TEST_MQTT_THREADS ] = { 0 }; - _publishParams_t publishThreadParams[ AWS_IOT_TEST_MQTT_THREADS ] = { 0 }; + pthread_t publishThreads[ IOT_TEST_MQTT_THREADS ] = { 0 }; + _publishParams_t publishThreadParams[ IOT_TEST_MQTT_THREADS ] = { 0 }; /* Set the parameters for each thread. */ - for( i = 0; i < AWS_IOT_TEST_MQTT_THREADS; i++ ) + for( i = 0; i < IOT_TEST_MQTT_THREADS; i++ ) { publishThreadParams[ i ].threadNumber = i; publishThreadParams[ i ].publishPeriodNs = 500000000; - publishThreadParams[ i ].publishLimit = AWS_IOT_TEST_MQTT_PUBLISHES_PER_THREAD; + publishThreadParams[ i ].publishLimit = IOT_TEST_MQTT_PUBLISHES_PER_THREAD; } IotLogInfo( "ClientClosesConnection test creating threads." ); /* Spawn the threads for the test. */ - for( i = 0; i < AWS_IOT_TEST_MQTT_THREADS; i++ ) + for( i = 0; i < IOT_TEST_MQTT_THREADS; i++ ) { if( pthread_create( &( publishThreads[ i ] ), NULL, diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c new file mode 100644 index 0000000000..c1cb5cae80 --- /dev/null +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -0,0 +1,1182 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_mqtt_system.c + * @brief Full system tests for the MQTT library. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER + +/* Test framework includes. */ +#include "unity_fixture.h" + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Including stdio.h also brings in unwanted (and conflicting) symbols on some + * platforms. Therefore, any functions in stdio.h needed in this file have an + * extern declaration here. */ +extern int snprintf( char *, + size_t, + const char *, + ... ); +/** @endcond */ + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef IOT_TEST_MQTT_TIMEOUT_MS + #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) +#endif +#ifndef IOT_TEST_MQTT_TOPIC_PREFIX + #define IOT_TEST_MQTT_TOPIC_PREFIX "iotmqtttest" +#endif +/** @endcond */ + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef IOT_MQTT_CONNECT_INFO_INITIALIZER + #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/** + * @brief The maximum length of an MQTT client identifier. + * + * When @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER is defined, this value must + * accommodate the length of @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER plus 4 + * to accommodate the Last Will and Testament test. Otherwise, this value is + * set to 24, which is the longest client identifier length an MQTT server is + * obligated to accept plus a NULL terminator. + */ +#ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) +#else + #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Parameter 1 of #_operationComplete. + */ +typedef struct _operationCompleteParams +{ + IotMqttOperationType_t expectedOperation; /**< @brief Expected completed operation. */ + IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ + IotMqttReference_t reference; /**< @brief Reference to expected completed operation. */ +} _operationCompleteParams_t; + +/*-----------------------------------------------------------*/ + +/* Network functions used by the tests, declared and implemented in one of + * the test network function files. */ +extern bool IotTest_NetworkSetup( void ); +extern void IotTest_NetworkCleanup( void ); +extern bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, + IotMqttConnection_t * pMqttConnection ); +extern IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ); +extern void IotTest_NetworkDestroy( void * pConnection ); + +/* Network variables used by the tests, declared in one of the test network + * function files. */ +extern IotMqttNetIf_t _IotTestNetworkInterface; +extern IotMqttConnection_t _IotTestMqttConnection; + +/*-----------------------------------------------------------*/ + +/** + * @brief Filler text to publish. + */ +static const char _pSamplePayload[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" + " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" + " culpa qui officia deserunt mollit anim id est laborum."; + +/** + * @brief Length of #_pSamplePayload. + */ +static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; + +/** + * @brief Buffer holding the client identifier used for the tests. + */ +static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + +/* + * Track if the serializer overrides were called for a test. + */ +static bool _freePacketOverride = false; /**< @brief Tracks if #_freePacket was called. */ +static bool _connectSerializerOverride = false; /**< @brief Tracks if #_connectSerializerOverride was called. */ +static bool _publishSerializerOverride = false; /**< @brief Tracks if #_publishSerializerOverride was called. */ +static bool _pubackSerializerOverride = false; /**< @brief Tracks if #_pubackSerializerOverride was called. */ +static bool _subscribeSerializerOverride = false; /**< @brief Tracks if #_subscribeSerializerOverride was called. */ +static bool _unsubscribeSerializerOverride = false; /**< @brief Tracks if #_unsubscribeSerializerOverride was called. */ +static bool _disconnectSerializerOverride = false; /**< @brief Tracks if #_disconnectSerializerOverride was called. */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Packet free function override + */ +static void _freePacket( uint8_t * pPacket ) +{ + _freePacketOverride = true; + + _IotMqtt_FreePacket( pPacket ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for CONNECT. + */ +static IotMqttError_t _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ) +{ + _connectSerializerOverride = true; + + return _IotMqtt_SerializeConnect( pConnectInfo, + pConnectPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for PUBLISH. + */ +static IotMqttError_t _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + _publishSerializerOverride = true; + + return _IotMqtt_SerializePublish( pPublishInfo, + pPublishPacket, + pPacketSize, + pPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for PUBACK. + */ +static IotMqttError_t _serializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) +{ + _pubackSerializerOverride = true; + + return _IotMqtt_SerializePuback( packetIdentifier, + pPubackPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for SUBSCRIBE. + */ +static IotMqttError_t _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + _subscribeSerializerOverride = true; + + return _IotMqtt_SerializeSubscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for UNSUBSCRIBE. + */ +static IotMqttError_t _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + _unsubscribeSerializerOverride = true; + + return _IotMqtt_SerializeUnsubscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializer override for DISCONNECT. + */ +static IotMqttError_t _serializeDisconnect( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ) +{ + _disconnectSerializerOverride = true; + + return _IotMqtt_SerializeDisconnect( pDisconnectPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscription callback function. Checks for valid parameters and unblocks + * the main test thread. + */ +static void _publishReceived( void * pArgument, + IotMqttCallbackParam_t * pPublish ) +{ + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; + + /* If the received messages matches what was sent, unblock the waiting thread. */ + if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && + ( strncmp( pPublish->message.info.pPayload, + _pSamplePayload, + _samplePayloadLength ) == 0 ) ) + { + IotSemaphore_Post( pWaitSem ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Operation completion callback function. Checks for valid parameters + * and unblocks the main test thread. + */ +static void _operationComplete( void * pArgument, + IotMqttCallbackParam_t * pOperation ) +{ + _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; + + /* If the operation information matches the parameters and the operation was + * successful, unblock the waiting thread. */ + if( ( pParams->expectedOperation == pOperation->operation.type ) && + ( pParams->reference == pOperation->operation.reference ) && + ( pOperation->operation.result == IOT_MQTT_SUCCESS ) ) + { + IotSemaphore_Post( &( pParams->waitSem ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Callback that makes additional MQTT API calls. + */ +static void _reentrantCallback( void * pArgument, + IotMqttCallbackParam_t * pOperation ) +{ + bool status = true; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotSemaphore_t * pWaitSemaphores = ( IotSemaphore_t * ) pArgument; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + + /* Topic used in this test. */ + const char * const pTopic = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; + const uint16_t topicLength = ( uint16_t ) strlen( pTopic ); + + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = pTopic; + publishInfo.topicNameLength = topicLength; + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryLimit = 3; + publishInfo.retryMs = 5000; + + mqttStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + + if( mqttStatus == IOT_MQTT_SUCCESS ) + { + status = IotSemaphore_TimedWait( &( pWaitSemaphores[ 0 ] ), + IOT_TEST_MQTT_TIMEOUT_MS ); + } + else + { + status = false; + } + + /* Remove subscription and disconnect. */ + if( status == true ) + { + subscription.pTopicFilter = pTopic; + subscription.topicFilterLength = topicLength; + + mqttStatus = IotMqtt_Unsubscribe( pOperation->mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeRef ); + + if( mqttStatus == IOT_MQTT_STATUS_PENDING ) + { + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( pOperation->mqttConnection, false ); + + /* Waiting on an operation whose connection is closed should return + * "Network Error". */ + mqttStatus = IotMqtt_Wait( unsubscribeRef, + 500 ); + + status = ( mqttStatus == IOT_MQTT_NETWORK_ERROR ); + } + else + { + status = false; + } + } + + /* Disconnect and unblock the main test thread. */ + if( status == true ) + { + IotSemaphore_Post( &( pWaitSemaphores[ 1 ] ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Run the subscribe-publish-wait tests at various QoS. + */ +static void _subscribePublishWait( IotMqttQos_t qos ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttNetIf_t networkInterface = _IotTestNetworkInterface; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotSemaphore_t waitSem; + + /* Set the serializer overrides. */ + networkInterface.freePacket = _freePacket; + networkInterface.serialize.connect = _serializeConnect; + networkInterface.serialize.publish = _serializePublish; + networkInterface.serialize.puback = _serializePuback; + networkInterface.serialize.subscribe = _serializeSubscribe; + networkInterface.serialize.unsubscribe = _serializeUnsubscribe; + networkInterface.serialize.disconnect = _serializeDisconnect; + + /* Create the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the members of the MQTT connection info. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + /* Establish the MQTT connection. */ + status = IotMqtt_Connect( &_IotTestMqttConnection, + &networkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Set the members of the subscription. */ + subscription.qos = qos; + subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.function = _publishReceived; + subscription.callback.param1 = &waitSem; + + /* Subscribe to the test topic filter using the blocking SUBSCRIBE + * function. */ + status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, + &subscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Set the members of the publish info. */ + publishInfo.qos = qos; + publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + + /* Publish the message. */ + status = IotMqtt_TimedPublish( _IotTestMqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + + /* Wait for the message to be received. */ + if( IotSemaphore_TimedWait( &waitSem, + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); + } + + /* Unsubscribe from the test topic filter. */ + status = IotMqtt_TimedUnsubscribe( _IotTestMqttConnection, + &subscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + } + + /* Close the MQTT connection. */ + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + } + + IotSemaphore_Destroy( &waitSem ); + + /* Check that the serializer overrides were called. */ + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, _freePacketOverride ); + TEST_ASSERT_EQUAL_INT( true, _connectSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _publishSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _subscribeSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _unsubscribeSerializerOverride ); + TEST_ASSERT_EQUAL_INT( true, _disconnectSerializerOverride ); + + if( qos == IOT_MQTT_QOS_1 ) + { + TEST_ASSERT_EQUAL_INT( true, _pubackSerializerOverride ); + } + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT system tests. + */ +TEST_GROUP( MQTT_System ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT system tests. + */ +TEST_SETUP( MQTT_System ) +{ + /* Clear the serializer override flags. */ + _freePacketOverride = false; + _connectSerializerOverride = false; + _publishSerializerOverride = false; + _pubackSerializerOverride = false; + _subscribeSerializerOverride = false; + _unsubscribeSerializerOverride = false; + _disconnectSerializerOverride = false; + + /* Initialize the MQTT library. */ + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } + + /* Set up the network stack. */ + if( IotTest_NetworkSetup() == false ) + { + TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + } + + /* Generate a new, unique client identifier based on the time if no client + * identifier is defined. Otherwise, copy the provided client identifier. */ + #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER + ( void ) snprintf( _pClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "iot%llu", + ( long long unsigned int ) IotClock_GetTimeMs() ); + #else + ( void ) strncpy( _pClientIdentifier, + IOT_TEST_MQTT_CLIENT_IDENTIFIER, + _CLIENT_IDENTIFIER_MAX_LENGTH ); + #endif +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT system tests. + */ +TEST_TEAR_DOWN( MQTT_System ) +{ + /* Clean up the MQTT library. */ + IotMqtt_Cleanup(); + _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + + /* Clean up the network stack. */ + IotTest_NetworkCleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT system tests. + */ +TEST_GROUP_RUNNER( MQTT_System ) +{ + RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS0 ); + RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS1 ); + RUN_TEST_CASE( MQTT_System, SubscribePublishAsync ); + RUN_TEST_CASE( MQTT_System, LastWillAndTestament ); + RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); + RUN_TEST_CASE( MQTT_System, WaitAfterDisconnect ); + RUN_TEST_CASE( MQTT_System, SubscribeCompleteReentrancy ); + RUN_TEST_CASE( MQTT_System, IncomingPublishReentrancy ) +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscribe-publish-wait (QoS 0). + */ +TEST( MQTT_System, SubscribePublishWaitQoS0 ) +{ + _subscribePublishWait( IOT_MQTT_QOS_0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscribe-publish-wait (QoS 1). + */ +TEST( MQTT_System, SubscribePublishWaitQoS1 ) +{ + _subscribePublishWait( IOT_MQTT_QOS_1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Subscribe-publish asynchronous (QoS 1). + */ +TEST( MQTT_System, SubscribePublishAsync ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + _operationCompleteParams_t callbackParam = { 0 }; + IotSemaphore_t publishWaitSem; + + /* Initialize members of the operation callback info. */ + callbackInfo.function = _operationComplete; + callbackInfo.param1 = &callbackParam; + + /* Initialize members of the subscription. */ + subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.function = _publishReceived; + subscription.callback.param1 = &publishWaitSem; + + /* Initialize members of the connect info. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + /* Initialize members of the publish info. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + + /* Create the wait semaphore for operations. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Create the wait semaphore for subscriptions. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &publishWaitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Establish the MQTT connection. */ + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Subscribe to the test topic filter. */ + callbackParam.expectedOperation = IOT_MQTT_SUBSCRIBE; + status = IotMqtt_Subscribe( _IotTestMqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for SUBSCRIBE to complete." ); + } + + /* Publish the message. */ + callbackParam.expectedOperation = IOT_MQTT_PUBLISH_TO_SERVER; + status = IotMqtt_Publish( _IotTestMqttConnection, + &publishInfo, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for PUBLISH to complete." ); + } + + /* Wait for the message to be received. */ + if( IotSemaphore_TimedWait( &publishWaitSem, + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); + } + + /* Unsubscribe from the test topic filter. */ + callbackParam.expectedOperation = IOT_MQTT_UNSUBSCRIBE; + status = IotMqtt_Unsubscribe( _IotTestMqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + &( callbackParam.reference ) ); + + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for UNSUBSCRIBE to complete." ); + } + } + + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + } + + IotSemaphore_Destroy( &publishWaitSem ); + } + + IotSemaphore_Destroy( &( callbackParam.waitSem ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that a LWT is published if an MQTT connection is unexpectedly closed. + */ +TEST( MQTT_System, LastWillAndTestament ) +{ + bool lwtListenerCreated = false; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttNetIf_t lwtNetIf = _IotTestNetworkInterface; + char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + IotMqttConnection_t lwtListener = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttConnectInfo_t lwtConnectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER, + connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t willSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotTestNetworkConnection_t lwtListenerConnection = IOT_TEST_NETWORK_CONNECTION_INITIALIZER; + IotSemaphore_t waitSem; + + /* Create the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); + + /* Generate a client identifier for LWT listener. */ + #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER + ( void ) snprintf( pLwtListenerClientIdentifier, + _CLIENT_IDENTIFIER_MAX_LENGTH, + "iotlwt%llu", + ( long long unsigned int ) IotClock_GetTimeMs() ); + #else + ( void ) strncpy( pLwtListenerClientIdentifier, + IOT_TEST_MQTT_CLIENT_IDENTIFIER "LWT", + _CLIENT_IDENTIFIER_MAX_LENGTH ); + #endif + + if( TEST_PROTECT() ) + { + /* Establish an independent MQTT over TCP connection to receive a Last + * Will and Testament message. */ + TEST_ASSERT_EQUAL( true, + IotTest_NetworkConnect( &lwtListenerConnection, + &lwtListener ) ); + lwtListenerCreated = true; + + if( TEST_PROTECT() ) + { + lwtNetIf.pDisconnectContext = &lwtListenerConnection; + lwtNetIf.pSendContext = &lwtListenerConnection; + lwtConnectInfo.cleanSession = true; + lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; + lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); + + status = IotMqtt_Connect( &lwtListener, + &lwtNetIf, + &lwtConnectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Register a subscription for the LWT. */ + willSubscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); + willSubscription.callback.function = _publishReceived; + willSubscription.callback.param1 = &waitSem; + + status = IotMqtt_TimedSubscribe( lwtListener, + &willSubscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Create a connection that requests the LWT. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + connectInfo.pWillInfo = &willInfo; + + willInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); + willInfo.pPayload = _pSamplePayload; + willInfo.payloadLength = _samplePayloadLength; + + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Abruptly close the MQTT connection. This should cause the LWT + * to be sent to the LWT listener. */ + IotTest_NetworkClose( NULL ); + IotMqtt_Disconnect( _IotTestMqttConnection, true ); + IotTest_NetworkDestroy( NULL ); + + /* Check that the LWT was received. */ + if( IotSemaphore_TimedWait( &waitSem, + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); + } + } + + IotMqtt_Disconnect( lwtListener, false ); + IotTest_NetworkDestroy( &lwtListenerConnection ); + lwtListenerCreated = false; + } + + if( lwtListenerCreated == true ) + { + IotTest_NetworkClose( &lwtListenerConnection ); + IotTest_NetworkDestroy( &lwtListenerConnection ); + } + } + + IotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that subscriptions from a previous session are restored. + */ +TEST( MQTT_System, RestorePreviousSession ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotSemaphore_t waitSem; + + /* Create the wait semaphore for operations. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); + + /* Set the members of the connection info for a persistent session. */ + connectInfo.cleanSession = false; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + if( TEST_PROTECT() ) + { + /* Establish a persistent MQTT connection. */ + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Add a subscription. */ + subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.param1 = &waitSem; + subscription.callback.function = _publishReceived; + + status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, + &subscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Disconnect the MQTT connection and clean up network connection. */ + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotTest_NetworkCleanup(); + + /* Re-establish the network connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_NetworkSetup() ); + + /* Re-establish the MQTT connection with a previous session. */ + connectInfo.cleanSession = false; + connectInfo.pPreviousSubscriptions = &subscription; + connectInfo.previousSubscriptionCount = 1; + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Publish a message to the subscription added in the previous session. */ + publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + + status = IotMqtt_TimedPublish( _IotTestMqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Wait for the message to be received. */ + if( IotSemaphore_TimedWait( &waitSem, + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for message." ); + } + + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotTest_NetworkCleanup(); + } + else + { + /* Close network connection on test failure. */ + IotTest_NetworkClose( NULL ); + } + + IotSemaphore_Destroy( &waitSem ); + + if( TEST_PROTECT() ) + { + /* Re-establish the network connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_NetworkSetup() ); + + /* After this test is finished, establish one more connection with a clean + * session to clean up persistent sessions on the MQTT server created by this + * test. */ + connectInfo.cleanSession = true; + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that Wait can be safely invoked after Disconnect. + */ +TEST( MQTT_System, WaitAfterDisconnect ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttReference_t pPublishRef[ 3 ] = { IOT_MQTT_REFERENCE_INITIALIZER }; + + /* Set the client identifier and length. */ + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + /* Set the members of the publish info. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/WaitAfterDisconnect"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryLimit = 3; + publishInfo.retryMs = 5000; + + /* Establish the MQTT connection. */ + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + if( TEST_PROTECT() ) + { + /* Publish a sequence of messages. */ + for( i = 0; i < 3; i++ ) + { + status = IotMqtt_Publish( _IotTestMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &( pPublishRef[ i ] ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); + } + } + + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + + if( TEST_PROTECT() ) + { + /* Waiting on operations after a connection is disconnected should not crash. + * The actual statuses of the PUBLISH operations may vary depending on the + * timing of publish versus disconnect, so the statuses are not checked. */ + for( i = 0; i < 3; i++ ) + { + status = IotMqtt_Wait( pPublishRef[ i ], 100 ); + } + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that API functions can be invoked from a callback for a completed + * subscription operation. + */ +TEST( MQTT_System, SubscribeCompleteReentrancy ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Two semaphores are needed for this test: one for incoming PUBLISH and one + * for test completion. */ + IotSemaphore_t pWaitSemaphores[ 2 ]; + + /* Create the semaphores. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + if( TEST_PROTECT() ) + { + /* Establish the MQTT connection. */ + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Subscribe with a completion callback. */ + subscription.qos = 1; + subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.function = _publishReceived; + subscription.callback.param1 = &( pWaitSemaphores[ 0 ] ); + + callbackInfo.function = _reentrantCallback; + callbackInfo.param1 = pWaitSemaphores; + + status = IotMqtt_Subscribe( _IotTestMqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); + + /* Wait for the reentrant callback to complete. */ + if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); + } + } + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that API functions can be invoked from a callback for an incoming + * PUBLISH. + */ +TEST( MQTT_System, IncomingPublishReentrancy ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t pSubscription[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Two semaphores are needed for this test: one for incoming PUBLISH and one + * for test completion. */ + IotSemaphore_t pWaitSemaphores[ 2 ]; + + /* Create the semaphores. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + if( TEST_PROTECT() ) + { + /* Establish the MQTT connection. */ + status = IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Subscribe with to the test topics. */ + pSubscription[ 0 ].qos = IOT_MQTT_QOS_1; + pSubscription[ 0 ].pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/IncomingPublishReentrancy"; + pSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 0 ].pTopicFilter ); + pSubscription[ 0 ].callback.function = _reentrantCallback; + pSubscription[ 0 ].callback.param1 = pWaitSemaphores; + + pSubscription[ 1 ].qos = IOT_MQTT_QOS_1; + pSubscription[ 1 ].pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; + pSubscription[ 1 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 1 ].pTopicFilter ); + pSubscription[ 1 ].callback.function = _publishReceived; + pSubscription[ 1 ].callback.param1 = &( pWaitSemaphores[ 0 ] ); + + status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, + pSubscription, + 2, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Publish a message to the test topic. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/IncomingPublishReentrancy"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryLimit = 3; + publishInfo.retryMs = 5000; + + status = IotMqtt_TimedPublish( _IotTestMqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Wait for the reentrant callback to complete. */ + if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); + } + } + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c b/tests/mqtt/unit/aws_iot_tests_mqtt_api.c deleted file mode 100644 index 79c67e63cb..0000000000 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_api.c +++ /dev/null @@ -1,954 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_mqtt_api.c - * @brief Tests for the user-facing API functions (declared in aws_iot_mqtt.h). - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include - -/* POSIX includes. */ -#ifdef POSIX_UNISTD_HEADER - #include POSIX_UNISTD_HEADER -#else - #include -#endif - -/* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* MQTT test access include. */ -#include "aws_iot_test_access_mqtt.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true -#else - #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER - #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } -#endif - -/** - * @brief Timeout to use for the tests. This can be short, but should allow time - * for other threads to run. - */ -#define _TIMEOUT_MS ( 400 ) - -/* - * Client identifier and length to use for the MQTT API tests. - */ -#define _CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ -#define _CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( _CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ - -/* - * Will topic name and length to use for the MQTT API tests. - */ -#define _TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ -#define _TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ - -/** - * @brief A non-NULL function pointer to use for subscription callback. This - * "function" should cause a crash if actually called. - */ -#define _SUBSCRIPTION_CALLBACK \ - ( ( void ( * )( void *, \ - AwsIotMqttCallbackParam_t * const ) ) 0x01 ) - -/** - * @brief How many times #TEST_MQTT_Unit_API_DisconnectMallocFail_ will test - * malloc failures. - * - * The DISCONNECT function provides no mechanism to wait on its successful - * completion. Therefore, this function simply uses the value below as an estimate - * for the maximum number of times DISCONNECT will use malloc. - */ -#define _DISCONNECT_MALLOC_LIMIT ( 20 ) - -/* - * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. - */ -#define _DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ -#define _DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ -#define _DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. - * Duplicates are sent using an exponential backoff strategy. */ -/** @brief The minimum amount of time the test can take. */ -#define _DUP_CHECK_MINIMUM_WAIT \ - ( _DUP_CHECK_RETRY_MS + \ - 2 * _DUP_CHECK_RETRY_MS + \ - 4 * _DUP_CHECK_RETRY_MS + \ - AWS_IOT_MQTT_RESPONSE_WAIT_MS ) - -/** - * @brief Tracks whether #_publishSetDup has been called. - */ -static bool _publishSetDupCalled = false; - -/*-----------------------------------------------------------*/ - -/** - * @brief PUBLISH set DUP function override. - */ -static void _publishSetDup( bool awsIotMqttMode, - uint8_t * const pPublishPacket, - uint16_t * const pNewPacketIdentifier ) -{ - _publishSetDupCalled = true; - - AwsIotMqttInternal_PublishSetDup( awsIotMqttMode, - pPublishPacket, - pNewPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A send function that always "succeeds". - */ -static size_t _sendSuccess( void * pSendContext, - const uint8_t * const pMessage, - size_t messageLength ) -{ - ( void ) pSendContext; - ( void ) pMessage; - - /* This function just returns the message length to simulate a successful - * send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief This send function checks that a duplicate outgoing message differs from - * the original. - */ -static size_t _dupChecker( void * pSendContext, - const uint8_t * const pMessage, - size_t messageLength ) -{ - static int runCount = 0; - static bool status = true; - bool * pDupCheckResult = ( bool * ) pSendContext; - uint8_t publishFlags = *pMessage; - - /* Declare the remaining variables required to check packet identifier - * for the AWS IoT MQTT server. */ - #if _AWS_IOT_MQTT_SERVER == true - static uint16_t lastPacketIdentifier = 0; - uint16_t currentPacketIdentifier = 0; - size_t bytesProcessed = 0; - AwsIotMqttPublishInfo_t publishInfo = { 0 }; - #endif - - /* Ignore any MQTT packet that's not a PUBLISH. */ - if( ( publishFlags & 0xf0 ) != _MQTT_PACKET_TYPE_PUBLISH ) - { - return messageLength; - } - - runCount++; - - /* Check how many times this function has been called. */ - if( runCount == 1 ) - { - #if _AWS_IOT_MQTT_SERVER == true - /* Deserialize the PUBLISH to read the packet identifier. */ - if( AwsIotMqttInternal_DeserializePublish( pMessage, - messageLength, - &publishInfo, - &lastPacketIdentifier, - &bytesProcessed ) != AWS_IOT_MQTT_SUCCESS ) - { - status = false; - } - else - { - /* Ensure that all bytes of the PUBLISH were processed. */ - status = ( bytesProcessed == messageLength ); - } - #else /* if _AWS_IOT_MQTT_SERVER == true */ - /* DUP flag should not be set on this function's first run. */ - if( ( publishFlags & 0x08 ) == 0x08 ) - { - status = false; - } - #endif /* if _AWS_IOT_MQTT_SERVER == true */ - } - else - { - /* Only check the packet again if the previous run checks passed. */ - if( status == true ) - { - #if _AWS_IOT_MQTT_SERVER == true - /* Deserialize the PUBLISH to read the packet identifier. */ - if( AwsIotMqttInternal_DeserializePublish( pMessage, - messageLength, - &publishInfo, - ¤tPacketIdentifier, - &bytesProcessed ) != AWS_IOT_MQTT_SUCCESS ) - { - status = false; - } - else - { - /* Ensure that all bytes of the PUBLISH were processed. */ - status = ( bytesProcessed == messageLength ); - - /* Check that the packet identifier is different. */ - if( status == true ) - { - status = ( currentPacketIdentifier != lastPacketIdentifier ); - lastPacketIdentifier = currentPacketIdentifier; - } - } - #else /* if _AWS_IOT_MQTT_SERVER == true */ - /* DUP flag should be set when this function runs again. */ - if( ( publishFlags & 0x08 ) != 0x08 ) - { - status = false; - } - #endif /* if _AWS_IOT_MQTT_SERVER == true */ - } - - /* Write the check result on the last expected run of this function. */ - if( runCount == _DUP_CHECK_RETRY_LIMIT ) - { - *pDupCheckResult = status; - } - } - - /* Return the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A disconnect function that only reports whether it was invoked. - */ -static IotNetworkError_t _disconnect( int32_t reason, - void * pDisconnectContext ) -{ - bool * pDisconnectInvoked = ( bool * ) pDisconnectContext; - - ( void ) reason; - - /* This function just sets a flag testifying that it was invoked. */ - if( pDisconnectInvoked != NULL ) - { - *pDisconnectInvoked = true; - } - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT API tests. - */ -TEST_GROUP( MQTT_Unit_API ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT API tests. - */ -TEST_SETUP( MQTT_Unit_API ) -{ - _publishSetDupCalled = false; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT API tests. - */ -TEST_TEAR_DOWN( MQTT_Unit_API ) -{ - AwsIotMqtt_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT API tests. - */ -TEST_GROUP_RUNNER( MQTT_Unit_API ) -{ - RUN_TEST_CASE( MQTT_Unit_API, ConnectParameters ); - RUN_TEST_CASE( MQTT_Unit_API, ConnectMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, ConnectRestoreSessionMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, DisconnectMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0Parameters ); - RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0MallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, PublishQoS1 ); - RUN_TEST_CASE( MQTT_Unit_API, PublishDuplicates ); - RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); - RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, UnsubscribeMallocFail ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect with various - * invalid parameters. - */ -TEST( MQTT_Unit_API, ConnectParameters ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttPublishInfo_t willInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - - /* Check that the network interface is validated. */ - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - networkInterface.send = _sendSuccess; - - /* Check that the connection info is validated. */ - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; - - /* Connect with bad previous session subscription. */ - connectInfo.cleanSession = false; - connectInfo.pPreviousSubscriptions = &subscription; - connectInfo.previousSubscriptionCount = 1; - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - /* Connect with bad subscription count. */ - connectInfo.previousSubscriptionCount = 0; - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - connectInfo.previousSubscriptionCount = 1; - - /* Check that the will info is validated when it's provided. */ - connectInfo.pWillInfo = &willInfo; - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - willInfo.pTopicName = _TEST_TOPIC_NAME; - willInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; - - /* Check that a will message longer than 65535 is not allowed. */ - willInfo.pPayload = ""; - willInfo.payloadLength = 65536; - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - willInfo.payloadLength = 0; - - /* Check that passing a wait time of 0 returns immediately. */ - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - 0 ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, ConnectMallocFail ) -{ - int i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* Initialize parameters. */ - networkInterface.send = _sendSuccess; - connectInfo.keepAliveSeconds = 100; - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call CONNECT. Memory allocation will fail at various times during - * this call. */ - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - - /* If the return value is timeout, then all memory allocation succeeded - * and the loop can exit. The expected return value is timeout (and not - * success) because the receive callback is never invoked. */ - if( status == AWS_IOT_MQTT_TIMEOUT ) - { - break; - } - - /* If the return value isn't timeout, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect when memory - * allocation fails at various points for a persistent session. - */ -TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) -{ - int i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttConnection_t mqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - - /* Initialize parameters. */ - networkInterface.send = _sendSuccess; - connectInfo.cleanSession = false; - connectInfo.keepAliveSeconds = 100; - connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; - - connectInfo.pPreviousSubscriptions = &subscription; - connectInfo.previousSubscriptionCount = 1; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call CONNECT with a previous session. Memory allocation will fail at - * various times during this call. */ - status = AwsIotMqtt_Connect( &mqttConnection, - &networkInterface, - &connectInfo, - _TIMEOUT_MS ); - - /* If the return value is timeout, then all memory allocation succeeded - * and the loop can exit. The expected return value is timeout (and not - * success) because the receive callback is never invoked. */ - if( status == AWS_IOT_MQTT_TIMEOUT ) - { - break; - } - - /* If the return value isn't timeout, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_disconnect when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, DisconnectMallocFail ) -{ - int i = 0; - bool disconnectInvoked = false; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - _mqttConnection_t * pMqttConnection = NULL; - - /* Set the members of the network interface. */ - networkInterface.pDisconnectContext = &disconnectInvoked; - networkInterface.send = _sendSuccess; - networkInterface.disconnect = _disconnect; - - for( i = 0; i < _DISCONNECT_MALLOC_LIMIT; i++ ) - { - /* Allow unlimited use of malloc during connection initialization. */ - UnityMalloc_MakeMallocFailAfterCount( -1 ); - - /* Create a new MQTT connection. */ - pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); - - /* Set malloc to eventually fail. */ - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call DISCONNECT; this function should always perform cleanup regardless - * of memory allocation errors. */ - AwsIotMqtt_Disconnect( pMqttConnection, false ); - TEST_ASSERT_EQUAL_INT( true, disconnectInvoked ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) with various - * valid and invalid parameters. - */ -TEST( MQTT_Unit_API, PublishQoS0Parameters ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t mqttConnection = { 0 }; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttReference_t publishReference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Set a send function for the MQTT connection. */ - mqttConnection.network.send = _sendSuccess; - - /* Check that the publish info is validated. */ - status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; - - /* Check that a QoS 0 publish is refused if a notification is requested. */ - status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, AWS_IOT_MQTT_FLAG_WAITABLE, NULL, &publishReference ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, &callbackInfo, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - /* If valid parameters are passed, QoS 0 publish should always return success. */ - status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, 0, &publishReference ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); - - /* Allow time for the send thread to run and clean up the PUBLISH. QoS 0 - * PUBLISH provides no mechanism to wait on completion, so sleep is used. */ - sleep( 1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, PublishQoS0MallocFail ) -{ - int i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t mqttConnection = { 0 }; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Set a send function for the MQTT connection; also set the necessary - * members of publish info. */ - mqttConnection.network.send = _sendSuccess; - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call PUBLISH. Memory allocation will fail at various times during - * this call. */ - status = AwsIotMqtt_Publish( &mqttConnection, &publishInfo, 0, NULL, NULL ); - - /* Once PUBLISH succeeds, the loop can exit. */ - if( status == AWS_IOT_MQTT_SUCCESS ) - { - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - } - - /* Wait for any pending QoS 0 publishes to clean up. */ - sleep( 1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publish (QoS 1) with various - * invalid parameters. Also tests the behavior of @ref mqtt_function_publish - * (QoS 1) when memory allocation fails at various points. - */ -TEST( MQTT_Unit_API, PublishQoS1 ) -{ - int i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = NULL; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - AwsIotMqttCallbackInfo_t callbackInfo = AWS_IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.send = _sendSuccess; - pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); - - /* Set the publish info. */ - publishInfo.QoS = 1; - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; - - if( TEST_PROTECT() ) - { - /* Setting the waitable flag with no reference is not allowed. */ - status = AwsIotMqtt_Publish( pMqttConnection, - &publishInfo, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - /* Setting both the waitable flag and callback info is not allowed. */ - status = AwsIotMqtt_Publish( pMqttConnection, - &publishInfo, - AWS_IOT_MQTT_FLAG_WAITABLE, - &callbackInfo, - &publishRef ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - /* Check QoS 1 PUBLISH behavior with malloc failures. */ - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call PUBLISH. Memory allocation will fail at various times during - * this call. */ - status = AwsIotMqtt_Publish( pMqttConnection, - &publishInfo, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishRef ); - - /* If the PUBLISH succeeded, the loop can exit after waiting for the QoS - * 1 PUBLISH to be cleaned up. */ - if( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, AwsIotMqtt_Wait( publishRef, _TIMEOUT_MS ) ); - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - } - } - - /* Clean up MQTT connection. */ - AwsIotTestMqtt_destroyMqttConnection( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that duplicate QoS 1 PUBLISH packets are different from the - * original. - * - * For non-AWS IoT MQTT servers, checks that the DUP flag is set. For - * AWS IoT MQTT servers, checks that the packet identifier is different. - */ -TEST( MQTT_Unit_API, PublishDuplicates ) -{ - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - _mqttConnection_t * pMqttConnection = NULL; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; - AwsIotMqttReference_t publishRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - volatile bool dupCheckResult = false; - uint64_t startTime = 0; - - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.pSendContext = ( void * ) &dupCheckResult; - networkInterface.send = _dupChecker; - networkInterface.serialize.publishSetDup = _publishSetDup; - pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_EQUAL( NULL, pMqttConnection ); - - /* Set the publish info. */ - publishInfo.QoS = 1; - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; - publishInfo.pPayload = "test"; - publishInfo.payloadLength = 4; - publishInfo.retryMs = _DUP_CHECK_RETRY_MS; - publishInfo.retryLimit = _DUP_CHECK_RETRY_LIMIT; - - startTime = IotClock_GetTimeMs(); - - if( TEST_PROTECT() ) - { - /* Send a PUBLISH with retransmissions enabled. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_STATUS_PENDING, - AwsIotMqtt_Publish( pMqttConnection, - &publishInfo, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishRef ) ); - - /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is - * expected. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_RETRY_NO_RESPONSE, - AwsIotMqtt_Wait( publishRef, _DUP_CHECK_TIMEOUT ) ); - - /* Check the result of the DUP check. */ - TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); - - /* Check that at least the minimum wait time elapsed. */ - TEST_ASSERT_TRUE( startTime + _DUP_CHECK_MINIMUM_WAIT <= IotClock_GetTimeMs() ); - } - - /* Clean up MQTT connection. */ - AwsIotTestMqtt_destroyMqttConnection( pMqttConnection ); - - /* Check that the set DUP override was called. */ - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, _publishSetDupCalled ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_subscribe and - * @ref mqtt_function_unsubscribe with various invalid parameters. - */ -TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) -{ - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t mqttConnection = { 0 }; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttReference_t reference = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - - /* Check that subscription info is validated. */ - status = AwsIotMqtt_Subscribe( &mqttConnection, - &subscription, - 1, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &reference ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - status = AwsIotMqtt_Unsubscribe( &mqttConnection, - &subscription, - 1, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &reference ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; - - /* A reference must be provided for a waitable SUBSCRIBE. */ - status = AwsIotMqtt_Subscribe( &mqttConnection, - &subscription, - 1, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); - - status = AwsIotMqtt_Unsubscribe( &mqttConnection, - &subscription, - 1, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_subscribe when memory allocation - * fails at various points. - */ -TEST( MQTT_Unit_API, SubscribeMallocFail ) -{ - int i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t mqttConnection = { 0 }; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttReference_t subscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - - /* Set the necessary members of the MQTT connection and subscription. */ - mqttConnection.network.send = _sendSuccess; - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; - - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ), false ) ); - IotListDouble_Create( &( mqttConnection.subscriptionList ) ); - - if( TEST_PROTECT() ) - { - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call SUBSCRIBE. Memory allocation will fail at various times during - * this call. */ - status = AwsIotMqtt_Subscribe( &mqttConnection, - &subscription, - 1, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeRef ); - - /* If the SUBSCRIBE succeeded, the loop can exit after waiting for - * the SUBSCRIBE to be cleaned up. */ - if( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, AwsIotMqtt_Wait( subscribeRef, _TIMEOUT_MS ) ); - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - } - - /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( mqttConnection.subscriptionList ) ) ); - } - - IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), - AwsIotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_unsubscribe when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, UnsubscribeMallocFail ) -{ - int i = 0; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - _mqttConnection_t mqttConnection = { 0 }; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttReference_t unsubscribeRef = AWS_IOT_MQTT_REFERENCE_INITIALIZER; - - /* Set the necessary members of the MQTT connection and subscription. */ - mqttConnection.network.send = _sendSuccess; - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; - - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( mqttConnection.subscriptionMutex ), false ) ); - IotListDouble_Create( &( mqttConnection.subscriptionList ) ); - - if( TEST_PROTECT() ) - { - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call UNSUBSCRIBE. Memory allocation will fail at various times during - * this call. */ - status = AwsIotMqtt_Unsubscribe( &mqttConnection, - &subscription, - 1, - AWS_IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeRef ); - - /* If the UNSUBSCRIBE succeeded, the loop can exit after waiting for - * the UNSUBSCRIBE to be cleaned up. */ - if( status == AWS_IOT_MQTT_STATUS_PENDING ) - { - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_TIMEOUT, AwsIotMqtt_Wait( unsubscribeRef, _TIMEOUT_MS ) ); - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - } - - /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( mqttConnection.subscriptionList ) ) ); - } - - IotListDouble_RemoveAll( &( mqttConnection.subscriptionList ), - AwsIotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Destroy( &( mqttConnection.subscriptionMutex ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c new file mode 100644 index 0000000000..53742f162b --- /dev/null +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -0,0 +1,1384 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_mqtt_api.c + * @brief Tests for the user-facing API functions (declared in iot_mqtt.h). + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* POSIX includes. */ +#ifdef POSIX_TIME_HEADER + #include POSIX_TIME_HEADER +#else + #include +#endif +#ifdef POSIX_UNISTD_HEADER + #include POSIX_UNISTD_HEADER +#else + #include +#endif + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* MQTT test access include. */ +#include "iot_test_access_mqtt.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef IOT_MQTT_CONNECT_INFO_INITIALIZER + #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + +/** + * @brief Timeout to use for the tests. This can be short, but should allow time + * for other threads to run. + */ +#define _TIMEOUT_MS ( 400 ) + +/** + * @brief A short keep-alive interval to use for the keep-alive tests. It may be + * shorter than the minimum 1 second specified by the MQTT spec. + */ +#define _SHORT_KEEP_ALIVE_MS ( 100 ) + +/** + * @brief The number of times the periodic keep-alive should run. + */ +#define _KEEP_ALIVE_COUNT ( 10 ) + +/* + * Client identifier and length to use for the MQTT API tests. + */ +#define _CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define _CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( _CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ + +/* + * Will topic name and length to use for the MQTT API tests. + */ +#define _TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define _TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ + +/** + * @brief A non-NULL function pointer to use for subscription callback. This + * "function" should cause a crash if actually called. + */ +#define _SUBSCRIPTION_CALLBACK \ + ( ( void ( * )( void *, \ + IotMqttCallbackParam_t * ) ) 0x01 ) + +/** + * @brief How many times #TEST_MQTT_Unit_API_DisconnectMallocFail_ will test + * malloc failures. + * + * The DISCONNECT function provides no mechanism to wait on its successful + * completion. Therefore, this function simply uses the value below as an estimate + * for the maximum number of times DISCONNECT will use malloc. + */ +#define _DISCONNECT_MALLOC_LIMIT ( 20 ) + +/* + * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. + */ +#define _DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ +#define _DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ +#define _DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. + * Duplicates are sent using an exponential backoff strategy. */ +/** @brief The minimum amount of time the test can take. */ +#define _DUP_CHECK_MINIMUM_WAIT \ + ( _DUP_CHECK_RETRY_MS + \ + 2 * _DUP_CHECK_RETRY_MS + \ + 4 * _DUP_CHECK_RETRY_MS + \ + IOT_MQTT_RESPONSE_WAIT_MS ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Tracks whether #_publishSetDup has been called. + */ +static bool _publishSetDupCalled = false; + +/** + * @brief Counts how many time #_sendPingreq has been called. + */ +static int32_t _pingreqSendCount = 0; + +/*-----------------------------------------------------------*/ + +/** + * @brief A thread routine that simulates an incoming PINGRESP. + */ +static void _incomingPingresp( void * pArgument ) +{ + /* The sleep time calculation below does not work if the short keep-alive + * interval is 1 second or greater. */ + #if _SHORT_KEEP_ALIVE_MS >= 1000 + #error "_SHORT_KEEP_ALIVE_MS must be less than 1000." + #endif + + /* This test will not work if the response wait time is too small. */ + #if IOT_MQTT_RESPONSE_WAIT_MS < ( 2 * _SHORT_KEEP_ALIVE_MS + 100 ) + #error "IOT_MQTT_RESPONSE_WAIT_MS too small for keep-alive tests." + #endif + + static int32_t invokeCount = 0; + static uint64_t lastInvokeTime = 0; + uint64_t currentTime = IotClock_GetTimeMs(); + + /* A PINGRESP packet. */ + const uint8_t pPingresp[ 2 ] = { _MQTT_PACKET_TYPE_PINGRESP, 0x00 }; + + /* Sleep time of twice the keep-alive interval. */ + const struct timespec sleepTime = { .tv_sec = 0, .tv_nsec = 2 * _SHORT_KEEP_ALIVE_MS * 1000000 }; + + /* Increment invoke count for this function. */ + invokeCount++; + + /* Sleep to simulate the network round-trip time. */ + if( nanosleep( &sleepTime, NULL ) == 0 ) + { + /* Respond with a PINGRESP. */ + if( invokeCount <= _KEEP_ALIVE_COUNT ) + { + /* Log a status with Unity, as this test may take a while. */ + UnityPrint( "KeepAlivePeriodic " ); + UnityPrintNumber( ( UNITY_INT ) invokeCount ); + UnityPrint( " of " ); + UnityPrintNumber( ( UNITY_INT ) _KEEP_ALIVE_COUNT ); + UnityPrint( " DONE at " ); + UnityPrintNumber( ( UNITY_INT ) IotClock_GetTimeMs() ); + UnityPrint( " ms" ); + + if( invokeCount > 1 ) + { + UnityPrint( " (+" ); + UnityPrintNumber( ( UNITY_INT ) ( currentTime - lastInvokeTime ) ); + UnityPrint( " ms)." ); + } + else + { + UnityPrint( "." ); + } + + UNITY_PRINT_EOL(); + lastInvokeTime = currentTime; + + ( void ) IotMqtt_ReceiveCallback( pArgument, + NULL, + pPingresp, + 2, + 0, + NULL ); + } + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief PUBLISH set DUP function override. + */ +static void _publishSetDup( bool awsIotMqttMode, + uint8_t * pPublishPacket, + uint16_t * pNewPacketIdentifier ) +{ + _publishSetDupCalled = true; + + _IotMqtt_PublishSetDup( awsIotMqttMode, + pPublishPacket, + pNewPacketIdentifier ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A send function that always "succeeds". May report that it was invoked + * through a semaphore. + */ +static size_t _sendSuccess( void * pSendContext, + const uint8_t * pMessage, + size_t messageLength ) +{ + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pSendContext; + + /* Silence warnings about unused parameters. */ + ( void ) pMessage; + + /* Post to the wait semaphore if given. */ + if( pWaitSem != NULL ) + { + IotSemaphore_Post( pWaitSem ); + } + + /* This function returns the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A send function for PINGREQ that responds with a PINGRESP. + */ +static size_t _sendPingreq( void * pSendContext, + const uint8_t * pMessage, + size_t messageLength ) +{ + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pSendContext; + + /* Silence warnings about unused parameters. */ + ( void ) pMessage; + + /* Create a thread that responds with PINGRESP, then increment the PINGREQ + * send counter if successful. */ + if( Iot_CreateDetachedThread( _incomingPingresp, + &( pMqttConnection->network.pSendContext ), + IOT_THREAD_DEFAULT_PRIORITY, + IOT_THREAD_DEFAULT_STACK_SIZE ) == true ) + { + _pingreqSendCount++; + } + + /* This function returns the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A send function that delays. + */ +static size_t _sendDelay( void * pSendContext, + const uint8_t * pMessage, + size_t messageLength ) +{ + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pSendContext; + + /* Silence warnings about unused parameters. */ + ( void ) pMessage; + + /* Post to the wait semaphore. */ + IotSemaphore_Post( pWaitSem ); + + /* Delay for 2 seconds. */ + sleep( 2 ); + + /* This function returns the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief This send function checks that a duplicate outgoing message differs from + * the original. + */ +static size_t _dupChecker( void * pSendContext, + const uint8_t * pMessage, + size_t messageLength ) +{ + static int32_t runCount = 0; + static bool status = true; + bool * pDupCheckResult = ( bool * ) pSendContext; + uint8_t publishFlags = *pMessage; + + /* Declare the remaining variables required to check packet identifier + * for the AWS IoT MQTT server. */ + #if _AWS_IOT_MQTT_SERVER == true + static uint16_t lastPacketIdentifier = 0; + uint16_t currentPacketIdentifier = 0; + size_t bytesProcessed = 0; + IotMqttPublishInfo_t publishInfo = { 0 }; + #endif + + /* Ignore any MQTT packet that's not a PUBLISH. */ + if( ( publishFlags & 0xf0 ) != _MQTT_PACKET_TYPE_PUBLISH ) + { + return messageLength; + } + + runCount++; + + /* Check how many times this function has been called. */ + if( runCount == 1 ) + { + #if _AWS_IOT_MQTT_SERVER == true + /* Deserialize the PUBLISH to read the packet identifier. */ + if( _IotMqtt_DeserializePublish( pMessage, + messageLength, + &publishInfo, + &lastPacketIdentifier, + &bytesProcessed ) != IOT_MQTT_SUCCESS ) + { + status = false; + } + else + { + /* Ensure that all bytes of the PUBLISH were processed. */ + status = ( bytesProcessed == messageLength ); + } + #else /* if _AWS_IOT_MQTT_SERVER == true */ + /* DUP flag should not be set on this function's first run. */ + if( ( publishFlags & 0x08 ) == 0x08 ) + { + status = false; + } + #endif /* if _AWS_IOT_MQTT_SERVER == true */ + } + else + { + /* Only check the packet again if the previous run checks passed. */ + if( status == true ) + { + #if _AWS_IOT_MQTT_SERVER == true + /* Deserialize the PUBLISH to read the packet identifier. */ + if( _IotMqtt_DeserializePublish( pMessage, + messageLength, + &publishInfo, + ¤tPacketIdentifier, + &bytesProcessed ) != IOT_MQTT_SUCCESS ) + { + status = false; + } + else + { + /* Ensure that all bytes of the PUBLISH were processed. */ + status = ( bytesProcessed == messageLength ); + + /* Check that the packet identifier is different. */ + if( status == true ) + { + status = ( currentPacketIdentifier != lastPacketIdentifier ); + lastPacketIdentifier = currentPacketIdentifier; + } + } + #else /* if _AWS_IOT_MQTT_SERVER == true */ + /* DUP flag should be set when this function runs again. */ + if( ( publishFlags & 0x08 ) != 0x08 ) + { + status = false; + } + #endif /* if _AWS_IOT_MQTT_SERVER == true */ + } + + /* Write the check result on the last expected run of this function. */ + if( runCount == _DUP_CHECK_RETRY_LIMIT ) + { + *pDupCheckResult = status; + } + } + + /* Return the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A disconnect function that counts how many times it was invoked. + */ +static IotNetworkError_t _disconnect( void * pDisconnectContext ) +{ + int32_t * pDisconnectCount = ( int32_t * ) pDisconnectContext; + + /* Increment the counter for how many times this function was invoked. */ + if( pDisconnectCount != NULL ) + { + ( *pDisconnectCount )++; + } + + return IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A task pool job routine that decrements an MQTT operation's job + * reference count. + */ +static void _decrementReferencesJob( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * pContext ) +{ + _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; + + /* Silence warnings about unused parameters. */ + ( void ) pTaskPool; + ( void ) pJob; + + /* Decrement an operation's reference count. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == false ) + { + /* Unblock the main test thread. */ + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT API tests. + */ +TEST_GROUP( MQTT_Unit_API ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT API tests. + */ +TEST_SETUP( MQTT_Unit_API ) +{ + _publishSetDupCalled = false; + _pingreqSendCount = 0; + + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT API tests. + */ +TEST_TEAR_DOWN( MQTT_Unit_API ) +{ + IotMqtt_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT API tests. + */ +TEST_GROUP_RUNNER( MQTT_Unit_API ) +{ + RUN_TEST_CASE( MQTT_Unit_API, OperationCreateDestroy ); + RUN_TEST_CASE( MQTT_Unit_API, OperationWaitTimeout ); + RUN_TEST_CASE( MQTT_Unit_API, ConnectParameters ); + RUN_TEST_CASE( MQTT_Unit_API, ConnectMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, ConnectRestoreSessionMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, DisconnectMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0Parameters ); + RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0MallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, PublishQoS1 ); + RUN_TEST_CASE( MQTT_Unit_API, PublishDuplicates ); + RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); + RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, UnsubscribeMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, KeepAlivePeriodic ); + RUN_TEST_CASE( MQTT_Unit_API, KeepAliveJobCleanup ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test reference counts as MQTT operations are created and destroyed. + */ +TEST( MQTT_Unit_API, OperationCreateDestroy ) +{ + _mqttConnection_t * pMqttConnection = NULL; + _mqttOperation_t * pOperation = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + + /* Create a new MQTT connection with an empty network interface. */ + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Adjustment to reference count based on keep-alive status. */ + const int32_t keepAliveReference = 1 + ( ( pMqttConnection->keepAliveMs != 0 ) ? 1 : 0 ); + + /* A new MQTT connection should only have a possible reference for keep-alive. */ + TEST_ASSERT_EQUAL_INT32( keepAliveReference, pMqttConnection->references ); + + /* Create a new operation referencing the MQTT connection. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ) ); + + /* Check reference counts and list placement. */ + TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, pMqttConnection->references ); + TEST_ASSERT_EQUAL_INT32( 2, pOperation->jobReference ); + TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( pMqttConnection->pendingProcessing ), + NULL, + NULL, + &( pOperation->link ) ) ); + + /* Schedule a job that destroys the operation. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _decrementReferencesJob, + pOperation, + &( pOperation->job ) ) ); + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_Schedule( &( _IotMqttTaskPool ), + &( pOperation->job ) ) ); + + /* Wait for the job to complete. */ + IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); + + /* Check reference counts after job completion. */ + TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, pMqttConnection->references ); + TEST_ASSERT_EQUAL_INT32( 1, pOperation->jobReference ); + TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( pMqttConnection->pendingProcessing ), + NULL, + NULL, + &( pOperation->link ) ) ); + + /* Disconnect the MQTT connection, then call Wait to clean up the operation. */ + IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Wait( pOperation, 0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that an operation is correctly cleaned up if @ref mqtt_function_wait + * times out while its job is executing. + */ +TEST( MQTT_Unit_API, OperationWaitTimeout ) +{ + _mqttConnection_t * pMqttConnection = NULL; + _mqttOperation_t * pOperation = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotSemaphore_t waitSem; + + /* An arbitrary MQTT packet for this test. */ + uint8_t pPacket[ 2 ] = { _MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; + + /* Create the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.pSendContext = &waitSem; + networkInterface.send = _sendDelay; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Create a new operation referencing the MQTT connection. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ) ); + + /* Set an arbitrary MQTT packet for the operation. */ + pOperation->operation = IOT_MQTT_DISCONNECT; + pOperation->pMqttPacket = pPacket; + pOperation->packetSize = 2; + + /* Schedule the send job. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + 0 ) ); + + /* Wait for the send job to begin. */ + IotSemaphore_Wait( &waitSem ); + + /* Wait on the MQTT operation with a short timeout. This should cause a + * timeout while the send job is still executing. */ + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( pOperation, 10 ) ); + + /* Check reference count after a timed out wait. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + TEST_ASSERT_EQUAL_INT32( 1, pOperation->jobReference ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( pMqttConnection, true ); + + /* Clean up the MQTT library, which waits for the send job to finish. The + * library must be re-initialized so that test tear down does not crash. */ + IotMqtt_Cleanup(); + IotMqtt_Init(); + } + + IotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect with various + * invalid parameters. + */ +TEST( MQTT_Unit_API, ConnectParameters ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Check that the network interface is validated. */ + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + networkInterface.send = _sendSuccess; + + /* Check that the connection info is validated. */ + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + + /* Connect with bad previous session subscription. */ + connectInfo.cleanSession = false; + connectInfo.pPreviousSubscriptions = &subscription; + connectInfo.previousSubscriptionCount = 1; + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + /* Connect with bad subscription count. */ + connectInfo.previousSubscriptionCount = 0; + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + connectInfo.previousSubscriptionCount = 1; + + /* Check that the will info is validated when it's provided. */ + connectInfo.pWillInfo = &willInfo; + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + willInfo.pTopicName = _TEST_TOPIC_NAME; + willInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + /* Check that a will message longer than 65535 is not allowed. */ + willInfo.pPayload = ""; + willInfo.payloadLength = 65536; + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + willInfo.payloadLength = 0; + + /* Check that passing a wait time of 0 returns immediately. */ + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + 0 ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, ConnectMallocFail ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Initialize parameters. */ + networkInterface.send = _sendSuccess; + connectInfo.keepAliveSeconds = 100; + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call CONNECT. Memory allocation will fail at various times during + * this call. */ + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + + /* If the return value is timeout, then all memory allocation succeeded + * and the loop can exit. The expected return value is timeout (and not + * success) because the receive callback is never invoked. */ + if( status == IOT_MQTT_TIMEOUT ) + { + break; + } + + /* If the return value isn't timeout, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect when memory + * allocation fails at various points for a persistent session. + */ +TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Initialize parameters. */ + networkInterface.send = _sendSuccess; + connectInfo.cleanSession = false; + connectInfo.keepAliveSeconds = 100; + connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + connectInfo.pPreviousSubscriptions = &subscription; + connectInfo.previousSubscriptionCount = 1; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call CONNECT with a previous session. Memory allocation will fail at + * various times during this call. */ + status = IotMqtt_Connect( &mqttConnection, + &networkInterface, + &connectInfo, + _TIMEOUT_MS ); + + /* If the return value is timeout, then all memory allocation succeeded + * and the loop can exit. The expected return value is timeout (and not + * success) because the receive callback is never invoked. */ + if( status == IOT_MQTT_TIMEOUT ) + { + break; + } + + /* If the return value isn't timeout, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_disconnect when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, DisconnectMallocFail ) +{ + int32_t i = 0; + int32_t disconnectCount = 0; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + _mqttConnection_t * pMqttConnection = NULL; + + /* Set the members of the network interface. */ + networkInterface.pDisconnectContext = &disconnectCount; + networkInterface.send = _sendSuccess; + networkInterface.disconnect = _disconnect; + + for( i = 0; i < _DISCONNECT_MALLOC_LIMIT; i++ ) + { + /* Allow unlimited use of malloc during connection initialization. */ + UnityMalloc_MakeMallocFailAfterCount( -1 ); + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set malloc to eventually fail. */ + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call DISCONNECT; this function should always perform cleanup regardless + * of memory allocation errors. */ + IotMqtt_Disconnect( pMqttConnection, false ); + TEST_ASSERT_EQUAL_INT( 1, disconnectCount ); + disconnectCount = 0; + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) with various + * valid and invalid parameters. + */ +TEST( MQTT_Unit_API, PublishQoS0Parameters ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttReference_t publishReference = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.send = _sendSuccess; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + if( TEST_PROTECT() ) + { + /* Check that the publish info is validated. */ + status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + /* Check that a QoS 0 publish is refused if a notification is requested. */ + status = IotMqtt_Publish( pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishReference ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + /* If valid parameters are passed, QoS 0 publish should always return success. */ + status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, 0, &publishReference ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Allow time for the send job to run and clean up the PUBLISH. QoS 0 + * PUBLISH provides no mechanism to wait on completion, so sleep is used. */ + sleep( 1 ); + } + + IotMqtt_Disconnect( pMqttConnection, true ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, PublishQoS0MallocFail ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.send = _sendSuccess; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the necessary members of publish info. */ + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + if( TEST_PROTECT() ) + { + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call PUBLISH. Memory allocation will fail at various times during + * this call. */ + status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, NULL, NULL ); + + /* Once PUBLISH succeeds, the loop can exit. */ + if( status == IOT_MQTT_SUCCESS ) + { + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + } + + /* Wait for any pending QoS 0 publishes to clean up. */ + sleep( 1 ); + } + + IotMqtt_Disconnect( pMqttConnection, true ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publish (QoS 1) with various + * invalid parameters. Also tests the behavior of @ref mqtt_function_publish + * (QoS 1) when memory allocation fails at various points. + */ +TEST( MQTT_Unit_API, PublishQoS1 ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.send = _sendSuccess; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the publish info. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + + if( TEST_PROTECT() ) + { + /* Setting the waitable flag with no reference is not allowed. */ + status = IotMqtt_Publish( pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + /* Setting both the waitable flag and callback info is not allowed. */ + status = IotMqtt_Publish( pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + &callbackInfo, + &publishRef ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + /* Check QoS 1 PUBLISH behavior with malloc failures. */ + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call PUBLISH. Memory allocation will fail at various times during + * this call. */ + status = IotMqtt_Publish( pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishRef ); + + /* If the PUBLISH succeeded, the loop can exit after waiting for the QoS + * 1 PUBLISH to be cleaned up. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( publishRef, _TIMEOUT_MS ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + } + } + + /* Clean up MQTT connection. */ + IotMqtt_Disconnect( pMqttConnection, true ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that duplicate QoS 1 PUBLISH packets are different from the + * original. + * + * For non-AWS IoT MQTT servers, checks that the DUP flag is set. For + * AWS IoT MQTT servers, checks that the packet identifier is different. + */ +TEST( MQTT_Unit_API, PublishDuplicates ) +{ + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; + volatile bool dupCheckResult = false; + uint64_t startTime = 0; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.pSendContext = ( void * ) &dupCheckResult; + networkInterface.send = _dupChecker; + networkInterface.serialize.publishSetDup = _publishSetDup; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the publish info. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = _TEST_TOPIC_NAME; + publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + publishInfo.pPayload = "test"; + publishInfo.payloadLength = 4; + publishInfo.retryMs = _DUP_CHECK_RETRY_MS; + publishInfo.retryLimit = _DUP_CHECK_RETRY_LIMIT; + + startTime = IotClock_GetTimeMs(); + + if( TEST_PROTECT() ) + { + /* Send a PUBLISH with retransmissions enabled. */ + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, + IotMqtt_Publish( pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishRef ) ); + + /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is + * expected. */ + TEST_ASSERT_EQUAL( IOT_MQTT_RETRY_NO_RESPONSE, + IotMqtt_Wait( publishRef, _DUP_CHECK_TIMEOUT ) ); + + /* Check the result of the DUP check. */ + TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); + + /* Check that at least the minimum wait time elapsed. */ + TEST_ASSERT_TRUE( startTime + _DUP_CHECK_MINIMUM_WAIT <= IotClock_GetTimeMs() ); + } + + /* Clean up MQTT connection. */ + IotMqtt_Disconnect( pMqttConnection, true ); + + /* Check that the set DUP override was called. */ + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, _publishSetDupCalled ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_subscribe and + * @ref mqtt_function_unsubscribe with various invalid parameters. + */ +TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t mqttConnection = { 0 }; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; + + /* Check that subscription info is validated. */ + status = IotMqtt_Subscribe( &mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_Unsubscribe( &mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &reference ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + /* A reference must be provided for a waitable SUBSCRIBE. */ + status = IotMqtt_Subscribe( &mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_Unsubscribe( &mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_subscribe when memory allocation + * fails at various points. + */ +TEST( MQTT_Unit_API, SubscribeMallocFail ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttReference_t subscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.send = _sendSuccess; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the necessary members of the subscription. */ + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + if( TEST_PROTECT() ) + { + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call SUBSCRIBE. Memory allocation will fail at various times during + * this call. */ + status = IotMqtt_Subscribe( pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeRef ); + + /* If the SUBSCRIBE succeeded, the loop can exit after waiting for + * the SUBSCRIBE to be cleaned up. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( subscribeRef, _TIMEOUT_MS ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + } + + /* No lingering subscriptions should be in the MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( pMqttConnection->subscriptionList ) ) ); + } + + IotMqtt_Disconnect( pMqttConnection, true ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_unsubscribe when memory + * allocation fails at various points. + */ +TEST( MQTT_Unit_API, UnsubscribeMallocFail ) +{ + int32_t i = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + + /* Set the members of the network interface and create a new MQTT connection. */ + networkInterface.send = _sendSuccess; + pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the necessary members of the subscription. */ + subscription.pTopicFilter = _TEST_TOPIC_NAME; + subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = _SUBSCRIPTION_CALLBACK; + + if( TEST_PROTECT() ) + { + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call UNSUBSCRIBE. Memory allocation will fail at various times during + * this call. */ + status = IotMqtt_Unsubscribe( pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeRef ); + + /* If the UNSUBSCRIBE succeeded, the loop can exit after waiting for + * the UNSUBSCRIBE to be cleaned up. */ + if( status == IOT_MQTT_STATUS_PENDING ) + { + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( unsubscribeRef, _TIMEOUT_MS ) ); + break; + } + + /* If the return value isn't success, check that it is memory allocation + * failure. */ + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + } + + /* No lingering subscriptions should be in the MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( pMqttConnection->subscriptionList ) ) ); + } + + IotMqtt_Disconnect( pMqttConnection, true ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests keep-alive handling and ensures that it is periodic. + */ +TEST( MQTT_Unit_API, KeepAlivePeriodic ) +{ + int32_t disconnectCount = 0; + _mqttConnection_t * pMqttConnection = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + + /* An estimate for the amount of time this test requires. */ + const unsigned sleepTime = ( ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) / 1000 ) + + ( ( ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) % 1000 ) != 0 ) + + ( IOT_MQTT_RESPONSE_WAIT_MS * _KEEP_ALIVE_COUNT ) / 1000 + 2; + + /* Print a newline so this test may log its status. */ + UNITY_PRINT_EOL(); + + /* Set the members of the network interface and create a new MQTT connection + * with keep-alive. */ + networkInterface.send = _sendPingreq; + networkInterface.pDisconnectContext = &disconnectCount; + networkInterface.disconnect = _disconnect; + pMqttConnection = IotTestMqtt_createMqttConnection( false, + &networkInterface, + 1 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Connection send context can only be set after connection is created. */ + pMqttConnection->network.pSendContext = pMqttConnection; + + /* Set a short keep-alive interval so this test runs faster. */ + pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; + pMqttConnection->nextKeepAliveMs = _SHORT_KEEP_ALIVE_MS; + + /* Schedule the initial PINGREQ. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, + IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), + &( pMqttConnection->keepAliveJob ), + pMqttConnection->nextKeepAliveMs ) ); + + /* Sleep to allow ample time for periodic PINGREQ sends and PINGRESP responses. */ + sleep( sleepTime ); + + /* Disconnect the connection. */ + IotMqtt_Disconnect( pMqttConnection, true ); + + /* Check the counters for PINGREQ send and disconnect. */ + TEST_ASSERT_EQUAL_INT32( _KEEP_ALIVE_COUNT + 1, _pingreqSendCount ); + TEST_ASSERT_EQUAL_INT32( 2, disconnectCount ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that the keep-alive job cleans up the MQTT connection after a call + * to @ref mqtt_function_disconnect. + */ +TEST( MQTT_Unit_API, KeepAliveJobCleanup ) +{ + _mqttConnection_t * pMqttConnection = NULL; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotSemaphore_t waitSem; + + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the members of the network interface and create a new MQTT connection + * with keep-alive. */ + networkInterface.pSendContext = &waitSem; + networkInterface.send = _sendSuccess; + pMqttConnection = IotTestMqtt_createMqttConnection( false, + &networkInterface, + 1 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set a short keep-alive interval so this test runs faster. */ + pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; + + /* Schedule the initial PINGREQ. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, + IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), + &( pMqttConnection->keepAliveJob ), + _SHORT_KEEP_ALIVE_MS ) ); + + /* Wait for the keep-alive job to send a PINGREQ. */ + IotSemaphore_Wait( &waitSem ); + + /* Immediately disconnect the connection. */ + IotMqtt_Disconnect( pMqttConnection, true ); + } + + IotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c similarity index 76% rename from tests/mqtt/unit/aws_iot_tests_mqtt_receive.c rename to tests/mqtt/unit/iot_tests_mqtt_receive.c index 78f2b612eb..4e0fe07507 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -20,7 +20,7 @@ */ /** - * @file aws_iot_tests_mqtt_receive.c + * @file iot_tests_mqtt_receive.c * @brief Tests for the function @ref mqtt_function_receivecallback. */ @@ -36,25 +36,25 @@ #include "platform/iot_threads.h" /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* Test framework includes. */ #include "unity_fixture.h" /* MQTT test access include. */ -#include "aws_iot_test_access_mqtt.h" +#include "iot_test_access_mqtt.h" /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ -#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER - #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } + #undef IOT_MQTT_CONNECT_INFO_INITIALIZER + #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /** @@ -64,13 +64,13 @@ * Provide default malloc and free functions in dynamic memory mode. */ #if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) - #ifndef AwsIotTest_Malloc + #ifndef IotTest_Malloc #include - #define AwsIotTest_Malloc malloc + #define IotTest_Malloc malloc #endif - #ifndef AwsIotTest_Free + #ifndef IotTest_Free #include - #define AwsIotTest_Free free + #define IotTest_Free free #endif #endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ /** @endcond */ @@ -160,12 +160,12 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Initializer for operations in the tests. */ -#define _INITIALIZE_OPERATION( name ) \ - { \ - .link = { 0 }, .operation = name, .pMqttConnection = _pMqttConnection, \ - .flags = AWS_IOT_MQTT_FLAG_WAITABLE, .packetIdentifier = 1, \ - .pMqttPacket = NULL, .packetSize = 0, .notify = { .callback = { 0 } }, \ - .status = AWS_IOT_MQTT_STATUS_PENDING, .pPublishRetry = NULL \ +#define _INITIALIZE_OPERATION( name ) \ + { \ + .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ + .job = { 0 }, .jobReference = 1, .operation = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ + .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, .notify = { .callback = { 0 } }, \ + .status = IOT_MQTT_STATUS_PENDING, .retry = { 0 } \ } /*-----------------------------------------------------------*/ @@ -193,7 +193,7 @@ static bool _getPacketTypeCalled = false; /*-----------------------------------------------------------*/ /** - * @brief Wrapper for AwsIotTest_Malloc. + * @brief Wrapper for IotTest_Malloc. */ static void * _mallocWrapper( size_t size ) { @@ -202,7 +202,7 @@ static void * _mallocWrapper( size_t size ) ( void ) size; #if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) - pBuffer = AwsIotTest_Malloc( size ); + pBuffer = IotTest_Malloc( size ); /* Decrement the malloc semaphore. */ if( pBuffer != NULL ) @@ -212,7 +212,7 @@ static void * _mallocWrapper( size_t size ) ( IotSemaphore_GetCount( &_mallocSemaphore ) > 0 ) ) { /* If the malloc semaphore value isn't what's expected, return NULL. */ - AwsIotTest_Free( pBuffer ); + IotTest_Free( pBuffer ); pBuffer = NULL; } } @@ -226,18 +226,18 @@ static void * _mallocWrapper( size_t size ) /** * @brief Get packet type function override. */ -static uint8_t _getPacketType( const uint8_t * const pPacket, +static uint8_t _getPacketType( const uint8_t * pPacket, size_t packetSize ) { _getPacketTypeCalled = true; - return AwsIotMqttInternal_GetPacketType( pPacket, packetSize ); + return _IotMqtt_GetPacketType( pPacket, packetSize ); } /*-----------------------------------------------------------*/ /** - * @brief Wrapper for AwsIotTest_Free. + * @brief Wrapper for IotTest_Free. */ static void _freeWrapper( void * ptr ) { @@ -245,7 +245,7 @@ static void _freeWrapper( void * ptr ) #if IOT_STATIC_MEMORY_ONLY == 1 ( void ) ptr; #else - AwsIotTest_Free( ptr ); + IotTest_Free( ptr ); #endif IotSemaphore_Post( &_mallocSemaphore ); @@ -256,15 +256,15 @@ static void _freeWrapper( void * ptr ) /** * @brief Deserializer override for CONNACK. */ -static AwsIotMqttError_t _deserializeConnack( const uint8_t * const pConnackStart, - size_t dataLength, - size_t * const pBytesProcessed ) +static IotMqttError_t _deserializeConnack( const uint8_t * pConnackStart, + size_t dataLength, + size_t * pBytesProcessed ) { _deserializeOverrideCalled = true; - return AwsIotMqttInternal_DeserializeConnack( pConnackStart, - dataLength, - pBytesProcessed ); + return _IotMqtt_DeserializeConnack( pConnackStart, + dataLength, + pBytesProcessed ); } /*-----------------------------------------------------------*/ @@ -272,19 +272,19 @@ static AwsIotMqttError_t _deserializeConnack( const uint8_t * const pConnackStar /** * @brief Deserializer override for PUBLISH. */ -static AwsIotMqttError_t _deserializePublish( const uint8_t * const pPublishStart, - size_t dataLength, - AwsIotMqttPublishInfo_t * const pOutput, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +static IotMqttError_t _deserializePublish( const uint8_t * pPublishStart, + size_t dataLength, + IotMqttPublishInfo_t * pOutput, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { _deserializeOverrideCalled = true; - return AwsIotMqttInternal_DeserializePublish( pPublishStart, - dataLength, - pOutput, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializePublish( pPublishStart, + dataLength, + pOutput, + pPacketIdentifier, + pBytesProcessed ); } /*-----------------------------------------------------------*/ @@ -292,17 +292,17 @@ static AwsIotMqttError_t _deserializePublish( const uint8_t * const pPublishStar /** * @brief Deserializer override for PUBACK. */ -static AwsIotMqttError_t _deserializePuback( const uint8_t * const pPubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +static IotMqttError_t _deserializePuback( const uint8_t * pPubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { _deserializeOverrideCalled = true; - return AwsIotMqttInternal_DeserializePuback( pPubackStart, - dataLength, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializePuback( pPubackStart, + dataLength, + pPacketIdentifier, + pBytesProcessed ); } /*-----------------------------------------------------------*/ @@ -310,19 +310,19 @@ static AwsIotMqttError_t _deserializePuback( const uint8_t * const pPubackStart, /** * @brief Deserializer override for SUBACK. */ -static AwsIotMqttError_t _deserializeSuback( AwsIotMqttConnection_t mqttConnection, - const uint8_t * const pSubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +static IotMqttError_t _deserializeSuback( IotMqttConnection_t mqttConnection, + const uint8_t * pSubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { _deserializeOverrideCalled = true; - return AwsIotMqttInternal_DeserializeSuback( mqttConnection, - pSubackStart, - dataLength, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializeSuback( mqttConnection, + pSubackStart, + dataLength, + pPacketIdentifier, + pBytesProcessed ); } /*-----------------------------------------------------------*/ @@ -330,17 +330,17 @@ static AwsIotMqttError_t _deserializeSuback( AwsIotMqttConnection_t mqttConnecti /** * @brief Deserializer override for UNSUBACK. */ -static AwsIotMqttError_t _deserializeUnsuback( const uint8_t * const pUnsubackStart, - size_t dataLength, - uint16_t * const pPacketIdentifier, - size_t * const pBytesProcessed ) +static IotMqttError_t _deserializeUnsuback( const uint8_t * pUnsubackStart, + size_t dataLength, + uint16_t * pPacketIdentifier, + size_t * pBytesProcessed ) { _deserializeOverrideCalled = true; - return AwsIotMqttInternal_DeserializeUnsuback( pUnsubackStart, - dataLength, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializeUnsuback( pUnsubackStart, + dataLength, + pPacketIdentifier, + pBytesProcessed ); } /*-----------------------------------------------------------*/ @@ -348,15 +348,15 @@ static AwsIotMqttError_t _deserializeUnsuback( const uint8_t * const pUnsubackSt /** * @brief Deserializer override for PINGRESP. */ -static AwsIotMqttError_t _deserializePingresp( const uint8_t * const pPingrespStart, - size_t dataLength, - size_t * const pBytesProcessed ) +static IotMqttError_t _deserializePingresp( const uint8_t * pPingrespStart, + size_t dataLength, + size_t * pBytesProcessed ) { _deserializeOverrideCalled = true; - return AwsIotMqttInternal_DeserializePingresp( pPingrespStart, - dataLength, - pBytesProcessed ); + return _IotMqtt_DeserializePingresp( pPingrespStart, + dataLength, + pBytesProcessed ); } /*-----------------------------------------------------------*/ @@ -365,10 +365,11 @@ static AwsIotMqttError_t _deserializePingresp( const uint8_t * const pPingrespSt * @brief Reset the status of an #_mqttOperation_t and push it to the queue of * MQTT operations awaiting network response. */ -static void _operationResetAndPush( _mqttOperation_t * const pOperation ) +static void _operationResetAndPush( _mqttOperation_t * pOperation ) { - pOperation->status = AWS_IOT_MQTT_STATUS_PENDING; - IotQueue_Enqueue( &( _IotMqttPendingResponse ), &( pOperation->link ) ); + pOperation->status = IOT_MQTT_STATUS_PENDING; + pOperation->jobReference = 1; + IotQueue_Enqueue( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); } /*-----------------------------------------------------------*/ @@ -376,19 +377,21 @@ static void _operationResetAndPush( _mqttOperation_t * const pOperation ) /** * @brief Process a non-PUBLISH buffer and check the result. */ -static bool _processBuffer( const _mqttOperation_t * const pOperation, +static bool _processBuffer( const _mqttOperation_t * pOperation, const uint8_t * pBuffer, size_t bufferSize, int32_t expectedBytesProcessed, - AwsIotMqttError_t expectedResult ) + IotMqttError_t expectedResult ) { + bool status = false; + /* Call the receive callback on pBuffer. */ - int32_t bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pBuffer, - bufferSize, - 0, - _freeWrapper ); + int32_t bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pBuffer, + bufferSize, + 0, + _freeWrapper ); /* Free pBuffer if the receive callback wasn't expected to free it. */ if( expectedBytesProcessed <= 0 ) @@ -396,9 +399,16 @@ static bool _processBuffer( const _mqttOperation_t * const pOperation, _freeWrapper( ( void * ) pBuffer ); } - /* Check results against expected values. */ - return ( expectedBytesProcessed == bytesProcessed ) && - ( expectedResult == pOperation->status ); + /* Check expected bytes processed. */ + status = ( expectedBytesProcessed == bytesProcessed ); + + /* Check expected result if operation is given. */ + if( pOperation != NULL ) + { + status = status && ( expectedResult == pOperation->status ); + } + + return status; } /*-----------------------------------------------------------*/ @@ -406,7 +416,7 @@ static bool _processBuffer( const _mqttOperation_t * const pOperation, /** * @brief Process a PUBLISH message and check the result. */ -static bool _processPublish( const uint8_t * const pPublish, +static bool _processPublish( const uint8_t * pPublish, size_t publishSize, int32_t expectedBytesProcessed, uint32_t expectedInvokeCount ) @@ -432,12 +442,12 @@ static bool _processPublish( const uint8_t * const pPublish, } /* Call the receive callback on pPublish. */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pPublish, - publishSize, - 0, - _freeWrapper ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pPublish, + publishSize, + 0, + _freeWrapper ); /* Check how many times the publish callback is invoked. */ for( i = 0; i < expectedInvokeCount; i++ ) @@ -468,17 +478,17 @@ static bool _processPublish( const uint8_t * const pPublish, * @brief Called when a PUBLISH message is "received". */ static void _publishCallback( void * param1, - AwsIotMqttCallbackParam_t * const pPublish ) + IotMqttCallbackParam_t * pPublish ) { IotSemaphore_t * pInvokeCount = ( IotSemaphore_t * ) param1; /* QoS 2 is valid for these tests, but currently unsupported by the MQTT library. * Change the QoS to 0 so that QoS validation passes. */ - pPublish->message.info.QoS = 0; + pPublish->message.info.qos = IOT_MQTT_QOS_0; /* Check that the parameters to this function are valid. */ - if( ( AwsIotMqttInternal_ValidatePublish( _AWS_IOT_MQTT_SERVER, - &( pPublish->message.info ) ) == true ) && + if( ( _IotMqtt_ValidatePublish( _AWS_IOT_MQTT_SERVER, + &( pPublish->message.info ) ) == true ) && ( pPublish->message.info.topicNameLength == _TEST_TOPIC_LENGTH ) && ( strncmp( _TEST_TOPIC_NAME, pPublish->message.info.pTopicName, _TEST_TOPIC_LENGTH ) == 0 ) ) { @@ -493,15 +503,15 @@ static void _publishCallback( void * param1, * * Prevents any tests on QoS 1 PUBLISH packets from sending any PUBACKS. */ -static AwsIotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** const pPubackPacket, - size_t * const pPacketSize ) +static IotMqttError_t _serializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) { ( void ) packetIdentifier; ( void ) pPubackPacket; ( void ) pPacketSize; - return AWS_IOT_MQTT_NO_MEMORY; + return IOT_MQTT_NO_MEMORY; } /*-----------------------------------------------------------*/ @@ -518,8 +528,8 @@ TEST_GROUP( MQTT_Unit_Receive ); */ TEST_SETUP( MQTT_Unit_Receive ) { - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; /* Set the deserializer overrides. */ networkInterface.deserialize.connack = _deserializeConnack; @@ -536,12 +546,12 @@ TEST_SETUP( MQTT_Unit_Receive ) 1 ) ); /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); /* Initialize the MQTT connection used by the tests. */ - _pMqttConnection = AwsIotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); TEST_ASSERT_NOT_EQUAL( NULL, _pMqttConnection ); /* Set the members of the subscription. */ @@ -550,10 +560,10 @@ TEST_SETUP( MQTT_Unit_Receive ) subscription.callback.function = _publishCallback; /* Add the subscription to the MQTT connection. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqttInternal_AddSubscriptions( _pMqttConnection, - 1, - &subscription, - 1 ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + &subscription, + 1 ) ); /* Clear the deserialize override called flag. */ _deserializeOverrideCalled = false; @@ -568,8 +578,8 @@ TEST_SETUP( MQTT_Unit_Receive ) TEST_TEAR_DOWN( MQTT_Unit_Receive ) { /* Clean up resources taken in test setup. */ - AwsIotTestMqtt_destroyMqttConnection( _pMqttConnection ); - AwsIotMqtt_Cleanup(); + IotMqtt_Disconnect( _pMqttConnection, true ); + IotMqtt_Cleanup(); IotSemaphore_Destroy( &_mallocSemaphore ); _pMqttConnection = NULL; @@ -617,10 +627,10 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { uint8_t pRemainingLength[ 4 ] = { 0 }; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + IotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 1 ); TEST_ASSERT_EQUAL( 0, decodedLength ); } @@ -629,10 +639,10 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { uint8_t pRemainingLength[ 4 ] = { 0x81, 0x01, 0x00, 0x00 }; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + IotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 2 ); TEST_ASSERT_EQUAL( 129, decodedLength ); } @@ -641,10 +651,10 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { uint8_t pRemainingLength[ 4 ] = { 0x82, 0x80, 0x01, 0x00 }; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + IotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 3 ); TEST_ASSERT_EQUAL( 16386, decodedLength ); } @@ -654,10 +664,10 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x7f }; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + IotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 4 ); TEST_ASSERT_EQUAL( 268435455, decodedLength ); } @@ -666,10 +676,10 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x8f }; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, - AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, + IotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); } /* Attempt to decode a 4-byte representation of 0. According to the spec, @@ -677,10 +687,10 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { uint8_t pRemainingLength[ 4 ] = { 0x80, 0x80, 0x80, 0x00 }; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_BAD_PARAMETER, - AwsIotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, + IotTestMqtt_decodeRemainingLength( pRemainingLength, + &pEnd, + &decodedLength ) ); } /* Test tear down for this test group checks that deserializer overrides @@ -702,12 +712,12 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) uint8_t invalidPacket = 0xf0; /* Processing a control packet 0xf is a protocol violation. */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - &invalidPacket, - sizeof( uint8_t ), - 0, - NULL ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + &invalidPacket, + sizeof( uint8_t ), + 0, + NULL ); TEST_ASSERT_EQUAL( -1, bytesProcessed ); /* This test should not have called any deserializer. Set the deserialize @@ -750,33 +760,33 @@ TEST( MQTT_Unit_Receive, DataStream ) TEST_ASSERT_EQUAL( _DATA_STREAM_SIZE, copyOffset + sizeof( _pPingrespTemplate ) ); /* Passing an offset greater than dataLength should not process anything. */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - 4, - 5, - _freeWrapper ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pDataStream, + 4, + 5, + _freeWrapper ); TEST_ASSERT_EQUAL( 0, bytesProcessed ); /* The first call to process 64 bytes should only process the CONNACK and * SUBACK. */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 64, - 0, - _freeWrapper ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pDataStream, + processOffset + 64, + 0, + _freeWrapper ); TEST_ASSERT_EQUAL( 11, bytesProcessed ); processOffset += ( size_t ) bytesProcessed; /* A second call to process 64 bytes should not process anything, as the * PUBLISH is incomplete. */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 64, - processOffset, - _freeWrapper ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pDataStream, + processOffset + 64, + processOffset, + _freeWrapper ); TEST_ASSERT_EQUAL( 0, bytesProcessed ); /* A call to process 273 bytes should process the PUBLISH packet (272 bytes). */ @@ -787,22 +797,22 @@ TEST( MQTT_Unit_Receive, DataStream ) processOffset += 272; /* A call to process 5 bytes should only process the UNSUBACK (4 bytes). */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 5, - processOffset, - _freeWrapper ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pDataStream, + processOffset + 5, + processOffset, + _freeWrapper ); TEST_ASSERT_EQUAL( 4, bytesProcessed ); processOffset += ( size_t ) bytesProcessed; /* Process the last 2 bytes (PINGRESP). */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 2, - processOffset, - _freeWrapper ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pDataStream, + processOffset + 2, + processOffset, + _freeWrapper ); TEST_ASSERT_EQUAL( 2, bytesProcessed ); /* Wait for the buffer to be freed. */ @@ -822,7 +832,7 @@ TEST( MQTT_Unit_Receive, DataStream ) TEST( MQTT_Unit_Receive, ConnackValid ) { uint8_t i = 0; - _mqttOperation_t connect = _INITIALIZE_OPERATION( AWS_IOT_MQTT_CONNECT ); + _mqttOperation_t connect = _INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -838,7 +848,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) pConnack, connackSize, ( int32_t ) connackSize, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Process a valid, successful CONNACK with no SP flag. */ @@ -849,7 +859,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) pConnack, connackSize, ( int32_t ) connackSize, - AWS_IOT_MQTT_SUCCESS ) ); + IOT_MQTT_SUCCESS ) ); } /* Process a valid, successful CONNACK with SP flag set. */ @@ -861,7 +871,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) pConnack, connackSize, ( int32_t ) connackSize, - AWS_IOT_MQTT_SUCCESS ) ); + IOT_MQTT_SUCCESS ) ); } /* Check each of the CONNACK failure codes, which range from 1 to 5. */ @@ -878,7 +888,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) pConnack, connackSize, ( int32_t ) connackSize, - AWS_IOT_MQTT_SERVER_REFUSED ) ); + IOT_MQTT_SERVER_REFUSED ) ); } IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); @@ -892,7 +902,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) */ TEST( MQTT_Unit_Receive, ConnackInvalid ) { - _mqttOperation_t connect = _INITIALIZE_OPERATION( AWS_IOT_MQTT_CONNECT ); + _mqttOperation_t connect = _INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -908,7 +918,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) pConnack, connackSize - 1, 0, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* The CONNACK control packet type must be 0x20. */ @@ -919,7 +929,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) pConnack, connackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* A CONNACK must have a remaining length of 2. */ @@ -931,7 +941,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) pConnack, connackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* The reserved bits in CONNACK must be 0. */ @@ -943,7 +953,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) pConnack, connackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* The fourth byte of CONNACK must be 0 if the SP flag is set. */ @@ -956,7 +966,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) pConnack, connackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* CONNACK return codes cannot be above 5. */ @@ -968,7 +978,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) pConnack, connackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); @@ -1039,7 +1049,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) { _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); IotListDouble_RemoveAll( &( _pMqttConnection->subscriptionList ), - AwsIotMqtt_FreeSubscription, + IotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, @@ -1254,7 +1264,7 @@ TEST( MQTT_Unit_Receive, PublishInvalidStream ) */ TEST( MQTT_Unit_Receive, PubackValid ) { - _mqttOperation_t publish = _INITIALIZE_OPERATION( AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + _mqttOperation_t publish = _INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1270,7 +1280,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) pPuback, pubackSize, ( int32_t ) pubackSize, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Process a valid PUBACK. */ @@ -1281,7 +1291,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) pPuback, pubackSize, ( int32_t ) pubackSize, - AWS_IOT_MQTT_SUCCESS ) ); + IOT_MQTT_SUCCESS ) ); } IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); @@ -1295,7 +1305,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) */ TEST( MQTT_Unit_Receive, PubackInvalid ) { - _mqttOperation_t publish = _INITIALIZE_OPERATION( AWS_IOT_MQTT_PUBLISH_TO_SERVER ); + _mqttOperation_t publish = _INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1311,7 +1321,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) pPuback, pubackSize - 1, 0, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* The PUBACK control packet type must be 0x40. */ @@ -1322,7 +1332,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) pPuback, pubackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* A PUBACK must have a remaining length of 2. */ @@ -1334,7 +1344,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) pPuback, pubackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* The packet identifier in PUBACK cannot be 0. No status should be set if @@ -1347,7 +1357,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) pPuback, pubackSize, -1, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Remove unprocessed PUBLISH if present. */ @@ -1368,8 +1378,8 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) TEST( MQTT_Unit_Receive, SubackValid ) { _mqttSubscription_t * pNewSubscription = NULL; - _mqttOperation_t subscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_SUBSCRIBE ); - AwsIotMqttSubscription_t pSubscriptions[ 2 ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + _mqttOperation_t subscribe = _INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); + IotMqttSubscription_t pSubscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1378,20 +1388,20 @@ TEST( MQTT_Unit_Receive, SubackValid ) 10 ) ); /* Add 2 additional subscriptions to the MQTT connection. */ - pSubscriptions[ 0 ].QoS = 1; + pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_1; pSubscriptions[ 0 ].callback.function = _publishCallback; pSubscriptions[ 0 ].pTopicFilter = _TEST_TOPIC_NAME "1"; pSubscriptions[ 0 ].topicFilterLength = _TEST_TOPIC_LENGTH + 1; - pSubscriptions[ 1 ].QoS = 1; + pSubscriptions[ 1 ].qos = IOT_MQTT_QOS_1; pSubscriptions[ 1 ].callback.function = _publishCallback; pSubscriptions[ 1 ].pTopicFilter = _TEST_TOPIC_NAME "2"; pSubscriptions[ 1 ].topicFilterLength = _TEST_TOPIC_LENGTH + 1; - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqttInternal_AddSubscriptions( _pMqttConnection, - 1, - pSubscriptions, - 2 ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + pSubscriptions, + 2 ) ); /* Set orders 2 and 1 for the new subscriptions. */ pNewSubscription = IotLink_Container( _mqttSubscription_t, @@ -1412,29 +1422,29 @@ TEST( MQTT_Unit_Receive, SubackValid ) pSuback, subackSize, ( int32_t ) subackSize, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Process a valid SUBACK where all subscriptions are successful. */ { - AwsIotMqttSubscription_t currentSubscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttSubscription_t currentSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); _operationResetAndPush( &subscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, ( int32_t ) subackSize, - AWS_IOT_MQTT_SUCCESS ) ); + IOT_MQTT_SUCCESS ) ); /* Test the subscription check function. QoS is not tested. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotMqtt_IsSubscribed( _pMqttConnection, - pSubscriptions[ 0 ].pTopicFilter, - pSubscriptions[ 0 ].topicFilterLength, - ¤tSubscription ) ); - currentSubscription.QoS = pSubscriptions[ 0 ].QoS; + TEST_ASSERT_EQUAL_INT( true, IotMqtt_IsSubscribed( _pMqttConnection, + pSubscriptions[ 0 ].pTopicFilter, + pSubscriptions[ 0 ].topicFilterLength, + ¤tSubscription ) ); + currentSubscription.qos = pSubscriptions[ 0 ].qos; TEST_ASSERT_EQUAL_MEMORY( &pSubscriptions[ 0 ], ¤tSubscription, - sizeof( AwsIotMqttSubscription_t ) ); + sizeof( IotMqttSubscription_t ) ); } /* Process a valid SUBACK where some subscriptions were rejected. */ @@ -1447,18 +1457,18 @@ TEST( MQTT_Unit_Receive, SubackValid ) pSuback, subackSize, ( int32_t ) subackSize, - AWS_IOT_MQTT_SERVER_REFUSED ) ); + IOT_MQTT_SERVER_REFUSED ) ); /* Check that rejected subscriptions were removed from the subscription * list. */ - TEST_ASSERT_EQUAL_INT( false, AwsIotMqtt_IsSubscribed( _pMqttConnection, - _TEST_TOPIC_NAME, - _TEST_TOPIC_LENGTH, - NULL ) ); - TEST_ASSERT_EQUAL_INT( false, AwsIotMqtt_IsSubscribed( _pMqttConnection, - pSubscriptions[ 1 ].pTopicFilter, - pSubscriptions[ 1 ].topicFilterLength, - NULL ) ); + TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, + _TEST_TOPIC_NAME, + _TEST_TOPIC_LENGTH, + NULL ) ); + TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, + pSubscriptions[ 1 ].pTopicFilter, + pSubscriptions[ 1 ].topicFilterLength, + NULL ) ); } IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); @@ -1472,7 +1482,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) */ TEST( MQTT_Unit_Receive, SubackInvalid ) { - _mqttOperation_t subscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_SUBSCRIBE ); + _mqttOperation_t subscribe = _INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1489,7 +1499,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) pSuback, 4, 0, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Attempt to process a SUBACK with an invalid "Remaining length". */ @@ -1503,7 +1513,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) pSuback, subackSize, -1, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Attempt to process a SUBACK larger than the size of the data stream. */ @@ -1514,7 +1524,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) pSuback, subackSize, 0, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Attempt to process a SUBACK with a "Remaining length" smaller than the @@ -1526,7 +1536,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) pSuback, subackSize, -1, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Attempt to process a SUBACK with a bad return code. */ @@ -1537,7 +1547,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) pSuback, subackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* The SUBACK control packet type must be 0x90. */ @@ -1549,7 +1559,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) pSuback, subackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); @@ -1563,7 +1573,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) */ TEST( MQTT_Unit_Receive, UnsubackValid ) { - _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_UNSUBSCRIBE ); + _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1579,7 +1589,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) pUnsuback, unsubackSize, ( int32_t ) unsubackSize, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Process a valid UNSUBACK. */ @@ -1590,7 +1600,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) pUnsuback, unsubackSize, ( int32_t ) unsubackSize, - AWS_IOT_MQTT_SUCCESS ) ); + IOT_MQTT_SUCCESS ) ); } IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); @@ -1604,7 +1614,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) */ TEST( MQTT_Unit_Receive, UnsubackInvalid ) { - _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( AWS_IOT_MQTT_UNSUBSCRIBE ); + _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1620,7 +1630,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) pUnsuback, unsubackSize - 1, 0, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* The UNSUBACK control packet type must be 0xb0. */ @@ -1631,7 +1641,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) pUnsuback, unsubackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* An UNSUBACK must have a remaining length of 2. */ @@ -1643,7 +1653,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) pUnsuback, unsubackSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_BAD_RESPONSE ) ); } /* The packet identifier in UNSUBACK cannot be 0. No status should be set if @@ -1656,7 +1666,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) pUnsuback, unsubackSize, -1, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_STATUS_PENDING ) ); } /* Remove unprocessed UNSUBSCRIBE if present. */ @@ -1676,71 +1686,79 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) */ TEST( MQTT_Unit_Receive, Pingresp ) { - _mqttOperation_t pingreq = _INITIALIZE_OPERATION( AWS_IOT_MQTT_PINGREQ ); - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pingreq.notify.waitSemaphore ), - 0, - 10 ) ); - - /* Even though no PINGREQ is in the receive queue, 2 bytes should still be - * processed (should not crash). */ + /* Even though no PINGREQ is expected, the keep-alive failure flag should + * be cleared (should not crash). */ { + _pMqttConnection->keepAliveFailure = false; + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, ( int32_t ) pingrespSize, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_SUCCESS ) ); + + TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); } /* Process a valid PINGRESP. */ { + _pMqttConnection->keepAliveFailure = true; + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - _operationResetAndPush( &pingreq ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, ( int32_t ) pingrespSize, - AWS_IOT_MQTT_SUCCESS ) ); + IOT_MQTT_SUCCESS ) ); + + TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); } - /* An incomplete PINGRESP should not be processed, and no status should be set. */ + /* An incomplete PINGRESP should not be processed, and the keep-alive failure + * flag should not be cleared. */ { + _pMqttConnection->keepAliveFailure = true; + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - _operationResetAndPush( &pingreq ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize - 1, 0, - AWS_IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_SUCCESS ) ); + + TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); } /* A PINGRESP should have a remaining length of 0. */ { + _pMqttConnection->keepAliveFailure = true; + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); pPingresp[ 1 ] = 0x01; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_SUCCESS ) ); + + TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); } /* The PINGRESP control packet type must be 0xd0. */ { + _pMqttConnection->keepAliveFailure = true; + _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); pPingresp[ 0 ] = 0xd1; - _operationResetAndPush( &pingreq ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &pingreq, + TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, -1, - AWS_IOT_MQTT_BAD_RESPONSE ) ); - } + IOT_MQTT_SUCCESS ) ); - IotSemaphore_Destroy( &( pingreq.notify.waitSemaphore ) ); + TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + } } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c similarity index 58% rename from tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c rename to tests/mqtt/unit/iot_tests_mqtt_subscription.c index c8dbd4b264..a89600afbb 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_tests_mqtt_subscription.c - * @brief Tests for the functions in aws_iot_mqtt_subscription.c + * @file iot_tests_mqtt_subscription.c + * @brief Tests for the functions in iot_mqtt_subscription.c */ /* Build using a config header, if provided. */ @@ -36,7 +36,7 @@ #include "platform/iot_threads.h" /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* POSIX includes. */ #ifdef POSIX_PTHREAD_HEADER @@ -44,12 +44,22 @@ #else #include #endif +#ifdef POSIX_TIME_HEADER + #include POSIX_TIME_HEADER +#else + #include +#endif +#ifdef POSIX_UNISTD_HEADER + #include POSIX_UNISTD_HEADER +#else + #include +#endif /* Test framework includes. */ #include "unity_fixture.h" /* MQTT test access include. */ -#include "aws_iot_test_access_mqtt.h" +#include "iot_test_access_mqtt.h" /** * @cond DOXYGEN_IGNORE @@ -66,17 +76,28 @@ extern int snprintf( char *, /*-----------------------------------------------------------*/ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef IOT_TEST_MQTT_TIMEOUT_MS + #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) +#endif +/** @endcond */ + /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ -#if !defined( AWS_IOT_TEST_MQTT_MOSQUITTO ) || AWS_IOT_TEST_MQTT_MOSQUITTO == 0 +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER - #define AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } + #undef IOT_MQTT_CONNECT_INFO_INITIALIZER + #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /* @@ -99,7 +120,7 @@ extern int snprintf( char *, */ #define _CALLBACK_FUNCTION \ ( ( void ( * )( void *, \ - AwsIotMqttCallbackParam_t * const ) ) 0x1 ) + IotMqttCallbackParam_t * ) ) 0x1 ) /** * @brief All test topic filters in the tests #TEST_MQTT_Unit_Subscription_TopicFilterMatchTrue_ @@ -120,19 +141,19 @@ extern int snprintf( char *, * @note This macro may only be used when a #_mqttSubscription_t pointer named pTopicFilter * is in scope. */ -#define _TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ - { \ - _topicMatchParams_t _topicMatchParams = { 0 }; \ - _topicMatchParams.pTopicName = topicNameString; \ - _topicMatchParams.topicNameLength = ( uint16_t ) strlen( _topicMatchParams.pTopicName ); \ - _topicMatchParams.exactMatchOnly = exactMatch; \ - \ - pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ - _TOPIC_FILTER_MATCH_MAX_LENGTH, \ - topicFilterString ); \ - \ - TEST_ASSERT_EQUAL_INT( expectedResult, \ - AwsIotTestMqtt_topicMatch( &( pTopicFilter->link ), &_topicMatchParams ) ); \ +#define _TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ + { \ + _topicMatchParams_t _topicMatchParams = { 0 }; \ + _topicMatchParams.pTopicName = topicNameString; \ + _topicMatchParams.topicNameLength = ( uint16_t ) strlen( _topicMatchParams.pTopicName ); \ + _topicMatchParams.exactMatchOnly = exactMatch; \ + \ + pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ + _TOPIC_FILTER_MATCH_MAX_LENGTH, \ + topicFilterString ); \ + \ + TEST_ASSERT_EQUAL_INT( expectedResult, \ + IotTestMqtt_topicMatch( &( pTopicFilter->link ), &_topicMatchParams ) ); \ } /*-----------------------------------------------------------*/ @@ -140,7 +161,7 @@ extern int snprintf( char *, /** * @brief The MQTT connection shared by all tests. */ -static _mqttConnection_t _connection = { 0 }; +static _mqttConnection_t * _pMqttConnection = NULL; /** * @brief Synchronizes threads in the multithreaded test. @@ -150,7 +171,7 @@ static pthread_barrier_t _mtTestBarrier; /*-----------------------------------------------------------*/ /** - * @brief Places dummy subscriptions in the subscription list of #_connection. + * @brief Places dummy subscriptions in the subscription list of #_pMqttConnection. */ static void _populateList( void ) { @@ -159,7 +180,7 @@ static void _populateList( void ) for( i = 0; i < _LIST_ITEM_COUNT; i++ ) { - pSubscription = AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); + pSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); TEST_ASSERT_NOT_NULL( pSubscription ); ( void ) memset( pSubscription, 0x00, sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); @@ -171,18 +192,63 @@ static void _populateList( void ) _TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); - IotListDouble_InsertHead( &( _connection.subscriptionList ), + IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), &( pSubscription->link ) ); } } /*-----------------------------------------------------------*/ +/** + * @brief Wait for a reference count to reach a target value, subject to a timeout. + */ +static bool _waitForCount( IotMutex_t * pMutex, + const int32_t * pReferenceCount, + int32_t target ) +{ + bool status = false; + int32_t referenceCount = 0, sleepCount = 0; + + /* 200 ms sleep time. */ + const struct timespec sleepTime = { .tv_sec = 0, .tv_nsec = 200000000 }; + + /* Calculate limit on the number of times to sleep for 200 ms. */ + const int32_t sleepLimit = ( IOT_TEST_MQTT_TIMEOUT_MS / 200 ) + + ( ( IOT_TEST_MQTT_TIMEOUT_MS % 200 ) != 0 ); + + /* Wait for the reference count to reach the target value. */ + for( sleepCount = 0; sleepCount < sleepLimit; sleepCount++ ) + { + /* Read reference count. */ + IotMutex_Lock( pMutex ); + referenceCount = *pReferenceCount; + IotMutex_Unlock( pMutex ); + + /* Exit if target value is reached. Otherwise, sleep. */ + if( referenceCount == target ) + { + status = true; + break; + } + else + { + if( nanosleep( &sleepTime, NULL ) != 0 ) + { + break; + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief A subscription callback function that only reports whether it was invoked. */ static void _publishCallback( void * pArgument, - AwsIotMqttCallbackParam_t * const pPublish ) + IotMqttCallbackParam_t * pPublish ) { uint16_t i = 0; bool * pCallbackInvoked = ( bool * ) pArgument; @@ -207,12 +273,29 @@ static void _publishCallback( void * pArgument, } /* Ensure that the MQTT connection was set correctly. */ - TEST_ASSERT_EQUAL_PTR( pPublish->mqttConnection, &_connection ); + TEST_ASSERT_EQUAL_PTR( pPublish->mqttConnection, _pMqttConnection ); /* Ensure that publish info is valid. */ TEST_ASSERT_EQUAL_INT( true, - AwsIotMqttInternal_ValidatePublish( _AWS_IOT_MQTT_SERVER, - &( pPublish->message.info ) ) ); + _IotMqtt_ValidatePublish( _AWS_IOT_MQTT_SERVER, + &( pPublish->message.info ) ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A subscription callback function that blocks on a semaphore until signaled. + */ +static void _blockingCallback( void * pArgument, + IotMqttCallbackParam_t * pPublish ) +{ + IotSemaphore_t * pSemaphore = ( IotSemaphore_t * ) pArgument; + + /* Silence warnings about unused parameters. */ + ( void ) pPublish; + + /* Wait until signaled. */ + IotSemaphore_Wait( pSemaphore ); } /*-----------------------------------------------------------*/ @@ -226,7 +309,7 @@ static void * _multithreadTestThread( void * pArgument ) int barrierResult = 0; bool * pThreadResult = ( bool * ) pArgument; char pTopicFilters[ _LIST_ITEM_COUNT ][ _MT_TOPIC_FILTER_LENGTH ] = { { 0 } }; - AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Synchronize with the other threads before starting the test. */ barrierResult = pthread_barrier_wait( &_mtTestBarrier ); @@ -249,10 +332,10 @@ static void * _multithreadTestThread( void * pArgument ) &i, ( unsigned long ) i ); - if( AwsIotMqttInternal_AddSubscriptions( &_connection, - 1, - &( subscription[ i ] ), - 1 ) != AWS_IOT_MQTT_SUCCESS ) + if( _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + &( subscription[ i ] ), + 1 ) != IOT_MQTT_SUCCESS ) { *pThreadResult = false; @@ -263,9 +346,9 @@ static void * _multithreadTestThread( void * pArgument ) /* Remove the previously added items from the list. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) { - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, - &( subscription[ i ] ), - 1 ); + _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, + &( subscription[ i ] ), + 1 ); } *pThreadResult = true; @@ -283,33 +366,44 @@ TEST_GROUP( MQTT_Unit_Subscription ); /*-----------------------------------------------------------*/ /** - * @brief Test setup for MQTT API tests. + * @brief Test setup for MQTT subscription tests. */ TEST_SETUP( MQTT_Unit_Subscription ) { - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &( _connection.subscriptionMutex ), false ) ); - IotListDouble_Create( &( _connection.subscriptionList ) ); + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + + /* Initialize the MQTT library. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); + + /* Create an MQTT connection with an empty network interface. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &networkInterface, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); } /*-----------------------------------------------------------*/ /** - * @brief Test tear down for MQTT API tests. + * @brief Test tear down for MQTT subscription tests. */ TEST_TEAR_DOWN( MQTT_Unit_Subscription ) { - IotListDouble_RemoveAll( &( _connection.subscriptionList ), - AwsIotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Destroy( &( _connection.subscriptionMutex ) ); + /* Destroy the MQTT connection used for the tests. */ + if( _pMqttConnection != NULL ) + { + IotMqtt_Disconnect( _pMqttConnection, true ); + _pMqttConnection = NULL; + } - ( void ) memset( &_connection, 0x00, sizeof( _mqttConnection_t ) ); + /* Clean up the MQTT library. */ + IotMqtt_Cleanup(); } /*-----------------------------------------------------------*/ /** - * @brief Test group runner for MQTT API tests. + * @brief Test group runner for MQTT subscription tests. */ TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) { @@ -323,6 +417,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionMultithreaded ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublish ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublishMultiple ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionReferences ); RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchTrue ); RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchFalse ); } @@ -342,18 +437,18 @@ TEST( MQTT_Unit_Subscription, ListInsertRemove ) ( void ) memset( &node2, 0x00, sizeof( _mqttSubscription_t ) ); ( void ) memset( &node3, 0x00, sizeof( _mqttSubscription_t ) ); - IotListDouble_InsertHead( &( _connection.subscriptionList ), + IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), &( node1.link ) ); - IotListDouble_InsertHead( &( _connection.subscriptionList ), + IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), &( node2.link ) ); - IotListDouble_InsertHead( &( _connection.subscriptionList ), + IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), &( node3.link ) ); IotListDouble_Remove( &( node1.link ) ); IotListDouble_Remove( &( node2.link ) ); IotListDouble_Remove( &( node3.link ) ); - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } /*-----------------------------------------------------------*/ @@ -364,41 +459,38 @@ TEST( MQTT_Unit_Subscription, ListInsertRemove ) TEST( MQTT_Unit_Subscription, ListFindByTopicFilter ) { _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { 0 }; topicMatchParams.pTopicName = "/test0"; topicMatchParams.topicNameLength = 6; /* On empty list. */ - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_topicMatch, - &topicMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_topicMatch, + &topicMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); _populateList(); /* Topic filter present. */ - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_topicMatch, - &topicMatchParams ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_topicMatch, + &topicMatchParams ); + TEST_ASSERT_NOT_EQUAL( NULL, pSubscriptionLink ); + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Topic filter not present. */ topicMatchParams.pTopicName = "/notpresent"; topicMatchParams.topicNameLength = 11; - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_topicMatch, - &topicMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_topicMatch, + &topicMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); } /*-----------------------------------------------------------*/ @@ -408,63 +500,56 @@ TEST( MQTT_Unit_Subscription, ListFindByTopicFilter ) */ TEST( MQTT_Unit_Subscription, ListFindByPacket ) { - _mqttSubscription_t * pSubscription = 0; + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _packetMatchParams_t packetMatchParams = { 0 }; packetMatchParams.packetIdentifier = 1; packetMatchParams.order = 0; /* On empty list. */ - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_packetMatch, - &packetMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_packetMatch, + &packetMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); _populateList(); /* Packet and order present. */ - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_packetMatch, - &packetMatchParams ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_packetMatch, + &packetMatchParams ); + TEST_ASSERT_NOT_EQUAL( NULL, pSubscriptionLink ); + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Packet present, order not present. */ packetMatchParams.order = _LIST_ITEM_COUNT; - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_packetMatch, - &packetMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_packetMatch, + &packetMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); /* Packet not present, order present. */ packetMatchParams.packetIdentifier = 0; packetMatchParams.order = 0; - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_packetMatch, - &packetMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_packetMatch, + &packetMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); /* Packet and order not present. */ packetMatchParams.packetIdentifier = 0; packetMatchParams.order = _LIST_ITEM_COUNT; - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_packetMatch, - &packetMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_packetMatch, + &packetMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); } /*-----------------------------------------------------------*/ @@ -477,29 +562,29 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) long i = 0; /* On empty list (should not crash). */ - AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, - 1, - 0 ); + _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, + 1, + 0 ); _populateList(); /* Remove all subscriptions by packet one-by-one. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) { - AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, - 1, - i ); + _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, + 1, + i ); } /* List should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); /* Remove all subscriptions for a packet one-shot. */ _populateList(); - AwsIotMqttInternal_RemoveSubscriptionByPacket( &_connection, - 1, - -1 ); - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, + 1, + -1 ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } /*-----------------------------------------------------------*/ @@ -511,14 +596,14 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) { size_t i = 0; char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* On empty list (should not crash). */ subscription[ 0 ].pTopicFilter = "/topic"; subscription[ 0 ].topicFilterLength = 6; - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, - &( subscription[ 0 ] ), - 1 ); + _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, + &( subscription[ 0 ] ), + 1 ); _populateList(); subscription[ 0 ].pTopicFilter = pTopicFilters[ 0 ]; @@ -531,17 +616,17 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) _TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, - &( subscription[ 0 ] ), - 1 ); + _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, + &( subscription[ 0 ] ), + 1 ); } /* List should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); /* Refill the list. */ _populateList(); - TEST_ASSERT_EQUAL_INT( false, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( false, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); /* Removal all at once. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) @@ -553,12 +638,12 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) ( unsigned long ) i ); } - AwsIotMqttInternal_RemoveSubscriptionByTopicFilter( &_connection, - subscription, - _LIST_ITEM_COUNT ); + _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, + subscription, + _LIST_ITEM_COUNT ); /* List should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } /*-----------------------------------------------------------*/ @@ -570,10 +655,11 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) { size_t i = 0; _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { 0 }; char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Set valid values in the subscription list. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) @@ -587,51 +673,49 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) } /* Add all subscriptions to the list. */ - status = AwsIotMqttInternal_AddSubscriptions( &_connection, - 1, - subscription, - _LIST_ITEM_COUNT ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + status = _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + subscription, + _LIST_ITEM_COUNT ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Change the callback information, but not the topic filter. */ subscription[ 1 ].callback.function = _publishCallback; - subscription[ 1 ].callback.param1 = &_connection; + subscription[ 1 ].callback.param1 = _pMqttConnection; /* Add the duplicate subscription. */ - status = AwsIotMqttInternal_AddSubscriptions( &_connection, - 3, - &( subscription[ 1 ] ), - 1 ); - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, status ); + status = _IotMqtt_AddSubscriptions( _pMqttConnection, + 3, + &( subscription[ 1 ] ), + 1 ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Find the subscription that was just modified. */ topicMatchParams.pTopicName = "/test1"; topicMatchParams.topicNameLength = 6; topicMatchParams.exactMatchOnly = true; - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_topicMatch, - &topicMatchParams ), - link ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_topicMatch, + &topicMatchParams ); + TEST_ASSERT_NOT_EQUAL( NULL, pSubscriptionLink ); + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Check that the information was changed. */ TEST_ASSERT_EQUAL_UINT16( 3, pSubscription->packetInfo.identifier ); TEST_ASSERT_EQUAL( 0, pSubscription->packetInfo.order ); TEST_ASSERT_EQUAL_PTR( _publishCallback, pSubscription->callback.function ); - TEST_ASSERT_EQUAL_PTR( &_connection, pSubscription->callback.param1 ); + TEST_ASSERT_EQUAL_PTR( _pMqttConnection, pSubscription->callback.param1 ); /* Check that a duplicate entry wasn't created. */ IotListDouble_Remove( &( pSubscription->link ) ); - AwsIotMqtt_FreeSubscription( pSubscription ); - pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_FindFirstMatch( &( _connection.subscriptionList ), - NULL, - AwsIotTestMqtt_topicMatch, - &topicMatchParams ), - link ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscription ); + IotMqtt_FreeSubscription( pSubscription ); + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), + NULL, + IotTestMqtt_topicMatch, + &topicMatchParams ); + TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); } /*-----------------------------------------------------------*/ @@ -643,8 +727,8 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) { size_t i = 0; char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; - AwsIotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Set valid values in the subscription list. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) @@ -662,18 +746,18 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) { UnityMalloc_MakeMallocFailAfterCount( ( int ) i ); - status = AwsIotMqttInternal_AddSubscriptions( &_connection, - 1, - subscription, - _LIST_ITEM_COUNT ); + status = _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + subscription, + _LIST_ITEM_COUNT ); - if( status == AWS_IOT_MQTT_SUCCESS ) + if( status == IOT_MQTT_SUCCESS ) { break; } - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_NO_MEMORY, status ); - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } } @@ -735,7 +819,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) } /* The subscription list should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _connection.subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } /* Destroy the synchronization barrier. */ @@ -753,8 +837,8 @@ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) TEST( MQTT_Unit_Subscription, ProcessPublish ) { bool callbackInvoked = false; - AwsIotMqttSubscription_t subscription = AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; /* Set the subscription and corresponding publish info. */ subscription.pTopicFilter = "/test"; @@ -768,15 +852,15 @@ TEST( MQTT_Unit_Subscription, ProcessPublish ) callbackParam.message.info.payloadLength = 0; /* Add the subscription. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotMqttInternal_AddSubscriptions( &_connection, - 1, - &subscription, - 1 ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + &subscription, + 1 ) ); /* Find the subscription and invoke its callback. */ - AwsIotMqttInternal_ProcessPublish( &_connection, - &callbackParam ); + _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, + &callbackParam ); /* Check that the callback was invoked. */ TEST_ASSERT_EQUAL_INT( true, callbackInvoked ); @@ -791,8 +875,8 @@ TEST( MQTT_Unit_Subscription, ProcessPublish ) TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) { bool callbackInvoked[ 3 ] = { false }; - AwsIotMqttSubscription_t subscription[ 3 ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - AwsIotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + IotMqttSubscription_t subscription[ 3 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; /* Set the subscription info. */ subscription[ 0 ].pTopicFilter = "/test"; @@ -817,15 +901,15 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) callbackParam.message.info.payloadLength = 0; /* Add the subscriptions. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, - AwsIotMqttInternal_AddSubscriptions( &_connection, - 1, - &( subscription[ 0 ] ), - 3 ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, + _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + &( subscription[ 0 ] ), + 3 ) ); /* Invoke subscription callbacks. */ - AwsIotMqttInternal_ProcessPublish( &_connection, - &callbackParam ); + _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, + &callbackParam ); /* Check that all 3 callbacks were invoked. */ TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 0 ] ); @@ -835,13 +919,119 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) /*-----------------------------------------------------------*/ +/** + * @brief Tests that subscriptions are properly reference counted. + */ +TEST( MQTT_Unit_Subscription, SubscriptionReferences ) +{ + int32_t i = 0; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + _mqttOperation_t * pIncomingPublish[ 3 ] = { NULL }; + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink; + IotSemaphore_t waitSem; + + /* Adjustment to reference count based on keep-alive status. */ + const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->keepAliveMs != 0 ) ? 1 : 0 ); + + #if ( IOT_STATIC_MEMORY_ONLY == 1 ) && ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS < 3 ) + #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS must be at least 3 for SubscriptionReferences test." + #endif + + /* The MQTT task pool must support at least 3 threads for this test to run successfully. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_SetMaxThreads( &( _IotMqttTaskPool ), 4 ) ); + + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 3 ) ); + + /* Set the subscription info. */ + subscription.pTopicFilter = "/test"; + subscription.topicFilterLength = 5; + subscription.callback.function = _blockingCallback; + subscription.callback.param1 = &waitSem; + + /* Add the subscriptions. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + &subscription, + 1 ) ); + + /* Get the pointer to the subscription in the MQTT connection. */ + pSubscriptionLink = IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ); + TEST_ASSERT_NOT_NULL( pSubscriptionLink ); + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + TEST_ASSERT_NOT_NULL( pSubscription ); + + /* Create 3 incoming PUBLISH messages that match the subscription. */ + for( i = 0; i < 3; i++ ) + { + pIncomingPublish[ i ] = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + TEST_ASSERT_NOT_NULL( pIncomingPublish ); + + ( void ) memset( pIncomingPublish[ i ], 0x00, sizeof( _mqttOperation_t ) ); + pIncomingPublish[ i ]->incomingPublish = true; + pIncomingPublish[ i ]->pMqttConnection = _pMqttConnection; + pIncomingPublish[ i ]->publishInfo.pTopicName = "/test"; + pIncomingPublish[ i ]->publishInfo.topicNameLength = 5; + pIncomingPublish[ i ]->publishInfo.pPayload = ""; + } + + if( TEST_PROTECT() ) + { + /* Schedule 3 callback invocations for the incoming PUBLISH. */ + for( i = 0; i < 3; i++ ) + { + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pIncomingPublish[ i ], + _IotMqtt_ProcessIncomingPublish, + 0 ) ); + } + + /* Wait for the connection reference count to reach 3 (adjusted for possible keep-alive). */ + TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( _pMqttConnection->referencesMutex ), + &( _pMqttConnection->references ), + 3 + keepAliveReference ) ); + + /* Check that the subscription also has a reference count of 3. */ + TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( _pMqttConnection->subscriptionMutex ), + &( pSubscription->references ), + 3 ) ); + + /* Post to the wait semaphore, which unblocks one subscription callback. */ + IotSemaphore_Post( &waitSem ); + + /* Wait for the connection reference count to decrease to 2 (adjusted for + * possible keep-alive). Check that the subscription reference count also + * decreases to 2. */ + TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( _pMqttConnection->referencesMutex ), + &( _pMqttConnection->references ), + 2 + keepAliveReference ) ); + TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( _pMqttConnection->subscriptionMutex ), + &( pSubscription->references ), + 2 ) ); + + /* Shut down the MQTT connection. */ + IotMqtt_Disconnect( _pMqttConnection, true ); + + /* Post twice to the wait semaphore, which unblocks the remaining blocking + * callbacks. */ + IotSemaphore_Post( &waitSem ); + IotSemaphore_Post( &waitSem ); + + /* Clear the MQTT connection pointer so test cleanup does not double-free it. */ + _pMqttConnection = NULL; + } + + IotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests result of matching topic filters and topic names. */ TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) { _mqttSubscription_t * pTopicFilter = - AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); + IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); @@ -874,7 +1064,7 @@ TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) _TEST_TOPIC_MATCH( "aws/iot/shadow/thing/temp", "aws/+/shadow/#", false, true ); } - AwsIotMqtt_FreeSubscription( pTopicFilter ); + IotMqtt_FreeSubscription( pTopicFilter ); } /*-----------------------------------------------------------*/ @@ -886,7 +1076,7 @@ TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) TEST( MQTT_Unit_Subscription, TopicFilterMatchFalse ) { _mqttSubscription_t * pTopicFilter = - AwsIotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); + IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); @@ -918,7 +1108,7 @@ TEST( MQTT_Unit_Subscription, TopicFilterMatchFalse ) _TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/+/#", false, false ); } - AwsIotMqtt_FreeSubscription( pTopicFilter ); + IotMqtt_FreeSubscription( pTopicFilter ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c similarity index 57% rename from tests/mqtt/unit/aws_iot_tests_mqtt_validate.c rename to tests/mqtt/unit/iot_tests_mqtt_validate.c index 127c60f9b7..9659dd2cb8 100644 --- a/tests/mqtt/unit/aws_iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_tests_mqtt_validate.c - * @brief Tests for the functions in aws_iot_mqtt_validate.c + * @file iot_tests_mqtt_validate.c + * @brief Tests for the functions in iot_mqtt_validate.c */ /* Build using a config header, if provided. */ @@ -33,13 +33,26 @@ #include /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* Test framework includes. */ #include "unity_fixture.h" /*-----------------------------------------------------------*/ +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 + #define _AWS_IOT_MQTT_SERVER true +#else + #define _AWS_IOT_MQTT_SERVER false + +/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ + #undef IOT_MQTT_CONNECT_INFO_INITIALIZER + #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } +#endif + /** * @brief Length of the subscription array used in * #TEST_MQTT_Unit_Validate_ValidateSubscriptionList_. @@ -93,120 +106,120 @@ TEST_GROUP_RUNNER( MQTT_Unit_Validate ) /*-----------------------------------------------------------*/ /** - * @brief Test validation of an #AwsIotMqttNetIf_t. + * @brief Test validation of an #IotMqttNetIf_t. */ TEST( MQTT_Unit_Validate, ValidateNetIf ) { bool validateStatus = false; - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; /* NULL parameter. */ - validateStatus = AwsIotMqttInternal_ValidateNetIf( NULL ); + validateStatus = _IotMqtt_ValidateNetIf( NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Uninitialized parameter. */ - validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Uninitialized disconnect function is allowed. */ networkInterface.send = _FUNCTION_POINTER; - validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Check serializer override function pointers. */ - #if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 /* No freePacket function with serializer. */ networkInterface.serialize.disconnect = _FUNCTION_POINTER; - validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); networkInterface.serialize.disconnect = NULL; /* No freePacket function with deserializer. */ networkInterface.deserialize.pingresp = _FUNCTION_POINTER; - validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* freePacket function pointer set. */ networkInterface.freePacket = _FUNCTION_POINTER; - validateStatus = AwsIotMqttInternal_ValidateNetIf( &networkInterface ); + validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - #endif /* if AWS_IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ } /*-----------------------------------------------------------*/ /** - * @brief Test validation of an #AwsIotMqttConnectInfo_t. + * @brief Test validation of an #IotMqttConnectInfo_t. */ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) { bool validateStatus = false; - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; /* NULL parameter. */ - validateStatus = AwsIotMqttInternal_ValidateConnect( NULL ); + validateStatus = _IotMqtt_ValidateConnect( NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Uninitialized parameter. */ - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Zero-length client identifier with clean session. */ connectInfo.cleanSession = true; connectInfo.pClientIdentifier = ""; connectInfo.clientIdentifierLength = 0; - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Client identifier longer than 23 characters. */ connectInfo.pClientIdentifier = "longlongclientidentifier"; connectInfo.clientIdentifierLength = 24; - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* AWS IoT MQTT service limit tests. */ - #if AWS_IOT_MQTT_MODE == 1 + #if _AWS_IOT_MQTT_SERVER == true /* Client identifier too long. */ connectInfo.clientIdentifierLength = _AWS_IOT_MQTT_SERVER_MAX_CLIENTID + 1; - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); connectInfo.clientIdentifierLength = 24; /* Keep-alive disabled. */ connectInfo.keepAliveSeconds = 0; - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Keep-alive too small. */ connectInfo.keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE - 1; - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Keep-alive too large. */ connectInfo.keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE + 1; - validateStatus = AwsIotMqttInternal_ValidateConnect( &connectInfo ); + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - #endif /* if AWS_IOT_MQTT_MODE == 1 */ + #endif /* if _AWS_IOT_MQTT_SERVER == true */ } /*-----------------------------------------------------------*/ /** - * @brief Test validation of an #AwsIotMqttPublishInfo_t. + * @brief Test validation of an #IotMqttPublishInfo_t. */ TEST( MQTT_Unit_Validate, ValidatePublish ) { bool validateStatus = false; - AwsIotMqttPublishInfo_t publishInfo = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* NULL parameter. */ - validateStatus = AwsIotMqttInternal_ValidatePublish( false, NULL ); + validateStatus = _IotMqtt_ValidatePublish( false, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Zero-length topic name. */ publishInfo.pTopicName = ""; publishInfo.topicNameLength = 0; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.pTopicName = "/test"; @@ -215,71 +228,72 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) /* Zero-length/NULL payload. */ publishInfo.pPayload = NULL; publishInfo.payloadLength = 0; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* NULL payload only allowed with length 0. */ publishInfo.pPayload = NULL; publishInfo.payloadLength = 1; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.payloadLength = 0; /* Negative QoS or QoS > 2. */ - publishInfo.QoS = -1; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + publishInfo.qos = -1; + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.QoS = 3; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + publishInfo.qos = 3; + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.QoS = 0; + publishInfo.qos = IOT_MQTT_QOS_0; /* Negative retry limit. */ publishInfo.retryLimit = -1; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Positive retry limit with no period. */ publishInfo.retryLimit = 1; publishInfo.retryMs = 0; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Positive retry limit with positive period. */ publishInfo.retryLimit = 1; publishInfo.retryMs = 1; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Retry limit 0. */ publishInfo.retryLimit = 0; - validateStatus = AwsIotMqttInternal_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* AWS IoT MQTT service limit tests. */ + #if _AWS_IOT_MQTT_SERVER == true + /* QoS 2. */ + publishInfo.qos = IOT_MQTT_QOS_2; + validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.qos = IOT_MQTT_QOS_0; - /* QoS 2. */ - publishInfo.QoS = 2; - validateStatus = AwsIotMqttInternal_ValidatePublish( true, &publishInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.QoS = 0; - - /* Retained message. */ - publishInfo.retain = true; - validateStatus = AwsIotMqttInternal_ValidatePublish( true, &publishInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.retain = false; + /* Retained message. */ + publishInfo.retain = true; + validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + publishInfo.retain = false; - /* Topic name too long. */ - publishInfo.topicNameLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; - validateStatus = AwsIotMqttInternal_ValidatePublish( true, &publishInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* Topic name too long. */ + publishInfo.topicNameLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; + validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + #endif /* if _AWS_IOT_MQTT_SERVER == true */ } /*-----------------------------------------------------------*/ /** - * @brief Test validation of an #AwsIotMqttReference_t. + * @brief Test validation of an #IotMqttReference_t. */ TEST( MQTT_Unit_Validate, ValidateReference ) { @@ -289,41 +303,41 @@ TEST( MQTT_Unit_Validate, ValidateReference ) ( void ) memset( &reference, 0x00, sizeof( _mqttOperation_t ) ); /* NULL parameter. */ - validateStatus = AwsIotMqttInternal_ValidateReference( NULL ); + validateStatus = _IotMqtt_ValidateReference( NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Non-waitable reference. */ reference.flags = 0; - validateStatus = AwsIotMqttInternal_ValidateReference( &reference ); + validateStatus = _IotMqtt_ValidateReference( &reference ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Waitable (valid) reference. */ - reference.flags = AWS_IOT_MQTT_FLAG_WAITABLE; - validateStatus = AwsIotMqttInternal_ValidateReference( &reference ); + reference.flags = IOT_MQTT_FLAG_WAITABLE; + validateStatus = _IotMqtt_ValidateReference( &reference ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); } /*-----------------------------------------------------------*/ /** - * @brief Test validation of a list of #AwsIotMqttSubscription_t. + * @brief Test validation of a list of #IotMqttSubscription_t. */ TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) { size_t i = 0; bool validateStatus = false; - AwsIotMqttSubscription_t pSubscriptions[ _SUBSCRIPTION_COUNT ] = { AWS_IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t pSubscriptions[ _SUBSCRIPTION_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* NULL parameter. */ - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, NULL, 1 ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, NULL, 1 ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Zero parameter. */ - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, 0 ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, 0 ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Uninitialized subscriptions. */ - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Initialize all subscriptions to valid values. */ @@ -334,112 +348,113 @@ TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) pSubscriptions[ i ].callback.function = _FUNCTION_POINTER; } - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* One subscription with invalid QoS. */ - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].QoS = -1; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = -1; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].QoS = 3; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = 3; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* QoS is not validated for UNSUBSCRIBE. */ - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].QoS = 0; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = IOT_MQTT_QOS_0; /* One subscription with no callback. */ pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].callback.function = NULL; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Callback is not validated for UNSUBSCRIBE. */ - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].callback.function = _FUNCTION_POINTER; /* Valid subscription filters. */ pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 1; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/#"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+/"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/+/+/+"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 7; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Invalid subscription filters. */ pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/#"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a#"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a+"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+a"; pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* AWS IoT MQTT service limit tests. */ + #if _AWS_IOT_MQTT_SERVER == true + /* Too many subscriptions. */ + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + true, + pSubscriptions, + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE + 1 ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); - /* Too many subscriptions. */ - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, - true, - pSubscriptions, - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE + 1 ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* QoS 2. */ - pSubscriptions[ 0 ].QoS = 2; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, - true, - pSubscriptions, - _SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ 0 ].QoS = 0; - - /* Topic filter too long. */ - pSubscriptions[ 0 ].topicFilterLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; - validateStatus = AwsIotMqttInternal_ValidateSubscriptionList( AWS_IOT_MQTT_SUBSCRIBE, - true, - pSubscriptions, - _SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* QoS 2. */ + pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + true, + pSubscriptions, + _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_0; + + /* Topic filter too long. */ + pSubscriptions[ 0 ].topicFilterLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + true, + pSubscriptions, + _SUBSCRIPTION_COUNT ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + #endif /* if _AWS_IOT_MQTT_SERVER == true */ } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index de587b1281..870d76c669 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -1,7 +1,7 @@ # Shadow tests executable. add_executable( aws_iot_tests_shadow aws_iot_tests_shadow.c - ${CMAKE_SOURCE_DIR}/tests/aws_iot_tests_network.c + ${CMAKE_SOURCE_DIR}/tests/iot_tests_network.c unit/aws_iot_tests_shadow_api.c unit/aws_iot_tests_shadow_parser.c system/aws_iot_tests_shadow_system.c ) diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index 488c9ad0a3..6807279ae9 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -50,12 +50,12 @@ static void _signalHandler( int signum ) if( signum == SIGSEGV ) { printf( "\nSegmentation fault.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } else if( signum == SIGABRT ) { printf( "\nAssertion failed.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } } @@ -72,18 +72,18 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return -1; + return EXIT_FAILURE; } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return -1; + return EXIT_FAILURE; } /* Initialize the common libraries before running the tests. */ if( IotCommon_Init() == false ) { - return -1; + return EXIT_FAILURE; } /* Unity setup. */ @@ -113,9 +113,13 @@ int main( int argc, /* Clean up common libraries. */ IotCommon_Cleanup(); - /* Return the number of test failures. This will cause a non-zero exit code - * if any test fails. */ - return UNITY_END(); + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index e750122335..a5e6c7becd 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -72,8 +72,8 @@ extern int snprintf( char *, * * Provide default values of test configuration constants. */ -#ifndef AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S - #define AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) +#ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S + #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) #endif #ifndef AWS_IOT_TEST_SHADOW_TIMEOUT #define AWS_IOT_TEST_SHADOW_TIMEOUT ( 5000 ) @@ -116,13 +116,18 @@ typedef struct _operationCompleteParams /* Network functions used by the tests, declared and implemented in one of * the test network function files. */ -extern bool AwsIotTest_NetworkSetup( void ); -extern void AwsIotTest_NetworkCleanup( void ); +extern bool IotTest_NetworkSetup( void ); +extern void IotTest_NetworkCleanup( void ); /* Network variables used by the tests, declared in one of the test network * function files. */ -extern AwsIotMqttNetIf_t _AwsIotTestNetworkInterface; -extern AwsIotMqttConnection_t _AwsIotTestMqttConnection; +extern IotMqttNetIf_t _IotTestNetworkInterface; +extern IotMqttConnection_t _IotTestMqttConnection; + +/** + * @brief Tracks whether connection cleanup should be done. + */ +static bool _connectionCreated = false; /*-----------------------------------------------------------*/ @@ -261,7 +266,7 @@ static void _updatedCallback( void * pArgument, /** * @brief Run the Update-Get-Delete asynchronous tests at various QoS. */ -static void _updateGetDeleteAsync( int QoS ) +static void _updateGetDeleteAsync( IotMqttQos_t qos ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; @@ -275,7 +280,7 @@ static void _updateGetDeleteAsync( int QoS ) /* Initialize the common members of the Shadow document info. */ documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; documentInfo.thingNameLength = _THING_NAME_LENGTH; - documentInfo.QoS = QoS; + documentInfo.qos = qos; /* Create the wait semaphore for operations. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); @@ -290,7 +295,7 @@ static void _updateGetDeleteAsync( int QoS ) documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ - status = AwsIotShadow_Update( _AwsIotTestMqttConnection, + status = AwsIotShadow_Update( _IotTestMqttConnection, &documentInfo, 0, &callbackInfo, @@ -306,7 +311,7 @@ static void _updateGetDeleteAsync( int QoS ) callbackParam.expectedType = AWS_IOT_SHADOW_GET_COMPLETE; /* Retrieve the Shadow document. */ - status = AwsIotShadow_Get( _AwsIotTestMqttConnection, + status = AwsIotShadow_Get( _IotTestMqttConnection, &documentInfo, 0, &callbackInfo, @@ -323,7 +328,7 @@ static void _updateGetDeleteAsync( int QoS ) callbackParam.expectedType = AWS_IOT_SHADOW_DELETE_COMPLETE; /* Delete the Shadow document. */ - status = AwsIotShadow_Delete( _AwsIotTestMqttConnection, + status = AwsIotShadow_Delete( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -345,7 +350,7 @@ static void _updateGetDeleteAsync( int QoS ) /** * @brief Run the Update-Get-Delete blocking tests at various QoS. */ -static void _updateGetDeleteBlocking( int QoS ) +static void _updateGetDeleteBlocking( IotMqttQos_t qos ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; @@ -355,24 +360,24 @@ static void _updateGetDeleteBlocking( int QoS ) /* Initialize the common members of the Shadow document info. */ documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; documentInfo.thingNameLength = _THING_NAME_LENGTH; - documentInfo.QoS = QoS; + documentInfo.qos = qos; /* Set the members of the Shadow document info for UPDATE. */ documentInfo.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ - status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, &documentInfo, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Set the members of the Shadow document info for GET. */ - documentInfo.get.mallocDocument = AwsIotTest_Malloc; + documentInfo.get.mallocDocument = IotTest_Malloc; /* Retrieve the Shadow document. */ - status = AwsIotShadow_TimedGet( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedGet( _IotTestMqttConnection, &documentInfo, 0, AWS_IOT_TEST_SHADOW_TIMEOUT, @@ -393,10 +398,10 @@ static void _updateGetDeleteBlocking( int QoS ) TEST_ASSERT_EQUAL_STRING_LEN( "\"value\"", pJsonValue, jsonValueLength ); /* Free the retrieved Shadow document. */ - AwsIotTest_Free( ( void * ) pShadowDocument ); + IotTest_Free( ( void * ) pShadowDocument ); /* Delete the Shadow document. */ - status = AwsIotShadow_TimedDelete( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedDelete( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -418,44 +423,48 @@ TEST_GROUP( Shadow_System ); */ TEST_SETUP( Shadow_System ) { - AwsIotMqttConnectInfo_t connectInfo = AWS_IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; /* Set up the network stack. */ - if( AwsIotTest_NetworkSetup() == false ) + if( IotTest_NetworkSetup() == false ) { TEST_FAIL_MESSAGE( "Failed to set up network connection." ); } /* Initialize the MQTT library. */ - if( AwsIotMqtt_Init() != AWS_IOT_MQTT_SUCCESS ) + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } + /* Initialize the Shadow library. */ + if( AwsIotShadow_Init( 0 ) != AWS_IOT_SHADOW_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize Shadow library." ); + } + /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT * client identifier. */ connectInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); - connectInfo.keepAliveSeconds = AWS_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; /* Establish an MQTT connection. */ - if( AwsIotMqtt_Connect( &_AwsIotTestMqttConnection, - &_AwsIotTestNetworkInterface, - &connectInfo, - AWS_IOT_TEST_SHADOW_TIMEOUT ) != AWS_IOT_MQTT_SUCCESS ) + if( IotMqtt_Connect( &_IotTestMqttConnection, + &_IotTestNetworkInterface, + &connectInfo, + AWS_IOT_TEST_SHADOW_TIMEOUT ) != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); } - - /* Initialize the Shadow library. */ - if( AwsIotShadow_Init( 0 ) != AWS_IOT_SHADOW_SUCCESS ) + else { - TEST_FAIL_MESSAGE( "Failed to initialize Shadow library." ); + _connectionCreated = true; } /* Delete any existing Shadow so all tests start with no Shadow. */ - status = AwsIotShadow_TimedDelete( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedDelete( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -476,18 +485,23 @@ TEST_SETUP( Shadow_System ) */ TEST_TEAR_DOWN( Shadow_System ) { - /* Disconnect the MQTT connection. */ - AwsIotMqtt_Disconnect( _AwsIotTestMqttConnection, false ); + /* Disconnect the MQTT connection if it was created. */ + if( _connectionCreated == true ) + { + IotMqtt_Disconnect( _IotTestMqttConnection, false ); + + _connectionCreated = false; + } /* Clean up the Shadow library. */ AwsIotShadow_Cleanup(); /* Clean up the network stack. */ - AwsIotTest_NetworkCleanup(); + IotTest_NetworkCleanup(); /* Clean up the MQTT library. */ - AwsIotMqtt_Cleanup(); - _AwsIotTestMqttConnection = AWS_IOT_MQTT_CONNECTION_INITIALIZER; + IotMqtt_Cleanup(); + _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ @@ -512,7 +526,7 @@ TEST_GROUP_RUNNER( Shadow_System ) */ TEST( Shadow_System, UpdateGetDeleteAsyncQoS0 ) { - _updateGetDeleteAsync( 0 ); + _updateGetDeleteAsync( IOT_MQTT_QOS_0 ); } /*-----------------------------------------------------------*/ @@ -522,7 +536,7 @@ TEST( Shadow_System, UpdateGetDeleteAsyncQoS0 ) */ TEST( Shadow_System, UpdateGetDeleteAsyncQoS1 ) { - _updateGetDeleteAsync( 1 ); + _updateGetDeleteAsync( IOT_MQTT_QOS_1 ); } /*-----------------------------------------------------------*/ @@ -532,7 +546,7 @@ TEST( Shadow_System, UpdateGetDeleteAsyncQoS1 ) */ TEST( Shadow_System, UpdateGetDeleteBlockingQoS0 ) { - _updateGetDeleteBlocking( 0 ); + _updateGetDeleteBlocking( IOT_MQTT_QOS_0 ); } /*-----------------------------------------------------------*/ @@ -542,7 +556,7 @@ TEST( Shadow_System, UpdateGetDeleteBlockingQoS0 ) */ TEST( Shadow_System, UpdateGetDeleteBlockingQoS1 ) { - _updateGetDeleteBlocking( 1 ); + _updateGetDeleteBlocking( IOT_MQTT_QOS_1 ); } /*-----------------------------------------------------------*/ @@ -573,7 +587,7 @@ TEST( Shadow_System, DeltaCallback ) if( TEST_PROTECT() ) { /* Set the delta callback. */ - status = AwsIotShadow_SetDeltaCallback( _AwsIotTestMqttConnection, + status = AwsIotShadow_SetDeltaCallback( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -581,7 +595,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Create a Shadow document with a desired state. */ - status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, &updateDocument, AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -592,7 +606,7 @@ TEST( Shadow_System, DeltaCallback ) updateDocument.update.updateDocumentLength = 67; /* Create a Shadow document with a reported state. */ - status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, &updateDocument, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -605,7 +619,7 @@ TEST( Shadow_System, DeltaCallback ) } /* Remove the delta callback. */ - status = AwsIotShadow_SetDeltaCallback( _AwsIotTestMqttConnection, + status = AwsIotShadow_SetDeltaCallback( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -613,7 +627,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Remove persistent subscriptions for Shadow Update. */ - status = AwsIotShadow_RemovePersistentSubscriptions( _AwsIotTestMqttConnection, + status = AwsIotShadow_RemovePersistentSubscriptions( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ); @@ -648,7 +662,7 @@ TEST( Shadow_System, UpdatedCallback ) if( TEST_PROTECT() ) { /* Set the updated callback. */ - status = AwsIotShadow_SetUpdatedCallback( _AwsIotTestMqttConnection, + status = AwsIotShadow_SetUpdatedCallback( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -656,7 +670,7 @@ TEST( Shadow_System, UpdatedCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Create a Shadow document. */ - status = AwsIotShadow_TimedUpdate( _AwsIotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, &updateDocument, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -669,7 +683,7 @@ TEST( Shadow_System, UpdatedCallback ) } /* Remove the updated callback. */ - status = AwsIotShadow_SetUpdatedCallback( _AwsIotTestMqttConnection, + status = AwsIotShadow_SetUpdatedCallback( _IotTestMqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 489e17294e..21aaf93554 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -21,7 +21,7 @@ /** * @file aws_iot_tests_shadow_api.c - * @brief Tests for the user-facing API functions (declared in aws_iot_shadwo.h). + * @brief Tests for the user-facing API functions (declared in aws_iot_shadow.h). */ /* Build using a config header, if provided. */ @@ -48,7 +48,7 @@ #undef _LIBRARY_LOG_LEVEL /* MQTT internal include. */ -#include "private/aws_iot_mqtt_internal.h" +#include "private/iot_mqtt_internal.h" /* Undefine logging configuration set in MQTT internal header. */ #undef _LIBRARY_LOG_NAME @@ -62,7 +62,7 @@ #include "unity_fixture.h" /* MQTT test access include. */ -#include "aws_iot_test_access_mqtt.h" +#include "iot_test_access_mqtt.h" /* Require Shadow library asserts to be enabled for these tests. The Shadow * assert function is used to abort the tests on failure from the MQTT send @@ -183,12 +183,12 @@ static void _receiveThread( void * pArgument ) } /* Call the MQTT receive callback to process the ACK packet. */ - bytesProcessed = AwsIotMqtt_ReceiveCallback( ( AwsIotMqttConnection_t * ) &_pMqttConnection, - NULL, - pReceivedData, - receivedDataLength, - 0, - NULL ); + bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, + NULL, + pReceivedData, + receivedDataLength, + 0, + NULL ); AwsIotShadow_Assert( bytesProcessed == ( int32_t ) receivedDataLength ); IotMutex_Unlock( &_lastPacketMutex ); @@ -204,9 +204,9 @@ static size_t _sendSuccess( void * pSendContext, const uint8_t * const pMessage, size_t messageLength ) { - AwsIotMqttError_t status = AWS_IOT_MQTT_STATUS_PENDING; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; const uint8_t * pPacketIdentifier = NULL; - AwsIotMqttPublishInfo_t decodedPublish = AWS_IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttPublishInfo_t decodedPublish = IOT_MQTT_PUBLISH_INFO_INITIALIZER; size_t publishBytesProcessed = 0; /* Ignore the send context. */ @@ -216,7 +216,7 @@ static size_t _sendSuccess( void * pSendContext, IotMutex_Lock( &_lastPacketMutex ); /* Set the last packet type based on the outgoing message. */ - switch( AwsIotMqttInternal_GetPacketType( pMessage, messageLength ) ) + switch( _IotMqtt_GetPacketType( pMessage, messageLength ) ) { case ( _MQTT_PACKET_TYPE_PUBLISH & 0xf0 ): @@ -254,25 +254,25 @@ static size_t _sendSuccess( void * pSendContext, /* Decode the remaining length. */ if( _lastPacketType != _MQTT_PACKET_TYPE_PUBLISH ) { - status = AwsIotTestMqtt_decodeRemainingLength( pMessage + 1, - &pPacketIdentifier, - NULL ); + status = IotTestMqtt_decodeRemainingLength( pMessage + 1, + &pPacketIdentifier, + NULL ); /* Save the packet identifier as the last packet identifier. */ _lastPacketIdentifier = _UINT16_DECODE( pPacketIdentifier ); } else { - status = AwsIotMqttInternal_DeserializePublish( pMessage, - messageLength, - &decodedPublish, - &_lastPacketIdentifier, - &publishBytesProcessed ); + status = _IotMqtt_DeserializePublish( pMessage, + messageLength, + &decodedPublish, + &_lastPacketIdentifier, + &publishBytesProcessed ); AwsIotShadow_Assert( publishBytesProcessed == messageLength ); } - AwsIotShadow_Assert( status == AWS_IOT_MQTT_SUCCESS ); + AwsIotShadow_Assert( status == IOT_MQTT_SUCCESS ); /* Set the receive thread to run after a "network round-trip". */ IotClock_TimerArm( &_receiveTimer, @@ -300,7 +300,7 @@ TEST_GROUP( Shadow_Unit_API ); */ TEST_SETUP( Shadow_Unit_API ) { - AwsIotMqttNetIf_t networkInterface = AWS_IOT_MQTT_NETIF_INITIALIZER; + IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; /* Clear the last packet type and identifier. */ _lastPacketType = 0; @@ -315,15 +315,15 @@ TEST_SETUP( Shadow_Unit_API ) NULL ); /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( AWS_IOT_MQTT_SUCCESS, AwsIotMqtt_Init() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); /* Set the network interface send function. */ networkInterface.send = _sendSuccess; /* Initialize the MQTT connection object to use for the Shadow tests. */ - _pMqttConnection = AwsIotTestMqtt_createMqttConnection( false, - &networkInterface, - 0 ); + _pMqttConnection = IotTestMqtt_createMqttConnection( false, + &networkInterface, + 0 ); /* Initialize the Shadow library. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); @@ -340,10 +340,10 @@ TEST_TEAR_DOWN( Shadow_Unit_API ) AwsIotShadow_Cleanup(); /* Clean up the MQTT connection object. */ - AwsIotTestMqtt_destroyMqttConnection( _pMqttConnection ); + IotMqtt_Disconnect( _pMqttConnection, true ); /* Clean up the MQTT library. */ - AwsIotMqtt_Cleanup(); + IotMqtt_Cleanup(); /* Destroy the receive thread timer. */ IotClock_TimerDestroy( &_receiveTimer ); @@ -497,14 +497,14 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; /* Invalid QoS. */ - documentInfo.QoS = 3; + documentInfo.qos = 3; status = AwsIotShadow_Get( _pMqttConnection, &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, &reference ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - documentInfo.QoS = 0; + documentInfo.qos = IOT_MQTT_QOS_0; /* Invalid retry parameters. */ documentInfo.retryLimit = -1; @@ -597,7 +597,7 @@ TEST( Shadow_Unit_API, WaitInvalidParameters ) */ TEST( Shadow_Unit_API, DeleteMallocFail ) { - int i = 0; + int32_t i = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; @@ -641,7 +641,7 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) */ TEST( Shadow_Unit_API, GetMallocFail ) { - int i = 0; + int32_t i = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; @@ -651,8 +651,8 @@ TEST( Shadow_Unit_API, GetMallocFail ) /* Set the members of the document info. */ documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; - documentInfo.QoS = 1; - documentInfo.get.mallocDocument = AwsIotTest_Malloc; + documentInfo.qos = IOT_MQTT_QOS_1; + documentInfo.get.mallocDocument = IotTest_Malloc; for( i = 0; ; i++ ) { @@ -689,7 +689,7 @@ TEST( Shadow_Unit_API, GetMallocFail ) TEST( Shadow_Unit_API, UpdateMallocFail ) { - int i = 0; + int32_t i = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; @@ -697,7 +697,7 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) /* Set the members of the document info. */ documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; - documentInfo.QoS = 1; + documentInfo.qos = IOT_MQTT_QOS_1; documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}},\"clientToken\":\"TEST\"}"; documentInfo.update.updateDocumentLength = 50; diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index eb4a1fb5ff..4a07ae2989 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -119,8 +119,8 @@ static void _generateParseErrorDocument( char * const pErrorDocument, /* Parse the error document and check the result. */ TEST_ASSERT_EQUAL( expectedCode, - AwsIotShadowInternal_ParseErrorDocument( pErrorDocument, - ( size_t ) errorDocumentLength ) ); + _AwsIotShadow_ParseErrorDocument( pErrorDocument, + ( size_t ) errorDocumentLength ) ); } /*-----------------------------------------------------------*/ @@ -137,10 +137,10 @@ static void _parseThingName( const char * const pTopicName, const char * pThingName = NULL; size_t thingNameLength = 0; - status = AwsIotShadowInternal_ParseThingName( pTopicName, - topicNameLength, - &pThingName, - &thingNameLength ); + status = _AwsIotShadow_ParseThingName( pTopicName, + topicNameLength, + &pThingName, + &thingNameLength ); TEST_ASSERT_EQUAL( expectedResult, status ); if( expectedResult == AWS_IOT_SHADOW_SUCCESS ) @@ -205,13 +205,13 @@ TEST( Shadow_Unit_Parser, StatusValid ) _shadowOperationStatus_t status = _UNKNOWN_STATUS; /* Parse "accepted" status. */ - status = AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/accepted", - 39 ); + status = _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/accepted", + 39 ); TEST_ASSERT_EQUAL( _SHADOW_ACCEPTED, status ); /* Parse "rejected" status. */ - status = AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/rejected", - 39 ); + status = _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/rejected", + 39 ); TEST_ASSERT_EQUAL( _SHADOW_REJECTED, status ); } @@ -224,23 +224,23 @@ TEST( Shadow_Unit_Parser, StatusInvalid ) { /* Topic too short. */ TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, - AwsIotShadowInternal_ParseShadowStatus( "accepted", - 8 ) ); + _AwsIotShadow_ParseShadowStatus( "accepted", + 8 ) ); /* Topic missing last character. */ TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, - AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/accepte", - 38 ) ); + _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/accepte", + 38 ) ); /* Topic missing level separator. */ TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, - AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadowaccepted", - 38 ) ); + _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadowaccepted", + 38 ) ); /* Topic suffix isn't "accepted" or "rejected". */ TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, - AwsIotShadowInternal_ParseShadowStatus( "$aws/things/Test_device/shadow/unknown", - 38 ) ); + _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/unknown", + 38 ) ); } /*-----------------------------------------------------------*/ From 331189d2fa899c969f4e4cc5addea5a40fe3c69c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 22 Feb 2019 11:13:29 -0800 Subject: [PATCH 025/844] Fix debug log build and update CI. --- .travis.yml | 2 +- lib/source/mqtt/iot_mqtt_api.c | 4 ++-- scripts/build_check_commit.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1b91b9af81..3f5b2aa54f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,4 +34,4 @@ script: after_success: # Send code coverage report to Coveralls, but only for one of the build jobs. - - if [ "$CC" = "gcc" ]; then bash ./scripts/coverage.sh; fi + - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$CC" = "gcc" ]; then bash ./scripts/coverage.sh; fi diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 3b95bb5261..5ae3772ad6 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -1721,7 +1721,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, if( pOperation->operation == IOT_MQTT_SUBSCRIBE ) { IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" - " subscriptions of timed-out SUBSCRIBE." + " subscriptions of timed-out SUBSCRIBE.", pMqttConnection, pOperation ); @@ -1743,7 +1743,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, } IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", - pMqttConnection + pMqttConnection, IotMqtt_OperationType( pOperation->operation ), pOperation, IotMqtt_strerror( status ) ); diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index 1970241974..f6a5ae6dbc 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -45,7 +45,7 @@ make # Rebuild in static memory mode. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" make # Run common tests in static memory mode (no network required). From b28147378ae5462cec555fc80ff5f575333f8e17 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 22 Feb 2019 11:39:45 -0800 Subject: [PATCH 026/844] Update names of config in README. (#281) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b53eb4b1f9..650317aa6e 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,12 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. * 4. [Create an AWS IoT Policy](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-policy.html) 5. [Attach an AWS IoT Policy to a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/attach-policy-to-certificate.html) 6. [Attach a Certificate to a Thing](https://docs.aws.amazon.com/iot/latest/developerguide/attach-cert-thing.html) -2. *Optional:* Set the following `#define` in [aws_iot_demo_config.h](demos/aws_iot_demo_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. +2. *Optional:* Set the following `#define` in [iot_demo_config.h](demos/aws_iot_demo_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. - Set `AWS_IOT_DEMO_THING_NAME` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. - - Set `AWS_IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. - - Set `AWS_IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - - Set `AWS_IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - - Set `AWS_IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. + - Set `IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. + - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. + - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. + - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. 3. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build From 4370d618509a1535a4cd0137ac8cc0043cd885e1 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Sun, 24 Feb 2019 18:56:01 -0500 Subject: [PATCH 027/844] Updated cbmc proof instructions in cbmc-batch.yaml. (#284) A file cbmc-batch.yaml in each proof directory under cbmc/proofs gives instructions for how to recheck the proof in that directory with CBMC. The proofs are rechecked each time a commit or pull request is added to the repository. A major commit changed the names of the loop labels CBMC uses (by changing the names of the functions containing the loops). So the CBMC proof instructions had to be updated, too, by running 'make cbmc-batch.yaml' in that directory. --- cbmc/proofs/DeserializeSuback/cbmc-batch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml index e98f729bd3..d3a9d2e005 100644 --- a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml +++ b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml @@ -1,4 +1,4 @@ jobos: ubuntu16 -cbmcflags: '=--unwindset;AwsIotMqttInternal_DeserializeSuback.0:200;--bounds-check;--pointer-check;--unwinding-assertions=' +cbmcflags: '=--unwindset;_IotMqtt_DeserializeSuback.0:100;--bounds-check;--pointer-check;--unwinding-assertions=' goto: DeserializeSuback.goto expected: SUCCESSFUL From 2006a25f1cf40b87f348ee50822a1e6d2dcc9cc4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 25 Feb 2019 13:43:41 -0800 Subject: [PATCH 028/844] Fix misprinted log message. (#285) --- lib/source/mqtt/iot_mqtt_operation.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 37734a985f..dcbb5941e3 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -1241,7 +1241,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) _IotMqtt_ProcessCompletedOperation, 0 ); - if( status != IOT_MQTT_SUCCESS ) + if( status == IOT_MQTT_SUCCESS ) { IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", pOperation->pMqttConnection, From 75136edcc5e2aa104a7204345d04e4c8ed36d28c Mon Sep 17 00:00:00 2001 From: qiutongs Date: Mon, 25 Feb 2019 15:10:34 -0800 Subject: [PATCH 029/844] Add tinycbor library. (#277) --- CMakeLists.txt | 6 +- third_party/include/cbor.h | 486 ++++++ third_party/tinycbor/CMakeLists.txt | 11 + third_party/tinycbor/LICENSE | 21 + third_party/tinycbor/assert_p.h | 29 + third_party/tinycbor/cborconstants_p.h | 52 + third_party/tinycbor/cborencoder.c | 626 ++++++++ .../cborencoder_close_container_checked.c | 82 ++ third_party/tinycbor/cborerrorstrings.c | 165 +++ third_party/tinycbor/cborparser.c | 1300 +++++++++++++++++ third_party/tinycbor/cborparser_dup_string.c | 113 ++ third_party/tinycbor/cborpretty.c | 471 ++++++ third_party/tinycbor/compilersupport_p.h | 234 +++ third_party/tinycbor/extract_number_p.h | 78 + third_party/tinycbor/math_support_p.h | 47 + 15 files changed, 3720 insertions(+), 1 deletion(-) create mode 100644 third_party/include/cbor.h create mode 100644 third_party/tinycbor/CMakeLists.txt create mode 100644 third_party/tinycbor/LICENSE create mode 100644 third_party/tinycbor/assert_p.h create mode 100644 third_party/tinycbor/cborconstants_p.h create mode 100644 third_party/tinycbor/cborencoder.c create mode 100644 third_party/tinycbor/cborencoder_close_container_checked.c create mode 100644 third_party/tinycbor/cborerrorstrings.c create mode 100644 third_party/tinycbor/cborparser.c create mode 100644 third_party/tinycbor/cborparser_dup_string.c create mode 100644 third_party/tinycbor/cborpretty.c create mode 100644 third_party/tinycbor/compilersupport_p.h create mode 100644 third_party/tinycbor/extract_number_p.h create mode 100644 third_party/tinycbor/math_support_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ec01c7b8f..38c4b0c23d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,8 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # SDK include paths. include_directories( ${PROJECT_SOURCE_DIR}/lib/include - ${PROJECT_SOURCE_DIR}/platform/include ) + ${PROJECT_SOURCE_DIR}/platform/include + ${PROJECT_SOURCE_DIR}/third_party/include ) # Demo include path. include_directories( ${PROJECT_SOURCE_DIR}/demos ) @@ -55,6 +56,9 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( platform/source/posix ) endif() +# Thrid party libraries (tinycbor). +add_subdirectory( third_party/tinycbor ) + # Common libraries (linear containers, logging, etc.) add_subdirectory( lib/source/common ) diff --git a/third_party/include/cbor.h b/third_party/include/cbor.h new file mode 100644 index 0000000000..d56c354417 --- /dev/null +++ b/third_party/include/cbor.h @@ -0,0 +1,486 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef CBOR_H +#define CBOR_H + +#include +#include +#include +#include +#include +#include + + + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + +#ifndef SIZE_MAX +/* Some systems fail to define SIZE_MAX in , even though C99 requires it... + * Conversion from signed to unsigned is defined in 6.3.1.3 (Signed and unsigned integers) p2, + * which says: "the value is converted by repeatedly adding or subtracting one more than the + * maximum value that can be represented in the new type until the value is in the range of the + * new type." + * So -1 gets converted to size_t by adding SIZE_MAX + 1, which results in SIZE_MAX. + */ +# define SIZE_MAX ((size_t)-1) +#endif + +#ifndef CBOR_API +# define CBOR_API +#endif +#ifndef CBOR_PRIVATE_API +# define CBOR_PRIVATE_API +#endif +#ifndef CBOR_INLINE_API +# if defined(__cplusplus) +# define CBOR_INLINE inline +# define CBOR_INLINE_API inline +# else +# define CBOR_INLINE_API static CBOR_INLINE +# if defined(_MSC_VER) +# define CBOR_INLINE __inline +# elif defined(__GNUC__) +# define CBOR_INLINE __inline__ +# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define CBOR_INLINE inline +# else +# define CBOR_INLINE +# endif +# endif +#endif + +typedef enum CborType { + CborIntegerType = 0x00, + CborByteStringType = 0x40, + CborTextStringType = 0x60, + CborArrayType = 0x80, + CborMapType = 0xa0, + CborTagType = 0xc0, + CborSimpleType = 0xe0, + CborBooleanType = 0xf5, + CborNullType = 0xf6, + CborUndefinedType = 0xf7, + CborHalfFloatType = 0xf9, + CborFloatType = 0xfa, + CborDoubleType = 0xfb, + + CborInvalidType = 0xff /* equivalent to the break byte, so it will never be used */ +} CborType; + +typedef uint64_t CborTag; +typedef enum CborKnownTags { + CborDateTimeStringTag = 0, /* RFC 3339 format: YYYY-MM-DD hh:mm:ss+zzzz */ + CborUnixTime_tTag = 1, + CborPositiveBignumTag = 2, + CborNegativeBignumTag = 3, + CborDecimalTag = 4, + CborBigfloatTag = 5, + CborExpectedBase64urlTag = 21, + CborExpectedBase64Tag = 22, + CborExpectedBase16Tag = 23, + CborUriTag = 32, + CborBase64urlTag = 33, + CborBase64Tag = 34, + CborRegularExpressionTag = 35, + CborMimeMessageTag = 36, /* RFC 2045-2047 */ + CborSignatureTag = 55799 +} CborKnownTags; + +/* Error API */ + +typedef enum CborError { + CborNoError = 0, + + /* errors in all modes */ + CborUnknownError, + CborErrorUnknownLength, /* request for length in array, map, or string with indeterminate length */ + CborErrorAdvancePastEOF, + CborErrorIO, + + /* parser errors streaming errors */ + CborErrorGarbageAtEnd = 256, + CborErrorUnexpectedEOF, + CborErrorUnexpectedBreak, + CborErrorUnknownType, /* can only heppen in major type 7 */ + CborErrorIllegalType, /* type not allowed here */ + CborErrorIllegalNumber, + CborErrorIllegalSimpleType, /* types of value less than 32 encoded in two bytes */ + + /* parser errors in strict mode parsing only */ + CborErrorUnknownSimpleType = 512, + CborErrorUnknownTag, + CborErrorInappropriateTagForType, + CborErrorDuplicateObjectKeys, + CborErrorInvalidUtf8TextString, + + /* encoder errors */ + CborErrorTooManyItems = 768, + CborErrorTooFewItems, + + /* internal implementation errors */ + CborErrorDataTooLarge = 1024, + CborErrorNestingTooDeep, + CborErrorUnsupportedType, + + /* errors in converting to JSON */ + CborErrorJsonObjectKeyIsAggregate, + CborErrorJsonObjectKeyNotString, + CborErrorJsonNotImplemented, + + CborErrorOutOfMemory = (int) (~0U / 2 + 1), + CborErrorInternalError = (int) ~0U +} CborError; + +CBOR_API const char *cbor_error_string(CborError error); + +/* Encoder API */ +struct CborEncoder +{ + union { + uint8_t *ptr; + ptrdiff_t bytes_needed; + } data; + const uint8_t *end; + size_t added; + int flags; +}; +typedef struct CborEncoder CborEncoder; + +static const size_t CborIndefiniteLength = SIZE_MAX; + +CBOR_API void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags); +CBOR_API CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value); +CBOR_API CborError cbor_encode_int(CborEncoder *encoder, int64_t value); +CBOR_API CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value); +CBOR_API CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value); +CBOR_API CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag); +CBOR_API CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length); +CBOR_INLINE_API CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) +{ return cbor_encode_text_string(encoder, string, strlen(string)); } +CBOR_API CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length); +CBOR_API CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value); + +CBOR_INLINE_API CborError cbor_encode_boolean(CborEncoder *encoder, bool value) +{ return cbor_encode_simple_value(encoder, (int)value - 1 + (CborBooleanType & 0x1f)); } +CBOR_INLINE_API CborError cbor_encode_null(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborNullType & 0x1f); } +CBOR_INLINE_API CborError cbor_encode_undefined(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborUndefinedType & 0x1f); } + +CBOR_INLINE_API CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) +{ return cbor_encode_floating_point(encoder, CborHalfFloatType, value); } +CBOR_INLINE_API CborError cbor_encode_float(CborEncoder *encoder, float value) +{ return cbor_encode_floating_point(encoder, CborFloatType, &value); } +CBOR_INLINE_API CborError cbor_encode_double(CborEncoder *encoder, double value) +{ return cbor_encode_floating_point(encoder, CborDoubleType, &value); } + +CBOR_API CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length); +CBOR_API CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); +CBOR_API CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder); +CBOR_API CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder); + +CBOR_INLINE_API size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) +{ + return (size_t)(encoder->data.ptr - buffer); +} + +CBOR_INLINE_API size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) +{ + return encoder->end ? 0 : (size_t)encoder->data.bytes_needed; +} + +/* Parser API */ + +enum CborParserIteratorFlags +{ + CborIteratorFlag_IntegerValueTooLarge = 0x01, + CborIteratorFlag_NegativeInteger = 0x02, + CborIteratorFlag_UnknownLength = 0x04, + CborIteratorFlag_ContainerIsMap = 0x20 +}; + +struct CborParser +{ + const uint8_t *end; + int flags; +}; +typedef struct CborParser CborParser; + +struct CborValue +{ + const CborParser *parser; + const uint8_t *ptr; + uint32_t remaining; + uint16_t extra; + uint8_t type; + uint8_t flags; +}; +typedef struct CborValue CborValue; + +CBOR_API CborError cbor_parser_init(const uint8_t *buffer, size_t size, int flags, CborParser *parser, CborValue *it); + +CBOR_INLINE_API bool cbor_value_at_end(const CborValue *it) +{ return it->remaining == 0; } +CBOR_INLINE_API const uint8_t *cbor_value_get_next_byte(const CborValue *it) +{ return it->ptr; } +CBOR_API CborError cbor_value_advance_fixed(CborValue *it); +CBOR_API CborError cbor_value_advance(CborValue *it); +CBOR_INLINE_API bool cbor_value_is_container(const CborValue *it) +{ return it->type == CborArrayType || it->type == CborMapType; } +CBOR_API CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed); +CBOR_API CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed); + +CBOR_PRIVATE_API uint64_t _cbor_value_decode_int64_internal(const CborValue *value); +CBOR_INLINE_API uint64_t _cbor_value_extract_int64_helper(const CborValue *value) +{ + return value->flags & CborIteratorFlag_IntegerValueTooLarge ? + _cbor_value_decode_int64_internal(value) : value->extra; +} + +CBOR_INLINE_API bool cbor_value_is_valid(const CborValue *value) +{ return value && value->type != CborInvalidType; } +CBOR_INLINE_API CborType cbor_value_get_type(const CborValue *value) +{ return (CborType)value->type; } + +/* Null & undefined type */ +CBOR_INLINE_API bool cbor_value_is_null(const CborValue *value) +{ return value->type == CborNullType; } +CBOR_INLINE_API bool cbor_value_is_undefined(const CborValue *value) +{ return value->type == CborUndefinedType; } + +/* Booleans */ +CBOR_INLINE_API bool cbor_value_is_boolean(const CborValue *value) +{ return value->type == CborBooleanType; } +CBOR_INLINE_API CborError cbor_value_get_boolean(const CborValue *value, bool *result) +{ + assert(cbor_value_is_boolean(value)); + *result = !!value->extra; + return CborNoError; +} + +/* Simple types */ +CBOR_INLINE_API bool cbor_value_is_simple_type(const CborValue *value) +{ return value->type == CborSimpleType; } +CBOR_INLINE_API CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) +{ + assert(cbor_value_is_simple_type(value)); + *result = (uint8_t)value->extra; + return CborNoError; +} + +/* Integers */ +CBOR_INLINE_API bool cbor_value_is_integer(const CborValue *value) +{ return value->type == CborIntegerType; } +CBOR_INLINE_API bool cbor_value_is_unsigned_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger) == 0; } +CBOR_INLINE_API bool cbor_value_is_negative_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger); } + +CBOR_INLINE_API CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) +{ + assert(cbor_value_is_integer(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) +{ + assert(cbor_value_is_unsigned_integer(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int64(const CborValue *value, int64_t *result) +{ + assert(cbor_value_is_integer(value)); + *result = (int64_t) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int(const CborValue *value, int *result) +{ + assert(cbor_value_is_integer(value)); + *result = (int) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_API CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result); +CBOR_API CborError cbor_value_get_int_checked(const CborValue *value, int *result); + +CBOR_INLINE_API bool cbor_value_is_length_known(const CborValue *value) +{ return (value->flags & CborIteratorFlag_UnknownLength) == 0; } + +/* Tags */ +CBOR_INLINE_API bool cbor_value_is_tag(const CborValue *value) +{ return value->type == CborTagType; } +CBOR_INLINE_API CborError cbor_value_get_tag(const CborValue *value, CborTag *result) +{ + assert(cbor_value_is_tag(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} +CBOR_API CborError cbor_value_skip_tag(CborValue *it); + +/* Strings */ +CBOR_INLINE_API bool cbor_value_is_byte_string(const CborValue *value) +{ return value->type == CborByteStringType; } +CBOR_INLINE_API bool cbor_value_is_text_string(const CborValue *value) +{ return value->type == CborTextStringType; } + +CBOR_INLINE_API CborError cbor_value_get_string_length(const CborValue *value, size_t *length) +{ + assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + uint64_t v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_PRIVATE_API CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next); +CBOR_PRIVATE_API CborError _cbor_value_dup_string(const CborValue *value, void **buffer, + size_t *buflen, CborValue *next); + +CBOR_API CborError cbor_value_calculate_string_length(const CborValue *value, size_t *length); + +CBOR_INLINE_API CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_copy_string(value, buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_copy_string(value, buffer, buflen, next); +} + +CBOR_INLINE_API CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} + +/* ### TBD: partial reading API */ + +CBOR_API CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result); + +/* Maps and arrays */ +CBOR_INLINE_API bool cbor_value_is_array(const CborValue *value) +{ return value->type == CborArrayType; } +CBOR_INLINE_API bool cbor_value_is_map(const CborValue *value) +{ return value->type == CborMapType; } + +CBOR_INLINE_API CborError cbor_value_get_array_length(const CborValue *value, size_t *length) +{ + assert(cbor_value_is_array(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + uint64_t v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_map_length(const CborValue *value, size_t *length) +{ + assert(cbor_value_is_map(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + uint64_t v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element); + +/* Floating point */ +CBOR_INLINE_API bool cbor_value_is_half_float(const CborValue *value) +{ return value->type == CborHalfFloatType; } +CBOR_API CborError cbor_value_get_half_float(const CborValue *value, void *result); + +CBOR_INLINE_API bool cbor_value_is_float(const CborValue *value) +{ return value->type == CborFloatType; } +CBOR_INLINE_API CborError cbor_value_get_float(const CborValue *value, float *result) +{ + assert(cbor_value_is_float(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); + uint32_t data = (uint32_t)_cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +CBOR_INLINE_API bool cbor_value_is_double(const CborValue *value) +{ return value->type == CborDoubleType; } +CBOR_INLINE_API CborError cbor_value_get_double(const CborValue *value, double *result) +{ + assert(cbor_value_is_double(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); + uint64_t data = _cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +/* The following API requires a hosted C implementation (uses FILE*) */ +#if !defined(__STDC_HOSTED__) || __STDC_HOSTED__-0 == 1 + +/* Human-readable (dump) API */ +CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value); +CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value) +{ + CborValue copy = *value; + return cbor_value_to_pretty_advance(out, ©); +} + +#endif /* __STDC_HOSTED__ check */ + +#ifdef __cplusplus +} +#endif + +#endif /* CBOR_H */ + diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt new file mode 100644 index 0000000000..c9382d7265 --- /dev/null +++ b/third_party/tinycbor/CMakeLists.txt @@ -0,0 +1,11 @@ +# Tinycbor library source files. +add_library( tinycbor SHARED + cborencoder.c + cborencoder_close_container_checked.c + cborerrorstrings.c + cborparser.c + cborparser_dup_string.c + cborpretty.c ) + +# Library version. +set_target_properties( tinycbor PROPERTIES VERSION ${PROJECT_VERSION} ) \ No newline at end of file diff --git a/third_party/tinycbor/LICENSE b/third_party/tinycbor/LICENSE new file mode 100644 index 0000000000..4aad977ceb --- /dev/null +++ b/third_party/tinycbor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Intel Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/tinycbor/assert_p.h b/third_party/tinycbor/assert_p.h new file mode 100644 index 0000000000..994be06441 --- /dev/null +++ b/third_party/tinycbor/assert_p.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include +#ifdef NDEBUG +# undef assert +# define assert(cond) do { if (!(cond)) unreachable(); } while (0) +#endif diff --git a/third_party/tinycbor/cborconstants_p.h b/third_party/tinycbor/cborconstants_p.h new file mode 100644 index 0000000000..d412056d24 --- /dev/null +++ b/third_party/tinycbor/cborconstants_p.h @@ -0,0 +1,52 @@ +#ifndef CBORCONSTANTS_P_H +#define CBORCONSTANTS_P_H + +/* + * CBOR Major types + * Encoded in the high 3 bits of the descriptor byte + * See http://tools.ietf.org/html/rfc7049#section-2.1 + */ +typedef enum CborMajorTypes { + UnsignedIntegerType = 0U, + NegativeIntegerType = 1U, + ByteStringType = 2U, + TextStringType = 3U, + ArrayType = 4U, + MapType = 5U, /* a.k.a. object */ + TagType = 6U, + SimpleTypesType = 7U +} CborMajorTypes; + +/* + * CBOR simple and floating point types + * Encoded in the low 8 bits of the descriptor byte when the + * Major Type is 7. + */ +typedef enum CborSimpleTypes { + FalseValue = 20, + TrueValue = 21, + NullValue = 22, + UndefinedValue = 23, + SimpleTypeInNextByte = 24, /* not really a simple type */ + HalfPrecisionFloat = 25, /* ditto */ + SinglePrecisionFloat = 26, /* ditto */ + DoublePrecisionFloat = 27, /* ditto */ + Break = 31 +} CborSimpleTypes; + +enum { + SmallValueBitLength = 5U, + SmallValueMask = (1U << SmallValueBitLength) - 1, /* 31 */ + Value8Bit = 24U, + Value16Bit = 25U, + Value32Bit = 26U, + Value64Bit = 27U, + IndefiniteLength = 31U, + + MajorTypeShift = SmallValueBitLength, + MajorTypeMask = (int) (~0U << MajorTypeShift), + + BreakByte = (unsigned)Break | (SimpleTypesType << MajorTypeShift) +}; + +#endif /* CBORCONSTANTS_P_H */ diff --git a/third_party/tinycbor/cborencoder.c b/third_party/tinycbor/cborencoder.c new file mode 100644 index 0000000000..cfe73defe7 --- /dev/null +++ b/third_party/tinycbor/cborencoder.c @@ -0,0 +1,626 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborconstants_p.h" +#include "compilersupport_p.h" + +#include +#include +#include + +#include "assert_p.h" /* Always include last */ + +/** + * \defgroup CborEncoding Encoding to CBOR + * \brief Group of functions used to encode data to CBOR. + * + * CborEncoder is used to encode data into a CBOR stream. The outermost + * CborEncoder is initialized by calling cbor_encoder_init(), with the buffer + * where the CBOR stream will be stored. The outermost CborEncoder is usually + * used to encode exactly one item, most often an array or map. It is possible + * to encode more than one item, but care must then be taken on the decoder + * side to ensure the state is reset after each item was decoded. + * + * Nested CborEncoder objects are created using cbor_encoder_create_array() and + * cbor_encoder_create_map(), later closed with cbor_encoder_close_container() + * or cbor_encoder_close_container_checked(). The pairs of creation and closing + * must be exactly matched and their parameters are always the same. + * + * CborEncoder writes directly to the user-supplied buffer, without extra + * buffering. CborEncoder does not allocate memory and CborEncoder objects are + * usually created on the stack of the encoding functions. + * + * The example below initializes a CborEncoder object with a buffer and encodes + * a single integer. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * cbor_encode_int(&encoder, some_value); + * \endcode + * + * As explained before, usually the outermost CborEncoder object is used to add + * one array or map, which in turn contains multiple elements. The example + * below creates a CBOR map with one element: a key "foo" and a boolean value. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * cbor_encode_text_stringz(&mapEncoder, "foo"); + * cbor_encode_boolean(&mapEncoder, some_value); + * cbor_encoder_close_container(&encoder, &mapEncoder); + * \endcode + * + *

Error checking and buffer size

+ * + * All functions operating on CborEncoder return a condition of type CborError. + * If the encoding was successful, they return CborNoError. Some functions do + * extra checking on the input provided and may return some other error + * conditions (for example, cbor_encode_simple_value() checks that the type is + * of the correct type). + * + * In addition, all functions check whether the buffer has enough bytes to + * encode the item being appended. If that is not possible, they return + * CborErrorOutOfMemory. + * + * It is possible to continue with the encoding of data past the first function + * that returns CborErrorOutOfMemory. CborEncoder functions will not overrun + * the buffer, but will instead count how many more bytes are needed to + * complete the encoding. At the end, you can obtain that count by calling + * cbor_encoder_get_extra_bytes_needed(). + * + * \section1 Finalizing the encoding + * + * Once all items have been appended and the containers have all been properly + * closed, the user-supplied buffer will contain the CBOR stream and may be + * immediately used. To obtain the size of the buffer, call + * cbor_encoder_get_buffer_size() with the original buffer pointer. + * + * The example below illustrates how one can encode an item with error checking + * and then pass on the buffer for network sending. + * + * \code + * uint8_t buf[16]; + * CborError err; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); + * err = cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * if (!err) + * return err; + * err = cbor_encode_text_stringz(&mapEncoder, "foo"); + * if (!err) + * return err; + * err = cbor_encode_boolean(&mapEncoder, some_value); + * if (!err) + * return err; + * err = cbor_encoder_close_container_checked(&encoder, &mapEncoder); + * if (!err) + * return err; + * + * size_t len = cbor_encoder_get_buffer_size(&encoder, buf); + * send_payload(buf, len); + * return CborNoError; + * \endcode + * + * Finally, the example below illustrates expands on the one above and also + * deals with dynamically growing the buffer if the initial allocation wasn't + * big enough. Note the two places where the error checking was replaced with + * an assertion, showing where the author assumes no error can occur. + * + * \code + * uint8_t *encode_string_array(const char **strings, int n, size_t *bufsize) + * { + * CborError err; + * CborEncoder encoder, arrayEncoder; + * size_t size = 256; + * uint8_t *buf = NULL; + * + * while (1) { + * int i; + * size_t more_bytes; + * uint8_t *nbuf = realloc(buf, size); + * if (nbuf == NULL) + * goto error; + * buf = nbuf; + * + * cbor_encoder_init(&encoder, &buf, size, 0); + * err = cbor_encoder_create_array(&encoder, &arrayEncoder, n); + * assert(err); // can't fail, the buffer is always big enough + * + * for (i = 0; i < n; ++i) { + * err = cbor_encode_text_stringz(&arrayEncoder, strings[i]); + * if (err && err != CborErrorOutOfMemory) + * goto error; + * } + * + * err = cbor_encoder_close_container_checked(&encoder, &arrayEncoder); + * assert(err); // shouldn't fail! + * + * more_bytes = cbor_encoder_get_extra_bytes_needed(encoder); + * if (more_size) { + * // buffer wasn't big enough, try again + * size += more_bytes; + * continue; + * } + * + * *bufsize = cbor_encoder_get_buffer_size(encoder, buf); + * return buf; + * } + * error: + * free(buf); + * return NULL; + * } + * \endcode + */ + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * \struct CborEncoder + * Structure used to encode to CBOR. + */ + +/** + * Initializes a CborEncoder structure \a encoder by pointing it to buffer \a + * buffer of size \a size. The \a flags field is currently unused and must be + * zero. + */ +void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags) +{ + encoder->data.ptr = buffer; + encoder->end = buffer + size; + encoder->added = 0; + encoder->flags = flags; +} + +static inline void put16(void *where, uint16_t v) +{ + v = cbor_htons(v); + memcpy(where, &v, sizeof(v)); +} + +/* Note: Since this is currently only used in situations where OOM is the only + * valid error, we KNOW this to be true. Thus, this function now returns just 'true', + * but if in the future, any function starts returning a non-OOM error, this will need + * to be changed to the test. At the moment, this is done to prevent more branches + * being created in the tinycbor output */ +static inline bool isOomError(CborError err) +{ + (void) err; + return true; +} + +static inline void put32(void *where, uint32_t v) +{ + v = cbor_htonl(v); + memcpy(where, &v, sizeof(v)); +} + +static inline void put64(void *where, uint64_t v) +{ + v = cbor_htonll(v); + memcpy(where, &v, sizeof(v)); +} + +static inline bool would_overflow(CborEncoder *encoder, size_t len) +{ + ptrdiff_t remaining = (ptrdiff_t)encoder->end; + remaining -= remaining ? (ptrdiff_t)encoder->data.ptr : encoder->data.bytes_needed; + remaining -= (ptrdiff_t)len; + return unlikely(remaining < 0); +} + +static inline void advance_ptr(CborEncoder *encoder, size_t n) +{ + if (encoder->end) + encoder->data.ptr += n; + else + encoder->data.bytes_needed += n; +} + +static inline CborError append_to_buffer(CborEncoder *encoder, const void *data, size_t len) +{ + if (would_overflow(encoder, len)) { + if (encoder->end != NULL) { + len -= encoder->end - encoder->data.ptr; + encoder->end = NULL; + encoder->data.bytes_needed = 0; + } + + advance_ptr(encoder, len); + return CborErrorOutOfMemory; + } + + memcpy(encoder->data.ptr, data, len); + encoder->data.ptr += len; + return CborNoError; +} + +static inline CborError append_byte_to_buffer(CborEncoder *encoder, uint8_t byte) +{ + return append_to_buffer(encoder, &byte, 1); +} + +static inline CborError encode_number_no_update(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + /* Little-endian would have been so much more convenient here: + * We could just write at the beginning of buf but append_to_buffer + * only the necessary bytes. + * Since it has to be big endian, do it the other way around: + * write from the end. */ + uint64_t buf[2]; + uint8_t *const bufend = (uint8_t *)buf + sizeof(buf); + uint8_t *bufstart = bufend - 1; + put64(buf + 1, ui); /* we probably have a bunch of zeros in the beginning */ + + if (ui < Value8Bit) { + *bufstart += shiftedMajorType; + } else { + uint8_t more = 0; + if (ui > 0xffU) + ++more; + if (ui > 0xffffU) + ++more; + if (ui > 0xffffffffU) + ++more; + bufstart -= (size_t)1 << more; + *bufstart = shiftedMajorType + Value8Bit + more; + } + + return append_to_buffer(encoder, bufstart, bufend - bufstart); +} + +static inline CborError encode_number(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + ++encoder->added; + return encode_number_no_update(encoder, ui, shiftedMajorType); +} + +/** + * Appends the unsigned 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_int + */ +CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value) +{ + return encode_number(encoder, value, UnsignedIntegerType << MajorTypeShift); +} + +/** + * Appends the negative 64-bit integer whose absolute value is \a + * absolute_value to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_uint, cbor_encode_int + */ +CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value) +{ + return encode_number(encoder, absolute_value, NegativeIntegerType << MajorTypeShift); +} + +/** + * Appends the signed 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_uint + */ +CborError cbor_encode_int(CborEncoder *encoder, int64_t value) +{ + /* adapted from code in RFC 7049 appendix C (pseudocode) */ + uint64_t ui = value >> 63; /* extend sign to whole length */ + uint8_t majorType = ui & 0x20; /* extract major type */ + ui ^= value; /* complement negatives */ + return encode_number(encoder, ui, majorType); +} + +/** + * Appends the CBOR Simple Type of value \a value to the CBOR stream provided by + * \a encoder. + * + * This function may return error CborErrorIllegalSimpleType if the \a value + * variable contains a number that is not a valid simple type. + */ +CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value) +{ +#ifndef CBOR_ENCODER_NO_CHECK_USER + /* check if this is a valid simple type */ + if (value >= HalfPrecisionFloat && value <= Break) + return CborErrorIllegalSimpleType; +#endif + return encode_number(encoder, value, SimpleTypesType << MajorTypeShift); +} + +/** + * Appends the floating-point value of type \a fpType and pointed to by \a + * value to the CBOR stream provided by \a encoder. The value of \a fpType must + * be one of CborHalfFloatType, CborFloatType or CborDoubleType, otherwise the + * behavior of this function is undefined. + * + * This function is useful for code that needs to pass through floating point + * values but does not wish to have the actual floating-point code. + * + * \sa cbor_encode_half_float, cbor_encode_float, cbor_encode_double + */ +CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value) +{ + uint8_t buf[1 + sizeof(uint64_t)]; + assert(fpType == CborHalfFloatType || fpType == CborFloatType || fpType == CborDoubleType); + buf[0] = fpType; + + unsigned size = 2U << (fpType - CborHalfFloatType); + if (size == 8) + put64(buf + 1, *(const uint64_t*)value); + else if (size == 4) + put32(buf + 1, *(const uint32_t*)value); + else + put16(buf + 1, *(const uint16_t*)value); + ++encoder->added; + return append_to_buffer(encoder, buf, size + 1); +} + +/** + * Appends the CBOR tag \a tag to the CBOR stream provided by \a encoder. + * + * \sa CborTag + */ +CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag) +{ + /* tags don't count towards the number of elements in an array or map */ + return encode_number_no_update(encoder, tag, TagType << MajorTypeShift); +} + +static CborError encode_string(CborEncoder *encoder, size_t length, uint8_t shiftedMajorType, const void *string) +{ + CborError err = encode_number(encoder, length, shiftedMajorType); + if (err && !isOomError(err)) + return err; + return append_to_buffer(encoder, string, length); +} + +/** + * \fn CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) + * + * Appends the null-terminated text string \a string to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. The terminating null is not + * included in the stream. + * + * \sa cbor_encode_text_string, cbor_encode_byte_string + */ + +/** + * Appends the text string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. + * + * \sa CborError cbor_encode_text_stringz, cbor_encode_byte_string + */ +CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length) +{ + return encode_string(encoder, length, ByteStringType << MajorTypeShift, string); +} + +/** + * Appends the byte string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR byte strings are arbitrary raw data. + * + * \sa cbor_encode_text_stringz, cbor_encode_text_string + */ +CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length) +{ + return encode_string(encoder, length, TextStringType << MajorTypeShift, string); +} + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static CborError create_container(CborEncoder *encoder, CborEncoder *container, size_t length, uint8_t shiftedMajorType) +{ + CborError err; + container->data.ptr = encoder->data.ptr; + container->end = encoder->end; + ++encoder->added; + container->added = 0; + + cbor_static_assert(((MapType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == CborIteratorFlag_ContainerIsMap); + cbor_static_assert(((ArrayType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == 0); + container->flags = shiftedMajorType & CborIteratorFlag_ContainerIsMap; + + if (length == CborIndefiniteLength) { + container->flags |= CborIteratorFlag_UnknownLength; + err = append_byte_to_buffer(container, shiftedMajorType + IndefiniteLength); + } else { + err = encode_number_no_update(container, length, shiftedMajorType); + } + return err; +} + +/** + * Creates a CBOR array in the CBOR stream provided by \a encoder and + * initializes \a arrayEncoder so that items can be added to the array using + * the CborEncoder functions. The array must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a arrayEncoder parameters. + * + * The number of items inserted into the array must be exactly \a length items, + * otherwise the stream is invalid. If the number of items is not known when + * creating the array, the constant \ref CborIndefiniteLength may be passed as + * length instead. + * + * \sa cbor_encoder_create_map + */ +CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length) +{ + return create_container(encoder, arrayEncoder, length, ArrayType << MajorTypeShift); +} + +/** + * Creates a CBOR map in the CBOR stream provided by \a encoder and + * initializes \a mapEncoder so that items can be added to the map using + * the CborEncoder functions. The map must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a mapEncoder parameters. + * + * The number of pair of items inserted into the map must be exactly \a length + * items, otherwise the stream is invalid. If the number of items is not known + * when creating the map, the constant \ref CborIndefiniteLength may be passed as + * length instead. + * + * \b{Implementation limitation:} TinyCBOR cannot encode more than SIZE_MAX/2 + * key-value pairs in the stream. If the length \a length is larger than this + * value, this function returns error CborErrorDataTooLarge. + * + * \sa cbor_encoder_create_array + */ +CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length) +{ + if (length != CborIndefiniteLength && length > SIZE_MAX / 2) + return CborErrorDataTooLarge; + return create_container(encoder, mapEncoder, length, MapType << MajorTypeShift); +} + +/** + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * This function does not verify that the number of items (or pair of items, in + * the case of a map) was correct. To execute that verification, call + * cbor_encoder_close_container_checked() instead. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + if (encoder->end) + encoder->data.ptr = containerEncoder->data.ptr; + else + encoder->data.bytes_needed = containerEncoder->data.bytes_needed; + encoder->end = containerEncoder->end; + if (containerEncoder->flags & CborIteratorFlag_UnknownLength) + return append_byte_to_buffer(encoder, BreakByte); + return CborNoError; +} + +/** + * \fn CborError cbor_encode_boolean(CborEncoder *encoder, bool value) + * + * Appends the boolean value \a value to the CBOR stream provided by \a encoder. + */ + +/** + * \fn CborError cbor_encode_null(CborEncoder *encoder) + * + * Appends the CBOR type representing a null value to the CBOR stream provided + * by \a encoder. + * + * \sa cbor_encode_undefined() + */ + +/** + * \fn CborError cbor_encode_undefined(CborEncoder *encoder) + * + * Appends the CBOR type representing an undefined value to the CBOR stream + * provided by \a encoder. + * + * \sa cbor_encode_null() + */ + +/** + * \fn CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) + * + * Appends the IEEE 754 half-precision (16-bit) floating point value pointed to + * by \a value to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_float(CborEncoder *encoder, float value) + * + * Appends the IEEE 754 single-precision (32-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_double(CborEncoder *encoder, double value) + * + * Appends the IEEE 754 double-precision (64-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float() + */ + +/** + * \fn size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) + * + * Returns the total size of the buffer starting at \a buffer after the + * encoding finished without errors. The \a encoder and \a buffer arguments + * must be the same as supplied to cbor_encoder_init(). + * + * If the encoding process had errors, the return value of this function is + * meaningless. If the only errors were CborErrorOutOfMemory, instead use + * cbor_encoder_get_extra_bytes_needed() to find out by how much to grow the + * buffer before encoding again. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_extra_bytes_needed(), CborEncoding + */ + +/** + * \fn size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) + * + * Returns how many more bytes the original buffer supplied to + * cbor_encoder_init() needs to be extended by so that no CborErrorOutOfMemory + * condition will happen for the encoding. If the buffer was big enough, this + * function returns 0. The \a encoder must be the original argument as passed + * to cbor_encoder_init(). + * + * This function is usually called after an encoding sequence ended with one or + * more CborErrorOutOfMemory errors, but no other error. If any other error + * happened, the return value of this function is meaningless. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_buffer_size(), CborEncoding + */ + +/** @} */ diff --git a/third_party/tinycbor/cborencoder_close_container_checked.c b/third_party/tinycbor/cborencoder_close_container_checked.c new file mode 100644 index 0000000000..9a1f549bdb --- /dev/null +++ b/third_party/tinycbor/cborencoder_close_container_checked.c @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborconstants_p.h" +#include "compilersupport_p.h" +#include "extract_number_p.h" + +#include + +#include "assert_p.h" /* Always include last */ + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Unlike cbor_encoder_close_container(), this function checks that the number + * of items (or pair of items, in the case of a map) was correct. If the number + * of items inserted does not match the length originally passed to + * cbor_encoder_create_array() or cbor_encoder_create_map(), this function + * returns either CborErrorTooFewItems or CborErrorTooManyItems. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + const uint8_t *ptr = encoder->data.ptr; + CborError err = cbor_encoder_close_container(encoder, containerEncoder); + if (containerEncoder->flags & CborIteratorFlag_UnknownLength || encoder->end == NULL) + return err; + + /* check what the original length was */ + uint64_t actually_added; + err = extract_number(&ptr, encoder->data.ptr, &actually_added); + if (err) + return err; + + if (containerEncoder->flags & CborIteratorFlag_ContainerIsMap) { + if (actually_added > SIZE_MAX / 2) + return CborErrorDataTooLarge; + actually_added *= 2; + } + return actually_added == containerEncoder->added ? CborNoError : + actually_added < containerEncoder->added ? CborErrorTooManyItems : CborErrorTooFewItems; +} + +/** @} */ diff --git a/third_party/tinycbor/cborerrorstrings.c b/third_party/tinycbor/cborerrorstrings.c new file mode 100644 index 0000000000..d2fe42fcd1 --- /dev/null +++ b/third_party/tinycbor/cborerrorstrings.c @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#include "cbor.h" + +#ifndef _ +# define _(msg) msg +#endif + +/** + * \enum CborError + * \ingroup CborGlobals + * The CborError enum contains the possible error values used by the CBOR encoder and decoder. + * + * TinyCBOR functions report success by returning CborNoError, or one error + * condition by returning one of the values below. One exception is the + * out-of-memory condition (CborErrorOutOfMemory), which the functions for \ref + * CborEncoding may report in bit-wise OR with other conditions. + * + * This technique allows code to determine whether the only error condition was + * a lack of buffer space, which may not be a fatal condition if the buffer can + * be resized. Additionally, the functions for \ref CborEncoding may continue + * to be used even after CborErrorOutOfMemory is returned, and instead they + * will simply calculate the extra space needed. + * + * \value CborNoError No error occurred + * \omitvalue CborUnknownError + * \value CborErrorUnknownLength Request for the length of an array, map or string whose length is not provided in the CBOR stream + * \value CborErrorAdvancePastEOF Not enough data in the stream to decode item (decoding would advance past end of stream) + * \value CborErrorIO An I/O error occurred, probably due to an out-of-memory situation + * \value CborErrorGarbageAtEnd Bytes exist past the end of the CBOR stream + * \value CborErrorUnexpectedEOF End of stream reached unexpectedly + * \value CborErrorUnexpectedBreak A CBOR break byte was found where not expected + * \value CborErrorUnknownType An unknown type (future extension to CBOR) was found in the stream + * \value CborErrorIllegalType An invalid type was found while parsing a chunked CBOR string + * \value CborErrorIllegalNumber An illegal initial byte (encoding unspecified additional information) was found + * \value CborErrorIllegalSimpleType An illegal encoding of a CBOR Simple Type of value less than 32 was found + * \omitvalue CborErrorUnknownSimpleType + * \omitvalue CborErrorUnknownTag + * \omitvalue CborErrorInappropriateTagForType + * \omitvalue CborErrorDuplicateObjectKeys + * \value CborErrorInvalidUtf8TextString Illegal UTF-8 encoding found while parsing CBOR Text String + * \value CborErrorTooManyItems Too many items were added to CBOR map or array of pre-determined length + * \value CborErrorTooFewItems Too few items were added to CBOR map or array of pre-determeined length + * \value CborErrorDataTooLarge Data item size exceeds TinyCBOR's implementation limits + * \value CborErrorNestingTooDeep Data item nesting exceeds TinyCBOR's implementation limits + * \omitvalue CborErrorUnsupportedType + * \value CborErrorJsonObjectKeyIsAggregate Conversion to JSON failed because the key in a map is a CBOR map or array + * \value CborErrorJsonObjectKeyNotString Conversion to JSON failed because the key in a map is not a text string + * \value CborErrorOutOfMemory During CBOR encoding, the buffer provided is insufficient for encoding the data item; + * in other situations, TinyCBOR failed to allocate memory + * \value CborErrorInternalError An internal error occurred in TinyCBOR + */ + +/** + * \ingroup CborGlobals + * Returns the error string corresponding to the CBOR error condition \a error. + */ +const char *cbor_error_string(CborError error) +{ + switch (error) { + case CborNoError: + return ""; + + case CborUnknownError: + return _("unknown error"); + + case CborErrorOutOfMemory: + return _("out of memory/need more memory"); + + case CborErrorUnknownLength: + return _("unknown length (attempted to get the length of a map/array/string of indeterminate length"); + + case CborErrorAdvancePastEOF: + return _("attempted to advance past EOF"); + + case CborErrorIO: + return _("I/O error"); + + case CborErrorGarbageAtEnd: + return _("garbage after the end of the content"); + + case CborErrorUnexpectedEOF: + return _("unexpected end of data"); + + case CborErrorUnexpectedBreak: + return _("unexpected 'break' byte"); + + case CborErrorUnknownType: + return _("illegal byte (encodes future extension type)"); + + case CborErrorIllegalType: + return _("mismatched string type in chunked string"); + + case CborErrorIllegalNumber: + return _("illegal initial byte (encodes unspecified additional information)"); + + case CborErrorIllegalSimpleType: + return _("illegal encoding of simple type smaller than 32"); + + case CborErrorUnknownSimpleType: + return _("unknown simple type"); + + case CborErrorUnknownTag: + return _("unknown tag"); + + case CborErrorInappropriateTagForType: + return _("inappropriate tag for type"); + + case CborErrorDuplicateObjectKeys: + return _("duplicate keys in object"); + + case CborErrorInvalidUtf8TextString: + return _("invalid UTF-8 content in string"); + + case CborErrorTooManyItems: + return _("too many items added to encoder"); + + case CborErrorTooFewItems: + return _("too few items added to encoder"); + + case CborErrorDataTooLarge: + return _("internal error: data too large"); + + case CborErrorNestingTooDeep: + return _("internal error: too many nested containers found in recursive function"); + + case CborErrorUnsupportedType: + return _("unsupported type"); + + case CborErrorJsonObjectKeyIsAggregate: + return _("conversion to JSON failed: key in object is an array or map"); + + case CborErrorJsonObjectKeyNotString: + return _("conversion to JSON failed: key in object is not a string"); + + case CborErrorJsonNotImplemented: + return _("conversion to JSON failed: open_memstream unavailable"); + + case CborErrorInternalError: + return _("internal error"); + } + return cbor_error_string(CborUnknownError); +} diff --git a/third_party/tinycbor/cborparser.c b/third_party/tinycbor/cborparser.c new file mode 100644 index 0000000000..70cc7153af --- /dev/null +++ b/third_party/tinycbor/cborparser.c @@ -0,0 +1,1300 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborconstants_p.h" +#include "compilersupport_p.h" +#include "extract_number_p.h" + +#include +#include + +#include "assert_p.h" /* Always include last */ + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +/** + * \defgroup CborParsing Parsing CBOR streams + * \brief Group of functions used to parse CBOR streams. + * + * TinyCBOR provides functions for pull-based stream parsing of a CBOR-encoded + * payload. The main data type for the parsing is a CborValue, which behaves + * like an iterator and can be used to extract the encoded data. It is first + * initialized with a call to cbor_parser_init() and is usually used to extract + * exactly one item, most often an array or map. + * + * Nested CborValue objects can be parsed using cbor_value_enter_container(). + * Each call to cbor_value_enter_container() must be matched by a call to + * cbor_value_leave_container(), with the exact same parameters. + * + * The example below initializes a CborParser object, begins the parsing with a + * CborValue and decodes a single integer: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * cbor_parser_init(buffer, len, 0, &buffer, &value); + * cbor_value_get_int(&value, &result); + * return result; + * } + * \endcode + * + * The code above does no error checking, which means it assumes the data comes + * from a source trusted to send one properly-encoded integer. The following + * example does the exact same operation, but includes error parsing and + * returns 0 on parsing failure: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * if (cbor_parser_init(buffer, len, 0, &buffer, &value) != CborNoError) + * return 0; + * if (!cbor_value_is_integer(&value) || + * cbor_value_get_int(&value, &result) != CborNoError) + * return 0; + * return result; + * } + * \endcode + * + * Note, in the example above, that one can't distinguish a parsing failure + * from an encoded value of zero. Reporting a parsing error is left as an + * exercise to the reader. + * + * The code above does not execute a range-check either: it is possible that + * the value decoded from the CBOR stream encodes a number larger than what can + * be represented in a variable of type \c{int}. If detecting that case is + * important, the code should call cbor_value_get_int_checked() instead. + * + *

Memory and parsing constraints

+ * + * TinyCBOR is designed to run with little memory and with minimal overhead. + * Except where otherwise noted, the parser functions always run on constant + * time (O(1)), do not recurse and never allocate memory (thus, stack usage is + * bounded and is O(1)). + * + *

Error handling and preconditions

+ * + * All functions operating on a CborValue return a CborError condition, with + * CborNoError standing for the normal situation in which no parsing error + * occurred. All functions may return parsing errors in case the stream cannot + * be decoded properly, be it due to corrupted data or due to reaching the end + * of the input buffer. + * + * Error conditions must not be ignored. All decoder functions have undefined + * behavior if called after an error has been reported, and may crash. + * + * Some functions are also documented to have preconditions, like + * cbor_value_get_int() requiring that the input be an integral value. + * Violation of preconditions also results in undefined behavior and the + * program may crash. + */ + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \struct CborValue + * + * This type contains one value parsed from the CBOR stream. Each CborValue + * behaves as an iterator in a StAX-style parser. + * + * \if privatedocs + * Implementation details: the CborValue contains these fields: + * \list + * \li ptr: pointer to the actual data + * \li flags: flags from the decoder + * \li extra: partially decoded integer value (0, 1 or 2 bytes) + * \li remaining: remaining items in this collection after this item or UINT32_MAX if length is unknown + * \endlist + * \endif + */ + +static CborError extract_length(const CborParser *parser, const uint8_t **ptr, size_t *len) +{ + uint64_t v; + CborError err = extract_number(ptr, parser->end, &v); + if (err) { + *len = 0; + return err; + } + + *len = (size_t)v; + if (v != *len) + return CborErrorDataTooLarge; + return CborNoError; +} + +static bool is_fixed_type(uint8_t type) +{ + return type != CborTextStringType && type != CborByteStringType && type != CborArrayType && + type != CborMapType; +} + +static CborError preparse_value(CborValue *it) +{ + const CborParser *parser = it->parser; + it->type = CborInvalidType; + + /* are we at the end? */ + if (it->ptr == parser->end) + return CborErrorUnexpectedEOF; + + uint8_t descriptor = *it->ptr; + uint8_t type = descriptor & MajorTypeMask; + it->type = type; + it->flags = 0; + it->extra = (descriptor &= SmallValueMask); + + if (descriptor > Value64Bit) { + if (unlikely(descriptor != IndefiniteLength)) + return type == CborSimpleType ? CborErrorUnknownType : CborErrorIllegalNumber; + if (likely(!is_fixed_type(type))) { + /* special case */ + it->flags |= CborIteratorFlag_UnknownLength; + it->type = type; + return CborNoError; + } + return type == CborSimpleType ? CborErrorUnexpectedBreak : CborErrorIllegalNumber; + } + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + if (bytesNeeded + 1 > (size_t)(parser->end - it->ptr)) + return CborErrorUnexpectedEOF; + + uint8_t majortype = type >> MajorTypeShift; + if (majortype == NegativeIntegerType) { + it->flags |= CborIteratorFlag_NegativeInteger; + it->type = CborIntegerType; + } else if (majortype == SimpleTypesType) { + switch (descriptor) { + case FalseValue: + it->extra = false; + it->type = CborBooleanType; + break; + + case SinglePrecisionFloat: + case DoublePrecisionFloat: + it->flags |= CborIteratorFlag_IntegerValueTooLarge; + /* fall through */ + case TrueValue: + case NullValue: + case UndefinedValue: + case HalfPrecisionFloat: + it->type = *it->ptr; + break; + + case SimpleTypeInNextByte: + it->extra = (uint8_t)it->ptr[1]; +#ifndef CBOR_PARSER_NO_STRICT_CHECKS + if (unlikely(it->extra < 32)) { + it->type = CborInvalidType; + return CborErrorIllegalSimpleType; + } +#endif + break; + + case 28: + case 29: + case 30: + case Break: + assert(false); /* these conditions can't be reached */ + return CborErrorUnexpectedBreak; + } + return CborNoError; + } + + /* try to decode up to 16 bits */ + if (descriptor < Value8Bit) + return CborNoError; + + if (descriptor == Value8Bit) + it->extra = (uint8_t)it->ptr[1]; + else if (descriptor == Value16Bit) + it->extra = get16(it->ptr + 1); + else + it->flags |= CborIteratorFlag_IntegerValueTooLarge; /* Value32Bit or Value64Bit */ + return CborNoError; +} + +static CborError preparse_next_value(CborValue *it) +{ + if (it->remaining != UINT32_MAX) { + /* don't decrement the item count if the current item is tag: they don't count */ + if (it->type != CborTagType && !--it->remaining) { + it->type = CborInvalidType; + return CborNoError; + } + } else if (it->remaining == UINT32_MAX && it->ptr != it->parser->end && *it->ptr == (uint8_t)BreakByte) { + /* end of map or array */ + ++it->ptr; + it->type = CborInvalidType; + it->remaining = 0; + return CborNoError; + } + + return preparse_value(it); +} + +static CborError advance_internal(CborValue *it) +{ + uint64_t length; + CborError err = extract_number(&it->ptr, it->parser->end, &length); + assert(err == CborNoError); + + if (it->type == CborByteStringType || it->type == CborTextStringType) { + assert(length == (size_t)length); + assert((it->flags & CborIteratorFlag_UnknownLength) == 0); + it->ptr += length; + } + + return preparse_next_value(it); +} + +/** \internal + * + * Decodes the CBOR integer value when it is larger than the 16 bits available + * in value->extra. This function requires that value->flags have the + * CborIteratorFlag_IntegerValueTooLarge flag set. + * + * This function is also used to extract single- and double-precision floating + * point values (SinglePrecisionFloat == Value32Bit and DoublePrecisionFloat == + * Value64Bit). + */ +uint64_t _cbor_value_decode_int64_internal(const CborValue *value) +{ + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge || + value->type == CborFloatType || value->type == CborDoubleType); + + /* since the additional information can only be Value32Bit or Value64Bit, + * we just need to test for the one bit those two options differ */ + assert((*value->ptr & SmallValueMask) == Value32Bit || (*value->ptr & SmallValueMask) == Value64Bit); + if ((*value->ptr & 1) == (Value32Bit & 1)) + return get32(value->ptr + 1); + + assert((*value->ptr & SmallValueMask) == Value64Bit); + return get64(value->ptr + 1); +} + +/** + * Initializes the CBOR parser for parsing \a size bytes beginning at \a + * buffer. Parsing will use flags set in \a flags. The iterator to the first + * element is returned in \a it. + * + * The \a parser structure needs to remain valid throughout the decoding + * process. It is not thread-safe to share one CborParser among multiple + * threads iterating at the same time, but the object can be copied so multiple + * threads can iterate. + */ +CborError cbor_parser_init(const uint8_t *buffer, size_t size, int flags, CborParser *parser, CborValue *it) +{ + memset(parser, 0, sizeof(*parser)); + parser->end = buffer + size; + parser->flags = flags; + it->parser = parser; + it->ptr = buffer; + it->remaining = 1; /* there's one type altogether, usually an array or map */ + return preparse_value(it); +} + +/** + * \fn bool cbor_value_at_end(const CborValue *it) + * + * Returns true if \a it has reached the end of the iteration, usually when + * advancing after the last item in an array or map. + * + * In the case of the outermost CborValue object, this function returns true + * after decoding a single element. A pointer to the first byte of the + * remaining data (if any) can be obtained with cbor_value_get_next_byte(). + * + * \sa cbor_value_advance(), cbor_value_is_valid(), cbor_value_get_next_byte() + */ + +/** + * \fn const uint8_t *cbor_value_get_next_byte(const CborValue *it) + * + * Returns a pointer to the next byte that would be decoded if this CborValue + * object were advanced. + * + * This function is useful if cbor_value_at_end() returns true for the + * outermost CborValue: the pointer returned is the first byte of the data + * remaining in the buffer, if any. Code can decide whether to begin decoding a + * new CBOR data stream from this point, or parse some other data appended to + * the same buffer. + * + * This function may be used even after a parsing error. If that occurred, + * then this function returns a pointer to where the parsing error occurred. + * Note that the error recovery is not precise and the pointer may not indicate + * the exact byte containing bad data. + * + * \sa cbor_value_at_end() + */ + +/** + * \fn bool cbor_value_is_valid(const CborValue *it) + * + * Returns true if the iterator \a it contains a valid value. Invalid iterators + * happen when iteration reaches the end of a container (see \ref + * cbor_value_at_end()) or when a search function resulted in no matches. + * + * \sa cbor_value_advance(), cbor_valie_at_end(), cbor_value_get_type() + */ + +/** + * Advances the CBOR value \a it by one fixed-size position. Fixed-size types + * are: integers, tags, simple types (including boolean, null and undefined + * values) and floating point types. + * + * If the type is not of fixed size, this function has undefined behavior. Code + * must be sure that the current type is one of the fixed-size types before + * calling this function. This function is provided because it can guarantee + * that runs in constant time (O(1)). + * + * If the caller is not able to determine whether the type is fixed or not, code + * can use the cbor_value_advance() function instead. + * + * \sa cbor_value_at_end(), cbor_value_advance(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance_fixed(CborValue *it) +{ + assert(it->type != CborInvalidType); + assert(is_fixed_type(it->type)); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_internal(it); +} + +static CborError advance_recursive(CborValue *it, int nestingLevel) +{ + if (is_fixed_type(it->type)) + return advance_internal(it); + + if (!cbor_value_is_container(it)) { + size_t len = SIZE_MAX; + return _cbor_value_copy_string(it, NULL, &len, it); + } + + /* map or array */ + if (nestingLevel == CBOR_PARSER_MAX_RECURSIONS) + return CborErrorNestingTooDeep; + + CborError err; + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; + while (!cbor_value_at_end(&recursed)) { + err = advance_recursive(&recursed, nestingLevel + 1); + if (err) + return err; + } + return cbor_value_leave_container(it, &recursed); +} + + +/** + * Advances the CBOR value \a it by one element, skipping over containers. + * Unlike cbor_value_advance_fixed(), this function can be called on a CBOR + * value of any type. However, if the type is a container (map or array) or a + * string with a chunked payload, this function will not run in constant time + * and will recurse into itself (it will run on O(n) time for the number of + * elements or chunks and will use O(n) memory for the number of nested + * containers). + * + * \sa cbor_value_at_end(), cbor_value_advance_fixed(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance(CborValue *it) +{ + assert(it->type != CborInvalidType); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_recursive(it, 0); +} + +/** + * \fn bool cbor_value_is_tag(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR tag. + * + * \sa cbor_value_get_tag(), cbor_value_skip_tag() + */ + +/** + * \fn CborError cbor_value_get_tag(const CborValue *value, CborTag *result) + * + * Retrieves the CBOR tag value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a CBOR tag value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_tag is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_tag() + */ + +/** + * Advances the CBOR value \a it until it no longer points to a tag. If \a it is + * already not pointing to a tag, then this function returns it unchanged. + * + * This function does not run in constant time: it will run on O(n) for n being + * the number of tags. It does use constant memory (O(1) memory requirements). + * + * \sa cbor_value_advance_fixed(), cbor_value_advance() + */ +CborError cbor_value_skip_tag(CborValue *it) +{ + while (cbor_value_is_tag(it)) { + CborError err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +/** + * \fn bool cbor_value_is_container(const CborValue *it) + * + * Returns true if the \a it value is a container and requires recursion in + * order to decode (maps and arrays), false otherwise. + */ + +/** + * Creates a CborValue iterator pointing to the first element of the container + * represented by \a it and saves it in \a recursed. The \a it container object + * needs to be kept and passed again to cbor_value_leave_container() in order + * to continue iterating past this container. + * + * The \a it CborValue iterator must point to a container. + * + * \sa cbor_value_is_container(), cbor_value_leave_container(), cbor_value_advance() + */ +CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed) +{ + CborError err; + assert(cbor_value_is_container(it)); + *recursed = *it; + + if (it->flags & CborIteratorFlag_UnknownLength) { + recursed->remaining = UINT32_MAX; + ++recursed->ptr; + err = preparse_value(recursed); + if (err != CborErrorUnexpectedBreak) + return err; + /* actually, break was expected here + * it's just an empty container */ + ++recursed->ptr; + } else { + uint64_t len; + err = extract_number(&recursed->ptr, recursed->parser->end, &len); + assert(err == CborNoError); + + recursed->remaining = (uint32_t)len; + if (recursed->remaining != len || len == UINT32_MAX) { + /* back track the pointer to indicate where the error occurred */ + recursed->ptr = it->ptr; + return CborErrorDataTooLarge; + } + if (recursed->type == CborMapType) { + /* maps have keys and values, so we need to multiply by 2 */ + if (recursed->remaining > UINT32_MAX / 2) { + /* back track the pointer to indicate where the error occurred */ + recursed->ptr = it->ptr; + return CborErrorDataTooLarge; + } + recursed->remaining *= 2; + } + if (len != 0) + return preparse_value(recursed); + } + + /* the case of the empty container */ + recursed->type = CborInvalidType; + recursed->remaining = 0; + return CborNoError; +} + +/** + * Updates \a it to point to the next element after the container. The \a + * recursed object needs to point to the element obtained either by advancing + * the last element of the container (via cbor_value_advance(), + * cbor_value_advance_fixed(), a nested cbor_value_leave_container(), or the \c + * next pointer from cbor_value_copy_string() or cbor_value_dup_string()). + * + * The \a it and \a recursed parameters must be the exact same as passed to + * cbor_value_enter_container(). + * + * \sa cbor_value_enter_container(), cbor_value_at_end() + */ +CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed) +{ + assert(cbor_value_is_container(it)); + assert(recursed->type == CborInvalidType); + it->ptr = recursed->ptr; + return preparse_next_value(it); +} + + +/** + * \fn CborType cbor_value_get_type(const CborValue *value) + * + * Returns the type of the CBOR value that the iterator \a value points to. If + * \a value does not point to a valid value, this function returns \ref + * CborInvalidType. + * + * TinyCBOR also provides functions to test directly if a given CborValue object + * is of a given type, like cbor_value_is_text_string() and cbor_value_is_null(). + * + * \sa cbor_value_is_valid() + */ + +/** + * \fn bool cbor_value_is_null(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR null type. + * + * \sa cbor_value_is_valid(), cbor_value_is_undefined() + */ + +/** + * \fn bool cbor_value_is_undefined(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR undefined type. + * + * \sa cbor_value_is_valid(), cbor_value_is_null() + */ + +/** + * \fn bool cbor_value_is_boolean(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR boolean + * type (true or false). + * + * \sa cbor_value_is_valid(), cbor_value_get_boolean() + */ + +/** + * \fn CborError cbor_value_get_boolean(const CborValue *value, bool *result) + * + * Retrieves the boolean value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a boolean value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_boolean is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_boolean() + */ + +/** + * \fn bool cbor_value_is_simple_type(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR Simple Type + * type (other than true, false, null and undefined). + * + * \sa cbor_value_is_valid(), cbor_value_get_simple_type() + */ + +/** + * \fn CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) + * + * Retrieves the CBOR Simple Type value that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a simple_type + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_simple_type is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_simple_type() + */ + +/** + * \fn bool cbor_value_is_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR integer + * type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_uint64, cbor_value_get_raw_integer + */ + +/** + * \fn bool cbor_value_is_unsigned_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR unsigned + * integer type (positive values or zero). + * + * \sa cbor_value_is_valid(), cbor_value_get_uint64() + */ + +/** + * \fn bool cbor_value_is_negative_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR negative + * integer type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_raw_integer + */ + +/** + * \fn CborError cbor_value_get_int(const CborValue *value, int *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int} are silently truncated to fit. Use + * cbor_value_get_int_checked() that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_int64(const CborValue *value, int64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int64_t} are silently truncated to fit. Use + * cbor_value_get_int64_checked() that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an unsigned integer + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_unsigned_integer is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_unsigned_integer() + */ + +/** + * \fn CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * This function is provided because CBOR negative integers can assume values + * that cannot be represented with normal 64-bit integer variables. + * + * If the integer is unsigned (that is, if cbor_value_is_unsigned_integer() + * returns true), then \a result will contain the actual value. If the integer + * is negative, then \a result will contain the absolute value of that integer, + * minus one. That is, \c {actual = -result - 1}. On architectures using two's + * complement for representation of negative integers, it is equivalent to say + * that \a result will contain the bitwise negation of the actual value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike cbor_value_get_int64(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use either + * cbor_value_get_uint64() (if the number is positive) or + * cbor_value_get_raw_integer(). + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64() + */ +CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result) +{ + assert(cbor_value_is_integer(value)); + uint64_t v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * The range for int64_t is -2^63 to 2^63-1 (int64_t is required to be + * two's complement, C11 7.20.1.1 paragraph 3), which in CBOR is + * represented the same way, differing only on the "sign bit" (the major + * type). + */ + + if (unlikely(v > (uint64_t)INT64_MAX)) + return CborErrorDataTooLarge; + + *result = v; + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike cbor_value_get_int(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use one of the other integer + * functions to obtain the value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64(), + * cbor_value_get_uint64(), cbor_value_get_int64_checked(), cbor_value_get_raw_integer() + */ +CborError cbor_value_get_int_checked(const CborValue *value, int *result) +{ + assert(cbor_value_is_integer(value)); + uint64_t v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * But we can convert from signed to unsigned without fault (paragraph 2). + * + * The range for int is implementation-defined and int is not guaranteed use + * two's complement representation (int32_t is). + */ + + if (value->flags & CborIteratorFlag_NegativeInteger) { + if (unlikely(v > (unsigned) -(INT_MIN + 1))) + return CborErrorDataTooLarge; + + *result = (int)v; + *result = -*result - 1; + } else { + if (unlikely(v > (uint64_t)INT_MAX)) + return CborErrorDataTooLarge; + + *result = (int)v; + } + return CborNoError; + +} + +/** + * \fn bool cbor_value_is_length_known(const CborValue *value) + * + * Returns true if the length of this type is known without calculation. That + * is, if the length of this CBOR string, map or array is encoded in the data + * stream, this function returns true. If the length is not encoded, it returns + * false. + * + * If the length is known, code can call cbor_value_get_string_length(), + * cbor_value_get_array_length() or cbor_value_get_map_length() to obtain the + * length. If the length is not known but is necessary, code can use the + * cbor_value_calculate_string_length() function (no equivalent function is + * provided for maps and arrays). + */ + +/** + * \fn bool cbor_value_is_text_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR text strings are UTF-8 encoded and usually contain + * human-readable text. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_text_string(), cbor_value_dup_text_string() + */ + +/** + * \fn bool cbor_value_is_byte_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR byte strings are binary data with no specified encoding or + * format. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_byte_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_get_string_length(const CborValue *value, size_t *length) + * + * Extracts the length of the byte or text string that \a value points to and + * stores it in \a result. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * If the length of this string is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * If the length of the string is required but the length was not encoded, use + * cbor_value_calculate_string_length(), but note that that function does not + * run in constant time. + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known(), cbor_value_calculate_string_length() + */ + +/** + * Calculates the length of the byte or text string that \a value points to and + * stores it in \a len. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * This function is different from cbor_value_get_string_length() in that it + * calculates the length even for strings sent in chunks. For that reason, this + * function may not run in constant time (it will run in O(n) time on the + * number of chunks). It does use constant memory (O(1)). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_get_string_length(), cbor_value_copy_string(), cbor_value_is_length_known() + */ +CborError cbor_value_calculate_string_length(const CborValue *value, size_t *len) +{ + *len = SIZE_MAX; + return _cbor_value_copy_string(value, NULL, len, NULL); +} + +/* We return uintptr_t so that we can pass memcpy directly as the iteration + * function. The choice is to optimize for memcpy, which is used in the base + * parser API (cbor_value_copy_string), while memcmp is used in convenience API + * only. */ +typedef uintptr_t (*IterateFunction)(char *, const uint8_t *, size_t); + +static uintptr_t iterate_noop(char *dest, const uint8_t *src, size_t len) +{ + (void)dest; + (void)src; + (void)len; + return true; +} + +static uintptr_t iterate_memcmp(char *s1, const uint8_t *s2, size_t len) +{ + return memcmp(s1, (const char *)s2, len) == 0; +} + +static uintptr_t iterate_memcpy(char *dest, const uint8_t *src, size_t len) +{ + return (uintptr_t)memcpy(dest, src, len); +} + +static CborError iterate_string_chunks(const CborValue *value, char *buffer, size_t *buflen, + bool *result, CborValue *next, IterateFunction func) +{ + assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + + size_t total; + CborError err; + const uint8_t *ptr = value->ptr; + if (cbor_value_is_length_known(value)) { + /* easy case: fixed length */ + err = extract_length(value->parser, &ptr, &total); + if (err) + return err; + if (total > (size_t)(value->parser->end - ptr)) + return CborErrorUnexpectedEOF; + if (total <= *buflen) + *result = !!func(buffer, ptr, total); + else + *result = false; + ptr += total; + } else { + /* chunked */ + ++ptr; + total = 0; + *result = true; + while (true) { + size_t chunkLen; + size_t newTotal; + + if (ptr == value->parser->end) + return CborErrorUnexpectedEOF; + + if (*ptr == (uint8_t)BreakByte) { + ++ptr; + break; + } + + /* is this the right type? */ + if ((*ptr & MajorTypeMask) != value->type) + return CborErrorIllegalType; + + err = extract_length(value->parser, &ptr, &chunkLen); + if (err) + return err; + + if (unlikely(add_check_overflow(total, chunkLen, &newTotal))) + return CborErrorDataTooLarge; + + if (chunkLen > (size_t)(value->parser->end - ptr)) + return CborErrorUnexpectedEOF; + + if (*result && *buflen >= newTotal) + *result = !!func(buffer + total, ptr, chunkLen); + else + *result = false; + + ptr += chunkLen; + total = newTotal; + } + } + + /* is there enough room for the ending NUL byte? */ + if (*result && *buflen > total) { + uint8_t nul[] = { 0 }; + *result = !!func(buffer + total, nul, 1); + } + *buflen = total; + + if (next) { + *next = *value; + next->ptr = ptr; + return preparse_next_value(next); + } + return CborNoError; +} + +/** + * \fn CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of text strings. That byte is + * not included in the returned value of \c{*buflen}. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_dup_text_string(), cbor_value_copy_byte_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +/** + * \fn CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of null-terminated strings. + * That byte is not included in the returned value of \c{*buflen}. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next) +{ + bool copied_all; + CborError err = iterate_string_chunks(value, (char*)buffer, buflen, &copied_all, next, + buffer ? iterate_memcpy : iterate_noop); + return err ? err : + copied_all ? CborNoError : CborErrorOutOfMemory; +} + +/** + * Compares the entry \a value with the string \a string and store the result + * in \a result. If the value is different from \a string \a result will + * contain \c false. + * + * The entry at \a value may be a tagged string. If \a is not a string or a + * tagged string, the comparison result will be false. + * + * CBOR requires text strings to be encoded in UTF-8, but this function does + * not validate either the strings in the stream or the string \a string to be + * matched. Moreover, comparison is done on strict codepoint comparison, + * without any Unicode normalization. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)). + * + * \sa cbor_value_skip_tag(), cbor_value_copy_text_string() + */ +CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result) +{ + CborValue copy = *value; + CborError err = cbor_value_skip_tag(©); + if (err) + return err; + if (!cbor_value_is_text_string(©)) { + *result = false; + return CborNoError; + } + + size_t len = strlen(string); + return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); +} + +/** + * \fn bool cbor_value_is_array(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR array. + * + * \sa cbor_value_is_valid(), cbor_value_is_map() + */ + +/** + * \fn CborError cbor_value_get_array_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR array that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a CBOR array, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_array is recommended. + * + * If the length of this array is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * \fn bool cbor_value_is_map(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR map. + * + * \sa cbor_value_is_valid(), cbor_value_is_array() + */ + +/** + * \fn CborError cbor_value_get_map_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR map that \a value points to and stores it in + * \a result. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the length of this map is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * Attempts to find the value in map \a map that corresponds to the text string + * entry \a string. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the item is found, it is stored in \a result. If no item is found + * matching the key, then \a result will contain an element of type \ref + * CborInvalidType. Matching is performed using + * cbor_value_text_string_equals(), so tagged strings will also match. + * + * This function has a time complexity of O(n) where n is the number of + * elements in the map to be searched. In addition, this function is has O(n) + * memory requirement based on the number of nested containers (maps or arrays) + * found as elements of this map. + * + * \sa cbor_value_is_valid(), cbor_value_text_string_equals(), cbor_value_advance() + */ +CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) +{ + assert(cbor_value_is_map(map)); + size_t len = strlen(string); + CborError err = cbor_value_enter_container(map, element); + if (err) + goto error; + + while (!cbor_value_at_end(element)) { + /* find the non-tag so we can compare */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + if (cbor_value_is_text_string(element)) { + bool equals; + size_t dummyLen = len; + err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, + &equals, element, iterate_memcmp); + if (err) + goto error; + if (equals) + return preparse_value(element); + } else { + /* skip this key */ + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* skip this value */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* not found */ + element->type = CborInvalidType; + return CborNoError; + +error: + element->type = CborInvalidType; + return err; +} + +/** + * \fn bool cbor_value_is_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (32-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_float(const CborValue *value, float *result) + * + * Retrieves the CBOR single-precision floating point (32-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a single-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_float is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_float(), cbor_value_get_double() + */ + +/** + * \fn bool cbor_value_is_double(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * double-precision floating point (64-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_float(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_double(const CborValue *value, float *result) + * + * Retrieves the CBOR double-precision floating point (64-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a double-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_double is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_double(), cbor_value_get_float() + */ + +/** + * \fn bool cbor_value_is_half_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (16-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_float() + */ + +/** + * Retrieves the CBOR half-precision floating point (16-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a half-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_half_float is recommended. + * + * Note: since the C language does not have a standard type for half-precision + * floating point, this function takes a \c{void *} as a parameter for the + * storage area, which must be at least 16 bits wide. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_float() + */ +CborError cbor_value_get_half_float(const CborValue *value, void *result) +{ + assert(cbor_value_is_half_float(value)); + + /* size has been computed already */ + uint16_t v = get16(value->ptr + 1); + memcpy(result, &v, sizeof(v)); + return CborNoError; +} + +/** @} */ diff --git a/third_party/tinycbor/cborparser_dup_string.c b/third_party/tinycbor/cborparser_dup_string.c new file mode 100644 index 0000000000..60dbdbeb95 --- /dev/null +++ b/third_party/tinycbor/cborparser_dup_string.c @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include + +/** + * \fn CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a len (those variables must not be NULL). + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If \c malloc returns a NULL pointer, this function will return error + * condition \ref CborErrorOutOfMemory. + * + * On success, \c{*buffer} will contain a valid pointer that must be freed by + * calling \c{free()}. This is the case even for zero-length strings. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)) in addition to the + * malloc'ed block. + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_copy_text_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a len (those variables must not be NULL). + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If \c malloc returns a NULL pointer, this function will return error + * condition \ref CborErrorOutOfMemory. + * + * On success, \c{*buffer} will contain a valid pointer that must be freed by + * calling \c{free()}. This is the case even for zero-length strings. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. If \a value points to the last item, then \a next will be + * invalid. + * + * This function may not run in constant time (it will run in O(n) time on the + * number of chunks). It requires constant memory (O(1)) in addition to the + * malloc'ed block. + * + * \sa cbor_value_copy_byte_string(), cbor_value_dup_text_string() + */ +CborError _cbor_value_dup_string(const CborValue *value, void **buffer, size_t *buflen, CborValue *next) +{ + assert(buffer); + assert(buflen); + *buflen = SIZE_MAX; + CborError err = _cbor_value_copy_string(value, NULL, buflen, NULL); + if (err) + return err; + + ++*buflen; + *buffer = malloc(*buflen); + if (!*buffer) { + /* out of memory */ + return CborErrorOutOfMemory; + } + err = _cbor_value_copy_string(value, *buffer, buflen, next); + if (err) { + free(*buffer); + return err; + } + return CborNoError; +} diff --git a/third_party/tinycbor/cborpretty.c b/third_party/tinycbor/cborpretty.c new file mode 100644 index 0000000000..4b1a6b6ab6 --- /dev/null +++ b/third_party/tinycbor/cborpretty.c @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "compilersupport_p.h" +#include "math_support_p.h" + +#include +#include +#include +#include +#include +#include + +/** + * \defgroup CborPretty Converting CBOR to text + * \brief Group of functions used to convert CBOR to text form. + * + * This group contains two functions that are can be used to convert one + * CborValue object to a text representation. This module attempts to follow + * the recommendations from RFC 7049 section 6 "Diagnostic Notation", though it + * has a few differences. They are noted below. + * + * TinyCBOR does not provide a way to convert from the text representation back + * to encoded form. To produce a text form meant to be parsed, CborToJson is + * recommended instead. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to text. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The output type produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle them. + * + * \sa CborParsing, CborToJson, cbor_parser_init() + */ + +/** + * \addtogroup CborPretty + * @{ + *

Text format

+ * + * As described in RFC 7049 section 6 "Diagnostic Notation", the format is + * largely borrowed from JSON, but modified to suit CBOR's different data + * types. TinyCBOR makes further modifications to distinguish different, but + * similar values. + * + * CBOR values are currently encoded as follows: + * \par Integrals (unsigned and negative) + * Base-10 (decimal) text representation of the value + * \par Byte strings: + * "h'" followed by the Base16 (hex) representation of the binary data, followed by an ending quote (') + * \par Text strings: + * C-style escaped string in quotes, with C11/C++11 escaping of Unicode codepoints above U+007F. + * \par Tags: + * Tag value, with the tagged value in parentheses. No special encoding of the tagged value is performed. + * \par Simple types: + * "simple(nn)" where \c nn is the simple value + * \par Null: + * \c null + * \par Undefined: + * \c undefined + * \par Booleans: + * \c true or \c false + * \par Floating point: + * If NaN or infinite, the actual words \c NaN or \c infinite. + * Otherwise, the decimal representation with as many digits as necessary to ensure no loss of information, + * with float values suffixed by "f" and half-float values suffixed by "f16" (doubles have no suffix). A dot is always present. + * \par Arrays: + * Comma-separated list of elements, enclosed in square brackets ("[" and "]"). + * If the array length is indeterminate, an underscore ("_") appears immediately after the opening bracket. + * \par Maps: + * Comma-separated list of key-value pairs, with the key and value separated + * by a colon (":"), enclosed in curly braces ("{" and "}"). + * If the map length is indeterminate, an underscore ("_") appears immediately after the opening brace. + */ + +static int hexDump(FILE *out, const uint8_t *buffer, size_t n) +{ + while (n--) { + int r = fprintf(out, "%02" PRIx8, *buffer++); + if (r < 0) + return r; + } + return 0; /* should be n * 2, but we don't have the original n anymore */ +} + +/* This function decodes buffer as UTF-8 and prints as escaped UTF-16. + * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */ +static int utf8EscapedDump(FILE *out, const char *buffer, size_t n) +{ + uint32_t uc; + while (n--) { + uc = (uint8_t)*buffer++; + if (uc < 0x80) { + /* single-byte UTF-8 */ + if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') { + if (fprintf(out, "%c", (char)uc) < 0) + return CborErrorIO; + continue; + } + + /* print as an escape sequence */ + char escaped = (char)uc; + switch (uc) { + case '"': + case '\\': + break; + case '\b': + escaped = 'b'; + break; + case '\f': + escaped = 'f'; + break; + case '\n': + escaped = 'n'; + break; + case '\r': + escaped = 'r'; + break; + case '\t': + escaped = 't'; + break; + default: + goto print_utf16; + } + if (fprintf(out, "\\%c", escaped) < 0) + return CborErrorIO; + continue; + } + + /* multi-byte UTF-8, decode it */ + unsigned charsNeeded; + uint32_t min_uc; + if (unlikely(uc <= 0xC1)) + return CborErrorInvalidUtf8TextString; + if (uc < 0xE0) { + /* two-byte UTF-8 */ + charsNeeded = 2; + min_uc = 0x80; + uc &= 0x1f; + } else if (uc < 0xF0) { + /* three-byte UTF-8 */ + charsNeeded = 3; + min_uc = 0x800; + uc &= 0x0f; + } else if (uc < 0xF5) { + /* four-byte UTF-8 */ + charsNeeded = 4; + min_uc = 0x10000; + uc &= 0x07; + } else { + return CborErrorInvalidUtf8TextString; + } + + if (n < charsNeeded - 1) + return CborErrorInvalidUtf8TextString; + n -= charsNeeded - 1; + + /* first continuation character */ + uint8_t b = (uint8_t)*buffer++; + if ((b & 0xc0) != 0x80) + return CborErrorInvalidUtf8TextString; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 2) { + /* second continuation character */ + b = (uint8_t)*buffer++; + if ((b & 0xc0) != 0x80) + return CborErrorInvalidUtf8TextString; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 3) { + /* third continuation character */ + b = (uint8_t)*buffer++; + if ((b & 0xc0) != 0x80) + return CborErrorInvalidUtf8TextString; + uc <<= 6; + uc |= b & 0x3f; + } + } + + /* overlong sequence? surrogate pair? out or range? */ + if (uc < min_uc || uc - 0xd800U < 2048U || uc > 0x10ffff) + return CborErrorInvalidUtf8TextString; + + /* now print the sequence */ + if (charsNeeded > 3) { + /* needs surrogate pairs */ + if (fprintf(out, "\\u%04" PRIX32 "\\u%04" PRIX32, + (uc >> 10) + 0xd7c0, /* high surrogate */ + (uc % 0x0400) + 0xdc00) < 0) + return CborErrorIO; + } else { +print_utf16: + /* no surrogate pair needed */ + if (fprintf(out, "\\u%04" PRIX32, uc) < 0) + return CborErrorIO; + } + } + return CborNoError; +} + +static CborError value_to_pretty(FILE *out, CborValue *it); +static CborError container_to_pretty(FILE *out, CborValue *it, CborType containerType) +{ + const char *comma = ""; + while (!cbor_value_at_end(it)) { + if (fprintf(out, "%s", comma) < 0) + return CborErrorIO; + comma = ", "; + + CborError err = value_to_pretty(out, it); + if (err) + return err; + + if (containerType == CborArrayType) + continue; + + /* map: that was the key, so get the value */ + if (fprintf(out, ": ") < 0) + return CborErrorIO; + err = value_to_pretty(out, it); + if (err) + return err; + } + return CborNoError; +} + +static CborError value_to_pretty(FILE *out, CborValue *it) +{ + CborError err; + CborType type = cbor_value_get_type(it); + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + + if (fprintf(out, type == CborArrayType ? "[" : "{") < 0) + return CborErrorIO; + if (!cbor_value_is_length_known(it)) { + if (fprintf(out, "_ ") < 0) + return CborErrorIO; + } + + err = cbor_value_enter_container(it, &recursed); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + err = container_to_pretty(out, &recursed, type); + if (err) { + it->ptr = recursed.ptr; + return err; /* parse error */ + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + if (fprintf(out, type == CborArrayType ? "]" : "}") < 0) + return CborErrorIO; + return CborNoError; + } + + case CborIntegerType: { + uint64_t val; + cbor_value_get_raw_integer(it, &val); /* can't fail */ + + if (cbor_value_is_unsigned_integer(it)) { + if (fprintf(out, "%" PRIu64, val) < 0) + return CborErrorIO; + } else { + /* CBOR stores the negative number X as -1 - X + * (that is, -1 is stored as 0, -2 as 1 and so forth) */ + if (++val) { /* unsigned overflow may happen */ + if (fprintf(out, "-%" PRIu64, val) < 0) + return CborErrorIO; + } else { + /* overflown + * 0xffff`ffff`ffff`ffff + 1 = + * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */ + if (fprintf(out, "-18446744073709551616") < 0) + return CborErrorIO; + } + } + break; + } + + case CborByteStringType:{ + size_t n = 0; + uint8_t *buffer; + err = cbor_value_dup_byte_string(it, &buffer, &n, it); + if (err) + return err; + + bool failed = fprintf(out, "h'") < 0 || hexDump(out, buffer, n) < 0 || fprintf(out, "'") < 0; + free(buffer); + return failed ? CborErrorIO : CborNoError; + } + + case CborTextStringType: { + size_t n = 0; + char *buffer; + err = cbor_value_dup_text_string(it, &buffer, &n, it); + if (err) + return err; + + err = CborNoError; + bool failed = fprintf(out, "\"") < 0 + || (err = utf8EscapedDump(out, buffer, n)) != CborNoError + || fprintf(out, "\"") < 0; + free(buffer); + return err != CborNoError ? err : + failed ? CborErrorIO : CborNoError; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); /* can't fail */ + if (fprintf(out, "%" PRIu64 "(", tag) < 0) + return CborErrorIO; + err = cbor_value_advance_fixed(it); + if (err) + return err; + err = value_to_pretty(out, it); + if (err) + return err; + if (fprintf(out, ")") < 0) + return CborErrorIO; + return CborNoError; + } + + case CborSimpleType: { + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); /* can't fail */ + if (fprintf(out, "simple(%" PRIu8 ")", simple_type) < 0) + return CborErrorIO; + break; + } + + case CborNullType: + if (fprintf(out, "null") < 0) + return CborErrorIO; + break; + + case CborUndefinedType: + if (fprintf(out, "undefined") < 0) + return CborErrorIO; + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); /* can't fail */ + if (fprintf(out, val ? "true" : "false") < 0) + return CborErrorIO; + break; + } + + case CborDoubleType: { + const char *suffix; + double val; + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + suffix = "f"; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); + suffix = "f16"; + } else { + cbor_value_get_double(it, &val); + suffix = ""; + } + + int r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) + suffix = ""; + + uint64_t ival = (uint64_t)fabs(val); + if (ival == fabs(val)) { + /* this double value fits in a 64-bit integer, so show it as such + * (followed by a floating point suffix, to disambiguate) */ + r = fprintf(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix); + } else { + /* this number is definitely not a 64-bit integer */ + r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix); + } + if (r < 0) + return CborErrorIO; + break; + } + + case CborInvalidType: + if (fprintf(out, "invalid") < 0) + return CborErrorIO; + return CborErrorUnknownType; + } + + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value) + * + * Converts the current CBOR type pointed by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * \sa cbor_value_to_pretty_advance(), cbor_value_to_json_advance() + */ + +/** + * Converts the current CBOR type pointed by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream. + * + * \sa cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value) +{ + return value_to_pretty(out, value); +} + +/** @} */ diff --git a/third_party/tinycbor/compilersupport_p.h b/third_party/tinycbor/compilersupport_p.h new file mode 100644 index 0000000000..dc8597db83 --- /dev/null +++ b/third_party/tinycbor/compilersupport_p.h @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef COMPILERSUPPORT_H +#define COMPILERSUPPORT_H + +#include "cbor.h" + +#ifndef _BSD_SOURCE +# define _BSD_SOURCE +#endif +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif +#include +#include +#include +#include +#include + +#ifndef __cplusplus +# include +#endif + +#ifdef __F16C__ +# include +#endif + +#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L || __cpp_static_assert >= 200410 +# define cbor_static_assert(x) static_assert(x, #x) +#elif !defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) && (__STDC_VERSION__ > 199901L) +# define cbor_static_assert(x) _Static_assert(x, #x) +#else +# define cbor_static_assert(x) ((void)sizeof(char[2*!!(x) - 1])) +#endif +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +/* inline is a keyword */ +#else +/* use the definition from cbor.h */ +# define inline CBOR_INLINE +#endif + +#ifndef STRINGIFY +#define STRINGIFY(x) STRINGIFY2(x) +#endif +#define STRINGIFY2(x) #x + +#if !defined(UINT32_MAX) || !defined(INT64_MAX) +/* C89? We can define UINT32_MAX portably, but not INT64_MAX */ +# error "Your system has stdint.h but that doesn't define UINT32_MAX or INT64_MAX" +#endif + +#ifndef DBL_DECIMAL_DIG +/* DBL_DECIMAL_DIG is C11 */ +# define DBL_DECIMAL_DIG 17 +#endif +#define DBL_DECIMAL_DIG_STR STRINGIFY(DBL_DECIMAL_DIG) + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + + +/* Disable this optimization for TI ARM compiler v18 or higher because it has some issues +with these intrinsics. */ +#if !defined(__TI_COMPILER_VERSION__) || __TI_COMPILER_VERSION__ < 18000000 +#if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) || \ + (__has_builtin(__builtin_bswap64) && __has_builtin(__builtin_bswap32)) +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define cbor_ntohll __builtin_bswap64 +# define cbor_htonll __builtin_bswap64 +# define cbor_ntohl __builtin_bswap32 +# define cbor_htonl __builtin_bswap32 +# ifdef __INTEL_COMPILER +# define cbor_ntohs _bswap16 +# define cbor_htons _bswap16 +# elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 608) || __has_builtin(__builtin_bswap16) +# define cbor_ntohs __builtin_bswap16 +# define cbor_htons __builtin_bswap16 +# else +# define cbor_ntohs(x) (((uint16_t)x >> 8) | ((uint16_t)x << 8)) +# define cbor_htons cbor_ntohs +# endif +# else +# define cbor_ntohll +# define cbor_htonll +# define cbor_ntohl +# define cbor_htonl +# define cbor_ntohs +# define cbor_htons +# endif +#elif defined(__sun) +# include +#elif defined(_MSC_VER) +/* MSVC, which implies Windows, which implies little-endian and sizeof(long) == 4 */ +# define cbor_ntohll _byteswap_uint64 +# define cbor_htonll _byteswap_uint64 +# define cbor_ntohl _byteswap_ulong +# define cbor_htonl _byteswap_ulong +# define cbor_ntohs _byteswap_ushort +# define cbor_htons _byteswap_ushort +#endif +#endif +#ifndef cbor_ntohs +# define cbor_ntohs(x) (((uint16_t)x >> 8) | ((uint16_t)x << 8)) +# define cbor_htons cbor_ntohs +//# include +//# define cbor_ntohs ntohs +//# define cbor_htons htons +#endif +#ifndef cbor_ntohl +//# include +//# define cbor_ntohl ntohl +//# define cbor_htonl htonl +# define cbor_ntohl(x) ((((uint32_t)x >> 24) & 0xff) | (((uint32_t)x >> 8) & 0xff00) | (((uint32_t)x & 0xff00) << 8) | (((uint32_t)x & 0xff) << 24)) +# define cbor_htonl cbor_ntohl +#endif +#ifndef cbor_ntohll +# define cbor_ntohll ntohll +# define cbor_htonll htonll +/* ntohll isn't usually defined */ +# ifndef ntohll +# if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define ntohll +# define htonll +# elif defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define ntohll(x) ((ntohl((uint32_t)(x)) * UINT64_C(0x100000000)) + (ntohl((x) >> 32))) +# define htonll ntohll +# elif __little_endian__ == 1 +# define ntohll(x) ((cbor_ntohl(((uint32_t)(x))) * UINT64_C(0x100000000)) + (cbor_ntohl(((x) >> 32)))) +# define htonll ntohll +# else +# error "Unable to determine byte order!" +# endif +# endif +#endif + + +#ifdef __cplusplus +# define CONST_CAST(t, v) const_cast(v) +#else +/* C-style const_cast without triggering a warning with -Wcast-qual */ +# define CONST_CAST(t, v) (t)(uintptr_t)(v) +#endif + +#ifdef __GNUC__ +#ifndef likely +# define likely(x) __builtin_expect(!!(x), 1) +#endif +#ifndef unlikely +# define unlikely(x) __builtin_expect(!!(x), 0) +#endif +# define unreachable() __builtin_unreachable() +#elif defined(_MSC_VER) +# define likely(x) (x) +# define unlikely(x) (x) +# define unreachable() __assume(0) +#else +# define likely(x) (x) +# define unlikely(x) (x) +# define unreachable() do {} while (0) +#endif + + + +static inline bool add_check_overflow(size_t v1, size_t v2, size_t *r) +{ +#if ((defined(__GNUC__) && (__GNUC__ >= 5)) && !defined(__INTEL_COMPILER)) || __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(v1, v2, r); +#else + /* unsigned additions are well-defined */ + *r = v1 + v2; + return v1 > v1 + v2; +#endif +} + +static inline unsigned short encode_half(double val) +{ +#ifdef __F16C__ + return _cvtss_sh(val, 3); +#else + uint64_t v; + memcpy(&v, &val, sizeof(v)); + int sign = v >> 63 << 15; + int exp = (v >> 52) & 0x7ff; + int mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ + exp -= 1023; + if (exp == 1024) { + /* infinity or NaN */ + exp = 16; + mant >>= 1; + } else if (exp >= 16) { + /* overflow, as largest number */ + exp = 15; + mant = 1023; + } else if (exp >= -14) { + /* regular normal */ + } else if (exp >= -24) { + /* subnormal */ + mant |= 1024; + mant >>= -(exp + 14); + exp = -15; + } else { + /* underflow, make zero */ + return 0; + } + + /* safe cast here as bit operations above guarantee not to overflow */ + return (unsigned short)(sign | ((exp + 15) << 10) | mant); +#endif +} + +#endif /* COMPILERSUPPORT_H */ diff --git a/third_party/tinycbor/extract_number_p.h b/third_party/tinycbor/extract_number_p.h new file mode 100644 index 0000000000..b65ca4419e --- /dev/null +++ b/third_party/tinycbor/extract_number_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#include "cbor.h" +#include "cborconstants_p.h" +#include "compilersupport_p.h" +#include + +static inline uint16_t get16(const uint8_t *ptr) +{ + uint16_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohs(result); +} + +static inline uint32_t get32(const uint8_t *ptr) +{ + uint32_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohl(result); +} + +static inline uint64_t get64(const uint8_t *ptr) +{ + uint64_t result; + memcpy(&result, ptr, sizeof(result)); + return cbor_ntohll(result); +} + +static CborError extract_number(const uint8_t **ptr, const uint8_t *end, uint64_t *len) +{ + uint8_t additional_information = **ptr & SmallValueMask; + ++*ptr; + if (additional_information < Value8Bit) { + *len = additional_information; + return CborNoError; + } + if (unlikely(additional_information > Value64Bit)) + return CborErrorIllegalNumber; + + size_t bytesNeeded = (size_t)(1 << (additional_information - Value8Bit)); + if (unlikely(bytesNeeded > (size_t)(end - *ptr))) { + return CborErrorUnexpectedEOF; + } else if (bytesNeeded == 1) { + *len = (uint8_t)(*ptr)[0]; + } else if (bytesNeeded == 2) { + *len = get16(*ptr); + } else if (bytesNeeded == 4) { + *len = get32(*ptr); + } else { + *len = get64(*ptr); + } + *ptr += bytesNeeded; + return CborNoError; +} diff --git a/third_party/tinycbor/math_support_p.h b/third_party/tinycbor/math_support_p.h new file mode 100644 index 0000000000..676f781cc1 --- /dev/null +++ b/third_party/tinycbor/math_support_p.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Intel Corporation +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** +****************************************************************************/ + +#ifndef MATH_SUPPORT_H +#define MATH_SUPPORT_H + +#include + +/* this function was copied & adapted from RFC 7049 Appendix D */ +static inline double decode_half(unsigned short half) +{ +#ifdef __F16C__ + return _cvtsh_ss(half); +#else + int exp = (half >> 10) & 0x1f; + int mant = half & 0x3ff; + double val; + if (exp == 0) val = ldexp(mant, -24); + else if (exp != 31) val = ldexp(mant + 1024, exp - 25); + else val = mant == 0 ? INFINITY : NAN; + return half & 0x8000 ? -val : val; +#endif +} + +#endif // MATH_SUPPORT_H + From 5cf94939083e6a6e74d005cc1ae7fc778ae12fe8 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Mon, 25 Feb 2019 15:36:02 -0800 Subject: [PATCH 030/844] Add high priority tasks that always try to grow task pool. (#287) --- lib/include/iot_taskpool.h | 6 +- lib/include/types/iot_taskpool_types.h | 8 ++ lib/source/common/iot_taskpool.c | 98 +++++++++++++++++------ tests/common/unit/iot_tests_taskpool.c | 105 +++++++++++++++++++++---- tests/mqtt/unit/iot_tests_mqtt_api.c | 3 +- 5 files changed, 180 insertions(+), 40 deletions(-) diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 129c844882..eb6cf6f9db 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -320,7 +320,8 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. * a call to @ref taskpool_function_create. - * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * @param[in] flags Flags to be passed by the user, e.g. to identify the job as high priority by specifying #IOT_TASKPOOL_JOB_HIGH_PRIORITY. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -401,7 +402,8 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, */ /* @[declare_taskpool_schedule] */ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ); + IotTaskPoolJob_t * const pJob, + uint32_t flags ); /* @[declare_taskpool_schedule] */ /** diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 2821272949..839072830e 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -352,6 +352,14 @@ typedef struct IotTaskPoolJob #define IOT_TASKPOOL_INITIALIZER { 0 } /**< @brief Initializer for a #IotTaskPoolJob_t. */ /* @[define_taskpool_initializers] */ +/** +* @brief Schedules a job to execute immediately. +* +* @warning This flag may cause the task pool to create a worker to serve the job immediately, and +* therefore using this flag may incur in additinal memory usage. +*/ +#define IOT_TASKPOOL_JOB_HIGH_PRIORITY 0x00000001 + /** * @brief Allows the use of the handle to the system task pool. * diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 78e022da35..994827f51c 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -203,7 +203,8 @@ static void _signalShutdown( IotTaskPool_t * const pTaskPool, * */ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ); + IotTaskPoolJob_t * const pJob, + uint32_t flags ); /** * Matches a deferred job in the timer queue with its timer event wrapper. @@ -570,7 +571,8 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, /*-----------------------------------------------------------*/ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ) + IotTaskPoolJob_t * const pJob, + uint32_t flags ) { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -595,7 +597,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /* If all safety checks completed, proceed. */ if( _TASKPOOL_SUCCEEDED( status ) ) { - status = _scheduleInternal( pTaskPool, pJob ); + status = _scheduleInternal( pTaskPool, pJob, flags ); } } _TASKPOOL_EXIT_CRITICAL_SECTION; @@ -617,7 +619,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool if( timeMs == 0 ) { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob ) ); + _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); } _TASKPOOL_ENTER_CRITICAL_SECTION; @@ -751,21 +753,27 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) case IOT_TASKPOOL_SUCCESS: pMessage = "SUCCESS"; break; + case IOT_TASKPOOL_BAD_PARAMETER: pMessage = "BAD PARAMETER"; break; + case IOT_TASKPOOL_ILLEGAL_OPERATION: pMessage = "ILLEGAL OPERATION"; break; + case IOT_TASKPOOL_NO_MEMORY: pMessage = "NO MEMORY"; break; + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: pMessage = "SHUTDOWN IN PROGRESS"; break; + case IOT_TASKPOOL_CANCEL_FAILED: pMessage = "CANCEL FAILED"; break; + default: pMessage = "INVALID STATUS"; break; @@ -989,12 +997,6 @@ static void _taskPoolWorker( void * pUserContext ) IotLogDebug( "Worker thread exiting because exit condition was set." ); } - else if( pTaskPool->activeThreads > pTaskPool->maxThreads ) - { - shouldExit = true; - - IotLogDebug( "Worker thread exiting because maximum quota was exceeded." ); - } /* Check if thread should exit. */ if( shouldExit ) @@ -1075,6 +1077,20 @@ static void _taskPoolWorker( void * pUserContext ) } _TASKPOOL_EXIT_CRITICAL_SECTION; } + + /* We check whether this thread needs to exit or not at the end of the outer loop, so + * we can support the case for scheduling 'high prioroty' jobs that exceed the + * max threads quota. */ + _TASKPOOL_ENTER_CRITICAL_SECTION; + { + if( pTaskPool->activeThreads > pTaskPool->maxThreads ) + { + shouldExit = true; + + IotLogDebug( "Worker thread exiting because maximum quota was exceeded." ); + } + } + _TASKPOOL_EXIT_CRITICAL_SECTION; } } @@ -1201,18 +1217,19 @@ static void _signalShutdown( IotTaskPool_t * const pTaskPool, /* ---------------------------------------------------------------------------------------------- */ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ) + IotTaskPoolJob_t * const pJob, + uint32_t flags ) { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + bool mustGrow = false; + bool shouldGrow = false; + /* Update the job status to 'scheduled'. */ pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; pJob->status |= IOT_TASKPOOL_STATUS_SCHEDULED; - /* Append the job to the dispatch queue. */ - IotQueue_Enqueue( &pTaskPool->dispatchQueue, &pJob->link ); - - /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ + /* Update the number of active jobs optimistically, so new requests can be served by creating new threads. */ pTaskPool->activeJobs++; /* If all threads are busy, try and create a new one. Failing to create a new thread @@ -1220,12 +1237,24 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, */ uint32_t activeThreads = pTaskPool->activeThreads; - if( activeThreads == pTaskPool->activeJobs ) + if( activeThreads <= pTaskPool->activeJobs ) { + /* If the job scheduling is tagged as high priority, then we must grow the task pool, + * no matter how many threads are active already. */ + if( ( flags & IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_JOB_HIGH_PRIORITY ) + { + mustGrow = true; + } + /* Grow the task pool up to the maximum number of threads indicated by the user. * Growing the taskpool can safely fail, the existing threads will eventually pick up * the job sometimes later. */ - if( activeThreads < pTaskPool->maxThreads ) + else if( activeThreads < pTaskPool->maxThreads ) + { + shouldGrow = true; + } + + if( ( mustGrow == true ) || ( shouldGrow == true ) ) { IotLogInfo( "Growing a Task pool with a new worker thread..." ); @@ -1240,16 +1269,39 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, } else { - /* Failure to create a worker thread does not hinder functional correctness, but rather just responsiveness. */ + /* Failure to create a worker thread may not hinder functional correctness, but rather just responsiveness. */ IotLogWarn( "Task pool failed to create a worker thread." ); + + /* Failure to create a worker thread for a high priority job is considered a failure. */ + if( mustGrow ) + { + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } } } } - /* Signal a worker to pick up the job. */ - IotSemaphore_Post( &pTaskPool->dispatchSignal ); + _TASKPOOL_FUNCTION_CLEANUP(); + + if( _TASKPOOL_SUCCEEDED( status ) ) + { + /* Append the job to the dispatch queue. */ + IotQueue_Enqueue( &pTaskPool->dispatchQueue, &pJob->link ); + + /* Signal a worker to pick up the job. */ + IotSemaphore_Post( &pTaskPool->dispatchSignal ); + } + else + { + /* Scheduling can only fail to allocate a new worker, which is an error + * only for high prority tasks. */ + IotTaskPool_Assert( mustGrow == true ); + + /* Revert updating the number of active jobs. */ + pTaskPool->activeJobs--; + } - _TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL(); + _TASKPOOL_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -1408,7 +1460,7 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, else if( IotLink_IsLinked( &pJob->link ) ) { /* If the job is not in the dispatch or timer queue, it must be in the cache. */ - IotTaskPool_Assert( ( pJob->jobStatus & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ); + IotTaskPool_Assert( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ); IotListDouble_Remove( &pJob->link ); } @@ -1540,7 +1592,7 @@ static void _timerThread( void * pArgument ) IotLogDebug( "Scheduling job from timer event." ); /* Queue the job associated with the received timer event. */ - _scheduleInternal( pTaskPool, pTimerEvent->pJob ); + _scheduleInternal( pTaskPool, pTimerEvent->pJob, 0 ); /* Free the timer event. */ IotTaskPool_FreeTimerEvent( pTimerEvent ); diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 0f04d4e65b..55cf9c1caa 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -106,7 +106,8 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) RUN_TEST_CASE( Common_Unit_Task_Pool, CreateJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasksError ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_Grow ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_Grow ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ); RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ); RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait ); RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ); @@ -253,7 +254,7 @@ static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, pUserContext = ( JobBlockingUserContext_t * ) context; - /* Signal that the vallback has been called. */ + /* Signal that the callback has been called. */ IotSemaphore_Post( &pUserContext->signal ); /* This callback will emulate a blocking wait, for the sole purpose of stealing a task pool @@ -463,9 +464,9 @@ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( NULL, &job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* Destroy the job, so we do not leak it. */ TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); @@ -489,9 +490,9 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* Recycle the job, so we do not leak it. */ TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); @@ -503,7 +504,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) /** * @brief Tests that the taskpool actually grows the number of tasks as expected. */ -TEST( Common_Unit_Task_Pool, TaskPool_Grow ) +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_Grow ) { #define _NUMBER_OF_JOBS 4 @@ -533,7 +534,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_Grow ) for( count = 0; count < _NUMBER_OF_JOBS; ++count ) { - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } count = 0; @@ -569,6 +570,82 @@ TEST( Common_Unit_Task_Pool, TaskPool_Grow ) /*-----------------------------------------------------------*/ +/** +* @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. +*/ +TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) +{ +#define _NUMBER_OF_JOBS 4 +#define _NUMBER_OF_THREADS 3 + + IotTaskPool_t taskPool; + + /* Use a taskpool with not enough threads. */ + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = _NUMBER_OF_THREADS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + JobBlockingUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, _NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, _NUMBER_OF_JOBS ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Statically allocated job, schedule one, then wait. */ + { + uint32_t count; + IotTaskPoolJob_t jobs[ _NUMBER_OF_JOBS ]; + + /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ + for ( count = 0; count < _NUMBER_OF_JOBS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + + /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ + for ( count = 0; count < _NUMBER_OF_THREADS; ++count ) + { + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + } + + /*Schedule a high pri task can make it grow more. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); + + count = 0; + + while ( true ) + { + /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ + IotSemaphore_Wait( &userContext.signal ); + + ++count; + + if ( count == _NUMBER_OF_JOBS ) + { + break; + } + } + + /* Signal all taskpool threads to exit. */ + for ( count = 0; count < _NUMBER_OF_JOBS; ++count ) + { + IotSemaphore_Post( &userContext.block ); + } + } + + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotSemaphore_Destroy( &userContext.signal ); + IotSemaphore_Destroy( &userContext.block ); + +#undef _NUMBER_OF_JOBS +} + +/*-----------------------------------------------------------*/ + /** * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. */ @@ -595,7 +672,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ) /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &job ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &job, 0 ); switch( errorSchedule ) { @@ -767,7 +844,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneRecyclableThenWai /* Shedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, pJob ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, pJob, 0 ); switch( errorSchedule ) { @@ -847,7 +924,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ) /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ] ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -931,7 +1008,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllRecyclableThenWai /* Shedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, tpJobs[ count ] ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -1099,7 +1176,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ) IotTaskPoolError_t errorSchedule; /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ] ); + errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -1126,7 +1203,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ) { IotTaskPoolError_t errorReSchedule; - errorReSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ] ); + errorReSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); switch( errorReSchedule ) { diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 53742f162b..700cb0e094 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -551,7 +551,8 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) pOperation, &( pOperation->job ) ) ); TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_Schedule( &( _IotMqttTaskPool ), - &( pOperation->job ) ) ); + &( pOperation->job ), + 0 ) ); /* Wait for the job to complete. */ IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); From cf93602f38992caedfadbbcb4e0dd94e3c4a02b9 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Mon, 25 Feb 2019 16:23:42 -0800 Subject: [PATCH 031/844] Return value for task poll internal cancellation was assigned to the wrong enum after a variable name change. (#290) --- lib/source/common/iot_taskpool.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 994827f51c..1e060e0483 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -1441,16 +1441,16 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, IotTaskPoolJobStatus_t statusAtCancellation; /* Cancellation can fail, e.g. if a job is being executed when we are trying to cancel it. */ - jobStatus = _tryCancelInternal( pTaskPool, pJob, &statusAtCancellation ); + status = _tryCancelInternal( pTaskPool, pJob, &statusAtCancellation ); - switch( jobStatus ) + switch( status ) { case IOT_TASKPOOL_SUCCESS: break; case IOT_TASKPOOL_CANCEL_FAILED: IotLogWarn( "Removing a scheduled job failed because the job could not be canceled." ); - jobStatus = IOT_TASKPOOL_ILLEGAL_OPERATION; + status = IOT_TASKPOOL_ILLEGAL_OPERATION; break; default: From 5d2e85c4b3139d16d8bf767b2c94c51fd2577a5e Mon Sep 17 00:00:00 2001 From: qiutongs Date: Mon, 25 Feb 2019 16:50:57 -0800 Subject: [PATCH 032/844] add metrics library and serializer library (#288) --- CMakeLists.txt | 6 + lib/include/iot_metrics.h | 133 +++++ lib/include/iot_serializer.h | 483 ++++++++++++++++++ lib/include/private/iot_static_memory.h | 160 ++++++ lib/source/common/CMakeLists.txt | 4 +- .../static_memory/iot_static_memory_metrics.c | 103 ++++ .../iot_static_memory_serializer.c | 240 +++++++++ lib/source/metrics/CMakeLists.txt | 9 + lib/source/metrics/iot_metrics.c | 130 +++++ lib/source/serializer/CMakeLists.txt | 15 + .../cbor/iot_serializer_tinycbor_decoder.c | 459 +++++++++++++++++ .../cbor/iot_serializer_tinycbor_encoder.c | 320 ++++++++++++ tests/CMakeLists.txt | 3 + tests/iot_tests_config.h | 24 + tests/serializer/CMakeLists.txt | 7 + tests/serializer/iot_test_serializer_cbor.c | 381 ++++++++++++++ tests/serializer/iot_tests_serializer.c | 106 ++++ 17 files changed, 2582 insertions(+), 1 deletion(-) create mode 100644 lib/include/iot_metrics.h create mode 100644 lib/include/iot_serializer.h create mode 100644 lib/source/common/static_memory/iot_static_memory_metrics.c create mode 100644 lib/source/common/static_memory/iot_static_memory_serializer.c create mode 100644 lib/source/metrics/CMakeLists.txt create mode 100644 lib/source/metrics/iot_metrics.c create mode 100644 lib/source/serializer/CMakeLists.txt create mode 100644 lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c create mode 100644 lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c create mode 100644 tests/serializer/CMakeLists.txt create mode 100644 tests/serializer/iot_test_serializer_cbor.c create mode 100644 tests/serializer/iot_tests_serializer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 38c4b0c23d..93ff8a52fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,12 @@ add_subdirectory( lib/source/mqtt ) # Shadow library. add_subdirectory( lib/source/shadow ) +# Metrics library. +add_subdirectory( lib/source/metrics ) + +# Serializer library. +add_subdirectory( lib/source/serializer ) + # Demo executables. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( demos/posix ) diff --git a/lib/include/iot_metrics.h b/lib/include/iot_metrics.h new file mode 100644 index 0000000000..02bb1af6b3 --- /dev/null +++ b/lib/include/iot_metrics.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_metrics.h + * @brief User-facing functions and structs of Metrics library + * + */ + +#ifndef _IOT_METRICS_H_ +#define _IOT_METRICS_H_ + +#include + +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +#include "iot_linear_containers.h" + +/** + * @def IotMetrics_Assert( expression ) + * @brief Assertion macro for the Metrics library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_METRICS_ENABLE_ASSERTS == 1 + #ifndef IotMetrics_Assert + #include + #define IotMetrics_Assert( expression ) assert( expression ) + #endif +#else + #define IotMetrics_Assert( expression ) +#endif + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotMetrics_MallocTcpConnection + #define IotMetrics_MallocTcpConnection Iot_MallocMetricsTcpConnection + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotMetrics_FreeTcpConnection + #define IotMetrics_FreeTcpConnection Iot_FreeMetricsTcpConnection + #endif + +#else /* if IOT_STATIC_MEMORY_ONLY */ + #include + + #ifndef IotMetrics_MallocTcpConnection + #define IotMetrics_MallocTcpConnection malloc + #endif + + #ifndef IotMetrics_FreeTcpConnection + #define IotMetrics_FreeTcpConnection free + #endif + +#endif /* if IOT_STATIC_MEMORY_ONLY */ + +/** + * Data handle of TCP connection + */ +typedef struct IotMetricsTcpConnection +{ + IotLink_t link; + uint32_t id; + uint32_t remoteIP; /* This is limited to IPv4. */ + uint16_t remotePort; +} IotMetricsTcpConnection_t; + +/** + * Callback data handle to process specific metrics. + */ +typedef struct IotMetricsListCallback +{ + void * param1; + void ( * function )( void * param1, + IotListDouble_t * pMetricsList ); /* pMetricsList is guaranteed a valid metrics list. */ +} IotMetricsListCallback_t; + +/** + * This function must be called before any other functions. + */ +bool IotMetrics_Init(); + +/** + * Record one TCP connection metric. + */ +void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ); + +/** + * Remove one TCP connection metric. + */ +void IotMetrics_RemoveTcpConnection( uint32_t tcpConnectionId ); + +/** + * Use a callback to process a list of TCP connections + */ +void IotMetrics_ProcessTcpConnections( IotMetricsListCallback_t tcpConnectionsCallback ); + +#endif /* ifndef _IOT_METRICS_H_ */ diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h new file mode 100644 index 0000000000..61cdc23988 --- /dev/null +++ b/lib/include/iot_serializer.h @@ -0,0 +1,483 @@ +/* + * Amazon FreeRTOS System Initialization + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/* + * This library is to provide a consistent layer for serializing JSON-like format. + * The implementations can be CBOR or JSON. + */ + +#ifndef _IOT_SERIALIZER_H_ +#define _IOT_SERIALIZER_H_ + +/* Standard includes. */ +#include +#include +#include +#include + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +#if IOT_SERIALIZER_ENABLE_ASSERTS == 1 + #ifndef IotSerializer_Assert + #include + #define IotSerializer_Assert( expression ) assert( expression ) + #endif +#else + #define IotSerializer_Assert( expression ) +#endif + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotSerializer_MallocCborEncoder + #define IotSerializer_MallocCborEncoder Iot_MallocSerializerCborEncoder + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotSerializer_FreeCborEncoder + #define IotSerializer_FreeCborEncoder Iot_FreeSerializerCborEncoder + #endif + +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotSerializer_MallocCborParser + #define IotSerializer_MallocCborParser Iot_MallocSerializerCborParser + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotSerializer_FreeCborParser + #define IotSerializer_FreeCborParser Iot_FreeSerializerCborParser + #endif + +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotSerializer_MallocCborValue + #define IotSerializer_MallocCborValue Iot_MallocSerializerCborValue + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotSerializer_FreeCborValue + #define IotSerializer_FreeCborValue Iot_FreeSerializerCborValue + #endif + +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotSerializer_MallocDecoderObject + #define IotSerializer_MallocDecoderObject Iot_MallocSerializerDecoderObject + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotSerializer_FreeDecoderObject + #define IotSerializer_FreeDecoderObject Iot_FreeSerializerDecoderObject + #endif + +#else /* if IOT_STATIC_MEMORY_ONLY */ + #include + + #ifndef IotSerializer_MallocCborEncoder + #define IotSerializer_MallocCborEncoder malloc + #endif + + #ifndef IotSerializer_FreeCborEncoder + #define IotSerializer_FreeCborEncoder free + #endif + + #ifndef IotSerializer_MallocCborParser + #define IotSerializer_MallocCborParser malloc + #endif + + #ifndef IotSerializer_FreeCborParser + #define IotSerializer_FreeCborParser free + #endif + + #ifndef IotSerializer_MallocCborValue + #define IotSerializer_MallocCborValue malloc + #endif + + #ifndef IotSerializer_FreeCborValue + #define IotSerializer_FreeCborValue free + #endif + + #ifndef IotSerializer_MallocDecoderObject + #define IotSerializer_MallocDecoderObject malloc + #endif + + #ifndef IotSerializer_FreeDecoderObject + #define IotSerializer_FreeDecoderObject free + #endif + +#endif /* if IOT_STATIC_MEMORY_ONLY */ + +#define IOT_SERIALIZER_INDEFINITE_LENGTH 0xffffffff + +#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_STREAM } + +#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_MAP } + +#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_ARRAY } + +#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED, .pHandle = NULL, .value = 0 } + +#define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL + +/* helper macro to create scalar data */ +#define IotSerializer_ScalarSignedInt( signedIntValue ) \ + ( IotSerializerScalarData_t ) { .value = { .signedInt = ( signedIntValue ) }, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT } \ + +#define IotSerializer_ScalarTextString( pTextStringValue ) \ + ( IotSerializerScalarData_t ) { .value = { .pString = ( ( uint8_t * ) pTextStringValue ), .stringLength = strlen( pTextStringValue ) }, .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ + +#define IotSerializer_ScalarByteString( pByteStringValue, length ) \ + ( IotSerializerScalarData_t ) { .value = { .pString = ( pByteStringValue ), .stringLength = ( length ) }, .type = IOT_SERIALIZER_SCALAR_BYTE_STRING } \ + +/* Determine if an object is a container. */ +#define IotSerializer_IsContainer( object ) \ + ( ( object ) \ + && ( ( object )->type == IOT_SERIALIZER_CONTAINER_STREAM \ + || ( object )->type == IOT_SERIALIZER_CONTAINER_ARRAY \ + || ( object )->type == IOT_SERIALIZER_CONTAINER_MAP ) ) + +/* error code returned by serializer function interface */ +typedef enum +{ + IOT_SERIALIZER_SUCCESS = 0, + IOT_SERIALIZER_BUFFER_TOO_SMALL, + IOT_SERIALIZER_OUT_OF_MEMORY, + IOT_SERIALIZER_INVALID_INPUT, + IOT_SERIALIZER_UNDEFINED_TYPE, + IOT_SERIALIZER_NOT_SUPPORTED, + IOT_SERIALIZER_NOT_FOUND, + IOT_SERIALIZER_INTERNAL_FAILURE +} IotSerializerError_t; + +/* two categories: + * 1. scalar: int, string, byte, ... + * 2. container: array, map, none + */ +typedef enum +{ + IOT_SERIALIZER_UNDEFINED = 0, + IOT_SERIALIZER_SCALAR_NULL, + IOT_SERIALIZER_SCALAR_BOOL, + IOT_SERIALIZER_SCALAR_SIGNED_INT, + IOT_SERIALIZER_SCALAR_TEXT_STRING, + IOT_SERIALIZER_SCALAR_BYTE_STRING, + /* below is container */ + IOT_SERIALIZER_CONTAINER_STREAM, + IOT_SERIALIZER_CONTAINER_ARRAY, + IOT_SERIALIZER_CONTAINER_MAP, +} IotSerializerDataType_t; + +/* encapsulate data value of different types */ +typedef struct IotSerializerScalarValue +{ + union + { + int64_t signedInt; + struct + { + uint8_t * pString; + size_t stringLength; + }; + bool booleanValue; + }; +} IotSerializerScalarValue_t; + +/* scalar data handle used in encoder */ +typedef struct IotSerializerScalarData +{ + IotSerializerDataType_t type; + IotSerializerScalarValue_t value; +} IotSerializerScalarData_t; + +/* container data handle used in encoder */ +typedef struct IotSerializerEncoderObject +{ + IotSerializerDataType_t type; + void * pHandle; +} IotSerializerEncoderObject_t; + + +/* data handle used in decoder: either container or scalar */ +typedef struct IotSerializerDecoderObject +{ + IotSerializerDataType_t type; + union + { + /* useful when this is a container */ + void * pHandle; + /* if the type is a container, the scalarValue is unuseful */ + IotSerializerScalarValue_t value; + }; +} IotSerializerDecoderObject_t; + +typedef void * IotSerializerDecoderIterator_t; + +/** + * @brief Table containing function pointers for encoder APIs. + */ +typedef struct IotSerializerEncodeInterface +{ + /** + * @brief Return the actual total size after encoding finished. + * + * @param[in] pEncoderObject: the outermost object pointer; behavior is undefined for any other object + * @param[in] pDataBuffer: the buffer pointer passed into init; behavior is undefined for any other buffer pointer + */ + size_t ( * getEncodedSize )( IotSerializerEncoderObject_t * pEncoderObject, + uint8_t * pDataBuffer ); + + /** + * @brief Return the extra size needed when the data to encode exceeds the maximum length of underlying buffer. + * When no exceeding, this should return 0. + * + * @param[in] pEncoderObject: the outermost object pointer; behavior is undefined for any other object + */ + size_t ( * getExtraBufferSizeNeeded )( IotSerializerEncoderObject_t * pEncoderObject ); + + /** + * @brief Initialize the object's handle with specified buffer and max size. + * + * @param[in] pEncoderObject Pointer of Encoder Object. After init, its type will be set to IOT_SERIALIZER_CONTAINER_STREAM. + * @param[in] pDataBuffer Pointer to allocated buffer by user; + * NULL pDataBuffer is valid, used to calculate needed size by calling getExtraBufferSizeNeeded. + * @param[in] maxSize Allocated buffer size + */ + IotSerializerError_t ( * init )( IotSerializerEncoderObject_t * pEncoderObject, + uint8_t * pDataBuffer, + size_t maxSize ); + + /** + * @brief Clean the object's handle + * + * @param[in] pEncoderObject The outermost object pointer; behavior is undefined for any other object + */ + void ( * destroy )( IotSerializerEncoderObject_t * pEncoderObject ); + + /** + * @brief Open a child container object. + * + * @param[in] pEncoderObject: the parent object. It must be a container object + * @param[in] pNewEncoderObject: the child object to create. It must be a container object + * @param[in] length: pre-known length of the container or IOT_SERIALIZER_INDEFINITE_LENGTH + * map: the length is number of key-value pairs + * array: the length is number of elements + * none: the length is unuseful + */ + IotSerializerError_t ( * openContainer )( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerEncoderObject_t * pNewEncoderObject, + size_t length ); + + /** + * @brief Open a child container object with string key. + * + * @param[in] pEncoderObject the parent object. It must be a container object + * @param[in] pKey Key for the child container + * @param[in] pNewEncoderObject the child object to create. It must be a container object + * @param[in] length: pre-known length of the container or IOT_SERIALIZER_INDEFINITE_LENGTH + * map: the length is number of key-value pairs + * array: the length is number of elements + * none: the length is unuseful + */ + IotSerializerError_t ( * openContainerWithKey )( IotSerializerEncoderObject_t * pEncoderObject, + const char * pKey, + IotSerializerEncoderObject_t * pNewEncoderObject, + size_t length ); + + /** + * @brief Close a child container object. + * pEncoderObject and pNewEncoderObject must be parent-child relationship and are in open state + * + * @param[in] pEncoderObject: the parent object. It must be a container object + * @param[in] pNewEncoderObject: the child object to create. It must be a container object + */ + IotSerializerError_t ( * closeContainer )( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerEncoderObject_t * pNewEncoderObject ); + + /** + * @brief Append a scalar data to a container, with type array or none. Map container is invalid + * + * @param[in] pEncoderObject: the parent object. It must be a container object, with type array or none. + * @param[in] scalarData: the scalar data to encode + */ + IotSerializerError_t ( * append )( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerScalarData_t scalarData ); + + /** + * @brief Append a key-value pair to a map container. The key is string type and value is a scalar. + * + * @param[in] pEncoderObject: the parent object. It must be a map container object. + * @param[in] pKey: text string representing key + * @param[in] scalarData: the scalar data to encode + */ + IotSerializerError_t ( * appendKeyValue )( IotSerializerEncoderObject_t * pEncoderObject, + const char * pKey, + IotSerializerScalarData_t scalarData ); +} IotSerializerEncodeInterface_t; + +/** + * @brief Table containing function pointers for decoder APIs. + */ +typedef struct IotSerializerDecodeInterface +{ + /** + * @brief Initialize decoder object with specified buffer. + * + * @param pDecoderObject Pointer to the decoder object allocated by user. + * @param pDataBuffer Pointer to the buffer containing data to be decoded. + * @param maxSize Maximum length of the buffer containing data to be decoded. + * @return IOT_SERIALIZER_SUCCESS if successful + */ + IotSerializerError_t ( * init )( IotSerializerDecoderObject_t * pDecoderObject, + const uint8_t * pDataBuffer, + size_t maxSize ); + + /** + * @brief Destroy the decoder object handle + * @param pDecoderObject Pointer to the decoder object + */ + void ( * destroy )( IotSerializerDecoderObject_t * pDecoderObject ); + + + /** + * @brief Steps into a container and creates an iterator to traverse through the container. + * Container can be of type array, map or stream. + * + * @param[in] pDecoderObject Pointer to the decoder object representing the container + * @param[out] pIterator Pointer to iterator which can be used for the traversing the container by calling next() + * @return IOT_SERIALIZER_SUCCESS if successful + */ + IotSerializerError_t ( * stepIn )( IotSerializerDecoderObject_t * pDecoderObject, + IotSerializerDecoderIterator_t * pIterator ); + + + /** + * @brief Gets the object value currently pointed to by an iterator. + * @param iterator The iterator handle + * @param pValueObject Value of the object pointed to by the iterator. + * @return IOT_SERIALIZER_SUCCESS if successful + */ + IotSerializerError_t ( * get )( IotSerializerDecoderIterator_t iterator, + IotSerializerDecoderObject_t * pValueObject ); + + /** + * + * @brief Find an object by key within a container. + * Container should always be of type MAP. + * + * @param[in] pDecoderObject Pointer to the decoder object representing container + * @param[in] pKey Pointer to the key for which value needs to be found. + * @param[out] pValueObject Pointer to the value object for the key. + * @return IOT_SERIALIZER_SUCCESS if successful + */ + IotSerializerError_t ( * find )( IotSerializerDecoderObject_t * pDecoderObject, + const char * pKey, + IotSerializerDecoderObject_t * pValueObject ); + + + /* + * Find the next object in the same value and save it to pNewDecoderObject + * + */ + + /** + * @brief Moves the iterator to next object within the container + * If the container is a map, it skips either a key or the value at a time. + * If the container is an array, it skips by one array element. + * + * @param[in] iterator Pointer to iterator + * @return IOT_SERIALIZER_SUCCESS if successful + */ + IotSerializerError_t ( * next )( IotSerializerDecoderIterator_t iterator ); + + /** + * @brief Function to check if the iterator reached end of container + * @param[in] iterator Pointer to iterator for the container + * @return IOT_SERIALIZER_SUCCESS if successful + */ + bool ( * isEndOfContainer )( IotSerializerDecoderIterator_t iterator ); + + + + /** + * @brief Steps out of the container by updating the decoder object to next byte position + * after the container. + * The iterator **should** point to the end of the container when calling this function. + * + * @param[in] iterator Pointer to iterator for the container. + * @param[in] The outer decoder object to the same container. + * @return IOT_SERIALIZER_SUCCESS if successful + */ + IotSerializerError_t ( * stepOut )( IotSerializerDecoderIterator_t iterator, + IotSerializerDecoderObject_t * pDecoderObject ); +} IotSerializerDecodeInterface_t; + +/* Global reference of CBOR/JSON encoder and decoder. */ +extern IotSerializerEncodeInterface_t _IotSerializerCborEncoder; + +extern IotSerializerDecodeInterface_t _IotSerializerCborDecoder; + +extern IotSerializerEncodeInterface_t _IotSerializerJsonEncoder; + +extern IotSerializerDecodeInterface_t _IotSerializerJsonDecoder; + +#endif /* ifndef _IOT_SERIALIZER_H_ */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 402c0fb7e1..e035e35041 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -423,4 +423,164 @@ void * AwsIot_MallocShadowSubscription( size_t size ); void AwsIot_FreeShadowSubscription( void * ptr ); /* @[declare_static_memory_freeshadowsubscription] */ +/** + * @brief Allocates memory to hold data for a new [Metrics TCP Connection] + * (@ref static_memory_mallocmetricstcpconnection). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Metrics TCP Connections. + * + * @param[in] size Requested size for the tcp connection. + * + * @return Pointer to a Metrics TCP Connection. If the size argument is larger than + * the fixed size of a Metrics TCP Connection object or no free Metrics TCP Connections + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocmetricstcpconnection] */ +void * Iot_MallocMetricsTcpConnection( size_t size ); +/* @[declare_static_memory_mallocmetricstcpconnection] */ + +/** + * @brief Frees an in-use [Metrics TCP Connection] + * (@ref static_memory_freemetricstcpconnection). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Metrics TCP Connections. + * + * @param[in] ptr Pointer to an active Metrics TCP Connection to free. + */ +/* @[declare_static_memory_freemetricstcpconnection] */ +void Iot_FreeMetricsTcpConnection( void * ptr ); +/* @[declare_static_memory_freemetricstcpconnection] */ + +/** + * @brief Allocates memory to hold data for a new [Serializer Cbor Encoder] + * (@ref static_memory_mallocserializercborencoder). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Serializer Cbor Encoder. + * + * @param[in] size Requested size for the serializer cbor encoder. + * + * @return Pointer to a Serializer Cbor Encoder. If the size argument is larger than + * the fixed size of a Serializer Cbor Encoder object or no free Serializer Cbor Encoder + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocserializercborencoder] */ +void * Iot_MallocSerializerCborEncoder( size_t size ); +/* @[declare_static_memory_mallocserializercborencoder] */ + +/** + * @brief Frees an in-use [Serializer Cbor Encoder] + * (@ref static_memory_freeserializercborencoder). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Serializer Cbor Encoder. + * + * @param[in] ptr Pointer to an active Serializer Cbor Encoder to free. + */ +/* @[declare_static_memory_freeserializercborencoder] */ +void Iot_FreeSerializerCborEncoder( void * ptr ); +/* @[declare_static_memory_freeserializercborencoder] */ + +/** + * @brief Allocates memory to hold data for a new [Serializer Cbor Parser] + * (@ref static_memory_mallocserializercborparser). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Serializer Cbor Parser. + * + * @param[in] size Requested size for the serializer cbor parser. + * + * @return Pointer to a Serializer Cbor Parser. If the size argument is larger than + * the fixed size of a Serializer Cbor Parser object or no free Serializer Cbor Parser + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocserializercborparser] */ +void * Iot_MallocSerializerCborParser( size_t size ); +/* @[declare_static_memory_mallocserializercborparser] */ + +/** + * @brief Frees an in-use [Serializer Cbor Parser] + * (@ref static_memory_freeserializercborparser). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Serializer Cbor Parser. + * + * @param[in] ptr Pointer to an active Serializer Cbor Parser to free. + */ +/* @[declare_static_memory_freeserializercborparser] */ +void Iot_FreeSerializerCborParser( void * ptr ); +/* @[declare_static_memory_freeserializercborparser] */ + +/** + * @brief Allocates memory to hold data for a new [Serializer Cbor Value] + * (@ref static_memory_mallocserializercborvalue). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Serializer Cbor Value. + * + * @param[in] size Requested size for the serializer cbor value. + * + * @return Pointer to a Serializer Cbor Parser. If the size argument is larger than + * the fixed size of a Serializer Cbor Value object or no free Serializer Cbor Value + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocserializercborvalue] */ +void * Iot_MallocSerializerCborValue( size_t size ); +/* @[declare_static_memory_mallocserializercborvalue] */ + +/** + * @brief Frees an in-use [Serializer Cbor Value] + * (@ref static_memory_freeserializercborvalue). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Serializer Cbor Value. + * + * @param[in] ptr Pointer to an active Serializer Cbor Value to free. + */ +/* @[declare_static_memory_freeserializercborvalue] */ +void Iot_FreeSerializerCborValue( void * ptr ); +/* @[declare_static_memory_freeserializercborvalue] */ + +/** + * @brief Allocates memory to hold data for a new [Serializer Decoder Object] + * (@ref static_memory_mallocserializerdecoderobject). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Serializer Decoder Object. + * + * @param[in] size Requested size for the serializer decoder object. + * + * @return Pointer to a Serializer Decoder Object. If the size argument is larger than + * the fixed size of a Serializer Decoder Object object or no free Serializer Decoder Object + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocserializerdecoderobject] */ +void * Iot_MallocSerializerDecoderObject( size_t size ); +/* @[declare_static_memory_mallocserializerdecoderobject] */ + +/** + * @brief Frees an in-use [Serializer Decoder Object] + * (@ref static_memory_freeserializerdecoderobject). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Serializer Decoder Object. + * + * @param[in] ptr Pointer to an active Serializer Decoder Object to free. + */ +/* @[declare_static_memory_freeserializerdecoderobject] */ +void Iot_FreeSerializerDecoderObject( void * ptr ); +/* @[declare_static_memory_freeserializerdecoderobject] */ + #endif /* if !defined( _IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 5507670b73..b3802a7987 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -7,7 +7,9 @@ add_library( iotcommon SHARED static_memory/iot_static_memory_common.c static_memory/iot_static_memory_taskpool.c static_memory/iot_static_memory_mqtt.c - static_memory/aws_iot_static_memory_shadow.c ) + static_memory/aws_iot_static_memory_shadow.c + static_memory/iot_static_memory_metrics.c + static_memory/iot_static_memory_serializer.c ) # Library version. set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) diff --git a/lib/source/common/static_memory/iot_static_memory_metrics.c b/lib/source/common/static_memory/iot_static_memory_metrics.c new file mode 100644 index 0000000000..b2c3ad7c8c --- /dev/null +++ b/lib/source/common/static_memory/iot_static_memory_metrics.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ + #include + #include + #include + +/* Static memory include. */ + #include "private/iot_static_memory.h" + +/* Metrics include. */ + #include "iot_metrics.h" + + #ifndef IOT_METRICS_TCP_CONNECTIONS + #define IOT_METRICS_TCP_CONNECTIONS ( 10 ) + #endif + +/* Validate static memory configuration settings. */ + #if IOT_METRICS_TCP_CONNECTIONS <= 0 + #error "IOT_METRICS_TCP_CONNECTIONS cannot be 0 or negative." + #endif + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in iot_static_memory_common.c + * Because these functions are specific to this static memory implementation, they are + * not placed in the common static memory header file. */ + extern int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); + extern void IotStaticMemory_ReturnInUse( void * ptr, + void * const pTcpConnection, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ + static bool _inUseTcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { 0 }; + static IotMetricsTcpConnection_t _tcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { { 0 } }; + +/*-----------------------------------------------------------*/ + + void * Iot_MallocMetricsTcpConnection( size_t size ) + { + int freeIndex = -1; + void * pNewTcpConnection = NULL; + + if( size == sizeof( IotMetricsTcpConnection_t ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseTcpConnections, + IOT_METRICS_TCP_CONNECTIONS ); + + if( freeIndex != -1 ) + { + pNewTcpConnection = &( _tcpConnections[ freeIndex ] ); + } + } + + return pNewTcpConnection; + } + +/*-----------------------------------------------------------*/ + + void Iot_FreeMetricsTcpConnection( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _tcpConnections, + _inUseTcpConnections, + IOT_METRICS_TCP_CONNECTIONS, + sizeof( IotMetricsTcpConnection_t ) ); + } + +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/common/static_memory/iot_static_memory_serializer.c b/lib/source/common/static_memory/iot_static_memory_serializer.c new file mode 100644 index 0000000000..7c131ce382 --- /dev/null +++ b/lib/source/common/static_memory/iot_static_memory_serializer.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ + #include + #include + #include + +/* Static memory include. */ + #include "private/iot_static_memory.h" + +/* Metrics include. */ + #include "iot_serializer.h" + + #include "cbor.h" + + #ifndef IOT_SERIALIZER_CBOR_ENCODERS + #define IOT_SERIALIZER_CBOR_ENCODERS ( 10 ) + #endif + + #ifndef IOT_SERIALIZER_CBOR_PARSERS + #define IOT_SERIALIZER_CBOR_PARSERS ( 10 ) + #endif + + #ifndef IOT_SERIALIZER_CBOR_VALUES + #define IOT_SERIALIZER_CBOR_VALUES ( 10 ) + #endif + + #ifndef IOT_SERIALIZER_DECODER_OBJECTS + #define IOT_SERIALIZER_DECODER_OBJECTS ( 10 ) + #endif + +/* Validate static memory configuration settings. */ + #if IOT_SERIALIZER_CBOR_ENCODERS <= 0 + #error "IOT_SERIALIZER_CBOR_ENCODERS cannot be 0 or negative." + #endif + + #if IOT_SERIALIZER_CBOR_PARSERS <= 0 + #error "IOT_SERIALIZER_CBOR_PARSERS cannot be 0 or negative." + #endif + + #if IOT_SERIALIZER_CBOR_VALUES <= 0 + #error "IOT_SERIALIZER_CBOR_VALUES cannot be 0 or negative." + #endif + + #if IOT_SERIALIZER_DECODER_OBJECTS <= 0 + #error "IOT_SERIALIZER_DECODER_OBJECTS cannot be 0 or negative." + #endif + + typedef struct _cborValueWrapper + { + CborValue cborValue; + bool isOutermost; + } _cborValueWrapper_t; + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in iot_static_memory_common.c + * Because these functions are specific to this static memory implementation, they are + * not placed in the common static memory header file. */ + extern int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); + extern void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ + static bool _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0 }; + static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { 0 } }; + + static bool _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0 }; + static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; + + static bool _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0 }; + static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { 0 } }; + + static bool _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0 }; + static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; + +/*-----------------------------------------------------------*/ + + void * Iot_MallocSerializerCborEncoder( size_t size ) + { + int freeIndex = -1; + void * pNewCborEncoder = NULL; + + if( size == sizeof( CborEncoder ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborEncoders, + IOT_SERIALIZER_CBOR_ENCODERS ); + + if( freeIndex != -1 ) + { + pNewCborEncoder = &( _cborEncoders[ freeIndex ] ); + } + } + + return pNewCborEncoder; + } + +/*-----------------------------------------------------------*/ + + void Iot_FreeSerializerCborEncoder( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _cborEncoders, + _inUseCborEncoders, + IOT_SERIALIZER_CBOR_ENCODERS, + sizeof( CborEncoder ) ); + } + +/*-----------------------------------------------------------*/ + + void * Iot_MallocSerializerCborParser( size_t size ) + { + int freeIndex = -1; + void * pNewCborParser = NULL; + + if( size == sizeof( CborParser ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborParsers, + IOT_SERIALIZER_CBOR_PARSERS ); + + if( freeIndex != -1 ) + { + pNewCborParser = &( _cborParsers[ freeIndex ] ); + } + } + + return pNewCborParser; + } + +/*-----------------------------------------------------------*/ + + void Iot_FreeSerializerCborParser( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _cborParsers, + _inUseCborParsers, + IOT_SERIALIZER_CBOR_PARSERS, + sizeof( CborParser ) ); + } + +/*-----------------------------------------------------------*/ + + void * Iot_MallocSerializerCborValue( size_t size ) + { + int freeIndex = -1; + void * pNewCborValue = NULL; + + if( size == sizeof( _cborValueWrapper_t ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborValues, + IOT_SERIALIZER_CBOR_VALUES ); + + if( freeIndex != -1 ) + { + pNewCborValue = &( _cborValues[ freeIndex ] ); + } + } + + return pNewCborValue; + } + +/*-----------------------------------------------------------*/ + + void Iot_FreeSerializerCborValue( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _cborValues, + _inUseCborValues, + IOT_SERIALIZER_CBOR_VALUES, + sizeof( _cborValueWrapper_t ) ); + } + +/*-----------------------------------------------------------*/ + + void * Iot_MallocSerializerDecoderObject( size_t size ) + { + int freeIndex = -1; + void * pNewDecoderObject = NULL; + + if( size == sizeof( IotSerializerDecoderObject_t ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborValues, + IOT_SERIALIZER_CBOR_VALUES ); + + if( freeIndex != -1 ) + { + pNewDecoderObject = &( _cborValues[ freeIndex ] ); + } + } + + return pNewDecoderObject; + } + +/*-----------------------------------------------------------*/ + + void Iot_FreeSerializerDecoderObject( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _cborValues, + _inUseCborValues, + IOT_SERIALIZER_CBOR_VALUES, + sizeof( IotSerializerDecoderObject_t ) ); + } + +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/metrics/CMakeLists.txt b/lib/source/metrics/CMakeLists.txt new file mode 100644 index 0000000000..5751e21c2f --- /dev/null +++ b/lib/source/metrics/CMakeLists.txt @@ -0,0 +1,9 @@ +# Serializer library source files. +add_library( iotmetrics SHARED + iot_metrics.c ) + +# Library version. +set_target_properties( iotmetrics PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Link required libraries. +target_link_libraries( iotmetrics iotplatform ) \ No newline at end of file diff --git a/lib/source/metrics/iot_metrics.c b/lib/source/metrics/iot_metrics.c new file mode 100644 index 0000000000..fa35e050c0 --- /dev/null +++ b/lib/source/metrics/iot_metrics.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Metrics include. */ +#include "iot_metrics.h" + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +static IotListDouble_t _connectionsList = IOT_LIST_DOUBLE_INITIALIZER; +static IotMutex_t _mutex; + +/* Compare function to identify the TCP connection id. */ +static bool _tcpConnectionMatch( const IotLink_t * pLink, + void * pId ); + +/*-----------------------------------------------------------*/ + +static bool _tcpConnectionMatch( const IotLink_t * pLink, + void * pId ) +{ + IotMetrics_Assert( pLink != NULL ); + IotMetrics_Assert( pId != NULL ); + + return *( ( uint32_t * ) pId ) == IotLink_Container( IotMetricsTcpConnection_t, pLink, link )->id; +} + +/*-----------------------------------------------------------*/ + +bool IotMetrics_Init() +{ + bool result = false; + + if( IotMutex_Create( &_mutex, false ) ) + { + IotListDouble_Create( &_connectionsList ); + result = true; + } + + return result; +} + +/*-----------------------------------------------------------*/ + +void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ) +{ + IotMetrics_Assert( pTcpConnection != NULL ); + + IotMutex_Lock( &_mutex ); + + /* Only add if it doesn't exist in the connectionsList. */ + if( IotListDouble_FindFirstMatch( &_connectionsList, + NULL, + _tcpConnectionMatch, + &pTcpConnection->id ) == NULL ) + { + IotMetricsTcpConnection_t * pNewTcpConnection = IotMetrics_MallocTcpConnection( sizeof( IotMetricsTcpConnection_t ) ); + + if( pNewTcpConnection != NULL ) + { + /* Copy TCP connection to the new one. */ + *pNewTcpConnection = *pTcpConnection; + + /* Insert to the list. */ + IotListDouble_InsertTail( &_connectionsList, &( pNewTcpConnection->link ) ); + } + } + + IotMutex_Unlock( &_mutex ); +} + +/*-----------------------------------------------------------*/ + +void IotMetrics_RemoveTcpConnection( uint32_t tcpConnectionId ) +{ + IotMutex_Lock( &_mutex ); + + IotLink_t * pFoundConnectionLink = IotListDouble_RemoveFirstMatch( &_connectionsList, + NULL, + _tcpConnectionMatch, + &tcpConnectionId ); + + if( pFoundConnectionLink != NULL ) + { + IotMetrics_FreeTcpConnection( IotLink_Container( IotMetricsTcpConnection_t, pFoundConnectionLink, link ) ); + } + + IotMutex_Unlock( &_mutex ); +} + +/*-----------------------------------------------------------*/ + +void IotMetrics_ProcessTcpConnections( IotMetricsListCallback_t tcpConnectionsCallback ) +{ + /* If no callback function is provided, simply return. */ + if( tcpConnectionsCallback.function == NULL ) + { + return; + } + + IotMutex_Lock( &_mutex ); + + /* Execute the callback function. */ + tcpConnectionsCallback.function( tcpConnectionsCallback.param1, &_connectionsList ); + + IotMutex_Unlock( &_mutex ); +} diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt new file mode 100644 index 0000000000..0ad7d1d176 --- /dev/null +++ b/lib/source/serializer/CMakeLists.txt @@ -0,0 +1,15 @@ +# Serializer library source files. +add_library( iotserializer SHARED + cbor/iot_serializer_tinycbor_decoder.c + cbor/iot_serializer_tinycbor_encoder.c ) + +# Library version. +set_target_properties( iotserializer PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Link required libraries. +target_link_libraries( iotserializer tinycbor ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotserializer unity ) +endif() \ No newline at end of file diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c new file mode 100644 index 0000000000..b834f29a47 --- /dev/null +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c @@ -0,0 +1,459 @@ +/* + * Amazon FreeRTOS System Initialization + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file aws_iot_serializer_tinycbor_decoder.c + * @brief Implements APIs to parse and decode data from CBOR format. The file relies on tiny + * CBOR library to parse and decode data from CBOR format. Supports all major data types within + * the CBOR format. + * The file implements the decoder interface in aws_iot_serialize.h. + */ + +#include "iot_serializer.h" +#include "cbor.h" + +#define _castDecoderObjectToCborValue( pDecoderObject ) ( ( pDecoderObject )->pHandle ) + +#define _castDecoderIteratorToCborValue( iterator ) ( ( ( IotSerializerDecoderObject_t * ) iterator )->pHandle ) + +#define _isArrayOrMap( dataType ) ( ( ( dataType ) == IOT_SERIALIZER_CONTAINER_ARRAY ) || ( ( dataType ) == IOT_SERIALIZER_CONTAINER_MAP ) ) + +static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject, + const uint8_t * pDataBuffer, + size_t maxSize ); +static IotSerializerError_t _get( IotSerializerDecoderIterator_t iterator, + IotSerializerDecoderObject_t * pValueObject ); + +static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject, + const char * pKey, + IotSerializerDecoderObject_t * pValueObject ); +static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObject, + IotSerializerDecoderIterator_t * pIterator ); +static IotSerializerError_t _stepOut( IotSerializerDecoderIterator_t iterator, + IotSerializerDecoderObject_t * pDecoderObject ); + +static IotSerializerError_t _next( IotSerializerDecoderIterator_t iterator ); + +static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ); + +static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ); + +IotSerializerDecodeInterface_t _IotSerializerCborDecoder = +{ + .init = _init, + .get = _get, + .find = _find, + .stepIn = _stepIn, + .stepOut = _stepOut, + .next = _next, + .isEndOfContainer = _isEndOfContainer, + .destroy = _destroy +}; + +/* Wrapper CborValue with additional fields. */ +typedef struct _cborValueWrapper +{ + CborValue cborValue; + bool isOutermost; +} _cborValueWrapper_t; + +/*-----------------------------------------------------------*/ + +static void _translateErrorCode( CborError cborError, + IotSerializerError_t * pSerializerError ) +{ + /* TODO: assert cborError == 0 || *pSerializerError == 0 */ + + /* Only translate if there is no error on serizlier currently. */ + if( *pSerializerError == IOT_SERIALIZER_SUCCESS ) + { + switch( cborError ) + { + case CborNoError: + *pSerializerError = IOT_SERIALIZER_SUCCESS; + break; + + case CborErrorOutOfMemory: + *pSerializerError = IOT_SERIALIZER_BUFFER_TOO_SMALL; + break; + + default: + *pSerializerError = IOT_SERIALIZER_INTERNAL_FAILURE; + } + } +} + +/*-----------------------------------------------------------*/ + +static IotSerializerDataType_t _toSerializerType( CborType type ) +{ + switch( type ) + { + case CborIntegerType: + + return IOT_SERIALIZER_SCALAR_SIGNED_INT; + + case CborBooleanType: + + return IOT_SERIALIZER_SCALAR_BOOL; + + case CborByteStringType: + + return IOT_SERIALIZER_SCALAR_BYTE_STRING; + + case CborTextStringType: + + return IOT_SERIALIZER_SCALAR_TEXT_STRING; + + case CborArrayType: + + return IOT_SERIALIZER_CONTAINER_ARRAY; + + case CborMapType: + + return IOT_SERIALIZER_CONTAINER_MAP; + + default: + + return IOT_SERIALIZER_UNDEFINED; + } +} + +/*-----------------------------------------------------------*/ + +/* Construct DecoderObject based on the wrapper of CborValue. */ +static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborValueWrapper, + IotSerializerDecoderObject_t * pDecoderObject ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + CborValue * pCborValue = &pCborValueWrapper->cborValue; + CborValue next = { 0 }; + + /* Get serializer data type from CborValue. */ + IotSerializerDataType_t dataType = _toSerializerType( cbor_value_get_type( pCborValue ) ); + + if( dataType == IOT_SERIALIZER_UNDEFINED ) + { + return IOT_SERIALIZER_UNDEFINED_TYPE; + } + else + { + /* Set type to decoder object as long as it is defined. */ + pDecoderObject->type = dataType; + } + + /* If this is a map or array. */ + if( _isArrayOrMap( dataType ) ) + { + /* Save to decoder object's handle. */ + pDecoderObject->pHandle = ( void * ) pCborValueWrapper; + } + else /* Create scalar object. */ + { + switch( dataType ) + { + case IOT_SERIALIZER_SCALAR_SIGNED_INT: + { + int i = 0; + cborError = cbor_value_get_int( pCborValue, &i ); + + /* TODO: assert no error on _createDecoderObject */ + + pDecoderObject->value.signedInt = i; + + break; + } + + case IOT_SERIALIZER_SCALAR_BYTE_STRING: + case IOT_SERIALIZER_SCALAR_TEXT_STRING: + + if( dataType == IOT_SERIALIZER_SCALAR_BYTE_STRING ) + { + cborError = cbor_value_copy_byte_string( + pCborValue, + pDecoderObject->value.pString, + &( pDecoderObject->value.stringLength ), + &next ); + } + else + { + cborError = cbor_value_copy_text_string( + pCborValue, + ( char * ) pDecoderObject->value.pString, + &( pDecoderObject->value.stringLength ), + &next ); + } + + if( cborError != CborNoError ) + { + if( ( cborError == CborErrorOutOfMemory ) && + ( pDecoderObject->value.pString == NULL ) && + ( cbor_value_is_length_known( pCborValue ) ) ) + + { + /* + * If its a finite length text/byte string, and user have passed a null length buffer, + * we avoid copying the string by storing pointer to the start of the string. + */ + pDecoderObject->value.pString = ( uint8_t * ) ( cbor_value_get_next_byte( &next ) - ( pDecoderObject->value.stringLength ) ); + } + else + { + returnedError = IOT_SERIALIZER_INTERNAL_FAILURE; + } + } + + break; + + default: + /* Other scalar types are not supported. */ + returnedError = IOT_SERIALIZER_NOT_SUPPORTED; + } + } + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject, + const uint8_t * pDataBuffer, + size_t maxSize ) +{ + CborParser * pCborParser = IotSerializer_MallocCborParser( sizeof( CborParser ) ); + _cborValueWrapper_t * pCborValueWrapper = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + + if( ( pCborParser == NULL ) || ( pCborValueWrapper == NULL ) ) + { + IotSerializer_FreeCborParser( pCborParser ); + IotSerializer_FreeCborValue( pCborValueWrapper ); + + return IOT_SERIALIZER_OUT_OF_MEMORY; + } + + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + cborError = cbor_parser_init( + pDataBuffer, + maxSize, + 0, + pCborParser, + &pCborValueWrapper->cborValue ); + + /* If init succeeds, create the decoder object. */ + if( cborError == CborNoError ) + { + pCborValueWrapper->isOutermost = true; + + returnedError = _createDecoderObject( pCborValueWrapper, pDecoderObject ); + } + + /* If there is any error or decoder object is a scalar type, free the cbor resources. */ + if( cborError || returnedError || !_isArrayOrMap( pDecoderObject->type ) ) + { + /* pDecoderObject is untouched. */ + + IotSerializer_FreeCborParser( pCborParser ); + IotSerializer_FreeCborValue( pCborValueWrapper ); + } + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ) +{ + /* If it is not an container, no action. */ + if( !IotSerializer_IsContainer( pDecoderObject ) ) + { + return; + } + + _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); + + /* If this is outmost object, the parser's memory needs to be freed. */ + if( pCborValueWrapper->isOutermost ) + { + IotSerializer_FreeCborParser( ( void * ) ( pCborValueWrapper->cborValue.parser ) ); + } + + IotSerializer_FreeCborValue( pCborValueWrapper ); + + /* Reset decoder object's handle to NULL. */ + pDecoderObject->pHandle = NULL; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _get( IotSerializerDecoderIterator_t iterator, + IotSerializerDecoderObject_t * pValueObject ) +{ + _cborValueWrapper_t * pCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); + + return _createDecoderObject( pCborValueWrapper, pValueObject ); +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject, + const char * pKey, + IotSerializerDecoderObject_t * pValueObject ) +{ + if( pDecoderObject->type != IOT_SERIALIZER_CONTAINER_MAP ) + { + return IOT_SERIALIZER_INVALID_INPUT; + } + + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); + + _cborValueWrapper_t * pNewCborValueWrapper = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + + if( pNewCborValueWrapper == NULL ) + { + return IOT_SERIALIZER_OUT_OF_MEMORY; + } + + /* Set this object not to be outermost one. */ + pNewCborValueWrapper->isOutermost = false; + + cborError = cbor_value_map_find_value( + &pCborValueWrapper->cborValue, + pKey, + &pNewCborValueWrapper->cborValue ); + + if( cborError == CborNoError ) + { + /* If not found the element in map. */ + if( pNewCborValueWrapper->cborValue.type == CborInvalidType ) + { + /* pValueObject is untouched. */ + + returnedError = IOT_SERIALIZER_NOT_FOUND; + } + else + { + returnedError = _createDecoderObject( pNewCborValueWrapper, pValueObject ); + } + } + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObject, + IotSerializerDecoderIterator_t * pIterator ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); + _cborValueWrapper_t * pNewCborValueWrapper = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + + cborError = cbor_value_enter_container( + &pCborValueWrapper->cborValue, + &pNewCborValueWrapper->cborValue ); + + if( cborError == CborNoError ) + { + IotSerializerDecoderObject_t * pNewObject = IotSerializer_MallocDecoderObject( sizeof( IotSerializerDecoderObject_t ) ); + + pNewObject->type = pDecoderObject->type; + + pNewCborValueWrapper->isOutermost = false; + pNewObject->pHandle = ( void * ) pNewCborValueWrapper; + + *pIterator = ( IotSerializerDecoderIterator_t ) pNewObject; + + returnedError = IOT_SERIALIZER_SUCCESS; + } + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _stepOut( IotSerializerDecoderIterator_t iterator, + IotSerializerDecoderObject_t * pDecoderObject ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + _cborValueWrapper_t * pOuterCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); + _cborValueWrapper_t * pInnerCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); + + cborError = cbor_value_leave_container( + &pOuterCborValueWrapper->cborValue, + &pInnerCborValueWrapper->cborValue ); + + if( cborError == CborNoError ) + { + IotSerializer_FreeCborValue( pInnerCborValueWrapper ); + IotSerializer_FreeDecoderObject( iterator ); + } + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _next( IotSerializerDecoderIterator_t iterator ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + _cborValueWrapper_t * pCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); + + cborError = cbor_value_advance( &pCborValueWrapper->cborValue ); + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ) +{ + _cborValueWrapper_t * pCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); + + return cbor_value_at_end( &pCborValueWrapper->cborValue ); +} diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c new file mode 100644 index 0000000000..9e9872ec0e --- /dev/null +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c @@ -0,0 +1,320 @@ +/* + * Amazon FreeRTOS System Initialization + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file aws_iot_serializer_tinycbor_encoder.c + * @brief Implements APIs to serialize data in CBOR format. The file relies on tiny + * CBOR library to serialize data into CBOR format. Supports all major data types within + * the CBOR format. + * The file implements the encoder interface in aws_iot_serialize.h. + */ + +#include "iot_serializer.h" +#include "cbor.h" + +/* Translate cbor error to serializer error. */ +static void _translateErrorCode( CborError cborError, + IotSerializerError_t * pSerializerError ); + +static size_t _getEncodedSize( IotSerializerEncoderObject_t * pEncoderObject, + uint8_t * pDataBuffer ); +static size_t _getExtraBufferSizeNeeded( IotSerializerEncoderObject_t * pEncoderObject ); +static IotSerializerError_t _init( IotSerializerEncoderObject_t * pEncoderObject, + uint8_t * pDataBuffer, + size_t maxSize ); +static void _destroy( IotSerializerEncoderObject_t * pEncoderObject ); +static IotSerializerError_t _openContainer( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerEncoderObject_t * pNewEncoderObject, + size_t length ); + +static IotSerializerError_t _openContainerWithKey( IotSerializerEncoderObject_t * pEncoderObject, + const char * pKey, + IotSerializerEncoderObject_t * pNewEncoderObject, + size_t length ); +static IotSerializerError_t _closeContainer( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerEncoderObject_t * pNewEncoderObject ); +static IotSerializerError_t _append( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerScalarData_t scalarData ); +static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEncoderObject, + const char * pKey, + IotSerializerScalarData_t scalarData ); + + +IotSerializerEncodeInterface_t _IotSerializerCborEncoder = +{ + .getEncodedSize = _getEncodedSize, + .getExtraBufferSizeNeeded = _getExtraBufferSizeNeeded, + .init = _init, + .destroy = _destroy, + .openContainer = _openContainer, + .openContainerWithKey = _openContainerWithKey, + .closeContainer = _closeContainer, + .append = _append, + .appendKeyValue = _appendKeyValue, +}; + +/*-----------------------------------------------------------*/ + +static void _translateErrorCode( CborError cborError, + IotSerializerError_t * pSerializerError ) +{ + /* This function translate cbor error to serializer error. + * It doesn't make sense that both of them are of error (greater than 0). + */ + IotSerializer_Assert( cborError == 0 || *pSerializerError == 0 ); + + /* Only translate if there is no error on serizlier currently. */ + if( *pSerializerError == IOT_SERIALIZER_SUCCESS ) + { + switch( cborError ) + { + case CborNoError: + *pSerializerError = IOT_SERIALIZER_SUCCESS; + break; + + case CborErrorOutOfMemory: + *pSerializerError = IOT_SERIALIZER_BUFFER_TOO_SMALL; + break; + + default: + *pSerializerError = IOT_SERIALIZER_INTERNAL_FAILURE; + } + } +} + +/*-----------------------------------------------------------*/ + +static size_t _getEncodedSize( IotSerializerEncoderObject_t * pEncoderObject, + uint8_t * pDataBuffer ) +{ + CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; + + return cbor_encoder_get_buffer_size( pCborEncoder, pDataBuffer ); +} + +/*-----------------------------------------------------------*/ + +static size_t _getExtraBufferSizeNeeded( IotSerializerEncoderObject_t * pEncoderObject ) +{ + CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; + + return cbor_encoder_get_extra_bytes_needed( pCborEncoder ); +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _init( IotSerializerEncoderObject_t * pEncoderObject, + uint8_t * pDataBuffer, + size_t maxSize ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + + /* Unused flags for tinycbor init. */ + int unusedCborFlags = 0; + + CborEncoder * pCborEncoder = IotSerializer_MallocCborEncoder( sizeof( CborEncoder ) ); + + if( pCborEncoder != NULL ) + { + /* Store the CborEncoder pointer to handle. */ + pEncoderObject->pHandle = pCborEncoder; + + /* Always set outmost type to IOT_SERIALIZER_CONTAINER_STREAM. */ + pEncoderObject->type = IOT_SERIALIZER_CONTAINER_STREAM; + + /* Perfomr the tinycbor init. */ + cbor_encoder_init( pCborEncoder, pDataBuffer, maxSize, unusedCborFlags ); + } + else + { + /* pEncoderObject is untouched. */ + + returnedError = IOT_SERIALIZER_OUT_OF_MEMORY; + } + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static void _destroy( IotSerializerEncoderObject_t * pEncoderObject ) +{ + CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; + + /* Free the memorry allocated in init function. */ + IotSerializer_FreeCborEncoder( pCborEncoder ); + + /* Reset pHandle to be NULL. */ + pEncoderObject->pHandle = NULL; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _openContainer( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerEncoderObject_t * pNewEncoderObject, + size_t length ) +{ + /* New object must be a container of map or array. */ + if( ( pNewEncoderObject->type != IOT_SERIALIZER_CONTAINER_ARRAY ) && + ( pNewEncoderObject->type != IOT_SERIALIZER_CONTAINER_MAP ) ) + { + return IOT_SERIALIZER_INVALID_INPUT; + } + + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + CborEncoder * pOuterEncoder = ( CborEncoder * ) pEncoderObject->pHandle; + CborEncoder * pInnerEncoder = IotSerializer_MallocCborEncoder( sizeof( CborEncoder ) ); + + if( pInnerEncoder != NULL ) + { + /* Store the CborEncoder pointer to handle. */ + pNewEncoderObject->pHandle = pInnerEncoder; + + switch( pNewEncoderObject->type ) + { + case IOT_SERIALIZER_CONTAINER_MAP: + cborError = cbor_encoder_create_map( pOuterEncoder, pInnerEncoder, length ); + break; + + case IOT_SERIALIZER_CONTAINER_ARRAY: + cborError = cbor_encoder_create_array( pOuterEncoder, pInnerEncoder, length ); + break; + + default: + IotSerializer_Assert( 0 ); + } + } + else + { + /* pEncoderObject is untouched. */ + returnedError = IOT_SERIALIZER_OUT_OF_MEMORY; + } + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _openContainerWithKey( IotSerializerEncoderObject_t * pEncoderObject, + const char * pKey, + IotSerializerEncoderObject_t * pNewEncoderObject, + size_t length ) +{ + IotSerializerScalarData_t keyScalarData = IotSerializer_ScalarTextString( pKey ); + + IotSerializerError_t returnedError = _append( pEncoderObject, keyScalarData ); + + /* Buffer too small is a special error case that serialization should continue. */ + if( ( returnedError == IOT_SERIALIZER_SUCCESS ) || ( returnedError == IOT_SERIALIZER_BUFFER_TOO_SMALL ) ) + { + returnedError = _openContainer( pEncoderObject, pNewEncoderObject, length ); + } + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _closeContainer( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerEncoderObject_t * pNewEncoderObject ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + CborEncoder * pOuterEncoder = ( CborEncoder * ) pEncoderObject->pHandle; + CborEncoder * pInnerEncoder = ( CborEncoder * ) pNewEncoderObject->pHandle; + + cborError = cbor_encoder_close_container( pOuterEncoder, pInnerEncoder ); + + /* Free inner encoder's memory regardless the result of "close container". */ + IotSerializer_FreeCborEncoder( pInnerEncoder ); + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _append( IotSerializerEncoderObject_t * pEncoderObject, + IotSerializerScalarData_t scalarData ) +{ + IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; + CborError cborError = CborNoError; + + CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; + + switch( scalarData.type ) + { + case IOT_SERIALIZER_SCALAR_SIGNED_INT: + cborError = cbor_encode_int( pCborEncoder, scalarData.value.signedInt ); + break; + + case IOT_SERIALIZER_SCALAR_TEXT_STRING: + cborError = cbor_encode_text_string( pCborEncoder, ( char * ) scalarData.value.pString, scalarData.value.stringLength ); + break; + + case IOT_SERIALIZER_SCALAR_BYTE_STRING: + cborError = cbor_encode_byte_string( pCborEncoder, scalarData.value.pString, scalarData.value.stringLength ); + break; + + case IOT_SERIALIZER_SCALAR_BOOL: + cborError = cbor_encode_boolean( pCborEncoder, scalarData.value.booleanValue ); + break; + + case IOT_SERIALIZER_SCALAR_NULL: + cborError = cbor_encode_null( pCborEncoder ); + break; + + default: + returnedError = IOT_SERIALIZER_UNDEFINED_TYPE; + } + + _translateErrorCode( cborError, &returnedError ); + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEncoderObject, + const char * pKey, + IotSerializerScalarData_t scalarData ) +{ + IotSerializerScalarData_t keyScalarData = IotSerializer_ScalarTextString( pKey ); + IotSerializerError_t returnedError = _append( pEncoderObject, keyScalarData ); + + /* Buffer too small is a special error case that serialization should continue. */ + if( ( returnedError == IOT_SERIALIZER_SUCCESS ) || ( returnedError == IOT_SERIALIZER_BUFFER_TOO_SMALL ) ) + { + returnedError = _append( pEncoderObject, scalarData ); + } + + return returnedError; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 036da7a3f1..6cef9e0ecc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,3 +11,6 @@ add_subdirectory( mqtt ) # Shadow tests. add_subdirectory( shadow ) + +# Serializer tests. +add_subdirectory( serializer ) diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 40cfd57dfd..f0bdd90e33 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -78,6 +78,16 @@ #define _EMPTY_ELSE_MARKER asm volatile ( "nop" ) #endif +/* Metrics library configuration. */ +#define IOT_METRICS_ENABLE_ASSERTS ( 1 ) + +/* Serializer library configuration. */ +#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) + +/* Defender library configuration. */ +#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) + /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ #define IotNetwork_Malloc unity_malloc_mt @@ -121,6 +131,20 @@ #define AwsIotShadow_FreeString unity_free_mt #define AwsIotShadow_MallocSubscription unity_malloc_mt #define AwsIotShadow_FreeSubscription unity_free_mt + #define IotMetrics_MallocTcpConnection unity_malloc_mt + #define IotMetrics_FreeTcpConnection unity_free_mt + #define IotSerializer_MallocCborEncoder unity_malloc_mt + #define IotSerializer_FreeCborEncoder unity_free_mt + #define IotSerializer_MallocCborParser unity_malloc_mt + #define IotSerializer_FreeCborParser unity_free_mt + #define IotSerializer_MallocCborValue unity_malloc_mt + #define IotSerializer_FreeCborValue unity_free_mt + #define IotSerializer_MallocDecoderObject unity_malloc_mt + #define IotSerializer_FreeDecoderObject unity_free_mt + #define AwsIotDefender_MallocReport unity_malloc_mt + #define AwsIotDefender_FreeReport unity_free_mt + #define AwsIotDefender_MallocTopic unity_malloc_mt + #define AwsIotDefender_FreeTopic unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* Network header to include in the tests. */ diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt new file mode 100644 index 0000000000..50b1126602 --- /dev/null +++ b/tests/serializer/CMakeLists.txt @@ -0,0 +1,7 @@ +# Shadow tests executable. +add_executable( iot_tests_serializer + iot_tests_serializer.c + iot_test_serializer_cbor.c ) + +# Shadow tests library dependencies. +target_link_libraries( iot_tests_serializer iotcommon iotplatform iotserializer unity ) \ No newline at end of file diff --git a/tests/serializer/iot_test_serializer_cbor.c b/tests/serializer/iot_test_serializer_cbor.c new file mode 100644 index 0000000000..0410c01abd --- /dev/null +++ b/tests/serializer/iot_test_serializer_cbor.c @@ -0,0 +1,381 @@ +/* + * Amazon FreeRTOS Serializer Test + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* Unity framework includes. */ +#include "unity_fixture.h" +#include "unity.h" + +/* Serializer and CBOR includes. */ +#include "iot_serializer.h" +#include "cbor.h" + +#define _encoder _IotSerializerCborEncoder +#define _decoder _IotSerializerCborDecoder + +#define _BUFFER_SIZE 100 + +static IotSerializerEncoderObject_t _encoderObject; + +uint8_t _buffer[ _BUFFER_SIZE ]; + +TEST_GROUP( Full_Serializer_CBOR ); + +TEST_SETUP( Full_Serializer_CBOR ) +{ + /* Reset buffer to zero. */ + memset( _buffer, 0, _BUFFER_SIZE ); + + /* Init encoder object with buffer. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.init( &_encoderObject, _buffer, _BUFFER_SIZE ) ); +} + +TEST_TEAR_DOWN( Full_Serializer_CBOR ) +{ + /* Destroy encoder object. */ + _encoder.destroy( &_encoderObject ); + + TEST_ASSERT_NULL( _encoderObject.pHandle ); +} + +/* TODO: + * - append NULL + * - append bool + */ +TEST_GROUP_RUNNER( Full_Serializer_CBOR ) +{ + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_init_with_null_buffer ); + + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_append_integer ); + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_append_text_string ); + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_append_byte_string ); + + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_open_a_scalar ); + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_open_map ); + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_open_array ); + + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_map_nest_map ); + RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_map_nest_array ); +} + +TEST( Full_Serializer_CBOR, Encoder_init_with_null_buffer ) +{ + IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; + IotSerializerEncoderObject_t encoderObject = { 0 }; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.init( &encoderObject, NULL, 0 ) ); + + /* Set the type to stream. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_STREAM, encoderObject.type ); + + /* Assigned value to handle pointer. */ + TEST_ASSERT_NOT_NULL( encoderObject.pHandle ); + + /* Append an integer. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_BUFFER_TOO_SMALL, _encoder.append( &encoderObject, IotSerializer_ScalarSignedInt( 1 ) ) ); + + /* Needed buffer size is 1 to encode integer "1". */ + TEST_ASSERT_EQUAL( 1, _encoder.getExtraBufferSizeNeeded( &encoderObject ) ); + + _encoder.destroy( &encoderObject ); + + TEST_ASSERT_NULL( encoderObject.pHandle ); +} + +TEST( Full_Serializer_CBOR, Encoder_append_integer ) +{ + int64_t value = 6; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.append( &_encoderObject, IotSerializer_ScalarSignedInt( value ) ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermostValue; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &outermostValue ) ); + + int64_t result = 0; + TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &outermostValue, &result ) ); + + TEST_ASSERT_EQUAL( value, result ); + + /* Encoded size is 1. */ + TEST_ASSERT_EQUAL( 1, _encoder.getEncodedSize( &_encoderObject, _buffer ) ); +} + +TEST( Full_Serializer_CBOR, Encoder_append_text_string ) +{ + char * str = "hello world"; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.append( &_encoderObject, IotSerializer_ScalarTextString( str ) ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermostValue; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &outermostValue ) ); + + bool equal = false; + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_text_string_equals( &outermostValue, str, &equal ) ); + + TEST_ASSERT_TRUE( equal ); +} + +TEST( Full_Serializer_CBOR, Encoder_append_byte_string ) +{ + uint8_t inputBytes[] = "hello world"; + size_t inputLength = strlen( inputBytes ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.append( &_encoderObject, IotSerializer_ScalarByteString( inputBytes, inputLength ) ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermostValue; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborByteStringType, cbor_value_get_type( &outermostValue ) ); + + uint8_t outputBytes[ 20 ]; + size_t outputLength = 20; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_copy_byte_string( &outermostValue, outputBytes, &outputLength, NULL ) ); + + TEST_ASSERT_EQUAL( inputLength, outputLength ); + + TEST_ASSERT_EQUAL( 0, strcmp( inputBytes, outputBytes ) ); +} + +TEST( Full_Serializer_CBOR, Encoder_open_a_scalar ) +{ + IotSerializerEncoderObject_t integerObject = { .pHandle = NULL, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT }; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_INVALID_INPUT, + _encoder.openContainer( &_encoderObject, &integerObject, 1 ) ); +} + +TEST( Full_Serializer_CBOR, Encoder_open_map ) +{ + IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.openContainer( &_encoderObject, &mapObject, 1 ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.appendKeyValue( &mapObject, "key", IotSerializer_ScalarTextString( "value" ) ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.closeContainer( &_encoderObject, &mapObject ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermostValue, value; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_map_find_value( &outermostValue, "key", &value ) ); + + TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &value ) ); + + bool equal = false; + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_text_string_equals( &value, "value", &equal ) ); + + TEST_ASSERT_TRUE( equal ); +} + +TEST( Full_Serializer_CBOR, Encoder_open_array ) +{ + IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; + + int64_t numberArray[] = { 3, 2, 1 }; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.openContainer( &_encoderObject, &arrayObject, 3 ) ); + + for( uint8_t i = 0; i < 3; i++ ) + { + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); + } + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.closeContainer( &_encoderObject, &arrayObject ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermostValue, arrayValue; + int64_t number = 0; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborArrayType, cbor_value_get_type( &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_enter_container( &outermostValue, &arrayValue ) ); + + for( uint8_t i = 0; i < 3; i++ ) + { + TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &arrayValue ) ); + TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &arrayValue, &number ) ); + TEST_ASSERT_EQUAL( numberArray[ i ], number ); + + TEST_ASSERT_EQUAL( CborNoError, cbor_value_advance( &arrayValue ) ); + } + + TEST_ASSERT_TRUE( cbor_value_at_end( &arrayValue ) ); +} + +TEST( Full_Serializer_CBOR, Encoder_map_nest_map ) +{ + IotSerializerEncoderObject_t mapObject_1 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t mapObject_2 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.openContainer( &_encoderObject, &mapObject_1, 1 ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.openContainerWithKey( &mapObject_1, "map1", &mapObject_2, 1 ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.appendKeyValue( &mapObject_2, "key", IotSerializer_ScalarTextString( "value" ) ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.closeContainer( &mapObject_1, &mapObject_2 ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.closeContainer( &_encoderObject, &mapObject_1 ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermostValue, map1, str; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermostValue ) ); + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_map_find_value( &outermostValue, "map1", &map1 ) ); + + TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &map1 ) ); + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_map_find_value( &map1, "key", &str ) ); + + TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &str ) ); + + bool equal = false; + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_text_string_equals( &str, "value", &equal ) ); + + TEST_ASSERT_TRUE( equal ); +} + +TEST( Full_Serializer_CBOR, Encoder_map_nest_array ) +{ + IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; + + int64_t numberArray[] = { 3, 2, 1 }; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.openContainer( &_encoderObject, &mapObject, 1 ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.openContainerWithKey( &mapObject, "array", &arrayObject, 3 ) ); + + for( uint8_t i = 0; i < 3; i++ ) + { + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); + } + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.closeContainer( &mapObject, &arrayObject ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _encoder.closeContainer( &_encoderObject, &mapObject ) ); + + /* --- Verification --- */ + + CborParser parser; + CborValue outermost, array, arrayElement; + int64_t number = 0; + + TEST_ASSERT_EQUAL( CborNoError, + cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermost ) ); + + TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermost ) ); + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_map_find_value( &outermost, "array", &array ) ); + + TEST_ASSERT_EQUAL( CborArrayType, cbor_value_get_type( &array ) ); + + TEST_ASSERT_EQUAL( CborNoError, + cbor_value_enter_container( &array, &arrayElement ) ); + + for( uint8_t i = 0; i < 3; i++ ) + { + TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &arrayElement ) ); + TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &arrayElement, &number ) ); + TEST_ASSERT_EQUAL( numberArray[ i ], number ); + + TEST_ASSERT_EQUAL( CborNoError, cbor_value_advance( &arrayElement ) ); + } + + TEST_ASSERT_TRUE( cbor_value_at_end( &arrayElement ) ); +} diff --git a/tests/serializer/iot_tests_serializer.c b/tests/serializer/iot_tests_serializer.c new file mode 100644 index 0000000000..5328839e36 --- /dev/null +++ b/tests/serializer/iot_tests_serializer.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Common include. */ +#include "iot_common.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler. Terminates the tests if called. + */ +static void _signalHandler( int signum ) +{ + /* Immediately terminate the tests if this signal handler is called. */ + if( signum == SIGSEGV ) + { + printf( "\nSegmentation fault.\n" ); + exit( EXIT_FAILURE ); + } + else if( signum == SIGABRT ) + { + printf( "\nAssertion failed.\n" ); + exit( EXIT_FAILURE ); + } +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + struct sigaction signalAction; + + /* Set a signal handler for segmentation faults and assertion failures. */ + ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); + signalAction.sa_handler = _signalHandler; + + if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) + { + return EXIT_FAILURE; + } + + if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + { + return EXIT_FAILURE; + } + + /* Initialize the common libraries before running the tests. */ + if( IotCommon_Init() == false ) + { + return EXIT_FAILURE; + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Run short tests. */ + RUN_TEST_GROUP( Full_Serializer_CBOR ); + + /* Clean up common libraries. */ + IotCommon_Cleanup(); + + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/*-----------------------------------------------------------*/ From 8b840736fde4a7a9a8fa09d91449a785467dcb21 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Mon, 25 Feb 2019 17:09:42 -0800 Subject: [PATCH 033/844] call metrics functions in openssl network implementation (#291) --- lib/source/common/CMakeLists.txt | 2 +- lib/source/common/iot_common.c | 8 +++++++ platform/include/posix/iot_network_openssl.h | 3 +++ .../posix/network/iot_network_openssl.c | 22 ++++++++++++++++--- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index b3802a7987..d3318452f0 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -15,7 +15,7 @@ add_library( iotcommon SHARED set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( iotcommon iotplatform ) +target_link_libraries( iotcommon iotplatform iotmetrics ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c index dbb506cc48..34b439ac17 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_common.c @@ -30,6 +30,9 @@ /* Task pool include. */ #include "iot_taskpool.h" +/* Metrics include. */ +#include "iot_metrics.h" + /* Static memory include (if dynamic memory allocation is disabled). */ #include "private/iot_static_memory.h" @@ -62,6 +65,11 @@ bool IotCommon_Init( void ) } } + if (status == true) + { + status = IotMetrics_Init(); + } + /* Initialize static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 status = IotStaticMemory_Init(); diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 1407f2e0cd..058a2eaeb6 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -40,6 +40,9 @@ #include #endif +/* Standard bool include. */ +#include + /* OpenSSL types include. */ #include diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 353c29acb5..ab351b5b0f 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -58,6 +58,9 @@ /* Platform threads include. */ #include "platform/iot_threads.h" +/* Metrics include. */ +#include "iot_metrics.h" + /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_NETWORK #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK @@ -290,10 +293,11 @@ static void * _networkReceiveThread( void * pArgument ) * @brief Perform a DNS lookup of a host name and establish a TCP connection. * * @param[in] pServerInfo Server host name and port. + * @param[out] pIpAddress Ip address number in network byte order. * * @return A connected TCP socket number; `-1` if the DNS lookup failed. */ -static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServerInfo ) +static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServerInfo, uint32_t * pIpAddress ) { int status = 0, tcpSocket = -1; const uint16_t netPort = htons( pServerInfo->port ); @@ -351,6 +355,9 @@ static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServe } else { + /* Copy IP address to the output parameter. */ + *pIpAddress = pServer->sin_addr.s_addr; + /* Connection successful; stop searching the list. */ IotLogDebug( "Socket connection successful." ); break; @@ -832,6 +839,8 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials = pCredentialInfo; IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; + IotMetricsTcpConnection_t connection = { .remotePort = pServerInfo->port }; + /* Check output parameter. */ if( pNetworkConnection == NULL ) { @@ -875,7 +884,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, } /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ - tcpSocket = _dnsLookup( pServerInfo ); + tcpSocket = _dnsLookup( pServerInfo, &connection.remoteIP ); if( tcpSocket == -1 ) { @@ -907,7 +916,11 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, IotNetworkOpenssl_Destroy( pNetworkConnection ); } - + else + { + connection.id = tcpSocket; + IotMetrics_AddTcpConnection( &connection ); + } return status; } @@ -1185,6 +1198,9 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) /* Unlock the connection mutex. */ IotMutex_Unlock( &( pNetworkConnection->mutex ) ); + /* Remove the socket from metrics. */ + IotMetrics_RemoveTcpConnection( pNetworkConnection->socket ); + return IOT_NETWORK_SUCCESS; } From 6e111ae8d81cdc0d3e3d716fcc7413c801b99ef7 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Tue, 26 Feb 2019 11:21:56 -0800 Subject: [PATCH 034/844] fix warning and tests of serializer (#293) --- .../static_memory/iot_static_memory_serializer.c | 14 +++++++------- scripts/build_check_commit.sh | 6 ++++++ scripts/build_check_pr.sh | 6 ++++++ tests/serializer/iot_test_serializer_cbor.c | 5 ++--- tests/serializer/iot_tests_serializer.c | 4 ++++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/source/common/static_memory/iot_static_memory_serializer.c b/lib/source/common/static_memory/iot_static_memory_serializer.c index 7c131ce382..78a3abbfed 100644 --- a/lib/source/common/static_memory/iot_static_memory_serializer.c +++ b/lib/source/common/static_memory/iot_static_memory_serializer.c @@ -104,7 +104,7 @@ static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; static bool _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0 }; - static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { 0 } }; + static _cborValueWrapper_t _cborValues[IOT_SERIALIZER_CBOR_VALUES] = { { 0 } }; static bool _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0 }; static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; @@ -214,12 +214,12 @@ if( size == sizeof( IotSerializerDecoderObject_t ) ) { - freeIndex = IotStaticMemory_FindFree( _inUseCborValues, - IOT_SERIALIZER_CBOR_VALUES ); + freeIndex = IotStaticMemory_FindFree( _inUseDecoderObjects, + IOT_SERIALIZER_DECODER_OBJECTS ); if( freeIndex != -1 ) { - pNewDecoderObject = &( _cborValues[ freeIndex ] ); + pNewDecoderObject = &( _decoderObjects[ freeIndex ] ); } } @@ -231,9 +231,9 @@ void Iot_FreeSerializerDecoderObject( void * ptr ) { IotStaticMemory_ReturnInUse( ptr, - _cborValues, - _inUseCborValues, - IOT_SERIALIZER_CBOR_VALUES, + _decoderObjects, + _inUseDecoderObjects, + IOT_SERIALIZER_DECODER_OBJECTS, sizeof( IotSerializerDecoderObject_t ) ); } diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index f6a5ae6dbc..81291c8b1c 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -43,6 +43,9 @@ make ./bin/aws_iot_tests_shadow ./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" +# Run serializer tests +./bin/iot_tests_serializer + # Rebuild in static memory mode. rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" @@ -54,3 +57,6 @@ make # Run MQTT and Shadow tests in static memory mode. ./bin/iot_tests_mqtt ./bin/aws_iot_tests_shadow + +# Run serializer tests +./bin/iot_tests_serializer \ No newline at end of file diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh index 293bdeb4a6..29c9e07d09 100644 --- a/scripts/build_check_pr.sh +++ b/scripts/build_check_pr.sh @@ -28,6 +28,9 @@ make # Run the Shadow tests that do not require the network. ./bin/aws_iot_tests_shadow -n +# Run serializer tests +./bin/iot_tests_serializer + # Rebuild in static memory mode. rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_STATIC_MEMORY_ONLY=1 $COMPILER_OPTIONS" @@ -39,3 +42,6 @@ make # Run MQTT tests and no-network Shadow tests in static memory mode. ./bin/iot_tests_mqtt ./bin/aws_iot_tests_shadow -n + +# Run serializer tests +./bin/iot_tests_serializer diff --git a/tests/serializer/iot_test_serializer_cbor.c b/tests/serializer/iot_test_serializer_cbor.c index 0410c01abd..a329aab20f 100644 --- a/tests/serializer/iot_test_serializer_cbor.c +++ b/tests/serializer/iot_test_serializer_cbor.c @@ -88,7 +88,6 @@ TEST_GROUP_RUNNER( Full_Serializer_CBOR ) TEST( Full_Serializer_CBOR, Encoder_init_with_null_buffer ) { - IotSerializerError_t error = IOT_SERIALIZER_SUCCESS; IotSerializerEncoderObject_t encoderObject = { 0 }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, @@ -164,7 +163,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_text_string ) TEST( Full_Serializer_CBOR, Encoder_append_byte_string ) { uint8_t inputBytes[] = "hello world"; - size_t inputLength = strlen( inputBytes ); + size_t inputLength = strlen( ( const char * ) inputBytes ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _encoder.append( &_encoderObject, IotSerializer_ScalarByteString( inputBytes, inputLength ) ) ); @@ -187,7 +186,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_byte_string ) TEST_ASSERT_EQUAL( inputLength, outputLength ); - TEST_ASSERT_EQUAL( 0, strcmp( inputBytes, outputBytes ) ); + TEST_ASSERT_EQUAL( 0, strcmp( ( const char * ) inputBytes, ( const char * ) outputBytes ) ); } TEST( Full_Serializer_CBOR, Encoder_open_a_scalar ) diff --git a/tests/serializer/iot_tests_serializer.c b/tests/serializer/iot_tests_serializer.c index 5328839e36..eb2da17935 100644 --- a/tests/serializer/iot_tests_serializer.c +++ b/tests/serializer/iot_tests_serializer.c @@ -61,6 +61,10 @@ int main( int argc, { struct sigaction signalAction; + /* Silence warnings about unused parameters. */ + ( void )argc; + ( void )argv; + /* Set a signal handler for segmentation faults and assertion failures. */ ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); signalAction.sa_handler = _signalHandler; From a410bce76dae523fddd9de3268a77544ffbdf1ca Mon Sep 17 00:00:00 2001 From: qiutongs Date: Wed, 27 Feb 2019 11:51:32 -0800 Subject: [PATCH 035/844] add defender library and tests (#294) --- CMakeLists.txt | 3 + lib/include/aws_iot_defender.h | 401 ++++++++ .../private/aws_iot_defender_internal.h | 365 ++++++++ lib/include/private/iot_static_memory.h | 70 ++ lib/source/common/CMakeLists.txt | 3 +- .../aws_iot_static_memory_defender.c | 151 +++ lib/source/defender/CMakeLists.txt | 16 + lib/source/defender/aws_iot_defender_api.c | 561 +++++++++++ .../defender/aws_iot_defender_collector.c | 502 ++++++++++ lib/source/defender/aws_iot_defender_mqtt.c | 235 +++++ .../cbor/iot_serializer_tinycbor_decoder.c | 7 +- tests/CMakeLists.txt | 3 + tests/defender/CMakeLists.txt | 7 + tests/defender/aws_iot_tests_defender.c | 117 +++ tests/defender/aws_iot_tests_defender_api.c | 880 ++++++++++++++++++ 15 files changed, 3319 insertions(+), 2 deletions(-) create mode 100644 lib/include/aws_iot_defender.h create mode 100644 lib/include/private/aws_iot_defender_internal.h create mode 100644 lib/source/common/static_memory/aws_iot_static_memory_defender.c create mode 100644 lib/source/defender/CMakeLists.txt create mode 100644 lib/source/defender/aws_iot_defender_api.c create mode 100644 lib/source/defender/aws_iot_defender_collector.c create mode 100644 lib/source/defender/aws_iot_defender_mqtt.c create mode 100644 tests/defender/CMakeLists.txt create mode 100644 tests/defender/aws_iot_tests_defender.c create mode 100644 tests/defender/aws_iot_tests_defender_api.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 93ff8a52fd..f9e868aff5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,9 @@ add_subdirectory( lib/source/metrics ) # Serializer library. add_subdirectory( lib/source/serializer ) +# Defender library. +add_subdirectory( lib/source/defender ) + # Demo executables. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( demos/posix ) diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h new file mode 100644 index 0000000000..396242a587 --- /dev/null +++ b/lib/include/aws_iot_defender.h @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_defender.h + * @brief User-facing functions and structs of AWS IoT Device Defender libraries + * + * [Link to AWS documentation](https://docs.aws.amazon.com/iot/latest/developerguide/device-defender-detect.html) + * + */ + +/** + * @mainpage + * + * ## Introduction + * AWS IoT Device Defender is an IoT security service that allows you to audit the configuration of your devices, + * monitor connected devices to detect abnormal behavior, and to mitigate security risks. + * Part of it relies on an dedicated agent to collect device-side metrics and send to AWS Iot. + * + * Amazon FreeRTOS provides a library that allows your Amazon FreeRTOS-based devices to work with AWS IoT Device Defender. + * + ## Dependencies + ##- MQTT library + ##- Serializer library + ##- Platform(POSIX) libraries + ##- Metrics library + */ + +#ifndef _AWS_IOT_DEFENDER_H_ +#define _AWS_IOT_DEFENDER_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include + +/* Network include. */ +#include "posix/iot_network_openssl.h" + +/** + * @page Defender_constants Constants + * @brief Defined constants of the Defender library. + * - @ref DefenderFormat "Serialization Format" + * - @ref DefenderMetricsFlags "Metrics Flags" + * - @ref DefenderInitializers "Initializers" + */ + +/** + * @anchor DefenderFormat + * @name Serialization Format + * + * @brief Format constants: Cbor or Json. + */ +/**@{ */ +#define AWS_IOT_DEFENDER_FORMAT_CBOR 1 /**< CBOR format. */ +#define AWS_IOT_DEFENDER_FORMAT_JSON 2 /**< JSON format. */ +/**@} */ + +/** + * @anchor DefenderMetricsFlags + * @name Metrics Flags + * + * @brief Bit flags or metrics used by @ref Defender_function_SetMetrics function. + */ +/**@{ */ +#define AWS_IOT_DEFENDER_METRICS_ALL 0xffffffff /**< Flag to indicate including all metrics. */ + +#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL 0x00000001 /**< Total count of established TCP connections. */ +#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR 0x00000004 /**< Remote address (IP:port) of established TCP connections. For example, 192.168.0.1:8000. */ + +/** + * Connections metrics including only remote address. Local port number is not supported. + * + */ +#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS \ + ( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) \ + + +/** + * Established connections metrics including connections metrics and total count. + */ +#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED \ + ( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS | AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) \ + +/**@} */ + +/** + * @anchor DefenderInitializers + * @name Initializers + * + * @brief Intializers of data handles. + */ +/**@{ */ +#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER { 0 } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ +/**@} */ + +/** + * @defgroup Defender_datatypes_enums Enumerated types + * @brief Enumerated types of the Defender library. + * + * @defgroup Defender_datatypes_paramstructs Parameter structures + * @brief Structures passed as parameters to [Defender functions](@ref Defender_functions) + * + * These structures are passed as parameters to library functions. + * Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. + */ + +/** + * @ingroup Defender_datatypes_enums + * @brief Metrics group options for AwsIotDefender_SetMetrics() function. + * + * Metrics group is defined based on the "metrics blocks" listed in [AWS IoT Defender document] + * (https://docs.aws.amazon.com/iot/latest/developerguide/device-defender-detect.html#DetectMetricsMessages) + */ +typedef enum +{ + AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, /**< TCP connection metrics group. */ +} AwsIotDefenderMetricsGroup_t; + +/** + * @ingroup Defender_datatypes_enums + * @brief Return codes of defender functions. + */ +typedef enum +{ + AWS_IOT_DEFENDER_SUCCESS = 0, /**< Defender operation completed successfully. */ + AWS_IOT_DEFENDER_INVALID_INPUT, /**< At least one input parameter is invalid. */ + AWS_IOT_DEFENDER_ALREADY_STARTED, /**< Defender has been already started. */ + AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, /**< Given period is too short. */ + AWS_IOT_DEFENDER_ERROR_NO_MEMORY, /**< Defender operation failed due to memory allocation failure. */ + AWS_IOT_DEFENDER_INTERNAL_FAILURE /**< Defender operation failed due to internal unexpected cause. */ +} AwsIotDefenderError_t; + +/** + * @ingroup Defender_datatypes_enums + * @brief Event codes passed into AwsIotDefenderCallbackInfo_t + */ +typedef enum +{ + AWS_IOT_DEFENDER_METRICS_ACCEPTED = 0, /**< Metrics report was accepted by defender service. */ + AWS_IOT_DEFENDER_METRICS_REJECTED, /**< Metrics report was rejected by defender service. */ + AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED, /**< Failed to connect to defender service endpoint. */ + AWS_IOT_DEFENDER_MQTT_CONNECTION_FAILED, /**< Failed to setup MQTT connection. */ + AWS_IOT_DEFENDER_MQTT_SUBSCRIPTION_FAILED, /**< Failed to subscribe defender MQTT topics. */ + AWS_IOT_DEFENDER_MQTT_PUBLISH_FAILED, /**< Failed to publish to MQTT topics. */ + AWS_IOT_DEFENDER_METRICS_SERIALIZATION_FAILED, /**< Failed to serialize metrics report. */ + AWS_IOT_DEFENDER_EVENT_NO_MEMORY /**< Metrics report was not able to be published due to memory allocation failure. */ +} AwsIotDefenderEventType_t; + +/** + * @defgroup Defender_datatypes_paramstructs Parameter structures + * @brief Structures passed as parameters to [Defender functions](@ref Defender_functions) + * + * These structures are passed as parameters to library functions. + * Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. + */ + +/** + * @ingroup Defender_datatypes_paramstructs + * @brief Callback parameters. + */ +typedef struct AwsIotDefenderCallbackInfo +{ + const uint8_t * pMetricsReport; /**< The published metrics report(could be NULL). */ + size_t metricsReportLength; /**< Length of the published metrics report. */ + const uint8_t * pPayload; /**< The received message if there is one(could be NULL). */ + size_t payloadLength; /**< Length of the message. */ + AwsIotDefenderEventType_t eventType; /**< Event code(always valid). */ +} AwsIotDefenderCallbackInfo_t; + +/** + * @ingroup Defender_datatypes_paramstructs + * @brief User provided callback handle. + */ +typedef struct AwsIotDefenderCallback +{ + void * param1; /**< The first parameter to pass to the callback function(optional). */ + void ( * function )( void *, + AwsIotDefenderCallbackInfo_t * const ); /**< Callback function signature(optional). */ +} AwsIotDefenderCallback_t; + +/** + * @ingroup Defender_datatypes_paramstructs + * @brief Parameters of AwsIotDefender_Start function. + */ +typedef struct AwsIotDefenderStartInfo +{ + IotNetworkServerInfoOpenssl_t serverInfo; + IotNetworkCredentialsOpenssl_t credentials; + const char * pThingName; /**< AWS IoT thing name(must be valid). */ + uint16_t thingNameLength; /**< Length of AWS IoT thing name(must be valid). */ + AwsIotDefenderCallback_t callback; /**< Length of AWS IoT thing name(optional). */ +} AwsIotDefenderStartInfo_t; + +/** + * @page Defender_functions Functions + * @brief Functions of the Defender library. + * - @subpage Defender_function_SetMetrics @copybrief Defender_function_SetMetrics + * - @subpage Defender_function_Start @copybrief Defender_function_Start + * - @subpage Defender_function_Stop @copybrief Defender_function_Stop + * - @subpage Defender_function_SetPeriod @copybrief Defender_function_SetPeriod + * - @subpage Defender_function_GetPeriod @copybrief Defender_function_GetPeriod + * - @subpage Defender_function_strerror @copybrief Defender_function_strerror + * - @subpage Defender_function_DescribeEventType @copybrief Defender_function_DescribeEventType + */ + +/** + * @page Defender_function_SetMetrics AwsIotDefender_SetMetrics + * @snippet this declare_defender_setmetrics + * @brief Set metrics that defender agent needs to collect for a metrics group. + * + * Metrics to be collected is global data which is independent of defender agent start or stop. + * + * @param[in] metricsGroup Metrics group defined in #AwsIotDefenderMetricsGroup_t + * @param[in] metrics Bit-flags to specify what metrics to collect. + * If all metrics in a group is needed, simply set metrics to #AWS_IOT_DEFENDER_METRICS_ALL. + * See @ref DefenderMetricsFlags "Metrics flags" for details. + * + * @return + * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. + * * If metricsGroup is invalid, #AWS_IOT_DEFENDER_INVALID_INPUT is returned. + * @note This function is thread safe. + */ + +/* @[declare_defender_setmetrics] */ +AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, + uint32_t metrics ); +/* @[declare_defender_setmetrics] */ + +/** + * @page Defender_function_Start AwsIotDefender_Start + * @snippet this declare_defender_start + * @brief Start the defender agent. + * + * @param[in] pStartInfo Pointer of parameters of start function + * + * Periodically, defender agent collects metrics and publish to specifc AWS reserved MQTT topic. + * + * @return + * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. + * * If pStartInfo is invalid, #AWS_IOT_DEFENDER_INVALID_INPUT is returned. + * * If memory allocation fails, #AWS_IOT_DEFENDER_ERROR_NO_MEMORY is returned. + * * If defender is already started, #AWS_IOT_DEFENDER_ALREADY_STARTED is returned. + * + * @warning This function is not thread safe. + * + * @note No need to manage the memory allocated for #AwsIotDefenderCallbackInfo_t. This function save the information internally. + * + * Example: + * + * @code{c} + * void logDefenderCallback(void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo) + * { + * const char * pEventStr = AwsIotDefender_DescribeEventType(pCallbackInfo->eventType); + * // log pEventStr + * + * if (pCallbackInfo->pPayload != NULL) + * { + * // log pCallbackInfo->pPayload which has length pCallbackInfo->payloadLength + * } + * + * if (pCallbackInfo->pMetricsReport != NULL) + * { + * // log pCallbackInfo->pMetricsReport which has length pCallbackInfo->metricsReportLength + * } + * } + * + * void startDefender() + * { + * // assume a valid AwsIotNetworkTlsInfo_t is created. + * const AwsIotNetworkTlsInfo_t tlsInfo; + * + * // define a simple callback function which simply logs + * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback,.param1 = NULL }; + * + * // define parameters of AwsIotDefender_Start function + * const AwsIotDefenderStartInfo_t startInfo = { + * .tlsInfo = tlsInfo, // copy TLS info + * .pAwsIotEndpoint = "iot endpoint", + * .port = 8883, + * .pThingName = "some thing name", + * .thingNameLength = strlen("some thing name"), + * .callback = callback }; + * + * // specify two TCP connections metrics: total count and local port + * AwsIotDefenderError_t error = AwsIotDefender_SetMetrics(AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + * AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL | + * AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ); + * + * if (error == AWS_IOT_DEFENDER_SUCCESS) + * { + * // set metrics report period to 10 minutes (600 seconds) + * error = AwsIotDefender_SetPeriod(600); + * } + * + * if (error == AWS_IOT_DEFENDER_SUCCESS) + * { + * // start the defender + * error = AwsIotDefender_Start(&startInfo); + * } + * + * if (error != AWS_IOT_DEFENDER_SUCCESS) + * { + * const char * pError = AwsIotDefender_strerror(error); + * // log pError + * } + * } + * + * void stopDefender() + * { + * //stop the defender + * AwsIotDefender_Stop(); + * } + * + * @endcode + */ +/* @[declare_defender_start] */ +AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ); +/* @[declare_defender_start] */ + +/** + * @page Defender_function_Stop AwsIotDefender_Stop + * @snippet this declare_defender_stop + * @brief Stop the defender agent. + * + * @warning This function must be called after successfully calling @ref Defender_function_Start "AwsIotDefender_Start". + * @warning This function is not thread safe. + */ +/* @[declare_defender_stop] */ +void AwsIotDefender_Stop( void ); +/* @[declare_defender_stop] */ + +/** + * @page Defender_function_SetPeriod AwsIotDefender_SetPeriod + * @snippet this declare_defender_setperiod + * @brief Set period in seconds. + * + * + * @param[in] periodSeconds Period is specified in seconds. Mininum is 300 (5 minutes) + * + * @return + * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. + * * If defender is not started yet, AWS_IOT_DEFENDER_NOT_STARTED is returned. + * + * @warning This function is not thread safe. + * + * @note If this function is called when defender agent is started, the period is re-calculated and updated in next iteration. + */ +/* @[declare_defender_setperiod] */ +AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint64_t periodSeconds ); +/* @[declare_defender_setperiod] */ + +/** + * @page Defender_function_GetPeriod AwsIotDefender_GetPeriod + * @snippet this declare_defender_getperiod + * @brief Get period in seconds. + */ +/* @[declare_defender_getperiod] */ +uint64_t AwsIotDefender_GetPeriod( void ); +/* @[declare_defender_getperiod] */ + +/** + * @page Defender_function_strerror AwsIotDefender_strerror + * @snippet this declare_defender_strerror + * @brief Return a string that describes #AwsIotDefenderError_t + */ +/* @[declare_defender_strerror] */ +const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); +/* @[declare_defender_strerror] */ + +/** + * @page Defender_function_DescribeEventType AwsIotDefender_DescribeEventType + * @snippet this declare_defender_describeeventtype + * @brief Return a string that describes #AwsIotDefenderEventType_t + */ +/* @[declare_defender_describeeventtype] */ +const char * AwsIotDefender_DescribeEventType( AwsIotDefenderEventType_t eventType ); +/* @[declare_defender_describeeventtype] */ + +#endif /* end of include guard: _AWS_IOT_DEFENDER_H_ */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h new file mode 100644 index 0000000000..cb28f3f70f --- /dev/null +++ b/lib/include/private/aws_iot_defender_internal.h @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * Internal header of Defender library. This header should not be included in + * typical application code. + */ + +#ifndef _AWS_IOT_DEFENDER_INTERNAL_H_ +#define _AWS_IOT_DEFENDER_INTERNAL_H_ + +/* Build using a config header, if provided. */ +#ifdef AWS_IOT_CONFIG_FILE + #include AWS_IOT_CONFIG_FILE +#endif + +/* Defender include. */ +#include "aws_iot_defender.h" + +/* Serializer include. */ +#include "iot_serializer.h" + +/* Platform thread include. */ +#include "platform/iot_threads.h" + +/* Double linked list include. */ +#include "iot_linear_containers.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/** + * @def AwsIotDefender_Assert( expression ) + * @brief Assertion macro for the Defender library. + * + * Set @ref AWS_IOT_DEFENDER_ENABLE_ASSERTS to `1` to enable assertions in the Defender + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 + #ifndef AwsIotDefender_Assert + #include + #define AwsIotDefender_Assert( expression ) assert( expression ) + #endif +#else + #define AwsIotDefender_Assert( expression ) +#endif + +/* Configure logs for Defender functions. */ +#ifdef AWS_IOT_LOG_LEVEL_DEFENDER + #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEFENDER +#else + #ifdef AWS_IOT_LOG_LEVEL_GLOBAL + #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define _LIBRARY_LOG_NAME ( "Defender" ) +#include "iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotDefender_MallocReport + #define AwsIotDefender_MallocReport AwsIot_MallocDefenderReport + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotDefender_FreeReport + #define AwsIotDefender_FreeReport AwsIot_FreeDefenderReport + #endif + +/** + * @brief Allocate an array of char. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef AwsIotDefender_MallocTopic + #define AwsIotDefender_MallocTopic AwsIot_MallocDefenderTopic + #endif + +/** + * @brief Free an array of char. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef AwsIotDefender_FreeTopic + #define AwsIotDefender_FreeTopic AwsIot_FreeDefenderTopic + #endif + +#else /* if IOT_STATIC_MEMORY_ONLY */ + #include + + #ifndef AwsIotDefender_MallocReport + #define AwsIotDefender_MallocReport malloc + #endif + + #ifndef AwsIotDefender_FreeReport + #define AwsIotDefender_FreeReport free + #endif + + #ifndef AwsIotDefender_MallocTopic + #define AwsIotDefender_MallocTopic malloc + #endif + + #ifndef AwsIotDefender_FreeTopic + #define AwsIotDefender_FreeTopic free + #endif + +#endif /* if IOT_STATIC_MEMORY_ONLY */ + +/** + * @page Defender_Config Configuration + * @brief Configuration settings of the Defender library + * @par configpagemarker + * + * @section AWS_IOT_DEFENDER_FORMAT + * @brief Default format for metrics data serialization. + * + * Possible values: #AWS_IOT_DEFENDER_FORMAT_CBOR or #AWS_IOT_DEFENDER_FORMAT_JSON
+ * Recommended values: Cbor is more compact than Json, thus more efficient.
+ * Default value (if undefined): #AWS_IOT_DEFENDER_FORMAT_CBOR
+ * + * @section AWS_IOT_DEFENDER_USE_LONG_TAG + * @brief Use long tag or short tag for metrics report. + * + * Possible values: `0` or `1`
+ * Recommended values: 0 to use short tag to reduce network transmit cost.
+ * Default value (if undefined): `0`
+ * + * @section AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS + * @brief Default period constants if users don't provide their own. + * + * If metrics is sent faster than 5 minutes for one "thing", it may be throttled. + * + * Possible values: greater than or equal to `300`
+ * Recommended values: greater than or equal to `300` seconds; defender service might throttle if the period is too short
+ * Default value (if undefined): `300`
+ * + * @section AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS + * @brief Default MQTT connect timeout. + * + * Possible values: greater than 0
+ * Default value (if undefined): `10`
+ * + * @section AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS + * @brief Default MQTT subscribe timeout. + * + * Possible values: greater than 0
+ * Default value (if undefined): `10`
+ * + * @section AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS + * @brief Default MQTT publish timeout. + * + * Possible values: greater than 0
+ * Default value (if undefined): `10`
+ */ + +#ifndef AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS + #define AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ( 300 ) +#endif + +#ifndef AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS + #define AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS ( 3 ) +#endif + +#ifndef AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS + #define AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ( 10U ) +#endif + +#ifndef AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS + #define AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ( 10U ) +#endif + +#ifndef AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS + #define AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ( 10U ) +#endif + +#ifndef AWS_IOT_DEFENDER_FORMAT + #define AWS_IOT_DEFENDER_FORMAT AWS_IOT_DEFENDER_FORMAT_CBOR +#endif + +/* In current release, JSON format is not supported. */ +#if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON + #error "AWS_IOT_DEFENDER_FORMAT_JSON is not supported." +#endif + +#ifndef AWS_IOT_DEFENDER_USE_LONG_TAG + #define AWS_IOT_DEFENDER_USE_LONG_TAG ( 0 ) +#endif + +/*----------------- Below this line is INTERNAL used only --------------------*/ + +/* This MUST be consistent with enum AwsIotDefenderMetricsGroup_t. */ +#define _DEFENDER_METRICS_GROUP_COUNT 1 + +/** + * Define encoder/decoder based on configuration AWS_IOT_DEFENDER_FORMAT. + */ +#if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR + + #define _DEFENDER_FORMAT "cbor" + #define _AwsIotDefenderEncoder _IotSerializerCborEncoder /**< Global defined in iot_serializer.h . */ + #define _AwsIotDefenderDecoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ + +#elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON + + #define _DEFENDER_FORMAT "json" + #define _AwsIotDefenderEncoder _IotSerializerJsonEncoder /**< Global defined in iot_serializer.h . */ + #define _AwsIotDefenderDecoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ + +#else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ + #error "AWS_IOT_DEFENDER_FORMAT must be either AWS_IOT_DEFENDER_FORMAT_CBOR or AWS_IOT_DEFENDER_FORMAT_JSON." + +#endif /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ + +/** + * Define a helper macro to select long tag or short tag based on configuration AWS_IOT_DEFENDER_USE_LONG_TAG. + */ +#if AWS_IOT_DEFENDER_USE_LONG_TAG == 1 + #define AwsIotDefenderInternal_SelectTag( long_tag, short_tag ) ( long_tag ) +#else + #define AwsIotDefenderInternal_SelectTag( long_tag, short_tag ) ( short_tag ) +#endif + +/** + * Convert seconds to milliseconds and vice versa. + */ +#define _defenderToMilliseconds( secondValue ) ( secondValue ) * 1000 +#define _defenderToSeconds( millisecondValue ) \ + ( millisecondValue ) / 1000 \ + + +/** + * Structure to hold the metrics. + */ +typedef struct _defenderMetrics +{ + /** + * Array of bit-flag of metrics. The index is enum value of AwsIotDefenderMetricsGroup_t. + */ + uint32_t metricsFlag[ _DEFENDER_METRICS_GROUP_COUNT ]; + + /** + * Mutex to protect _AwsIotDefenderMetricsFlag referenced by: + * - metrics timer callback + * - SetMetrics API + */ + IotMutex_t mutex; +} _defenderMetrics_t; + +/** + * Create a report, memory is allocated inside the function. + */ +AwsIotDefenderEventType_t AwsIotDefenderInternal_CreateReport(); + +/** + * Get the buffer pointer of report. + */ +uint8_t * AwsIotDefenderInternal_GetReportBuffer(); + +/** + * Get the buffer size of report. + */ +size_t AwsIotDefenderInternal_GetReportBufferSize(); + +/** + * Delete a report when it is useless. Internally, memory will be freed. + */ +void AwsIotDefenderInternal_DeleteReport(); + +/** + * Build three topics names used by defender library. + */ +AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( const char * pThingName, + uint16_t thingNameLength ); + +/** + * Free the memory of three topics names. + */ +void AwsIotDefenderInternal_DeleteTopicsNames(); + +/** + * Creat a network connection. + */ +bool AwsIotDefenderInternal_NetworkConnect( IotNetworkServerInfoOpenssl_t * pServerInfo, + IotNetworkCredentialsOpenssl_t * pCredentials ); + +/** + * Set the network connection to callback MQTT. + */ +void AwsIotDefenderInternal_SetMqttCallback(); + +/** + * Connect to AWS with MQTT. + */ +bool AwsIotDefenderInternal_MqttConnect( const char * pThingName, + uint16_t thingNameLength ); + +/** + * Subscribe accept/reject defender topics. + */ +bool AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, + IotMqttCallbackInfo_t rejectCallback ); + +/** + * Publish metrics data to defender topic. + */ +bool AwsIotDefenderInternal_MqttPublish( uint8_t * pData, + size_t dataLength ); + +/** + * Disconnect with AWS MQTT. + */ +void AwsIotDefenderInternal_MqttDisconnect(); + +/** + * Close network connection. + */ +void AwsIotDefenderInternal_NetworkClose(); + +/** + * Destory network connection. + */ +void AwsIotDefenderInternal_NetworkDestroy(); + +/*----------------- Below this line are INTERNAL global variables --------------------*/ + +extern _defenderMetrics_t _AwsIotDefenderMetrics; + +#endif /* ifndef _AWS_IOT_DEFENDER_INTERNAL_H_ */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index e035e35041..f6a5006c2d 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -583,4 +583,74 @@ void * Iot_MallocSerializerDecoderObject( size_t size ); void Iot_FreeSerializerDecoderObject( void * ptr ); /* @[declare_static_memory_freeserializerdecoderobject] */ +/** + * @brief Allocates memory to hold data for a new [Defender Report] + * (@ref static_memory_mallocdefenderreport). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Defender Report. + * + * @param[in] size Requested size for the report. Because each report + * contains a different metrics content, reports will be different sizes. This + * value should be checked to make sure that the statically-allocated report + * object is large enough to accommodate all the metrics data. + * + * @return Pointer to a Defender Report. If the size argument is larger than + * the fixed size of a Defender Report object or no free Defender Report + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocdefenderreport] */ +void * AwsIot_MallocDefenderReport( size_t size ); +/* @[declare_static_memory_mallocdefenderreport] */ + +/** + * @brief Frees an in-use [Defender Report] + * (@ref static_memory_freedefenderreport). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Defender Report. + * + * @param[in] ptr Pointer to an active Defender Report to free. + */ +/* @[declare_static_memory_freedefenderreport] */ +void AwsIot_FreeDefenderReport( void * ptr ); +/* @[declare_static_memory_freedefenderreport] */ + +/** + * @brief Allocates memory to hold data for a new [Defender Topic] + * (@ref static_memory_mallocdefendertopic). + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Defender Topic. + * + * @param[in] size Requested size for the topic. Because each topic + * contains a different thing name, topics will be different sizes. This + * value should be checked to make sure that the statically-allocated topic + * object is large enough to accommodate all the topics. + * + * @return Pointer to a Defender Topic. If the size argument is larger than + * the fixed size of a Defender Topic object or no free Defender Topic + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocdefendertopic] */ +void * AwsIot_MallocDefenderTopic( size_t size ); +/* @[declare_static_memory_mallocdefendertopic] */ + +/** + * @brief Frees an in-use [Defender Topic] + * (@ref static_memory_freedefendertopic). + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Defender Topic. + * + * @param[in] ptr Pointer to an active Defender Topic to free. + */ +/* @[declare_static_memory_freedefendertopic] */ +void AwsIot_FreeDefenderTopic( void * ptr ); +/* @[declare_static_memory_freedefendertopic] */ + #endif /* if !defined( _IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index d3318452f0..d94d51ec83 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -9,7 +9,8 @@ add_library( iotcommon SHARED static_memory/iot_static_memory_mqtt.c static_memory/aws_iot_static_memory_shadow.c static_memory/iot_static_memory_metrics.c - static_memory/iot_static_memory_serializer.c ) + static_memory/iot_static_memory_serializer.c + static_memory/aws_iot_static_memory_defender.c ) # Library version. set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) diff --git a/lib/source/common/static_memory/aws_iot_static_memory_defender.c b/lib/source/common/static_memory/aws_iot_static_memory_defender.c new file mode 100644 index 0000000000..45d68347f8 --- /dev/null +++ b/lib/source/common/static_memory/aws_iot_static_memory_defender.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ + #include + #include + #include + +/* Static memory include. */ + #include "private/iot_static_memory.h" + +/* Defender include. */ + #include "private/aws_iot_defender_internal.h" + + #ifndef AWS_IOT_DEFENDER_REPORTS + #define AWS_IOT_DEFENDER_REPORTS ( 1 ) + #endif + + #ifndef AWS_IOT_DEFENDER_TOPICS + #define AWS_IOT_DEFENDER_TOPICS ( 3 ) + #endif + +/* Validate static memory configuration settings. */ + #if AWS_IOT_DEFENDER_REPORTS <= 0 + #error "AWS_IOT_DEFENDER_REPORTS cannot be 0 or negative." + #endif + + #if AWS_IOT_DEFENDER_TOPICS <= 0 + #error "AWS_IOT_DEFENDER_TOPICS cannot be 0 or negative." + #endif + + #define _DEFENDER_REPORT_SIZE 200 + +/* Prefix(30) + esimated "thing name"(128) + sufix(30). */ + #define _DEFENDER_TOPIC_SIZE 200 + +/*-----------------------------------------------------------*/ + +/* Extern declarations of common static memory functions in iot_static_memory_common.c + * Because these functions are specific to this static memory implementation, they are + * not placed in the common static memory header file. */ + extern int IotStaticMemory_FindFree( bool * const pInUse, + int limit ); + extern void IotStaticMemory_ReturnInUse( void * ptr, + void * const pPool, + bool * const pInUse, + int limit, + size_t elementSize ); + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ + static bool _inUseDefenderReports[ AWS_IOT_DEFENDER_REPORTS ] = { 0 }; + static uint8_t _defenderReports[ AWS_IOT_DEFENDER_REPORTS ][ _DEFENDER_REPORT_SIZE ] = { { 0 } }; + + static bool _inUseDefenderTopics[ AWS_IOT_DEFENDER_TOPICS ] = { 0 }; + static char _defenderTopics[ AWS_IOT_DEFENDER_TOPICS ][ _DEFENDER_TOPIC_SIZE ] = { { 0 } }; + +/*-----------------------------------------------------------*/ + + void * AwsIot_MallocDefenderReport( size_t size ) + { + int freeIndex = -1; + void * pNewReport = NULL; + + if( size <= _DEFENDER_REPORT_SIZE ) + { + freeIndex = IotStaticMemory_FindFree( _inUseDefenderReports, + AWS_IOT_DEFENDER_REPORTS ); + + if( freeIndex != -1 ) + { + pNewReport = &( _defenderReports[ freeIndex ][ 0 ] ); + } + } + + return pNewReport; + } + +/*-----------------------------------------------------------*/ + + void AwsIot_FreeDefenderReport( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _defenderReports, + _inUseDefenderReports, + AWS_IOT_DEFENDER_REPORTS, + _DEFENDER_REPORT_SIZE ); + } + +/*-----------------------------------------------------------*/ + + void * AwsIot_MallocDefenderTopic( size_t size ) + { + int freeIndex = -1; + void * pNewTopic = NULL; + + if( size <= _DEFENDER_TOPIC_SIZE ) + { + freeIndex = IotStaticMemory_FindFree( _inUseDefenderTopics, + AWS_IOT_DEFENDER_TOPICS ); + + if( freeIndex != -1 ) + { + pNewTopic = &( _defenderTopics[ freeIndex ][ 0 ] ); + } + } + + return pNewTopic; + } + +/*-----------------------------------------------------------*/ + + void AwsIot_FreeDefenderTopic( void * ptr ) + { + IotStaticMemory_ReturnInUse( ptr, + _defenderTopics, + _inUseDefenderTopics, + AWS_IOT_DEFENDER_TOPICS, + _DEFENDER_TOPIC_SIZE ); + } + +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt new file mode 100644 index 0000000000..cda9ec1ddd --- /dev/null +++ b/lib/source/defender/CMakeLists.txt @@ -0,0 +1,16 @@ +# Serializer library source files. +add_library( awsiotdefender SHARED + aws_iot_defender_api.c + aws_iot_defender_collector.c + aws_iot_defender_mqtt.c ) + +# Library version. +set_target_properties( awsiotdefender PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Link required libraries. +target_link_libraries( awsiotdefender iotserializer iotmetrics iotmqtt iotplatform ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotdefender unity ) +endif() \ No newline at end of file diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c new file mode 100644 index 0000000000..892d93a5ac --- /dev/null +++ b/lib/source/defender/aws_iot_defender_api.c @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Defender internal include. */ +#include "private/aws_iot_defender_internal.h" + +/* Task pool include. */ +#include "iot_taskpool.h" + +/* POSIX includes. */ +#include + +#define _WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) + +#if _WAIT_METRICS_JOB_MAX_SECONDS < AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS + #error "_WAIT_METRICS_JOB_MAX_SECONDS must be greater than AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS." +#endif + +/** + * callback registerd on accept topic. + */ +void _acceptCallback( void * pArgument, + IotMqttCallbackParam_t * const pPublish ); + +/** + * callback registerd on reject topic. + */ +void _rejectCallback( void * pArgument, + IotMqttCallbackParam_t * const pPublish ); + +/** + * Callback routine of _metricsPublishJob. + */ +static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * pUserContext ); + +/* Callback routine of _disconnectJob. */ +static void _disconnectRoutine( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * pUserContext ); + + +/*------------------- Below are global variables. ---------------------------*/ + +/* Define global metrics and initialize metrics flags array to zero. */ +_defenderMetrics_t _AwsIotDefenderMetrics = +{ + .metricsFlag = { 0 } +}; + +/** + * Period between reports in milliseconds. + * Set default value. + */ +static uint64_t _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); + +static IotTaskPoolJob_t _metricsPublishJob = { 0 }; + +static IotTaskPoolJob_t _disconnectJob = { 0 }; + +/* + * State variable to indicate if defender has been started successfully, initialized to false. + * There is no lock to protect it so the functions referencing it are not thread safe. + */ +static bool _started = false; + +/* Internal copy of startInfo so that user's input doesn't have to be valid all the time. */ +static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + +/*-----------------------------------------------------------*/ + +AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, + uint32_t metrics ) +{ + if( metricsGroup >= _DEFENDER_METRICS_GROUP_COUNT ) + { + IotLogError( "Input metrics group is invalid. Please use AwsIotDefenderMetricsGroup_t data type." ); + + return AWS_IOT_DEFENDER_INVALID_INPUT; + } + + /* If started, it needs to lock the metrics to protect concurrent read from metrics timer callback. */ + if( _started ) + { + IotMutex_Lock( &_AwsIotDefenderMetrics.mutex ); + + _AwsIotDefenderMetrics.metricsFlag[ metricsGroup ] = metrics; + + IotMutex_Unlock( &_AwsIotDefenderMetrics.mutex ); + } + else + { + _AwsIotDefenderMetrics.metricsFlag[ metricsGroup ] = metrics; + } + + IotLogInfo( "Metrics are successfully updated." ); + + return AWS_IOT_DEFENDER_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ) +{ + if( ( pStartInfo == NULL ) || + ( pStartInfo->pThingName == NULL ) ) + { + IotLogError( "Input start info is invalid." ); + + return AWS_IOT_DEFENDER_INVALID_INPUT; + } + + /* Assert system task pool is pre-created! */ + AwsIotDefender_Assert( IOT_SYSTEM_TASKPOOL != NULL ); + + AwsIotDefenderError_t defenderError = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + + IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; + + /* Initialize flow control states to false. */ + bool buildTopicsNamesSuccess = false, + metricsMutexCreateSuccess = false; + + if( !_started ) + { + /* copy input start info into global variable _startInfo */ + _startInfo = *pStartInfo; + + defenderError = AwsIotDefenderInternal_BuildTopicsNames( _startInfo.pThingName, + _startInfo.thingNameLength ); + + buildTopicsNamesSuccess = ( defenderError == AWS_IOT_DEFENDER_SUCCESS ); + + if( buildTopicsNamesSuccess ) + { + metricsMutexCreateSuccess = IotMutex_Create( &_AwsIotDefenderMetrics.mutex, false ); + } + + if( metricsMutexCreateSuccess ) + { + /* Create disconnect job. */ + taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + /* Create metrics job. */ + taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + /* Schedule metrics job. */ + taskPoolError = IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, 0 ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + /* Everything is good. Declare success. */ + _started = true; + defenderError = AWS_IOT_DEFENDER_SUCCESS; + IotLogInfo( "Defender agent has successfully started." ); + } + + /* Do the cleanup jobs if not success. + * It is almost the same work as AwsIotDefender_Stop except here it must "clean" on condition. + */ + if( defenderError != AWS_IOT_DEFENDER_SUCCESS ) + { + /* reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ + _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + + if( buildTopicsNamesSuccess ) + { + AwsIotDefenderInternal_DeleteTopicsNames(); + + taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob ); + + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_disconnectJob ); + + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + } + + if( metricsMutexCreateSuccess ) + { + IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); + } + + IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( defenderError ) ); + } + } + else + { + defenderError = AWS_IOT_DEFENDER_ALREADY_STARTED; + } + + return defenderError; +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefender_Stop( void ) +{ + if( !_started ) + { + IotLogWarn( "Defender has not started yet." ); + + return; + } + + /* As first step, set flag to NOT started. */ + _started = false; + + IotTaskPoolJobStatus_t status; + IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, &status ); + + /* If cancel failed, let it sleep for a while and hope everything finishes. */ + if( taskPoolError != IOT_TASKPOOL_SUCCESS ) + { + IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); + sleep( _WAIT_METRICS_JOB_MAX_SECONDS ); + } + + /* Destroy two task pool jobs. */ + taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_disconnectJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + /* Destroy metrics' mutex. */ + IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); + + /* Delete topics names. */ + AwsIotDefenderInternal_DeleteTopicsNames(); + + /* Reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ + _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + + /* Reset _periodMilliSecond to default value. */ + _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); + + /* Reset metrics flag array to 0. */ + memset( _AwsIotDefenderMetrics.metricsFlag, 0, sizeof( _AwsIotDefenderMetrics.metricsFlag ) ); + + IotLogInfo( "Defender agent has stopped." ); +} + +/*-----------------------------------------------------------*/ + +AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint64_t periodSeconds ) +{ + /* Input period cannot be too long, which will cause integer overflow. */ + AwsIotDefender_Assert( periodSeconds < _defenderToSeconds( UINT64_MAX ) ); + + AwsIotDefenderError_t defenderError = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + + /* period can not be too short unless this is test mode. */ + if( periodSeconds < AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ) + { + defenderError = AWS_IOT_DEFENDER_PERIOD_TOO_SHORT; + IotLogError( "Input period is too short. It must be greater than %d seconds.", AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); + } + else + { + _periodMilliSecond = _defenderToMilliseconds( periodSeconds ); + + defenderError = AWS_IOT_DEFENDER_SUCCESS; + IotLogInfo( "Period has been set to %d seconds successfully.", periodSeconds ); + } + + return defenderError; +} + +/*-----------------------------------------------------------*/ + +uint64_t AwsIotDefender_GetPeriod( void ) +{ + return _defenderToSeconds( _periodMilliSecond ); +} + +/*-----------------------------------------------------------*/ + +const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) +{ + /* The string returned if the parameter is invalid. */ + static const char * pInvalidError = "INVALID ERROR"; + /* Lookup table of Defender errors. */ + static const char * pErrorNames[] = + { + "SUCCESS", /* AWS_IOT_DEFENDER_SUCCESS */ + "INLVALID INPUT", /* AWS_IOT_DEFENDER_INVALID_INPUT */ + "ALREADY STARTED", /* AWS_IOT_DEFENDER_ALREADY_STARTED */ + "PERIOD TOO SHORT", /* AWS_IOT_DEFENDER_PERIOD_TOO_SHORT */ + "NO MEMORY", /* AWS_IOT_DEFENDER_ERROR_NO_MEMORY */ + "INTERNAL FAILURE" /* AWS_IOT_DEFENDER_INTERNAL_FAILURE */ + }; + + /* Check that the parameter is valid. */ + if( ( error < 0 ) || + ( error >= ( sizeof( pErrorNames ) / sizeof( pErrorNames[ 0 ] ) ) ) ) + { + return pInvalidError; + } + + return pErrorNames[ error ]; +} + +/*-----------------------------------------------------------*/ + +const char * AwsIotDefender_DescribeEventType( AwsIotDefenderEventType_t eventType ) +{ + /* The string returned if the parameter is invalid. */ + static const char * pInvalidEvent = "INVALID EVENT"; + + /* Lookup table of Defender events. */ + static const char * pEventNames[] = + { + "METRICS ACCEPTED", /* AWS_IOT_DEFENDER_METRICS_ACCEPTED */ + "METRICS REJECTED", /* AWS_IOT_DEFENDER_METRICS_REJECTED */ + "NETWORK CONNECTION FAILED" /* AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED */ + "MQTT CONNECTION FAILED", /* AWS_IOT_DEFENDER_MQTT_CONNECTION_FAILED */ + "MQTT SUBSCRIPTION FAILED", /* AWS_IOT_DEFENDER_MQTT_SUBSCRIPTION_FAILED */ + "MQTT PUBLISH FAILED", /* AWS_IOT_DEFENDER_MQTT_PUBLISH_FAILED */ + "METRICS SERIALIZATION FAILED", /* AWS_IOT_DEFENDER_METRICS_SERIALIZATION_FAILED */ + "NO MEMORY" /* AWS_IOT_DEFENDER_EVENT_NO_MEMORY */ + }; + + /* Check that the parameter is valid. */ + if( ( eventType < 0 ) || + ( eventType >= ( sizeof( pEventNames ) / sizeof( pEventNames[ 0 ] ) ) ) ) + { + return pInvalidEvent; + } + + return pEventNames[ eventType ]; +} + +/*-----------------------------------------------------------*/ +static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * pUserContext ) +{ + IotLogDebug( "Metrics publish job starts." ); + + if( !_started ) + { + IotLogWarn( "Defender has been stopped. No further processing." ); + + return; + } + + bool networkConnected = false, mqttConnected = false, reportCreated = false, reportPublished = false; + + AwsIotDefenderEventType_t eventType; + AwsIotDefenderCallbackInfo_t callbackInfo; + + const IotMqttCallbackInfo_t acceptCallbackInfo = { .function = _acceptCallback, .param1 = NULL }; + const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .param1 = NULL }; + + /* Step 1: connect to Iot endpoint. */ + if( networkConnected = AwsIotDefenderInternal_NetworkConnect( &_startInfo.serverInfo, + &_startInfo.credentials ) ) + { + /* Step 2: set MQTT callback. */ + AwsIotDefenderInternal_SetMqttCallback(); + + /* Step 3: connect to MQTT. */ + if( mqttConnected = AwsIotDefenderInternal_MqttConnect( _startInfo.pThingName, + _startInfo.thingNameLength ) ) + { + /* Step 4: subscribe to accept/reject MQTT topics. */ + if( AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, + rejectCallbackInfo ) ) + { + /* Step 5: create serialized metrics report. */ + eventType = AwsIotDefenderInternal_CreateReport(); + + /* If Report is created successfully. */ + if( eventType == 0 ) + { + reportCreated = true; + + /* Step 6: publish report to defender topic. */ + if( reportPublished = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), + AwsIotDefenderInternal_GetReportBufferSize() ) ) + { + IotLogDebug( "Metrics report has been published successfully." ); + } + else + { + eventType = AWS_IOT_DEFENDER_MQTT_PUBLISH_FAILED; + } + } + } + else + { + eventType = AWS_IOT_DEFENDER_MQTT_SUBSCRIPTION_FAILED; + } + } + else + { + eventType = AWS_IOT_DEFENDER_MQTT_CONNECTION_FAILED; + } + } + else + { + eventType = AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED; + } + + if( reportPublished ) + { + IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, + &_disconnectJob, + _defenderToMilliseconds( AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS ) ); + } + /* Something is wrong during the above process. */ + else + { + /* Invoke user's callback if there is. */ + if( _startInfo.callback.function != NULL ) + { + callbackInfo.eventType = eventType; + + /* No message to be given to user's callback */ + callbackInfo.pPayload = NULL; + callbackInfo.payloadLength = 0; + + /* It is possible the report buffer is NULL and size is 0. */ + callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); + callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); + + _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); + } + + /* Clean up resources conditionally. */ + if( reportCreated ) + { + AwsIotDefenderInternal_DeleteReport(); + } + + if( mqttConnected ) + { + AwsIotDefenderInternal_MqttDisconnect(); + } + + if( networkConnected ) + { + AwsIotDefenderInternal_NetworkDestroy(); + } + } + + IotLogDebug( "Metrics publish job ends." ); +} + +/*-----------------------------------------------------------*/ + +static void _disconnectRoutine( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * pUserContext ) +{ + IotLogDebug( "Disconnect job starts." ); + + AwsIotDefenderInternal_DeleteReport(); + AwsIotDefenderInternal_MqttDisconnect(); + AwsIotDefenderInternal_NetworkDestroy(); + + if( _started ) + { + /* Re-create metrics job. */ + IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + /* Re-schedule metrics job with period as deferred interval. */ + taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, _periodMilliSecond ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + } + else + { + IotLogWarn( "Defender has been stopped. Skip scheduling new metrics publish job." ); + } + + IotLogDebug( "Disconnect job ends." ); +} + +/*-----------------------------------------------------------*/ + +void _acceptCallback( void * pArgument, + IotMqttCallbackParam_t * const pPublish ) +{ + ( void ) pArgument; + + IotLogInfo( "Metrics report was accepted by defender service." ); + + /* In accepted case, report and MQTT message must exist. */ + AwsIotDefender_Assert( AwsIotDefenderInternal_GetReportBuffer() ); + AwsIotDefender_Assert( pPublish->message.info.pPayload ); + + /* Invoke user's callback with accept event. */ + AwsIotDefenderCallbackInfo_t callbackInfo; + + if( _startInfo.callback.function != NULL ) + { + callbackInfo.eventType = AWS_IOT_DEFENDER_METRICS_ACCEPTED; + + callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); + callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); + + callbackInfo.pPayload = pPublish->message.info.pPayload; + callbackInfo.payloadLength = pPublish->message.info.payloadLength; + + _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); + } +} + +/*-----------------------------------------------------------*/ + +void _rejectCallback( void * pArgument, + IotMqttCallbackParam_t * const pPublish ) +{ + ( void ) pArgument; + + IotLogError( "Metrics report was rejected by defender service." ); + + /* In rejected case, MQTT message must exist. */ + AwsIotDefender_Assert( pPublish->message.info.pPayload ); + + /* Invoke user's callback with rejected event. */ + AwsIotDefenderCallbackInfo_t callbackInfo; + + if( _startInfo.callback.function != NULL ) + { + callbackInfo.eventType = AWS_IOT_DEFENDER_METRICS_REJECTED; + + callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); + callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); + + callbackInfo.pPayload = pPublish->message.info.pPayload; + callbackInfo.payloadLength = pPublish->message.info.payloadLength; + + _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); + } +} diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c new file mode 100644 index 0000000000..c3cd566496 --- /dev/null +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes */ +#include +#include + +/* Defender internal include. */ +#include "private/aws_iot_defender_internal.h" + +#include "iot_metrics.h" + +#include "platform/iot_clock.h" + +#define _HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) +#define _REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) +#define _VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) +#define _VERSION_1_0 "1.0" /* Used by defender service to indicate the schema change of report, e.g. adding new field. */ +#define _METRICS_TAG AwsIotDefenderInternal_SelectTag( "metrics", "met" ) + +/* 15 for IP + 1 for ":" + 5 for port + 1 terminator + * For example: "192.168.0.1:8000\0" + */ +#define _REMOTE_ADDR_LENGTH 22 + +#define _TCP_CONN_TAG AwsIotDefenderInternal_SelectTag( "tcp_connections", "tc" ) +#define _EST_CONN_TAG AwsIotDefenderInternal_SelectTag( "established_connections", "ec" ) +#define _TOTAL_TAG AwsIotDefenderInternal_SelectTag( "total", "t" ) +#define _CONN_TAG AwsIotDefenderInternal_SelectTag( "connections", "cs" ) +#define _REMOTE_ADDR_TAG AwsIotDefenderInternal_SelectTag( "remote_addr", "rad" ) + +/** + * Structure to hold a metrics report. + */ +typedef struct _metricsReport +{ + IotSerializerEncoderObject_t object; /* Encoder object handle. */ + uint8_t * pDataBuffer; /* Raw data buffer to be published with MQTT. */ + size_t size; /* Raw data size. */ +} _metricsReport_t; + +typedef struct _tcpConns +{ + IotMetricsTcpConnection_t * pArray; + uint8_t count; +} _tcpConns_t; + +typedef struct _metrics +{ + _tcpConns_t tcpConns; +} _metrics_t; + +/* Initialize metrics report. */ +static _metricsReport_t _report = +{ + .object = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM, + .pDataBuffer = NULL, + .size = 0 +}; + +/* Initialize metrics data. */ +static _metrics_t _metrics = { 0 }; + +/* Define a "snapshot" global array of metrics flag. */ +static uint32_t _metricsFlagSnapshot[ _DEFENDER_METRICS_GROUP_COUNT ]; + +/* Report id integer. */ +uint64_t _AwsIotDefenderReportId = 0; + +/*---------------------- Helper Functions -------------------------*/ + +static void copyMetricsFlag(); + +static AwsIotDefenderEventType_t getLatestMetricsData(); + +static void freeMetricsData(); + +static void tcpConnectionsCallback( void * param1, + IotListDouble_t * pTcpConnectionsMetricsList ); + +static void _serialize(); + +static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObject ); + +/*-----------------------------------------------------------*/ + +void assertSuccess( IotSerializerError_t error ) +{ + AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS ); +} + +/*-----------------------------------------------------------*/ + +void assertSuccessOrBufferToSmall( IotSerializerError_t error ) +{ + AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS || error == IOT_SERIALIZER_BUFFER_TOO_SMALL ); +} + +/*-----------------------------------------------------------*/ + +uint8_t * AwsIotDefenderInternal_GetReportBuffer() +{ + return _report.pDataBuffer; +} + +/*-----------------------------------------------------------*/ + +size_t AwsIotDefenderInternal_GetReportBufferSize() +{ + /* Encoder might over-calculate the needed size. Therefor encoded size might be smaller than buffer size: _report.size. */ + return _report.pDataBuffer == NULL ? 0 + : _AwsIotDefenderEncoder.getEncodedSize(&_report.object, _report.pDataBuffer); +} + +/*-----------------------------------------------------------*/ + +AwsIotDefenderEventType_t AwsIotDefenderInternal_CreateReport() +{ + /* Assert report buffer is not allocated. */ + AwsIotDefender_Assert( _report.pDataBuffer == NULL && _report.size == 0 ); + + AwsIotDefenderEventType_t eventError = 0; + + IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); + size_t dataSize = 0; + uint8_t * pReportBuffer = NULL; + + /* Copy the metrics flag user specified. */ + copyMetricsFlag(); + + /* Get latest metrics data. */ + eventError = getLatestMetricsData(); + + if( !eventError ) + { + /* Generate report id based on current time. */ + _AwsIotDefenderReportId = IotClock_GetTimeMs(); + + /* Dry-run serialization to calculate the required size. */ + _serialize(); + + /* Get the calculated required size. */ + dataSize = _AwsIotDefenderEncoder.getExtraBufferSizeNeeded( pEncoderObject ); + + /* Clean the encoder object handle. */ + _AwsIotDefenderEncoder.destroy( pEncoderObject ); + + /* Allocate memory once. */ + pReportBuffer = AwsIotDefender_MallocReport( dataSize * sizeof( uint8_t ) ); + + if( pReportBuffer != NULL ) + { + _report.pDataBuffer = pReportBuffer; + _report.size = dataSize; + + /* Actual serialization. */ + _serialize(); + } + else + { + eventError = AWS_IOT_DEFENDER_EVENT_NO_MEMORY; + } + + /* Metrics data can be freed. */ + freeMetricsData(); + } + + return eventError; +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_DeleteReport() +{ + /* Destroy the encoder object. */ + _AwsIotDefenderEncoder.destroy( &( _report.object ) ); + + /* Free the memory of data buffer. */ + AwsIotDefender_FreeReport( _report.pDataBuffer ); + + /* Reset report members. */ + _report.pDataBuffer = NULL; + _report.size = 0; + _report.object = ( IotSerializerEncoderObject_t ) IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM; + + _metrics = ( _metrics_t ) { + 0 + }; +} + +/* + * report: + * { + * "header": { + * "report_id": 1530304554, + * "version": "1.0" + * }, + * "metrics": { + * ... + * } + * } + */ +static void _serialize() +{ + IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; + + IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); + + IotSerializerEncoderObject_t reportMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t headerMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t metricsMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + + /* Define an assert function for serialization returned error. */ + void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall + : assertSuccess; + + uint8_t metricsGroupCount = 0; + uint32_t i = 0; + + serializerError = _AwsIotDefenderEncoder.init( pEncoderObject, _report.pDataBuffer, _report.size ); + assertNoError( serializerError ); + + /* Create the outermost map with 2 keys: "header", "metrics". */ + serializerError = _AwsIotDefenderEncoder.openContainer( pEncoderObject, &reportMap, 2 ); + assertNoError( serializerError ); + + /* Create the "header" map with 2 keys: "report_id", "version". */ + serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &reportMap, + _HEADER_TAG, + &headerMap, + 2 ); + assertNoError( serializerError ); + + /* Append key-value pair of "report_Id" which uses clock time. */ + serializerError = _AwsIotDefenderEncoder.appendKeyValue( &headerMap, + _REPORTID_TAG, + IotSerializer_ScalarSignedInt( _AwsIotDefenderReportId ) ); + assertNoError( serializerError ); + + /* Append key-value pair of "version". */ + serializerError = _AwsIotDefenderEncoder.appendKeyValue( &headerMap, + _VERSION_TAG, + IotSerializer_ScalarTextString( _VERSION_1_0 ) ); + assertNoError( serializerError ); + + /* Close the "header" map. */ + serializerError = _AwsIotDefenderEncoder.closeContainer( &reportMap, &headerMap ); + assertNoError( serializerError ); + + /* Count how many metrics groups user specified. */ + for( i = 0; i < _DEFENDER_METRICS_GROUP_COUNT; i++ ) + { + metricsGroupCount += _metricsFlagSnapshot[ i ] > 0; + } + + /* Create the "metrics" map with number of keys as the number of metrics groups. */ + serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &reportMap, + _METRICS_TAG, + &metricsMap, + metricsGroupCount ); + assertNoError( serializerError ); + + for( i = 0; i < _DEFENDER_METRICS_GROUP_COUNT; i++ ) + { + /* Skip if this metrics group has 0 metrics flag. */ + if( _metricsFlagSnapshot[ i ] ) + { + switch( i ) + { + case AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS: + _serializeTcpConnections( &metricsMap ); + break; + + default: + /* The index of metricsFlagSnapshot must be one of the metrics group. */ + AwsIotDefender_Assert( 0 ); + } + } + } + + /* Close the "metrics" map. */ + serializerError = _AwsIotDefenderEncoder.closeContainer( &reportMap, &metricsMap ); + assertNoError( serializerError ); + + /* Close the "report" map. */ + serializerError = _AwsIotDefenderEncoder.closeContainer( pEncoderObject, &reportMap ); + assertNoError( serializerError ); +} + +/*-----------------------------------------------------------*/ + +static void copyMetricsFlag() +{ + /* Copy the metrics flags to snapshot so that it is unlocked quicker. */ + IotMutex_Lock( &_AwsIotDefenderMetrics.mutex ); + + /* Memory copy from the metricsFlag array to metricsFlagSnapshot array. */ + memcpy( _metricsFlagSnapshot, _AwsIotDefenderMetrics.metricsFlag, sizeof( _metricsFlagSnapshot ) ); + + IotMutex_Unlock( &_AwsIotDefenderMetrics.mutex ); +} + +/*-----------------------------------------------------------*/ + +static AwsIotDefenderEventType_t getLatestMetricsData() +{ + AwsIotDefenderEventType_t eventError = 0; + + /* Get TCP connections metrics data. */ + if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) + { + IotMetricsListCallback_t tcpConnectionscallback; + + + tcpConnectionscallback.function = tcpConnectionsCallback; + tcpConnectionscallback.param1 = ( void * ) &eventError; + + IotMetrics_ProcessTcpConnections( tcpConnectionscallback ); + } + + return eventError; +} + +/*-----------------------------------------------------------*/ + +static void freeMetricsData() +{ + if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) + { + IotMetrics_FreeTcpConnection( _metrics.tcpConns.pArray ); + _metrics.tcpConns.pArray = NULL; + _metrics.tcpConns.count = 0; + } +} + +/*-----------------------------------------------------------*/ + +static void tcpConnectionsCallback( void * param1, + IotListDouble_t * pTcpConnectionsMetricsList ) +{ + AwsIotDefenderEventType_t * pEventError = ( AwsIotDefenderEventType_t * ) param1; + + IotLink_t * pConnectionLink = IotListDouble_PeekHead( pTcpConnectionsMetricsList ); + IotMetricsTcpConnection_t * pConnection = NULL; + + uint32_t i = 0; + size_t total = IotListDouble_Count( pTcpConnectionsMetricsList ); + + /* If there is at least one TCP connection. */ + if( total > 0 ) + { + /* Allocate memory to copy TCP connections metrics data. */ + _metrics.tcpConns.pArray = IotMetrics_MallocTcpConnection( total * sizeof( IotMetricsTcpConnection_t ) ); + + if( _metrics.tcpConns.pArray != NULL ) + { + /* Set count only the memory allocation succeeds. */ + _metrics.tcpConns.count = ( uint8_t ) total; + + /* At least one element in the list*/ + AwsIotDefender_Assert( pConnectionLink ); + + for( i = 0; i < total; i++ ) + { + pConnection = IotLink_Container( IotMetricsTcpConnection_t, pConnectionLink, link ); + + /* Copy to new allocated array. */ + _metrics.tcpConns.pArray[ i ] = *pConnection; + + /* Iterate to next one. */ + pConnectionLink = pConnectionLink->pNext; + } + } + else + { + *pEventError = AWS_IOT_DEFENDER_EVENT_NO_MEMORY; + } + } +} + +/*-----------------------------------------------------------*/ + +static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObject ) +{ + AwsIotDefender_Assert( pMetricsObject != NULL ); + + IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; + + IotSerializerEncoderObject_t tcpConnectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t establishedMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerEncoderObject_t connectionsArray = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; + + uint32_t tcpConnFlag = _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ]; + + uint8_t hasEstablishedConnections = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) > 0; + /* Whether "connections" should show up is not only determined by user input, but also if there is at least 1 connection. */ + uint8_t hasConnections = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) > 0 && + ( _metrics.tcpConns.count > 0 ); + uint8_t hasTotal = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) > 0; + uint8_t hasRemoteAddr = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) > 0; + + char remoteAddr[ _REMOTE_ADDR_LENGTH ] = ""; + char * pRemoteAddr = remoteAddr; + + void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall + : assertSuccess; + + uint32_t i = 0; + + /* Create the "tcp_connections" map with 1 key "established_connections" */ + serializerError = _AwsIotDefenderEncoder.openContainerWithKey( pMetricsObject, + _TCP_CONN_TAG, + &tcpConnectionMap, + 1 ); + assertNoError( serializerError ); + + /* if user specify any metrics under "established_connections" */ + if( hasEstablishedConnections ) + { + /* Create the "established_connections" map with "total" and/or "connections". */ + serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &tcpConnectionMap, + _EST_CONN_TAG, + &establishedMap, + hasConnections + hasTotal ); + assertNoError( serializerError ); + + /* if user specify any metrics under "connections" and there are at least one connection */ + if( hasConnections ) + { + AwsIotDefender_Assert( _metrics.tcpConns.pArray != NULL ); + + /* create array "connections" under "established_connections" */ + serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &establishedMap, + _CONN_TAG, + &connectionsArray, + _metrics.tcpConns.count ); + assertNoError( serializerError ); + + for( i = 0; i < _metrics.tcpConns.count; i++ ) + { + IotSerializerEncoderObject_t connectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + + /* open a map under "connections" */ + serializerError = _AwsIotDefenderEncoder.openContainer( &connectionsArray, + &connectionMap, + hasRemoteAddr ); + assertNoError( serializerError ); + + /* add remote address */ + if( hasRemoteAddr ) + { + /* Remote IP is with host endian. So it is converted to network endian and passed into SOCKETS_inet_ntoa. */ + struct in_addr remoteInAddr = { .s_addr = _metrics.tcpConns.pArray[ i ].remoteIP }; + char * pRemoteIp = inet_ntoa( remoteInAddr ); + sprintf( remoteAddr, "%s:%d", pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); + + serializerError = _AwsIotDefenderEncoder.appendKeyValue( &connectionMap, _REMOTE_ADDR_TAG, + IotSerializer_ScalarTextString( pRemoteAddr ) ); + assertNoError( serializerError ); + } + + serializerError = _AwsIotDefenderEncoder.closeContainer( &connectionsArray, &connectionMap ); + assertNoError( serializerError ); + } + + serializerError = _AwsIotDefenderEncoder.closeContainer( &establishedMap, &connectionsArray ); + assertNoError( serializerError ); + } + + if( hasTotal ) + { + serializerError = _AwsIotDefenderEncoder.appendKeyValue( &establishedMap, + _TOTAL_TAG, + IotSerializer_ScalarSignedInt( _metrics.tcpConns.count ) ); + assertNoError( serializerError ); + } + + serializerError = _AwsIotDefenderEncoder.closeContainer( &tcpConnectionMap, &establishedMap ); + assertNoError( serializerError ); + } + + serializerError = _AwsIotDefenderEncoder.closeContainer( pMetricsObject, &tcpConnectionMap ); + assertNoError( serializerError ); +} diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c new file mode 100644 index 0000000000..99007cced4 --- /dev/null +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Defender internal include. */ +#include "private/aws_iot_defender_internal.h" + +/* + * 30 seconds is the minimum allowed in AFR MQTT + */ +#define _DEFENDER_MQTT_KEEP_ALIVE_SECONDS 30 + +/* Define topics segments used by defender. */ +#define _TOPIC_PREFIX "$aws/things/" + +#define _TOPIC_SUFFIX_PUBLISH "/defender/metrics/" _DEFENDER_FORMAT + +#define _TOPIC_SUFFIX_ACCEPTED _TOPIC_SUFFIX_PUBLISH "/accepted" + +#define _TOPIC_SUFFIX_REJECTED _TOPIC_SUFFIX_PUBLISH "/rejected" + +static IotNetworkInterface_t _networkInterface; + +/* defender internally manages network and mqtt connection */ +static IotNetworkConnectionOpenssl_t _networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +static char * _pPublishTopic = NULL; + +static char * _pAcceptTopic = NULL; + +static char * _pRejectTopic = NULL; + +/*-----------------------------------------------------------*/ + +AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( const char * pThingName, + uint16_t thingNameLength ) +{ + AwsIotDefenderError_t returnedError = AWS_IOT_DEFENDER_SUCCESS; + + /* Calculate topics lengths. Plus one for string terminator. */ + size_t publishTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_PUBLISH ) + 1; + size_t acceptTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_ACCEPTED ) + 1; + size_t rejectTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_REJECTED ) + 1; + + /* Allocate memory for each of them. */ + char * pPublishTopic = AwsIotDefender_MallocTopic( publishTopicLength * sizeof( char ) ); + char * pAcceptTopic = AwsIotDefender_MallocTopic( acceptTopicLength * sizeof( char ) ); + char * pRejectTopic = AwsIotDefender_MallocTopic( rejectTopicLength * sizeof( char ) ); + + /* Free memory if any allocation failed. */ + if( ( pPublishTopic == NULL ) || ( pAcceptTopic == NULL ) || ( pRejectTopic == NULL ) ) + { + /* Null pointer is safe for "free" function. */ + AwsIotDefender_FreeTopic( pPublishTopic ); + AwsIotDefender_FreeTopic( pAcceptTopic ); + AwsIotDefender_FreeTopic( pRejectTopic ); + returnedError = AWS_IOT_DEFENDER_ERROR_NO_MEMORY; + } + else + { + _pPublishTopic = pPublishTopic; + _pAcceptTopic = pAcceptTopic; + _pRejectTopic = pRejectTopic; + + strcpy( _pPublishTopic, _TOPIC_PREFIX ); + strncat( _pPublishTopic, pThingName, thingNameLength ); + strcat( _pPublishTopic, _TOPIC_SUFFIX_PUBLISH ); + + strcpy( _pAcceptTopic, _TOPIC_PREFIX ); + strncat( _pAcceptTopic, pThingName, thingNameLength ); + strcat( _pAcceptTopic, _TOPIC_SUFFIX_ACCEPTED ); + + strcpy( _pRejectTopic, _TOPIC_PREFIX ); + strncat( _pRejectTopic, pThingName, thingNameLength ); + strcat( _pRejectTopic, _TOPIC_SUFFIX_REJECTED ); + } + + return returnedError; +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_DeleteTopicsNames() +{ + AwsIotDefender_FreeTopic( _pPublishTopic ); + AwsIotDefender_FreeTopic( _pAcceptTopic ); + AwsIotDefender_FreeTopic( _pRejectTopic ); + _pPublishTopic = NULL; + _pAcceptTopic = NULL; + _pRejectTopic = NULL; +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_SetNetworkInterface() +{ + _networkInterface = _IotNetworkOpenssl; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotDefenderInternal_NetworkConnect( IotNetworkServerInfoOpenssl_t * pServerInfo, + IotNetworkCredentialsOpenssl_t * pCredentials ) +{ + _networkInterface = _IotNetworkOpenssl; + + return _networkInterface.create( pServerInfo, pCredentials, &_networkConnection ) == IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_SetMqttCallback() +{ + IotNetworkError_t error = _networkInterface.setReceiveCallback( &_networkConnection, + IotMqtt_ReceiveCallback, + &_mqttConnection ); + + AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); +} + +/*-----------------------------------------------------------*/ + +bool AwsIotDefenderInternal_MqttConnect( const char * pThingName, + uint16_t thingNameLength ) +{ + IotMqttNetIf_t mqttNetInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + + mqttNetInterface.pDisconnectContext = &_networkConnection; + mqttNetInterface.pSendContext = &_networkConnection; + mqttNetInterface.disconnect = _networkInterface.close; + mqttNetInterface.send = _networkInterface.send; + + /* Set the members of the connection info (password and username not used). */ + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = _DEFENDER_MQTT_KEEP_ALIVE_SECONDS; + connectInfo.pClientIdentifier = pThingName; + connectInfo.clientIdentifierLength = thingNameLength; + + return IotMqtt_Connect( &_mqttConnection, + &mqttNetInterface, + &connectInfo, + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, + IotMqttCallbackInfo_t rejectCallback ) +{ + /* subscribe to two topics: accept and reject. */ + IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + subscriptions[ 0 ].qos = 0; + subscriptions[ 0 ].pTopicFilter = _pAcceptTopic; + subscriptions[ 0 ].topicFilterLength = ( uint16_t ) strlen( _pAcceptTopic ); + subscriptions[ 0 ].callback = acceptCallback; + + subscriptions[ 1 ].qos = 0; + subscriptions[ 1 ].pTopicFilter = _pRejectTopic; + subscriptions[ 1 ].topicFilterLength = ( uint16_t ) strlen( _pRejectTopic ); + subscriptions[ 1 ].callback = rejectCallback; + + return IotMqtt_TimedSubscribe( _mqttConnection, + subscriptions, + 2, + 0, + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +bool AwsIotDefenderInternal_MqttPublish( uint8_t * pData, + size_t dataLength ) +{ + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + publishInfo.qos = 0; + publishInfo.pTopicName = _pPublishTopic; + publishInfo.topicNameLength = ( uint16_t ) strlen( _pPublishTopic ); + publishInfo.pPayload = pData; + publishInfo.payloadLength = dataLength; + + return IotMqtt_TimedPublish( _mqttConnection, + &publishInfo, + 0, + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_MqttDisconnect() +{ + IotMqtt_Disconnect( _mqttConnection, false ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_NetworkClose() +{ + IotNetworkError_t error = _networkInterface.close( &_networkConnection ); + + AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); +} + +/*-----------------------------------------------------------*/ + +void AwsIotDefenderInternal_NetworkDestroy() +{ + IotNetworkError_t error = _networkInterface.destroy( &_networkConnection ); + + AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); + + _networkInterface = ( IotNetworkInterface_t ) { + 0 + }; +} diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c index b834f29a47..79a8fce45d 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c @@ -358,7 +358,6 @@ static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject if( pNewCborValueWrapper->cborValue.type == CborInvalidType ) { /* pValueObject is untouched. */ - returnedError = IOT_SERIALIZER_NOT_FOUND; } else @@ -369,6 +368,12 @@ static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject _translateErrorCode( cborError, &returnedError ); + /* If there is any error or decoder object is a scalar type, free the cbor resources. */ + if( returnedError || !_isArrayOrMap( pValueObject->type ) ) + { + IotSerializer_FreeCborValue( pNewCborValueWrapper ); + } + return returnedError; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6cef9e0ecc..bc94d35435 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,3 +14,6 @@ add_subdirectory( shadow ) # Serializer tests. add_subdirectory( serializer ) + +# Defender tests. +add_subdirectory( defender ) diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt new file mode 100644 index 0000000000..7091652437 --- /dev/null +++ b/tests/defender/CMakeLists.txt @@ -0,0 +1,7 @@ +# Shadow tests executable. +add_executable( aws_iot_tests_defender + aws_iot_tests_defender.c + aws_iot_tests_defender_api.c ) + +# Shadow tests library dependencies. +target_link_libraries( aws_iot_tests_defender iotcommon iotplatform iotmqtt awsiotdefender unity ) \ No newline at end of file diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c new file mode 100644 index 0000000000..7ea16372ee --- /dev/null +++ b/tests/defender/aws_iot_tests_defender.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Common include. */ +#include "iot_common.h" + +#include "iot_mqtt.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler. Terminates the tests if called. + */ +static void _signalHandler( int signum ) +{ + /* Immediately terminate the tests if this signal handler is called. */ + if( signum == SIGSEGV ) + { + printf( "\nSegmentation fault.\n" ); + exit( EXIT_FAILURE ); + } + else if( signum == SIGABRT ) + { + printf( "\nAssertion failed.\n" ); + exit( EXIT_FAILURE ); + } +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + struct sigaction signalAction; + + /* Set a signal handler for segmentation faults and assertion failures. */ + ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); + signalAction.sa_handler = _signalHandler; + + if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) + { + return EXIT_FAILURE; + } + + if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + { + return EXIT_FAILURE; + } + + /* Initialize the common libraries before running the tests. */ + if( IotCommon_Init() == false ) + { + return EXIT_FAILURE; + } + + /* Initialize the MQTT library before running the tests. */ + if (IotMqtt_Init() != IOT_MQTT_SUCCESS) + { + return EXIT_FAILURE; + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Run short tests. */ + RUN_TEST_GROUP(Full_DEFENDER); + + /* Clean up common libraries. */ + IotCommon_Cleanup(); + + /* Clean up MQTT library. */ + IotMqtt_Cleanup(); + + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/*-----------------------------------------------------------*/ diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c new file mode 100644 index 0000000000..10d8a0a7ac --- /dev/null +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -0,0 +1,880 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* Network includes. */ +#include +#include + +/* Defender internal includes. */ +#include "private/aws_iot_defender_internal.h" + +/* Serializer includes. */ +#include "iot_serializer.h" + +#include "cbor.h" + +/* Metrics includes. */ +#include "iot_metrics.h" + +#include "unity_fixture.h" + +/* Time interval to wait for a state to be true. */ +#define _WAIT_STATE_INTERVAL_SECONDS 1 + +/* Total time to wait for a state to be true. */ +#define _WAIT_STATE_TOTAL_SECONDS 5 + +/* Time interval for defender agent to publish metrics. It will be throttled if too frequent. */ +/* TODO: if we can change "thingname" in each test, this can be lowered. */ +#define _DEFENDER_PUBLISH_INTERVAL_SECONDS 15 + +/* Estimated max size of message payload received in MQTT callback. */ +#define _PAYLOAD_MAX_SIZE 200 + +/* Estimated max size of metrics report defender published. */ +#define _METRICS_MAX_SIZE 200 + +/* Max size of address: IP + port. */ +#define _MAX_ADDRESS_LENGTH 25 + +/* Define a decoder based on chosen format. */ +#if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR + + #define _Decoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ + +#elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON + + #define _Decoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ + +#endif + +/* Empty callback structure passed to startInfo. */ +static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .param1 = NULL }; + +/*------------------ global variables -----------------------------*/ + +static uint8_t _payloadBuffer[ _PAYLOAD_MAX_SIZE ]; +static uint8_t _metricsBuffer[ _METRICS_MAX_SIZE ]; + +static AwsIotDefenderCallback_t _testCallback; + +static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + +static AwsIotDefenderCallbackInfo_t _callbackInfo; + +static IotSerializerDecoderObject_t _decoderObject; +static IotSerializerDecoderObject_t _metricsObject; + +/*------------------ Functions -----------------------------*/ + +/* Copy data from MQTT callback to local buffer. */ +static void _copyDataCallbackFunction( void * param1, + AwsIotDefenderCallbackInfo_t * const pCallbackInfo ); + +static void _waitForAnyEvent( uint32_t timeoutSec ); + +static void _assertEvent( AwsIotDefenderEventType_t event, + uint32_t timeoutSec ); + +/* Wait for metrics to be accepted by defender service, for maxinum timeout. */ +static void _waitForMetricsAccepted( uint32_t timeoutSec ); + +/* Verify common section of metrics report. */ +static void _verifyMetricsCommon(); + +/* Verify tcp connections in metrics report. */ +static void _verifyTcpConections( int total, + ... ); + +/* Indicate this test doesn't actually publish report. */ +static void _publishMetricsNotNeeded(); + +static void _resetCalbackInfo(); + +static char * _getIotAddress(); + +TEST_GROUP( Full_DEFENDER ); + +TEST_SETUP( Full_DEFENDER ) +{ + _resetCalbackInfo(); + + _decoderObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + _metricsObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + /* Reset test callback. */ + _testCallback = ( AwsIotDefenderCallback_t ) { + .function = _copyDataCallbackFunction, .param1 = NULL + }; + + /* Setup startInfo. */ + _startInfo.serverInfo.pHostName = IOT_TEST_SERVER; + _startInfo.serverInfo.port = IOT_TEST_PORT; + _startInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; + _startInfo.thingNameLength = strlen( AWS_IOT_TEST_SHADOW_THING_NAME ); + _startInfo.callback = _EMPTY_CALLBACK; + + /* Setup TLS information. */ + _startInfo.credentials = ( IotNetworkCredentialsOpenssl_t ) IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +} + +TEST_TEAR_DOWN( Full_DEFENDER ) +{ + AwsIotDefender_Stop(); + + /* Actually get defender callback. */ + if( ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_ACCEPTED ) || + ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) ) + { + sleep( _DEFENDER_PUBLISH_INTERVAL_SECONDS ); + } +} + +TEST_GROUP_RUNNER( Full_DEFENDER ) +{ + /* + * Setup: none + * Action: call Start API with invliad IoT endpoint + * Expectation: Start API returns network connection failure + */ + RUN_TEST_CASE( Full_DEFENDER, Start_with_wrong_network_information ); + + /* + * Setup: defender not started yet + * Action: call SetMetrics API with an invalid big integer as metrics group + * Expectation: + * - SetMetrics API return invalid input + * - global metrics flag array are untouched + */ + RUN_TEST_CASE( Full_DEFENDER, SetMetrics_with_invalid_metrics_group ); + + /* + * Setup: defender not started yet + * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value + * Expectation: + * - SetMetrics API return success + * - global metrics flag array are updated correctly + */ + RUN_TEST_CASE( Full_DEFENDER, SetMetrics_with_TCP_connections_all ); + + /* + * Setup: defender is started + * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value + * Expectation: + * - SetMetrics API return success + * - global metrics flag array are updated correctly + */ + RUN_TEST_CASE( Full_DEFENDER, SetMetrics_after_defender_started ); + + /* + * Setup: defender not started yet + * Action: call SetPeriod API with small value less than 300 + * Expectation: + * - SetPeriod API return "period too short" error + */ + /*RUN_TEST_CASE( Full_DEFENDER, SetPeriod_too_short ); */ + + /* + * Setup: defender not started yet + * Action: call SetPeriod API with 301 + * Expectation: + * - SetPeriod API return success + */ + RUN_TEST_CASE( Full_DEFENDER, SetPeriod_with_proper_value ); + + /* + * Setup: defender is started + * Action: call SetPeriod API with 600 + * Expectation: + * - SetPeriod API return success + */ + RUN_TEST_CASE( Full_DEFENDER, SetPeriod_after_started ); + + /* + * Setup: kept from publishing metrics report + * Action: call Start API with correct network information + * Expectation: Start API return success + */ + RUN_TEST_CASE( Full_DEFENDER, Start_should_return_success ); + + /* + * Setup: call Start API the first time; kept from publishing metrics report + * Action: call Start API second time + * Expectation: Start API return "already started" error + */ + RUN_TEST_CASE( Full_DEFENDER, Start_should_return_err_if_already_started ); + + /* + * Setup: not set any metrics; register test callback + * Action: call Start API + * Expectation: metrics are accepted by defender service + */ + RUN_TEST_CASE( Full_DEFENDER, Metrics_empty_are_published ); + + /* + * Setup: set "tcp connections" with "all metrics"; register test callback + * Action: call Start API + * Expectation: + * - metrics are accepted by defender service + * - verify metrics report has correct content + */ + RUN_TEST_CASE( Full_DEFENDER, Metrics_TCP_connections_all_are_published ); + + /* + * Setup: set "tcp connections" with "total count"; register test callback + * Action: call Start API + * Expectation: + * - metrics are accepted by defender service + * - verify metrics report has correct content + */ + RUN_TEST_CASE( Full_DEFENDER, Metrics_TCP_connections_total_are_published ); + + /* + * Setup: set "tcp connections" with "remote address"; register test callback + * Action: call Start API + * Expectation: + * - metrics are accepted by defender service + * - verify metrics report has correct content + */ + RUN_TEST_CASE( Full_DEFENDER, Metrics_TCP_connections_remote_addr_are_published ); + + /* + * Setup: set "tcp connections" with "total count"; register test callback; call Start API + * Action: call Stop API; set "tcp connections" with "all metrics"; call Start again + * Expectation: + * - metrics are accepted by defender service in both times + * - verify metrics report has correct content respectively in both times + */ + RUN_TEST_CASE( Full_DEFENDER, Restart_and_updated_metrics_are_published ); +} + +TEST( Full_DEFENDER, SetMetrics_with_invalid_metrics_group ) +{ + uint8_t i = 0; + + /* Input a dummy, invalid metrics group. */ + AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( 10000, + AWS_IOT_DEFENDER_METRICS_ALL ); + + /* SetMetrics should return "invalid input". */ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); + + /* Assert metrics flag in each metrics group remains 0. */ + for( i = 0; i < _DEFENDER_METRICS_GROUP_COUNT; i++ ) + { + TEST_ASSERT_EQUAL( 0, _AwsIotDefenderMetrics.metricsFlag[ i ] ); + } +} + +TEST( Full_DEFENDER, SetMetrics_with_TCP_connections_all ) +{ + /* Set "all metrics" for TCP connections metrics group. */ + AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_ALL ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); +} + +TEST( Full_DEFENDER, SetMetrics_after_defender_started ) +{ + _publishMetricsNotNeeded(); + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Set "all metrics" for TCP connections metrics group. */ + error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_ALL ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); +} + +TEST( Full_DEFENDER, Start_with_wrong_network_information ) +{ + _publishMetricsNotNeeded(); + + /* Set test callback to verify report. */ + _startInfo.callback = _testCallback; + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + _assertEvent( AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED, _WAIT_STATE_TOTAL_SECONDS ); +} + +TEST( Full_DEFENDER, Start_should_return_success ) +{ + _publishMetricsNotNeeded(); + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); +} + +TEST( Full_DEFENDER, Start_should_return_err_if_already_started ) +{ + _publishMetricsNotNeeded(); + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Start defender for a second time. */ + error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_ALREADY_STARTED, error ); +} + +TEST( Full_DEFENDER, Metrics_empty_are_published ) +{ + AwsIotDefenderError_t error; + + /* Set test callback to verify report. */ + _startInfo.callback = _testCallback; + + /* Start defender. */ + error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + + _verifyMetricsCommon(); + _verifyTcpConections( 0 ); +} + +TEST( Full_DEFENDER, Metrics_TCP_connections_all_are_published ) +{ + AwsIotDefenderError_t error; + + /* Set "all metrics" for TCP connections metrics group. */ + error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_ALL ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Set test callback to verify report. */ + _startInfo.callback = _testCallback; + + /* Get Iot address from DNS. */ + char * pIotAddress = _getIotAddress(); + + /* Start defender. */ + error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Wait certain time for _reportAccepted to be true. */ + _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + + _verifyMetricsCommon(); + _verifyTcpConections( 1, pIotAddress ); +} + +TEST( Full_DEFENDER, Metrics_TCP_connections_total_are_published ) +{ + AwsIotDefenderError_t error; + + /* Set "total count" for TCP connections metrics group. */ + error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Set test callback to verify report. */ + _startInfo.callback = _testCallback; + + /* Start defender. */ + error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Wait certain time for _reportAccepted to be true. */ + _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + + _verifyMetricsCommon(); + _verifyTcpConections( 1 ); +} + +TEST( Full_DEFENDER, Metrics_TCP_connections_remote_addr_are_published ) +{ + AwsIotDefenderError_t error; + + /* Set "remote address" for TCP connections metrics group. */ + error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Set test callback to verify report. */ + _startInfo.callback = _testCallback; + + /* Get Iot address from DNS. */ + char * pIotAddress = _getIotAddress(); + + /* Start defender. */ + error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Wait certain time for _reportAccepted to be true. */ + _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + + _verifyMetricsCommon(); + _verifyTcpConections( 1, pIotAddress ); +} + +TEST( Full_DEFENDER, Restart_and_updated_metrics_are_published ) +{ + char * pIotAddress = NULL; + + /* Set "total count" for TCP connections metrics group. */ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, + AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) ); + + /* Set test callback to verify report. */ + _startInfo.callback = _testCallback; + + pIotAddress = _getIotAddress(); + + /* Start defender. */ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); + + /* Wait certain time for _reportAccepted to be true. */ + _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + + _verifyMetricsCommon(); + _verifyTcpConections( 1, pIotAddress ); + + AwsIotDefender_Stop(); + + /* Reset _callbackInfo before restarting. */ + _resetCalbackInfo(); + + sleep( _DEFENDER_PUBLISH_INTERVAL_SECONDS ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, + AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ) ); + + pIotAddress = _getIotAddress(); + + /* Restart defender. */ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); + + /* Wait certain time for _reportAccepted to be true. */ + _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + + _verifyMetricsCommon(); + _verifyTcpConections( 1, pIotAddress ); +} + +TEST( Full_DEFENDER, SetPeriod_too_short ) +{ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, AwsIotDefender_SetPeriod( 299 ) ); +} + +TEST( Full_DEFENDER, SetPeriod_with_proper_value ) +{ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 301 ) ); + + TEST_ASSERT_EQUAL( 301, AwsIotDefender_GetPeriod() ); +} + +TEST( Full_DEFENDER, SetPeriod_after_started ) +{ + _publishMetricsNotNeeded(); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, + AwsIotDefender_Start( &_startInfo ) ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 600 ) ); + + TEST_ASSERT_EQUAL( 600, AwsIotDefender_GetPeriod() ); +} + +/*-----------------------------------------------------------*/ + +static void _copyDataCallbackFunction( void * param1, + AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) +{ + /* Print out rejected message to stdout. */ + if( pCallbackInfo->eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) + { + CborParser cborParser; + CborValue cborValue; + cbor_parser_init( pCallbackInfo->pPayload, pCallbackInfo->payloadLength, 0, &cborParser, &cborValue ); + cbor_value_to_pretty( stdout, &cborValue ); + } + + /* Copy data from pCallbackInfo to _callbackInfo. */ + if( pCallbackInfo != NULL ) + { + _callbackInfo.eventType = pCallbackInfo->eventType; + _callbackInfo.metricsReportLength = pCallbackInfo->metricsReportLength; + _callbackInfo.payloadLength = pCallbackInfo->payloadLength; + + if( _callbackInfo.payloadLength > 0 ) + { + memcpy( ( uint8_t * ) _callbackInfo.pPayload, pCallbackInfo->pPayload, _callbackInfo.payloadLength ); + } + + if( _callbackInfo.metricsReportLength > 0 ) + { + memcpy( ( uint8_t * ) _callbackInfo.pMetricsReport, pCallbackInfo->pMetricsReport, _callbackInfo.metricsReportLength ); + } + } +} + +/*-----------------------------------------------------------*/ + +static void _publishMetricsNotNeeded() +{ + /*_startInfo.pThingName = "dummy-thing-for-test"; */ + /*_startInfo.thingNameLength = ( uint16_t ) strlen( "dummy-thing-for-test" ); */ + + /* Given a dummy IoT endpoint to fail network connection. */ + _startInfo.serverInfo.pHostName = "dummy endpoint"; +} + +/*-----------------------------------------------------------*/ + +static void _resetCalbackInfo() +{ + /* Clean data buffer. */ + memset( _payloadBuffer, 0, _PAYLOAD_MAX_SIZE ); + memset( _metricsBuffer, 0, _METRICS_MAX_SIZE ); + + /* Reset callback info. */ + _callbackInfo = ( AwsIotDefenderCallbackInfo_t ) { + .pMetricsReport = _metricsBuffer, + .metricsReportLength = 0, + .pPayload = _payloadBuffer, + .payloadLength = 0, + .eventType = -1 /* Initialize to -1 to indicate there is no event. */ + }; +} + +/*-----------------------------------------------------------*/ + +static void _waitForAnyEvent( uint32_t timeoutSec ) +{ + uint32_t maxIterations = timeoutSec / _WAIT_STATE_INTERVAL_SECONDS; + uint32_t iter = 1; + + /* Wait for an event type to be set. */ + while( _callbackInfo.eventType == -1 ) + { + if( iter > maxIterations ) + { + /* Timeout. */ + TEST_FAIL_MESSAGE( "No event has happened after max timeout." ); + } + + sleep( _WAIT_STATE_INTERVAL_SECONDS ); + + iter++; + } +} + +/*-----------------------------------------------------------*/ + +static void _assertEvent( AwsIotDefenderEventType_t event, + uint32_t timeoutSec ) +{ + _waitForAnyEvent( timeoutSec ); + + TEST_ASSERT_EQUAL( event, _callbackInfo.eventType ); +} + +/*-----------------------------------------------------------*/ + +/* Assert the cause of rejection is throttle. */ +static void _assertRejectDueToThrottle() +{ + TEST_ASSERT_NOT_NULL( _callbackInfo.pPayload ); + TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.payloadLength ); + + IotSerializerDecoderObject_t decoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t statusDetailsObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t errorCodeObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + char errorCode[ 12 ] = ""; + + IotSerializerError_t error = _Decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, decoderObject.type ); + + error = _Decoder.find( &decoderObject, "statusDetails", &statusDetailsObject ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, statusDetailsObject.type ); + + errorCodeObject.value.pString = ( uint8_t * ) errorCode; + errorCodeObject.value.stringLength = 12; + + error = _Decoder.find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, errorCodeObject.type ); + + TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) errorCodeObject.value.pString, "Throttled", errorCodeObject.value.stringLength ) ); + + _Decoder.destroy( &statusDetailsObject ); + _Decoder.destroy( &decoderObject ); +} + +/*-----------------------------------------------------------*/ + +static void _waitForMetricsAccepted( uint32_t timeoutSec ) +{ + _waitForAnyEvent( timeoutSec ); + + if( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) + { + _assertRejectDueToThrottle(); + + return; + } + + /* Assert metrics is accepted. */ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ACCEPTED, _callbackInfo.eventType ); + + TEST_ASSERT_NOT_NULL( _callbackInfo.pPayload ); + TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.payloadLength ); + + IotSerializerDecoderObject_t decoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + IotSerializerError_t error = _Decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, decoderObject.type ); + + IotSerializerDecoderObject_t statusObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + char status[ 10 ] = ""; + statusObject.value.pString = ( uint8_t * ) status; + statusObject.value.stringLength = 10; + + error = _Decoder.find( &decoderObject, "status", &statusObject ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, statusObject.type ); + + TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) statusObject.value.pString, "ACCEPTED", statusObject.value.stringLength ) ); + + _Decoder.destroy( &statusObject ); + _Decoder.destroy( &decoderObject ); +} + +/*-----------------------------------------------------------*/ + +static void _verifyMetricsCommon() +{ + TEST_ASSERT_NOT_NULL( _callbackInfo.pMetricsReport ); + TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.metricsReportLength ); + + IotSerializerError_t error = _Decoder.init( &_decoderObject, _callbackInfo.pMetricsReport, _callbackInfo.metricsReportLength ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, _decoderObject.type ); + + error = _Decoder.find( &_decoderObject, "metrics", &_metricsObject ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, _metricsObject.type ); +} + +/*-----------------------------------------------------------*/ + +static void _verifyTcpConections( int total, + ... ) +{ + uint8_t i = 0; + + uint32_t tcpConnFlag = _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ]; + + /* Assert find a "tcp_connections" map in "metrics" */ + IotSerializerDecoderObject_t tcpConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + IotSerializerError_t error = _Decoder.find( &_metricsObject, "tcp_connections", &tcpConnObject ); + + /* If any TCP connections flag is specified. */ + if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_ALL ) + { + /* Assert found the "tcp_connections" map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, tcpConnObject.type ); + + IotSerializerDecoderObject_t estConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + error = _Decoder.find( &tcpConnObject, "established_connections", &estConnObject ); + + if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) + { + /* Assert found a "established_connections" map in "tcp_connections" */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, estConnObject.type ); + + IotSerializerDecoderObject_t totalObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + error = _Decoder.find( &estConnObject, "total", &totalObject ); + + if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) + { + /* Assert find a "total" integer with value 1 in "established_connections" */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_SIGNED_INT, totalObject.type ); + + TEST_ASSERT_EQUAL( total, totalObject.value.signedInt ); + } + else + { + /* Assert not found the "total". */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); + } + + IotSerializerDecoderObject_t connsObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderIterator_t connIterator = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; + + error = _Decoder.find( &estConnObject, "connections", &connsObject ); + + if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) + { + /* Assert find a "connections" array in "established_connections" */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_ARRAY, connsObject.type ); + + error = _Decoder.stepIn( &connsObject, &connIterator ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + /* Create argument list for expected remote addresses. */ + va_list valist; + va_start( valist, total ); + + for( i = 0; i < total; i++ ) + { + /* Assert find one "connection" map in "connections" */ + IotSerializerDecoderObject_t connMap = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + error = _Decoder.get( connIterator, &connMap ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, connMap.type ); + + IotSerializerDecoderObject_t remoteAddrObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + + error = _Decoder.find( &connMap, "remote_addr", &remoteAddrObject ); + + if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) + { + /* Assert find a "remote_addr" string in "connection" */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, remoteAddrObject.type ); + + /* Verify the passed address matching. */ + TEST_ASSERT_EQUAL_STRING_LEN( va_arg( valist, char * ), + remoteAddrObject.value.pString, + remoteAddrObject.value.stringLength ); + } + else + { + /* Assert not found the "remote_addr". */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); + } + + error = _Decoder.next( connIterator ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + } + + va_end( valist ); + + TEST_ASSERT_TRUE( _Decoder.isEndOfContainer( connIterator ) ); + + _Decoder.stepOut( connIterator, &connsObject ); + } + else + { + /* Assert not found the "connections". */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); + } + + _Decoder.destroy( &connsObject ); + } + else + { + /* Assert not found the "established_connections" map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); + } + + _Decoder.destroy( &estConnObject ); + } + else + { + /* Assert not found the "tcp_connections" map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); + } + + _Decoder.destroy( &tcpConnObject ); + _Decoder.destroy( &_metricsObject ); + _Decoder.destroy( &_decoderObject ); +} + +/*-----------------------------------------------------------*/ + +static char * _getIotAddress() +{ + static char iotAddress[ _MAX_ADDRESS_LENGTH ]; + + struct addrinfo * pListHead = NULL; + char * pIotAddressIp = NULL; + + /* Query DNS to get all the records. */ + getaddrinfo( IOT_TEST_SERVER, NULL, NULL, &pListHead ); + + /* Convert the first record to string format of IP. */ + pIotAddressIp = inet_ntoa( ( ( struct sockaddr_in * ) pListHead->ai_addr )->sin_addr ); + + sprintf( iotAddress, "%s:%d", pIotAddressIp, IOT_TEST_PORT ); + + return iotAddress; +} From 9febf33a4b619a24aeb5e99df60ef4184182d174 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 27 Feb 2019 13:41:22 -0800 Subject: [PATCH 036/844] Fix waitable MQTT operation notify. (#296) --- lib/source/mqtt/iot_mqtt_operation.c | 90 ++++++++++---------- platform/include/posix/iot_network_openssl.h | 6 +- tests/mqtt/system/iot_tests_mqtt_system.c | 2 +- 3 files changed, 51 insertions(+), 47 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index dcbb5941e3..8eb7a8b86b 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -1195,7 +1195,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { - IotMqttError_t status = IOT_MQTT_SUCCESS; + IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; /* Check if operation is waitable. */ @@ -1225,65 +1225,65 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) _EMPTY_ELSE_MARKER; } - /* Check if a callback function is set. */ - if( pOperation->notify.callback.function != NULL ) + /* For a waitable operation, post to the wait semaphore. */ + if( waitable == true ) { - /* A waitable operation may not have a callback. */ - IotMqtt_Assert( waitable == false ); + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " + "notified of completion.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + else + { /* Non-waitable operation should have job reference of 1. */ IotMqtt_Assert( pOperation->jobReference == 1 ); - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - /* Schedule an invocation of the callback. */ - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessCompletedOperation, - 0 ); - - if( status == IOT_MQTT_SUCCESS ) + if( pOperation->notify.callback.function != NULL ) { - IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), - pOperation ); + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - /* A completed operation should not be linked. */ - IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessCompletedOperation, + 0 ); - /* Place the scheduled operation back in the list of operations pending - * processing. */ - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - } - else - { - IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), - pOperation ); - } + if( status == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - else - { - /* For a waitable operation, post to the wait semaphore. */ - if( waitable == true ) - { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + /* A completed operation should not be linked. */ + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); - IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " - "notified of completion.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), - pOperation ); + /* Place the scheduled operation back in the list of operations pending + * processing. */ + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + } + else + { + IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->operation ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } else { _EMPTY_ELSE_MARKER; } + } + /* Operations that weren't scheduled may be destroyed. */ + if( status == IOT_MQTT_SCHEDULING_ERROR ) + { /* Decrement reference count of operations with no callback. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) { @@ -1294,6 +1294,10 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) _EMPTY_ELSE_MARKER; } } + else + { + IotMqtt_Assert( status == IOT_MQTT_SUCCESS ); + } } /*-----------------------------------------------------------*/ diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 058a2eaeb6..b78a7e2673 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -98,9 +98,9 @@ typedef struct IotNetworkServerInfoOpenssl * * Passed to #IotNetworkOpenssl_Create as `pCredentialInfo`. * - * All instances of #IotNetworkCredentialsOpenssl should be initialized with either - * #AWS_IOT_NETWORK_CREDENTIALS_OPENSSL (for connections to AWS IoT) or - * #IOT_NETWORK_CREDENTIALS_OPENSSL (for other connections). + * All instances of #IotNetworkCredentialsOpenssl_t should be initialized with either + * #AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER (for connections to AWS IoT) or + * #IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER (for other connections). */ typedef struct IotNetworkCredentialsOpenssl { diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index c1cb5cae80..13c89fc92b 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -570,10 +570,10 @@ TEST_TEAR_DOWN( MQTT_System ) { /* Clean up the MQTT library. */ IotMqtt_Cleanup(); - _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /* Clean up the network stack. */ IotTest_NetworkCleanup(); + _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ From 2f87984e50806e134ff8ef7020977cc7471855a8 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Thu, 28 Feb 2019 10:05:27 -0800 Subject: [PATCH 037/844] enable defender tests in commit build and fix warning (#297) --- lib/source/defender/aws_iot_defender_api.c | 22 +++++++++++++++------ scripts/build_check_commit.sh | 8 +++++++- tests/defender/CMakeLists.txt | 9 ++++++--- tests/defender/aws_iot_tests_defender.c | 4 ++++ tests/defender/aws_iot_tests_defender_api.c | 10 ++++++++-- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 892d93a5ac..855ef9902c 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -356,6 +356,11 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, void * pUserContext ) { + /* Unsed parameter; silence the compiler. */ + ( void )pTaskPool; + ( void )pJob; + ( void )pUserContext; + IotLogDebug( "Metrics publish job starts." ); if( !_started ) @@ -374,15 +379,15 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .param1 = NULL }; /* Step 1: connect to Iot endpoint. */ - if( networkConnected = AwsIotDefenderInternal_NetworkConnect( &_startInfo.serverInfo, - &_startInfo.credentials ) ) + if( ( networkConnected = AwsIotDefenderInternal_NetworkConnect( &_startInfo.serverInfo, + &_startInfo.credentials ) ) ) { /* Step 2: set MQTT callback. */ AwsIotDefenderInternal_SetMqttCallback(); /* Step 3: connect to MQTT. */ - if( mqttConnected = AwsIotDefenderInternal_MqttConnect( _startInfo.pThingName, - _startInfo.thingNameLength ) ) + if( ( mqttConnected = AwsIotDefenderInternal_MqttConnect( _startInfo.pThingName, + _startInfo.thingNameLength ) ) ) { /* Step 4: subscribe to accept/reject MQTT topics. */ if( AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, @@ -397,8 +402,8 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, reportCreated = true; /* Step 6: publish report to defender topic. */ - if( reportPublished = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), - AwsIotDefenderInternal_GetReportBufferSize() ) ) + if( ( reportPublished = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), + AwsIotDefenderInternal_GetReportBufferSize() ) ) ) { IotLogDebug( "Metrics report has been published successfully." ); } @@ -477,6 +482,11 @@ static void _disconnectRoutine( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, void * pUserContext ) { + /* Unsed parameter; silence the compiler. */ + ( void ) pTaskPool; + ( void ) pJob; + ( void ) pUserContext; + IotLogDebug( "Disconnect job starts." ); AwsIotDefenderInternal_DeleteReport(); diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh index 81291c8b1c..70865941d6 100644 --- a/scripts/build_check_commit.sh +++ b/scripts/build_check_commit.sh @@ -46,6 +46,9 @@ make # Run serializer tests ./bin/iot_tests_serializer +# Run defender tests +./bin/aws_iot_tests_defender + # Rebuild in static memory mode. rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" @@ -59,4 +62,7 @@ make ./bin/aws_iot_tests_shadow # Run serializer tests -./bin/iot_tests_serializer \ No newline at end of file +./bin/iot_tests_serializer + +# Run defender tests +./bin/aws_iot_tests_defender \ No newline at end of file diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt index 7091652437..de3763f12b 100644 --- a/tests/defender/CMakeLists.txt +++ b/tests/defender/CMakeLists.txt @@ -1,7 +1,10 @@ -# Shadow tests executable. +# Defender tests executable. add_executable( aws_iot_tests_defender aws_iot_tests_defender.c aws_iot_tests_defender_api.c ) -# Shadow tests library dependencies. -target_link_libraries( aws_iot_tests_defender iotcommon iotplatform iotmqtt awsiotdefender unity ) \ No newline at end of file +# Defender tests library dependencies. +target_link_libraries( aws_iot_tests_defender iotcommon iotplatform iotmqtt awsiotdefender unity ) + +# Not produce warning on missing braces case. +target_compile_options( aws_iot_tests_defender PRIVATE "-Wno-missing-braces") \ No newline at end of file diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index 7ea16372ee..20b8ddf5b8 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -63,6 +63,10 @@ int main( int argc, { struct sigaction signalAction; + /* Silence warnings about unused parameters. */ + ( void )argc; + ( void )argv; + /* Set a signal handler for segmentation faults and assertion failures. */ ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); signalAction.sa_handler = _signalHandler; diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 10d8a0a7ac..4dcf9723ad 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -61,6 +61,9 @@ /* Max size of address: IP + port. */ #define _MAX_ADDRESS_LENGTH 25 +/* Use a big number to represent no event happened in defender. */ +#define _NO_EVENT 10000 + /* Define a decoder based on chosen format. */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR @@ -527,6 +530,9 @@ TEST( Full_DEFENDER, SetPeriod_after_started ) static void _copyDataCallbackFunction( void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) { + /* Silence the compiler. */ + ( void ) param1; + /* Print out rejected message to stdout. */ if( pCallbackInfo->eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) { @@ -580,7 +586,7 @@ static void _resetCalbackInfo() .metricsReportLength = 0, .pPayload = _payloadBuffer, .payloadLength = 0, - .eventType = -1 /* Initialize to -1 to indicate there is no event. */ + .eventType = _NO_EVENT }; } @@ -592,7 +598,7 @@ static void _waitForAnyEvent( uint32_t timeoutSec ) uint32_t iter = 1; /* Wait for an event type to be set. */ - while( _callbackInfo.eventType == -1 ) + while( _callbackInfo.eventType == _NO_EVENT ) { if( iter > maxIterations ) { From a584bef9ba393259fc2f99b899fd026aadc48aef Mon Sep 17 00:00:00 2001 From: qiutongs Date: Thu, 28 Feb 2019 16:29:51 -0800 Subject: [PATCH 038/844] Qiutongs/defender test fix (#298) --- lib/include/aws_iot_defender.h | 31 ++++---- lib/include/platform/iot_network.h | 1 + .../private/aws_iot_defender_internal.h | 12 +-- lib/source/defender/aws_iot_defender_api.c | 41 +++++----- lib/source/defender/aws_iot_defender_mqtt.c | 75 +++++++------------ tests/defender/aws_iot_tests_defender.c | 15 +++- tests/defender/aws_iot_tests_defender_api.c | 40 +++++++--- 7 files changed, 111 insertions(+), 104 deletions(-) diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 396242a587..fd0eb9ac1f 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -54,9 +54,13 @@ /* Standard includes. */ #include +#include /* Network include. */ -#include "posix/iot_network_openssl.h" +#include "platform/iot_network.h" + +/* MQTT include. */ +#include "iot_mqtt.h" /** * @page Defender_constants Constants @@ -74,7 +78,7 @@ */ /**@{ */ #define AWS_IOT_DEFENDER_FORMAT_CBOR 1 /**< CBOR format. */ -#define AWS_IOT_DEFENDER_FORMAT_JSON 2 /**< JSON format. */ +#define AWS_IOT_DEFENDER_FORMAT_JSON 2 /**< JSON format (NOT supported). */ /**@} */ /** @@ -144,12 +148,12 @@ typedef enum */ typedef enum { - AWS_IOT_DEFENDER_SUCCESS = 0, /**< Defender operation completed successfully. */ - AWS_IOT_DEFENDER_INVALID_INPUT, /**< At least one input parameter is invalid. */ - AWS_IOT_DEFENDER_ALREADY_STARTED, /**< Defender has been already started. */ - AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, /**< Given period is too short. */ - AWS_IOT_DEFENDER_ERROR_NO_MEMORY, /**< Defender operation failed due to memory allocation failure. */ - AWS_IOT_DEFENDER_INTERNAL_FAILURE /**< Defender operation failed due to internal unexpected cause. */ + AWS_IOT_DEFENDER_SUCCESS = 0, /**< Defender operation completed successfully. */ + AWS_IOT_DEFENDER_INVALID_INPUT, /**< At least one input parameter is invalid. */ + AWS_IOT_DEFENDER_ALREADY_STARTED, /**< Defender has been already started. */ + AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, /**< Given period is too short. */ + AWS_IOT_DEFENDER_ERROR_NO_MEMORY, /**< Defender operation failed due to memory allocation failure. */ + AWS_IOT_DEFENDER_INTERNAL_FAILURE /**< Defender operation failed due to internal unexpected cause. */ } AwsIotDefenderError_t; /** @@ -206,11 +210,12 @@ typedef struct AwsIotDefenderCallback */ typedef struct AwsIotDefenderStartInfo { - IotNetworkServerInfoOpenssl_t serverInfo; - IotNetworkCredentialsOpenssl_t credentials; - const char * pThingName; /**< AWS IoT thing name(must be valid). */ - uint16_t thingNameLength; /**< Length of AWS IoT thing name(must be valid). */ - AwsIotDefenderCallback_t callback; /**< Length of AWS IoT thing name(optional). */ + void * pConnectionInfo; + void * pCredentialInfo; + void * pConnection; + const IotNetworkInterface_t * pNetworkInterface; + IotMqttConnectInfo_t mqttConnectionInfo; + AwsIotDefenderCallback_t callback; } AwsIotDefenderStartInfo_t; /** diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index c7debee0c9..c629a93f02 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -29,6 +29,7 @@ /* Standard includes. */ #include +#include /** * @ingroup platform_datatypes_enums diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index cb28f3f70f..8eef00de55 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -44,9 +44,6 @@ /* Double linked list include. */ #include "iot_linear_containers.h" -/* MQTT include. */ -#include "iot_mqtt.h" - /** * @def AwsIotDefender_Assert( expression ) * @brief Assertion macro for the Defender library. @@ -306,8 +303,7 @@ void AwsIotDefenderInternal_DeleteReport(); /** * Build three topics names used by defender library. */ -AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( const char * pThingName, - uint16_t thingNameLength ); +AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames(); /** * Free the memory of three topics names. @@ -317,8 +313,7 @@ void AwsIotDefenderInternal_DeleteTopicsNames(); /** * Creat a network connection. */ -bool AwsIotDefenderInternal_NetworkConnect( IotNetworkServerInfoOpenssl_t * pServerInfo, - IotNetworkCredentialsOpenssl_t * pCredentials ); +bool AwsIotDefenderInternal_NetworkConnect(); /** * Set the network connection to callback MQTT. @@ -328,8 +323,7 @@ void AwsIotDefenderInternal_SetMqttCallback(); /** * Connect to AWS with MQTT. */ -bool AwsIotDefenderInternal_MqttConnect( const char * pThingName, - uint16_t thingNameLength ); +bool AwsIotDefenderInternal_MqttConnect(); /** * Subscribe accept/reject defender topics. diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 855ef9902c..8cf0de741d 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -84,7 +84,7 @@ static IotTaskPoolJob_t _disconnectJob = { 0 }; static bool _started = false; /* Internal copy of startInfo so that user's input doesn't have to be valid all the time. */ -static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; +AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; /*-----------------------------------------------------------*/ @@ -122,7 +122,9 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ) { if( ( pStartInfo == NULL ) || - ( pStartInfo->pThingName == NULL ) ) + ( pStartInfo->pConnectionInfo == NULL ) || + ( pStartInfo->pCredentialInfo == NULL ) || + ( pStartInfo->pNetworkInterface == NULL ) ) { IotLogError( "Input start info is invalid." ); @@ -145,8 +147,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /* copy input start info into global variable _startInfo */ _startInfo = *pStartInfo; - defenderError = AwsIotDefenderInternal_BuildTopicsNames( _startInfo.pThingName, - _startInfo.thingNameLength ); + defenderError = AwsIotDefenderInternal_BuildTopicsNames(); buildTopicsNamesSuccess = ( defenderError == AWS_IOT_DEFENDER_SUCCESS ); @@ -175,9 +176,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn IotLogInfo( "Defender agent has successfully started." ); } - /* Do the cleanup jobs if not success. - * It is almost the same work as AwsIotDefender_Stop except here it must "clean" on condition. - */ + /* Do the cleanup jobs if not success. */ if( defenderError != AWS_IOT_DEFENDER_SUCCESS ) { /* reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ @@ -186,19 +185,19 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn if( buildTopicsNamesSuccess ) { AwsIotDefenderInternal_DeleteTopicsNames(); - - taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob ); - - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - - taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_disconnectJob ); - - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); } if( metricsMutexCreateSuccess ) { IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); + + taskPoolError = IotTaskPool_DestroyJob(IOT_SYSTEM_TASKPOOL, &_metricsPublishJob); + + AwsIotDefender_Assert(taskPoolError == IOT_TASKPOOL_SUCCESS); + + taskPoolError = IotTaskPool_DestroyJob(IOT_SYSTEM_TASKPOOL, &_disconnectJob); + + AwsIotDefender_Assert(taskPoolError == IOT_TASKPOOL_SUCCESS); } IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( defenderError ) ); @@ -357,9 +356,9 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, void * pUserContext ) { /* Unsed parameter; silence the compiler. */ - ( void )pTaskPool; - ( void )pJob; - ( void )pUserContext; + ( void ) pTaskPool; + ( void ) pJob; + ( void ) pUserContext; IotLogDebug( "Metrics publish job starts." ); @@ -379,15 +378,13 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .param1 = NULL }; /* Step 1: connect to Iot endpoint. */ - if( ( networkConnected = AwsIotDefenderInternal_NetworkConnect( &_startInfo.serverInfo, - &_startInfo.credentials ) ) ) + if( ( networkConnected = AwsIotDefenderInternal_NetworkConnect() ) ) { /* Step 2: set MQTT callback. */ AwsIotDefenderInternal_SetMqttCallback(); /* Step 3: connect to MQTT. */ - if( ( mqttConnected = AwsIotDefenderInternal_MqttConnect( _startInfo.pThingName, - _startInfo.thingNameLength ) ) ) + if( ( mqttConnected = AwsIotDefenderInternal_MqttConnect() ) ) { /* Step 4: subscribe to accept/reject MQTT topics. */ if( AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index 99007cced4..35b2c40c5a 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -19,27 +19,23 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include + /* Defender internal include. */ #include "private/aws_iot_defender_internal.h" -/* - * 30 seconds is the minimum allowed in AFR MQTT - */ -#define _DEFENDER_MQTT_KEEP_ALIVE_SECONDS 30 - /* Define topics segments used by defender. */ -#define _TOPIC_PREFIX "$aws/things/" +#define _TOPIC_PREFIX "$aws/things/" -#define _TOPIC_SUFFIX_PUBLISH "/defender/metrics/" _DEFENDER_FORMAT +#define _TOPIC_SUFFIX_PUBLISH "/defender/metrics/" _DEFENDER_FORMAT -#define _TOPIC_SUFFIX_ACCEPTED _TOPIC_SUFFIX_PUBLISH "/accepted" +#define _TOPIC_SUFFIX_ACCEPTED _TOPIC_SUFFIX_PUBLISH "/accepted" -#define _TOPIC_SUFFIX_REJECTED _TOPIC_SUFFIX_PUBLISH "/rejected" +#define _TOPIC_SUFFIX_REJECTED _TOPIC_SUFFIX_PUBLISH "/rejected" -static IotNetworkInterface_t _networkInterface; +extern AwsIotDefenderStartInfo_t _startInfo; /* defender internally manages network and mqtt connection */ -static IotNetworkConnectionOpenssl_t _networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; static char * _pPublishTopic = NULL; @@ -50,11 +46,13 @@ static char * _pRejectTopic = NULL; /*-----------------------------------------------------------*/ -AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( const char * pThingName, - uint16_t thingNameLength ) +AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames() { AwsIotDefenderError_t returnedError = AWS_IOT_DEFENDER_SUCCESS; + const char * pThingName = _startInfo.mqttConnectionInfo.pClientIdentifier; + uint16_t thingNameLength = _startInfo.mqttConnectionInfo.clientIdentifierLength; + /* Calculate topics lengths. Plus one for string terminator. */ size_t publishTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_PUBLISH ) + 1; size_t acceptTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_ACCEPTED ) + 1; @@ -110,54 +108,41 @@ void AwsIotDefenderInternal_DeleteTopicsNames() /*-----------------------------------------------------------*/ -void AwsIotDefenderInternal_SetNetworkInterface() -{ - _networkInterface = _IotNetworkOpenssl; -} - -/*-----------------------------------------------------------*/ - -bool AwsIotDefenderInternal_NetworkConnect( IotNetworkServerInfoOpenssl_t * pServerInfo, - IotNetworkCredentialsOpenssl_t * pCredentials ) +bool AwsIotDefenderInternal_NetworkConnect() { - _networkInterface = _IotNetworkOpenssl; - - return _networkInterface.create( pServerInfo, pCredentials, &_networkConnection ) == IOT_NETWORK_SUCCESS; + return _startInfo.pNetworkInterface->create( _startInfo.pConnectionInfo, + _startInfo.pCredentialInfo, + _startInfo.pConnection ) == IOT_NETWORK_SUCCESS; } /*-----------------------------------------------------------*/ void AwsIotDefenderInternal_SetMqttCallback() { - IotNetworkError_t error = _networkInterface.setReceiveCallback( &_networkConnection, - IotMqtt_ReceiveCallback, - &_mqttConnection ); + IotNetworkError_t error = _startInfo.pNetworkInterface->setReceiveCallback( _startInfo.pConnection, + IotMqtt_ReceiveCallback, + &_mqttConnection ); AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); } /*-----------------------------------------------------------*/ -bool AwsIotDefenderInternal_MqttConnect( const char * pThingName, - uint16_t thingNameLength ) +bool AwsIotDefenderInternal_MqttConnect() { IotMqttNetIf_t mqttNetInterface = IOT_MQTT_NETIF_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - mqttNetInterface.pDisconnectContext = &_networkConnection; - mqttNetInterface.pSendContext = &_networkConnection; - mqttNetInterface.disconnect = _networkInterface.close; - mqttNetInterface.send = _networkInterface.send; + const char * pThingName = _startInfo.mqttConnectionInfo.pClientIdentifier; + uint16_t thingNameLength = _startInfo.mqttConnectionInfo.clientIdentifierLength; - /* Set the members of the connection info (password and username not used). */ - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = _DEFENDER_MQTT_KEEP_ALIVE_SECONDS; - connectInfo.pClientIdentifier = pThingName; - connectInfo.clientIdentifierLength = thingNameLength; + mqttNetInterface.pDisconnectContext = _startInfo.pConnection; + mqttNetInterface.pSendContext = _startInfo.pConnection; + mqttNetInterface.disconnect = _startInfo.pNetworkInterface->close; + mqttNetInterface.send = _startInfo.pNetworkInterface->send; return IotMqtt_Connect( &_mqttConnection, &mqttNetInterface, - &connectInfo, + &_startInfo.mqttConnectionInfo, _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; } @@ -216,7 +201,7 @@ void AwsIotDefenderInternal_MqttDisconnect() void AwsIotDefenderInternal_NetworkClose() { - IotNetworkError_t error = _networkInterface.close( &_networkConnection ); + IotNetworkError_t error = _startInfo.pNetworkInterface->close( _startInfo.pConnection ); AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); } @@ -225,11 +210,7 @@ void AwsIotDefenderInternal_NetworkClose() void AwsIotDefenderInternal_NetworkDestroy() { - IotNetworkError_t error = _networkInterface.destroy( &_networkConnection ); + IotNetworkError_t error = _startInfo.pNetworkInterface->destroy( _startInfo.pConnection ); AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); - - _networkInterface = ( IotNetworkInterface_t ) { - 0 - }; } diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index 20b8ddf5b8..e4ff857eba 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -28,9 +28,13 @@ /* POSIX includes. */ #include +/* Network includes. */ +#include "posix/iot_network_openssl.h" + /* Common include. */ #include "iot_common.h" +/* MQTT include. */ #include "iot_mqtt.h" /* Test framework includes. */ @@ -87,8 +91,14 @@ int main( int argc, return EXIT_FAILURE; } + /* Set up the network stack. */ + if ( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) + { + return EXIT_FAILURE; + } + /* Initialize the MQTT library before running the tests. */ - if (IotMqtt_Init() != IOT_MQTT_SUCCESS) + if ( IotMqtt_Init() != IOT_MQTT_SUCCESS ) { return EXIT_FAILURE; } @@ -106,6 +116,9 @@ int main( int argc, /* Clean up common libraries. */ IotCommon_Cleanup(); + /* Clean up the network stack. */ + IotNetworkOpenssl_Cleanup(); + /* Clean up MQTT library. */ IotMqtt_Cleanup(); diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 4dcf9723ad..3b01e1190c 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -32,6 +32,9 @@ /* Defender internal includes. */ #include "private/aws_iot_defender_internal.h" +/* Openssl includes. */ +#include "posix/iot_network_openssl.h" + /* Serializer includes. */ #include "iot_serializer.h" @@ -78,6 +81,10 @@ /* Empty callback structure passed to startInfo. */ static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .param1 = NULL }; +static IotNetworkServerInfoOpenssl_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; +static IotNetworkCredentialsOpenssl_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +static IotNetworkConnectionOpenssl_t _networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; + /*------------------ global variables -----------------------------*/ static uint8_t _payloadBuffer[ _PAYLOAD_MAX_SIZE ]; @@ -134,15 +141,27 @@ TEST_SETUP( Full_DEFENDER ) .function = _copyDataCallbackFunction, .param1 = NULL }; - /* Setup startInfo. */ - _startInfo.serverInfo.pHostName = IOT_TEST_SERVER; - _startInfo.serverInfo.port = IOT_TEST_PORT; - _startInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - _startInfo.thingNameLength = strlen( AWS_IOT_TEST_SHADOW_THING_NAME ); - _startInfo.callback = _EMPTY_CALLBACK; + /* By default IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER enables ALPN. ALPN + * must be used with port 443; disable ALPN if another port is being used. */ + if( _serverInfo.port != 443 ) + { + _credential.pAlpnProtos = NULL; + } - /* Setup TLS information. */ - _startInfo.credentials = ( IotNetworkCredentialsOpenssl_t ) IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; + /* Reset server info. */ + _serverInfo = ( IotNetworkServerInfoOpenssl_t ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + + /* Set fields of start info. */ + _startInfo.pConnectionInfo = &_serverInfo; + _startInfo.pCredentialInfo = &_credential; + _startInfo.pConnection = &_networkConnection; + _startInfo.pNetworkInterface = IOT_NETWORK_INTERFACE_OPENSSL; + + _startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; + _startInfo.mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; + _startInfo.mqttConnectionInfo.clientIdentifierLength = strlen( AWS_IOT_TEST_SHADOW_THING_NAME ); + + _startInfo.callback = _EMPTY_CALLBACK; } TEST_TEAR_DOWN( Full_DEFENDER ) @@ -565,11 +584,8 @@ static void _copyDataCallbackFunction( void * param1, static void _publishMetricsNotNeeded() { - /*_startInfo.pThingName = "dummy-thing-for-test"; */ - /*_startInfo.thingNameLength = ( uint16_t ) strlen( "dummy-thing-for-test" ); */ - /* Given a dummy IoT endpoint to fail network connection. */ - _startInfo.serverInfo.pHostName = "dummy endpoint"; + _serverInfo.pHostName = "dummy endpoint"; } /*-----------------------------------------------------------*/ From 060c4ad5a41334bc5fa53a8a2032bbe58b300834 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Fri, 1 Mar 2019 14:54:44 -0800 Subject: [PATCH 039/844] add semaphore in defender tests to solve race condition (#302) --- lib/include/iot_serializer.h | 2 +- lib/source/defender/aws_iot_defender_api.c | 59 +++++++++++++-------- lib/source/defender/aws_iot_defender_mqtt.c | 3 -- tests/defender/aws_iot_tests_defender_api.c | 51 +++++++++--------- 4 files changed, 66 insertions(+), 49 deletions(-) diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 61cdc23988..6183dd2509 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -175,7 +175,7 @@ #define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_ARRAY } -#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED, .pHandle = NULL, .value = 0 } +#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED, 0 } #define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 8cf0de741d..b921822b4a 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -77,6 +77,8 @@ static IotTaskPoolJob_t _metricsPublishJob = { 0 }; static IotTaskPoolJob_t _disconnectJob = { 0 }; +static IotSemaphore_t _doneSem; + /* * State variable to indicate if defender has been started successfully, initialized to false. * There is no lock to protect it so the functions referencing it are not thread safe. @@ -140,6 +142,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /* Initialize flow control states to false. */ bool buildTopicsNamesSuccess = false, + doneSemaphoreCreateSuccess = false, metricsMutexCreateSuccess = false; if( !_started ) @@ -152,6 +155,12 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn buildTopicsNamesSuccess = ( defenderError == AWS_IOT_DEFENDER_SUCCESS ); if( buildTopicsNamesSuccess ) + { + /* Create a binary semaphore with initial value 1. */ + doneSemaphoreCreateSuccess = IotSemaphore_Create( &_doneSem, 1, 1 ); + } + + if( doneSemaphoreCreateSuccess ) { metricsMutexCreateSuccess = IotMutex_Create( &_AwsIotDefenderMetrics.mutex, false ); } @@ -187,17 +196,22 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn AwsIotDefenderInternal_DeleteTopicsNames(); } + if( doneSemaphoreCreateSuccess ) + { + IotSemaphore_Destroy( &_doneSem ); + } + if( metricsMutexCreateSuccess ) { IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); - taskPoolError = IotTaskPool_DestroyJob(IOT_SYSTEM_TASKPOOL, &_metricsPublishJob); + taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob ); - AwsIotDefender_Assert(taskPoolError == IOT_TASKPOOL_SUCCESS); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - taskPoolError = IotTaskPool_DestroyJob(IOT_SYSTEM_TASKPOOL, &_disconnectJob); + taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_disconnectJob ); - AwsIotDefender_Assert(taskPoolError == IOT_TASKPOOL_SUCCESS); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); } IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( defenderError ) ); @@ -222,8 +236,8 @@ void AwsIotDefender_Stop( void ) return; } - /* As first step, set flag to NOT started. */ - _started = false; + /* First thing to do: wait for all the metrics processing to be done. */ + IotSemaphore_Wait( &_doneSem ); IotTaskPoolJobStatus_t status; IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, &status ); @@ -245,6 +259,9 @@ void AwsIotDefender_Stop( void ) /* Destroy metrics' mutex. */ IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); + /* Destroy 'done' semaphore. */ + IotSemaphore_Destroy( &_doneSem ); + /* Delete topics names. */ AwsIotDefenderInternal_DeleteTopicsNames(); @@ -257,6 +274,9 @@ void AwsIotDefender_Stop( void ) /* Reset metrics flag array to 0. */ memset( _AwsIotDefenderMetrics.metricsFlag, 0, sizeof( _AwsIotDefenderMetrics.metricsFlag ) ); + /* Set to not started. */ + _started = false; + IotLogInfo( "Defender agent has stopped." ); } @@ -362,9 +382,9 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, IotLogDebug( "Metrics publish job starts." ); - if( !_started ) + if( !IotSemaphore_TryWait( &_doneSem ) ) { - IotLogWarn( "Defender has been stopped. No further processing." ); + IotLogWarn( "Defender has been stopped or the previous metrics is in process. No further action." ); return; } @@ -468,6 +488,8 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, { AwsIotDefenderInternal_NetworkDestroy(); } + + IotSemaphore_Post( &_doneSem ); } IotLogDebug( "Metrics publish job ends." ); @@ -490,20 +512,15 @@ static void _disconnectRoutine( IotTaskPool_t * pTaskPool, AwsIotDefenderInternal_MqttDisconnect(); AwsIotDefenderInternal_NetworkDestroy(); - if( _started ) - { - /* Re-create metrics job. */ - IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + /* Re-create metrics job. */ + IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - /* Re-schedule metrics job with period as deferred interval. */ - taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, _periodMilliSecond ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - } - else - { - IotLogWarn( "Defender has been stopped. Skip scheduling new metrics publish job." ); - } + /* Re-schedule metrics job with period as deferred interval. */ + taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, _periodMilliSecond ); + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + + IotSemaphore_Post( &_doneSem ); IotLogDebug( "Disconnect job ends." ); } diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index 35b2c40c5a..c30e80e24b 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -132,9 +132,6 @@ bool AwsIotDefenderInternal_MqttConnect() { IotMqttNetIf_t mqttNetInterface = IOT_MQTT_NETIF_INITIALIZER; - const char * pThingName = _startInfo.mqttConnectionInfo.pClientIdentifier; - uint16_t thingNameLength = _startInfo.mqttConnectionInfo.clientIdentifierLength; - mqttNetInterface.pDisconnectContext = _startInfo.pConnection; mqttNetInterface.pSendContext = _startInfo.pConnection; mqttNetInterface.disconnect = _startInfo.pNetworkInterface->close; diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 3b01e1190c..c6f509de60 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -45,15 +45,12 @@ #include "unity_fixture.h" -/* Time interval to wait for a state to be true. */ -#define _WAIT_STATE_INTERVAL_SECONDS 1 - /* Total time to wait for a state to be true. */ -#define _WAIT_STATE_TOTAL_SECONDS 5 +#define _WAIT_STATE_TOTAL_SECONDS 5 /* Time interval for defender agent to publish metrics. It will be throttled if too frequent. */ /* TODO: if we can change "thingname" in each test, this can be lowered. */ -#define _DEFENDER_PUBLISH_INTERVAL_SECONDS 15 +#define _DEFENDER_PUBLISH_INTERVAL_SECONDS 20 /* Estimated max size of message payload received in MQTT callback. */ #define _PAYLOAD_MAX_SIZE 200 @@ -94,6 +91,12 @@ static AwsIotDefenderCallback_t _testCallback; static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; +/* +Waiting for it indicates waiting for any event to happen +Posting it indicates any event happened +*/ +static IotSemaphore_t _callbackInfoSem; + static AwsIotDefenderCallbackInfo_t _callbackInfo; static IotSerializerDecoderObject_t _decoderObject; @@ -105,7 +108,7 @@ static IotSerializerDecoderObject_t _metricsObject; static void _copyDataCallbackFunction( void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo ); -static void _waitForAnyEvent( uint32_t timeoutSec ); +static bool _waitForAnyEvent( uint32_t timeoutSec ); static void _assertEvent( AwsIotDefenderEventType_t event, uint32_t timeoutSec ); @@ -131,6 +134,12 @@ TEST_GROUP( Full_DEFENDER ); TEST_SETUP( Full_DEFENDER ) { + /* Create a binary semaphore with initial value 0. */ + if( !IotSemaphore_Create( &_callbackInfoSem, 0, 1 ) ) + { + TEST_FAIL_MESSAGE( "Fail to create semaphore for callback info." ); + } + _resetCalbackInfo(); _decoderObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; @@ -174,6 +183,8 @@ TEST_TEAR_DOWN( Full_DEFENDER ) { sleep( _DEFENDER_PUBLISH_INTERVAL_SECONDS ); } + + IotSemaphore_Destroy( &_callbackInfoSem ); } TEST_GROUP_RUNNER( Full_DEFENDER ) @@ -578,6 +589,8 @@ static void _copyDataCallbackFunction( void * param1, memcpy( ( uint8_t * ) _callbackInfo.pMetricsReport, pCallbackInfo->pMetricsReport, _callbackInfo.metricsReportLength ); } } + + IotSemaphore_Post( &_callbackInfoSem ); } /*-----------------------------------------------------------*/ @@ -606,26 +619,12 @@ static void _resetCalbackInfo() }; } + /*-----------------------------------------------------------*/ -static void _waitForAnyEvent( uint32_t timeoutSec ) +static bool _waitForAnyEvent( uint32_t timeoutSec ) { - uint32_t maxIterations = timeoutSec / _WAIT_STATE_INTERVAL_SECONDS; - uint32_t iter = 1; - - /* Wait for an event type to be set. */ - while( _callbackInfo.eventType == _NO_EVENT ) - { - if( iter > maxIterations ) - { - /* Timeout. */ - TEST_FAIL_MESSAGE( "No event has happened after max timeout." ); - } - - sleep( _WAIT_STATE_INTERVAL_SECONDS ); - - iter++; - } + return IotSemaphore_TimedWait( &_callbackInfoSem, timeoutSec * 1000 ); } /*-----------------------------------------------------------*/ @@ -683,7 +682,11 @@ static void _assertRejectDueToThrottle() static void _waitForMetricsAccepted( uint32_t timeoutSec ) { - _waitForAnyEvent( timeoutSec ); + /* If not event has occured, simply fail the test. */ + if( !_waitForAnyEvent( timeoutSec ) ) + { + TEST_FAIL_MESSAGE( "No event has occured within timeout." ); + } if( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) { From 7e62d779807a39c54dfcf199c8d0263fb9a30683 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Tue, 5 Mar 2019 11:14:40 -0800 Subject: [PATCH 040/844] Fix taskpool worker thread behaviour when exiting on account of max-threads quota being exceeded (#299) * Fix taskpool worker thread behaviour when exiting on account of max-threads quota being exceeded * Changed DestroyJob to DestroyRecyclableJob, and improved Create/Destroy tests. * Added state diagram for jobs to documentation. * Uncrustify code. --- doc/plantuml/RecyclableJobStatus.pu | 46 ++++ doc/plantuml/StaticJobStatus.pu | 32 +++ doc/plantuml/images/RecyclableJobStatus.png | Bin 0 -> 57106 bytes doc/plantuml/images/StaticJobStatus.png | Bin 0 -> 32500 bytes .../taskpool_design_typicaloperation.png | Bin 70318 -> 74493 bytes .../taskpool_design_typicaloperation.pu | 4 +- lib/include/iot_taskpool.h | 29 ++- lib/include/types/iot_taskpool_types.h | 2 +- lib/source/common/iot_taskpool.c | 155 +++++++----- lib/source/defender/aws_iot_defender_api.c | 15 -- lib/source/mqtt/iot_mqtt_api.c | 2 - lib/source/mqtt/iot_mqtt_network.c | 2 - tests/common/unit/iot_tests_taskpool.c | 221 +++++++++++++----- 13 files changed, 346 insertions(+), 162 deletions(-) create mode 100644 doc/plantuml/RecyclableJobStatus.pu create mode 100644 doc/plantuml/StaticJobStatus.pu create mode 100644 doc/plantuml/images/RecyclableJobStatus.png create mode 100644 doc/plantuml/images/StaticJobStatus.png diff --git a/doc/plantuml/RecyclableJobStatus.pu b/doc/plantuml/RecyclableJobStatus.pu new file mode 100644 index 0000000000..84fa443c11 --- /dev/null +++ b/doc/plantuml/RecyclableJobStatus.pu @@ -0,0 +1,46 @@ +@startuml + +skinparam classFontSize 8 +skinparam classFontName Helvetica +skinparam state { + BackgroundColor<> Gray +} +state READY { +} +state SCHEDULED { +} +state DEFERRED { +} +state COMPLETED { +} +state CANCELED { +} +state UNDEFINED { +} +state CACHED <> { +} + +[*] --[#green]> READY : CreateRecyclableJob +READY --[#blue]> SCHEDULED : Schedule +READY --[#blue]> DEFERRED : ScheduleDeferred +DEFERRED --[#cyan]> SCHEDULED : +SCHEDULED --[#cyan]> COMPLETED : +COMPLETED -up[#orange]-> CACHED : RecycleJob +COMPLETED --[#orange]> UNDEFINED : DestroyRecyclableJob + +READY -right[#red]-> CANCELED : TryCancel +DEFERRED -right[#red]-> CANCELED : TryCancel +SCHEDULED -right[#red]-> CANCELED : TryCancel + +CANCELED --[#orange]> CACHED : RecycleJob +CANCELED --[#orange]> UNDEFINED : DestroyRecyclableJob + +READY -up[#orange]-> CACHED : RecycleJob +DEFERRED -up[#orange]-> CACHED : RecycleJob +SCHEDULED -up[#orange]-> CACHED : RecycleJob + +READY --[#orange]> UNDEFINED : DestroyRecyclableJob +DEFERRED --[#orange]> UNDEFINED : DestroyRecyclableJob +SCHEDULED --[#orange]> UNDEFINED : DestroyRecyclableJob + +@enduml \ No newline at end of file diff --git a/doc/plantuml/StaticJobStatus.pu b/doc/plantuml/StaticJobStatus.pu new file mode 100644 index 0000000000..6325664ca0 --- /dev/null +++ b/doc/plantuml/StaticJobStatus.pu @@ -0,0 +1,32 @@ +@startuml + +skinparam classFontSize 8 +skinparam classFontName Helvetica + +state READY { +} +state SCHEDULED { +} +state DEFERRED { +} +state COMPLETED { +} +state CANCELED { +} +state UNDEFINED { +} + +[*] --[#green]> READY : CreateJob +READY --[#blue]> SCHEDULED : Schedule +READY --[#blue]> DEFERRED : ScheduleDeferred +DEFERRED --[#cyan]> SCHEDULED : +SCHEDULED --[#cyan]> COMPLETED : +COMPLETED --[#green]> READY : CreateJob + +READY --[#red]> CANCELED : TryCancel +DEFERRED --[#red]> CANCELED : TryCancel +SCHEDULED --[#red]> CANCELED : TryCancel + +CANCELED --[#green]> READY : CreateJob + +@enduml \ No newline at end of file diff --git a/doc/plantuml/images/RecyclableJobStatus.png b/doc/plantuml/images/RecyclableJobStatus.png new file mode 100644 index 0000000000000000000000000000000000000000..bc263ded664d975f585bc1e4e235f5e6a20cc354 GIT binary patch literal 57106 zcmY&iiN?ZvF3g!nC)SIq%Z-8%NbuEp72aVGw z4JRX8J9ldnQzs}%6B`o;11A$>VncUgb0;S|UM40xYXci6=dad`Mz&wknR!4!59$^w z8czT7Jrp$1jaz1t3fOi*_`R1<|BC^IZccoh>_n*6I|>#`nspi6V%or;oh$K}+o<11 z#pR}7R@hMWsnJe%m(yC(&Q=6^!ljdbkp$0@w#+Q?XnuF?L)de4fi}kFp_VCuQ z@)>Bc$4JFTRo?j`zd8L9Fs&3G7*ZY@Xw9yJ*K9Cn-4XGlMC93W1y$fYOG}7tBzGNB zp=&j9rA0NJJbNvXU!BLdivn4>ZN8s!ZE<}5Q9T*(!EEg(r3US50E5 z*UID6o*$herX=nU-q=rp1eMHED?@)Hs70a#J?#FBQc|Jo`Vo8~P-J+FQZh7;6=NfN zOS5I>h#y|2NRG&ymw~%cC=z}2W(PNZyBVob6v0VmUapmr;K8&iEh+GaSD0gUBU-Qj z(pQ^WKTfp2rX;%XTDWvH6V;KZ*A&{VC7Pc-htTdGHBlo?_LT_rRW5x{n)Dy=8aOK} zvfUaEZ%8lUAnL{*PM)w0`qzj~_Iox8zEz%Fr*b%_g$;ypA&{F;WU_(jAG1LW1E~n8 zF14ofS3kY=Y-9@I-zrqM|KxLZjedd>^F`Kd6CGgcWb7i1gBI<(WU-E;7RV9h^;k1V zJUtxXj`dFJZ9%+UX?;hy%1U#K*GT%^f(gF8RN+~onBaD8#mf}YD^UyQi|5^#MfvzR z>4EA&3brBRFI(f^BA>A~WKJhF))xGUB16^%;1)??+JvirVvna}QN~S>zCpK)_c7&k zIkJj!a`r<2TMqYym1ZkbD8o|<-oZcAwHo*>W}`tt`9Xaa7g2FrIQ)*Hp|X>9Gmn?2 zQf4fpGn2BN*Xk00zmytPA;U#Zk{s30j)lKCkx0p2pjNLTYek!(R7s*8Nsb|gVOc41 z2NLH+^hdz>^z9q;FnPQb?2-9<_>V!P3;vNFD-YLy``>kP|3S`-c{$<5nOz%7V`Ce= zyB8R3%zik-0K6LbDA4FqzaAIhGY|XkvkK|or!mgI&pyU~pRwS7pTrRT=7tH66{U;u zyPx^2*&bS2mDR650e`IDX*$|J`y~K*5m4j0Qv5#aRq!Q9nQ-|gyL3_7YWKm2>YuX- zhJtgLUQmBI;1cg0KJ#`uXruRjIx|zsj$Zxn;Fu<_O`5&Td$ZW6=N{PVzQ>itUn=C+ zf&dI5>;irJjd3H4^d*QqVzYs%x9sld{s)S5K&zwW&}f}+ly7%C)odKZsQy3W1kMPl z?okTGbM56$6>V@)gOpad2;IZI>eu78zV9%!w6x5%z1nV!W1ez7fQESTcw^lL+xBo* z!C4$MOJ+FlO18eZu+Rj7jf6aY4Ws7+H?(^M=o{a)Q8#ue>$qLC8?$OM9d2M^!^!A@corhgmuZW^@#uQz7 z_s|ZP6pweo_I>s-9;IS>W~RgSHS+W^j$sp(E$3Z4n>ut+cUo-gh2fd|}!Y3yI&;~M0s>9rPwV>*;t zY1|(V48wJKBJXpKsi(JETulAoY&x3tG$w`y3k%!$EU{C?H zZQ^(MrtcWe*4CD);`ua02pJhU@%ia~wvEwQ+3~rD#b%!2i-+^W^%0-T;asERPOJAr zdPasl6jTn)FEuMgMMZ0CdT>=2E&K3T8xIeUfPers5fm{*7*cg`a`N(cF}ld4+2-Tz z&||;_SM*j61`wavWh^X8W|?!--M@Zipt04~)^>H}O2m0RM1hC2 zctC(!=(4Qk4Vxqghn%!#lw(X!Psg9+kwd?31|tygi21_9!Qovl$4cm zqswn}%gRu;5P*f~i?1#a&BBo%{@9T;1I<*IG!qMJ6BbHApmHWI6l=m|un}A3IXwXT z$r%R9FC5n%w{zKbwQO=v%B8xxIwB&Xw6s)~67%2D=y34wz16usQzKzVB77^5^V$+m zkET~3Q#Lqk@~@J)_NA4fZi+w(0Sw
>`-z=;%O1LOP>=2bijWT1S|Bt|-E#NPUWZ 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 literal 0 HcmV?d00001 diff --git a/doc/plantuml/images/StaticJobStatus.png b/doc/plantuml/images/StaticJobStatus.png new file mode 100644 index 0000000000000000000000000000000000000000..064babb6fde27d464d25dc183a128b9627cb0d95 GIT binary patch literal 32500 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 oV3=Vb!vmud_!C1Q>PPa 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 b=fqS??!mkp=%7T?mMywkMi+|CIidd#{_l9A literal 70318 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 diff --git a/doc/plantuml/taskpool_design_typicaloperation.pu b/doc/plantuml/taskpool_design_typicaloperation.pu index ab06d2e6d1..eaf919e5e9 100644 --- a/doc/plantuml/taskpool_design_typicaloperation.pu +++ b/doc/plantuml/taskpool_design_typicaloperation.pu @@ -33,7 +33,7 @@ workers -> workers: Wait on incoming jobs == Use Task Pool == loop Application loop: Create and schedule jobs - app -[#blue]> TP: AwsIotTaskPool_CreateJob: create a job + app -[#blue]> TP: AwsIotTaskPool_CreateRecyclableJob: create a job TP --[#blue]> app activate job note left: job status: //ready// @@ -52,7 +52,7 @@ loop Application loop: Create and schedule jobs note left: job status: //executing// job -[#green]> callback: Invoke activate callback - callback -[#blue]> TP: AwsIotTaskPool_DestroyJob: destroy job + callback -[#blue]> TP: AwsIotTaskPool_RecycleJob: recycles job TP --[#blue]> callback note left: job status: //completed// deactivate job diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index eb6cf6f9db..c28cf435bc 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -51,7 +51,7 @@ * - @functionname{taskpool_function_setmaxthreads} * - @functionname{taskpool_function_createjob} * - @functionname{taskpool_function_createrecyclablejob} - * - @functionname{taskpool_function_destroyjob} + * - @functionname{taskpool_function_destroyrecyclablejob} * - @functionname{taskpool_function_recyclejob} * - @functionname{taskpool_function_schedule} * - @functionname{taskpool_function_scheduledeferred} @@ -67,7 +67,7 @@ * @functionpage{IotTaskPool_SetMaxThreads,taskpool,setmaxthreads} * @functionpage{IotTaskPool_CreateJob,taskpool,createjob} * @functionpage{IotTaskPool_CreateJob,taskpool,createrecyclablejob} - * @functionpage{IotTaskPool_DestroyJob,taskpool,destroyjob} + * @functionpage{IotTaskPool_DestroyRecyclableJob,taskpool,destroyrecyclablejob} * @functionpage{IotTaskPool_RecycleJob,taskpool,recyclejob} * @functionpage{IotTaskPool_Schedule,taskpool,schedule} * @functionpage{IotTaskPool_ScheduleDeferred,taskpool,scheduledeferred} @@ -252,7 +252,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP /** * @brief This function uninitializes a job. * - * This function will destroy a job created with @ref AwsIotTaskPool_CreateJob or @ref AwsIotTaskPool_CreateRecyclableJob. + * This function will destroy a job created with @ref AwsIotTaskPool_CreateRecyclableJob. * A job should not be destroyed twice. A job that was previously scheduled but has not completed yet should not be destroyed, * but rather the application should attempt to cancel it first by calling @ref AwsIotTaskPool_TryCancel. * An attempt to destroy a job that was scheduled but not yet executed or canceled, may result in a @@ -264,17 +264,21 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP * @return One of the following: * - #IOT_TASKPOOL_SUCCESS * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_ILLEGAL_OPERATION * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * * @warning The task pool will try and prevent destroying jobs that are currently queued for execution, but does - * not enforce strict ordering of operations. It is up to the user to make sure @ref IotTaskPool_DestroyJob is not called + * not enforce strict ordering of operations. It is up to the user to make sure @ref IotTaskPool_DestroyRecyclableJob is not called * our of order. * + * @warning Calling this function on job that was not previously created with @ref AwsIotTaskPool_CreateRecyclableJob + * will result in a @ref AWS_IOT_TASKPOOL_ILLEGAL_OPERATION error. + * */ -/* @[declare_taskpool_destroyjob] */ -IotTaskPoolError_t IotTaskPool_DestroyJob( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ); -/* @[declare_taskpool_destroyjob] */ +/* @[declare_taskpool_destroyrecyclablejob] */ +IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ); +/* @[declare_taskpool_destroyrecyclablejob] */ /** * @brief Rrecycles a job into the task pool job cache. @@ -302,7 +306,7 @@ IotTaskPoolError_t IotTaskPool_DestroyJob( IotTaskPool_t * const pTaskPool, * error. * * @warning This function should be used to recycle a job in the task pool cache when after the job executed. - * Failing to call either this function or @ref IotTaskPool_DestroyJob will result is a memory leak. Statically + * Failing to call either this function or @ref IotTaskPool_DestroyRecyclableJob will result is a memory leak. Statically * alloted jobs do not need to be recycled or destroyed. * */ @@ -353,9 +357,6 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * JobUserContext_t * pUserContext = ( JobUserContext_t * )context; * * pUserContext->counter++; - * - * // Destroy the job. - * IotTaskPool_DestroyJob( pTaskPool, pJob ); * } * * void TaskPoolExample( ) @@ -394,8 +395,6 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * // ... Perform other operations ... * // * - * - * IotTaskPool_DestroyJob( pTaskPool, pJob ); * IotTaskPool_Destroy( pTaskPool ); * } * @endcode @@ -463,7 +462,7 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, * * A job can be canceled only if it is not yet executing, i.e. if its status is * @ref IOT_TASKPOOL_STATUS_READY or @ref IOT_TASKPOOL_STATUS_SCHEDULED. Calling - * @ref IotTaskPool_TryCancel on a job whose status is @ref IOT_TASKPOOL_STATUS_EXECUTING, + * @ref IotTaskPool_TryCancel on a job whose status is @ref IOT_TASKPOOL_STATUS_COMPLETED, * or @AWS_IOT_TASKPOOL_STATUS_CANCELED will yield a @AWS_IOT_TASKPOOL_CANCEL_FAILED return result. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 839072830e..c564e737c5 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -178,7 +178,7 @@ typedef enum IotTaskPoolJobStatus * @brief Job is executing. * */ - IOT_TASKPOOL_STATUS_EXECUTING, + IOT_TASKPOOL_STATUS_COMPLETED, /** * @brief Job has been canceled before executing. diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 1e060e0483..2ac8dfbf2c 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -96,6 +96,16 @@ IotTaskPool_t _IotSystemTaskPool = { 0 }; */ static void _initJobsCache( IotTaskPoolCache_t * const pCache ); +/** + * @brief Initialize a job. + * + * @param[in] pJob The job to initialize. + */ +static void _initializeJob( IotTaskPoolJob_t * const pJob, + IotTaskPoolRoutine_t userCallback, + void * pUserContext, + bool isStatic ); + /** * @brief Extracts and initializes one instance of a job from the cache or, if there is none available, it allocates and initialized a new one. * @@ -237,7 +247,7 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, */ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, - bool checkExecutionInProgress ); + bool atCompletion ); /** @endcond */ /* ---------------------------------------------------------------------------------------------- */ @@ -360,9 +370,6 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) IotSemaphore_Wait( &pTaskPool->startStopSignal ); } - IotTaskPool_Assert( pTaskPool->activeThreads == 0 ); - IotTaskPool_Assert( IotSemaphore_GetCount( &pTaskPool->startStopSignal ) == 0 ); - /* (6) Destroy all signaling objects. */ _destroyTaskPool( pTaskPool ); @@ -427,11 +434,7 @@ IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallbac _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); /* Build a job around the user-provided storage. */ - pJob->link.pNext = NULL; - pJob->link.pPrevious = NULL; - pJob->userCallback = userCallback; - pJob->pUserContext = pUserContext; - pJob->status = IOT_TASKPOOL_STATUS_READY | IOT_TASK_POOL_INTERNAL_STATIC; + _initializeJob( pJob, userCallback, pUserContext, true ); _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -475,11 +478,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } - pTempJob->link.pNext = NULL; - pTempJob->link.pPrevious = NULL; - pTempJob->userCallback = userCallback; - pTempJob->pUserContext = pUserContext; - pTempJob->status = IOT_TASKPOOL_STATUS_READY; + _initializeJob( pTempJob, userCallback, pUserContext, false ); *ppJob = pTempJob; } @@ -489,12 +488,13 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_DestroyJob( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ) +IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTaskPool, + IotTaskPoolJob_t * const pJob ) { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ + _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); _TASKPOOL_ENTER_CRITICAL_SECTION; @@ -502,13 +502,18 @@ IotTaskPoolError_t IotTaskPool_DestroyJob( IotTaskPool_t * const pTaskPool, /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; + } + /* Do not destroy statically allocated jobs. */ + else if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == IOT_TASK_POOL_INTERNAL_STATIC ) + { + IotLogWarn( "Attempt to destroy a statically allocated job." ); - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + status = IOT_TASKPOOL_ILLEGAL_OPERATION; } else { - status = _trySafeExtraction( pTaskPool, pJob, false ); + status = _trySafeExtraction( pTaskPool, pJob, true ); } } _TASKPOOL_EXIT_CRITICAL_SECTION; @@ -540,15 +545,12 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; - - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; } - /* Do not recycle statically allocated jobs. */ - if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ) + else if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ) { - status = _trySafeExtraction( pTaskPool, pJob, false ); + status = _trySafeExtraction( pTaskPool, pJob, true ); } else { @@ -560,6 +562,9 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, /* If all safety checks completed, proceed. */ if( _TASKPOOL_SUCCEEDED( status ) ) { + /* At this point, the job must not be in any queue or list. */ + IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); + _recycleJob( &pTaskPool->jobsCache, pJob ); } } @@ -579,19 +584,18 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /* Parameter checking. */ _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0 ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); _TASKPOOL_ENTER_CRITICAL_SECTION; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; - - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; } else { - status = _trySafeExtraction( pTaskPool, pJob, true ); + status = _trySafeExtraction( pTaskPool, pJob, false ); } /* If all safety checks completed, proceed. */ @@ -633,7 +637,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool } /* If all safety checks completed, proceed. */ - if( _TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, true ) ) ) + if( _TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, false ) ) ) { _taskPoolTimerEvent_t * pTimerEvent = IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); @@ -641,7 +645,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool { _TASKPOOL_EXIT_CRITICAL_SECTION; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } IotLink_t * pTimerEventLink; @@ -674,6 +678,12 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool _rescheduleDeferredJobsTimer( &pTaskPool->timer, pTimerEvent ); } } + else + { + _TASKPOOL_EXIT_CRITICAL_SECTION; + + _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); + } } _TASKPOOL_EXIT_CRITICAL_SECTION; @@ -790,6 +800,7 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. */ + static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, IotTaskPool_t * const pTaskPool ) { @@ -967,6 +978,7 @@ static void _taskPoolWorker( void * pUserContext ) { /* Extract pTaskPool pointer from context. */ IotTaskPool_Assert( pUserContext != NULL ); + bool running = true; IotTaskPool_t * pTaskPool = ( IotTaskPool_t * ) pUserContext; /* Signal that this worker completed initialization and it is ready to receive notifications. */ @@ -976,11 +988,10 @@ static void _taskPoolWorker( void * pUserContext ) * is setting maxThreads to zero. A worker thread is running until the maximum number of allowed * threads is not zero and the active threads are less than the maximum number of allowed threads. */ - for( ; ; ) + do { IotLink_t * pFirst = NULL; IotTaskPoolJob_t * pJob = NULL; - bool shouldExit = false; /* Wait on incoming notifications... */ IotSemaphore_Wait( &pTaskPool->dispatchSignal ); @@ -993,14 +1004,8 @@ static void _taskPoolWorker( void * pUserContext ) /* If the exit condition is verified, update the number of active threads and exit the loop. */ if( _IsShutdownStarted( pTaskPool ) ) { - shouldExit = true; - IotLogDebug( "Worker thread exiting because exit condition was set." ); - } - /* Check if thread should exit. */ - if( shouldExit ) - { /* Decrease the number of active threads. */ pTaskPool->activeThreads--; @@ -1009,10 +1014,24 @@ static void _taskPoolWorker( void * pUserContext ) /* Signal that this worker is exiting. */ IotSemaphore_Post( &pTaskPool->startStopSignal ); - /* Abandon the OUTER LOOP. */ + /* On shutdown, abandon the OUTER LOOP immediately. */ break; } + /* Check if this thread needs to exit but let is run once, so we can support + * the case for scheduling 'high prioroty' jobs that causes exceeding the + * max threads quota for the purpose of executing the high-piority task. */ + if( pTaskPool->activeThreads > pTaskPool->maxThreads ) + { + IotLogDebug( "Worker thread exiting because maximum quota was exceeded." ); + + /* Decrease the number of active threads pro-actively. */ + pTaskPool->activeThreads--; + + /* Mark this thread as dead. */ + running = false; + } + /* Dequeue the first job in FIFO order. */ pFirst = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); @@ -1024,7 +1043,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Update status to 'executing'. */ pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_EXECUTING; + pJob->status |= IOT_TASKPOOL_STATUS_COMPLETED; } } _TASKPOOL_EXIT_CRITICAL_SECTION; @@ -1045,6 +1064,13 @@ static void _taskPoolWorker( void * pUserContext ) /* This job is finished, clear its pointer. */ pJob = NULL; + + /* If this thread exceeded the quota, then let it terminate. */ + if ( running == false ) + { + /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ + break; + } } /* Acquire the lock before updating the job status. */ @@ -1073,25 +1099,12 @@ static void _taskPoolWorker( void * pUserContext ) } pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_EXECUTING; + pJob->status |= IOT_TASKPOOL_STATUS_COMPLETED; } _TASKPOOL_EXIT_CRITICAL_SECTION; } - /* We check whether this thread needs to exit or not at the end of the outer loop, so - * we can support the case for scheduling 'high prioroty' jobs that exceed the - * max threads quota. */ - _TASKPOOL_ENTER_CRITICAL_SECTION; - { - if( pTaskPool->activeThreads > pTaskPool->maxThreads ) - { - shouldExit = true; - - IotLogDebug( "Worker thread exiting because maximum quota was exceeded." ); - } - } - _TASKPOOL_EXIT_CRITICAL_SECTION; - } + } while( running == true ); } /* ---------------------------------------------------------------------------------------------- */ @@ -1105,6 +1118,26 @@ static void _initJobsCache( IotTaskPoolCache_t * const pCache ) /*-----------------------------------------------------------*/ +static void _initializeJob( IotTaskPoolJob_t * const pJob, + IotTaskPoolRoutine_t userCallback, + void * pUserContext, + bool isStatic ) +{ + pJob->link.pNext = NULL; + pJob->link.pPrevious = NULL; + pJob->userCallback = userCallback; + pJob->pUserContext = pUserContext; + + if( isStatic ) + { + pJob->status = IOT_TASKPOOL_STATUS_READY | IOT_TASK_POOL_INTERNAL_STATIC; + } + else + { + pJob->status = IOT_TASKPOOL_STATUS_READY; + } +} + static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache ) { IotTaskPoolJob_t * pJob = NULL; @@ -1175,7 +1208,7 @@ static void _recycleJob( IotTaskPoolCache_t * const pCache, static void _destroyJob( IotTaskPoolJob_t * const pJob ) { - /* Destroy user data, for added safety&security. */ + /* Destroy user data, for added safety & security. */ pJob->userCallback = NULL; pJob->pUserContext = NULL; @@ -1340,11 +1373,11 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, case IOT_TASKPOOL_STATUS_READY: case IOT_TASKPOOL_STATUS_DEFERRED: case IOT_TASKPOOL_STATUS_SCHEDULED: + case IOT_TASKPOOL_STATUS_CANCELED: cancelable = true; break; - case IOT_TASKPOOL_STATUS_EXECUTING: - case IOT_TASKPOOL_STATUS_CANCELED: + case IOT_TASKPOOL_STATUS_COMPLETED: IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." ); break; @@ -1424,14 +1457,14 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, - bool checkExecutionInProgress ) + bool atCompletion ) { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); IotTaskPoolJobStatus_t jobStatus = pJob->status & IOT_TASKPOOL_STATUS_MASK; /* if the job is executing, we cannot touch it. */ - if( checkExecutionInProgress && ( jobStatus == IOT_TASKPOOL_STATUS_EXECUTING ) ) + if( ( atCompletion == false ) && ( jobStatus == IOT_TASKPOOL_STATUS_COMPLETED ) ) { _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); } diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index b921822b4a..5135964324 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -204,14 +204,6 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn if( metricsMutexCreateSuccess ) { IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); - - taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob ); - - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - - taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_disconnectJob ); - - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); } IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( defenderError ) ); @@ -249,13 +241,6 @@ void AwsIotDefender_Stop( void ) sleep( _WAIT_METRICS_JOB_MAX_SECONDS ); } - /* Destroy two task pool jobs. */ - taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - - taskPoolError = IotTaskPool_DestroyJob( IOT_SYSTEM_TASKPOOL, &_disconnectJob ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - /* Destroy metrics' mutex. */ IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 5ae3772ad6..fadd87bd18 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -431,8 +431,6 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); - IotTaskPool_DestroyJob( &( _IotMqttTaskPool ), - &( pMqttConnection->keepAliveJob ) ); /* Clear data about the keep-alive. */ pMqttConnection->keepAliveMs = 0; diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index a0aa035767..f5374e7901 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -610,8 +610,6 @@ void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) { /* Clean up PINGREQ packet and job. */ _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); - IotTaskPool_DestroyJob( &( _IotMqttTaskPool ), - &( pMqttConnection->keepAliveJob ) ); /* Clear data about the keep-alive. */ pMqttConnection->keepAliveMs = 0; diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 55cf9c1caa..5c72db8691 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -103,7 +103,8 @@ TEST_TEAR_DOWN( Common_Unit_Task_Pool ) TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) { RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroy ); - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateJobError ); + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyJobError ); + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasksError ); RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_Grow ); @@ -120,11 +121,11 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) /*-----------------------------------------------------------*/ -/** + /** * @brief Number of iterations for each test loop. */ #ifndef _TASKPOOL_TEST_ITERATIONS - #define _TASKPOOL_TEST_ITERATIONS ( 200 ) +#define _TASKPOOL_TEST_ITERATIONS ( 200 ) #endif /** @@ -187,38 +188,12 @@ static void EmulateWorkLong() TEST_ASSERT_TRUE( error == 0 ); } -/** - * @brief A callback that recycles its job. - */ -static void ExecutionWithDestroyCb( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, - void * context ) -{ - JobUserContext_t * pUserContext; - IotTaskPoolJobStatus_t status; - - TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - - TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); - - EmulateWork(); - - pUserContext = ( JobUserContext_t * ) context; - - IotMutex_Lock( &pUserContext->lock ); - pUserContext->counter++; - IotMutex_Unlock( &pUserContext->lock ); - - TEST_ASSERT( IotTaskPool_DestroyJob( pTaskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); -} - /** * @brief A callback that does not recycle its job. */ static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, - void * context ) + void * pContext ) { JobUserContext_t * pUserContext; IotTaskPoolJobStatus_t status; @@ -226,11 +201,11 @@ static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); EmulateWork(); - pUserContext = ( JobUserContext_t * ) context; + pUserContext = ( JobUserContext_t * ) pContext; IotMutex_Lock( &pUserContext->lock ); pUserContext->counter++; @@ -242,7 +217,7 @@ static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, */ static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, - void * context ) + void * pContext ) { JobBlockingUserContext_t * pUserContext; IotTaskPoolJobStatus_t status; @@ -250,9 +225,9 @@ static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); - pUserContext = ( JobBlockingUserContext_t * ) context; + pUserContext = ( JobBlockingUserContext_t * ) pContext; /* Signal that the callback has been called. */ IotSemaphore_Post( &pUserContext->signal ); @@ -267,7 +242,7 @@ static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, */ static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, - void * context ) + void * pContext ) { JobUserContext_t * pUserContext; IotTaskPoolJobStatus_t status; @@ -275,11 +250,11 @@ static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); EmulateWork(); - pUserContext = ( JobUserContext_t * ) context; + pUserContext = ( JobUserContext_t * ) pContext; IotMutex_Lock( &pUserContext->lock ); pUserContext->counter++; @@ -293,7 +268,7 @@ static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, */ static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, - void * context ) + void * pContext ) { JobUserContext_t * pUserContext; IotTaskPoolJobStatus_t status; @@ -301,17 +276,36 @@ static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_EXECUTING ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); EmulateWorkLong(); - pUserContext = ( JobUserContext_t * ) context; + pUserContext = ( JobUserContext_t * ) pContext; IotMutex_Lock( &pUserContext->lock ); pUserContext->counter++; IotMutex_Unlock( &pUserContext->lock ); } +/** +* @brief A callback that does not recycle its job. +*/ +static void BlankExecution( IotTaskPool_t * pTaskPool, + IotTaskPoolJob_t * pJob, + void * pContext ) +{ + IotTaskPoolJobStatus_t status; + + ( void )pTaskPool; + ( void )pJob; + ( void )pContext; + + TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + + TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); +} + /* ---------------------------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------------------------- */ @@ -376,24 +370,80 @@ TEST( Common_Unit_Task_Pool, CreateDestroy ) /** * @brief Test task pool job static and dynamic memory creation with bogus parameters. */ -TEST( Common_Unit_Task_Pool, CreateJobError ) +TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) { +#define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) + IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; IotTaskPool_Create( &tpInfo, &taskPool ); - /* Non-recyclable jobs. */ + /* Trivial parameter validation. */ { IotTaskPoolJob_t job; /* NULL callback. */ TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithDestroyCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); } - /* Recyclable jobs. */ + /* Create/Destroy. */ + { + IotTaskPoolJob_t job; + + /* Create legal static job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + /* Illegally recycle legal static job. */ + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + } + + /* Create/Destroy. */ + { + IotTaskPoolJob_t job; + IotTaskPoolJobStatus_t jobStatusAtCancellation; + + /* Create legal static job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule deferred, then try to illegally destroy, then cancel */ + TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, &job, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, &job, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); + } + + /* Create/Destroy. */ + { + IotTaskPoolJob_t job; + + /* Create legal static job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule immediate, then try to illegally destroy it. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &job, 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + } + + IotTaskPool_Destroy( &taskPool ); + +#undef ONE_HOUR_FROM_NOW_MS +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test task pool job static and dynamic memory creation with bogus parameters. + */ +TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) +{ +#define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) + + IotTaskPool_t taskPool; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Trivial parameter validation jobs. */ { IotTaskPoolJob_t * pJob = NULL; /* NULL callback. */ @@ -404,7 +454,65 @@ TEST( Common_Unit_Task_Pool, CreateJobError ) TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); } + /* Create/Destroy. */ + { + IotTaskPoolJob_t * pJob = NULL; + + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Recycle the job. */ + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Create/Schedule/Destroy. */ + { + IotTaskPoolJob_t * pJob = NULL; + + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule deferred, then try to destroy it. */ + TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Create/Recycle. */ + { + IotTaskPoolJob_t * pJob = NULL; + + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Illegally recycle legal static job. */ + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Create/Schedule/Cancel/Recycle. */ + { + IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJobStatus_t jobStatusAtCancellation; + + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule deferred, then try to cancel it and finally recycle it. */ + TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, pJob, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Create/Schedule/Recycle. */ + { + IotTaskPoolJob_t * pJob = NULL; + + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule immediate, then try to illegally destroy, then cancel */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, pJob, 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } + IotTaskPool_Destroy( &taskPool ); + +#undef ONE_HOUR_FROM_NOW_MS } /*-----------------------------------------------------------*/ @@ -461,14 +569,12 @@ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) IotTaskPoolJob_t job; - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); /* NULL Task Pool Handle. */ TEST_ASSERT( IotTaskPool_Schedule( NULL, &job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL Work item Handle. */ TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* Destroy the job, so we do not leak it. */ - TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); IotTaskPool_Destroy( &taskPool ); } @@ -642,6 +748,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) IotSemaphore_Destroy( &userContext.block ); #undef _NUMBER_OF_JOBS +#undef _NUMBER_OF_THREADS } /*-----------------------------------------------------------*/ @@ -714,8 +821,6 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ) } TEST_ASSERT( userContext.counter == scheduled ); - - TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); } /* Since jobs were build from a static buffer and scheduled one-by-one, we @@ -800,8 +905,6 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait } TEST_ASSERT( userContext.counter == scheduled ); - - TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &job ) == IOT_TASKPOOL_SUCCESS ); } /* Since jobs were build from a static buffer and scheduled one-by-one, we @@ -960,11 +1063,6 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ) } TEST_ASSERT_TRUE( userContext.counter == scheduled ); - - for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) - { - TEST_ASSERT( IotTaskPool_DestroyJob( &taskPool, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } } IotTaskPool_Destroy( &taskPool ); @@ -1465,9 +1563,9 @@ TEST( Common_Unit_Task_Pool, TaskPool_CancelTasks ) break; case IOT_TASKPOOL_CANCEL_FAILED: - TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_EXECUTING ) ); + TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_EXECUTING ) ); + TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); break; case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: @@ -1489,11 +1587,6 @@ TEST( Common_Unit_Task_Pool, TaskPool_CancelTasks ) TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPool_DestroyJob( &taskPool, &jobs[ count ] ); - } - IotTaskPool_Destroy( &taskPool ); /* Destroy user context. */ From 473591ac51bc7a1f6a6bb84835b0d8b2b964c6cd Mon Sep 17 00:00:00 2001 From: qiutongs Date: Tue, 5 Mar 2019 16:37:21 -0800 Subject: [PATCH 041/844] update defender doc and add AFR specific code (#304) --- doc/config/defender | 32 +++++++++++++++++ lib/include/aws_iot_defender.h | 33 +++++++++-------- .../private/aws_iot_defender_internal.h | 5 +-- lib/source/defender/aws_iot_defender_api.c | 6 +++- .../defender/aws_iot_defender_collector.c | 36 ++++++++++++------- 5 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 doc/config/defender diff --git a/doc/config/defender b/doc/config/defender new file mode 100644 index 0000000000..4876799a10 --- /dev/null +++ b/doc/config/defender @@ -0,0 +1,32 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Defender" +PROJECT_BRIEF = "AWS IoT Device Defender library" + +# Library documentation output directory. +HTML_OUTPUT = defender + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/defender.tag + +# Directories containing library source code. +INPUT = doc/lib/ \ + lib/include \ + lib/include/private \ + lib/source/defender + +# Library file names. +FILE_PATTERNS = *defender*.h *defender*.txt + +EXAMPLE_PATH = lib/include/aws_iot_defender.h + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/mqtt.tag=../mqtt \ + doc/tag/logging.tag=../logging \ + doc/tag/static_memory.tag=../static_memory \ + doc/tag/platform.tag=../platform \ + doc/tag/linear_containers.tag=../linear_containers diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index fd0eb9ac1f..5bf35fcb3e 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -37,19 +37,19 @@ * * Amazon FreeRTOS provides a library that allows your Amazon FreeRTOS-based devices to work with AWS IoT Device Defender. * - ## Dependencies - ##- MQTT library - ##- Serializer library - ##- Platform(POSIX) libraries - ##- Metrics library + * ## Dependencies + * * MQTT library + * * Serializer library + * * Platform(POSIX) libraries + * * Metrics library */ #ifndef _AWS_IOT_DEFENDER_H_ #define _AWS_IOT_DEFENDER_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Standard includes. */ @@ -75,6 +75,7 @@ * @name Serialization Format * * @brief Format constants: Cbor or Json. + * @warning JSON format is not supported for now. */ /**@{ */ #define AWS_IOT_DEFENDER_FORMAT_CBOR 1 /**< CBOR format. */ @@ -107,7 +108,7 @@ #define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED \ ( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS | AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) \ -/**@} */ +/**@} end of DefenderMetricsFlags */ /** * @anchor DefenderInitializers @@ -210,12 +211,12 @@ typedef struct AwsIotDefenderCallback */ typedef struct AwsIotDefenderStartInfo { - void * pConnectionInfo; - void * pCredentialInfo; - void * pConnection; - const IotNetworkInterface_t * pNetworkInterface; - IotMqttConnectInfo_t mqttConnectionInfo; - AwsIotDefenderCallback_t callback; + void * pConnectionInfo; /**< Connection information(required). */ + void * pCredentialInfo; /**< Credential information(required). */ + void * pConnection; /**< Connection object(required). */ + const IotNetworkInterface_t * pNetworkInterface; /**< Network inferface defender uses(required). */ + IotMqttConnectInfo_t mqttConnectionInfo; /**< Network inferface defender uses(required). */ + AwsIotDefenderCallback_t callback; /**< Callback function parameter(optional). */ } AwsIotDefenderStartInfo_t; /** @@ -235,7 +236,8 @@ typedef struct AwsIotDefenderStartInfo * @snippet this declare_defender_setmetrics * @brief Set metrics that defender agent needs to collect for a metrics group. * - * Metrics to be collected is global data which is independent of defender agent start or stop. + * * If defender agent is not started, this function will provide the metrics to be collected. + * * If defender agent is started, this function will update the metrics and take effect in defender agent's next iteration. * * @param[in] metricsGroup Metrics group defined in #AwsIotDefenderMetricsGroup_t * @param[in] metrics Bit-flags to specify what metrics to collect. @@ -246,6 +248,7 @@ typedef struct AwsIotDefenderStartInfo * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. * * If metricsGroup is invalid, #AWS_IOT_DEFENDER_INVALID_INPUT is returned. * @note This function is thread safe. + * @note @ref Defender_function_Stop "AwsIotDefender_Stop" will clear the metrics. */ /* @[declare_defender_setmetrics] */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 8eef00de55..560cb76cd3 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -28,8 +28,8 @@ #define _AWS_IOT_DEFENDER_INTERNAL_H_ /* Build using a config header, if provided. */ -#ifdef AWS_IOT_CONFIG_FILE - #include AWS_IOT_CONFIG_FILE +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE #endif /* Defender include. */ @@ -216,6 +216,7 @@ #error "AWS_IOT_DEFENDER_FORMAT_JSON is not supported." #endif +/* Default to short tag to save memory and network. */ #ifndef AWS_IOT_DEFENDER_USE_LONG_TAG #define AWS_IOT_DEFENDER_USE_LONG_TAG ( 0 ) #endif diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 5135964324..c8714fd2e8 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -26,7 +26,11 @@ #include "iot_taskpool.h" /* POSIX includes. */ -#include +#ifdef _DEFENDER_ON_AMAZON_FREERTOS + #include "FreeRTOS_POSIX/unistd.h" +#else + #include +#endif #define _WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index c3cd566496..19147d80f1 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -21,7 +21,6 @@ /* Standard includes */ #include -#include /* Defender internal include. */ #include "private/aws_iot_defender_internal.h" @@ -30,6 +29,12 @@ #include "platform/iot_clock.h" +#ifdef _DEFENDER_ON_AMAZON_FREERTOS + #include "aws_secure_sockets.h" +#else + #include +#endif + #define _HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) #define _REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) #define _VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) @@ -53,8 +58,8 @@ typedef struct _metricsReport { IotSerializerEncoderObject_t object; /* Encoder object handle. */ - uint8_t * pDataBuffer; /* Raw data buffer to be published with MQTT. */ - size_t size; /* Raw data size. */ + uint8_t * pDataBuffer; /* Raw data buffer to be published with MQTT. */ + size_t size; /* Raw data size. */ } _metricsReport_t; typedef struct _tcpConns @@ -126,8 +131,8 @@ uint8_t * AwsIotDefenderInternal_GetReportBuffer() size_t AwsIotDefenderInternal_GetReportBufferSize() { /* Encoder might over-calculate the needed size. Therefor encoded size might be smaller than buffer size: _report.size. */ - return _report.pDataBuffer == NULL ? 0 - : _AwsIotDefenderEncoder.getEncodedSize(&_report.object, _report.pDataBuffer); + return _report.pDataBuffer == NULL ? 0 + : _AwsIotDefenderEncoder.getEncodedSize( &_report.object, _report.pDataBuffer ); } /*-----------------------------------------------------------*/ @@ -230,7 +235,7 @@ static void _serialize() /* Define an assert function for serialization returned error. */ void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall - : assertSuccess; + : assertSuccess; uint8_t metricsGroupCount = 0; uint32_t i = 0; @@ -418,10 +423,9 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj uint8_t hasRemoteAddr = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) > 0; char remoteAddr[ _REMOTE_ADDR_LENGTH ] = ""; - char * pRemoteAddr = remoteAddr; void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall - : assertSuccess; + : assertSuccess; uint32_t i = 0; @@ -467,13 +471,19 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj /* add remote address */ if( hasRemoteAddr ) { - /* Remote IP is with host endian. So it is converted to network endian and passed into SOCKETS_inet_ntoa. */ - struct in_addr remoteInAddr = { .s_addr = _metrics.tcpConns.pArray[ i ].remoteIP }; - char * pRemoteIp = inet_ntoa( remoteInAddr ); - sprintf( remoteAddr, "%s:%d", pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); + #ifdef _DEFENDER_ON_AMAZON_FREERTOS + /* Remote IP is with host endian. So it is converted to network endian and passed into SOCKETS_inet_ntoa. */ + SOCKETS_inet_ntoa( SOCKETS_htonl( _metrics.tcpConns.pArray[ i ].remoteIP ), remoteAddr ); + sprintf( remoteAddr, "%s:%d", remoteAddr, _metrics.tcpConns.pArray[ i ].remotePort ); + #else + /* Remote IP is with network endian. */ + struct in_addr remoteInAddr = { .s_addr = _metrics.tcpConns.pArray[ i ].remoteIP }; + char * pRemoteIp = inet_ntoa( remoteInAddr ); + sprintf( remoteAddr, "%s:%d", pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); + #endif serializerError = _AwsIotDefenderEncoder.appendKeyValue( &connectionMap, _REMOTE_ADDR_TAG, - IotSerializer_ScalarTextString( pRemoteAddr ) ); + IotSerializer_ScalarTextString( remoteAddr ) ); assertNoError( serializerError ); } From 40aea5031c8bf0a99636971fa8c83b9a621d7a84 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Wed, 6 Mar 2019 13:55:54 -0800 Subject: [PATCH 042/844] Add tests for max threads and creation/destruction. (#305) --- lib/source/common/iot_taskpool.c | 28 ++- tests/common/unit/iot_tests_taskpool.c | 249 ++++++++++++++++++------- 2 files changed, 207 insertions(+), 70 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 2ac8dfbf2c..f6aa658c4d 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -383,6 +383,8 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + uint32_t count, i; + /* Parameter checking. */ _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); @@ -398,19 +400,23 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } - uint32_t currentMaxThreads = pTaskPool->maxThreads; + uint32_t previousMaxThreads = pTaskPool->maxThreads; /* Reset the max threads counter. */ pTaskPool->maxThreads = maxThreads; + count = previousMaxThreads - maxThreads; + /* If the number of maximum threads in the pool is set to be smaller than the current value, - * then we need to signal all redundant to exit. + * then we need to signal all redundant threads to exit. */ - if( maxThreads < currentMaxThreads ) + if( maxThreads < previousMaxThreads ) { - uint32_t count = currentMaxThreads - maxThreads; + IotLogDebug( "Setting max threads caused %d thread to exit.", count ); + + i = count; - while( count-- > 0 ) + while( i-- > 0 ) { IotSemaphore_Post( &pTaskPool->dispatchSignal ); } @@ -418,6 +424,13 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, } _TASKPOOL_EXIT_CRITICAL_SECTION; + //i = count; + + //while( count-- > 0 ) + //{ + // IotSemaphore_Wait( &pTaskPool->startStopSignal ); + //} + _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -702,6 +715,8 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); + + *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; _TASKPOOL_ENTER_CRITICAL_SECTION; { @@ -1066,7 +1081,7 @@ static void _taskPoolWorker( void * pUserContext ) pJob = NULL; /* If this thread exceeded the quota, then let it terminate. */ - if ( running == false ) + if( running == false ) { /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ break; @@ -1103,7 +1118,6 @@ static void _taskPoolWorker( void * pUserContext ) } _TASKPOOL_EXIT_CRITICAL_SECTION; } - } while( running == true ); } diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 5c72db8691..b041b601ec 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -102,30 +102,31 @@ TEST_TEAR_DOWN( Common_Unit_Task_Pool ) */ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) { - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroy ); + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyMaxThreads ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasksError ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_Grow ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllRecyclableThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReScheduleDeferred ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_CancelTasks ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_Grow ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ); + //RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_CancelTasks ); } /*-----------------------------------------------------------*/ - /** +/** * @brief Number of iterations for each test loop. */ #ifndef _TASKPOOL_TEST_ITERATIONS -#define _TASKPOOL_TEST_ITERATIONS ( 200 ) + #define _TASKPOOL_TEST_ITERATIONS ( 200 ) #endif /** @@ -145,6 +146,8 @@ static struct itimerspec _TEST_DELAY_50MS = .it_interval = { 0 } }; +#define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) + /* ---------------------------------------------------------- */ /** @@ -196,12 +199,14 @@ static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, void * pContext ) { JobUserContext_t * pUserContext; + IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); + error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); + TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); + TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); EmulateWork(); @@ -220,12 +225,14 @@ static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, void * pContext ) { JobBlockingUserContext_t * pUserContext; + IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); + error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); + TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); + TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); pUserContext = ( JobBlockingUserContext_t * ) pContext; @@ -245,12 +252,14 @@ static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, void * pContext ) { JobUserContext_t * pUserContext; + IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); + error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); + TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); + TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); EmulateWork(); @@ -271,12 +280,14 @@ static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, void * pContext ) { JobUserContext_t * pUserContext; + IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); + error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); + TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); + TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); EmulateWorkLong(); @@ -288,22 +299,24 @@ static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, } /** -* @brief A callback that does not recycle its job. -*/ + * @brief A callback that does not recycle its job. + */ static void BlankExecution( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, - void * pContext ) + IotTaskPoolJob_t * pJob, + void * pContext ) { + IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; - ( void )pTaskPool; - ( void )pJob; - ( void )pContext; + ( void ) pTaskPool; + ( void ) pJob; + ( void ) pContext; TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - TEST_ASSERT( IotTaskPool_GetStatus( pTaskPool, pJob, &status ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( status == IOT_TASKPOOL_STATUS_COMPLETED ); + error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); + TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); + TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); } /* ---------------------------------------------------------------------------------------------- */ @@ -345,11 +358,14 @@ IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = /** * @brief Test task pool dynamic memory creation and destruction, with both legal and illegal information. */ -TEST( Common_Unit_Task_Pool, CreateDestroy ) +TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) { +#define MAX_THREADS 7 + uint32_t count; IotTaskPool_t taskPool; + /* Some legal and illegal create/destroy patterns. */ for( count = 0; count < LEGAL_INFOS; ++count ) { TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ count ], &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -363,6 +379,37 @@ TEST( Common_Unit_Task_Pool, CreateDestroy ) TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ 0 ], NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); TEST_ASSERT( IotTaskPool_Create( NULL, &taskPool ) == IOT_TASKPOOL_BAD_PARAMETER ); + + /* Create a task pool a tweak max threads up & down. */ + { + uint32_t count; + IotTaskPoolJob_t jobs[ 2 * MAX_THREADS ] = { 0 }; + IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 5 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 3 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 4 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 7 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ + + /* Initialize more jobs than max threads. */ + for ( count = 0; count < 2 * MAX_THREADS; ++count ) + { + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + /* Schedule all jobs to make the task pool grow. */ + for( count = 0; count < 2 * MAX_THREADS; ++count ) + { + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + } + + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + } + +#undef MAX_THREADS } /*-----------------------------------------------------------*/ @@ -372,8 +419,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroy ) */ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) { -#define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) - IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -425,8 +470,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) } IotTaskPool_Destroy( &taskPool ); - -#undef ONE_HOUR_FROM_NOW_MS } /*-----------------------------------------------------------*/ @@ -436,8 +479,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) */ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) { -#define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) - IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -511,8 +552,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) } IotTaskPool_Destroy( &taskPool ); - -#undef ONE_HOUR_FROM_NOW_MS } /*-----------------------------------------------------------*/ @@ -581,6 +620,94 @@ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) /*-----------------------------------------------------------*/ +/** + * @brief Test scheduling a set of jobs: static allocation, bulk execution. + */ +TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) +{ +#define _LONG_JOBS_NUMBER 3 + + IotTaskPool_t taskPool; + IotTaskPoolJob_t * pRecyclableJob; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + IotTaskPoolJob_t tpJobs[ _LONG_JOBS_NUMBER ] = { { 0 } }; + IotTaskPoolJob_t tpDeferredJobs[ _LONG_JOBS_NUMBER ] = { { 0 } }; + + JobUserContext_t userContext = { 0 }; + + /* Initialize user context. */ + TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + + IotTaskPool_Create( &tpInfo, &taskPool ); + + /* Create a recyclable job we will never schedule, just to have it in the cache for code coverage purposes. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); + + /* Statically allocated jobs, schedule all, then wait all. */ + { + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolError_t errorSchedule; + + for( count = 0; count < _LONG_JOBS_NUMBER; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + + errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + + for( count = 0; count < _LONG_JOBS_NUMBER; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); + + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } + } + } + + /* Destroy the taskpool. It will empty all queues. */ + IotTaskPool_Destroy( &taskPool ); + + /* Destroy user context. */ + IotMutex_Destroy( &userContext.lock ); + +#undef _LONG_JOBS_NUMBER +} + +/*-----------------------------------------------------------*/ + /** * @brief Test scheduling a job with bad parameters. */ @@ -610,7 +737,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) /** * @brief Tests that the taskpool actually grows the number of tasks as expected. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_Grow ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) { #define _NUMBER_OF_JOBS 4 @@ -677,12 +804,12 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_Grow ) /*-----------------------------------------------------------*/ /** -* @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. -*/ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) + * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. + */ +TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) { -#define _NUMBER_OF_JOBS 4 -#define _NUMBER_OF_THREADS 3 +#define _NUMBER_OF_JOBS 4 +#define _NUMBER_OF_THREADS 3 IotTaskPool_t taskPool; @@ -703,7 +830,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) IotTaskPoolJob_t jobs[ _NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for ( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < _NUMBER_OF_JOBS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ @@ -711,7 +838,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) } /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ - for ( count = 0; count < _NUMBER_OF_THREADS; ++count ) + for( count = 0; count < _NUMBER_OF_THREADS; ++count ) { TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } @@ -721,21 +848,21 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) count = 0; - while ( true ) + while( true ) { /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ IotSemaphore_Wait( &userContext.signal ); ++count; - if ( count == _NUMBER_OF_JOBS ) + if( count == _NUMBER_OF_JOBS ) { break; } } /* Signal all taskpool threads to exit. */ - for ( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < _NUMBER_OF_JOBS; ++count ) { IotSemaphore_Post( &userContext.block ); } @@ -756,7 +883,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_GrowHighPri ) /** * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -840,7 +967,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneThenWait ) /** * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -924,7 +1051,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneDeferredThenWait /** * @brief Test scheduling a set of recyclable jobs: dynamic allocation, sequential execution. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneRecyclableThenWait ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -1004,7 +1131,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleOneRecyclableThenWai /** * @brief Test scheduling a set of jobs: static allocation, bulk execution. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -1075,7 +1202,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllThenWait ) /** * @brief Test scheduling a set of jobs: static allocation, bulk execution. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllRecyclableThenWait ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -1155,7 +1282,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllRecyclableThenWai /** * @brief Test scheduling a set of deferred jobs. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -1235,7 +1362,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ScheduleAllDeferredRecyclabl /** * @brief Test scheduling and re-scheduling (without canceling first) a set of jobs. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) { IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -1357,10 +1484,8 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReSchedule ) /** * @brief Test scheduling and re-scheduling (without canceling first) a set of deferred jobs. */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReScheduleDeferred ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) { -#define _ONE_HOUR_IN_MS ( 60 * 60 * 1000 ) - IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -1394,7 +1519,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReScheduleDeferred ) TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], _ONE_HOUR_IN_MS ); + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], ONE_HOUR_FROM_NOW_MS ); switch( errorSchedule ) { @@ -1462,8 +1587,6 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleTasks_ReScheduleDeferred ) /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); - -#undef _ONE_HOUR_IN_MS } /*-----------------------------------------------------------*/ From 4c8268e63b0dd2966bb41fbb188375a3c4d3e5eb Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Wed, 6 Mar 2019 19:41:21 -0800 Subject: [PATCH 043/844] Enable all tests (#307) --- lib/source/common/iot_taskpool.c | 9 +-------- tests/common/unit/iot_tests_taskpool.c | 23 ++++++++++++----------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index f6aa658c4d..9b23819ba3 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -424,13 +424,6 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, } _TASKPOOL_EXIT_CRITICAL_SECTION; - //i = count; - - //while( count-- > 0 ) - //{ - // IotSemaphore_Wait( &pTaskPool->startStopSignal ); - //} - _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -715,7 +708,7 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); - + *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; _TASKPOOL_ENTER_CRITICAL_SECTION; diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index b041b601ec..3d9e4fb2c5 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -107,17 +107,17 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasksError ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_Grow ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ); - //RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_CancelTasks ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ); + RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_CancelTasks ); } /*-----------------------------------------------------------*/ @@ -394,11 +394,12 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ /* Initialize more jobs than max threads. */ - for ( count = 0; count < 2 * MAX_THREADS; ++count ) + for( count = 0; count < 2 * MAX_THREADS; ++count ) { /* Create legal recyclable job. */ TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } + /* Schedule all jobs to make the task pool grow. */ for( count = 0; count < 2 * MAX_THREADS; ++count ) { From 11f7d6373b278809fb72822e665e5ef589b78738 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Thu, 7 Mar 2019 20:13:48 -0800 Subject: [PATCH 044/844] Add common tests (lists, taskpool, ...) and defender to code coverage run. (#308) --- scripts/coverage.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/coverage.sh b/scripts/coverage.sh index de094946c0..aa6fc5e825 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -17,6 +17,9 @@ rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" make +# Run Common tests with code coverage. +./bin/iot_tests_mqtt 2&> /dev/null + # Run MQTT tests and demo against AWS IoT with code coverage. ./bin/iot_tests_mqtt 2&> /dev/null ./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" 2&> /dev/null @@ -25,6 +28,9 @@ make ./bin/aws_iot_tests_shadow 2&> /dev/null ./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" 2&> /dev/null +# Run Defender tests with code coverage. +./bin/aws_iot_tests_defender 2&> /dev/null + # Generate code coverage results, excluding Unity test framework, demo files, and tests files. lcov --directory . --capture --output-file coverage.info lcov --remove coverage.info '*unity*' --output-file coverage.info From 42375390b183429f62e2fa684224e24e7d5e2aae Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Fri, 15 Mar 2019 20:11:49 -0700 Subject: [PATCH 045/844] Update coverage.sh --- scripts/coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/coverage.sh b/scripts/coverage.sh index aa6fc5e825..b9a78ae5ba 100644 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -18,7 +18,7 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TES make # Run Common tests with code coverage. -./bin/iot_tests_mqtt 2&> /dev/null +./bin/iot_tests_common 2&> /dev/null # Run MQTT tests and demo against AWS IoT with code coverage. ./bin/iot_tests_mqtt 2&> /dev/null From 2143ba73c61f2e40df69ae202e8a66b53de0aa9d Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Sat, 16 Mar 2019 16:59:25 -0700 Subject: [PATCH 046/844] Address most linting and MISRA issues. (#318) * Task pool documentation improvements. * Split status and flags for MISRA and address most MISRA & lint issues, including MISRA issues for container extraction (IotLink_Container). --- doc/config/layout_library.xml | 27 ++- doc/config/taskpool | 7 +- doc/lib/taskpool.txt | 32 +-- .../taskpool_design_typicaloperation.pu | 10 +- lib/include/iot_linear_containers.h | 4 +- lib/include/iot_taskpool.h | 67 +++--- lib/include/private/iot_taskpool_internal.h | 60 ++++-- lib/include/types/iot_taskpool_types.h | 55 +++-- lib/source/common/iot_taskpool.c | 194 ++++++++++-------- tests/common/unit/iot_tests_taskpool.c | 143 +++++++------ 10 files changed, 332 insertions(+), 267 deletions(-) diff --git a/doc/config/layout_library.xml b/doc/config/layout_library.xml index 7aebbfee9b..2437f4ca05 100644 --- a/doc/config/layout_library.xml +++ b/doc/config/layout_library.xml @@ -1,28 +1,25 @@ - - - + - - - - - + + + + + - - - + + + - + @@ -112,8 +109,8 @@ - - + + diff --git a/doc/config/taskpool b/doc/config/taskpool index 7b66be5dbc..c8deb7d33d 100644 --- a/doc/config/taskpool +++ b/doc/config/taskpool @@ -17,17 +17,18 @@ INPUT = doc \ doc/lib \ lib/include \ lib/include/private \ + lib/include/types \ lib/source/common \ demos/ \ tests/ \ - tests/taskpool/unit \ - tests/taskpool/system + tests/common/unit # Library file names. FILE_PATTERNS = *taskpool*.c *taskpool*.h *taskpool*.txt # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ - doc/tag/queue.tag=../queue \ + doc/tag/linear_containers.tag=../linear_containers \ doc/tag/logging.tag=../logging \ doc/tag/platform.tag=../platform \ + doc/tag/static_memory.tag=../static_memory \ diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index 207b7ed1b9..9239ede3d8 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -46,7 +46,7 @@ digraph taskpool_dependencies Currently, the task pool library has the following dependencies: - The linear containers (list/queue) library for maintaining the data structures for scheduled and in-progress task pool operations. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_TASKPOOL is not @ref AWS_IOT_LOG_NONE. +- The logging library may be used if @ref IOT_LOG_LEVEL_TASKPOOL is not @ref IOT_LOG_NONE. - The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. In addition to the components above, the task pool library may also depend on C standard library headers. @@ -78,7 +78,7 @@ The task pool demo demonstrates the create-schedule workflow of task pool. After See @subpage taskpool_demo_config for configuration settings that change the behavior of the demo. -The main task pool demo file, aws_iot_demo_taskpool.c, contains platform-independent code. See the following guides for running the task pool demo on various platforms. +The main task pool demo file, iot_demo_taskpool.c, contains platform-independent code. See the following guides for running the task pool demo on various platforms. - @subpage demo_posix
@copybrief demo_posix */ @@ -99,7 +99,7 @@ The current task pool tests use the [Unity test framework](http://www.throwthesw /** @configpage{taskpool_tests,task pool tests,Test,tests} -
The settings below only affect the [stress](@ref aws_iot_tests_taskpool_stress.c) tests.
+
The settings below only affect the [stress](@ref iot_tests_taskpool_stress.c) tests.
*/ @@ -117,9 +117,9 @@ The current task pool tests use the [Unity test framework](http://www.throwthesw Before building the task pool tests, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [test](@ref global_tests_config) settings, as well as settings for specific libraries and demos. -All tests specify their configuration settings in `tests/aws_iot_tests_config.h`. Any undefined settings will use a default value when possible. +All tests specify their configuration settings in `tests/iot_tests_config.h`. Any undefined settings will use a default value when possible. -The task pool tests build with a POSIX Makefile. A single Makefile will build all the task pool tests in both dynamic memory and static memory only modes. See @ref AWS_IOT_STATIC_MEMORY_ONLY for more information about static memory only mode. The Makefile also builds tests against AWS IoT and a [public Mosquitto test server](https://test.mosquitto.org/). +The task pool tests build with a POSIX Makefile. A single Makefile will build all the task pool tests in both dynamic memory and static memory only modes. See @ref IOT_STATIC_MEMORY_ONLY for more information about static memory only mode. The Makefile also builds tests against AWS IoT and a [public Mosquitto test server](https://test.mosquitto.org/). The Makefile for building the task pool tests is located in `tests/taskpool`. To build the tests: @code @@ -128,7 +128,7 @@ make @endcode The Makefile will generate four test executables: -- `aws_iot_tests_taskpool_aws`: Tests against AWS IoT in dynamic memory mode. +- `iot_tests_taskpool_aws`: Tests against AWS IoT in dynamic memory mode. Run `make clean` to remove all test executables. @@ -146,7 +146,7 @@ By default, the task pool test executables don't run the stress tests, which may /** @configpage{taskpool_demo,task pool demo,Demo,demos} -@section AWS_IOT_DEMO_MQTT_CLIENT_IDENTIFIER +@section IOT_DEMO_MQTT_CLIENT_IDENTIFIER @brief The task pool client identifier to use for the demo. */ @@ -154,36 +154,36 @@ By default, the task pool test executables don't run the stress tests, which may /** @configpage{taskpool,task pool library} -@section AWS_IOT_TASKPOOL_THREADS_STACK_MIN +@section IOT_TASKPOOL_THREADS_STACK_MIN @brief Set this to minimum stack depth to be enforced at runtime. -@section AWS_IOT_TASKPOOL_THREADS_STACK_SIZE +@section IOT_TASKPOOL_THREADS_STACK_SIZE @brief Set this to the default stack depth used by the task pool initializers. -@section AWS_IOT_TASKPOOL_PRIORITY +@section IOT_TASKPOOL_PRIORITY @brief Set this to the default task priority used by the task pool initializers. -@section AWS_IOT_TASKPOOL_ENABLE_ASSERTS +@section IOT_TASKPOOL_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the task pool library. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotTaskPool_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotTaskPool_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
@configdefault `0` -@section AWS_IOT_LOG_LEVEL_TASKPOOL +@section IOT_LOG_LEVEL_TASKPOOL @brief Set the log level of the task pool library. Log messages from the task pool library at or below this setting will be printed. @configpossible One of the @ref logging_constants_levels.
-@configdefault @ref AWS_IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #AWS_IOT_LOG_NONE. +@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. -@section AWS_IOT_TASKPOOL_TEST +@section IOT_TASKPOOL_TEST @brief Set this to `1` to enable test access for the task pool library. -This setting is used by the [task pool tests](@ref taskpool_tests) to access the internal `static` functions and variables of the task pool library. When `1`, it enables the @c \#include directives at the bottom of the [task pool library source files](@ref lib/source/taskpool). These @c \#include directives include the test access files (located in `tests/taskpool/access`) that interact with the library's internal symbols. Unless the task pool library is being tested, this setting should be `0`. +This setting is used by the [task pool tests](@ref taskpool_tests) to access the internal `static` functions and variables of the task pool library. When `1`, it enables the @c \#include directives at the bottom of the [task pool library source files](@ref lib/source/common/). These @c \#include directives include the test access files (located in `tests/taskpool/access`) that interact with the library's internal symbols. Unless the task pool library is being tested, this setting should be `0`. @configpossible `0` (test access disabled) or `1` (test access enabled)
@configrecommended `0`
diff --git a/doc/plantuml/taskpool_design_typicaloperation.pu b/doc/plantuml/taskpool_design_typicaloperation.pu index eaf919e5e9..50afed070a 100644 --- a/doc/plantuml/taskpool_design_typicaloperation.pu +++ b/doc/plantuml/taskpool_design_typicaloperation.pu @@ -21,7 +21,7 @@ end box activate app -app -[#blue]> TP: AwsIotTaskPool_Create: create a Task +app -[#blue]> TP: IotTaskPool_Create: create a Task TP -> queue: Initialize dispatch queue activate queue TP -> workers: Create minimum number of worker threads @@ -33,12 +33,12 @@ workers -> workers: Wait on incoming jobs == Use Task Pool == loop Application loop: Create and schedule jobs - app -[#blue]> TP: AwsIotTaskPool_CreateRecyclableJob: create a job + app -[#blue]> TP: IotTaskPool_CreateRecyclableJob: create a job TP --[#blue]> app activate job note left: job status: //ready// - app -[#blue]> TP: AwsIotTaskPool_Schedule: schedule a job + app -[#blue]> TP: IotTaskPool_Schedule: schedule a job TP -> queue: Queue job TP -> TP: Grow pool up to maximum threads, if all threads are busy TP -> workers: Signal incoming job @@ -52,7 +52,7 @@ loop Application loop: Create and schedule jobs note left: job status: //executing// job -[#green]> callback: Invoke activate callback - callback -[#blue]> TP: AwsIotTaskPool_RecycleJob: recycles job + callback -[#blue]> TP: IotTaskPool_RecycleJob: recycles job TP --[#blue]> callback note left: job status: //completed// deactivate job @@ -66,7 +66,7 @@ end == Destroy Task Pool == - app -[#blue]> TP: AwsIotTaskPool_Destroy: destroy the task pool + app -[#blue]> TP: IotTaskPool_Destroy: destroy the task pool TP -> workers: Shutdown worker threads deactivate workers TP -> queue: Destroy all jobs in the dispatch queue diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 03a0bd666b..070b793684 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -116,7 +116,7 @@ typedef IotLink_t IotQueue_t; * @param[in] linkName Name of the #IotLink_t in the containing struct. */ #define IotLink_Container( type, pLink, linkName ) \ - ( ( type * ) ( ( ( uint8_t * ) ( pLink ) ) - offsetof( type, linkName ) ) ) + ( ( type * ) ( void * ) ( ( ( uint8_t * ) ( pLink ) ) - offsetof( type, linkName ) ) ) /** * @functionspage{linear_containers,linear containers library} @@ -596,7 +596,7 @@ static inline void IotListDouble_RemoveAll( IotListDouble_t * const pList, /* @[declare_linear_containers_list_double_findfirstmatch] */ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * const pList, const IotLink_t * const pStartPoint, - bool ( *isMatch )( const IotLink_t *, void * ), + bool ( *isMatch )( const IotLink_t * const, void * ), void * pMatch ) /* @[declare_linear_containers_list_double_findfirstmatch] */ { diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index c28cf435bc..72f113ac95 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -86,8 +86,8 @@ * * This function does not allocate memory to hold the task pool data structures and state, but it * may allocate memory to hold the dependent entities and data structures, e.g. the threads of the task - * pool. The system task pool handle is recoverable for later use by calling (@ref taskpool_function_getsystemtaskpool) or - * the shortcut @ref IOT_TASKPOOL_SYSTEM_TASKPOOL. + * pool. The system task pool handle is recoverable for later use by calling @ref taskpool_function_getsystemtaskpool or + * the shortcut @ref IOT_SYSTEM_TASKPOOL. * * @param[in] pInfo A pointer to the task pool initialization data. * @@ -107,8 +107,8 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c /** * @brief Retrieves the one and only instance of a system task pool * - * This function retrieves the sytem task pool created with (@ref taskpool_function_createsystemtaskpool), and it is functionally - * equivalent to using the shortcut @ref AWS_IOT_TASKPOOL_SYSTEM_TASKPOOL. + * This function retrieves the sytem task pool created with @ref taskpool_function_createsystemtaskpool, and it is functionally + * equivalent to using the shortcut @ref IOT_SYSTEM_TASKPOOL. * * @return The system task pool handle. * @@ -155,8 +155,7 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, * This function should be called to destroy one instance of a task pool previously created with a call * to @ref taskpool_function_create or @ref taskpool_function_createsystemtaskpool. * Calling this fuction release all underlying resources. After calling this function, any job scheduled but not yet executed - * will be cancelled and destroyed, as if the user called @ref AwsIotTaskPool_DestroyJob on every job - * previously scheduled. + * will be cancelled and destroyed. * The `pTaskPool` instance will no longer be valid after this function returns. * * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create or @@ -187,12 +186,12 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ); * @return One of the following: * - #IOT_TASKPOOL_SUCCESS * - #IOT_TASKPOOL_BAD_PARAMETER - * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * */ /* @[declare_taskpool_setmaxthreads] */ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, - uint32_t maxThreads ); + size_t maxThreads ); /* @[declare_taskpool_setmaxthreads] */ /** @@ -222,24 +221,24 @@ IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallbac * brief Creates a job for the task pool by allocating the job dynamically. * * A recyclable job does not need to be allocated twice, but it can rather be reused through - * subsequent calls to @ref AwsIotTaskPool_CreateRecyclableJob. + * subsequent calls to @ref IotTaskPool_CreateRecyclableJob. * * @param[in] pTaskPool A handle to the task pool for which to create a recyclable job. * @param[in] userCallback A user-specified callback for the job. * @param[in] pUserContext A user-specified context for the callback. - * @param[out] ppJob A pointer to an instance of @ref AwsIotTaskPoolJob_t that will be initialized when this + * @param[out] ppJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this * function returns succesfully. This handle can be used to inspect the job status with * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS - * - #AWS_IOT_TASKPOOL_BAD_PARAMETER - * - #AWS_IOT_TASKPOOL_NO_MEMORY + * - #IOT_TASKPOOL_BAD_PARAMETER + * - #IOT_TASKPOOL_NO_MEMORY * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * * @note This function will not allocate memory. * - * @warning A recyclable job should be recycled with a call to @ref AwsIotTaskPool_RecycleJob rather than destroyed. + * @warning A recyclable job should be recycled with a call to @ref IotTaskPool_RecycleJob rather than destroyed. * */ /* @[declare_taskpool_createrecyclablejob] */ @@ -252,11 +251,11 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP /** * @brief This function uninitializes a job. * - * This function will destroy a job created with @ref AwsIotTaskPool_CreateRecyclableJob. + * This function will destroy a job created with @ref IotTaskPool_CreateRecyclableJob. * A job should not be destroyed twice. A job that was previously scheduled but has not completed yet should not be destroyed, - * but rather the application should attempt to cancel it first by calling @ref AwsIotTaskPool_TryCancel. + * but rather the application should attempt to cancel it first by calling @ref IotTaskPool_TryCancel. * An attempt to destroy a job that was scheduled but not yet executed or canceled, may result in a - * @ref AWS_IOT_TASKPOOL_ILLEGAL_OPERATION error. + * @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. * * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create. * @param[in] pJob A handle to a job that was create with a call to @ref IotTaskPool_CreateJob. @@ -265,14 +264,14 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP * - #IOT_TASKPOOL_SUCCESS * - #IOT_TASKPOOL_BAD_PARAMETER * - #IOT_TASKPOOL_ILLEGAL_OPERATION - * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * * @warning The task pool will try and prevent destroying jobs that are currently queued for execution, but does * not enforce strict ordering of operations. It is up to the user to make sure @ref IotTaskPool_DestroyRecyclableJob is not called * our of order. * - * @warning Calling this function on job that was not previously created with @ref AwsIotTaskPool_CreateRecyclableJob - * will result in a @ref AWS_IOT_TASKPOOL_ILLEGAL_OPERATION error. + * @warning Calling this function on job that was not previously created with @ref IotTaskPool_CreateRecyclableJob + * will result in a @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. * */ /* @[declare_taskpool_destroyrecyclablejob] */ @@ -283,8 +282,8 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask /** * @brief Rrecycles a job into the task pool job cache. * - * This function will try and recycle the job into the task pool cache. If the cache is at the upper limit, - * the job memory is destroyed as if the user called @ref AwsIotTaskPool_DestroyJob. The job should be recycled into + * This function will try and recycle the job into the task pool cache. If the cache is full, + * the job memory is destroyed as if the user called @ref IotTaskPool_DestroyRecyclableJob. The job should be recycled into * the task pool instance from where it was allocated. * Failure to do so will yield undefined results. A job should not be recycled twice. A job * that was previously scheduled but not completed or canceled cannot be safely recycled. An attempt to do so will result @@ -297,7 +296,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask * - #IOT_TASKPOOL_SUCCESS * - #IOT_TASKPOOL_BAD_PARAMETER * - #IOT_TASKPOOL_ILLEGAL_OPERATION - * - #AWS_IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS + * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * * @warning The `pTaskPool` used in this function should be the same * used to create the job pointed to by `pJob`, or the results will be undefined. @@ -324,7 +323,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. * a call to @ref taskpool_function_create. - * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. * @param[in] flags Flags to be passed by the user, e.g. to identify the job as high priority by specifying #IOT_TASKPOOL_JOB_HIGH_PRIORITY. * * @return One of the following: @@ -415,6 +414,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. * a call to @ref taskpool_function_create. * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * @param[in] timeMs The time in milliseconds to wait before scheduling the job. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -463,7 +463,7 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, * A job can be canceled only if it is not yet executing, i.e. if its status is * @ref IOT_TASKPOOL_STATUS_READY or @ref IOT_TASKPOOL_STATUS_SCHEDULED. Calling * @ref IotTaskPool_TryCancel on a job whose status is @ref IOT_TASKPOOL_STATUS_COMPLETED, - * or @AWS_IOT_TASKPOOL_STATUS_CANCELED will yield a @AWS_IOT_TASKPOOL_CANCEL_FAILED return result. + * or #IOT_TASKPOOL_STATUS_CANCELED will yield a #IOT_TASKPOOL_CANCEL_FAILED return result. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with * a call to @ref taskpool_function_create. @@ -486,6 +486,25 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, IotTaskPoolJobStatus_t * const pStatus ); /* @[declare_taskpool_trycancel] */ +/** + * @brief Returns a string that describes an @ref IotTaskPoolError_t. + * + * Like the POSIX's `strerror`, this function returns a string describing a + * return code. In this case, the return code is a task pool library error code, + * `status`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] status The status to describe. + * + * @return A read-only string that describes `status`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_taskpool_strerror] */ const char * IotTaskPool_strerror( IotTaskPoolError_t status ); +/* @[declare_taskpool_strerror] */ #endif /* ifndef _IOT_TASKPOOL_H_ */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index 1015475363..2e0fca7921 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -49,27 +49,35 @@ */ #define _TASKPOOL_GOTO_CLEANUP() _IOT_GOTO_CLEANUP() - /** +/** * @brief Declare the storage for the error status variable. */ #define _TASKPOOL_FUNCTION_ENTRY( result ) _IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) -#define _TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL() return status - /** * @brief Check error and leave in case of failure. */ #define _TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( _TASKPOOL_FAILED( status = ( expr ) ) ) { _IOT_GOTO_CLEANUP(); } } +/** + * @brief Exit if an argument is NULL. + */ +#define _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) + +/** + * @brief Exit if an argument is NULL. + */ +#define _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) + /** * @brief Set error and leave. */ -#define _TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) _IOT_SET_AND_GOTO_CLEANUP( expr ) +#define _TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) _IOT_SET_AND_GOTO_CLEANUP( expr ) /** * @brief Initialize error and declare start of cleanup area. */ -#define _TASKPOOL_FUNCTION_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN( ) +#define _TASKPOOL_FUNCTION_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN() /** * @brief Initialize error and declare end of cleanup area. @@ -82,15 +90,9 @@ #define _TASKPOOL_NO_FUNCTION_CLEANUP() _IOT_FUNCTION_EXIT_NO_CLEANUP() /** - * @brief Exit if an argument is NULL. - */ -#define _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) - -/** - * @brief Exit if an argument is NULL. + * @brief Does not create a cleanup area. */ -#define _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) - +#define _TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL() return status /** * @def IotTaskPool_Assert( expression ) @@ -132,28 +134,39 @@ #include "private/iot_static_memory.h" /** - * @brief Overridable allocator. + * @brief Allocate an #IotTaskPoolJob_t. This function should have the + * same signature as [malloc]. */ #ifndef IotTaskPool_MallocJob #define IotTaskPool_MallocJob Iot_MallocTaskPoolJob #endif +/** + * @brief Allocate an #_taskPoolTimerEvent_t. This function should have the + * same signature as [malloc]. + */ #ifndef IotTaskPool_MallocTimerEvent #define IotTaskPool_MallocTimerEvent Iot_MallocTaskPoolTimerEvent #endif /** - * @brief Overridable deallocator. + * @brief Free an #IotTaskPoolJob_t. This function should have the same + * signature as [free] */ #ifndef IotTaskPool_FreeJob #define IotTaskPool_FreeJob Iot_FreeTaskPoolJob #endif +/** + * @brief Free an #_taskPoolTimerEvent_t. This function should have the + * same signature as[ free ]. + */ #ifndef IotTaskPool_FreeTimerEvent #define IotTaskPool_FreeTimerEvent Iot_FreeTaskPoolTimerEvent #endif #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ + #include /** @@ -197,11 +210,9 @@ * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. * - * A few macros to manage task pool status. + * A macros to manage task pool memory allocation. */ -#define IOT_TASKPOOL_STATUS_MASK 0x0F /* Lower 4 bits reserved for status (IotTaskPoolJobStatus_t). */ -#define IOT_TASKPOOL_FLAGS_MASK 0xF0 /* Upper 4 bits reserved for flags. */ -#define IOT_TASK_POOL_INTERNAL_STATIC 0x80 /* Flag to mark a job as user-allocated. */ +#define IOT_TASK_POOL_INTERNAL_STATIC ( ( uint32_t ) 0x00000001 ) /* Flag to mark a job as user-allocated. */ /** @endcond */ /** @@ -213,8 +224,19 @@ typedef struct _taskPoolTimerEvent { IotLink_t link; /**< @brief List link member. */ + uint64_t expirationTime; /**< @brief When this event should be processed. */ IotTaskPoolJob_t * pJob; /**< @brief The task pool job associated with this event. */ } _taskPoolTimerEvent_t; + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * System task pool. + */ +extern IotTaskPool_t _IotSystemTaskPool; +/** @endcond */ + #endif /* ifndef _IOT_TASKPOOL_INTERNAL_H_ */ diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index c564e737c5..81aeb3294c 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -63,7 +63,8 @@ typedef enum IotTaskPoolError * - @ref taskpool_function_destroy * - @ref taskpool_function_setmaxthreads * - @ref taskpool_function_createjob - * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_createrecyclablejob + * - @ref taskpool_function_destroyrecyclablejob * - @ref taskpool_function_recyclejob * - @ref taskpool_function_schedule * - @ref taskpool_function_scheduledeferred @@ -82,7 +83,8 @@ typedef enum IotTaskPoolError * - @ref taskpool_function_destroy * - @ref taskpool_function_setmaxthreads * - @ref taskpool_function_createjob - * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_createrecyclablejob + * - @ref taskpool_function_destroyrecyclablejob * - @ref taskpool_function_recyclejob * - @ref taskpool_function_schedule * - @ref taskpool_function_scheduledeferred @@ -96,7 +98,9 @@ typedef enum IotTaskPoolError * @brief Task pool operation failed because it is illegal. * * Functions that may return this value: - * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_createjob + * - @ref taskpool_function_createrecyclablejob + * - @ref taskpool_function_destroyrecyclablejob * - @ref taskpool_function_recyclejob * - @ref taskpool_function_schedule * - @ref taskpool_function_scheduledeferred @@ -125,7 +129,7 @@ typedef enum IotTaskPoolError * Functions that may return this value: * - @ref taskpool_function_setmaxthreads * - @ref taskpool_function_createrecyclablejob - * - @ref taskpool_function_destroyjob + * - @ref taskpool_function_destroyrecyclablejob * - @ref taskpool_function_recyclejob * - @ref taskpool_function_schedule * - @ref taskpool_function_scheduledeferred @@ -202,10 +206,10 @@ struct IotTaskPool; struct IotTaskPoolJob; /** @endcond */ -/*---------------------------- Task pool function pointer types ----------------------------*/ +/*------------------------- Task pool parameter structs --------------------------*/ /** - * @functionpointers{taskpool,task pool library} + * @paramstructs{taskpool,task pool} */ /** @@ -221,12 +225,6 @@ typedef void ( * IotTaskPoolRoutine_t )( struct IotTaskPool * pTaskPool, struct IotTaskPoolJob * pJob, void * pUserContext ); -/*------------------------- Task pool parameter structs --------------------------*/ - -/** - * @paramstructs{taskpool,task pool} - */ - /** * @ingroup taskpool_datatypes_paramstructs * @brief Initialization information to create one task pool instance. @@ -248,10 +246,10 @@ typedef struct IotTaskPoolInfo * number of worker threads at run time. */ - uint32_t minThreads; /**< @brief Minimum number of threads in a task pool. These threads will be created when the task pool is first created with @ref taskpool_function_create. */ - uint32_t maxThreads; /**< @brief Maximum number of threads in a task pool. A task pool may try and grow the number of active threads up to #AwsIotTaskPoolInfo_t.maxThreads. */ - uint32_t stackSize; /**< @brief Stack size for every task pool thread. The stack size for each thread is fixed after the task pool is created and cannot be changed. */ - uint32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ + size_t minThreads; /**< @brief Minimum number of threads in a task pool. These threads will be created when the task pool is first created with @ref taskpool_function_create. */ + size_t maxThreads; /**< @brief Maximum number of threads in a task pool. A task pool may try and grow the number of active threads up to #IotTaskPoolInfo_t.maxThreads. */ + size_t stackSize; /**< @brief Stack size for every task pool thread. The stack size for each thread is fixed after the task pool is created and cannot be changed. */ + int32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ } IotTaskPoolInfo_t; /*------------------------- Task pool handles structs --------------------------*/ @@ -266,7 +264,7 @@ typedef struct IotTaskPoolInfo typedef struct IotTaskPoolCache { IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ - uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ + size_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ } IotTaskPoolCache_t; @@ -283,10 +281,10 @@ typedef struct IotTaskPool IotQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ IotTaskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ - uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ - uint32_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ - uint32_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ - uint32_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ + size_t minThreads; /**< @brief The minimum number of threads for the task pool. */ + size_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ + size_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ + size_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ size_t stackSize; /**< @brief The stack size for all task pool threads. */ int32_t priority; /**< @brief The priority for all task pool threads. */ IotSemaphore_t dispatchSignal; /**< @brief The synchronization object on which threads are waiting for incoming jobs. */ @@ -307,6 +305,7 @@ typedef struct IotTaskPoolJob IotTaskPoolRoutine_t userCallback; /**< @brief The user provided callback. */ void * pUserContext; /**< @brief The user provided context. */ IotLink_t link; /**< @brief The link to insert the job in the dispatch queue. */ + uint32_t flags; /**< @brief Internal flags. */ IotTaskPoolJobStatus_t status; /**< @brief The status for the job. */ } IotTaskPoolJob_t; @@ -353,12 +352,12 @@ typedef struct IotTaskPoolJob /* @[define_taskpool_initializers] */ /** -* @brief Schedules a job to execute immediately. -* -* @warning This flag may cause the task pool to create a worker to serve the job immediately, and -* therefore using this flag may incur in additinal memory usage. -*/ -#define IOT_TASKPOOL_JOB_HIGH_PRIORITY 0x00000001 + * @brief Schedules a job to execute immediately. + * + * @warning This flag may cause the task pool to create a worker to serve the job immediately, and + * therefore using this flag may incur in additinal memory usage. + */ +#define IOT_TASKPOOL_JOB_HIGH_PRIORITY ( ( uint32_t ) 0x00000001 ) /** * @brief Allows the use of the handle to the system task pool. @@ -366,6 +365,6 @@ typedef struct IotTaskPoolJob * @warning The task pool handle is not valid unless @ref taskpool_function_createsystemtaskpool is * called before the handle is used. */ -#define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) +#define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) #endif /* ifndef _IOT_TASKPOOL_TYPES_H_ */ diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 9b23819ba3..479c5e161b 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -41,29 +41,29 @@ * @brief Enter a critical section by locking a mutex. * */ -#define _TASKPOOL_ENTER_CRITICAL_SECTION IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define INTERNAL_TASKPOOL_ENTER_CRITICAL IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Try entering a critical section by trying and lock a mutex. * */ -#define _TASKPOOL_TRY_ENTER_CRITICAL_SECTION IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define INTERNAL_TASKPOOL_TRY_ENTER_CRITICAL IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Exit a critical section by unlocking a mutex. * */ -#define _TASKPOOL_EXIT_CRITICAL_SECTION IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define INTERNAL_TASKPOOL__EXIT_CRITICAL IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Maximum semaphore value for wait operations. */ -#define _TASKPOOL_MAX_SEM_VALUE 0xFFFF +#define INTERNAL_TASKPOOL__TASKPOOL_MAX_SEM_VALUE 0xFFFF /** * @brief Reschedule delay in milliseconds for deferred jobs. */ -#define _TASKPOOL_JOB_RESCHEDULE_DELAY_MS 10 +#define INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS ( ( uint64_t ) 10 ) /* ---------------------------------------------------------------------------------------------- */ @@ -178,7 +178,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ * */ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, - IotTaskPool_t * const ppTaskPool ); + IotTaskPool_t * const pTaskPool ); /** * Destroys one instance of a task pool. @@ -223,7 +223,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, * @param[in] pMatch A pointer to the job to match. * */ -static bool _matchJobByPointer( const IotLink_t * pLink, +static bool _matchJobByPointer( const IotLink_t * const pLink, void * pMatch ); /** @@ -299,7 +299,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /* Destroying the task pool should be safe, and therefore we will grab the task pool lock. * No worker thread or application thread should access any data structure * in the task pool while the task pool is being destroyed. */ - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { IotLink_t * pItemLink; @@ -362,7 +362,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /* (4) Set the exit condition. */ _signalShutdown( pTaskPool, activeThreads ); } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; /* (5) Wait for all active threads to reach the end of their life-span. */ for( count = 0; count < activeThreads; ++count ) @@ -379,7 +379,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /*-----------------------------------------------------------*/ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, - uint32_t maxThreads ) + size_t maxThreads ) { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -388,14 +388,14 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, /* Parameter checking. */ _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < 1 ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < ( size_t ) 1 ); - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } @@ -416,13 +416,15 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, i = count; - while( i-- > 0 ) + while( i > ( size_t ) 0 ) { IotSemaphore_Post( &pTaskPool->dispatchSignal ); + + --i; } } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -462,19 +464,19 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP { IotTaskPoolJob_t * pTempJob = NULL; - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } pTempJob = _fetchOrAllocateJob( &pTaskPool->jobsCache ); } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; /* Initialize all members. */ if( pTempJob == NULL ) @@ -503,7 +505,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -511,7 +513,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; } /* Do not destroy statically allocated jobs. */ - else if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == IOT_TASK_POOL_INTERNAL_STATIC ) + else if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == IOT_TASK_POOL_INTERNAL_STATIC ) { IotLogWarn( "Attempt to destroy a statically allocated job." ); @@ -522,7 +524,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask status = _trySafeExtraction( pTaskPool, pJob, true ); } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; if( _TASKPOOL_SUCCEEDED( status ) ) { @@ -546,7 +548,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -554,7 +556,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; } /* Do not recycle statically allocated jobs. */ - else if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ) + else if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == ( ( uint32_t ) 0 ) ) { status = _trySafeExtraction( pTaskPool, pJob, true ); } @@ -574,7 +576,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, _recycleJob( &pTaskPool->jobsCache, pJob ); } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -590,9 +592,9 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /* Parameter checking. */ _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0 ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != ( ( uint32_t ) 0 ) ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -610,7 +612,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, status = _scheduleInternal( pTaskPool, pJob, flags ); } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -627,17 +629,17 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - if( timeMs == 0 ) + if( timeMs == ( ( uint32_t ) 0 ) ) { _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); } - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } @@ -649,7 +651,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool if( pTimerEvent == NULL ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } @@ -667,8 +669,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool IotListDouble_InsertSorted( &pTaskPool->timerEventsList, &pTimerEvent->link, _timerEventCompare ); /* Update the job status to 'scheduled'. */ - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_DEFERRED; + pJob->status = IOT_TASKPOOL_STATUS_DEFERRED; /* Peek the first event in the timer event list. There must be at least one, * since we just inserted it. */ @@ -686,12 +687,12 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool } else { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -711,19 +712,19 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } - *pStatus = ( pJob->status & IOT_TASKPOOL_STATUS_MASK ); + *pStatus = pJob->status; } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -745,19 +746,19 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; } - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Check again if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } status = _tryCancelInternal( pTaskPool, pJob, pStatus ); } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -822,8 +823,8 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo ); _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < 1 ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < 1 ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < ( ( uint32_t ) 1 ) ); + _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < ( ( uint32_t ) 1 ) ); /* Initialize all internal data structure prior to creating all threads. */ _TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); @@ -919,7 +920,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ _initJobsCache( &pTaskPool->jobsCache ); /* Initialize the semaphore to ensure all threads have started. */ - if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, _TASKPOOL_MAX_SEM_VALUE ) ) + if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, INTERNAL_TASKPOOL__TASKPOOL_MAX_SEM_VALUE ) ) { semStartStopInit = true; @@ -928,7 +929,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ lockInit = true; /* Initialize the semaphore for waiting for incoming work. */ - if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, _TASKPOOL_MAX_SEM_VALUE ) ) + if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, INTERNAL_TASKPOOL__TASKPOOL_MAX_SEM_VALUE ) ) { semDispatchInit = true; } @@ -1007,7 +1008,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified, * or before waiting for incoming notifications. */ - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* If the exit condition is verified, update the number of active threads and exit the loop. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -1017,7 +1018,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Decrease the number of active threads. */ pTaskPool->activeThreads--; - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; /* Signal that this worker is exiting. */ IotSemaphore_Post( &pTaskPool->startStopSignal ); @@ -1050,11 +1051,10 @@ static void _taskPoolWorker( void * pUserContext ) pJob = IotLink_Container( IotTaskPoolJob_t, pFirst, link ); /* Update status to 'executing'. */ - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_COMPLETED; + pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */ while( pJob != NULL ) @@ -1082,7 +1082,7 @@ static void _taskPoolWorker( void * pUserContext ) } /* Acquire the lock before updating the job status. */ - _TASKPOOL_ENTER_CRITICAL_SECTION; + INTERNAL_TASKPOOL_ENTER_CRITICAL; { /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ pTaskPool->activeJobs--; @@ -1096,7 +1096,7 @@ static void _taskPoolWorker( void * pUserContext ) /* If there is no job left in the dispatch queue, update the worker status and leave. */ if( pItem == NULL ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ break; @@ -1106,10 +1106,9 @@ static void _taskPoolWorker( void * pUserContext ) pJob = IotLink_Container( IotTaskPoolJob_t, pItem, link ); } - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_COMPLETED; + pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; } } while( running == true ); } @@ -1137,7 +1136,8 @@ static void _initializeJob( IotTaskPoolJob_t * const pJob, if( isStatic ) { - pJob->status = IOT_TASKPOOL_STATUS_READY | IOT_TASK_POOL_INTERNAL_STATIC; + pJob->flags = IOT_TASK_POOL_INTERNAL_STATIC; + pJob->status = IOT_TASKPOOL_STATUS_READY; } else { @@ -1166,6 +1166,7 @@ static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache } else { + /* Log alocation failure for troubleshooting purposes. */ IotLogInfo( "Failed to allocate job." ); } } @@ -1189,15 +1190,14 @@ static void _recycleJob( IotTaskPoolCache_t * const pCache, IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); /* We will recycle the job if there is space in the cache. */ - if( pCache->freeCount < IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ) + if( pCache->freeCount < ( size_t ) IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ) { /* Destroy user data, for added safety&security. */ pJob->userCallback = NULL; pJob->pUserContext = NULL; /* Reset the status for added debuggability. */ - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_UNDEFINED; + pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED; IotListDouble_InsertTail( &pCache->freeList, &pJob->link ); @@ -1220,11 +1220,10 @@ static void _destroyJob( IotTaskPoolJob_t * const pJob ) pJob->pUserContext = NULL; /* Reset the status for added debuggability. */ - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_UNDEFINED; + pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED; /* Only dispose of dynamically allocated jobs. */ - if( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ) + if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == ( ( uint32_t ) 0 ) ) { IotTaskPool_FreeJob( pJob ); } @@ -1234,7 +1233,7 @@ static void _destroyJob( IotTaskPoolJob_t * const pJob ) static bool _IsShutdownStarted( const IotTaskPool_t * const pTaskPool ) { - return( pTaskPool->maxThreads == 0 ); + return( pTaskPool->maxThreads == ( ( uint32_t ) 0 ) ); } /*-----------------------------------------------------------*/ @@ -1266,8 +1265,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, bool shouldGrow = false; /* Update the job status to 'scheduled'. */ - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_SCHEDULED; + pJob->status = IOT_TASKPOOL_STATUS_SCHEDULED; /* Update the number of active jobs optimistically, so new requests can be served by creating new threads. */ pTaskPool->activeJobs++; @@ -1293,6 +1291,10 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, { shouldGrow = true; } + else + { + /* Nothing to do. */ + } if( ( mustGrow == true ) || ( shouldGrow == true ) ) { @@ -1346,12 +1348,12 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, /*-----------------------------------------------------------*/ -static bool _matchJobByPointer( const IotLink_t * pLink, +static bool _matchJobByPointer( const IotLink_t * const pLink, void * pMatch ) { - const IotTaskPoolJob_t * pJob = ( IotTaskPoolJob_t * ) pMatch; + const IotTaskPoolJob_t * const pJob = ( IotTaskPoolJob_t * ) pMatch; - const _taskPoolTimerEvent_t * pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); + const _taskPoolTimerEvent_t * const pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); if( pJob == pTimerEvent->pJob ) { @@ -1373,7 +1375,7 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, /* We can only cancel jobs that are either 'ready' (waiting to be scheduled). 'deferred', or 'scheduled'. */ - IotTaskPoolJobStatus_t currentStatus = ( pJob->status & IOT_TASKPOOL_STATUS_MASK ); + IotTaskPoolJobStatus_t currentStatus = pJob->status; switch( currentStatus ) { @@ -1385,10 +1387,12 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, break; case IOT_TASKPOOL_STATUS_COMPLETED: + /* Log mesggesong purposes. */ IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." ); break; default: + /* Log mesggesong purposes. */ IotLogError( "Attempt to cancel a job with an undefined state." ); break; } @@ -1406,8 +1410,7 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, else { /* Update the status of the job. */ - pJob->status &= ~IOT_TASKPOOL_STATUS_MASK; - pJob->status |= IOT_TASKPOOL_STATUS_CANCELED; + pJob->status = IOT_TASKPOOL_STATUS_CANCELED; /* If the job is cancelable and its current status is 'scheduled' then unlink it from the dispatch * queue and signal any waiting threads. */ @@ -1455,6 +1458,11 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, } } } + else + { + /* A cancelable job status should be either 'scheduled' or 'deferrred'. */ + IotTaskPool_Assert( false ); + } } _TASKPOOL_NO_FUNCTION_CLEANUP(); @@ -1468,15 +1476,15 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, { _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - IotTaskPoolJobStatus_t jobStatus = pJob->status & IOT_TASKPOOL_STATUS_MASK; + IotTaskPoolJobStatus_t currentStatus = pJob->status; /* if the job is executing, we cannot touch it. */ - if( ( atCompletion == false ) && ( jobStatus == IOT_TASKPOOL_STATUS_COMPLETED ) ) + if( ( atCompletion == false ) && ( currentStatus == IOT_TASKPOOL_STATUS_COMPLETED ) ) { _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); } /* Do not destroy a job in the dispatch queue or the timer queue without cancelling first. */ - else if( ( jobStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) || ( jobStatus == IOT_TASKPOOL_STATUS_DEFERRED ) ) + else if( ( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) || ( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED ) ) { IotTaskPoolJobStatus_t statusAtCancellation; @@ -1486,6 +1494,7 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, switch( status ) { case IOT_TASKPOOL_SUCCESS: + /* Nothing to do. */ break; case IOT_TASKPOOL_CANCEL_FAILED: @@ -1494,16 +1503,21 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, break; default: + /* Nothing to do. */ break; } } else if( IotLink_IsLinked( &pJob->link ) ) { /* If the job is not in the dispatch or timer queue, it must be in the cache. */ - IotTaskPool_Assert( ( pJob->status & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ); + IotTaskPool_Assert( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ); IotListDouble_Remove( &pJob->link ); } + else + { + /* Nothing to do */ + } _TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -1513,12 +1527,12 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, const IotLink_t * const pTimerEventLink2 ) { - const _taskPoolTimerEvent_t * pTimerEvent1 = IotLink_Container( _taskPoolTimerEvent_t, - pTimerEventLink1, - link ); - const _taskPoolTimerEvent_t * pTimerEvent2 = IotLink_Container( _taskPoolTimerEvent_t, - pTimerEventLink2, - link ); + const _taskPoolTimerEvent_t * const pTimerEvent1 = IotLink_Container( _taskPoolTimerEvent_t, + pTimerEventLink1, + link ); + const _taskPoolTimerEvent_t * const pTimerEvent2 = IotLink_Container( _taskPoolTimerEvent_t, + pTimerEventLink2, + link ); if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) { @@ -1546,9 +1560,9 @@ static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, delta = pFirstTimerEvent->expirationTime - now; } - if( delta < _TASKPOOL_JOB_RESCHEDULE_DELAY_MS ) + if( delta < INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS ) { - delta = _TASKPOOL_JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ + delta = INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ } IotTaskPool_Assert( delta > 0 ); @@ -1572,11 +1586,11 @@ static void _timerThread( void * pArgument ) * If this mutex cannot be locked it means that another thread is manipulating the * timeouts list, and will reset the timer to fire again, although it will be late. */ - if( _TASKPOOL_TRY_ENTER_CRITICAL_SECTION == false ) + if( INTERNAL_TASKPOOL_TRY_ENTER_CRITICAL == false ) { IotLogWarn( "Failed to lock timer mutex in timer thread. Rescheduling deferred job." ); - IotClock_TimerArm( &pTaskPool->timer, _TASKPOOL_JOB_RESCHEDULE_DELAY_MS, 0 ); + ( void ) IotClock_TimerArm( &pTaskPool->timer, INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS, 0 ); return; } @@ -1585,14 +1599,14 @@ static void _timerThread( void * pArgument ) /* Check again for shutdown and bail out early in case. */ if( _IsShutdownStarted( pTaskPool ) ) { - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; return; } /* Dispatch all deferred job whose timer expired, then reset the timer for the next * job down the line. */ - while( true ) + for( ; ; ) { /* Peek the first event in the timer event list. */ IotLink_t * pLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); @@ -1632,14 +1646,14 @@ static void _timerThread( void * pArgument ) IotLogDebug( "Scheduling job from timer event." ); /* Queue the job associated with the received timer event. */ - _scheduleInternal( pTaskPool, pTimerEvent->pJob, 0 ); + ( void ) _scheduleInternal( pTaskPool, pTimerEvent->pJob, 0 ); /* Free the timer event. */ IotTaskPool_FreeTimerEvent( pTimerEvent ); } } - _TASKPOOL_EXIT_CRITICAL_SECTION; + INTERNAL_TASKPOOL__EXIT_CRITICAL; } /** @endcond */ diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 3d9e4fb2c5..ac594a81cb 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -125,15 +125,43 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) /** * @brief Number of iterations for each test loop. */ -#ifndef _TASKPOOL_TEST_ITERATIONS - #define _TASKPOOL_TEST_ITERATIONS ( 200 ) +#ifndef TEST_TASKPOOL_ITERATIONS + #define TEST_TASKPOOL_ITERATIONS ( 200 ) #endif /** * @brief Define the stress job max duration time (emulated duration). */ -#ifndef _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX - #define _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX ( 55 ) +#ifndef TEST_TASKPOOL_WORK_ITEM_DURATION_MAX + #define TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ( 55 ) +#endif + +/** + * @brief Define the number of long running jobs. + */ +#ifndef TEST_TASKPOOL_LONG_JOBS_NUMBER + #define TEST_TASKPOOL_LONG_JOBS_NUMBER 3 +#endif + +/** + * @brief Define the number of running jobs to grow the taskpool for. + */ +#ifndef TEST_TAKPOOL_NUMBER_OF_JOBS + #define TEST_TAKPOOL_NUMBER_OF_JOBS 4 +#endif + +/** + * @brief Define the number of threads to grow the taskpool to. + */ +#ifndef TEST_TASKPOOL_NUMBER_OF_THREADS + #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TAKPOOL_NUMBER_OF_JOBS - 1 ) +#endif + +/** + * @brief Define the number of threads to grow the taskpool to. + */ +#ifndef TEST_TASKPOOL_MAX_THREADS + #define TEST_TASKPOOL_MAX_THREADS 7 #endif /** @@ -146,6 +174,9 @@ static struct itimerspec _TEST_DELAY_50MS = .it_interval = { 0 } }; +/** + * @brief One hour in milliseconds. + */ #define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) /* ---------------------------------------------------------- */ @@ -155,7 +186,7 @@ static struct itimerspec _TEST_DELAY_50MS = */ static void EmulateWork() { - int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX ) ); + int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ) ); TEST_ASSERT_TRUE( duration_in_nsec <= 999999999 ); @@ -175,7 +206,7 @@ static void EmulateWork() */ static void EmulateWorkLong() { - int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % _TASKPOOL_TEST_WORK_ITEM_DURATION_MAX ) ); + int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ) ); TEST_ASSERT_TRUE( duration_in_nsec <= 999999999 ); @@ -360,8 +391,6 @@ IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = */ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) { -#define MAX_THREADS 7 - uint32_t count; IotTaskPool_t taskPool; @@ -383,7 +412,7 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) /* Create a task pool a tweak max threads up & down. */ { uint32_t count; - IotTaskPoolJob_t jobs[ 2 * MAX_THREADS ] = { 0 }; + IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ] = { 0 }; IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -394,14 +423,14 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ /* Initialize more jobs than max threads. */ - for( count = 0; count < 2 * MAX_THREADS; ++count ) + for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) { /* Create legal recyclable job. */ TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } /* Schedule all jobs to make the task pool grow. */ - for( count = 0; count < 2 * MAX_THREADS; ++count ) + for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) { /* Create legal recyclable job. */ TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); @@ -409,8 +438,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); } - -#undef MAX_THREADS } /*-----------------------------------------------------------*/ @@ -577,7 +604,7 @@ TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) IotTaskPoolJob_t * pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; #else jobLimit = 2 * IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * pJobs[ 2 * _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + IotTaskPoolJob_t * pJobs[ 2 * TEST_TASKPOOL_ITERATIONS ] = { 0 }; #endif for( count = 0; count < jobLimit; ++count ) @@ -626,13 +653,11 @@ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) { -#define _LONG_JOBS_NUMBER 3 - IotTaskPool_t taskPool; IotTaskPoolJob_t * pRecyclableJob; const IotTaskPoolInfo_t tpInfo = { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPoolJob_t tpJobs[ _LONG_JOBS_NUMBER ] = { { 0 } }; - IotTaskPoolJob_t tpDeferredJobs[ _LONG_JOBS_NUMBER ] = { { 0 } }; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ] = { { 0 } }; + IotTaskPoolJob_t tpDeferredJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ] = { { 0 } }; JobUserContext_t userContext = { 0 }; @@ -651,7 +676,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) uint32_t scheduled = 0; IotTaskPoolError_t errorSchedule; - for( count = 0; count < _LONG_JOBS_NUMBER; ++count ) + for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); @@ -674,7 +699,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) } } - for( count = 0; count < _LONG_JOBS_NUMBER; ++count ) + for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); @@ -703,8 +728,6 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); - -#undef _LONG_JOBS_NUMBER } /*-----------------------------------------------------------*/ @@ -740,33 +763,31 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) { -#define _NUMBER_OF_JOBS 4 - IotTaskPool_t taskPool; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = _NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TAKPOOL_NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; JobBlockingUserContext_t userContext = { 0 }; /* Initialize user context. */ - TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, _NUMBER_OF_JOBS ) ); - TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, _NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); IotTaskPool_Create( &tpInfo, &taskPool ); /* Statically allocated job, schedule one, then wait. */ { uint32_t count; - IotTaskPoolJob_t jobs[ _NUMBER_OF_JOBS ]; + IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } - for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) { TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } @@ -780,14 +801,14 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) ++count; - if( count == _NUMBER_OF_JOBS ) + if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) { break; } } /* Signal all taskpool threads to exit. */ - for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) { IotSemaphore_Post( &userContext.block ); } @@ -798,8 +819,6 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) /* Destroy user context. */ IotSemaphore_Destroy( &userContext.signal ); IotSemaphore_Destroy( &userContext.block ); - -#undef _NUMBER_OF_JOBS } /*-----------------------------------------------------------*/ @@ -809,29 +828,26 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) { -#define _NUMBER_OF_JOBS 4 -#define _NUMBER_OF_THREADS 3 - IotTaskPool_t taskPool; /* Use a taskpool with not enough threads. */ - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = _NUMBER_OF_THREADS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TASKPOOL_NUMBER_OF_THREADS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; JobBlockingUserContext_t userContext = { 0 }; /* Initialize user context. */ - TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, _NUMBER_OF_JOBS ) ); - TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, _NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); IotTaskPool_Create( &tpInfo, &taskPool ); /* Statically allocated job, schedule one, then wait. */ { uint32_t count; - IotTaskPoolJob_t jobs[ _NUMBER_OF_JOBS ]; + IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ @@ -839,7 +855,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) } /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ - for( count = 0; count < _NUMBER_OF_THREADS; ++count ) + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_THREADS; ++count ) { TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } @@ -856,14 +872,14 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) ++count; - if( count == _NUMBER_OF_JOBS ) + if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) { break; } } /* Signal all taskpool threads to exit. */ - for( count = 0; count < _NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) { IotSemaphore_Post( &userContext.block ); } @@ -874,9 +890,6 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) /* Destroy user context. */ IotSemaphore_Destroy( &userContext.signal ); IotSemaphore_Destroy( &userContext.block ); - -#undef _NUMBER_OF_JOBS -#undef _NUMBER_OF_THREADS } /*-----------------------------------------------------------*/ @@ -902,7 +915,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) uint32_t scheduled = 0; IotTaskPoolJob_t job; - for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); @@ -954,7 +967,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) /* Since jobs were build from a static buffer and scheduled one-by-one, we * should have received all callbacks. */ - TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } IotTaskPool_Destroy( &taskPool ); @@ -986,7 +999,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) uint32_t scheduled = 0; IotTaskPoolJob_t job; - for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); @@ -1038,7 +1051,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) /* Since jobs were build from a static buffer and scheduled one-by-one, we * should have received all callbacks. */ - TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } IotTaskPool_Destroy( &taskPool ); @@ -1070,7 +1083,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) uint32_t scheduled = 0; IotTaskPoolJob_t * pJob; - for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); @@ -1113,12 +1126,12 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) TEST_ASSERT( userContext.counter == scheduled ); } - TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); /* Since jobs were build from a static buffer and scheduled one-by-one, we * should have received all callbacks. */ - TEST_ASSERT( scheduled == _TASKPOOL_TEST_ITERATIONS ); + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } IotTaskPool_Destroy( &taskPool ); @@ -1148,9 +1161,9 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) { uint32_t count; uint32_t scheduled = 0; - IotTaskPoolJob_t tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { { 0 } }; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; - for( count = 0; count < _TASKPOOL_TEST_ITERATIONS; ++count ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); @@ -1225,8 +1238,8 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; #else - maxJobs = _TASKPOOL_TEST_ITERATIONS; - IotTaskPoolJob_t * tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; #endif for( count = 0; count < maxJobs; ++count ) @@ -1305,8 +1318,8 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; #else - maxJobs = _TASKPOOL_TEST_ITERATIONS; - IotTaskPoolJob_t * tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; #endif for( count = 0; count < maxJobs; ++count ) @@ -1507,8 +1520,8 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; #else - maxJobs = _TASKPOOL_TEST_ITERATIONS; - IotTaskPoolJob_t tpJobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; #endif /* Schedule all jobs. */ @@ -1610,8 +1623,8 @@ TEST( Common_Unit_Task_Pool, TaskPool_CancelTasks ) maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; #else - maxJobs = _TASKPOOL_TEST_ITERATIONS; - IotTaskPoolJob_t jobs[ _TASKPOOL_TEST_ITERATIONS ] = { 0 }; + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t jobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; #endif /* Initialize user context. */ From 22c0aecc09f1608579f81b2079a3960c7472e4e4 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Mon, 18 Mar 2019 09:33:49 -0700 Subject: [PATCH 047/844] Revert bogus commit on layout doxygen file. (#321) --- doc/config/layout_library.xml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/doc/config/layout_library.xml b/doc/config/layout_library.xml index 2437f4ca05..7aebbfee9b 100644 --- a/doc/config/layout_library.xml +++ b/doc/config/layout_library.xml @@ -1,25 +1,28 @@ + - + + - - - - - + + + + + - - - + + + - + @@ -109,8 +112,8 @@ - - + + From b860748be1edbe1097492e95d3b4e30f83009825 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 18 Mar 2019 10:39:32 -0700 Subject: [PATCH 048/844] Refactor MQTT receive callback and metrics. (#322) --- .travis.yml | 60 +- CMakeLists.txt | 5 +- README.md | 6 +- .../DeserializeConnack_harness.c | 10 +- .../DeserializePingresp_harness.c | 10 +- .../DeserializePuback_harness.c | 12 +- .../DeserializePublish_harness.c | 25 +- .../DeserializeSuback_harness.c | 16 +- .../DeserializeUnsuback_harness.c | 12 +- cbmc/proofs/Makefile.common | 2 +- demos/aws_iot_demo_shadow.c | 252 +++- .../iot_demo_arguments.h} | 17 +- demos/{ => include}/iot_demo_config.h | 37 +- .../iot_demo_logging.h} | 39 +- demos/iot_demo_mqtt.c | 603 ++++++--- demos/posix/CMakeLists.txt | 19 +- demos/posix/aws_iot_demo_shadow_posix.c | 218 ---- ...mon_posix.c => iot_demo_arguments_posix.c} | 156 ++- demos/posix/iot_demo_mqtt_posix.c | 205 --- demos/posix/iot_demo_posix.c | 157 +++ doc/config/defender | 2 - doc/config/mqtt | 4 + doc/config/taskpool | 6 +- doc/guide/building.txt | 2 +- doc/lib/mqtt.txt | 12 +- doc/mainpage.txt | 14 +- ...qtt_function_receivecallback_nopartial.png | Bin 42752 -> 0 bytes .../mqtt_function_receivecallback_partial.png | Bin 59817 -> 0 bytes ...mqtt_function_receivecallback_nopartial.pu | 38 - .../mqtt_function_receivecallback_partial.pu | 40 - lib/include/aws_iot_defender.h | 23 +- lib/include/aws_iot_shadow.h | 4 +- lib/include/iot_mqtt.h | 1125 +---------------- lib/include/platform/iot_clock.h | 23 +- lib/include/{ => platform}/iot_metrics.h | 12 +- lib/include/platform/iot_network.h | 60 +- lib/include/platform/iot_threads.h | 24 +- .../private/aws_iot_defender_internal.h | 28 +- lib/include/private/iot_mqtt_internal.h | 255 ++-- lib/include/private/iot_static_memory.h | 2 +- lib/include/types/iot_mqtt_types.h | 1056 ++++++++++++++++ lib/source/common/CMakeLists.txt | 2 +- lib/source/common/iot_common.c | 56 +- .../static_memory/iot_static_memory_metrics.c | 2 +- lib/source/defender/CMakeLists.txt | 2 +- lib/source/defender/aws_iot_defender_api.c | 134 +- .../defender/aws_iot_defender_collector.c | 58 +- lib/source/defender/aws_iot_defender_mqtt.c | 65 +- lib/source/metrics/CMakeLists.txt | 9 - lib/source/mqtt/iot_mqtt_api.c | 583 +++++---- lib/source/mqtt/iot_mqtt_network.c | 960 ++++++++------ lib/source/mqtt/iot_mqtt_operation.c | 132 +- lib/source/mqtt/iot_mqtt_serialize.c | 582 +++------ lib/source/mqtt/iot_mqtt_subscription.c | 139 +- lib/source/mqtt/iot_mqtt_validate.c | 94 -- lib/source/shadow/aws_iot_shadow_api.c | 3 + lib/source/shadow/aws_iot_shadow_operation.c | 3 + .../shadow/aws_iot_shadow_subscription.c | 3 + platform/include/posix/iot_network_openssl.h | 42 +- platform/source/posix/CMakeLists.txt | 4 +- platform/source/posix/iot_clock_posix.c | 31 +- platform/source/posix/iot_threads_posix.c | 227 +--- .../source/posix/linux}/iot_metrics.c | 29 +- .../posix/linux/iot_network_openssl_metrics.c | 118 ++ .../posix/network/iot_network_openssl.c | 832 +++++------- scripts/build_check_commit.sh | 68 - scripts/build_check_pr.sh | 47 - scripts/ci_test_common.sh | 24 + scripts/ci_test_defender.sh | 6 + scripts/ci_test_general.sh | 13 + scripts/ci_test_mqtt.sh | 43 + scripts/ci_test_shadow.sh | 37 + scripts/coverage.sh | 42 - scripts/general/ci_test_coverage.sh | 32 + .../ci_test_doc.sh} | 0 tests/common/iot_tests_common.c | 13 +- tests/common/unit/iot_tests_taskpool.c | 85 +- tests/defender/aws_iot_tests_defender_api.c | 25 +- tests/iot_tests_config.h | 13 +- tests/iot_tests_network.c | 63 +- tests/mqtt/CMakeLists.txt | 2 +- tests/mqtt/access/iot_test_access_mqtt.h | 11 +- tests/mqtt/access/iot_test_access_mqtt_api.c | 4 +- .../access/iot_test_access_mqtt_serialize.c | 40 - tests/mqtt/iot_tests_mqtt.c | 22 +- tests/mqtt/system/iot_tests_mqtt_stress.c | 79 +- tests/mqtt/system/iot_tests_mqtt_system.c | 136 +- tests/mqtt/unit/iot_tests_mqtt_api.c | 622 ++++----- tests/mqtt/unit/iot_tests_mqtt_receive.c | 887 ++++++------- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 82 +- tests/mqtt/unit/iot_tests_mqtt_validate.c | 51 +- tests/shadow/aws_iot_tests_shadow.c | 21 +- .../system/aws_iot_tests_shadow_system.c | 26 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 244 +++- .../shadow/unit/aws_iot_tests_shadow_parser.c | 3 + third_party/tinycbor/CMakeLists.txt | 7 +- 96 files changed, 5531 insertions(+), 5918 deletions(-) rename demos/{posix/iot_demo_posix.h => include/iot_demo_arguments.h} (91%) rename demos/{ => include}/iot_demo_config.h (61%) rename demos/{iot_demo.h => include/iot_demo_logging.h} (62%) delete mode 100644 demos/posix/aws_iot_demo_shadow_posix.c rename demos/posix/{iot_demo_common_posix.c => iot_demo_arguments_posix.c} (81%) delete mode 100644 demos/posix/iot_demo_mqtt_posix.c create mode 100644 demos/posix/iot_demo_posix.c delete mode 100644 doc/plantuml/images/mqtt_function_receivecallback_nopartial.png delete mode 100644 doc/plantuml/images/mqtt_function_receivecallback_partial.png delete mode 100644 doc/plantuml/mqtt_function_receivecallback_nopartial.pu delete mode 100644 doc/plantuml/mqtt_function_receivecallback_partial.pu rename lib/include/{ => platform}/iot_metrics.h (92%) create mode 100644 lib/include/types/iot_mqtt_types.h delete mode 100644 lib/source/metrics/CMakeLists.txt rename {lib/source/metrics => platform/source/posix/linux}/iot_metrics.c (87%) create mode 100644 platform/source/posix/linux/iot_network_openssl_metrics.c delete mode 100644 scripts/build_check_commit.sh delete mode 100644 scripts/build_check_pr.sh create mode 100644 scripts/ci_test_common.sh create mode 100644 scripts/ci_test_defender.sh create mode 100644 scripts/ci_test_general.sh create mode 100644 scripts/ci_test_mqtt.sh create mode 100644 scripts/ci_test_shadow.sh delete mode 100644 scripts/coverage.sh create mode 100644 scripts/general/ci_test_coverage.sh rename scripts/{generate_doc.sh => general/ci_test_doc.sh} (100%) delete mode 100644 tests/mqtt/access/iot_test_access_mqtt_serialize.c diff --git a/.travis.yml b/.travis.yml index 3f5b2aa54f..e076944306 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,37 +1,51 @@ +# Only run on the v4 beta branch for now. +branches: + only: + - v4_beta + # Build on Ubuntu 16.04. os: linux dist: xenial -# Build with both clang and gcc. +# Use clang compiler (seems to provide more warnings than gcc). language: c compiler: - clang - - gcc -# Only run on the v4 beta branch for now. -branches: - only: - - v4_beta +# Matrix of tests to run. +env: + - RUN_TEST=general # General tests (code coverage, Coverity, etc.) + - RUN_TEST=common # Common libraries (linear containers, common libraries, etc.) + - RUN_TEST=mqtt # MQTT + - RUN_TEST=shadow # AWS IoT Thing Shadows + - RUN_TEST=defender # AWS IoT Device Defender +# Update repositories. before_install: - # Install coveralls only for commit check builds. - - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then pip install --user cpp-coveralls; fi + - sudo apt-get update # Install dependencies. -addons: - apt: - packages: - - cmake - - libssl-dev - - lcov +install: + # Dependencies required across the entire build matrix. + - sudo apt-get install -y cmake libssl-dev + # Install coveralls only for "general" commit check builds. + - if [ "$RUN_TEST" = "general" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then sudo apt-get install -y lcov; pip install --user cpp-coveralls; fi +# Run the test script based on matrix environment variable. script: - # Run the pull request build check script for pull requests. - - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./scripts/build_check_pr.sh; fi - - # Run the checks with AWS IoT credentials for commits. - - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then bash ./scripts/build_check_commit.sh; fi - -after_success: - # Send code coverage report to Coveralls, but only for one of the build jobs. - - if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$CC" = "gcc" ]; then bash ./scripts/coverage.sh; fi + # Set identifier (client identifier OR Thing Name). + - export IOT_IDENTIFIER="$IOT_IDENTIFIER_PREFIX$RUN_TEST" + # Set default compiler options. Individual test scripts may override this. + - export COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" + # Get AWS credentials when not a pull request build. + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then mkdir credentials; fi + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O credentials/AmazonRootCA1.pem; fi + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then echo -e $AWS_IOT_CLIENT_CERT > credentials/clientCert.pem; fi + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then echo -e $AWS_IOT_PRIVATE_KEY > credentials/privateKey.pem; fi + # Set credential defines passed to CMake when not a pull request build. + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then export AWS_IOT_CREDENTIAL_DEFINES="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\""; fi + # Create build directory. + - mkdir build + - cd build + # Run test script. + - bash ../scripts/ci_test_$RUN_TEST.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index f9e868aff5..8250d15577 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ include_directories( ${PROJECT_SOURCE_DIR}/lib/include ${PROJECT_SOURCE_DIR}/third_party/include ) # Demo include path. -include_directories( ${PROJECT_SOURCE_DIR}/demos ) +include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) # Use either the demo or test configuration file. if( ${IOT_BUILD_TESTS} ) @@ -68,9 +68,6 @@ add_subdirectory( lib/source/mqtt ) # Shadow library. add_subdirectory( lib/source/shadow ) -# Metrics library. -add_subdirectory( lib/source/metrics ) - # Serializer library. add_subdirectory( lib/source/serializer ) diff --git a/README.md b/README.md index 650317aa6e..45e04bf59d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C.svg?branch=v4_beta)](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) [![Coverage Status](https://coveralls.io/repos/github/aws/aws-iot-device-sdk-embedded-C/badge.svg?branch=v4_beta)](https://coveralls.io/github/aws/aws-iot-device-sdk-embedded-C?branch=v4_beta) -The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be build into firmware along with application code. +The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be built into firmware along with application code. This library, currently a Beta release, will supersede both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. @@ -26,8 +26,8 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. * 4. [Create an AWS IoT Policy](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-policy.html) 5. [Attach an AWS IoT Policy to a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/attach-policy-to-certificate.html) 6. [Attach a Certificate to a Thing](https://docs.aws.amazon.com/iot/latest/developerguide/attach-cert-thing.html) -2. *Optional:* Set the following `#define` in [iot_demo_config.h](demos/aws_iot_demo_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. - - Set `AWS_IOT_DEMO_THING_NAME` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. +2. *Optional:* Set the following `#define` in [iot_demo_config.h](demos/include/iot_demo_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. + - Set `IOT_DEMO_IDENTIFIER` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. - Set `IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. diff --git a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c index 90b7ba13dc..c20690710e 100644 --- a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c +++ b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c @@ -7,11 +7,9 @@ void harness() { - size_t dataLength; - uint8_t * pConnackStart = malloc( sizeof( uint8_t ) * dataLength ); - size_t bytesProcessed; + _mqttPacket_t connack; - _IotMqtt_DeserializeConnack( pConnackStart, - dataLength, - &bytesProcessed ); + connack.pRemainingData = malloc( sizeof( uint8_t ) * connack.remainingLength ); + + _IotMqtt_DeserializeConnack( &connack ); } diff --git a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c index cf683607bd..6a47813e19 100644 --- a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c +++ b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c @@ -7,11 +7,9 @@ void harness() { - size_t dataLength; - uint8_t * pPingrespStart = malloc( sizeof( uint8_t ) * dataLength ); - size_t bytesProcessed; + _mqttPacket_t pingresp; - _IotMqtt_DeserializePingresp( pPingrespStart, - dataLength, - &bytesProcessed ); + pingresp.pRemainingData = malloc( sizeof( uint8_t ) * pingresp.remainingLength ); + + _IotMqtt_DeserializePingresp( &pingresp ); } diff --git a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c index 6d28136572..f3c94e3d85 100644 --- a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c +++ b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c @@ -7,13 +7,9 @@ void harness() { - size_t dataLength; - uint8_t * pPubackStart = malloc( sizeof( uint8_t ) * dataLength ); - uint16_t packetIdentifier; - size_t bytesProcessed; + _mqttPacket_t puback; - _IotMqtt_DeserializePuback( pPubackStart, - dataLength, - &packetIdentifier, - &bytesProcessed ); + puback.pRemainingData = malloc( sizeof( uint8_t ) * puback.remainingLength ); + + _IotMqtt_DeserializePuback( &puback ); } diff --git a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c index ad8e9253d0..f64a0f0929 100644 --- a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c +++ b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c @@ -7,15 +7,18 @@ void harness() { - size_t dataLength; - uint8_t * pPublishStart = malloc( sizeof( uint8_t ) * dataLength ); - IotMqttPublishInfo_t output; - uint16_t packetIdentifier; - size_t bytesProcessed; - - _IotMqtt_DeserializePublish( pPublishStart, - dataLength, - &output, - &packetIdentifier, - &bytesProcessed ); + IotMqttPublishInfo_t publishInfo; + + publishInfo.pTopicName = malloc( publishInfo.topicNameLength ); + publishInfo.pPayload = malloc( publishInfo.payloadLength ); + + _mqttOperation_t operation; + operation.publishInfo = publishInfo; + + _mqttPacket_t publish; + publish.pRemainingData = malloc( sizeof( uint8_t ) * publish.remainingLength ); + publish.pIncomingPublish = &operation; + + + _IotMqtt_DeserializePublish( &publish ); } diff --git a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c index 6b710c9778..e9e6390401 100644 --- a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c +++ b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c @@ -7,17 +7,11 @@ void harness() { - IotMqttConnection_t mqttConnection; - size_t dataLength; - uint8_t * pSubackStart = malloc( sizeof( uint8_t ) * dataLength ); - uint16_t packetIdentifier; - size_t bytesProcessed; + _mqttPacket_t suback; - __CPROVER_assume( dataLength <= BUFFER_SIZE ); + suback.pRemainingData = malloc( sizeof( uint8_t ) * suback.remainingLength ); - _IotMqtt_DeserializeSuback( mqttConnection, - pSubackStart, - dataLength, - &packetIdentifier, - &bytesProcessed ); + __CPROVER_assume( suback.remainingLength <= BUFFER_SIZE ); + + _IotMqtt_DeserializeSuback( &suback ); } diff --git a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c index 76765c58f4..e6dbb4feaa 100644 --- a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c +++ b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c @@ -7,13 +7,9 @@ void harness() { - size_t dataLength; - uint8_t * pUnsubackStart = malloc( sizeof( uint8_t ) * dataLength ); - uint16_t packetIdentifier; - size_t bytesProcessed; + _mqttPacket_t unsuback; - _IotMqtt_DeserializeUnsuback( pUnsubackStart, - dataLength, - &packetIdentifier, - &bytesProcessed ); + unsuback.pRemainingData = malloc( sizeof( uint8_t ) * unsuback.remainingLength ); + + _IotMqtt_DeserializeUnsuback( &unsuback ); } diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index a9c03227bc..9a23c2f05b 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -21,7 +21,7 @@ VIEWER ?= cbmc-viewer INC = \ -I$(MQTT)/lib/include \ -I$(MQTT)/platform/include \ - -I$(MQTT)/demos \ + -I$(MQTT)/demos/include \ DEF += \ -DIOT_CONFIG_FILE=\"iot_demo_config.h\" \ diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index a341fb6d54..2555e33379 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -31,15 +31,22 @@ /* Standard includes. */ #include +#include #include -/* Common demo include. */ -#include "iot_demo.h" +/* Set up logging for this demo. */ +#include "iot_demo_logging.h" /* Platform layer includes. */ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* Common libraries include. */ +#include "iot_common.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + /* Shadow include. */ #include "aws_iot_shadow.h" @@ -71,59 +78,226 @@ extern int snprintf( char *, /*-----------------------------------------------------------*/ /** - * @brief The function that runs the Shadow demo. + * @brief Initialize the common libraries, the MQTT library, and the Shadow library. + * + * @return `EXIT_SUCCESS` if all libraries were successfully initialized; + * `EXIT_FAILURE` otherwise. + */ +static int _initializeDemo( void ) +{ + int status = EXIT_SUCCESS; + IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; + AwsIotShadowError_t shadowInitStatus = AWS_IOT_SHADOW_SUCCESS; + + /* Flags to track cleanup on error. */ + bool commonInitialized = false, mqttInitialized = false; + + /* Initialize the common libraries. */ + commonInitialized = IotCommon_Init(); + + if( commonInitialized == false ) + { + status = EXIT_FAILURE; + } + + /* Initialize the MQTT library. */ + if( status == EXIT_SUCCESS ) + { + mqttInitStatus = IotMqtt_Init(); + + if( mqttInitStatus == IOT_MQTT_SUCCESS ) + { + mqttInitialized = true; + } + else + { + status = EXIT_FAILURE; + } + } + + /* Initialize the Shadow library. */ + if( status == EXIT_SUCCESS ) + { + /* Use the default MQTT timeout. */ + shadowInitStatus = AwsIotShadow_Init( 0 ); + + if( shadowInitStatus != AWS_IOT_SHADOW_SUCCESS ) + { + status = EXIT_FAILURE; + } + } + + /* Clean up on error. */ + if( status == EXIT_FAILURE ) + { + if( commonInitialized == true ) + { + IotCommon_Cleanup(); + } + + if( mqttInitialized == true ) + { + IotMqtt_Cleanup(); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Clean up the common libraries, the MQTT library, and the Shadow library. + */ +static void _cleanupDemo( void ) +{ + IotCommon_Cleanup(); + IotMqtt_Cleanup(); + AwsIotShadow_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a new connection to the MQTT server for the Shadow demo. * - * This function is called to run the Shadow demo once a network connection has - * been established. - * @param[in] pThingName NULL-terminated Thing Name to use for this demo. - * @param[in] pMqttConnection Pointer to the MQTT connection to use. This MQTT - * connection must be initialized to IOT_MQTT_CONNECTION_INITIALIZER. - * @param[in] pNetworkInterface Pointer to an MQTT network interface to use. - * All necessary members of the network interface should be set before calling - * this function. + * @param[in] pIdentifier NULL-terminated MQTT client identifier. The Shadow + * demo will use the Thing Name as the client identifier. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkInterface The network interface to use for this demo. + * @param[out] pMqttConnection Set to the handle to the new MQTT connection. * - * @return 0 if the demo completes successfully; -1 if some part of it fails. + * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` + * otherwise. */ -int AwsIotDemo_RunShadowDemo( const char * const pThingName, - IotMqttConnection_t * const pMqttConnection, - const IotMqttNetIf_t * const pNetworkInterface ) +static int _establishMqttConnection( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface, + IotMqttConnection_t * pMqttConnection ) { - int status = 0; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + int status = EXIT_SUCCESS; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - /* Set the common members of the connection info. */ - connectInfo.awsIotMqttMode = true; - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + if( pIdentifier == NULL ) + { + IotLogError( "Shadow Thing Name must be provided." ); + + status = EXIT_FAILURE; + } + + if( status == EXIT_SUCCESS ) + { + /* Set the members of the network info not set by the initializer. This + * struct provided information on the transport layer to the MQTT connection. */ + networkInfo.createNetworkConnection = true; + networkInfo.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.pNetworkInterface = pNetworkInterface; + + /* Set the members of the connection info not set by the initializer. */ + connectInfo.awsIotMqttMode = true; + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + + /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ + connectInfo.pClientIdentifier = pIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); + + IotLogInfo( "Shadow Thing Name is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); + + /* Establish the MQTT connection. */ + connectStatus = IotMqtt_Connect( &networkInfo, + &connectInfo, + _TIMEOUT_MS, + pMqttConnection ); + + if( connectStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "MQTT CONNECT returned error %s.", + IotMqtt_strerror( connectStatus ) ); + + status = EXIT_FAILURE; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ - /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ - connectInfo.pClientIdentifier = pThingName; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pThingName ); +/** + * @brief The function that runs the Shadow demo, called by the demo runner. + * + * @param[in] awsIotMqttMode Ignored for the Shadow demo. + * @param[in] pIdentifier NULL-terminated Shadow Thing Name. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection for Shadows. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection for Shadows. + * @param[in] pNetworkInterface The network interface to use for this demo. + * + * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. + */ +int RunShadowDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ) +{ + /* Return value of this function and the exit status of this program. */ + int status = 0; - IotLogInfo( "Shadow Thing Name is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); + /* Handle of the MQTT connection used in this demo. */ + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - /* Establish the MQTT connection. */ - mqttStatus = IotMqtt_Connect( pMqttConnection, - pNetworkInterface, - &connectInfo, - _TIMEOUT_MS ); + /* Flags for tracking which cleanup functions must be called. */ + bool librariesInitialized = false, connectionEstablished = false; - if( mqttStatus != IOT_MQTT_SUCCESS ) + /* The first parameter of this demo function is not used. Shadows are specific + * to AWS IoT, so this value is hardcoded to true whenever needed. */ + ( void ) awsIotMqttMode; + + /* Initialize the libraries required for this demo. */ + status = _initializeDemo(); + + if( status == EXIT_SUCCESS ) { - IotLogError( "MQTT CONNECT returned error %s.", - IotMqtt_strerror( mqttStatus ) ); + /* Mark the libraries as initialized. */ + librariesInitialized = true; + + /* Establish a new MQTT connection. */ + status = _establishMqttConnection( pIdentifier, + pNetworkServerInfo, + pNetworkCredentialInfo, + pNetworkInterface, + &mqttConnection ); + } - status = -1; + if( status == EXIT_SUCCESS ) + { + /* Mark the MQTT connection as established. */ + connectionEstablished = true; } /* Disconnect the MQTT connection if it was established. */ - if( *pMqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + if( connectionEstablished == true ) + { + IotMqtt_Disconnect( mqttConnection, 0 ); + } + + /* Clean up libraries if they were initialized. */ + if( librariesInitialized == true ) { - IotMqtt_Disconnect( *pMqttConnection, false ); + _cleanupDemo(); } return status; diff --git a/demos/posix/iot_demo_posix.h b/demos/include/iot_demo_arguments.h similarity index 91% rename from demos/posix/iot_demo_posix.h rename to demos/include/iot_demo_arguments.h index 5a2e0b520a..e9521526d0 100644 --- a/demos/posix/iot_demo_posix.h +++ b/demos/include/iot_demo_arguments.h @@ -20,12 +20,16 @@ */ /** - * @file iot_demo_posix.h - * @brief Declares the POSIX-specific demo functions. + * @file iot_demo_arguments.h + * @brief Declares the function and structure used for processing command line + * arguments */ -#ifndef _IOT_DEMO_POSIX_H_ -#define _IOT_DEMO_POSIX_H_ +#ifndef _IOT_DEMO_ARGUMENTS_H_ +#define _IOT_DEMO_ARGUMENTS_H_ + +/* Standard includes. */ +#include /** * @brief Holds the arguments for a single demo. @@ -79,6 +83,7 @@ typedef struct IotDemoArguments * The functions for parsing command line arguments differ depending on the * operating system. Therefore, this function is re-implemented for different * platforms. + * * @param[in] argc The argument count originally passed to main(). * @param[in] argv The argument vector originally passed to main(). * @param[out] pArguments Set to the arguments parsed from the command line. @@ -89,6 +94,6 @@ typedef struct IotDemoArguments */ bool IotDemo_ParseArguments( int argc, char ** argv, - IotDemoArguments_t * const pArguments ); + IotDemoArguments_t * pArguments ); -#endif /* ifndef _IOT_DEMO_POSIX_H_ */ +#endif /* ifndef _IOT_DEMO_ARGUMENTS_H_ */ diff --git a/demos/iot_demo_config.h b/demos/include/iot_demo_config.h similarity index 61% rename from demos/iot_demo_config.h rename to demos/include/iot_demo_config.h index b88e9de9d3..d0a6ccf2db 100644 --- a/demos/iot_demo_config.h +++ b/demos/include/iot_demo_config.h @@ -25,31 +25,36 @@ #define _IOT_DEMO_CONFIG_H_ /* Server endpoints used for the demos. May be overridden with command line - * options. */ -#define IOT_DEMO_SECURED_CONNECTION ( true ) -#define IOT_DEMO_SERVER "" -#define IOT_DEMO_PORT ( 443 ) + * options at runtime. */ +#define IOT_DEMO_SECURED_CONNECTION ( true ) /* Command line: -s (secured) or -u (unsecured) */ +#define IOT_DEMO_SERVER "" /* Command line: -h */ +#define IOT_DEMO_PORT ( 443 ) /* Command line: -p */ -/* Credential paths. May be overridden with command line options. */ -#define IOT_DEMO_ROOT_CA "" -#define IOT_DEMO_CLIENT_CERT "" -#define IOT_DEMO_PRIVATE_KEY "" +/* Credential paths. May be overridden with command line options at runtime. */ +#define IOT_DEMO_ROOT_CA "" /* Command line: -r */ +#define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ +#define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ -/* Default Thing Name to use for AWS IoT demos. */ -/* #define AWS_IOT_DEMO_THING_NAME "" */ +/* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at + * runtime with the command line option -i. Identifiers are optional for the + * MQTT demo, but required for demos requiring a Thing Name. (The MQTT demo will + * generate a unique identifier if no identifier is given). */ +/* #define IOT_DEMO_IDENTIFIER "" */ -/* MQTT demo configuration. */ -/* #define IOT_DEMO_MQTT_CLIENT_IDENTIFIER "" */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) -#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) +/* MQTT demo configuration. The demo publishes bursts of messages. */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ /* Enable asserts in linear containers and MQTT. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) -/* Library logging configuration. */ -#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO +/* Library logging configuration. IOT_LOG_LEVEL_GLOBAL provides a global log + * level for all libraries; the library-specific settings override the global + * setting. If both the library-specific and global settings are undefined, + * no logs will be printed. */ #define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO +#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO #define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO #define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO #define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO diff --git a/demos/iot_demo.h b/demos/include/iot_demo_logging.h similarity index 62% rename from demos/iot_demo.h rename to demos/include/iot_demo_logging.h index 8ecf071648..eb41adff90 100644 --- a/demos/iot_demo.h +++ b/demos/include/iot_demo_logging.h @@ -20,26 +20,23 @@ */ /** - * @file iot_demo.h - * @brief Declares the platform-independent demo functions. + * @file iot_demo_logging.h + * @brief Sets the log level for all demos. */ -#ifndef _IOT_DEMO_H_ -#define _IOT_DEMO_H_ +#ifndef _IOT_DEMO_LOGGING_H_ +#define _IOT_DEMO_LOGGING_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE #include IOT_CONFIG_FILE #endif -/* Standard includes. */ -#include -#include - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Configure logs for the demos. */ +/* Configure logs for the demos. The demos will have a log level of: + * - IOT_LOG_LEVEL_DEMO if defined. + * - IOT_LOG_LEVEL_GLOBAL if defined and IOT_LOG_LEVEL_DEMO is undefined. + * - IOT_LOG_NONE if neither IOT_LOG_LEVEL_DEMO nor IOT_LOG_LEVEL_GLOBAL are defined. + */ #ifdef IOT_LOG_LEVEL_DEMO #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_DEMO #else @@ -50,20 +47,10 @@ #endif #endif +/* Set the library name to print with the demos. */ #define _LIBRARY_LOG_NAME ( "DEMO" ) -#include "iot_logging_setup.h" -/*----------------------------- Demo functions ------------------------------*/ - -/* See iot_demo_mqtt.c for documentation of this function. */ -int IotDemo_RunMqttDemo( bool awsIotMqttMode, - const char * const pClientIdentifier, - IotMqttConnection_t * const pMqttConnection, - const IotMqttNetIf_t * const pNetworkInterface ); - -/* See aws_iot_demo_shadow.c for documentation of this function. */ -int AwsIotDemo_RunShadowDemo( const char * const pThingName, - IotMqttConnection_t * const pMqttConnection, - const IotMqttNetIf_t * const pNetworkInterface ); +/* Include the logging setup header. This enables the logs. */ +#include "iot_logging_setup.h" -#endif /* ifndef _IOT_DEMO_H_ */ +#endif /* ifndef _IOT_DEMO_LOGGING_H_ */ diff --git a/demos/iot_demo_mqtt.c b/demos/iot_demo_mqtt.c index 8a07c4ec0b..7331498d85 100644 --- a/demos/iot_demo_mqtt.c +++ b/demos/iot_demo_mqtt.c @@ -31,15 +31,19 @@ /* Standard includes. */ #include +#include #include -/* Common demo include. */ -#include "iot_demo.h" +/* Set up logging for this demo. */ +#include "iot_demo_logging.h" /* Platform layer includes. */ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* Common libraries include. */ +#include "iot_common.h" + /* MQTT include. */ #include "iot_mqtt.h" @@ -341,46 +345,89 @@ static void _mqttSubscriptionCallback( void * param1, /*-----------------------------------------------------------*/ /** - * @brief The function that runs the MQTT demo. + * @brief Initialize the common libraries and the MQTT library. + * + * @return `EXIT_SUCCESS` if all libraries were successfully initialized; + * `EXIT_FAILURE` otherwise. + */ +static int _initializeDemo( void ) +{ + int status = EXIT_SUCCESS; + IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; + + if( IotCommon_Init() == true ) + { + mqttInitStatus = IotMqtt_Init(); + + if( mqttInitStatus != IOT_MQTT_SUCCESS ) + { + /* Failed to initialize MQTT library. */ + status = EXIT_FAILURE; + + /* Clean up the common libraries if MQTT could not be initialized. */ + IotCommon_Cleanup(); + } + } + else + { + /* Failed to initialize common libraries. */ + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Clean up the common libraries and the MQTT library. + */ +static void _cleanupDemo( void ) +{ + IotCommon_Cleanup(); + IotMqtt_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a new connection to the MQTT server. * - * This function is called to run the MQTT demo once a network connection has - * been established. * @param[in] awsIotMqttMode Specify if this demo is running with the AWS IoT - * MQTT server. Set this to false if using another MQTT server. - * @param[in] pClientIdentifier NULL-terminated MQTT client identifier. - * @param[in] pMqttConnection Pointer to the MQTT connection to use. This MQTT - * connection must be initialized to IOT_MQTT_CONNECTION_INITIALIZER. - * @param[in] pNetworkInterface Pointer to an MQTT network interface to use. - * All necessary members of the network interface should be set before calling - * this function. + * MQTT server. Set this to `false` if using another MQTT server. + * @param[in] pIdentifier NULL-terminated MQTT client identifier. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkInterface The network interface to use for this demo. + * @param[out] pMqttConnection Set to the handle to the new MQTT connection. * - * @return 0 if the demo completes successfully; -1 if some part of it fails. + * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` + * otherwise. */ -int IotDemo_RunMqttDemo( bool awsIotMqttMode, - const char * const pClientIdentifier, - IotMqttConnection_t * const pMqttConnection, - const IotMqttNetIf_t * const pNetworkInterface ) +static int _establishMqttConnection( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface, + IotMqttConnection_t * pMqttConnection ) { - int status = 0, i = 0; - intptr_t publishCount = 0; - char pClientIdentifierBuffer[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - char pPublishPayload[ _PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + int status = EXIT_SUCCESS; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER, - publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - IotMqttCallbackInfo_t publishComplete = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - IotSemaphore_t publishesReceived; - const char * pTopicFilters[ _TOPIC_FILTER_COUNT ] = - { - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/3", - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/4", - }; + IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + char pClientIdentifierBuffer[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + + /* Set the members of the network info not set by the initializer. This + * struct provided information on the transport layer to the MQTT connection. */ + networkInfo.createNetworkConnection = true; + networkInfo.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.pNetworkInterface = pNetworkInterface; - /* Set the common members of the connection info. */ + /* Set the members of the connection info not set by the initializer. */ connectInfo.awsIotMqttMode = awsIotMqttMode; connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; @@ -396,10 +443,10 @@ int IotDemo_RunMqttDemo( bool awsIotMqttMode, /* Use the parameter client identifier if provided. Otherwise, generate a * unique client identifier. */ - if( pClientIdentifier != NULL ) + if( pIdentifier != NULL ) { - connectInfo.pClientIdentifier = pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pClientIdentifier ); + connectInfo.pClientIdentifier = pIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); } else { @@ -415,7 +462,7 @@ int IotDemo_RunMqttDemo( bool awsIotMqttMode, if( status < 0 ) { IotLogError( "Failed to generate unique client identifier for demo." ); - status = -1; + status = EXIT_FAILURE; } else { @@ -423,54 +470,80 @@ int IotDemo_RunMqttDemo( bool awsIotMqttMode, connectInfo.pClientIdentifier = pClientIdentifierBuffer; connectInfo.clientIdentifierLength = ( uint16_t ) status; - status = 0; + status = EXIT_SUCCESS; } } - if( status == 0 ) + /* Establish the MQTT connection. */ + if( status == EXIT_SUCCESS ) { IotLogInfo( "MQTT demo client identifier is %.*s (length %hu).", connectInfo.clientIdentifierLength, connectInfo.pClientIdentifier, connectInfo.clientIdentifierLength ); - /* Establish the MQTT connection. */ - mqttStatus = IotMqtt_Connect( pMqttConnection, - pNetworkInterface, - &connectInfo, - _MQTT_TIMEOUT_MS ); + connectStatus = IotMqtt_Connect( &networkInfo, + &connectInfo, + _MQTT_TIMEOUT_MS, + pMqttConnection ); - if( mqttStatus != IOT_MQTT_SUCCESS ) + if( connectStatus != IOT_MQTT_SUCCESS ) { IotLogError( "MQTT CONNECT returned error %s.", - IotMqtt_strerror( mqttStatus ) ); + IotMqtt_strerror( connectStatus ) ); - status = -1; + status = EXIT_FAILURE; } } - if( status == 0 ) - { - /* Set the members of the subscription list. */ - for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) - { - pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; - pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; - pSubscriptions[ i ].topicFilterLength = _TOPIC_FILTER_LENGTH; - pSubscriptions[ i ].callback.param1 = &publishesReceived; - pSubscriptions[ i ].callback.function = _mqttSubscriptionCallback; - } + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Add or remove subscriptions by either subscribing or unsubscribing. + * + * @param[in] mqttConnection The MQTT connection to use for subscriptions. + * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. + * @param[in] pTopicFilters Array of topic filters for subscriptions. + * @param[in] pCallbackParameter The parameter to pass to the subscription + * callback. + * + * @return `EXIT_SUCCESS` if the subscription operation succeeded; `EXIT_FAILURE` + * otherwise. + */ +static int _modifySubscriptions( IotMqttConnection_t mqttConnection, + IotMqttOperationType_t operation, + const char ** pTopicFilters, + void * pCallbackParameter ) +{ + int status = EXIT_SUCCESS; + int32_t i = 0; + IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - /* Subscribe to all the topic filters in the subscription list. The - * blocking SUBSCRIBE function is used because the demo should not - * continue until SUBSCRIBE completes. */ - mqttStatus = IotMqtt_TimedSubscribe( *pMqttConnection, - pSubscriptions, - _TOPIC_FILTER_COUNT, - 0, - _MQTT_TIMEOUT_MS ); + /* Set the members of the subscription list. */ + for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) + { + pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; + pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; + pSubscriptions[ i ].topicFilterLength = _TOPIC_FILTER_LENGTH; + pSubscriptions[ i ].callback.param1 = pCallbackParameter; + pSubscriptions[ i ].callback.function = _mqttSubscriptionCallback; + } - switch( mqttStatus ) + /* Modify subscriptions by either subscribing or unsubscribing. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + subscriptionStatus = IotMqtt_TimedSubscribe( mqttConnection, + pSubscriptions, + _TOPIC_FILTER_COUNT, + 0, + _MQTT_TIMEOUT_MS ); + + /* Check the status of SUBSCRIBE. */ + switch( subscriptionStatus ) { case IOT_MQTT_SUCCESS: IotLogInfo( "All demo topic filter subscriptions accepted." ); @@ -481,7 +554,7 @@ int IotDemo_RunMqttDemo( bool awsIotMqttMode, /* Check which subscriptions were rejected before exiting the demo. */ for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) { - if( IotMqtt_IsSubscribed( *pMqttConnection, + if( IotMqtt_IsSubscribed( mqttConnection, pSubscriptions[ i ].pTopicFilter, pSubscriptions[ i ].topicFilterLength, NULL ) == true ) @@ -492,177 +565,313 @@ int IotDemo_RunMqttDemo( bool awsIotMqttMode, } else { - IotLogInfo( "Topic filter %.*s was rejected.", - pSubscriptions[ i ].topicFilterLength, - pSubscriptions[ i ].pTopicFilter ); + IotLogError( "Topic filter %.*s was rejected.", + pSubscriptions[ i ].topicFilterLength, + pSubscriptions[ i ].pTopicFilter ); } } - status = -1; + status = EXIT_FAILURE; break; default: - status = -1; + status = EXIT_FAILURE; break; } } - - if( status == 0 ) + else if( operation == IOT_MQTT_UNSUBSCRIBE ) { - /* The MQTT library should invoke this callback when a PUBLISH message - * is successfully transmitted. */ - publishComplete.function = _operationCompleteCallback; - - /* Set the common members of the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.topicNameLength = _TOPIC_FILTER_LENGTH; - publishInfo.pPayload = pPublishPayload; - publishInfo.retryMs = _PUBLISH_RETRY_MS; - publishInfo.retryLimit = _PUBLISH_RETRY_LIMIT; - - /* Create the semaphore that counts received PUBLISH messages.*/ - if( IotSemaphore_Create( &publishesReceived, - 0, - IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) + subscriptionStatus = IotMqtt_TimedUnsubscribe( mqttConnection, + pSubscriptions, + _TOPIC_FILTER_COUNT, + 0, + _MQTT_TIMEOUT_MS ); + + /* Check the status of UNSUBSCRIBE. */ + if( subscriptionStatus != IOT_MQTT_SUCCESS ) { - for( publishCount = 0; - publishCount < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; - publishCount++ ) - { - if( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) - { - IotLogInfo( "Publishing messages %d to %d.", - publishCount, - publishCount + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); - } + status = EXIT_FAILURE; + } + } + else + { + /* Only SUBSCRIBE and UNSUBSCRIBE are valid for modifying subscriptions. */ + IotLogError( "MQTT operation %s is not valid for modifying subscriptions.", + IotMqtt_OperationType( operation ) ); - /* Pass the PUBLISH number to the operation complete callback. */ - publishComplete.param1 = ( void * ) publishCount; + status = EXIT_FAILURE; + } - /* Choose a topic name. */ - publishInfo.pTopicName = pTopicFilters[ publishCount % _TOPIC_FILTER_COUNT ]; + return status; +} - /* Generate the payload for the PUBLISH. */ - status = snprintf( pPublishPayload, - _PUBLISH_PAYLOAD_BUFFER_LENGTH, - _PUBLISH_PAYLOAD_FORMAT, - ( int ) publishCount ); +/*-----------------------------------------------------------*/ - /* Check for errors from snprintf. */ - if( status < 0 ) - { - IotLogError( "Failed to generate MQTT PUBLISH payload for PUBLISH %d.", - ( int ) publishCount ); - status = -1; +/** + * @brief Transmit all messages and wait for them to be received on topic filters. + * + * @param[in] mqttConnection The MQTT connection to use for publishing. + * @param[in] pTopicNames Array of topic names for publishing. These were previously + * subscribed to as topic filters. + * @param[in] pPublishReceivedCounter Counts the number of messages received on + * topic filters. + * + * @return `EXIT_SUCCESS` if all messages are published and received; `EXIT_FAILURE` + * otherwise. + */ +static int _publishAllMessages( IotMqttConnection_t mqttConnection, + const char ** pTopicNames, + IotSemaphore_t * pPublishReceivedCounter ) +{ + int status = EXIT_SUCCESS; + intptr_t publishCount = 0, i = 0; + IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttCallbackInfo_t publishComplete = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + char pPublishPayload[ _PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; - break; - } - else - { - publishInfo.payloadLength = ( size_t ) status; - status = 0; - } + /* The MQTT library should invoke this callback when a PUBLISH message + * is successfully transmitted. */ + publishComplete.function = _operationCompleteCallback; + + /* Set the common members of the publish info. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.topicNameLength = _TOPIC_FILTER_LENGTH; + publishInfo.pPayload = pPublishPayload; + publishInfo.retryMs = _PUBLISH_RETRY_MS; + publishInfo.retryLimit = _PUBLISH_RETRY_LIMIT; + + /* Loop to PUBLISH all messages of this demo. */ + for( publishCount = 0; + publishCount < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; + publishCount++ ) + { + /* Announce which burst of messages is being published. */ + if( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) + { + IotLogInfo( "Publishing messages %d to %d.", + publishCount, + publishCount + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); + } - /* PUBLISH a message. */ - mqttStatus = IotMqtt_Publish( *pMqttConnection, - &publishInfo, - 0, - &publishComplete, - NULL ); + /* Pass the PUBLISH number to the operation complete callback. */ + publishComplete.param1 = ( void * ) publishCount; - if( mqttStatus != IOT_MQTT_STATUS_PENDING ) - { - IotLogError( "MQTT PUBLISH %d returned error %s.", - ( int ) publishCount, - IotMqtt_strerror( mqttStatus ) ); - status = -1; - break; - } + /* Choose a topic name (round-robin through the array of topic names). */ + publishInfo.pTopicName = pTopicNames[ publishCount % _TOPIC_FILTER_COUNT ]; - /* If a complete burst of messages has been published, wait for - * an equal number of messages to be received. Note that messages - * may be received out-of-order, especially if a message was - * dropped and had to be retried. */ - if( ( publishCount > 0 ) && - ( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) - { - IotLogInfo( "Waiting for %d publishes to be received.", - IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); + /* Generate the payload for the PUBLISH. */ + status = snprintf( pPublishPayload, + _PUBLISH_PAYLOAD_BUFFER_LENGTH, + _PUBLISH_PAYLOAD_FORMAT, + ( int ) publishCount ); - for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) - { - if( IotSemaphore_TimedWait( &publishesReceived, - _MQTT_TIMEOUT_MS ) == false ) - { - IotLogError( "Timed out waiting for incoming PUBLISH messages." ); - status = -1; - break; - } - } + /* Check for errors from snprintf. */ + if( status < 0 ) + { + IotLogError( "Failed to generate MQTT PUBLISH payload for PUBLISH %d.", + ( int ) publishCount ); + status = EXIT_FAILURE; - IotLogInfo( "%d publishes received.", - IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); - } + break; + } + else + { + publishInfo.payloadLength = ( size_t ) status; + status = EXIT_SUCCESS; + } + + /* PUBLISH a message. This is an asynchronous function that notifies of + * completion through a callback. */ + publishStatus = IotMqtt_Publish( mqttConnection, + &publishInfo, + 0, + &publishComplete, + NULL ); + + if( publishStatus != IOT_MQTT_STATUS_PENDING ) + { + IotLogError( "MQTT PUBLISH %d returned error %s.", + ( int ) publishCount, + IotMqtt_strerror( publishStatus ) ); + status = EXIT_FAILURE; + + break; + } + + /* If a complete burst of messages has been published, wait for an equal + * number of messages to be received. Note that messages may be received + * out-of-order, especially if a message was lost and had to be retried. */ + if( ( publishCount > 0 ) && + ( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) + { + IotLogInfo( "Waiting for %d publishes to be received.", + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); - /* Stop publishing if there was an error. */ - if( status == -1 ) + for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) + { + if( IotSemaphore_TimedWait( pPublishReceivedCounter, + _MQTT_TIMEOUT_MS ) == false ) { + IotLogError( "Timed out waiting for incoming PUBLISH messages." ); + status = EXIT_FAILURE; break; } } - /* Wait for the messages in the last burst to be received. This - * should also wait for all previously published messages. */ - if( status == 0 ) - { - IotLogInfo( "Waiting for all publishes to be received." ); + IotLogInfo( "%d publishes received.", + i ); + } - for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) - { - if( IotSemaphore_TimedWait( &publishesReceived, - _MQTT_TIMEOUT_MS ) == false ) - { - IotLogError( "Timed out waiting for incoming PUBLISH messages." ); - status = -1; - break; - } - } + /* Stop publishing if there was an error. */ + if( status == EXIT_FAILURE ) + { + break; + } + } - IotLogInfo( "All publishes received." ); + /* Wait for the messages in the last burst to be received. This should also + * wait for all previously published messages. */ + if( status == EXIT_SUCCESS ) + { + IotLogInfo( "Waiting for all publishes to be received." ); + + for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) + { + if( IotSemaphore_TimedWait( pPublishReceivedCounter, + _MQTT_TIMEOUT_MS ) == false ) + { + IotLogError( "Timed out waiting for incoming PUBLISH messages." ); + status = EXIT_FAILURE; + + break; } + } + + if( i == IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) + { + IotLogInfo( "All publishes received." ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The function that runs the MQTT demo, called by the demo runner. + * + * @param[in] awsIotMqttMode Specify if this demo is running with the AWS IoT + * MQTT server. Set this to `false` if using another MQTT server. + * @param[in] pIdentifier NULL-terminated MQTT client identifier. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkInterface The network interface to use for this demo. + * + * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. + */ +int RunMqttDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ) +{ + /* Return value of this function and the exit status of this program. */ + int status = EXIT_SUCCESS; + + /* Handle of the MQTT connection used in this demo. */ + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + + /* Counts the number of incoming PUBLISHES received (and allows the demo + * application to wait on incoming PUBLISH messages). */ + IotSemaphore_t publishesReceived; + + /* Topics used as both topic filters and topic names in this demo. */ + const char * pTopics[ _TOPIC_FILTER_COUNT ] = + { + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/3", + IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/4", + }; + + /* Flags for tracking which cleanup functions must be called. */ + bool librariesInitialized = false, connectionEstablished = false; + + /* Initialize the libraries required for this demo. */ + status = _initializeDemo(); + + if( status == EXIT_SUCCESS ) + { + /* Mark the libraries as initialized. */ + librariesInitialized = true; + + /* Establish a new MQTT connection. */ + status = _establishMqttConnection( awsIotMqttMode, + pIdentifier, + pNetworkServerInfo, + pNetworkCredentialInfo, + pNetworkInterface, + &mqttConnection ); + } + + if( status == EXIT_SUCCESS ) + { + /* Mark the MQTT connection as established. */ + connectionEstablished = true; + + /* Add the topic filter subscriptions used in this demo. */ + status = _modifySubscriptions( mqttConnection, + IOT_MQTT_SUBSCRIBE, + pTopics, + &publishesReceived ); + } - /* Destroy the received message counter. */ + if( status == EXIT_SUCCESS ) + { + /* Create the semaphore to count incoming PUBLISH messages. */ + if( IotSemaphore_Create( &publishesReceived, + 0, + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) + { + /* PUBLISH (and wait) for all messages. */ + status = _publishAllMessages( mqttConnection, + pTopics, + &publishesReceived ); + + /* Destroy the incoming PUBLISH counter. */ IotSemaphore_Destroy( &publishesReceived ); } else { - /* Couldn't create received message counter. */ - status = -1; + /* Failed to create incoming PUBLISH counter. */ + status = EXIT_FAILURE; } } - if( status == 0 ) + if( status == EXIT_SUCCESS ) { - /* Unsubscribe from all demo topic filters. */ - mqttStatus = IotMqtt_TimedUnsubscribe( *pMqttConnection, - pSubscriptions, - _TOPIC_FILTER_COUNT, - 0, - _MQTT_TIMEOUT_MS ); - - if( mqttStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT UNSUBSCRIBE returned error %s.", - IotMqtt_strerror( mqttStatus ) ); - status = -1; - } + /* Remove the topic subscription filters used in this demo. */ + status = _modifySubscriptions( mqttConnection, + IOT_MQTT_UNSUBSCRIBE, + pTopics, + NULL ); } /* Disconnect the MQTT connection if it was established. */ - if( *pMqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + if( connectionEstablished == true ) + { + IotMqtt_Disconnect( mqttConnection, 0 ); + } + + /* Clean up libraries if they were initialized. */ + if( librariesInitialized == true ) { - IotMqtt_Disconnect( *pMqttConnection, false ); + _cleanupDemo(); } return status; diff --git a/demos/posix/CMakeLists.txt b/demos/posix/CMakeLists.txt index 36d3848f79..3c9ac77d01 100644 --- a/demos/posix/CMakeLists.txt +++ b/demos/posix/CMakeLists.txt @@ -1,17 +1,14 @@ # Common demo files. -set( DEMO_COMMON_INCLUDE_FILES ".;${CMAKE_SOURCE_DIR}/demos" ) -set( DEMO_COMMON_SOURCE_FILES iot_demo_common_posix.c ) +set( DEMO_COMMON_SOURCE_FILES "iot_demo_arguments_posix.c;iot_demo_posix.c" ) # MQTT demo source files. add_executable( iot_demo_mqtt ${CMAKE_SOURCE_DIR}/demos/iot_demo_mqtt.c - iot_demo_mqtt_posix.c ${DEMO_COMMON_SOURCE_FILES} ) -# MQTT demo include files. -target_include_directories( iot_demo_mqtt - PRIVATE - ${DEMO_COMMON_INCLUDE_FILES} ) +# Select the demo function for the MQTT demo. +target_compile_definitions( iot_demo_mqtt + PRIVATE RunDemo=RunMqttDemo ) # MQTT demo library dependencies. target_link_libraries( iot_demo_mqtt iotplatform iotmqtt ) @@ -19,13 +16,11 @@ target_link_libraries( iot_demo_mqtt iotplatform iotmqtt ) # Shadow demo source files. add_executable( aws_iot_demo_shadow ${CMAKE_SOURCE_DIR}/demos/aws_iot_demo_shadow.c - aws_iot_demo_shadow_posix.c ${DEMO_COMMON_SOURCE_FILES} ) -# Shadow demo include files. -target_include_directories( iot_demo_mqtt - PRIVATE - ${DEMO_COMMON_INCLUDE_FILES} ) +# Select the demo function for the Shadow demo. +target_compile_definitions( aws_iot_demo_shadow + PRIVATE RunDemo=RunShadowDemo ) # Shadow demo library dependencies. target_link_libraries( aws_iot_demo_shadow iotplatform iotmqtt awsiotshadow ) diff --git a/demos/posix/aws_iot_demo_shadow_posix.c b/demos/posix/aws_iot_demo_shadow_posix.c deleted file mode 100644 index 870755e0a4..0000000000 --- a/demos/posix/aws_iot_demo_shadow_posix.c +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_demo_shadow_posix.c - * @brief Runs the Thing Shadow demo on POSIX systems. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include -#include -#include - -/* Shadow include. */ -#include "aws_iot_shadow.h" - -/* Common libraries include. */ -#include "iot_common.h" - -/* Common demo includes. */ -#include "iot_demo.h" -#include "iot_demo_posix.h" - -/* POSIX+OpenSSL network include. */ -#include "posix/iot_network_openssl.h" - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - bool commonInitialized = false, networkConnectionCreated = false; - int status = 0; - IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; - IotNetworkConnectionOpenssl_t networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; - IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; - IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, * pCredentials = NULL; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - - /* This function parses arguments and establishes the network connection - * before running the Shadow demo. */ - - /* Set the default Thing Name. */ - #ifdef IOT_DEMO_THING_NAME - demoArguments.pIdentifier = IOT_DEMO_THING_NAME; - #endif - - /* Parse any command line arguments. */ - if( IotDemo_ParseArguments( argc, - argv, - &demoArguments ) == false ) - { - status = -1; - } - - /* Initialize the network. */ - if( status == 0 ) - { - if( IotCommon_Init() == false ) - { - status = -1; - } - else - { - if( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) - { - IotCommon_Cleanup(); - status = -1; - } - else - { - commonInitialized = true; - } - } - } - - /* Thing Name must be set for this demo. */ - if( demoArguments.pIdentifier == NULL ) - { - IotLogError( "Thing Name must be set for Shadow demo." ); - - status = -1; - } - - if( status == 0 ) - { - /* Set the TLS connection information for secured connections. Thing - * Shadow is specific to AWS IoT, so it always requires a secured connection. */ - pCredentials = &credentials; - - /* By default AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER enables ALPN. ALPN - * must be used with port 443; disable ALPN if another port is being used. */ - if( demoArguments.port != 443 ) - { - credentials.pAlpnProtos = NULL; - } - - /* Set the paths to the credentials. Lengths of credential paths are - * ignored by the POSIX platform layer, so they are not set. */ - credentials.pRootCaPath = demoArguments.pRootCaPath; - credentials.pClientCertPath = demoArguments.pClientCertPath; - credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; - - /* Set server info. */ - serverInfo.pHostName = demoArguments.pHostName; - serverInfo.port = demoArguments.port; - - /* Establish a TCP connection to the MQTT server. */ - if( IotNetworkOpenssl_Create( &serverInfo, - pCredentials, - &networkConnection ) != IOT_NETWORK_SUCCESS ) - { - status = -1; - } - } - - if( status == 0 ) - { - /* Set the MQTT receive callback for a network connection. This receive - * callback processes MQTT data from the network. */ - if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, - IotMqtt_ReceiveCallback, - &mqttConnection ) != IOT_NETWORK_SUCCESS ) - { - status = -1; - } - } - - if( status == 0 ) - { - /* Set the members of the network interface used by the MQTT connection. */ - networkInterface.pDisconnectContext = ( void * ) &networkConnection; - networkInterface.pSendContext = ( void * ) &networkConnection; - networkInterface.disconnect = IotNetworkOpenssl_Close; - networkInterface.send = IotNetworkOpenssl_Send; - - /* Initialize the MQTT library and Shadow library. */ - if( IotMqtt_Init() == IOT_MQTT_SUCCESS ) - { - if( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ) - { - /* Run the Shadow demo. */ - status = AwsIotDemo_RunShadowDemo( demoArguments.pIdentifier, - &mqttConnection, - &networkInterface ); - - /* Clean up the MQTT library and Shadow library. */ - AwsIotShadow_Cleanup(); - } - else - { - status = -1; - } - - IotMqtt_Cleanup(); - } - else - { - status = -1; - } - } - - /* Close and destroy the network connection (if it was established). */ - if( networkConnectionCreated == true ) - { - /* Note that the MQTT library may have already closed the connection. - * However, the network close function is safe to call on a closed connection. - * On the other hand, the destroy connection function must only be called ONCE. - */ - IotNetworkOpenssl_Close( &networkConnection ); - IotNetworkOpenssl_Destroy( &networkConnection ); - } - - /* Clean up the network. */ - if( commonInitialized == true ) - { - IotCommon_Cleanup(); - IotNetworkOpenssl_Cleanup(); - } - - /* Log the demo status. */ - if( status == 0 ) - { - IotLogInfo( "Demo completed successfully." ); - } - else - { - IotLogError( "Error running demo, status %d.", status ); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/demos/posix/iot_demo_common_posix.c b/demos/posix/iot_demo_arguments_posix.c similarity index 81% rename from demos/posix/iot_demo_common_posix.c rename to demos/posix/iot_demo_arguments_posix.c index b4c109e33a..ec2a982a3d 100644 --- a/demos/posix/iot_demo_common_posix.c +++ b/demos/posix/iot_demo_arguments_posix.c @@ -20,8 +20,9 @@ */ /** - * @file iot_mqtt_demo_common_posix.c - * @brief Implements the common demo functions for POSIX systems. + * @file iot_demo_arguments_posix.c + * @brief Implements a function for retrieving command line arguments on POSIX + * systems. */ /* Build using a config header, if provided. */ @@ -35,19 +36,23 @@ #include #include +/* Error handling include. */ +#include "private/iot_error.h" + /* Common demo includes. */ -#include "iot_demo.h" -#include "iot_demo_posix.h" +#include "iot_demo_arguments.h" +#include "iot_demo_logging.h" /*-----------------------------------------------------------*/ -bool IotDemo_ParseArguments( int argc, - char ** argv, - IotDemoArguments_t * const pArguments ) +/** + * @brief Set the default values of an #IotDemoArguments_t based on compile-time + * defined constants. + * + * @param[out] pArguments Default values will be placed here. + */ +static void _setDefaultArguments( IotDemoArguments_t * pArguments ) { - int option = 0; - unsigned long int port = 0; - /* Default to AWS IoT MQTT mode. */ pArguments->awsIotMqttMode = true; @@ -80,6 +85,86 @@ bool IotDemo_ParseArguments( int argc, #ifdef IOT_DEMO_PRIVATE_KEY pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Validates the members of an #IotDemoArguments_t. + * + * @param[in] pArguments The #IotDemoArguments_t to validate. + * + * @return `true` if every member of the #IotDemoArguments_t is valid; `false` + * otherwise. + */ +static bool _validateArguments( const IotDemoArguments_t * pArguments ) +{ + /* Declare a status variable for this function. */ + _IOT_FUNCTION_ENTRY( bool, true ); + + /* Check that a server was set. */ + if( ( pArguments->pHostName == NULL ) || + ( strlen( pArguments->pHostName ) == 0 ) ) + { + IotLogError( "MQTT server not set. Exiting." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a server port was set. */ + if( pArguments->port == 0 ) + { + IotLogError( "MQTT server port not set. Exiting." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check credentials for a secured connection. */ + if( pArguments->securedConnection == true ) + { + /* Check that a root CA path was set. */ + if( ( pArguments->pRootCaPath == NULL ) || + ( strlen( pArguments->pRootCaPath ) == 0 ) ) + { + IotLogError( "Root CA path not set. Exiting." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + IotLogError( "Client certificate private key not set. Exiting." ); + + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + /* No cleanup is required for this function. */ + _IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +bool IotDemo_ParseArguments( int argc, + char ** argv, + IotDemoArguments_t * pArguments ) +{ + int option = 0; + unsigned long int port = 0; + + /* Set default arguments based on compile-time constants. */ + _setDefaultArguments( pArguments ); IotLogInfo( "Parsing command line arguments." ); @@ -170,54 +255,6 @@ bool IotDemo_ParseArguments( int argc, } } - /* Check that a server was set. */ - if( ( pArguments->pHostName == NULL ) || - ( strlen( pArguments->pHostName ) == 0 ) ) - { - IotLogError( "MQTT server not set. Exiting." ); - - return false; - } - - /* Check that a server port was set. */ - if( pArguments->port == 0 ) - { - IotLogError( "MQTT server port not set. Exiting." ); - - return false; - } - - /* Check credentials for a secured connection. */ - if( pArguments->securedConnection == true ) - { - /* Check that a root CA path was set. */ - if( ( pArguments->pRootCaPath == NULL ) || - ( strlen( pArguments->pRootCaPath ) == 0 ) ) - { - IotLogError( "Root CA path not set. Exiting." ); - - return false; - } - - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - return false; - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Client certificate private key not set. Exiting." ); - - return false; - } - } - IotLogInfo( "Command line arguments successfully parsed." ); IotLogDebug( "AWS IoT MQTT mode: %s", pArguments->awsIotMqttMode == true ? "true" : "false" ); @@ -228,7 +265,8 @@ bool IotDemo_ParseArguments( int argc, IotLogDebug( "Client certificate: %s", pArguments->pClientCertPath ); IotLogDebug( "Private key: %s", pArguments->pPrivateKeyPath ); - return true; + /* Validate the arguments and return the value of that check. */ + return _validateArguments( pArguments ); } /*-----------------------------------------------------------*/ diff --git a/demos/posix/iot_demo_mqtt_posix.c b/demos/posix/iot_demo_mqtt_posix.c deleted file mode 100644 index c4ff61d2ee..0000000000 --- a/demos/posix/iot_demo_mqtt_posix.c +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_mqtt_posix.c - * @brief Runs the MQTT demo on POSIX systems. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include -#include -#include - -/* Common libraries include. */ -#include "iot_common.h" - -/* Common demo includes. */ -#include "iot_demo.h" -#include "iot_demo_posix.h" - -/* POSIX+OpenSSL network include. */ -#include "posix/iot_network_openssl.h" - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - bool commonInitialized = false, networkConnectionCreated = false; - int status = 0; - IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; - IotNetworkConnectionOpenssl_t networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; - IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; - IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, * pCredentials = NULL; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - - /* This function parses arguments and establishes the network connection - * before running the MQTT demo. */ - - /* Set default client identifier. */ - #ifdef IOT_DEMO_MQTT_CLIENT_IDENTIFIER - demoArguments.pIdentifier = IOT_DEMO_MQTT_CLIENT_IDENTIFIER; - #endif - - /* Parse any command line arguments. */ - if( IotDemo_ParseArguments( argc, - argv, - &demoArguments ) == false ) - { - status = -1; - } - - /* Initialize the common libraries and network. */ - if( status == 0 ) - { - if( IotCommon_Init() == false ) - { - status = -1; - } - else - { - if( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) - { - IotCommon_Cleanup(); - status = -1; - } - else - { - commonInitialized = true; - } - } - } - - if( status == 0 ) - { - /* Set the TLS connection information for secured connections. */ - if( demoArguments.securedConnection == true ) - { - pCredentials = &credentials; - - /* By default AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER enables ALPN. ALPN - * must be used with port 443; disable ALPN if another port is being used. */ - if( demoArguments.port != 443 ) - { - credentials.pAlpnProtos = NULL; - } - - /* Set the paths to the credentials. Lengths of credential paths are - * ignored by the POSIX platform layer, so they are not set. */ - credentials.pRootCaPath = demoArguments.pRootCaPath; - credentials.pClientCertPath = demoArguments.pClientCertPath; - credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; - } - - /* Set server info. */ - serverInfo.pHostName = demoArguments.pHostName; - serverInfo.port = demoArguments.port; - - /* Establish a TCP connection to the MQTT server. */ - if( IotNetworkOpenssl_Create( &serverInfo, - pCredentials, - &networkConnection ) != IOT_NETWORK_SUCCESS ) - { - status = -1; - } - else - { - networkConnectionCreated = true; - } - } - - if( status == 0 ) - { - /* Set the MQTT receive callback for a network connection. This receive - * callback processes MQTT data from the network. */ - if( IotNetworkOpenssl_SetReceiveCallback( &networkConnection, - IotMqtt_ReceiveCallback, - &mqttConnection ) != IOT_NETWORK_SUCCESS ) - { - status = -1; - } - } - - if( status == 0 ) - { - /* Set the members of the network interface used by the MQTT connection. */ - networkInterface.pDisconnectContext = ( void * ) &networkConnection; - networkInterface.pSendContext = ( void * ) &networkConnection; - networkInterface.disconnect = IotNetworkOpenssl_Close; - networkInterface.send = IotNetworkOpenssl_Send; - - /* Initialize the MQTT library. */ - if( IotMqtt_Init() == IOT_MQTT_SUCCESS ) - { - /* Run the MQTT demo. */ - status = IotDemo_RunMqttDemo( demoArguments.awsIotMqttMode, - demoArguments.pIdentifier, - &mqttConnection, - &networkInterface ); - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); - } - else - { - status = -1; - } - } - - /* Close and destroy the network connection (if it was established). */ - if( networkConnectionCreated == true ) - { - /* Note that the MQTT library may have already closed the connection. - * However, the network close function is safe to call on a closed connection. - * On the other hand, the destroy connection function must only be called ONCE. - */ - IotNetworkOpenssl_Close( &networkConnection ); - IotNetworkOpenssl_Destroy( &networkConnection ); - } - - /* Clean up the common libraries and network. */ - if( commonInitialized == true ) - { - IotCommon_Cleanup(); - IotNetworkOpenssl_Cleanup(); - } - - /* Log the demo status. */ - if( status == 0 ) - { - IotLogInfo( "Demo completed successfully." ); - } - else - { - IotLogError( "Error running demo, status %d.", status ); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/demos/posix/iot_demo_posix.c b/demos/posix/iot_demo_posix.c new file mode 100644 index 0000000000..f7dc7bef28 --- /dev/null +++ b/demos/posix/iot_demo_posix.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_demo_posix.c + * @brief Generic demo runner for POSIX systems. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/* Common libraries include. */ +#include "iot_common.h" + +/* Common demo includes. */ +#include "iot_demo_arguments.h" +#include "iot_demo_logging.h" + +/* POSIX+OpenSSL network include. */ +#include "posix/iot_network_openssl.h" + +/* This file calls a generic placeholder demo function. The build system selects + * the actual demo function to run by defining it. */ +#ifndef RunDemo + #error "Demo function undefined." +#endif + +/*-----------------------------------------------------------*/ + +/* Declaration of generic demo function. */ +extern int RunDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ); + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + /* Return value of this function and the exit status of this program. */ + int status = EXIT_SUCCESS; + + /* Status returned from network stack initialization. */ + IotNetworkError_t networkInitStatus = IOT_NETWORK_SUCCESS; + + /* Arguments for this demo. */ + IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; + + /* Network server info and credentials. */ + IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; + IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, + * pCredentials = NULL; + + /* Set default identifier if defined. The identifier is used as either the + * MQTT client identifier or the Thing Name, which identifies this client to + * the cloud. */ + #ifdef IOT_DEMO_IDENTIFIER + demoArguments.pIdentifier = IOT_DEMO_IDENTIFIER; + #endif + + /* Parse any command line arguments. */ + if( IotDemo_ParseArguments( argc, + argv, + &demoArguments ) == true ) + { + /* Set the members of the server info. */ + serverInfo.pHostName = demoArguments.pHostName; + serverInfo.port = demoArguments.port; + + /* For a secured connection, set the members of the credentials. */ + if( demoArguments.securedConnection == true ) + { + /* Set credential paths. */ + credentials.pClientCertPath = demoArguments.pClientCertPath; + credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; + credentials.pRootCaPath = demoArguments.pRootCaPath; + + /* By default, the credential initializer enables ALPN with AWS IoT, + * which only works over port 443. Disable ALPN if another port is + * used. */ + if( demoArguments.port != 443 ) + { + credentials.pAlpnProtos = NULL; + } + + /* Set the pointer to the credentials. */ + pCredentials = &credentials; + } + + /* Initialize the network stack. */ + networkInitStatus = IotNetworkOpenssl_Init(); + + if( networkInitStatus == IOT_NETWORK_SUCCESS ) + { + /* Run the demo. */ + status = RunDemo( demoArguments.awsIotMqttMode, + demoArguments.pIdentifier, + &serverInfo, + pCredentials, + IOT_NETWORK_INTERFACE_OPENSSL ); + + /* Clean up the network stack. */ + IotNetworkOpenssl_Cleanup(); + } + else + { + /* Network stack failed to initialize. */ + status = EXIT_FAILURE; + } + } + else + { + /* Error parsing arguments. */ + status = EXIT_FAILURE; + } + + /* Log the demo status. */ + if( status == EXIT_SUCCESS ) + { + IotLogInfo( "Demo completed successfully." ); + } + else + { + IotLogError( "Error occurred while running demo." ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/doc/config/defender b/doc/config/defender index 4876799a10..99164235b0 100644 --- a/doc/config/defender +++ b/doc/config/defender @@ -21,8 +21,6 @@ INPUT = doc/lib/ \ # Library file names. FILE_PATTERNS = *defender*.h *defender*.txt -EXAMPLE_PATH = lib/include/aws_iot_defender.h - # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ doc/tag/mqtt.tag=../mqtt \ diff --git a/doc/config/mqtt b/doc/config/mqtt index 2283e0dca1..82b8556673 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -9,6 +9,9 @@ PROJECT_BRIEF = "MQTT 3.1.1 client library" # Library documentation output directory. HTML_OUTPUT = mqtt +# Generate documentation for MQTT packet serializer overrides. +PREDEFINED += "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES=1" + # Generate Doxygen tag file for this library. GENERATE_TAGFILE = doc/tag/mqtt.tag @@ -16,6 +19,7 @@ GENERATE_TAGFILE = doc/tag/mqtt.tag INPUT = doc \ doc/lib \ lib/include \ + lib/include/types \ lib/include/private \ lib/source/mqtt \ demos/ \ diff --git a/doc/config/taskpool b/doc/config/taskpool index c8deb7d33d..5f621d1260 100644 --- a/doc/config/taskpool +++ b/doc/config/taskpool @@ -3,7 +3,7 @@ @INCLUDE = common # Basic project information. -PROJECT_NAME = "TASKPOOL" +PROJECT_NAME = "Task Pool" PROJECT_BRIEF = "Task pool library" # Library documentation output directory. @@ -19,9 +19,7 @@ INPUT = doc \ lib/include/private \ lib/include/types \ lib/source/common \ - demos/ \ - tests/ \ - tests/common/unit + tests/common/unit # Library file names. FILE_PATTERNS = *taskpool*.c *taskpool*.h *taskpool*.txt diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 763485526e..12976527f1 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -98,7 +98,7 @@ See @ref IOT_DEMO_ROOT_CA, @ref IOT_DEMO_CLIENT_CERT, and @ref IOT_DEMO_PRIVATE_ Must be followed by a string representing either an MQTT client identifier (MQTT demo only) or a Thing Name (all other demos). Because AWS IoT recommends that MQTT client identifier and Thing Name match, demos will use the Thing Name as the MQTT client identifier when possible. -See @ref IOT_DEMO_MQTT_CLIENT_IDENTIFIER (MQTT demo only) or @ref AWS_IOT_DEMO_THING_NAME for the compile-time default settings of this option. +See @ref IOT_DEMO_IDENTIFIER for the compile-time default settings of this option. @section building_tests Building and running the tests @brief How to build and run the SDK tests. diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 3bcffdf5f8..ef6ba8df61 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -186,16 +186,6 @@ Up to @ref IOT_TEST_MQTT_THREADS `*` @ref IOT_TEST_MQTT_PUBLISHES_PER_THREAD pub /** @configpage{mqtt_demo,MQTT demo,Demo,demos} -@section IOT_DEMO_MQTT_CLIENT_IDENTIFIER -@brief The MQTT client identifier to use for the demo. - -No two clients may connect using the same client identifier simultaneously. - -The MQTT client identifier may also be set with [the command line option -i](@ref demo_optioni). The command line option will override this setting. If this setting is undefined and no command line option is provided, the demo will generate a unique client identifier to use. - -@configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
-@configdefault The demo will generate a unique client identifier if this setting is undefined. - @section IOT_DEMO_MQTT_TOPIC_PREFIX @brief The string prepended to topic filters in the demo. @@ -248,7 +238,7 @@ Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SD @section IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES @brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. -Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #IotMqttNetIf_t for a list of functions that can be overridden. If this setting is `1`, the serializer [initialization](@ref _IotMqtt_InitSerialize) and [cleanup](@ref _IotMqtt_CleanupSerialize) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default serializer initialization and cleanup functions. They must have the following signatures: +Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #IotMqttSerializer_t for a list of functions that can be overridden. If this setting is `1`, the serializer [initialization](@ref _IotMqtt_InitSerialize) and [cleanup](@ref _IotMqtt_CleanupSerialize) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default serializer initialization and cleanup functions. They must have the following signatures: @code{c} #include diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 05cebbced2..34a0cd1eb2 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -128,18 +128,18 @@ In demo applications, the default client certificate private key can be overridd @configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref IOT_DEMO_CLIENT_CERT). -@section AWS_IOT_DEMO_THING_NAME -@brief Default Thing Name used for demos specific to AWS IoT. +@section IOT_DEMO_IDENTIFIER +@brief A string that identifies this client to the cloud. -Thing Names are used to manage devices in AWS IoT. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-thing-management.html) for more information. +Depending on the demo, this identifier is used for the client identifier (general MQTT demos) or the AWS IoT Thing Name (demos specific to AWS IoT). -In demo applications, the default Thing Name can be overridden with the command line option `-i`. The command line option will override this setting. +In demo applications, the default identifier can be overridden with the command line option `-i`. The command line option will override this setting. -No default value is provided for this setting. For demos requiring a Thing Name (such as Shadow) this setting must be defined (or a command line option `-i` given to the demo application); otherwise, the demo will fail. +In the [general MQTT demos](@ref mqtt_demo), this identifier becomes the MQTT client identifier. No two clients may use the same identifier simultaneously. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters. If this setting is undefined and no command line option is provided at runtime, the MQTT demos may generate a unique client identifier for use. -This setting does not affect the MQTT demo (which is not specific to AWS IoT); see @ref IOT_DEMO_MQTT_CLIENT_IDENTIFIER for the equivalent setting for the MQTT demo. +In the demos specific to AWS IoT, this identifier becomes the Thing Name, which is used to manage devices in AWS IoT. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-thing-management.html) for more information. No default value is provided for Thing Names. For demos requiring a Thing Name (such as Shadow) this setting must be defined (or a command line option `-i` given to the demo application); otherwise, the demo will fail. -@configpossible Any string representing an AWS IoT Thing Name. +@configpossible Strings representing MQTT client identifiers or Thing Names. */ /** diff --git a/doc/plantuml/images/mqtt_function_receivecallback_nopartial.png b/doc/plantuml/images/mqtt_function_receivecallback_nopartial.png deleted file mode 100644 index a083896560359b4a208b37751553b587ec12001d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42752 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 system: Incoming bytes\nfrom network -activate system -system -> system: Allocate pReceivedData -group pReceivedData owned by system -system -> system: Read network bytes\ninto pReceivedData -end - -group pReceivedData owned by the MQTT library -system -> mqtt_receive: Call MQTT receive\ncallback -deactivate system - -activate mqtt_receive -mqtt_receive -> mqtt_receive: Process all bytes\nin pReceivedData -mqtt_receive -> mqtt_other: Pass processed data -activate mqtt_other -mqtt_receive -> system: Receive callback returns -deactivate mqtt_receive -activate system - -mqtt_other -> mqtt_other: Handle processed data -mqtt_other -> system: Call freeReceivedData -deactivate mqtt_other -end - -group pReceivedData owned by system -system -> system: Free pReceivedData -deactivate system -end -@enduml diff --git a/doc/plantuml/mqtt_function_receivecallback_partial.pu b/doc/plantuml/mqtt_function_receivecallback_partial.pu deleted file mode 100644 index 3814fbbf94..0000000000 --- a/doc/plantuml/mqtt_function_receivecallback_partial.pu +++ /dev/null @@ -1,40 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -participant "System" as system -participant "MQTT Receive Callback" as mqtt_receive -participant "Other MQTT functions" as mqtt_other - --> system: 1 complete packet\nand 1 partial packet\nfrom network -activate system -system -> system: Allocate pReceivedData -group pReceivedData owned by system -system -> system: Read packets into\npReceivedData -end - -group pReceivedData owned by the MQTT library -system -> mqtt_receive: Call MQTT receive\ncallback -deactivate system - -activate mqtt_receive -mqtt_receive -> mqtt_receive: Process complete packet\nin pReceivedData -mqtt_receive -> mqtt_other: Pass complete packet -activate mqtt_other -mqtt_receive -> mqtt_receive: Detect partial packet -mqtt_receive -> system: Return bytes processed -deactivate mqtt_receive -activate system -end -group pReceivedData owned by system -mqtt_other -> mqtt_other: Handle complete packet;\ndon't call freeReceivedData -deactivate mqtt_other --> system: Remainder of partial\npacket from network -system -> system: Read remainder of\npacket in pReceivedData.\nOverwrite bytes processed. -end -group pReceivedData owned by MQTT library -system -> mqtt_receive: Call MQTT receive callback -deactivate system -end -@enduml diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 5bf35fcb3e..f0c7a526be 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -163,14 +163,10 @@ typedef enum */ typedef enum { - AWS_IOT_DEFENDER_METRICS_ACCEPTED = 0, /**< Metrics report was accepted by defender service. */ - AWS_IOT_DEFENDER_METRICS_REJECTED, /**< Metrics report was rejected by defender service. */ - AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED, /**< Failed to connect to defender service endpoint. */ - AWS_IOT_DEFENDER_MQTT_CONNECTION_FAILED, /**< Failed to setup MQTT connection. */ - AWS_IOT_DEFENDER_MQTT_SUBSCRIPTION_FAILED, /**< Failed to subscribe defender MQTT topics. */ - AWS_IOT_DEFENDER_MQTT_PUBLISH_FAILED, /**< Failed to publish to MQTT topics. */ - AWS_IOT_DEFENDER_METRICS_SERIALIZATION_FAILED, /**< Failed to serialize metrics report. */ - AWS_IOT_DEFENDER_EVENT_NO_MEMORY /**< Metrics report was not able to be published due to memory allocation failure. */ + AWS_IOT_DEFENDER_METRICS_ACCEPTED = 0, /**< Metrics report was accepted by defender service. */ + AWS_IOT_DEFENDER_METRICS_REJECTED, /**< Metrics report was rejected by defender service. */ + AWS_IOT_DEFENDER_FAILURE_MQTT, /**< Defender failed to perform MQTT operation. */ + AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT /**< Defender failed to create metrics report. */ } AwsIotDefenderEventType_t; /** @@ -211,12 +207,9 @@ typedef struct AwsIotDefenderCallback */ typedef struct AwsIotDefenderStartInfo { - void * pConnectionInfo; /**< Connection information(required). */ - void * pCredentialInfo; /**< Credential information(required). */ - void * pConnection; /**< Connection object(required). */ - const IotNetworkInterface_t * pNetworkInterface; /**< Network inferface defender uses(required). */ - IotMqttConnectInfo_t mqttConnectionInfo; /**< Network inferface defender uses(required). */ - AwsIotDefenderCallback_t callback; /**< Callback function parameter(optional). */ + IotMqttNetworkInfo_t mqttNetworkInfo; /**< MQTT Network info used by defender (required). */ + IotMqttConnectInfo_t mqttConnectionInfo; /**< MQTT connection info used by defender (required). */ + AwsIotDefenderCallback_t callback; /**< Callback function parameter(optional). */ } AwsIotDefenderStartInfo_t; /** @@ -403,7 +396,7 @@ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); * @brief Return a string that describes #AwsIotDefenderEventType_t */ /* @[declare_defender_describeeventtype] */ -const char * AwsIotDefender_DescribeEventType( AwsIotDefenderEventType_t eventType ); +const char * AwsIotDefender_GetEventError(); /* @[declare_defender_describeeventtype] */ #endif /* end of include guard: _AWS_IOT_DEFENDER_H_ */ diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index a868d50df7..c08dd19649 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -32,8 +32,8 @@ #include IOT_CONFIG_FILE #endif -/* MQTT include. */ -#include "iot_mqtt.h" +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" /*--------------------------- Shadow handle types ---------------------------*/ diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 8eb00fa0c6..44bfc9e132 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -32,982 +32,8 @@ #include IOT_CONFIG_FILE #endif -/* Standard includes. */ -#include -#include -#include - -/* Platform network include. */ -#include "platform/iot_network.h" - -/*---------------------------- MQTT handle types ----------------------------*/ - -/** - * @handles{mqtt,MQTT library} - */ - -/** - * @ingroup mqtt_datatypes_handles - * @brief Opaque handle of an MQTT connection. - * - * This type identifies an MQTT connection, which is valid after a successful call - * to @ref mqtt_function_connect. A variable of this type is passed as the first - * argument to [MQTT library functions](@ref mqtt_functions) to identify which - * connection that function acts on. - * - * A call to @ref mqtt_function_disconnect makes a connection handle invalid. Once - * @ref mqtt_function_disconnect returns, the connection handle should no longer - * be used. - * - * @initializer{IotMqttConnection_t,IOT_MQTT_CONNECTION_INITIALIZER} - */ -typedef void * IotMqttConnection_t; - -/** - * @ingroup mqtt_datatypes_handles - * @brief Opaque handle that references an in-progress MQTT operation. - * - * Set as an output parameter of @ref mqtt_function_publish, @ref mqtt_function_subscribe, - * and @ref mqtt_function_unsubscribe. These functions queue an MQTT operation; the result - * of the operation is unknown until a response from the MQTT server is received. Therefore, - * this handle serves as a reference to MQTT operations awaiting MQTT server response. - * - * This reference will be valid from the successful return of @ref mqtt_function_publish, - * @ref mqtt_function_subscribe, or @ref mqtt_function_unsubscribe. The reference becomes - * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or - * @ref mqtt_function_wait returns. - * - * @initializer{IotMqttReference_t,IOT_MQTT_REFERENCE_INITIALIZER} - * - * @see @ref mqtt_function_wait and #IOT_MQTT_FLAG_WAITABLE for waiting on a reference. - * #IotMqttCallbackInfo_t and #IotMqttCallbackParam_t for an asynchronous notification - * of completion. - */ -typedef void * IotMqttReference_t; - -/*-------------------------- MQTT enumerated types --------------------------*/ - -/** - * @enums{mqtt,MQTT library} - */ - -/** - * @ingroup mqtt_datatypes_enums - * @brief Return codes of [MQTT functions](@ref mqtt_functions). - * - * The function @ref mqtt_function_strerror can be used to get a return code's - * description. - */ -typedef enum IotMqttError -{ - /** - * @brief MQTT operation completed successfully. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_publish with QoS 0 parameter - * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish - * - * Will also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result when successful. - */ - IOT_MQTT_SUCCESS = 0, - - /** - * @brief MQTT operation queued, awaiting result. - * - * Functions that may return this value: - * - @ref mqtt_function_subscribe - * - @ref mqtt_function_unsubscribe - * - @ref mqtt_function_publish with QoS 1 parameter - */ - IOT_MQTT_STATUS_PENDING, - - /** - * @brief Initialization failed. - * - * Functions that may return this value: - * - @ref mqtt_function_init - */ - IOT_MQTT_INIT_FAILED, - - /** - * @brief At least one parameter is invalid. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish - * - @ref mqtt_function_wait - */ - IOT_MQTT_BAD_PARAMETER, - - /** - * @brief MQTT operation failed because of memory allocation failure. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish - */ - IOT_MQTT_NO_MEMORY, - - /** - * @brief MQTT operation failed because the network was unusable. - * - * This return value may indicate that the network is disconnected. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result. - */ - IOT_MQTT_NETWORK_ERROR, - - /** - * @brief MQTT operation could not be scheduled, i.e. enqueued for sending. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish - */ - IOT_MQTT_SCHEDULING_ERROR, - - /** - * @brief MQTT response packet received from the network is malformed. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result. - * - * @note If this value is received, the network connection has been closed - * (unless a [disconnect function](@ref IotMqttNetIf_t.disconnect) was not - * provided to @ref mqtt_function_connect). - */ - IOT_MQTT_BAD_RESPONSE, - - /** - * @brief A blocking MQTT operation timed out. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish - */ - IOT_MQTT_TIMEOUT, - - /** - * @brief A CONNECT or at least one subscription was refused by the server. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter - * is associated with a SUBSCRIBE operation. - * - @ref mqtt_function_timedsubscribe - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result for a SUBSCRIBE. - * - * @note If this value is returned and multiple subscriptions were passed to - * @ref mqtt_function_subscribe (or @ref mqtt_function_timedsubscribe), it's - * still possible that some of the subscriptions succeeded. This value only - * signifies that AT LEAST ONE subscription was rejected. The function @ref - * mqtt_function_issubscribed can be used to determine which subscriptions - * were accepted or rejected. - */ - IOT_MQTT_SERVER_REFUSED, - - /** - * @brief A QoS 1 PUBLISH received no response and [the retry limit] - * (#IotMqttPublishInfo_t.retryLimit) was reached. - * - * Functions that may return this value: - * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter - * is associated with a QoS 1 PUBLISH operation - * - @ref mqtt_function_timedpublish - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. - */ - IOT_MQTT_RETRY_NO_RESPONSE -} IotMqttError_t; - -/** - * @ingroup mqtt_datatypes_enums - * @brief Types of MQTT operations. - * - * The function @ref mqtt_function_operationtype can be used to get an operation - * type's description. - */ -typedef enum IotMqttOperationType -{ - IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ - IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ - IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ - IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ - IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ - IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ - IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ -} IotMqttOperationType_t; - -/** - * @ingroup mqtt_datatypes_enums - * @brief Quality of service levels for MQTT PUBLISH messages. - * - * All MQTT PUBLISH messages, including Last Will and Testament and messages - * received on subscription filters, have an associated Quality of Service, - * which defines any delivery guarantees for that message. - * - QoS 0 messages will be delivered at most once. This is a "best effort" - * transmission with no retransmissions. - * - QoS 1 messages will be delivered at least once. See #IotMqttPublishInfo_t - * for the retransmission strategy this library uses to redeliver messages - * assumed to be lost. - * - * @attention QoS 2 is not supported by this library and should not be used. - */ -typedef enum IotMqttQos -{ - IOT_MQTT_QOS_0 = 0, /**< Delivery at most once. */ - IOT_MQTT_QOS_1 = 1, /**< Delivery at least once. See #IotMqttPublishInfo_t for client-side retry strategy. */ - IOT_MQTT_QOS_2 = 2 /**< Delivery exactly once. Unsupported, but enumerated for completeness. */ -} IotMqttQos_t; - -/*------------------------- MQTT parameter structs --------------------------*/ - -/** - * @paramstructs{mqtt,MQTT} - */ - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a PUBLISH message. - * - * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publish - * - * Passed to @ref mqtt_function_publish as the message to publish and @ref - * mqtt_function_connect as the Last Will and Testament (LWT) message. - * - * @initializer{IotMqttPublishInfo_t,IOT_MQTT_PUBLISH_INFO_INITIALIZER} - * - * #IotMqttPublishInfo_t.retryMs and #IotMqttPublishInfo_t.retryLimit are only - * relevant to QoS 1 PUBLISH messages. They are ignored for QoS 0 PUBLISH - * messages and LWT messages. These members control retransmissions of QoS 1 - * messages under the following rules: - * - Retransmission is disabled when #IotMqttPublishInfo_t.retryLimit is 0. - * After sending the PUBLISH, the library will wait indefinitely for a PUBACK. - * - If #IotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes - * that do not receive a PUBACK within #IotMqttPublishInfo_t.retryMs will be - * retransmitted, up to #IotMqttPublishInfo_t.retryLimit times. - * - * Retransmission follows a truncated exponential backoff strategy. The constant - * @ref IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. - * - * After #IotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT - * library will wait @ref IOT_MQTT_RESPONSE_WAIT_MS before a final check - * for a PUBACK. If no PUBACK was received within this time, the QoS 1 PUBLISH - * fails with the code #IOT_MQTT_RETRY_NO_RESPONSE. - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - * - * @note The AWS IoT MQTT server does not support the DUP bit. When - * [using this library with the AWS IoT MQTT server](@ref IotMqttConnectInfo_t.awsIotMqttMode), - * retransmissions will instead be sent with a new packet identifier in the PUBLISH - * packet. This is a nonstandard workaround. Note that this workaround has some - * flaws, including the following: - * - The previous packet identifier is forgotten, so if a PUBACK arrives for that - * packet identifier, it will be ignored. On an exceptionally busy network, this - * may cause excessive retransmissions when too many PUBACKS arrive after the - * PUBLISH packet identifier is changed. However, the exponential backoff - * retransmission strategy should mitigate this problem. - * - Log messages will be printed using the new packet identifier; the old packet - * identifier is not saved. - * - * Example - * - * Consider a situation where - * - @ref IOT_MQTT_RETRY_MS_CEILING is 60000 - * - #IotMqttPublishInfo_t.retryMs is 2000 - * - #IotMqttPublishInfo_t.retryLimit is 20 - * - * A PUBLISH message will be retransmitted at the following times after the initial - * transmission if no PUBACK is received: - * - 2000 ms (2000 ms after previous transmission) - * - 6000 ms (4000 ms after previous transmission) - * - 14000 ms (8000 ms after previous transmission) - * - 30000 ms (16000 ms after previous transmission) - * - 62000 ms (32000 ms after previous transmission) - * - 122000 ms, 182000 ms, 242000 ms... (every 60000 ms until 20 transmissions have been sent) - * - * After the 20th retransmission, the MQTT library will wait - * @ref IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. - */ -typedef struct IotMqttPublishInfo -{ - IotMqttQos_t qos; /**< @brief QoS of message. Must be 0 or 1. */ - bool retain; /**< @brief MQTT message retain flag. */ - - const char * pTopicName; /**< @brief Topic name of PUBLISH. */ - uint16_t topicNameLength; /**< @brief Length of #IotMqttPublishInfo_t.pTopicName. */ - - const void * pPayload; /**< @brief Payload of PUBLISH. */ - size_t payloadLength; /**< @brief Length of #IotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ - - uint64_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ - uint32_t retryLimit; /**< @brief How many times to attempt retransmission. */ -} IotMqttPublishInfo_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Parameter to an MQTT callback function. - * - * @paramfor MQTT callback functions - * - * The MQTT library passes this struct to registered callback whenever an - * operation completes or a message is received on a topic filter. - * - * The members of this struct are different based on the callback trigger. If the - * callback function was triggered for completed operation, the `operation` - * member is valid. Otherwise, if the callback was triggered because of a - * server-to-client PUBLISH, the `message` member is valid. - * - * For an incoming PUBLISH, the `message.pTopicFilter` parameter provides the - * subscription topic filter that matched the topic name in the PUBLISH. Because - * topic filters may use MQTT wildcards, the topic filter may be different from the - * topic name. This pointer must be treated as read-only; the topic filter must not - * be modified. Additionally, the topic filter may go out of scope as soon as the - * callback function returns, so it must be copied if it is needed at a later time. - * - * @attention Any pointers in this callback parameter may be freed as soon as - * the [callback function](@ref IotMqttCallbackInfo_t.function) returns. - * Therefore, data must be copied if it is needed after the callback function - * returns. - * @attention The MQTT library may set strings that are not NULL-terminated. - * - * @see #IotMqttCallbackInfo_t for the signature of a callback function. - */ -typedef struct IotMqttCallbackParam -{ - /** - * @brief The MQTT connection associated with this completed operation or - * incoming PUBLISH. - * - * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback. - * However, blocking function calls (including @ref mqtt_function_wait) are - * not recommended (though still safe). - */ - IotMqttConnection_t mqttConnection; - - union - { - /* Valid for completed operations. */ - struct - { - IotMqttOperationType_t type; /**< @brief Type of operation that completed. */ - IotMqttReference_t reference; /**< @brief Reference to the operation that completed. */ - IotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ - } operation; - - /* Valid for incoming PUBLISH messages. */ - struct - { - const char * pTopicFilter; /**< @brief Topic filter that matched the message. */ - uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ - IotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ - } message; - }; -} IotMqttCallbackParam_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a user-provided MQTT callback function. - * - * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, - * and @ref mqtt_function_publish. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. - * - * Provides a function to be invoked when an operation completes or when a - * server-to-client PUBLISH is received. - * - * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} - * - * Below is an example for receiving an asynchronous notification on operation - * completion. See @ref mqtt_function_subscribe for an example of using this struct - * with for incoming PUBLISH messages. - * - * @code{c} - * // Operation completion callback. - * void operationComplete( void * pArgument, IotMqttCallbackParam_t * pOperation ); - * - * // Callback information. - * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - * callbackInfo.function = operationComplete; - * - * // Operation to wait for. - * IotMqttError_t result = IotMqtt_Publish( mqttConnection, - * &publishInfo, - * 0, - * &callbackInfo, - * &reference ); - * - * // Publish should have returned IOT_MQTT_STATUS_PENDING. Once a response - * // is received, operationComplete is executed with the actual status passed - * // in pOperation. - * @endcode - */ -typedef struct IotMqttCallbackInfo -{ - void * param1; /**< @brief The first parameter to pass to the callback function. */ - - /** - * @brief User-provided callback function signature. - * - * @param[in] void * #IotMqttCallbackInfo_t.param1 - * @param[in] IotMqttCallbackParam_t * Details on the outcome of the MQTT operation - * or an incoming MQTT PUBLISH. - * - * @see #IotMqttCallbackParam_t for more information on the second parameter. - */ - void ( * function )( void *, - IotMqttCallbackParam_t * ); -} IotMqttCallbackInfo_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Information on an MQTT subscription. - * - * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe - * - * An array of these is passed to @ref mqtt_function_subscribe and @ref - * mqtt_function_unsubscribe. However, #IotMqttSubscription_t.callback and - * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribe. - * - * @initializer{IotMqttSubscription_t,IOT_MQTT_SUBSCRIPTION_INITIALIZER} - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - * @see #IotMqttCallbackInfo_t for details on setting a callback function. - */ -typedef struct IotMqttSubscription -{ - /** - * @brief QoS of messages delivered on subscription. - * - * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe. - */ - IotMqttQos_t qos; - - const char * pTopicFilter; /**< @brief Topic filter of subscription. */ - uint16_t topicFilterLength; /**< @brief Length of #IotMqttSubscription_t.pTopicFilter. */ - - /** - * @brief Callback to invoke when a message is received. - * - * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. - */ - IotMqttCallbackInfo_t callback; -} IotMqttSubscription_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a new MQTT connection. - * - * @paramfor @ref mqtt_function_connect - * - * Passed as an argument to @ref mqtt_function_connect. Most members of this struct - * correspond to the content of an [MQTT CONNECT packet.] - * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) - * - * @initializer{IotMqttConnectInfo_t,IOT_MQTT_CONNECT_INFO_INITIALIZER} - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - */ -typedef struct IotMqttConnectInfo -{ - /** - * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. - * - * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] - * (https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html) - * When this member is `true`, the MQTT library will accommodate these - * differences. This setting should be `false` when communicating with a - * fully-compliant MQTT broker. - * - * @attention This setting MUST be `true` when using the AWS IoT MQTT - * server; it MUST be `false` otherwise. - * @note Currently, @ref IOT_MQTT_CONNECT_INFO_INITIALIZER sets this - * this member to `true`. - */ - bool awsIotMqttMode; - - /** - * @brief Whether this connection is a clean session. - * - * MQTT servers can maintain and topic filter subscriptions and unacknowledged - * PUBLISH messages. These form part of an MQTT session, which is identified by - * the [client identifier](@ref IotMqttConnectInfo_t.pClientIdentifier). - * - * Setting this value to `true` establishes a clean session, which causes - * the MQTT server to discard any previous session data for a client identifier. - * When the client disconnects, the server discards all session data. If this - * value is `true`, #IotMqttConnectInfo_t.pPreviousSubscriptions and - * #IotMqttConnectInfo_t.previousSubscriptionCount are ignored. - * - * Setting this value to `false` does one of the following: - * - If no previous session exists, the MQTT server will create a new - * persistent session. The server may maintain subscriptions and - * unacknowledged PUBLISH messages after a client disconnects, to be restored - * once the same client identifier reconnects. - * - If a previous session exists, the MQTT server restores all of the session's - * subscriptions for the client identifier and may immediately transmit any - * unacknowledged PUBLISH packets to the client. - * - * When a client with a persistent session disconnects, the MQTT server - * continues to maintain all subscriptions and unacknowledged PUBLISH messages. - * The client must also remember the session subscriptions to restore them - * upon reconnecting. #IotMqttConnectInfo_t.pPreviousSubscriptions and - * #IotMqttConnectInfo_t.previousSubscriptionCount are used to restore a - * previous session's subscriptions client-side. - */ - bool cleanSession; - - /** - * @brief An array of MQTT subscriptions present in a previous session, if any. - * - * Pointer to the start of an array of subscriptions present a previous session, - * if any. These subscriptions will be immediately restored upon reconnecting. - * - * This member is ignored if it is `NULL` or #IotMqttConnectInfo_t.cleanSession - * is `true`. If this member is not `NULL`, #IotMqttConnectInfo_t.previousSubscriptionCount - * must be nonzero. - */ - const IotMqttSubscription_t * pPreviousSubscriptions; - - /** - * @brief The number of MQTT subscriptions present in a previous session, if any. - * - * Number of subscriptions contained in the array - * #IotMqttConnectInfo_t.pPreviousSubscriptions. - * - * This value is ignored if #IotMqttConnectInfo_t.pPreviousSubscriptions - * is `NULL` or #IotMqttConnectInfo_t.cleanSession is `true`. If - * #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value - * must be nonzero. - */ - size_t previousSubscriptionCount; - - /** - * @brief A message to publish if the new MQTT connection is unexpectedly closed. - * - * A Last Will and Testament (LWT) message may be published if this connection is - * closed without sending an MQTT DISCONNECT packet. This pointer should be set to - * an #IotMqttPublishInfo_t representing any LWT message to publish. If an LWT - * is not needed, this member must be set to `NULL`. - * - * Unlike other PUBLISH messages, an LWT message is limited to 65535 bytes in - * length. Additionally, [pWillInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) - * and [pWillInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) will - * be ignored. - */ - const IotMqttPublishInfo_t * pWillInfo; - - uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ - - const char * pClientIdentifier; /**< @brief MQTT client identifier. */ - uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - - /* These credentials are not used by AWS IoT and may be ignored if - * awsIotMqttMode is true. */ - const char * pUserName; /**< @brief Username for MQTT connection. */ - uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ - const char * pPassword; /**< @brief Password for MQTT connection. */ - uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ -} IotMqttConnectInfo_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Provides the network functions for sending data and closing connections. - * - * @paramfor @ref mqtt_function_connect - * - * The MQTT library needs to be able to send and receive data over a network. - * This struct provides the functions (as function pointers) for sending data - * and closing the network connection. In addition to providing this struct to - * @ref mqtt_function_connect, the function @ref mqtt_function_receivecallback - * should be called to process data received from the network. - * - * @initializer{IotMqttNetIf_t,IOT_MQTT_NETIF_INITIALIZER} - */ -typedef struct IotMqttNetIf -{ - void * pSendContext; /**< Passed as the first argument to #IotMqttNetIf_t.send. */ - void * pDisconnectContext; /**< Passed as the first argument to #IotMqttNetIf_t.disconnect. */ - - /** - * @brief Function that sends data on the network. - * - * @param[in] void * #IotMqttNetIf_t.pSendContext - * @param[in] const void * Pointer to the data to send. - * @param[in] size_t Size of the data to send. - * - * @return Number of bytes successfully sent, 0 on failure. - */ - size_t ( * send )( void *, - const uint8_t *, - size_t ); - - /** - * @brief Function that closes the network connection. - * - * If this function is not provided, the network connection will not be closed - * by the MQTT library. - * - * @param[in] void * #IotMqttNetIf_t.pDisconnectContext - * - * @note Optional; set to `NULL` to ignore. The MQTT spec states that connections - * must be closed in certain conditions; if this function is not provided, the - * MQTT library is noncompliant. - */ - IotNetworkError_t ( * disconnect )( void * ); - - /* - * In addition to providing the network send and disconnect functions, this - * struct also allows the MQTT serialization and deserialization functions - * to be overridden for an MQTT connection. The compile-time setting - * IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 1 to enable this - * functionality. See the IotMqttNetIf_t.serialize and - * IotMqttNetIf_t.deserialize members for a list of functions that can be - * overridden. In addition, the functions for freeing packets and determining - * the packet type can also be overridden. If - * IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is 1, the serializer initialization - * and cleanup functions may be extended. See documentation of - * IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. - * - * If any function pointers that are NULL (the default value set by - * IOT_MQTT_NETIF_INITIALIZER), then the default implementation of that - * function will be used. - */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - - struct - { - /** - * @brief CONNECT packet serializer function. - * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. - * @param[out] uint8_t** Where the CONNECT packet is written. - * @param[out] size_t* Size of the CONNECT packet. - * - * Default implementation: #_IotMqtt_SerializeConnect - */ - IotMqttError_t ( * connect )( const IotMqttConnectInfo_t * /* pConnectInfo */, - uint8_t ** /* pConnectPacket */, - size_t * /* pPacketSize */ ); - - /** - * @brief PUBLISH packet serializer function. - * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. - * @param[out] uint8_t** Where the PUBLISH packet is written. - * @param[out] size_t* Size of the PUBLISH packet. - * @param[out] uint16_t* The packet identifier generated for this PUBLISH. - * - * Default implementation: #_IotMqtt_SerializePublish - */ - IotMqttError_t ( * publish )( const IotMqttPublishInfo_t * /* pPublishInfo */, - uint8_t ** /* pPublishPacket */, - size_t * /* pPacketSize */, - uint16_t * /* pPacketIdentifier */ ); - - /** - * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. - * @param[in] bool Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. - * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. - * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). - * - * Default implementation: #_IotMqtt_PublishSetDup - */ - void ( * publishSetDup )( bool /* awsIotMqttMode */, - uint8_t * /* pPublishPacket */, - uint16_t * /* pNewPacketIdentifier */ ); - - /** - * @brief PUBACK packet serializer function. - * @param[in] uint16_t The packet identifier to place in PUBACK. - * @param[out] uint8_t** Where the PUBACK packet is written. - * @param[out] size_t* Size of the PUBACK packet. - * - * Default implementation: #_IotMqtt_SerializePuback - */ - IotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, - uint8_t ** /* pPubackPacket */, - size_t * /* pPacketSize */ ); - - /** - * @brief SUBSCRIBE packet serializer function. - * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. - * @param[in] size_t Number of elements in the subscription array. - * @param[out] uint8_t** Where the SUBSCRIBE packet is written. - * @param[out] size_t* Size of the SUBSCRIBE packet. - * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. - * - * Default implementation: #_IotMqtt_SerializeSubscribe - */ - IotMqttError_t ( * subscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, - size_t /* subscriptionCount */, - uint8_t ** /* pSubscribePacket */, - size_t * /* pPacketSize */, - uint16_t * /* pPacketIdentifier */ ); - - /** - * @brief UNSUBSCRIBE packet serializer function. - * @param[in] IotMqttSubscription_t* User-provided array of subscriptions to remove. - * @param[in] size_t Number of elements in the subscription array. - * @param[out] uint8_t** Where the UNSUBSCRIBE packet is written. - * @param[out] size_t* Size of the UNSUBSCRIBE packet. - * @param[out] uint16_t* The packet identifier generated for this UNSUBSCRIBE. - * - * Default implementation: #_IotMqtt_SerializeUnsubscribe - */ - IotMqttError_t ( * unsubscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, - size_t /* subscriptionCount */, - uint8_t ** /* pUnsubscribePacket */, - size_t * /* pPacketSize */, - uint16_t * /* pPacketIdentifier */ ); - - /** - * @brief PINGREQ packet serializer function. - * @param[out] uint8_t** Where the PINGREQ packet is written. - * @param[out] size_t* Size of the PINGREQ packet. - * - * Default implementation: #_IotMqtt_SerializePingreq - */ - IotMqttError_t ( * pingreq )( uint8_t ** /* pPingreqPacket */, - size_t * /* pPacketSize */ ); - - /** - * @brief DISCONNECT packet serializer function. - * @param[out] uint8_t** Where the DISCONNECT packet is written. - * @param[out] size_t* Size of the DISCONNECT packet. - * - * Default implementation: #_IotMqtt_SerializeDisconnect - */ - IotMqttError_t ( * disconnect )( uint8_t ** /* pDisconnectPacket */, - size_t * /* pPacketSize */ ); - } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ - - struct - { - /** - * @brief CONNACK packet deserializer function. - * @param[in] uint8_t* Pointer to the start of a CONNACK packet. - * @param[in] size_t Length of the data stream. - * @param[out] size_t* The number of bytes in the data stream processed - * by this function. - * - * Default implementation: #_IotMqtt_DeserializeConnack - */ - IotMqttError_t ( * connack )( const uint8_t * /* pConnackStart */, - size_t /* dataLength */, - size_t * /* pBytesProcessed */ ); - - /** - * @brief PUBLISH packet deserializer function. - * @param[in] uint8_t* Pointer to the start of a PUBLISH packet. - * @param[in] size_t Length of the data stream. - * @param[out] IotMqttPublishInfo_t* Where the deserialized PUBLISH will be written. - * @param[out] uint16_t* The packet identifier in the PUBLISH. - * @param[out] size_t* The number of bytes in the data stream processed - * by this function. - * - * Default implementation: #_IotMqtt_DeserializePublish - */ - IotMqttError_t ( * publish )( const uint8_t * /* pPublishStart */, - size_t /* dataLength */, - IotMqttPublishInfo_t * /* pOutput */, - uint16_t * /* pPacketIdentifier */, - size_t * /* pBytesProcessed */ ); - - /** - * @brief PUBACK packet deserializer function. - * @param[in] uint8_t* Pointer to the start of a PUBACK packet. - * @param[in] size_t Length of the data stream. - * @param[out] uint16_t* The packet identifier in the PUBACK. - * @param[out] size_t* The number of bytes in the data stream processed - * by this function. - * - * Default implementation: #_IotMqtt_DeserializePuback - */ - IotMqttError_t ( * puback )( const uint8_t * /* pPubackStart */, - size_t /* dataLength */, - uint16_t * /* pPacketIdentifier */, - size_t * /* pBytesProcessed */ ); - - /** - * @brief SUBACK packet deserializer function. - * @param[in] IotMqttConnection_t The MQTT connection associated with - * the subscription. Rejected topic filters should be removed from this - * connection. - * @param[in] uint8_t* Pointer to the start of a SUBACK packet. - * @param[in] size_t Length of the data stream. - * @param[out] uint16_t* The packet identifier in the SUBACK. - * @param[out] size_t* The number of bytes in the data stream processed - * by this function. - * - * Default implementation: #_IotMqtt_DeserializeSuback - */ - IotMqttError_t ( * suback )( IotMqttConnection_t /* mqttConnection */, - const uint8_t * /* pSubackStart */, - size_t /* dataLength */, - uint16_t * /* pPacketIdentifier */, - size_t * /* pBytesProcessed */ ); - - /** - * @brief UNSUBACK packet deserializer function. - * @param[in] uint8_t* Pointer to the start of a UNSUBACK packet. - * @param[in] size_t Length of the data stream. - * @param[out] uint16_t* The packet identifier in the UNSUBACK. - * @param[out] size_t* The number of bytes in the data stream processed - * by this function. - * - * Default implementation: #_IotMqtt_DeserializeUnsuback - */ - IotMqttError_t ( * unsuback )( const uint8_t * /* pUnsubackStart */, - size_t /* dataLength */, - uint16_t * /* pPacketIdentifier */, - size_t * /* pBytesProcessed */ ); - - /** - * @brief PINGRESP packet deserializer function. - * @param[in] uint8_t* Pointer to the start of a PINGRESP packet. - * @param[in] size_t Length of the data stream. - * @param[out] size_t* The number of bytes in the data stream processed - * by this function. - * - * Default implementation: #_IotMqtt_DeserializePingresp - */ - IotMqttError_t ( * pingresp )( const uint8_t * /* pPingrespStart */, - size_t /* dataLength */, - size_t * /* pBytesProcessed */ ); - } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ - - /** - * @brief Get the MQTT packet type from a stream of bytes. - * - * @param[in] uint8_t* pPacket Pointer to the beginning of the byte stream. - * @param[in] size_t Size of the byte stream. - * - * Default implementation: #_IotMqtt_GetPacketType - */ - uint8_t ( * getPacketType )( const uint8_t * /* pPacket */, - size_t /* packetSize */ ); - - /** - * @brief Free a packet generated by the serializer. - * - * This function pointer must be set if any other serializer override is set. - * @param[in] uint8_t* The packet to free. - * - * Default implementation: #_IotMqtt_FreePacket - */ - void ( * freePacket )( uint8_t * /* pPacket */ ); - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -} IotMqttNetIf_t; - -/*------------------------- MQTT defined constants --------------------------*/ - -/** - * @constantspage{mqtt,MQTT library} - * - * @section mqtt_constants_initializers MQTT Initializers - * @brief Provides default values for the data types of the MQTT library. - * - * @snippet this define_mqtt_initializers - * - * All user-facing data types of the MQTT library should be initialized using - * one of the following. - * - * @warning Failing to initialize an MQTT data type with the appropriate initializer - * may result in undefined behavior! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - * - * Example - * @code{c} - * IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - * IotMqttConnection_t connection = IOT_MQTT_CONNECTION_INITIALIZER; - * IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; - * @endcode - * - * @section mqtt_constants_flags MQTT Function Flags - * @brief Flags that modify the behavior of MQTT library functions. - * - #IOT_MQTT_FLAG_WAITABLE
- * @copybrief IOT_MQTT_FLAG_WAITABLE - * - * Flags should be bitwise-ORed with each other to change the behavior of - * @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, or - * @ref mqtt_function_publish. - * - * @note The values of the flags may change at any time in future versions, but - * their names will remain the same. Additionally, flags will be bitwise-exclusive - * of each other. - */ - -/* @[define_mqtt_initializers] */ -/** @brief Initializer for #IotMqttNetIf_t. */ -#define IOT_MQTT_NETIF_INITIALIZER { 0 } -/** @brief Initializer for #IotMqttConnectInfo_t. */ -#define IOT_MQTT_CONNECT_INFO_INITIALIZER { .awsIotMqttMode = true, \ - .cleanSession = true } -/** @brief Initializer for #IotMqttPublishInfo_t. */ -#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { 0 } -/** @brief Initializer for #IotMqttSubscription_t. */ -#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { 0 } -/** @brief Initializer for #IotMqttCallbackInfo_t. */ -#define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } -/** @brief Initializer for #IotMqttConnection_t. */ -#define IOT_MQTT_CONNECTION_INITIALIZER NULL -/** @brief Initializer for #IotMqttReference_t. */ -#define IOT_MQTT_REFERENCE_INITIALIZER NULL -/* @[define_mqtt_initializers] */ - -/** - * @brief Allows the use of @ref mqtt_function_wait for blocking until completion. - * - * This flag is always valid for @ref mqtt_function_subscribe and - * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, - * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. - * - * An #IotMqttReference_t MUST be provided if this flag is set. Additionally, an - * #IotMqttCallbackInfo_t MUST NOT be provided. - * - * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up - * resources. - */ -#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" /*------------------------- MQTT library functions --------------------------*/ @@ -1034,15 +60,6 @@ typedef struct IotMqttNetIf * @functionpage{IotMqtt_Init,mqtt,init} * @functionpage{IotMqtt_Cleanup,mqtt,cleanup} * @functionpage{IotMqtt_ReceiveCallback,mqtt,receivecallback} - * - * @anchor mqtt_function_receivecallback_nopartial - * Sequence Diagram: Processing a network buffer without partial packets - * @image html mqtt_function_receivecallback_nopartial.png width=80% - * - * @anchor mqtt_function_receivecallback_partial - * Sequence Diagram: Processing a network buffer with partial packets - * @image html mqtt_function_receivecallback_partial.png width=80% - * * @functionpage{IotMqtt_Connect,mqtt,connect} * @functionpage{IotMqtt_Disconnect,mqtt,disconnect} * @functionpage{IotMqtt_Subscribe,mqtt,subscribe} @@ -1097,76 +114,17 @@ void IotMqtt_Cleanup( void ); /** * @brief Network receive callback for the MQTT library. * - * This function should be called by the system whenever a stream of MQTT data - * is received from the network. It processes the data stream and decodes any - * MQTT packets it finds. The MQTT library uses #IotMqttNetIf_t for sending - * data and closing network connections. - * - * @attention Remember that this function's input `pReceivedData` is a data - * stream! A data stream may contain any number of different MQTT packets, - * including incomplete MQTT packets. - * - * The input parameter `pReceivedData` must be allocated by the system. The system - * should read data from the network and place the data in `pReceivedData`. Then, - * the system should call this function. - * - * An important concept associated with this function is *buffer ownership*. - * Normally, once `pReceivedData` is passed to this function, it is considered - * property of the MQTT library. The system must keep `pReceivedData` valid - * and in-scope even after this function returns, and must not make any changes - * to `pReceivedData` until the MQTT library calls `freeReceivedData` (which - * transfers ownership of `pReceivedData` back to the system). - * [This sequence diagram](@ref mqtt_function_receivecallback_nopartial) illustrates - * the flow where `pReceivedData` is completely processed. - * - * If `pReceivedData` ends with a partial MQTT packet, the MQTT library will not - * call `freeReceivedData`. In this case, this function will return the actual - * number of bytes processed (from this point onwards called `bytesProcessed`). - * The buffer `pReceivedData` will be returned to the system. The system should - * wait for the rest of the MQTT packet to be received and place the remainder - * of the MQTT packet immediately after the last processed byte in `pReceivedData`. - * The bytes in `pReceivedData` in the range of `[offset, offset+bytesProcessed]` - * should be considered "already processed" and should be discarded. - * [This sequence diagram](@ref mqtt_function_receivecallback_partial) illustrates - * the case where `pReceivedData` contains a partial packet. - * - * If `freeReceivedData` is `NULL`, then ownership of `pReceivedData` will always - * revert to the system as soon as this function returns. In this scenario, the - * MQTT library will copy any data it requires out of `pReceivedData`. Therefore, - * passing `NULL` for `freeReceivedData` may increase memory usage. - * - * @param[in] pMqttConnection A pointer to the MQTT connection handle for which - * the packet was received. - * @param[in] pConnection The network connection associated with the MQTT connection, - * passed by the network stack. - * @param[in] pReceivedData Pointer to the beginning of the data stream. This buffer - * must remain valid and in-scope until the MQTT library calls `freeReceivedData`. - * @param[in] dataLength The length of `pReceivedData` in bytes. - * @param[in] offset The offset (in bytes) into `pReceivedData` where the MQTT library - * begins processing. All bytes from `pReceivedData+offset` to `pReceivedData+dataLength` - * will be processed. Pass `0` to start from the beginning of `pReceivedData`. - * @param[in] freeReceivedData The function that the MQTT library calls when it is - * finished with `pReceivedData`. This function will only be called when all data is - * successfully processed, i.e. when this function returns a value of `dataLength-offset`. - * Pass `NULL` to ignore. + * This function should be called by the system whenever data is available for + * the MQTT library. * - * @return - * - `-1` if a protocol violation is encountered. If this function returns `-1`, then - * the network connection is closed (unless a [disconnect function] - * (@ref IotMqttNetIf_t.disconnect) was not provided to @ref mqtt_function_connect). - * `freeReceivedData` is not called if this function returns `-1`. - * - Number of bytes processed otherwise. If the return value is less than `dataLength` - * (but not `-1`), the data stream probably contained a partial MQTT packet. The function - * `freeReceivedData` will only be called if all bytes in the range `pReceivedData+offset` - * to `pReceivedData+dataLength` were successfully processed. + * @param[in] pNetworkConnection The network connection associated with the MQTT + * connection, passed by the network stack. + * @param[in] pReceiveContext A pointer to the MQTT connection handle for which + * the packet was received. */ /* @[declare_mqtt_receivecallback] */ -int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, - void * pConnection, - const uint8_t * pReceivedData, - size_t dataLength, - size_t offset, - void ( * freeReceivedData )( void * ) ); +void IotMqtt_ReceiveCallback( void * pNetworkConnection, + void * pReceiveContext ); /* @[declare_mqtt_receivecallback] */ /** @@ -1197,7 +155,7 @@ int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, * different network stacks. The network abstraction layer is established * per-connection, allowing every #IotMqttConnection_t to use a different network * stack. The parameter `pNetworkInterface` sets up the network abstraction layer - * for an MQTT connection; see the documentation on #IotMqttNetIf_t for details + * for an MQTT connection; see the documentation on #IotMqttNetworkInfo_t for details * on its members. * * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet. @@ -1219,13 +177,13 @@ int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, * threads making calls to @ref mqtt_function_connect will be serialized to send * their CONNECT packets one-by-one. * - * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle - * if this function succeeds. - * @param[in] pNetworkInterface The network `send` and `disconnect` functions that - * this MQTT connection will use. + * @param[in] pNetworkInfo Information on the transport-layer network connection + * to use with the MQTT connection. * @param[in] pConnectInfo MQTT connection setup parameters. * @param[in] timeoutMs If the MQTT server does not accept the connection within * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle + * if this function succeeds. * * @return One of the following: * - #IOT_MQTT_SUCCESS @@ -1244,15 +202,20 @@ int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, * * // Parameters to MQTT connect. * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - * IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; * + * // Example network abstraction types. + * IotNetworkServerInfo_t serverInfo = { ... }; + * IotNetworkCredentialInfo_t credentialInfo = { ... }; + * IotNetworkInterface_t networkInterface = { ... }; + * * // Example using a generic network implementation. - * networkInterface.pSendContext = pNetworkConnection; - * networkInterface.pDisconnectContext = pNetworkConnection; - * networkInterface.send = IotNetwork_Send; - * networkInterface.disconnect = IotNetwork_CloseConnection; + * networkInfo.createNetworkConnection = true; + * networkInfo.pNetworkServerInfo = &serverInfo; + * networkInfo.pNetworkCredentialInfo = &credentialInfo; + * networkInfo.pNetworkInterface = &networkInterface; * * // Set the members of the connection info (password and username not used). * connectInfo.cleanSession = true; @@ -1272,25 +235,25 @@ int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, * * // Call CONNECT with a 5 second block time. Should return * // IOT_MQTT_SUCCESS when successful. - * IotMqttError_t result = IotMqtt_Connect( &mqttConnection, - * &networkInterface, + * IotMqttError_t result = IotMqtt_Connect( &networkInfo, * &connectInfo, - * 5000 ); + * 5000, + * &mqttConnection ); * * if( result == IOT_MQTT_SUCCESS ) * { * // Do something with the MQTT connection... * * // Clean up and close the MQTT connection once it's no longer needed. - * IotMqtt_Disconnect( mqttConnection, false ); + * IotMqtt_Disconnect( mqttConnection, 0 ); * } * @endcode */ /* @[declare_mqtt_connect] */ -IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, - const IotMqttNetIf_t * pNetworkInterface, +IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, const IotMqttConnectInfo_t * pConnectInfo, - uint64_t timeoutMs ); + uint64_t timeoutMs, + IotMqttConnection_t * pMqttConnection ); /* @[declare_mqtt_connect] */ /** @@ -1298,11 +261,11 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, * * This function closes an MQTT connection and should only be called once * the MQTT connection is no longer needed. Its exact behavior depends on the - * `cleanupOnly` parameter. + * `flags` parameter. * - * Normally, `cleanupOnly` should be `false`. This gracefully shuts down an MQTT - * connection by sending an MQTT DISCONNECT packet. Any [disconnect function] - * (@ref IotMqttNetIf_t.disconnect) provided [when the connection was established] + * Normally, `flags` should be `0`. This gracefully shuts down an MQTT + * connection by sending an MQTT DISCONNECT packet. Any [network close function] + * (@ref IotNetworkInterface_t::close) provided [when the connection was established] * (@ref mqtt_function_connect) will also be called. Note that because the MQTT server * will not acknowledge a DISCONNECT packet, the client has no way of knowing if * the server received the DISCONNECT packet. In the case where the DISCONNECT @@ -1311,23 +274,21 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, * MQTT server, the LWT message will be discarded and not published. * * Should the underlying network connection become unusable, this function should - * be called with `cleanupOnly` set to `true`. In this case, no DISCONNECT packet - * nor [disconnect function](@ref IotMqttNetIf_t.disconnect) will be called. - * This function will only free the resources used by the MQTT connection; it still - * must be called even if the network is offline to avoid leaking resources. + * be called with `flags` set to #IOT_MQTT_FLAG_CLEANUP_ONLY. In this case, no + * DISCONNECT packet will be sent, though the [network close function](@ref IotNetworkInterface_t::close) + * will still be called. This function will only free the resources used by the MQTT + * connection; it still must be called even if the network is offline to avoid leaking + * resources. * * Once this function is called, its parameter `mqttConnection` should no longer * be used. * * @param[in] mqttConnection The MQTT connection to close and clean up. - * @param[in] cleanupOnly Passing `true` will cause this function to only perform - * cleanup of the MQTT connection and not send a DISCONNECT packet. This parameter - * should be `true` if the network goes offline or is otherwise unusable. Otherwise, - * it should be `false`. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. */ /* @[declare_mqtt_disconnect] */ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, - bool cleanupOnly ); + uint32_t flags ); /* @[declare_mqtt_disconnect] */ /** diff --git a/lib/include/platform/iot_clock.h b/lib/include/platform/iot_clock.h index e580029f19..d41988db7d 100644 --- a/lib/include/platform/iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -44,6 +44,7 @@ * @functionspage{platform_clock,platform clock component,Clock} * - @functionname{platform_clock_function_gettimestring} * - @functionname{platform_clock_function_gettimems} + * - @functionname{platform_clock_function_sleepms} * - @functionname{platform_clock_function_timercreate} * - @functionname{platform_clock_function_timerdestroy} * - @functionname{platform_clock_function_timerarm} @@ -52,6 +53,7 @@ /** * @functionpage{IotClock_GetTimestring,platform_clock,gettimestring} * @functionpage{IotClock_GetTimeMs,platform_clock,gettimems} + * @functionpage{IotClock_SleepMs,platform_clock,sleepms} * @functionpage{IotClock_TimerCreate,platform_clock,timercreate} * @functionpage{IotClock_TimerDestroy,platform_clock,timerdestroy} * @functionpage{IotClock_TimerArm,platform_clock,timerarm} @@ -85,9 +87,9 @@ * @endcode */ /* @[declare_platform_clock_gettimestring] */ -bool IotClock_GetTimestring( char * const pBuffer, +bool IotClock_GetTimestring( char * pBuffer, size_t bufferSize, - size_t * const pTimestringLength ); + size_t * pTimestringLength ); /* @[declare_platform_clock_gettimestring] */ /** @@ -109,6 +111,17 @@ bool IotClock_GetTimestring( char * const pBuffer, uint64_t IotClock_GetTimeMs( void ); /* @[declare_platform_clock_gettimems] */ +/** + * @brief Delay for the given number of milliseconds. + * + * This function suspends its calling thread for at least `sleepTimeMs` milliseconds. + * + * @param[in] sleepTimeMs Sleep time (in milliseconds). + */ +/* @[declare_platform_clock_sleepms] */ +void IotClock_SleepMs( uint32_t sleepTimeMs ); +/* @[declare_platform_clock_sleepms] */ + /** * @brief Create a new timer. * @@ -125,7 +138,7 @@ uint64_t IotClock_GetTimeMs( void ); * @see @ref platform_clock_function_timerdestroy, @ref platform_clock_function_timerarm */ /* @[declare_platform_clock_timercreate] */ -bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, +bool IotClock_TimerCreate( IotTimer_t * pNewTimer, IotThreadRoutine_t expirationRoutine, void * pArgument ); /* @[declare_platform_clock_timercreate] */ @@ -144,7 +157,7 @@ bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, * @see @ref platform_clock_function_timercreate, @ref platform_clock_function_timerarm */ /* @[declare_platform_clock_timerdestroy] */ -void IotClock_TimerDestroy( IotTimer_t * const pTimer ); +void IotClock_TimerDestroy( IotTimer_t * pTimer ); /* @[declare_platform_clock_timerdestroy] */ /** @@ -193,7 +206,7 @@ void IotClock_TimerDestroy( IotTimer_t * const pTimer ); * @endcode */ /* @[declare_platform_clock_timerarm] */ -bool IotClock_TimerArm( IotTimer_t * const pTimer, +bool IotClock_TimerArm( IotTimer_t * pTimer, uint64_t relativeTimeoutMs, uint64_t periodMs ); /* @[declare_platform_clock_timerarm] */ diff --git a/lib/include/iot_metrics.h b/lib/include/platform/iot_metrics.h similarity index 92% rename from lib/include/iot_metrics.h rename to lib/include/platform/iot_metrics.h index 02bb1af6b3..16dc76dd93 100644 --- a/lib/include/iot_metrics.h +++ b/lib/include/platform/iot_metrics.h @@ -89,15 +89,19 @@ #endif /* if IOT_STATIC_MEMORY_ONLY */ +#ifndef IotMetricsConnectionId_t + #define IotMetricsConnectionId_t uint32_t +#endif + /** * Data handle of TCP connection */ typedef struct IotMetricsTcpConnection { IotLink_t link; - uint32_t id; - uint32_t remoteIP; /* This is limited to IPv4. */ - uint16_t remotePort; + IotMetricsConnectionId_t id; + char * pRemoteIP; /* This is limited to IPv4. */ + uint16_t remotePort; /* In host order. */ } IotMetricsTcpConnection_t; /** @@ -123,7 +127,7 @@ void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ); /** * Remove one TCP connection metric. */ -void IotMetrics_RemoveTcpConnection( uint32_t tcpConnectionId ); +void IotMetrics_RemoveTcpConnection( IotMetricsConnectionId_t tcpConnectionId ); /** * Use a callback to process a list of TCP connections diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index c629a93f02..f3faf73c63 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -81,55 +81,13 @@ typedef enum IotNetworkError * A function with this signature may be set with @ref platform_network_function_setreceivecallback * to be invoked when data is available on the network. * - * An important concept associated with this function is buffer ownership. - * Normally, once `pReceivedData` is passed to this function, it is considered - * property of the library that set the receive callback. The network stack - * must keep `pReceivedData` valid and in-scope even after this function returns, - * and must not make any changes to `pReceivedData` until `freeReceivedData` is called - * (which transfers ownership of `pReceivedData` back to the network stack). - * - * If `freeReceivedData` is `NULL`, then ownership of `pReceivedData` will always - * revert to the network stack as soon as the library's callback function returns. - * In this scenario, the library must copy any data it requires out of `pReceivedData`. - * Therefore, passing `NULL` for `freeReceivedData` may increase memory usage. - * - * @param[in] pContext The third argument passed to @ref platform_network_function_setreceivecallback. - * @param[in] pConnection The connection on which data was received, defined by + * @param[in] pConnection The connection on which data is available, defined by * the network stack. - * @param[in] pReceivedData Buffer holding data received from the network. This buffer - * must remain valid and in-scope until `freeReceivedData` is called if this function - * returns a value of `dataLength-offset`. - * @param[in] receivedDataLength The length of the buffer `pReceivedData`. - * @param[in] offset The offset (in bytes) into `pReceivedData` where the received data - * begins. The received data ranges from `pReceivedData+offset` to `pReceivedData+dataLength`. - * Pass `0` to start from the beginning of `pReceivedData`. - * @param[in] freeReceivedData The function to call when finished with `pReceivedData`. - * Its parameter must be `pReceivedData`. Pass `NULL` to ignore. - * - * @return - * The network stack must respond to this function's return value as follows: - * - This function returns `-1` to indicate a fatal, unrecoverable error. The network - * connection should no longer be used. The receive callback will not call `freeReceivedData` - * if it returns `-1`. - * - This function returns a positive value less than `dataLength-offset` to indicate - * that the received data was incomplete in some way. The network stack should keep - * the data in `pReceivedBuffer` and wait more data to arrive. The receive callback should - * be called again with this additional data. The receive callback will not call - * `freeReceivedData` in this case. - * - This function returns `dataLength-offset` to indicate all received data was - * processed. The buffer `pReceivedData` must remain valid and in-scope until - * `freeReceivedData` is called. - * - * @attention The function @ref platform_network_function_receive must not be - * called if a receive callback is active! + * @param[in] pContext The third argument passed to @ref platform_network_function_setreceivecallback. */ /* @[declare_platform_network_receivecallback] */ -typedef int32_t ( * IotNetworkReceiveCallback_t )( void * pContext, - void * pConnection, - const uint8_t * pReceivedData, - size_t receivedDataLength, - size_t offset, - void ( * freeReceivedData )( void * ) ); +typedef void ( * IotNetworkReceiveCallback_t )( void * pConnection, + void * pContext ); /* @[declare_platform_network_receivecallback] */ /** @@ -157,7 +115,7 @@ typedef struct IotNetworkInterface /* @[declare_platform_network_create] */ IotNetworkError_t ( * create )( void * pConnectionInfo, void * pCredentialInfo, - void * const pConnection ); + void * pConnection ); /* @[declare_platform_network_create] */ /** @@ -181,9 +139,6 @@ typedef struct IotNetworkInterface * * @return Any #IotNetworkError_t, as defined by the network stack. * - * @attention @ref platform_network_function_receive must not be called if - * a receive callback is set! - * * @see platform_network_function_receivecallback */ /* @[declare_platform_network_setreceivecallback] */ @@ -227,13 +182,10 @@ typedef struct IotNetworkInterface * * @return The number of bytes successfully received. This should be * `bytesRequested` when successful. Any other value may indicate an error. - * - * @attention This function must not be called if a - * [receive callback](@ref platform_network_function_receivecallback) is active! */ /* @[declare_platform_network_receive] */ size_t ( * receive )( void * pConnection, - uint8_t * const pBuffer, + uint8_t * pBuffer, size_t bytesRequested ); /* @[declare_platform_network_receive] */ diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index a578644d92..5d8c794abd 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -145,7 +145,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, * @endcode */ /* @[declare_platform_threads_mutexcreate] */ -bool IotMutex_Create( IotMutex_t * const pNewMutex, bool recursive ); +bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ); /* @[declare_platform_threads_mutexcreate] */ /** @@ -161,7 +161,7 @@ bool IotMutex_Create( IotMutex_t * const pNewMutex, bool recursive ); * @see @ref platform_threads_function_mutexcreate */ /* @[declare_platform_threads_mutexdestroy] */ -void IotMutex_Destroy( IotMutex_t * const pMutex ); +void IotMutex_Destroy( IotMutex_t * pMutex ); /* @[declare_platform_threads_mutexdestroy] */ /** @@ -176,7 +176,7 @@ void IotMutex_Destroy( IotMutex_t * const pMutex ); * @see @ref platform_threads_function_mutextrylock for a nonblocking lock. */ /* @[declare_platform_threads_mutexlock] */ -void IotMutex_Lock( IotMutex_t * const pMutex ); +void IotMutex_Lock( IotMutex_t * pMutex ); /* @[declare_platform_threads_mutexlock] */ /** @@ -193,7 +193,7 @@ void IotMutex_Lock( IotMutex_t * const pMutex ); * @see @ref platform_threads_function_mutexlock for a blocking lock. */ /* @[declare_platform_threads_mutextrylock] */ -bool IotMutex_TryLock( IotMutex_t * const pMutex ); +bool IotMutex_TryLock( IotMutex_t * pMutex ); /* @[declare_platform_threads_mutextrylock] */ /** @@ -208,7 +208,7 @@ bool IotMutex_TryLock( IotMutex_t * const pMutex ); * @note This function should not be called on a mutex that is already unlocked. */ /* @[declare_platform_threads_mutexunlock] */ -void IotMutex_Unlock( IotMutex_t * const pMutex ); +void IotMutex_Unlock( IotMutex_t * pMutex ); /* @[declare_platform_threads_mutexunlock] */ /** @@ -242,7 +242,7 @@ void IotMutex_Unlock( IotMutex_t * const pMutex ); * @endcode */ /* @[declare_platform_threads_semaphorecreate] */ -bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, +bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, uint32_t initialValue, uint32_t maxValue ); /* @[declare_platform_threads_semaphorecreate] */ @@ -260,7 +260,7 @@ bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, * @see @ref platform_threads_function_semaphorecreate */ /* @[declare_platform_threads_semaphoredestroy] */ -void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ); +void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ); /* @[declare_platform_threads_semaphoredestroy] */ /** @@ -274,7 +274,7 @@ void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ); * @return The current count of the semaphore. This function should not fail. */ /* @[declare_platform_threads_semaphoregetcount] */ -uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ); +uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ); /* @[declare_platform_threads_semaphoregetcount] */ /** @@ -291,7 +291,7 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ); * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. */ /* @[declare_platform_threads_semaphorewait] */ -void IotSemaphore_Wait( IotSemaphore_t * const pSemaphore ); +void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ); /* @[declare_platform_threads_semaphorewait] */ /** @@ -311,7 +311,7 @@ void IotSemaphore_Wait( IotSemaphore_t * const pSemaphore ); * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. */ /* @[declare_platform_threads_semaphoretrywait] */ -bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ); +bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ); /* @[declare_platform_threads_semaphoretrywait] */ /** @@ -333,7 +333,7 @@ bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ); * @ref platform_threads_function_semaphorewait for a blocking wait. */ /* @[declare_platform_threads_semaphoretimedwait] */ -bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, uint64_t timeoutMs ); /* @[declare_platform_threads_semaphoretimedwait] */ @@ -347,7 +347,7 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, * @param[in] pSemaphore The semaphore to unlock. */ /* @[declare_platform_threads_semaphorepost] */ -void IotSemaphore_Post( IotSemaphore_t * const pSemaphore ); +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ); /* @[declare_platform_threads_semaphorepost] */ #endif /* ifndef _IOT_THREADS_H_ */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 560cb76cd3..39cb80ef71 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -284,7 +284,7 @@ typedef struct _defenderMetrics /** * Create a report, memory is allocated inside the function. */ -AwsIotDefenderEventType_t AwsIotDefenderInternal_CreateReport(); +bool AwsIotDefenderInternal_CreateReport(); /** * Get the buffer pointer of report. @@ -311,31 +311,21 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames(); */ void AwsIotDefenderInternal_DeleteTopicsNames(); -/** - * Creat a network connection. - */ -bool AwsIotDefenderInternal_NetworkConnect(); - -/** - * Set the network connection to callback MQTT. - */ -void AwsIotDefenderInternal_SetMqttCallback(); - /** * Connect to AWS with MQTT. */ -bool AwsIotDefenderInternal_MqttConnect(); +IotMqttError_t AwsIotDefenderInternal_MqttConnect(); /** * Subscribe accept/reject defender topics. */ -bool AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, +IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, IotMqttCallbackInfo_t rejectCallback ); /** * Publish metrics data to defender topic. */ -bool AwsIotDefenderInternal_MqttPublish( uint8_t * pData, +IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, size_t dataLength ); /** @@ -343,16 +333,6 @@ bool AwsIotDefenderInternal_MqttPublish( uint8_t * pData, */ void AwsIotDefenderInternal_MqttDisconnect(); -/** - * Close network connection. - */ -void AwsIotDefenderInternal_NetworkClose(); - -/** - * Destory network connection. - */ -void AwsIotDefenderInternal_NetworkDestroy(); - /*----------------- Below this line are INTERNAL global variables --------------------*/ extern _defenderMetrics_t _AwsIotDefenderMetrics; diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 9c0776650a..d3b099ce42 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -36,9 +36,6 @@ /* Linear containers (lists and queues) include. */ #include "iot_linear_containers.h" -/* Platform layer types include. */ -#include "types/iot_platform_types.h" - /* MQTT include. */ #include "iot_mqtt.h" @@ -258,19 +255,45 @@ #define _MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ #define _MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ +/** + * @brief A value that represents an invalid remaining length. + * + * This value is greater than what is allowed by the MQTT specification. + */ +#define _MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + /*---------------------- MQTT internal data structures ----------------------*/ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declarations. + * @brief Represents an MQTT connection. */ -struct _mqttSubscription; -struct _mqttConnection; -struct _mqttOperation; -struct _mqttTimerEvent; -/** @endcond */ +typedef struct _mqttConnection +{ + bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ + bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ + void * pNetworkConnection; /**< @brief References the transport-layer network connection. */ + const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ + #endif + + bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ + IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ + int32_t references; /**< @brief Counts callbacks and operations using this connection. */ + IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ + IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ + + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ + + bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ + uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ + uint64_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ + IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ + uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ + size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ +} _mqttConnection_t; /** * @brief Represents a subscription stored in an MQTT connection. @@ -304,31 +327,6 @@ typedef struct _mqttSubscription char pTopicFilter[]; /**< @brief The subscription topic filter. */ } _mqttSubscription_t; -/** - * @brief Internal structure to hold data on an MQTT connection. - */ -typedef struct _mqttConnection -{ - bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ - IotMqttNetIf_t network; /**< @brief Network interface provided to @ref mqtt_function_connect. */ - - bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ - IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ - int32_t references; /**< @brief Counts callbacks and operations using this connection. */ - IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ - IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ - - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to #_mqttConnection_t.subscriptionList. */ - - bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ - uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ - uint64_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ - IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ - uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ - size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ -} _mqttConnection_t; - /** * @brief Internal structure representing a single MQTT operation, such as * CONNECT, SUBSCRIBE, PUBLISH, etc. @@ -338,12 +336,12 @@ typedef struct _mqttConnection typedef struct _mqttOperation { /* Pointers to neighboring queue elements. */ - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ - struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ + bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ + _mqttConnection_t * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ - IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ + IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ union { @@ -357,8 +355,9 @@ typedef struct _mqttOperation uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ /* Serialized packet and size. */ - uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ - size_t packetSize; /**< @brief Size of `pMqttPacket`. */ + uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ + uint8_t * pPacketIdentifierHigh; /**< @brief The location of the high byte of the packet identifier in the MQTT packet. */ + size_t packetSize; /**< @brief Size of `pMqttPacket`. */ /* How to notify of an operation's completion. */ union @@ -379,27 +378,42 @@ typedef struct _mqttOperation /* If incomingPublish is true, this struct is valid. */ struct { - IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ - struct _mqttOperation * pNextPublish; /**< @brief Pointer to the next PUBLISH in the data stream. */ - const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ - void ( * freeReceivedData )( void * ); /**< @brief Function called to free `pReceivedData`. */ + IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ + const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ }; }; } _mqttOperation_t; -/* Declaration of the MQTT task pool for internal files. */ -extern IotTaskPool_t _IotMqttTaskPool; - -/*-------------------- MQTT struct validation functions ---------------------*/ - /** - * @brief Check that an #IotMqttNetIf_t is valid. + * @brief Represents an MQTT packet received from the network. * - * @param[in] pNetworkInterface The #IotMqttNetIf_t to validate. - * - * @return `true` if `pNetworkInterface` is valid; `false` otherwise. + * This struct is used to hold parameters for the deserializers so that all + * deserializers have the same function signature. */ -bool _IotMqtt_ValidateNetIf( const IotMqttNetIf_t * pNetworkInterface ); +typedef struct _mqttPacket +{ + union + { + /** + * @brief (Input) MQTT connection associated with this packet. Only used + * when deserializing SUBACKs. + */ + _mqttConnection_t * pMqttConnection; + + /** + * @brief (Output) Operation representing an incoming PUBLISH. Only used + * when deserializing PUBLISHes. + */ + _mqttOperation_t * pIncomingPublish; + }; + + uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ + size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ + uint16_t packetIdentifier; /**< @brief (Output) MQTT packet identifier. */ + uint8_t type; /**< @brief (Input) A value identifying the packet type. */ +} _mqttPacket_t; + +/*-------------------- MQTT struct validation functions ---------------------*/ /** * @brief Check that an #IotMqttConnectInfo_t is valid. @@ -466,18 +480,31 @@ IotMqttError_t _IotMqtt_InitSerialize( void ); void _IotMqtt_CleanupSerialize( void ); /** - * @brief Get the MQTT packet type from a stream of bytes. + * @brief Get the MQTT packet type from a stream of bytes off the network. * - * @param[in] pPacket Pointer to the beginning of the byte stream. - * @param[in] packetSize Size of the byte stream. + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. * * @return One of the server-to-client MQTT packet types. * * @note This function is only used for incoming packets, and may not work * correctly for outgoing packets. */ -uint8_t _IotMqtt_GetPacketType( const uint8_t * pPacket, - size_t packetSize ); +uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * @return The remaining length; #_MQTT_REMAINING_LENGTH_INVALID on error. + */ +size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); /** * @brief Generate a CONNECT packet from the given parameters. @@ -497,18 +524,14 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI * * Converts the packet from a stream of bytes to an #IotMqttError_t. Also * prints out debug log messages about the packet. - * @param[in] pConnackStart Pointer to the start of a CONNACK packet. - * @param[in] dataLength Length of the data stream after `pConnackStart`. - * @param[out] pBytesProcessed The number of bytes in the data stream processed - * by this function. For CONNACK, this is always 4. + * + * @param[in,out] pConnack Pointer to an MQTT packet struct representing a CONNACK. * * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. */ -IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, - size_t dataLength, - size_t * pBytesProcessed ); +IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ); /** * @brief Generate a PUBLISH packet from the given parameters. @@ -517,20 +540,22 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, * @param[out] pPublishPacket Where the PUBLISH packet is written. * @param[out] pPacketSize Size of the packet written to `pPublishPacket`. * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier + * is written. * * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. */ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, uint8_t ** pPublishPacket, size_t * pPacketSize, - uint16_t * pPacketIdentifier ); + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ); /** * @brief Set the DUP bit in a QoS 1 PUBLISH packet. * - * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify. + * @param[in] pPacketIdentifierHigh The high byte of any packet identifier to modify. * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag, * a new packet identifier is generated and should be written here. This parameter * is only used when connected to an AWS IoT MQTT server. @@ -538,8 +563,8 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI * @note See #IotMqttPublishInfo_t for caveats with retransmission to the * AWS IoT MQTT server. */ -void _IotMqtt_PublishSetDup( bool awsIotMqttMode, - uint8_t * pPublishPacket, +void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, uint16_t * pNewPacketIdentifier ); /** @@ -549,21 +574,12 @@ void _IotMqtt_PublishSetDup( bool awsIotMqttMode, * extracts the packet identifier. Also prints out debug log messages about the * packet. * - * @param[in] pPublishStart Pointer to the start of a PUBLISH packet. - * @param[in] dataLength Length of the data stream after `pPublishStart`. - * @param[out] pOutput Where the deserialized PUBLISH will be written. - * @param[out] pPacketIdentifier The packet identifier in the PUBLISH. - * @param[out] pBytesProcessed The number of bytes in the data stream processed - * by this function. + * @param[in,out] pPublish Pointer to an MQTT packet struct representing a PUBLISH. * * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE * if the PUBLISH packet doesn't follow MQTT spec. */ -IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, - size_t dataLength, - IotMqttPublishInfo_t * pOutput, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ); +IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ); /** * @brief Generate a PUBACK packet for the given packet identifier. @@ -584,19 +600,12 @@ IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts * the packet identifier. Also prints out debug log messages about the packet. * - * @param[in] pPubackStart Pointer to the start of a PUBACK packet. - * @param[in] dataLength Length of the data stream after `pPubackStart`. - * @param[out] pPacketIdentifier The packet identifier in the PUBACK. - * @param[out] pBytesProcessed The number of bytes in the data stream processed - * by this function. For PUBACK, this is always 4. + * @param[in,out] pPuback Pointer to an MQTT packet struct representing a PUBACK. * * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE * if the PUBACK packet doesn't follow MQTT spec. */ -IotMqttError_t _IotMqtt_DeserializePuback( const uint8_t * pPubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ); +IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ); /** * @brief Generate a SUBSCRIBE packet from the given parameters. @@ -621,22 +630,12 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts * the packet identifier. Also prints out debug log messages about the packet. * - * @param[in] mqttConnection The MQTT connection associated with the subscription. - * Rejected topic filters are removed from this connection. - * @param[in] pSubackStart Pointer to the start of a SUBACK packet. - * @param[in] dataLength Length of the data stream after `pSubackStart`. - * @param[out] pPacketIdentifier The packet identifier in the SUBACK. - * @param[out] pBytesProcessed The number of bytes in the data stream processed by - * this function. + * @param[in,out] pSuback Pointer to an MQTT packet struct representing a SUBACK. * * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE * if the SUBACK packet doesn't follow MQTT spec. */ -IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, - const uint8_t * pSubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ); +IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ); /** * @brief Generate an UNSUBSCRIBE packet from the given parameters. @@ -661,19 +660,12 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts * the packet identifier. Also prints out debug log messages about the packet. * - * @param[in] pUnsubackStart Pointer to the start of an UNSUBACK packet. - * @param[in] dataLength Length of the data stream after `pUnsubackStart`. - * @param[out] pPacketIdentifier The packet identifier in the UNSUBACK. - * @param[out] pBytesProcessed The number of bytes in the data stream processed by - * this function. For UNSUBACK, this is always 4. + * @param[in,out] pUnsuback Pointer to an MQTT packet struct representing an UNSUBACK. * * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE * if the UNSUBACK packet doesn't follow MQTT spec. */ -IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ); +IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ); /** * @brief Generate a PINGREQ packet. @@ -691,17 +683,13 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, * * Converts the packet from a stream of bytes to an #IotMqttError_t. Also * prints out debug log messages about the packet. - * @param[in] pPingrespStart Pointer to the start of a PINGRESP packet. - * @param[in] dataLength Length of the data stream after `pPingrespStart`. - * @param[out] pBytesProcessed The number of bytes in the data stream processed by - * this function. For PINGRESP, this is always 2. + * + * @param[in,out] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. * * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE * if the PINGRESP packet doesn't follow MQTT spec. */ -IotMqttError_t _IotMqtt_DeserializePingresp( const uint8_t * pPingrespStart, - size_t dataLength, - size_t * pBytesProcessed ); +IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ); /** * @brief Generate a DISCONNECT packet. @@ -763,7 +751,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); /** * @brief Task pool routine for processing an MQTT connection's keep-alive. * - * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pTaskPool Pointer to the system task pool. * @param[in] pKeepAliveJob Pointer the an MQTT connection's keep-alive job. * @param[in] pContext Pointer to an MQTT connection, passed as an opaque context. */ @@ -774,7 +762,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, /** * @brief Task pool routine for processing an incoming PUBLISH message. * - * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pTaskPool Pointer to the system task pool. * @param[in] pPublishJob Pointer to the incoming PUBLISH operation's job. * @param[in] pContext Pointer to the incoming PUBLISH operation, passed as an * opaque context. @@ -786,7 +774,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, /** * @brief Task pool routine for processing an MQTT operation to send. * - * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pTaskPool Pointer to the system task pool. * @param[in] pSendJob Pointer to an operation's job. * @param[in] pContext Pointer to the operation to send, passed as an opaque * context. @@ -798,7 +786,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /** * @brief Task pool routine for processing a completed MQTT operation. * - * @param[in] pTaskPool Pointer to #_IotMqttTaskPool. + * @param[in] pTaskPool Pointer to the system task pool. * @param[in] pOperationJob Pointer to the completed operation's job. * @param[in] pContext Pointer to the completed operation, passed as an opaque * context. @@ -924,6 +912,21 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection */ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ); +/** + * @brief Read the next available byte on a network connection. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * @param[out] pIncomingByte The byte read from the network. + * + * @return `true` if a byte was successfully received from the network; `false` + * otherwise. + */ +bool _IotMqtt_GetNextByte( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface, + uint8_t * pIncomingByte ); + /** * @brief Closes the network connection associated with an MQTT connection. * diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index f6a5006c2d..a15cf24104 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -45,11 +45,11 @@ * - @functionname{static_memory_function_cleanup} * - @functionname{static_memory_function_messagebuffersize} * - @functionname{static_memory_function_mallocmessagebuffer} + * - @functionname{static_memory_function_freemessagebuffer} * - @functionname{static_memory_function_malloctaskpooljob} * - @functionname{static_memory_function_freetaskpooljob} * - @functionname{static_memory_function_malloctaskpooltimerevent} * - @functionname{static_memory_function_freetaskpooltimerevent} - * - @functionname{static_memory_function_freemessagebuffer} * - @functionname{static_memory_function_mallocmqttconnection} * - @functionname{static_memory_function_freemqttconnection} * - @functionname{static_memory_function_mallocmqttoperation} diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h new file mode 100644 index 0000000000..39adf157ce --- /dev/null +++ b/lib/include/types/iot_mqtt_types.h @@ -0,0 +1,1056 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_types.h + * @brief Types of the MQTT library. + */ + +#ifndef _IOT_MQTT_TYPES_H_ +#define _IOT_MQTT_TYPES_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* Standard includes. */ +#include +#include +#include + +/* Type includes. */ +#include "types/iot_platform_types.h" +#include "types/iot_taskpool_types.h" + +/* Platform network include. */ +#include "platform/iot_network.h" + +/*---------------------------- MQTT handle types ----------------------------*/ + +/** + * @handles{mqtt,MQTT library} + */ + +/** + * @ingroup mqtt_datatypes_handles + * @brief Opaque handle of an MQTT connection. + * + * This type identifies an MQTT connection, which is valid after a successful call + * to @ref mqtt_function_connect. A variable of this type is passed as the first + * argument to [MQTT library functions](@ref mqtt_functions) to identify which + * connection that function acts on. + * + * A call to @ref mqtt_function_disconnect makes a connection handle invalid. Once + * @ref mqtt_function_disconnect returns, the connection handle should no longer + * be used. + * + * @initializer{IotMqttConnection_t,IOT_MQTT_CONNECTION_INITIALIZER} + */ +typedef struct _mqttConnection * IotMqttConnection_t; + +/** + * @ingroup mqtt_datatypes_handles + * @brief Opaque handle that references an in-progress MQTT operation. + * + * Set as an output parameter of @ref mqtt_function_publish, @ref mqtt_function_subscribe, + * and @ref mqtt_function_unsubscribe. These functions queue an MQTT operation; the result + * of the operation is unknown until a response from the MQTT server is received. Therefore, + * this handle serves as a reference to MQTT operations awaiting MQTT server response. + * + * This reference will be valid from the successful return of @ref mqtt_function_publish, + * @ref mqtt_function_subscribe, or @ref mqtt_function_unsubscribe. The reference becomes + * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or + * @ref mqtt_function_wait returns. + * + * @initializer{IotMqttReference_t,IOT_MQTT_REFERENCE_INITIALIZER} + * + * @see @ref mqtt_function_wait and #IOT_MQTT_FLAG_WAITABLE for waiting on a reference. + * #IotMqttCallbackInfo_t and #IotMqttCallbackParam_t for an asynchronous notification + * of completion. + */ +typedef struct _mqttOperation * IotMqttReference_t; + +/*-------------------------- MQTT enumerated types --------------------------*/ + +/** + * @enums{mqtt,MQTT library} + */ + +/** + * @ingroup mqtt_datatypes_enums + * @brief Return codes of [MQTT functions](@ref mqtt_functions). + * + * The function @ref mqtt_function_strerror can be used to get a return code's + * description. + */ +typedef enum IotMqttError +{ + /** + * @brief MQTT operation completed successfully. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_publish with QoS 0 parameter + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * Will also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result when successful. + */ + IOT_MQTT_SUCCESS = 0, + + /** + * @brief MQTT operation queued, awaiting result. + * + * Functions that may return this value: + * - @ref mqtt_function_subscribe + * - @ref mqtt_function_unsubscribe + * - @ref mqtt_function_publish with QoS 1 parameter + */ + IOT_MQTT_STATUS_PENDING, + + /** + * @brief Initialization failed. + * + * Functions that may return this value: + * - @ref mqtt_function_init + */ + IOT_MQTT_INIT_FAILED, + + /** + * @brief At least one parameter is invalid. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + * - @ref mqtt_function_wait + */ + IOT_MQTT_BAD_PARAMETER, + + /** + * @brief MQTT operation failed because of memory allocation failure. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + */ + IOT_MQTT_NO_MEMORY, + + /** + * @brief MQTT operation failed because the network was unusable. + * + * This return value may indicate that the network is disconnected. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result. + */ + IOT_MQTT_NETWORK_ERROR, + + /** + * @brief MQTT operation could not be scheduled, i.e. enqueued for sending. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + */ + IOT_MQTT_SCHEDULING_ERROR, + + /** + * @brief MQTT response packet received from the network is malformed. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result. + * + * @note If this value is received, the network connection has been closed. + */ + IOT_MQTT_BAD_RESPONSE, + + /** + * @brief A blocking MQTT operation timed out. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait + * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_timedunsubscribe + * - @ref mqtt_function_timedpublish + */ + IOT_MQTT_TIMEOUT, + + /** + * @brief A CONNECT or at least one subscription was refused by the server. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter + * is associated with a SUBSCRIBE operation. + * - @ref mqtt_function_timedsubscribe + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result for a SUBSCRIBE. + * + * @note If this value is returned and multiple subscriptions were passed to + * @ref mqtt_function_subscribe (or @ref mqtt_function_timedsubscribe), it's + * still possible that some of the subscriptions succeeded. This value only + * signifies that AT LEAST ONE subscription was rejected. The function @ref + * mqtt_function_issubscribed can be used to determine which subscriptions + * were accepted or rejected. + */ + IOT_MQTT_SERVER_REFUSED, + + /** + * @brief A QoS 1 PUBLISH received no response and [the retry limit] + * (#IotMqttPublishInfo_t.retryLimit) was reached. + * + * Functions that may return this value: + * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter + * is associated with a QoS 1 PUBLISH operation + * - @ref mqtt_function_timedpublish + * + * May also be the value of an operation completion callback's + * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. + */ + IOT_MQTT_RETRY_NO_RESPONSE +} IotMqttError_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief Types of MQTT operations. + * + * The function @ref mqtt_function_operationtype can be used to get an operation + * type's description. + */ +typedef enum IotMqttOperationType +{ + IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ + IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ + IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ + IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ + IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ + IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ + IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ +} IotMqttOperationType_t; + +/** + * @ingroup mqtt_datatypes_enums + * @brief Quality of service levels for MQTT PUBLISH messages. + * + * All MQTT PUBLISH messages, including Last Will and Testament and messages + * received on subscription filters, have an associated Quality of Service, + * which defines any delivery guarantees for that message. + * - QoS 0 messages will be delivered at most once. This is a "best effort" + * transmission with no retransmissions. + * - QoS 1 messages will be delivered at least once. See #IotMqttPublishInfo_t + * for the retransmission strategy this library uses to redeliver messages + * assumed to be lost. + * + * @attention QoS 2 is not supported by this library and should not be used. + */ +typedef enum IotMqttQos +{ + IOT_MQTT_QOS_0 = 0, /**< Delivery at most once. */ + IOT_MQTT_QOS_1 = 1, /**< Delivery at least once. See #IotMqttPublishInfo_t for client-side retry strategy. */ + IOT_MQTT_QOS_2 = 2 /**< Delivery exactly once. Unsupported, but enumerated for completeness. */ +} IotMqttQos_t; + +/*------------------------- MQTT parameter structs --------------------------*/ + +/** + * @paramstructs{mqtt,MQTT} + */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a PUBLISH message. + * + * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publish + * + * Passed to @ref mqtt_function_publish as the message to publish and @ref + * mqtt_function_connect as the Last Will and Testament (LWT) message. + * + * @initializer{IotMqttPublishInfo_t,IOT_MQTT_PUBLISH_INFO_INITIALIZER} + * + * #IotMqttPublishInfo_t.retryMs and #IotMqttPublishInfo_t.retryLimit are only + * relevant to QoS 1 PUBLISH messages. They are ignored for QoS 0 PUBLISH + * messages and LWT messages. These members control retransmissions of QoS 1 + * messages under the following rules: + * - Retransmission is disabled when #IotMqttPublishInfo_t.retryLimit is 0. + * After sending the PUBLISH, the library will wait indefinitely for a PUBACK. + * - If #IotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes + * that do not receive a PUBACK within #IotMqttPublishInfo_t.retryMs will be + * retransmitted, up to #IotMqttPublishInfo_t.retryLimit times. + * + * Retransmission follows a truncated exponential backoff strategy. The constant + * @ref IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. + * + * After #IotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT + * library will wait @ref IOT_MQTT_RESPONSE_WAIT_MS before a final check + * for a PUBACK. If no PUBACK was received within this time, the QoS 1 PUBLISH + * fails with the code #IOT_MQTT_RETRY_NO_RESPONSE. + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + * + * @note The AWS IoT MQTT server does not support the DUP bit. When + * [using this library with the AWS IoT MQTT server](@ref IotMqttConnectInfo_t.awsIotMqttMode), + * retransmissions will instead be sent with a new packet identifier in the PUBLISH + * packet. This is a nonstandard workaround. Note that this workaround has some + * flaws, including the following: + * - The previous packet identifier is forgotten, so if a PUBACK arrives for that + * packet identifier, it will be ignored. On an exceptionally busy network, this + * may cause excessive retransmissions when too many PUBACKS arrive after the + * PUBLISH packet identifier is changed. However, the exponential backoff + * retransmission strategy should mitigate this problem. + * - Log messages will be printed using the new packet identifier; the old packet + * identifier is not saved. + * + * Example + * + * Consider a situation where + * - @ref IOT_MQTT_RETRY_MS_CEILING is 60000 + * - #IotMqttPublishInfo_t.retryMs is 2000 + * - #IotMqttPublishInfo_t.retryLimit is 20 + * + * A PUBLISH message will be retransmitted at the following times after the initial + * transmission if no PUBACK is received: + * - 2000 ms (2000 ms after previous transmission) + * - 6000 ms (4000 ms after previous transmission) + * - 14000 ms (8000 ms after previous transmission) + * - 30000 ms (16000 ms after previous transmission) + * - 62000 ms (32000 ms after previous transmission) + * - 122000 ms, 182000 ms, 242000 ms... (every 60000 ms until 20 transmissions have been sent) + * + * After the 20th retransmission, the MQTT library will wait + * @ref IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. + */ +typedef struct IotMqttPublishInfo +{ + IotMqttQos_t qos; /**< @brief QoS of message. Must be 0 or 1. */ + bool retain; /**< @brief MQTT message retain flag. */ + + const char * pTopicName; /**< @brief Topic name of PUBLISH. */ + uint16_t topicNameLength; /**< @brief Length of #IotMqttPublishInfo_t.pTopicName. */ + + const void * pPayload; /**< @brief Payload of PUBLISH. */ + size_t payloadLength; /**< @brief Length of #IotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ + + uint64_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ + uint32_t retryLimit; /**< @brief How many times to attempt retransmission. */ +} IotMqttPublishInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Parameter to an MQTT callback function. + * + * @paramfor MQTT callback functions + * + * The MQTT library passes this struct to registered callback whenever an + * operation completes or a message is received on a topic filter. + * + * The members of this struct are different based on the callback trigger. If the + * callback function was triggered for completed operation, the `operation` + * member is valid. Otherwise, if the callback was triggered because of a + * server-to-client PUBLISH, the `message` member is valid. + * + * For an incoming PUBLISH, the `message.pTopicFilter` parameter provides the + * subscription topic filter that matched the topic name in the PUBLISH. Because + * topic filters may use MQTT wildcards, the topic filter may be different from the + * topic name. This pointer must be treated as read-only; the topic filter must not + * be modified. Additionally, the topic filter may go out of scope as soon as the + * callback function returns, so it must be copied if it is needed at a later time. + * + * @attention Any pointers in this callback parameter may be freed as soon as + * the [callback function](@ref IotMqttCallbackInfo_t.function) returns. + * Therefore, data must be copied if it is needed after the callback function + * returns. + * @attention The MQTT library may set strings that are not NULL-terminated. + * + * @see #IotMqttCallbackInfo_t for the signature of a callback function. + */ +typedef struct IotMqttCallbackParam +{ + /** + * @brief The MQTT connection associated with this completed operation or + * incoming PUBLISH. + * + * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback. + * However, blocking function calls (including @ref mqtt_function_wait) are + * not recommended (though still safe). + */ + IotMqttConnection_t mqttConnection; + + union + { + /* Valid for completed operations. */ + struct + { + IotMqttOperationType_t type; /**< @brief Type of operation that completed. */ + IotMqttReference_t reference; /**< @brief Reference to the operation that completed. */ + IotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ + } operation; + + /* Valid for incoming PUBLISH messages. */ + struct + { + const char * pTopicFilter; /**< @brief Topic filter that matched the message. */ + uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ + IotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ + } message; + }; +} IotMqttCallbackParam_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a user-provided MQTT callback function. + * + * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, + * and @ref mqtt_function_publish. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. + * + * Provides a function to be invoked when an operation completes or when a + * server-to-client PUBLISH is received. + * + * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} + * + * Below is an example for receiving an asynchronous notification on operation + * completion. See @ref mqtt_function_subscribe for an example of using this struct + * with for incoming PUBLISH messages. + * + * @code{c} + * // Operation completion callback. + * void operationComplete( void * pArgument, IotMqttCallbackParam_t * pOperation ); + * + * // Callback information. + * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * callbackInfo.function = operationComplete; + * + * // Operation to wait for. + * IotMqttError_t result = IotMqtt_Publish( &mqttConnection, + * &publishInfo, + * 0, + * &callbackInfo, + * &reference ); + * + * // Publish should have returned IOT_MQTT_STATUS_PENDING. Once a response + * // is received, operationComplete is executed with the actual status passed + * // in pOperation. + * @endcode + */ +typedef struct IotMqttCallbackInfo +{ + void * param1; /**< @brief The first parameter to pass to the callback function. */ + + /** + * @brief User-provided callback function signature. + * + * @param[in] void * #IotMqttCallbackInfo_t.param1 + * @param[in] IotMqttCallbackParam_t * Details on the outcome of the MQTT operation + * or an incoming MQTT PUBLISH. + * + * @see #IotMqttCallbackParam_t for more information on the second parameter. + */ + void ( * function )( void *, + IotMqttCallbackParam_t * ); +} IotMqttCallbackInfo_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on an MQTT subscription. + * + * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe + * + * An array of these is passed to @ref mqtt_function_subscribe and @ref + * mqtt_function_unsubscribe. However, #IotMqttSubscription_t.callback and + * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribe. + * + * @initializer{IotMqttSubscription_t,IOT_MQTT_SUBSCRIPTION_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + * @see #IotMqttCallbackInfo_t for details on setting a callback function. + */ +typedef struct IotMqttSubscription +{ + /** + * @brief QoS of messages delivered on subscription. + * + * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe. + */ + IotMqttQos_t qos; + + const char * pTopicFilter; /**< @brief Topic filter of subscription. */ + uint16_t topicFilterLength; /**< @brief Length of #IotMqttSubscription_t.pTopicFilter. */ + + /** + * @brief Callback to invoke when a message is received. + * + * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. + */ + IotMqttCallbackInfo_t callback; +} IotMqttSubscription_t; + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Information on a new MQTT connection. + * + * @paramfor @ref mqtt_function_connect + * + * Passed as an argument to @ref mqtt_function_connect. Most members of this struct + * correspond to the content of an [MQTT CONNECT packet.] + * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) + * + * @initializer{IotMqttConnectInfo_t,IOT_MQTT_CONNECT_INFO_INITIALIZER} + * + * @note The lengths of the strings in this struct should not include the NULL + * terminator. Strings in this struct do not need to be NULL-terminated. + */ +typedef struct IotMqttConnectInfo +{ + /** + * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. + * + * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] + * (https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html) + * When this member is `true`, the MQTT library will accommodate these + * differences. This setting should be `false` when communicating with a + * fully-compliant MQTT broker. + * + * @attention This setting MUST be `true` when using the AWS IoT MQTT + * server; it MUST be `false` otherwise. + * @note Currently, @ref IOT_MQTT_CONNECT_INFO_INITIALIZER sets this + * this member to `true`. + */ + bool awsIotMqttMode; + + /** + * @brief Whether this connection is a clean session. + * + * MQTT servers can maintain and topic filter subscriptions and unacknowledged + * PUBLISH messages. These form part of an MQTT session, which is identified by + * the [client identifier](@ref IotMqttConnectInfo_t.pClientIdentifier). + * + * Setting this value to `true` establishes a clean session, which causes + * the MQTT server to discard any previous session data for a client identifier. + * When the client disconnects, the server discards all session data. If this + * value is `true`, #IotMqttConnectInfo_t.pPreviousSubscriptions and + * #IotMqttConnectInfo_t.previousSubscriptionCount are ignored. + * + * Setting this value to `false` does one of the following: + * - If no previous session exists, the MQTT server will create a new + * persistent session. The server may maintain subscriptions and + * unacknowledged PUBLISH messages after a client disconnects, to be restored + * once the same client identifier reconnects. + * - If a previous session exists, the MQTT server restores all of the session's + * subscriptions for the client identifier and may immediately transmit any + * unacknowledged PUBLISH packets to the client. + * + * When a client with a persistent session disconnects, the MQTT server + * continues to maintain all subscriptions and unacknowledged PUBLISH messages. + * The client must also remember the session subscriptions to restore them + * upon reconnecting. #IotMqttConnectInfo_t.pPreviousSubscriptions and + * #IotMqttConnectInfo_t.previousSubscriptionCount are used to restore a + * previous session's subscriptions client-side. + */ + bool cleanSession; + + /** + * @brief An array of MQTT subscriptions present in a previous session, if any. + * + * Pointer to the start of an array of subscriptions present a previous session, + * if any. These subscriptions will be immediately restored upon reconnecting. + * + * This member is ignored if it is `NULL` or #IotMqttConnectInfo_t.cleanSession + * is `true`. If this member is not `NULL`, #IotMqttConnectInfo_t.previousSubscriptionCount + * must be nonzero. + */ + const IotMqttSubscription_t * pPreviousSubscriptions; + + /** + * @brief The number of MQTT subscriptions present in a previous session, if any. + * + * Number of subscriptions contained in the array + * #IotMqttConnectInfo_t.pPreviousSubscriptions. + * + * This value is ignored if #IotMqttConnectInfo_t.pPreviousSubscriptions + * is `NULL` or #IotMqttConnectInfo_t.cleanSession is `true`. If + * #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value + * must be nonzero. + */ + size_t previousSubscriptionCount; + + /** + * @brief A message to publish if the new MQTT connection is unexpectedly closed. + * + * A Last Will and Testament (LWT) message may be published if this connection is + * closed without sending an MQTT DISCONNECT packet. This pointer should be set to + * an #IotMqttPublishInfo_t representing any LWT message to publish. If an LWT + * is not needed, this member must be set to `NULL`. + * + * Unlike other PUBLISH messages, an LWT message is limited to 65535 bytes in + * length. Additionally, [pWillInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) + * and [pWillInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) will + * be ignored. + */ + const IotMqttPublishInfo_t * pWillInfo; + + uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ + + const char * pClientIdentifier; /**< @brief MQTT client identifier. */ + uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ + + /* These credentials are not used by AWS IoT and may be ignored if + * awsIotMqttMode is true. */ + const char * pUserName; /**< @brief Username for MQTT connection. */ + uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ + const char * pPassword; /**< @brief Password for MQTT connection. */ + uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ +} IotMqttConnectInfo_t; + +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declaration of the internal MQTT packet structure. + */ + struct _mqttPacket; +/** @endcond */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Function pointers for MQTT packet serializer overrides. + * + * These funciton pointers allow the MQTT serialization and deserialization functions + * to be overridden for an MQTT connection. The compile-time setting + * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be `1` to enable this functionality. + * See the #IotMqttSerializer_t::serialize and #IotMqttSerializer_t::deserialize + * members for a list of functions that can be overridden. In addition, the functions + * for freeing packets and determining the packet type can also be overridden. If + * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is `1`, the serializer initialization and + * cleanup functions may be extended. See documentation of + * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. + * + * If any function pointers that are `NULL`, then the default implementation of that + * function will be used. + */ + typedef struct IotMqttSerializer + { + /** + * @brief Get the MQTT packet type from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * Default implementation: #_IotMqtt_GetPacketType + */ + uint8_t ( * getPacketType )( void * /* pNetworkConnection */, + const IotNetworkInterface_t * /* pNetworkInterface */ ); + + /** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + * + * Default implementation: #_IotMqtt_GetRemainingLength + */ + size_t ( * getRemainingLength )( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + + /** + * @brief Free a packet generated by the serializer. + * + * This function pointer must be set if any other serializer override is set. + * @param[in] uint8_t* The packet to free. + * + * Default implementation: #_IotMqtt_FreePacket + */ + void ( * freePacket )( uint8_t * /* pPacket */ ); + + struct + { + /** + * @brief CONNECT packet serializer function. + * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. + * @param[out] uint8_t** Where the CONNECT packet is written. + * @param[out] size_t* Size of the CONNECT packet. + * + * Default implementation: #_IotMqtt_SerializeConnect + */ + IotMqttError_t ( * connect )( const IotMqttConnectInfo_t * /* pConnectInfo */, + uint8_t ** /* pConnectPacket */, + size_t * /* pPacketSize */ ); + + /** + * @brief PUBLISH packet serializer function. + * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. + * @param[out] uint8_t** Where the PUBLISH packet is written. + * @param[out] size_t* Size of the PUBLISH packet. + * @param[out] uint16_t* The packet identifier generated for this PUBLISH. + * @param[out] uint8_t** Where the high byte of the packet identifier + * is written. + * + * Default implementation: #_IotMqtt_SerializePublish + */ + IotMqttError_t ( * publish )( const IotMqttPublishInfo_t * /* pPublishInfo */, + uint8_t ** /* pPublishPacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */, + uint8_t ** /* pPacketIdentifierHigh */ ); + + /** + * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. + * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. + * @param[in] uint8_t* The high byte of any packet identifier to modify. + * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). + * + * Default implementation: #_IotMqtt_PublishSetDup + */ + void ( *publishSetDup )( uint8_t * /* pPublishPacket */, + uint8_t * /* pPacketIdentifierHigh */, + uint16_t * /* pNewPacketIdentifier */ ); + + /** + * @brief PUBACK packet serializer function. + * @param[in] uint16_t The packet identifier to place in PUBACK. + * @param[out] uint8_t** Where the PUBACK packet is written. + * @param[out] size_t* Size of the PUBACK packet. + * + * Default implementation: #_IotMqtt_SerializePuback + */ + IotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, + uint8_t ** /* pPubackPacket */, + size_t * /* pPacketSize */ ); + + /** + * @brief SUBSCRIBE packet serializer function. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the SUBSCRIBE packet is written. + * @param[out] size_t* Size of the SUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. + * + * Default implementation: #_IotMqtt_SerializeSubscribe + */ + IotMqttError_t ( * subscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** /* pSubscribePacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); + + /** + * @brief UNSUBSCRIBE packet serializer function. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions to remove. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the UNSUBSCRIBE packet is written. + * @param[out] size_t* Size of the UNSUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this UNSUBSCRIBE. + * + * Default implementation: #_IotMqtt_SerializeUnsubscribe + */ + IotMqttError_t ( * unsubscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, + size_t /* subscriptionCount */, + uint8_t ** /* pUnsubscribePacket */, + size_t * /* pPacketSize */, + uint16_t * /* pPacketIdentifier */ ); + + /** + * @brief PINGREQ packet serializer function. + * @param[out] uint8_t** Where the PINGREQ packet is written. + * @param[out] size_t* Size of the PINGREQ packet. + * + * Default implementation: #_IotMqtt_SerializePingreq + */ + IotMqttError_t ( * pingreq )( uint8_t ** /* pPingreqPacket */, + size_t * /* pPacketSize */ ); + + /** + * @brief DISCONNECT packet serializer function. + * @param[out] uint8_t** Where the DISCONNECT packet is written. + * @param[out] size_t* Size of the DISCONNECT packet. + * + * Default implementation: #_IotMqtt_SerializeDisconnect + */ + IotMqttError_t ( * disconnect )( uint8_t ** /* pDisconnectPacket */, + size_t * /* pPacketSize */ ); + } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ + + struct + { + /** + * @brief CONNACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a CONNACK. + * + * Default implementation: #_IotMqtt_DeserializeConnack + */ + IotMqttError_t ( * connack )( struct _mqttPacket * /* pConnack */ ); + + /** + * @brief PUBLISH packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PUBLISH. + * + * Default implementation: #_IotMqtt_DeserializePublish + */ + IotMqttError_t ( * publish )( struct _mqttPacket * /* pPublish */ ); + + /** + * @brief PUBACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PUBACK. + * + * Default implementation: #_IotMqtt_DeserializePuback + */ + IotMqttError_t ( * puback )( struct _mqttPacket * pPuback ); + + /** + * @brief SUBACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a SUBACK. + * + * Default implementation: #_IotMqtt_DeserializeSuback + */ + IotMqttError_t ( * suback )( struct _mqttPacket * /* pSuback */ ); + + /** + * @brief UNSUBACK packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing an UNSUBACK. + * + * Default implementation: #_IotMqtt_DeserializeUnsuback + */ + IotMqttError_t ( * unsuback )( struct _mqttPacket * /* pUnsuback */ ); + + /** + * @brief PINGRESP packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PINGRESP. + * + * Default implementation: #_IotMqtt_DeserializePingresp + */ + IotMqttError_t ( * pingresp )( struct _mqttPacket * /* pPingresp */ ); + } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ + } IotMqttSerializer_t; +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + +/* When MQTT packet serializer overrides are disabled, this struct is an + * incomplete type. */ + typedef struct IotMqttSerializer IotMqttSerializer_t; + +#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief Infomation on the transport-layer network connection for the new MQTT + * connection. + * + * @paramfor @ref mqtt_function_connect + * + * The MQTT library needs to be able to send and receive data over a network. + * This struct provides an interface for interacting with the network. + * + * @initializer{IotMqttNetworkInfo_t,IOT_MQTT_NETWORK_INFO_INITIALIZER} + */ +typedef struct IotMqttNetworkInfo +{ + /** + * @brief Whether a new network connection should be created. + * + * When this value is `true`, a new transport-layer network connection will + * be created along with the MQTT connection. #IotMqttNetworkInfo_t::pNetworkServerInfo + * and #IotMqttNetworkInfo_t::pNetworkCredentialInfo are valid when this value + * is `true`. + * + * When this value is `false`, the MQTT connection will use a transport-layer + * network connection that has already been established. The MQTT library will + * still set the appropriate receive callback even if the network connection + * has been established. + * #IotMqttNetworkInfo_t::pNetworkConnection, which represents an established + * network connection, is valid when this value is `false`. + */ + bool createNetworkConnection; + + union + { + struct + { + /** + * @brief Information on the MQTT server, passed as `pConnectionInfo` to + * #IotNetworkInterface_t::create. + * + * This member is opaque to the MQTT library. It is passed to the network + * interface when creating a new network connection. It is only valid when + * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. + */ + void * pNetworkServerInfo; + + /** + * @brief Credentials for the MQTT server, passed as `pCredentialInfo` to + * #IotNetworkInterface_t::create. + * + * This member is opaque to the MQTT library. It is passed to the network + * interface when creating a new network connection. It is only valid when + * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. + */ + void * pNetworkCredentialInfo; + }; + + /** + * @brief An established transport-layer network connection. + * + * This member is opaque to the MQTT library. It is passed to the network + * interface to reference an established network connection. It is only + * valid when #IotMqttNetworkInfo_t::createNetworkConnection is `false`. + */ + void * pNetworkConnection; + }; + + /** + * @brief The network functions used by the new MQTT connection. + * + * @attention The function pointers of the network interface must remain valid + * for the lifetime of the MQTT connection. + */ + const IotNetworkInterface_t * pNetworkInterface; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + + /** + * @brief MQTT packet serializer overrides used by the new MQTT connection. + * + * @attention The function pointers of the MQTT serializer overrides must + * remain valid for the lifetime of the MQTT connection. + */ + const IotMqttSerializer_t * pMqttSerializer; + #endif +} IotMqttNetworkInfo_t; + +/*------------------------- MQTT defined constants --------------------------*/ + +/** + * @constantspage{mqtt,MQTT library} + * + * @section mqtt_constants_initializers MQTT Initializers + * @brief Provides default values for the data types of the MQTT library. + * + * @snippet this define_mqtt_initializers + * + * All user-facing data types of the MQTT library should be initialized using + * one of the following. + * + * @warning Failing to initialize an MQTT data type with the appropriate initializer + * may result in undefined behavior! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + * IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; + * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + * IotMqttConnection_t connection = IOT_MQTT_CONNECTION_INITIALIZER; + * IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; + * @endcode + * + * @section mqtt_constants_flags MQTT Function Flags + * @brief Flags that modify the behavior of MQTT library functions. + * - #IOT_MQTT_FLAG_WAITABLE
+ * @copybrief IOT_MQTT_FLAG_WAITABLE + * - #IOT_MQTT_FLAG_CLEANUP_ONLY
+ * @copybrief IOT_MQTT_FLAG_CLEANUP_ONLY + * + * Flags should be bitwise-ORed with each other to change the behavior of + * @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, + * @ref mqtt_function_publish, or @ref mqtt_function_disconnect. + * + * @note The values of the flags may change at any time in future versions, but + * their names will remain the same. Additionally, flags that may be used together + * will be bitwise-exclusive of each other. + */ + +/* @[define_mqtt_initializers] */ +/** @brief Initializer for #IotMqttNetworkInfo_t. */ +#define IOT_MQTT_NETWORK_INFO_INITIALIZER { .createNetworkConnection = true } +/** @brief Initializer for #IotMqttSerializer_t. */ +#define IOT_MQTT_SERIALIZER_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttConnectInfo_t. */ +#define IOT_MQTT_CONNECT_INFO_INITIALIZER { .cleanSession = true } +/** @brief Initializer for #IotMqttPublishInfo_t. */ +#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttSubscription_t. */ +#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttCallbackInfo_t. */ +#define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } +/** @brief Initializer for #IotMqttConnection_t. */ +#define IOT_MQTT_CONNECTION_INITIALIZER NULL +/** @brief Initializer for #IotMqttReference_t. */ +#define IOT_MQTT_REFERENCE_INITIALIZER NULL +/* @[define_mqtt_initializers] */ + +/** + * @brief Allows the use of @ref mqtt_function_wait for blocking until completion. + * + * This flag is always valid for @ref mqtt_function_subscribe and + * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, + * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. + * + * An #IotMqttReference_t MUST be provided if this flag is set. Additionally, an + * #IotMqttCallbackInfo_t MUST NOT be provided. + * + * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up + * resources. + */ +#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) + +/** + * @brief Causes @ref mqtt_function_disconnect to only free memory and not send + * an MQTT DISCONNECT packet. + * + * This flag is only valid for @ref mqtt_function_disconnect. It should be passed + * to @ref mqtt_function_disconnect if the network goes offline or is otherwise + * unusable. + */ +#define IOT_MQTT_FLAG_CLEANUP_ONLY ( 0x00000001 ) + +#endif /* ifndef _IOT_MQTT_TYPES_H_ */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index d94d51ec83..b41c562c9f 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -16,7 +16,7 @@ add_library( iotcommon SHARED set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( iotcommon iotplatform iotmetrics ) +target_link_libraries( iotcommon iotplatform ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c index 34b439ac17..663a9ed674 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_common.c @@ -31,11 +31,14 @@ #include "iot_taskpool.h" /* Metrics include. */ -#include "iot_metrics.h" +#include "platform/iot_metrics.h" /* Static memory include (if dynamic memory allocation is disabled). */ #include "private/iot_static_memory.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_GLOBAL #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL @@ -50,20 +53,9 @@ bool IotCommon_Init( void ) { - bool status = true; - - /* Create system task pool. */ - if( status == true ) - { - IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; - - if( IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) != IOT_TASKPOOL_SUCCESS ) - { - IotLogError( "Failed to create system task pool." ); - - status = false; - } - } + _IOT_FUNCTION_ENTRY( bool, true ); + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; if (status == true) { @@ -72,34 +64,54 @@ bool IotCommon_Init( void ) /* Initialize static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 - status = IotStaticMemory_Init(); + bool staticMemoryInitialized = IotStaticMemory_Init(); - if( status == false ) + if( staticMemoryInitialized == false ) { IotLogError( "Failed to initialize static memory." ); - IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); + _IOT_GOTO_CLEANUP(); } #endif - if( status == true ) + /* Create system task pool. */ + taskPoolStatus = IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ); + + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + IotLogError( "Failed to create system task pool." ); + _IOT_SET_AND_GOTO_CLEANUP( false ); + } + + _IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status == false ) + { + #if IOT_STATIC_MEMORY_ONLY == 1 + if( staticMemoryInitialized == true ) + { + IotStaticMemory_Cleanup(); + } + #endif + } + else { IotLogInfo( "Common libraries successfully initialized." ); } - return status; + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ void IotCommon_Cleanup( void ) { + IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); + /* Cleanup static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 IotStaticMemory_Cleanup(); #endif - IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); - IotLogInfo( "Common libraries cleanup done." ); } diff --git a/lib/source/common/static_memory/iot_static_memory_metrics.c b/lib/source/common/static_memory/iot_static_memory_metrics.c index b2c3ad7c8c..48aeb61eaf 100644 --- a/lib/source/common/static_memory/iot_static_memory_metrics.c +++ b/lib/source/common/static_memory/iot_static_memory_metrics.c @@ -36,7 +36,7 @@ #include "private/iot_static_memory.h" /* Metrics include. */ - #include "iot_metrics.h" + #include "platform/iot_metrics.h" #ifndef IOT_METRICS_TCP_CONNECTIONS #define IOT_METRICS_TCP_CONNECTIONS ( 10 ) diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt index cda9ec1ddd..f343807b06 100644 --- a/lib/source/defender/CMakeLists.txt +++ b/lib/source/defender/CMakeLists.txt @@ -8,7 +8,7 @@ add_library( awsiotdefender SHARED set_target_properties( awsiotdefender PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( awsiotdefender iotserializer iotmetrics iotmqtt iotplatform ) +target_link_libraries( awsiotdefender iotserializer iotmqtt iotplatform ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index c8714fd2e8..d7c4fec991 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -25,12 +25,8 @@ /* Task pool include. */ #include "iot_taskpool.h" -/* POSIX includes. */ -#ifdef _DEFENDER_ON_AMAZON_FREERTOS - #include "FreeRTOS_POSIX/unistd.h" -#else - #include -#endif +/* Clock include. */ +#include "platform/iot_clock.h" #define _WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) @@ -92,6 +88,9 @@ static bool _started = false; /* Internal copy of startInfo so that user's input doesn't have to be valid all the time. */ AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; +/* For debug purpose. */ +const char * _AwsIotDefenderEventError = "None"; + /*-----------------------------------------------------------*/ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, @@ -127,10 +126,7 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ) { - if( ( pStartInfo == NULL ) || - ( pStartInfo->pConnectionInfo == NULL ) || - ( pStartInfo->pCredentialInfo == NULL ) || - ( pStartInfo->pNetworkInterface == NULL ) ) + if( pStartInfo == NULL ) { IotLogError( "Input start info is invalid." ); @@ -242,7 +238,7 @@ void AwsIotDefender_Stop( void ) if( taskPoolError != IOT_TASKPOOL_SUCCESS ) { IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); - sleep( _WAIT_METRICS_JOB_MAX_SECONDS ); + IotClock_SleepMs( _WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); } /* Destroy metrics' mutex. */ @@ -328,35 +324,10 @@ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) return pErrorNames[ error ]; } - /*-----------------------------------------------------------*/ - -const char * AwsIotDefender_DescribeEventType( AwsIotDefenderEventType_t eventType ) +const char * AwsIotDefender_GetEventError() { - /* The string returned if the parameter is invalid. */ - static const char * pInvalidEvent = "INVALID EVENT"; - - /* Lookup table of Defender events. */ - static const char * pEventNames[] = - { - "METRICS ACCEPTED", /* AWS_IOT_DEFENDER_METRICS_ACCEPTED */ - "METRICS REJECTED", /* AWS_IOT_DEFENDER_METRICS_REJECTED */ - "NETWORK CONNECTION FAILED" /* AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED */ - "MQTT CONNECTION FAILED", /* AWS_IOT_DEFENDER_MQTT_CONNECTION_FAILED */ - "MQTT SUBSCRIPTION FAILED", /* AWS_IOT_DEFENDER_MQTT_SUBSCRIPTION_FAILED */ - "MQTT PUBLISH FAILED", /* AWS_IOT_DEFENDER_MQTT_PUBLISH_FAILED */ - "METRICS SERIALIZATION FAILED", /* AWS_IOT_DEFENDER_METRICS_SERIALIZATION_FAILED */ - "NO MEMORY" /* AWS_IOT_DEFENDER_EVENT_NO_MEMORY */ - }; - - /* Check that the parameter is valid. */ - if( ( eventType < 0 ) || - ( eventType >= ( sizeof( pEventNames ) / sizeof( pEventNames[ 0 ] ) ) ) ) - { - return pInvalidEvent; - } - - return pEventNames[ eventType ]; + return _AwsIotDefenderEventError; } /*-----------------------------------------------------------*/ @@ -378,61 +349,41 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, return; } - bool networkConnected = false, mqttConnected = false, reportCreated = false, reportPublished = false; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - AwsIotDefenderEventType_t eventType; - AwsIotDefenderCallbackInfo_t callbackInfo; + bool mqttConnected = true, reportCreated = true, reportPublished = false; const IotMqttCallbackInfo_t acceptCallbackInfo = { .function = _acceptCallback, .param1 = NULL }; const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .param1 = NULL }; - /* Step 1: connect to Iot endpoint. */ - if( ( networkConnected = AwsIotDefenderInternal_NetworkConnect() ) ) + /* Step 1: connect to MQTT. */ + mqttError = AwsIotDefenderInternal_MqttConnect(); + + if( ( mqttConnected = ( mqttError == IOT_MQTT_SUCCESS ) ) ) { - /* Step 2: set MQTT callback. */ - AwsIotDefenderInternal_SetMqttCallback(); + mqttError = AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, + rejectCallbackInfo ); - /* Step 3: connect to MQTT. */ - if( ( mqttConnected = AwsIotDefenderInternal_MqttConnect() ) ) + /* Step 2: subscribe to accept/reject MQTT topics. */ + if( mqttError == IOT_MQTT_SUCCESS ) { - /* Step 4: subscribe to accept/reject MQTT topics. */ - if( AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, - rejectCallbackInfo ) ) + /* Step 3: create serialized metrics report. */ + reportCreated = AwsIotDefenderInternal_CreateReport(); + + /* If Report is created successfully. */ + if( reportCreated ) { - /* Step 5: create serialized metrics report. */ - eventType = AwsIotDefenderInternal_CreateReport(); + /* Step 4: publish report to defender topic. */ + mqttError = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), + AwsIotDefenderInternal_GetReportBufferSize() ); - /* If Report is created successfully. */ - if( eventType == 0 ) + if( ( reportPublished = ( mqttError == IOT_MQTT_SUCCESS ) ) ) { - reportCreated = true; - - /* Step 6: publish report to defender topic. */ - if( ( reportPublished = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), - AwsIotDefenderInternal_GetReportBufferSize() ) ) ) - { - IotLogDebug( "Metrics report has been published successfully." ); - } - else - { - eventType = AWS_IOT_DEFENDER_MQTT_PUBLISH_FAILED; - } + IotLogDebug( "Metrics report has been published successfully." ); } } - else - { - eventType = AWS_IOT_DEFENDER_MQTT_SUBSCRIPTION_FAILED; - } - } - else - { - eventType = AWS_IOT_DEFENDER_MQTT_CONNECTION_FAILED; } } - else - { - eventType = AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED; - } if( reportPublished ) { @@ -446,9 +397,29 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, /* Something is wrong during the above process. */ else { + /* Either MQTT functions had problem or report failed to be created. */ + AwsIotDefender_Assert( mqttError != IOT_MQTT_SUCCESS || !reportCreated ); + + AwsIotDefenderEventType_t eventType; + + /* Set event type to only two possible categories. */ + if( reportCreated ) + { + eventType = AWS_IOT_DEFENDER_FAILURE_MQTT; + _AwsIotDefenderEventError = IotMqtt_strerror( mqttError ); + } + else + { + eventType = AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT; + /* As of today, no memory is the only reason to fail metrics report creation. */ + _AwsIotDefenderEventError = "Failed to create metrics report due to no memory."; + } + /* Invoke user's callback if there is. */ if( _startInfo.callback.function != NULL ) { + AwsIotDefenderCallbackInfo_t callbackInfo; + callbackInfo.eventType = eventType; /* No message to be given to user's callback */ @@ -473,11 +444,6 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, AwsIotDefenderInternal_MqttDisconnect(); } - if( networkConnected ) - { - AwsIotDefenderInternal_NetworkDestroy(); - } - IotSemaphore_Post( &_doneSem ); } @@ -499,8 +465,6 @@ static void _disconnectRoutine( IotTaskPool_t * pTaskPool, AwsIotDefenderInternal_DeleteReport(); AwsIotDefenderInternal_MqttDisconnect(); - AwsIotDefenderInternal_NetworkDestroy(); - /* Re-create metrics job. */ IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index 19147d80f1..5f49b3ae76 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -25,16 +25,10 @@ /* Defender internal include. */ #include "private/aws_iot_defender_internal.h" -#include "iot_metrics.h" +#include "platform/iot_metrics.h" #include "platform/iot_clock.h" -#ifdef _DEFENDER_ON_AMAZON_FREERTOS - #include "aws_secure_sockets.h" -#else - #include -#endif - #define _HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) #define _REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) #define _VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) @@ -94,7 +88,7 @@ uint64_t _AwsIotDefenderReportId = 0; static void copyMetricsFlag(); -static AwsIotDefenderEventType_t getLatestMetricsData(); +static bool getLatestMetricsData(); static void freeMetricsData(); @@ -137,12 +131,12 @@ size_t AwsIotDefenderInternal_GetReportBufferSize() /*-----------------------------------------------------------*/ -AwsIotDefenderEventType_t AwsIotDefenderInternal_CreateReport() +bool AwsIotDefenderInternal_CreateReport() { /* Assert report buffer is not allocated. */ AwsIotDefender_Assert( _report.pDataBuffer == NULL && _report.size == 0 ); - AwsIotDefenderEventType_t eventError = 0; + bool result = true; IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); size_t dataSize = 0; @@ -152,9 +146,9 @@ AwsIotDefenderEventType_t AwsIotDefenderInternal_CreateReport() copyMetricsFlag(); /* Get latest metrics data. */ - eventError = getLatestMetricsData(); + result = getLatestMetricsData(); - if( !eventError ) + if( result ) { /* Generate report id based on current time. */ _AwsIotDefenderReportId = IotClock_GetTimeMs(); @@ -181,14 +175,14 @@ AwsIotDefenderEventType_t AwsIotDefenderInternal_CreateReport() } else { - eventError = AWS_IOT_DEFENDER_EVENT_NO_MEMORY; + result = false; } /* Metrics data can be freed. */ freeMetricsData(); } - return eventError; + return result; } /*-----------------------------------------------------------*/ @@ -325,9 +319,9 @@ static void copyMetricsFlag() /*-----------------------------------------------------------*/ -static AwsIotDefenderEventType_t getLatestMetricsData() +static bool getLatestMetricsData() { - AwsIotDefenderEventType_t eventError = 0; + bool result = true; /* Get TCP connections metrics data. */ if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) @@ -336,12 +330,12 @@ static AwsIotDefenderEventType_t getLatestMetricsData() tcpConnectionscallback.function = tcpConnectionsCallback; - tcpConnectionscallback.param1 = ( void * ) &eventError; + tcpConnectionscallback.param1 = ( bool * ) &result; IotMetrics_ProcessTcpConnections( tcpConnectionscallback ); } - return eventError; + return result; } /*-----------------------------------------------------------*/ @@ -350,7 +344,11 @@ static void freeMetricsData() { if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) { - IotMetrics_FreeTcpConnection( _metrics.tcpConns.pArray ); + if( _metrics.tcpConns.pArray != NULL ) + { + IotMetrics_FreeTcpConnection( _metrics.tcpConns.pArray ); + } + _metrics.tcpConns.pArray = NULL; _metrics.tcpConns.count = 0; } @@ -361,7 +359,7 @@ static void freeMetricsData() static void tcpConnectionsCallback( void * param1, IotListDouble_t * pTcpConnectionsMetricsList ) { - AwsIotDefenderEventType_t * pEventError = ( AwsIotDefenderEventType_t * ) param1; + bool * pResult = ( bool * ) param1; IotLink_t * pConnectionLink = IotListDouble_PeekHead( pTcpConnectionsMetricsList ); IotMetricsTcpConnection_t * pConnection = NULL; @@ -375,7 +373,10 @@ static void tcpConnectionsCallback( void * param1, /* Allocate memory to copy TCP connections metrics data. */ _metrics.tcpConns.pArray = IotMetrics_MallocTcpConnection( total * sizeof( IotMetricsTcpConnection_t ) ); - if( _metrics.tcpConns.pArray != NULL ) + /* Set whether success of memory allocation to the output pointer. */ + *pResult = _metrics.tcpConns.pArray != NULL; + + if( *pResult ) { /* Set count only the memory allocation succeeds. */ _metrics.tcpConns.count = ( uint8_t ) total; @@ -394,10 +395,6 @@ static void tcpConnectionsCallback( void * param1, pConnectionLink = pConnectionLink->pNext; } } - else - { - *pEventError = AWS_IOT_DEFENDER_EVENT_NO_MEMORY; - } } } @@ -471,16 +468,7 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj /* add remote address */ if( hasRemoteAddr ) { - #ifdef _DEFENDER_ON_AMAZON_FREERTOS - /* Remote IP is with host endian. So it is converted to network endian and passed into SOCKETS_inet_ntoa. */ - SOCKETS_inet_ntoa( SOCKETS_htonl( _metrics.tcpConns.pArray[ i ].remoteIP ), remoteAddr ); - sprintf( remoteAddr, "%s:%d", remoteAddr, _metrics.tcpConns.pArray[ i ].remotePort ); - #else - /* Remote IP is with network endian. */ - struct in_addr remoteInAddr = { .s_addr = _metrics.tcpConns.pArray[ i ].remoteIP }; - char * pRemoteIp = inet_ntoa( remoteInAddr ); - sprintf( remoteAddr, "%s:%d", pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); - #endif + sprintf( remoteAddr, "%s:%d", _metrics.tcpConns.pArray[ i ].pRemoteIP, _metrics.tcpConns.pArray[ i ].remotePort ); serializerError = _AwsIotDefenderEncoder.appendKeyValue( &connectionMap, _REMOTE_ADDR_TAG, IotSerializer_ScalarTextString( remoteAddr ) ); diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index c30e80e24b..81ef414ae1 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -108,45 +108,18 @@ void AwsIotDefenderInternal_DeleteTopicsNames() /*-----------------------------------------------------------*/ -bool AwsIotDefenderInternal_NetworkConnect() +IotMqttError_t AwsIotDefenderInternal_MqttConnect() { - return _startInfo.pNetworkInterface->create( _startInfo.pConnectionInfo, - _startInfo.pCredentialInfo, - _startInfo.pConnection ) == IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -void AwsIotDefenderInternal_SetMqttCallback() -{ - IotNetworkError_t error = _startInfo.pNetworkInterface->setReceiveCallback( _startInfo.pConnection, - IotMqtt_ReceiveCallback, - &_mqttConnection ); - - AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -bool AwsIotDefenderInternal_MqttConnect() -{ - IotMqttNetIf_t mqttNetInterface = IOT_MQTT_NETIF_INITIALIZER; - - mqttNetInterface.pDisconnectContext = _startInfo.pConnection; - mqttNetInterface.pSendContext = _startInfo.pConnection; - mqttNetInterface.disconnect = _startInfo.pNetworkInterface->close; - mqttNetInterface.send = _startInfo.pNetworkInterface->send; - - return IotMqtt_Connect( &_mqttConnection, - &mqttNetInterface, + return IotMqtt_Connect( &_startInfo.mqttNetworkInfo, &_startInfo.mqttConnectionInfo, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ), + &_mqttConnection ); } /*-----------------------------------------------------------*/ -bool AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, - IotMqttCallbackInfo_t rejectCallback ) +IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, + IotMqttCallbackInfo_t rejectCallback ) { /* subscribe to two topics: accept and reject. */ IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; @@ -165,13 +138,13 @@ bool AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, subscriptions, 2, 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ); } /*-----------------------------------------------------------*/ -bool AwsIotDefenderInternal_MqttPublish( uint8_t * pData, - size_t dataLength ) +IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, + size_t dataLength ) { IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; @@ -184,7 +157,7 @@ bool AwsIotDefenderInternal_MqttPublish( uint8_t * pData, return IotMqtt_TimedPublish( _mqttConnection, &publishInfo, 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ) == IOT_MQTT_SUCCESS; + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ); } /*-----------------------------------------------------------*/ @@ -193,21 +166,3 @@ void AwsIotDefenderInternal_MqttDisconnect() { IotMqtt_Disconnect( _mqttConnection, false ); } - -/*-----------------------------------------------------------*/ - -void AwsIotDefenderInternal_NetworkClose() -{ - IotNetworkError_t error = _startInfo.pNetworkInterface->close( _startInfo.pConnection ); - - AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -void AwsIotDefenderInternal_NetworkDestroy() -{ - IotNetworkError_t error = _startInfo.pNetworkInterface->destroy( _startInfo.pConnection ); - - AwsIotDefender_Assert( error == IOT_NETWORK_SUCCESS ); -} diff --git a/lib/source/metrics/CMakeLists.txt b/lib/source/metrics/CMakeLists.txt deleted file mode 100644 index 5751e21c2f..0000000000 --- a/lib/source/metrics/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Serializer library source files. -add_library( iotmetrics SHARED - iot_metrics.c ) - -# Library version. -set_target_properties( iotmetrics PROPERTIES VERSION ${PROJECT_VERSION} ) - -# Link required libraries. -target_link_libraries( iotmetrics iotplatform ) \ No newline at end of file diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index fadd87bd18..59d67b48ae 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -65,16 +65,24 @@ /*-----------------------------------------------------------*/ /** - * @brief Determines if an MQTT subscription is safe to remove based on its - * reference count. + * @brief Set the unsubscribed flag of an MQTT subscription. * * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. * @param[in] pMatch Not used. * - * @return `true` if the given subscription has no references; `false` otherwise. + * @return Always returns `true`. */ -static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, - void * pMatch ); +static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, + void * pMatch ); + +/** + * @brief Destroy an MQTT subscription if its reference count is 0. + * + * @param[in] pData The subscription to destroy. This parameter is of type + * `void*` for compatibility with [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ +static void _mqttSubscription_tryDestroy( void * pData ); /** * @brief Decrement the reference count of an MQTT operation and attempt to @@ -89,30 +97,29 @@ static void _mqttOperation_tryDestroy( void * pData ); /** * @brief Create a keep-alive job for an MQTT connection. * - * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT - * server. - * @param[in] pMqttConnection The MQTT connection associated with the keep-alive. + * @param[in] pNetworkInfo User-provided network information for the new + * connection. * @param[in] keepAliveSeconds User-provided keep-alive interval. + * @param[out] pMqttConnection The MQTT connection associated with the keep-alive. * * @return `true` if the keep-alive job was successfully created; `false` otherwise. */ -static bool _createKeepAliveJob( bool awsIotMqttMode, - _mqttConnection_t * pMqttConnection, - uint16_t keepAliveSeconds ); +static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds, + _mqttConnection_t * pMqttConnection ); /** * @brief Creates a new MQTT connection and initializes its members. * * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. - * @param[in] pNetworkInterface User-provided network interface for the new + * @param[in] pNetworkInfo User-provided network information for the new * connection. * @param[in] keepAliveSeconds User-provided keep-alive interval for the new connection. * - * @return Pointer to a newly-allocated MQTT connection on success; `NULL` on - * failure. + * @return Pointer to a newly-created MQTT connection; `NULL` on failure. */ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, - const IotMqttNetIf_t * pNetworkInterface, + const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ); /** @@ -139,21 +146,9 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /*-----------------------------------------------------------*/ -/** - * @brief Ensures that only one CONNECT operation is in-progress at any time. - * - * Because CONNACK contains no data about which CONNECT packet it acknowledges, - * only one CONNECT operation may be in-progress at any time. - */ -static IotMutex_t _connectMutex; - -/*-----------------------------------------------------------*/ - -static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, - void * pMatch ) +static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, + void * pMatch ) { - bool match = false; - /* Because this function is called from a container function, the given link * must never be NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); @@ -165,22 +160,33 @@ static bool _mqttSubscription_shouldRemove( const IotLink_t * pSubscriptionLink, /* Silence warnings about unused parameters. */ ( void ) pMatch; + /* Set the unsubscribed flag. */ + pSubscription->unsubscribed = true; + + return true; +} + +/*-----------------------------------------------------------*/ + +static void _mqttSubscription_tryDestroy( void * pData ) +{ + _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; + /* Reference count must not be negative. */ IotMqtt_Assert( pSubscription->references >= 0 ); - /* Check if any subscription callbacks are using this subscription. */ - if( pSubscription->references > 0 ) + /* Unsubscribed flag should be set. */ + IotMqtt_Assert( pSubscription->unsubscribed == true ); + + /* Free the subscription if it has no references. */ + if( pSubscription->references == 0 ) { - /* Set the unsubscribed flag, but do not remove the subscription yet. */ - pSubscription->unsubscribed = true; + IotMqtt_FreeSubscription( pSubscription ); } else { - /* No references for this subscription; it can be removed. */ - match = true; + _EMPTY_ELSE_MARKER; } - - return match; } /*-----------------------------------------------------------*/ @@ -202,59 +208,43 @@ static void _mqttOperation_tryDestroy( void * pData ) /*-----------------------------------------------------------*/ -static bool _createKeepAliveJob( bool awsIotMqttMode, - _mqttConnection_t * pMqttConnection, - uint16_t keepAliveSeconds ) +static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds, + _mqttConnection_t * pMqttConnection ) { bool status = true; IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; IotTaskPoolError_t jobStatus = IOT_TASKPOOL_SUCCESS; + /* Network information is not used when MQTT packet serializers are disabled. */ + ( void ) pNetworkInfo; + /* Default PINGREQ serializer function. */ IotMqttError_t ( * serializePingreq )( uint8_t **, size_t * ) = _IotMqtt_SerializePingreq; - /* AWS IoT service limits set minimum and maximum values for keep-alive interval. - * Adjust the user-provided keep-alive interval based on these requirements. */ - if( awsIotMqttMode == true ) - { - if( keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) - { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; - } - else if( keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) - { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; - } - else if( keepAliveSeconds == 0 ) - { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; - } - else - { - _EMPTY_ELSE_MARKER; - } - } - else - { - _EMPTY_ELSE_MARKER; - } - /* Convert the keep-alive interval to milliseconds. */ pMqttConnection->keepAliveMs = keepAliveSeconds * 1000; pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; /* Choose a PINGREQ serializer function. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.pingreq != NULL ) + if( pNetworkInfo->pMqttSerializer != NULL ) { - serializePingreq = pMqttConnection->network.serialize.pingreq; + if( pNetworkInfo->pMqttSerializer->serialize.pingreq != NULL ) + { + serializePingreq = pNetworkInfo->pMqttSerializer->serialize.pingreq; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Generate a PINGREQ packet. */ serializeStatus = serializePingreq( &( pMqttConnection->pPingreqPacket ), @@ -296,39 +286,36 @@ static bool _createKeepAliveJob( bool awsIotMqttMode, /*-----------------------------------------------------------*/ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, - const IotMqttNetIf_t * pNetworkInterface, + const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ) { _IOT_FUNCTION_ENTRY( bool, true ); + _mqttConnection_t * pMqttConnection = NULL; bool referencesMutexCreated = false, subscriptionMutexCreated = false; - _mqttConnection_t * pNewMqttConnection = NULL; - /* Allocate memory to store data for the new MQTT connection. */ - pNewMqttConnection = ( _mqttConnection_t * ) - IotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); + /* Allocate memory for the new MQTT connection. */ + pMqttConnection = IotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); - if( pNewMqttConnection == NULL ) + if( pMqttConnection == NULL ) { - IotLogError( "Failed to allocate memory for new MQTT connection." ); + IotLogError( "Failed to allocate memory for new connection." ); _IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; - } - - /* Clear the MQTT connection, then copy the MQTT server mode and network - * interface. */ - ( void ) memset( pNewMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); - pNewMqttConnection->awsIotMqttMode = awsIotMqttMode; - pNewMqttConnection->network = *pNetworkInterface; + /* Clear the MQTT connection, then copy the MQTT server mode and network + * interface. */ + ( void ) memset( pMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); + pMqttConnection->awsIotMqttMode = awsIotMqttMode; + pMqttConnection->pNetworkInterface = pNetworkInfo->pNetworkInterface; - /* Start a new MQTT connection with a reference count of 1. */ - pNewMqttConnection->references = 1; + /* Start a new MQTT connection with a reference count of 1. */ + pMqttConnection->references = 1; + } /* Create the references mutex for a new connection. It is a recursive mutex. */ - referencesMutexCreated = IotMutex_Create( &( pNewMqttConnection->referencesMutex ), true ); + referencesMutexCreated = IotMutex_Create( &( pMqttConnection->referencesMutex ), true ); if( referencesMutexCreated == false ) { @@ -342,7 +329,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } /* Create the subscription mutex for a new connection. */ - subscriptionMutexCreated = IotMutex_Create( &( pNewMqttConnection->subscriptionMutex ), false ); + subscriptionMutexCreated = IotMutex_Create( &( pMqttConnection->subscriptionMutex ), false ); if( subscriptionMutexCreated == false ) { @@ -356,16 +343,42 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } /* Create the new connection's subscription and operation lists. */ - IotListDouble_Create( &( pNewMqttConnection->subscriptionList ) ); - IotListDouble_Create( &( pNewMqttConnection->pendingProcessing ) ); - IotListDouble_Create( &( pNewMqttConnection->pendingResponse ) ); + IotListDouble_Create( &( pMqttConnection->subscriptionList ) ); + IotListDouble_Create( &( pMqttConnection->pendingProcessing ) ); + IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); + + /* AWS IoT service limits set minimum and maximum values for keep-alive interval. + * Adjust the user-provided keep-alive interval based on these requirements. */ + if( awsIotMqttMode == true ) + { + if( keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; + } + else if( keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + else if( keepAliveSeconds == 0 ) + { + keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } /* Check if keep-alive is active for this connection. */ if( keepAliveSeconds != 0 ) { - if( _createKeepAliveJob( awsIotMqttMode, - pNewMqttConnection, - keepAliveSeconds ) == false ) + if( _createKeepAliveJob( pNetworkInfo, + keepAliveSeconds, + pMqttConnection ) == false ) { _IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -386,7 +399,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { if( subscriptionMutexCreated == true ) { - IotMutex_Destroy( &( pNewMqttConnection->subscriptionMutex ) ); + IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); } else { @@ -395,18 +408,17 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, if( referencesMutexCreated == true ) { - IotMutex_Destroy( &( pNewMqttConnection->referencesMutex ) ); + IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); } else { _EMPTY_ELSE_MARKER; } - if( pNewMqttConnection != NULL ) + if( pMqttConnection != NULL ) { - IotMqtt_FreeConnection( pNewMqttConnection ); - - pNewMqttConnection = NULL; + IotMqtt_FreeConnection( pMqttConnection ); + pMqttConnection = NULL; } else { @@ -418,13 +430,15 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, _EMPTY_ELSE_MARKER; } - return pNewMqttConnection; + return pMqttConnection; } /*-----------------------------------------------------------*/ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) { + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; + /* Clean up keep-alive if still allocated. */ if( pMqttConnection->keepAliveMs != 0 ) { @@ -455,18 +469,39 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) /* Remove all subscriptions. */ IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _mqttSubscription_shouldRemove, + _mqttSubscription_setUnsubscribe, NULL, - IotMqtt_FreeSubscription, + _mqttSubscription_tryDestroy, offsetof( _mqttSubscription_t, link ) ); IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - /* Destroy mutexes and free connection. */ + /* Destroy mutexes. */ IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); - IotMqtt_FreeConnection( pMqttConnection ); + + /* An MQTT connection that owns its network connection should destroy it. */ + if( pMqttConnection->ownNetworkConnection == true ) + { + networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "Failed to destroy network connection." ); + } + else + { + IotLogInfo( "Network connection destroyed." ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } IotLogDebug( "(MQTT connection %p) Connection destroyed.", pMqttConnection ); + + /* Free connection. */ + IotMqtt_FreeConnection( pMqttConnection ); } /*-----------------------------------------------------------*/ @@ -481,7 +516,6 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pSubscriptionOperation = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; /* Subscription serializer function. */ IotMqttError_t ( * serializeSubscription )( const IotMqttSubscription_t *, @@ -496,7 +530,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Check that all elements in the subscription list are valid. */ if( _IotMqtt_ValidateSubscriptionList( operation, - pMqttConnection->awsIotMqttMode, + mqttConnection->awsIotMqttMode, pSubscriptionList, subscriptionCount ) == false ) { @@ -533,36 +567,50 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, serializeSubscription = _IotMqtt_SerializeSubscribe; #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.subscribe != NULL ) + if( mqttConnection->pSerializer != NULL ) { - serializeSubscription = pMqttConnection->network.serialize.subscribe; + if( mqttConnection->pSerializer->serialize.subscribe != NULL ) + { + serializeSubscription = mqttConnection->pSerializer->serialize.subscribe; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ } else { serializeSubscription = _IotMqtt_SerializeUnsubscribe; #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.unsubscribe != NULL ) + if( mqttConnection->pSerializer != NULL ) { - serializeSubscription = pMqttConnection->network.serialize.unsubscribe; + if( mqttConnection->pSerializer->serialize.unsubscribe != NULL ) + { + serializeSubscription = mqttConnection->pSerializer->serialize.unsubscribe; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ } /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ if( operation == IOT_MQTT_UNSUBSCRIBE ) { - _IotMqtt_RemoveSubscriptionByTopicFilter( pMqttConnection, + _IotMqtt_RemoveSubscriptionByTopicFilter( mqttConnection, pSubscriptionList, subscriptionCount ); } @@ -572,7 +620,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Create a subscription operation. */ - status = _IotMqtt_CreateOperation( pMqttConnection, + status = _IotMqtt_CreateOperation( mqttConnection, flags, pCallbackInfo, &pSubscriptionOperation ); @@ -606,7 +654,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Add the subscription list for a SUBSCRIBE. */ if( operation == IOT_MQTT_SUBSCRIBE ) { - status = _IotMqtt_AddSubscriptions( pMqttConnection, + status = _IotMqtt_AddSubscriptions( mqttConnection, pSubscriptionOperation->packetIdentifier, pSubscriptionList, subscriptionCount ); @@ -631,12 +679,12 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( status != IOT_MQTT_SUCCESS ) { IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", - pMqttConnection, + mqttConnection, IotMqtt_OperationType( operation ) ); if( operation == IOT_MQTT_SUBSCRIBE ) { - _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, + _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, pSubscriptionOperation->packetIdentifier, -1 ); } @@ -665,7 +713,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, status = IOT_MQTT_STATUS_PENDING; IotLogInfo( "(MQTT connection %p) %s operation scheduled.", - pMqttConnection, + mqttConnection, IotMqtt_OperationType( operation ) ); } @@ -753,120 +801,65 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection IotMqttError_t IotMqtt_Init( void ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - bool taskPoolCreated = false, connectMutexCreated = false; - const IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM; - - /* Create MQTT library task pool. */ - if( IotTaskPool_Create( &taskPoolInfo, &_IotMqttTaskPool ) != IOT_TASKPOOL_SUCCESS ) - { - IotLogError( "Failed to initialize MQTT library task pool." ); - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); - } - else - { - taskPoolCreated = true; - } - - /* Create CONNECT mutex. */ - connectMutexCreated = IotMutex_Create( &( _connectMutex ), false ); - - if( connectMutexCreated == false ) - { - IotLogError( "Failed to initialize MQTT library connect mutex." ); - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); - } - else - { - _EMPTY_ELSE_MARKER; - } + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Initialize MQTT serializer. */ if( _IotMqtt_InitSerialize() != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to initialize MQTT library serializer. " ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + status = IOT_MQTT_INIT_FAILED; } else { - _EMPTY_ELSE_MARKER; + IotLogInfo( "MQTT library successfully initialized." ); } - IotLogInfo( "MQTT library successfully initialized." ); - - /* Clean up if this function failed. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status != IOT_MQTT_SUCCESS ) - { - if( taskPoolCreated == true ) - { - IotTaskPool_Destroy( &( _IotMqttTaskPool ) ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - if( connectMutexCreated == true ) - { - IotMutex_Destroy( &( _connectMutex ) ); - } - else - { - _EMPTY_ELSE_MARKER; - } - } - else - { - _EMPTY_ELSE_MARKER; - } - - _IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ void IotMqtt_Cleanup() { - /* Clean up MQTT library task pool. */ - IotTaskPool_Destroy( &_IotMqttTaskPool ); - /* Clean up MQTT serializer. */ _IotMqtt_CleanupSerialize(); - /* Clean up CONNECT mutex. */ - IotMutex_Destroy( &( _connectMutex ) ); - IotLogInfo( "MQTT library cleanup done." ); } /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, - const IotMqttNetIf_t * pNetworkInterface, +IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, const IotMqttConnectInfo_t * pConnectInfo, - uint64_t timeoutMs ) + uint64_t timeoutMs, + IotMqttConnection_t * pMqttConnection ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + bool networkCreated = false, ownNetworkConnection = false; + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - _mqttConnection_t * pNewMqttConnection = NULL; + void * pNetworkConnection = NULL; _mqttOperation_t * pConnectOperation = NULL; + _mqttConnection_t * pNewMqttConnection = NULL; /* Default CONNECT serializer function. */ IotMqttError_t ( * serializeConnect )( const IotMqttConnectInfo_t *, uint8_t **, size_t * ) = _IotMqtt_SerializeConnect; - /* Validate network interface and connect info. */ - if( _IotMqtt_ValidateNetIf( pNetworkInterface ) == false ) + /* Network info must not be NULL. */ + if( pNetworkInfo == NULL ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - else if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) + else + { + _EMPTY_ELSE_MARKER; + } + + /* Validate network interface and connect info. */ + if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } @@ -928,23 +921,38 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, _EMPTY_ELSE_MARKER; } - /* Choose a CONNECT serializer function. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNetworkInterface->serialize.connect != NULL ) + /* Create a new MQTT connection if requested. Otherwise, copy the existing + * network connection. */ + if( pNetworkInfo->createNetworkConnection == true ) + { + networkStatus = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->pNetworkServerInfo, + pNetworkInfo->pNetworkCredentialInfo, + &pNetworkConnection ); + + if( networkStatus == IOT_NETWORK_SUCCESS ) { - serializeConnect = pNetworkInterface->serialize.connect; + networkCreated = true; + + /* This MQTT connection owns the network connection it created and + * should destroy it on cleanup. */ + ownNetworkConnection = true; } else { - _EMPTY_ELSE_MARKER; + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); } - #endif + } + else + { + pNetworkConnection = pNetworkInfo->pNetworkConnection; + networkCreated = true; + } IotLogInfo( "Establishing new MQTT connection." ); - /* Allocate memory to store data for the new MQTT connection. */ + /* Initialize a new MQTT connection object. */ pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, - pNetworkInterface, + pNetworkInfo, pConnectInfo->keepAliveSeconds ); if( pNewMqttConnection == NULL ) @@ -952,6 +960,29 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else + { + /* Set the network connection associated with the MQTT connection. */ + pNewMqttConnection->pNetworkConnection = pNetworkConnection; + pNewMqttConnection->ownNetworkConnection = ownNetworkConnection; + + /* Set the MQTT packet serializer overrides. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + pNewMqttConnection->pSerializer = pNetworkInfo->pMqttSerializer; + #endif + } + + /* Set the MQTT receive callback. */ + networkStatus = pNewMqttConnection->pNetworkInterface->setReceiveCallback( pNetworkConnection, + IotMqtt_ReceiveCallback, + pNewMqttConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogError( "Failed to set MQTT network receive callback." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + } + else { _EMPTY_ELSE_MARKER; } @@ -1006,6 +1037,25 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, _EMPTY_ELSE_MARKER; } + /* Choose a CONNECT serializer function. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pNewMqttConnection->pSerializer != NULL ) + { + if( pNewMqttConnection->pSerializer->serialize.connect != NULL ) + { + serializeConnect = pNewMqttConnection->pSerializer->serialize.connect; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ status = serializeConnect( pConnectInfo, &( pConnectOperation->pMqttPacket ), @@ -1024,12 +1074,6 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, IotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); IotMqtt_Assert( pConnectOperation->packetSize > 0 ); - /* Set the output parameter so it may be used by the network receive callback. */ - *pMqttConnection = pNewMqttConnection; - - /* Prevent another CONNECT operation from using the network. */ - IotMutex_Lock( &_connectMutex ); - /* Add the CONNECT operation to the send queue for network transmission. */ status = _IotMqtt_ScheduleOperation( pConnectOperation, _IotMqtt_ProcessSend, @@ -1050,9 +1094,6 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, pConnectOperation = NULL; } - /* Unlock the CONNECT mutex. */ - IotMutex_Unlock( &_connectMutex ); - /* When a connection is successfully established, schedule keep-alive job. */ if( status == IOT_MQTT_SUCCESS ) { @@ -1061,7 +1102,7 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, { IotLogDebug( "Scheduling first MQTT keep-alive job." ); - taskPoolStatus = IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), + taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, &( pNewMqttConnection->keepAliveJob ), pNewMqttConnection->nextKeepAliveMs ); @@ -1091,6 +1132,25 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, IotLogError( "Failed to establish new MQTT connection, error %s.", IotMqtt_strerror( status ) ); + /* The network connection must be closed if it was created. */ + if( networkCreated == true ) + { + networkStatus = pNetworkInfo->pNetworkInterface->close( pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "Failed to close network connection." ); + } + else + { + IotLogInfo( "Network connection closed on error." ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } + if( pConnectOperation != NULL ) { _IotMqtt_DestroyOperation( pConnectOperation ); @@ -1103,7 +1163,6 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, if( pNewMqttConnection != NULL ) { _destroyMqttConnection( pNewMqttConnection ); - *pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } else { @@ -1112,7 +1171,10 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, } else { - IotLogInfo( "New MQTT connection %p established.", pNewMqttConnection ); + IotLogInfo( "New MQTT connection %p established.", pMqttConnection ); + + /* Set the output parameter. */ + *pMqttConnection = pNewMqttConnection; } _IOT_FUNCTION_CLEANUP_END(); @@ -1121,29 +1183,28 @@ IotMqttError_t IotMqtt_Connect( IotMqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, - bool cleanupOnly ) + uint32_t flags ) { bool disconnected = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; _mqttOperation_t * pDisconnectOperation = NULL; - IotLogInfo( "(MQTT connection %p) Disconnecting connection.", pMqttConnection ); + IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); /* Read the connection status. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - disconnected = pMqttConnection->disconnected; - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + IotMutex_Lock( &( mqttConnection->referencesMutex ) ); + disconnected = mqttConnection->disconnected; + IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" - * option is false. */ + * flag is not set. */ if( disconnected == false ) { - if( cleanupOnly == false ) + if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == 0 ) { /* Create a DISCONNECT operation. This function blocks until the DISCONNECT * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ - status = _IotMqtt_CreateOperation( pMqttConnection, + status = _IotMqtt_CreateOperation( mqttConnection, IOT_MQTT_FLAG_WAITABLE, NULL, &pDisconnectOperation ); @@ -1165,15 +1226,22 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, size_t * ) = _IotMqtt_SerializeDisconnect; #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.disconnect != NULL ) + if( mqttConnection->pSerializer != NULL ) { - serializeDisconnect = pMqttConnection->network.serialize.disconnect; + if( mqttConnection->pSerializer->serialize.disconnect != NULL ) + { + serializeDisconnect = mqttConnection->pSerializer->serialize.disconnect; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Generate a DISCONNECT packet. */ status = serializeDisconnect( &( pDisconnectOperation->pMqttPacket ), @@ -1196,7 +1264,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, 0 ) != IOT_MQTT_SUCCESS ) { IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", - pMqttConnection ); + mqttConnection ); _IotMqtt_DestroyOperation( pDisconnectOperation ); } else @@ -1209,7 +1277,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * or NETWORK ERROR. */ if( status == IOT_MQTT_SUCCESS ) { - IotLogInfo( "(MQTT connection %p) Connection disconnected.", pMqttConnection ); + IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); } else { @@ -1217,7 +1285,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, ( status == IOT_MQTT_NETWORK_ERROR ) ); IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", - pMqttConnection, + mqttConnection, IotMqtt_strerror( status ) ); } } @@ -1238,27 +1306,27 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, } /* Close the underlying network connection. This also cleans up keep-alive. */ - _IotMqtt_CloseNetworkConnection( pMqttConnection ); + _IotMqtt_CloseNetworkConnection( mqttConnection ); /* Check if the connection may be destroyed. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotMutex_Lock( &( mqttConnection->referencesMutex ) ); /* At this point, the connection should be marked disconnected. */ - IotMqtt_Assert( pMqttConnection->disconnected == true ); + IotMqtt_Assert( mqttConnection->disconnected == true ); /* Attempt cancel and destroy each operation in the connection's lists. */ - IotListDouble_RemoveAll( &( pMqttConnection->pendingProcessing ), + IotListDouble_RemoveAll( &( mqttConnection->pendingProcessing ), _mqttOperation_tryDestroy, offsetof( _mqttOperation_t, link ) ); - IotListDouble_RemoveAll( &( pMqttConnection->pendingResponse ), + IotListDouble_RemoveAll( &( mqttConnection->pendingResponse ), _mqttOperation_tryDestroy, offsetof( _mqttOperation_t, link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); /* Decrement the connection reference count and destroy it if possible. */ - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + _IotMqtt_DecrementConnectionReferences( mqttConnection ); } /*-----------------------------------------------------------*/ @@ -1383,16 +1451,17 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pPublishOperation = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; + uint8_t ** pPacketIdentifierHigh = NULL; /* Default PUBLISH serializer function. */ IotMqttError_t ( * serializePublish )( const IotMqttPublishInfo_t *, uint8_t **, size_t *, - uint16_t * ) = _IotMqtt_SerializePublish; + uint16_t *, + uint8_t ** ) = _IotMqtt_SerializePublish; /* Check that the PUBLISH information is valid. */ - if( _IotMqtt_ValidatePublish( pMqttConnection->awsIotMqttMode, + if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, pPublishInfo ) == false ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); @@ -1456,7 +1525,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } /* Create a PUBLISH operation. */ - status = _IotMqtt_CreateOperation( pMqttConnection, + status = _IotMqtt_CreateOperation( mqttConnection, flags, pCallbackInfo, &pPublishOperation ); @@ -1476,21 +1545,39 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* Choose a PUBLISH serializer function. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.publish != NULL ) + if( mqttConnection->pSerializer != NULL ) { - serializePublish = pMqttConnection->network.serialize.publish; + if( mqttConnection->pSerializer->serialize.publish != NULL ) + { + serializePublish = mqttConnection->pSerializer->serialize.publish; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ + if( mqttConnection->awsIotMqttMode == true ) + { + pPacketIdentifierHigh = &( pPublishOperation->pPacketIdentifierHigh ); + } + else + { + _EMPTY_ELSE_MARKER; + } /* Generate a PUBLISH packet from pPublishInfo. */ status = serializePublish( pPublishInfo, &( pPublishOperation->pMqttPacket ), &( pPublishOperation->packetSize ), - &( pPublishOperation->packetIdentifier ) ); + &( pPublishOperation->packetIdentifier ), + pPacketIdentifierHigh ); if( status != IOT_MQTT_SUCCESS ) { @@ -1549,7 +1636,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, if( status != IOT_MQTT_SUCCESS ) { IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", - pMqttConnection ); + mqttConnection ); /* Clear the previously set (and now invalid) reference. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) @@ -1602,7 +1689,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } IotLogInfo( "(MQTT connection %p) MQTT PUBLISH operation queued.", - pMqttConnection ); + mqttConnection ); } _IOT_FUNCTION_CLEANUP_END(); @@ -1666,7 +1753,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, uint64_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; + _mqttOperation_t * pOperation = reference; _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; /* Validate the given reference. */ @@ -1723,11 +1810,9 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, pMqttConnection, pOperation ); - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, pOperation->packetIdentifier, -1 ); - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); } else { diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index f5374e7901..526c54691d 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -32,6 +32,9 @@ /* Standard includes. */ #include +/* Error handling include. */ +#include "private/iot_error.h" + /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -40,6 +43,41 @@ /*-----------------------------------------------------------*/ +/** + * @brief Check if an incoming packet type is valid. + * + * @param[in] packetType The packet type to check. + * + * @return `true` if the packet type is valid; `false` otherwise. + */ +static bool _incomingPacketValid( uint8_t packetType ); + +/** + * @brief Get an incoming MQTT packet from the network. + * + * @param[in] pNetworkConnection Network connection to use for receive, which + * may be different from the network connection associated with the MQTT connection. + * @param[in] pMqttConnection The associated MQTT connection. + * @param[out] pIncomingPacket Output parameter for the incoming packet. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + +/** + * @brief Deserialize a packet received from the network. + * + * @param[in] pMqttConnection The associated MQTT connection. + * @param[in] pIncomingPacket The packet received from the network. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY, #IOT_MQTT_NETWORK_ERROR, + * #IOT_MQTT_SCHEDULING_ERROR, #IOT_MQTT_BAD_RESPONSE, or #IOT_MQTT_SERVER_REFUSED. + */ +static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + /** * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. * @@ -51,526 +89,607 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ -static void _sendPuback( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier ) +static bool _incomingPacketValid( uint8_t packetType ) { - IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; - uint8_t * pPuback = NULL; - size_t pubackSize = 0, bytesSent = 0; + bool status = true; - /* Default PUBACK serializer and free packet functions. */ - IotMqttError_t ( * serializePuback )( uint16_t, - uint8_t **, - size_t * ) = _IotMqtt_SerializePuback; - void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; - - /* Increment the reference count for the MQTT connection. */ - if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) + /* Check packet type. Mask out lower bits to ignore flags. */ + switch( packetType & 0xf0 ) { - IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", - pMqttConnection, - packetIdentifier ); + /* Valid incoming packet types. */ + case _MQTT_PACKET_TYPE_CONNACK: + case _MQTT_PACKET_TYPE_PUBLISH: + case _MQTT_PACKET_TYPE_PUBACK: + case _MQTT_PACKET_TYPE_SUBACK: + case _MQTT_PACKET_TYPE_UNSUBACK: + case _MQTT_PACKET_TYPE_PINGRESP: + break; + + /* Any other packet type is invalid. */ + default: + status = false; + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ - /* Choose PUBACK serializer and free packet functions. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.puback != NULL ) +static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) +{ + _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t dataBytesRead = 0; + + /* Default functions for retrieving packet type and length. */ + uint8_t ( * getPacketType )( void *, + const IotNetworkInterface_t * ) = _IotMqtt_GetPacketType; + size_t ( * getRemainingLength )( void *, + const IotNetworkInterface_t * ) = _IotMqtt_GetRemainingLength; + + /* No buffer for remaining data should be allocated. */ + IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); + IotMqtt_Assert( pIncomingPacket->remainingLength == 0 ); + + /* Choose packet type and length functions. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->getPacketType != NULL ) { - serializePuback = pMqttConnection->network.serialize.puback; + getPacketType = pMqttConnection->pSerializer->getPacketType; } else { _EMPTY_ELSE_MARKER; } - #endif - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.freePacket != NULL ) + + if( pMqttConnection->pSerializer->getRemainingLength != NULL ) { - freePacket = pMqttConnection->network.freePacket; + getRemainingLength = pMqttConnection->pSerializer->getRemainingLength; } else { _EMPTY_ELSE_MARKER; } - #endif + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Generate a PUBACK packet from the packet identifier. */ - serializeStatus = serializePuback( packetIdentifier, - &pPuback, - &pubackSize ); + /* Read the packet type, which is the first byte available. */ + pIncomingPacket->type = getPacketType( pNetworkConnection, + pMqttConnection->pNetworkInterface ); - if( serializeStatus != IOT_MQTT_SUCCESS ) + /* Check that the incoming packet type is valid. */ + if( _incomingPacketValid( pIncomingPacket->type ) == false ) + { + IotLogError( "(MQTT connection %p) Unknown packet type %02x received.", + pMqttConnection, + pIncomingPacket->type ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Read the remaining length. */ + pIncomingPacket->remainingLength = getRemainingLength( pNetworkConnection, + pMqttConnection->pNetworkInterface ); + + if( pIncomingPacket->remainingLength == _MQTT_REMAINING_LENGTH_INVALID ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Allocate a buffer for the remaining data and read the data. */ + if( pIncomingPacket->remainingLength > 0 ) + { + pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); + + if( pIncomingPacket->pRemainingData == NULL ) { - IotLogWarn( "(MQTT connection %p) Failed to generate PUBACK packet for " - "received PUBLISH %hu.", - pMqttConnection, - packetIdentifier ); + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { - bytesSent = pMqttConnection->network.send( pMqttConnection->network.pSendContext, - pPuback, - pubackSize ); + _EMPTY_ELSE_MARKER; + } - if( bytesSent != pubackSize ) - { - IotLogWarn( "(MQTT connection %p) Failed to send PUBACK for received" - " PUBLISH %hu.", - pMqttConnection, - packetIdentifier ); - } - else - { - IotLogDebug( "(MQTT connection %p) PUBACK for received PUBLISH %hu sent.", - pMqttConnection, - packetIdentifier ); - } + dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, + pIncomingPacket->pRemainingData, + pIncomingPacket->remainingLength ); - freePacket( pPuback ); + if( dataBytesRead != pIncomingPacket->remainingLength ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Clean up on error. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + if( status != IOT_MQTT_SUCCESS ) + { + if( pIncomingPacket->pRemainingData != NULL ) + { + IotMqtt_FreeMessage( pIncomingPacket->pRemainingData ); + } + else + { + _EMPTY_ELSE_MARKER; + } } else { - IotLogWarn( "(MQTT connection %p) Connection is closed, PUBACK for received" - " PUBLISH %hu will not be sent.", - pMqttConnection, - packetIdentifier ); + _EMPTY_ELSE_MARKER; } + + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ -int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, - void * pConnection, - const uint8_t * pReceivedData, - size_t dataLength, - size_t offset, - void ( *freeReceivedData )( void * ) ) +static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) { - size_t bytesProcessed = 0, totalBytesProcessed = 0, remainingDataLength = 0; - _mqttConnection_t * pConnectionInfo = *( ( _mqttConnection_t ** ) ( pMqttConnection ) ); IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - uint16_t packetIdentifier = 0; - const uint8_t * pNextPacket = pReceivedData; - _mqttOperation_t * pOperation = NULL, * pFirstPublish = NULL, * pLastPublish = NULL; + _mqttOperation_t * pOperation = NULL; - /* Network connection parameter is ignored. */ - ( void ) pConnection; + /* Deserializer function. */ + IotMqttError_t ( * deserialize )( _mqttPacket_t * ) = NULL; - /* Choose a packet type decoder function. */ - uint8_t ( * getPacketType )( const uint8_t *, - size_t ) = _IotMqtt_GetPacketType; + /* A buffer for remaining data must be allocated if remaining length is not 0. */ + IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0 ) == + ( pIncomingPacket->pRemainingData != NULL ) ); - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.getPacketType != NULL ) - { - getPacketType = pConnectionInfo->network.getPacketType; - } - #endif + /* Only valid packets should be given to this function. */ + IotMqtt_Assert( _incomingPacketValid( pIncomingPacket->type ) == true ); - /* Ensure that offset is smaller than dataLength. */ - if( offset >= dataLength ) + /* Mask out the low bits of packet type to ignore flags. */ + switch( ( pIncomingPacket->type & 0xf0 ) ) { - return 0; - } + case _MQTT_PACKET_TYPE_CONNACK: + IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); - /* Adjust the packet pointer based on the offset. */ - pNextPacket += offset; - remainingDataLength = dataLength - offset; + /* Choose CONNACK deserializer. */ + deserialize = _IotMqtt_DeserializeConnack; - /* Process the stream of data until the entire stream is proccessed or an - * incomplete packet is found. */ - while( ( totalBytesProcessed < remainingDataLength ) && ( status != IOT_MQTT_BAD_RESPONSE ) ) - { - switch( getPacketType( pNextPacket, remainingDataLength - totalBytesProcessed ) ) - { - case _MQTT_PACKET_TYPE_CONNACK: - IotLog_PrintBuffer( "CONNACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the CONNACK. */ - IotMqttError_t ( * deserializeConnack )( const uint8_t *, - size_t, - size_t * ) = _IotMqtt_DeserializeConnack; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.connack != NULL ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.connack != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.connack; + } + else { - deserializeConnack = pConnectionInfo->network.deserialize.connack; + _EMPTY_ELSE_MARKER; } - #endif + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - status = deserializeConnack( pNextPacket, - remainingDataLength - totalBytesProcessed, - &bytesProcessed ); + /* Deserialize CONNACK and notify of result. */ + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_CONNECT, + NULL ); - /* If a complete CONNACK was deserialized, check if there's an - * in-progress CONNECT operation. */ - if( bytesProcessed > 0 ) - { - pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_CONNECT, NULL ); + if( pOperation != NULL ) + { + pOperation->status = status; + _IotMqtt_Notify( pOperation ); + } + + break; + + case _MQTT_PACKET_TYPE_PUBLISH: + IotLogDebug( "(MQTT connection %p) PUBLISH in data stream.", pMqttConnection ); + + /* Allocate memory to handle the incoming PUBLISH. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); + status = IOT_MQTT_NO_MEMORY; - if( pOperation != NULL ) + break; + } + + /* Set the members of the incoming PUBLISH operation. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + pOperation->incomingPublish = true; + pOperation->pMqttConnection = pMqttConnection; + + pIncomingPacket->pIncomingPublish = pOperation; + + /* Choose a PUBLISH deserializer. */ + deserialize = _IotMqtt_DeserializePublish; + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->deserialize.publish != NULL ) { - pOperation->status = status; - _IotMqtt_Notify( pOperation ); + deserialize = pMqttConnection->pSerializer->deserialize.publish; } } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - break; + /* Deserialize incoming PUBLISH. */ + status = deserialize( pIncomingPacket ); - case _MQTT_PACKET_TYPE_PUBLISH: - IotLog_PrintBuffer( "PUBLISH in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Send a PUBACK for QoS 1 PUBLISH. */ + if( pOperation->publishInfo.qos == IOT_MQTT_QOS_1 ) + { + _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); + } + else + { + _EMPTY_ELSE_MARKER; + } - /* Allocate memory to handle the incoming PUBLISH. */ - pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ + pOperation->pReceivedData = pIncomingPacket->pRemainingData; + pIncomingPacket->pRemainingData = NULL; - if( pOperation == NULL ) + /* Increment the MQTT connection reference count before scheduling an + * incoming PUBLISH. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) { - IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); - bytesProcessed = 0; + /* Schedule PUBLISH for callback invocation. */ + status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessIncomingPublish, 0 ); + } + else + { + status = IOT_MQTT_NETWORK_ERROR; + } + } - break; + /* Free PUBLISH operation on error. */ + if( status != IOT_MQTT_SUCCESS ) + { + /* Check ownership of the received MQTT packet. */ + if( pOperation->pReceivedData != NULL ) + { + /* Retrieve the pointer MQTT packet pointer so it may be freed later. */ + IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); + pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->pReceivedData; + } + else + { + /* The received MQTT packet must be part of the incoming + * packet structure. */ + IotMqtt_Assert( pIncomingPacket->pRemainingData != NULL ); } - /* Set the members of the incoming PUBLISH operation. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - pOperation->incomingPublish = true; - pOperation->pMqttConnection = pConnectionInfo; + IotMqtt_Assert( pOperation != NULL ); + IotMqtt_FreeOperation( pOperation ); + } - /* Deserialize the PUBLISH into an IotMqttPublishInfo_t. */ - IotMqttError_t ( * deserializePublish )( const uint8_t *, - size_t, - IotMqttPublishInfo_t *, - uint16_t *, - size_t * ) = _IotMqtt_DeserializePublish; + break; - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.publish != NULL ) - { - deserializePublish = pConnectionInfo->network.deserialize.publish; - } - #endif + case _MQTT_PACKET_TYPE_PUBACK: + IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); - status = deserializePublish( pNextPacket, - remainingDataLength - totalBytesProcessed, - &( pOperation->publishInfo ), - &packetIdentifier, - &bytesProcessed ); + /* Choose PUBACK deserializer. */ + deserialize = _IotMqtt_DeserializePuback; - /* If a complete PUBLISH was deserialized, process it. */ - if( ( bytesProcessed > 0 ) && ( status == IOT_MQTT_SUCCESS ) ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) { - /* If a QoS 1 PUBLISH was received, send a PUBACK. */ - if( pOperation->publishInfo.qos == IOT_MQTT_QOS_1 ) + if( pMqttConnection->pSerializer->deserialize.puback != NULL ) { - _sendPuback( pConnectionInfo, packetIdentifier ); - } - - /* Change the first and last PUBLISH pointers. */ - if( pFirstPublish == NULL ) - { - pFirstPublish = pOperation; - pLastPublish = pOperation; + deserialize = pMqttConnection->pSerializer->deserialize.puback; } else { - pLastPublish->pNextPublish = pOperation; - pLastPublish = pOperation; + _EMPTY_ELSE_MARKER; } } else { - /* Free the PUBLISH operation here if the PUBLISH packet isn't - * valid. */ - IotMqtt_FreeOperation( pOperation ); + _EMPTY_ELSE_MARKER; } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - break; + /* Deserialize PUBACK and notify of result. */ + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_PUBLISH_TO_SERVER, + &( pIncomingPacket->packetIdentifier ) ); - case _MQTT_PACKET_TYPE_PUBACK: - IotLog_PrintBuffer( "PUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + if( pOperation != NULL ) + { + pOperation->status = status; + _IotMqtt_Notify( pOperation ); + } - /* Deserialize the PUBACK to get the packet identifier. */ - IotMqttError_t ( * deserializePuback )( const uint8_t *, - size_t, - uint16_t *, - size_t * ) = _IotMqtt_DeserializePuback; + break; - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.puback != NULL ) - { - deserializePuback = pConnectionInfo->network.deserialize.puback; - } - #endif + case _MQTT_PACKET_TYPE_SUBACK: + IotLogDebug( "(MQTT connection %p) SUBACK in data stream.", pMqttConnection ); - status = deserializePuback( pNextPacket, - remainingDataLength - totalBytesProcessed, - &packetIdentifier, - &bytesProcessed ); + /* Choose SUBACK deserializer. */ + deserialize = _IotMqtt_DeserializeSuback; - /* If a complete PUBACK packet was deserialized, find an in-progress - * PUBLISH with a matching client identifier. */ - if( bytesProcessed > 0 ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) { - pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_PUBLISH_TO_SERVER, &packetIdentifier ); - - if( pOperation != NULL ) + if( pMqttConnection->pSerializer->deserialize.suback != NULL ) + { + deserialize = pMqttConnection->pSerializer->deserialize.suback; + } + else { - pOperation->status = status; - _IotMqtt_Notify( pOperation ); + _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - break; + /* Deserialize SUBACK and notify of result. */ + pIncomingPacket->pMqttConnection = pMqttConnection; + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_SUBSCRIBE, + &( pIncomingPacket->packetIdentifier ) ); - case _MQTT_PACKET_TYPE_SUBACK: - IotLog_PrintBuffer( "SUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + if( pOperation != NULL ) + { + pOperation->status = status; + _IotMqtt_Notify( pOperation ); + } - /* Deserialize the SUBACK to get the packet identifier. */ - IotMqttError_t ( * deserializeSuback )( IotMqttConnection_t, - const uint8_t *, - size_t, - uint16_t *, - size_t * ) = _IotMqtt_DeserializeSuback; + break; - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.suback != NULL ) - { - deserializeSuback = pConnectionInfo->network.deserialize.suback; - } - #endif + case _MQTT_PACKET_TYPE_UNSUBACK: + IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); - status = deserializeSuback( pConnectionInfo, - pNextPacket, - remainingDataLength - totalBytesProcessed, - &packetIdentifier, - &bytesProcessed ); + /* Choose UNSUBACK deserializer. */ + deserialize = _IotMqtt_DeserializeUnsuback; - /* If a complete SUBACK was deserialized, find an in-progress - * SUBACK with a matching client identifier. */ - if( bytesProcessed > 0 ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) { - pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_SUBSCRIBE, &packetIdentifier ); - - if( pOperation != NULL ) + if( pMqttConnection->pSerializer->deserialize.unsuback != NULL ) { - pOperation->status = status; - _IotMqtt_Notify( pOperation ); + deserialize = pMqttConnection->pSerializer->deserialize.unsuback; } } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - break; + /* Deserialize UNSUBACK and notify of result. */ + status = deserialize( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, + IOT_MQTT_UNSUBSCRIBE, + &( pIncomingPacket->packetIdentifier ) ); - case _MQTT_PACKET_TYPE_UNSUBACK: - IotLog_PrintBuffer( "UNSUBACK in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); + if( pOperation != NULL ) + { + pOperation->status = status; + _IotMqtt_Notify( pOperation ); + } - /* Deserialize the UNSUBACK to get the packet identifier. */ - IotMqttError_t ( * deserializeUnsuback )( const uint8_t *, - size_t, - uint16_t *, - size_t * ) = _IotMqtt_DeserializeUnsuback; + break; - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.unsuback != NULL ) - { - deserializeUnsuback = pConnectionInfo->network.deserialize.unsuback; - } - #endif + default: + /* The only remaining valid type is PINGRESP. */ + IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == _MQTT_PACKET_TYPE_PINGRESP ); + IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); - status = deserializeUnsuback( pNextPacket, - remainingDataLength - totalBytesProcessed, - &packetIdentifier, - &bytesProcessed ); + /* Choose PINGRESP deserializer. */ + deserialize = _IotMqtt_DeserializePingresp; - /* If a complete UNSUBACK was deserialized, find an in-progress - * UNSUBACK with a matching client identifier. */ - if( bytesProcessed > 0 ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) { - pOperation = _IotMqtt_FindOperation( pConnectionInfo, IOT_MQTT_UNSUBSCRIBE, &packetIdentifier ); - - if( pOperation != NULL ) + if( pMqttConnection->pSerializer->deserialize.pingresp != NULL ) { - pOperation->status = status; - _IotMqtt_Notify( pOperation ); + deserialize = pMqttConnection->pSerializer->deserialize.pingresp; } } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - break; - - case _MQTT_PACKET_TYPE_PINGRESP: - IotLog_PrintBuffer( "PINGRESP in data stream:", pNextPacket, remainingDataLength - totalBytesProcessed ); - - /* Deserialize the PINGRESP. */ - IotMqttError_t ( * deserializePingresp )( const uint8_t *, - size_t, - size_t * ) = _IotMqtt_DeserializePingresp; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pConnectionInfo->network.deserialize.pingresp != NULL ) - { - deserializePingresp = pConnectionInfo->network.deserialize.pingresp; - } - #endif + /* Deserialize PINGRESP. */ + status = deserialize( pIncomingPacket ); - status = deserializePingresp( pNextPacket, - remainingDataLength - totalBytesProcessed, - &bytesProcessed ); + if( status == IOT_MQTT_SUCCESS ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - /* If a complete PINGRESP was successfully deserialized, clear the keep-alive - * failure flag. */ - if( bytesProcessed > 0 ) + if( pMqttConnection->keepAliveFailure == false ) { - if( status == IOT_MQTT_SUCCESS ) - { - IotMutex_Lock( &( pConnectionInfo->referencesMutex ) ); - - if( pConnectionInfo->keepAliveFailure == false ) - { - IotLogWarn( "Unexpected PINGRESP received." ); - } - else - { - pConnectionInfo->keepAliveFailure = false; - } - - IotMutex_Unlock( &( pConnectionInfo->referencesMutex ) ); - } - else - { - IotLogError( "Failed to process PINGRESP, status %s.", - IotMqtt_strerror( status ) ); - } + IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", + pMqttConnection ); } + else + { + IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", + pMqttConnection ); - break; - - default: + pMqttConnection->keepAliveFailure = false; + } - /* If an unknown packet is received, stop processing pReceivedData. */ - IotLogError( "Unknown packet type %02x received.", - pNextPacket[ 0 ] ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } - bytesProcessed = 1; - status = IOT_MQTT_BAD_RESPONSE; + break; + } - break; - } + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Packet parser status %s.", + pMqttConnection, + IotMqtt_strerror( status ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } - /* Check if a protocol violation was encountered. */ - if( ( bytesProcessed > 0 ) && ( status == IOT_MQTT_BAD_RESPONSE ) ) - { - IotLogError( "MQTT protocol violation encountered. Closing network connection" ); + return status; +} - /* Clean up any previously allocated incoming PUBLISH operations. */ - while( pFirstPublish != NULL ) - { - pLastPublish = pFirstPublish; - pFirstPublish = pFirstPublish->pNextPublish; +/*-----------------------------------------------------------*/ - IotMqtt_FreeOperation( pLastPublish ); - } +static void _sendPuback( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier ) +{ + IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; + uint8_t * pPuback = NULL; + size_t pubackSize = 0, bytesSent = 0; - pLastPublish = NULL; + /* Default PUBACK serializer and free packet functions. */ + IotMqttError_t ( * serializePuback )( uint16_t, + uint8_t **, + size_t * ) = _IotMqtt_SerializePuback; + void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; - _IotMqtt_CloseNetworkConnection( pConnectionInfo ); + IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); - return -1; + /* Choose PUBACK serializer and free packet functions. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->serialize.puback != NULL ) + { + serializePuback = pMqttConnection->pSerializer->serialize.puback; + } + else + { + _EMPTY_ELSE_MARKER; + } } - - /* Check if a partial packet was encountered. */ - if( bytesProcessed == 0 ) + else { - break; + _EMPTY_ELSE_MARKER; } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->freePacket != NULL ) + { + freePacket = pMqttConnection->pSerializer->freePacket; + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Move the "next packet" pointer and increment the number of bytes processed. */ - pNextPacket += bytesProcessed; - totalBytesProcessed += bytesProcessed; - - /* Number of bytes processed should never exceed remainingDataLength. */ - IotMqtt_Assert( pNextPacket - totalBytesProcessed - offset == pReceivedData ); - IotMqtt_Assert( totalBytesProcessed <= remainingDataLength ); - } + /* Generate a PUBACK packet from the packet identifier. */ + serializeStatus = serializePuback( packetIdentifier, + &pPuback, + &pubackSize ); - /* Only free pReceivedData if all bytes were processed and no PUBLISH messages - * were in the data stream. */ - if( ( freeReceivedData != NULL ) && - ( totalBytesProcessed == remainingDataLength ) && - ( pFirstPublish == NULL ) ) + if( serializeStatus != IOT_MQTT_SUCCESS ) { - IotMqtt_Assert( pLastPublish == NULL ); - - freeReceivedData( ( void * ) pReceivedData ); + IotLogWarn( "(MQTT connection %p) Failed to generate PUBACK packet for " + "received PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); } - - /* Add all PUBLISH messages to the pending operations queue. */ - if( pLastPublish != NULL ) + else { - /* If all bytes of the receive buffer were processed, set the function - * to free the receive buffer. */ - if( ( totalBytesProcessed == remainingDataLength ) && ( freeReceivedData != NULL ) ) + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pPuback, + pubackSize ); + + if( bytesSent != pubackSize ) { - pLastPublish->pReceivedData = pReceivedData; - pLastPublish->freeReceivedData = freeReceivedData; + IotLogWarn( "(MQTT connection %p) Failed to send PUBACK for received" + " PUBLISH %hu.", + pMqttConnection, + packetIdentifier ); } else { - /* When some of the receive buffer is unprocessed, the receive buffer is - * given back to the calling function. The MQTT library cannot guarantee - * that the calling function will keep the receive buffer in scope; - * therefore, data in the receive buffer must be copied for the MQTT - * library's use. */ - for( pOperation = pFirstPublish; pOperation != NULL; pOperation = pOperation->pNextPublish ) - { - /* Neither the buffer pointer nor the free function should be set. */ - IotMqtt_Assert( pOperation->pReceivedData == NULL ); - IotMqtt_Assert( pOperation->freeReceivedData == NULL ); + IotLogDebug( "(MQTT connection %p) PUBACK for received PUBLISH %hu sent.", + pMqttConnection, + packetIdentifier ); + } - /* Allocate a new buffer to hold the topic name and payload. */ - pOperation->pReceivedData = IotMqtt_MallocMessage( pOperation->publishInfo.topicNameLength + - pOperation->publishInfo.payloadLength ); + freePacket( pPuback ); + } +} - if( pOperation->pReceivedData != NULL ) - { - /* Copy the topic name and payload. */ - ( void ) memcpy( ( void * ) pOperation->pReceivedData, - pOperation->publishInfo.pTopicName, - pOperation->publishInfo.topicNameLength ); - ( void ) memcpy( ( uint8_t * ) ( pOperation->pReceivedData ) + - pOperation->publishInfo.topicNameLength, - pOperation->publishInfo.pPayload, - pOperation->publishInfo.payloadLength ); - - /* Set the topic name and payload pointers into the new buffer. - * Also set the free function. */ - pOperation->publishInfo.pTopicName = pOperation->pReceivedData; - pOperation->publishInfo.pPayload = ( uint8_t * ) ( pOperation->pReceivedData ) + - pOperation->publishInfo.topicNameLength; - pOperation->freeReceivedData = IotMqtt_FreeMessage; - } - else - { - /* If a new buffer couldn't be allocated, clear the topic name and - * payload pointers so that this PUBLISH message will be ignored. */ - IotLogWarn( "Failed to allocate memory for incoming PUBLISH message." ); - pOperation->publishInfo.pTopicName = NULL; - pOperation->publishInfo.topicNameLength = 0; - pOperation->publishInfo.pPayload = NULL; - pOperation->publishInfo.payloadLength = 0; - } - } - } +/*-----------------------------------------------------------*/ - if( _IotMqtt_ScheduleOperation( pFirstPublish, - _IotMqtt_ProcessIncomingPublish, - 0 ) != IOT_MQTT_SUCCESS ) - { - IotLogWarn( "Failed to schedule incoming PUBLISH callback." ); - } +bool _IotMqtt_GetNextByte( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface, + uint8_t * pIncomingByte ) +{ + bool status = false; + uint8_t incomingByte = 0; + size_t bytesReceived = 0; + + /* Attempt to read 1 byte. */ + bytesReceived = pNetworkInterface->receive( pNetworkConnection, + &incomingByte, + 1 ); + + /* Set the output parameter and return success if 1 byte was read. */ + if( bytesReceived == 1 ) + { + *pIncomingByte = incomingByte; + status = true; + } + else + { + /* Network receive must return 0 on failure. */ + IotMqtt_Assert( bytesReceived == 0 ); } - return ( int32_t ) totalBytesProcessed; + return status; } /*-----------------------------------------------------------*/ @@ -578,6 +697,7 @@ int32_t IotMqtt_ReceiveCallback( void * pMqttConnection, void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) { IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -595,7 +715,7 @@ void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) IotMqtt_Assert( pMqttConnection->references > 0 ); /* Attempt to cancel the keep-alive job. */ - taskPoolStatus = IotTaskPool_TryCancel( &( _IotMqttTaskPool ), + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &( pMqttConnection->keepAliveJob ), NULL ); @@ -638,17 +758,77 @@ void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) /* Close the network connection regardless of whether an MQTT DISCONNECT * packet was sent. */ - if( pMqttConnection->network.disconnect != NULL ) + if( pMqttConnection->pNetworkInterface->close != NULL ) { - pMqttConnection->network.disconnect( pMqttConnection->network.pDisconnectContext ); + closeStatus = pMqttConnection->pNetworkInterface->close( pMqttConnection->pNetworkConnection ); - IotLogInfo( "(MQTT connection %p) Network connection closed.", pMqttConnection ); + if( closeStatus == IOT_NETWORK_SUCCESS ) + { + IotLogInfo( "(MQTT connection %p) Network connection closed.", pMqttConnection ); + } + else + { + IotLogWarn( "(MQTT connection %p) Failed to close network connection, error %d.", + pMqttConnection, + closeStatus ); + } } else { - IotLogWarn( "(MQTT connection %p) No disconnect function was set. Network connection" + IotLogWarn( "(MQTT connection %p) No network close function was set. Network connection" " not closed.", pMqttConnection ); } } /*-----------------------------------------------------------*/ + +void IotMqtt_ReceiveCallback( void * pNetworkConnection, + void * pReceiveContext ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + _mqttPacket_t incomingPacket = { 0 }; + + /* Cast context to correct type. */ + _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pReceiveContext; + + /* Read an MQTT packet from the network. */ + status = _getIncomingPacket( pNetworkConnection, + pMqttConnection, + &incomingPacket ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Deserialize the received packet. */ + status = _deserializeIncomingPacket( pMqttConnection, + &incomingPacket ); + + /* Free any buffers allocated for the MQTT packet. */ + if( incomingPacket.pRemainingData != NULL ) + { + IotMqtt_FreeMessage( incomingPacket.pRemainingData ); + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Close the network connection on a bad response. */ + if( status == IOT_MQTT_BAD_RESPONSE ) + { + IotLogError( "(MQTT connection %p) Error processing incoming data. Closing connection.", + pMqttConnection ); + + _IotMqtt_CloseNetworkConnection( pMqttConnection ); + } + else + { + _EMPTY_ELSE_MARKER; + } +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 8eb7a8b86b..e3b655f694 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -91,13 +91,6 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ); /*-----------------------------------------------------------*/ -/** - * @brief The task pool that processes MQTT operations. - */ -IotTaskPool_t _IotMqttTaskPool = { 0 }; - -/*-----------------------------------------------------------*/ - static bool _mqttOperation_match( const IotLink_t * pOperationLink, void * pMatch ) { @@ -141,20 +134,27 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) bool status = true; /* Choose a set DUP function. */ - void ( * publishSetDup )( bool, + void ( * publishSetDup )( uint8_t *, uint8_t *, uint16_t * ) = _IotMqtt_PublishSetDup; #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.serialize.publishSetDup != NULL ) + if( pMqttConnection->pSerializer != NULL ) { - publishSetDup = pMqttConnection->network.serialize.publishSetDup; + if( pMqttConnection->pSerializer->serialize.publishSetDup != NULL ) + { + publishSetDup = pMqttConnection->pSerializer->serialize.publishSetDup; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Only PUBLISH may be retried. */ IotMqtt_Assert( pOperation->operation == IOT_MQTT_PUBLISH_TO_SERVER ); @@ -177,8 +177,8 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) else if( pOperation->retry.count == 1 ) { /* Always set the DUP flag on the first retry. */ - publishSetDup( pMqttConnection->awsIotMqttMode, - pOperation->pMqttPacket, + publishSetDup( pOperation->pMqttPacket, + pOperation->pPacketIdentifierHigh, &( pOperation->packetIdentifier ) ); } else @@ -187,8 +187,8 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) * identifier) must be reset on every retry. */ if( pMqttConnection->awsIotMqttMode == true ) { - publishSetDup( pMqttConnection->awsIotMqttMode, - pOperation->pMqttPacket, + publishSetDup( pOperation->pMqttPacket, + pOperation->pPacketIdentifierHigh, &( pOperation->packetIdentifier ) ); } else @@ -462,7 +462,7 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, /* Attempt to cancel the operation's job. */ if( cancelJob == true ) { - taskPoolStatus = IotTaskPool_TryCancel( &( _IotMqttTaskPool ), + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &( pOperation->job ), NULL ); @@ -572,15 +572,22 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) if( pOperation->pMqttPacket != NULL ) { #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->network.freePacket != NULL ) + if( pMqttConnection->pSerializer != NULL ) { - freePacket = pMqttConnection->network.freePacket; + if( pMqttConnection->pSerializer->freePacket != NULL ) + { + freePacket = pMqttConnection->pSerializer->freePacket; + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ freePacket( pOperation->pMqttPacket ); @@ -639,7 +646,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; /* Check parameters. */ - IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pKeepAliveJob == &( pMqttConnection->keepAliveJob ) ); /* Check that keep-alive interval is valid. The MQTT spec states its maximum @@ -668,9 +675,9 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, /* Because PINGREQ may be used to keep the MQTT connection alive, it is * more important than other operations. Bypass the queue of jobs for * operations by directly sending the PINGREQ in this job. */ - bytesSent = pMqttConnection->network.send( pMqttConnection->network.pSendContext, - pMqttConnection->pPingreqPacket, - pMqttConnection->pingreqPacketSize ); + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pMqttConnection->pPingreqPacket, + pMqttConnection->pingreqPacketSize ); if( bytesSent != pMqttConnection->pingreqPacketSize ) { @@ -760,59 +767,35 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pPublishJob, void * pContext ) { - _mqttOperation_t * pCurrent = NULL, * pNext = pContext; + _mqttOperation_t * pOperation = pContext; IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; /* Check parameters. The task pool and job parameter is not used when asserts * are disabled. */ ( void ) pTaskPool; ( void ) pPublishJob; - IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); - IotMqtt_Assert( pNext->incomingPublish == true ); - IotMqtt_Assert( pPublishJob == &( pNext->job ) ); + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); + IotMqtt_Assert( pOperation->incomingPublish == true ); + IotMqtt_Assert( pPublishJob == &( pOperation->job ) ); - /* Process each linked incoming PUBLISH. */ - while( pNext != NULL ) - { - /* Save a pointer to the current PUBLISH and move the iterating - * pointer. */ - pCurrent = pNext; - pNext = pNext->pNextPublish; - - /* Process the current PUBLISH. */ - if( pCurrent->publishInfo.pPayload != NULL ) - { - if( pCurrent->publishInfo.pTopicName != NULL ) - { - callbackParam.message.info = pCurrent->publishInfo; - - _IotMqtt_InvokeSubscriptionCallback( pCurrent->pMqttConnection, - &callbackParam ); - } - else - { - _EMPTY_ELSE_MARKER; - } - } - else - { - _EMPTY_ELSE_MARKER; - } + /* Process the current PUBLISH. */ + callbackParam.message.info = pOperation->publishInfo; - /* Free any buffers associated with the current PUBLISH message. */ - if( pCurrent->freeReceivedData != NULL ) - { - IotMqtt_Assert( pCurrent->pReceivedData != NULL ); - pCurrent->freeReceivedData( ( void * ) pCurrent->pReceivedData ); - } - else - { - _EMPTY_ELSE_MARKER; - } + _IotMqtt_InvokeSubscriptionCallback( pOperation->pMqttConnection, + &callbackParam ); - /* Free the current PUBLISH. */ - IotMqtt_FreeOperation( pCurrent ); + /* Free any buffers associated with the current PUBLISH message. */ + if( pOperation->pReceivedData != NULL ) + { + IotMqtt_FreeMessage( ( void* ) pOperation->pReceivedData ); } + else + { + _EMPTY_ELSE_MARKER; + } + + /* Free the incoming PUBLISH operation. */ + IotMqtt_FreeOperation( pOperation ); } /*-----------------------------------------------------------*/ @@ -830,7 +813,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, * are disabled. */ ( void ) pTaskPool; ( void ) pSendJob; - IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pSendJob == &( pOperation->job ) ); /* The given operation must have an allocated packet and be waiting for a status. */ @@ -867,9 +850,9 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, pOperation ); /* Transmit the MQTT packet from the operation over the network. */ - bytesSent = pMqttConnection->network.send( pMqttConnection->network.pSendContext, - pOperation->pMqttPacket, - pOperation->packetSize ); + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pOperation->pMqttPacket, + pOperation->packetSize ); /* Check transmission status. */ if( bytesSent != pOperation->packetSize ) @@ -883,6 +866,9 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, * may also be considered successful. */ if( pOperation->operation == IOT_MQTT_DISCONNECT ) { + /* DISCONNECT operations are always waitable. */ + IotMqtt_Assert( waitable == true ); + pOperation->status = IOT_MQTT_SUCCESS; } else if( waitable == false ) @@ -1007,7 +993,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, * are disabled. */ ( void ) pTaskPool; ( void ) pOperationJob; - IotMqtt_Assert( pTaskPool == &( _IotMqttTaskPool ) ); + IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pOperationJob == &( pOperation->job ) ); /* The operation's callback function and status must be set. */ @@ -1055,7 +1041,7 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); /* Schedule the new job with a delay. */ - taskPoolStatus = IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), + taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, &( pOperation->job ), delay ); @@ -1130,7 +1116,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, * the retry job. */ if( pResult->retry.limit > 0 ) { - taskPoolStatus = IotTaskPool_TryCancel( &( _IotMqttTaskPool ), + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &( pResult->job ), NULL ); diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 52f078763d..e58ccd4c0d 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -117,7 +117,6 @@ /* * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. */ -#define _MQTT_PACKET_CONNACK_SIZE ( 4 ) /**< @brief A CONNACK packet is always 4 bytes in size. */ #define _MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ #define _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01 ) /**< @brief The "Session Present" bit is always the lowest bit. */ @@ -141,7 +140,6 @@ * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. */ #define _MQTT_PACKET_PINGREQ_SIZE ( 2 ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ -#define _MQTT_PACKET_PINGRESP_SIZE ( 2 ) /**< @brief A PINGRESP packet is always 2 bytes in size. */ #define _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0 ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ /* @@ -198,22 +196,6 @@ static size_t _remainingLengthEncodedSize( size_t length ); static uint8_t * _encodeRemainingLength( uint8_t * pDestination, size_t length ); -/** - * @brief Decodes the "Remaining length" field in an MQTT packet. - * - * @param[in] pSource Pointer to the beginning of remaining length. - * @param[out] pEnd Set to point to the byte after the encoded "Remaining length". - * @param[out] pLength Set to the decoded remaining length. - * - * @return Pointer to the end of the encoded "Remaining length", which is 1-4 - * bytes greater than pSource. - * - * @warning This function does not check the size of `pSource`! - */ -static IotMqttError_t _decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** pEnd, - size_t * pLength ); - /** * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. * @@ -399,76 +381,6 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /*-----------------------------------------------------------*/ -static IotMqttError_t _decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** pEnd, - size_t * const pLength ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t encodedByte = 0; - size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - if( multiplier > 2097152 ) /* 128 ^ 3 */ - { - status = IOT_MQTT_BAD_PARAMETER; - break; - } - else - { - encodedByte = *pSource; - pSource++; - - remainingLength += ( encodedByte & 0x7F ) * multiplier; - multiplier *= 128; - bytesDecoded++; - } - } while( ( encodedByte & 0x80 ) != 0 ); - - if( status == IOT_MQTT_SUCCESS ) - { - expectedSize = _remainingLengthEncodedSize( remainingLength ); - - if( bytesDecoded != expectedSize ) - { - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4 ); - - /* Set the output parameters. */ - if( pLength != NULL ) - { - *pLength = remainingLength; - } - else - { - _EMPTY_ELSE_MARKER; - } - - if( pEnd != NULL ) - { - *pEnd = pSource; - } - else - { - _EMPTY_ELSE_MARKER; - } - } - } - else - { - _EMPTY_ELSE_MARKER; - } - - return status; -} - -/*-----------------------------------------------------------*/ - static uint8_t * _encodeString( uint8_t * pDestination, const char * source, uint16_t sourceLength ) @@ -711,7 +623,7 @@ IotMqttError_t _IotMqtt_InitSerialize( void ) _EMPTY_ELSE_MARKER; } #endif - #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ _IOT_FUNCTION_CLEANUP_BEGIN(); @@ -753,23 +665,74 @@ void _IotMqtt_CleanupSerialize( void ) /*-----------------------------------------------------------*/ -uint8_t _IotMqtt_GetPacketType( const uint8_t * pPacket, - size_t packetSize ) +uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) { - uint8_t packetType = 0; + uint8_t packetType = 0xff; + + /* The MQTT packet type is in the first byte of the packet. */ + ( void ) _IotMqtt_GetNextByte( pNetworkConnection, + pNetworkInterface, + &packetType ); + + return packetType; +} - if( packetSize > 0 ) +/*-----------------------------------------------------------*/ + +size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) +{ + uint8_t encodedByte = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do { - /* The MQTT packet type is in the first byte of the packet. Mask out the - * lower bits to ignore flags. */ - packetType = *pPacket & 0xf0; + if( multiplier > 2097152 ) /* 128 ^ 3 */ + { + remainingLength = _MQTT_REMAINING_LENGTH_INVALID; + break; + } + else + { + if( _IotMqtt_GetNextByte( pNetworkConnection, + pNetworkInterface, + &encodedByte ) == true ) + { + remainingLength += ( encodedByte & 0x7F ) * multiplier; + multiplier *= 128; + bytesDecoded++; + } + else + { + remainingLength = _MQTT_REMAINING_LENGTH_INVALID; + break; + } + } + } while( ( encodedByte & 0x80 ) != 0 ); + + /* Check that the decoded remaining length conforms to the MQTT specification. */ + if( remainingLength != _MQTT_REMAINING_LENGTH_INVALID ) + { + expectedSize = _remainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + remainingLength = _MQTT_REMAINING_LENGTH_INVALID; + } + else + { + /* Valid remaining length should be at most 4 bytes. */ + IotMqtt_Assert( bytesDecoded <= 4 ); + } } else { _EMPTY_ELSE_MARKER; } - return packetType; + return remainingLength; } /*-----------------------------------------------------------*/ @@ -993,11 +956,10 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, - size_t dataLength, - size_t * pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + const uint8_t * pRemainingData = pConnack->pRemainingData; /* If logging is enabled, declare the CONNACK response code strings. The * fourth byte of CONNACK indexes into this array for the corresponding response. */ @@ -1013,33 +975,13 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, }; #endif - /* According to MQTT 3.1.1, CONNACK packets are all 4 bytes in size. If the - * data stream has fewer than 4 bytes, then the CONNACK packet is incomplete. */ - if( dataLength < _MQTT_PACKET_CONNACK_SIZE ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "CONNACK less than %d bytes in size.", - _MQTT_PACKET_CONNACK_SIZE ); - *pBytesProcessed = 0; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* The next 4 bytes will be processed by this function. */ - *pBytesProcessed = _MQTT_PACKET_CONNACK_SIZE; - /* Check that the control packet type is 0x20. */ - if( pConnackStart[ 0 ] != _MQTT_PACKET_TYPE_CONNACK ) + if( pConnack->type != _MQTT_PACKET_TYPE_CONNACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", - pConnackStart[ 0 ] ); + pConnack->type ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1050,7 +992,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, /* According to MQTT 3.1.1, the second byte of CONNACK must specify a * "Remaining length" of 2. */ - if( pConnackStart[ 1 ] != _MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + if( pConnack->remainingLength != _MQTT_PACKET_CONNACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, @@ -1066,7 +1008,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, /* Check the reserved bits in CONNACK. The high 7 bits of the second byte * in CONNACK must be 0. */ - if( ( pConnackStart[ 2 ] | 0x01 ) != 0x01 ) + if( ( pRemainingData[ 0 ] | 0x01 ) != 0x01 ) { IotLog( IOT_LOG_ERROR, &_logHideAll, @@ -1081,7 +1023,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, /* Determine if the "Session Present" bit it set. This is the lowest bit of * the second byte in CONNACK. */ - if( ( pConnackStart[ 2 ] & _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + if( ( pRemainingData[ 0 ] & _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) == _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) { IotLog( IOT_LOG_DEBUG, @@ -1090,7 +1032,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the * "Session Present" bit is set. */ - if( pConnackStart[ 3 ] != 0 ) + if( pRemainingData[ 1 ] != 0 ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1107,12 +1049,12 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, } /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ - if( pConnackStart[ 3 ] > 5 ) + if( pRemainingData[ 1 ] > 5 ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "CONNACK response %hhu is not valid.", - pConnackStart[ 3 ] ); + pRemainingData[ 1 ] ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1127,11 +1069,11 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, IotLog( IOT_LOG_DEBUG, &_logHideAll, "%s", - pConnackResponses[ pConnackStart[ 3 ] ] ); + pConnackResponses[ pRemainingData[ 1 ] ] ); #endif /* A nonzero CONNACK response code means the connection was refused. */ - if( pConnackStart[ 3 ] > 0 ) + if( pRemainingData[ 1 ] > 0 ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); } @@ -1148,7 +1090,8 @@ IotMqttError_t _IotMqtt_DeserializeConnack( const uint8_t * pConnackStart, IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, uint8_t ** pPublishPacket, size_t * pPacketSize, - uint16_t * pPacketIdentifier ) + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint8_t publishFlags = 0; @@ -1235,9 +1178,20 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI { /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; IotMqtt_Assert( packetIdentifier != 0 ); + /* Set the packet identifier output parameters. */ + *pPacketIdentifier = packetIdentifier; + + if( pPacketIdentifierHigh != NULL ) + { + *pPacketIdentifierHigh = pBuffer; + } + else + { + _EMPTY_ELSE_MARKER; + } + /* Place the packet identifier into the PUBLISH packet. */ *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); @@ -1271,35 +1225,28 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI /*-----------------------------------------------------------*/ -void _IotMqtt_PublishSetDup( bool awsIotMqttMode, - uint8_t * pPublishPacket, +void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, uint16_t * pNewPacketIdentifier ) { - const uint8_t * pTopicNameLength = NULL; - uint8_t * pPacketIdentifier = NULL; - uint16_t topicNameLength = 0, newPacketIdentifier = _nextPacketIdentifier(); + uint16_t newPacketIdentifier = 0; /* For an AWS IoT MQTT server, change the packet identifier. */ - if( awsIotMqttMode == true ) + if( pPacketIdentifierHigh != NULL ) { - /* Decode the "Remaining length" to find where it ends. Because the - * "Remaining length" was not received from the network, it is "trusted" - * so the return value of this function isn't checked. */ - ( void ) _decodeRemainingLength( pPublishPacket + 1, - &pTopicNameLength, - NULL ); + /* Output parameter for new packet identifier must be provided. */ + IotMqtt_Assert( pNewPacketIdentifier != NULL ); - /* Decode the topic name length and calculate the address of the packet identifier. */ - topicNameLength = _UINT16_DECODE( pTopicNameLength ); - pPacketIdentifier = ( uint8_t * ) ( pTopicNameLength + topicNameLength + sizeof( uint16_t ) ); + /* Generate a new packet identifier. */ + newPacketIdentifier = _nextPacketIdentifier(); IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", - _UINT16_DECODE( pPacketIdentifier ), + _UINT16_DECODE( pPacketIdentifierHigh ), newPacketIdentifier ); /* Replace the packet identifier. */ - *pPacketIdentifier = _UINT16_HIGH_BYTE( newPacketIdentifier ); - *( pPacketIdentifier + 1 ) = _UINT16_LOW_BYTE( newPacketIdentifier ); + *pPacketIdentifierHigh = _UINT16_HIGH_BYTE( newPacketIdentifier ); + *( pPacketIdentifierHigh + 1 ) = _UINT16_LOW_BYTE( newPacketIdentifier ); *pNewPacketIdentifier = newPacketIdentifier; } else @@ -1313,83 +1260,15 @@ void _IotMqtt_PublishSetDup( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, - size_t dataLength, - IotMqttPublishInfo_t * pOutput, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - size_t remainingLength = 0, packetSize = 0; + IotMqttPublishInfo_t * pOutput = &( pPublish->pIncomingPublish->publishInfo ); uint8_t publishFlags = 0; - uint16_t packetIdentifier = 0; - const uint8_t * pVariableHeader = NULL, * pPacketIdentifierHigh = NULL; - - /* Ensure that at least 5 bytes are available. If not, this is an incomplete - * PUBLISH packet. */ - if( dataLength < _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ) - { - IotLogError( "PUBLISH size %lu is smaller than the smallest possible " - "size %d.", - ( unsigned long ) dataLength, - _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ); - *pBytesProcessed = 0; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Decode the "Remaining length", which is the second byte in PUBLISH. */ - status = _decodeRemainingLength( pPublishStart + 1, &pVariableHeader, &remainingLength ); - - if( status != IOT_MQTT_SUCCESS ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad remaining length." ); - - /* If the "Remaining length" couldn't be determined, invalidate the rest - * of the data stream by marking it processed. */ - *pBytesProcessed = dataLength; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length %lu.", ( unsigned long ) remainingLength ); - - /* Calculate the packet size and set the output parameter. */ - packetSize = remainingLength + _remainingLengthEncodedSize( remainingLength ) + 1; - *pBytesProcessed = packetSize; - - /* Ensure that the PUBLISH packet fits within the data stream. If it doesn't, - * then this is a partial packet. */ - if( packetSize > dataLength ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "PUBLISH packet size %lu exceeds data length %lu.", - ( unsigned long ) packetSize, - ( unsigned long ) dataLength ); - *pBytesProcessed = 0; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } + const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; /* The flags are the lower 4 bits of the first byte in PUBLISH. */ - publishFlags = *pPublishStart; + publishFlags = pPublish->type; /* Parse the Retain bit. */ pOutput->retain = _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); @@ -1451,12 +1330,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, { /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate * topic name length (2 bytes) and topic name (at least 1 byte). */ - if( remainingLength < 3 ) + if( pPublish->remainingLength < 3 ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "QoS 0 PUBLISH cannot have a remaining length less than 3." ); - *pBytesProcessed = dataLength; _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1470,12 +1348,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, /* A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 to * accommodate a packet identifier as well as the topic name length and * topic name. */ - if( remainingLength < 5 ) + if( pPublish->remainingLength < 5 ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); - *pBytesProcessed = dataLength; _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1494,12 +1371,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, { /* Check that the "Remaining length" is at least as large as the variable * header. */ - if( remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) + if( pPublish->remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "Remaining length cannot be less than variable header length." ); - *pBytesProcessed = dataLength; _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1512,12 +1388,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, { /* Check that the "Remaining length" is at least as large as the variable * header. */ - if( remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) + if( pPublish->remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "Remaining length cannot be less than variable header length." ); - *pBytesProcessed = dataLength; _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1543,15 +1418,14 @@ IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, if( pOutput->qos > IOT_MQTT_QOS_0 ) { - packetIdentifier = _UINT16_DECODE( pPacketIdentifierHigh ); - *pPacketIdentifier = packetIdentifier; + pPublish->packetIdentifier = _UINT16_DECODE( pPacketIdentifierHigh ); IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + "Packet identifier %hu.", pPublish->packetIdentifier ); /* Packet identifier cannot be 0. */ - if( packetIdentifier == 0 ) + if( pPublish->packetIdentifier == 0 ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1569,19 +1443,18 @@ IotMqttError_t _IotMqtt_DeserializePublish( const uint8_t * pPublishStart, * a packet identifer, but QoS 0 PUBLISH packets do not. */ if( pOutput->qos == IOT_MQTT_QOS_0 ) { - pOutput->payloadLength = ( uint16_t ) ( remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->payloadLength = ( uint16_t ) ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh; } else { - pOutput->payloadLength = ( uint16_t ) ( remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->payloadLength = ( uint16_t ) ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Payload length %hu. Payload:", pOutput->payloadLength ); - IotLog_PrintBuffer( NULL, pOutput->pPayload, pOutput->payloadLength ); + "Payload length %hu.", pOutput->payloadLength ); _IOT_FUNCTION_EXIT_NO_CLEANUP(); } @@ -1624,23 +1497,17 @@ IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializePuback( const uint8_t * pPubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - uint16_t packetIdentifier = 0; - /* According to MQTT 3.1.1, PUBACK packets are all 4 bytes in size. If the - * data stream has fewer than 4 bytes, then the PUBACK packet is incomplete. */ - if( dataLength < _MQTT_PACKET_PUBACK_SIZE ) + /* Check the "Remaining length" of the received PUBACK. */ + if( pPuback->remainingLength != _MQTT_PACKET_PUBACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, - "PUBACK less than %d bytes in size.", - _MQTT_PACKET_PUBACK_SIZE ); - *pBytesProcessed = 0; + "PUBACK does not have remaining length of %d.", + _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1649,19 +1516,15 @@ IotMqttError_t _IotMqtt_DeserializePuback( const uint8_t * pPubackStart, _EMPTY_ELSE_MARKER; } - /* The next 4 bytes will be processed by this function. */ - *pBytesProcessed = _MQTT_PACKET_PUBACK_SIZE; - /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ - packetIdentifier = _UINT16_DECODE( pPubackStart + 2 ); - *pPacketIdentifier = packetIdentifier; + pPuback->packetIdentifier = _UINT16_DECODE( pPuback->pRemainingData ); IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + "Packet identifier %hu.", pPuback->packetIdentifier ); /* Packet identifier cannot be 0. */ - if( packetIdentifier == 0 ) + if( pPuback->packetIdentifier == 0 ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1670,28 +1533,14 @@ IotMqttError_t _IotMqtt_DeserializePuback( const uint8_t * pPubackStart, _EMPTY_ELSE_MARKER; } - /* Check that the control packet type is 0x40. */ - if( pPubackStart[ 0 ] != _MQTT_PACKET_TYPE_PUBACK ) + /* Check that the control packet type is 0x40 (this must be done after the + * packet identifier is parsed). */ + if( pPuback->type != _MQTT_PACKET_TYPE_PUBACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", - pPubackStart[ 0 ] ); - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Check the "Remaining length" (second byte) of the received PUBACK. */ - if( pPubackStart[ 1 ] != _MQTT_PACKET_PUBACK_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "PUBACK does not have remaining length of %d.", - _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + pPuback->type ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1799,59 +1648,12 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, - const uint8_t * pSubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - size_t i = 0, remainingLength = 0, packetSize = 0; - uint16_t packetIdentifier = 0; + size_t i = 0, remainingLength = pSuback->remainingLength; uint8_t subscriptionStatus = 0; - const uint8_t * pVariableHeader = NULL; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; - - /* Ensure that at least 5 bytes are available. If not, this is an incomplete - * SUBACK packet. */ - if( dataLength < _MQTT_PACKET_SUBACK_MINIMUM_SIZE ) - { - IotLogError( "SUBACK size %lu is smaller than the smallest possible " - "size %d.", - ( unsigned long ) dataLength, - _MQTT_PACKET_SUBACK_MINIMUM_SIZE ); - *pBytesProcessed = 0; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Decode the "Remaining length" in SUBACK, which starts at byte 2. */ - if( _decodeRemainingLength( pSubackStart + 1, - &pVariableHeader, - &remainingLength ) != IOT_MQTT_SUCCESS ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad remaining length." ); - - /* If the "Remaining length" couldn't be determined, invalidate the rest - * of the data stream by marking it processed. */ - *pBytesProcessed = dataLength; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length %lu.", ( unsigned long ) remainingLength ); + const uint8_t * pVariableHeader = pSuback->pRemainingData; /* A SUBACK must have a remaining length of at least 3 to accommodate the * packet identifer and at least 1 return code. */ @@ -1860,29 +1662,6 @@ IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, IotLog( IOT_LOG_DEBUG, &_logHideAll, "SUBACK cannot have a remaining length less than 3." ); - *pBytesProcessed = dataLength; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Calculate the packet size and set the output parameter. */ - packetSize = remainingLength + _remainingLengthEncodedSize( remainingLength ) + 1; - *pBytesProcessed = packetSize; - - /* Ensure that the SUBACK packet fits within the data stream. If it doesn't, - * then this is a partial packet. */ - if( packetSize > dataLength ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "SUBACK packet size %lu exceeds data length %lu.", - ( unsigned long ) packetSize, - ( unsigned long ) dataLength ); - *pBytesProcessed = 0; _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1892,20 +1671,20 @@ IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, } /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ - packetIdentifier = _UINT16_DECODE( pVariableHeader ); - *pPacketIdentifier = packetIdentifier; + pSuback->packetIdentifier = _UINT16_DECODE( pVariableHeader ); IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); + "Packet identifier %hu.", pSuback->packetIdentifier ); - /* Check that the control packet type is 0x90. */ - if( pSubackStart[ 0 ] != _MQTT_PACKET_TYPE_SUBACK ) + /* Check that the control packet type is 0x90 (this must be done after the + * packet identifier is parsed). */ + if( pSuback->type != _MQTT_PACKET_TYPE_SUBACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", - pSubackStart[ 0 ] ); + pSuback->type ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1938,9 +1717,9 @@ IotMqttError_t _IotMqtt_DeserializeSuback( IotMqttConnection_t mqttConnection, "Topic filter %lu refused.", ( unsigned long ) i ); /* Remove a rejected subscription from the subscription manager. */ - _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, - packetIdentifier, - ( long ) i ); + _IotMqtt_RemoveSubscriptionByPacket( pSuback->pMqttConnection, + pSuback->packetIdentifier, + ( int32_t ) i ); status = IOT_MQTT_SERVER_REFUSED; @@ -2062,23 +1841,17 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - uint16_t packetIdentifier = 0; - /* According to MQTT 3.1.1, UNSUBACK packets are all 4 bytes in size. If the - * data stream has fewer than 4 bytes, then the UNSUBACK packet is incomplete. */ - if( dataLength < _MQTT_PACKET_UNSUBACK_SIZE ) + /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ + if( pUnsuback->remainingLength != _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, - "UNSUBACK less than %d bytes in size.", - _MQTT_PACKET_UNSUBACK_SIZE ); - *pBytesProcessed = 0; + "UNSUBACK does not have remaining length of %d.", + _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -2087,15 +1860,11 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, _EMPTY_ELSE_MARKER; } - /* The next 4 bytes will be processed by this function. */ - *pBytesProcessed = _MQTT_PACKET_UNSUBACK_SIZE; - /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ - packetIdentifier = _UINT16_DECODE( pUnsubackStart + 2 ); - *pPacketIdentifier = packetIdentifier; + pUnsuback->packetIdentifier = _UINT16_DECODE( pUnsuback->pRemainingData ); /* Packet identifier cannot be 0. */ - if( packetIdentifier == 0 ) + if( pUnsuback->packetIdentifier == 0 ) { _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -2104,28 +1873,18 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, _EMPTY_ELSE_MARKER; } - /* Check that the control packet type is 0xb0. */ - if( pUnsubackStart[ 0 ] != _MQTT_PACKET_TYPE_UNSUBACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pUnsubackStart[ 0 ] ); - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pUnsuback->packetIdentifier ); - /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ - if( pUnsubackStart[ 1 ] != _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) + /* Check that the control packet type is 0xb0 (this must be done after the + * packet identifier is parsed). */ + if( pUnsuback->type != _MQTT_PACKET_TYPE_UNSUBACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, - "UNSUBACK does not have remaining length of %d.", - _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); + "Bad control packet type 0x%02x.", + pUnsuback->type ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -2134,10 +1893,6 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( const uint8_t * pUnsubackStart, _EMPTY_ELSE_MARKER; } - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", packetIdentifier ); - _IOT_FUNCTION_EXIT_NO_CLEANUP(); } @@ -2165,39 +1920,17 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializePingresp( const uint8_t * pPingrespStart, - size_t dataLength, - size_t * pBytesProcessed ) +IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - /* According to MQTT 3.1.1, PINGRESP packets are all 2 bytes in size. If the - * data stream has fewer than 2 bytes, then the PINGRESP packet is incomplete. */ - if( dataLength < _MQTT_PACKET_PINGRESP_SIZE ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "PINGRESP less than %d bytes in size.", - _MQTT_PACKET_PINGRESP_SIZE ); - *pBytesProcessed = 0; - - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* The next 2 bytes will be processed by this function. */ - *pBytesProcessed = _MQTT_PACKET_PINGRESP_SIZE; - /* Check that the control packet type is 0xd0. */ - if( pPingrespStart[ 0 ] != _MQTT_PACKET_TYPE_PINGRESP ) + if( pPingresp->type != _MQTT_PACKET_TYPE_PINGRESP ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", - pPingrespStart[ 0 ] ); + pPingresp->type ); _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -2207,7 +1940,7 @@ IotMqttError_t _IotMqtt_DeserializePingresp( const uint8_t * pPingrespStart, } /* Check the "Remaining length" (second byte) of the received PINGRESP. */ - if( pPingrespStart[ 1 ] != _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + if( pPingresp->remainingLength != _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, @@ -2269,13 +2002,6 @@ void _IotMqtt_FreePacket( uint8_t * pPacket ) { _EMPTY_ELSE_MARKER; } - } /*-----------------------------------------------------------*/ - -/* If the MQTT library is being tested, include a file that allows access to - * internal functions and variables. */ -#if IOT_MQTT_TEST == 1 - #include "iot_test_access_mqtt_serialize.c" -#endif diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index c6574242e7..1931591e0e 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -411,93 +411,93 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, .exactMatchOnly = false }; - /* Increment MQTT connection reference count to mark it as being used for - * subscription callbacks. Do not proceed if the reference count could not - * be incremented; in this case, the connection is likely closed. */ - if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) + /* Prevent any other thread from modifying the subscription list while this + * function is searching. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + + /* Search the subscription list for all matching subscriptions starting at + * the list head. */ + while( true ) { - /* Prevent any other thread from modifying the subscription list while this - * function is searching. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + pCurrentLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + pCurrentLink, + _topicMatch, + &topicMatchParams ); - /* Search the subscription list for all matching subscriptions starting at - * the list head. */ - while( true ) + /* No subscription found. Exit loop. */ + if( pCurrentLink == NULL ) + { + break; + } + else { - pCurrentLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - pCurrentLink, - _topicMatch, - &topicMatchParams ); + _EMPTY_ELSE_MARKER; + } - /* No subscription found. Exit loop. */ - if( pCurrentLink == NULL ) - { - break; - } - else - { - _EMPTY_ELSE_MARKER; - } + /* Subscription found. Calculate pointer to subscription object. */ + pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); - /* Subscription found. Calculate pointer to subscription object. */ - pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); + /* Subscription validation should not have allowed a NULL callback function. */ + IotMqtt_Assert( pSubscription->callback.function != NULL ); - /* Subscription validation should not have allowed a NULL callback function. */ - IotMqtt_Assert( pSubscription->callback.function != NULL ); + /* Increment the subscription's reference count. */ + ( pSubscription->references )++; - /* Increment the subscription's reference count. */ - ( pSubscription->references )++; + /* Copy the necessary members of the subscription before releasing the + * subscription list mutex. */ + pParam1 = pSubscription->callback.param1; + callbackFunction = pSubscription->callback.function; - /* Copy the necessary members of the subscription before releasing the - * subscription list mutex. */ - pParam1 = pSubscription->callback.param1; - callbackFunction = pSubscription->callback.function; + /* Unlock the subscription list mutex. */ + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - /* Unlock the subscription list mutex. */ - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + /* Set the members of the callback parameter. */ + pCallbackParam->mqttConnection = pMqttConnection; + pCallbackParam->message.pTopicFilter = pSubscription->pTopicFilter; + pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; - /* Set the members of the callback parameter. */ - pCallbackParam->mqttConnection = pMqttConnection; - pCallbackParam->message.pTopicFilter = pSubscription->pTopicFilter; - pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; + /* Invoke the subscription callback. */ + callbackFunction( pParam1, pCallbackParam ); - /* Invoke the subscription callback. */ - callbackFunction( pParam1, pCallbackParam ); + /* Lock the subscription list mutex to decrement the reference count. */ + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - /* Lock the subscription list mutex to decrement the reference count. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + /* Decrement the reference count. It must still be positive. */ + ( pSubscription->references )--; + IotMqtt_Assert( pSubscription->references >= 0 ); - /* Decrement the reference count. It must still be positive. */ - ( pSubscription->references )--; - IotMqtt_Assert( pSubscription->references >= 0 ); + /* Save the pointer to the next link in case this subscription is freed. */ + pNextLink = pCurrentLink->pNext; - /* Save the pointer to the next link in case this subscription is freed. */ - pNextLink = pCurrentLink->pNext; + /* Remove this subscription if it has no references and the unsubscribed + * flag is set. */ + if( pSubscription->unsubscribed == true ) + { + /* An unsubscribed subscription should have been removed from the list. */ + IotMqtt_Assert( IotLink_IsLinked( &( pSubscription->link ) ) == false ); - /* Remove this subscription if it has no references and the unsubscribed - * flag is set. */ - if( ( pSubscription->references == 0 ) && ( pSubscription->unsubscribed == true ) ) + /* Free subscriptions with no references. */ + if( pSubscription->references == 0 ) { - IotListDouble_Remove( &( pSubscription->link ) ); IotMqtt_FreeSubscription( pSubscription ); } else { _EMPTY_ELSE_MARKER; } - - /* Move current link pointer. */ - pCurrentLink = pNextLink; + } + else + { + _EMPTY_ELSE_MARKER; } - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); - } - else - { - _EMPTY_ELSE_MARKER; + /* Move current link pointer. */ + pCurrentLink = pNextLink; } + + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } /*-----------------------------------------------------------*/ @@ -512,11 +512,13 @@ void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, .order = order }; + IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), _packetMatch, ( void * ) ( &packetMatchParams ), IotMqtt_FreeSubscription, offsetof( _mqttSubscription_t, link ) ); + IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); } /*-----------------------------------------------------------*/ @@ -553,6 +555,9 @@ void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnecti /* Reference count must not be negative. */ IotMqtt_Assert( pSubscription->references >= 0 ); + /* Remove subscription from list. */ + IotListDouble_Remove( pSubscriptionLink ); + /* Check the reference count. This subscription cannot be removed if * there are subscription callbacks using it. */ if( pSubscription->references > 0 ) @@ -563,8 +568,7 @@ void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnecti } else { - /* No subscription callbacks are using this subscription. Remove it. */ - IotListDouble_Remove( &( pSubscription->link ) ); + /* Free a subscription with no references. */ IotMqtt_FreeSubscription( pSubscription ); } } @@ -585,7 +589,6 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, IotMqttSubscription_t * pCurrentSubscription ) { bool status = false; - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) mqttConnection; _mqttSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = @@ -597,10 +600,10 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, /* Prevent any other thread from modifying the subscription list while this * function is running. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Lock( &( mqttConnection->subscriptionMutex ) ); /* Search for a matching subscription. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), + pSubscriptionLink = IotListDouble_FindFirstMatch( &( mqttConnection->subscriptionList ), NULL, _topicMatch, &topicMatchParams ); @@ -630,7 +633,7 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, _EMPTY_ELSE_MARKER; } - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + IotMutex_Unlock( &( mqttConnection->subscriptionMutex ) ); return status; } diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index 5f6f621173..34b515ad75 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -37,100 +37,6 @@ /*-----------------------------------------------------------*/ -bool _IotMqtt_ValidateNetIf( const IotMqttNetIf_t * pNetworkInterface ) -{ - _IOT_FUNCTION_ENTRY( bool, true ); - - /* Check for NULL. */ - if( pNetworkInterface == NULL ) - { - IotLogError( "Network interface cannot be NULL." ); - - _IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Check for a non-NULL send function. */ - if( pNetworkInterface->send == NULL ) - { - IotLogError( "Network interface send function must be set." ); - - _IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* The MQTT 3.1.1 spec suggests disconnecting the client on errors. Check - * that a function has been provided to do this. */ - if( pNetworkInterface->disconnect == NULL ) - { - IotLogWarn( "No disconnect function was provided. The MQTT connection will not be " - "closed on errors, which is against MQTT 3.1.1 specification." ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Check that the freePacket function pointer is set if any other serializer - * override is set. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNetworkInterface->freePacket == NULL ) - { - /* Check serializer overrides. */ - if( ( pNetworkInterface->serialize.connect != NULL ) || - ( pNetworkInterface->serialize.publish != NULL ) || - ( pNetworkInterface->serialize.publishSetDup != NULL ) || - ( pNetworkInterface->serialize.puback != NULL ) || - ( pNetworkInterface->serialize.subscribe != NULL ) || - ( pNetworkInterface->serialize.unsubscribe != NULL ) || - ( pNetworkInterface->serialize.pingreq != NULL ) || - ( pNetworkInterface->serialize.disconnect != NULL ) ) - { - IotLogError( "Network interface must have a freePacket function " - "if a serializer override isn't NULL." ); - - _IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - _EMPTY_ELSE_MARKER; - } - - /* Check deserializer overrides. */ - if( ( pNetworkInterface->deserialize.connack != NULL ) || - ( pNetworkInterface->deserialize.puback != NULL ) || - ( pNetworkInterface->deserialize.publish != NULL ) || - ( pNetworkInterface->deserialize.suback != NULL ) || - ( pNetworkInterface->deserialize.unsuback != NULL ) || - ( pNetworkInterface->deserialize.pingresp != NULL ) ) - { - IotLogError( "Network interface must have a freePacket function " - "if a deserializer override isn't NULL." ); - - _IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - _EMPTY_ELSE_MARKER; - } - } - else - { - _EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - - _IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { _IOT_FUNCTION_ENTRY( bool, true ); diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 9e8586eedf..e4b46c8ddb 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -41,6 +41,9 @@ /* JSON utilities include. */ #include "iot_json_utils.h" +/* MQTT include. */ +#include "iot_mqtt.h" + /* Validate Shadow configuration settings. */ #if AWS_IOT_SHADOW_ENABLE_ASSERTS != 0 && AWS_IOT_SHADOW_ENABLE_ASSERTS != 1 #error "AWS_IOT_SHADOW_ENABLE_ASSERTS must be 0 or 1." diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index de28da6c8b..f4118bec28 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -41,6 +41,9 @@ /* JSON utils include. */ #include "iot_json_utils.h" +/* MQTT include. */ +#include "iot_mqtt.h" + /*-----------------------------------------------------------*/ /** diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index afedac2c51..d9e5bfd84f 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -39,6 +39,9 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" +/* MQTT include. */ +#include "iot_mqtt.h" + /*-----------------------------------------------------------*/ /** diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index b78a7e2673..ad60eff192 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -55,29 +55,10 @@ /** * @brief Represents a network connection that uses OpenSSL. * - * All instances of #IotNetworkConnectionOpenssl_t should be initialized with - * #IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER. - * - * @attention The members of this struct are intended to be opaque and may change - * at any time. This struct should only be passed as the connection handle to the - * functions declared in this file. Do not directly modify its members! + * This is an incomplete type. In application code, only pointers to this type + * should be used. */ -typedef struct IotNetworkConnectionOpenssl -{ - int socket; /**< @brief Socket associated with this connection. */ - SSL * pSslContext; /**< @brief SSL context for connection. */ - IotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ - - /** @brief Status of the receive thread for this connection. */ - enum - { - _NONE = 0, _ACTIVE, _TERMINATED - } receiveThreadStatus; - pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ - - IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ - void * pReceiveContext; /**< @brief The context for the receive callback. */ -} IotNetworkConnectionOpenssl_t; +typedef struct _networkConnection IotNetworkConnectionOpenssl_t; /** * @brief Information on the remote server for connection setup. @@ -135,19 +116,6 @@ typedef struct IotNetworkCredentialsOpenssl const char * pPrivateKeyPath; /**< @brief Filesystem path of the client certificate's private key. */ } IotNetworkCredentialsOpenssl_t; -/** - * @brief Provides a default value for an #IotNetworkConnectionOpenssl_t. - * - * All instances of #IotNetworkConnectionOpenssl_t should be initialized with - * this constant. - * - * @warning Failing to initialize an #IotNetworkConnectionOpenssl_t with this - * initializer may result in undefined behavior! - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER { 0 } - /** * @brief Provides a default value for an #IotNetworkServerInfoOpenssl_t. * @@ -219,7 +187,7 @@ void IotNetworkOpenssl_Cleanup( void ); */ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, void * pCredentialInfo, - void * const pConnection ); + void * pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for @@ -242,7 +210,7 @@ size_t IotNetworkOpenssl_Send( void * pConnection, * with OpenSSL. */ size_t IotNetworkOpenssl_Receive( void * pConnection, - uint8_t * const pBuffer, + uint8_t * pBuffer, size_t bytesRequested ); /** diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 5eab107348..a6c20c977d 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -75,7 +75,9 @@ endif() add_library( iotplatform SHARED iot_clock_posix.c iot_threads_posix.c - ${NETWORK_SOURCE_FILE} ) + ${NETWORK_SOURCE_FILE} + linux/iot_metrics.c + linux/iot_network_openssl_metrics.c ) # Library version. set_target_properties( iotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index b5e0b65053..9d751d2cdb 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -106,7 +106,7 @@ static void _timerExpirationWrapper( union sigval argument ); * @return `true` if `timeoutMs` was successfully converted; `false` otherwise. */ bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, - struct timespec * const pOutput ); + struct timespec * pOutput ); /*-----------------------------------------------------------*/ @@ -121,7 +121,7 @@ static void _timerExpirationWrapper( union sigval argument ) /*-----------------------------------------------------------*/ bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, - struct timespec * const pOutput ) + struct timespec * pOutput ) { bool status = true; struct timespec systemTime = { 0 }; @@ -156,9 +156,9 @@ bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, /*-----------------------------------------------------------*/ -bool IotClock_GetTimestring( char * const pBuffer, +bool IotClock_GetTimestring( char * pBuffer, size_t bufferSize, - size_t * const pTimestringLength ) + size_t * pTimestringLength ) { bool status = true; const time_t unixTime = time( NULL ); @@ -210,7 +210,24 @@ uint64_t IotClock_GetTimeMs( void ) /*-----------------------------------------------------------*/ -bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, +void IotClock_SleepMs( uint32_t sleepTimeMs ) +{ + /* Convert parameter to timespec. */ + struct timespec sleepTime = + { + .tv_sec = sleepTimeMs / _MILLISECONDS_PER_SECOND, + .tv_nsec = ( sleepTimeMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND + }; + + if( nanosleep( &sleepTime, NULL ) == -1 ) + { + IotLogWarn( "Sleep failed. errno=%d.", errno ); + } +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerCreate( IotTimer_t * pNewTimer, IotThreadRoutine_t expirationRoutine, void * pArgument ) { @@ -244,7 +261,7 @@ bool IotClock_TimerCreate( IotTimer_t * const pNewTimer, /*-----------------------------------------------------------*/ -void IotClock_TimerDestroy( IotTimer_t * const pTimer ) +void IotClock_TimerDestroy( IotTimer_t * pTimer ) { IotLogDebug( "Destroying timer %p.", pTimer ); @@ -257,7 +274,7 @@ void IotClock_TimerDestroy( IotTimer_t * const pTimer ) /*-----------------------------------------------------------*/ -bool IotClock_TimerArm( IotTimer_t * const pTimer, +bool IotClock_TimerArm( IotTimer_t * pTimer, uint64_t relativeTimeoutMs, uint64_t periodMs ) { diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index b52090cf1f..0495e4bca4 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -54,6 +54,9 @@ /* Platform threads include. */ #include "platform/iot_threads.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM @@ -103,7 +106,7 @@ static void * _threadRoutineWrapper( void * pArgument ); /* Platform-specific function implemented in iot_clock_posix.c */ extern bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, - struct timespec * const pOutput ); + struct timespec * pOutput ); /*-----------------------------------------------------------*/ @@ -141,182 +144,76 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, int32_t priority, size_t stackSize ) { - bool status = true; + _IOT_FUNCTION_ENTRY( bool, true ); int posixErrno = 0; + bool threadAttibutesCreated = false; _threadInfo_t * pThreadInfo = NULL; pthread_t newThread; pthread_attr_t threadAttributes; - IotLogDebug( "Creating new thread." ); - - /* Check priority if a non-default scheduling priority is given. */ - if( priority != IOT_THREAD_DEFAULT_PRIORITY ) - { - if( priority < 0 ) - { - IotLogError( "Priority %ld is not valid for a new thread.", - ( long int ) priority ); - status = false; - } - else if( ( int ) priority < sched_get_priority_min( SCHED_RR ) ) - { - IotLogError( "Priority %ld is less than minimum allowed %d.", - sched_get_priority_min( SCHED_RR ) ); - status = false; - } - else if( ( int ) priority > sched_get_priority_max( SCHED_RR ) ) - { - IotLogError( "Priority %ld is greater than maximum allowed %d.", - sched_get_priority_max( SCHED_RR ) ); - status = false; - } - else - { - IotLogInfo( "New thread will have priority %ld with SCHED_RR policy.", - ( long int ) priority ); - } - } + /* Ignore priority and stack size. */ + ( void ) priority; + ( void ) stackSize; - /* Check stack size if a non-default value is given. */ - if( status == true ) - { - /* Adjust stack size based on system minimum. */ - if( stackSize != IOT_THREAD_DEFAULT_STACK_SIZE ) - { - if( stackSize < PTHREAD_STACK_MIN ) - { - IotLogWarn( "Stack size of %lu is smaller than system minimum %d." - " System minimum will be used instead.", - ( unsigned long ) stackSize, - PTHREAD_STACK_MIN ); + IotLogDebug( "Creating new thread." ); - stackSize = PTHREAD_STACK_MIN; - } - } - } + /* Allocate memory for the new thread. */ + pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); - if( status == true ) + if( pThreadInfo == NULL ) { - /* Allocate memory for the new thread. */ - pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); + IotLogError( "Failed to allocate memory for new thread." ); - if( pThreadInfo == NULL ) - { - IotLogError( "Failed to allocate memory for new thread." ); - - status = false; - } + _IOT_SET_AND_GOTO_CLEANUP( false ); } /* Set up thread attributes object. */ - if( status == true ) + posixErrno = pthread_attr_init( &threadAttributes ); + + if( posixErrno != 0 ) { - /* Create a new thread attributes object. */ - posixErrno = pthread_attr_init( &threadAttributes ); + IotLogError( "Failed to initialize thread attributes. errno=%d.", + posixErrno ); - if( posixErrno != 0 ) - { - IotLogError( "Failed to initialize thread attributes. errno=%d.", - posixErrno ); + _IOT_SET_AND_GOTO_CLEANUP( false ); + } - status = false; - } - else - { - /* Set the new thread to detached. */ - posixErrno = pthread_attr_setdetachstate( &threadAttributes, - PTHREAD_CREATE_DETACHED ); + threadAttibutesCreated = true; - if( posixErrno != 0 ) - { - IotLogError( "Failed to set detached thread attribute. errno=%d.", - posixErrno ); + /* Set the new thread to detached. */ + posixErrno = pthread_attr_setdetachstate( &threadAttributes, + PTHREAD_CREATE_DETACHED ); - status = false; - } + if( posixErrno != 0 ) + { + IotLogError( "Failed to set detached thread attribute. errno=%d.", + posixErrno ); - /* Set the stack size if given. */ - if( status == true ) - { - if( stackSize != IOT_THREAD_DEFAULT_STACK_SIZE ) - { - posixErrno = pthread_attr_setstacksize( &threadAttributes, - stackSize ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set stack size %lu.", ( unsigned long ) stackSize ); - - status = false; - } - } - } + _IOT_SET_AND_GOTO_CLEANUP( false ); + } - /* Set scheduler policy and priority if given. */ - if( status == true ) - { - if( priority != IOT_THREAD_DEFAULT_PRIORITY ) - { - struct sched_param schedulerParam = - { - .sched_priority = ( int ) priority - }; - - /* Set scheduler policy SCHED_RR. */ - posixErrno = pthread_attr_setschedpolicy( &threadAttributes, - SCHED_RR ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set scheduling policy SCHED_RR." ); - - status = false; - } - - /* Set scheduler priority. */ - if( status == true ) - { - posixErrno = pthread_attr_setschedparam( &threadAttributes, - &schedulerParam ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set scheduling priority %d.", - schedulerParam.sched_priority ); - - status = false; - } - } - } - } + /* Set the thread routine and argument. */ + pThreadInfo->threadRoutine = threadRoutine; + pThreadInfo->pArgument = pArgument; - if( status == false ) - { - ( void ) pthread_attr_destroy( &threadAttributes ); - } - } - } + /* Create the underlying POSIX thread. */ + posixErrno = pthread_create( &newThread, + &threadAttributes, + _threadRoutineWrapper, + pThreadInfo ); - if( status == true ) + if( posixErrno != 0 ) { - /* Set the thread routine and argument. */ - pThreadInfo->threadRoutine = threadRoutine; - pThreadInfo->pArgument = pArgument; - - /* Create the underlying POSIX thread. */ - posixErrno = pthread_create( &newThread, - &threadAttributes, - _threadRoutineWrapper, - pThreadInfo ); + IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); - if( posixErrno != 0 ) - { - IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); + _IOT_SET_AND_GOTO_CLEANUP( false ); + } - status = false; - } + _IOT_FUNCTION_CLEANUP_BEGIN(); - /* Destroy thread attributes object. */ + /* Destroy thread attributes object. */ + if( threadAttibutesCreated == true ) + { posixErrno = pthread_attr_destroy( &threadAttributes ); if( posixErrno != 0 ) @@ -326,6 +223,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, } } + /* Clean up on error. */ if( status == false ) { if( pThreadInfo != NULL ) @@ -334,16 +232,17 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, } } - return status; + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ -bool IotMutex_Create( IotMutex_t * const pNewMutex, bool recursive ) +bool IotMutex_Create( IotMutex_t * pNewMutex, + bool recursive ) { bool status = true; int mutexError = 0; - pthread_mutexattr_t mutexAttributes, *pMutexAttributes = NULL; + pthread_mutexattr_t mutexAttributes, * pMutexAttributes = NULL; if( recursive == true ) { @@ -404,7 +303,7 @@ bool IotMutex_Create( IotMutex_t * const pNewMutex, bool recursive ) /*-----------------------------------------------------------*/ -void IotMutex_Destroy( IotMutex_t * const pMutex ) +void IotMutex_Destroy( IotMutex_t * pMutex ) { IotLogDebug( "Destroying mutex %p.", pMutex ); @@ -420,7 +319,7 @@ void IotMutex_Destroy( IotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -void IotMutex_Lock( IotMutex_t * const pMutex ) +void IotMutex_Lock( IotMutex_t * pMutex ) { IotLogDebug( "Locking mutex %p.", pMutex ); @@ -436,7 +335,7 @@ void IotMutex_Lock( IotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -bool IotMutex_TryLock( IotMutex_t * const pMutex ) +bool IotMutex_TryLock( IotMutex_t * pMutex ) { bool status = true; @@ -458,7 +357,7 @@ bool IotMutex_TryLock( IotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -void IotMutex_Unlock( IotMutex_t * const pMutex ) +void IotMutex_Unlock( IotMutex_t * pMutex ) { IotLogDebug( "Unlocking mutex %p.", pMutex ); @@ -474,7 +373,7 @@ void IotMutex_Unlock( IotMutex_t * const pMutex ) /*-----------------------------------------------------------*/ -bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, +bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, uint32_t initialValue, uint32_t maxValue ) { @@ -506,7 +405,7 @@ bool IotSemaphore_Create( IotSemaphore_t * const pNewSemaphore, /*-----------------------------------------------------------*/ -uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ) +uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) { int count = 0; @@ -524,7 +423,7 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * const pSemaphore ) /*-----------------------------------------------------------*/ -void IotSemaphore_Destroy( IotSemaphore_t * const pSemaphore ) +void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) { IotLogDebug( "Destroying semaphore %p.", pSemaphore ); @@ -552,7 +451,7 @@ void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) /*-----------------------------------------------------------*/ -bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ) +bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) { bool status = true; @@ -572,7 +471,7 @@ bool IotSemaphore_TryWait( IotSemaphore_t * const pSemaphore ) /*-----------------------------------------------------------*/ -bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, uint64_t timeoutMs ) { bool status = true; @@ -605,7 +504,7 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * const pSemaphore, /*-----------------------------------------------------------*/ -void IotSemaphore_Post( IotSemaphore_t * const pSemaphore ) +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) { IotLogDebug( "Posting to semaphore %p.", pSemaphore ); diff --git a/lib/source/metrics/iot_metrics.c b/platform/source/posix/linux/iot_metrics.c similarity index 87% rename from lib/source/metrics/iot_metrics.c rename to platform/source/posix/linux/iot_metrics.c index fa35e050c0..4114342f01 100644 --- a/lib/source/metrics/iot_metrics.c +++ b/platform/source/posix/linux/iot_metrics.c @@ -25,18 +25,20 @@ #endif /* Metrics include. */ -#include "iot_metrics.h" +#include "platform/iot_metrics.h" /* Platform threads include. */ #include "platform/iot_threads.h" -static IotListDouble_t _connectionsList = IOT_LIST_DOUBLE_INITIALIZER; -static IotMutex_t _mutex; - /* Compare function to identify the TCP connection id. */ static bool _tcpConnectionMatch( const IotLink_t * pLink, void * pId ); +/*------------------- Global Variables ------------------------*/ + +static IotListDouble_t _connectionsList = IOT_LIST_DOUBLE_INITIALIZER; +static IotMutex_t _mutex; + /*-----------------------------------------------------------*/ static bool _tcpConnectionMatch( const IotLink_t * pLink, @@ -45,7 +47,7 @@ static bool _tcpConnectionMatch( const IotLink_t * pLink, IotMetrics_Assert( pLink != NULL ); IotMetrics_Assert( pId != NULL ); - return *( ( uint32_t * ) pId ) == IotLink_Container( IotMetricsTcpConnection_t, pLink, link )->id; + return *( ( IotMetricsConnectionId_t * ) pId ) == IotLink_Container( IotMetricsTcpConnection_t, pLink, link )->id; } /*-----------------------------------------------------------*/ @@ -94,7 +96,7 @@ void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ) /*-----------------------------------------------------------*/ -void IotMetrics_RemoveTcpConnection( uint32_t tcpConnectionId ) +void IotMetrics_RemoveTcpConnection( IotMetricsConnectionId_t tcpConnectionId ) { IotMutex_Lock( &_mutex ); @@ -115,16 +117,15 @@ void IotMetrics_RemoveTcpConnection( uint32_t tcpConnectionId ) void IotMetrics_ProcessTcpConnections( IotMetricsListCallback_t tcpConnectionsCallback ) { - /* If no callback function is provided, simply return. */ - if( tcpConnectionsCallback.function == NULL ) + if( tcpConnectionsCallback.function != NULL ) { - return; - } + IotMutex_Lock( &_mutex ); - IotMutex_Lock( &_mutex ); + /* Execute the callback function. */ + tcpConnectionsCallback.function( tcpConnectionsCallback.param1, &_connectionsList ); - /* Execute the callback function. */ - tcpConnectionsCallback.function( tcpConnectionsCallback.param1, &_connectionsList ); + IotMutex_Unlock( &_mutex ); + } - IotMutex_Unlock( &_mutex ); + /* If no callback function is provided, simply return. */ } diff --git a/platform/source/posix/linux/iot_network_openssl_metrics.c b/platform/source/posix/linux/iot_network_openssl_metrics.c new file mode 100644 index 0000000000..afb67aa892 --- /dev/null +++ b/platform/source/posix/linux/iot_network_openssl_metrics.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * This file servers as an example. + * It tracks the socket metrics in network interface layer and uses openssl as underlying network. + */ + +/* Linux network includes. */ +#include +#include +#include +#include + +/* Network include. */ +#include "platform/iot_network.h" +#include "posix/iot_network_openssl.h" + +/* Metrics include. */ +#include "platform/iot_metrics.h" + +/* Metrics wrapper of create interface. */ +static IotNetworkError_t _metricsCreate( void * pConnectionInfo, + void * pCredentialInfo, + void * pConnection ); + +/* Metrics wrapper of close interface. */ +static IotNetworkError_t _metricsClose( void * pConnection ); + +/* Metrics network interface which is on top of openssl network interface. */ +const IotNetworkInterface_t _IotNetworkOpensslMetrics = +{ + .create = _metricsCreate, + .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .send = IotNetworkOpenssl_Send, + .receive = IotNetworkOpenssl_Receive, + .close = _metricsClose, + .destroy = IotNetworkOpenssl_Destroy +}; + +/*-----------------------------------------------------------*/ + +static IotNetworkError_t _metricsCreate( void * pConnectionInfo, + void * pCredentialInfo, + void * pConnection ) +{ + /* Call the openssl create. */ + IotNetworkError_t error = IotNetworkOpenssl_Create( pConnectionInfo, pCredentialInfo, pConnection ); + + /* Add socket metrics only if creation succeeded. */ + if( error == IOT_NETWORK_SUCCESS ) + { + /* This is an hacky to to get the socket integer value. Not recommended in production code. */ + int socket = **( ( int ** ) pConnection ); + + /* Assume this is Ipv4. */ + struct sockaddr_in socketAddressIpv4; + socklen_t socklen = sizeof( struct sockaddr_in ); + + /* Initialize a tcp connection with socket as id. */ + IotMetricsTcpConnection_t connection = { .id = socket }; + + /* Get the ip and port from the peer socket. */ + int ret = getpeername( socket, ( struct sockaddr * ) &socketAddressIpv4, &socklen ); + + /* Assert its succees. */ + IotMetrics_Assert( ret == 0 ); + + /* Convert ip value from integer(network byte order) to string. */ + connection.pRemoteIP = inet_ntoa( socketAddressIpv4.sin_addr ); + + /* Convert port value from network byte order to host byte order. */ + connection.remotePort = ntohs( socketAddressIpv4.sin_port ); + + /* Add this metircs, a established TCP connection. */ + IotMetrics_AddTcpConnection( &connection ); + } + + return error; +} + +/*-----------------------------------------------------------*/ + +static IotNetworkError_t _metricsClose( void * pConnection ) +{ + /* This is an hacky to to get the socket integer value. Not recommended in production code. */ + int socket = *( ( int * ) pConnection ); + + /* Call the openssl close. */ + IotNetworkError_t error = IotNetworkOpenssl_Close( pConnection ); + + /* Remove socket metrics only if close succeeded. */ + if( error == IOT_NETWORK_SUCCESS ) + { + /* Remove this metircs by its id. */ + IotMetrics_RemoveTcpConnection( socket ); + } + + return error; +} diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index ab351b5b0f..370230d920 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -58,8 +58,8 @@ /* Platform threads include. */ #include "platform/iot_threads.h" -/* Metrics include. */ -#include "iot_metrics.h" +/* Error handling include. */ +#include "private/iot_error.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_NETWORK @@ -76,15 +76,14 @@ #include "iot_logging_setup.h" /* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. + * Provide default values for undefined memory allocation functions. */ #ifndef IotNetwork_Malloc #include /** - * @brief Memory allocation. This function should have the same signature as - * [malloc.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ #define IotNetwork_Malloc malloc #endif @@ -93,13 +92,35 @@ /** * @brief Free memory. This function should have the same signature as - * [free.](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ #define IotNetwork_Free free #endif /*-----------------------------------------------------------*/ +/** + * @brief Represents a network connection. + */ +typedef struct _networkConnection +{ + int socket; /**< @brief Socket associated with this connection. */ + SSL * pSslContext; /**< @brief SSL context for connection. */ + IotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ + + /** @brief Status of the receive thread for this connection. */ + enum + { + _NONE = 0, _ACTIVE, _TERMINATED + } receiveThreadStatus; + pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ + + IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ + void * pReceiveContext; /**< @brief The context for the receive callback. */ +} _networkConnection_t; + +/*-----------------------------------------------------------*/ + /** * @brief An #IotNetworkInterface_t that uses the functions in this file. */ @@ -125,17 +146,14 @@ const IotNetworkInterface_t _IotNetworkOpenssl = */ static void * _networkReceiveThread( void * pArgument ) { - int bytesAvailable = 0, bytesRead = 0; - int32_t bytesProcessed = 0; - uint8_t * pReceiveBuffer = NULL; struct pollfd fileDescriptor = { - .events = POLLIN, + .events = POLLIN | POLLPRI, .revents = 0 }; /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pArgument; + _networkConnection_t * const pNetworkConnection = pArgument; /* Set the file descriptor for poll. */ fileDescriptor.fd = pNetworkConnection->socket; @@ -160,112 +178,15 @@ static void * _networkReceiveThread( void * pArgument ) break; } - /* Query how many bytes are available to read. The value returned by this - * function may include more than just the data. */ - if( ioctl( pNetworkConnection->socket, FIONREAD, &bytesAvailable ) != 0 ) - { - IotLogError( "Failed to query bytes available on socket. errno=%d.", - errno ); - - /* Unlock the connection mutex before polling again. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - - continue; - } - - IotLogDebug( "%d bytes available on socket %d.", - bytesAvailable, - pNetworkConnection->socket ); - - /* If no bytes can be read, the socket is likely closed. Terminate this thread. */ - if( bytesAvailable == 0 ) - { - IotLogInfo( "No data available, terminating receive thread for socket %d.", - pNetworkConnection->socket ); - break; - } - - /* Allocate memory to hold the received message. */ - pReceiveBuffer = IotNetwork_Malloc( ( size_t ) bytesAvailable ); - - if( pReceiveBuffer == NULL ) - { - IotLogError( "Failed to allocate %d bytes for socket read on %d.", - bytesAvailable, - pNetworkConnection->socket ); - - /* Terminate on memory allocation failure to prevent unread incoming - * data from accumulating. */ - break; - } - - /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on - * whether the SSL connection context is set up. */ - if( pNetworkConnection->pSslContext != NULL ) - { - bytesRead = SSL_read( pNetworkConnection->pSslContext, - pReceiveBuffer, - bytesAvailable ); - } - else - { - bytesRead = ( int ) recv( pNetworkConnection->socket, - pReceiveBuffer, - ( size_t ) bytesAvailable, - 0 ); - } - - /* Check how many bytes were read. */ - if( bytesRead <= 0 ) - { - IotLogError( "Error reading from socket %d.", - pNetworkConnection->socket ); - - IotNetwork_Free( pReceiveBuffer ); - break; - } - - IotLogDebug( "Received %d bytes from socket %d.", - bytesRead, - pNetworkConnection->socket ); - /* Invoke the callback function. But if there's no callback to invoke, * terminate this thread. */ if( pNetworkConnection->receiveCallback != NULL ) { - bytesProcessed = pNetworkConnection->receiveCallback( pNetworkConnection->pReceiveContext, - pNetworkConnection, - pReceiveBuffer, - ( size_t ) bytesRead, - 0, - IotNetwork_Free ); + pNetworkConnection->receiveCallback( pNetworkConnection, + pNetworkConnection->pReceiveContext ); } else { - /* Free resources and terminate thread. */ - IotNetwork_Free( pReceiveBuffer ); - - break; - } - - /* Free resources and terminate thread if some data was unprocessed. */ - if( bytesProcessed != ( int32_t ) bytesRead ) - { - if( bytesProcessed < 0 ) - { - IotLogError( "Receive callback for socket %d returned error." - "Receive thread terminating.", pNetworkConnection->socket ); - } - else - { - /* This implementation should never read incomplete packets - * because it is able to allocate buffers large enough for - * entire streams. */ - IotLogError( "Incomplete data received from network on socket %d.", - pNetworkConnection->socket ); - } - - IotNetwork_Free( pReceiveBuffer ); break; } @@ -293,13 +214,13 @@ static void * _networkReceiveThread( void * pArgument ) * @brief Perform a DNS lookup of a host name and establish a TCP connection. * * @param[in] pServerInfo Server host name and port. - * @param[out] pIpAddress Ip address number in network byte order. * * @return A connected TCP socket number; `-1` if the DNS lookup failed. */ -static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServerInfo, uint32_t * pIpAddress ) +static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) { - int status = 0, tcpSocket = -1; + _IOT_FUNCTION_ENTRY( int, 0 ); + int tcpSocket = -1; const uint16_t netPort = htons( pServerInfo->port ); struct addrinfo * pListHead = NULL, * pAddressInfo = NULL; struct sockaddr_in * pServer = NULL; @@ -312,7 +233,7 @@ static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServe { IotLogError( "DNS lookup failed. %s.", gai_strerror( status ) ); - return -1; + _IOT_SET_AND_GOTO_CLEANUP( -1 ); } IotLogDebug( "Successfully received DNS records." ); @@ -355,27 +276,36 @@ static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServe } else { - /* Copy IP address to the output parameter. */ - *pIpAddress = pServer->sin_addr.s_addr; - /* Connection successful; stop searching the list. */ IotLogDebug( "Socket connection successful." ); break; } } - freeaddrinfo( pListHead ); - /* If pAddressInfo is NULL, then the entire list of records was parsed but none * of them provided a successful connection. */ if( pAddressInfo == NULL ) { IotLogError( "Failed to connect to all retrieved DNS records." ); - tcpSocket = -1; + _IOT_SET_AND_GOTO_CLEANUP( -1 ); } - return tcpSocket; + _IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Free DNS records. */ + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + /* Return the socket descriptor on success. */ + if( status == 0 ) + { + status = tcpSocket; + } + + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -385,19 +315,19 @@ static inline int _dnsLookup( const IotNetworkServerInfoOpenssl_t * const pServe * * Uses OpenSSL to import the root CA certificate, client certificate, and * client certificate private key. - * @param[in] pSSLContext Destination for the imported credentials. + * @param[in] pSslContext Destination for the imported credentials. * @param[in] pRootCAPath Path to the root CA certificate. * @param[in] pClientCertPath Path to the client certificate. * @param[in] pCertPrivateKeyPath Path to the client certificate private key. * * @return `true` if all credentials were successfully read; `false` otherwise. */ -static inline bool _readCredentials( SSL_CTX * pSSLContext, - const char * const pRootCAPath, - const char * const pClientCertPath, - const char * const pCertPrivateKeyPath ) +static bool _readCredentials( SSL_CTX * pSslContext, + const char * pRootCAPath, + const char * pClientCertPath, + const char * pCertPrivateKeyPath ) { - bool status = true; + _IOT_FUNCTION_ENTRY( bool, true ); X509 * pRootCa = NULL; /* OpenSSL does not provide a single function for reading and loading certificates @@ -409,82 +339,70 @@ static inline bool _readCredentials( SSL_CTX * pSSLContext, { IotLogError( "Failed to open %s", pRootCAPath ); - status = false; + _IOT_SET_AND_GOTO_CLEANUP( false ); } /* Read the root CA into an X509 object, then close its file handle. */ - if( status == true ) - { - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - if( fclose( pRootCaFile ) != 0 ) - { - IotLogWarn( "Failed to close file %s", pRootCAPath ); - } + if( fclose( pRootCaFile ) != 0 ) + { + IotLogWarn( "Failed to close file %s", pRootCAPath ); + } - if( pRootCa == NULL ) - { - IotLogError( "Failed to parse root CA." ); + if( pRootCa == NULL ) + { + IotLogError( "Failed to parse root CA." ); - status = false; - } - else - { - /* Add the root CA to certificate store. */ - if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSSLContext ), - pRootCa ) != 1 ) - { - IotLogError( "Failed to add root CA to certificate store." ); + _IOT_SET_AND_GOTO_CLEANUP( false ); + } - status = false; - } - else - { - IotLogInfo( "Successfully imported root CA." ); - } + /* Add the root CA to certificate store. */ + if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), + pRootCa ) != 1 ) + { + IotLogError( "Failed to add root CA to certificate store." ); - /* Free the root CA object. */ - X509_free( pRootCa ); - } + _IOT_SET_AND_GOTO_CLEANUP( false ); } + IotLogInfo( "Successfully imported root CA." ); + /* Import the client certificate. */ - if( status == true ) + if( SSL_CTX_use_certificate_file( pSslContext, + pClientCertPath, + SSL_FILETYPE_PEM ) != 1 ) { - if( SSL_CTX_use_certificate_file( pSSLContext, - pClientCertPath, - SSL_FILETYPE_PEM ) != 1 ) - { - IotLogError( "Failed to import client certificate at %s", - pClientCertPath ); + IotLogError( "Failed to import client certificate at %s", + pClientCertPath ); - status = false; - } - else - { - IotLogInfo( "Successfully imported client certificate." ); - } + _IOT_SET_AND_GOTO_CLEANUP( false ); } + IotLogInfo( "Successfully imported client certificate." ); + /* Import the client certificate private key. */ - if( status == true ) + if( SSL_CTX_use_PrivateKey_file( pSslContext, + pCertPrivateKeyPath, + SSL_FILETYPE_PEM ) != 1 ) { - if( SSL_CTX_use_PrivateKey_file( pSSLContext, - pCertPrivateKeyPath, - SSL_FILETYPE_PEM ) != 1 ) - { - IotLogError( "Failed to import client certificate private key at %s", - pCertPrivateKeyPath ); + IotLogError( "Failed to import client certificate private key at %s", + pCertPrivateKeyPath ); - status = false; - } - else - { - IotLogInfo( "Successfully imported client certificate private key." ); - } + _IOT_SET_AND_GOTO_CLEANUP( false ); } - return status; + IotLogInfo( "Successfully imported client certificate private key." ); + + _IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Free the root CA object. */ + if( pRootCa != NULL ) + { + X509_free( pRootCa ); + } + + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -498,171 +416,158 @@ static inline bool _readCredentials( SSL_CTX * pSSLContext, * * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. */ -static inline IotNetworkError_t _tlsSetup( IotNetworkConnectionOpenssl_t * const pNetworkConnection, - const char * const pServerName, - const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials ) +static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, + const char * pServerName, + const IotNetworkCredentialsOpenssl_t * pOpensslCredentials ) { - SSL_CTX * pSSLContext = NULL; - IotNetworkError_t status = IOT_NETWORK_SUCCESS; + _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + SSL_CTX * pSslContext = NULL; /* Create a new SSL context. */ #if OPENSSL_VERSION_NUMBER < 0x10100000L - pSSLContext = SSL_CTX_new( TLSv1_2_client_method() ); + pSslContext = SSL_CTX_new( TLSv1_2_client_method() ); #else - pSSLContext = SSL_CTX_new( TLS_client_method() ); + pSslContext = SSL_CTX_new( TLS_client_method() ); #endif - if( pSSLContext == NULL ) + if( pSslContext == NULL ) { IotLogError( "Failed to create new SSL context." ); - status = IOT_NETWORK_SYSTEM_ERROR; + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } - else + + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The + * mask returned by SSL_CTX_set_mode does not need to be checked. */ + IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); + ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); + + /* Import all credentials. */ + if( _readCredentials( pSslContext, + pOpensslCredentials->pRootCaPath, + pOpensslCredentials->pClientCertPath, + pOpensslCredentials->pPrivateKeyPath ) == false ) { - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The - * mask returned by SSL_CTX_set_mode does not need to be checked. */ - IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); - ( void ) SSL_CTX_set_mode( pSSLContext, SSL_MODE_AUTO_RETRY ); - - /* Import all credentials. */ - if( _readCredentials( pSSLContext, - pOpensslCredentials->pRootCaPath, - pOpensslCredentials->pClientCertPath, - pOpensslCredentials->pPrivateKeyPath ) == false ) - { - status = IOT_NETWORK_FAILURE; - } - else - { - /* Create a new SSL connection context */ - pNetworkConnection->pSslContext = SSL_new( pSSLContext ); + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } - if( pNetworkConnection->pSslContext == NULL ) - { - IotLogError( "Failed to create new SSL connection context." ); + /* Create a new SSL connection context */ + pNetworkConnection->pSslContext = SSL_new( pSslContext ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - } + if( pNetworkConnection->pSslContext == NULL ) + { + IotLogError( "Failed to create new SSL connection context." ); - SSL_CTX_free( pSSLContext ); + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } - if( status == IOT_NETWORK_SUCCESS ) + /* Enable SSL peer verification. */ + IotLogDebug( "Setting SSL_VERIFY_PEER." ); + SSL_set_verify( pNetworkConnection->pSslContext, SSL_VERIFY_PEER, NULL ); + + /* Set the socket for the SSL connection. */ + if( SSL_set_fd( pNetworkConnection->pSslContext, + pNetworkConnection->socket ) != 1 ) { - /* Enable SSL peer verification. */ - IotLogDebug( "Setting SSL_VERIFY_PEER." ); - SSL_set_verify( pNetworkConnection->pSslContext, SSL_VERIFY_PEER, NULL ); + IotLogError( "Failed to set SSL socket %d.", pNetworkConnection->socket ); - /* Set the socket for the SSL connection. */ - if( SSL_set_fd( pNetworkConnection->pSslContext, - pNetworkConnection->socket ) != 1 ) - { - IotLogError( "Failed to set SSL socket %d.", pNetworkConnection->socket ); - status = IOT_NETWORK_SYSTEM_ERROR; - } + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + } - /* Set up ALPN if requested. */ - if( status == IOT_NETWORK_SUCCESS ) - { - if( pOpensslCredentials->pAlpnProtos != NULL ) - { - IotLogDebug( "Setting ALPN protos." ); - - if( ( SSL_set_alpn_protos( pNetworkConnection->pSslContext, - ( const unsigned char * ) pOpensslCredentials->pAlpnProtos, - ( unsigned int ) strlen( pOpensslCredentials->pAlpnProtos ) ) != 0 ) ) - { - IotLogError( "Failed to set ALPN protos." ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - } - } + /* Set up ALPN if requested. */ + if( pOpensslCredentials->pAlpnProtos != NULL ) + { + IotLogDebug( "Setting ALPN protos." ); - /* Set TLS MFLN if requested. */ - if( status == IOT_NETWORK_SUCCESS ) + if( ( SSL_set_alpn_protos( pNetworkConnection->pSslContext, + ( const unsigned char * ) pOpensslCredentials->pAlpnProtos, + ( unsigned int ) strlen( pOpensslCredentials->pAlpnProtos ) ) != 0 ) ) { - if( pOpensslCredentials->maxFragmentLength > 0 ) - { - IotLogDebug( "Setting max send fragment length %lu.", - ( unsigned long ) pOpensslCredentials->maxFragmentLength ); - - /* Set the maximum send fragment length. */ - if( SSL_set_max_send_fragment( pNetworkConnection->pSslContext, - ( long ) pOpensslCredentials->maxFragmentLength ) != 1 ) - { - IotLogError( "Failed to set max send fragment length %lu.", - ( unsigned long ) pOpensslCredentials->maxFragmentLength ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - else - { - /* In supported versions of OpenSSL, change the size of the read buffer - * to match the maximum fragment length + some extra bytes for overhead. - * Note that OpenSSL ignores this setting if it's smaller than the default. - */ - #if OPENSSL_VERSION_NUMBER > 0x10100000L - SSL_set_default_read_buffer_len( pNetworkConnection->pSslContext, - pOpensslCredentials->maxFragmentLength + - SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); - #endif - } - } - } + IotLogError( "Failed to set ALPN protos." ); - if( status == IOT_NETWORK_SUCCESS ) - { - if( pOpensslCredentials->disableSni == false ) - { - IotLogDebug( "Setting server name %s for SNI.", pServerName ); - - if( SSL_set_tlsext_host_name( pNetworkConnection->pSslContext, - pServerName ) != 1 ) - { - IotLogError( "Failed to set server name %s for SNI.", pServerName ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - } + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } + } - /* Perform the TLS handshake. */ - if( status == IOT_NETWORK_SUCCESS ) + /* Set TLS MFLN if requested. */ + if( pOpensslCredentials->maxFragmentLength > 0 ) + { + IotLogDebug( "Setting max send fragment length %lu.", + ( unsigned long ) pOpensslCredentials->maxFragmentLength ); + + /* Set the maximum send fragment length. */ + if( SSL_set_max_send_fragment( pNetworkConnection->pSslContext, + ( long ) pOpensslCredentials->maxFragmentLength ) != 1 ) { - if( SSL_connect( pNetworkConnection->pSslContext ) != 1 ) - { - IotLogError( "TLS handshake failed." ); - status = IOT_NETWORK_FAILURE; - } - else - { - IotLogInfo( "TLS handshake succeeded." ); - } + IotLogError( "Failed to set max send fragment length %lu.", + ( unsigned long ) pOpensslCredentials->maxFragmentLength ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } - /* Verify the peer certificate. */ - if( status == IOT_NETWORK_SUCCESS ) + /* In supported versions of OpenSSL, change the size of the read buffer + * to match the maximum fragment length + some extra bytes for overhead. + * Note that OpenSSL ignores this setting if it's smaller than the default. + */ + #if OPENSSL_VERSION_NUMBER > 0x10100000L + SSL_set_default_read_buffer_len( pNetworkConnection->pSslContext, + pOpensslCredentials->maxFragmentLength + + SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); + #endif + } + + if( pOpensslCredentials->disableSni == false ) + { + IotLogDebug( "Setting server name %s for SNI.", pServerName ); + + if( SSL_set_tlsext_host_name( pNetworkConnection->pSslContext, + pServerName ) != 1 ) { - if( SSL_get_verify_result( pNetworkConnection->pSslContext ) != X509_V_OK ) - { - IotLogError( "Peer certificate verification failed." ); - status = IOT_NETWORK_FAILURE; - } - else - { - IotLogInfo( "Peer certificate verified. TLS connection established." ); - } + IotLogError( "Failed to set server name %s for SNI.", pServerName ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } + } + + /* Perform the TLS handshake. */ + if( SSL_connect( pNetworkConnection->pSslContext ) != 1 ) + { + IotLogError( "TLS handshake failed." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + } + + IotLogInfo( "TLS handshake succeeded." ); + + /* Verify the peer certificate. */ + if( SSL_get_verify_result( pNetworkConnection->pSslContext ) != X509_V_OK ) + { + IotLogError( "Peer certificate verification failed." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + } + + IotLogInfo( "Peer certificate verified. TLS connection established." ); + + _IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Free the SSL context. */ + if( pSslContext != NULL ) + { + SSL_CTX_free( pSslContext ); + } - /* Clean up on error. */ - if( status != IOT_NETWORK_SUCCESS ) + /* Clean up on error. */ + if( status != IOT_NETWORK_SUCCESS ) + { + if( pNetworkConnection->pSslContext != NULL ) { SSL_free( pNetworkConnection->pSslContext ); pNetworkConnection->pSslContext = NULL; } } - return status; + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -672,7 +577,7 @@ static inline IotNetworkError_t _tlsSetup( IotNetworkConnectionOpenssl_t * const * * @param[in] pNetworkConnection The TLS connection to clean up. */ -static inline void _tlsCleanup( IotNetworkConnectionOpenssl_t * const pNetworkConnection ) +static void _tlsCleanup( _networkConnection_t * pNetworkConnection ) { /* Shut down the TLS connection. */ IotLogInfo( "Shutting down TLS connection." ); @@ -714,66 +619,63 @@ static inline void _tlsCleanup( IotNetworkConnectionOpenssl_t * const pNetworkCo * * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_SYSTEM_ERROR. */ -static IotNetworkError_t _cancelReceiveThread( IotNetworkConnectionOpenssl_t * const pNetworkConnection ) +static IotNetworkError_t _cancelReceiveThread( _networkConnection_t * pNetworkConnection ) { + _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int posixError = 0; - IotNetworkError_t status = IOT_NETWORK_SUCCESS; /* Do nothing if this thread is attempting to cancel itself. */ if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) { if( pNetworkConnection->receiveThread == pthread_self() ) { - status = IOT_NETWORK_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SUCCESS ); } } - if( status == IOT_NETWORK_SUCCESS ) + if( pNetworkConnection->receiveThreadStatus != _NONE ) { - if( pNetworkConnection->receiveThreadStatus != _NONE ) + /* Send a cancellation request to the receive thread if active. */ + if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) { - /* Send a cancellation request to the receive thread if active. */ - if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) + posixError = pthread_cancel( pNetworkConnection->receiveThread ); + + if( ( posixError != 0 ) && ( posixError != ESRCH ) ) { - posixError = pthread_cancel( pNetworkConnection->receiveThread ); - - if( ( posixError != 0 ) && ( posixError != ESRCH ) ) - { - IotLogWarn( "Failed to send cancellation request to socket %d receive " - "thread. errno=%d.", - pNetworkConnection->socket, - posixError ); - - status = IOT_NETWORK_SYSTEM_ERROR; - } - else - { - pNetworkConnection->receiveThreadStatus = _TERMINATED; - } + IotLogWarn( "Failed to send cancellation request to socket %d receive " + "thread. errno=%d.", + pNetworkConnection->socket, + posixError ); + + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pNetworkConnection->receiveThreadStatus = _TERMINATED; } + } + + if( pNetworkConnection->receiveThreadStatus == _TERMINATED ) + { + /* Join the receive thread. */ + posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); - if( pNetworkConnection->receiveThreadStatus == _TERMINATED ) + if( posixError != 0 ) { - /* Join the receive thread. */ - posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); - - if( posixError != 0 ) - { - IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d.", - pNetworkConnection->socket, - posixError ); - } - - /* Clear data about the receive thread and callback. */ - pNetworkConnection->receiveThreadStatus = _NONE; - ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); - pNetworkConnection->receiveCallback = NULL; - pNetworkConnection->pReceiveContext = NULL; + IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d.", + pNetworkConnection->socket, + posixError ); } + + /* Clear data about the receive thread and callback. */ + pNetworkConnection->receiveThreadStatus = _NONE; + ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); + pNetworkConnection->receiveCallback = NULL; + pNetworkConnection->pReceiveContext = NULL; } } - return status; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -829,84 +731,69 @@ void IotNetworkOpenssl_Cleanup( void ) IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, void * pCredentialInfo, - void * const pConnection ) + void * pConnection ) { + _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int tcpSocket = -1; - IotNetworkError_t status = IOT_NETWORK_SUCCESS; + bool networkMutexCreated = false; + _networkConnection_t * pNewNetworkConnection = NULL; /* Cast function parameters to correct types. */ const IotNetworkServerInfoOpenssl_t * const pServerInfo = pConnectionInfo; const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials = pCredentialInfo; - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - - IotMetricsTcpConnection_t connection = { .remotePort = pServerInfo->port }; - - /* Check output parameter. */ - if( pNetworkConnection == NULL ) - { - IotLogError( "Output parameter for connection create cannot be NULL." ); - - return IOT_NETWORK_BAD_PARAMETER; - } + _networkConnection_t ** const pNetworkConnection = pConnection; - /* Check server info. */ - if( ( pServerInfo == NULL ) || - ( pServerInfo->pHostName == NULL ) || - ( pServerInfo->port == 0 ) ) - { - IotLogError( "Missing server information." ); - - return IOT_NETWORK_BAD_PARAMETER; - } + /* Allocate memory for a new connection. */ + pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); - /* Check credentials parameter if given. */ - if( pOpensslCredentials != NULL ) + if( pNewNetworkConnection == NULL ) { - if( ( pOpensslCredentials->pRootCaPath == NULL ) || - ( pOpensslCredentials->pClientCertPath == NULL ) || - ( pOpensslCredentials->pPrivateKeyPath == NULL ) ) - { - IotLogError( "Missing credential information." ); + IotLogError( "Failed to allocate memory for new network connection." ); - return IOT_NETWORK_BAD_PARAMETER; - } + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_NO_MEMORY ); } /* Clear connection data. */ - ( void ) memset( pNetworkConnection, 0x00, sizeof( IotNetworkConnectionOpenssl_t ) ); + ( void ) memset( pNewNetworkConnection, 0x00, sizeof( _networkConnection_t ) ); /* Create the network connection mutex. */ - if( IotMutex_Create( &( pNetworkConnection->mutex ), true ) == false ) + networkMutexCreated = IotMutex_Create( &( pNewNetworkConnection->mutex ), true ); + + if( networkMutexCreated == false ) { IotLogError( "Failed to create network connection mutex." ); - return IOT_NETWORK_NO_MEMORY; + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ - tcpSocket = _dnsLookup( pServerInfo, &connection.remoteIP ); + tcpSocket = _dnsLookup( pServerInfo ); if( tcpSocket == -1 ) { - status = IOT_NETWORK_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } else { IotLogInfo( "TCP connection successful." ); + } - /* Set the socket in the network connection. */ - pNetworkConnection->socket = tcpSocket; + /* Set the socket in the network connection. */ + pNewNetworkConnection->socket = tcpSocket; - /* Set up TLS if credentials are provided. */ - if( pOpensslCredentials != NULL ) - { - IotLogInfo( "Setting up TLS." ); + /* Set up TLS if credentials are provided. */ + if( pOpensslCredentials != NULL ) + { + IotLogInfo( "Setting up TLS." ); - status = _tlsSetup( pNetworkConnection, pServerInfo->pHostName, pOpensslCredentials ); - } + status = _tlsSetup( pNewNetworkConnection, + pServerInfo->pHostName, + pOpensslCredentials ); } /* Clean up on error. */ + _IOT_FUNCTION_CLEANUP_BEGIN(); + if( status != IOT_NETWORK_SUCCESS ) { if( tcpSocket != -1 ) @@ -914,14 +801,23 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, ( void ) close( tcpSocket ); } - IotNetworkOpenssl_Destroy( pNetworkConnection ); + if( networkMutexCreated == true ) + { + IotMutex_Destroy( &( pNewNetworkConnection->mutex ) ); + } + + if( pNewNetworkConnection != NULL ) + { + IotNetwork_Free( pNewNetworkConnection ); + } } else { - connection.id = tcpSocket; - IotMetrics_AddTcpConnection( &connection ); + /* Set the output parameter. */ + *pNetworkConnection = pNewNetworkConnection; } - return status; + + _IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -934,7 +830,7 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, IotNetworkError_t status = IOT_NETWORK_SUCCESS; /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; + _networkConnection_t * const pNetworkConnection = pConnection; /* Lock the connection mutex before changing the callback and its parameter. */ IotMutex_Lock( &( pNetworkConnection->mutex ) ); @@ -990,15 +886,7 @@ size_t IotNetworkOpenssl_Send( void * pConnection, }; /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - - /* Check parameters. */ - if( ( pNetworkConnection == NULL ) || ( pMessage == NULL ) || ( messageLength == 0 ) ) - { - IotLogError( "Bad parameter to network connection send." ); - - return 0; - } + _networkConnection_t * const pNetworkConnection = pConnection; IotLogDebug( "Sending %lu bytes over socket %d.", ( unsigned long ) messageLength, @@ -1063,95 +951,74 @@ size_t IotNetworkOpenssl_Send( void * pConnection, /*-----------------------------------------------------------*/ size_t IotNetworkOpenssl_Receive( void * pConnection, - uint8_t * const pBuffer, + uint8_t * pBuffer, size_t bytesRequested ) { - int bytesRead = 0; - size_t totalBytesRead = 0; - struct pollfd fileDescriptor = - { - .events = POLLIN | POLLPRI, - .revents = 0 - }; + int receiveStatus = 0; + size_t bytesRead = 0, bytesRemaining = bytesRequested; /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - - /* Check parameters. */ - if( ( pNetworkConnection == NULL ) || ( pBuffer == NULL ) || ( bytesRequested == 0 ) ) - { - IotLogError( "Bad parameter to network connection receive." ); - - return 0; - } + _networkConnection_t * const pNetworkConnection = pConnection; IotLogDebug( "Blocking to wait for %lu bytes on socket %d.", ( unsigned long ) bytesRequested, pNetworkConnection->socket ); - /* Set the file descriptor for poll. */ - fileDescriptor.fd = pNetworkConnection->socket; - /* Lock the connection mutex to prevent the connection from being closed * while receiving. */ IotMutex_Lock( &( pNetworkConnection->mutex ) ); - /* Continuously block on the socket for available data. */ - while( poll( &fileDescriptor, 1, -1 ) == 1 ) + /* Loop until all bytes are received. */ + while( bytesRemaining > 0 ) { - /* If an error event is detected, stop receiving. */ - if( ( fileDescriptor.revents & POLLERR ) || - ( fileDescriptor.revents & POLLHUP ) || - ( fileDescriptor.revents & POLLNVAL ) ) - { - IotLogError( "Error receiving on socket %d after %lu bytes;" - " stopping blocking receive.", - ( unsigned long ) totalBytesRead, - pNetworkConnection->socket ); - break; - } - /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on * whether the SSL connection context is set up. */ if( pNetworkConnection->pSslContext != NULL ) { - bytesRead = SSL_read( pNetworkConnection->pSslContext, - pBuffer + totalBytesRead, - ( int ) ( bytesRequested - totalBytesRead ) ); + receiveStatus = SSL_read( pNetworkConnection->pSslContext, + pBuffer + bytesRead, + ( int ) bytesRemaining ); } else { - bytesRead = ( int ) recv( pNetworkConnection->socket, - pBuffer + totalBytesRead, - bytesRequested - totalBytesRead, - 0 ); + receiveStatus = ( int ) recv( pNetworkConnection->socket, + pBuffer + bytesRead, + bytesRemaining, + 0 ); } - /* Check for an error during receive. */ - if( bytesRead <= 0 ) + /* Check receive status. */ + if( receiveStatus <= 0 ) { - IotLogError( "Error receiving from socket %d after receiving %lu bytes", - pNetworkConnection->socket, - ( unsigned long ) totalBytesRead ); + IotLogError( "Error receiving from socket %d.", + pNetworkConnection->socket ); + break; } - - totalBytesRead += ( size_t ) bytesRead; - - /* Check if any more data needs to be read. */ - if( totalBytesRead == bytesRequested ) + else { - IotLogDebug( "Successfully received %lu bytes.", - ( unsigned long ) bytesRequested ); - - break; + bytesRead += ( size_t ) receiveStatus; + bytesRemaining -= ( size_t ) receiveStatus; } } /* Unlock the connection mutex. */ IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - return totalBytesRead; + /* Check how many bytes were read. */ + if( bytesRead < bytesRequested ) + { + IotLogWarn( "Receive requested %lu bytes, but only %lu bytes received.", + ( unsigned long ) bytesRequested, + ( unsigned long ) bytesRead ); + } + else + { + IotLogDebug( "Successfully received %lu bytes.", + ( unsigned long ) bytesRequested ); + } + + return bytesRead; } /*-----------------------------------------------------------*/ @@ -1159,15 +1026,7 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) { /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; - - /* Check parameter. */ - if( pNetworkConnection == NULL ) - { - IotLogError( "NULL parameter to network connection close." ); - - return IOT_NETWORK_BAD_PARAMETER; - } + _networkConnection_t * const pNetworkConnection = pConnection; /* Lock the connection mutex to block the receive thread. */ IotMutex_Lock( &( pNetworkConnection->mutex ) ); @@ -1198,9 +1057,6 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) /* Unlock the connection mutex. */ IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - /* Remove the socket from metrics. */ - IotMetrics_RemoveTcpConnection( pNetworkConnection->socket ); - return IOT_NETWORK_SUCCESS; } @@ -1208,24 +1064,16 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) { - IotNetworkError_t status = IOT_NETWORK_SUCCESS; - /* Cast function parameter to correct type. */ - IotNetworkConnectionOpenssl_t * const pNetworkConnection = pConnection; + _networkConnection_t * const pNetworkConnection = pConnection; - if( pNetworkConnection == NULL ) - { - IotLogError( "NULL parameter to network connection destroy." ); + /* Clean up the connection by destroying its mutex. */ + IotMutex_Destroy( &( pNetworkConnection->mutex ) ); - status = IOT_NETWORK_BAD_PARAMETER; - } - else - { - /* Clean up the connection by destroying its mutex. */ - IotMutex_Destroy( &( pNetworkConnection->mutex ) ); - } + /* Free the connection. */ + IotNetwork_Free( pNetworkConnection ); - return status; + return IOT_NETWORK_SUCCESS; } /*-----------------------------------------------------------*/ diff --git a/scripts/build_check_commit.sh b/scripts/build_check_commit.sh deleted file mode 100644 index 70865941d6..0000000000 --- a/scripts/build_check_commit.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to check GitHub commits. It builds the -# demos and tests, then runs the demos and tests against AWS IoT with -# the appropriate credentials. - -# Exit on any nonzero return code. -set -e - -# Set Thing Name. -AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" - -# Additional options for compilation. -COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" - -# Create build directory. -mkdir -p build -cd build -rm -rf * - -# Create directory for credentials. -mkdir ../credentials - -# Download Amazon Root CA 1. -wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O ../credentials/AmazonRootCA1.pem - -# Write the client certificate and private key into files. -echo -e $AWS_IOT_CLIENT_CERT > ../credentials/clientCert.pem -echo -e $AWS_IOT_PRIVATE_KEY > ../credentials/privateKey.pem - -# Build tests and demos against AWS IoT with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" -make - -# Run common tests (no network required). -./bin/iot_tests_common - -# Run MQTT tests and demo against AWS IoT. -./bin/iot_tests_mqtt -./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" - -# Run Shadow tests and demo. -./bin/aws_iot_tests_shadow -./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" - -# Run serializer tests -./bin/iot_tests_serializer - -# Run defender tests -./bin/aws_iot_tests_defender - -# Rebuild in static memory mode. -rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_STATIC_MEMORY_ONLY=1 -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" $COMPILER_OPTIONS" -make - -# Run common tests in static memory mode (no network required). -./bin/iot_tests_common - -# Run MQTT and Shadow tests in static memory mode. -./bin/iot_tests_mqtt -./bin/aws_iot_tests_shadow - -# Run serializer tests -./bin/iot_tests_serializer - -# Run defender tests -./bin/aws_iot_tests_defender \ No newline at end of file diff --git a/scripts/build_check_pr.sh b/scripts/build_check_pr.sh deleted file mode 100644 index 29c9e07d09..0000000000 --- a/scripts/build_check_pr.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to check GitHub pull requests. It builds the -# demos and tests, then runs the demos and tests that require no credentials. - -# Exit on any nonzero return code. -set -e - -# Additional options for compilation. -COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" - -# Create build directory. -mkdir -p build -cd build -rm -rf * - -# Build tests and demos against Mosquitto broker with ThreadSanitizer. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" -make - -# Run common tests (no network required). -./bin/iot_tests_common - -# Run MQTT tests and demos against Mosquitto. -./bin/iot_tests_mqtt -./bin/iot_demo_mqtt -h test.mosquitto.org -p 1883 -n - -# Run the Shadow tests that do not require the network. -./bin/aws_iot_tests_shadow -n - -# Run serializer tests -./bin/iot_tests_serializer - -# Rebuild in static memory mode. -rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_STATIC_MEMORY_ONLY=1 $COMPILER_OPTIONS" -make - -# Run common tests in static memory mode (no network required). -./bin/iot_tests_common - -# Run MQTT tests and no-network Shadow tests in static memory mode. -./bin/iot_tests_mqtt -./bin/aws_iot_tests_shadow -n - -# Run serializer tests -./bin/iot_tests_serializer diff --git a/scripts/ci_test_common.sh b/scripts/ci_test_common.sh new file mode 100644 index 0000000000..6a28100386 --- /dev/null +++ b/scripts/ci_test_common.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# Travis CI uses this script to test the common libraries. + +# Exit on any nonzero return code. +set -e + +# CMake compiler flags for building common libraries. +CMAKE_FLAGS="$COMPILER_OPTIONS" + +# Build executables. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +make -j2 + +# Run common tests. +./bin/iot_tests_common + +# Rebuild in static memory mode. +rm -rf * +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" +make -j2 + +# Run common tests in static memory mode. +./bin/iot_tests_common diff --git a/scripts/ci_test_defender.sh b/scripts/ci_test_defender.sh new file mode 100644 index 0000000000..7df44ae4cd --- /dev/null +++ b/scripts/ci_test_defender.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# Travis CI uses this script to test the Defender library. + +# Exit on any nonzero return code. +set -e diff --git a/scripts/ci_test_general.sh b/scripts/ci_test_general.sh new file mode 100644 index 0000000000..39cf88e3d2 --- /dev/null +++ b/scripts/ci_test_general.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# Travis CI uses this script to run the general test scripts. +# This script is invoked from the build directory, so all helper scripts have a relative +# path from the build directory. + +# Exit on any nonzero return code. +set -e + +# Run test coverage script only for commit builds. +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + echo "Nothing to do right now." +fi diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh new file mode 100644 index 0000000000..96c11503c9 --- /dev/null +++ b/scripts/ci_test_mqtt.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +# Travis CI uses this script to test the MQTT library. + +# Exit on any nonzero return code. +set -e + +# Function for running the existing test and demo executables. +run_tests_and_demos() { + # Run MQTT tests. + ./bin/iot_tests_mqtt + + # Run MQTT demo with appropriate arguments. + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + # Run against AWS IoT. + ./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" + else + # Run against Mosquitto. + ./bin/iot_demo_mqtt -h test.mosquitto.org -p 1883 -n + fi +} + +# For pull request builds, run against test.mosquitto.org. Otherwise, run against AWS IoT. +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" +else + CMAKE_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" +fi + +# Build executables. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +make -j2 + +# Run tests and demos. +run_tests_and_demos + +# Rebuild in static memory mode. +rm -rf * +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" +make -j2 + +# Run tests in static memory mode. +./bin/iot_tests_mqtt diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh new file mode 100644 index 0000000000..27148755d8 --- /dev/null +++ b/scripts/ci_test_shadow.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +# Travis CI uses this script to test the Shadow library. + +# Exit on any nonzero return code. +set -e + +# Function for running the existing test and demo executables. +run_tests_and_demos() { + # For commit builds, run the full Shadow demo and tests. For pull request builds, + # run only the unit tests (credentials are not available for pull request builds). + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + ./bin/aws_iot_tests_shadow + ./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" + else + # Run only Shadow unit tests. + ./bin/aws_iot_tests_shadow -n + fi +} + +# CMake compiler flags for building Shadow. +CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO $COMPILER_OPTIONS" + +# Build executables. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +make -j2 + +# Run tests and demos. +run_tests_and_demos + +# Rebuild in static memory mode. +rm -rf * +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" +make -j2 + +# Run tests and demos in static memory mode. +run_tests_and_demos diff --git a/scripts/coverage.sh b/scripts/coverage.sh deleted file mode 100644 index b9a78ae5ba..0000000000 --- a/scripts/coverage.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to build an submit code coverage results. - -# Exit on any nonzero return code. -set -e - -# Set Thing Name. -AWS_IOT_THING_NAME="$AWS_IOT_THING_NAME_PREFIX$CC" - -# Delete everything in the build directory. -mkdir -p build -cd build -rm -rf * - -# Build tests and demos against AWS IoT with gcov. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\" -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$AWS_IOT_THING_NAME\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" -make - -# Run Common tests with code coverage. -./bin/iot_tests_common 2&> /dev/null - -# Run MQTT tests and demo against AWS IoT with code coverage. -./bin/iot_tests_mqtt 2&> /dev/null -./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" 2&> /dev/null - -# Run Shadow tests and demo with code coverage. -./bin/aws_iot_tests_shadow 2&> /dev/null -./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$AWS_IOT_THING_NAME" 2&> /dev/null - -# Run Defender tests with code coverage. -./bin/aws_iot_tests_defender 2&> /dev/null - -# Generate code coverage results, excluding Unity test framework, demo files, and tests files. -lcov --directory . --capture --output-file coverage.info -lcov --remove coverage.info '*unity*' --output-file coverage.info -lcov --remove coverage.info '*demo*' --output-file coverage.info -lcov --remove coverage.info '*tests*' --output-file coverage.info - -# Submit the code coverage results. -cd .. -coveralls --lcov-file build/coverage.info diff --git a/scripts/general/ci_test_coverage.sh b/scripts/general/ci_test_coverage.sh new file mode 100644 index 0000000000..62d58feedb --- /dev/null +++ b/scripts/general/ci_test_coverage.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +# Travis CI uses this script to build an submit code coverage results. + +# Exit on any nonzero return code. +set -ev + +# Build tests and demos against AWS IoT with coverage. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" &> /dev/null +make &> /dev/null + +# Run common tests with code coverage. +./bin/iot_tests_common &> /dev/null + +# Run MQTT tests and demo against AWS IoT with code coverage. +./bin/iot_tests_mqtt &> /dev/null +./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" &> /dev/null + +# Run Shadow tests and demo with code coverage. +./bin/aws_iot_tests_shadow &> /dev/null +./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" &> /dev/null + +# Generate code coverage results, excluding Unity test framework, demo files, tests files, and third party files. +lcov --directory . --capture --output-file coverage.info +lcov --remove coverage.info '*unity*' --output-file coverage.info +lcov --remove coverage.info '*demo*' --output-file coverage.info +lcov --remove coverage.info '*tests*' --output-file coverage.info +lcov --remove coverage.info '*third_party*' --output-file coverage.info + +# Submit the code coverage results. +cd .. +coveralls --lcov-file build/coverage.info diff --git a/scripts/generate_doc.sh b/scripts/general/ci_test_doc.sh similarity index 100% rename from scripts/generate_doc.sh rename to scripts/general/ci_test_doc.sh diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index a7a5bf2fd6..c0c4f0b063 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -34,8 +34,8 @@ /* POSIX includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* Error handling include. */ +#include "private/iot_error.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -65,6 +65,7 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { + _IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Silence warnings about unused parameters. */ @@ -77,12 +78,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -99,10 +100,10 @@ int main( int argc, /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - return EXIT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index ac594a81cb..b68e881cab 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -35,11 +35,9 @@ #include #include -/* POSIX includes. */ -#include - /* Platform layer includes. */ #include "platform/iot_threads.h" +#include "platform/iot_clock.h" /* MQTT internal include. */ #include "private/iot_taskpool_internal.h" @@ -165,14 +163,32 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) #endif /** - * @brief A global delay to wait for threads to exit or such... + * @brief Define the number of long running jobs. */ -static struct itimerspec _TEST_DELAY_50MS = -{ - .it_value.tv_sec = 0, - .it_value.tv_nsec = ( 50000000L ), /* 50ms */ - .it_interval = { 0 } -}; +#ifndef TEST_TASKPOOL_LONG_JOBS_NUMBER + #define TEST_TASKPOOL_LONG_JOBS_NUMBER 3 +#endif + +/** + * @brief Define the number of running jobs to grow the taskpool for. + */ +#ifndef TEST_TAKPOOL_NUMBER_OF_JOBS + #define TEST_TAKPOOL_NUMBER_OF_JOBS 4 +#endif + +/** + * @brief Define the number of threads to grow the taskpool to. + */ +#ifndef TEST_TASKPOOL_NUMBER_OF_THREADS + #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TAKPOOL_NUMBER_OF_JOBS - 1 ) +#endif + +/** + * @brief Define the number of threads to grow the taskpool to. + */ +#ifndef TEST_TASKPOOL_MAX_THREADS + #define TEST_TASKPOOL_MAX_THREADS 7 +#endif /** * @brief One hour in milliseconds. @@ -186,19 +202,7 @@ static struct itimerspec _TEST_DELAY_50MS = */ static void EmulateWork() { - int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ) ); - - TEST_ASSERT_TRUE( duration_in_nsec <= 999999999 ); - - struct timespec delay = - { - .tv_sec = 0, - .tv_nsec = duration_in_nsec - }; - - int error = clock_nanosleep( CLOCK_MONOTONIC, 0, &delay, NULL ); - - TEST_ASSERT_TRUE( error == 0 ); + IotClock_SleepMs( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ); } /** @@ -206,20 +210,7 @@ static void EmulateWork() */ static void EmulateWorkLong() { - int32_t duration_in_nsec = ( ( 1000000 ) * ( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ) ); - - TEST_ASSERT_TRUE( duration_in_nsec <= 999999999 ); - - /* Emulate at least 10 seconds worth of work. */ - struct timespec delay = - { - .tv_sec = 2, - .tv_nsec = duration_in_nsec - }; - - int error = clock_nanosleep( CLOCK_MONOTONIC, 0, &delay, NULL ); - - TEST_ASSERT_TRUE( error == 0 ); + IotClock_SleepMs( 2000 + ( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ) ); } /** @@ -947,7 +938,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) /* Ensure callback actually executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1031,7 +1022,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) /* Ensure callback actually executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1109,7 +1100,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) /* Ensure callback actually executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1189,7 +1180,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) /* Wait until callback is executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1268,7 +1259,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) /* Wait until callback is executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1348,7 +1339,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait /* Wait until callback is executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1435,7 +1426,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) } /* Give a chance to some jobs to start execution. */ - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); /* Reschedule all. Some will fail to be rescheduled... */ for( count = 0; count < maxJobs; ++count ) @@ -1470,7 +1461,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) /* Wait until callback is executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1580,7 +1571,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) /* Wait until callback is executed. */ while( true ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); IotMutex_Lock( &userContext.lock ); @@ -1719,7 +1710,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_CancelTasks ) /* Wait until callback is executed. */ while( ( scheduled - canceled ) != userContext.counter ) { - ( void ) clock_nanosleep( CLOCK_REALTIME, 0, &_TEST_DELAY_50MS.it_value, NULL ); + IotClock_SleepMs( 50 ); } TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index c6f509de60..dd7ff28f39 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -40,9 +40,6 @@ #include "cbor.h" -/* Metrics includes. */ -#include "iot_metrics.h" - #include "unity_fixture.h" /* Total time to wait for a state to be true. */ @@ -80,8 +77,8 @@ static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .par static IotNetworkServerInfoOpenssl_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; static IotNetworkCredentialsOpenssl_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -static IotNetworkConnectionOpenssl_t _networkConnection = IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER; +extern const IotNetworkInterface_t _IotNetworkOpensslMetrics; /*------------------ global variables -----------------------------*/ static uint8_t _payloadBuffer[ _PAYLOAD_MAX_SIZE ]; @@ -91,10 +88,10 @@ static AwsIotDefenderCallback_t _testCallback; static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; -/* -Waiting for it indicates waiting for any event to happen -Posting it indicates any event happened -*/ +/* + * Waiting for it indicates waiting for any event to happen + * Posting it indicates any event happened + */ static IotSemaphore_t _callbackInfoSem; static AwsIotDefenderCallbackInfo_t _callbackInfo; @@ -161,10 +158,12 @@ TEST_SETUP( Full_DEFENDER ) _serverInfo = ( IotNetworkServerInfoOpenssl_t ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /* Set fields of start info. */ - _startInfo.pConnectionInfo = &_serverInfo; - _startInfo.pCredentialInfo = &_credential; - _startInfo.pConnection = &_networkConnection; - _startInfo.pNetworkInterface = IOT_NETWORK_INTERFACE_OPENSSL; + _startInfo.mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; + _startInfo.mqttNetworkInfo.createNetworkConnection = true; + _startInfo.mqttNetworkInfo.pNetworkServerInfo = &_serverInfo; + _startInfo.mqttNetworkInfo.pNetworkCredentialInfo = &_credential; + + _startInfo.mqttNetworkInfo.pNetworkInterface = &_IotNetworkOpensslMetrics; _startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; _startInfo.mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; @@ -362,7 +361,7 @@ TEST( Full_DEFENDER, Start_with_wrong_network_information ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - _assertEvent( AWS_IOT_DEFENDER_NETWORK_CONNECTION_FAILED, _WAIT_STATE_TOTAL_SECONDS ); + _assertEvent( AWS_IOT_DEFENDER_FAILURE_MQTT, _WAIT_STATE_TOTAL_SECONDS ); } TEST( Full_DEFENDER, Start_should_return_success ) diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index f0bdd90e33..3c479c202a 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -68,8 +68,8 @@ #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_METRICS ( 0 ) #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) - #define IOT_MQTT_TEST ( 1 ) + /* Shadow library configuration. */ #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) @@ -90,10 +90,10 @@ /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define IotNetwork_Malloc unity_malloc_mt -#define IotNetwork_Free unity_free_mt #define IotThreads_Malloc unity_malloc_mt #define IotThreads_Free unity_free_mt +#define IotNetwork_Malloc unity_malloc_mt +#define IotNetwork_Free unity_free_mt #define IotLogging_Malloc unity_malloc_mt #define IotLogging_Free unity_free_mt /* #define IotLogging_StaticBufferSize */ @@ -115,10 +115,10 @@ #define IotTaskPool_FreeJob unity_free_mt #define IotTaskPool_MallocTimerEvent unity_malloc_mt #define IotTaskPool_FreeTimerEvent unity_free_mt - #define IotMqtt_MallocConnection unity_malloc_mt - #define IotMqtt_FreeConnection unity_free_mt #define IotMqtt_MallocMessage unity_malloc_mt #define IotMqtt_FreeMessage unity_free_mt + #define IotMqtt_MallocConnection unity_malloc_mt + #define IotMqtt_FreeConnection unity_free_mt #define IotMqtt_MallocOperation unity_malloc_mt #define IotMqtt_FreeOperation unity_free_mt #define IotMqtt_MallocSubscription unity_malloc_mt @@ -151,12 +151,11 @@ #define IOT_TEST_NETWORK_HEADER "posix/iot_network_openssl.h" /* Network types to use in the tests. These are forward declarations. */ -typedef struct IotNetworkConnectionOpenssl IotTestNetworkConnection_t; +typedef struct _networkConnection IotTestNetworkConnection_t; typedef struct IotNetworkServerInfoOpenssl IotTestNetworkServerInfo_t; typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; /* Initializers for the tests' network types. */ -#define IOT_TEST_NETWORK_CONNECTION_INITIALIZER IOT_NETWORK_CONNECTION_OPENSSL_INITIALIZER #define IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER \ { \ .pHostName = IOT_TEST_SERVER, \ diff --git a/tests/iot_tests_network.c b/tests/iot_tests_network.c index e8e788fa08..dbec0c31d3 100644 --- a/tests/iot_tests_network.c +++ b/tests/iot_tests_network.c @@ -64,12 +64,10 @@ void IotTest_NetworkCleanup( void ); * Creates a new network connection for use with MQTT. * * @param[out] pNewConnection The handle by which this new connection will be referenced. - * @param[in] pMqttConnection The MQTT connection associated with the new network connection. * * @return true if a new network connection was successfully created; false otherwise. */ -bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, - IotMqttConnection_t * pMqttConnection ); +bool IotTest_NetworkConnect( IotTestNetworkConnection_t ** pNewConnection ); /** * @brief Network interface close connection function for the tests. @@ -90,15 +88,10 @@ void IotTest_NetworkDestroy( void * pNetworkConnection ); /*-----------------------------------------------------------*/ -/** - * @brief Flag that tracks if the network connection is created. - */ -static bool _networkConnectionCreated = false; - /** * @brief The network connection shared among the tests. */ -static IotTestNetworkConnection_t _networkConnection = IOT_TEST_NETWORK_CONNECTION_INITIALIZER; +static IotTestNetworkConnection_t * _pNetworkConnection = NULL; /** * @brief Network interface to use in the tests. @@ -108,7 +101,7 @@ static const IotNetworkInterface_t * const _pNetworkInterface = IOT_TEST_NETWORK /** * @brief The MQTT network interface shared among the tests. */ -IotMqttNetIf_t _IotTestNetworkInterface = IOT_MQTT_NETIF_INITIALIZER; +IotMqttNetworkInfo_t _IotTestNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; /** * @brief The MQTT connection shared among the tests. @@ -125,21 +118,17 @@ bool IotTest_NetworkSetup( void ) return false; } - if( IotTest_NetworkConnect( &_networkConnection, - &_IotTestMqttConnection ) == false ) + if( IotTest_NetworkConnect( &_pNetworkConnection ) == false ) { IotTestNetwork_Cleanup(); return false; } - /* Set the members of the network interface. */ - _IotTestNetworkInterface.pDisconnectContext = NULL; - _IotTestNetworkInterface.disconnect = IotTest_NetworkClose; - _IotTestNetworkInterface.pSendContext = ( void * ) &_networkConnection; - _IotTestNetworkInterface.send = _pNetworkInterface->send; - - _networkConnectionCreated = true; + /* Set the members of the network info. */ + _IotTestNetworkInfo.createNetworkConnection = false; + _IotTestNetworkInfo.pNetworkConnection = _pNetworkConnection; + _IotTestNetworkInfo.pNetworkInterface = _pNetworkInterface; return true; } @@ -149,24 +138,23 @@ bool IotTest_NetworkSetup( void ) void IotTest_NetworkCleanup( void ) { /* Close the TCP connection to the server. */ - if( _networkConnectionCreated == true ) + if( _pNetworkConnection != NULL ) { IotTest_NetworkClose( NULL ); - IotTest_NetworkDestroy( &_networkConnection ); - _networkConnectionCreated = false; + IotTest_NetworkDestroy( _pNetworkConnection ); + _pNetworkConnection = NULL; } /* Clean up the network library. */ IotTestNetwork_Cleanup(); /* Clear the network interface. */ - ( void ) memset( &_IotTestNetworkInterface, 0x00, sizeof( IotMqttNetIf_t ) ); + ( void ) memset( &_IotTestNetworkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); } /*-----------------------------------------------------------*/ -bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, - IotMqttConnection_t * pMqttConnection ) +bool IotTest_NetworkConnect( IotTestNetworkConnection_t ** pNewConnection ) { IotTestNetworkServerInfo_t serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; IotTestNetworkCredentials_t * pCredentials = NULL; @@ -185,17 +173,6 @@ bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, return false; } - /* Set the MQTT receive callback. */ - if( _pNetworkInterface->setReceiveCallback( pNewConnection, - IotMqtt_ReceiveCallback, - pMqttConnection ) != IOT_NETWORK_SUCCESS ) - { - _pNetworkInterface->close( pNewConnection ); - _pNetworkInterface->destroy( pNewConnection ); - - return false; - } - return true; } @@ -206,13 +183,13 @@ IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ) /* Close the provided network handle; if that is NULL, close the * global network handle. */ if( ( pNetworkConnection != NULL ) && - ( pNetworkConnection != &_networkConnection ) ) + ( pNetworkConnection != _pNetworkConnection ) ) { _pNetworkInterface->close( pNetworkConnection ); } - else if( _networkConnectionCreated == true ) + else if( _pNetworkConnection != NULL ) { - _pNetworkInterface->close( &_networkConnection ); + _pNetworkInterface->close( _pNetworkConnection ); } return IOT_NETWORK_SUCCESS; @@ -223,17 +200,17 @@ IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ) void IotTest_NetworkDestroy( void * pNetworkConnection ) { if( ( pNetworkConnection != NULL ) && - ( pNetworkConnection != &_networkConnection ) ) + ( pNetworkConnection != _pNetworkConnection ) ) { /* Wrap the network interface's destroy function. */ _pNetworkInterface->destroy( pNetworkConnection ); } else { - if( _networkConnectionCreated == true ) + if( _pNetworkConnection != NULL ) { - _pNetworkInterface->destroy( &_networkConnection ); - _networkConnectionCreated = false; + _pNetworkInterface->destroy( _pNetworkConnection ); + _pNetworkConnection = NULL; } } } diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index eedc6d0923..0b165e9dd6 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -10,4 +10,4 @@ add_executable( iot_tests_mqtt system/iot_tests_mqtt_stress.c ) # MQTT tests library dependencies. -target_link_libraries( iot_tests_mqtt iotcommon iotplatform iotmqtt unity ) +target_link_libraries( iot_tests_mqtt iotcommon iotplatform iotmqtt unity pthread ) diff --git a/tests/mqtt/access/iot_test_access_mqtt.h b/tests/mqtt/access/iot_test_access_mqtt.h index 4843875a5f..0c8ed62ca6 100644 --- a/tests/mqtt/access/iot_test_access_mqtt.h +++ b/tests/mqtt/access/iot_test_access_mqtt.h @@ -36,7 +36,7 @@ * @see #_createMqttConnection. */ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const IotMqttNetIf_t * pNetworkInterface, + const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ); /*------------------------- iot_mqtt_serialize.c ------------------------*/ @@ -56,15 +56,6 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) -/** - * @brief Test access function for #_decodeRemainingLength. - * - * @see #_decodeRemainingLength. - */ -IotMqttError_t IotTestMqtt_decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** pEnd, - size_t * pLength ); - /*----------------------- iot_mqtt_subscription.c -----------------------*/ /* Internal data structures of iot_mqtt_subscription.c, redefined for the tests. */ diff --git a/tests/mqtt/access/iot_test_access_mqtt_api.c b/tests/mqtt/access/iot_test_access_mqtt_api.c index aca4fc8f7e..341fcec6b8 100644 --- a/tests/mqtt/access/iot_test_access_mqtt_api.c +++ b/tests/mqtt/access/iot_test_access_mqtt_api.c @@ -31,10 +31,10 @@ /*-----------------------------------------------------------*/ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const IotMqttNetIf_t * pNetworkInterface, + const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ) { - return _createMqttConnection( awsIotMqttMode, pNetworkInterface, keepAliveSeconds ); + return _createMqttConnection( awsIotMqttMode, pNetworkInfo, keepAliveSeconds ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/access/iot_test_access_mqtt_serialize.c b/tests/mqtt/access/iot_test_access_mqtt_serialize.c deleted file mode 100644 index 8199777230..0000000000 --- a/tests/mqtt/access/iot_test_access_mqtt_serialize.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_test_access_mqtt_serialize.c - * @brief Provides access to the internal functions and variables of - * iot_mqtt_serialize.c - * - * This file should only be included at the bottom of iot_mqtt_serialize.c - * and never compiled by itself. - */ - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotTestMqtt_decodeRemainingLength( const uint8_t * pSource, - const uint8_t ** pEnd, - size_t * pLength ) -{ - return _decodeRemainingLength( pSource, pEnd, pLength ); -} - -/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/iot_tests_mqtt.c b/tests/mqtt/iot_tests_mqtt.c index 4466dc748a..b4434059c1 100644 --- a/tests/mqtt/iot_tests_mqtt.c +++ b/tests/mqtt/iot_tests_mqtt.c @@ -33,8 +33,8 @@ /* POSIX includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* Error handling include. */ +#include "private/iot_error.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -64,6 +64,7 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { + _IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Set a signal handler for segmentation faults and assertion failures. */ @@ -72,18 +73,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; - } - - /* Initialize the common libraries before running the tests. */ - if( IotCommon_Init() == false ) - { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -107,16 +102,13 @@ int main( int argc, RUN_TEST_GROUP( MQTT_Stress ); } - /* Clean up common libraries. */ - IotCommon_Cleanup(); - /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - return EXIT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 1b2d8ac730..a40b82970a 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -39,6 +39,9 @@ /* POSIX includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* MQTT include. */ #include "iot_mqtt.h" @@ -48,11 +51,6 @@ #else #include #endif -#ifdef POSIX_UNISTD_HEADER - #include POSIX_UNISTD_HEADER -#else - #include -#endif /* Platform layer include. */ #include "platform/iot_clock.h" @@ -147,10 +145,10 @@ */ typedef struct _publishParams { - int threadNumber; /**< @brief ID number of this publish thread. */ - long publishPeriodNs; /**< @brief How long to wait (in nanoseconds) between each publish. */ - unsigned publishLimit; /**< @brief How many publishes this thread will send. */ - IotMqttError_t status; /**< @brief Final status of this publish thread. */ + int threadNumber; /**< @brief ID number of this publish thread. */ + uint32_t publishPeriodMs; /**< @brief How long to wait (in milliseconds) between each publish. */ + unsigned publishLimit; /**< @brief How many publishes this thread will send. */ + IotMqttError_t status; /**< @brief Final status of this publish thread. */ } _publishParams_t; /*-----------------------------------------------------------*/ @@ -164,15 +162,19 @@ extern void IotTest_NetworkCleanup( void ); * be included by this file. */ extern IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, size_t * pPacketSize ); -extern void _IotMqtt_FreePacket( uint8_t * pPacket ); /* Network variables used by the tests, declared in one of the test network * function files. */ -extern IotMqttNetIf_t _IotTestNetworkInterface; +extern IotMqttNetworkInfo_t _IotTestNetworkInfo; extern IotMqttConnection_t _IotTestMqttConnection; /*-----------------------------------------------------------*/ +/** + * @brief Tracks whether the test MQTT connection has been created. + */ +static bool _connectionCreated = false; + /** * @brief Filler text to publish. */ @@ -318,12 +320,12 @@ static void _blockingCallback( void * pArgument, IotMqttCallbackParam_t * param ) { IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - const unsigned blockTime = 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + const uint32_t blockTimeMs = ( 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ) * 1000; ( void ) param; - IotLogInfo( "Callback blocking for %u seconds.", blockTime ); - sleep( blockTime ); + IotLogInfo( "Callback blocking for %lu milliseconds.", blockTimeMs ); + IotClock_SleepMs( blockTimeMs ); IotSemaphore_Post( pWaitSem ); } @@ -340,11 +342,6 @@ static void * _publishThread( void * pArgument ) IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _publishParams_t * pParams = ( _publishParams_t * ) pArgument; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - const struct timespec sleepTime = - { - .tv_sec = 0, - .tv_nsec = pParams->publishPeriodNs - }; /* Set the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; @@ -375,7 +372,7 @@ static void * _publishThread( void * pArgument ) pParams->threadNumber, i, IOT_TEST_MQTT_DECONGEST_S ); - sleep( IOT_TEST_MQTT_DECONGEST_S ); + IotClock_SleepMs( IOT_TEST_MQTT_DECONGEST_S * 1000 ); continue; } /* If the PUBLISH failed, exit this thread. */ @@ -399,12 +396,7 @@ static void * _publishThread( void * pArgument ) } /* Sleep until the next PUBLISH should be sent. */ - if( nanosleep( &sleepTime, NULL ) != 0 ) - { - IotLogError( "Error in nanosleep." ); - status = IOT_MQTT_BAD_RESPONSE; - break; - } + IotClock_SleepMs( pParams->publishPeriodMs ); } /* Set the thread's last status. */ @@ -427,11 +419,15 @@ TEST_GROUP( MQTT_Stress ); */ TEST_SETUP( MQTT_Stress ) { - int i = 0; + int32_t i = 0; + IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; const IotLogConfig_t logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; + /* Initialize common components. */ + TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + /* Clear the PINGREQ override flag. */ _pingreqOverrideCalled = false; @@ -444,8 +440,8 @@ TEST_SETUP( MQTT_Stress ) _MAX_RECEIVED_PUBLISH ) ); /* Set the serializer overrides. */ - _IotTestNetworkInterface.serialize.pingreq = _serializePingreq; - _IotTestNetworkInterface.freePacket = _IotMqtt_FreePacket; + serializer.serialize.pingreq = _serializePingreq; + _IotTestNetworkInfo.pMqttSerializer = &serializer; /* Set up the network stack. */ if( IotTest_NetworkSetup() == false ) @@ -489,10 +485,12 @@ TEST_SETUP( MQTT_Stress ) /* Establish the MQTT connection. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ) ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ) ); + + _connectionCreated = true; /* Subscribe to the test topic filters. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, @@ -515,17 +513,20 @@ TEST_TEAR_DOWN( MQTT_Stress ) /* Disconnect the MQTT connection. Unsubscribe is not called; the subscriptions * should be cleaned up by Disconnect. */ - if( _IotTestMqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + if( _connectionCreated == true ) { - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); + _connectionCreated = false; } /* Clean up the network stack. */ IotTest_NetworkCleanup(); + /* Clean up common components. */ + IotCommon_Cleanup(); + /* Clean up the MQTT library. */ IotMqtt_Cleanup(); - _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ @@ -547,12 +548,12 @@ TEST_GROUP_RUNNER( MQTT_Stress ) */ TEST( MQTT_Stress, KeepAlive ) { - const unsigned sleepTime = 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + const uint32_t sleepTimeMs = ( 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ) * 1000; /* Send no MQTT packets for a long time. The keep-alive must be used to keep * the connection open. */ - IotLogInfo( "KeepAlive test sleeping for %u seconds.", sleepTime ); - sleep( sleepTime ); + IotLogInfo( "KeepAlive test sleeping for %lu milliseconds.", sleepTimeMs ); + IotClock_SleepMs( sleepTimeMs ); /* Send a PUBLISH to verify that the connection is still usable. */ IotLogInfo( "KeepAlive test checking MQTT connection." ); @@ -626,7 +627,7 @@ TEST( MQTT_Stress, ClientClosesConnection ) for( i = 0; i < IOT_TEST_MQTT_THREADS; i++ ) { publishThreadParams[ i ].threadNumber = i; - publishThreadParams[ i ].publishPeriodNs = 500000000; + publishThreadParams[ i ].publishPeriodMs = 500; publishThreadParams[ i ].publishLimit = IOT_TEST_MQTT_PUBLISHES_PER_THREAD; } diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 13c89fc92b..226fc127ac 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -32,6 +32,9 @@ /* Standard includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -120,14 +123,13 @@ typedef struct _operationCompleteParams * the test network function files. */ extern bool IotTest_NetworkSetup( void ); extern void IotTest_NetworkCleanup( void ); -extern bool IotTest_NetworkConnect( IotTestNetworkConnection_t * pNewConnection, - IotMqttConnection_t * pMqttConnection ); +extern bool IotTest_NetworkConnect( IotTestNetworkConnection_t ** pNewConnection ); extern IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ); extern void IotTest_NetworkDestroy( void * pConnection ); /* Network variables used by the tests, declared in one of the test network * function files. */ -extern IotMqttNetIf_t _IotTestNetworkInterface; +extern IotMqttNetworkInfo_t _IotTestNetworkInfo; extern IotMqttConnection_t _IotTestMqttConnection; /*-----------------------------------------------------------*/ @@ -200,14 +202,16 @@ static IotMqttError_t _serializeConnect( const IotMqttConnectInfo_t * pConnectIn static IotMqttError_t _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, uint8_t ** pPublishPacket, size_t * pPacketSize, - uint16_t * pPacketIdentifier ) + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ) { _publishSerializerOverride = true; return _IotMqtt_SerializePublish( pPublishInfo, pPublishPacket, pPacketSize, - pPacketIdentifier ); + pPacketIdentifier, + pPacketIdentifierHigh ); } /*-----------------------------------------------------------*/ @@ -380,7 +384,7 @@ static void _reentrantCallback( void * pArgument, if( mqttStatus == IOT_MQTT_STATUS_PENDING ) { /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( pOperation->mqttConnection, false ); + IotMqtt_Disconnect( pOperation->mqttConnection, 0 ); /* Waiting on an operation whose connection is closed should return * "Network Error". */ @@ -410,20 +414,22 @@ static void _reentrantCallback( void * pArgument, static void _subscribePublishWait( IotMqttQos_t qos ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetIf_t networkInterface = _IotTestNetworkInterface; + IotMqttNetworkInfo_t networkInfo = _IotTestNetworkInfo; + IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotSemaphore_t waitSem; /* Set the serializer overrides. */ - networkInterface.freePacket = _freePacket; - networkInterface.serialize.connect = _serializeConnect; - networkInterface.serialize.publish = _serializePublish; - networkInterface.serialize.puback = _serializePuback; - networkInterface.serialize.subscribe = _serializeSubscribe; - networkInterface.serialize.unsubscribe = _serializeUnsubscribe; - networkInterface.serialize.disconnect = _serializeDisconnect; + serializer.freePacket = _freePacket; + serializer.serialize.connect = _serializeConnect; + serializer.serialize.publish = _serializePublish; + serializer.serialize.puback = _serializePuback; + serializer.serialize.subscribe = _serializeSubscribe; + serializer.serialize.unsubscribe = _serializeUnsubscribe; + serializer.serialize.disconnect = _serializeDisconnect; + networkInfo.pMqttSerializer = &serializer; /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -436,10 +442,10 @@ static void _subscribePublishWait( IotMqttQos_t qos ) connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestMqttConnection, - &networkInterface, + status = IotMqtt_Connect( &networkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -490,7 +496,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) } /* Close the MQTT connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); } IotSemaphore_Destroy( &waitSem ); @@ -535,6 +541,12 @@ TEST_SETUP( MQTT_System ) _unsubscribeSerializerOverride = false; _disconnectSerializerOverride = false; + /* Initialize common components. */ + if( IotCommon_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize common components." ); + } + /* Initialize the MQTT library. */ if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) { @@ -568,6 +580,9 @@ TEST_SETUP( MQTT_System ) */ TEST_TEAR_DOWN( MQTT_System ) { + /* Clean up common components. */ + IotCommon_Cleanup(); + /* Clean up the MQTT library. */ IotMqtt_Cleanup(); @@ -661,10 +676,10 @@ TEST( MQTT_System, SubscribePublishAsync ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -721,7 +736,7 @@ TEST( MQTT_System, SubscribePublishAsync ) } } - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); } IotSemaphore_Destroy( &publishWaitSem ); @@ -739,14 +754,14 @@ TEST( MQTT_System, LastWillAndTestament ) { bool lwtListenerCreated = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetIf_t lwtNetIf = _IotTestNetworkInterface; + IotMqttNetworkInfo_t lwtNetworkInfo = _IotTestNetworkInfo; char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; IotMqttConnection_t lwtListener = IOT_MQTT_CONNECTION_INITIALIZER; IotMqttConnectInfo_t lwtConnectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER, connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t willSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotTestNetworkConnection_t lwtListenerConnection = IOT_TEST_NETWORK_CONNECTION_INITIALIZER; + IotTestNetworkConnection_t * pLwtListenerConnection = NULL; IotSemaphore_t waitSem; /* Create the wait semaphore. */ @@ -769,22 +784,21 @@ TEST( MQTT_System, LastWillAndTestament ) /* Establish an independent MQTT over TCP connection to receive a Last * Will and Testament message. */ TEST_ASSERT_EQUAL( true, - IotTest_NetworkConnect( &lwtListenerConnection, - &lwtListener ) ); + IotTest_NetworkConnect( &pLwtListenerConnection ) ); lwtListenerCreated = true; if( TEST_PROTECT() ) { - lwtNetIf.pDisconnectContext = &lwtListenerConnection; - lwtNetIf.pSendContext = &lwtListenerConnection; + lwtNetworkInfo.createNetworkConnection = false; + lwtNetworkInfo.pNetworkConnection = pLwtListenerConnection; lwtConnectInfo.cleanSession = true; lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); - status = IotMqtt_Connect( &lwtListener, - &lwtNetIf, + status = IotMqtt_Connect( &lwtNetworkInfo, &lwtConnectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &lwtListener ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -813,16 +827,16 @@ TEST( MQTT_System, LastWillAndTestament ) willInfo.pPayload = _pSamplePayload; willInfo.payloadLength = _samplePayloadLength; - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Abruptly close the MQTT connection. This should cause the LWT * to be sent to the LWT listener. */ IotTest_NetworkClose( NULL ); - IotMqtt_Disconnect( _IotTestMqttConnection, true ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); IotTest_NetworkDestroy( NULL ); /* Check that the LWT was received. */ @@ -833,15 +847,15 @@ TEST( MQTT_System, LastWillAndTestament ) } } - IotMqtt_Disconnect( lwtListener, false ); - IotTest_NetworkDestroy( &lwtListenerConnection ); + IotMqtt_Disconnect( lwtListener, 0 ); + IotTest_NetworkDestroy( pLwtListenerConnection ); lwtListenerCreated = false; } if( lwtListenerCreated == true ) { - IotTest_NetworkClose( &lwtListenerConnection ); - IotTest_NetworkDestroy( &lwtListenerConnection ); + IotTest_NetworkClose( pLwtListenerConnection ); + IotTest_NetworkDestroy( pLwtListenerConnection ); } } @@ -872,10 +886,10 @@ TEST( MQTT_System, RestorePreviousSession ) if( TEST_PROTECT() ) { /* Establish a persistent MQTT connection. */ - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Add a subscription. */ @@ -892,7 +906,7 @@ TEST( MQTT_System, RestorePreviousSession ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Disconnect the MQTT connection and clean up network connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); IotTest_NetworkCleanup(); /* Re-establish the network connection. */ @@ -902,10 +916,10 @@ TEST( MQTT_System, RestorePreviousSession ) connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ @@ -928,7 +942,7 @@ TEST( MQTT_System, RestorePreviousSession ) } /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); IotTest_NetworkCleanup(); } else @@ -948,13 +962,13 @@ TEST( MQTT_System, RestorePreviousSession ) * session to clean up persistent sessions on the MQTT server created by this * test. */ connectInfo.cleanSession = true; - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); } } @@ -985,10 +999,10 @@ TEST( MQTT_System, WaitAfterDisconnect ) publishInfo.retryMs = 5000; /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -1006,7 +1020,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) } /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); if( TEST_PROTECT() ) { @@ -1052,10 +1066,10 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with a completion callback. */ @@ -1123,10 +1137,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + status = IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS ); + IOT_TEST_MQTT_TIMEOUT_MS, + &_IotTestMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with to the test topics. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 700cb0e094..34e4f475a3 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -32,17 +32,8 @@ /* Standard includes. */ #include -/* POSIX includes. */ -#ifdef POSIX_TIME_HEADER - #include POSIX_TIME_HEADER -#else - #include -#endif -#ifdef POSIX_UNISTD_HEADER - #include POSIX_UNISTD_HEADER -#else - #include -#endif +/* Common include. */ +#include "iot_common.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -145,6 +136,26 @@ static bool _publishSetDupCalled = false; */ static int32_t _pingreqSendCount = 0; +/** + * @brief Counts how many times #_close has been called. + */ +static int32_t _closeCount = 0; + +/** + * @brief An MQTT connection to share among the tests. + */ +static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/** + * @brief An #IotMqttNetworkInfo_t to share among the tests. + */ +static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + +/** + * @brief An #IotNetworkInterface_t to share among the tests. + */ +static IotNetworkInterface_t _networkInterface = { 0 }; + /*-----------------------------------------------------------*/ /** @@ -152,11 +163,8 @@ static int32_t _pingreqSendCount = 0; */ static void _incomingPingresp( void * pArgument ) { - /* The sleep time calculation below does not work if the short keep-alive - * interval is 1 second or greater. */ - #if _SHORT_KEEP_ALIVE_MS >= 1000 - #error "_SHORT_KEEP_ALIVE_MS must be less than 1000." - #endif + /* Silence warnings about unused parameters. */ + ( void ) pArgument; /* This test will not work if the response wait time is too small. */ #if IOT_MQTT_RESPONSE_WAIT_MS < ( 2 * _SHORT_KEEP_ALIVE_MS + 100 ) @@ -167,51 +175,40 @@ static void _incomingPingresp( void * pArgument ) static uint64_t lastInvokeTime = 0; uint64_t currentTime = IotClock_GetTimeMs(); - /* A PINGRESP packet. */ - const uint8_t pPingresp[ 2 ] = { _MQTT_PACKET_TYPE_PINGRESP, 0x00 }; - - /* Sleep time of twice the keep-alive interval. */ - const struct timespec sleepTime = { .tv_sec = 0, .tv_nsec = 2 * _SHORT_KEEP_ALIVE_MS * 1000000 }; - /* Increment invoke count for this function. */ invokeCount++; /* Sleep to simulate the network round-trip time. */ - if( nanosleep( &sleepTime, NULL ) == 0 ) + IotClock_SleepMs( 2 * _SHORT_KEEP_ALIVE_MS ); + + /* Respond with a PINGRESP. */ + if( invokeCount <= _KEEP_ALIVE_COUNT ) { - /* Respond with a PINGRESP. */ - if( invokeCount <= _KEEP_ALIVE_COUNT ) + /* Log a status with Unity, as this test may take a while. */ + UnityPrint( "KeepAlivePeriodic " ); + UnityPrintNumber( ( UNITY_INT ) invokeCount ); + UnityPrint( " of " ); + UnityPrintNumber( ( UNITY_INT ) _KEEP_ALIVE_COUNT ); + UnityPrint( " DONE at " ); + UnityPrintNumber( ( UNITY_INT ) IotClock_GetTimeMs() ); + UnityPrint( " ms" ); + + if( invokeCount > 1 ) { - /* Log a status with Unity, as this test may take a while. */ - UnityPrint( "KeepAlivePeriodic " ); - UnityPrintNumber( ( UNITY_INT ) invokeCount ); - UnityPrint( " of " ); - UnityPrintNumber( ( UNITY_INT ) _KEEP_ALIVE_COUNT ); - UnityPrint( " DONE at " ); - UnityPrintNumber( ( UNITY_INT ) IotClock_GetTimeMs() ); - UnityPrint( " ms" ); - - if( invokeCount > 1 ) - { - UnityPrint( " (+" ); - UnityPrintNumber( ( UNITY_INT ) ( currentTime - lastInvokeTime ) ); - UnityPrint( " ms)." ); - } - else - { - UnityPrint( "." ); - } + UnityPrint( " (+" ); + UnityPrintNumber( ( UNITY_INT ) ( currentTime - lastInvokeTime ) ); + UnityPrint( " ms)." ); + } + else + { + UnityPrint( "." ); + } - UNITY_PRINT_EOL(); - lastInvokeTime = currentTime; + UNITY_PRINT_EOL(); + lastInvokeTime = currentTime; - ( void ) IotMqtt_ReceiveCallback( pArgument, - NULL, - pPingresp, - 2, - 0, - NULL ); - } + IotMqtt_ReceiveCallback( NULL, + _pMqttConnection ); } } @@ -220,14 +217,14 @@ static void _incomingPingresp( void * pArgument ) /** * @brief PUBLISH set DUP function override. */ -static void _publishSetDup( bool awsIotMqttMode, - uint8_t * pPublishPacket, +static void _publishSetDup( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, uint16_t * pNewPacketIdentifier ) { _publishSetDupCalled = true; - _IotMqtt_PublishSetDup( awsIotMqttMode, - pPublishPacket, + _IotMqtt_PublishSetDup( pPublishPacket, + pPacketIdentifierHigh, pNewPacketIdentifier ); } @@ -265,15 +262,14 @@ static size_t _sendPingreq( void * pSendContext, const uint8_t * pMessage, size_t messageLength ) { - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pSendContext; - /* Silence warnings about unused parameters. */ + ( void ) pSendContext; ( void ) pMessage; /* Create a thread that responds with PINGRESP, then increment the PINGREQ * send counter if successful. */ if( Iot_CreateDetachedThread( _incomingPingresp, - &( pMqttConnection->network.pSendContext ), + NULL, IOT_THREAD_DEFAULT_PRIORITY, IOT_THREAD_DEFAULT_STACK_SIZE ) == true ) { @@ -302,7 +298,7 @@ static size_t _sendDelay( void * pSendContext, IotSemaphore_Post( pWaitSem ); /* Delay for 2 seconds. */ - sleep( 2 ); + IotClock_SleepMs( 2000 ); /* This function returns the message length to simulate a successful send. */ return messageLength; @@ -327,9 +323,13 @@ static size_t _dupChecker( void * pSendContext, * for the AWS IoT MQTT server. */ #if _AWS_IOT_MQTT_SERVER == true static uint16_t lastPacketIdentifier = 0; - uint16_t currentPacketIdentifier = 0; - size_t bytesProcessed = 0; - IotMqttPublishInfo_t publishInfo = { 0 }; + _mqttPacket_t publishPacket = { 0 }; + _mqttOperation_t publishOperation = { 0 }; + + publishPacket.type = publishFlags; + publishPacket.pIncomingPublish = &publishOperation; + publishPacket.remainingLength = 8 + _TEST_TOPIC_NAME_LENGTH; + publishPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - publishPacket.remainingLength ); #endif /* Ignore any MQTT packet that's not a PUBLISH. */ @@ -345,18 +345,13 @@ static size_t _dupChecker( void * pSendContext, { #if _AWS_IOT_MQTT_SERVER == true /* Deserialize the PUBLISH to read the packet identifier. */ - if( _IotMqtt_DeserializePublish( pMessage, - messageLength, - &publishInfo, - &lastPacketIdentifier, - &bytesProcessed ) != IOT_MQTT_SUCCESS ) + if( _IotMqtt_DeserializePublish( &publishPacket ) != IOT_MQTT_SUCCESS ) { status = false; } else { - /* Ensure that all bytes of the PUBLISH were processed. */ - status = ( bytesProcessed == messageLength ); + lastPacketIdentifier = publishPacket.packetIdentifier; } #else /* if _AWS_IOT_MQTT_SERVER == true */ /* DUP flag should not be set on this function's first run. */ @@ -373,25 +368,15 @@ static size_t _dupChecker( void * pSendContext, { #if _AWS_IOT_MQTT_SERVER == true /* Deserialize the PUBLISH to read the packet identifier. */ - if( _IotMqtt_DeserializePublish( pMessage, - messageLength, - &publishInfo, - ¤tPacketIdentifier, - &bytesProcessed ) != IOT_MQTT_SUCCESS ) + if( _IotMqtt_DeserializePublish( &publishPacket ) != IOT_MQTT_SUCCESS ) { status = false; } else { - /* Ensure that all bytes of the PUBLISH were processed. */ - status = ( bytesProcessed == messageLength ); - /* Check that the packet identifier is different. */ - if( status == true ) - { - status = ( currentPacketIdentifier != lastPacketIdentifier ); - lastPacketIdentifier = currentPacketIdentifier; - } + status = ( publishPacket.packetIdentifier != lastPacketIdentifier ); + lastPacketIdentifier = publishPacket.packetIdentifier; } #else /* if _AWS_IOT_MQTT_SERVER == true */ /* DUP flag should be set when this function runs again. */ @@ -416,18 +401,62 @@ static size_t _dupChecker( void * pSendContext, /*-----------------------------------------------------------*/ /** - * @brief A disconnect function that counts how many times it was invoked. + * @brief A network receive function that simulates receiving a PINGRESP. */ -static IotNetworkError_t _disconnect( void * pDisconnectContext ) +static size_t _receivePingresp( void * pReceiveContext, + uint8_t * pBuffer, + size_t bytesRequested ) { - int32_t * pDisconnectCount = ( int32_t * ) pDisconnectContext; + size_t bytesReceived = 0; + static size_t receiveIndex = 0; + const uint8_t pPingresp[ 2 ] = { _MQTT_PACKET_TYPE_PINGRESP, 0x00 }; - /* Increment the counter for how many times this function was invoked. */ - if( pDisconnectCount != NULL ) + /* Silence warnings about unused parameters. */ + ( void ) pReceiveContext; + + /* Receive of PINGRESP should only ever request 1 byte. */ + if( bytesRequested == 1 ) { - ( *pDisconnectCount )++; + /* Write a byte of PINGRESP. */ + *pBuffer = pPingresp[ receiveIndex ]; + bytesReceived = 1; + + /* Alternate the byte of PINGRESP to write. */ + receiveIndex = ( receiveIndex + 1 ) % 2; } + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A function for setting the receive callback that just returns success. + */ +static IotNetworkError_t _setReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pReceiveContext ) +{ + /* Silence warnings about unused parameters. */ + ( void ) pConnection; + ( void ) receiveCallback; + ( void ) pReceiveContext; + + return IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A network close function that counts how many times it was invoked. + */ +static IotNetworkError_t _close( void * pCloseContext ) +{ + /* Silence warnings about unused parameters. */ + ( void ) pCloseContext; + + _closeCount++; + return IOT_NETWORK_SUCCESS; } @@ -472,6 +501,18 @@ TEST_SETUP( MQTT_Unit_API ) _publishSetDupCalled = false; _pingreqSendCount = 0; + /* Reset the network info and interface. */ + ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); + _networkInterface.setReceiveCallback = _setReceiveCallback; + _networkInfo.pNetworkInterface = &_networkInterface; + + /* Reset the counters. */ + _pingreqSendCount = 0; + _closeCount = 0; + + /* Initialize libraries. */ + TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); } @@ -482,6 +523,7 @@ TEST_SETUP( MQTT_Unit_API ) */ TEST_TEAR_DOWN( MQTT_Unit_API ) { + IotCommon_Cleanup(); IotMqtt_Cleanup(); } @@ -516,32 +558,30 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) */ TEST( MQTT_Unit_API, OperationCreateDestroy ) { - _mqttConnection_t * pMqttConnection = NULL; _mqttOperation_t * pOperation = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - /* Create a new MQTT connection with an empty network interface. */ - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Adjustment to reference count based on keep-alive status. */ - const int32_t keepAliveReference = 1 + ( ( pMqttConnection->keepAliveMs != 0 ) ? 1 : 0 ); + const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->keepAliveMs != 0 ) ? 1 : 0 ); /* A new MQTT connection should only have a possible reference for keep-alive. */ - TEST_ASSERT_EQUAL_INT32( keepAliveReference, pMqttConnection->references ); + TEST_ASSERT_EQUAL_INT32( keepAliveReference, _pMqttConnection->references ); /* Create a new operation referencing the MQTT connection. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, IOT_MQTT_FLAG_WAITABLE, NULL, &pOperation ) ); /* Check reference counts and list placement. */ - TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, pMqttConnection->references ); + TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, _pMqttConnection->references ); TEST_ASSERT_EQUAL_INT32( 2, pOperation->jobReference ); - TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( pMqttConnection->pendingProcessing ), + TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( _pMqttConnection->pendingProcessing ), NULL, NULL, &( pOperation->link ) ) ); @@ -550,7 +590,7 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _decrementReferencesJob, pOperation, &( pOperation->job ) ) ); - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_Schedule( &( _IotMqttTaskPool ), + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, &( pOperation->job ), 0 ) ); @@ -558,15 +598,15 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); /* Check reference counts after job completion. */ - TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, pMqttConnection->references ); + TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, _pMqttConnection->references ); TEST_ASSERT_EQUAL_INT32( 1, pOperation->jobReference ); - TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( pMqttConnection->pendingProcessing ), + TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( _pMqttConnection->pendingProcessing ), NULL, NULL, &( pOperation->link ) ) ); /* Disconnect the MQTT connection, then call Wait to clean up the operation. */ - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); IotMqtt_Wait( pOperation, 0 ); } @@ -578,29 +618,31 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) */ TEST( MQTT_Unit_API, OperationWaitTimeout ) { - _mqttConnection_t * pMqttConnection = NULL; _mqttOperation_t * pOperation = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotSemaphore_t waitSem; /* An arbitrary MQTT packet for this test. */ - uint8_t pPacket[ 2 ] = { _MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; + static uint8_t pPacket[ 2 ] = { _MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); if( TEST_PROTECT() ) { - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.pSendContext = &waitSem; - networkInterface.send = _sendDelay; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Set the network interface send function. */ + _networkInterface.send = _sendDelay; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Set parameter to network send function. */ + _pMqttConnection->pNetworkConnection = &waitSem; /* Create a new operation referencing the MQTT connection. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, IOT_MQTT_FLAG_WAITABLE, NULL, &pOperation ) ); @@ -623,12 +665,12 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( pOperation, 10 ) ); /* Check reference count after a timed out wait. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotMutex_Lock( &( _pMqttConnection->referencesMutex ) ); TEST_ASSERT_EQUAL_INT32( 1, pOperation->jobReference ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + IotMutex_Unlock( &( _pMqttConnection->referencesMutex ) ); /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); /* Clean up the MQTT library, which waits for the send job to finish. The * library must be re-initialized so that test tear down does not crash. */ @@ -648,25 +690,25 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) TEST( MQTT_Unit_API, ConnectParameters ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + _networkInterface.send = _sendSuccess; + _networkInterface.close = _close; + /* Check that the network interface is validated. */ - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - networkInterface.send = _sendSuccess; /* Check that the connection info is validated. */ - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; @@ -675,10 +717,10 @@ TEST( MQTT_Unit_API, ConnectParameters ) connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Connect with bad subscription count. */ @@ -686,19 +728,19 @@ TEST( MQTT_Unit_API, ConnectParameters ) subscription.pTopicFilter = _TEST_TOPIC_NAME; subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; subscription.callback.function = _SUBSCRIPTION_CALLBACK; - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); connectInfo.previousSubscriptionCount = 1; /* Check that the will info is validated when it's provided. */ connectInfo.pWillInfo = &willInfo; - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); willInfo.pTopicName = _TEST_TOPIC_NAME; willInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; @@ -706,18 +748,18 @@ TEST( MQTT_Unit_API, ConnectParameters ) /* Check that a will message longer than 65535 is not allowed. */ willInfo.pPayload = ""; willInfo.payloadLength = 65536; - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); willInfo.payloadLength = 0; /* Check that passing a wait time of 0 returns immediately. */ - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - 0 ); + 0, + &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, status ); } @@ -731,12 +773,11 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) { int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; /* Initialize parameters. */ - networkInterface.send = _sendSuccess; + _networkInterface.send = _sendSuccess; + _networkInterface.close = _close; connectInfo.keepAliveSeconds = 100; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; @@ -748,10 +789,10 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) /* Call CONNECT. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); /* If the return value is timeout, then all memory allocation succeeded * and the loop can exit. The expected return value is timeout (and not @@ -777,13 +818,12 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) { int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; /* Initialize parameters. */ - networkInterface.send = _sendSuccess; + _networkInterface.send = _sendSuccess; + _networkInterface.close = _close; connectInfo.cleanSession = false; connectInfo.keepAliveSeconds = 100; connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; @@ -801,10 +841,10 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) /* Call CONNECT with a previous session. Memory allocation will fail at * various times during this call. */ - status = IotMqtt_Connect( &mqttConnection, - &networkInterface, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS ); + _TIMEOUT_MS, + &_pMqttConnection ); /* If the return value is timeout, then all memory allocation succeeded * and the loop can exit. The expected return value is timeout (and not @@ -829,14 +869,11 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) TEST( MQTT_Unit_API, DisconnectMallocFail ) { int32_t i = 0; - int32_t disconnectCount = 0; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - _mqttConnection_t * pMqttConnection = NULL; /* Set the members of the network interface. */ - networkInterface.pDisconnectContext = &disconnectCount; - networkInterface.send = _sendSuccess; - networkInterface.disconnect = _disconnect; + _networkInterface.send = _sendSuccess; + _networkInterface.close = _close; + _networkInfo.createNetworkConnection = false; for( i = 0; i < _DISCONNECT_MALLOC_LIMIT; i++ ) { @@ -844,19 +881,19 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) UnityMalloc_MakeMallocFailAfterCount( -1 ); /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set malloc to eventually fail. */ UnityMalloc_MakeMallocFailAfterCount( i ); /* Call DISCONNECT; this function should always perform cleanup regardless * of memory allocation errors. */ - IotMqtt_Disconnect( pMqttConnection, false ); - TEST_ASSERT_EQUAL_INT( 1, disconnectCount ); - disconnectCount = 0; + IotMqtt_Disconnect( _pMqttConnection, 0 ); + TEST_ASSERT_EQUAL_INT( 1, _closeCount ); + _closeCount = 0; } } @@ -869,43 +906,39 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) TEST( MQTT_Unit_API, PublishQoS0Parameters ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttReference_t publishReference = IOT_MQTT_REFERENCE_INITIALIZER; IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.send = _sendSuccess; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initialize parameters. */ + _networkInterface.send = _sendSuccess; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); if( TEST_PROTECT() ) { /* Check that the publish info is validated. */ - status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, NULL, NULL ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); publishInfo.pTopicName = _TEST_TOPIC_NAME; publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; /* Check that a QoS 0 publish is refused if a notification is requested. */ - status = IotMqtt_Publish( pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishReference ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishReference ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* If valid parameters are passed, QoS 0 publish should always return success. */ - status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, 0, &publishReference ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, 0, &publishReference ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Allow time for the send job to run and clean up the PUBLISH. QoS 0 - * PUBLISH provides no mechanism to wait on completion, so sleep is used. */ - sleep( 1 ); } - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -918,16 +951,16 @@ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) { int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.send = _sendSuccess; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initialize parameters. */ + _networkInterface.send = _sendSuccess; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the necessary members of publish info. */ publishInfo.pTopicName = _TEST_TOPIC_NAME; @@ -941,7 +974,7 @@ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) /* Call PUBLISH. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Publish( pMqttConnection, &publishInfo, 0, NULL, NULL ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, NULL, NULL ); /* Once PUBLISH succeeds, the loop can exit. */ if( status == IOT_MQTT_SUCCESS ) @@ -953,12 +986,9 @@ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) * failure. */ TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); } - - /* Wait for any pending QoS 0 publishes to clean up. */ - sleep( 1 ); } - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -972,18 +1002,18 @@ TEST( MQTT_Unit_API, PublishQoS1 ) { int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.send = _sendSuccess; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initialize parameters. */ + _networkInterface.send = _sendSuccess; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; @@ -993,7 +1023,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) if( TEST_PROTECT() ) { /* Setting the waitable flag with no reference is not allowed. */ - status = IotMqtt_Publish( pMqttConnection, + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, @@ -1001,7 +1031,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Setting both the waitable flag and callback info is not allowed. */ - status = IotMqtt_Publish( pMqttConnection, + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, &callbackInfo, @@ -1015,7 +1045,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) /* Call PUBLISH. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Publish( pMqttConnection, + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, @@ -1036,7 +1066,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) } /* Clean up MQTT connection. */ - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -1050,21 +1080,25 @@ TEST( MQTT_Unit_API, PublishQoS1 ) */ TEST( MQTT_Unit_API, PublishDuplicates ) { - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - _mqttConnection_t * pMqttConnection = NULL; + static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; - volatile bool dupCheckResult = false; + bool dupCheckResult = false; uint64_t startTime = 0; - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.pSendContext = ( void * ) &dupCheckResult; - networkInterface.send = _dupChecker; - networkInterface.serialize.publishSetDup = _publishSetDup; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initializer parameters. */ + serializer.serialize.publishSetDup = _publishSetDup; + _networkInterface.send = _dupChecker; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Set the serializers and parameter to the send function. */ + _pMqttConnection->pNetworkConnection = &dupCheckResult; + _pMqttConnection->pSerializer = &serializer; /* Set the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; @@ -1081,7 +1115,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) { /* Send a PUBLISH with retransmissions enabled. */ TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, - IotMqtt_Publish( pMqttConnection, + IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, @@ -1100,7 +1134,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) } /* Clean up MQTT connection. */ - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); /* Check that the set DUP override was called. */ if( TEST_PROTECT() ) @@ -1118,12 +1152,17 @@ TEST( MQTT_Unit_API, PublishDuplicates ) TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t mqttConnection = { 0 }; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + /* Check that subscription info is validated. */ - status = IotMqtt_Subscribe( &mqttConnection, + status = IotMqtt_Subscribe( _pMqttConnection, &subscription, 1, IOT_MQTT_FLAG_WAITABLE, @@ -1131,7 +1170,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) &reference ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_Unsubscribe( &mqttConnection, + status = IotMqtt_Unsubscribe( _pMqttConnection, &subscription, 1, IOT_MQTT_FLAG_WAITABLE, @@ -1144,7 +1183,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) subscription.callback.function = _SUBSCRIPTION_CALLBACK; /* A reference must be provided for a waitable SUBSCRIBE. */ - status = IotMqtt_Subscribe( &mqttConnection, + status = IotMqtt_Subscribe( _pMqttConnection, &subscription, 1, IOT_MQTT_FLAG_WAITABLE, @@ -1152,13 +1191,15 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_Unsubscribe( &mqttConnection, + status = IotMqtt_Unsubscribe( _pMqttConnection, &subscription, 1, IOT_MQTT_FLAG_WAITABLE, NULL, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -1171,17 +1212,17 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) { int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - _mqttConnection_t * pMqttConnection = NULL; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttReference_t subscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.send = _sendSuccess; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initializer parameters. */ + _networkInterface.send = _sendSuccess; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the necessary members of the subscription. */ subscription.pTopicFilter = _TEST_TOPIC_NAME; @@ -1196,7 +1237,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) /* Call SUBSCRIBE. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Subscribe( pMqttConnection, + status = IotMqtt_Subscribe( _pMqttConnection, &subscription, 1, IOT_MQTT_FLAG_WAITABLE, @@ -1217,10 +1258,10 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) } /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( pMqttConnection->subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -1233,17 +1274,17 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) { int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - _mqttConnection_t * pMqttConnection = NULL; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; - /* Set the members of the network interface and create a new MQTT connection. */ - networkInterface.send = _sendSuccess; - pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, - 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initialize parameters. */ + _networkInterface.send = _sendSuccess; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the necessary members of the subscription. */ subscription.pTopicFilter = _TEST_TOPIC_NAME; @@ -1258,7 +1299,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) /* Call UNSUBSCRIBE. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Unsubscribe( pMqttConnection, + status = IotMqtt_Unsubscribe( _pMqttConnection, &subscription, 1, IOT_MQTT_FLAG_WAITABLE, @@ -1279,10 +1320,10 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) } /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( pMqttConnection->subscriptionList ) ) ); + TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -1292,50 +1333,43 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) */ TEST( MQTT_Unit_API, KeepAlivePeriodic ) { - int32_t disconnectCount = 0; - _mqttConnection_t * pMqttConnection = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - /* An estimate for the amount of time this test requires. */ - const unsigned sleepTime = ( ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) / 1000 ) + - ( ( ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) % 1000 ) != 0 ) + - ( IOT_MQTT_RESPONSE_WAIT_MS * _KEEP_ALIVE_COUNT ) / 1000 + 2; + const uint32_t sleepTimeMs = ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) + + ( IOT_MQTT_RESPONSE_WAIT_MS * _KEEP_ALIVE_COUNT ) + 1500; /* Print a newline so this test may log its status. */ UNITY_PRINT_EOL(); - /* Set the members of the network interface and create a new MQTT connection - * with keep-alive. */ - networkInterface.send = _sendPingreq; - networkInterface.pDisconnectContext = &disconnectCount; - networkInterface.disconnect = _disconnect; - pMqttConnection = IotTestMqtt_createMqttConnection( false, - &networkInterface, - 1 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Initialize parameters. */ + _networkInterface.send = _sendPingreq; + _networkInterface.receive = _receivePingresp; + _networkInterface.close = _close; - /* Connection send context can only be set after connection is created. */ - pMqttConnection->network.pSendContext = pMqttConnection; + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 1 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set a short keep-alive interval so this test runs faster. */ - pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; - pMqttConnection->nextKeepAliveMs = _SHORT_KEEP_ALIVE_MS; + _pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; + _pMqttConnection->nextKeepAliveMs = _SHORT_KEEP_ALIVE_MS; /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, - IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), - &( pMqttConnection->keepAliveJob ), - pMqttConnection->nextKeepAliveMs ) ); + IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, + &( _pMqttConnection->keepAliveJob ), + _pMqttConnection->nextKeepAliveMs ) ); /* Sleep to allow ample time for periodic PINGREQ sends and PINGRESP responses. */ - sleep( sleepTime ); + IotClock_SleepMs( sleepTimeMs ); /* Disconnect the connection. */ - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - /* Check the counters for PINGREQ send and disconnect. */ + /* Check the counters for PINGREQ send and close. */ TEST_ASSERT_EQUAL_INT32( _KEEP_ALIVE_COUNT + 1, _pingreqSendCount ); - TEST_ASSERT_EQUAL_INT32( 2, disconnectCount ); + TEST_ASSERT_EQUAL_INT32( 2, _closeCount ); } /*-----------------------------------------------------------*/ @@ -1346,37 +1380,39 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) */ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) { - _mqttConnection_t * pMqttConnection = NULL; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; IotSemaphore_t waitSem; + /* Initialize parameters. */ + _networkInterface.send = _sendSuccess; + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); if( TEST_PROTECT() ) { - /* Set the members of the network interface and create a new MQTT connection - * with keep-alive. */ - networkInterface.pSendContext = &waitSem; - networkInterface.send = _sendSuccess; - pMqttConnection = IotTestMqtt_createMqttConnection( false, - &networkInterface, - 1 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + &_networkInfo, + 1 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Set the parameter to the send function. */ + _pMqttConnection->pNetworkConnection = &waitSem; /* Set a short keep-alive interval so this test runs faster. */ - pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; + _pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; + _pMqttConnection->nextKeepAliveMs = _SHORT_KEEP_ALIVE_MS; /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, - IotTaskPool_ScheduleDeferred( &( _IotMqttTaskPool ), - &( pMqttConnection->keepAliveJob ), - _SHORT_KEEP_ALIVE_MS ) ); + IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, + &( _pMqttConnection->keepAliveJob ), + _pMqttConnection->nextKeepAliveMs ) ); /* Wait for the keep-alive job to send a PINGREQ. */ IotSemaphore_Wait( &waitSem ); /* Immediately disconnect the connection. */ - IotMqtt_Disconnect( pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } IotSemaphore_Destroy( &waitSem ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 4e0fe07507..b5e28a9061 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -32,6 +32,9 @@ /* Standard includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -57,24 +60,6 @@ #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default malloc and free functions in dynamic memory mode. - */ -#if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) - #ifndef IotTest_Malloc - #include - #define IotTest_Malloc malloc - #endif - #ifndef IotTest_Free - #include - #define IotTest_Free free - #endif -#endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ -/** @endcond */ - /** @brief Default CONNACK packet for the receive tests. */ static const uint8_t _pConnackTemplate[] = { 0x20, 0x02, 0x00, 0x00 }; /** @brief Default PUBLISH packet for the receive tests. */ @@ -124,38 +109,13 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; */ #define _PUBLISH_CALLBACK_TIMEOUT ( 1000 ) -/** - * @brief Size of data stream in #TEST_MQTT_Unit_Receive_DataStream_. - */ -#define _DATA_STREAM_SIZE \ - ( sizeof( _pConnackTemplate ) + \ - sizeof( _pSubackTemplate ) + \ - sizeof( _pPublishTemplate ) + \ - sizeof( _pUnsubackTemplate ) + \ - sizeof( _pPingrespTemplate ) ) - -/** - * @brief Number of PUBLISH messages in the stream for #TEST_MQTT_Unit_Receive_PublishStream_ - * and #TEST_MQTT_Unit_Receive_PublishInvalidStream_. - */ -#define _PUBLISH_STREAM_COUNT ( 3 ) - /** * @brief Declare a buffer holding a packet and its size. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ - uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ - const size_t sizeName = sizeof( pTemplate ); \ - ( void ) memcpy( bufferName, pTemplate, sizeName ); \ - TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); -#else - #define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ - uint8_t * bufferName = _mallocWrapper( sizeof( pTemplate ) ); \ - TEST_ASSERT_NOT_EQUAL( NULL, bufferName ); \ - const size_t sizeName = sizeof( pTemplate ); \ +#define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ + uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ + const size_t sizeName = sizeof( pTemplate ); \ ( void ) memcpy( bufferName, pTemplate, sizeName ); -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /** * @brief Initializer for operations in the tests. @@ -170,15 +130,32 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /*-----------------------------------------------------------*/ +/** + * @brief Context for calls to the network receive function. + */ +typedef struct _receiveContext +{ + const uint8_t * pData; /**< @brief The data to receive. */ + size_t dataLength; /**< @brief Length of data. */ + size_t dataIndex; /**< @brief Next byte of data to read. */ +} _receiveContext_t; + +/*-----------------------------------------------------------*/ + /** * @brief The MQTT connection shared by all the tests. */ -static _mqttConnection_t * _pMqttConnection = NULL; +static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/** + * @brief The network interface shared by all the tests. + */ +static IotNetworkInterface_t _networkInterface = { 0 }; /** - * @brief Synchronizes malloc and free, which may be called from different threads. + * @brief The subscription shared by all the tests. */ -static IotSemaphore_t _mallocSemaphore; +static IotMqttSubscription_t _subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; /** * @brief Tracks whether a deserializer override was called for a test. @@ -190,65 +167,40 @@ static bool _deserializeOverrideCalled = false; */ static bool _getPacketTypeCalled = false; -/*-----------------------------------------------------------*/ - /** - * @brief Wrapper for IotTest_Malloc. + * @brief Tracks whether #_getRemainingLength has been called. */ -static void * _mallocWrapper( size_t size ) -{ - void * pBuffer = NULL; - - ( void ) size; - - #if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) - pBuffer = IotTest_Malloc( size ); - - /* Decrement the malloc semaphore. */ - if( pBuffer != NULL ) - #endif - { - if( ( IotSemaphore_TryWait( &_mallocSemaphore ) == true ) && - ( IotSemaphore_GetCount( &_mallocSemaphore ) > 0 ) ) - { - /* If the malloc semaphore value isn't what's expected, return NULL. */ - IotTest_Free( pBuffer ); - pBuffer = NULL; - } - } - - return pBuffer; -} +static bool _getRemainingLengthCalled = false; +/** + * @brief Tracks whether #_close has been called. + */ +static bool _networkCloseCalled = false; /*-----------------------------------------------------------*/ /** * @brief Get packet type function override. */ -static uint8_t _getPacketType( const uint8_t * pPacket, - size_t packetSize ) +static uint8_t _getPacketType( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) { _getPacketTypeCalled = true; - return _IotMqtt_GetPacketType( pPacket, packetSize ); + return _IotMqtt_GetPacketType( pNetworkConnection, pNetworkInterface ); } /*-----------------------------------------------------------*/ /** - * @brief Wrapper for IotTest_Free. + * @brief Get remaining length function override. */ -static void _freeWrapper( void * ptr ) +static size_t _getRemainingLength( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) { - /* This function should do nothing in static memory mode. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - ( void ) ptr; - #else - IotTest_Free( ptr ); - #endif - - IotSemaphore_Post( &_mallocSemaphore ); + _getRemainingLengthCalled = true; + + return _IotMqtt_GetRemainingLength( pNetworkConnection, pNetworkInterface ); } /*-----------------------------------------------------------*/ @@ -256,15 +208,11 @@ static void _freeWrapper( void * ptr ) /** * @brief Deserializer override for CONNACK. */ -static IotMqttError_t _deserializeConnack( const uint8_t * pConnackStart, - size_t dataLength, - size_t * pBytesProcessed ) +static IotMqttError_t _deserializeConnack( _mqttPacket_t * pConnack ) { _deserializeOverrideCalled = true; - return _IotMqtt_DeserializeConnack( pConnackStart, - dataLength, - pBytesProcessed ); + return _IotMqtt_DeserializeConnack( pConnack ); } /*-----------------------------------------------------------*/ @@ -272,19 +220,11 @@ static IotMqttError_t _deserializeConnack( const uint8_t * pConnackStart, /** * @brief Deserializer override for PUBLISH. */ -static IotMqttError_t _deserializePublish( const uint8_t * pPublishStart, - size_t dataLength, - IotMqttPublishInfo_t * pOutput, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +static IotMqttError_t _deserializePublish( _mqttPacket_t * pPublish ) { _deserializeOverrideCalled = true; - return _IotMqtt_DeserializePublish( pPublishStart, - dataLength, - pOutput, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializePublish( pPublish ); } /*-----------------------------------------------------------*/ @@ -292,17 +232,11 @@ static IotMqttError_t _deserializePublish( const uint8_t * pPublishStart, /** * @brief Deserializer override for PUBACK. */ -static IotMqttError_t _deserializePuback( const uint8_t * pPubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +static IotMqttError_t _deserializePuback( _mqttPacket_t * pPuback ) { _deserializeOverrideCalled = true; - return _IotMqtt_DeserializePuback( pPubackStart, - dataLength, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializePuback( pPuback ); } /*-----------------------------------------------------------*/ @@ -310,19 +244,11 @@ static IotMqttError_t _deserializePuback( const uint8_t * pPubackStart, /** * @brief Deserializer override for SUBACK. */ -static IotMqttError_t _deserializeSuback( IotMqttConnection_t mqttConnection, - const uint8_t * pSubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +static IotMqttError_t _deserializeSuback( _mqttPacket_t * pSuback ) { _deserializeOverrideCalled = true; - return _IotMqtt_DeserializeSuback( mqttConnection, - pSubackStart, - dataLength, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializeSuback( pSuback ); } /*-----------------------------------------------------------*/ @@ -330,17 +256,11 @@ static IotMqttError_t _deserializeSuback( IotMqttConnection_t mqttConnection, /** * @brief Deserializer override for UNSUBACK. */ -static IotMqttError_t _deserializeUnsuback( const uint8_t * pUnsubackStart, - size_t dataLength, - uint16_t * pPacketIdentifier, - size_t * pBytesProcessed ) +static IotMqttError_t _deserializeUnsuback( _mqttPacket_t * pUnsuback ) { _deserializeOverrideCalled = true; - return _IotMqtt_DeserializeUnsuback( pUnsubackStart, - dataLength, - pPacketIdentifier, - pBytesProcessed ); + return _IotMqtt_DeserializeUnsuback( pUnsuback ); } /*-----------------------------------------------------------*/ @@ -348,15 +268,11 @@ static IotMqttError_t _deserializeUnsuback( const uint8_t * pUnsubackStart, /** * @brief Deserializer override for PINGRESP. */ -static IotMqttError_t _deserializePingresp( const uint8_t * pPingrespStart, - size_t dataLength, - size_t * pBytesProcessed ) +static IotMqttError_t _deserializePingresp( _mqttPacket_t * pPingresp ) { _deserializeOverrideCalled = true; - return _IotMqtt_DeserializePingresp( pPingrespStart, - dataLength, - pBytesProcessed ); + return _IotMqtt_DeserializePingresp( pPingresp ); } /*-----------------------------------------------------------*/ @@ -380,32 +296,23 @@ static void _operationResetAndPush( _mqttOperation_t * pOperation ) static bool _processBuffer( const _mqttOperation_t * pOperation, const uint8_t * pBuffer, size_t bufferSize, - int32_t expectedBytesProcessed, IotMqttError_t expectedResult ) { - bool status = false; - - /* Call the receive callback on pBuffer. */ - int32_t bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pBuffer, - bufferSize, - 0, - _freeWrapper ); + bool status = true; + _receiveContext_t receiveContext = { 0 }; - /* Free pBuffer if the receive callback wasn't expected to free it. */ - if( expectedBytesProcessed <= 0 ) - { - _freeWrapper( ( void * ) pBuffer ); - } + /* Set the members of the receive context. */ + receiveContext.pData = pBuffer; + receiveContext.dataLength = bufferSize; - /* Check expected bytes processed. */ - status = ( expectedBytesProcessed == bytesProcessed ); + /* Call the receive callback on pBuffer. */ + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); /* Check expected result if operation is given. */ if( pOperation != NULL ) { - status = status && ( expectedResult == pOperation->status ); + status = ( expectedResult == pOperation->status ); } return status; @@ -418,13 +325,12 @@ static bool _processBuffer( const _mqttOperation_t * pOperation, */ static bool _processPublish( const uint8_t * pPublish, size_t publishSize, - int32_t expectedBytesProcessed, uint32_t expectedInvokeCount ) { IotSemaphore_t invokeCount; uint32_t finalInvokeCount = 0, i = 0; - int32_t bytesProcessed = 0; bool waitResult = true; + _receiveContext_t receiveContext = { 0 }; /* Create a semaphore that counts how many times the publish callback is invoked. */ if( IotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) @@ -441,13 +347,13 @@ static bool _processPublish( const uint8_t * pPublish, pSubscription->callback.param1 = &invokeCount; } + /* Set the members of the receive context. */ + receiveContext.pData = pPublish; + receiveContext.dataLength = publishSize; + /* Call the receive callback on pPublish. */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pPublish, - publishSize, - 0, - _freeWrapper ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); /* Check how many times the publish callback is invoked. */ for( i = 0; i < expectedInvokeCount; i++ ) @@ -468,7 +374,6 @@ static bool _processPublish( const uint8_t * pPublish, /* Check results against expected values. */ return ( finalInvokeCount == 0 ) && - ( expectedBytesProcessed == bytesProcessed ) && ( waitResult == true ); } @@ -516,6 +421,65 @@ static IotMqttError_t _serializePuback( uint16_t packetIdentifier, /*-----------------------------------------------------------*/ +/** + * @brief Simulates a network receive function. + */ +static size_t _receive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ) +{ + size_t bytesReceived = 0; + _receiveContext_t * pReceiveContext = pConnection; + + if( pReceiveContext->dataIndex != pReceiveContext->dataLength ) + { + TEST_ASSERT_NOT_EQUAL( 0, bytesRequested ); + TEST_ASSERT_LESS_THAN( pReceiveContext->dataLength, pReceiveContext->dataIndex ); + + /* Calculate how much data to copy. */ + const size_t dataAvailable = pReceiveContext->dataLength - pReceiveContext->dataIndex; + + if( bytesRequested > dataAvailable ) + { + bytesReceived = dataAvailable; + } + else + { + bytesReceived = bytesRequested; + } + + /* Copy data into given buffer. */ + if( bytesReceived > 0 ) + { + ( void ) memcpy( pBuffer, + pReceiveContext->pData + pReceiveContext->dataIndex, + bytesReceived ); + + pReceiveContext->dataIndex += bytesReceived; + } + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A network close function that reports if it was invoked. + */ +static IotNetworkError_t _close( void * pConnection ) +{ + /* Silence warnings about unused parameters. */ + ( void ) pConnection; + + /* Report that this function was called. */ + _networkCloseCalled = true; + + return IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT Receive tests. */ @@ -528,46 +492,55 @@ TEST_GROUP( MQTT_Unit_Receive ); */ TEST_SETUP( MQTT_Unit_Receive ) { - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + + /* Initialize common components. */ + TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); /* Set the deserializer overrides. */ - networkInterface.deserialize.connack = _deserializeConnack; - networkInterface.deserialize.publish = _deserializePublish; - networkInterface.deserialize.puback = _deserializePuback; - networkInterface.deserialize.suback = _deserializeSuback; - networkInterface.deserialize.unsuback = _deserializeUnsuback; - networkInterface.deserialize.pingresp = _deserializePingresp; - networkInterface.getPacketType = _getPacketType; - - /* Create the memory allocation semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &_mallocSemaphore, - 1, - 1 ) ); + serializer.serialize.puback = _serializePuback; + serializer.deserialize.connack = _deserializeConnack; + serializer.deserialize.publish = _deserializePublish; + serializer.deserialize.puback = _deserializePuback; + serializer.deserialize.suback = _deserializeSuback; + serializer.deserialize.unsuback = _deserializeUnsuback; + serializer.deserialize.pingresp = _deserializePingresp; + serializer.getPacketType = _getPacketType; + serializer.getRemainingLength = _getRemainingLength; + + _networkInterface.receive = _receive; + _networkInterface.close = _close; + networkInfo.pNetworkInterface = &_networkInterface; /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); /* Initialize the MQTT connection used by the tests. */ _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, + &networkInfo, 0 ); - TEST_ASSERT_NOT_EQUAL( NULL, _pMqttConnection ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Set the MQTT serializer overrides. */ + _pMqttConnection->pSerializer = &serializer; /* Set the members of the subscription. */ - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_LENGTH; - subscription.callback.function = _publishCallback; + _subscription.pTopicFilter = _TEST_TOPIC_NAME; + _subscription.topicFilterLength = _TEST_TOPIC_LENGTH; + _subscription.callback.function = _publishCallback; /* Add the subscription to the MQTT connection. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, 1, - &subscription, + &_subscription, 1 ) ); - /* Clear the deserialize override called flag. */ + /* Clear functions called flags. */ _deserializeOverrideCalled = false; _getPacketTypeCalled = false; + _getRemainingLengthCalled = false; + _networkCloseCalled = false; } /*-----------------------------------------------------------*/ @@ -578,14 +551,14 @@ TEST_SETUP( MQTT_Unit_Receive ) TEST_TEAR_DOWN( MQTT_Unit_Receive ) { /* Clean up resources taken in test setup. */ - IotMqtt_Disconnect( _pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + IotCommon_Cleanup(); IotMqtt_Cleanup(); - IotSemaphore_Destroy( &_mallocSemaphore ); - _pMqttConnection = NULL; /* Check that the tests used a deserializer override. */ TEST_ASSERT_EQUAL_INT( true, _deserializeOverrideCalled ); TEST_ASSERT_EQUAL_INT( true, _getPacketTypeCalled ); + TEST_ASSERT_EQUAL_INT( true, _getRemainingLengthCalled ); } /*-----------------------------------------------------------*/ @@ -597,13 +570,10 @@ TEST_GROUP_RUNNER( MQTT_Unit_Receive ) { RUN_TEST_CASE( MQTT_Unit_Receive, DecodeRemainingLength ); RUN_TEST_CASE( MQTT_Unit_Receive, InvalidPacket ); - RUN_TEST_CASE( MQTT_Unit_Receive, DataStream ); RUN_TEST_CASE( MQTT_Unit_Receive, ConnackValid ); RUN_TEST_CASE( MQTT_Unit_Receive, ConnackInvalid ); RUN_TEST_CASE( MQTT_Unit_Receive, PublishValid ); RUN_TEST_CASE( MQTT_Unit_Receive, PublishInvalid ); - RUN_TEST_CASE( MQTT_Unit_Receive, PublishStream ); - RUN_TEST_CASE( MQTT_Unit_Receive, PublishInvalidStream ); RUN_TEST_CASE( MQTT_Unit_Receive, PubackValid ); RUN_TEST_CASE( MQTT_Unit_Receive, PubackInvalid ); RUN_TEST_CASE( MQTT_Unit_Receive, SubackValid ); @@ -616,81 +586,94 @@ TEST_GROUP_RUNNER( MQTT_Unit_Receive ) /*-----------------------------------------------------------*/ /** - * @brief Tests the function #_decodeRemainingLength. + * @brief Tests the function for decoding MQTT remaining length. */ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) { - const uint8_t * pEnd = NULL; - size_t decodedLength = 0; - /* Decode 0, which has a 1-byte representation. */ { uint8_t pRemainingLength[ 4 ] = { 0 }; + _receiveContext_t receiveContext = { 0 }; - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); - TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 1 ); - TEST_ASSERT_EQUAL( 0, decodedLength ); + /* Set the members of the receive context. */ + receiveContext.pData = pRemainingLength; + receiveContext.dataLength = 4; + + TEST_ASSERT_EQUAL( 0, + _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ) ); } /* Decode 129, which has a 2-byte representation. */ { uint8_t pRemainingLength[ 4 ] = { 0x81, 0x01, 0x00, 0x00 }; + _receiveContext_t receiveContext = { 0 }; + + /* Set the members of the receive context. */ + receiveContext.pData = pRemainingLength; + receiveContext.dataLength = 4; - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); - TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 2 ); - TEST_ASSERT_EQUAL( 129, decodedLength ); + TEST_ASSERT_EQUAL( 129, + _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ) ); } /* Decode 16,386, which has a 3-byte representation. */ { uint8_t pRemainingLength[ 4 ] = { 0x82, 0x80, 0x01, 0x00 }; + _receiveContext_t receiveContext = { 0 }; - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); - TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 3 ); - TEST_ASSERT_EQUAL( 16386, decodedLength ); + /* Set the members of the receive context. */ + receiveContext.pData = pRemainingLength; + receiveContext.dataLength = 4; + + TEST_ASSERT_EQUAL( 16386, + _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ) ); } /* Decode 268,435,455, which has a 4-byte representation. This value is the * largest representable value. */ { uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x7f }; + _receiveContext_t receiveContext = { 0 }; + + /* Set the members of the receive context. */ + receiveContext.pData = pRemainingLength; + receiveContext.dataLength = 4; - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); - TEST_ASSERT_EQUAL_PTR( pEnd, pRemainingLength + 4 ); - TEST_ASSERT_EQUAL( 268435455, decodedLength ); + TEST_ASSERT_EQUAL( 268435455, + _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ) ); } /* Attempt to decode an invalid value where all continuation bits are set. */ { uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x8f }; + _receiveContext_t receiveContext = { 0 }; + + /* Set the members of the receive context. */ + receiveContext.pData = pRemainingLength; + receiveContext.dataLength = 4; - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, - IotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + TEST_ASSERT_EQUAL( _MQTT_REMAINING_LENGTH_INVALID, + _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ) ); } /* Attempt to decode a 4-byte representation of 0. According to the spec, * this representation is not valid. */ { uint8_t pRemainingLength[ 4 ] = { 0x80, 0x80, 0x80, 0x00 }; + _receiveContext_t receiveContext = { 0 }; - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, - IotTestMqtt_decodeRemainingLength( pRemainingLength, - &pEnd, - &decodedLength ) ); + /* Set the members of the receive context. */ + receiveContext.pData = pRemainingLength; + receiveContext.dataLength = 4; + + TEST_ASSERT_EQUAL( _MQTT_REMAINING_LENGTH_INVALID, + _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ) ); } /* Test tear down for this test group checks that deserializer overrides @@ -698,6 +681,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) * set these values to true so that the checks pass. */ _deserializeOverrideCalled = true; _getPacketTypeCalled = true; + _getRemainingLengthCalled = true; } /*-----------------------------------------------------------*/ @@ -708,119 +692,26 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) */ TEST( MQTT_Unit_Receive, InvalidPacket ) { - int32_t bytesProcessed = 0; uint8_t invalidPacket = 0xf0; + _receiveContext_t receiveContext = { 0 }; + + /* Set the members of the receive context. */ + receiveContext.pData = &invalidPacket; + receiveContext.dataLength = 1; /* Processing a control packet 0xf is a protocol violation. */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - &invalidPacket, - sizeof( uint8_t ), - 0, - NULL ); - TEST_ASSERT_EQUAL( -1, bytesProcessed ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); + + /* Processing an invalid packet should cause the network connection to be closed. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); /* This test should not have called any deserializer. Set the deserialize * override called flag to true so that the check for it passes. */ TEST_ASSERT_EQUAL_INT( false, _deserializeOverrideCalled ); _deserializeOverrideCalled = true; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with an stream - * of data (instead of discrete packets). - */ -TEST( MQTT_Unit_Receive, DataStream ) -{ - size_t copyOffset = 0, processOffset = 0; - int32_t bytesProcessed = 0; - - /* Allocate the data stream depending on the memory allocation mode. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - uint8_t pDataStream[ _DATA_STREAM_SIZE ] = { 0 }; - TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); - #else - uint8_t * pDataStream = _mallocWrapper( _DATA_STREAM_SIZE ); - TEST_ASSERT_NOT_EQUAL( NULL, pDataStream ); - #endif - - /* Construct the data stream by placing a CONNACK, SUBACK, PUBLISH, UNSUBACK, - * and PINGRESP in it. */ - ( void ) memcpy( pDataStream, _pConnackTemplate, sizeof( _pConnackTemplate ) ); - copyOffset += sizeof( _pConnackTemplate ); - ( void ) memcpy( pDataStream + copyOffset, _pSubackTemplate, sizeof( _pSubackTemplate ) ); - copyOffset += sizeof( _pSubackTemplate ); - ( void ) memcpy( pDataStream + copyOffset, _pPublishTemplate, sizeof( _pPublishTemplate ) ); - copyOffset += sizeof( _pPublishTemplate ); - ( void ) memcpy( pDataStream + copyOffset, _pUnsubackTemplate, sizeof( _pUnsubackTemplate ) ); - copyOffset += sizeof( _pUnsubackTemplate ); - ( void ) memcpy( pDataStream + copyOffset, _pPingrespTemplate, sizeof( _pPingrespTemplate ) ); - TEST_ASSERT_EQUAL( _DATA_STREAM_SIZE, copyOffset + sizeof( _pPingrespTemplate ) ); - - /* Passing an offset greater than dataLength should not process anything. */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - 4, - 5, - _freeWrapper ); - TEST_ASSERT_EQUAL( 0, bytesProcessed ); - - /* The first call to process 64 bytes should only process the CONNACK and - * SUBACK. */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 64, - 0, - _freeWrapper ); - TEST_ASSERT_EQUAL( 11, bytesProcessed ); - processOffset += ( size_t ) bytesProcessed; - - /* A second call to process 64 bytes should not process anything, as the - * PUBLISH is incomplete. */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 64, - processOffset, - _freeWrapper ); - TEST_ASSERT_EQUAL( 0, bytesProcessed ); - - /* A call to process 273 bytes should process the PUBLISH packet (272 bytes). */ - TEST_ASSERT_EQUAL_INT( true, _processPublish( pDataStream + processOffset, - 273, - 272, - 1 ) ); - processOffset += 272; - - /* A call to process 5 bytes should only process the UNSUBACK (4 bytes). */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 5, - processOffset, - _freeWrapper ); - TEST_ASSERT_EQUAL( 4, bytesProcessed ); - processOffset += ( size_t ) bytesProcessed; - - /* Process the last 2 bytes (PINGRESP). */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pDataStream, - processOffset + 2, - processOffset, - _freeWrapper ); - TEST_ASSERT_EQUAL( 2, bytesProcessed ); - - /* Wait for the buffer to be freed. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); - - /* Check that the entire buffer was processed. */ - TEST_ASSERT_EQUAL( _DATA_STREAM_SIZE, processOffset + ( size_t ) bytesProcessed ); + TEST_ASSERT_EQUAL_INT( false, _getRemainingLengthCalled ); + _getRemainingLengthCalled = true; } /*-----------------------------------------------------------*/ @@ -847,7 +738,6 @@ TEST( MQTT_Unit_Receive, ConnackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - ( int32_t ) connackSize, IOT_MQTT_STATUS_PENDING ) ); } @@ -858,7 +748,6 @@ TEST( MQTT_Unit_Receive, ConnackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - ( int32_t ) connackSize, IOT_MQTT_SUCCESS ) ); } @@ -870,7 +759,6 @@ TEST( MQTT_Unit_Receive, ConnackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - ( int32_t ) connackSize, IOT_MQTT_SUCCESS ) ); } @@ -887,11 +775,13 @@ TEST( MQTT_Unit_Receive, ConnackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - ( int32_t ) connackSize, IOT_MQTT_SERVER_REFUSED ) ); } IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /*-----------------------------------------------------------*/ @@ -917,8 +807,11 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize - 1, - 0, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The CONNACK control packet type must be 0x20. */ @@ -928,8 +821,11 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* A CONNACK must have a remaining length of 2. */ @@ -940,20 +836,25 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - -1, - IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The reserved bits in CONNACK must be 0. */ { _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 2 ] = 0x80; - _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The fourth byte of CONNACK must be 0 if the SP flag is set. */ @@ -965,8 +866,11 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* CONNACK return codes cannot be above 5. */ @@ -977,8 +881,11 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); @@ -992,19 +899,12 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) */ TEST( MQTT_Unit_Receive, PublishValid ) { - /* Set the PUBACK serializer function. This serializer function always returns - * failure to prevent any PUBACK packets from actually being sent. */ - _pMqttConnection->network.serialize.puback = _serializePuback; - /* Process a valid QoS 0 PUBLISH. */ { _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Process a valid QoS 1 PUBLISH. Prevent an attempt to send PUBACK by @@ -1014,10 +914,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) pPublish[ 0 ] = 0x32; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Process a valid QoS 2 PUBLISH. */ @@ -1026,10 +923,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) pPublish[ 0 ] = 0x34; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Process a valid PUBLISH with DUP flag set. */ @@ -1038,27 +932,25 @@ TEST( MQTT_Unit_Receive, PublishValid ) pPublish[ 0 ] = 0x38; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - ( int32_t ) publishSize, 1 ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); } /* Remove the subscription. Even though there is no matching subscription, * all bytes of the PUBLISH should still be processed (should not crash). */ { _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - IotListDouble_RemoveAll( &( _pMqttConnection->subscriptionList ), - IotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); + + _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, + &_subscription, + 1 ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - ( int32_t ) publishSize, 0 ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); } + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /*-----------------------------------------------------------*/ @@ -1075,10 +967,11 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, 4, - 0, 0 ) ); - _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The PUBLISH cannot have a QoS of 3. */ @@ -1087,10 +980,11 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) pPublish[ 0 ] = 0x36; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - -1, 0 ) ); - _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a PUBLISH with an invalid "Remaining length". */ @@ -1102,22 +996,24 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) pPublish[ 4 ] = 0xff; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - -1, 0 ) ); - _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } - /* Attempt to process a PUBLISH larger than the size of the data stream. */ + /* Attempt to process a PUBLISH where some bytes could not be received. */ { _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 2 ] = 0x52; + pPublish[ 2 ] = 0x03; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - 0, 0 ) ); - _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a PUBLISH with a "Remaining length" smaller than the @@ -1128,10 +1024,11 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) pPublish0[ 1 ] = 0x02; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish0, publish0Size, - -1, 0 ) ); - _freeWrapper( pPublish0 ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; /* QoS 1. */ _DECLARE_PACKET( _pPublishTemplate, pPublish1, publish1Size ); @@ -1139,10 +1036,11 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) pPublish1[ 1 ] = 0x04; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish1, publish1Size, - -1, 0 ) ); - _freeWrapper( pPublish1 ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a PUBLISH with a "Remaining length" smaller than the @@ -1152,10 +1050,11 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) pPublish[ 1 ] = 0x0a; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - -1, 0 ) ); - _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a PUBLISH with packet identifier 0. */ @@ -1165,95 +1064,12 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) pPublish[ 17 ] = 0x00; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, - -1, 0 ) ); - _freeWrapper( pPublish ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &_mallocSemaphore ) ); - } -} -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a stream - * of incoming PUBLISH messages. - */ -TEST( MQTT_Unit_Receive, PublishStream ) -{ - size_t i = 0; - - /* Allocate the data stream depending on the memory allocation mode. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - uint8_t pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ] = { 0 }; - TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); - #else - uint8_t * pPublishStream = _mallocWrapper( _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ); - TEST_ASSERT_NOT_EQUAL( NULL, pPublishStream ); - #endif - - /* Fill the data stream with PUBLISH messages. */ - for( i = 0; i < _PUBLISH_STREAM_COUNT; i++ ) - { - ( void ) memcpy( pPublishStream + i * sizeof( _pPublishTemplate ), - _pPublishTemplate, - sizeof( _pPublishTemplate ) ); + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } - - /* Process a stream that contains one complete and one partial PUBLISH message. */ - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, - sizeof( _pPublishTemplate ) + 1, - sizeof( _pPublishTemplate ), - 1 ) ); - TEST_ASSERT_EQUAL_INT( false, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); - - /* Process the complete stream of PUBLISH messages. */ - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, - _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ), - _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ), - _PUBLISH_STREAM_COUNT ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a stream - * of incoming PUBLISH messages that is invalid. - */ -TEST( MQTT_Unit_Receive, PublishInvalidStream ) -{ - size_t i = 0; - - /* Allocate the data stream depending on the memory allocation mode. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - uint8_t pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1 ] = { 0 }; - TEST_ASSERT_EQUAL_PTR( NULL, _mallocWrapper( 0 ) ); - #else - uint8_t * pPublishStream = _mallocWrapper( _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1 ); - TEST_ASSERT_NOT_EQUAL( NULL, pPublishStream ); - #endif - - /* Fill the data stream with PUBLISH messages. */ - for( i = 0; i < _PUBLISH_STREAM_COUNT; i++ ) - { - ( void ) memcpy( pPublishStream + i * sizeof( _pPublishTemplate ), - _pPublishTemplate, - sizeof( _pPublishTemplate ) ); - } - - /* Place an invalid byte at the end of the stream. */ - pPublishStream[ _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) ] = 0xff; - - /* Process the stream of PUBLISH messages. */ - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublishStream, - _PUBLISH_STREAM_COUNT * sizeof( _pPublishTemplate ) + 1, - -1, - 0 ) ); - TEST_ASSERT_EQUAL_INT( false, IotSemaphore_TimedWait( &_mallocSemaphore, - _PUBLISH_CALLBACK_TIMEOUT ) ); - _freeWrapper( pPublishStream ); } /*-----------------------------------------------------------*/ @@ -1279,7 +1095,6 @@ TEST( MQTT_Unit_Receive, PubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, - ( int32_t ) pubackSize, IOT_MQTT_STATUS_PENDING ) ); } @@ -1290,11 +1105,13 @@ TEST( MQTT_Unit_Receive, PubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, - ( int32_t ) pubackSize, IOT_MQTT_SUCCESS ) ); } IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /*-----------------------------------------------------------*/ @@ -1313,15 +1130,19 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) 0, 10 ) ); + _operationResetAndPush( &publish ); + /* An incomplete PUBACK should not be processed, and no status should be set. */ { _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - _operationResetAndPush( &publish ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize - 1, - 0, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The PUBACK control packet type must be 0x40. */ @@ -1331,20 +1152,27 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } + _operationResetAndPush( &publish ); + /* A PUBACK must have a remaining length of 2. */ { _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); pPuback[ 1 ] = 0x03; - _operationResetAndPush( &publish ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, - -1, - IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The packet identifier in PUBACK cannot be 0. No status should be set if @@ -1352,12 +1180,14 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) { _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); pPuback[ 3 ] = 0x00; - _operationResetAndPush( &publish ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, - -1, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Remove unprocessed PUBLISH if present. */ @@ -1421,7 +1251,6 @@ TEST( MQTT_Unit_Receive, SubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - ( int32_t ) subackSize, IOT_MQTT_STATUS_PENDING ) ); } @@ -1433,7 +1262,6 @@ TEST( MQTT_Unit_Receive, SubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - ( int32_t ) subackSize, IOT_MQTT_SUCCESS ) ); /* Test the subscription check function. QoS is not tested. */ @@ -1456,7 +1284,6 @@ TEST( MQTT_Unit_Receive, SubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - ( int32_t ) subackSize, IOT_MQTT_SERVER_REFUSED ) ); /* Check that rejected subscriptions were removed from the subscription @@ -1472,6 +1299,9 @@ TEST( MQTT_Unit_Receive, SubackValid ) } IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /*-----------------------------------------------------------*/ @@ -1490,16 +1320,20 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) 0, 10 ) ); + _operationResetAndPush( &subscribe ); + /* Attempting to process a packet smaller than 5 bytes should result in no * bytes processed. 5 bytes is the minimum size of a SUBACK. */ { _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - _operationResetAndPush( &subscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, 4, - 0, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a SUBACK with an invalid "Remaining length". */ @@ -1512,8 +1346,11 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - -1, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a SUBACK larger than the size of the data stream. */ @@ -1523,8 +1360,11 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - 0, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a SUBACK with a "Remaining length" smaller than the @@ -1535,8 +1375,11 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - -1, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Attempt to process a SUBACK with a bad return code. */ @@ -1546,20 +1389,25 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The SUBACK control packet type must be 0x90. */ { _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 0 ] = 0x91; - _operationResetAndPush( &subscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); @@ -1588,7 +1436,6 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, - ( int32_t ) unsubackSize, IOT_MQTT_STATUS_PENDING ) ); } @@ -1599,11 +1446,13 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, - ( int32_t ) unsubackSize, IOT_MQTT_SUCCESS ) ); } IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /*-----------------------------------------------------------*/ @@ -1622,15 +1471,19 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) 0, 10 ) ); + _operationResetAndPush( &unsubscribe ); + /* An incomplete UNSUBACK should not be processed, and no status should be set. */ { _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - _operationResetAndPush( &unsubscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize - 1, - 0, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The UNSUBACK control packet type must be 0xb0. */ @@ -1640,20 +1493,27 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, - -1, IOT_MQTT_BAD_RESPONSE ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } + _operationResetAndPush( &unsubscribe ); + /* An UNSUBACK must have a remaining length of 2. */ { _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); pUnsuback[ 1 ] = 0x03; - _operationResetAndPush( &unsubscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, - -1, - IOT_MQTT_BAD_RESPONSE ) ); + IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The packet identifier in UNSUBACK cannot be 0. No status should be set if @@ -1661,12 +1521,14 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) { _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); pUnsuback[ 3 ] = 0x00; - _operationResetAndPush( &unsubscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, - -1, IOT_MQTT_STATUS_PENDING ) ); + + /* Network close should have been called for invalid packet. */ + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* Remove unprocessed UNSUBSCRIBE if present. */ @@ -1695,10 +1557,10 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, - ( int32_t ) pingrespSize, IOT_MQTT_SUCCESS ) ); TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /* Process a valid PINGRESP. */ @@ -1709,10 +1571,10 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, - ( int32_t ) pingrespSize, IOT_MQTT_SUCCESS ) ); TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); } /* An incomplete PINGRESP should not be processed, and the keep-alive failure @@ -1724,10 +1586,11 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize - 1, - 0, IOT_MQTT_SUCCESS ) ); TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* A PINGRESP should have a remaining length of 0. */ @@ -1739,10 +1602,11 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, - -1, IOT_MQTT_SUCCESS ) ); TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } /* The PINGRESP control packet type must be 0xd0. */ @@ -1754,10 +1618,11 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, - -1, IOT_MQTT_SUCCESS ) ); TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + _networkCloseCalled = false; } } diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index a89600afbb..662bd4a71a 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -32,8 +32,12 @@ /* Standard includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* Platform layer includes. */ #include "platform/iot_threads.h" +#include "platform/iot_clock.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -44,16 +48,6 @@ #else #include #endif -#ifdef POSIX_TIME_HEADER - #include POSIX_TIME_HEADER -#else - #include -#endif -#ifdef POSIX_UNISTD_HEADER - #include POSIX_UNISTD_HEADER -#else - #include -#endif /* Test framework includes. */ #include "unity_fixture.h" @@ -158,10 +152,15 @@ extern int snprintf( char *, /*-----------------------------------------------------------*/ +/** + * @brief Tracks whether the global MQTT connection has been created. + */ +static bool _connectionCreated = false; + /** * @brief The MQTT connection shared by all tests. */ -static _mqttConnection_t * _pMqttConnection = NULL; +static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /** * @brief Synchronizes threads in the multithreaded test. @@ -207,14 +206,12 @@ static bool _waitForCount( IotMutex_t * pMutex, int32_t target ) { bool status = false; - int32_t referenceCount = 0, sleepCount = 0; + int32_t referenceCount = 0; + uint32_t sleepCount = 0; - /* 200 ms sleep time. */ - const struct timespec sleepTime = { .tv_sec = 0, .tv_nsec = 200000000 }; - - /* Calculate limit on the number of times to sleep for 200 ms. */ - const int32_t sleepLimit = ( IOT_TEST_MQTT_TIMEOUT_MS / 200 ) + - ( ( IOT_TEST_MQTT_TIMEOUT_MS % 200 ) != 0 ); + /* Calculate limit on the number of times to sleep for 100 ms. */ + const uint32_t sleepLimit = ( IOT_TEST_MQTT_TIMEOUT_MS / 100 ) + + ( ( IOT_TEST_MQTT_TIMEOUT_MS % 100 ) != 0 ); /* Wait for the reference count to reach the target value. */ for( sleepCount = 0; sleepCount < sleepLimit; sleepCount++ ) @@ -232,10 +229,7 @@ static bool _waitForCount( IotMutex_t * pMutex, } else { - if( nanosleep( &sleepTime, NULL ) != 0 ) - { - break; - } + IotClock_SleepMs( 100 ); } } @@ -370,16 +364,24 @@ TEST_GROUP( MQTT_Unit_Subscription ); */ TEST_SETUP( MQTT_Unit_Subscription ) { - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + static IotNetworkInterface_t networkInterface = { 0 }; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + + /* Initialize common components. */ + TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + + networkInfo.pNetworkInterface = &networkInterface; /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - /* Create an MQTT connection with an empty network interface. */ + /* Create an MQTT connection with empty network info. */ _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, - &networkInterface, + &networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + _connectionCreated = true; } /*-----------------------------------------------------------*/ @@ -390,13 +392,14 @@ TEST_SETUP( MQTT_Unit_Subscription ) TEST_TEAR_DOWN( MQTT_Unit_Subscription ) { /* Destroy the MQTT connection used for the tests. */ - if( _pMqttConnection != NULL ) + if( _connectionCreated == true ) { - IotMqtt_Disconnect( _pMqttConnection, true ); - _pMqttConnection = NULL; + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + _connectionCreated = false; } - /* Clean up the MQTT library. */ + /* Clean up libraries. */ + IotCommon_Cleanup(); IotMqtt_Cleanup(); } @@ -858,6 +861,9 @@ TEST( MQTT_Unit_Subscription, ProcessPublish ) &subscription, 1 ) ); + /* Increment connection reference count for processing subscription callbacks. */ + TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( _pMqttConnection ) ); + /* Find the subscription and invoke its callback. */ _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, &callbackParam ); @@ -907,6 +913,9 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) &( subscription[ 0 ] ), 3 ) ); + /* Increment connection reference count for processing subscription callbacks. */ + TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( _pMqttConnection ) ); + /* Invoke subscription callbacks. */ _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, &callbackParam ); @@ -939,7 +948,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) #endif /* The MQTT task pool must support at least 3 threads for this test to run successfully. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_SetMaxThreads( &( _IotMqttTaskPool ), 4 ) ); + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_SetMaxThreads( IOT_SYSTEM_TASKPOOL, 4 ) ); TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 3 ) ); @@ -980,6 +989,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) /* Schedule 3 callback invocations for the incoming PUBLISH. */ for( i = 0; i < 3; i++ ) { + TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( _pMqttConnection ) ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pIncomingPublish[ i ], _IotMqtt_ProcessIncomingPublish, 0 ) ); @@ -1009,15 +1019,21 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) 2 ) ); /* Shut down the MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); /* Post twice to the wait semaphore, which unblocks the remaining blocking * callbacks. */ IotSemaphore_Post( &waitSem ); IotSemaphore_Post( &waitSem ); - /* Clear the MQTT connection pointer so test cleanup does not double-free it. */ - _pMqttConnection = NULL; + /* Wait for the callbacks to exit. */ + while( IotSemaphore_GetCount( &waitSem ) > 0 ) + { + IotClock_SleepMs( 100 ); + } + + /* Clear the MQTT connection flag so test cleanup does not double-free it. */ + _connectionCreated = false; } IotSemaphore_Destroy( &waitSem ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 9659dd2cb8..0134149975 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -32,6 +32,9 @@ /* Standard includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -78,6 +81,7 @@ TEST_GROUP( MQTT_Unit_Validate ); */ TEST_SETUP( MQTT_Unit_Validate ) { + TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); } /*-----------------------------------------------------------*/ @@ -87,6 +91,7 @@ TEST_SETUP( MQTT_Unit_Validate ) */ TEST_TEAR_DOWN( MQTT_Unit_Validate ) { + IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ @@ -96,7 +101,6 @@ TEST_TEAR_DOWN( MQTT_Unit_Validate ) */ TEST_GROUP_RUNNER( MQTT_Unit_Validate ) { - RUN_TEST_CASE( MQTT_Unit_Validate, ValidateNetIf ); RUN_TEST_CASE( MQTT_Unit_Validate, ValidateConnectInfo ); RUN_TEST_CASE( MQTT_Unit_Validate, ValidatePublish ); RUN_TEST_CASE( MQTT_Unit_Validate, ValidateReference ); @@ -105,49 +109,6 @@ TEST_GROUP_RUNNER( MQTT_Unit_Validate ) /*-----------------------------------------------------------*/ -/** - * @brief Test validation of an #IotMqttNetIf_t. - */ -TEST( MQTT_Unit_Validate, ValidateNetIf ) -{ - bool validateStatus = false; - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; - - /* NULL parameter. */ - validateStatus = _IotMqtt_ValidateNetIf( NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Uninitialized parameter. */ - validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Uninitialized disconnect function is allowed. */ - networkInterface.send = _FUNCTION_POINTER; - validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Check serializer override function pointers. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - /* No freePacket function with serializer. */ - networkInterface.serialize.disconnect = _FUNCTION_POINTER; - validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - networkInterface.serialize.disconnect = NULL; - - /* No freePacket function with deserializer. */ - networkInterface.deserialize.pingresp = _FUNCTION_POINTER; - validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* freePacket function pointer set. */ - networkInterface.freePacket = _FUNCTION_POINTER; - validateStatus = _IotMqtt_ValidateNetIf( &networkInterface ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -} - -/*-----------------------------------------------------------*/ - /** * @brief Test validation of an #IotMqttConnectInfo_t. */ @@ -156,6 +117,8 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) bool validateStatus = false; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + /* NULL parameter. */ validateStatus = _IotMqtt_ValidateConnect( NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index 6807279ae9..ff8e0a7828 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -33,6 +33,9 @@ /* POSIX includes. */ #include +/* Error handling include. */ +#include "private/iot_error.h" + /* Common include. */ #include "iot_common.h" @@ -64,6 +67,7 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { + _IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Set a signal handler for segmentation faults and assertion failures. */ @@ -72,18 +76,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; - } - - /* Initialize the common libraries before running the tests. */ - if( IotCommon_Init() == false ) - { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -110,16 +108,13 @@ int main( int argc, { } - /* Clean up common libraries. */ - IotCommon_Cleanup(); - /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - return EXIT_FAILURE; + _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - return EXIT_SUCCESS; + _IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index a5e6c7becd..df6491bfab 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -32,6 +32,9 @@ /* Standard includes. */ #include +/* Common include. */ +#include "iot_common.h" + /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" @@ -41,6 +44,9 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" +/* MQTT include. */ +#include "iot_mqtt.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -121,7 +127,7 @@ extern void IotTest_NetworkCleanup( void ); /* Network variables used by the tests, declared in one of the test network * function files. */ -extern IotMqttNetIf_t _IotTestNetworkInterface; +extern IotMqttNetworkInfo_t _IotTestNetworkInfo; extern IotMqttConnection_t _IotTestMqttConnection; /** @@ -426,6 +432,12 @@ TEST_SETUP( Shadow_System ) IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + /* Initialize common components. */ + if( IotCommon_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize common components." ); + } + /* Set up the network stack. */ if( IotTest_NetworkSetup() == false ) { @@ -451,10 +463,10 @@ TEST_SETUP( Shadow_System ) connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; /* Establish an MQTT connection. */ - if( IotMqtt_Connect( &_IotTestMqttConnection, - &_IotTestNetworkInterface, + if( IotMqtt_Connect( &_IotTestNetworkInfo, &connectInfo, - AWS_IOT_TEST_SHADOW_TIMEOUT ) != IOT_MQTT_SUCCESS ) + AWS_IOT_TEST_SHADOW_TIMEOUT, + &_IotTestMqttConnection ) != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); } @@ -488,7 +500,7 @@ TEST_TEAR_DOWN( Shadow_System ) /* Disconnect the MQTT connection if it was created. */ if( _connectionCreated == true ) { - IotMqtt_Disconnect( _IotTestMqttConnection, false ); + IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); _connectionCreated = false; } @@ -499,9 +511,11 @@ TEST_TEAR_DOWN( Shadow_System ) /* Clean up the network stack. */ IotTest_NetworkCleanup(); + /* Clean up common components. */ + IotCommon_Cleanup(); + /* Clean up the MQTT library. */ IotMqtt_Cleanup(); - _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 21aaf93554..f2d53b1210 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -40,6 +40,9 @@ #include #endif +/* Common include. */ +#include "iot_common.h" + /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" @@ -87,7 +90,7 @@ * @brief A delay that simulates the time required for an MQTT packet to be sent * to the server and for the server to send a response. */ -#define _NETWORK_ROUND_TRIP_TIME_MS ( 250 ) +#define _NETWORK_ROUND_TRIP_TIME_MS ( 25 ) /** * @brief The maximum size of any MQTT acknowledgement packet (e.g. SUBACK, @@ -97,10 +100,27 @@ /*-----------------------------------------------------------*/ +/** + * @brief Context for calls to the network receive function. + */ +typedef struct _receiveContext +{ + const uint8_t * pData; /**< @brief The data to receive. */ + size_t dataLength; /**< @brief Length of data. */ + size_t dataIndex; /**< @brief Next byte of data to read. */ +} _receiveContext_t; + +/*-----------------------------------------------------------*/ + /** * @brief The MQTT connection object shared among all the tests. */ -static _mqttConnection_t * _pMqttConnection = NULL; +static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/** + * @brief The #IotNetworkInterface_t to share among the tests. + */ +static IotNetworkInterface_t _networkInterface = { 0 }; /** * @brief Timer used to simulate a response from the network. @@ -133,8 +153,10 @@ static uint16_t _lastPacketIdentifier = 0; static void _receiveThread( void * pArgument ) { uint8_t pReceivedData[ _ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; - size_t receivedDataLength = 0; - int32_t bytesProcessed = 0; + _receiveContext_t receiveContext = { 0 }; + + receiveContext.pData = pReceivedData; + receiveContext.dataLength = _ACKNOWLEDGEMENT_PACKET_SIZE; /* Silence warnings about unused parameters. */ ( void ) pArgument; @@ -157,7 +179,7 @@ static void _receiveThread( void * pArgument ) pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; pReceivedData[ 1 ] = 2; - receivedDataLength = 4; + receiveContext.dataLength = 4; break; case _MQTT_PACKET_TYPE_SUBSCRIBE: @@ -165,14 +187,14 @@ static void _receiveThread( void * pArgument ) pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_SUBACK; pReceivedData[ 1 ] = 3; pReceivedData[ 4 ] = 1; - receivedDataLength = 5; + receiveContext.dataLength = 5; break; case _MQTT_PACKET_TYPE_UNSUBSCRIBE: pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_UNSUBACK; pReceivedData[ 1 ] = 2; - receivedDataLength = 4; + receiveContext.dataLength = 4; break; default: @@ -183,13 +205,8 @@ static void _receiveThread( void * pArgument ) } /* Call the MQTT receive callback to process the ACK packet. */ - bytesProcessed = IotMqtt_ReceiveCallback( ( IotMqttConnection_t * ) &_pMqttConnection, - NULL, - pReceivedData, - receivedDataLength, - 0, - NULL ); - AwsIotShadow_Assert( bytesProcessed == ( int32_t ) receivedDataLength ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); IotMutex_Unlock( &_lastPacketMutex ); } @@ -201,24 +218,36 @@ static void _receiveThread( void * pArgument ) * timer to respond with an ACK when necessary. */ static size_t _sendSuccess( void * pSendContext, - const uint8_t * const pMessage, + const uint8_t * pMessage, size_t messageLength ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - const uint8_t * pPacketIdentifier = NULL; - IotMqttPublishInfo_t decodedPublish = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - size_t publishBytesProcessed = 0; + _mqttOperation_t deserializedPublish = { 0 }; + _mqttPacket_t mqttPacket = { 0 }; + _receiveContext_t receiveContext = { 0 }; /* Ignore the send context. */ ( void ) pSendContext; + /* Read the packet type, which is the first byte in the message. */ + mqttPacket.type = *pMessage; + + /* Set the members of the receive context. */ + receiveContext.pData = pMessage + 1; + receiveContext.dataLength = messageLength; + /* Lock the mutex to modify the information on the last packet sent. */ IotMutex_Lock( &_lastPacketMutex ); + /* Read the remaining length. */ + mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ); + AwsIotShadow_Assert( mqttPacket.remainingLength != _MQTT_REMAINING_LENGTH_INVALID ); + /* Set the last packet type based on the outgoing message. */ - switch( _IotMqtt_GetPacketType( pMessage, messageLength ) ) + switch( mqttPacket.type & 0xf0 ) { - case ( _MQTT_PACKET_TYPE_PUBLISH & 0xf0 ): + case _MQTT_PACKET_TYPE_PUBLISH: /* Only set the last packet type to PUBLISH for QoS 1. */ if( ( ( *pMessage & 0x06 ) >> 1 ) == 1 ) @@ -251,33 +280,28 @@ static size_t _sendSuccess( void * pSendContext, /* Check if a network response is needed. */ if( _lastPacketType != 0 ) { - /* Decode the remaining length. */ + /* Save the packet identifier as the last packet identifier. */ if( _lastPacketType != _MQTT_PACKET_TYPE_PUBLISH ) { - status = IotTestMqtt_decodeRemainingLength( pMessage + 1, - &pPacketIdentifier, - NULL ); - - /* Save the packet identifier as the last packet identifier. */ - _lastPacketIdentifier = _UINT16_DECODE( pPacketIdentifier ); + _lastPacketIdentifier = _UINT16_DECODE( receiveContext.pData + receiveContext.dataIndex ); + status = IOT_MQTT_SUCCESS; } else { - status = _IotMqtt_DeserializePublish( pMessage, - messageLength, - &decodedPublish, - &_lastPacketIdentifier, - &publishBytesProcessed ); + mqttPacket.pIncomingPublish = &deserializedPublish; + mqttPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - mqttPacket.remainingLength ); - AwsIotShadow_Assert( publishBytesProcessed == messageLength ); + status = _IotMqtt_DeserializePublish( &mqttPacket ); + _lastPacketIdentifier = mqttPacket.packetIdentifier; } AwsIotShadow_Assert( status == IOT_MQTT_SUCCESS ); + AwsIotShadow_Assert( _lastPacketIdentifier != 0 ); /* Set the receive thread to run after a "network round-trip". */ - IotClock_TimerArm( &_receiveTimer, - _NETWORK_ROUND_TRIP_TIME_MS, - 0 ); + AwsIotShadow_Assert( IotClock_TimerArm( &_receiveTimer, + _NETWORK_ROUND_TRIP_TIME_MS, + 0 ) == true ); } IotMutex_Unlock( &_lastPacketMutex ); @@ -288,6 +312,46 @@ static size_t _sendSuccess( void * pSendContext, /*-----------------------------------------------------------*/ +/** + * @brief Simulates a network receive function. + */ +static size_t _receive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ) +{ + size_t bytesReceived = 0; + _receiveContext_t * pReceiveContext = pConnection; + + AwsIotShadow_Assert( bytesRequested != 0 ); + AwsIotShadow_Assert( pReceiveContext->dataIndex < pReceiveContext->dataLength ); + + /* Calculate how much data to copy. */ + const size_t dataAvailable = pReceiveContext->dataLength - pReceiveContext->dataIndex; + + if( bytesRequested > dataAvailable ) + { + bytesReceived = dataAvailable; + } + else + { + bytesReceived = bytesRequested; + } + + /* Copy data into given buffer. */ + if( bytesReceived > 0 ) + { + ( void ) memcpy( pBuffer, + pReceiveContext->pData + pReceiveContext->dataIndex, + bytesReceived ); + + pReceiveContext->dataIndex += bytesReceived; + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Shadow API tests. */ @@ -300,7 +364,10 @@ TEST_GROUP( Shadow_Unit_API ); */ TEST_SETUP( Shadow_Unit_API ) { - IotMqttNetIf_t networkInterface = IOT_MQTT_NETIF_INITIALIZER; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + + /* Initialize common components. */ + TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); /* Clear the last packet type and identifier. */ _lastPacketType = 0; @@ -318,12 +385,16 @@ TEST_SETUP( Shadow_Unit_API ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); /* Set the network interface send function. */ - networkInterface.send = _sendSuccess; + ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); + _networkInterface.send = _sendSuccess; + _networkInterface.receive = _receive; + networkInfo.pNetworkInterface = &_networkInterface; /* Initialize the MQTT connection object to use for the Shadow tests. */ _pMqttConnection = IotTestMqtt_createMqttConnection( false, - &networkInterface, + &networkInfo, 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Initialize the Shadow library. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); @@ -340,7 +411,10 @@ TEST_TEAR_DOWN( Shadow_Unit_API ) AwsIotShadow_Cleanup(); /* Clean up the MQTT connection object. */ - IotMqtt_Disconnect( _pMqttConnection, true ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + + /* Clean up common components. */ + IotCommon_Cleanup(); /* Clean up the MQTT library. */ IotMqtt_Cleanup(); @@ -507,14 +581,6 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) documentInfo.qos = IOT_MQTT_QOS_0; /* Invalid retry parameters. */ - documentInfo.retryLimit = -1; - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &reference ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - documentInfo.retryLimit = 1; status = AwsIotShadow_Get( _pMqttConnection, &documentInfo, @@ -597,10 +663,13 @@ TEST( Shadow_Unit_API, WaitInvalidParameters ) */ TEST( Shadow_Unit_API, DeleteMallocFail ) { - int32_t i = 0; + int32_t i = 0, mqttErrorCount = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + /* Set a short timeout so this test runs faster. */ + _AwsIotShadowMqttTimeoutMs = 75; + for( i = 0; ; i++ ) { UnityMalloc_MakeMallocFailAfterCount( i ); @@ -624,13 +693,26 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) break; } - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + /* Count the number of MQTT library errors. Otherwise, check that the error + * is a "No memory" error. */ + if( status == AWS_IOT_SHADOW_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } } - /* Wait for any pending QoS 0 publishes to clean up. */ - sleep( 1 ); + /* Allow 2 MQTT library errors, which are caused by failure to allocate memory + * for incoming packets (SUBSCRIBE, UNSUBSCRIBE). These allocation errors do + * not happen in static memory mode. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); + #else + TEST_ASSERT_EQUAL_INT32( 2, mqttErrorCount ); + #endif } /*-----------------------------------------------------------*/ @@ -641,13 +723,16 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) */ TEST( Shadow_Unit_API, GetMallocFail ) { - int32_t i = 0; + int32_t i = 0, mqttErrorCount = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; const char * pRetrievedDocument = NULL; size_t retrievedDocumentSize = 0; + /* Set a short timeout so this test runs faster. */ + _AwsIotShadowMqttTimeoutMs = 75; + /* Set the members of the document info. */ documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; @@ -679,21 +764,40 @@ TEST( Shadow_Unit_API, GetMallocFail ) break; } - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + /* Count the number of MQTT library errors. Otherwise, check that the error + * is a "No memory" error. */ + if( status == AWS_IOT_SHADOW_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } } + + /* Allow 3 MQTT library errors, which are caused by failure to allocate memory + * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). These allocation + * errors do not happen in static memory mode. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); + #else + TEST_ASSERT_EQUAL_INT32( 3, mqttErrorCount ); + #endif } /*-----------------------------------------------------------*/ TEST( Shadow_Unit_API, UpdateMallocFail ) { - int32_t i = 0; + int32_t i = 0, mqttErrorCount = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + /* Set a short timeout so this test runs faster. */ + _AwsIotShadowMqttTimeoutMs = 75; + /* Set the members of the document info. */ documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; @@ -726,10 +830,26 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) break; } - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + /* Count the number of MQTT library errors. Otherwise, check that the error + * is a "No memory" error. */ + if( status == AWS_IOT_SHADOW_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } } + + /* Allow 3 MQTT library errors, which are caused by failure to allocate memory + * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). These allocation + * errors do not happen in static memory mode. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); + #else + TEST_ASSERT_EQUAL_INT32( 3, mqttErrorCount ); + #endif } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 4a07ae2989..4f49455476 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -33,6 +33,9 @@ #include #include +/* Common include. */ +#include "iot_common.h" + /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index c9382d7265..49c6e7ef26 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -3,9 +3,12 @@ add_library( tinycbor SHARED cborencoder.c cborencoder_close_container_checked.c cborerrorstrings.c - cborparser.c + cborparser.c cborparser_dup_string.c cborpretty.c ) # Library version. -set_target_properties( tinycbor PROPERTIES VERSION ${PROJECT_VERSION} ) \ No newline at end of file +set_target_properties( tinycbor PROPERTIES VERSION ${PROJECT_VERSION} ) + +# Link math library. +target_link_libraries( tinycbor m ) From 85000b549ec8c8b1089b66b8db57cfb8f1135050 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 18 Mar 2019 11:35:58 -0700 Subject: [PATCH 049/844] Control MQTT test access from CMake. (#323) --- CMakeLists.txt | 3 +++ doc/lib/mqtt.txt | 9 --------- lib/include/private/iot_mqtt_internal.h | 3 --- lib/source/mqtt/iot_mqtt_api.c | 8 ++------ lib/source/mqtt/iot_mqtt_subscription.c | 5 ++--- tests/iot_tests_config.h | 1 - 6 files changed, 7 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8250d15577..e5dd7e0a08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,9 @@ if( ${IOT_BUILD_TESTS} ) tests/unity/fixture tests/mqtt/access ) + # Define the constant to enable test access. + add_definitions( -DIOT_BUILD_TESTS=1 ) + # Tests config file. add_definitions( -DIOT_CONFIG_FILE="iot_tests_config.h" ) diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index ef6ba8df61..95a0cb7fb5 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -277,15 +277,6 @@ QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. T @configpossible Any positive integer.
@configdefault `60000` -@section IOT_MQTT_TEST -@brief Set this to `1` to enable test access for the MQTT library. - -This setting is used by the [MQTT tests](@ref mqtt_tests) to access the internal `static` functions and variables of the MQTT library. When `1`, it enables the @c \#include directives at the bottom of the [MQTT library source files](@ref lib/source/mqtt). These @c \#include directives include the test access files (located in `tests/mqtt/access`) that interact with the library's internal symbols. Unless the MQTT library is being tested, this setting should be `0`. - -@configpossible `0` (test access disabled) or `1` (test access enabled)
-@configrecommended `0`
-@configdefault `0` - @section IotMqtt_Assert @brief Assertion function used when @ref IOT_MQTT_ENABLE_ASSERTS is `1`. diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index d3b099ce42..b1af53105b 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -200,9 +200,6 @@ #ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) #endif -#ifndef IOT_MQTT_TEST - #define IOT_MQTT_TEST ( 0 ) -#endif #ifndef IOT_MQTT_RESPONSE_WAIT_MS #define IOT_MQTT_RESPONSE_WAIT_MS ( 1000 ) #endif diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 59d67b48ae..22eabd82d3 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -52,9 +52,6 @@ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 #error "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." #endif -#if IOT_MQTT_TEST != 0 && IOT_MQTT_TEST != 1 - #error "IOT_MQTT_MQTT_TEST must be 0 or 1." -#endif #if IOT_MQTT_RESPONSE_WAIT_MS <= 0 #error "IOT_MQTT_RESPONSE_WAIT_MS cannot be 0 or negative." #endif @@ -1960,8 +1957,7 @@ const char * IotMqtt_OperationType( IotMqttOperationType_t operation ) /*-----------------------------------------------------------*/ -/* If the MQTT library is being tested, include a file that allows access to - * internal functions and variables. */ -#if IOT_MQTT_TEST == 1 +/* Provide access to internal functions and variables if testing. */ +#if IOT_BUILD_TESTS == 1 #include "iot_test_access_mqtt_api.c" #endif diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 1931591e0e..7463c2876f 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -640,8 +640,7 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -/* If the MQTT library is being tested, include a file that allows access to - * internal functions and variables. */ -#if IOT_MQTT_TEST == 1 +/* Provide access to internal functions and variables if testing. */ +#if IOT_BUILD_TESTS == 1 #include "iot_test_access_mqtt_subscription.c" #endif diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 3c479c202a..1379ad9d29 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -68,7 +68,6 @@ #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_METRICS ( 0 ) #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) -#define IOT_MQTT_TEST ( 1 ) /* Shadow library configuration. */ #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) From b4b3e572e168e819327348ff776ff9397af79b84 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 18 Mar 2019 13:41:39 -0700 Subject: [PATCH 050/844] Add MQTT disconnect callback. (#324) --- demos/iot_demo_mqtt.c | 4 +- lib/include/private/iot_mqtt_internal.h | 6 +- lib/include/types/iot_mqtt_types.h | 47 +++++++-- lib/source/defender/aws_iot_defender_api.c | 4 +- lib/source/mqtt/iot_mqtt_api.c | 8 +- lib/source/mqtt/iot_mqtt_network.c | 25 ++++- lib/source/mqtt/iot_mqtt_operation.c | 5 +- lib/source/mqtt/iot_mqtt_subscription.c | 6 +- lib/source/shadow/aws_iot_shadow_api.c | 2 +- .../shadow/aws_iot_shadow_subscription.c | 2 +- tests/mqtt/system/iot_tests_mqtt_stress.c | 2 +- tests/mqtt/system/iot_tests_mqtt_system.c | 18 ++-- tests/mqtt/unit/iot_tests_mqtt_api.c | 37 +++++++ tests/mqtt/unit/iot_tests_mqtt_receive.c | 99 ++++++++++++++++++- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 14 +-- 15 files changed, 231 insertions(+), 48 deletions(-) diff --git a/demos/iot_demo_mqtt.c b/demos/iot_demo_mqtt.c index 7331498d85..7eb78d8cdc 100644 --- a/demos/iot_demo_mqtt.c +++ b/demos/iot_demo_mqtt.c @@ -529,7 +529,7 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; pSubscriptions[ i ].topicFilterLength = _TOPIC_FILTER_LENGTH; - pSubscriptions[ i ].callback.param1 = pCallbackParameter; + pSubscriptions[ i ].callback.pCallbackContext = pCallbackParameter; pSubscriptions[ i ].callback.function = _mqttSubscriptionCallback; } @@ -656,7 +656,7 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, } /* Pass the PUBLISH number to the operation complete callback. */ - publishComplete.param1 = ( void * ) publishCount; + publishComplete.pCallbackContext = ( void * ) publishCount; /* Choose a topic name (round-robin through the array of topic names). */ publishInfo.pTopicName = pTopicNames[ publishCount % _TOPIC_FILTER_COUNT ]; diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index b1af53105b..664c550368 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -270,6 +270,7 @@ typedef struct _mqttConnection bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ void * pNetworkConnection; /**< @brief References the transport-layer network connection. */ const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ @@ -930,9 +931,12 @@ bool _IotMqtt_GetNextByte( void * pNetworkConnection, * A network disconnect function must be set in the network interface for the * network connection to be closed. * + * @param[in] disconnectReason A reason to pass to the connection's disconnect + * callback. * @param[in] pMqttConnection The MQTT connection with the network connection * to close. */ -void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ); +void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, + _mqttConnection_t * pMqttConnection ); #endif /* ifndef _IOT_MQTT_INTERNAL_H_ */ diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 39adf157ce..fb76c4cdcb 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -294,6 +294,23 @@ typedef enum IotMqttQos IOT_MQTT_QOS_2 = 2 /**< Delivery exactly once. Unsupported, but enumerated for completeness. */ } IotMqttQos_t; +/** + * @ingroup mqtt_datatypes_enums + * @brief The reason that an MQTT connection (and its associated network connection) + * was disconnected. + * + * When an MQTT connection is closed, its associated [disconnect callback] + * (@ref IotMqttNetworkInfo_t::disconnectCallback) will be invoked. This type + * is passed inside of an #IotMqttCallbackParam_t to provide a reason for the + * disconnect. + */ +typedef enum IotMqttDisconnectReason +{ + IOT_MQTT_DISCONNECT_CALLED, /**< @ref mqtt_function_disconnect was invoked. */ + IOT_MQTT_BAD_PACKET_RECEIVED, /**< An invalid packet was received from the network. */ + IOT_MQTT_KEEP_ALIVE_TIMEOUT /**< Keep-alive response was not received within @ref IOT_MQTT_RESPONSE_WAIT_MS. */ +} IotMqttDisconnectReason_t; + /*------------------------- MQTT parameter structs --------------------------*/ /** @@ -386,12 +403,14 @@ typedef struct IotMqttPublishInfo * @paramfor MQTT callback functions * * The MQTT library passes this struct to registered callback whenever an - * operation completes or a message is received on a topic filter. + * operation completes, a message is received on a topic filter, or an MQTT + * connection is disconnected. * * The members of this struct are different based on the callback trigger. If the * callback function was triggered for completed operation, the `operation` * member is valid. Otherwise, if the callback was triggered because of a - * server-to-client PUBLISH, the `message` member is valid. + * server-to-client PUBLISH, the `message` member is valid. Finally, if the callback + * was triggered because of a disconnect, the `disconnectReason` member is valid. * * For an incoming PUBLISH, the `message.pTopicFilter` parameter provides the * subscription topic filter that matched the topic name in the PUBLISH. Because @@ -411,12 +430,14 @@ typedef struct IotMqttPublishInfo typedef struct IotMqttCallbackParam { /** - * @brief The MQTT connection associated with this completed operation or - * incoming PUBLISH. + * @brief The MQTT connection associated with this completed operation, + * incoming PUBLISH, or disconnect. * - * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback. - * However, blocking function calls (including @ref mqtt_function_wait) are - * not recommended (though still safe). + * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback + * for completed operations or incoming PUBLISH messages. However, blocking + * function calls (including @ref mqtt_function_wait) are not recommended + * (though still safe). Do not call any API functions from a disconnect + * callback. */ IotMqttConnection_t mqttConnection; @@ -437,6 +458,9 @@ typedef struct IotMqttCallbackParam uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ IotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ } message; + + /* Valid when a connection is disconnected. */ + IotMqttDisconnectReason_t disconnectReason; /**< @brief Why the MQTT connection was disconnected. */ }; } IotMqttCallbackParam_t; @@ -478,12 +502,12 @@ typedef struct IotMqttCallbackParam */ typedef struct IotMqttCallbackInfo { - void * param1; /**< @brief The first parameter to pass to the callback function. */ + void * pCallbackContext; /**< @brief The first parameter to pass to the callback function to provide context. */ /** * @brief User-provided callback function signature. * - * @param[in] void * #IotMqttCallbackInfo_t.param1 + * @param[in] void * #IotMqttCallbackInfo_t.pCallbackContext * @param[in] IotMqttCallbackParam_t * Details on the outcome of the MQTT operation * or an incoming MQTT PUBLISH. * @@ -951,6 +975,11 @@ typedef struct IotMqttNetworkInfo */ const IotNetworkInterface_t * pNetworkInterface; + /** + * @brief A callback function to invoke when this MQTT connection is disconnected. + */ + IotMqttCallbackInfo_t disconnectCallback; + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 /** diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index d7c4fec991..3b9d5a0d4f 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -353,8 +353,8 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, bool mqttConnected = true, reportCreated = true, reportPublished = false; - const IotMqttCallbackInfo_t acceptCallbackInfo = { .function = _acceptCallback, .param1 = NULL }; - const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .param1 = NULL }; + const IotMqttCallbackInfo_t acceptCallbackInfo = { .function = _acceptCallback, .pCallbackContext = NULL }; + const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .pCallbackContext = NULL }; /* Step 1: connect to MQTT. */ mqttError = AwsIotDefenderInternal_MqttConnect(); diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 22eabd82d3..b0609ca50d 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -301,11 +301,12 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } else { - /* Clear the MQTT connection, then copy the MQTT server mode and network - * interface. */ + /* Clear the MQTT connection, then copy the MQTT server mode, network + * interface, and disconnect callback. */ ( void ) memset( pMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); pMqttConnection->awsIotMqttMode = awsIotMqttMode; pMqttConnection->pNetworkInterface = pNetworkInfo->pNetworkInterface; + pMqttConnection->disconnectCallback = pNetworkInfo->disconnectCallback; /* Start a new MQTT connection with a reference count of 1. */ pMqttConnection->references = 1; @@ -1303,7 +1304,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, } /* Close the underlying network connection. This also cleans up keep-alive. */ - _IotMqtt_CloseNetworkConnection( mqttConnection ); + _IotMqtt_CloseNetworkConnection( IOT_MQTT_DISCONNECT_CALLED, + mqttConnection ); /* Check if the connection may be destroyed. */ IotMutex_Lock( &( mqttConnection->referencesMutex ) ); diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 526c54691d..dbbab68fc9 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -694,10 +694,12 @@ bool _IotMqtt_GetNextByte( void * pNetworkConnection, /*-----------------------------------------------------------*/ -void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) +void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, + _mqttConnection_t * pMqttConnection ) { IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; + IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -756,8 +758,7 @@ void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - /* Close the network connection regardless of whether an MQTT DISCONNECT - * packet was sent. */ + /* Close the network connection. */ if( pMqttConnection->pNetworkInterface->close != NULL ) { closeStatus = pMqttConnection->pNetworkInterface->close( pMqttConnection->pNetworkConnection ); @@ -778,6 +779,21 @@ void _IotMqtt_CloseNetworkConnection( _mqttConnection_t * pMqttConnection ) IotLogWarn( "(MQTT connection %p) No network close function was set. Network connection" " not closed.", pMqttConnection ); } + + /* Invoke the disconnect callback. */ + if( pMqttConnection->disconnectCallback.function != NULL ) + { + /* Set the members of the callback parameter. */ + callbackParam.mqttConnection = pMqttConnection; + callbackParam.disconnectReason = disconnectReason; + + pMqttConnection->disconnectCallback.function( pMqttConnection->disconnectCallback.pCallbackContext, + &callbackParam ); + } + else + { + _EMPTY_ELSE_MARKER; + } } /*-----------------------------------------------------------*/ @@ -823,7 +839,8 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, IotLogError( "(MQTT connection %p) Error processing incoming data. Closing connection.", pMqttConnection ); - _IotMqtt_CloseNetworkConnection( pMqttConnection ); + _IotMqtt_CloseNetworkConnection( IOT_MQTT_BAD_PACKET_RECEIVED, + pMqttConnection ); } else { diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index e3b655f694..212ddff2b2 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -751,7 +751,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, /* Close the connection on failures. */ if( status == false ) { - _IotMqtt_CloseNetworkConnection( pMqttConnection ); + _IotMqtt_CloseNetworkConnection( IOT_MQTT_KEEP_ALIVE_TIMEOUT, + pMqttConnection ); } else { @@ -1006,7 +1007,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, callbackParam.operation.result = pOperation->status; /* Invoke the user callback function. */ - pOperation->notify.callback.function( pOperation->notify.callback.param1, + pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, &callbackParam ); /* Attempt to destroy the operation once the user callback returns. */ diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 7463c2876f..2f83dd6474 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -400,7 +400,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, { _mqttSubscription_t * pSubscription = NULL; IotLink_t * pCurrentLink = NULL, * pNextLink = NULL; - void * pParam1 = NULL; + void * pCallbackContext = NULL; void ( * callbackFunction )( void *, IotMqttCallbackParam_t * ) = NULL; @@ -445,7 +445,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, /* Copy the necessary members of the subscription before releasing the * subscription list mutex. */ - pParam1 = pSubscription->callback.param1; + pCallbackContext = pSubscription->callback.pCallbackContext; callbackFunction = pSubscription->callback.function; /* Unlock the subscription list mutex. */ @@ -457,7 +457,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; /* Invoke the subscription callback. */ - callbackFunction( pParam1, pCallbackParam ); + callbackFunction( pCallbackContext, pCallbackParam ); /* Lock the subscription list mutex to decrement the reference count. */ IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index e4b46c8ddb..682b18845e 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -483,7 +483,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt subscription.qos = IOT_MQTT_QOS_1; subscription.pTopicFilter = pTopicFilter; subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); - subscription.callback.param1 = ( void * ) pSubscription; + subscription.callback.pCallbackContext = ( void * ) pSubscription; subscription.callback.function = pCallbackWrapper[ type ]; /* Call the MQTT operation function. */ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index d9e5bfd84f..6303fc2d67 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -151,7 +151,7 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mq /* Set the members of the subscription parameter. */ subscription.pTopicFilter = pTopicFilter; subscription.topicFilterLength = topicFilterLength; - subscription.callback.param1 = NULL; + subscription.callback.pCallbackContext = NULL; subscription.callback.function = callback; /* Call the MQTT operation function. */ diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index a40b82970a..e75953eb26 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -575,7 +575,7 @@ TEST( MQTT_Stress, BlockingCallback ) IotSemaphore_t waitSem; callbackInfo.function = _blockingCallback; - callbackInfo.param1 = &waitSem; + callbackInfo.pCallbackContext = &waitSem; publishInfo.qos = IOT_MQTT_QOS_1; publishInfo.pTopicName = _pTopicNames[ 0 ]; diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 226fc127ac..a121a859b1 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -455,7 +455,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; - subscription.callback.param1 = &waitSem; + subscription.callback.pCallbackContext = &waitSem; /* Subscribe to the test topic filter using the blocking SUBSCRIBE * function. */ @@ -645,13 +645,13 @@ TEST( MQTT_System, SubscribePublishAsync ) /* Initialize members of the operation callback info. */ callbackInfo.function = _operationComplete; - callbackInfo.param1 = &callbackParam; + callbackInfo.pCallbackContext = &callbackParam; /* Initialize members of the subscription. */ subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; - subscription.callback.param1 = &publishWaitSem; + subscription.callback.pCallbackContext = &publishWaitSem; /* Initialize members of the connect info. */ connectInfo.cleanSession = true; @@ -807,7 +807,7 @@ TEST( MQTT_System, LastWillAndTestament ) willSubscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); willSubscription.callback.function = _publishReceived; - willSubscription.callback.param1 = &waitSem; + willSubscription.callback.pCallbackContext = &waitSem; status = IotMqtt_TimedSubscribe( lwtListener, &willSubscription, @@ -895,7 +895,7 @@ TEST( MQTT_System, RestorePreviousSession ) /* Add a subscription. */ subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.param1 = &waitSem; + subscription.callback.pCallbackContext = &waitSem; subscription.callback.function = _publishReceived; status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, @@ -1077,10 +1077,10 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; - subscription.callback.param1 = &( pWaitSemaphores[ 0 ] ); + subscription.callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); callbackInfo.function = _reentrantCallback; - callbackInfo.param1 = pWaitSemaphores; + callbackInfo.pCallbackContext = pWaitSemaphores; status = IotMqtt_Subscribe( _IotTestMqttConnection, &subscription, @@ -1148,13 +1148,13 @@ TEST( MQTT_System, IncomingPublishReentrancy ) pSubscription[ 0 ].pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/IncomingPublishReentrancy"; pSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 0 ].pTopicFilter ); pSubscription[ 0 ].callback.function = _reentrantCallback; - pSubscription[ 0 ].callback.param1 = pWaitSemaphores; + pSubscription[ 0 ].callback.pCallbackContext = pWaitSemaphores; pSubscription[ 1 ].qos = IOT_MQTT_QOS_1; pSubscription[ 1 ].pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; pSubscription[ 1 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 1 ].pTopicFilter ); pSubscription[ 1 ].callback.function = _publishReceived; - pSubscription[ 1 ].callback.param1 = &( pWaitSemaphores[ 0 ] ); + pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, pSubscription, diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 34e4f475a3..f7693808c0 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -141,6 +141,11 @@ static int32_t _pingreqSendCount = 0; */ static int32_t _closeCount = 0; +/** + * @brief Counts how many times #_disconnectCallback has been called. + */ +static int32_t _disconnectCallbackCount = 0; + /** * @brief An MQTT connection to share among the tests. */ @@ -462,6 +467,23 @@ static IotNetworkError_t _close( void * pCloseContext ) /*-----------------------------------------------------------*/ +/** + * @brief An MQTT disconnect callback that counts how many times it was invoked. + */ +static void _disconnectCallback( void * pCallbackContext, + IotMqttCallbackParam_t * pCallbackParam ) +{ + IotMqttDisconnectReason_t * pExpectedReason = ( IotMqttDisconnectReason_t * ) pCallbackContext; + + /* Only increment counter if the reasons match. */ + if( pCallbackParam->disconnectReason == *pExpectedReason ) + { + _disconnectCallbackCount++; + } +} + +/*-----------------------------------------------------------*/ + /** * @brief A task pool job routine that decrements an MQTT operation's job * reference count. @@ -510,6 +532,7 @@ TEST_SETUP( MQTT_Unit_API ) /* Reset the counters. */ _pingreqSendCount = 0; _closeCount = 0; + _disconnectCallbackCount = 0; /* Initialize libraries. */ TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); @@ -869,11 +892,14 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) TEST( MQTT_Unit_API, DisconnectMallocFail ) { int32_t i = 0; + IotMqttDisconnectReason_t expectedReason = IOT_MQTT_DISCONNECT_CALLED; /* Set the members of the network interface. */ _networkInterface.send = _sendSuccess; _networkInterface.close = _close; _networkInfo.createNetworkConnection = false; + _networkInfo.disconnectCallback.pCallbackContext = &expectedReason; + _networkInfo.disconnectCallback.function = _disconnectCallback; for( i = 0; i < _DISCONNECT_MALLOC_LIMIT; i++ ) { @@ -893,7 +919,9 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) * of memory allocation errors. */ IotMqtt_Disconnect( _pMqttConnection, 0 ); TEST_ASSERT_EQUAL_INT( 1, _closeCount ); + TEST_ASSERT_EQUAL_INT( 1, _disconnectCallbackCount ); _closeCount = 0; + _disconnectCallbackCount = 0; } } @@ -1333,6 +1361,9 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) */ TEST( MQTT_Unit_API, KeepAlivePeriodic ) { + /* The expected disconnect reason for this test's disconnect callback. */ + IotMqttDisconnectReason_t expectedReason = IOT_MQTT_KEEP_ALIVE_TIMEOUT; + /* An estimate for the amount of time this test requires. */ const uint32_t sleepTimeMs = ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) + ( IOT_MQTT_RESPONSE_WAIT_MS * _KEEP_ALIVE_COUNT ) + 1500; @@ -1344,6 +1375,8 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) _networkInterface.send = _sendPingreq; _networkInterface.receive = _receivePingresp; _networkInterface.close = _close; + _networkInfo.disconnectCallback.pCallbackContext = &expectedReason; + _networkInfo.disconnectCallback.function = _disconnectCallback; /* Create a new MQTT connection. */ _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, @@ -1370,6 +1403,10 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) /* Check the counters for PINGREQ send and close. */ TEST_ASSERT_EQUAL_INT32( _KEEP_ALIVE_COUNT + 1, _pingreqSendCount ); TEST_ASSERT_EQUAL_INT32( 2, _closeCount ); + + /* Check that the disconnect callback was invoked once (with reason + * "keep-alive timeout"). */ + TEST_ASSERT_EQUAL_INT32( 1, _disconnectCallbackCount ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index b5e28a9061..31a44142c1 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -177,6 +177,11 @@ static bool _getRemainingLengthCalled = false; */ static bool _networkCloseCalled = false; +/** + * @brief Tracks whether #_disconnectCallback has been called. + */ +static bool _disconnectCallbackCalled = false; + /*-----------------------------------------------------------*/ /** @@ -344,7 +349,7 @@ static bool _processPublish( const uint8_t * pPublish, _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ), link ); - pSubscription->callback.param1 = &invokeCount; + pSubscription->callback.pCallbackContext = &invokeCount; } /* Set the members of the receive context. */ @@ -382,10 +387,10 @@ static bool _processPublish( const uint8_t * pPublish, /** * @brief Called when a PUBLISH message is "received". */ -static void _publishCallback( void * param1, +static void _publishCallback( void * pCallbackContext, IotMqttCallbackParam_t * pPublish ) { - IotSemaphore_t * pInvokeCount = ( IotSemaphore_t * ) param1; + IotSemaphore_t * pInvokeCount = ( IotSemaphore_t * ) pCallbackContext; /* QoS 2 is valid for these tests, but currently unsupported by the MQTT library. * Change the QoS to 0 so that QoS validation passes. */ @@ -480,6 +485,24 @@ static IotNetworkError_t _close( void * pConnection ) /*-----------------------------------------------------------*/ +/** + * @brief A disconnect callback function that checks for a "bad packet" reason + * and reports if it was invoked. + */ +static void _disconnectCallback( void * pCallbackContext, + IotMqttCallbackParam_t * pCallbackParam ) +{ + /* Silence warnings about unused parameters. */ + ( void ) pCallbackContext; + + if( pCallbackParam->disconnectReason == IOT_MQTT_BAD_PACKET_RECEIVED ) + { + _disconnectCallbackCalled = true; + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT Receive tests. */ @@ -512,6 +535,7 @@ TEST_SETUP( MQTT_Unit_Receive ) _networkInterface.receive = _receive; _networkInterface.close = _close; networkInfo.pNetworkInterface = &_networkInterface; + networkInfo.disconnectCallback.function = _disconnectCallback; /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); @@ -541,6 +565,7 @@ TEST_SETUP( MQTT_Unit_Receive ) _getPacketTypeCalled = false; _getRemainingLengthCalled = false; _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /*-----------------------------------------------------------*/ @@ -705,6 +730,7 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) /* Processing an invalid packet should cause the network connection to be closed. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); /* This test should not have called any deserializer. Set the deserialize * override called flag to true so that the check for it passes. */ @@ -782,6 +808,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /*-----------------------------------------------------------*/ @@ -811,7 +838,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The CONNACK control packet type must be 0x20. */ @@ -825,7 +854,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* A CONNACK must have a remaining length of 2. */ @@ -840,7 +871,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The reserved bits in CONNACK must be 0. */ @@ -854,7 +887,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The fourth byte of CONNACK must be 0 if the SP flag is set. */ @@ -870,7 +905,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* CONNACK return codes cannot be above 5. */ @@ -885,7 +922,9 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); @@ -951,6 +990,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /*-----------------------------------------------------------*/ @@ -971,7 +1011,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The PUBLISH cannot have a QoS of 3. */ @@ -984,7 +1026,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a PUBLISH with an invalid "Remaining length". */ @@ -1000,7 +1044,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a PUBLISH where some bytes could not be received. */ @@ -1013,7 +1059,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a PUBLISH with a "Remaining length" smaller than the @@ -1040,7 +1088,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a PUBLISH with a "Remaining length" smaller than the @@ -1054,7 +1104,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a PUBLISH with packet identifier 0. */ @@ -1068,7 +1120,9 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } } @@ -1112,6 +1166,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /*-----------------------------------------------------------*/ @@ -1142,7 +1197,9 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The PUBACK control packet type must be 0x40. */ @@ -1156,7 +1213,9 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } _operationResetAndPush( &publish ); @@ -1172,7 +1231,9 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The packet identifier in PUBACK cannot be 0. No status should be set if @@ -1187,7 +1248,9 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Remove unprocessed PUBLISH if present. */ @@ -1302,6 +1365,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /*-----------------------------------------------------------*/ @@ -1333,7 +1397,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a SUBACK with an invalid "Remaining length". */ @@ -1350,7 +1416,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a SUBACK larger than the size of the data stream. */ @@ -1364,7 +1432,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a SUBACK with a "Remaining length" smaller than the @@ -1379,7 +1449,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Attempt to process a SUBACK with a bad return code. */ @@ -1393,7 +1465,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The SUBACK control packet type must be 0x90. */ @@ -1407,7 +1481,9 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); @@ -1453,6 +1529,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /*-----------------------------------------------------------*/ @@ -1483,7 +1560,9 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The UNSUBACK control packet type must be 0xb0. */ @@ -1497,7 +1576,9 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } _operationResetAndPush( &unsubscribe ); @@ -1513,7 +1594,9 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The packet identifier in UNSUBACK cannot be 0. No status should be set if @@ -1528,7 +1611,9 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* Remove unprocessed UNSUBSCRIBE if present. */ @@ -1561,6 +1646,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /* Process a valid PINGRESP. */ @@ -1575,6 +1661,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /* An incomplete PINGRESP should not be processed, and the keep-alive failure @@ -1590,7 +1677,9 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* A PINGRESP should have a remaining length of 0. */ @@ -1606,7 +1695,9 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } /* The PINGRESP control packet type must be 0xd0. */ @@ -1622,7 +1713,9 @@ TEST( MQTT_Unit_Receive, Pingresp ) TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; + _disconnectCallbackCalled = false; } } diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 662bd4a71a..7c29cdffa5 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -684,7 +684,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) /* Change the callback information, but not the topic filter. */ subscription[ 1 ].callback.function = _publishCallback; - subscription[ 1 ].callback.param1 = _pMqttConnection; + subscription[ 1 ].callback.pCallbackContext = _pMqttConnection; /* Add the duplicate subscription. */ status = _IotMqtt_AddSubscriptions( _pMqttConnection, @@ -709,7 +709,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) TEST_ASSERT_EQUAL_UINT16( 3, pSubscription->packetInfo.identifier ); TEST_ASSERT_EQUAL( 0, pSubscription->packetInfo.order ); TEST_ASSERT_EQUAL_PTR( _publishCallback, pSubscription->callback.function ); - TEST_ASSERT_EQUAL_PTR( _pMqttConnection, pSubscription->callback.param1 ); + TEST_ASSERT_EQUAL_PTR( _pMqttConnection, pSubscription->callback.pCallbackContext ); /* Check that a duplicate entry wasn't created. */ IotListDouble_Remove( &( pSubscription->link ) ); @@ -847,7 +847,7 @@ TEST( MQTT_Unit_Subscription, ProcessPublish ) subscription.pTopicFilter = "/test"; subscription.topicFilterLength = 5; subscription.callback.function = _publishCallback; - subscription.callback.param1 = &callbackInvoked; + subscription.callback.pCallbackContext = &callbackInvoked; callbackParam.message.info.pTopicName = "/test"; callbackParam.message.info.topicNameLength = 5; @@ -888,17 +888,17 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) subscription[ 0 ].pTopicFilter = "/test"; subscription[ 0 ].topicFilterLength = 5; subscription[ 0 ].callback.function = _publishCallback; - subscription[ 0 ].callback.param1 = &( callbackInvoked[ 0 ] ); + subscription[ 0 ].callback.pCallbackContext = &( callbackInvoked[ 0 ] ); subscription[ 1 ].pTopicFilter = "/+"; subscription[ 1 ].topicFilterLength = 2; subscription[ 1 ].callback.function = _publishCallback; - subscription[ 1 ].callback.param1 = &( callbackInvoked[ 1 ] ); + subscription[ 1 ].callback.pCallbackContext = &( callbackInvoked[ 1 ] ); subscription[ 2 ].pTopicFilter = "/#"; subscription[ 2 ].topicFilterLength = 2; subscription[ 2 ].callback.function = _publishCallback; - subscription[ 2 ].callback.param1 = &( callbackInvoked[ 2 ] ); + subscription[ 2 ].callback.pCallbackContext = &( callbackInvoked[ 2 ] ); /* Create a PUBLISH that matches all 3 subscriptions. */ callbackParam.message.info.pTopicName = "/test"; @@ -956,7 +956,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) subscription.pTopicFilter = "/test"; subscription.topicFilterLength = 5; subscription.callback.function = _blockingCallback; - subscription.callback.param1 = &waitSem; + subscription.callback.pCallbackContext = &waitSem; /* Add the subscriptions. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, From e9b9bc3f8db84962666303db584c0baf9087ad3a Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Mon, 18 Mar 2019 17:17:09 -0700 Subject: [PATCH 051/844] Improved task pool coding conventions and MISRA compliance. (#325) * Fixed some thread sanitizer issues and wrongly named #define in task pool. * Improved task pool coding conventions and MISRA compliance. --- lib/include/iot_taskpool.h | 10 +- lib/include/private/iot_taskpool_internal.h | 32 +- lib/include/types/iot_taskpool_types.h | 16 +- lib/source/common/iot_taskpool.c | 308 ++++++++++---------- 4 files changed, 187 insertions(+), 179 deletions(-) diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 72f113ac95..66287946e2 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -24,8 +24,8 @@ * @brief User-facing functions of the task pool library. */ -#ifndef _IOT_TASKPOOL_H_ -#define _IOT_TASKPOOL_H_ +#ifndef IOT_TASKPOOL_H_ +#define IOT_TASKPOOL_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -118,7 +118,7 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c * */ /* @[declare_taskpool_getsystemtaskpool] */ -IotTaskPool_t * IotTaskPool_GetSystemTaskPool(); +IotTaskPool_t * IotTaskPool_GetSystemTaskPool( void ); /* @[declare_taskpool_getsystemtaskpool] */ /** @@ -191,7 +191,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ); */ /* @[declare_taskpool_setmaxthreads] */ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, - size_t maxThreads ); + uint32_t maxThreads ); /* @[declare_taskpool_setmaxthreads] */ /** @@ -507,4 +507,4 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, const char * IotTaskPool_strerror( IotTaskPoolError_t status ); /* @[declare_taskpool_strerror] */ -#endif /* ifndef _IOT_TASKPOOL_H_ */ +#endif /* ifndef IOT_TASKPOOL_H_ */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index 2e0fca7921..0672958b18 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -25,8 +25,8 @@ * typical application code. */ -#ifndef _IOT_TASKPOOL_INTERNAL_H_ -#define _IOT_TASKPOOL_INTERNAL_H_ +#ifndef IOT_TASKPOOL_INTERNAL_H_ +#define IOT_TASKPOOL_INTERNAL_H_ /* Task pool include. */ #include "private/iot_error.h" @@ -37,62 +37,62 @@ /** * @brief Every public API return an enumeration value with an undelying value of 0 in case of success. */ -#define _TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) +#define TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) /** * @brief Every public API returns an enumeration value with an undelying value different than 0 in case of success. */ -#define _TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) +#define TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) /** * @brief Jump to the cleanup area. */ -#define _TASKPOOL_GOTO_CLEANUP() _IOT_GOTO_CLEANUP() +#define TASKPOOL_GOTO_CLEANUP() _IOT_GOTO_CLEANUP() /** * @brief Declare the storage for the error status variable. */ -#define _TASKPOOL_FUNCTION_ENTRY( result ) _IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) +#define TASKPOOL_FUNCTION_ENTRY( result ) _IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) /** * @brief Check error and leave in case of failure. */ -#define _TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( _TASKPOOL_FAILED( status = ( expr ) ) ) { _IOT_GOTO_CLEANUP(); } } +#define TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( TASKPOOL_FAILED( status = ( expr ) ) ) { _IOT_GOTO_CLEANUP(); } } /** * @brief Exit if an argument is NULL. */ -#define _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) +#define TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) /** * @brief Exit if an argument is NULL. */ -#define _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) +#define TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) /** * @brief Set error and leave. */ -#define _TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) _IOT_SET_AND_GOTO_CLEANUP( expr ) +#define TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) _IOT_SET_AND_GOTO_CLEANUP( expr ) /** * @brief Initialize error and declare start of cleanup area. */ -#define _TASKPOOL_FUNCTION_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN() +#define TASKPOOL_FUNCTION_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN() /** * @brief Initialize error and declare end of cleanup area. */ -#define _TASKPOOL_FUNCTION_CLEANUP_END() _IOT_FUNCTION_CLEANUP_END() +#define TASKPOOL_FUNCTION_CLEANUP_END() _IOT_FUNCTION_CLEANUP_END() /** * @brief Create an empty cleanup area. */ -#define _TASKPOOL_NO_FUNCTION_CLEANUP() _IOT_FUNCTION_EXIT_NO_CLEANUP() +#define TASKPOOL_NO_FUNCTION_CLEANUP() _IOT_FUNCTION_EXIT_NO_CLEANUP() /** * @brief Does not create a cleanup area. */ -#define _TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL() return status +#define TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL() return status /** * @def IotTaskPool_Assert( expression ) @@ -200,7 +200,7 @@ * Provide default values for undefined configuration constants. */ #ifndef IOT_TASKPOOL_JOBS_RECYCLE_LIMIT - #define IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ( 32 ) + #define IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ( 32UL ) #endif /** @endcond */ @@ -239,4 +239,4 @@ typedef struct _taskPoolTimerEvent extern IotTaskPool_t _IotSystemTaskPool; /** @endcond */ -#endif /* ifndef _IOT_TASKPOOL_INTERNAL_H_ */ +#endif /* ifndef IOT_TASKPOOL_INTERNAL_H_ */ diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 81aeb3294c..abdd6e4285 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -24,8 +24,8 @@ * @brief Types of the task pool. */ -#ifndef _IOT_TASKPOOL_TYPES_H_ -#define _IOT_TASKPOOL_TYPES_H_ +#ifndef IOT_TASKPOOL_TYPES_H_ +#define IOT_TASKPOOL_TYPES_H_ /* Build using a config header, if provided. */ #ifdef IOT_CONFIG_FILE @@ -281,11 +281,11 @@ typedef struct IotTaskPool IotQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ IotTaskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ - size_t minThreads; /**< @brief The minimum number of threads for the task pool. */ - size_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ - size_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ - size_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ - size_t stackSize; /**< @brief The stack size for all task pool threads. */ + uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ + uint32_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ + uint32_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ + uint32_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ + uint32_t stackSize; /**< @brief The stack size for all task pool threads. */ int32_t priority; /**< @brief The priority for all task pool threads. */ IotSemaphore_t dispatchSignal; /**< @brief The synchronization object on which threads are waiting for incoming jobs. */ IotSemaphore_t startStopSignal; /**< @brief The synchronization object for threads to signal start and stop condition. */ @@ -367,4 +367,4 @@ typedef struct IotTaskPoolJob */ #define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) -#endif /* ifndef _IOT_TASKPOOL_TYPES_H_ */ +#endif /* ifndef IOT_TASKPOOL_TYPES_H_ */ diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 479c5e161b..72b857c683 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -41,29 +41,29 @@ * @brief Enter a critical section by locking a mutex. * */ -#define INTERNAL_TASKPOOL_ENTER_CRITICAL IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_ENTER_CRITICAL IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Try entering a critical section by trying and lock a mutex. * */ -#define INTERNAL_TASKPOOL_TRY_ENTER_CRITICAL IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_TRY_ENTER_CRITICAL IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Exit a critical section by unlocking a mutex. * */ -#define INTERNAL_TASKPOOL__EXIT_CRITICAL IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_EXIT_CRITICAL IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Maximum semaphore value for wait operations. */ -#define INTERNAL_TASKPOOL__TASKPOOL_MAX_SEM_VALUE 0xFFFF +#define TASKPOOL_MAX_SEM_VALUE 0xFFFF /** * @brief Reschedule delay in milliseconds for deferred jobs. */ -#define INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS ( ( uint64_t ) 10 ) +#define TASKPOOL_JOB_RESCHEDULE_DELAY_MS ( 10ULL ) /* ---------------------------------------------------------------------------------------------- */ @@ -252,7 +252,7 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, /* ---------------------------------------------------------------------------------------------- */ -IotTaskPool_t * IotTaskPool_GetSystemTaskPool() +IotTaskPool_t * IotTaskPool_GetSystemTaskPool( void ) { return &_IotSystemTaskPool; } @@ -261,11 +261,11 @@ IotTaskPool_t * IotTaskPool_GetSystemTaskPool() IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Create( pInfo, &_IotSystemTaskPool ) ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Create( pInfo, &_IotSystemTaskPool ) ); - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -273,20 +273,20 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, IotTaskPool_t * const pTaskPool ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTaskPool ) ); + TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTaskPool ) ); - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); uint32_t count; @@ -294,12 +294,12 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) uint32_t activeThreads; /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); /* Destroying the task pool should be safe, and therefore we will grab the task pool lock. * No worker thread or application thread should access any data structure * in the task pool while the task pool is being destroyed. */ - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { IotLink_t * pItemLink; @@ -362,7 +362,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /* (4) Set the exit condition. */ _signalShutdown( pTaskPool, activeThreads ); } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; /* (5) Wait for all active threads to reach the end of their life-span. */ for( count = 0; count < activeThreads; ++count ) @@ -370,34 +370,37 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) IotSemaphore_Wait( &pTaskPool->startStopSignal ); } + IotTaskPool_Assert( IotSemaphore_GetCount( &pTaskPool->startStopSignal ) == 0 ); + IotTaskPool_Assert( pTaskPool->activeThreads == 0 ); + /* (6) Destroy all signaling objects. */ _destroyTaskPool( pTaskPool ); - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, - size_t maxThreads ) + uint32_t maxThreads ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); uint32_t count, i; /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < ( size_t ) 1 ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); + TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < 1UL ); - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } uint32_t previousMaxThreads = pTaskPool->maxThreads; @@ -416,7 +419,7 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, i = count; - while( i > ( size_t ) 0 ) + while( i > 0UL ) { IotSemaphore_Post( &pTaskPool->dispatchSignal ); @@ -424,9 +427,9 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, } } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -435,16 +438,16 @@ IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallbac void * const pUserContext, IotTaskPoolJob_t * const pJob ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); /* Build a job around the user-provided storage. */ _initializeJob( pJob, userCallback, pUserContext, true ); - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -454,36 +457,36 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP void * const pUserContext, IotTaskPoolJob_t ** const ppJob ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); { IotTaskPoolJob_t * pTempJob = NULL; - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } pTempJob = _fetchOrAllocateJob( &pTaskPool->jobsCache ); } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; /* Initialize all members. */ if( pTempJob == NULL ) { IotLogInfo( "Failed to allocate a job." ); - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } _initializeJob( pTempJob, userCallback, pUserContext, false ); @@ -491,7 +494,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP *ppJob = pTempJob; } - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -499,13 +502,13 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -524,9 +527,9 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask status = _trySafeExtraction( pTaskPool, pJob, true ); } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - if( _TASKPOOL_SUCCEEDED( status ) ) + if( TASKPOOL_SUCCEEDED( status ) ) { /* At this point, the job must not be in any queue or list. */ IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); @@ -534,7 +537,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask _destroyJob( pJob ); } - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -542,13 +545,13 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -556,7 +559,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; } /* Do not recycle statically allocated jobs. */ - else if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == ( ( uint32_t ) 0 ) ) + else if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0UL ) { status = _trySafeExtraction( pTaskPool, pJob, true ); } @@ -568,7 +571,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, } /* If all safety checks completed, proceed. */ - if( _TASKPOOL_SUCCEEDED( status ) ) + if( TASKPOOL_SUCCEEDED( status ) ) { /* At this point, the job must not be in any queue or list. */ IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); @@ -576,9 +579,9 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, _recycleJob( &pTaskPool->jobsCache, pJob ); } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -587,14 +590,14 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, uint32_t flags ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != ( ( uint32_t ) 0 ) ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -607,14 +610,14 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, } /* If all safety checks completed, proceed. */ - if( _TASKPOOL_SUCCEEDED( status ) ) + if( TASKPOOL_SUCCEEDED( status ) ) { status = _scheduleInternal( pTaskPool, pJob, flags ); } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -623,37 +626,37 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool IotTaskPoolJob_t * const pJob, uint64_t timeMs ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - if( timeMs == ( ( uint32_t ) 0 ) ) + if( timeMs == 0UL ) { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); } - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } /* If all safety checks completed, proceed. */ - if( _TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, false ) ) ) + if( TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, false ) ) ) { _taskPoolTimerEvent_t * pTimerEvent = IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); if( pTimerEvent == NULL ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } IotLink_t * pTimerEventLink; @@ -687,14 +690,14 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool } else { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -703,30 +706,30 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, const IotTaskPoolJob_t * const pJob, IotTaskPoolJobStatus_t * const pStatus ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } *pStatus = pJob->status; } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -735,32 +738,32 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, IotTaskPoolJobStatus_t * const pStatus ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); if( pStatus != NULL ) { *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; } - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Check again if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } status = _tryCancelInternal( pTaskPool, pJob, pStatus ); } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } const char * IotTaskPool_strerror( IotTaskPoolError_t status ) @@ -813,28 +816,28 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, IotTaskPool_t * const pTaskPool ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); uint32_t count; uint32_t threadsCreated = 0; /* Check input values for consistency. */ /* Parameter checking. */ - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - _TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < ( ( uint32_t ) 1 ) ); - _TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < ( ( uint32_t ) 1 ) ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo ); + TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads ); + TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < 1UL ); + TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < 1UL ); /* Initialize all internal data structure prior to creating all threads. */ - _TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); + TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); /* Create the timer mutex for a new connection. */ if( IotClock_TimerCreate( &( pTaskPool->timer ), _timerThread, pTaskPool ) == false ) { IotLogError( "Failed to create timer for task pool." ); - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } IotTaskPool_Assert( pInfo->minThreads == pTaskPool->minThreads ); @@ -857,7 +860,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo IotLogError( "Could not create worker thread! Exiting..." ); /* If creating one thread fails, set error condition and exit the loop. */ - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } /* Upon successful thread creation, increase the number of active threads. */ @@ -866,7 +869,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo ++threadsCreated; } - _TASKPOOL_FUNCTION_CLEANUP(); + TASKPOOL_FUNCTION_CLEANUP(); /* Wait for threads to be ready to wait on the condition, so that threads are actually able to receive messages. */ for( count = 0; count < threadsCreated; ++count ) @@ -875,7 +878,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo } /* In case of failure, wait on the created threads to exit. */ - if( _TASKPOOL_FAILED( status ) ) + if( TASKPOOL_FAILED( status ) ) { /* Set the exit condition for the newly created threads. */ _signalShutdown( pTaskPool, threadsCreated ); @@ -889,7 +892,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo _destroyTaskPool( pTaskPool ); } - _TASKPOOL_FUNCTION_CLEANUP_END(); + TASKPOOL_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -897,7 +900,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, IotTaskPool_t * const pTaskPool ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); bool semStartStopInit = false; bool lockInit = false; @@ -920,7 +923,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ _initJobsCache( &pTaskPool->jobsCache ); /* Initialize the semaphore to ensure all threads have started. */ - if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, INTERNAL_TASKPOOL__TASKPOOL_MAX_SEM_VALUE ) ) + if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, TASKPOOL_MAX_SEM_VALUE ) ) { semStartStopInit = true; @@ -929,28 +932,28 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ lockInit = true; /* Initialize the semaphore for waiting for incoming work. */ - if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, INTERNAL_TASKPOOL__TASKPOOL_MAX_SEM_VALUE ) ) + if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, TASKPOOL_MAX_SEM_VALUE ) ) { semDispatchInit = true; } else { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } } else { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } } else { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } - _TASKPOOL_FUNCTION_CLEANUP(); + TASKPOOL_FUNCTION_CLEANUP(); - if( _TASKPOOL_FAILED( status ) ) + if( TASKPOOL_FAILED( status ) ) { if( semStartStopInit ) { @@ -968,7 +971,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ } } - _TASKPOOL_FUNCTION_CLEANUP_END(); + TASKPOOL_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -985,9 +988,12 @@ static void _destroyTaskPool( IotTaskPool_t * const pTaskPool ) static void _taskPoolWorker( void * pUserContext ) { - /* Extract pTaskPool pointer from context. */ IotTaskPool_Assert( pUserContext != NULL ); + + IotTaskPoolRoutine_t userCallback = NULL; bool running = true; + + /* Extract pTaskPool pointer from context. */ IotTaskPool_t * pTaskPool = ( IotTaskPool_t * ) pUserContext; /* Signal that this worker completed initialization and it is ready to receive notifications. */ @@ -1008,7 +1014,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified, * or before waiting for incoming notifications. */ - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* If the exit condition is verified, update the number of active threads and exit the loop. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -1018,7 +1024,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Decrease the number of active threads. */ pTaskPool->activeThreads--; - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; /* Signal that this worker is exiting. */ IotSemaphore_Post( &pTaskPool->startStopSignal ); @@ -1052,26 +1058,26 @@ static void _taskPoolWorker( void * pUserContext ) /* Update status to 'executing'. */ pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; + userCallback = pJob->userCallback; } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */ while( pJob != NULL ) { - /* Record callback, so job can be re-used in the callback. */ - IotTaskPoolRoutine_t userCallback = pJob->userCallback; - /* Process the job by invoking the associated callback with the user context. * This task pool thread will not be available until the user callback returns. */ { IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); + IotTaskPool_Assert( userCallback != NULL ); userCallback( pTaskPool, pJob, pJob->pUserContext ); /* This job is finished, clear its pointer. */ pJob = NULL; + userCallback = NULL; /* If this thread exceeded the quota, then let it terminate. */ if( running == false ) @@ -1082,7 +1088,7 @@ static void _taskPoolWorker( void * pUserContext ) } /* Acquire the lock before updating the job status. */ - INTERNAL_TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL; { /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ pTaskPool->activeJobs--; @@ -1096,7 +1102,7 @@ static void _taskPoolWorker( void * pUserContext ) /* If there is no job left in the dispatch queue, update the worker status and leave. */ if( pItem == NULL ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ break; @@ -1104,11 +1110,13 @@ static void _taskPoolWorker( void * pUserContext ) else { pJob = IotLink_Container( IotTaskPoolJob_t, pItem, link ); + + userCallback = pJob->userCallback; } pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; } } while( running == true ); } @@ -1190,7 +1198,7 @@ static void _recycleJob( IotTaskPoolCache_t * const pCache, IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); /* We will recycle the job if there is space in the cache. */ - if( pCache->freeCount < ( size_t ) IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ) + if( pCache->freeCount < IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ) { /* Destroy user data, for added safety&security. */ pJob->userCallback = NULL; @@ -1223,7 +1231,7 @@ static void _destroyJob( IotTaskPoolJob_t * const pJob ) pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED; /* Only dispose of dynamically allocated jobs. */ - if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == ( ( uint32_t ) 0 ) ) + if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0UL ) { IotTaskPool_FreeJob( pJob ); } @@ -1233,7 +1241,7 @@ static void _destroyJob( IotTaskPoolJob_t * const pJob ) static bool _IsShutdownStarted( const IotTaskPool_t * const pTaskPool ) { - return( pTaskPool->maxThreads == ( ( uint32_t ) 0 ) ); + return( pTaskPool->maxThreads == 0UL ); } /*-----------------------------------------------------------*/ @@ -1259,7 +1267,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, uint32_t flags ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); bool mustGrow = false; bool shouldGrow = false; @@ -1317,15 +1325,15 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, /* Failure to create a worker thread for a high priority job is considered a failure. */ if( mustGrow ) { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } } } } - _TASKPOOL_FUNCTION_CLEANUP(); + TASKPOOL_FUNCTION_CLEANUP(); - if( _TASKPOOL_SUCCEEDED( status ) ) + if( TASKPOOL_SUCCEEDED( status ) ) { /* Append the job to the dispatch queue. */ IotQueue_Enqueue( &pTaskPool->dispatchQueue, &pJob->link ); @@ -1343,7 +1351,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, pTaskPool->activeJobs--; } - _TASKPOOL_FUNCTION_CLEANUP_END(); + TASKPOOL_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -1369,7 +1377,7 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, IotTaskPoolJobStatus_t * const pStatus ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); bool cancelable = false; @@ -1405,7 +1413,7 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, if( cancelable == false ) { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_CANCEL_FAILED ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_CANCEL_FAILED ); } else { @@ -1461,11 +1469,11 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, else { /* A cancelable job status should be either 'scheduled' or 'deferrred'. */ - IotTaskPool_Assert( false ); + IotTaskPool_Assert( ( currentStatus == IOT_TASKPOOL_STATUS_READY ) || ( currentStatus == IOT_TASKPOOL_STATUS_CANCELED ) ); } } - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1474,14 +1482,14 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, bool atCompletion ) { - _TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); IotTaskPoolJobStatus_t currentStatus = pJob->status; /* if the job is executing, we cannot touch it. */ if( ( atCompletion == false ) && ( currentStatus == IOT_TASKPOOL_STATUS_COMPLETED ) ) { - _TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); } /* Do not destroy a job in the dispatch queue or the timer queue without cancelling first. */ else if( ( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) || ( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED ) ) @@ -1519,7 +1527,7 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, /* Nothing to do */ } - _TASKPOOL_NO_FUNCTION_CLEANUP(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1560,9 +1568,9 @@ static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, delta = pFirstTimerEvent->expirationTime - now; } - if( delta < INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS ) + if( delta < TASKPOOL_JOB_RESCHEDULE_DELAY_MS ) { - delta = INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ + delta = TASKPOOL_JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ } IotTaskPool_Assert( delta > 0 ); @@ -1586,11 +1594,11 @@ static void _timerThread( void * pArgument ) * If this mutex cannot be locked it means that another thread is manipulating the * timeouts list, and will reset the timer to fire again, although it will be late. */ - if( INTERNAL_TASKPOOL_TRY_ENTER_CRITICAL == false ) + if( TASKPOOL_TRY_ENTER_CRITICAL == false ) { IotLogWarn( "Failed to lock timer mutex in timer thread. Rescheduling deferred job." ); - ( void ) IotClock_TimerArm( &pTaskPool->timer, INTERNAL_TASKPOOL__JOB_RESCHEDULE_DELAY_MS, 0 ); + ( void ) IotClock_TimerArm( &pTaskPool->timer, TASKPOOL_JOB_RESCHEDULE_DELAY_MS, 0 ); return; } @@ -1599,7 +1607,7 @@ static void _timerThread( void * pArgument ) /* Check again for shutdown and bail out early in case. */ if( _IsShutdownStarted( pTaskPool ) ) { - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; return; } @@ -1653,7 +1661,7 @@ static void _timerThread( void * pArgument ) } } - INTERNAL_TASKPOOL__EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL; } /** @endcond */ From 15aedc6f3a443bd7dd08a1edfaa4ce33c326ecbd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 18 Mar 2019 18:55:36 -0700 Subject: [PATCH 052/844] Split Shadow headers into types and functions. (#326) --- doc/config/shadow | 1 + lib/include/aws_iot_shadow.h | 635 +----------------- lib/include/iot_json_utils.h | 8 +- lib/include/iot_mqtt.h | 2 +- lib/include/private/aws_iot_shadow_internal.h | 48 +- lib/include/types/aws_iot_shadow_types.h | 625 +++++++++++++++++ lib/source/common/iot_json_utils.c | 8 +- lib/source/shadow/aws_iot_shadow_api.c | 80 +-- lib/source/shadow/aws_iot_shadow_operation.c | 52 +- lib/source/shadow/aws_iot_shadow_parser.c | 18 +- .../shadow/aws_iot_shadow_subscription.c | 34 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 7 - .../shadow/unit/aws_iot_tests_shadow_parser.c | 14 +- 13 files changed, 782 insertions(+), 750 deletions(-) create mode 100644 lib/include/types/aws_iot_shadow_types.h diff --git a/doc/config/shadow b/doc/config/shadow index 63e3fe2110..a59ad74dcf 100644 --- a/doc/config/shadow +++ b/doc/config/shadow @@ -16,6 +16,7 @@ GENERATE_TAGFILE = doc/tag/shadow.tag INPUT = doc/lib/ \ lib/include \ lib/include/private \ + lib/include/types \ lib/source/shadow # Library file names. diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index c08dd19649..fe5f0cb357 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -21,7 +21,7 @@ /** * @file aws_iot_shadow.h - * @brief User-facing functions and structs of the Shadow library. + * @brief User-facing functions of the Shadow library. */ #ifndef _AWS_IOT_SHADOW_H_ @@ -32,595 +32,8 @@ #include IOT_CONFIG_FILE #endif -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/*--------------------------- Shadow handle types ---------------------------*/ - -/** - * @handles{shadow,Shadow library} - */ - -/** - * @ingroup shadow_datatypes_handles - * @brief Opaque handle that references an in-progress Shadow operation. - * - * Set as an output parameter of @ref shadow_function_delete, @ref shadow_function_get, - * and @ref shadow_function_update. These functions send a message to the Shadow - * service requesting a Shadow operation; the result of this operation is unknown - * until the Shadow service sends a response. Therefore, this handle serves as a - * reference to Shadow operations awaiting a response from the Shadow service. - * - * This reference will be valid from the successful return of @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update. The reference becomes - * invalid once the [completion callback](@ref AwsIotShadowCallbackInfo_t) is invoked, - * or @ref shadow_function_wait returns. - * - * @initializer{AwsIotShadowReference_t,AWS_IOT_SHADOW_REFERENCE_INITIALIZER} - * - * @see @ref shadow_function_wait and #AWS_IOT_SHADOW_FLAG_WAITABLE for waiting on - * a reference. #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an - * asynchronous notification of completion. - */ -typedef void * AwsIotShadowReference_t; - -/*------------------------- Shadow enumerated types -------------------------*/ - -/** - * @enums{shadow,Shadow library} - */ - -/** - * @ingroup shadow_datatypes_enums - * @brief Return codes of [Shadow functions](@ref shadow_functions). - * - * The function @ref shadow_function_strerror can be used to get a return code's - * description. - * - * The values between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) and 500 - * (#AWS_IOT_SHADOW_SERVER_ERROR) may be returned by the Shadow service when it - * rejects a Shadow operation. See [this page] - * (https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-error-messages.html) - * for more information. - */ -typedef enum AwsIotShadowError -{ - /** - * @brief Shadow operation completed successfully. - * - * Functions that may return this value: - * - @ref shadow_function_init - * - @ref shadow_function_wait - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - * - @ref shadow_function_removepersistentsubscriptions - * - * Will also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - * when successful. - */ - AWS_IOT_SHADOW_SUCCESS = 0, - - /** - * @brief Shadow operation queued, awaiting result. - * - * Functions that may return this value: - * - @ref shadow_function_delete - * - @ref shadow_function_get - * - @ref shadow_function_update - */ - AWS_IOT_SHADOW_STATUS_PENDING, - - /** - * @brief Initialization failed. - * - * Functions that may return this value: - * - @ref shadow_function_init - */ - AWS_IOT_SHADOW_INIT_FAILED, - - /** - * @brief At least one parameter is invalid. - * - * Functions that may return this value: - * - @ref shadow_function_delete and @ref shadow_function_timeddelete - * - @ref shadow_function_get and @ref shadow_function_timedget - * - @ref shadow_function_update and @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_BAD_PARAMETER, - - /** - * @brief Shadow operation failed because of memory allocation failure. - * - * Functions that may return this value: - * - @ref shadow_function_delete and @ref shadow_function_timeddelete - * - @ref shadow_function_get and @ref shadow_function_timedget - * - @ref shadow_function_update and @ref shadow_function_timedupdate - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_NO_MEMORY, - - /** - * @brief Shadow operation failed because of failure in MQTT library. - * - * Check the Shadow library logs for the error code returned by the MQTT - * library. - * - * Functions that may return this value: - * - @ref shadow_function_delete and @ref shadow_function_timeddelete - * - @ref shadow_function_get and @ref shadow_function_timedget - * - @ref shadow_function_update and @ref shadow_function_timedupdate - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - * - @ref shadow_function_removepersistentsubscriptions - */ - AWS_IOT_SHADOW_MQTT_ERROR, - - /** - * @brief Reponse received from Shadow service not understood. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_BAD_RESPONSE, - - /** - * @brief A blocking Shadow operation timed out. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_TIMEOUT, - - /** - * @brief Shadow operation rejected: Bad request. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_BAD_REQUEST = 400, - - /** - * @brief Shadow operation rejected: Unauthorized. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_UNAUTHORIZED = 401, - - /** - * @brief Shadow operation rejected: Forbidden. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_FORBIDDEN = 403, - - /** - * @brief Shadow operation rejected: Thing not found. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_NOT_FOUND = 404, - - /** - * @brief Shadow operation rejected: Version conflict. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_CONFLICT = 409, - - /** - * @brief Shadow operation rejected: The payload exceeds the maximum size - * allowed. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_TOO_LARGE = 413, - - /** - * @brief Shadow operation rejected: Unsupported document encoding; supported - * encoding is UTF-8. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_UNSUPPORTED = 415, - - /** - * @brief Shadow operation rejected: The Device Shadow service will generate - * this error message when there are more than 10 in-flight requests. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_TOO_MANY_REQUESTS = 429, - - /** - * @brief Shadow operation rejected: Internal service failure. - * - * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_SERVER_ERROR = 500, -} AwsIotShadowError_t; - -/** - * @ingroup shadow_datatypes_enums - * @brief Types of Shadow library callbacks. - * - * One of these values will be placed in #AwsIotShadowCallbackParam_t.callbackType - * to identify the reason for invoking a callback function. - */ -typedef enum AwsIotShadowCallbackType -{ - AWS_IOT_SHADOW_DELETE_COMPLETE, /**< Callback invoked because a [Shadow delete](@ref shadow_function_delete) completed. */ - AWS_IOT_SHADOW_GET_COMPLETE, /**< Callback invoked because a [Shadow get](@ref shadow_function_get) completed. */ - AWS_IOT_SHADOW_UPDATE_COMPLETE, /**< Callback invoked because a [Shadow update](@ref shadow_function_update) completed. */ - AWS_IOT_SHADOW_DELTA_CALLBACK, /**< Callback invoked for an incoming message on a [Shadow delta](@ref shadow_function_setdeltacallback) topic. */ - AWS_IOT_SHADOW_UPDATED_CALLBACK /**< Callback invoked for an incoming message on a [Shadow updated](@ref shadow_function_setupdatedcallback) topic. */ -} AwsIotShadowCallbackType_t; - -/*------------------------- Shadow parameter structs ------------------------*/ - -/** - * @paramstructs{shadow,Shadow} - */ - -/** - * @ingroup shadow_datatypes_paramstructs - * @brief Parameter to a Shadow callback function. - * - * @paramfor Shadow callback functions - * - * The Shadow library passes this struct to a callback function whenever a - * Shadow operation completes or a message is received on a Shadow delta or - * updated topic. - * - * The valid members of this struct are different based on - * #AwsIotShadowCallbackParam_t.callbackType. If the callback type is - * #AWS_IOT_SHADOW_DELETE_COMPLETE, #AWS_IOT_SHADOW_GET_COMPLETE, or - * #AWS_IOT_SHADOW_UPDATE_COMPLETE, then #AwsIotShadowCallbackParam_t.operation - * is valid. Otherwise, if the callback type is #AWS_IOT_SHADOW_DELTA_CALLBACK - * or #AWS_IOT_SHADOW_UPDATED_CALLBACK, then #AwsIotShadowCallbackParam_t.callback - * is valid. - * - * @attention Any pointers in this callback parameter may be freed as soon as the - * [callback function](@ref AwsIotShadowCallbackInfo_t.function) returns. Therefore, - * data must be copied if it is needed after the callback function returns. - * @attention The Shadow library may set strings that are not NULL-terminated. - * - * @see #AwsIotShadowCallbackInfo_t for the signature of a callback function. - */ -typedef struct AwsIotShadowCallbackParam -{ - AwsIotShadowCallbackType_t callbackType; /**< @brief Reason for invoking the Shadow callback function. */ - - const char * pThingName; /**< @brief The Thing Name associated with this Shadow callback. */ - size_t thingNameLength; /**< @brief Length of #AwsIotShadowCallbackParam_t.pThingName. */ - - union - { - /* Valid for completed Shadow operations. */ - struct - { - /* Valid for a completed Shadow GET operation. */ - struct - { - const char * pDocument; /**< @brief Retrieved Shadow document. */ - size_t documentLength; /**< @brief Length of retrieved Shadow document. */ - } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_get). */ - - AwsIotShadowError_t result; /**< @brief Result of Shadow operation, e.g. succeeded or failed. */ - AwsIotShadowReference_t reference; /**< @brief Reference to the Shadow operation that completed. */ - } operation; /**< @brief Information on a completed Shadow operation. */ - - /* Valid for a message on a Shadow delta or updated topic. */ - struct - { - const char * pDocument; /**< @brief Shadow delta or updated document. */ - size_t documentLength; /**< @brief Length of Shadow delta or updated document. */ - } callback; /**< @brief Shadow document from an incoming delta or updated topic. */ - }; -} AwsIotShadowCallbackParam_t; - -/** - * @ingroup shadow_datatypes_paramstructs - * @brief Information on a user-provided Shadow callback function. - * - * @paramfor @ref shadow_function_delete, @ref shadow_function_get, @ref - * shadow_function_update, @ref shadow_function_setdeltacallback, @ref - * shadow_function_setupdatedcallback - * - * Provides a function to be invoked when a Shadow operation completes or when a - * Shadow document is received on a callback topic (delta or updated). - * - * @initializer{AwsIotShadowCallbackInfo_t,AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER} - */ -typedef struct AwsIotShadowCallbackInfo -{ - void * param1; /**< @brief The first parameter to pass to the callback function. */ - - /** - * @brief User-provided callback function signature. - * - * @param[in] void* #AwsIotShadowCallbackInfo_t.param1 - * @param[in] AwsIotShadowCallbackParam_t* Details on the outcome of the Shadow - * operation or an incoming Shadow document. - * - * @see #AwsIotShadowCallbackParam_t for more information on the second parameter. - */ - void ( * function )( void *, - AwsIotShadowCallbackParam_t * const ); -} AwsIotShadowCallbackInfo_t; - -/** - * @ingroup shadow_datatypes_paramstructs - * @brief Information on a Shadow document for @ref shadow_function_get or @ref - * shadow_function_update. - * - * @paramfor @ref shadow_function_get, @ref shadow_function_update - * - * The valid members of this struct are different based on whether this struct - * is passed to @ref shadow_function_get or @ref shadow_function_update. When - * passed to @ref shadow_function_get, the `get` member is valid. When passed to - * @ref shadow_function_update, the `update` member is valid. All other members - * must always be set. - * - * @initializer{AwsIotShadowDocumentInfo_t,AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER} - */ -typedef struct AwsIotShadowDocumentInfo -{ - const char * pThingName; /**< @brief The Thing Name associated with this Shadow document. */ - size_t thingNameLength; /**< @brief Length of #AwsIotShadowDocumentInfo_t.pThingName. */ - - IotMqttQos_t qos; /**< @brief QoS when sending a Shadow get or update message. See #IotMqttPublishInfo_t.qos. */ - uint32_t retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #IotMqttPublishInfo_t.retryLimit. */ - uint64_t retryMs; /**< @brief First retry time for a Shadow get or update message. See IotMqttPublishInfo_t.retryMs. */ - - union - { - /* Valid for Shadow get. */ - struct - { - /** - * @brief Function to allocate memory for an incoming Shadow document. - * - * This only needs to be set if #AWS_IOT_SHADOW_FLAG_WAITABLE is passed to - * @ref shadow_function_get. - */ - void *( *mallocDocument )( size_t ); - } get; /**< @brief Valid members for @ref shadow_function_get. */ - - /* Valid for Shadow update. */ - struct - { - const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ - size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ - } update; /**< @brief Valid members for @ref shadow_function_update. */ - }; -} AwsIotShadowDocumentInfo_t; - -/*------------------------ Shadow defined constants -------------------------*/ - -/** - * @constantspage{shadow,Shadow library} - * - * @section shadow_constants_initializers Shadow Initializers - * @brief Provides default values for the data types of the Shadow library. - * - * @snippet this define_shadow_initializers - * - * All user-facing data types of the Shadow library should be initialized - * using one of the following. - * - * @warning Failing to initialize a Shadow data type with the appropriate - * initializer may result in undefined behavior! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - * - * Example - * @code{c} - * AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - * AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; - * @endcode - * - * @section shadow_constants_flags Shadow Function Flags - * @brief Flags that modify the behavior of Shadow library functions. - * - * Flags should be bitwise-ORed with each other to change the behavior of - * Shadow library functions. - * - * The following flags are valid for the Shadow operation functions: - * @ref shadow_function_delete, @ref shadow_function_get, @ref shadow_function_update, - * and their Timed variants. - * - #AWS_IOT_SHADOW_FLAG_WAITABLE
- * @copybrief AWS_IOT_SHADOW_FLAG_WAITABLE - * - #AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS - * - * The following flags are valid for @ref shadow_function_removepersistentsubscriptions. - * These flags are not valid for the Shadow operation functions. - * - #AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS - * - #AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS - * - #AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS - * - * @note The values of the flags may change at any time in future versions, but - * their names will remain the same. Additionally, flags which may be used at - * the same time will be bitwise-exclusive of each other. - */ - -/* @[define_shadow_initializers] */ -#define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ -#define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ -#define AWS_IOT_SHADOW_REFERENCE_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowReference_t. */ -/* @[define_shadow_initializers] */ - -/** - * @brief Allows the use of @ref shadow_function_wait for blocking until completion. - * - * This flag is only valid if passed to the functions @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update. - * - * An #AwsIotShadowReference_t MUST be provided if this flag is set. - * Additionally, an #AwsIotShadowCallbackInfo_t MUST NOT be provided. - * - * @note If this flag is set, @ref shadow_function_wait MUST be called to - * clean up resources. - */ -#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001 ) - -/** - * @brief Maintain the subscriptions for the Shadow operation topics, even after - * this function returns. - * - * This flag is only valid if passed to the functions @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update. - * - * The Shadow service reports results of Shadow operations by publishing - * messages to MQTT topics. By default, the functions @ref shadow_function_delete, - * @ref shadow_function_get, and @ref shadow_function_update subscribe to the - * necessary topics, wait for the Shadow service to publish the result of the - * Shadow operation, then unsubscribe from those topics. This workflow is suitable - * for infrequent Shadow operations, but is inefficient for frequent, periodic - * Shadow operations (where subscriptions for the Shadow operation topics would be - * constantly added and removed). - * - * This flag causes @ref shadow_function_delete, @ref shadow_function_get, or - * @ref shadow_function_update to maintain Shadow operation topic subscriptions, - * even after the function returns. These subscriptions may then be used by a - * future call to the same function. - * - * This flags only needs to be set once, after which subscriptions are maintained - * and reused for a specific Thing Name and Shadow function. The function @ref - * shadow_function_removepersistentsubscriptions may be used to remove - * subscriptions maintained by this flag. - */ -#define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) - -/** - * @brief Remove the persistent subscriptions from a Shadow delete operation. - * - * This flag is only valid if passed to the function @ref - * shadow_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref shadow_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_delete. - * - * @warning Do not call @ref shadow_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Shadow delete operations. - */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001 ) - -/** - * @brief Remove the persistent subscriptions from a Shadow get operation. - * - * This flag is only valid if passed to the function @ref - * shadow_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref shadow_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_get. - * - * @warning Do not call @ref shadow_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Shadow get operations. - */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002 ) - -/** - * @brief Remove the persistent subscriptions from a Shadow update operation. - * - * This flag is only valid if passed to the function @ref - * shadow_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref shadow_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_update. - * - * @warning Do not call @ref shadow_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Shadow update operations. - */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004 ) +/* Shadow types include. */ +#include "types/aws_iot_shadow_types.h" /*------------------------ Shadow library functions -------------------------*/ @@ -699,11 +112,11 @@ void AwsIotShadow_Cleanup( void ); */ /* @[declare_shadow_delete] */ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - AwsIotShadowReference_t * const pDeleteRef ); + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowReference_t * pDeleteRef ); /* @[declare_shadow_delete] */ /** @@ -711,7 +124,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, */ /* @[declare_shadow_timeddelete] */ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, uint64_t timeoutMs ); @@ -723,10 +136,10 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection */ /* @[declare_shadow_get] */ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pGetInfo, + const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - AwsIotShadowReference_t * const pGetRef ); + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowReference_t * pGetRef ); /* @[declare_shadow_get] */ /** @@ -734,11 +147,11 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, */ /* @[declare_shadow_timedget] */ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pGetInfo, + const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, uint64_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ); + const char ** pShadowDocument, + size_t * pShadowDocumentLength ); /* @[declare_shadow_timedget] */ /** @@ -747,10 +160,10 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, */ /* @[declare_shadow_update] */ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - AwsIotShadowReference_t * const pUpdateRef ); + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowReference_t * pUpdateRef ); /* @[declare_shadow_update] */ /** @@ -758,7 +171,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, */ /* @[declare_shadow_timedupdate] */ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, uint64_t timeoutMs ); /* @[declare_shadow_timedupdate] */ @@ -880,8 +293,8 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection /* @[declare_shadow_wait] */ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, uint64_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ); + const char ** pShadowDocument, + size_t * pShadowDocumentLength ); /* @[declare_shadow_wait] */ /** @@ -1003,10 +416,10 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, */ /* @[declare_shadow_setdeltacallback] */ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pDeltaCallback ); + const AwsIotShadowCallbackInfo_t * pDeltaCallback ); /* @[declare_shadow_setdeltacallback] */ /** @@ -1014,10 +427,10 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConne */ /* @[declare_shadow_setupdatedcallback] */ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pUpdatedCallback ); + const AwsIotShadowCallbackInfo_t * pUpdatedCallback ); /* @[declare_shadow_setupdatedcallback] */ /** @@ -1027,7 +440,7 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon */ /* @[declare_shadow_removepersistentsubscriptions] */ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags ); /* @[declare_shadow_removepersistentsubscriptions] */ diff --git a/lib/include/iot_json_utils.h b/lib/include/iot_json_utils.h index 4f0160e38c..9b7464e963 100644 --- a/lib/include/iot_json_utils.h +++ b/lib/include/iot_json_utils.h @@ -31,11 +31,11 @@ #include #include -bool IotJsonUtils_FindJsonValue( const char * const pJsonDocument, +bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, size_t jsonDocumentLength, - const char * const pJsonKey, + const char * pJsonKey, size_t jsonKeyLength, - const char ** const pJsonValue, - size_t * const pJsonValueLength ); + const char ** pJsonValue, + size_t * pJsonValueLength ); #endif /* ifndef _IOT_JSON_UTILS_H_ */ diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 44bfc9e132..9e0bde568d 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -21,7 +21,7 @@ /** * @file iot_mqtt.h - * @brief User-facing functions and structs of the MQTT 3.1.1 library. + * @brief User-facing functions of the MQTT 3.1.1 library. */ #ifndef _IOT_MQTT_H_ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index c3d18be4f9..f9eb7f60aa 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -324,7 +324,7 @@ struct _shadowSubscription; * @ref mqtt_function_timedunsubscribe. */ typedef IotMqttError_t ( * _mqttOperationFunction_t )( IotMqttConnection_t, - const IotMqttSubscription_t * const, + const IotMqttSubscription_t *, size_t, uint32_t, uint64_t ); @@ -333,7 +333,7 @@ typedef IotMqttError_t ( * _mqttOperationFunction_t )( IotMqttConnection_t, * @brief Function pointer representing an MQTT library callback function. */ typedef void ( * _mqttCallbackFunction_t )( void *, - IotMqttCallbackParam_t * const ); + IotMqttCallbackParam_t * ); /** * @brief Enumerations representing each of the Shadow library's API functions. @@ -469,10 +469,10 @@ extern IotMutex_t _AwsIotShadowSubscriptionsMutex; * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY */ -AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** const pNewOperation, +AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOperation, _shadowOperationType_t operation, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo ); + const AwsIotShadowCallbackInfo_t * pCallbackInfo ); /** * @brief Free resources used to record a Shadow operation. This is called when @@ -504,10 +504,10 @@ void _AwsIotShadow_DestroyOperation( void * pData ); * will not return #AWS_IOT_SHADOW_NO_MEMORY when a buffer is provided. */ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t type, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, - char ** const pTopicBuffer, - uint16_t * const pOperationTopicLength ); + char ** pTopicBuffer, + uint16_t * pOperationTopicLength ); /** * @brief Process a Shadow operation by sending the necessary MQTT packets. @@ -523,10 +523,10 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. */ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, - _shadowOperation_t * const pOperation, - const AwsIotShadowDocumentInfo_t * const pDocumentInfo ); + _shadowOperation_t * pOperation, + const AwsIotShadowDocumentInfo_t * pDocumentInfo ); /** * @brief Notify of a completed Shadow operation. @@ -537,7 +537,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn * notification will cause @ref shadow_function_wait to return or invoke a * user-provided callback. */ -void _AwsIotShadow_Notify( _shadowOperation_t * const pOperation ); +void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ); /*---------------------- Shadow subscription functions ----------------------*/ @@ -554,7 +554,7 @@ void _AwsIotShadow_Notify( _shadowOperation_t * const pOperation ); * * @note This function should be called with the subscription list mutex locked. */ -_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * const pThingName, +_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, size_t thingNameLength ); /** @@ -569,8 +569,8 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * const pThin * * @note This function should be called with the subscription list mutex locked. */ -void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * const pSubscription, - _shadowSubscription_t ** const pRemovedSubscription ); +void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, + _shadowSubscription_t ** pRemovedSubscription ); /** * @brief Free resources used for a Shadow subscription object. @@ -602,8 +602,8 @@ void _AwsIotShadow_DestroySubscription( void * pData ); * * @note This function should be called with the subscription list mutex locked. */ -AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, +AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation, + char * pTopicBuffer, uint16_t operationTopicLength, _mqttCallbackFunction_t callback ); @@ -626,9 +626,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * cons * * @note This function should be called with the subscription list mutex locked. */ -void _AwsIotShadow_DecrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, - _shadowSubscription_t ** const pRemovedSubscription ); +void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, + char * pTopicBuffer, + _shadowSubscription_t ** pRemovedSubscription ); /*------------------------- Shadow parser functions -------------------------*/ @@ -640,7 +640,7 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * const pOperation, * * @return Any #_shadowOperationStatus_t. */ -_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * const pTopicName, +_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicName, size_t topicNameLength ); /** @@ -653,10 +653,10 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * const pTo * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_RESPONSE. */ -AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * const pTopicName, +AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, uint16_t topicNameLength, - const char ** const pThingName, - size_t * const pThingNameLength ); + const char ** pThingName, + size_t * pThingNameLength ); /** * @brief Parse a Shadow error document. @@ -667,7 +667,7 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * const pTopicName, * @return One of the #AwsIotShadowError_t ranging from 400 to 500 on success. * #AWS_IOT_SHADOW_BAD_RESPONSE on error. */ -AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * const pErrorDocument, +AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ); #endif /* ifndef _AWS_IOT_SHADOW_INTERNAL_H_ */ diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h new file mode 100644 index 0000000000..c39a1bc6ca --- /dev/null +++ b/lib/include/types/aws_iot_shadow_types.h @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_shadow_types.h + * @brief Types of the Thing Shadow library. + */ + +#ifndef _AWS_IOT_SHADOW_TYPES_H_ +#define _AWS_IOT_SHADOW_TYPES_H_ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + +/*--------------------------- Shadow handle types ---------------------------*/ + +/** + * @handles{shadow,Shadow library} + */ + +/** + * @ingroup shadow_datatypes_handles + * @brief Opaque handle that references an in-progress Shadow operation. + * + * Set as an output parameter of @ref shadow_function_delete, @ref shadow_function_get, + * and @ref shadow_function_update. These functions send a message to the Shadow + * service requesting a Shadow operation; the result of this operation is unknown + * until the Shadow service sends a response. Therefore, this handle serves as a + * reference to Shadow operations awaiting a response from the Shadow service. + * + * This reference will be valid from the successful return of @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update. The reference becomes + * invalid once the [completion callback](@ref AwsIotShadowCallbackInfo_t) is invoked, + * or @ref shadow_function_wait returns. + * + * @initializer{AwsIotShadowReference_t,AWS_IOT_SHADOW_REFERENCE_INITIALIZER} + * + * @see @ref shadow_function_wait and #AWS_IOT_SHADOW_FLAG_WAITABLE for waiting on + * a reference. #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an + * asynchronous notification of completion. + */ +typedef void * AwsIotShadowReference_t; + +/*------------------------- Shadow enumerated types -------------------------*/ + +/** + * @enums{shadow,Shadow library} + */ + +/** + * @ingroup shadow_datatypes_enums + * @brief Return codes of [Shadow functions](@ref shadow_functions). + * + * The function @ref shadow_function_strerror can be used to get a return code's + * description. + * + * The values between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) and 500 + * (#AWS_IOT_SHADOW_SERVER_ERROR) may be returned by the Shadow service when it + * rejects a Shadow operation. See [this page] + * (https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-error-messages.html) + * for more information. + */ +typedef enum AwsIotShadowError +{ + /** + * @brief Shadow operation completed successfully. + * + * Functions that may return this value: + * - @ref shadow_function_init + * - @ref shadow_function_wait + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + * - @ref shadow_function_removepersistentsubscriptions + * + * Will also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + * when successful. + */ + AWS_IOT_SHADOW_SUCCESS = 0, + + /** + * @brief Shadow operation queued, awaiting result. + * + * Functions that may return this value: + * - @ref shadow_function_delete + * - @ref shadow_function_get + * - @ref shadow_function_update + */ + AWS_IOT_SHADOW_STATUS_PENDING, + + /** + * @brief Initialization failed. + * + * Functions that may return this value: + * - @ref shadow_function_init + */ + AWS_IOT_SHADOW_INIT_FAILED, + + /** + * @brief At least one parameter is invalid. + * + * Functions that may return this value: + * - @ref shadow_function_delete and @ref shadow_function_timeddelete + * - @ref shadow_function_get and @ref shadow_function_timedget + * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + */ + AWS_IOT_SHADOW_BAD_PARAMETER, + + /** + * @brief Shadow operation failed because of memory allocation failure. + * + * Functions that may return this value: + * - @ref shadow_function_delete and @ref shadow_function_timeddelete + * - @ref shadow_function_get and @ref shadow_function_timedget + * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + */ + AWS_IOT_SHADOW_NO_MEMORY, + + /** + * @brief Shadow operation failed because of failure in MQTT library. + * + * Check the Shadow library logs for the error code returned by the MQTT + * library. + * + * Functions that may return this value: + * - @ref shadow_function_delete and @ref shadow_function_timeddelete + * - @ref shadow_function_get and @ref shadow_function_timedget + * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + * - @ref shadow_function_removepersistentsubscriptions + */ + AWS_IOT_SHADOW_MQTT_ERROR, + + /** + * @brief Reponse received from Shadow service not understood. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_BAD_RESPONSE, + + /** + * @brief A blocking Shadow operation timed out. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + */ + AWS_IOT_SHADOW_TIMEOUT, + + /** + * @brief Shadow operation rejected: Bad request. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_BAD_REQUEST = 400, + + /** + * @brief Shadow operation rejected: Unauthorized. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_UNAUTHORIZED = 401, + + /** + * @brief Shadow operation rejected: Forbidden. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_FORBIDDEN = 403, + + /** + * @brief Shadow operation rejected: Thing not found. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_NOT_FOUND = 404, + + /** + * @brief Shadow operation rejected: Version conflict. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_CONFLICT = 409, + + /** + * @brief Shadow operation rejected: The payload exceeds the maximum size + * allowed. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_TOO_LARGE = 413, + + /** + * @brief Shadow operation rejected: Unsupported document encoding; supported + * encoding is UTF-8. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_UNSUPPORTED = 415, + + /** + * @brief Shadow operation rejected: The Device Shadow service will generate + * this error message when there are more than 10 in-flight requests. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_TOO_MANY_REQUESTS = 429, + + /** + * @brief Shadow operation rejected: Internal service failure. + * + * Functions that may return this value: + * - @ref shadow_function_timeddelete + * - @ref shadow_function_timedget + * - @ref shadow_function_timedupdate + * - @ref shadow_function_wait + * + * May also be the value of a Shadow operation completion callback's
+ * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) + */ + AWS_IOT_SHADOW_SERVER_ERROR = 500, +} AwsIotShadowError_t; + +/** + * @ingroup shadow_datatypes_enums + * @brief Types of Shadow library callbacks. + * + * One of these values will be placed in #AwsIotShadowCallbackParam_t.callbackType + * to identify the reason for invoking a callback function. + */ +typedef enum AwsIotShadowCallbackType +{ + AWS_IOT_SHADOW_DELETE_COMPLETE, /**< Callback invoked because a [Shadow delete](@ref shadow_function_delete) completed. */ + AWS_IOT_SHADOW_GET_COMPLETE, /**< Callback invoked because a [Shadow get](@ref shadow_function_get) completed. */ + AWS_IOT_SHADOW_UPDATE_COMPLETE, /**< Callback invoked because a [Shadow update](@ref shadow_function_update) completed. */ + AWS_IOT_SHADOW_DELTA_CALLBACK, /**< Callback invoked for an incoming message on a [Shadow delta](@ref shadow_function_setdeltacallback) topic. */ + AWS_IOT_SHADOW_UPDATED_CALLBACK /**< Callback invoked for an incoming message on a [Shadow updated](@ref shadow_function_setupdatedcallback) topic. */ +} AwsIotShadowCallbackType_t; + +/*------------------------- Shadow parameter structs ------------------------*/ + +/** + * @paramstructs{shadow,Shadow} + */ + +/** + * @ingroup shadow_datatypes_paramstructs + * @brief Parameter to a Shadow callback function. + * + * @paramfor Shadow callback functions + * + * The Shadow library passes this struct to a callback function whenever a + * Shadow operation completes or a message is received on a Shadow delta or + * updated topic. + * + * The valid members of this struct are different based on + * #AwsIotShadowCallbackParam_t.callbackType. If the callback type is + * #AWS_IOT_SHADOW_DELETE_COMPLETE, #AWS_IOT_SHADOW_GET_COMPLETE, or + * #AWS_IOT_SHADOW_UPDATE_COMPLETE, then #AwsIotShadowCallbackParam_t.operation + * is valid. Otherwise, if the callback type is #AWS_IOT_SHADOW_DELTA_CALLBACK + * or #AWS_IOT_SHADOW_UPDATED_CALLBACK, then #AwsIotShadowCallbackParam_t.callback + * is valid. + * + * @attention Any pointers in this callback parameter may be freed as soon as the + * [callback function](@ref AwsIotShadowCallbackInfo_t.function) returns. Therefore, + * data must be copied if it is needed after the callback function returns. + * @attention The Shadow library may set strings that are not NULL-terminated. + * + * @see #AwsIotShadowCallbackInfo_t for the signature of a callback function. + */ +typedef struct AwsIotShadowCallbackParam +{ + AwsIotShadowCallbackType_t callbackType; /**< @brief Reason for invoking the Shadow callback function. */ + + const char * pThingName; /**< @brief The Thing Name associated with this Shadow callback. */ + size_t thingNameLength; /**< @brief Length of #AwsIotShadowCallbackParam_t.pThingName. */ + + union + { + /* Valid for completed Shadow operations. */ + struct + { + /* Valid for a completed Shadow GET operation. */ + struct + { + const char * pDocument; /**< @brief Retrieved Shadow document. */ + size_t documentLength; /**< @brief Length of retrieved Shadow document. */ + } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_get). */ + + AwsIotShadowError_t result; /**< @brief Result of Shadow operation, e.g. succeeded or failed. */ + AwsIotShadowReference_t reference; /**< @brief Reference to the Shadow operation that completed. */ + } operation; /**< @brief Information on a completed Shadow operation. */ + + /* Valid for a message on a Shadow delta or updated topic. */ + struct + { + const char * pDocument; /**< @brief Shadow delta or updated document. */ + size_t documentLength; /**< @brief Length of Shadow delta or updated document. */ + } callback; /**< @brief Shadow document from an incoming delta or updated topic. */ + }; +} AwsIotShadowCallbackParam_t; + +/** + * @ingroup shadow_datatypes_paramstructs + * @brief Information on a user-provided Shadow callback function. + * + * @paramfor @ref shadow_function_delete, @ref shadow_function_get, @ref + * shadow_function_update, @ref shadow_function_setdeltacallback, @ref + * shadow_function_setupdatedcallback + * + * Provides a function to be invoked when a Shadow operation completes or when a + * Shadow document is received on a callback topic (delta or updated). + * + * @initializer{AwsIotShadowCallbackInfo_t,AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER} + */ +typedef struct AwsIotShadowCallbackInfo +{ + void * param1; /**< @brief The first parameter to pass to the callback function. */ + + /** + * @brief User-provided callback function signature. + * + * @param[in] void* #AwsIotShadowCallbackInfo_t.param1 + * @param[in] AwsIotShadowCallbackParam_t* Details on the outcome of the Shadow + * operation or an incoming Shadow document. + * + * @see #AwsIotShadowCallbackParam_t for more information on the second parameter. + */ + void ( * function )( void *, + AwsIotShadowCallbackParam_t * const ); +} AwsIotShadowCallbackInfo_t; + +/** + * @ingroup shadow_datatypes_paramstructs + * @brief Information on a Shadow document for @ref shadow_function_get or @ref + * shadow_function_update. + * + * @paramfor @ref shadow_function_get, @ref shadow_function_update + * + * The valid members of this struct are different based on whether this struct + * is passed to @ref shadow_function_get or @ref shadow_function_update. When + * passed to @ref shadow_function_get, the `get` member is valid. When passed to + * @ref shadow_function_update, the `update` member is valid. All other members + * must always be set. + * + * @initializer{AwsIotShadowDocumentInfo_t,AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER} + */ +typedef struct AwsIotShadowDocumentInfo +{ + const char * pThingName; /**< @brief The Thing Name associated with this Shadow document. */ + size_t thingNameLength; /**< @brief Length of #AwsIotShadowDocumentInfo_t.pThingName. */ + + IotMqttQos_t qos; /**< @brief QoS when sending a Shadow get or update message. See #IotMqttPublishInfo_t.qos. */ + uint32_t retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #IotMqttPublishInfo_t.retryLimit. */ + uint64_t retryMs; /**< @brief First retry time for a Shadow get or update message. See IotMqttPublishInfo_t.retryMs. */ + + union + { + /* Valid for Shadow get. */ + struct + { + /** + * @brief Function to allocate memory for an incoming Shadow document. + * + * This only needs to be set if #AWS_IOT_SHADOW_FLAG_WAITABLE is passed to + * @ref shadow_function_get. + */ + void *( *mallocDocument )( size_t ); + } get; /**< @brief Valid members for @ref shadow_function_get. */ + + /* Valid for Shadow update. */ + struct + { + const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ + size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ + } update; /**< @brief Valid members for @ref shadow_function_update. */ + }; +} AwsIotShadowDocumentInfo_t; + +/*------------------------ Shadow defined constants -------------------------*/ + +/** + * @constantspage{shadow,Shadow library} + * + * @section shadow_constants_initializers Shadow Initializers + * @brief Provides default values for the data types of the Shadow library. + * + * @snippet this define_shadow_initializers + * + * All user-facing data types of the Shadow library should be initialized + * using one of the following. + * + * @warning Failing to initialize a Shadow data type with the appropriate + * initializer may result in undefined behavior! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + * AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * @endcode + * + * @section shadow_constants_flags Shadow Function Flags + * @brief Flags that modify the behavior of Shadow library functions. + * + * Flags should be bitwise-ORed with each other to change the behavior of + * Shadow library functions. + * + * The following flags are valid for the Shadow operation functions: + * @ref shadow_function_delete, @ref shadow_function_get, @ref shadow_function_update, + * and their Timed variants. + * - #AWS_IOT_SHADOW_FLAG_WAITABLE
+ * @copybrief AWS_IOT_SHADOW_FLAG_WAITABLE + * - #AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS + * + * The following flags are valid for @ref shadow_function_removepersistentsubscriptions. + * These flags are not valid for the Shadow operation functions. + * - #AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS + * - #AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS + * - #AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS + * + * @note The values of the flags may change at any time in future versions, but + * their names will remain the same. Additionally, flags which may be used at + * the same time will be bitwise-exclusive of each other. + */ + +/* @[define_shadow_initializers] */ +#define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ +#define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ +#define AWS_IOT_SHADOW_REFERENCE_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowReference_t. */ +/* @[define_shadow_initializers] */ + +/** + * @brief Allows the use of @ref shadow_function_wait for blocking until completion. + * + * This flag is only valid if passed to the functions @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update. + * + * An #AwsIotShadowReference_t MUST be provided if this flag is set. + * Additionally, an #AwsIotShadowCallbackInfo_t MUST NOT be provided. + * + * @note If this flag is set, @ref shadow_function_wait MUST be called to + * clean up resources. + */ +#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001 ) + +/** + * @brief Maintain the subscriptions for the Shadow operation topics, even after + * this function returns. + * + * This flag is only valid if passed to the functions @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update. + * + * The Shadow service reports results of Shadow operations by publishing + * messages to MQTT topics. By default, the functions @ref shadow_function_delete, + * @ref shadow_function_get, and @ref shadow_function_update subscribe to the + * necessary topics, wait for the Shadow service to publish the result of the + * Shadow operation, then unsubscribe from those topics. This workflow is suitable + * for infrequent Shadow operations, but is inefficient for frequent, periodic + * Shadow operations (where subscriptions for the Shadow operation topics would be + * constantly added and removed). + * + * This flag causes @ref shadow_function_delete, @ref shadow_function_get, or + * @ref shadow_function_update to maintain Shadow operation topic subscriptions, + * even after the function returns. These subscriptions may then be used by a + * future call to the same function. + * + * This flags only needs to be set once, after which subscriptions are maintained + * and reused for a specific Thing Name and Shadow function. The function @ref + * shadow_function_removepersistentsubscriptions may be used to remove + * subscriptions maintained by this flag. + */ +#define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) + +/** + * @brief Remove the persistent subscriptions from a Shadow delete operation. + * + * This flag is only valid if passed to the function @ref + * shadow_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref shadow_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref shadow_function_delete. + * + * @warning Do not call @ref shadow_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Shadow delete operations. + */ +#define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001 ) + +/** + * @brief Remove the persistent subscriptions from a Shadow get operation. + * + * This flag is only valid if passed to the function @ref + * shadow_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref shadow_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref shadow_function_get. + * + * @warning Do not call @ref shadow_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Shadow get operations. + */ +#define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002 ) + +/** + * @brief Remove the persistent subscriptions from a Shadow update operation. + * + * This flag is only valid if passed to the function @ref + * shadow_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref shadow_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref shadow_function_update. + * + * @warning Do not call @ref shadow_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Shadow update operations. + */ +#define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004 ) + +#endif /* ifndef AWS_IOT_SHADOW_TYPES_H_ */ diff --git a/lib/source/common/iot_json_utils.c b/lib/source/common/iot_json_utils.c index a0aacedab6..5c3f9b48a8 100644 --- a/lib/source/common/iot_json_utils.c +++ b/lib/source/common/iot_json_utils.c @@ -37,12 +37,12 @@ /*-----------------------------------------------------------*/ -bool IotJsonUtils_FindJsonValue( const char * const pJsonDocument, +bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, size_t jsonDocumentLength, - const char * const pJsonKey, + const char * pJsonKey, size_t jsonKeyLength, - const char ** const pJsonValue, - size_t * const pJsonValueLength ) + const char ** pJsonValue, + size_t * pJsonValueLength ) { size_t i = 0; size_t jsonValueLength = 0; diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 682b18845e..22f7575c91 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -67,11 +67,11 @@ * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_PARAMETER. */ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - const AwsIotShadowReference_t * const pReference ); + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + const AwsIotShadowReference_t * pReference ); /** * @brief Checks document info parameter passed to Shadow API functions. @@ -100,9 +100,9 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, */ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo ); + const AwsIotShadowCallbackInfo_t * pCallbackInfo ); /** * @brief Change the subscriptions for Shadow callbacks, either by subscribing @@ -116,7 +116,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio */ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, - _shadowSubscription_t * const pSubscription, + _shadowSubscription_t * pSubscription, _mqttOperationFunction_t mqttOperation ); /** @@ -128,8 +128,8 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt * message). */ static void _callbackWrapperCommon( _shadowCallbackType_t type, - const _shadowSubscription_t * const pSubscription, - IotMqttCallbackParam_t * const pMessage ); + const _shadowSubscription_t * pSubscription, + IotMqttCallbackParam_t * pMessage ); /** * @brief Invoked when a document is received on the Shadow DELTA callback. @@ -138,7 +138,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). */ static void _deltaCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * pMessage ); /** * @brief Invoked when a document is received on the Shadow UPDATED callback. @@ -147,7 +147,7 @@ static void _deltaCallbackWrapper( void * pArgument, * @param[in] pMessage The received UPDATED document (as an MQTT PUBLISH message). */ static void _updatedCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * pMessage ); /*-----------------------------------------------------------*/ @@ -171,11 +171,11 @@ uint64_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; /*-----------------------------------------------------------*/ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - const AwsIotShadowReference_t * const pReference ) + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + const AwsIotShadowReference_t * pReference ) { /* Type is not used when logging is disabled. */ ( void ) type; @@ -310,9 +310,9 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo ) + const AwsIotShadowCallbackInfo_t * pCallbackInfo ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; _shadowSubscription_t * pSubscription = NULL; @@ -418,7 +418,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, - _shadowSubscription_t * const pSubscription, + _shadowSubscription_t * pSubscription, _mqttOperationFunction_t mqttOperation ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; @@ -543,8 +543,8 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt /*-----------------------------------------------------------*/ static void _callbackWrapperCommon( _shadowCallbackType_t type, - const _shadowSubscription_t * const pSubscription, - IotMqttCallbackParam_t * const pMessage ) + const _shadowSubscription_t * pSubscription, + IotMqttCallbackParam_t * pMessage ) { AwsIotShadowCallbackParam_t callbackParam = { 0 }; @@ -566,7 +566,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, /*-----------------------------------------------------------*/ static void _deltaCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * pMessage ) { _callbackWrapperCommon( _DELTA_CALLBACK, pArgument, pMessage ); } @@ -574,7 +574,7 @@ static void _deltaCallbackWrapper( void * pArgument, /*-----------------------------------------------------------*/ static void _updatedCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * pMessage ) { _callbackWrapperCommon( _UPDATED_CALLBACK, pArgument, pMessage ); } @@ -646,11 +646,11 @@ void AwsIotShadow_Cleanup( void ) /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - AwsIotShadowReference_t * const pDeleteRef ) + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowReference_t * pDeleteRef ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -710,7 +710,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, uint64_t timeoutMs ) @@ -744,10 +744,10 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pGetInfo, + const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - AwsIotShadowReference_t * const pGetRef ) + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowReference_t * pGetRef ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -819,11 +819,11 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pGetInfo, + const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, uint64_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ) + const char ** pShadowDocument, + size_t * pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowReference_t getRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; @@ -856,10 +856,10 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo, - AwsIotShadowReference_t * const pUpdateRef ) + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowReference_t * pUpdateRef ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -973,7 +973,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * const pUpdateInfo, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, uint64_t timeoutMs ) { @@ -1006,8 +1006,8 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, uint64_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ) + const char ** pShadowDocument, + size_t * pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; _shadowOperation_t * pOperation = ( _shadowOperation_t * ) reference; @@ -1080,10 +1080,10 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pDeltaCallback ) + const AwsIotShadowCallbackInfo_t * pDeltaCallback ) { /* Flags are currently not used by this function. */ ( void ) flags; @@ -1098,10 +1098,10 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConne /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pUpdatedCallback ) + const AwsIotShadowCallbackInfo_t * pUpdatedCallback ) { /* Flags are currently not used by this function. */ ( void ) flags; diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index f4118bec28..8d35187598 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -80,7 +80,7 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _commonOperationCallback( _shadowOperationType_t type, - IotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * pMessage ); /** * @brief Invoked when a Shadow response is received for Shadow DELETE. @@ -89,7 +89,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _deleteCallback( void * pArgument, - IotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * pMessage ); /** * @brief Invoked when a Shadow response is received for a Shadow GET. @@ -98,7 +98,7 @@ static void _deleteCallback( void * pArgument, * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _getCallback( void * pArgument, - IotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * pMessage ); /** * @brief Process an incoming Shadow document received when a Shadow GET is @@ -112,8 +112,8 @@ static void _getCallback( void * pArgument, * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. Memory allocation * only happens for a waitable `pOperation`. */ -static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOperation, - const IotMqttPublishInfo_t * const pPublishInfo ); +static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, + const IotMqttPublishInfo_t * pPublishInfo ); /** * @brief Invoked when a Shadow response is received for a Shadow UPDATE. @@ -122,7 +122,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOper * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). */ static void _updateCallback( void * pArgument, - IotMqttCallbackParam_t * const pMessage ); + IotMqttCallbackParam_t * pMessage ); /*-----------------------------------------------------------*/ @@ -161,11 +161,11 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, * must never be NULL. */ AwsIotShadow_Assert( pOperationLink != NULL ); - _shadowOperation_t * const pOperation = IotLink_Container( _shadowOperation_t, - pOperationLink, - link ); - _operationMatchParams_t * const pParam = ( _operationMatchParams_t * ) pMatch; - _shadowSubscription_t * const pSubscription = pOperation->pSubscription; + _shadowOperation_t * pOperation = IotLink_Container( _shadowOperation_t, + pOperationLink, + link ); + _operationMatchParams_t * pParam = ( _operationMatchParams_t * ) pMatch; + _shadowSubscription_t * pSubscription = pOperation->pSubscription; const char * pClientToken = NULL; size_t clientTokenLength = 0; @@ -219,7 +219,7 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, /*-----------------------------------------------------------*/ static void _commonOperationCallback( _shadowOperationType_t type, - IotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * pMessage ) { _shadowOperation_t * pOperation = NULL; IotLink_t * pOperationLink = NULL; @@ -350,7 +350,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, /*-----------------------------------------------------------*/ static void _deleteCallback( void * pArgument, - IotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; @@ -361,7 +361,7 @@ static void _deleteCallback( void * pArgument, /*-----------------------------------------------------------*/ static void _getCallback( void * pArgument, - IotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; @@ -371,8 +371,8 @@ static void _getCallback( void * pArgument, /*-----------------------------------------------------------*/ -static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOperation, - const IotMqttPublishInfo_t * const pPublishInfo ) +static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, + const IotMqttPublishInfo_t * pPublishInfo ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; @@ -418,7 +418,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * const pOper /*-----------------------------------------------------------*/ static void _updateCallback( void * pArgument, - IotMqttCallbackParam_t * const pMessage ) + IotMqttCallbackParam_t * pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; @@ -428,10 +428,10 @@ static void _updateCallback( void * pArgument, /*-----------------------------------------------------------*/ -AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** const pNewOperation, +AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOperation, _shadowOperationType_t type, uint32_t flags, - const AwsIotShadowCallbackInfo_t * const pCallbackInfo ) + const AwsIotShadowCallbackInfo_t * pCallbackInfo ) { _shadowOperation_t * pOperation = NULL; @@ -522,10 +522,10 @@ void _AwsIotShadow_DestroyOperation( void * pData ) /*-----------------------------------------------------------*/ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t type, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, - char ** const pTopicBuffer, - uint16_t * const pOperationTopicLength ) + char ** pTopicBuffer, + uint16_t * pOperationTopicLength ) { uint16_t bufferLength = 0; uint16_t operationTopicLength = 0; @@ -606,10 +606,10 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty /*-----------------------------------------------------------*/ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, - _shadowOperation_t * const pOperation, - const AwsIotShadowDocumentInfo_t * const pDocumentInfo ) + _shadowOperation_t * pOperation, + const AwsIotShadowDocumentInfo_t * pDocumentInfo ) { _shadowSubscription_t * pSubscription = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -819,7 +819,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn /*-----------------------------------------------------------*/ -void _AwsIotShadow_Notify( _shadowOperation_t * const pOperation ) +void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) { AwsIotShadowCallbackParam_t callbackParam = { 0 }; _shadowSubscription_t * pSubscription = pOperation->pSubscription, diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index 9a12b42cb7..58638a3ad0 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -82,11 +82,11 @@ * @return A corresponding #AwsIotShadowError_t; #AWS_IOT_SHADOW_BAD_RESPONSE * if `code` is unknown. */ -static AwsIotShadowError_t _codeToShadowStatus( unsigned long code ); +static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ); /*-----------------------------------------------------------*/ -static AwsIotShadowError_t _codeToShadowStatus( unsigned long code ) +static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ) { /* Convert the Shadow response code to an AwsIotShadowError_t. */ switch( code ) @@ -135,7 +135,7 @@ static AwsIotShadowError_t _codeToShadowStatus( unsigned long code ) /*-----------------------------------------------------------*/ -_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * const pTopicName, +_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicName, size_t topicNameLength ) { const char * pSuffixStart = NULL; @@ -186,12 +186,12 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * const pTo /*-----------------------------------------------------------*/ -AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * const pErrorDocument, +AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ) { const char * pCode = NULL, * pMessage = NULL; size_t codeLength = 0, messageLength = 0; - unsigned long code = 0; + uint32_t code = 0; /* Parse the code from the error document. */ if( IotJsonUtils_FindJsonValue( pErrorDocument, @@ -214,7 +214,7 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * const pErrorD ( pCode + codeLength < pErrorDocument + errorDocumentLength ) ); /* Convert the code to an unsigned integer value. */ - code = strtoul( pCode, NULL, 10 ); + code = ( uint32_t ) strtoul( pCode, NULL, 10 ); /* Parse the error message and print it. An error document must always contain * a message. */ @@ -246,10 +246,10 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * const pErrorD /*-----------------------------------------------------------*/ -AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * const pTopicName, +AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, uint16_t topicNameLength, - const char ** const pThingName, - size_t * const pThingNameLength ) + const char ** pThingName, + size_t * pThingNameLength ) { const char * pThingNameStart = NULL; size_t thingNameLength = 0; diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 6303fc2d67..b9a00a911a 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -81,7 +81,7 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. */ static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mqttConnection, - const char * const pTopicFilter, + const char * pTopicFilter, uint16_t topicFilterLength, _mqttCallbackFunction_t callback, _mqttOperationFunction_t mqttOperation ); @@ -109,10 +109,10 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, * must never be NULL. */ AwsIotShadow_Assert( pSubscriptionLink != NULL ); - const _shadowSubscription_t * const pSubscription = IotLink_Container( _shadowSubscription_t, - pSubscriptionLink, - link ); - const _thingName_t * const pThingName = ( _thingName_t * ) pMatch; + const _shadowSubscription_t * pSubscription = IotLink_Container( _shadowSubscription_t, + pSubscriptionLink, + link ); + const _thingName_t * pThingName = ( _thingName_t * ) pMatch; if( pThingName->thingNameLength == pSubscription->thingNameLength ) { @@ -128,7 +128,7 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mqttConnection, - const char * const pTopicFilter, + const char * pTopicFilter, uint16_t topicFilterLength, _mqttCallbackFunction_t callback, _mqttOperationFunction_t mqttOperation ) @@ -191,7 +191,7 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mq /*-----------------------------------------------------------*/ -_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * const pThingName, +_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, size_t thingNameLength ) { _shadowSubscription_t * pSubscription = NULL; @@ -252,8 +252,8 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * const pThin /*-----------------------------------------------------------*/ -void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * const pSubscription, - _shadowSubscription_t ** const pRemovedSubscription ) +void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, + _shadowSubscription_t ** pRemovedSubscription ) { int i = 0; @@ -337,15 +337,15 @@ void _AwsIotShadow_DestroySubscription( void * pData ) /*-----------------------------------------------------------*/ -AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, +AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation, + char * pTopicBuffer, uint16_t operationTopicLength, _mqttCallbackFunction_t callback ) { uint16_t topicFilterLength = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; const _shadowOperationType_t type = pOperation->type; - _shadowSubscription_t * const pSubscription = pOperation->pSubscription; + _shadowSubscription_t * pSubscription = pOperation->pSubscription; /* Do nothing if this operation has persistent subscriptions. */ if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) @@ -456,13 +456,13 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * cons /*-----------------------------------------------------------*/ -void _AwsIotShadow_DecrementReferences( _shadowOperation_t * const pOperation, - char * const pTopicBuffer, - _shadowSubscription_t ** const pRemovedSubscription ) +void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, + char * pTopicBuffer, + _shadowSubscription_t ** pRemovedSubscription ) { uint16_t topicFilterLength = 0; const _shadowOperationType_t type = pOperation->type; - _shadowSubscription_t * const pSubscription = pOperation->pSubscription; + _shadowSubscription_t * pSubscription = pOperation->pSubscription; uint16_t operationTopicLength = 0; /* Do nothing if this Shadow operation has persistent subscriptions. */ @@ -548,7 +548,7 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * const pOperation, /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, - const char * const pThingName, + const char * pThingName, size_t thingNameLength, uint32_t flags ) { diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index f2d53b1210..690203ee79 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -33,13 +33,6 @@ #include #include -/* POSIX includes. */ -#ifdef POSIX_UNISTD_HEADER - #include POSIX_UNISTD_HEADER -#else - #include -#endif - /* Common include. */ #include "iot_common.h" diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 4f49455476..42218ba436 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -71,10 +71,10 @@ extern int vsnprintf( char *, * @brief Wrapper for parsing JSON documents and checking the result. */ static void _parseJson( bool expectedResult, - const char * const pJsonDocument, + const char * pJsonDocument, size_t jsonDocumentLength, - const char * const pJsonKey, - const char * const pExpectedJsonValue, + const char * pJsonKey, + const char * pExpectedJsonValue, size_t expectedJsonValueLength ) { const char * pJsonValue = NULL; @@ -100,9 +100,9 @@ static void _parseJson( bool expectedResult, /** * @brief Wrapper for generating and parsing error documents. */ -static void _generateParseErrorDocument( char * const pErrorDocument, +static void _generateParseErrorDocument( char * pErrorDocument, AwsIotShadowError_t expectedCode, - const char * const pFormat, + const char * pFormat, ... ) { int errorDocumentLength = 0; @@ -131,9 +131,9 @@ static void _generateParseErrorDocument( char * const pErrorDocument, /** * @brief Wrapper for parsing Shadow Thing Names and checking the result. */ -static void _parseThingName( const char * const pTopicName, +static void _parseThingName( const char * pTopicName, AwsIotShadowError_t expectedResult, - const char * const pExpectedThingName ) + const char * pExpectedThingName ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; uint16_t topicNameLength = ( uint16_t ) strlen( pTopicName ); From c03d4f40e038cf1f64e552b0275c4a7f05acedb9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 18 Mar 2019 20:28:53 -0700 Subject: [PATCH 053/844] Rename "reference" to "operation". (#327) --- lib/include/aws_iot_shadow.h | 24 +-- lib/include/iot_mqtt.h | 34 ++-- lib/include/private/iot_mqtt_internal.h | 14 +- lib/include/types/aws_iot_shadow_types.h | 12 +- lib/include/types/iot_mqtt_types.h | 18 +- lib/source/mqtt/iot_mqtt_api.c | 183 +++++++++--------- lib/source/mqtt/iot_mqtt_operation.c | 54 +++--- lib/source/mqtt/iot_mqtt_validate.c | 11 +- lib/source/shadow/aws_iot_shadow_api.c | 93 +++++---- tests/mqtt/system/iot_tests_mqtt_stress.c | 6 +- tests/mqtt/system/iot_tests_mqtt_system.c | 22 +-- tests/mqtt/unit/iot_tests_mqtt_api.c | 40 ++-- tests/mqtt/unit/iot_tests_mqtt_receive.c | 2 +- tests/mqtt/unit/iot_tests_mqtt_validate.c | 39 ++-- .../system/aws_iot_tests_shadow_system.c | 10 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 34 ++-- 16 files changed, 300 insertions(+), 296 deletions(-) diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index fe5f0cb357..49fafe1bad 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -116,7 +116,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, size_t thingNameLength, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowReference_t * pDeleteRef ); + AwsIotShadowOperation_t * pDeleteOperation ); /* @[declare_shadow_delete] */ /** @@ -139,7 +139,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowReference_t * pGetRef ); + AwsIotShadowOperation_t * pGetOperation ); /* @[declare_shadow_get] */ /** @@ -163,7 +163,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowReference_t * pUpdateRef ); + AwsIotShadowOperation_t * pUpdateOperation ); /* @[declare_shadow_update] */ /** @@ -190,11 +190,11 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * be called with any waitable operation to clean up resources. * * Regardless of its return value, this function always clean up resources used - * by the waitable operation. This means `reference` is invalidated as soon as + * by the waitable operation. This means `operation` is invalidated as soon as * this function returns, even if it returns #AWS_IOT_SHADOW_TIMEOUT or another * error. * - * @param[in] reference Reference to the Shadow operation to wait for. The flag + * @param[in] operation Reference to the Shadow operation to wait for. The flag * #AWS_IOT_SHADOW_FLAG_WAITABLE must have been set for this operation. * @param[in] timeoutMs How long to wait before returning #AWS_IOT_SHADOW_TIMEOUT. * @param[out] pShadowDocument A pointer to a buffer containing the Shadow document @@ -222,7 +222,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * AwsIotShadowDocumentInfo_t updateInfo = { ... }; * * // Reference and timeout. - * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; * uint64_t timeout = 5000; // 5 seconds * * // Shadow update operation. @@ -230,14 +230,14 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * &updateInfo, * AWS_IOT_SHADOW_FLAG_WAITABLE, * NULL, - * &reference ); + * &updateOperation ); * * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait * // returns once the result of the update is available or the timeout expires. * if( result == AWS_IOT_SHADOW_STATUS_PENDING ) * { * // The last two parameters are ignored for a Shadow update. - * result = AwsIotShadow_Wait( reference, timeout, NULL, NULL ); + * result = AwsIotShadow_Wait( updateOperation, timeout, NULL, NULL ); * * // After the call to wait, the result of the update is known * // (not AWS_IOT_SHADOW_STATUS_PENDING). @@ -251,7 +251,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * AwsIotShadowDocumentInfo_t getInfo = { ... }; * * // Reference and timeout. - * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; * uint64_t timeout = 5000; // 5 seconds * * // Buffer pointer and size for retrieved Shadow document. @@ -266,14 +266,14 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * &getInfo, * AWS_IOT_SHADOW_FLAG_WAITABLE, * NULL, - * &reference ); + * &getOperation ); * * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait * // returns once the result of the get is available or the timeout expires. * if( result == AWS_IOT_SHADOW_STATUS_PENDING ) * { * // The last two parameters must be set for a Shadow get. - * result = AwsIotShadow_Wait( reference, timeout, &pShadowDocument, &documentLength ); + * result = AwsIotShadow_Wait( getOperation, timeout, &pShadowDocument, &documentLength ); * * // After the call to wait, the result of the get is known * // (not AWS_IOT_SHADOW_STATUS_PENDING). @@ -291,7 +291,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * @endcode */ /* @[declare_shadow_wait] */ -AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, +AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, uint64_t timeoutMs, const char ** pShadowDocument, size_t * pShadowDocumentLength ); diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 9e0bde568d..6256d957a5 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -326,7 +326,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pSubscribeRef Set to a handle by which this operation may be + * @param[out] pSubscribeOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the subscription operation completes. * @@ -358,7 +358,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * * // Subscription information. * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - * IotMqttReference_t lastOperation = IOT_MQTT_REFERENCE_INITIALIZER; + * IotMqttOperation_t lastOperation = IOT_MQTT_OPERATION_INITIALIZER; * * // Set the subscription information. * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) @@ -425,7 +425,7 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pSubscribeRef ); + IotMqttOperation_t * pSubscribeOperation ); /* @[declare_mqtt_subscribe] */ /** @@ -485,7 +485,7 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pUnsubscribeRef Set to a handle by which this operation may be + * @param[out] pUnsubscribeOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the unsubscribe operation completes. * @@ -510,7 +510,7 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pUnsubscribeRef ); + IotMqttOperation_t * pUnsubscribeOperation ); /* @[declare_mqtt_unsubscribe] */ /** @@ -568,7 +568,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * @param[in] pPublishInfo MQTT publish parameters. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pPublishRef Set to a handle by which this operation may be + * @param[out] pPublishOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the publish operation completes. * @@ -588,7 +588,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - * @note The parameters `pCallbackInfo` and `pPublishRef` should only be used for QoS + * @note The parameters `pCallbackInfo` and `pPublishOperation` should only be used for QoS * 1 publishes. For QoS 0, they should both be `NULL`. * * @see @ref mqtt_function_timedpublish for a blocking variant of this function. @@ -616,7 +616,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * NULL ); * * // QoS 1 with retry example (using same topic name and payload as QoS 0 example): - * IotMqttReference_t qos1Reference = IOT_MQTT_REFERENCE_INITIALIZER; + * IotMqttOperation_t qos1Operation = IOT_MQTT_OPERATION_INITIALIZER; * publishInfo.qos = IOT_MQTT_QOS_1; * publishInfo.retryMs = 1000; // Retry if no response is received in 1 second. * publishInfo.retryLimit = 5; // Retry up to 5 times. @@ -626,12 +626,12 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * &publishInfo, * IOT_MQTT_FLAG_WAITABLE, * NULL, - * &qos1Reference ); + * &qos1Operation ); * * // Wait up to 5 seconds for the publish to complete. * if( qos1Result == IOT_MQTT_STATUS_PENDING ) * { - * qos1Result = IotMqtt_Wait( qos1Reference, 5000 ); + * qos1Result = IotMqtt_Wait( qos1Operation, 5000 ); * } * @endcode */ @@ -640,7 +640,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pPublishRef ); + IotMqttOperation_t * pPublishOperation ); /* @[declare_mqtt_publish] */ /** @@ -697,7 +697,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, * by the waitable operation. This means `reference` is invalidated as soon as * this function returns, even if it returns #IOT_MQTT_TIMEOUT or another error. * - * @param[in] reference Reference to the operation to wait for. The flag + * @param[in] operation Reference to the operation to wait for. The flag * #IOT_MQTT_FLAG_WAITABLE must have been set for this operation. * @param[in] timeoutMs How long to wait before returning #IOT_MQTT_TIMEOUT. * @@ -706,8 +706,8 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, * * Example * @code{c} - * // Reference and timeout. - * IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; + * // Operation reference and timeout. + * IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; * uint64_t timeoutMs = 5000; // 5 seconds * * // MQTT operation to wait for. @@ -715,13 +715,13 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, * &publishInfo, * IOT_MQTT_FLAG_WAITABLE, * NULL, - * &reference ); + * &publishOperation ); * * // Publish should have returned IOT_MQTT_STATUS_PENDING. The call to wait * // returns once the result of the publish is available or the timeout expires. * if( result == IOT_MQTT_STATUS_PENDING ) * { - * result = IotMqtt_Wait( reference, timeoutMs ); + * result = IotMqtt_Wait( publishOperation, timeoutMs ); * * // After the call to wait, the result of the publish is known * // (not IOT_MQTT_STATUS_PENDING). @@ -730,7 +730,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, * @endcode */ /* @[declare_mqtt_wait] */ -IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, +IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, uint64_t timeoutMs ); /* @[declare_mqtt_wait] */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 664c550368..40d4fda0bb 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -348,7 +348,7 @@ typedef struct _mqttOperation { /* Basic operation information. */ int32_t jobReference; /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */ - IotMqttOperationType_t operation; /**< @brief What operation this structure represents. */ + IotMqttOperationType_t type; /**< @brief What operation this structure represents. */ uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ @@ -435,13 +435,13 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, const IotMqttPublishInfo_t * pPublishInfo ); /** - * @brief Check that an #IotMqttReference_t is valid and waitable. + * @brief Check that an #IotMqttOperation_t is valid and waitable. * - * @param[in] reference The #IotMqttReference_t to validate. + * @param[in] operation The #IotMqttOperation_t to validate. * - * @return `true` if `reference` is valid; `false` otherwise. + * @return `true` if `operation` is valid; `false` otherwise. */ -bool _IotMqtt_ValidateReference( IotMqttReference_t reference ); +bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ); /** * @brief Check that a list of #IotMqttSubscription_t is valid. @@ -814,13 +814,13 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, * name and packet identifier. Removes a matching operation from the list if found. * * @param[in] pMqttConnection The connection associated with the operation. - * @param[in] operation The operation type to look for. + * @param[in] type The operation type to look for. * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore. * * @return Pointer to any matching operation; `NULL` if no match was found. */ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, - IotMqttOperationType_t operation, + IotMqttOperationType_t type, const uint16_t * pPacketIdentifier ); /** diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index c39a1bc6ca..6beba6b92a 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -56,13 +56,13 @@ * invalid once the [completion callback](@ref AwsIotShadowCallbackInfo_t) is invoked, * or @ref shadow_function_wait returns. * - * @initializer{AwsIotShadowReference_t,AWS_IOT_SHADOW_REFERENCE_INITIALIZER} + * @initializer{AwsIotShadowOperation_t,AWS_IOT_SHADOW_OPERATION_INITIALIZER} * * @see @ref shadow_function_wait and #AWS_IOT_SHADOW_FLAG_WAITABLE for waiting on * a reference. #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an * asynchronous notification of completion. */ -typedef void * AwsIotShadowReference_t; +typedef struct _shadowOperation * AwsIotShadowOperation_t; /*------------------------- Shadow enumerated types -------------------------*/ @@ -387,7 +387,7 @@ typedef struct AwsIotShadowCallbackParam } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_get). */ AwsIotShadowError_t result; /**< @brief Result of Shadow operation, e.g. succeeded or failed. */ - AwsIotShadowReference_t reference; /**< @brief Reference to the Shadow operation that completed. */ + AwsIotShadowOperation_t reference; /**< @brief Reference to the Shadow operation that completed. */ } operation; /**< @brief Information on a completed Shadow operation. */ /* Valid for a message on a Shadow delta or updated topic. */ @@ -498,7 +498,7 @@ typedef struct AwsIotShadowDocumentInfo * @code{c} * AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; * AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - * AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + * AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; * @endcode * * @section shadow_constants_flags Shadow Function Flags @@ -532,7 +532,7 @@ typedef struct AwsIotShadowDocumentInfo /* @[define_shadow_initializers] */ #define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ #define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ -#define AWS_IOT_SHADOW_REFERENCE_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowReference_t. */ +#define AWS_IOT_SHADOW_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowOperation_t. */ /* @[define_shadow_initializers] */ /** @@ -541,7 +541,7 @@ typedef struct AwsIotShadowDocumentInfo * This flag is only valid if passed to the functions @ref shadow_function_delete, * @ref shadow_function_get, or @ref shadow_function_update. * - * An #AwsIotShadowReference_t MUST be provided if this flag is set. + * An #AwsIotShadowOperation_t MUST be provided if this flag is set. * Additionally, an #AwsIotShadowCallbackInfo_t MUST NOT be provided. * * @note If this flag is set, @ref shadow_function_wait MUST be called to diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index fb76c4cdcb..6df1450fb8 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -81,13 +81,13 @@ typedef struct _mqttConnection * IotMqttConnection_t; * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or * @ref mqtt_function_wait returns. * - * @initializer{IotMqttReference_t,IOT_MQTT_REFERENCE_INITIALIZER} + * @initializer{IotMqttOperation_t,IOT_MQTT_OPERATION_INITIALIZER} * * @see @ref mqtt_function_wait and #IOT_MQTT_FLAG_WAITABLE for waiting on a reference. * #IotMqttCallbackInfo_t and #IotMqttCallbackParam_t for an asynchronous notification * of completion. */ -typedef struct _mqttOperation * IotMqttReference_t; +typedef struct _mqttOperation * IotMqttOperation_t; /*-------------------------- MQTT enumerated types --------------------------*/ @@ -223,7 +223,7 @@ typedef enum IotMqttError * * Functions that may return this value: * - @ref mqtt_function_connect - * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter + * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter * is associated with a SUBSCRIBE operation. * - @ref mqtt_function_timedsubscribe * @@ -244,7 +244,7 @@ typedef enum IotMqttError * (#IotMqttPublishInfo_t.retryLimit) was reached. * * Functions that may return this value: - * - @ref mqtt_function_wait, but only when its #IotMqttReference_t parameter + * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter * is associated with a QoS 1 PUBLISH operation * - @ref mqtt_function_timedpublish * @@ -447,7 +447,7 @@ typedef struct IotMqttCallbackParam struct { IotMqttOperationType_t type; /**< @brief Type of operation that completed. */ - IotMqttReference_t reference; /**< @brief Reference to the operation that completed. */ + IotMqttOperation_t reference; /**< @brief Reference to the operation that completed. */ IotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ } operation; @@ -1019,7 +1019,7 @@ typedef struct IotMqttNetworkInfo * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; * IotMqttConnection_t connection = IOT_MQTT_CONNECTION_INITIALIZER; - * IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; + * IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; * @endcode * * @section mqtt_constants_flags MQTT Function Flags @@ -1053,8 +1053,8 @@ typedef struct IotMqttNetworkInfo #define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } /** @brief Initializer for #IotMqttConnection_t. */ #define IOT_MQTT_CONNECTION_INITIALIZER NULL -/** @brief Initializer for #IotMqttReference_t. */ -#define IOT_MQTT_REFERENCE_INITIALIZER NULL +/** @brief Initializer for #IotMqttOperation_t. */ +#define IOT_MQTT_OPERATION_INITIALIZER NULL /* @[define_mqtt_initializers] */ /** @@ -1064,7 +1064,7 @@ typedef struct IotMqttNetworkInfo * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. * - * An #IotMqttReference_t MUST be provided if this flag is set. Additionally, an + * An #IotMqttOperation_t MUST be provided if this flag is set. Additionally, an * #IotMqttCallbackInfo_t MUST NOT be provided. * * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index b0609ca50d..7da15d1693 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -139,7 +139,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pSubscriptionRef ); + IotMqttOperation_t * pOperationReference ); /*-----------------------------------------------------------*/ @@ -510,7 +510,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pSubscriptionRef ) + IotMqttOperation_t * pOperationReference ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pSubscriptionOperation = NULL; @@ -542,7 +542,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Check that a reference pointer is provided for a waitable operation. */ if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) { - if( pSubscriptionRef == NULL ) + if( pOperationReference == NULL ) { IotLogError( "Reference must be provided for a waitable %s.", IotMqtt_OperationType( operation ) ); @@ -631,7 +631,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Check the subscription operation data and set the operation type. */ IotMqtt_Assert( pSubscriptionOperation->status == IOT_MQTT_STATUS_PENDING ); IotMqtt_Assert( pSubscriptionOperation->retry.limit == 0 ); - pSubscriptionOperation->operation = operation; + pSubscriptionOperation->type = operation; /* Generate a subscription packet from the subscription list. */ status = serializeSubscription( pSubscriptionList, @@ -664,9 +664,9 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Set the reference, if provided. */ - if( pSubscriptionRef != NULL ) + if( pOperationReference != NULL ) { - *pSubscriptionRef = pSubscriptionOperation; + *pOperationReference = pSubscriptionOperation; } /* Schedule the subscription operation for network transmission. */ @@ -688,9 +688,9 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Clear the previously set (and now invalid) reference. */ - if( pSubscriptionRef != NULL ) + if( pOperationReference != NULL ) { - *pSubscriptionRef = IOT_MQTT_REFERENCE_INITIALIZER; + *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; } _IOT_GOTO_CLEANUP(); @@ -838,7 +838,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; void * pNetworkConnection = NULL; - _mqttOperation_t * pConnectOperation = NULL; + _mqttOperation_t * pOperation = NULL; _mqttConnection_t * pNewMqttConnection = NULL; /* Default CONNECT serializer function. */ @@ -989,7 +989,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = _IotMqtt_CreateOperation( pNewMqttConnection, IOT_MQTT_FLAG_WAITABLE, NULL, - &pConnectOperation ); + &pOperation ); if( status != IOT_MQTT_SUCCESS ) { @@ -1002,13 +1002,13 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Ensure the members set by operation creation and serialization * are appropriate for a blocking CONNECT. */ - IotMqtt_Assert( pConnectOperation->status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pConnectOperation->flags & IOT_MQTT_FLAG_WAITABLE ) + IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pConnectOperation->retry.limit == 0 ); + IotMqtt_Assert( pOperation->retry.limit == 0 ); /* Set the operation type. */ - pConnectOperation->operation = IOT_MQTT_CONNECT; + pOperation->type = IOT_MQTT_CONNECT; /* Add previous session subscriptions. */ if( pConnectInfo->pPreviousSubscriptions != NULL ) @@ -1056,8 +1056,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ status = serializeConnect( pConnectInfo, - &( pConnectOperation->pMqttPacket ), - &( pConnectOperation->packetSize ) ); + &( pOperation->pMqttPacket ), + &( pOperation->packetSize ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -1069,11 +1069,11 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pConnectOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pConnectOperation->packetSize > 0 ); + IotMqtt_Assert( pOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->packetSize > 0 ); /* Add the CONNECT operation to the send queue for network transmission. */ - status = _IotMqtt_ScheduleOperation( pConnectOperation, + status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessSend, 0 ); @@ -1084,12 +1084,12 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, else { /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - status = IotMqtt_Wait( ( IotMqttReference_t ) pConnectOperation, + status = IotMqtt_Wait( pOperation, timeoutMs ); /* The call to wait cleans up the CONNECT operation, so set the pointer * to NULL. */ - pConnectOperation = NULL; + pOperation = NULL; } /* When a connection is successfully established, schedule keep-alive job. */ @@ -1149,9 +1149,9 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, _EMPTY_ELSE_MARKER; } - if( pConnectOperation != NULL ) + if( pOperation != NULL ) { - _IotMqtt_DestroyOperation( pConnectOperation ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -1185,7 +1185,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { bool disconnected = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pDisconnectOperation = NULL; + _mqttOperation_t * pOperation = NULL; IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); @@ -1205,19 +1205,19 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, status = _IotMqtt_CreateOperation( mqttConnection, IOT_MQTT_FLAG_WAITABLE, NULL, - &pDisconnectOperation ); + &pOperation ); if( status == IOT_MQTT_SUCCESS ) { /* Ensure that the members set by operation creation and serialization * are appropriate for a blocking DISCONNECT. */ - IotMqtt_Assert( pDisconnectOperation->status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pDisconnectOperation->flags & IOT_MQTT_FLAG_WAITABLE ) + IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pDisconnectOperation->retry.limit == 0 ); + IotMqtt_Assert( pOperation->retry.limit == 0 ); /* Set the operation type. */ - pDisconnectOperation->operation = IOT_MQTT_DISCONNECT; + pOperation->type = IOT_MQTT_DISCONNECT; /* Choose a disconnect serializer. */ IotMqttError_t ( * serializeDisconnect )( uint8_t **, @@ -1242,8 +1242,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Generate a DISCONNECT packet. */ - status = serializeDisconnect( &( pDisconnectOperation->pMqttPacket ), - &( pDisconnectOperation->packetSize ) ); + status = serializeDisconnect( &( pOperation->pMqttPacket ), + &( pOperation->packetSize ) ); } else { @@ -1253,22 +1253,22 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, if( status == IOT_MQTT_SUCCESS ) { /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pDisconnectOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pDisconnectOperation->packetSize > 0 ); + IotMqtt_Assert( pOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->packetSize > 0 ); /* Schedule the DISCONNECT operation for network transmission. */ - if( _IotMqtt_ScheduleOperation( pDisconnectOperation, + if( _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessSend, 0 ) != IOT_MQTT_SUCCESS ) { IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", mqttConnection ); - _IotMqtt_DestroyOperation( pDisconnectOperation ); + _IotMqtt_DestroyOperation( pOperation ); } else { /* Wait a short time for the DISCONNECT packet to be transmitted. */ - status = IotMqtt_Wait( ( IotMqttReference_t ) pDisconnectOperation, + status = IotMqtt_Wait( pOperation, IOT_MQTT_RESPONSE_WAIT_MS ); /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, @@ -1335,7 +1335,7 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pSubscribeRef ) + IotMqttOperation_t * pSubscribeOperation ) { return _subscriptionCommon( IOT_MQTT_SUBSCRIBE, mqttConnection, @@ -1343,7 +1343,7 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, subscriptionCount, flags, pCallbackInfo, - pSubscribeRef ); + pSubscribeOperation ); } /*-----------------------------------------------------------*/ @@ -1355,7 +1355,7 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, uint64_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttReference_t subscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Flags are not used, but the parameter is present for future compatibility. */ ( void ) flags; @@ -1366,12 +1366,12 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, subscriptionCount, IOT_MQTT_FLAG_WAITABLE, NULL, - &subscribeRef ); + &subscribeOperation ); /* Wait for the SUBSCRIBE operation to complete. */ if( status == IOT_MQTT_STATUS_PENDING ) { - status = IotMqtt_Wait( subscribeRef, timeoutMs ); + status = IotMqtt_Wait( subscribeOperation, timeoutMs ); } else { @@ -1391,7 +1391,7 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pUnsubscribeRef ) + IotMqttOperation_t * pUnsubscribeOperation ) { return _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, mqttConnection, @@ -1399,7 +1399,7 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, subscriptionCount, flags, pCallbackInfo, - pUnsubscribeRef ); + pUnsubscribeOperation ); } /*-----------------------------------------------------------*/ @@ -1411,7 +1411,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, uint64_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Flags are not used, but the parameter is present for future compatibility. */ ( void ) flags; @@ -1422,12 +1422,12 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, subscriptionCount, IOT_MQTT_FLAG_WAITABLE, NULL, - &unsubscribeRef ); + &unsubscribeOperation ); /* Wait for the UNSUBSCRIBE operation to complete. */ if( status == IOT_MQTT_STATUS_PENDING ) { - status = IotMqtt_Wait( unsubscribeRef, timeoutMs ); + status = IotMqtt_Wait( unsubscribeOperation, timeoutMs ); } else { @@ -1446,10 +1446,10 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttReference_t * pPublishRef ) + IotMqttOperation_t * pPublishOperation ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - _mqttOperation_t * pPublishOperation = NULL; + _mqttOperation_t * pOperation = NULL; uint8_t ** pPacketIdentifierHigh = NULL; /* Default PUBLISH serializer function. */ @@ -1490,7 +1490,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, _EMPTY_ELSE_MARKER; } - if( pPublishRef != NULL ) + if( pPublishOperation != NULL ) { IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); } @@ -1507,7 +1507,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* Check that a reference pointer is provided for a waitable operation. */ if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) { - if( pPublishRef == NULL ) + if( pPublishOperation == NULL ) { IotLogError( "Reference must be provided for a waitable PUBLISH." ); @@ -1527,7 +1527,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, status = _IotMqtt_CreateOperation( mqttConnection, flags, pCallbackInfo, - &pPublishOperation ); + &pOperation ); if( status != IOT_MQTT_SUCCESS ) { @@ -1539,8 +1539,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } /* Check the PUBLISH operation data and set the operation type. */ - IotMqtt_Assert( pPublishOperation->status == IOT_MQTT_STATUS_PENDING ); - pPublishOperation->operation = IOT_MQTT_PUBLISH_TO_SERVER; + IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); + pOperation->type = IOT_MQTT_PUBLISH_TO_SERVER; /* Choose a PUBLISH serializer function. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 @@ -1564,7 +1564,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ if( mqttConnection->awsIotMqttMode == true ) { - pPacketIdentifierHigh = &( pPublishOperation->pPacketIdentifierHigh ); + pPacketIdentifierHigh = &( pOperation->pPacketIdentifierHigh ); } else { @@ -1573,9 +1573,9 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* Generate a PUBLISH packet from pPublishInfo. */ status = serializePublish( pPublishInfo, - &( pPublishOperation->pMqttPacket ), - &( pPublishOperation->packetSize ), - &( pPublishOperation->packetIdentifier ), + &( pOperation->pMqttPacket ), + &( pOperation->packetSize ), + &( pOperation->packetIdentifier ), pPacketIdentifierHigh ); if( status != IOT_MQTT_SUCCESS ) @@ -1588,8 +1588,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pPublishOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pPublishOperation->packetSize > 0 ); + IotMqtt_Assert( pOperation->pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->packetSize > 0 ); /* Initialize PUBLISH retry if retryLimit is set. */ if( pPublishInfo->retryLimit > 0 ) @@ -1597,8 +1597,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* A QoS 0 PUBLISH may not be retried. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - pPublishOperation->retry.limit = pPublishInfo->retryLimit; - pPublishOperation->retry.nextPeriod = pPublishInfo->retryMs; + pOperation->retry.limit = pPublishInfo->retryLimit; + pOperation->retry.nextPeriod = pPublishInfo->retryMs; } else { @@ -1613,9 +1613,9 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* Set the reference, if provided. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - if( pPublishRef != NULL ) + if( pPublishOperation != NULL ) { - *pPublishRef = pPublishOperation; + *pPublishOperation = pOperation; } else { @@ -1628,7 +1628,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } /* Add the PUBLISH operation to the send queue for network transmission. */ - status = _IotMqtt_ScheduleOperation( pPublishOperation, + status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessSend, 0 ); @@ -1640,9 +1640,9 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* Clear the previously set (and now invalid) reference. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - if( pPublishRef != NULL ) + if( pPublishOperation != NULL ) { - *pPublishRef = IOT_MQTT_REFERENCE_INITIALIZER; + *pPublishOperation = IOT_MQTT_OPERATION_INITIALIZER; } else { @@ -1667,9 +1667,9 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, if( status != IOT_MQTT_SUCCESS ) { - if( pPublishOperation != NULL ) + if( pOperation != NULL ) { - _IotMqtt_DestroyOperation( pPublishOperation ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -1702,8 +1702,8 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, uint64_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER, - * pPublishRef = NULL; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, + * pPublishOperation = NULL; /* Clear the flags. */ flags = 0; @@ -1712,7 +1712,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { flags = IOT_MQTT_FLAG_WAITABLE; - pPublishRef = &publishRef; + pPublishOperation = &publishOperation; } else { @@ -1724,14 +1724,14 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, pPublishInfo, flags, NULL, - pPublishRef ); + pPublishOperation ); /* Wait for a queued QoS 1 PUBLISH to complete. */ if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { if( status == IOT_MQTT_STATUS_PENDING ) { - status = IotMqtt_Wait( publishRef, timeoutMs ); + status = IotMqtt_Wait( publishOperation, timeoutMs ); } else { @@ -1748,15 +1748,14 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, +IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, uint64_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttOperation_t * pOperation = reference; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + _mqttConnection_t * pMqttConnection = operation->pMqttConnection; - /* Validate the given reference. */ - if( _IotMqtt_ValidateReference( reference ) == false ) + /* Validate the given operation reference. */ + if( _IotMqtt_ValidateOperation( operation ) == false ) { status = IOT_MQTT_BAD_PARAMETER; } @@ -1775,8 +1774,8 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, IotLogError( "(MQTT connection %p, %s operation %p) MQTT connection is closed. " "Operation cannot be waited on.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), - pOperation ); + IotMqtt_OperationType( operation->type ), + operation ); status = IOT_MQTT_NETWORK_ERROR; } @@ -1784,8 +1783,8 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, { IotLogInfo( "(MQTT connection %p, %s operation %p) Waiting for operation completion.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), - pOperation ); + IotMqtt_OperationType( operation->type ), + operation ); } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -1793,24 +1792,24 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, /* Only wait on an operation if the MQTT connection is active. */ if( status == IOT_MQTT_SUCCESS ) { - if( IotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), timeoutMs ) == false ) { status = IOT_MQTT_TIMEOUT; /* Attempt to cancel the job of the timed out operation. */ - ( void ) _IotMqtt_DecrementOperationReferences( pOperation, true ); + ( void ) _IotMqtt_DecrementOperationReferences( operation, true ); /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ - if( pOperation->operation == IOT_MQTT_SUBSCRIBE ) + if( operation->type == IOT_MQTT_SUBSCRIBE ) { IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" " subscriptions of timed-out SUBSCRIBE.", pMqttConnection, - pOperation ); + operation ); _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, - pOperation->packetIdentifier, + operation->packetIdentifier, -1 ); } else @@ -1821,13 +1820,13 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, else { /* Retrieve the status of the completed operation. */ - status = pOperation->status; + status = operation->status; } IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), - pOperation, + IotMqtt_OperationType( operation->type ), + operation, IotMqtt_strerror( status ) ); } else @@ -1836,9 +1835,9 @@ IotMqttError_t IotMqtt_Wait( IotMqttReference_t reference, } /* Wait is finished; decrement operation reference count. */ - if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) + if( _IotMqtt_DecrementOperationReferences( operation, false ) == true ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( operation ); } else { diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 212ddff2b2..fc8f09e7fa 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -49,7 +49,7 @@ */ typedef struct _operationMatchParam { - IotMqttOperationType_t operation; /**< @brief The operation to look for. */ + IotMqttOperationType_t type; /**< @brief The type of operation to look for. */ const uint16_t * pPacketIdentifier; /**< @brief The packet identifier associated with the operation. * Set to `NULL` to ignore packet identifier. */ } _operationMatchParam_t; @@ -106,7 +106,7 @@ static bool _mqttOperation_match( const IotLink_t * pOperationLink, _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; /* Check for matching operations. */ - if( pParam->operation == pOperation->operation ) + if( pParam->type == pOperation->type ) { /* Check for matching packet identifiers. */ if( pParam->pPacketIdentifier == NULL ) @@ -157,7 +157,7 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Only PUBLISH may be retried. */ - IotMqtt_Assert( pOperation->operation == IOT_MQTT_PUBLISH_TO_SERVER ); + IotMqtt_Assert( pOperation->type == IOT_MQTT_PUBLISH_TO_SERVER ); /* Check if the retry limit is exhausted. */ if( pOperation->retry.count > pOperation->retry.limit ) @@ -475,7 +475,7 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, { IotLogDebug( "(MQTT connection %p, %s operation %p) Job canceled.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } else @@ -497,7 +497,7 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" " from %ld to %ld.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation, pOperation->jobReference + 1, pOperation->jobReference ); @@ -537,7 +537,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, %s operation %p) Destroying operation.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); /* The job reference count must be between 0 and 2. */ @@ -552,7 +552,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) { IotLogDebug( "(MQTT connection %p, %s operation %p) Removed operation from connection lists.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation, pMqttConnection ); @@ -562,7 +562,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) { IotLogDebug( "(MQTT connection %p, %s operation %p) Operation was not present in connection lists.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } @@ -593,14 +593,14 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } else { IotLogDebug( "(MQTT connection %p, %s operation %p) Operation has no allocated MQTT packet.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } @@ -611,7 +611,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, %s operation %p) Wait semaphore destroyed.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } else @@ -621,7 +621,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); /* Free the memory used to hold operation data. */ @@ -847,7 +847,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, { IotLogDebug( "(MQTT connection %p, %s operation %p) Sending MQTT packet.", pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); /* Transmit the MQTT packet from the operation over the network. */ @@ -865,7 +865,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /* DISCONNECT operations are considered successful upon successful * transmission. In addition, non-waitable operations with no callback * may also be considered successful. */ - if( pOperation->operation == IOT_MQTT_DISCONNECT ) + if( pOperation->type == IOT_MQTT_DISCONNECT ) { /* DISCONNECT operations are always waitable. */ IotMqtt_Assert( waitable == true ); @@ -1002,7 +1002,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, IotMqtt_Assert( pOperation->status != IOT_MQTT_STATUS_PENDING ); callbackParam.mqttConnection = pOperation->pMqttConnection; - callbackParam.operation.type = pOperation->operation; + callbackParam.operation.type = pOperation->type; callbackParam.operation.reference = pOperation; callbackParam.operation.result = pOperation->status; @@ -1054,7 +1054,7 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule operation job, error %s.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation, IotTaskPool_strerror( taskPoolStatus ) ); @@ -1071,7 +1071,7 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, /*-----------------------------------------------------------*/ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, - IotMqttOperationType_t operation, + IotMqttOperationType_t type, const uint16_t * pPacketIdentifier ) { bool waitable = false; @@ -1085,18 +1085,18 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response " "with packet identifier %hu.", pMqttConnection, - IotMqtt_OperationType( operation ), + IotMqtt_OperationType( type ), *pPacketIdentifier ); } else { IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response.", pMqttConnection, - IotMqtt_OperationType( operation ) ); + IotMqtt_OperationType( type ) ); } /* Set the search parameters. */ - param.operation = operation; + param.type = type; param.pPacketIdentifier = pPacketIdentifier; /* Find and remove the first matching element in the list. */ @@ -1148,7 +1148,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", pMqttConnection, - IotMqtt_OperationType( operation ), + IotMqtt_OperationType( type ), pResult, ( long int ) ( pResult->jobReference - 1 ), ( long int ) ( pResult->jobReference ) ); @@ -1166,13 +1166,13 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { IotLogDebug( "(MQTT connection %p) Found operation %s.", pMqttConnection, - IotMqtt_OperationType( operation ) ); + IotMqtt_OperationType( type ) ); } else { IotLogDebug( "(MQTT connection %p) Operation %s not found.", pMqttConnection, - IotMqtt_OperationType( operation ) ); + IotMqtt_OperationType( type ) ); } return pResult; @@ -1190,7 +1190,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected * subscriptions are removed by the deserializer, so not removed here. */ - if( pOperation->operation == IOT_MQTT_SUBSCRIBE ) + if( pOperation->type == IOT_MQTT_SUBSCRIBE ) { switch( pOperation->status ) { @@ -1220,7 +1220,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " "notified of completion.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } else @@ -1241,7 +1241,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); /* A completed operation should not be linked. */ @@ -1256,7 +1256,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->operation ), + IotMqtt_OperationType( pOperation->type ), pOperation ); } diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index 34b515ad75..3e35668df2 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -311,15 +311,14 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -bool _IotMqtt_ValidateReference( IotMqttReference_t reference ) +bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) { _IOT_FUNCTION_ENTRY( bool, true ); - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) reference; /* Check for NULL. */ - if( pOperation == NULL ) + if( operation == NULL ) { - IotLogError( "Reference cannot be NULL." ); + IotLogError( "Operation reference cannot be NULL." ); _IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -329,9 +328,9 @@ bool _IotMqtt_ValidateReference( IotMqttReference_t reference ) } /* Check that reference is waitable. */ - if( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) + if( ( operation->flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) { - IotLogError( "Reference is not a waitable MQTT operation." ); + IotLogError( "Operation is not waitable." ); _IOT_SET_AND_GOTO_CLEANUP( false ); } diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 22f7575c91..a6c002c2e8 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -62,7 +62,7 @@ * @param[in] thingNameLength Length of `pThingName`. * @param[in] flags Flags passed to Shadow API function. * @param[in] pCallbackInfo Callback info passed to Shadow API function. - * @param[in] pReference Reference pointer passed to Shadow API function. + * @param[in] pOperation Operation reference pointer passed to Shadow API function. * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_PARAMETER. */ @@ -71,7 +71,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, size_t thingNameLength, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - const AwsIotShadowReference_t * pReference ); + const AwsIotShadowOperation_t * pOperation ); /** * @brief Checks document info parameter passed to Shadow API functions. @@ -175,7 +175,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, size_t thingNameLength, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - const AwsIotShadowReference_t * pReference ) + const AwsIotShadowOperation_t * pOperation ) { /* Type is not used when logging is disabled. */ ( void ) type; @@ -203,7 +203,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { /* Check that a reference pointer is provided for a waitable operation. */ - if( pReference == NULL ) + if( pOperation == NULL ) { IotLogError( "Reference must be set for a waitable Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); @@ -650,7 +650,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, size_t thingNameLength, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowReference_t * pDeleteRef ) + AwsIotShadowOperation_t * pDeleteOperation ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -661,7 +661,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, thingNameLength, flags, pCallbackInfo, - pDeleteRef ) != AWS_IOT_SHADOW_SUCCESS ) + pDeleteOperation ) != AWS_IOT_SHADOW_SUCCESS ) { /* The Thing Name or some flag was invalid. */ return AWS_IOT_SHADOW_BAD_PARAMETER; @@ -685,9 +685,9 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, /* Set the reference if provided. This must be done before the Shadow operation * is processed. */ - if( pDeleteRef != NULL ) + if( pDeleteOperation != NULL ) { - *pDeleteRef = pOperation; + *pDeleteOperation = pOperation; } /* Process the Shadow operation. This subscribes to any required topics and @@ -699,9 +699,9 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, NULL ); /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteRef != NULL ) ) + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteOperation != NULL ) ) { - *pDeleteRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + *pDeleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; } return status; @@ -716,7 +716,7 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection uint64_t timeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowReference_t deleteRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Set the waitable flag. */ flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; @@ -727,12 +727,12 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection thingNameLength, flags, NULL, - &deleteRef ); + &deleteOperation ); /* Wait for the Shadow delete operation to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) { - status = AwsIotShadow_Wait( deleteRef, timeoutMs, NULL, NULL ); + status = AwsIotShadow_Wait( deleteOperation, timeoutMs, NULL, NULL ); } /* Ensure that a status was set. */ @@ -747,7 +747,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowReference_t * pGetRef ) + AwsIotShadowOperation_t * pGetOperation ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -758,7 +758,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, pGetInfo->thingNameLength, flags, pCallbackInfo, - pGetRef ) != AWS_IOT_SHADOW_SUCCESS ) + pGetOperation ) != AWS_IOT_SHADOW_SUCCESS ) { /* The Thing Name or some flag was invalid. */ return AWS_IOT_SHADOW_BAD_PARAMETER; @@ -794,9 +794,9 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, /* Set the reference if provided. This must be done before the Shadow operation * is processed. */ - if( pGetRef != NULL ) + if( pGetOperation != NULL ) { - *pGetRef = pOperation; + *pGetOperation = pOperation; } /* Process the Shadow operation. This subscribes to any required topics and @@ -808,9 +808,9 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, pGetInfo ); /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetRef != NULL ) ) + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetOperation != NULL ) ) { - *pGetRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + *pGetOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; } return status; @@ -826,7 +826,7 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, size_t * pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowReference_t getRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Set the waitable flag. */ flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; @@ -836,12 +836,12 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, pGetInfo, flags, NULL, - &getRef ); + &getOperation ); /* Wait for the Shadow get operation to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) { - status = AwsIotShadow_Wait( getRef, + status = AwsIotShadow_Wait( getOperation, timeoutMs, pShadowDocument, pShadowDocumentLength ); @@ -859,7 +859,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowReference_t * pUpdateRef ) + AwsIotShadowOperation_t * pUpdateOperation ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -872,7 +872,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, pUpdateInfo->thingNameLength, flags, pCallbackInfo, - pUpdateRef ) != AWS_IOT_SHADOW_SUCCESS ) + pUpdateOperation ) != AWS_IOT_SHADOW_SUCCESS ) { /* The Thing Name or some flag was invalid. */ return AWS_IOT_SHADOW_BAD_PARAMETER; @@ -948,9 +948,9 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /* Set the reference if provided. This must be done before the Shadow operation * is processed. */ - if( pUpdateRef != NULL ) + if( pUpdateOperation != NULL ) { - *pUpdateRef = pOperation; + *pUpdateOperation = pOperation; } /* Process the Shadow operation. This subscribes to any required topics and @@ -962,9 +962,9 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, pUpdateInfo ); /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateRef != NULL ) ) + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateOperation != NULL ) ) { - *pUpdateRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + *pUpdateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; } return status; @@ -978,7 +978,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection uint64_t timeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowReference_t updateRef = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Set the waitable flag. */ flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; @@ -988,12 +988,12 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection pUpdateInfo, flags, NULL, - &updateRef ); + &updateOperation ); /* Wait for the Shadow update operation to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) { - status = AwsIotShadow_Wait( updateRef, timeoutMs, NULL, NULL ); + status = AwsIotShadow_Wait( updateOperation, timeoutMs, NULL, NULL ); } /* Ensure that a status was set. */ @@ -1004,32 +1004,31 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, +AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, uint64_t timeoutMs, const char ** pShadowDocument, size_t * pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - _shadowOperation_t * pOperation = ( _shadowOperation_t * ) reference; /* Check that reference is set. */ - if( pOperation == NULL ) + if( operation == NULL ) { - IotLogError( "Reference cannot be NULL." ); + IotLogError( "Operation reference cannot be NULL." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } /* Check that reference is waitable. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + if( ( operation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { - IotLogError( "Reference is not a waitable Shadow operation." ); + IotLogError( "Operation is not waitable." ); return AWS_IOT_SHADOW_BAD_PARAMETER; } /* Check that output parameters are set for a Shadow GET. */ - if( pOperation->type == _SHADOW_GET ) + if( operation->type == _SHADOW_GET ) { if( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) { @@ -1040,10 +1039,10 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, } /* Wait for a response to the Shadow operation. */ - if( IotSemaphore_TimedWait( &( pOperation->notify.waitSemaphore ), + if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), timeoutMs ) == true ) { - status = pOperation->status; + status = operation->status; } else { @@ -1052,27 +1051,27 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowReference_t reference, /* Remove the completed operation from the pending operation list. */ IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_Remove( &( pOperation->link ) ); + IotListDouble_Remove( &( operation->link ) ); IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Decrement the reference count. This also removes subscriptions if the * count reaches 0. */ IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pOperation->pSubscription->pTopicBuffer, + _AwsIotShadow_DecrementReferences( operation, + operation->pSubscription->pTopicBuffer, NULL ); IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the output parameters for Shadow GET. */ - if( ( pOperation->type == _SHADOW_GET ) && + if( ( operation->type == _SHADOW_GET ) && ( status == AWS_IOT_SHADOW_SUCCESS ) ) { - *pShadowDocument = pOperation->get.pDocument; - *pShadowDocumentLength = pOperation->get.documentLength; + *pShadowDocument = operation->get.pDocument; + *pShadowDocumentLength = operation->get.documentLength; } /* Destroy the Shadow operation. */ - _AwsIotShadow_DestroyOperation( pOperation ); + _AwsIotShadow_DestroyOperation( operation ); return status; } diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index e75953eb26..7ef977f54f 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -256,7 +256,7 @@ static IotMqttError_t _checkConnection( void ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Set the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; @@ -272,7 +272,7 @@ static IotMqttError_t _checkConnection( void ) &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, - &publishRef ); + &publishOperation ); if( status != IOT_MQTT_STATUS_PENDING ) { @@ -280,7 +280,7 @@ static IotMqttError_t _checkConnection( void ) } /* Return the result of the PUBLISH. */ - return IotMqtt_Wait( publishRef, + return IotMqtt_Wait( publishOperation, IOT_TEST_MQTT_TIMEOUT_MS ); } diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index a121a859b1..ea13f277b5 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -114,7 +114,7 @@ typedef struct _operationCompleteParams { IotMqttOperationType_t expectedOperation; /**< @brief Expected completed operation. */ IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ - IotMqttReference_t reference; /**< @brief Reference to expected completed operation. */ + IotMqttOperation_t operation; /**< @brief Reference to expected completed operation. */ } _operationCompleteParams_t; /*-----------------------------------------------------------*/ @@ -319,7 +319,7 @@ static void _operationComplete( void * pArgument, /* If the operation information matches the parameters and the operation was * successful, unblock the waiting thread. */ if( ( pParams->expectedOperation == pOperation->operation.type ) && - ( pParams->reference == pOperation->operation.reference ) && + ( pParams->operation == pOperation->operation.reference ) && ( pOperation->operation.result == IOT_MQTT_SUCCESS ) ) { IotSemaphore_Post( &( pParams->waitSem ) ); @@ -339,7 +339,7 @@ static void _reentrantCallback( void * pArgument, IotSemaphore_t * pWaitSemaphores = ( IotSemaphore_t * ) pArgument; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Topic used in this test. */ const char * const pTopic = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; @@ -379,7 +379,7 @@ static void _reentrantCallback( void * pArgument, 1, IOT_MQTT_FLAG_WAITABLE, NULL, - &unsubscribeRef ); + &unsubscribeOperation ); if( mqttStatus == IOT_MQTT_STATUS_PENDING ) { @@ -388,7 +388,7 @@ static void _reentrantCallback( void * pArgument, /* Waiting on an operation whose connection is closed should return * "Network Error". */ - mqttStatus = IotMqtt_Wait( unsubscribeRef, + mqttStatus = IotMqtt_Wait( unsubscribeOperation, 500 ); status = ( mqttStatus == IOT_MQTT_NETWORK_ERROR ); @@ -691,7 +691,7 @@ TEST( MQTT_System, SubscribePublishAsync ) 1, 0, &callbackInfo, - &( callbackParam.reference ) ); + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), IOT_TEST_MQTT_TIMEOUT_MS ) == false ) @@ -705,7 +705,7 @@ TEST( MQTT_System, SubscribePublishAsync ) &publishInfo, 0, &callbackInfo, - &( callbackParam.reference ) ); + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), IOT_TEST_MQTT_TIMEOUT_MS ) == false ) @@ -727,7 +727,7 @@ TEST( MQTT_System, SubscribePublishAsync ) 1, 0, &callbackInfo, - &( callbackParam.reference ) ); + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), IOT_TEST_MQTT_TIMEOUT_MS ) == false ) @@ -983,7 +983,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttReference_t pPublishRef[ 3 ] = { IOT_MQTT_REFERENCE_INITIALIZER }; + IotMqttOperation_t pPublishOperation[ 3 ] = { IOT_MQTT_OPERATION_INITIALIZER }; /* Set the client identifier and length. */ connectInfo.pClientIdentifier = _pClientIdentifier; @@ -1014,7 +1014,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, - &( pPublishRef[ i ] ) ); + &( pPublishOperation[ i ] ) ); TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); } } @@ -1029,7 +1029,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) * timing of publish versus disconnect, so the statuses are not checked. */ for( i = 0; i < 3; i++ ) { - status = IotMqtt_Wait( pPublishRef[ i ], 100 ); + status = IotMqtt_Wait( pPublishOperation[ i ], 100 ); } } } diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index f7693808c0..8fd91daa45 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -671,7 +671,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) &pOperation ) ); /* Set an arbitrary MQTT packet for the operation. */ - pOperation->operation = IOT_MQTT_DISCONNECT; + pOperation->type = IOT_MQTT_DISCONNECT; pOperation->pMqttPacket = pPacket; pOperation->packetSize = 2; @@ -935,7 +935,7 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttReference_t publishReference = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; /* Initialize parameters. */ @@ -956,13 +956,13 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; /* Check that a QoS 0 publish is refused if a notification is requested. */ - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishReference ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* If valid parameters are passed, QoS 0 publish should always return success. */ - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, 0, &publishReference ); + status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, 0, &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); } @@ -1031,7 +1031,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; /* Initialize parameters. */ @@ -1063,7 +1063,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) &publishInfo, IOT_MQTT_FLAG_WAITABLE, &callbackInfo, - &publishRef ); + &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Check QoS 1 PUBLISH behavior with malloc failures. */ @@ -1077,13 +1077,13 @@ TEST( MQTT_Unit_API, PublishQoS1 ) &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, - &publishRef ); + &publishOperation ); /* If the PUBLISH succeeded, the loop can exit after waiting for the QoS * 1 PUBLISH to be cleaned up. */ if( status == IOT_MQTT_STATUS_PENDING ) { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( publishRef, _TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( publishOperation, _TIMEOUT_MS ) ); break; } @@ -1110,7 +1110,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) { static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttReference_t publishRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; bool dupCheckResult = false; uint64_t startTime = 0; @@ -1147,12 +1147,12 @@ TEST( MQTT_Unit_API, PublishDuplicates ) &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, - &publishRef ) ); + &publishOperation ) ); /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is * expected. */ TEST_ASSERT_EQUAL( IOT_MQTT_RETRY_NO_RESPONSE, - IotMqtt_Wait( publishRef, _DUP_CHECK_TIMEOUT ) ); + IotMqtt_Wait( publishOperation, _DUP_CHECK_TIMEOUT ) ); /* Check the result of the DUP check. */ TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); @@ -1181,7 +1181,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttReference_t reference = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Create a new MQTT connection. */ _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, @@ -1195,7 +1195,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) 1, IOT_MQTT_FLAG_WAITABLE, NULL, - &reference ); + &subscribeOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); status = IotMqtt_Unsubscribe( _pMqttConnection, @@ -1203,7 +1203,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) 1, IOT_MQTT_FLAG_WAITABLE, NULL, - &reference ); + &subscribeOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); subscription.pTopicFilter = _TEST_TOPIC_NAME; @@ -1241,7 +1241,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttReference_t subscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Initializer parameters. */ _networkInterface.send = _sendSuccess; @@ -1270,13 +1270,13 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) 1, IOT_MQTT_FLAG_WAITABLE, NULL, - &subscribeRef ); + &subscribeOperation ); /* If the SUBSCRIBE succeeded, the loop can exit after waiting for * the SUBSCRIBE to be cleaned up. */ if( status == IOT_MQTT_STATUS_PENDING ) { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( subscribeRef, _TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( subscribeOperation, _TIMEOUT_MS ) ); break; } @@ -1303,7 +1303,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttReference_t unsubscribeRef = IOT_MQTT_REFERENCE_INITIALIZER; + IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Initialize parameters. */ _networkInterface.send = _sendSuccess; @@ -1332,13 +1332,13 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) 1, IOT_MQTT_FLAG_WAITABLE, NULL, - &unsubscribeRef ); + &unsubscribeOperation ); /* If the UNSUBSCRIBE succeeded, the loop can exit after waiting for * the UNSUBSCRIBE to be cleaned up. */ if( status == IOT_MQTT_STATUS_PENDING ) { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( unsubscribeRef, _TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( unsubscribeOperation, _TIMEOUT_MS ) ); break; } diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 31a44142c1..f959b1a689 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -123,7 +123,7 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; #define _INITIALIZE_OPERATION( name ) \ { \ .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ - .job = { 0 }, .jobReference = 1, .operation = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ + .job = { 0 }, .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, .notify = { .callback = { 0 } }, \ .status = IOT_MQTT_STATUS_PENDING, .retry = { 0 } \ } diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 0134149975..2bf3d634cc 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -103,7 +103,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_Validate ) { RUN_TEST_CASE( MQTT_Unit_Validate, ValidateConnectInfo ); RUN_TEST_CASE( MQTT_Unit_Validate, ValidatePublish ); - RUN_TEST_CASE( MQTT_Unit_Validate, ValidateReference ); + RUN_TEST_CASE( MQTT_Unit_Validate, ValidateOperation ); RUN_TEST_CASE( MQTT_Unit_Validate, ValidateSubscriptionList ); } @@ -256,28 +256,35 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) /*-----------------------------------------------------------*/ /** - * @brief Test validation of an #IotMqttReference_t. + * @brief Test validation of an #IotMqttOperation_t. */ -TEST( MQTT_Unit_Validate, ValidateReference ) +TEST( MQTT_Unit_Validate, ValidateOperation ) { bool validateStatus = false; - _mqttOperation_t reference; + IotMqttOperation_t operation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - ( void ) memset( &reference, 0x00, sizeof( _mqttOperation_t ) ); + TEST_ASSERT_NOT_NULL( operation ); - /* NULL parameter. */ - validateStatus = _IotMqtt_ValidateReference( NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); + if( TEST_PROTECT() ) + { + ( void ) memset( operation, 0x00, sizeof( _mqttOperation_t ) ); - /* Non-waitable reference. */ - reference.flags = 0; - validateStatus = _IotMqtt_ValidateReference( &reference ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* NULL parameter. */ + validateStatus = _IotMqtt_ValidateOperation( NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); - /* Waitable (valid) reference. */ - reference.flags = IOT_MQTT_FLAG_WAITABLE; - validateStatus = _IotMqtt_ValidateReference( &reference ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); + /* Non-waitable reference. */ + operation->flags = 0; + validateStatus = _IotMqtt_ValidateOperation( operation ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Waitable (valid) reference. */ + operation->flags = IOT_MQTT_FLAG_WAITABLE; + validateStatus = _IotMqtt_ValidateOperation( operation ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + } + + IotMqtt_FreeOperation( operation ); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index df6491bfab..4a07da73ed 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -115,7 +115,7 @@ typedef struct _operationCompleteParams { AwsIotShadowCallbackType_t expectedType; /**< @brief Expected callback type. */ IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ - AwsIotShadowReference_t reference; /**< @brief Reference to expected completed operation. */ + AwsIotShadowOperation_t operation; /**< @brief Reference to expected completed operation. */ } _operationCompleteParams_t; /*-----------------------------------------------------------*/ @@ -151,7 +151,7 @@ static void _operationComplete( void * pArgument, /* Check parameters against received operation information. */ AwsIotShadow_Assert( pOperation->callbackType == pParams->expectedType ); AwsIotShadow_Assert( pOperation->operation.result == AWS_IOT_SHADOW_SUCCESS ); - AwsIotShadow_Assert( pOperation->operation.reference == pParams->reference ); + AwsIotShadow_Assert( pOperation->operation.reference == pParams->operation ); AwsIotShadow_Assert( pOperation->thingNameLength == _THING_NAME_LENGTH ); AwsIotShadow_Assert( strncmp( pOperation->pThingName, AWS_IOT_TEST_SHADOW_THING_NAME, @@ -305,7 +305,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) &documentInfo, 0, &callbackInfo, - &( callbackParam.reference ) ); + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) @@ -321,7 +321,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) &documentInfo, 0, &callbackInfo, - &( callbackParam.reference ) ); + &( callbackParam.operation ) ); TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_STATUS_PENDING, status ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), @@ -339,7 +339,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) _THING_NAME_LENGTH, 0, &callbackInfo, - &( callbackParam.reference ) ); + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 690203ee79..975865d0e9 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -475,7 +475,7 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; /* Missing Thing Name. */ @@ -518,7 +518,7 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) _TEST_THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_WAITABLE, &callbackInfo, - &reference ); + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* No callback for non-waitable GET. */ @@ -537,7 +537,7 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) _TEST_THING_NAME_LENGTH, 0, &callbackInfo, - &reference ); + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); } @@ -551,14 +551,14 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Missing Thing Name. */ status = AwsIotShadow_Get( _pMqttConnection, &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; @@ -569,7 +569,7 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); documentInfo.qos = IOT_MQTT_QOS_0; @@ -579,7 +579,7 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); documentInfo.retryLimit = 0; @@ -588,7 +588,7 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Update with no document. */ @@ -658,7 +658,7 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) { int32_t i = 0, mqttErrorCount = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Set a short timeout so this test runs faster. */ _AwsIotShadowMqttTimeoutMs = 75; @@ -674,7 +674,7 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) _TEST_THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &deleteOperation ); /* Once the Shadow DELETE call succeeds, wait for it to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -682,7 +682,7 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) /* No response will be received from the network, so the Shadow DELETE * is expected to time out. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, - AwsIotShadow_Wait( reference, 0, NULL, NULL ) ); + AwsIotShadow_Wait( deleteOperation, 0, NULL, NULL ) ); break; } @@ -719,7 +719,7 @@ TEST( Shadow_Unit_API, GetMallocFail ) int32_t i = 0, mqttErrorCount = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; const char * pRetrievedDocument = NULL; size_t retrievedDocumentSize = 0; @@ -742,7 +742,7 @@ TEST( Shadow_Unit_API, GetMallocFail ) &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &getOperation ); /* Once the Shadow GET call succeeds, wait for it to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -750,7 +750,7 @@ TEST( Shadow_Unit_API, GetMallocFail ) /* No response will be received from the network, so the Shadow GET * is expected to time out. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, - AwsIotShadow_Wait( reference, + AwsIotShadow_Wait( getOperation, 0, &pRetrievedDocument, &retrievedDocumentSize ) ); @@ -786,7 +786,7 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) int32_t i = 0, mqttErrorCount = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowReference_t reference = AWS_IOT_SHADOW_REFERENCE_INITIALIZER; + AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Set a short timeout so this test runs faster. */ _AwsIotShadowMqttTimeoutMs = 75; @@ -808,7 +808,7 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, - &reference ); + &updateOperation ); /* Once the Shadow UPDATE call succeeds, wait for it to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -816,7 +816,7 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) /* No response will be received from the network, so the Shadow UPDATE * is expected to time out. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, - AwsIotShadow_Wait( reference, + AwsIotShadow_Wait( updateOperation, 0, NULL, NULL ) ); From 08908aa4a8e05489d22f68fe57c54a697d3fa52f Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Tue, 19 Mar 2019 07:08:04 -0700 Subject: [PATCH 054/844] Minor fix to timer thread, update to enter/exit CS macro and test cleanup. (#329) * Eliminated one useless taskpool test. * Minor fix to timer thread and change to enter/exit critical section macro. --- lib/source/common/iot_taskpool.c | 85 ++++++++++++-------------- tests/common/unit/iot_tests_taskpool.c | 11 ---- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 72b857c683..00d58cad33 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -41,24 +41,24 @@ * @brief Enter a critical section by locking a mutex. * */ -#define TASKPOOL_ENTER_CRITICAL IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Try entering a critical section by trying and lock a mutex. * */ -#define TASKPOOL_TRY_ENTER_CRITICAL IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_TRY_ENTER_CRITICAL() IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Exit a critical section by unlocking a mutex. * */ -#define TASKPOOL_EXIT_CRITICAL IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_EXIT_CRITICAL() IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) /** * @brief Maximum semaphore value for wait operations. */ -#define TASKPOOL_MAX_SEM_VALUE 0xFFFF +#define TASKPOOL_MAX_SEM_VALUE 0xFFFF /** * @brief Reschedule delay in milliseconds for deferred jobs. @@ -299,7 +299,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /* Destroying the task pool should be safe, and therefore we will grab the task pool lock. * No worker thread or application thread should access any data structure * in the task pool while the task pool is being destroyed. */ - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { IotLink_t * pItemLink; @@ -362,7 +362,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /* (4) Set the exit condition. */ _signalShutdown( pTaskPool, activeThreads ); } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); /* (5) Wait for all active threads to reach the end of their life-span. */ for( count = 0; count < activeThreads; ++count ) @@ -393,12 +393,12 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < 1UL ); - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } @@ -427,7 +427,7 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, } } } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -467,19 +467,19 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP { IotTaskPoolJob_t * pTempJob = NULL; - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } pTempJob = _fetchOrAllocateJob( &pTaskPool->jobsCache ); } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); /* Initialize all members. */ if( pTempJob == NULL ) @@ -508,7 +508,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -527,7 +527,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask status = _trySafeExtraction( pTaskPool, pJob, true ); } } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); if( TASKPOOL_SUCCEEDED( status ) ) { @@ -551,7 +551,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -579,7 +579,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, _recycleJob( &pTaskPool->jobsCache, pJob ); } } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -597,7 +597,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -615,7 +615,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, status = _scheduleInternal( pTaskPool, pJob, flags ); } } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -637,12 +637,12 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); } - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } @@ -654,7 +654,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool if( pTimerEvent == NULL ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } @@ -690,12 +690,12 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool } else { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); } } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -715,19 +715,19 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } *pStatus = pJob->status; } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -749,19 +749,19 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; } - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Check again if this task pool is shutting down. */ if( _IsShutdownStarted( pTaskPool ) ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); } status = _tryCancelInternal( pTaskPool, pJob, pStatus ); } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -1014,7 +1014,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified, * or before waiting for incoming notifications. */ - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* If the exit condition is verified, update the number of active threads and exit the loop. */ if( _IsShutdownStarted( pTaskPool ) ) @@ -1024,7 +1024,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Decrease the number of active threads. */ pTaskPool->activeThreads--; - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); /* Signal that this worker is exiting. */ IotSemaphore_Post( &pTaskPool->startStopSignal ); @@ -1061,7 +1061,7 @@ static void _taskPoolWorker( void * pUserContext ) userCallback = pJob->userCallback; } } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */ while( pJob != NULL ) @@ -1088,7 +1088,7 @@ static void _taskPoolWorker( void * pUserContext ) } /* Acquire the lock before updating the job status. */ - TASKPOOL_ENTER_CRITICAL; + TASKPOOL_ENTER_CRITICAL(); { /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ pTaskPool->activeJobs--; @@ -1102,7 +1102,7 @@ static void _taskPoolWorker( void * pUserContext ) /* If there is no job left in the dispatch queue, update the worker status and leave. */ if( pItem == NULL ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ break; @@ -1116,7 +1116,7 @@ static void _taskPoolWorker( void * pUserContext ) pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; } - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); } } while( running == true ); } @@ -1594,20 +1594,12 @@ static void _timerThread( void * pArgument ) * If this mutex cannot be locked it means that another thread is manipulating the * timeouts list, and will reset the timer to fire again, although it will be late. */ - if( TASKPOOL_TRY_ENTER_CRITICAL == false ) - { - IotLogWarn( "Failed to lock timer mutex in timer thread. Rescheduling deferred job." ); - - ( void ) IotClock_TimerArm( &pTaskPool->timer, TASKPOOL_JOB_RESCHEDULE_DELAY_MS, 0 ); - - return; - } - else + TASKPOOL_ENTER_CRITICAL(); { /* Check again for shutdown and bail out early in case. */ if( _IsShutdownStarted( pTaskPool ) ) { - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); return; } @@ -1660,8 +1652,7 @@ static void _timerThread( void * pArgument ) IotTaskPool_FreeTimerEvent( pTimerEvent ); } } - - TASKPOOL_EXIT_CRITICAL; + TASKPOOL_EXIT_CRITICAL(); } /** @endcond */ diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index b68e881cab..66df3ae65b 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -559,17 +559,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); } - /* Create/Schedule/Recycle. */ - { - IotTaskPoolJob_t * pJob = NULL; - - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule immediate, then try to illegally destroy, then cancel */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, pJob, 0 ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } - IotTaskPool_Destroy( &taskPool ); } From e953baa52c3bbc82e1911bc3576c22fae704b797 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 19 Mar 2019 11:52:04 -0700 Subject: [PATCH 055/844] Make tests network-agnostic. (#330) --- doc/config/mqtt | 2 +- platform/include/posix/iot_network_openssl.h | 4 +- .../posix/network/iot_network_openssl.c | 2 +- tests/iot_tests_config.h | 92 ++--- tests/iot_tests_network.c | 218 ----------- tests/mqtt/CMakeLists.txt | 1 - tests/mqtt/system/iot_tests_mqtt_stress.c | 83 +++-- tests/mqtt/system/iot_tests_mqtt_system.c | 349 ++++++++++-------- tests/shadow/CMakeLists.txt | 1 - .../system/aws_iot_tests_shadow_system.c | 92 +++-- 10 files changed, 349 insertions(+), 495 deletions(-) delete mode 100644 tests/iot_tests_network.c diff --git a/doc/config/mqtt b/doc/config/mqtt index 82b8556673..9e1f2f2955 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -29,7 +29,7 @@ INPUT = doc \ tests/mqtt/system # Library file names. -FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt iot_tests_network.c +FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index ad60eff192..0f81c347a1 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -152,7 +152,7 @@ typedef struct IotNetworkCredentialsOpenssl * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions * declared in this file. */ -#define IOT_NETWORK_INTERFACE_OPENSSL ( &( _IotNetworkOpenssl ) ) +#define IOT_NETWORK_INTERFACE_OPENSSL ( &( IotNetworkOpenssl ) ) /** * @brief One-time initialization function for this network stack. @@ -231,7 +231,7 @@ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); * * Declaration of a network interface struct using the functions in this file. */ -extern const IotNetworkInterface_t _IotNetworkOpenssl; +extern const IotNetworkInterface_t IotNetworkOpenssl; /** @endcond */ #endif /* ifndef _IOT_NETWORK_OPENSSL_H_ */ diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 370230d920..f63ab073bc 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -124,7 +124,7 @@ typedef struct _networkConnection /** * @brief An #IotNetworkInterface_t that uses the functions in this file. */ -const IotNetworkInterface_t _IotNetworkOpenssl = +const IotNetworkInterface_t IotNetworkOpenssl = { .create = IotNetworkOpenssl_Create, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 1379ad9d29..cdfd84f61c 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -78,26 +78,26 @@ #endif /* Metrics library configuration. */ -#define IOT_METRICS_ENABLE_ASSERTS ( 1 ) +#define IOT_METRICS_ENABLE_ASSERTS ( 1 ) /* Serializer library configuration. */ -#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) +#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) /* Defender library configuration. */ -#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) +#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define IotThreads_Malloc unity_malloc_mt -#define IotThreads_Free unity_free_mt -#define IotNetwork_Malloc unity_malloc_mt -#define IotNetwork_Free unity_free_mt -#define IotLogging_Malloc unity_malloc_mt -#define IotLogging_Free unity_free_mt +#define IotThreads_Malloc unity_malloc_mt +#define IotThreads_Free unity_free_mt +#define IotNetwork_Malloc unity_malloc_mt +#define IotNetwork_Free unity_free_mt +#define IotLogging_Malloc unity_malloc_mt +#define IotLogging_Free unity_free_mt /* #define IotLogging_StaticBufferSize */ -#define IotTest_Malloc unity_malloc_mt -#define IotTest_Free unity_free_mt +#define IotTest_Malloc unity_malloc_mt +#define IotTest_Free unity_free_mt /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ @@ -110,40 +110,40 @@ /* Memory allocation function configuration for libraries affected by * IOT_STATIC_MEMORY_ONLY. */ #if IOT_STATIC_MEMORY_ONLY == 0 - #define IotTaskPool_MallocJob unity_malloc_mt - #define IotTaskPool_FreeJob unity_free_mt - #define IotTaskPool_MallocTimerEvent unity_malloc_mt - #define IotTaskPool_FreeTimerEvent unity_free_mt - #define IotMqtt_MallocMessage unity_malloc_mt - #define IotMqtt_FreeMessage unity_free_mt - #define IotMqtt_MallocConnection unity_malloc_mt - #define IotMqtt_FreeConnection unity_free_mt - #define IotMqtt_MallocOperation unity_malloc_mt - #define IotMqtt_FreeOperation unity_free_mt - #define IotMqtt_MallocSubscription unity_malloc_mt - #define IotMqtt_FreeSubscription unity_free_mt - #define IotMqtt_MallocTimerEvent unity_malloc_mt - #define IotMqtt_FreeTimerEvent unity_free_mt - #define AwsIotShadow_MallocOperation unity_malloc_mt - #define AwsIotShadow_FreeOperation unity_free_mt - #define AwsIotShadow_MallocString unity_malloc_mt - #define AwsIotShadow_FreeString unity_free_mt - #define AwsIotShadow_MallocSubscription unity_malloc_mt - #define AwsIotShadow_FreeSubscription unity_free_mt - #define IotMetrics_MallocTcpConnection unity_malloc_mt - #define IotMetrics_FreeTcpConnection unity_free_mt - #define IotSerializer_MallocCborEncoder unity_malloc_mt - #define IotSerializer_FreeCborEncoder unity_free_mt - #define IotSerializer_MallocCborParser unity_malloc_mt - #define IotSerializer_FreeCborParser unity_free_mt - #define IotSerializer_MallocCborValue unity_malloc_mt - #define IotSerializer_FreeCborValue unity_free_mt - #define IotSerializer_MallocDecoderObject unity_malloc_mt - #define IotSerializer_FreeDecoderObject unity_free_mt - #define AwsIotDefender_MallocReport unity_malloc_mt - #define AwsIotDefender_FreeReport unity_free_mt - #define AwsIotDefender_MallocTopic unity_malloc_mt - #define AwsIotDefender_FreeTopic unity_free_mt + #define IotTaskPool_MallocJob unity_malloc_mt + #define IotTaskPool_FreeJob unity_free_mt + #define IotTaskPool_MallocTimerEvent unity_malloc_mt + #define IotTaskPool_FreeTimerEvent unity_free_mt + #define IotMqtt_MallocMessage unity_malloc_mt + #define IotMqtt_FreeMessage unity_free_mt + #define IotMqtt_MallocConnection unity_malloc_mt + #define IotMqtt_FreeConnection unity_free_mt + #define IotMqtt_MallocOperation unity_malloc_mt + #define IotMqtt_FreeOperation unity_free_mt + #define IotMqtt_MallocSubscription unity_malloc_mt + #define IotMqtt_FreeSubscription unity_free_mt + #define IotMqtt_MallocTimerEvent unity_malloc_mt + #define IotMqtt_FreeTimerEvent unity_free_mt + #define AwsIotShadow_MallocOperation unity_malloc_mt + #define AwsIotShadow_FreeOperation unity_free_mt + #define AwsIotShadow_MallocString unity_malloc_mt + #define AwsIotShadow_FreeString unity_free_mt + #define AwsIotShadow_MallocSubscription unity_malloc_mt + #define AwsIotShadow_FreeSubscription unity_free_mt + #define IotMetrics_MallocTcpConnection unity_malloc_mt + #define IotMetrics_FreeTcpConnection unity_free_mt + #define IotSerializer_MallocCborEncoder unity_malloc_mt + #define IotSerializer_FreeCborEncoder unity_free_mt + #define IotSerializer_MallocCborParser unity_malloc_mt + #define IotSerializer_FreeCborParser unity_free_mt + #define IotSerializer_MallocCborValue unity_malloc_mt + #define IotSerializer_FreeCborValue unity_free_mt + #define IotSerializer_MallocDecoderObject unity_malloc_mt + #define IotSerializer_FreeDecoderObject unity_free_mt + #define AwsIotDefender_MallocReport unity_malloc_mt + #define AwsIotDefender_FreeReport unity_free_mt + #define AwsIotDefender_MallocTopic unity_malloc_mt + #define AwsIotDefender_FreeTopic unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* Network header to include in the tests. */ diff --git a/tests/iot_tests_network.c b/tests/iot_tests_network.c deleted file mode 100644 index dbec0c31d3..0000000000 --- a/tests/iot_tests_network.c +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_network.c - * @brief Common network function implementations for the tests. - */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - -/* Standard includes. */ -#include -#include - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/*-----------------------------------------------------------*/ - -/** - * @brief Network interface setup function for the tests. - * - * Creates a global network connection to be used by the tests. - * @return true if setup succeeded; false otherwise. - * - * @see #IotTest_NetworkCleanup - */ -bool IotTest_NetworkSetup( void ); - -/** - * @brief Network interface cleanup function for the tests. - * - * @see #IotTest_NetworkSetup - */ -void IotTest_NetworkCleanup( void ); - -/** - * @brief Network interface connect function for the tests. - * - * Creates a new network connection for use with MQTT. - * - * @param[out] pNewConnection The handle by which this new connection will be referenced. - * - * @return true if a new network connection was successfully created; false otherwise. - */ -bool IotTest_NetworkConnect( IotTestNetworkConnection_t ** pNewConnection ); - -/** - * @brief Network interface close connection function for the tests. - * - * @param[in] pNetworkConnection The connection to close. Pass NULL to close - * the global network connection created by #IotTest_NetworkSetup. - * - * @return Always returns #IOT_NETWORK_SUCCESS. - */ -IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ); - -/** - * @brief Network interface cleanup function for the tests. - * - * @param[in] pNetworkConnection The connection to destroy. - */ -void IotTest_NetworkDestroy( void * pNetworkConnection ); - -/*-----------------------------------------------------------*/ - -/** - * @brief The network connection shared among the tests. - */ -static IotTestNetworkConnection_t * _pNetworkConnection = NULL; - -/** - * @brief Network interface to use in the tests. - */ -static const IotNetworkInterface_t * const _pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - -/** - * @brief The MQTT network interface shared among the tests. - */ -IotMqttNetworkInfo_t _IotTestNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - -/** - * @brief The MQTT connection shared among the tests. - */ -IotMqttConnection_t _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*-----------------------------------------------------------*/ - -bool IotTest_NetworkSetup( void ) -{ - /* Initialize the network library. */ - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - return false; - } - - if( IotTest_NetworkConnect( &_pNetworkConnection ) == false ) - { - IotTestNetwork_Cleanup(); - - return false; - } - - /* Set the members of the network info. */ - _IotTestNetworkInfo.createNetworkConnection = false; - _IotTestNetworkInfo.pNetworkConnection = _pNetworkConnection; - _IotTestNetworkInfo.pNetworkInterface = _pNetworkInterface; - - return true; -} - -/*-----------------------------------------------------------*/ - -void IotTest_NetworkCleanup( void ) -{ - /* Close the TCP connection to the server. */ - if( _pNetworkConnection != NULL ) - { - IotTest_NetworkClose( NULL ); - IotTest_NetworkDestroy( _pNetworkConnection ); - _pNetworkConnection = NULL; - } - - /* Clean up the network library. */ - IotTestNetwork_Cleanup(); - - /* Clear the network interface. */ - ( void ) memset( &_IotTestNetworkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); -} - -/*-----------------------------------------------------------*/ - -bool IotTest_NetworkConnect( IotTestNetworkConnection_t ** pNewConnection ) -{ - IotTestNetworkServerInfo_t serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - IotTestNetworkCredentials_t * pCredentials = NULL; - - /* Set up TLS if the endpoint is secured. These tests should always use ALPN. */ - #if IOT_TEST_SECURED_CONNECTION == 1 - IotTestNetworkCredentials_t credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; - pCredentials = &credentials; - #endif - - /* Open a connection to the test server. */ - if( _pNetworkInterface->create( &serverInfo, - pCredentials, - pNewConnection ) != IOT_NETWORK_SUCCESS ) - { - return false; - } - - return true; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ) -{ - /* Close the provided network handle; if that is NULL, close the - * global network handle. */ - if( ( pNetworkConnection != NULL ) && - ( pNetworkConnection != _pNetworkConnection ) ) - { - _pNetworkInterface->close( pNetworkConnection ); - } - else if( _pNetworkConnection != NULL ) - { - _pNetworkInterface->close( _pNetworkConnection ); - } - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -void IotTest_NetworkDestroy( void * pNetworkConnection ) -{ - if( ( pNetworkConnection != NULL ) && - ( pNetworkConnection != _pNetworkConnection ) ) - { - /* Wrap the network interface's destroy function. */ - _pNetworkInterface->destroy( pNetworkConnection ); - } - else - { - if( _pNetworkConnection != NULL ) - { - _pNetworkInterface->destroy( _pNetworkConnection ); - _pNetworkConnection = NULL; - } - } -} - -/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index 0b165e9dd6..4252bb1dd6 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -1,7 +1,6 @@ # MQTT tests executable. add_executable( iot_tests_mqtt iot_tests_mqtt.c - ${CMAKE_SOURCE_DIR}/tests/iot_tests_network.c unit/iot_tests_mqtt_api.c unit/iot_tests_mqtt_receive.c unit/iot_tests_mqtt_subscription.c diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 7ef977f54f..adbfa3fb2f 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -56,6 +56,9 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER + /* Test framework includes. */ #include "unity_fixture.h" @@ -153,27 +156,27 @@ typedef struct _publishParams /*-----------------------------------------------------------*/ -/* Network functions used by the tests, declared and implemented in one of - * the test network function files. */ -extern bool IotTest_NetworkSetup( void ); -extern void IotTest_NetworkCleanup( void ); - -/* Extern declarations of default serializer functions. The internal MQTT header cannot - * be included by this file. */ -extern IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ); +/** + * @brief Network server info to share among the tests. + */ +static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; -/* Network variables used by the tests, declared in one of the test network - * function files. */ -extern IotMqttNetworkInfo_t _IotTestNetworkInfo; -extern IotMqttConnection_t _IotTestMqttConnection; +/** + * @brief Network credential info to share among the tests. + */ +#if IOT_TEST_SECURED_CONNECTION == 1 + static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +#endif -/*-----------------------------------------------------------*/ +/** + * @brief An MQTT network setup parameter to share among the tests. + */ +static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; /** - * @brief Tracks whether the test MQTT connection has been created. + * @brief An MQTT connection to share among the tests. */ -static bool _connectionCreated = false; +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /** * @brief Filler text to publish. @@ -234,6 +237,13 @@ static bool _pingreqOverrideCalled = false; /*-----------------------------------------------------------*/ +/* Declaration of MQTT serializer function. The internal MQTT header cannot be + * included here. */ +extern IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ); + +/*-----------------------------------------------------------*/ + /** * @brief Serializer override for PINGREQ. */ @@ -268,7 +278,7 @@ static IotMqttError_t _checkConnection( void ) publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; /* Send a PUBLISH. */ - status = IotMqtt_Publish( _IotTestMqttConnection, + status = IotMqtt_Publish( _mqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, @@ -357,7 +367,7 @@ static void * _publishThread( void * pArgument ) publishInfo.pTopicName = _pTopicNames[ i % _TEST_TOPIC_NAME_COUNT ]; /* PUBLISH the message. */ - status = IotMqtt_Publish( _IotTestMqttConnection, + status = IotMqtt_Publish( _mqttConnection, &publishInfo, 0, NULL, @@ -423,7 +433,6 @@ TEST_SETUP( MQTT_Stress ) IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - const IotLogConfig_t logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; /* Initialize common components. */ TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); @@ -432,7 +441,7 @@ TEST_SETUP( MQTT_Stress ) _pingreqOverrideCalled = false; /* Empty log message to log a new line. */ - IotLog( IOT_LOG_INFO, &logHideAll, " " ); + IotLogInfo( "Stress tests starting." ); /* Create the publish counter semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &receivedPublishCounter, @@ -441,12 +450,12 @@ TEST_SETUP( MQTT_Stress ) /* Set the serializer overrides. */ serializer.serialize.pingreq = _serializePingreq; - _IotTestNetworkInfo.pMqttSerializer = &serializer; + _networkInfo.pMqttSerializer = &serializer; /* Set up the network stack. */ - if( IotTest_NetworkSetup() == false ) + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) { - TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + TEST_FAIL_MESSAGE( "Failed to set up network stack." ); } /* Initialize the MQTT library. */ @@ -468,6 +477,16 @@ TEST_SETUP( MQTT_Stress ) _CLIENT_IDENTIFIER_MAX_LENGTH ); #endif + /* Set the MQTT network setup parameters. */ + ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + _networkInfo.createNetworkConnection = true; + _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + + #if IOT_TEST_SECURED_CONNECTION == 1 + _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + #endif + /* Set the members of the connect info. */ connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; @@ -485,16 +504,14 @@ TEST_SETUP( MQTT_Stress ) /* Establish the MQTT connection. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotMqtt_Connect( &_IotTestNetworkInfo, + IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ) ); - - _connectionCreated = true; + &_mqttConnection ) ); /* Subscribe to the test topic filters. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotMqtt_TimedSubscribe( _IotTestMqttConnection, + IotMqtt_TimedSubscribe( _mqttConnection, pSubscriptions, _TEST_TOPIC_NAME_COUNT, 0, @@ -513,14 +530,14 @@ TEST_TEAR_DOWN( MQTT_Stress ) /* Disconnect the MQTT connection. Unsubscribe is not called; the subscriptions * should be cleaned up by Disconnect. */ - if( _connectionCreated == true ) + if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) { - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); - _connectionCreated = false; + IotMqtt_Disconnect( _mqttConnection, 0 ); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /* Clean up the network stack. */ - IotTest_NetworkCleanup(); + IotTestNetwork_Cleanup(); /* Clean up common components. */ IotCommon_Cleanup(); @@ -592,7 +609,7 @@ TEST( MQTT_Stress, BlockingCallback ) { /* Call a function that will invoke the blocking callback. */ TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, - IotMqtt_Publish( _IotTestMqttConnection, + IotMqtt_Publish( _mqttConnection, &publishInfo, 0, &callbackInfo, diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index ea13f277b5..95a5c910c8 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -119,20 +119,62 @@ typedef struct _operationCompleteParams /*-----------------------------------------------------------*/ -/* Network functions used by the tests, declared and implemented in one of - * the test network function files. */ -extern bool IotTest_NetworkSetup( void ); -extern void IotTest_NetworkCleanup( void ); -extern bool IotTest_NetworkConnect( IotTestNetworkConnection_t ** pNewConnection ); -extern IotNetworkError_t IotTest_NetworkClose( void * pNetworkConnection ); -extern void IotTest_NetworkDestroy( void * pConnection ); - -/* Network variables used by the tests, declared in one of the test network - * function files. */ -extern IotMqttNetworkInfo_t _IotTestNetworkInfo; -extern IotMqttConnection_t _IotTestMqttConnection; +/** + * @brief Network server info to share among the tests. + */ +static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; -/*-----------------------------------------------------------*/ +/** + * @brief Network credential info to share among the tests. + */ +#if IOT_TEST_SECURED_CONNECTION == 1 + static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +#endif + +/** + * @brief An MQTT network setup parameter to share among the tests. + */ +static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + +/** + * @brief MQTT serializer functions to call in the tests. May be overridden by a + * defined initializer. + */ +#ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER + static const IotMqttSerializer_t _mqttSerializer = IOT_TEST_MQTT_SERIALIZER_INITIALIZER; +#else + static const IotMqttSerializer_t _mqttSerializer = + { + .getPacketType = _IotMqtt_GetPacketType, + .getRemainingLength = _IotMqtt_GetRemainingLength, + .freePacket = _IotMqtt_FreePacket, + .serialize = + { + .connect = _IotMqtt_SerializeConnect, + .publish = _IotMqtt_SerializePublish, + .publishSetDup = _IotMqtt_PublishSetDup, + .puback = _IotMqtt_SerializePuback, + .subscribe = _IotMqtt_SerializeSubscribe, + .unsubscribe = _IotMqtt_SerializeUnsubscribe, + .pingreq = _IotMqtt_SerializePingreq, + .disconnect = _IotMqtt_SerializeDisconnect + }, + .deserialize = + { + .connack = _IotMqtt_DeserializeConnack, + .publish = _IotMqtt_DeserializePublish, + .puback = _IotMqtt_DeserializePuback, + .suback = _IotMqtt_DeserializeSuback, + .unsuback = _IotMqtt_DeserializeUnsuback, + .pingresp = _IotMqtt_DeserializePingresp + } + }; +#endif /* ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER */ + +/** + * @brief An MQTT connection to share among the tests. + */ +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /** * @brief Filler text to publish. @@ -175,7 +217,7 @@ static void _freePacket( uint8_t * pPacket ) { _freePacketOverride = true; - _IotMqtt_FreePacket( pPacket ); + _mqttSerializer.freePacket( pPacket ); } /*-----------------------------------------------------------*/ @@ -189,9 +231,9 @@ static IotMqttError_t _serializeConnect( const IotMqttConnectInfo_t * pConnectIn { _connectSerializerOverride = true; - return _IotMqtt_SerializeConnect( pConnectInfo, - pConnectPacket, - pPacketSize ); + return _mqttSerializer.serialize.connect( pConnectInfo, + pConnectPacket, + pPacketSize ); } /*-----------------------------------------------------------*/ @@ -207,11 +249,11 @@ static IotMqttError_t _serializePublish( const IotMqttPublishInfo_t * pPublishIn { _publishSerializerOverride = true; - return _IotMqtt_SerializePublish( pPublishInfo, - pPublishPacket, - pPacketSize, - pPacketIdentifier, - pPacketIdentifierHigh ); + return _mqttSerializer.serialize.publish( pPublishInfo, + pPublishPacket, + pPacketSize, + pPacketIdentifier, + pPacketIdentifierHigh ); } /*-----------------------------------------------------------*/ @@ -225,9 +267,9 @@ static IotMqttError_t _serializePuback( uint16_t packetIdentifier, { _pubackSerializerOverride = true; - return _IotMqtt_SerializePuback( packetIdentifier, - pPubackPacket, - pPacketSize ); + return _mqttSerializer.serialize.puback( packetIdentifier, + pPubackPacket, + pPacketSize ); } /*-----------------------------------------------------------*/ @@ -243,11 +285,11 @@ static IotMqttError_t _serializeSubscribe( const IotMqttSubscription_t * pSubscr { _subscribeSerializerOverride = true; - return _IotMqtt_SerializeSubscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); + return _mqttSerializer.serialize.subscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); } /*-----------------------------------------------------------*/ @@ -263,11 +305,11 @@ static IotMqttError_t _serializeUnsubscribe( const IotMqttSubscription_t * pSubs { _unsubscribeSerializerOverride = true; - return _IotMqtt_SerializeUnsubscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); + return _mqttSerializer.serialize.unsubscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); } /*-----------------------------------------------------------*/ @@ -280,8 +322,8 @@ static IotMqttError_t _serializeDisconnect( uint8_t ** pDisconnectPacket, { _disconnectSerializerOverride = true; - return _IotMqtt_SerializeDisconnect( pDisconnectPacket, - pPacketSize ); + return _mqttSerializer.serialize.disconnect( pDisconnectPacket, + pPacketSize ); } /*-----------------------------------------------------------*/ @@ -414,7 +456,7 @@ static void _reentrantCallback( void * pArgument, static void _subscribePublishWait( IotMqttQos_t qos ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = _IotTestNetworkInfo; + IotMqttNetworkInfo_t networkInfo = _networkInfo; IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; @@ -445,7 +487,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) status = IotMqtt_Connect( &networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -459,7 +501,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) /* Subscribe to the test topic filter using the blocking SUBSCRIBE * function. */ - status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, + status = IotMqtt_TimedSubscribe( _mqttConnection, &subscription, 1, 0, @@ -474,7 +516,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) publishInfo.payloadLength = _samplePayloadLength; /* Publish the message. */ - status = IotMqtt_TimedPublish( _IotTestMqttConnection, + status = IotMqtt_TimedPublish( _mqttConnection, &publishInfo, 0, IOT_TEST_MQTT_TIMEOUT_MS ); @@ -487,7 +529,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) } /* Unsubscribe from the test topic filter. */ - status = IotMqtt_TimedUnsubscribe( _IotTestMqttConnection, + status = IotMqtt_TimedUnsubscribe( _mqttConnection, &subscription, 1, 0, @@ -496,7 +538,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) } /* Close the MQTT connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); + IotMqtt_Disconnect( _mqttConnection, 0 ); } IotSemaphore_Destroy( &waitSem ); @@ -553,10 +595,10 @@ TEST_SETUP( MQTT_System ) TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } - /* Set up the network stack. */ - if( IotTest_NetworkSetup() == false ) + /* Call the network stack initialization function. */ + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) { - TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + TEST_FAIL_MESSAGE( "Failed to initialize network stack." ); } /* Generate a new, unique client identifier based on the time if no client @@ -571,6 +613,16 @@ TEST_SETUP( MQTT_System ) IOT_TEST_MQTT_CLIENT_IDENTIFIER, _CLIENT_IDENTIFIER_MAX_LENGTH ); #endif + + /* Set the MQTT network setup parameters. */ + ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + _networkInfo.createNetworkConnection = true; + _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + + #if IOT_TEST_SECURED_CONNECTION == 1 + _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + #endif } /*-----------------------------------------------------------*/ @@ -587,8 +639,10 @@ TEST_TEAR_DOWN( MQTT_System ) IotMqtt_Cleanup(); /* Clean up the network stack. */ - IotTest_NetworkCleanup(); - _IotTestMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotTestNetwork_Cleanup(); + + /* Clear the connection pointer. */ + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ @@ -676,17 +730,17 @@ TEST( MQTT_System, SubscribePublishAsync ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) { /* Subscribe to the test topic filter. */ callbackParam.expectedOperation = IOT_MQTT_SUBSCRIBE; - status = IotMqtt_Subscribe( _IotTestMqttConnection, + status = IotMqtt_Subscribe( _mqttConnection, &subscription, 1, 0, @@ -701,7 +755,7 @@ TEST( MQTT_System, SubscribePublishAsync ) /* Publish the message. */ callbackParam.expectedOperation = IOT_MQTT_PUBLISH_TO_SERVER; - status = IotMqtt_Publish( _IotTestMqttConnection, + status = IotMqtt_Publish( _mqttConnection, &publishInfo, 0, &callbackInfo, @@ -722,7 +776,7 @@ TEST( MQTT_System, SubscribePublishAsync ) /* Unsubscribe from the test topic filter. */ callbackParam.expectedOperation = IOT_MQTT_UNSUBSCRIBE; - status = IotMqtt_Unsubscribe( _IotTestMqttConnection, + status = IotMqtt_Unsubscribe( _mqttConnection, &subscription, 1, 0, @@ -736,7 +790,7 @@ TEST( MQTT_System, SubscribePublishAsync ) } } - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); + IotMqtt_Disconnect( _mqttConnection, 0 ); } IotSemaphore_Destroy( &publishWaitSem ); @@ -752,16 +806,14 @@ TEST( MQTT_System, SubscribePublishAsync ) */ TEST( MQTT_System, LastWillAndTestament ) { - bool lwtListenerCreated = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t lwtNetworkInfo = _IotTestNetworkInfo; + IotMqttNetworkInfo_t lwtNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; IotMqttConnection_t lwtListener = IOT_MQTT_CONNECTION_INITIALIZER; IotMqttConnectInfo_t lwtConnectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER, connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t willSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotTestNetworkConnection_t * pLwtListenerConnection = NULL; IotSemaphore_t waitSem; /* Create the wait semaphore. */ @@ -779,84 +831,74 @@ TEST( MQTT_System, LastWillAndTestament ) _CLIENT_IDENTIFIER_MAX_LENGTH ); #endif + /* Establish an independent MQTT over TCP connection to receive a Last + * Will and Testament message. */ + lwtNetworkInfo.createNetworkConnection = true; + lwtNetworkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + + #if IOT_TEST_SECURED_CONNECTION == 1 + lwtNetworkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + #endif + + lwtNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + + lwtConnectInfo.cleanSession = true; + lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; + lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); + if( TEST_PROTECT() ) { - /* Establish an independent MQTT over TCP connection to receive a Last - * Will and Testament message. */ - TEST_ASSERT_EQUAL( true, - IotTest_NetworkConnect( &pLwtListenerConnection ) ); - lwtListenerCreated = true; + status = IotMqtt_Connect( &lwtNetworkInfo, + &lwtConnectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &lwtListener ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) { - lwtNetworkInfo.createNetworkConnection = false; - lwtNetworkInfo.pNetworkConnection = pLwtListenerConnection; - lwtConnectInfo.cleanSession = true; - lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; - lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); - - status = IotMqtt_Connect( &lwtNetworkInfo, - &lwtConnectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &lwtListener ); + /* Register a subscription for the LWT. */ + willSubscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); + willSubscription.callback.function = _publishReceived; + willSubscription.callback.pCallbackContext = &waitSem; + + status = IotMqtt_TimedSubscribe( lwtListener, + &willSubscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - if( TEST_PROTECT() ) - { - /* Register a subscription for the LWT. */ - willSubscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; - willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); - willSubscription.callback.function = _publishReceived; - willSubscription.callback.pCallbackContext = &waitSem; - - status = IotMqtt_TimedSubscribe( lwtListener, - &willSubscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Create a connection that requests the LWT. */ - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - connectInfo.pWillInfo = &willInfo; + /* Create a connection that requests the LWT. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + connectInfo.pWillInfo = &willInfo; - willInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; - willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); - willInfo.pPayload = _pSamplePayload; - willInfo.payloadLength = _samplePayloadLength; + willInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); + willInfo.pPayload = _pSamplePayload; + willInfo.payloadLength = _samplePayloadLength; - status = IotMqtt_Connect( &_IotTestNetworkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - /* Abruptly close the MQTT connection. This should cause the LWT - * to be sent to the LWT listener. */ - IotTest_NetworkClose( NULL ); - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); - IotTest_NetworkDestroy( NULL ); + /* Abruptly close the MQTT connection. This should cause the LWT + * to be sent to the LWT listener. */ + IotMqtt_Disconnect( _mqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - /* Check that the LWT was received. */ - if( IotSemaphore_TimedWait( &waitSem, - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); - } + /* Check that the LWT was received. */ + if( IotSemaphore_TimedWait( &waitSem, + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); } - - IotMqtt_Disconnect( lwtListener, 0 ); - IotTest_NetworkDestroy( pLwtListenerConnection ); - lwtListenerCreated = false; } - if( lwtListenerCreated == true ) - { - IotTest_NetworkClose( pLwtListenerConnection ); - IotTest_NetworkDestroy( pLwtListenerConnection ); - } + IotMqtt_Disconnect( lwtListener, 0 ); } IotSemaphore_Destroy( &waitSem ); @@ -886,10 +928,10 @@ TEST( MQTT_System, RestorePreviousSession ) if( TEST_PROTECT() ) { /* Establish a persistent MQTT connection. */ - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Add a subscription. */ @@ -898,7 +940,7 @@ TEST( MQTT_System, RestorePreviousSession ) subscription.callback.pCallbackContext = &waitSem; subscription.callback.function = _publishReceived; - status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, + status = IotMqtt_TimedSubscribe( _mqttConnection, &subscription, 1, 0, @@ -906,20 +948,17 @@ TEST( MQTT_System, RestorePreviousSession ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Disconnect the MQTT connection and clean up network connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); - IotTest_NetworkCleanup(); - - /* Re-establish the network connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_NetworkSetup() ); + IotMqtt_Disconnect( _mqttConnection, 0 ); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /* Re-establish the MQTT connection with a previous session. */ connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ @@ -928,7 +967,7 @@ TEST( MQTT_System, RestorePreviousSession ) publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - status = IotMqtt_TimedPublish( _IotTestMqttConnection, + status = IotMqtt_TimedPublish( _mqttConnection, &publishInfo, 0, IOT_TEST_MQTT_TIMEOUT_MS ); @@ -942,33 +981,35 @@ TEST( MQTT_System, RestorePreviousSession ) } /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); - IotTest_NetworkCleanup(); + IotMqtt_Disconnect( _mqttConnection, 0 ); } else { - /* Close network connection on test failure. */ - IotTest_NetworkClose( NULL ); + /* Close MQTT connection on test failure. */ + if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + { + IotMqtt_Disconnect( _mqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + } } IotSemaphore_Destroy( &waitSem ); if( TEST_PROTECT() ) { - /* Re-establish the network connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_NetworkSetup() ); - /* After this test is finished, establish one more connection with a clean * session to clean up persistent sessions on the MQTT server created by this * test. */ connectInfo.cleanSession = true; - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + &_mqttConnection ); - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); + if( status == IOT_MQTT_SUCCESS ) + { + IotMqtt_Disconnect( _mqttConnection, 0 ); + } } } @@ -999,10 +1040,10 @@ TEST( MQTT_System, WaitAfterDisconnect ) publishInfo.retryMs = 5000; /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -1010,7 +1051,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) /* Publish a sequence of messages. */ for( i = 0; i < 3; i++ ) { - status = IotMqtt_Publish( _IotTestMqttConnection, + status = IotMqtt_Publish( _mqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, @@ -1020,7 +1061,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) } /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); + IotMqtt_Disconnect( _mqttConnection, 0 ); if( TEST_PROTECT() ) { @@ -1066,10 +1107,10 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with a completion callback. */ @@ -1082,7 +1123,7 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) callbackInfo.function = _reentrantCallback; callbackInfo.pCallbackContext = pWaitSemaphores; - status = IotMqtt_Subscribe( _IotTestMqttConnection, + status = IotMqtt_Subscribe( _mqttConnection, &subscription, 1, 0, @@ -1137,10 +1178,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_IotTestNetworkInfo, + status = IotMqtt_Connect( &_networkInfo, &connectInfo, IOT_TEST_MQTT_TIMEOUT_MS, - &_IotTestMqttConnection ); + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with to the test topics. */ @@ -1156,7 +1197,7 @@ TEST( MQTT_System, IncomingPublishReentrancy ) pSubscription[ 1 ].callback.function = _publishReceived; pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); - status = IotMqtt_TimedSubscribe( _IotTestMqttConnection, + status = IotMqtt_TimedSubscribe( _mqttConnection, pSubscription, 2, 0, @@ -1172,7 +1213,7 @@ TEST( MQTT_System, IncomingPublishReentrancy ) publishInfo.retryLimit = 3; publishInfo.retryMs = 5000; - status = IotMqtt_TimedPublish( _IotTestMqttConnection, + status = IotMqtt_TimedPublish( _mqttConnection, &publishInfo, 0, IOT_TEST_MQTT_TIMEOUT_MS ); diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index 870d76c669..9eecb847d9 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -1,7 +1,6 @@ # Shadow tests executable. add_executable( aws_iot_tests_shadow aws_iot_tests_shadow.c - ${CMAKE_SOURCE_DIR}/tests/iot_tests_network.c unit/aws_iot_tests_shadow_api.c unit/aws_iot_tests_shadow_parser.c system/aws_iot_tests_shadow_system.c ) diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 4a07da73ed..b5b80ed782 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -44,6 +44,9 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER + /* MQTT include. */ #include "iot_mqtt.h" @@ -120,20 +123,27 @@ typedef struct _operationCompleteParams /*-----------------------------------------------------------*/ -/* Network functions used by the tests, declared and implemented in one of - * the test network function files. */ -extern bool IotTest_NetworkSetup( void ); -extern void IotTest_NetworkCleanup( void ); +/** + * @brief Network server info to share among the tests. + */ +static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + +/** + * @brief Network credential info to share among the tests. + */ +#if IOT_TEST_SECURED_CONNECTION == 1 +static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +#endif -/* Network variables used by the tests, declared in one of the test network - * function files. */ -extern IotMqttNetworkInfo_t _IotTestNetworkInfo; -extern IotMqttConnection_t _IotTestMqttConnection; +/** + * @brief An MQTT network setup parameter to share among the tests. + */ +static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; /** - * @brief Tracks whether connection cleanup should be done. + * @brief An MQTT connection to share among the tests. */ -static bool _connectionCreated = false; +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*-----------------------------------------------------------*/ @@ -301,7 +311,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ - status = AwsIotShadow_Update( _IotTestMqttConnection, + status = AwsIotShadow_Update( _mqttConnection, &documentInfo, 0, &callbackInfo, @@ -317,7 +327,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) callbackParam.expectedType = AWS_IOT_SHADOW_GET_COMPLETE; /* Retrieve the Shadow document. */ - status = AwsIotShadow_Get( _IotTestMqttConnection, + status = AwsIotShadow_Get( _mqttConnection, &documentInfo, 0, &callbackInfo, @@ -334,7 +344,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) callbackParam.expectedType = AWS_IOT_SHADOW_DELETE_COMPLETE; /* Delete the Shadow document. */ - status = AwsIotShadow_Delete( _IotTestMqttConnection, + status = AwsIotShadow_Delete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -373,7 +383,7 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ - status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _mqttConnection, &documentInfo, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -383,7 +393,7 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) documentInfo.get.mallocDocument = IotTest_Malloc; /* Retrieve the Shadow document. */ - status = AwsIotShadow_TimedGet( _IotTestMqttConnection, + status = AwsIotShadow_TimedGet( _mqttConnection, &documentInfo, 0, AWS_IOT_TEST_SHADOW_TIMEOUT, @@ -407,7 +417,7 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) IotTest_Free( ( void * ) pShadowDocument ); /* Delete the Shadow document. */ - status = AwsIotShadow_TimedDelete( _IotTestMqttConnection, + status = AwsIotShadow_TimedDelete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -439,9 +449,9 @@ TEST_SETUP( Shadow_System ) } /* Set up the network stack. */ - if( IotTest_NetworkSetup() == false ) + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) { - TEST_FAIL_MESSAGE( "Failed to set up network connection." ); + TEST_FAIL_MESSAGE( "Failed to set up network stack." ); } /* Initialize the MQTT library. */ @@ -456,27 +466,34 @@ TEST_SETUP( Shadow_System ) TEST_FAIL_MESSAGE( "Failed to initialize Shadow library." ); } + /* Set the MQTT network setup parameters. */ + ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + _networkInfo.createNetworkConnection = true; + _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + + #if IOT_TEST_SECURED_CONNECTION == 1 + _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + #endif + /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT * client identifier. */ + connectInfo.awsIotMqttMode = true; connectInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; /* Establish an MQTT connection. */ - if( IotMqtt_Connect( &_IotTestNetworkInfo, + if( IotMqtt_Connect( &_networkInfo, &connectInfo, AWS_IOT_TEST_SHADOW_TIMEOUT, - &_IotTestMqttConnection ) != IOT_MQTT_SUCCESS ) + &_mqttConnection ) != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); } - else - { - _connectionCreated = true; - } /* Delete any existing Shadow so all tests start with no Shadow. */ - status = AwsIotShadow_TimedDelete( _IotTestMqttConnection, + status = AwsIotShadow_TimedDelete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -498,18 +515,17 @@ TEST_SETUP( Shadow_System ) TEST_TEAR_DOWN( Shadow_System ) { /* Disconnect the MQTT connection if it was created. */ - if( _connectionCreated == true ) + if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) { - IotMqtt_Disconnect( _IotTestMqttConnection, 0 ); - - _connectionCreated = false; + IotMqtt_Disconnect( _mqttConnection, 0 ); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /* Clean up the Shadow library. */ AwsIotShadow_Cleanup(); /* Clean up the network stack. */ - IotTest_NetworkCleanup(); + IotTestNetwork_Cleanup(); /* Clean up common components. */ IotCommon_Cleanup(); @@ -601,7 +617,7 @@ TEST( Shadow_System, DeltaCallback ) if( TEST_PROTECT() ) { /* Set the delta callback. */ - status = AwsIotShadow_SetDeltaCallback( _IotTestMqttConnection, + status = AwsIotShadow_SetDeltaCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -609,7 +625,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Create a Shadow document with a desired state. */ - status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _mqttConnection, &updateDocument, AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -620,7 +636,7 @@ TEST( Shadow_System, DeltaCallback ) updateDocument.update.updateDocumentLength = 67; /* Create a Shadow document with a reported state. */ - status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _mqttConnection, &updateDocument, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -633,7 +649,7 @@ TEST( Shadow_System, DeltaCallback ) } /* Remove the delta callback. */ - status = AwsIotShadow_SetDeltaCallback( _IotTestMqttConnection, + status = AwsIotShadow_SetDeltaCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -641,7 +657,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Remove persistent subscriptions for Shadow Update. */ - status = AwsIotShadow_RemovePersistentSubscriptions( _IotTestMqttConnection, + status = AwsIotShadow_RemovePersistentSubscriptions( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ); @@ -676,7 +692,7 @@ TEST( Shadow_System, UpdatedCallback ) if( TEST_PROTECT() ) { /* Set the updated callback. */ - status = AwsIotShadow_SetUpdatedCallback( _IotTestMqttConnection, + status = AwsIotShadow_SetUpdatedCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, @@ -684,7 +700,7 @@ TEST( Shadow_System, UpdatedCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Create a Shadow document. */ - status = AwsIotShadow_TimedUpdate( _IotTestMqttConnection, + status = AwsIotShadow_TimedUpdate( _mqttConnection, &updateDocument, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -697,7 +713,7 @@ TEST( Shadow_System, UpdatedCallback ) } /* Remove the updated callback. */ - status = AwsIotShadow_SetUpdatedCallback( _IotTestMqttConnection, + status = AwsIotShadow_SetUpdatedCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, _THING_NAME_LENGTH, 0, From 5d5267647e1da2ff1387a2539849d4a2eb79ee02 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 20 Mar 2019 12:56:43 -0700 Subject: [PATCH 056/844] Fix memory leak with incoming PUBLISH messages. (#332) --- lib/source/mqtt/iot_mqtt_api.c | 39 +++++++++- lib/source/mqtt/iot_mqtt_network.c | 75 +++++++++++++++++-- lib/source/mqtt/iot_mqtt_operation.c | 16 +++- scripts/ci_test_general.sh | 2 +- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 3 + .../system/aws_iot_tests_shadow_system.c | 8 +- 6 files changed, 126 insertions(+), 17 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 7da15d1693..eabf05e870 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -191,15 +191,46 @@ static void _mqttSubscription_tryDestroy( void * pData ) static void _mqttOperation_tryDestroy( void * pData ) { _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - /* Decrement reference count and destroy operation if possible. */ - if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) + /* Incoming PUBLISH operations may always be freed. */ + if( pOperation->incomingPublish == true ) { - _IotMqtt_DestroyOperation( pOperation ); + /* Cancel the incoming PUBLISH operation's job. */ + taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + &( pOperation->job ), + NULL ); + + /* If the operation's job was not canceled, it must be already executing. + * Any other return value is invalid. */ + IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || + ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); + + /* Check if the incoming PUBLISH job was canceled. */ + if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) + { + /* Job was canceled. Process incoming PUBLISH now to clean up. */ + _IotMqtt_ProcessIncomingPublish( IOT_SYSTEM_TASKPOOL, + &( pOperation->job ), + pOperation ); + } + else + { + /* The executing job will process the PUBLISH, so nothing is done here. */ + _EMPTY_ELSE_MARKER; + } } else { - _EMPTY_ELSE_MARKER; + /* Decrement reference count and destroy operation if possible. */ + if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + else + { + _EMPTY_ELSE_MARKER; + } } } diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index dbbab68fc9..7b909d05b1 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -302,6 +302,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->status = status; _IotMqtt_Notify( pOperation ); } + else + { + _EMPTY_ELSE_MARKER; + } break; @@ -318,13 +322,14 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne break; } - - /* Set the members of the incoming PUBLISH operation. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - pOperation->incomingPublish = true; - pOperation->pMqttConnection = pMqttConnection; - - pIncomingPacket->pIncomingPublish = pOperation; + else + { + /* Set the members of the incoming PUBLISH operation. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + pOperation->incomingPublish = true; + pOperation->pMqttConnection = pMqttConnection; + pIncomingPacket->pIncomingPublish = pOperation; + } /* Choose a PUBLISH deserializer. */ deserialize = _IotMqtt_DeserializePublish; @@ -336,6 +341,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { deserialize = pMqttConnection->pSerializer->deserialize.publish; } + else + { + _EMPTY_ELSE_MARKER; + } } else { @@ -362,6 +371,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->pReceivedData = pIncomingPacket->pRemainingData; pIncomingPacket->pRemainingData = NULL; + /* Add the PUBLISH to the list of operations pending processing. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + /* Increment the MQTT connection reference count before scheduling an * incoming PUBLISH. */ if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) @@ -374,6 +389,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne status = IOT_MQTT_NETWORK_ERROR; } } + else + { + _EMPTY_ELSE_MARKER; + } /* Free PUBLISH operation on error. */ if( status != IOT_MQTT_SUCCESS ) @@ -392,9 +411,27 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMqtt_Assert( pIncomingPacket->pRemainingData != NULL ); } + /* Remove operation from pending processing list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + IotMqtt_Assert( pOperation != NULL ); IotMqtt_FreeOperation( pOperation ); } + else + { + _EMPTY_ELSE_MARKER; + } break; @@ -433,6 +470,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->status = status; _IotMqtt_Notify( pOperation ); } + else + { + _EMPTY_ELSE_MARKER; + } break; @@ -472,6 +513,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->status = status; _IotMqtt_Notify( pOperation ); } + else + { + _EMPTY_ELSE_MARKER; + } break; @@ -488,6 +533,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { deserialize = pMqttConnection->pSerializer->deserialize.unsuback; } + else + { + _EMPTY_ELSE_MARKER; + } } else { @@ -506,6 +555,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->status = status; _IotMqtt_Notify( pOperation ); } + else + { + _EMPTY_ELSE_MARKER; + } break; @@ -524,6 +577,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { deserialize = pMqttConnection->pSerializer->deserialize.pingresp; } + else + { + _EMPTY_ELSE_MARKER; + } } else { @@ -553,6 +610,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } + else + { + _EMPTY_ELSE_MARKER; + } break; } diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index fc8f09e7fa..d426616442 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -779,6 +779,20 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, IotMqtt_Assert( pOperation->incomingPublish == true ); IotMqtt_Assert( pPublishJob == &( pOperation->job ) ); + /* Remove the operation from the pending processing list. */ + IotMutex_Lock( &( pOperation->pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + + IotMutex_Unlock( &( pOperation->pMqttConnection->referencesMutex ) ); + /* Process the current PUBLISH. */ callbackParam.message.info = pOperation->publishInfo; @@ -788,7 +802,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, /* Free any buffers associated with the current PUBLISH message. */ if( pOperation->pReceivedData != NULL ) { - IotMqtt_FreeMessage( ( void* ) pOperation->pReceivedData ); + IotMqtt_FreeMessage( ( void * ) pOperation->pReceivedData ); } else { diff --git a/scripts/ci_test_general.sh b/scripts/ci_test_general.sh index 39cf88e3d2..ee2d77e258 100644 --- a/scripts/ci_test_general.sh +++ b/scripts/ci_test_general.sh @@ -9,5 +9,5 @@ set -e # Run test coverage script only for commit builds. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - echo "Nothing to do right now." + bash ../scripts/general/ci_test_coverage.sh fi diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 7c29cdffa5..fda0b7ba83 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -982,6 +982,9 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) pIncomingPublish[ i ]->publishInfo.pTopicName = "/test"; pIncomingPublish[ i ]->publishInfo.topicNameLength = 5; pIncomingPublish[ i ]->publishInfo.pPayload = ""; + + IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), + &( pIncomingPublish[ i ]->link ) ); } if( TEST_PROTECT() ) diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index b5b80ed782..5a139a22e5 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -82,10 +82,10 @@ extern int snprintf( char *, * Provide default values of test configuration constants. */ #ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S - #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) + #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) #endif #ifndef AWS_IOT_TEST_SHADOW_TIMEOUT - #define AWS_IOT_TEST_SHADOW_TIMEOUT ( 5000 ) + #define AWS_IOT_TEST_SHADOW_TIMEOUT ( 5000 ) #endif /** @endcond */ @@ -132,7 +132,7 @@ static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_IN * @brief Network credential info to share among the tests. */ #if IOT_TEST_SECURED_CONNECTION == 1 -static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; + static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; #endif /** @@ -473,7 +473,7 @@ TEST_SETUP( Shadow_System ) _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; #endif /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT From a8f90a9cf900edb5029e68189df4c82e4308ee61 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 20 Mar 2019 14:25:44 -0700 Subject: [PATCH 057/844] Revert coverage script. --- scripts/ci_test_general.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci_test_general.sh b/scripts/ci_test_general.sh index ee2d77e258..39cf88e3d2 100644 --- a/scripts/ci_test_general.sh +++ b/scripts/ci_test_general.sh @@ -9,5 +9,5 @@ set -e # Run test coverage script only for commit builds. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - bash ../scripts/general/ci_test_coverage.sh + echo "Nothing to do right now." fi From 07b230efff3ee2187fd5877847b98425c19cd3e4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 20 Mar 2019 22:54:44 -0700 Subject: [PATCH 058/844] Reduce the usage of 64-bit integers. (#333) --- lib/include/aws_iot_defender.h | 4 ++-- lib/include/aws_iot_shadow.h | 14 +++++++------- lib/include/iot_mqtt.h | 12 ++++++------ lib/include/iot_taskpool.h | 2 +- lib/include/platform/iot_clock.h | 4 ++-- lib/include/platform/iot_threads.h | 2 +- lib/include/private/aws_iot_shadow_internal.h | 4 ++-- lib/include/private/iot_mqtt_internal.h | 6 +++--- lib/include/types/aws_iot_shadow_types.h | 2 +- lib/include/types/iot_mqtt_types.h | 2 +- lib/source/common/iot_taskpool.c | 2 +- lib/source/defender/aws_iot_defender_api.c | 8 ++++---- lib/source/mqtt/iot_mqtt_api.c | 10 +++++----- lib/source/mqtt/iot_mqtt_operation.c | 8 ++++---- lib/source/shadow/aws_iot_shadow_api.c | 12 ++++++------ platform/source/posix/iot_clock_posix.c | 10 +++++----- platform/source/posix/iot_threads_posix.c | 4 ++-- 17 files changed, 53 insertions(+), 53 deletions(-) diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index f0c7a526be..3557b781c2 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -369,7 +369,7 @@ void AwsIotDefender_Stop( void ); * @note If this function is called when defender agent is started, the period is re-calculated and updated in next iteration. */ /* @[declare_defender_setperiod] */ -AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint64_t periodSeconds ); +AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ); /* @[declare_defender_setperiod] */ /** @@ -378,7 +378,7 @@ AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint64_t periodSeconds ); * @brief Get period in seconds. */ /* @[declare_defender_getperiod] */ -uint64_t AwsIotDefender_GetPeriod( void ); +uint32_t AwsIotDefender_GetPeriod( void ); /* @[declare_defender_getperiod] */ /** diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 49fafe1bad..3ca0d1a9cb 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -87,7 +87,7 @@ * @see @ref shadow_function_cleanup */ /* @[declare_shadow_init] */ -AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeout ); +AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeout ); /* @[declare_shadow_init] */ /** @@ -127,7 +127,7 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection const char * pThingName, size_t thingNameLength, uint32_t flags, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_shadow_timeddelete] */ /** @@ -149,7 +149,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, - uint64_t timeoutMs, + uint32_t timeoutMs, const char ** pShadowDocument, size_t * pShadowDocumentLength ); /* @[declare_shadow_timedget] */ @@ -173,7 +173,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_shadow_timedupdate] */ /** @@ -223,7 +223,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * * // Reference and timeout. * AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - * uint64_t timeout = 5000; // 5 seconds + * uint32_t timeout = 5000; // 5 seconds * * // Shadow update operation. * result = AwsIotShadow_Update( mqttConnection, @@ -252,7 +252,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * * // Reference and timeout. * AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - * uint64_t timeout = 5000; // 5 seconds + * uint32_t timeout = 5000; // 5 seconds * * // Buffer pointer and size for retrieved Shadow document. * const char * pShadowDocument = NULL; @@ -292,7 +292,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection */ /* @[declare_shadow_wait] */ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, - uint64_t timeoutMs, + uint32_t timeoutMs, const char ** pShadowDocument, size_t * pShadowDocumentLength ); /* @[declare_shadow_wait] */ diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 6256d957a5..08d4ca711d 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -252,7 +252,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, /* @[declare_mqtt_connect] */ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, const IotMqttConnectInfo_t * pConnectInfo, - uint64_t timeoutMs, + uint32_t timeoutMs, IotMqttConnection_t * pMqttConnection ); /* @[declare_mqtt_connect] */ @@ -464,7 +464,7 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_mqtt_timedsubscribe] */ /** @@ -545,7 +545,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_mqtt_timedunsubscribe] */ /** @@ -677,7 +677,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_mqtt_timedpublish] */ /** @@ -708,7 +708,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, * @code{c} * // Operation reference and timeout. * IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - * uint64_t timeoutMs = 5000; // 5 seconds + * uint32_t timeoutMs = 5000; // 5 seconds * * // MQTT operation to wait for. * IotMqttError_t result = IotMqtt_Publish( mqttConnection, @@ -731,7 +731,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, */ /* @[declare_mqtt_wait] */ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_mqtt_wait] */ /*-------------------------- MQTT helper functions --------------------------*/ diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 66287946e2..eefb39c8d8 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -432,7 +432,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /* @[declare_taskpool_scheduledeferred] */ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, - uint64_t timeMs ); + uint32_t timeMs ); /* @[declare_taskpool_scheduledeferred] */ /** diff --git a/lib/include/platform/iot_clock.h b/lib/include/platform/iot_clock.h index d41988db7d..d858575c89 100644 --- a/lib/include/platform/iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -207,8 +207,8 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ); */ /* @[declare_platform_clock_timerarm] */ bool IotClock_TimerArm( IotTimer_t * pTimer, - uint64_t relativeTimeoutMs, - uint64_t periodMs ); + uint32_t relativeTimeoutMs, + uint32_t periodMs ); /* @[declare_platform_clock_timerarm] */ #endif /* ifndef _IOT_CLOCK_H_ */ diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index 5d8c794abd..fa17755605 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -334,7 +334,7 @@ bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ); */ /* @[declare_platform_threads_semaphoretimedwait] */ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint64_t timeoutMs ); + uint32_t timeoutMs ); /* @[declare_platform_threads_semaphoretimedwait] */ /** diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index f9eb7f60aa..2e2a1d6dea 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -327,7 +327,7 @@ typedef IotMqttError_t ( * _mqttOperationFunction_t )( IotMqttConnection_t, const IotMqttSubscription_t *, size_t, uint32_t, - uint64_t ); + uint32_t ); /** * @brief Function pointer representing an MQTT library callback function. @@ -451,7 +451,7 @@ typedef struct _shadowSubscription #endif /* Declarations of variables for internal Shadow files. */ -extern uint64_t _AwsIotShadowMqttTimeoutMs; +extern uint32_t _AwsIotShadowMqttTimeoutMs; extern IotListDouble_t _AwsIotShadowPendingOperations; extern IotListDouble_t _AwsIotShadowSubscriptions; extern IotMutex_t _AwsIotShadowPendingOperationsMutex; diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 40d4fda0bb..44c9fd7add 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -287,7 +287,7 @@ typedef struct _mqttConnection bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ - uint64_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ + uint32_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ @@ -369,7 +369,7 @@ typedef struct _mqttOperation { uint32_t count; uint32_t limit; - uint64_t nextPeriod; + uint32_t nextPeriod; } retry; }; @@ -807,7 +807,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, */ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, IotTaskPoolRoutine_t jobRoutine, - uint64_t delay ); + uint32_t delay ); /** * @brief Search a list of MQTT operations pending responses using an operation diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 6beba6b92a..e6cc5c8e43 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -451,7 +451,7 @@ typedef struct AwsIotShadowDocumentInfo IotMqttQos_t qos; /**< @brief QoS when sending a Shadow get or update message. See #IotMqttPublishInfo_t.qos. */ uint32_t retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #IotMqttPublishInfo_t.retryLimit. */ - uint64_t retryMs; /**< @brief First retry time for a Shadow get or update message. See IotMqttPublishInfo_t.retryMs. */ + uint32_t retryMs; /**< @brief First retry time for a Shadow get or update message. See IotMqttPublishInfo_t.retryMs. */ union { diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 6df1450fb8..243131834c 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -392,7 +392,7 @@ typedef struct IotMqttPublishInfo const void * pPayload; /**< @brief Payload of PUBLISH. */ size_t payloadLength; /**< @brief Length of #IotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ - uint64_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ + uint32_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ uint32_t retryLimit; /**< @brief How many times to attempt retransmission. */ } IotMqttPublishInfo_t; diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 00d58cad33..c5a95082d9 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -624,7 +624,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool, IotTaskPoolJob_t * const pJob, - uint64_t timeMs ) + uint32_t timeMs ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 3b9d5a0d4f..70247740c8 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -71,7 +71,7 @@ _defenderMetrics_t _AwsIotDefenderMetrics = * Period between reports in milliseconds. * Set default value. */ -static uint64_t _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); +static uint32_t _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); static IotTaskPoolJob_t _metricsPublishJob = { 0 }; @@ -267,10 +267,10 @@ void AwsIotDefender_Stop( void ) /*-----------------------------------------------------------*/ -AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint64_t periodSeconds ) +AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ) { /* Input period cannot be too long, which will cause integer overflow. */ - AwsIotDefender_Assert( periodSeconds < _defenderToSeconds( UINT64_MAX ) ); + AwsIotDefender_Assert( periodSeconds < _defenderToSeconds( UINT32_MAX ) ); AwsIotDefenderError_t defenderError = AWS_IOT_DEFENDER_INTERNAL_FAILURE; @@ -293,7 +293,7 @@ AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint64_t periodSeconds ) /*-----------------------------------------------------------*/ -uint64_t AwsIotDefender_GetPeriod( void ) +uint32_t AwsIotDefender_GetPeriod( void ) { return _defenderToSeconds( _periodMilliSecond ); } diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index eabf05e870..3d7dce301b 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -861,7 +861,7 @@ void IotMqtt_Cleanup() IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, const IotMqttConnectInfo_t * pConnectInfo, - uint64_t timeoutMs, + uint32_t timeoutMs, IotMqttConnection_t * pMqttConnection ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); @@ -1383,7 +1383,7 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; @@ -1439,7 +1439,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; @@ -1730,7 +1730,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, @@ -1780,7 +1780,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_SUCCESS; _mqttConnection_t * pMqttConnection = operation->pMqttConnection; diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index d426616442..6357d0cd8c 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -205,7 +205,7 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) { bool firstRetry = false; - uint64_t scheduleDelay = 0; + uint32_t scheduleDelay = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; @@ -244,12 +244,12 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) _EMPTY_ELSE_MARKER; } - IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %llu ms.", + IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", pMqttConnection, pOperation, ( unsigned long ) pOperation->retry.count, ( unsigned long ) pOperation->retry.limit, - ( unsigned long long ) scheduleDelay ); + ( unsigned long ) scheduleDelay ); /* Check if this is the first retry. */ firstRetry = ( pOperation->retry.count == 1 ); @@ -1039,7 +1039,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, IotTaskPoolRoutine_t jobRoutine, - uint64_t delay ) + uint32_t delay ) { IotMqttError_t status = IOT_MQTT_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index a6c002c2e8..be813b41c8 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -154,7 +154,7 @@ static void _updatedCallbackWrapper( void * pArgument, /** * @brief Timeout used for MQTT operations. */ -uint64_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; +uint32_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -581,7 +581,7 @@ static void _updatedCallbackWrapper( void * pArgument, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Init( uint64_t mqttTimeoutMs ) +AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) { /* Create the Shadow pending operation list mutex. */ if( IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ), false ) == false ) @@ -713,7 +713,7 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection const char * pThingName, size_t thingNameLength, uint32_t flags, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -821,7 +821,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, - uint64_t timeoutMs, + uint32_t timeoutMs, const char ** pShadowDocument, size_t * pShadowDocumentLength ) { @@ -975,7 +975,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -1005,7 +1005,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, - uint64_t timeoutMs, + uint32_t timeoutMs, const char ** pShadowDocument, size_t * pShadowDocumentLength ) { diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index 9d751d2cdb..1ec2af99f0 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -105,7 +105,7 @@ static void _timerExpirationWrapper( union sigval argument ); * * @return `true` if `timeoutMs` was successfully converted; `false` otherwise. */ -bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, +bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, struct timespec * pOutput ); /*-----------------------------------------------------------*/ @@ -120,7 +120,7 @@ static void _timerExpirationWrapper( union sigval argument ) /*-----------------------------------------------------------*/ -bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, +bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, struct timespec * pOutput ) { bool status = true; @@ -275,8 +275,8 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) /*-----------------------------------------------------------*/ bool IotClock_TimerArm( IotTimer_t * pTimer, - uint64_t relativeTimeoutMs, - uint64_t periodMs ) + uint32_t relativeTimeoutMs, + uint32_t periodMs ) { bool status = true; struct itimerspec timerExpiration = @@ -285,7 +285,7 @@ bool IotClock_TimerArm( IotTimer_t * pTimer, .it_interval = { 0 } }; - IotLogDebug( "Arming timer %p with timeout %llu and period %llu.", + IotLogDebug( "Arming timer %p with timeout %lu and period %lu.", pTimer, relativeTimeoutMs, periodMs ); diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 0495e4bca4..31696322bd 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -105,7 +105,7 @@ static void * _threadRoutineWrapper( void * pArgument ); /* Platform-specific function implemented in iot_clock_posix.c */ -extern bool IotClock_TimeoutToTimespec( uint64_t timeoutMs, +extern bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, struct timespec * pOutput ); /*-----------------------------------------------------------*/ @@ -472,7 +472,7 @@ bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) /*-----------------------------------------------------------*/ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint64_t timeoutMs ) + uint32_t timeoutMs ) { bool status = true; struct timespec timeout = { 0 }; From bc49800d6b43b76401d22ab476d29f91d24c0b56 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Thu, 21 Mar 2019 10:04:55 -0700 Subject: [PATCH 059/844] Fixed race condition on task pool destruction sequence when a deferred job is scheduled. (#335) --- lib/source/common/iot_taskpool.c | 50 ++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index c5a95082d9..f69aadfdec 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -58,7 +58,7 @@ /** * @brief Maximum semaphore value for wait operations. */ -#define TASKPOOL_MAX_SEM_VALUE 0xFFFF +#define TASKPOOL_MAX_SEM_VALUE 0xFFFF /** * @brief Reschedule delay in milliseconds for deferred jobs. @@ -289,6 +289,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); uint32_t count; + bool completeShutdown = true; /* Track how many threads the task pool owns. */ uint32_t activeThreads; @@ -328,21 +329,46 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) } while( pItemLink ); /* (2) Clear the timer queue. */ - do { - pItemLink = NULL; + _taskPoolTimerEvent_t * pTimerEvent; + + /* A deferred job may have fired already. Since deferred jobs will go through the same mutex + * the shutdown sequence is holding at this stage, there is no risk for race conditions. Yet, we + * need to let the deferred job to destroy the task pool. */ - pItemLink = IotListDouble_RemoveHead( &pTaskPool->timerEventsList ); + pItemLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); if( pItemLink != NULL ) { - _taskPoolTimerEvent_t * pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pItemLink, link ); + uint64_t now = IotClock_GetTimeMs(); + + pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pItemLink, link ); + + if( pTimerEvent->expirationTime <= now ) + { + /* Timer may have fired already! Let the timer thread destroy + * complete the taskpool destruction sequence. */ + completeShutdown = false; + } + + /* Remove all timers from the timeout list. */ + for( ; ; ) + { + pItemLink = IotListDouble_RemoveHead( &pTaskPool->timerEventsList ); - _destroyJob( pTimerEvent->pJob ); + if( pItemLink == NULL ) + { + break; + } + + pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pItemLink, link ); - IotTaskPool_FreeTimerEvent( pTimerEvent ); + _destroyJob( pTimerEvent->pJob ); + + IotTaskPool_FreeTimerEvent( pTimerEvent ); + } } - } while( pItemLink ); + } /* (3) Clear the job cache. */ do @@ -374,7 +400,10 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) IotTaskPool_Assert( pTaskPool->activeThreads == 0 ); /* (6) Destroy all signaling objects. */ - _destroyTaskPool( pTaskPool ); + if( completeShutdown == true ) + { + _destroyTaskPool( pTaskPool ); + } TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -1601,6 +1630,9 @@ static void _timerThread( void * pArgument ) { TASKPOOL_EXIT_CRITICAL(); + /* Complete the shutdown sequence. */ + _destroyTaskPool( pTaskPool ); + return; } From 1bfcf2dcfdaeeb0852cde60f0626a89a7bf15330 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Thu, 21 Mar 2019 11:57:57 -0700 Subject: [PATCH 060/844] Eliminate use of size_t in task pool (#336) --- lib/include/types/iot_taskpool_types.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index abdd6e4285..cc0687219a 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -246,10 +246,10 @@ typedef struct IotTaskPoolInfo * number of worker threads at run time. */ - size_t minThreads; /**< @brief Minimum number of threads in a task pool. These threads will be created when the task pool is first created with @ref taskpool_function_create. */ - size_t maxThreads; /**< @brief Maximum number of threads in a task pool. A task pool may try and grow the number of active threads up to #IotTaskPoolInfo_t.maxThreads. */ - size_t stackSize; /**< @brief Stack size for every task pool thread. The stack size for each thread is fixed after the task pool is created and cannot be changed. */ - int32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ + uint32_t minThreads; /**< @brief Minimum number of threads in a task pool. These threads will be created when the task pool is first created with @ref taskpool_function_create. */ + uint32_t maxThreads; /**< @brief Maximum number of threads in a task pool. A task pool may try and grow the number of active threads up to #IotTaskPoolInfo_t.maxThreads. */ + uint32_t stackSize; /**< @brief Stack size for every task pool thread. The stack size for each thread is fixed after the task pool is created and cannot be changed. */ + int32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ } IotTaskPoolInfo_t; /*------------------------- Task pool handles structs --------------------------*/ @@ -264,7 +264,7 @@ typedef struct IotTaskPoolInfo typedef struct IotTaskPoolCache { IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ - size_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ + uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ } IotTaskPoolCache_t; From cb08739d741fec1275858b0bd4d183cb40a7faad Mon Sep 17 00:00:00 2001 From: "Yuhui.Zheng" <10982575+yuhui-zheng@users.noreply.github.com> Date: Thu, 21 Mar 2019 12:34:30 -0700 Subject: [PATCH 061/844] atomic & its abstraction layer. (#313) --- CMakeLists.txt | 15 + lib/include/platform/atomic.h | 307 +++++++++++++++ lib/source/mqtt/CMakeLists.txt | 4 + lib/source/mqtt/iot_mqtt_serialize.c | 106 +++-- .../atomic/atomic_user_override_template.h | 140 +++++++ tests/common/CMakeLists.txt | 3 +- tests/common/iot_tests_common.c | 1 + tests/common/unit/iot_tests_atomic.c | 362 ++++++++++++++++++ 8 files changed, 898 insertions(+), 40 deletions(-) create mode 100644 lib/include/platform/atomic.h create mode 100644 platform/include/atomic/atomic_user_override_template.h create mode 100644 tests/common/unit/iot_tests_atomic.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e5dd7e0a08..40485b0034 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,21 @@ endif() set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) +# Atomic headers. +# If GCC -- +# for version <4.7.0, __sync_* shall be called. +# for version >= 4.7.0, __atomic_* shall be called. +# We only implemented atomic with __atomic_*. +if( CMAKE_C_COMPILER_ID STREQUAL GNU AND ( CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.7 OR CMAKE_C_COMPILER_VERSION VERSION_EQUAL 4.7 ) ) + add_definitions( -DCOMPILER_OPTION_GCC_ATOMIC_BUILTIN=1 ) +endif() + +# If Clang -- +# Clang supports GCC's __atomic_* built-in functions since 3.1 release. +if( CMAKE_C_COMPILER_ID STREQUAL Clang AND ( CMAKE_C_COMPILER_VERSION VERSION_GREATER 3.1 OR CMAKE_C_COMPILER_VERSION VERSION_EQUAL 3.1 ) ) + add_definitions( -DCOMPILER_OPTION_GCC_ATOMIC_BUILTIN=1 ) +endif() + # SDK include paths. include_directories( ${PROJECT_SOURCE_DIR}/lib/include ${PROJECT_SOURCE_DIR}/platform/include diff --git a/lib/include/platform/atomic.h b/lib/include/platform/atomic.h new file mode 100644 index 0000000000..2a956a8e70 --- /dev/null +++ b/lib/include/platform/atomic.h @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file atomic.h + * @brief Atomic operations. + */ + +#ifndef ATOMIC_H +#define ATOMIC_H + +/* Standard includes. */ +#include +#include + +/* Compiler specific implementation. */ +#if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + +/* Default GCC compiler. + * If GCC is used && the target architecture supports atomic instructions, + * (e.g. ARM, Xtensa, ... ) + * there's no reason to not use GCC built-in atomics. + * Thus, no user extension is provided in this branch. + * + * If you believe atomic built-in is not compiled correctly by GCC for your target, + * please use the other branch and provide your own implementation for atomic. + * + * attribute flatten is not needed in this branch. + */ + #define FORCE_INLINE inline __attribute__( ( always_inline ) ) + +/* GCC asm volatile. + * This can be helpful when user wants to observe how atomic inline function is + * compiled. Simply add a tag (or no-op) before and after atomic inline function + * call, and observe objdump disassembly. Though, it is not suggested to call this + * macro in user application code. + */ + #define COMPILER_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) + +#else + +/* Using none GCC compiler || user customization + * User needs to figure out compiler specific syntax for always-inline and flatten.*/ + #include "atomic/atomic_user_override_template.h" + +#endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + +/*----------------------------- Swap && CAS ------------------------------*/ + +/** + * Atomic compare-and-swap + * + * @brief Performs an atomic compare-and-swap operation on the specified values. + * + * @param[in, out] pDestination Pointer to memory location from where value is to be loaded and checked. + * @param[in] ulExchange If condition meets, write this value to memory. + * @param[in] ulComparand Swap condition, checks and waits for *pDestination to be equal to ulComparand. + * + * @return The initial value of *pDestination. + * + * @note This function only swaps *pDestination with ulExchange, if previous *pDestination value equals ulComparand. + * @note It's up to caller to check whether swapped or not. User cannot tell whether swapped or not if initial value + * and new value are the same. But similar to ABA problems, it can be treated as "swapped". + */ +static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestination, + uint32_t ulExchange, + uint32_t ulComparand ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + uint32_t ulReturnValue = *pDestination; + __atomic_compare_exchange( pDestination, &ulComparand, &ulExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); + return ulReturnValue; + #else + return Atomic_CompareAndSwap_u32_User_Override( pDestination, ulExchange, ulComparand ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic swap (pointers) + * + * @brief Atomically sets the address pointed to by *ppDestination to the value of *pExchange. + * + * @param[in, out] ppDestination Pointer to memory location from where a pointer value is to be loaded and writen back to. + * @param[in] pExchange Pointer value to be written to *ppDestination. + * + * @return The initial value of *ppDestination. + */ +static FORCE_INLINE void * Atomic_SwapPointers_p32( void * volatile * ppDestination, + void * pExchange ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + void * pReturnValue; + __atomic_exchange( ppDestination, &pExchange, &pReturnValue, __ATOMIC_SEQ_CST ); + + return pReturnValue; + #else + return Atomic_SwapPointers_p32_User_Override( ppDestination, pExchange ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic compare-and-swap (pointers) + * + * @brief Performs an atomic compare-and-swap operation on the specified pointer values. + * + * @param[in, out] ppDestination Pointer to memory location from where a pointer value is to be loaded and checked. + * @param[in] pExchange If condition meets, write this value to memory. + * @param[in] pComparand Swap condition, checks and waits for *ppDestination to be equal to *pComparand. + * + * @return Initial value of *ppDestination. + * + * @note This function only swaps *ppDestination with pExchange, if previous *ppDestination value equals pComparand. + * @note It's up to caller to check whether swapped or not. User cannot tell whether swapped or not if initial value + * and new value are the same. But similar to ABA problems, it can be treated as "swapped". + */ +static FORCE_INLINE void * Atomic_CompareAndSwapPointers_p32( void * volatile * ppDestination, + void * pExchange, + void * pComparand ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + void * pReturnValue = *ppDestination; + __atomic_compare_exchange( ppDestination, &pComparand, &pExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); + return pReturnValue; + #else + return Atomic_CompareAndSwapPointers_p32_User_Override( ppDestination, pExchange, pComparand ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + + +/*----------------------------- Arithmetic ------------------------------*/ + +/** + * Atomic add + * + * @brief Adds count to the value of the specified 32-bit variable as an atomic operation. + * + * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. + * @param[in] lCount Value to be added to *pAddend. + * + * @return previous *pAddend value. + */ +static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAddend, + uint32_t lCount ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_add( pAddend, lCount, __ATOMIC_SEQ_CST ); + #else + return Atomic_Add_i32_User_Override( pAddend, lCount ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic sub + * + * @brief Subtracts count from the value of the specified 32-bit variable as an atomic operation. + * + * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. + * @param[in] lCount Value to be subtract from *pAddend. + * + * @return previous *pAddend value. + */ +static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pAddend, + uint32_t lCount ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_sub( pAddend, lCount, __ATOMIC_SEQ_CST ); + #else + return Atomic_Subtract_i32_User_Override( pAddend, lCount ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic increment + * + * @brief Increments the value of the specified 32-bit variable as an atomic operation. + * + * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. + * + * @return *pAddend value before increment. + */ +static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAddend ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_add( pAddend, 1, __ATOMIC_SEQ_CST ); + #else + return Atomic_Increment_i32_User_Override( pAddend ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic decrement + * + * @brief Decrements the value of the specified 32-bit variable as an atomic operation. + * + * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. + * + * @return *pAddend value before decrement. + */ +static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pAddend ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_sub( pAddend, 1, __ATOMIC_SEQ_CST ); + #else + return Atomic_Decrement_i32_User_Override( pAddend ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/*----------------------------- Bitwise Logical ------------------------------*/ + +/** + * Atomic OR + * + * @brief Performs an atomic OR operation on the specified values. + * + * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. + * @param [in] ulValue Value to be ORed with *pDestination. + * + * @return The original value of *pDestination. + */ +static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pDestination, + uint32_t ulValue ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_or( pDestination, ulValue, __ATOMIC_SEQ_CST ); + #else + return Atomic_OR_u32_User_Override( pDestination, ulValue ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic AND + * + * @brief Performs an atomic AND operation on the specified values. + * + * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. + * @param [in] ulValue Value to be ANDed with *pDestination. + * + * @return The original value of *pDestination. + */ +static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pDestination, + uint32_t ulValue ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_and( pDestination, ulValue, __ATOMIC_SEQ_CST ); + #else + return Atomic_AND_u32_User_Override( pDestination, ulValue ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic NAND + * + * @brief Performs an atomic NAND operation on the specified values. + * + * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. + * @param [in] ulValue Value to be NANDed with *pDestination. + * + * @return The original value of *pDestination. + */ +static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pDestination, + uint32_t ulValue ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_nand( pDestination, ulValue, __ATOMIC_SEQ_CST ); + #else + return Atomic_NAND_u32_User_Override( pDestination, ulValue ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +/** + * Atomic XOR + * @brief Performs an atomic XOR operation on the specified values. + * + * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. + * @param [in] ulValue Value to be XORed with *pDestination. + * + * @return The original value of *pDestination. + */ +static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pDestination, + uint32_t ulValue ) +{ + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) + return __atomic_fetch_xor( pDestination, ulValue, __ATOMIC_SEQ_CST ); + #else + return Atomic_XOR_u32_User_Override( pDestination, ulValue ); + #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ +} + +#endif /* ATOMIC_H */ diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index b7646a52ad..b7cedbe54c 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,3 +1,7 @@ +#Whether to use atomic or not. +#Default to "no". +add_definitions( -DIOT_ATOMIC_OPERATION=0 ) + # MQTT library source files. add_library( iotmqtt SHARED iot_mqtt_api.c diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index e58ccd4c0d..d5f3ebe673 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -41,6 +41,10 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" +/* Atomic operations. */ +#include "platform/atomic.h" + + /*-----------------------------------------------------------*/ /* @@ -283,33 +287,46 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, * @brief Guards access to the packet identifier counter. * * Each packet should have a unique packet identifier. This mutex ensures that only - * one thread at a time may read the global packet identifer. + * one thread at a time may read the global packet identifer. The mutex is only needed + * when atomic operation is not supported. */ -static IotMutex_t _packetIdentifierMutex; +#if ( IOT_ATOMIC_OPERATION != 1 ) + static IotMutex_t _packetIdentifierMutex; +#endif /* IOT_ATOMIC_OPERATION */ /*-----------------------------------------------------------*/ static uint16_t _nextPacketIdentifier( void ) { - static uint16_t nextPacketIdentifier = 1; - uint16_t newPacketIdentifier = 0; + #if ( IOT_ATOMIC_OPERATION == 1 ) + + /* MQTT protocol specifies 2 bytes for Packet Identifier. + * For atomic operation, operating 16-bit on 32-bit MCU is not faster than operating 32-bit directly. + * Here, using addition two bytes and casting, to achieve the same implementation as in the other branch. */ + static uint32_t nextPacketIdentifier = 1; - /* Lock the packet identifier mutex so that only one thread may read and - * modify nextPacketIdentifier. */ - IotMutex_Lock( &( _packetIdentifierMutex ) ); + return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); + #else /* ( IOT_ATOMIC_OPERATION != 1 ) */ + static uint16_t nextPacketIdentifier = 1; + uint16_t newPacketIdentifier = 0; - /* Read the next packet identifier. */ - newPacketIdentifier = nextPacketIdentifier; + /* Lock the packet identifier mutex so that only one thread may read and + * modify nextPacketIdentifier. */ + IotMutex_Lock( &( _packetIdentifierMutex ) ); - /* The next packet identifier will be greater by 2. This prevents packet - * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet - * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); + /* Read the next packet identifier. */ + newPacketIdentifier = nextPacketIdentifier; - /* Unlock the packet identifier mutex. */ - IotMutex_Unlock( &( _packetIdentifierMutex ) ); + /* The next packet identifier will be greater by 2. This prevents packet + * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet + * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); - return newPacketIdentifier; + /* Unlock the packet identifier mutex. */ + IotMutex_Unlock( &( _packetIdentifierMutex ) ); + + return newPacketIdentifier; + #endif /* IOT_ATOMIC_OPERATION */ } /*-----------------------------------------------------------*/ @@ -596,19 +613,23 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, IotMqttError_t _IotMqtt_InitSerialize( void ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - bool packetMutexCreated = false; - /* Create the packet identifier mutex. */ - packetMutexCreated = IotMutex_Create( &( _packetIdentifierMutex ), false ); + /* Create the packet identifier mutex. + * This is only needed when atomic operation is not supported. */ + #if ( IOT_ATOMIC_OPERATION != 1 ) + bool packetMutexCreated = false; - if( packetMutexCreated == false ) - { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); - } - else - { - _EMPTY_ELSE_MARKER; - } + packetMutexCreated = IotMutex_Create( &( _packetIdentifierMutex ), false ); + + if( packetMutexCreated == false ) + { + _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif /* IOT_ATOMIC_OPERATION */ /* Call any additional serializer initialization function if serializer * overrides are enabled. */ @@ -627,22 +648,25 @@ IotMqttError_t _IotMqtt_InitSerialize( void ) _IOT_FUNCTION_CLEANUP_BEGIN(); - /* Clean up on error. */ - if( status != IOT_MQTT_SUCCESS ) - { - if( packetMutexCreated == true ) + /* Only needs to clean up when atomic operation is not supported. */ + #if ( IOT_ATOMIC_OPERATION != 1 ) + /* Clean up on error. */ + if( status != IOT_MQTT_SUCCESS ) { - IotMutex_Destroy( &( _packetIdentifierMutex ) ); + if( packetMutexCreated == true ) + { + IotMutex_Destroy( &( _packetIdentifierMutex ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } } else { _EMPTY_ELSE_MARKER; } - } - else - { - _EMPTY_ELSE_MARKER; - } + #endif /* IOT_ATOMIC_OPERATION */ _IOT_FUNCTION_CLEANUP_END(); } @@ -651,8 +675,12 @@ IotMqttError_t _IotMqtt_InitSerialize( void ) void _IotMqtt_CleanupSerialize( void ) { - /* Destroy the packet identifier mutex. */ - IotMutex_Destroy( &( _packetIdentifierMutex ) ); + #if ( IOT_ATOMIC_OPERATION != 1 ) + + /* Destroy the packet identifier mutex. + * Only needed when atomic operation is not supported.*/ + IotMutex_Destroy( &( _packetIdentifierMutex ) ); + #endif /* IOT_ATOMIC_OPERATION */ /* Call any additional serializer cleanup initialization function is serializer * overrides are enabled. */ diff --git a/platform/include/atomic/atomic_user_override_template.h b/platform/include/atomic/atomic_user_override_template.h new file mode 100644 index 0000000000..06e8d047c7 --- /dev/null +++ b/platform/include/atomic/atomic_user_override_template.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file atomic_user_override.h + * @brief Provides a method for user to override atomic implementation. + */ + +#include +#include + +/** + * GCC flatten/always_inline equivalent. + * + * @todo Define this macro, refer to compiler specific docs. + * Please note that, Atomic_*() functions in atomic.h are calling + * into user defined functions in this header file. Please make + * sure atomic nested function calls are "flattened" and "inlined", + * from caller's perspective. + */ +#define FORCE_INLINE + +/** + * GCC always_inline equivalent. + * + * @todo Define this macro, refer to compiler specific docs. + * This, in combine with FORCE_INLINE, should guarantee the atomic + * functions are always inlined even without compiler optimization. + * The behavior can be checked with compiler objdump disassembly. + */ +#define FORCE_INLINE_NESTED + +/** + * GCC __asm__ __volatile__ equivalent. + * + * @todo Optionally define this macro, refer to compiler specific docs. + */ +#define COMPILER_ASM_VOLATILE + +/** + * Atomic user defined implementations. + * + * @todo Implement ALL below functions. + * + * @note Here, we are mearly providing extension points for user to + * supply their own atomic implementation. It's solely up to user + * to validate whether the implementation is correct. + * + * Though, two suggested approaches -- + * (1) (always works) Emulate atomic with critical section. + * (2) (recommended) ISA supported atomic instruction(s). + * + * Example -- Emulating atomic with critical section + * @code{.c} + * static FORCE_INLINE int32_t Atomic_Add_i32_User_Override(int32_t volatile * pAddend, int32_t lCount) + * { + * int32_t lCurrent; + * CriticalSectionType_t temp; + * + * // Platform specific definition of enter/exit critical section. + * // The return value from enter critical section function call + * // could be previous interrupt mask or priority. + * temp = ENTER_CRITICAL_SECTION( ); + * lCurrent = *pAddend; + * *pAddend += lCount; + * EXIT_CRITICAL_SECTION( temp ); + * + * return lCurrent; + * } + * @endcode + * + * Example -- ISA supported atomic instruction(s), with compiler support. + * @code{.c} + * // A compiler has atomic built-in extension. + * // Include atomic header in app code. + * + * static FORCE_INLINE int32_t Atomic_Add_i32_User_Override(int32_t volatile * pAddend, int32_t lCount) + * { + * // Assume atomic_fetch_add() does something similar to GCC __atomic_fetch_add() + * // with memory order __ATOMIC_SEQ_CST. + * return atomic_fetch_add(pAddend, lCount); + * } + * @endcode + * + * Example -- ISA supported atomic instructions(s), without compiler built-in function. + * @code{.c} + * static FORCE_INLINE int32_t Atomic_Add_i32_User_Override(int32_t volatile * pAddend, int32_t lCount) + * { + * // assembly, if you have to. + * __asm__ __volatile__(......); + * } + * @endcode + * + */ + +/*----------------------------- Swap && CAS ------------------------------*/ +static FORCE_INLINE_NESTED uint32_t Atomic_CompareAndSwap_u32_User_Override( uint32_t volatile * pDestination, + uint32_t ulExchange, + uint32_t ulComparand ); +static FORCE_INLINE_NESTED void * Atomic_SwapPointers_p32_User_Override( void * volatile * ppDestination, + void * pExchange ); +static FORCE_INLINE_NESTED void * Atomic_CompareAndSwapPointers_p32_User_Override( void * volatile * ppDestination, + void * pExchange, + void * pComparand ); + +/*----------------------------- Arithmetic ------------------------------*/ +static FORCE_INLINE_NESTED int32_t Atomic_Add_i32_User_Override( int32_t volatile * pAddend, + int32_t lCount ); +static FORCE_INLINE_NESTED int32_t Atomic_Subtract_i32_User_Override( int32_t volatile * pAddend, + int32_t lCount ); +static FORCE_INLINE_NESTED int32_t Atomic_Increment_i32_User_Override( int32_t volatile * pAddend ); +static FORCE_INLINE_NESTED int32_t Atomic_Decrement_i32_User_Override( int32_t volatile * pAddend ); + +/*----------------------------- Bitwise Logical ------------------------------*/ +static FORCE_INLINE_NESTED uint32_t Atomic_OR_u32_User_Override( uint32_t volatile * pDestination, + uint32_t ulValue ); +static FORCE_INLINE_NESTED uint32_t Atomic_AND_u32_User_Override( uint32_t volatile * pDestination, + uint32_t ulValue ); +static FORCE_INLINE_NESTED uint32_t Atomic_NAND_u32_User_Override( uint32_t volatile * pDestination, + uint32_t ulValue ); +static FORCE_INLINE_NESTED uint32_t Atomic_XOR_u32_User_Override( uint32_t volatile * pDestination, + uint32_t ulValue ); diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 6c457a5b56..6496f2967a 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -2,7 +2,8 @@ add_executable( iot_tests_common iot_tests_common.c unit/iot_tests_linear_containers.c - unit/iot_tests_taskpool.c ) + unit/iot_tests_taskpool.c + unit/iot_tests_atomic.c ) # Common tests library dependencies. target_link_libraries( iot_tests_common unity iotcommon ) diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index c0c4f0b063..920e3a3557 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -96,6 +96,7 @@ int main( int argc, /* Run linear containers tests. */ RUN_TEST_GROUP( Common_Unit_Linear_Containers ); RUN_TEST_GROUP( Common_Unit_Task_Pool ); + RUN_TEST_GROUP( Common_Unit_Atomic ); /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c new file mode 100644 index 0000000000..7e3600beb4 --- /dev/null +++ b/tests/common/unit/iot_tests_atomic.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_atomic.c + * @brief Simple API tests for atomic interfaces. + * + * Tests in this file do not check extensively for atomicity, + * but only guarantee APIs at least do what they supposed to do. + * + * Atomic APIs are wrapped with asm tag, so that objdump disassembly + * can be examined. + */ + +/* Build using a config header, if provided. */ +#ifdef IOT_CONFIG_FILE + #include IOT_CONFIG_FILE +#endif + +/* NULL */ +#include + +/* Platform layer includes. */ +#include "platform/atomic.h" + +/* Test framework includes. */ +#include "unity.h" +#include "unity_fixture.h" + +/* Magic numbers. */ +#define MAGIC_NUMBER_32BIT_1 ( 0xA5A5A5A5 ) +#define MAGIC_NUMBER_32BIT_2 ( 0xF0F0F0F0 ) +#define MAGIC_NUMBER_32BIT_3 ( 0x0000000F ) + +#define MAGIC_NUMBER_8BIT_1 ( 0xA5 ) +#define MAGIC_NUMBER_8BIT_2 ( 0xF0 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for atomic tests. + */ +TEST_GROUP( Common_Unit_Atomic ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for atomic tests. + */ +TEST_SETUP( Common_Unit_Atomic ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for atomic tests. + */ +TEST_TEAR_DOWN( Common_Unit_Atomic ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for atomic tests. + */ +TEST_GROUP_RUNNER( Common_Unit_Atomic ) +{ + RUN_TEST_CASE( Common_Unit_Atomic, AtomicCasHappyPath ); + RUN_TEST_CASE( Common_Unit_Atomic, AtomicArithmeticHappyPath ); + RUN_TEST_CASE( Common_Unit_Atomic, AtomicBitwiseHappyPath ); + RUN_TEST_CASE( Common_Unit_Atomic, AtomicCasFailToSwap ); +} + + +TEST( Common_Unit_Atomic, AtomicCasHappyPath ) +{ + uint32_t ulCasDestination_32; + uint32_t ulCasComparator_32; + uint32_t ulCasNewValue_32; + uint32_t ulReturlValue_32; + + uint32_t * pSwapDestination_32; + uint32_t * pSwapNewValue_32; + uint32_t * pReturnValue_32; + + uint8_t uCasDestination_8 = MAGIC_NUMBER_8BIT_1; + uint8_t uCasComparator_8 = MAGIC_NUMBER_8BIT_1; + + uint8_t * pSwapDestination_8 = &uCasDestination_8; + uint8_t * pSwapNewValue_8 = &uCasComparator_8; + uint8_t * pReturnValue_8 = NULL; + + /* #1 -- CAS */ + ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; + ulCasComparator_32 = MAGIC_NUMBER_32BIT_1; + ulCasNewValue_32 = MAGIC_NUMBER_32BIT_2; + + COMPILER_ASM_VOLATILE( "atomic_cas_1: " ); + ulReturlValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); + COMPILER_ASM_VOLATILE( "atomic_cas_1_end: " ); + + TEST_ASSERT_MESSAGE( ulCasDestination_32 == ulCasNewValue_32, "Atomic_CompareAndSwap_u32 -- did not swap." ); + TEST_ASSERT_MESSAGE( ulReturlValue_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); + + /* #2 -- CAS, comparator from the same mem location. */ + ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; + + COMPILER_ASM_VOLATILE( "atomic_cas_2: " ); + ulReturlValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, MAGIC_NUMBER_32BIT_2, ulCasDestination_32 ); + COMPILER_ASM_VOLATILE( "atomic_cas_2_end: " ); + + TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_2, "Atomic_CompareAndSwap_u32 -- did not swap." ); + TEST_ASSERT_MESSAGE( ulReturlValue_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); + + /* #3 -- swap */ + pSwapDestination_32 = &ulCasDestination_32; + pSwapNewValue_32 = &ulCasNewValue_32; + pReturnValue_32 = NULL; + + COMPILER_ASM_VOLATILE( "atomic_xchg_32bit: " ); + pReturnValue_32 = Atomic_SwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32 ); + COMPILER_ASM_VOLATILE( "atomic_xchg_32bit_end: " ); + + TEST_ASSERT_MESSAGE( pSwapDestination_32 == &ulCasNewValue_32, "Atomic_SwapPointers_p32 -- did not swap." ); + TEST_ASSERT_MESSAGE( pReturnValue_32 == &ulCasDestination_32, "Atomic_SwapPointers_p32 -- expected to return previous value." ); + + /* #4 -- swap, pointer to variable of uint8_t type. */ + pSwapDestination_8 = &uCasDestination_8; + pSwapNewValue_8 = &uCasComparator_8; + pReturnValue_8 = NULL; + + COMPILER_ASM_VOLATILE( "atomic_xchg_8bit: nop" ); + pReturnValue_8 = Atomic_SwapPointers_p32( ( void ** ) &pSwapDestination_8, pSwapNewValue_8 ); + COMPILER_ASM_VOLATILE( "atomic_xchg_8bit_end: nop" ); + + TEST_ASSERT_MESSAGE( pSwapDestination_8 == &uCasComparator_8, "Atomic_SwapPointers_p32 -- did not swap." ); + TEST_ASSERT_MESSAGE( pReturnValue_8 == &uCasDestination_8, "Atomic_SwapPointers_p32 -- expected to return previous value." ); + + /* #5 -- CAS pointers. */ + pSwapDestination_32 = &ulCasDestination_32; + pSwapNewValue_32 = &ulCasNewValue_32; + + COMPILER_ASM_VOLATILE( "atomic_CAS_pointers: nop" ); + pReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); + COMPILER_ASM_VOLATILE( "atomic_CAS_pointers_end: nop" ); + + TEST_ASSERT_MESSAGE( ( intptr_t ) pSwapDestination_32 == ( intptr_t ) pSwapNewValue_32, "Atomic_CompareAndSwapPointers_p32 -- did not swap." ); + TEST_ASSERT_MESSAGE( ( intptr_t ) pReturnValue_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- expected return value true." ); + + return; +} + +TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) +{ + uint32_t uAddend_32; + int32_t iAddend_32; + + uint32_t uDelta_32; + + uint32_t uReturnValue_32; + int32_t iReturnValue_32; + + uint8_t uAddend_8; + + /* asm (built-in function) implementation -- + * for curiosity, see what instructions add-register and add-immediate are using. */ + + /* #0 -- Some examples for user -- + * casting number in range 0x80000000-0xFFFFFFFF. + * COMPILER_ASM_VOLATILE is omitted, as this is the normal user caller routine. */ + uAddend_32 = ( uint32_t ) 0xFFFFFFFE; /* signed 0xFFFFFFFE is -2, 2's complement. */ + + uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); + TEST_ASSERT_MESSAGE( uAddend_32 == UINT32_MAX, "Atomic_Add_u32 -- did not add correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == ( uint32_t ) 0xFFFFFFFE, "Atomic_Add_u32 -- expected return value (UINT32_MAX - 1)." ); + + iAddend_32 = ( int32_t ) uAddend_32; + iReturnValue_32 = ( int32_t ) uReturnValue_32; + TEST_ASSERT_MESSAGE( iAddend_32 == -1, "Atomic_Add_u32 -- did not cast correctly." ); + TEST_ASSERT_MESSAGE( iReturnValue_32 == -2, "Atomic_Add_u32 -- expected return value -2." ); + + + iAddend_32 = INT32_MIN + 1; /* unsigned 0x80000001, 2's complement. */ + + iReturnValue_32 = ( uint32_t ) Atomic_Subtract_u32( ( uint32_t * ) &iAddend_32, 1 ); + TEST_ASSERT_MESSAGE( iAddend_32 == INT32_MIN, "Atomic_Subtract_u32 -- did not subtract correctly." ); + TEST_ASSERT_MESSAGE( iReturnValue_32 == ( INT32_MIN + 1 ), "Atomic_Subtract_u32 -- expected return value (INT32_MIN + 1)." ); + + uAddend_32 = ( uint32_t ) iAddend_32; + uReturnValue_32 = ( uint32_t ) iReturnValue_32; + TEST_ASSERT_MESSAGE( uAddend_32 == ( uint32_t ) 0x80000000, "Atomic_Subtract_u32 -- did not subtract correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == ( uint32_t ) 0x80000001, "Atomic_Add_u32 -- expected return value (INT32_MIN + 1)." ); + + /* #1 -- add register */ + uAddend_32 = 0; + uDelta_32 = 1; + + COMPILER_ASM_VOLATILE( "atomic_add_reg: " ); + uReturnValue_32 = Atomic_Add_u32( &uAddend_32, uDelta_32 ); + COMPILER_ASM_VOLATILE( "atomic_add_reg_end: " ); + + TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); + + /* #2 -- add immediate */ + uAddend_32 = 0; + + COMPILER_ASM_VOLATILE( "atomic_add_imme: " ); + uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); + COMPILER_ASM_VOLATILE( "atomic_add_imme_end: " ); + + TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add immediate number correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); + + /* #3 -- add, 8-bit casting */ + uAddend_8 = 1; + uAddend_32 = ( uint32_t ) uAddend_8; + + COMPILER_ASM_VOLATILE( "atomic_add_8bit: " ); + uReturnValue_32 = Atomic_Add_u32( &uAddend_32, UINT8_MAX ); + COMPILER_ASM_VOLATILE( "atomic_add_8bit_end: " ); + + TEST_ASSERT_MESSAGE( ( uint8_t ) uReturnValue_32 == 1, "Atomic_Add_u32 -- did not roll over correctly." ); + + /* #4 -- sub, almost but not underflow */ + uAddend_32 = 1; + + COMPILER_ASM_VOLATILE( "atomic_sub_reg: " ); + uReturnValue_32 = Atomic_Subtract_u32( &uAddend_32, 1 ); + COMPILER_ASM_VOLATILE( "atomic_sub_reg_end: " ); + + TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Subtract_u32 -- did not subtract correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Subtract_u32 -- expected return value 1." ); + + /* #5 -- inc, sanity check */ + uAddend_32 = 0; + + COMPILER_ASM_VOLATILE( "atomic_inc: " ); + uReturnValue_32 = Atomic_Increment_u32( &uAddend_32 ); + COMPILER_ASM_VOLATILE( "atomic_inc_end: " ); + + TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Increment_u32 -- did not increment correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Increment_u32 -- expected return value 0." ); + + /* #6 -- dec, sanity check */ + uAddend_32 = 1; + + COMPILER_ASM_VOLATILE( "atomic_dec: " ); + uReturnValue_32 = Atomic_Decrement_u32( &uAddend_32 ); + COMPILER_ASM_VOLATILE( "atomic_dec_end: " ); + + TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Decrement_u32 -- did not decrement correctly." ); + TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Decrement_u32 -- expected return value 1." ); +} + +TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) +{ + uint32_t ulOp1; + uint32_t ulOp2; + uint32_t ulReturnValue; + + /* #1 -- and */ + ulOp1 = MAGIC_NUMBER_32BIT_1; + ulOp2 = MAGIC_NUMBER_32BIT_2; + + COMPILER_ASM_VOLATILE( "atomic_and: " ); + ulReturnValue = Atomic_AND_u32( &ulOp1, ulOp2 ); + COMPILER_ASM_VOLATILE( "atomic_and_end: " ); + + TEST_ASSERT_MESSAGE( ulOp1 == 0xA0A0A0A0, "Atomic_AND_u32 -- did not ANDed correctly." ); + TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_AND_u32 -- expected return value 0xA5A5A5A5." ); + + /* #2 -- or */ + ulOp1 = MAGIC_NUMBER_32BIT_2; + ulOp2 = MAGIC_NUMBER_32BIT_3; + + COMPILER_ASM_VOLATILE( "atomic_or: " ); + ulReturnValue = Atomic_OR_u32( &ulOp1, ulOp2 ); + COMPILER_ASM_VOLATILE( "atomic_or_end: " ); + + TEST_ASSERT_MESSAGE( ulOp1 == 0xF0F0F0FF, "Atomic_OR_u32 -- did not ORed correctly." ); + TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_2, "Atomic_AND_u32 -- expected return value 0xF0F0F0F0." ); + + /* #3 -- nand */ + ulOp1 = MAGIC_NUMBER_32BIT_1; + ulOp2 = MAGIC_NUMBER_32BIT_2; + + COMPILER_ASM_VOLATILE( "atomic_nand: " ); + ulReturnValue = Atomic_NAND_u32( &ulOp1, ulOp2 ); + COMPILER_ASM_VOLATILE( "atomic_nand_end: " ); + + TEST_ASSERT_MESSAGE( ulOp1 == 0x5F5F5F5F, "Atomic_NAND_u32 -- did not NANDed correctly." ); + TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_NAND_u32 -- expected return value 0xA5A5A5A5." ); + + /* #4 -- xor */ + ulOp1 = MAGIC_NUMBER_32BIT_1; + ulOp2 = MAGIC_NUMBER_32BIT_2; + + COMPILER_ASM_VOLATILE( "atomic_xor: " ); + ulReturnValue = Atomic_XOR_u32( &ulOp1, ulOp2 ); + COMPILER_ASM_VOLATILE( "atomic_XOR_end: " ); + + TEST_ASSERT_MESSAGE( ulOp1 == 0x55555555, "Atomic_XOR_u32 -- did not XORed correctly." ); + TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_XOR_u32 -- expected return value 0xA5A5A5A5." ); +} + +TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) +{ + uint32_t ulCasDestination_32; + uint32_t ulCasComparator_32; + uint32_t ulCasNewValue_32; + uint32_t ulReturnValue_32; + + uint32_t * pCasDestination_32; + uint32_t * pCasComparator_32; + uint32_t * pCasNewValue_32; + uint32_t * pReturnValue_32; + + /* #1 -- CAS, not equal, don't swap. */ + ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; + ulCasComparator_32 = MAGIC_NUMBER_32BIT_2; + ulCasNewValue_32 = MAGIC_NUMBER_32BIT_3; + + COMPILER_ASM_VOLATILE( "atomic_cas_neq: " ); + ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); + COMPILER_ASM_VOLATILE( "atomic_cas_neq_end: " ); + + TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should not swap." ); + TEST_ASSERT_MESSAGE( ulReturnValue_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should return previous value." ); + + /* #2 -- CAS, pointers not equal, don't swap. */ + pCasDestination_32 = &ulCasDestination_32; + pCasComparator_32 = &ulCasComparator_32; + pCasNewValue_32 = &ulCasNewValue_32; + + COMPILER_ASM_VOLATILE( "atomic_cas_pointers_neq: " ); + pReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); + COMPILER_ASM_VOLATILE( "atomic_cas_pointers_neq_end: " ); + + TEST_ASSERT_MESSAGE( ( intptr_t ) pCasDestination_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); + TEST_ASSERT_MESSAGE( ( intptr_t ) pReturnValue_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should return previous value." ); +} From 863e07b1c8366644d3c7db9e2bde76d86f96a2c7 Mon Sep 17 00:00:00 2001 From: "Yuhui.Zheng" <10982575+yuhui-zheng@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:59:00 -0700 Subject: [PATCH 062/844] - bugfix to atomic cas. return value should not be read outside of the lock. (#337) - in conjuction to above, return value type changed to uint32_t for cas. --- lib/include/platform/atomic.h | 58 +++++++++++-------- .../atomic/atomic_user_override_template.h | 18 +++--- tests/common/unit/iot_tests_atomic.c | 21 ++++--- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/lib/include/platform/atomic.h b/lib/include/platform/atomic.h index 2a956a8e70..7986e4f851 100644 --- a/lib/include/platform/atomic.h +++ b/lib/include/platform/atomic.h @@ -29,11 +29,13 @@ /* Standard includes. */ #include -#include /* Compiler specific implementation. */ #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) +/* Standard boolean for CAS "strong". */ + #include + /* Default GCC compiler. * If GCC is used && the target architecture supports atomic instructions, * (e.g. ARM, Xtensa, ... ) @@ -55,7 +57,7 @@ */ #define COMPILER_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) -#else +#else /* if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) */ /* Using none GCC compiler || user customization * User needs to figure out compiler specific syntax for always-inline and flatten.*/ @@ -74,23 +76,26 @@ * @param[in] ulExchange If condition meets, write this value to memory. * @param[in] ulComparand Swap condition, checks and waits for *pDestination to be equal to ulComparand. * - * @return The initial value of *pDestination. + * @return unsigned integer of value 1 or 0. 1 for swapped, 0 for not swapped. * * @note This function only swaps *pDestination with ulExchange, if previous *pDestination value equals ulComparand. - * @note It's up to caller to check whether swapped or not. User cannot tell whether swapped or not if initial value - * and new value are the same. But similar to ABA problems, it can be treated as "swapped". */ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestination, - uint32_t ulExchange, - uint32_t ulComparand ) + uint32_t ulExchange, + uint32_t ulComparand ) { + uint32_t ulReturnValue = 0; + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - uint32_t ulReturnValue = *pDestination; - __atomic_compare_exchange( pDestination, &ulComparand, &ulExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); - return ulReturnValue; + if( __atomic_compare_exchange( pDestination, &ulComparand, &ulExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) + { + ulReturnValue = 1; + } #else - return Atomic_CompareAndSwap_u32_User_Override( pDestination, ulExchange, ulComparand ); + ulReturnValue = Atomic_CompareAndSwap_u32_User_Override( pDestination, ulExchange, ulComparand ); #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + + return ulReturnValue; } /** @@ -125,23 +130,26 @@ static FORCE_INLINE void * Atomic_SwapPointers_p32( void * volatile * ppDestinat * @param[in] pExchange If condition meets, write this value to memory. * @param[in] pComparand Swap condition, checks and waits for *ppDestination to be equal to *pComparand. * - * @return Initial value of *ppDestination. + * @return unsigned integer of value 1 or 0. 1 for swapped, 0 for not swapped. * * @note This function only swaps *ppDestination with pExchange, if previous *ppDestination value equals pComparand. - * @note It's up to caller to check whether swapped or not. User cannot tell whether swapped or not if initial value - * and new value are the same. But similar to ABA problems, it can be treated as "swapped". */ -static FORCE_INLINE void * Atomic_CompareAndSwapPointers_p32( void * volatile * ppDestination, - void * pExchange, - void * pComparand ) +static FORCE_INLINE uint32_t Atomic_CompareAndSwapPointers_p32( void * volatile * ppDestination, + void * pExchange, + void * pComparand ) { + uint32_t ulReturnValue = 0; + #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - void * pReturnValue = *ppDestination; - __atomic_compare_exchange( ppDestination, &pComparand, &pExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); - return pReturnValue; + if( __atomic_compare_exchange( ppDestination, &pComparand, &pExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) + { + ulReturnValue = 1; + } #else - return Atomic_CompareAndSwapPointers_p32_User_Override( ppDestination, pExchange, pComparand ); + ulReturnValue = Atomic_CompareAndSwapPointers_p32_User_Override( ppDestination, pExchange, pComparand ); #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + + return ulReturnValue; } @@ -163,7 +171,7 @@ static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAddend, #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) return __atomic_fetch_add( pAddend, lCount, __ATOMIC_SEQ_CST ); #else - return Atomic_Add_i32_User_Override( pAddend, lCount ); + return Atomic_Add_u32_User_Override( pAddend, lCount ); #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ } @@ -183,7 +191,7 @@ static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pAddend, #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) return __atomic_fetch_sub( pAddend, lCount, __ATOMIC_SEQ_CST ); #else - return Atomic_Subtract_i32_User_Override( pAddend, lCount ); + return Atomic_Subtract_u32_User_Override( pAddend, lCount ); #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ } @@ -201,7 +209,7 @@ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAddend ) #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) return __atomic_fetch_add( pAddend, 1, __ATOMIC_SEQ_CST ); #else - return Atomic_Increment_i32_User_Override( pAddend ); + return Atomic_Increment_u32_User_Override( pAddend ); #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ } @@ -219,7 +227,7 @@ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pAddend ) #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) return __atomic_fetch_sub( pAddend, 1, __ATOMIC_SEQ_CST ); #else - return Atomic_Decrement_i32_User_Override( pAddend ); + return Atomic_Decrement_u32_User_Override( pAddend ); #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ } diff --git a/platform/include/atomic/atomic_user_override_template.h b/platform/include/atomic/atomic_user_override_template.h index 06e8d047c7..02c6375d06 100644 --- a/platform/include/atomic/atomic_user_override_template.h +++ b/platform/include/atomic/atomic_user_override_template.h @@ -117,17 +117,17 @@ static FORCE_INLINE_NESTED uint32_t Atomic_CompareAndSwap_u32_User_Override( uin uint32_t ulComparand ); static FORCE_INLINE_NESTED void * Atomic_SwapPointers_p32_User_Override( void * volatile * ppDestination, void * pExchange ); -static FORCE_INLINE_NESTED void * Atomic_CompareAndSwapPointers_p32_User_Override( void * volatile * ppDestination, - void * pExchange, - void * pComparand ); +static FORCE_INLINE_NESTED uint32_t Atomic_CompareAndSwapPointers_p32_User_Override( void * volatile * ppDestination, + void * pExchange, + void * pComparand ); /*----------------------------- Arithmetic ------------------------------*/ -static FORCE_INLINE_NESTED int32_t Atomic_Add_i32_User_Override( int32_t volatile * pAddend, - int32_t lCount ); -static FORCE_INLINE_NESTED int32_t Atomic_Subtract_i32_User_Override( int32_t volatile * pAddend, - int32_t lCount ); -static FORCE_INLINE_NESTED int32_t Atomic_Increment_i32_User_Override( int32_t volatile * pAddend ); -static FORCE_INLINE_NESTED int32_t Atomic_Decrement_i32_User_Override( int32_t volatile * pAddend ); +static FORCE_INLINE_NESTED uint32_t Atomic_Add_u32_User_Override( uint32_t volatile * pAddend, + uint32_t lCount ); +static FORCE_INLINE_NESTED uint32_t Atomic_Subtract_u32_User_Override( uint32_t volatile * pAddend, + uint32_t lCount ); +static FORCE_INLINE_NESTED uint32_t Atomic_Increment_u32_User_Override( uint32_t volatile * pAddend ); +static FORCE_INLINE_NESTED uint32_t Atomic_Decrement_u32_User_Override( uint32_t volatile * pAddend ); /*----------------------------- Bitwise Logical ------------------------------*/ static FORCE_INLINE_NESTED uint32_t Atomic_OR_u32_User_Override( uint32_t volatile * pDestination, diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index 7e3600beb4..2f8fbea5d3 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -97,7 +97,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) uint32_t ulCasDestination_32; uint32_t ulCasComparator_32; uint32_t ulCasNewValue_32; - uint32_t ulReturlValue_32; + uint32_t ulReturnValue_32; uint32_t * pSwapDestination_32; uint32_t * pSwapNewValue_32; @@ -116,21 +116,21 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) ulCasNewValue_32 = MAGIC_NUMBER_32BIT_2; COMPILER_ASM_VOLATILE( "atomic_cas_1: " ); - ulReturlValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); + ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); COMPILER_ASM_VOLATILE( "atomic_cas_1_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == ulCasNewValue_32, "Atomic_CompareAndSwap_u32 -- did not swap." ); - TEST_ASSERT_MESSAGE( ulReturlValue_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); + TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); /* #2 -- CAS, comparator from the same mem location. */ ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; COMPILER_ASM_VOLATILE( "atomic_cas_2: " ); - ulReturlValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, MAGIC_NUMBER_32BIT_2, ulCasDestination_32 ); + ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, MAGIC_NUMBER_32BIT_2, ulCasDestination_32 ); COMPILER_ASM_VOLATILE( "atomic_cas_2_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_2, "Atomic_CompareAndSwap_u32 -- did not swap." ); - TEST_ASSERT_MESSAGE( ulReturlValue_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); + TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); /* #3 -- swap */ pSwapDestination_32 = &ulCasDestination_32; @@ -161,11 +161,11 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapNewValue_32 = &ulCasNewValue_32; COMPILER_ASM_VOLATILE( "atomic_CAS_pointers: nop" ); - pReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); + ulReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); COMPILER_ASM_VOLATILE( "atomic_CAS_pointers_end: nop" ); TEST_ASSERT_MESSAGE( ( intptr_t ) pSwapDestination_32 == ( intptr_t ) pSwapNewValue_32, "Atomic_CompareAndSwapPointers_p32 -- did not swap." ); - TEST_ASSERT_MESSAGE( ( intptr_t ) pReturnValue_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- expected return value true." ); + TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwapPointers_p32 -- expected return value true." ); return; } @@ -334,7 +334,6 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) uint32_t * pCasDestination_32; uint32_t * pCasComparator_32; uint32_t * pCasNewValue_32; - uint32_t * pReturnValue_32; /* #1 -- CAS, not equal, don't swap. */ ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; @@ -346,7 +345,7 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) COMPILER_ASM_VOLATILE( "atomic_cas_neq_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should not swap." ); - TEST_ASSERT_MESSAGE( ulReturnValue_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should return previous value." ); + TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwap_u32 -- should not swap." ); /* #2 -- CAS, pointers not equal, don't swap. */ pCasDestination_32 = &ulCasDestination_32; @@ -354,9 +353,9 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) pCasNewValue_32 = &ulCasNewValue_32; COMPILER_ASM_VOLATILE( "atomic_cas_pointers_neq: " ); - pReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); + ulReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); COMPILER_ASM_VOLATILE( "atomic_cas_pointers_neq_end: " ); TEST_ASSERT_MESSAGE( ( intptr_t ) pCasDestination_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); - TEST_ASSERT_MESSAGE( ( intptr_t ) pReturnValue_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should return previous value." ); + TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); } From 4ebd42c8a51187c99a36dacd2843a120cb4817e3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 21 Mar 2019 15:54:21 -0700 Subject: [PATCH 063/844] Make MQTT system test serializer overridable. (#338) --- tests/mqtt/system/iot_tests_mqtt_system.c | 71 ++++++++++++++--------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 95a5c910c8..193d929226 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -136,13 +136,17 @@ static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_IN */ static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; +#ifdef IOT_TEST_MQTT_SERIALIZER + /** - * @brief MQTT serializer functions to call in the tests. May be overridden by a - * defined initializer. + * @brief Provide a pointer to a serializer that may be overridden. */ -#ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER - static const IotMqttSerializer_t _mqttSerializer = IOT_TEST_MQTT_SERIALIZER_INITIALIZER; + static const IotMqttSerializer_t * _pMqttSerializer = NULL; #else + +/** + * @brief Function pointers to the default MQTT serializers. + */ static const IotMqttSerializer_t _mqttSerializer = { .getPacketType = _IotMqtt_GetPacketType, @@ -169,6 +173,11 @@ static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; .pingresp = _IotMqtt_DeserializePingresp } }; + +/** + * @brief The MQTT serializers to use in these tests. + */ + static const IotMqttSerializer_t * _pMqttSerializer = &_mqttSerializer; #endif /* ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER */ /** @@ -217,7 +226,7 @@ static void _freePacket( uint8_t * pPacket ) { _freePacketOverride = true; - _mqttSerializer.freePacket( pPacket ); + _pMqttSerializer->freePacket( pPacket ); } /*-----------------------------------------------------------*/ @@ -231,9 +240,9 @@ static IotMqttError_t _serializeConnect( const IotMqttConnectInfo_t * pConnectIn { _connectSerializerOverride = true; - return _mqttSerializer.serialize.connect( pConnectInfo, - pConnectPacket, - pPacketSize ); + return _pMqttSerializer->serialize.connect( pConnectInfo, + pConnectPacket, + pPacketSize ); } /*-----------------------------------------------------------*/ @@ -249,11 +258,11 @@ static IotMqttError_t _serializePublish( const IotMqttPublishInfo_t * pPublishIn { _publishSerializerOverride = true; - return _mqttSerializer.serialize.publish( pPublishInfo, - pPublishPacket, - pPacketSize, - pPacketIdentifier, - pPacketIdentifierHigh ); + return _pMqttSerializer->serialize.publish( pPublishInfo, + pPublishPacket, + pPacketSize, + pPacketIdentifier, + pPacketIdentifierHigh ); } /*-----------------------------------------------------------*/ @@ -267,9 +276,9 @@ static IotMqttError_t _serializePuback( uint16_t packetIdentifier, { _pubackSerializerOverride = true; - return _mqttSerializer.serialize.puback( packetIdentifier, - pPubackPacket, - pPacketSize ); + return _pMqttSerializer->serialize.puback( packetIdentifier, + pPubackPacket, + pPacketSize ); } /*-----------------------------------------------------------*/ @@ -285,11 +294,11 @@ static IotMqttError_t _serializeSubscribe( const IotMqttSubscription_t * pSubscr { _subscribeSerializerOverride = true; - return _mqttSerializer.serialize.subscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); + return _pMqttSerializer->serialize.subscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); } /*-----------------------------------------------------------*/ @@ -305,11 +314,11 @@ static IotMqttError_t _serializeUnsubscribe( const IotMqttSubscription_t * pSubs { _unsubscribeSerializerOverride = true; - return _mqttSerializer.serialize.unsubscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); + return _pMqttSerializer->serialize.unsubscribe( pSubscriptionList, + subscriptionCount, + pSubscribePacket, + pPacketSize, + pPacketIdentifier ); } /*-----------------------------------------------------------*/ @@ -322,8 +331,8 @@ static IotMqttError_t _serializeDisconnect( uint8_t ** pDisconnectPacket, { _disconnectSerializerOverride = true; - return _mqttSerializer.serialize.disconnect( pDisconnectPacket, - pPacketSize ); + return _pMqttSerializer->serialize.disconnect( pDisconnectPacket, + pPacketSize ); } /*-----------------------------------------------------------*/ @@ -623,6 +632,12 @@ TEST_SETUP( MQTT_System ) #if IOT_TEST_SECURED_CONNECTION == 1 _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; #endif + + + /* Set the overrides for the default serializers. */ + #ifdef IOT_TEST_MQTT_SERIALIZER + _pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + #endif } /*-----------------------------------------------------------*/ From 50dd8a1d2898eb9f9266d0ac62ded182506644bc Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 22 Mar 2019 10:34:24 -0700 Subject: [PATCH 064/844] Remove assert that's not always true. (#339) --- lib/source/mqtt/iot_mqtt_network.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 7b909d05b1..41c19e36c2 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -406,9 +406,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - /* The received MQTT packet must be part of the incoming - * packet structure. */ - IotMqtt_Assert( pIncomingPacket->pRemainingData != NULL ); + _EMPTY_ELSE_MARKER; } /* Remove operation from pending processing list. */ From 4204872d00150d5fcd033e6a921f43655468a2d4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 22 Mar 2019 10:49:11 -0700 Subject: [PATCH 065/844] Make connection create output parameter void**. (#340) --- lib/include/platform/iot_network.h | 2 +- platform/include/posix/iot_network_openssl.h | 2 +- platform/source/posix/linux/iot_network_openssl_metrics.c | 4 ++-- platform/source/posix/network/iot_network_openssl.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index f3faf73c63..62bb9b186b 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -115,7 +115,7 @@ typedef struct IotNetworkInterface /* @[declare_platform_network_create] */ IotNetworkError_t ( * create )( void * pConnectionInfo, void * pCredentialInfo, - void * pConnection ); + void ** pConnection ); /* @[declare_platform_network_create] */ /** diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 0f81c347a1..c2001a3a0a 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -187,7 +187,7 @@ void IotNetworkOpenssl_Cleanup( void ); */ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, void * pCredentialInfo, - void * pConnection ); + void ** pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for diff --git a/platform/source/posix/linux/iot_network_openssl_metrics.c b/platform/source/posix/linux/iot_network_openssl_metrics.c index afb67aa892..24a4f6985b 100644 --- a/platform/source/posix/linux/iot_network_openssl_metrics.c +++ b/platform/source/posix/linux/iot_network_openssl_metrics.c @@ -40,7 +40,7 @@ /* Metrics wrapper of create interface. */ static IotNetworkError_t _metricsCreate( void * pConnectionInfo, void * pCredentialInfo, - void * pConnection ); + void ** pConnection ); /* Metrics wrapper of close interface. */ static IotNetworkError_t _metricsClose( void * pConnection ); @@ -60,7 +60,7 @@ const IotNetworkInterface_t _IotNetworkOpensslMetrics = static IotNetworkError_t _metricsCreate( void * pConnectionInfo, void * pCredentialInfo, - void * pConnection ) + void ** pConnection ) { /* Call the openssl create. */ IotNetworkError_t error = IotNetworkOpenssl_Create( pConnectionInfo, pCredentialInfo, pConnection ); diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index f63ab073bc..cf778c1adc 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -731,7 +731,7 @@ void IotNetworkOpenssl_Cleanup( void ) IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, void * pCredentialInfo, - void * pConnection ) + void ** pConnection ) { _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int tcpSocket = -1; @@ -741,7 +741,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, /* Cast function parameters to correct types. */ const IotNetworkServerInfoOpenssl_t * const pServerInfo = pConnectionInfo; const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials = pCredentialInfo; - _networkConnection_t ** const pNetworkConnection = pConnection; + _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t** const ) pConnection; /* Allocate memory for a new connection. */ pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); From 9e5037133f2c28a79d6733af5264781cf73c3ed2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 22 Mar 2019 14:57:53 -0700 Subject: [PATCH 066/844] Fix serializer override in MQTT test. (#341) --- tests/mqtt/system/iot_tests_mqtt_system.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 193d929226..22a73c123b 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -189,6 +189,12 @@ static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; * @brief Filler text to publish. */ static const char _pSamplePayload[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" + " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" + " culpa qui officia deserunt mollit anim id est laborum." "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " @@ -466,7 +472,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttNetworkInfo_t networkInfo = _networkInfo; - IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; + IotMqttSerializer_t serializer = *_pMqttSerializer; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; @@ -628,6 +634,7 @@ TEST_SETUP( MQTT_System ) _networkInfo.createNetworkConnection = true; _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + _networkInfo.pMqttSerializer = _pMqttSerializer; #if IOT_TEST_SECURED_CONNECTION == 1 _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; From 5ed420f283f776ac51f5688e302d2487fbb7838d Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 23 Mar 2019 12:58:31 -0700 Subject: [PATCH 067/844] Enable code coverage through Travis CI. (#342) * Add code coverage to CI. * Move coverage job to VM. --- .travis.yml | 14 +++-- scripts/{general => }/ci_test_coverage.sh | 17 +++--- scripts/{general => }/ci_test_doc.sh | 0 scripts/ci_test_general.sh | 13 ----- tests/iot_tests_config.h | 59 ++++++++++++++++++-- tests/shadow/unit/aws_iot_tests_shadow_api.c | 18 ++++-- 6 files changed, 85 insertions(+), 36 deletions(-) rename scripts/{general => }/ci_test_coverage.sh (81%) rename scripts/{general => }/ci_test_doc.sh (100%) delete mode 100644 scripts/ci_test_general.sh diff --git a/.travis.yml b/.travis.yml index e076944306..e9bc85c781 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,9 +12,15 @@ language: c compiler: - clang -# Matrix of tests to run. +jobs: + include: + # Run code coverage job for commit builds. + - if: type = push + compiler: gcc + env: RUN_TEST=coverage + +# Matrix of tests to always run. env: - - RUN_TEST=general # General tests (code coverage, Coverity, etc.) - RUN_TEST=common # Common libraries (linear containers, common libraries, etc.) - RUN_TEST=mqtt # MQTT - RUN_TEST=shadow # AWS IoT Thing Shadows @@ -28,8 +34,8 @@ before_install: install: # Dependencies required across the entire build matrix. - sudo apt-get install -y cmake libssl-dev - # Install coveralls only for "general" commit check builds. - - if [ "$RUN_TEST" = "general" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS_EVENT_TYPE" != "cron" ]; then sudo apt-get install -y lcov; pip install --user cpp-coveralls; fi + # Install lcov and coveralls only for coverage builds. + - if [ "$RUN_TEST" = "coverage" ]; then sudo apt-get install -y lcov; pip install --user cpp-coveralls; fi # Run the test script based on matrix environment variable. script: diff --git a/scripts/general/ci_test_coverage.sh b/scripts/ci_test_coverage.sh similarity index 81% rename from scripts/general/ci_test_coverage.sh rename to scripts/ci_test_coverage.sh index 62d58feedb..d2a509a67c 100644 --- a/scripts/general/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -6,19 +6,19 @@ set -ev # Build tests and demos against AWS IoT with coverage. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" &> /dev/null -make &> /dev/null +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" +make -j2 # Run common tests with code coverage. -./bin/iot_tests_common &> /dev/null +./bin/iot_tests_common # Run MQTT tests and demo against AWS IoT with code coverage. -./bin/iot_tests_mqtt &> /dev/null -./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" &> /dev/null +./bin/iot_tests_mqtt +./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" # Run Shadow tests and demo with code coverage. -./bin/aws_iot_tests_shadow &> /dev/null -./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" &> /dev/null +./bin/aws_iot_tests_shadow +./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" # Generate code coverage results, excluding Unity test framework, demo files, tests files, and third party files. lcov --directory . --capture --output-file coverage.info @@ -27,6 +27,7 @@ lcov --remove coverage.info '*demo*' --output-file coverage.info lcov --remove coverage.info '*tests*' --output-file coverage.info lcov --remove coverage.info '*third_party*' --output-file coverage.info -# Submit the code coverage results. +# Submit the code coverage results. Must be submitted from SDK root directory so +# Coveralls displays the correct paths. cd .. coveralls --lcov-file build/coverage.info diff --git a/scripts/general/ci_test_doc.sh b/scripts/ci_test_doc.sh similarity index 100% rename from scripts/general/ci_test_doc.sh rename to scripts/ci_test_doc.sh diff --git a/scripts/ci_test_general.sh b/scripts/ci_test_general.sh deleted file mode 100644 index 39cf88e3d2..0000000000 --- a/scripts/ci_test_general.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to run the general test scripts. -# This script is invoked from the build directory, so all helper scripts have a relative -# path from the build directory. - -# Exit on any nonzero return code. -set -e - -# Run test coverage script only for commit builds. -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - echo "Nothing to do right now." -fi diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index cdfd84f61c..651678b647 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -72,11 +72,6 @@ /* Shadow library configuration. */ #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) -/* Define the empty else marker if test coverage is enabled. */ -#if IOT_TEST_COVERAGE == 1 - #define _EMPTY_ELSE_MARKER asm volatile ( "nop" ) -#endif - /* Metrics library configuration. */ #define IOT_METRICS_ENABLE_ASSERTS ( 1 ) @@ -179,6 +174,60 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; #define IotTestNetwork_Init IotNetworkOpenssl_Init #define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup +/* Configure code coverage testing if enabled. */ +#if IOT_TEST_COVERAGE == 1 + /* Define the empty else marker if test coverage is enabled. */ + #define _EMPTY_ELSE_MARKER asm volatile ( "nop" ) + + /* Define a custom logging puts function. This function allows coverage + * testing of logging functions, but prevents excessive logs from being + * printed. */ + #define IotLogging_Puts _coveragePuts + + /* Includes for coverage logging puts. */ + #include + #include + #include + + /* Logging output function that only prints messages from demo executables. + * May be unused, hence the gcc unused attribute (not portable!) */ + static int __attribute__ ( ( unused ) ) _coveragePuts( const char * pMessage ) + { + bool printMessage = false; + + /* Name of this executable, available through glibc (not portable!) */ + extern const char * __progname; + + /* Check if this is a demo executable. */ + if( strstr( __progname, "demo" ) != NULL ) + { + /* Always print messages from the demo executables. */ + if( strstr( pMessage, "[DEMO]" ) != NULL ) + { + printMessage = true; + } + /* Always print errors in demo executables. */ + else if( strstr( pMessage, "[ERROR]" ) != NULL ) + { + printMessage = true; + } + /* Always print warnings in demo executables. */ + else if( strstr( pMessage, "[WARN ]" ) != NULL ) + { + printMessage = true; + } + } + + if( printMessage == true ) + { + puts( pMessage ); + } + + /* Puts should return a nonzero value. */ + return 1; + } +#endif + /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ #include IOT_SYSTEM_TYPES_FILE diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 975865d0e9..7a1df44fe8 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -700,10 +700,12 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) /* Allow 2 MQTT library errors, which are caused by failure to allocate memory * for incoming packets (SUBSCRIBE, UNSUBSCRIBE). These allocation errors do - * not happen in static memory mode. */ + * not happen in static memory mode. Don't perform this check when running code + * coverage, as the code coverage logging function interferes with the malloc + * failure count. */ #if IOT_STATIC_MEMORY_ONLY == 1 TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); - #else + #elif IOT_TEST_COVERAGE != 1 TEST_ASSERT_EQUAL_INT32( 2, mqttErrorCount ); #endif } @@ -771,10 +773,12 @@ TEST( Shadow_Unit_API, GetMallocFail ) /* Allow 3 MQTT library errors, which are caused by failure to allocate memory * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). These allocation - * errors do not happen in static memory mode. */ + * errors do not happen in static memory mode. Don't perform this check when + * running code coverage, as the code coverage logging function interferes with + * the malloc failure count.*/ #if IOT_STATIC_MEMORY_ONLY == 1 TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); - #else + #elif IOT_TEST_COVERAGE != 1 TEST_ASSERT_EQUAL_INT32( 3, mqttErrorCount ); #endif } @@ -837,10 +841,12 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) /* Allow 3 MQTT library errors, which are caused by failure to allocate memory * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). These allocation - * errors do not happen in static memory mode. */ + * errors do not happen in static memory mode. Don't perform this check when + * running code coverage, as the code coverage logging function interferes with + * the malloc failure count.*/ #if IOT_STATIC_MEMORY_ONLY == 1 TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); - #else + #elif IOT_TEST_COVERAGE != 1 TEST_ASSERT_EQUAL_INT32( 3, mqttErrorCount ); #endif } From f1482bf955824b384ec2d4af2e2bb955cd8597e1 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Sun, 24 Mar 2019 11:07:34 -0700 Subject: [PATCH 068/844] Add test coverage for IotTaskPool_strerror. (#344) --- lib/source/common/iot_taskpool.c | 3 +- tests/common/unit/iot_tests_taskpool.c | 45 ++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index f69aadfdec..b317001914 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -1535,7 +1535,8 @@ static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, break; case IOT_TASKPOOL_CANCEL_FAILED: - IotLogWarn( "Removing a scheduled job failed because the job could not be canceled." ); + IotLogWarn( "Removing a scheduled job failed because the job could not be canceled, error %s.", + IotTaskPool_strerror( status ) ); status = IOT_TASKPOOL_ILLEGAL_OPERATION; break; diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 66df3ae65b..4b4f6c6c98 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -100,6 +100,8 @@ TEST_TEAR_DOWN( Common_Unit_Task_Pool ) */ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) { + RUN_TEST_CASE( Common_Unit_Task_Pool, Error ); + RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyMaxThreads ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyMaxThreads ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ); @@ -115,7 +117,7 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ); RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ); - RUN_TEST_CASE( Common_Unit_Task_Pool, TaskPool_CancelTasks ); + RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ); } /*-----------------------------------------------------------*/ @@ -377,6 +379,45 @@ IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = /*-----------------------------------------------------------*/ +/** +* @brief Test retrieving error string for each task pool status error. +*/ +TEST( Common_Unit_Task_Pool, Error ) +{ + IotTaskPoolError_t error; + const char * errorString = NULL; + + /* Set error to all possible values, and test that the corresponding string is as expected. */ + /* NOTE: by convention, 'success' value must be equal to zero (0). */ + error = IOT_TASKPOOL_SUCCESS; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "SUCCESS", strlen( "SUCCESS" ) ) ); + + error = IOT_TASKPOOL_BAD_PARAMETER; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "BAD PARAMETER", strlen( "BAD PARAMETER" ) ) ); + + error = IOT_TASKPOOL_ILLEGAL_OPERATION; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "ILLEGAL OPERATION", strlen( "ILLEGAL OPERATION" ) ) ); + + error = IOT_TASKPOOL_NO_MEMORY; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "NO MEMORY", strlen( "NO MEMORY" ) ) ); + + error = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "SHUTDOWN IN PROGRESS", strlen( "SHUTDOWN IN PROGRESS" ) ) ); + + error = IOT_TASKPOOL_CANCEL_FAILED; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "CANCEL FAILED", strlen( "CANCEL FAILED" ) ) ); + + error = ( IotTaskPoolError_t ) -1; + errorString = IotTaskPool_strerror( error ); + TEST_ASSERT( 0 == strncmp( errorString, "INVALID STATUS", strlen( "INVALID STATUS" ) ) ); +} + /** * @brief Test task pool dynamic memory creation and destruction, with both legal and illegal information. */ @@ -1588,7 +1629,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) /** * @brief Test scheduling and canceling jobs. */ -TEST( Common_Unit_Task_Pool, TaskPool_CancelTasks ) +TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) { uint32_t count, maxJobs; IotTaskPool_t taskPool; From 6aa88d9b7ecd246c2ab3a79f83910a6bbd471169 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Sun, 24 Mar 2019 12:26:13 -0700 Subject: [PATCH 069/844] Use TEST_PROTECT in task pool tests to make sure the task pool instance is always destroyed upon test failure. (#345) --- tests/common/unit/iot_tests_taskpool.c | 1487 ++++++++++++------------ 1 file changed, 771 insertions(+), 716 deletions(-) diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 4b4f6c6c98..e6d985735f 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -380,8 +380,8 @@ IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = /*-----------------------------------------------------------*/ /** -* @brief Test retrieving error string for each task pool status error. -*/ + * @brief Test retrieving error string for each task pool status error. + */ TEST( Common_Unit_Task_Pool, Error ) { IotTaskPoolError_t error; @@ -448,24 +448,28 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 5 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 3 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 4 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 7 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ - - /* Initialize more jobs than max threads. */ - for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) - { - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - /* Schedule all jobs to make the task pool grow. */ - for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) + if( TEST_PROTECT() ) { - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 5 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 3 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 4 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 7 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ + + /* Initialize more jobs than max threads. */ + for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) + { + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } + + /* Schedule all jobs to make the task pool grow. */ + for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) + { + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + } } TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -482,54 +486,57 @@ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Trivial parameter validation. */ + if( TEST_PROTECT() ) { - IotTaskPoolJob_t job; + /* Trivial parameter validation. */ + { + IotTaskPoolJob_t job; - /* NULL callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); - } + /* NULL callback. */ + TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL job handle. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + } - /* Create/Destroy. */ - { - IotTaskPoolJob_t job; + /* Create/Destroy. */ + { + IotTaskPoolJob_t job; - /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); - /* Illegally recycle legal static job. */ - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); - } + /* Create legal static job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + /* Illegally recycle legal static job. */ + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + } - /* Create/Destroy. */ - { - IotTaskPoolJob_t job; - IotTaskPoolJobStatus_t jobStatusAtCancellation; - - /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule deferred, then try to illegally destroy, then cancel */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, &job, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); - TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, &job, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); - } + /* Create/Destroy. */ + { + IotTaskPoolJob_t job; + IotTaskPoolJobStatus_t jobStatusAtCancellation; + + /* Create legal static job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule deferred, then try to illegally destroy, then cancel */ + TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, &job, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, &job, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); + } - /* Create/Destroy. */ - { - IotTaskPoolJob_t job; + /* Create/Destroy. */ + { + IotTaskPoolJob_t job; - /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule immediate, then try to illegally destroy it. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &job, 0 ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + /* Create legal static job. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule immediate, then try to illegally destroy it. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &job, 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + } } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -542,65 +549,68 @@ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Trivial parameter validation jobs. */ + if( TEST_PROTECT() ) { - IotTaskPoolJob_t * pJob = NULL; - /* NULL callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, NULL, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL engine handle. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( NULL, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); - } + /* Trivial parameter validation jobs. */ + { + IotTaskPoolJob_t * pJob = NULL; + /* NULL callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, NULL, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL engine handle. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( NULL, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL job handle. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + } - /* Create/Destroy. */ - { - IotTaskPoolJob_t * pJob = NULL; + /* Create/Destroy. */ + { + IotTaskPoolJob_t * pJob = NULL; - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Recycle the job. */ - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Recycle the job. */ + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } - /* Create/Schedule/Destroy. */ - { - IotTaskPoolJob_t * pJob = NULL; + /* Create/Schedule/Destroy. */ + { + IotTaskPoolJob_t * pJob = NULL; - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule deferred, then try to destroy it. */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule deferred, then try to destroy it. */ + TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } - /* Create/Recycle. */ - { - IotTaskPoolJob_t * pJob = NULL; + /* Create/Recycle. */ + { + IotTaskPoolJob_t * pJob = NULL; - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Illegally recycle legal static job. */ - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Illegally recycle legal static job. */ + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } - /* Create/Schedule/Cancel/Recycle. */ - { - IotTaskPoolJob_t * pJob = NULL; - IotTaskPoolJobStatus_t jobStatusAtCancellation; - - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule deferred, then try to cancel it and finally recycle it. */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, pJob, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Create/Schedule/Cancel/Recycle. */ + { + IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJobStatus_t jobStatusAtCancellation; + + /* Create legal recyclable job. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + /* Schedule deferred, then try to cancel it and finally recycle it. */ + TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, pJob, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -613,34 +623,37 @@ TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Recyclable jobs. */ + if( TEST_PROTECT() ) { - uint32_t count, jobLimit; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - jobLimit = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; - #else - jobLimit = 2 * IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * pJobs[ 2 * TEST_TASKPOOL_ITERATIONS ] = { 0 }; - #endif - - for( count = 0; count < jobLimit; ++count ) + /* Recyclable jobs. */ { - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( pJobs[ count ] != NULL ); - } + uint32_t count, jobLimit; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + jobLimit = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + jobLimit = 2 * IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * pJobs[ 2 * TEST_TASKPOOL_ITERATIONS ] = { 0 }; + #endif + + for( count = 0; count < jobLimit; ++count ) + { + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( pJobs[ count ] != NULL ); + } - for( count = 0; count < jobLimit; ++count ) - { - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + for( count = 0; count < jobLimit; ++count ) + { + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } } } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -653,18 +666,21 @@ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolJob_t job; + if( TEST_PROTECT() ) + { + IotTaskPoolJob_t job; - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); - /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, &job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL Task Pool Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( NULL, &job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL Work item Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -685,67 +701,70 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Create a recyclable job we will never schedule, just to have it in the cache for code coverage purposes. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated jobs, schedule all, then wait all. */ + if( TEST_PROTECT() ) { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolError_t errorSchedule; - - for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) + /* Statically allocated jobs, schedule all, then wait all. */ { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolError_t errorSchedule; - switch( errorSchedule ) + for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); - default: - TEST_ASSERT( false ); - } - } + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) - { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); + default: + TEST_ASSERT( false ); + } + } - switch( errorSchedule ) + for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); - default: - TEST_ASSERT( false ); + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } } } } /* Destroy the taskpool. It will empty all queues. */ - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -761,20 +780,23 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolJob_t * pJob; + if( TEST_PROTECT() ) + { + IotTaskPoolJob_t * pJob; - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* Recycle the job, so we do not leak it. */ - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + /* NULL Task Pool Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL Work item Handle. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* Recycle the job, so we do not leak it. */ + TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -793,49 +815,52 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated job, schedule one, then wait. */ + if( TEST_PROTECT() ) { - uint32_t count; - IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; - - /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) - { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + /* Statically allocated job, schedule one, then wait. */ { - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); - } + uint32_t count; + IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; - count = 0; + /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } - while( true ) - { - /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ - IotSemaphore_Wait( &userContext.signal ); + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + { + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + } - ++count; + count = 0; - if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) + while( true ) { - break; + /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ + IotSemaphore_Wait( &userContext.signal ); + + ++count; + + if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) + { + break; + } } - } - /* Signal all taskpool threads to exit. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) - { - IotSemaphore_Post( &userContext.block ); + /* Signal all taskpool threads to exit. */ + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + { + IotSemaphore_Post( &userContext.block ); + } } } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotSemaphore_Destroy( &userContext.signal ); @@ -860,53 +885,56 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated job, schedule one, then wait. */ + if( TEST_PROTECT() ) { - uint32_t count; - IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; - - /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) - { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_THREADS; ++count ) + /* Statically allocated job, schedule one, then wait. */ { - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); - } + uint32_t count; + IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; - /*Schedule a high pri task can make it grow more. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); + /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + { + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } - count = 0; + /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_THREADS; ++count ) + { + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + } - while( true ) - { - /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ - IotSemaphore_Wait( &userContext.signal ); + /*Schedule a high pri task can make it grow more. */ + TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); - ++count; + count = 0; - if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) + while( true ) { - break; + /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ + IotSemaphore_Wait( &userContext.signal ); + + ++count; + + if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) + { + break; + } } - } - /* Signal all taskpool threads to exit. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) - { - IotSemaphore_Post( &userContext.block ); + /* Signal all taskpool threads to exit. */ + for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + { + IotSemaphore_Post( &userContext.block ); + } } } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotSemaphore_Destroy( &userContext.signal ); @@ -926,72 +954,75 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) JobUserContext_t userContext = { 0 }; /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); + TEST_ASSERT_TRUE( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated job, schedule one, then wait. */ + if( TEST_PROTECT() ) { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJob_t job; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) + /* Statically allocated job, schedule one, then wait. */ { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t job; - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &job, 0 ); - - switch( errorSchedule ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); - case IOT_TASKPOOL_BAD_PARAMETER: - TEST_ASSERT( false ); - break; + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &job, 0 ); - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; + case IOT_TASKPOOL_BAD_PARAMETER: + TEST_ASSERT( false ); + break; - default: - TEST_ASSERT( false ); - } + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - /* Ensure callback actually executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; - IotMutex_Lock( &userContext.lock ); + default: + TEST_ASSERT( false ); + } - if( userContext.counter == scheduled ) + /* Ensure callback actually executed. */ + while( true ) { - IotMutex_Unlock( &userContext.lock ); + IotClock_SleepMs( 50 ); - break; + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT( userContext.counter == scheduled ); } - TEST_ASSERT( userContext.counter == scheduled ); + /* Since jobs were build from a static buffer and scheduled one-by-one, we + * should have received all callbacks. + */ + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } - - /* Since jobs were build from a static buffer and scheduled one-by-one, we - * should have received all callbacks. - */ - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1012,70 +1043,73 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated job, schedule one, then wait. */ + if( TEST_PROTECT() ) { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJob_t job; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) + /* Statically allocated job, schedule one, then wait. */ { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &job, 10 + ( rand() % 50 ) ); + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t job; - switch( errorSchedule ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); - case IOT_TASKPOOL_BAD_PARAMETER: - TEST_ASSERT( false ); - break; + IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &job, 10 + ( rand() % 50 ) ); - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; + case IOT_TASKPOOL_BAD_PARAMETER: + TEST_ASSERT( false ); + break; - default: - TEST_ASSERT( false ); - } + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - /* Ensure callback actually executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; - IotMutex_Lock( &userContext.lock ); + default: + TEST_ASSERT( false ); + } - if( userContext.counter == scheduled ) + /* Ensure callback actually executed. */ + while( true ) { - IotMutex_Unlock( &userContext.lock ); + IotClock_SleepMs( 50 ); - break; + IotMutex_Lock( &userContext.lock ); + + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT( userContext.counter == scheduled ); } - TEST_ASSERT( userContext.counter == scheduled ); + /* Since jobs were build from a static buffer and scheduled one-by-one, we + * should have received all callbacks. + */ + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } - - /* Since jobs were build from a static buffer and scheduled one-by-one, we - * should have received all callbacks. - */ - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1096,66 +1130,69 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Dynamically allocated job, schedule one, then wait. */ + if( TEST_PROTECT() ) { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJob_t * pJob; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) + /* Dynamically allocated job, schedule one, then wait. */ { - /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t * pJob; - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, pJob, 0 ); - - switch( errorSchedule ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, pJob, 0 ); - default: - TEST_ASSERT( false ); - } + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } - /* Ensure callback actually executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + /* Ensure callback actually executed. */ + while( true ) + { + IotClock_SleepMs( 50 ); - IotMutex_Lock( &userContext.lock ); + IotMutex_Lock( &userContext.lock ); - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); - break; + break; + } + + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT( userContext.counter == scheduled ); } - TEST_ASSERT( userContext.counter == scheduled ); - } - - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); - /* Since jobs were build from a static buffer and scheduled one-by-one, we - * should have received all callbacks. - */ - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); + /* Since jobs were build from a static buffer and scheduled one-by-one, we + * should have received all callbacks. + */ + TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); + } } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1176,58 +1213,61 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated jobs, schedule all, then wait all. */ + if( TEST_PROTECT() ) { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) + /* Statically allocated jobs, schedule all, then wait all. */ { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + uint32_t count; + uint32_t scheduled = 0; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); - - switch( errorSchedule ) + for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); - default: - TEST_ASSERT( false ); - } - } + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - IotMutex_Lock( &userContext.lock ); + default: + TEST_ASSERT( false ); + } + } - if( userContext.counter == scheduled ) + /* Wait until callback is executed. */ + while( true ) { - IotMutex_Unlock( &userContext.lock ); + IotClock_SleepMs( 50 ); + + IotMutex_Lock( &userContext.lock ); - break; + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } + + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1247,66 +1287,69 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated jobs, schedule all, then wait all. */ + if( TEST_PROTECT() ) { - uint32_t count, maxJobs; - uint32_t scheduled = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; - #endif - - for( count = 0; count < maxJobs; ++count ) + /* Statically allocated jobs, schedule all, then wait all. */ { - /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + uint32_t count, maxJobs; + uint32_t scheduled = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + #endif + + for( count = 0; count < maxJobs; ++count ) + { + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, tpJobs[ count ], 0 ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, tpJobs[ count ], 0 ); - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - default: - TEST_ASSERT( false ); + default: + TEST_ASSERT( false ); + } } - } - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + /* Wait until callback is executed. */ + while( true ) + { + IotClock_SleepMs( 50 ); + + IotMutex_Lock( &userContext.lock ); - IotMutex_Lock( &userContext.lock ); + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); + break; + } - break; + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1327,66 +1370,69 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated jobs, schedule all, then wait all. */ + if( TEST_PROTECT() ) { - uint32_t count, maxJobs; - uint32_t scheduled = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; - #endif - - for( count = 0; count < maxJobs; ++count ) + /* Statically allocated jobs, schedule all, then wait all. */ { - /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + uint32_t count, maxJobs; + uint32_t scheduled = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + #endif + + for( count = 0; count < maxJobs; ++count ) + { + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); + IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - default: - TEST_ASSERT( false ); + default: + TEST_ASSERT( false ); + } } - } - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + /* Wait until callback is executed. */ + while( true ) + { + IotClock_SleepMs( 50 ); - IotMutex_Lock( &userContext.lock ); + IotMutex_Lock( &userContext.lock ); - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } - break; + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1407,108 +1453,111 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated jobs, schedule all, then wait all. */ + if( TEST_PROTECT() ) { - uint32_t count, maxJobs; - uint32_t scheduled = 0, rescheduled = 0, failedReschudule = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; - #else - maxJobs = 10; - IotTaskPoolJob_t tpJobs[ 10 ] = { 0 }; - #endif - - /* Create all jobs. */ - for( count = 0; count < maxJobs; ++count ) - { - /* Shedule the job to be recycled in the callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - /* Schedule all jobs. */ - for( count = 0; count < maxJobs; ++count ) + /* Statically allocated jobs, schedule all, then wait all. */ { - IotTaskPoolError_t errorSchedule; - - /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + uint32_t count, maxJobs; + uint32_t scheduled = 0, rescheduled = 0, failedReschudule = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = 10; + IotTaskPoolJob_t tpJobs[ 10 ] = { 0 }; + #endif + + /* Create all jobs. */ + for( count = 0; count < maxJobs; ++count ) + { + /* Shedule the job to be recycled in the callback. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + } - switch( errorSchedule ) + /* Schedule all jobs. */ + for( count = 0; count < maxJobs; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + IotTaskPoolError_t errorSchedule; - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; + /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ + errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); - default: - TEST_ASSERT( false ); + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } } - } - - /* Give a chance to some jobs to start execution. */ - IotClock_SleepMs( 50 ); - - /* Reschedule all. Some will fail to be rescheduled... */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorReSchedule; - errorReSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + /* Give a chance to some jobs to start execution. */ + IotClock_SleepMs( 50 ); - switch( errorReSchedule ) + /* Reschedule all. Some will fail to be rescheduled... */ + for( count = 0; count < maxJobs; ++count ) { - case IOT_TASKPOOL_SUCCESS: - rescheduled++; - break; - - case IOT_TASKPOOL_ILLEGAL_OPERATION: - /* Job already executed. */ - failedReschudule++; - break; + IotTaskPoolError_t errorReSchedule; - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; + errorReSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); - default: - TEST_ASSERT( false ); + switch( errorReSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + rescheduled++; + break; + + case IOT_TASKPOOL_ILLEGAL_OPERATION: + /* Job already executed. */ + failedReschudule++; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } } - } - TEST_ASSERT_TRUE( ( rescheduled + failedReschudule ) == scheduled ); + TEST_ASSERT_TRUE( ( rescheduled + failedReschudule ) == scheduled ); - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + /* Wait until callback is executed. */ + while( true ) + { + IotClock_SleepMs( 50 ); - IotMutex_Lock( &userContext.lock ); + IotMutex_Lock( &userContext.lock ); - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); - break; + break; + } + + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1529,96 +1578,99 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Statically allocated jobs, schedule all, then wait all. */ + if( TEST_PROTECT() ) { - uint32_t count, maxJobs; - uint32_t scheduled = 0, rescheduled = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; - #endif - - /* Schedule all jobs. */ - for( count = 0; count < maxJobs; ++count ) + /* Statically allocated jobs, schedule all, then wait all. */ { - IotTaskPoolError_t errorSchedule; + uint32_t count, maxJobs; + uint32_t scheduled = 0, rescheduled = 0; + + /* In static memory mode, only the recyclable job limit may be allocated. */ + #if IOT_STATIC_MEMORY_ONLY == 1 + maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + #else + maxJobs = TEST_TASKPOOL_ITERATIONS; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + #endif + + /* Schedule all jobs. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorSchedule; - /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + /* Shedule the job to be recycle in the callback. */ + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], ONE_HOUR_FROM_NOW_MS ); + /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], ONE_HOUR_FROM_NOW_MS ); - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + TEST_ASSERT( false ); + break; - default: - TEST_ASSERT( false ); + default: + TEST_ASSERT( false ); + } } - } - - /* Reschedule all. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorReSchedule; - - errorReSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], 10 + ( rand() % 500 ) ); - switch( errorReSchedule ) + /* Reschedule all. */ + for( count = 0; count < maxJobs; ++count ) { - case IOT_TASKPOOL_SUCCESS: - ++rescheduled; - break; + IotTaskPoolError_t errorReSchedule; - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; + errorReSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], 10 + ( rand() % 500 ) ); - default: - TEST_ASSERT( false ); + switch( errorReSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++rescheduled; + break; + + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } } - } - TEST_ASSERT_TRUE( rescheduled == scheduled ); + TEST_ASSERT_TRUE( rescheduled == scheduled ); - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); + /* Wait until callback is executed. */ + while( true ) + { + IotClock_SleepMs( 50 ); - IotMutex_Lock( &userContext.lock ); + IotMutex_Lock( &userContext.lock ); - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); + if( userContext.counter == scheduled ) + { + IotMutex_Unlock( &userContext.lock ); + + break; + } - break; + IotMutex_Unlock( &userContext.lock ); } - IotMutex_Unlock( &userContext.lock ); + TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); } - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1651,101 +1703,104 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - IotTaskPool_Create( &tpInfo, &taskPool ); + TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - /* Create and schedule loop. */ - for( count = 0; count < maxJobs; ++count ) + if( TEST_PROTECT() ) { - IotTaskPoolError_t errorSchedule; + /* Create and schedule loop. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t errorSchedule; - IotTaskPoolError_t errorCreate = IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobs[ count ] ); + IotTaskPoolError_t errorCreate = IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobs[ count ] ); - switch( errorCreate ) - { - case IOT_TASKPOOL_SUCCESS: - break; + switch( errorCreate ) + { + case IOT_TASKPOOL_SUCCESS: + break; - case IOT_TASKPOOL_NO_MEMORY: /* OK. */ - continue; + case IOT_TASKPOOL_NO_MEMORY: /* OK. */ + continue; + + case IOT_TASKPOOL_BAD_PARAMETER: + TEST_ASSERT( false ); + break; - case IOT_TASKPOOL_BAD_PARAMETER: - TEST_ASSERT( false ); - break; + default: + TEST_ASSERT( false ); + } - default: - TEST_ASSERT( false ); - } + errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &jobs[ count ], 10 + ( rand() % 20 ) ); - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &jobs[ count ], 10 + ( rand() % 20 ) ); + switch( errorSchedule ) + { + case IOT_TASKPOOL_SUCCESS: + ++scheduled; + break; - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); + case IOT_TASKPOOL_BAD_PARAMETER: + case IOT_TASKPOOL_ILLEGAL_OPERATION: + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + } } - } - /* Cancellation loop. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t statusAtCancellation = IOT_TASKPOOL_STATUS_READY; - IotTaskPoolJobStatus_t statusAfterCancellation = IOT_TASKPOOL_STATUS_READY; + /* Cancellation loop. */ + for( count = 0; count < maxJobs; ++count ) + { + IotTaskPoolError_t error; + IotTaskPoolJobStatus_t statusAtCancellation = IOT_TASKPOOL_STATUS_READY; + IotTaskPoolJobStatus_t statusAfterCancellation = IOT_TASKPOOL_STATUS_READY; + + error = IotTaskPool_TryCancel( &taskPool, &jobs[ count ], &statusAtCancellation ); + + switch( error ) + { + case IOT_TASKPOOL_SUCCESS: + canceled++; + + TEST_ASSERT( + ( statusAtCancellation == IOT_TASKPOOL_STATUS_READY ) || + ( statusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ) || + ( statusAtCancellation == IOT_TASKPOOL_STATUS_SCHEDULED ) || + ( statusAtCancellation == IOT_TASKPOOL_STATUS_CANCELED ) + ); + + TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( statusAfterCancellation == IOT_TASKPOOL_STATUS_CANCELED ); + break; - error = IotTaskPool_TryCancel( &taskPool, &jobs[ count ], &statusAtCancellation ); + case IOT_TASKPOOL_CANCEL_FAILED: + TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); + TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); + break; + + case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: + /* This must be a test issue. */ + TEST_ASSERT( false ); + break; + + default: + TEST_ASSERT( false ); + break; + } + } - switch( error ) + /* Wait until callback is executed. */ + while( ( scheduled - canceled ) != userContext.counter ) { - case IOT_TASKPOOL_SUCCESS: - canceled++; - - TEST_ASSERT( - ( statusAtCancellation == IOT_TASKPOOL_STATUS_READY ) || - ( statusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ) || - ( statusAtCancellation == IOT_TASKPOOL_STATUS_SCHEDULED ) || - ( statusAtCancellation == IOT_TASKPOOL_STATUS_CANCELED ) - ); - - TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( statusAfterCancellation == IOT_TASKPOOL_STATUS_CANCELED ); - break; - - case IOT_TASKPOOL_CANCEL_FAILED: - TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); - TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); - break; - - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - /* This must be a test issue. */ - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - break; + IotClock_SleepMs( 50 ); } - } - /* Wait until callback is executed. */ - while( ( scheduled - canceled ) != userContext.counter ) - { - IotClock_SleepMs( 50 ); + TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); } - TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); - - IotTaskPool_Destroy( &taskPool ); + TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); From 5e82d9d5e67ec04cce47ef9728d182cd4a36aaf3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 25 Mar 2019 14:28:47 -0700 Subject: [PATCH 070/844] Fix double free bug in Shadow. (#346) --- lib/include/types/aws_iot_shadow_types.h | 6 +-- lib/source/shadow/aws_iot_shadow_api.c | 44 ++++++++++--------- lib/source/shadow/aws_iot_shadow_operation.c | 2 +- .../system/aws_iot_tests_shadow_system.c | 12 ++--- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index e6cc5c8e43..367b010c2a 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -369,7 +369,7 @@ typedef enum AwsIotShadowCallbackType */ typedef struct AwsIotShadowCallbackParam { - AwsIotShadowCallbackType_t callbackType; /**< @brief Reason for invoking the Shadow callback function. */ + AwsIotShadowCallbackType_t callbackType; /**< @brief Reason for invoking the Shadow callback function to provide context. */ const char * pThingName; /**< @brief The Thing Name associated with this Shadow callback. */ size_t thingNameLength; /**< @brief Length of #AwsIotShadowCallbackParam_t.pThingName. */ @@ -414,7 +414,7 @@ typedef struct AwsIotShadowCallbackParam */ typedef struct AwsIotShadowCallbackInfo { - void * param1; /**< @brief The first parameter to pass to the callback function. */ + void * pCallbackContext; /**< @brief The first parameter to pass to the callback function. */ /** * @brief User-provided callback function signature. @@ -426,7 +426,7 @@ typedef struct AwsIotShadowCallbackInfo * @see #AwsIotShadowCallbackParam_t for more information on the second parameter. */ void ( * function )( void *, - AwsIotShadowCallbackParam_t * const ); + AwsIotShadowCallbackParam_t * ); } AwsIotShadowCallbackInfo_t; /** diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index be813b41c8..78ee8fbb8c 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -328,10 +328,10 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio return AWS_IOT_SHADOW_BAD_PARAMETER; } - IotLogDebug( "Processing Shadow %s callback for %.*s.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogInfo( "(%.*s) Modifying Shadow %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); /* Lock the subscription list mutex to check for an existing subscription * object. */ @@ -356,20 +356,20 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio /* Replace existing callback. */ if( pCallbackInfo != NULL ) { - IotLogDebug( "Found existing %s callback for %.*s. Replacing callback.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); pSubscription->callbacks[ type ] = *pCallbackInfo; } /* Remove existing callback. */ else { - IotLogDebug( "Removing existing %s callback for %.*s.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogInfo( "(%.*s) Removing existing %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); /* Unsubscribe, then clear the callback information. */ ( void ) _modifyCallbackSubscriptions( mqttConnection, @@ -390,10 +390,10 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio /* Add new callback. */ if( pCallbackInfo != NULL ) { - IotLogDebug( "Adding new %s callback for %.*s.", - _pAwsIotShadowCallbackNames[ type ], - thingNameLength, - pThingName ); + IotLogInfo( "(%.*s) Adding new %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); pSubscription->callbacks[ type ] = *pCallbackInfo; status = _modifyCallbackSubscriptions( mqttConnection, @@ -411,6 +411,12 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotLogInfo( "(%.*s) Shadow %s callback operation complete with result %s.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ], + AwsIotShadow_strerror( status ) ); + return status; } @@ -531,10 +537,6 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt { pSubscription->pTopicBuffer = pTopicFilter; } - else - { - AwsIotShadow_FreeString( pTopicFilter ); - } } return status; @@ -559,7 +561,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, callbackParam.callback.documentLength = pMessage->message.info.payloadLength; /* Invoke the callback function. */ - pSubscription->callbacks[ type ].function( pSubscription->callbacks[ type ].param1, + pSubscription->callbacks[ type ].function( pSubscription->callbacks[ type ].pCallbackContext, &callbackParam ); } diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 8d35187598..f01734e774 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -867,7 +867,7 @@ void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) callbackParam.operation.get.documentLength = pOperation->get.documentLength; } - pOperation->notify.callback.function( pOperation->notify.callback.param1, + pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, &callbackParam ); } diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 5a139a22e5..b7140fa489 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -152,7 +152,7 @@ static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; * and unblocks the main test thread. */ static void _operationComplete( void * pArgument, - AwsIotShadowCallbackParam_t * const pOperation ) + AwsIotShadowCallbackParam_t * pOperation ) { _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; const char * pJsonValue = NULL; @@ -193,7 +193,7 @@ static void _operationComplete( void * pArgument, * thread. */ static void _deltaCallback( void * pArgument, - AwsIotShadowCallbackParam_t * const pCallback ) + AwsIotShadowCallbackParam_t * pCallback ) { IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; const char * pValue = NULL, * pClientToken = NULL; @@ -233,7 +233,7 @@ static void _deltaCallback( void * pArgument, * thread. */ static void _updatedCallback( void * pArgument, - AwsIotShadowCallbackParam_t * const pCallback ) + AwsIotShadowCallbackParam_t * pCallback ) { IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; const char * pPrevious = NULL, * pCurrent = NULL, * pClientToken = NULL; @@ -290,7 +290,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) _operationCompleteParams_t callbackParam = { 0 }; /* Initialize the members of the operation callback info. */ - callbackInfo.param1 = &callbackParam; + callbackInfo.pCallbackContext = &callbackParam; callbackInfo.function = _operationComplete; /* Initialize the common members of the Shadow document info. */ @@ -605,7 +605,7 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); /* Set the delta callback information. */ - deltaCallback.param1 = &waitSem; + deltaCallback.pCallbackContext = &waitSem; deltaCallback.function = _deltaCallback; /* Set a desired state in the Update document. */ @@ -680,7 +680,7 @@ TEST( Shadow_System, UpdatedCallback ) TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); /* Set the delta callback information. */ - updatedCallback.param1 = &waitSem; + updatedCallback.pCallbackContext = &waitSem; updatedCallback.function = _updatedCallback; /* Set a desired state in the Update document. */ From 62096fc045697ec9e70c0c7cb9cb5f3de21320d6 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 25 Mar 2019 14:45:59 -0700 Subject: [PATCH 071/844] Fix AWS IoT MQTT mode in tests. (#347) --- lib/source/mqtt/iot_mqtt_api.c | 41 +++++++++---------- tests/mqtt/system/iot_tests_mqtt_system.c | 12 ++++-- tests/mqtt/unit/iot_tests_mqtt_api.c | 4 -- tests/mqtt/unit/iot_tests_mqtt_receive.c | 4 -- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 4 -- tests/mqtt/unit/iot_tests_mqtt_validate.c | 4 -- 6 files changed, 28 insertions(+), 41 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 3d7dce301b..b352724dd7 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -466,8 +466,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) { - IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - /* Clean up keep-alive if still allocated. */ if( pMqttConnection->keepAliveMs != 0 ) { @@ -508,25 +506,6 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); - /* An MQTT connection that owns its network connection should destroy it. */ - if( pMqttConnection->ownNetworkConnection == true ) - { - networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "Failed to destroy network connection." ); - } - else - { - IotLogInfo( "Network connection destroyed." ); - } - } - else - { - _EMPTY_ELSE_MARKER; - } - IotLogDebug( "(MQTT connection %p) Connection destroyed.", pMqttConnection ); /* Free connection. */ @@ -1216,6 +1195,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { bool disconnected = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; _mqttOperation_t * pOperation = NULL; IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); @@ -1355,6 +1335,25 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); + /* An MQTT connection that owns its network connection should destroy it. */ + if( mqttConnection->ownNetworkConnection == true ) + { + networkStatus = mqttConnection->pNetworkInterface->destroy( mqttConnection->pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "Failed to destroy network connection." ); + } + else + { + IotLogInfo( "Network connection destroyed." ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } + /* Decrement the connection reference count and destroy it if possible. */ _IotMqtt_DecrementConnectionReferences( mqttConnection ); } diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 22a73c123b..0d8a423237 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -84,10 +84,6 @@ extern int snprintf( char *, #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef IOT_MQTT_CONNECT_INFO_INITIALIZER - #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /** @@ -494,6 +490,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) if( TEST_PROTECT() ) { /* Set the members of the MQTT connection info. */ + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -730,6 +727,7 @@ TEST( MQTT_System, SubscribePublishAsync ) subscription.callback.pCallbackContext = &publishWaitSem; /* Initialize members of the connect info. */ + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -864,6 +862,7 @@ TEST( MQTT_System, LastWillAndTestament ) lwtNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + lwtConnectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; lwtConnectInfo.cleanSession = true; lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); @@ -892,6 +891,7 @@ TEST( MQTT_System, LastWillAndTestament ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Create a connection that requests the LWT. */ + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -974,6 +974,7 @@ TEST( MQTT_System, RestorePreviousSession ) _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /* Re-establish the MQTT connection with a previous session. */ + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; @@ -1049,6 +1050,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) IotMqttOperation_t pPublishOperation[ 3 ] = { IOT_MQTT_OPERATION_INITIALIZER }; /* Set the client identifier and length. */ + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -1123,6 +1125,7 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -1194,6 +1197,7 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { + connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 8fd91daa45..2afc04a041 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -57,10 +57,6 @@ #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef IOT_MQTT_CONNECT_INFO_INITIALIZER - #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /** diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index f959b1a689..21d544dce1 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -54,10 +54,6 @@ #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef IOT_MQTT_CONNECT_INFO_INITIALIZER - #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /** @brief Default CONNACK packet for the receive tests. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index fda0b7ba83..cd95b0dff2 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -88,10 +88,6 @@ extern int snprintf( char *, #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef IOT_MQTT_CONNECT_INFO_INITIALIZER - #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /* diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 2bf3d634cc..57f209751d 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -50,10 +50,6 @@ #define _AWS_IOT_MQTT_SERVER true #else #define _AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef IOT_MQTT_CONNECT_INFO_INITIALIZER - #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } #endif /** From 2e225e715311ec46decbfa34542d1d706c3e5147 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 25 Mar 2019 16:26:18 -0700 Subject: [PATCH 072/844] Fix order setting MQTT serializer in tests. (#348) --- tests/mqtt/system/iot_tests_mqtt_system.c | 11 +++++------ tests/shadow/system/aws_iot_tests_shadow_system.c | 4 ++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 0d8a423237..a0e73a4c32 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -626,6 +626,11 @@ TEST_SETUP( MQTT_System ) _CLIENT_IDENTIFIER_MAX_LENGTH ); #endif + /* Set the overrides for the default serializers. */ + #ifdef IOT_TEST_MQTT_SERIALIZER + _pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + #endif + /* Set the MQTT network setup parameters. */ ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); _networkInfo.createNetworkConnection = true; @@ -636,12 +641,6 @@ TEST_SETUP( MQTT_System ) #if IOT_TEST_SECURED_CONNECTION == 1 _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; #endif - - - /* Set the overrides for the default serializers. */ - #ifdef IOT_TEST_MQTT_SERIALIZER - _pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; - #endif } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index b7140fa489..b43061fa19 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -476,6 +476,10 @@ TEST_SETUP( Shadow_System ) _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; #endif + #ifdef IOT_TEST_MQTT_SERIALIZER + _networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + #endif + /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT * client identifier. */ connectInfo.awsIotMqttMode = true; From 450f7651da5733ac061a4517f6096716d0df8b43 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 27 Mar 2019 14:30:59 -0700 Subject: [PATCH 073/844] Don't create recv test semaphore for expected 0. (#349) * Don't create recv test semaphore for expected 0. * Remove POSIX in MQTT subscription tests. --- CMakeLists.txt | 10 +-- tests/mqtt/unit/iot_tests_mqtt_receive.c | 14 +++- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 78 +++++++++---------- 3 files changed, 50 insertions(+), 52 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40485b0034..7cd14096ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,14 +28,14 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # Atomic headers. # If GCC -- # for version <4.7.0, __sync_* shall be called. -# for version >= 4.7.0, __atomic_* shall be called. -# We only implemented atomic with __atomic_*. +# for version >= 4.7.0, __atomic_* shall be called. +# We only implemented atomic with __atomic_*. if( CMAKE_C_COMPILER_ID STREQUAL GNU AND ( CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.7 OR CMAKE_C_COMPILER_VERSION VERSION_EQUAL 4.7 ) ) add_definitions( -DCOMPILER_OPTION_GCC_ATOMIC_BUILTIN=1 ) endif() # If Clang -- -# Clang supports GCC's __atomic_* built-in functions since 3.1 release. +# Clang supports GCC's __atomic_* built-in functions since 3.1 release. if( CMAKE_C_COMPILER_ID STREQUAL Clang AND ( CMAKE_C_COMPILER_VERSION VERSION_GREATER 3.1 OR CMAKE_C_COMPILER_VERSION VERSION_EQUAL 3.1 ) ) add_definitions( -DCOMPILER_OPTION_GCC_ATOMIC_BUILTIN=1 ) endif() @@ -56,8 +56,8 @@ if( ${IOT_BUILD_TESTS} ) tests/unity/fixture tests/mqtt/access ) - # Define the constant to enable test access. - add_definitions( -DIOT_BUILD_TESTS=1 ) + # Define the constant to enable test access. + add_definitions( -DIOT_BUILD_TESTS=1 ) # Tests config file. add_definitions( -DIOT_CONFIG_FILE="iot_tests_config.h" ) diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 21d544dce1..ad6756e644 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -334,9 +334,12 @@ static bool _processPublish( const uint8_t * pPublish, _receiveContext_t receiveContext = { 0 }; /* Create a semaphore that counts how many times the publish callback is invoked. */ - if( IotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) + if( expectedInvokeCount > 0 ) { - return false; + if( IotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) + { + return false; + } } /* Set the subscription parameter. */ @@ -370,8 +373,11 @@ static bool _processPublish( const uint8_t * pPublish, /* Ensure that the invoke count semaphore has a value of 0, then destroy the * semaphore. */ - finalInvokeCount = IotSemaphore_GetCount( &invokeCount ); - IotSemaphore_Destroy( &invokeCount ); + if( expectedInvokeCount > 0 ) + { + finalInvokeCount = IotSemaphore_GetCount( &invokeCount ); + IotSemaphore_Destroy( &invokeCount ); + } /* Check results against expected values. */ return ( finalInvokeCount == 0 ) && diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index cd95b0dff2..5013c7fd5c 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -42,13 +42,6 @@ /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" -/* POSIX includes. */ -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif - /* Test framework includes. */ #include "unity_fixture.h" @@ -159,9 +152,14 @@ static bool _connectionCreated = false; static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /** - * @brief Synchronizes threads in the multithreaded test. + * @brief Synchronizes threads in the multithreaded test at start. */ -static pthread_barrier_t _mtTestBarrier; +static IotSemaphore_t _mtTestStart; + +/** + * @brief Synchronizes threads in the multithreaded test at exit. + */ +static IotSemaphore_t _mtTestExit; /*-----------------------------------------------------------*/ @@ -293,7 +291,7 @@ static void _blockingCallback( void * pArgument, /** * @brief Thread routing of the multithreaded test. */ -static void * _multithreadTestThread( void * pArgument ) +static void _multithreadTestThread( void * pArgument ) { size_t i = 0; int barrierResult = 0; @@ -302,14 +300,7 @@ static void * _multithreadTestThread( void * pArgument ) IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Synchronize with the other threads before starting the test. */ - barrierResult = pthread_barrier_wait( &_mtTestBarrier ); - - if( ( barrierResult != 0 ) && ( barrierResult != PTHREAD_BARRIER_SERIAL_THREAD ) ) - { - *pThreadResult = false; - - return NULL; - } + IotSemaphore_Wait( &( _mtTestStart ) ); /* Add items to the subscription list. */ for( i = 0; i < _LIST_ITEM_COUNT; i++ ) @@ -329,7 +320,7 @@ static void * _multithreadTestThread( void * pArgument ) { *pThreadResult = false; - return NULL; + return; } } @@ -343,7 +334,8 @@ static void * _multithreadTestThread( void * pArgument ) *pThreadResult = true; - return NULL; + /* Synchronize with the other threads before exiting the test. */ + IotSemaphore_Post( &( _mtTestExit ) ); } /*-----------------------------------------------------------*/ @@ -767,23 +759,20 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) */ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) { - int i = 0, threadsCreated = 0, barrierReturn = 0; - volatile int threadsJoined = 0; - pthread_t testThreads[ _MT_THREAD_COUNT ] = { 0 }; + int32_t i = 0, threadsCreated = 0, threadsExited = 0; bool threadResults[ _MT_THREAD_COUNT ] = { 0 }; - /* Create the synchronization barrier. */ - TEST_ASSERT_EQUAL_INT( 0, pthread_barrier_init( &_mtTestBarrier, - NULL, - _MT_THREAD_COUNT + 1 ) ); + /* Create the synchronization semaphores. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestStart ), 0, _MT_THREAD_COUNT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestExit ), 0, _MT_THREAD_COUNT ) ); /* Spawn threads for the test. */ for( i = 0; i < _MT_THREAD_COUNT; i++ ) { - if( pthread_create( &( testThreads[ i ] ), - NULL, - _multithreadTestThread, - &( threadResults[ i ] ) ) != 0 ) + if( Iot_CreateDetachedThread( _multithreadTestThread, + &( threadResults[ i ] ), + IOT_THREAD_DEFAULT_PRIORITY, + IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) { break; } @@ -792,23 +781,28 @@ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) /* Record how many threads were created. */ threadsCreated = i; - /* Synchronize with the test threads. */ - barrierReturn = pthread_barrier_wait( &_mtTestBarrier ); + /* Signal all created threads to start. */ + for( i = 0; i < threadsCreated; i++ ) + { + IotSemaphore_Post( &_mtTestStart ); + } /* Wait for all created threads to finish. */ for( i = 0; i < threadsCreated; i++ ) { - if( pthread_join( testThreads[ i ], NULL ) == 0 ) + if( IotSemaphore_TimedWait( &_mtTestExit, + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) { - threadsJoined++; + break; } + + threadsExited++; } if( TEST_PROTECT() ) { - /* Check the results of barrier wait and thread create/join. */ - TEST_ASSERT_TRUE( barrierReturn == 0 || barrierReturn == PTHREAD_BARRIER_SERIAL_THREAD ); - TEST_ASSERT_EQUAL_INT( threadsCreated, threadsJoined ); + /* Check how many threads ran. */ + TEST_ASSERT_EQUAL_INT( threadsCreated, threadsExited ); TEST_ASSERT_EQUAL_INT( threadsCreated, _MT_THREAD_COUNT ); /* Check the results of the test threads. */ @@ -821,11 +815,9 @@ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } - /* Destroy the synchronization barrier. */ - if( pthread_barrier_destroy( &_mtTestBarrier ) != 0 ) - { - TEST_FAIL_MESSAGE( "Failed to destroy barrier" ); - } + /* Destroy the synchronization semaphores. */ + IotSemaphore_Destroy( &_mtTestStart ); + IotSemaphore_Destroy( &_mtTestExit ); } /*-----------------------------------------------------------*/ From 512793cb4ddd0fb66456e09d6e4fa8308970bd02 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 28 Mar 2019 13:20:36 -0700 Subject: [PATCH 074/844] Add associated MQTT connection to Shadow callback param. (#350) --- lib/include/types/aws_iot_shadow_types.h | 2 ++ lib/source/common/iot_json_utils.c | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 1 + lib/source/shadow/aws_iot_shadow_operation.c | 1 + tests/shadow/system/aws_iot_tests_shadow_system.c | 7 ++++++- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 367b010c2a..12a289c769 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -374,6 +374,8 @@ typedef struct AwsIotShadowCallbackParam const char * pThingName; /**< @brief The Thing Name associated with this Shadow callback. */ size_t thingNameLength; /**< @brief Length of #AwsIotShadowCallbackParam_t.pThingName. */ + IotMqttConnection_t mqttConnection; /**< @brief The MQTT connection associated with the Shadow callback. */ + union { /* Valid for completed Shadow operations. */ diff --git a/lib/source/common/iot_json_utils.c b/lib/source/common/iot_json_utils.c index 5c3f9b48a8..327762fcd2 100644 --- a/lib/source/common/iot_json_utils.c +++ b/lib/source/common/iot_json_utils.c @@ -60,7 +60,7 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, /* Search the characters in the JSON document for the key. The end of the JSON * document does not have to be searched once too few characters remain to hold a * value. */ - while( i < jsonDocumentLength - jsonKeyLength - 5 ) + while( i < jsonDocumentLength - jsonKeyLength - 3 ) { /* If the first character in the key is found and there's an unescaped double * quote after the key length, do a string compare for the key. */ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 78ee8fbb8c..a218ed3c83 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -555,6 +555,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, /* Set the members of the callback param. */ callbackParam.callbackType = type + _SHADOW_OPERATION_COUNT; /* Shadow callbacks are enumerated after the operations. */ + callbackParam.mqttConnection = pMessage->mqttConnection; callbackParam.pThingName = pSubscription->pThingName; callbackParam.thingNameLength = pSubscription->thingNameLength; callbackParam.callback.pDocument = pMessage->message.info.pPayload; diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index f01734e774..362c5b764f 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -855,6 +855,7 @@ void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) { /* Set the common members of the callback parameter. */ callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; + callbackParam.mqttConnection = pOperation->mqttConnection; callbackParam.operation.result = pOperation->status; callbackParam.operation.reference = pOperation; callbackParam.pThingName = pSubscription->pThingName; diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index b43061fa19..cffa5c44b5 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -160,6 +160,7 @@ static void _operationComplete( void * pArgument, /* Check parameters against received operation information. */ AwsIotShadow_Assert( pOperation->callbackType == pParams->expectedType ); + AwsIotShadow_Assert( pOperation->mqttConnection == _mqttConnection ); AwsIotShadow_Assert( pOperation->operation.result == AWS_IOT_SHADOW_SUCCESS ); AwsIotShadow_Assert( pOperation->operation.reference == pParams->operation ); AwsIotShadow_Assert( pOperation->thingNameLength == _THING_NAME_LENGTH ); @@ -199,8 +200,9 @@ static void _deltaCallback( void * pArgument, const char * pValue = NULL, * pClientToken = NULL; size_t valueLength = 0, clientTokenLength = 0; - /* Check callback type. */ + /* Check callback type and MQTT connection. */ AwsIotShadow_Assert( pCallback->callbackType == AWS_IOT_SHADOW_DELTA_CALLBACK ); + AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); /* Check delta document state. */ AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, @@ -239,6 +241,9 @@ static void _updatedCallback( void * pArgument, const char * pPrevious = NULL, * pCurrent = NULL, * pClientToken = NULL; size_t previousStateLength = 0, currentStateLength = 0, clientTokenLength = 0; + /* Check MQTT connection. */ + AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); + /* Check updated document previous state. */ AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, pCallback->callback.documentLength, From c98b17972aa3eec8d5e889428d6b479ccd9d7f0f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 29 Mar 2019 10:03:33 -0700 Subject: [PATCH 075/844] Finish Shadow demo. (#351) * Finish Shadow demo. * Update demo config and CI script for Shadow demo. --- README.md | 2 +- demos/aws_iot_demo_shadow.c | 605 +++++++++++++++++- demos/include/iot_demo_config.h | 5 + lib/source/shadow/aws_iot_shadow_operation.c | 2 +- scripts/ci_test_shadow.sh | 2 +- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 1 - 6 files changed, 612 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 45e04bf59d..798a33c0d3 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ This Beta library is a new design that inherits from both the AWS IoT Device SDK - MQTT persistent session support. Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: -- Documentation and demo for Shadow library is incomplete. +- Documentation for Shadow library is incomplete. - Auto-reconnect for MQTT connections. - mbedTLS network stack. - Shadow JSON document generator. diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 2555e33379..1bd8dacd09 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -22,6 +22,9 @@ /** * @file aws_iot_demo_shadow.c * @brief Demonstrates usage of the Thing Shadow library. + * + * This program demonstrates the using Shadow documents to toggle a state called + * "powerOn" in a remote device. */ /* Build using a config header, if provided. */ @@ -50,6 +53,9 @@ /* Shadow include. */ #include "aws_iot_shadow.h" +/* JSON utilities include. */ +#include "iot_json_utils.h" + /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -63,6 +69,28 @@ extern int snprintf( char *, ... ); /** @endcond */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration settings. + */ +#ifndef AWS_IOT_DEMO_SHADOW_UPDATE_COUNT + #define AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ( 20 ) +#endif +#ifndef AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS + #define AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ( 3000 ) +#endif +/** @endcond */ + +/* Validate Shadow demo configuration settings. */ +#if AWS_IOT_DEMO_SHADOW_UPDATE_COUNT <= 0 + #error "AWS_IOT_DEMO_SHADOW_UPDATE_COUNT cannot be 0 or negative." +#endif +#if AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS <= 0 + #error "AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS cannot be 0 or negative." +#endif + /** * @brief The keep-alive interval used for this demo. * @@ -75,6 +103,330 @@ extern int snprintf( char *, */ #define _TIMEOUT_MS ( 5000 ) +/** + * @brief Format string representing a Shadow document with a "desired" state. + * + * Note the client token, which is required for all Shadow updates. The client + * token must be unique at any given time, but may be reused once the update is + * completed. For this demo, a timestamp is used for a client token. + */ +#define _SHADOW_DESIRED_JSON \ + "{" \ + "\"state\":{" \ + "\"desired\":{" \ + "\"powerOn\":%.1d" \ + "}" \ + "}," \ + "\"clientToken\":\"%.6llu\"" \ + "}" + +/** + * @brief The expected size of #_SHADOW_DESIRED_JSON. + * + * Because all the format specifiers in #_SHADOW_DESIRED_JSON include a length, + * its full size is known at compile-time. + */ +#define _EXPECTED_DESIRED_JSON_SIZE ( sizeof( _SHADOW_DESIRED_JSON ) - 4 ) + +/** + * @brief Format string representing a Shadow document with a "reported" state. + * + * Note the client token, which is required for all Shadow updates. The client + * token must be unique at any given time, but may be reused once the update is + * completed. For this demo, a timestamp is used for a client token. + */ +#define _SHADOW_REPORTED_JSON \ + "{" \ + "\"state\":{" \ + "\"reported\":{" \ + "\"powerOn\":%.1d" \ + "}" \ + "}," \ + "\"clientToken\":\"%.6llu\"" \ + "}" + +/** + * @brief The expected size of #_SHADOW_REPORTED_JSON. + * + * Because all the format specifiers in #_SHADOW_REPORTED_JSON include a length, + * its full size is known at compile-time. + */ +#define _EXPECTED_REPORTED_JSON_SIZE ( sizeof( _SHADOW_REPORTED_JSON ) - 4 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Parses a key in the "state" section of a Shadow delta document. + * + * @param[in] pDeltaDocument The Shadow delta document to parse. + * @param[in] deltaDocumentLength The length of `pDeltaDocument`. + * @param[in] pDeltaKey The key in the delta document to find. Must be NULL-terminated. + * @param[out] pDelta Set to the first character in the delta key. + * @param[out] pDeltaLength The length of the delta key. + * + * @return `true` if the given delta key is found; `false` otherwise. + */ +static bool _getDelta( const char * pDeltaDocument, + size_t deltaDocumentLength, + const char * pDeltaKey, + const char ** pDelta, + size_t * pDeltaLength ) +{ + bool stateFound = false, deltaFound = false; + const size_t deltaKeyLength = strlen( pDeltaKey ); + const char * pState = NULL; + size_t stateLength = 0; + + /* Find the "state" key in the delta document. */ + stateFound = IotJsonUtils_FindJsonValue( pDeltaDocument, + deltaDocumentLength, + "state", + 5, + &pState, + &stateLength ); + + if( stateFound == true ) + { + /* Find the delta key within the "state" section. */ + deltaFound = IotJsonUtils_FindJsonValue( pState, + stateLength, + pDeltaKey, + deltaKeyLength, + pDelta, + pDeltaLength ); + } + else + { + IotLogWarn( "Failed to find \"state\" in Shadow delta document." ); + } + + return deltaFound; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Parses the "state" key from the "previous" or "current" sections of a + * Shadow updated document. + * + * @param[in] pUpdatedDocument The Shadow updated document to parse. + * @param[in] updatedDocumentLength The length of `pUpdatedDocument`. + * @param[in] pSectionKey Either "previous" or "current". Must be NULL-terminated. + * @param[out] pState Set to the first character in "state". + * @param[out] pStateLength Length of the "state" section. + * + * @return `true` if the "state" was found; `false` otherwise. + */ +static bool _getUpdatedState( const char * pUpdatedDocument, + size_t updatedDocumentLength, + const char * pSectionKey, + const char ** pState, + size_t * pStateLength ) +{ + bool sectionFound = false, stateFound = false; + const size_t sectionKeyLength = strlen( pSectionKey ); + const char * pSection = NULL; + size_t sectionLength = 0; + + /* Find the given section in the updated document. */ + sectionFound = IotJsonUtils_FindJsonValue( pUpdatedDocument, + updatedDocumentLength, + pSectionKey, + sectionKeyLength, + &pSection, + §ionLength ); + + if( sectionFound == true ) + { + /* Find the "state" key within the "previous" or "current" section. */ + stateFound = IotJsonUtils_FindJsonValue( pSection, + sectionLength, + "state", + 5, + pState, + pStateLength ); + } + else + { + IotLogWarn( "Failed to find section %s in Shadow updated document.", + pSectionKey ); + } + + return stateFound; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Shadow delta callback, invoked when the desired and updates Shadow + * states differ. + * + * This function simulates a device updating its state in response to a Shadow. + * + * @param[in] pCallbackContext Not used. + * @param[in] pCallbackParam The received Shadow delta document. + */ +static void _shadowDeltaCallback( void * pCallbackContext, + AwsIotShadowCallbackParam_t * pCallbackParam ) +{ + bool deltaFound = false; + const char * pDelta = NULL; + size_t deltaLength = 0; + IotSemaphore_t * pDeltaSemaphore = pCallbackContext; + int updateDocumentLength = 0; + AwsIotShadowError_t updateStatus = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + + /* Stored state. */ + static int32_t currentState = 0; + + /* A buffer containing the update document. It has static duration to prevent + * it from being placed on the call stack. */ + static char pUpdateDocument[ _EXPECTED_REPORTED_JSON_SIZE + 1 ] = { 0 }; + + /* Check if there is a different "powerOn" state in the Shadow. */ + deltaFound = _getDelta( pCallbackParam->callback.pDocument, + pCallbackParam->callback.documentLength, + "powerOn", + &pDelta, + &deltaLength ); + + if( deltaFound == true ) + { + /* Change the current state based on the value in the delta document. */ + if( *pDelta == '0' ) + { + IotLogInfo( "%.*s changing state from %d to 0.", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName, + currentState ); + + currentState = 0; + } + else if( *pDelta == '1' ) + { + IotLogInfo( "%.*s changing state from %d to 1.", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName, + currentState ); + + currentState = 1; + } + else + { + IotLogWarn( "Unknown powerOn state parsed from delta document." ); + } + + /* Set the common members to report the new state. */ + updateDocument.pThingName = pCallbackParam->pThingName; + updateDocument.thingNameLength = pCallbackParam->thingNameLength; + updateDocument.update.pUpdateDocument = pUpdateDocument; + updateDocument.update.updateDocumentLength = _EXPECTED_REPORTED_JSON_SIZE; + + /* Generate a Shadow document for the reported state. To keep the client + * token within 6 characters, it is modded by 1000000. */ + updateDocumentLength = snprintf( pUpdateDocument, + _EXPECTED_REPORTED_JSON_SIZE + 1, + _SHADOW_REPORTED_JSON, + currentState, + ( long long unsigned ) IotClock_GetTimeMs() % 1000000 ); + + if( ( size_t ) updateDocumentLength != _EXPECTED_REPORTED_JSON_SIZE ) + { + IotLogError( "Failed to generate reported state document for Shadow update." ); + } + else + { + /* Send the Shadow update. Its result is not checked, as the Shadow updated + * callback will report if the Shadow was successfully updated. */ + updateStatus = AwsIotShadow_Update( pCallbackParam->mqttConnection, + &updateDocument, + 0, + NULL, + NULL ); + + if( updateStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + IotLogWarn( "%.*s failed to report new state.", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName ); + } + else + { + IotLogInfo( "%.*s sent new state report.", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName ); + } + } + } + else + { + IotLogWarn( "Failed to parse powerOn state from delta document." ); + } + + /* Post to the delta semaphore to unblock the thread sending Shadow updates. */ + IotSemaphore_Post( pDeltaSemaphore ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Shadow updated callback, invoked when the Shadow document changes. + * + * This function reports when a Shadow has been updated. + * + * @param[in] pCallbackContext Not used. + * @param[in] pCallbackParam The received Shadow updated document. + */ +static void _shadowUpdatedCallback( void * pCallbackContext, + AwsIotShadowCallbackParam_t * pCallbackParam ) +{ + bool previousFound = false, currentFound = false; + const char * pPrevious = NULL, * pCurrent = NULL; + size_t previousLength = 0, currentLength = 0; + + /* Silence warnings about unused parameters. */ + ( void ) pCallbackContext; + + /* Find the previous Shadow document. */ + previousFound = _getUpdatedState( pCallbackParam->callback.pDocument, + pCallbackParam->callback.documentLength, + "previous", + &pPrevious, + &previousLength ); + + /* Find the current Shadow document. */ + currentFound = _getUpdatedState( pCallbackParam->callback.pDocument, + pCallbackParam->callback.documentLength, + "current", + &pCurrent, + ¤tLength ); + + /* Log the previous and current states. */ + if( ( previousFound == true ) && ( currentFound == true ) ) + { + IotLogInfo( "Shadow was updated!\n" + "Previous: {\"state\":%.*s}\n" + "Current: {\"state\":%.*s}", + previousLength, + pPrevious, + currentLength, + pCurrent ); + } + else + { + if( previousFound == false ) + { + IotLogWarn( "Previous state not found in Shadow updated document." ); + } + + if( currentFound == false ) + { + IotLogWarn( "Current state not found in Shadow updated document." ); + } + } +} + /*-----------------------------------------------------------*/ /** @@ -234,6 +586,211 @@ static int _establishMqttConnection( const char * pIdentifier, /*-----------------------------------------------------------*/ +/** + * @brief Set the Shadow callback functions used in this demo. + * + * @param[in] pDeltaSemaphore Used to synchronize Shadow updates with the delta + * callback. + * @param[in] mqttConnection The MQTT connection used for Shadows. + * @param[in] pThingName The Thing Name for Shadows in this demo. + * @param[in] thingNameLength The length of `pThingName`. + * + * @return `EXIT_SUCCESS` if all Shadow callbacks were set; `EXIT_FAILURE` + * otherwise. + */ +static int _setShadowCallbacks( IotSemaphore_t * pDeltaSemaphore, + IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength ) +{ + int status = EXIT_SUCCESS; + AwsIotShadowError_t callbackStatus = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER, + updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + + /* Set the functions for callbacks. */ + deltaCallback.pCallbackContext = pDeltaSemaphore; + deltaCallback.function = _shadowDeltaCallback; + updatedCallback.function = _shadowUpdatedCallback; + + /* Set the delta callback, which notifies of different desired and reported + * Shadow states. */ + callbackStatus = AwsIotShadow_SetDeltaCallback( mqttConnection, + pThingName, + thingNameLength, + 0, + &deltaCallback ); + + if( callbackStatus == AWS_IOT_SHADOW_SUCCESS ) + { + /* Set the updated callback, which notifies when a Shadow document is + * changed. */ + callbackStatus = AwsIotShadow_SetUpdatedCallback( mqttConnection, + pThingName, + thingNameLength, + 0, + &updatedCallback ); + } + + if( callbackStatus != AWS_IOT_SHADOW_SUCCESS ) + { + IotLogError( "Failed to set demo shadow callback, error %s.", + AwsIotShadow_strerror( callbackStatus ) ); + + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Try to delete any Shadow document in the cloud. + * + * @param[in] mqttConnection The MQTT connection used for Shadows. + * @param[in] pThingName The Shadow Thing Name to delete. + * @param[in] thingNameLength The length of `pThingName`. + */ +static void _clearShadowDocument( IotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength ) +{ + AwsIotShadowError_t deleteStatus = AWS_IOT_SHADOW_STATUS_PENDING; + + /* Delete any existing Shadow document so that this demo starts with an empty + * Shadow. */ + deleteStatus = AwsIotShadow_TimedDelete( mqttConnection, + pThingName, + thingNameLength, + 0, + _TIMEOUT_MS ); + + /* Check for return values of "SUCCESS" and "NOT FOUND". Both of these values + * mean that the Shadow document is now empty. */ + if( ( deleteStatus == AWS_IOT_SHADOW_SUCCESS ) || ( deleteStatus == AWS_IOT_SHADOW_NOT_FOUND ) ) + { + IotLogInfo( "Successfully cleared Shadow of %.*s.", + thingNameLength, + pThingName ); + } + else + { + IotLogWarn( "Shadow of %.*s not cleared.", + thingNameLength, + pThingName ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Send the Shadow updates that will trigger the Shadow callbacks. + * + * @param[in] pDeltaSemaphore Used to synchronize Shadow updates with the delta + * callback. + * @param[in] mqttConnection The MQTT connection used for Shadows. + * @param[in] pThingName The Thing Name for Shadows in this demo. + * @param[in] thingNameLength The length of `pThingName`. + * + * @return `EXIT_SUCCESS` if all Shadow updates were sent; `EXIT_FAILURE` + * otherwise. + */ +static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, + IotMqttConnection_t mqttConnection, + const char * const pThingName, + size_t thingNameLength ) +{ + int status = EXIT_SUCCESS; + int32_t i = 0, desiredState = 0; + AwsIotShadowError_t updateStatus = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + + /* A buffer containing the update document. It has static duration to prevent + * it from being placed on the call stack. */ + static char pUpdateDocument[ _EXPECTED_DESIRED_JSON_SIZE + 1 ] = { 0 }; + + /* Set the common members of the Shadow update document info. */ + updateDocument.pThingName = pThingName; + updateDocument.thingNameLength = thingNameLength; + updateDocument.update.pUpdateDocument = pUpdateDocument; + updateDocument.update.updateDocumentLength = _EXPECTED_DESIRED_JSON_SIZE; + + /* Publish Shadow updates at a set period. */ + for( i = 1; i <= AWS_IOT_DEMO_SHADOW_UPDATE_COUNT; i++ ) + { + /* Toggle the desired state. */ + desiredState = !( desiredState ); + + /* Generate a Shadow desired state document, using a timestamp for the client + * token. To keep the client token within 6 characters, it is modded by 1000000. */ + status = snprintf( pUpdateDocument, + _EXPECTED_DESIRED_JSON_SIZE + 1, + _SHADOW_DESIRED_JSON, + desiredState, + ( long long unsigned ) IotClock_GetTimeMs() % 1000000 ); + + /* Check for errors from snprintf. The expected value is the length of + * the desired JSON document less the format specifier for the state. */ + if( ( size_t ) status != _EXPECTED_DESIRED_JSON_SIZE ) + { + IotLogError( "Failed to generate desired state document for Shadow update" + " %d of %d.", i, AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ); + + status = EXIT_FAILURE; + break; + } + else + { + status = EXIT_SUCCESS; + } + + IotLogInfo( "Sending Shadow update %d of %d: %s", + i, + AWS_IOT_DEMO_SHADOW_UPDATE_COUNT, + pUpdateDocument ); + + /* Send the Shadow update. */ + updateStatus = AwsIotShadow_TimedUpdate( mqttConnection, + &updateDocument, + 0, + _TIMEOUT_MS ); + + /* Check the status of the Shadow update. */ + if( updateStatus != AWS_IOT_SHADOW_SUCCESS ) + { + IotLogError( "Failed to send Shadow update %d of %d, error %s.", + i, + AWS_IOT_DEMO_SHADOW_UPDATE_COUNT, + AwsIotShadow_strerror( updateStatus ) ); + + status = EXIT_FAILURE; + break; + } + else + { + IotLogInfo( "Successfully sent Shadow update %d of %d.", + i, + AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ); + + /* Wait for the delta callback to change its state before continuing. */ + if( IotSemaphore_TimedWait( pDeltaSemaphore, _TIMEOUT_MS ) == false ) + { + IotLogError( "Timed out waiting on delta callback to change state." ); + + status = EXIT_FAILURE; + break; + } + } + + IotClock_SleepMs( AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief The function that runs the Shadow demo, called by the demo runner. * @@ -259,8 +816,15 @@ int RunShadowDemo( bool awsIotMqttMode, /* Handle of the MQTT connection used in this demo. */ IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + /* Length of Shadow Thing Name. */ + const size_t thingNameLength = strlen( pIdentifier ); + + /* Allows the Shadow update function to wait for the delta callback to complete + * a state change before continuing. */ + IotSemaphore_t deltaSemaphore; + /* Flags for tracking which cleanup functions must be called. */ - bool librariesInitialized = false, connectionEstablished = false; + bool librariesInitialized = false, connectionEstablished = false, deltaSemaphoreCreated = false; /* The first parameter of this demo function is not used. Shadows are specific * to AWS IoT, so this value is hardcoded to true whenever needed. */ @@ -286,6 +850,39 @@ int RunShadowDemo( bool awsIotMqttMode, { /* Mark the MQTT connection as established. */ connectionEstablished = true; + + /* Create the semaphore that synchronizes with the delta callback. */ + deltaSemaphoreCreated = IotSemaphore_Create( &deltaSemaphore, 0, 1 ); + + if( deltaSemaphoreCreated == false ) + { + status = EXIT_FAILURE; + } + } + + if( status == EXIT_SUCCESS ) + { + /* Set the Shadow callbacks for this demo. */ + status = _setShadowCallbacks( &deltaSemaphore, + mqttConnection, + pIdentifier, + thingNameLength ); + } + + if( status == EXIT_SUCCESS ) + { + /* Clear the Shadow document so that this demo starts with no existing + * Shadow. */ + _clearShadowDocument( mqttConnection, pIdentifier, thingNameLength ); + + /* Send Shadow updates. */ + status = _sendShadowUpdates( &deltaSemaphore, + mqttConnection, + pIdentifier, + thingNameLength ); + + /* Delete the Shadow document created by this demo to clean up. */ + _clearShadowDocument( mqttConnection, pIdentifier, thingNameLength ); } /* Disconnect the MQTT connection if it was established. */ @@ -300,6 +897,12 @@ int RunShadowDemo( bool awsIotMqttMode, _cleanupDemo(); } + /* Destroy the delta semaphore if it was created. */ + if( deltaSemaphoreCreated == true ) + { + IotSemaphore_Destroy( &deltaSemaphore ); + } + return status; } diff --git a/demos/include/iot_demo_config.h b/demos/include/iot_demo_config.h index d0a6ccf2db..0de2f47e2b 100644 --- a/demos/include/iot_demo_config.h +++ b/demos/include/iot_demo_config.h @@ -45,6 +45,11 @@ #define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ #define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ +/* Shadow demo configuration. The demo publishes periodic Shadow updates and responds + * to changing Shadows. */ +#define AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ( 20 ) /* Number of updates to publish. */ +#define AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ( 3000 ) /* Period of Shadow updates. */ + /* Enable asserts in linear containers and MQTT. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 362c5b764f..6120cd6a9f 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -823,7 +823,7 @@ void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) { AwsIotShadowCallbackParam_t callbackParam = { 0 }; _shadowSubscription_t * pSubscription = pOperation->pSubscription, - * pRemovedSubscription; + * pRemovedSubscription = NULL; /* If the operation is waiting, post to its wait semaphore and return. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index 27148755d8..346e43f6ce 100644 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -19,7 +19,7 @@ run_tests_and_demos() { } # CMake compiler flags for building Shadow. -CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO $COMPILER_OPTIONS" +CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS=1 $COMPILER_OPTIONS" # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 5013c7fd5c..4c7526ce02 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -294,7 +294,6 @@ static void _blockingCallback( void * pArgument, static void _multithreadTestThread( void * pArgument ) { size_t i = 0; - int barrierResult = 0; bool * pThreadResult = ( bool * ) pArgument; char pTopicFilters[ _LIST_ITEM_COUNT ][ _MT_TOPIC_FILTER_LENGTH ] = { { 0 } }; IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; From de326eab0f8054c28bdb58f8b56823490718dc22 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 29 Mar 2019 11:02:57 -0700 Subject: [PATCH 076/844] Fix order of cleanup functions. (#352) --- demos/aws_iot_demo_shadow.c | 4 +-- demos/iot_demo_mqtt.c | 2 +- lib/source/common/iot_common.c | 6 ++-- tests/mqtt/system/iot_tests_mqtt_stress.c | 35 ++++++++++--------- tests/mqtt/system/iot_tests_mqtt_system.c | 18 +++++----- tests/mqtt/unit/iot_tests_mqtt_api.c | 2 +- tests/mqtt/unit/iot_tests_mqtt_receive.c | 8 ++--- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 6 ++-- .../system/aws_iot_tests_shadow_system.c | 6 ++-- tests/shadow/unit/aws_iot_tests_shadow_api.c | 22 ++++++------ 10 files changed, 57 insertions(+), 52 deletions(-) diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 1bd8dacd09..df5c43e87c 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -503,9 +503,9 @@ static int _initializeDemo( void ) */ static void _cleanupDemo( void ) { - IotCommon_Cleanup(); - IotMqtt_Cleanup(); AwsIotShadow_Cleanup(); + IotMqtt_Cleanup(); + IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/demos/iot_demo_mqtt.c b/demos/iot_demo_mqtt.c index 7eb78d8cdc..929cfaf726 100644 --- a/demos/iot_demo_mqtt.c +++ b/demos/iot_demo_mqtt.c @@ -384,8 +384,8 @@ static int _initializeDemo( void ) */ static void _cleanupDemo( void ) { - IotCommon_Cleanup(); IotMqtt_Cleanup(); + IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c index 663a9ed674..05f30d90b8 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_common.c @@ -107,12 +107,14 @@ void IotCommon_Cleanup( void ) { IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); + /* This log message must be printed before static memory management is + * cleaned up. */ + IotLogInfo( "Common libraries cleanup done." ); + /* Cleanup static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 IotStaticMemory_Cleanup(); #endif - - IotLogInfo( "Common libraries cleanup done." ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index adbfa3fb2f..6c2f67057e 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -435,7 +435,22 @@ TEST_SETUP( MQTT_Stress ) IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Initialize common components. */ - TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + if( IotCommon_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize common components." ); + } + + /* Set up the network stack. */ + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to set up network stack." ); + } + + /* Initialize the MQTT library. */ + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } /* Clear the PINGREQ override flag. */ _pingreqOverrideCalled = false; @@ -452,18 +467,6 @@ TEST_SETUP( MQTT_Stress ) serializer.serialize.pingreq = _serializePingreq; _networkInfo.pMqttSerializer = &serializer; - /* Set up the network stack. */ - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to set up network stack." ); - } - - /* Initialize the MQTT library. */ - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER @@ -536,14 +539,14 @@ TEST_TEAR_DOWN( MQTT_Stress ) _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } + /* Clean up the MQTT library. */ + IotMqtt_Cleanup(); + /* Clean up the network stack. */ IotTestNetwork_Cleanup(); /* Clean up common components. */ IotCommon_Cleanup(); - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index a0e73a4c32..0e37c53054 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -601,18 +601,18 @@ TEST_SETUP( MQTT_System ) TEST_FAIL_MESSAGE( "Failed to initialize common components." ); } - /* Initialize the MQTT library. */ - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - /* Call the network stack initialization function. */ if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to initialize network stack." ); } + /* Initialize the MQTT library. */ + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } + /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER @@ -650,15 +650,15 @@ TEST_SETUP( MQTT_System ) */ TEST_TEAR_DOWN( MQTT_System ) { - /* Clean up common components. */ - IotCommon_Cleanup(); - /* Clean up the MQTT library. */ IotMqtt_Cleanup(); /* Clean up the network stack. */ IotTestNetwork_Cleanup(); + /* Clean up common components. */ + IotCommon_Cleanup(); + /* Clear the connection pointer. */ _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 2afc04a041..ce2cc3944f 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -542,8 +542,8 @@ TEST_SETUP( MQTT_Unit_API ) */ TEST_TEAR_DOWN( MQTT_Unit_API ) { - IotCommon_Cleanup(); IotMqtt_Cleanup(); + IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index ad6756e644..4d0e985ec1 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -523,6 +523,9 @@ TEST_SETUP( MQTT_Unit_Receive ) /* Initialize common components. */ TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + /* Initialize the MQTT library. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); + /* Set the deserializer overrides. */ serializer.serialize.puback = _serializePuback; serializer.deserialize.connack = _deserializeConnack; @@ -539,9 +542,6 @@ TEST_SETUP( MQTT_Unit_Receive ) networkInfo.pNetworkInterface = &_networkInterface; networkInfo.disconnectCallback.function = _disconnectCallback; - /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - /* Initialize the MQTT connection used by the tests. */ _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, &networkInfo, @@ -579,8 +579,8 @@ TEST_TEAR_DOWN( MQTT_Unit_Receive ) { /* Clean up resources taken in test setup. */ IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - IotCommon_Cleanup(); IotMqtt_Cleanup(); + IotCommon_Cleanup(); /* Check that the tests used a deserializer override. */ TEST_ASSERT_EQUAL_INT( true, _deserializeOverrideCalled ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 4c7526ce02..121575c12c 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -357,11 +357,11 @@ TEST_SETUP( MQTT_Unit_Subscription ) /* Initialize common components. */ TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); - networkInfo.pNetworkInterface = &networkInterface; - /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); + networkInfo.pNetworkInterface = &networkInterface; + /* Create an MQTT connection with empty network info. */ _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, &networkInfo, @@ -386,8 +386,8 @@ TEST_TEAR_DOWN( MQTT_Unit_Subscription ) } /* Clean up libraries. */ - IotCommon_Cleanup(); IotMqtt_Cleanup(); + IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index cffa5c44b5..96396a755f 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -533,14 +533,14 @@ TEST_TEAR_DOWN( Shadow_System ) /* Clean up the Shadow library. */ AwsIotShadow_Cleanup(); + /* Clean up the MQTT library. */ + IotMqtt_Cleanup(); + /* Clean up the network stack. */ IotTestNetwork_Cleanup(); /* Clean up common components. */ IotCommon_Cleanup(); - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 7a1df44fe8..48d6ec8016 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -362,6 +362,12 @@ TEST_SETUP( Shadow_Unit_API ) /* Initialize common components. */ TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + /* Initialize the MQTT library. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); + + /* Initialize the Shadow library. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); + /* Clear the last packet type and identifier. */ _lastPacketType = 0; _lastPacketIdentifier = 0; @@ -374,9 +380,6 @@ TEST_SETUP( Shadow_Unit_API ) _receiveThread, NULL ); - /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - /* Set the network interface send function. */ ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); _networkInterface.send = _sendSuccess; @@ -388,9 +391,6 @@ TEST_SETUP( Shadow_Unit_API ) &networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Initialize the Shadow library. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); } /*-----------------------------------------------------------*/ @@ -400,18 +400,18 @@ TEST_SETUP( Shadow_Unit_API ) */ TEST_TEAR_DOWN( Shadow_Unit_API ) { - /* Clean up the Shadow library. */ - AwsIotShadow_Cleanup(); - /* Clean up the MQTT connection object. */ IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - /* Clean up common components. */ - IotCommon_Cleanup(); + /* Clean up the Shadow library. */ + AwsIotShadow_Cleanup(); /* Clean up the MQTT library. */ IotMqtt_Cleanup(); + /* Clean up common components. */ + IotCommon_Cleanup(); + /* Destroy the receive thread timer. */ IotClock_TimerDestroy( &_receiveTimer ); From 20e9bbe1399ad1b4cfb4e51026dae78b5458d3b4 Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Fri, 29 Mar 2019 16:57:23 -0700 Subject: [PATCH 077/844] Improvements in task poll documentation --- doc/lib/taskpool.txt | 134 +++++------------- doc/plantuml/RecyclableJobStatus.pu | 30 ++-- doc/plantuml/StaticJobStatus.pu | 16 +-- doc/plantuml/images/RecyclableJobStatus.png | Bin 57106 -> 56514 bytes doc/plantuml/images/StaticJobStatus.png | Bin 32500 -> 31124 bytes .../taskpool_design_typicaloperation.png | Bin 74493 -> 72591 bytes lib/include/iot_taskpool.h | 34 ++--- 7 files changed, 77 insertions(+), 137 deletions(-) diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index 9239ede3d8..4ad6115e2a 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -3,12 +3,22 @@ @anchor taskpool @brief Task pool library. -> A task pool is an adaptive set of threads that can grow and shrink to execute a user-provided callback through a user-defined job that can be scheduled with a non-blocking call. The design principles are to minimize the memory footprint while allowing asynchrnous execution. The adaptive behavior allows serving jobs in a timely way, without allocating system resources for the entire duration of the application. The user does not have to worry about synchronization and thread management. +> A task pool is an adaptive set of threads that can grow and shrink to execute a user-provided callback through a user-defined job that can be scheduled with a non-blocking call. The design principles are to minimize the memory footprint while allowing non-blocking execution. The adaptive behavior allows serving jobs in a timely manner, without allocating system resources for the entire duration of the application, or causing recurring allocation/deallocation patterns. The user does not have to worry about synchronization and thread management other than within its own application. Features of this library include: -- Both fully asynchronous and blocking API functions. +- Non-blocking API functions to schedule immediate and deferred jobs. +- Ability to create statically and dynamically allocated jobs. - Scalable performance and footprint. The [configuration settings](@ref taskpool_config) allow this library to be tailored to a system's resources. -- Customizable caching for low memory allocation overhead. +- Customizable caching for low memory overhead when creating jobs dynamically. + +This library uses a user-specified set of threads to process jobs speficied by the user as a callback and a context. +Overall, the task pool hinges on two main data structures: the task pool Job (IotTaskPoolJob_t) and the task pool itself (IotTaskPool_t). A task pool job carries the information about the user callback and context, one flag to track the status and a link structure for moving the job in and out of the dispatch queue and cache. User can create two types of jobs: static and recyclable. Static jobs are intended for users that know exactly how many jobs they will schedule (e.g. see Defender scenario above) or for embedding in other data structures. Static jobs need no destruction, and creation simply sets the user callback and context. Recyclable jobs are intended for scenario where user cannot know ahead of time how many jobs she will need. Recyclable jobs are dynamically allocated, and can be either destroyed after use or recycled. If jobs are recycled they are maintained in a cache (IotTaskPoolCache_t) owned by the task pool itself, and re-used when user wants to create more recyclable jobs. The task pool cache has a compile time limit, and can be pre-populated with recyclable jobs by simply creating recyclable jobs and recycling them, in an effort to limit memory allocations at run-time. This is handy for scenarios where user is aware of the steady state requirements for his application. +User jobs are queued through a non-blocking call and processed asynchronously in the order they are received. +- [Task pool API functions](@ref taskpool_functions) Provides a set of functions to enqueue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully enqueuing an operation. +- Worker threads in the task pool are woken up when operations arrive in the dispatch queue. These threads remove operations from the dispatch queue in FIFO order and execute the user-provided callback. After executing the user callback, the task pool threads try and execute any remaining jobs in the dispatch queue. The task pool tries and execute a user job as soon as it is received and if there are no threads available it will try and create one, up to the maximum number of allowed threads. The user can specificy the minimum and maximum number of threads allowed when creating the task pool. +- The user can try and cancel a job after the task has been scheduled. Cancellation is only allowed before the task enters execution. Wating on a completed task returns immediately. + +Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the task pool library on-the-go on some systems, while other systems may use an always-allocated thread pool. @dependencies{taskpool,task pool library} @dot "Task pool direct dependencies" @@ -24,30 +34,30 @@ digraph taskpool_dependencies { node[fillcolor="#aed8a9ff"]; rank = same; - singlelinkedlist[label="SingleLinkedList", URL="@ref singlelinkedlist"]; - queue[label="Queue", URL="@ref queue"]; + linear_containers[label="List/Queue", URL="@ref linear_containers"]; logging[label="Logging", URL="@ref logging"]; + static_memory[label="Static memory", URL="@ref static_memory"]; } subgraph { rank = same; platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; - platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"];\ - platform_static_memory[label="Static memory", fillcolor="#e89025ff", URL="@ref platform_static_memory"]; + platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; } - taskpool -> queue; - taskpool -> logging [label=" if logging enabled", style="dashed"]; + taskpool -> linear_containers; + taskpool -> platform_clock; taskpool -> platform_threads; - queue -> platform_threads; + taskpool -> static_memory [label=" if static memory only", style="dashed"]; + taskpool -> logging [label=" if logging enabled", style="dashed"]; logging -> platform_clock; - logging -> platform_static_memory [label=" if static memory only", style="dashed"]; + logging -> static_memory [label=" if static memory only", style="dashed"]; } @enddot Currently, the task pool library has the following dependencies: - The linear containers (list/queue) library for maintaining the data structures for scheduled and in-progress task pool operations. - The logging library may be used if @ref IOT_LOG_LEVEL_TASKPOOL is not @ref IOT_LOG_NONE. -- The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. +- The platform layer provides an interface to the operating system for thread management, timers, clock functions, etc. In addition to the components above, the task pool library may also depend on C standard library headers. */ @@ -56,113 +66,52 @@ In addition to the components above, the task pool library may also depend on C @page taskpool_design Design @brief Architecture behind the task pool library. -This library uses a user-specified set of threads to process jobs speficied by the user as a callback and a context. User jobs are queued through a non-blocking call and processed asynchronously in the order they are received. -- [Task pool API functions](@ref taskpool_functions) Provides a set of functions to enqueue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully enqueuing an operation. -- Worker threads in the task pool are woken up when operations arrive in the dispatch queue. These threads remove operations from the dispatch queue in FIFO order and execute the user-provided callback. After executing the user callback, the task pool threads try and execute any remaining jobs in the dispatch queue. The task pool tries and execute a user job as soon as it is received and if there are no threads available it will try and create one, up to the maximum number of allowed threads. The user can specificy the minimum and maximum number of threads allowed when creating the task pool. -- The user can try and cancel a job after the task has been scheduled. Cancellation is only allowed before the task enters execution. Wating on a completed task returns immediately. - -Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the task pool library on-the-go on some systems, while other systems may use an always-allocated thread pool. - The sequence diagram below illustrates the workflow described above. The application thread is able to continue executing while the task pool library processes the operation. @image html taskpool_design_typicaloperation.png width=100% + +The state diagrams for statically allocated, non-recyclable jobs with all legal transitions is presented in the diagram below. A static job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Static jobs cannot be recycled and do no need to be destroyed. Static jobs are suitable for embedding on other data structures that own them. + +@image html StaticJobStatus.png width=40% + +The state diagram and legal transitions for all recyclable jobs is presented in the diagram below. A recyclable job is dynamically allocated. Just like a static job, a recyclable job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Unlike static jobs, recyclable jobs can be recycled, or destroyed. Recycling a job effectively pushes a job to the task pool cache, where the task pool manages the lifetime of the job itself. The size of the cache is controlled via a compile time parameter. A user can get rid of a recyclable job by destroying it explicitly. Recyclable jobs should not be embedded in other data structures, but could be referenced from other data structures. + +@image html RecyclableJobStatus.png width=50% + */ /** @page taskpool_demo Demo @brief The task pool demo demonstrates usage of the task pool library. -The task pool demo demonstrates the create-schedule workflow of task pool. After creating multiple jobs, it schedules all jobs and wait on their completion, or try and cancel them. - -@image html taskpool_demo.png "task pool Demo Workflow" width=80% +The task pool demo demonstrates the create-schedule workflow of task pool. After creating multiple jobs, it schedules all jobs and wait on their completion (by synchronizing at the application level), or try and cancel them. See @subpage taskpool_demo_config for configuration settings that change the behavior of the demo. The main task pool demo file, iot_demo_taskpool.c, contains platform-independent code. See the following guides for running the task pool demo on various platforms. -- @subpage demo_posix
- @copybrief demo_posix +- @subpage demo_taskpool
+ @copybrief demo_taskpool */ /** @page taskpool_tests Tests @brief Tests written for the task pool library. -The task pool tests are divided into the following subdirectories: -- `system`: task pool system and stress tests. Stress tests may run for a long time, so they are not run unless the [-l option is passed to the test executable](@ref taskpool_tests_optionl). +The task pool tests reside in the `tests/common` directory. They are divided into the following subdirectories: +- `system`: task pool system and stress tests. Stress tests may run for a long time, so they are not run unless the `-l` option is passed to the test executable. - `unit`: task pool unit tests. These tests do not require a network connection. -See @subpage taskpool_tests_config for configuration settings that change the behavior of the tests. - -The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. See @subpage taskpool_tests_running for a guide on running the tests. -*/ - -/** -@configpage{taskpool_tests,task pool tests,Test,tests} - -
The settings below only affect the [stress](@ref iot_tests_taskpool_stress.c) tests.
- -*/ - -/** -@page taskpool_tests_running Running task pool tests -@brief Guide for running the task pool tests. - -The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. - -@pre The tests have the following system dependencies: -- POSIX threads, mutexes, and semaphores (link with `-lpthread`). - -@section taskpool_tests_building Building the tests -@brief How to build the task pool tests. - -Before building the task pool tests, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [test](@ref global_tests_config) settings, as well as settings for specific libraries and demos. - -All tests specify their configuration settings in `tests/iot_tests_config.h`. Any undefined settings will use a default value when possible. - -The task pool tests build with a POSIX Makefile. A single Makefile will build all the task pool tests in both dynamic memory and static memory only modes. See @ref IOT_STATIC_MEMORY_ONLY for more information about static memory only mode. The Makefile also builds tests against AWS IoT and a [public Mosquitto test server](https://test.mosquitto.org/). - -The Makefile for building the task pool tests is located in `tests/taskpool`. To build the tests: -@code -cd tests/task_pool -make -@endcode - -The Makefile will generate four test executables: -- `iot_tests_taskpool_aws`: Tests against AWS IoT in dynamic memory mode. - -Run `make clean` to remove all test executables. - -@section taskpool_tests_commandlineoptions Command line options -@brief Command line options of the task pool test executables. - -All task pool test executables accept the following command line options. - -@subsection taskpool_tests_optionl -l -@brief Enable long-running tests. - -By default, the task pool test executables don't run the stress tests, which may take a long time to run and require an extremely stable network connection. Passing `-l` enables the long-running tests, which will run after all of the default tests finish. +The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). */ /** @configpage{taskpool_demo,task pool demo,Demo,demos} -@section IOT_DEMO_MQTT_CLIENT_IDENTIFIER -@brief The task pool client identifier to use for the demo. - */ /** @configpage{taskpool,task pool library} -@section IOT_TASKPOOL_THREADS_STACK_MIN -@brief Set this to minimum stack depth to be enforced at runtime. - -@section IOT_TASKPOOL_THREADS_STACK_SIZE -@brief Set this to the default stack depth used by the task pool initializers. - -@section IOT_TASKPOOL_PRIORITY -@brief Set this to the default task priority used by the task pool initializers. - @section IOT_TASKPOOL_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the task pool library. @@ -179,13 +128,4 @@ Log messages from the task pool library at or below this setting will be printed @configpossible One of the @ref logging_constants_levels.
@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section IOT_TASKPOOL_TEST -@brief Set this to `1` to enable test access for the task pool library. - -This setting is used by the [task pool tests](@ref taskpool_tests) to access the internal `static` functions and variables of the task pool library. When `1`, it enables the @c \#include directives at the bottom of the [task pool library source files](@ref lib/source/common/). These @c \#include directives include the test access files (located in `tests/taskpool/access`) that interact with the library's internal symbols. Unless the task pool library is being tested, this setting should be `0`. - -@configpossible `0` (test access disabled) or `1` (test access enabled)
-@configrecommended `0`
-@configdefault `0` */ diff --git a/doc/plantuml/RecyclableJobStatus.pu b/doc/plantuml/RecyclableJobStatus.pu index 84fa443c11..eded456e2b 100644 --- a/doc/plantuml/RecyclableJobStatus.pu +++ b/doc/plantuml/RecyclableJobStatus.pu @@ -23,24 +23,24 @@ state CACHED <> { [*] --[#green]> READY : CreateRecyclableJob READY --[#blue]> SCHEDULED : Schedule READY --[#blue]> DEFERRED : ScheduleDeferred -DEFERRED --[#cyan]> SCHEDULED : -SCHEDULED --[#cyan]> COMPLETED : -COMPLETED -up[#orange]-> CACHED : RecycleJob -COMPLETED --[#orange]> UNDEFINED : DestroyRecyclableJob +DEFERRED --[#red]> SCHEDULED : +SCHEDULED --[#green]> COMPLETED : +COMPLETED -up[#blue]-> CACHED : RecycleJob +COMPLETED --[#blue]> UNDEFINED : DestroyRecyclableJob -READY -right[#red]-> CANCELED : TryCancel -DEFERRED -right[#red]-> CANCELED : TryCancel -SCHEDULED -right[#red]-> CANCELED : TryCancel +READY -right[#blue]-> CANCELED : TryCancel +DEFERRED -right[#blue]-> CANCELED : TryCancel +SCHEDULED -right[#blue]-> CANCELED : TryCancel -CANCELED --[#orange]> CACHED : RecycleJob -CANCELED --[#orange]> UNDEFINED : DestroyRecyclableJob +CANCELED --[#blue]> CACHED : RecycleJob +CANCELED --[#blue]> UNDEFINED : DestroyRecyclableJob -READY -up[#orange]-> CACHED : RecycleJob -DEFERRED -up[#orange]-> CACHED : RecycleJob -SCHEDULED -up[#orange]-> CACHED : RecycleJob +READY -up[#blue]-> CACHED : RecycleJob +DEFERRED -up[#blue]-> CACHED : RecycleJob +SCHEDULED -up[#blue]-> CACHED : RecycleJob -READY --[#orange]> UNDEFINED : DestroyRecyclableJob -DEFERRED --[#orange]> UNDEFINED : DestroyRecyclableJob -SCHEDULED --[#orange]> UNDEFINED : DestroyRecyclableJob +READY --[#blue]> UNDEFINED : DestroyRecyclableJob +DEFERRED --[#blue]> UNDEFINED : DestroyRecyclableJob +SCHEDULED --[#blue]> UNDEFINED : DestroyRecyclableJob @enduml \ No newline at end of file diff --git a/doc/plantuml/StaticJobStatus.pu b/doc/plantuml/StaticJobStatus.pu index 6325664ca0..38cfd5540d 100644 --- a/doc/plantuml/StaticJobStatus.pu +++ b/doc/plantuml/StaticJobStatus.pu @@ -16,17 +16,17 @@ state CANCELED { state UNDEFINED { } -[*] --[#green]> READY : CreateJob +[*] --[#blue]> READY : CreateJob READY --[#blue]> SCHEDULED : Schedule READY --[#blue]> DEFERRED : ScheduleDeferred -DEFERRED --[#cyan]> SCHEDULED : -SCHEDULED --[#cyan]> COMPLETED : -COMPLETED --[#green]> READY : CreateJob +DEFERRED --[#red]> SCHEDULED : +SCHEDULED --[#green]> COMPLETED : +COMPLETED --[#blue]> READY : CreateJob -READY --[#red]> CANCELED : TryCancel -DEFERRED --[#red]> CANCELED : TryCancel -SCHEDULED --[#red]> CANCELED : TryCancel +READY --[#blue]> CANCELED : TryCancel +DEFERRED --[#blue]> CANCELED : TryCancel +SCHEDULED --[#blue]> CANCELED : TryCancel -CANCELED --[#green]> READY : CreateJob +CANCELED --[#blue]> READY : CreateJob @enduml \ No newline at end of file diff --git a/doc/plantuml/images/RecyclableJobStatus.png b/doc/plantuml/images/RecyclableJobStatus.png index bc263ded664d975f585bc1e4e235f5e6a20cc354..f9adcd921f89c46316d7c0ceff6ffdb435f08b4f 100644 GIT binary patch literal 56514 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 literal 57106 zcmY&iiN?ZvF3g!nC)SIq%Z-8%NbuEp72aVGw z4JRX8J9ldnQzs}%6B`o;11A$>VncUgb0;S|UM40xYXci6=dad`Mz&wknR!4!59$^w z8czT7Jrp$1jaz1t3fOi*_`R1<|BC^IZccoh>_n*6I|>#`nspi6V%or;oh$K}+o<11 z#pR}7R@hMWsnJe%m(yC(&Q=6^!ljdbkp$0@w#+Q?XnuF?L)de4fi}kFp_VCuQ z@)>Bc$4JFTRo?j`zd8L9Fs&3G7*ZY@Xw9yJ*K9Cn-4XGlMC93W1y$fYOG}7tBzGNB zp=&j9rA0NJJbNvXU!BLdivn4>ZN8s!ZE<}5Q9T*(!EEg(r3US50E5 z*UID6o*$herX=nU-q=rp1eMHED?@)Hs70a#J?#FBQc|Jo`Vo8~P-J+FQZh7;6=NfN zOS5I>h#y|2NRG&ymw~%cC=z}2W(PNZyBVob6v0VmUapmr;K8&iEh+GaSD0gUBU-Qj z(pQ^WKTfp2rX;%XTDWvH6V;KZ*A&{VC7Pc-htTdGHBlo?_LT_rRW5x{n)Dy=8aOK} zvfUaEZ%8lUAnL{*PM)w0`qzj~_Iox8zEz%Fr*b%_g$;ypA&{F;WU_(jAG1LW1E~n8 zF14ofS3kY=Y-9@I-zrqM|KxLZjedd>^F`Kd6CGgcWb7i1gBI<(WU-E;7RV9h^;k1V zJUtxXj`dFJZ9%+UX?;hy%1U#K*GT%^f(gF8RN+~onBaD8#mf}YD^UyQi|5^#MfvzR z>4EA&3brBRFI(f^BA>A~WKJhF))xGUB16^%;1)??+JvirVvna}QN~S>zCpK)_c7&k zIkJj!a`r<2TMqYym1ZkbD8o|<-oZcAwHo*>W}`tt`9Xaa7g2FrIQ)*Hp|X>9Gmn?2 zQf4fpGn2BN*Xk00zmytPA;U#Zk{s30j)lKCkx0p2pjNLTYek!(R7s*8Nsb|gVOc41 z2NLH+^hdz>^z9q;FnPQb?2-9<_>V!P3;vNFD-YLy``>kP|3S`-c{$<5nOz%7V`Ce= zyB8R3%zik-0K6LbDA4FqzaAIhGY|XkvkK|or!mgI&pyU~pRwS7pTrRT=7tH66{U;u zyPx^2*&bS2mDR650e`IDX*$|J`y~K*5m4j0Qv5#aRq!Q9nQ-|gyL3_7YWKm2>YuX- zhJtgLUQmBI;1cg0KJ#`uXruRjIx|zsj$Zxn;Fu<_O`5&Td$ZW6=N{PVzQ>itUn=C+ zf&dI5>;irJjd3H4^d*QqVzYs%x9sld{s)S5K&zwW&}f}+ly7%C)odKZsQy3W1kMPl z?okTGbM56$6>V@)gOpad2;IZI>eu78zV9%!w6x5%z1nV!W1ez7fQESTcw^lL+xBo* z!C4$MOJ+FlO18eZu+Rj7jf6aY4Ws7+H?(^M=o{a)Q8#ue>$qLC8?$OM9d2M^!^!A@corhgmuZW^@#uQz7 z_s|ZP6pweo_I>s-9;IS>W~RgSHS+W^j$sp(E$3Z4n>ut+cUo-gh2fd|}!Y3yI&;~M0s>9rPwV>*;t zY1|(V48wJKBJXpKsi(JETulAoY&x3tG$w`y3k%!$EU{C?H zZQ^(MrtcWe*4CD);`ua02pJhU@%ia~wvEwQ+3~rD#b%!2i-+^W^%0-T;asERPOJAr zdPasl6jTn)FEuMgMMZ0CdT>=2E&K3T8xIeUfPers5fm{*7*cg`a`N(cF}ld4+2-Tz z&||;_SM*j61`wavWh^X8W|?!--M@Zipt04~)^>H}O2m0RM1hC2 zctC(!=(4Qk4Vxqghn%!#lw(X!Psg9+kwd?31|tygi21_9!Qovl$4cm zqswn}%gRu;5P*f~i?1#a&BBo%{@9T;1I<*IG!qMJ6BbHApmHWI6l=m|un}A3IXwXT z$r%R9FC5n%w{zKbwQO=v%B8xxIwB&Xw6s)~67%2D=y34wz16usQzKzVB77^5^V$+m zkET~3Q#Lqk@~@J)_NA4fZi+w(0Sw
>`-z=;%O1LOP>=2bijWT1S|Bt|-E#NPUWZ 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 diff --git a/doc/plantuml/images/StaticJobStatus.png b/doc/plantuml/images/StaticJobStatus.png index 064babb6fde27d464d25dc183a128b9627cb0d95..44c4480230adacf41f567c178b625c07df180aa9 100644 GIT binary patch literal 31124 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 oV3=Vb!vmud_!C1Q><}SXk+u7i;?lUskXU| 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 literal 74493 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 b=fqS??!mkp=%7T?mMywkMi+|CIidd#{_l9A diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index eefb39c8d8..e38c8bbc07 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -86,7 +86,7 @@ * * This function does not allocate memory to hold the task pool data structures and state, but it * may allocate memory to hold the dependent entities and data structures, e.g. the threads of the task - * pool. The system task pool handle is recoverable for later use by calling @ref taskpool_function_getsystemtaskpool or + * pool. The system task pool handle is recoverable for later use by calling @ref IotTaskPool_GetSystemTaskPool or * the shortcut @ref IOT_SYSTEM_TASKPOOL. * * @param[in] pInfo A pointer to the task pool initialization data. @@ -107,7 +107,7 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c /** * @brief Retrieves the one and only instance of a system task pool * - * This function retrieves the sytem task pool created with @ref taskpool_function_createsystemtaskpool, and it is functionally + * This function retrieves the sytem task pool created with @ref IotTaskPool_CreateSystemTaskPool, and it is functionally * equivalent to using the shortcut @ref IOT_SYSTEM_TASKPOOL. * * @return The system task pool handle. @@ -134,7 +134,7 @@ IotTaskPool_t * IotTaskPool_GetSystemTaskPool( void ); * * @param[in] pInfo A pointer to the task pool initialization data. * @param[in,out] pTaskPool A pointer to the task pool storage to be initialized. - * The pointer `pTaskPool` will hold a valid handle only if (@ref taskpool_function_create) + * The pointer `pTaskPool` will hold a valid handle only if (@ref IotTaskPool_Create) * completes succesfully. * * @return One of the following: @@ -153,13 +153,13 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, * satellite data structures. * * This function should be called to destroy one instance of a task pool previously created with a call - * to @ref taskpool_function_create or @ref taskpool_function_createsystemtaskpool. + * to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. * Calling this fuction release all underlying resources. After calling this function, any job scheduled but not yet executed * will be cancelled and destroyed. * The `pTaskPool` instance will no longer be valid after this function returns. * - * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create or - * @ref taskpool_function_createsystemtaskpool. The `pTaskPool` instance will no longer be valid after this function returns. + * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or + * @ref IotTaskPool_CreateSystemTaskPool. The `pTaskPool` instance will no longer be valid after this function returns. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -180,7 +180,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ); * function causes the task pool to shrink the number of active threads. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with - * a call to @ref taskpool_function_create. + * a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. * @param[in] maxThreads The maximum number of threads for the task pool. * * @return One of the following: @@ -257,7 +257,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP * An attempt to destroy a job that was scheduled but not yet executed or canceled, may result in a * @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. * - * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create. + * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. * @param[in] pJob A handle to a job that was create with a call to @ref IotTaskPool_CreateJob. * * @return One of the following: @@ -289,7 +289,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask * that was previously scheduled but not completed or canceled cannot be safely recycled. An attempt to do so will result * in an @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. * - * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref taskpool_function_create. + * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create. * @param[out] pJob A pointer to a job that was create with a call to @ref IotTaskPool_CreateJob. * * @return One of the following: @@ -315,15 +315,15 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, /* @[declare_taskpool_recyclejob] */ /** - * @brief This function schedules a job created with @ref taskpool_function_createjob or @ref taskpool_function_createrecyclablejob + * @brief This function schedules a job created with @ref IotTaskPool_CreateJob or @ref IotTaskPool_CreateRecyclableJob * against the task pool pointed to by `pTaskPool`. * * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool * library. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. - * a call to @ref taskpool_function_create. - * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * a call to @ref IotTaskPool_Create. + * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. * @param[in] flags Flags to be passed by the user, e.g. to identify the job as high priority by specifying #IOT_TASKPOOL_JOB_HIGH_PRIORITY. * * @return One of the following: @@ -405,15 +405,15 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /* @[declare_taskpool_schedule] */ /** - * @brief This function schedules a job created with @ref taskpool_function_createjob against the task pool + * @brief This function schedules a job created with @ref IotTaskPool_CreateJob against the task pool * pointed to by `pTaskPool` to be executed after a userdefined time interval. * * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool * library. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. - * a call to @ref taskpool_function_create. - * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref taskpool_function_createjob. + * a call to @ref IotTaskPool_Create. + * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. * @param[in] timeMs The time in milliseconds to wait before scheduling the job. * * @return One of the following: @@ -439,7 +439,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool * @brief This function retrieves the current status of a job. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with - * a call to @ref taskpool_function_create or @ref taskpool_function_createsystemtaskpool. + * a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. * @param[in] pJob The job to cancel. * @param[out] pStatus The status of the job at the time of cancellation. * @@ -466,7 +466,7 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, * or #IOT_TASKPOOL_STATUS_CANCELED will yield a #IOT_TASKPOOL_CANCEL_FAILED return result. * * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with - * a call to @ref taskpool_function_create. + * a call to @ref IotTaskPool_Create. * @param[in] pJob The job to cancel. * @param[out] pStatus The status of the job at the time of cancellation. * From d4bf70aa88686f06c57569054c8e3728dfc0008c Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Fri, 29 Mar 2019 17:07:51 -0700 Subject: [PATCH 078/844] Improvements in task pool documentation (#353) From 10d3c4ef63fe283a1df830a010488359d3618f9f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 1 Apr 2019 09:49:31 -0700 Subject: [PATCH 079/844] Enhancements to OpenSSL network implementation (#354) --- lib/source/mqtt/iot_mqtt_api.c | 20 ++ .../posix/network/iot_network_openssl.c | 308 +++++++----------- tests/mqtt/system/iot_tests_mqtt_stress.c | 43 +-- 3 files changed, 144 insertions(+), 227 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index b352724dd7..95e758cb09 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -1159,6 +1159,26 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, _EMPTY_ELSE_MARKER; } + /* Destroy a network connection owned by this MQTT connection. */ + if( ownNetworkConnection == true ) + { + networkStatus = pNetworkInfo->pNetworkInterface->destroy( pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "Failed to destroy network connection." ); + } + else + { + IotLogInfo( "Network connection destroyed on error." ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } + + if( pOperation != NULL ) { _IotMqtt_DestroyOperation( pOperation ); diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index cf778c1adc..084b31fa9b 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -104,16 +104,12 @@ */ typedef struct _networkConnection { - int socket; /**< @brief Socket associated with this connection. */ - SSL * pSslContext; /**< @brief SSL context for connection. */ - IotMutex_t mutex; /**< @brief Synchronizes the various network threads. */ + int socket; /**< @brief Socket associated with this connection. */ + SSL * pSslContext; /**< @brief SSL context for connection. */ + IotMutex_t sslMutex; /**< @brief Prevents concurrent use of the SSL context. */ - /** @brief Status of the receive thread for this connection. */ - enum - { - _NONE = 0, _ACTIVE, _TERMINATED - } receiveThreadStatus; - pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ + bool receiveThreadCreated; /**< @brief Whether a receive thread exists for this connection. */ + pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ void * pReceiveContext; /**< @brief The context for the receive callback. */ @@ -146,6 +142,7 @@ const IotNetworkInterface_t IotNetworkOpenssl = */ static void * _networkReceiveThread( void * pArgument ) { + int pollStatus = 0; struct pollfd fileDescriptor = { .events = POLLIN | POLLPRI, @@ -159,13 +156,16 @@ static void * _networkReceiveThread( void * pArgument ) fileDescriptor.fd = pNetworkConnection->socket; /* Continuously poll the network socket for events. */ - while( poll( &fileDescriptor, 1, -1 ) == 1 ) + while( true ) { - /* Prevent this thread from being cancelled while running. But if the - * connection mutex is locked, wait until it is available. */ - if( IotMutex_TryLock( &( pNetworkConnection->mutex ) ) == false ) + pollStatus = poll( &fileDescriptor, 1, -1 ); + + if( pollStatus == -1 ) { - continue; + IotLogError( "Failed to poll socket %d. errno=%d.", + pNetworkConnection->socket, + errno ); + break; } /* If an error event is detected, terminate this thread. */ @@ -180,31 +180,13 @@ static void * _networkReceiveThread( void * pArgument ) /* Invoke the callback function. But if there's no callback to invoke, * terminate this thread. */ - if( pNetworkConnection->receiveCallback != NULL ) - { - pNetworkConnection->receiveCallback( pNetworkConnection, - pNetworkConnection->pReceiveContext ); - } - else - { - break; - } - - /* Unlock the connection mutex before polling again. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); + pNetworkConnection->receiveCallback( pNetworkConnection, + pNetworkConnection->pReceiveContext ); } IotLogDebug( "Network receive thread for socket %d terminating.", pNetworkConnection->socket ); - /* Clear data about the callback. */ - pNetworkConnection->receiveThreadStatus = _TERMINATED; - pNetworkConnection->receiveCallback = NULL; - pNetworkConnection->pReceiveContext = NULL; - - /* Unlock the connection mutex before exiting. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - return NULL; } @@ -369,9 +351,8 @@ static bool _readCredentials( SSL_CTX * pSslContext, IotLogInfo( "Successfully imported root CA." ); /* Import the client certificate. */ - if( SSL_CTX_use_certificate_file( pSslContext, - pClientCertPath, - SSL_FILETYPE_PEM ) != 1 ) + if( SSL_CTX_use_certificate_chain_file( pSslContext, + pClientCertPath ) != 1 ) { IotLogError( "Failed to import client certificate at %s", pClientCertPath ); @@ -573,15 +554,18 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /*-----------------------------------------------------------*/ /** - * @brief Cleans up TLS for a connection. + * @brief Close a TLS connection. * - * @param[in] pNetworkConnection The TLS connection to clean up. + * @param[in] pNetworkConnection The TLS connection to close. */ -static void _tlsCleanup( _networkConnection_t * pNetworkConnection ) +static void _tlsClose( _networkConnection_t * pNetworkConnection ) { /* Shut down the TLS connection. */ IotLogInfo( "Shutting down TLS connection." ); + /* Lock the SSL context mutex. */ + IotMutex_Lock( &( pNetworkConnection->sslMutex ) ); + /* SSL shutdown should be called twice: once to send "close notify" and once * more to receive the peer's "close notify". */ if( SSL_shutdown( pNetworkConnection->pSslContext ) == 0 ) @@ -600,82 +584,10 @@ static void _tlsCleanup( _networkConnection_t * pNetworkConnection ) } } - IotLogInfo( "TLS connection terminated." ); - - /* Free the memory used by the TLS connection. */ - SSL_free( pNetworkConnection->pSslContext ); - pNetworkConnection->pSslContext = NULL; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Cleans up the receive thread for a connection. - * - * @param[in] pNetworkConnection The network connection with the receive thread - * to clean up. - * - * This function must be called with the network connection mutex locked. - * - * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_SYSTEM_ERROR. - */ -static IotNetworkError_t _cancelReceiveThread( _networkConnection_t * pNetworkConnection ) -{ - _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - int posixError = 0; - - /* Do nothing if this thread is attempting to cancel itself. */ - if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) - { - if( pNetworkConnection->receiveThread == pthread_self() ) - { - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SUCCESS ); - } - } - - if( pNetworkConnection->receiveThreadStatus != _NONE ) - { - /* Send a cancellation request to the receive thread if active. */ - if( pNetworkConnection->receiveThreadStatus == _ACTIVE ) - { - posixError = pthread_cancel( pNetworkConnection->receiveThread ); - - if( ( posixError != 0 ) && ( posixError != ESRCH ) ) - { - IotLogWarn( "Failed to send cancellation request to socket %d receive " - "thread. errno=%d.", - pNetworkConnection->socket, - posixError ); - - status = IOT_NETWORK_SYSTEM_ERROR; - } - else - { - pNetworkConnection->receiveThreadStatus = _TERMINATED; - } - } - - if( pNetworkConnection->receiveThreadStatus == _TERMINATED ) - { - /* Join the receive thread. */ - posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + /* Unlock the SSL context mutex. */ + IotMutex_Unlock( &( pNetworkConnection->sslMutex ) ); - if( posixError != 0 ) - { - IotLogWarn( "Failed to join network receive thread for socket %d. errno=%d.", - pNetworkConnection->socket, - posixError ); - } - - /* Clear data about the receive thread and callback. */ - pNetworkConnection->receiveThreadStatus = _NONE; - ( void ) memset( &( pNetworkConnection->receiveThread ), 0x00, sizeof( pthread_t ) ); - pNetworkConnection->receiveCallback = NULL; - pNetworkConnection->pReceiveContext = NULL; - } - } - - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IotLogInfo( "TLS connection closed." ); } /*-----------------------------------------------------------*/ @@ -735,7 +647,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, { _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int tcpSocket = -1; - bool networkMutexCreated = false; + bool sslMutexCreated = false; _networkConnection_t * pNewNetworkConnection = NULL; /* Cast function parameters to correct types. */ @@ -756,16 +668,6 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, /* Clear connection data. */ ( void ) memset( pNewNetworkConnection, 0x00, sizeof( _networkConnection_t ) ); - /* Create the network connection mutex. */ - networkMutexCreated = IotMutex_Create( &( pNewNetworkConnection->mutex ), true ); - - if( networkMutexCreated == false ) - { - IotLogError( "Failed to create network connection mutex." ); - - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ tcpSocket = _dnsLookup( pServerInfo ); @@ -786,6 +688,16 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, { IotLogInfo( "Setting up TLS." ); + /* Create the mutex that protects the SSL context. */ + sslMutexCreated = IotMutex_Create( &( pNewNetworkConnection->sslMutex ), true ); + + if( sslMutexCreated == false ) + { + IotLogError( "Failed to create network send mutex." ); + + _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + } + status = _tlsSetup( pNewNetworkConnection, pServerInfo->pHostName, pOpensslCredentials ); @@ -801,9 +713,9 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, ( void ) close( tcpSocket ); } - if( networkMutexCreated == true ) + if( sslMutexCreated == true ) { - IotMutex_Destroy( &( pNewNetworkConnection->mutex ) ); + IotMutex_Destroy( &( pNewNetworkConnection->sslMutex ) ); } if( pNewNetworkConnection != NULL ) @@ -832,43 +744,31 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - /* Lock the connection mutex before changing the callback and its parameter. */ - IotMutex_Lock( &( pNetworkConnection->mutex ) ); - - /* Cancel any active receive thread for this connection. */ - status = _cancelReceiveThread( pNetworkConnection ); - - if( status == IOT_NETWORK_SUCCESS ) + /* Create a new receive thread if a callback is given. */ + if( receiveCallback != NULL ) { - /* Create a new receive thread if a callback is given. */ - if( receiveCallback != NULL ) - { - /* Update the callback and parameter. */ - pNetworkConnection->receiveCallback = receiveCallback; - pNetworkConnection->pReceiveContext = pContext; + /* Update the callback and parameter. */ + pNetworkConnection->receiveCallback = receiveCallback; + pNetworkConnection->pReceiveContext = pContext; - posixError = pthread_create( &pNetworkConnection->receiveThread, - NULL, - _networkReceiveThread, - pNetworkConnection ); + posixError = pthread_create( &pNetworkConnection->receiveThread, + NULL, + _networkReceiveThread, + pNetworkConnection ); - if( posixError != 0 ) - { - IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pNetworkConnection->socket, - posixError ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - else - { - pNetworkConnection->receiveThreadStatus = _ACTIVE; - } + if( posixError != 0 ) + { + IotLogError( "Failed to create socket %d network receive thread. errno=%d.", + pNetworkConnection->socket, + posixError ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pNetworkConnection->receiveThreadCreated = true; } } - /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - return status; } @@ -895,9 +795,12 @@ size_t IotNetworkOpenssl_Send( void * pConnection, /* Set the file descriptor for poll. */ fileDescriptor.fd = pNetworkConnection->socket; - /* Lock the connection mutex to prevent the connection from being closed - * while sending. */ - IotMutex_Lock( &( pNetworkConnection->mutex ) ); + /* The SSL mutex must be locked to protect an SSL context from concurrent + * access. */ + if( pNetworkConnection->pSslContext != NULL ) + { + IotMutex_Lock( &( pNetworkConnection->sslMutex ) ); + } /* Check that it's possible to send data right now. */ if( poll( &fileDescriptor, 1, 0 ) == 1 ) @@ -923,8 +826,11 @@ size_t IotNetworkOpenssl_Send( void * pConnection, IotLogError( "Not possible to send on socket %d.", pNetworkConnection->socket ); } - /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); + /* Unlock the SSL context mutex. */ + if( pNetworkConnection->pSslContext != NULL ) + { + IotMutex_Unlock( &( pNetworkConnection->sslMutex ) ); + } /* Log the number of bytes sent. */ if( bytesSent <= 0 ) @@ -964,9 +870,12 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, ( unsigned long ) bytesRequested, pNetworkConnection->socket ); - /* Lock the connection mutex to prevent the connection from being closed - * while receiving. */ - IotMutex_Lock( &( pNetworkConnection->mutex ) ); + /* The SSL mutex must be locked to protect an SSL context from concurrent + * access. */ + if( pNetworkConnection->pSslContext != NULL ) + { + IotMutex_Lock( &( pNetworkConnection->sslMutex ) ); + } /* Loop until all bytes are received. */ while( bytesRemaining > 0 ) @@ -1002,8 +911,11 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, } } - /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); + /* Unlock the SSL context mutex. */ + if( pNetworkConnection->pSslContext != NULL ) + { + IotMutex_Unlock( &( pNetworkConnection->sslMutex ) ); + } /* Check how many bytes were read. */ if( bytesRead < bytesRequested ) @@ -1028,35 +940,25 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - /* Lock the connection mutex to block the receive thread. */ - IotMutex_Lock( &( pNetworkConnection->mutex ) ); - - /* Cancel any receive thread for this connection. The return value is not - * checked because the socket will be closed anyways. */ - ( void ) _cancelReceiveThread( pNetworkConnection ); - /* If TLS was set up for this connection, clean it up. */ if( pNetworkConnection->pSslContext != NULL ) { - _tlsCleanup( pNetworkConnection ); + _tlsClose( pNetworkConnection ); } - /* Close the connection. */ - if( close( pNetworkConnection->socket ) != 0 ) + /* Shut down the connection. */ + if( shutdown( pNetworkConnection->socket, SHUT_RDWR ) != 0 ) { - IotLogWarn( "Could not close socket %d. errno=%d.", + IotLogWarn( "Could not shut down socket %d. errno=%d.", pNetworkConnection->socket, errno ); } else { - IotLogInfo( "Connection (socket %d) closed.", + IotLogInfo( "Connection (socket %d) shut down.", pNetworkConnection->socket ); } - /* Unlock the connection mutex. */ - IotMutex_Unlock( &( pNetworkConnection->mutex ) ); - return IOT_NETWORK_SUCCESS; } @@ -1064,11 +966,45 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) { + int posixError = 0; + /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - /* Clean up the connection by destroying its mutex. */ - IotMutex_Destroy( &( pNetworkConnection->mutex ) ); + /* Wait for the receive thread to terminate. */ + if( pNetworkConnection->receiveThreadCreated == true ) + { + posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + + if( posixError != 0 ) + { + IotLogWarn( "Failed to join receive thread for socket %d. errno=%d.", + pNetworkConnection->socket, + posixError ); + } + } + + /* Free the memory used by the TLS connection, if any. */ + if( pNetworkConnection->pSslContext != NULL ) + { + SSL_free( pNetworkConnection->pSslContext ); + + /* Destroy the SSL context mutex. */ + IotMutex_Destroy( &( pNetworkConnection->sslMutex ) ); + } + + /* Close the socket file descriptor. */ + if( close( pNetworkConnection->socket ) != 0 ) + { + IotLogWarn( "Could not close socket %d. errno=%d.", + pNetworkConnection->socket, + errno ); + } + else + { + IotLogInfo( "(Socket %d) Socket closed.", + pNetworkConnection->socket ); + } /* Free the connection. */ IotNetwork_Free( pNetworkConnection ); diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 6c2f67057e..5d77f61946 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -230,31 +230,6 @@ static IotSemaphore_t receivedPublishCounter; */ static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; -/** - * @brief Tracks whether the PINGREQ serializer override was called. - */ -static bool _pingreqOverrideCalled = false; - -/*-----------------------------------------------------------*/ - -/* Declaration of MQTT serializer function. The internal MQTT header cannot be - * included here. */ -extern IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PINGREQ. - */ -static IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ) -{ - _pingreqOverrideCalled = true; - - return _IotMqtt_SerializePingreq( pPingreqPacket, pPacketSize ); -} - /*-----------------------------------------------------------*/ /** @@ -430,7 +405,6 @@ TEST_GROUP( MQTT_Stress ); TEST_SETUP( MQTT_Stress ) { int32_t i = 0; - IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; @@ -452,21 +426,14 @@ TEST_SETUP( MQTT_Stress ) TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); } - /* Clear the PINGREQ override flag. */ - _pingreqOverrideCalled = false; - - /* Empty log message to log a new line. */ - IotLogInfo( "Stress tests starting." ); + /* Print a new line. */ + UNITY_PRINT_EOL(); /* Create the publish counter semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &receivedPublishCounter, 0, _MAX_RECEIVED_PUBLISH ) ); - /* Set the serializer overrides. */ - serializer.serialize.pingreq = _serializePingreq; - _networkInfo.pMqttSerializer = &serializer; - /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER @@ -578,9 +545,6 @@ TEST( MQTT_Stress, KeepAlive ) /* Send a PUBLISH to verify that the connection is still usable. */ IotLogInfo( "KeepAlive test checking MQTT connection." ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _checkConnection() ); - - /* Check that the PINGREQ override was used. */ - TEST_ASSERT_EQUAL( true, _pingreqOverrideCalled ); } /*-----------------------------------------------------------*/ @@ -626,9 +590,6 @@ TEST( MQTT_Stress, BlockingCallback ) } IotSemaphore_Destroy( &waitSem ); - - /* Check that the PINGREQ override was used. */ - TEST_ASSERT_EQUAL( true, _pingreqOverrideCalled ); } /*-----------------------------------------------------------*/ From 7549de50b600864c0a6114e1f30b550edf05ef0a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 1 Apr 2019 10:18:58 -0700 Subject: [PATCH 080/844] Allow MQTT error count disabling in Shadow API tests (#355) --- tests/shadow/unit/aws_iot_tests_shadow_api.c | 50 +++++++++----------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 48d6ec8016..22d2d7d835 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -67,6 +67,23 @@ #error "Shadow API unit tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." #endif +/** + * @brief Whether to check the number of MQTT library errors in the malloc + * failure tests. + * + * Should only be checked if malloc overrides are available and not testing for + * code coverage. In static memory mode, there should be no MQTT library errors. + */ +#if ( IOT_TEST_COVERAGE == 1 ) || ( IOT_TEST_NO_MALLOC_OVERRIDES == 1 ) + #define CHECK_MQTT_ERROR_COUNT( expected, actual ) +#else + #if ( IOT_STATIC_MEMORY_ONLY == 1 ) + #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( 0, actual ) + #else + #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( expected, actual ) + #endif +#endif + /*-----------------------------------------------------------*/ /** @@ -699,15 +716,8 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) } /* Allow 2 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, UNSUBSCRIBE). These allocation errors do - * not happen in static memory mode. Don't perform this check when running code - * coverage, as the code coverage logging function interferes with the malloc - * failure count. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); - #elif IOT_TEST_COVERAGE != 1 - TEST_ASSERT_EQUAL_INT32( 2, mqttErrorCount ); - #endif + * for incoming packets (SUBSCRIBE, UNSUBSCRIBE). */ + CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); } /*-----------------------------------------------------------*/ @@ -772,15 +782,8 @@ TEST( Shadow_Unit_API, GetMallocFail ) } /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). These allocation - * errors do not happen in static memory mode. Don't perform this check when - * running code coverage, as the code coverage logging function interferes with - * the malloc failure count.*/ - #if IOT_STATIC_MEMORY_ONLY == 1 - TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); - #elif IOT_TEST_COVERAGE != 1 - TEST_ASSERT_EQUAL_INT32( 3, mqttErrorCount ); - #endif + * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). */ + CHECK_MQTT_ERROR_COUNT( 3, mqttErrorCount ); } /*-----------------------------------------------------------*/ @@ -840,15 +843,8 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) } /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). These allocation - * errors do not happen in static memory mode. Don't perform this check when - * running code coverage, as the code coverage logging function interferes with - * the malloc failure count.*/ - #if IOT_STATIC_MEMORY_ONLY == 1 - TEST_ASSERT_EQUAL_INT32( 0, mqttErrorCount ); - #elif IOT_TEST_COVERAGE != 1 - TEST_ASSERT_EQUAL_INT32( 3, mqttErrorCount ); - #endif + * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). */ + CHECK_MQTT_ERROR_COUNT( 3, mqttErrorCount ); } /*-----------------------------------------------------------*/ From 326feffbddcc4eb38da34799b5644e5f43402fe8 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 1 Apr 2019 11:10:06 -0700 Subject: [PATCH 081/844] Add foreach to linear containers (#356) --- lib/include/iot_linear_containers.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 070b793684..f0cbdb15c8 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -118,6 +118,19 @@ typedef IotLink_t IotQueue_t; #define IotLink_Container( type, pLink, linkName ) \ ( ( type * ) ( void * ) ( ( ( uint8_t * ) ( pLink ) ) - offsetof( type, linkName ) ) ) +/** + * @brief Iterates through all elements of a linear container. + * + * Container elements must not be freed or removed while iterating. + * + * @param[in] pStart The first element to iterate from. + * @param[out] pLink Pointer to a container element. + */ +#define IotContainers_ForEach( pStart, pLink ) \ + for( ( pLink ) = ( pStart )->pNext; \ + ( pLink ) != ( pStart ); \ + ( pLink ) = ( pLink )->pNext ) + /** * @functionspage{linear_containers,linear containers library} * - @functionname{linear_containers_function_link_islinked} From a5eaab1a5f18211a0de5b689cb72d4c8408e607f Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Mon, 1 Apr 2019 11:49:16 -0700 Subject: [PATCH 082/844] Enabled ability for task pool to shrink. Trimmed caching threshold. Enhanced docs. (#357) * Added ability to shrink number of workers in task pool and fixed documentation. * Changes config value for cached task pool jobs from 32 to 8 (176 bytes of RAM). --- doc/config/common | 2 +- doc/lib/taskpool.txt | 24 +++++++-- lib/include/iot_taskpool.h | 28 ++++++++-- lib/include/private/iot_taskpool_internal.h | 11 ---- lib/include/types/iot_taskpool_types.h | 16 +++--- lib/source/common/iot_taskpool.c | 58 +++++++++++++++------ 6 files changed, 96 insertions(+), 43 deletions(-) diff --git a/doc/config/common b/doc/config/common index 0053df88d8..18ce9bbf09 100644 --- a/doc/config/common +++ b/doc/config/common @@ -71,7 +71,7 @@ EXCLUDE_SYMBOLS += "TEST_*_run" ALIASES += dependencies{2}="@section \1_dependencies Dependencies^^@brief Dependencies of the \2.^^^^" # Alias for starting a configuration settings page. -ALIASES += describeconfig="Configuration settings are C proprocessor constants. They can be set with a @c #`define` in an @ref IOT_CONFIG_FILE or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." +ALIASES += describeconfig="Configuration settings are C pre-processor constants. They can be set with a @c #`define` in an @ref IOT_CONFIG_FILE or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." ALIASES += configpage{2}="@page \1_config Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^@par configpagemarker" ALIASES += configpage{4}="@page \1_config \3 Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^^^The settings on this page only affect the [\2](@ref \1). In addition to the settings on this page, them \2 will also be affected by [settings that affect all \4](@ref global_\4_config).^^@par configpagemarker" ALIASES += globalconfigpage{3}="@page global_\1_config Global \2 Configuration^^^^@describeconfig^^@brief Configuration settings that affect all \3.^^@par configpagemarker" diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index 4ad6115e2a..fc9e1584b9 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -11,12 +11,12 @@ Features of this library include: - Scalable performance and footprint. The [configuration settings](@ref taskpool_config) allow this library to be tailored to a system's resources. - Customizable caching for low memory overhead when creating jobs dynamically. -This library uses a user-specified set of threads to process jobs speficied by the user as a callback and a context. +This library uses a user-specified set of threads to process jobs specified by the user as a callback and a context. Overall, the task pool hinges on two main data structures: the task pool Job (IotTaskPoolJob_t) and the task pool itself (IotTaskPool_t). A task pool job carries the information about the user callback and context, one flag to track the status and a link structure for moving the job in and out of the dispatch queue and cache. User can create two types of jobs: static and recyclable. Static jobs are intended for users that know exactly how many jobs they will schedule (e.g. see Defender scenario above) or for embedding in other data structures. Static jobs need no destruction, and creation simply sets the user callback and context. Recyclable jobs are intended for scenario where user cannot know ahead of time how many jobs she will need. Recyclable jobs are dynamically allocated, and can be either destroyed after use or recycled. If jobs are recycled they are maintained in a cache (IotTaskPoolCache_t) owned by the task pool itself, and re-used when user wants to create more recyclable jobs. The task pool cache has a compile time limit, and can be pre-populated with recyclable jobs by simply creating recyclable jobs and recycling them, in an effort to limit memory allocations at run-time. This is handy for scenarios where user is aware of the steady state requirements for his application. User jobs are queued through a non-blocking call and processed asynchronously in the order they are received. -- [Task pool API functions](@ref taskpool_functions) Provides a set of functions to enqueue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully enqueuing an operation. +- [Task pool API functions](@ref taskpool_functions) Provides a set of functions to queue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully queuing an operation. - Worker threads in the task pool are woken up when operations arrive in the dispatch queue. These threads remove operations from the dispatch queue in FIFO order and execute the user-provided callback. After executing the user callback, the task pool threads try and execute any remaining jobs in the dispatch queue. The task pool tries and execute a user job as soon as it is received and if there are no threads available it will try and create one, up to the maximum number of allowed threads. The user can specificy the minimum and maximum number of threads allowed when creating the task pool. -- The user can try and cancel a job after the task has been scheduled. Cancellation is only allowed before the task enters execution. Wating on a completed task returns immediately. +- The user can try and cancel a job after the task has been scheduled. Cancellation is only allowed before the task enters execution. Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the task pool library on-the-go on some systems, while other systems may use an always-allocated thread pool. @@ -112,6 +112,24 @@ The current task pool tests use the [Unity test framework](http://www.throwthesw /** @configpage{taskpool,task pool library} +@section IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS +@brief Set this to the desired wait timeout in milliseconds for a worker in the task pool to wait for an incoming job. + +If a worker in the task pool wakes up because of a timeout, then the worker will terminate if it exceeds the desired minimum thread quota, which the user can configure via @ref IotTaskPoolInfo_t.minThreads. + +@configdefault `1 minute` + +@section IOT_TASKPOOL_JOBS_RECYCLE_LIMIT +@brief Set this to the number of recyclable tasks for the task pool to cache. + +Caching dynamically allocated tasks (recyclable tasks) helps the application to limit the number of allocations at runtime. +Caching recyclable tasks may help making the application more responsive and predictable, by removing a potential +for memory allocation failures, but it may also have negative repercussions on the amount of memory available at any given time. +It is up to the application developer to strike the correct balance these competing needs. +The task pool will cache when the application calling @ref IotTaskPool_RecycleJob. Any recycled tasks in excess of @ref IOT_TASKPOOL_JOBS_RECYCLE_LIMIT will be destroyed and its memory will be release. + +@configdefault `8` + @section IOT_TASKPOOL_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the task pool library. diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index e38c8bbc07..5e2ab8eba8 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -330,6 +330,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * - #IOT_TASKPOOL_SUCCESS * - #IOT_TASKPOOL_BAD_PARAMETER * - #IOT_TASKPOOL_ILLEGAL_OPERATION + * - #IOT_TASKPOOL_NO_MEMORY * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * * @@ -375,15 +376,16 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * // Statically allocate one job, schedule it. * IotTaskPool_CreateJob( &ExecutionCb, &userContext, &job ); * - * IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( pTaskPool, &job ); + * IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( pTaskPool, &job, 0 ); * * switch ( errorSchedule ) * { * case IOT_TASKPOOL_SUCCESS: * break; - * case IOT_TASKPOOL_BAD_PARAMETER: // Invalid parameters, such as a NULL handle, can trigger this condition. - * case IOT_TASKPOOL_ILLEGAL_OPERATION: // Scheduling a job that was previously scheduled or destroyed could trigger this condition. - * case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: // Scheduling a job after trying to destroy the task pool could trigger this operation. + * case IOT_TASKPOOL_BAD_PARAMETER: // Invalid parameters, such as a NULL handle, can trigger this error. + * case IOT_TASKPOOL_ILLEGAL_OPERATION: // Scheduling a job that was previously scheduled or destroyed could trigger this error. + * case IOT_TASKPOOL_NO_MEMORY: // Scheduling a with flag #IOT_TASKPOOL_JOB_HIGH_PRIORITY could trigger this error. + * case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: // Scheduling a job after trying to destroy the task pool could trigger this error. * // ASSERT * break; * default: @@ -406,7 +408,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /** * @brief This function schedules a job created with @ref IotTaskPool_CreateJob against the task pool - * pointed to by `pTaskPool` to be executed after a userdefined time interval. + * pointed to by `pTaskPool` to be executed after a user-defined time interval. * * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool * library. @@ -507,4 +509,20 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, const char * IotTaskPool_strerror( IotTaskPoolError_t status ); /* @[declare_taskpool_strerror] */ +/** + * @brief The maximum number of jobs to cache. + */ +#ifndef IOT_TASKPOOL_JOBS_RECYCLE_LIMIT + #define IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ( 8UL ) +#endif + +/** + * @brief The maximum timeout i milliseconds to wait for a job to be scheduled before waking up a worker thread. + * A worker thread that wakes up as a result of a timeout may exit to allow the task pool to fold back to its + * minimum number of threads. + */ +#ifndef IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS + #define IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS ( 60 * 1000UL ) +#endif + #endif /* ifndef IOT_TASKPOOL_H_ */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index 0672958b18..e43131991c 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -193,17 +193,6 @@ #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef IOT_TASKPOOL_JOBS_RECYCLE_LIMIT - #define IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ( 32UL ) -#endif -/** @endcond */ - /* ---------------------------------------------------------------------------------------------- */ /** diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index cc0687219a..d572ea48b7 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -258,7 +258,8 @@ typedef struct IotTaskPoolInfo * @ingroup taskpool_datatypes_types * @brief Task pool jobs cache. * - * @warning This is a system-level data type that should not be modified. + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. * */ typedef struct IotTaskPoolCache @@ -273,7 +274,8 @@ typedef struct IotTaskPoolCache * @brief The task pool data structure keeps track of the internal state and the signals for the dispatcher threads. * The task pool is a thread safe data structure. * - * @warning This is a system-level data type that should not be modified. + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. * */ typedef struct IotTaskPool @@ -297,7 +299,8 @@ typedef struct IotTaskPool * @ingroup taskpool_datatypes_types * @brief The job data structure keeps track of the user callback and context, as well as the status of the job. * - * @warning This is a system-level data type that should not be modified. + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. * */ typedef struct IotTaskPoolJob @@ -352,17 +355,18 @@ typedef struct IotTaskPoolJob /* @[define_taskpool_initializers] */ /** - * @brief Schedules a job to execute immediately. + * @brief Flag for scheduling a job to execute immediately, even if the maximum number of threads in the + * task pool was reached already. * * @warning This flag may cause the task pool to create a worker to serve the job immediately, and - * therefore using this flag may incur in additinal memory usage. + * therefore using this flag may incur in additional memory usage and potentially fail scheduling the job. */ #define IOT_TASKPOOL_JOB_HIGH_PRIORITY ( ( uint32_t ) 0x00000001 ) /** * @brief Allows the use of the handle to the system task pool. * - * @warning The task pool handle is not valid unless @ref taskpool_function_createsystemtaskpool is + * @warning The task pool handle is not valid unless @ref IotTaskPool_CreateSystemTaskPool is * called before the handle is used. */ #define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index b317001914..f95db764ff 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -1034,11 +1034,14 @@ static void _taskPoolWorker( void * pUserContext ) */ do { - IotLink_t * pFirst = NULL; + bool jobAvailable; + IotLink_t * pFirst; IotTaskPoolJob_t * pJob = NULL; - /* Wait on incoming notifications... */ - IotSemaphore_Wait( &pTaskPool->dispatchSignal ); + /* Wait on incoming notifications. If waiting on the semaphore return with timeout, then + * it means that this thread should consider shutting down for the task pool to fold back + * to its minimum number of threads. */ + jobAvailable = IotSemaphore_TimedWait( &pTaskPool->dispatchSignal, IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS ); /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified, * or before waiting for incoming notifications. @@ -1048,7 +1051,7 @@ static void _taskPoolWorker( void * pUserContext ) /* If the exit condition is verified, update the number of active threads and exit the loop. */ if( _IsShutdownStarted( pTaskPool ) ) { - IotLogDebug( "Worker thread exiting because exit condition was set." ); + IotLogDebug( "Worker thread exiting because shutdown condition was set." ); /* Decrease the number of active threads. */ pTaskPool->activeThreads--; @@ -1062,12 +1065,13 @@ static void _taskPoolWorker( void * pUserContext ) break; } - /* Check if this thread needs to exit but let is run once, so we can support - * the case for scheduling 'high prioroty' jobs that causes exceeding the - * max threads quota for the purpose of executing the high-piority task. */ + /* Check if this thread needs to exit because 'max threads' quota was exceeded. + * In that case, let is run once, so we can support the case for scheduling 'high priority' + * jobs that causes exceeding the max threads quota for the purpose of executing + * the high-piority task. */ if( pTaskPool->activeThreads > pTaskPool->maxThreads ) { - IotLogDebug( "Worker thread exiting because maximum quota was exceeded." ); + IotLogDebug( "Worker thread will exit because maximum quota was exceeded." ); /* Decrease the number of active threads pro-actively. */ pTaskPool->activeThreads--; @@ -1075,19 +1079,39 @@ static void _taskPoolWorker( void * pUserContext ) /* Mark this thread as dead. */ running = false; } + /* Check if this thread needs to exit because the worker woke up after a timeout. */ + else if( jobAvailable == false ) + { + /* If there was a timeout, shrink back the task pool to the minimum nunber of threads. */ + if( pTaskPool->activeThreads > pTaskPool->minThreads ) + { + /* After waking up from a timeout, the thread will try and pick up a new job, but . */ + IotLogDebug( "Worker will exit because task pool is shrinking." ); + + /* Decrease the number of active threads pro-actively. */ + pTaskPool->activeThreads--; - /* Dequeue the first job in FIFO order. */ - pFirst = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + /* Mark this thread as dead. */ + running = false; + } + } - /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */ - if( pFirst != NULL ) + /* Only look for a job if waiting did not timed out. */ + if( jobAvailable == true ) { - /* Extract the job from its link. */ - pJob = IotLink_Container( IotTaskPoolJob_t, pFirst, link ); + /* Dequeue the first job in FIFO order. */ + pFirst = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); - /* Update status to 'executing'. */ - pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; - userCallback = pJob->userCallback; + /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */ + if( pFirst != NULL ) + { + /* Extract the job from its link. */ + pJob = IotLink_Container( IotTaskPoolJob_t, pFirst, link ); + + /* Update status to 'executing'. */ + pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; + userCallback = pJob->userCallback; + } } } TASKPOOL_EXIT_CRITICAL(); From 21f5b4494307b3846b55b2893cec8d39c1e63779 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Tue, 2 Apr 2019 15:52:30 -0700 Subject: [PATCH 083/844] Trim cached jobs amount for task pool and improved docs. (#359) * Added ability to shrink number of workers in task pool and fixed documentation. * Changes config value for cached task pool jobs from 32 to 8 (176 bytes of RAM). * Added structs and function pointers types to Doxygen for task pool docs. --- doc/config/common | 6 ++++++ doc/lib/taskpool.txt | 8 ++++++++ lib/include/types/iot_taskpool_types.h | 16 ++++------------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/doc/config/common b/doc/config/common index 18ce9bbf09..a8f9bca7c0 100644 --- a/doc/config/common +++ b/doc/config/common @@ -101,6 +101,12 @@ ALIASES += handles{2}="@defgroup \1_datatypes_handles Handles^^@brief Opaque han # Alias for starting an enum group. ALIASES += enums{2}="@defgroup \1_datatypes_enums Enumerated types^^@brief Enumerated types of the \2." +# Alias for starting a function pointers group. +ALIASES += functionpointers{2}="@defgroup \1_datatypes_functionpointers Function pointers types^^@brief Function pointers types of the \2."" + +# Alias for starting a structs group. +ALIASES += structs{2}="@defgroup \1_datatypes_structs Structured types^^@brief Structured types of the \2." + # Alias for starting a parameter structures group. ALIASES += paramstructs{2}="@defgroup \1_datatypes_paramstructs Parameter structures^^@brief Structures passed as parameters to [\2 functions](@ref \1_functions)^^^^These structures are passed as parameters to library functions. Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member." diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index fc9e1584b9..62188e22ed 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -147,3 +147,11 @@ Log messages from the task pool library at or below this setting will be printed @configpossible One of the @ref logging_constants_levels.
@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. */ + +/** +@enums{taskpool,task pool} +@paramstructs{taskpool,task pool} +@functionpointers{taskpool,task pool} +@structs{taskpool,task pool} +*/ + diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index d572ea48b7..e578c92011 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -44,10 +44,6 @@ /*-------------------------- Task pool enumerated types --------------------------*/ -/** - * @enums{taskpool,Task pool library} - */ - /** * @ingroup taskpool_datatypes_enums * @brief Return codes of [task pool functions](@ref taskpool_functions). @@ -208,10 +204,6 @@ struct IotTaskPoolJob; /*------------------------- Task pool parameter structs --------------------------*/ -/** - * @paramstructs{taskpool,task pool} - */ - /** * @ingroup taskpool_datatypes_functionpointers * @brief Callback type for a user callback. @@ -255,7 +247,7 @@ typedef struct IotTaskPoolInfo /*------------------------- Task pool handles structs --------------------------*/ /** - * @ingroup taskpool_datatypes_types + * @ingroup taskpool_datatypes_structs * @brief Task pool jobs cache. * * @warning This is a system-level data type that should not be modified or used directly in any application. @@ -270,7 +262,7 @@ typedef struct IotTaskPoolCache /** - * @ingroup taskpool_datatypes_types + * @ingroup taskpool_datatypes_structs * @brief The task pool data structure keeps track of the internal state and the signals for the dispatcher threads. * The task pool is a thread safe data structure. * @@ -296,7 +288,7 @@ typedef struct IotTaskPool } IotTaskPool_t; /** - * @ingroup taskpool_datatypes_types + * @ingroup taskpool_datatypes_structs * @brief The job data structure keeps track of the user callback and context, as well as the status of the job. * * @warning This is a system-level data type that should not be modified or used directly in any application. @@ -317,7 +309,7 @@ typedef struct IotTaskPoolJob /** * @constantspage{taskpool,task pool library} * - * @section taskpool_constants_initializers task pool Initializers + * @section taskpool_constants_initializers Task pool Initializers * @brief Provides default values for initializing the data types of the task pool library. * * @snippet this define_taskpool_initializers From af8e6139d71ac1f190118db7c96e85bb1825a6cf Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 3 Apr 2019 15:38:12 -0700 Subject: [PATCH 084/844] Resolve warnings in documentation. (#360) --- doc/config/common | 2 +- doc/config/layout_main.xml | 2 + doc/lib/taskpool.txt | 37 +++---------- lib/include/aws_iot_defender.h | 55 +++++++++---------- lib/include/private/iot_static_memory.h | 48 ++++++---------- lib/include/types/aws_iot_shadow_types.h | 2 +- .../iot_static_memory_serializer.c | 7 ++- tests/mqtt/system/iot_tests_mqtt_stress.c | 2 +- 8 files changed, 62 insertions(+), 93 deletions(-) diff --git a/doc/config/common b/doc/config/common index a8f9bca7c0..52cf8e583f 100644 --- a/doc/config/common +++ b/doc/config/common @@ -102,7 +102,7 @@ ALIASES += handles{2}="@defgroup \1_datatypes_handles Handles^^@brief Opaque han ALIASES += enums{2}="@defgroup \1_datatypes_enums Enumerated types^^@brief Enumerated types of the \2." # Alias for starting a function pointers group. -ALIASES += functionpointers{2}="@defgroup \1_datatypes_functionpointers Function pointers types^^@brief Function pointers types of the \2."" +ALIASES += functionpointers{2}="@defgroup \1_datatypes_functionpointers Function pointers types^^@brief Function pointers types of the \2." # Alias for starting a structs group. ALIASES += structs{2}="@defgroup \1_datatypes_structs Structured types^^@brief Structured types of the \2." diff --git a/doc/config/layout_main.xml b/doc/config/layout_main.xml index a5090fcaa0..e8ce5edbbe 100644 --- a/doc/config/layout_main.xml +++ b/doc/config/layout_main.xml @@ -9,10 +9,12 @@ + + diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index 62188e22ed..52d34a7971 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -11,7 +11,7 @@ Features of this library include: - Scalable performance and footprint. The [configuration settings](@ref taskpool_config) allow this library to be tailored to a system's resources. - Customizable caching for low memory overhead when creating jobs dynamically. -This library uses a user-specified set of threads to process jobs specified by the user as a callback and a context. +This library uses a user-specified set of threads to process jobs specified by the user as a callback and a context. Overall, the task pool hinges on two main data structures: the task pool Job (IotTaskPoolJob_t) and the task pool itself (IotTaskPool_t). A task pool job carries the information about the user callback and context, one flag to track the status and a link structure for moving the job in and out of the dispatch queue and cache. User can create two types of jobs: static and recyclable. Static jobs are intended for users that know exactly how many jobs they will schedule (e.g. see Defender scenario above) or for embedding in other data structures. Static jobs need no destruction, and creation simply sets the user callback and context. Recyclable jobs are intended for scenario where user cannot know ahead of time how many jobs she will need. Recyclable jobs are dynamically allocated, and can be either destroyed after use or recycled. If jobs are recycled they are maintained in a cache (IotTaskPoolCache_t) owned by the task pool itself, and re-used when user wants to create more recyclable jobs. The task pool cache has a compile time limit, and can be pre-populated with recyclable jobs by simply creating recyclable jobs and recycling them, in an effort to limit memory allocations at run-time. This is handy for scenarios where user is aware of the steady state requirements for his application. User jobs are queued through a non-blocking call and processed asynchronously in the order they are received. - [Task pool API functions](@ref taskpool_functions) Provides a set of functions to queue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully queuing an operation. @@ -70,29 +70,16 @@ The sequence diagram below illustrates the workflow described above. The applica @image html taskpool_design_typicaloperation.png width=100% -The state diagrams for statically allocated, non-recyclable jobs with all legal transitions is presented in the diagram below. A static job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Static jobs cannot be recycled and do no need to be destroyed. Static jobs are suitable for embedding on other data structures that own them. +The state diagrams for statically allocated, non-recyclable jobs with all legal transitions is presented in the diagram below. A static job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Static jobs cannot be recycled and do no need to be destroyed. Static jobs are suitable for embedding on other data structures that own them. @image html StaticJobStatus.png width=40% -The state diagram and legal transitions for all recyclable jobs is presented in the diagram below. A recyclable job is dynamically allocated. Just like a static job, a recyclable job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Unlike static jobs, recyclable jobs can be recycled, or destroyed. Recycling a job effectively pushes a job to the task pool cache, where the task pool manages the lifetime of the job itself. The size of the cache is controlled via a compile time parameter. A user can get rid of a recyclable job by destroying it explicitly. Recyclable jobs should not be embedded in other data structures, but could be referenced from other data structures. +The state diagram and legal transitions for all recyclable jobs is presented in the diagram below. A recyclable job is dynamically allocated. Just like a static job, a recyclable job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Unlike static jobs, recyclable jobs can be recycled, or destroyed. Recycling a job effectively pushes a job to the task pool cache, where the task pool manages the lifetime of the job itself. The size of the cache is controlled via a compile time parameter. A user can get rid of a recyclable job by destroying it explicitly. Recyclable jobs should not be embedded in other data structures, but could be referenced from other data structures. @image html RecyclableJobStatus.png width=50% */ -/** -@page taskpool_demo Demo -@brief The task pool demo demonstrates usage of the task pool library. - -The task pool demo demonstrates the create-schedule workflow of task pool. After creating multiple jobs, it schedules all jobs and wait on their completion (by synchronizing at the application level), or try and cancel them. - -See @subpage taskpool_demo_config for configuration settings that change the behavior of the demo. - -The main task pool demo file, iot_demo_taskpool.c, contains platform-independent code. See the following guides for running the task pool demo on various platforms. -- @subpage demo_taskpool
- @copybrief demo_taskpool -*/ - /** @page taskpool_tests Tests @brief Tests written for the task pool library. @@ -104,29 +91,24 @@ The task pool tests reside in the `tests/common` directory. They are divided int The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). */ -/** -@configpage{taskpool_demo,task pool demo,Demo,demos} - -*/ - /** @configpage{taskpool,task pool library} @section IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS @brief Set this to the desired wait timeout in milliseconds for a worker in the task pool to wait for an incoming job. -If a worker in the task pool wakes up because of a timeout, then the worker will terminate if it exceeds the desired minimum thread quota, which the user can configure via @ref IotTaskPoolInfo_t.minThreads. +If a worker in the task pool wakes up because of a timeout, then the worker will terminate if it exceeds the desired minimum thread quota, which the user can configure via @ref IotTaskPoolInfo_t.minThreads. @configdefault `1 minute` @section IOT_TASKPOOL_JOBS_RECYCLE_LIMIT -@brief Set this to the number of recyclable tasks for the task pool to cache. +@brief Set this to the number of recyclable tasks for the task pool to cache. Caching dynamically allocated tasks (recyclable tasks) helps the application to limit the number of allocations at runtime. -Caching recyclable tasks may help making the application more responsive and predictable, by removing a potential -for memory allocation failures, but it may also have negative repercussions on the amount of memory available at any given time. -It is up to the application developer to strike the correct balance these competing needs. -The task pool will cache when the application calling @ref IotTaskPool_RecycleJob. Any recycled tasks in excess of @ref IOT_TASKPOOL_JOBS_RECYCLE_LIMIT will be destroyed and its memory will be release. +Caching recyclable tasks may help making the application more responsive and predictable, by removing a potential +for memory allocation failures, but it may also have negative repercussions on the amount of memory available at any given time. +It is up to the application developer to strike the correct balance these competing needs. +The task pool will cache when the application calling @ref IotTaskPool_RecycleJob. Any recycled tasks in excess of @ref IOT_TASKPOOL_JOBS_RECYCLE_LIMIT will be destroyed and its memory will be release. @configdefault `8` @@ -154,4 +136,3 @@ Log messages from the task pool library at or below this setting will be printed @functionpointers{taskpool,task pool} @structs{taskpool,task pool} */ - diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 3557b781c2..0a539bedcf 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -86,7 +86,7 @@ * @anchor DefenderMetricsFlags * @name Metrics Flags * - * @brief Bit flags or metrics used by @ref Defender_function_SetMetrics function. + * @brief Bit flags or metrics used by @ref defender_function_setmetrics function. */ /**@{ */ #define AWS_IOT_DEFENDER_METRICS_ALL 0xffffffff /**< Flag to indicate including all metrics. */ @@ -125,7 +125,7 @@ * @brief Enumerated types of the Defender library. * * @defgroup Defender_datatypes_paramstructs Parameter structures - * @brief Structures passed as parameters to [Defender functions](@ref Defender_functions) + * @brief Structures passed as parameters to [Defender functions](@ref defender_functions) * * These structures are passed as parameters to library functions. * Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. @@ -171,7 +171,7 @@ typedef enum /** * @defgroup Defender_datatypes_paramstructs Parameter structures - * @brief Structures passed as parameters to [Defender functions](@ref Defender_functions) + * @brief Structures passed as parameters to [Defender functions](@ref defender_functions) * * These structures are passed as parameters to library functions. * Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. @@ -213,20 +213,27 @@ typedef struct AwsIotDefenderStartInfo } AwsIotDefenderStartInfo_t; /** - * @page Defender_functions Functions - * @brief Functions of the Defender library. - * - @subpage Defender_function_SetMetrics @copybrief Defender_function_SetMetrics - * - @subpage Defender_function_Start @copybrief Defender_function_Start - * - @subpage Defender_function_Stop @copybrief Defender_function_Stop - * - @subpage Defender_function_SetPeriod @copybrief Defender_function_SetPeriod - * - @subpage Defender_function_GetPeriod @copybrief Defender_function_GetPeriod - * - @subpage Defender_function_strerror @copybrief Defender_function_strerror - * - @subpage Defender_function_DescribeEventType @copybrief Defender_function_DescribeEventType + * @functionspage{defender,Device Defender library} + * - @functionname{defender_function_setmetrics} + * - @functionname{defender_function_start} + * - @functionname{defender_function_stop} + * - @functionname{defender_function_setperiod} + * - @functionname{defender_function_getperiod} + * - @functionname{defender_function_strerror} + * - @functionname{defender_function_geteventerror} + */ + +/** + * @functionpage{AwsIotDefender_SetMetrics,defender,setmetrics} + * @functionpage{AwsIotDefender_Start,defender,start} + * @functionpage{AwsIotDefender_Stop,defender,stop} + * @functionpage{AwsIotDefender_SetPeriod,defender,setperiod} + * @functionpage{AwsIotDefender_GetPeriod,defender,getperiod} + * @functionpage{AwsIotDefender_strerror,defender,strerror} + * @functionpage{AwsIotDefender_GetEventError,defender,geteventerror} */ /** - * @page Defender_function_SetMetrics AwsIotDefender_SetMetrics - * @snippet this declare_defender_setmetrics * @brief Set metrics that defender agent needs to collect for a metrics group. * * * If defender agent is not started, this function will provide the metrics to be collected. @@ -241,7 +248,7 @@ typedef struct AwsIotDefenderStartInfo * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. * * If metricsGroup is invalid, #AWS_IOT_DEFENDER_INVALID_INPUT is returned. * @note This function is thread safe. - * @note @ref Defender_function_Stop "AwsIotDefender_Stop" will clear the metrics. + * @note @ref defender_function_stop will clear the metrics. */ /* @[declare_defender_setmetrics] */ @@ -250,8 +257,6 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me /* @[declare_defender_setmetrics] */ /** - * @page Defender_function_Start AwsIotDefender_Start - * @snippet this declare_defender_start * @brief Start the defender agent. * * @param[in] pStartInfo Pointer of parameters of start function @@ -341,11 +346,9 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /* @[declare_defender_start] */ /** - * @page Defender_function_Stop AwsIotDefender_Stop - * @snippet this declare_defender_stop * @brief Stop the defender agent. * - * @warning This function must be called after successfully calling @ref Defender_function_Start "AwsIotDefender_Start". + * @warning This function must be called after successfully calling @ref defender_function_start. * @warning This function is not thread safe. */ /* @[declare_defender_stop] */ @@ -353,8 +356,6 @@ void AwsIotDefender_Stop( void ); /* @[declare_defender_stop] */ /** - * @page Defender_function_SetPeriod AwsIotDefender_SetPeriod - * @snippet this declare_defender_setperiod * @brief Set period in seconds. * * @@ -373,8 +374,6 @@ AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ); /* @[declare_defender_setperiod] */ /** - * @page Defender_function_GetPeriod AwsIotDefender_GetPeriod - * @snippet this declare_defender_getperiod * @brief Get period in seconds. */ /* @[declare_defender_getperiod] */ @@ -382,8 +381,6 @@ uint32_t AwsIotDefender_GetPeriod( void ); /* @[declare_defender_getperiod] */ /** - * @page Defender_function_strerror AwsIotDefender_strerror - * @snippet this declare_defender_strerror * @brief Return a string that describes #AwsIotDefenderError_t */ /* @[declare_defender_strerror] */ @@ -391,12 +388,10 @@ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); /* @[declare_defender_strerror] */ /** - * @page Defender_function_DescribeEventType AwsIotDefender_DescribeEventType - * @snippet this declare_defender_describeeventtype * @brief Return a string that describes #AwsIotDefenderEventType_t */ -/* @[declare_defender_describeeventtype] */ +/* @[declare_defender_geteventerror] */ const char * AwsIotDefender_GetEventError(); -/* @[declare_defender_describeeventtype] */ +/* @[declare_defender_geteventerror] */ #endif /* end of include guard: _AWS_IOT_DEFENDER_H_ */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index a15cf24104..59067e7622 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -423,15 +423,16 @@ void * AwsIot_MallocShadowSubscription( size_t size ); void AwsIot_FreeShadowSubscription( void * ptr ); /* @[declare_static_memory_freeshadowsubscription] */ +/*------------------------- Metrics data management -------------------------*/ + /** - * @brief Allocates memory to hold data for a new [Metrics TCP Connection] - * (@ref static_memory_mallocmetricstcpconnection). + * @brief Allocates memory to hold data for a new metrics TCP Connection. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Metrics TCP Connections. + * for metrics TCP Connections. * - * @param[in] size Requested size for the tcp connection. + * @param[in] size Requested size for the tcp connection. * * @return Pointer to a Metrics TCP Connection. If the size argument is larger than * the fixed size of a Metrics TCP Connection object or no free Metrics TCP Connections @@ -442,8 +443,7 @@ void * Iot_MallocMetricsTcpConnection( size_t size ); /* @[declare_static_memory_mallocmetricstcpconnection] */ /** - * @brief Frees an in-use [Metrics TCP Connection] - * (@ref static_memory_freemetricstcpconnection). + * @brief Frees an in-use metrics TCP Connection. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -456,8 +456,7 @@ void Iot_FreeMetricsTcpConnection( void * ptr ); /* @[declare_static_memory_freemetricstcpconnection] */ /** - * @brief Allocates memory to hold data for a new [Serializer Cbor Encoder] - * (@ref static_memory_mallocserializercborencoder). + * @brief Allocates memory to hold data for a new Serializer Cbor Encoder. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -474,8 +473,7 @@ void * Iot_MallocSerializerCborEncoder( size_t size ); /* @[declare_static_memory_mallocserializercborencoder] */ /** - * @brief Frees an in-use [Serializer Cbor Encoder] - * (@ref static_memory_freeserializercborencoder). + * @brief Frees an in-use Serializer Cbor Encoder. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -488,8 +486,7 @@ void Iot_FreeSerializerCborEncoder( void * ptr ); /* @[declare_static_memory_freeserializercborencoder] */ /** - * @brief Allocates memory to hold data for a new [Serializer Cbor Parser] - * (@ref static_memory_mallocserializercborparser). + * @brief Allocates memory to hold data for a new Serializer Cbor Parser. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -506,8 +503,7 @@ void * Iot_MallocSerializerCborParser( size_t size ); /* @[declare_static_memory_mallocserializercborparser] */ /** - * @brief Frees an in-use [Serializer Cbor Parser] - * (@ref static_memory_freeserializercborparser). + * @brief Frees an in-use Serializer Cbor Parser. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -520,8 +516,7 @@ void Iot_FreeSerializerCborParser( void * ptr ); /* @[declare_static_memory_freeserializercborparser] */ /** - * @brief Allocates memory to hold data for a new [Serializer Cbor Value] - * (@ref static_memory_mallocserializercborvalue). + * @brief Allocates memory to hold data for a new Serializer Cbor Value. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -538,8 +533,7 @@ void * Iot_MallocSerializerCborValue( size_t size ); /* @[declare_static_memory_mallocserializercborvalue] */ /** - * @brief Frees an in-use [Serializer Cbor Value] - * (@ref static_memory_freeserializercborvalue). + * @brief Frees an in-use Serializer Cbor Value. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -552,8 +546,7 @@ void Iot_FreeSerializerCborValue( void * ptr ); /* @[declare_static_memory_freeserializercborvalue] */ /** - * @brief Allocates memory to hold data for a new [Serializer Decoder Object] - * (@ref static_memory_mallocserializerdecoderobject). + * @brief Allocates memory to hold data for a new Serializer Decoder Object. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -570,8 +563,7 @@ void * Iot_MallocSerializerDecoderObject( size_t size ); /* @[declare_static_memory_mallocserializerdecoderobject] */ /** - * @brief Frees an in-use [Serializer Decoder Object] - * (@ref static_memory_freeserializerdecoderobject). + * @brief Frees an in-use Serializer Decoder Object. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -584,8 +576,7 @@ void Iot_FreeSerializerDecoderObject( void * ptr ); /* @[declare_static_memory_freeserializerdecoderobject] */ /** - * @brief Allocates memory to hold data for a new [Defender Report] - * (@ref static_memory_mallocdefenderreport). + * @brief Allocates memory to hold data for a new Defender Report. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -605,8 +596,7 @@ void * AwsIot_MallocDefenderReport( size_t size ); /* @[declare_static_memory_mallocdefenderreport] */ /** - * @brief Frees an in-use [Defender Report] - * (@ref static_memory_freedefenderreport). + * @brief Frees an in-use Defender Report. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -619,8 +609,7 @@ void AwsIot_FreeDefenderReport( void * ptr ); /* @[declare_static_memory_freedefenderreport] */ /** - * @brief Allocates memory to hold data for a new [Defender Topic] - * (@ref static_memory_mallocdefendertopic). + * @brief Allocates memory to hold data for a new Defender Topic. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -640,8 +629,7 @@ void * AwsIot_MallocDefenderTopic( size_t size ); /* @[declare_static_memory_mallocdefendertopic] */ /** - * @brief Frees an in-use [Defender Topic] - * (@ref static_memory_freedefendertopic). + * @brief Frees an in-use Defender Topic. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 12a289c769..2d4ac8523b 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -421,7 +421,7 @@ typedef struct AwsIotShadowCallbackInfo /** * @brief User-provided callback function signature. * - * @param[in] void* #AwsIotShadowCallbackInfo_t.param1 + * @param[in] void* #AwsIotShadowCallbackInfo_t.pCallbackContext * @param[in] AwsIotShadowCallbackParam_t* Details on the outcome of the Shadow * operation or an incoming Shadow document. * diff --git a/lib/source/common/static_memory/iot_static_memory_serializer.c b/lib/source/common/static_memory/iot_static_memory_serializer.c index 78a3abbfed..a4227ffd02 100644 --- a/lib/source/common/static_memory/iot_static_memory_serializer.c +++ b/lib/source/common/static_memory/iot_static_memory_serializer.c @@ -73,10 +73,13 @@ #error "IOT_SERIALIZER_DECODER_OBJECTS cannot be 0 or negative." #endif + /** + * @todo Placeholder. + */ typedef struct _cborValueWrapper { - CborValue cborValue; - bool isOutermost; + CborValue cborValue; /**< @brief Placeholder. */ + bool isOutermost; /**< @brief Placeholder. */ } _cborValueWrapper_t; /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 5d77f61946..0d4c32d91f 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -233,7 +233,7 @@ static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; /*-----------------------------------------------------------*/ /** - * @brief Checks that #_IotTestMqttConnection is still usable by sending a PUBLISH. + * @brief Checks that the MQTT connection is still usable by sending a PUBLISH. * * @return The result of the PUBLISH. */ From 9dee0fe1812d5e1664f1334e40166e9288a73369 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 4 Apr 2019 08:58:36 -0700 Subject: [PATCH 085/844] Update MQTT architecture diagram (#361) --- doc/config/mqtt | 3 +- doc/lib/mqtt.txt | 4 +- .../images/mqtt_design_typicaloperation.png | Bin 109457 -> 65834 bytes doc/plantuml/mqtt_design_typicaloperation.pu | 67 +++++++----------- 4 files changed, 29 insertions(+), 45 deletions(-) diff --git a/doc/config/mqtt b/doc/config/mqtt index 9e1f2f2955..62244d214e 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -36,4 +36,5 @@ TAGFILES = doc/tag/main.tag=../main \ doc/tag/logging.tag=../logging \ doc/tag/static_memory.tag=../static_memory \ doc/tag/platform.tag=../platform \ - doc/tag/linear_containers.tag=../linear_containers + doc/tag/linear_containers.tag=../linear_containers \ + doc/tag/taskpool.tag=../taskpool diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 95a0cb7fb5..a30ebff222 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -62,7 +62,9 @@ In addition to the components above, the MQTT library also depends on C standard @page mqtt_design Design @brief Architecture behind the MQTT library. -@image html mqtt_design_typicaloperation.png width=100% +MQTT relies on the [task pool library](@ref taskpool) to process MQTT operations, such as SUBSCRIBE, PUBLISH, or keep-alive. The MQTT API [functions](@ref mqtt_functions) post a job to the task pool, which is transmitted from a background task. Incoming responses are received through a [network receive callback](@ref platform_network_function_receivecallback), which posts another job to process the response to the task pool. + +@image html mqtt_design_typicaloperation.png width=80% */ /** diff --git a/doc/plantuml/images/mqtt_design_typicaloperation.png b/doc/plantuml/images/mqtt_design_typicaloperation.png index 41ef79b58c1f29d96db34ab26c5580f52a5500fb..25a4472b05420223bada6a97c1e2612156ccbd78 100644 GIT binary patch literal 65834 zcma%DWmr_}+NP9Fl@vj`K~khdN*V!ahLEm7nxUjoK)Oq$m7!A@5KyF3hVBxFhM~c4 zA?|(l-p75e?-v(ytyyn9ao^8#zw=T}MGhZ_5(fnZ1z-NLv^ok3dI1Uw+6opL@X0-V zS3TeltBZ_|%QLW}r=7Wl3yPe%gSnH5i@6!CsVA+Ki;Lp}Zf-|A69*SpdpoXYV0&U7 z;oHC)cC9saT>khR1r>OWN4l%#q{9Lakxy;BAGxcM9`s?Zd4qjbu>#nxVT$xoRy5lU zJY?)x%*H^4QS)L@P_DA4Waf^r^GeTs?FlvWSQeMC8AoCb8G5Wo?~t&Y7F|E?)fFn^ zTYs7(!>#Sve(@lPN%j?k9?vpSD8~}jZP&&7pVH^mZ?`0g8#O0qnkwDL*bem$Te^2E z-|1FQxT%XLh9;r_RKBR868y7`FNDrEAz0;F$N@Sksy<` zXN=ENy3M0NOedlh@A81B`pehXc1uvY0JbRLHR^o{~>OH7`dYT!^s*)f;@#h zlbc8!nLA3!@;7a=P7M77eeTTSgK0vI_2Pm7|xL z=w_U85o_19PF!U=HTXcESFx2e7?{oPU5#GPwv(WFGlMK9>hw%p-H2uQiwrG2LSgec z_0jpOFr{Z77#Qto627KrQ|OvUu|b)$%Vpt&bCtF8(d_A_Cq9geM67anqUfWt&rSms@@3P|?PE%BecfB5#E!DMp$LO5g*nM1klj$Xo zsOg;uQ>Hs$zg5(_8~P==VaYs8JJybMjx!Dh&T*x!!?cT@U1pwhHL1{*HSNRVd-}9b zhaBVfn@3!@)$A*kWyk3jXqQto3p=C zd0X?4E9PThir;Jdm+(`q`L=9PfHyi?MW6 zi#H1J_vgRFTvo8Fk9o*<0^#9*d@RAO1OAZB6hQkurn%T9!s!dqdy@;ZZHndRcd=q^ zdyI_9-+!5ctX}YwNfHT^4Nn-W))=0XmW!`{ebJRnU>1lfNrc22CaojTAh|0_+K8`F3v|Pj&g4-tSxA_m;MO35Khk(9Tho;?~lqg zfeJP6Mz4Ql^KU!EW8OQ}-WC!_R$ArKsW6_jw(s1Wu(1-(MyD^leUupQTQF z-^^M)3tPkFQ|ISAwTH%gC16f5kJO0nk;=A@^Nu3T;B4Ur0+!mxD|VCMZ||!SA0Qsr z<2l59cWijAdtb7fh@IXdzzaZTb^dS!YG_~|fj zTOnzPy6<%%W+`F6w)M>rW`xq>GJ1#Yf-a5+IVnHO(g>E~#>m__ zm>Wz2uZ$MF$*Pq{-k2+O8Wa%QieIK~P4ZlhsEd`XKzjXknR{8S(1aY#%f3zqK19a( z^!nT7@Yankdv<06#W3Mp!weNKy@k|i^4l{buX%-do|LR9UQbws{Lr5d9G4DHi?SDA zJcxjxD`kijLe#!T6%E-ra+Gb-=h_v5Tm z_3G>7^|!cqIM!@qM>*Wp>7K!?pDy$Em)lX9Q~C!&laH-jO3aTOUd~T&3_of9>M+LC zT6L`?5T2&k?(?8;KUI+Yqrn$n-o&XpJ2P0k33bm9WAG)1wUhkXAJ+q^mds*PkOX7= zFN#IyA8g#XS#~|SKz5XJiR&{2(~ciH(AVd%BE)BMdp)t(@6&0126JW1aJ#%vTu1H` zFFDrQpP?#02}#mA^PP~7-)@w`LDjJGnuC|eGJ+-zs}%>0tylJI9E;Nq@A8be6R4i; zIDJ}9mDb$|YDl)(s>Q^i`>cawEpoLf=((e*dM!59OrBD#Rv26n`_eIHlHEc&67#8w z#Ei;jrk^R*Qb&eJXr*jsrdoW!#Zm0*V+*dRE45w#_RiFXZ#*OZxGL?fGa{T!yu zxBiNEx~7uvtY*mN5OJK}5Ut9bwn@yV^W)`pv3%39&6$cdyk?0eiYUC0;*obv~eKURBPYE@YNrM%x zsP%1O?NPg>M2F~xG0(mMeAtdAc8e)aUOFu1b}L}q6-bBk1yI!B4RnQs=g*y)6VOqG zg5~wOW}#GYg7r8&%18o>Rfs%9`ZJ3VgXGHX#CV0xR-AbUXg+64?tIAmP-Y6h)dlK` znkSW{PnhJ{r4e};Q0+$*Gg*lg-l9rDX&}`8Vs{g;y6(!|Rnvq= z55DpQ9i?qPf{gwE2ZKubSAIPmylr^fa!SzW zq2T0=5ApKCqN9o`;{m&`B?Rx_-LC?(OmW`Qd!@uV+t}C=fYcKmP4j$93Dp)`uM%X} z=N2x<=@N18m*kmS0Urc3EZa6R^_CqhjRYY@KDx^r#_%;si&52hwGDQ<*nfM`Dc@yJ z3_3B`I-!o2AHo@guu38lqKnBK`qxZ8D2p#D7AK{ry1Me7Z#6rO!*LMPH%%y6SHXIn zXSGTXyKde)Z}9!*$F7-)5aSRTeP1;LLgMwk&Zi28q-_PJXC^&O>dFpQ(B z%_w#u^*|D zVdK?j9;jm>4wgd|uY>%ZYz4a?`A4-vrq^E4d^U+EZ&;GWsOybPObYfK% zS{f>80jvHn8qjdJ_tA>CL}8CRnwfihi7jDi z1*5GnISqHe^<|BZ+fxurhx0k43OL_xwqfn8_o!OmWNKvk*{$} z(7Vj7e}acxjn#P5*&RcSQ@4Pjquj{4xyd%m&@yr3GHacliQIk|pJ9;k>M0j#xd_^I zNp&hb>$#PUmCeNqo3ZyJ548C@irvr3cgmOv`UrS*YM$#>-eQ`0j4{qhr1^1he6ThM zoMRGce51?fA)=T1%5V$K5Fm8X-Q~lcEyGIr@Duw78t^Xo)oh@d-aq1|wtKob1RO%L0is zZEryx#txU!FKsSNw`lc_ZOVVtU|HZ*P#JX0Em*~_7i#4@SDBG~hGbb}KCbCM1BIEN z|48%SHp^J!BXKpl*XiIZG+bK-%vRn)1REmuF4+1IKq(VXgYOFsj=a?J|%;BsCO zYY%y6%U&Wu${EP%78lgI2bQFI+@|KgdtSPR{esb5g zD}VMfvvy^kEak4}zf9#Yqb`1Aue~INv_@EP>7!;AqUg1PhU6ay4#F5DchjTND+QHl zptAvN?Dvxlkwn-}*36fgfLXp6;Q(ah=xmyc})!z%dg7wVCMR%-YTXM6ocZK2wvEYaYO=)SJ;F;pm7x%ltw1O$g*=g7L^ z;&E_HpC8WhRa!)H>GFOYA1^YfC+SZ7+{N6RN&WPL%bW0UjY!JnmgGu9=?9|?X7Wne zkHqK3hjN8x?TGKpylA@ruR}Ol!szAP_V(Cmr!#oX9Unj5-dVKuJKY#-3GBut-Jhv@ z{vd_lTHViR_DsaB=Xg0uRW)5l$iuO~jx475aK24SMdkHYb8~dWj}ycbA)zGq8Tou} zm4^tK;;7$POG;ApjNgSYa;+d=_5O>aSb4v*=Tpuhd0=O1{A@GMDg9x^N}2Y=o>Aim9a~rh8!ExyjJl zaK3yJH<(YK$~-2546x>vlsk7EjQbrMWWpc#oYwTZJx9|ktvRcl!nzgk_#^sT@Ayko zWwu3JD+-vz>gJdclU&os!p-Fyo*2r~=k7?U2qID*``|zW3|w;9FA)VEi(Xn^#}HRw z%6L$Xn!7iY2kpx1$Ys~Au@uf+rla82+oJKi-JjHtUUM|p;){v7=#Q>gX_)>3{+=^e zl<4IavnLx(p34&e17z!NjeOM|SR{f2ey#=ha2pVahK#B}-ZHkho{Vpi7A)eUA|{V! z9|e2g5j){uJpMwiu1=Sr7$a|O9anL?-zf&~vrrOpV^WZ#c&5>F5e@AuQ&c!(V*#TI zWPf+rpL(TX)gBd^G(J##D1wVmFi193ItWP#&VXB3z>Z$QNp}^b5xr_U+(!;A0W)z- zNM`rNh3lT4^}r(cyScMwJ|Jh_%G_9342_k)!F&)X?7o?f8=fv&5BiWASrL|k+?;Lx z+R{Q!LL%y5z-=8}0hp$?{iZ1csT{jfRP2>y?_zpBQBE1)<`v_$(f(S8zWC7l2AF{j zi92}hGRS~4AAw%7#tbhcl`87hy25J+A9`#1xpiKLCW#C{s%6S{)zDd?y%XQ8ikg_)dk|} zDg4|jH~~%BgKYG$t&%a*FC=2%UOu0glwaHOWzSd4_F%G-nKO$Y()+E@@Ck5;3IBO} z4Cv)vQpsb3I+yPYwQ+EJ!^T4vJ?>Lr%fpSAv#%>yV_(&^{N5mW39cq2iC89kl}cOx z@N(1gsxJL66dNIM8Z3W|tY;f^v$trx0EYTD)3wu><8`QYeo`%kFu(1lmzDq>Jt+;t z>n?q+xM)#0ZdnBq(6KqS$s}e>gv&m;!9ZpOx})_`-M%nLUGFjk$0;HhC609pR2Y|Y z4UCJfXdDTz8Vsu{H-=&q%p(Yc0OeyT`!5HCvx*^w6>feiZr*_9$8F)=q&7527Zsh_ zob}7E7uDO^nA{>`U6^b9QmgC+;JnWwF$gu|8UtIjSJrW_~OBXBNseXETnV~G@61b?bxXGjK)^K~~ zqd1*pap1%6o=)42&M+=65h}21m)Iqh;&-|9B-i4Qam_6)>A)Zz7w7p#c1Z_NzaIxc ztl!;ZpF^bi9c`EVIGwHZiBl^{ued2Z#$}l53Hcyorz1wcr(@AeX;@)~QYTaI#F)q# zo&dL7bL8dqGruE<%Fe1+MSG@;9U2Jl76&#a0j^loLnyAJS#6^WWGMB#SG~#iVl|u2 z49!MMJk@G#;Yxh^%z1=rpzL`0#A2bJ2@8SYx#Tu~_TTGAPl%KB)o zJT&5V7&JmCq6g5mNjceQ5s5;29;}b9aXQ>^M&HL&W-}%Dv zN-tmbz4P-!d25n*;;(d#QfK?a%$S5+m}EgVeQvRN2SPwzPPYH@TDbXackSg3L@r6v z3yIs91nWd3BuXG`idPc&%eGV^HDv1t<PAN6T$)+8~IeHDFygc*47%;J9XvA zW!;fzf_HcOe13Jm(w zJE7&*LWk|Q51cI)2_!@9ZGo94x&^v&yFJs)qp0jmnj z1J_@OWceM7&x>80Jf*IeNOjWMDv89`PYCo%rMV#0c{SiQitN(fAp)Iu@|Pa}0(58Y zb#GepbwAk$_sL5^(=j`5k?J$Im$&M-V}m}=KgFwUc`?7{yPQDFTcnbqFr1Iy-LaYA zaFB0TB)9}X=&fDZ93S-|$4ZKo9t!kDi67oNl>gal#AvC4_#5;l#HzM&cjC>Owrx!OBuvC!h@uCv|l>Fw9%2R|Sb{j)N2_OyP!{ZgS?EZUD&IxDV>ltQ=+bh40m z@MY-INEw$(v@ORE_&6l!)oF5 z!ZkkIUyB_26^&N&pob^Oi~-xtspA(@)Dlgq;iRs)MQXA`ekLf}?o>B1&UTcu;>}M3 zc>^da&Zf3MhwD#N6%qOow`Mtn^gQ;<)2*0MOsZ+MTPjs*oomTR6@)4~v^7OijVh*M zgntYeGQ`e9>gnMy$;i4TF~x-cn0t;+YgP*YwoXmOQ1nGdsu`a=WR-&DnkUL@2KU-sf_Q9-pV~pqVEaOHm4^l% zZHy~8eptYoYkbe&Q3*0^OgD`6_AYNVt6H2YBsH_f#cWcy^*BOYd-6k^&ZMi*DuY>>tTv{=0BP{j>A_NDs`ZHLYRtI1yTXhQc*cb z!92ETo8~{Px{qPRU^XGivagsdrD|Qd5w~@?eVzlc&Sai^ z`3;i9Y4m-i-6=(4*3V#vqN|@MAb*)6)w<}VEMO0lxOzWoF(UFBg)LZSoF(r~PaB$8 zaC0C2IN#b|>O{xH`$p?iC^V`4VZ7QF2y_I#t&)w6>Bx({epZ$k9%? z>;w3V6cQ?Sf1H*rl{)okspxjukq9To$3L#TWuf-dqXK=}Rqs1uia?bkk?L&25#L9o zI-Wvv*SU>mR#vqiUVs?CsMEY@e8d{Z>tqbu|IE1&Me*Mbq&tCHQNv*y`Kaiw?c5Kqtqgh?eqI_ zjE-*It=H7f>FKvSUlP3>ugrNx#{Gz^(&zj*7;C$Lu$~ANLYflE3uMl~=6M`|Yt=h* zou#+}7U$%Tc9yWUu^|8ec_TluljGGic5Ppo8To8C#K?O(9|Oatn(}8}ExthHJ}Af% zPkG=Pwilk|T>EQl6pqot!>KU*G9AXEF`{Om`dgfO{CCBDtz-!ZLJfKk7Na}_sDuD8 z{Z)rMk2i{1pU=@a)OmGi-hO3$TzNZ*=c{u_@j5G*Azu8UXRtbxx$V_<J0>cM|A^F1h~5tGQkUn?>ELN5$G!KYb~4jqjt4T{YH6oe7zC*>39+|baV03%co<*%wpOAEjI9?TW~>O1!5jAi#!on38V3-$tzJa2zg zT(Z~94b(R^`RU$v2V$Of-d}d~{c+CcF#uquxxA5PztftdUUSipDz+NWVDVi++75aX zxe5tgbsru`-BZmJRXfSiW;+Ff8Y$ZnpH2KR74|PkAzTjX`ZHK3bjXy}d_CY4qu;Lv zOil61!v?zBdLZ+#{^4Q6iW$6a@N0{$dWwFDXV`vY(G(BmJ3}Fzh9~UPu}BM@P3_BF zK1;wkdRAvO7(hTEyPNNQxEX2@uBM=v3>1h!eUM5ST^-?+pvIe4un3^+)LoC%!+($T z*Mfe3O?^81Io}gikGomzGD!p^gi^BPF06fAa{y z{eH~=D9v4iCK<8V1T3Ll5{~#GlAjej(8a@_u>O)={m)rlvY6kH226naT+JZ_o>uS% zxr-~K9Bn|W<5}o`1^)raNOQGmQixuS0myOiWgH+X7XAA03KL%vaN)|iV<{cTP*`&oOq4I(RkFK)xhzOt>s6^=m4}&cPIs@+i~l0$C8c3BXMLCFPC?> zTMAz{QR@Q>0RGJEVSC^LprGK-BT)1s9{W5$iu!}20sp>x&@C;f48r_|o1XrOGh*89 zdpq3q6szc75kN3kBGO|?Z-GIYkGXWJ;iScfS#p^Lni!$o4|-p?VD6oDR+b|mXiTO7-(^K9i<;jlzV&qqw`I7ah2n#yB`3X zKkbK@=i1HOX!f4I#`zzE_cDaR)J6sJQcrrf;p2`7SV23eO$_paa#|%N#N?i^2NHVEC zUN4k>V$TwhCId3Nt{(V%UsNpIKI4N6ewf}uYhD;+E+**9jXO_0?6hx-GdChL72Sk+ zMl9v*ust-4_&6hM=ZNMW&N;JZmYhW6B3JFVi-E znQ|O_Tee(vM~rH6a^D46de<36?_~+SN{=bS@Mg`XDPWqXA+>o2s9Z=Uy0CQ z27DISyZxRG^Y~gOu?J{w+<(;yey-cKN^n`h&`*Tp>eT>iowKyYRC!T&54TY-Av-Q$%?~Olq0T_2lP&s92kP;S zMe%2Zs;C9U5M7`UlLBzX+#1&S@w6{Zg2J0g5mAi>u_q%&u?UyDjAVb5x*NlDoTAAt zi}!Qb z$t0^D{dJO7Fv$P2O#n;^fCohEfLAR}Kz3{dN#7gP8P< zc7{M(HFCFUZ$lwW z4=$|(`H&q%dP}I+a|A3AKt^=={&h>oJ|GXm*GAdvN4Kfl61wRgZ~mOZzr7xiAL((w zy|@3a(xQ%3t}!(-_!}cp4=6gQjIi?a&l0T_%nBCPKJdOTb$$X;AM$gC#blD7q^>q^ z5JDVzxq!f5O^-Lg5orCYp8xUY-}Pn?GW;PRS2*4&oT}LkK}GyzVX$+KhFMw(bEqX+ zgg}|sZ&Z#*KP02}y8d70I93ZHl17&uP+GIPD9Lh8hS6f1&{%|r{^K!^@|h2=2-#;C z3RYHy3J3vM*v&w!*c*URxy?}83IAnDpCK)AzO7g{JstrIff?6$jCtCY0a*Gil8#cU z^nop4Cpn5?$OD@t2*==_K}9zZHN3*tX`nnwC^$W@a4(OXPy~!$bGr&u1(JUD?Wtw& z8>FcVcKJ*nKF&Y8DtC;rEk0lS-HZny_dW#afKS`a1yxF#=xZ>-w(8B2fRPd|zr+3b z(OBsCg2^hhB@yU)z{9QZVBd=YF<1oGUHD{)`M2*u1hh>Jrs33z`;7wqh5INN<<$SR z5~1#?y65zJY1MeA2OE4@OfER299VK0iHR2Ck_ge$AgNWA_p6bHe4KX9b{+mj;-ebW z33p!5)}a4mecQG$Bj025*(M)!G_>2))P{zJ=jZ1@jlfz^wM43R2Im%AbWx6MWbAdX z!%gMuRQy)7_(0XTSvgJcO)LQcK~qx`RImDEyN$4uft#C~w^^4>yF@940V*67D}u0x z7gKNmXw7dA`*N+c3jbI~h!MhmrcpTJ%coCM4end5!MM1j9N&iW!pBAH0oF#gzu%W3 z!s7%GCQD08UKZW0Q~}$EZY>?!P(4>*-2lS}pbk}J`z>~@j!`YBo`B9fpVNcg7CYwU zCzk2= z<(*k)>~OwvTq0oTr0dQ_>UmRBQ|~m3DlGf&98ZKHWo2bsHy#D!PzyUPF?UDVj8~X< zYCZP$_RfW>y`6!i{MB>%{NU_hdqCy*teT}C85@QAqRRE#z+Fbh<^8o`v$KY+CR}W6 ze|cK_TJWszUat^-#c=ee?;j7iK#g^F zilo`1@9M=zCyO)+%Ab7+2@Y=Ql1%C};sw?e=N@xx4?hMI>@@`g;!V%nZ{-va84`X? zDGackb}EUBqs4sXG`ti`q2}`g{p5|0{^*Wm2GQO({dK5?5uST1$`x}nFfU-~Dh__< z$Wfj(VAM-}RLtw1#qkS0a3-Q@2P!7NFXMm0&b?LXDl9`f=>^78T}hw~HU{N`Lspiw%4ncRP;#IT{&ewRqH#HiUpkV=>8i5bt}d+pNL z=O;O-wusIaZFrX zT+)j*z>zqwC^=1X$VJ^bU;F%{rP=F{D`g)rVs4!>6okw6sZF_AWeKD(vI!>)7W{WDtOkwo!VeRu@xZ{sk~Yl|(rE^1|nE*&JdE z{d%Y6_DHJv>mJ+gvp$yMS>}k|>3UF|nB5qU)QXI&qrL+S*g$AhR1`n(LW9zzBr-d_ zU)u*Lj5-%ly1drM?(xhu`JA>+4-5=sIXRXb_6p51kgewB=0*dXrclDs6O3G7zRg1( zkw6b@)H7d!$(7!;95)%6e7V;kK0OqsQ2uEu?$5kh#;ifs+{=sK z){3vI3y!1|Ams*mAFPj`9&UlBKaBMC^$id2btSOBF<;LDh|xrH~_3?<|qJ<;!CUW}O#OXGx9|LZE4e}C;C6iLXr8i#fO&JFFCk0O!5KE} zn8XasJo=*RjRIJ=>xV5UoB^TBut60F;|cm@M)8G`#p7xcDv>SW89<*ewgYcxjB}W2 z#HPg5Ff=d#3Lpe3fOXvC?TkgX8kV&|kl+22n|8a{<>lqgVkr^9-ur`cOQ(<)X%iCi zZ6Gw6DJEa7bSZ4k1M#&89l>q*VSl39*5vF9&@<&V3#+ZIjfzs$(IMaQFoj8o{UE^f z2s1D=WY;cf1j51bM8{&K>1UiVdm^K^Yw>co95ml)zD&Op!W@7%lrsawYf&Ix6Ww}_+W>~``*kzWA;{R5Y*8N>`xLP?wo6j>zG;+{Iyo>FL|GADPEzX#F^;sU7VPFmkAdU z*-S#?F3xq}QjF5$Tnw3ZGPW5FE6I2(f&>le|79?s!D*gSb|;@Och>52Fw0kc?n;T$;7b_S)~6rHi3ufxk`0nyVn;yG zZV03RWp299v;j*PzlBeLYgt7+^k=>ad5Px0DZ4FBwcb;j|pTa0` zX6qH%l;!pxux$uK0fN~t5xjtDn^GWCg?={7gNR5!@;K!%vUwn8S9Nd0Vg#S{QrPadq8})Wd_CBzI!$X1j zy_I~%q?CcOFv(t`Jsa#pKO2>vX86;6|BI;pT9HVcWv@zxn4m*(WAr5vakPg4bz)qA zb^ZbOex0yUA4G18#xUFbnmdiV9N8DTyD}4>s^yObWrWkDRf=ov7Avj?N{X|dJm!zM zUoR|APcK2PTk}NMlF(Al^2-Ya)M--Jz?S$^){tUTaFq~E7hVK=ZON@mUN!OO`5;FA zc0Q*ha1pC+T1?AO**410>90~e!4~_2-QA<*@_bPc*txPF!Q6GvTZ20;BA@fa?$ zo#9_LO*+WA!1amIH)YJ{xjPwm(#m3Lx3+qba^8fP4ZF63wSn`cdsGJ>W(fK59z}rA zvPa#BQ2`=%B}Wq~fTx}Cj4%&idTR<}V_B1$@{q5uuN!A--Oz98F%u1i;~uP2&xt{P zSkeHWi1 zBVxB&w?bcWF;%L31_Yb`Syc1~AF(1N ze7%=~32i!tYQg$@*{l9fSr0MpJ6q5zK{X0u$M28JhXn?fOJo0dz6d!#n7qe3^F~r& z88QIbqnvS{IqRI`gJ1`NCS@>Qj4u#hDJ#?Uz@Ij;)ZPLYI#`%f22CKLY#Z;RCxvTg zf^4z5KwR=hjJG(ATm}*)W0B&Mn~ZxK5KNES*Y=##)n>Jc?E;|=u+QOS&z+qw6=$6t zvwm6od+aEIvB;UGbKe-cCdb3p;9kFT*M;3>`(LVsQk#o2D@#k}Wu|X&OL!DR^|Fz> z2L}gb*vEBdwPoPbL+;+BetNpM;oehtRWps(OD&$=g<$^~@~>(RU=?VRZ%OUpyBXA5 z=pQvYk6THq6{2_2^|_}CC$iZdzXM~>ehn=W79O3n&t6Lv3}9@i<}VR;L0rp#K7IcP z=w$I$xFU*&^3WUH!Hl|KKy`+Oj{DzXF6oO!!OAT780B3)`%* zI~Yky5LN^=a7BY?YWv8sj#C$$IJpr6Z~JBy(M-u5sq@n$;-E+_!w1-(k|L_?C{pA3 z4Ju~jqQnI#H%va+wFo=6f2?x}xgYor(8Q7eq6l`ao~_Qf;WVLs4xNEhpuedQ2M}C* z6F|oK*YcOM4a}LZxvgbzd_(o z>P&qlNH`W5J1NWjEc_d(2A7Kb30u)A$*}wVAXS0R@}R2?9oOS7gf=uFfWC3l#R)ny z^B1qf19`aDron@67Y7@ay`^dDdBXf`|0>6*l!j&LnJi3CRq7ZU(uA!?BhTm7+7#b$FYYn(Z5FQ~JEh7Yq(WQfO%7eh z?V6U|^M0U71^_q81#+!BzOri+7^I_FVApouugll>tqn6I=G*MC zx-r`Rd6;n?f)=4|XIO3!=TyFGYSK7ZCQJLNNFp_Ybmfq3Nnp1v#@RQS9(vm$N|?W0 zXsmX%BGc^CxIA8{|Nic5^L_pWKTZ$*9; z$~Qj8|@}hUpK)vmcfS~?IQTA^M(+ksU&7tf3X0r4C z#h^2vp~l4q4sbmu6}Zv303$ZD-^Cld|DtEsN8a7~V&**rQxnh8$(e31zVK;4gnk9+ zBy;~U;x0{=3nZ4`GBXw+=fcjKBk#-;7E_HaV4j$Nb*F#8t27M+qovLw*Qb~U3rn4} z*A8sG@_oqGbhiVymqtu%+KleQ)}#63R2wG4okq+B;!5^eW0~Hs$gqk@lJk&5he6K~ z8)G}JyR=dYiFhw!fWG((OhrDIwV`68X1ApSMCs`IY_m_)9q8E^&e6#Vjd&yC$A#qy zwZPo0Uw_YERqQ~VONluSCmHIxd#M!!uHgXM#Aoe|PwN~}fjj`HIu5ps*zDsg?kWP; zeFYk~8js^zEcZ#UU^U@bD1UYxgsVYs%rat^(~>Zo8M5*@M{{pe2>FzZPI8*MV<4mqZS?wP0{-V|l zc1uso>VEEg`c{_Df%Zi`s4J7l0rpURXxnC_OWu7AZVLuCJ1xfpIaSQp8=91K`s&tm zv!Pu1b4gYA_2fN39-Vo=++ll8#VmanI}`wAx7b(Q*T+&r2?;-c#&>daJNf>8dfcG{ zNdBJqTO#1g3*mon=KwM}d9CmJ#q<(#k9p@mVN_OsHi|5r$bRRA>P~1B^&PCK+PJeW zTC(O0%Si>yqIG~#G0Gq0TAx$dpSl&eHc0>Y?10KoJ3Zy|k&Cs=GLsyYka}NU;9yJ(pp7pbH}6 z?!Z<%1E_$9802MDGbW5X&c;fvUI^$*_z=|mh0iK2N@FGy1*L%t5&s=*0s0Ybi?AOn zJNfkTJhvf%y*U<&j8(m#o2MLGnmABmm~$yfa_<$NFZ)xFqGbIi<;NIRf0rw0U%=p8YCnCa`Q>*t*E@53GFPJbzln2ng^i;t^%bRmQ99pj9+h5jd|*5&@A zjBwR~w8h;aLLL4{-t!B`Cc5L0O>%{Nm3sbb?FF-`;zE1u)TxoyS|32xmV~PTjJO0d zg2_F=wf%^b!0)FRCv_{%(Bb!eJzxthYaHJZZRVwa*Wz!%a}Vd4jC~cF)?(~t7Yr+pG5yRP69)oTt6ikvpR`K z<5`&_|A5qcFiNGv$O8!JfervGuY+5OcD--qh#t+)7Uw^2+4aJZS~#Ve<*qV3)16tW zT*GhhrXllg$1BT$M!1=d-mQS0edyVEBtEL2Z5|vVMcH@kOORr8Q9l#ws?bVj{9(x% zwwxMI|K}NnVf^FnU78mnQj7|h6tVPOGuI%dukwMVEsVCo^>n0nD{HoQ5+BaDmzKhF z|F40M$WNfjCiLCrwJ9-gv3Jc+#CBxqC~B*GeeS}&T>Cf%B=u2V1GUZae~txIDQwev znjiR#r-vO2#U{ip5h1Dt0H#cEx&6rmzps~7TJD_QOB0nE<+rc(7?obuYz+vP?=B!@ z`s9L5Lv!cO;O(Y5h|L;}FzQOFAe9Z*&I9!+los|68 zm*LwR*W^{5r@~g(=E&zC&wI=lT27@%zEioV7`nG68cu(iVw5o7dhFGp{Y}XbpkEra zo13$HlRB2OTO%ZI()^@k*cM@A-*oTIV91bik}8$-kK|HU;{wRIwY`Bp_vn=(1yD42 zTuCu>O0T~ED3oQZ9Su3^zXcgFoX)Py>qmY^6B+6L&kfN(^T-#iVzMWl6`pNE!e1xM zia+;t8uE=5fqs9B@$mtNuY^U8su)~lGqs%VLza`;rG)Cgq$z;VrGj4ec1+u>g%ktu z05I3zq0-3;&^#fL2`VtyD7^a*9`+C5cy+UN(eQRcVB-~g`3FV1y42eKKNKW%SnJ+a zX@D;9-=2AGugfiB8dS(^xFhx4f`zP{0QfN)m(0(^mo*%qc-`o!kNP)60L)yP;TyH| z7t{Q${NiH(o8XdjUB`Fyz)Lj1g(`q!!kF(})eD0$+~+Nma2 zTET#OPbzQ)>gN^SKMx3CZS%JvnEplQrYA^+O#$yOj>{FEIKO?ZUH7HjC+3YiQPoTV zP*eKXO&`dA;~w2uIkPbG$~|NBsi%HG(=#~U|2Twg99ZBRq&QF}C;VA7`n$gZ=%jQ& zB#a3@X=BPB*fIc=LXV9z^}y9OBY=XzTs(F1$}`n8(3NEV?wLTF`10L_wKHF7Gn)qA4Knu3CY zXDm;VtZffa`Qpm@o+Gt&1bKLPjGDa5IyNS2%|nkk64;)m-hXZycpV?;OAFdhFc{^& zy6ZRbh52PDGBQQ0qdB=U+_v6EK%-`hF_1k9R7eHy>n?cMgw}*V=?ZdQz@a$YVZU9w zOYP(}p3PS#VXF!Xo|H6~dO$$=q^aGO%V9I7GO1X?hqKsHLI>KAq&+MIu@gOH?5Uhz z$0y(CT-aJ!dkQtVaYCT>@i+AUnLa!`4CCj1YU{%$=%JWXSU^EnqO9q1XZ{2OUu8nN zfabBy$h%wPaogLMolsDd_wj0$ALxV2+U9gaUWPZ&OdqebR7_y2Hfg)gYwA`4;PeAd z{hCJ4J*GluO-)T;D*$D*ku@8(4tTybIO>iVjxC>lZgsZf%V-?^Qt!&SHhNjiFP|bs zh9#N>&NHTg-v_wsbp1lFx&_fqn57Tdb_v$Y1~7?6u@LaR6>!FNE(FDAVny9KaKc1+A0mn(d?;RTxm!&y&Or)$I zV>>_bm2&pPgfR9?QZ( z_Q;!_$AF+u1^68FEfMLe0o$?4Sw_^^hMBi=dYzJ?w&f_d+2c5ufwE~8Z!@$gSqPnP z-vE-BaQ6Wj0?EwrOBL~yhnc=9gkYVo@!Br6l(Sb8_E%V4<(4@zSX#NRlUmP`$}{6B zle8)JA_?zlpm+8h-Gezp`+7w+GcDI_=0MXW5dX5@O%<=4s;Mq_JRkuAom4H+R3;Fe zB+4aq`$J6x&;wf40i)gMC6Jg{T3XUNE>EfeFEG-U^6}%h`0Y)+1#c|85mSs*`M_tRg>-pbTzL2E?8H$sPtq4u zwcXpeiQnJLMHWY^=F~wLTEO9*+4?3cu_c}1l@VB$Gnn_NDBO-@gY4mq@94S894R@% z@|c%(xT3>qc7-m&AN75+@+5k;6a5R21()C~a5WHbE#-}!0}id{J@K6dWB02uR6m1I zGzlpma%Np77V>C>l$s}9K1H3MpC8N|UC-B-K0dtz0|R}1(y*iMMcf3OysyGLRjZok z^5vJ_*E^|U{{C-}O*{8^&$JfNq zo>fh3AeJNNwq2@N$Wb#y=h^4k5jB&Ie;7^^)wIL6%Btnb{jfXWEblp^90n{wyJ15@ zma%s@&B7EHw$azlrFii|Yv(L=uU!0xv^b?95t$+0tJiG8-_YY%9x{jDVVZTvbPOxA z7w<5;OYdzm%b+6tLkW!c$KW*|pcmJ>Ui?YatvlgsSbH~294#J9|Kkt*)ARlTBm4*& zgt1~5q&bd|G`1M*2cD{W-`E`)QyP0vN}Ndd@D5*~DeZRHD~cKT zFHW>CsIC#M)|6d5qnwnU=3-XpLqKRub*JoG!E+ZO)%qk46_p#4tO;`<8q{r1jf#oU z0j1UVx={9-X>3x#iz6I#8*WxjO$~%>jMH+l(z<|afs1kys`5LXW;KpDFaFxGx5Rbd zv5VlZfGeQ5L+5h0qM$91zT9x7m!?hrLEOXQX}?72LpM2t&<8MSp~eRsxg?VG_60`T z+er>R%y}f2ma-omk7_6O**~kc!N7unkelC`etMAlloB?0y7)Lu<_SkFDt{Sq|L;f5 z9(Wih8`o*^Rql~bvU)T(xDuh$9eDIG#rJ#lPxhEh=ok8TNQ-<3I5+euQuT>#0EX(= zG0+aRju))0#wF#)Dx^hUlmJm@;WOjzyoJGrh2&Zs{3=(GRMyxKXOqILr13x4Yl;aYGpWlV_5P^ zf+!)c?hEWN5keGA%yreKa{kl}g(w_!=(xde{pI6!(>aI> z9)1I@q2}@Bmg;IONL%46EJ1yyxf~Pf;MAVK@SSRL`9!&9kRW+^K1b7aC1>AGQ>?P( zAW6L8!dJ7tv-62y(}R=={f_BCx~~A_#qV_Z`Y8)jGEU>D`8W#=xCo=kq{Ru4NyF54 zIX+g_RK9$Tr%O$eyJ>X!Dn^0eKd0L`bo)Rqy{fowJHay%5iFni;7b=57xOu=*P0>4 zE@Zx96>0jdi7~Ga=NfhKoL>udT;2QaZSBkg=$p^fM0@O|=PxT0 zxYSl&P_}ATJ!R1U-@}wQ27>X~oqp;Zjd!LQAGeaG+&DG8rIOjwxI;|7sp*I`Gjs~O zS>hZBPd8njowg77cb91a>xqvKF5WyUk|bbekENVjyz@$gzm(VynUSiAcYCR6St|C+ zsg0fHP+El7zHhZ!zl@G3hKjS&+!OUA?TPuxH$H&Tbhfwe>*O|oa*K%B7gMAY(ZDdD zy%#6gpXpDL$W#*I$)@$y44s6YRSXEl8p2#)F$r6J*D9`6MS@Mz*#dG+t7mF6Y)&rU-w3$Cw>#} zz=#jv(-ROO=8LYK&;G9HUZXgc-(*TQN^a%!e zZ%%dFj@#JU272iQn}-OT`gn=^{r0nen&dsu&oCqeDr+&|4MRb^3(Dpj&qz2zZ$Qh? zVdto0U!YCvuVzj7jOW|~G{im{sI$s>Xfz1bAvu`@$>FQIFsMtx6-n-U2<2a!jixp# zI`Apak#q#sMIT?^0n8t!dMon>q5qlX?HvbaNY%e#L=z35_QR&fW;*6+-JlkF>$ZW^ za^2Yhs!gpHw)>*%KTqL3U9=Wepo4BWHZQU9){OUqBi?JfJAvm5FqFo$7q>UGbG((j znkCfroqvtzf|ke4h=se_s-NvzGb*YTla>iPkDMIftvfN(+sFK;dIPZ@?xJyr*;EeR zmp!(3l!}Pn2S;kB^Y%&F4LDOKl)dBycSiC*m(i2`qdgG6Et$=geKKiUGz^^j}9C{>@oRs3}Db6hn49o*iT_nifa zcHUq@K;!^4jI1vDL_7uKop%uSfoFoW=EZUD>t)47cM4cQqfdG8;6=lZbPo@b^Q&N) z576KIsJgnmKuCIgzSFqC+kZc{%X&5Lm}j7j;lyH@nTnIA;bmKTNZ)$sR*gasEyp-- zFU_AKd6r3ps^t?E&<^AMUnc{H>AV=LD}P76hD(gy4X{L%@jZu+(c4Rq<&K;*yEvvI zZY|IOX{lj;-V!o*G!AKNOMAt7zqw_Z$58|V0XkdsMO>vXlTKX-R1P;k+ecM#%i8)f zGbsE~j;jj;QKG1^ZggP$u6VARyHT3XA~m`B8U0hr>KYkuGUF8sCVf`Qp#bb~x6Y3= z&a=`CegDRWTiJw~pyZ|K%nWI`E1n&GIBAO4SUgw=f09Y!6AxL-F8OgkQK?S>V0VnD zI~CD5@mJ@rr5vg8H8vOV*ik2Ur zdqIcV+I9<7=%R4GlsY>W&H(cBt6oehkOb$e1mGkYg&AC`v zSvffm8yR%xWsCT;jE#-;MeGR+3p+Po$rO6T^X5IGs35HU z#Fy=Dqo7Nt5pW=R5%}hfA{eNcW-~PW6RW{|7wpm{rh*eJnKNHbZzL~g*b3((A~C&!|?ws!2BmjLu$DS*)&K62z%JY^%K`k;zdhWHDm;-HXe@Os${9Y}I1 z(0uH$G^b{N193^29;fa-v|1AZEloS@>^`>f;;tFuEVaCw-?R;pWS{L8^rJow5&R$E zQYJ!mKIE&`F+USaD^5*^jO$7M=!y?WEH#M1pz6uRMHm$7P(^X4tnksw@^Vm6kX~yd zpI$R_CGwi33n{f?ygbLl{%cQmzqWVTdF05TK?6AhikfFW>cjoy10`<~h}L&z6}`n1Y-jD2VA%TxCq zkezP0?`&lvO*-XNbxf7%pBAg+4ThNH4($WzerYg}oZUVu4?PSSCN@jw{;C|rC&;py z2<{o`YUnz(>)%SYp8@t$6>Q77`y1deaLxZh8+SzyyDyLqd7FnMp!AQN-iBKjrBYpk z0^^PPYJ9G9D<^RPIkcpm0e**op5QJ2pBs1+#k)y#6(X>oWa8HJ#^d0l#h+&l4~-&f z7|CZyoiIy&X8(uh+4xQW58UowQD{9fG#ocfh#%TNXL(n`+-b!mhjWtKhW`Nmeuc5X ze4uJvUpZRjnEHs+BQHJvArks6ocxvihslrkDpSa1ji;wvMVF43Gt0%%q$9-s7l6i` zV72Pm*Wa|;aF1SIWj=-x)jD;{W&rB$U_zakHyS){I5H!2v~TVxkD+N?T8b)KZ1xh6 z0)bXlydY@DV?2V7w*bQDEFNkmomNX|op0&sQ#SVP)Mq?;2~(xt9}HDHmej zufxFR?E^>@18MvI`g2`(iFdc64Bxc&iuQ2m<8#yhwUT`p(eEnO=N-h{XNSa-@YGKq zf@!=2g@=;=liGpV!>6!~NCpsRD=6`@N)#lu+8LV@{U1;4DMjwJS?o0UZW|%>pE~Ye zaT4S@c~90c?cV3>Y2PlVKhi$R1Aq!aLSL2rU*P=%v_u0E_vFv6o%mF0a})nT1g<05 zTTt?!=j+!_+<0Mcivg=3J63Df>C{tusQTP2B|fye&5-EFp#G~Qcio0<8QP!vjdjfo&1#0kOe*&bG|XHnOyQX_Q2r48YL!T(}YP6qDknbtZf4R%UOU=FfVv|$- zX(bHtr(L$YPk%xx?K8ThTKp(IO}8@F2VHGTA!UHmJ~LYDipxCS?8Evpq^9S=*w6s? zC7syJKrvPHBjunS&M4dtRsK5{*!ED%v+qANOCtFSrzjPpOs(#gp@?Zrl#EB6qqlnI z>)WKS-?w(}>gOy6)MEbQ{QPVA4SW$Xp+VwH<_=kAS06sgMcF>^t+$He>Dz^z8N4=r z*!QB*XL$=Guo5Rvp6u@KhUz@aGwjOA z2n|(%>eWmr2kc}=9j`MfeUgYF2B)?l&a9uU-snHp-Wx-BY zoF&aU*6?fe?6f_{GO*%Gb1Aal9w{fgOTGPw-2U)j^y|Y6WQ)oQyLG_H}kP% zT-Y_gUuxuagH&hOIV@f?-jz8U8On$@8wq|>qwk~DczLYTBdh7cyF4& zjIB3g9zhsGN6)}O08?ktpt&9I2@MLm)5&Wwe2|jTYPk8mQkD?|1A~x|5TEtL3HBbO zD-rGGhhG4CfU?R2`IKHr>2KRY)()-EOd*1RO5Z58;%7pN#=(QpI3@lp^SU{AD|@*t z6B32!(GsLR+p4deq2jc%cxa>vBHy8Uj=Jg1ac2IJ-h<4^h;7H6b)yNwc47&_i$bm) z!7QN~9Pf=4-L&)wGEsYGIe?M4oPMOOsJK-_q}FfJrQBDH@%ZbjOCH_4Z|s7Wr~4** zysYLdX@mI`E!3SA*e4ZfX+wc%QsfE|a10q?gKlsRr}Fhvn1!K#wjQ;0blN@PO$PG1&Xev~_T8?&j zlR~1!YudL*7w8`{zHp0MS$TF?Wi;|q*93q+kt+aNL18oFaQw&w)ZdafA(@i8M|yh~ zZ&j}{%t=%)8kptd6dmhsJdv%lvNemu^qKEAPCI%sFTORI{nYkv8g0ECn;-DT;gJ^6 zEJ;x&Lgc>4aSj1WF3M3B*rf*t(cP}XOpRVuXhc-;XRO&0>+m;*?9XUhRL~7`b8{H% z^q0b&;c186oo57WXXDe-rg7NTmSCb3L<_n*nAa*b0o9FF&ciCjYQ@^3m3K3;lxe@T zMZYe!JyiD!zp*D$5r}dE#*fLR8gF#34SqyEewVDXKM*7OG{a$FF~ai28*g6v{;O3K z79lU)COHEyNnEmx70=}mDLTC;j{@&j&vttA+-6a9~mOX5$KxKsD?f=z5~T5;p~bj4_TF^Y;Q}N9z;WNdA6Y z3RofIwAXuQNp@fv6ix{($g@hd8nx)Xt~EH8d#VYo5H0GvFD#hrg+syREC*P8{N$1> zT_-=s|BQ{M=??Mr7y||Na}*;-cVU*GxYt_bYyvN?p64hfXZ{t2ruUQ2I?$XKZRwjD zJGzw$)YV=dW45e0owrQuBwKNr&ONF5dGg8h>1Ua)RM*Mf z9I#a)+o)4a8msd2t|3L;V`$YEaK2gTB%LHy$_SXn> zZ+k?p`s;z0cRph;b)&n*^sTg!mmE)}){1e;cWB~5Z;s4JPjxRV> z3mr|X`g-F*4^6k!sT@v*0S$XIIfA8un;23WUmh02?iq{a0-x|(Z5jud^WsshCXe@? zGnjm(t+<~j8F7LIKbL_zN3j0>tuaxv-xG;u>pbJBr(~<@U7Hw_DjJRSFRnki%jw@w z?#Hstj%#bIeO5)^|MyW)MceF7=~gdZ%`QY{KSf=g;5mD4`AkE9u(~3e`;u zt%~+?pWyr|>G1hVS8Q|Q)Ko7j!(LiCPHr@cfL*uqvDk0l#s2t<#eLNsTV{)|N1Gng zX?j;Q6+)n!1L?Kmwc-(TFEEyAyD|Ed>nr|0=9=#wgidpd_jM=-@#y@m#wlt>Kj ztyWHj9IoqNZw{GA+8fCcD|Hafbw?6?&7!earF5-^&A3sld*Lf}Y=CGgZlxc5|MprSp{_)k-yd^G{Agj_#lrU!x1bR^aswtk zFCyYXMkfqPrVo58pbfR;Ou)Uwz+DEX8hUNs*rG|W8Pe$_YvmK%t>5dt1m6nVbGsd9 zcDSEs{1WW%RQDaStxE|O;0yCc$Ti2CO;wu)76261Ny}2O{ArWc) zF0^jO=n^A;qdtwI=3O~gR+`Oi1_Ta|eE$5~h=1;z$2k78ezs^SF9}aOj`NpUUUk0_ zTV}nNqQ+nxZ>tf)hv{5;(k;R@l~NbZZ*KEF>UE^ju+{r2MT+8(Yj8X^cY!K;+hw93 zRt}ZbzNbVMf?=!B&R|xFD?AOA?YbT8P3eLAFJxwRf2_Z%SNyb3xe|kkdyqIxA1}9e z>+$%@P9RC^JCJ*+%dD8!G#9|l`1b4chQ4n~to>kIUQ-dwp*O%hWrB%Ph(sIR?~;9@ zRFlopL(V8@>0y^yoK|Aaw5)$UAs|B)&GP%!!}f34dZh7LPa|nWaPPm|2$guXJYLyB z%7PUW2o+zn3Z?HE6B9snYd;7iI(g_bx+{+B-D<}hC*FF9QxWgA8Fb{JW`Oh?nHQPi zvEi|P1Vgw;mO|HG@B3}5!~0I%S<+Ihm%3IWd=x`W!}Z73-fC$GG5TC-XKRQI$%kP_ zUWj0dN74U}pM#?@9_rB!wAC<@hDt}KnAI~&UTqvZFx3a}-fPRj{?k7h4-KWgBWw6f z>7#O@IvJ7w+m1m%82Mw5&o(fii7zS3@Q9DS8zrGRvvo9E%@_mW9C87on^>C2X6gxK^63t(I20x!G>pmbvb_~WQevMh z5-i|84Jx$lm7kLiTVB;9qxE_@_!8n(nT#`G1!rGml673X)qW!b+2TjlT_tNoV{|B1 z?m>ORwZ__0SdG&?LuK7$YR$Fx>MX4TJc?S1FTt6Pwhv_KsV@8HKsTo|?U^C^@ueM9 zmd5k7WaE*cqO{9dg&CcgOa#?}x~4C`m~`MLG(Xpg%a77qA4uQGy)nvYfYl0pL{uu+ zR2zNCF(NxAbf7utJ!Z6Hm47?GjGkOrfwoOZ8FENAjlGV>_Mt&wm|>7|@KVxnlA=)3YJbi(^_Edf+7LD*ku)LG}Z4s-H`~Crn^00Ih*B16 zU>V7?tF%2obz9vnKqR2SR+ut-Z!yyS%JxGTMxOOFlVYXoC<)e(FY*%icA->5#iZ-s zx;JAz%2|LxF2eDa6sCr~%ICPBbQvG(aXHN7_C&HK@} zGV(M4J{P%$c!HsTd3J$+x%2Mw(nwmS(tH}KBgY1NzmtjH|(qDtN`Pl2mcu&Ot@?!l?1=w#t;#ckX#c9|fM>FxCtB`f2(;eD}W?L30I zI-ju&#IBAK=5wY@&uTIe$Rw+g!j#wykCg}GIC2{X$0uo#I`4{7b@uC_jXlPv?>!Vv z&7%to#4TKrZDWeoQAOvedI`M+bYp8;10kKoNT-42y_1pGmp@qdM~J8mAVQVEhn7tH z1#o5M9VMq$M-wLCo@J3{IgD7qYzb1h@+;2WyBqw7C?V!>ySSnM#E5$x=Wq+$mEvj3 zb#_jWJFpISIFokqzVQe)rAqwyh@`VJMRx#en$G@I%lhYX{PE5A$7m;ajNy*a&erY2 zR5Rr5D*7|^8!m%|Zqb!o6l8h(W3>3?6D6z-1)WeK_|mSYsUALi};WYkvBBeUMQg^}I*j5~3YY!^2;PmAjSn z(S4eIP%T`xNJ?0SKZ54d5A`pk*Wf)(&P9Dl=wBG~3y310xv|^sKB_+yrC1j|pV_A%omwT?L^Gl+8d+!+=9~1FMJzN_W!|sqZ9j_AWP}A-I z9wE!WcwW{1Ty67iY(D|St6Oa~>-W%GD(7_ummLe@lnE}MU$Z`TsHYA))9l86578)& zaX8Z4>$w*_NlhC5aXlX1^%GZ6MQ19^6i)aj@Fd37FRv7MH6;>!4y(iImCD^i6n*hd z`Vv^3eqCF>O@K?D9rY6*g6O!L4jk3mx0(K~+@v|ZfT3nUDKGe*9)C(t5g#h^R<~KS zxxhR&57QgRJU(DytJS-NR6{SX@7h$vpO{?rkuTw7&yo@rmXR@KuNRC(65`s5G^_Oa z&rgS9WlH8S5-|t+8Ag-4f0~lta|*34ByHE{nl;DXr!pzj(3SbB&VC^~E@X-JYFJ+S zE0x#tB5MRI!mIX?mYwBpCefL9{Ka8VDl&! ziB|jd(A3q7Yu4CpTP^4(V>D)`v~zs0Bc1(_-(zfu0lJ%=u}Q`u?_^WjP|lRKtJsn6 z1CfqLPOY)HS=3Ic^rXH$JSA#C(LdLQP@3sTW-BhEZeB|AQMULGt0S(Eg@_th>MTnf zthYhyNQD<*y8^G;IX_}%Um;41@(+wb=?gkiP%W65|q8=BlN8IN1nU(yE7X604B~sn#|)Sq%XijZ5(@v)om)#)%x*jk(wg9iaq&+ z%N7aZ~!H{}Gm%nmHU$9tQ( zR{(CuPIW1pft3ZhmlKCnEL(<-%CocMS91%*2VIH;)vDs0Bj!`%#OppHZT^*2?Zi6c zh;B^!XT23*+WNMlYNX9?=nDL-*sZ{lEq_Y#;H0v5y;$xnM`7ZY3HU9)B*YM75A6<5 z7DTuw2FNp6v+Qg$D2gw@C->j ziV~kZB6g^?QTjZZe5nG2M9N$o1b3CvHoq#w9hGSZf==)#e~+0EUuNHICCtIwP05n2ZBFe;^|Xy`OJ{a zIa)5~nVE?hw+HnPzV~FF3GNj2*U-=aGMtW%Zgru~5!&9xfyn)^d%q`Fw>9wc)ZSB1 zZ-7$WkGDQ@Qlj`fH3FmmW7?8qE(?Z!1e^4+aASF7fSE9Td?+TVCv&oVT7+I91eZna z|K!HaPm1r%iQ^?Nv0>9JgvIkOs;4_TU!^MF*Ledo}Yw1Z8}<74F?niTHS5YESvBhnXTK6nZ5Cp7~+37$EQVz`957>6wuB{FcbQEDa~ zu+8yr-y%GEGg=W;{C1a>pW)8`00Tx&lQuBHawpyX*lE!55ebuu5Yj=7uAtJ~s|N{a z6S&Ron(DGwBpF*g;*&{-4vMPwpXw-rPY3 z*cH_QsNRmbeTGY$hVKI3wC_h2nRX2C*N9KGJD+(IG&;(6T%nZ9%5agWuboviE`LES z{Nnfm2zXBO?IYiEc7ARG#=^@j)o`Gt4##K7T({~c-v3}RpAV61Mg*v=W~b>Pr~6%> z^@KPFC#t9nuw4&FMye~{!{+$R$9LPMftX*C2SyI}oP7QIH8C-9o|JIgC!!xK`*Y}8 zHu;rl47J^eH<20J!1H2humy&~?#%I<`Pv%^r?>$)BIgH!CTB6Uj{kZA6O+`Shpa*j>4v0{c%RM z3Xus%FWN-=tt~xmJGXUn~ ze7(z_d1#2!x{5VQt2Q$@vb4BbN%wW;*td#T0bmJ?XlTMRLXQS;8c);qC`5avxx43_ z7JxpJ!)ll5V4ZaxCaHb_Q@`&;KLTuUj<#sTK!-nBY~R-*oThvN>ZV6@!3odpDc!>o z!uwHhbp#ISoX(8lHJa4pvYH#a;d^*|(Xv#)@pwSM*HN7zPE9|Yc)H4pepfAx=xH!; zj~=VJ52T0_-{Qc#k{?DI|8eA3Xar(qd`Fv zIhtkEC35G^DNo?TsLrW=#2@<{3O#%s%V=AQ1_+RKmw*$wHSh(lFmQBCExw>r`?8FN z56L_iB06#Aob}$PYAUyOl$9r!O0}gw8U*JV-+Jl@lo_tOWlreft5rV%Q)A>`A8dF0 z++^3YgwAMI#oGy7-7gsjXzOxwvK0gl-c!f2spKz1KhWp3(NR$ftO2k~ zSoEO*mH(0@|^DqC_W)X^Y_)N4!r3ZA7xHr*^n zJh->FS3HL5L%M9@W)YO!>BL z_Nli8uSr87l(9Dhk-DF1O^yz47C2Xel!9#0H+q8O(*4?y@RhN49$sFw0uEOcDypj7 z+}x%nC&RqxN=iyzmI1rcQCccw({XKY`|T(Zf1pL7FC%r7NnHpZK^=#@7Brd#v~HWn zHMOQ4;Sf1(T7<{A4#AXe;86Bo8d;jPPK}p0R;$D1e8^#|<`2ie7R+9pUE~~+T_yj{ z2%fdUtL*UYag83uJ^|$GNAnpfT$=){#A1$O)pr!~0<&oN+q{ zi`PT5jN9J8m8LEl*Kz8P4Y3KBUiG`EJ4Pq7My@peibF$PUER*!9&SL%f+ocs;v^p= zqeMV8DE8*!Q4aN|>S#gB9=quph$jxn(OiD`c9{40t>?A;D>F?4b)ihvC6T_qzBg{% zfas%wCKFENlBSLhNYQh1a)>~vx9NIgl6AK?K+yBUv?g~qrrGtRfIH#YcO+K1^lfrV z+mcM~=i+rb?}(c%ahJo8%H%4Y+$am)YW}qV)J1JVaX~@BObMUgDa#yj_9Vb4-2q>D z#8aPQdG&jfs!CH#Vx(fazM*+h+E&)61$-3-NpkBd@o^TZo4 zTvjs4NtaVCJ09tv_QlQD@Q!sDT0F7$dCvdK>20Pc8%e_vPp5Hg%jeHBh55Y|9j;H5 zv`I);=~_f{6O58K2lvPO+L<7cym4>2r=~2wR~7x79PBaWj7 z561^`m-bo*icxJQJ^%Q>TSI;P;}phe+jCV_Op+0T_)zm->tqSz-F*2LXRwYk0duu~ z>$`vd>px=4^ybnT%yqeu3PWGjR$O8;p z4hRC8njmcFJ?7F!S=JNu^+bQmxBm!Yu@60r(VkRXP{R5?CnNH!Rw$ggUCw|1L1$!2 z!l~QGu4n}`p4aIVZ|wcrpD_CA<0DMxk9>yuAY!%>`{C-{Ho4~@}ROMCuV|5imy}k6w zLMKw>yI-ACPU+sA5P~`djQM-@7CrQ{0`$JIZ&AUS2%2C*{^Snb4b?b z2X(Dj6V zP+D3V%wyq}&cwiQ;G_d&d85!iydTs>&<}3Bs_ro>E#xGutOih^nJ#3J)Y^e`=J)}nLdqy?ZqtoM?W*^23g)HT2dK7of_gh?GpkQB$ii{yHm1% zUolCNJ9u`^lX&>SCf)In1<{QYpZQwN%2 zB$_N+UVjSV3(V}vv++$=`S37Ibx8oy_@{3x+IM2y>$k)q;PPbNVnh#v*rKD)c^+mP z;>(Q8?noPV{pLhb{>B)BBCK$eWq7vh(6s+bMV7;vT{6VB$kDG99TEE(*Tp=s9{u=2 zFJqXWifSt{tg+nl^hKGkk=l%zwu-!TkuW(Rb z(Am-=qbnC&30jtfPKlt)VKeDqyZ2o4P9uz`4BKKC;@--SH(yeE-iNcdy`mr+Z`I!I z$esJa12NI?=0>^ezB$p?(ic+syYAu^k1brD>B<%i))(TGx0%oxeGDog0!cn(M=USC zJVfTE;5ZJ3hiPwLSj1sPa>@0%&~^){?(PuhC>izSuRsIyY`BFi#`<=6M1-LtJGxbL zR-#iz2cRVF@L$uo5!(12zy_x*(C$;pz@^CNt+4J>$_ieah_m?iqMJHaUhqEVkt&emP77po!=>{xe$RCmRUO0{K805g++MThhL0}l2X{^>ir8R zPN+ikQgUCXHfU&IKsXdDcI(z4=z+w=YrAr-$Y`$I;_ohS44swxJOlEoK9YjP;WMdv zaMGa*yBiL=o0~xKE!+h_wen!)zA;=zSP^0v;#!yhl9uhc%%-!CaC8@cnNLXFViLQC zzmJJuu^4{C2KL8VM@Sa+*tH`#Edfe5TStm;&+i@q$ocJJOzK!6)+8i4Xf{87XgUr|te!(x?^5NJNz*?LR5!k^-w5(P*| zVrS$&`#pVb3gIiHX~}na>BDCPt=ApowG^Au(~p01C3lR`)pKiCRYvK?L_;4w*X!ni z{G=lo>rs>P5$R5bf5p~^*0X31G>*J53ZpdL{?4D!VOYS3NP+a7v`tOew~iI8>wi4B zY2NYAHbr4X3;3+y@XUNG@#1_imjt;nz;Uy#S?!>q+~T1(y=0RT`^jVxnFL&Z?pKP@ zRoqj&Fryl|J(+m+5Fu8`Z|g}0@WE;@*UX>kI6^d7BmjWwaMk-f;jyak|Q8l z-Ld7Z+l|w7rEbQzK7t?M8G#$WB7~n{RM@q+0fyVArlzrwu_k>rHeTdLtpjoC*49X& ziw`bN8+W>Ae^r`B@gE=;;Weke;$&{U=DwoIZCD%w0!#&g5PBUdT3QJ%lD&Ih!|kt- z6AoSVD?fr4y@p148(RaQ$|H0Cyau8}&vYp6&Y#1$KZ6l2P+S8bE!_b;XL4 zV6Yf(XAB9n$??BLUh;Kfl@xMs&dN5|Fewy({$-MSPR%))eFLXkV(AQNrRqZ(Py~vyYSE{N8{7{B0Rofm>mT8;N>qP0&thX^5k-S!RWi=$Iq){ zu1`&(9W`@5kbTx>kbdKlW7fXxLBZL`~4KXY8}m+~;%F<@RI@x@cC`AMQr;T)Uj0HeU`PNO$GVo%ykhNA zJ}WlL$U9klNwSu;vPP&Xgt<%Um2K>yd=|&Ds+c zWEVZR9lZI=Cu~4Qki|xBxK$r#vi#*37t~ykA#aAgLAb#O?lr5TeXlunYOJ`s9S*0~ zrz;aS%E#PGYn6;K!gw&y(}Vh!#yoo-!)rThkYfk8qo`*=N%duu>#IIQLS`VHR(ORY zzZYGNPuc7tz!(Ct4Zy=t*eB?O&)kY4YNl?C<5+0pL=f&OzT#EJBqA{bhnBzDvnsDz zn}p@viMO&GcbW|p?VBYg)=a`pzI+kAG8DQzJzlBr%GKmmV#S6S<|j%gv+gs5>Z zFB+S8c38N6tNPAx`ztO&*S3oA47M~A2?9n#p1D_OTy?K;o9@T@w)v+G#304d~-4LLm;KAE-}KDFBr!bEl%Cm!O}tksXt+Rcznz z)FAD+?U}yd-z%y+eIhOEME2?3T2T}c?)F!jy_3xE?DWz|k(+s9 zrx^V<=X-r4+p$6qwm=bZ`RYG0UgIvcCJD-Cb=Gy&t;Sv6zNMchg8LP~3zg!==7tAd z`!pWbOdyJTNZ-l3CMvTEWq*K6d{X;CT?$xN5UZN>Lm z@kX2g%^qz4?ku-{!O!qrlE2O7V`ZgoW0mJ?;kr9@%csC}vm{aQSB+dzZ7ZT)y0w&? zZgV!#2plVO?Xz8ty<fTcgX`|wvu7v^@nLfOQdVC^L|_@~y0^m{Q?v~sA??b_6Ze5-&;YVu=Myp^vR zL?;SQPYE|(J40w=9k{RU_ODa;E6i?N_lVgsHiX6<+ug0-7AB#OSsL7OSe(8Oo~Ebrx$$xqBKrm-5owI*@+J88ix->A%UzDELuGp8B;4LBCLuva zOgud`MYob=&>n8UtNrN}M4T!rJpd`8>4oYYliAt)kevQIR{D}m1}V-cD@jj~Ws6}~ zETa6nMTz20*<@E!XL_&>KD2$ktgZAwXyJWNnOY;}o*vt_x0(mJZ9gIp9Qz&ngmky` z-oi*)T@{Kw$%su^x{Q@faN3J&+s!9G^UB|8Hc?%l*RQj6o1#0jjA^+|p|5EUAXTz+ z%*x=X1^>z?@#LK!V{4MiIN*v4Q~jZKQK!5zX6KPM@X<;>3YJlxd*7K1PXLV#Yc^(oi?9h#|x)Fp8d7B z8-SxmQrx8TidmCllAW2=%o0_sWrew3(9nVDc|WTQ@p4vx(rpC|Hdc4@Z5Yg%p3nAb z*3mlCcM4=**NUBIOQ4OLGJ1v^CdJ3GYCo}?jmXSnV+BAn&2e#|i*hkxuR(5Ml0~#W zMWfHqXKBLzE)r}DHE4sJivB$#sozY6Y>P|QE-idS$IqJL~(yoI9{|IU|&SPaluw9q!anFVw{Z-U`j~Tve zZ+X;Py`ggc=c;qW;Tl&1^Z5e~3Fti$HdAm!D;*KB!*AI?(56j{vg|V^?nKX`;Z%pl zNHZCjf~~bMf=I!{|DH#$?X6AfB&HV|1;P40SE%4|3A<^dXe_*peFuOC2$T8aUXS$X;=L3hd%nrks%HomO~yuVskd_-3tkQrzsj%X{27uNCg z6g~Zuk^0U~UQW(gKm^h!p<#@xt1Be_tE;Ph9{@238wJu8zz##`qLh?04o!v>lMsAb z0>G0&dvAXGMRN`=P#K_86C&`Sf2Crt8665 zW+iK?rYJ~DJjN5|n_|WHMSZw6)O=+K<02_(dzwWM`O-1;l|1{suRDGF(Gca!q#yh& zK#E$1R)N=9r=g92Zd-DgW;HU9%eZ!M8k!qz!P`o|dny@nVc&6{bZCc5@!%3jaQYcW zU2j+ghTlNx-=As6>r5yDJbm`;i{~RyLFD7(1F0I#73-I%qy*^4)}3p`&g}{BL0Vhe z0iS5@yO)A79h~iaU{lD8eOrl*b-Ll_u`|H(t%dXr(l3X@dVY0a^Fb>r49$0kVv3OoMz2R#@}v*J2mQ*HqH9J(UA_udE-eKgBzt_&5zbqK49V zC$%OmZZ2RoO&Tpynm`sskR$@3Y@+l+FsETALuJ=X`2 zuFs<*IXVDiq9~9CiD%_H>qw_R^ca%n2ekgU!|2L+eHnLH1_JZik2 zwpkarZwZIB>Z>@=h`+-Vxd;2w$Y!A! zTfN3sX&B|mnHOqkE0pN={8ZB@#&>fHN5T&E4;-$oMd9Wa)o~qA4<~1lWCeE@`jjDM z=8*pUD0US~6BD>5v>G~b;x`zcw7hlpwz+wFc(R8PCZ+pnHx`g6)|Cg-h$J7N4ddZ@ zqV+TYWzY+~%2pOFEMn{o8M^$uG~;4r;^5lAY`F7tCn0I1XKBypuj`=X9~9RHU!fsC zzO9lzN2Du72M4$WUIL<=}dei_|ys1>d0}NfMTEvD0_DhxkGeNHqFE6t87C?h^b7hr)F&E!0 z86!@OE$cNFHyv-psT^JzUCmgN%0Qj?4}avWkW>p)o7zAJmvH8;{N$v84`*_T@_7<8 z|KS#Xx%o@N4oEAl0NYQyTcaJx##hG`^hGSOpjY`Ro7&_-@zp2z_?i@>|K_-OkeGu< zXzS2|O!c+EYCbt9rGj5*EMr=Cp@yVzR83&MQJkex#D)a%7f)4lkR|`AVGClJ$Du?= zO?e~-NBq-@(+A;FkMfvwyjl>|Hf|5=!aO(h-&51XTc@~d0>A+FV9e0XhyrHdQ z*A8={Y{!Z)NiB|ZZ`KbY9^Rvu*mdmW1^y@Hk)$?fw62e4_DK-wo~#PIs9)H3NP&3O zJE5!O#~(gcT)%xYVmxn>)GC-gTjTA?&vp=7V``Wab-gn}qo*8=IBMx#h^%?K!42QM zi>3DG^twDY_#q$vmycd>+$N6gG@tULVMRnozAH?!Q0z!AD^{dqFZ(e#mvoCpS(yDyTW`wYg<}4%x}S zgG1&&81w5&>`=CO9cFWLLy2gkQzS%B*$0Y30JpQFqPih$nEz=EOQosZD&NrTdEN3O z@9p_42nl9y(``{e-h6;Pw9?n&cvKx2z#l=L6W39fZ+1N9EHFJ@kml9B@GL~OBj11t9krnF?d|118Z&$+BeB~*xCt%+2L%@YkX zh1SrFurD1(7^7|u^9wrZ%Lar#Y!kShlhrE`$8*WAmo7?bI#P*N5#T z$eXk(G%}c(n3xBGb;t&~dxZgbWu6T!8lcv@t(nN>+u(kWqgcqb}B zLh^2pIXUr@VQw8}*L^0<#oj~d%E&T+fpM0U2&P_*PXT~Q9}|BPj~b)6>T06wpVO1? zgP{JMr5RQ}iN%0SkPiaA!NmA==omz0_-mQ*)GY-PZbSjnz3&@j2D)qD^_nbu$0G4$ z2B#IG?X~>ex)^9f9Q*w2i;kQ3&WZ2yJO2}i`9Ll|o^&Q^f=SaXxKS)8_x_s}B}Afv zg8hxwH?TCs2>+nqNagoayoMcl3@ugFbESAvc3>77M$5gs`xRibm8oj>m6etC9>rx4 zAid)Xh=hnvmn-W9j-`xd9S+v7e|xRKVFmi$g&RO)4M{Dz$;nCRvDgC_i^s*qHCp4b zWnT!D>Q_O_muVzm(kmE}5PYHbspvZ5yNQ?nWmV*MU{`FkABP6NqIbN)Rq*ATw^a+D z%u`vvXiMJ$AaKWVrH+b15{(^{(^KR2&@0{oI(yhJ&K$k`@Pr}UufEyX!5=`uK&5iz zAq}#NeIFvzO4YMLQHEH>H&>9sDE43(a>G5L&jV)ysth?G6yjRD)v)6a_puF=y1}yH zN97n7>2_GbEN1TIYS#TXO~MG$34c^VuCn|Ii|0Q1$Io=#r(4l^pZ>0ZHCH9FA`U>) zI8*J)EH^T7Z-K;s@M!~GeIQSOSw^GLLx$3Co<4mF4cPkn`u3Y#fyN~Z*8i`v?~bSX zegCgSMTG|06_Jqa6|$2MvRATq2su`iC@VWV+2fc883$$WeXNi@j?J;h?><)J)93sB z{(8Lsct3E?>wewix}VqcdS2HBVBLMya5@=ic7Zz?kIlz&e4$(QbP8!NtgJwf`HTJR z2XSC_MMqTQsP!O^02oKth_d&&%w|6+6ji#0`jrObCs*#>!Pr3mmecIw=9~wvsaxs8m zi^d(`u3Lp`_=9D>Y@7xyuX?X&wn1D9u=u@0$%%S*2Mb`=!Kzm~d1frz$ z3G_UlD%Gtg(CuT=2H}T__03I0s-iiNCc<_=0s+`z=fOX0?CJKzW>5<^a90J%U^2np z{1!wifll$5TL1m}_<9q%wtKTnb5pT6AEZ*$xnxTFn_pu#%|6C0zGnzQNg6wCi znoycZQDu;cb6e1D>{YE7nTjp86N#kyBK=F#^v_FLIUK=dgSw0w4KLa#k?8K?d@McI zNHY83_Gw<4Hl8D%=NX!J`9ovip%@!Jzgt~gh=E+<^-?d)%@w~Mr(Md>X zT%j{N>cqfjLI9eF3@AuAj-7Lt)ZpfAmC%Yl9O=hio0o+I$&}|*WPVUHn5K%!F133`;9qQ@+ z10DRSrHg?ot^_FV>3R_DmreLx>~+AK{PRxv^E+BAW%_PoqhIFTF}rdc#NWr;|GIns zmesu_5Zf zNNj5lsO{6+;b`C=JBdh&1pBP;kvvV<&7}w25}TPkC_f7D4bKQ?lnD>Te#aksKP!G# zwyn5*tl8)VvvWxAyE(C$&-YXUkMjUzJMEBd@zWKklPr3 z@u_PQ#c7lT~V3#sW;SP9z17Na+xP`=nD$#gvs~+9Fmtd98?T$+_SvH z6%`f5W{e;Oy(mCFRp-vjTE^1two>OknrMf%#>U3_dUP5S_*r)0+200pw>Lk(?qM0_vqsjvy?BCN!rg_SW4CqYD5} z9{+2)4$oQ@Ov4{Y#r=FM9HCwQUkeI&Q5|w}36Evi!Buc`78FB%@9m9RTvX0{_6-nr z4=JRjn*rJj*dgVy@mdAiq${nH;O1du%m6Y&$w+P#7*m~7pc)(!MCvwU1!92H)(w$K zO>dnlO#pu|*=t_1@y_9;we=1_I-aGMnMr%og;8&AI>tE&BvKIB-<_!z^yM_^&Lm(^ zS>M?3gCf$^B@`p?+gbiBbI~!^q045NvI1g%Boq{0ZY{Jn&XxyClL@~zJ#r5VGZ9f^ zI9;#%H;BNZR&yZ`$<|ULq8LIP| z?<=3*Fa}@(i1F`RjW^7fZ29En*nZ&-0D;~|8`8!g^6le5byI@RX0qj5dZa<@rhs1n z6$OoC*vkMrh}({m*{+4~aI&y8I+0>{45Yw-WD!7`ftpP;sRIxsLv}t$fqO&GVbZ+- z{Ea#VP-|Z&^_E2NN8N0BDJdzfGtVPbEx>*U3d>QQ;gONr^i!u!O*%(_m%hTU;AG6> z3rU)|aasV+Ua&v@Ards|HYbwiJWtH(kp|ctnjP@=MijN6g!N;-U^>OSyu6W|r1VA8u@0CfqUx}pndQ|o-9>b zevtYy7zCY^Tm!C1t2_w7GR{2kvWg|^-Wbs^&j(T=2KBgwJyl^_n~vh1jnp?(GkCuKMHv{{y)BqpNaqr%fJ3k=6hwcH+ zG&o|QkBXd9hag6 z^os>c;IRSzfwT?jnaJ)d=mXoH99&Xu!?Jfy(cbnsStl-FhZRP(8^hRowCQK6Eth>M z+a@6VJ1cUQQNWY8U!5s%*kE>i+Y8cDz)@1=?@C3#dhAgI!RG zu{902&fxU&UB7;vk1uM)4?BMV983n~Y{05g3+I6gAe`Ow(GSmbZlL-Cv7(?1!s~p6 z=7W5NkFGw~jw4}G|MFUA57aFOQ5PQd%I^1)T06s@j7j+ZEQ-Xl{qL=x@+a+Vtpx;7 zF@VTLiSl|B5kCP$I9onPudznuW(z3M`6X2S_;gNmx6ygU}P>;Xm`&0C`sRFJx) zNtsr3QZNLMby-fP#uEqhO_M)U(Q9a0^$z9J!!iyfGsjM zkFEZJUQ-vPkEN2Pl!R#Ye@*CPO}IEm6tY7dh?Vd)XxnnU@y5dI37SY*tX?Lr+_=Du zbtZ$H=&P0Ni4&Co1^DM;*jG`b(%9C#>kcz7gr3jB*tN?cG<&Re!+!5aN=eL5G5?sR zUy64J_P8=k+i2$T2jX$-YJ#Z9=6M5@_fGifF1EI(G;Yb|jc@`gv zk~(K_8i`ryehf#Bb3=qZ${+%?2iFlA{~szc36b9K-xGOXUwzvF5_!tGl1RyW-25M6|)~kM#JSt zT_!6)$;SougEu-#>o|m#qWZ{OJv~HXKuCHN?8Q_O4nd>lww5yG2=pA?V|=^oMCMb~ z9ZhHe#a_}|V(65}qV^xiKZ~{$~{CgV+Kqo5SubRiRiivbCF?UG=utw6m04qoD?aNK=&~&pSgjaP8;I zd4Q7A87$i%7$?|&P3u8s6e)DAoOkf@B6>tDIefR~R1u#pwLIKG4pn;H|0sU4Fv~n_ zaBV6|*bd;q!ObTO7fxKkvPZc6AxGv^nGc({RG8Uw`8xk4@{GrB@N3MBI6%eSD2JA8 zc9nYq^4_Zd<;cr-IE($po)Nn}i|@DSI@(S=(E59Kyo*HzlOJ-&qb>%!i-;p;{{QZe z95B`YBANcoBKjYFn!~JUN*JdZDYw`!-)FZaJ|z$T_4i-l|Kpq@LDh_w1HSr*u0P6I zI>yb6|Kf`O$rSuKRG@D-_+-A$cB#DV;JANA;?oIn)E#?^pXra!|56VfaOZ%3Kk&0O z@CWsbNvRne;m_n*j`h2)`Xl^W7&v>-%ue1>we> zG({@|+{0&lJT>KPKf~-5Zf;ahiZOsN8H=W@t(?FnJw=jPoR`E$4bvDidZLRzg5LkF z{qXul(~sQVf)TotuI00Q#HrboC-B+n@`12d1MF| z()@_Um-v79R=h3xlNY(>zR_ln`>ScvJ4b9~7nADvk*CS5Q6a)RG*ZadC-E_jk? zWK`3X+=Ab$#1xt=jp0CJ_lH6CwN=4u`p8=D7F4vw=wwk(pvC-(NQii33tcwa`>sL`}@e!U{ zh7YKy2s(31h=xG3_S90fCl1c$m1@XTHD4B5$hj1IC0y*jiij4en<-rQ!PL^m+Nh)l zw9U7I`F#z!WI$(owU9=OS$NxGtJr1>%Lwa3=Oyj9*->|K5VxL1M+Oj%q!)jkjAI(; zH#SFXape3)RZ408_TE_Ra`a++VZyX1G=$!PMPN4?75C;jyh!erVu%>;JoMw*i)Y!z zY7WR-i9=J=gO(b+N-3j^&P1WvAHH*fQVEI|?kWwi#5{Y6=n1CExasVF(m!XmOO|_S zF4iVWv`2I{xX8 zyfRKxb&VpuTnha(^UVkeou+b0>;=aakd^ZyStyX+c}1q{D}Pkotdb@%5skCl>7AOq zfeYoM`dX0@yHuVCum@+;ucS!^er$O%Ak;*!%!_812Q+WYUZdo%eQ@znu;O`t=Tj@+ zbh8;2+t^So_Hy2kf(Qc3xZ=HQgwQu(ED zebedfi2wyP@2*K6}wY-1n? z7~zbLk9fyJ1 zta?7OhZa8e<}rQW6xi79*c)CN!D?e&w1Pr@PEqp@EUe{s&Vq_I)3`?*=6^7QSE~6a z&diKNNt|CVR#CrM6xWwmRs-FY5mnImjPdI(kwjO-5&iXKt-gto@^Oox@FEhb9@}$! zXVY=UFzrOn-BRW;Ez7BYC%-*TW;>f#_>Hf2Z*aiwwW7?Jse|mfxa%=*)4$vX!Y!Gp zn*?FE>py(>{8Z+{jkh;l9#Eb2ZwbEh`AQ|8z!jdlFRxhzUNEF)-<|$Y^Cdk!^`_DK zZuz!` z=4`+^R_S+qXEZ5(#}yP|$>8S4KwiMMFe=l1o2{YUpiLy+7uMPAYq;^!#B&Krctb*JwcFDmv6`h2)9g<48&0>v8M2%-lUlh$QvoM17< z!6;a|adH^v>>b=oYAhXsC0iC!RJ8NB_0W;6bH`&i6{VMR20?OVxZ`#GSzWl4?+``( z!`{~6=YGx^gsPdj%vQ>*cl%E?ed>74g?i%?rN+eXsm&x2hCa`uLVPdlG~ffWYjo-s#ArRO=_wkW^q z)=phvsMystP?PC%;*syLiup25Zne7#3BylA_P7694g5g}5s93T()8=dInxJJ!N)S2rAxuy?3S{JiPD%QFT$)?Ep10cua=?UV8q7KDd5sf=(3(sIp{ zG)bI{zOrXT^9i~sK^xW7Yme@sb#tHMK_Nt^RILi*b=wgXMq@zkr=+W z=)J`TQ>-S;3-D-nFxZ`tZ>ijI_MP&k8q3#=m6vQwxpkEr&J@rQzEbJIn8=nEKO2=Y z_9MA7CC+~Pr%x}1elUMT^Nyv9jhr^zZ=9txx`fP!k*jF53U(PXNg)=?27l1!r`$aU z>laM-)Plpt>{;6jS!qph3r?CBrMzI2Ghm>v*Z%qYM#Lm92gjJQh_jbOR{(tk`7wcQ z_+Sig_N6u1fcWjMmeOGlrjuwzB5EN-r5UXQwTQb?tx?6LnQp?+O}k#z(9O@jRx5op z57(WPlV`(Hyr)z+n-~~g8gEaisiWEoCuI#DCc0Ge)h?{+OyS&v=BMX8CJ;X@PRIXB zc&nM&bn1a)e3IYW@R^00Q?s5Mg_&)}+SH?gTM1KjucYnOMa6Au-}BdY5^_50F!AN1 zZmC44qRcNwH?^i0KO8i>0nL{!&SMkOtR`}NnZKJqMO@%mf8J#!%2)N+Vw zyu~26Ik!8}!XC*;hguZJ8;pn(5s88ey8`p&yRf!qlKBFaZfw$rb0EN*@?MG?vWxZ+fB~y zR~x(OAqPJ&$Dq2t9L8PB3=}z!l_n#Lpp7J~W(#tmL?cd>xDjl-mx|$5T|Fe?jR^(| z)t4dKvUWQPCpSH6&d!Fjatbzam)!ZLF2dpB#=PihG&lqGR9WTHeL! zq|))dcW*+=t*k|Mk;1bTk4z`7?2LDPL=@Q-?^3K#S+Vxk*`w?odf4it7+_|Ss3IqE z*EPPl(s+D&3NW}f<1q0)iCkQY))LJGUhbp?jc%I@D>lsPHk$L2-a-u(-l#TYD$?Cr z)7;Y4N$b;TTLjr=*;Llo9P15{)nc~+K0+fB4 z)=pT5H=u_(9Cxtlf$1oKU@bgF zaJ7AYc(n7|70;fGq8NX3l{_ zclOND8z0)LTon2;JcVQkAN6iQ__i^AC3jFND$Zk{;YOZKJe{X;zwzCR{m8xV5E?M` zLUc7>A)7w3fZblpA%W5)O}&yaNW{E^H9JuarHr`L4cVdZeSUmxmT9lcPw49qirL4~ zqxZ$g(Y5>jf4@=w^9o1bpkg)_FpfG`PPU)SPS~fNV_RFEWij5vyR2m!T=MK8Xgu)? z-qi*u@8i2gx$EmB42&(ta3`}fKQA4<(h=^W%tbx&-v7Q{a_87@5SN)F0FrTZz%mCu zAHbBp5j0HsNOm-h@~8jYIO~S?`v@hw{1`aAPVAX+L1DT5^9hF#=kUJgH}x%TeFne& zc!8DV`#$}7{~g$sqDIId#~~<>P}9-ZYu%h_8a`U^pZkz3+cOp}Lr{@?GGzaCp%0O~ zSyY=87%4m1_p116c=umj%a-5>j+{DJP<7H-@9gVR7ruH$pZYdvK#Jt$E*U0KRLf?{ z>RGIPzuE(FjqW@7Rk%B{@{c$2`|E=?`;U8;-%D~iY(hk>phRuCO>RK$l;@R9%HPH+ zf|m%{06QX%Kj*f zhtlz4+hq>CK3;9ueH&=LZtV8@xU^Wl4s{_@jB+wx1=ef#UwZ@qE&FG1fg9*ae1b6k z(Pd9hIh7H@rbO6psjgFxvS_?f@&NnDQXYe2&%_>Kk57pDDt@p|I~r`!l19OV7Gv%4 z+awns-287lz{3QNRn6=_U?N*s+J!^)z4;3R!8W$o_HVo8uLEL{79x}49Dd>9+yDK@ zfi$7Nkowh=(m6eULmKoJ-TwWU|LYb-<8;@=9y}?uV(56?^E8t20pJJd;kDwgDLlN# z4v*RY&o6(Qd~`3FjAJu+a4HTR6(AxbdlhzJ2<x<@3YTf5inr#9pwKy>NN5Au=LfzDdR0c=b7Hx<sT>1z>%UdUKJ?$kdt;Sg(spjQiFl_3ZFdMzp8qK0L`! z&evEaquO-+off>J_f52@u&|fvIwzcZ&9psNU)FN$Ls06=$sYU8DBK=S3~IioP=!cK z6&(!1wgk5(m+DgENejqS6#0!5D0F~=7UuI zT%djc?YFi-a9!FhEXcxB5aio=3xHQuD}J>ITDF=w)B#P%rK?v_Ad*YOKtIkyD(G}G zXW$$!j6KBYy@g6U4vZ`mVO5~GfJ=(@8vkL^A!o%*x!zopLf9+Ur-tXw!%X^I;#Jqw z2U6JJ=zvQ$9T31^30#n8fmcG%~-M7SjF(v98^ztaO(-rQ!;vO4lgJum_dQ|;9|=gKnRNt z;zBIe7qk$&ps#mpUj~qA2bwBKA?gP}fq@W>2++e9l*%AsH@SyJ#6)eoDXa&-42j^i z;z)~UAT!{Nv_G|11~iC0Lq}K4(eZr-Kkw6PiN)lV*z-^y$zjH>tINlx2kpVTdezh` zU~Nu|SW9%*a|QB!Se{j!mti-`evS(WMf>nUZ?zn~3-&wpjHNB;CcZ0GB<3JwO)`?X z3bd3F*^M8x;S>%F0%~eMI0hYrmZinT!MTiMs_|edXs2ODsh<+x`aLxOfI2f7>ZUevj&iN(R$+x!$1BFmZ8lFfW&9J%DnZpdQi_)CKan zB67nsP%3&r?IM?Gu2KVPAoLfazFLel%!<-?L1v?C($FY4s=xwD_}c$3P!pG@aWYzu zr9Ej(o`L%%bvr|GMt*;82j@msv8ub5(pS3)VkoFpN(U+~I&CkC_2L65^+QR?9T4YF zv#DzzJUu*~EyE7?Pk zO7FJFlt> z7fuTp{R9D7P;N<@7Zqq-7RqWq-~i?Yp&<8fe&+zsoYN%5jH_|$Vx02{D@w0J+2KQ% z>hiCO*>QMi!7(v0!9mG2>crk4B^d)oiF>|y88neJBYXB?AN^!%;88mfm}VVcBKuhS z3a!u0a^+V$!*gtJmY=Ikh~(9!w(v>Er52yZH@bfJZaNUoGs0K=t;?o9&QMcPUD+V? z+W8+jsB(O<#Vr0tJ+&X|gneNOl8Mi0P6pY&xrRWB@4sAZ3?L1an7;I`(q^q1p1cPT z{htn>rLuWUjiraNvFSHIjS0#`f5l9_$1wP9=j0s|H4(CxXxGi5=RzYQ2hw-iZUVD zs?}##zoYjMdnM^r#>(`t5ZF?jwpIA%w`F=M*rww;kvD+@>b2=N^ZUrvY#L4 zU$@?e185)RwjR8^O5ZDr$#%0>zh_;aaYt0`{osq+@u#1|`w}bse}=^nEcFAjRNT&4 z$&8HR1nrXq)sJYXc8HoQ)7ExZ&Xu#|?NTK^R{@bexBcJ}9)*^t zSg4u6^sjSL=Ye7t+%?Vn{4>K-_*@<2&8FJt6Wf|DR-mAbVf;e>T&!cPk_#xRECpv_V-s1>r9X}w^;DtE8}4UKeP)NwqW#xn(2@? z)2lI+-^-QV$~PLD=UFm*;p(^0E*t>3-!uu|UkXHFZ~ZZ|@mU%&%gnzV+0LzL=gz@X4~F@s2f{^nn+?r^96j#Q zb=N;u6+F(%#Tcs>8uHSnrV!)>hBHCxCzr0)x3{EUF6vE;ZygR8F=kC++FCb347Wl0 zE}Gdl_IGvnNxXK?!9D}XPvE{`qIdR_Q}`Vdr!eu3)~YKA6=n#LkRw(}G$h-)o;~2R zX8Sljz=VLW9FpjGAF7<8cTWD&r4(w|OuOY~V!L-i)<@bVwvFepND`tq+=0l}1(_1ycznrX2wJZfq8O4wexJ4KYS3eFaG;b{OE z(E)~k?dl8;1kcv`OyDLhD@>poYc)m(J{oAt3sIEKrLwFpm{4If)QxTOPH^J1uCte$ z&WB28C>M>%CT-j?3>Prn8Y?#~t4ab@M}$aqY0RNZZx z_nRNg6j_VYhvF~-^c?u!@WroQ&7E*yai`+BL;Zoz5z(rQYvS~?cc?4d&=WRLkazXv z`(HtkgE0$spYeaaeteXW-YA=E2p6oW-L*$Cj$v8|o{2s#>+nwajh%M|)X2z{CaSO( zOW(dD6GjGz#msYdqbjS;RYQ9E2%OCX7%hFqD`={5mDtLzRs3|o9)x(4uzxD>XU)kU zEsEn>zHuo-kaW=#>G*s>Hx#OtHO+ta76FfYIz!xZ^JFpjBP}BhD zoaiiUiP&GiI#Xl#!A&Esn_urAEj?g-yKxSJsRpN6SB)~5C*!* zOTXBTzpRM5vU!(RddDU8yPE?S^qqqme0m@(tGX@gSMSK*n?u4U!#wX64KZ6jv8gsK zM`F*{!M79YdCnv6Z4J8dZ$YhvgW~#=jc!@PzXR(?4Yv-j(;&yJ`fOciO z-UZ|VnhHbP;@=GAJQspzucbjuA2)i>Z%!YppW0Tt#JK4#?_~qLjnsHvKCXe1Yv9?v zxW>}j#E5W|j6#lTc%`C`bdrC|a5F$e!+t;K9M|>>3{8g ze=1TOm7Y$h%9jVs0o)2=45D}0u61RHVZr?qkf!+O2~ahFd$RL>&9=|T4eGlKG5q4l zHymKankX2Ly%W5agL{e(9ga9Y^l2QwU1gD=W6fH^YIJXW-*lbn^38OIl*>3J3H&35_WqClBB1vLfb<_NUuqBBaGKkC~MKL0sK{~}4 zjs>8}k7&Mc=&)_V7=4XOXz9-&vQ=?mArROJ{S2rE)IJ)~t%RJRzt-Zc=ge{$Yu8X@ z)<#+c_9a(CE@!7VW%hcBETt6cLwl~c5j|D(F_B8%m0jbp6r^-AvRO+n#NA4)~{F1Ww?(ND)T{4|48JvY0zV`-bmKEfZ9GC|w<*b35%G>Lcou-AgTlVl1Xfh`m zb>}sjj6B8wpn)2LtQUYq>?c0Bf$#1>Y^^Nx9EN78ZzHd+x=>x`J48vKm3-#l)yRR|Ia`8d{$%tHcK-6()1UzdG61v z9Snue=i13;{6t9QRPQ0B=z8g_7RAF;!;}Y@4c)i*fffi(SQN4+LluqC1S!CMC!gRR z;37;ko&pQ>?J#PNj_bx8V7UkAmiLgPfrjNDC`G{o!FbT* zIkli`w#FahX#uupff5|tByyPRs;GGAUJredIi^PnTRF|B2AEYIW4JvoR1>az*I;PY z`&9POtPrSjDt*_JJJ@fOWTjwLv^gdyJN4Y{lYAj(D4ajIOV_l?qGvq@YTIfkaQbG> zJ*vcVtd>X}=pNLmG`?7M`Z6J)SEVrMBhPnr?wJtWeUBQa6?;?agbv?iz(UIdW^W#)B)kH{Evr_v*}KM*Od!NzquH>{KFW$&1=*L+&)`l zEE^4;g32m!LVr-GlE_v^E3SXy0q1Q6aG9ql~dTJBI zt!?yZ$fF2{yAuBK4216ZXnIYfQZN&wa}p+L7_cgt{nt~Tca&|%*XIz@M;01XuCO5q z;hO|$iVjUPMapy&@fnaL)dXs1W}2#hOxB3%Nm+!^yBtDra-PBCvC|YaIE_v{ zdi{KC2usS4cHjHTaCq_Cek4iDf9is&!x8&)4^qBG4LDf2Z_V%J?Bv=PM~1seMW&N- z%$eESPgzVvs$*T8sa=GgSS-*x<-Y$khdhlvUyoB0+CP%}UBh+Xp|Au$*$3mIzZNg? z{`cy}tY#NE6}11B_!d#*yr&}Bo?eiU<@n^>LJOqG#+1jXgRDw5#46`ms2wdC3n^9Z z!0t|wTy9J0c-6|vZpW+g3yLHRnob+jEirI(pRUW7CHF3pJJ97m*>c`160)j!eUd{p zyy>l;uBQK~i8fsvanOjQaN)a(cJ=Ds;GRwP>gsHG-$Z#24lsre$T1V8L}+#O9_5#^ zjcc|>`9JkSwcuFe?c_^k--!L~h~$hd~XQxVt#ale4<&mPkSwYMl7GQaV`zP@VnWO{_U?t-%X*>T>Q zbRt?aR?zM1eCc45iqnp@`J+d=MyzRTIn5d=GO^h3oxZ^(>4rSYus!@#1a@u$^ z1t_qewnMr8rhzjky|t({8@{{Q_TlQ~cetgEKg?YA=YJhk9%(pyA?x{}{kH+S+@;Yz zDppVpD-NTPFO2g=$LYY=H|r_y-etcL^Sw3JEsQP}_0G*0r6@%v3aVx5rC_`|>*l=aO`hDDiDC5H0^mdR*-_r3i|hQ$b`j$MiqCWUd6#&B;Jc z;BGx*k$3XsigyX~*|cZU*ns_BMU}(CHH8}7ZXdF32i>n&>kG+NPi_p zdC^F2+LvZE&CYM7u9of=c3l(J70!LA?=fFu4>BQjM_r#bid?kJ9`N!KVC}8)&MDn< z+KS>e_PWIJnbAk+-H+ZR8Q$2Dksm`JqXwc0>?fM!L312Yz40g9WuRS+9XlX;nVFZe zsuQ*s%2)e~6lrc%GHON{ZqRQvaLKiP_V7469q7F`9?m?bp&~^*>%7+|o+{mEI@{~c zu$-T9G4lT8=iXB1*~JmnsaQjdI%9Z;zTbLwaJZQfujA&~a)=>@sECNHr|$Qjoa!e< z7H(h51e!jO>Xgxflv#bG@|}Q`LWd1Y)^k^v`S&)yzdeUBU^kI`s$KbQ9!sR)r110T ztBDjpNn;`ehQQxjCXQLCh=GnkDkM4d)c%(CNuXEoi?YG ztFGUn+$=FGb{zFP1MHT<=;x0Ji8Ci6t&>E(#RUa5i<8DLlB-n@a4XL@^Uo(>lx<7; zl-Lq$w`KQRN{DH$R zrq-v{BIHx!0nEm<*`>_b;m-cO`SUa49}aJnZp2nCO>=AQ{BpCVo_V!z=|mMt2GcuP zqEPdRoA#ewpLI=hz7bSm48r%uTPo3FPMquSYvmB!oKA@JS!8Ieg$z17^Lkg6=rYg2 z*Q6tPG%GdQv%Sgs2CoSRw7jiS#VA5?ONzXam78b9%A!d!D8j#z*9|K6JQs>oaPOUB zi*)qfc!@+PXdvSWSD?>UCQIZJFUF!6I32?+IT0Z6xFL!o*2MYd+IAb;E{8X-k5K)_ zh_89pDzhAxO7tU6CJfy)U=$oz7rGt{vV&X@VhN1 zQ6^7I9V}Wq*GmR+X4%t1N^|HZ)VPP3t>QBhifl8?y3%_Bq@|ouog!E7RP1ab2X6Yl zTow<_z0#xUW(XhWI=tPk^g*VI{034(1%r$}oesh;60FKp%4i208U!JwjS|hH*{HQz zdXmigr=K>uj12uoPChenSJVDg#e4E4l^JKmC>*LiW%S3O!=W8S;)4%C1Y+*7C z)jDZw-OWt0Cm?sd6eTMyhAkphU@P$2##ma7zE=H*$?G-Tle-E;tPc5gy9O@Fcme5uASzmMN_=&!lE!kKn3d7rkI>dh|Ur`%eS zVfSd?;<8?f5XWxn9|5%qi=J1W-mJ(db&A~lz@QpT@M{jMC`t4CZFqYCE43Y|%QfNu zysA1R(J#P%*m=uzaI{tk3%8{6YjS=KW&hHQ3g=FcOm{@U(<^f?KHjO!(b(LSy0z3H z$I{^a^qXA5k9rN;>>b-1kqjS5ue3pWoSlf8`H37BE9;;3fV$44_Vwum*J!lw4C7O7 zUZW(j(%xGGm4#TfpR^}jXxLjD8qLt7XPJ1cOH_f-MWj8gKacZu-vk(h1{IXMz)M!z zaAalg!4N)Kc!_bcY8qxp@Vf>8iG4F!sme@1;Xfe%pq^c(?i)lR3)8oev*m85mx598Keew%28GJLXc-)?u zt>_bN{%z_E3qU3-Kdh{h*M5L4zWTX5qjV1Xl=eNOlfu~n*>AfX&RWpnz?{$vDj6kP z6cyT;O-=0>a@o#n?DX53gPuTXKMK|gcs5dr>cN%PgPYmV5VL|>sijE-GG2%EN;e!D z9o>FKysGKU#GaB3(M_O4n$<(<@?%?#cz3m|S@%+dB;v=Q8A8&S<{qT-{`>zj&2v8r zjA}lr;*!?Hwm0z1-xtxONs7XGtlP}x_hR#86Mh}h(>T-;jjalKqBSc9k}Z>zx;vwwwjtW!)H`E(`3_$HJR_}(gh=J0ho<1KHpm#Z(I`o#uD?6(FDxNq7 ze66Ss;b8T9RBVl;21dj%#;JgwVfOBwxsRoW!$-T7zsv&@rz)qi_D*oxi2}PwNl9sl)1JW2?)T32IZne)J;ii!Q7z-P`h>l{ z^jXR7>!AmA20=$b3aE)oU5_71MLC3*47cYyuL@zG`o8bn=prDX1QSXnHwCK1RLNH( z^bEfAUFPzWQ-68ZXlHRa%=Q2dv~kE1;bt;B~ZudN`BxANZBCoF^%vHZuVkx zirjqO#UhLZ4cQio?~l42T>nSYv0ywNs&6T7H_N|1d``+aqSeQJ#~B)tFqVSJFTEXYUl(sxX-?eX+)?a31Ds-wb|O$AO?7j1*56;Y;8D=BsMni*o`$r!0CeZLc3M%m_GSRfA`S8X65xHXw=& z+lxV&E%{XPJ3mtZx3#@=Tki1kUPxPu?csv$y*6lZvRDFd-VS*`)h-4E0WGiU^9Mj5 zooe#%rE)--%xgpjUMY33qb3Oirf20Pu*8GTrH;)}Y*iG@WBZ;$sZ?ZOB#)xw)+|YA zs1Sp5THDl{h;{9&5 z0G7g<0xpKvT5zhOxMHf3v>ju8aA~{_Is^HX(QJcrL~75X`Bi@ z!!k=fO%tyJ*RGD*iOqhg5tl;=2PGJx35IA=;J)g+0947|E+lL;)|Z|5%`xcYal|`Q zkY-!n=Z?teX;`lhbmN+0(c}HdX8Uy9A4dPvzqCPpWx@599O2!1l{`I#T^xzL8CqjR zJs0^PfcXH`%0pFXZ{Bq6k9ekLI;J$AsRj4uGU+e$O4!`pnvE#1Ab35lP-KJcIMQG0 z3@so-AQ@;vM0R*6{j#i|)aC5lg`_xpc48EzlQVra$u<(5t~1O}kNokxJ&v0v=Q3`y zCpRYYDrF3`t6|-AWa?#9A$!ni&!)Ktq%X(R!ynnZugU;+Zq;#uMnlpQP7o;q0DbRb>~v<45oyO zy@5pAY8>=YpUp-dhHfJy(x)06ArIH=`hq9$b=TGg*D5Q7`IjHxC8^6A*qWnr zPzP>A(~k&(*Nss9W*=}>kpth}iYG09h4UVZ5BoTp23&5^Swqc zQ-etshH887FcX|rxIP}=mQ3b>F^k{J2(vYy(50x$o9E%D%|d}{I>X=2oFO&`fCBaq zsk;|bpdo+09*pcoI=YOuQBg=z?cEoVoXau7d9BIYxz zSfQ;qxa@IpPuZO*-htG97}1(k#)Cd9uqH6AdV1I<4BtSm1q;xp!&lN2y{?$T0pr_R z;IN&yI2GPLReZ6+W%cYVVJ{EN7X*#VOXj!ZDiAz|Z9j4DCT$G4H*Zhy_a#UcMK|ey zqBa6w)N+mDC!)f)fF)R)bNv45m7A1_X&*bN(rufWKb~U0s?lRW) z`iSa$Nw~D8K{-rgcBS2<;|9Q2-Fuje69kqgyMJ`YnBr?HnNL~hXi=HgVz7(=X0=V> zx0Q1r+YH{1lZdD?PCZ?XFD8Lo(e z!HOmu2$w?Y4_-3_W&$Ye+U8)B9-%K-zn0U^c`NO_N=EzQSG(n5QSa$id@)TkUvyd;of z`%2@AX&W;Rl+W~Z?-iJUR9Z*A*M4ZL7jp8f`vO$)!f39>PYiTE-&;dCkhEg%9*Sqm zz_kThN}<*0`&!;ZxzEoFXvxn#v?P098GNXAS-?WeajyLl?*gHjR~YEBA?ML?9w1@- zdG%0uQ+yIF{l!8*^*2=v=D-Pb>1m8VVsPMN=C45-fs4x@Q8G{im}6f^^Z-NvK>_FE zI8hnNCzKN_kGm9&?nxCn(DQgd{n8V4$Le)7mlIIv@gFLCUSZ@6WMhGxLt*qcIEqL` zXNIEgfFG=jA10ZeGXJ#KlP1zE{Y+=0 zdwhJlGC4;6^VOKnm>_NtLMh2U41$6Mn6KTu=XvGDfhDLt+xZURhX_Q3ARtu}EaUrM zWe-jl*RV^d1u*a8MDgzwTom3axGPM%k0IDF%Y=@m9Jf{z9I)&%aKPf;7zrUC8b3lx z7)M&M>Z9Mz46fr|9U}aZ8bm%sN^Y~kuwgf0+%Q=h1(mKVCvblcW?nc<4SEaHI01rQ zzlAxEe)IdjD%0MNW9%md{MyS3NX!#os~wauuWh-W9L$_M&!w%&c;dnPEb?a_^IX*s zd{B4{{3l!sdNdvUFA4)Gg8tEIfezd>!u{W#mf_FTIQ+w%|MMSi^M^H@!cgz2T|WW- NiHk@HrwcxF{eP58>@olV literal 109457 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

Kn|MU%mAb%;hmFh_+8LCPO5Q7O1q4KDMts~y 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 bGIw?^f{xOH%@Y}g`lyVgqC~Ek{_Fn*s`R8Z diff --git a/doc/plantuml/mqtt_design_typicaloperation.pu b/doc/plantuml/mqtt_design_typicaloperation.pu index 740209438e..3efb0af0ea 100644 --- a/doc/plantuml/mqtt_design_typicaloperation.pu +++ b/doc/plantuml/mqtt_design_typicaloperation.pu @@ -3,69 +3,50 @@ skinparam classFontSize 8 skinparam classFontName Helvetica autonumber -box "Application thread" #LightGreen - participant "Application" as application +box "Application" #LightGreen + participant "Application thread" as application create participant "MQTT API\nfunction" as api end box -database "Send queue" as send_queue +box "Task pool" #LightBlue + participant "Task pool jobs" as task_pool +end box + +box "Network stack" #Orange + participant "Receive\ncallback" as receive_callback + participant "Network IO" as network +end box -== Enqueue Operation == +== Send MQTT packet == activate application application -> api: Call MQTT\noperation\nfunction deactivate application activate api api -> api: Allocate resources\nand generate\nMQTT packet -api -> send_queue: Add operation to\nsend queue -activate send_queue +api -> task_pool: Post send job +activate task_pool api -> application: Return\nSTATUS_PENDING destroy api activate application - -== Transmit MQTT Packet == -create participant "Send thread" as send_thread -database "Receive queue" as receive_queue -participant "Network" as network - -send_queue -> send_thread: Remove oldest\noperation -activate send_thread -send_thread -> network: Transmit operation +task_pool -> network: Transmit\n MQTT packet activate network -send_thread -> receive_queue: Add operation to\nreceive queue -activate receive_queue -send_thread -> send_queue: Check for more\noperations -send_queue -> send_thread: Send queue empty -deactivate send_queue -destroy send_thread +task_pool -> task_pool: Mark operation\nas awaiting response +network -> : Send data to server +deactivate task_pool -network -> network: Wait for\nserver response +network <- : Server response == Parse Server Response == -create participant "Receive\ncallback" as receive_callback -network -> receive_callback: Parse server\nresponse +network -> receive_callback: Notify of response deactivate network - -database "Callback queue" as callback_queue - activate receive_callback -receive_callback -> receive_queue: Find operation\nwaiting for response -receive_queue -> receive_callback: Remove waiting\noperation -deactivate receive_queue -receive_callback -> callback_queue: Add operation to\ncallback queue -activate callback_queue +receive_callback -> receive_callback: Parse incoming packet +receive_callback -> task_pool: Post notify job deactivate receive_callback +activate task_pool +task_pool -> application: Notify of result -== Notify Application of Result == -create participant "Callback\nthread" as callback_thread -callback_queue -> callback_thread: Remove oldest\noperation - -activate callback_thread -callback_thread -> application: Notify application\nof result -callback_thread -> callback_queue: Check for more\noperations -callback_queue -> callback_thread: Callback queue empty -deactivate callback_queue -destroy callback_thread - +deactivate task_pool deactivate application @enduml From 3df249ad34328ae550ae08d0e76a735e0d5ddbc9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 5 Apr 2019 13:07:21 -0700 Subject: [PATCH 086/844] Use \r\n for new lines. (#363) --- demos/aws_iot_demo_shadow.c | 4 ++-- demos/iot_demo_mqtt.c | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index df5c43e87c..78a838697b 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -405,8 +405,8 @@ static void _shadowUpdatedCallback( void * pCallbackContext, /* Log the previous and current states. */ if( ( previousFound == true ) && ( currentFound == true ) ) { - IotLogInfo( "Shadow was updated!\n" - "Previous: {\"state\":%.*s}\n" + IotLogInfo( "Shadow was updated!\r\n" + "Previous: {\"state\":%.*s}\r\n" "Current: {\"state\":%.*s}", previousLength, pPrevious, diff --git a/demos/iot_demo_mqtt.c b/demos/iot_demo_mqtt.c index 929cfaf726..84d5c5e0a8 100644 --- a/demos/iot_demo_mqtt.c +++ b/demos/iot_demo_mqtt.c @@ -250,11 +250,11 @@ static void _mqttSubscriptionCallback( void * param1, IotMqttPublishInfo_t acknowledgementInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Print information about the incoming PUBLISH message. */ - IotLogInfo( "Incoming PUBLISH received:\n" - "Subscription topic filter: %.*s\n" - "Publish topic name: %.*s\n" - "Publish retain flag: %d\n" - "Publish QoS: %d\n" + IotLogInfo( "Incoming PUBLISH received:\r\n" + "Subscription topic filter: %.*s\r\n" + "Publish topic name: %.*s\r\n" + "Publish retain flag: %d\r\n" + "Publish QoS: %d\r\n" "Publish payload: %.*s", pPublish->message.topicFilterLength, pPublish->message.pTopicFilter, From 0375dc61d0c62cb27ec15620d64a7da08c57c2f7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 10 Apr 2019 09:25:22 -0700 Subject: [PATCH 087/844] Destroy network connection with MQTT, but not from receive callback (#365) --- lib/include/private/iot_mqtt_internal.h | 15 +++- lib/source/mqtt/iot_mqtt_api.c | 100 ++++++++++++------------ lib/source/mqtt/iot_mqtt_network.c | 8 +- lib/source/mqtt/iot_mqtt_operation.c | 18 +++-- lib/source/mqtt/iot_mqtt_subscription.c | 2 +- 5 files changed, 75 insertions(+), 68 deletions(-) diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 44c9fd7add..742ab12113 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -743,8 +743,11 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, * the operation completes. * * @param[in] pOperation The operation which completed. + * @param[in] receiveCallback Whether this operation is being destroyed from + * the receive callback. This affects the MQTT operation cleanup routine. */ -void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, + bool receiveCallback ); /** * @brief Task pool routine for processing an MQTT connection's keep-alive. @@ -827,12 +830,15 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, * @brief Notify of a completed MQTT operation. * * @param[in] pOperation The MQTT operation which completed. + * @param[in] receiveCallback Whether this notification is coming from the receive + * callback. This affects the MQTT operation cleanup routine. * * Depending on the parameters passed to a user-facing MQTT function, the * notification will cause @ref mqtt_function_wait to return or invoke a * user-provided callback. */ -void _IotMqtt_Notify( _mqttOperation_t * pOperation ); +void _IotMqtt_Notify( _mqttOperation_t * pOperation, + bool receiveCallback ); /*----------------- MQTT subscription management functions ------------------*/ @@ -907,8 +913,11 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection * Also destroys an unreferenced MQTT connection. * * @param[in] pMqttConnection The referenced MQTT connection. + * @param[in] receiveCallback Whether the reference count is being changed from + * the receive callback. This affects the MQTT operation cleanup routine. */ -void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ); +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection, + bool receiveCallback ); /** * @brief Read the next available byte on a network connection. diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 95e758cb09..32644ac7f7 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -123,8 +123,11 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, * @brief Destroys the members of an MQTT connection. * * @param[in] pMqttConnection Which connection to destroy. + * @param[in] receiveCallback Whether the connection is being destroyed from the + * receive callback. This affects the MQTT operation cleanup routine. */ -static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection, + bool receiveCallback ); /** * @brief The common component of both @ref mqtt_function_subscribe and @ref @@ -225,7 +228,7 @@ static void _mqttOperation_tryDestroy( void * pData ) /* Decrement reference count and destroy operation if possible. */ if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, false ); } else { @@ -464,8 +467,11 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection, + bool receiveCallback ) { + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; + /* Clean up keep-alive if still allocated. */ if( pMqttConnection->keepAliveMs != 0 ) { @@ -502,6 +508,35 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) offsetof( _mqttSubscription_t, link ) ); IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); + /* Destroy an owned network connection if this function is not being called + * from the receive callback. */ + if( receiveCallback == false ) + { + if( pMqttConnection->ownNetworkConnection == true ) + { + networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "(MQTT connection %p) Failed to destroy network connection.", + pMqttConnection ); + } + else + { + IotLogInfo( "(MQTT connection %p) Network connection destroyed.", + pMqttConnection ); + } + } + else + { + _EMPTY_ELSE_MARKER; + } + } + else + { + _EMPTY_ELSE_MARKER; + } + /* Destroy mutexes. */ IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); @@ -713,7 +748,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { if( pSubscriptionOperation != NULL ) { - _IotMqtt_DestroyOperation( pSubscriptionOperation ); + _IotMqtt_DestroyOperation( pSubscriptionOperation, false ); } } else @@ -764,7 +799,8 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection /*-----------------------------------------------------------*/ -void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ) +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection, + bool receiveCallback ) { bool destroyConnection = false; @@ -797,7 +833,7 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection { IotLogDebug( "(MQTT connection %p) Connection will be destroyed now.", pMqttConnection ); - _destroyMqttConnection( pMqttConnection ); + _destroyMqttConnection( pMqttConnection, receiveCallback ); } else { @@ -1159,29 +1195,9 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, _EMPTY_ELSE_MARKER; } - /* Destroy a network connection owned by this MQTT connection. */ - if( ownNetworkConnection == true ) - { - networkStatus = pNetworkInfo->pNetworkInterface->destroy( pNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "Failed to destroy network connection." ); - } - else - { - IotLogInfo( "Network connection destroyed on error." ); - } - } - else - { - _EMPTY_ELSE_MARKER; - } - - if( pOperation != NULL ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, false ); } else { @@ -1190,7 +1206,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pNewMqttConnection != NULL ) { - _destroyMqttConnection( pNewMqttConnection ); + _destroyMqttConnection( pNewMqttConnection, false ); } else { @@ -1215,7 +1231,6 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { bool disconnected = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; _mqttOperation_t * pOperation = NULL; IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); @@ -1294,7 +1309,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", mqttConnection ); - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, false ); } else { @@ -1355,27 +1370,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - /* An MQTT connection that owns its network connection should destroy it. */ - if( mqttConnection->ownNetworkConnection == true ) - { - networkStatus = mqttConnection->pNetworkInterface->destroy( mqttConnection->pNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "Failed to destroy network connection." ); - } - else - { - IotLogInfo( "Network connection destroyed." ); - } - } - else - { - _EMPTY_ELSE_MARKER; - } - /* Decrement the connection reference count and destroy it if possible. */ - _IotMqtt_DecrementConnectionReferences( mqttConnection ); + _IotMqtt_DecrementConnectionReferences( mqttConnection, false ); } /*-----------------------------------------------------------*/ @@ -1719,7 +1715,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, { if( pOperation != NULL ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, false ); } else { @@ -1887,7 +1883,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /* Wait is finished; decrement operation reference count. */ if( _IotMqtt_DecrementOperationReferences( operation, false ) == true ) { - _IotMqtt_DestroyOperation( operation ); + _IotMqtt_DestroyOperation( operation, false ); } else { diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 41c19e36c2..c0f3e3e077 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -300,7 +300,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->status = status; - _IotMqtt_Notify( pOperation ); + _IotMqtt_Notify( pOperation, true ); } else { @@ -466,7 +466,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->status = status; - _IotMqtt_Notify( pOperation ); + _IotMqtt_Notify( pOperation, true ); } else { @@ -509,7 +509,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->status = status; - _IotMqtt_Notify( pOperation ); + _IotMqtt_Notify( pOperation, true ); } else { @@ -551,7 +551,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->status = status; - _IotMqtt_Notify( pOperation ); + _IotMqtt_Notify( pOperation, true ); } else { diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 6357d0cd8c..2e1bc9b53d 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -430,7 +430,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { if( decrementOnError == true ) { - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + _IotMqtt_DecrementConnectionReferences( pMqttConnection, false ); } else { @@ -528,7 +528,8 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, /*-----------------------------------------------------------*/ -void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, + bool receiveCallback ) { _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; @@ -629,7 +630,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) /* Decrement the MQTT connection's reference count after destroying an * operation. */ - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + _IotMqtt_DecrementConnectionReferences( pMqttConnection, receiveCallback ); } /*-----------------------------------------------------------*/ @@ -970,7 +971,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /* Destroy the operation or notify of completion if necessary. */ if( destroyOperation == true ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, false ); } else { @@ -981,7 +982,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /* Notify of operation completion if this job set a status. */ if( pOperation->status != IOT_MQTT_STATUS_PENDING ) { - _IotMqtt_Notify( pOperation ); + _IotMqtt_Notify( pOperation, false ); } else { @@ -1027,7 +1028,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, /* Attempt to destroy the operation once the user callback returns. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, false ); } else { @@ -1194,7 +1195,8 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ -void _IotMqtt_Notify( _mqttOperation_t * pOperation ) +void _IotMqtt_Notify( _mqttOperation_t * pOperation, + bool receiveCallback ) { IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; @@ -1288,7 +1290,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) /* Decrement reference count of operations with no callback. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) { - _IotMqtt_DestroyOperation( pOperation ); + _IotMqtt_DestroyOperation( pOperation, receiveCallback ); } else { diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 2f83dd6474..fd013b011a 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -497,7 +497,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + _IotMqtt_DecrementConnectionReferences( pMqttConnection, false ); } /*-----------------------------------------------------------*/ From 377ff18cdacb0a33891617bfe7c74662740ca6a5 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 10 Apr 2019 10:27:52 -0700 Subject: [PATCH 088/844] Add documentation for Shadow demo (#366) --- doc/lib/shadow.txt | 28 +++++++++++++++++++++++ doc/plantuml/images/mqtt_demo.png | Bin 50307 -> 48978 bytes doc/plantuml/images/shadow_demo.png | Bin 0 -> 42958 bytes doc/plantuml/mqtt_demo.pu | 6 ++--- doc/plantuml/shadow_demo.pu | 33 ++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 doc/plantuml/images/shadow_demo.png create mode 100644 doc/plantuml/shadow_demo.pu diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index ce16650387..ef75ac5029 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -50,6 +50,34 @@ In addition to the components above, the Shadow library also depends on C standa /** @page shadow_demo Demo @brief The Shadow demo demonstrates usage of the Shadow library. + +The demo program uses [Shadow updates](@ref shadow_function_update) and [delta callbacks](@ref shadow_function_setdeltacallback) to simulate toggling a remote device's state. It sends a Shadow update with a new desired state and waits for the device to change its reported state in response to the new desired state. In addition, a [Shadow updated callback](@ref shadow_function_setupdatedcallback) is used to print the changing Shadow states. + +See @subpage shadow_demo_config for configuration settings that change the behavior of the demo. + +The diagram below shows the workflow of the Shadow demo. The Shadow demo runs on an established MQTT connection; MQTT connection setup and teardown are not shown. + +@image html shadow_demo.png "Shadow Demo Workflow" width=80% +*/ + +/** +@configpage{shadow_demo,Shadow demo,Demo,demos} + +@section AWS_IOT_DEMO_SHADOW_UPDATE_COUNT +@brief The number of periodic Shadow updates to send in the demo. + +The Shadow demo sends a new update every @ref AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS milliseconds. Each update contains a new desired state, which causes a Shadow delta document to be generated and sent to the device. The device will respond to the delta document by changing its state and reporting its new state. + +@configpossible Any positive integer.
+@configdefault `20` + +@section AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS +@brief How often (in milliseconds) to send a periodic Shadow update. + +This value may be `0`, which causes a new Shadow update to be sent as soon as the device responds to the Shadow delta document. The total runtime of the demo will be @ref AWS_IOT_DEMO_SHADOW_UPDATE_COUNT `*` @ref AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS milliseconds. + +@configpossible Any non-negative integer.
+@configdefault `3000` */ /** diff --git a/doc/plantuml/images/mqtt_demo.png b/doc/plantuml/images/mqtt_demo.png index 2ccdf79f7c90ebe5464e2b1b3da2c0b05ea69dd9..bc3cc8819c731cfd0fd92551bf14d50dcacb13f4 100644 GIT binary patch literal 48978 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 RJ#b2hq`0hD?qgl={{u+%9*h70 literal 0 HcmV?d00001 diff --git a/doc/plantuml/images/jobs_sync_detail.png b/doc/plantuml/images/jobs_sync_detail.png new file mode 100644 index 0000000000000000000000000000000000000000..ca8410299806a12f5ec3930804d2082c832046b7 GIT binary patch literal 152414 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ë}4tIx4 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 EKhpi6$^ZZW literal 0 HcmV?d00001 diff --git a/doc/plantuml/jobs_async_detail.pu b/doc/plantuml/jobs_async_detail.pu new file mode 100644 index 0000000000..457207f7bf --- /dev/null +++ b/doc/plantuml/jobs_async_detail.pu @@ -0,0 +1,67 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica + +box "Application Thread" #LightGreen +actor Application as app +participant "Jobs API function" as jobs +participant "Internal Jobs functions" as internal +end box + +box "Task Pool" #LightBlue +participant "MQTT subscription callback" as callback +end box + +app -> jobs: Call Jobs API\nInput: AwsIotJobsRequestInfo_t, callback function\nAdditional input for Update and StartNext: AwsIotJobsUpdateInfo_t +activate jobs + +note over internal: _validateRequestInfo\n_validateUpdateInfo +jobs -> internal: Input: AwsIotJobsRequestInfo_t\nAwsIotJobsUpdateInfo_t +deactivate jobs +activate internal +internal -> internal: Check AwsIotJobsRequestInfo_t +internal -> internal: Check AwsIotJobsUpdateInfo_t +return Return SUCCESS or BAD_PARAMETER +activate jobs + +note over internal: _AwsIotJobs_CreateOperation +jobs -> internal: Input: AwsIotJobsRequestInfo_t +deactivate jobs +activate internal +internal -> internal: Calculate required memory for _jobsOperation_t +internal -> internal: Malloc _jobsOperation_t +internal -> internal: Setup _jobsOperation_t, save callback function +internal -> internal: Convert parameters to JSON +return Return _jobsOperation_t +activate jobs + +note over internal: _AwsIotJobs_ProcessOperation +jobs -> internal: Input: AwsIotJobsRequestInfo_t\n_jobsOperation_t +deactivate jobs +activate internal +internal -> internal: Generate topic for operation +alt Topic has subscription +internal -> internal: Retrieve subscription +else +internal -> internal: Create subscription +end alt +internal -> internal: Create MQTT PUBLISH command +internal -> internal: Add _jobsOperation_t to pending list +internal -> : Send MQTT PUBLISH +return Return SUCCESS or error + +jobs -> app: Return AwsIotJobsError_t +activate app + +== Wait for response == + +note over callback: _commonOperationCallback\nResponse received: Topic, Message +activate callback +callback -> callback: Parse Thing Name in Topic +callback -> callback: Match Thing Name with pending operation +callback -> callback: Parse ACCEPTED or REJECTED Status +callback -> app: Invoke app callback for completed operation\nInput: Thing Name, Status, Message\nApp callback runs from task pool +callback -> callback: Destroy _jobsOperation_t +deactivate callback + +@enduml diff --git a/doc/plantuml/jobs_sync_detail.pu b/doc/plantuml/jobs_sync_detail.pu new file mode 100644 index 0000000000..63c0e2e11e --- /dev/null +++ b/doc/plantuml/jobs_sync_detail.pu @@ -0,0 +1,68 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica + +box "Application Thread" #LightGreen +actor Application as app +participant "Jobs API function" as jobs +participant "Internal Jobs functions" as internal +end box + +box "Task Pool" #LightBlue +participant "MQTT subscription callback" as callback +end box + +app -> jobs: Call Jobs API\nInput: AwsIotJobsRequestInfo_t, callback function\nAdditional input for Update and StartNext: AwsIotJobsUpdateInfo_t +activate jobs + +note over internal: _validateRequestInfo\n_validateUpdateInfo +jobs -> internal: Input: AwsIotJobsRequestInfo_t\nAwsIotJobsUpdateInfo_t +deactivate jobs +activate internal +internal -> internal: Check AwsIotJobsRequestInfo_t +internal -> internal: Check AwsIotJobsUpdateInfo_t +return Return SUCCESS or BAD_PARAMETER +activate jobs + +note over internal: _AwsIotJobs_CreateOperation +jobs -> internal: Input: AwsIotJobsRequestInfo_t +deactivate jobs +activate internal +internal -> internal: Calculate required memory for _jobsOperation_t +internal -> internal: Malloc _jobsOperation_t +internal -> internal: Setup _jobsOperation_t, save callback function +internal -> internal: Convert parameters to JSON +return Return _jobsOperation_t +activate jobs + +note over internal: _AwsIotJobs_ProcessOperation +jobs -> internal: Input: AwsIotJobsRequestInfo_t\n_jobsOperation_t +deactivate jobs +activate internal +internal -> internal: Generate topic for operation +alt Topic has subscription +internal -> internal: Retrieve subscription +else +internal -> internal: Create subscription +end alt +internal -> internal: Create MQTT PUBLISH command +internal -> internal: Add _jobsOperation_t to pending list +internal -> : Send MQTT PUBLISH +return Return SUCCESS or error + +activate jobs +jobs -> jobs: Wait on a semaphore for MQTT response + + +note over callback: _commonOperationCallback\nResponse received: Topic, Message +activate callback +callback -> callback: Parse Thing Name in Topic +callback -> callback: Match Thing Name with pending operation +callback -> callback: Parse ACCEPTED or REJECTED Status +callback -> jobs: Post semaphore to unblock the wait +callback -> callback: Destroy _jobsOperation_t +deactivate callback +jobs -> app: Return AwsIotJobsError_t +deactivate jobs + +@enduml From 1ed614bd52a0b55726046d7db0efd1945a947c9f Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 20 Nov 2019 22:26:39 -0800 Subject: [PATCH 332/844] Fixed articles. (#652) --- libraries/standard/mqtt/include/iot_mqtt.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index 7096bc32c0..2004b28793 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -151,8 +151,8 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions) * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) can * also be used to pass a list of subscriptions to be stored locally without a SUBSCRIBE packet being - * sent to broker. These subscriptions are useful to invoke application level callbacks for messages received - * on unsolicited topics from broker. + * sent to the broker. These subscriptions are useful to invoke application level callbacks for messages received + * on unsolicited topics from the broker. * * This MQTT library is network agnostic, meaning it has no knowledge of the * underlying network protocol carrying the MQTT packets. It interacts with the @@ -204,7 +204,7 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * Example * @code{c} * - * // Callback function to receive messages from broker on an unsolicited topic. + * // Callback function to receive messages from the broker on an unsolicited topic. * void unsolicitedMessageCallback( void * pArgument, IotMqttCallbackParam_t * pPublish ); * * // Parameters to MQTT connect. @@ -213,7 +213,7 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; * - * // A local subscription to receive messages from broker on an unsolicited topic. + * // A local subscription to receive messages from the broker on an unsolicited topic. * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; * * // Example network abstraction types. @@ -243,7 +243,7 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * // Set the pointer to the will info. * connectInfo.pWillInfo = &willInfo; * - * // [Optional] Set a local subscription to receive broker messages on an unsolicited topic. + * // [Optional] Set a local subscription to receive the broker messages on an unsolicited topic. * subscription.qos = IOT_MQTT_QOS_0; * subscription.pTopicFilter = "some/unsolicited/topic"; * subscription.topicLength = ( uint16_t ) strlen( subscription.pTopicFilter ); From 78b90e4e72dcb5d02480de1516b46665d735772a Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Thu, 21 Nov 2019 09:36:22 -0800 Subject: [PATCH 333/844] Clarify in docs how iot_config.h is used (#651) * Updates documentation to clarify that all files use tests/iot_config.h when IOT_BUILD_TESTS is enabled --- doc/guide/building.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/guide/building.txt b/doc/guide/building.txt index aaeaeccde2..7e2e8ea1aa 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -33,7 +33,7 @@ The following options may be passed to the CMake build system to modify the buil Set this to `ON` to automatically clone any required Git submodules. When `OFF`, submodules must be manually cloned. - `IOT_BUILD_TESTS`
Default: `OFF`
- Set this to `ON` to build both demo and test executables. When `OFF`, only demo executables are built. + Set this to `ON` to build both demo and test executables. When `OFF`, only demo executables are built. If enabled, both demos and tests will use the settings defined in `tests/iot_config.h`. - `IOT_NETWORK_USE_OPENSSL` [Linux only]
Default: `OFF`
Set this to `ON` to use a network implementation based on OpenSSL instead of the default mbed TLS network implementation. If this is set to `ON`, OpenSSL development libraries and headers must be installed. @@ -131,7 +131,7 @@ See @ref IOT_DEMO_IDENTIFIER for the compile-time default settings of this optio @section building_tests Building and running the tests @brief How to build and run the SDK tests. -Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_config.h`. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. +Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_config.h`. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. Note that enabling this option means that both demos and tests will use the settings defined in `tests/iot_config.h`. If distinct configurations for demos and tests are desired, they can be built in separate build directories. @code{sh} # Create build directory and change to build directory. From a2993aa41d9e8806e981bc7ecd6fa5c3c9528dbe Mon Sep 17 00:00:00 2001 From: dcgaws <33439635+dcgaws@users.noreply.github.com> Date: Thu, 21 Nov 2019 11:58:28 -0800 Subject: [PATCH 334/844] Add support for MQTT username/password and IANA protocol string. (#642) The purpose of this change is to allow standard-based username/password device authentication to an MQTT broker (#641). By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. --- README.md | 2 + demos/app/iot_demo.c | 64 +++++++-- demos/app/iot_demo_arguments.c | 128 ++++++++++++----- demos/include/iot_demo_arguments.h | 4 +- demos/iot_config.h | 2 + demos/src/iot_demo_mqtt.c | 16 +++ libraries/platform/iot_network.h | 4 + .../mqtt/include/types/iot_mqtt_types.h | 3 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 133 ++++++++++++------ ports/common/include/iot_network_mbedtls.h | 11 +- ports/common/include/iot_network_openssl.h | 11 +- ports/common/src/iot_network_mbedtls.c | 108 +++++++------- ports/common/src/iot_network_openssl.c | 103 ++++++++------ tests/iot_config.h | 16 ++- 14 files changed, 409 insertions(+), 196 deletions(-) diff --git a/README.md b/README.md index ab8a1d5969..ffbdb51c5f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. + - Set `IOT_DEMO_USER_NAME` to the username string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-m`. + - Set `IOT_DEMO_PASSWORD` to the password string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-w`. 4. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 27b7dd566b..6bb69247b3 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -48,22 +48,24 @@ /* OpenSSL network include. */ #include "iot_network_openssl.h" - #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL - #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER - #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER - - #define IotDemoNetwork_Init IotNetworkOpenssl_Init - #define IotDemoNetwork_Cleanup IotNetworkOpenssl_Cleanup -#else + #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL + #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER + #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER + #define IOT_DEMO_ALPN_FOR_PASSWORD_AUTHENTICATION AWS_IOT_PASSWORD_ALPN_FOR_OPENSSL + + #define IotDemoNetwork_Init IotNetworkOpenssl_Init + #define IotDemoNetwork_Cleanup IotNetworkOpenssl_Cleanup +#else /* if IOT_NETWORK_USE_OPENSSL == 1 */ /* mbed TLS network include. */ #include "iot_network_mbedtls.h" - #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS - #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER - #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER + #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS + #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER + #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER + #define IOT_DEMO_ALPN_FOR_PASSWORD_AUTHENTICATION AWS_IOT_PASSWORD_ALPN_FOR_MBEDTLS - #define IotDemoNetwork_Init IotNetworkMbedtls_Init - #define IotDemoNetwork_Cleanup IotNetworkMbedtls_Cleanup + #define IotDemoNetwork_Init IotNetworkMbedtls_Init + #define IotDemoNetwork_Cleanup IotNetworkMbedtls_Cleanup #endif /* if IOT_NETWORK_USE_OPENSSL == 1 */ /* This file calls a generic placeholder demo function. The build system selects @@ -101,7 +103,7 @@ int main( int argc, /* Network server info and credentials. */ struct IotNetworkServerInfo serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; struct IotNetworkCredentials credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, - * pCredentials = NULL; + * pCredentials = NULL; /* Parse and validate any command line arguments. */ if( IotDemo_ParseArguments( argc, @@ -118,19 +120,51 @@ int main( int argc, /* For a secured connection, set the members of the credentials. */ if( demoArguments.securedConnection == true ) { - /* Set credential paths. */ + /* Set credential information. */ credentials.pClientCert = demoArguments.pClientCertPath; credentials.pPrivateKey = demoArguments.pPrivateKeyPath; credentials.pRootCa = demoArguments.pRootCaPath; + credentials.pUserName = NULL; + credentials.pPassword = NULL; + + /* Set the MQTT username, as long as it's not empty or NULL. */ + if( demoArguments.pUserName != NULL ) + { + credentials.userNameSize = strlen( demoArguments.pUserName ); + + if( credentials.userNameSize > 0 ) + { + credentials.pUserName = demoArguments.pUserName; + } + } + + /* Set the MQTT password, as long as it's not empty or NULL. */ + if( demoArguments.pPassword != NULL ) + { + credentials.passwordSize = strlen( demoArguments.pPassword ); + + if( credentials.passwordSize > 0 ) + { + credentials.pPassword = demoArguments.pPassword; + } + } /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Disable ALPN if another port is + * which only works over port 443. Clear that value if another port is * used. */ if( demoArguments.port != 443 ) { credentials.pAlpnProtos = NULL; } + /* Per IANA standard: + * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml. */ + if( ( credentials.pUserName != NULL ) && + ( demoArguments.awsIotMqttMode == true ) ) + { + credentials.pAlpnProtos = IOT_DEMO_ALPN_FOR_PASSWORD_AUTHENTICATION; + } + /* Set the pointer to the credentials. */ pCredentials = &credentials; } diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index d97974a58e..395a6a3a89 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -30,6 +30,7 @@ /* Standard includes. */ #include #include +#include /* Error handling include. */ #include "iot_error.h" @@ -87,6 +88,16 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) #ifdef IOT_DEMO_PRIVATE_KEY pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif + + /* Set default MQTT broker username if defined. */ + #ifdef IOT_DEMO_USER_NAME + pArguments->pUserName = IOT_DEMO_USER_NAME; + #endif + + /* Set default MQTT broker password if defined. */ + #ifdef IOT_DEMO_PASSWORD + pArguments->pUserName = IOT_DEMO_PASSWORD; + #endif } /*-----------------------------------------------------------*/ @@ -133,22 +144,37 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) IOT_SET_AND_GOTO_CLEANUP( false ); } - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) + /* If the host is connecting to the MQTT broker hosted by AWS IoT Core, + * there must either be a set of X.509 credentials or a + * username/password. Therefore, check that here in order to facilitate + * debugging. For other MQTT brokers, assume that the CLI arguments are + * as intended. + */ + if( pArguments->awsIotMqttMode == true ) { - IotLogError( "Client certificate path not set. Exiting." ); + if( ( pArguments->pUserName == NULL ) || + ( strlen( pArguments->pUserName ) == 0 ) || + ( pArguments->pPassword == NULL ) || + ( strlen( pArguments->pPassword ) == 0 ) ) + { + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Either username/password or client certificate path must be set. Exiting." ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } + IOT_SET_AND_GOTO_CLEANUP( false ); + } - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Client certificate private key not set. Exiting." ); + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + IotLogError( "Either username/password or private key path must be set. Exiting." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } } } else @@ -161,6 +187,26 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) } } + /* Check that the length of the encoded username, if any, will be within + * the specification of the MQTT standard. */ + if( pArguments->pUserName != NULL ) + { + if( strlen( pArguments->pUserName ) > UINT16_MAX ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + /* Check that the length of the encoded username, if any, will be within + * the specification of the MQTT standard. */ + if( pArguments->pPassword != NULL ) + { + if( strlen( pArguments->pPassword ) > UINT16_MAX ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + /* No cleanup is required for this function. */ IOT_FUNCTION_EXIT_NO_CLEANUP(); } @@ -203,25 +249,39 @@ bool IotDemo_ParseArguments( int argc, switch( pOption[ 1 ] ) { - /* Secured connection. */ - case 's': - pArguments->securedConnection = true; + /* Client certificate path. */ + case 'c': + i++; + pArguments->pClientCertPath = argv[ i ]; break; - /* Unsecured connection. */ - case 'u': - pArguments->securedConnection = false; + /* Server. */ + case 'h': + i++; + pArguments->pHostName = argv[ i ]; break; - /* MQTT server is not AWS. */ - case 'n': - pArguments->awsIotMqttMode = false; + /* Client identifier or Thing Name. */ + case 'i': + i++; + pArguments->pIdentifier = argv[ i ]; break; - /* Server. */ - case 'h': + /* Client certificate private key path. */ + case 'k': i++; - pArguments->pHostName = argv[ i ]; + pArguments->pPrivateKeyPath = argv[ i ]; + break; + + /* Username for MQTT. */ + case 'm': + i++; + pArguments->pUserName = argv[ i ]; + break; + + /* MQTT server is not AWS. */ + case 'n': + pArguments->awsIotMqttMode = false; break; /* Server port. */ @@ -249,22 +309,20 @@ bool IotDemo_ParseArguments( int argc, pArguments->pRootCaPath = argv[ i ]; break; - /* Client certificate path. */ - case 'c': - i++; - pArguments->pClientCertPath = argv[ i ]; + /* Secured connection. */ + case 's': + pArguments->securedConnection = true; break; - /* Client certificate private key path. */ - case 'k': - i++; - pArguments->pPrivateKeyPath = argv[ i ]; + /* Unsecured connection. */ + case 'u': + pArguments->securedConnection = false; break; - /* Client identifier or Thing Name. */ - case 'i': + /* Password for MQTT. */ + case 'w': i++; - pArguments->pIdentifier = argv[ i ]; + pArguments->pPassword = argv[ i ]; break; default: diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index 9f7cfd6ea3..c42343de77 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -56,8 +56,10 @@ typedef struct IotDemoArguments const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ const char * pPrivateKeyPath; /**< @brief The path to the private key that matches the client certificate. */ + const char * pUserName; /**< @brief The username for authenticating to the MQTT broker. */ + const char * pPassword; /**< @brief The password for authenticating to the MQTT broker. */ - const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ + const char * pIdentifier; /**< @brief The client identifier or Thing Name to use for demo. */ } IotDemoArguments_t; /** diff --git a/demos/iot_config.h b/demos/iot_config.h index 21a05fff5a..b91dd8105b 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -34,6 +34,8 @@ #define IOT_DEMO_ROOT_CA "" /* Command line: -r */ #define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ #define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ +#define IOT_DEMO_USER_NAME "" /* Command line: -m */ +#define IOT_DEMO_PASSWORD "" /* Command line: -w */ /* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at * runtime with the command line option -i. Identifiers are optional for the diff --git a/demos/src/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c index 17a34e2674..d0f252d3ff 100644 --- a/demos/src/iot_demo_mqtt.c +++ b/demos/src/iot_demo_mqtt.c @@ -399,6 +399,7 @@ static int _establishMqttConnection( bool awsIotMqttMode, IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + IotNetworkCredentials_t pCredentials = ( IotNetworkCredentials_t ) pNetworkCredentialInfo; /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ @@ -417,6 +418,21 @@ static int _establishMqttConnection( bool awsIotMqttMode, connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; connectInfo.pWillInfo = &willInfo; + if( pCredentials != NULL ) + { + connectInfo.pUserName = pCredentials->pUserName; + connectInfo.userNameLength = ( uint16_t ) pCredentials->userNameSize; + connectInfo.pPassword = pCredentials->pPassword; + connectInfo.passwordLength = ( uint16_t ) pCredentials->passwordSize; + } + else + { + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0; + } + /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects * unexpectedly. */ diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 651f0d0ac1..03303b3fa9 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -356,6 +356,10 @@ struct IotNetworkCredentials size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ + const char * pUserName; /**< @brief String representing the username for MQTT. */ + size_t userNameSize; /**< @brief Size associated with #IotNetworkCredentials.pUserName. */ + const char * pPassword; /**< @brief String representing the password for MQTT. */ + size_t passwordSize; /**< @brief Size associated with #IotNetworkCredentials.pPassword. */ }; #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 2ef88f2e1d..62c0ede165 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -669,8 +669,7 @@ typedef struct IotMqttConnectInfo const char * pClientIdentifier; /**< @brief MQTT client identifier. */ uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - /* These credentials are not used by AWS IoT and may be ignored if - * awsIotMqttMode is true. */ + /* Use these fields if your MQTT broker requires username and password. */ const char * pUserName; /**< @brief Username for MQTT connection. */ uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index d517fc6733..2a963e6ccc 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -30,6 +30,7 @@ /* Standard includes. */ #include +#include /* Error handling include. */ #include "iot_error.h" @@ -463,6 +464,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, size_t * pPacketSize ) { bool status = true; + bool encodedUserName = false; size_t connectPacketSize = 0, remainingLength = 0; /* The CONNECT packet will always include a 10-byte variable header. */ @@ -487,30 +489,30 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - connectPacketSize += AWS_IOT_METRICS_USERNAME_LENGTH + sizeof( uint16_t ); + connectPacketSize += AWS_IOT_METRICS_USERNAME_LENGTH + + pConnectInfo->userNameLength + sizeof( uint16_t ); + encodedUserName = true; #endif } + + /* Add the lengths of the username (if it wasn't already handled above) and + * password, if specified. */ + if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) + { + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + } else { - /* Add the lengths of the username and password if provided and not - * connecting to an AWS IoT MQTT server. */ - if( pConnectInfo->pUserName != NULL ) - { - connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); - } - else - { - EMPTY_ELSE_MARKER; - } + EMPTY_ELSE_MARKER; + } - if( pConnectInfo->pPassword != NULL ) - { - connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); - } - else - { - EMPTY_ELSE_MARKER; - } + if( pConnectInfo->pPassword != NULL ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + else + { + EMPTY_ELSE_MARKER; } /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has @@ -663,6 +665,7 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, { uint8_t connectFlags = 0; uint8_t * pConnectPacket = pBuffer; + bool encodedUserName = false; /* The first byte in the CONNECT packet is the control packet type. */ *pBuffer = MQTT_PACKET_TYPE_CONNECT; @@ -692,7 +695,8 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, } /* Username and password depend on MQTT mode. */ - if( pConnectInfo->awsIotMqttMode == true ) + if( ( pConnectInfo->pUserName == NULL ) && + ( pConnectInfo->awsIotMqttMode == true ) ) { /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server * never uses a password. */ @@ -786,7 +790,7 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, } /* If metrics are enabled, write the metrics username into the CONNECT packet. - * Otherwise, write the username and password only when not connecting to an + * Otherwise, write the username and password only when not connecting to the * AWS IoT MQTT server. */ if( pConnectInfo->awsIotMqttMode == true ) { @@ -794,34 +798,73 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); - pBuffer = _encodeString( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); - #endif + /* Determine if the Connect packet should use a combination of the username + * for authentication plus the SDK version string. */ + if( pConnectInfo->pUserName != NULL ) + { + /* Only include metrics if it will fit within the encoding + * standard. */ + if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= UINT16_MAX ) + { + /* Write the high byte of the combined length. */ + *pBuffer = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + + /* Write the low byte of the combined length. */ + *( pBuffer + sizeof( uint8_t ) ) = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer += sizeof( uint16_t ); + + /* Write the identity portion of the username. */ + memcpy( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + pBuffer += pConnectInfo->userNameLength; + + /* Write the metrics portion of the username. */ + memcpy( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; + + encodedUserName = true; + } + } + else + { + /* The username is not being used for authentication, but + * metrics are enabled. */ + pBuffer = _encodeString( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); + + encodedUserName = true; + } + #endif /* #if AWS_IOT_MQTT_ENABLE_METRICS == 1 && IOT_STATIC_MEMORY_ONLY == 0 */ + } + + /* Encode the username if there is one and it hasn't already been done. */ + if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); } else { - if( pConnectInfo->pUserName != NULL ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - } - else - { - EMPTY_ELSE_MARKER; - } + EMPTY_ELSE_MARKER; + } - if( pConnectInfo->pPassword != NULL ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pPassword, - pConnectInfo->passwordLength ); - } - else - { - EMPTY_ELSE_MARKER; - } + /* Encode the password field, if requested by the app. */ + if( pConnectInfo->pPassword != NULL ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + } + else + { + EMPTY_ELSE_MARKER; } /* Ensure that the difference between the end and beginning of the buffer diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h index 3cb5bc4441..18ca5ebca2 100644 --- a/ports/common/include/iot_network_mbedtls.h +++ b/ports/common/include/iot_network_mbedtls.h @@ -51,8 +51,8 @@ #define IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER { 0 } /** - * @brief Initialize an #IotNetworkCredentials for AWS IoT with ALPN enabled - * when using this mbed TLS network stack. + * @brief Initialize an #IotNetworkCredentials for AWS IoT for using TLS mutual + * authentication with certificates, TCP port 443, and mbedTLS. * * @note This initializer may change at any time in future versions, but its * name will remain the same. @@ -62,6 +62,13 @@ .pAlpnProtos = "x-amzn-mqtt-ca" \ } +/** + * @brief This is the ALPN (Application-Layer Protocol Negotiation) string + * required by AWS IoT for password-based authentication to the MQTT broker, + * TCP port 443, and mbedTLS. + */ +#define AWS_IOT_PASSWORD_ALPN_FOR_MBEDTLS "mqtt" + /** * @brief Generic initializer for an #IotNetworkCredentials when using this * mbed TLS network stack. diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h index 357595bca7..648bf5b41f 100644 --- a/ports/common/include/iot_network_openssl.h +++ b/ports/common/include/iot_network_openssl.h @@ -54,8 +54,8 @@ #define IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER { 0 } /** - * @brief Initialize an #IotNetworkCredentials for AWS IoT with ALPN enabled - * when using this OpenSSL network stack. + * @brief Initialize an #IotNetworkCredentials for AWS IoT for using TLS mutual + * authentication with certificates, TCP port 443, and OpenSSL. * * @note This initializer may change at any time in future versions, but its * name will remain the same. @@ -65,6 +65,13 @@ .pAlpnProtos = "\x0ex-amzn-mqtt-ca" \ } +/** + * @brief This is the ALPN (Application-Layer Protocol Negotiation) string + * required by AWS IoT for password-based authentication to the MQTT broker, + * TCP port 443, and OpenSSL. + */ +#define AWS_IOT_PASSWORD_ALPN_FOR_OPENSSL "\x04mqtt" + /** * @brief Generic initializer for an #IotNetworkCredentials when using this * OpenSSL network stack. diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index e8371ac2f7..c11820e623 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -454,66 +454,79 @@ static bool _readCredentials( _networkConnection_t * pConnection, IOT_FUNCTION_ENTRY( bool, true ); int mbedtlsError = 0; - /* Read the root CA certificate. */ - mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.rootCa ), - pRootCaPath ); - - if( mbedtlsError < 0 ) + if( ( pRootCaPath != NULL ) && + ( strlen( pRootCaPath ) != 0 ) ) { - _logConnectionError( mbedtlsError, pConnection, "Failed to read root CA certificate file." ); + /* Read the root CA certificate. */ + mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.rootCa ), + pRootCaPath ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else if( mbedtlsError > 0 ) - { - IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", - pRootCaPath, - mbedtlsError ); - } + if( mbedtlsError < 0 ) + { + _logConnectionError( mbedtlsError, pConnection, "Failed to read root CA certificate file." ); - /* Read the client certificate. */ - mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.clientCert ), - pClientCertPath ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else if( mbedtlsError > 0 ) + { + IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", + pRootCaPath, + mbedtlsError ); + } + else + { + /* Set the root certificate in the SSL configuration. */ + mbedtls_ssl_conf_ca_chain( &( pConnection->ssl.config ), + &( pConnection->ssl.credentials.rootCa ), + NULL ); + } + } - if( mbedtlsError < 0 ) + if( ( pClientCertPath != NULL ) && + ( strlen( pClientCertPath ) != 0 ) && + ( pCertPrivateKeyPath != NULL ) && + ( strlen( pCertPrivateKeyPath ) != 0 ) ) { - _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate file." ); + /* Read the client certificate. */ + mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.clientCert ), + pClientCertPath ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else if( mbedtlsError > 0 ) - { - IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", - pClientCertPath, - mbedtlsError ); - } + if( mbedtlsError < 0 ) + { + _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate file." ); - /* Read the client certificate private key. */ - mbedtlsError = mbedtls_pk_parse_keyfile( &( pConnection->ssl.credentials.privateKey ), - pCertPrivateKeyPath, - NULL ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else if( mbedtlsError > 0 ) + { + IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", + pClientCertPath, + mbedtlsError ); + } - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate private key file." ); + /* Read the client certificate private key. */ + mbedtlsError = mbedtls_pk_parse_keyfile( &( pConnection->ssl.credentials.privateKey ), + pCertPrivateKeyPath, + NULL ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } + if( mbedtlsError != 0 ) + { + _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate private key file." ); - /* Set the credentials in the SSL configuration. */ - mbedtls_ssl_conf_ca_chain( &( pConnection->ssl.config ), - &( pConnection->ssl.credentials.rootCa ), - NULL ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } - mbedtlsError = mbedtls_ssl_conf_own_cert( &( pConnection->ssl.config ), - &( pConnection->ssl.credentials.clientCert ), - &( pConnection->ssl.credentials.privateKey ) ); + /* Set the client credential to the SSL context configuration. */ + mbedtlsError = mbedtls_ssl_conf_own_cert( &( pConnection->ssl.config ), + &( pConnection->ssl.credentials.clientCert ), + &( pConnection->ssl.credentials.privateKey ) ); - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to configure credentials." ); + if( mbedtlsError != 0 ) + { + _logConnectionError( mbedtlsError, pConnection, "Failed to configure credentials." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } } IOT_FUNCTION_EXIT_NO_CLEANUP(); @@ -563,6 +576,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); + /* Setup TLS authentication. */ if( _readCredentials( pConnection, pMbedtlsCredentials->pRootCa, pMbedtlsCredentials->pClientCert, diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index fa1c27a821..a57fa0b1a6 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -338,67 +338,78 @@ static bool _readCredentials( SSL_CTX * pSslContext, X509 * pRootCa = NULL; /* OpenSSL does not provide a single function for reading and loading certificates - * from files into stores, so the file API must be called. */ - IotLogDebug( "Opening root certificate %s", pRootCaPath ); - FILE * pRootCaFile = fopen( pRootCaPath, "r" ); - - if( pRootCaFile == NULL ) + * from files into stores, so the file API must be called. Start with the + * root certificate. */ + if( ( pRootCaPath != NULL ) && + ( strlen( pRootCaPath ) != 0 ) ) { - IotLogError( "Failed to open %s", pRootCaPath ); + IotLogDebug( "Opening root certificate %s", pRootCaPath ); + FILE * pRootCaFile = fopen( pRootCaPath, "r" ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } + if( pRootCaFile == NULL ) + { + IotLogError( "Failed to open %s", pRootCaPath ); - /* Read the root CA into an X509 object, then close its file handle. */ - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } - if( fclose( pRootCaFile ) != 0 ) - { - IotLogWarn( "Failed to close file %s", pRootCaPath ); - } + /* Read the root CA into an X509 object, then close its file handle. */ + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - if( pRootCa == NULL ) - { - IotLogError( "Failed to parse root CA." ); + if( fclose( pRootCaFile ) != 0 ) + { + IotLogWarn( "Failed to close file %s", pRootCaPath ); + } - IOT_SET_AND_GOTO_CLEANUP( false ); - } + if( pRootCa == NULL ) + { + IotLogError( "Failed to parse root CA." ); - /* Add the root CA to certificate store. */ - if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), - pRootCa ) != 1 ) - { - IotLogError( "Failed to add root CA to certificate store." ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } - IOT_SET_AND_GOTO_CLEANUP( false ); - } + /* Add the root CA to certificate store. */ + if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), + pRootCa ) != 1 ) + { + IotLogError( "Failed to add root CA to certificate store." ); - IotLogInfo( "Successfully imported root CA." ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } - /* Import the client certificate. */ - if( SSL_CTX_use_certificate_chain_file( pSslContext, - pClientCertPath ) != 1 ) + IotLogInfo( "Successfully imported root CA." ); + } + + if( ( pClientCertPath != NULL ) && + ( strlen( pClientCertPath ) != 0 ) && + ( pCertPrivateKeyPath != NULL ) && + ( strlen( pCertPrivateKeyPath ) != 0 ) ) { - IotLogError( "Failed to import client certificate at %s", - pClientCertPath ); + /* Import the client certificate. */ + if( SSL_CTX_use_certificate_chain_file( pSslContext, + pClientCertPath ) != 1 ) + { + IotLogError( "Failed to import client certificate at %s", + pClientCertPath ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } + IOT_SET_AND_GOTO_CLEANUP( false ); + } - IotLogInfo( "Successfully imported client certificate." ); + IotLogInfo( "Successfully imported client certificate." ); - /* Import the client certificate private key. */ - if( SSL_CTX_use_PrivateKey_file( pSslContext, - pCertPrivateKeyPath, - SSL_FILETYPE_PEM ) != 1 ) - { - IotLogError( "Failed to import client certificate private key at %s", - pCertPrivateKeyPath ); + /* Import the client certificate private key. */ + if( SSL_CTX_use_PrivateKey_file( pSslContext, + pCertPrivateKeyPath, + SSL_FILETYPE_PEM ) != 1 ) + { + IotLogError( "Failed to import client certificate private key at %s", + pCertPrivateKeyPath ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } + IOT_SET_AND_GOTO_CLEANUP( false ); + } - IotLogInfo( "Successfully imported client certificate private key." ); + IotLogInfo( "Successfully imported client certificate private key." ); + } IOT_FUNCTION_CLEANUP_BEGIN(); @@ -448,7 +459,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); - /* Import all credentials. */ + /* Setup authentication. */ if( _readCredentials( pSslContext, pOpensslCredentials->pRootCa, pOpensslCredentials->pClientCert, diff --git a/tests/iot_config.h b/tests/iot_config.h index 687393b7ae..3a06374ae0 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -62,6 +62,12 @@ #ifndef IOT_TEST_PRIVATE_KEY #define IOT_TEST_PRIVATE_KEY "" #endif + #ifndef IOT_TEST_USER_NAME + #define IOT_TEST_USER_NAME "" + #endif + #ifndef IOT_TEST_PASSWORD + #define IOT_TEST_PASSWORD "" + #endif #endif /* if IOT_TEST_MQTT_MOSQUITTO == 1 */ /* Shadow tests configuration. */ @@ -101,6 +107,12 @@ #ifdef IOT_TEST_PRIVATE_KEY #define IOT_DEMO_PRIVATE_KEY IOT_TEST_PRIVATE_KEY #endif +#ifdef IOT_TEST_USER_NAME + #define IOT_DEMO_USER_NAME IOT_TEST_USER_NAME +#endif +#ifdef IOT_TEST_PASSWORD + #define IOT_DEMO_PASSWORD IOT_TEST_PASSWORD +#endif #if defined( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) #define IOT_DEMO_IDENTIFIER IOT_TEST_MQTT_CLIENT_IDENTIFIER #elif defined( AWS_IOT_TEST_SHADOW_THING_NAME ) @@ -192,7 +204,9 @@ .pAlpnProtos = IOT_TEST_ALPN_PROTOS, \ .pRootCa = IOT_TEST_ROOT_CA, \ .pClientCert = IOT_TEST_CLIENT_CERT, \ - .pPrivateKey = IOT_TEST_PRIVATE_KEY \ + .pPrivateKey = IOT_TEST_PRIVATE_KEY, \ + .pUserName = IOT_TEST_USER_NAME, \ + .pPassword = IOT_TEST_PASSWORD \ } #else #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER { 0 } From 541a62a91f630b684a04ae4f5c5bde24ea990ddf Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Thu, 21 Nov 2019 17:53:11 -0800 Subject: [PATCH 335/844] Doxygen comment grammar fix. (#654) * fix: grammar. Added articles in doxygen comments. --- libraries/standard/mqtt/include/types/iot_mqtt_types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 62c0ede165..db94062c5b 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -628,9 +628,9 @@ typedef struct IotMqttConnectInfo * if any. These subscriptions will be immediately restored upon reconnecting. * * [Optional] The field can also be used to pass a list of subscriptions to be - * stored locally without a SUBSCRIBE packet being sent to broker. These subscriptions + * stored locally without a SUBSCRIBE packet being sent to the broker. These subscriptions * are useful to invoke application level callbacks for messages received on unsolicited - * topics from broker. + * topics from the broker. * * This member is ignored if it is `NULL`. If this member is not `NULL`, * #IotMqttConnectInfo_t.previousSubscriptionCount must be nonzero. From 2ffa7d63bdbfc2721269ce11273c2125265fef85 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 25 Nov 2019 15:54:18 -0800 Subject: [PATCH 336/844] Update docs with enhanced auth link (#655) --- libraries/standard/mqtt/include/types/iot_mqtt_types.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index db94062c5b..5719495ddd 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -669,8 +669,14 @@ typedef struct IotMqttConnectInfo const char * pClientIdentifier; /**< @brief MQTT client identifier. */ uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - /* Use these fields if your MQTT broker requires username and password. */ - const char * pUserName; /**< @brief Username for MQTT connection. */ + /** + * @brief Username for MQTT connection. + * + * The MQTT username and password can be used for AWS IoT Enhanced Custom + * Authentication as described [here] + * (https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html). + */ + const char * pUserName; uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ From f588ca2152d72d5cb88df641e38b0413be988873 Mon Sep 17 00:00:00 2001 From: Gary Wicker <14828980+gkwicker@users.noreply.github.com> Date: Mon, 25 Nov 2019 16:41:51 -0800 Subject: [PATCH 337/844] Automated spell checking for repository; spelling fixes (#656) Basic automated spell checking for the repository. --- .travis.yml | 3 + demos/lexicon.txt | 48 +++++ libraries/aws/common/lexicon.txt | 32 +++ libraries/aws/defender/lexicon.txt | 64 ++++++ libraries/aws/jobs/lexicon.txt | 122 +++++++++++ libraries/aws/shadow/lexicon.txt | 92 ++++++++ libraries/platform/lexicon.txt | 64 ++++++ libraries/standard/common/lexicon.txt | 132 ++++++++++++ .../common/src/iot_static_memory_common.c | 2 +- .../src/private/iot_taskpool_internal.h | 2 +- .../common/test/unit/iot_tests_taskpool.c | 4 +- .../mqtt/include/iot_mqtt_serialize.h | 2 +- libraries/standard/mqtt/lexicon.txt | 197 ++++++++++++++++++ .../mqtt/src/private/iot_mqtt_internal.h | 2 +- .../serializer/include/iot_serializer.h | 2 +- libraries/standard/serializer/lexicon.txt | 40 ++++ .../cbor/iot_serializer_tinycbor_encoder.c | 2 +- ports/common/lexicon.txt | 62 ++++++ ports/posix/lexicon.txt | 20 ++ ports/template/lexicon.txt | 29 +++ scripts/ablexicon | 96 +++++++++ scripts/ci_test_jobs.sh | 0 scripts/ci_test_spelling.sh | 38 ++++ scripts/extract-comments | 41 ++++ scripts/find-unknown-comment-words | 143 +++++++++++++ scripts/setup/ci_setup_linux.sh | 5 + tests/lexicon.txt | 15 ++ 27 files changed, 1251 insertions(+), 8 deletions(-) create mode 100644 demos/lexicon.txt create mode 100644 libraries/aws/common/lexicon.txt create mode 100644 libraries/aws/defender/lexicon.txt create mode 100644 libraries/aws/jobs/lexicon.txt create mode 100644 libraries/aws/shadow/lexicon.txt create mode 100644 libraries/platform/lexicon.txt create mode 100644 libraries/standard/common/lexicon.txt create mode 100644 libraries/standard/mqtt/lexicon.txt create mode 100644 libraries/standard/serializer/lexicon.txt create mode 100644 ports/common/lexicon.txt create mode 100644 ports/posix/lexicon.txt create mode 100644 ports/template/lexicon.txt create mode 100755 scripts/ablexicon mode change 100644 => 100755 scripts/ci_test_jobs.sh create mode 100755 scripts/ci_test_spelling.sh create mode 100755 scripts/extract-comments create mode 100755 scripts/find-unknown-comment-words create mode 100644 tests/lexicon.txt diff --git a/.travis.yml b/.travis.yml index 988f314efe..6c653b266e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,9 @@ jobs: # Documentation check for pull requests. - if: type = pull_request env: RUN_TEST=doc + # Spelling check for pull requests. + - if: type = pull_request + env: RUN_TEST=spelling # Library tests. - env: RUN_TEST=common - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls diff --git a/demos/lexicon.txt b/demos/lexicon.txt new file mode 100644 index 0000000000..39fe6a9356 --- /dev/null +++ b/demos/lexicon.txt @@ -0,0 +1,48 @@ +alpn +api +ascii +aws +awsiotmqttmode +bool +ca +cert +cli +com +config +const +endcond +endif +extensiontype +gr +html +https +iana +ifndef +int +iot +jobscallback +json +lwt +mbed +mqtt +openssl +org +pclientcertpath +phostname +pidentifier +poweron +ppassword +pprivatekeypath +prootcapath +publishcount +pusername +sdk +securedconnection +snprintf +startnext +tcp +tls +uint +username +www +xhtml diff --git a/libraries/aws/common/lexicon.txt b/libraries/aws/common/lexicon.txt new file mode 100644 index 0000000000..36f8d53ae0 --- /dev/null +++ b/libraries/aws/common/lexicon.txt @@ -0,0 +1,32 @@ +aws +awsiotjsondocumentlength +awsiotmqttcallbackfunction +bool +callbackfunction +com +config +const +endif +gr +html +https +ifndef +int +iot +iotmqttconnection +isn +json +longestsuffixlength +mallocstring +mqtt +mqttconnection +mutex +operationnamelength +poperationname +pthingname +ptopicfilterbase +qos +thingnamelength +topicfilterbaselength +uint +unescaped diff --git a/libraries/aws/defender/lexicon.txt b/libraries/aws/defender/lexicon.txt new file mode 100644 index 0000000000..2e056f26e4 --- /dev/null +++ b/libraries/aws/defender/lexicon.txt @@ -0,0 +1,64 @@ +addr +alpn +aws +awsiotdefender +awsiotdefendercallback +awsiotdefendercallbackinfo +awsiotdefendereventtype +awsiotdefendermetricsgroup +awsiotdefenderstartinfo +callbackinfo +cbor +clientidentifierlength +com +config +const +defendermetricsflags +detectmetricsmessages +developerguide +doesn +endif +enum +eventtype +getperiod +html +http +https +ifndef +iot +iotmqttconnectinfo +iotmqttconnection +iotserializerencoderobject +ip +json +malloc +metricsflag +metricsflagsnapshot +metricsreportlength +mqtt +mqttconnection +mutex +onlinepubs +opengroup +org +payloadlength +pcallbackcontext +pcallbackinfo +pclientidentifier +pdatabuffer +periodmillisecond +perror +pmetricsreport +ppayload +pre +reportaccepted +setmetrics +setperiod +startinfo +stdout +strerror +tcp +thingname +todo +uint +xffffffff diff --git a/libraries/aws/jobs/lexicon.txt b/libraries/aws/jobs/lexicon.txt new file mode 100644 index 0000000000..d44508df38 --- /dev/null +++ b/libraries/aws/jobs/lexicon.txt @@ -0,0 +1,122 @@ +api +apireference +autogenerated +aws +awsiotjobs +awsiotjobscallbackinfo +awsiotjobscallbackparam +awsiotjobscallbacktype +awsiotjobserror +awsiotjobsoperation +awsiotjobsrequestinfo +awsiotjobsresponse +awsiotjobsupdateinfo +bool +callbackreferences +callbacktype +clienttoken +clienttokenlength +com +config +const +describeasync +describejobexecution +describesync +developerguide +documentlength +doesn +endcond +endif +executionnumber +expectedresult +expectedtype +getpendingasync +getpendingjobexecutions +getpendingsync +gr +html +http +https +ifndef +includejobdocument +includejobexecutionstate +init +int +internalerror +invalidjson +invalidrequest +invalidstatetransition +invalidtopic +iot +iotlink +iotmqttconnection +iotmqttpublishinfo +iotmqttqos +iotsemaphore +isn +jobexecutionschanged +jobexecutionstate +jobidlength +jobscallback +jobsoperation +jobsoperationtype +jobsrequestlength +jobsresponselength +jobssubscription +json +malloc +mqtt +mqttconnection +mutex +mutexes +nextjobexecutionchanged +notifynextcallbackwrapper +notifypendingcallbackwrapper +oldfunction +onlinepubs +opengroup +operationmatchparams +operationreferences +org +param +pcallbackcontext +pclienttoken +pdocument +pinusejobsoperations +pinusejobssubscriptions +pjobid +pjobsoperations +pjobsrequest +pjobsresponse +pjobssubscriptions +presponse +psubscription +pthingname +pupdateinfo +qos +removepersistentsubscriptions +requestthrottled +resourcenotfound +responselength +retrylimit +retryms +sdk +setnotifynextcallback +setnotifypendingcallback +startnextasync +startnextpendingjobexecution +startnextsync +statename +strerror +structs +suback +terminalstatereached +thingnamelength +uint +updateasync +updatejobexecution +updatesync +versionmismatch +waitable +waitsem +waitsemaphore diff --git a/libraries/aws/shadow/lexicon.txt b/libraries/aws/shadow/lexicon.txt new file mode 100644 index 0000000000..8fa07243dc --- /dev/null +++ b/libraries/aws/shadow/lexicon.txt @@ -0,0 +1,92 @@ +api +aws +awsiotshadow +awsiotshadowcallbackinfo +awsiotshadowcallbackparam +awsiotshadowcallbacktype +awsiotshadowdocumentinfo +awsiotshadowerror +awsiotshadowoperation +callbacktype +clienttoken +clienttokenlength +com +config +const +deleteasync +deletesync +deltacallbackfunction +deltacallbackwrapper +developerguide +deviceon +documentlength +endcond +endif +expectedtype +getasync +getsync +html +http +https +ifndef +init +int +iot +iotlink +iotmqttconnection +iotmqttpublishinfo +iotmqttqos +iotsemaphore +isn +json +malloc +metadata +mqtt +mqttconnection +mutex +mutexes +onlinepubs +opengroup +operationmatchparams +org +param +pcallbackcontext +pcallbackparam +pclienttoken +pdeltacallback +pdocument +pinuseshadowoperations +pinuseshadowsubscriptions +processretrieveddocument +pshadowoperations +pshadowsubscriptions +psubscription +pthingname +pupdatedcallback +pupdatedocument +qos +removepersistentsubscriptions +retrylimit +retryms +sdk +setdeltacallback +setupdatedcallback +shadowoperation +shadowoperationtype +shadowsubscription +strerror +structs +suback +thingnamelength +uint +updateasync +updatecomplete +updatedcallbackfunction +updatedcallbackwrapper +updatedocumentlength +updateinfo +updatesync +vsnprintf +waitable +waitsem +waitsemaphore diff --git a/libraries/platform/lexicon.txt b/libraries/platform/lexicon.txt new file mode 100644 index 0000000000..309feb0a90 --- /dev/null +++ b/libraries/platform/lexicon.txt @@ -0,0 +1,64 @@ +addresslength +api +aws +clientcertsize +closecallback +com +config +const +createdetachedthread +endif +gettcpconnections +gettimems +gettimestring +https +ifndef +init +iot +iotlink +iotmetricstcpconnection +iotnetworkclose +iotnetworkcreate +iotnetworkcredentials +iotnetworkdestroy +iotnetworkreceive +iotnetworksend +iotnetworksetclosecallback +iotnetworksetreceivecallback +mqtt +mutex +mutexcreate +mutexdestroy +mutexlock +mutextrylock +mutexunlock +passwordsize +pclientcert +phostname +pnetworkcontext +ppassword +pprivatekey +premoteaddress +privatekeysize +prootca +pusername +receivecallback +rootcasize +semaphorecreate +semaphoredestroy +semaphoregetcount +semaphorepost +semaphoretimedwait +semaphoretrywait +semaphorewait +setclosecallback +setreceivecallback +sleepms +threadroutine +timerarm +timercreate +timerdestroy +tls +uint +username +usernamesize diff --git a/libraries/standard/common/lexicon.txt b/libraries/standard/common/lexicon.txt new file mode 100644 index 0000000000..8504e952f4 --- /dev/null +++ b/libraries/standard/common/lexicon.txt @@ -0,0 +1,132 @@ +activejobs +activethreads +bool +cancelable +cas +config +const +createjob +createrecyclablejob +createsystemtaskpool +de +debuggability +dec +dequeuehead +dequeuetail +destroyrecyclablejob +dispatchqueue +dispatchsignal +endcond +endif +enqueuehead +enqueuetail +expirationtime +fifo +findfirstmatch +findfree +freecount +freelist +freemessagebuffer +genericprintbuffer +getjobstoragefromhandle +getstatus +getsystemtaskpool +hidelibraryname +hideloglevel +hidetimestring +html +http +iaddend +ifndef +inc +init +insertafter +insertbefore +inserthead +insertsorted +inserttail +int +iot +iotdequeue +iotlink +iotlistdouble +iotlog +iotmutex +iotsemaphore +iottaskpool +iottaskpoolinfo +iottaskpooljob +iottaskpooljobstatus +iottaskpooljobstorage +iottaskpoolroutine +iottimer +isempty +islinked +ismatch +isn +jobscache +malloc +mallocmessagebuffer +maxthreads +mem +messagebuffersize +min +minthreads +mutex +nand +ok +onlinepubs +opengroup +org +pbuffer +peekhead +peektail +pheader +pinusemessagebuffers +pinusetaskpooljobs +pinusetaskpools +pinusetaskpooltimerevents +pjob +plist +pmessagebuffer +pmessagebuffers +pnext +pobjects +ppool +pprevious +printbuffer +ptaskpool +ptaskpooljobs +ptaskpools +ptaskpooltimerevents +pusercontext +recyclejob +removeall +removeallmatches +removefirstmatch +removehead +removetail +returninuse +scheduledeferred +sdk +setmaxthreads +shouldn +stacksize +startstopsignal +stdout +strerror +struct +structs +synch +taskpool +taskpoolcache +taskpooljob +taskpools +taskpooltimerevent +timereventslist +timestring +trycancel +uaddend +uint +usercallback +xor diff --git a/libraries/standard/common/src/iot_static_memory_common.c b/libraries/standard/common/src/iot_static_memory_common.c index f289837933..003e3358c9 100644 --- a/libraries/standard/common/src/iot_static_memory_common.c +++ b/libraries/standard/common/src/iot_static_memory_common.c @@ -109,7 +109,7 @@ void IotStaticMemory_ReturnInUse( void * ptr, size_t i = 0; uint8_t * pElement = NULL; - /* Clear ptr. */ + /* Clear memory region at this address. */ ( void ) memset( ptr, 0x00, elementSize ); for( i = 0; i < limit; i++ ) diff --git a/libraries/standard/common/src/private/iot_taskpool_internal.h b/libraries/standard/common/src/private/iot_taskpool_internal.h index 9364da2250..d8e779197c 100644 --- a/libraries/standard/common/src/private/iot_taskpool_internal.h +++ b/libraries/standard/common/src/private/iot_taskpool_internal.h @@ -251,7 +251,7 @@ */ typedef struct _taskPoolCache { - IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ + IotListDouble_t freeList; /**< @brief A list of hold cached jobs. */ uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ } _taskPoolCache_t; diff --git a/libraries/standard/common/test/unit/iot_tests_taskpool.c b/libraries/standard/common/test/unit/iot_tests_taskpool.c index fa2933018c..41af486bc8 100644 --- a/libraries/standard/common/test/unit/iot_tests_taskpool.c +++ b/libraries/standard/common/test/unit/iot_tests_taskpool.c @@ -921,13 +921,13 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } - /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ + /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high priority task can make it grow more. */ for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_THREADS; ++count ) { TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } - /*Schedule a high pri task can make it grow more. */ + /*Schedule a high priority task can make it grow more. */ TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); count = 0; diff --git a/libraries/standard/mqtt/include/iot_mqtt_serialize.h b/libraries/standard/mqtt/include/iot_mqtt_serialize.h index 1ca98a616d..cc1562570d 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_serialize.h +++ b/libraries/standard/mqtt/include/iot_mqtt_serialize.h @@ -546,7 +546,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, * * Example * @code{c} - * // Example code below shows how to implement getNetxByte function with posix sockets. + * // Example code below shows how to implement getNextByte function with posix sockets. * // Note: IotMqttGetNextByte_t typedef IotMqttError_t (* IotMqttGetNextByte_t)( void * pNetworkContext, * // uint8_t * pNextByte ); * // Note: It is assumed that socket is already created and connected, diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt new file mode 100644 index 0000000000..90f74c014f --- /dev/null +++ b/libraries/standard/mqtt/lexicon.txt @@ -0,0 +1,197 @@ +ack +api +app +aws +awsiotmqttmode +bool +clientid +clientidentifierlength +com +config +connack +connectserializeroverride +const +createnetworkconnection +dataindex +datalength +deserialization +deserialize +deserialized +deserializepublish +deserializer +deserializeresponse +deserializing +developerguide +disconnectcallback +disconnectreason +disconnectserializeroverride +doesn +doxygen +dup +dupchecker +endcond +endif +exactmatchonly +expectedoperation +freepacket +freepacketoverride +getconnectpacketsize +getincomingmqttpackettypeandlength +getnextbyte +getpublishpacketsize +getsubscriptionpacketsize +gr +hasn +html +http +https +ifdef +ifndef +incomingpublish +init +initserializeadditional +int +iot +iotlink +iotlistdouble +iotmqtt +iotmqttcallbackinfo +iotmqttconnectinfo +iotmqttconnection +iotmqttdisconnectreason +iotmqtterror +iotmqttgetnextbyte +iotmqttnetworkinfo +iotmqttoperation +iotmqttoperationtype +iotmqttpacketinfo +iotmqttpublishinfo +iotmqttqos +iotmqttserializer +iotmqttsubscription +iotmutex +iotnetworkinterface +iotsemaphore +iottaskpooljob +iottaskpooljobstorage +isn +issubscribed +jobreference +jobstorage +keepalivems +keepaliveseconds +lu +lwt +malloc +mqtt +mqttconnection +mqttoperation +mqttpacketinfo +mqttsubscription +mutex +mutexes +nextperiodms +onlinepubs +opengroup +operationcomplete +operationtype +org +ownnetworkconnection +packetidentifier +packetinfo +packetsize +paramameters +passwordlength +payloadlength +pbuffer +pcallbackcontext +pclientidentifier +pdata +pdestination +pendingprocessing +pendingresponse +pingreq +pingresp +pinusemqttconnections +pinusemqttoperations +pinusemqttsubscriptions +pmqttconnection +pmqttconnections +pmqttoperations +pmqttpacket +pmqttsubscriptions +pnetworkconnection +pnetworkcontext +pnetworkinterface +pnextbyte +poperation +posix +ppacketidentifier +ppacketidentifierhigh +ppassword +ppayload +ppublish +ppublishinfo +pre +preceiveddata +premainingdata +pserializer +ptopicfilter +ptopicname +puback +pubackserializeroverride +pubinfo +publishasync +publishinfo +publishserializeroverride +publishsync +pusername +qos +receivecallback +referencesmutex +remaininglength +retrylimit +retryms +sdk +serializeconnect +serializedisconnect +serializepingreq +serializepublish +serializesubscribe +serializeunsubscribe +sizeof +sp +standalone +strerror +struct +structs +suback +subacks +subsciption +subscribeasync +subscribeserializeroverride +subscribesync +subscriptionlist +subscriptionmutex +tcp +timeoutms +toc +topicfilterlength +topicmatchparams +topicnamelength +ucsharedbuffer +uint +unsuback +unsubscribeasync +unsubscribeserializeroverride +unsubscribesync +username +usernamelength +utf +waitable +waitsem +waitsemaphore +wasn +weren +xmqttsocket +xpacketsize diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 0dc28262cb..f0c81adb27 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -261,7 +261,7 @@ */ #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ #define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ #define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ #define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ #define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ diff --git a/libraries/standard/serializer/include/iot_serializer.h b/libraries/standard/serializer/include/iot_serializer.h index 87ef663f3e..948868ebe0 100644 --- a/libraries/standard/serializer/include/iot_serializer.h +++ b/libraries/standard/serializer/include/iot_serializer.h @@ -293,7 +293,7 @@ typedef struct IotSerializerDecoderObject { /* Useful if the type is a container. */ void * pHandle; - /* if the type is a container, the scalarValue is unuseful */ + /* if the type is a container, the scalarValue is not useful */ IotSerializerScalarValue_t value; } u; } IotSerializerDecoderObject_t; diff --git a/libraries/standard/serializer/lexicon.txt b/libraries/standard/serializer/lexicon.txt new file mode 100644 index 0000000000..94b0b880ea --- /dev/null +++ b/libraries/standard/serializer/lexicon.txt @@ -0,0 +1,40 @@ +api +aws +bool +cbor +cborencoder +cborerror +cborvalue +com +config +decoderobject +endif +freertos +getbufferaddress +getsizeofencodeddata +html +http +ifndef +init +iot +isoutermost +json +malloc +memcpy +nextvalue +onlinepubs +opengroup +org +outmost +pdecoderobject +pencoderobject +phandle +pserializererror +pvalue +pvalueobject +scalarvalue +sdk +sizeof +tinycbor +todo +www diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c index c804eff014..ec02e948d0 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c @@ -141,7 +141,7 @@ static IotSerializerError_t _init( IotSerializerEncoderObject_t * pEncoderObject /* Always set outmost type to IOT_SERIALIZER_CONTAINER_STREAM. */ pEncoderObject->type = IOT_SERIALIZER_CONTAINER_STREAM; - /* Perfomr the tinycbor init. */ + /* Perform the tinycbor init. */ cbor_encoder_init( pCborEncoder, pDataBuffer, maxSize, unusedCborFlags ); } else diff --git a/ports/common/lexicon.txt b/ports/common/lexicon.txt new file mode 100644 index 0000000000..04f9f2b623 --- /dev/null +++ b/ports/common/lexicon.txt @@ -0,0 +1,62 @@ +alpn +authmode +bool +ca +callbackmutex +clientcert +closecallback +compareandswap +config +contextmutex +crt +destroynotification +dns +endcond +endif +gcc +gnuc +html +http +ifdef +ifndef +int +io +iot +iotmutex +iotnetworkclosecallback +iotnetworkreceivecallback +iotsemaphore +ip +malloc +mbed +mbedtls +mfln +mutex +nand +networkcontext +onlinepubs +opengroup +openssl +org +paddressinfo +pclosecontext +phostname +posix +preceivecontext +privatekey +psslcontext +pthread +receivecallback +receivethread +receivethreadcreated +recv +rng +rootca +sigpipe +sni +ssl +sslmutex +tcp +tls +uint +xor diff --git a/ports/posix/lexicon.txt b/ports/posix/lexicon.txt new file mode 100644 index 0000000000..ce50422af5 --- /dev/null +++ b/ports/posix/lexicon.txt @@ -0,0 +1,20 @@ +config +endcond +endif +html +http +ifndef +iot +iotthreadroutine +localtime +malloc +mutex +onlinepubs +opengroup +org +pargument +posix +strftime +struct +threadroutine +timespec diff --git a/ports/template/lexicon.txt b/ports/template/lexicon.txt new file mode 100644 index 0000000000..c7789e9353 --- /dev/null +++ b/ports/template/lexicon.txt @@ -0,0 +1,29 @@ +aws +com +config +createdetachedthread +endif +freertos +gettimems +gettimestring +html +https +ifndef +iot +mutexcreate +mutexdestroy +mutexlock +mutextrylock +mutexunlock +sdk +semaphorecreate +semaphoredestroy +semaphoregetcount +semaphorepost +semaphoretimedwait +semaphoretrywait +semaphorewait +sleepms +timerarm +timercreate +timerdestroy diff --git a/scripts/ablexicon b/scripts/ablexicon new file mode 100755 index 0000000000..c79c018d91 --- /dev/null +++ b/scripts/ablexicon @@ -0,0 +1,96 @@ +#!/bin/bash +# +# ablexicon - Compare an input list of words against a dictionary and +# optional lexicon. If any words are in neither the dictionary nor the +# lexicon, log them to stdout. +# +set -e +set -f + +function usage () { + echo "Find occurrences of non-dictionary/lexicon words" + echo "" + echo "Usage:" + echo " ${0##*/} [options]" + echo "" + echo "Options:" + echo " -f, --file source text (defaults to /dev/fd/0)" + echo " -l, --lexicon lexicon file (one word per line)" + echo " -h, --help display this help" + exit 1 +} + +# +# Verify that required commands are present +# +REQUIRED=( "spell" "getopt" ) +for i in "${REQUIRED[@]}" +do + command -v $i"" >/dev/null + if [ $? -ne "0" ] + then + echo "'"$i"' must be installed, exiting...">&2 + exit 1 + fi +done + +GETOPT_OUT=`getopt -o hf:l: --long help,file:,lexicon: -n "${0##*/}" -- "$@"` +if [ $? != 0 ] +then + echo "Exiting..." >&2 + exit 1 +fi + +eval set -- "$GETOPT_OUT" + +INFILE=/dev/fd/0 +LEXICON=/dev/null +while true; do + case "$1" in + -h | --help ) usage $0 ;; + -f | --file ) INFILE="$2"; shift 2 ;; + -l | --lexicon ) LEXICON="$2"; shift 2 ;; + -- ) shift; break ;; + * ) break ;; + esac +done + +if [ ! -f $INFILE"" ] && [ $INFILE"" != /dev/fd/0 ] +then + echo "Invalid input file" + usage +fi +# +# Read the lexicon into an array +# +readarray -t lexicon < $LEXICON"" +lexicon_size="${#lexicon[@]}" + +# +# Search for all input words in the dictionary +# and sort the output +# +for word in `cat $INFILE"" | spell | sort -u` +do + # + # Search for each remaining word in the lexicon + # + found="false" + i="0" + while [[ "$i" -lt "$lexicon_size" ]] && [ "$found" == "false" ] + do + if [ "${lexicon[i]}" == "$word" ] + then + found="true" + fi + i=$((i+1)) + done + if [ $found"" == "false" ] + then + # + # The word is neither in the dictionary nor the lexicon, send + # it to stdout. + # + echo $word + fi +done diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh old mode 100644 new mode 100755 diff --git a/scripts/ci_test_spelling.sh b/scripts/ci_test_spelling.sh new file mode 100755 index 0000000000..1e9428e6ee --- /dev/null +++ b/scripts/ci_test_spelling.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# Check arguments when running locally. If running on CI, install dependencies. +if [ -z "$TRAVIS_PULL_REQUEST" ]; then + if [ $# -ne 1 ]; then + echo "Usage: ${0##*/} sdk_root_directory" + exit 1 + fi + + # Change to SDK root directory. + cd $1 +else + set -ev + cd .. # change to SDK root directory +fi +PATH=$PATH:$PWD/scripts + +# Check for find-unknown-comment-words. +command -v find-unknown-comment-words > /dev/null || { echo "Can't find spellcheck script, exiting."; exit 1; } + +STATUS= +# +# Check spelling in all directories with a 'lexicon.txt' file. +# +for lexfile in `find . -name lexicon.txt` +do + dir=${lexfile%/lexicon.txt} + find-unknown-comment-words --directory $dir + if [ $? -ne "0" ] + then + STATUS=1 + fi +done + +if [ $STATUS"" = "1" ] +then + exit 1 +fi diff --git a/scripts/extract-comments b/scripts/extract-comments new file mode 100755 index 0000000000..861e77e5d1 --- /dev/null +++ b/scripts/extract-comments @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Extract comments from C/C++ files +# +set -e +set -f + +function usage () { + echo "Extract comments from C/C++ files" + echo "" + echo "usage: "${0##*/}" file-list" + exit 1 +} + +if [ $# -lt 1 ] +then + usage $0 +fi + +if [ $1 = "-h" ] || [ $1 == "--help" ] +then + usage $0 +fi + +while test $# -gt 0 +do + if [ ! -f $1 ] + then + echo $0": '"$1"' is not a file." 2>/dev/null + exit 1 + fi +# +# Extract all words from C/C++ language comments; add line +# numbers to aid in searching. +# +# NOTE: This has some limitations. For example, it prints +# non-comment text at the beginning of a comment line. +# + nl -ba $1 | awk '/\/\// {print $0}; /\/\*/ {comment=1; if(comment) print $0}; /\*\// {comment=0}' + shift +done diff --git a/scripts/find-unknown-comment-words b/scripts/find-unknown-comment-words new file mode 100755 index 0000000000..028df0cc83 --- /dev/null +++ b/scripts/find-unknown-comment-words @@ -0,0 +1,143 @@ +#!/bin/bash +# +# Locate unknown words in C/C++ comments. Uses "extract-comments" +# and "ablexicon" scripts. +# +set -o nounset +set -o pipefail +set -o errexit +set -f + +BLUE="\e[1;34m" +GREEN="\e[1;32m" +DEFAULTFG="\e[39m" + +function usage () { + echo "Find unknown words in C/C++ comments" + echo "" + echo "Usage:" + echo " ${0##*/} [options]" + echo "" + echo "Options:" + echo " -d, --directory directory to scan (defaults to .)" + echo " -l, --lexicon lexicon file (one word per line, default 'lexicon.txt')" + echo " -t, --terse terse output only (enabled if no lexicon available)" + echo " -h, --help display this help" + exit 1 +} + +# +# Verify that required commands are present +# +REQUIRED=( "extract-comments" "ablexicon" "getopt" ) +for i in "${REQUIRED[@]}" +do + command -v $i"" >/dev/null + if [ $? -ne "0" ] + then + echo "Can't find '"$i"' , exiting...">&2 + exit 1 + fi +done + +GETOPT_OUT=`getopt -o htd:l: --long help,terse,directory:,lexicon: -n "${0##*/}" -- "$@"` +if [ $? != 0 ] +then + echo "Exiting..." >&2 + exit 1 +fi + +eval set -- "$GETOPT_OUT" + +DIRNAME=/dev/fd/0 +LEXICON= +STATUS= +TERSE= +while true; do + case "$1" in + -h | --help ) usage $0 ;; + -t | --terse ) TERSE=1; shift ;; + -d | --directory ) DIRNAME="$2"; shift 2 ;; + -l | --lexicon ) LEXICON="$2"; shift 2 ;; + -- ) shift; break ;; + * ) break ;; + esac +done + +if [ ! -d $DIRNAME"" ] +then + echo "Invalid directory: "$DIRNAME + usage +fi + +if [ $LEXICON"" = "" ] +then + if [ -f $DIRNAME/lexicon.txt ] + then + LEXICON=$DIRNAME/lexicon.txt + else + LEXICON=/dev/null + TERSE=1 + fi +fi + +TMPFILE=${0##*/}-$USER-$RANDOM +unknowns=( "not-used" ) # get around empty array with nounset +extract-comments `find $DIRNAME -name \*.[ch]` | + tr [:upper:] [:lower:] | + grep -o -E '[a-zA-Z]+' | + ablexicon -l $LEXICON > $TMPFILE +readarray -O 1 -t unknowns < $TMPFILE +rm -f $TMPFILE + +for word in "${unknowns[@]}" +do + if [ $word"" == "not-used" ] + then + continue + fi + + if [ $TERSE"" != "" ] + then + echo $word + continue + fi + + for file in `find $DIRNAME -name \*.[ch]` + do + # Disable errexit here, extract-comments can return non-zero + set +e + # + # A little inefficient here; we will grep twice, once to detect + # the unknown word and another to print it with color highlighting. + # If there's a way to preserve ANSI color output with the first + # search and reuse it within the if statement (I gave up trying + # to find one after a few minutes), that would be nice. + # + extract-comments $file | grep -iw $word > /dev/null + if [ $? == "0" ] + then + if [ $STATUS"" != "1" ] + then + echo -e $GREEN"############################################################################"$DEFAULTFG + echo -e $GREEN"#"$DEFAULTFG + echo -e $GREEN"# Unknown word(s) found. Please either correct the spelling or add them"$DEFAULTFG + echo -e $GREEN"# to the lexicon file '"$LEXICON"'".$DEFAULTFG + echo -e $GREEN"#"$DEFAULTFG + echo -e $GREEN"############################################################################"$DEFAULTFG + STATUS=1 # Return non-zero status if any unidentified words are found + fi + echo "" + echo -e $BLUE$file$DEFAULTFG + echo "" + extract-comments $file | grep --color=always -iw $word | GREP_COLORS="mt=01;32" grep --color=always -E -e '^[ \t]*[0-9]+' + fi + # Re-enable errexit + set -o errexit + done +done + +if [ $STATUS"" = "1" ] +then + exit 1 +fi diff --git a/scripts/setup/ci_setup_linux.sh b/scripts/setup/ci_setup_linux.sh index 6c6ec4c272..d912c969da 100755 --- a/scripts/setup/ci_setup_linux.sh +++ b/scripts/setup/ci_setup_linux.sh @@ -21,6 +21,11 @@ if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then if [ "$RUN_TEST" = "doc" ]; then sudo apt-get install -y graphviz; fi + # Install util-linux and spell for spelling checks. + if [ "$RUN_TEST" = "spelling" ]; then + sudo apt-get -y install util-linux # for gnu getopt + sudo apt-get -y install spell # for spell + fi # Set up for coverage builds. else # Install dependencies for Jobs tests. diff --git a/tests/lexicon.txt b/tests/lexicon.txt new file mode 100644 index 0000000000..70ac516544 --- /dev/null +++ b/tests/lexicon.txt @@ -0,0 +1,15 @@ +aws +config +endif +glibc +ifndef +iot +iotlogging +malloc +mbed +mosquitto +mqtt +mutex +openssl +staticbuffersize +tls From a87281770f0105471168b12b6123f66d42e206fa Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Mon, 25 Nov 2019 17:45:27 -0800 Subject: [PATCH 338/844] fix: mqtt demo default arguments. (#659) Default arguments for username was getting overwritten. --- demos/app/iot_demo_arguments.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index 395a6a3a89..4e05ac156c 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -96,7 +96,7 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) /* Set default MQTT broker password if defined. */ #ifdef IOT_DEMO_PASSWORD - pArguments->pUserName = IOT_DEMO_PASSWORD; + pArguments->pPassword = IOT_DEMO_PASSWORD; #endif } From 8ce82fb3612dbec67111942a7d79a53b7b0d58b7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 26 Nov 2019 13:03:48 -0800 Subject: [PATCH 339/844] Update porting template for network types (#658) --- .../include/iot_platform_types_template.h | 15 +++++++++++++++ ports/template/template.cmake | 19 ++----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ports/template/include/iot_platform_types_template.h b/ports/template/include/iot_platform_types_template.h index f39a22828d..d795d6fa1e 100644 --- a/ports/template/include/iot_platform_types_template.h +++ b/ports/template/include/iot_platform_types_template.h @@ -42,4 +42,19 @@ typedef void * _IotSystemSemaphore_t; */ typedef void * _IotSystemTimer_t; +/** + * @brief The format for remote server host and port on this system. + */ +typedef void * _IotNetworkServerInfo_t; + +/** + * @brief The format for network credentials on this system. + */ +typedef void * _IotNetworkCredentials_t; + +/** + * @brief The handle of a network connection on this system. + */ +typedef void * _IotNetworkConnection_t; + #endif /* ifndef IOT_PLATFORM_TYPES_TEMPLATE_H_ */ diff --git a/ports/template/template.cmake b/ports/template/template.cmake index 15e97bed11..f33a79ebaf 100644 --- a/ports/template/template.cmake +++ b/ports/template/template.cmake @@ -4,25 +4,10 @@ # Warn that the template port only builds. It will not create usable libraries. message( WARNING "This is a template port that contains only stubs. Libraries built with this port will not work!") -# Add the mbed TLS header in the template. The mbed TLS network port is supposed -# to be platform-independent, so it is built in the template. -set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} - ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) - -# Template platform sources. Except for the network sources, these files contain -# only stubs. +# Template platform sources. A network implementation (not listed below) is also needed. set( PLATFORM_SOURCES - # Stubs ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c - - # Network sources - ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c - ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ) + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c ) # Set the types header for this port. set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) - -# Set the dependencies required by this port. -# As this template uses the mbed TLS network implementation, mbed TLS is linked. -set( PLATFORM_DEPENDENCIES mbedtls ) From 3c909bccfd0c224ac1415ce413fa347ba80f6dc1 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 5 Dec 2019 15:54:32 -0800 Subject: [PATCH 340/844] Refactor coverage build script for hygiene improvements (#673) --- scripts/ci_test_common.sh | 13 ++++--- scripts/ci_test_coverage.sh | 75 ++++++++++++++++--------------------- scripts/ci_test_jobs.sh | 19 ++++++---- scripts/ci_test_mqtt.sh | 25 ++++++++----- scripts/ci_test_shadow.sh | 19 +++++++--- 5 files changed, 80 insertions(+), 71 deletions(-) diff --git a/scripts/ci_test_common.sh b/scripts/ci_test_common.sh index 466a02d707..8c41191954 100755 --- a/scripts/ci_test_common.sh +++ b/scripts/ci_test_common.sh @@ -15,9 +15,12 @@ make -j2 iot_tests_common # Run common tests. ./output/bin/iot_tests_common -# Rebuild in static memory mode. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 iot_tests_common +# Don't reconfigure CMake if script is invoked for coverage build. +if [ "$RUN_TEST" != "coverage" ]; then + # Rebuild in static memory mode. + cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" + make -j2 iot_tests_common -# Run common tests in static memory mode. -./output/bin/iot_tests_common + # Run common tests in static memory mode. + ./output/bin/iot_tests_common +fi \ No newline at end of file diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 9656ad8faf..3109e4676b 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -4,59 +4,50 @@ # It does not run tests that require the network. # Exit on any nonzero return code. -set -ev - -# Query the AWS account ID. -AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account') - -# Prefix of Job IDs used in the tests, set by the create_jobs function. -JOB_PREFIX="" - -# Function to create Jobs to use in the tests. -create_jobs() { - JOB_PREFIX=$IOT_IDENTIFIER-$(jot -r 1 1 100000) - aws iot create-job \ - --job-id $JOB_PREFIX-1 \ - --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ - --document '{"action":"print","message":"Hello world!"}' - aws iot create-job \ - --job-id $JOB_PREFIX-2 \ - --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ - --document '{"action":"print","message":"Hello world!"}' +set -e + +# Function that generates code coverage report from the gcov files for a library. (it ignores all non-production code) +function generate_coverage() { + if [ $# -ne 1 ]; then + echo '"generate_coverage" requires input argument of coverage filename.' + exit 1 + fi + + # Generate code coverage results, but only for files in libraries/. + lcov --directory . --capture --output-file $1 + lcov --remove $1 '*demo*' --output-file $1 + lcov --remove $1 '*ports*' --output-file $1 + lcov --remove $1 '*tests*' --output-file $1 + lcov --remove $1 '*third_party*' --output-file $1 } -# Function to delete Jobs and clean up the tests. -delete_jobs() { - aws iot delete-job --job-id $JOB_PREFIX-1 --force - aws iot delete-job --job-id $JOB_PREFIX-2 --force -} +# Overwrite the value of the COMPILER_OPTIONS varirable to remove any thread sanitizer flags, and replace with coverage flags. +export COMPILER_OPTIONS="-DIOT_TEST_COVERAGE=1 --coverage" -# Build tests and demos against AWS IoT with coverage. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_JOBS_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" -make -j2 +SCRIPTS_FOLDER_PATH=../scripts # Run common tests with code coverage. -./output/bin/iot_tests_common +$SCRIPTS_FOLDER_PATH/ci_test_common.sh +generate_coverage common.info # Run MQTT tests against AWS IoT with code coverage. -./output/bin/iot_tests_mqtt +$SCRIPTS_FOLDER_PATH/ci_test_mqtt.sh +generate_coverage mqtt.info # Run Shadow tests with code coverage. -./output/bin/aws_iot_tests_shadow +$SCRIPTS_FOLDER_PATH/ci_test_shadow.sh +generate_coverage shadow.info # Run Jobs tests with code coverage. -create_jobs -trap "delete_jobs" EXIT -./output/bin/aws_iot_tests_jobs -delete_jobs -trap - EXIT - -# Generate code coverage results, but only for files in libraries/. -lcov --directory . --capture --output-file coverage.info -lcov --remove coverage.info '*demo*' --output-file coverage.info -lcov --remove coverage.info '*ports*' --output-file coverage.info -lcov --remove coverage.info '*tests*' --output-file coverage.info -lcov --remove coverage.info '*third_party*' --output-file coverage.info +$SCRIPTS_FOLDER_PATH/ci_test_jobs.sh +generate_coverage jobs.info + +# Combine the coverage files of all libraries into a single master coverage file. +lcov --add-tracefile common.info \ + --add-tracefile mqtt.info \ + --add-tracefile shadow.info \ + --add-tracefile jobs.info \ + --output-file coverage.info # Submit the code coverage results. Must be submitted from SDK root directory so # Coveralls displays the correct paths. diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh index f62ddc9469..5b38786600 100755 --- a/scripts/ci_test_jobs.sh +++ b/scripts/ci_test_jobs.sh @@ -57,13 +57,16 @@ if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then trap "delete_jobs" EXIT; fi run_tests if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi -# Rebuild in static memory mode. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 aws_iot_tests_jobs - -# Run tests in static memory mode. -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi -run_tests -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi +# Don't reconfigure CMake if script is invoked for coverage build. +if [ "$RUN_TEST" != "coverage" ]; then + # Rebuild in static memory mode. + cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" + make -j2 aws_iot_tests_jobs + # Run tests in static memory mode. + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi + run_tests + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi +fi + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then trap - EXIT; fi diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index e5dd2f586e..8e4adc3839 100755 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -5,7 +5,7 @@ # Exit on any nonzero return code. set -e -CMAKE_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" +CMAKE_NON_CREDENTIAL_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" TEST_OPTIONS="" DEMO_OPTIONS="-i $IOT_IDENTIFIER" @@ -16,14 +16,16 @@ DEMO_OPTIONS="-i $IOT_IDENTIFIER" if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then if [ "$TRAVIS_OS_NAME" = "linux" ]; then # Set the flags and options for a local Mosquitto broker on Linux. - CMAKE_FLAGS+=" -DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\"" + CMAKE_CREDENTIAL_FLAGS+=" -DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\"" DEMO_OPTIONS+=" -n -u -h localhost -p 1883" fi else # Set credentials for AWS IoT. - CMAKE_FLAGS+=" $AWS_IOT_CREDENTIAL_DEFINES" + CMAKE_CREDENTIAL_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES" fi +CMAKE_FLAGS="$CMAKE_NON_CREDENTIAL_FLAGS $CMAKE_CREDENTIAL_FLAGS" + # Build and run executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" @@ -31,13 +33,16 @@ make -j2 iot_tests_mqtt iot_demo_mqtt ./output/bin/iot_tests_mqtt $TEST_OPTIONS -if [ "$TRAVIS_OS_NAME" = "linux" ]; then - ./output/bin/iot_demo_mqtt $DEMO_OPTIONS -fi +# Don't reconfigure CMake if script is invoked for coverage build. +if [ "$RUN_TEST" != "coverage" ]; then + if [ "$TRAVIS_OS_NAME" = "linux" ]; then + ./output/bin/iot_demo_mqtt $DEMO_OPTIONS + fi -# Rebuild and run tests in static memory mode. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" + # Rebuild and run tests in static memory mode. + cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 iot_tests_mqtt iot_demo_mqtt + make -j2 iot_tests_mqtt iot_demo_mqtt -./output/bin/iot_tests_mqtt $TEST_OPTIONS + ./output/bin/iot_tests_mqtt $TEST_OPTIONS +fi diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index 780a5886c1..df56f4b0d1 100755 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -13,7 +13,11 @@ run_tests_and_demos() { if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./output/bin/aws_iot_tests_shadow sleep 1.1 - ./output/bin/aws_iot_demo_shadow + + # Don't reconfigure CMake if script is invoked for coverage build. + if [ "$RUN_TEST" != "coverage" ]; then + ./output/bin/aws_iot_demo_shadow + fi else # Run only Shadow unit tests. ./output/bin/aws_iot_tests_shadow -n @@ -30,9 +34,12 @@ make -j2 aws_iot_tests_shadow aws_iot_demo_shadow # Run tests and demos. run_tests_and_demos -# Rebuild in static memory mode. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 aws_iot_tests_shadow aws_iot_demo_shadow +# Don't reconfigure CMake if script is invoked for coverage build. +if [ "$RUN_TEST" != "coverage" ]; then + # Rebuild in static memory mode. + cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" + make -j2 aws_iot_tests_shadow aws_iot_demo_shadow -# Run tests and demos in static memory mode. -run_tests_and_demos + # Run tests and demos in static memory mode. + run_tests_and_demos +fi \ No newline at end of file From 38f09a25e9e683a2f65b56ccada36c4a5f5581d8 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Fri, 6 Dec 2019 14:32:26 -0800 Subject: [PATCH 341/844] fix: MQTT lib warnings when asserts are disabled. (#674) --- .../standard/mqtt/src/iot_mqtt_serialize.c | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 2a963e6ccc..485f89eb6d 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -277,7 +277,7 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, * @param[in] connectPacketSize Size of the buffer pointed to by `pBuffer`. * */ -void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, +static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, size_t remainingLength, uint8_t * pBuffer, size_t connectPacketSize ); @@ -294,7 +294,7 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, * @param[in] publishPacketSize Size of buffer pointed to by `pBuffer`. * */ -void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, +static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh, @@ -312,7 +312,7 @@ void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, * @param[in] subscribePacketSize Size of the buffer pointed to by `pBuffer`. * */ -void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, +static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, @@ -330,7 +330,7 @@ void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, * @param[in] unsubscribePacketSize size of the buffer pointed to by `pBuffer`. * */ -void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, +static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, @@ -658,7 +658,7 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /*-----------------------------------------------------------*/ -void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, +static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, size_t remainingLength, uint8_t * pBuffer, size_t connectPacketSize ) @@ -667,6 +667,10 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, uint8_t * pConnectPacket = pBuffer; bool encodedUserName = false; + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pConnectPacket; + ( void ) connectPacketSize; + /* The first byte in the CONNECT packet is the control packet type. */ *pBuffer = MQTT_PACKET_TYPE_CONNECT; pBuffer++; @@ -877,7 +881,7 @@ void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, /*-----------------------------------------------------------*/ -void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, +static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh, @@ -888,6 +892,10 @@ void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, uint16_t packetIdentifier = 0; uint8_t * pPublishPacket = pBuffer; + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pPublishPacket; + ( void ) publishPacketSize; + /* The first byte of a PUBLISH packet contains the packet type and flags. */ publishFlags = MQTT_PACKET_TYPE_PUBLISH; @@ -974,7 +982,7 @@ void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ -void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, +static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, @@ -985,6 +993,10 @@ void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t i = 0; uint8_t * pSubscribePacket = pBuffer; + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pSubscribePacket; + ( void ) subscribePacketSize; + /* The first byte in SUBSCRIBE is the packet type. */ *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; pBuffer++; @@ -1024,7 +1036,7 @@ void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, /*-----------------------------------------------------------*/ -void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, +static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, @@ -1034,6 +1046,10 @@ void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, uint16_t packetIdentifier = 0; size_t i = 0; uint8_t * pUnsubscribePacket = pBuffer; + + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pUnsubscribePacket; + ( void ) unsubscribePacketSize; /* The first byte in UNSUBSCRIBE is the packet type. */ *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; From 5b0ea3893f2beea29e6a6dc8c382b625b0dd183d Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 6 Dec 2019 15:29:15 -0800 Subject: [PATCH 342/844] Expand CI build checks for multiple build configurations (#661) --- .travis.yml | 8 +++++-- .../test/system/aws_iot_tests_jobs_system.c | 8 +++++++ .../test/system/aws_iot_tests_shadow_system.c | 24 +++++++++++++++++-- .../standard/mqtt/src/iot_mqtt_validate.c | 4 ++++ scripts/ci_test_build.sh | 7 +++++- scripts/ci_test_quality.sh | 11 +++++++++ scripts/setup/ci_setup_linux.sh | 6 +++++ 7 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 scripts/ci_test_quality.sh diff --git a/.travis.yml b/.travis.yml index 6c653b266e..8472d87fac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,10 @@ compiler: # Matrix of tests to run. jobs: include: + # Code checks. + - stage: Code checks # Build checks for pull requests. - - if: type = pull_request + if: type = pull_request env: RUN_TEST=build # Documentation check for pull requests. - if: type = pull_request @@ -24,8 +26,10 @@ jobs: # Spelling check for pull requests. - if: type = pull_request env: RUN_TEST=spelling + # Library tests. - - env: RUN_TEST=common + - stage: Tests + env: RUN_TEST=common - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - env: RUN_TEST=mqtt NETWORK_STACK=openssl - env: RUN_TEST=shadow diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c index 2fb03f74a6..0cfe67211b 100644 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c @@ -142,6 +142,9 @@ static void _operationComplete( void * pArgument, { _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; + /* Silence warnings when asserts are enabled. */ + ( void ) pOperation; + /* Check parameters against expected values. */ AwsIotJobs_Assert( pParams->expectedType == pOperation->callbackType ); AwsIotJobs_Assert( pParams->operation == pOperation->u.operation.reference ); @@ -174,6 +177,11 @@ static void _jobsCallback( void * pArgument, const char * pJobId = NULL; size_t jobIdLength = 0; + /* Silence warnings when asserts are disabled. */ + ( void ) pCallbackParam; + ( void ) pJobId; + ( void ) jobIdLength; + /* Check parameters against expected values. */ AwsIotJobs_Assert( pCallbackParam->mqttConnection == _mqttConnection ); AwsIotJobs_Assert( pCallbackParam->thingNameLength == sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); diff --git a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c index a0c6287d97..dd5bf63945 100644 --- a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c +++ b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c @@ -141,6 +141,10 @@ static void _operationComplete( void * pArgument, const char * pJsonValue = NULL; size_t jsonValueLength = 0; + /* Silence warnings when asserts are disabled. */ + ( void ) pJsonValue; + ( void ) jsonValueLength; + /* Check parameters against received operation information. */ AwsIotShadow_Assert( pOperation->callbackType == pParams->expectedType ); AwsIotShadow_Assert( pOperation->mqttConnection == _mqttConnection ); @@ -183,6 +187,13 @@ static void _deltaCallback( void * pArgument, const char * pValue = NULL, * pClientToken = NULL; size_t valueLength = 0, clientTokenLength = 0; + /* Silence warnings when asserts are disabled. */ + ( void ) pCallback; + ( void ) pValue; + ( void ) pClientToken; + ( void ) valueLength; + ( void ) clientTokenLength; + /* Check callback type and MQTT connection. */ AwsIotShadow_Assert( pCallback->callbackType == AWS_IOT_SHADOW_DELTA_CALLBACK ); AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); @@ -203,7 +214,7 @@ static void _deltaCallback( void * pArgument, "clientToken", 11, &pClientToken, - &clientTokenLength ) ); + &clientTokenLength ) == true ); AwsIotShadow_Assert( clientTokenLength == 12 ); AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); @@ -224,6 +235,15 @@ static void _updatedCallback( void * pArgument, const char * pPrevious = NULL, * pCurrent = NULL, * pClientToken = NULL; size_t previousStateLength = 0, currentStateLength = 0, clientTokenLength = 0; + /* Silence warnings when asserts are disabled. */ + ( void ) pCallback; + ( void ) pPrevious; + ( void ) pCurrent; + ( void ) pClientToken; + ( void ) previousStateLength; + ( void ) currentStateLength; + ( void ) clientTokenLength; + /* Check MQTT connection. */ AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); @@ -257,7 +277,7 @@ static void _updatedCallback( void * pArgument, "clientToken", 11, &pClientToken, - &clientTokenLength ) ); + &clientTokenLength ) == true ); AwsIotShadow_Assert( clientTokenLength == 12 ); AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 993e65a3b6..7456a9dffe 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -178,6 +178,7 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) } /*-----------------------------------------------------------*/ + static bool _validatePublish( bool awsIotMqttMode, size_t maximumPayloadLength, const char * pPublishTypeDescription, @@ -185,6 +186,9 @@ static bool _validatePublish( bool awsIotMqttMode, { IOT_FUNCTION_ENTRY( bool, true ); + /* This parameter is not used when logging is disabled. */ + ( void ) pPublishTypeDescription; + /* Check for NULL. */ if( pPublishInfo == NULL ) { diff --git a/scripts/ci_test_build.sh b/scripts/ci_test_build.sh index e576c455a0..b2da6663ab 100755 --- a/scripts/ci_test_build.sh +++ b/scripts/ci_test_build.sh @@ -16,5 +16,10 @@ make -j2 # Build tests. Enable all logging. rm -rf * -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" +cmake .. -DCMAKE_BUILD_TYPE=Debug -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" +make -j2 + +# Release build with logging disabled. +rm -rf * +cmake .. -DCMAKE_BUILD_TYPE=Release -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_NONE" make -j2 diff --git a/scripts/ci_test_quality.sh b/scripts/ci_test_quality.sh new file mode 100644 index 0000000000..8ef6461970 --- /dev/null +++ b/scripts/ci_test_quality.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Travis CI uses this script to test code quality. + +# Exit on any nonzero return code. +set -e + +# Get the list of files to check. Only check library files and exclude tests. +# Run complexity with a threshold of 8. +find ../libraries/ \( -name '*.c' ! -name *tests*.c \) -type f | \ +xargs complexity --scores --threshold=8 --horrid-threshold=8 diff --git a/scripts/setup/ci_setup_linux.sh b/scripts/setup/ci_setup_linux.sh index d912c969da..9f4e1717d9 100755 --- a/scripts/setup/ci_setup_linux.sh +++ b/scripts/setup/ci_setup_linux.sh @@ -21,11 +21,17 @@ if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then if [ "$RUN_TEST" = "doc" ]; then sudo apt-get install -y graphviz; fi + # Install util-linux and spell for spelling checks. if [ "$RUN_TEST" = "spelling" ]; then sudo apt-get -y install util-linux # for gnu getopt sudo apt-get -y install spell # for spell fi + + # Install complexity for complexity checks. + if [ "$RUN_TEST" = "quality" ]; then + sudo apt-get -y install complexity + fi # Set up for coverage builds. else # Install dependencies for Jobs tests. From 4ec51f0a77941d35a9356e9e6b829a5fe5387772 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 12 Dec 2019 17:06:41 -0500 Subject: [PATCH 343/844] Remove macros hiding the control flow (#681) --- libraries/standard/mqtt/src/iot_mqtt_api.c | 135 ++++--- .../standard/mqtt/src/iot_mqtt_network.c | 29 +- .../standard/mqtt/src/iot_mqtt_operation.c | 23 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 373 +++++++++++------- .../standard/mqtt/src/iot_mqtt_subscription.c | 28 +- .../standard/mqtt/src/iot_mqtt_validate.c | 113 ++++-- .../mqtt/test/mock/iot_tests_mqtt_mock.c | 18 +- 7 files changed, 428 insertions(+), 291 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 98eefebbe7..c9de27c0b4 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -31,9 +31,6 @@ /* Standard includes. */ #include -/* Error handling include. */ -#include "iot_error.h" - /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -437,14 +434,15 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * IotNetworkConnection_t * pNetworkConnection, bool * pCreatedNewNetworkConnection ) { - IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + IotNetworkError_t status = IOT_NETWORK_SUCCESS; /* Network info must not be NULL. */ if( pNetworkInfo == NULL ) { IotLogError( "Network information cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_BAD_PARAMETER ); + status = IOT_NETWORK_BAD_PARAMETER; + goto cleanup; } else { @@ -469,7 +467,7 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * { IotLogError( "Failed to create network connection: %d", status ); - IOT_GOTO_CLEANUP(); + goto cleanup; } } else @@ -480,7 +478,8 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * *pCreatedNewNetworkConnection = false; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -489,7 +488,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ) { - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; _mqttConnection_t * pMqttConnection = NULL; bool referencesMutexCreated = false, subscriptionMutexCreated = false; @@ -500,7 +499,8 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotLogError( "Failed to allocate memory for new connection." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -522,7 +522,8 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotLogError( "Failed to create references mutex for new connection." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -536,7 +537,8 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotLogError( "Failed to create subscription mutex for new connection." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -555,7 +557,8 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, keepAliveSeconds, pMqttConnection ) == false ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -568,7 +571,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } /* Clean up mutexes and connection if this function failed. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status == false ) { @@ -689,7 +692,7 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation uint32_t flags, IotMqttOperation_t * const pOperationReference ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; /* This function should only be called for subscribe or unsubscribe. */ IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || @@ -698,7 +701,8 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); + status = IOT_MQTT_NOT_INITIALIZED; + goto cleanup; } else { @@ -711,7 +715,8 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation pSubscriptionList, subscriptionCount ) == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -726,7 +731,8 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation IotLogError( "Reference must be provided for a waitable %s.", IotMqtt_OperationType( operation ) ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -738,7 +744,8 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -752,7 +759,7 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op const IotMqttCallbackInfo_t * pCallbackInfo, _mqttOperation_t ** ppSubscriptionOperation ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; _mqttOperation_t * pSubscriptionOperation = NULL; /* Create a subscription operation. */ @@ -763,7 +770,7 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -784,7 +791,7 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -795,7 +802,8 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -809,7 +817,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * const pOperationReference ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; _mqttOperation_t * pSubscriptionOperation = NULL; /* Create and serialize the subscription operation. */ @@ -824,7 +832,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -841,7 +849,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -897,12 +905,12 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, EMPTY_ELSE_MARKER; } - IOT_GOTO_CLEANUP(); + goto cleanup; } } /* Clean up if this function failed. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -924,7 +932,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotMqtt_OperationType( operation ) ); } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -1083,7 +1091,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, uint32_t timeoutMs, IotMqttConnection_t * const pMqttConnection ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; bool ownNetworkConnection = false; IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; @@ -1094,7 +1102,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); + status = IOT_MQTT_NOT_INITIALIZED; + goto cleanup; } else { @@ -1104,7 +1113,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Validate network interface and connect info. */ if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1117,7 +1127,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( networkStatus != IOT_NETWORK_SUCCESS ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + status = IOT_MQTT_NETWORK_ERROR; + goto cleanup; } else { @@ -1133,7 +1144,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pNewMqttConnection == NULL ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -1158,7 +1170,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, { IotLogError( "Failed to set MQTT network receive callback." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + status = IOT_MQTT_NETWORK_ERROR; + goto cleanup; } else { @@ -1173,7 +1186,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1203,7 +1216,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1222,7 +1235,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1257,7 +1270,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SCHEDULING_ERROR ); + status = IOT_MQTT_SCHEDULING_ERROR; + goto cleanup; } else { @@ -1274,7 +1288,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, EMPTY_ELSE_MARKER; } - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -1326,7 +1340,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, *pMqttConnection = pNewMqttConnection; } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -1343,7 +1357,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, if( initCalled == false ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1354,7 +1368,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * flag is not set. */ if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == IOT_MQTT_FLAG_CLEANUP_ONLY ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } /* Read the connection status. */ @@ -1364,7 +1378,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, if( disconnected == true ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); @@ -1433,7 +1447,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, /* This function has no return value and no cleanup, but uses the cleanup * label to exit on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( initCalled == true ) { @@ -1622,14 +1636,15 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * const pPublishOperation ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; _mqttOperation_t * pOperation = NULL; uint8_t ** pPacketIdentifierHigh = NULL; /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); + status = IOT_MQTT_NOT_INITIALIZED; + goto cleanup; } else { @@ -1640,7 +1655,8 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, pPublishInfo ) == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1654,13 +1670,15 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) { IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1688,7 +1706,8 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { IotLogError( "Reference must be provided for a waitable PUBLISH." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1708,7 +1727,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1738,7 +1757,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1818,7 +1837,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, EMPTY_ELSE_MARKER; } - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -1828,7 +1847,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, /* Clean up the PUBLISH operation if this function fails. Otherwise, set the * appropriate return code based on QoS. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -1856,7 +1875,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, mqttConnection ); } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -1922,7 +1941,8 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); + status = IOT_MQTT_NOT_INITIALIZED; + goto cleanup; } else { @@ -2026,7 +2046,8 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 19c3f60f4e..12ecc5cfca 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -31,9 +31,6 @@ /* Standard includes. */ #include -/* Error handling include. */ -#include "iot_error.h" - /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -199,7 +196,7 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t dataBytesRead = 0; /* No buffer for remaining data should be allocated. */ @@ -217,7 +214,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, pMqttConnection, pIncomingPacket->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -230,7 +228,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -252,7 +251,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -265,7 +265,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( dataBytesRead != pIncomingPacket->remainingLength ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -278,7 +279,7 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, } /* Clean up on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -296,7 +297,7 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, EMPTY_ELSE_MARKER; } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -576,7 +577,7 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } /* Set the operation type. */ @@ -589,7 +590,7 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, if( status != IOT_MQTT_SUCCESS ) { - IOT_GOTO_CLEANUP(); + goto cleanup; } /* Add the PUBACK operation to the send queue for network transmission. */ @@ -602,7 +603,7 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, IotLogError( "(MQTT connection %p) Failed to enqueue PUBACK for sending.", pMqttConnection ); - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -610,7 +611,7 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, } /* Clean up on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status != IOT_MQTT_SUCCESS ) { diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 5235fdf6a3..f49fdf04d4 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -31,9 +31,6 @@ /* Standard includes. */ #include -/* Error handling include. */ -#include "iot_error.h" - /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -59,7 +56,7 @@ _getMqttFreePacketFunc, _IotMqtt_FreePacket, freePacket ) -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket #define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -349,7 +346,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, const IotMqttCallbackInfo_t * pCallbackInfo, _mqttOperation_t ** pNewOperation ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; bool decrementOnError = false; _mqttOperation_t * pOperation = NULL; bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); @@ -361,7 +358,8 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { IotLogError( "Callback should not be set for a waitable operation." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -384,7 +382,8 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, " for a closed connection", pMqttConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + status = IOT_MQTT_NETWORK_ERROR; + goto cleanup; } else { @@ -401,7 +400,8 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, "operation record.", pMqttConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -426,7 +426,8 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, "waitable operation.", pMqttConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -459,7 +460,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, *pNewOperation = pOperation; /* Clean up operation and decrement reference count if this function failed. */ - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -486,7 +487,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, EMPTY_ELSE_MARKER; } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 485f89eb6d..f77208f404 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -32,9 +32,6 @@ #include #include -/* Error handling include. */ -#include "iot_error.h" - /* MQTT internal includes. */ #include "private/iot_mqtt_internal.h" @@ -278,9 +275,9 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, * */ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pBuffer, - size_t connectPacketSize ); + size_t remainingLength, + uint8_t * pBuffer, + size_t connectPacketSize ); /** * @brief Generate a PUBLISH packet from the given parameters. @@ -295,11 +292,11 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, * */ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, - size_t publishPacketSize ); + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t publishPacketSize ); /** * @brief Generate a SUBSCRIBE packet from the given parameters. @@ -313,11 +310,11 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, * */ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t subscribePacketSize ); + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t subscribePacketSize ); /** * @brief Generate an UNSUBSCRIBE packet from the given parameters. @@ -331,11 +328,11 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList * */ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t unsubscribePacketSize ); + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t unsubscribePacketSize ); /*-----------------------------------------------------------*/ @@ -659,9 +656,9 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /*-----------------------------------------------------------*/ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pBuffer, - size_t connectPacketSize ) + size_t remainingLength, + uint8_t * pBuffer, + size_t connectPacketSize ) { uint8_t connectFlags = 0; uint8_t * pConnectPacket = pBuffer; @@ -882,11 +879,11 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, /*-----------------------------------------------------------*/ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, - size_t publishPacketSize ) + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t publishPacketSize ) { uint8_t publishFlags = 0; uint16_t packetIdentifier = 0; @@ -983,11 +980,11 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t subscribePacketSize ) + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t subscribePacketSize ) { uint16_t packetIdentifier = 0; size_t i = 0; @@ -1037,16 +1034,16 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList /*-----------------------------------------------------------*/ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t unsubscribePacketSize ) + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t unsubscribePacketSize ) { uint16_t packetIdentifier = 0; size_t i = 0; uint8_t * pUnsubscribePacket = pBuffer; - + /* Avoid unused variable warning when logging and asserts are disabled. */ ( void ) pUnsubscribePacket; ( void ) unsubscribePacketSize; @@ -1217,7 +1214,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI uint8_t ** pConnectPacket, size_t * pPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t remainingLength = 0, connectPacketSize = 0; uint8_t * pBuffer = NULL; @@ -1229,7 +1226,8 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI " size allowed by MQTT 3.1.1.", MQTT_PACKET_CONNECT_MAX_SIZE ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1248,7 +1246,8 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI { IotLogError( "Failed to allocate memory for CONNECT packet." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -1261,14 +1260,15 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI _serializeConnect( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; const uint8_t * pRemainingData = pConnack->pRemainingData; /* If logging is enabled, declare the CONNACK response code strings. The @@ -1293,7 +1293,8 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) "Bad control packet type 0x%02x.", pConnack->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1309,7 +1310,8 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) "CONNACK does not have remaining length of %d.", MQTT_PACKET_CONNACK_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1324,7 +1326,8 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) &_logHideAll, "Reserved bits in CONNACK incorrect." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1344,7 +1347,8 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) * "Session Present" bit is set. */ if( pRemainingData[ 1 ] != 0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1366,7 +1370,8 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) "CONNACK response %hhu is not valid.", pRemainingData[ 1 ] ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1385,14 +1390,16 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) /* A nonzero CONNACK response code means the connection was refused. */ if( pRemainingData[ 1 ] > 0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); + status = IOT_MQTT_SERVER_REFUSED; + goto cleanup; } else { EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -1403,7 +1410,7 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t remainingLength = 0, publishPacketSize = 0; uint8_t * pBuffer = NULL; @@ -1415,7 +1422,8 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1434,7 +1442,8 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI { IotLogError( "Failed to allocate memory for PUBLISH packet." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -1453,7 +1462,8 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI pBuffer, publishPacketSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -1495,7 +1505,7 @@ void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); uint8_t publishFlags = 0; const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; @@ -1520,7 +1530,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Bad QoS: 3." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1569,7 +1580,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "QoS 0 PUBLISH cannot have a remaining length less than 3." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1587,7 +1599,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1610,7 +1623,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Remaining length cannot be less than variable header length." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1627,7 +1641,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Remaining length cannot be less than variable header length." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1660,7 +1675,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) /* Packet identifier cannot be 0. */ if( pPublish->packetIdentifier == 0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1689,7 +1705,8 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Payload length %hu.", pOutput->payloadLength ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -1732,7 +1749,7 @@ IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Check the "Remaining length" of the received PUBACK. */ if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) @@ -1742,7 +1759,8 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) "PUBACK does not have remaining length of %d.", MQTT_PACKET_PUBACK_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1759,7 +1777,8 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) /* Packet identifier cannot be 0. */ if( pPuback->packetIdentifier == 0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1775,14 +1794,16 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) "Bad control packet type 0x%02x.", pPuback->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -1793,7 +1814,7 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc size_t * pPacketSize, uint16_t * pPacketIdentifier ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t subscribePacketSize = 0, remainingLength = 0; uint8_t * pBuffer = NULL; @@ -1809,7 +1830,8 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1828,7 +1850,8 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc { IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -1848,14 +1871,15 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc subscribePacketSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t i = 0, remainingLength = pSuback->remainingLength; uint8_t subscriptionStatus = 0; const uint8_t * pVariableHeader = pSuback->pRemainingData; @@ -1868,7 +1892,8 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) &_logHideAll, "SUBACK cannot have a remaining length less than 3." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1891,7 +1916,8 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) "Bad control packet type 0x%02x.", pSuback->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -1951,7 +1977,8 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -1962,7 +1989,7 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub size_t * pPacketSize, uint16_t * pPacketIdentifier ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; size_t unsubscribePacketSize = 0, remainingLength = 0; uint8_t * pBuffer = NULL; @@ -1978,7 +2005,8 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -1997,7 +2025,8 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub { IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + status = IOT_MQTT_NO_MEMORY; + goto cleanup; } else { @@ -2016,14 +2045,15 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub pBuffer, unsubscribePacketSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) @@ -2033,7 +2063,8 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) "UNSUBACK does not have remaining length of %d.", MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -2046,7 +2077,8 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) /* Packet identifier cannot be 0. */ if( pUnsuback->packetIdentifier == 0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -2066,14 +2098,16 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) "Bad control packet type 0x%02x.", pUnsuback->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2102,7 +2136,7 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Check that the control packet type is 0xd0. */ if( pPingresp->type != MQTT_PACKET_TYPE_PINGRESP ) @@ -2112,7 +2146,8 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) "Bad control packet type 0x%02x.", pPingresp->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { @@ -2127,14 +2162,16 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) "PINGRESP does not have remaining length of %d.", MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + status = IOT_MQTT_BAD_RESPONSE; + goto cleanup; } else { EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2194,18 +2231,20 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne size_t * pRemainingLength, size_t * pPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { IotLogError( "IotMqtt_GetConnectPacketSize() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( ( pConnectInfo->clientIdentifierLength == 0 ) || ( pConnectInfo->pClientIdentifier == NULL ) ) { IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Calculate the "Remaining length" field and total packet size. If it exceeds @@ -2216,7 +2255,8 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne " size allowed by MQTT 3.1.1.", MQTT_PACKET_CONNECT_MAX_SIZE ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -2229,10 +2269,12 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne { IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", ( *pRemainingLength ), ( *pPacketSize ) ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2242,25 +2284,28 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn uint8_t * pBuffer, size_t bufferSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pBuffer == NULL ) || ( pConnectInfo == NULL ) ) { IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( ( pConnectInfo->clientIdentifierLength == 0 ) || ( pConnectInfo->pClientIdentifier == NULL ) ) { IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( remainingLength > bufferSize ) { IotLogError( " Serialize Connect packet remaining length (%lu) exceeds buffer size (%lu)", remainingLength, bufferSize ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } _serializeConnect( pConnectInfo, @@ -2268,7 +2313,8 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn pBuffer, bufferSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2279,24 +2325,27 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, size_t * pRemainingLength, size_t * pPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with unknown type." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( subscriptionCount == 0 ) { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( _subscriptionPacketSize( type, @@ -2308,7 +2357,8 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -2321,10 +2371,12 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, { IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", ( *pRemainingLength ), ( *pPacketSize ) ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2336,25 +2388,28 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr uint8_t * pBuffer, size_t bufferSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) || ( pPacketIdentifier == NULL ) ) { IotLogError( "IotMqtt_SerializeSubscribe() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( subscriptionCount == 0 ) { IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( remainingLength > bufferSize ) { IotLogError( " Subscribe packet remaining length (%lu) exceeds buffer size (%lu).", remainingLength, bufferSize ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } _serializeSubscribe( pSubscriptionList, @@ -2364,7 +2419,8 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr pBuffer, bufferSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2373,18 +2429,20 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo size_t * pRemainingLength, size_t * pPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { IotLogError( "IotMqtt_GetPublishPacketSize() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) { IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Calculate the "Remaining length" field and total packet size. If it exceeds @@ -2395,7 +2453,8 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } else { @@ -2408,10 +2467,12 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo { IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", ( *pRemainingLength ), ( *pPacketSize ) ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2424,25 +2485,28 @@ IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, size_t bufferSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pPacketIdentifier == NULL ) ) { IotLogError( "IotMqtt_SerializePublish() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) { IotLogError( "IotMqtt_SerializePublish() called with no topic." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( remainingLength > bufferSize ) { IotLogError( "Publish packet remaining length (%lu) exceeds buffer size (%lu).", remainingLength, bufferSize ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } _serializePublish( pPublishInfo, @@ -2451,7 +2515,8 @@ IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, pPacketIdentifierHigh, pBuffer, bufferSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2463,25 +2528,28 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs uint8_t * pBuffer, size_t bufferSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; if( ( pBuffer == NULL ) || ( pPacketIdentifier == NULL ) || ( pSubscriptionList == NULL ) ) { IotLogError( "IotMqtt_SerializeUnsubscribe() called with required parameter(s) set to NULL." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( subscriptionCount == 0 ) { IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( remainingLength > bufferSize ) { IotLogError( "Unsubscribe packet remaining length (%lu) exceeds buffer size (%lu).", remainingLength, bufferSize ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } _serializeUnsubscribe( pSubscriptionList, @@ -2490,7 +2558,8 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs pPacketIdentifier, pBuffer, bufferSize ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2498,21 +2567,23 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, size_t bufferSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t * pDisconnectPacket = NULL; size_t remainingLength = 0; if( pBuffer == NULL ) { IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) { IotLogError( "Disconnect packet length (%lu) exceeds buffer size (%lu).", MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Call internal function with local variables, as disconnect uses @@ -2521,7 +2592,8 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -2529,21 +2601,23 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, size_t bufferSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t * pPingreqPacket = NULL; size_t packetSize = 0; if( pBuffer == NULL ) { IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) { IotLogError( "Pingreq length (%lu) exceeds buffer size (%lu).", MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Call internal function with local variables, as ping request uses @@ -2551,14 +2625,15 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, * Note: _IotMqtt_SerializePingReq always succeeds */ _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Internal MQTT packet structure */ _mqttPacket_t mqttPacket; /* Internal MQTT operation structure needed for deserializing publish */ @@ -2567,13 +2642,15 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) if( pMqttPacket == NULL ) { IotLogError( "IotMqtt_DeserializePublish()called with NULL pMqttPacket pointer." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( ( pMqttPacket->type & 0xf0 ) != MQTT_PACKET_TYPE_PUBLISH ) { IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Set internal mqtt packet parameters. */ @@ -2594,21 +2671,23 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Internal MQTT packet structure */ _mqttPacket_t mqttPacket; if( ( pMqttPacket == NULL ) || ( pMqttPacket->pRemainingData == NULL ) ) { IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer or NULL pRemainingLength." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Set internal mqtt packet parameters. */ @@ -2644,12 +2723,13 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) /* Any other packet type is invalid. */ default: IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } if( status != IOT_MQTT_SUCCESS ) { - IOT_SET_AND_GOTO_CLEANUP( status ); + goto cleanup; } else { @@ -2657,7 +2737,8 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 782de35bb7..77ca5589cf 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -32,9 +32,6 @@ #include #include -/* Error handling include. */ -#include "iot_error.h" - /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -94,7 +91,7 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, static bool _topicMatch( const IotLink_t * pSubscriptionLink, void * pMatch ) { - IOT_FUNCTION_ENTRY( bool, false ); + bool status = false; uint16_t nameIndex = 0, filterIndex = 0; /* Because this function is called from a container function, the given link @@ -117,7 +114,7 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); - IOT_GOTO_CLEANUP(); + goto cleanup; } else { @@ -128,7 +125,8 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, * false. */ if( pParam->exactMatchOnly == true ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -152,7 +150,8 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { if( pTopicFilter[ filterIndex + 2 ] == '#' ) { - IOT_SET_AND_GOTO_CLEANUP( true ); + status = true; + goto cleanup; } else { @@ -181,7 +180,8 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { if( pTopicFilter[ filterIndex + 1 ] == '+' ) { - IOT_SET_AND_GOTO_CLEANUP( true ); + status = true; + goto cleanup; } else { @@ -218,13 +218,15 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { /* Subsequent characters don't need to be checked if the for the * multi-level wildcard. */ - IOT_SET_AND_GOTO_CLEANUP( true ); + status = true; + goto cleanup; } else { /* Any character mismatch other than '+' or '#' means the topic * name does not match the topic filter. */ - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } } @@ -236,14 +238,16 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, /* If the end of both strings has been reached, they match. */ if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) { - IOT_SET_AND_GOTO_CLEANUP( true ); + status = true; + goto cleanup; } else { EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 7456a9dffe..5876d2fa0a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -28,9 +28,6 @@ /* The config header is always included first. */ #include "iot_config.h" -/* Error handling include. */ -#include "iot_error.h" - /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -54,7 +51,7 @@ static bool _validatePublish( bool awsIotMqttMode, bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; bool enforceMaxClientIdLength = false; @@ -63,7 +60,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogError( "MQTT connection information cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -75,7 +73,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogError( "Client identifier must be set." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -92,7 +91,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogError( "A zero-length client identifier cannot be used with a clean session." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -108,11 +108,12 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) if( pConnectInfo->pPreviousSubscriptions != NULL ) { if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ) == false ) + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ) == false ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -130,7 +131,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) if( _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, pConnectInfo->pWillInfo ) == false ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -166,7 +168,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) pConnectInfo->clientIdentifierLength, maxClientIdLength ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } } else @@ -174,7 +177,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -184,7 +188,7 @@ static bool _validatePublish( bool awsIotMqttMode, const char * pPublishTypeDescription, const IotMqttPublishInfo_t * pPublishInfo ) { - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; /* This parameter is not used when logging is disabled. */ ( void ) pPublishTypeDescription; @@ -194,7 +198,8 @@ static bool _validatePublish( bool awsIotMqttMode, { IotLogError( "Publish information cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -215,7 +220,8 @@ static bool _validatePublish( bool awsIotMqttMode, { IotLogError( "Publish topic name length cannot be 0." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -231,7 +237,8 @@ static bool _validatePublish( bool awsIotMqttMode, pPublishInfo->payloadLength, maximumPayloadLength ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -239,7 +246,8 @@ static bool _validatePublish( bool awsIotMqttMode, { IotLogError( "Nonzero payload length cannot have a NULL payload." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -259,7 +267,8 @@ static bool _validatePublish( bool awsIotMqttMode, { IotLogError( "Publish QoS must be either 0 or 1." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -278,7 +287,8 @@ static bool _validatePublish( bool awsIotMqttMode, { IotLogError( "Publish retry time must be positive." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -298,7 +308,8 @@ static bool _validatePublish( bool awsIotMqttMode, { IotLogError( "AWS IoT does not support retained publish messages." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -311,7 +322,8 @@ static bool _validatePublish( bool awsIotMqttMode, IotLogError( "AWS IoT does not support topic names longer than %d bytes.", AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -323,7 +335,8 @@ static bool _validatePublish( bool awsIotMqttMode, EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -363,14 +376,15 @@ bool _IotMqtt_ValidateLwtPublish( bool awsIotMqttMode, bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) { - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; /* Check for NULL. */ if( operation == NULL ) { IotLogError( "Operation reference cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -382,14 +396,16 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) { IotLogError( "Operation is not waitable." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { EMPTY_ELSE_MARKER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ @@ -399,7 +415,7 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, const IotMqttSubscription_t * pListStart, size_t listSize ) { - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; size_t i = 0; uint16_t j = 0; const IotMqttSubscription_t * pListElement = NULL; @@ -413,7 +429,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription list pointer cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -424,7 +441,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Empty subscription list." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -440,7 +458,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, "subscription request.", AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -465,7 +484,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription QoS must be either 0 or 1." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -481,7 +501,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Callback function must be set." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -498,7 +519,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription topic filter cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -509,7 +531,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription topic filter length cannot be 0." ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -525,7 +548,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -554,7 +578,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -575,7 +600,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -599,7 +625,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -615,7 +642,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } else { @@ -635,7 +663,8 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); +cleanup: + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c index bac2a0d992..cca00991c7 100644 --- a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c +++ b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c @@ -44,9 +44,6 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" -/* Error handling include. */ -#include "iot_error.h" - /*-----------------------------------------------------------*/ /** @@ -317,7 +314,7 @@ static size_t _receive( IotNetworkConnection_t pNetworkConnection, bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) { - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; /* Flags to track clean up */ @@ -334,7 +331,8 @@ bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) if( packetMutexCreated == false ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } /* Create the receive thread timer. */ @@ -344,7 +342,8 @@ bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) if( timerCreated == false ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } /* Initialize the MQTT connection object. */ @@ -354,10 +353,11 @@ bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) if( _pMqttConnection == NULL ) { - IOT_SET_AND_GOTO_CLEANUP( false ); + status = false; + goto cleanup; } - IOT_FUNCTION_CLEANUP_BEGIN(); +cleanup: /* Clean up on error. */ if( status == false ) @@ -378,7 +378,7 @@ bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) *pMqttConnection = _pMqttConnection; } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ From 60eb23e009080dff635bc9a56b22a076ea15855e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 13 Dec 2019 15:02:50 -0800 Subject: [PATCH 344/844] Switch coverage tool to codecov.io (#683) --- .travis.yml | 4 ++++ libraries/platform/iot_network.h | 3 +++ scripts/ci_test_coverage.sh | 22 +++++++++------------- scripts/setup/ci_setup_linux.sh | 1 - 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8472d87fac..5800e7760d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,3 +62,7 @@ script: - cd build # Run test script. - bash ../scripts/ci_test_$RUN_TEST.sh + +after_success: + # Submit coverage results. + - if [ "$RUN_TEST" = "coverage" ]; then bash <(curl -s https://codecov.io/bash) -f coverage.info ; fi diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 03303b3fa9..04a41e0d11 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -35,6 +35,9 @@ #include #include +/* Platform types include. */ +#include "types/iot_platform_types.h" + /** * @ingroup platform_datatypes_enums * @brief Return codes for [network functions](@ref platform_network_functions). diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 3109e4676b..a30179f6cc 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -14,15 +14,15 @@ function generate_coverage() { fi # Generate code coverage results, but only for files in libraries/. - lcov --directory . --capture --output-file $1 - lcov --remove $1 '*demo*' --output-file $1 - lcov --remove $1 '*ports*' --output-file $1 - lcov --remove $1 '*tests*' --output-file $1 - lcov --remove $1 '*third_party*' --output-file $1 + lcov --rc lcov_branch_coverage=1 --directory . --capture --output-file $1 + lcov --rc lcov_branch_coverage=1 --remove $1 '*demo*' --output-file $1 + lcov --rc lcov_branch_coverage=1 --remove $1 '*ports*' --output-file $1 + lcov --rc lcov_branch_coverage=1 --remove $1 '*test*' --output-file $1 + lcov --rc lcov_branch_coverage=1 --remove $1 '*third_party*' --output-file $1 } -# Overwrite the value of the COMPILER_OPTIONS varirable to remove any thread sanitizer flags, and replace with coverage flags. -export COMPILER_OPTIONS="-DIOT_TEST_COVERAGE=1 --coverage" +# Overwrite the value of the COMPILER_OPTIONS variable to remove any thread sanitizer flags, and replace with coverage flags. +export COMPILER_OPTIONS="-DIOT_TEST_COVERAGE=1 --coverage -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" SCRIPTS_FOLDER_PATH=../scripts @@ -43,13 +43,9 @@ $SCRIPTS_FOLDER_PATH/ci_test_jobs.sh generate_coverage jobs.info # Combine the coverage files of all libraries into a single master coverage file. -lcov --add-tracefile common.info \ +lcov --rc lcov_branch_coverage=1 \ + --add-tracefile common.info \ --add-tracefile mqtt.info \ --add-tracefile shadow.info \ --add-tracefile jobs.info \ --output-file coverage.info - -# Submit the code coverage results. Must be submitted from SDK root directory so -# Coveralls displays the correct paths. -cd .. -coveralls --lcov-file build/coverage.info diff --git a/scripts/setup/ci_setup_linux.sh b/scripts/setup/ci_setup_linux.sh index 9f4e1717d9..3932e40644 100755 --- a/scripts/setup/ci_setup_linux.sh +++ b/scripts/setup/ci_setup_linux.sh @@ -45,7 +45,6 @@ else # Install dependencies for coverage builds. if [ "$RUN_TEST" = "coverage" ]; then sudo apt-get install -y lcov; - pip3 install --user cpp-coveralls; fi fi From 1cbdd6b9b32d3268f2872c9817ae76f251abf52f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 16 Dec 2019 09:54:38 -0800 Subject: [PATCH 345/844] Update documentation for new coverage tool (#685) --- README.md | 2 +- doc/guide/developer/automated_tests.txt | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ffbdb51c5f..81a54ab31e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/index.html)** [![Build Status](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C.svg?branch=v4_beta)](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) -[![Coverage Status](https://coveralls.io/repos/github/aws/aws-iot-device-sdk-embedded-C/badge.svg?branch=v4_beta)](https://coveralls.io/github/aws/aws-iot-device-sdk-embedded-C?branch=v4_beta) +[![codecov](https://codecov.io/gh/aws/aws-iot-device-sdk-embedded-C/branch/v4_beta/graph/badge.svg)](https://codecov.io/gh/aws/aws-iot-device-sdk-embedded-C) The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be built into firmware along with application code. diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index 5a6bd21913..4ede09d1be 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -5,8 +5,8 @@ The SDK's GitHub repo relies on the following tools to provide automated testing of commits and pull requests. - [Travis CI](https://travis-ci.org/) is the primary service used for testing.
See: [aws-iot-device-sdk-embedded-C on Travis CI](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) -- [Coveralls](https://coveralls.io/) provides code coverage results.
- See: [aws-iot-device-sdk-embedded-C on Coveralls](https://coveralls.io/github/aws/aws-iot-device-sdk-embedded-C) +- [Codecov](https://codecov.io/) provides code coverage results.
+ See: [aws-iot-device-sdk-embedded-C on Codecov](https://codecov.io/gh/aws/aws-iot-device-sdk-embedded-C) The following files on the GitHub repo control the automated tests: - `.travis.yml`
@@ -15,7 +15,7 @@ The following files on the GitHub repo control the automated tests: Shell scripts (`sh`) that run on Travis CI servers. - The scripts that test the libraries are named after the library they test. For example, `ci_test_mqtt.sh` tests the MQTT library. - `ci_test_doc.sh` tests the documentation build. Runs for pull requests only. - - `ci_test_coverage.sh` generates and submits coverage results to Coveralls. Runs for commits only. + - `ci_test_coverage.sh` generates and submits coverage results to Codecov. Runs for commits only. @section guide_developer_automated_tests_setup Setup @brief How to set up the automated testing services. @@ -32,15 +32,15 @@ All of the automated testing services have web interfaces that can be connected 3. On the Settings page, toggle the switch next to your fork of the C SDK to On. 4. Travis CI is now set up. See @ref guide_developer_automated_tests_credentials_policy_travis for adding commit credentials to Travis CI. -@subsection guide_developer_automated_tests_setup_coveralls Coveralls -@brief How to set up Coveralls. +@subsection guide_developer_automated_tests_setup_codecov Codecov +@brief How to set up Codecov. -Link to Coveralls: https://coveralls.io/ +Link to Codecov: https://codecov.io/ -1. Click SIGN IN in the upper right corner. Then select GITHUB SIGN IN to log in with your GitHub account. On your first login, Coveralls will prompt for GitHub permissions access. -2. Once signed in, hover over the menu on the left and click ADD REPOS. -3. On the ADD REPO page, toggle the switch next to your fork of the C SDK to ON. -4. Coveralls is now set up up. Travis CI will automatically submit coverage data on commit check builds. +1. Click Log In in the upper right corner. Then select Log in with GitHub to log in with your GitHub account. On your first login, Codecov will prompt for GitHub permissions access. +2. Navigate to codecov.io/gh/your GitHub username/aws-iot-device-sdk-embedded-C +3. Click on Settings, then copy the Repository Upload Token displayed. +4. Set the Travis CI environment variable `CODECOV_TOKEN` to the Repository Upload Token. Travis CI will automatically submit coverage data on commit check builds. @section guide_developer_automated_tests_commit_pr Commit vs. Pull Request @brief The automated tests distinguish between GitHub commits and pull requests. Different sets of tests run on commits and pull requests. @@ -55,7 +55,7 @@ For commits, the credentials necessary for connecting to AWS IoT are provided by - Common (`ci_test_common.sh`): runs the same tests as the common pull request tests. - MQTT (`ci_test_mqtt.sh`): runs the same MQTT tests as the MQTT pull request tests, but against the AWS IoT MQTT broker instead of a Mosquitto broker. - Shadow (`ci_test_shadow.sh`): runs both the Shadow unit tests and the Shadow system tests. Requires connection to AWS IoT. -- Coverage (`ci_test_coverage.sh`): Gathers code coverage data and submits it to Coveralls. +- Coverage (`ci_test_coverage.sh`): Gathers code coverage data and submits it to Codecov. @note Rarely, the Shadow tests on a GitHub fork will fail with a `429 TOO MANY REQUESTS` error. If this happens, restart the Shadow tests. The main C SDK GitHub repo has an increased limit for Shadow requests, so it should not encounter this error. From b6fd0aaf63901f48895937792bd90390e17a9de1 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 16 Dec 2019 10:13:44 -0800 Subject: [PATCH 346/844] Remove EMPTY_ELSE_MARKER (#686) --- libraries/standard/mqtt/src/iot_mqtt_api.c | 321 ------------------ .../standard/mqtt/src/iot_mqtt_network.c | 108 ------ .../standard/mqtt/src/iot_mqtt_operation.c | 170 ---------- .../standard/mqtt/src/iot_mqtt_serialize.c | 232 ------------- .../standard/mqtt/src/iot_mqtt_subscription.c | 76 ----- .../standard/mqtt/src/iot_mqtt_validate.c | 172 ---------- .../mqtt/src/private/iot_mqtt_internal.h | 20 +- tests/iot_config.h | 3 - 8 files changed, 1 insertion(+), 1101 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index c9de27c0b4..d5538db859 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -269,10 +269,6 @@ static bool _checkInit( void ) status = false; } - else - { - EMPTY_ELSE_MARKER; - } return status; } @@ -316,10 +312,6 @@ static void _mqttSubscription_tryDestroy( void * pData ) { IotMqtt_FreeSubscription( pSubscription ); } - else - { - EMPTY_ELSE_MARKER; - } } /*-----------------------------------------------------------*/ @@ -350,11 +342,6 @@ static void _mqttOperation_tryDestroy( void * pData ) pOperation->job, pOperation ); } - else - { - /* The executing job will process the PUBLISH, so nothing is done here. */ - EMPTY_ELSE_MARKER; - } } else { @@ -363,10 +350,6 @@ static void _mqttOperation_tryDestroy( void * pData ) { _IotMqtt_DestroyOperation( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } } } @@ -416,10 +399,6 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo IotMqtt_Assert( false ); } - else - { - EMPTY_ELSE_MARKER; - } /* Keep-alive references its MQTT connection, so increment reference. */ ( pMqttConnection->references )++; @@ -444,10 +423,6 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * status = IOT_NETWORK_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Create a new network connection if requested. Otherwise, copy the existing * network connection. */ @@ -525,10 +500,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Create the subscription mutex for a new connection. */ subscriptionMutexCreated = IotMutex_Create( &( pMqttConnection->subscriptionMutex ), false ); @@ -540,10 +511,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Create the new connection's subscription and operation lists. */ IotListDouble_Create( &( pMqttConnection->subscriptionList ) ); @@ -560,14 +527,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Clean up mutexes and connection if this function failed. */ @@ -579,33 +538,17 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } if( referencesMutexCreated == true ) { IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } if( pMqttConnection != NULL ) { IotMqtt_FreeConnection( pMqttConnection ); pMqttConnection = NULL; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } return pMqttConnection; @@ -632,10 +575,6 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) /* Decrement reference count. */ pMqttConnection->references--; } - else - { - EMPTY_ELSE_MARKER; - } /* A connection to be destroyed should have no keep-alive and at most 1 * reference. */ @@ -669,10 +608,6 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) pMqttConnection ); } } - else - { - EMPTY_ELSE_MARKER; - } /* Destroy mutexes. */ IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); @@ -704,10 +639,6 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation status = IOT_MQTT_NOT_INITIALIZED; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that all elements in the subscription list are valid. */ if( _IotMqtt_ValidateSubscriptionList( operation, @@ -718,10 +649,6 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that a reference pointer is provided for a waitable operation. */ if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) @@ -734,14 +661,6 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } cleanup: @@ -793,10 +712,6 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the serialized MQTT packet. */ IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); @@ -834,10 +749,6 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Add the subscription list for a SUBSCRIBE. */ if( operation == IOT_MQTT_SUBSCRIBE ) @@ -851,10 +762,6 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } /* Set the reference, if provided. */ @@ -862,10 +769,6 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { *pOperationReference = pSubscriptionOperation; } - else - { - EMPTY_ELSE_MARKER; - } /* Send the SUBSCRIBE packet. */ if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) @@ -890,20 +793,12 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, pSubscriptionOperation->u.operation.packetIdentifier, MQTT_REMOVE_ALL_SUBSCRIPTIONS ); } - else - { - EMPTY_ELSE_MARKER; - } /* Clear the previously set (and now invalid) reference. */ if( pOperationReference != NULL ) { *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; } - else - { - EMPTY_ELSE_MARKER; - } goto cleanup; } @@ -918,10 +813,6 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { _IotMqtt_DestroyOperation( pSubscriptionOperation ); } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -992,10 +883,6 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection { destroyConnection = true; } - else - { - EMPTY_ELSE_MARKER; - } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -1006,10 +893,6 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection pMqttConnection ); _destroyMqttConnection( pMqttConnection ); } - else - { - EMPTY_ELSE_MARKER; - } } /*-----------------------------------------------------------*/ @@ -1034,10 +917,6 @@ IotMqttError_t IotMqtt_Init( void ) status = IOT_MQTT_INIT_FAILED; } - else - { - EMPTY_ELSE_MARKER; - } #endif /* ifdef _IotMqtt_InitSerializeAdditional */ #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -1045,10 +924,6 @@ IotMqttError_t IotMqtt_Init( void ) { IotLogInfo( "MQTT library successfully initialized." ); } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -1105,10 +980,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_NOT_INITIALIZED; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Validate network interface and connect info. */ if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) @@ -1116,10 +987,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } networkStatus = _createNetworkConnection( pNetworkInfo, &pNetworkConnection, @@ -1130,10 +997,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_NETWORK_ERROR; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } IotLogInfo( "Establishing new MQTT connection." ); @@ -1173,10 +1036,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_NETWORK_ERROR; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Create a CONNECT operation. */ status = _IotMqtt_CreateOperation( pNewMqttConnection, @@ -1188,10 +1047,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Ensure the members set by operation creation and serialization * are appropriate for a blocking CONNECT. */ @@ -1218,14 +1073,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ @@ -1237,10 +1084,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the serialized MQTT packet. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); @@ -1273,19 +1116,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_SCHEDULING_ERROR; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } cleanup: @@ -1309,28 +1140,16 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotLogInfo( "Network connection closed on error." ); } } - else - { - EMPTY_ELSE_MARKER; - } if( pOperation != NULL ) { _IotMqtt_DestroyOperation( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } if( pNewMqttConnection != NULL ) { _destroyMqttConnection( pNewMqttConnection ); } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -1359,10 +1178,6 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" * flag is not set. */ @@ -1406,10 +1221,6 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( &( pOperation->u.operation.pMqttPacket ), &( pOperation->u.operation.packetSize ) ); } - else - { - EMPTY_ELSE_MARKER; - } if( status == IOT_MQTT_SUCCESS ) { @@ -1440,10 +1251,6 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMqtt_strerror( status ) ); } } - else - { - EMPTY_ELSE_MARKER; - } /* This function has no return value and no cleanup, but uses the cleanup * label to exit on error. */ @@ -1504,10 +1311,6 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, pCallbackInfo, pSubscribeOperation ); } - else - { - EMPTY_ELSE_MARKER; - } return status; } @@ -1539,10 +1342,6 @@ IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, { status = IotMqtt_Wait( subscribeOperation, timeoutMs ); } - else - { - EMPTY_ELSE_MARKER; - } /* Ensure that a status was set. */ IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); @@ -1582,10 +1381,6 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, pCallbackInfo, pUnsubscribeOperation ); } - else - { - EMPTY_ELSE_MARKER; - } return status; } @@ -1617,10 +1412,6 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, { status = IotMqtt_Wait( unsubscribeOperation, timeoutMs ); } - else - { - EMPTY_ELSE_MARKER; - } /* Ensure that a status was set. */ IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); @@ -1646,10 +1437,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, status = IOT_MQTT_NOT_INITIALIZED; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that the PUBLISH information is valid. */ if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, @@ -1658,10 +1445,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that no notification is requested for a QoS 0 publish. */ if( pPublishInfo->qos == IOT_MQTT_QOS_0 ) @@ -1680,23 +1463,11 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } if( pPublishOperation != NULL ) { IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Check that a reference pointer is provided for a waitable operation. */ @@ -1709,14 +1480,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Create a PUBLISH operation. */ @@ -1729,10 +1492,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the PUBLISH operation data and set the operation type. */ IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); @@ -1743,10 +1502,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { pPacketIdentifierHigh = &( pOperation->u.operation.pPacketIdentifierHigh ); } - else - { - EMPTY_ELSE_MARKER; - } /* Generate a PUBLISH packet from pPublishInfo. */ status = _getMqttPublishSerializer( mqttConnection->pSerializer )( pPublishInfo, @@ -1759,10 +1514,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the serialized MQTT packet. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); @@ -1777,14 +1528,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, pOperation->u.operation.periodic.retry.limit = pPublishInfo->retryLimit; pOperation->u.operation.periodic.retry.nextPeriodMs = pPublishInfo->retryMs; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Set the reference, if provided. */ @@ -1794,14 +1537,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { *pPublishOperation = pOperation; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Send the PUBLISH packet. */ @@ -1827,22 +1562,10 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { *pPublishOperation = IOT_MQTT_OPERATION_INITIALIZER; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } /* Clean up the PUBLISH operation if this function fails. Otherwise, set the @@ -1855,10 +1578,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { _IotMqtt_DestroyOperation( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -1866,10 +1585,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { status = IOT_MQTT_STATUS_PENDING; } - else - { - EMPTY_ELSE_MARKER; - } IotLogInfo( "(MQTT connection %p) MQTT PUBLISH operation queued.", mqttConnection ); @@ -1898,10 +1613,6 @@ IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, flags |= IOT_MQTT_FLAG_WAITABLE; pPublishOperation = &publishOperation; } - else - { - EMPTY_ELSE_MARKER; - } /* Call the asynchronous PUBLISH function. */ status = IotMqtt_PublishAsync( mqttConnection, @@ -1917,14 +1628,6 @@ IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, { status = IotMqtt_Wait( publishOperation, timeoutMs ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } return status; @@ -1944,20 +1647,12 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, status = IOT_MQTT_NOT_INITIALIZED; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Validate the given operation reference. */ if( _IotMqtt_ValidateOperation( operation ) == false ) { status = IOT_MQTT_BAD_PARAMETER; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the MQTT connection status. */ pMqttConnection = operation->pMqttConnection; @@ -2009,10 +1704,6 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, operation->u.operation.packetIdentifier, MQTT_REMOVE_ALL_SUBSCRIPTIONS ); } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -2026,24 +1717,12 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, operation, IotMqtt_strerror( status ) ); } - else - { - EMPTY_ELSE_MARKER; - } /* Wait is finished; decrement operation reference count. */ if( _IotMqtt_DecrementOperationReferences( operation, false ) == true ) { _IotMqtt_DestroyOperation( operation ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } cleanup: diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 12ecc5cfca..6819e143e2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -217,10 +217,6 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Read the remaining length. */ pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( pNetworkConnection, @@ -231,10 +227,6 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Allocate a buffer for the remaining data and read the data. */ if( pIncomingPacket->remainingLength > 0 ) @@ -254,10 +246,6 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, status = IOT_MQTT_NO_MEMORY; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, pIncomingPacket->pRemainingData, @@ -268,14 +256,6 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Clean up on error. */ @@ -287,14 +267,6 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, { IotMqtt_FreeMessage( pIncomingPacket->pRemainingData ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } return status; @@ -333,10 +305,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } break; @@ -372,10 +340,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); } - else - { - EMPTY_ELSE_MARKER; - } /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; @@ -399,10 +363,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne status = IOT_MQTT_NETWORK_ERROR; } } - else - { - EMPTY_ELSE_MARKER; - } /* Free PUBLISH operation on error. */ if( status != IOT_MQTT_SUCCESS ) @@ -414,10 +374,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->u.publish.pReceivedData; } - else - { - EMPTY_ELSE_MARKER; - } /* Remove operation from pending processing list. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -426,20 +382,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { IotListDouble_Remove( &( pOperation->link ) ); } - else - { - EMPTY_ELSE_MARKER; - } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); IotMqtt_Assert( pOperation != NULL ); IotMqtt_FreeOperation( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } break; @@ -458,10 +406,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } break; @@ -482,10 +426,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } break; @@ -504,10 +444,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } break; @@ -535,10 +471,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pMqttConnection ); } } - else - { - EMPTY_ELSE_MARKER; - } break; } @@ -549,10 +481,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pMqttConnection, IotMqtt_strerror( status ) ); } - else - { - EMPTY_ELSE_MARKER; - } return status; } @@ -605,10 +533,6 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Clean up on error. */ cleanup: @@ -619,14 +543,6 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, { _IotMqtt_DestroyOperation( pPubackOperation ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } @@ -733,14 +649,6 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason IotLogDebug( "(MQTT connection %p) Keep-alive job canceled and cleaned up.", pMqttConnection ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Copy the function pointers and contexts, as the MQTT connection may be @@ -785,10 +693,6 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason disconnectCallback( pDisconnectCallbackContext, &callbackParam ); } - else - { - EMPTY_ELSE_MARKER; - } } /*-----------------------------------------------------------*/ @@ -818,14 +722,6 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, { IotMqtt_FreeMessage( incomingPacket.pRemainingData ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Close the network connection on a bad response. */ @@ -837,10 +733,6 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, _IotMqtt_CloseNetworkConnection( IOT_MQTT_BAD_PACKET_RECEIVED, pMqttConnection ); } - else - { - EMPTY_ELSE_MARKER; - } } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index f49fdf04d4..64c4bd445b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -138,10 +138,6 @@ static bool _mqttOperation_match( const IotLink_t * pOperationLink, match = ( *( pParam->pPacketIdentifier ) == pOperation->u.operation.packetIdentifier ); } } - else - { - EMPTY_ELSE_MARKER; - } return match; } @@ -184,10 +180,6 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) * identifier) must be reset on every retry. */ setDup = true; } - else - { - EMPTY_ELSE_MARKER; - } if( setDup == true ) { @@ -198,10 +190,6 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } /* Set the DUP flag */ _getMqttPublishSetDupFunc( pMqttConnection->pSerializer )( pOperation->u.operation.pMqttPacket, @@ -212,14 +200,6 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) { IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } @@ -267,10 +247,6 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) { pOperation->u.operation.periodic.retry.nextPeriodMs = IOT_MQTT_RETRY_MS_CEILING; } - else - { - EMPTY_ELSE_MARKER; - } IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", pMqttConnection, @@ -289,10 +265,6 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } } /* Reschedule the PUBLISH for another send. */ @@ -315,14 +287,6 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), &( pOperation->link ) ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* The references mutex only needs to be unlocked on the first retry, since @@ -331,10 +295,6 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) { IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } return( status == IOT_MQTT_SUCCESS ); } @@ -361,14 +321,6 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } IotLogDebug( "(MQTT connection %p) Creating new operation record.", @@ -444,10 +396,6 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { pOperation->u.operation.notify.callback = *pCallbackInfo; } - else - { - EMPTY_ELSE_MARKER; - } } /* Add this operation to the MQTT connection's operation list. */ @@ -468,23 +416,11 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } - else - { - EMPTY_ELSE_MARKER; - } if( pOperation != NULL ) { IotMqtt_FreeOperation( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } return status; @@ -513,14 +449,6 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Decrement job reference count. */ @@ -546,17 +474,9 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, { destroyOperation = true; } - else - { - EMPTY_ELSE_MARKER; - } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } return destroyOperation; } @@ -628,10 +548,6 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", pMqttConnection, @@ -771,10 +687,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } /* Close the connection on failures. */ if( status == false ) @@ -785,10 +697,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, /* Keep-alive has failed and will no longer use this MQTT connection. */ _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } - else - { - EMPTY_ELSE_MARKER; - } } /*-----------------------------------------------------------*/ @@ -814,12 +722,6 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, { IotListDouble_Remove( &( pOperation->link ) ); } - else - { - /* This operation may have already been removed by cleanup of pending - * operations (called from Disconnect). In that case, do nothing here. */ - EMPTY_ELSE_MARKER; - } IotMutex_Unlock( &( pOperation->pMqttConnection->referencesMutex ) ); @@ -869,14 +771,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { pOperation->u.operation.status = IOT_MQTT_RETRY_NO_RESPONSE; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Send an operation that is waiting for a response. */ @@ -915,21 +809,9 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { pOperation->u.operation.status = IOT_MQTT_SUCCESS; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } } - else - { - EMPTY_ELSE_MARKER; - } /* Check if this operation requires further processing. */ if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) @@ -958,10 +840,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); } - else - { - EMPTY_ELSE_MARKER; - } /* If the operation should not be destroyed, transfer it from the * pending processing to the pending response list. */ @@ -978,18 +856,10 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, /* This operation is now awaiting a response from the network. */ networkPending = true; } - else - { - EMPTY_ELSE_MARKER; - } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } } - else - { - EMPTY_ELSE_MARKER; - } /* Destroy the operation or notify of completion if necessary. */ if( destroyOperation == true ) @@ -1007,14 +877,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { _IotMqtt_Notify( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } } @@ -1052,10 +914,6 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, { _IotMqtt_DestroyOperation( pOperation ); } - else - { - EMPTY_ELSE_MARKER; - } } /*-----------------------------------------------------------*/ @@ -1098,10 +956,6 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, status = IOT_MQTT_SCHEDULING_ERROR; } - else - { - EMPTY_ELSE_MARKER; - } return status; } @@ -1193,10 +1047,6 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, } } } - else - { - EMPTY_ELSE_MARKER; - } if( pResult != NULL ) { @@ -1248,10 +1098,6 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) break; } } - else - { - EMPTY_ELSE_MARKER; - } /* Schedule callback invocation for non-waitable operation. */ if( waitable == false ) @@ -1281,10 +1127,6 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { IotListDouble_Remove( &( pOperation->link ) ); } - else - { - EMPTY_ELSE_MARKER; - } IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), &( pOperation->link ) ); @@ -1299,14 +1141,6 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Operations that weren't scheduled may be destroyed. */ @@ -1330,10 +1164,6 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); } - else - { - EMPTY_ELSE_MARKER; - } } } else diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index f77208f404..dac7a6782a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -418,10 +418,6 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, { UINT8_SET_BIT( lengthByte, 7 ); } - else - { - EMPTY_ELSE_MARKER; - } /* Output a single encoded byte. */ *pLengthEnd = lengthByte; @@ -476,10 +472,6 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); } - else - { - EMPTY_ELSE_MARKER; - } /* Depending on the status of metrics, add the length of the metrics username * or the user-provided username. */ @@ -498,19 +490,11 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, { connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); } - else - { - EMPTY_ELSE_MARKER; - } if( pConnectInfo->pPassword != NULL ) { connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); } - else - { - EMPTY_ELSE_MARKER; - } /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has * been calculated. */ @@ -552,10 +536,6 @@ static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, { publishPacketSize += sizeof( uint16_t ); } - else - { - EMPTY_ELSE_MARKER; - } /* Calculate the maximum allowed size of the payload for the given parameters. * This calculation excludes the "Remaining length" encoding, whose size is not @@ -626,10 +606,6 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, { subscriptionPacketSize += 1; } - else - { - EMPTY_ELSE_MARKER; - } } /* At this point, the "Remaining length" has been calculated. Return error @@ -690,10 +666,6 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, { UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); } - else - { - EMPTY_ELSE_MARKER; - } /* Username and password depend on MQTT mode. */ if( ( pConnectInfo->pUserName == NULL ) && @@ -712,19 +684,11 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, { UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); } - else - { - EMPTY_ELSE_MARKER; - } if( pConnectInfo->pPassword != NULL ) { UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); } - else - { - EMPTY_ELSE_MARKER; - } } /* Set will flag if an LWT is provided. */ @@ -751,14 +715,6 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, { UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } *pBuffer = connectFlags; @@ -785,10 +741,6 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, pConnectInfo->pWillInfo->pPayload, ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); } - else - { - EMPTY_ELSE_MARKER; - } /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the @@ -851,10 +803,6 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, pConnectInfo->pUserName, pConnectInfo->userNameLength ); } - else - { - EMPTY_ELSE_MARKER; - } /* Encode the password field, if requested by the app. */ if( pConnectInfo->pPassword != NULL ) @@ -863,10 +811,6 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, pConnectInfo->pPassword, pConnectInfo->passwordLength ); } - else - { - EMPTY_ELSE_MARKER; - } /* Ensure that the difference between the end and beginning of the buffer * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ @@ -904,19 +848,11 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, { UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); } - else - { - EMPTY_ELSE_MARKER; - } if( pPublishInfo->retain == true ) { UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); } - else - { - EMPTY_ELSE_MARKER; - } *pBuffer = publishFlags; pBuffer++; @@ -943,20 +879,12 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, { *pPacketIdentifierHigh = pBuffer; } - else - { - EMPTY_ELSE_MARKER; - } /* Place the packet identifier into the PUBLISH packet. */ *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); pBuffer += 2; } - else - { - EMPTY_ELSE_MARKER; - } /* The payload is placed after the packet identifier. */ if( pPublishInfo->payloadLength > 0 ) @@ -964,10 +892,6 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); pBuffer += pPublishInfo->payloadLength; } - else - { - EMPTY_ELSE_MARKER; - } /* Ensure that the difference between the end and beginning of the buffer * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ @@ -1145,10 +1069,6 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, IotMqtt_Assert( bytesDecoded <= 4 ); } } - else - { - EMPTY_ELSE_MARKER; - } return remainingLength; } @@ -1200,10 +1120,6 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, IotMqtt_Assert( bytesDecoded <= 4 ); } } - else - { - EMPTY_ELSE_MARKER; - } return remainingLength; } @@ -1229,10 +1145,6 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the connect packet should be larger than the "Remaining length" * field. */ @@ -1249,10 +1161,6 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI status = IOT_MQTT_NO_MEMORY; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Set the output parameters. The remainder of this function always succeeds. */ *pConnectPacket = pBuffer; @@ -1296,10 +1204,6 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* According to MQTT 3.1.1, the second byte of CONNACK must specify a * "Remaining length" of 2. */ @@ -1313,10 +1217,6 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the reserved bits in CONNACK. The high 7 bits of the second byte * in CONNACK must be 0. */ @@ -1329,10 +1229,6 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Determine if the "Session Present" bit it set. This is the lowest bit of * the second byte in CONNACK. */ @@ -1350,10 +1246,6 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -1373,10 +1265,6 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Print the appropriate message for the CONNACK response code if logs are * enabled. */ @@ -1393,10 +1281,6 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) status = IOT_MQTT_SERVER_REFUSED; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -1425,10 +1309,6 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the publish packet should be larger than the "Remaining length" * field. */ @@ -1445,10 +1325,6 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI status = IOT_MQTT_NO_MEMORY; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Set the output parameters. The remainder of this function always succeeds. */ *pPublishPacket = pBuffer; @@ -1533,10 +1409,6 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } pOutput->qos = IOT_MQTT_QOS_2; } @@ -1583,10 +1455,6 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -1602,10 +1470,6 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } /* Extract the topic name starting from the first byte of the variable header. @@ -1626,10 +1490,6 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -1644,10 +1504,6 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } /* Parse the topic. */ @@ -1678,14 +1534,6 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain @@ -1762,10 +1610,6 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); @@ -1780,10 +1624,6 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that the control packet type is 0x40 (this must be done after the * packet identifier is parsed). */ @@ -1797,10 +1637,6 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -1833,10 +1669,6 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the subscribe packet should be larger than the "Remaining length" * field. */ @@ -1853,10 +1685,6 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc status = IOT_MQTT_NO_MEMORY; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Set the output parameters. The remainder of this function always succeeds. */ *pSubscribePacket = pBuffer; @@ -1895,10 +1723,6 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); @@ -1919,10 +1743,6 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Iterate through each status byte in the SUBACK packet. */ for( i = 0; i < remainingLength - sizeof( uint16_t ); i++ ) @@ -1971,10 +1791,6 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) { break; } - else - { - EMPTY_ELSE_MARKER; - } } cleanup: @@ -2008,10 +1824,6 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the unsubscribe packet should be larger than the "Remaining length" * field. */ @@ -2028,10 +1840,6 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub status = IOT_MQTT_NO_MEMORY; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Set the output parameters. The remainder of this function always succeeds. */ *pUnsubscribePacket = pBuffer; @@ -2066,10 +1874,6 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); @@ -2080,10 +1884,6 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -2101,10 +1901,6 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -2149,10 +1945,6 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check the "Remaining length" (second byte) of the received PINGRESP. */ if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) @@ -2165,10 +1957,6 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) status = IOT_MQTT_BAD_RESPONSE; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -2210,14 +1998,6 @@ void _IotMqtt_FreePacket( uint8_t * pPacket ) { IotMqtt_FreeMessage( pPacket ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } @@ -2258,10 +2038,6 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the subscribe packet should be larger than the "Remaining length" * field. */ @@ -2360,10 +2136,6 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the subscribe packet should be larger than the "Remaining length" * field. */ @@ -2456,10 +2228,6 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Total size of the publish packet should be larger than the "Remaining length" * field. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 77ca5589cf..c8005288b1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -116,10 +116,6 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* If the topic lengths are different but an exact match is required, return * false. */ @@ -128,10 +124,6 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) { @@ -153,25 +145,9 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, status = true; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } - else - { - EMPTY_ELSE_MARKER; - } /* Filter "sport/+" also matches the "sport/" but not "sport". */ if( nameIndex == topicNameLength - 1 ) @@ -183,20 +159,8 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, status = true; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } } - else - { - EMPTY_ELSE_MARKER; - } } else { @@ -241,10 +205,6 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, status = true; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -296,14 +256,6 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, * will remove and clean up this subscription. */ pSubscription->unsubscribed = true; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } return match; @@ -388,10 +340,6 @@ IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, pSubscriptionList, i ); } - else - { - EMPTY_ELSE_MARKER; - } return status; } @@ -432,10 +380,6 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, { break; } - else - { - EMPTY_ELSE_MARKER; - } /* Subscription found. Calculate pointer to subscription object. */ pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); @@ -484,14 +428,6 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, { IotMqtt_FreeSubscription( pSubscription ); } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Move current link pointer. */ @@ -575,10 +511,6 @@ void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnecti IotMqtt_FreeSubscription( pSubscription ); } } - else - { - EMPTY_ELSE_MARKER; - } } IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); @@ -624,17 +556,9 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, pCurrentSubscription->qos = IOT_MQTT_QOS_0; pCurrentSubscription->callback = pSubscription->callback; } - else - { - EMPTY_ELSE_MARKER; - } status = true; } - else - { - EMPTY_ELSE_MARKER; - } IotMutex_Unlock( &( mqttConnection->subscriptionMutex ) ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 5876d2fa0a..ea68d8063f 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -63,10 +63,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that a client identifier was set. */ if( pConnectInfo->pClientIdentifier == NULL ) @@ -76,10 +72,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check for a zero-length client identifier. Zero-length client identifiers * are not allowed with clean sessions. */ @@ -94,14 +86,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Check that the number of persistent session subscriptions is valid. */ @@ -115,14 +99,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* If will info is provided, check that it is valid. */ @@ -134,14 +110,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* The AWS IoT MQTT service enforces a client ID length limit. */ @@ -172,10 +140,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) goto cleanup; } } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -201,20 +165,12 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check topic name for NULL or zero-length. */ if( pPublishInfo->pTopicName == NULL ) { IotLogError( "Publish topic name must be set." ); } - else - { - EMPTY_ELSE_MARKER; - } if( pPublishInfo->topicNameLength == 0 ) { @@ -223,10 +179,6 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } if( pPublishInfo->payloadLength != 0 ) { @@ -249,16 +201,8 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } } } - else - { - EMPTY_ELSE_MARKER; - } /* Check for a valid QoS. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) @@ -270,14 +214,6 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Check the retry parameters. */ @@ -290,14 +226,6 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Check for compatibility with AWS IoT MQTT server. */ @@ -311,10 +239,6 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check topic name length. */ if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) @@ -325,14 +249,6 @@ static bool _validatePublish( bool awsIotMqttMode, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } cleanup: @@ -350,10 +266,6 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, { maximumPayloadLength = AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; } - else - { - EMPTY_ELSE_MARKER; - } return _validatePublish( awsIotMqttMode, maximumPayloadLength, @@ -386,10 +298,6 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check that reference is waitable. */ if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) @@ -399,10 +307,6 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } cleanup: return status; @@ -432,10 +336,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } if( listSize == 0 ) { @@ -444,10 +344,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ if( awsIotMqttMode == true ) @@ -461,14 +357,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } for( i = 0; i < listSize; i++ ) @@ -487,14 +375,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } if( pListElement->callback.function == NULL ) @@ -504,14 +384,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Check subscription topic filter. */ @@ -522,10 +394,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } if( pListElement->topicFilterLength == 0 ) { @@ -534,10 +402,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Check for compatibility with AWS IoT MQTT server. */ if( awsIotMqttMode == true ) @@ -551,14 +415,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Check that the wildcards '+' and '#' are being used correctly. */ @@ -581,14 +437,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ @@ -603,14 +451,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } break; @@ -628,10 +468,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } /* Unless '#' is standalone, it must be preceded by '/'. */ if( pListElement->topicFilterLength > 1 ) @@ -645,14 +481,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, status = false; goto cleanup; } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; } break; diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index f0c81adb27..0f06224beb 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -224,17 +224,6 @@ #endif /** @endcond */ -/** - * @brief Marks the empty statement of an `else` branch. - * - * Does nothing, but allows test coverage to detect branches not taken. By default, - * this is defined to nothing. When running code coverage testing, this is defined - * to an assembly NOP. - */ -#ifndef EMPTY_ELSE_MARKER - #define EMPTY_ELSE_MARKER -#endif - #define MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 23 ) /**< @brief Optional maximum length of client identifier specified by MQTT 3.1.1. */ #define MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 268435456 ) ) /**< @brief Maximum publish payload length supported by MQTT 3.1.1. */ #define MQTT_SERVER_MAX_LWT_PAYLOAD_LENGTH ( ( size_t ) UINT16_MAX ) /**< @brief Maximum LWT payload length supported by MQTT 3.1.1. */ @@ -1022,15 +1011,8 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason { \ _returnValue = pSerializer->_serializerMember; \ } \ - else \ - { \ - EMPTY_ELSE_MARKER; \ - } \ - } \ - else \ - { \ - EMPTY_ELSE_MARKER; \ } \ + \ return _returnValue; \ } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ diff --git a/tests/iot_config.h b/tests/iot_config.h index 3a06374ae0..7937718d1d 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -218,9 +218,6 @@ #error "Unsupported compiler. Only gcc and clang are supported for coverage." #endif - /* Define the empty else marker if test coverage is enabled. */ - #define EMPTY_ELSE_MARKER __asm__ __volatile__ ( "nop" ) - /* Define a custom logging puts function. This function allows coverage * testing of logging functions, but prevents excessive logs from being * printed. */ From 66d1cb81c8a226eb1bc83a7ada9f9db7bd36d5cb Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 16 Dec 2019 16:41:55 -0800 Subject: [PATCH 347/844] Refactor to reduce complexity (#687) * Refactor _IotMqtt_DeserializeSuback * Refactor find operation * Refactor QoS and string check * Fix CBMC proof for new function name * Refactor subscription list validation --- cbmc/proofs/DeserializeSuback/Makefile | 2 +- .../standard/mqtt/src/iot_mqtt_operation.c | 25 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 150 ++-- .../standard/mqtt/src/iot_mqtt_validate.c | 650 +++++++++++------- .../mqtt/test/unit/iot_tests_mqtt_api.c | 4 +- 5 files changed, 515 insertions(+), 316 deletions(-) diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/DeserializeSuback/Makefile index 854cf3ca49..258a1c8ff2 100644 --- a/cbmc/proofs/DeserializeSuback/Makefile +++ b/cbmc/proofs/DeserializeSuback/Makefile @@ -7,7 +7,7 @@ ABSTRACTIONS = \ --remove-function-body _IotMqtt_RemoveSubscriptionByPacket \ UNWINDING = \ - --unwindset _IotMqtt_DeserializeSuback.0:$(BUFFER_SIZE) \ + --unwindset _decodeSubackStatus.0:$(BUFFER_SIZE) \ OBJS = \ $(ENTRY)_harness.goto \ diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 64c4bd445b..78aa8fca73 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -976,20 +976,11 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, operationMatchParams.type = type; operationMatchParams.pPacketIdentifier = pPacketIdentifier; - if( pPacketIdentifier != NULL ) - { - IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response " - "with packet identifier %hu.", - pMqttConnection, - IotMqtt_OperationType( type ), - *pPacketIdentifier ); - } - else - { - IotLogDebug( "(MQTT connection %p) Searching for operation %s pending response.", - pMqttConnection, - IotMqtt_OperationType( type ) ); - } + IotLogDebug( "(MQTT connection %p) Searching for operation %s " + "with packet identifier %hu.", + pMqttConnection, + IotMqtt_OperationType( type ), + ( pPacketIdentifier == NULL ) ? 0 : *pPacketIdentifier ); /* Find and remove the first matching element in the list. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -1019,12 +1010,6 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { pResult = NULL; } - else - { - /* Check job reference counts. A waitable operation should have a - * count of 2; a non-waitable operation should have a count of 1. */ - IotMqtt_Assert( pResult->u.operation.jobReference == ( 1 + ( waitable == true ) ) ); - } } else { diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index dac7a6782a..57fe108e99 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -334,6 +334,19 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi uint8_t * pBuffer, size_t unsubscribePacketSize ); +/** + * @brief Decode the status bytes of a SUBACK packet. + * + * @param[in] statusCount Number of status bytes in the SUBACK. + * @param[in] pStatusStart The first status byte in the SUBACK. + * @param[in] pSuback The SUBACK packet received from the network. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SERVER_REFUSED, or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _decodeSubackStatus( size_t statusCount, + const uint8_t * pStatusStart, + _mqttPacket_t * pSuback ); + /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -1007,6 +1020,68 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi /*-----------------------------------------------------------*/ +static IotMqttError_t _decodeSubackStatus( size_t statusCount, + const uint8_t * pStatusStart, + _mqttPacket_t * pSuback ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t subscriptionStatus = 0; + size_t i = 0; + + /* Iterate through each status byte in the SUBACK packet. */ + for( i = 0; i < statusCount; i++ ) + { + /* Read a single status byte in SUBACK. */ + subscriptionStatus = *( pStatusStart + i ); + + /* MQTT 3.1.1 defines the following values as status codes. */ + switch( subscriptionStatus ) + { + case 0x00: + case 0x01: + case 0x02: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); + break; + + case 0x80: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu refused.", ( unsigned long ) i ); + + /* Remove a rejected subscription from the subscription manager. */ + _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, + pSuback->packetIdentifier, + ( int32_t ) i ); + + status = IOT_MQTT_SERVER_REFUSED; + + break; + + default: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + status = IOT_MQTT_BAD_RESPONSE; + + break; + } + + /* Stop parsing the subscription statuses if a bad response was received. */ + if( status == IOT_MQTT_BAD_RESPONSE ) + { + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { @@ -1169,6 +1244,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI _serializeConnect( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); cleanup: + return status; } @@ -1283,6 +1359,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) } cleanup: + return status; } @@ -1339,6 +1416,7 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI publishPacketSize ); cleanup: + return status; } @@ -1554,6 +1632,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) "Payload length %hu.", pOutput->payloadLength ); cleanup: + return status; } @@ -1639,6 +1718,7 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) } cleanup: + return status; } @@ -1700,6 +1780,7 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc cleanup: + return status; } @@ -1708,8 +1789,7 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t i = 0, remainingLength = pSuback->remainingLength; - uint8_t subscriptionStatus = 0; + size_t remainingLength = pSuback->remainingLength; const uint8_t * pVariableHeader = pSuback->pRemainingData; /* A SUBACK must have a remaining length of at least 3 to accommodate the @@ -1744,56 +1824,12 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) goto cleanup; } - /* Iterate through each status byte in the SUBACK packet. */ - for( i = 0; i < remainingLength - sizeof( uint16_t ); i++ ) - { - /* Read a single status byte in SUBACK. */ - subscriptionStatus = *( pVariableHeader + sizeof( uint16_t ) + i ); - - /* MQTT 3.1.1 defines the following values as status codes. */ - switch( subscriptionStatus ) - { - case 0x00: - case 0x01: - case 0x02: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); - break; - - case 0x80: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu refused.", ( unsigned long ) i ); - - /* Remove a rejected subscription from the subscription manager. */ - _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, - pSuback->packetIdentifier, - ( int32_t ) i ); - - status = IOT_MQTT_SERVER_REFUSED; - - break; - - default: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); - - status = IOT_MQTT_BAD_RESPONSE; - - break; - } - - /* Stop parsing the subscription statuses if a bad response was received. */ - if( status == IOT_MQTT_BAD_RESPONSE ) - { - break; - } - } + status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), + pVariableHeader + sizeof( uint16_t ), + pSuback ); cleanup: + return status; } @@ -1854,6 +1890,7 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub unsubscribePacketSize ); cleanup: + return status; } @@ -1903,6 +1940,7 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) } cleanup: + return status; } @@ -1959,6 +1997,7 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) } cleanup: + return status; } @@ -2050,6 +2089,7 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne } cleanup: + return status; } @@ -2090,6 +2130,7 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn bufferSize ); cleanup: + return status; } @@ -2148,6 +2189,7 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, } cleanup: + return status; } @@ -2192,6 +2234,7 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr bufferSize ); cleanup: + return status; } @@ -2240,6 +2283,7 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo } cleanup: + return status; } @@ -2284,6 +2328,7 @@ IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, pBuffer, bufferSize ); cleanup: + return status; } @@ -2327,6 +2372,7 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs pBuffer, bufferSize ); cleanup: + return status; } @@ -2361,6 +2407,7 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); cleanup: + return status; } @@ -2394,6 +2441,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); cleanup: + return status; } @@ -2440,6 +2488,7 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) } cleanup: + return status; } @@ -2506,6 +2555,7 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) } cleanup: + return status; } diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index ea68d8063f..9e2d52a737 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -47,101 +47,117 @@ static bool _validatePublish( bool awsIotMqttMode, const char * pPublishTypeDescription, const IotMqttPublishInfo_t * pPublishInfo ); -/*-----------------------------------------------------------*/ +/** + * @brief Check that an #IotMqttQos_t is valid. + * + * @param[in] qos The QoS to check. + * + * @return `true` if `qos` is valid; `false` otherwise. + */ +static bool _validateQos( IotMqttQos_t qos ); -bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) -{ - bool status = true; - uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; - bool enforceMaxClientIdLength = false; +/** + * @brief Check that a string is valid. + * + * @param[in] pString The string to check. + * @param[in] length Length of string to check. + * + * @return `true` if `pString` is valid; `false` otherwise. + */ +static bool _validateString( const char * pString, + uint16_t length ); - /* Check for NULL. */ - if( pConnectInfo == NULL ) - { - IotLogError( "MQTT connection information cannot be NULL." ); +/** + * @brief Check that a list of subscriptions is valid. + * + * @param[in] awsIotMqttMode Whether to enforce list length restrictions from AWS IoT. + * @param[in] pListStart First element of the list. + * @param[in] listSize Length of the list. + * + * @return `true` if `pListStart` is valid; `false` otherwise. + */ +static bool _validateListSize( bool awsIotMqttMode, + const IotMqttSubscription_t * pListStart, + size_t listSize ); - status = false; - goto cleanup; - } +/** + * @brief Check that a single subscription is valid. + * + * @param[in] awsIotMqttMode Whether to enforce the topic filter restrictions from AWS IoT. + * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. + * @param[in] pSubscription The subscription to check. + * + * @return `true` if `pSubscription` is valid; `false` otherwise. + */ +static bool _validateSubscription( bool awsIotMqttMode, + IotMqttOperationType_t operation, + const IotMqttSubscription_t * pSubscription ); - /* Check that a client identifier was set. */ - if( pConnectInfo->pClientIdentifier == NULL ) - { - IotLogError( "Client identifier must be set." ); +/** + * @brief Check that the MQTT `+` wildcard is being used correctly. + * + * @param[in] index Index of `+` in the topic filter. + * @param[in] pSubscription Subscription with the topic filter to check. + * + * @return `true` if the `+` wilcard is valid; `false` otherwise. + */ +static bool _validateWildcardPlus( uint16_t index, + const IotMqttSubscription_t * pSubscription ); - status = false; - goto cleanup; - } +/** + * @brief Check that the MQTT `#` wildcard is being used correctly. + * + * @param[in] index Index of `#` in the topic filter. + * @param[in] pSubscription Subscription with the topic filter to check. + * + * @return `true` if the `#` wilcard is valid; `false` otherwise. + */ +static bool _validateWildcardHash( uint16_t index, + const IotMqttSubscription_t * pSubscription ); - /* Check for a zero-length client identifier. Zero-length client identifiers - * are not allowed with clean sessions. */ - if( pConnectInfo->clientIdentifierLength == 0 ) +/*-----------------------------------------------------------*/ + +static bool _validateQos( IotMqttQos_t qos ) +{ + bool status = false; + + switch( qos ) { - IotLogWarn( "A zero-length client identifier was provided." ); + case IOT_MQTT_QOS_0: + case IOT_MQTT_QOS_1: + status = true; + break; - if( pConnectInfo->cleanSession == true ) - { - IotLogError( "A zero-length client identifier cannot be used with a clean session." ); + default: + IotLogError( "QoS must be either 0 or 1." ); - status = false; - goto cleanup; - } + break; } - /* Check that the number of persistent session subscriptions is valid. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) - { - if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ) == false ) - { - status = false; - goto cleanup; - } - } + return status; +} - /* If will info is provided, check that it is valid. */ - if( pConnectInfo->pWillInfo != NULL ) - { - if( _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, - pConnectInfo->pWillInfo ) == false ) - { - status = false; - goto cleanup; - } - } +/*-----------------------------------------------------------*/ - /* The AWS IoT MQTT service enforces a client ID length limit. */ - if( pConnectInfo->awsIotMqttMode == true ) +static bool _validateString( const char * pString, + uint16_t length ) +{ + bool status = true; + + if( pString == NULL ) { - maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; - enforceMaxClientIdLength = true; + status = false; + goto cleanup; } - if( pConnectInfo->clientIdentifierLength > maxClientIdLength ) + if( length == 0 ) { - if( enforceMaxClientIdLength == false ) - { - IotLogWarn( "A client identifier length of %hu is longer than %hu, " - "which is " - "the longest client identifier a server must accept.", - pConnectInfo->clientIdentifierLength, - maxClientIdLength ); - } - else - { - IotLogError( "A client identifier length of %hu exceeds the " - "maximum supported length of %hu.", - pConnectInfo->clientIdentifierLength, - maxClientIdLength ); - - status = false; - goto cleanup; - } + status = false; + goto cleanup; } cleanup: + return status; } @@ -167,16 +183,12 @@ static bool _validatePublish( bool awsIotMqttMode, } /* Check topic name for NULL or zero-length. */ - if( pPublishInfo->pTopicName == NULL ) - { - IotLogError( "Publish topic name must be set." ); - } + status = _validateString( pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); - if( pPublishInfo->topicNameLength == 0 ) + if( status == false ) { - IotLogError( "Publish topic name length cannot be 0." ); + IotLogError( "Publish topic name must be set." ); - status = false; goto cleanup; } @@ -205,15 +217,11 @@ static bool _validatePublish( bool awsIotMqttMode, } /* Check for a valid QoS. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) - { - if( pPublishInfo->qos != IOT_MQTT_QOS_1 ) - { - IotLogError( "Publish QoS must be either 0 or 1." ); + status = _validateQos( pPublishInfo->qos ); - status = false; - goto cleanup; - } + if( status == false ) + { + goto cleanup; } /* Check the retry parameters. */ @@ -252,6 +260,302 @@ static bool _validatePublish( bool awsIotMqttMode, } cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _validateListSize( bool awsIotMqttMode, + const IotMqttSubscription_t * pListStart, + size_t listSize ) +{ + bool status = true; + + /* Check for empty list. */ + if( pListStart == NULL ) + { + IotLogError( "Subscription list pointer cannot be NULL." ); + + status = false; + goto cleanup; + } + + if( listSize == 0 ) + { + IotLogError( "Empty subscription list." ); + + status = false; + goto cleanup; + } + + /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ + if( awsIotMqttMode == true ) + { + if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + { + IotLogError( "AWS IoT does not support more than %d topic filters per " + "subscription request.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); + + status = false; + goto cleanup; + } + } + +cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _validateSubscription( bool awsIotMqttMode, + IotMqttOperationType_t operation, + const IotMqttSubscription_t * pSubscription ) +{ + bool status = true; + uint16_t i = 0; + + /* Check for a valid QoS and callback function when subscribing. */ + if( operation == IOT_MQTT_SUBSCRIBE ) + { + status = _validateQos( pSubscription->qos ); + + if( status == false ) + { + goto cleanup; + } + + if( pSubscription->callback.function == NULL ) + { + IotLogError( "Callback function must be set." ); + + status = false; + goto cleanup; + } + } + + /* Check subscription topic filter. */ + status = _validateString( pSubscription->pTopicFilter, pSubscription->topicFilterLength ); + + if( status == false ) + { + IotLogError( "Subscription topic filter must be set." ); + + goto cleanup; + } + + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) + { + /* Check topic filter length. */ + if( pSubscription->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + status = false; + goto cleanup; + } + } + + /* Check that the wildcards '+' and '#' are being used correctly. */ + for( i = 0; i < pSubscription->topicFilterLength; i++ ) + { + if( pSubscription->pTopicFilter[ i ] == '+' ) + { + status = _validateWildcardPlus( i, pSubscription ); + } + else if( pSubscription->pTopicFilter[ i ] == '#' ) + { + status = _validateWildcardHash( i, pSubscription ); + } + + if( status == false ) + { + goto cleanup; + } + } + +cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _validateWildcardPlus( uint16_t index, + const IotMqttSubscription_t * pSubscription ) +{ + bool status = true; + + /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ + if( index > 0 ) + { + if( pSubscription->pTopicFilter[ index - 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", + pSubscription->topicFilterLength, + pSubscription->pTopicFilter ); + + status = false; + goto cleanup; + } + } + + /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ + if( index < pSubscription->topicFilterLength - 1 ) + { + if( pSubscription->pTopicFilter[ index + 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", + pSubscription->topicFilterLength, + pSubscription->pTopicFilter ); + + status = false; + goto cleanup; + } + } + +cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _validateWildcardHash( uint16_t index, + const IotMqttSubscription_t * pSubscription ) +{ + bool status = true; + + /* '#' must be the last character in the filter. */ + if( index != pSubscription->topicFilterLength - 1 ) + { + IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", + pSubscription->topicFilterLength, + pSubscription->pTopicFilter ); + + status = false; + goto cleanup; + } + + /* Unless '#' is standalone, it must be preceded by '/'. */ + if( pSubscription->topicFilterLength > 1 ) + { + if( pSubscription->pTopicFilter[ index - 1 ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", + pSubscription->topicFilterLength, + pSubscription->pTopicFilter ); + + status = false; + goto cleanup; + } + } + +cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) +{ + bool status = true; + uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; + bool enforceMaxClientIdLength = false; + + /* Check for NULL. */ + if( pConnectInfo == NULL ) + { + IotLogError( "MQTT connection information cannot be NULL." ); + + status = false; + goto cleanup; + } + + /* Check that a client identifier was set. */ + if( pConnectInfo->pClientIdentifier == NULL ) + { + IotLogError( "Client identifier must be set." ); + + status = false; + goto cleanup; + } + + /* Check for a zero-length client identifier. Zero-length client identifiers + * are not allowed with clean sessions. */ + if( pConnectInfo->clientIdentifierLength == 0 ) + { + IotLogWarn( "A zero-length client identifier was provided." ); + + if( pConnectInfo->cleanSession == true ) + { + IotLogError( "A zero-length client identifier cannot be used with a clean session." ); + + status = false; + goto cleanup; + } + } + + /* Check that the number of persistent session subscriptions is valid. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ) == false ) + { + status = false; + goto cleanup; + } + } + + /* If will info is provided, check that it is valid. */ + if( pConnectInfo->pWillInfo != NULL ) + { + if( _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, + pConnectInfo->pWillInfo ) == false ) + { + status = false; + goto cleanup; + } + } + + /* The AWS IoT MQTT service enforces a client ID length limit. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; + enforceMaxClientIdLength = true; + } + + if( pConnectInfo->clientIdentifierLength > maxClientIdLength ) + { + if( enforceMaxClientIdLength == false ) + { + IotLogWarn( "A client identifier length of %hu is longer than %hu, " + "which is " + "the longest client identifier a server must accept.", + pConnectInfo->clientIdentifierLength, + maxClientIdLength ); + } + else + { + IotLogError( "A client identifier length of %hu exceeds the " + "maximum supported length of %hu.", + pConnectInfo->clientIdentifierLength, + maxClientIdLength ); + + status = false; + goto cleanup; + } + } + +cleanup: + return status; } @@ -309,6 +613,7 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) } cleanup: + return status; } @@ -321,177 +626,36 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { bool status = true; size_t i = 0; - uint16_t j = 0; - const IotMqttSubscription_t * pListElement = NULL; /* Operation must be either subscribe or unsubscribe. */ IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || ( operation == IOT_MQTT_UNSUBSCRIBE ) ); - /* Check for empty list. */ - if( pListStart == NULL ) - { - IotLogError( "Subscription list pointer cannot be NULL." ); - - status = false; - goto cleanup; - } + /* Check that subscription list is valid. */ + status = _validateListSize( awsIotMqttMode, + pListStart, + listSize ); - if( listSize == 0 ) + if( status == false ) { - IotLogError( "Empty subscription list." ); - - status = false; goto cleanup; } - /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ - if( awsIotMqttMode == true ) - { - if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) - { - IotLogError( "AWS IoT does not support more than %d topic filters per " - "subscription request.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); - - status = false; - goto cleanup; - } - } - + /* Check each member of the subscription list. */ for( i = 0; i < listSize; i++ ) { - pListElement = &( pListStart[ i ] ); + status = _validateSubscription( awsIotMqttMode, + operation, + &( pListStart[ i ] ) ); - /* Check for a valid QoS and callback function when subscribing. */ - if( operation == IOT_MQTT_SUBSCRIBE ) + if( status == false ) { - if( pListElement->qos != IOT_MQTT_QOS_0 ) - { - if( pListElement->qos != IOT_MQTT_QOS_1 ) - { - IotLogError( "Subscription QoS must be either 0 or 1." ); - - status = false; - goto cleanup; - } - } - - if( pListElement->callback.function == NULL ) - { - IotLogError( "Callback function must be set." ); - - status = false; - goto cleanup; - } - } - - /* Check subscription topic filter. */ - if( pListElement->pTopicFilter == NULL ) - { - IotLogError( "Subscription topic filter cannot be NULL." ); - - status = false; - goto cleanup; - } - - if( pListElement->topicFilterLength == 0 ) - { - IotLogError( "Subscription topic filter length cannot be 0." ); - - status = false; - goto cleanup; - } - - /* Check for compatibility with AWS IoT MQTT server. */ - if( awsIotMqttMode == true ) - { - /* Check topic filter length. */ - if( pListElement->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - { - IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - - status = false; - goto cleanup; - } - } - - /* Check that the wildcards '+' and '#' are being used correctly. */ - for( j = 0; j < pListElement->topicFilterLength; j++ ) - { - switch( pListElement->pTopicFilter[ j ] ) - { - /* Check that the single level wildcard '+' is used correctly. */ - case '+': - - /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ - if( j > 0 ) - { - if( pListElement->pTopicFilter[ j - 1 ] != '/' ) - { - IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - status = false; - goto cleanup; - } - } - - /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ - if( j < pListElement->topicFilterLength - 1 ) - { - if( pListElement->pTopicFilter[ j + 1 ] != '/' ) - { - IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - status = false; - goto cleanup; - } - } - - break; - - /* Check that the multi-level wildcard '#' is used correctly. */ - case '#': - - /* '#' must be the last character in the filter. */ - if( j != pListElement->topicFilterLength - 1 ) - { - IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - status = false; - goto cleanup; - } - - /* Unless '#' is standalone, it must be preceded by '/'. */ - if( pListElement->topicFilterLength > 1 ) - { - if( pListElement->pTopicFilter[ j - 1 ] != '/' ) - { - IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", - pListElement->topicFilterLength, - pListElement->pTopicFilter ); - - status = false; - goto cleanup; - } - } - - break; - - default: - break; - } + break; } } cleanup: + return status; } diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index c5769a036a..c3dccc48e1 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -2246,9 +2246,9 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) /* 1. Find out length of the packet .*/ memset( &publishInfo, 0x00, sizeof( publishInfo ) ); publishInfo.pTopicName = "/test/topic"; - publishInfo.topicNameLength = strlen( "/test/topic" ); + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = "Hello World"; - publishInfo.payloadLength = strlen( "Hello World" ); + publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); publishInfo.qos = IOT_MQTT_QOS_0; /* Calculate exact packet size and remaining length */ status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &bufferSize ); From 49ac4f6a1f90e49b44364dbfd88ff0443bfbc1bb Mon Sep 17 00:00:00 2001 From: gerardoti <51765879+gerardoti@users.noreply.github.com> Date: Tue, 17 Dec 2019 12:33:20 -0800 Subject: [PATCH 348/844] Set priority and stack size for posix threads (#682) --- ports/posix/src/iot_threads_posix.c | 33 +++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/ports/posix/src/iot_threads_posix.c b/ports/posix/src/iot_threads_posix.c index 95d331e83b..6ef8e03ad3 100644 --- a/ports/posix/src/iot_threads_posix.c +++ b/ports/posix/src/iot_threads_posix.c @@ -160,10 +160,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, _threadInfo_t * pThreadInfo = NULL; pthread_t newThread; pthread_attr_t threadAttributes; - - /* Ignore priority and stack size. */ - ( void ) priority; - ( void ) stackSize; + struct sched_param priorityParam; /* Allocate memory for the new thread info. */ pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); @@ -200,6 +197,34 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IOT_SET_AND_GOTO_CLEANUP( false ); } + if( stackSize != 0 ) + { + posixErrno = pthread_attr_setstacksize( &threadAttributes, stackSize ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to set thread stack size. errno=%d.", + posixErrno ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + if( priority != 0 ) + { + priorityParam.sched_priority = priority; + posixErrno = pthread_attr_setschedparam( &threadAttributes, + &priorityParam ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to set thread priority. errno=%d.", + posixErrno ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + /* Set the thread routine and argument. */ pThreadInfo->threadRoutine = threadRoutine; pThreadInfo->pArgument = pArgument; From b5e7bcc8e9d0ae0dafa6998a72aa0cb7c6a3ef18 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Tue, 17 Dec 2019 16:15:18 -0500 Subject: [PATCH 349/844] Automate generation of cbmc-batch.yaml during continuous integration. (#690) The CBMC proof checking done during continuous integration is driven by a set of yaml files. Proofs are developed locally using a Makefile, but proofs are checked in CI using a file cbmc-batch.yaml sitting next to the Makefile in the proof directory. The Makefile has a target to generate the yaml file from the Makefile. This patch automates the regeneration of the yaml files during CI before launching the proof checking. --- cbmc/proofs/DeserializeSuback/cbmc-batch.yaml | 2 +- cbmc/proofs/make_cbmc_batch_files.py | 53 +++++++++++++++++++ cbmc/proofs/prepare.py | 48 +++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 cbmc/proofs/make_cbmc_batch_files.py create mode 100755 cbmc/proofs/prepare.py diff --git a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml index d3a9d2e005..955e25aeb5 100644 --- a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml +++ b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml @@ -1,4 +1,4 @@ jobos: ubuntu16 -cbmcflags: '=--unwindset;_IotMqtt_DeserializeSuback.0:100;--bounds-check;--pointer-check;--unwinding-assertions=' +cbmcflags: '=--unwindset;_decodeSubackStatus.0:100;--bounds-check;--pointer-check;--unwinding-assertions=' goto: DeserializeSuback.goto expected: SUCCESSFUL diff --git a/cbmc/proofs/make_cbmc_batch_files.py b/cbmc/proofs/make_cbmc_batch_files.py new file mode 100644 index 0000000000..622e000c3a --- /dev/null +++ b/cbmc/proofs/make_cbmc_batch_files.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# +# Generation of the cbmc-batch.yaml files for the CBMC proofs. +# +# Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import platform +import subprocess + + +def remove_cbmc_yaml_files(): + for dyr, _, files in os.walk("."): + cbmc_batch_files = [os.path.join(os.path.abspath(dyr), file) + for file in files if file == "cbmc-batch.yaml"] + for file in cbmc_batch_files: + os.remove(file) + + +def create_cbmc_yaml_files(): + # The YAML files are only used by CI and are not needed on Windows. + if platform.system() == "Windows": + return + for dyr, _, files in os.walk("."): + harness = [file for file in files if file.endswith("_harness.c")] + if harness and "Makefile" in files: + subprocess.run(["make", "cbmc-batch.yaml"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=os.path.abspath(dyr), + check=True) + +if __name__ == '__main__': + remove_cbmc_yaml_files() + create_cbmc_yaml_files() diff --git a/cbmc/proofs/prepare.py b/cbmc/proofs/prepare.py new file mode 100755 index 0000000000..205d4fee12 --- /dev/null +++ b/cbmc/proofs/prepare.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# +# Python script for preparing the code base for the CBMC proofs. +# +# Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import logging +import os +import sys +import textwrap +from subprocess import CalledProcessError + +from make_cbmc_batch_files import create_cbmc_yaml_files + +def build(): + try: + create_cbmc_yaml_files() + except CalledProcessError as e: + logging.error(textwrap.dedent("""\ + An error occured during cbmc-batch generation. + The error message is: {} + """.format(str(e)))) + exit(1) + +################################################################ + +if __name__ == '__main__': + logging.basicConfig(format="{script}: %(levelname)s %(message)s".format( + script=os.path.basename(__file__))) + build() From 9de8d2a4277903225e72b05e2c4ea00badf9fd19 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 17 Dec 2019 13:23:47 -0800 Subject: [PATCH 350/844] Add macros for ignoring stack size and priority (#691) --- libraries/platform/iot_threads.h | 6 +++-- libraries/platform/types/iot_platform_types.h | 24 ++++++++++++++----- ports/posix/src/iot_threads_posix.c | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/libraries/platform/iot_threads.h b/libraries/platform/iot_threads.h index 15152b9a1d..72dd04d2d6 100644 --- a/libraries/platform/iot_threads.h +++ b/libraries/platform/iot_threads.h @@ -82,10 +82,12 @@ * @param[in] pArgument The argument passed to `threadRoutine`. * @param[in] priority Represents the priority of the new thread, as defined by * the system. The value #IOT_THREAD_DEFAULT_PRIORITY (i.e. `0`) must be used to - * represent the system default for thread priority. + * represent the system default for thread priority. #IOT_THREAD_IGNORE_PRIORITY + * should be passed if this parameter is not relevant for the port implementation. * @param[in] stackSize Represents the stack size of the new thread, as defined * by the system. The value #IOT_THREAD_DEFAULT_STACK_SIZE (i.e. `0`) must be used - * to represent the system default for stack size. + * to represent the system default for stack size. #IOT_THREAD_IGNORE_STACK_SIZE + * should be passed if this parameter is not relevant for the port implementation. * * @return `true` if the new thread was successfully created; `false` otherwise. * diff --git a/libraries/platform/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h index 4e4c7092ae..5ec68735b2 100644 --- a/libraries/platform/types/iot_platform_types.h +++ b/libraries/platform/types/iot_platform_types.h @@ -36,18 +36,30 @@ /*------------------------- Thread management types -------------------------*/ +/** + * @brief Placeholder value that should cause implementations of + * @ref platform_threads_function_createdetachedthread to ignore the priority argument. + */ +#define IOT_THREAD_IGNORE_PRIORITY ( -1 ) + +/** + * @brief Placeholder value that should cause implementations of + * @ref platform_threads_function_createdetachedthread to ignore the stack size argument. + */ +#define IOT_THREAD_IGNORE_STACK_SIZE ( 0 ) + /** * @brief A value representing the system default for new thread priority. */ #ifndef IOT_THREAD_DEFAULT_PRIORITY - #define IOT_THREAD_DEFAULT_PRIORITY 0 + #define IOT_THREAD_DEFAULT_PRIORITY IOT_THREAD_IGNORE_PRIORITY #endif /** * @brief A value representhing the system default for new thread stack size. */ #ifndef IOT_THREAD_DEFAULT_STACK_SIZE - #define IOT_THREAD_DEFAULT_STACK_SIZE 0 + #define IOT_THREAD_DEFAULT_STACK_SIZE IOT_THREAD_IGNORE_STACK_SIZE #endif /** @@ -114,7 +126,7 @@ typedef void ( * IotThreadRoutine_t )( void * ); * #include "iot_clock.h" * @endcode */ -typedef _IotSystemTimer_t IotTimer_t; +typedef _IotSystemTimer_t IotTimer_t; /*--------------------------- Network stack types ---------------------------*/ @@ -130,7 +142,7 @@ typedef _IotSystemTimer_t IotTimer_t; * the necessary information to connect to a TCP peer. For other network protocols, * this type should be set to an alternate structure as needed by the other protocol. */ -typedef _IotNetworkServerInfo_t IotNetworkServerInfo_t; +typedef _IotNetworkServerInfo_t IotNetworkServerInfo_t; /** * @ingroup platform_datatypes_handles @@ -145,7 +157,7 @@ typedef _IotNetworkServerInfo_t IotNetworkServerInfo_t; * protocols, this type should be set to an alternate structure as needed by the other * protocol. */ -typedef _IotNetworkCredentials_t IotNetworkCredentials_t; +typedef _IotNetworkCredentials_t IotNetworkCredentials_t; /** * @ingroup platform_datatypes_handles @@ -155,7 +167,7 @@ typedef _IotNetworkCredentials_t IotNetworkCredentials_t; * For the provided ports, `_IotNetworkConnection_t` will be automatically configured. * For other ports, `_IotNetworkConnection_t` should be set in `iot_config.h`. */ -typedef _IotNetworkConnection_t IotNetworkConnection_t; +typedef _IotNetworkConnection_t IotNetworkConnection_t; /*------------------------------ Metrics types ------------------------------*/ diff --git a/ports/posix/src/iot_threads_posix.c b/ports/posix/src/iot_threads_posix.c index 6ef8e03ad3..83d907e8c1 100644 --- a/ports/posix/src/iot_threads_posix.c +++ b/ports/posix/src/iot_threads_posix.c @@ -197,7 +197,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IOT_SET_AND_GOTO_CLEANUP( false ); } - if( stackSize != 0 ) + if( stackSize != IOT_THREAD_IGNORE_STACK_SIZE ) { posixErrno = pthread_attr_setstacksize( &threadAttributes, stackSize ); @@ -210,7 +210,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, } } - if( priority != 0 ) + if( priority != IOT_THREAD_IGNORE_PRIORITY ) { priorityParam.sched_priority = priority; posixErrno = pthread_attr_setschedparam( &threadAttributes, From 44787d0615849c06e5c6e098bd829191e3aeadca Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 17 Dec 2019 13:38:36 -0800 Subject: [PATCH 351/844] fix: MQTT serializer unit tests. (#688) --- libraries/standard/mqtt/lexicon.txt | 1 - .../mqtt/test/unit/iot_tests_mqtt_api.c | 71 +++++++++++-------- 2 files changed, 43 insertions(+), 29 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 90f74c014f..4000505954 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -167,7 +167,6 @@ struct structs suback subacks -subsciption subscribeasync subscribeserializeroverride subscribesync diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index c3dccc48e1..980ca6ce80 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -1724,7 +1724,8 @@ TEST( MQTT_Unit_API, SerializeConnectChecks ) IotMqttConnectInfo_t connectInfo; size_t remainingLength = 0; uint8_t buffer[ 20 ]; - size_t packetSize = sizeof( buffer ); + size_t bufferSize = sizeof( buffer ); + size_t packetSize = bufferSize; IotMqttError_t status = IOT_MQTT_SUCCESS; /* Verify bad parameter errors. */ @@ -1747,10 +1748,12 @@ TEST( MQTT_Unit_API, SerializeConnectChecks ) connectInfo.clientIdentifierLength = 4; status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); /* Make sure test succeeds. */ status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - + /* For this example, IotMqtt_GetConnectPacketSize() will return * packetSize = remainingLength +2 (two byte fixed header). * Make sure IotMqtt_SerializeConnect() @@ -1841,8 +1844,9 @@ TEST( MQTT_Unit_API, SerializeSubscribeChecks ) size_t subscriptionCount = 0; size_t remainingLength = 0; uint16_t packetIdentifier; - uint8_t buffer[ 20 ]; - size_t packetSize = sizeof( buffer ); + uint8_t buffer[ 25 ]; + size_t bufferSize = sizeof( buffer ); + size_t packetSize = bufferSize; IotMqttError_t status = IOT_MQTT_SUCCESS; /* Verify bad parameters fail. */ @@ -1882,8 +1886,10 @@ TEST( MQTT_Unit_API, SerializeSubscribeChecks ) &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - /* Make sure subsciption count of zero fails. */ + /* Make sure subscription count of zero fails. */ status = IotMqtt_SerializeSubscribe( &subscriptionList, 0, remainingLength, @@ -1926,8 +1932,9 @@ TEST( MQTT_Unit_API, SerializeUnsubscribeChecks ) size_t subscriptionCount = 0; size_t remainingLength = 0; uint16_t packetIdentifier; - uint8_t buffer[ 20 ]; - size_t packetSize = sizeof( buffer ); + uint8_t buffer[ 25 ]; + size_t bufferSize = sizeof( buffer ); + size_t packetSize = bufferSize; IotMqttError_t status = IOT_MQTT_SUCCESS; status = IotMqtt_SerializeUnsubscribe( NULL, @@ -1966,8 +1973,10 @@ TEST( MQTT_Unit_API, SerializeUnsubscribeChecks ) &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - /* Make sure subsciption count of zero fails. */ + /* Make sure subscription count of zero fails. */ status = IotMqtt_SerializeUnsubscribe( &subscriptionList, 0, remainingLength, @@ -2043,9 +2052,10 @@ TEST( MQTT_Unit_API, SerializePublishChecks ) IotMqttPublishInfo_t publishInfo; size_t remainingLength = 98; uint16_t packetIdentifier; - uint8_t * pPakcetIndentifierHigh; + uint8_t * pPacketIdentifierHigh; uint8_t buffer[ 100 ]; - size_t bufferSize; + size_t bufferSize = sizeof( buffer ); + size_t packetSize = bufferSize; IotMqttError_t status = IOT_MQTT_SUCCESS; /* Verify bad parameters fail. */ @@ -2056,25 +2066,25 @@ TEST( MQTT_Unit_API, SerializePublishChecks ) status = IotMqtt_SerializePublish( &publishInfo, remainingLength, NULL, - &pPakcetIndentifierHigh, + &pPacketIdentifierHigh, buffer, - 100 ); + packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); status = IotMqtt_SerializePublish( NULL, remainingLength, &packetIdentifier, - &pPakcetIndentifierHigh, + &pPacketIdentifierHigh, buffer, - 100 ); + packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); status = IotMqtt_SerializePublish( &publishInfo, remainingLength, &packetIdentifier, - &pPakcetIndentifierHigh, + &pPacketIdentifierHigh, NULL, - 100 ); + packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); /* NULL topic fails. */ @@ -2082,24 +2092,26 @@ TEST( MQTT_Unit_API, SerializePublishChecks ) status = IotMqtt_SerializePublish( &publishInfo, remainingLength, &packetIdentifier, - &pPakcetIndentifierHigh, + &pPacketIdentifierHigh, buffer, - 100 ); + packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); /* Good case succeeds */ publishInfo.pTopicName = "/test/topic"; publishInfo.topicNameLength = sizeof( "/test/topic" ); /* Calculate exact packet size and remaining length. */ - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &bufferSize ); + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); status = IotMqtt_SerializePublish( &publishInfo, remainingLength, &packetIdentifier, - &pPakcetIndentifierHigh, + &pPacketIdentifierHigh, buffer, - bufferSize ); + packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); } @@ -2204,7 +2216,7 @@ TEST( MQTT_Unit_API, DeserializeResponseChecks ) buffer[ 0 ] = 0x00; buffer[ 1 ] = 0x00; /* Set type, remaining length and remaining data. */ - mqttPacketInfo.type = 0x20; /* CONN ACK */ + mqttPacketInfo.type = 0x20; /* CONN ACK */ mqttPacketInfo.pRemainingData = buffer; mqttPacketInfo.remainingLength = 0x02; /* CONN ACK Remaining Length. */ status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); @@ -2223,10 +2235,12 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) IotMqttPublishInfo_t publishInfo; IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t buffer[ 100 ]; - size_t bufferSize = sizeof(buffer); + size_t bufferSize = sizeof( buffer ); + size_t packetSize = bufferSize; + size_t remainingLength = 0; uint16_t packetIdentifier; - uint8_t * pPakcetIndentifierHigh; + uint8_t * pPacketIdentifierHigh; uint8_t * pNetworkInterface; /* Verify parameters. */ @@ -2251,16 +2265,18 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); publishInfo.qos = IOT_MQTT_QOS_0; /* Calculate exact packet size and remaining length */ - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &bufferSize ); + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); /* 2. Serialize packet in the buffer. */ status = IotMqtt_SerializePublish( &publishInfo, remainingLength, &packetIdentifier, - &pPakcetIndentifierHigh, + &pPacketIdentifierHigh, buffer, - bufferSize ); + packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); /* 3. Deserialize - get type and length. */ @@ -2275,4 +2291,3 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) } /*-----------------------------------------------------------*/ - From 706daad20b4d810704ba5929786dd8f30ab1105f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 17 Dec 2019 16:06:54 -0800 Subject: [PATCH 352/844] Refactor subscription topic parsing (#689) --- .../standard/mqtt/src/iot_mqtt_subscription.c | 272 ++++++++++++------ 1 file changed, 192 insertions(+), 80 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index c8005288b1..5c4f9230ae 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -61,6 +61,63 @@ typedef struct _packetMatchParams /*-----------------------------------------------------------*/ +/** + * @brief Handle special corner cases regarding wildcards at the end of topic + * filters, as documented by the MQTT protocol spec. + * + * @param[in] pTopicFilter The topic filter containing the wildcard. + * @param[in] nameIndex Index of the topic name being examined. + * @param[in] filterIndex Index of the topic filter being examined. + * @param[in] topicNameLength Length of the topic name being examined. + * @param[in] topicFilterLength Length of the topic filter being examined. + * @param[out] pMatch Whether the topic filter and topic name match. + * + * @return `true` if the caller of this function should exit; `false` if the caller + * should continue parsing the topics. + */ +static bool _matchEndWildcards( const char * pTopicFilter, + uint16_t topicNameLength, + uint16_t topicFilterLength, + uint16_t nameIndex, + uint16_t filterIndex, + bool * pMatch ); + +/** + * @brief Attempt to match characters in a topic filter by wildcards. + * + * @param[in] pTopicFilter The topic filter containing the wildcard. + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of the topic name. + * @param[in] filterIndex Index of the wildcard in the topic filter. + * @param[in,out] pNameIndex Index of character in topic name. This variable is + * advanced for `+` wildcards. + * @param[out] pMatch Whether the topic filter and topic name match. + * + * @return `true` if the caller of this function should exit; `false` if the caller + * should continue parsing the topics. + */ +static bool _matchWildcards( const char * pTopicFilter, + const char * pTopicName, + uint16_t topicNameLength, + uint16_t filterIndex, + uint16_t * pNameIndex, + bool * pMatch ); + +/** + * @brief Match a topic name and topic filter while allowing the use of wildcards. + * + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of `pTopicName`. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of `pTopicFilter`. + * + * @return `true` if the topic name and topic filter match; `false` otherwise. + */ +static bool _topicFilterMatch( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength ); + /** * @brief Matches a topic name (from a publish) with a topic filter (from a * subscription). @@ -88,108 +145,125 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ -static bool _topicMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ) +static bool _matchEndWildcards( const char * pTopicFilter, + uint16_t topicNameLength, + uint16_t topicFilterLength, + uint16_t nameIndex, + uint16_t filterIndex, + bool * pMatch ) { - bool status = false; - uint16_t nameIndex = 0, filterIndex = 0; + bool status = false, endChar = false; - /* Because this function is called from a container function, the given link - * must never be NULL. */ - IotMqtt_Assert( pSubscriptionLink != NULL ); + /* Determine if the last character is reached for both topic name and topic + * filter for the '#' wildcard. */ + endChar = ( nameIndex == topicNameLength - 1 ) && ( filterIndex == topicFilterLength - 3 ); - _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - pSubscriptionLink, - link ); - _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; + if( endChar == true ) + { + /* Determine if the topic filter ends with the '#' wildcard. */ + status = ( pTopicFilter[ filterIndex + 1 ] == '/' ) && ( pTopicFilter[ filterIndex + 2 ] == '#' ); - /* Extract the relevant strings and lengths from parameters. */ - const char * pTopicName = pParam->pTopicName; - const char * pTopicFilter = pSubscription->pTopicFilter; - const uint16_t topicNameLength = pParam->topicNameLength; - const uint16_t topicFilterLength = pSubscription->topicFilterLength; + if( status == true ) + { + goto cleanup; + } + } - /* Check for an exact match. */ - if( topicNameLength == topicFilterLength ) - { - status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); + /* Determine if the last character is reached for both topic name and topic + * filter for the '+' wildcard. */ + endChar = ( nameIndex == topicNameLength - 1 ) && ( filterIndex == topicFilterLength - 2 ); - goto cleanup; + if( endChar == true ) + { + /* Filter "sport/+" also matches the "sport/" but not "sport". */ + status = ( pTopicFilter[ filterIndex + 1 ] == '+' ); } - /* If the topic lengths are different but an exact match is required, return - * false. */ - if( pParam->exactMatchOnly == true ) +cleanup: + *pMatch = status; + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _matchWildcards( const char * pTopicFilter, + const char * pTopicName, + uint16_t topicNameLength, + uint16_t filterIndex, + uint16_t * pNameIndex, + bool * pMatch ) +{ + bool status = false; + + /* Check for wildcards. */ + if( pTopicFilter[ filterIndex ] == '+' ) + { + /* Move topic name index to the end of the current level. + * This is identified by '/'. */ + while( ( *pNameIndex < topicNameLength ) && ( pTopicName[ *pNameIndex ] != '/' ) ) + { + ( *pNameIndex )++; + } + + ( *pNameIndex )--; + } + else if( pTopicFilter[ filterIndex ] == '#' ) + { + /* Subsequent characters don't need to be checked for the + * multi-level wildcard. */ + *pMatch = true; + status = true; + } + else { - status = false; - goto cleanup; + /* Any character mismatch other than '+' or '#' means the topic + * name does not match the topic filter. */ + *pMatch = false; + status = true; } + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _topicFilterMatch( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + bool status = false; + uint16_t nameIndex = 0, filterIndex = 0; + while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) { /* Check if the character in the topic name matches the corresponding * character in the topic filter string. */ if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) { - /* Handle special corner cases as documented by the MQTT protocol spec. */ - - /* Filter "sport/#" also matches "sport" since # includes the parent level. */ - if( nameIndex == topicNameLength - 1 ) - { - if( filterIndex == topicFilterLength - 3 ) - { - if( pTopicFilter[ filterIndex + 1 ] == '/' ) - { - if( pTopicFilter[ filterIndex + 2 ] == '#' ) - { - status = true; - goto cleanup; - } - } - } - } - - /* Filter "sport/+" also matches the "sport/" but not "sport". */ - if( nameIndex == topicNameLength - 1 ) + /* Handle special corner cases regarding wildcards at the end of + * topic filters, as documented by the MQTT protocol spec. */ + if( _matchEndWildcards( pTopicFilter, + topicNameLength, + topicFilterLength, + nameIndex, + filterIndex, + &status ) == true ) { - if( filterIndex == topicFilterLength - 2 ) - { - if( pTopicFilter[ filterIndex + 1 ] == '+' ) - { - status = true; - goto cleanup; - } - } + goto cleanup; } } else { - /* Check for wildcards. */ - if( pTopicFilter[ filterIndex ] == '+' ) - { - /* Move topic name index to the end of the current level. - * This is identified by '/'. */ - while( nameIndex < topicNameLength && pTopicName[ nameIndex ] != '/' ) - { - nameIndex++; - } - - /* Increment filter index to skip '/'. */ - filterIndex++; - continue; - } - else if( pTopicFilter[ filterIndex ] == '#' ) - { - /* Subsequent characters don't need to be checked if the for the - * multi-level wildcard. */ - status = true; - goto cleanup; - } - else + /* Check for matching wildcards. */ + if( _matchWildcards( pTopicFilter, + pTopicName, + topicNameLength, + filterIndex, + &nameIndex, + &status ) == true ) { - /* Any character mismatch other than '+' or '#' means the topic - * name does not match the topic filter. */ - status = false; goto cleanup; } } @@ -203,10 +277,48 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) { status = true; - goto cleanup; } cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ) +{ + bool status = false; + + /* This function is called from a container function; the caller + * will never pass NULL. */ + IotMqtt_Assert( pSubscriptionLink != NULL ); + + _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + pSubscriptionLink, + link ); + _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; + + /* Extract the relevant strings and lengths from parameters. */ + const char * pTopicName = pParam->pTopicName; + const char * pTopicFilter = pSubscription->pTopicFilter; + const uint16_t topicNameLength = pParam->topicNameLength; + const uint16_t topicFilterLength = pSubscription->topicFilterLength; + + /* Check for an exact match. */ + if( topicNameLength == topicFilterLength ) + { + status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); + } + + /* If an exact match is required, return the result of the comparison above. + * Otherwise, attempt to match with MQTT wildcards in topic filters. */ + if( pParam->exactMatchOnly == false ) + { + status = _topicFilterMatch( pTopicName, topicNameLength, pTopicFilter, topicFilterLength ); + } + return status; } From 70347b984bc299208f3d9296573c82773b5ddd88 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 19 Dec 2019 16:00:45 -0800 Subject: [PATCH 353/844] Add migration guide for platform (#693) --- doc/config/main | 3 +- doc/guide/migration.txt | 10 ++ doc/guide/migration/platform.txt | 288 +++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 doc/guide/migration.txt create mode 100644 doc/guide/migration/platform.txt diff --git a/doc/config/main b/doc/config/main index 99ab2b42a3..32b1919719 100644 --- a/doc/config/main +++ b/doc/config/main @@ -14,7 +14,8 @@ GENERATE_TAGFILE = doc/tag/main.tag # Input directories. INPUT = doc/ \ doc/guide \ - doc/guide/developer + doc/guide/developer \ + doc/guide/migration # Path of CMake GUI image. IMAGE_PATH += doc/guide diff --git a/doc/guide/migration.txt b/doc/guide/migration.txt new file mode 100644 index 0000000000..ba58f8855b --- /dev/null +++ b/doc/guide/migration.txt @@ -0,0 +1,10 @@ +/** +@page migration Migration Guide +@brief Migration guide from C SDK version 2 or 3 to version 4. + +Version 4 of the C SDK is a new design, and therefore NOT backwards compatible with previous versions. The pages below provide instructions on migrating from version 2 or 3 to version 4. + +In these pages, v3 refers to both previous versions 2 and 3, which are largely compatible with each other. +- @subpage migration_platform
+ Migrating @ref platform from v3 to v4. This must be done before any other component. +*/ diff --git a/doc/guide/migration/platform.txt b/doc/guide/migration/platform.txt new file mode 100644 index 0000000000..38f6624e13 --- /dev/null +++ b/doc/guide/migration/platform.txt @@ -0,0 +1,288 @@ +/** +@page migration_platform Platform layer +@brief How to migrate the platform layer from v3 to v4. + +The platform layer provides portability across different operating systems. Both v3 and v4 provide a POSIX port as an example. If you are running on a v3 application on a POSIX system, you can switch the to provided v4 POSIX port and skip this page. This guide is intended for applications running on other operating systems that would like to move their porting layer from v3 to v4. + +@section migration_platform_mutex Mutex +@brief How to migrate the mutex type from v3 to v4. + +The mutexes used in v3 and v4 implement the same interface. In v4, recursive mutexes must be supported. + +The table below shows the equivalent mutex types for v3 and v4. + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
Namestruct _IoT_Mutex_t_IotSystemMutex_tIn v3, the mutex type must be a struct.
In v4, the mutex type may be any type.
Declare in filethreads_platform.hiot_config.h + In v3, the mutex type must be declared in a file called threads_platform.h. + Link to sample +
+ In v4, we recommend declaring the mutex type in iot_config.h. +
+ +@subsection migration_platform_mutex_interface Interface +@brief Equivalent functions of the mutex interface between v3 and v4. + +The following functions should be implemented for the mutex type: +- v3: See functions in
threads_interface.h +- v4: IotMutex functions in @ref platform_threads_functions + +The table below shows the equivalent functions in v3 and v4. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
aws_iot_thread_mutex_init@ref platform_threads_function_mutexcreateThe v4 function must support recursive mutexes.
aws_iot_thread_mutex_lock@ref platform_threads_function_mutexlock
aws_iot_thread_mutex_trylock@ref platform_threads_function_mutextrylock
aws_iot_thread_mutex_unlock@ref platform_threads_function_mutexunlock
aws_iot_thread_mutex_destroy@ref platform_threads_function_mutexdestroy
+ +@subsection migration_platform_mutex_example Example +@brief Example of migration between v3 and v4. + +The table below provides examples of the equivalent mutex types and interfaces implemented on v3 and v4. + + + + + + + + + + + + + + + + + +
Version 3Version 4
Declaration + @code{c} + /* In threads_platform.h */ + struct _IoT_Mutex_t { + pthread_mutex_t lock; + }; + @endcode + + @code{c} + /* In iot_config.h */ + typedef pthread_mutex_t _IotSystemMutex_t; + @endcode +
Sample implementation (POSIX)threads_pthread_wrapper.ciot_threads_posix.c
+ +@section migration_platform_timer Timer +@brief Differences between v3 and v4 timers. + +The timers in v4 are more efficient than v3, but their interface is completely different. In v4, the timer interface is designed to be similar to the timer APIs provided by most operating systems. + +Because of the differences between the timers in v3 and v4, it is not possible to migrate a v3 timer implementation to v4. Therefore, a new timer implementation will need to be written for v4. See @ref platform_clock_functions for the list of functions that need to be implemented. + +@section migration_platform_semaphore Semaphores +@brief Details about counting semaphores needed in v4. + +Counting semaphores are a new addition to the platform layer in v4. They were not present in v3. See the IotSemaphore functions in @ref platform_threads_functions for the list of functions that need to be implemented. + +@section migration_platform_network Networking +@brief How to migrate the network implementation from v3 to v4. + +The networking functions are similar between v3 and v4. However, v4 requires some additional function to be implemented. + +The networking component requires a type to provide credentials for secured connections. The table below shows the equivalent credentials type for v3 and v4. + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
NameTLSDataParams_IotNetworkCredentials_tv4 provides @ref IotNetworkCredentials as a struct that satisfies most use cases.
Declare in filenetwork_platform.hiot_config.h + In v3, the credentials type must be declared in a file called network_platform.h. + Link to sample +
+ In v4, we recommend declaring the credentials type in iot_config.h. +
+ +Version 4 requires two additional types to be declared: +- `_IotNetworkServerInfo_t` which provides data on the remote server. @ref IotNetworkServerInfo should satisfy most use cases. +- `_IotNetworkConnection_t` to represent the handle of a network connection. This should be an opaque handle. + +@subsection migration_platform_networking_interface Interface +@brief Equivalent functions of the networking interface between v3 and v4. + +The following functions should be implemented for networking: +- v3: See functions in network_interface.h +- v4: Functions in @ref platform_network_functions + +The table below shows the equivalent functions in v3 and v4. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
iot_tls_initPlatform-specific init functions + The v4 networking interface does not define a function that must be implemented to initialize the network, as some network stacks do not require initialization. It may be implemented if needed.

+ The parameters of iot_tls_init have been moved to @ref platform_network_function_create. +
iot_tls_connect@ref platform_network_function_create
iot_tls_write@ref platform_network_function_send
iot_tls_read@ref platform_network_function_receive
iot_tls_disconnect@ref platform_network_function_close
iot_tls_destroy@ref platform_network_function_destroyIn v3, this function destroys the entire network stack.
In v4, this function only destroys the connection passed to it.
+ +Version 4 has the following new functions that must be implemented: +- @ref platform_network_function_setreceivecallback +- @ref platform_network_function_setclosecallback + +Version 4 requires the networking functions to be given as an @ref IotNetworkInterface_t. We recommend implementing a function that returns the @ref IotNetworkInterface_t containing the networking functions. + +@subsection migration_platform_networking_example Example +@brief Example of migration between v3 and v4. + +The table below provides examples of the equivalent networking types and interfaces implemented on v3 and v4. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4
Credentials type declaration + @code{c} + /* In threads_platform.h */ + typedef struct _TLSDataParams { + /* Members omitted due to length. */ + } TLSDataParams; + @endcode + + @code{c} + /* In iot_config.h */ + + /* Forward declare standard type from v4. */ + struct IotNetworkCredentials; + + typedef struct IotNetworkCredentials * _IotNetworkCredentials_t; + @endcode +
Server info declarationNone + @code{c} + /* In iot_config.h */ + + /* Forward declare standard type from v4. */ + struct IotNetworkServerInfo; + + typedef struct IotNetworkServerInfo * _IotNetworkServerInfo_t; + @endcode +
Opaque network type declarationNone + @code{c} + /* In iot_config.h */ + + /* Forward declare opaque struct. */ + struct _networkConnection; + + typedef struct _networkConnection * _IotNetworkConnection_t; + @endcode +
Sample implementation (mbed TLS)network_mbedtls_wrapper.ciot_network_mbedtls.c
+*/ From e9f81f7ca2a40d3a3d5c7ac9b8e89fe4d1cf7feb Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Fri, 20 Dec 2019 14:15:52 -0800 Subject: [PATCH 354/844] Reduce complexity for _deserializeIncomingPacket (#696) --- .../standard/mqtt/src/iot_mqtt_network.c | 348 +++++++++++------- 1 file changed, 209 insertions(+), 139 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 6819e143e2..31aeac7891 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -65,6 +65,47 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ); +/** + * @brief Deserialize a CONNACK, PUBACK, SUBACK, or UNSUBACK packet. + * + * @param[in] pMqttConnection The associated MQTT connection. + * @param[in] pIncomingPacket The packet received from the network. + * @param[in] _deserializer The deserialization function for the packet. + * @param[in] opType The type of operation corresponding to the packet. + * @param[in] pPacketIdentifier Address of incoming packet's packet identifier; + * `NULL` for a CONNACK. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE, or #IOT_MQTT_SERVER_REFUSED. + */ +static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket, + IotMqttDeserialize_t _deserializer, + IotMqttOperationType_t opType, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a PUBLISH packet. + * + * @param[in] pMqttConnection The associated MQTT connection. + * @param[in] pIncomingPacket The packet received from the network. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY, #IOT_MQTT_NETWORK_ERROR, + * or #IOT_MQTT_SCHEDULING_ERROR. + */ +static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * @param[in] pMqttConnection The associated MQTT connection. + * @param[in] pIncomingPacket The packet received from the network. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _deserializePingResp( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + /** * @brief Deserialize a packet received from the network. * @@ -274,11 +315,155 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, /*-----------------------------------------------------------*/ +static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket, + IotMqttDeserialize_t _deserializer, + IotMqttOperationType_t opType, + uint16_t * pPacketIdentifier ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pOperation = NULL; + + status = _deserializer( pIncomingPacket ); + + pOperation = _IotMqtt_FindOperation( pMqttConnection, + opType, + pPacketIdentifier ); + + if( pOperation != NULL ) + { + pOperation->u.operation.status = status; + _IotMqtt_Notify( pOperation ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pOperation = NULL; + + /* Allocate memory to handle the incoming PUBLISH. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) + { + IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); + status = IOT_MQTT_NO_MEMORY; + + goto cleanup; + } + else + { + /* Set the members of the incoming PUBLISH operation. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + pOperation->incomingPublish = true; + pOperation->pMqttConnection = pMqttConnection; + pIncomingPacket->u.pIncomingPublish = pOperation; + } + + /* Deserialize incoming PUBLISH. */ + status = _getPublishDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Send a PUBACK for QoS 1 PUBLISH. */ + if( pOperation->u.publish.publishInfo.qos == IOT_MQTT_QOS_1 ) + { + _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); + } + + /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ + pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; + pIncomingPacket->pRemainingData = NULL; + + /* Add the PUBLISH to the list of operations pending processing. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + /* Increment the MQTT connection reference count before scheduling an + * incoming PUBLISH. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) + { + /* Schedule PUBLISH for callback invocation. */ + status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessIncomingPublish, 0 ); + } + else + { + status = IOT_MQTT_NETWORK_ERROR; + } + } + + /* Free PUBLISH operation on error. */ + if( status != IOT_MQTT_SUCCESS ) + { + /* Check ownership of the received MQTT packet. */ + if( pOperation->u.publish.pReceivedData != NULL ) + { + /* Retrieve the pointer MQTT packet pointer so it may be freed later. */ + IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); + pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->u.publish.pReceivedData; + } + + /* Remove operation from pending processing list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + IotMqtt_Assert( pOperation != NULL ); + IotMqtt_FreeOperation( pOperation ); + } + +cleanup: + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializePingResp( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + + status = _getPingrespDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); + + if( status == IOT_MQTT_SUCCESS ) + { + if( Atomic_CompareAndSwap_u32( &( pMqttConnection->pingreq.u.operation.periodic.ping.failure ), + 0, + 1 ) == 1 ) + { + IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", + pMqttConnection ); + } + else + { + IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", + pMqttConnection ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pOperation = NULL; /* A buffer for remaining data must be allocated if remaining length is not 0. */ IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0 ) == @@ -294,100 +479,19 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); /* Deserialize CONNACK and notify of result. */ - status = _getConnackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - pOperation = _IotMqtt_FindOperation( pMqttConnection, - IOT_MQTT_CONNECT, - NULL ); - - if( pOperation != NULL ) - { - pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation ); - } + status = _deserializeAck( pMqttConnection, + pIncomingPacket, + _getConnackDeserializer( pMqttConnection->pSerializer ), + IOT_MQTT_CONNECT, + NULL ); break; case MQTT_PACKET_TYPE_PUBLISH: IotLogDebug( "(MQTT connection %p) PUBLISH in data stream.", pMqttConnection ); - /* Allocate memory to handle the incoming PUBLISH. */ - pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - if( pOperation == NULL ) - { - IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); - status = IOT_MQTT_NO_MEMORY; - - break; - } - else - { - /* Set the members of the incoming PUBLISH operation. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - pOperation->incomingPublish = true; - pOperation->pMqttConnection = pMqttConnection; - pIncomingPacket->u.pIncomingPublish = pOperation; - } - - /* Deserialize incoming PUBLISH. */ - status = _getPublishDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Send a PUBACK for QoS 1 PUBLISH. */ - if( pOperation->u.publish.publishInfo.qos == IOT_MQTT_QOS_1 ) - { - _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); - } - - /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ - pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; - pIncomingPacket->pRemainingData = NULL; - - /* Add the PUBLISH to the list of operations pending processing. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Increment the MQTT connection reference count before scheduling an - * incoming PUBLISH. */ - if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) - { - /* Schedule PUBLISH for callback invocation. */ - status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessIncomingPublish, 0 ); - } - else - { - status = IOT_MQTT_NETWORK_ERROR; - } - } - - /* Free PUBLISH operation on error. */ - if( status != IOT_MQTT_SUCCESS ) - { - /* Check ownership of the received MQTT packet. */ - if( pOperation->u.publish.pReceivedData != NULL ) - { - /* Retrieve the pointer MQTT packet pointer so it may be freed later. */ - IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); - pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->u.publish.pReceivedData; - } - - /* Remove operation from pending processing list. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - IotMqtt_Assert( pOperation != NULL ); - IotMqtt_FreeOperation( pOperation ); - } + /* Deserialize PUBLISH. */ + status = _deserializePublish( pMqttConnection, pIncomingPacket ); break; @@ -395,17 +499,11 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); /* Deserialize PUBACK and notify of result. */ - status = _getPubackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - pOperation = _IotMqtt_FindOperation( pMqttConnection, - IOT_MQTT_PUBLISH_TO_SERVER, - &( pIncomingPacket->packetIdentifier ) ); - - if( pOperation != NULL ) - { - pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation ); - } + status = _deserializeAck( pMqttConnection, + pIncomingPacket, + _getPubackDeserializer( pMqttConnection->pSerializer ), + IOT_MQTT_PUBLISH_TO_SERVER, + &( pIncomingPacket->packetIdentifier ) ); break; @@ -415,17 +513,11 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne /* Deserialize SUBACK and notify of result. */ pIncomingPacket->u.pMqttConnection = pMqttConnection; - status = _getSubackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - pOperation = _IotMqtt_FindOperation( pMqttConnection, - IOT_MQTT_SUBSCRIBE, - &( pIncomingPacket->packetIdentifier ) ); - - if( pOperation != NULL ) - { - pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation ); - } + status = _deserializeAck( pMqttConnection, + pIncomingPacket, + _getSubackDeserializer( pMqttConnection->pSerializer ), + IOT_MQTT_SUBSCRIBE, + &( pIncomingPacket->packetIdentifier ) ); break; @@ -433,17 +525,11 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); /* Deserialize UNSUBACK and notify of result. */ - status = _getUnsubackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - pOperation = _IotMqtt_FindOperation( pMqttConnection, - IOT_MQTT_UNSUBSCRIBE, - &( pIncomingPacket->packetIdentifier ) ); - - if( pOperation != NULL ) - { - pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation ); - } + status = _deserializeAck( pMqttConnection, + pIncomingPacket, + _getUnsubackDeserializer( pMqttConnection->pSerializer ), + IOT_MQTT_UNSUBSCRIBE, + &( pIncomingPacket->packetIdentifier ) ); break; @@ -454,23 +540,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); /* Deserialize PINGRESP. */ - status = _getPingrespDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - if( status == IOT_MQTT_SUCCESS ) - { - if( Atomic_CompareAndSwap_u32( &( pMqttConnection->pingreq.u.operation.periodic.ping.failure ), - 0, - 1 ) == 1 ) - { - IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", - pMqttConnection ); - } - else - { - IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", - pMqttConnection ); - } - } + status = _deserializePingResp( pMqttConnection, pIncomingPacket ); break; } From 8e718fe1bb8a9c3a9f7e24e320400032a7fde4d4 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Fri, 20 Dec 2019 14:32:53 -0800 Subject: [PATCH 355/844] Refactor _serializeConnect for complexity (#694) --- .../standard/mqtt/src/iot_mqtt_serialize.c | 147 ++++++++++-------- 1 file changed, 85 insertions(+), 62 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 57fe108e99..543246168f 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -216,6 +216,18 @@ static uint8_t * _encodeString( uint8_t * pDestination, const char * source, uint16_t sourceLength ); +/** + * @brief Encode a username into a CONNECT packet, if necessary. + * + * @param[out] pBuffer Buffer for the CONNECT packet. + * @param[in] pConnectInfo User-provided CONNECT information. + * + * @return Pointer to the end of the encoded string, which will be identical to + * `pBuffer` if nothing was encoded. + */ +static uint8_t * _encodeUserName( uint8_t * pBuffer, + const IotMqttConnectInfo_t * pConnectInfo ); + /** * @brief Calculate the size and "Remaining length" of a CONNECT packet generated * from the given parameters. @@ -465,6 +477,77 @@ static uint8_t * _encodeString( uint8_t * pDestination, /*-----------------------------------------------------------*/ +static uint8_t * _encodeUserName( uint8_t * pBuffer, + const IotMqttConnectInfo_t * pConnectInfo ) +{ + bool encodedUserName = false; + + /* If metrics are enabled, write the metrics username into the CONNECT packet. + * Otherwise, write the username and password only when not connecting to the + * AWS IoT MQTT server. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " + "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); + + /* Determine if the Connect packet should use a combination of the username + * for authentication plus the SDK version string. */ + if( pConnectInfo->pUserName != NULL ) + { + /* Only include metrics if it will fit within the encoding + * standard. */ + if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= UINT16_MAX ) + { + /* Write the high byte of the combined length. */ + *( pBuffer++ ) = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + + /* Write the low byte of the combined length. */ + *( pBuffer++ ) = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + + /* Write the identity portion of the username. */ + memcpy( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + pBuffer += pConnectInfo->userNameLength; + + /* Write the metrics portion of the username. */ + memcpy( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; + + encodedUserName = true; + } + } + else + { + /* The username is not being used for authentication, but + * metrics are enabled. */ + pBuffer = _encodeString( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); + + encodedUserName = true; + } + #endif /* #if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ + } + + /* Encode the username if there is one and it hasn't already been done. */ + if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) + { + pBuffer = _encodeString( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + } + + return pBuffer; +} + +/*-----------------------------------------------------------*/ + static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, size_t * pRemainingLength, size_t * pPacketSize ) @@ -651,7 +734,6 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, { uint8_t connectFlags = 0; uint8_t * pConnectPacket = pBuffer; - bool encodedUserName = false; /* Avoid unused variable warning when logging and asserts are disabled. */ ( void ) pConnectPacket; @@ -755,67 +837,8 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); } - /* If metrics are enabled, write the metrics username into the CONNECT packet. - * Otherwise, write the username and password only when not connecting to the - * AWS IoT MQTT server. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " - "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); - - /* Determine if the Connect packet should use a combination of the username - * for authentication plus the SDK version string. */ - if( pConnectInfo->pUserName != NULL ) - { - /* Only include metrics if it will fit within the encoding - * standard. */ - if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= UINT16_MAX ) - { - /* Write the high byte of the combined length. */ - *pBuffer = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - - /* Write the low byte of the combined length. */ - *( pBuffer + sizeof( uint8_t ) ) = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer += sizeof( uint16_t ); - - /* Write the identity portion of the username. */ - memcpy( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - pBuffer += pConnectInfo->userNameLength; - - /* Write the metrics portion of the username. */ - memcpy( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); - pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; - - encodedUserName = true; - } - } - else - { - /* The username is not being used for authentication, but - * metrics are enabled. */ - pBuffer = _encodeString( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); - - encodedUserName = true; - } - #endif /* #if AWS_IOT_MQTT_ENABLE_METRICS == 1 && IOT_STATIC_MEMORY_ONLY == 0 */ - } - - /* Encode the username if there is one and it hasn't already been done. */ - if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - } + /* Encode the username if there is one or metrics are enabled. */ + pBuffer = _encodeUserName( pBuffer, pConnectInfo ); /* Encode the password field, if requested by the app. */ if( pConnectInfo->pPassword != NULL ) From d3c32a8eff2d1fdc2ec0fc6c9090552ea7528140 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 23 Dec 2019 11:31:42 -0800 Subject: [PATCH 356/844] Add migration guide for MQTT (#695) --- doc/guide/migration.txt | 2 + doc/guide/migration/mqtt.txt | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 doc/guide/migration/mqtt.txt diff --git a/doc/guide/migration.txt b/doc/guide/migration.txt index ba58f8855b..b66ab47240 100644 --- a/doc/guide/migration.txt +++ b/doc/guide/migration.txt @@ -7,4 +7,6 @@ Version 4 of the C SDK is a new design, and therefore NOT backwards compatible w In these pages, v3 refers to both previous versions 2 and 3, which are largely compatible with each other. - @subpage migration_platform
Migrating @ref platform from v3 to v4. This must be done before any other component. +- @subpage migration_mqtt
+ Migrating an [MQTT](@ref mqtt) application from v3 to v4. */ diff --git a/doc/guide/migration/mqtt.txt b/doc/guide/migration/mqtt.txt new file mode 100644 index 0000000000..d93c586683 --- /dev/null +++ b/doc/guide/migration/mqtt.txt @@ -0,0 +1,113 @@ +/** +@page migration_mqtt MQTT +@brief How to migrate an MQTT application from v3 to v4. + +The MQTT library has been redesigned in v4 as a thread-aware library supporting asynchronous operations. New features such as persistent session support and retry of QoS 1 publish messages have also been added. + +The features of the MQTT libraries in v3 and v4 are defined by the common MQTT 3.1.1 spec; therefore, the two versions are similar. + +@section migration_mqtt_removed Removed features +@brief The following features which were present in v3 were removed in v4. +- Auto-reconnect
+ Version 3 has an auto-reconnect feature triggered through a call to `aws_iot_mqtt_yield`. In version 4, `aws_iot_mqtt_yield` has been removed and replaced with background tasks.
+ Workaround: When a version 4 API call returns @ref IOT_MQTT_NETWORK_ERROR or the version 4 MQTT disconnect callback is invoked, call @ref mqtt_function_connect through the application. The application should employ an exponential backoff strategy when re-establishing connections. + +@section migration_mqtt_datatypes Data Types +@brief The following table lists equivalent data types in v3 and v4. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
enum QoS@ref IotMqttQos_tMQTT Quality of service.
AWS_IoT_Client@ref IotMqttConnection_tMQTT connection handle.
IoT_Publish_Message_Params@ref IotMqttPublishInfo_tParameters of an MQTT publish.
IoT_MQTT_Will_Options
IoT_Client_Connect_Params
@ref IotMqttConnectInfo_tParameters of an MQTT connect.
The two v3 structs are combined in v4.
IoT_Client_Init_ParamsNoneThe members of this struct in v3 handled setup of the network connection.
This has been moved to @ref platform_network in v4.
+ +@section migration_mqtt_functions Functions +@brief The following table lists equivalent API functions in v3 and v4. These functions are the API functions declared in: +- In v3: aws_iot_mqtt_client_interface.h +- In v4: [MQTT functions](@ref mqtt_functions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
aws_iot_mqtt_init@ref mqtt_function_initIn v3, this function initializes a single client.
In v4, this function initializes the entire library.
aws_iot_mqtt_free@ref mqtt_function_cleanupIn v3, this function frees a single client.
In v4, this function cleans up the entire library.
aws_iot_mqtt_connect@ref mqtt_function_connect
aws_iot_mqtt_publish@ref mqtt_function_publishsync@ref mqtt_function_publishasync is the equivalent asynchronous function.
aws_iot_mqtt_subscribe@ref mqtt_function_subscribesync@ref mqtt_function_subscribeasync is the equivalent asynchronous function.
aws_iot_mqtt_resubscribeNoneFunction removed because auto-reconnect was removed.
aws_iot_mqtt_unsubscribe@ref mqtt_function_unsubscribesync@ref mqtt_function_unsubscribeasync is the equivalent asynchronous function.
aws_iot_mqtt_disconnect@ref mqtt_function_disconnect
aws_iot_mqtt_yieldNoneRemoved in favor of background tasks.
Nothing needs to be changed in an application.
aws_iot_mqtt_attempt_reconnectNoneFunction removed because auto-reconnect was removed.
+*/ From d2b2eeaa4cfbf1a6123d1916716dc1381c54b022 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 23 Dec 2019 15:22:48 -0800 Subject: [PATCH 357/844] Add migration guides for Shadow and Jobs (#698) --- doc/guide/migration.txt | 4 ++ doc/guide/migration/jobs.txt | 90 ++++++++++++++++++++++++++++++++++ doc/guide/migration/shadow.txt | 90 ++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 doc/guide/migration/jobs.txt create mode 100644 doc/guide/migration/shadow.txt diff --git a/doc/guide/migration.txt b/doc/guide/migration.txt index b66ab47240..6393883871 100644 --- a/doc/guide/migration.txt +++ b/doc/guide/migration.txt @@ -9,4 +9,8 @@ In these pages, v3 refers to both previous versions 2 and 3, which are la Migrating @ref platform from v3 to v4. This must be done before any other component. - @subpage migration_mqtt
Migrating an [MQTT](@ref mqtt) application from v3 to v4. +- @subpage migration_shadow
+ Migrating a [Shadow](@ref shadow) application from v3 to v4. +- @subpage migration_jobs
+ Migrating a [Jobs](@ref jobs) application from v3 to v4. */ diff --git a/doc/guide/migration/jobs.txt b/doc/guide/migration/jobs.txt new file mode 100644 index 0000000000..47f1d96307 --- /dev/null +++ b/doc/guide/migration/jobs.txt @@ -0,0 +1,90 @@ +/** +@page migration_jobs Jobs +@brief How to migrate a Jobs application from v3 to v4. + +The Jobs library has been redesigned in v4 as a thread-aware library supporting asynchronous operations. + +The features of the Jobs libraries in v3 and v4 are defined by the common AWS service; therefore, the two versions are similar. + +@section migration_jobs_functions Functions +@brief The following table lists equivalent API functions in v3 and v4. These functions are the API functions declared in: +- In v3: aws_iot_jobs_interface.h +- In v4: [Jobs functions](@ref jobs_functions) + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
+ aws_iot_jobs_subscribe_to_job_messages
+ aws_iot_jobs_subscribe_to_all_job_messages
+ aws_iot_jobs_unsubscribe_from_job_messages
+ aws_iot_jobs_send_query +
Specific Jobs operation function + These v3 functions for sending generic Jobs operations have been removed and replaced with individual functions for each supported Jobs operation.

+ In v3, the `topicType` parameter determined the Jobs operation. The equivalent functions in v4 are: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
v3 topicTypev4 Function
JOB_UNRECOGNIZED_TOPICNone (not valid)
JOB_GET_PENDING_TOPIC@ref jobs_function_getpendingsync
JOB_START_NEXT_TOPIC@ref jobs_function_startnextsync
JOB_DESCRIBE_TOPIC@ref jobs_function_describesync
JOB_UPDATE_TOPIC@ref jobs_function_updatesync
JOB_NOTIFY_TOPIC@ref jobs_function_setnotifypendingcallback
JOB_NOTIFY_NEXT_TOPIC@ref jobs_function_setnotifynextcallback
JOB_WILDCARD_TOPICCombination of functions, based on what the wildcard represented
+ + For example, a call to `aws_iot_jobs_send_query(..., topicType=JOB_GET_PENDING_TOPIC, ...)` should be replaced with a call to the v4 API @ref jobs_function_getpendingsync. +
aws_iot_jobs_start_next@ref jobs_function_startnextsync@ref jobs_function_startnextasync is the equivalent asynchronous function.
aws_iot_jobs_describe@ref jobs_function_describesync@ref jobs_function_describeasync is the equivalent asynchronous function.
aws_iot_jobs_update@ref jobs_function_updatesync@ref jobs_function_updateasync is the equivalent asynchronous function.
+ +*/ diff --git a/doc/guide/migration/shadow.txt b/doc/guide/migration/shadow.txt new file mode 100644 index 0000000000..d96d3c355a --- /dev/null +++ b/doc/guide/migration/shadow.txt @@ -0,0 +1,90 @@ +/** +@page migration_shadow Shadow +@brief How to migrate a Shadow application from v3 to v4. + +The Shadow library has been redesigned in v4 as a thread-aware library supporting asynchronous operations. + +The features of the Shadow libraries in v3 and v4 are defined by the common AWS service; therefore, the two versions are similar. + +@section migration_shadow_removed Removed features +@brief The following features which were present in v3 were removed in v4. +- Shadow JSON functions
+ Version 3 has functions for generating and parsing JSON Shadow documents. These functions have been removed in v4 due to their high memory usage.
+ Workaround: `snprintf` can be used to generate Shadow documents. JSON parsing can be done through third-party libraries. + +@section migration_shadow_functions Functions +@brief The following table lists equivalent API functions in v3 and v4. These functions are the API functions declared in: +- In v3: aws_iot_shadow_interface.h +- In v4: [Shadow functions](@ref shadow_functions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 4Notes
aws_iot_shadow_init@ref shadow_function_initIn v3, this function initializes a single client.
In v4, this function initializes the entire library.
aws_iot_shadow_free@ref shadow_function_cleanupIn v3, this function frees a single client.
In v4, this function cleans up the entire library.
aws_iot_mqtt_connect
aws_iot_shadow_disconnect
None + These functions were thin wrappers for the MQTT connect and disconnect functions.
+ Applications using these v3 functions should replace them with calls to:
+ @ref mqtt_function_connect
+ @ref mqtt_function_disconnect +
aws_iot_shadow_yieldNoneRemoved in favor of background tasks.
Nothing needs to be changed in an application.
aws_iot_shadow_update@ref shadow_function_updatesync@ref shadow_function_updateasync is the equivalent asynchronous function.
aws_iot_shadow_get@ref shadow_function_getsync@ref shadow_function_getasync is the equivalent asynchronous function.
aws_iot_shadow_delete@ref shadow_function_deletesync@ref shadow_function_deleteasync is the equivalent asynchronous function.
aws_iot_shadow_register_delta@ref shadow_function_setdeltacallback
+ aws_iot_shadow_reset_last_received_version
+ aws_iot_shadow_get_last_received_version
+ aws_iot_shadow_enable_discard_old_delta_msgs
+ aws_iot_shadow_disable_discard_old_delta_msgs
+
None + Removed due to removal of JSON parsing.
+ The application should parse received Shadow documents and decide whether to discard them based on the version key. +
aws_iot_shadow_set_autoreconnect_statusNoneRemoved due to removal of auto-reconnect in the MQTT library.
+*/ From 7fb1a16341e6e006ac6b68f4d60802878c4489bd Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 30 Dec 2019 16:05:13 -0500 Subject: [PATCH 358/844] Coverity annotations for False positive warnings (#697) * Coverity annotations for False poisitives * Coverity annotations * Add coverity to lexicon * spell fix in a comment * Fix comments * fixing review comments * reword comments * improve comments * Review comments fix --- libraries/standard/mqtt/lexicon.txt | 1 + libraries/standard/mqtt/src/iot_mqtt_api.c | 32 ++++++++++++++++- .../standard/mqtt/src/iot_mqtt_network.c | 34 ++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 4000505954..f026227e23 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -11,6 +11,7 @@ config connack connectserializeroverride const +coverity createnetworkconnection dataindex datalength diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index d5538db859..231661ec0d 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -1148,6 +1148,21 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pNewMqttConnection != NULL ) { + /* Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. + * + * This error is triggered by a dereference of 'pNewMqttConnection' in + * '_destroyMqttConnection'. Coverity assumes that 'pNewMqttConnection' + * was freed in '_IotMqtt_CreateOperation' above, where cleanup code will + * free 'pNewMqttConnection' upon allocation failure. + * + * This will never happen as a valid MQTT connection passed to this + * function always has a positive reference count; therefore, + * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT + * connections will be freed. + * + * The annotation below suppresses this Coverity error. + */ + /* coverity[deref_arg] */ _destroyMqttConnection( pNewMqttConnection ); } } @@ -1258,7 +1273,22 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, if( initCalled == true ) { - /* Close the underlying network connection. This also cleans up keep-alive. */ + /* Close the underlying network connection. This also cleans up keep-alive. + * Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. + * + * This error is triggered by a dereference of 'mqttConnection' in + * '_IotMqtt_CloseNetworkConnection'. Coverity assumes that 'mqttConnection' + * was freed in '_IotMqtt_CreateOperation' above, where cleanup code will + * free 'pNewMqttConnection' upon allocation failure. + * + * This will never happen as a valid MQTT connection passed to this + * function always has a positive reference count; therefore, + * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT + * connections will be freed. + * + * The annotation below suppresses this Coverity error. + */ + /* coverity[deref_arg] */ _IotMqtt_CloseNetworkConnection( IOT_MQTT_DISCONNECT_CALLED, mqttConnection ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 31aeac7891..f25b23d52b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -381,7 +381,23 @@ static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; pIncomingPacket->pRemainingData = NULL; - /* Add the PUBLISH to the list of operations pending processing. */ + /* Add the PUBLISH to the list of operations pending processing. + * Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. + * + * This error is triggered by a dereference of 'pMqttConnection' in + * 'IotMutex_Lock'. Coverity assumes that 'pMqttConnection' was freed in + * '_IotMqtt_CreateOperation', which was invoked in '_sendPuback'. + * + * This will never happen as a valid MQTT connection passed to this + * function always has a positive reference count; therefore, + * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT + * connections will be freed. Coverity also evaluates an incorrect + * path by assuming a waitable (1) value for flags, while the flags + * passed to this function were explicitly 0. + * + * The annotation below suppresses this Coverity error. + */ + /* coverity[deref_after_free] */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), &( pOperation->link ) ); @@ -547,6 +563,22 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( status != IOT_MQTT_SUCCESS ) { + /* Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. + * + * This error is triggered by passing a freed argument 'pMqttConnection' + * to 'IotLogError'. Coverity assumes that 'pMqttConnection' was freed in + * '_IotMqtt_CreateOperation', which was invoked in '_deserializePublish'. + * + * This will never happen as a valid MQTT connection passed to this + * function always has a positive reference count; therefore, + * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT + * connections will be freed. Coverity also evaluates an incorrect + * path by assuming a waitable (1) value for flags, while the flags + * passed to this function were explicitly 0. + * + * The annotation below suppresses this Coverity error. + */ + /* coverity[pass_freed_arg] */ IotLogError( "(MQTT connection %p) Packet parser status %s.", pMqttConnection, IotMqtt_strerror( status ) ); From 26b4e3ff43dae9eb1dc579d216234f34f3e7c553 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 7 Jan 2020 11:37:43 -0800 Subject: [PATCH 359/844] fix: IotMqtt_Wait Error Handling and increased MQTT code coverage * fix: "Fixed error handling case in IotMqtt_Wait and increased MQTT code coverage" --- libraries/standard/mqtt/src/iot_mqtt_api.c | 1 + .../mqtt/test/access/iot_test_access_mqtt.h | 19 ++ .../test/access/iot_test_access_mqtt_api.c | 23 +++ .../mqtt/test/mock/iot_tests_mqtt_mock.c | 9 +- .../mqtt/test/unit/iot_tests_mqtt_api.c | 169 ++++++++++++++++++ 5 files changed, 219 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 231661ec0d..3fc02271a5 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -1682,6 +1682,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, if( _IotMqtt_ValidateOperation( operation ) == false ) { status = IOT_MQTT_BAD_PARAMETER; + goto cleanup; } /* Check the MQTT connection status. */ diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h index 30e66c38cb..a1375ade5c 100644 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h @@ -40,6 +40,25 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ); +/** + * @brief Test access function for #_createNetworkConnection. + * + * @see #_createNetworkConnection. + */ + +IotNetworkError_t IotTestMqtt_createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, + IotNetworkConnection_t * pNetworkConnection, + bool * pCreatedNewNetworkConnection ); + +/*------------------------- iot_mqtt_serialize.c ------------------------*/ + +/** + * @brief Test access function for #_mqttOperation_tryDestroy. + * + * @see #_mqttOperation_tryDestroy. + */ +void IotTestMqtt_mqttOperation_tryDestroy( void * pData ); + /*------------------------- iot_mqtt_serialize.c ------------------------*/ /* diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c index 298b3f4082..34ae412a22 100644 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c @@ -33,6 +33,13 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ); +void IotTestMqtt_mqttOperation_tryDestroy( void * pData ); + +IotNetworkError_t IotTestMqtt_createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, + IotNetworkConnection_t * pNetworkConnection, + bool * pCreatedNewNetworkConnection ); + + /*-----------------------------------------------------------*/ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, @@ -43,3 +50,19 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, } /*-----------------------------------------------------------*/ + +void IotTestMqtt_mqttOperation_tryDestroy( void * pData ) +{ + return _mqttOperation_tryDestroy( pData ); +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotTestMqtt_createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, + IotNetworkConnection_t * pNetworkConnection, + bool * pCreatedNewNetworkConnection ) +{ + return _createNetworkConnection( pNetworkInfo, pNetworkConnection, pCreatedNewNetworkConnection ); +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c index cca00991c7..f00fdd52af 100644 --- a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c +++ b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c @@ -230,10 +230,15 @@ static size_t _sendSuccess( IotNetworkConnection_t pNetworkConnection, _lastPacketType = MQTT_PACKET_TYPE_UNSUBSCRIBE; break; + case ( MQTT_PACKET_TYPE_DISCONNECT & 0xf0 ): + _lastPacketType = 0; + _lastPacketIdentifier = 0; + break; + default: - /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and - * UNSUBSCRIBE. Abort if any other packet is found. */ + /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, + * UNSUBSCRIBE and DISCONNECT. Abort if any other packet is found. */ IotTest_Assert( 0 ); } diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 980ca6ce80..9be846a79c 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -318,6 +318,43 @@ static size_t _sendDelay( IotNetworkConnection_t pSendContext, /*-----------------------------------------------------------*/ +/** + * @brief A create function that fails when server info is not supplied. + */ +static IotNetworkError_t _createMock( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ) +{ + ( void ) pCredentialInfo; + ( void ) pConnection; + + if( pServerInfo == NULL ) + { + return IOT_NETWORK_FAILURE; + } + else + { + return IOT_NETWORK_SUCCESS; + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief An empty function for task pool job. + */ +static void _testTaskRoutine( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, + void * pUserContext ) +{ + /* Do Nothing */ + ( void ) pTaskPool; + ( void ) pJob; + ( void ) pUserContext; +} + +/*-----------------------------------------------------------*/ + /** * @brief This send function checks that a duplicate outgoing message differs from * the original. @@ -595,6 +632,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, ConnectMallocFail ); RUN_TEST_CASE( MQTT_Unit_API, ConnectRestoreSessionMallocFail ); RUN_TEST_CASE( MQTT_Unit_API, DisconnectMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, DisconnectAlreadyDisconnected ); RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0Parameters ); RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0MallocFail ); RUN_TEST_CASE( MQTT_Unit_API, PublishQoS1 ); @@ -617,6 +655,8 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, DeserializeResponseChecks ); RUN_TEST_CASE( MQTT_Unit_API, DeserializePublishChecks ); RUN_TEST_CASE( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ); + RUN_TEST_CASE( MQTT_Unit_API, MqttOperationTryDestroy ); + RUN_TEST_CASE( MQTT_Unit_API, CreateNetworkConnectionCheck ); } /*-----------------------------------------------------------*/ @@ -833,6 +873,9 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) * timeout while the send job is still executing. */ TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( pOperation, 10 ) ); + /* Wait with an invalid operation */ + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, IotMqtt_Wait( NULL, 10 ) ); + /* Check reference count after a timed out wait. */ IotMutex_Lock( &( _pMqttConnection->referencesMutex ) ); TEST_ASSERT_EQUAL_INT32( 1, pOperation->u.operation.jobReference ); @@ -924,6 +967,13 @@ TEST( MQTT_Unit_API, ConnectParameters ) TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); willInfo.payloadLength = 0; + /* Check connect returns error if network info is invalid. */ + status = IotMqtt_Connect( NULL, + &connectInfo, + TIMEOUT_MS, + &_pMqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); + /* Check that passing a wait time of 0 returns immediately. */ status = IotMqtt_Connect( &_networkInfo, &connectInfo, @@ -1073,6 +1123,40 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of @ref mqtt_function_disconnect when + * disconnected mqtt connection is passed. + */ +TEST( MQTT_Unit_API, DisconnectAlreadyDisconnected ) +{ + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); + TEST_ASSERT_EQUAL_INT( 1, mqttConnection->references ); + + /* Increase reference count to 3 so the subsequent disconnect + * calls do not free the connection. */ + mqttConnection->references += 2; + /* Call Disconnect, reference count should decrement. */ + IotMqtt_Disconnect( mqttConnection, 0 ); + TEST_ASSERT_EQUAL_INT( 2, mqttConnection->references ); + /* 'disconnected' flag should be set */ + TEST_ASSERT_EQUAL( true, mqttConnection->disconnected ); + + /* Make sure reference count is decremented when 'disconnected' + * connection is passed without any attempts to close socket again. */ + IotMqtt_Disconnect( mqttConnection, 0 ); + TEST_ASSERT_EQUAL_INT( 1, mqttConnection->references ); + + /* One final disconnect to bring reference count to zero and free + * the connection */ + IotMqtt_Disconnect( mqttConnection, 0 ); + /* mqttConnection should be freed after above call. + * Test should not fail with any Unity memory leak asserts. */ +} +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 0) with various * valid and invalid parameters. @@ -2291,3 +2375,88 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests internal function _mqttOperation_tryDestroy works + * as intended. + * @note: Uses access function. + */ +TEST( MQTT_Unit_API, MqttOperationTryDestroy ) +{ + _mqttOperation_t * pMqttOperation = NULL; + /* _mqttOperation_t mqttOperation ; */ + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotTaskPoolJob_t pTaskPoolJob = IOT_TASKPOOL_JOB_INITIALIZER; + IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; + IotTaskPoolJobStorage_t _testJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; + + /* Create Task pool Job */ + taskPoolError = IotTaskPool_CreateJob( _testTaskRoutine, NULL, &_testJobStorage, &pTaskPoolJob ); + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, taskPoolError ); + /* Allocate operation */ + pMqttOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + TEST_ASSERT_NOT_NULL( pMqttOperation ) + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); + + void * pData = ( void * ) pMqttOperation; + memset( pData, 0, sizeof( _mqttOperation_t ) ); + + /* Non Publish operation */ + pMqttOperation->incomingPublish = false; + pMqttOperation->pMqttConnection = mqttConnection; + pMqttOperation->job = pTaskPoolJob; + pMqttOperation->u.operation.jobReference = 2; + IotTestMqtt_mqttOperation_tryDestroy( pData ); + /* Job reference must be decremented, Operation must be still allocated */ + TEST_ASSERT_EQUAL_INT( 1, pMqttOperation->u.operation.jobReference ); + + /* Publish Operation */ + /* reset mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); + pMqttOperation->incomingPublish = true; + pMqttOperation->u.publish.pReceivedData = IotMqtt_MallocMessage( 10 ); + + /* + * pOperation will be destroyed after this call and the test + * should not report any memory leaks because the destroy + * Operation should free all memory. + */ + IotTestMqtt_mqttOperation_tryDestroy( pData ); + + /* The call should not assert, operation should be destroyed */ + IotTest_MqttMockCleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests internal function _createNetworkConnection works + * as intended. + * @note: Uses access function. + */ +TEST( MQTT_Unit_API, CreateNetworkConnectionCheck ) +{ + IotMqttNetworkInfo_t * pNetworkInfo = NULL; + IotNetworkConnection_t * pNetworkConnection = { 0 }; + bool createdNetworkConnection = false; + + /* Test for parameter validation */ + TEST_ASSERT_EQUAL( IOT_NETWORK_BAD_PARAMETER, IotTestMqtt_createNetworkConnection( pNetworkInfo, + pNetworkConnection, + &createdNetworkConnection ) ); + + /* Setup correct network info */ + pNetworkInfo = &_networkInfo; + pNetworkInfo->createNetworkConnection = true; + pNetworkInfo->pNetworkInterface = &_networkInterface; + _networkInterface.create = _createMock; + /* Invalid server info */ + pNetworkInfo->u.setup.pNetworkServerInfo = NULL; + + TEST_ASSERT_NOT_EQUAL( IOT_MQTT_SUCCESS, IotTestMqtt_createNetworkConnection( pNetworkInfo, + pNetworkConnection, + &createdNetworkConnection ) ); +} + +/*-----------------------------------------------------------*/ From 0b7a3d23d0495466ce1c259530f38810c9129ac2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 7 Jan 2020 13:54:57 -0800 Subject: [PATCH 360/844] Add Coverity MISRA config (#708) --- scripts/coverity_misra.config | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 scripts/coverity_misra.config diff --git a/scripts/coverity_misra.config b/scripts/coverity_misra.config new file mode 100644 index 0000000000..9d9d5ac95d --- /dev/null +++ b/scripts/coverity_misra.config @@ -0,0 +1,34 @@ +// MISRA C-2012 Rules + +{ + version : "2.0", + standard : "c2012", + title: "Coverity MISRA Configuration", + deviations : [ + // Disable the following rules. + { + deviation: "Directive 4.5", + reason: "Allow names that MISRA considers ambiguous (such as enum IOT_MQTT_CONNECT and function IotMqtt_Connect)." + }, + { + deviation: "Directive 4.8", + reason: "Allow inclusion of unused types. Header files for a specific port, which are needed by all files, may define types that are not used by a specific file." + }, + { + deviation: "Rule 2.4", + reason: "Allow unused tags. Some compilers warn if types are not tagged." + }, + { + deviation: "Rule 2.5", + reason: "Allow unused macros. Library headers may define macros intended for the application's use, but not used by a specific file." + }, + { + deviation: "Rule 21.1", + reason: "Allow use of all names." + }, + { + deviation: "Rule 21.2", + reason: "Allow use of all names." + } + ] +} From 63e2936380e5eb9e727b2c4c919fd8a75dffd02b Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 9 Jan 2020 11:23:42 -0800 Subject: [PATCH 361/844] Reduce complexity in MQTT serialize and API (#701) * Refactor Wait and PublishAsync API functions * Refactor _IotMqtt_DeserializePublish * Move publish setup checks to iot_mqtt_validate.c --- libraries/standard/mqtt/src/iot_mqtt_api.c | 166 +++++++++--------- .../standard/mqtt/src/iot_mqtt_serialize.c | 135 ++++++++------ .../standard/mqtt/src/iot_mqtt_validate.c | 52 +++++- .../mqtt/src/private/iot_mqtt_internal.h | 12 +- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 5 +- .../test/unit/iot_tests_mqtt_subscription.c | 5 +- .../mqtt/test/unit/iot_tests_mqtt_validate.c | 43 +++-- 7 files changed, 255 insertions(+), 163 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 3fc02271a5..caae514d47 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -202,6 +202,23 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * const pOperationReference ); +/** + * @brief Set an operation reference if provided. + * + * @param[out] pOperationReference Operation reference provided by the application. + * @param[in] pNewOperation Operation to set. + */ +static void _setOperationReference( IotMqttOperation_t * const pOperationReference, + _mqttOperation_t * pNewOperation ); + +/** + * @brief Wait for an MQTT operation to complete. + * + * See @ref mqtt_function_wait for a description of the parameters and return values. + */ +static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, + uint32_t timeoutMs ); + /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -765,10 +782,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Set the reference, if provided. */ - if( pOperationReference != NULL ) - { - *pOperationReference = pSubscriptionOperation; - } + _setOperationReference( pOperationReference, pSubscriptionOperation ); /* Send the SUBSCRIBE packet. */ if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) @@ -795,10 +809,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Clear the previously set (and now invalid) reference. */ - if( pOperationReference != NULL ) - { - *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; - } + _setOperationReference( pOperationReference, IOT_MQTT_OPERATION_INITIALIZER ); goto cleanup; } @@ -828,6 +839,60 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /*-----------------------------------------------------------*/ +static void _setOperationReference( IotMqttOperation_t * const pOperationReference, + _mqttOperation_t * pNewOperation ) +{ + if( pOperationReference != NULL ) + { + *pOperationReference = pNewOperation; + } +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, + uint32_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( IotSemaphore_TimedWait( &( operation->u.operation.notify.waitSemaphore ), + timeoutMs ) == false ) + { + status = IOT_MQTT_TIMEOUT; + + /* Attempt to cancel the job of the timed out operation. */ + ( void ) _IotMqtt_DecrementOperationReferences( operation, true ); + + /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ + if( operation->u.operation.type == IOT_MQTT_SUBSCRIBE ) + { + IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" + " subscriptions of timed-out SUBSCRIBE.", + operation->pMqttConnection, + operation ); + + _IotMqtt_RemoveSubscriptionByPacket( operation->pMqttConnection, + operation->u.operation.packetIdentifier, + MQTT_REMOVE_ALL_SUBSCRIPTIONS ); + } + } + else + { + /* Retrieve the status of the completed operation. */ + status = operation->u.operation.status; + } + + IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", + operation->pMqttConnection, + IotMqtt_OperationType( operation->u.operation.type ), + operation, + IotMqtt_strerror( status ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ) { bool disconnected = false; @@ -1468,50 +1533,16 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, goto cleanup; } - /* Check that the PUBLISH information is valid. */ if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, - pPublishInfo ) == false ) + pPublishInfo, + flags, + pCallbackInfo, + pPublishOperation ) == false ) { status = IOT_MQTT_BAD_PARAMETER; goto cleanup; } - /* Check that no notification is requested for a QoS 0 publish. */ - if( pPublishInfo->qos == IOT_MQTT_QOS_0 ) - { - if( pCallbackInfo != NULL ) - { - IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; - } - else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) - { - IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; - } - - if( pPublishOperation != NULL ) - { - IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); - } - } - - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) - { - if( pPublishOperation == NULL ) - { - IotLogError( "Reference must be provided for a waitable PUBLISH." ); - - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; - } - } - /* Create a PUBLISH operation. */ status = _IotMqtt_CreateOperation( mqttConnection, flags, @@ -1563,10 +1594,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, /* Set the reference, if provided. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - if( pPublishOperation != NULL ) - { - *pPublishOperation = pOperation; - } + _setOperationReference( pPublishOperation, pOperation ); } /* Send the PUBLISH packet. */ @@ -1588,10 +1616,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, /* Clear the previously set (and now invalid) reference. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - if( pPublishOperation != NULL ) - { - *pPublishOperation = IOT_MQTT_OPERATION_INITIALIZER; - } + _setOperationReference( pPublishOperation, IOT_MQTT_OPERATION_INITIALIZER ); } goto cleanup; @@ -1715,38 +1740,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /* Only wait on an operation if the MQTT connection is active. */ if( status == IOT_MQTT_SUCCESS ) { - if( IotSemaphore_TimedWait( &( operation->u.operation.notify.waitSemaphore ), - timeoutMs ) == false ) - { - status = IOT_MQTT_TIMEOUT; - - /* Attempt to cancel the job of the timed out operation. */ - ( void ) _IotMqtt_DecrementOperationReferences( operation, true ); - - /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ - if( operation->u.operation.type == IOT_MQTT_SUBSCRIBE ) - { - IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" - " subscriptions of timed-out SUBSCRIBE.", - pMqttConnection, - operation ); - - _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, - operation->u.operation.packetIdentifier, - MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - } - } - else - { - /* Retrieve the status of the completed operation. */ - status = operation->u.operation.status; - } - - IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", - pMqttConnection, - IotMqtt_OperationType( operation->u.operation.type ), - operation, - IotMqtt_strerror( status ) ); + status = _waitForOperation( operation, timeoutMs ); } /* Wait is finished; decrement operation reference count. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 543246168f..2cd08d9595 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -106,6 +106,13 @@ */ #define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) +/** + * @brief The minimum remaining length for a QoS PUBLISH. + * + * Includes two bytes for topic name length and one byte for topic name. + */ +#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3 ) + /** * @brief The maximum possible size of a CONNECT packet. * @@ -218,10 +225,10 @@ static uint8_t * _encodeString( uint8_t * pDestination, /** * @brief Encode a username into a CONNECT packet, if necessary. - * + * * @param[out] pBuffer Buffer for the CONNECT packet. * @param[in] pConnectInfo User-provided CONNECT information. - * + * * @return Pointer to the end of the encoded string, which will be identical to * `pBuffer` if nothing was encoded. */ @@ -359,6 +366,21 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, const uint8_t * pStatusStart, _mqttPacket_t * pSuback ); +/** + * @brief Check the remaining length against some value for QoS 0 or QoS 1/2. + * + * The remaining length for a QoS 1/2 will always be two greater than for a QoS 0. + * + * @param[in] pPublish Pointer to an MQTT packet struct representing a PUBLISH. + * @param[in] qos The QoS of the PUBLISH. + * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, + IotMqttQos_t qos, + size_t qos0Minimum ); + /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -1105,6 +1127,47 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, /*-----------------------------------------------------------*/ +static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, + IotMqttQos_t qos, + size_t qos0Minimum ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Sanity checks for "Remaining length". */ + if( qos == IOT_MQTT_QOS_0 ) + { + /* Check that the "Remaining length" is greater than the minimum. */ + if( pPublish->remainingLength < qos0Minimum ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 0 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum ); + + status = IOT_MQTT_BAD_RESPONSE; + } + } + else + { + /* Check that the "Remaining length" is greater than the minimum. For + * QoS 1 or 2, this will be two bytes greater than for QoS due to the + * packet identifier. */ + if( pPublish->remainingLength < qos0Minimum + 2 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum + 2 ); + + status = IOT_MQTT_BAD_RESPONSE; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { @@ -1542,69 +1605,31 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) "DUP is 0." ); } - /* Sanity checks for "Remaining length". */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) - { - /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate - * topic name length (2 bytes) and topic name (at least 1 byte). */ - if( pPublish->remainingLength < 3 ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS 0 PUBLISH cannot have a remaining length less than 3." ); + /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining + * length of at least 3 to accommodate topic name length (2 bytes) and topic + * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of + * at least 5 for the packet identifier in addition to the topic name length and + * topic name. */ + status = _checkRemainingLength( pPublish, pOutput->qos, MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } - } - else + if( status != IOT_MQTT_SUCCESS ) { - /* A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 to - * accommodate a packet identifier as well as the topic name length and - * topic name. */ - if( pPublish->remainingLength < 5 ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); - - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + goto cleanup; } /* Extract the topic name starting from the first byte of the variable header. * The topic name string starts at byte 3 in the variable header. */ pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); - /* Sanity checks for topic name length and "Remaining length". */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) - { - /* Check that the "Remaining length" is at least as large as the variable - * header. */ - if( pPublish->remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length cannot be less than variable header length." ); + /* Sanity checks for topic name length and "Remaining length". The remaining + * length must be at least as large as the variable length header. */ + status = _checkRemainingLength( pPublish, + pOutput->qos, + pOutput->topicNameLength + sizeof( uint16_t ) ); - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } - } - else + if( status != IOT_MQTT_SUCCESS ) { - /* Check that the "Remaining length" is at least as large as the variable - * header. */ - if( pPublish->remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length cannot be less than variable header length." ); - - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + goto cleanup; } /* Parse the topic. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 9e2d52a737..bbba340c1f 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -562,8 +562,12 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) /*-----------------------------------------------------------*/ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pPublishInfo ) + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pPublishOperation ) { + bool status = true; size_t maximumPayloadLength = MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; if( awsIotMqttMode == true ) @@ -571,10 +575,48 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, maximumPayloadLength = AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; } - return _validatePublish( awsIotMqttMode, - maximumPayloadLength, - "Publish", - pPublishInfo ); + status = _validatePublish( awsIotMqttMode, + maximumPayloadLength, + "Publish", + pPublishInfo ); + + if( status == true ) + { + /* Check that no notification is requested for a QoS 0 publish. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_0 ) + { + if( pCallbackInfo != NULL ) + { + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + status = false; + } + else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) + { + IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); + + status = false; + } + + if( pPublishOperation != NULL ) + { + IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); + } + } + + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + { + if( pPublishOperation == NULL ) + { + IotLogError( "Reference must be provided for a waitable PUBLISH." ); + + status = false; + } + } + } + + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 0f06224beb..dbb3be2577 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -464,16 +464,22 @@ typedef struct _mqttPacket bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ); /** - * @brief Check that an #IotMqttPublishInfo_t is valid. + * @brief Check that parameters for an MQTT PUBLISH are valid. * * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to * an AWS IoT MQTT server. * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. + * @param[in] flags Behavior modification flags to validate. See @ref mqtt_constants_flags. + * @param[in] pCallbackInfo #IotMqttCallbackInfo_t to validate. + * @param[in] pPublishOperation Handle to a PUBLISH operation. * - * @return `true` if `pPublishInfo` is valid; `false` otherwise. + * @return `true` if all parameters are valid; `false` otherwise. */ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pPublishInfo ); + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pPublishOperation ); /** * @brief Check that an #IotMqttPublishInfo_t is valid for an LWT publish diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index 26a8f869ea..4eab1426ac 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -418,7 +418,10 @@ static void _publishCallback( void * pCallbackContext, /* Check that the parameters to this function are valid. */ if( ( _IotMqtt_ValidatePublish( AWS_IOT_MQTT_SERVER, - &( pPublish->u.message.info ) ) == true ) && + &( pPublish->u.message.info ), + 0, + NULL, + NULL ) == true ) && ( pPublish->u.message.info.topicNameLength == TEST_TOPIC_LENGTH ) && ( strncmp( TEST_TOPIC_NAME, pPublish->u.message.info.pTopicName, TEST_TOPIC_LENGTH ) == 0 ) ) { diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c index 8856be4dee..cd6f7124ea 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c @@ -236,7 +236,10 @@ static void _publishCallback( void * pArgument, /* Ensure that publish info is valid. */ TEST_ASSERT_EQUAL_INT( true, _IotMqtt_ValidatePublish( AWS_IOT_MQTT_SERVER, - &( pPublish->u.message.info ) ) ); + &( pPublish->u.message.info ), + 0, + NULL, + NULL ) ); } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c index 601cf450d4..b9dc642514 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c @@ -156,15 +156,17 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) { bool validateStatus = false; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; /* NULL parameter. */ - validateStatus = _IotMqtt_ValidatePublish( false, NULL ); + validateStatus = _IotMqtt_ValidatePublish( false, NULL, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Zero-length topic name. */ publishInfo.pTopicName = ""; publishInfo.topicNameLength = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.pTopicName = "/test"; @@ -173,59 +175,76 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) /* Zero-length/NULL payload. */ publishInfo.pPayload = NULL; publishInfo.payloadLength = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* NULL payload only allowed with length 0. */ publishInfo.pPayload = NULL; publishInfo.payloadLength = 1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.payloadLength = 0; /* Negative QoS or QoS > 2. */ publishInfo.qos = ( IotMqttQos_t ) -1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.qos = ( IotMqttQos_t ) 3; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.qos = IOT_MQTT_QOS_0; /* Positive retry limit with no period. */ publishInfo.retryLimit = 1; publishInfo.retryMs = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Positive retry limit with positive period. */ publishInfo.retryLimit = 1; publishInfo.retryMs = 1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Retry limit 0. */ publishInfo.retryLimit = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); + + /* Invalid flags. */ + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &operation ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Invalid callback. */ + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, &callbackInfo, NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Invalid operation. */ + publishInfo.qos = IOT_MQTT_QOS_1; + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, IOT_MQTT_FLAG_WAITABLE, &callbackInfo, NULL ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + + /* Valid flags, callback, and operation. */ + validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, IOT_MQTT_FLAG_WAITABLE, &callbackInfo, &operation ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* AWS IoT MQTT service limit tests. */ #if AWS_IOT_MQTT_SERVER == true /* QoS 2. */ publishInfo.qos = IOT_MQTT_QOS_2; - validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.qos = IOT_MQTT_QOS_0; /* Retained message. */ publishInfo.retain = true; - validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.retain = false; /* Topic name too long. */ publishInfo.topicNameLength = AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; - validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); + validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); #endif /* if AWS_IOT_MQTT_SERVER == true */ } From f9f7b75d5ece333f83a6b934a4819e3fc5f9cc94 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Fri, 10 Jan 2020 15:40:05 -0800 Subject: [PATCH 362/844] fix: Increase MQTT test code coverage. (#713) * fix: Increase MQTT test code coverage. Code coverage for file: iot_mqtt_network.c --- .../mqtt/test/unit/iot_tests_mqtt_api.c | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 9be846a79c..63f0eecdd6 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -559,18 +559,24 @@ static IotMqttError_t _getNextByte( void * pNetworkInterface, uint8_t * nextByte ) { uint8_t * buffer; + IotMqttError_t status = IOT_MQTT_SUCCESS; /* Treat network interface as pointer to buffer for mocking */ /* Send next byte */ - IotTest_Assert( pNetworkInterface != NULL ); - IotTest_Assert( nextByte != NULL ); - - buffer = ( *( uint8_t ** ) pNetworkInterface ); - /* read single byte */ - *nextByte = *buffer; - /* Move stream by 1 byte */ - ( *( uint8_t ** ) pNetworkInterface ) = ++buffer; - return IOT_MQTT_SUCCESS; + if( ( pNetworkInterface != NULL ) && ( nextByte != NULL ) ) + { + buffer = ( *( uint8_t ** ) pNetworkInterface ); + /* read single byte */ + *nextByte = *buffer; + /* Move stream by 1 byte */ + ( *( uint8_t ** ) pNetworkInterface ) = ++buffer; + } + else + { + status = IOT_MQTT_NETWORK_ERROR; + } + + return status; } /*-----------------------------------------------------------*/ @@ -1152,6 +1158,7 @@ TEST( MQTT_Unit_API, DisconnectAlreadyDisconnected ) /* One final disconnect to bring reference count to zero and free * the connection */ IotMqtt_Disconnect( mqttConnection, 0 ); + /* mqttConnection should be freed after above call. * Test should not fail with any Unity memory leak asserts. */ } @@ -2260,7 +2267,7 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) uint8_t buffer[ 10 ]; uint8_t * bufPtr = buffer; - /* Dummy network interface - pointer to pointer to buffer. */ + /* Dummy network interface - pointer to pointer to a buffer. */ void * pNetworkInterface = ( void * ) &bufPtr; buffer[ 0 ] = 0x20; /* CONN ACK */ @@ -2270,6 +2277,21 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); TEST_ASSERT_EQUAL_INT( 0x20, mqttPacket.type ); TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); + + /* Test with NULL network interface */ + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); + + /* Test with incorrect packet type. */ + buffer[ 0 ] = 0x10; /* INVALID */ + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); + + /* Test with invalid remaining length. */ + buffer[ 0 ] = 0x20; /* CONN ACK */ + buffer[ 1 ] = 0xFF; /* Remaining length. */ + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); } /*-----------------------------------------------------------*/ From 90e4c7d98f3dee1b7cbc97096eaf49530b784556 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Mon, 13 Jan 2020 12:35:42 -0800 Subject: [PATCH 363/844] chore: Disable MISRA directive 4.9. (#714) Allow use of function like macros. CSDK repository uses lotLog() macros. We will postpone fixing this directive till we determin how to handle all these macros. --- scripts/coverity_misra.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/coverity_misra.config b/scripts/coverity_misra.config index 9d9d5ac95d..8a94b955c5 100644 --- a/scripts/coverity_misra.config +++ b/scripts/coverity_misra.config @@ -14,6 +14,10 @@ deviation: "Directive 4.8", reason: "Allow inclusion of unused types. Header files for a specific port, which are needed by all files, may define types that are not used by a specific file." }, + { + deviation: "Directive 4.9", + reason: "Allow inclusion of function like macros. Logging is done using function like macros." + }, { deviation: "Rule 2.4", reason: "Allow unused tags. Some compilers warn if types are not tagged." From 748be88c38e1ff43199dbe3470507c75898d7166 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Mon, 13 Jan 2020 12:57:31 -0800 Subject: [PATCH 364/844] MISRA Rule 10.1 on all MQTT library files. (#712) --- .../mqtt/include/types/iot_mqtt_types.h | 4 ++-- .../standard/mqtt/src/iot_mqtt_network.c | 6 ++--- .../standard/mqtt/src/iot_mqtt_serialize.c | 24 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 5719495ddd..cf907adaa7 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -1154,7 +1154,7 @@ typedef struct IotMqttNetworkInfo * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up * resources. */ -#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001 ) +#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001UL ) /** * @brief Causes @ref mqtt_function_disconnect to only free memory and not send @@ -1164,6 +1164,6 @@ typedef struct IotMqttNetworkInfo * to @ref mqtt_function_disconnect if the network goes offline or is otherwise * unusable. */ -#define IOT_MQTT_FLAG_CLEANUP_ONLY ( 0x00000001 ) +#define IOT_MQTT_FLAG_CLEANUP_ONLY ( 0x00000001UL ) #endif /* ifndef IOT_MQTT_TYPES_H_ */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index f25b23d52b..cc8e95b6de 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -211,7 +211,7 @@ static bool _incomingPacketValid( uint8_t packetType ) bool status = true; /* Check packet type. Mask out lower bits to ignore flags. */ - switch( packetType & 0xf0 ) + switch( packetType & 0xf0U ) { /* Valid incoming packet types. */ case MQTT_PACKET_TYPE_CONNACK: @@ -489,7 +489,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMqtt_Assert( _incomingPacketValid( pIncomingPacket->type ) == true ); /* Mask out the low bits of packet type to ignore flags. */ - switch( ( pIncomingPacket->type & 0xf0 ) ) + switch( ( pIncomingPacket->type & 0xf0U ) ) { case MQTT_PACKET_TYPE_CONNACK: IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); @@ -551,7 +551,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne default: /* The only remaining valid type is PINGRESP. */ - IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == MQTT_PACKET_TYPE_PINGRESP ); + IotMqtt_Assert( ( pIncomingPacket->type & 0xf0U ) == MQTT_PACKET_TYPE_PINGRESP ); IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 2cd08d9595..1cefc1d86d 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -47,7 +47,7 @@ * Macros for reading the high and low byte of a 2-byte unsigned int. */ #define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ffU ) ) /**< @brief Get low byte. */ /** * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. @@ -64,7 +64,7 @@ * @param[in] x The unsigned int to set. * @param[in] position Which bit to set. */ -#define UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01 << position ) ) ) +#define UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01U << position ) ) ) /** * @brief Macro for checking if a bit is set in a 1-byte unsigned int. @@ -72,7 +72,7 @@ * @param[in] x The unsigned int to check. * @param[in] position Which bit to check. */ -#define UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01 << position ) ) == ( 0x01 << position ) ) +#define UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01U << position ) ) == ( 0x01U << position ) ) /* * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT @@ -125,7 +125,7 @@ * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. */ #define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ -#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01 ) /**< @brief The "Session Present" bit is always the lowest bit. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ /* * Constants relating to PUBLISH and PUBACK packets, defined by MQTT @@ -164,7 +164,7 @@ /** * @brief The length of #AWS_IOT_METRICS_USERNAME. */ - #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1 ) + #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1U ) #endif /* ifndef AWS_IOT_METRICS_USERNAME */ #endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ @@ -1203,7 +1203,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, pNetworkInterface, &encodedByte ) == true ) { - remainingLength += ( encodedByte & 0x7F ) * multiplier; + remainingLength += ( encodedByte & 0x7FU ) * multiplier; multiplier *= 128; bytesDecoded++; } @@ -1213,7 +1213,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, break; } } - } while( ( encodedByte & 0x80 ) != 0 ); + } while( ( encodedByte & 0x80U ) != 0 ); /* Check that the decoded remaining length conforms to the MQTT specification. */ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) @@ -1254,7 +1254,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, { if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) { - remainingLength += ( encodedByte & 0x7F ) * multiplier; + remainingLength += ( encodedByte & 0x7FU ) * multiplier; multiplier *= 128; bytesDecoded++; } @@ -1264,7 +1264,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, break; } } - } while( ( encodedByte & 0x80 ) != 0 ); + } while( ( encodedByte & 0x80U ) != 0 ); /* Check that the decoded remaining length conforms to the MQTT specification. */ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) @@ -1382,7 +1382,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) /* Check the reserved bits in CONNACK. The high 7 bits of the second byte * in CONNACK must be 0. */ - if( ( pRemainingData[ 0 ] | 0x01 ) != 0x01 ) + if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) { IotLog( IOT_LOG_ERROR, &_logHideAll, @@ -2510,7 +2510,7 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) goto cleanup; } - if( ( pMqttPacket->type & 0xf0 ) != MQTT_PACKET_TYPE_PUBLISH ) + if( ( pMqttPacket->type & 0xf0U ) != MQTT_PACKET_TYPE_PUBLISH ) { IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); status = IOT_MQTT_BAD_PARAMETER; @@ -2563,7 +2563,7 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) mqttPacket.type = pMqttPacket->type; /* Call internal deserialize */ - switch( pMqttPacket->type & 0xf0 ) + switch( pMqttPacket->type & 0xf0U ) { case MQTT_PACKET_TYPE_CONNACK: status = _IotMqtt_DeserializeConnack( &mqttPacket ); From 330c20357255bbb740dc041bbc9bf676fed2b006 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Mon, 13 Jan 2020 15:23:58 -0800 Subject: [PATCH 365/844] MISRA Directive 4.6 on MQTT Files (#711) * Ignore MISRA directive 4.6 in iot_atomic_gcc.h * MISRA Directive 4.6 on MQTT files and logging. * Add comments to coverity annotations. * Elaborate the iot_atomic_gcc.h comments about annotations. * Add IotLog() to the lexicon.txt for mqtt. * Fix grammar on coverity comments. Co-authored-by: Gary Wicker <14828980+gkwicker@users.noreply.github.com> --- .../standard/common/include/iot_logging.h | 4 ++-- libraries/standard/common/src/iot_logging.c | 4 ++-- libraries/standard/mqtt/lexicon.txt | 1 + libraries/standard/mqtt/src/iot_mqtt_api.c | 11 +++++++++ .../standard/mqtt/src/iot_mqtt_network.c | 5 ++++ .../standard/mqtt/src/iot_mqtt_operation.c | 20 ++++++++++++++++ .../standard/mqtt/src/iot_mqtt_serialize.c | 10 ++++++++ ports/common/include/atomic/iot_atomic_gcc.h | 24 +++++++++++++++++++ ports/common/lexicon.txt | 1 + 9 files changed, 76 insertions(+), 4 deletions(-) diff --git a/libraries/standard/common/include/iot_logging.h b/libraries/standard/common/include/iot_logging.h index bc3ec21bd0..8875747290 100644 --- a/libraries/standard/common/include/iot_logging.h +++ b/libraries/standard/common/include/iot_logging.h @@ -186,9 +186,9 @@ typedef struct IotLogConfig * @return No return value. On errors, it prints nothing. */ /* @[declare_logging_generic] */ -void IotLog_Generic( int libraryLogSetting, +void IotLog_Generic( int32_t libraryLogSetting, const char * const pLibraryName, - int messageLevel, + int32_t messageLevel, const IotLogConfig_t * const pLogConfig, const char * const pFormat, ... ); diff --git a/libraries/standard/common/src/iot_logging.c b/libraries/standard/common/src/iot_logging.c index ce8a2690e1..876dc4ffb7 100644 --- a/libraries/standard/common/src/iot_logging.c +++ b/libraries/standard/common/src/iot_logging.c @@ -185,9 +185,9 @@ static const char * const _pLogLevelStrings[ 5 ] = /*-----------------------------------------------------------*/ -void IotLog_Generic( int libraryLogSetting, +void IotLog_Generic( int32_t libraryLogSetting, const char * const pLibraryName, - int messageLevel, + int32_t messageLevel, const IotLogConfig_t * const pLogConfig, const char * const pFormat, ... ) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index f026227e23..fe9a78ca1c 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -55,6 +55,7 @@ int iot iotlink iotlistdouble +iotlog iotmqtt iotmqttcallbackinfo iotmqttconnectinfo diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index caae514d47..cfc0197708 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -910,6 +910,12 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection if( disconnected == false ) { ( pMqttConnection->references )++; + + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", pMqttConnection, ( long int ) pMqttConnection->references - 1, @@ -938,6 +944,11 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ( pMqttConnection->references )--; IotMqtt_Assert( pMqttConnection->references >= 0 ); + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite stdint.h + * being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", pMqttConnection, ( long int ) pMqttConnection->references + 1, diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index cc8e95b6de..76713458b9 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -276,6 +276,11 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( pIncomingPacket->pRemainingData == NULL ) { + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite stdint.h + * being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " "%lu for incoming packet type %lu.", pMqttConnection, diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 78aa8fca73..060f394231 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -248,6 +248,11 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) pOperation->u.operation.periodic.retry.nextPeriodMs = IOT_MQTT_RETRY_MS_CEILING; } + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", pMqttConnection, pOperation, @@ -457,6 +462,11 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pOperation->u.operation.jobReference--; + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" " from %ld to %ld.", pMqttConnection, @@ -672,6 +682,11 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { + /* In some implementations IotLog() maps to a C standard printing API + * that need specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Next keep-alive job in %lu ms.", pMqttConnection, ( unsigned long ) pPingreqOperation->u.operation.periodic.ping.nextPeriodMs ); @@ -1023,6 +1038,11 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { ( pResult->u.operation.jobReference )++; + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", pMqttConnection, IotMqtt_OperationType( type ), diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 1cefc1d86d..f55819b823 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1085,6 +1085,11 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, case 0x00: case 0x01: case 0x02: + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLog( IOT_LOG_DEBUG, &_logHideAll, "Topic filter %lu accepted, max QoS %hhu.", @@ -1092,6 +1097,11 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, break; case 0x80: + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLog( IOT_LOG_DEBUG, &_logHideAll, "Topic filter %lu refused.", ( unsigned long ) i ); diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h index 2a6cdd6bfa..ad8058496d 100644 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ b/ports/common/include/atomic/iot_atomic_gcc.h @@ -49,6 +49,9 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes { uint32_t swapped = 0; + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ if( __atomic_compare_exchange( pDestination, &comparand, &newValue, @@ -72,6 +75,9 @@ static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, { void * pOldValue = NULL; + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); return pOldValue; @@ -109,6 +115,9 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pD static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, uint32_t addend ) { + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ); } @@ -120,6 +129,9 @@ static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, uint32_t subtrahend ) { + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ); } @@ -151,6 +163,9 @@ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, uint32_t mask ) { + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ); } @@ -162,6 +177,9 @@ static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, uint32_t mask ) { + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ); } @@ -173,6 +191,9 @@ static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, uint32_t mask ) { + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ); } @@ -184,6 +205,9 @@ static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, uint32_t mask ) { + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ); } diff --git a/ports/common/lexicon.txt b/ports/common/lexicon.txt index 04f9f2b623..fe32da2aec 100644 --- a/ports/common/lexicon.txt +++ b/ports/common/lexicon.txt @@ -8,6 +8,7 @@ closecallback compareandswap config contextmutex +coverity crt destroynotification dns From f7869320f5366ff1fad1c3cdd2d89150877ae2a6 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Mon, 13 Jan 2020 15:50:51 -0800 Subject: [PATCH 366/844] Refactor MQTT operations for complexity (#700) * Refactor MQTT operations for complexity * Refactor _IotMqtt_AddSubscriptions for complexity * Minor comment fix * Address review comments * Revert change to _IotMqtt_ProcessKeepAlive Co-authored-by: Gary Wicker <14828980+gkwicker@users.noreply.github.com> --- .../standard/mqtt/src/iot_mqtt_operation.c | 243 +++++++++++------- .../standard/mqtt/src/iot_mqtt_subscription.c | 36 ++- 2 files changed, 161 insertions(+), 118 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 060f394231..1d2d942590 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -109,6 +109,28 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ); */ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ); +/** + * @brief Schedule a callback for a completed MQTT operation. + * + * @param[in] pOperation The completed MQTT operation. + * + * @return `IOT_MQTT_SUCCESS` if the schedule was successful; + * `IOT_MQTT_SCHEDULING_ERROR` otherwise. + */ +static IotMqttError_t _scheduleCallback( _mqttOperation_t * pOperation ); + +/** + * @brief Complete a pending send operation. + * + * @param[in] pOperation The pending MQTT send operation. + * @param[out] pDestroyOperation Whether the operation should be destroyed afterwards. + * + * @return `true` if the operation is awaiting a response from the network; + * `false` if not. + */ +static bool _completePendingSend( _mqttOperation_t * pOperation, + bool * pDestroyOperation ); + /*-----------------------------------------------------------*/ static bool _mqttOperation_match( const IotLink_t * pOperationLink, @@ -306,6 +328,114 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) /*-----------------------------------------------------------*/ +static IotMqttError_t _scheduleCallback( _mqttOperation_t * pOperation ) +{ + IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Non-waitable operation should have job reference of 1. */ + IotMqtt_Assert( pOperation->u.operation.jobReference == 1 ); + + /* Schedule an invocation of the callback. */ + if( pOperation->u.operation.notify.callback.function != NULL ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessCompletedOperation, + 0 ); + + if( status == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + /* Place the scheduled operation back in the list of operations pending + * processing. */ + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + } + else + { + IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _completePendingSend( _mqttOperation_t * pOperation, + bool * pDestroyOperation ) +{ + bool networkPending = false, waitable = false; + _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; + + /* Check if this operation is waitable. */ + waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + + /* Check if this operation should be scheduled for retransmission. */ + if( pOperation->u.operation.periodic.retry.limit > 0 ) + { + if( _scheduleNextRetry( pOperation ) == false ) + { + pOperation->u.operation.status = IOT_MQTT_SCHEDULING_ERROR; + } + else + { + /* A successfully scheduled PUBLISH retry is awaiting a response + * from the network. */ + networkPending = true; + } + } + else + { + /* Decrement reference count to signal completion of send job. Check + * if the operation should be destroyed. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + if( waitable == true ) + { + *pDestroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); + } + + /* If the operation should not be destroyed, transfer it from the + * pending processing to the pending response list. */ + if( *pDestroyOperation == false ) + { + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + + IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), + &( pOperation->link ) ); + + /* This operation is now awaiting a response from the network. */ + networkPending = true; + } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + + return networkPending; +} + +/*-----------------------------------------------------------*/ + IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, @@ -632,7 +762,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, 0 ); IotMqtt_Assert( swapStatus == 1 ); - /* Schedule a check for PINGRESP. */ + /* Set the period for scheduling a PINGRESP check. */ pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; IotLogDebug( "(MQTT connection %p) PINGREQ sent. Scheduling check for PINGRESP in %d ms.", @@ -806,24 +936,20 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { pOperation->u.operation.status = IOT_MQTT_NETWORK_ERROR; } - else + /* DISCONNECT operations are considered successful upon successful transmission. */ + else if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) { - /* DISCONNECT operations are considered successful upon successful - * transmission. In addition, non-waitable operations with no callback - * may also be considered successful. */ - if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) - { - /* DISCONNECT operations are always waitable. */ - IotMqtt_Assert( waitable == true ); + /* DISCONNECT operations are always waitable. */ + IotMqtt_Assert( waitable == true ); - pOperation->u.operation.status = IOT_MQTT_SUCCESS; - } - else if( waitable == false ) + pOperation->u.operation.status = IOT_MQTT_SUCCESS; + } + /* Non-waitable operations with no callback are also considered successful. */ + else if( waitable == false ) + { + if( pOperation->u.operation.notify.callback.function == NULL ) { - if( pOperation->u.operation.notify.callback.function == NULL ) - { - pOperation->u.operation.status = IOT_MQTT_SUCCESS; - } + pOperation->u.operation.status = IOT_MQTT_SUCCESS; } } } @@ -831,49 +957,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, /* Check if this operation requires further processing. */ if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) { - /* Check if this operation should be scheduled for retransmission. */ - if( pOperation->u.operation.periodic.retry.limit > 0 ) - { - if( _scheduleNextRetry( pOperation ) == false ) - { - pOperation->u.operation.status = IOT_MQTT_SCHEDULING_ERROR; - } - else - { - /* A successfully scheduled PUBLISH retry is awaiting a response - * from the network. */ - networkPending = true; - } - } - else - { - /* Decrement reference count to signal completion of send job. Check - * if the operation should be destroyed. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( waitable == true ) - { - destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); - } - - /* If the operation should not be destroyed, transfer it from the - * pending processing to the pending response list. */ - if( destroyOperation == false ) - { - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), - &( pOperation->link ) ); - - /* This operation is now awaiting a response from the network. */ - networkPending = true; - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } + networkPending = _completePendingSend( pOperation, &destroyOperation ); } /* Destroy the operation or notify of completion if necessary. */ @@ -1079,7 +1163,6 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; /* Check if operation is waitable. */ bool waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; @@ -1107,45 +1190,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) /* Schedule callback invocation for non-waitable operation. */ if( waitable == false ) { - /* Non-waitable operation should have job reference of 1. */ - IotMqtt_Assert( pOperation->u.operation.jobReference == 1 ); - - /* Schedule an invocation of the callback. */ - if( pOperation->u.operation.notify.callback.function != NULL ) - { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessCompletedOperation, - 0 ); - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - - /* Place the scheduled operation back in the list of operations pending - * processing. */ - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - } - else - { - IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } + status = _scheduleCallback( pOperation ); } /* Operations that weren't scheduled may be destroyed. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 5c4f9230ae..e0dcae25df 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -421,25 +421,23 @@ IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, status = IOT_MQTT_NO_MEMORY; break; } - else - { - /* Clear the new subscription. */ - ( void ) memset( pNewSubscription, - 0x00, - sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); - - /* Set the members of the new subscription and add it to the list. */ - pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; - pNewSubscription->packetInfo.order = i; - pNewSubscription->callback = pSubscriptionList[ i ].callback; - pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; - ( void ) memcpy( pNewSubscription->pTopicFilter, - pSubscriptionList[ i ].pTopicFilter, - ( size_t ) ( pSubscriptionList[ i ].topicFilterLength ) ); - - IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), - &( pNewSubscription->link ) ); - } + + /* Clear the new subscription. */ + ( void ) memset( pNewSubscription, + 0x00, + sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); + + /* Set the members of the new subscription and add it to the list. */ + pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; + pNewSubscription->packetInfo.order = i; + pNewSubscription->callback = pSubscriptionList[ i ].callback; + pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; + ( void ) memcpy( pNewSubscription->pTopicFilter, + pSubscriptionList[ i ].pTopicFilter, + ( size_t ) ( pSubscriptionList[ i ].topicFilterLength ) ); + + IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), + &( pNewSubscription->link ) ); } } From a1ff752f60eed1e7b68df607ee23228322a55607 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Mon, 13 Jan 2020 16:04:14 -0800 Subject: [PATCH 367/844] Update demos/iot_config.h and iot_static_memory.h for MISRA Directives 4.4 and 4.10 respectively. (#710) Also add the uncrustify.cfg to the scripts folder. --- demos/iot_config.h | 5 +- .../common/include/iot_static_memory.h | 41 ++--- scripts/uncrustify.cfg | 160 ++++++++++++++++++ 3 files changed, 185 insertions(+), 21 deletions(-) create mode 100644 scripts/uncrustify.cfg diff --git a/demos/iot_config.h b/demos/iot_config.h index b91dd8105b..0cab8ca9a2 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -40,8 +40,9 @@ /* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at * runtime with the command line option -i. Identifiers are optional for the * MQTT demo, but required for demos requiring a Thing Name. (The MQTT demo will - * generate a unique identifier if no identifier is given). */ -/* #define IOT_DEMO_IDENTIFIER "" */ + * generate a unique identifier if no identifier is given). If a specific Thing Name + * is required please define the following line: + * #define IOT_DEMO_IDENTIFIER "" */ /* MQTT demo configuration. The demo publishes bursts of messages. */ #define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ diff --git a/libraries/standard/common/include/iot_static_memory.h b/libraries/standard/common/include/iot_static_memory.h index b51a6f0a68..6537506693 100644 --- a/libraries/standard/common/include/iot_static_memory.h +++ b/libraries/standard/common/include/iot_static_memory.h @@ -26,17 +26,19 @@ * @ref IOT_STATIC_MEMORY_ONLY is `1`. */ +#ifndef IOT_STATIC_MEMORY_H_ +#define IOT_STATIC_MEMORY_H_ + /* The config header is always included first. */ #include "iot_config.h" -/* The functions in this file should only exist in static memory only mode, hence - * the check for IOT_STATIC_MEMORY_ONLY in the double inclusion guard. */ -#if !defined( IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) -#define IOT_STATIC_MEMORY_H_ +/* The functions in this file should only exist in static memory only mode. */ +#if ( IOT_STATIC_MEMORY_ONLY == 1 ) + /* Standard includes. */ -#include -#include + #include + #include /** * @functionspage{static_memory,static memory component} @@ -81,7 +83,7 @@ * int32_t freeIndex = -1; * void * pNewObject = NULL; * - * // Check that sizes match. + * // Check that sizes match. * if( size != OBJECT_SIZE ) * { * // Get the index of a free object. @@ -99,8 +101,8 @@ * @endcode */ /* @[declare_static_memory_findfree] */ -int32_t IotStaticMemory_FindFree( uint32_t * pInUse, - size_t limit ); + int32_t IotStaticMemory_FindFree( uint32_t * pInUse, + size_t limit ); /* @[declare_static_memory_findfree] */ /** @@ -136,11 +138,11 @@ int32_t IotStaticMemory_FindFree( uint32_t * pInUse, * @endcode */ /* @[declare_static_memory_returninuse] */ -void IotStaticMemory_ReturnInUse( void * ptr, - void * pPool, - uint32_t * pInUse, - size_t limit, - size_t elementSize ); + void IotStaticMemory_ReturnInUse( void * ptr, + void * pPool, + uint32_t * pInUse, + size_t limit, + size_t elementSize ); /* @[declare_static_memory_returninuse] */ /*------------------------ Message buffer management ------------------------*/ @@ -161,7 +163,7 @@ void IotStaticMemory_ReturnInUse( void * ptr, * @return The size, in bytes, of a single message buffer. */ /* @[declare_static_memory_messagebuffersize] */ -size_t Iot_MessageBufferSize( void ); + size_t Iot_MessageBufferSize( void ); /* @[declare_static_memory_messagebuffersize] */ /** @@ -178,7 +180,7 @@ size_t Iot_MessageBufferSize( void ); * or no message buffers are available, `NULL` is returned. */ /* @[declare_static_memory_mallocmessagebuffer] */ -void * Iot_MallocMessageBuffer( size_t size ); + void * Iot_MallocMessageBuffer( size_t size ); /* @[declare_static_memory_mallocmessagebuffer] */ /** @@ -191,7 +193,8 @@ void * Iot_MallocMessageBuffer( size_t size ); * @param[in] ptr Pointer to the message buffer to free. */ /* @[declare_static_memory_freemessagebuffer] */ -void Iot_FreeMessageBuffer( void * ptr ); + void Iot_FreeMessageBuffer( void * ptr ); /* @[declare_static_memory_freemessagebuffer] */ - -#endif /* if !defined( IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ + +#endif /* if ( IOT_STATIC_MEMORY_ONLY == 1 ) */ +#endif /* ifndef IOT_STATIC_MEMORY_H_ */ diff --git a/scripts/uncrustify.cfg b/scripts/uncrustify.cfg new file mode 100644 index 0000000000..0cb7d3fbd6 --- /dev/null +++ b/scripts/uncrustify.cfg @@ -0,0 +1,160 @@ +# Uncrustify-0.67 +input_tab_size = 4 # unsigned number +output_tab_size = 4 # unsigned number +sp_arith = force # ignore/add/remove/force +sp_assign = force # ignore/add/remove/force +sp_assign_default = force # ignore/add/remove/force +sp_before_assign = force # ignore/add/remove/force +sp_after_assign = force # ignore/add/remove/force +sp_enum_assign = force # ignore/add/remove/force +sp_enum_before_assign = force # ignore/add/remove/force +sp_enum_after_assign = force # ignore/add/remove/force +sp_pp_stringify = add # ignore/add/remove/force +sp_bool = force # ignore/add/remove/force +sp_compare = force # ignore/add/remove/force +sp_inside_paren = force # ignore/add/remove/force +sp_paren_paren = force # ignore/add/remove/force +sp_paren_brace = force # ignore/add/remove/force +sp_before_ptr_star = force # ignore/add/remove/force +sp_before_unnamed_ptr_star = force # ignore/add/remove/force +sp_between_ptr_star = remove # ignore/add/remove/force +sp_after_ptr_star = force # ignore/add/remove/force +sp_before_byref = force # ignore/add/remove/force +sp_after_byref = remove # ignore/add/remove/force +sp_after_byref_func = remove # ignore/add/remove/force +sp_before_angle = remove # ignore/add/remove/force +sp_inside_angle = remove # ignore/add/remove/force +sp_after_angle = force # ignore/add/remove/force +sp_before_sparen = remove # ignore/add/remove/force +sp_inside_sparen = force # ignore/add/remove/force +sp_after_sparen = force # ignore/add/remove/force +sp_sparen_brace = force # ignore/add/remove/force +sp_before_semi_for = remove # ignore/add/remove/force +sp_before_semi_for_empty = add # ignore/add/remove/force +sp_after_semi_for_empty = force # ignore/add/remove/force +sp_before_square = remove # ignore/add/remove/force +sp_before_squares = remove # ignore/add/remove/force +sp_inside_square = force # ignore/add/remove/force +sp_after_comma = force # ignore/add/remove/force +sp_after_cast = force # ignore/add/remove/force +sp_inside_paren_cast = force # ignore/add/remove/force +sp_sizeof_paren = remove # ignore/add/remove/force +sp_inside_braces_enum = force # ignore/add/remove/force +sp_inside_braces_struct = force # ignore/add/remove/force +sp_inside_braces = force # ignore/add/remove/force +sp_inside_braces_empty = remove # ignore/add/remove/force +sp_type_func = force # ignore/add/remove/force +sp_func_proto_paren = remove # ignore/add/remove/force +sp_func_def_paren = remove # ignore/add/remove/force +sp_inside_fparens = remove # ignore/add/remove/force +sp_inside_fparen = force # ignore/add/remove/force +sp_fparen_brace = add # ignore/add/remove/force +sp_func_call_paren = remove # ignore/add/remove/force +sp_func_class_paren = remove # ignore/add/remove/force +sp_return_paren = remove # ignore/add/remove/force +sp_attribute_paren = remove # ignore/add/remove/force +sp_defined_paren = remove # ignore/add/remove/force +sp_macro = force # ignore/add/remove/force +sp_macro_func = force # ignore/add/remove/force +sp_brace_typedef = force # ignore/add/remove/force +sp_before_dc = remove # ignore/add/remove/force +sp_after_dc = remove # ignore/add/remove/force +sp_cond_colon = force # ignore/add/remove/force +sp_cond_question = force # ignore/add/remove/force +sp_case_label = force # ignore/add/remove/force +sp_endif_cmt = force # ignore/add/remove/force +sp_before_tr_emb_cmt = force # ignore/add/remove/force +sp_num_before_tr_emb_cmt = 1 # unsigned number +indent_columns = 4 # unsigned number +indent_with_tabs = 0 # unsigned number +indent_align_string = true # false/true +indent_class = true # false/true +indent_class_colon = true # false/true +indent_member = 3 # unsigned number +indent_switch_case = 4 # unsigned number +indent_case_brace = 3 # number +nl_assign_leave_one_liners = true # false/true +nl_class_leave_one_liners = true # false/true +nl_start_of_file = remove # ignore/add/remove/force +nl_end_of_file = force # ignore/add/remove/force +nl_end_of_file_min = 1 # unsigned number +nl_assign_brace = add # ignore/add/remove/force +nl_func_var_def_blk = 1 # unsigned number +nl_fcall_brace = add # ignore/add/remove/force +nl_enum_brace = force # ignore/add/remove/force +nl_struct_brace = force # ignore/add/remove/force +nl_union_brace = force # ignore/add/remove/force +nl_if_brace = add # ignore/add/remove/force +nl_brace_else = add # ignore/add/remove/force +nl_else_brace = add # ignore/add/remove/force +nl_getset_brace = force # ignore/add/remove/force +nl_for_brace = add # ignore/add/remove/force +nl_while_brace = add # ignore/add/remove/force +nl_do_brace = add # ignore/add/remove/force +nl_switch_brace = add # ignore/add/remove/force +nl_multi_line_define = true # false/true +nl_before_case = true # false/true +nl_after_case = true # false/true +nl_func_type_name = remove # ignore/add/remove/force +nl_func_proto_type_name = remove # ignore/add/remove/force +nl_func_paren = remove # ignore/add/remove/force +nl_func_def_paren = remove # ignore/add/remove/force +nl_func_decl_start = remove # ignore/add/remove/force +nl_func_def_start = remove # ignore/add/remove/force +nl_func_decl_args = add # ignore/add/remove/force +nl_func_def_args = add # ignore/add/remove/force +nl_func_decl_end = remove # ignore/add/remove/force +nl_func_def_end = remove # ignore/add/remove/force +nl_fdef_brace = add # ignore/add/remove/force +nl_after_semicolon = true # false/true +nl_after_brace_open = true # false/true +nl_after_brace_close = true # false/true +nl_squeeze_ifdef = true # false/true +nl_before_if = force # ignore/add/remove/force +nl_after_if = force # ignore/add/remove/force +nl_before_for = force # ignore/add/remove/force +nl_after_for = force # ignore/add/remove/force +nl_before_while = force # ignore/add/remove/force +nl_after_while = force # ignore/add/remove/force +nl_before_switch = force # ignore/add/remove/force +nl_after_switch = force # ignore/add/remove/force +nl_before_do = force # ignore/add/remove/force +nl_after_do = force # ignore/add/remove/force +nl_max = 4 # unsigned number +nl_after_func_proto_group = 1 # unsigned number +nl_after_func_body_class = 2 # unsigned number +nl_before_block_comment = 2 # unsigned number +eat_blanks_after_open_brace = true # false/true +eat_blanks_before_close_brace = true # false/true +nl_after_return = true # false/true +pos_bool = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force +align_var_def_amp_style = 1 # unsigned number +align_var_def_thresh = 16 # unsigned number +align_assign_thresh = 12 # unsigned number +align_struct_init_span = 3 # unsigned number +align_typedef_gap = 3 # unsigned number +align_typedef_span = 5 # unsigned number +align_typedef_star_style = 1 # unsigned number +align_typedef_amp_style = 1 # unsigned number +align_right_cmt_span = 3 # unsigned number +align_nl_cont = true # false/true +align_pp_define_gap = 4 # unsigned number +align_pp_define_span = 3 # unsigned number +cmt_cpp_to_c = true # false/true +cmt_star_cont = true # false/true +mod_full_brace_do = add # ignore/add/remove/force +mod_full_brace_for = add # ignore/add/remove/force +mod_full_brace_if = add # ignore/add/remove/force +mod_full_brace_while = add # ignore/add/remove/force +mod_full_paren_if_bool = true # false/true +mod_remove_extra_semicolon = true # false/true +mod_add_long_ifdef_endif_comment = 10 # unsigned number +mod_add_long_ifdef_else_comment = 10 # unsigned number +mod_case_brace = remove # ignore/add/remove/force +mod_remove_empty_return = true # false/true +pp_indent = force # ignore/add/remove/force +pp_indent_at_level = true # false/true +pp_indent_count = 4 # unsigned number +pp_space = remove # ignore/add/remove/force +pp_if_indent_code = true # false/true +# option(s) with 'not default' value: 158 From f1a8b698b1d2fd5b8475b11a16d399181c1d867f Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Mon, 13 Jan 2020 18:00:54 -0800 Subject: [PATCH 368/844] fix: MISRA 15.1 (goto) violations - part 1 (#717) * fix: MISRA 15.1 (goto) violations - part 1 Remove gotos, fix a unit test failure. * fix: MISRA 15.1 (goto) violations - part 1 Remove gotos, fix a unit test failure. * fix: MQTT remaining length test. Three bytes need to have continuation bit set. * Update iot_mqtt_network.c --- .../standard/mqtt/src/iot_mqtt_network.c | 137 +++++++-------- .../standard/mqtt/src/iot_mqtt_serialize.c | 162 +++++++++--------- .../mqtt/test/unit/iot_tests_mqtt_api.c | 6 +- 3 files changed, 148 insertions(+), 157 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 76713458b9..28c0c532ac 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -256,57 +256,60 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, pIncomingPacket->type ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - /* Read the remaining length. */ - pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( pNetworkConnection, - pMqttConnection->pNetworkInterface ); - - if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } - - /* Allocate a buffer for the remaining data and read the data. */ - if( pIncomingPacket->remainingLength > 0 ) + if( status == IOT_MQTT_SUCCESS ) { - pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); + /* Read the remaining length. */ + pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( pNetworkConnection, + pMqttConnection->pNetworkInterface ); - if( pIncomingPacket->pRemainingData == NULL ) + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite stdint.h - * being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " - "%lu for incoming packet type %lu.", - pMqttConnection, - ( unsigned long ) pIncomingPacket->remainingLength, - ( unsigned long ) pIncomingPacket->type ); - - _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); - - status = IOT_MQTT_NO_MEMORY; - goto cleanup; + status = IOT_MQTT_BAD_RESPONSE; } + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Allocate a buffer for the remaining data and read the data. */ + if( pIncomingPacket->remainingLength > 0 ) + { + pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); - dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, - pIncomingPacket->pRemainingData, - pIncomingPacket->remainingLength ); + if( pIncomingPacket->pRemainingData == NULL ) + { + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite stdint.h + * being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ + IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " + "%lu for incoming packet type %lu.", + pMqttConnection, + ( unsigned long ) pIncomingPacket->remainingLength, + ( unsigned long ) pIncomingPacket->type ); + + _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); + + status = IOT_MQTT_NO_MEMORY; + } - if( dataBytesRead != pIncomingPacket->remainingLength ) - { - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; + if( status == IOT_MQTT_SUCCESS ) + { + dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, + pIncomingPacket->pRemainingData, + pIncomingPacket->remainingLength ); + + if( dataBytesRead != pIncomingPacket->remainingLength ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } } } /* Clean up on error. */ -cleanup: - if( status != IOT_MQTT_SUCCESS ) { if( pIncomingPacket->pRemainingData != NULL ) @@ -359,8 +362,6 @@ static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, { IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); status = IOT_MQTT_NO_MEMORY; - - goto cleanup; } else { @@ -369,11 +370,10 @@ static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, pOperation->incomingPublish = true; pOperation->pMqttConnection = pMqttConnection; pIncomingPacket->u.pIncomingPublish = pOperation; + /* Deserialize incoming PUBLISH. */ + status = _getPublishDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); } - /* Deserialize incoming PUBLISH. */ - status = _getPublishDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - if( status == IOT_MQTT_SUCCESS ) { /* Send a PUBACK for QoS 1 PUBLISH. */ @@ -446,8 +446,6 @@ static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, IotMqtt_FreeOperation( pOperation ); } -cleanup: - return status; } @@ -610,40 +608,31 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, NULL, &pPubackOperation ); - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; - } - - /* Set the operation type. */ - pPubackOperation->u.operation.type = IOT_MQTT_PUBACK; - - /* Generate a PUBACK packet from the packet identifier. */ - status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( packetIdentifier, - &( pPubackOperation->u.operation.pMqttPacket ), - &( pPubackOperation->u.operation.packetSize ) ); - - if( status != IOT_MQTT_SUCCESS ) + if( status == IOT_MQTT_SUCCESS ) { - goto cleanup; - } + /* Set the operation type. */ + pPubackOperation->u.operation.type = IOT_MQTT_PUBACK; - /* Add the PUBACK operation to the send queue for network transmission. */ - status = _IotMqtt_ScheduleOperation( pPubackOperation, - _IotMqtt_ProcessSend, - 0 ); + /* Generate a PUBACK packet from the packet identifier. */ + status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( packetIdentifier, + &( pPubackOperation->u.operation.pMqttPacket ), + &( pPubackOperation->u.operation.packetSize ) ); - if( status != IOT_MQTT_SUCCESS ) - { - IotLogError( "(MQTT connection %p) Failed to enqueue PUBACK for sending.", - pMqttConnection ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Add the PUBACK operation to the send queue for network transmission. */ + status = _IotMqtt_ScheduleOperation( pPubackOperation, + _IotMqtt_ProcessSend, + 0 ); - goto cleanup; + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to enqueue PUBACK for sending.", + pMqttConnection ); + } + } } - /* Clean up on error. */ -cleanup: - if( status != IOT_MQTT_SUCCESS ) { if( pPubackOperation != NULL ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index f55819b823..990d26c428 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -47,7 +47,7 @@ * Macros for reading the high and low byte of a 2-byte unsigned int. */ #define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ffU ) ) /**< @brief Get low byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ffU ) ) /**< @brief Get low byte. */ /** * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. @@ -124,7 +124,7 @@ /* * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. */ -#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ #define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ /* @@ -1314,32 +1314,33 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI MQTT_PACKET_CONNECT_MAX_SIZE ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - /* Total size of the connect packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( connectPacketSize > remainingLength ); - - /* Allocate memory to hold the CONNECT packet. */ - pBuffer = IotMqtt_MallocMessage( connectPacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) + if( status == IOT_MQTT_SUCCESS ) { - IotLogError( "Failed to allocate memory for CONNECT packet." ); + /* Total size of the connect packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( connectPacketSize > remainingLength ); - status = IOT_MQTT_NO_MEMORY; - goto cleanup; - } + /* Allocate memory to hold the CONNECT packet. */ + pBuffer = IotMqtt_MallocMessage( connectPacketSize ); - /* Set the output parameters. The remainder of this function always succeeds. */ - *pConnectPacket = pBuffer; - *pPacketSize = connectPacketSize; + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for CONNECT packet." ); - _serializeConnect( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* Set the output parameters. The remainder of this function always succeeds. */ + *pConnectPacket = pBuffer; + *pPacketSize = connectPacketSize; -cleanup: + _serializeConnect( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); + } + } return status; } @@ -1480,38 +1481,39 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - /* Total size of the publish packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( publishPacketSize > remainingLength ); - - /* Allocate memory to hold the PUBLISH packet. */ - pBuffer = IotMqtt_MallocMessage( publishPacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) + if( status == IOT_MQTT_SUCCESS ) { - IotLogError( "Failed to allocate memory for PUBLISH packet." ); + /* Total size of the publish packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( publishPacketSize > remainingLength ); - status = IOT_MQTT_NO_MEMORY; - goto cleanup; - } + /* Allocate memory to hold the PUBLISH packet. */ + pBuffer = IotMqtt_MallocMessage( publishPacketSize ); - /* Set the output parameters. The remainder of this function always succeeds. */ - *pPublishPacket = pBuffer; - *pPacketSize = publishPacketSize; + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for PUBLISH packet." ); - /* Serialize publish into buffer pointed to by pBuffer */ - _serializePublish( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - publishPacketSize ); + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPublishPacket = pBuffer; + *pPacketSize = publishPacketSize; -cleanup: + /* Serialize publish into buffer pointed to by pBuffer */ + _serializePublish( pPublishInfo, + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + publishPacketSize ); + } + } return status; } @@ -2562,58 +2564,54 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) { IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer or NULL pRemainingLength." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - /* Set internal mqtt packet parameters. */ - memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Set internal mqtt packet parameters. */ + memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; - mqttPacket.remainingLength = pMqttPacket->remainingLength; - mqttPacket.type = pMqttPacket->type; + mqttPacket.pRemainingData = pMqttPacket->pRemainingData; + mqttPacket.remainingLength = pMqttPacket->remainingLength; + mqttPacket.type = pMqttPacket->type; - /* Call internal deserialize */ - switch( pMqttPacket->type & 0xf0U ) - { - case MQTT_PACKET_TYPE_CONNACK: - status = _IotMqtt_DeserializeConnack( &mqttPacket ); - break; + /* Call internal deserialize */ + switch( pMqttPacket->type & 0xf0U ) + { + case MQTT_PACKET_TYPE_CONNACK: + status = _IotMqtt_DeserializeConnack( &mqttPacket ); + break; - case MQTT_PACKET_TYPE_PUBACK: - status = _IotMqtt_DeserializePuback( &mqttPacket ); - break; + case MQTT_PACKET_TYPE_PUBACK: + status = _IotMqtt_DeserializePuback( &mqttPacket ); + break; - case MQTT_PACKET_TYPE_SUBACK: - status = _IotMqtt_DeserializeSuback( &mqttPacket ); - break; + case MQTT_PACKET_TYPE_SUBACK: + status = _IotMqtt_DeserializeSuback( &mqttPacket ); + break; - case MQTT_PACKET_TYPE_UNSUBACK: - status = _IotMqtt_DeserializeUnsuback( &mqttPacket ); - break; + case MQTT_PACKET_TYPE_UNSUBACK: + status = _IotMqtt_DeserializeUnsuback( &mqttPacket ); + break; - case MQTT_PACKET_TYPE_PINGRESP: - status = _IotMqtt_DeserializePingresp( &mqttPacket ); - break; + case MQTT_PACKET_TYPE_PINGRESP: + status = _IotMqtt_DeserializePingresp( &mqttPacket ); + break; - /* Any other packet type is invalid. */ - default: - IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; + /* Any other packet type is invalid. */ + default: + IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); + status = IOT_MQTT_BAD_PARAMETER; + break; + } } - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; - } - else + if( status == IOT_MQTT_SUCCESS ) { /* Set packetIdentifier only if success is returned. */ pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } -cleanup: - return status; } diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 63f0eecdd6..c7d814df8a 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -2289,7 +2289,11 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) /* Test with invalid remaining length. */ buffer[ 0 ] = 0x20; /* CONN ACK */ - buffer[ 1 ] = 0xFF; /* Remaining length. */ + /* To generate invalid remaining length response, + * three bytes need to have MSB (or continuation bit, 0x80) set */ + buffer[ 1 ] = 0xFF; + buffer[ 2 ] = 0xFF; + buffer[ 3 ] = 0xFF; status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); } From 704774449c9a1634fb1c162801a7479e50396fa1 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 14 Jan 2020 14:31:32 -0800 Subject: [PATCH 369/844] Update documentation for MQTT memory usage (#720) --- doc/lib/mqtt.txt | 161 +++++++++++++++++++- doc/plantuml/images/mqtt_memory_example.png | Bin 0 -> 27841 bytes 2 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 doc/plantuml/images/mqtt_memory_example.png diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 1b75453d49..49382ed086 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -35,17 +35,14 @@ digraph mqtt_dependencies subgraph { rank = same; - platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; - platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; + task_pool[label="Task pool", fillcolor="#e89025ff", URL="@ref taskpool"]; platform_network[label="Network", fillcolor="#e89025ff", URL="@ref platform_network"]; } mqtt -> linear_containers; mqtt -> logging [label=" if logging enabled", style="dashed"]; - mqtt -> platform_threads; - mqtt -> platform_clock; + mqtt -> task_pool; mqtt -> platform_network; mqtt -> static_memory [label=" if static memory only", style="dashed"]; - logging -> platform_clock; logging -> static_memory [label=" if static memory only", style="dashed"]; } @enddot @@ -53,9 +50,157 @@ digraph mqtt_dependencies Currently, the MQTT library has the following dependencies: - The queue library for maintaining the data structures for managing in-progress MQTT operations. - The logging library may be used if @ref IOT_LOG_LEVEL_MQTT is not #IOT_LOG_NONE. -- The platform layer provides an interface to the operating system for thread management, clock functions, networking, etc. - -In addition to the components above, the MQTT library also depends on C standard library headers. +- The task pool to submit background jobs. The MQTT library does not create or manage any threads, but relies on at least one thread being available in the task pool. + +In addition to the components above, the MQTT library also depends on C standard library headers and the [platform layer](@ref platform). + +@section mqtt_memory Memory requirements +@brief Memory requirements of the MQTT library. + +@subsection mqtt_memory_code Code size +@brief Code size of the MQTT library. + +The following measurements were taken with arm-none-eabi-gcc v9.2.1 (October 2019) with the [MQTT library from January 2020](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/f1a8b698b1d2fd5b8475b11a16d399181c1d867f). These values are rough estimates not tuned for any specific ARM processor. + +Debug Build
+All logging, asserts, and diagnostic information enabled. Debugging symbols available. Compiled using the ARM Thumb instruction set (`-mthumb`). Values are in bytes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
objecttextdatabss
iot_mqtt_api1189704
iot_mqtt_network541200
iot_mqtt_operation1048400
iot_mqtt_serialize14454280
iot_mqtt_subscription388500
iot_mqtt_validate433000
total50462284
+ +Minimal Size Build
+No logging, asserts, or diagnostic information. Compiled using the ARM Thumb instruction set (`-mthumb`) and optimized for size (`-Os`). Values are in bytes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
objecttextdatabss
iot_mqtt_api270304
iot_mqtt_network88400
iot_mqtt_operation177300
iot_mqtt_serialize260640
iot_mqtt_subscription124400
iot_mqtt_validate44400
total965444
+ +@subsection mqtt_memory_runtime Runtime memory requirements +@brief Runtime memory requirements of the MQTT library. + +The values given below do not consider any dependencies, such as the task pool or TLS stack. + +Approximate sizes of MQTT objects are given below. Note that the exact size depends on how certain platform types (such as mutexes) are implemented.
+ + + + + + + + + + + + + +
Each MQTT connection250 bytes
Each MQTT operation150 bytes, plus the length of a topic name and message if publishing
Each MQTT subscription70 bytes, plus the length of the subscription topic filter
+ +For example, consider an application that does the following: +1. Establishes an MQTT connection. +2. Subscribes to a topic. +3. Publishes a message to a topic. +4. Unsubscribes from a topic. +5. Closes the connection. + +This application is expected to have a peak runtime memory usage of about 600 bytes (not counting any usage by dependencies or the TLS stack). + +@image html mqtt_memory_example.png width=80% */ /** diff --git a/doc/plantuml/images/mqtt_memory_example.png b/doc/plantuml/images/mqtt_memory_example.png new file mode 100644 index 0000000000000000000000000000000000000000..11fadbc3f84739218bb28e163790ae630e88eae1 GIT binary patch literal 27841 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 G{{H~^D`YVM literal 0 HcmV?d00001 From e60ddd731c9d9db7cf8f4ffdc3cf58137c9f37dd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 16 Jan 2020 09:23:12 -0800 Subject: [PATCH 370/844] Add documentation to MQTT design page (#721) --- doc/lib/mqtt.txt | 35 ++++++++++- doc/plantuml/images/mqtt_design_keepalive.png | Bin 0 -> 82072 bytes doc/plantuml/mqtt_design_keepalive.pu | 57 ++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 doc/plantuml/images/mqtt_design_keepalive.png create mode 100644 doc/plantuml/mqtt_design_keepalive.pu diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 49382ed086..9257da496c 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -207,9 +207,42 @@ This application is expected to have a peak runtime memory usage of about 600 by @page mqtt_design Design @brief Architecture behind the MQTT library. -MQTT relies on the [task pool library](@ref taskpool) to process MQTT operations, such as SUBSCRIBE, PUBLISH, or keep-alive. The MQTT API [functions](@ref mqtt_functions) post a job to the task pool, which is transmitted from a background task. Incoming responses are received through a [network receive callback](@ref platform_network_function_receivecallback), which posts another job to process the response to the task pool. +@section mqtt_design_operations Asynchronous and synchronous operations +@brief This library provides both asynchronous and synchronous MQTT operations. Asynchronous operation functions end with Async, and their synchronous variants end with Sync. + +For example, @ref mqtt_function_publishasync and @ref mqtt_function_publishsync are the respective asynchronous and synchronous MQTT PUBLISH functions. The CONNECT and DISCONNECT operations only have synchronous functions. + +Asynchronous functions return immediately to the calling thread after allocating appropriate resources. The application thread can then continue executing while the MQTT operation is processed in the background. Once the MQTT operation is complete, the MQTT library will invoke a callback function provided by the application. The function @ref mqtt_function_wait can be used to wait for an asynchronous operation's completion. It can also be used to cancel asynchronous operations if passed a timeout of `0`. + +Synchronous functions block their calling thread until the MQTT operation is complete. Synchronous functions take a timeout parameter; if the operation does not complete within this timeout, the function returns @ref IOT_MQTT_TIMEOUT and cancels the operation. Synchronous operations do not use a callback; they return a value that represents the result of the operation to the calling thread. + +@section mqtt_design_asyncoperation Operation diagram +@brief The following diagram shows the workflow of an operation. + +MQTT relies on the [task pool library](@ref taskpool) to process asynchronous MQTT operations in the background. The MQTT API [functions](@ref mqtt_functions) post a job to the task pool, which is transmitted from a background task. Incoming responses are received through a [network receive callback](@ref platform_network_function_receivecallback), which posts another job to process the response to the task pool. + +Synchronous operations are implemented as a call to the corresponding asynchronous operation. The synchronous function then waits on a semaphore until it receives a notification of completion. The workflow of a synchronous operation is otherwise identical to an asynchronous operation. + +Some operations, such as QoS 1 PUBLISHes, may be retried. See @ref IotMqttPublishInfo_t for a description of the retry strategy. @image html mqtt_design_typicaloperation.png width=80% + +@section mqtt_design_keepalive Periodic keep-alive +@brief The following diagram shows the workflow of the periodic keep-alive. + +The MQTT standard specifies a keep-alive mechanism to detect half-open or otherwise unusable network connections. An MQTT client will send periodic ping requests (PINGREQ) to the server if the connection is idle. The MQTT server must respond to ping requests with a ping response (PINGRESP). + +The standard does not specify how long the server has to respond to a ping request, noting only a "reasonable amount of time". In this library, the amount of time a server has to respond to a ping request is set with @ref IOT_MQTT_RESPONSE_WAIT_MS. + +The PINGREQ is allocated with the MQTT connection when it is created. A `failure` flag is also set to `0`. For simplicity, the diagram below only shows the portions of MQTT CONNECT relevant for keep-alive. + +Once MQTT CONNECT returns to the application, the keep-alive is processed entirely through background tasks. The period of the keep-alive is controlled with @ref IotMqttConnectInfo_t.keepAliveSeconds (represented by `keepAliveSec` in the diagram below). Every `keepAliveSec`, the following code is run by a background task in the task pool. +1. The `failure` flag is set to `1`. +2. A PINGREQ is sent to the server. +3. If the connection is alive, the server will respond with a PINGRESP. When the client receives the PINGRESP, it sets `failure` to `0`. +4. After @ref IOT_MQTT_RESPONSE_WAIT_MS, the client checks the `failure` flag. If `failure=0`, then a PINGRESP was received and the next PINGREQ is scheduled. Otherwise, if `failure=1`, then no PINGRESP was received and the connection terminates. + +@image html mqtt_design_keepalive.png width=80% */ /** diff --git a/doc/plantuml/images/mqtt_design_keepalive.png b/doc/plantuml/images/mqtt_design_keepalive.png new file mode 100644 index 0000000000000000000000000000000000000000..03c747f6ebda7a6084b2b82d0fc669ac3d1902da GIT binary patch literal 82072 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 api: Call MQTT CONNECT +deactivate application +activate api +api -> api: Initialize failure=0 +api -> api: Generate PINGREQ +api -> task_pool: Schedule PINGREQ\nsend in keepAliveSec +api -> application: Return +deactivate api +activate application + +== Periodic keep-alive == + +loop While connection is open + ... After keepAliveSec ... + task_pool -> task_pool: Set failure=1 + task_pool -> network: Send PINGREQ + network -> : PINGREQ\nto server + task_pool -> task_pool: Schedule check in\nIOT_MQTT_RESPONSE_WAIT_MS + + alt Connection is alive + network <- : PINGRESP\nfrom server + network -> receive_callback: Notify of PINGRESP + receive_callback -> receive_callback: Set failure=0 + end + + ... After IOT_MQTT_RESPONSE_WAIT_MS ... + alt failure=0 + task_pool -> task_pool: Schedule next PINGREQ\nin keepAliveSec + else failure=1 + task_pool -> application: No response, terminate connection + end +end + +deactivate application + +@enduml From 4df033d7d9cdfcf2316feb9160d4f3a04c8921a7 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Fri, 17 Jan 2020 09:44:03 -0800 Subject: [PATCH 371/844] fix: MISRA 15.1 (goto) violations - part 2 (#718) * fix: MISRA 15.1 (goto) violations - part 2 This PR has changes for iot_mqtt_api.c, iot_mqtt_operation.c. and iot_mqtt_subscription.c. --- libraries/standard/mqtt/lexicon.txt | 2 + libraries/standard/mqtt/src/iot_mqtt_api.c | 613 +++++++++--------- .../standard/mqtt/src/iot_mqtt_operation.c | 157 ++--- .../standard/mqtt/src/iot_mqtt_subscription.c | 35 +- 4 files changed, 388 insertions(+), 419 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index fe9a78ca1c..52d12d80bd 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -56,6 +56,7 @@ iot iotlink iotlistdouble iotlog +iotlogdebug iotmqtt iotmqttcallbackinfo iotmqttconnectinfo @@ -85,6 +86,7 @@ keepaliveseconds lu lwt malloc +misra mqtt mqttconnection mqttoperation diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index cfc0197708..605fbc62ff 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -438,13 +438,11 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * IotLogError( "Network information cannot be NULL." ); status = IOT_NETWORK_BAD_PARAMETER; - goto cleanup; } - - /* Create a new network connection if requested. Otherwise, copy the existing - * network connection. */ - if( pNetworkInfo->createNetworkConnection == true ) + else if( pNetworkInfo->createNetworkConnection == true ) { + /* Create a new network connection if requested. Otherwise, copy the existing + * network connection. */ status = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->u.setup.pNetworkServerInfo, pNetworkInfo->u.setup.pNetworkCredentialInfo, pNetworkConnection ); @@ -458,8 +456,6 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * else { IotLogError( "Failed to create network connection: %d", status ); - - goto cleanup; } } else @@ -470,7 +466,6 @@ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * *pCreatedNewNetworkConnection = false; } -cleanup: return status; } @@ -492,7 +487,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, IotLogError( "Failed to allocate memory for new connection." ); status = false; - goto cleanup; } else { @@ -505,50 +499,47 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, /* Start a new MQTT connection with a reference count of 1. */ pMqttConnection->references = 1; - } - /* Create the references mutex for a new connection. It is a recursive mutex. */ - referencesMutexCreated = IotMutex_Create( &( pMqttConnection->referencesMutex ), true ); + /* Create the references mutex for a new connection. It is a recursive mutex. */ + referencesMutexCreated = IotMutex_Create( &( pMqttConnection->referencesMutex ), true ); - if( referencesMutexCreated == false ) - { - IotLogError( "Failed to create references mutex for new connection." ); + if( referencesMutexCreated == false ) + { + IotLogError( "Failed to create references mutex for new connection." ); - status = false; - goto cleanup; + status = false; + } } - /* Create the subscription mutex for a new connection. */ - subscriptionMutexCreated = IotMutex_Create( &( pMqttConnection->subscriptionMutex ), false ); - - if( subscriptionMutexCreated == false ) + if( status == true ) { - IotLogError( "Failed to create subscription mutex for new connection." ); - - status = false; - goto cleanup; - } - - /* Create the new connection's subscription and operation lists. */ - IotListDouble_Create( &( pMqttConnection->subscriptionList ) ); - IotListDouble_Create( &( pMqttConnection->pendingProcessing ) ); - IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); + /* Create the subscription mutex for a new connection. */ + subscriptionMutexCreated = IotMutex_Create( &( pMqttConnection->subscriptionMutex ), false ); - /* Check if keep-alive is active for this connection. */ - if( keepAliveSeconds != 0 ) - { - if( _createKeepAliveOperation( pNetworkInfo, - keepAliveSeconds, - pMqttConnection ) == false ) + if( subscriptionMutexCreated == false ) { + IotLogError( "Failed to create subscription mutex for new connection." ); + status = false; - goto cleanup; + } + else + { + /* Create the new connection's subscription and operation lists. */ + IotListDouble_Create( &( pMqttConnection->subscriptionList ) ); + IotListDouble_Create( &( pMqttConnection->pendingProcessing ) ); + IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); + + /* Check if keep-alive is active for this connection. */ + if( keepAliveSeconds != 0 ) + { + status = _createKeepAliveOperation( pNetworkInfo, + keepAliveSeconds, + pMqttConnection ); + } } } /* Clean up mutexes and connection if this function failed. */ -cleanup: - if( status == false ) { if( subscriptionMutexCreated == true ) @@ -654,33 +645,34 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation if( _checkInit() == false ) { status = IOT_MQTT_NOT_INITIALIZED; - goto cleanup; } - - /* Check that all elements in the subscription list are valid. */ - if( _IotMqtt_ValidateSubscriptionList( operation, - mqttConnection->awsIotMqttMode, - pSubscriptionList, - subscriptionCount ) == false ) + else { - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; + /* Check that all elements in the subscription list are valid. */ + if( _IotMqtt_ValidateSubscriptionList( operation, + mqttConnection->awsIotMqttMode, + pSubscriptionList, + subscriptionCount ) == false ) + { + status = IOT_MQTT_BAD_PARAMETER; + } } - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + if( status == IOT_MQTT_SUCCESS ) { - if( pOperationReference == NULL ) + /* Check that a reference pointer is provided for a waitable operation. */ + if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) { - IotLogError( "Reference must be provided for a waitable %s.", - IotMqtt_OperationType( operation ) ); + if( pOperationReference == NULL ) + { + IotLogError( "Reference must be provided for a waitable %s.", + IotMqtt_OperationType( operation ) ); - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; + status = IOT_MQTT_BAD_PARAMETER; + } } } -cleanup: return status; } @@ -704,37 +696,31 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op pCallbackInfo, ppSubscriptionOperation ); - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; - } - else + if( status == IOT_MQTT_SUCCESS ) { pSubscriptionOperation = ( *ppSubscriptionOperation ); - } - /* Check the subscription operation data and set the operation type. */ - IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.periodic.retry.limit == 0 ); - pSubscriptionOperation->u.operation.type = operation; - /* Generate a subscription packet from the subscription list. */ - status = serializeSubscription( pSubscriptionList, - subscriptionCount, - &( pSubscriptionOperation->u.operation.pMqttPacket ), - &( pSubscriptionOperation->u.operation.packetSize ), - &( pSubscriptionOperation->u.operation.packetIdentifier ) ); + /* Check the subscription operation data and set the operation type. */ + IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.periodic.retry.limit == 0 ); + pSubscriptionOperation->u.operation.type = operation; - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; + /* Generate a subscription packet from the subscription list. */ + status = serializeSubscription( pSubscriptionList, + subscriptionCount, + &( pSubscriptionOperation->u.operation.pMqttPacket ), + &( pSubscriptionOperation->u.operation.packetSize ), + &( pSubscriptionOperation->u.operation.packetIdentifier ) ); } - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); + } -cleanup: return status; } @@ -762,62 +748,54 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, pCallbackInfo, &pSubscriptionOperation ); - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; - } - - /* Add the subscription list for a SUBSCRIBE. */ - if( operation == IOT_MQTT_SUBSCRIBE ) + if( status == IOT_MQTT_SUCCESS ) { - status = _IotMqtt_AddSubscriptions( mqttConnection, - pSubscriptionOperation->u.operation.packetIdentifier, - pSubscriptionList, - subscriptionCount ); - - if( status != IOT_MQTT_SUCCESS ) + /* Add the subscription list for a SUBSCRIBE. */ + if( operation == IOT_MQTT_SUBSCRIBE ) { - goto cleanup; + status = _IotMqtt_AddSubscriptions( mqttConnection, + pSubscriptionOperation->u.operation.packetIdentifier, + pSubscriptionList, + subscriptionCount ); } } - /* Set the reference, if provided. */ - _setOperationReference( pOperationReference, pSubscriptionOperation ); - - /* Send the SUBSCRIBE packet. */ - if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) - { - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pSubscriptionOperation->job, pSubscriptionOperation ); - } - else + if( status == IOT_MQTT_SUCCESS ) { - status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, - _IotMqtt_ProcessSend, - 0 ); + /* Set the reference, if provided. */ + _setOperationReference( pOperationReference, pSubscriptionOperation ); - if( status != IOT_MQTT_SUCCESS ) + /* Send the SUBSCRIBE packet. */ + if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) { - IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", - mqttConnection, - IotMqtt_OperationType( operation ) ); + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pSubscriptionOperation->job, pSubscriptionOperation ); + } + else + { + status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, + _IotMqtt_ProcessSend, + 0 ); - if( operation == IOT_MQTT_SUBSCRIBE ) + if( status != IOT_MQTT_SUCCESS ) { - _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, - pSubscriptionOperation->u.operation.packetIdentifier, - MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - } + IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", + mqttConnection, + IotMqtt_OperationType( operation ) ); - /* Clear the previously set (and now invalid) reference. */ - _setOperationReference( pOperationReference, IOT_MQTT_OPERATION_INITIALIZER ); + if( operation == IOT_MQTT_SUBSCRIBE ) + { + _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, + pSubscriptionOperation->u.operation.packetIdentifier, + MQTT_REMOVE_ALL_SUBSCRIPTIONS ); + } - goto cleanup; + /* Clear the previously set (and now invalid) reference. */ + _setOperationReference( pOperationReference, IOT_MQTT_OPERATION_INITIALIZER ); + } } } /* Clean up if this function failed. */ -cleanup: - if( status != IOT_MQTT_SUCCESS ) { if( pSubscriptionOperation != NULL ) @@ -911,9 +889,9 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection { ( pMqttConnection->references )++; - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite + /* In some implementations IotLogDebug() maps to C standard printing API + * that needs specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", @@ -944,9 +922,9 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ( pMqttConnection->references )--; IotMqtt_Assert( pMqttConnection->references >= 0 ); - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite stdint.h + /* In some implementations IotLogDebug() maps to C standard printing API + * that needs specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite stdint.h * being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", @@ -1054,39 +1032,41 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( _checkInit() == false ) { status = IOT_MQTT_NOT_INITIALIZED; - goto cleanup; } - /* Validate network interface and connect info. */ - if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) + else if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) { status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - networkStatus = _createNetworkConnection( pNetworkInfo, - &pNetworkConnection, - &ownNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) + else { - status = IOT_MQTT_NETWORK_ERROR; - goto cleanup; + networkStatus = _createNetworkConnection( pNetworkInfo, + &pNetworkConnection, + &ownNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + status = IOT_MQTT_NETWORK_ERROR; + } } - IotLogInfo( "Establishing new MQTT connection." ); + if( status == IOT_MQTT_SUCCESS ) + { + IotLogInfo( "Establishing new MQTT connection." ); - /* Initialize a new MQTT connection object. */ - pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, - pNetworkInfo, - pConnectInfo->keepAliveSeconds ); + /* Initialize a new MQTT connection object. */ + pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, + pNetworkInfo, + pConnectInfo->keepAliveSeconds ); - if( pNewMqttConnection == NULL ) - { - status = IOT_MQTT_NO_MEMORY; - goto cleanup; + if( pNewMqttConnection == NULL ) + { + status = IOT_MQTT_NO_MEMORY; + } } - else + + /* Set the MQTT receive callback. */ + if( status == IOT_MQTT_SUCCESS ) { /* Set the network connection associated with the MQTT connection. */ pNewMqttConnection->pNetworkConnection = pNetworkConnection; @@ -1098,82 +1078,76 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, #else pNewMqttConnection->pSerializer = NULL; #endif - } - - /* Set the MQTT receive callback. */ - networkStatus = pNewMqttConnection->pNetworkInterface->setReceiveCallback( pNetworkConnection, - IotMqtt_ReceiveCallback, - pNewMqttConnection ); - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogError( "Failed to set MQTT network receive callback." ); - - status = IOT_MQTT_NETWORK_ERROR; - goto cleanup; - } + networkStatus = pNewMqttConnection->pNetworkInterface->setReceiveCallback( pNetworkConnection, + IotMqtt_ReceiveCallback, + pNewMqttConnection ); - /* Create a CONNECT operation. */ - status = _IotMqtt_CreateOperation( pNewMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ); + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogError( "Failed to set MQTT network receive callback." ); - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; + status = IOT_MQTT_NETWORK_ERROR; + } + else + { + /* Create a CONNECT operation. */ + status = _IotMqtt_CreateOperation( pNewMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ); + } } - /* Ensure the members set by operation creation and serialization - * are appropriate for a blocking CONNECT. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); - - /* Set the operation type. */ - pOperation->u.operation.type = IOT_MQTT_CONNECT; - - /* Add previous session subscriptions. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) + if( status == IOT_MQTT_SUCCESS ) { - /* Previous subscription count should have been validated as nonzero. */ - IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); + /* Ensure the members set by operation creation and serialization + * are appropriate for a blocking CONNECT. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); - status = _IotMqtt_AddSubscriptions( pNewMqttConnection, - 2, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ); + /* Set the operation type. */ + pOperation->u.operation.type = IOT_MQTT_CONNECT; - if( status != IOT_MQTT_SUCCESS ) + /* Add previous session subscriptions. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) { - goto cleanup; + /* Previous subscription count should have been validated as nonzero. */ + IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); + + status = _IotMqtt_AddSubscriptions( pNewMqttConnection, + 2, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ); } } - /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ - status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( pConnectInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); - - if( status != IOT_MQTT_SUCCESS ) + if( status == IOT_MQTT_SUCCESS ) { - goto cleanup; + /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ + status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( pConnectInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); } - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); - /* Send the CONNECT packet. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + /* Send the CONNECT packet. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - status = IotMqtt_Wait( pOperation, timeoutMs ); + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + status = IotMqtt_Wait( pOperation, timeoutMs ); - /* The call to wait cleans up the CONNECT operation, so set the pointer - * to NULL. */ - pOperation = NULL; + /* The call to wait cleans up the CONNECT operation, so set the pointer + * to NULL. */ + pOperation = NULL; + } /* When a connection is successfully established, schedule keep-alive job. */ if( status == IOT_MQTT_SUCCESS ) @@ -1190,13 +1164,10 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) { status = IOT_MQTT_SCHEDULING_ERROR; - goto cleanup; } } } -cleanup: - if( status != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to establish new MQTT connection, error %s.", @@ -1259,7 +1230,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, uint32_t flags ) { bool disconnected = false, initCalled = false; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttError_t status = IOT_MQTT_SUCCESS; _mqttOperation_t * pOperation = NULL; /* Check that IotMqtt_Init was called. */ @@ -1267,50 +1238,58 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, if( initCalled == false ) { - goto cleanup; + status = IOT_MQTT_STATUS_PENDING; } - - /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" - * flag is not set. */ - if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == IOT_MQTT_FLAG_CLEANUP_ONLY ) + else { - goto cleanup; + /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" + * flag is not set. */ + if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == IOT_MQTT_FLAG_CLEANUP_ONLY ) + { + status = IOT_MQTT_STATUS_PENDING; + } } - /* Read the connection status. */ - IotMutex_Lock( &( mqttConnection->referencesMutex ) ); - disconnected = mqttConnection->disconnected; - IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - - if( disconnected == true ) + if( status == IOT_MQTT_SUCCESS ) { - goto cleanup; - } - - IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); + /* Read the connection status. */ + IotMutex_Lock( &( mqttConnection->referencesMutex ) ); + disconnected = mqttConnection->disconnected; + IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - /* Create a DISCONNECT operation. This function blocks until the DISCONNECT - * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ - status = _IotMqtt_CreateOperation( mqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ); + if( disconnected == true ) + { + status = IOT_MQTT_STATUS_PENDING; + } + } if( status == IOT_MQTT_SUCCESS ) { - /* Ensure that the members set by operation creation and serialization - * are appropriate for a blocking DISCONNECT. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); + IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); - /* Set the operation type. */ - pOperation->u.operation.type = IOT_MQTT_DISCONNECT; + /* Create a DISCONNECT operation. This function blocks until the DISCONNECT + * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ + status = _IotMqtt_CreateOperation( mqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ); - /* Generate a DISCONNECT packet. */ - status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Ensure that the members set by operation creation and serialization + * are appropriate for a blocking DISCONNECT. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); + + /* Set the operation type. */ + pOperation->u.operation.type = IOT_MQTT_DISCONNECT; + + /* Generate a DISCONNECT packet. */ + status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); + } } if( status == IOT_MQTT_SUCCESS ) @@ -1343,10 +1322,6 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, } } - /* This function has no return value and no cleanup, but uses the cleanup - * label to exit on error. */ -cleanup: - if( initCalled == true ) { /* Close the underlying network connection. This also cleans up keep-alive. @@ -1541,102 +1516,94 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, if( _checkInit() == false ) { status = IOT_MQTT_NOT_INITIALIZED; - goto cleanup; - } - - if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, - pPublishInfo, - flags, - pCallbackInfo, - pPublishOperation ) == false ) - { - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Create a PUBLISH operation. */ - status = _IotMqtt_CreateOperation( mqttConnection, + else if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, + pPublishInfo, flags, pCallbackInfo, - &pOperation ); - - if( status != IOT_MQTT_SUCCESS ) + pPublishOperation ) == false ) { - goto cleanup; + status = IOT_MQTT_BAD_PARAMETER; } - - /* Check the PUBLISH operation data and set the operation type. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - - /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ - if( mqttConnection->awsIotMqttMode == true ) + else { - pPacketIdentifierHigh = &( pOperation->u.operation.pPacketIdentifierHigh ); + /* Create a PUBLISH operation. */ + status = _IotMqtt_CreateOperation( mqttConnection, + flags, + pCallbackInfo, + &pOperation ); } - /* Generate a PUBLISH packet from pPublishInfo. */ - status = _getMqttPublishSerializer( mqttConnection->pSerializer )( pPublishInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ), - &( pOperation->u.operation.packetIdentifier ), - pPacketIdentifierHigh ); - - if( status != IOT_MQTT_SUCCESS ) + if( status == IOT_MQTT_SUCCESS ) { - goto cleanup; - } - - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + /* Check the PUBLISH operation data and set the operation type. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - /* Initialize PUBLISH retry if retryLimit is set. */ - if( pPublishInfo->retryLimit > 0 ) - { - /* A QoS 0 PUBLISH may not be retried. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ + if( mqttConnection->awsIotMqttMode == true ) { - pOperation->u.operation.periodic.retry.limit = pPublishInfo->retryLimit; - pOperation->u.operation.periodic.retry.nextPeriodMs = pPublishInfo->retryMs; + pPacketIdentifierHigh = &( pOperation->u.operation.pPacketIdentifierHigh ); } - } - /* Set the reference, if provided. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) - { - _setOperationReference( pPublishOperation, pOperation ); + /* Generate a PUBLISH packet from pPublishInfo. */ + status = _getMqttPublishSerializer( mqttConnection->pSerializer )( pPublishInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ), + &( pOperation->u.operation.packetIdentifier ), + pPacketIdentifierHigh ); } - /* Send the PUBLISH packet. */ - if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) - { - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - } - else + if( status == IOT_MQTT_SUCCESS ) { - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - 0 ); + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); - if( status != IOT_MQTT_SUCCESS ) + /* Initialize PUBLISH retry if retryLimit is set. */ + if( pPublishInfo->retryLimit > 0 ) { - IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", - mqttConnection ); - - /* Clear the previously set (and now invalid) reference. */ + /* A QoS 0 PUBLISH may not be retried. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - _setOperationReference( pPublishOperation, IOT_MQTT_OPERATION_INITIALIZER ); + pOperation->u.operation.periodic.retry.limit = pPublishInfo->retryLimit; + pOperation->u.operation.periodic.retry.nextPeriodMs = pPublishInfo->retryMs; } + } + + /* Set the reference, if provided. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + _setOperationReference( pPublishOperation, pOperation ); + } + + /* Send the PUBLISH packet. */ + if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) + { + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + } + else + { + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", + mqttConnection ); - goto cleanup; + /* Clear the previously set (and now invalid) reference. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + { + _setOperationReference( pPublishOperation, IOT_MQTT_OPERATION_INITIALIZER ); + } + } } } /* Clean up the PUBLISH operation if this function fails. Otherwise, set the * appropriate return code based on QoS. */ -cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -1711,18 +1678,17 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, if( _checkInit() == false ) { status = IOT_MQTT_NOT_INITIALIZED; - goto cleanup; } - /* Validate the given operation reference. */ - if( _IotMqtt_ValidateOperation( operation ) == false ) + else if( _IotMqtt_ValidateOperation( operation ) == false ) { status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Check the MQTT connection status. */ - pMqttConnection = operation->pMqttConnection; + else + { + /* Check the MQTT connection status. */ + pMqttConnection = operation->pMqttConnection; + } if( status == IOT_MQTT_SUCCESS ) { @@ -1761,7 +1727,6 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, } } -cleanup: return status; } diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 1d2d942590..dfb7a60f66 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -270,9 +270,9 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) pOperation->u.operation.periodic.retry.nextPeriodMs = IOT_MQTT_RETRY_MS_CEILING; } - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", @@ -454,96 +454,103 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, IotLogError( "Callback should not be set for a waitable operation." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } } - IotLogDebug( "(MQTT connection %p) Creating new operation record.", - pMqttConnection ); - - /* Increment the reference count for the MQTT connection when creating a new - * operation. */ - if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == false ) + if( status == IOT_MQTT_SUCCESS ) { - IotLogError( "(MQTT connection %p) New operation record cannot be created" - " for a closed connection", + IotLogDebug( "(MQTT connection %p) Creating new operation record.", pMqttConnection ); - status = IOT_MQTT_NETWORK_ERROR; - goto cleanup; - } - else - { - /* Reference count will need to be decremented on error. */ - decrementOnError = true; - } - - /* Allocate memory for a new operation. */ - pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - if( pOperation == NULL ) - { - IotLogError( "(MQTT connection %p) Failed to allocate memory for new " - "operation record.", - pMqttConnection ); + /* Increment the reference count for the MQTT connection when creating a new + * operation. */ + if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == false ) + { + IotLogError( "(MQTT connection %p) New operation record cannot be created" + " for a closed connection", + pMqttConnection ); - status = IOT_MQTT_NO_MEMORY; - goto cleanup; - } - else - { - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - - /* Initialize some members of the new operation. */ - pOperation->pMqttConnection = pMqttConnection; - pOperation->u.operation.jobReference = 1; - pOperation->u.operation.flags = flags; - pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; + status = IOT_MQTT_NETWORK_ERROR; + } + else + { + /* Reference count will need to be decremented on error. */ + decrementOnError = true; + } } - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( waitable == true ) + if( status == IOT_MQTT_SUCCESS ) { - /* Create a semaphore to wait on for a waitable operation. */ - if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) + /* Allocate memory for a new operation. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + + if( pOperation == NULL ) { - IotLogError( "(MQTT connection %p) Failed to create semaphore for " - "waitable operation.", + IotLogError( "(MQTT connection %p) Failed to allocate memory for new " + "operation record.", pMqttConnection ); status = IOT_MQTT_NO_MEMORY; - goto cleanup; } else { - /* A waitable operation is created with an additional reference for the - * Wait function. */ - ( pOperation->u.operation.jobReference )++; + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + + /* Initialize some members of the new operation. */ + pOperation->pMqttConnection = pMqttConnection; + pOperation->u.operation.jobReference = 1; + pOperation->u.operation.flags = flags; + pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; } } - else + + if( status == IOT_MQTT_SUCCESS ) { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( waitable == true ) { - pOperation->u.operation.notify.callback = *pCallbackInfo; + /* Create a semaphore to wait on for a waitable operation. */ + if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) + { + IotLogError( "(MQTT connection %p) Failed to create semaphore for " + "waitable operation.", + pMqttConnection ); + + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* A waitable operation is created with an additional reference for the + * Wait function. */ + ( pOperation->u.operation.jobReference )++; + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->u.operation.notify.callback = *pCallbackInfo; + } } - } - /* Add this operation to the MQTT connection's operation list. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Add this operation to the MQTT connection's operation list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - /* Set the output parameter. */ - *pNewOperation = pOperation; + /* Set the output parameter. */ + *pNewOperation = pOperation; + } + } /* Clean up operation and decrement reference count if this function failed. */ -cleanup: if( status != IOT_MQTT_SUCCESS ) { @@ -592,9 +599,9 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pOperation->u.operation.jobReference--; - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" @@ -812,9 +819,9 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { - /* In some implementations IotLog() maps to a C standard printing API + /* In some implementations IotLog() maps to a C standard printing API * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Next keep-alive job in %lu ms.", @@ -1122,9 +1129,9 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { ( pResult->u.operation.jobReference )++; - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index e0dcae25df..5908f95541 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -162,24 +162,21 @@ static bool _matchEndWildcards( const char * pTopicFilter, { /* Determine if the topic filter ends with the '#' wildcard. */ status = ( pTopicFilter[ filterIndex + 1 ] == '/' ) && ( pTopicFilter[ filterIndex + 2 ] == '#' ); - - if( status == true ) - { - goto cleanup; - } } - /* Determine if the last character is reached for both topic name and topic - * filter for the '+' wildcard. */ - endChar = ( nameIndex == topicNameLength - 1 ) && ( filterIndex == topicFilterLength - 2 ); - - if( endChar == true ) + if( status == false ) { - /* Filter "sport/+" also matches the "sport/" but not "sport". */ - status = ( pTopicFilter[ filterIndex + 1 ] == '+' ); + /* Determine if the last character is reached for both topic name and topic + * filter for the '+' wildcard. */ + endChar = ( nameIndex == topicNameLength - 1 ) && ( filterIndex == topicFilterLength - 2 ); + + if( endChar == true ) + { + /* Filter "sport/+" also matches the "sport/" but not "sport". */ + status = ( pTopicFilter[ filterIndex + 1 ] == '+' ); + } } -cleanup: *pMatch = status; return status; @@ -251,7 +248,7 @@ static bool _topicFilterMatch( const char * pTopicName, filterIndex, &status ) == true ) { - goto cleanup; + break; } } else @@ -264,7 +261,7 @@ static bool _topicFilterMatch( const char * pTopicName, &nameIndex, &status ) == true ) { - goto cleanup; + break; } } @@ -273,14 +270,12 @@ static bool _topicFilterMatch( const char * pTopicName, filterIndex++; } - /* If the end of both strings has been reached, they match. */ - if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) + if( status == false ) { - status = true; + /* If the end of both strings has been reached, they match. */ + status = ( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ); } -cleanup: - return status; } From 6f7357a66ff51dc99603e5075cb95b0ef81cfd82 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Fri, 17 Jan 2020 14:13:41 -0800 Subject: [PATCH 372/844] Check for a NULL pOperation before cleaning up. (#722) Before _deserializePublish would immediately goto cleanup if the MallocOperation failed. In commit f1a8b698b the goto was deleted. --- libraries/standard/mqtt/src/iot_mqtt_network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 28c0c532ac..bb562cee7c 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -422,7 +422,7 @@ static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, } /* Free PUBLISH operation on error. */ - if( status != IOT_MQTT_SUCCESS ) + if( ( status != IOT_MQTT_SUCCESS ) && ( pOperation != NULL ) ) { /* Check ownership of the received MQTT packet. */ if( pOperation->u.publish.pReceivedData != NULL ) From 0d8f764fbaa5cfb8f652cf229933c108c72b417f Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Mon, 20 Jan 2020 18:22:11 -0800 Subject: [PATCH 373/844] MISRA Rule 10.3 updates for MQTT files and iot_atomic_gcc.h (#724) * Update MQTT files and iot_atomic_gcc.h for MISRA Rule 10.3 violations. --- .../mqtt/include/types/iot_mqtt_types.h | 2 +- libraries/standard/mqtt/src/iot_mqtt_api.c | 4 +-- .../standard/mqtt/src/iot_mqtt_operation.c | 4 ++- .../standard/mqtt/src/iot_mqtt_serialize.c | 24 +++++++------ .../mqtt/src/private/iot_mqtt_internal.h | 2 +- ports/common/include/atomic/iot_atomic_gcc.h | 34 +++++++++---------- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index cf907adaa7..4c0e381bd3 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -1154,7 +1154,7 @@ typedef struct IotMqttNetworkInfo * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up * resources. */ -#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001UL ) +#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001U ) /** * @brief Causes @ref mqtt_function_disconnect to only free memory and not send diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 605fbc62ff..e2e12418f0 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -387,8 +387,8 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo pMqttConnection->pingreq.u.operation.type = IOT_MQTT_PINGREQ; /* Convert the keep-alive interval to milliseconds. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = keepAliveSeconds * 1000; - pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = keepAliveSeconds * 1000; + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = keepAliveSeconds * 1000U; + pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = keepAliveSeconds * 1000U; /* Generate a PINGREQ packet. */ serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index dfb7a60f66..06d9f2bee1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -1076,7 +1076,9 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; _mqttOperation_t * pResult = NULL; IotLink_t * pResultLink = NULL; - _operationMatchParam_t operationMatchParams = { 0 }; + _operationMatchParam_t operationMatchParams; + + memset( &operationMatchParams, 0, sizeof( _operationMatchParam_t ) ); /* Set the members of the search parameter. */ operationMatchParams.type = type; diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 990d26c428..d51c9f42d6 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -391,9 +391,9 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, */ static const IotLogConfig_t _logHideAll = { - .hideLibraryName = true, - .hideLogLevel = true, - .hideTimestring = true + .hideLibraryName = ( bool ) ( true ), + .hideLogLevel = ( bool ) ( true ), + .hideTimestring = ( bool ) ( true ) }; #endif @@ -457,8 +457,8 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { - lengthByte = length % 128; - length = length / 128; + lengthByte = ( uint8_t ) ( length % 128U ); + length = length / 128U; /* Set the high bit of this byte, indicating that there's more data. */ if( length > 0 ) @@ -1085,9 +1085,10 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, case 0x00: case 0x01: case 0x02: - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite + + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLog( IOT_LOG_DEBUG, @@ -1097,9 +1098,10 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, break; case 0x80: - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite + + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLog( IOT_LOG_DEBUG, diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index dbb3be2577..ee69ebc60c 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -275,7 +275,7 @@ * use only. Nevertheless, its value must be bitwise exclusive of all conflicting * @ref mqtt_constants_flags. */ -#define MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ( 0x80000000 ) +#define MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ( 0x80000000U ) /** * @brief When calling #_IotMqtt_RemoveSubscriptionByPacket, use this value diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h index ad8058496d..b26d50e544 100644 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ b/ports/common/include/atomic/iot_atomic_gcc.h @@ -49,8 +49,8 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes { uint32_t swapped = 0; - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is used with only the gcc compiler which + * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ if( __atomic_compare_exchange( pDestination, &comparand, @@ -75,7 +75,7 @@ static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, { void * pOldValue = NULL; - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); @@ -115,10 +115,10 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pD static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, uint32_t addend ) { - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - return __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ) ); } /*-----------------------------------------------------------*/ @@ -129,10 +129,10 @@ static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, uint32_t subtrahend ) { - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - return __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ) ); } /*-----------------------------------------------------------*/ @@ -142,7 +142,7 @@ static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, */ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) { - return __atomic_fetch_add( pAugend, 1, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_add( pAugend, 1U, __ATOMIC_SEQ_CST ) ); } /*-----------------------------------------------------------*/ @@ -152,7 +152,7 @@ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) */ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend ) { - return __atomic_fetch_sub( pMinuend, 1, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, 1U, __ATOMIC_SEQ_CST ) ); } /*--------------------- Bitwise logic -------------------------*/ @@ -163,10 +163,10 @@ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - return __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ) ); } /*-----------------------------------------------------------*/ @@ -177,10 +177,10 @@ static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - return __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ) ); } /*-----------------------------------------------------------*/ @@ -191,10 +191,10 @@ static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - return __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ) ); } /*-----------------------------------------------------------*/ @@ -205,10 +205,10 @@ static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which + /* This header file is used with only the gcc compiler which * requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - return __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ); + return ( uint32_t ) ( __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ) ); } #endif /* IOT_ATOMIC_GCC_H_ */ From 822fff40964c40e268d9db96b97e9afa8db86d61 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Mon, 20 Jan 2020 18:36:40 -0800 Subject: [PATCH 374/844] MISRA Rules 10.4 and 10.7 on MQTT library files and iot_atomic_gcc.h (#725) * MISRA Rule 10.4 updates on all MQTT library files and iot_atomic_gcc.h * Update IotLog() in coverity annotation comment for specificity. * Add IotLogError to the lexicon. --- libraries/standard/mqtt/lexicon.txt | 1 + libraries/standard/mqtt/src/iot_mqtt_api.c | 36 +++--- .../standard/mqtt/src/iot_mqtt_network.c | 28 ++--- .../standard/mqtt/src/iot_mqtt_operation.c | 44 ++++---- .../standard/mqtt/src/iot_mqtt_serialize.c | 104 +++++++++--------- .../standard/mqtt/src/iot_mqtt_subscription.c | 8 +- .../standard/mqtt/src/iot_mqtt_validate.c | 28 ++--- .../mqtt/src/private/iot_mqtt_internal.h | 4 +- ports/common/include/atomic/iot_atomic_gcc.h | 12 +- 9 files changed, 136 insertions(+), 129 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 52d12d80bd..a6f10a6c3d 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -57,6 +57,7 @@ iotlink iotlistdouble iotlog iotlogdebug +iotlogerror iotmqtt iotmqttcallbackinfo iotmqttconnectinfo diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index e2e12418f0..a6278b489b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -387,8 +387,8 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo pMqttConnection->pingreq.u.operation.type = IOT_MQTT_PINGREQ; /* Convert the keep-alive interval to milliseconds. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = keepAliveSeconds * 1000U; - pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = keepAliveSeconds * 1000U; + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = ( uint32_t ) ( keepAliveSeconds * 1000U ); + pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = ( uint32_t ) ( keepAliveSeconds * 1000U ); /* Generate a PINGREQ packet. */ serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), @@ -530,7 +530,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); /* Check if keep-alive is active for this connection. */ - if( keepAliveSeconds != 0 ) + if( keepAliveSeconds != 0U ) { status = _createKeepAliveOperation( pNetworkInfo, keepAliveSeconds, @@ -569,7 +569,7 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; /* Clean up keep-alive if still allocated. */ - if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) + if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) { IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); @@ -587,9 +587,9 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) /* A connection to be destroyed should have no keep-alive and at most 1 * reference. */ IotMqtt_Assert( pMqttConnection->references <= 1 ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs == 0 ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs == 0U ); IotMqtt_Assert( pMqttConnection->pingreq.u.operation.pMqttPacket == NULL ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize == 0 ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize == 0U ); /* Remove all subscriptions. */ IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); @@ -703,7 +703,7 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op /* Check the subscription operation data and set the operation type. */ IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.periodic.retry.limit == 0 ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.periodic.retry.limit == 0U ); pSubscriptionOperation->u.operation.type = operation; /* Generate a subscription packet from the subscription list. */ @@ -718,7 +718,7 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op { /* Check the serialized MQTT packet. */ IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0U ); } return status; @@ -958,7 +958,7 @@ IotMqttError_t IotMqtt_Init( void ) MQTT_LIBRARY_INITIALIZED, MQTT_LIBRARY_UNINITIALIZED ); - if( allowInitialization == 1 ) + if( allowInitialization == 1U ) { /* Call any additional serializer initialization function if serializer * overrides are enabled. */ @@ -995,7 +995,7 @@ void IotMqtt_Cleanup( void ) MQTT_LIBRARY_UNINITIALIZED, MQTT_LIBRARY_INITIALIZED ); - if( allowCleanup == 1 ) + if( allowCleanup == 1U ) { /* Call any additional serializer cleanup initialization function if serializer * overrides are enabled. */ @@ -1106,7 +1106,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0U ); /* Set the operation type. */ pOperation->u.operation.type = IOT_MQTT_CONNECT; @@ -1115,7 +1115,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pConnectInfo->pPreviousSubscriptions != NULL ) { /* Previous subscription count should have been validated as nonzero. */ - IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0 ); + IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0U ); status = _IotMqtt_AddSubscriptions( pNewMqttConnection, 2, @@ -1136,7 +1136,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, { /* Check the serialized MQTT packet. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); /* Send the CONNECT packet. */ _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); @@ -1153,7 +1153,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status == IOT_MQTT_SUCCESS ) { /* Check if a keep-alive job should be scheduled. */ - if( pNewMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) + if( pNewMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) { IotLogDebug( "Scheduling first MQTT keep-alive job." ); @@ -1281,7 +1281,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0U ); /* Set the operation type. */ pOperation->u.operation.type = IOT_MQTT_DISCONNECT; @@ -1296,7 +1296,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { /* Check the serialized MQTT packet. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); /* Send the DISCONNECT packet. */ _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); @@ -1558,10 +1558,10 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, { /* Check the serialized MQTT packet. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); /* Initialize PUBLISH retry if retryLimit is set. */ - if( pPublishInfo->retryLimit > 0 ) + if( pPublishInfo->retryLimit > 0U ) { /* A QoS 0 PUBLISH may not be retried. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index bb562cee7c..882690ed37 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -242,7 +242,7 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, /* No buffer for remaining data should be allocated. */ IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); - IotMqtt_Assert( pIncomingPacket->remainingLength == 0 ); + IotMqtt_Assert( pIncomingPacket->remainingLength == 0U ); /* Read the packet type, which is the first byte available. */ pIncomingPacket->type = _getPacketTypeFunc( pMqttConnection->pSerializer )( pNetworkConnection, @@ -273,15 +273,15 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( status == IOT_MQTT_SUCCESS ) { /* Allocate a buffer for the remaining data and read the data. */ - if( pIncomingPacket->remainingLength > 0 ) - { + if( pIncomingPacket->remainingLength > 0U ) + { pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); if( pIncomingPacket->pRemainingData == NULL ) { - /* In some implementations IotLog() maps to C standard printing API + /* In some implementations IotLogError() maps to C standard printing API * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite stdint.h + * inttypes.h may not be available on some C99 compilers, despite stdint.h * being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " @@ -461,8 +461,8 @@ static IotMqttError_t _deserializePingResp( _mqttConnection_t * pMqttConnection, if( status == IOT_MQTT_SUCCESS ) { if( Atomic_CompareAndSwap_u32( &( pMqttConnection->pingreq.u.operation.periodic.ping.failure ), - 0, - 1 ) == 1 ) + 0U, + 1U ) == 1U ) { IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", pMqttConnection ); @@ -485,7 +485,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMqttError_t status = IOT_MQTT_STATUS_PENDING; /* A buffer for remaining data must be allocated if remaining length is not 0. */ - IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0 ) == + IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0U ) == ( pIncomingPacket->pRemainingData != NULL ) ); /* Only valid packets should be given to this function. */ @@ -675,7 +675,7 @@ bool _IotMqtt_GetNextByte( void * pNetworkConnection, 1 ); /* Set the output parameter and return success if 1 byte was read. */ - if( bytesReceived == 1 ) + if( bytesReceived == 1U ) { *pIncomingByte = incomingByte; status = true; @@ -683,7 +683,7 @@ bool _IotMqtt_GetNextByte( void * pNetworkConnection, else { /* Network receive must return 0 on failure. */ - IotMqtt_Assert( bytesReceived == 0 ); + IotMqtt_Assert( bytesReceived == 0U ); } return status; @@ -710,11 +710,11 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pMqttConnection->disconnected = true; - if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) + if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) { /* Keep-alive must have a PINGREQ allocated. */ IotMqtt_Assert( pMqttConnection->pingreq.u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize != 0 ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize != 0U ); /* PINGREQ provides a reference to the connection, so reference count must * be nonzero. */ @@ -733,9 +733,9 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0U; pMqttConnection->pingreq.u.operation.pMqttPacket = NULL; - pMqttConnection->pingreq.u.operation.packetSize = 0; + pMqttConnection->pingreq.u.operation.packetSize = 0U; /* Keep-alive is cleaned up; decrement reference count. Since this * function must be followed with a call to DISCONNECT, a check to diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 06d9f2bee1..dd8d4176c8 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -180,7 +180,7 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) /* The retry count may be at most one more than the retry limit, which * accounts for the final check for a PUBACK. */ IotMqtt_Assert( pOperation->u.operation.periodic.retry.count == - pOperation->u.operation.periodic.retry.limit + 1 ); + pOperation->u.operation.periodic.retry.limit + 1U ); IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", pMqttConnection, @@ -191,7 +191,7 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) } else { - if( pOperation->u.operation.periodic.retry.count == 1 ) + if( pOperation->u.operation.periodic.retry.count == 1U ) { /* The DUP flag should always be set on the first retry. */ setDup = true; @@ -263,14 +263,14 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) scheduleDelay = pOperation->u.operation.periodic.retry.nextPeriodMs; /* Double the retry period, subject to a ceiling value. */ - pOperation->u.operation.periodic.retry.nextPeriodMs *= 2; + pOperation->u.operation.periodic.retry.nextPeriodMs *= 2U; if( pOperation->u.operation.periodic.retry.nextPeriodMs > IOT_MQTT_RETRY_MS_CEILING ) { pOperation->u.operation.periodic.retry.nextPeriodMs = IOT_MQTT_RETRY_MS_CEILING; } - /* In some implementations IotLog() maps to C standard printing API + /* In some implementations IotLogDebug() maps to C standard printing API * that need specific primitive types for format specifiers. Also * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ @@ -283,7 +283,7 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) ( unsigned long ) scheduleDelay ); /* Check if this is the first retry. */ - firstRetry = ( pOperation->u.operation.periodic.retry.count == 1 ); + firstRetry = ( pOperation->u.operation.periodic.retry.count == 1U ); /* On the first retry, the PUBLISH will be moved from the pending processing * list to the pending responses list. Lock the connection references mutex @@ -388,7 +388,7 @@ static bool _completePendingSend( _mqttOperation_t * pOperation, waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; /* Check if this operation should be scheduled for retransmission. */ - if( pOperation->u.operation.periodic.retry.limit > 0 ) + if( pOperation->u.operation.periodic.retry.limit > 0U ) { if( _scheduleNextRetry( pOperation ) == false ) { @@ -599,18 +599,18 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pOperation->u.operation.jobReference--; - /* In some implementations IotLog() maps to C standard printing API + /* In some implementations IotLogDebug() maps to C standard printing API * that need specific primitive types for format specifiers. Also * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" - " from %ld to %ld.", + " from %d to %d.", pMqttConnection, IotMqtt_OperationType( pOperation->u.operation.type ), pOperation, - ( long ) ( pOperation->u.operation.jobReference + 1 ), - ( long ) ( pOperation->u.operation.jobReference ) ); + ( int ) ( pOperation->u.operation.jobReference + 1 ), + ( int ) ( pOperation->u.operation.jobReference ) ); /* The job reference count must be 0 or 1 after the decrement. */ IotMqtt_Assert( ( pOperation->u.operation.jobReference == 0 ) || @@ -732,7 +732,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, /* Check that keep-alive interval is valid. The MQTT spec states its maximum * value is 65,535 seconds. */ - IotMqtt_Assert( pPingreqOperation->u.operation.periodic.ping.keepAliveMs <= 65535000 ); + IotMqtt_Assert( pPingreqOperation->u.operation.periodic.ping.keepAliveMs <= 65535000U ); /* Only two values are valid for the next keep alive job delay. */ IotMqtt_Assert( ( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == @@ -767,7 +767,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, swapStatus = Atomic_CompareAndSwap_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), 1, 0 ); - IotMqtt_Assert( swapStatus == 1 ); + IotMqtt_Assert( swapStatus == 1U ); /* Set the period for scheduling a PINGRESP check. */ pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; @@ -781,7 +781,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { IotLogDebug( "(MQTT connection %p) Checking for PINGRESP.", pMqttConnection ); - if( Atomic_Add_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), 0 ) == 0 ) + if( Atomic_Add_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), 0U ) == 0U ) { IotLogDebug( "(MQTT connection %p) PINGRESP was received.", pMqttConnection ); @@ -819,7 +819,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { - /* In some implementations IotLog() maps to a C standard printing API + /* In some implementations IotLogDebug() maps to a C standard printing API * that need specific primitive types for format specifiers. Also, * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ @@ -910,14 +910,14 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, /* The given operation must have an allocated packet and be waiting for a status. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize != 0 ); + IotMqtt_Assert( pOperation->u.operation.packetSize != 0U ); IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); /* Check if this operation is waitable. */ waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; /* Check PUBLISH retry counts and limits. */ - if( pOperation->u.operation.periodic.retry.limit > 0 ) + if( pOperation->u.operation.periodic.retry.limit > 0U ) { if( _checkRetryLimit( pOperation ) == false ) { @@ -1088,7 +1088,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, "with packet identifier %hu.", pMqttConnection, IotMqtt_OperationType( type ), - ( pPacketIdentifier == NULL ) ? 0 : *pPacketIdentifier ); + ( pPacketIdentifier == NULL ) ? 0U : *pPacketIdentifier ); /* Find and remove the first matching element in the list. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -1106,7 +1106,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, /* Check if the matched operation is a PUBLISH with retry. If it is, cancel * the retry job. */ - if( pResult->u.operation.periodic.retry.limit > 0 ) + if( pResult->u.operation.periodic.retry.limit > 0U ) { taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, pResult->job, @@ -1131,17 +1131,17 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { ( pResult->u.operation.jobReference )++; - /* In some implementations IotLog() maps to C standard printing API + /* In some implementations IotLogDebug() maps to C standard printing API * that need specific primitive types for format specifiers. Also * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", + IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %d to %d.", pMqttConnection, IotMqtt_OperationType( type ), pResult, - ( long int ) ( pResult->u.operation.jobReference - 1 ), - ( long int ) ( pResult->u.operation.jobReference ) ); + ( int ) ( pResult->u.operation.jobReference - 1 ), + ( int ) ( pResult->u.operation.jobReference ) ); } } } diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index d51c9f42d6..7104f160f5 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -144,13 +144,13 @@ /* * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2 ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ -#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0 ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ /* * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. */ -#define MQTT_PACKET_DISCONNECT_SIZE ( 2 ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ +#define MQTT_PACKET_DISCONNECT_SIZE ( 2U ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ /* Username for metrics with AWS IoT. */ #if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 @@ -424,17 +424,17 @@ static size_t _remainingLengthEncodedSize( size_t length ) * The values below are taken from the MQTT 3.1.1 spec. */ /* 1 byte is needed to encode lengths between 0 and 127. */ - if( length < 128 ) + if( length < 128U ) { encodedSize = 1; } /* 2 bytes are needed to encode lengths between 128 and 16,383. */ - else if( length < 16384 ) + else if( length < 16384U ) { encodedSize = 2; } /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ - else if( length < 2097152 ) + else if( length < 2097152U ) { encodedSize = 3; } @@ -457,11 +457,11 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { - lengthByte = ( uint8_t ) ( length % 128U ); + lengthByte = length % 128U; length = length / 128U; /* Set the high bit of this byte, indicating that there's more data. */ - if( length > 0 ) + if( length > 0U ) { UINT8_SET_BIT( lengthByte, 7 ); } @@ -469,7 +469,7 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /* Output a single encoded byte. */ *pLengthEnd = lengthByte; pLengthEnd++; - } while( length > 0 ); + } while( length > 0U ); return pLengthEnd; } @@ -519,7 +519,7 @@ static uint8_t * _encodeUserName( uint8_t * pBuffer, { /* Only include metrics if it will fit within the encoding * standard. */ - if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= UINT16_MAX ) + if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) { /* Write the high byte of the combined length. */ *( pBuffer++ ) = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + @@ -596,8 +596,8 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - connectPacketSize += AWS_IOT_METRICS_USERNAME_LENGTH + - pConnectInfo->userNameLength + sizeof( uint16_t ); + connectPacketSize += ( size_t ) ( AWS_IOT_METRICS_USERNAME_LENGTH + + pConnectInfo->userNameLength + ( uint16_t ) ( sizeof( uint16_t ) ) ); encodedUserName = true; #endif } @@ -620,7 +620,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, /* Calculate the full size of the MQTT CONNECT packet by adding the size of * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ - connectPacketSize += 1 + _remainingLengthEncodedSize( connectPacketSize ); + connectPacketSize += 1U + _remainingLengthEncodedSize( connectPacketSize ); /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) @@ -658,7 +658,7 @@ static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, /* Calculate the maximum allowed size of the payload for the given parameters. * This calculation excludes the "Remaining length" encoding, whose size is not * yet known. */ - payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1; + payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1U; /* Ensure that the given payload fits within the calculated limit. */ if( pPublishInfo->payloadLength > payloadLimit ) @@ -686,7 +686,7 @@ static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, * size of the PUBLISH packet. */ *pRemainingLength = publishPacketSize; - publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); + publishPacketSize += 1U + _remainingLengthEncodedSize( publishPacketSize ); *pPacketSize = publishPacketSize; } } @@ -722,7 +722,7 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /* Only SUBSCRIBE packets include the QoS. */ if( type == IOT_MQTT_SUBSCRIBE ) { - subscriptionPacketSize += 1; + subscriptionPacketSize += 1U; } } @@ -740,7 +740,7 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /* Calculate the full size of the subscription packet by adding the size of the * "Remaining length" field plus 1 byte for the "Packet type" field. Set the * pPacketSize output parameter. */ - subscriptionPacketSize += 1 + _remainingLengthEncodedSize( subscriptionPacketSize ); + subscriptionPacketSize += 1U + _remainingLengthEncodedSize( subscriptionPacketSize ); *pPacketSize = subscriptionPacketSize; } @@ -872,7 +872,7 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, /* Ensure that the difference between the end and beginning of the buffer * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - pConnectPacket ) == connectPacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pConnectPacket ) ) == connectPacketSize ); /* Print out the serialized CONNECT packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT CONNECT packet:", pConnectPacket, connectPacketSize ); @@ -928,7 +928,7 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, { /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); - IotMqtt_Assert( packetIdentifier != 0 ); + IotMqtt_Assert( packetIdentifier != 0U ); /* Set the packet identifier output parameters. */ *pPacketIdentifier = packetIdentifier; @@ -945,7 +945,7 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, } /* The payload is placed after the packet identifier. */ - if( pPublishInfo->payloadLength > 0 ) + if( pPublishInfo->payloadLength > 0U ) { ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); pBuffer += pPublishInfo->payloadLength; @@ -953,7 +953,7 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, /* Ensure that the difference between the end and beginning of the buffer * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - pPublishPacket ) == publishPacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPublishPacket ) ) == publishPacketSize ); /* Print out the serialized PUBLISH packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPublishPacket, publishPacketSize ); @@ -986,7 +986,7 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0 ); + IotMqtt_Assert( packetIdentifier != 0U ); /* Place the packet identifier into the SUBSCRIBE packet. */ *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); @@ -1007,7 +1007,7 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList /* Ensure that the difference between the end and beginning of the buffer * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - pSubscribePacket ) == subscribePacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pSubscribePacket ) ) == subscribePacketSize ); /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pSubscribePacket, subscribePacketSize ); @@ -1040,7 +1040,7 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _nextPacketIdentifier(); *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0 ); + IotMqtt_Assert( packetIdentifier != 0U ); /* Place the packet identifier into the UNSUBSCRIBE packet. */ *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); @@ -1057,7 +1057,7 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi /* Ensure that the difference between the end and beginning of the buffer * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - pUnsubscribePacket ) == unsubscribePacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pUnsubscribePacket ) ) == unsubscribePacketSize ); /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pUnsubscribePacket, unsubscribePacketSize ); @@ -1164,12 +1164,12 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, /* Check that the "Remaining length" is greater than the minimum. For * QoS 1 or 2, this will be two bytes greater than for QoS due to the * packet identifier. */ - if( pPublish->remainingLength < qos0Minimum + 2 ) + if( pPublish->remainingLength < qos0Minimum + 2U ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum + 2 ); + qos0Minimum + 2U ); status = IOT_MQTT_BAD_RESPONSE; } @@ -1204,7 +1204,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { - if( multiplier > 2097152 ) /* 128 ^ 3 */ + if( multiplier > 2097152U ) /* 128 ^ 3 */ { remainingLength = MQTT_REMAINING_LENGTH_INVALID; break; @@ -1215,8 +1215,8 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, pNetworkInterface, &encodedByte ) == true ) { - remainingLength += ( encodedByte & 0x7FU ) * multiplier; - multiplier *= 128; + remainingLength += ( size_t ) ( encodedByte & 0x7FU ) * multiplier; + multiplier *= 128U; bytesDecoded++; } else @@ -1225,7 +1225,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, break; } } - } while( ( encodedByte & 0x80U ) != 0 ); + } while( ( encodedByte & 0x80U ) != 0U ); /* Check that the decoded remaining length conforms to the MQTT specification. */ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) @@ -1239,7 +1239,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, else { /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4 ); + IotMqtt_Assert( bytesDecoded <= 4U ); } } @@ -1257,7 +1257,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { - if( multiplier > 2097152 ) /* 128 ^ 3 */ + if( multiplier > 2097152U ) /* 128 ^ 3 */ { remainingLength = MQTT_REMAINING_LENGTH_INVALID; break; @@ -1266,8 +1266,8 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, { if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) { - remainingLength += ( encodedByte & 0x7FU ) * multiplier; - multiplier *= 128; + remainingLength += ( size_t ) ( encodedByte & 0x7FU ) * multiplier; + multiplier *= 128U; bytesDecoded++; } else @@ -1276,7 +1276,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, break; } } - } while( ( encodedByte & 0x80U ) != 0 ); + } while( ( encodedByte & 0x80U ) != 0U ); /* Check that the decoded remaining length conforms to the MQTT specification. */ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) @@ -1290,7 +1290,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, else { /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4 ); + IotMqtt_Assert( bytesDecoded <= 4U ); } } @@ -1416,7 +1416,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the * "Session Present" bit is set. */ - if( pRemainingData[ 1 ] != 0 ) + if( pRemainingData[ 1 ] != 0U ) { status = IOT_MQTT_BAD_RESPONSE; goto cleanup; @@ -1430,7 +1430,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) } /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ - if( pRemainingData[ 1 ] > 5 ) + if( pRemainingData[ 1 ] > 5U ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1451,7 +1451,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) #endif /* A nonzero CONNACK response code means the connection was refused. */ - if( pRemainingData[ 1 ] > 0 ) + if( pRemainingData[ 1 ] > 0U ) { status = IOT_MQTT_SERVER_REFUSED; goto cleanup; @@ -1669,7 +1669,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) "Packet identifier %hu.", pPublish->packetIdentifier ); /* Packet identifier cannot be 0. */ - if( pPublish->packetIdentifier == 0 ) + if( pPublish->packetIdentifier == 0U ) { status = IOT_MQTT_BAD_RESPONSE; goto cleanup; @@ -1685,7 +1685,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) } else { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2U * sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } @@ -1760,7 +1760,7 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) "Packet identifier %hu.", pPuback->packetIdentifier ); /* Packet identifier cannot be 0. */ - if( pPuback->packetIdentifier == 0 ) + if( pPuback->packetIdentifier == 0U ) { status = IOT_MQTT_BAD_RESPONSE; goto cleanup; @@ -1856,7 +1856,7 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) /* A SUBACK must have a remaining length of at least 3 to accommodate the * packet identifier and at least 1 return code. */ - if( remainingLength < 3 ) + if( remainingLength < 3U ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1978,7 +1978,7 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); /* Packet identifier cannot be 0. */ - if( pUnsuback->packetIdentifier == 0 ) + if( pUnsuback->packetIdentifier == 0U ) { status = IOT_MQTT_BAD_RESPONSE; goto cleanup; @@ -2121,7 +2121,7 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne goto cleanup; } - if( ( pConnectInfo->clientIdentifierLength == 0 ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) { IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); status = IOT_MQTT_BAD_PARAMETER; @@ -2171,7 +2171,7 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn goto cleanup; } - if( ( pConnectInfo->clientIdentifierLength == 0 ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) { IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); status = IOT_MQTT_BAD_PARAMETER; @@ -2220,7 +2220,7 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, goto cleanup; } - if( subscriptionCount == 0 ) + if( subscriptionCount == 0U ) { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; @@ -2273,7 +2273,7 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr goto cleanup; } - if( subscriptionCount == 0 ) + if( subscriptionCount == 0U ) { IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; @@ -2315,7 +2315,7 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo goto cleanup; } - if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) + if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); status = IOT_MQTT_BAD_PARAMETER; @@ -2368,7 +2368,7 @@ IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, goto cleanup; } - if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) + if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { IotLogError( "IotMqtt_SerializePublish() called with no topic." ); status = IOT_MQTT_BAD_PARAMETER; @@ -2412,7 +2412,7 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs goto cleanup; } - if( subscriptionCount == 0 ) + if( subscriptionCount == 0U ) { IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 5908f95541..440eeabe3e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -156,24 +156,24 @@ static bool _matchEndWildcards( const char * pTopicFilter, /* Determine if the last character is reached for both topic name and topic * filter for the '#' wildcard. */ - endChar = ( nameIndex == topicNameLength - 1 ) && ( filterIndex == topicFilterLength - 3 ); + endChar = ( nameIndex == topicNameLength - 1U ) && ( filterIndex == topicFilterLength - 3U ); if( endChar == true ) { /* Determine if the topic filter ends with the '#' wildcard. */ - status = ( pTopicFilter[ filterIndex + 1 ] == '/' ) && ( pTopicFilter[ filterIndex + 2 ] == '#' ); + status = ( pTopicFilter[ filterIndex + 1U ] == '/' ) && ( pTopicFilter[ filterIndex + 2U ] == '#' ); } if( status == false ) { /* Determine if the last character is reached for both topic name and topic * filter for the '+' wildcard. */ - endChar = ( nameIndex == topicNameLength - 1 ) && ( filterIndex == topicFilterLength - 2 ); + endChar = ( nameIndex == topicNameLength - 1U ) && ( filterIndex == topicFilterLength - 2U ); if( endChar == true ) { /* Filter "sport/+" also matches the "sport/" but not "sport". */ - status = ( pTopicFilter[ filterIndex + 1 ] == '+' ); + status = ( pTopicFilter[ filterIndex + 1U ] == '+' ); } } diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index bbba340c1f..8bb8dbb9d8 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -150,7 +150,7 @@ static bool _validateString( const char * pString, goto cleanup; } - if( length == 0 ) + if( length == 0U ) { status = false; goto cleanup; @@ -192,7 +192,7 @@ static bool _validatePublish( bool awsIotMqttMode, goto cleanup; } - if( pPublishInfo->payloadLength != 0 ) + if( pPublishInfo->payloadLength != 0U ) { if( pPublishInfo->payloadLength > maximumPayloadLength ) { @@ -225,9 +225,9 @@ static bool _validatePublish( bool awsIotMqttMode, } /* Check the retry parameters. */ - if( pPublishInfo->retryLimit > 0 ) + if( pPublishInfo->retryLimit > 0U ) { - if( pPublishInfo->retryMs == 0 ) + if( pPublishInfo->retryMs == 0U ) { IotLogError( "Publish retry time must be positive." ); @@ -281,7 +281,7 @@ static bool _validateListSize( bool awsIotMqttMode, goto cleanup; } - if( listSize == 0 ) + if( listSize == 0U ) { IotLogError( "Empty subscription list." ); @@ -391,9 +391,9 @@ static bool _validateWildcardPlus( uint16_t index, bool status = true; /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ - if( index > 0 ) + if( index > 0U ) { - if( pSubscription->pTopicFilter[ index - 1 ] != '/' ) + if( pSubscription->pTopicFilter[ index - 1U ] != '/' ) { IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", pSubscription->topicFilterLength, @@ -405,9 +405,9 @@ static bool _validateWildcardPlus( uint16_t index, } /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ - if( index < pSubscription->topicFilterLength - 1 ) + if( index < pSubscription->topicFilterLength - 1U ) { - if( pSubscription->pTopicFilter[ index + 1 ] != '/' ) + if( pSubscription->pTopicFilter[ index + 1U ] != '/' ) { IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", pSubscription->topicFilterLength, @@ -431,7 +431,7 @@ static bool _validateWildcardHash( uint16_t index, bool status = true; /* '#' must be the last character in the filter. */ - if( index != pSubscription->topicFilterLength - 1 ) + if( index != pSubscription->topicFilterLength - 1U ) { IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", pSubscription->topicFilterLength, @@ -442,9 +442,9 @@ static bool _validateWildcardHash( uint16_t index, } /* Unless '#' is standalone, it must be preceded by '/'. */ - if( pSubscription->topicFilterLength > 1 ) + if( pSubscription->topicFilterLength > 1U ) { - if( pSubscription->pTopicFilter[ index - 1 ] != '/' ) + if( pSubscription->pTopicFilter[ index - 1U ] != '/' ) { IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", pSubscription->topicFilterLength, @@ -488,7 +488,7 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) /* Check for a zero-length client identifier. Zero-length client identifiers * are not allowed with clean sessions. */ - if( pConnectInfo->clientIdentifierLength == 0 ) + if( pConnectInfo->clientIdentifierLength == 0U ) { IotLogWarn( "A zero-length client identifier was provided." ); @@ -591,7 +591,7 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, status = false; } - else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) + else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0U ) { IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index ee69ebc60c..e481ed5c06 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -217,10 +217,10 @@ #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) #endif #ifndef IOT_MQTT_RESPONSE_WAIT_MS - #define IOT_MQTT_RESPONSE_WAIT_MS ( 1000 ) + #define IOT_MQTT_RESPONSE_WAIT_MS ( 1000U ) #endif #ifndef IOT_MQTT_RETRY_MS_CEILING - #define IOT_MQTT_RETRY_MS_CEILING ( 60000 ) + #define IOT_MQTT_RETRY_MS_CEILING ( 60000U ) #endif /** @endcond */ diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h index b26d50e544..8add2867a5 100644 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ b/ports/common/include/atomic/iot_atomic_gcc.h @@ -50,14 +50,17 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes uint32_t swapped = 0; /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + * requires an int parameter for this routine. + * This routine is built into gcc and defined to return a bool + * type. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_10_4_violation] */ if( __atomic_compare_exchange( pDestination, &comparand, &newValue, false, __ATOMIC_SEQ_CST, - __ATOMIC_SEQ_CST ) == true ) + __ATOMIC_SEQ_CST ) == ( ( bool ) ( true ) ) ) { swapped = 1; } @@ -94,12 +97,15 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pD { uint32_t swapped = 0; + /* This routine is built into gcc and defined to return a bool + * type. */ + /* coverity[misra_c_2012_rule_10_4_violation] */ if( __atomic_compare_exchange( pDestination, &pComparand, &pNewValue, false, __ATOMIC_SEQ_CST, - __ATOMIC_SEQ_CST ) == true ) + __ATOMIC_SEQ_CST ) == ( ( bool ) ( true ) ) ) { swapped = 1; } From d8fdbb2cf1702cca284f1d346bf7ef3161095269 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Tue, 21 Jan 2020 13:57:49 -0800 Subject: [PATCH 375/844] Remove goto statements in aws_iot_jobs_serialize.c (#731) --- .../aws/jobs/src/aws_iot_jobs_serialize.c | 519 +++++++++--------- 1 file changed, 258 insertions(+), 261 deletions(-) diff --git a/libraries/aws/jobs/src/aws_iot_jobs_serialize.c b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c index 2e9fcb53e5..0b4338abcc 100644 --- a/libraries/aws/jobs/src/aws_iot_jobs_serialize.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c @@ -35,9 +35,6 @@ /* Jobs internal include. */ #include "private/aws_iot_jobs_internal.h" -/* Error handling include. */ -#include "iot_error.h" - /* JSON utilities include. */ #include "aws_iot_doc_parser.h" @@ -469,7 +466,7 @@ static size_t _appendClientToken( char * pBuffer, static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, _jobsOperation_t * pOperation ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; char * pJobsRequest = NULL; size_t copyOffset = 0; size_t requestLength = MINIMUM_REQUEST_LENGTH; @@ -492,33 +489,34 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo if( pJobsRequest == NULL ) { IotLogError( "No memory for Jobs GET PENDING request." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + status = AWS_IOT_JOBS_NO_MEMORY; + } + else + { + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); + + /* Construct the request JSON, which consists of just a clientToken key. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); + + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; + + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); + + IotLogDebug( "Jobs GET PENDING request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); } - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON, which consists of just a clientToken key. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; - - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); - - IotLogDebug( "Jobs GET PENDING request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -527,7 +525,7 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ const AwsIotJobsUpdateInfo_t * pUpdateInfo, _jobsOperation_t * pOperation ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; char * pJobsRequest = NULL; size_t copyOffset = 0; size_t requestLength = MINIMUM_REQUEST_LENGTH; @@ -586,54 +584,55 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ if( pJobsRequest == NULL ) { IotLogError( "No memory for Jobs START NEXT request." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + status = AWS_IOT_JOBS_NO_MEMORY; } + else + { + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + /* Construct the request JSON. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - /* Add status details if present. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) - { - copyOffset = _appendStatusDetails( pJobsRequest, - copyOffset, - pUpdateInfo->pStatusDetails, - pUpdateInfo->statusDetailsLength ); - } + /* Add status details if present. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + copyOffset = _appendStatusDetails( pJobsRequest, + copyOffset, + pUpdateInfo->pStatusDetails, + pUpdateInfo->statusDetailsLength ); + } - /* Add step timeout if present. */ - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) - { - copyOffset = _appendStepTimeout( pJobsRequest, - copyOffset, - pStepTimeout, - stepTimeoutLength ); - } + /* Add step timeout if present. */ + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + copyOffset = _appendStepTimeout( pJobsRequest, + copyOffset, + pStepTimeout, + stepTimeoutLength ); + } - /* Add client token. */ - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); + /* Add client token. */ + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); - IotLogDebug( "Jobs START NEXT request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); + IotLogDebug( "Jobs START NEXT request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -643,7 +642,7 @@ static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t bool includeJobDocument, _jobsOperation_t * pOperation ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; char * pJobsRequest = NULL; size_t copyOffset = 0; size_t requestLength = MINIMUM_REQUEST_LENGTH; @@ -695,55 +694,56 @@ static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t if( pJobsRequest == NULL ) { IotLogError( "No memory for Jobs DESCRIBE request." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + status = AWS_IOT_JOBS_NO_MEMORY; } + else + { + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + /* Construct the request JSON. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - /* Add the "include job document" flag if false. */ - if( includeJobDocument == false ) - { - copyOffset = _appendFlag( pJobsRequest, - copyOffset, - INCLUDE_JOB_DOCUMENT_KEY, - INCLUDE_JOB_DOCUMENT_KEY_LENGTH, - false ); - } + /* Add the "include job document" flag if false. */ + if( includeJobDocument == false ) + { + copyOffset = _appendFlag( pJobsRequest, + copyOffset, + INCLUDE_JOB_DOCUMENT_KEY, + INCLUDE_JOB_DOCUMENT_KEY_LENGTH, + false ); + } - /* Add the length of the execution number if present. */ - if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) - { - copyOffset = _appendExecutionNumber( pJobsRequest, - copyOffset, - pExecutionNumber, - ( size_t ) executionNumberLength ); - } + /* Add the length of the execution number if present. */ + if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) + { + copyOffset = _appendExecutionNumber( pJobsRequest, + copyOffset, + pExecutionNumber, + ( size_t ) executionNumberLength ); + } - /* Add client token. */ - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); + /* Add client token. */ + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); - IotLogDebug( "Jobs DESCRIBE request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); + IotLogDebug( "Jobs DESCRIBE request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -752,7 +752,7 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * const AwsIotJobsUpdateInfo_t * pUpdateInfo, _jobsOperation_t * pOperation ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; char * pJobsRequest = NULL; size_t copyOffset = 0; size_t requestLength = MINIMUM_REQUEST_LENGTH; @@ -897,100 +897,101 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * if( pJobsRequest == NULL ) { IotLogError( "No memory for Jobs UPDATE request." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + status = AWS_IOT_JOBS_NO_MEMORY; } - - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - - /* Add the status. */ - APPEND_STRING( pJobsRequest, copyOffset, STATUS_KEY, STATUS_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); - APPEND_STRING( pJobsRequest, copyOffset, pStatus, statusLength ); - APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); - - /* Add status details if present. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + else { - copyOffset = _appendStatusDetails( pJobsRequest, - copyOffset, - pUpdateInfo->pStatusDetails, - pUpdateInfo->statusDetailsLength ); - } + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); - /* Add expected version. */ - if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION ) - { - APPEND_STRING( pJobsRequest, - copyOffset, - EXPECTED_VERSION_KEY, - EXPECTED_VERSION_KEY_LENGTH ); + /* Construct the request JSON. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + + /* Add the status. */ + APPEND_STRING( pJobsRequest, copyOffset, STATUS_KEY, STATUS_KEY_LENGTH ); APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); - APPEND_STRING( pJobsRequest, copyOffset, pExpectedVersion, expectedVersionLength ); + APPEND_STRING( pJobsRequest, copyOffset, pStatus, statusLength ); APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); - } - /* Add execution number. */ - if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) - { - copyOffset = _appendExecutionNumber( pJobsRequest, - copyOffset, - pExecutionNumber, - executionNumberLength ); - } + /* Add status details if present. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + copyOffset = _appendStatusDetails( pJobsRequest, + copyOffset, + pUpdateInfo->pStatusDetails, + pUpdateInfo->statusDetailsLength ); + } - /* Add flags if not default values. */ - if( pUpdateInfo->includeJobExecutionState == true ) - { - copyOffset = _appendFlag( pJobsRequest, - copyOffset, - INCLUDE_JOB_EXECUTION_STATE_KEY, - INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH, - true ); - } + /* Add expected version. */ + if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION ) + { + APPEND_STRING( pJobsRequest, + copyOffset, + EXPECTED_VERSION_KEY, + EXPECTED_VERSION_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); + APPEND_STRING( pJobsRequest, copyOffset, pExpectedVersion, expectedVersionLength ); + APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); + } - if( pUpdateInfo->includeJobDocument == true ) - { - copyOffset = _appendFlag( pJobsRequest, - copyOffset, - INCLUDE_JOB_DOCUMENT_KEY, - INCLUDE_JOB_DOCUMENT_KEY_LENGTH, - true ); - } + /* Add execution number. */ + if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) + { + copyOffset = _appendExecutionNumber( pJobsRequest, + copyOffset, + pExecutionNumber, + executionNumberLength ); + } - /* Add step timeout if provided. */ - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) - { - copyOffset = _appendStepTimeout( pJobsRequest, - copyOffset, - pStepTimeout, - stepTimeoutLength ); - } + /* Add flags if not default values. */ + if( pUpdateInfo->includeJobExecutionState == true ) + { + copyOffset = _appendFlag( pJobsRequest, + copyOffset, + INCLUDE_JOB_EXECUTION_STATE_KEY, + INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH, + true ); + } + + if( pUpdateInfo->includeJobDocument == true ) + { + copyOffset = _appendFlag( pJobsRequest, + copyOffset, + INCLUDE_JOB_DOCUMENT_KEY, + INCLUDE_JOB_DOCUMENT_KEY_LENGTH, + true ); + } - /* Add the client token. */ - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); + /* Add step timeout if provided. */ + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + copyOffset = _appendStepTimeout( pJobsRequest, + copyOffset, + pStepTimeout, + stepTimeoutLength ); + } - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); + /* Add the client token. */ + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; - IotLogDebug( "Jobs UPDATE request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); + IotLogDebug( "Jobs UPDATE request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + } + + return status; } /*-----------------------------------------------------------*/ @@ -998,7 +999,7 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + AwsIotJobsError_t status = AWS_IOT_JOBS_BAD_RESPONSE; const char * pCode = NULL; size_t codeLength = 0; @@ -1008,106 +1009,102 @@ static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, CODE_KEY, CODE_KEY_LENGTH, &pCode, - &codeLength ) == false ) + &codeLength ) == true ) { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_RESPONSE ); - } - - /* Match the JSON error code to a Jobs return value. Assume invalid status - * unless matched.*/ - status = AWS_IOT_JOBS_BAD_RESPONSE; + switch( codeLength ) + { + /* InvalidJson */ + case 13: - switch( codeLength ) - { - /* InvalidJson */ - case 13: + if( strncmp( "\"InvalidJson\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_JSON; + } - if( strncmp( "\"InvalidJson\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_JSON; - } + break; - break; + /* InvalidTopic */ + case 14: - /* InvalidTopic */ - case 14: + if( strncmp( "\"InvalidTopic\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_TOPIC; + } - if( strncmp( "\"InvalidTopic\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_TOPIC; - } + break; - break; + /* InternalError */ + case 15: - /* InternalError */ - case 15: + if( strncmp( "\"InternalError\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INTERNAL_ERROR; + } - if( strncmp( "\"InternalError\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INTERNAL_ERROR; - } + break; - break; + /* InvalidRequest */ + case 16: - /* InvalidRequest */ - case 16: + if( strncmp( "\"InvalidRequest\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_REQUEST; + } - if( strncmp( "\"InvalidRequest\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_REQUEST; - } + break; - break; + /* VersionMismatch */ + case 17: - /* VersionMismatch */ - case 17: + if( strncmp( "\"VersionMismatch\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_VERSION_MISMATCH; + } - if( strncmp( "\"VersionMismatch\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_VERSION_MISMATCH; - } + break; - break; + /* ResourceNotFound, RequestThrottled */ + case 18: - /* ResourceNotFound, RequestThrottled */ - case 18: + if( strncmp( "\"ResourceNotFound\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_NOT_FOUND; + } + else if( strncmp( "\"RequestThrottled\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_THROTTLED; + } - if( strncmp( "\"ResourceNotFound\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_NOT_FOUND; - } - else if( strncmp( "\"RequestThrottled\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_THROTTLED; - } + break; - break; + /* TerminalStateReached */ + case 22: - /* TerminalStateReached */ - case 22: + if( strncmp( "\"TerminalStateReached\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_TERMINAL_STATE; + } - if( strncmp( "\"TerminalStateReached\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_TERMINAL_STATE; - } + break; - break; + /* InvalidStateTransition */ + case 24: - /* InvalidStateTransition */ - case 24: + if( strncmp( "\"InvalidStateTransition\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_STATE; + } - if( strncmp( "\"InvalidStateTransition\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_STATE; - } + break; - break; + default: - default: - break; + /* Assume bad response status unless matched.*/ + break; + } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ From 49b98a66f9121d9e37fafc9c1e6be2f3467413a3 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Tue, 21 Jan 2020 15:55:00 -0800 Subject: [PATCH 376/844] Update iot_atomic_gcc.h to annotate Misra Rule 1.2. (#723) * Update iot_atomic_gcc.h to annotate Misra Rule 1.2. --- ports/common/include/atomic/iot_atomic_gcc.h | 36 +++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h index 8add2867a5..6611f923d7 100644 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ b/ports/common/include/atomic/iot_atomic_gcc.h @@ -36,6 +36,10 @@ /** * @brief GCC function attribute to always inline a function. */ + +/* This header file is intended to be used with only the gcc compiler + * which will have the __attribute__ language extension available. */ +/* coverity[misra_banned_extension_origin] */ #define FORCE_INLINE inline __attribute__( ( always_inline ) ) /*---------------- Swap and compare-and-swap ------------------*/ @@ -49,8 +53,8 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes { uint32_t swapped = 0; - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. * This routine is built into gcc and defined to return a bool * type. */ /* coverity[misra_c_2012_directive_4_6_violation] */ @@ -78,8 +82,8 @@ static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, { void * pOldValue = NULL; - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); @@ -121,8 +125,8 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pD static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, uint32_t addend ) { - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ return ( uint32_t ) ( __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ) ); } @@ -135,8 +139,8 @@ static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, uint32_t subtrahend ) { - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ) ); } @@ -169,8 +173,8 @@ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ return ( uint32_t ) ( __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ) ); } @@ -183,8 +187,8 @@ static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ return ( uint32_t ) ( __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ) ); } @@ -197,8 +201,8 @@ static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ return ( uint32_t ) ( __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ) ); } @@ -211,8 +215,8 @@ static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, uint32_t mask ) { - /* This header file is used with only the gcc compiler which - * requires an int parameter for this routine. */ + /* This header file is intended to be used with only the gcc compiler + * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ return ( uint32_t ) ( __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ) ); } From 669046c53c96798955a301b7e720a7ed3a83fee8 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 22 Jan 2020 11:34:25 -0800 Subject: [PATCH 377/844] Fix MISRA 15.1 part 3 (#729) * fix: MISRA 15.1 (goto) violations - part 3. Changes made to iot_mqtt_serialize.c and iot_mqtt_validate.c. --- .../standard/mqtt/src/iot_mqtt_serialize.c | 866 +++++++++--------- .../standard/mqtt/src/iot_mqtt_validate.c | 480 +++++----- 2 files changed, 694 insertions(+), 652 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 7104f160f5..007b5542bb 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -381,6 +381,19 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, IotMqttQos_t qos, size_t qos0Minimum ); +/** + * @brief Process incoming publish flags. + * + * @param[in] publishFlags Incoming publish flags. + * @param[in, out] pOutput Pointer to #IotMqttPublishInfo_t struct. + * where output will be written. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE. + */ + +static IotMqttError_t _processIncomingPublishFlags( const uint8_t publishFlags, + IotMqttPublishInfo_t * pOutput ); + /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -1178,6 +1191,74 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, return status; } +static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, + IotMqttPublishInfo_t * pOutput ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( pOutput == NULL ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + /* Check for QoS 2. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) + { + /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad QoS: 3." ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + pOutput->qos = IOT_MQTT_QOS_2; + } + } + /* Check for QoS 1. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + pOutput->qos = IOT_MQTT_QOS_1; + } + /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ + else + { + pOutput->qos = IOT_MQTT_QOS_0; + } + + if( status == IOT_MQTT_SUCCESS ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS is %d.", pOutput->qos ); + + /* Parse the Retain bit. */ + pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Retain bit is %d.", pOutput->retain ); + + /* Parse the DUP bit. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 1." ); + } + else + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 0." ); + } + } + + return status; +} + /*-----------------------------------------------------------*/ uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, @@ -1377,12 +1458,11 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) pConnack->type ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } /* According to MQTT 3.1.1, the second byte of CONNACK must specify a * "Remaining length" of 2. */ - if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + else if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, @@ -1390,75 +1470,75 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) MQTT_PACKET_CONNACK_REMAINING_LENGTH ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } /* Check the reserved bits in CONNACK. The high 7 bits of the second byte * in CONNACK must be 0. */ - if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) + else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Reserved bits in CONNACK incorrect." ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - - /* Determine if the "Session Present" bit it set. This is the lowest bit of - * the second byte in CONNACK. */ - if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + else { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit set." ); + /* Determine if the "Session Present" bit is set. This is the lowest bit of + * the second byte in CONNACK. */ + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit set." ); - /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the - * "Session Present" bit is set. */ - if( pRemainingData[ 1 ] != 0U ) + /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pRemainingData[ 1 ] != 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + else { - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit not set." ); } } - else - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit not set." ); - } - /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ - if( pRemainingData[ 1 ] > 5U ) + if( status == IOT_MQTT_SUCCESS ) { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ); - - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ + if( pRemainingData[ 1 ] > 5U ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ); - /* Print the appropriate message for the CONNACK response code if logs are - * enabled. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "%s", - pConnackResponses[ pRemainingData[ 1 ] ] ); - #endif + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Print the appropriate message for the CONNACK response code if logs are + * enabled. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "%s", + pConnackResponses[ pRemainingData[ 1 ] ] ); + #endif - /* A nonzero CONNACK response code means the connection was refused. */ - if( pRemainingData[ 1 ] > 0U ) - { - status = IOT_MQTT_SERVER_REFUSED; - goto cleanup; + /* A nonzero CONNACK response code means the connection was refused. */ + if( pRemainingData[ 1 ] > 0U ) + { + status = IOT_MQTT_SERVER_REFUSED; + } + } } -cleanup: - return status; } @@ -1567,134 +1647,83 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) /* The flags are the lower 4 bits of the first byte in PUBLISH. */ publishFlags = pPublish->type; - /* Parse the Retain bit. */ - pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Retain bit is %d.", pOutput->retain ); - - /* Check for QoS 2. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) - { - /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad QoS: 3." ); + status = _processIncomingPublishFlags( publishFlags, pOutput ); - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } - - pOutput->qos = IOT_MQTT_QOS_2; - } - /* Check for QoS 1. */ - else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - pOutput->qos = IOT_MQTT_QOS_1; - } - /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ - else + if( status == IOT_MQTT_SUCCESS ) { - pOutput->qos = IOT_MQTT_QOS_0; + /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining + * length of at least 3 to accommodate topic name length (2 bytes) and topic + * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of + * at least 5 for the packet identifier in addition to the topic name length and + * topic name. */ + status = _checkRemainingLength( pPublish, pOutput->qos, MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); } - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS is %d.", pOutput->qos ); - - /* Parse the DUP bit. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) + if( status == IOT_MQTT_SUCCESS ) { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "DUP is 1." ); + /* Extract the topic name starting from the first byte of the variable header. + * The topic name string starts at byte 3 in the variable header. */ + pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); + + /* Sanity checks for topic name length and "Remaining length". The remaining + * length must be at least as large as the variable length header. */ + status = _checkRemainingLength( pPublish, + pOutput->qos, + pOutput->topicNameLength + sizeof( uint16_t ) ); } - else + + if( status == IOT_MQTT_SUCCESS ) { + /* Parse the topic. */ + pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + IotLog( IOT_LOG_DEBUG, &_logHideAll, - "DUP is 0." ); - } + "Topic name length %hu: %.*s", + pOutput->topicNameLength, + pOutput->topicNameLength, + pOutput->pTopicName ); - /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining - * length of at least 3 to accommodate topic name length (2 bytes) and topic - * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of - * at least 5 for the packet identifier in addition to the topic name length and - * topic name. */ - status = _checkRemainingLength( pPublish, pOutput->qos, MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); + /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet + * identifier starts immediately after the topic name. */ + pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; - } - - /* Extract the topic name starting from the first byte of the variable header. - * The topic name string starts at byte 3 in the variable header. */ - pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); + if( pOutput->qos > IOT_MQTT_QOS_0 ) + { + pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); - /* Sanity checks for topic name length and "Remaining length". The remaining - * length must be at least as large as the variable length header. */ - status = _checkRemainingLength( pPublish, - pOutput->qos, - pOutput->topicNameLength + sizeof( uint16_t ) ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pPublish->packetIdentifier ); - if( status != IOT_MQTT_SUCCESS ) - { - goto cleanup; + /* Packet identifier cannot be 0. */ + if( pPublish->packetIdentifier == 0 ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } } - /* Parse the topic. */ - pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic name length %hu: %.*s", - pOutput->topicNameLength, - pOutput->topicNameLength, - pOutput->pTopicName ); - - /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet - * identifier starts immediately after the topic name. */ - pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); - - if( pOutput->qos > IOT_MQTT_QOS_0 ) + if( status == IOT_MQTT_SUCCESS ) { - pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pPublish->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPublish->packetIdentifier == 0U ) + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain + * a packet identifier, but QoS 0 PUBLISH packets do not. */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) { - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh; + } + else + { + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } - } - /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain - * a packet identifier, but QoS 0 PUBLISH packets do not. */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) - { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh; - } - else - { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2U * sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Payload length %hu.", pOutput->payloadLength ); } - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Payload length %hu.", pOutput->payloadLength ); - -cleanup: - return status; } @@ -1749,37 +1778,36 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) MQTT_PACKET_PUBACK_REMAINING_LENGTH ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - - /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ - pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pPuback->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPuback->packetIdentifier == 0U ) + else { - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ + pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); - /* Check that the control packet type is 0x40 (this must be done after the - * packet identifier is parsed). */ - if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) - { - IotLog( IOT_LOG_ERROR, + IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Bad control packet type 0x%02x.", - pPuback->type ); + "Packet identifier %hu.", pPuback->packetIdentifier ); - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + /* Packet identifier cannot be 0. */ + if( pPuback->packetIdentifier == 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Check that the control packet type is 0x40 (this must be done after the + * packet identifier is parsed). */ + if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPuback->type ); -cleanup: + status = IOT_MQTT_BAD_RESPONSE; + } + } + } return status; } @@ -1809,39 +1837,38 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( subscribePacketSize > remainingLength ); - - /* Allocate memory to hold the SUBSCRIBE packet. */ - pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) + else { - IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); - - status = IOT_MQTT_NO_MEMORY; - goto cleanup; - } + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( subscribePacketSize > remainingLength ); - /* Set the output parameters. The remainder of this function always succeeds. */ - *pSubscribePacket = pBuffer; - *pPacketSize = subscribePacketSize; + /* Allocate memory to hold the SUBSCRIBE packet. */ + pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); - /* Serialize subscribe into buffer pointed to by pBuffer */ - _serializeSubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - subscribePacketSize ); + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* Set the output parameters. The remainder of this function always succeeds. */ + *pSubscribePacket = pBuffer; + *pPacketSize = subscribePacketSize; -cleanup: + /* Serialize subscribe into buffer pointed to by pBuffer */ + _serializeSubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + subscribePacketSize ); + } + } return status; } @@ -1863,34 +1890,34 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) "SUBACK cannot have a remaining length less than 3." ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - - /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ - pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pSuback->packetIdentifier ); - - /* Check that the control packet type is 0x90 (this must be done after the - * packet identifier is parsed). */ - if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) + else { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pSuback->type ); + /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ + pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pSuback->packetIdentifier ); - status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), - pVariableHeader + sizeof( uint16_t ), - pSuback ); + /* Check that the control packet type is 0x90 (this must be done after the + * packet identifier is parsed). */ + if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pSuback->type ); -cleanup: + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), + pVariableHeader + sizeof( uint16_t ), + pSuback ); + } + } return status; } @@ -1920,38 +1947,38 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Total size of the unsubscribe packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( unsubscribePacketSize > remainingLength ); - - /* Allocate memory to hold the UNSUBSCRIBE packet. */ - pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) + else { - IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - - status = IOT_MQTT_NO_MEMORY; - goto cleanup; - } + /* Total size of the unsubscribe packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( unsubscribePacketSize > remainingLength ); - /* Set the output parameters. The remainder of this function always succeeds. */ - *pUnsubscribePacket = pBuffer; - *pPacketSize = unsubscribePacketSize; + /* Allocate memory to hold the UNSUBSCRIBE packet. */ + pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); - /* Serialize unsubscribe into buffer pointed to by pBuffer */ - _serializeUnsubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - unsubscribePacketSize ); + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); -cleanup: + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* Set the output parameters. The remainder of this function always succeeds. */ + *pUnsubscribePacket = pBuffer; + *pPacketSize = unsubscribePacketSize; + + /* Serialize unsubscribe into buffer pointed to by pBuffer */ + _serializeUnsubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + unsubscribePacketSize ); + } + } return status; } @@ -1971,37 +1998,37 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - - /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ - pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); - - /* Packet identifier cannot be 0. */ - if( pUnsuback->packetIdentifier == 0U ) + else { - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ + pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pUnsuback->packetIdentifier ); + /* Packet identifier cannot be 0. */ + if( pUnsuback->packetIdentifier == 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } - /* Check that the control packet type is 0xb0 (this must be done after the - * packet identifier is parsed). */ - if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) + if( status == IOT_MQTT_SUCCESS ) { - IotLog( IOT_LOG_ERROR, + IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Bad control packet type 0x%02x.", - pUnsuback->type ); + "Packet identifier %hu.", pUnsuback->packetIdentifier ); - status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; - } + /* Check that the control packet type is 0xb0 (this must be done after the + * packet identifier is parsed). */ + if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pUnsuback->type ); -cleanup: + status = IOT_MQTT_BAD_RESPONSE; + } + } return status; } @@ -2043,11 +2070,9 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) pPingresp->type ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - /* Check the "Remaining length" (second byte) of the received PINGRESP. */ - if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + else if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, @@ -2055,10 +2080,11 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); status = IOT_MQTT_BAD_RESPONSE; - goto cleanup; } - -cleanup: + else + { + /* Empty else MISRA 15.7 */ + } return status; } @@ -2118,40 +2144,35 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne { IotLogError( "IotMqtt_GetConnectPacketSize() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) { IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _connectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) + else if( _connectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) { IotLogError( "Connect packet length exceeds %lu, which is the maximum" " size allowed by MQTT 3.1.1.", MQTT_PACKET_CONNECT_MAX_SIZE ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) + else { - IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", - ( *pRemainingLength ), ( *pPacketSize ) ); - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) + { + IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", + ( *pRemainingLength ), ( *pPacketSize ) ); + status = IOT_MQTT_BAD_PARAMETER; + } } -cleanup: - return status; } @@ -2168,30 +2189,25 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn { IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) { IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( remainingLength > bufferSize ) + else if( remainingLength > bufferSize ) { IotLogError( " Serialize Connect packet remaining length (%lu) exceeds buffer size (%lu)", remainingLength, bufferSize ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - _serializeConnect( pConnectInfo, - remainingLength, - pBuffer, - bufferSize ); - -cleanup: + else + { + _serializeConnect( pConnectInfo, + remainingLength, + pBuffer, + bufferSize ); + } return status; } @@ -2210,48 +2226,40 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) + else if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with unknown type." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( subscriptionCount == 0U ) + else if( subscriptionCount == 0U ) { IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( _subscriptionPacketSize( type, - pSubscriptionList, - subscriptionCount, - pRemainingLength, - pPacketSize ) == false ) + else if( _subscriptionPacketSize( type, + pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize ) == false ) { IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) + else { - IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", - ( *pRemainingLength ), ( *pPacketSize ) ); - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) + { + IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", + ( *pRemainingLength ), ( *pPacketSize ) ); + status = IOT_MQTT_BAD_PARAMETER; + } } -cleanup: - return status; } @@ -2270,32 +2278,27 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr { IotLogError( "IotMqtt_SerializeSubscribe() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( subscriptionCount == 0U ) + else if( subscriptionCount == 0U ) { IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( remainingLength > bufferSize ) + else if( remainingLength > bufferSize ) { IotLogError( " Subscribe packet remaining length (%lu) exceeds buffer size (%lu).", remainingLength, bufferSize ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - _serializeSubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); - -cleanup: + else + { + _serializeSubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); + } return status; } @@ -2312,40 +2315,35 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo { IotLogError( "IotMqtt_GetPublishPacketSize() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _publishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) + else if( _publishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) { IotLogError( "Publish packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Total size of the publish packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) + else { - IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", - ( *pRemainingLength ), ( *pPacketSize ) ); - status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; + /* Total size of the publish packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) + { + IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", + ( *pRemainingLength ), ( *pPacketSize ) ); + status = IOT_MQTT_BAD_PARAMETER; + } } -cleanup: - return status; } @@ -2365,31 +2363,27 @@ IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, { IotLogError( "IotMqtt_SerializePublish() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { IotLogError( "IotMqtt_SerializePublish() called with no topic." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( remainingLength > bufferSize ) + else if( remainingLength > bufferSize ) { IotLogError( "Publish packet remaining length (%lu) exceeds buffer size (%lu).", remainingLength, bufferSize ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - _serializePublish( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - bufferSize ); -cleanup: + else + { + _serializePublish( pPublishInfo, + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + bufferSize ); + } return status; } @@ -2409,31 +2403,27 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs { IotLogError( "IotMqtt_SerializeUnsubscribe() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( subscriptionCount == 0U ) + else if( subscriptionCount == 0U ) { IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( remainingLength > bufferSize ) + else if( remainingLength > bufferSize ) { IotLogError( "Unsubscribe packet remaining length (%lu) exceeds buffer size (%lu).", remainingLength, bufferSize ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - _serializeUnsubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); -cleanup: + else + { + _serializeUnsubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); + } return status; } @@ -2451,24 +2441,23 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, { IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) + else if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) { IotLogError( "Disconnect packet length (%lu) exceeds buffer size (%lu).", MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } + else + { + /* Call internal function with local variables, as disconnect uses + * static memory, there is no need to pass the buffer + * Note: _IotMqtt_SerializeDisconnect always succeeds */ + _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); - /* Call internal function with local variables, as disconnect uses - * static memory, there is no need to pass the buffer - * Note: _IotMqtt_SerializeDisconnect always succeeds */ - _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); - - memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); -cleanup: + memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); + } return status; } @@ -2486,23 +2475,21 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, { IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) + else if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) { IotLogError( "Pingreq length (%lu) exceeds buffer size (%lu).", MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - /* Call internal function with local variables, as ping request uses - * static memory, there is no need to pass the buffer - * Note: _IotMqtt_SerializePingReq always succeeds */ - _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); - memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); -cleanup: + else + { + /* Call internal function with local variables, as ping request uses + * static memory, there is no need to pass the buffer + * Note: _IotMqtt_SerializePingReq always succeeds */ + _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); + memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); + } return status; } @@ -2521,27 +2508,26 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) { IotLogError( "IotMqtt_DeserializePublish()called with NULL pMqttPacket pointer." ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } - - if( ( pMqttPacket->type & 0xf0U ) != MQTT_PACKET_TYPE_PUBLISH ) + else if( ( pMqttPacket->type & 0xf0U ) != MQTT_PACKET_TYPE_PUBLISH ) { IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); status = IOT_MQTT_BAD_PARAMETER; - goto cleanup; } + else + { + /* Set internal mqtt packet parameters. */ + memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); + mqttPacket.pRemainingData = pMqttPacket->pRemainingData; + mqttPacket.remainingLength = pMqttPacket->remainingLength; + mqttPacket.type = pMqttPacket->type; - /* Set internal mqtt packet parameters. */ - memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; - mqttPacket.remainingLength = pMqttPacket->remainingLength; - mqttPacket.type = pMqttPacket->type; - - /* Set Publish specific parameters */ - memset( ( void * ) &mqttOperation, 0x00, sizeof( _mqttOperation_t ) ); - mqttOperation.incomingPublish = true; - mqttPacket.u.pIncomingPublish = &mqttOperation; - status = _IotMqtt_DeserializePublish( &mqttPacket ); + /* Set Publish specific parameters */ + memset( ( void * ) &mqttOperation, 0x00, sizeof( _mqttOperation_t ) ); + mqttOperation.incomingPublish = true; + mqttPacket.u.pIncomingPublish = &mqttOperation; + status = _IotMqtt_DeserializePublish( &mqttPacket ); + } if( status == IOT_MQTT_SUCCESS ) { @@ -2549,8 +2535,6 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } -cleanup: - return status; } diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 8bb8dbb9d8..416e02a2e8 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -47,6 +47,20 @@ static bool _validatePublish( bool awsIotMqttMode, const char * pPublishTypeDescription, const IotMqttPublishInfo_t * pPublishInfo ); +/** + * @brief Check that the payload inside #IotMqttPublishInfo_t is valid. + * + * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. + * @param[in] maximumPayloadLength Maximum payload length. + * @param[in] pPublishTypeDescription String describing the publish type. + * + * @return `true` if payload is valid; `false` otherwise + */ +static bool _validatePublishPayload( const IotMqttPublishInfo_t * pPublishInfo, + size_t maximumPayloadLength, + const char * pPublishTypeDescription ); + + /** * @brief Check that an #IotMqttQos_t is valid. * @@ -99,7 +113,7 @@ static bool _validateSubscription( bool awsIotMqttMode, * @param[in] index Index of `+` in the topic filter. * @param[in] pSubscription Subscription with the topic filter to check. * - * @return `true` if the `+` wilcard is valid; `false` otherwise. + * @return `true` if the `+` wildcard is valid; `false` otherwise. */ static bool _validateWildcardPlus( uint16_t index, const IotMqttSubscription_t * pSubscription ); @@ -110,11 +124,20 @@ static bool _validateWildcardPlus( uint16_t index, * @param[in] index Index of `#` in the topic filter. * @param[in] pSubscription Subscription with the topic filter to check. * - * @return `true` if the `#` wilcard is valid; `false` otherwise. + * @return `true` if the `#` wildcard is valid; `false` otherwise. */ static bool _validateWildcardHash( uint16_t index, const IotMqttSubscription_t * pSubscription ); +/** + * @brief Check the MQTT clientId length does not exceed. + * + * @param[in] pConnectInfo The #IotMqttConnectInfo_t to validate. + * + * @return `true` if client id length is valid, `false` otherwise. + */ +static bool _validateClientIdLength( const IotMqttConnectInfo_t * pConnectInfo ); + /*-----------------------------------------------------------*/ static bool _validateQos( IotMqttQos_t qos ) @@ -147,52 +170,37 @@ static bool _validateString( const char * pString, if( pString == NULL ) { status = false; - goto cleanup; } - - if( length == 0U ) + else if( length == 0U ) { status = false; - goto cleanup; } - -cleanup: + else + { + status = true; + } return status; } /*-----------------------------------------------------------*/ -static bool _validatePublish( bool awsIotMqttMode, - size_t maximumPayloadLength, - const char * pPublishTypeDescription, - const IotMqttPublishInfo_t * pPublishInfo ) +static bool _validatePublishPayload( const IotMqttPublishInfo_t * pPublishInfo, + size_t maximumPayloadLength, + const char * pPublishTypeDescription ) { bool status = true; /* This parameter is not used when logging is disabled. */ ( void ) pPublishTypeDescription; - /* Check for NULL. */ if( pPublishInfo == NULL ) { IotLogError( "Publish information cannot be NULL." ); status = false; - goto cleanup; - } - - /* Check topic name for NULL or zero-length. */ - status = _validateString( pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); - - if( status == false ) - { - IotLogError( "Publish topic name must be set." ); - - goto cleanup; } - - if( pPublishInfo->payloadLength != 0U ) + else if( pPublishInfo->payloadLength != 0U ) { if( pPublishInfo->payloadLength > maximumPayloadLength ) { @@ -202,65 +210,105 @@ static bool _validatePublish( bool awsIotMqttMode, maximumPayloadLength ); status = false; - goto cleanup; + } + else if( pPublishInfo->pPayload == NULL ) + { + IotLogError( "Nonzero payload length cannot have a NULL payload." ); + + status = false; } else { - if( pPublishInfo->pPayload == NULL ) - { - IotLogError( "Nonzero payload length cannot have a NULL payload." ); + /* Empty else MISRA 15.7 */ + } + } + else + { + /* Empty else MISRA 15.7 */ + } + - status = false; - goto cleanup; - } + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _validatePublish( bool awsIotMqttMode, + size_t maximumPayloadLength, + const char * pPublishTypeDescription, + const IotMqttPublishInfo_t * pPublishInfo ) +{ + bool status = true; + + /* Check for NULL. */ + if( pPublishInfo == NULL ) + { + IotLogError( "Publish information cannot be NULL." ); + + status = false; + } + /* Check topic name for NULL or zero-length. */ + else + { + status = _validateString( pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); + + if( status != true ) + { + IotLogError( "Publish topic name must be set." ); } } - /* Check for a valid QoS. */ - status = _validateQos( pPublishInfo->qos ); + if( status == true ) + { + status = _validatePublishPayload( pPublishInfo, + maximumPayloadLength, + pPublishTypeDescription ); + } - if( status == false ) + if( status == true ) { - goto cleanup; + /* Check for a valid QoS. */ + status = _validateQos( pPublishInfo->qos ); } - /* Check the retry parameters. */ - if( pPublishInfo->retryLimit > 0U ) + if( status == true ) { - if( pPublishInfo->retryMs == 0U ) + /* Check the retry parameters. */ + if( pPublishInfo->retryLimit > 0U ) { - IotLogError( "Publish retry time must be positive." ); + if( pPublishInfo->retryMs == 0U ) + { + IotLogError( "Publish retry time must be positive." ); - status = false; - goto cleanup; + status = false; + } } } - /* Check for compatibility with AWS IoT MQTT server. */ - if( awsIotMqttMode == true ) + if( status == true ) { - /* Check for retained message. */ - if( pPublishInfo->retain == true ) + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) { - IotLogError( "AWS IoT does not support retained publish messages." ); + /* Check for retained message. */ + if( pPublishInfo->retain == true ) + { + IotLogError( "AWS IoT does not support retained publish messages." ); - status = false; - goto cleanup; - } + status = false; + } - /* Check topic name length. */ - if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - { - IotLogError( "AWS IoT does not support topic names longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + /* Check topic name length. */ + if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + IotLogError( "AWS IoT does not support topic names longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - status = false; - goto cleanup; + status = false; + } } } -cleanup: - return status; } @@ -278,33 +326,29 @@ static bool _validateListSize( bool awsIotMqttMode, IotLogError( "Subscription list pointer cannot be NULL." ); status = false; - goto cleanup; } - - if( listSize == 0U ) + else if( listSize == 0U ) { IotLogError( "Empty subscription list." ); status = false; - goto cleanup; } - - /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ - if( awsIotMqttMode == true ) + else { - if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ + if( awsIotMqttMode == true ) { - IotLogError( "AWS IoT does not support more than %d topic filters per " - "subscription request.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); + if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + { + IotLogError( "AWS IoT does not support more than %d topic filters per " + "subscription request.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); - status = false; - goto cleanup; + status = false; + } } } -cleanup: - return status; } @@ -322,64 +366,64 @@ static bool _validateSubscription( bool awsIotMqttMode, { status = _validateQos( pSubscription->qos ); - if( status == false ) - { - goto cleanup; - } - - if( pSubscription->callback.function == NULL ) + if( status == true ) { - IotLogError( "Callback function must be set." ); + if( pSubscription->callback.function == NULL ) + { + IotLogError( "Callback function must be set." ); - status = false; - goto cleanup; + status = false; + } } } - /* Check subscription topic filter. */ - status = _validateString( pSubscription->pTopicFilter, pSubscription->topicFilterLength ); - - if( status == false ) + if( status == true ) { - IotLogError( "Subscription topic filter must be set." ); + /* Check subscription topic filter. */ + status = _validateString( pSubscription->pTopicFilter, pSubscription->topicFilterLength ); - goto cleanup; - } - - /* Check for compatibility with AWS IoT MQTT server. */ - if( awsIotMqttMode == true ) - { - /* Check topic filter length. */ - if( pSubscription->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + if( status == false ) { - IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - - status = false; - goto cleanup; + IotLogError( "Subscription topic filter must be set." ); + } + else + { + /* Check for compatibility with AWS IoT MQTT server. */ + if( awsIotMqttMode == true ) + { + /* Check topic filter length. */ + if( pSubscription->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + { + IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + status = false; + } + } } } - /* Check that the wildcards '+' and '#' are being used correctly. */ - for( i = 0; i < pSubscription->topicFilterLength; i++ ) + if( status == true ) { - if( pSubscription->pTopicFilter[ i ] == '+' ) - { - status = _validateWildcardPlus( i, pSubscription ); - } - else if( pSubscription->pTopicFilter[ i ] == '#' ) + /* Check that the wildcards '+' and '#' are being used correctly. */ + for( i = 0; i < pSubscription->topicFilterLength; i++ ) { - status = _validateWildcardHash( i, pSubscription ); - } + if( pSubscription->pTopicFilter[ i ] == '+' ) + { + status = _validateWildcardPlus( i, pSubscription ); + } + else if( pSubscription->pTopicFilter[ i ] == '#' ) + { + status = _validateWildcardHash( i, pSubscription ); + } - if( status == false ) - { - goto cleanup; + if( status == false ) + { + break; + } } } -cleanup: - return status; } @@ -400,26 +444,25 @@ static bool _validateWildcardPlus( uint16_t index, pSubscription->pTopicFilter ); status = false; - goto cleanup; } } - /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ - if( index < pSubscription->topicFilterLength - 1U ) + if( status == true ) { - if( pSubscription->pTopicFilter[ index + 1U ] != '/' ) + /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ + if( index < pSubscription->topicFilterLength - 1U ) { - IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", - pSubscription->topicFilterLength, - pSubscription->pTopicFilter ); + if( pSubscription->pTopicFilter[ index + 1U ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", + pSubscription->topicFilterLength, + pSubscription->pTopicFilter ); - status = false; - goto cleanup; + status = false; + } } } -cleanup: - return status; } @@ -438,57 +481,103 @@ static bool _validateWildcardHash( uint16_t index, pSubscription->pTopicFilter ); status = false; - goto cleanup; } - /* Unless '#' is standalone, it must be preceded by '/'. */ - if( pSubscription->topicFilterLength > 1U ) + if( status == true ) { - if( pSubscription->pTopicFilter[ index - 1U ] != '/' ) + /* Unless '#' is standalone, it must be preceded by '/'. */ + if( pSubscription->topicFilterLength > 1U ) { - IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", - pSubscription->topicFilterLength, - pSubscription->pTopicFilter ); + if( pSubscription->pTopicFilter[ index - 1U ] != '/' ) + { + IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", + pSubscription->topicFilterLength, + pSubscription->pTopicFilter ); - status = false; - goto cleanup; + status = false; + } } } -cleanup: - return status; } /*-----------------------------------------------------------*/ -bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) +static bool _validateClientIdLength( const IotMqttConnectInfo_t * pConnectInfo ) { bool status = true; uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; bool enforceMaxClientIdLength = false; - /* Check for NULL. */ if( pConnectInfo == NULL ) { IotLogError( "MQTT connection information cannot be NULL." ); status = false; - goto cleanup; } + /* The AWS IoT MQTT service enforces a client ID length limit. */ + else if( pConnectInfo->awsIotMqttMode == true ) + { + maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; + enforceMaxClientIdLength = true; + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( status == true ) + { + if( pConnectInfo->clientIdentifierLength > maxClientIdLength ) + { + if( enforceMaxClientIdLength == false ) + { + IotLogWarn( "A client identifier length of %hu is longer than %hu, " + "which is " + "the longest client identifier a server must accept.", + pConnectInfo->clientIdentifierLength, + maxClientIdLength ); + } + else + { + IotLogError( "A client identifier length of %hu exceeds the " + "maximum supported length of %hu.", + pConnectInfo->clientIdentifierLength, + maxClientIdLength ); + + status = false; + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) +{ + bool status = true; + + /* Check for NULL. */ + if( pConnectInfo == NULL ) + { + IotLogError( "MQTT connection information cannot be NULL." ); + status = false; + } /* Check that a client identifier was set. */ - if( pConnectInfo->pClientIdentifier == NULL ) + else if( pConnectInfo->pClientIdentifier == NULL ) { IotLogError( "Client identifier must be set." ); status = false; - goto cleanup; } /* Check for a zero-length client identifier. Zero-length client identifiers * are not allowed with clean sessions. */ - if( pConnectInfo->clientIdentifierLength == 0U ) + else if( pConnectInfo->clientIdentifierLength == 0U ) { IotLogWarn( "A zero-length client identifier was provided." ); @@ -497,64 +586,39 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) IotLogError( "A zero-length client identifier cannot be used with a clean session." ); status = false; - goto cleanup; } } - - /* Check that the number of persistent session subscriptions is valid. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) + else { - if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ) == false ) - { - status = false; - goto cleanup; - } + /* Empty else MISRA 15.7 */ } - /* If will info is provided, check that it is valid. */ - if( pConnectInfo->pWillInfo != NULL ) + if( status == true ) { - if( _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, - pConnectInfo->pWillInfo ) == false ) + /* Check that the number of persistent session subscriptions is valid. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) { - status = false; - goto cleanup; + status = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ); } } - /* The AWS IoT MQTT service enforces a client ID length limit. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; - enforceMaxClientIdLength = true; - } - - if( pConnectInfo->clientIdentifierLength > maxClientIdLength ) + if( status == true ) { - if( enforceMaxClientIdLength == false ) + /* If will info is provided, check that it is valid. */ + if( pConnectInfo->pWillInfo != NULL ) { - IotLogWarn( "A client identifier length of %hu is longer than %hu, " - "which is " - "the longest client identifier a server must accept.", - pConnectInfo->clientIdentifierLength, - maxClientIdLength ); - } - else - { - IotLogError( "A client identifier length of %hu exceeds the " - "maximum supported length of %hu.", - pConnectInfo->clientIdentifierLength, - maxClientIdLength ); - - status = false; - goto cleanup; + status = _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, + pConnectInfo->pWillInfo ); } } -cleanup: + if( status == true ) + { + status = _validateClientIdLength( pConnectInfo ); + } return status; } @@ -642,20 +706,18 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) IotLogError( "Operation reference cannot be NULL." ); status = false; - goto cleanup; } - - /* Check that reference is waitable. */ - if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) + else { - IotLogError( "Operation is not waitable." ); + /* Check that reference is waitable. */ + if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) + { + IotLogError( "Operation is not waitable." ); - status = false; - goto cleanup; + status = false; + } } -cleanup: - return status; } @@ -678,26 +740,22 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListStart, listSize ); - if( status == false ) - { - goto cleanup; - } - - /* Check each member of the subscription list. */ - for( i = 0; i < listSize; i++ ) + if( status == true ) { - status = _validateSubscription( awsIotMqttMode, - operation, - &( pListStart[ i ] ) ); - - if( status == false ) + /* Check each member of the subscription list. */ + for( i = 0; i < listSize; i++ ) { - break; + status = _validateSubscription( awsIotMqttMode, + operation, + &( pListStart[ i ] ) ); + + if( status == false ) + { + break; + } } } -cleanup: - return status; } From 16156b15b84b4e88a2479f1903baaa61eb373080 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Wed, 22 Jan 2020 14:03:02 -0800 Subject: [PATCH 378/844] Remove goto statements in aws_iot_shadow_subscription.c (#733) --- .../shadow/src/aws_iot_shadow_subscription.c | 89 +++++++++---------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index c964351e6c..8f9d444603 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -35,9 +35,6 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" -/* Error handling include. */ -#include "iot_error.h" - /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -270,7 +267,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe uint16_t operationTopicLength, AwsIotMqttCallbackFunction_t callback ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; const _shadowOperationType_t type = pOperation->type; _shadowSubscription_t * pSubscription = pOperation->pSubscription; IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; @@ -284,60 +281,58 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe _pAwsIotShadowOperationNames[ type ], pSubscription->thingNameLength, pSubscription->pThingName ); - - IOT_GOTO_CLEANUP(); } - - /* When persistent subscriptions are not present, the reference count must - * not be negative. */ - AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); - - /* Check if there are any existing references for this operation. */ - if( pSubscription->references[ type ] == 0 ) + else { - /* Set the parameters needed to add subscriptions. */ - subscriptionInfo.mqttConnection = pOperation->mqttConnection; - subscriptionInfo.callbackFunction = callback; - subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; + /* When persistent subscriptions are not present, the reference count must + * not be negative. */ + AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); - subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync, - &subscriptionInfo ); + /* Check if there are any existing references for this operation. */ + if( pSubscription->references[ type ] == 0 ) + { + /* Set the parameters needed to add subscriptions. */ + subscriptionInfo.mqttConnection = pOperation->mqttConnection; + subscriptionInfo.callbackFunction = callback; + subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; - /* Convert MQTT return code to Shadow return code. */ - status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( subscriptionStatus ); + subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync, + &subscriptionInfo ); - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - IOT_GOTO_CLEANUP(); + /* Convert MQTT return code to Shadow return code. */ + status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( subscriptionStatus ); } - } - /* Increment the number of subscription references for this operation when - * the keep subscriptions flag is not set. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) - { - ( pSubscription->references[ type ] )++; + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + /* Increment the number of subscription references for this operation when + * the keep subscriptions flag is not set. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + { + ( pSubscription->references[ type ] )++; - IotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName, - pSubscription->references[ type ] ); - } - /* Otherwise, set the persistent subscriptions flag. */ - else - { - pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; + IotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName, + pSubscription->references[ type ] ); + } + /* Otherwise, set the persistent subscriptions flag. */ + else + { + pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; - IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + } + } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ From 0809b1295b8d4f08d991c8ecfb768353156d06fc Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Wed, 22 Jan 2020 14:20:26 -0800 Subject: [PATCH 379/844] Shadow/misra directive 4 6 (#734) * misra: fix directive 4.6 violation * Change long int to type explicitly indicating sign and size. * misra shadow: add comment for dir4.6 violation * Add coverity to shadow lexicon * Add log function to lexicon --- libraries/aws/shadow/lexicon.txt | 2 ++ libraries/aws/shadow/src/aws_iot_shadow_subscription.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/libraries/aws/shadow/lexicon.txt b/libraries/aws/shadow/lexicon.txt index 8fa07243dc..a115c8cd22 100644 --- a/libraries/aws/shadow/lexicon.txt +++ b/libraries/aws/shadow/lexicon.txt @@ -13,6 +13,7 @@ clienttokenlength com config const +coverity deleteasync deletesync deltacallbackfunction @@ -33,6 +34,7 @@ init int iot iotlink +iotlogdebug iotmqttconnection iotmqttpublishinfo iotmqttqos diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index 8f9d444603..f6023811cd 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -176,6 +176,11 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, { if( pSubscription->references[ i ] > 0 ) { + /* In some implementations IotLogDebug() maps to C standard printing API + * that needs specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "Reference count %ld for %.*s subscription object. " "Subscription cannot be removed yet.", ( long int ) pSubscription->references[ i ], From d51da4a3315fa351837d4db23cc45acd45e36a79 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Wed, 22 Jan 2020 15:18:37 -0800 Subject: [PATCH 380/844] misra shadow: resolve 10.1 rule violations (#735) * misra shadow: resolve 10.1 rule violations --- .../aws/shadow/include/types/aws_iot_shadow_types.h | 10 +++++----- libraries/aws/shadow/src/aws_iot_shadow_api.c | 4 ++-- libraries/aws/shadow/src/aws_iot_shadow_subscription.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h index bc7eb92082..2d8d3b1e04 100644 --- a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h +++ b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h @@ -563,7 +563,7 @@ typedef struct AwsIotShadowDocumentInfo * @note If this flag is set, @ref shadow_function_wait MUST be called to * clean up resources. */ -#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001 ) +#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001UL ) /** * @brief Maintain the subscriptions for the Shadow operation topics, even after @@ -591,7 +591,7 @@ typedef struct AwsIotShadowDocumentInfo * shadow_function_removepersistentsubscriptions may be used to remove * subscriptions maintained by this flag. */ -#define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) +#define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002UL ) /** * @brief Remove the persistent subscriptions from a Shadow delete operation. @@ -606,7 +606,7 @@ typedef struct AwsIotShadowDocumentInfo * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow delete operations. */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001 ) +#define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001UL ) /** * @brief Remove the persistent subscriptions from a Shadow get operation. @@ -621,7 +621,7 @@ typedef struct AwsIotShadowDocumentInfo * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow get operations. */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002 ) +#define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002UL ) /** * @brief Remove the persistent subscriptions from a Shadow update operation. @@ -636,6 +636,6 @@ typedef struct AwsIotShadowDocumentInfo * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow update operations. */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004 ) +#define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004UL ) #endif /* ifndef AWS_IOT_SHADOW_TYPES_H_ */ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c index 53a812b061..0b1d329add 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_api.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_api.c @@ -349,7 +349,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio } /* Check parameters. */ - status = _validateThingNameFlags( ( _shadowOperationType_t ) ( type + SHADOW_OPERATION_COUNT ), + status = _validateThingNameFlags( ( _shadowOperationType_t ) ( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ), pThingName, thingNameLength, 0, @@ -628,7 +628,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the callback type. Shadow callbacks are enumerated after the operations. */ - callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( type + SHADOW_OPERATION_COUNT ); + callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ); /* Set the remaining members of the callback param. */ callbackParam.mqttConnection = pMessage->mqttConnection; diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index f6023811cd..78dfb417d3 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -409,7 +409,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio size_t thingNameLength, uint32_t flags ) { - int32_t i = 0; + uint32_t i = 0; uint16_t operationTopicLength = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING; From 93e2f70dd6592e1e486bfc27f8b9da470bc550c8 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Wed, 22 Jan 2020 22:44:20 -0800 Subject: [PATCH 381/844] Remove goto statements in aws_iot_shadow_parser.c (#738) --- .../aws/shadow/src/aws_iot_shadow_parser.c | 80 +++++++++---------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/libraries/aws/shadow/src/aws_iot_shadow_parser.c b/libraries/aws/shadow/src/aws_iot_shadow_parser.c index 64abec633c..286f9cdb17 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_parser.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_parser.c @@ -35,9 +35,6 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" -/* Error handling include. */ -#include "iot_error.h" - /* AWS Parser include. */ #include "aws_iot_doc_parser.h" @@ -133,7 +130,7 @@ static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ) AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; const char * pCode = NULL, * pMessage = NULL; size_t codeLength = 0, messageLength = 0; uint32_t code = 0; @@ -144,52 +141,53 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen ERROR_DOCUMENT_CODE_KEY, ERROR_DOCUMENT_CODE_KEY_LENGTH, &pCode, - &codeLength ) == false ) - { - /* Error parsing JSON document, or no "code" key was found. */ - IotLogWarn( "Failed to parse code from error document.\n%.*s", - errorDocumentLength, - pErrorDocument ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); - } - - /* Code must be in error document. */ - AwsIotShadow_Assert( ( pCode > pErrorDocument ) && - ( pCode + codeLength < pErrorDocument + errorDocumentLength ) ); - - /* Convert the code to an unsigned integer value. */ - code = ( uint32_t ) strtoul( pCode, NULL, 10 ); - - /* Parse the error message and print it. An error document must always contain - * a message. */ - if( AwsIotDocParser_FindValue( pErrorDocument, - errorDocumentLength, - ERROR_DOCUMENT_MESSAGE_KEY, - ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, - &pMessage, - &messageLength ) == true ) + &codeLength ) == true ) { - IotLogWarn( "Code %lu: %.*s.", - code, - messageLength, - pMessage ); + /* Code must be in error document. */ + AwsIotShadow_Assert( ( pCode >= pErrorDocument ) && + ( pCode + codeLength <= pErrorDocument + errorDocumentLength ) ); + + /* Convert the code to an unsigned integer value. */ + code = ( uint32_t ) strtoul( pCode, NULL, 10 ); + + /* Parse the error message and print it. An error document must always contain + * a message. */ + if( AwsIotDocParser_FindValue( pErrorDocument, + errorDocumentLength, + ERROR_DOCUMENT_MESSAGE_KEY, + ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, + &pMessage, + &messageLength ) == true ) + { + IotLogWarn( "Code %lu: %.*s.", + code, + messageLength, + pMessage ); + + /* Convert a successfully parsed JSON code to a Shadow status. */ + status = _codeToShadowStatus( code ); + } + else + { + IotLogWarn( "Code %lu; failed to parse message from error document.\n%.*s", + code, + errorDocumentLength, + pErrorDocument ); + + status = AWS_IOT_SHADOW_BAD_RESPONSE; + } } else { - IotLogWarn( "Code %lu; failed to parse message from error document.\n%.*s", - code, + /* Error parsing JSON document, or no "code" key was found. */ + IotLogWarn( "Failed to parse code from error document.\n%.*s", errorDocumentLength, pErrorDocument ); - /* An error document must contain a message; if it does not, then it is invalid. */ - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); + status = AWS_IOT_SHADOW_BAD_RESPONSE; } - /* Convert a successfully parsed JSON code to a Shadow status. */ - status = _codeToShadowStatus( code ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ From e23e442722a68fd71a81e596cdfc8a8bc82aaacc Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 23 Jan 2020 10:56:46 -0800 Subject: [PATCH 382/844] Address MISRA Rule 11.2 violations (#730) --- .../standard/mqtt/include/iot_mqtt_serialize.h | 8 ++++---- .../standard/mqtt/include/types/iot_mqtt_types.h | 6 +++--- libraries/standard/mqtt/src/iot_mqtt_network.c | 15 ++++++++------- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 6 +++--- .../mqtt/src/private/iot_mqtt_internal.h | 10 +++++----- .../mqtt/test/mock/iot_tests_mqtt_mock.c | 2 +- .../standard/mqtt/test/unit/iot_tests_mqtt_api.c | 10 +++++----- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 16 ++++++++-------- 8 files changed, 37 insertions(+), 36 deletions(-) diff --git a/libraries/standard/mqtt/include/iot_mqtt_serialize.h b/libraries/standard/mqtt/include/iot_mqtt_serialize.h index cc1562570d..1b864f965d 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_serialize.h +++ b/libraries/standard/mqtt/include/iot_mqtt_serialize.h @@ -547,11 +547,11 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, * Example * @code{c} * // Example code below shows how to implement getNextByte function with posix sockets. - * // Note: IotMqttGetNextByte_t typedef IotMqttError_t (* IotMqttGetNextByte_t)( void * pNetworkContext, + * // Note: IotMqttGetNextByte_t typedef IotMqttError_t (* IotMqttGetNextByte_t)( IotNetworkConnection_t pNetworkContext, * // uint8_t * pNextByte ); * // Note: It is assumed that socket is already created and connected, * - * IotMqttError_t getNextByte( void * pContext, + * IotMqttError_t getNextByte( IotNetworkConnection_t pContext, * uint8_t * pNextByte ) * { * int socket = ( int ) ( *pvContext ); @@ -578,7 +578,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, * void getTypeAndLengthFromIncomingMQTTPingResponse( int xMQTTSocket ) * { * IotMqttPacketInfo_t xIncomingPacket; - * IotMqttError_t xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); + * IotMqttError_t xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( IotNetworkConnection_t ) xMQTTSocket ); * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * IotMqtt_Assert( xIncomingPacket.type == MQTT_PACKET_TYPE_PINGRESP ); * } @@ -588,7 +588,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, /* @[declare_mqtt_getincomingmqttpackettypeandlength] */ IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, IotMqttGetNextByte_t getNextByte, - void * pNetworkConnection ); + IotNetworkConnection_t pNetworkConnection ); /* @[declare_mqtt_getincomingmqttpackettypeandlength] */ /** diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 4c0e381bd3..2dc3dc19d2 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -721,7 +721,7 @@ struct _mqttPacket; * @param[in] pNetworkInterface Function pointers used to interact with the * network. */ -typedef uint8_t ( * IotMqttGetPacketType_t )( void * pNetworkConnection, +typedef uint8_t ( * IotMqttGetPacketType_t )( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); /** @@ -731,7 +731,7 @@ typedef uint8_t ( * IotMqttGetPacketType_t )( void * pNetworkConnection, * @param[in] pNetworkInterface Function pointers used to interact with the * network. */ -typedef size_t ( * IotMqttGetRemainingLength_t )( void * pNetworkConnection, +typedef size_t ( * IotMqttGetRemainingLength_t )( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); /** @@ -828,7 +828,7 @@ typedef void ( * IotMqttPublishSetDup_t )( uint8_t * pPublishPacket, * @param[in] pNetworkContext reference to network connection like socket. * @param[out] pNextByte Pointer to the byte read from the network. */ -typedef IotMqttError_t (* IotMqttGetNextByte_t)( void * pNetworkContext, +typedef IotMqttError_t (* IotMqttGetNextByte_t)( IotNetworkConnection_t pNetworkContext, uint8_t * pNextByte ); #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 882690ed37..a7e800e9f3 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -61,7 +61,7 @@ static bool _incomingPacketValid( uint8_t packetType ); * * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY or #IOT_MQTT_BAD_RESPONSE. */ -static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, +static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ); @@ -139,7 +139,7 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, * @param[in] pMqttConnection The associated MQTT connection. * @param[in] length The length of the packet to flush. */ -static void _flushPacket( void * pNetworkConnection, +static void _flushPacket( IotNetworkConnection_t pNetworkConnection, const _mqttConnection_t * pMqttConnection, size_t length ); @@ -233,7 +233,7 @@ static bool _incomingPacketValid( uint8_t packetType ) /*-----------------------------------------------------------*/ -static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, +static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ) { @@ -644,7 +644,7 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ -static void _flushPacket( void * pNetworkConnection, +static void _flushPacket( IotNetworkConnection_t pNetworkConnection, const _mqttConnection_t * pMqttConnection, size_t length ) { @@ -661,7 +661,7 @@ static void _flushPacket( void * pNetworkConnection, /*-----------------------------------------------------------*/ -bool _IotMqtt_GetNextByte( void * pNetworkConnection, +bool _IotMqtt_GetNextByte( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface, uint8_t * pIncomingByte ) { @@ -697,7 +697,8 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; - void * pNetworkConnection = NULL, * pDisconnectCallbackContext = NULL; + IotNetworkConnection_t pNetworkConnection = NULL; + void * pDisconnectCallbackContext = NULL; /* Disconnect callback function. */ void ( * disconnectCallback )( void *, @@ -835,7 +836,7 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, IotMqttGetNextByte_t getNextByte, - void * pNetworkConnection ) + IotNetworkConnection_t pNetworkConnection ) { IotMqttError_t status = IOT_MQTT_SUCCESS; diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 007b5542bb..59a1e9b86c 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1261,7 +1261,7 @@ static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, /*-----------------------------------------------------------*/ -uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, +uint8_t _IotMqtt_GetPacketType( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { uint8_t packetType = 0xff; @@ -1276,7 +1276,7 @@ uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, /*-----------------------------------------------------------*/ -size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, +size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { uint8_t encodedByte = 0; @@ -1329,7 +1329,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, /*-----------------------------------------------------------*/ -size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, +size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConnection, IotMqttGetNextByte_t getNextByte ) { uint8_t encodedByte = 0; diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index e481ed5c06..27ad9d4fe7 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -374,7 +374,7 @@ typedef struct _mqttConnection { bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ - void * pNetworkConnection; /**< @brief References the transport-layer network connection. */ + IotNetworkConnection_t pNetworkConnection; /**< @brief References the transport-layer network connection. */ const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ @@ -533,7 +533,7 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, * @note This function is only used for incoming packets, and may not work * correctly for outgoing packets. */ -uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, +uint8_t _IotMqtt_GetPacketType( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); /** @@ -545,7 +545,7 @@ uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, * * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. */ -size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, +size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); /** @@ -563,7 +563,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, * user provided function makes use of it. * */ -size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, +size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConnection, IotMqttGetNextByte_t getNextByte ); /** @@ -983,7 +983,7 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection * @return `true` if a byte was successfully received from the network; `false` * otherwise. */ -bool _IotMqtt_GetNextByte( void * pNetworkConnection, +bool _IotMqtt_GetNextByte( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface, uint8_t * pIncomingByte ); diff --git a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c index f00fdd52af..e5f5002e5a 100644 --- a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c +++ b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c @@ -200,7 +200,7 @@ static size_t _sendSuccess( IotNetworkConnection_t pNetworkConnection, IotMutex_Lock( &_lastPacketMutex ); /* Read the remaining length. */ - mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( &receiveContext, + mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ); IotTest_Assert( mqttPacket.remainingLength != MQTT_REMAINING_LENGTH_INVALID ); diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index c7d814df8a..64ec7a8e6a 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -555,7 +555,7 @@ static void _decrementReferencesJob( IotTaskPool_t pTaskPool, /** * @brief get next byte mock function to test MQTT serializer API */ -static IotMqttError_t _getNextByte( void * pNetworkInterface, +static IotMqttError_t _getNextByte( IotNetworkConnection_t pNetworkInterface, uint8_t * nextByte ) { uint8_t * buffer; @@ -854,7 +854,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set parameter to network send function. */ - _pMqttConnection->pNetworkConnection = &waitSem; + _pMqttConnection->pNetworkConnection = ( IotNetworkConnection_t ) &waitSem; /* Create a new operation referencing the MQTT connection. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, @@ -1362,7 +1362,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the serializers and parameter to the send function. */ - _pMqttConnection->pNetworkConnection = &dupCheckResult; + _pMqttConnection->pNetworkConnection = ( IotNetworkConnection_t ) &dupCheckResult; _pMqttConnection->pSerializer = &serializer; /* Set the publish info. */ @@ -1734,7 +1734,7 @@ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the parameter to the send function. */ - _pMqttConnection->pNetworkConnection = &waitSem; + _pMqttConnection->pNetworkConnection = ( IotNetworkConnection_t ) &waitSem; /* Set a short keep-alive interval so this test runs faster. */ _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; @@ -2268,7 +2268,7 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) uint8_t * bufPtr = buffer; /* Dummy network interface - pointer to pointer to a buffer. */ - void * pNetworkInterface = ( void * ) &bufPtr; + IotNetworkConnection_t pNetworkInterface = ( IotNetworkConnection_t ) &bufPtr; buffer[ 0 ] = 0x20; /* CONN ACK */ buffer[ 1 ] = 0x02; /* Remaining length. */ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index 4eab1426ac..ca046924de 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -187,7 +187,7 @@ static bool _disconnectCallbackCalled = false; /** * @brief Get packet type function override. */ -static uint8_t _getPacketType( void * pNetworkConnection, +static uint8_t _getPacketType( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { _getPacketTypeCalled = true; @@ -200,7 +200,7 @@ static uint8_t _getPacketType( void * pNetworkConnection, /** * @brief Get remaining length function override. */ -static size_t _getRemainingLength( void * pNetworkConnection, +static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { _getRemainingLengthCalled = true; @@ -663,7 +663,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.dataLength = 4; TEST_ASSERT_EQUAL( 0, - _IotMqtt_GetRemainingLength( &receiveContext, + _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ) ); } @@ -677,7 +677,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.dataLength = 4; TEST_ASSERT_EQUAL( 129, - _IotMqtt_GetRemainingLength( &receiveContext, + _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ) ); } @@ -691,7 +691,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.dataLength = 4; TEST_ASSERT_EQUAL( 16386, - _IotMqtt_GetRemainingLength( &receiveContext, + _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ) ); } @@ -706,7 +706,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.dataLength = 4; TEST_ASSERT_EQUAL( 268435455, - _IotMqtt_GetRemainingLength( &receiveContext, + _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ) ); } @@ -720,7 +720,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.dataLength = 4; TEST_ASSERT_EQUAL( MQTT_REMAINING_LENGTH_INVALID, - _IotMqtt_GetRemainingLength( &receiveContext, + _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ) ); } @@ -735,7 +735,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.dataLength = 4; TEST_ASSERT_EQUAL( MQTT_REMAINING_LENGTH_INVALID, - _IotMqtt_GetRemainingLength( &receiveContext, + _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, &_networkInterface ) ); } From cef79f720f26daf717e5a2fd0ece7d3cc84f45e1 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Thu, 23 Jan 2020 11:05:44 -0800 Subject: [PATCH 383/844] Shadow/misra rule 10 3 10 4 (#737) --- .../include/types/aws_iot_shadow_types.h | 2 +- libraries/aws/shadow/src/aws_iot_shadow_api.c | 24 +++++++++---------- .../aws/shadow/src/aws_iot_shadow_operation.c | 12 +++++----- .../shadow/src/aws_iot_shadow_static_memory.c | 4 ++-- .../shadow/src/aws_iot_shadow_subscription.c | 6 ++--- .../src/private/aws_iot_shadow_internal.h | 10 ++++---- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h index 2d8d3b1e04..6b0e7fc572 100644 --- a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h +++ b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h @@ -563,7 +563,7 @@ typedef struct AwsIotShadowDocumentInfo * @note If this flag is set, @ref shadow_function_wait MUST be called to * clean up resources. */ -#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001UL ) +#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001U ) /** * @brief Maintain the subscriptions for the Shadow operation topics, even after diff --git a/libraries/aws/shadow/src/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c index 0b1d329add..41b123f586 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_api.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_api.c @@ -163,7 +163,7 @@ static void _updatedCallbackWrapper( void * pArgument, * * API functions will fail if @ref shadow_function_init was not called. */ -static uint32_t _initCalled = 0; +static uint32_t _initCalled = 0U; /** * @brief Timeout used for MQTT operations. @@ -188,7 +188,7 @@ static bool _checkInit( void ) { bool status = true; - if( _initCalled == 0 ) + if( _initCalled == 0U ) { IotLogError( "AwsIotShadow_Init was not called." ); @@ -245,7 +245,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, /* A callback info must be passed to a non-waitable GET. */ if( ( type == SHADOW_GET ) && - ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) && + ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) && ( pCallbackInfo == NULL ) ) { IotLogError( "Callback info must be provided for non-waitable Shadow GET." ); @@ -290,9 +290,9 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, } /* Check the retry parameters. */ - if( pDocumentInfo->retryLimit > 0 ) + if( pDocumentInfo->retryLimit > 0U ) { - if( pDocumentInfo->retryMs == 0 ) + if( pDocumentInfo->retryMs == 0U ) { IotLogError( "Retry time of Shadow %s must be positive.", _pAwsIotShadowOperationNames[ type ] ); @@ -318,7 +318,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, { /* Check UPDATE document pointer and length. */ if( ( pDocumentInfo->u.update.pUpdateDocument == NULL ) || - ( pDocumentInfo->u.update.updateDocumentLength == 0 ) ) + ( pDocumentInfo->u.update.updateDocumentLength == 0U ) ) { IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" " have length 0." ); @@ -674,7 +674,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; bool listInitStatus = false; - if( _initCalled == 0 ) + if( _initCalled == 0U ) { /* Initialize Shadow lists and mutexes. */ listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, @@ -691,13 +691,13 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) else { /* Save the MQTT timeout option. */ - if( mqttTimeoutMs != 0 ) + if( mqttTimeoutMs != 0U ) { _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; } /* Set the flag that specifies initialization is complete. */ - _initCalled = 1; + _initCalled = 1U; IotLogInfo( "Shadow library successfully initialized." ); } @@ -714,9 +714,9 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) void AwsIotShadow_Cleanup( void ) { - if( _initCalled == 1 ) + if( _initCalled == 1U ) { - _initCalled = 0; + _initCalled = 0U; /* Remove and free all items in the Shadow pending operation list. */ IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); @@ -1150,7 +1150,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, } /* Check that reference is waitable. */ - if( ( operation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + if( ( operation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) { IotLogError( "Operation is not waitable." ); diff --git a/libraries/aws/shadow/src/aws_iot_shadow_operation.c b/libraries/aws/shadow/src/aws_iot_shadow_operation.c index 5fdaf5ef72..c8ea058784 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_operation.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_operation.c @@ -214,9 +214,9 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, { /* Check document pointers. */ AwsIotShadow_Assert( pParam->pDocument != NULL ); - AwsIotShadow_Assert( pParam->documentLength > 0 ); + AwsIotShadow_Assert( pParam->documentLength > 0U ); AwsIotShadow_Assert( pOperation->u.update.pClientToken != NULL ); - AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0 ); + AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0U ); IotLogDebug( "Verifying client tokens for Shadow UPDATE." ); @@ -304,7 +304,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, /* Remove a non-waitable operation from the pending operation list. * Waitable operations are removed by the Wait function. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) { IotListDouble_Remove( &( pOperation->link ) ); IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); @@ -417,7 +417,7 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, * But a waitable operation must copy the data from the publish info because * AwsIotShadow_Wait may be called after the MQTT library frees the publish * info. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) { pOperation->u.get.pDocument = pPublishInfo->pPayload; pOperation->u.get.documentLength = pPublishInfo->payloadLength; @@ -702,7 +702,7 @@ void _AwsIotShadow_DestroyOperation( void * pData ) if( ( pOperation->type == SHADOW_UPDATE ) && ( pOperation->u.update.pClientToken != NULL ) ) { - AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0 ); + AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0U ); AwsIotShadow_FreeString( ( void * ) ( pOperation->u.update.pClientToken ) ); } @@ -878,7 +878,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn /* If the "keep subscriptions" flag is not set, decrement the reference * count. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0U ) { IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); _AwsIotShadow_DecrementReferences( pOperation, diff --git a/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c b/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c index 2e2d67d00f..1eee1469a5 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c @@ -73,7 +73,7 @@ * the constant `AWS_IOT_MAX_THING_NAME_LENGTH` is used for the length of * #_shadowSubscription_t.pThingName. */ -#define SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + AWS_IOT_MAX_THING_NAME_LENGTH ) +#define SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + ( size_t ) AWS_IOT_MAX_THING_NAME_LENGTH ) /*-----------------------------------------------------------*/ @@ -84,7 +84,7 @@ static uint32_t _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIO static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Shadow operations. */ static uint32_t _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0U }; /**< @brief Shadow subscription in-use flags. */ -static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ SHADOW_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Shadow subscriptions. */ +static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ SHADOW_SUBSCRIPTION_SIZE ] = { { '\0' } }; /**< @brief Shadow subscriptions. */ /*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index 78dfb417d3..42bf531971 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -314,7 +314,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe { /* Increment the number of subscription references for this operation when * the keep subscriptions flag is not set. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0U ) { ( pSubscription->references[ type ] )++; @@ -443,9 +443,9 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); - for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) + for( i = 0; i < ( uint32_t ) SHADOW_OPERATION_COUNT; i++ ) { - if( ( flags & ( 0x1UL << i ) ) != 0 ) + if( ( flags & ( 0x1UL << i ) ) != 0U ) { IotLogDebug( "Removing %.*s %s persistent subscriptions.", thingNameLength, diff --git a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h index 0e7236c661..e7b7ab3022 100644 --- a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h +++ b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h @@ -211,7 +211,7 @@ /** * @brief The length of #SHADOW_DELETE_OPERATION_STRING. */ -#define SHADOW_DELETE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELETE_OPERATION_STRING ) - 1 ) ) +#define SHADOW_DELETE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELETE_OPERATION_STRING ) - 1U ) ) /** * @brief The string representing a Shadow GET operation in a Shadow MQTT topic. @@ -221,7 +221,7 @@ /** * @brief The length of #SHADOW_GET_OPERATION_STRING. */ -#define SHADOW_GET_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_GET_OPERATION_STRING ) - 1 ) ) +#define SHADOW_GET_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_GET_OPERATION_STRING ) - 1U ) ) /** * @brief The string representing a Shadow UPDATE operation in a Shadow MQTT topic. @@ -231,7 +231,7 @@ /** * @brief The length of #SHADOW_UPDATE_OPERATION_STRING. */ -#define SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATE_OPERATION_STRING ) - 1 ) ) +#define SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATE_OPERATION_STRING ) - 1U ) ) /** * @brief The suffix for a Shadow delta topic. @@ -241,7 +241,7 @@ /** * @brief The length of #SHADOW_DELTA_SUFFIX. */ -#define SHADOW_DELTA_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELTA_SUFFIX ) - 1 ) ) +#define SHADOW_DELTA_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELTA_SUFFIX ) - 1U ) ) /** * @brief The suffix for a Shadow updated topic. @@ -251,7 +251,7 @@ /** * @brief The length of #SHADOW_UPDATED_SUFFIX. */ -#define SHADOW_UPDATED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATED_SUFFIX ) - 1 ) ) +#define SHADOW_UPDATED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATED_SUFFIX ) - 1U ) ) /** * @brief The length of the longest Shadow suffix. From 832cd00211931e80508492936c23060705cf43a4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 23 Jan 2020 14:12:45 -0800 Subject: [PATCH 384/844] Enable quality check, but allow it to fail (#739) --- .travis.yml | 3 +++ scripts/ci_test_quality.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 scripts/ci_test_quality.sh diff --git a/.travis.yml b/.travis.yml index 5800e7760d..58c4989055 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,9 @@ jobs: # Build checks for pull requests. if: type = pull_request env: RUN_TEST=build + # Code quality checks for pull requests. + - if: type = pull_request + env: RUN_TEST=quality # Documentation check for pull requests. - if: type = pull_request env: RUN_TEST=doc diff --git a/scripts/ci_test_quality.sh b/scripts/ci_test_quality.sh old mode 100644 new mode 100755 index 8ef6461970..8149bd7892 --- a/scripts/ci_test_quality.sh +++ b/scripts/ci_test_quality.sh @@ -8,4 +8,4 @@ set -e # Get the list of files to check. Only check library files and exclude tests. # Run complexity with a threshold of 8. find ../libraries/ \( -name '*.c' ! -name *tests*.c \) -type f | \ -xargs complexity --scores --threshold=8 --horrid-threshold=8 +xargs complexity --scores --threshold=8 #--horrid-threshold=8 From 27d5ead6602b33b5772272043c74b58cdd09b009 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 23 Jan 2020 14:24:11 -0800 Subject: [PATCH 385/844] Comply with MISRA Rules 11.1, 15.7, and 16.4 (#728) * Address MISRA 15.7 and 16.4 * Comply with MISRA 11.1 Co-authored-by: Gary Wicker <14828980+gkwicker@users.noreply.github.com> --- .../standard/common/include/iot_linear_containers.h | 6 +++--- libraries/standard/mqtt/src/iot_mqtt_api.c | 4 ++-- libraries/standard/mqtt/src/iot_mqtt_operation.c | 13 +++++++++++-- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 5 +++++ libraries/standard/mqtt/src/iot_mqtt_subscription.c | 8 ++++---- libraries/standard/mqtt/src/iot_mqtt_validate.c | 8 ++++++++ 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h index 5fe222f07e..843f5065db 100644 --- a/libraries/standard/common/include/iot_linear_containers.h +++ b/libraries/standard/common/include/iot_linear_containers.h @@ -683,7 +683,7 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * /* @[declare_linear_containers_list_double_removefirstmatch] */ static inline IotLink_t * IotListDouble_RemoveFirstMatch( IotListDouble_t * const pList, const IotLink_t * const pStartPoint, - bool ( *isMatch )( const IotLink_t *, void * ), + bool ( *isMatch )( const IotLink_t * const, void * ), void * pMatch ) /* @[declare_linear_containers_list_double_removefirstmatch] */ { @@ -718,7 +718,7 @@ static inline IotLink_t * IotListDouble_RemoveFirstMatch( IotListDouble_t * cons */ /* @[declare_linear_containers_list_double_removeallmatches] */ static inline void IotListDouble_RemoveAllMatches( IotListDouble_t * const pList, - bool ( *isMatch )( const IotLink_t *, void * ), + bool ( *isMatch )( const IotLink_t * const, void * ), void * pMatch, void ( *freeElement )( void * ), size_t linkOffset ) @@ -944,7 +944,7 @@ static inline void IotDeQueue_RemoveAll( IotDeQueue_t * const pQueue, */ /* @[declare_linear_containers_queue_removeallmatches] */ static inline void IotDeQueue_RemoveAllMatches( IotDeQueue_t * const pQueue, - bool ( * isMatch )( const IotLink_t *, void * ), + bool ( * isMatch )( const IotLink_t * const, void * ), void * pMatch, void ( * freeElement )( void * ), size_t linkOffset ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index a6278b489b..3af0ee0cc5 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -87,7 +87,7 @@ static bool _checkInit( void ); * * @return Always returns `true`. */ -static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, +static bool _mqttSubscription_setUnsubscribe( const IotLink_t * const pSubscriptionLink, void * pMatch ); /** @@ -292,7 +292,7 @@ static bool _checkInit( void ) /*-----------------------------------------------------------*/ -static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, +static bool _mqttSubscription_setUnsubscribe( const IotLink_t * const pSubscriptionLink, void * pMatch ) { /* Because this function is called from a container function, the given link diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index dd8d4176c8..ad932188ac 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -85,7 +85,7 @@ typedef struct _operationMatchParam * @return `true` if the operation matches the parameters in `pArgument`; `false` * otherwise. */ -static bool _mqttOperation_match( const IotLink_t * pOperationLink, +static bool _mqttOperation_match( const IotLink_t * const pOperationLink, void * pMatch ); /** @@ -133,7 +133,7 @@ static bool _completePendingSend( _mqttOperation_t * pOperation, /*-----------------------------------------------------------*/ -static bool _mqttOperation_match( const IotLink_t * pOperationLink, +static bool _mqttOperation_match( const IotLink_t * const pOperationLink, void * pMatch ) { bool match = false; @@ -202,6 +202,10 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) * identifier) must be reset on every retry. */ setDup = true; } + else + { + setDup = false; + } if( setDup == true ) { @@ -959,6 +963,11 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, pOperation->u.operation.status = IOT_MQTT_SUCCESS; } } + else + { + /* Empty else MISRA 15.7 */ + } + } /* Check if this operation requires further processing. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 59a1e9b86c..4364abf285 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -838,6 +838,7 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, break; default: + /* Empty default MISRA 16.4 */ break; } @@ -919,6 +920,10 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, { UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); } + else + { + /* Empty else MISRA 15.7 */ + } if( pPublishInfo->retain == true ) { diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 440eeabe3e..d0ac6f2ddf 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -128,7 +128,7 @@ static bool _topicFilterMatch( const char * pTopicName, * @return `true` if the arguments match the subscription topic filter; `false` * otherwise. */ -static bool _topicMatch( const IotLink_t * pSubscriptionLink, +static bool _topicMatch( const IotLink_t * const pSubscriptionLink, void * pMatch ); /** @@ -140,7 +140,7 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, * @return `true` if the arguments match the subscription's packet info; `false` * otherwise. */ -static bool _packetMatch( const IotLink_t * pSubscriptionLink, +static bool _packetMatch( const IotLink_t * const pSubscriptionLink, void * pMatch ); /*-----------------------------------------------------------*/ @@ -281,7 +281,7 @@ static bool _topicFilterMatch( const char * pTopicName, /*-----------------------------------------------------------*/ -static bool _topicMatch( const IotLink_t * pSubscriptionLink, +static bool _topicMatch( const IotLink_t * const pSubscriptionLink, void * pMatch ) { bool status = false; @@ -319,7 +319,7 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ -static bool _packetMatch( const IotLink_t * pSubscriptionLink, +static bool _packetMatch( const IotLink_t * const pSubscriptionLink, void * pMatch ) { bool match = false; diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 416e02a2e8..40712235f1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -416,6 +416,10 @@ static bool _validateSubscription( bool awsIotMqttMode, { status = _validateWildcardHash( i, pSubscription ); } + else + { + /* Empty else MISRA 15.7 */ + } if( status == false ) { @@ -661,6 +665,10 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, status = false; } + else + { + /* Empty else MISRA 15.7 */ + } if( pPublishOperation != NULL ) { From da072e033916ac7b1bcc897c44622e0254cda129 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 23 Jan 2020 14:57:09 -0800 Subject: [PATCH 386/844] Address MISRA 12.1, 13, and 14 (#726) --- .../standard/common/include/iot_logging_setup.h | 14 +++++++------- libraries/standard/common/lexicon.txt | 1 + libraries/standard/mqtt/src/iot_mqtt_api.c | 16 +++++++++++++--- libraries/standard/mqtt/src/iot_mqtt_operation.c | 7 ++++++- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 14 ++++++++------ .../standard/mqtt/src/iot_mqtt_subscription.c | 4 ++-- libraries/standard/mqtt/src/iot_mqtt_validate.c | 4 ++-- ports/common/include/iot_atomic.h | 4 ++-- 8 files changed, 41 insertions(+), 23 deletions(-) diff --git a/libraries/standard/common/include/iot_logging_setup.h b/libraries/standard/common/include/iot_logging_setup.h index 1d38841818..76e414a3f2 100644 --- a/libraries/standard/common/include/iot_logging_setup.h +++ b/libraries/standard/common/include/iot_logging_setup.h @@ -167,11 +167,11 @@ /* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ #if !defined( LIBRARY_LOG_LEVEL ) || \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE && \ - LIBRARY_LOG_LEVEL != IOT_LOG_ERROR && \ - LIBRARY_LOG_LEVEL != IOT_LOG_WARN && \ - LIBRARY_LOG_LEVEL != IOT_LOG_INFO && \ - LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) + ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." /* Check that LIBRARY_LOG_NAME is defined and has a valid value. */ #elif !defined( LIBRARY_LOG_NAME ) @@ -181,7 +181,7 @@ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE #define IotLog( messageLevel, pLogConfig, ... ) \ IotLog_Generic( LIBRARY_LOG_LEVEL, \ - LIBRARY_LOG_NAME, \ + LIBRARY_LOG_NAME, \ messageLevel, \ pLogConfig, \ __VA_ARGS__ ) @@ -195,7 +195,7 @@ /* If log level is DEBUG, enable the function to print buffers. */ #if LIBRARY_LOG_LEVEL >= IOT_LOG_DEBUG #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) \ - IotLog_GenericPrintBuffer( LIBRARY_LOG_NAME, \ + IotLog_GenericPrintBuffer( LIBRARY_LOG_NAME, \ pHeader, \ pBuffer, \ bufferSize ) diff --git a/libraries/standard/common/lexicon.txt b/libraries/standard/common/lexicon.txt index 8504e952f4..e1354049cd 100644 --- a/libraries/standard/common/lexicon.txt +++ b/libraries/standard/common/lexicon.txt @@ -5,6 +5,7 @@ cancelable cas config const +coverity createjob createrecyclablejob createsystemtaskpool diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 3af0ee0cc5..2532dd59ef 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -42,13 +42,13 @@ #include "iot_atomic.h" /* Validate MQTT configuration settings. */ -#if IOT_MQTT_ENABLE_ASSERTS != 0 && IOT_MQTT_ENABLE_ASSERTS != 1 +#if ( IOT_MQTT_ENABLE_ASSERTS != 0 ) && ( IOT_MQTT_ENABLE_ASSERTS != 1 ) #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." #endif -#if IOT_MQTT_ENABLE_METRICS != 0 && IOT_MQTT_ENABLE_METRICS != 1 +#if ( IOT_MQTT_ENABLE_METRICS != 0 ) && ( IOT_MQTT_ENABLE_METRICS != 1 ) #error "IOT_MQTT_ENABLE_METRICS must be 0 or 1." #endif -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 && IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 +#if ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 ) && ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 ) #error "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." #endif #if IOT_MQTT_RESPONSE_WAIT_MS <= 0 @@ -860,6 +860,11 @@ static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, status = operation->u.operation.status; } + /* Coverity finds a MISRA 13.2 violation in this log statement as the order + * of evaluation for IotMqtt_OperationType and IotMqtt_strerror is not + * defined. This is not an issue as these functions do not change data and + * only convert codes into constant strings. */ + /* coverity[misra_c_2012_rule_13_2_violation] */ IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", operation->pMqttConnection, IotMqtt_OperationType( operation->u.operation.type ), @@ -974,6 +979,11 @@ IotMqttError_t IotMqtt_Init( void ) #endif /* ifdef _IotMqtt_InitSerializeAdditional */ #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + /* If the above preprocessor conditions are satisfied, it is + * possible that status != IOT_MQTT_SUCCESS. Therefore, this + * condition is not an invariant, and the MISRA 14.3 violation is + * a false positive. */ + /* coverity[misra_c_2012_rule_14_3_violation] */ if( status == IOT_MQTT_SUCCESS ) { IotLogInfo( "MQTT library successfully initialized." ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index ad932188ac..04b38140e9 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -180,7 +180,7 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) /* The retry count may be at most one more than the retry limit, which * accounts for the final check for a PUBACK. */ IotMqtt_Assert( pOperation->u.operation.periodic.retry.count == - pOperation->u.operation.periodic.retry.limit + 1U ); + ( pOperation->u.operation.periodic.retry.limit + 1U ) ); IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", pMqttConnection, @@ -1063,6 +1063,11 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_BAD_PARAMETER ); IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_ILLEGAL_OPERATION ); + /* Coverity finds a MISRA 13.2 violation in this log statement as the order + * of evaluation for IotMqtt_OperationType and IotTaskPool_strerror is not + * defined. This is not an issue as these functions do not change data and + * only convert codes into constant strings. */ + /* coverity[misra_c_2012_rule_13_2_violation] */ IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule operation job, error %s.", pOperation->pMqttConnection, IotMqtt_OperationType( pOperation->u.operation.type ), diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 4364abf285..54dede30fe 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -153,7 +153,7 @@ #define MQTT_PACKET_DISCONNECT_SIZE ( 2U ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ /* Username for metrics with AWS IoT. */ -#if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 +#if ( AWS_IOT_MQTT_ENABLE_METRICS == 1 ) || ( DOXYGEN == 1 ) #ifndef AWS_IOT_METRICS_USERNAME /** @@ -535,12 +535,14 @@ static uint8_t * _encodeUserName( uint8_t * pBuffer, if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) { /* Write the high byte of the combined length. */ - *( pBuffer++ ) = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); + *pBuffer = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer++; /* Write the low byte of the combined length. */ - *( pBuffer++ ) = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); + *pBuffer = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer++; /* Write the identity portion of the username. */ memcpy( pBuffer, @@ -1182,7 +1184,7 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, /* Check that the "Remaining length" is greater than the minimum. For * QoS 1 or 2, this will be two bytes greater than for QoS due to the * packet identifier. */ - if( pPublish->remainingLength < qos0Minimum + 2U ) + if( pPublish->remainingLength < ( qos0Minimum + 2U ) ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index d0ac6f2ddf..d7a17e8217 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -156,7 +156,7 @@ static bool _matchEndWildcards( const char * pTopicFilter, /* Determine if the last character is reached for both topic name and topic * filter for the '#' wildcard. */ - endChar = ( nameIndex == topicNameLength - 1U ) && ( filterIndex == topicFilterLength - 3U ); + endChar = ( nameIndex == ( topicNameLength - 1U ) ) && ( filterIndex == ( topicFilterLength - 3U ) ); if( endChar == true ) { @@ -168,7 +168,7 @@ static bool _matchEndWildcards( const char * pTopicFilter, { /* Determine if the last character is reached for both topic name and topic * filter for the '+' wildcard. */ - endChar = ( nameIndex == topicNameLength - 1U ) && ( filterIndex == topicFilterLength - 2U ); + endChar = ( nameIndex == ( topicNameLength - 1U ) ) && ( filterIndex == ( topicFilterLength - 2U ) ); if( endChar == true ) { diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 40712235f1..2629caa0c9 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -454,7 +454,7 @@ static bool _validateWildcardPlus( uint16_t index, if( status == true ) { /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ - if( index < pSubscription->topicFilterLength - 1U ) + if( index < ( pSubscription->topicFilterLength - 1U ) ) { if( pSubscription->pTopicFilter[ index + 1U ] != '/' ) { @@ -478,7 +478,7 @@ static bool _validateWildcardHash( uint16_t index, bool status = true; /* '#' must be the last character in the filter. */ - if( index != pSubscription->topicFilterLength - 1U ) + if( index != ( pSubscription->topicFilterLength - 1U ) ) { IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", pSubscription->topicFilterLength, diff --git a/ports/common/include/iot_atomic.h b/ports/common/include/iot_atomic.h index 50060b175d..bd064af687 100644 --- a/ports/common/include/iot_atomic.h +++ b/ports/common/include/iot_atomic.h @@ -43,7 +43,7 @@ /* Both clang and gcc define __GNUC__, but only clang defines __clang__ */ #ifdef __clang__ /* clang versions 3.1.0 and greater have built-in atomic support. */ - #define CLANG_VERSION ( __clang_major__ * 100 + __clang_minor__ ) + #define CLANG_VERSION ( ( __clang_major__ * 100 ) + __clang_minor__ ) #if CLANG_VERSION > 301 /* clang is compatible with gcc atomic extensions. */ #include "atomic/iot_atomic_gcc.h" @@ -52,7 +52,7 @@ #endif #else /* GCC versions 4.7.0 and greater have built-in atomic support. */ - #define GCC_VERSION ( __GNUC__ * 100 + __GNUC_MINOR__ ) + #define GCC_VERSION ( ( __GNUC__ * 100 ) + __GNUC_MINOR__ ) #if GCC_VERSION >= 407 #include "atomic/iot_atomic_gcc.h" #else From c8a6ce4fb9ad30c20f76f514a25d0b27f6425919 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Thu, 23 Jan 2020 16:03:46 -0800 Subject: [PATCH 387/844] Reduce code complexity. (#740) * fix: MISRA 15.1 (goto) violations - part 2 This PR has changes for iot_mqtt_api.c. * fix: MISRA 15.1 (goto) violations - part 2 This commit has changes for iot_mqtt_operation.c. * fix: MISRA 15.1 (goto) violations - part 2 This commit has changes for iot_mqtt_subscription.c. * fix: MISRA 15.1 (goto) violations - part 2 Addressed review comments. * fix: Add 'misra' word in lexicon file. Added a new word to avoid spellcheck failure. * fix: MISRA 15.1 (goto) violations - part 2 Addressed review comments. * Fix: Reduce code complexity below 9 Files in PR: iot_mqtt_network.c iot_mqtt_operation.c * Fix: Reduce code complexity below 9 Files in PR: iot_mqtt_network.c iot_mqtt_operation.c --- .../standard/mqtt/src/iot_mqtt_network.c | 101 +++++--- .../standard/mqtt/src/iot_mqtt_operation.c | 222 +++++++++++------- 2 files changed, 202 insertions(+), 121 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index a7e800e9f3..1d8270ac78 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -51,6 +51,19 @@ */ static bool _incomingPacketValid( uint8_t packetType ); +/** + * @brief Allocate space for an incoming MQTT packet received from the network. + * + * @param[in] pNetworkConnection Network connection to be used for receive. + * @param[in] pMqttConnection The associated MQTT connection. + * @param[out] pIncomingPacket Output parameter for the incoming packet. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _allocateAndReceivePacket( IotNetworkConnection_t pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); + /** * @brief Get an incoming MQTT packet from the network. * @@ -233,12 +246,60 @@ static bool _incomingPacketValid( uint8_t packetType ) /*-----------------------------------------------------------*/ +static IotMqttError_t _allocateAndReceivePacket( IotNetworkConnection_t pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + size_t dataBytesRead = 0; + + IotMqtt_Assert( pMqttConnection != NULL ); + IotMqtt_Assert( pIncomingPacket != NULL ); + + /* Allocate a buffer for the remaining data and read the data. */ + if( pIncomingPacket->remainingLength > 0U ) + { + pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); + + if( pIncomingPacket->pRemainingData == NULL ) + { + /* In some implementations IotLogError() maps to C standard printing API + * that need specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite stdint.h + * being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ + IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " + "%lu for incoming packet type %lu.", + pMqttConnection, + ( unsigned long ) pIncomingPacket->remainingLength, + ( unsigned long ) pIncomingPacket->type ); + + _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); + + status = IOT_MQTT_NO_MEMORY; + } + + if( status == IOT_MQTT_SUCCESS ) + { + dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, + pIncomingPacket->pRemainingData, + pIncomingPacket->remainingLength ); + + if( dataBytesRead != pIncomingPacket->remainingLength ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + } + + return status; +} + static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t dataBytesRead = 0; /* No buffer for remaining data should be allocated. */ IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); @@ -272,41 +333,9 @@ static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnect if( status == IOT_MQTT_SUCCESS ) { - /* Allocate a buffer for the remaining data and read the data. */ - if( pIncomingPacket->remainingLength > 0U ) - { - pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); - - if( pIncomingPacket->pRemainingData == NULL ) - { - /* In some implementations IotLogError() maps to C standard printing API - * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite stdint.h - * being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " - "%lu for incoming packet type %lu.", - pMqttConnection, - ( unsigned long ) pIncomingPacket->remainingLength, - ( unsigned long ) pIncomingPacket->type ); - - _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); - - status = IOT_MQTT_NO_MEMORY; - } - - if( status == IOT_MQTT_SUCCESS ) - { - dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, - pIncomingPacket->pRemainingData, - pIncomingPacket->remainingLength ); - - if( dataBytesRead != pIncomingPacket->remainingLength ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - } + status = _allocateAndReceivePacket( pNetworkConnection, + pMqttConnection, + pIncomingPacket ); } /* Clean up on error. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 04b38140e9..8f2cd479ef 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -131,6 +131,31 @@ static IotMqttError_t _scheduleCallback( _mqttOperation_t * pOperation ); static bool _completePendingSend( _mqttOperation_t * pOperation, bool * pDestroyOperation ); +/** + * @brief Initialize newly created MQTT operation. + * + * @param[in] pMqttConnection The MQTT connection associated with the operation. + * @param[in] pOperation pointer to the new operation. + * @param[in] flags Flags variable passed to a user-facing MQTT function. + * @param[in] pCallbackInfo User-provided callback function and parameter. + * + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + */ +static IotMqttError_t _initializeOperation( _mqttConnection_t * pMqttConnection, + _mqttOperation_t * pOperation, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo ); + +/** + * @brief Send MQTT Ping Request to the broker. + * + * @param[in] pMqttConnection The MQTT connection associated with the request. + * + * @return `true` if send is successful; `false` otherwise. + */ +static bool _sendPingRequest( _mqttConnection_t * pMqttConnection ); + /*-----------------------------------------------------------*/ static bool _mqttOperation_match( const IotLink_t * const pOperationLink, @@ -440,6 +465,106 @@ static bool _completePendingSend( _mqttOperation_t * pOperation, /*-----------------------------------------------------------*/ +static IotMqttError_t _initializeOperation( _mqttConnection_t * pMqttConnection, + _mqttOperation_t * pOperation, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); + + IotMqtt_Assert( pMqttConnection != NULL ); + IotMqtt_Assert( pOperation != NULL ); + + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + + /* Initialize some members of the new operation. */ + pOperation->pMqttConnection = pMqttConnection; + pOperation->u.operation.jobReference = 1; + pOperation->u.operation.flags = flags; + pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; + + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( waitable == true ) + { + /* Create a semaphore to wait on for a waitable operation. */ + if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) + { + IotLogError( "(MQTT connection %p) Failed to create semaphore for " + "waitable operation.", + pMqttConnection ); + + status = IOT_MQTT_NO_MEMORY; + } + else + { + /* A waitable operation is created with an additional reference for the + * Wait function. */ + ( pOperation->u.operation.jobReference )++; + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->u.operation.notify.callback = *pCallbackInfo; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static bool _sendPingRequest( _mqttConnection_t * pMqttConnection ) +{ + size_t bytesSent = 0; + bool status = true; + uint32_t swapStatus = 0; + _mqttOperation_t * pPingreqOperation = NULL; + + IotMqtt_Assert( pMqttConnection != NULL ); + + IotLogDebug( "(MQTT connection %p) Sending PINGREQ.", pMqttConnection ); + + pPingreqOperation = &( pMqttConnection->pingreq ); + + /* Because PINGREQ may be used to keep the MQTT connection alive, it is + * more important than other operations. Bypass the queue of jobs for + * operations by directly sending the PINGREQ in this job. */ + bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, + pPingreqOperation->u.operation.pMqttPacket, + pPingreqOperation->u.operation.packetSize ); + + if( bytesSent != pPingreqOperation->u.operation.packetSize ) + { + IotLogError( "(MQTT connection %p) Failed to send PINGREQ.", pMqttConnection ); + status = false; + } + else + { + /* Assume the keep-alive will fail. The network receive callback will + * clear the failure flag upon receiving a PINGRESP. */ + swapStatus = Atomic_CompareAndSwap_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), + 1, + 0 ); + IotMqtt_Assert( swapStatus == 1U ); + + /* Set the period for scheduling a PINGRESP check. */ + pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; + + IotLogDebug( "(MQTT connection %p) PINGREQ sent.", pMqttConnection ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, @@ -496,62 +621,23 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, status = IOT_MQTT_NO_MEMORY; } - else - { - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - - /* Initialize some members of the new operation. */ - pOperation->pMqttConnection = pMqttConnection; - pOperation->u.operation.jobReference = 1; - pOperation->u.operation.flags = flags; - pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; - } } if( status == IOT_MQTT_SUCCESS ) { - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( waitable == true ) - { - /* Create a semaphore to wait on for a waitable operation. */ - if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) - { - IotLogError( "(MQTT connection %p) Failed to create semaphore for " - "waitable operation.", - pMqttConnection ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* A waitable operation is created with an additional reference for the - * Wait function. */ - ( pOperation->u.operation.jobReference )++; - } - } - else - { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) - { - pOperation->u.operation.notify.callback = *pCallbackInfo; - } - } + status = _initializeOperation( pMqttConnection, pOperation, flags, pCallbackInfo ); + } - if( status == IOT_MQTT_SUCCESS ) - { - /* Add this operation to the MQTT connection's operation list. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + if( status == IOT_MQTT_SUCCESS ) + { + /* Add this operation to the MQTT connection's operation list. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - /* Set the output parameter. */ - *pNewOperation = pOperation; - } + /* Set the output parameter. */ + *pNewOperation = pOperation; } /* Clean up operation and decrement reference count if this function failed. */ @@ -720,12 +806,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, void * pContext ) { bool status = true; - uint32_t swapStatus = 0; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - size_t bytesSent = 0; - - /* Swap status is not checked when asserts are disabled. */ - ( void ) swapStatus; /* Retrieve the MQTT connection from the context. */ _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; @@ -750,36 +831,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, if( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) { - IotLogDebug( "(MQTT connection %p) Sending PINGREQ.", pMqttConnection ); - - /* Because PINGREQ may be used to keep the MQTT connection alive, it is - * more important than other operations. Bypass the queue of jobs for - * operations by directly sending the PINGREQ in this job. */ - bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, - pPingreqOperation->u.operation.pMqttPacket, - pPingreqOperation->u.operation.packetSize ); - - if( bytesSent != pPingreqOperation->u.operation.packetSize ) - { - IotLogError( "(MQTT connection %p) Failed to send PINGREQ.", pMqttConnection ); - status = false; - } - else - { - /* Assume the keep-alive will fail. The network receive callback will - * clear the failure flag upon receiving a PINGRESP. */ - swapStatus = Atomic_CompareAndSwap_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), - 1, - 0 ); - IotMqtt_Assert( swapStatus == 1U ); - - /* Set the period for scheduling a PINGRESP check. */ - pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; - - IotLogDebug( "(MQTT connection %p) PINGREQ sent. Scheduling check for PINGRESP in %d ms.", - pMqttConnection, - IOT_MQTT_RESPONSE_WAIT_MS ); - } + status = _sendPingRequest( pMqttConnection ); } else { From 410a197c23a65b6f7890b3c11cbd4f172f6f7388 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 24 Jan 2020 16:08:43 -0800 Subject: [PATCH 388/844] Address MISRA 17.7, 17.8, and 18.7 in MQTT (#741) * Address MISRA 17.7 violations * Address MISRA 17.8 violations * Ignore MISRA 18.7 for MQTT subscriptions struct * Rename synchronous flags --- libraries/standard/mqtt/src/iot_mqtt_api.c | 10 +- .../standard/mqtt/src/iot_mqtt_operation.c | 2 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 126 +++++++++--------- .../mqtt/src/private/iot_mqtt_internal.h | 3 + ports/common/include/atomic/iot_atomic_gcc.h | 1 + 5 files changed, 76 insertions(+), 66 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 2532dd59ef..93f0786f0f 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -1646,21 +1646,23 @@ IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, * pPublishOperation = NULL; + /* Set only the "serial" flag. */ + uint32_t syncFlags = MQTT_INTERNAL_FLAG_BLOCK_ON_SEND; - /* Clear the flags, setting only the "serial" flag. */ - flags = MQTT_INTERNAL_FLAG_BLOCK_ON_SEND; + /* Flags are currently ignored. */ + ( void ) flags; /* Set the waitable flag and reference for QoS 1 PUBLISH. */ if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { - flags |= IOT_MQTT_FLAG_WAITABLE; + syncFlags |= IOT_MQTT_FLAG_WAITABLE; pPublishOperation = &publishOperation; } /* Call the asynchronous PUBLISH function. */ status = IotMqtt_PublishAsync( mqttConnection, pPublishInfo, - flags, + syncFlags, NULL, pPublishOperation ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 8f2cd479ef..7d4e4ad5a2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -1144,7 +1144,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotLink_t * pResultLink = NULL; _operationMatchParam_t operationMatchParams; - memset( &operationMatchParams, 0, sizeof( _operationMatchParam_t ) ); + ( void ) memset( &operationMatchParams, 0, sizeof( _operationMatchParam_t ) ); /* Set the members of the search parameter. */ operationMatchParams.type = type; diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 54dede30fe..bd151fd1f4 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -226,13 +226,13 @@ static uint8_t * _encodeString( uint8_t * pDestination, /** * @brief Encode a username into a CONNECT packet, if necessary. * - * @param[out] pBuffer Buffer for the CONNECT packet. + * @param[out] pDestination Buffer for the CONNECT packet. * @param[in] pConnectInfo User-provided CONNECT information. * * @return Pointer to the end of the encoded string, which will be identical to - * `pBuffer` if nothing was encoded. + * `pDestination` if nothing was encoded. */ -static uint8_t * _encodeUserName( uint8_t * pBuffer, +static uint8_t * _encodeUserName( uint8_t * pDestination, const IotMqttConnectInfo_t * pConnectInfo ); /** @@ -289,13 +289,13 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, * * @param[in] pConnectInfo User-provided CONNECT information. * @param[in] remainingLength User provided remaining length. - * @param[in, out] pBuffer User provided buffer where the CONNECT packet is written. - * @param[in] connectPacketSize Size of the buffer pointed to by `pBuffer`. + * @param[in, out] pPacket User provided buffer where the CONNECT packet is written. + * @param[in] connectPacketSize Size of the buffer pointed to by `pPacket`. * */ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, size_t remainingLength, - uint8_t * pBuffer, + uint8_t * pPacket, size_t connectPacketSize ); /** @@ -306,15 +306,15 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier * is written. - * @param[in, out] pBuffer User provided buffer where the PUBLISH packet is written. - * @param[in] publishPacketSize Size of buffer pointed to by `pBuffer`. + * @param[in, out] pPacket User provided buffer where the PUBLISH packet is written. + * @param[in] publishPacketSize Size of buffer pointed to by `pPacket`. * */ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, + uint8_t * pPacket, size_t publishPacketSize ); /** @@ -324,15 +324,15 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, * @param[in] subscriptionCount Size of `pSubscriptionList`. * @param[in] remainingLength User provided remaining length. * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. - * @param[in, out] pBuffer User provided buffer where the SUBSCRIBE packet is written. - * @param[in] subscribePacketSize Size of the buffer pointed to by `pBuffer`. + * @param[in, out] pPacket User provided buffer where the SUBSCRIBE packet is written. + * @param[in] subscribePacketSize Size of the buffer pointed to by `pPacket`. * */ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, - uint8_t * pBuffer, + uint8_t * pPacket, size_t subscribePacketSize ); /** @@ -342,15 +342,15 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList * @param[in] subscriptionCount Size of `pSubscriptionList`. * @param[in] remainingLength User provided remaining length. * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. - * @param[in, out] pBuffer User provided buffer where the UNSUBSCRIBE packet is written. - * @param[in] unsubscribePacketSize size of the buffer pointed to by `pBuffer`. + * @param[in, out] pPacket User provided buffer where the UNSUBSCRIBE packet is written. + * @param[in] unsubscribePacketSize size of the buffer pointed to by `pPacket`. * */ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, - uint8_t * pBuffer, + uint8_t * pPacket, size_t unsubscribePacketSize ); /** @@ -466,15 +466,16 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, size_t length ) { uint8_t lengthByte = 0, * pLengthEnd = pDestination; + size_t remainingLength = length; /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { - lengthByte = length % 128U; - length = length / 128U; + lengthByte = remainingLength % 128U; + remainingLength = remainingLength / 128U; /* Set the high bit of this byte, indicating that there's more data. */ - if( length > 0U ) + if( remainingLength > 0U ) { UINT8_SET_BIT( lengthByte, 7 ); } @@ -482,7 +483,7 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /* Output a single encoded byte. */ *pLengthEnd = lengthByte; pLengthEnd++; - } while( length > 0U ); + } while( remainingLength > 0U ); return pLengthEnd; } @@ -493,29 +494,32 @@ static uint8_t * _encodeString( uint8_t * pDestination, const char * source, uint16_t sourceLength ) { + uint8_t * pBuffer = pDestination; + /* The first byte of a UTF-8 string is the high byte of the string length. */ - *pDestination = UINT16_HIGH_BYTE( sourceLength ); - pDestination++; + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; /* The second byte of a UTF-8 string is the low byte of the string length. */ - *pDestination = UINT16_LOW_BYTE( sourceLength ); - pDestination++; + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; - /* Copy the string into pDestination. */ - ( void ) memcpy( pDestination, source, sourceLength ); + /* Copy the string into pBuffer. */ + ( void ) memcpy( pBuffer, source, sourceLength ); /* Return the pointer to the end of the encoded string. */ - pDestination += sourceLength; + pBuffer += sourceLength; - return pDestination; + return pBuffer; } /*-----------------------------------------------------------*/ -static uint8_t * _encodeUserName( uint8_t * pBuffer, +static uint8_t * _encodeUserName( uint8_t * pDestination, const IotMqttConnectInfo_t * pConnectInfo ) { bool encodedUserName = false; + uint8_t * pBuffer = pDestination; /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the @@ -545,15 +549,15 @@ static uint8_t * _encodeUserName( uint8_t * pBuffer, pBuffer++; /* Write the identity portion of the username. */ - memcpy( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); + ( void ) memcpy( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); pBuffer += pConnectInfo->userNameLength; /* Write the metrics portion of the username. */ - memcpy( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); + ( void ) memcpy( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; encodedUserName = true; @@ -766,14 +770,14 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, size_t remainingLength, - uint8_t * pBuffer, + uint8_t * pPacket, size_t connectPacketSize ) { uint8_t connectFlags = 0; - uint8_t * pConnectPacket = pBuffer; + uint8_t * pBuffer = pPacket; /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pConnectPacket; + ( void ) pPacket; ( void ) connectPacketSize; /* The first byte in the CONNECT packet is the control packet type. */ @@ -888,10 +892,10 @@ static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, /* Ensure that the difference between the end and beginning of the buffer * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pConnectPacket ) ) == connectPacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == connectPacketSize ); /* Print out the serialized CONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT CONNECT packet:", pConnectPacket, connectPacketSize ); + IotLog_PrintBuffer( "MQTT CONNECT packet:", pPacket, connectPacketSize ); } /*-----------------------------------------------------------*/ @@ -900,15 +904,15 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, + uint8_t * pPacket, size_t publishPacketSize ) { uint8_t publishFlags = 0; uint16_t packetIdentifier = 0; - uint8_t * pPublishPacket = pBuffer; + uint8_t * pBuffer = pPacket; /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPublishPacket; + ( void ) pPacket; ( void ) publishPacketSize; /* The first byte of a PUBLISH packet contains the packet type and flags. */ @@ -973,10 +977,10 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, /* Ensure that the difference between the end and beginning of the buffer * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPublishPacket ) ) == publishPacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == publishPacketSize ); /* Print out the serialized PUBLISH packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPublishPacket, publishPacketSize ); + IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPacket, publishPacketSize ); } /*-----------------------------------------------------------*/ @@ -985,15 +989,15 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, - uint8_t * pBuffer, + uint8_t * pPacket, size_t subscribePacketSize ) { uint16_t packetIdentifier = 0; size_t i = 0; - uint8_t * pSubscribePacket = pBuffer; + uint8_t * pBuffer = pPacket; /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pSubscribePacket; + ( void ) pPacket; ( void ) subscribePacketSize; /* The first byte in SUBSCRIBE is the packet type. */ @@ -1027,10 +1031,10 @@ static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList /* Ensure that the difference between the end and beginning of the buffer * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pSubscribePacket ) ) == subscribePacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == subscribePacketSize ); /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pSubscribePacket, subscribePacketSize ); + IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pPacket, subscribePacketSize ); } /*-----------------------------------------------------------*/ @@ -1039,15 +1043,15 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi size_t subscriptionCount, size_t remainingLength, uint16_t * pPacketIdentifier, - uint8_t * pBuffer, + uint8_t * pPacket, size_t unsubscribePacketSize ) { uint16_t packetIdentifier = 0; size_t i = 0; - uint8_t * pUnsubscribePacket = pBuffer; + uint8_t * pBuffer = pPacket; /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pUnsubscribePacket; + ( void ) pPacket; ( void ) unsubscribePacketSize; /* The first byte in UNSUBSCRIBE is the packet type. */ @@ -1077,10 +1081,10 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi /* Ensure that the difference between the end and beginning of the buffer * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pUnsubscribePacket ) ) == unsubscribePacketSize ); + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == unsubscribePacketSize ); /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pUnsubscribePacket, unsubscribePacketSize ); + IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pPacket, unsubscribePacketSize ); } /*-----------------------------------------------------------*/ @@ -2461,9 +2465,9 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, /* Call internal function with local variables, as disconnect uses * static memory, there is no need to pass the buffer * Note: _IotMqtt_SerializeDisconnect always succeeds */ - _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); + ( void ) _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); - memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); + ( void ) memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); } return status; @@ -2494,8 +2498,8 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, /* Call internal function with local variables, as ping request uses * static memory, there is no need to pass the buffer * Note: _IotMqtt_SerializePingReq always succeeds */ - _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); - memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); + ( void ) _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); + ( void ) memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); } return status; @@ -2524,13 +2528,13 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) else { /* Set internal mqtt packet parameters. */ - memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); + ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); mqttPacket.pRemainingData = pMqttPacket->pRemainingData; mqttPacket.remainingLength = pMqttPacket->remainingLength; mqttPacket.type = pMqttPacket->type; /* Set Publish specific parameters */ - memset( ( void * ) &mqttOperation, 0x00, sizeof( _mqttOperation_t ) ); + ( void ) memset( ( void * ) &mqttOperation, 0x00, sizeof( _mqttOperation_t ) ); mqttOperation.incomingPublish = true; mqttPacket.u.pIncomingPublish = &mqttOperation; status = _IotMqtt_DeserializePublish( &mqttPacket ); @@ -2562,7 +2566,7 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) if( status == IOT_MQTT_SUCCESS ) { /* Set internal mqtt packet parameters. */ - memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); + ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); mqttPacket.pRemainingData = pMqttPacket->pRemainingData; mqttPacket.remainingLength = pMqttPacket->remainingLength; diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 27ad9d4fe7..57e4d9a039 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -420,6 +420,9 @@ typedef struct _mqttSubscription IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ + /* A flexible length array is used here so that the topic filter may + * be of an arbitrary length. */ + /* coverity[misra_c_2012_rule_18_7_violation] */ char pTopicFilter[]; /**< @brief The subscription topic filter. */ } _mqttSubscription_t; diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h index 6611f923d7..77e00b8dfc 100644 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ b/ports/common/include/atomic/iot_atomic_gcc.h @@ -85,6 +85,7 @@ static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_17_7_violation] */ __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); return pOldValue; From 352b98e032b3bd2244f26557ae86f7e012a29297 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Sat, 25 Jan 2020 17:24:11 -0800 Subject: [PATCH 389/844] Fix MISRA Rule 10.8 on MQTT files. (#744) Fix missed 10.4 and 10.3 rules. --- libraries/standard/mqtt/src/iot_mqtt_api.c | 4 ++-- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 14 +++++++------- .../standard/mqtt/src/iot_mqtt_static_memory.c | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 93f0786f0f..23d3866dce 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -387,8 +387,8 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo pMqttConnection->pingreq.u.operation.type = IOT_MQTT_PINGREQ; /* Convert the keep-alive interval to milliseconds. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = ( uint32_t ) ( keepAliveSeconds * 1000U ); - pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = ( uint32_t ) ( keepAliveSeconds * 1000U ); + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = ( uint32_t ) keepAliveSeconds * 1000U; + pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = ( uint32_t ) keepAliveSeconds * 1000U; /* Generate a PINGREQ packet. */ serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index bd151fd1f4..8a55fe4cb1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -471,7 +471,7 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { - lengthByte = remainingLength % 128U; + lengthByte = ( uint8_t ) ( remainingLength % 128U ); remainingLength = remainingLength / 128U; /* Set the high bit of this byte, indicating that there's more data. */ @@ -615,8 +615,8 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - connectPacketSize += ( size_t ) ( AWS_IOT_METRICS_USERNAME_LENGTH + - pConnectInfo->userNameLength + ( uint16_t ) ( sizeof( uint16_t ) ) ); + connectPacketSize += ( AWS_IOT_METRICS_USERNAME_LENGTH + + ( size_t ) ( pConnectInfo->userNameLength ) + sizeof( uint16_t ) ); encodedUserName = true; #endif } @@ -1307,7 +1307,7 @@ size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, pNetworkInterface, &encodedByte ) == true ) { - remainingLength += ( size_t ) ( encodedByte & 0x7FU ) * multiplier; + remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; multiplier *= 128U; bytesDecoded++; } @@ -1358,7 +1358,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConne { if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) { - remainingLength += ( size_t ) ( encodedByte & 0x7FU ) * multiplier; + remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; multiplier *= 128U; bytesDecoded++; } @@ -1708,7 +1708,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) "Packet identifier %hu.", pPublish->packetIdentifier ); /* Packet identifier cannot be 0. */ - if( pPublish->packetIdentifier == 0 ) + if( pPublish->packetIdentifier == 0U ) { status = IOT_MQTT_BAD_RESPONSE; } @@ -1726,7 +1726,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) } else { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2U * sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } diff --git a/libraries/standard/mqtt/src/iot_mqtt_static_memory.c b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c index 3186ff45eb..ba1ba921d3 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_static_memory.c +++ b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c @@ -92,7 +92,7 @@ static uint32_t _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief MQTT operations. */ static uint32_t _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0U }; /**< @brief MQTT subscription in-use flags. */ -static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ +static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ] = { { '\0' } }; /**< @brief MQTT subscriptions. */ /*-----------------------------------------------------------*/ From f64f64a05dde7217a13e5974ff206c4ba7443a19 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Mon, 27 Jan 2020 20:11:48 -0800 Subject: [PATCH 390/844] Fix MISRA 20.7 in iot_mqtt_serialize.c (#749) --- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 8a55fe4cb1..aa0a43b7fe 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -46,8 +46,8 @@ /* * Macros for reading the high and low byte of a 2-byte unsigned int. */ -#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ffU ) ) /**< @brief Get low byte. */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) /**< @brief Get high byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) /**< @brief Get low byte. */ /** * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. @@ -56,7 +56,7 @@ */ #define UINT16_DECODE( ptr ) \ ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ - ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) + ( ( uint16_t ) ( *( ( ptr ) + 1 ) ) ) ) /** * @brief Macro for setting a bit in a 1-byte unsigned int. @@ -64,7 +64,7 @@ * @param[in] x The unsigned int to set. * @param[in] position Which bit to set. */ -#define UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01U << position ) ) ) +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) /** * @brief Macro for checking if a bit is set in a 1-byte unsigned int. @@ -72,7 +72,7 @@ * @param[in] x The unsigned int to check. * @param[in] position Which bit to check. */ -#define UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01U << position ) ) == ( 0x01U << position ) ) +#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) /* * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT @@ -1202,6 +1202,8 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, return status; } +/*-----------------------------------------------------------*/ + static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, IotMqttPublishInfo_t * pOutput ) { From 350a749390dc39732bdfe548252c3c6718ad3027 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 28 Jan 2020 00:53:39 -0800 Subject: [PATCH 391/844] MQTT MISRA 7.4, 8.2, 8.3, 8.4 (#747) * Address MISRA 7.4 false positive * Address MISRA 8.4 * Address MISRA 8.2 * Address MISRA 8.3 * Address MISRA 8.13 - part 1 --- libraries/platform/types/iot_platform_types.h | 4 +-- .../common/include/iot_linear_containers.h | 36 +++++++++---------- .../mqtt/include/iot_mqtt_serialize.h | 4 +-- .../mqtt/include/types/iot_mqtt_types.h | 8 ++--- libraries/standard/mqtt/src/iot_mqtt_api.c | 4 +-- .../standard/mqtt/src/iot_mqtt_network.c | 10 +++--- .../standard/mqtt/src/iot_mqtt_operation.c | 4 +-- .../standard/mqtt/src/iot_mqtt_serialize.c | 21 +++++------ .../standard/mqtt/src/iot_mqtt_subscription.c | 14 ++++---- .../standard/mqtt/src/iot_mqtt_validate.c | 2 +- .../mqtt/src/private/iot_mqtt_internal.h | 3 +- 11 files changed, 56 insertions(+), 54 deletions(-) diff --git a/libraries/platform/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h index 5ec68735b2..31d36bed27 100644 --- a/libraries/platform/types/iot_platform_types.h +++ b/libraries/platform/types/iot_platform_types.h @@ -104,10 +104,10 @@ typedef _IotSystemSemaphore_t IotSemaphore_t; /** * @brief Thread routine function. * - * @param[in] void * The argument passed to the @ref + * @param[in] pArgument The argument passed to the @ref * platform_threads_function_createdetachedthread. For application use. */ -typedef void ( * IotThreadRoutine_t )( void * ); +typedef void ( * IotThreadRoutine_t )( void * pArgument ); /*-------------------------- Clock and timer types --------------------------*/ diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h index 843f5065db..f392ef371a 100644 --- a/libraries/standard/common/include/iot_linear_containers.h +++ b/libraries/standard/common/include/iot_linear_containers.h @@ -442,7 +442,7 @@ static inline void IotListDouble_InsertAfter( IotLink_t * const pElement, /* @[declare_linear_containers_list_double_insertsorted] */ static inline void IotListDouble_InsertSorted( IotListDouble_t * const pList, IotLink_t * const pLink, - int32_t ( *compare )( const IotLink_t * const, const IotLink_t * const ) ) + int32_t ( *compare )( const IotLink_t * const pParam1, const IotLink_t * const pParam2 ) ) /* @[declare_linear_containers_list_double_insertsorted] */ { /* This function must not be called with NULL parameters. */ @@ -514,7 +514,7 @@ static inline void IotListDouble_Remove( IotLink_t * const pLink ) * the address of the link's container. */ /* @[declare_linear_containers_list_double_removehead] */ -static inline IotLink_t * IotListDouble_RemoveHead( IotListDouble_t * const pList ) +static inline IotLink_t * IotListDouble_RemoveHead( const IotListDouble_t * const pList ) /* @[declare_linear_containers_list_double_removehead] */ { IotLink_t * pHead = NULL; @@ -538,7 +538,7 @@ static inline IotLink_t * IotListDouble_RemoveHead( IotListDouble_t * const pLis * the address of the link's container. */ /* @[declare_linear_containers_list_double_removetail] */ -static inline IotLink_t * IotListDouble_RemoveTail( IotListDouble_t * const pList ) +static inline IotLink_t * IotListDouble_RemoveTail( const IotListDouble_t * const pList ) /* @[declare_linear_containers_list_double_removetail] */ { IotLink_t * pTail = NULL; @@ -564,8 +564,8 @@ static inline IotLink_t * IotListDouble_RemoveTail( IotListDouble_t * const pLis * or its value is `0`. */ /* @[declare_linear_containers_list_double_removeall] */ -static inline void IotListDouble_RemoveAll( IotListDouble_t * const pList, - void ( *freeElement )( void * ), +static inline void IotListDouble_RemoveAll( const IotListDouble_t * const pList, + void ( *freeElement )( void * pData ), size_t linkOffset ) /* @[declare_linear_containers_list_double_removeall] */ { @@ -617,7 +617,7 @@ static inline void IotListDouble_RemoveAll( IotListDouble_t * const pList, /* @[declare_linear_containers_list_double_findfirstmatch] */ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * const pList, const IotLink_t * const pStartPoint, - bool ( *isMatch )( const IotLink_t * const, void * ), + bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), void * pMatch ) /* @[declare_linear_containers_list_double_findfirstmatch] */ { @@ -681,9 +681,9 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * * the address of the link's container. */ /* @[declare_linear_containers_list_double_removefirstmatch] */ -static inline IotLink_t * IotListDouble_RemoveFirstMatch( IotListDouble_t * const pList, +static inline IotLink_t * IotListDouble_RemoveFirstMatch( const IotListDouble_t * const pList, const IotLink_t * const pStartPoint, - bool ( *isMatch )( const IotLink_t * const, void * ), + bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), void * pMatch ) /* @[declare_linear_containers_list_double_removefirstmatch] */ { @@ -717,10 +717,10 @@ static inline IotLink_t * IotListDouble_RemoveFirstMatch( IotListDouble_t * cons * or its value is `0`. */ /* @[declare_linear_containers_list_double_removeallmatches] */ -static inline void IotListDouble_RemoveAllMatches( IotListDouble_t * const pList, - bool ( *isMatch )( const IotLink_t * const, void * ), +static inline void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), void * pMatch, - void ( *freeElement )( void * ), + void ( *freeElement )( void * pData ), size_t linkOffset ) /* @[declare_linear_containers_list_double_removeallmatches] */ { @@ -858,7 +858,7 @@ static inline void IotDeQueue_EnqueueHead( IotDeQueue_t * const pQueue, * the address of the link's container. */ /* @[declare_linear_containers_queue_dequeuehead] */ -static inline IotLink_t * IotDeQueue_DequeueHead( IotDeQueue_t * const pQueue ) +static inline IotLink_t * IotDeQueue_DequeueHead( const IotDeQueue_t * const pQueue ) /* @[declare_linear_containers_queue_dequeuehead] */ { return IotListDouble_RemoveHead( pQueue ); @@ -888,7 +888,7 @@ static inline void IotDeQueue_EnqueueTail( IotDeQueue_t * const pQueue, * the address of the link's container. */ /* @[declare_linear_containers_queue_dequeuetail] */ -static inline IotLink_t * IotDeQueue_DequeueTail( IotDeQueue_t * const pQueue ) +static inline IotLink_t * IotDeQueue_DequeueTail( const IotDeQueue_t * const pQueue ) /* @[declare_linear_containers_queue_dequeuetail] */ { return IotListDouble_RemoveTail( pQueue ); @@ -918,8 +918,8 @@ static inline void IotDeQueue_Remove( IotLink_t * const pLink ) * or its value is `0`. */ /* @[declare_linear_containers_queue_removeall] */ -static inline void IotDeQueue_RemoveAll( IotDeQueue_t * const pQueue, - void ( * freeElement )( void * ), +static inline void IotDeQueue_RemoveAll( const IotDeQueue_t * const pQueue, + void ( * freeElement )( void * pData ), size_t linkOffset ) /* @[declare_linear_containers_queue_removeall] */ { @@ -943,10 +943,10 @@ static inline void IotDeQueue_RemoveAll( IotDeQueue_t * const pQueue, * or its value is `0`. */ /* @[declare_linear_containers_queue_removeallmatches] */ -static inline void IotDeQueue_RemoveAllMatches( IotDeQueue_t * const pQueue, - bool ( * isMatch )( const IotLink_t * const, void * ), +static inline void IotDeQueue_RemoveAllMatches( const IotDeQueue_t * const pQueue, + bool ( * isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), void * pMatch, - void ( * freeElement )( void * ), + void ( * freeElement )( void * pData ), size_t linkOffset ) /* @[declare_linear_containers_queue_removeallmatches] */ { diff --git a/libraries/standard/mqtt/include/iot_mqtt_serialize.h b/libraries/standard/mqtt/include/iot_mqtt_serialize.h index 1b864f965d..885c576ac0 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_serialize.h +++ b/libraries/standard/mqtt/include/iot_mqtt_serialize.h @@ -378,7 +378,7 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs * @endcode */ /* @[declare_mqtt_getpublishpacketsize] */ -IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo, +IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, size_t * pRemainingLength, size_t * pPacketSize ); /* @[declare_mqtt_getpublishpacketsize] */ @@ -443,7 +443,7 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo * @endcode */ /* @[declare_mqtt_serializepublish] */ -IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, +IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh, diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 2dc3dc19d2..d36aa686d2 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -511,14 +511,14 @@ typedef struct IotMqttCallbackInfo /** * @brief User-provided callback function signature. * - * @param[in] void * #IotMqttCallbackInfo_t.pCallbackContext - * @param[in] IotMqttCallbackParam_t * Details on the outcome of the MQTT operation + * @param[in] pCallbackContext #IotMqttCallbackInfo_t.pCallbackContext. + * @param[in] pCallbackParam Details on the outcome of the MQTT operation * or an incoming MQTT PUBLISH. * * @see #IotMqttCallbackParam_t for more information on the second parameter. */ - void ( * function )( void *, - IotMqttCallbackParam_t * ); + void ( * function )( void * pCallbackContext, + IotMqttCallbackParam_t * pCallbackParam ); } IotMqttCallbackInfo_t; /** diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 23d3866dce..12704b7795 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -169,7 +169,7 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, - IotMqttOperation_t * const pOperationReference ); + const IotMqttOperation_t * const pOperationReference ); /** * @brief Utility function for creating and serializing subscription requests @@ -633,7 +633,7 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, - IotMqttOperation_t * const pOperationReference ) + const IotMqttOperation_t * const pOperationReference ) { IotMqttError_t status = IOT_MQTT_SUCCESS; diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 1d8270ac78..12f6e3c65c 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -94,7 +94,7 @@ static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket, IotMqttDeserialize_t _deserializer, IotMqttOperationType_t opType, - uint16_t * pPacketIdentifier ); + const uint16_t * pPacketIdentifier ); /** * @brief Deserialize a PUBLISH packet. @@ -356,7 +356,7 @@ static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket, IotMqttDeserialize_t _deserializer, IotMqttOperationType_t opType, - uint16_t * pPacketIdentifier ) + const uint16_t * pPacketIdentifier ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttOperation_t * pOperation = NULL; @@ -730,11 +730,11 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason void * pDisconnectCallbackContext = NULL; /* Disconnect callback function. */ - void ( * disconnectCallback )( void *, - IotMqttCallbackParam_t * ) = NULL; + void ( * disconnectCallback )( void * pContext, + IotMqttCallbackParam_t * pParam ) = NULL; /* Network close function. */ - IotNetworkError_t ( * closeConnection) ( IotNetworkConnection_t ) = NULL; + IotNetworkError_t ( * closeConnection) ( IotNetworkConnection_t pConnection ) = NULL; /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 7d4e4ad5a2..e2cd2f45b8 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -167,10 +167,10 @@ static bool _mqttOperation_match( const IotLink_t * const pOperationLink, * must never be NULL. */ IotMqtt_Assert( pOperationLink != NULL ); - _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, + const _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, pOperationLink, link ); - _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; + const _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; /* Check for matching operations. */ if( pParam->type == pOperation->u.operation.type ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index aa0a43b7fe..073eccab57 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -364,7 +364,7 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi */ static IotMqttError_t _decodeSubackStatus( size_t statusCount, const uint8_t * pStatusStart, - _mqttPacket_t * pSuback ); + const _mqttPacket_t * pSuback ); /** * @brief Check the remaining length against some value for QoS 0 or QoS 1/2. @@ -377,7 +377,7 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, * * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. */ -static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, +static IotMqttError_t _checkRemainingLength( const _mqttPacket_t * pPublish, IotMqttQos_t qos, size_t qos0Minimum ); @@ -391,7 +391,7 @@ static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE. */ -static IotMqttError_t _processIncomingPublishFlags( const uint8_t publishFlags, +static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, IotMqttPublishInfo_t * pOutput ); /*-----------------------------------------------------------*/ @@ -520,6 +520,7 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, { bool encodedUserName = false; uint8_t * pBuffer = pDestination; + const char * pMetricsUserName = AWS_IOT_METRICS_USERNAME; /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the @@ -556,7 +557,7 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, /* Write the metrics portion of the username. */ ( void ) memcpy( pBuffer, - AWS_IOT_METRICS_USERNAME, + pMetricsUserName, AWS_IOT_METRICS_USERNAME_LENGTH ); pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; @@ -568,7 +569,7 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, /* The username is not being used for authentication, but * metrics are enabled. */ pBuffer = _encodeString( pBuffer, - AWS_IOT_METRICS_USERNAME, + pMetricsUserName, AWS_IOT_METRICS_USERNAME_LENGTH ); encodedUserName = true; @@ -1091,7 +1092,7 @@ static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionLi static IotMqttError_t _decodeSubackStatus( size_t statusCount, const uint8_t * pStatusStart, - _mqttPacket_t * pSuback ) + const _mqttPacket_t * pSuback ) { IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t subscriptionStatus = 0; @@ -1163,7 +1164,7 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, /*-----------------------------------------------------------*/ -static IotMqttError_t _checkRemainingLength( _mqttPacket_t * pPublish, +static IotMqttError_t _checkRemainingLength( const _mqttPacket_t * pPublish, IotMqttQos_t qos, size_t qos0Minimum ) { @@ -1451,7 +1452,7 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) /* If logging is enabled, declare the CONNACK response code strings. The * fourth byte of CONNACK indexes into this array for the corresponding response. */ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - static const char * pConnackResponses[ 6 ] = + static const char * const pConnackResponses[ 6 ] = { "Connection accepted.", /* 0 */ "Connection refused: unacceptable protocol version.", /* 1 */ @@ -2318,7 +2319,7 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo, +IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, size_t * pRemainingLength, size_t * pPacketSize ) { @@ -2362,7 +2363,7 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, +IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh, diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index d7a17e8217..408d4e1a51 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -290,10 +290,10 @@ static bool _topicMatch( const IotLink_t * const pSubscriptionLink, * will never pass NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); - _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, + const _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; + const _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; /* Extract the relevant strings and lengths from parameters. */ const char * pTopicName = pParam->pTopicName; @@ -331,7 +331,7 @@ static bool _packetMatch( const IotLink_t * const pSubscriptionLink, _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pMatch; + const _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pMatch; /* Compare packet identifiers. */ if( pParam->packetIdentifier == pSubscription->packetInfo.identifier ) @@ -458,8 +458,8 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, IotLink_t * pCurrentLink = NULL, * pNextLink = NULL; void * pCallbackContext = NULL; - void ( * callbackFunction )( void *, - IotMqttCallbackParam_t * ) = NULL; + void ( * callbackFunction )( void * pContext, + IotMqttCallbackParam_t * pParam ) = NULL; _topicMatchParams_t topicMatchParams = { 0 }; /* Set the members of the search parameter. */ @@ -629,8 +629,8 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, IotMqttSubscription_t * const pCurrentSubscription ) { bool status = false; - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; + const _mqttSubscription_t * pSubscription = NULL; + const IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { 0 }; /* Set the members of the search parameter. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 2629caa0c9..a71a404ce7 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -633,7 +633,7 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pPublishOperation ) + const IotMqttOperation_t * const pPublishOperation ) { bool status = true; size_t maximumPayloadLength = MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 57e4d9a039..f67136209c 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -37,6 +37,7 @@ /* MQTT include. */ #include "iot_mqtt.h" +#include "iot_mqtt_serialize.h" /* Task pool include. */ #include "iot_taskpool.h" @@ -482,7 +483,7 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pPublishOperation ); + const IotMqttOperation_t * const pPublishOperation ); /** * @brief Check that an #IotMqttPublishInfo_t is valid for an LWT publish From 568df1c4b0ab3908fafeacc49fbd8890e14ae603 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Tue, 28 Jan 2020 17:03:12 -0800 Subject: [PATCH 392/844] Remove goto statements in aws_iot_shadow_operation.c (#746) --- .../aws/shadow/src/aws_iot_shadow_operation.c | 438 +++++++++--------- 1 file changed, 209 insertions(+), 229 deletions(-) diff --git a/libraries/aws/shadow/src/aws_iot_shadow_operation.c b/libraries/aws/shadow/src/aws_iot_shadow_operation.c index c8ea058784..7b267c1fd4 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_operation.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_operation.c @@ -40,13 +40,10 @@ /* MQTT include. */ #include "iot_mqtt.h" -/* Error handling include. */ -#include "iot_error.h" - /*-----------------------------------------------------------*/ /** - * @brief First parameter to #_shadowOperation_match. + * @brief First parameter to #_shadowOperationMatch. */ typedef struct _operationMatchParams { @@ -69,8 +66,8 @@ typedef struct _operationMatchParams * * @return `true` if `pMatch` matches the received response; `false` otherwise. */ -static bool _shadowOperation_match( const IotLink_t * pOperationLink, - void * pMatch ); +static bool _shadowOperationMatch( const IotLink_t * pOperationLink, + void * pMatch ); /** * @brief Common function for processing received Shadow responses. @@ -187,8 +184,8 @@ IotMutex_t _AwsIotShadowPendingOperationsMutex; /*-----------------------------------------------------------*/ -static bool _shadowOperation_match( const IotLink_t * pOperationLink, - void * pMatch ) +static bool _shadowOperationMatch( const IotLink_t * pOperationLink, + void * pMatch ) { /* Because this function is called from a container function, the given link * must never be NULL. */ @@ -272,115 +269,106 @@ static void _commonOperationCallback( _shadowOperationType_t type, if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, pMessage->u.message.info.topicNameLength, &( param.pThingName ), - &( param.thingNameLength ) ) == false ) + &( param.thingNameLength ) ) == true ) { - IOT_GOTO_CLEANUP(); - } - - /* Lock the pending operations list for exclusive access. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Search for a matching pending operation. */ - pOperationLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowPendingOperations ), - NULL, - _shadowOperation_match, - ¶m ); - - /* Find and remove the first Shadow operation of the given type. */ - if( pOperationLink == NULL ) - { - /* Operation is not pending. It may have already been processed. Return - * without doing anything */ - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + /* Lock the pending operations list for exclusive access. */ + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotLogWarn( "Shadow %s callback received an unknown operation.", - _pAwsIotShadowOperationNames[ type ] ); + /* Search for a matching pending operation. */ + pOperationLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowPendingOperations ), + NULL, + _shadowOperationMatch, + ¶m ); - IOT_GOTO_CLEANUP(); - } - else - { - pOperation = IotLink_Container( _shadowOperation_t, pOperationLink, link ); - - /* Remove a non-waitable operation from the pending operation list. - * Waitable operations are removed by the Wait function. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) + /* Find and remove the first Shadow operation of the given type. */ + if( pOperationLink == NULL ) { - IotListDouble_Remove( &( pOperation->link ) ); + /* Operation is not pending. It may have already been processed. Return + * without doing anything */ IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - } - } - - /* Check that the Shadow operation type and status. */ - AwsIotShadow_Assert( pOperation->type == type ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - IotLogDebug( "Received Shadow response on topic %.*s", - pMessage->u.message.info.topicNameLength, - pMessage->u.message.info.pTopicName ); - - /* Parse the status from the topic name. */ - status = AwsIot_ParseStatus( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength ); + IotLogWarn( "Shadow %s callback received an unknown operation.", + _pAwsIotShadowOperationNames[ type ] ); + } + else + { + pOperation = IotLink_Container( _shadowOperation_t, pOperationLink, link ); - switch( status ) - { - case AWS_IOT_ACCEPTED: - IotLogInfo( "Shadow %s of %.*s was ACCEPTED.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); - - /* Process the retrieved document for a Shadow GET. Otherwise, set - * status to success. */ - if( type == SHADOW_GET ) + /* Remove a non-waitable operation from the pending operation list. + * Waitable operations are removed by the Wait function. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) { - pOperation->status = _processAcceptedGet( pOperation, - &( pMessage->u.message.info ) ); + IotListDouble_Remove( &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } - else - { - pOperation->status = AWS_IOT_SHADOW_SUCCESS; - } - - break; - case AWS_IOT_REJECTED: - IotLogWarn( "Shadow %s of %.*s was REJECTED.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); + /* Check that the Shadow operation type and status. */ + AwsIotShadow_Assert( pOperation->type == type ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - pOperation->status = _AwsIotShadow_ParseErrorDocument( pMessage->u.message.info.pPayload, - pMessage->u.message.info.payloadLength ); - break; + IotLogDebug( "Received Shadow response on topic %.*s", + pMessage->u.message.info.topicNameLength, + pMessage->u.message.info.pTopicName ); - default: - IotLogWarn( "Unknown status for %s of %.*s Shadow. Ignoring message.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); + /* Parse the status from the topic name. */ + status = AwsIot_ParseStatus( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength ); - pOperation->status = AWS_IOT_SHADOW_BAD_RESPONSE; - break; - } + switch( status ) + { + case AWS_IOT_ACCEPTED: + IotLogInfo( "Shadow %s of %.*s was ACCEPTED.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + /* Process the retrieved document for a Shadow GET. Otherwise, set + * status to success. */ + if( type == SHADOW_GET ) + { + pOperation->status = _processAcceptedGet( pOperation, + &( pMessage->u.message.info ) ); + } + else + { + pOperation->status = AWS_IOT_SHADOW_SUCCESS; + } + + break; + + case AWS_IOT_REJECTED: + IotLogWarn( "Shadow %s of %.*s was REJECTED.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + pOperation->status = _AwsIotShadow_ParseErrorDocument( pMessage->u.message.info.pPayload, + pMessage->u.message.info.payloadLength ); + break; + + default: + IotLogWarn( "Unknown status for %s of %.*s Shadow. Ignoring message.", + _pAwsIotShadowOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + pOperation->status = AWS_IOT_SHADOW_BAD_RESPONSE; + } - /* Copy the flags from the Shadow operation. The notify function may delete the operation. */ - flags = pOperation->flags; + /* Copy the flags from the Shadow operation. The notify function may delete the operation. */ + flags = pOperation->flags; - /* Notify of operation completion. */ - _notifyCompletion( pOperation ); + /* Notify of operation completion. */ + _notifyCompletion( pOperation ); - /* For waitable operations, unlock the pending operation list mutex to allow - * the Wait function to run. */ - if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + /* For waitable operations, unlock the pending operation list mutex to allow + * the Wait function to run. */ + if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + } + } } - - /* This function has no return value and no cleanup, but uses the cleanup - * label to exit on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); } /*-----------------------------------------------------------*/ @@ -614,7 +602,7 @@ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOpe uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; _shadowOperation_t * pOperation = NULL; IotLogDebug( "Creating operation record for Shadow %s.", @@ -623,60 +611,52 @@ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOpe /* Allocate memory for a new Shadow operation. */ pOperation = AwsIotShadow_MallocOperation( sizeof( _shadowOperation_t ) ); - if( pOperation == NULL ) + if( pOperation != NULL ) { - IotLogError( "Failed to allocate memory for Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _shadowOperation_t ) ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); - } + /* Set the remaining common members of the Shadow operation. */ + pOperation->type = type; + pOperation->flags = flags; + pOperation->status = AWS_IOT_SHADOW_STATUS_PENDING; - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _shadowOperation_t ) ); + /* Set the output parameter. */ + *pNewOperation = pOperation; - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { - IotLogError( "Failed to create semaphore for waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + { + IotLogError( "Failed to create semaphore for waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); - } - } - else - { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) - { - pOperation->notify.callback = *pCallbackInfo; + *pNewOperation = NULL; + AwsIotShadow_FreeOperation( pOperation ); + status = AWS_IOT_SHADOW_NO_MEMORY; + } } - } - - /* Set the remaining common members of the Shadow operation. */ - pOperation->type = type; - pOperation->flags = flags; - pOperation->status = AWS_IOT_SHADOW_STATUS_PENDING; - - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - if( pOperation != NULL ) + else { - AwsIotShadow_FreeOperation( pOperation ); + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->notify.callback = *pCallbackInfo; + } } } else { - /* Set the output parameter. */ - *pNewOperation = pOperation; + IotLogError( "Failed to allocate memory for Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + + status = AWS_IOT_SHADOW_NO_MEMORY; } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -770,7 +750,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn _shadowOperation_t * pOperation, const AwsIotShadowDocumentInfo_t * pDocumentInfo ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; char * pTopicBuffer = NULL; uint16_t operationTopicLength = 0; @@ -792,114 +772,114 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn &pTopicBuffer, &operationTopicLength ); - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - IotLogError( "No memory for Shadow operation topic buffer." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); + /* Get a subscription object for this Shadow operation. */ + status = _findSubscription( pThingName, + thingNameLength, + pTopicBuffer, + operationTopicLength, + pOperation, + &freeTopicBuffer ); } - - /* Get a subscription object for this Shadow operation. */ - status = _findSubscription( pThingName, - thingNameLength, - pTopicBuffer, - operationTopicLength, - pOperation, - &freeTopicBuffer ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + else { - /* No subscription was found and no subscription could be allocated. */ - IOT_GOTO_CLEANUP(); - } - - /* Set the operation topic name. */ - publishInfo.pTopicName = pTopicBuffer; - publishInfo.topicNameLength = operationTopicLength; + IotLogError( "No memory for Shadow operation topic buffer." ); - IotLogDebug( "Shadow %s message will be published to topic %.*s", - _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.topicNameLength, - publishInfo.pTopicName ); + status = AWS_IOT_SHADOW_NO_MEMORY; + } - /* Set the document info if this operation is not a Shadow DELETE. */ - if( pOperation->type != SHADOW_DELETE ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - publishInfo.qos = pDocumentInfo->qos; - publishInfo.retryLimit = pDocumentInfo->retryLimit; - publishInfo.retryMs = pDocumentInfo->retryMs; + /* Set the operation topic name. */ + publishInfo.pTopicName = pTopicBuffer; + publishInfo.topicNameLength = operationTopicLength; - IotLogDebug( "Shadow %s message will be published at QoS %d with " - "retryLimit %d and retryMs %llu.", + IotLogDebug( "Shadow %s message will be published to topic %.*s", _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.qos, - publishInfo.retryLimit, - publishInfo.retryMs ); - } - - /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ - if( pOperation->type == SHADOW_UPDATE ) - { - publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; - publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; - } + publishInfo.topicNameLength, + publishInfo.pTopicName ); - /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, - * per the Shadow spec. */ - else - { - publishInfo.pPayload = ""; - publishInfo.payloadLength = 0; - } + /* Set the document info if this operation is not a Shadow DELETE. */ + if( pOperation->type != SHADOW_DELETE ) + { + publishInfo.qos = pDocumentInfo->qos; + publishInfo.retryLimit = pDocumentInfo->retryLimit; + publishInfo.retryMs = pDocumentInfo->retryMs; + + IotLogDebug( "Shadow %s message will be published at QoS %d with " + "retryLimit %d and retryMs %llu.", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.qos, + publishInfo.retryLimit, + publishInfo.retryMs ); + } - /* Add Shadow operation to the pending operations list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), - &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ + if( pOperation->type == SHADOW_UPDATE ) + { + publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; + publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; + } - /* Publish to the Shadow topic name. */ - publishStatus = IotMqtt_PublishSync( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotShadowMqttTimeoutMs ); + /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, + * per the Shadow spec. */ + else + { + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; + } - /* Check for errors from the MQTT publish. */ - if( publishStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", - _pAwsIotShadowOperationNames[ pOperation->type ], - thingNameLength, - pThingName, - IotMqtt_strerror( publishStatus ) ); + /* Add Shadow operation to the pending operations list. */ + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), + &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( publishStatus ); + /* Publish to the Shadow topic name. */ + publishStatus = IotMqtt_PublishSync( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotShadowMqttTimeoutMs ); - /* If the "keep subscriptions" flag is not set, decrement the reference - * count. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0U ) + if( publishStatus == IOT_MQTT_SUCCESS ) { - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + IotLogDebug( "Shadow %s PUBLISH message successfully sent.", + _pAwsIotShadowOperationNames[ pOperation->type ] ); } + else + { + IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", + _pAwsIotShadowOperationNames[ pOperation->type ], + thingNameLength, + pThingName, + IotMqtt_strerror( publishStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( publishStatus ); + + /* If the "keep subscriptions" flag is not set, decrement the reference + * count. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0U ) + { + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + _AwsIotShadow_DecrementReferences( pOperation, + pTopicBuffer, + NULL ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + } - /* Remove Shadow operation from the pending operations list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_Remove( &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + /* Remove Shadow operation from the pending operations list. */ + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_Remove( &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + } } else { - IotLogDebug( "Shadow %s PUBLISH message successfully sent.", - _pAwsIotShadowOperationNames[ pOperation->type ] ); + /* No subscription was found and no subscription could be allocated. */ } - IOT_FUNCTION_CLEANUP_BEGIN(); - /* Free the topic buffer used by this function if it was not assigned to a * subscription. */ if( ( freeTopicBuffer == true ) && ( pTopicBuffer != NULL ) ) @@ -919,7 +899,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn status = AWS_IOT_SHADOW_STATUS_PENDING; } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ From 2dc96f2e423f33563627016bee39d1d8f1f997e0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 29 Jan 2020 10:28:31 -0800 Subject: [PATCH 393/844] Add doc notes about demo and metrics (#754) --- doc/lib/mqtt.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 9257da496c..3db01a0385 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -251,6 +251,8 @@ Once MQTT CONNECT returns to the application, the keep-alive is processed entire The MQTT demo demonstrates the subscribe-publish workflow of MQTT. After subscribing to multiple topic filters, it publishes [bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) of data to various topic names. The demo then waits for all messages in a burst to be received on a topic filter. As each message arrives, the demo publishes an acknowledgement message back to the MQTT server. It repeats this cycle @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times. +Messages in this demo are sent at QoS 1, which the MQTT spec guarantees at least once delivery. However, for practical purposes, QoS 1 messages are subject to the retry policy described [here](@ref IotMqttPublishInfo_t). A QoS 1 message may fail to be delivered if all its retries are exhausted. + @image html mqtt_demo.png "MQTT Demo Workflow" width=80% See @subpage mqtt_demo_config for configuration settings that change the behavior of the demo. @@ -364,7 +366,7 @@ Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SD @configpossible `0` (metrics reporting disabled) or `1` (metrics reporting enabled)
@configrecommended `1`
@configdefault `1` -@note This setting is only in effect for [MQTT connections with AWS IoT](@ref IotMqttConnectInfo_t.awsIotMqttMode). Metrics are reported through [the MQTT username.](@ref IotMqttConnectInfo_t.pUserName) The MQTT library may overwrite any provided user name if metrics are enabled. +@note This setting is only in effect for [MQTT connections with AWS IoT](@ref IotMqttConnectInfo_t.awsIotMqttMode). @section IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES @brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. From 96bf6dfe9e5550c819b0f2079b9deacfb05cc6b4 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 29 Jan 2020 10:59:03 -0800 Subject: [PATCH 394/844] Address MQTT MISRA 15 (#748) * Fix MISRA 14.3 annotation * Address MISRA 15.4 * Address MISRA 15.5 --- .../common/include/iot_linear_containers.h | 23 ++++++----- libraries/standard/mqtt/src/iot_mqtt_api.c | 1 + .../standard/mqtt/src/iot_mqtt_serialize.c | 14 +++++-- .../standard/mqtt/src/iot_mqtt_subscription.c | 41 +++++++++---------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h index f392ef371a..08b0340f31 100644 --- a/libraries/standard/common/include/iot_linear_containers.h +++ b/libraries/standard/common/include/iot_linear_containers.h @@ -623,7 +623,8 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * { /* The const must be cast away to match this function's return value. Nevertheless, * this function will respect the const-ness of pStartPoint. */ - IotLink_t * pCurrent = ( IotLink_t * ) pStartPoint; + IotLink_t * pCurrent = ( IotLink_t * ) pStartPoint, * pMatchedLink = NULL; + bool matchFound = false; /* This function must not be called with a NULL pList parameter. */ IotContainers_Assert( pList != NULL ); @@ -640,24 +641,24 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * /* Call isMatch if provided. Otherwise, compare pointers. */ if( isMatch != NULL ) { - if( isMatch( pCurrent, pMatch ) == true ) - { - return pCurrent; - } + matchFound = isMatch( pCurrent, pMatch ); } else { - if( pCurrent == pMatch ) - { - return pCurrent; - } + matchFound = ( pCurrent == pMatch ); + } + + if( matchFound == true ) + { + pMatchedLink = pCurrent; + break; } pCurrent = pCurrent->pNext; } - /* No match found, return NULL. */ - return NULL; + /* Return match if found, else NULL. */ + return pMatchedLink; } /** diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 12704b7795..841fd8e596 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -984,6 +984,7 @@ IotMqttError_t IotMqtt_Init( void ) * condition is not an invariant, and the MISRA 14.3 violation is * a false positive. */ /* coverity[misra_c_2012_rule_14_3_violation] */ + /* coverity[const] */ if( status == IOT_MQTT_SUCCESS ) { IotLogInfo( "MQTT library successfully initialized." ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 073eccab57..cb9b080ead 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1302,7 +1302,6 @@ size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, if( multiplier > 2097152U ) /* 128 ^ 3 */ { remainingLength = MQTT_REMAINING_LENGTH_INVALID; - break; } else { @@ -1317,9 +1316,13 @@ size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, else { remainingLength = MQTT_REMAINING_LENGTH_INVALID; - break; } } + + if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + break; + } } while( ( encodedByte & 0x80U ) != 0U ); /* Check that the decoded remaining length conforms to the MQTT specification. */ @@ -1355,7 +1358,6 @@ size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConne if( multiplier > 2097152U ) /* 128 ^ 3 */ { remainingLength = MQTT_REMAINING_LENGTH_INVALID; - break; } else { @@ -1368,9 +1370,13 @@ size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConne else { remainingLength = MQTT_REMAINING_LENGTH_INVALID; - break; } } + + if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + break; + } } while( ( encodedByte & 0x80U ) != 0U ); /* Check that the decoded remaining length conforms to the MQTT specification. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 408d4e1a51..fd4b3317f2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -230,7 +230,7 @@ static bool _topicFilterMatch( const char * pTopicName, const char * pTopicFilter, uint16_t topicFilterLength ) { - bool status = false; + bool status = false, matchFound = false; uint16_t nameIndex = 0, filterIndex = 0; while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) @@ -241,28 +241,27 @@ static bool _topicFilterMatch( const char * pTopicName, { /* Handle special corner cases regarding wildcards at the end of * topic filters, as documented by the MQTT protocol spec. */ - if( _matchEndWildcards( pTopicFilter, - topicNameLength, - topicFilterLength, - nameIndex, - filterIndex, - &status ) == true ) - { - break; - } + matchFound = _matchEndWildcards( pTopicFilter, + topicNameLength, + topicFilterLength, + nameIndex, + filterIndex, + &status ); } else { /* Check for matching wildcards. */ - if( _matchWildcards( pTopicFilter, - pTopicName, - topicNameLength, - filterIndex, - &nameIndex, - &status ) == true ) - { - break; - } + matchFound = _matchWildcards( pTopicFilter, + pTopicName, + topicNameLength, + filterIndex, + &nameIndex, + &status ); + } + + if( matchFound == true ) + { + break; } /* Increment indexes. */ @@ -291,8 +290,8 @@ static bool _topicMatch( const IotLink_t * const pSubscriptionLink, IotMqtt_Assert( pSubscriptionLink != NULL ); const _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - pSubscriptionLink, - link ); + pSubscriptionLink, + link ); const _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; /* Extract the relevant strings and lengths from parameters. */ From daa0c2763e51281be2f52d2f7505a1866145f97e Mon Sep 17 00:00:00 2001 From: qiutongs Date: Wed, 29 Jan 2020 11:37:22 -0800 Subject: [PATCH 395/844] Annotate mqtt source files to suppress MISRA 20.7 (#752) --- libraries/standard/mqtt/lexicon.txt | 2 ++ libraries/standard/mqtt/src/iot_mqtt_api.c | 4 +++ .../standard/mqtt/src/iot_mqtt_operation.c | 14 ++++++++--- .../standard/mqtt/src/iot_mqtt_subscription.c | 25 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index a6f10a6c3d..4ba8da84a0 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -4,6 +4,7 @@ app aws awsiotmqttmode bool +caretline clientid clientidentifierlength com @@ -96,6 +97,7 @@ mqttsubscription mutex mutexes nextperiodms +offsetof onlinepubs opengroup operationcomplete diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 841fd8e596..06060a2e60 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -299,6 +299,10 @@ static bool _mqttSubscription_setUnsubscribe( const IotLink_t * const pSubscript * must never be NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index e2cd2f45b8..f46f6f6e97 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -167,9 +167,13 @@ static bool _mqttOperation_match( const IotLink_t * const pOperationLink, * must never be NULL. */ IotMqtt_Assert( pOperationLink != NULL ); + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ const _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, - pOperationLink, - link ); + pOperationLink, + link ); const _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; /* Check for matching operations. */ @@ -1019,7 +1023,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { /* Empty else MISRA 15.7 */ } - } /* Check if this operation requires further processing. */ @@ -1167,6 +1170,11 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, if( pResultLink != NULL ) { /* Get operation pointer and check if it is waitable. */ + + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ pResult = IotLink_Container( _mqttOperation_t, pResultLink, link ); waitable = ( pResult->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index fd4b3317f2..ddcd4b6bec 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -289,6 +289,10 @@ static bool _topicMatch( const IotLink_t * const pSubscriptionLink, * will never pass NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ const _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); @@ -327,6 +331,10 @@ static bool _packetMatch( const IotLink_t * const pSubscriptionLink, * must never be NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); @@ -394,6 +402,10 @@ IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, if( pSubscriptionLink != NULL ) { + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ pNewSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); /* The lengths of exactly matching topic filters must match. */ @@ -486,6 +498,11 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, } /* Subscription found. Calculate pointer to subscription object. */ + + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); /* Subscription validation should not have allowed a NULL callback function. */ @@ -593,6 +610,10 @@ void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnecti if( pSubscriptionLink != NULL ) { + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); /* Reference count must not be negative. */ @@ -650,6 +671,10 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, /* Check if a matching subscription was found. */ if( pSubscriptionLink != NULL ) { + /* Adding parentheses to parameters of IotLink_Container is not applicable + * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_20_7_violation] */ + /* coverity[caretline] */ pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); /* Copy the matching subscription to the output parameter. */ From 5bdf67a046e059b42c81ce90779eb1954593715c Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 29 Jan 2020 15:34:50 -0800 Subject: [PATCH 396/844] Fix: Reduce code complexity below 9 Part 2 (#742) * Fix: Reduce code complexity below 9 Part 2 Files in PR: iot_mqtt_api.c --- libraries/standard/mqtt/src/iot_mqtt_api.c | 395 ++++++++++++++------- 1 file changed, 259 insertions(+), 136 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 06060a2e60..5ecda2fb50 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -172,7 +172,7 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation const IotMqttOperation_t * const pOperationReference ); /** - * @brief Utility function for creating and serializing subscription requests + * @brief Utility function for creating and serializing subscription requests. * * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a * description of the parameters and return values. @@ -186,6 +186,17 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op const IotMqttCallbackInfo_t * pCallbackInfo, _mqttOperation_t ** ppSubscriptionOperation ); +/** + * @brief Utility function for sending/scheduling a subscribe, unsubscribe or publish message. + * + * @param[in] pMqttOperation Reference to MQTT operation. + * @param[in] flags Flags which modify the behavior of this function. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_SCHEDULING_ERROR. + */ +static IotMqttError_t _sendMqttMessage( _mqttOperation_t * pMqttOperation, + uint32_t flags ); + /** * @brief The common component of both @ref mqtt_function_subscribeasync and @ref * mqtt_function_unsubscribeasync. @@ -219,6 +230,56 @@ static void _setOperationReference( IotMqttOperation_t * const pOperationReferen static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, uint32_t timeoutMs ); +/** + * @brief Utility function for scheduling ping request after connection with + * with the broker is established. + * + * @param[in] pMqttConnection MQTT connection reference. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SCHEDULING_ERROR. + */ +static IotMqttError_t _scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); + +/** + * @brief Utility function for sending connect request. + * + * @param[in] pOperation CONNECT operation reference. + * @param[in] timeoutMs How many milliseconds to wait for CONN_ACK. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_PARAMETER, #IOT_MQTT_NETWORK_ERROR. + */ +static IotMqttError_t _sendConnectRequest( _mqttOperation_t * pOperation, + uint32_t timeoutMs ); + +/** + * @brief Utility function for adding subscriptions to connect request. + * + * @param[in] pOperation CONNECT operation reference. + * @param[in] pMqttConnection MQTT connection reference. + * @param[in] pConnectInfo MQTT connection information. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY + */ +static IotMqttError_t _addSubscriptions( _mqttOperation_t * pOperation, + IotMqttConnection_t pMqttConnection, + const IotMqttConnectInfo_t * pConnectInfo ); + +/** + * @brief Utility function for handling MQTT connect failure. + * + * @param[in] pMqttConnection MQTT connection reference. + * @param[in] pNetworkConnection Network connection reference. + * @param[in] pNetworkInfo User-provided network information. + * @param[in] pOperation CONNECT operation reference. + * @param[in] ownNetworkConnection if true, connection needs to be closed. + * + */ +static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, + IotNetworkConnection_t pNetworkConnection, + const IotMqttNetworkInfo_t * pNetworkInfo, + _mqttOperation_t * pOperation, + bool ownNetworkConnection ); + /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -267,6 +328,14 @@ static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, /*-----------------------------------------------------------*/ +/** + * @brief Place holder packet identifier used when + * _IotMqtt_AddSubscriptions is called with previous subscriptions lists. + */ +#define IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID 2 + +/*-----------------------------------------------------------*/ + /** * @brief Tracks whether @ref mqtt_function_init has been called. * @@ -730,6 +799,30 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op /*-----------------------------------------------------------*/ +static IotMqttError_t _sendMqttMessage( _mqttOperation_t * pMqttOperation, + uint32_t flags ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + IotMqtt_Assert( pMqttOperation != NULL ); + + /* Send the SUBSCRIBE packet. */ + if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) + { + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pMqttOperation->job, pMqttOperation ); + } + else + { + status = _IotMqtt_ScheduleOperation( pMqttOperation, + _IotMqtt_ProcessSend, + 0 ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotMqttConnection_t mqttConnection, IotMqttSerializeSubscribe_t serializeSubscription, @@ -769,33 +862,24 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Set the reference, if provided. */ _setOperationReference( pOperationReference, pSubscriptionOperation ); - /* Send the SUBSCRIBE packet. */ - if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) - { - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pSubscriptionOperation->job, pSubscriptionOperation ); - } - else + /* Send or schedule subscribe request. */ + status = _sendMqttMessage( pSubscriptionOperation, flags ); + + if( status != IOT_MQTT_SUCCESS ) { - status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, - _IotMqtt_ProcessSend, - 0 ); + IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", + mqttConnection, + IotMqtt_OperationType( operation ) ); - if( status != IOT_MQTT_SUCCESS ) + if( operation == IOT_MQTT_SUBSCRIBE ) { - IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", - mqttConnection, - IotMqtt_OperationType( operation ) ); - - if( operation == IOT_MQTT_SUBSCRIBE ) - { - _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, - pSubscriptionOperation->u.operation.packetIdentifier, - MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - } - - /* Clear the previously set (and now invalid) reference. */ - _setOperationReference( pOperationReference, IOT_MQTT_OPERATION_INITIALIZER ); + _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, + pSubscriptionOperation->u.operation.packetIdentifier, + MQTT_REMOVE_ALL_SUBSCRIPTIONS ); } + + /* Clear the previously set (and now invalid) reference. */ + _setOperationReference( pOperationReference, IOT_MQTT_OPERATION_INITIALIZER ); } } @@ -880,6 +964,125 @@ static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, /*-----------------------------------------------------------*/ +static IotMqttError_t _scheduleKeepAlive( IotMqttConnection_t pMqttConnection ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + + /* Check if a keep-alive job should be scheduled. */ + if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) + { + IotLogDebug( "Scheduling first MQTT keep-alive job." ); + + taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, + pMqttConnection->pingreq.job, + pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ); + } + + if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) + { + status = IOT_MQTT_SCHEDULING_ERROR; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _sendConnectRequest( _mqttOperation_t * pOperation, + uint32_t timeoutMs ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + IotMqtt_Assert( pOperation != NULL ); + + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); + + /* Send the CONNECT packet. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + status = IotMqtt_Wait( pOperation, timeoutMs ); + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _addSubscriptions( _mqttOperation_t * pOperation, + IotMqttConnection_t pMqttConnection, + const IotMqttConnectInfo_t * pConnectInfo ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + IotMqtt_Assert( pOperation != NULL ); + IotMqtt_Assert( pConnectInfo != NULL ); + + /* Ensure the members set by operation creation and serialization + * are appropriate for a blocking CONNECT. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0U ); + + /* Set the operation type. */ + pOperation->u.operation.type = IOT_MQTT_CONNECT; + + /* Add previous session subscriptions. */ + if( pConnectInfo->pPreviousSubscriptions != NULL ) + { + /* Previous subscription count should have been validated as nonzero. */ + IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0U ); + + status = _IotMqtt_AddSubscriptions( pMqttConnection, + IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, + IotNetworkConnection_t pNetworkConnection, + const IotMqttNetworkInfo_t * pNetworkInfo, + _mqttOperation_t * pOperation, + bool ownNetworkConnection ) +{ + IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; + + /* The network connection must be closed if it was created. */ + if( ownNetworkConnection == true ) + { + networkStatus = pNetworkInfo->pNetworkInterface->close( pNetworkConnection ); + + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "Failed to close network connection." ); + } + else + { + IotLogInfo( "Network connection closed on error." ); + } + } + + if( pOperation != NULL ) + { + _IotMqtt_DestroyOperation( pOperation ); + } + + if( pMqttConnection != NULL ) + { + _destroyMqttConnection( pMqttConnection ); + } +} + +/*-----------------------------------------------------------*/ + bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ) { bool disconnected = false; @@ -1038,7 +1241,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqttError_t status = IOT_MQTT_SUCCESS; bool ownNetworkConnection = false; IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotNetworkConnection_t pNetworkConnection = { 0 }; _mqttOperation_t * pOperation = NULL; _mqttConnection_t * pNewMqttConnection = NULL; @@ -1104,39 +1306,23 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_NETWORK_ERROR; } - else - { - /* Create a CONNECT operation. */ - status = _IotMqtt_CreateOperation( pNewMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ); - } } if( status == IOT_MQTT_SUCCESS ) { - /* Ensure the members set by operation creation and serialization - * are appropriate for a blocking CONNECT. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0U ); - - /* Set the operation type. */ - pOperation->u.operation.type = IOT_MQTT_CONNECT; - - /* Add previous session subscriptions. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) - { - /* Previous subscription count should have been validated as nonzero. */ - IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0U ); + /* Create a CONNECT operation. */ + status = _IotMqtt_CreateOperation( pNewMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ); + } - status = _IotMqtt_AddSubscriptions( pNewMqttConnection, - 2, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ); - } + if( status == IOT_MQTT_SUCCESS ) + { + /* Add subscriptions to the operation */ + status = _addSubscriptions( pOperation, + pNewMqttConnection, + pConnectInfo ); } if( status == IOT_MQTT_SUCCESS ) @@ -1149,37 +1335,17 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status == IOT_MQTT_SUCCESS ) { - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); - - /* Send the CONNECT packet. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + /* Send the CONNECT packet */ + status = _sendConnectRequest( pOperation, timeoutMs ); - /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - status = IotMqtt_Wait( pOperation, timeoutMs ); - - /* The call to wait cleans up the CONNECT operation, so set the pointer - * to NULL. */ + /* The call to wait inside _sendConnectRequest cleans up + * the CONNECT operation, so set the pointer to NULL. */ pOperation = NULL; - } - /* When a connection is successfully established, schedule keep-alive job. */ - if( status == IOT_MQTT_SUCCESS ) - { - /* Check if a keep-alive job should be scheduled. */ - if( pNewMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) + /* When a connection is successfully established, schedule keep-alive job. */ + if( status == IOT_MQTT_SUCCESS ) { - IotLogDebug( "Scheduling first MQTT keep-alive job." ); - - taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - pNewMqttConnection->pingreq.job, - pNewMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ); - - if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) - { - status = IOT_MQTT_SCHEDULING_ERROR; - } + status = _scheduleKeepAlive( pNewMqttConnection ); } } @@ -1188,45 +1354,11 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotLogError( "Failed to establish new MQTT connection, error %s.", IotMqtt_strerror( status ) ); - /* The network connection must be closed if it was created. */ - if( ownNetworkConnection == true ) - { - networkStatus = pNetworkInfo->pNetworkInterface->close( pNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "Failed to close network connection." ); - } - else - { - IotLogInfo( "Network connection closed on error." ); - } - } - - if( pOperation != NULL ) - { - _IotMqtt_DestroyOperation( pOperation ); - } - - if( pNewMqttConnection != NULL ) - { - /* Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. - * - * This error is triggered by a dereference of 'pNewMqttConnection' in - * '_destroyMqttConnection'. Coverity assumes that 'pNewMqttConnection' - * was freed in '_IotMqtt_CreateOperation' above, where cleanup code will - * free 'pNewMqttConnection' upon allocation failure. - * - * This will never happen as a valid MQTT connection passed to this - * function always has a positive reference count; therefore, - * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT - * connections will be freed. - * - * The annotation below suppresses this Coverity error. - */ - /* coverity[deref_arg] */ - _destroyMqttConnection( pNewMqttConnection ); - } + _handleConnectFailure( pNewMqttConnection, + pNetworkConnection, + pNetworkInfo, + pOperation, + ownNetworkConnection ); } else { @@ -1593,26 +1725,17 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, } /* Send the PUBLISH packet. */ - if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) - { - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - } - else + status = _sendMqttMessage( pOperation, flags ); + + if( status != IOT_MQTT_SUCCESS ) { - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - 0 ); + IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", + mqttConnection ); - if( status != IOT_MQTT_SUCCESS ) + /* Clear the previously set (and now invalid) reference. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", - mqttConnection ); - - /* Clear the previously set (and now invalid) reference. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) - { - _setOperationReference( pPublishOperation, IOT_MQTT_OPERATION_INITIALIZER ); - } + _setOperationReference( pPublishOperation, IOT_MQTT_OPERATION_INITIALIZER ); } } } From a050042f660d957991dfd27ab0c3b4dbe93132c5 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Wed, 29 Jan 2020 16:17:38 -0800 Subject: [PATCH 397/844] Suppress MISRA 19.2 in MQTT files (#756) --- .../standard/mqtt/include/types/iot_mqtt_types.h | 6 ++++++ .../mqtt/src/private/iot_mqtt_internal.h | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index d36aa686d2..f80d8d0ba7 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -445,6 +445,9 @@ typedef struct IotMqttCallbackParam */ IotMqttConnection_t mqttConnection; + /* MISRA rule 19.2 doesn't allow usage of union + * but it is intentionally used here to reduce the size of struct. */ + /* coverity[misra_c_2012_rule_19_2_violation] */ union { /* Valid for completed operations. */ @@ -1014,6 +1017,9 @@ typedef struct IotMqttNetworkInfo */ bool createNetworkConnection; + /* MISRA rule 19.2 doesn't allow usage of union + * but it is intentionally used here to reduce the size of struct. */ + /* coverity[misra_c_2012_rule_19_2_violation] */ union { struct diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index f67136209c..01887c7fa0 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -317,6 +317,9 @@ typedef struct _mqttOperation IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ + /* MISRA rule 19.2 doesn't allow usage of union + * but it is intentionally used here to reduce the size of struct. */ + /* coverity[misra_c_2012_rule_19_2_violation] */ union { /* If incomingPublish is false, this struct is valid. */ @@ -334,6 +337,10 @@ typedef struct _mqttOperation size_t packetSize; /**< @brief Size of `pMqttPacket`. */ /* How to notify of an operation's completion. */ + + /* MISRA rule 19.2 doesn't allow usage of union + * but it is intentionally used here to reduce the size of struct. */ + /* coverity[misra_c_2012_rule_19_2_violation] */ union { IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ @@ -341,6 +348,9 @@ typedef struct _mqttOperation } notify; /**< @brief How to notify of this operation's completion. */ IotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ + /* MISRA rule 19.2 doesn't allow usage of union + * but it is intentionally used here to reduce the size of struct. */ + /* coverity[misra_c_2012_rule_19_2_violation] */ union { struct @@ -421,10 +431,11 @@ typedef struct _mqttSubscription IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ + /* A flexible length array is used here so that the topic filter may * be of an arbitrary length. */ /* coverity[misra_c_2012_rule_18_7_violation] */ - char pTopicFilter[]; /**< @brief The subscription topic filter. */ + char pTopicFilter[]; /**< @brief The subscription topic filter. */ } _mqttSubscription_t; /** @@ -435,6 +446,9 @@ typedef struct _mqttSubscription */ typedef struct _mqttPacket { + /* MISRA rule 19.2 doesn't allow usage of union + * but it is intentionally used here to reduce the size of struct. */ + /* coverity[misra_c_2012_rule_19_2_violation] */ union { /** From 9f7ff6b1d56dc6ab1437ec75f91dbef55e74fdc7 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 29 Jan 2020 16:34:20 -0800 Subject: [PATCH 398/844] Fix build failure when AWS_IOT_MQTT_ENABLE_METRICS is disabled. (#759) * Fix: CSDK build failure. Fix build failure when AWS_IOT_MQTT_ENABLE_METRICS is disabled. --- libraries/standard/mqtt/src/iot_mqtt_api.c | 4 +++- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 5ecda2fb50..225e4a24ee 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -331,8 +331,10 @@ static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, /** * @brief Place holder packet identifier used when * _IotMqtt_AddSubscriptions is called with previous subscriptions lists. + * Any non-zero value is acceptable, since this value is never sent out to + * the broker. */ -#define IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID 2 +#define IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID 1 /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index cb9b080ead..5191e43c59 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -520,7 +520,7 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, { bool encodedUserName = false; uint8_t * pBuffer = pDestination; - const char * pMetricsUserName = AWS_IOT_METRICS_USERNAME; + const char * pMetricsUserName = NULL; /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the @@ -531,6 +531,7 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); + pMetricsUserName = AWS_IOT_METRICS_USERNAME; /* Determine if the Connect packet should use a combination of the username * for authentication plus the SDK version string. */ if( pConnectInfo->pUserName != NULL ) From 2a6a90b124343f94985db7970fe745eedc28fc46 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 29 Jan 2020 17:05:57 -0800 Subject: [PATCH 399/844] Annotate gcc atomic and posix headers for MISRA (#758) * Annotate GCC header for 17.3 false positives * Annotate GCC header for 8.5 false positives * Edit posix platform for MISRA 8.2 --- ports/common/include/atomic/iot_atomic_gcc.h | 28 +++++++++++++++++++ ports/common/lexicon.txt | 1 + .../posix/include/iot_platform_types_posix.h | 6 ++-- ports/posix/lexicon.txt | 1 + 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h index 77e00b8dfc..89fb55366a 100644 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ b/ports/common/include/atomic/iot_atomic_gcc.h @@ -58,7 +58,10 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes * This routine is built into gcc and defined to return a bool * type. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_8_5_violation] */ /* coverity[misra_c_2012_rule_10_4_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ if( __atomic_compare_exchange( pDestination, &comparand, &newValue, @@ -86,6 +89,8 @@ static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ /* coverity[misra_c_2012_rule_17_7_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); return pOldValue; @@ -105,6 +110,9 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pD /* This routine is built into gcc and defined to return a bool * type. */ /* coverity[misra_c_2012_rule_10_4_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ + /* coverity[other_declaration] */ if( __atomic_compare_exchange( pDestination, &pComparand, &pNewValue, @@ -129,6 +137,9 @@ static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_8_5_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ return ( uint32_t ) ( __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ) ); } @@ -143,6 +154,9 @@ static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_8_5_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ) ); } @@ -153,6 +167,9 @@ static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, */ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) { + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ + /* coverity[other_declaration] */ return ( uint32_t ) ( __atomic_fetch_add( pAugend, 1U, __ATOMIC_SEQ_CST ) ); } @@ -163,6 +180,9 @@ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) */ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend ) { + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ + /* coverity[other_declaration] */ return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, 1U, __ATOMIC_SEQ_CST ) ); } @@ -177,6 +197,8 @@ static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ return ( uint32_t ) ( __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ) ); } @@ -191,6 +213,8 @@ static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ return ( uint32_t ) ( __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ) ); } @@ -205,6 +229,8 @@ static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ return ( uint32_t ) ( __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ) ); } @@ -219,6 +245,8 @@ static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, /* This header file is intended to be used with only the gcc compiler * which requires an int parameter for this routine. */ /* coverity[misra_c_2012_directive_4_6_violation] */ + /* coverity[misra_c_2012_rule_17_3_violation] */ + /* coverity[caretline] */ return ( uint32_t ) ( __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ) ); } diff --git a/ports/common/lexicon.txt b/ports/common/lexicon.txt index fe32da2aec..1646aac9ac 100644 --- a/ports/common/lexicon.txt +++ b/ports/common/lexicon.txt @@ -3,6 +3,7 @@ authmode bool ca callbackmutex +caretline clientcert closecallback compareandswap diff --git a/ports/posix/include/iot_platform_types_posix.h b/ports/posix/include/iot_platform_types_posix.h index 2c985e05e4..40be7ff077 100644 --- a/ports/posix/include/iot_platform_types_posix.h +++ b/ports/posix/include/iot_platform_types_posix.h @@ -54,9 +54,9 @@ typedef sem_t _IotSystemSemaphore_t; */ typedef struct _IotSystemTimer { - timer_t timer; /**< @brief Underlying POSIX timer. */ - void * pArgument; /**< @brief First argument to threadRoutine. */ - void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ + timer_t timer; /**< @brief Underlying POSIX timer. */ + void * pArgument; /**< @brief First argument to threadRoutine. */ + void ( * threadRoutine )( void * pArg ); /**< @brief Thread function to run on timer expiration. */ } _IotSystemTimer_t; /** diff --git a/ports/posix/lexicon.txt b/ports/posix/lexicon.txt index ce50422af5..ae0ad258e9 100644 --- a/ports/posix/lexicon.txt +++ b/ports/posix/lexicon.txt @@ -12,6 +12,7 @@ mutex onlinepubs opengroup org +parg pargument posix strftime From 0136f65b5689f2975fc6d2f8b58666aad547d90a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 30 Jan 2020 09:44:21 -0800 Subject: [PATCH 400/844] Complete coverage of iot_mqtt_validate.c (#757) --- .../standard/mqtt/src/iot_mqtt_validate.c | 82 +++++++------------ .../test/access/iot_test_access_mqtt_api.c | 2 +- .../mqtt/test/unit/iot_tests_mqtt_validate.c | 24 +++--- tests/iot_config.h | 2 +- 4 files changed, 46 insertions(+), 64 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index a71a404ce7..b571f937d2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -130,13 +130,14 @@ static bool _validateWildcardHash( uint16_t index, const IotMqttSubscription_t * pSubscription ); /** - * @brief Check the MQTT clientId length does not exceed. + * @brief Validate the MQTT client identifier. * - * @param[in] pConnectInfo The #IotMqttConnectInfo_t to validate. + * @param[in] pConnectInfo The #IotMqttConnectInfo_t containing the client identifier + * to validate. * - * @return `true` if client id length is valid, `false` otherwise. + * @return `true` if client identifier is valid, `false` otherwise. */ -static bool _validateClientIdLength( const IotMqttConnectInfo_t * pConnectInfo ); +static bool _validateClientId( const IotMqttConnectInfo_t * pConnectInfo ); /*-----------------------------------------------------------*/ @@ -194,13 +195,7 @@ static bool _validatePublishPayload( const IotMqttPublishInfo_t * pPublishInfo, /* This parameter is not used when logging is disabled. */ ( void ) pPublishTypeDescription; - if( pPublishInfo == NULL ) - { - IotLogError( "Publish information cannot be NULL." ); - - status = false; - } - else if( pPublishInfo->payloadLength != 0U ) + if( pPublishInfo->payloadLength != 0U ) { if( pPublishInfo->payloadLength > maximumPayloadLength ) { @@ -222,11 +217,6 @@ static bool _validatePublishPayload( const IotMqttPublishInfo_t * pPublishInfo, /* Empty else MISRA 15.7 */ } } - else - { - /* Empty else MISRA 15.7 */ - } - return status; } @@ -508,28 +498,43 @@ static bool _validateWildcardHash( uint16_t index, /*-----------------------------------------------------------*/ -static bool _validateClientIdLength( const IotMqttConnectInfo_t * pConnectInfo ) +static bool _validateClientId( const IotMqttConnectInfo_t * pConnectInfo ) { bool status = true; uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; bool enforceMaxClientIdLength = false; - if( pConnectInfo == NULL ) + /* Check that a client identifier was set. */ + if( pConnectInfo->pClientIdentifier == NULL ) { - IotLogError( "MQTT connection information cannot be NULL." ); + IotLogError( "Client identifier must be set." ); status = false; } + + /* Check for a zero-length client identifier. Zero-length client identifiers + * are not allowed with persistent sessions. */ + if( status == true ) + { + if( pConnectInfo->clientIdentifierLength == 0U ) + { + IotLogWarn( "A zero-length client identifier was provided." ); + + if( pConnectInfo->cleanSession == false ) + { + IotLogError( "A zero-length client identifier cannot be used with a persistent session." ); + + status = false; + } + } + } + /* The AWS IoT MQTT service enforces a client ID length limit. */ - else if( pConnectInfo->awsIotMqttMode == true ) + if( pConnectInfo->awsIotMqttMode == true ) { maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; enforceMaxClientIdLength = true; } - else - { - /* Empty else MISRA 15.7 */ - } if( status == true ) { @@ -571,30 +576,10 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) status = false; } - /* Check that a client identifier was set. */ - else if( pConnectInfo->pClientIdentifier == NULL ) - { - IotLogError( "Client identifier must be set." ); - - status = false; - } - - /* Check for a zero-length client identifier. Zero-length client identifiers - * are not allowed with clean sessions. */ - else if( pConnectInfo->clientIdentifierLength == 0U ) - { - IotLogWarn( "A zero-length client identifier was provided." ); - - if( pConnectInfo->cleanSession == true ) - { - IotLogError( "A zero-length client identifier cannot be used with a clean session." ); - - status = false; - } - } else { - /* Empty else MISRA 15.7 */ + /* Check client identifier.*/ + status = _validateClientId( pConnectInfo ); } if( status == true ) @@ -619,11 +604,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) } } - if( status == true ) - { - status = _validateClientIdLength( pConnectInfo ); - } - return status; } diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c index 34ae412a22..ff3caec768 100644 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c @@ -53,7 +53,7 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, void IotTestMqtt_mqttOperation_tryDestroy( void * pData ) { - return _mqttOperation_tryDestroy( pData ); + _mqttOperation_tryDestroy( pData ); } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c index b9dc642514..8dd22789c2 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c @@ -114,8 +114,6 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) bool validateStatus = false; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - /* NULL parameter. */ validateStatus = _IotMqtt_ValidateConnect( NULL ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); @@ -124,12 +122,19 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* Zero-length client identifier with persistent session. */ + connectInfo.cleanSession = false; + connectInfo.pClientIdentifier = ""; + connectInfo.clientIdentifierLength = 0; + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); + /* Zero-length client identifier with clean session. */ connectInfo.cleanSession = true; connectInfo.pClientIdentifier = ""; connectInfo.clientIdentifierLength = 0; validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); + TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Client identifier longer than the MQTT 3.1.1 recommended maximum length. */ connectInfo.pClientIdentifier = "longlongclientidentifier"; @@ -137,14 +142,11 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - /* AWS IoT MQTT service limit tests. */ - #if AWS_IOT_MQTT_SERVER == true - /* Client identifier too long. */ - connectInfo.clientIdentifierLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH + 1; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - connectInfo.clientIdentifierLength = 24; - #endif /* if AWS_IOT_MQTT_SERVER == true */ + /* Client identifier too long for AWS. */ + connectInfo.awsIotMqttMode = true; + connectInfo.clientIdentifierLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH + 1; + validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); + TEST_ASSERT_EQUAL_INT( false, validateStatus ); } /*-----------------------------------------------------------*/ diff --git a/tests/iot_config.h b/tests/iot_config.h index 7937718d1d..2d0a9cb8a1 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -121,7 +121,7 @@ /* Enable asserts in the libraries. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) -#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) +#define IOT_MQTT_ENABLE_ASSERTS ( ! IOT_TEST_COVERAGE ) #define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) #define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) From 65852f91a7d117eb4f676ff8e5516bc4055d48c3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 30 Jan 2020 13:30:18 -0800 Subject: [PATCH 401/844] Complete coverage of iot_mqtt_subscription.c (#762) --- libraries/standard/mqtt/src/iot_mqtt_api.c | 58 ++++++------- .../standard/mqtt/src/iot_mqtt_network.c | 82 ++++++++++--------- .../standard/mqtt/src/iot_mqtt_operation.c | 16 ++-- .../standard/mqtt/src/iot_mqtt_subscription.c | 2 +- .../standard/mqtt/src/iot_mqtt_validate.c | 39 +++++---- .../mqtt/src/private/iot_mqtt_internal.h | 28 +++---- .../test/unit/iot_tests_mqtt_subscription.c | 74 +++++++++++++++++ 7 files changed, 187 insertions(+), 112 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 225e4a24ee..e781f5bfe1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -287,34 +287,34 @@ static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePingreq_t, - _getMqttPingreqSerializer, - _IotMqtt_SerializePingreq, - serialize.pingreq ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqtt_SerializePublish_t, - _getMqttPublishSerializer, - _IotMqtt_SerializePublish, - serialize.publish ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, - _getMqttSubscribeSerializer, - _IotMqtt_SerializeSubscribe, - serialize.subscribe ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, - _getMqttUnsubscribeSerializer, - _IotMqtt_SerializeUnsubscribe, - serialize.unsubscribe ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeConnect_t, - _getMqttConnectSerializer, - _IotMqtt_SerializeConnect, - serialize.connect ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeDisconnect_t, - _getMqttDisconnectSerializer, - _IotMqtt_SerializeDisconnect, - serialize.disconnect ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePingreq_t, + _getMqttPingreqSerializer, + _IotMqtt_SerializePingreq, + serialize.pingreq ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqtt_SerializePublish_t, + _getMqttPublishSerializer, + _IotMqtt_SerializePublish, + serialize.publish ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, + _getMqttSubscribeSerializer, + _IotMqtt_SerializeSubscribe, + serialize.subscribe ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, + _getMqttUnsubscribeSerializer, + _IotMqtt_SerializeUnsubscribe, + serialize.unsubscribe ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeConnect_t, + _getMqttConnectSerializer, + _IotMqtt_SerializeConnect, + serialize.connect ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeDisconnect_t, + _getMqttDisconnectSerializer, + _IotMqtt_SerializeDisconnect, + serialize.disconnect ) #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq #define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish @@ -332,7 +332,7 @@ static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, * @brief Place holder packet identifier used when * _IotMqtt_AddSubscriptions is called with previous subscriptions lists. * Any non-zero value is acceptable, since this value is never sent out to - * the broker. + * the broker. */ #define IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID 1 diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 12f6e3c65c..ce68868d0c 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -163,46 +163,46 @@ static void _flushPacket( IotNetworkConnection_t pNetworkConnection, * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetPacketType_t, - _getPacketTypeFunc, - _IotMqtt_GetPacketType, - getPacketType ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetRemainingLength_t, - _getRemainingLengthFunc, - _IotMqtt_GetRemainingLength, - getRemainingLength ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getConnackDeserializer, - _IotMqtt_DeserializeConnack, - deserialize.connack ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getPublishDeserializer, - _IotMqtt_DeserializePublish, - deserialize.publish ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getPubackDeserializer, - _IotMqtt_DeserializePuback, - deserialize.puback ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getSubackDeserializer, - _IotMqtt_DeserializeSuback, - deserialize.suback ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getUnsubackDeserializer, - _IotMqtt_DeserializeUnsuback, - deserialize.unsuback ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getPingrespDeserializer, - _IotMqtt_DeserializePingresp, - deserialize.pingresp ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePuback_t, - _getMqttPubackSerializer, - _IotMqtt_SerializePuback, - serialize.puback ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetPacketType_t, + _getPacketTypeFunc, + _IotMqtt_GetPacketType, + getPacketType ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetRemainingLength_t, + _getRemainingLengthFunc, + _IotMqtt_GetRemainingLength, + getRemainingLength ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getConnackDeserializer, + _IotMqtt_DeserializeConnack, + deserialize.connack ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getPublishDeserializer, + _IotMqtt_DeserializePublish, + deserialize.publish ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getPubackDeserializer, + _IotMqtt_DeserializePuback, + deserialize.puback ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getSubackDeserializer, + _IotMqtt_DeserializeSuback, + deserialize.suback ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getUnsubackDeserializer, + _IotMqtt_DeserializeUnsuback, + deserialize.unsuback ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getPingrespDeserializer, + _IotMqtt_DeserializePingresp, + deserialize.pingresp ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePuback_t, + _getMqttPubackSerializer, + _IotMqtt_SerializePuback, + serialize.puback ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket ) #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType #define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength @@ -295,6 +295,8 @@ static IotMqttError_t _allocateAndReceivePacket( IotNetworkConnection_t pNetwork return status; } +/*-----------------------------------------------------------*/ + static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index f46f6f6e97..4a1741ed4d 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -48,14 +48,14 @@ * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttPublishSetDup_t, - _getMqttPublishSetDupFunc, - _IotMqtt_PublishSetDup, - serialize.publishSetDup ) - _SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttPublishSetDup_t, + _getMqttPublishSetDupFunc, + _IotMqtt_PublishSetDup, + serialize.publishSetDup ) + SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket ) #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket #define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index ddcd4b6bec..a5b1294d2a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -161,7 +161,7 @@ static bool _matchEndWildcards( const char * pTopicFilter, if( endChar == true ) { /* Determine if the topic filter ends with the '#' wildcard. */ - status = ( pTopicFilter[ filterIndex + 1U ] == '/' ) && ( pTopicFilter[ filterIndex + 2U ] == '#' ); + status = ( pTopicFilter[ filterIndex + 2U ] == '#' ); } if( status == false ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index b571f937d2..1799984b97 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -354,41 +354,40 @@ static bool _validateSubscription( bool awsIotMqttMode, /* Check for a valid QoS and callback function when subscribing. */ if( operation == IOT_MQTT_SUBSCRIBE ) { - status = _validateQos( pSubscription->qos ); - - if( status == true ) + if( pSubscription->callback.function == NULL ) { - if( pSubscription->callback.function == NULL ) - { - IotLogError( "Callback function must be set." ); + IotLogError( "Callback function must be set." ); - status = false; - } + status = false; + } + else + { + status = _validateQos( pSubscription->qos ); } } + /* Check subscription topic filter. */ if( status == true ) { - /* Check subscription topic filter. */ status = _validateString( pSubscription->pTopicFilter, pSubscription->topicFilterLength ); if( status == false ) { IotLogError( "Subscription topic filter must be set." ); } - else + } + + /* Check topic filter length compatibility with AWS IoT MQTT server. */ + if( status == true ) + { + if( awsIotMqttMode == true ) { - /* Check for compatibility with AWS IoT MQTT server. */ - if( awsIotMqttMode == true ) + if( pSubscription->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) { - /* Check topic filter length. */ - if( pSubscription->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - { - IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - - status = false; - } + IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + + status = false; } } } diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 01887c7fa0..4fdbea3a34 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -1024,20 +1024,20 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason /** * @brief Utility macro for creating serializer override selector functions */ - #define _SERIALIZER_OVERRIDE_SELECTOR( _funcType_t, _funcName, _defaultFunc, _serializerMember ) \ - static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ); \ - static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ) \ - { \ - _funcType_t _returnValue = _defaultFunc; \ - if( pSerializer != NULL ) \ - { \ - if( pSerializer->_serializerMember != NULL ) \ - { \ - _returnValue = pSerializer->_serializerMember; \ - } \ - } \ - \ - return _returnValue; \ + #define SERIALIZER_OVERRIDE_SELECTOR( funcType_t, funcName, defaultFunc, serializerMember ) \ + static funcType_t funcName( const IotMqttSerializer_t * pSerializer ); \ + static funcType_t funcName( const IotMqttSerializer_t * pSerializer ) \ + { \ + funcType_t returnValue = defaultFunc; \ + if( pSerializer != NULL ) \ + { \ + if( pSerializer->serializerMember != NULL ) \ + { \ + returnValue = pSerializer->serializerMember; \ + } \ + } \ + \ + return returnValue; \ } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c index cd6f7124ea..578669c3c8 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c @@ -202,6 +202,22 @@ static bool _waitForCount( IotMutex_t * pMutex, /*-----------------------------------------------------------*/ +/** + * @brief A subscription callback function that unlinks its subscription. + */ +static void _removalCallback( void * pArgument, + IotMqttCallbackParam_t * pPublish ) +{ + _mqttSubscription_t * pSubscription = pArgument; + + /* Silence warnings about unused parameters. */ + ( void ) pPublish; + + IotListDouble_Remove( &( pSubscription->link ) ); +} + +/*-----------------------------------------------------------*/ + /** * @brief A subscription callback function that only reports whether it was invoked. */ @@ -328,6 +344,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddMallocFail ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublish ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublishMultiple ); + RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionUnsubscribe ); RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionReferences ); RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchTrue ); RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchFalse ); @@ -768,6 +785,63 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) /*-----------------------------------------------------------*/ +/** + * @brief Tests that the unsubscribed flag is respected for subscriptions. + */ +TEST( MQTT_Unit_Subscription, SubscriptionUnsubscribe ) +{ + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink; + IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; + + /* Set the subscription and corresponding publish info. */ + subscription.pTopicFilter = "/test"; + subscription.topicFilterLength = 5; + subscription.callback.function = _removalCallback; + + callbackParam.u.message.info.pTopicName = "/test"; + callbackParam.u.message.info.topicNameLength = 5; + callbackParam.u.message.info.pPayload = ""; + callbackParam.u.message.info.payloadLength = 0; + + /* Add the subscription. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, + 1, + &subscription, + 1 ) ); + + /* Increment reference count of subscription and connection. */ + pSubscriptionLink = IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ); + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + pSubscription->references++; + _pMqttConnection->references++; + + /* Attempt to remove the subscription. */ + _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, 1, MQTT_REMOVE_ALL_SUBSCRIPTIONS ); + + /* Check that the subscription wasn't removed and the unsubscribed flag was set. */ + TEST_ASSERT_EQUAL_INT( true, IotLink_IsLinked( &( pSubscription->link ) ) ); + TEST_ASSERT_EQUAL_INT( true, pSubscription->unsubscribed ); + + /* Invoke the publish callback. This should remove the subscription. */ + pSubscription->callback.pCallbackContext = pSubscription; + _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, &callbackParam ); + TEST_ASSERT_EQUAL_INT( false, IotLink_IsLinked( &( pSubscription->link ) ) ); + + /* Put the subscription back in the list. */ + IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), pSubscriptionLink ); + + /* Disconnect the MQTT connection. As the subscription had its reference count + * incremented by this test, it should not be freed by disconnect. */ + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + _connectionCreated = false; + + IotMqtt_FreeSubscription( pSubscription ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests that subscriptions are properly reference counted. */ From b43696270e1bc8101d4a4a3c52448dc2895e6e9f Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 30 Jan 2020 14:25:11 -0800 Subject: [PATCH 402/844] Address MQTT MISRA 11.5, 11.8, and 11.9 (#753) * Fix and annotate MISRA 11.8 * Turn 11.8 violations into 8.13 violations * Fix MQTT MISRA 11.9 violation * Disable 11.5 in MISRA config --- .../common/include/iot_linear_containers.h | 1 + libraries/standard/mqtt/lexicon.txt | 2 ++ libraries/standard/mqtt/src/iot_mqtt_api.c | 7 +++++-- libraries/standard/mqtt/src/iot_mqtt_operation.c | 7 +++++-- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 12 ++++++++++-- .../standard/mqtt/src/iot_mqtt_subscription.c | 15 ++++++++++++--- .../standard/mqtt/src/private/iot_mqtt_internal.h | 2 +- scripts/coverity_misra.config | 4 ++++ 8 files changed, 40 insertions(+), 10 deletions(-) diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h index 08b0340f31..689ea5e9d0 100644 --- a/libraries/standard/common/include/iot_linear_containers.h +++ b/libraries/standard/common/include/iot_linear_containers.h @@ -623,6 +623,7 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * { /* The const must be cast away to match this function's return value. Nevertheless, * this function will respect the const-ness of pStartPoint. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ IotLink_t * pCurrent = ( IotLink_t * ) pStartPoint, * pMatchedLink = NULL; bool matchFound = false; diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 4ba8da84a0..e5e06b0079 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -132,6 +132,7 @@ pnetworkcontext pnetworkinterface pnextbyte poperation +poperationlink posix ppacketidentifier ppacketidentifierhigh @@ -143,6 +144,7 @@ pre preceiveddata premainingdata pserializer +psubscriptionlink ptopicfilter ptopicname puback diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index e781f5bfe1..be8b1c04ca 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -370,8 +370,11 @@ static bool _mqttSubscription_setUnsubscribe( const IotLink_t * const pSubscript * must never be NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); - /* Adding parentheses to parameters of IotLink_Container is not applicable + /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the + * starting address of the struct and does not modify the link it points to. + * Adding parentheses to parameters of IotLink_Container is not applicable * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ /* coverity[misra_c_2012_rule_20_7_violation] */ /* coverity[caretline] */ _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, @@ -1243,7 +1246,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqttError_t status = IOT_MQTT_SUCCESS; bool ownNetworkConnection = false; IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - IotNetworkConnection_t pNetworkConnection = { 0 }; + IotNetworkConnection_t pNetworkConnection = NULL; _mqttOperation_t * pOperation = NULL; _mqttConnection_t * pNewMqttConnection = NULL; diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 4a1741ed4d..8bf96297b3 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -167,8 +167,11 @@ static bool _mqttOperation_match( const IotLink_t * const pOperationLink, * must never be NULL. */ IotMqtt_Assert( pOperationLink != NULL ); - /* Adding parentheses to parameters of IotLink_Container is not applicable + /* Casting `pOperationLink` to uint8_t * is done only to calculate the + * starting address of the struct and does not modify the link it points to. + * Adding parentheses to parameters of IotLink_Container is not applicable * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ /* coverity[misra_c_2012_rule_20_7_violation] */ /* coverity[caretline] */ const _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, @@ -945,7 +948,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, /* Free buffers associated with the current PUBLISH message. */ IotMqtt_Assert( pOperation->u.publish.pReceivedData != NULL ); - IotMqtt_FreeMessage( ( void * ) pOperation->u.publish.pReceivedData ); + IotMqtt_FreeMessage( pOperation->u.publish.pReceivedData ); /* Free the incoming PUBLISH operation. */ IotMqtt_FreeOperation( pOperation ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 5191e43c59..e5fb6b2cb4 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -2060,7 +2060,11 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, size_t * pPacketSize ) { /* PINGREQ packets are always the same. */ - static const uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = + /* It is not necessary to make this array const. Since there are other + * types of MQTT packets that are not constant, this array would be + * cast to remove the const qualifier anyway. */ + /* coverity[misra_c_2012_rule_8_13_violation] */ + static uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = { MQTT_PACKET_TYPE_PINGREQ, 0x00 @@ -2116,7 +2120,11 @@ IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, size_t * pPacketSize ) { /* DISCONNECT packets are always the same. */ - static const uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = + /* It is not necessary to make this array const. Since there are other + * types of MQTT packets that are not constant, this array would be + * cast to remove the const qualifier anyway. */ + /* coverity[misra_c_2012_rule_8_13_violation] */ + static uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = { MQTT_PACKET_TYPE_DISCONNECT, 0x00 diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index a5b1294d2a..67b53c461e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -289,8 +289,11 @@ static bool _topicMatch( const IotLink_t * const pSubscriptionLink, * will never pass NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); - /* Adding parentheses to parameters of IotLink_Container is not applicable + /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the + * starting address of the struct and does not modify the link it points to. + * Adding parentheses to parameters of IotLink_Container is not applicable * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ /* coverity[misra_c_2012_rule_20_7_violation] */ /* coverity[caretline] */ const _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, @@ -331,8 +334,11 @@ static bool _packetMatch( const IotLink_t * const pSubscriptionLink, * must never be NULL. */ IotMqtt_Assert( pSubscriptionLink != NULL ); - /* Adding parentheses to parameters of IotLink_Container is not applicable + /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the + * starting address of the struct and does not modify the link it points to. + * Adding parentheses to parameters of IotLink_Container is not applicable * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ /* coverity[misra_c_2012_rule_20_7_violation] */ /* coverity[caretline] */ _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, @@ -671,8 +677,11 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, /* Check if a matching subscription was found. */ if( pSubscriptionLink != NULL ) { - /* Adding parentheses to parameters of IotLink_Container is not applicable + /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the + * starting address of the struct and does not modify the link it points to. + * Adding parentheses to parameters of IotLink_Container is not applicable * because it uses type-casting and offsetof, and would cause compiling errors. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ /* coverity[misra_c_2012_rule_20_7_violation] */ /* coverity[caretline] */ pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 4fdbea3a34..b74e537e7d 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -373,7 +373,7 @@ typedef struct _mqttOperation struct { IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ - const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ + void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ } publish; } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ } _mqttOperation_t; diff --git a/scripts/coverity_misra.config b/scripts/coverity_misra.config index 8a94b955c5..fd518e1973 100644 --- a/scripts/coverity_misra.config +++ b/scripts/coverity_misra.config @@ -26,6 +26,10 @@ deviation: "Rule 2.5", reason: "Allow unused macros. Library headers may define macros intended for the application's use, but not used by a specific file." }, + { + deviation: "Rule 11.5", + reason: "Allow casts from void *. Contexts are passed as void * and must be cast to the correct data type before use." + }, { deviation: "Rule 21.1", reason: "Allow use of all names." From d12bae8f33c5aff33a859749bf22d0ec3cf71bb3 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Thu, 30 Jan 2020 15:49:06 -0800 Subject: [PATCH 403/844] Shadow Misra 8.3 and 10.5 Violations (#755) * misra shadow: 8.3 Violations * misra shadow: 10.5 violations Co-authored-by: Gary Wicker <14828980+gkwicker@users.noreply.github.com> --- libraries/aws/shadow/include/aws_iot_shadow.h | 4 +- libraries/aws/shadow/src/aws_iot_shadow_api.c | 6 +- .../aws/shadow/src/aws_iot_shadow_operation.c | 78 ++++++++++++++++++- .../shadow/src/aws_iot_shadow_subscription.c | 12 +-- .../src/private/aws_iot_shadow_internal.h | 30 +++++-- 5 files changed, 111 insertions(+), 19 deletions(-) diff --git a/libraries/aws/shadow/include/aws_iot_shadow.h b/libraries/aws/shadow/include/aws_iot_shadow.h index dee775f255..cdfa70d443 100644 --- a/libraries/aws/shadow/include/aws_iot_shadow.h +++ b/libraries/aws/shadow/include/aws_iot_shadow.h @@ -77,7 +77,7 @@ * Calling this function more than once without first calling @ref * shadow_function_cleanup may result in a crash. * - * @param[in] mqttTimeout The amount of time (in milliseconds) that the Shadow + * @param[in] mqttTimeoutMs The amount of time (in milliseconds) that the Shadow * library will wait for MQTT operations. Optional; set this to `0` to use * @ref AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS. * @@ -90,7 +90,7 @@ * @see @ref shadow_function_cleanup */ /* @[declare_shadow_init] */ -AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeout ); +AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ); /* @[declare_shadow_init] */ /** diff --git a/libraries/aws/shadow/src/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c index 41b123f586..254f593f43 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_api.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_api.c @@ -349,7 +349,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio } /* Check parameters. */ - status = _validateThingNameFlags( ( _shadowOperationType_t ) ( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ), + status = _validateThingNameFlags( _AwsIotShadow_IntToShadowOperationType( ( ( uint32_t ) type ) + SHADOW_OPERATION_COUNT ), pThingName, thingNameLength, 0, @@ -590,7 +590,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, IotMqttCallbackParam_t * pMessage ) { AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; + AwsIotShadowCallbackParam_t callbackParam = { .callbackType = AWS_IOT_SHADOW_DELETE_COMPLETE }; _shadowSubscription_t * pSubscription = NULL; const char * pThingName = NULL; size_t thingNameLength = 0; @@ -628,7 +628,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the callback type. Shadow callbacks are enumerated after the operations. */ - callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ); + callbackParam.callbackType = _AwsIotShadow_IntToShadowCallbackType( ( ( uint32_t ) type ) + SHADOW_OPERATION_COUNT ); /* Set the remaining members of the callback param. */ callbackParam.mqttConnection = pMessage->mqttConnection; diff --git a/libraries/aws/shadow/src/aws_iot_shadow_operation.c b/libraries/aws/shadow/src/aws_iot_shadow_operation.c index 7b267c1fd4..07abc2a25e 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_operation.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_operation.c @@ -184,6 +184,78 @@ IotMutex_t _AwsIotShadowPendingOperationsMutex; /*-----------------------------------------------------------*/ +_shadowOperationType_t _AwsIotShadow_IntToShadowOperationType( uint32_t n ) +{ + _shadowOperationType_t val = SHADOW_DELETE; + + switch( n ) + { + case 0: + val = SHADOW_DELETE; + break; + + case 1: + val = SHADOW_GET; + break; + + case 2: + val = SHADOW_UPDATE; + break; + + case 3: + val = SET_DELTA_CALLBACK; + break; + + case 4: + val = SET_UPDATED_CALLBACK; + break; + + default: + AwsIotShadow_Assert( n < 5U ); + break; + } + + return val; +} + +/*-----------------------------------------------------------*/ + +AwsIotShadowCallbackType_t _AwsIotShadow_IntToShadowCallbackType( uint32_t n ) +{ + AwsIotShadowCallbackType_t val = AWS_IOT_SHADOW_DELETE_COMPLETE; + + switch( n ) + { + case 0: + val = AWS_IOT_SHADOW_DELETE_COMPLETE; + break; + + case 1: + val = AWS_IOT_SHADOW_GET_COMPLETE; + break; + + case 2: + val = AWS_IOT_SHADOW_UPDATE_COMPLETE; + break; + + case 3: + val = AWS_IOT_SHADOW_DELTA_CALLBACK; + break; + + case 4: + val = AWS_IOT_SHADOW_UPDATED_CALLBACK; + break; + + default: + AwsIotShadow_Assert( n < 5U ); + break; + } + + return val; +} + +/*-----------------------------------------------------------*/ + static bool _shadowOperationMatch( const IotLink_t * pOperationLink, void * pMatch ) { @@ -252,7 +324,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, _shadowOperation_t * pOperation = NULL; IotLink_t * pOperationLink = NULL; AwsIotStatus_t status = AWS_IOT_UNKNOWN; - _operationMatchParams_t param = { .type = ( _shadowOperationType_t ) 0 }; + _operationMatchParams_t param = { .type = SHADOW_DELETE }; uint32_t flags = 0; /* Set operation type to search. */ @@ -454,7 +526,7 @@ static void _updateCallback( void * pArgument, static void _notifyCompletion( _shadowOperation_t * pOperation ) { - AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; + AwsIotShadowCallbackParam_t callbackParam = { .callbackType = AWS_IOT_SHADOW_DELETE_COMPLETE }; _shadowSubscription_t * pSubscription = pOperation->pSubscription, * pRemovedSubscription = NULL; @@ -486,7 +558,7 @@ static void _notifyCompletion( _shadowOperation_t * pOperation ) if( pOperation->notify.callback.function != NULL ) { /* Set the common members of the callback parameter. */ - callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; + callbackParam.callbackType = _AwsIotShadow_IntToShadowCallbackType( ( uint32_t ) pOperation->type ); callbackParam.mqttConnection = pOperation->mqttConnection; callbackParam.u.operation.result = pOperation->status; callbackParam.u.operation.reference = pOperation; diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index 42bf531971..a5a2a2d6d0 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -172,14 +172,14 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, /* Check for active operations. If any Shadow operation's subscription * reference count is not 0, then the subscription cannot be removed. */ - for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) + for( i = 0; i < ( int32_t ) SHADOW_OPERATION_COUNT; i++ ) { if( pSubscription->references[ i ] > 0 ) { /* In some implementations IotLogDebug() maps to C standard printing API - * that needs specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ + * that needs specific primitive types for format specifiers. Also, + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "Reference count %ld for %.*s subscription object. " "Subscription cannot be removed yet.", @@ -443,7 +443,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); - for( i = 0; i < ( uint32_t ) SHADOW_OPERATION_COUNT; i++ ) + for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) { if( ( flags & ( 0x1UL << i ) ) != 0U ) { @@ -459,7 +459,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio { /* Generate the prefix of the Shadow topic. This function will not * fail when given a buffer. */ - ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) i, + ( void ) _AwsIotShadow_GenerateShadowTopic( _AwsIotShadow_IntToShadowOperationType( i ), pThingName, thingNameLength, &( pSubscription->pTopicBuffer ), diff --git a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h index e7b7ab3022..b276a5e901 100644 --- a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h +++ b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h @@ -193,7 +193,7 @@ * * The 3 Shadow operations are DELETE, GET, and UPDATE. */ -#define SHADOW_OPERATION_COUNT ( 3 ) +#define SHADOW_OPERATION_COUNT ( 3U ) /** * @brief The number of currently available Shadow callbacks. @@ -265,8 +265,8 @@ * IOT_MQTT_NO_MEMORY to AWS_IOT_SHADOW_NO_MEMORY * all other error codes to AWS_IOT_SHADOW_MQTT_ERROR */ -#define SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( X ) \ - ( ( X ) == IOT_MQTT_SUCCESS ) ? AWS_IOT_SHADOW_SUCCESS : \ +#define SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( X ) \ + ( ( X ) == IOT_MQTT_SUCCESS ) ? AWS_IOT_SHADOW_SUCCESS : \ ( ( X ) == IOT_MQTT_NO_MEMORY ) ? AWS_IOT_SHADOW_NO_MEMORY : \ AWS_IOT_SHADOW_MQTT_ERROR @@ -389,14 +389,14 @@ extern IotMutex_t _AwsIotShadowSubscriptionsMutex; * @brief Create a record for a new in-progress Shadow operation. * * @param[out] pNewOperation Set to point to the new operation on success. - * @param[in] operation The type of Shadow operation. + * @param[in] type The type of Shadow operation. * @param[in] flags Flags variables passed to a user-facing Shadow function. * @param[in] pCallbackInfo User-provided callback function and parameter. * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY */ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOperation, - _shadowOperationType_t operation, + _shadowOperationType_t type, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo ); @@ -454,6 +454,26 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn _shadowOperation_t * pOperation, const AwsIotShadowDocumentInfo_t * pDocumentInfo ); + + +/** + * @brief Convert an integer to the shadow operation type. + * + * @param[in] n The integer to convert. + * + * @return The enum value associated with the input. + */ +_shadowOperationType_t _AwsIotShadow_IntToShadowOperationType( uint32_t n ); + +/** + * @brief Convert an integer to the shadow callback type. + * + * @param[in] n The integer to convert. + * + * @return The enum value associated with the input. + */ +AwsIotShadowCallbackType_t _AwsIotShadow_IntToShadowCallbackType( uint32_t n ); + /*---------------------- Shadow subscription functions ----------------------*/ /** From 1f62b2398fc50193dbf48cc5c28798c9245486c4 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 31 Jan 2020 11:00:39 -0800 Subject: [PATCH 404/844] Disable MISRA Rule 3.1 (#766) --- scripts/coverity_misra.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/coverity_misra.config b/scripts/coverity_misra.config index fd518e1973..2ef1337996 100644 --- a/scripts/coverity_misra.config +++ b/scripts/coverity_misra.config @@ -26,6 +26,10 @@ deviation: "Rule 2.5", reason: "Allow unused macros. Library headers may define macros intended for the application's use, but not used by a specific file." }, + { + deviation: "Rule 3.1", + reason: "Allow nested comments. Documentation blocks contain comments for example code." + }, { deviation: "Rule 11.5", reason: "Allow casts from void *. Contexts are passed as void * and must be cast to the correct data type before use." From 0d5de5cf4fbbc8105d67030139a7a8a536968921 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 31 Jan 2020 11:58:15 -0800 Subject: [PATCH 405/844] Re-enable checks for resource allocation failure (#768) --- .../mqtt/include/types/iot_mqtt_types.h | 4 +- libraries/standard/mqtt/src/iot_mqtt_api.c | 17 +- .../mqtt/test/unit/iot_tests_mqtt_api.c | 180 +++++------------- scripts/ci_test_coverage.sh | 2 +- 4 files changed, 56 insertions(+), 147 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index f80d8d0ba7..2d01119032 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -760,7 +760,7 @@ typedef IotMqttError_t ( * IotMqttSerializeConnect_t )( const IotMqttConnectInfo * @param[out] uint8_t** Where the PINGREQ packet is written. * @param[out] size_t* Size of the PINGREQ packet. */ -typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( uint8_t ** pDisconnectPacket, +typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( uint8_t ** pPingreqPacket, size_t * pPacketSize ); /** @@ -797,7 +797,7 @@ typedef IotMqttError_t ( * IotMqttSerializeSubscribe_t )( const IotMqttSubscript * @param[out] uint8_t** Where the DISCONNECT packet is written. * @param[out] size_t* Size of the DISCONNECT packet. */ -typedef IotMqttError_t ( * IotMqttSerializeDisconnect_t )( uint8_t ** ppDisconnectPacket, +typedef IotMqttError_t ( * IotMqttSerializeDisconnect_t )( uint8_t ** pDisconnectPacket, size_t * pPacketSize ); /** diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index be8b1c04ca..8f55cf03de 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -469,8 +469,8 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = ( uint32_t ) keepAliveSeconds * 1000U; /* Generate a PINGREQ packet. */ - serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), - &( pMqttConnection->pingreq.u.operation.packetSize ) ); + serializeStatus = _getMqttPingreqSerializer( pNetworkInfo->pMqttSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), + &( pMqttConnection->pingreq.u.operation.packetSize ) ); if( serializeStatus != IOT_MQTT_SUCCESS ) { @@ -480,20 +480,13 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo } else { - /* Create the task pool job that processes keep-alive. */ + /* Create the task pool job that processes keep-alive. Task pool job + * creation for a pre-allocated job should never fail. */ jobStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, pMqttConnection, &( pMqttConnection->pingreq.jobStorage ), &( pMqttConnection->pingreq.job ) ); - - /* Task pool job creation for a pre-allocated job should never fail. - * Abort the program if it does. */ - if( jobStatus != IOT_TASKPOOL_SUCCESS ) - { - IotLogError( "Failed to create keep-alive job for new connection." ); - - IotMqtt_Assert( false ); - } + IotMqtt_Assert( jobStatus == IOT_TASKPOOL_SUCCESS ); /* Keep-alive references its MQTT connection, so increment reference. */ ( pMqttConnection->references )++; diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 64ec7a8e6a..6bd4bc3198 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -165,6 +165,11 @@ static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; */ static IotNetworkInterface_t _networkInterface = { 0 }; +/** + * @brief A packet allocated by _serializePingreq. + */ +static uint8_t * _pAllocatedPingreq = NULL; + /*-----------------------------------------------------------*/ /** @@ -318,43 +323,6 @@ static size_t _sendDelay( IotNetworkConnection_t pSendContext, /*-----------------------------------------------------------*/ -/** - * @brief A create function that fails when server info is not supplied. - */ -static IotNetworkError_t _createMock( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ) -{ - ( void ) pCredentialInfo; - ( void ) pConnection; - - if( pServerInfo == NULL ) - { - return IOT_NETWORK_FAILURE; - } - else - { - return IOT_NETWORK_SUCCESS; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief An empty function for task pool job. - */ -static void _testTaskRoutine( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ) -{ - /* Do Nothing */ - ( void ) pTaskPool; - ( void ) pJob; - ( void ) pUserContext; -} - -/*-----------------------------------------------------------*/ - /** * @brief This send function checks that a duplicate outgoing message differs from * the original. @@ -581,6 +549,33 @@ static IotMqttError_t _getNextByte( IotNetworkConnection_t pNetworkInterface, /*-----------------------------------------------------------*/ +/** + * @brief A PINGREQ serializer that attempts to allocate memory (unlike the default). + */ +IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + TEST_ASSERT_NULL( _pAllocatedPingreq ); + _pAllocatedPingreq = IotTest_Malloc( 1 ); + + if( _pAllocatedPingreq != NULL ) + { + *_pAllocatedPingreq = MQTT_PACKET_TYPE_PINGREQ; + *pPingreqPacket = _pAllocatedPingreq; + *pPacketSize = 1; + } + else + { + status = IOT_MQTT_NO_MEMORY; + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT API tests. */ @@ -661,8 +656,6 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, DeserializeResponseChecks ); RUN_TEST_CASE( MQTT_Unit_API, DeserializePublishChecks ); RUN_TEST_CASE( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ); - RUN_TEST_CASE( MQTT_Unit_API, MqttOperationTryDestroy ); - RUN_TEST_CASE( MQTT_Unit_API, CreateNetworkConnectionCheck ); } /*-----------------------------------------------------------*/ @@ -999,6 +992,7 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) int32_t i = 0; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; /* Initialize parameters. */ _networkInterface.send = _sendSuccess; @@ -1008,6 +1002,9 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + serializer.serialize.pingreq = _serializePingreq; + _networkInfo.pMqttSerializer = &serializer; + for( i = 0; ; i++ ) { UnityMalloc_MakeMallocFailAfterCount( i ); @@ -1019,6 +1016,13 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) TIMEOUT_MS, &_pMqttConnection ); + /* Free any allocated PINGREQ. */ + if( _pAllocatedPingreq != NULL ) + { + IotTest_Free( _pAllocatedPingreq ); + _pAllocatedPingreq = NULL; + } + /* If the return value is timeout, then all memory allocation succeeded * and the loop can exit. The expected return value is timeout (and not * success) because the receive callback is never invoked. */ @@ -1155,12 +1159,8 @@ TEST( MQTT_Unit_API, DisconnectAlreadyDisconnected ) IotMqtt_Disconnect( mqttConnection, 0 ); TEST_ASSERT_EQUAL_INT( 1, mqttConnection->references ); - /* One final disconnect to bring reference count to zero and free - * the connection */ - IotMqtt_Disconnect( mqttConnection, 0 ); - - /* mqttConnection should be freed after above call. - * Test should not fail with any Unity memory leak asserts. */ + /* Clean up test. */ + IotTest_MqttMockCleanup(); } /*-----------------------------------------------------------*/ @@ -2289,11 +2289,12 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) /* Test with invalid remaining length. */ buffer[ 0 ] = 0x20; /* CONN ACK */ + /* To generate invalid remaining length response, * three bytes need to have MSB (or continuation bit, 0x80) set */ buffer[ 1 ] = 0xFF; - buffer[ 2 ] = 0xFF; - buffer[ 3 ] = 0xFF; + buffer[ 2 ] = 0xFF; + buffer[ 3 ] = 0xFF; status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); } @@ -2401,88 +2402,3 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) } /*-----------------------------------------------------------*/ - -/** - * @brief Tests internal function _mqttOperation_tryDestroy works - * as intended. - * @note: Uses access function. - */ -TEST( MQTT_Unit_API, MqttOperationTryDestroy ) -{ - _mqttOperation_t * pMqttOperation = NULL; - /* _mqttOperation_t mqttOperation ; */ - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotTaskPoolJob_t pTaskPoolJob = IOT_TASKPOOL_JOB_INITIALIZER; - IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; - IotTaskPoolJobStorage_t _testJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; - - /* Create Task pool Job */ - taskPoolError = IotTaskPool_CreateJob( _testTaskRoutine, NULL, &_testJobStorage, &pTaskPoolJob ); - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, taskPoolError ); - /* Allocate operation */ - pMqttOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - TEST_ASSERT_NOT_NULL( pMqttOperation ) - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); - - void * pData = ( void * ) pMqttOperation; - memset( pData, 0, sizeof( _mqttOperation_t ) ); - - /* Non Publish operation */ - pMqttOperation->incomingPublish = false; - pMqttOperation->pMqttConnection = mqttConnection; - pMqttOperation->job = pTaskPoolJob; - pMqttOperation->u.operation.jobReference = 2; - IotTestMqtt_mqttOperation_tryDestroy( pData ); - /* Job reference must be decremented, Operation must be still allocated */ - TEST_ASSERT_EQUAL_INT( 1, pMqttOperation->u.operation.jobReference ); - - /* Publish Operation */ - /* reset mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); - pMqttOperation->incomingPublish = true; - pMqttOperation->u.publish.pReceivedData = IotMqtt_MallocMessage( 10 ); - - /* - * pOperation will be destroyed after this call and the test - * should not report any memory leaks because the destroy - * Operation should free all memory. - */ - IotTestMqtt_mqttOperation_tryDestroy( pData ); - - /* The call should not assert, operation should be destroyed */ - IotTest_MqttMockCleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests internal function _createNetworkConnection works - * as intended. - * @note: Uses access function. - */ -TEST( MQTT_Unit_API, CreateNetworkConnectionCheck ) -{ - IotMqttNetworkInfo_t * pNetworkInfo = NULL; - IotNetworkConnection_t * pNetworkConnection = { 0 }; - bool createdNetworkConnection = false; - - /* Test for parameter validation */ - TEST_ASSERT_EQUAL( IOT_NETWORK_BAD_PARAMETER, IotTestMqtt_createNetworkConnection( pNetworkInfo, - pNetworkConnection, - &createdNetworkConnection ) ); - - /* Setup correct network info */ - pNetworkInfo = &_networkInfo; - pNetworkInfo->createNetworkConnection = true; - pNetworkInfo->pNetworkInterface = &_networkInterface; - _networkInterface.create = _createMock; - /* Invalid server info */ - pNetworkInfo->u.setup.pNetworkServerInfo = NULL; - - TEST_ASSERT_NOT_EQUAL( IOT_MQTT_SUCCESS, IotTestMqtt_createNetworkConnection( pNetworkInfo, - pNetworkConnection, - &createdNetworkConnection ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index a30179f6cc..3c58af13d0 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -22,7 +22,7 @@ function generate_coverage() { } # Overwrite the value of the COMPILER_OPTIONS variable to remove any thread sanitizer flags, and replace with coverage flags. -export COMPILER_OPTIONS="-DIOT_TEST_COVERAGE=1 --coverage -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" +export COMPILER_OPTIONS="-DIOT_BUILD_TESTS=1 -DIOT_TEST_COVERAGE=1 --coverage -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" SCRIPTS_FOLDER_PATH=../scripts From 06e1727c6a45b9fdea009b61e0209b1e33005d69 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Fri, 31 Jan 2020 15:01:15 -0800 Subject: [PATCH 406/844] Fix MISRA 20.9 by providing default macro definitions (#763) --- demos/iot_config.h | 9 +++++++-- libraries/standard/mqtt/src/iot_mqtt_api.c | 7 +++++-- libraries/standard/mqtt/src/iot_mqtt_subscription.c | 3 +++ ports/common/include/iot_atomic.h | 10 +++++++++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/demos/iot_config.h b/demos/iot_config.h index 0cab8ca9a2..750b7ab9f9 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -45,8 +45,13 @@ * #define IOT_DEMO_IDENTIFIER "" */ /* MQTT demo configuration. The demo publishes bursts of messages. */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ + +/* MQTT library configuration. */ +#ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES + #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) +#endif /* Shadow demo configuration. The demo publishes periodic Shadow updates and responds * to changing Shadows. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 8f55cf03de..c2b72d731c 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -45,8 +45,8 @@ #if ( IOT_MQTT_ENABLE_ASSERTS != 0 ) && ( IOT_MQTT_ENABLE_ASSERTS != 1 ) #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." #endif -#if ( IOT_MQTT_ENABLE_METRICS != 0 ) && ( IOT_MQTT_ENABLE_METRICS != 1 ) - #error "IOT_MQTT_ENABLE_METRICS must be 0 or 1." +#if ( AWS_IOT_MQTT_ENABLE_METRICS != 0 ) && ( AWS_IOT_MQTT_ENABLE_METRICS != 1 ) + #error "AWS_IOT_MQTT_ENABLE_METRICS must be 0 or 1." #endif #if ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 ) && ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 ) #error "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." @@ -1979,6 +1979,9 @@ const char * IotMqtt_OperationType( IotMqttOperationType_t operation ) /*-----------------------------------------------------------*/ /* Provide access to internal functions and variables if testing. */ +/* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ +/* coverity[misra_c_2012_rule_20_9_violation] */ +/* coverity[caretline] */ #if IOT_BUILD_TESTS == 1 #include "iot_test_access_mqtt_api.c" #endif diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 67b53c461e..765373aacf 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -706,6 +706,9 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ /* Provide access to internal functions and variables if testing. */ +/* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ +/* coverity[misra_c_2012_rule_20_9_violation] */ +/* coverity[caretline] */ #if IOT_BUILD_TESTS == 1 #include "iot_test_access_mqtt_subscription.c" #endif diff --git a/ports/common/include/iot_atomic.h b/ports/common/include/iot_atomic.h index bd064af687..286f4ad436 100644 --- a/ports/common/include/iot_atomic.h +++ b/ports/common/include/iot_atomic.h @@ -36,6 +36,10 @@ #ifndef IOT_ATOMIC_H_ #define IOT_ATOMIC_H_ +#ifndef IOT_ATOMIC_USE_PORT + #define IOT_ATOMIC_USE_PORT ( 0 ) +#endif + /* Use an atomic port if provided. */ #if IOT_ATOMIC_USE_PORT == 1 #include "atomic/iot_atomic_port.h" @@ -59,10 +63,14 @@ #define IOT_ATOMIC_GENERIC 1 #endif #endif /* ifdef __clang__ */ -#else /* if IOT_ATOMIC_USE_PORT == 1 */ +#else /* if IOT_ATOMIC_USE_PORT == 1 */ #define IOT_ATOMIC_GENERIC 1 #endif /* if IOT_ATOMIC_USE_PORT == 1 */ +#ifndef IOT_ATOMIC_GENERIC + #define IOT_ATOMIC_GENERIC ( 0 ) +#endif + /* Include the generic atomic header if no supported compiler was found. */ #if ( IOT_ATOMIC_GENERIC == 1 ) #include "atomic/iot_atomic_generic.h" From fff8078c0764ae7e00b4474edea9d8809f5a3151 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 31 Jan 2020 20:31:08 -0800 Subject: [PATCH 407/844] Increase coverage in iot_mqtt_api.c (#769) --- libraries/standard/mqtt/CMakeLists.txt | 1 + libraries/standard/mqtt/src/iot_mqtt_api.c | 11 +- .../mqtt/test/access/iot_test_access_mqtt.h | 18 +- .../test/access/iot_test_access_mqtt_api.c | 20 +- libraries/standard/mqtt/test/iot_tests_mqtt.c | 1 + .../mqtt/test/system/iot_tests_mqtt_system.c | 24 +- .../mqtt/test/unit/iot_tests_mqtt_api.c | 96 +++- .../mqtt/test/unit/iot_tests_mqtt_platform.c | 412 ++++++++++++++++++ 8 files changed, 517 insertions(+), 66 deletions(-) create mode 100644 libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index 8ef20a7f43..cf655d9795 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -70,6 +70,7 @@ if( ${IOT_BUILD_TESTS} ) # MQTT unit test sources. set( MQTT_UNIT_TEST_SOURCES test/unit/iot_tests_mqtt_api.c + test/unit/iot_tests_mqtt_platform.c test/unit/iot_tests_mqtt_receive.c test/unit/iot_tests_mqtt_subscription.c test/unit/iot_tests_mqtt_validate.c ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index c2b72d731c..d4d0e3d0a6 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -699,6 +699,7 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) } /*-----------------------------------------------------------*/ + static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation, IotMqttConnection_t mqttConnection, const IotMqttSubscription_t * pSubscriptionList, @@ -1181,16 +1182,10 @@ IotMqttError_t IotMqtt_Init( void ) status = IOT_MQTT_INIT_FAILED; } + + if( status == IOT_MQTT_SUCCESS ) #endif /* ifdef _IotMqtt_InitSerializeAdditional */ #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - - /* If the above preprocessor conditions are satisfied, it is - * possible that status != IOT_MQTT_SUCCESS. Therefore, this - * condition is not an invariant, and the MISRA 14.3 violation is - * a false positive. */ - /* coverity[misra_c_2012_rule_14_3_violation] */ - /* coverity[const] */ - if( status == IOT_MQTT_SUCCESS ) { IotLogInfo( "MQTT library successfully initialized." ); } diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h index a1375ade5c..a32f820b83 100644 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h @@ -41,23 +41,11 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, uint16_t keepAliveSeconds ); /** - * @brief Test access function for #_createNetworkConnection. + * @brief Test access function for #_scheduleKeepAlive. * - * @see #_createNetworkConnection. + * @see #_scheduleKeepAlive. */ - -IotNetworkError_t IotTestMqtt_createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - IotNetworkConnection_t * pNetworkConnection, - bool * pCreatedNewNetworkConnection ); - -/*------------------------- iot_mqtt_serialize.c ------------------------*/ - -/** - * @brief Test access function for #_mqttOperation_tryDestroy. - * - * @see #_mqttOperation_tryDestroy. - */ -void IotTestMqtt_mqttOperation_tryDestroy( void * pData ); +IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); /*------------------------- iot_mqtt_serialize.c ------------------------*/ diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c index ff3caec768..a349e9b19a 100644 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c @@ -33,12 +33,7 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ); -void IotTestMqtt_mqttOperation_tryDestroy( void * pData ); - -IotNetworkError_t IotTestMqtt_createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - IotNetworkConnection_t * pNetworkConnection, - bool * pCreatedNewNetworkConnection ); - +IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); /*-----------------------------------------------------------*/ @@ -51,18 +46,9 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -void IotTestMqtt_mqttOperation_tryDestroy( void * pData ) -{ - _mqttOperation_tryDestroy( pData ); -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotTestMqtt_createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - IotNetworkConnection_t * pNetworkConnection, - bool * pCreatedNewNetworkConnection ) +IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ) { - return _createNetworkConnection( pNetworkInfo, pNetworkConnection, pCreatedNewNetworkConnection ); + return _scheduleKeepAlive( pMqttConnection ); } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/iot_tests_mqtt.c b/libraries/standard/mqtt/test/iot_tests_mqtt.c index 841406d568..f4e83dda5e 100644 --- a/libraries/standard/mqtt/test/iot_tests_mqtt.c +++ b/libraries/standard/mqtt/test/iot_tests_mqtt.c @@ -47,6 +47,7 @@ void RunMqttTests( bool disableNetworkTests, bool disableLongTests ) RUN_TEST_GROUP( MQTT_Unit_Subscription ); RUN_TEST_GROUP( MQTT_Unit_Validate ); RUN_TEST_GROUP( MQTT_Unit_Receive ); + RUN_TEST_GROUP( MQTT_Unit_Platform ); RUN_TEST_GROUP( MQTT_Unit_API ); if( disableNetworkTests == false ) diff --git a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c index a98deb1c10..18e236708d 100644 --- a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c +++ b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c @@ -57,16 +57,22 @@ * Provide default values of test configuration constants. */ #ifndef IOT_TEST_MQTT_TIMEOUT_MS - #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) + #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) #endif #ifndef IOT_TEST_MQTT_CONNECT_RETRY_COUNT #define IOT_TEST_MQTT_CONNECT_RETRY_COUNT ( 1 ) #endif +#ifndef IOT_TEST_MQTT_PUBLISH_RETRY_COUNT + #define IOT_TEST_MQTT_PUBLISH_RETRY_COUNT ( 3 ) +#endif /** @endcond */ #if IOT_TEST_MQTT_CONNECT_RETRY_COUNT < 1 #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 1." #endif +#if IOT_TEST_MQTT_PUBLISH_RETRY_COUNT < 0 + #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 0." +#endif /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). @@ -246,7 +252,7 @@ static IotMqttError_t _mqttConnect( const IotMqttNetworkInfo_t * pNetworkInfo, if( ( status == IOT_MQTT_NETWORK_ERROR ) || ( status == IOT_MQTT_TIMEOUT ) ) { /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. - * Initially wait until 1100 ms have elapsed since the last connection, then + * Initially wait until 1100 ms have elapsed since the last connection, then * increase exponentially. */ IotClock_SleepMs( 1100 << retryCount ); } @@ -444,8 +450,8 @@ static void _reentrantCallback( void * pArgument, publishInfo.topicNameLength = topicLength; publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = 3; - publishInfo.retryMs = 5000; + publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; + publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; mqttStatus = IotMqtt_PublishSync( pOperation->mqttConnection, &publishInfo, @@ -580,6 +586,8 @@ static void _subscribePublishWait( IotMqttQos_t qos ) publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; + publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; /* Publish the message. */ status = IotMqtt_PublishSync( _mqttConnection, @@ -1125,8 +1133,8 @@ TEST( MQTT_System, WaitAfterDisconnect ) publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = 3; - publishInfo.retryMs = 5000; + publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; + publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; /* Establish the MQTT connection. */ status = _mqttConnect( &_networkInfo, @@ -1308,8 +1316,8 @@ TEST( MQTT_System, IncomingPublishReentrancy ) publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = 3; - publishInfo.retryMs = 5000; + publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; + publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; status = IotMqtt_PublishSync( _mqttConnection, &publishInfo, diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 6bd4bc3198..63a61ce6d7 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -128,6 +128,12 @@ 4 * DUP_CHECK_RETRY_MS + \ IOT_MQTT_RESPONSE_WAIT_MS ) +/** + * @brief Length of an arbitrary packet for testing. A buffer will be allocated + * for it, but its contents don't matter. + */ +#define PACKET_LENGTH ( 1 ) + /*-----------------------------------------------------------*/ /** @@ -558,13 +564,13 @@ IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, IotMqttError_t status = IOT_MQTT_SUCCESS; TEST_ASSERT_NULL( _pAllocatedPingreq ); - _pAllocatedPingreq = IotTest_Malloc( 1 ); + _pAllocatedPingreq = IotTest_Malloc( PACKET_LENGTH ); if( _pAllocatedPingreq != NULL ) { *_pAllocatedPingreq = MQTT_PACKET_TYPE_PINGREQ; *pPingreqPacket = _pAllocatedPingreq; - *pPacketSize = 1; + *pPacketSize = PACKET_LENGTH; } else { @@ -816,6 +822,52 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) /* Disconnect the MQTT connection, then call Wait to clean up the operation. */ IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); IotMqtt_Wait( pOperation, 0 ); + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Allocate an operation for an incoming publish. */ + pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + TEST_ASSERT_NOT_NULL( pOperation ); + ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); + + pOperation->incomingPublish = true; + pOperation->pMqttConnection = _pMqttConnection; + pOperation->u.publish.publishInfo.pTopicName = TEST_TOPIC_NAME; + pOperation->u.publish.publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + + pOperation->u.publish.publishInfo.payloadLength = PACKET_LENGTH; + pOperation->u.publish.pReceivedData = IotMqtt_MallocMessage( pOperation->u.publish.publishInfo.payloadLength ); + pOperation->u.publish.publishInfo.pPayload = pOperation->u.publish.pReceivedData; + + /* Increment the MQTT connection's reference count to prevent it from being destroyed + * until the test is over. */ + _pMqttConnection->references += 2; + + /* Set an invalid job status, which will cause cancellation of the job to fail. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _IotMqtt_ProcessIncomingPublish, + NULL, + &( pOperation->jobStorage ), + &( pOperation->job ) ) ); + pOperation->jobStorage.status = IOT_TASKPOOL_STATUS_COMPLETED; + + /* Insert the publish into the list of operations pending processing. Cancellation + * failure will cause it to be removed from the list, but it will not be destroyed. */ + IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), &( pOperation->link ) ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + TEST_ASSERT_EQUAL_INT( false, IotLink_IsLinked( &( pOperation->link ) ) ); + + /* Set a valid job status to test behavior when job cancellation succeeds. This + * should free everything allocated by this test. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _IotMqtt_ProcessIncomingPublish, + NULL, + &( pOperation->jobStorage ), + &( pOperation->job ) ) ); + IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), &( pOperation->link ) ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -1139,29 +1191,37 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) */ TEST( MQTT_Unit_API, DisconnectAlreadyDisconnected ) { - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); - TEST_ASSERT_EQUAL_INT( 1, mqttConnection->references ); + /* Increment the MQTT connection's reference count to prevent it from being destroyed + * until the test is over. */ + _pMqttConnection->references++; - /* Increase reference count to 3 so the subsequent disconnect - * calls do not free the connection. */ - mqttConnection->references += 2; /* Call Disconnect, reference count should decrement. */ - IotMqtt_Disconnect( mqttConnection, 0 ); - TEST_ASSERT_EQUAL_INT( 2, mqttConnection->references ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->references ); /* 'disconnected' flag should be set */ - TEST_ASSERT_EQUAL( true, mqttConnection->disconnected ); + TEST_ASSERT_EQUAL( true, _pMqttConnection->disconnected ); - /* Make sure reference count is decremented when 'disconnected' - * connection is passed without any attempts to close socket again. */ - IotMqtt_Disconnect( mqttConnection, 0 ); - TEST_ASSERT_EQUAL_INT( 1, mqttConnection->references ); + /* Attempt to use a closed connection. */ + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; - /* Clean up test. */ - IotTest_MqttMockCleanup(); + status = IotMqtt_PublishSync( _pMqttConnection, &publishInfo, 0, TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); + + /* Disconnect and clean up test. */ + IotMqtt_Disconnect( _pMqttConnection, 0 ); } + /*-----------------------------------------------------------*/ /** diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c new file mode 100644 index 0000000000..e3540d7860 --- /dev/null +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -0,0 +1,412 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_mqtt_platform.c + * @brief Tests interaction of MQTT with the lower layers, such as network and task pool. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* SDK initialization include. */ +#include "iot_init.h" + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* Allow these tests to manipulate the task pool and create failures by including + * the task pool internal header. */ +#undef LIBRARY_LOG_LEVEL +#undef LIBRARY_LOG_NAME +#include "../src/private/iot_taskpool_internal.h" + +/* MQTT test access include. */ +#include "iot_test_access_mqtt.h" + +/* MQTT mock include. */ +#include "iot_tests_mqtt_mock.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Timeout to use for the tests. This can be short, but should allow time + * for other threads to run. + */ +#define TIMEOUT_MS ( 400 ) + +/* + * Client identifier and length to use for the MQTT API tests. + */ +#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ + +/** + * @brief A non-NULL function pointer to use for subscription callback. This + * "function" should cause a crash if actually called. + */ +#define SUBSCRIPTION_CALLBACK_FUNCTION \ + ( ( void ( * )( void *, \ + IotMqttCallbackParam_t * ) ) 0x1 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief An #IotMqttNetworkInfo_t to share among the tests. + */ +static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + +/** + * @brief An #IotNetworkInterface_t to share among the tests. + */ +static IotNetworkInterface_t _networkInterface = { 0 }; + +/* + * Return values of the mocked network functions. + */ +static IotNetworkError_t _createStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkCreate. */ +static IotNetworkError_t _setReceiveCallbackStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkSetReceiveCallback. */ +static IotNetworkError_t _sendStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkSend. */ +static IotNetworkError_t _closeStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkClose. */ +static IotNetworkError_t _destroyStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkDestroy. */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Mocked network create function. + */ +static IotNetworkError_t _networkCreate( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ) +{ + ( void ) pServerInfo; + ( void ) pCredentialInfo; + + *pConnection = NULL; + + return _createStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Mocked network set receive callback function. + */ +static IotNetworkError_t _networkSetReceiveCallback( IotNetworkConnection_t pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ) +{ + ( void ) pConnection; + ( void ) receiveCallback; + ( void ) pContext; + + return _setReceiveCallbackStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Mocked network send function. + */ +static size_t _networkSend( IotNetworkConnection_t pConnection, + const uint8_t * pMessage, + size_t messageLength ) +{ + size_t bytesSent = 0; + + ( void ) pConnection; + ( void ) pMessage; + + if( _sendStatus == IOT_NETWORK_SUCCESS ) + { + bytesSent = messageLength; + } + + return bytesSent; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Mocked network close function. + */ +static IotNetworkError_t _networkClose( IotNetworkConnection_t pConnection ) +{ + ( void ) pConnection; + + return _closeStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Mocked network destroy function. + */ +static IotNetworkError_t _networkDestroy( IotNetworkConnection_t pConnection ) +{ + ( void ) pConnection; + + return _destroyStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for MQTT platform tests. + */ +TEST_GROUP( MQTT_Unit_Platform ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT platform tests. + */ +TEST_SETUP( MQTT_Unit_Platform ) +{ + /* Reset the network info and interface. */ + ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); + + _createStatus = IOT_NETWORK_SUCCESS; + _setReceiveCallbackStatus = IOT_NETWORK_SUCCESS; + _sendStatus = IOT_NETWORK_SUCCESS; + _closeStatus = IOT_NETWORK_SUCCESS; + _destroyStatus = IOT_NETWORK_SUCCESS; + + _networkInterface.create = _networkCreate; + _networkInterface.setReceiveCallback = _networkSetReceiveCallback; + _networkInterface.send = _networkSend; + _networkInterface.close = _networkClose; + _networkInterface.destroy = _networkDestroy; + + _networkInfo.pNetworkInterface = &_networkInterface; + + /* Initialize libraries. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT platform tests. + */ +TEST_TEAR_DOWN( MQTT_Unit_Platform ) +{ + IotMqtt_Cleanup(); + IotSdk_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT platform tests. + */ +TEST_GROUP_RUNNER( MQTT_Unit_Platform ) +{ + RUN_TEST_CASE( MQTT_Unit_Platform, ConnectNetworkFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, ConnectScheduleFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, DisconnectSendFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, PublishScheduleFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionScheduleFailure ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect when the network fails. + */ +TEST( MQTT_Unit_Platform, ConnectNetworkFailure ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + + /* Set test client identifier. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + /* Network connection creation failure. */ + _networkInfo.createNetworkConnection = true; + _createStatus = IOT_NETWORK_FAILURE; + status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); + + /* Set receive callback failure. */ + _createStatus = IOT_NETWORK_SUCCESS; + _setReceiveCallbackStatus = IOT_NETWORK_FAILURE; + status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); + + /* Failure in set receive callback, close, and destroy. */ + _closeStatus = IOT_NETWORK_FAILURE; + _destroyStatus = IOT_NETWORK_FAILURE; + status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); + + /* Failure to send MQTT Connect. */ + _setReceiveCallbackStatus = IOT_NETWORK_SUCCESS; + _closeStatus = IOT_NETWORK_SUCCESS; + _destroyStatus = IOT_NETWORK_SUCCESS; + _sendStatus = IOT_NETWORK_FAILURE; + status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_connect when the keep-alive + * job fails to schedule. + */ +TEST( MQTT_Unit_Platform, ConnectScheduleFailure ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttConnection_t * pMqttConnection = NULL; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set an invalid status for the keep-alive job, preventing it from being + * scheduled. */ + pMqttConnection->pingreq.jobStorage.status = IOT_TASKPOOL_STATUS_COMPLETED; + status = IotTestMqtt_scheduleKeepAlive( pMqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, status ); + + /* Clean up. */ + pMqttConnection->references--; + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_disconnect when the network fails. + */ +TEST( MQTT_Unit_Platform, DisconnectSendFailure ) +{ + _mqttConnection_t * pMqttConnection = NULL; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Call disconnect with a failing send. */ + _sendStatus = IOT_NETWORK_FAILURE; + IotMqtt_Disconnect( pMqttConnection, 0 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_publishasync when scheduling fails. + */ +TEST( MQTT_Unit_Platform, PublishScheduleFailure ) +{ + IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; + IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; + uint32_t maxThreads = 0; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the task pool to an invalid state and cause all further scheduling to fail. */ + maxThreads = taskPool->maxThreads; + taskPool->maxThreads = 0; + + /* Send a QoS 0 publish that fails to schedule. */ + publishInfo.pTopicName = "test/"; + publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; + + status = IotMqtt_PublishAsync( pMqttConnection, &publishInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, status ); + + /* Send a QoS 1 publish that fails to schedule. */ + publishInfo.qos = IOT_MQTT_QOS_1; + status = IotMqtt_PublishAsync( pMqttConnection, &publishInfo, 0, NULL, &publishOperation ); + TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, status ); + TEST_ASSERT_EQUAL( NULL, publishOperation ); + + /* Restore the task pool to a valid state. */ + taskPool->maxThreads = maxThreads; + + /* Clean up. */ + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref mqtt_function_subscribeasync and + * @ref mqtt_function_unsubscribeasync when scheduling fails. + */ +TEST( MQTT_Unit_Platform, SubscriptionScheduleFailure ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; + IotMqttOperation_t subscriptionOperation = IOT_MQTT_OPERATION_INITIALIZER; + uint32_t maxThreads = 0; + + /* Set subscription parameters. */ + subscription.pTopicFilter = "test/"; + subscription.topicFilterLength = strlen( subscription.pTopicFilter ); + subscription.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the task pool to an invalid state and cause all further scheduling to fail. */ + maxThreads = taskPool->maxThreads; + taskPool->maxThreads = 0; + + /* Send a SUBSCRIBE that fails to schedule. */ + status = IotMqtt_SubscribeAsync( pMqttConnection, &subscription, 1, 0, NULL, &subscriptionOperation ); + TEST_ASSERT_EQUAL( status, IOT_MQTT_SCHEDULING_ERROR ); + + /* Send an UNSUBSCRIBE that fails to schedule. */ + status = IotMqtt_UnsubscribeAsync( pMqttConnection, &subscription, 1, 0, NULL, &subscriptionOperation ); + TEST_ASSERT_EQUAL( status, IOT_MQTT_SCHEDULING_ERROR ); + + /* Restore the task pool to a valid state. */ + taskPool->maxThreads = maxThreads; + + /* Clean up. */ + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ From 7327a2f24a680b585c05e55dc980b22dbc172c72 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 31 Jan 2020 20:41:42 -0800 Subject: [PATCH 408/844] Fix MISRA 10.3 violation (#767) --- libraries/standard/mqtt/src/iot_mqtt_static_memory.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_static_memory.c b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c index ba1ba921d3..400270a63b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_static_memory.c +++ b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c @@ -85,14 +85,14 @@ /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static uint32_t _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ] = { 0U }; /**< @brief MQTT connection in-use flags. */ -static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ +static uint32_t _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ]; /**< @brief MQTT connection in-use flags. */ +static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ]; /**< @brief MQTT connections. */ -static uint32_t _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief MQTT operation in-use flags. */ -static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief MQTT operations. */ +static uint32_t _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ]; /**< @brief MQTT operation in-use flags. */ +static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ]; /**< @brief MQTT operations. */ -static uint32_t _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0U }; /**< @brief MQTT subscription in-use flags. */ -static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ] = { { '\0' } }; /**< @brief MQTT subscriptions. */ +static uint32_t _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ]; /**< @brief MQTT subscription in-use flags. */ +static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ]; /**< @brief MQTT subscriptions. */ /*-----------------------------------------------------------*/ From c82e67059f4dfd9cbf7246a37704abe7240ee78f Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Mon, 3 Feb 2020 10:19:00 -0800 Subject: [PATCH 409/844] Fix: Unused variable warning. (#765) Unused variable warning when AWS_IOT_MQTT_ENABLE_METRICS is set to 0. --- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index e5fb6b2cb4..92c9d62cef 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -522,6 +522,9 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, uint8_t * pBuffer = pDestination; const char * pMetricsUserName = NULL; + /* Avoid unused variable warning when AWS_IOT_MQTT_ENABLE_METRICS is set to 0 */ + ( void ) pMetricsUserName; + /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the * AWS IoT MQTT server. */ From 901c34a676c00c0ae73ff37be9c6f6b80f36da2a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 3 Feb 2020 12:28:29 -0800 Subject: [PATCH 410/844] Increase coverage of iot_mqtt_network.c (#770) --- .../standard/mqtt/src/iot_mqtt_network.c | 8 ++ .../mqtt/test/access/iot_test_access_mqtt.h | 10 ++ .../access/iot_test_access_mqtt_network.c | 43 +++++++++ .../mqtt/test/unit/iot_tests_mqtt_api.c | 3 + .../mqtt/test/unit/iot_tests_mqtt_platform.c | 74 +++++++++++++-- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 94 ++++++++++++++++--- 6 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index ce68868d0c..9c3681b032 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -903,3 +903,11 @@ IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * } /*-----------------------------------------------------------*/ + +/* Provide access to internal functions and variables if testing. */ +/* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ +/* coverity[misra_c_2012_rule_20_9_violation] */ +/* coverity[caretline] */ +#if IOT_BUILD_TESTS == 1 + #include "iot_test_access_mqtt_network.c" +#endif diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h index a32f820b83..d1eca6cd66 100644 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h @@ -47,6 +47,16 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, */ IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); +/*-------------------------- iot_mqtt_network.c -------------------------*/ + +/** + * @brief Test access function for #_sendPuback. + * + * @see #_sendPuback. + */ +void IotTestMqtt_sendPuback( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier ); + /*------------------------- iot_mqtt_serialize.c ------------------------*/ /* diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c new file mode 100644 index 0000000000..cfeda02ee7 --- /dev/null +++ b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c @@ -0,0 +1,43 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_test_access_mqtt_network.c + * @brief Provides access to the internal functions and variables of + * iot_mqtt_network.c + * + * This file should only be included at the bottom of iot_mqtt_network.c + * and never compiled by itself. + */ + +void IotTestMqtt_sendPuback( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier ); + +/*-----------------------------------------------------------*/ + +void IotTestMqtt_sendPuback( _mqttConnection_t * pMqttConnection, + uint16_t packetIdentifier ) +{ + _sendPuback( pMqttConnection, packetIdentifier ); +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 63a61ce6d7..9a0ff50981 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -2339,15 +2339,18 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); /* Test with NULL network interface */ + bufPtr = buffer; status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); /* Test with incorrect packet type. */ + bufPtr = buffer; buffer[ 0 ] = 0x10; /* INVALID */ status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); /* Test with invalid remaining length. */ + bufPtr = buffer; buffer[ 0 ] = 0x20; /* CONN ACK */ /* To generate invalid remaining length response, diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index e3540d7860..63b31e4a9b 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -176,6 +176,22 @@ static IotNetworkError_t _networkDestroy( IotNetworkConnection_t pConnection ) /*-----------------------------------------------------------*/ +/** + * @brief Serializer override for PUBACK that always fails. + */ +static IotMqttError_t _serializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) +{ + ( void ) packetIdentifier; + ( void ) pPubackPacket; + ( void ) pPacketSize; + + return IOT_MQTT_NO_MEMORY; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT platform tests. */ @@ -231,8 +247,9 @@ TEST_GROUP_RUNNER( MQTT_Unit_Platform ) { RUN_TEST_CASE( MQTT_Unit_Platform, ConnectNetworkFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, ConnectScheduleFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, DisconnectSendFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, DisconnectNetworkFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, PublishScheduleFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, PubackScheduleSerializeFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionScheduleFailure ); } @@ -309,17 +326,22 @@ TEST( MQTT_Unit_Platform, ConnectScheduleFailure ) /** * @brief Tests the behavior of @ref mqtt_function_disconnect when the network fails. */ -TEST( MQTT_Unit_Platform, DisconnectSendFailure ) +TEST( MQTT_Unit_Platform, DisconnectNetworkFailure ) { _mqttConnection_t * pMqttConnection = NULL; - /* Create a new MQTT connection. */ + /* Call disconnect with a failing send. */ pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Call disconnect with a failing send. */ _sendStatus = IOT_NETWORK_FAILURE; IotMqtt_Disconnect( pMqttConnection, 0 ); + _sendStatus = IOT_NETWORK_SUCCESS; + + /* Call disconnect with a failing close. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + _closeStatus = IOT_NETWORK_FAILURE; + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); } /*-----------------------------------------------------------*/ @@ -346,7 +368,7 @@ TEST( MQTT_Unit_Platform, PublishScheduleFailure ) /* Send a QoS 0 publish that fails to schedule. */ publishInfo.pTopicName = "test/"; - publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = ""; publishInfo.payloadLength = 0; @@ -368,6 +390,44 @@ TEST( MQTT_Unit_Platform, PublishScheduleFailure ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of the client-to-server PUBACK when scheduling and + * serializing fail. + */ +TEST( MQTT_Unit_Platform, PubackScheduleSerializeFailure ) +{ + IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; + IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; + uint32_t maxThreads = 0; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Set the task pool to an invalid state and cause all further scheduling to fail. */ + maxThreads = taskPool->maxThreads; + taskPool->maxThreads = 0; + + /* Call the function to send a PUBACK with scheduling failure. The failed PUBACK + * should be cleaned up and not create memory leaks. */ + IotTestMqtt_sendPuback( pMqttConnection, 1 ); + + /* Restore the task pool to a valid state. */ + taskPool->maxThreads = maxThreads; + + /* Call the function to send PUBACK with serializer failure. The failed PUBACK + * should be cleaned up and not create memory leaks. */ + serializer.serialize.puback = _serializePuback; + pMqttConnection->pSerializer = &serializer; + IotTestMqtt_sendPuback( pMqttConnection, 1 ); + + /* Clean up. */ + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref mqtt_function_subscribeasync and * @ref mqtt_function_unsubscribeasync when scheduling fails. @@ -383,7 +443,7 @@ TEST( MQTT_Unit_Platform, SubscriptionScheduleFailure ) /* Set subscription parameters. */ subscription.pTopicFilter = "test/"; - subscription.topicFilterLength = strlen( subscription.pTopicFilter ); + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; /* Create a new MQTT connection. */ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index ca046924de..97f9a4e22e 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -115,17 +115,17 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Initializer for operations in the tests. */ -#define INITIALIZE_OPERATION( name ) \ - { \ - .link = { 0 }, .incomingPublish = false, .pMqttConnection = NULL, \ - .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ - .u.operation = \ - { \ - .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ - .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ - .notify = { .callback = { 0 } }, .status = IOT_MQTT_STATUS_PENDING, \ - .periodic = { .retry = { 0 } } \ - } \ +#define INITIALIZE_OPERATION( name ) \ + { \ + .link = { 0 }, .incomingPublish = false, .pMqttConnection = NULL, \ + .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ + .u.operation = \ + { \ + .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ + .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ + .notify = { .callback = { 0 } },.status = IOT_MQTT_STATUS_PENDING, \ + .periodic = { .retry = { 0 } } \ + } \ } /*-----------------------------------------------------------*/ @@ -539,6 +539,45 @@ static void _disconnectCallback( void * pCallbackContext, /*-----------------------------------------------------------*/ +/** + * @brief Common code for PUBLISH malloc failure tests. + */ +static void _publishMallocFail( IotMqttQos_t qos ) +{ + int32_t i = 0; + bool status = false; + + for( i = 0; ; i++ ) + { + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + + if( qos == IOT_MQTT_QOS_1 ) + { + pPublish[ 0 ] = 0x32; + } + + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Attempt to process a PUBLISH. Memory allocation will fail at various + * times during this call. */ + status = _processPublish( pPublish, publishSize, 1 ); + + /* Exit once the publish is successfully processed. */ + if( status == true ) + { + break; + } + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); + } + + UnityMalloc_MakeMallocFailAfterCount( -1 ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT Receive tests. */ @@ -637,6 +676,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_Receive ) RUN_TEST_CASE( MQTT_Unit_Receive, ConnackInvalid ); RUN_TEST_CASE( MQTT_Unit_Receive, PublishValid ); RUN_TEST_CASE( MQTT_Unit_Receive, PublishInvalid ); + RUN_TEST_CASE( MQTT_Unit_Receive, PublishResourceFailure ); RUN_TEST_CASE( MQTT_Unit_Receive, PubackValid ); RUN_TEST_CASE( MQTT_Unit_Receive, PubackInvalid ); RUN_TEST_CASE( MQTT_Unit_Receive, SubackValid ); @@ -815,7 +855,8 @@ TEST( MQTT_Unit_Receive, ReceiveMallocFail ) /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); - #else + #else /* if ( LIBRARY_LOG_LEVEL == IOT_LOG_NONE ) */ + /* Test tear down for this test group checks that deserializer overrides * were called. Set these values to true so that the checks pass. */ _deserializeOverrideCalled = true; @@ -1034,8 +1075,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) 1 ) ); } - /* Process a valid QoS 1 PUBLISH. Prevent an attempt to send PUBACK by - * making no memory available for the PUBACK. */ + /* Process a valid QoS 1 PUBLISH. */ { DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 0 ] = 0x32; @@ -1216,6 +1256,32 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback with errors + * such as memory allocation failure and closed connections. + */ +TEST( MQTT_Unit_Receive, PublishResourceFailure ) +{ + /* Test the behavior when memory allocation fails for QoS 0 and 1 PUBLISH. */ + _publishMallocFail( IOT_MQTT_QOS_0 ); + _publishMallocFail( IOT_MQTT_QOS_1 ); + + /* Test the behavior when a closed connection is used. */ + { + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + + /* Mark the connection as closed, then attempt to process a PUBLISH with a closed connection. */ + _pMqttConnection->disconnected = true; + TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, 0 ) ); + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref mqtt_function_receivecallback with a * spec-compliant PUBACK. From c68facc737e162dc2f40f455f19f9f44867be604 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 3 Feb 2020 16:50:24 -0800 Subject: [PATCH 411/844] Increase test coverage of iot_mqtt_operation.c (#771) --- .../standard/mqtt/src/iot_mqtt_operation.c | 40 ++-- .../mqtt/test/unit/iot_tests_mqtt_api.c | 184 +++++++++++++++++- .../mqtt/test/unit/iot_tests_mqtt_platform.c | 116 ++++++++++- 3 files changed, 316 insertions(+), 24 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 8bf96297b3..e9a8ab3551 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -1045,11 +1045,12 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, * since a network response could modify the status. */ if( networkPending == false ) { + /* Operations that are not waiting for a network response either failed or + * completed successfully. Check that a status was set. */ + IotMqtt_Assert( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ); + /* Notify of operation completion if this job set a status. */ - if( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ) - { - _IotMqtt_Notify( pOperation ); - } + _IotMqtt_Notify( pOperation ); } } } @@ -1060,6 +1061,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, IotTaskPoolJob_t pOperationJob, void * pContext ) { + bool destroyOperation = false; _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; IotMqttCallbackParam_t callbackParam = { 0 }; @@ -1067,6 +1069,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, * are disabled. */ ( void ) pTaskPool; ( void ) pOperationJob; + ( void ) destroyOperation; IotMqtt_Assert( pOperationJob == pOperation->job ); /* The operation's callback function and status must be set. */ @@ -1082,11 +1085,11 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, pOperation->u.operation.notify.callback.function( pOperation->u.operation.notify.callback.pCallbackContext, &callbackParam ); - /* Attempt to destroy the operation once the user callback returns. */ - if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) - { - _IotMqtt_DestroyOperation( pOperation ); - } + /* Decrement the operation reference count. This function is at the end of the + * operation lifecycle, so the operation must be destroyed here. */ + destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); + IotMqtt_Assert( destroyOperation == true ); + _IotMqtt_DestroyOperation( pOperation ); } /*-----------------------------------------------------------*/ @@ -1289,17 +1292,18 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { + /* Only waitable operations will have a reference count greater than 1. + * Non-waitable operations will not reach this block. */ + IotMqtt_Assert( waitable == true ); + /* Post to a waitable operation's semaphore. */ - if( waitable == true ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " - "notified of completion.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); + IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " + "notified of completion.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); - IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); - } + IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); } } else diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 9a0ff50981..106ea0aef7 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -132,7 +132,12 @@ * @brief Length of an arbitrary packet for testing. A buffer will be allocated * for it, but its contents don't matter. */ -#define PACKET_LENGTH ( 1 ) +#define PACKET_LENGTH ( 32 ) + +/** + * @brief How many operations to use for the OperationFindMatch test. + */ +#define OPERATION_COUNT (2) /*-----------------------------------------------------------*/ @@ -558,8 +563,8 @@ static IotMqttError_t _getNextByte( IotNetworkConnection_t pNetworkInterface, /** * @brief A PINGREQ serializer that attempts to allocate memory (unlike the default). */ -IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ) +static IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ) { IotMqttError_t status = IOT_MQTT_SUCCESS; @@ -582,6 +587,18 @@ IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, /*-----------------------------------------------------------*/ +/** + * @brief A completion callback that does nothing. + */ +static void _completionCallback( void * pContext, + IotMqttCallbackParam_t * pCallbackParam ) +{ + ( void ) pContext; + ( void ) pCallbackParam; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT API tests. */ @@ -635,6 +652,8 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, StringCoverage ); RUN_TEST_CASE( MQTT_Unit_API, OperationCreateDestroy ); RUN_TEST_CASE( MQTT_Unit_API, OperationWaitTimeout ); + RUN_TEST_CASE( MQTT_Unit_API, OperationFindMatch ); + RUN_TEST_CASE( MQTT_Unit_API, OperationLists ); RUN_TEST_CASE( MQTT_Unit_API, ConnectParameters ); RUN_TEST_CASE( MQTT_Unit_API, ConnectMallocFail ); RUN_TEST_CASE( MQTT_Unit_API, ConnectRestoreSessionMallocFail ); @@ -643,6 +662,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0Parameters ); RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0MallocFail ); RUN_TEST_CASE( MQTT_Unit_API, PublishQoS1 ); + RUN_TEST_CASE( MQTT_Unit_API, PublishRetryPeriod ); RUN_TEST_CASE( MQTT_Unit_API, PublishDuplicates ); RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); @@ -946,6 +966,107 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) /*-----------------------------------------------------------*/ +/** + * @brief Test edge cases when searching for operations. + */ +TEST( MQTT_Unit_API, OperationFindMatch ) +{ + int32_t i = 0; + uint16_t packetIdentifier = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pMatchedOperation = NULL; + _mqttOperation_t * pOperation[ OPERATION_COUNT ] = { NULL, NULL }; + + /* Create a new MQTT connection. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Set up operations. */ + for( i = 0; i < OPERATION_COUNT; i++ ) + { + status = _IotMqtt_CreateOperation( _pMqttConnection, 0, NULL, &( pOperation[ i ] ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _IotMqtt_ProcessCompletedOperation, + pOperation[ i ], + &( pOperation[ i ]->jobStorage ), + &( pOperation[ i ]->job ) ) ); + + IotListDouble_Remove( &( pOperation[ i ]->link ) ); + IotListDouble_InsertHead( &( _pMqttConnection->pendingResponse ), &( pOperation[ i ]->link ) ); + + pOperation[ i ]->u.operation.packetIdentifier = ( uint16_t ) ( i + 1 ); + pOperation[ i ]->u.operation.periodic.retry.nextPeriodMs = DUP_CHECK_RETRY_MS; + pOperation[ i ]->u.operation.periodic.retry.limit = DUP_CHECK_RETRY_LIMIT; + } + + pOperation[ 0 ]->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; + pOperation[ 1 ]->u.operation.type = IOT_MQTT_SUBSCRIBE; + + /* Set one operation's job to an invalid state, then try to find it. The invalid state + * will cause that job to be ignored. */ + packetIdentifier = 1; + pOperation[ 0 ]->jobStorage.status = IOT_TASKPOOL_STATUS_COMPLETED; + pMatchedOperation = _IotMqtt_FindOperation( _pMqttConnection, + IOT_MQTT_PUBLISH_TO_SERVER, + &packetIdentifier ); + TEST_ASSERT_NULL( pMatchedOperation ); + + /* Clean up operations. */ + for( i = 0; i < OPERATION_COUNT; i++ ) + { + TEST_ASSERT_EQUAL_INT( true, _IotMqtt_DecrementOperationReferences( pOperation[ i ], false ) ); + _IotMqtt_DestroyOperation( pOperation[ i ] ); + } + + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of send and notify with different link statuses. + */ +TEST( MQTT_Unit_API, OperationLists ) +{ + _mqttOperation_t * pOperation = NULL; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Create a new MQTT connection. */ + _networkInterface.send = _sendSuccess; + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Create a new MQTT operation. */ + callbackInfo.function = _completionCallback; + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, + 0, + &callbackInfo, + &pOperation ) ); + TEST_ASSERT_NOT_NULL( pOperation ); + pOperation->u.operation.pMqttPacket = IotMqtt_MallocMessage( PACKET_LENGTH ); + pOperation->u.operation.packetSize = PACKET_LENGTH; + + /* Process a send with operation unlinked. Check that operation gets linked afterwards. */ + IotListDouble_Remove( &( pOperation->link ) ); + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + TEST_ASSERT_EQUAL_INT( true, IotLink_IsLinked( &( pOperation->link ) ) ); + + /* Notify with the operation linked. */ + pOperation->u.operation.status = IOT_MQTT_SUCCESS; + _IotMqtt_Notify( pOperation ); + + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref mqtt_function_connect with various * invalid parameters. @@ -1396,6 +1517,63 @@ TEST( MQTT_Unit_API, PublishQoS1 ) /*-----------------------------------------------------------*/ +/** + * @brief Tests that PUBLISH retry periods are calculated correctly. + */ +TEST( MQTT_Unit_API, PublishRetryPeriod ) +{ + _mqttOperation_t * pOperation = NULL; + uint32_t periodMs = IOT_MQTT_RETRY_MS_CEILING / 2; + + /* Create a new MQTT connection. */ + _networkInterface.send = _sendSuccess; + _pMqttConnection = IotTestMqtt_createMqttConnection( false, + &_networkInfo, + 0 ); + TEST_ASSERT_NOT_NULL( _pMqttConnection ); + + /* Create a PUBLISH with retry operation. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ) ); + TEST_ASSERT_NOT_NULL( pOperation ); + pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; + pOperation->u.operation.pMqttPacket = IotMqtt_MallocMessage( PACKET_LENGTH ); + pOperation->u.operation.packetSize = PACKET_LENGTH; + pOperation->u.operation.periodic.retry.limit = DUP_CHECK_RETRY_LIMIT; + pOperation->u.operation.periodic.retry.nextPeriodMs = periodMs; + IotListDouble_Remove( &( pOperation->link ) ); + + /* Simulate send of PUBLISH. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + + /* Immediately cancel retried PUBLISH, then check statuses set by send. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + pOperation->job, + NULL ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, pOperation->u.operation.status ); + TEST_ASSERT_EQUAL( 1, pOperation->u.operation.periodic.retry.count ); + TEST_ASSERT_EQUAL( 2 * periodMs, pOperation->u.operation.periodic.retry.nextPeriodMs ); + + /* Simulate another send. Check that the retry ceiling is respected. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + + /* Immediately cancel retried PUBLISH, then check statuses set by send. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + pOperation->job, + NULL ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, pOperation->u.operation.status ); + TEST_ASSERT_EQUAL( 2, pOperation->u.operation.periodic.retry.count ); + TEST_ASSERT_EQUAL( IOT_MQTT_RETRY_MS_CEILING, pOperation->u.operation.periodic.retry.nextPeriodMs ); + + /* Clean up. */ + TEST_ASSERT_EQUAL_INT( false, _IotMqtt_DecrementOperationReferences( pOperation, false ) ); + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests that duplicate QoS 1 PUBLISH packets are different from the * original. diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index 63b31e4a9b..b656d7c650 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -31,6 +31,9 @@ /* Standard includes. */ #include +/* Platform threads include. */ +#include "platform/iot_threads.h" + /* SDK initialization include. */ #include "iot_init.h" @@ -74,6 +77,12 @@ ( ( void ( * )( void *, \ IotMqttCallbackParam_t * ) ) 0x1 ) +/** + * @brief Length of an arbitrary packet for testing. A buffer will be allocated + * for it, but its contents don't matter. + */ +#define PACKET_LENGTH ( 1 ) + /*-----------------------------------------------------------*/ /** @@ -180,8 +189,8 @@ static IotNetworkError_t _networkDestroy( IotNetworkConnection_t pConnection ) * @brief Serializer override for PUBACK that always fails. */ static IotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) + uint8_t ** pPubackPacket, + size_t * pPacketSize ) { ( void ) packetIdentifier; ( void ) pPubackPacket; @@ -248,9 +257,12 @@ TEST_GROUP_RUNNER( MQTT_Unit_Platform ) RUN_TEST_CASE( MQTT_Unit_Platform, ConnectNetworkFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, ConnectScheduleFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, DisconnectNetworkFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, PingreqSendFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, PublishScheduleFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, PublishRetryScheduleFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, PubackScheduleSerializeFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionScheduleFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, NotifyScheduleFailure ); } /*-----------------------------------------------------------*/ @@ -346,6 +358,22 @@ TEST( MQTT_Unit_Platform, DisconnectNetworkFailure ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior when a PINGREQ cannot be sent. + */ +TEST( MQTT_Unit_Platform, PingreqSendFailure ) +{ + _mqttConnection_t * pMqttConnection = NULL; + + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + _sendStatus = IOT_NETWORK_FAILURE; + _IotMqtt_ProcessKeepAlive( IOT_SYSTEM_TASKPOOL, pMqttConnection->pingreq.job, pMqttConnection ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref mqtt_function_publishasync when scheduling fails. */ @@ -390,9 +418,57 @@ TEST( MQTT_Unit_Platform, PublishScheduleFailure ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior when a client-to-server PUBLISH retry fails to + * schedule. + */ +TEST( MQTT_Unit_Platform, PublishRetryScheduleFailure ) +{ + IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + _mqttOperation_t * pOperation = NULL; + IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; + uint32_t maxThreads = 0; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Create a new PUBLISH with retry operation. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ) ); + TEST_ASSERT_NOT_NULL( pOperation ); + pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; + pOperation->u.operation.periodic.retry.limit = 3; + pOperation->u.operation.periodic.retry.nextPeriodMs = TIMEOUT_MS; + pOperation->u.operation.pMqttPacket = IotMqtt_MallocMessage( PACKET_LENGTH ); + pOperation->u.operation.packetSize = PACKET_LENGTH; + + /* Set the task pool to an invalid state and cause all further scheduling to fail. */ + maxThreads = taskPool->maxThreads; + taskPool->maxThreads = 0; + + /* Send the MQTT PUBLISH. Retry will fail to schedule. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, pOperation->u.operation.status ); + TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &( pOperation->u.operation.notify.waitSemaphore ) ) ); + TEST_ASSERT_EQUAL_UINT32( 1, pOperation->u.operation.periodic.retry.count ); + + /* Restore the task pool to a valid state. */ + taskPool->maxThreads = maxThreads; + + /* Clean up. */ + TEST_ASSERT_EQUAL_INT( true, _IotMqtt_DecrementOperationReferences( pOperation, false ) ); + _IotMqtt_DestroyOperation( pOperation ); + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of the client-to-server PUBACK when scheduling and - * serializing fail. + * serializing fail. */ TEST( MQTT_Unit_Platform, PubackScheduleSerializeFailure ) { @@ -470,3 +546,37 @@ TEST( MQTT_Unit_Platform, SubscriptionScheduleFailure ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of #_IotMqtt_Notify when scheduling fails. + */ +TEST( MQTT_Unit_Platform, NotifyScheduleFailure ) +{ + IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + _mqttOperation_t * pOperation = NULL; + IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; + uint32_t maxThreads = 0; + + /* Create a new MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); + TEST_ASSERT_NOT_NULL( pMqttConnection ); + + /* Create a new MQTT operation. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, 0, NULL, &pOperation ) ); + TEST_ASSERT_NOT_NULL( pOperation ); + pOperation->u.operation.notify.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; + + /* Set the task pool to an invalid state and cause all further scheduling to fail. */ + maxThreads = taskPool->maxThreads; + taskPool->maxThreads = 0; + + _IotMqtt_Notify( pOperation ); + + /* Restore the task pool to a valid state. */ + taskPool->maxThreads = maxThreads; + + /* Clean up. */ + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); +} + +/*-----------------------------------------------------------*/ From f0f4d666d9ed0a680ce9f91e7a70fa27de7280b7 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 4 Feb 2020 13:31:05 -0800 Subject: [PATCH 412/844] Address MISRA 21.15, reduce complexity (#761) * Reduce complexity for _encodeUserName * Address MISRA 21.15 * Add warnings to _encodeUserName* functions --- libraries/standard/mqtt/lexicon.txt | 1 + .../standard/mqtt/src/iot_mqtt_serialize.c | 119 ++++++++++++------ 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index e5e06b0079..12af91be60 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -88,6 +88,7 @@ keepaliveseconds lu lwt malloc +memcpy misra mqtt mqttconnection diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 92c9d62cef..3d07bc72e1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -231,10 +231,34 @@ static uint8_t * _encodeString( uint8_t * pDestination, * * @return Pointer to the end of the encoded string, which will be identical to * `pDestination` if nothing was encoded. + * + * @warning This function does not check the size of `pDestination`! To avoid a buffer + * overflow, ensure that `pDestination` is large enough to hold `pConnectInfo->userNameLength` + * bytes if a username is supplied, and/or #AWS_IOT_METRICS_USERNAME_LENGTH bytes if + * metrics are enabled. */ static uint8_t * _encodeUserName( uint8_t * pDestination, const IotMqttConnectInfo_t * pConnectInfo ); +/** + * @brief Encode both connection and metrics username into a buffer, + * if they will fit. + * + * @param[in] pDestination Buffer to write username into. + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[out] pEncodedUserName Whether the username was written into the buffer. + * + * @return Pointer to the end of encoded string, which will be identical to + * `pDestination` if nothing was encoded. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold `pConnectInfo->userNameLength` + + * #AWS_IOT_METRICS_USERNAME_LENGTH bytes to avoid a buffer overflow. + */ +static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo, + bool * pEncodedUserName ); + /** * @brief Calculate the size and "Remaining length" of a CONNECT packet generated * from the given parameters. @@ -504,7 +528,10 @@ static uint8_t * _encodeString( uint8_t * pDestination, *pBuffer = UINT16_LOW_BYTE( sourceLength ); pBuffer++; - /* Copy the string into pBuffer. */ + /* Copy the string into pBuffer. + * As the types of char and uint8_t are of the same size, this memcpy + * is acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ ( void ) memcpy( pBuffer, source, sourceLength ); /* Return the pointer to the end of the encoded string. */ @@ -520,10 +547,6 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, { bool encodedUserName = false; uint8_t * pBuffer = pDestination; - const char * pMetricsUserName = NULL; - - /* Avoid unused variable warning when AWS_IOT_MQTT_ENABLE_METRICS is set to 0 */ - ( void ) pMetricsUserName; /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the @@ -534,46 +557,19 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); - pMetricsUserName = AWS_IOT_METRICS_USERNAME; /* Determine if the Connect packet should use a combination of the username * for authentication plus the SDK version string. */ if( pConnectInfo->pUserName != NULL ) { - /* Only include metrics if it will fit within the encoding - * standard. */ - if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) - { - /* Write the high byte of the combined length. */ - *pBuffer = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer++; - - /* Write the low byte of the combined length. */ - *pBuffer = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer++; - - /* Write the identity portion of the username. */ - ( void ) memcpy( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - pBuffer += pConnectInfo->userNameLength; - - /* Write the metrics portion of the username. */ - ( void ) memcpy( pBuffer, - pMetricsUserName, - AWS_IOT_METRICS_USERNAME_LENGTH ); - pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; - - encodedUserName = true; - } + /* Encode username and metrics if they will fit. */ + pBuffer = _encodeUserNameAndMetrics( pBuffer, pConnectInfo, &encodedUserName ); } else { /* The username is not being used for authentication, but * metrics are enabled. */ pBuffer = _encodeString( pBuffer, - pMetricsUserName, + AWS_IOT_METRICS_USERNAME, AWS_IOT_METRICS_USERNAME_LENGTH ); encodedUserName = true; @@ -594,6 +590,56 @@ static uint8_t * _encodeUserName( uint8_t * pDestination, /*-----------------------------------------------------------*/ +static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo, + bool * pEncodedUserName ) +{ + uint8_t * pBuffer = pDestination; + + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + const char * pMetricsUserName = AWS_IOT_METRICS_USERNAME; + + /* Only include metrics if it will fit within the encoding + * standard. */ + if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) + { + /* Write the high byte of the combined length. */ + pBuffer[ 0 ] = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + + /* Write the low byte of the combined length. */ + pBuffer[ 1 ] = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer += 2; + + /* Write the identity portion of the username. + * As the types of char and uint8_t are of the same size, this memcpy + * is acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBuffer, pConnectInfo->pUserName, pConnectInfo->userNameLength ); + pBuffer += pConnectInfo->userNameLength; + + /* Write the metrics portion of the username. + * As the types of char and uint8_t are of the same size, this memcpy + * is acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBuffer, pMetricsUserName, AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; + + *pEncodedUserName = true; + } + #else + /* Avoid unused variable warnings when AWS_IOT_MQTT_ENABLE_METRICS is set to 0. */ + ( void ) pBuffer; + ( void ) pConnectInfo; + ( void ) pEncodedUserName; + #endif + + return pBuffer; +} + +/*-----------------------------------------------------------*/ + static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, size_t * pRemainingLength, size_t * pPacketSize ) @@ -976,6 +1022,9 @@ static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, /* The payload is placed after the packet identifier. */ if( pPublishInfo->payloadLength > 0U ) { + /* This memcpy intentionally copies bytes from a void * buffer into + * a uint8_t * buffer. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); pBuffer += pPublishInfo->payloadLength; } From 173cf48a8c9687d8dd0b798e0a85c6a86015d89c Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 4 Feb 2020 15:21:47 -0800 Subject: [PATCH 413/844] Feat: Refactor MQTT Light Weight API code (#772) * Feat: Refactor MQTT Light Weight API code Light Weight API code has been factored out and put in separate files. --- libraries/standard/mqtt/CMakeLists.txt | 4 +- ...qtt_serialize.h => iot_mqtt_lightweight.h} | 6 +- .../standard/mqtt/include/iot_mqtt_protocol.h | 140 ++ .../mqtt/include/types/iot_mqtt_types.h | 1 - libraries/standard/mqtt/src/iot_mqtt_helper.c | 850 ++++++++++++ .../mqtt/src/iot_mqtt_lightweight_api.c | 1147 +++++++++++++++++ .../standard/mqtt/src/iot_mqtt_network.c | 39 - .../standard/mqtt/src/iot_mqtt_serialize.c | 523 -------- .../mqtt/src/private/iot_mqtt_helper.h | 283 ++++ .../mqtt/src/private/iot_mqtt_internal.h | 20 - .../mqtt/test/unit/iot_tests_mqtt_api.c | 7 +- 11 files changed, 2430 insertions(+), 590 deletions(-) rename libraries/standard/mqtt/include/{iot_mqtt_serialize.h => iot_mqtt_lightweight.h} (99%) create mode 100644 libraries/standard/mqtt/include/iot_mqtt_protocol.h create mode 100644 libraries/standard/mqtt/src/iot_mqtt_helper.c create mode 100644 libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c create mode 100644 libraries/standard/mqtt/src/private/iot_mqtt_helper.h diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index cf655d9795..256812b206 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -4,6 +4,8 @@ set( MQTT_SOURCES src/iot_mqtt_network.c src/iot_mqtt_operation.c src/iot_mqtt_serialize.c + src/iot_mqtt_lightweight_api.c + src/iot_mqtt_helper.c src/iot_mqtt_static_memory.c src/iot_mqtt_subscription.c src/iot_mqtt_validate.c ) @@ -13,7 +15,7 @@ add_library( iotmqtt ${CONFIG_HEADER_PATH}/iot_config.h ${MQTT_SOURCES} include/iot_mqtt.h - include/iot_mqtt_serialize.h + include/iot_mqtt_lightweight.h include/types/iot_mqtt_types.h src/private/iot_mqtt_internal.h ) diff --git a/libraries/standard/mqtt/include/iot_mqtt_serialize.h b/libraries/standard/mqtt/include/iot_mqtt_lightweight.h similarity index 99% rename from libraries/standard/mqtt/include/iot_mqtt_serialize.h rename to libraries/standard/mqtt/include/iot_mqtt_lightweight.h index 885c576ac0..c82948090d 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_serialize.h +++ b/libraries/standard/mqtt/include/iot_mqtt_lightweight.h @@ -21,14 +21,14 @@ */ /** - * @file iot_mqtt_serialize.h + * @file iot_mqtt_lightweight.h * @brief User-facing functions for serializing MQTT 3.1.1 packets. This header should * be included for building a single threaded light-weight MQTT client bypassing * stateful CSDK MQTT library. */ -#ifndef _IOT_MQTT_SERIALIZE_H_ -#define _IOT_MQTT_SERIALIZE_H_ +#ifndef _IOT_MQTT_LIGHTWEIGHT_H_ +#define _IOT_MQTT_LIGHTWEIGHT_H_ /* The config header is always included first. */ #include "iot_config.h" diff --git a/libraries/standard/mqtt/include/iot_mqtt_protocol.h b/libraries/standard/mqtt/include/iot_mqtt_protocol.h new file mode 100644 index 0000000000..ed3c7ab7b0 --- /dev/null +++ b/libraries/standard/mqtt/include/iot_mqtt_protocol.h @@ -0,0 +1,140 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_protocol.h + * @brief This file contains MQTT 3.1.1 specific defines. This is a common header + * to be included for building a single threaded light-weight MQTT client as well + * as stateful CSDK MQTT library. + */ + +#ifndef IOT_MQTT_PROTOCOL_H_ +#define IOT_MQTT_PROTOCOL_H_ + +/* + * MQTT control packet type and flags. Always the first byte of an MQTT + * packet. + * + * For details, see + * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 + */ +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ +#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ + +/* + * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT + * packet. + */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ + +/* + * Positions of each flag in the first byte of an MQTT PUBLISH packet's + * fixed header. + */ +#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ +#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ +#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ + + +/** + * @brief A value that represents an invalid remaining length. + * + * This value is greater than what is allowed by the MQTT specification. + */ +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + +/** + * @brief The maximum possible size of a CONNECT packet. + * + * All strings in a CONNECT packet are constrained to 2-byte lengths, giving a + * maximum length smaller than the max "Remaining Length" constant above. + */ +#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) + +/** + * @brief The minimum remaining length for a QoS PUBLISH. + * + * Includes two bytes for topic name length and one byte for topic name. + */ +#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) + +/** + * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT + * packet is this value. + */ +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) + +/** + * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. + */ +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) + +/* + * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ + +/* + * Constants relating to PUBLISH and PUBACK packets, defined by MQTT + * 3.1.1 spec. + */ +#define MQTT_PACKET_PUBACK_SIZE ( 4U ) /**< @brief A PUBACK packet is always 4 bytes in size. */ +#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ + +/* + * Constants relating to SUBACK and UNSUBACK packets, defined by MQTT + * 3.1.1 spec. + */ +#define MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5U ) /**< @brief The size of the smallest valid SUBACK packet. */ +#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ + +/* + * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ + +/* + * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_DISCONNECT_SIZE ( 2U ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ + + + +#endif /* ifndef _IOT_MQTT_PROTOCOL_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 2d01119032..92a71ff374 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -38,7 +38,6 @@ /* Type includes. */ #include "types/iot_platform_types.h" -#include "types/iot_taskpool_types.h" /* Platform network include. */ #include "platform/iot_network.h" diff --git a/libraries/standard/mqtt/src/iot_mqtt_helper.c b/libraries/standard/mqtt/src/iot_mqtt_helper.c new file mode 100644 index 0000000000..651eec9463 --- /dev/null +++ b/libraries/standard/mqtt/src/iot_mqtt_helper.c @@ -0,0 +1,850 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_helper.c + * @brief Implements helper functions for the MQTT library. + */ + +/* Standard includes. */ +#include +#include +/* Default assert and memory allocation functions. */ +#include +#include + +/* For use of atomics library */ +#include "iot_atomic.h" + +/* MQTT helper header. */ +#include "private/iot_mqtt_helper.h" + +/*-----------------------------------------------------------*/ + +/* Configure logs for MQTT functions. */ +#ifdef IOT_LOG_LEVEL_MQTT + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "MQTT" ) +#include "iot_logging_setup.h" + +/*-----------------------------------------------------------*/ + +/** + * @def IotMqtt_Assert( expression ) + * @brief Assertion macro for the MQTT library. + * + * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_MQTT_ENABLE_ASSERTS == 1 + #ifndef IotMqtt_Assert + #ifdef Iot_DefaultAssert + #define IotMqtt_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for MQTT, but IotMqtt_Assert is not defined" + #endif + #endif +#else + #define IotMqtt_Assert( expression ) +#endif + + +/*-----------------------------------------------------------*/ + +uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo ) +{ + bool encodedUserName = false; + uint8_t * pBuffer = pDestination; + const char * pMetricsUserName = NULL; + + /* Avoid unused variable warning when AWS_IOT_MQTT_ENABLE_METRICS is set to 0 */ + ( void ) pMetricsUserName; + + /* If metrics are enabled, write the metrics username into the CONNECT packet. + * Otherwise, write the username and password only when not connecting to the + * AWS IoT MQTT server. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " + "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); + + pMetricsUserName = AWS_IOT_METRICS_USERNAME; + + /* Determine if the Connect packet should use a combination of the username + * for authentication plus the SDK version string. */ + if( pConnectInfo->pUserName != NULL ) + { + /* Only include metrics if it will fit within the encoding + * standard. */ + if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) + { + /* Write the high byte of the combined length. */ + *pBuffer = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer++; + + /* Write the low byte of the combined length. */ + *pBuffer = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer++; + + /* Write the identity portion of the username. */ + ( void ) memcpy( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + pBuffer += pConnectInfo->userNameLength; + + /* Write the metrics portion of the username. */ + ( void ) memcpy( pBuffer, + pMetricsUserName, + AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; + + encodedUserName = true; + } + } + else + { + /* The username is not being used for authentication, but + * metrics are enabled. */ + pBuffer = _encodeString( pBuffer, + pMetricsUserName, + AWS_IOT_METRICS_USERNAME_LENGTH ); + + encodedUserName = true; + } + #endif /* #if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ + } + + /* Encode the username if there is one and it hasn't already been done. */ + if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) + { + pBuffer = _IotMqtt_EncodeString( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + } + + return pBuffer; +} +/*-----------------------------------------------------------*/ + +uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) +{ + uint8_t * pBuffer = pDestination; + + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; + + /* Copy the string into pBuffer. */ + ( void ) memcpy( pBuffer, source, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pBuffer += sourceLength; + + return pBuffer; +} + +/*-----------------------------------------------------------*/ + +uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte = 0, * pLengthEnd = pDestination; + size_t remainingLength = length; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = ( uint8_t ) ( remainingLength % 128U ); + remainingLength = remainingLength / 128U; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( remainingLength > 0U ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( remainingLength > 0U ); + + return pLengthEnd; +} + +/*-----------------------------------------------------------*/ + +size_t _IotMqtt_RemainingLengthEncodedSize( size_t length ) +{ + size_t encodedSize = 0; + + /* length should have already been checked before calling this function. */ + IotMqtt_Assert( length <= MQTT_MAX_REMAINING_LENGTH ); + + /* Determine how many bytes are needed to encode length. + * The values below are taken from the MQTT 3.1.1 spec. */ + + /* 1 byte is needed to encode lengths between 0 and 127. */ + if( length < 128U ) + { + encodedSize = 1; + } + /* 2 bytes are needed to encode lengths between 128 and 16,383. */ + else if( length < 16384U ) + { + encodedSize = 2; + } + /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ + else if( length < 2097152U ) + { + encodedSize = 3; + } + /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ + else + { + encodedSize = 4; + } + + return encodedSize; +} + +/*-----------------------------------------------------------*/ + +uint16_t _IotMqtt_NextPacketIdentifier( void ) +{ + /* MQTT specifies 2 bytes for the packet identifier; however, operating on + * 32-bit integers is generally faster. */ + static uint32_t nextPacketIdentifier = 1; + + /* The next packet identifier will be greater by 2. This prevents packet + * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet + * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + bool encodedUserName = false; + size_t connectPacketSize = 0, remainingLength = 0; + + /* The CONNECT packet will always include a 10-byte variable header. */ + connectPacketSize += 10U; + + /* Add the length of the client identifier if provided. */ + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); + + /* Add the lengths of the will message and topic name if provided. */ + if( pConnectInfo->pWillInfo != NULL ) + { + connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + + pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); + } + + /* Depending on the status of metrics, add the length of the metrics username + * or the user-provided username. */ + if( pConnectInfo->awsIotMqttMode == true ) + { + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + connectPacketSize += ( AWS_IOT_METRICS_USERNAME_LENGTH + + ( size_t ) ( pConnectInfo->userNameLength ) + sizeof( uint16_t ) ); + encodedUserName = true; + #endif + } + + /* Add the lengths of the username (if it wasn't already handled above) and + * password, if specified. */ + if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) + { + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + } + + if( pConnectInfo->pPassword != NULL ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + + /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has + * been calculated. */ + remainingLength = connectPacketSize; + + /* Calculate the full size of the MQTT CONNECT packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ + connectPacketSize += 1U + _IotMqtt_RemainingLengthEncodedSize( connectPacketSize ); + + /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ + if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) + { + status = false; + } + else + { + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pPacket, + size_t connectPacketSize ) +{ + uint8_t connectFlags = 0; + uint8_t * pBuffer = pPacket; + + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pPacket; + ( void ) connectPacketSize; + + /* The first byte in the CONNECT packet is the control packet type. */ + *pBuffer = MQTT_PACKET_TYPE_CONNECT; + pBuffer++; + + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + pBuffer = _IotMqtt_EncodeString( pBuffer, "MQTT", 4 ); + + /* The MQTT protocol version is the second byte of the variable header. */ + *pBuffer = MQTT_VERSION_3_1_1; + pBuffer++; + + /* Set the CONNECT flags based on the given parameters. */ + if( pConnectInfo->cleanSession == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + } + + /* Username and password depend on MQTT mode. */ + if( ( pConnectInfo->pUserName == NULL ) && + ( pConnectInfo->awsIotMqttMode == true ) ) + { + /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server + * never uses a password. */ + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + #endif + } + else + { + /* Set the flags for username and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + } + + if( pConnectInfo->pPassword != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + } + } + + /* Set will flag if an LWT is provided. */ + if( pConnectInfo->pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for will QoS 1 and 2. */ + switch( pConnectInfo->pWillInfo->qos ) + { + case IOT_MQTT_QOS_1: + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + break; + + case IOT_MQTT_QOS_2: + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + break; + + default: + /* Empty default MISRA 16.4 */ + break; + } + + if( pConnectInfo->pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + } + + *pBuffer = connectFlags; + pBuffer++; + + /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ + *pBuffer = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + pBuffer += 2; + + /* Write the client identifier into the CONNECT packet. */ + pBuffer = _IotMqtt_EncodeString( pBuffer, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + + /* Write the will topic name and message into the CONNECT packet if provided. */ + if( pConnectInfo->pWillInfo != NULL ) + { + pBuffer = _IotMqtt_EncodeString( pBuffer, + pConnectInfo->pWillInfo->pTopicName, + pConnectInfo->pWillInfo->topicNameLength ); + + pBuffer = _IotMqtt_EncodeString( pBuffer, + pConnectInfo->pWillInfo->pPayload, + ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); + } + + /* Encode the username if there is one or metrics are enabled. */ + pBuffer = _IotMqtt_EncodeUserName( pBuffer, pConnectInfo ); + + /* Encode the password field, if requested by the app. */ + if( pConnectInfo->pPassword != NULL ) + { + pBuffer = _IotMqtt_EncodeString( pBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == connectPacketSize ); + + /* Print out the serialized CONNECT packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT CONNECT packet:", pPacket, connectPacketSize ); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_IncomingPacketValid( uint8_t packetType ) +{ + bool status = true; + + /* Check packet type. Mask out lower bits to ignore flags. */ + switch( packetType & 0xf0U ) + { + /* Valid incoming packet types. */ + case MQTT_PACKET_TYPE_CONNACK: + case MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_SUBACK: + case MQTT_PACKET_TYPE_UNSUBACK: + case MQTT_PACKET_TYPE_PINGRESP: + break; + + /* Any other packet type is invalid. */ + default: + status = false; + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t subscribePacketSize ) +{ + uint16_t packetIdentifier = 0; + size_t i = 0; + uint8_t * pBuffer = pPacket; + + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pPacket; + ( void ) subscribePacketSize; + + /* The first byte in SUBSCRIBE is the packet type. */ + *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _IotMqtt_NextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + IotMqtt_Assert( packetIdentifier != 0U ); + + /* Place the packet identifier into the SUBSCRIBE packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter and QoS. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _IotMqtt_EncodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + /* Place the QoS in the SUBSCRIBE packet. */ + *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); + pBuffer++; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == subscribePacketSize ); + + /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pPacket, subscribePacketSize ); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t i = 0, subscriptionPacketSize = 0; + + /* Only SUBSCRIBE and UNSUBSCRIBE operations should call this function. */ + IotMqtt_Assert( ( type == IOT_MQTT_SUBSCRIBE ) || ( type == IOT_MQTT_UNSUBSCRIBE ) ); + + /* The variable header of a subscription packet consists of a 2-byte packet + * identifier. */ + subscriptionPacketSize += sizeof( uint16_t ); + + /* Sum the lengths of all subscription topic filters; add 1 byte for each + * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ + for( i = 0; i < subscriptionCount; i++ ) + { + /* Add the length of the topic filter. */ + subscriptionPacketSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); + + /* Only SUBSCRIBE packets include the QoS. */ + if( type == IOT_MQTT_SUBSCRIBE ) + { + subscriptionPacketSize += 1U; + } + } + + /* At this point, the "Remaining length" has been calculated. Return error + * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, + * set the output parameter.*/ + if( subscriptionPacketSize > MQTT_MAX_REMAINING_LENGTH ) + { + status = false; + } + else + { + *pRemainingLength = subscriptionPacketSize; + + /* Calculate the full size of the subscription packet by adding the size of the + * "Remaining length" field plus 1 byte for the "Packet type" field. Set the + * pPacketSize output parameter. */ + subscriptionPacketSize += 1U + _IotMqtt_RemainingLengthEncodedSize( subscriptionPacketSize ); + *pPacketSize = subscriptionPacketSize; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t publishPacketSize = 0, payloadLimit = 0; + + /* The variable header of a PUBLISH packet always contains the topic name. */ + publishPacketSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); + + /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte + * packet identifier. */ + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + { + publishPacketSize += sizeof( uint16_t ); + } + + /* Calculate the maximum allowed size of the payload for the given parameters. + * This calculation excludes the "Remaining length" encoding, whose size is not + * yet known. */ + payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1U; + + /* Ensure that the given payload fits within the calculated limit. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + status = false; + } + else + { + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. */ + publishPacketSize += pPublishInfo->payloadLength; + + /* Now that the "Remaining length" is known, recalculate the payload limit + * based on the size of its encoding. */ + payloadLimit -= _IotMqtt_RemainingLengthEncodedSize( publishPacketSize ); + + /* Check that the given payload fits within the size allowed by MQTT spec. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + status = false; + } + else + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = publishPacketSize; + + publishPacketSize += 1U + _IotMqtt_RemainingLengthEncodedSize( publishPacketSize ); + *pPacketSize = publishPacketSize; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pPacket, + size_t publishPacketSize ) +{ + uint8_t publishFlags = 0; + uint16_t packetIdentifier = 0; + uint8_t * pBuffer = pPacket; + + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pPacket; + ( void ) publishPacketSize; + + /* The first byte of a PUBLISH packet contains the packet type and flags. */ + publishFlags = MQTT_PACKET_TYPE_PUBLISH; + + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); + } + else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) + { + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( pPublishInfo->retain == true ) + { + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + } + + *pBuffer = publishFlags; + pBuffer++; + + /* The "Remaining length" is encoded from the second byte. */ + pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + + /* The topic name is placed after the "Remaining length". */ + pBuffer = _IotMqtt_EncodeString( pBuffer, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + + /* A packet identifier is required for QoS 1 and 2 messages. */ + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + { + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _IotMqtt_NextPacketIdentifier(); + IotMqtt_Assert( packetIdentifier != 0U ); + + /* Set the packet identifier output parameters. */ + *pPacketIdentifier = packetIdentifier; + + if( pPacketIdentifierHigh != NULL ) + { + *pPacketIdentifierHigh = pBuffer; + } + + /* Place the packet identifier into the PUBLISH packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + } + + /* The payload is placed after the packet identifier. */ + if( pPublishInfo->payloadLength > 0U ) + { + ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + pBuffer += pPublishInfo->payloadLength; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == publishPacketSize ); + + /* Print out the serialized PUBLISH packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPacket, publishPacketSize ); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t unsubscribePacketSize ) +{ + uint16_t packetIdentifier = 0; + size_t i = 0; + uint8_t * pBuffer = pPacket; + + /* Avoid unused variable warning when logging and asserts are disabled. */ + ( void ) pPacket; + ( void ) unsubscribePacketSize; + + /* The first byte in UNSUBSCRIBE is the packet type. */ + *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _IotMqtt_NextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + IotMqtt_Assert( packetIdentifier != 0U ); + + /* Place the packet identifier into the UNSUBSCRIBE packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _IotMqtt_EncodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == unsubscribePacketSize ); + + /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pPacket, unsubscribePacketSize ); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, + IotMqttPublishInfo_t * pOutput ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( pOutput == NULL ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + /* Check for QoS 2. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) + { + /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + IotLogDebug( "Bad QoS: 3." ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + pOutput->qos = IOT_MQTT_QOS_2; + } + } + /* Check for QoS 1. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + pOutput->qos = IOT_MQTT_QOS_1; + } + /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ + else + { + pOutput->qos = IOT_MQTT_QOS_0; + } + + if( status == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "QoS is %d.", pOutput->qos ); + + /* Parse the Retain bit. */ + pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + + IotLogDebug( "Retain bit is %d.", pOutput->retain ); + + /* Parse the DUP bit. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) + { + IotLogDebug( "DUP is 1." ); + } + else + { + IotLogDebug( "DUP is 0." ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c new file mode 100644 index 0000000000..10099b8ab6 --- /dev/null +++ b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c @@ -0,0 +1,1147 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_lightweight_api.c + * @brief Implements most user-facing functions of the MQTT library. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* MQTT include. */ +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" +#include "private/iot_mqtt_helper.h" + +/*-----------------------------------------------------------*/ + +/* Configure logs for MQTT functions. */ +#ifdef IOT_LOG_LEVEL_MQTT + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "MQTT" ) +#include "iot_logging_setup.h" + + +/*-----------------------------------------------------------*/ + +/** + * @def IotMqtt_Assert( expression ) + * @brief Assertion macro for the MQTT library. + * + * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if IOT_MQTT_ENABLE_ASSERTS == 1 + #ifndef IotMqtt_Assert + #ifdef Iot_DefaultAssert + #define IotMqtt_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for MQTT, but IotMqtt_Assert is not defined" + #endif + #endif +#else + #define IotMqtt_Assert( expression ) +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] getNextByte Function pointer used to interact with the + * network to get next byte. + * + * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. + * + * @note This function is similar to _IotMqtt_GetRemainingLength() but it uses + * user provided getNextByte function to parse the stream instead of using + * _IotMqtt_GetNextByte(). pNetworkConnection is implementation dependent and + * user provided function makes use of it. + * + */ +static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, + IotMqttGetNextByte_t getNextByte ); + +/** + * @brief Deserialize a CONNACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t. Also + * prints out debug log messages about the packet. + * + * @param[in,out] pConnack Pointer to an MQTT packet struct representing a CONNACK. + * + * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; + * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; + * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. + */ +static IotMqttError_t _deserializeConnack( IotMqttPacketInfo_t * pConnack ); + +/** + * @brief Deserialize a SUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in,out] pSuback Pointer to an MQTT packet struct representing a SUBACK. + * + * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the SUBACK packet doesn't follow MQTT spec. + */ +static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ); + +/** + * @brief Deserialize a PUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in,out] pPuback Pointer to an MQTT packet struct representing a PUBACK. + * + * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the PUBACK packet doesn't follow MQTT spec. + */ +static IotMqttError_t _deserializePuback( IotMqttPacketInfo_t * pPuback ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t. Also + * prints out debug log messages about the packet. + * + * @param[in,out] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. + * + * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE + * if the PINGRESP packet doesn't follow MQTT spec. + */ +static IotMqttError_t _deserializePingresp( IotMqttPacketInfo_t * pPingresp ); + +/** + * @brief Deserialize a UNSUBACK packet. + * + * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts + * the packet identifier. Also prints out debug log messages about the packet. + * + * @param[in,out] pUnsuback Pointer to an MQTT packet struct representing an UNSUBACK. + * + * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE + * if the UNSUBACK packet doesn't follow MQTT spec. + */ +static IotMqttError_t _deserializeUnsuback( IotMqttPacketInfo_t * pUnsuback ); + +/** + * @brief Deserialize a PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #IotMqttPublishInfo_t and + * extracts the packet identifier. Also prints out debug log messages about the + * packet. + * + * @param[in,out] pPublish Pointer to an MQTT packet struct representing a PUBLISH. + * + * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE + * if the PUBLISH packet doesn't follow MQTT spec. + */ +static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ); + +/** + * @brief Decode the status bytes of a SUBACK packet. + * + * @param[in] statusCount Number of status bytes in the SUBACK. + * @param[in] pStatusStart The first status byte in the SUBACK. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SERVER_REFUSED, or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _decodeSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ); + +/** + * @brief Check the remaining length of incoming Publish against some value for + * QoS 0 or QoS 1/2. + * + * The remaining length for a QoS 1/2 will always be two greater than for a QoS 0. + * + * @param[in] pPublish Pointer to an MQTT packet struct representing a PUBLISH. + * @param[in] qos The QoS of the PUBLISH. + * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. + */ +static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, + IotMqttQos_t qos, + size_t qos0Minimum ); + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _decodeSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t subscriptionStatus = 0; + size_t i = 0; + + /* Iterate through each status byte in the SUBACK packet. */ + for( i = 0; i < statusCount; i++ ) + { + /* Read a single status byte in SUBACK. */ + subscriptionStatus = *( pStatusStart + i ); + + /* MQTT 3.1.1 defines the following values as status codes. */ + switch( subscriptionStatus ) + { + case 0x00: + case 0x01: + case 0x02: + + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ + IotLogDebug( "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); + break; + + case 0x80: + + /* In some implementations IotLog() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ + IotLogDebug( "Topic filter %lu refused.", ( unsigned long ) i ); + + /* Application should remove subscription from the list */ + status = IOT_MQTT_SERVER_REFUSED; + + break; + + default: + IotLogDebug( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + status = IOT_MQTT_BAD_RESPONSE; + + break; + } + + /* Stop parsing the subscription statuses if a bad response was received. */ + if( status == IOT_MQTT_BAD_RESPONSE ) + { + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, + IotMqttGetNextByte_t getNextByte ) +{ + uint8_t encodedByte = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152U ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + else + { + if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) + { + remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; + multiplier *= 128U; + bytesDecoded++; + } + else + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + } + + if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + break; + } + } while( ( encodedByte & 0x80U ) != 0U ); + + /* Check that the decoded remaining length conforms to the MQTT specification. */ + if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) + { + expectedSize = _IotMqtt_RemainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + else + { + /* Valid remaining length should be at most 4 bytes. */ + IotMqtt_Assert( bytesDecoded <= 4U ); + } + } + + return remainingLength; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializeConnack( IotMqttPacketInfo_t * pConnack ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + const uint8_t * pRemainingData = pConnack->pRemainingData; + + /* If logging is enabled, declare the CONNACK response code strings. The + * fourth byte of CONNACK indexes into this array for the corresponding response. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + static const char * const pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; + #endif + + /* According to MQTT 3.1.1, the second byte of CONNACK must specify a + * "Remaining length" of 2. */ + if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + { + IotLogError( "CONNACK does not have remaining length of %d.", + MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + + status = IOT_MQTT_BAD_RESPONSE; + } + + /* Check the reserved bits in CONNACK. The high 7 bits of the second byte + * in CONNACK must be 0. */ + else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) + { + IotLogError( "Reserved bits in CONNACK incorrect." ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Determine if the "Session Present" bit is set. This is the lowest bit of + * the second byte in CONNACK. */ + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + IotLogError( "CONNACK session present bit set." ); + + /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pRemainingData[ 1 ] != 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + else + { + IotLogError( "CONNACK session present bit not set." ); + } + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ + if( pRemainingData[ 1 ] > 5U ) + { + IotLogError( "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Print the appropriate message for the CONNACK response code if logs are + * enabled. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + IotLogError( "%s", pConnackResponses[ pRemainingData[ 1 ] ] ); + #endif + + /* A nonzero CONNACK response code means the connection was refused. */ + if( pRemainingData[ 1 ] > 0U ) + { + status = IOT_MQTT_SERVER_REFUSED; + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + size_t remainingLength = pSuback->remainingLength; + const uint8_t * pVariableHeader = pSuback->pRemainingData; + + /* A SUBACK must have a remaining length of at least 3 to accommodate the + * packet identifier and at least 1 return code. */ + if( remainingLength < 3U ) + { + IotLogDebug( "SUBACK cannot have a remaining length less than 3." ); + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ + pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); + + IotLogDebug( "Packet identifier %hu.", pSuback->packetIdentifier ); + + + + status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), + pVariableHeader + sizeof( uint16_t ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializeUnsuback( IotMqttPacketInfo_t * pUnsuback ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ + if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) + { + IotLogError( "UNSUBACK does not have remaining length of %d.", + MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ + pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); + + /* Packet identifier cannot be 0. */ + if( pUnsuback->packetIdentifier == 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + + if( status == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "Packet identifier %hu.", pUnsuback->packetIdentifier ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializePingresp( IotMqttPacketInfo_t * pPingresp ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Check the "Remaining length" (second byte) of the received PINGRESP. */ + if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + { + IotLogError( "PINGRESP does not have remaining length of %d.", + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + + status = IOT_MQTT_BAD_RESPONSE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializePuback( IotMqttPacketInfo_t * pPuback ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Check the "Remaining length" of the received PUBACK. */ + if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) + { + IotLogError( "PUBACK does not have remaining length of %d.", + MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ + pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); + + IotLogDebug( "Packet identifier %hu.", pPuback->packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( pPuback->packetIdentifier == 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + IotMqttPublishInfo_t * pOutput = &( pPublish->pubInfo ); + uint8_t publishFlags = 0; + const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; + + /* The flags are the lower 4 bits of the first byte in PUBLISH. */ + publishFlags = pPublish->type; + + status = _IotMqtt_ProcessIncomingPublishFlags( publishFlags, pOutput ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining + * length of at least 3 to accommodate topic name length (2 bytes) and topic + * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of + * at least 5 for the packet identifier in addition to the topic name length and + * topic name. */ + status = _checkPublishRemainingLength( pPublish, pOutput->qos, MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Extract the topic name starting from the first byte of the variable header. + * The topic name string starts at byte 3 in the variable header. */ + pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); + + /* Sanity checks for topic name length and "Remaining length". The remaining + * length must be at least as large as the variable length header. */ + status = _checkPublishRemainingLength( pPublish, + pOutput->qos, + pOutput->topicNameLength + sizeof( uint16_t ) ); + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Parse the topic. */ + pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + + IotLogDebug( "Topic name length %hu: %.*s", + pOutput->topicNameLength, + pOutput->topicNameLength, + pOutput->pTopicName ); + + /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet + * identifier starts immediately after the topic name. */ + pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); + + if( pOutput->qos > IOT_MQTT_QOS_0 ) + { + pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); + + IotLogDebug( "Packet identifier %hu.", pPublish->packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( pPublish->packetIdentifier == 0U ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain + * a packet identifier, but QoS 0 PUBLISH packets do not. */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) + { + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh; + } + else + { + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2U * sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); + } + + IotLogDebug( "Payload length %hu.", pOutput->payloadLength ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, + IotMqttQos_t qos, + size_t qos0Minimum ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Sanity checks for "Remaining length". */ + if( qos == IOT_MQTT_QOS_0 ) + { + /* Check that the "Remaining length" is greater than the minimum. */ + if( pPublish->remainingLength < qos0Minimum ) + { + IotLogDebug( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum ); + + status = IOT_MQTT_BAD_RESPONSE; + } + } + else + { + /* Check that the "Remaining length" is greater than the minimum. For + * QoS 1 or 2, this will be two bytes greater than for QoS due to the + * packet identifier. */ + if( pPublish->remainingLength < ( qos0Minimum + 2U ) ) + { + IotLogDebug( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum + 2U ); + + status = IOT_MQTT_BAD_RESPONSE; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/* Lightweight Public API Functions */ + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + IotLogError( "IotMqtt_GetConnectPacketSize() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); + status = IOT_MQTT_BAD_PARAMETER; + } + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + else if( _IotMqtt_ConnectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) + { + IotLogError( "Connect packet length exceeds %lu, which is the maximum" + " size allowed by MQTT 3.1.1.", + MQTT_PACKET_CONNECT_MAX_SIZE ); + + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) + { + IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", + ( *pRemainingLength ), ( *pPacketSize ) ); + status = IOT_MQTT_BAD_PARAMETER; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pBuffer, + size_t bufferSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pBuffer == NULL ) || ( pConnectInfo == NULL ) ) + { + IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( remainingLength > bufferSize ) + { + IotLogError( " Serialize Connect packet remaining length (%lu) exceeds buffer size (%lu)", + remainingLength, bufferSize ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + _IotMqtt_SerializeConnectCommon( pConnectInfo, + remainingLength, + pBuffer, + bufferSize ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) + { + IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with unknown type." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( subscriptionCount == 0U ) + { + IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( _IotMqtt_SubscriptionPacketSize( type, + pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize ) == false ) + { + IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) + { + IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", + ( *pRemainingLength ), ( *pPacketSize ) ); + status = IOT_MQTT_BAD_PARAMETER; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t bufferSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) || ( pPacketIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_SerializeSubscribe() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( subscriptionCount == 0U ) + { + IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( remainingLength > bufferSize ) + { + IotLogError( " Subscribe packet remaining length (%lu) exceeds buffer size (%lu).", + remainingLength, bufferSize ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + _IotMqtt_SerializeSubscribeCommon( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, + IotMqttGetNextByte_t getNextByte, + IotNetworkConnection_t pNetworkConnection ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Read the packet type, which is the first byte available. */ + if( getNextByte( pNetworkConnection, &( pIncomingPacket->type ) ) == IOT_MQTT_SUCCESS ) + { + /* Check that the incoming packet type is valid. */ + if( _IotMqtt_IncomingPacketValid( pIncomingPacket->type ) == false ) + { + IotLogError( "(MQTT connection) Unknown packet type %02x received.", + pIncomingPacket->type ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Read the remaining length. */ + pIncomingPacket->remainingLength = _getRemainingLength( pNetworkConnection, + getNextByte ); + + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + } + else + { + status = IOT_MQTT_NETWORK_ERROR; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + IotLogError( "IotMqtt_GetPublishPacketSize() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + { + IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); + status = IOT_MQTT_BAD_PARAMETER; + } + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + else if( _IotMqtt_PublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) + { + IotLogError( "Publish packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Total size of the publish packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) + { + IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", + ( *pRemainingLength ), ( *pPacketSize ) ); + status = IOT_MQTT_BAD_PARAMETER; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t bufferSize ) + +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pPacketIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_SerializePublish() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + { + IotLogError( "IotMqtt_SerializePublish() called with no topic." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( remainingLength > bufferSize ) + { + IotLogError( "Publish packet remaining length (%lu) exceeds buffer size (%lu).", + remainingLength, bufferSize ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + _IotMqtt_SerializePublishCommon( pPublishInfo, + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + bufferSize ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t bufferSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( ( pBuffer == NULL ) || ( pPacketIdentifier == NULL ) || ( pSubscriptionList == NULL ) ) + { + IotLogError( "IotMqtt_SerializeUnsubscribe() called with required parameter(s) set to NULL." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( subscriptionCount == 0U ) + { + IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( remainingLength > bufferSize ) + { + IotLogError( "Unsubscribe packet remaining length (%lu) exceeds buffer size (%lu).", + remainingLength, bufferSize ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + _IotMqtt_SerializeUnsubscribeCommon( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, + size_t bufferSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( pBuffer == NULL ) + { + IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); + status = IOT_MQTT_BAD_PARAMETER; + } + + else if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) + { + IotLogError( "Disconnect packet length (%lu) exceeds buffer size (%lu).", + MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Disconnect packets are always the same. */ + pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + pBuffer[ 1 ] = 0x00; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, + size_t bufferSize ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + if( pBuffer == NULL ) + { + IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) + { + IotLogError( "Pingreq length (%lu) exceeds buffer size (%lu).", + MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Ping request packets are always the same. */ + pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; + pBuffer[ 1 ] = 0x00; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + /* Internal MQTT packet structure */ + IotMqttPacketInfo_t mqttPacket; + + if( pMqttPacket == NULL ) + { + IotLogError( "IotMqtt_DeserializePublish()called with NULL pMqttPacket pointer." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( ( pMqttPacket->type & 0xf0U ) != MQTT_PACKET_TYPE_PUBLISH ) + { + IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); + status = IOT_MQTT_BAD_PARAMETER; + } + else + { + /* Set internal mqtt packet parameters. */ + ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( IotMqttPacketInfo_t ) ); + mqttPacket.pRemainingData = pMqttPacket->pRemainingData; + mqttPacket.remainingLength = pMqttPacket->remainingLength; + mqttPacket.type = pMqttPacket->type; + status = _deserializePublish( &mqttPacket ); + } + + if( status == IOT_MQTT_SUCCESS ) + { + pMqttPacket->pubInfo = mqttPacket.pubInfo; + pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + /* Internal MQTT packet structure */ + IotMqttPacketInfo_t mqttPacket; + + if( ( pMqttPacket == NULL ) || ( pMqttPacket->pRemainingData == NULL ) ) + { + IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer or NULL pRemainingLength." ); + status = IOT_MQTT_BAD_PARAMETER; + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Set internal mqtt packet parameters. */ + ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( IotMqttPacketInfo_t ) ); + + mqttPacket.pRemainingData = pMqttPacket->pRemainingData; + mqttPacket.remainingLength = pMqttPacket->remainingLength; + mqttPacket.type = pMqttPacket->type; + + /* Make sure response packet is a valid packet */ + switch( pMqttPacket->type & 0xf0U ) + { + case MQTT_PACKET_TYPE_CONNACK: + status = _deserializeConnack( &mqttPacket ); + break; + + case MQTT_PACKET_TYPE_SUBACK: + status = _deserializeSuback( &mqttPacket ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + status = _deserializeUnsuback( &mqttPacket ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + status = _deserializePingresp( &mqttPacket ); + break; + + case MQTT_PACKET_TYPE_PUBACK: + status = _deserializePuback( &mqttPacket ); + break; + + /* Any other packet type is invalid. */ + default: + IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); + status = IOT_MQTT_BAD_RESPONSE; + break; + } + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Set packetIdentifier only if success is returned. */ + pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 9c3681b032..ef5a98e258 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -865,45 +865,6 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, - IotMqttGetNextByte_t getNextByte, - IotNetworkConnection_t pNetworkConnection ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Read the packet type, which is the first byte available. */ - if( getNextByte( pNetworkConnection, &( pIncomingPacket->type ) ) == IOT_MQTT_SUCCESS ) - { - /* Check that the incoming packet type is valid. */ - if( _incomingPacketValid( pIncomingPacket->type ) == false ) - { - IotLogError( "(MQTT connection) Unknown packet type %02x received.", - pIncomingPacket->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Read the remaining length. */ - pIncomingPacket->remainingLength = _IotMqtt_GetRemainingLength_Generic( pNetworkConnection, - getNextByte ); - - if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - } - else - { - status = IOT_MQTT_NETWORK_ERROR; - } - - return status; -} - -/*-----------------------------------------------------------*/ - /* Provide access to internal functions and variables if testing. */ /* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ /* coverity[misra_c_2012_rule_20_9_violation] */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 3d07bc72e1..87cac5564a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1399,60 +1399,6 @@ size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, /*-----------------------------------------------------------*/ -size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConnection, - IotMqttGetNextByte_t getNextByte ) -{ - uint8_t encodedByte = 0; - size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - if( multiplier > 2097152U ) /* 128 ^ 3 */ - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) - { - remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; - multiplier *= 128U; - bytesDecoded++; - } - else - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - } - - if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - break; - } - } while( ( encodedByte & 0x80U ) != 0U ); - - /* Check that the decoded remaining length conforms to the MQTT specification. */ - if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) - { - expectedSize = _remainingLengthEncodedSize( remainingLength ); - - if( bytesDecoded != expectedSize ) - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4U ); - } - } - - return remainingLength; -} - -/*-----------------------------------------------------------*/ - IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, uint8_t ** pConnectPacket, size_t * pPacketSize ) @@ -2214,472 +2160,3 @@ void _IotMqtt_FreePacket( uint8_t * pPacket ) /* Public interface functions for serialization */ /*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) - { - IotLogError( "IotMqtt_GetConnectPacketSize() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); - status = IOT_MQTT_BAD_PARAMETER; - } - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - else if( _connectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) - { - IotLogError( "Connect packet length exceeds %lu, which is the maximum" - " size allowed by MQTT 3.1.1.", - MQTT_PACKET_CONNECT_MAX_SIZE ); - - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) - { - IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", - ( *pRemainingLength ), ( *pPacketSize ) ); - status = IOT_MQTT_BAD_PARAMETER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pConnectInfo == NULL ) ) - { - IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( " Serialize Connect packet remaining length (%lu) exceeds buffer size (%lu)", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _serializeConnect( pConnectInfo, - remainingLength, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) - { - IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) - { - IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with unknown type." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( subscriptionCount == 0U ) - { - IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( _subscriptionPacketSize( type, - pSubscriptionList, - subscriptionCount, - pRemainingLength, - pPacketSize ) == false ) - { - IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) - { - IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", - ( *pRemainingLength ), ( *pPacketSize ) ); - status = IOT_MQTT_BAD_PARAMETER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) || ( pPacketIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_SerializeSubscribe() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( subscriptionCount == 0U ) - { - IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( " Subscribe packet remaining length (%lu) exceeds buffer size (%lu).", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _serializeSubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) - { - IotLogError( "IotMqtt_GetPublishPacketSize() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) - { - IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); - status = IOT_MQTT_BAD_PARAMETER; - } - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - else if( _publishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) - { - IotLogError( "Publish packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Total size of the publish packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) - { - IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", - ( *pRemainingLength ), ( *pPacketSize ) ); - status = IOT_MQTT_BAD_PARAMETER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, - size_t bufferSize ) - -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pPacketIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_SerializePublish() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) - { - IotLogError( "IotMqtt_SerializePublish() called with no topic." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( "Publish packet remaining length (%lu) exceeds buffer size (%lu).", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _serializePublish( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pPacketIdentifier == NULL ) || ( pSubscriptionList == NULL ) ) - { - IotLogError( "IotMqtt_SerializeUnsubscribe() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( subscriptionCount == 0U ) - { - IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( "Unsubscribe packet remaining length (%lu) exceeds buffer size (%lu).", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _serializeUnsubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t * pDisconnectPacket = NULL; - size_t remainingLength = 0; - - if( pBuffer == NULL ) - { - IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - - else if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) - { - IotLogError( "Disconnect packet length (%lu) exceeds buffer size (%lu).", - MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Call internal function with local variables, as disconnect uses - * static memory, there is no need to pass the buffer - * Note: _IotMqtt_SerializeDisconnect always succeeds */ - ( void ) _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); - - ( void ) memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t * pPingreqPacket = NULL; - size_t packetSize = 0; - - if( pBuffer == NULL ) - { - IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) - { - IotLogError( "Pingreq length (%lu) exceeds buffer size (%lu).", - MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Call internal function with local variables, as ping request uses - * static memory, there is no need to pass the buffer - * Note: _IotMqtt_SerializePingReq always succeeds */ - ( void ) _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); - ( void ) memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Internal MQTT packet structure */ - _mqttPacket_t mqttPacket; - /* Internal MQTT operation structure needed for deserializing publish */ - _mqttOperation_t mqttOperation; - - if( pMqttPacket == NULL ) - { - IotLogError( "IotMqtt_DeserializePublish()called with NULL pMqttPacket pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pMqttPacket->type & 0xf0U ) != MQTT_PACKET_TYPE_PUBLISH ) - { - IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Set internal mqtt packet parameters. */ - ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; - mqttPacket.remainingLength = pMqttPacket->remainingLength; - mqttPacket.type = pMqttPacket->type; - - /* Set Publish specific parameters */ - ( void ) memset( ( void * ) &mqttOperation, 0x00, sizeof( _mqttOperation_t ) ); - mqttOperation.incomingPublish = true; - mqttPacket.u.pIncomingPublish = &mqttOperation; - status = _IotMqtt_DeserializePublish( &mqttPacket ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - pMqttPacket->pubInfo = mqttOperation.u.publish.publishInfo; - pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Internal MQTT packet structure */ - _mqttPacket_t mqttPacket; - - if( ( pMqttPacket == NULL ) || ( pMqttPacket->pRemainingData == NULL ) ) - { - IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer or NULL pRemainingLength." ); - status = IOT_MQTT_BAD_PARAMETER; - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Set internal mqtt packet parameters. */ - ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); - - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; - mqttPacket.remainingLength = pMqttPacket->remainingLength; - mqttPacket.type = pMqttPacket->type; - - /* Call internal deserialize */ - switch( pMqttPacket->type & 0xf0U ) - { - case MQTT_PACKET_TYPE_CONNACK: - status = _IotMqtt_DeserializeConnack( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_PUBACK: - status = _IotMqtt_DeserializePuback( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_SUBACK: - status = _IotMqtt_DeserializeSuback( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_UNSUBACK: - status = _IotMqtt_DeserializeUnsuback( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_PINGRESP: - status = _IotMqtt_DeserializePingresp( &mqttPacket ); - break; - - /* Any other packet type is invalid. */ - default: - IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); - status = IOT_MQTT_BAD_PARAMETER; - break; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Set packetIdentifier only if success is returned. */ - pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h new file mode 100644 index 0000000000..551476f0d8 --- /dev/null +++ b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h @@ -0,0 +1,283 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_helper.c + * @brief Implements internal helper functions for the MQTT library. + */ + +#ifndef IOT_MQTT_SERIALIZE_INTERNAL_H_ +#define IOT_MQTT_SERIALIZE_INTERNAL_H_ + +/* Standard includes. */ +#include +#include +/* Default assert and memory allocation functions. */ +#include +#include + +/* MQTT Protocol Specific defines */ +#include "iot_mqtt_protocol.h" +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + +/*-----------------------------------------------------------*/ + +/* + * Macros for reading the high and low byte of a 2-byte unsigned int. + */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) /**< @brief Get high byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) /**< @brief Get low byte. */ + +/** + * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. + * + * @param[in] ptr A uint8_t* that points to the high byte. + */ +#define UINT16_DECODE( ptr ) \ + ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ + ( ( uint16_t ) ( *( ( ptr ) + 1 ) ) ) ) + +/** + * @brief Macro for setting a bit in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to set. + * @param[in] position Which bit to set. + */ +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) + +/** + * @brief Macro for checking if a bit is set in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to check. + * @param[in] position Which bit to check. + */ +#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. + * + * @param[out] pDestination Where to write the encoded string. + * @param[in] source The string to encode. + * @param[in] sourceLength The length of source. + * + * @return Pointer to the end of the encoded string, which is `sourceLength+2` + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer + * overflow. + */ +uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ); + +/** + * @brief Calculate the number of bytes required to encode an MQTT + * "Remaining length" field. + * + * @param[in] length The value of the "Remaining length" to encode. + * + * @return The size of the encoding of length. This is always `1`, `2`, `3`, or `4`. + */ +size_t _IotMqtt_RemainingLengthEncodedSize( size_t length ); + +/** + * @brief Encode the "Remaining length" field per MQTT spec. + * + * @param[out] pDestination Where to write the encoded "Remaining length". + * @param[in] length The "Remaining length" to encode. + * + * @return Pointer to the end of the encoded "Remaining length", which is 1-4 + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold the encoded "Remaining length" using + * the function #_remainingLengthEncodedSize to avoid buffer overflows. + */ +uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, + size_t length ); + +/** + * @brief Calculate the size and "Remaining length" of a CONNECT packet generated + * from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); + + +/** + * @brief Generate a CONNECT packet from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[in] remainingLength User provided remaining length. + * @param[in, out] pPacket User provided buffer where the CONNECT packet is written. + * @param[in] connectPacketSize Size of the buffer pointed to by `pPacket`. + * + */ +void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pPacket, + size_t connectPacketSize ); + +/** + * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE + * packet generated from the given parameters. + * + * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/** + * @brief Generate a SUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[in] remainingLength User provided remaining length. + * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. + * @param[in, out] pPacket User provided buffer where the SUBSCRIBE packet is written. + * @param[in] subscribePacketSize Size of the buffer pointed to by `pPacket`. + * + */ +void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t subscribePacketSize ); + +/** + * @brief Generate an UNSUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions to remove. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[in] remainingLength User provided remaining length. + * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. + * @param[in, out] pPacket User provided buffer where the UNSUBSCRIBE packet is written. + * @param[in] unsubscribePacketSize size of the buffer pointed to by `pPacket`. + * + */ +void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t unsubscribePacketSize ); + +/** + * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated + * from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` + * otherwise. If this function returns `false`, the output parameters should be ignored. + */ +bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/** + * @brief Generate a PUBLISH packet from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information. + * @param[in] remainingLength User provided remaining length. + * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier + * is written. + * @param[in, out] pPacket User provided buffer where the PUBLISH packet is written. + * @param[in] publishPacketSize Size of buffer pointed to by `pPacket`. + * + */ +void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pPacket, + size_t publishPacketSize ); + +/** + * @brief Check if an incoming packet type is valid. + * + * @param[in] packetType The packet type to check. + * + * @return `true` if the packet type is valid; `false` otherwise. + */ +bool _IotMqtt_IncomingPacketValid( uint8_t packetType ); + +/** + * @brief Generate and return a 2-byte packet identifier. + * + * This packet identifier will be nonzero. + * + * @return The packet identifier. + */ +uint16_t _IotMqtt_NextPacketIdentifier( void ); + +/** + * @brief Process incoming publish flags. + * + * @param[in] publishFlags Incoming publish flags. + * @param[in, out] pOutput Pointer to #IotMqttPublishInfo_t struct. + * where output will be written. + * + * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE. + */ + +IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, + IotMqttPublishInfo_t * pOutput ); + +/** + * @brief Encode a username into a CONNECT packet, if necessary. + * + * @param[out] pDestination Buffer for the CONNECT packet. + * @param[in] pConnectInfo User-provided CONNECT information. + * + * @return Pointer to the end of the encoded string, which will be identical to + * `pDestination` if nothing was encoded. + */ + uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo ); +#endif \ No newline at end of file diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index b74e537e7d..fc727c5726 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -37,7 +37,6 @@ /* MQTT include. */ #include "iot_mqtt.h" -#include "iot_mqtt_serialize.h" /* Task pool include. */ #include "iot_taskpool.h" @@ -565,25 +564,6 @@ uint8_t _IotMqtt_GetPacketType( IotNetworkConnection_t pNetworkConnection, */ size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); - -/** - * @brief Get the remaining length from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] getNextByte Function pointer used to interact with the - * network to get next byte. - * - * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. - * - * @note This function is similar to _IotMqtt_GetRemainingLength() but it uses - * user provided getNextByte function to parse the stream instead of using - * _IotMqtt_GetNextByte(). pNetworkConnection is implementation dependent and - * user provided function makes use of it. - * - */ -size_t _IotMqtt_GetRemainingLength_Generic( IotNetworkConnection_t pNetworkConnection, - IotMqttGetNextByte_t getNextByte ); - /** * @brief Generate a CONNECT packet from the given parameters. * diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 106ea0aef7..1f12b7643f 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -48,7 +48,7 @@ #include "iot_test_access_mqtt.h" /* MQTT serializer API include */ -#include "iot_mqtt_serialize.h" +#include "iot_mqtt_lightweight.h" /* MQTT mock include. */ #include "iot_tests_mqtt_mock.h" @@ -2532,10 +2532,11 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) buffer[ 0 ] = 0x20; /* CONN ACK */ /* To generate invalid remaining length response, - * three bytes need to have MSB (or continuation bit, 0x80) set */ + * four bytes need to have MSB (or continuation bit, 0x80) set */ buffer[ 1 ] = 0xFF; buffer[ 2 ] = 0xFF; buffer[ 3 ] = 0xFF; + buffer[ 4 ] = 0xFF; status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); } @@ -2561,7 +2562,7 @@ TEST( MQTT_Unit_API, DeserializeResponseChecks ) mqttPacketInfo.type = 0x01; mqttPacketInfo.pRemainingData = buffer; status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); /* Good case succeeds - Test for CONN ACK */ /* Set conn ack variable portion */ From 1c646c1caf3c84c3d97ec7c8cf94aa8c5e299bc1 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 4 Feb 2020 16:34:06 -0800 Subject: [PATCH 414/844] Documentation update for MQTT design (#775) * Documentation update for MQTT design --- doc/lib/mqtt.txt | 20 ++++++++++++---- doc/plantuml/images/mqtt_design_keepalive.png | Bin 82072 -> 82140 bytes .../images/mqtt_design_typicaloperation.png | Bin 65834 -> 50276 bytes doc/plantuml/mqtt_design_keepalive.pu | 2 +- doc/plantuml/mqtt_design_typicaloperation.pu | 22 ++++-------------- libraries/standard/mqtt/include/iot_mqtt.h | 9 +++++++ 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 3db01a0385..a31e769653 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -200,6 +200,8 @@ For example, consider an application that does the following: This application is expected to have a peak runtime memory usage of about 600 bytes (not counting any usage by dependencies or the TLS stack). +By default, this memory would be allocated from the heap using the [configured memory allocation functions](@ref mqtt_config_memory). If @ref IOT_STATIC_MEMORY_ONLY is defined to `1`, then the memory will be allocated at compile-time. + @image html mqtt_memory_example.png width=80% */ @@ -219,7 +221,7 @@ Synchronous functions block their calling thread until the MQTT operation is com @section mqtt_design_asyncoperation Operation diagram @brief The following diagram shows the workflow of an operation. -MQTT relies on the [task pool library](@ref taskpool) to process asynchronous MQTT operations in the background. The MQTT API [functions](@ref mqtt_functions) post a job to the task pool, which is transmitted from a background task. Incoming responses are received through a [network receive callback](@ref platform_network_function_receivecallback), which posts another job to process the response to the task pool. +MQTT relies on the [task pool library](@ref taskpool) to process asynchronous MQTT operations in the background. Each MQTT API [function](@ref mqtt_functions) allocates the required resources, then schedules a background task to send the MQTT packet and receive the server's response. Incoming responses are received through a [network receive callback](@ref platform_network_function_receivecallback), which schedules another job to process the response. Synchronous operations are implemented as a call to the corresponding asynchronous operation. The synchronous function then waits on a semaphore until it receives a notification of completion. The workflow of a synchronous operation is otherwise identical to an asynchronous operation. @@ -227,6 +229,13 @@ Some operations, such as QoS 1 PUBLISHes, may be retried. See @ref IotMqttPublis @image html mqtt_design_typicaloperation.png width=80% +@section mqtt_design_subscriptions Subscriptions +@brief The following diagram shows the lifecycle of MQTT subscriptions. + +MQTT subscriptions are added with @ref mqtt_function_subscribeasync or @ref mqtt_function_subscribesync; subscriptions are removed with @ref mqtt_function_unsubscribesync or @ref mqtt_function_unsubscribeasync. Subscriptions are associated with a callback function that will be invoked by the library every time a message matching the associated topic filter is received. For example, if a connection has subscriptions for `#` (which matches everything) and `test`, and a message is received on `test`, the callback functions for both `#` and `test` will be invoked. Callbacks are invoked from the context of a background task pool threads. Because incoming messages are asynchronous (may arrive at any time), subscription callbacks are also asynchronous. + +This library supports the use of MQTT persistent sessions. Persistent sessions cause the MQTT server to store subscriptions and undelivered messages. When re-establishing a persistent session, the client should set @ref IotMqttConnectInfo_t.pPreviousSubscriptions and @ref IotMqttConnectInfo_t.previousSubscriptionCount to restore a list of sessions that were present in the persistent session. Setting these members does not send an MQTT SUBSCRIBE packet to the server, so they may only be used with topics that are active on the server. + @section mqtt_design_keepalive Periodic keep-alive @brief The following diagram shows the workflow of the periodic keep-alive. @@ -237,10 +246,11 @@ The standard does not specify how long the server has to respond to a ping reque The PINGREQ is allocated with the MQTT connection when it is created. A `failure` flag is also set to `0`. For simplicity, the diagram below only shows the portions of MQTT CONNECT relevant for keep-alive. Once MQTT CONNECT returns to the application, the keep-alive is processed entirely through background tasks. The period of the keep-alive is controlled with @ref IotMqttConnectInfo_t.keepAliveSeconds (represented by `keepAliveSec` in the diagram below). Every `keepAliveSec`, the following code is run by a background task in the task pool. -1. The `failure` flag is set to `1`. -2. A PINGREQ is sent to the server. -3. If the connection is alive, the server will respond with a PINGRESP. When the client receives the PINGRESP, it sets `failure` to `0`. -4. After @ref IOT_MQTT_RESPONSE_WAIT_MS, the client checks the `failure` flag. If `failure=0`, then a PINGRESP was received and the next PINGREQ is scheduled. Otherwise, if `failure=1`, then no PINGRESP was received and the connection terminates. +1. If another MQTT packet was sent within the keep-alive period, don't send the PINGREQ and reschedule for the next keep-alive period. For simplicity, this is not shown in the diagram below. +2. The `failure` flag is set to `1`. +3. A PINGREQ is sent to the server. +4. If the connection is alive, the server will respond with a PINGRESP. When the client receives the PINGRESP, it sets `failure` to `0`. +5. After @ref IOT_MQTT_RESPONSE_WAIT_MS, the client checks the `failure` flag. If `failure=0`, then a PINGRESP was received and the next PINGREQ is scheduled. Otherwise, if `failure=1`, then no PINGRESP was received and the connection terminates. @image html mqtt_design_keepalive.png width=80% */ diff --git a/doc/plantuml/images/mqtt_design_keepalive.png b/doc/plantuml/images/mqtt_design_keepalive.png index 03c747f6ebda7a6084b2b82d0fc669ac3d1902da..9bb0dda47cae3563f7274f61cab0e756a872d06a 100644 GIT binary patch literal 82140 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≦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 literal 82072 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 NiHk@HrwcxF{eP58>@olV diff --git a/doc/plantuml/mqtt_design_keepalive.pu b/doc/plantuml/mqtt_design_keepalive.pu index a318770a83..75dd383187 100644 --- a/doc/plantuml/mqtt_design_keepalive.pu +++ b/doc/plantuml/mqtt_design_keepalive.pu @@ -4,7 +4,7 @@ skinparam classFontName Helvetica autonumber box "Application" #LightGreen - participant "Application thread" as application + participant "Application code" as application create participant "MQTT CONNECT" as api end box diff --git a/doc/plantuml/mqtt_design_typicaloperation.pu b/doc/plantuml/mqtt_design_typicaloperation.pu index 3efb0af0ea..b06dbad484 100644 --- a/doc/plantuml/mqtt_design_typicaloperation.pu +++ b/doc/plantuml/mqtt_design_typicaloperation.pu @@ -4,14 +4,10 @@ skinparam classFontName Helvetica autonumber box "Application" #LightGreen - participant "Application thread" as application + participant "Application code" as application create participant "MQTT API\nfunction" as api end box -box "Task pool" #LightBlue - participant "Task pool jobs" as task_pool -end box - box "Network stack" #Orange participant "Receive\ncallback" as receive_callback participant "Network IO" as network @@ -23,30 +19,22 @@ application -> api: Call MQTT\noperation\nfunction deactivate application activate api api -> api: Allocate resources\nand generate\nMQTT packet -api -> task_pool: Post send job -activate task_pool +api -> network: Transmit\n MQTT packet +api -> api: Mark operation\nas awaiting response api -> application: Return\nSTATUS_PENDING destroy api activate application -task_pool -> network: Transmit\n MQTT packet activate network -task_pool -> task_pool: Mark operation\nas awaiting response network -> : Send data to server -deactivate task_pool - -network <- : Server response == Parse Server Response == +network <- : Server response network -> receive_callback: Notify of response deactivate network activate receive_callback receive_callback -> receive_callback: Parse incoming packet -receive_callback -> task_pool: Post notify job +receive_callback -> application: Notify of result deactivate receive_callback -activate task_pool -task_pool -> application: Notify of result - -deactivate task_pool deactivate application @enduml diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index 2004b28793..0b46f3ecb2 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -174,6 +174,15 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * (@ref IotMqttPublishInfo_t.retryLimit) members of #IotMqttPublishInfo_t * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL. * + * MQTT keep-alive is configured by @ref IotMqttConnectInfo_t.keepAliveSeconds. + * Keep-alive is used to detect half-open or otherwise unusable network connections. + * An MQTT client will send periodic ping requests (PINGREQ) to the server if the + * connection is idle. The MQTT server must respond to ping requests with a ping + * response (PINGRESP). The standard does not specify how long the server has to + * respond to a ping request, noting only a "reasonable amount of time". In this library, + * the amount of time a server has to respond to a ping request is set with + * @ref IOT_MQTT_RESPONSE_WAIT_MS. + * * Unlike @ref mqtt_function_publishasync, @ref mqtt_function_subscribeasync, and * @ref mqtt_function_unsubscribeasync, this function is always blocking. Additionally, * because the MQTT connection acknowledgement packet (CONNACK packet) does not From 5cdd1258317adb63aa829bf61e526eca7139ce37 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Fri, 7 Feb 2020 10:13:54 -0800 Subject: [PATCH 415/844] Disable the allocation failure Jobs test if malloc overrides are not allowed. (#781) --- .../test/unit/aws_iot_tests_jobs_serialize.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c index 58f45e130f..fa0cfa545a 100644 --- a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c +++ b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c @@ -739,16 +739,18 @@ TEST( Jobs_Unit_Serialize, ParseErrorResponse ) _jobsOperation_t operation = { .link = { 0 } }; /* Test for failure to allocate memory for response. */ - operation.status = AWS_IOT_JOBS_STATUS_PENDING; - operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; - operation.mallocResponse = IotTest_Malloc; + #if IOT_TEST_NO_MALLOC_OVERRIDES != 1 + operation.status = AWS_IOT_JOBS_STATUS_PENDING; + operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; + operation.mallocResponse = IotTest_Malloc; - UnityMalloc_MakeMallocFailAfterCount( 0 ); + UnityMalloc_MakeMallocFailAfterCount( 0 ); - _AwsIotJobs_ParseResponse( AWS_IOT_ACCEPTED, "{}", 2, &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, operation.status ); - UnityMalloc_MakeMallocFailAfterCount( -1 ); - operation.flags = 0; + _AwsIotJobs_ParseResponse( AWS_IOT_ACCEPTED, "{}", 2, &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, operation.status ); + UnityMalloc_MakeMallocFailAfterCount( -1 ); + operation.flags = 0; + #endif /* if IOT_TEST_NO_MALLOC_OVERRIDES != 1 */ /* Test parsing of error responses. */ const char * pErrorResponses[] = From afacef27731dcc067d5ba80a16f54a505829d555 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 7 Feb 2020 16:16:56 -0800 Subject: [PATCH 416/844] Move dependent MQTT tests to platform group (#782) * Add reentrant tests to platform group * Remove reentrancy tests from system * Move SingleThreaded test to platform group * Move SubscriptionReferences test to platform group --- .../mqtt/test/system/iot_tests_mqtt_system.c | 250 ------ .../mqtt/test/unit/iot_tests_mqtt_api.c | 65 -- .../mqtt/test/unit/iot_tests_mqtt_platform.c | 757 +++++++++++++++++- .../test/unit/iot_tests_mqtt_subscription.c | 175 ---- 4 files changed, 756 insertions(+), 491 deletions(-) diff --git a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c index 18e236708d..a7c87af73a 100644 --- a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c +++ b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c @@ -428,86 +428,6 @@ static void _operationComplete( void * pArgument, /*-----------------------------------------------------------*/ -/** - * @brief Callback that makes additional MQTT API calls. - */ -static void _reentrantCallback( void * pArgument, - IotMqttCallbackParam_t * pOperation ) -{ - bool status = true; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; - IotSemaphore_t * pWaitSemaphores = ( IotSemaphore_t * ) pArgument; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); - const uint16_t topicLength = ( uint16_t ) strlen( pTopic ); - - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = topicLength; - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; - publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; - - mqttStatus = IotMqtt_PublishSync( pOperation->mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - - if( mqttStatus == IOT_MQTT_SUCCESS ) - { - status = IotSemaphore_TimedWait( &( pWaitSemaphores[ 0 ] ), - IOT_TEST_MQTT_TIMEOUT_MS ); - } - else - { - status = false; - } - - /* Remove subscription and disconnect. */ - if( status == true ) - { - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = topicLength; - - mqttStatus = IotMqtt_UnsubscribeAsync( pOperation->mqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeOperation ); - - if( mqttStatus == IOT_MQTT_STATUS_PENDING ) - { - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( pOperation->mqttConnection, 0 ); - - /* Waiting on an operation whose connection is closed should return - * "Network Error". */ - mqttStatus = IotMqtt_Wait( unsubscribeOperation, - 500 ); - - status = ( mqttStatus == IOT_MQTT_NETWORK_ERROR ); - } - else - { - status = false; - } - } - - /* Disconnect and unblock the main test thread. */ - if( status == true ) - { - IotSemaphore_Post( &( pWaitSemaphores[ 1 ] ) ); - } -} - -/*-----------------------------------------------------------*/ - /** * @brief Run the subscribe-publish-wait tests at various QoS. */ @@ -738,8 +658,6 @@ TEST_GROUP_RUNNER( MQTT_System ) RUN_TEST_CASE( MQTT_System, LastWillAndTestament ); RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); RUN_TEST_CASE( MQTT_System, WaitAfterDisconnect ); - RUN_TEST_CASE( MQTT_System, SubscribeCompleteReentrancy ); - RUN_TEST_CASE( MQTT_System, IncomingPublishReentrancy ); } /*-----------------------------------------------------------*/ @@ -1173,171 +1091,3 @@ TEST( MQTT_System, WaitAfterDisconnect ) } /*-----------------------------------------------------------*/ - -/** - * @brief Test that API functions can be invoked from a callback for a completed - * subscription operation. - */ -TEST( MQTT_System, SubscribeCompleteReentrancy ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Two semaphores are needed for this test: one for incoming PUBLISH and one - * for test completion. */ - IotSemaphore_t pWaitSemaphores[ 2 ]; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); - - /* Create the semaphores. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - if( TEST_PROTECT() ) - { - /* Establish the MQTT connection. */ - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Subscribe with a completion callback. */ - subscription.qos = IOT_MQTT_QOS_1; - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = _publishReceived; - subscription.callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); - - callbackInfo.function = _reentrantCallback; - callbackInfo.pCallbackContext = pWaitSemaphores; - - status = IotMqtt_SubscribeAsync( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); - - /* Wait for the reentrant callback to complete. */ - if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); - } - } - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that API functions can be invoked from a callback for an incoming - * PUBLISH. - */ -TEST( MQTT_System, IncomingPublishReentrancy ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t pSubscription[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Two semaphores are needed for this test: one for incoming PUBLISH and one - * for test completion. */ - IotSemaphore_t pWaitSemaphores[ 2 ]; - - /* The topics to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic1, "/IncomingPublishReentrancy" ); - GENERATE_TOPIC_WITH_SUFFIX( pTopic2, "/Reentrancy" ); - - /* Create the semaphores. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - if( TEST_PROTECT() ) - { - /* Establish the MQTT connection. */ - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Subscribe with to the test topics. */ - pSubscription[ 0 ].qos = IOT_MQTT_QOS_1; - pSubscription[ 0 ].pTopicFilter = pTopic1; - pSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 0 ].pTopicFilter ); - pSubscription[ 0 ].callback.function = _reentrantCallback; - pSubscription[ 0 ].callback.pCallbackContext = pWaitSemaphores; - - pSubscription[ 1 ].qos = IOT_MQTT_QOS_1; - pSubscription[ 1 ].pTopicFilter = pTopic2; - pSubscription[ 1 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 1 ].pTopicFilter ); - pSubscription[ 1 ].callback.function = _publishReceived; - pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); - - status = IotMqtt_SubscribeSync( _mqttConnection, - pSubscription, - 2, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Publish a message to the test topic. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = pTopic1; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; - publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; - - status = IotMqtt_PublishSync( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Wait for the reentrant callback to complete. */ - if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); - } - } - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 1f12b7643f..81e991f4b2 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -667,7 +667,6 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); RUN_TEST_CASE( MQTT_Unit_API, UnsubscribeMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, SingleThreaded ); RUN_TEST_CASE( MQTT_Unit_API, KeepAlivePeriodic ); RUN_TEST_CASE( MQTT_Unit_API, KeepAliveJobCleanup ); RUN_TEST_CASE( MQTT_Unit_API, GetConnectPacketSizeChecks ); @@ -1831,70 +1830,6 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) /*-----------------------------------------------------------*/ -/** - * @brief Test that MQTT can work in a single thread without the task pool. - */ -TEST( MQTT_Unit_API, SingleThreaded ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_SMALL; - - /* Shut down the system task pool to test if MQTT works without it. */ - IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); - - /* Set the members of the subscription. */ - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - - /* Set the members of the publish info. */ - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - publishInfo.pPayload = "test"; - publishInfo.payloadLength = 4; - publishInfo.qos = IOT_MQTT_QOS_1; - - if( TEST_PROTECT() ) - { - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); - - /* Add a subscription. */ - status = IotMqtt_SubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Transmit a message with no retry. */ - status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Remove the subscription. */ - status = IotMqtt_UnsubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Re-initialize the system task pool. The task pool must be available to - * send messages with a retry. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); - - /* Transmit a message with a retry. */ - publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; - publishInfo.retryMs = DUP_CHECK_RETRY_MS; - status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - IotTest_MqttMockCleanup(); - } - else - { - /* Re-initialize the system task pool for test tear down. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); - } -} - -/*-----------------------------------------------------------*/ - /** * @brief Tests keep-alive handling and ensures that it is periodic. */ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index b656d7c650..c0372d8eba 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -31,7 +31,8 @@ /* Standard includes. */ #include -/* Platform threads include. */ +/* Platform layer includes. */ +#include "platform/iot_clock.h" #include "platform/iot_threads.h" /* SDK initialization include. */ @@ -52,6 +53,9 @@ /* MQTT mock include. */ #include "iot_tests_mqtt_mock.h" +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER + /* Test framework includes. */ #include "unity_fixture.h" @@ -83,6 +87,82 @@ */ #define PACKET_LENGTH ( 1 ) +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef IOT_TEST_MQTT_TIMEOUT_MS + #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) +#endif +#ifndef IOT_TEST_MQTT_CONNECT_RETRY_COUNT + #define IOT_TEST_MQTT_CONNECT_RETRY_COUNT ( 1 ) +#endif +#ifndef IOT_TEST_MQTT_PUBLISH_RETRY_COUNT + #define IOT_TEST_MQTT_PUBLISH_RETRY_COUNT ( 3 ) +#endif +/** @endcond */ + +#if IOT_TEST_MQTT_CONNECT_RETRY_COUNT < 1 + #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 1." +#endif +#if IOT_TEST_MQTT_PUBLISH_RETRY_COUNT < 0 + #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 0." +#endif + +/** + * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). + */ +#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 + #define AWS_IOT_MQTT_SERVER true +#else + #define AWS_IOT_MQTT_SERVER false +#endif + +/** + * @brief The maximum length of an MQTT client identifier. + * + * When @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER is defined, this value must + * accommodate the length of @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER plus 4 + * to accommodate the Last Will and Testament test. Otherwise, this value is + * set to 24, which is the longest client identifier length an MQTT server is + * obligated to accept plus a NULL terminator. + */ +#ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) +#else + #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) +#endif + +/** + * @brief Generates a topic by suffixing the client identifier with a suffix. + * + * @param[in] bufferName The name of the buffer for the topic. + * @param[in] suffix The suffix to place at the end of the client identifier. + */ +#define GENERATE_TOPIC_WITH_SUFFIX( bufferName, suffix ) \ + char bufferName[ CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ) ] = { 0 }; \ + ( void ) snprintf( bufferName, \ + CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ), \ + "%s%s", \ + _pClientIdentifier, \ + suffix ); + +/* + * Will topic name and length to use for the MQTT API tests. + */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ + +/* + * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. + */ +#define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ +#define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ +#define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. + * Duplicates are sent using an exponential backoff strategy. */ + /*-----------------------------------------------------------*/ /** @@ -95,6 +175,72 @@ static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; */ static IotNetworkInterface_t _networkInterface = { 0 }; +/** + * @brief An MQTT connection to share among the tests. + */ +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/** + * @brief An #IotMqttNetworkInfo_t to use for the re-entrancy tests. + */ +static IotMqttNetworkInfo_t _reentrantNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + +/** + * @brief Network server info to use for the re-entrancy tests. + */ +static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + +/** + * @brief Network credential info to use for the re-entrancy tests. + */ +#if IOT_TEST_SECURED_CONNECTION == 1 + static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +#endif + +#ifdef IOT_TEST_MQTT_SERIALIZER + +/** + * @brief Provide a pointer to a serializer that may be overridden. + */ + static const IotMqttSerializer_t * _pMqttSerializer = NULL; +#else + +/** + * @brief Function pointers to the default MQTT serializers. + */ + static const IotMqttSerializer_t _mqttSerializer = + { + .getPacketType = _IotMqtt_GetPacketType, + .getRemainingLength = _IotMqtt_GetRemainingLength, + .freePacket = _IotMqtt_FreePacket, + .serialize = + { + .connect = _IotMqtt_SerializeConnect, + .publish = _IotMqtt_SerializePublish, + .publishSetDup = _IotMqtt_PublishSetDup, + .puback = _IotMqtt_SerializePuback, + .subscribe = _IotMqtt_SerializeSubscribe, + .unsubscribe = _IotMqtt_SerializeUnsubscribe, + .pingreq = _IotMqtt_SerializePingreq, + .disconnect = _IotMqtt_SerializeDisconnect + }, + .deserialize = + { + .connack = _IotMqtt_DeserializeConnack, + .publish = _IotMqtt_DeserializePublish, + .puback = _IotMqtt_DeserializePuback, + .suback = _IotMqtt_DeserializeSuback, + .unsuback = _IotMqtt_DeserializeUnsuback, + .pingresp = _IotMqtt_DeserializePingresp + } + }; + +/** + * @brief The MQTT serializers to use in these tests. + */ + static const IotMqttSerializer_t * _pMqttSerializer = &_mqttSerializer; +#endif /* ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER */ + /* * Return values of the mocked network functions. */ @@ -104,6 +250,68 @@ static IotNetworkError_t _sendStatus = IOT_NETWORK_SUCCESS; /**< @ static IotNetworkError_t _closeStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkClose. */ static IotNetworkError_t _destroyStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkDestroy. */ +/** + * @brief Filler text to publish. + */ +static const char _pSamplePayload[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" + " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" + " culpa qui officia deserunt mollit anim id est laborum." + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" + " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" + " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" + " culpa qui officia deserunt mollit anim id est laborum."; + +/** + * @brief Length of #_pSamplePayload. + */ +static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; + +/** + * @brief Buffer holding the client identifier used for the tests. + */ +static char _pClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish an MQTT connection. Retry if enabled. + */ +static IotMqttError_t _mqttConnect( const IotMqttNetworkInfo_t * pNetworkInfo, + const IotMqttConnectInfo_t * pConnectInfo, + uint32_t timeoutMs, + IotMqttConnection_t * const pMqttConnection ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + int32_t retryCount = 0; + + for( ; retryCount < IOT_TEST_MQTT_CONNECT_RETRY_COUNT; retryCount++ ) + { + status = IotMqtt_Connect( pNetworkInfo, pConnectInfo, timeoutMs, pMqttConnection ); + + #if ( IOT_TEST_MQTT_CONNECT_RETRY_COUNT > 1 ) + if( ( status == IOT_MQTT_NETWORK_ERROR ) || ( status == IOT_MQTT_TIMEOUT ) ) + { + /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. + * Initially wait until 1100 ms have elapsed since the last connection, then + * increase exponentially. */ + IotClock_SleepMs( 1100 << retryCount ); + } + else + { + break; + } + #endif + } + + return status; +} + /*-----------------------------------------------------------*/ /** @@ -185,6 +393,124 @@ static IotNetworkError_t _networkDestroy( IotNetworkConnection_t pConnection ) /*-----------------------------------------------------------*/ +/** + * @brief Subscription callback function. Checks for valid parameters and unblocks + * the main test thread. + */ +static void _publishReceived( void * pArgument, + IotMqttCallbackParam_t * pPublish ) +{ + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; + + /* If the received messages matches what was sent, unblock the waiting thread. */ + if( ( pPublish->u.message.info.payloadLength == _samplePayloadLength ) && + ( strncmp( pPublish->u.message.info.pPayload, + _pSamplePayload, + _samplePayloadLength ) == 0 ) ) + { + IotSemaphore_Post( pWaitSem ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A subscription callback function that blocks on a semaphore until signaled. + */ +static void _blockingCallback( void * pArgument, + IotMqttCallbackParam_t * pPublish ) +{ + IotSemaphore_t * pSemaphore = ( IotSemaphore_t * ) pArgument; + + /* Silence warnings about unused parameters. */ + ( void ) pPublish; + + /* Wait until signaled. */ + IotSemaphore_Wait( pSemaphore ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Callback that makes additional MQTT API calls. + */ +static void _reentrantCallback( void * pArgument, + IotMqttCallbackParam_t * pOperation ) +{ + bool status = true; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotSemaphore_t * pWaitSemaphores = ( IotSemaphore_t * ) pArgument; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; + + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); + const uint16_t topicLength = ( uint16_t ) strlen( pTopic ); + + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = pTopic; + publishInfo.topicNameLength = topicLength; + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; + publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; + + mqttStatus = IotMqtt_PublishSync( pOperation->mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + + if( mqttStatus == IOT_MQTT_SUCCESS ) + { + status = IotSemaphore_TimedWait( &( pWaitSemaphores[ 0 ] ), + IOT_TEST_MQTT_TIMEOUT_MS ); + } + else + { + status = false; + } + + /* Remove subscription and disconnect. */ + if( status == true ) + { + subscription.pTopicFilter = pTopic; + subscription.topicFilterLength = topicLength; + + mqttStatus = IotMqtt_UnsubscribeAsync( pOperation->mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeOperation ); + + if( mqttStatus == IOT_MQTT_STATUS_PENDING ) + { + /* Disconnect the MQTT connection. */ + IotMqtt_Disconnect( pOperation->mqttConnection, 0 ); + + /* Waiting on an operation whose connection is closed should return + * "Network Error". */ + mqttStatus = IotMqtt_Wait( unsubscribeOperation, + 500 ); + + status = ( mqttStatus == IOT_MQTT_NETWORK_ERROR ); + } + else + { + status = false; + } + } + + /* Disconnect and unblock the main test thread. */ + if( status == true ) + { + IotSemaphore_Post( &( pWaitSemaphores[ 1 ] ) ); + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Serializer override for PUBACK that always fails. */ @@ -201,6 +527,46 @@ static IotMqttError_t _serializePuback( uint16_t packetIdentifier, /*-----------------------------------------------------------*/ +/** + * @brief Wait for a reference count to reach a target value, subject to a timeout. + */ +static bool _waitForCount( IotMutex_t * pMutex, + const int32_t * pReferenceCount, + int32_t target ) +{ + bool status = false; + int32_t referenceCount = 0; + uint32_t sleepCount = 0; + + /* Calculate limit on the number of times to sleep for 100 ms. */ + const uint32_t sleepLimit = ( IOT_TEST_MQTT_TIMEOUT_MS / 100 ) + + ( ( IOT_TEST_MQTT_TIMEOUT_MS % 100 ) != 0 ); + + /* Wait for the reference count to reach the target value. */ + for( sleepCount = 0; sleepCount < sleepLimit; sleepCount++ ) + { + /* Read reference count. */ + IotMutex_Lock( pMutex ); + referenceCount = *pReferenceCount; + IotMutex_Unlock( pMutex ); + + /* Exit if target value is reached. Otherwise, sleep. */ + if( referenceCount == target ) + { + status = true; + break; + } + else + { + IotClock_SleepMs( 100 ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT platform tests. */ @@ -231,6 +597,35 @@ TEST_SETUP( MQTT_Unit_Platform ) _networkInfo.pNetworkInterface = &_networkInterface; + /* Generate a new, unique client identifier based on the time if no client + * identifier is defined. Otherwise, copy the provided client identifier. */ + #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER + ( void ) snprintf( _pClientIdentifier, + CLIENT_IDENTIFIER_MAX_LENGTH, + "iot%llu", + ( long long unsigned int ) IotClock_GetTimeMs() ); + #else + ( void ) strncpy( _pClientIdentifier, + IOT_TEST_MQTT_CLIENT_IDENTIFIER, + CLIENT_IDENTIFIER_MAX_LENGTH ); + #endif + + /* Set the overrides for the default serializers. */ + #ifdef IOT_TEST_MQTT_SERIALIZER + _pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + #endif + + /* Set the MQTT network setup parameters. */ + ( void ) memset( &_reentrantNetworkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + _reentrantNetworkInfo.createNetworkConnection = true; + _reentrantNetworkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; + _reentrantNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + _reentrantNetworkInfo.pMqttSerializer = _pMqttSerializer; + + #if IOT_TEST_SECURED_CONNECTION == 1 + _reentrantNetworkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; + #endif + /* Initialize libraries. */ TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); @@ -244,7 +639,11 @@ TEST_SETUP( MQTT_Unit_Platform ) TEST_TEAR_DOWN( MQTT_Unit_Platform ) { IotMqtt_Cleanup(); + IotTestNetwork_Cleanup(); IotSdk_Cleanup(); + + /* Clear the connection pointer. */ + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; } /*-----------------------------------------------------------*/ @@ -263,6 +662,10 @@ TEST_GROUP_RUNNER( MQTT_Unit_Platform ) RUN_TEST_CASE( MQTT_Unit_Platform, PubackScheduleSerializeFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionScheduleFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, NotifyScheduleFailure ); + RUN_TEST_CASE( MQTT_Unit_Platform, SingleThreaded ); + RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionReferences ); + RUN_TEST_CASE( MQTT_Unit_Platform, SubscribeCompleteReentrancy ); + RUN_TEST_CASE( MQTT_Unit_Platform, IncomingPublishReentrancy ); } /*-----------------------------------------------------------*/ @@ -580,3 +983,355 @@ TEST( MQTT_Unit_Platform, NotifyScheduleFailure ) } /*-----------------------------------------------------------*/ + +/** + * @brief Test that MQTT can work in a single thread without the task pool. + */ +TEST( MQTT_Unit_Platform, SingleThreaded ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_SMALL; + + /* Shut down the system task pool to test if MQTT works without it. */ + IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); + + /* Set the members of the subscription. */ + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; + + /* Set the members of the publish info. */ + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + publishInfo.pPayload = "test"; + publishInfo.payloadLength = 4; + publishInfo.qos = IOT_MQTT_QOS_1; + + if( TEST_PROTECT() ) + { + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); + + /* Add a subscription. */ + status = IotMqtt_SubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Transmit a message with no retry. */ + status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Remove the subscription. */ + status = IotMqtt_UnsubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Re-initialize the system task pool. The task pool must be available to + * send messages with a retry. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); + + /* Transmit a message with a retry. */ + publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; + publishInfo.retryMs = DUP_CHECK_RETRY_MS; + status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + IotTest_MqttMockCleanup(); + } + else + { + /* Re-initialize the system task pool for test tear down. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that subscriptions are properly reference counted. + */ +TEST( MQTT_Unit_Platform, SubscriptionReferences ) +{ + int32_t i = 0; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + _mqttOperation_t * pIncomingPublish[ 3 ] = { NULL }; + _mqttSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink; + IotSemaphore_t waitSem; + _mqttConnection_t * pMqttConnection = NULL; + + /* Create an MQTT connection. */ + pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, + &_networkInfo, + 0 ); + + /* Adjustment to reference count based on keep-alive status. */ + const int32_t keepAliveReference = 1 + ( ( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) ? 1 : 0 ); + + #if ( IOT_STATIC_MEMORY_ONLY == 1 ) && ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS < 3 ) + #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS must be at least 3 for SubscriptionReferences test." + #endif + + /* The MQTT task pool must support at least 3 threads for this test to run successfully. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_SetMaxThreads( IOT_SYSTEM_TASKPOOL, 4 ) ); + + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 3 ) ); + + /* Set the subscription info. */ + subscription.pTopicFilter = "/test"; + subscription.topicFilterLength = 5; + subscription.callback.function = _blockingCallback; + subscription.callback.pCallbackContext = &waitSem; + + /* Add the subscriptions. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( pMqttConnection, + 1, + &subscription, + 1 ) ); + + /* Get the pointer to the subscription in the MQTT connection. */ + pSubscriptionLink = IotListDouble_PeekHead( &( pMqttConnection->subscriptionList ) ); + TEST_ASSERT_NOT_NULL( pSubscriptionLink ); + pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); + TEST_ASSERT_NOT_NULL( pSubscription ); + + /* Create 3 incoming PUBLISH messages that match the subscription. */ + for( i = 0; i < 3; i++ ) + { + pIncomingPublish[ i ] = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); + TEST_ASSERT_NOT_NULL( pIncomingPublish ); + + ( void ) memset( pIncomingPublish[ i ], 0x00, sizeof( _mqttOperation_t ) ); + pIncomingPublish[ i ]->incomingPublish = true; + pIncomingPublish[ i ]->pMqttConnection = pMqttConnection; + pIncomingPublish[ i ]->u.publish.publishInfo.pTopicName = "/test"; + pIncomingPublish[ i ]->u.publish.publishInfo.topicNameLength = 5; + pIncomingPublish[ i ]->u.publish.publishInfo.pPayload = ""; + pIncomingPublish[ i ]->u.publish.pReceivedData = IotMqtt_MallocMessage( 1 ); + + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), + &( pIncomingPublish[ i ]->link ) ); + } + + if( TEST_PROTECT() ) + { + /* Schedule 3 callback invocations for the incoming PUBLISH. */ + for( i = 0; i < 3; i++ ) + { + TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( pMqttConnection ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pIncomingPublish[ i ], + _IotMqtt_ProcessIncomingPublish, + 0 ) ); + } + + /* Wait for the connection reference count to reach 3 (adjusted for possible keep-alive). */ + TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( pMqttConnection->referencesMutex ), + &( pMqttConnection->references ), + 3 + keepAliveReference ) ); + + /* Check that the subscription also has a reference count of 3. */ + TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( pMqttConnection->subscriptionMutex ), + &( pSubscription->references ), + 3 ) ); + + /* Post to the wait semaphore, which unblocks one subscription callback. */ + IotSemaphore_Post( &waitSem ); + + /* Wait for the connection reference count to decrease to 2 (adjusted for + * possible keep-alive). Check that the subscription reference count also + * decreases to 2. */ + TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( pMqttConnection->referencesMutex ), + &( pMqttConnection->references ), + 2 + keepAliveReference ) ); + TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( pMqttConnection->subscriptionMutex ), + &( pSubscription->references ), + 2 ) ); + + /* Shut down the MQTT connection. */ + IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + + /* Post twice to the wait semaphore, which unblocks the remaining blocking + * callbacks. */ + IotSemaphore_Post( &waitSem ); + IotSemaphore_Post( &waitSem ); + + /* Wait for the callbacks to exit. */ + while( IotSemaphore_GetCount( &waitSem ) > 0 ) + { + IotClock_SleepMs( 100 ); + } + } + + IotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that API functions can be invoked from a callback for a completed + * subscription operation. + */ +TEST( MQTT_Unit_Platform, SubscribeCompleteReentrancy ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + + /* Two semaphores are needed for this test: one for incoming PUBLISH and one + * for test completion. */ + IotSemaphore_t pWaitSemaphores[ 2 ]; + + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); + + /* Create the semaphores. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + if( TEST_PROTECT() ) + { + /* Establish the MQTT connection. */ + status = _mqttConnect( &_reentrantNetworkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Subscribe with a completion callback. */ + subscription.qos = IOT_MQTT_QOS_1; + subscription.pTopicFilter = pTopic; + subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + subscription.callback.function = _publishReceived; + subscription.callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); + + callbackInfo.function = _reentrantCallback; + callbackInfo.pCallbackContext = pWaitSemaphores; + + status = IotMqtt_SubscribeAsync( _mqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); + + /* Wait for the reentrant callback to complete. */ + if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); + } + } + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test that API functions can be invoked from a callback for an incoming + * PUBLISH. + */ +TEST( MQTT_Unit_Platform, IncomingPublishReentrancy ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t pSubscription[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + + /* Two semaphores are needed for this test: one for incoming PUBLISH and one + * for test completion. */ + IotSemaphore_t pWaitSemaphores[ 2 ]; + + /* The topics to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic1, "/IncomingPublishReentrancy" ); + GENERATE_TOPIC_WITH_SUFFIX( pTopic2, "/Reentrancy" ); + + /* Create the semaphores. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; + connectInfo.pClientIdentifier = _pClientIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + + if( TEST_PROTECT() ) + { + /* Establish the MQTT connection. */ + status = _mqttConnect( &_reentrantNetworkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Subscribe with to the test topics. */ + pSubscription[ 0 ].qos = IOT_MQTT_QOS_1; + pSubscription[ 0 ].pTopicFilter = pTopic1; + pSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 0 ].pTopicFilter ); + pSubscription[ 0 ].callback.function = _reentrantCallback; + pSubscription[ 0 ].callback.pCallbackContext = pWaitSemaphores; + + pSubscription[ 1 ].qos = IOT_MQTT_QOS_1; + pSubscription[ 1 ].pTopicFilter = pTopic2; + pSubscription[ 1 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 1 ].pTopicFilter ); + pSubscription[ 1 ].callback.function = _publishReceived; + pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); + + status = IotMqtt_SubscribeSync( _mqttConnection, + pSubscription, + 2, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Publish a message to the test topic. */ + publishInfo.qos = IOT_MQTT_QOS_1; + publishInfo.pTopicName = pTopic1; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = _pSamplePayload; + publishInfo.payloadLength = _samplePayloadLength; + publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; + publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; + + status = IotMqtt_PublishSync( _mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Wait for the reentrant callback to complete. */ + if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), + IOT_TEST_MQTT_TIMEOUT_MS ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); + } + } + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); + } + + IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c index 578669c3c8..86e9ba7cde 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c @@ -162,46 +162,6 @@ static void _populateList( void ) /*-----------------------------------------------------------*/ -/** - * @brief Wait for a reference count to reach a target value, subject to a timeout. - */ -static bool _waitForCount( IotMutex_t * pMutex, - const int32_t * pReferenceCount, - int32_t target ) -{ - bool status = false; - int32_t referenceCount = 0; - uint32_t sleepCount = 0; - - /* Calculate limit on the number of times to sleep for 100 ms. */ - const uint32_t sleepLimit = ( IOT_TEST_MQTT_TIMEOUT_MS / 100 ) + - ( ( IOT_TEST_MQTT_TIMEOUT_MS % 100 ) != 0 ); - - /* Wait for the reference count to reach the target value. */ - for( sleepCount = 0; sleepCount < sleepLimit; sleepCount++ ) - { - /* Read reference count. */ - IotMutex_Lock( pMutex ); - referenceCount = *pReferenceCount; - IotMutex_Unlock( pMutex ); - - /* Exit if target value is reached. Otherwise, sleep. */ - if( referenceCount == target ) - { - status = true; - break; - } - else - { - IotClock_SleepMs( 100 ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - /** * @brief A subscription callback function that unlinks its subscription. */ @@ -260,23 +220,6 @@ static void _publishCallback( void * pArgument, /*-----------------------------------------------------------*/ -/** - * @brief A subscription callback function that blocks on a semaphore until signaled. - */ -static void _blockingCallback( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - IotSemaphore_t * pSemaphore = ( IotSemaphore_t * ) pArgument; - - /* Silence warnings about unused parameters. */ - ( void ) pPublish; - - /* Wait until signaled. */ - IotSemaphore_Wait( pSemaphore ); -} - -/*-----------------------------------------------------------*/ - /** * @brief Test group for MQTT subscription tests. */ @@ -345,7 +288,6 @@ TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublish ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublishMultiple ); RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionUnsubscribe ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionReferences ); RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchTrue ); RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchFalse ); } @@ -842,123 +784,6 @@ TEST( MQTT_Unit_Subscription, SubscriptionUnsubscribe ) /*-----------------------------------------------------------*/ -/** - * @brief Tests that subscriptions are properly reference counted. - */ -TEST( MQTT_Unit_Subscription, SubscriptionReferences ) -{ - int32_t i = 0; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - _mqttOperation_t * pIncomingPublish[ 3 ] = { NULL }; - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink; - IotSemaphore_t waitSem; - - /* Adjustment to reference count based on keep-alive status. */ - const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) ? 1 : 0 ); - - #if ( IOT_STATIC_MEMORY_ONLY == 1 ) && ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS < 3 ) - #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS must be at least 3 for SubscriptionReferences test." - #endif - - /* The MQTT task pool must support at least 3 threads for this test to run successfully. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_SetMaxThreads( IOT_SYSTEM_TASKPOOL, 4 ) ); - - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 3 ) ); - - /* Set the subscription info. */ - subscription.pTopicFilter = "/test"; - subscription.topicFilterLength = 5; - subscription.callback.function = _blockingCallback; - subscription.callback.pCallbackContext = &waitSem; - - /* Add the subscriptions. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - &subscription, - 1 ) ); - - /* Get the pointer to the subscription in the MQTT connection. */ - pSubscriptionLink = IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ); - TEST_ASSERT_NOT_NULL( pSubscriptionLink ); - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - TEST_ASSERT_NOT_NULL( pSubscription ); - - /* Create 3 incoming PUBLISH messages that match the subscription. */ - for( i = 0; i < 3; i++ ) - { - pIncomingPublish[ i ] = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - TEST_ASSERT_NOT_NULL( pIncomingPublish ); - - ( void ) memset( pIncomingPublish[ i ], 0x00, sizeof( _mqttOperation_t ) ); - pIncomingPublish[ i ]->incomingPublish = true; - pIncomingPublish[ i ]->pMqttConnection = _pMqttConnection; - pIncomingPublish[ i ]->u.publish.publishInfo.pTopicName = "/test"; - pIncomingPublish[ i ]->u.publish.publishInfo.topicNameLength = 5; - pIncomingPublish[ i ]->u.publish.publishInfo.pPayload = ""; - pIncomingPublish[ i ]->u.publish.pReceivedData = IotMqtt_MallocMessage( 1 ); - - IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), - &( pIncomingPublish[ i ]->link ) ); - } - - if( TEST_PROTECT() ) - { - /* Schedule 3 callback invocations for the incoming PUBLISH. */ - for( i = 0; i < 3; i++ ) - { - TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( _pMqttConnection ) ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pIncomingPublish[ i ], - _IotMqtt_ProcessIncomingPublish, - 0 ) ); - } - - /* Wait for the connection reference count to reach 3 (adjusted for possible keep-alive). */ - TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( _pMqttConnection->referencesMutex ), - &( _pMqttConnection->references ), - 3 + keepAliveReference ) ); - - /* Check that the subscription also has a reference count of 3. */ - TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( _pMqttConnection->subscriptionMutex ), - &( pSubscription->references ), - 3 ) ); - - /* Post to the wait semaphore, which unblocks one subscription callback. */ - IotSemaphore_Post( &waitSem ); - - /* Wait for the connection reference count to decrease to 2 (adjusted for - * possible keep-alive). Check that the subscription reference count also - * decreases to 2. */ - TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( _pMqttConnection->referencesMutex ), - &( _pMqttConnection->references ), - 2 + keepAliveReference ) ); - TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( _pMqttConnection->subscriptionMutex ), - &( pSubscription->references ), - 2 ) ); - - /* Shut down the MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Post twice to the wait semaphore, which unblocks the remaining blocking - * callbacks. */ - IotSemaphore_Post( &waitSem ); - IotSemaphore_Post( &waitSem ); - - /* Wait for the callbacks to exit. */ - while( IotSemaphore_GetCount( &waitSem ) > 0 ) - { - IotClock_SleepMs( 100 ); - } - - /* Clear the MQTT connection flag so test cleanup does not double-free it. */ - _connectionCreated = false; - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - /** * @brief Tests result of matching topic filters and topic names. */ From cc27c6578f05d5764854836c9acfd155584d1f62 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Sun, 9 Feb 2020 16:57:40 -0800 Subject: [PATCH 417/844] chore: Changed managed API to use common code. (#784) * chore: Changed managed API to use common code. Use helper functions in iot_mqtt_serialize.c. --- .../mqtt/include/iot_mqtt_lightweight.h | 90 +- libraries/standard/mqtt/src/iot_mqtt_helper.c | 221 +-- .../mqtt/src/iot_mqtt_lightweight_api.c | 76 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 1193 +---------------- .../mqtt/src/private/iot_mqtt_helper.h | 85 +- 5 files changed, 311 insertions(+), 1354 deletions(-) diff --git a/libraries/standard/mqtt/include/iot_mqtt_lightweight.h b/libraries/standard/mqtt/include/iot_mqtt_lightweight.h index c82948090d..6a38f060b4 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/iot_mqtt_lightweight.h @@ -82,23 +82,23 @@ * the output parameters should be ignored. * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example code below shows how IotMqtt_GetConnectPacketSize() should be used to calculate - * // the size of connect request. + * // the size of connect request. * * IotMqttConnectInfo_t xConnectInfo; * size_t xRemainingLength = 0; * size_t xPacketSize = 0; * IotMqttError_t xResult; - * + * * // start with everything set to zero * memset( ( void * ) &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); - * + * * // Initialize connection info, details are out of scope for this example. * _initializeConnectInfo( &xConnectInfo ); - * // Get size requirement for the connect packet + * // Get size requirement for the connect packet * xResult = IotMqtt_GetConnectPacketSize( &xConnectInfo, &xRemainingLength, &xPacketSize ); * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * @@ -125,14 +125,14 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne * @note pBuffer must be allocated by caller. Use @ref mqtt_function_getconnectpacketsize * to determine the required size. * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example code below shows how IotMqtt_SerializeConnect() should be used to serialize * // MQTT connect packet and send it to MQTT broker. * // Example uses static memory but dynamically allocated memory can be used as well. * // Get size requirement for the connect packet. - * + * * #define mqttexampleSHARED_BUFFER_SIZE 100 * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; * void sendConnectPacket( int xMQTTSocket ) @@ -149,7 +149,7 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); * // Serialize MQTT connect packet into provided buffer * xResult = IotMqtt_SerializeConnect( &xConnectInfo, xRemainingLength, ucSharedBuffer, xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); * IotMqtt_Assert( xSentBytes == xPacketSize ); @@ -178,19 +178,19 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn * the output parameters should be ignored. * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example code below shows how IotMqtt_GetSubscriptionPacketSize() should be used to calculate - * // the size of subscribe or unsubscribe request. + * // the size of subscribe or unsubscribe request. * * IotMqttError_t xResult; * IotMqttSubscription_t xMQTTSubscription[ 1 ]; * size_t xRemainingLength = 0; * size_t xPacketSize = 0; - * + * * // Initialize Subscribe parameters. Details are out of scope for this example. - * // It will involve setting QOS, topic filter and topic filter length. + * // It will involve setting QOS, topic filter and topic filter length. * _initializeSubscribe( xMQTTSubscription ); * * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, @@ -231,7 +231,7 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, * // MQTT Subscribe packet and send it to MQTT broker. * // Example uses static memory, but dynamically allocated memory can be used as well. * // Get size requirement for the MQTT subscribe packet. - * + * * #define mqttexampleSHARED_BUFFER_SIZE 100 * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; * void sendSubscribePacket( int xMQTTSocket ) @@ -241,9 +241,9 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, * size_t xPacketSize = 0; * IotMqttError_t xResult; * size_t xSentBytes = 0; - * + * * // Initialize Subscribe parameters. Details are out of scope for this example. - * // It will involve setting QOS, topic filter and topic filter length. + * // It will involve setting QOS, topic filter and topic filter length. * _initializeSubscribe( xMQTTSubscription ); * // Get size requirement for MQTT Subscribe packet. * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, @@ -253,15 +253,15 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // Make sure the packet size is less than static buffer size. * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * + * * // Serialize subscribe into statically allocated ucSharedBuffer. - * xResult = IotMqtt_SerializeSubscribe( xMQTTSubscription, + * xResult = IotMqtt_SerializeSubscribe( xMQTTSubscription, * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), * xRemainingLength, * &usPacketIdentifier, * ucSharedBuffer, * xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); * IotMqtt_Assert( xSentBytes == xPacketSize ); @@ -291,14 +291,14 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr * * @note pBuffer must be allocated by caller. * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example code below shows how IotMqtt_SerializeUnsubscribe() should be used to serialize * // MQTT unsubscribe packet and send it to MQTT broker. * // Example uses static memory, but dynamically allocated memory can be used as well. * // Get size requirement for the Unsubscribe packet. - * + * * #define mqttexampleSHARED_BUFFER_SIZE 100 * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; * void sendUnsubscribePacket( int xMQTTSocket ) @@ -318,13 +318,13 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr * // Make sure the packet size is less than static buffer size. * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); * // Serialize subscribe into statically allocated ucSharedBuffer. - * xResult = IotMqtt_SerializeUnsubscribe( xMQTTSubscription, + * xResult = IotMqtt_SerializeUnsubscribe( xMQTTSubscription, * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), * xRemainingLength, * &usPacketIdentifier, * ucSharedBuffer, * xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); * IotMqtt_Assert( xSentBytes == xPacketSize ); @@ -353,17 +353,17 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs * the output parameters should be ignored. * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example code below shows how IotMqtt_GetPublishPacketSize() should be used to calculate - * // the size of MQTT publish request. + * // the size of MQTT publish request. * * IotMqttError_t xResult; * IotMqttPublishInfo_t xMQTTPublishInfo; * size_t xRemainingLength = 0; * size_t xPacketSize = 0; - * + * * // Initialize Publish parameters. Details are out of scope for this example. * // It will involve setting QOS, topic filter, topic filter length, payload * // payload length @@ -404,7 +404,7 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPubli * // Example code below shows how IotMqtt_SerializePublish() should be used to serialize * // MQTT Publish packet and send it to broker. * // Example uses static memory, but dynamically allocated memory can be used as well. - * + * * #define mqttexampleSHARED_BUFFER_SIZE 100 * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; * void sendUnsubscribePacket( int xMQTTSocket ) @@ -427,15 +427,15 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPubli * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // Make sure the packet size is less than static buffer size * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * + * * xResult = IotMqtt_SerializePublish( &xMQTTPublishInfo, * xRemainingLength, * &usPacketIdentifier, * &pusPacketIdentifierHigh, * ucSharedBuffer, * xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); * IotMqtt_Assert( xSentBytes == xPacketSize ); @@ -460,17 +460,17 @@ IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishIn * @return returns #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example below shows how IotMqtt_SerializeDisconnect() should be used. - * + * * #define mqttexampleSHARED_BUFFER_SIZE 100 * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; * void sendDisconnectRequest( int xMQTTSocket ) * { * size_t xSentBytes = 0; - * + * * // Disconnect is fixed length packet, therefore there is no need to calculate the size, * // just makes sure static buffer can accommodate disconnect request. * IotMqtt_Assert( MQTT_PACKET_DISCONNECT_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); @@ -483,7 +483,7 @@ IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishIn * xSentByte = send( xMQTTSocket, ( void * ) ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE, 0 ); * IotMqtt_Assert( xSentByte == MQTT_PACKET_DISCONNECT_SIZE ); * } - * + * * @endcode */ /* @[declare_mqtt_serializedisconnect] */ @@ -498,29 +498,29 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, * @param[in] bufferSize Size of the buffer pointed to by pBuffer. * * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER. - * + * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example below shows how IotMqtt_SerializePingReq() should be used. - * + * * #define mqttexampleSHARED_BUFFER_SIZE 100 * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; * void sendPingRequest( int xMQTTSocket ) * { * size_t xSentBytes = 0; - * + * * // PingReq is fixed length packet, therefore there is no need to calculate the size, * // just makes sure static buffer can accommodate Ping request. * IotMqtt_Assert( MQTT_PACKET_PINGREQ_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); * * xResult = IotMqtt_SerializePingreq( ucSharedBuffer, MQTT_PACKET_PINGREQ_SIZE ); * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * + * * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. * xSentByte = send( xMQTTSocket, ( void * ) ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE, 0 ); - * IotMqtt_Assert( xSentByte == MQTT_PACKET_PINGREQ_SIZE); + * IotMqtt_Assert( xSentByte == MQTT_PACKET_PINGREQ_SIZE); * } * @endcode */ @@ -571,7 +571,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, * * return result; * } - * + * * // Example below shows how IotMqtt_GetIncomingMQTTPacketTypeAndLength() is used to extract type * // and length from incoming ping response. * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. @@ -603,10 +603,10 @@ IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * * - #IOT_MQTT_SERVER_REFUSED * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} - * // Example below shows how IotMqtt_DeserializePublish() used to extract contents of incoming + * // Example below shows how IotMqtt_DeserializePublish() used to extract contents of incoming * // Publish. xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. * void processIncomingPublish( int xMQTTSocket ) * { @@ -660,7 +660,7 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ); * - #IOT_MQTT_SERVER_REFUSED * * @note This call is part of serializer API used for implementing light-weight MQTT client. - * + * * Example * @code{c} * // Example below shows how IotMqtt_DeserializeResponse() is used to process unsubscribe ack. @@ -669,8 +669,8 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ); * { * IotMqttError_t xResult; * IotMqttPacketInfo_t xIncomingPacket; - * - * xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); + * + * xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * IotMqtt_Assert( xIncomingPacket.type == MQTT_PACKET_TYPE_UNSUBACK ); * IotMqtt_Assert( xIncomingPacket.remainingLength <= sizeof( ucSharedBuffer ) ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_helper.c b/libraries/standard/mqtt/src/iot_mqtt_helper.c index 651eec9463..e414efa83a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_helper.c +++ b/libraries/standard/mqtt/src/iot_mqtt_helper.c @@ -25,6 +25,9 @@ * @brief Implements helper functions for the MQTT library. */ +/* The config header is always included first. */ +#include "iot_config.h" + /* Standard includes. */ #include #include @@ -80,8 +83,96 @@ /*-----------------------------------------------------------*/ +/* Username for metrics with AWS IoT. */ +#if ( AWS_IOT_MQTT_ENABLE_METRICS == 1 ) || ( DOXYGEN == 1 ) + #ifndef AWS_IOT_METRICS_USERNAME + +/** + * @brief Specify C SDK and version. + */ + #define AWS_IOT_METRICS_USERNAME "?SDK=C&Version=4.0.0" + +/** + * @brief The length of #AWS_IOT_METRICS_USERNAME. + */ + #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1U ) + #endif /* ifndef AWS_IOT_METRICS_USERNAME */ +#endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Encode both connection and metrics username into a buffer, + * if they will fit. + * + * @param[in] pDestination Buffer to write username into. + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[out] pEncodedUserName Whether the username was written into the buffer. + * + * @return Pointer to the end of encoded string, which will be identical to + * `pDestination` if nothing was encoded. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold `pConnectInfo->userNameLength` + + * #AWS_IOT_METRICS_USERNAME_LENGTH bytes to avoid a buffer overflow. + */ + +static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo, + bool * pEncodedUserName ); + +/*-----------------------------------------------------------*/ + +static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo, + bool * pEncodedUserName ) +{ + uint8_t * pBuffer = pDestination; + + #if AWS_IOT_MQTT_ENABLE_METRICS == 1 + const char * pMetricsUserName = AWS_IOT_METRICS_USERNAME; + + /* Only include metrics if it will fit within the encoding + * standard. */ + if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) + { + /* Write the high byte of the combined length. */ + pBuffer[ 0 ] = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + + /* Write the low byte of the combined length. */ + pBuffer[ 1 ] = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + + AWS_IOT_METRICS_USERNAME_LENGTH ) ); + pBuffer += 2; + + /* Write the identity portion of the username. + * As the types of char and uint8_t are of the same size, this memcpy + * is acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBuffer, pConnectInfo->pUserName, pConnectInfo->userNameLength ); + pBuffer += pConnectInfo->userNameLength; + + /* Write the metrics portion of the username. + * As the types of char and uint8_t are of the same size, this memcpy + * is acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBuffer, pMetricsUserName, AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; + + *pEncodedUserName = true; + } + #else /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ + /* Avoid unused variable warnings when AWS_IOT_MQTT_ENABLE_METRICS is set to 0. */ + ( void ) pBuffer; + ( void ) pConnectInfo; + ( void ) pEncodedUserName; + #endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ + + return pBuffer; +} + uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ) + const IotMqttConnectInfo_t * pConnectInfo ) { bool encodedUserName = false; uint8_t * pBuffer = pDestination; @@ -105,42 +196,16 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, * for authentication plus the SDK version string. */ if( pConnectInfo->pUserName != NULL ) { - /* Only include metrics if it will fit within the encoding - * standard. */ - if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) - { - /* Write the high byte of the combined length. */ - *pBuffer = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer++; - - /* Write the low byte of the combined length. */ - *pBuffer = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer++; - - /* Write the identity portion of the username. */ - ( void ) memcpy( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - pBuffer += pConnectInfo->userNameLength; - - /* Write the metrics portion of the username. */ - ( void ) memcpy( pBuffer, - pMetricsUserName, - AWS_IOT_METRICS_USERNAME_LENGTH ); - pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; - - encodedUserName = true; - } + /* Encode username and metrics if they will fit. */ + pBuffer = _encodeUserNameAndMetrics( pBuffer, pConnectInfo, &encodedUserName ); } else { /* The username is not being used for authentication, but * metrics are enabled. */ - pBuffer = _encodeString( pBuffer, - pMetricsUserName, - AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer = _IotMqtt_EncodeString( pBuffer, + pMetricsUserName, + AWS_IOT_METRICS_USERNAME_LENGTH ); encodedUserName = true; } @@ -151,8 +216,8 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) { pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); } return pBuffer; @@ -160,8 +225,8 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, /*-----------------------------------------------------------*/ uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ) + const char * source, + uint16_t sourceLength ) { uint8_t * pBuffer = pDestination; @@ -185,7 +250,7 @@ uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, /*-----------------------------------------------------------*/ uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, - size_t length ) + size_t length ) { uint8_t lengthByte = 0, * pLengthEnd = pDestination; size_t remainingLength = length; @@ -263,8 +328,8 @@ uint16_t _IotMqtt_NextPacketIdentifier( void ) /*-----------------------------------------------------------*/ bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) + size_t * pRemainingLength, + size_t * pPacketSize ) { bool status = true; bool encodedUserName = false; @@ -331,9 +396,9 @@ bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, /*-----------------------------------------------------------*/ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pPacket, - size_t connectPacketSize ) + size_t remainingLength, + uint8_t * pPacket, + size_t connectPacketSize ) { uint8_t connectFlags = 0; uint8_t * pBuffer = pPacket; @@ -426,19 +491,19 @@ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, /* Write the client identifier into the CONNECT packet. */ pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pClientIdentifier, - pConnectInfo->clientIdentifierLength ); + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); /* Write the will topic name and message into the CONNECT packet if provided. */ if( pConnectInfo->pWillInfo != NULL ) { pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pWillInfo->pTopicName, - pConnectInfo->pWillInfo->topicNameLength ); + pConnectInfo->pWillInfo->pTopicName, + pConnectInfo->pWillInfo->topicNameLength ); pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pWillInfo->pPayload, - ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); + pConnectInfo->pWillInfo->pPayload, + ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); } /* Encode the username if there is one or metrics are enabled. */ @@ -448,8 +513,8 @@ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, if( pConnectInfo->pPassword != NULL ) { pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pPassword, - pConnectInfo->passwordLength ); + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); } /* Ensure that the difference between the end and beginning of the buffer @@ -490,11 +555,11 @@ bool _IotMqtt_IncomingPacketValid( uint8_t packetType ) /*-----------------------------------------------------------*/ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t subscribePacketSize ) + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t subscribePacketSize ) { uint16_t packetIdentifier = 0; size_t i = 0; @@ -525,8 +590,8 @@ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscript for( i = 0; i < subscriptionCount; i++ ) { pBuffer = _IotMqtt_EncodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); /* Place the QoS in the SUBSCRIBE packet. */ *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); @@ -544,10 +609,10 @@ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscript /*-----------------------------------------------------------*/ bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ) + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) { bool status = true; size_t i = 0, subscriptionPacketSize = 0; @@ -597,8 +662,8 @@ bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, /*-----------------------------------------------------------*/ bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) + size_t * pRemainingLength, + size_t * pPacketSize ) { bool status = true; size_t publishPacketSize = 0, payloadLimit = 0; @@ -655,11 +720,11 @@ bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pPacket, - size_t publishPacketSize ) + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pPacket, + size_t publishPacketSize ) { uint8_t publishFlags = 0; uint16_t packetIdentifier = 0; @@ -698,8 +763,8 @@ void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, /* The topic name is placed after the "Remaining length". */ pBuffer = _IotMqtt_EncodeString( pBuffer, - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); /* A packet identifier is required for QoS 1 and 2 messages. */ if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) @@ -740,11 +805,11 @@ void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t unsubscribePacketSize ) + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t unsubscribePacketSize ) { uint16_t packetIdentifier = 0; size_t i = 0; @@ -775,8 +840,8 @@ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscri for( i = 0; i < subscriptionCount; i++ ) { pBuffer = _IotMqtt_EncodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); } /* Ensure that the difference between the end and beginning of the buffer @@ -790,7 +855,7 @@ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscri /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ) + IotMqttPublishInfo_t * pOutput ) { IotMqttError_t status = IOT_MQTT_SUCCESS; diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c index 10099b8ab6..9ac5f7302e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c @@ -95,7 +95,7 @@ * */ static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, - IotMqttGetNextByte_t getNextByte ); + IotMqttGetNextByte_t getNextByte ); /** * @brief Deserialize a CONNACK packet. @@ -186,7 +186,7 @@ static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ); * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SERVER_REFUSED, or #IOT_MQTT_BAD_RESPONSE. */ static IotMqttError_t _decodeSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ); + const uint8_t * pStatusStart ); /** * @brief Check the remaining length of incoming Publish against some value for @@ -201,13 +201,13 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. */ static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ); + IotMqttQos_t qos, + size_t qos0Minimum ); /*-----------------------------------------------------------*/ static IotMqttError_t _decodeSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ) + const uint8_t * pStatusStart ) { IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t subscriptionStatus = 0; @@ -226,7 +226,7 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, case 0x01: case 0x02: - /* In some implementations IotLog() maps to C standard printing API + /* In some implementations IotLogDebug() maps to C standard printing API * that need specific primitive types for format specifiers. Also * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ @@ -237,7 +237,7 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, case 0x80: - /* In some implementations IotLog() maps to C standard printing API + /* In some implementations IotLogDebug() maps to C standard printing API * that need specific primitive types for format specifiers. Also * inttypes.h may not be available on some C99 compilers, despite * stdint.h being available. */ @@ -270,7 +270,7 @@ static IotMqttError_t _decodeSubackStatus( size_t statusCount, /*-----------------------------------------------------------*/ static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, - IotMqttGetNextByte_t getNextByte ) + IotMqttGetNextByte_t getNextByte ) { uint8_t encodedByte = 0; size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; @@ -436,7 +436,7 @@ static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ) status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), - pVariableHeader + sizeof( uint16_t ) ); + pVariableHeader + sizeof( uint16_t ) ); } return status; @@ -558,8 +558,8 @@ static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ) /* Sanity checks for topic name length and "Remaining length". The remaining * length must be at least as large as the variable length header. */ status = _checkPublishRemainingLength( pPublish, - pOutput->qos, - pOutput->topicNameLength + sizeof( uint16_t ) ); + pOutput->qos, + pOutput->topicNameLength + sizeof( uint16_t ) ); } if( status == IOT_MQTT_SUCCESS ) @@ -614,8 +614,8 @@ static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ) /*-----------------------------------------------------------*/ IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ) + IotMqttQos_t qos, + size_t qos0Minimum ) { IotMqttError_t status = IOT_MQTT_SUCCESS; @@ -724,9 +724,9 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn else { _IotMqtt_SerializeConnectCommon( pConnectInfo, - remainingLength, - pBuffer, - bufferSize ); + remainingLength, + pBuffer, + bufferSize ); } return status; @@ -758,10 +758,10 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, status = IOT_MQTT_BAD_PARAMETER; } else if( _IotMqtt_SubscriptionPacketSize( type, - pSubscriptionList, - subscriptionCount, - pRemainingLength, - pPacketSize ) == false ) + pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize ) == false ) { IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", @@ -813,11 +813,11 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr else { _IotMqtt_SerializeSubscribeCommon( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); } return status; @@ -846,7 +846,7 @@ IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * { /* Read the remaining length. */ pIncomingPacket->remainingLength = _getRemainingLength( pNetworkConnection, - getNextByte ); + getNextByte ); if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { @@ -937,11 +937,11 @@ IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishIn else { _IotMqtt_SerializePublishCommon( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - bufferSize ); + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + bufferSize ); } return status; @@ -977,11 +977,11 @@ IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubs else { _IotMqtt_SerializeUnsubscribeCommon( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); } return status; @@ -993,7 +993,7 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, size_t bufferSize ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - + if( pBuffer == NULL ) { IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); @@ -1022,7 +1022,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, size_t bufferSize ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - + if( pBuffer == NULL ) { IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 87cac5564a..0bc186eda2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -34,6 +34,7 @@ /* MQTT internal includes. */ #include "private/iot_mqtt_internal.h" +#include "private/iot_mqtt_helper.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -43,340 +44,6 @@ /*-----------------------------------------------------------*/ -/* - * Macros for reading the high and low byte of a 2-byte unsigned int. - */ -#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) /**< @brief Get high byte. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) /**< @brief Get low byte. */ - -/** - * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. - * - * @param[in] ptr A uint8_t* that points to the high byte. - */ -#define UINT16_DECODE( ptr ) \ - ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ - ( ( uint16_t ) ( *( ( ptr ) + 1 ) ) ) ) - -/** - * @brief Macro for setting a bit in a 1-byte unsigned int. - * - * @param[in] x The unsigned int to set. - * @param[in] position Which bit to set. - */ -#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) - -/** - * @brief Macro for checking if a bit is set in a 1-byte unsigned int. - * - * @param[in] x The unsigned int to check. - * @param[in] position Which bit to check. - */ -#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) - -/* - * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT - * packet. - */ -#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ -#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ -#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ -#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ -#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ -#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ -#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ - -/* - * Positions of each flag in the first byte of an MQTT PUBLISH packet's - * fixed header. - */ -#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ -#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ -#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ -#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ - -/** - * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. - */ -#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) - -/** - * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT - * packet is this value. - */ -#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) - -/** - * @brief The minimum remaining length for a QoS PUBLISH. - * - * Includes two bytes for topic name length and one byte for topic name. - */ -#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3 ) - -/** - * @brief The maximum possible size of a CONNECT packet. - * - * All strings in a CONNECT packet are constrained to 2-byte lengths, giving a - * maximum length smaller than the max "Remaining Length" constant above. - */ -#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) - -/* - * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ -#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ - -/* - * Constants relating to PUBLISH and PUBACK packets, defined by MQTT - * 3.1.1 spec. - */ -#define MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ -#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ - -/* - * Constants relating to SUBACK and UNSUBACK packets, defined by MQTT - * 3.1.1 spec. - */ -#define MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid SUBACK packet. */ -#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ - -/* - * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ -#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ - -/* - * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_DISCONNECT_SIZE ( 2U ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ - -/* Username for metrics with AWS IoT. */ -#if ( AWS_IOT_MQTT_ENABLE_METRICS == 1 ) || ( DOXYGEN == 1 ) - #ifndef AWS_IOT_METRICS_USERNAME - -/** - * @brief Specify C SDK and version. - */ - #define AWS_IOT_METRICS_USERNAME "?SDK=C&Version=4.0.0" - -/** - * @brief The length of #AWS_IOT_METRICS_USERNAME. - */ - #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1U ) - #endif /* ifndef AWS_IOT_METRICS_USERNAME */ -#endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Generate and return a 2-byte packet identifier. - * - * This packet identifier will be nonzero. - * - * @return The packet identifier. - */ -static uint16_t _nextPacketIdentifier( void ); - -/** - * @brief Calculate the number of bytes required to encode an MQTT - * "Remaining length" field. - * - * @param[in] length The value of the "Remaining length" to encode. - * - * @return The size of the encoding of length. This is always `1`, `2`, `3`, or `4`. - */ -static size_t _remainingLengthEncodedSize( size_t length ); - -/** - * @brief Encode the "Remaining length" field per MQTT spec. - * - * @param[out] pDestination Where to write the encoded "Remaining length". - * @param[in] length The "Remaining length" to encode. - * - * @return Pointer to the end of the encoded "Remaining length", which is 1-4 - * bytes greater than `pDestination`. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold the encoded "Remaining length" using - * the function #_remainingLengthEncodedSize to avoid buffer overflows. - */ -static uint8_t * _encodeRemainingLength( uint8_t * pDestination, - size_t length ); - -/** - * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. - * - * @param[out] pDestination Where to write the encoded string. - * @param[in] source The string to encode. - * @param[in] sourceLength The length of source. - * - * @return Pointer to the end of the encoded string, which is `sourceLength+2` - * bytes greater than `pDestination`. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer - * overflow. - */ -static uint8_t * _encodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ); - -/** - * @brief Encode a username into a CONNECT packet, if necessary. - * - * @param[out] pDestination Buffer for the CONNECT packet. - * @param[in] pConnectInfo User-provided CONNECT information. - * - * @return Pointer to the end of the encoded string, which will be identical to - * `pDestination` if nothing was encoded. - * - * @warning This function does not check the size of `pDestination`! To avoid a buffer - * overflow, ensure that `pDestination` is large enough to hold `pConnectInfo->userNameLength` - * bytes if a username is supplied, and/or #AWS_IOT_METRICS_USERNAME_LENGTH bytes if - * metrics are enabled. - */ -static uint8_t * _encodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ); - -/** - * @brief Encode both connection and metrics username into a buffer, - * if they will fit. - * - * @param[in] pDestination Buffer to write username into. - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[out] pEncodedUserName Whether the username was written into the buffer. - * - * @return Pointer to the end of encoded string, which will be identical to - * `pDestination` if nothing was encoded. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold `pConnectInfo->userNameLength` + - * #AWS_IOT_METRICS_USERNAME_LENGTH bytes to avoid a buffer overflow. - */ -static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo, - bool * pEncodedUserName ); - -/** - * @brief Calculate the size and "Remaining length" of a CONNECT packet generated - * from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information struct. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` - * otherwise. If this function returns `false`, the output parameters should be ignored. - */ -static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); - -/** - * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated - * from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information struct. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` - * otherwise. If this function returns `false`, the output parameters should be ignored. - */ -static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); - -/** - * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE - * packet generated from the given parameters. - * - * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` - * otherwise. If this function returns `false`, the output parameters should be ignored. - */ -static bool _subscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ); - -/** - * @brief Generate a CONNECT packet from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[in] remainingLength User provided remaining length. - * @param[in, out] pPacket User provided buffer where the CONNECT packet is written. - * @param[in] connectPacketSize Size of the buffer pointed to by `pPacket`. - * - */ -static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pPacket, - size_t connectPacketSize ); - -/** - * @brief Generate a PUBLISH packet from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information. - * @param[in] remainingLength User provided remaining length. - * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. - * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier - * is written. - * @param[in, out] pPacket User provided buffer where the PUBLISH packet is written. - * @param[in] publishPacketSize Size of buffer pointed to by `pPacket`. - * - */ -static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pPacket, - size_t publishPacketSize ); - -/** - * @brief Generate a SUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[in] remainingLength User provided remaining length. - * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. - * @param[in, out] pPacket User provided buffer where the SUBSCRIBE packet is written. - * @param[in] subscribePacketSize Size of the buffer pointed to by `pPacket`. - * - */ -static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t subscribePacketSize ); - -/** - * @brief Generate an UNSUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions to remove. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[in] remainingLength User provided remaining length. - * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. - * @param[in, out] pPacket User provided buffer where the UNSUBSCRIBE packet is written. - * @param[in] unsubscribePacketSize size of the buffer pointed to by `pPacket`. - * - */ -static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t unsubscribePacketSize ); - /** * @brief Decode the status bytes of a SUBACK packet. * @@ -405,19 +72,6 @@ static IotMqttError_t _checkRemainingLength( const _mqttPacket_t * pPublish, IotMqttQos_t qos, size_t qos0Minimum ); -/** - * @brief Process incoming publish flags. - * - * @param[in] publishFlags Incoming publish flags. - * @param[in, out] pOutput Pointer to #IotMqttPublishInfo_t struct. - * where output will be written. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE. - */ - -static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ); - /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -434,712 +88,6 @@ static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, }; #endif -/*-----------------------------------------------------------*/ - -static uint16_t _nextPacketIdentifier( void ) -{ - /* MQTT specifies 2 bytes for the packet identifier; however, operating on - * 32-bit integers is generally faster. */ - static uint32_t nextPacketIdentifier = 1; - - /* The next packet identifier will be greater by 2. This prevents packet - * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet - * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); -} - -/*-----------------------------------------------------------*/ - -static size_t _remainingLengthEncodedSize( size_t length ) -{ - size_t encodedSize = 0; - - /* length should have already been checked before calling this function. */ - IotMqtt_Assert( length <= MQTT_MAX_REMAINING_LENGTH ); - - /* Determine how many bytes are needed to encode length. - * The values below are taken from the MQTT 3.1.1 spec. */ - - /* 1 byte is needed to encode lengths between 0 and 127. */ - if( length < 128U ) - { - encodedSize = 1; - } - /* 2 bytes are needed to encode lengths between 128 and 16,383. */ - else if( length < 16384U ) - { - encodedSize = 2; - } - /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ - else if( length < 2097152U ) - { - encodedSize = 3; - } - /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ - else - { - encodedSize = 4; - } - - return encodedSize; -} - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeRemainingLength( uint8_t * pDestination, - size_t length ) -{ - uint8_t lengthByte = 0, * pLengthEnd = pDestination; - size_t remainingLength = length; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - lengthByte = ( uint8_t ) ( remainingLength % 128U ); - remainingLength = remainingLength / 128U; - - /* Set the high bit of this byte, indicating that there's more data. */ - if( remainingLength > 0U ) - { - UINT8_SET_BIT( lengthByte, 7 ); - } - - /* Output a single encoded byte. */ - *pLengthEnd = lengthByte; - pLengthEnd++; - } while( remainingLength > 0U ); - - return pLengthEnd; -} - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ) -{ - uint8_t * pBuffer = pDestination; - - /* The first byte of a UTF-8 string is the high byte of the string length. */ - *pBuffer = UINT16_HIGH_BYTE( sourceLength ); - pBuffer++; - - /* The second byte of a UTF-8 string is the low byte of the string length. */ - *pBuffer = UINT16_LOW_BYTE( sourceLength ); - pBuffer++; - - /* Copy the string into pBuffer. - * As the types of char and uint8_t are of the same size, this memcpy - * is acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, source, sourceLength ); - - /* Return the pointer to the end of the encoded string. */ - pBuffer += sourceLength; - - return pBuffer; -} - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ) -{ - bool encodedUserName = false; - uint8_t * pBuffer = pDestination; - - /* If metrics are enabled, write the metrics username into the CONNECT packet. - * Otherwise, write the username and password only when not connecting to the - * AWS IoT MQTT server. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " - "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); - - /* Determine if the Connect packet should use a combination of the username - * for authentication plus the SDK version string. */ - if( pConnectInfo->pUserName != NULL ) - { - /* Encode username and metrics if they will fit. */ - pBuffer = _encodeUserNameAndMetrics( pBuffer, pConnectInfo, &encodedUserName ); - } - else - { - /* The username is not being used for authentication, but - * metrics are enabled. */ - pBuffer = _encodeString( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); - - encodedUserName = true; - } - #endif /* #if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ - } - - /* Encode the username if there is one and it hasn't already been done. */ - if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - } - - return pBuffer; -} - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo, - bool * pEncodedUserName ) -{ - uint8_t * pBuffer = pDestination; - - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - const char * pMetricsUserName = AWS_IOT_METRICS_USERNAME; - - /* Only include metrics if it will fit within the encoding - * standard. */ - if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) - { - /* Write the high byte of the combined length. */ - pBuffer[ 0 ] = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - - /* Write the low byte of the combined length. */ - pBuffer[ 1 ] = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer += 2; - - /* Write the identity portion of the username. - * As the types of char and uint8_t are of the same size, this memcpy - * is acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, pConnectInfo->pUserName, pConnectInfo->userNameLength ); - pBuffer += pConnectInfo->userNameLength; - - /* Write the metrics portion of the username. - * As the types of char and uint8_t are of the same size, this memcpy - * is acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, pMetricsUserName, AWS_IOT_METRICS_USERNAME_LENGTH ); - pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; - - *pEncodedUserName = true; - } - #else - /* Avoid unused variable warnings when AWS_IOT_MQTT_ENABLE_METRICS is set to 0. */ - ( void ) pBuffer; - ( void ) pConnectInfo; - ( void ) pEncodedUserName; - #endif - - return pBuffer; -} - -/*-----------------------------------------------------------*/ - -static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - bool encodedUserName = false; - size_t connectPacketSize = 0, remainingLength = 0; - - /* The CONNECT packet will always include a 10-byte variable header. */ - connectPacketSize += 10U; - - /* Add the length of the client identifier if provided. */ - connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); - - /* Add the lengths of the will message and topic name if provided. */ - if( pConnectInfo->pWillInfo != NULL ) - { - connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + - pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); - } - - /* Depending on the status of metrics, add the length of the metrics username - * or the user-provided username. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - connectPacketSize += ( AWS_IOT_METRICS_USERNAME_LENGTH + - ( size_t ) ( pConnectInfo->userNameLength ) + sizeof( uint16_t ) ); - encodedUserName = true; - #endif - } - - /* Add the lengths of the username (if it wasn't already handled above) and - * password, if specified. */ - if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) - { - connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); - } - - if( pConnectInfo->pPassword != NULL ) - { - connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); - } - - /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has - * been calculated. */ - remainingLength = connectPacketSize; - - /* Calculate the full size of the MQTT CONNECT packet by adding the size of - * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ - connectPacketSize += 1U + _remainingLengthEncodedSize( connectPacketSize ); - - /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ - if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) - { - status = false; - } - else - { - *pRemainingLength = remainingLength; - *pPacketSize = connectPacketSize; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - size_t publishPacketSize = 0, payloadLimit = 0; - - /* The variable header of a PUBLISH packet always contains the topic name. */ - publishPacketSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); - - /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte - * packet identifier. */ - if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) - { - publishPacketSize += sizeof( uint16_t ); - } - - /* Calculate the maximum allowed size of the payload for the given parameters. - * This calculation excludes the "Remaining length" encoding, whose size is not - * yet known. */ - payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1U; - - /* Ensure that the given payload fits within the calculated limit. */ - if( pPublishInfo->payloadLength > payloadLimit ) - { - status = false; - } - else - { - /* Add the length of the PUBLISH payload. At this point, the "Remaining length" - * has been calculated. */ - publishPacketSize += pPublishInfo->payloadLength; - - /* Now that the "Remaining length" is known, recalculate the payload limit - * based on the size of its encoding. */ - payloadLimit -= _remainingLengthEncodedSize( publishPacketSize ); - - /* Check that the given payload fits within the size allowed by MQTT spec. */ - if( pPublishInfo->payloadLength > payloadLimit ) - { - status = false; - } - else - { - /* Set the "Remaining length" output parameter and calculate the full - * size of the PUBLISH packet. */ - *pRemainingLength = publishPacketSize; - - publishPacketSize += 1U + _remainingLengthEncodedSize( publishPacketSize ); - *pPacketSize = publishPacketSize; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _subscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - size_t i = 0, subscriptionPacketSize = 0; - - /* Only SUBSCRIBE and UNSUBSCRIBE operations should call this function. */ - IotMqtt_Assert( ( type == IOT_MQTT_SUBSCRIBE ) || ( type == IOT_MQTT_UNSUBSCRIBE ) ); - - /* The variable header of a subscription packet consists of a 2-byte packet - * identifier. */ - subscriptionPacketSize += sizeof( uint16_t ); - - /* Sum the lengths of all subscription topic filters; add 1 byte for each - * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ - for( i = 0; i < subscriptionCount; i++ ) - { - /* Add the length of the topic filter. */ - subscriptionPacketSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); - - /* Only SUBSCRIBE packets include the QoS. */ - if( type == IOT_MQTT_SUBSCRIBE ) - { - subscriptionPacketSize += 1U; - } - } - - /* At this point, the "Remaining length" has been calculated. Return error - * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, - * set the output parameter.*/ - if( subscriptionPacketSize > MQTT_MAX_REMAINING_LENGTH ) - { - status = false; - } - else - { - *pRemainingLength = subscriptionPacketSize; - - /* Calculate the full size of the subscription packet by adding the size of the - * "Remaining length" field plus 1 byte for the "Packet type" field. Set the - * pPacketSize output parameter. */ - subscriptionPacketSize += 1U + _remainingLengthEncodedSize( subscriptionPacketSize ); - *pPacketSize = subscriptionPacketSize; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pPacket, - size_t connectPacketSize ) -{ - uint8_t connectFlags = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) connectPacketSize; - - /* The first byte in the CONNECT packet is the control packet type. */ - *pBuffer = MQTT_PACKET_TYPE_CONNECT; - pBuffer++; - - /* The remaining length of the CONNECT packet is encoded starting from the - * second byte. The remaining length does not include the length of the fixed - * header or the encoding of the remaining length. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable - * header. This string is 4 bytes long. */ - pBuffer = _encodeString( pBuffer, "MQTT", 4 ); - - /* The MQTT protocol version is the second byte of the variable header. */ - *pBuffer = MQTT_VERSION_3_1_1; - pBuffer++; - - /* Set the CONNECT flags based on the given parameters. */ - if( pConnectInfo->cleanSession == true ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); - } - - /* Username and password depend on MQTT mode. */ - if( ( pConnectInfo->pUserName == NULL ) && - ( pConnectInfo->awsIotMqttMode == true ) ) - { - /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server - * never uses a password. */ - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); - #endif - } - else - { - /* Set the flags for username and password if provided. */ - if( pConnectInfo->pUserName != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); - } - - if( pConnectInfo->pPassword != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); - } - } - - /* Set will flag if an LWT is provided. */ - if( pConnectInfo->pWillInfo != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); - - /* Flags only need to be changed for will QoS 1 and 2. */ - switch( pConnectInfo->pWillInfo->qos ) - { - case IOT_MQTT_QOS_1: - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); - break; - - case IOT_MQTT_QOS_2: - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); - break; - - default: - /* Empty default MISRA 16.4 */ - break; - } - - if( pConnectInfo->pWillInfo->retain == true ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); - } - } - - *pBuffer = connectFlags; - pBuffer++; - - /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ - *pBuffer = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); - pBuffer += 2; - - /* Write the client identifier into the CONNECT packet. */ - pBuffer = _encodeString( pBuffer, - pConnectInfo->pClientIdentifier, - pConnectInfo->clientIdentifierLength ); - - /* Write the will topic name and message into the CONNECT packet if provided. */ - if( pConnectInfo->pWillInfo != NULL ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pWillInfo->pTopicName, - pConnectInfo->pWillInfo->topicNameLength ); - - pBuffer = _encodeString( pBuffer, - pConnectInfo->pWillInfo->pPayload, - ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); - } - - /* Encode the username if there is one or metrics are enabled. */ - pBuffer = _encodeUserName( pBuffer, pConnectInfo ); - - /* Encode the password field, if requested by the app. */ - if( pConnectInfo->pPassword != NULL ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pPassword, - pConnectInfo->passwordLength ); - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == connectPacketSize ); - - /* Print out the serialized CONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT CONNECT packet:", pPacket, connectPacketSize ); -} - -/*-----------------------------------------------------------*/ - -static void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pPacket, - size_t publishPacketSize ) -{ - uint8_t publishFlags = 0; - uint16_t packetIdentifier = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) publishPacketSize; - - /* The first byte of a PUBLISH packet contains the packet type and flags. */ - publishFlags = MQTT_PACKET_TYPE_PUBLISH; - - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); - } - else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); - } - else - { - /* Empty else MISRA 15.7 */ - } - - if( pPublishInfo->retain == true ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - } - - *pBuffer = publishFlags; - pBuffer++; - - /* The "Remaining length" is encoded from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* The topic name is placed after the "Remaining length". */ - pBuffer = _encodeString( pBuffer, - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); - - /* A packet identifier is required for QoS 1 and 2 messages. */ - if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) - { - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _nextPacketIdentifier(); - IotMqtt_Assert( packetIdentifier != 0U ); - - /* Set the packet identifier output parameters. */ - *pPacketIdentifier = packetIdentifier; - - if( pPacketIdentifierHigh != NULL ) - { - *pPacketIdentifierHigh = pBuffer; - } - - /* Place the packet identifier into the PUBLISH packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; - } - - /* The payload is placed after the packet identifier. */ - if( pPublishInfo->payloadLength > 0U ) - { - /* This memcpy intentionally copies bytes from a void * buffer into - * a uint8_t * buffer. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); - pBuffer += pPublishInfo->payloadLength; - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == publishPacketSize ); - - /* Print out the serialized PUBLISH packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPacket, publishPacketSize ); -} - -/*-----------------------------------------------------------*/ - -static void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t subscribePacketSize ) -{ - uint16_t packetIdentifier = 0; - size_t i = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) subscribePacketSize; - - /* The first byte in SUBSCRIBE is the packet type. */ - *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; - pBuffer++; - - /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _nextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0U ); - - /* Place the packet identifier into the SUBSCRIBE packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; - - /* Serialize each subscription topic filter and QoS. */ - for( i = 0; i < subscriptionCount; i++ ) - { - pBuffer = _encodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); - - /* Place the QoS in the SUBSCRIBE packet. */ - *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); - pBuffer++; - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == subscribePacketSize ); - - /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pPacket, subscribePacketSize ); -} - -/*-----------------------------------------------------------*/ - -static void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t unsubscribePacketSize ) -{ - uint16_t packetIdentifier = 0; - size_t i = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) unsubscribePacketSize; - - /* The first byte in UNSUBSCRIBE is the packet type. */ - *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; - pBuffer++; - - /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _nextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0U ); - - /* Place the packet identifier into the UNSUBSCRIBE packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; - - /* Serialize each subscription topic filter. */ - for( i = 0; i < subscriptionCount; i++ ) - { - pBuffer = _encodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == unsubscribePacketSize ); - - /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pPacket, unsubscribePacketSize ); -} /*-----------------------------------------------------------*/ @@ -1256,75 +204,6 @@ static IotMqttError_t _checkRemainingLength( const _mqttPacket_t * pPublish, return status; } -/*-----------------------------------------------------------*/ - -static IotMqttError_t _processIncomingPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( pOutput == NULL ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - /* Check for QoS 2. */ - else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) - { - /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad QoS: 3." ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - pOutput->qos = IOT_MQTT_QOS_2; - } - } - /* Check for QoS 1. */ - else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - pOutput->qos = IOT_MQTT_QOS_1; - } - /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ - else - { - pOutput->qos = IOT_MQTT_QOS_0; - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS is %d.", pOutput->qos ); - - /* Parse the Retain bit. */ - pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Retain bit is %d.", pOutput->retain ); - - /* Parse the DUP bit. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "DUP is 1." ); - } - else - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "DUP is 0." ); - } - } - - return status; -} /*-----------------------------------------------------------*/ @@ -1381,7 +260,7 @@ size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, /* Check that the decoded remaining length conforms to the MQTT specification. */ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) { - expectedSize = _remainingLengthEncodedSize( remainingLength ); + expectedSize = _IotMqtt_RemainingLengthEncodedSize( remainingLength ); if( bytesDecoded != expectedSize ) { @@ -1409,7 +288,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _connectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) + if( _IotMqtt_ConnectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) { IotLogError( "Connect packet length exceeds %lu, which is the maximum" " size allowed by MQTT 3.1.1.", @@ -1440,7 +319,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI *pConnectPacket = pBuffer; *pPacketSize = connectPacketSize; - _serializeConnect( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); + _IotMqtt_SerializeConnectCommon( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); } } @@ -1575,7 +454,7 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _publishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) + if( _IotMqtt_PublishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) { IotLogError( "Publish packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", @@ -1607,12 +486,12 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI *pPacketSize = publishPacketSize; /* Serialize publish into buffer pointed to by pBuffer */ - _serializePublish( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - publishPacketSize ); + _IotMqtt_SerializePublishCommon( pPublishInfo, + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + publishPacketSize ); } } @@ -1634,7 +513,7 @@ void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, IotMqtt_Assert( pNewPacketIdentifier != NULL ); /* Generate a new packet identifier. */ - newPacketIdentifier = _nextPacketIdentifier(); + newPacketIdentifier = _IotMqtt_NextPacketIdentifier(); IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", UINT16_DECODE( pPacketIdentifierHigh ), @@ -1666,7 +545,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) /* The flags are the lower 4 bits of the first byte in PUBLISH. */ publishFlags = pPublish->type; - status = _processIncomingPublishFlags( publishFlags, pOutput ); + status = _IotMqtt_ProcessIncomingPublishFlags( publishFlags, pOutput ); if( status == IOT_MQTT_SUCCESS ) { @@ -1845,11 +724,11 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _subscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &subscribePacketSize ) == false ) + if( _IotMqtt_SubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &subscribePacketSize ) == false ) { IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", @@ -1880,12 +759,12 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc *pPacketSize = subscribePacketSize; /* Serialize subscribe into buffer pointed to by pBuffer */ - _serializeSubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - subscribePacketSize ); + _IotMqtt_SerializeSubscribeCommon( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + subscribePacketSize ); } } @@ -1955,11 +834,11 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _subscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &unsubscribePacketSize ) == false ) + if( _IotMqtt_SubscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &unsubscribePacketSize ) == false ) { IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", @@ -1990,12 +869,12 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub *pPacketSize = unsubscribePacketSize; /* Serialize unsubscribe into buffer pointed to by pBuffer */ - _serializeUnsubscribe( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - unsubscribePacketSize ); + _IotMqtt_SerializeUnsubscribeCommon( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + unsubscribePacketSize ); } } @@ -2058,6 +937,7 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, size_t * pPacketSize ) { /* PINGREQ packets are always the same. */ + /* It is not necessary to make this array const. Since there are other * types of MQTT packets that are not constant, this array would be * cast to remove the const qualifier anyway. */ @@ -2118,6 +998,7 @@ IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, size_t * pPacketSize ) { /* DISCONNECT packets are always the same. */ + /* It is not necessary to make this array const. Since there are other * types of MQTT packets that are not constant, this array would be * cast to remove the const qualifier anyway. */ diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h index 551476f0d8..6c11f79605 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h @@ -25,8 +25,8 @@ * @brief Implements internal helper functions for the MQTT library. */ -#ifndef IOT_MQTT_SERIALIZE_INTERNAL_H_ -#define IOT_MQTT_SERIALIZE_INTERNAL_H_ +#ifndef IOT_MQTT_HELPER_H_ +#define IOT_MQTT_HELPER_H_ /* Standard includes. */ #include @@ -71,7 +71,18 @@ * @param[in] x The unsigned int to check. * @param[in] position Which bit to check. */ -#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) +#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) &( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_MQTT_ENABLE_METRICS + #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) +#endif +/** @endcond */ /*-----------------------------------------------------------*/ @@ -90,8 +101,8 @@ * overflow. */ uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ); + const char * source, + uint16_t sourceLength ); /** * @brief Calculate the number of bytes required to encode an MQTT @@ -114,10 +125,10 @@ size_t _IotMqtt_RemainingLengthEncodedSize( size_t length ); * * @warning This function does not check the size of `pDestination`! Ensure that * `pDestination` is large enough to hold the encoded "Remaining length" using - * the function #_remainingLengthEncodedSize to avoid buffer overflows. + * the function #_IotMqtt_RemainingLengthEncodedSize to avoid buffer overflows. */ uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, - size_t length ); + size_t length ); /** * @brief Calculate the size and "Remaining length" of a CONNECT packet generated @@ -131,8 +142,8 @@ uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, * otherwise. If this function returns `false`, the output parameters should be ignored. */ bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); + size_t * pRemainingLength, + size_t * pPacketSize ); /** @@ -145,9 +156,9 @@ bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, * */ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pPacket, - size_t connectPacketSize ); + size_t remainingLength, + uint8_t * pPacket, + size_t connectPacketSize ); /** * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE @@ -163,10 +174,10 @@ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, * otherwise. If this function returns `false`, the output parameters should be ignored. */ bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ); + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ); /** * @brief Generate a SUBSCRIBE packet from the given parameters. @@ -180,11 +191,11 @@ bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, * */ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t subscribePacketSize ); + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t subscribePacketSize ); /** * @brief Generate an UNSUBSCRIBE packet from the given parameters. @@ -198,11 +209,11 @@ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscript * */ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t unsubscribePacketSize ); + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pPacket, + size_t unsubscribePacketSize ); /** * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated @@ -216,8 +227,8 @@ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscri * otherwise. If this function returns `false`, the output parameters should be ignored. */ bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); + size_t * pRemainingLength, + size_t * pPacketSize ); /** * @brief Generate a PUBLISH packet from the given parameters. @@ -232,11 +243,11 @@ bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, * */ void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pPacket, - size_t publishPacketSize ); + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pPacket, + size_t publishPacketSize ); /** * @brief Check if an incoming packet type is valid. @@ -267,7 +278,7 @@ uint16_t _IotMqtt_NextPacketIdentifier( void ); */ IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ); + IotMqttPublishInfo_t * pOutput ); /** * @brief Encode a username into a CONNECT packet, if necessary. @@ -278,6 +289,6 @@ IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, * @return Pointer to the end of the encoded string, which will be identical to * `pDestination` if nothing was encoded. */ - uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ); -#endif \ No newline at end of file +uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo ); +#endif /* ifndef IOT_MQTT_HELPER_H_ */ From 220d56e15932d521af25fb5b948e6974fec00342 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Mon, 10 Feb 2020 10:11:06 -0800 Subject: [PATCH 418/844] Fix platform test failure (#783) --- libraries/standard/mqtt/test/iot_tests_mqtt.c | 1 + .../mqtt/test/unit/iot_tests_mqtt_platform.c | 49 +++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/libraries/standard/mqtt/test/iot_tests_mqtt.c b/libraries/standard/mqtt/test/iot_tests_mqtt.c index f4e83dda5e..ef5762be01 100644 --- a/libraries/standard/mqtt/test/iot_tests_mqtt.c +++ b/libraries/standard/mqtt/test/iot_tests_mqtt.c @@ -52,6 +52,7 @@ void RunMqttTests( bool disableNetworkTests, bool disableLongTests ) if( disableNetworkTests == false ) { + RUN_TEST_GROUP( MQTT_System_Platform ); RUN_TEST_GROUP( MQTT_System ); } } diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index c0372d8eba..5d35d4f733 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -572,6 +572,11 @@ static bool _waitForCount( IotMutex_t * pMutex, */ TEST_GROUP( MQTT_Unit_Platform ); +/** + * @brief Test group for MQTT platform tests requiring the network. + */ +TEST_GROUP( MQTT_System_Platform ); + /*-----------------------------------------------------------*/ /** @@ -597,6 +602,18 @@ TEST_SETUP( MQTT_Unit_Platform ) _networkInfo.pNetworkInterface = &_networkInterface; + /* Initialize libraries. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for MQTT system platform tests. + */ +TEST_SETUP( MQTT_System_Platform ) +{ /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER @@ -628,6 +645,7 @@ TEST_SETUP( MQTT_Unit_Platform ) /* Initialize libraries. */ TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + TEST_ASSERT_EQUAL( IOT_NETWORK_SUCCESS, IotTestNetwork_Init() ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); } @@ -637,6 +655,20 @@ TEST_SETUP( MQTT_Unit_Platform ) * @brief Test tear down for MQTT platform tests. */ TEST_TEAR_DOWN( MQTT_Unit_Platform ) +{ + IotMqtt_Cleanup(); + IotSdk_Cleanup(); + + /* Clear the connection pointer. */ + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for MQTT system platform tests. + */ +TEST_TEAR_DOWN( MQTT_System_Platform ) { IotMqtt_Cleanup(); IotTestNetwork_Cleanup(); @@ -664,8 +696,17 @@ TEST_GROUP_RUNNER( MQTT_Unit_Platform ) RUN_TEST_CASE( MQTT_Unit_Platform, NotifyScheduleFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, SingleThreaded ); RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionReferences ); - RUN_TEST_CASE( MQTT_Unit_Platform, SubscribeCompleteReentrancy ); - RUN_TEST_CASE( MQTT_Unit_Platform, IncomingPublishReentrancy ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for MQTT system platform tests. + */ +TEST_GROUP_RUNNER( MQTT_System_Platform ) +{ + RUN_TEST_CASE( MQTT_System_Platform, SubscribeCompleteReentrancy ); + RUN_TEST_CASE( MQTT_System_Platform, IncomingPublishReentrancy ); } /*-----------------------------------------------------------*/ @@ -1172,7 +1213,7 @@ TEST( MQTT_Unit_Platform, SubscriptionReferences ) * @brief Test that API functions can be invoked from a callback for a completed * subscription operation. */ -TEST( MQTT_Unit_Platform, SubscribeCompleteReentrancy ) +TEST( MQTT_System_Platform, SubscribeCompleteReentrancy ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; @@ -1247,7 +1288,7 @@ TEST( MQTT_Unit_Platform, SubscribeCompleteReentrancy ) * @brief Test that API functions can be invoked from a callback for an incoming * PUBLISH. */ -TEST( MQTT_Unit_Platform, IncomingPublishReentrancy ) +TEST( MQTT_System_Platform, IncomingPublishReentrancy ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; From 73c0658cee9b1b8de128be67f66470243b156d7f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 10 Feb 2020 10:20:08 -0800 Subject: [PATCH 419/844] Only send PINGREQ when connection is idle (#778) --- libraries/standard/mqtt/lexicon.txt | 1 + .../standard/mqtt/src/iot_mqtt_operation.c | 85 ++++++++++++++----- .../mqtt/src/private/iot_mqtt_internal.h | 1 + .../mqtt/test/unit/iot_tests_mqtt_api.c | 14 +++ .../mqtt/test/unit/iot_tests_mqtt_platform.c | 16 +++- 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 12af91be60..4b33cc3627 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -85,6 +85,7 @@ jobreference jobstorage keepalivems keepaliveseconds +lastmessagetime lu lwt malloc diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index e9a8ab3551..bfda533987 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -554,6 +554,11 @@ static bool _sendPingRequest( _mqttConnection_t * pMqttConnection ) } else { + /* Update the timestamp of the last message on successful transmission. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pMqttConnection->lastMessageTime = IotClock_GetTimeMs(); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + /* Assume the keep-alive will fail. The network receive callback will * clear the failure flag upon receiving a PINGRESP. */ swapStatus = Atomic_CompareAndSwap_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), @@ -814,6 +819,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { bool status = true; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; + uint32_t scheduleDelay = 0; + uint64_t elapsedTime = 0; /* Retrieve the MQTT connection from the context. */ _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; @@ -838,7 +845,32 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, if( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) { - status = _sendPingRequest( pMqttConnection ); + /* Only send the PINGREQ if the keep-alive period has elapsed since the connection + * was last used. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + elapsedTime = IotClock_GetTimeMs() - pMqttConnection->lastMessageTime; + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + + if( elapsedTime < ( uint64_t ) pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs ) + { + /* In some implementations IotLogDebug() maps to C standard printing API + * that need specific primitive types for format specifiers. Also + * inttypes.h may not be available on some C99 compilers, despite + * stdint.h being available. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ + IotLogDebug( "(MQTT connection %p) Connection was last used %llu ms ago, which " + "is less than keep-alive period %lu ms. PINGREQ will not be sent.", + pMqttConnection, + ( unsigned long long ) elapsedTime, + ( unsigned long ) pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs ); + + /* Schedule the next keep-alive job one keep-alive period after the last packet was sent. */ + scheduleDelay = pPingreqOperation->u.operation.periodic.ping.keepAliveMs - ( ( uint32_t ) elapsedTime ); + } + else + { + status = _sendPingRequest( pMqttConnection ); + } } else { @@ -863,10 +895,16 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, } } - /* When a PINGREQ is successfully sent, reschedule this job to check for a - * response shortly. */ + /* Reschedule this job. When a PINGREQ is sent, schedule a check for PINGRESP. + * When PINGREQ is not sent (because the connection was recently used) schedule + * another PINGREQ after the keep-alive period. */ if( status == true ) { + if( scheduleDelay == 0 ) + { + scheduleDelay = pPingreqOperation->u.operation.periodic.ping.nextPeriodMs; + } + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); /* Re-create the keep-alive job for rescheduling. This should never fail. */ @@ -876,9 +914,10 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, &pKeepAliveJob ); IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); + IotMqtt_Assert( scheduleDelay > 0 ); taskPoolStatus = IotTaskPool_ScheduleDeferred( pTaskPool, pKeepAliveJob, - pPingreqOperation->u.operation.periodic.ping.nextPeriodMs ); + scheduleDelay ); if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { @@ -889,7 +928,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, /* coverity[misra_c_2012_directive_4_6_violation] */ IotLogDebug( "(MQTT connection %p) Next keep-alive job in %lu ms.", pMqttConnection, - ( unsigned long ) pPingreqOperation->u.operation.periodic.ping.nextPeriodMs ); + ( unsigned long ) scheduleDelay ); } else { @@ -1006,25 +1045,33 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { pOperation->u.operation.status = IOT_MQTT_NETWORK_ERROR; } - /* DISCONNECT operations are considered successful upon successful transmission. */ - else if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) + else { - /* DISCONNECT operations are always waitable. */ - IotMqtt_Assert( waitable == true ); + /* Update the timestamp of the last message on successful transmission. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + pMqttConnection->lastMessageTime = IotClock_GetTimeMs(); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - pOperation->u.operation.status = IOT_MQTT_SUCCESS; - } - /* Non-waitable operations with no callback are also considered successful. */ - else if( waitable == false ) - { - if( pOperation->u.operation.notify.callback.function == NULL ) + if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) { + /* DISCONNECT operations are always waitable. */ + IotMqtt_Assert( waitable == true ); + + /* DISCONNECT operations are considered successful upon successful transmission. */ pOperation->u.operation.status = IOT_MQTT_SUCCESS; } - } - else - { - /* Empty else MISRA 15.7 */ + /* Non-waitable operations with no callback are also considered successful. */ + else if( waitable == false ) + { + if( pOperation->u.operation.notify.callback.function == NULL ) + { + pOperation->u.operation.status = IOT_MQTT_SUCCESS; + } + } + else + { + /* Empty else MISRA 15.7 */ + } } } diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index fc727c5726..ccfb802839 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -399,6 +399,7 @@ typedef struct _mqttConnection IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ + uint64_t lastMessageTime; /**< @brief When the most recent message was transmitted. */ _mqttOperation_t pingreq; /**< @brief Operation used for MQTT keep-alive. */ } _mqttConnection_t; diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 81e991f4b2..cb75f97f04 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -1835,6 +1835,8 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) */ TEST( MQTT_Unit_API, KeepAlivePeriodic ) { + IotTaskPoolJobStatus_t cancelStatus = IOT_TASKPOOL_STATUS_UNDEFINED; + /* The expected disconnect reason for this test's disconnect callback. */ IotMqttDisconnectReason_t expectedReason = IOT_MQTT_KEEP_ALIVE_TIMEOUT; @@ -1858,7 +1860,19 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) 1 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); + /* Check that PINGREQ is not sent when the connection was used recently. */ + _pMqttConnection->lastMessageTime = IotClock_GetTimeMs(); + _IotMqtt_ProcessKeepAlive( IOT_SYSTEM_TASKPOOL, _pMqttConnection->pingreq.job, _pMqttConnection ); + TEST_ASSERT_EQUAL_INT( 0, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); + TEST_ASSERT_EQUAL_INT( _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs, + _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ); + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, + _pMqttConnection->pingreq.job, + &cancelStatus ) ); + TEST_ASSERT_EQUAL( IOT_TASKPOOL_STATUS_DEFERRED, cancelStatus ); + /* Set a short keep-alive interval so this test runs faster. */ + _pMqttConnection->lastMessageTime = 0; _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index 5d35d4f733..272baedb59 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -85,7 +85,13 @@ * @brief Length of an arbitrary packet for testing. A buffer will be allocated * for it, but its contents don't matter. */ -#define PACKET_LENGTH ( 1 ) +#define PACKET_LENGTH ( 1 ) + +/** + * @brief A short keep-alive interval to use for the keep-alive tests. It may be + * shorter than the minimum 1 second specified by the MQTT spec. + */ +#define SHORT_KEEP_ALIVE_MS ( 100 ) /** * @cond DOXYGEN_IGNORE @@ -809,9 +815,15 @@ TEST( MQTT_Unit_Platform, PingreqSendFailure ) { _mqttConnection_t * pMqttConnection = NULL; - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); + pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 1 ); TEST_ASSERT_NOT_NULL( pMqttConnection ); + /* Set a short keep alive period and sleep for at least that period. Otherwise, + * the PINGREQ will not be sent. */ + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; + pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; + IotClock_SleepMs( SHORT_KEEP_ALIVE_MS * 2 ); + _sendStatus = IOT_NETWORK_FAILURE; _IotMqtt_ProcessKeepAlive( IOT_SYSTEM_TASKPOOL, pMqttConnection->pingreq.job, pMqttConnection ); } From c38273ec4fd4a2f16d2ed1363e6ee48a539db92c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 10 Feb 2020 15:29:53 -0800 Subject: [PATCH 420/844] Complete coverage of iot_mqtt_helper.c (#786) --- libraries/standard/mqtt/src/iot_mqtt_helper.c | 22 ++--- .../mqtt/src/iot_mqtt_lightweight_api.c | 14 +-- .../mqtt/test/unit/iot_tests_mqtt_api.c | 85 +++++++++++++++++-- .../mqtt/test/unit/iot_tests_mqtt_platform.c | 74 ++++++++++++++++ .../mqtt/test/unit/iot_tests_mqtt_receive.c | 27 +++--- 5 files changed, 182 insertions(+), 40 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_helper.c b/libraries/standard/mqtt/src/iot_mqtt_helper.c index e414efa83a..3ca67d7570 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_helper.c +++ b/libraries/standard/mqtt/src/iot_mqtt_helper.c @@ -134,7 +134,7 @@ static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, /* Only include metrics if it will fit within the encoding * standard. */ - if( ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( uint16_t ) ( UINT16_MAX ) ) ) + if( ( ( size_t ) pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( size_t ) ( UINT16_MAX ) ) ) { /* Write the high byte of the combined length. */ pBuffer[ 0 ] = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + @@ -171,6 +171,8 @@ static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, return pBuffer; } +/*-----------------------------------------------------------*/ + uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, const IotMqttConnectInfo_t * pConnectInfo ) { @@ -353,14 +355,16 @@ bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - connectPacketSize += ( AWS_IOT_METRICS_USERNAME_LENGTH + - ( size_t ) ( pConnectInfo->userNameLength ) + sizeof( uint16_t ) ); - encodedUserName = true; + if( ( ( size_t ) pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( size_t ) ( UINT16_MAX ) ) ) + { + connectPacketSize += ( AWS_IOT_METRICS_USERNAME_LENGTH + + ( size_t ) ( pConnectInfo->userNameLength ) + sizeof( uint16_t ) ); + + encodedUserName = true; + } #endif } - /* Add the lengths of the username (if it wasn't already handled above) and - * password, if specified. */ if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) { connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); @@ -859,12 +863,8 @@ IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, { IotMqttError_t status = IOT_MQTT_SUCCESS; - if( pOutput == NULL ) - { - status = IOT_MQTT_BAD_RESPONSE; - } /* Check for QoS 2. */ - else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) { /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c index 9ac5f7302e..bcf328c67a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c @@ -763,7 +763,7 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, pRemainingLength, pPacketSize ) == false ) { - IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " + IotLogError( "Subscription packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; @@ -1089,13 +1089,17 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) /* Internal MQTT packet structure */ IotMqttPacketInfo_t mqttPacket; - if( ( pMqttPacket == NULL ) || ( pMqttPacket->pRemainingData == NULL ) ) + if( pMqttPacket == NULL ) { - IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer or NULL pRemainingLength." ); + IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer." ); status = IOT_MQTT_BAD_PARAMETER; } - - if( status == IOT_MQTT_SUCCESS ) + else if( pMqttPacket->pRemainingData == NULL ) + { + IotLogError( "IotMqtt_DeserializeResponse() called with NULL pRemainingLength." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else { /* Set internal mqtt packet parameters. */ ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( IotMqttPacketInfo_t ) ); diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index cb75f97f04..1db453d787 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -37,6 +37,9 @@ /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" +/* MQTT protocol include. */ +#include "iot_mqtt_protocol.h" + /* Platform layer includes. */ #include "platform/iot_clock.h" #include "platform/iot_threads.h" @@ -132,12 +135,12 @@ * @brief Length of an arbitrary packet for testing. A buffer will be allocated * for it, but its contents don't matter. */ -#define PACKET_LENGTH ( 32 ) +#define PACKET_LENGTH ( 32 ) /** * @brief How many operations to use for the OperationFindMatch test. */ -#define OPERATION_COUNT (2) +#define OPERATION_COUNT ( 2 ) /*-----------------------------------------------------------*/ @@ -2000,8 +2003,9 @@ TEST( MQTT_Unit_API, GetConnectPacketSizeChecks ) TEST( MQTT_Unit_API, SerializeConnectChecks ) { IotMqttConnectInfo_t connectInfo; + IotMqttPublishInfo_t willInfo; size_t remainingLength = 0; - uint8_t buffer[ 20 ]; + uint8_t buffer[ 70 ]; size_t bufferSize = sizeof( buffer ); size_t packetSize = bufferSize; IotMqttError_t status = IOT_MQTT_SUCCESS; @@ -2018,12 +2022,31 @@ TEST( MQTT_Unit_API, SerializeConnectChecks ) status = IotMqtt_SerializeConnect( &connectInfo, 120, buffer, packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + /* Connect packet too large. */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = UINT16_MAX; + connectInfo.pPassword = ""; + connectInfo.passwordLength = UINT16_MAX; + connectInfo.pUserName = ""; + connectInfo.userNameLength = UINT16_MAX; + willInfo.pTopicName = TEST_TOPIC_NAME; + willInfo.topicNameLength = UINT16_MAX; + willInfo.payloadLength = UINT16_MAX + 2; + connectInfo.pWillInfo = &willInfo; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + /* Good case succeeds */ /* Calculate packet size. */ memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); connectInfo.cleanSession = true; connectInfo.pClientIdentifier = "TEST"; connectInfo.clientIdentifierLength = 4; + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 4; status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); /* Make sure buffer has enough space */ @@ -2032,6 +2055,36 @@ TEST( MQTT_Unit_API, SerializeConnectChecks ) status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Encode user name in AWS mode. */ + connectInfo.awsIotMqttMode = true; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Serialize connect with LWT. */ + ( void ) memset( &willInfo, 0x00, sizeof( IotMqttPublishInfo_t ) ); + willInfo.retain = true; + willInfo.qos = IOT_MQTT_QOS_1; + willInfo.pTopicName = "test"; + willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); + willInfo.pPayload = "test"; + willInfo.payloadLength = ( uint16_t ) strlen( willInfo.pPayload ); + connectInfo.pWillInfo = &willInfo; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + willInfo.qos = IOT_MQTT_QOS_2; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + /* For this example, IotMqtt_GetConnectPacketSize() will return * packetSize = remainingLength +2 (two byte fixed header). * Make sure IotMqtt_SerializeConnect() @@ -2299,24 +2352,38 @@ TEST( MQTT_Unit_API, GetPublishPacketSizeChecks ) /* Verify bad paramameters fail. */ status = IotMqtt_GetPublishPacketSize( NULL, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); status = IotMqtt_GetPublishPacketSize( &publishInfo, NULL, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, NULL ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Empty topic must fail. */ memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + /* Packet too large. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH; + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH - publishInfo.topicNameLength - sizeof( uint16_t ) - 1; + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Good case succeeds. */ publishInfo.pTopicName = "/test/topic"; publishInfo.topicNameLength = sizeof( "/test/topic" ); + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); } /*-----------------------------------------------------------*/ @@ -2376,6 +2443,8 @@ TEST( MQTT_Unit_API, SerializePublishChecks ) TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); /* Good case succeeds */ + publishInfo.qos = IOT_MQTT_QOS_2; + publishInfo.retain = true; publishInfo.pTopicName = "/test/topic"; publishInfo.topicNameLength = sizeof( "/test/topic" ); /* Calculate exact packet size and remaining length. */ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index 272baedb59..01eafef97a 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -41,6 +41,10 @@ /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" +/* MQTT lightweight includes. */ +#include "iot_mqtt_protocol.h" +#include "iot_mqtt_lightweight.h" + /* Allow these tests to manipulate the task pool and create failures by including * the task pool internal header. */ #undef LIBRARY_LOG_LEVEL @@ -702,6 +706,8 @@ TEST_GROUP_RUNNER( MQTT_Unit_Platform ) RUN_TEST_CASE( MQTT_Unit_Platform, NotifyScheduleFailure ); RUN_TEST_CASE( MQTT_Unit_Platform, SingleThreaded ); RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionReferences ); + RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionListTooLarge ); + RUN_TEST_CASE( MQTT_Unit_Platform, LongUserName ); } /*-----------------------------------------------------------*/ @@ -1221,6 +1227,74 @@ TEST( MQTT_Unit_Platform, SubscriptionReferences ) /*-----------------------------------------------------------*/ +/** + * @brief Test the behavior when the subscription list exceeds the size of an MQTT + * packet. Requires a large amount of memory not available on smaller systems. + */ +TEST( MQTT_Unit_Platform, SubscriptionListTooLarge ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + size_t subscriptionCount = MQTT_MAX_REMAINING_LENGTH / UINT16_MAX + 1, i = 0; + size_t remainingLength = 0, packetSize = 0; + IotMqttSubscription_t * pSubscriptionList = IotTest_Malloc( subscriptionCount * sizeof( IotMqttSubscription_t ) ); + + TEST_ASSERT_NOT_NULL( pSubscriptionList ); + ( void ) memset( pSubscriptionList, 0x00, subscriptionCount * sizeof( IotMqttSubscription_t ) ); + + for( i = 0; i < subscriptionCount; i++ ) + { + pSubscriptionList[ i ].topicFilterLength = UINT16_MAX; + } + + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + IotTest_Free( pSubscriptionList ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test the behavior when the maximum length user name. Requires a large + * amount of memory not available on smaller systems. + */ +TEST( MQTT_Unit_Platform, LongUserName ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + size_t remainingLength = 0, packetSize = 0; + uint8_t * pConnectPacket = NULL; + + char * pUserName = IotTest_Malloc( UINT16_MAX ); + TEST_ASSERT_NOT_NULL( pUserName ); + + ( void ) memset( pUserName, ( int )'a', UINT16_MAX ); + + connectInfo.awsIotMqttMode = true; + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + connectInfo.pUserName = pUserName; + connectInfo.userNameLength = UINT16_MAX; + + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_NOT_EQUAL( 0, packetSize ); + + pConnectPacket = IotTest_Malloc( packetSize ); + TEST_ASSERT_NOT_NULL( pConnectPacket ); + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, pConnectPacket, packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + IotTest_Free( pConnectPacket ); + IotTest_Free( pUserName ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test that API functions can be invoked from a callback for a completed * subscription operation. diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index 97f9a4e22e..fd005a0c2d 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -991,12 +991,12 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* A CONNACK must have a remaining length of 2. */ { DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 1 ] = 0x03; + pConnack[ 1 ] = 0x01; _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, - IOT_MQTT_STATUS_PENDING ) ); + IOT_MQTT_BAD_RESPONSE ) ); /* Network close should have been called for invalid packet. */ TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); @@ -1381,7 +1381,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* A PUBACK must have a remaining length of 2. */ { DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - pPuback[ 1 ] = 0x03; + pPuback[ 1 ] = 0x01; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, @@ -1757,7 +1757,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* An UNSUBACK must have a remaining length of 2. */ { DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - pUnsuback[ 1 ] = 0x03; + pUnsuback[ 1 ] = 0x01; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, @@ -1855,20 +1855,15 @@ TEST( MQTT_Unit_Receive, Pingresp ) /* A PINGRESP should have a remaining length of 0. */ { - _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttPacket_t pingresp; - DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - pPingresp[ 1 ] = 0x01; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, - pPingresp, - pingrespSize, - IOT_MQTT_SUCCESS ) ); + ( void ) memset( &pingresp, 0x00, sizeof( _mqttPacket_t ) ); + pingresp.type = MQTT_PACKET_TYPE_PINGRESP; + pingresp.remainingLength = 1; - TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; + status = _IotMqtt_DeserializePingresp( &pingresp ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); } /* The PINGRESP control packet type must be 0xd0. */ From 9c53485620aa72ea8e440d470f1a11d9651450f0 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 11 Feb 2020 11:11:00 -0800 Subject: [PATCH 421/844] Address MISRA for LW API, sync changes (#777) * Address MISRA violations * Log warning if metrics username is too large --- libraries/standard/mqtt/src/iot_mqtt_helper.c | 26 ++++++++++++------- .../mqtt/src/iot_mqtt_lightweight_api.c | 21 ++++++++------- .../standard/mqtt/src/iot_mqtt_network.c | 12 ++++----- .../standard/mqtt/src/iot_mqtt_operation.c | 2 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 2 +- .../mqtt/src/private/iot_mqtt_helper.h | 9 +++++-- 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_helper.c b/libraries/standard/mqtt/src/iot_mqtt_helper.c index 3ca67d7570..78a2d86ae0 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_helper.c +++ b/libraries/standard/mqtt/src/iot_mqtt_helper.c @@ -161,6 +161,12 @@ static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, *pEncodedUserName = true; } + else + { + IotLogWarn( "Username length of %lu is larger than maximum %lu.", + ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ), + UINT16_MAX ); + } #else /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ /* Avoid unused variable warnings when AWS_IOT_MQTT_ENABLE_METRICS is set to 0. */ ( void ) pBuffer; @@ -178,10 +184,6 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, { bool encodedUserName = false; uint8_t * pBuffer = pDestination; - const char * pMetricsUserName = NULL; - - /* Avoid unused variable warning when AWS_IOT_MQTT_ENABLE_METRICS is set to 0 */ - ( void ) pMetricsUserName; /* If metrics are enabled, write the metrics username into the CONNECT packet. * Otherwise, write the username and password only when not connecting to the @@ -192,8 +194,6 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); - pMetricsUserName = AWS_IOT_METRICS_USERNAME; - /* Determine if the Connect packet should use a combination of the username * for authentication plus the SDK version string. */ if( pConnectInfo->pUserName != NULL ) @@ -206,7 +206,7 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, /* The username is not being used for authentication, but * metrics are enabled. */ pBuffer = _IotMqtt_EncodeString( pBuffer, - pMetricsUserName, + AWS_IOT_METRICS_USERNAME, AWS_IOT_METRICS_USERNAME_LENGTH ); encodedUserName = true; @@ -240,7 +240,10 @@ uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, *pBuffer = UINT16_LOW_BYTE( sourceLength ); pBuffer++; - /* Copy the string into pBuffer. */ + /* Copy the string into pBuffer. + * A precondition of this function is that pBuffer can hold sourceLength+2 + * bytes. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ ( void ) memcpy( pBuffer, source, sourceLength ); /* Return the pointer to the end of the encoded string. */ @@ -794,6 +797,9 @@ void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, /* The payload is placed after the packet identifier. */ if( pPublishInfo->payloadLength > 0U ) { + /* This memcpy intentionally copies bytes from a void * buffer into + * a uint8_t * buffer. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); pBuffer += pPublishInfo->payloadLength; } @@ -858,8 +864,8 @@ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscri /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ) +IotMqttError_t _IotMqtt_ProcessPublishFlags( uint8_t publishFlags, + IotMqttPublishInfo_t * pOutput ) { IotMqttError_t status = IOT_MQTT_SUCCESS; diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c index bcf328c67a..e41843bf7c 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c @@ -36,6 +36,7 @@ /* MQTT types include. */ #include "types/iot_mqtt_types.h" #include "private/iot_mqtt_helper.h" +#include "iot_mqtt_lightweight.h" /*-----------------------------------------------------------*/ @@ -185,8 +186,8 @@ static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ); * * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SERVER_REFUSED, or #IOT_MQTT_BAD_RESPONSE. */ -static IotMqttError_t _decodeSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ); +static IotMqttError_t _readSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ); /** * @brief Check the remaining length of incoming Publish against some value for @@ -206,8 +207,8 @@ static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * /*-----------------------------------------------------------*/ -static IotMqttError_t _decodeSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ) +static IotMqttError_t _readSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ) { IotMqttError_t status = IOT_MQTT_SUCCESS; uint8_t subscriptionStatus = 0; @@ -435,8 +436,8 @@ static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ) - status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), - pVariableHeader + sizeof( uint16_t ) ); + status = _readSubackStatus( remainingLength - sizeof( uint16_t ), + pVariableHeader + sizeof( uint16_t ) ); } return status; @@ -537,7 +538,7 @@ static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ) /* The flags are the lower 4 bits of the first byte in PUBLISH. */ publishFlags = pPublish->type; - status = _IotMqtt_ProcessIncomingPublishFlags( publishFlags, pOutput ); + status = _IotMqtt_ProcessPublishFlags( publishFlags, pOutput ); if( status == IOT_MQTT_SUCCESS ) { @@ -613,9 +614,9 @@ static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ) /*-----------------------------------------------------------*/ -IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ) +static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, + IotMqttQos_t qos, + size_t qos0Minimum ) { IotMqttError_t status = IOT_MQTT_SUCCESS; diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index ef5a98e258..f39eb98853 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -105,8 +105,8 @@ static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY, #IOT_MQTT_NETWORK_ERROR, * or #IOT_MQTT_SCHEDULING_ERROR. */ -static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ); +static IotMqttError_t _deserializePublishPacket( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ); /** * @brief Deserialize a PINGRESP packet. @@ -380,8 +380,8 @@ static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ -static IotMqttError_t _deserializePublish( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ) +static IotMqttError_t _deserializePublishPacket( _mqttConnection_t * pMqttConnection, + _mqttPacket_t * pIncomingPacket ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttOperation_t * pOperation = NULL; @@ -541,7 +541,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) PUBLISH in data stream.", pMqttConnection ); /* Deserialize PUBLISH. */ - status = _deserializePublish( pMqttConnection, pIncomingPacket ); + status = _deserializePublishPacket( pMqttConnection, pIncomingPacket ); break; @@ -601,7 +601,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne * * This error is triggered by passing a freed argument 'pMqttConnection' * to 'IotLogError'. Coverity assumes that 'pMqttConnection' was freed in - * '_IotMqtt_CreateOperation', which was invoked in '_deserializePublish'. + * '_IotMqtt_CreateOperation', which was invoked in '_deserializePublishPacket'. * * This will never happen as a valid MQTT connection passed to this * function always has a positive reference count; therefore, diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index bfda533987..5afce540cb 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -900,7 +900,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, * another PINGREQ after the keep-alive period. */ if( status == true ) { - if( scheduleDelay == 0 ) + if( scheduleDelay == 0U ) { scheduleDelay = pPingreqOperation->u.operation.periodic.ping.nextPeriodMs; } diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 0bc186eda2..1c28c1ce12 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -545,7 +545,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) /* The flags are the lower 4 bits of the first byte in PUBLISH. */ publishFlags = pPublish->type; - status = _IotMqtt_ProcessIncomingPublishFlags( publishFlags, pOutput ); + status = _IotMqtt_ProcessPublishFlags( publishFlags, pOutput ); if( status == IOT_MQTT_SUCCESS ) { diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h index 6c11f79605..92c4cb9efd 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h @@ -277,8 +277,8 @@ uint16_t _IotMqtt_NextPacketIdentifier( void ); * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE. */ -IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ); +IotMqttError_t _IotMqtt_ProcessPublishFlags( uint8_t publishFlags, + IotMqttPublishInfo_t * pOutput ); /** * @brief Encode a username into a CONNECT packet, if necessary. @@ -288,6 +288,11 @@ IotMqttError_t _IotMqtt_ProcessIncomingPublishFlags( uint8_t publishFlags, * * @return Pointer to the end of the encoded string, which will be identical to * `pDestination` if nothing was encoded. + * + * @warning This function does not check the size of `pDestination`! To avoid a + * buffer overflow, ensure that `pDestination` is large enough to hold + * `pConnectInfo->userNameLength` bytes if a username is supplied, and/or + * #AWS_IOT_METRICS_USERNAME_LENGTH bytes if metrics are enabled. */ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, const IotMqttConnectInfo_t * pConnectInfo ); From ec71c39d5ba0a09276d0aadcbea36a0fd47766fc Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 11 Feb 2020 11:26:50 -0800 Subject: [PATCH 422/844] Complete coverage of iot_mqtt_serialize.c (#787) --- .../standard/mqtt/src/iot_mqtt_serialize.c | 4 --- .../mqtt/test/unit/iot_tests_mqtt_api.c | 17 +++++++++++ .../mqtt/test/unit/iot_tests_mqtt_platform.c | 30 +++++++++++++++---- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 17 +++++++++++ 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 1c28c1ce12..016111e7fd 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1037,7 +1037,3 @@ void _IotMqtt_FreePacket( uint8_t * pPacket ) } /*-----------------------------------------------------------*/ - -/* Public interface functions for serialization */ - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 1db453d787..c99273f912 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -1154,6 +1154,13 @@ TEST( MQTT_Unit_API, ConnectParameters ) 0, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, status ); + + /* Check detection of packets that are too large. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + willInfo.payloadLength = MQTT_PACKET_CONNECT_MAX_SIZE + 1; + status = _IotMqtt_SerializeConnect( &connectInfo, NULL, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); } /*-----------------------------------------------------------*/ @@ -1358,6 +1365,11 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; + /* Parameters of PUBLISH serialization. */ + uint8_t * pPublishPacket = NULL; + size_t packetSize = 0; + uint16_t packetIdentifier = 0; + /* Initialize parameters. */ _networkInterface.send = _sendSuccess; @@ -1384,6 +1396,11 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) /* If valid parameters are passed, QoS 0 publish should always return success. */ status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, 0, &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); + + /* Check detection of packets that are too large. */ + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH; + status = _IotMqtt_SerializePublish( &publishInfo, &pPublishPacket, &packetSize, &packetIdentifier, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); } IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c index 01eafef97a..b3120897aa 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c @@ -162,15 +162,15 @@ /* * Will topic name and length to use for the MQTT API tests. */ -#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ -#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ /* * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. */ -#define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ -#define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ -#define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. +#define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ +#define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ +#define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. * Duplicates are sent using an exponential backoff strategy. */ /*-----------------------------------------------------------*/ @@ -1236,6 +1236,8 @@ TEST( MQTT_Unit_Platform, SubscriptionListTooLarge ) IotMqttError_t status = IOT_MQTT_STATUS_PENDING; size_t subscriptionCount = MQTT_MAX_REMAINING_LENGTH / UINT16_MAX + 1, i = 0; size_t remainingLength = 0, packetSize = 0; + uint16_t packetIdentifier = 0; + uint8_t * pPacket = NULL; IotMqttSubscription_t * pSubscriptionList = IotTest_Malloc( subscriptionCount * sizeof( IotMqttSubscription_t ) ); TEST_ASSERT_NOT_NULL( pSubscriptionList ); @@ -1253,6 +1255,21 @@ TEST( MQTT_Unit_Platform, SubscriptionListTooLarge ) &packetSize ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + /* Attempt to serialize SUBSCRIBE and UNSUBSCRIBE when the subscription list is too large. */ + status = _IotMqtt_SerializeSubscribe( pSubscriptionList, + subscriptionCount, + &pPacket, + &packetSize, + &packetIdentifier ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + status = _IotMqtt_SerializeUnsubscribe( pSubscriptionList, + subscriptionCount, + &pPacket, + &packetSize, + &packetIdentifier ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + IotTest_Free( pSubscriptionList ); } @@ -1270,9 +1287,10 @@ TEST( MQTT_Unit_Platform, LongUserName ) uint8_t * pConnectPacket = NULL; char * pUserName = IotTest_Malloc( UINT16_MAX ); + TEST_ASSERT_NOT_NULL( pUserName ); - ( void ) memset( pUserName, ( int )'a', UINT16_MAX ); + ( void ) memset( pUserName, ( int ) 'a', UINT16_MAX ); connectInfo.awsIotMqttMode = true; connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index fd005a0c2d..91b2c63cb1 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -1266,6 +1266,23 @@ TEST( MQTT_Unit_Receive, PublishResourceFailure ) _publishMallocFail( IOT_MQTT_QOS_0 ); _publishMallocFail( IOT_MQTT_QOS_1 ); + /* Test PUBACK generation when malloc fails. */ + { + #if ( IOT_TEST_NO_MALLOC_OVERRIDES != 1 ) && ( IOT_STATIC_MEMORY_ONLY != 1 ) + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + uint8_t * pPubackPacket = NULL; + size_t pubackSize = 0; + + /* Set malloc to fail, then attempt to generate a PUBACK. */ + UnityMalloc_MakeMallocFailAfterCount( 0 ); + status = _IotMqtt_SerializePuback( 1, &pPubackPacket, &pubackSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); + + /* Reset malloc failure. */ + UnityMalloc_MakeMallocFailAfterCount( -1 ); + #endif /* if ( IOT_TEST_NO_MALLOC_OVERRIDES != 1 ) && ( IOT_STATIC_MEMORY_ONLY != 1 ) */ + } + /* Test the behavior when a closed connection is used. */ { DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); From d191c96cfdcdcb55af8dd889086fa73ab02e662b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 11 Feb 2020 16:06:03 -0800 Subject: [PATCH 423/844] Complete coverage of iot_mqtt_lightweight_api.c (#792) --- .../standard/mqtt/include/iot_mqtt_protocol.h | 2 +- .../mqtt/src/iot_mqtt_lightweight_api.c | 83 ++--- .../mqtt/test/unit/iot_tests_mqtt_api.c | 339 ++++++++++++++++-- 3 files changed, 344 insertions(+), 80 deletions(-) diff --git a/libraries/standard/mqtt/include/iot_mqtt_protocol.h b/libraries/standard/mqtt/include/iot_mqtt_protocol.h index ed3c7ab7b0..0fedceaab3 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_protocol.h +++ b/libraries/standard/mqtt/include/iot_mqtt_protocol.h @@ -87,7 +87,7 @@ #define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) /** - * @brief The minimum remaining length for a QoS PUBLISH. + * @brief The minimum remaining length for a QoS 0 PUBLISH. * * Includes two bytes for topic name length and one byte for topic name. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c index e41843bf7c..843b14ebcb 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c @@ -434,8 +434,6 @@ static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ) IotLogDebug( "Packet identifier %hu.", pSuback->packetIdentifier ); - - status = _readSubackStatus( remainingLength - sizeof( uint16_t ), pVariableHeader + sizeof( uint16_t ) ); } @@ -635,7 +633,7 @@ static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * else { /* Check that the "Remaining length" is greater than the minimum. For - * QoS 1 or 2, this will be two bytes greater than for QoS due to the + * QoS 1 or 2, this will be two bytes greater than for QoS 0 due to the * packet identifier. */ if( pPublish->remainingLength < ( qos0Minimum + 2U ) ) { @@ -671,25 +669,16 @@ IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConne IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); status = IOT_MQTT_BAD_PARAMETER; } - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - else if( _IotMqtt_ConnectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) - { - IotLogError( "Connect packet length exceeds %lu, which is the maximum" - " size allowed by MQTT 3.1.1.", - MQTT_PACKET_CONNECT_MAX_SIZE ); - - status = IOT_MQTT_BAD_PARAMETER; - } else { - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _IotMqtt_ConnectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) { - IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", - ( *pRemainingLength ), ( *pPacketSize ) ); + IotLogError( "Connect packet length exceeds %lu, which is the maximum" + " size allowed by MQTT 3.1.1.", + MQTT_PACKET_CONNECT_MAX_SIZE ); + status = IOT_MQTT_BAD_PARAMETER; } } @@ -711,9 +700,14 @@ IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectIn IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); status = IOT_MQTT_BAD_PARAMETER; } - else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + else if( pConnectInfo->pClientIdentifier == NULL ) { - IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); + IotLogError( "Client identifier must be set." ); + status = IOT_MQTT_BAD_PARAMETER; + } + else if( pConnectInfo->clientIdentifierLength == 0U ) + { + IotLogError( "Client identifier length must be set." ); status = IOT_MQTT_BAD_PARAMETER; } else if( remainingLength > bufferSize ) @@ -758,25 +752,17 @@ IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); status = IOT_MQTT_BAD_PARAMETER; } - else if( _IotMqtt_SubscriptionPacketSize( type, - pSubscriptionList, - subscriptionCount, - pRemainingLength, - pPacketSize ) == false ) - { - IotLogError( "Subscription packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - status = IOT_MQTT_BAD_PARAMETER; - } else { - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) + if( _IotMqtt_SubscriptionPacketSize( type, + pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize ) == false ) { - IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", - ( *pRemainingLength ), ( *pPacketSize ) ); + IotLogError( "Subscription packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); status = IOT_MQTT_BAD_PARAMETER; } } @@ -881,25 +867,16 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPubli IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); status = IOT_MQTT_BAD_PARAMETER; } - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - else if( _IotMqtt_PublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) - { - IotLogError( "Publish packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_PARAMETER; - } else { - /* Total size of the publish packet should be larger than the "Remaining length" - * field. */ - if( ( *pPacketSize ) < ( *pRemainingLength ) ) + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _IotMqtt_PublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) { - IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", - ( *pRemainingLength ), ( *pPacketSize ) ); + IotLogError( "Publish packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + status = IOT_MQTT_BAD_PARAMETER; } } diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index c99273f912..ca963f9783 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -50,7 +50,7 @@ /* MQTT test access include. */ #include "iot_test_access_mqtt.h" -/* MQTT serializer API include */ +/* MQTT lightweight API include */ #include "iot_mqtt_lightweight.h" /* MQTT mock include. */ @@ -535,7 +535,7 @@ static void _decrementReferencesJob( IotTaskPool_t pTaskPool, /*-----------------------------------------------------------*/ /** - * @brief get next byte mock function to test MQTT serializer API + * @brief Get next byte mock function to test MQTT serializer API. */ static IotMqttError_t _getNextByte( IotNetworkConnection_t pNetworkInterface, uint8_t * nextByte ) @@ -563,6 +563,33 @@ static IotMqttError_t _getNextByte( IotNetworkConnection_t pNetworkInterface, /*-----------------------------------------------------------*/ +/** + * @brief Get next byte mock function to test MQTT serializer API that fails when + * reading the remaining length. + */ +static IotMqttError_t _getNextByteFailure( IotNetworkConnection_t pNetworkInterface, + uint8_t * nextByte ) +{ + IotMqttError_t status = IOT_MQTT_NETWORK_ERROR; + static int32_t invokeCount = 0; + + ( void ) pNetworkInterface; + ( void ) nextByte; + + /* Return a valid packet type on the first invocation. */ + if( invokeCount == 0 ) + { + status = IOT_MQTT_SUCCESS; + *nextByte = MQTT_PACKET_TYPE_CONNACK; + } + + invokeCount++; + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief A PINGREQ serializer that attempts to allocate memory (unlike the default). */ @@ -681,7 +708,11 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, SerializePublishChecks ); RUN_TEST_CASE( MQTT_Unit_API, SerializeDisconnectChecks ); RUN_TEST_CASE( MQTT_Unit_API, SerializePingReqChecks ); - RUN_TEST_CASE( MQTT_Unit_API, DeserializeResponseChecks ); + RUN_TEST_CASE( MQTT_Unit_API, LightweightConnack ); + RUN_TEST_CASE( MQTT_Unit_API, LightweightSuback ); + RUN_TEST_CASE( MQTT_Unit_API, LightweightUnsuback ); + RUN_TEST_CASE( MQTT_Unit_API, LightweightPingresp ); + RUN_TEST_CASE( MQTT_Unit_API, LightweightPuback ); RUN_TEST_CASE( MQTT_Unit_API, DeserializePublishChecks ); RUN_TEST_CASE( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ); } @@ -1998,6 +2029,17 @@ TEST( MQTT_Unit_API, GetConnectPacketSizeChecks ) status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + /* Verify empty client identifier fails. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = 0; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + connectInfo.pClientIdentifier = NULL; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + /* Verify good case */ memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); connectInfo.cleanSession = true; @@ -2034,8 +2076,10 @@ TEST( MQTT_Unit_API, SerializeConnectChecks ) TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); - /* Make sure greater remaining length returns error. */ - remainingLength = 120; + status = IotMqtt_SerializeConnect( &connectInfo, 120, buffer, packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; status = IotMqtt_SerializeConnect( &connectInfo, 120, buffer, packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); @@ -2379,6 +2423,13 @@ TEST( MQTT_Unit_API, GetPublishPacketSizeChecks ) /* Empty topic must fail. */ memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = NULL; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = 0; status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); @@ -2449,8 +2500,9 @@ TEST( MQTT_Unit_API, SerializePublishChecks ) packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - /* NULL topic fails. */ + /* Empty topic fails. */ publishInfo.pTopicName = NULL; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; status = IotMqtt_SerializePublish( &publishInfo, remainingLength, &packetIdentifier, @@ -2459,6 +2511,27 @@ TEST( MQTT_Unit_API, SerializePublishChecks ) packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = 0; + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + &packetIdentifier, + &pPacketIdentifierHigh, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Remaining length larger than buffer size. */ + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = IotMqtt_SerializePublish( &publishInfo, + 10, + &packetIdentifier, + &pPacketIdentifierHigh, + buffer, + 5 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + /* Good case succeeds */ publishInfo.qos = IOT_MQTT_QOS_2; publishInfo.retain = true; @@ -2531,7 +2604,6 @@ TEST( MQTT_Unit_API, SerializePingReqChecks ) /** * @brief Tests that IotMqtt_GetIncomingMQTTPacketTypeAndLength works as intended. - * to @ref mqtt_function_getincomingmqttpackettypeandlength. */ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) { @@ -2574,15 +2646,28 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) buffer[ 4 ] = 0xFF; status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); + + /* Check with an encoding that does not conform to the MQTT spec. */ + bufPtr = buffer; + buffer[ 1 ] = 0x80; + buffer[ 2 ] = 0x80; + buffer[ 3 ] = 0x80; + buffer[ 4 ] = 0x00; + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); + + /* Check when network receive fails. */ + memset( buffer, 0x00, 10 ); + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByteFailure, pNetworkInterface ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); } /*-----------------------------------------------------------*/ /** - * @brief Tests that IotMqtt_DeserializeResponse works as intended. - * to @ref mqtt_function_deserializeresponse. + * @brief Tests that IotMqtt_DeserializeResponse works as intended with a CONNACK. */ -TEST( MQTT_Unit_API, DeserializeResponseChecks ) +TEST( MQTT_Unit_API, LightweightConnack ) { IotMqttPacketInfo_t mqttPacketInfo; IotMqttError_t status = IOT_MQTT_SUCCESS; @@ -2593,20 +2678,177 @@ TEST( MQTT_Unit_API, DeserializeResponseChecks ) TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); + /* Bad packet type. */ mqttPacketInfo.type = 0x01; mqttPacketInfo.pRemainingData = buffer; status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - /* Good case succeeds - Test for CONN ACK */ - /* Set conn ack variable portion */ - buffer[ 0 ] = 0x00; - buffer[ 1 ] = 0x00; - /* Set type, remaining length and remaining data. */ - mqttPacketInfo.type = 0x20; /* CONN ACK */ + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK; + mqttPacketInfo.remainingLength = MQTT_PACKET_CONNACK_REMAINING_LENGTH - 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Incorrect reserved bits. */ + mqttPacketInfo.remainingLength = MQTT_PACKET_CONNACK_REMAINING_LENGTH; + buffer[ 0 ] = 0xf; + buffer[ 1 ] = 0; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Session present but nonzero return code. */ + buffer[ 0 ] = MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK; + buffer[ 1 ] = 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Invalid response code. */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 6; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Valid packet with rejected code. */ + buffer[ 1 ] = 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SERVER_REFUSED, status ); + + /* Valid packet with success code. */ + buffer[ 0 ] = 1; + buffer[ 1 ] = 0; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_DeserializeResponse works as intended with a SUBACK. + */ +TEST( MQTT_Unit_API, LightweightSuback ) +{ + IotMqttPacketInfo_t mqttPacketInfo; + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = 2; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Set packet identifier. */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 1; + + /* Bad response code. */ + mqttPacketInfo.remainingLength = 3; + buffer[ 2 ] = 5; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Process a valid SUBACK with server refused response code. */ + mqttPacketInfo.remainingLength = 3; + buffer[ 2 ] = 0x80; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SERVER_REFUSED, status ); + + /* Process a valid SUBACK with various server acceptance codes. */ + mqttPacketInfo.remainingLength = 5; + buffer[ 2 ] = 0x00; + buffer[ 3 ] = 0x01; + buffer[ 4 ] = 0x02; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_DeserializeResponse works as intended with an UNSUBACK. + */ +TEST( MQTT_Unit_API, LightweightUnsuback ) +{ + IotMqttPacketInfo_t mqttPacketInfo; + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_UNSUBACK; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = MQTT_PACKET_UNSUBACK_REMAINING_LENGTH - 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Packet identifier 0 is not valid (per spec). */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 0; + mqttPacketInfo.remainingLength = MQTT_PACKET_UNSUBACK_REMAINING_LENGTH; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Process a valid UNSUBACK. */ + buffer[ 1 ] = 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_DeserializeResponse works as intended with a PINGRESP. + */ +TEST( MQTT_Unit_API, LightweightPingresp ) +{ + IotMqttPacketInfo_t mqttPacketInfo; + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PINGRESP; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH + 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Process a valid PINGRESP. */ + mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_DeserializeResponse works as intended with a PUBACK. + */ +TEST( MQTT_Unit_API, LightweightPuback ) +{ + IotMqttPacketInfo_t mqttPacketInfo; + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBACK; mqttPacketInfo.pRemainingData = buffer; - mqttPacketInfo.remainingLength = 0x02; /* CONN ACK Remaining Length. */ + mqttPacketInfo.remainingLength = MQTT_PACKET_PUBACK_REMAINING_LENGTH - 1; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Packet identifier 0 is not valid (per spec). */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 0; + mqttPacketInfo.remainingLength = MQTT_PACKET_PUBACK_REMAINING_LENGTH; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Process a valid PUBACK. */ + buffer[ 1 ] = 1; status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); } @@ -2615,7 +2857,6 @@ TEST( MQTT_Unit_API, DeserializeResponseChecks ) /** * @brief Tests that IotMqtt_DeserializePublish works as intended. - * to @ref mqtt_function_deserializepublish. */ TEST( MQTT_Unit_API, DeserializePublishChecks ) { @@ -2643,22 +2884,71 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) status = IotMqtt_DeserializePublish( &mqttPacketInfo ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + /* Incorrect flags. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH | 0xf; + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* QoS 0 bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH; + mqttPacketInfo.remainingLength = 0; + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - /* Good case succeeds - Test for Publish. */ - /* 1. Find out length of the packet .*/ + /* QoS 1 bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH | 0x2; + mqttPacketInfo.remainingLength = 0; + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* QoS 1 invalid packet identifier. */ + mqttPacketInfo.remainingLength = 5; + buffer[ 0 ] = 0; + buffer[ 1 ] = 1; + buffer[ 2 ] = ( uint8_t )'a'; + buffer[ 3 ] = 0; + buffer[ 4 ] = 0; + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); + + /* Create a PUBLISH packet to test. */ memset( &publishInfo, 0x00, sizeof( publishInfo ) ); publishInfo.pTopicName = "/test/topic"; publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = "Hello World"; publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); + + /* Test serialization and deserialization of a QoS 0 PUBLISH. */ publishInfo.qos = IOT_MQTT_QOS_0; - /* Calculate exact packet size and remaining length */ + + /* Generate QoS 0 packet. */ + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + &packetIdentifier, + &pPacketIdentifierHigh, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Deserialize QoS 0 packet. */ + pNetworkInterface = buffer; + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacketInfo, _getNextByte, ( void * ) &pNetworkInterface ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + mqttPacketInfo.pRemainingData = &buffer[ 2 ]; + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Test serialization and deserialization of a QoS 1 PUBLISH. */ + publishInfo.qos = IOT_MQTT_QOS_1; + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure buffer has enough space */ TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - /* 2. Serialize packet in the buffer. */ status = IotMqtt_SerializePublish( &publishInfo, remainingLength, &packetIdentifier, @@ -2667,13 +2957,10 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* 3. Deserialize - get type and length. */ pNetworkInterface = buffer; status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacketInfo, _getNextByte, ( void * ) &pNetworkInterface ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Remaining data points to byte 3. */ mqttPacketInfo.pRemainingData = &buffer[ 2 ]; - /* 4. Deserialize publish. */ status = IotMqtt_DeserializePublish( &mqttPacketInfo ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); } From 696224499fc29f6971f3ac7ddb138256d4aab332 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 12 Feb 2020 09:53:36 -0800 Subject: [PATCH 424/844] Make certain helper functions static (#795) --- libraries/standard/mqtt/src/iot_mqtt_helper.c | 131 ++++++++++++------ .../mqtt/src/private/iot_mqtt_helper.h | 50 ------- 2 files changed, 91 insertions(+), 90 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_helper.c b/libraries/standard/mqtt/src/iot_mqtt_helper.c index 78a2d86ae0..505de80a9a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_helper.c +++ b/libraries/standard/mqtt/src/iot_mqtt_helper.c @@ -121,6 +121,57 @@ static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, const IotMqttConnectInfo_t * pConnectInfo, bool * pEncodedUserName ); +/** + * @brief Encode a username into a CONNECT packet, if necessary. + * + * @param[out] pDestination Buffer for the CONNECT packet. + * @param[in] pConnectInfo User-provided CONNECT information. + * + * @return Pointer to the end of the encoded string, which will be identical to + * `pDestination` if nothing was encoded. + * + * @warning This function does not check the size of `pDestination`! To avoid a + * buffer overflow, ensure that `pDestination` is large enough to hold + * `pConnectInfo->userNameLength` bytes if a username is supplied, and/or + * #AWS_IOT_METRICS_USERNAME_LENGTH bytes if metrics are enabled. + */ +static uint8_t * _encodeUserName( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo ); + +/** + * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. + * + * @param[out] pDestination Where to write the encoded string. + * @param[in] source The string to encode. + * @param[in] sourceLength The length of source. + * + * @return Pointer to the end of the encoded string, which is `sourceLength+2` + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer + * overflow. + */ +static uint8_t * _encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ); + +/** + * @brief Encode the "Remaining length" field per MQTT spec. + * + * @param[out] pDestination Where to write the encoded "Remaining length". + * @param[in] length The "Remaining length" to encode. + * + * @return Pointer to the end of the encoded "Remaining length", which is 1-4 + * bytes greater than `pDestination`. + * + * @warning This function does not check the size of `pDestination`! Ensure that + * `pDestination` is large enough to hold the encoded "Remaining length" using + * the function #_IotMqtt_RemainingLengthEncodedSize to avoid buffer overflows. + */ +static uint8_t * _encodeRemainingLength( uint8_t * pDestination, + size_t length ); + /*-----------------------------------------------------------*/ static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, @@ -179,8 +230,8 @@ static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, /*-----------------------------------------------------------*/ -uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ) +static uint8_t * _encodeUserName( uint8_t * pDestination, + const IotMqttConnectInfo_t * pConnectInfo ) { bool encodedUserName = false; uint8_t * pBuffer = pDestination; @@ -205,9 +256,9 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, { /* The username is not being used for authentication, but * metrics are enabled. */ - pBuffer = _IotMqtt_EncodeString( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); + pBuffer = _encodeString( pBuffer, + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); encodedUserName = true; } @@ -217,18 +268,18 @@ uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, /* Encode the username if there is one and it hasn't already been done. */ if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) { - pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); + pBuffer = _encodeString( pBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); } return pBuffer; } /*-----------------------------------------------------------*/ -uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ) +static uint8_t * _encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) { uint8_t * pBuffer = pDestination; @@ -254,8 +305,8 @@ uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, /*-----------------------------------------------------------*/ -uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, - size_t length ) +static uint8_t * _encodeRemainingLength( uint8_t * pDestination, + size_t length ) { uint8_t lengthByte = 0, * pLengthEnd = pDestination; size_t remainingLength = length; @@ -421,11 +472,11 @@ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, /* The remaining length of the CONNECT packet is encoded starting from the * second byte. The remaining length does not include the length of the fixed * header or the encoding of the remaining length. */ - pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable * header. This string is 4 bytes long. */ - pBuffer = _IotMqtt_EncodeString( pBuffer, "MQTT", 4 ); + pBuffer = _encodeString( pBuffer, "MQTT", 4 ); /* The MQTT protocol version is the second byte of the variable header. */ *pBuffer = MQTT_VERSION_3_1_1; @@ -497,31 +548,31 @@ void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, pBuffer += 2; /* Write the client identifier into the CONNECT packet. */ - pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pClientIdentifier, - pConnectInfo->clientIdentifierLength ); + pBuffer = _encodeString( pBuffer, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); /* Write the will topic name and message into the CONNECT packet if provided. */ if( pConnectInfo->pWillInfo != NULL ) { - pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pWillInfo->pTopicName, - pConnectInfo->pWillInfo->topicNameLength ); + pBuffer = _encodeString( pBuffer, + pConnectInfo->pWillInfo->pTopicName, + pConnectInfo->pWillInfo->topicNameLength ); - pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pWillInfo->pPayload, - ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); + pBuffer = _encodeString( pBuffer, + pConnectInfo->pWillInfo->pPayload, + ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); } /* Encode the username if there is one or metrics are enabled. */ - pBuffer = _IotMqtt_EncodeUserName( pBuffer, pConnectInfo ); + pBuffer = _encodeUserName( pBuffer, pConnectInfo ); /* Encode the password field, if requested by the app. */ if( pConnectInfo->pPassword != NULL ) { - pBuffer = _IotMqtt_EncodeString( pBuffer, - pConnectInfo->pPassword, - pConnectInfo->passwordLength ); + pBuffer = _encodeString( pBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); } /* Ensure that the difference between the end and beginning of the buffer @@ -581,7 +632,7 @@ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscript pBuffer++; /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _IotMqtt_NextPacketIdentifier(); @@ -596,9 +647,9 @@ void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscript /* Serialize each subscription topic filter and QoS. */ for( i = 0; i < subscriptionCount; i++ ) { - pBuffer = _IotMqtt_EncodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); /* Place the QoS in the SUBSCRIBE packet. */ *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); @@ -766,12 +817,12 @@ void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, pBuffer++; /* The "Remaining length" is encoded from the second byte. */ - pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); /* The topic name is placed after the "Remaining length". */ - pBuffer = _IotMqtt_EncodeString( pBuffer, - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + pBuffer = _encodeString( pBuffer, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); /* A packet identifier is required for QoS 1 and 2 messages. */ if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) @@ -834,7 +885,7 @@ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscri pBuffer++; /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _IotMqtt_EncodeRemainingLength( pBuffer, remainingLength ); + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); /* Get the next packet identifier. It should always be nonzero. */ packetIdentifier = _IotMqtt_NextPacketIdentifier(); @@ -849,9 +900,9 @@ void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscri /* Serialize each subscription topic filter. */ for( i = 0; i < subscriptionCount; i++ ) { - pBuffer = _IotMqtt_EncodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); } /* Ensure that the difference between the end and beginning of the buffer diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h index 92c4cb9efd..3b417738f7 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h @@ -86,24 +86,6 @@ /*-----------------------------------------------------------*/ -/** - * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. - * - * @param[out] pDestination Where to write the encoded string. - * @param[in] source The string to encode. - * @param[in] sourceLength The length of source. - * - * @return Pointer to the end of the encoded string, which is `sourceLength+2` - * bytes greater than `pDestination`. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer - * overflow. - */ -uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ); - /** * @brief Calculate the number of bytes required to encode an MQTT * "Remaining length" field. @@ -114,22 +96,6 @@ uint8_t * _IotMqtt_EncodeString( uint8_t * pDestination, */ size_t _IotMqtt_RemainingLengthEncodedSize( size_t length ); -/** - * @brief Encode the "Remaining length" field per MQTT spec. - * - * @param[out] pDestination Where to write the encoded "Remaining length". - * @param[in] length The "Remaining length" to encode. - * - * @return Pointer to the end of the encoded "Remaining length", which is 1-4 - * bytes greater than `pDestination`. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold the encoded "Remaining length" using - * the function #_IotMqtt_RemainingLengthEncodedSize to avoid buffer overflows. - */ -uint8_t * _IotMqtt_EncodeRemainingLength( uint8_t * pDestination, - size_t length ); - /** * @brief Calculate the size and "Remaining length" of a CONNECT packet generated * from the given parameters. @@ -280,20 +246,4 @@ uint16_t _IotMqtt_NextPacketIdentifier( void ); IotMqttError_t _IotMqtt_ProcessPublishFlags( uint8_t publishFlags, IotMqttPublishInfo_t * pOutput ); -/** - * @brief Encode a username into a CONNECT packet, if necessary. - * - * @param[out] pDestination Buffer for the CONNECT packet. - * @param[in] pConnectInfo User-provided CONNECT information. - * - * @return Pointer to the end of the encoded string, which will be identical to - * `pDestination` if nothing was encoded. - * - * @warning This function does not check the size of `pDestination`! To avoid a - * buffer overflow, ensure that `pDestination` is large enough to hold - * `pConnectInfo->userNameLength` bytes if a username is supplied, and/or - * #AWS_IOT_METRICS_USERNAME_LENGTH bytes if metrics are enabled. - */ -uint8_t * _IotMqtt_EncodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ); #endif /* ifndef IOT_MQTT_HELPER_H_ */ From bd911bbb4944e72dc4c837fbdec4bdaf6839abf9 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Wed, 12 Feb 2020 15:28:13 -0500 Subject: [PATCH 425/844] Add an MQTT state model for CBMC proofs of the MQTT client API. (#780) --- cbmc/proofs/mqtt_state.c | 698 +++++++++++++++++++++++++++++++++++++++ cbmc/proofs/mqtt_state.h | 223 +++++++++++++ 2 files changed, 921 insertions(+) create mode 100644 cbmc/proofs/mqtt_state.c create mode 100644 cbmc/proofs/mqtt_state.h diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c new file mode 100644 index 0000000000..8f32998643 --- /dev/null +++ b/cbmc/proofs/mqtt_state.c @@ -0,0 +1,698 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include + +#include "mqtt_state.h" + +/**************************************************************** + * Bound the lengths of subscription and operation lists. + * + * Lists are empty by defaut. + * Set this variable to L+1 for lists of length <= L. + ****************************************************************/ + +#ifndef SUBSCRIPTION_COUNT_MAX + #define SUBSCRIPTION_COUNT_MAX 1 +#endif + +#ifndef OPERATION_COUNT_MAX + #define OPERATION_COUNT_MAX 1 +#endif + +/**************************************************************** + * Model a malloc that can fail and return NULL. + ****************************************************************/ + +void *malloc_can_fail( size_t size ) +{ + __CPROVER_assert( VALID_CBMC_SIZE( size ), "malloc_can_fail size too big" ); + return nondet_bool() ? NULL : malloc( size ); +} + +/**************************************************************** + * Model an opaque type as nothing but a valid pointer. + ****************************************************************/ + +void *allocate_opaque_type() +{ + return malloc( 1 ); // consider using malloc(0) +} + +/**************************************************************** + * MqttOperation + ****************************************************************/ + +IotMqttOperation_t allocate_IotMqttOperation( IotMqttOperation_t pOp, + IotMqttConnection_t pConn ) +{ + // assume a valid connection for the operation + assert( pConn != NULL ); + + if ( pOp == NULL ) pOp = malloc_can_fail( sizeof( *pOp ) ); + if ( pOp == NULL ) return NULL; + + pOp->pMqttConnection = pConn; + + if ( pOp->incomingPublish ) { + // allocate publish member of the union + + allocate_IotMqttPublishInfo( &(pOp->u.publish.publishInfo ) ); + + size_t length; + __CPROVER_assume( VALID_CBMC_SIZE( length ) ); + pOp->u.publish.pReceivedData = malloc_can_fail( length ); + + } else { + // allocate operation member of the union + + // assumption here is checked below in valid_* + __CPROVER_assume( VALID_CBMC_SIZE( pOp->u.operation.packetSize ) ); + pOp->u.operation.pMqttPacket = + malloc_can_fail( pOp->u.operation.packetSize ); + + } + + return pOp; +} + +// TODO: valid_ should have same signature as allocate_, +// should check that operation points to the connection + +bool valid_IotMqttOperation( const IotMqttOperation_t pOp ) +{ + if ( pOp == NULL ) return false; + + // publish member of the union should be valid (pOp->incomingPublish ) + + bool valid_publishinfo = + valid_IotMqttPublishInfo( &(pOp->u.publish.publishInfo) ); + + bool valid_publish_member = + IMPLIES( pOp->incomingPublish, valid_publishinfo ); + + // operation member of the union should be valid (!pOp->incomingPublish) + + bool valid_packet = + VALID_STRING( pOp->u.operation.pMqttPacket, pOp->u.operation.packetSize ) && +#ifdef PACKET_SIZE_MAX + // Some proofs iterate over a packet and must bound the packet size + pOp->u.operation.packetSize < PACKET_SIZE_MAX && +#endif + VALID_CBMC_SIZE( pOp->u.operation.packetSize ); + bool valid_pingreq_packet = + IMPLIES( pOp->u.operation.type == IOT_MQTT_PINGREQ, + IFF( pOp->u.operation.periodic.ping.keepAliveMs == 0, + pOp->u.operation.packetSize == 0 ) ); + bool valid_other_packet = + IMPLIES( pOp->u.operation.type != IOT_MQTT_PINGREQ, + pOp->u.operation.packetSize > 0 ); + bool waitable = + ( pOp->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE; + bool valid_jobReference = + // Async operations are waitable. Loosely speaking, an async operation + // is split into independent send and ack events, and an sync operation + // is not. + IMPLIES( waitable, pOp->u.operation.jobReference == 2 ) && + IMPLIES( !waitable, pOp->u.operation.jobReference == 1 ); + + bool valid_operation_member = + IMPLIES( !pOp->incomingPublish, + valid_packet && + valid_pingreq_packet && + valid_other_packet && + valid_jobReference ); + + return + // assume valid connection + valid_publish_member && + valid_operation_member; +} + +/**************************************************************** + * IotMqttOperation list + ****************************************************************/ + +IotListDouble_t *allocate_IotMqttOperationList( IotListDouble_t *pOp, + size_t length, + IotMqttConnection_t pConn ) +{ + if ( pOp == NULL ) pOp = malloc_can_fail( sizeof( *pOp ) ); + if ( pOp == NULL ) return NULL; + + // Allocate lists of length L <= 3 (MAX = L+1) + __CPROVER_assert(OPERATION_COUNT_MAX <= 3+1, + "Operation list bound is too big"); + __CPROVER_assert(length < OPERATION_COUNT_MAX, + "Operation list requested is too long"); + + IotListDouble_Create( pOp ); + + size_t num_elts; + __CPROVER_assume(num_elts <= length); + + if (1 <= num_elts) + { + IotMqttOperation_t pElt = allocate_IotMqttOperation( NULL, pConn ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pOp, &( pElt->link ) ); + } + if (2 <= num_elts) + { + IotMqttOperation_t pElt = allocate_IotMqttOperation( NULL, pConn ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pOp, &( pElt->link ) ); + } + if (3 <= num_elts) + { + IotMqttOperation_t pElt = allocate_IotMqttOperation( NULL, pConn ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pOp, &( pElt->link ) ); + } + + return pOp; +} + +bool valid_IotMqttOperationList( const IotListDouble_t *pOp, + const size_t length ) +{ + if ( pOp == NULL ) return false; + + IotListDouble_t *pLink; + IotContainers_ForEach( pOp, pLink ) { + IotMqttOperation_t + *pElt = IotLink_Container( _mqttSubscription_t, pLink, link ); + if (! valid_IotMqttOperation( pElt ) ) return false; + } + + return + // MAX is one greater than the maximum length + length < OPERATION_COUNT_MAX; +} + +/**************************************************************** + * IotMqttConnection + ****************************************************************/ + +IotMqttConnection_t allocate_IotMqttConnection( IotMqttConnection_t pConn ) +{ + if ( pConn == NULL ) pConn = malloc_can_fail( sizeof( *pConn ) ); + if ( pConn == NULL ) return NULL; + + pConn->pNetworkConnection = allocate_IotNetworkConnection(); + pConn->pNetworkInterface = allocate_IotNetworkInterface(); + allocate_IotMqttOperation( &(pConn->pingreq ), pConn ); + allocate_IotMqttCallbackInfo( &(pConn->disconnectCallback) ); + return pConn; +} + +void ensure_IotMqttConnection_has_lists( IotMqttConnection_t pConn ) +{ + allocate_IotMqttOperationList( &pConn->pendingProcessing, + OPERATION_COUNT_MAX - 1, + pConn ); + allocate_IotMqttOperationList( &pConn->pendingResponse, + OPERATION_COUNT_MAX - 1, + pConn ); + allocate_IotMqttSubscriptionList( &pConn->subscriptionList, + SUBSCRIPTION_COUNT_MAX - 1 ); + return pConn; +} + +bool valid_IotMqttConnection( const IotMqttConnection_t pConn ) +{ + if ( pConn == NULL ) return false; + + // This is the number of callbacks and operations using the connection. + // It is a uint32 and must be bounded by a number smaller than the + // maximum value to avoid integer overflows. We expect to run out of + // memory before having 2^16 references on a device. + bool valid_references = + pConn->references >= 1 && + pConn->references <= (1 << 16); + + bool valid_pingreq = + ( valid_IotMqttOperation( &(pConn->pingreq ) ) ) && + ( pConn->pingreq.u.operation.type == IOT_MQTT_PINGREQ ) && + ( pConn->pingreq.pMqttConnection == pConn ) && + ( !pConn->pingreq.incomingPublish ) ; + + return + valid_IotMqttOperationList( &pConn->pendingProcessing, + OPERATION_COUNT_MAX - 1 ) && + valid_IotMqttOperationList( &pConn->pendingResponse, + OPERATION_COUNT_MAX - 1 ) && + valid_IotMqttSubscriptionList( &pConn->subscriptionList, + SUBSCRIPTION_COUNT_MAX - 1 ) && + valid_IotNetworkInterface( pConn->pNetworkInterface ) && + valid_references && + valid_pingreq; +} + +/**************************************************************** + * IotMqttNetworkInfo + ****************************************************************/ + +IotMqttNetworkInfo_t *allocate_IotMqttNetworkInfo( IotMqttNetworkInfo_t *pInfo ) +{ + if ( pInfo == NULL ) pInfo = malloc_can_fail( sizeof( *pInfo ) ); + if ( pInfo == NULL ) return NULL; + + if ( pInfo->createNetworkConnection ) { + // allocate setup member of union + pInfo->u.setup.pNetworkServerInfo = allocate_opaque_type(); + pInfo->u.setup.pNetworkCredentialInfo = allocate_opaque_type(); + } else { + // allocate network member of union + pInfo->u.pNetworkConnection = allocate_IotNetworkConnection(); + } + pInfo->pNetworkInterface = allocate_IotNetworkInterface(); + return pInfo; +} + +bool valid_IotMqttNetworkInfo( const IotMqttNetworkInfo_t *pInfo ) +{ + return pInfo != NULL; +} + +/**************************************************************** + * IotMqttConnectInfo + ****************************************************************/ + +IotMqttConnectInfo_t *allocate_IotMqttConnectInfo( IotMqttConnectInfo_t *pInfo ) +{ + if ( pInfo == NULL ) pInfo = malloc_can_fail( sizeof( *pInfo ) ); + if ( pInfo == NULL ) return NULL; + + pInfo->pPreviousSubscriptions = + allocate_IotMqttSubscriptionArray( NULL, + pInfo->previousSubscriptionCount ); + pInfo->pWillInfo = allocate_IotMqttPublishInfo( NULL ); + pInfo->pClientIdentifier = malloc_can_fail( pInfo->clientIdentifierLength ); + pInfo->pUserName = malloc_can_fail( pInfo->userNameLength ); + pInfo->pPassword = malloc_can_fail( pInfo->passwordLength ); +} + +bool valid_IotMqttConnectInfo( const IotMqttConnectInfo_t *pInfo ) +{ + return + pInfo != NULL && + + VALID_STRING( pInfo->pClientIdentifier, pInfo->clientIdentifierLength ) && + VALID_CBMC_SIZE( pInfo->clientIdentifierLength ) && + + VALID_STRING( pInfo->pUserName, pInfo->userNameLength ) && + VALID_CBMC_SIZE( pInfo->userNameLength ) && + + VALID_STRING( pInfo->pPassword, pInfo->passwordLength ) && + VALID_CBMC_SIZE( pInfo->passwordLength ) && + + // MAX is one greater than the maximum length + pInfo->previousSubscriptionCount < SUBSCRIPTION_COUNT_MAX && + IFF( pInfo->pPreviousSubscriptions == NULL, + pInfo->previousSubscriptionCount == 0 ) && + valid_IotMqttSubscriptionArray( pInfo->pPreviousSubscriptions, + pInfo->previousSubscriptionCount ); +} + +/**************************************************************** + * IotMqttSubscription + ****************************************************************/ + +IotMqttSubscription_t *allocate_IotMqttSubscription( IotMqttSubscription_t *pSub ) +{ + if ( pSub == NULL ) pSub = malloc_can_fail( sizeof( *pSub ) ); + if ( pSub == NULL ) return NULL; + + pSub->pTopicFilter = malloc_can_fail( pSub->topicFilterLength ); + return pSub; +} + +bool valid_IotMqttSubscription( const IotMqttSubscription_t *pSub ) +{ + return + pSub && + + VALID_STRING( pSub->pTopicFilter, pSub->topicFilterLength ) && + VALID_CBMC_SIZE( pSub->topicFilterLength ) + +#ifdef TOPIC_LENGTH_MAX + // MAX is one greater than the maximum length + && pSub->topicFilterLength < TOPIC_LENGTH_MAX +#endif + ; +} + +/**************************************************************** + * IotMqttSubscription array list + ****************************************************************/ + +IotMqttSubscription_t *allocate_IotMqttSubscriptionArray( IotMqttSubscription_t *pSub, + size_t length ) +{ + if ( pSub == NULL ) pSub = malloc_can_fail( length * sizeof( *pSub ) ); + if ( pSub == NULL ) return NULL; + + for ( size_t i = 0; i < length; i++ ) + allocate_IotMqttSubscription( pSub + i ); + + return pSub; +} + +bool valid_IotMqttSubscriptionArray( const IotMqttSubscription_t *pSub, + const size_t length ) +{ + if ( !IFF( pSub == NULL, length == 0 ) ) return false; + if ( pSub == NULL ) return false; + + for ( size_t i = 0; i < length; i++ ) + if ( !valid_IotMqttSubscription( pSub + i ) ) return false; + return + // MAX is one greater than the maximum length + length < SUBSCRIPTION_COUNT_MAX; +} + +/**************************************************************** + * IotMqttSubscription array list + ****************************************************************/ + +// Subscription linked list elements + +_mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *pElt ) +{ + size_t length; + // Assumption avoids arithmetic overflow in malloc, rechecked in valid_* + __CPROVER_assume( length < CBMC_MAX_OBJECT_SIZE - sizeof( *pElt ) ); + + if ( pElt == NULL ) pElt = malloc_can_fail( sizeof( *pElt ) + length ); + if ( pElt == NULL ) return NULL; + + pElt->link.pPrevious = NULL; + pElt->link.pNext = NULL; + pElt->topicFilterLength = length; + return pElt; +} + +bool valid_IotMqttSubscriptionListElt( const _mqttSubscription_t *pElt ) +{ + if ( pElt == NULL ) return false; + + return +#ifdef TOPIC_LENGTH_MAX + // MAX is one greater than the maximum length + pElt->topicFilterLength < TOPIC_LENGTH_MAX && +#endif + pElt->topicFilterLength < CBMC_MAX_OBJECT_SIZE - sizeof( *pElt ) && + __CPROVER_r_ok( pElt->pTopicFilter, pElt->topicFilterLength ) && + __CPROVER_w_ok( pElt->pTopicFilter, pElt->topicFilterLength ); +} + +// Subscription linked lists + +IotListDouble_t *allocate_IotMqttSubscriptionList( IotListDouble_t *pSub, + size_t length ) +{ + if ( pSub == NULL ) pSub = malloc_can_fail( sizeof( *pSub ) ); + if ( pSub == NULL ) return NULL; + + // Allocate lists of length L <= 3 (MAX = L+1) + __CPROVER_assert(SUBSCRIPTION_COUNT_MAX <= 3+1, + "Subscription list bound is too big"); + __CPROVER_assert(length < SUBSCRIPTION_COUNT_MAX, + "Subscription list requested is too long"); + + IotListDouble_Create( pSub ); + + size_t num_elts; + __CPROVER_assume(num_elts <= length); + + if (1 <= num_elts) + { + _mqttSubscription_t *pElt = allocate_IotMqttSubscriptionListElt( NULL ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pSub, &( pElt->link ) ); + } + if (2 <= num_elts) + { + _mqttSubscription_t *pElt = allocate_IotMqttSubscriptionListElt( NULL ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pSub, &( pElt->link ) ); + } + if (3 <= num_elts) + { + _mqttSubscription_t *pElt = allocate_IotMqttSubscriptionListElt( NULL ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pSub, &( pElt->link ) ); + } + + return pSub; +} + +bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, + const size_t length ) +{ + if ( pSub == NULL ) return false; + + IotListDouble_t *pLink; + IotContainers_ForEach( pSub, pLink ) { + _mqttSubscription_t + *pElt = IotLink_Container( _mqttSubscription_t, pLink, link ); + if ( !valid_IotMqttSubscriptionListElt( pElt ) ) return false; + } + + return + // MAX is one greater than the maximum length + length < SUBSCRIPTION_COUNT_MAX; +} + +/**************************************************************** + * IotMqttPublishInfo + ****************************************************************/ + +IotMqttPublishInfo_t *allocate_IotMqttPublishInfo( IotMqttPublishInfo_t *pInfo ) +{ + if ( pInfo == NULL ) pInfo = malloc_can_fail( sizeof( *pInfo ) ); + if ( pInfo == NULL ) return NULL; + + pInfo->pTopicName = malloc_can_fail( pInfo->topicNameLength ); + // assumption here is checked below in valid_ + __CPROVER_assume( VALID_CBMC_SIZE( pInfo->payloadLength ) ); + pInfo->pPayload = malloc_can_fail( pInfo->payloadLength ); + return pInfo; +} + +bool valid_IotMqttPublishInfo( const IotMqttPublishInfo_t *pInfo ) +{ + return + pInfo && + + VALID_STRING( pInfo->pTopicName, pInfo->topicNameLength ) && + VALID_CBMC_SIZE( pInfo->topicNameLength ) && + pInfo->pTopicName != NULL && + + VALID_STRING( pInfo->pPayload, pInfo->payloadLength ) && + VALID_CBMC_SIZE( pInfo->payloadLength ) && + + pInfo->retryMs <= IOT_MQTT_RETRY_MS_CEILING && + pInfo->retryMs > 0 && + + // TODO: experiment with removing these assumptions + // topicNameLength is a unint16 + pInfo->topicNameLength <= UINT16_MAX && + pInfo->payloadLength > 0; +} + +/**************************************************************** + * IotNetworkConnection + ****************************************************************/ + +void *allocate_IotNetworkConnection() +{ + return allocate_opaque_type(); +} + +/**************************************************************** + * IotNetworkInterface + ****************************************************************/ + +IotNetworkInterface_t *allocate_IotNetworkInterface() +{ + return malloc_can_fail( sizeof( IotNetworkInterface_t ) ); +} + +bool valid_IotNetworkInterface( const IotNetworkInterface_t *netif ) +{ + return ( netif != NULL ); +} + +bool stubbed_IotNetworkInterface( const IotNetworkInterface_t *netif ) +{ + return + IS_STUBBED_NETWORKIF_CREATE( netif ) && + IS_STUBBED_NETWORKIF_CLOSE( netif ) && + IS_STUBBED_NETWORKIF_SEND( netif ) && + IS_STUBBED_NETWORKIF_RECEIVE( netif ) && + IS_STUBBED_NETWORKIF_SETRECEIVECALLBACK( netif ) && + IS_STUBBED_NETWORKIF_SETCLOSECALLBACK( netif ) && + IS_STUBBED_NETWORKIF_DESTROY( netif ); +} + +/**************************************************************** + * IotNetworkInterface stubs + ****************************************************************/ + +IotNetworkError_t IotNetworkInterfaceCreate( void * pConnectionInfo, + void * pCredentialInfo, + void * pConnection ) +{ + // pCredentialInfo can be null + // create accepts NULL credentials when there is no TLS configuration. + __CPROVER_assert( pConnectionInfo != NULL, + "IotNetworkInterfaceCreate pConnectionInfo is not NULL" ); + __CPROVER_assert( pConnection != NULL, + "IotNetworkInterfaceCreate pConnection is not NULL" ); + + // create the connection + *(void ** )pConnection = allocate_IotNetworkConnection(); + + IotNetworkError_t error; + return error; +} + +#ifndef MAX_TRIES + #define MAX_TRIES 2 +#endif + +size_t IotNetworkInterfaceSend( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ) +{ + __CPROVER_assert( pConnection != NULL, + "IotNetworkInterfaceSend pConnection is not NULL" ); + __CPROVER_assert( pMessage != NULL, + "IotNetworkInterfaceSend pMessage is not NULL" ); + + /**************************************************************** + * The send method sends some portion of the message and returns the + * total number of bytes in the prefix sent so far. The send method + * is used in a loop of the form + * + * while ( send( conn, msg, len ) < len ) { ... } + * + * We need to bound the number of loop iterations, so we need to + * bound the number of times it takes for send to finish sending the + * message. We use a static variable 'tries' to count the number of + * times send has tried to send the message, and we force send to + * finish the message after MAX_TRIES tries. + ****************************************************************/ + + // The number of tries to send the message before this invocation + static size_t tries; + + // The number of bytes considered sent after this invocation + size_t size; + + if ( tries >= MAX_TRIES || size > messageLength ) + { + tries = 0; + return messageLength; + } + + tries++; + return size; +} + +IotNetworkError_t IotNetworkInterfaceClose( void * pConnection ) +{ + __CPROVER_assert( pConnection != NULL, + "IotNetworkInterfaceClose pConnection is not NULL" ); + + IotNetworkError_t error; + return error; +} + +size_t IotNetworkInterfaceReceive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ) +{ + __CPROVER_assert( pConnection, + "IotNetworkInterfaceReceive pConnection is not NULL" ); + __CPROVER_assert( pBuffer, + "IotNetworkInterfaceReceive pBuffer is not NULL" ); + + // Fill the memory object pointed to by pBuffer with unconstrained + // data. What follows a CBMC idiom to do that. + uint8_t byte; + __CPROVER_array_copy( pBuffer, &byte ); + + // Choose the number of bytes in pBuffer considered received. + size_t size; + __CPROVER_assume( size <= bytesRequested ); + + return size; +} + +IotNetworkError_t IotNetworkInterfaceReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t + receiveCallback, + void * pContext ) +{ + __CPROVER_assert( pConnection != NULL, + "IotNetworkInterfaceCallback pConnection is not NULL" ); + __CPROVER_assert( receiveCallback != NULL, + "IotNetworkInterfaceCallback receiveCallback is not NULL" ); + __CPROVER_assert( pContext != NULL, + "IotNetworkInterfaceCallback pContext is not NULL" ); + + IotNetworkError_t error; + return error; +} + +IotNetworkError_t IotNetworkInterfaceCloseCallback( void * pConnection, + IotNetworkCloseCallback_t + closeCallback, + void * pContext ) +{ + __CPROVER_assert( pConnection != NULL, + "IotNetworkInterfaceCallback pConnection is not NULL" ); + __CPROVER_assert( closeCallback != NULL, + "IotNetworkInterfaceCallback closeCallback is not NULL" ); + __CPROVER_assert( pContext != NULL, + "IotNetworkInterfaceCallback pContext is not NULL" ); + + IotNetworkError_t error; + return error; +} + +IotNetworkError_t IotNetworkInterfaceDestroy( void * pConnection ) +{ + __CPROVER_assert( pConnection != NULL, + "IotNetworkInterfaceDestroy pConnection is not NULL" ); + + IotNetworkError_t error; + return error; +} + +/****************************************************************/ + +IotMqttCallbackInfo_t *allocate_IotMqttCallbackInfo(IotMqttCallbackInfo_t *pCb) +{ + if ( pCb == NULL ) pCb = malloc_can_fail( sizeof( *pCb ) ); + if ( pCb == NULL ) return NULL; + + pCb->pCallbackContext = allocate_opaque_type(); + pCb->function = nondet_bool() ? NULL : IotUserCallback; + + return pCb; +} + +void IotUserCallback( void * pCallbackContext, + IotMqttCallbackParam_t * pCallbackParam ) +{ + __CPROVER_assert( pCallbackContext != NULL, + "IotUserCallback pCallbackContext is not NULL" ); + __CPROVER_assert( pCallbackParam != NULL, + "IotUserCallback pCallbackParam is not NULL" ); + return; +} diff --git a/cbmc/proofs/mqtt_state.h b/cbmc/proofs/mqtt_state.h new file mode 100644 index 0000000000..3d75413aa9 --- /dev/null +++ b/cbmc/proofs/mqtt_state.h @@ -0,0 +1,223 @@ +#include + +/**************************************************************** + * Logical connectives useful in assuptions + ****************************************************************/ + +#define IMPLIES(a, b) (!(a) || (b)) +#define IFF(a, b) (IMPLIES(a, b) && IMPLIES(b, a)) +//#define IFF(a, b) ((a) == (b)) + +/**************************************************************** + * String pointers satisfy an invariant that the pointer is null + * iff the length is zero. The string and length are typically + * members of a struct, and this invariant is part of a validity + * condition for the struct. + ****************************************************************/ + +#define VALID_STRING(string, length) IFF((string) == NULL, (length) == 0) + +/**************************************************************** + * There is a bound on the size of an object that can be modeled in + * CBMC. A point in CBMC consists of an object id and an offset + * into the object. The top few bits of a pointer are used to encode + * the object id, leaving only the bottom remaining bits to encode + * the object offset. The number of bits used for the object id, + * that thus the bound on the size of the object, is defined in the + * Makefile. + ****************************************************************/ + +#define VALID_CBMC_SIZE(size) ((size) < CBMC_MAX_OBJECT_SIZE) + +/**************************************************************** + * Model a malloc that can fail and return NULL. CBMC currently + * models malloc as an allocator that never fails. CBMC will soon + * have an option to let malloc fail. + ****************************************************************/ + +void *malloc_can_fail(size_t size ); + +/**************************************************************** + * Type allocators and validators + * + * For every type used by MQTT, like a connection or an operation, we + * provide an allocator and a validator. The purpose of the allocator + * is simply to allocate on the heap the space for an unconstrained + * value of the right shape. The purpose of the validator is to state + * restrictions on the possible values of the object. + * + * A type is typically a tree of structs and buffers and arrays. The + * allocator lays out space on the heap for this tree. The allocator + * may, however, prune the tree in arbitrary ways by inserting NULL + * into a pointer in a struct intended to point to the children of the + * struct. The allocator may even prune away the entire tree and + * return nothing but a NULL pointer. If a proof requires that some + * portion of the tree is not pruned away (that some pointer is not + * NULL), this assumption must be made explicitly, either in the + * validator or in the proof harness itself. + * + * Each allocator takes a pointer to the struct at the root of the + * tree. If that pointer is NULL, the allocator allocates the root + * struct and the result of the tree. If it points to an existing + * root struct, the allocators uses that root and fills in the rest of + * the tree. The pointer is usually NULL. In constrast, because a + * connection struct includes an ping request operation struct as a + * substruct, we allocate that ping request operation by passing a + * pointer to the connection's operation substruct. + ****************************************************************/ + +/**************************************************************** + * IotMqttConnection + ****************************************************************/ + +IotMqttConnection_t allocate_IotMqttConnection( IotMqttConnection_t pConn ); +bool valid_IotMqttConnection( const IotMqttConnection_t pConn ); +IotMqttConnection_t + ensure_IotMqttConnection_has_lists( IotMqttConnection_t pConn ); + +/**************************************************************** + * MqttOperation + * + * A pending operation includes a pointer to its connection. A + * connection includes a list of all of its pending operations. All + * operations pending on a connection include a pointer to this + * connection. For this reason, the allocator for an operation takes + * a reference to a connection. + ****************************************************************/ + +IotMqttOperation_t allocate_IotMqttOperation( IotMqttOperation_t pOp, + IotMqttConnection_t pConn ); +bool valid_IotMqttOperation( const IotMqttOperation_t pOp ); + +/**************************************************************** + * IotMqttNetworkInfo + ****************************************************************/ + +IotMqttNetworkInfo_t *allocate_IotMqttNetworkInfo( IotMqttNetworkInfo_t *pInfo ); +bool valid_IotMqttNetworkInfo( const IotMqttNetworkInfo_t *pInfo ); + +/**************************************************************** + * IotMqttConnectInfo + ****************************************************************/ + +IotMqttConnectInfo_t *allocate_IotMqttConnectInfo( IotMqttConnectInfo_t *pInfo ); +bool valid_IotMqttConnectInfo( const IotMqttConnectInfo_t *pInfo ); + +/**************************************************************** + * IotMqttSubscription + * + * A client subscribes to a topic represented by a string. For some + * proofs, we need to bound the length of this string (for example, + * when we have to unwind a loop in a function that iterates over the + * string). When we need to bound the length for a proof, we define + * TOPIC_LENGTH_MAX in the proof's Makefile. + ****************************************************************/ + +IotMqttSubscription_t *allocate_IotMqttSubscription( IotMqttSubscription_t *pSub ); +bool valid_IotMqttSubscription( const IotMqttSubscription_t *pSub ); + +/**************************************************************** + * IotMqttSubscription list + * + * There are two kinds of subscription lists in MQTT: array lists and + * linked lists. + * + * For some proofs, we need to bound the length of the list. For + * these proofs, we define SUBSCRIPTION_COUNT_MAX in the proof + * Makefile. + ****************************************************************/ + +// Array lists + +IotMqttSubscription_t *allocate_IotMqttSubscriptionArray( IotMqttSubscription_t *pSub, + size_t length ); +bool valid_IotMqttSubscriptionArray( const IotMqttSubscription_t *pSub, + const size_t length ); + +// Linked lists + +_mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *pElt ); +bool valid_IotMqttSubscriptionListElt( const _mqttSubscription_t *pElt ); +IotListDouble_t *allocate_IotMqttSubscriptionList( IotListDouble_t *pSub, + size_t length ); +bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, + const size_t length ); + +/**************************************************************** + * IotMqttPublishInfo + ****************************************************************/ + +IotMqttPublishInfo_t *allocate_IotMqttPublishInfo( IotMqttPublishInfo_t *pInfo ); +bool valid_IotMqttPublishInfo( const IotMqttPublishInfo_t *pInfo ); + +/**************************************************************** + * IotNetworkConnection + ****************************************************************/ + +void *allocate_IotNetworkConnection(); + +/**************************************************************** + * IotNetworkInterface + * + * The network interface is a struct of fuction pointers that point to + * implementions of the network API. We define a collection of stubs + * for these implementations that do little more that check the + * validity of arguments and generate some minor side effects. + * + * The presence of these stubs can play havoc on CBMC function pointer + * elimination. CBMC considers all functions whose address has been + * taken to be a candiate for the value of a function pointer. So we + * have to be careful not to take the address of these stubs unless we + * have to. In particular, we don't assign them in the allocator or + * validator. + * + * Instead, when a proof requires a stub, we make an explicit + * assumption that the needed struct member is pointing to the correct + * stub. The macro IS_STUBBED_NETWORKIF_XXX(IF) states that the + * method XXX in the interface IF points to the correct stub. + ****************************************************************/ + +IotNetworkInterface_t *allocate_IotNetworkInterface(); +bool valid_IotNetworkInterface( const IotNetworkInterface_t *netif ); +bool stubbed_IotNetworkInterface( const IotNetworkInterface_t *netif ); + +#define IS_STUBBED_NETWORKIF_CREATE(netif) \ + (netif->create == IotNetworkInterfaceCreate) +#define IS_STUBBED_NETWORKIF_CLOSE(netif) \ + (netif->close == IotNetworkInterfaceClose) +#define IS_STUBBED_NETWORKIF_SEND(netif) \ + (netif->send == IotNetworkInterfaceSend) +#define IS_STUBBED_NETWORKIF_RECEIVE(netif) \ + (netif->receive == IotNetworkInterfaceReceive) +#define IS_STUBBED_NETWORKIF_SETRECEIVECALLBACK(netif) \ + (netif->setReceiveCallback == IotNetworkInterfaceReceiveCallback) +#define IS_STUBBED_NETWORKIF_SETCLOSECALLBACK(netif) \ + (netif->setCloseCallback == IotNetworkInterfaceCloseCallback) +#define IS_STUBBED_NETWORKIF_DESTROY(netif) \ + (netif->destroy == IotNetworkInterfaceDestroy) + +IotNetworkError_t IotNetworkInterfaceCreate( void * pConnectionInfo, + void * pCredentialInfo, + void * pConnection ); +size_t IotNetworkInterfaceSend( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ); +IotNetworkError_t IotNetworkInterfaceClose( void * pConnection ); +size_t IotNetworkInterfaceReceive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ); +IotNetworkError_t IotNetworkInterfaceReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t + receiveCallback, + void * pContext ); +IotNetworkError_t IotNetworkInterfaceCloseCallback( void * pConnection, + IotNetworkCloseCallback_t + closeCallback, + void * pContext ); +IotNetworkError_t IotNetworkInterfaceDestroy( void * pConnection ); + +/****************************************************************/ + +IotMqttCallbackInfo_t *allocate_IotMqttCallbackInfo(IotMqttCallbackInfo_t *pCb); +void IotUserCallback( void * pCallbackContext, + IotMqttCallbackParam_t * pCallbackParam ); From 8fbe1c0001dcac8c64f70cd1ec10a6791bd54b1e Mon Sep 17 00:00:00 2001 From: Kareem Khazem Date: Wed, 12 Feb 2020 11:24:18 -0500 Subject: [PATCH 426/844] Apply patches before running proofs prepare.py will now apply any patch files in the cbmc/patches directory before starting the proof. Patches to the codebase are necessary for some of the proofs to run. --- cbmc/patches/README.md | 6 ++++++ cbmc/proofs/prepare.py | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 cbmc/patches/README.md diff --git a/cbmc/patches/README.md b/cbmc/patches/README.md new file mode 100644 index 0000000000..fbf485a1ef --- /dev/null +++ b/cbmc/patches/README.md @@ -0,0 +1,6 @@ +Patches for Proofs +================== + +This directory contains patch files that must be applied to the codebase +before running CBMC proofs. The `cbmc/proofs/prepare.py` script takes +care of applying these patches. diff --git a/cbmc/proofs/prepare.py b/cbmc/proofs/prepare.py index 205d4fee12..31aa567e43 100755 --- a/cbmc/proofs/prepare.py +++ b/cbmc/proofs/prepare.py @@ -24,16 +24,52 @@ import logging import os +import pathlib +import subprocess import sys import textwrap -from subprocess import CalledProcessError from make_cbmc_batch_files import create_cbmc_yaml_files + +def apply_patches(): + patch_dir = pathlib.Path(__file__).resolve().parent.parent / "patches" + if not patch_dir.is_dir(): + logging.error("Patches directory at '%s' is not a directory", patch_dir) + sys.exit(1) + + proj_dir = pathlib.Path(__file__).resolve().parent.parent.parent + if not proj_dir.is_dir(): + logging.error("Root of project at '%s' is not a directory", proj_dir) + sys.exit(1) + if not (proj_dir / "LICENSE").exists(): + logging.error( + "Directory '%s' doesn't seem to be root of project", proj_dir) + sys.exit(1) + + for fyle in sorted(patch_dir.glob("*.patch")): + logging.info("Checking patch '%s'", fyle.name) + cmd = ["git", "apply", "--check", str(fyle)] + proc = subprocess.run( + cmd, cwd=proj_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + universal_newlines=True) + if proc.returncode: + logging.warning( + "Patch checking failed. Check output:\n%s", + "\n".join([" %s" % line for line in proc.stdout])) + + logging.info("Applying patch '%s'", fyle.name) + cmd = ["git", "apply", str(fyle)] + proc = subprocess.run(cmd, cwd=proj_dir) + if proc.returncode: + logging.error("Failed to apply patch '%s'", fyle.name) + sys.exit(1) + + def build(): try: create_cbmc_yaml_files() - except CalledProcessError as e: + except subprocess.CalledProcessError as e: logging.error(textwrap.dedent("""\ An error occured during cbmc-batch generation. The error message is: {} @@ -46,3 +82,4 @@ def build(): logging.basicConfig(format="{script}: %(levelname)s %(message)s".format( script=os.path.basename(__file__))) build() + apply_patches() From 95a52e516996c5b3338a49987054ca178c541596 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Thu, 13 Feb 2020 15:35:31 -0500 Subject: [PATCH 427/844] Adds a proof harness for the IotMqtt_Init function (#788) * Adds a .gitignore file to cbmc proofs dir Signed-off-by: Felipe R. Monteiro * Update Makefile to include atomics and new CBMC flags Signed-off-by: Felipe R. Monteiro * Adds a proof harness for the IotMqtt_Init function Signed-off-by: Felipe R. Monteiro * Only apply atomic patch in the proofs that need it Signed-off-by: Felipe R. Monteiro * Remove unnecessary white-space at the end of this file Signed-off-by: Felipe R. Monteiro * Check if the status output is either IOT_MQTT_SUCCESS or IOT_MQTT_NOT_INITIALIZED Signed-off-by: Felipe R. Monteiro * Adds an comment about the abstractions in the proof Signed-off-by: Felipe R. Monteiro * Update cbmc-batch.yaml parameters Signed-off-by: Felipe R. Monteiro * Alphabatize Makefile instructions Signed-off-by: Felipe R. Monteiro * Update cbmc-batch.yaml file generation Signed-off-by: Felipe R. Monteiro * Do not mess with the actual file tree of MQTT Signed-off-by: Felipe R. Monteiro * Only add to the .gitignore what is related to current state of the development project Signed-off-by: Felipe R. Monteiro --- cbmc/.gitignore | 2 + .../IotMqtt_Init/IotMqtt_Init_harness.c | 11 +++++ cbmc/proofs/IotMqtt_Init/Makefile | 12 +++++ cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml | 4 ++ cbmc/proofs/IotMqtt_Init/cbmc-viewer.json | 22 +++++++++ cbmc/proofs/Makefile.common | 46 +++++++++++++------ 6 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 cbmc/.gitignore create mode 100644 cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c create mode 100644 cbmc/proofs/IotMqtt_Init/Makefile create mode 100644 cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_Init/cbmc-viewer.json diff --git a/cbmc/.gitignore b/cbmc/.gitignore new file mode 100644 index 0000000000..031b1db6c9 --- /dev/null +++ b/cbmc/.gitignore @@ -0,0 +1,2 @@ +*.csv +*.log diff --git a/cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c b/cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c new file mode 100644 index 0000000000..d3f73e87bf --- /dev/null +++ b/cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c @@ -0,0 +1,11 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include +#include + +void harness() +{ + IotMqttError_t status = IotMqtt_Init(); + assert(status == IOT_MQTT_SUCCESS || status == IOT_MQTT_NOT_INITIALIZED); +} diff --git a/cbmc/proofs/IotMqtt_Init/Makefile b/cbmc/proofs/IotMqtt_Init/Makefile new file mode 100644 index 0000000000..84491fbba2 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Init/Makefile @@ -0,0 +1,12 @@ +ENTRY=IotMqtt_Init + +# We abstract all the log and concurrency related functions in this proof +# and assume their implementation is correct +ABSTRACTIONS += --remove-function-body IotLog_Generic + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto + +UNWINDING += --unwind 1 + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml new file mode 100644 index 0000000000..0233491a13 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--unwind;1;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" +expected: SUCCESSFUL +goto: IotMqtt_Init.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_Init/cbmc-viewer.json b/cbmc/proofs/IotMqtt_Init/cbmc-viewer.json new file mode 100644 index 0000000000..e4ce3e59d8 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Init/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_Init", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index d0de15a72b..e33e090426 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -14,23 +14,25 @@ GOTO_ANALYZER ?= goto-analyzer BATCH ?= cbmc-batch VIEWER ?= cbmc-viewer + ################################################################ # Build goto binary for cbmc # Build goto binaries with options taken from top-level cmake -INC = \ +INC += \ -I$(MQTT)/libraries/standard/mqtt/include \ -I$(MQTT)/demos \ -I$(MQTT)/libraries/platform/.. \ -I$(MQTT)/libraries/platform \ -I$(MQTT)/ports/common/include \ -I$(MQTT)/libraries/standard/common/include \ - \ -I$(MQTT)/libraries/standard/mqtt/src \ DEF += \ -DIOT_SYSTEM_TYPES_FILE=\"$(MQTT)/ports/posix/include/iot_platform_types_posix.h\" \ -Diotmqtt_EXPORTS \ + -DCBMC_OBJECT_BITS=$(CBMC_OBJECT_BITS) \ + -DCBMC_MAX_OBJECT_SIZE=$(CBMC_MAX_OBJECT_SIZE) \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 @@ -38,26 +40,32 @@ CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 $(GOTO_CC) -o $@ $(CFLAGS) $< $(MQTT)/build: - (mkdir -p $(MQTT)/build; cd $(MQTT)/build; cmake ..) 2>&1 \ - | tee $(ENTRY)0.txt \ - ; exit $${PIPESTATUS[0]} + (mkdir -p $(MQTT)/build; cd $(MQTT)/build; cmake ..) \ + > $(ENTRY)0.txt 2>&1 $(ENTRY)1.goto: $(MQTT)/build $(OBJS) - $(GOTO_CC) --function harness -o $@ $(OBJS) 2>&1 \ - | tee $(ENTRY)1.txt \ - ; exit $${PIPESTATUS[0]} + $(GOTO_CC) --function harness -o $@ $(OBJS) + > $(ENTRY)1.txt 2>&1 $(ENTRY)2.goto: $(ENTRY)1.goto $(GOTO_INSTRUMENT) \ $(ABSTRACTIONS) \ --drop-unused-functions \ - --slice-global-inits $< $@ 2>&1 \ - | tee $(ENTRY)2.txt \ - ; exit $${PIPESTATUS[0]} + --slice-global-inits $< $@ \ + > $(ENTRY)2.txt 2>&1 $(ENTRY).goto: $(ENTRY)2.goto cp $< $@ + +################################################################ +# Set C compiler defines + +CBMC_OBJECT_BITS ?= 8 +CBMCFLAGS += --object-bits $(CBMC_OBJECT_BITS) +CBMC_MAX_OBJECT_SIZE = "(SIZE_MAX>>(CBMC_OBJECT_BITS+1))" + + ################################################################ # Run cbmc and build html report @@ -66,6 +74,14 @@ CBMCFLAGS += \ --bounds-check \ --pointer-check \ --unwinding-assertions \ + --nondet-static \ + --div-by-zero-check \ + --float-overflow-check \ + --nan-check \ + --pointer-overflow-check \ + --undefined-shift-check \ + --signed-overflow-check \ + --unsigned-overflow-check \ goto: $(ENTRY).goto @@ -103,7 +119,7 @@ clean: $(RM) *~ \#* veryclean: clean - $(RM) -r html + $(RM) -r html TAGS-* .PHONY: cbmc property coverage report clean veryclean @@ -186,9 +202,9 @@ launch-veryclean: launch-clean cbmc-batch.yaml: Makefile ../Makefile.common @echo "Building $@" @$(RM) $@ - @echo "jobos: $(JOBOS)" >> $@ - @echo "cbmcflags: $(call encode_options,$(CBMCFLAGS))" >> $@ - @echo "goto: $(ENTRY).goto" >> $@ + @echo 'cbmcflags: $(strip $(call yaml_encode_options,$(CBMCFLAGS)))' > $@ @echo "expected: SUCCESSFUL" >> $@ + @echo "goto: $(ENTRY).goto" >> $@ + @echo "jobos: $(JOBOS)" >> $@ ################################################################ From 97b9f9d7bab25b0eee531c9c7eee3cfbbd99e00e Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Thu, 13 Feb 2020 16:51:06 -0500 Subject: [PATCH 428/844] Adds a proof harness for the IotMqtt_IsSubscribed function (#794) * Avoids undefined behavior when calling strncmp function Signed-off-by: Felipe R. Monteiro * Adds a proof harness for the IotMqtt_IsSubscribed function Signed-off-by: Felipe R. Monteiro * APIs always assume that a valid IotMqttConnection_t is passed Signed-off-by: Felipe R. Monteiro * Update cbmc-batch.yaml for the IotMqtt_IsSubscribed proof harness Signed-off-by: Felipe R. Monteiro * Clean up IotMqtt_IsSubscribed proof harness Signed-off-by: Felipe R. Monteiro * Assume in the proof harness that a valid IotMqttConnection_t is always passed Signed-off-by: Felipe R. Monteiro * Fix typo on cbmc-batch.yaml Signed-off-by: Felipe R. Monteiro * Fix cbmc-batch.yaml file generation Signed-off-by: Felipe R. Monteiro * Fix typo on the proof harness Signed-off-by: Felipe R. Monteiro * _topicMatch expects its caller to perform parameter checks Signed-off-by: Felipe R. Monteiro * Improving comments on the IotMqtt_IsSubscribed proof harness Signed-off-by: Felipe R. Monteiro * Revert: Avoids undefined behavior when calling strncmp function Signed-off-by: Felipe R. Monteiro --- .../IotMqtt_IsSubscribed_harness.c | 31 +++++++++++++ cbmc/proofs/IotMqtt_IsSubscribed/Makefile | 45 +++++++++++++++++++ .../IotMqtt_IsSubscribed/cbmc-batch.yaml | 4 ++ .../IotMqtt_IsSubscribed/cbmc-viewer.json | 22 +++++++++ cbmc/proofs/Makefile.common | 5 ++- 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c create mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/Makefile create mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c b/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c new file mode 100644 index 0000000000..7b0d3ad12e --- /dev/null +++ b/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c @@ -0,0 +1,31 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include +#include + +#include "mqtt_state.h" + +void harness() +{ + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection(NULL); + __CPROVER_assume(mqttConnection); + ensure_IotMqttConnection_has_lists(mqttConnection); + __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); + + allocate_IotMqttSubscriptionList(&(mqttConnection->subscriptionList), 1); + __CPROVER_assume(valid_IotMqttSubscriptionList(&(mqttConnection->subscriptionList), 1)); + + uint16_t topicFilterLength; + __CPROVER_assume(topicFilterLength < TOPIC_LENGTH_MAX); + + char* TopicFilter = malloc_can_fail(topicFilterLength); + __CPROVER_assume( VALID_STRING(TopicFilter, topicFilterLength) ); + + IotMqttSubscription_t result; + + IotMqtt_IsSubscribed( mqttConnection, + TopicFilter, + topicFilterLength, + nondet_bool() ? NULL : &result ); +} diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/Makefile b/cbmc/proofs/IotMqtt_IsSubscribed/Makefile new file mode 100644 index 0000000000..7de04ac573 --- /dev/null +++ b/cbmc/proofs/IotMqtt_IsSubscribed/Makefile @@ -0,0 +1,45 @@ +ENTRY=IotMqtt_IsSubscribed + +ABSTRACTIONS += --remove-function-body IotLog_Generic + +# Remove for coverage +ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe +ABSTRACTIONS += --remove-function-body _matchEndWildcards +ABSTRACTIONS += --remove-function-body _matchWildcards +ABSTRACTIONS += --remove-function-body _packetMatch +ABSTRACTIONS += --remove-function-body _topicFilterMatch + + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto + +# One more than actual number of subscriptions in a subscription list +SUBSCRIPTION_COUNT_MAX=4 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# One more than actual number of operations in an operation list +OPERATION_COUNT_MAX=4 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# One more than actual length of topics +TOPIC_LENGTH_MAX=6 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +# Should be 2*SUBSCRIPTION_COUNT_MAX-1 +SUBSCRIPTION_LIST_MAX=6 +DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) + +LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += allocate_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttOperationList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml new file mode 100644 index 0000000000..a6b1ba20e3 --- /dev/null +++ b/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: "--object-bits;8;--unwind;1;--unwindset;allocate_IotMqttSubscriptionArray.0:4,valid_IotMqttSubscriptionArray.0:4,allocate_IotMqttSubscriptionList.0:4,valid_IotMqttSubscriptionList.0:4,valid_IotMqttOperationList.0:4,IotListDouble_FindFirstMatch$link1.0:4,strncmp.0:6;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" +goto: IotMqtt_IsSubscribed.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json b/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json new file mode 100644 index 0000000000..8a8e256886 --- /dev/null +++ b/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_IsSubscribed", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index e33e090426..b99871aaae 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -27,6 +27,7 @@ INC += \ -I$(MQTT)/ports/common/include \ -I$(MQTT)/libraries/standard/common/include \ -I$(MQTT)/libraries/standard/mqtt/src \ + -I$(MQTT)/cbmc/proofs \ DEF += \ -DIOT_SYSTEM_TYPES_FILE=\"$(MQTT)/ports/posix/include/iot_platform_types_posix.h\" \ @@ -86,7 +87,8 @@ CBMCFLAGS += \ goto: $(ENTRY).goto cbmc.txt: $(ENTRY).goto - cbmc $(CBMCFLAGS) --trace $< 2>&1 | tee $@ + @echo cbmc $(CBMCFLAGS) --trace $< 2>&1 | tee $@ + cbmc $(CBMCFLAGS) --trace $< 2>&1 | tee -a $@ property.xml: $(ENTRY).goto cbmc $(CBMCFLAGS) --show-properties --xml-ui $< 2>&1 > $@ @@ -199,6 +201,7 @@ launch-veryclean: launch-clean ################################################################ # Build configuration file to run cbmc under cbmc-batch in CI +CBMC_FLAGS = '$(call encode_options,$(CBMCFLAGS))' cbmc-batch.yaml: Makefile ../Makefile.common @echo "Building $@" @$(RM) $@ From ac1e6b7101a05eddc102cb2aa2bec20642ff71d0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 14 Feb 2020 10:32:02 -0800 Subject: [PATCH 429/844] Update documentation (#802) --- README.md | 4 +- doc/lib/mqtt.txt | 59 +++++++++--------- doc/lib/platform.txt | 2 +- doc/plantuml/images/mqtt_design_keepalive.png | Bin 82140 -> 0 bytes .../images/mqtt_design_typicaloperation.png | Bin 50276 -> 71961 bytes doc/plantuml/images/mqtt_memory_example.png | Bin 27841 -> 32407 bytes doc/plantuml/mqtt_design_keepalive.pu | 57 ----------------- doc/plantuml/mqtt_design_typicaloperation.pu | 22 +++++-- .../serializer/include/iot_serializer.h | 7 +-- 9 files changed, 49 insertions(+), 102 deletions(-) delete mode 100644 doc/plantuml/images/mqtt_design_keepalive.png delete mode 100644 doc/plantuml/mqtt_design_keepalive.pu diff --git a/README.md b/README.md index 81a54ab31e..7342c9abad 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be built into firmware along with application code. -This library supersedes both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. +This library supersedes both the AWS IoT Device SDK Embedded C and the libraries provided with FreeRTOS. ## Features -This library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. In addition, it provides the following new features: +This library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with FreeRTOS. In addition, it provides the following new features: - Asynchronous API for both MQTT and Thing Shadow. - Multithreaded API by default (removed the `yield` function). - More efficient platform layer (especially timers). diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index a31e769653..eb1a04ab45 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -7,9 +7,8 @@ Official description of MQTT from [mqtt.org](http://mqtt.org)
-This MQTT library implements a subset of the MQTT 3.1.1 standard for compatibility with the [AWS IoT MQTT server](https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html#mqtt). It is also compatible with other MQTT servers. Features of this library include: +This MQTT library implements a subset of the MQTT 3.1.1 standard. Features of this library include: - Both fully asynchronous and blocking API functions. -- Thread-aware and parallelizable for high throughput. - Scalable performance and footprint. The [configuration settings](@ref mqtt_config) allow this library to be tailored to a system's resources. @attention Currently, this library does not support QoS 2 MQTT messages. @@ -28,7 +27,7 @@ digraph mqtt_dependencies { node[fillcolor="#aed8a9ff"]; rank = same; - linear_containers[label="List/Queue", URL="@ref linear_containers"]; + linear_containers[label="Linear containers", URL="@ref linear_containers"]; logging[label="Logging", URL="@ref logging"]; static_memory[label="Static memory", URL="@ref static_memory"]; } @@ -48,7 +47,7 @@ digraph mqtt_dependencies @enddot Currently, the MQTT library has the following dependencies: -- The queue library for maintaining the data structures for managing in-progress MQTT operations. +- The linear containers library for maintaining the data structures for managing in-progress MQTT operations. - The logging library may be used if @ref IOT_LOG_LEVEL_MQTT is not #IOT_LOG_NONE. - The task pool to submit background jobs. The MQTT library does not create or manage any threads, but relies on at least one thread being available in the task pool. @@ -62,8 +61,8 @@ In addition to the components above, the MQTT library also depends on C standard The following measurements were taken with arm-none-eabi-gcc v9.2.1 (October 2019) with the [MQTT library from January 2020](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/f1a8b698b1d2fd5b8475b11a16d399181c1d867f). These values are rough estimates not tuned for any specific ARM processor. -Debug Build
-All logging, asserts, and diagnostic information enabled. Debugging symbols available. Compiled using the ARM Thumb instruction set (`-mthumb`). Values are in bytes. +Minimal Size Build
+No logging, asserts, or diagnostic information. Compiled using the ARM Thumb instruction set (`-mthumb`) and optimized for size (`-Os`). Values are in bytes. @@ -74,50 +73,50 @@ All logging, asserts, and diagnostic information enabled. Debugging symbols avai - + - + - + - - + + - + - + - - + +
iot_mqtt_api118972703 0 4
iot_mqtt_network5412884 0 0
iot_mqtt_operation104841773 0 0
iot_mqtt_serialize144542826064 0
iot_mqtt_subscription38851244 0 0
iot_mqtt_validate4330444 0 0
total504622896544 4
-Minimal Size Build
-No logging, asserts, or diagnostic information. Compiled using the ARM Thumb instruction set (`-mthumb`) and optimized for size (`-Os`). Values are in bytes. +Debug Build
+All logging, asserts, and diagnostic information enabled. Debugging symbols available. Compiled using the ARM Thumb instruction set (`-mthumb`). Values are in bytes. @@ -128,44 +127,44 @@ No logging, asserts, or diagnostic information. Compiled using the ARM Thumb ins - + - + - + - - + + - + - + - - + +
iot_mqtt_api270311897 0 4
iot_mqtt_network8845412 0 0
iot_mqtt_operation177310484 0 0
iot_mqtt_serialize260641445428 0
iot_mqtt_subscription12443885 0 0
iot_mqtt_validate4444330 0 0
total965445046228 4
@@ -230,14 +229,14 @@ Some operations, such as QoS 1 PUBLISHes, may be retried. See @ref IotMqttPublis @image html mqtt_design_typicaloperation.png width=80% @section mqtt_design_subscriptions Subscriptions -@brief The following diagram shows the lifecycle of MQTT subscriptions. +@brief This section provides a description of how the library handles subscriptions. MQTT subscriptions are added with @ref mqtt_function_subscribeasync or @ref mqtt_function_subscribesync; subscriptions are removed with @ref mqtt_function_unsubscribesync or @ref mqtt_function_unsubscribeasync. Subscriptions are associated with a callback function that will be invoked by the library every time a message matching the associated topic filter is received. For example, if a connection has subscriptions for `#` (which matches everything) and `test`, and a message is received on `test`, the callback functions for both `#` and `test` will be invoked. Callbacks are invoked from the context of a background task pool threads. Because incoming messages are asynchronous (may arrive at any time), subscription callbacks are also asynchronous. This library supports the use of MQTT persistent sessions. Persistent sessions cause the MQTT server to store subscriptions and undelivered messages. When re-establishing a persistent session, the client should set @ref IotMqttConnectInfo_t.pPreviousSubscriptions and @ref IotMqttConnectInfo_t.previousSubscriptionCount to restore a list of sessions that were present in the persistent session. Setting these members does not send an MQTT SUBSCRIBE packet to the server, so they may only be used with topics that are active on the server. @section mqtt_design_keepalive Periodic keep-alive -@brief The following diagram shows the workflow of the periodic keep-alive. +@brief This section provides a description of the workflow of periodic keep-alive. The MQTT standard specifies a keep-alive mechanism to detect half-open or otherwise unusable network connections. An MQTT client will send periodic ping requests (PINGREQ) to the server if the connection is idle. The MQTT server must respond to ping requests with a ping response (PINGRESP). @@ -251,8 +250,6 @@ Once MQTT CONNECT returns to the application, the keep-alive is processed entire 3. A PINGREQ is sent to the server. 4. If the connection is alive, the server will respond with a PINGRESP. When the client receives the PINGRESP, it sets `failure` to `0`. 5. After @ref IOT_MQTT_RESPONSE_WAIT_MS, the client checks the `failure` flag. If `failure=0`, then a PINGRESP was received and the next PINGREQ is scheduled. Otherwise, if `failure=1`, then no PINGRESP was received and the connection terminates. - -@image html mqtt_design_keepalive.png width=80% */ /** @@ -381,7 +378,7 @@ Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SD @section IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES @brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. -Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #IotMqttSerializer_t for a list of functions that can be overridden. If this setting is `1`, the [initialization](@ref mqtt_function_init) and [cleanup](@ref mqtt_function_cleanup) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default initialization and cleanup functions. They must have the following signatures: +Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on FreeRTOS. See #IotMqttSerializer_t for a list of functions that can be overridden. If this setting is `1`, the [initialization](@ref mqtt_function_init) and [cleanup](@ref mqtt_function_cleanup) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default initialization and cleanup functions. They must have the following signatures: @code{c} #include diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 919e28d71b..eee32b2aa8 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -22,7 +22,7 @@ Component | Supported platforms --------- | ------------------- @ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) -@ref platform_network | [mbed TLS](https://tls.mbed.org/)
[Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) +@ref platform_network | [mbed TLS](https://tls.mbed.org/)
[Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_metrics | Sample implementation using the @ref platform_network abstraction.
This implementation is not intended for production use. @ref platform_atomic | GNU compilers (gcc/clang)
Generic implementation using a @ref platform_threads mutex. */ diff --git a/doc/plantuml/images/mqtt_design_keepalive.png b/doc/plantuml/images/mqtt_design_keepalive.png deleted file mode 100644 index 9bb0dda47cae3563f7274f61cab0e756a872d06a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82140 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≦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 diff --git a/doc/plantuml/images/mqtt_design_typicaloperation.png b/doc/plantuml/images/mqtt_design_typicaloperation.png index 068824408e03df225dcc49eedc6edd17e7fa3bb9..dd139b843a46988427b0ff3df235c0935ceb7e9d 100644 GIT binary patch literal 71961 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ℜ`0dkw3fwe}oZUlc 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%0DDeJX~eaAwPKryM??CK_68K;Xp$ml+o zI4CNA zprVG_R_uQ9=4xjqu&uKcHmWonMtwY)zoT}k$~gEWKpc{p@*#uF#I_lP#dtP| zrCG{~eN6seS63bm<=Vzyj3k7QB3a5gAyalI8j&U2VT}4}Q1&f4j%aLSn{*^0vYe7_ zteHe74u`RvDO*yO1{Gm^62>l5)|}^^nH;|BdawCou6OQvp67n=XYS|s``!06%@Xk$ z|I<9)k9{n$5{l+a1}+vF;lO>zv5?gw_cvGKn=hK|k+YaYMY!dV8l^93t*heSK<900 zS{?xW;PVJ#DEx$>vR=qyZ@K%=v15Iu2R+?SIrn#0Kk=%x%#d4Jr?$6=U&2qG!AsTV z&r^7fUZ8Ty9Bb`WVASb`+-qsAh_t_+Zq2e+>{Mg_#@G0zvK%RX*N;WEa*7hN-nw*=W`v1AM2|z$<3} zcfgB<5bn(=5scw@ASXb-h@zetV+uc}g{$YV6PMbq?!nwpjfF0wsrr;yB$|-yKQ-EA zmo6O>XjMxrxgbx}!=%Hm(DLl~j4FC;ujf4JZSFwbue5Cv4O4`jlSso>iL2(l#X8on zug%S^gOqvW05-Wt>=JqH*Eh{66Up9a?0{haWe0iTLgdyfhhH6++hERsZu zk;R7SW4G7%(5D5mK%(!jt`ize;dk4chvO;c8WbigzT!{S9qlFm^R{NT((mRj4KEkxN9K)^j@b-W+ z#lzo4m)Y~O(9yWNm?2zi%s-$T?=MB%i!+&M=z?7`6X(?zDxdNqlL7*sn%`O}kN#y7 zqzZ1H%06Ahn+(6pgMQ;3?rDl?!&N@t($#DA)>TSEzuw8K16vO`T$^rIB|1bl-n1o= z_FY!qXhw}5Yz#C3sMghvMSC6v=d91`cL0*B5V_5fe{J5{;mH4jGz}zni&U%7;gIpk zRYnOsXM;*_G4WyXh=t~8IKq07k~-B5=4q+&Ig&>%Bv5!W^?>8@vvpKXIs4j~(R=Sl zE9IQL4l0U2zFIup$)saDW)KR^7Hb*_{;&P!67!9=x%BnAM-gMF?MF3vP@I1yXn0Ut zyT$@++IE%RM!aN(pE$0SG35#1v_aW3d#LB;Cm%kXhz4hJMH>}#k4B$k-SWZ%O(aOY zyH>GA2{F6v{v)czF^+jerkCDGtx#%<$VkT0B* z0RMF`-s3fWpOU0i`bJ$yW2t(!pZ+E?Pov_vqb{1gpCdtfQw5r}SY)lq#98I{ z-)Sm9te_}x2Q~`3w;S}~IV+oiK5&4-NeJ4-Zs;NvWDL8D>_GgT3jRYDpZTvnX&v##(Z>aGIgnLf_JU&%n?I78he3tA$ zrh_Lg19W#etpYDkA&TRNP^$T_2hJUF{Uz>vO6^&>86UZzLcZMg+_;mvPlyZ`j(^uR z2aeSDYHsij8jqHl@#XyKkgJfrvOS~8P-Gma*M<1ubpSF(z<{_}nf7ZGogC`|==v88 z953!LP8(+pGY881w%iujZ#mIMkDvJ$b&}EW2BlT6VAO5%sOingv2!4hNyS%@r2E{2JZ9VP*NBU{)EHhb}^aB=sxEtB|uG3ux7 zqXdJo9tv~9KL?yms&VL+MW9B*?jgjnr4xiTj6voXFA4XS(IFX=qve0+UMWBpP8F79 z1If$4?Mznm z|LH+B5Ilry@r{bN%d^EH>n2I!P%48ALBg)mPD!||F~p(WQOxVMFx+|xp${(1F+e@C z-i>krQc>2sLZWd%?Z&#DXak~&d9M(nIS&*&vy3S02h;{^0bEZ(3&iiH&ur-VEKMJo TDtY+^Q0I`@8MINEfm75!)kKvH literal 50276 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 G{{H~^D`YVM diff --git a/doc/plantuml/mqtt_design_keepalive.pu b/doc/plantuml/mqtt_design_keepalive.pu deleted file mode 100644 index 75dd383187..0000000000 --- a/doc/plantuml/mqtt_design_keepalive.pu +++ /dev/null @@ -1,57 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -box "Application" #LightGreen - participant "Application code" as application - create participant "MQTT CONNECT" as api -end box - -box "Task pool" #LightBlue - participant "Task pool jobs" as task_pool -end box - -box "Network stack" #Orange - participant "Receive\ncallback" as receive_callback - participant "Network IO" as network -end box - -== Create PINGREQ (one-time) == -activate application -application -> api: Call MQTT CONNECT -deactivate application -activate api -api -> api: Initialize failure=0 -api -> api: Generate PINGREQ -api -> task_pool: Schedule PINGREQ\nsend in keepAliveSec -api -> application: Return -deactivate api -activate application - -== Periodic keep-alive == - -loop While connection is open - ... After keepAliveSec ... - task_pool -> task_pool: Set failure=1 - task_pool -> network: Send PINGREQ - network -> : PINGREQ\nto server - task_pool -> task_pool: Schedule check in\nIOT_MQTT_RESPONSE_WAIT_MS - - alt Connection is alive - network <- : PINGRESP\nfrom server - network -> receive_callback: Notify of PINGRESP - receive_callback -> receive_callback: Set failure=0 - end - - ... After IOT_MQTT_RESPONSE_WAIT_MS ... - alt failure=0 - task_pool -> task_pool: Schedule next PINGREQ\nin keepAliveSec - else failure=1 - task_pool -> application: No response, terminate connection - end -end - -deactivate application - -@enduml diff --git a/doc/plantuml/mqtt_design_typicaloperation.pu b/doc/plantuml/mqtt_design_typicaloperation.pu index b06dbad484..5bbe065d70 100644 --- a/doc/plantuml/mqtt_design_typicaloperation.pu +++ b/doc/plantuml/mqtt_design_typicaloperation.pu @@ -4,10 +4,14 @@ skinparam classFontName Helvetica autonumber box "Application" #LightGreen - participant "Application code" as application + participant "Application thread" as application create participant "MQTT API\nfunction" as api end box +box "Task pool\nThe task pool processes\nbackground work without\nblocking the application.\nIt also invokes asynchronous\ncallbacks.\n" #LightBlue + participant "Task pool jobs" as task_pool +end box + box "Network stack" #Orange participant "Receive\ncallback" as receive_callback participant "Network IO" as network @@ -19,22 +23,30 @@ application -> api: Call MQTT\noperation\nfunction deactivate application activate api api -> api: Allocate resources\nand generate\nMQTT packet -api -> network: Transmit\n MQTT packet -api -> api: Mark operation\nas awaiting response +api -> task_pool: Post send job +activate task_pool api -> application: Return\nSTATUS_PENDING destroy api activate application +task_pool -> network: Transmit\n MQTT packet activate network +task_pool -> task_pool: Mark operation\nas awaiting response network -> : Send data to server +deactivate task_pool -== Parse Server Response == network <- : Server response + +== Parse Server Response == network -> receive_callback: Notify of response deactivate network activate receive_callback receive_callback -> receive_callback: Parse incoming packet -receive_callback -> application: Notify of result +receive_callback -> task_pool: Post notify job deactivate receive_callback +activate task_pool +task_pool -> application: Notify of result + +deactivate task_pool deactivate application @enduml diff --git a/libraries/standard/serializer/include/iot_serializer.h b/libraries/standard/serializer/include/iot_serializer.h index 948868ebe0..4a3588f3f6 100644 --- a/libraries/standard/serializer/include/iot_serializer.h +++ b/libraries/standard/serializer/include/iot_serializer.h @@ -1,7 +1,6 @@ /* * IoT Serializer V1.1.0 - * Amazon FreeRTOS System Initialization - * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,11 +18,7 @@ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://aws.amazon.com/freertos - * http://www.FreeRTOS.org */ - /* * This library is to provide a consistent layer for serializing JSON-like format. * The implementations can be CBOR or JSON. From c262e5469546c27d1a00070d48647bcd0e1fea72 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Fri, 14 Feb 2020 16:36:51 -0500 Subject: [PATCH 430/844] Adds a proof harness for the IotMqtt_Cleanup function (#789) --- .../IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c | 23 +++++++++++++++++++ cbmc/proofs/IotMqtt_Cleanup/Makefile | 12 ++++++++++ cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml | 4 ++++ cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json | 22 ++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c create mode 100644 cbmc/proofs/IotMqtt_Cleanup/Makefile create mode 100644 cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c b/cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c new file mode 100644 index 0000000000..c87fae1a33 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c @@ -0,0 +1,23 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include +#include + +void harness() +{ + /* + * The code specification says that IotMqtt_Cleanup must be + * called after IotMqtt_Init, since it is supposed to undo + * anything that IotMqtt_Init does. Both IotMqtt_Cleanup and + * IotMqtt_Init basically update the status of the same global + * variable _initCalled. This variable is declared as volatile, + * so CBMC will always treat it as non-deterministic. + * Thus, even if we called IotMqtt_Init first, it does not make + * a significant impact in this proof harness. However, since + * the developers may change the implementation of both + * functions in the future, we should keep both functions in the proof. + */ + IotMqtt_Init(); + IotMqtt_Cleanup(); +} diff --git a/cbmc/proofs/IotMqtt_Cleanup/Makefile b/cbmc/proofs/IotMqtt_Cleanup/Makefile new file mode 100644 index 0000000000..b8ffb296aa --- /dev/null +++ b/cbmc/proofs/IotMqtt_Cleanup/Makefile @@ -0,0 +1,12 @@ +ENTRY=IotMqtt_Cleanup + +# We abstract all the log and concurrency related functions in this proof +# and assume their implementation is correct +ABSTRACTIONS += --remove-function-body IotLog_Generic + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto + +UNWINDING += --unwind 1 + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml new file mode 100644 index 0000000000..a4d18f6094 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml @@ -0,0 +1,4 @@ +jobos: ubuntu16 +cbmcflags: '=--unwind;1;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static=' +goto: IotMqtt_Cleanup.goto +expected: SUCCESSFUL diff --git a/cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json b/cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json new file mode 100644 index 0000000000..f56ccaf7d0 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_Cleanup", + "proof-root": "cbmc/proofs" +} From 7deab7abf0cfed110fdab672686a2b71d0edc22d Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Fri, 14 Feb 2020 16:42:30 -0500 Subject: [PATCH 431/844] Adds a proof harness for the IotMqtt_strerror function (#790) --- .../IotMqtt_strerror_harness.c | 9 ++++++++ cbmc/proofs/IotMqtt_strerror/Makefile | 6 +++++ cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml | 4 ++++ cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json | 22 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c create mode 100644 cbmc/proofs/IotMqtt_strerror/Makefile create mode 100644 cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c b/cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c new file mode 100644 index 0000000000..64972421e8 --- /dev/null +++ b/cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c @@ -0,0 +1,9 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +void harness() +{ + IotMqttError_t status; + const char *pMessage = IotMqtt_strerror(status); + assert(pMessage != NULL); +} diff --git a/cbmc/proofs/IotMqtt_strerror/Makefile b/cbmc/proofs/IotMqtt_strerror/Makefile new file mode 100644 index 0000000000..1c15a306e2 --- /dev/null +++ b/cbmc/proofs/IotMqtt_strerror/Makefile @@ -0,0 +1,6 @@ +ENTRY=IotMqtt_strerror + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml new file mode 100644 index 0000000000..3b3467c140 --- /dev/null +++ b/cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" +expected: SUCCESSFUL +goto: IotMqtt_strerror.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json b/cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json new file mode 100644 index 0000000000..f2edc1ee88 --- /dev/null +++ b/cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_strerror", + "proof-root": "cbmc/proofs" +} From 8849363f7f76cc80f9a99f8ac8883e951e7d2861 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Fri, 14 Feb 2020 16:48:05 -0500 Subject: [PATCH 432/844] Adds a proof harness for the IotMqtt_OperationType function (#791) --- .../IotMqtt_OperationType_harness.c | 9 ++++++++ cbmc/proofs/IotMqtt_OperationType/Makefile | 6 +++++ .../IotMqtt_OperationType/cbmc-batch.yaml | 4 ++++ .../IotMqtt_OperationType/cbmc-viewer.json | 22 +++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c create mode 100644 cbmc/proofs/IotMqtt_OperationType/Makefile create mode 100644 cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c b/cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c new file mode 100644 index 0000000000..98a730d596 --- /dev/null +++ b/cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c @@ -0,0 +1,9 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +void harness() +{ + IotMqttOperationType_t operation; + const char *pMessage = IotMqtt_OperationType(operation); + assert(pMessage != NULL); +} diff --git a/cbmc/proofs/IotMqtt_OperationType/Makefile b/cbmc/proofs/IotMqtt_OperationType/Makefile new file mode 100644 index 0000000000..94d3ce228b --- /dev/null +++ b/cbmc/proofs/IotMqtt_OperationType/Makefile @@ -0,0 +1,6 @@ +ENTRY=IotMqtt_OperationType + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml new file mode 100644 index 0000000000..61f0662cfb --- /dev/null +++ b/cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" +expected: SUCCESSFUL +goto: IotMqtt_OperationType.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json b/cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json new file mode 100644 index 0000000000..f7b64021e8 --- /dev/null +++ b/cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_OperationType", + "proof-root": "cbmc/proofs" +} From 51b2ae86ae6961432dcfe00758ac22489c3158d0 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Fri, 14 Feb 2020 16:54:48 -0500 Subject: [PATCH 433/844] CBMC proof for DeserializeSuback (#801) --- ...et-CBMC-proofs-stub-out-RemoveAllMat.patch | 38 ++++++++++ .../DeserializeSuback_harness.c | 75 ++++++++++++++++++- cbmc/proofs/DeserializeSuback/Makefile | 52 ++++++++++--- cbmc/proofs/DeserializeSuback/cbmc-batch.yaml | 6 +- .../proofs/DeserializeSuback/cbmc-viewer.json | 22 ++++++ cbmc/proofs/Makefile.common | 7 +- cbmc/proofs/mqtt_state.c | 1 + cbmc/proofs/prepare.py | 5 +- 8 files changed, 185 insertions(+), 21 deletions(-) create mode 100644 cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch create mode 100644 cbmc/proofs/DeserializeSuback/cbmc-viewer.json diff --git a/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch b/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch new file mode 100644 index 0000000000..071e668871 --- /dev/null +++ b/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch @@ -0,0 +1,38 @@ +From a1455991c02aa6d7db1d4333e2cdc5add392c105 Mon Sep 17 00:00:00 2001 +From: Mark R Tuttle +Date: Thu, 13 Feb 2020 17:39:51 +0000 +Subject: [PATCH] Modify MQTT to let CBMC proofs stub out RemoveAllMatches + +--- + libraries/standard/common/include/iot_linear_containers.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h +index 689ea5e..ffb3469 100644 +--- a/libraries/standard/common/include/iot_linear_containers.h ++++ b/libraries/standard/common/include/iot_linear_containers.h +@@ -719,6 +719,13 @@ static inline IotLink_t * IotListDouble_RemoveFirstMatch( const IotListDouble_t + * or its value is `0`. + */ + /* @[declare_linear_containers_list_double_removeallmatches] */ ++#ifdef CBMC_PROOF_DESERIALIZE_SUBACK ++void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, ++ bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), ++ void * pMatch, ++ void ( *freeElement )( void * pData ), ++ size_t linkOffset ); ++#else + static inline void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), + void * pMatch, +@@ -754,6 +761,7 @@ static inline void IotListDouble_RemoveAllMatches( const IotListDouble_t * const + } + } while( pMatchedElement != NULL ); + } ++#endif + + /** + * @brief Create a new queue. +-- +2.7.4 + diff --git a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c index 0152d5e3ec..3eaa8b3135 100644 --- a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c +++ b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c @@ -3,15 +3,84 @@ #include -/*-----------------------------------------------------------*/ +#include "mqtt_state.h" + +/**************************************************************** + * Stub out RemoveAllMatches. + * + * The only invocation of RemoveAllMatches in DeserializeSuback is to + * remove subscriptions from the connection's subscription list. This + * stub abstracts RemoveAllMatches by replacing the list with an + * unconstrained list of subscriptions. We don't even bother to fill + * in the topic subscription filters. By design, any actual use of + * the list in subsequent code (there is none) will trigger pointer + * errors. +/****************************************************************/ + +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + bool ( *isMatch )( const IotLink_t * const pOperationLink, + void * pCompare ), + void * pMatch, + void ( *freeElement )( void * pData ), + size_t linkOffset ) +{ + IotListDouble_t *sentinel = pList->pNext->pPrevious; + + size_t num_elts; + __CPROVER_assume(num_elts < SUBSCRIPTION_COUNT_MAX); + + // Allocate lists of length at most 3 + __CPROVER_assert(SUBSCRIPTION_COUNT_MAX <= 4, + "Subscription lists limited to 3 elements"); + + if (1 <= num_elts) + { + _mqttSubscription_t *pElt = malloc(sizeof(*pElt)); + sentinel->pNext = &pElt->link; + pElt->link.pPrevious = sentinel; + pElt->link.pNext = sentinel; + sentinel->pPrevious = &pElt->link; + } + if (2 <= num_elts) + { + _mqttSubscription_t *pElt = malloc(sizeof(*pElt)); + sentinel->pNext = &pElt->link; + pElt->link.pPrevious = sentinel; + pElt->link.pNext = sentinel; + sentinel->pPrevious = &pElt->link; + } + if (3 <= num_elts) + { + _mqttSubscription_t *pElt = malloc(sizeof(*pElt)); + sentinel->pNext = &pElt->link; + pElt->link.pPrevious = sentinel; + pElt->link.pNext = sentinel; + sentinel->pPrevious = &pElt->link; + } +} + +/**************************************************************** + * Proof harness + ****************************************************************/ void harness() { _mqttPacket_t suback; - suback.pRemainingData = malloc( sizeof( uint8_t ) * suback.remainingLength ); - + // Create packet data __CPROVER_assume( suback.remainingLength <= BUFFER_SIZE ); + suback.pRemainingData = malloc( sizeof( uint8_t ) + * suback.remainingLength ); + // Create packet connection + IotMqttConnection_t pConn = allocate_IotMqttConnection(NULL); + __CPROVER_assume(pConn != NULL); + ensure_IotMqttConnection_has_lists(pConn); + __CPROVER_assume(valid_IotMqttConnection(pConn)); + suback.u.pMqttConnection = pConn; + + // Argument must be a nonnull pointer _IotMqtt_DeserializeSuback( &suback ); } + +/****************************************************************/ diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/DeserializeSuback/Makefile index 258a1c8ff2..df7a7f0cb9 100644 --- a/cbmc/proofs/DeserializeSuback/Makefile +++ b/cbmc/proofs/DeserializeSuback/Makefile @@ -1,19 +1,49 @@ ENTRY=DeserializeSuback -BUFFER_SIZE = 100 +################################################################ +# Proof assumptions -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - --remove-function-body _IotMqtt_RemoveSubscriptionByPacket \ +# Bound the number of subscriptions in a list (BOUND = MAX-1) +SUBSCRIPTION_COUNT_MAX=2 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) -UNWINDING = \ - --unwindset _decodeSubackStatus.0:$(BUFFER_SIZE) \ +# Bound the number of operations in a list (BOUND = MAX-1) +OPERATION_COUNT_MAX=2 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ +BUFFER_SIZE = 15 +DEF += -DBUFFER_SIZE=$(BUFFER_SIZE) -DEF = -DBUFFER_SIZE=$(BUFFER_SIZE) +################################################################ +# Function omitted from the proof + +# We abstract all the log and concurrency related functions in this +# proof and assume their implementation is correct +ABSTRACTIONS += --remove-function-body IotLog_Generic + +# These defines have the effect of removing RemoveAllMatches from the +# linear containers header so that our stub will be used. +DEF += -DCBMC=1 -DCBMC_PROOF_DESERIALIZE_SUBACK=1 + +################################################################ +# Loop unwinding + +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) + +LOOP += _decodeSubackStatus.0:$(BUFFER_SIZE) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +################################################################ + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto + +################################################################ include ../Makefile.common + diff --git a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml index 955e25aeb5..0b02511d08 100644 --- a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml +++ b/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml @@ -1,4 +1,4 @@ -jobos: ubuntu16 -cbmcflags: '=--unwindset;_decodeSubackStatus.0:100;--bounds-check;--pointer-check;--unwinding-assertions=' -goto: DeserializeSuback.goto +cbmcflags: "--object-bits;8;--unwind;1;--unwindset;valid_IotMqttSubscriptionList.0:2,valid_IotMqttOperationList.0:2,_decodeSubackStatus.0:15;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" expected: SUCCESSFUL +goto: DeserializeSuback.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/DeserializeSuback/cbmc-viewer.json b/cbmc/proofs/DeserializeSuback/cbmc-viewer.json new file mode 100644 index 0000000000..8c426b8d9d --- /dev/null +++ b/cbmc/proofs/DeserializeSuback/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "DeserializeSuback", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index b99871aaae..26325fab57 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -83,6 +83,7 @@ CBMCFLAGS += \ --undefined-shift-check \ --signed-overflow-check \ --unsigned-overflow-check \ + --flush \ goto: $(ENTRY).goto @@ -117,11 +118,11 @@ report: cbmc.txt property.xml coverage.xml clean: $(RM) $(OBJS) $(ENTRY).goto $(RM) $(ENTRY)[0-9].goto $(ENTRY)[0-9].txt - $(RM) cbmc.txt property.xml coverage.xml TAGS + $(RM) cbmc.txt property.xml coverage.xml TAGS TAGS-* $(RM) *~ \#* veryclean: clean - $(RM) -r html TAGS-* + $(RM) -r html .PHONY: cbmc property coverage report clean veryclean @@ -210,4 +211,6 @@ cbmc-batch.yaml: Makefile ../Makefile.common @echo "goto: $(ENTRY).goto" >> $@ @echo "jobos: $(JOBOS)" >> $@ +.PHONY: cbmc-batch.yaml + ################################################################ diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c index 8f32998643..85fe93fd53 100644 --- a/cbmc/proofs/mqtt_state.c +++ b/cbmc/proofs/mqtt_state.c @@ -310,6 +310,7 @@ bool valid_IotMqttConnectInfo( const IotMqttConnectInfo_t *pInfo ) // MAX is one greater than the maximum length pInfo->previousSubscriptionCount < SUBSCRIPTION_COUNT_MAX && + IFF( pInfo->pPreviousSubscriptions == NULL, pInfo->previousSubscriptionCount == 0 ) && valid_IotMqttSubscriptionArray( pInfo->pPreviousSubscriptions, diff --git a/cbmc/proofs/prepare.py b/cbmc/proofs/prepare.py index 31aa567e43..bc25a5e0b9 100755 --- a/cbmc/proofs/prepare.py +++ b/cbmc/proofs/prepare.py @@ -51,7 +51,8 @@ def apply_patches(): logging.info("Checking patch '%s'", fyle.name) cmd = ["git", "apply", "--check", str(fyle)] proc = subprocess.run( - cmd, cwd=proj_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + cmd, cwd=str(proj_dir), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) if proc.returncode: logging.warning( @@ -60,7 +61,7 @@ def apply_patches(): logging.info("Applying patch '%s'", fyle.name) cmd = ["git", "apply", str(fyle)] - proc = subprocess.run(cmd, cwd=proj_dir) + proc = subprocess.run(cmd, cwd=str(proj_dir)) if proc.returncode: logging.error("Failed to apply patch '%s'", fyle.name) sys.exit(1) From 03f57c39bc20f5813e4f3b80c388dc58f02040fe Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Fri, 14 Feb 2020 17:01:34 -0500 Subject: [PATCH 434/844] CBMC: Correct type used in mqtt_state for operation list allocation. (#804) --- cbmc/proofs/mqtt_state.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c index 85fe93fd53..bf2ab09988 100644 --- a/cbmc/proofs/mqtt_state.c +++ b/cbmc/proofs/mqtt_state.c @@ -181,8 +181,7 @@ bool valid_IotMqttOperationList( const IotListDouble_t *pOp, IotListDouble_t *pLink; IotContainers_ForEach( pOp, pLink ) { - IotMqttOperation_t - *pElt = IotLink_Container( _mqttSubscription_t, pLink, link ); + IotMqttOperation_t *pElt = IotLink_Container( struct _mqttOperation, pLink, link ); if (! valid_IotMqttOperation( pElt ) ) return false; } From 6adee766278dfbd84327dc9b43d7f5f56bb3f27d Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Fri, 14 Feb 2020 17:16:23 -0500 Subject: [PATCH 435/844] Adds a proof harness for the IotMqtt_Wait function (#797) --- .../IotMqtt_Wait/IotMqtt_Wait_harness.c | 31 +++++++++++++ cbmc/proofs/IotMqtt_Wait/Makefile | 45 +++++++++++++++++++ cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml | 4 ++ cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json | 22 +++++++++ cbmc/proofs/mqtt_state.c | 2 + 5 files changed, 104 insertions(+) create mode 100644 cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c create mode 100644 cbmc/proofs/IotMqtt_Wait/Makefile create mode 100644 cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c b/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c new file mode 100644 index 0000000000..44d3e52de5 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c @@ -0,0 +1,31 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include +#include + +#include "mqtt_state.h" + +void harness() +{ + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection(NULL); + __CPROVER_assume(mqttConnection != NULL); + __CPROVER_assume(mqttConnection->pNetworkInterface != NULL); + __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); + ensure_IotMqttConnection_has_lists(mqttConnection); + __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); + + IotMqttOperation_t publishOperation = allocate_IotMqttOperation(NULL, mqttConnection); + __CPROVER_assume(valid_IotMqttOperation(publishOperation)); + + IotListDouble_Create( &( publishOperation->link )); + + if (nondet_bool()) + { + IotListDouble_InsertHead( &( mqttConnection->pendingProcessing ), &( publishOperation->link )); + } + + uint32_t timeoutMs; + IotMqtt_Wait( nondet_bool() ? publishOperation : NULL, timeoutMs ); +} + diff --git a/cbmc/proofs/IotMqtt_Wait/Makefile b/cbmc/proofs/IotMqtt_Wait/Makefile new file mode 100644 index 0000000000..0ab3658aa1 --- /dev/null +++ b/cbmc/proofs/IotMqtt_Wait/Makefile @@ -0,0 +1,45 @@ +ENTRY=IotMqtt_Wait + +# We abstract all the log and concurrency related functions in this proof +# and assume their implementation is correct +ABSTRACTIONS += --remove-function-body _IotMqtt_ProcessIncomingPublish +ABSTRACTIONS += --remove-function-body _matchEndWildcards +ABSTRACTIONS += --remove-function-body _matchWildcards +ABSTRACTIONS += --remove-function-body _mqttOperation_match +ABSTRACTIONS += --remove-function-body _mqttOperation_tryDestroy +ABSTRACTIONS += --remove-function-body _packetMatch +ABSTRACTIONS += --remove-function-body _topicFilterMatch +ABSTRACTIONS += --remove-function-body _topicMatch +ABSTRACTIONS += --remove-function-body IotListDouble_Remove +ABSTRACTIONS += --remove-function-body 'IotListDouble_Remove$$link3' +ABSTRACTIONS += --remove-function-body IotLog_Generic +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +# One more than actual number of subscriptions in a subscription list +SUBSCRIPTION_COUNT_MAX=4 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# One more than actual number of operations in an operation list +OPERATION_COUNT_MAX=4 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# One more than actual length of topics +TOPIC_LENGTH_MAX=6 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttOperationList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml new file mode 100644 index 0000000000..4c2535b0ae --- /dev/null +++ b/cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--unwind;1;--unwindset;valid_IotMqttSubscriptionList.0:4,valid_IotMqttOperationList.0:4,IotListDouble_RemoveAllMatches.0:4;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" +expected: SUCCESSFUL +goto: IotMqtt_Wait.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json b/cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json new file mode 100644 index 0000000000..4d21020e0e --- /dev/null +++ b/cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_Wait", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c index bf2ab09988..ca8ee64c80 100644 --- a/cbmc/proofs/mqtt_state.c +++ b/cbmc/proofs/mqtt_state.c @@ -388,6 +388,8 @@ _mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *p if ( pElt == NULL ) pElt = malloc_can_fail( sizeof( *pElt ) + length ); if ( pElt == NULL ) return NULL; + // References must never be negative + __CPROVER_assume( pElt->references >= 0 ); pElt->link.pPrevious = NULL; pElt->link.pNext = NULL; pElt->topicFilterLength = length; From 5494fb1ba9242e8f5c3fe6fce393127abdf2ffe3 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Wed, 26 Feb 2020 09:41:50 -0800 Subject: [PATCH 436/844] Capitalize beginning of comments in iot_network.h (#809) --- libraries/platform/iot_network.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 04a41e0d11..d0e809d904 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -295,13 +295,13 @@ typedef IotNetworkError_t ( * IotNetworkDestroy_t )( IotNetworkConnection_t pCon */ typedef struct IotNetworkInterface { - IotNetworkCreate_t create; /**< @brief create network connection. */ - IotNetworkSetReceiveCallback_t setReceiveCallback; /**< @brief set receive callback. */ - IotNetworkSetCloseCallback_t setCloseCallback; /**< @brief set close callback. */ - IotNetworkSend_t send; /**< @brief send data. */ - IotNetworkReceive_t receive; /**< @brief block and wait for receive data. */ - IotNetworkClose_t close; /**< @brief close network connection. */ - IotNetworkDestroy_t destroy; /**< @brief destroy network connection. */ + IotNetworkCreate_t create; /**< @brief Create network connection. */ + IotNetworkSetReceiveCallback_t setReceiveCallback; /**< @brief Set receive callback. */ + IotNetworkSetCloseCallback_t setCloseCallback; /**< @brief Set close callback. */ + IotNetworkSend_t send; /**< @brief Send data. */ + IotNetworkReceive_t receive; /**< @brief Block and wait for receive data. */ + IotNetworkClose_t close; /**< @brief Close network connection. */ + IotNetworkDestroy_t destroy; /**< @brief Destroy network connection. */ } IotNetworkInterface_t; /** From 1ad675b8532f329bebaf40cda54192d0cb4feecb Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Wed, 26 Feb 2020 22:31:59 -0500 Subject: [PATCH 437/844] Adds a proof harness for the IotMqtt_PublishAsync function (#805) --- .../IotMqtt_PublishAsync_harness.c | 88 +++++++++++++++++++ cbmc/proofs/IotMqtt_PublishAsync/Makefile | 33 +++++++ .../IotMqtt_PublishAsync/cbmc-batch.yaml | 4 + .../IotMqtt_PublishAsync/cbmc-viewer.json | 22 +++++ .../IotMqtt_Wait/IotMqtt_Wait_harness.c | 1 + cbmc/proofs/Makefile.common | 10 ++- cbmc/proofs/mqtt_state.c | 7 +- cbmc/proofs/mqtt_state.h | 9 ++ 8 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c create mode 100644 cbmc/proofs/IotMqtt_PublishAsync/Makefile create mode 100644 cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c b/cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c new file mode 100644 index 0000000000..00c6189551 --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c @@ -0,0 +1,88 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include +#include + +#include "mqtt_state.h" + +/** + * We abstract all functions related to concurrency and assume they are correct. + * Thus, we add these stub to increase coverage. + */ +IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJobStorage_t * const pJobStorage, + IotTaskPoolJob_t * const pJob ) +{ + if (userCallback == NULL || pJobStorage == NULL || pJob == NULL) + { + return IOT_TASKPOOL_BAD_PARAMETER; + } + /* _IotMqtt_ScheduleOperation asserts this */ + return IOT_TASKPOOL_SUCCESS; +} + +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + uint32_t timeMs ) +{ + IotTaskPoolError_t error; + + /* _IotMqtt_ScheduleOperation asserts this */ + __CPROVER_assume(error != IOT_TASKPOOL_BAD_PARAMETER && + error != IOT_TASKPOOL_ILLEGAL_OPERATION); + return error; +} + +/** + * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives + * a volatile variable as input. Thus, CBMC will always consider that + * Atomic_Add_u32 will operate over nondetermistic values and raises + * a unsigned integer overflow failure. However, developers have reported + * that the use of this overflow is part of the function implementation. + * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid + * spurious alarms, we stub out this function to always + * return a nondetermistic odd value. + */ +uint16_t _IotMqtt_NextPacketIdentifier( void ) { + uint16_t id; + + /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + __CPROVER_assume(id & 0x0001 == 1); + + return id; +} + +void harness() +{ + /* assume a valid MQTT connection */ + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection(NULL); + __CPROVER_assume(mqttConnection != NULL); + __CPROVER_assume(mqttConnection->pNetworkInterface != NULL); + __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); + ensure_IotMqttConnection_has_lists(mqttConnection); + __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); + + /* assume a valid publish info */ + IotMqttPublishInfo_t *pPublishInfo = allocate_IotMqttPublishInfo(NULL); + if (pPublishInfo != NULL) __CPROVER_assume(valid_IotMqttPublishInfo(pPublishInfo)); + + /* assume unconstrained inputs */ + uint32_t flags; + IotMqttCallbackInfo_t *callbackInfo = malloc_can_fail(sizeof (*callbackInfo) ); + + /* output */ + IotMqttOperation_t *publishOperation = malloc_can_fail(sizeof (*publishOperation) ); + + + /* function under verification */ + IotMqttError_t status = IotMqtt_PublishAsync( mqttConnection, /* always assume a valid connection */ + pPublishInfo, + flags, + callbackInfo, + publishOperation ); + /* assert post-conditions */ + assert(IMPLIES(status == IOT_MQTT_STATUS_PENDING, pPublishInfo->qos == IOT_MQTT_QOS_1)); + assert(IMPLIES(status == IOT_MQTT_SUCCESS, pPublishInfo->qos == IOT_MQTT_QOS_0 || pPublishInfo->qos == IOT_MQTT_QOS_1)); +} diff --git a/cbmc/proofs/IotMqtt_PublishAsync/Makefile b/cbmc/proofs/IotMqtt_PublishAsync/Makefile new file mode 100644 index 0000000000..b84411fc7c --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishAsync/Makefile @@ -0,0 +1,33 @@ +ENTRY=IotMqtt_PublishAsync + +# We abstract all the log and concurrency related functions in this proof +# and assume their implementation is correct +ABSTRACTIONS += --remove-function-body IotLog_Generic +ABSTRACTIONS += --remove-function-body _IotMqtt_ProcessSend +ABSTRACTIONS += --remove-function-body _destroyMqttConnection + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +# One more than actual number of subscriptions +SUBSCRIPTION_COUNT_MAX=4 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# One more than actual number of operations +OPERATION_COUNT_MAX=4 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) +LOOP += _encodeRemainingLength.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml new file mode 100644 index 0000000000..0b28bbe6b7 --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--unwind;1;--unwindset;IotListDouble_RemoveAllMatches.0:4,valid_IotMqttSubscriptionList.0:4,valid_IotMqttOperationList.0:4,_encodeRemainingLength.0:4;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" +expected: SUCCESSFUL +goto: IotMqtt_PublishAsync.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json new file mode 100644 index 0000000000..8a9dedf46c --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "IotMqtt_PublishAsync", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c b/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c index 44d3e52de5..6957b1c11b 100644 --- a/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c +++ b/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c @@ -14,6 +14,7 @@ void harness() __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); ensure_IotMqttConnection_has_lists(mqttConnection); __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); + __CPROVER_assume(mqttConnection->references > 0); IotMqttOperation_t publishOperation = allocate_IotMqttOperation(NULL, mqttConnection); __CPROVER_assume(valid_IotMqttOperation(publishOperation)); diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 26325fab57..3b94ddd3bf 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -20,20 +20,22 @@ VIEWER ?= cbmc-viewer # Build goto binaries with options taken from top-level cmake INC += \ - -I$(MQTT)/libraries/standard/mqtt/include \ + -I$(MQTT)/cbmc/proofs \ -I$(MQTT)/demos \ - -I$(MQTT)/libraries/platform/.. \ + -I$(MQTT)/libraries/ \ -I$(MQTT)/libraries/platform \ - -I$(MQTT)/ports/common/include \ -I$(MQTT)/libraries/standard/common/include \ + -I$(MQTT)/libraries/standard/mqtt/include \ -I$(MQTT)/libraries/standard/mqtt/src \ - -I$(MQTT)/cbmc/proofs \ + -I$(MQTT)/libraries/standard/mqtt/src/private \ + -I$(MQTT)/ports/common/include \ DEF += \ -DIOT_SYSTEM_TYPES_FILE=\"$(MQTT)/ports/posix/include/iot_platform_types_posix.h\" \ -Diotmqtt_EXPORTS \ -DCBMC_OBJECT_BITS=$(CBMC_OBJECT_BITS) \ -DCBMC_MAX_OBJECT_SIZE=$(CBMC_MAX_OBJECT_SIZE) \ + -DCBMC=1 \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c index ca8ee64c80..d21efb6eee 100644 --- a/cbmc/proofs/mqtt_state.c +++ b/cbmc/proofs/mqtt_state.c @@ -227,9 +227,8 @@ bool valid_IotMqttConnection( const IotMqttConnection_t pConn ) // It is a uint32 and must be bounded by a number smaller than the // maximum value to avoid integer overflows. We expect to run out of // memory before having 2^16 references on a device. - bool valid_references = - pConn->references >= 1 && - pConn->references <= (1 << 16); + bool valid_references = pConn->references >= 0 && + pConn->references <= (1 << 16); bool valid_pingreq = ( valid_IotMqttOperation( &(pConn->pingreq ) ) ) && @@ -489,6 +488,8 @@ bool valid_IotMqttPublishInfo( const IotMqttPublishInfo_t *pInfo ) return pInfo && + VALID_QOS(pInfo->qos) && + VALID_STRING( pInfo->pTopicName, pInfo->topicNameLength ) && VALID_CBMC_SIZE( pInfo->topicNameLength ) && pInfo->pTopicName != NULL && diff --git a/cbmc/proofs/mqtt_state.h b/cbmc/proofs/mqtt_state.h index 3d75413aa9..caa81d8272 100644 --- a/cbmc/proofs/mqtt_state.h +++ b/cbmc/proofs/mqtt_state.h @@ -29,6 +29,15 @@ #define VALID_CBMC_SIZE(size) ((size) < CBMC_MAX_OBJECT_SIZE) +/**************************************************************** + * According to the documentation, IOT_MQTT_QOS_2 is not entirely + * supported, but it is expected that all functions that deals + * with MQTT PUBLISH messages will be resilient to it. + ****************************************************************/ +#define VALID_QOS(qos) ( qos == IOT_MQTT_QOS_0 || \ + qos == IOT_MQTT_QOS_1 || \ + qos == IOT_MQTT_QOS_2 ) + /**************************************************************** * Model a malloc that can fail and return NULL. CBMC currently * models malloc as an allocator that never fails. CBMC will soon From c08f85a679eeb57262cd754fa1a1abf4d8d084bc Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 4 Mar 2020 15:46:04 -0800 Subject: [PATCH 438/844] Move unit tests out of defender system tests (#817) --- libraries/aws/defender/CMakeLists.txt | 1 + .../defender/test/aws_iot_tests_defender.c | 2 + .../system/aws_iot_tests_defender_system.c | 194 ------------ .../test/unit/aws_iot_tests_defender_unit.c | 277 ++++++++++++++++++ 4 files changed, 280 insertions(+), 194 deletions(-) create mode 100644 libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c diff --git a/libraries/aws/defender/CMakeLists.txt b/libraries/aws/defender/CMakeLists.txt index e23f0da435..5c8b49b65f 100644 --- a/libraries/aws/defender/CMakeLists.txt +++ b/libraries/aws/defender/CMakeLists.txt @@ -36,6 +36,7 @@ source_group( src FILES ${DEFENDER_SOURCES} ) if( ${IOT_BUILD_TESTS} ) # Defender system test sources. set( DEFENDER_SYSTEM_TEST_SOURCES + test/unit/aws_iot_tests_defender_unit.c test/system/aws_iot_tests_defender_system.c ) # Defender tests executable. diff --git a/libraries/aws/defender/test/aws_iot_tests_defender.c b/libraries/aws/defender/test/aws_iot_tests_defender.c index 9c260b8a15..0ee1d1e205 100644 --- a/libraries/aws/defender/test/aws_iot_tests_defender.c +++ b/libraries/aws/defender/test/aws_iot_tests_defender.c @@ -44,6 +44,8 @@ void RunDefenderTests( bool disableNetworkTests, bool disableLongTests ) /* Silence warnings about unused parameters. */ ( void ) disableLongTests; + RUN_TEST_GROUP( Defender_Unit ); + if( disableNetworkTests == false ) { RUN_TEST_GROUP( Defender_System ); diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c index fff91bc311..a0be9ddec0 100644 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c @@ -32,7 +32,6 @@ /* Defender internal includes. */ #include "private/aws_iot_defender_internal.h" -#include "iot_tests_mqtt_mock.h" #include "iot_network_metrics.h" @@ -95,7 +94,6 @@ static IotSerializerDecoderObject_t _decoderObject; static IotSerializerDecoderObject_t _metricsObject; static bool _mqttConnectionStarted = false; -static bool _mockedMqttConnection = false; /*------------------ Functions -----------------------------*/ /* Copy data from MQTT callback to local buffer. */ @@ -197,78 +195,6 @@ TEST_TEAR_DOWN( Defender_System ) TEST_GROUP_RUNNER( Defender_System ) { - /* - * Setup: none - * Action: call Start API with invalid MQTT connection - * Expectation: Start API returns failure - */ - RUN_TEST_CASE( Defender_System, Start_with_invalid_mqtt_connection ); - - /* - * Setup: defender not started yet - * Action: call SetMetrics API with an invalid big integer as metrics group - * Expectation: - * - SetMetrics API return invalid input - * - global metrics flag array are untouched - */ - RUN_TEST_CASE( Defender_System, SetMetrics_with_invalid_metrics_group ); - - /* - * Setup: defender not started yet - * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value - * Expectation: - * - SetMetrics API return success - * - global metrics flag array are updated correctly - */ - RUN_TEST_CASE( Defender_System, SetMetrics_with_TCP_connections_all ); - - /* - * Setup: defender is started - * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value - * Expectation: - * - SetMetrics API return success - * - global metrics flag array are updated correctly - */ - RUN_TEST_CASE( Defender_System, SetMetrics_after_defender_started ); - - /* - * Setup: defender not started yet - * Action: call SetPeriod API with small value less than 300 - * Expectation: - * - SetPeriod API return "period too short" error - */ - RUN_TEST_CASE( Defender_System, SetPeriod_too_short ); - - /* - * Setup: defender not started yet - * Action: call SetPeriod API with 301 - * Expectation: - * - SetPeriod API return success - */ - RUN_TEST_CASE( Defender_System, SetPeriod_with_proper_value ); - - /* - * Setup: defender is started - * Action: call SetPeriod API with 600 - * Expectation: - * - SetPeriod API return success - */ - RUN_TEST_CASE( Defender_System, SetPeriod_after_started ); - - /* - * Setup: kept from publishing metrics report - * Action: call Start API with correct network information - * Expectation: Start API return success - */ - RUN_TEST_CASE( Defender_System, Start_should_return_success ); - - /* - * Setup: call Start API the first time; kept from publishing metrics report - * Action: call Start API second time - * Expectation: Start API return "already started" error - */ - RUN_TEST_CASE( Defender_System, Start_should_return_err_if_already_started ); - /* * Setup: not set any metrics; register test callback * Action: call Start API @@ -313,93 +239,6 @@ TEST_GROUP_RUNNER( Defender_System ) RUN_TEST_CASE( Defender_System, Restart_and_updated_metrics_are_published ); } -TEST( Defender_System, SetMetrics_with_invalid_metrics_group ) -{ - uint8_t i = 0; - - /* Input a dummy, invalid metrics group. */ - AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( 10000, - AWS_IOT_DEFENDER_METRICS_ALL ); - - /* SetMetrics should return "invalid input". */ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); - - /* Assert metrics flag in each metrics group remains 0. */ - for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) - { - TEST_ASSERT_EQUAL( 0, _AwsIotDefenderMetrics.metricsFlag[ i ] ); - } -} - -TEST( Defender_System, SetMetrics_with_TCP_connections_all ) -{ - /* Set "all metrics" for TCP connections metrics group. */ - AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_ALL ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); -} - -TEST( Defender_System, SetMetrics_after_defender_started ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Set "all metrics" for TCP connections metrics group. */ - error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_ALL ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); -} - -TEST( Defender_System, Start_with_invalid_mqtt_connection ) -{ - /* Set uninitialized MQTT connection */ - _startInfo.mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); -} - -TEST( Defender_System, Start_should_return_success ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); -} - -TEST( Defender_System, Start_should_return_err_if_already_started ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Start defender for a second time. */ - error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_ALREADY_STARTED, error ); -} TEST( Defender_System, Metrics_empty_are_published ) { @@ -563,33 +402,6 @@ TEST( Defender_System, Restart_and_updated_metrics_are_published ) _verifyTcpConnections( 1 ); } -TEST( Defender_System, SetPeriod_too_short ) -{ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, AwsIotDefender_SetPeriod( 299 ) ); -} - -TEST( Defender_System, SetPeriod_with_proper_value ) -{ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 301 ) ); - - TEST_ASSERT_EQUAL( 301, AwsIotDefender_GetPeriod() ); -} - -TEST( Defender_System, SetPeriod_after_started ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, - AwsIotDefender_Start( &_startInfo ) ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 600 ) ); - - TEST_ASSERT_EQUAL( 600, AwsIotDefender_GetPeriod() ); -} - /*-----------------------------------------------------------*/ static void _copyDataCallbackFunction( void * param1, @@ -675,12 +487,6 @@ static void _stopMqttConnection( void ) _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; _mqttConnectionStarted = false; } - if (_mockedMqttConnection) - { - IotTest_MqttMockCleanup(); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - _mockedMqttConnection = false; - } } /*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c b/libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c new file mode 100644 index 0000000000..6e111a63a6 --- /dev/null +++ b/libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c @@ -0,0 +1,277 @@ +/* + * AWS IoT Defender V3.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Defender internal includes. */ +#include "private/aws_iot_defender_internal.h" + +/* MQTT Mock include */ +#include "iot_tests_mqtt_mock.h" + +#include "iot_network_metrics.h" +#include "iot_init.h" +#include "unity_fixture.h" + +/* Empty callback structure passed to startInfo. */ +static const AwsIotDefenderCallback_t _emptyCallback = { .function = NULL, .pCallbackContext = NULL }; + +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/*------------------ global variables -----------------------------*/ + +static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + +static bool _mockedMqttConnection = false; +/*------------------ Functions -----------------------------*/ + +TEST_GROUP( Defender_Unit ); + +TEST_SETUP( Defender_Unit ) +{ + if( IotSdk_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); + } + + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT." ); + } + + if( IotMetrics_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize metrics." ); + } + + /* Set fields of start info. */ + _startInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; + _startInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_DEFENDER_THING_NAME ); + _startInfo.callback = _emptyCallback; +} + +TEST_TEAR_DOWN( Defender_Unit ) +{ + AwsIotDefender_Stop(); + + if (_mockedMqttConnection) + { + IotTest_MqttMockCleanup(); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + _mockedMqttConnection = false; + } + + IotMetrics_Cleanup(); + IotMqtt_Cleanup(); + IotSdk_Cleanup(); +} + +TEST_GROUP_RUNNER( Defender_Unit ) +{ + /* + * Setup: none + * Action: call Start API with invalid MQTT connection + * Expectation: Start API returns failure + */ + RUN_TEST_CASE( Defender_Unit, Start_with_invalid_mqtt_connection ); + + /* + * Setup: defender not started yet + * Action: call SetMetrics API with an invalid big integer as metrics group + * Expectation: + * - SetMetrics API return invalid input + * - global metrics flag array are untouched + */ + RUN_TEST_CASE( Defender_Unit, SetMetrics_with_invalid_metrics_group ); + + /* + * Setup: defender not started yet + * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value + * Expectation: + * - SetMetrics API return success + * - global metrics flag array are updated correctly + */ + RUN_TEST_CASE( Defender_Unit, SetMetrics_with_TCP_connections_all ); + + /* + * Setup: defender is started + * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value + * Expectation: + * - SetMetrics API return success + * - global metrics flag array are updated correctly + */ + RUN_TEST_CASE( Defender_Unit, SetMetrics_after_defender_started ); + + /* + * Setup: defender not started yet + * Action: call SetPeriod API with small value less than 300 + * Expectation: + * - SetPeriod API return "period too short" error + */ + RUN_TEST_CASE( Defender_Unit, SetPeriod_too_short ); + + /* + * Setup: defender not started yet + * Action: call SetPeriod API with 301 + * Expectation: + * - SetPeriod API return success + */ + RUN_TEST_CASE( Defender_Unit, SetPeriod_with_proper_value ); + + /* + * Setup: defender is started + * Action: call SetPeriod API with 600 + * Expectation: + * - SetPeriod API return success + */ + RUN_TEST_CASE( Defender_Unit, SetPeriod_after_started ); + + /* + * Setup: kept from publishing metrics report + * Action: call Start API with correct network information + * Expectation: Start API return success + */ + RUN_TEST_CASE( Defender_Unit, Start_should_return_success ); + + /* + * Setup: call Start API the first time; kept from publishing metrics report + * Action: call Start API second time + * Expectation: Start API return "already started" error + */ + RUN_TEST_CASE( Defender_Unit, Start_should_return_err_if_already_started ); +} + +TEST( Defender_Unit, SetMetrics_with_invalid_metrics_group ) +{ + uint8_t i = 0; + + /* Input a dummy, invalid metrics group. */ + AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( 10000, + AWS_IOT_DEFENDER_METRICS_ALL ); + + /* SetMetrics should return "invalid input". */ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); + + /* Assert metrics flag in each metrics group remains 0. */ + for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) + { + TEST_ASSERT_EQUAL( 0, _AwsIotDefenderMetrics.metricsFlag[ i ] ); + } +} + +TEST( Defender_Unit, SetMetrics_with_TCP_connections_all ) +{ + /* Set "all metrics" for TCP connections metrics group. */ + AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_ALL ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); +} + +TEST( Defender_Unit, SetMetrics_after_defender_started ) +{ + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Set "all metrics" for TCP connections metrics group. */ + error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, + AWS_IOT_DEFENDER_METRICS_ALL ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); +} + +TEST( Defender_Unit, Start_with_invalid_mqtt_connection ) +{ + /* Set uninitialized MQTT connection */ + _startInfo.mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); +} + +TEST( Defender_Unit, Start_should_return_success ) +{ + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); +} + +TEST( Defender_Unit, Start_should_return_err_if_already_started ) +{ + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; + + AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + + /* Start defender for a second time. */ + error = AwsIotDefender_Start( &_startInfo ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_ALREADY_STARTED, error ); +} + +TEST( Defender_Unit, SetPeriod_too_short ) +{ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, AwsIotDefender_SetPeriod( 299 ) ); +} + +TEST( Defender_Unit, SetPeriod_with_proper_value ) +{ + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 301 ) ); + + TEST_ASSERT_EQUAL( 301, AwsIotDefender_GetPeriod() ); +} + +TEST( Defender_Unit, SetPeriod_after_started ) +{ + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, + AwsIotDefender_Start( &_startInfo ) ); + + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 600 ) ); + + TEST_ASSERT_EQUAL( 600, AwsIotDefender_GetPeriod() ); +} \ No newline at end of file From 23af5c8daff72754d5efb1bcb7d59218c2c8e1e0 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Thu, 5 Mar 2020 19:18:37 -0500 Subject: [PATCH 439/844] Clean up IotMqtt_Deserialize* proof harnesses (#806) Signed-off-by: Felipe R. Monteiro --- .../proofs/DeserializeConnack/cbmc-batch.yaml | 4 ---- .../DeserializePingresp/cbmc-batch.yaml | 4 ---- cbmc/proofs/DeserializePuback/cbmc-batch.yaml | 4 ---- .../proofs/DeserializePublish/cbmc-batch.yaml | 4 ---- cbmc/proofs/DeserializeUnsuback/Makefile | 10 --------- .../DeserializeUnsuback/cbmc-batch.yaml | 4 ---- .../Makefile | 2 +- .../_IotMqtt_DeserializeConnack_harness.c} | 0 .../cbmc-batch.yaml | 4 ++++ .../cbmc-viewer.json | 22 +++++++++++++++++++ .../Makefile | 2 +- .../_IotMqtt_DeserializePingresp_harness.c} | 0 .../cbmc-batch.yaml | 4 ++++ .../cbmc-viewer.json | 22 +++++++++++++++++++ .../Makefile | 2 +- .../_IotMqtt_DeserializePuback_harness.c} | 0 .../IotMqtt_DeserializePuback/cbmc-batch.yaml | 4 ++++ .../cbmc-viewer.json | 2 +- .../Makefile | 2 +- .../_IotMqtt_DeserializePublish_harness.c} | 0 .../cbmc-batch.yaml | 4 ++++ .../cbmc-viewer.json | 22 +++++++++++++++++++ .../Makefile | 2 +- .../_IotMqtt_DeserializeSuback_harness.c} | 0 .../cbmc-batch.yaml | 2 +- .../cbmc-viewer.json | 22 +++++++++++++++++++ .../IotMqtt_DeserializeUnsuback/Makefile | 10 +++++++++ .../_IotMqtt_DeserializeUnsuback_harness.c} | 0 .../cbmc-batch.yaml | 4 ++++ .../cbmc-viewer.json | 22 +++++++++++++++++++ 30 files changed, 147 insertions(+), 37 deletions(-) delete mode 100644 cbmc/proofs/DeserializeConnack/cbmc-batch.yaml delete mode 100644 cbmc/proofs/DeserializePingresp/cbmc-batch.yaml delete mode 100644 cbmc/proofs/DeserializePuback/cbmc-batch.yaml delete mode 100644 cbmc/proofs/DeserializePublish/cbmc-batch.yaml delete mode 100644 cbmc/proofs/DeserializeUnsuback/Makefile delete mode 100644 cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml rename cbmc/proofs/{DeserializePuback => IotMqtt_DeserializeConnack}/Makefile (84%) rename cbmc/proofs/{DeserializeConnack/DeserializeConnack_harness.c => IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c} (100%) create mode 100644 cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json rename cbmc/proofs/{DeserializePublish => IotMqtt_DeserializePingresp}/Makefile (84%) rename cbmc/proofs/{DeserializePingresp/DeserializePingresp_harness.c => IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c} (100%) create mode 100644 cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json rename cbmc/proofs/{DeserializeConnack => IotMqtt_DeserializePuback}/Makefile (84%) rename cbmc/proofs/{DeserializePuback/DeserializePuback_harness.c => IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c} (100%) create mode 100644 cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml rename cbmc/proofs/{DeserializeSuback => IotMqtt_DeserializePuback}/cbmc-viewer.json (91%) rename cbmc/proofs/{DeserializePingresp => IotMqtt_DeserializePublish}/Makefile (84%) rename cbmc/proofs/{DeserializePublish/DeserializePublish_harness.c => IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c} (100%) create mode 100644 cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json rename cbmc/proofs/{DeserializeSuback => IotMqtt_DeserializeSuback}/Makefile (97%) rename cbmc/proofs/{DeserializeSuback/DeserializeSuback_harness.c => IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c} (100%) rename cbmc/proofs/{DeserializeSuback => IotMqtt_DeserializeSuback}/cbmc-batch.yaml (91%) create mode 100644 cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json create mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile rename cbmc/proofs/{DeserializeUnsuback/DeserializeUnsuback_harness.c => IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c} (100%) create mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json diff --git a/cbmc/proofs/DeserializeConnack/cbmc-batch.yaml b/cbmc/proofs/DeserializeConnack/cbmc-batch.yaml deleted file mode 100644 index c35e2a5af1..0000000000 --- a/cbmc/proofs/DeserializeConnack/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' -goto: DeserializeConnack.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePingresp/cbmc-batch.yaml b/cbmc/proofs/DeserializePingresp/cbmc-batch.yaml deleted file mode 100644 index 505ba4001b..0000000000 --- a/cbmc/proofs/DeserializePingresp/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' -goto: DeserializePingresp.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePuback/cbmc-batch.yaml b/cbmc/proofs/DeserializePuback/cbmc-batch.yaml deleted file mode 100644 index ad66a6fd49..0000000000 --- a/cbmc/proofs/DeserializePuback/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' -goto: DeserializePuback.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePublish/cbmc-batch.yaml b/cbmc/proofs/DeserializePublish/cbmc-batch.yaml deleted file mode 100644 index 0dd0fe5712..0000000000 --- a/cbmc/proofs/DeserializePublish/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' -goto: DeserializePublish.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializeUnsuback/Makefile b/cbmc/proofs/DeserializeUnsuback/Makefile deleted file mode 100644 index f5a359a21e..0000000000 --- a/cbmc/proofs/DeserializeUnsuback/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ENTRY=DeserializeUnsuback - -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ - -include ../Makefile.common diff --git a/cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml b/cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml deleted file mode 100644 index 34bda60043..0000000000 --- a/cbmc/proofs/DeserializeUnsuback/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: '=--bounds-check;--pointer-check;--unwinding-assertions=' -goto: DeserializeUnsuback.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/DeserializePuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeConnack/Makefile similarity index 84% rename from cbmc/proofs/DeserializePuback/Makefile rename to cbmc/proofs/IotMqtt_DeserializeConnack/Makefile index 46515d8b2a..5a297f3c97 100644 --- a/cbmc/proofs/DeserializePuback/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializeConnack/Makefile @@ -1,4 +1,4 @@ -ENTRY=DeserializePuback +ENTRY=_IotMqtt_DeserializeConnack ABSTRACTIONS = \ --remove-function-body IotLog_Generic \ diff --git a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c b/cbmc/proofs/IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c similarity index 100% rename from cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c rename to cbmc/proofs/IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c diff --git a/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml new file mode 100644 index 0000000000..dc42695b88 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" +expected: SUCCESSFUL +goto: _IotMqtt_DeserializeConnack.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json new file mode 100644 index 0000000000..53858156a7 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "_IotMqtt_DeserializeConnack", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/DeserializePublish/Makefile b/cbmc/proofs/IotMqtt_DeserializePingresp/Makefile similarity index 84% rename from cbmc/proofs/DeserializePublish/Makefile rename to cbmc/proofs/IotMqtt_DeserializePingresp/Makefile index 3d76675473..5f172241bc 100644 --- a/cbmc/proofs/DeserializePublish/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializePingresp/Makefile @@ -1,4 +1,4 @@ -ENTRY=DeserializePublish +ENTRY=_IotMqtt_DeserializePingresp ABSTRACTIONS = \ --remove-function-body IotLog_Generic \ diff --git a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c b/cbmc/proofs/IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c similarity index 100% rename from cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c rename to cbmc/proofs/IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c diff --git a/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml new file mode 100644 index 0000000000..032b39a1c5 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" +expected: SUCCESSFUL +goto: _IotMqtt_DeserializePingresp.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json new file mode 100644 index 0000000000..a9c8470c02 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "_IotMqtt_DeserializePingresp", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/DeserializeConnack/Makefile b/cbmc/proofs/IotMqtt_DeserializePuback/Makefile similarity index 84% rename from cbmc/proofs/DeserializeConnack/Makefile rename to cbmc/proofs/IotMqtt_DeserializePuback/Makefile index c2b0f10aef..ef448863c2 100644 --- a/cbmc/proofs/DeserializeConnack/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializePuback/Makefile @@ -1,4 +1,4 @@ -ENTRY=DeserializeConnack +ENTRY=_IotMqtt_DeserializePuback ABSTRACTIONS = \ --remove-function-body IotLog_Generic \ diff --git a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c b/cbmc/proofs/IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c similarity index 100% rename from cbmc/proofs/DeserializePuback/DeserializePuback_harness.c rename to cbmc/proofs/IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c diff --git a/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml new file mode 100644 index 0000000000..87c3065c33 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" +expected: SUCCESSFUL +goto: _IotMqtt_DeserializePuback.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/DeserializeSuback/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json similarity index 91% rename from cbmc/proofs/DeserializeSuback/cbmc-viewer.json rename to cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json index 8c426b8d9d..b38daecdfe 100644 --- a/cbmc/proofs/DeserializeSuback/cbmc-viewer.json +++ b/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json @@ -17,6 +17,6 @@ "IotTaskPool_strerror", "IotTaskPool_TryCancel" ], - "proof-name": "DeserializeSuback", + "proof-name": "_IotMqtt_DeserializePuback", "proof-root": "cbmc/proofs" } diff --git a/cbmc/proofs/DeserializePingresp/Makefile b/cbmc/proofs/IotMqtt_DeserializePublish/Makefile similarity index 84% rename from cbmc/proofs/DeserializePingresp/Makefile rename to cbmc/proofs/IotMqtt_DeserializePublish/Makefile index c5bca579f2..9287861438 100644 --- a/cbmc/proofs/DeserializePingresp/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializePublish/Makefile @@ -1,4 +1,4 @@ -ENTRY=DeserializePingresp +ENTRY=_IotMqtt_DeserializePublish ABSTRACTIONS = \ --remove-function-body IotLog_Generic \ diff --git a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c b/cbmc/proofs/IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c similarity index 100% rename from cbmc/proofs/DeserializePublish/DeserializePublish_harness.c rename to cbmc/proofs/IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c diff --git a/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml new file mode 100644 index 0000000000..81b52871a8 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" +expected: SUCCESSFUL +goto: _IotMqtt_DeserializePublish.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json new file mode 100644 index 0000000000..cfe722fbc2 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "_IotMqtt_DeserializePublish", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile similarity index 97% rename from cbmc/proofs/DeserializeSuback/Makefile rename to cbmc/proofs/IotMqtt_DeserializeSuback/Makefile index df7a7f0cb9..115ee3019e 100644 --- a/cbmc/proofs/DeserializeSuback/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile @@ -1,4 +1,4 @@ -ENTRY=DeserializeSuback +ENTRY=_IotMqtt_DeserializeSuback ################################################################ # Proof assumptions diff --git a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c b/cbmc/proofs/IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c similarity index 100% rename from cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c rename to cbmc/proofs/IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c diff --git a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml similarity index 91% rename from cbmc/proofs/DeserializeSuback/cbmc-batch.yaml rename to cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml index 0b02511d08..fefaa22dba 100644 --- a/cbmc/proofs/DeserializeSuback/cbmc-batch.yaml +++ b/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml @@ -1,4 +1,4 @@ cbmcflags: "--object-bits;8;--unwind;1;--unwindset;valid_IotMqttSubscriptionList.0:2,valid_IotMqttOperationList.0:2,_decodeSubackStatus.0:15;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" expected: SUCCESSFUL -goto: DeserializeSuback.goto +goto: _IotMqtt_DeserializeSuback.goto jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json new file mode 100644 index 0000000000..aa2b240a94 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "_IotMqtt_DeserializeSuback", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile new file mode 100644 index 0000000000..9790d0f2f7 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile @@ -0,0 +1,10 @@ +ENTRY=_IotMqtt_DeserializeUnsuback + +ABSTRACTIONS = \ + --remove-function-body IotLog_Generic \ + +OBJS = \ + $(ENTRY)_harness.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ + +include ../Makefile.common diff --git a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c b/cbmc/proofs/IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c similarity index 100% rename from cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c rename to cbmc/proofs/IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml new file mode 100644 index 0000000000..10adae41b1 --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" +expected: SUCCESSFUL +goto: _IotMqtt_DeserializeUnsuback.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json new file mode 100644 index 0000000000..eb1c975b0c --- /dev/null +++ b/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json @@ -0,0 +1,22 @@ +{ "expected-missing-functions": + [ + "IotLog_Generic", + "IotMqtt_Wait", + "IotMutex_Create", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotSemaphore_TimedWait", + "IotSemaphore_Wait", + "IotTaskPool_CreateJob", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_ScheduleDeferred", + "IotTaskPool_strerror", + "IotTaskPool_TryCancel" + ], + "proof-name": "_IotMqtt_DeserializeUnsuback", + "proof-root": "cbmc/proofs" +} From 501433fdd06432b681a512a521df7a8419bd5a88 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Wed, 18 Mar 2020 16:12:36 -0400 Subject: [PATCH 440/844] Avoid undefined behaviour in IotMqtt_IsSubscribed function (#825) * Remove unnecessary assumption from IotMqtt_IsSubscribed proof Signed-off-by: Felipe R. Monteiro * Avoid undefined behaviour with NULL pTopicFilter Signed-off-by: Felipe R. Monteiro * All variables must be declared at the start of the function Signed-off-by: Felipe R. Monteiro --- .../IotMqtt_IsSubscribed_harness.c | 4 ++-- .../standard/mqtt/src/iot_mqtt_subscription.c | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c b/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c index 7b0d3ad12e..10c23f2388 100644 --- a/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c +++ b/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c @@ -25,7 +25,7 @@ void harness() IotMqttSubscription_t result; IotMqtt_IsSubscribed( mqttConnection, - TopicFilter, - topicFilterLength, + nondet_bool() ? NULL : TopicFilter, + topicFilterLength, nondet_bool() ? NULL : &result ); } diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 765373aacf..678b2db20a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -668,11 +668,18 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, * function is running. */ IotMutex_Lock( &( mqttConnection->subscriptionMutex ) ); - /* Search for a matching subscription. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( mqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ); + if( pTopicFilter == NULL ) + { + IotLogError( "Topic filter must be set." ); + } + else + { + /* Search for a matching subscription. */ + pSubscriptionLink = IotListDouble_FindFirstMatch( &( mqttConnection->subscriptionList ), + NULL, + _topicMatch, + &topicMatchParams ); + } /* Check if a matching subscription was found. */ if( pSubscriptionLink != NULL ) From fe8dda3cd969f06e38f4fecf508baf2d6ca7cf9f Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 18 Mar 2020 16:42:47 -0700 Subject: [PATCH 441/844] fix: 'Potentially uninitialized variable' warning. (#813) Fixed warning by moving initialization. --- libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c index 843b14ebcb..94b8d4d9ec 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c @@ -1028,7 +1028,7 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) { IotMqttError_t status = IOT_MQTT_SUCCESS; /* Internal MQTT packet structure */ - IotMqttPacketInfo_t mqttPacket; + IotMqttPacketInfo_t mqttPacket = { 0 }; if( pMqttPacket == NULL ) { @@ -1043,7 +1043,6 @@ IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) else { /* Set internal mqtt packet parameters. */ - ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( IotMqttPacketInfo_t ) ); mqttPacket.pRemainingData = pMqttPacket->pRemainingData; mqttPacket.remainingLength = pMqttPacket->remainingLength; mqttPacket.type = pMqttPacket->type; @@ -1065,7 +1064,7 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) { IotMqttError_t status = IOT_MQTT_SUCCESS; /* Internal MQTT packet structure */ - IotMqttPacketInfo_t mqttPacket; + IotMqttPacketInfo_t mqttPacket = { 0 }; if( pMqttPacket == NULL ) { @@ -1080,8 +1079,6 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) else { /* Set internal mqtt packet parameters. */ - ( void ) memset( ( void * ) &mqttPacket, 0x00, sizeof( IotMqttPacketInfo_t ) ); - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; mqttPacket.remainingLength = pMqttPacket->remainingLength; mqttPacket.type = pMqttPacket->type; From a98c07635125ceef5aca1269be12f7b2b31b7737 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:12:24 -0700 Subject: [PATCH 442/844] Update style guide (#824) --- doc/guide/developer/style.txt | 47 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/doc/guide/developer/style.txt b/doc/guide/developer/style.txt index 6b6261f281..8efe6aa524 100644 --- a/doc/guide/developer/style.txt +++ b/doc/guide/developer/style.txt @@ -17,6 +17,7 @@ The coding style aims to produce code that is readable and easy to debug. An exa - Code should be well-commented. - Only `/*` and @c *`/` should be used to start and end comments. - All comments end with a period. +- Use of `goto` is discouraged. - Only spaces should be used for indenting. A single indent is 4 spaces. No tab characters should be used. - A parenthesis is usually followed by a space (see @ref guide_developer_styleguide_codingstyle_example). - Lines of code should be less than 80 characters long, although longer lines are permitted. @@ -29,6 +30,8 @@ The coding style aims to produce code that is readable and easy to debug. An exa @endcode - All files must include `iot_config.h` at the top of the file before any other includes. - `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. +- The [GNU complexity](https://www.gnu.org/software/complexity/manual/complexity.html) of every function should be less than 9. +- Deviations from [MISRA C:2012](https://en.wikipedia.org/wiki/MISRA_C) coding guidelines should be documented as comments in the code. @subsection guide_developer_styleguide_codingstyle_typeguidelines Type guidelines @brief Guidelines for variable types. @@ -71,13 +74,13 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Defined constants follow the included headers. */ -/* When possible, parentheses () should be placed around contant values and a type +/* When possible, parentheses () should be placed around constant values and a type * should be specified. */ #define LIBRARY_CONSTANT ( ( int32_t ) 10 ) #define LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ { \ /* Function-like macros are surrounded by curly braces {}. */ - macro_body( argument ); \ + macro_body( ( argument ) ); \ /* Parentheses surround macro arguments for MISRA 20.7 */ } /*-----------------------------------------------------------*/ @@ -93,13 +96,17 @@ typedef struct _structType /* Structs are named along with the typedef. */ { int32_t member; + /* As usage of unions violates MISRA rule 19.2, we must document our usage + * and justification. Unions are used here to reduce the size of this struct. */ union /* Anonymous structs/unions are permitted only inside of other structs. */ { int32_t a; int32_t b; }; - int32_t variableLengthMember[]; /* Variable length arrays (a C99 feature) are permitted. */ + /* Although variable length arrays (a C99 feature) violate MISRA rule 18.7, + * they are permitted provided documentation is supplied in comments. */ + int32_t variableLengthMember[]; } _structType_t; /*-----------------------------------------------------------*/ @@ -135,10 +142,7 @@ static bool _libraryStaticFunction( void * pArgument, size_t i = 0; int32_t localVariable = 0; int32_t * pLocalPointer = ( int32_t * ) pArgument; - - /* Error handling setup, which declares a status variable type and initial - * value. */ - IOT_FUNCTION_ENTRY( bool, true ); + bool status = true; /* All functions make generous use of the logging library. */ IotLogInfo( "Performing calculation..." ); @@ -147,26 +151,27 @@ static bool _libraryStaticFunction( void * pArgument, { IotLogError( "Bad parameters." ); - /* Error handling exit. */ - IOT_SET_AND_GOTO_CLEANUP( false ); + /* Local variable instead of a goto for error handling. */ + status = false; } - - for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ + else /* Note an else clause is used instead of goto for error handling */ { - localVariable += IotLibrary_ExternalFunction( pArgument ); + for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ + { + localVariable += IotLibrary_ExternalFunction( pArgument ); - IotLogDebug( "Current value is %d.", ( int ) localVariable ); - } + IotLogDebug( "Current value is %d.", ( int ) localVariable ); + } - if( localVariable < 0 ) - { - IotLogWarn( "Failed to calculate positive value." ); - } + if( localVariable < 0 ) + { + IotLogWarn( "Failed to calculate positive value." ); + } - IotLogInfo( "Calculation done." ); + IotLogInfo( "Calculation done." ); + } - /* Error handling exit. */ - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /* A separator is placed between all function implementations. */ From 2d337f5d334a2ff45446c1def6e7d699534819de Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Fri, 20 Mar 2020 17:09:57 -0400 Subject: [PATCH 443/844] Avoid null dereference in IotMqtt_PublishSync (#828) * Avoid null dereference in IotMqtt_PublishSync Signed-off-by: Felipe R. Monteiro * All variables must be declared at the start of the function Signed-off-by: Felipe R. Monteiro --- libraries/standard/mqtt/src/iot_mqtt_api.c | 38 +++++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index d4d0e3d0a6..319ca82772 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -1773,29 +1773,35 @@ IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, /* Flags are currently ignored. */ ( void ) flags; - /* Set the waitable flag and reference for QoS 1 PUBLISH. */ - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + if( pPublishInfo == NULL ) { - syncFlags |= IOT_MQTT_FLAG_WAITABLE; - pPublishOperation = &publishOperation; + status = IOT_MQTT_BAD_PARAMETER; } + else + { + /* Set the waitable flag and reference for QoS 1 PUBLISH. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) + { + syncFlags |= IOT_MQTT_FLAG_WAITABLE; + pPublishOperation = &publishOperation; + } - /* Call the asynchronous PUBLISH function. */ - status = IotMqtt_PublishAsync( mqttConnection, - pPublishInfo, - syncFlags, - NULL, - pPublishOperation ); + /* Call the asynchronous PUBLISH function. */ + status = IotMqtt_PublishAsync( mqttConnection, + pPublishInfo, + syncFlags, + NULL, + pPublishOperation ); - /* Wait for a queued QoS 1 PUBLISH to complete. */ - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) - { - if( status == IOT_MQTT_STATUS_PENDING ) + /* Wait for a queued QoS 1 PUBLISH to complete. */ + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { - status = IotMqtt_Wait( publishOperation, timeoutMs ); + if( status == IOT_MQTT_STATUS_PENDING ) + { + status = IotMqtt_Wait( publishOperation, timeoutMs ); + } } } - return status; } From 9ea467a8facdc56b8de086a34bec4075572c6580 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Fri, 20 Mar 2020 17:53:53 -0400 Subject: [PATCH 444/844] Adds --conversion-check flag to CBMC Makefile (#815) * Adds --conversion-check flag to CBMC Makefile Signed-off-by: Felipe R. Monteiro * Remove unnecessary assertion from allocate_IotMqttSubscriptionListElt Signed-off-by: Felipe R. Monteiro * Adds unwinding bounds to DeserializeSuback proof Signed-off-by: Felipe R. Monteiro --- cbmc/proofs/IotMqtt_DeserializeSuback/Makefile | 5 +++-- cbmc/proofs/Makefile.common | 11 ++++++----- cbmc/proofs/mqtt_state.c | 4 +--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile index 115ee3019e..03368c8070 100644 --- a/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile @@ -28,11 +28,12 @@ DEF += -DCBMC=1 -DCBMC_PROOF_DESERIALIZE_SUBACK=1 ################################################################ # Loop unwinding +LOOP += _decodeSubackStatus.0:$(BUFFER_SIZE) +LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) -LOOP += _decodeSubackStatus.0:$(BUFFER_SIZE) - UNWINDING += --unwind 1 UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 3b94ddd3bf..b217481472 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -75,17 +75,18 @@ CBMC_MAX_OBJECT_SIZE = "(SIZE_MAX>>(CBMC_OBJECT_BITS+1))" CBMCFLAGS += \ $(UNWINDING) \ --bounds-check \ - --pointer-check \ - --unwinding-assertions \ - --nondet-static \ + --conversion-check \ --div-by-zero-check \ --float-overflow-check \ + --flush \ --nan-check \ + --nondet-static \ + --pointer-check \ --pointer-overflow-check \ - --undefined-shift-check \ --signed-overflow-check \ + --undefined-shift-check \ --unsigned-overflow-check \ - --flush \ + --unwinding-assertions \ goto: $(ENTRY).goto diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c index d21efb6eee..3de379122f 100644 --- a/cbmc/proofs/mqtt_state.c +++ b/cbmc/proofs/mqtt_state.c @@ -380,9 +380,7 @@ bool valid_IotMqttSubscriptionArray( const IotMqttSubscription_t *pSub, _mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *pElt ) { - size_t length; - // Assumption avoids arithmetic overflow in malloc, rechecked in valid_* - __CPROVER_assume( length < CBMC_MAX_OBJECT_SIZE - sizeof( *pElt ) ); + uint16_t length; if ( pElt == NULL ) pElt = malloc_can_fail( sizeof( *pElt ) + length ); if ( pElt == NULL ) return NULL; From ec452c3a02067fdf945872ca8b6c47b307606c9d Mon Sep 17 00:00:00 2001 From: qiutongs Date: Mon, 23 Mar 2020 14:09:32 -0700 Subject: [PATCH 445/844] Remove goto statements in aws_iot_shadow_api.c (#774) --- libraries/aws/shadow/src/aws_iot_shadow_api.c | 934 +++++++++--------- 1 file changed, 452 insertions(+), 482 deletions(-) diff --git a/libraries/aws/shadow/src/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c index 254f593f43..2441b28ff4 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_api.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_api.c @@ -34,9 +34,6 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" -/* Error handling include. */ -#include "iot_error.h" - /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -207,63 +204,69 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, const AwsIotShadowCallbackInfo_t * pCallbackInfo, const AwsIotShadowOperation_t * pOperation ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; /* Type is not used when logging is disabled. */ ( void ) type; /* Check Thing Name. */ - if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == false ) - { - IotLogError( "Thing Name for Shadow %s is not valid.", - _pAwsIotShadowOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } - - /* Check the waitable operation flag. */ - if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == true ) { - /* Check that a reference pointer is provided for a waitable operation. */ - if( pOperation == NULL ) + /* Check the waitable operation flag. */ + if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { - IotLogError( "Reference must be set for a waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + /* Check that a reference pointer is provided for a waitable operation. */ + if( pOperation != NULL ) + { + /* A callback should not be set for a waitable operation. */ + if( pCallbackInfo != NULL ) + { + IotLogError( "Callback should not be set for a waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } + } + else + { + IotLogError( "Reference must be set for a waitable Shadow %s.", + _pAwsIotShadowOperationNames[ type ] ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } } - /* A callback should not be set for a waitable operation. */ - if( pCallbackInfo != NULL ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - IotLogError( "Callback should not be set for a waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); + /* A callback info must be passed to a non-waitable GET. */ + if( ( type == SHADOW_GET ) && + ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) && + ( pCallbackInfo == NULL ) ) + { + IotLogError( "Callback info must be provided for non-waitable Shadow GET." ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } + else + { + /* Check that a callback function is set. */ + if( ( pCallbackInfo != NULL ) && + ( pCallbackInfo->function == NULL ) ) + { + IotLogError( "Callback function must be set for Shadow %s callback.", + _pAwsIotShadowOperationNames[ type ] ); + + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } + } } } - - /* A callback info must be passed to a non-waitable GET. */ - if( ( type == SHADOW_GET ) && - ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) && - ( pCallbackInfo == NULL ) ) - { - IotLogError( "Callback info must be provided for non-waitable Shadow GET." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } - - /* Check that a callback function is set. */ - if( ( pCallbackInfo != NULL ) && - ( pCallbackInfo->function == NULL ) ) + else { - IotLogError( "Callback function must be set for Shadow %s callback.", + IotLogError( "Thing Name for Shadow %s is not valid.", _pAwsIotShadowOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -272,62 +275,53 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, uint32_t flags, const AwsIotShadowDocumentInfo_t * pDocumentInfo ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; /* This function should only be called for Shadow GET or UPDATE. */ AwsIotShadow_Assert( ( type == SHADOW_GET ) || ( type == SHADOW_UPDATE ) ); /* Check QoS. */ - if( pDocumentInfo->qos != IOT_MQTT_QOS_0 ) + if( ( pDocumentInfo->qos != IOT_MQTT_QOS_0 ) && ( pDocumentInfo->qos != IOT_MQTT_QOS_1 ) ) { - if( pDocumentInfo->qos != IOT_MQTT_QOS_1 ) - { - IotLogError( "QoS for Shadow %d must be 0 or 1.", - _pAwsIotShadowOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } + IotLogError( "QoS for Shadow %d must be 0 or 1.", + _pAwsIotShadowOperationNames[ type ] ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; } - /* Check the retry parameters. */ - if( pDocumentInfo->retryLimit > 0U ) + else if( ( pDocumentInfo->retryLimit > 0U ) && ( pDocumentInfo->retryMs == 0U ) ) { - if( pDocumentInfo->retryMs == 0U ) - { - IotLogError( "Retry time of Shadow %s must be positive.", - _pAwsIotShadowOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } + IotLogError( "Retry time of Shadow %s must be positive.", + _pAwsIotShadowOperationNames[ type ] ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; } - - /* Check members relevant to a Shadow GET. */ - if( type == SHADOW_GET ) + else { - /* Check memory allocation function for waitable GET. */ - if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && - ( pDocumentInfo->u.get.mallocDocument == NULL ) ) + /* Check members relevant to a Shadow GET. */ + if( type == SHADOW_GET ) { - IotLogError( "Memory allocation function must be set for waitable Shadow GET." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + /* Check memory allocation function for waitable GET. */ + if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && + ( pDocumentInfo->u.get.mallocDocument == NULL ) ) + { + IotLogError( "Memory allocation function must be set for waitable Shadow GET." ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } } - } - /* Check members relevant to a Shadow UPDATE. */ - else - { - /* Check UPDATE document pointer and length. */ - if( ( pDocumentInfo->u.update.pUpdateDocument == NULL ) || - ( pDocumentInfo->u.update.updateDocumentLength == 0U ) ) + /* Check members relevant to a Shadow UPDATE. */ + else { - IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" - " have length 0." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + /* Check UPDATE document pointer and length. */ + if( ( pDocumentInfo->u.update.pUpdateDocument == NULL ) || + ( pDocumentInfo->u.update.updateDocumentLength == 0U ) ) + { + IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" + " have length 0." ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -338,121 +332,117 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio size_t thingNameLength, const AwsIotShadowCallbackInfo_t * pCallbackInfo ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; bool subscriptionMutexLocked = false; _shadowSubscription_t * pSubscription = NULL; /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == false ) + if( _checkInit() == true ) { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + /* Check parameters. */ + status = _validateThingNameFlags( _AwsIotShadow_IntToShadowOperationType( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ), + pThingName, + thingNameLength, + 0, + pCallbackInfo, + NULL ); } - - /* Check parameters. */ - status = _validateThingNameFlags( _AwsIotShadow_IntToShadowOperationType( ( ( uint32_t ) type ) + SHADOW_OPERATION_COUNT ), - pThingName, - thingNameLength, - 0, - pCallbackInfo, - NULL ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + else { - IOT_GOTO_CLEANUP(); + status = AWS_IOT_SHADOW_NOT_INITIALIZED; } - IotLogInfo( "(%.*s) Modifying Shadow %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - /* Lock the subscription list mutex to check for an existing subscription - * object. */ - IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); - subscriptionMutexLocked = true; - - /* Check for an existing subscription. This function will attempt to allocate - * a new subscription if not found. */ - pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength, - true ); - - if( pSubscription == NULL ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* No existing subscription was found, and no new subscription could be - * allocated. */ - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); - } + IotLogInfo( "(%.*s) Modifying Shadow %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); - /* Check for an existing callback. */ - if( pSubscription->callbacks[ type ].function != NULL ) - { - /* Replace existing callback. */ - if( pCallbackInfo != NULL ) - { - IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); + /* Lock the subscription list mutex to check for an existing subscription + * object. */ + IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + subscriptionMutexLocked = true; - pSubscription->callbacks[ type ] = *pCallbackInfo; - } - /* Remove existing callback. */ - else + /* Check for an existing subscription. This function will attempt to allocate + * a new subscription if not found. */ + pSubscription = _AwsIotShadow_FindSubscription( pThingName, + thingNameLength, + true ); + + if( pSubscription == NULL ) { - IotLogInfo( "(%.*s) Removing existing %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - /* Unsubscribe, then clear the callback information. */ - ( void ) _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_UnsubscribeSync ); - ( void ) memset( &( pSubscription->callbacks[ type ] ), - 0x00, - sizeof( AwsIotShadowCallbackInfo_t ) ); - - /* Check if this subscription object can be removed. */ - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); + /* No existing subscription was found, and no new subscription could be + * allocated. */ + status = AWS_IOT_SHADOW_NO_MEMORY; } } - /* No existing callback. */ - else + + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* Add new callback. */ - if( pCallbackInfo != NULL ) + /* Check for an existing callback. */ + if( pSubscription->callbacks[ type ].function != NULL ) { - IotLogInfo( "(%.*s) Adding new %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - status = _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_SubscribeSync ); - - if( status == AWS_IOT_SHADOW_SUCCESS ) + /* Replace existing callback. */ + if( pCallbackInfo != NULL ) { + IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + pSubscription->callbacks[ type ] = *pCallbackInfo; } + /* Remove existing callback. */ else { - /* On failure, check if this subscription can be removed. */ + IotLogInfo( "(%.*s) Removing existing %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + + /* Unsubscribe, then clear the callback information. */ + ( void ) _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_UnsubscribeSync ); + ( void ) memset( &( pSubscription->callbacks[ type ] ), + 0x00, + sizeof( AwsIotShadowCallbackInfo_t ) ); + + /* Check if this subscription object can be removed. */ _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); } } - /* Do nothing; set return value to success. */ + /* No existing callback. */ else { - status = AWS_IOT_SHADOW_SUCCESS; + /* Add new callback. */ + if( pCallbackInfo != NULL ) + { + IotLogInfo( "(%.*s) Adding new %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + + status = _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_SubscribeSync ); + + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + pSubscription->callbacks[ type ] = *pCallbackInfo; + } + else + { + /* On failure, check if this subscription can be removed. */ + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); + } + } } } - IOT_FUNCTION_CLEANUP_BEGIN(); - if( subscriptionMutexLocked == true ) { IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); @@ -464,7 +454,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio _pAwsIotShadowCallbackNames[ type ], AwsIotShadow_strerror( status ) ); - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -474,7 +464,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt _shadowSubscription_t * pSubscription, AwsIotMqttFunction_t mqttOperation ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; char * pTopicFilter = NULL; @@ -519,57 +509,55 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt &pTopicFilter, &operationTopicLength ); - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Place the callback suffix in the topic filter. */ - ( void ) memcpy( pTopicFilter + operationTopicLength, - pCallbackSuffix[ type ], - pCallbackSuffixLength[ type ] ); - - IotLogDebug( "%s subscription for %.*s", - mqttOperation == IotMqtt_SubscribeSync ? "Adding" : "Removing", - operationTopicLength + pCallbackSuffixLength[ type ], - pTopicFilter ); - - /* Set the members of the MQTT subscription. */ - subscription.qos = IOT_MQTT_QOS_1; - subscription.pTopicFilter = pTopicFilter; - subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); - subscription.callback.pCallbackContext = NULL; - subscription.callback.function = pCallbackWrapper[ type ]; - - /* Call the MQTT operation function. */ - mqttStatus = mqttOperation( mqttConnection, - &subscription, - 1, - 0, - _AwsIotShadowMqttTimeoutMs ); - - /* Check the result of the MQTT operation. */ - if( mqttStatus != IOT_MQTT_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == IotMqtt_SubscribeSync ? "subscribe to" : "unsubscribe from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ], - IotMqtt_strerror( mqttStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - IOT_SET_AND_GOTO_CLEANUP( SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( mqttStatus ) ); + /* Place the callback suffix in the topic filter. */ + ( void ) memcpy( pTopicFilter + operationTopicLength, + pCallbackSuffix[ type ], + pCallbackSuffixLength[ type ] ); + + IotLogDebug( "%s subscription for %.*s", + mqttOperation == IotMqtt_SubscribeSync ? "Adding" : "Removing", + operationTopicLength + pCallbackSuffixLength[ type ], + pTopicFilter ); + + /* Set the members of the MQTT subscription. */ + subscription.qos = IOT_MQTT_QOS_1; + subscription.pTopicFilter = pTopicFilter; + subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); + subscription.callback.pCallbackContext = NULL; + subscription.callback.function = pCallbackWrapper[ type ]; + + /* Call the MQTT operation function. */ + mqttStatus = mqttOperation( mqttConnection, + &subscription, + 1, + 0, + _AwsIotShadowMqttTimeoutMs ); + + /* Check the result of the MQTT operation. */ + if( mqttStatus == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "Successfully %s %.*s Shadow %s callback.", + mqttOperation == IotMqtt_SubscribeSync ? "subscribed to" : "unsubscribed from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + } + else + { + IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", + mqttOperation == IotMqtt_SubscribeSync ? "subscribe to" : "unsubscribe from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ], + IotMqtt_strerror( mqttStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( mqttStatus ); + } } - IotLogDebug( "Successfully %s %.*s Shadow %s callback.", - mqttOperation == IotMqtt_SubscribeSync ? "subscribed to" : "unsubscribed from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - IOT_FUNCTION_CLEANUP_BEGIN(); - /* MQTT subscribe should check the subscription topic buffer. */ if( mqttOperation == IotMqtt_SubscribeSync ) { @@ -581,7 +569,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt } } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ @@ -599,50 +587,46 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, pMessage->u.message.info.topicNameLength, &pThingName, - &thingNameLength ) == false ) - { - IOT_GOTO_CLEANUP(); - } - - /* Search for a matching subscription. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - - pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength, - false ); - - if( pSubscription == NULL ) + &thingNameLength ) == true ) { - /* No subscription found. */ - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - IOT_GOTO_CLEANUP(); - } + /* Search for a matching subscription. */ + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - /* Ensure that a callback function is set. */ - AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); + pSubscription = _AwsIotShadow_FindSubscription( pThingName, + thingNameLength, + false ); - /* Copy the subscription callback info, as the subscription may be modified - * when the subscriptions mutex is released. */ - callbackInfo = pSubscription->callbacks[ type ]; + if( pSubscription == NULL ) + { + /* No subscription found. */ + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + } + else + { + /* Ensure that a callback function is set. */ + AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + /* Copy the subscription callback info, as the subscription may be modified + * when the subscriptions mutex is released. */ + callbackInfo = pSubscription->callbacks[ type ]; - /* Set the callback type. Shadow callbacks are enumerated after the operations. */ - callbackParam.callbackType = _AwsIotShadow_IntToShadowCallbackType( ( ( uint32_t ) type ) + SHADOW_OPERATION_COUNT ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - /* Set the remaining members of the callback param. */ - callbackParam.mqttConnection = pMessage->mqttConnection; - callbackParam.pThingName = pThingName; - callbackParam.thingNameLength = thingNameLength; - callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; - callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; + /* Set the callback type. Shadow callbacks are enumerated after the operations. */ + callbackParam.callbackType = _AwsIotShadow_IntToShadowCallbackType( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ); - /* Invoke the callback function. */ - callbackInfo.function( callbackInfo.pCallbackContext, - &callbackParam ); + /* Set the remaining members of the callback param. */ + callbackParam.mqttConnection = pMessage->mqttConnection; + callbackParam.pThingName = pThingName; + callbackParam.thingNameLength = thingNameLength; + callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; + callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; - /* This function uses cleanup sections to exit on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); + /* Invoke the callback function. */ + callbackInfo.function( callbackInfo.pCallbackContext, + &callbackParam ); + } + } } /*-----------------------------------------------------------*/ @@ -756,69 +740,65 @@ AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection const AwsIotShadowCallbackInfo_t * pCallbackInfo, AwsIotShadowOperation_t * const pDeleteOperation ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; _shadowOperation_t * pOperation = NULL; /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == false ) + if( _checkInit() == true ) { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); - } - - /* Validate the Thing Name and flags for Shadow DELETE. */ - status = _validateThingNameFlags( SHADOW_DELETE, - pThingName, - thingNameLength, - flags, - pCallbackInfo, - pDeleteOperation ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - /* The Thing Name or some flag was invalid. */ - IOT_GOTO_CLEANUP(); - } - - /* Allocate a new Shadow operation for DELETE. */ - status = _AwsIotShadow_CreateOperation( &pOperation, - SHADOW_DELETE, - flags, - pCallbackInfo ); + /* Validate the Thing Name and flags for Shadow DELETE. */ + status = _validateThingNameFlags( SHADOW_DELETE, + pThingName, + thingNameLength, + flags, + pCallbackInfo, + pDeleteOperation ); + + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + /* Allocate a new Shadow operation for DELETE. */ + status = _AwsIotShadow_CreateOperation( &pOperation, + SHADOW_DELETE, + flags, + pCallbackInfo ); + } - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - /* No memory for a new Shadow operation. */ - IOT_GOTO_CLEANUP(); - } + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + /* Check the members set by Shadow operation creation. */ + AwsIotShadow_Assert( pOperation != NULL ); + AwsIotShadow_Assert( pOperation->type == SHADOW_DELETE ); + AwsIotShadow_Assert( pOperation->flags == flags ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + + /* Set the reference if provided. This must be done before the Shadow operation + * is processed. */ + if( pDeleteOperation != NULL ) + { + *pDeleteOperation = pOperation; + } - /* Check the members set by Shadow operation creation. */ - AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == SHADOW_DELETE ); - AwsIotShadow_Assert( pOperation->flags == flags ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + /* Process the Shadow operation. This subscribes to any required topics and + * sends the MQTT message for the Shadow operation. */ + status = _AwsIotShadow_ProcessOperation( mqttConnection, + pThingName, + thingNameLength, + pOperation, + NULL ); - /* Set the reference if provided. This must be done before the Shadow operation - * is processed. */ - if( pDeleteOperation != NULL ) - { - *pDeleteOperation = pOperation; + /* If the Shadow operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteOperation != NULL ) ) + { + *pDeleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + } + } } - - /* Process the Shadow operation. This subscribes to any required topics and - * sends the MQTT message for the Shadow operation. */ - status = _AwsIotShadow_ProcessOperation( mqttConnection, - pThingName, - thingNameLength, - pOperation, - NULL ); - - /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteOperation != NULL ) ) + else { - *pDeleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + status = AWS_IOT_SHADOW_NOT_INITIALIZED; } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -863,83 +843,77 @@ AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, const AwsIotShadowCallbackInfo_t * pCallbackInfo, AwsIotShadowOperation_t * const pGetOperation ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; _shadowOperation_t * pOperation = NULL; /* Check that AwsIotShadow_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + status = AWS_IOT_SHADOW_NOT_INITIALIZED; } - /* Validate the Thing Name and flags for Shadow GET. */ - status = _validateThingNameFlags( SHADOW_GET, - pGetInfo->pThingName, - pGetInfo->thingNameLength, - flags, - pCallbackInfo, - pGetOperation ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* The Thing Name or some flag was invalid. */ - IOT_GOTO_CLEANUP(); + /* Validate the Thing Name and flags for Shadow GET. */ + status = _validateThingNameFlags( SHADOW_GET, + pGetInfo->pThingName, + pGetInfo->thingNameLength, + flags, + pCallbackInfo, + pGetOperation ); } - /* Validate the document info for Shadow GET. */ - status = _validateDocumentInfo( SHADOW_GET, - flags, - pGetInfo ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* Document info was invalid. */ - IOT_GOTO_CLEANUP(); + /* Validate the document info for Shadow GET. */ + status = _validateDocumentInfo( SHADOW_GET, + flags, + pGetInfo ); } - /* Allocate a new Shadow operation for GET. */ - status = _AwsIotShadow_CreateOperation( &pOperation, - SHADOW_GET, - flags, - pCallbackInfo ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* No memory for a new Shadow operation. */ - IOT_GOTO_CLEANUP(); + /* Allocate a new Shadow operation for GET. */ + status = _AwsIotShadow_CreateOperation( &pOperation, + SHADOW_GET, + flags, + pCallbackInfo ); } - /* Check the members set by Shadow operation creation. */ - AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == SHADOW_GET ); - AwsIotShadow_Assert( pOperation->flags == flags ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - - /* Copy the memory allocation function. */ - pOperation->u.get.mallocDocument = pGetInfo->u.get.mallocDocument; - - /* Set the reference if provided. This must be done before the Shadow operation - * is processed. */ - if( pGetOperation != NULL ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - *pGetOperation = pOperation; - } + /* Check the members set by Shadow operation creation. */ + AwsIotShadow_Assert( pOperation != NULL ); + AwsIotShadow_Assert( pOperation->type == SHADOW_GET ); + AwsIotShadow_Assert( pOperation->flags == flags ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + + /* Copy the memory allocation function. */ + pOperation->u.get.mallocDocument = pGetInfo->u.get.mallocDocument; + + /* Set the reference if provided. This must be done before the Shadow operation + * is processed. */ + if( pGetOperation != NULL ) + { + *pGetOperation = pOperation; + } - /* Process the Shadow operation. This subscribes to any required topics and - * sends the MQTT message for the Shadow operation. */ - status = _AwsIotShadow_ProcessOperation( mqttConnection, - pGetInfo->pThingName, - pGetInfo->thingNameLength, - pOperation, - pGetInfo ); + /* Process the Shadow operation. This subscribes to any required topics and + * sends the MQTT message for the Shadow operation. */ + status = _AwsIotShadow_ProcessOperation( mqttConnection, + pGetInfo->pThingName, + pGetInfo->thingNameLength, + pOperation, + pGetInfo ); - /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetOperation != NULL ) ) - { - *pGetOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + /* If the Shadow operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetOperation != NULL ) ) + { + *pGetOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -987,7 +961,7 @@ AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection const AwsIotShadowCallbackInfo_t * pCallbackInfo, AwsIotShadowOperation_t * const pUpdateOperation ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; _shadowOperation_t * pOperation = NULL; const char * pClientToken = NULL; size_t clientTokenLength = 0; @@ -995,103 +969,103 @@ AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection /* Check that AwsIotShadow_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + status = AWS_IOT_SHADOW_NOT_INITIALIZED; } - /* Validate the Thing Name and flags for Shadow UPDATE. */ - status = _validateThingNameFlags( SHADOW_UPDATE, - pUpdateInfo->pThingName, - pUpdateInfo->thingNameLength, - flags, - pCallbackInfo, - pUpdateOperation ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* The Thing Name or some flag was invalid. */ - IOT_GOTO_CLEANUP(); + /* Validate the Thing Name and flags for Shadow UPDATE. */ + status = _validateThingNameFlags( SHADOW_UPDATE, + pUpdateInfo->pThingName, + pUpdateInfo->thingNameLength, + flags, + pCallbackInfo, + pUpdateOperation ); } - /* Validate the document info for Shadow UPDATE. */ - status = _validateDocumentInfo( SHADOW_UPDATE, - flags, - pUpdateInfo ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* Document info was invalid. */ - IOT_GOTO_CLEANUP(); + /* Validate the document info for Shadow UPDATE. */ + status = _validateDocumentInfo( SHADOW_UPDATE, + flags, + pUpdateInfo ); } - /* Check UPDATE document for a client token. */ - if( AwsIot_GetClientToken( pUpdateInfo->u.update.pUpdateDocument, - pUpdateInfo->u.update.updateDocumentLength, - &pClientToken, - &clientTokenLength ) == false ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - IotLogError( "Shadow document for Shadow UPDATE does not contain a valid client token." ); + /* Check UPDATE document for a client token. */ + if( AwsIot_GetClientToken( pUpdateInfo->u.update.pUpdateDocument, + pUpdateInfo->u.update.updateDocumentLength, + &pClientToken, + &clientTokenLength ) == false ) + { + IotLogError( "Shadow document for Shadow UPDATE does not contain a valid client token." ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; + } } - /* Allocate a new Shadow operation for UPDATE. */ - status = _AwsIotShadow_CreateOperation( &pOperation, - SHADOW_UPDATE, - flags, - pCallbackInfo ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - /* No memory for a new Shadow operation. */ - IOT_GOTO_CLEANUP(); + /* Allocate a new Shadow operation for UPDATE. */ + status = _AwsIotShadow_CreateOperation( &pOperation, + SHADOW_UPDATE, + flags, + pCallbackInfo ); } - /* Check the members set by Shadow operation creation. */ - AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == SHADOW_UPDATE ); - AwsIotShadow_Assert( pOperation->flags == flags ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + /* Check the members set by Shadow operation creation. */ + AwsIotShadow_Assert( pOperation != NULL ); + AwsIotShadow_Assert( pOperation->type == SHADOW_UPDATE ); + AwsIotShadow_Assert( pOperation->flags == flags ); + AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - /* Allocate memory for the client token. */ - pOperation->u.update.pClientToken = AwsIotShadow_MallocString( clientTokenLength ); + /* Allocate memory for the client token. */ + pOperation->u.update.pClientToken = AwsIotShadow_MallocString( clientTokenLength ); - if( pOperation->u.update.pClientToken == NULL ) - { - IotLogError( "Failed to allocate memory for Shadow update client token." ); - _AwsIotShadow_DestroyOperation( pOperation ); + if( pOperation->u.update.pClientToken == NULL ) + { + IotLogError( "Failed to allocate memory for Shadow update client token." ); + _AwsIotShadow_DestroyOperation( pOperation ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); + status = AWS_IOT_SHADOW_NO_MEMORY; + } } - /* Copy the client token. The client token must be copied in case the application - * frees the buffer containing it. */ - ( void ) memcpy( ( void * ) pOperation->u.update.pClientToken, - pClientToken, - clientTokenLength ); - pOperation->u.update.clientTokenLength = clientTokenLength; - - /* Set the reference if provided. This must be done before the Shadow operation - * is processed. */ - if( pUpdateOperation != NULL ) + if( status == AWS_IOT_SHADOW_SUCCESS ) { - *pUpdateOperation = pOperation; - } + /* Copy the client token. The client token must be copied in case the application + * frees the buffer containing it. */ + ( void ) memcpy( ( void * ) pOperation->u.update.pClientToken, + pClientToken, + clientTokenLength ); + pOperation->u.update.clientTokenLength = clientTokenLength; + + /* Set the reference if provided. This must be done before the Shadow operation + * is processed. */ + if( pUpdateOperation != NULL ) + { + *pUpdateOperation = pOperation; + } - /* Process the Shadow operation. This subscribes to any required topics and - * sends the MQTT message for the Shadow operation. */ - status = _AwsIotShadow_ProcessOperation( mqttConnection, - pUpdateInfo->pThingName, - pUpdateInfo->thingNameLength, - pOperation, - pUpdateInfo ); + /* Process the Shadow operation. This subscribes to any required topics and + * sends the MQTT message for the Shadow operation. */ + status = _AwsIotShadow_ProcessOperation( mqttConnection, + pUpdateInfo->pThingName, + pUpdateInfo->thingNameLength, + pOperation, + pUpdateInfo ); - /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateOperation != NULL ) ) - { - *pUpdateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + /* If the Shadow operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateOperation != NULL ) ) + { + *pUpdateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + return status; } /*-----------------------------------------------------------*/ @@ -1133,77 +1107,73 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, const char ** const pShadowDocument, size_t * const pShadowDocumentLength ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; /* Check that AwsIotShadow_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + status = AWS_IOT_SHADOW_NOT_INITIALIZED; } - /* Check that reference is set. */ - if( operation == NULL ) + else if( operation == NULL ) { IotLogError( "Operation reference cannot be NULL." ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; } - /* Check that reference is waitable. */ - if( ( operation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) + else if( ( operation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) { IotLogError( "Operation is not waitable." ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); + status = AWS_IOT_SHADOW_BAD_PARAMETER; } - /* Check that output parameters are set for a Shadow GET. */ - if( operation->type == SHADOW_GET ) + else if( ( operation->type == SHADOW_GET ) && ( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) ) { - if( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) - { - IotLogError( "Output buffer and size pointer must be set for Shadow GET." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } - } + IotLogError( "Output buffer and size pointer must be set for Shadow GET." ); - /* Wait for a response to the Shadow operation. */ - if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), - timeoutMs ) == true ) - { - status = operation->status; + status = AWS_IOT_SHADOW_BAD_PARAMETER; } else { - status = AWS_IOT_SHADOW_TIMEOUT; - } + /* Wait for a response to the Shadow operation. */ + if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), + timeoutMs ) == true ) + { + status = operation->status; + } + else + { + status = AWS_IOT_SHADOW_TIMEOUT; + } - /* Remove the completed operation from the pending operation list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_Remove( &( operation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( operation, - operation->pSubscription->pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - /* Set the output parameters for Shadow GET. */ - if( ( operation->type == SHADOW_GET ) && - ( status == AWS_IOT_SHADOW_SUCCESS ) ) - { - *pShadowDocument = operation->u.get.pDocument; - *pShadowDocumentLength = operation->u.get.documentLength; - } + /* Remove the completed operation from the pending operation list. */ + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_Remove( &( operation->link ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - /* Destroy the Shadow operation. */ - _AwsIotShadow_DestroyOperation( operation ); + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + _AwsIotShadow_DecrementReferences( operation, + operation->pSubscription->pTopicBuffer, + NULL ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - IOT_FUNCTION_EXIT_NO_CLEANUP(); + /* Set the output parameters for Shadow GET. */ + if( ( operation->type == SHADOW_GET ) && + ( status == AWS_IOT_SHADOW_SUCCESS ) ) + { + *pShadowDocument = operation->u.get.pDocument; + *pShadowDocumentLength = operation->u.get.documentLength; + } + + /* Destroy the Shadow operation. */ + _AwsIotShadow_DestroyOperation( operation ); + } + + return status; } /*-----------------------------------------------------------*/ From 01f1d566e8eda10afa531fefcac3b61b559b0a2c Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Wed, 25 Mar 2020 18:22:54 -0400 Subject: [PATCH 446/844] Adds a proof harness for IotMqtt_PublishSync function (#830) * Adds a proof harness for IotMqtt_PublishSync function Signed-off-by: Felipe R. Monteiro * Implement comments from reviewers Signed-off-by: Felipe R. Monteiro * Update Makefile comments Signed-off-by: Felipe R. Monteiro * More accurate IotSemaphore model Signed-off-by: Felipe R. Monteiro * Implement PRBR's comments Signed-off-by: Felipe R. Monteiro --- ...et-CBMC-proofs-stub-out-RemoveAllMat.patch | 3 +- .../IotMqtt_PublishSync_harness.c | 174 ++++++++++++++++ cbmc/proofs/IotMqtt_PublishSync/Makefile | 60 ++++++ .../IotMqtt_PublishSync/cbmc-batch.yaml | 4 + .../IotMqtt_PublishSync/cbmc-viewer.json | 16 ++ cbmc/proofs/mqtt_state.c | 190 +++++++++++++----- cbmc/proofs/mqtt_state.h | 4 + 7 files changed, 394 insertions(+), 57 deletions(-) create mode 100644 cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c create mode 100644 cbmc/proofs/IotMqtt_PublishSync/Makefile create mode 100644 cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json diff --git a/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch b/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch index 071e668871..063f46309e 100644 --- a/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch +++ b/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch @@ -15,7 +15,7 @@ index 689ea5e..ffb3469 100644 * or its value is `0`. */ /* @[declare_linear_containers_list_double_removeallmatches] */ -+#ifdef CBMC_PROOF_DESERIALIZE_SUBACK ++#ifdef CBMC_STUB_REMOVEALLMATCHES +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), + void * pMatch, @@ -35,4 +35,3 @@ index 689ea5e..ffb3469 100644 * @brief Create a new queue. -- 2.7.4 - diff --git a/cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c b/cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c new file mode 100644 index 0000000000..c65451efb1 --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c @@ -0,0 +1,174 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file IotMqtt_PublishSync_harness.c + * @brief Implements the proof harness for IotMqtt_PublishSync function. + */ + +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" +#include "platform/iot_threads.h" + +#include +#include + +#include "mqtt_state.h" + +typedef bool ( * MatchFunction_t )( const IotLink_t * const pOperationLink, + void * pCompare ); + +static unsigned int flagSemaphore; + +/** + * We constrain the return values of these functions because they + * are checked by assertions in the MQTT code. + */ +IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJobStorage_t * const pJobStorage, + IotTaskPoolJob_t * const pJob ) +{ + assert( userCallback != NULL ); + assert( pJobStorage != NULL ); + assert( pJob != NULL ); + + /* _IotMqtt_ScheduleOperation asserts this. */ + return IOT_TASKPOOL_SUCCESS; +} + +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + uint32_t timeMs ) +{ + IotTaskPoolError_t error; + + /* _IotMqtt_ScheduleOperation asserts this. */ + __CPROVER_assume( error != IOT_TASKPOOL_BAD_PARAMETER && + error != IOT_TASKPOOL_ILLEGAL_OPERATION ); + return error; +} + +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + IotTaskPoolJobStatus_t * const pStatus ) +{ + if( ( taskPool == NULL ) || ( job == NULL ) ) + { + return IOT_TASKPOOL_BAD_PARAMETER; + } + + return IOT_TASKPOOL_SUCCESS; +} + +/** + * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives + * a volatile variable as input. Thus, CBMC will always consider that + * Atomic_Add_u32 will operate over nondetermistic values and raise + * a unsigned integer overflow failure. However, developers have reported + * that the use of this overflow is part of the function implementation. + * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid + * spurious alarms, we stub out this function to always + * return a nondetermistic odd value. + */ +uint16_t _IotMqtt_NextPacketIdentifier( void ) +{ + uint16_t id; + + /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + __CPROVER_assume( id % 2 == 1 ); + + return id; +} + +/** + * We abstract this function for performance reasons. In its original + * implementation, CBMC ended up creating byte extracts for all possible + * objects, due to the polymorphic nature of the linked list. We assume + * that the function is memory safe, and we free and havoc the list to + * demonstrate that no subsequent code makes use of the values in the list. + */ +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + MatchFunction_t isMatch, + void * pMatch, + void ( *freeElement )( void * pData ), + size_t linkOffset ) +{ + free_IotMqttSubscriptionList( pList ); + allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); +} + + +/** + * We are abstracting the semaphores because we are doing sequential proof. + * But the semaphore API assures us that TimedWait called after Post will + * never fail. Our abstraction of the semaphores models this behavior. + */ +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) +{ + assert( pSemaphore != NULL ); + flagSemaphore++; +} + +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, + uint32_t timeoutMs ) +{ + assert( pSemaphore != NULL ); + + if( flagSemaphore > 0 ) + { + flagSemaphore--; + return true; + } + + return false; +} + +void harness() +{ + /* Assume a valid MQTT connection. */ + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); + + __CPROVER_assume( mqttConnection != NULL ); + __CPROVER_assume( mqttConnection->pNetworkInterface != NULL ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); + ensure_IotMqttConnection_has_lists( mqttConnection ); + __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); + + /* Assume a valid publish info. */ + IotMqttPublishInfo_t * pPublishInfo = allocate_IotMqttPublishInfo( NULL ); + __CPROVER_assume( IMPLIES( pPublishInfo != NULL, valid_IotMqttPublishInfo( pPublishInfo ) ) ); + + /* Assume unconstrained inputs. */ + uint32_t flags; + uint32_t timeoutMs; + + /* Initialize semaphore flag. */ + flagSemaphore = 0; + + /* Function under verification. */ + IotMqtt_PublishSync( mqttConnection, /* Always assume a valid connection. */ + pPublishInfo, + flags, + timeoutMs ); +} diff --git a/cbmc/proofs/IotMqtt_PublishSync/Makefile b/cbmc/proofs/IotMqtt_PublishSync/Makefile new file mode 100644 index 0000000000..db360e48d7 --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishSync/Makefile @@ -0,0 +1,60 @@ +ENTRY=IotMqtt_PublishSync + +# We abstract all the log and concurrency related functions in this proof +# and assume their implementation is correct +ABSTRACTIONS += --remove-function-body IotLog_Generic + +# We abstract this function because manual inspection demonstrates that it is unreachable +ABSTRACTIONS += --remove-function-body _IotMqtt_RemoveSubscriptionByPacket + +# We also abstract unreachable functions to improve coverage metrics +ABSTRACTIONS += --remove-function-body _IotMqtt_ProcessIncomingPublish +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +# One more than actual number of subscriptions +SUBSCRIPTION_COUNT_MAX=4 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# One more than actual number of operations +OPERATION_COUNT_MAX=4 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# One more than actual length of topics +TOPIC_LENGTH_MAX=6 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +# One more than actual length of payload +PAYLOAD_LENGTH_MAX=6 +DEF += -DPAYLOAD_LENGTH_MAX=$(PAYLOAD_LENGTH_MAX) + +# Enables CBMC stub for RemoveAllMatches function +DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 + +# Allow MQTT allocations to fail for coverage +DEF += -include mqtt_state.h +DEF += -DIotMqtt_MallocMessage=malloc_can_fail +DEF += -DIotMqtt_MallocOperation=malloc_can_fail + +LOOP += _encodeRemainingLength.0:$(PAYLOAD_LENGTH_MAX) +LOOP += _IotMqtt_InvokeSubscriptionCallback.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _matchWildcards.0:$(TOPIC_LENGTH_MAX) +LOOP += _topicFilterMatch.0:$(TOPIC_LENGTH_MAX) +LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_RemoveAllMatches$$link2.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml new file mode 100644 index 0000000000..8fe378dfcd --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml @@ -0,0 +1,4 @@ +cbmcflags: "--object-bits;8;--unwind;1;--unwindset;_encodeRemainingLength.0:6,_IotMqtt_InvokeSubscriptionCallback.0:4,_matchWildcards.0:6,_topicFilterMatch.0:6,IotListDouble_RemoveAllMatches.0:4,IotListDouble_RemoveAllMatches.0:4,valid_IotMqttOperationList.0:4,valid_IotMqttSubscriptionList.0:4;--bounds-check;--conversion-check;--div-by-zero-check;--float-overflow-check;--flush;--nan-check;--nondet-static;--pointer-check;--pointer-overflow-check;--signed-overflow-check;--undefined-shift-check;--unsigned-overflow-check;--unwinding-assertions" +expected: SUCCESSFUL +goto: IotMqtt_PublishSync.goto +jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json new file mode 100644 index 0000000000..501be64da3 --- /dev/null +++ b/cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json @@ -0,0 +1,16 @@ +{ "expected-missing-functions": + [ + "IotClock_GetTimeMs", + "IotLog_Generic", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_strerror", + "_IotMqtt_RemoveSubscriptionByPacket" + ], + "proof-name": "IotMqtt_PublishSync", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c index 3de379122f..83f655ba28 100644 --- a/cbmc/proofs/mqtt_state.c +++ b/cbmc/proofs/mqtt_state.c @@ -138,38 +138,43 @@ IotListDouble_t *allocate_IotMqttOperationList( IotListDouble_t *pOp, size_t length, IotMqttConnection_t pConn ) { - if ( pOp == NULL ) pOp = malloc_can_fail( sizeof( *pOp ) ); - if ( pOp == NULL ) return NULL; - // Allocate lists of length L <= 3 (MAX = L+1) - __CPROVER_assert(OPERATION_COUNT_MAX <= 3+1, - "Operation list bound is too big"); __CPROVER_assert(length < OPERATION_COUNT_MAX, - "Operation list requested is too long"); + "Operation list length max is greater than MAX"); + __CPROVER_assert(length <= 3, + "Operation list length max is greater than 3"); + + if ( pOp == NULL ) pOp = malloc_can_fail( sizeof( *pOp ) ); + if ( pOp == NULL ) return NULL; IotListDouble_Create( pOp ); - size_t num_elts; - __CPROVER_assume(num_elts <= length); + size_t numElts; + __CPROVER_assume(numElts <= length); - if (1 <= num_elts) - { - IotMqttOperation_t pElt = allocate_IotMqttOperation( NULL, pConn ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pOp, &( pElt->link ) ); - } - if (2 <= num_elts) - { - IotMqttOperation_t pElt = allocate_IotMqttOperation( NULL, pConn ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pOp, &( pElt->link ) ); - } - if (3 <= num_elts) - { - IotMqttOperation_t pElt = allocate_IotMqttOperation( NULL, pConn ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pOp, &( pElt->link ) ); - } + IotMqttOperation_t pElt; + switch (numElts) { +#if 3 < OPERATION_COUNT_MAX + case 3: + pElt = allocate_IotMqttOperation( NULL, pConn ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pOp, &( pElt->link ) ); +#endif +#if 2 < OPERATION_COUNT_MAX + case 2: + pElt = allocate_IotMqttOperation( NULL, pConn ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pOp, &( pElt->link ) ); +#endif +#if 1 < OPERATION_COUNT_MAX + case 1: + pElt = allocate_IotMqttOperation( NULL, pConn ); + __CPROVER_assume( pElt ); + IotListDouble_InsertHead( pOp, &( pElt->link ) ); +#endif + default: + ; + } return pOp; } @@ -179,6 +184,8 @@ bool valid_IotMqttOperationList( const IotListDouble_t *pOp, { if ( pOp == NULL ) return false; + // TODO: Consider replacing loop with straight line code + IotListDouble_t *pLink; IotContainers_ForEach( pOp, pLink ) { IotMqttOperation_t *pElt = IotLink_Container( struct _mqttOperation, pLink, link ); @@ -206,7 +213,7 @@ IotMqttConnection_t allocate_IotMqttConnection( IotMqttConnection_t pConn ) return pConn; } -void ensure_IotMqttConnection_has_lists( IotMqttConnection_t pConn ) +IotMqttConnection_t ensure_IotMqttConnection_has_lists( IotMqttConnection_t pConn ) { allocate_IotMqttOperationList( &pConn->pendingProcessing, OPERATION_COUNT_MAX - 1, @@ -331,7 +338,7 @@ IotMqttSubscription_t *allocate_IotMqttSubscription( IotMqttSubscription_t *pSub bool valid_IotMqttSubscription( const IotMqttSubscription_t *pSub ) { return - pSub && + pSub != NULL && VALID_STRING( pSub->pTopicFilter, pSub->topicFilterLength ) && VALID_CBMC_SIZE( pSub->topicFilterLength ) @@ -350,21 +357,42 @@ bool valid_IotMqttSubscription( const IotMqttSubscription_t *pSub ) IotMqttSubscription_t *allocate_IotMqttSubscriptionArray( IotMqttSubscription_t *pSub, size_t length ) { + // Allocate lists of length L <= 3 (MAX = L+1) + __CPROVER_assert(length < SUBSCRIPTION_COUNT_MAX, + "Subscription array length max is greater than MAX"); + __CPROVER_assert(length <= 3, + "Subscription array length max is greater than 3"); + if ( pSub == NULL ) pSub = malloc_can_fail( length * sizeof( *pSub ) ); if ( pSub == NULL ) return NULL; - for ( size_t i = 0; i < length; i++ ) - allocate_IotMqttSubscription( pSub + i ); + switch (length) { +#if 3 < SUBSCRIPTION_COUNT_MAX + case 3: + allocate_IotMqttSubscription( pSub + 2 ); +#endif +#if 2 < SUBSCRIPTION_COUNT_MAX + case 2: + allocate_IotMqttSubscription( pSub + 1 ); +#endif +#if 1 < SUBSCRIPTION_COUNT_MAX + case 1: + allocate_IotMqttSubscription( pSub + 0 ); +#endif + default: + ; + } return pSub; } - bool valid_IotMqttSubscriptionArray( const IotMqttSubscription_t *pSub, const size_t length ) { if ( !IFF( pSub == NULL, length == 0 ) ) return false; if ( pSub == NULL ) return false; + // TODO: Consider replacing loop with straight line code + for ( size_t i = 0; i < length; i++ ) if ( !valid_IotMqttSubscription( pSub + i ) ) return false; return @@ -390,6 +418,7 @@ _mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *p pElt->link.pPrevious = NULL; pElt->link.pNext = NULL; pElt->topicFilterLength = length; + // pElt->topicFilter is a flexible struct member following pElt return pElt; } @@ -402,9 +431,7 @@ bool valid_IotMqttSubscriptionListElt( const _mqttSubscription_t *pElt ) // MAX is one greater than the maximum length pElt->topicFilterLength < TOPIC_LENGTH_MAX && #endif - pElt->topicFilterLength < CBMC_MAX_OBJECT_SIZE - sizeof( *pElt ) && - __CPROVER_r_ok( pElt->pTopicFilter, pElt->topicFilterLength ) && - __CPROVER_w_ok( pElt->pTopicFilter, pElt->topicFilterLength ); + pElt->topicFilterLength < CBMC_MAX_OBJECT_SIZE - sizeof( *pElt ); } // Subscription linked lists @@ -412,47 +439,53 @@ bool valid_IotMqttSubscriptionListElt( const _mqttSubscription_t *pElt ) IotListDouble_t *allocate_IotMqttSubscriptionList( IotListDouble_t *pSub, size_t length ) { - if ( pSub == NULL ) pSub = malloc_can_fail( sizeof( *pSub ) ); - if ( pSub == NULL ) return NULL; - // Allocate lists of length L <= 3 (MAX = L+1) - __CPROVER_assert(SUBSCRIPTION_COUNT_MAX <= 3+1, - "Subscription list bound is too big"); __CPROVER_assert(length < SUBSCRIPTION_COUNT_MAX, - "Subscription list requested is too long"); + "Subscription list length max is greater than MAX"); + __CPROVER_assert(length <= 3, + "Subscription list length max is greater than 3"); + + if ( pSub == NULL ) pSub = malloc_can_fail( sizeof( *pSub ) ); + if ( pSub == NULL ) return NULL; IotListDouble_Create( pSub ); - size_t num_elts; - __CPROVER_assume(num_elts <= length); + size_t numElts; + __CPROVER_assume(numElts <= length); - if (1 <= num_elts) - { - _mqttSubscription_t *pElt = allocate_IotMqttSubscriptionListElt( NULL ); + _mqttSubscription_t *pElt; + switch (numElts) { +#if 3 < SUBSCRIPTION_COUNT_MAX + case 3: + pElt = allocate_IotMqttSubscriptionListElt( NULL ); __CPROVER_assume( pElt ); IotListDouble_InsertHead( pSub, &( pElt->link ) ); - } - if (2 <= num_elts) - { - _mqttSubscription_t *pElt = allocate_IotMqttSubscriptionListElt( NULL ); +#endif +#if 2 < SUBSCRIPTION_COUNT_MAX + case 2: + pElt = allocate_IotMqttSubscriptionListElt( NULL ); __CPROVER_assume( pElt ); IotListDouble_InsertHead( pSub, &( pElt->link ) ); - } - if (3 <= num_elts) - { - _mqttSubscription_t *pElt = allocate_IotMqttSubscriptionListElt( NULL ); +#endif +#if 1 < SUBSCRIPTION_COUNT_MAX + case 1: + pElt = allocate_IotMqttSubscriptionListElt( NULL ); __CPROVER_assume( pElt ); IotListDouble_InsertHead( pSub, &( pElt->link ) ); - } +#endif + default: + ; + } return pSub; } - bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, const size_t length ) { if ( pSub == NULL ) return false; + // TODO: Consider replacing loop with straight line code + IotListDouble_t *pLink; IotContainers_ForEach( pSub, pLink ) { _mqttSubscription_t @@ -465,6 +498,49 @@ bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, length < SUBSCRIPTION_COUNT_MAX; } +void *invalid_pointer() +{ + void *ptr; + return ptr; +} + +void free_IotMqttSubscriptionList( IotListDouble_t *pSub ) +{ + IotListDouble_t *pThis; + IotListDouble_t *pNext; + + if ( pSub == NULL ) return; + pThis = pSub->pNext; + pSub->pNext = invalid_pointer(); + pSub->pPrevious = invalid_pointer(); + +#if 3 < SUBSCRIPTION_COUNT_MAX + if ( pThis == pSub ) return; + pNext = pThis->pNext; + free( IotLink_Container( _mqttSubscription_t, pThis, link ) ); + pThis = pNext; +#endif + +#if 2 < SUBSCRIPTION_COUNT_MAX + if ( pThis == pSub ) return; + pNext = pThis->pNext; + free( IotLink_Container( _mqttSubscription_t, pThis, link ) ); + pThis = pNext; +#endif + +#if 1 < SUBSCRIPTION_COUNT_MAX + if ( pThis == pSub ) return; + pNext = pThis->pNext; + free( IotLink_Container( _mqttSubscription_t, pThis, link ) ); + pThis = pNext; +#endif + + /* The being freed could be longer than SUBSCRIPTION_COUNT_MAX: + * Allocate a list of maximal length, then add one subscription, then free it. */ + return; +} + + /**************************************************************** * IotMqttPublishInfo ****************************************************************/ @@ -494,6 +570,10 @@ bool valid_IotMqttPublishInfo( const IotMqttPublishInfo_t *pInfo ) VALID_STRING( pInfo->pPayload, pInfo->payloadLength ) && VALID_CBMC_SIZE( pInfo->payloadLength ) && +#ifdef PAYLOAD_LENGTH_MAX + // Some proofs iterate over it + pInfo->payloadLength < PAYLOAD_LENGTH_MAX && +#endif pInfo->retryMs <= IOT_MQTT_RETRY_MS_CEILING && pInfo->retryMs > 0 && diff --git a/cbmc/proofs/mqtt_state.h b/cbmc/proofs/mqtt_state.h index caa81d8272..237b8cb31b 100644 --- a/cbmc/proofs/mqtt_state.h +++ b/cbmc/proofs/mqtt_state.h @@ -1,3 +1,6 @@ +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + #include /**************************************************************** @@ -151,6 +154,7 @@ IotListDouble_t *allocate_IotMqttSubscriptionList( IotListDouble_t *pSub, size_t length ); bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, const size_t length ); +void free_IotMqttSubscriptionList( IotListDouble_t *pSub ); /**************************************************************** * IotMqttPublishInfo From ec06217fbb4ac4d5e615543169b5f0f59d9dfb79 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 25 Mar 2020 17:30:32 -0700 Subject: [PATCH 447/844] Serializer/fix test and add build job (#838) * Fix a failing serializer decoder test * Attempt to add a serializer tests job to TravisCI --- .travis.yml | 1 + .../test/unit/iot_tests_serializer_cbor.c | 3 +-- scripts/ci_test_serializer.sh | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 scripts/ci_test_serializer.sh diff --git a/.travis.yml b/.travis.yml index 58c4989055..9dcd5764d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ jobs: # Library tests. - stage: Tests env: RUN_TEST=common + env: RUN_TEST=serializer - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - env: RUN_TEST=mqtt NETWORK_STACK=openssl - env: RUN_TEST=shadow diff --git a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c index cea483a5e9..2544441560 100644 --- a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c +++ b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c @@ -678,10 +678,9 @@ TEST( Serializer_Decoder_Unit_CBOR, TestDecoderObjectReuseAfterIteration ) TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator2, &mapDecoder ) ); /* End the second round of iteration */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator1, &mapDecoder ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator2, &mapDecoder ) ); _pCborDecoder->destroy( &valueDecoder ); _pCborDecoder->destroy( &mapDecoder ); diff --git a/scripts/ci_test_serializer.sh b/scripts/ci_test_serializer.sh new file mode 100644 index 0000000000..28c2f3bc42 --- /dev/null +++ b/scripts/ci_test_serializer.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# Travis CI uses this script to test the serializer library. + +# Exit on any nonzero return code. +set -e + +# CMake compiler flags for building serializer libraries. +CMAKE_FLAGS="$COMPILER_OPTIONS" + +# Build executables. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +make -j2 iot_tests_serializer + +# Run serializer tests. +./output/bin/iot_tests_serializer + +# Don't reconfigure CMake if script is invoked for coverage build. +if [ "$RUN_TEST" != "coverage" ]; then + # Rebuild in static memory mode. + cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" + make -j2 iot_tests_serializer + + # Run serializer tests in static memory mode. + ./output/bin/iot_tests_serializer +fi \ No newline at end of file From 4c649ef901811da09c1b3ded38d3627f4212078d Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 25 Mar 2020 18:35:21 -0700 Subject: [PATCH 448/844] Add constness to input params of serializer interface (#839) --- .../standard/serializer/include/iot_serializer.h | 16 ++++++++++++---- .../src/cbor/iot_serializer_tinycbor_decoder.c | 16 ++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/libraries/standard/serializer/include/iot_serializer.h b/libraries/standard/serializer/include/iot_serializer.h index 4a3588f3f6..b3f4be1e38 100644 --- a/libraries/standard/serializer/include/iot_serializer.h +++ b/libraries/standard/serializer/include/iot_serializer.h @@ -19,6 +19,7 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + /* * This library is to provide a consistent layer for serializing JSON-like format. * The implementations can be CBOR or JSON. @@ -410,6 +411,7 @@ typedef struct IotSerializerDecodeInterface * @param pDecoderObject Pointer to the decoder object allocated by user. * @param pDataBuffer Pointer to the buffer containing data to be decoded. * @param maxSize Maximum length of the buffer containing data to be decoded. + * * @return IOT_SERIALIZER_SUCCESS if successful */ IotSerializerError_t ( * init )( IotSerializerDecoderObject_t * pDecoderObject, @@ -429,9 +431,10 @@ typedef struct IotSerializerDecodeInterface * * @param[in] pDecoderObject Pointer to the decoder object representing the container * @param[out] pIterator Pointer to iterator which can be used for the traversing the container by calling next() + * * @return IOT_SERIALIZER_SUCCESS if successful */ - IotSerializerError_t ( * stepIn )( IotSerializerDecoderObject_t * pDecoderObject, + IotSerializerError_t ( * stepIn )( const IotSerializerDecoderObject_t * pDecoderObject, IotSerializerDecoderIterator_t * pIterator ); @@ -439,6 +442,7 @@ typedef struct IotSerializerDecodeInterface * @brief Gets the object value currently pointed to by an iterator. * @param iterator The iterator handle * @param pValueObject Value of the object pointed to by the iterator. + * * @return IOT_SERIALIZER_SUCCESS if successful */ IotSerializerError_t ( * get )( IotSerializerDecoderIterator_t iterator, @@ -452,9 +456,10 @@ typedef struct IotSerializerDecodeInterface * @param[in] pDecoderObject Pointer to the decoder object representing container * @param[in] pKey Pointer to the key for which value needs to be found. * @param[out] pValueObject Pointer to the value object for the key. + * * @return IOT_SERIALIZER_SUCCESS if successful */ - IotSerializerError_t ( * find )( IotSerializerDecoderObject_t * pDecoderObject, + IotSerializerError_t ( * find )( const IotSerializerDecoderObject_t * pDecoderObject, const char * pKey, IotSerializerDecoderObject_t * pValueObject ); @@ -470,6 +475,7 @@ typedef struct IotSerializerDecodeInterface * If the container is an array, it skips by one array element. * * @param[in] iterator Pointer to iterator + * * @return IOT_SERIALIZER_SUCCESS if successful */ IotSerializerError_t ( * next )( IotSerializerDecoderIterator_t iterator ); @@ -485,14 +491,16 @@ typedef struct IotSerializerDecodeInterface * @brief Function to obtain the starting buffer address of the raw encoded data (scalar or container type) * represented by the passed decoder object. * Container SHOULD be of type array or map. + * * @param[in] pDecoderObject The decoder object whose underlying data's starting location in the buffer * is to be returned. * @param[out] pEncodedDataStartAddr This will be populated with the starting location of the encoded object * in the data buffer. + * * @return #IOT_SERIALIZER_SUCCESS if successful; otherwise #IOT_SERIALIZER_NOT_SUPPORTED * for a non-container type iterator. */ - IotSerializerError_t ( * getBufferAddress )( IotSerializerDecoderObject_t * + IotSerializerError_t ( * getBufferAddress )( const IotSerializerDecoderObject_t * pDecoderObject, const uint8_t ** pEncodedDataStartAddr ); @@ -508,7 +516,7 @@ typedef struct IotSerializerDecodeInterface * @return #IOT_SERIALIZER_SUCCESS if successful; otherwise #IOT_SERIALIZER_NOT_SUPPORTED * for a non-container type iterator. */ - IotSerializerError_t ( * getSizeOfEncodedData )( IotSerializerDecoderObject_t * pDecoderObject, + IotSerializerError_t ( * getSizeOfEncodedData )( const IotSerializerDecoderObject_t * pDecoderObject, size_t * pEncodedDataLength ); diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c index 12ba2848b9..4349829d1a 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c @@ -48,10 +48,10 @@ static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject static IotSerializerError_t _get( IotSerializerDecoderIterator_t iterator, IotSerializerDecoderObject_t * pValueObject ); -static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _find( const IotSerializerDecoderObject_t * pDecoderObject, const char * pKey, IotSerializerDecoderObject_t * pValueObject ); -static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _stepIn( const IotSerializerDecoderObject_t * pDecoderObject, IotSerializerDecoderIterator_t * pIterator ); static IotSerializerError_t _stepOut( IotSerializerDecoderIterator_t iterator, IotSerializerDecoderObject_t * pDecoderObject ); @@ -62,12 +62,12 @@ static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ); static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ); -static IotSerializerError_t _getBufferAddress( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _getBufferAddress( const IotSerializerDecoderObject_t * pDecoderObject, const uint8_t ** pEncodedDataStartAddr ); -static IotSerializerError_t _getSizeOfEncodedData( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _getSizeOfEncodedData( const IotSerializerDecoderObject_t * pDecoderObject, size_t * pEncodedDataLength ); @@ -404,7 +404,7 @@ static IotSerializerError_t _get( IotSerializerDecoderIterator_t iterator, /*-----------------------------------------------------------*/ -static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _find( const IotSerializerDecoderObject_t * pDecoderObject, const char * pKey, IotSerializerDecoderObject_t * pValueObject ) { @@ -451,7 +451,7 @@ static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject /*-----------------------------------------------------------*/ -static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _stepIn( const IotSerializerDecoderObject_t * pDecoderObject, IotSerializerDecoderIterator_t * pIterator ) { IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; @@ -548,7 +548,7 @@ static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ) } /*-----------------------------------------------------------*/ -static IotSerializerError_t _getBufferAddress( IotSerializerDecoderObject_t * pDecoderObject, +static IotSerializerError_t _getBufferAddress( const IotSerializerDecoderObject_t * pDecoderObject, const uint8_t ** pEncodedDataStartAddr ) { IotSerializer_Assert( pDecoderObject != NULL ); @@ -570,7 +570,7 @@ static IotSerializerError_t _getBufferAddress( IotSerializerDecoderObject_t * pD /*-----------------------------------------------------------*/ -IotSerializerError_t _getSizeOfEncodedData( IotSerializerDecoderObject_t * pDecoderObject, +IotSerializerError_t _getSizeOfEncodedData( const IotSerializerDecoderObject_t * pDecoderObject, size_t * pEncodedDataLength ) { IotSerializer_Assert( pDecoderObject != NULL ); From 2664d2d853e41e4d0bb9e1c5d1e1d02306e8ea48 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Mon, 30 Mar 2020 10:01:52 -0400 Subject: [PATCH 449/844] Add a proof harness for IotMqtt_SubscribeAsync function (#827) * Adds a proof harness for IotMqtt_SubscribeAsync function Signed-off-by: Felipe R. Monteiro --- .../proofs/IotMqtt_DeserializeSuback/Makefile | 4 +- .../IotMqtt_SubscribeAsync_harness.c | 144 ++++++++++++++++++ cbmc/proofs/IotMqtt_SubscribeAsync/Makefile | 71 +++++++++ .../IotMqtt_SubscribeAsync/cbmc-batch.yaml | 8 + .../IotMqtt_SubscribeAsync/cbmc-viewer.json | 18 +++ cbmc/proofs/Makefile.common | 14 +- 6 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c create mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/Makefile create mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile index 03368c8070..84bf676658 100644 --- a/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile +++ b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile @@ -21,9 +21,9 @@ DEF += -DBUFFER_SIZE=$(BUFFER_SIZE) # proof and assume their implementation is correct ABSTRACTIONS += --remove-function-body IotLog_Generic -# These defines have the effect of removing RemoveAllMatches from the +# This define has the effect of removing RemoveAllMatches from the # linear containers header so that our stub will be used. -DEF += -DCBMC=1 -DCBMC_PROOF_DESERIALIZE_SUBACK=1 +DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 ################################################################ # Loop unwinding diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c b/cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c new file mode 100644 index 0000000000..85f53b2356 --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c @@ -0,0 +1,144 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file IotMqtt_SubscribeAsync_harness.c + * @brief Implements the proof harness for IotMqtt_SubscribeAsync function. + */ + +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include + +#include "mqtt_state.h" + +typedef bool ( * MatchFunction_t )( const IotLink_t * const pOperationLink, + void * pCompare ); + +typedef void ( * FreeElementFunction_t )( void * pData ); + +/** + * We constrain the return values of these functions because + * they are checked by assertions in the MQTT code. + */ +IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJobStorage_t * const pJobStorage, + IotTaskPoolJob_t * const pJob ) +{ + assert( userCallback != NULL ); + assert( pJobStorage != NULL ); + assert( pJob != NULL ); + + /* _IotMqtt_ScheduleOperation asserts this. */ + return IOT_TASKPOOL_SUCCESS; +} + +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + uint32_t timeMs ) +{ + IotTaskPoolError_t error; + + /* _IotMqtt_ScheduleOperation asserts this. */ + __CPROVER_assume( error != IOT_TASKPOOL_BAD_PARAMETER && + error != IOT_TASKPOOL_ILLEGAL_OPERATION ); + return error; +} + +/** + * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives + * a volatile variable as input. Thus, CBMC will always consider that + * Atomic_Add_u32 will operate over nondetermistic values and raise + * an unsigned integer overflow failure. However, developers have reported + * that the use of this overflow is part of the function implementation. + * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid + * spurious alarms, we stub out this function to always + * return a nondetermistic odd value. + */ +uint16_t _IotMqtt_NextPacketIdentifier( void ) +{ + uint16_t id; + + /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + __CPROVER_assume( id & 0x0001 == 1 ); + + return id; +} + +/** + * We abstract these functions for performance reasons. In their original + * implementation, CBMC ended up creating byte extracts for all possible + * objects, due to the polymorphic nature of the linked list. We assume + * that the functions are memory safe, and we free and havoc the list to + * demonstrate that no subsequent code makes use of the values in the list. + */ +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + MatchFunction_t isMatch, + void * pMatch, + FreeElementFunction_t freeElement, + size_t linkOffset ) +{ + free_IotMqttSubscriptionList( pList ); + allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); +} + +void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ) +{ + free_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ) ); + allocate_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ), subscriptionCount ); +} + +void harness() +{ + /* Assume a valid MQTT connection. */ + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); + + __CPROVER_assume( mqttConnection != NULL ); + __CPROVER_assume( mqttConnection->pNetworkInterface != NULL ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); + ensure_IotMqttConnection_has_lists( mqttConnection ); + __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); + + /* Assume unconstrained inputs. */ + size_t subscriptionCount; + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + IotMqttSubscription_t * pSubscriptionList = allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); + __CPROVER_assume( valid_IotMqttSubscriptionArray( pSubscriptionList, subscriptionCount ) ); + uint32_t flags; + IotMqttCallbackInfo_t * pCallbackInfo = malloc_can_fail( sizeof( *pCallbackInfo ) ); + + /* Output. */ + IotMqttOperation_t pSubscribeOperation = allocate_IotMqttOperation( NULL, mqttConnection ); + + /* Function under verification. */ + IotMqttError_t status = IotMqtt_SubscribeAsync( mqttConnection, /* Always assume a valid connection. */ + pSubscriptionList, /* Always assume a valid subscription list. */ + subscriptionCount, + flags, + pCallbackInfo, + pSubscribeOperation ); +} diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/Makefile b/cbmc/proofs/IotMqtt_SubscribeAsync/Makefile new file mode 100644 index 0000000000..52ca919a42 --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeAsync/Makefile @@ -0,0 +1,71 @@ +ENTRY=IotMqtt_SubscribeAsync + +# We abstract this function because manual inspection demonstrates that it is unreachable +ABSTRACTIONS += --remove-function-body _checkRetryLimit +ABSTRACTIONS += --remove-function-body _scheduleNextRetry + +# We abstract all unreachable functions to improve coverage metrics +ABSTRACTIONS += --remove-function-body _IotMqtt_PublishSetDup +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribeCommon +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribe +ABSTRACTIONS += --remove-function-body _mqttOperation_match +ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe +ABSTRACTIONS += --remove-function-body _packetMatch +ABSTRACTIONS += --remove-function-body _topicFilterMatch +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +# One more than actual number of subscriptions in a subscription list +SUBSCRIPTION_COUNT_MAX=2 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# One more than actual number of operations in an operation list +OPERATION_COUNT_MAX=2 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# One more than actual length of topics +TOPIC_LENGTH_MAX=6 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +# Should be 2*SUBSCRIPTION_COUNT_MAX-1 +SUBSCRIPTION_LIST_MAX=3 +DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) + +# Enables CBMC stub for RemoveAllMatches function +DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 + +# Allow MQTT allocations to fail for coverage +DEF += -include mqtt_state.h +DEF += -DIotMqtt_MallocMessage=malloc_can_fail +DEF += -DIotMqtt_MallocOperation=malloc_can_fail +DEF += -DIotMqtt_MallocSubscription=malloc_can_fail + +LOOP += _IotMqtt_AddSubscriptions.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _IotMqtt_SerializeSubscribeCommon.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) +LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += allocate_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) +LOOP += valid_IotMqttOperationList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml new file mode 100644 index 0000000000..16b101ea1f --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml @@ -0,0 +1,8 @@ +build_memory: 32000 +cbmcflags: "--object-bits;8;--unwind;1;--unwindset;_IotMqtt_AddSubscriptions.0:3,_IotMqtt_SerializeSubscribeCommon.0:3,_IotMqtt_SubscriptionPacketSize.0:3,_IotMqtt_ValidateSubscriptionList.0:3,_validateSubscription.0:6,allocate_IotMqttSubscriptionArray.0:2,allocate_IotMqttSubscriptionList.0:2,IotListDouble_FindFirstMatch.0:3,IotListDouble_FindFirstMatch$link1.0:2,IotListDouble_RemoveAllMatches.0:3,IotListDouble_RemoveAllMatches$link1.0:3,strncmp.0:6,valid_IotMqttOperationList.0:2,valid_IotMqttSubscriptionArray.0:2,valid_IotMqttSubscriptionList.0:2;--object-bits;8;--bounds-check;--conversion-check;--div-by-zero-check;--float-overflow-check;--flush;--nan-check;--nondet-static;--pointer-check;--pointer-overflow-check;--signed-overflow-check;--undefined-shift-check;--unsigned-overflow-check;--unwinding-assertions" +coverage_memory: 32000 +expected: SUCCESSFUL +goto: IotMqtt_SubscribeAsync.goto +jobos: ubuntu16 +property_memory: 32000 +report_memory: 32000 diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json new file mode 100644 index 0000000000..dff93223e3 --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json @@ -0,0 +1,18 @@ +{ "expected-missing-functions": + [ + "IotClock_GetTimeMs", + "IotLog_Generic", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_strerror", + "_checkRetryLimit", + "_scheduleNextRetry" + ], + "proof-name": "IotMqtt_SubscribeAsync", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index b217481472..dcf8c1ebe1 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -74,6 +74,7 @@ CBMC_MAX_OBJECT_SIZE = "(SIZE_MAX>>(CBMC_OBJECT_BITS+1))" CBMCFLAGS += \ $(UNWINDING) \ + --object-bits $(CBMC_OBJECT_BITS) \ --bounds-check \ --conversion-check \ --div-by-zero-check \ @@ -140,8 +141,7 @@ define encode_options '=$(shell echo $(1) | sed 's/ ,/ /g' | sed 's/ /;/g')=' endef -PROPMEM ?= 64000 -COVMEM ?= 64000 +PROOFMEM ?= 32000 CBMCPKG ?= cbmc BATCHPKG ?= cbmc-batch VIEWERPKG ?= cbmc-viewer @@ -178,8 +178,8 @@ $(ENTRY).yaml: $(ENTRY).goto Makefile echo 'goto: $(ENTRY).goto' >> $@ echo 'build: false' >> $@ echo 'cbmcflags: $(call yaml_encode_options,$(CBMCFLAGS))' >> $@ - echo 'property_memory: $(PROPMEM)' >> $@ - echo 'coverage_memory: $(COVMEM)' >> $@ + echo 'property_memory: $(PROOFMEM)' >> $@ + echo 'coverage_memory: $(PROOFMEM)' >> $@ echo 'expected: "SUCCESSFUL"' >> $@ launch: $(ENTRY).goto Makefile @@ -209,10 +209,14 @@ CBMC_FLAGS = '$(call encode_options,$(CBMCFLAGS))' cbmc-batch.yaml: Makefile ../Makefile.common @echo "Building $@" @$(RM) $@ - @echo 'cbmcflags: $(strip $(call yaml_encode_options,$(CBMCFLAGS)))' > $@ + @echo 'build_memory: $(PROOFMEM)' > $@ + @echo 'cbmcflags: $(strip $(call yaml_encode_options,$(CBMCFLAGS)))' >> $@ + @echo 'coverage_memory: $(PROOFMEM)' >> $@ @echo "expected: SUCCESSFUL" >> $@ @echo "goto: $(ENTRY).goto" >> $@ @echo "jobos: $(JOBOS)" >> $@ + @echo 'property_memory: $(PROOFMEM)' >> $@ + @echo 'report_memory: $(PROOFMEM)' >> $@ .PHONY: cbmc-batch.yaml From 1ab60be5ae9988b7df9bf478de15c085f1d01b5f Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Mon, 30 Mar 2020 17:01:36 -0400 Subject: [PATCH 450/844] Adds a proof harness for IotMqtt_SubscribeSync function (#837) * Adds a proof harness for IotMqtt_SubscribeSync function Signed-off-by: Felipe R. Monteiro --- .../IotMqtt_SubscribeSync_harness.c | 157 ++++++++++++++++++ cbmc/proofs/IotMqtt_SubscribeSync/Makefile | 77 +++++++++ .../IotMqtt_SubscribeSync/cbmc-batch.yaml | 2 + .../IotMqtt_SubscribeSync/cbmc-viewer.json | 19 +++ 4 files changed, 255 insertions(+) create mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c create mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/Makefile create mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c b/cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c new file mode 100644 index 0000000000..fdf89eb2fa --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c @@ -0,0 +1,157 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file IotMqtt_SubscribeSync_harness.c + * @brief Implements the proof harness for IotMqtt_SubscribeSync function. + */ + +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include + +#include "mqtt_state.h" + +typedef bool ( * MatchFunction_t )( const IotLink_t * const pOperationLink, + void * pCompare ); + +typedef void ( * FreeElementFunction_t )( void * pData ); + +static unsigned int flagSemaphore; + +/** + * We constrain the return values of this function because it + * is checked by assertions in the MQTT code. + */ +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + IotTaskPoolJobStatus_t * const pStatus ) +{ + if( ( taskPool == NULL ) || ( job == NULL ) ) + { + return IOT_TASKPOOL_BAD_PARAMETER; + } + + return IOT_TASKPOOL_SUCCESS; +} + +/** + * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives + * a volatile variable as input. Thus, CBMC will always consider that + * Atomic_Add_u32 will operate over nondetermistic values and raise + * an unsigned integer overflow failure. However, developers have reported + * that the use of this overflow is part of the function implementation. + * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid + * spurious alarms, we stub out this function to always + * return a nondetermistic odd value. + */ +uint16_t _IotMqtt_NextPacketIdentifier( void ) +{ + uint16_t id; + + /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + __CPROVER_assume( id & 0x0001 == 1 ); + + return id; +} + +/** + * We abstract these functions for performance reasons. In their original + * implementation, CBMC ended up creating byte extracts for all possible + * objects, due to the polymorphic nature of the linked list. We assume + * that these functions are memory safe, and we free and havoc the list to + * demonstrate that no subsequent code makes use of the values in the list. + */ +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + MatchFunction_t isMatch, + void * pMatch, + FreeElementFunction_t freeElement, + size_t linkOffset ) +{ + free_IotMqttSubscriptionList( pList ); + allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); +} + +void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount ) +{ + free_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ) ); + allocate_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ), subscriptionCount ); +} + +/** + * We are abstracting the semaphores because we are doing sequential proof. + * But the semaphore API assures us that TimedWait called after Post will + * never fail. Our abstraction of the semaphores models this behavior. + */ +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) +{ + assert( pSemaphore != NULL ); + flagSemaphore++; +} + +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, + uint32_t timeoutMs ) +{ + assert( pSemaphore != NULL ); + + if( flagSemaphore > 0 ) + { + flagSemaphore--; + return true; + } + + return false; +} + +void harness() +{ + /* Assume a valid MQTT connection. */ + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); + + __CPROVER_assume( mqttConnection != NULL ); + __CPROVER_assume( mqttConnection->pNetworkInterface != NULL ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); + ensure_IotMqttConnection_has_lists( mqttConnection ); + __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); + + /* Assume unconstrained inputs. */ + size_t subscriptionCount; + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + IotMqttSubscription_t * pSubscriptionList = allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); + __CPROVER_assume( valid_IotMqttSubscriptionArray( pSubscriptionList, subscriptionCount ) ); + uint32_t flags; + uint32_t timeoutMs; + + /* Initialize semaphore flag. */ + flagSemaphore = 0; + + /* Function under verification. */ + IotMqttError_t status = IotMqtt_SubscribeSync( mqttConnection, /* Always assume a valid connection. */ + pSubscriptionList, /* Always assume a valid subscription list. */ + subscriptionCount, + flags, + timeoutMs ); +} diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/Makefile b/cbmc/proofs/IotMqtt_SubscribeSync/Makefile new file mode 100644 index 0000000000..d70925fa0a --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeSync/Makefile @@ -0,0 +1,77 @@ +ENTRY=IotMqtt_SubscribeSync + +# We abstract these functions because manual inspection demonstrates they are unreachable +ABSTRACTIONS += --remove-function-body _checkRetryLimit +ABSTRACTIONS += --remove-function-body _scheduleCallback +ABSTRACTIONS += --remove-function-body _scheduleNextRetry + +# We abstract all unreachable functions to improve coverage metrics +ABSTRACTIONS += --remove-function-body _IotMqtt_ScheduleOperation +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribe +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribeCommon +ABSTRACTIONS += --remove-function-body _mqttOperation_match +ABSTRACTIONS += --remove-function-body _mqttOperation_tryDestroy +ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe +ABSTRACTIONS += --remove-function-body _mqttSubscription_tryDestroy +ABSTRACTIONS += --remove-function-body _packetMatch +ABSTRACTIONS += --remove-function-body _topicFilterMatch +ABSTRACTIONS += --remove-function-body IotMqtt_strerror +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +# One more than actual number of subscriptions in a subscription list +SUBSCRIPTION_COUNT_MAX=2 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# One more than actual number of operations in an operation list +OPERATION_COUNT_MAX=2 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# One more than actual length of topics +TOPIC_LENGTH_MAX=6 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +# Should be 2*SUBSCRIPTION_COUNT_MAX-1 +SUBSCRIPTION_LIST_MAX=3 +DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) + +# Enables CBMC stub for RemoveAllMatches function +DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 + +# These functions default to malloc. The CBMC model of malloc never fails, +# so we replace the CBMC model with a model that can fail to improve +# coverage and exercise more code +DEF += -include mqtt_state.h +DEF += -DIotMqtt_MallocMessage=malloc_can_fail +DEF += -DIotMqtt_MallocOperation=malloc_can_fail +DEF += -DIotMqtt_MallocSubscription=malloc_can_fail + +LOOP += _IotMqtt_AddSubscriptions.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _IotMqtt_SerializeSubscribeCommon.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) +LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += allocate_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) +LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json new file mode 100644 index 0000000000..f0873e2e28 --- /dev/null +++ b/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json @@ -0,0 +1,19 @@ +{ "expected-missing-functions": + [ + "IotClock_GetTimeMs", + "IotLog_Generic", + "IotMqtt_strerror", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotTaskPool_GetSystemTaskPool", + + "_checkRetryLimit", + "_scheduleCallback", + "_scheduleNextRetry" + ], + "proof-name": "IotMqtt_SubscribeSync", + "proof-root": "cbmc/proofs" +} From e4f60ab3ab05fcbe22062bd788c262ce81fd6599 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 30 Mar 2020 17:20:15 -0700 Subject: [PATCH 451/844] Fix clobbered words in the device defender library doxygen blurb. (#845) --- doc/lib/defender.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lib/defender.txt b/doc/lib/defender.txt index 4440d67b27..f3f3781248 100644 --- a/doc/lib/defender.txt +++ b/doc/lib/defender.txt @@ -3,7 +3,7 @@ @anchor defender @brief AWS IoT Device Defender library. -> AWS IoT Device Defender is a fully managed service that helps you secure your fleet of IoT devices. AWS IoT Device Defender continuously audits your IoT configurations to make sure that they aren’t deviating from security bestractices. A configuration is a set of technical controls you set to help keep information secure when devices are communicating with each other and the cloud. AWS IoT Device Defender makes it easy to maintain and enforce IoTonfigurations, such as ensuring device identity, authenticating and authorizing devices, and encrypting device data. AWS IoT Device Defender continuously audits the IoT configurations on your devices against a set of predefinedecurity best practices. AWS IoT Device Defender sends an alert if there are any gaps in your IoT configuration that might create a security risk, such as identity certificates being shared across multiple devices or a device with aevoked identity certificate trying to connect to AWS IoT Core. +> AWS IoT Device Defender is a fully managed service that helps you secure your fleet of IoT devices. AWS IoT Device Defender continuously audits your IoT configurations to make sure that they aren’t deviating from security best practices. A configuration is a set of technical controls you set to help keep information secure when devices are communicating with each other and the cloud. AWS IoT Device Defender makes it easy to maintain and enforce IoT configurations, such as ensuring device identity, authenticating and authorizing devices, and encrypting device data. AWS IoT Device Defender continuously audits the IoT configurations on your devices against a set of predefined security best practices. AWS IoT Device Defender sends an alert if there are any gaps in your IoT configuration that might create a security risk, such as identity certificates being shared across multiple devices or a device with a revoked identity certificate trying to connect to AWS IoT Core. Description of Device Defender from [AWS IoT documentation](https://aws.amazon.com/iot-device-defender/)
From 1e01ae158a96c2017bc595fe21cd41b9cba28480 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Tue, 31 Mar 2020 12:20:31 -0400 Subject: [PATCH 452/844] Add CBMC proofs for MQTT UnsubscribeAsync and UnsubscribeSync (#835) * Add CBMC proofs for MQTT UnsubscribeAsync and UnsubscribeSync * Add Async README and update Sync harness * Use only a single --unwindset in cbmc invocation * Revise Unsubscribe Sync and Async proofs following Felipe's review. * Fix prepare.py error message when patching fails. * Fix typos the UnsubscribeAsync README. * Fix bad italics in the Unsuscribe proof README.md files. Co-authored-by: Mark R Tuttle --- ...ary-code-for-CBMC-Unsubscribe-proofs.patch | 87 ++++++++++ .../IotMqtt_UnsubscribeAsync_harness.c | 157 +++++++++++++++++ cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile | 116 +++++++++++++ .../proofs/IotMqtt_UnsubscribeAsync/README.md | 113 ++++++++++++ .../IotMqtt_UnsubscribeAsync/cbmc-batch.yaml | 2 + .../IotMqtt_UnsubscribeAsync/cbmc-viewer.json | 20 +++ .../IotMqtt_UnsubscribeSync_harness.c | 161 ++++++++++++++++++ cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile | 106 ++++++++++++ cbmc/proofs/IotMqtt_UnsubscribeSync/README.md | 133 +++++++++++++++ .../IotMqtt_UnsubscribeSync/cbmc-batch.yaml | 2 + .../IotMqtt_UnsubscribeSync/cbmc-viewer.json | 19 +++ cbmc/proofs/prepare.py | 2 +- 12 files changed, 917 insertions(+), 1 deletion(-) create mode 100644 cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/README.md create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml create mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json diff --git a/cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch b/cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch new file mode 100644 index 0000000000..e652d56664 --- /dev/null +++ b/cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch @@ -0,0 +1,87 @@ +From 60ea7452b4097d9682b67e75d7b90bbc2092544e Mon Sep 17 00:00:00 2001 +From: Mark R Tuttle +Date: Mon, 30 Mar 2020 20:15:29 +0000 +Subject: [PATCH] Patch MQTT library code for CBMC Unsubscribe proofs + +--- + libraries/standard/common/include/iot_linear_containers.h | 5 +++++ + libraries/standard/mqtt/include/iot_mqtt_protocol.h | 2 ++ + libraries/standard/mqtt/src/iot_mqtt_operation.c | 10 ++++++++++ + libraries/standard/mqtt/src/private/iot_mqtt_internal.h | 2 ++ + 4 files changed, 19 insertions(+) + +diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h +index 689ea5e..841e4a0 100644 +--- a/libraries/standard/common/include/iot_linear_containers.h ++++ b/libraries/standard/common/include/iot_linear_containers.h +@@ -639,6 +639,11 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * + /* Iterate through the list to search for matches. */ + while( pCurrent != pList ) + { ++#ifdef CBMC_FINDFIRSTMATCH_MATCH_IS_NONNULL ++ __CPROVER_assert(isMatch != NULL, "FindFirstMatch called with match predicate"); ++ __CPROVER_assume(isMatch != NULL); ++#endif ++ + /* Call isMatch if provided. Otherwise, compare pointers. */ + if( isMatch != NULL ) + { +diff --git a/libraries/standard/mqtt/include/iot_mqtt_protocol.h b/libraries/standard/mqtt/include/iot_mqtt_protocol.h +index 0fedcea..ef9f8c0 100644 +--- a/libraries/standard/mqtt/include/iot_mqtt_protocol.h ++++ b/libraries/standard/mqtt/include/iot_mqtt_protocol.h +@@ -97,7 +97,9 @@ + * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT + * packet is this value. + */ ++#ifndef MQTT_MAX_REMAINING_LENGTH + #define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) ++#endif + + /** + * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. +diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c +index 5afce54..0e4d923 100644 +--- a/libraries/standard/mqtt/src/iot_mqtt_operation.c ++++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c +@@ -638,6 +638,10 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, + if( status == IOT_MQTT_SUCCESS ) + { + status = _initializeOperation( pMqttConnection, pOperation, flags, pCallbackInfo ); ++#ifdef CBMC_CREATEOPERATION_INITIALIZE_CANT_FAIL ++ __CPROVER_assert(status == IOT_MQTT_SUCCESS, ++ "An async _initializeOperation can't fail (unwaitable)"); ++#endif + } + + if( status == IOT_MQTT_SUCCESS ) +@@ -661,6 +665,12 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); + } + ++#ifdef CBMC_CREATEOPERATION_UNSUCCESSFUL_STATUS_MEANS_NULL_POINTER ++ __CPROVER_assert(pOperation == NULL, ++ "Bad status implies null pointer"); ++ __CPROVER_assume(pOperation == NULL); ++#endif ++ + if( pOperation != NULL ) + { + IotMqtt_FreeOperation( pOperation ); +diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +index ccfb802..d1b540c 100644 +--- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h ++++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +@@ -237,7 +237,9 @@ + * Used to validate parameters if when connecting to an AWS IoT MQTT server. + */ + #define AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ ++#ifndef AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + #define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( ( uint16_t ) 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ ++#endif + #define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( ( size_t ) 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ + #define AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 131072 ) ) /**< @brief Maximum publish payload length accepted by AWS IoT. */ + +-- +2.7.4 + diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c b/cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c new file mode 100644 index 0000000000..270208987b --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c @@ -0,0 +1,157 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file IotMqtt_UnsubscribeAsync_harness.c + * @brief Implements the proof harness for IotMqtt_UnsubscribeAsync function. + */ + +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include + +#include "mqtt_state.h" + +/****************************************************************/ + +/** + * We abstract these functions for performance reasons. In their original + * implementation, CBMC ended up creating byte extracts for all possible + * objects, due to the polymorphic nature of the linked list. We assume + * that the functions are memory safe, and we free and havoc the list to + * demonstrate that no subsequent code makes use of the values in the list. + */ + +typedef bool ( *MatchFunction_t )( const IotLink_t * const pOperationLink, + void * pCompare ); +typedef void ( *FreeElementFunction_t )( void * pData ); + +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + MatchFunction_t isMatch, + void * pMatch, + FreeElementFunction_t freeElement, + size_t linkOffset ) +{ + free_IotMqttSubscriptionList( pList ); + allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); +} + +/****************************************************************/ + +/** + * We constrain the return values of these functions because + * they are checked by assertions in the MQTT code. + */ + +IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJobStorage_t * const pJobStorage, + IotTaskPoolJob_t * const pJob ) +{ + assert( userCallback != NULL ); + assert( pJobStorage != NULL ); + assert( pJob != NULL ); + + /* _IotMqtt_ScheduleOperation asserts this */ + return IOT_TASKPOOL_SUCCESS; +} + +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + uint32_t timeMs ) +{ + IotTaskPoolError_t error; + + /* _IotMqtt_ScheduleOperation asserts this */ + __CPROVER_assume( error != IOT_TASKPOOL_BAD_PARAMETER && + error != IOT_TASKPOOL_ILLEGAL_OPERATION ); + return error; +} + +/****************************************************************/ + +/** + * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives + * a volatile variable as input. Thus, CBMC will always consider that + * Atomic_Add_u32 will operate over nondetermistic values and it raises + * an unsigned integer overflow failure. However, developers have reported + * that the use of this overflow is part of the function implementation. + * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid + * spurious alarms, we stub out this function to always + * return a nondetermistic odd value. + */ + +uint16_t _IotMqtt_NextPacketIdentifier( void ) +{ + uint16_t id; + + /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + __CPROVER_assume( id & 0x0001 == 1 ); + + return id; +} + +/****************************************************************/ + +void harness() +{ + size_t subscriptionCount; + + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + + IotMqttSubscription_t * subscriptionList = + allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); + __CPROVER_assume( valid_IotMqttSubscriptionArray( subscriptionList, + subscriptionCount ) ); + + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); + __CPROVER_assume( mqttConnection ); + ensure_IotMqttConnection_has_lists( mqttConnection ); + __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection-> + pNetworkInterface ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection-> + pNetworkInterface ) ); + + uint32_t flags; + + IotMqttCallbackInfo_t * pCallbackInfo = + malloc_can_fail( sizeof( *pCallbackInfo ) ); + __CPROVER_assume(pCallbackInfo != NULL); + + /* output */ + IotMqttOperation_t pUnsubscribeOperation = + allocate_IotMqttOperation( NULL, mqttConnection ); + + + + IotMqtt_UnsubscribeAsync( mqttConnection, + subscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pUnsubscribeOperation ); +} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile b/cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile new file mode 100644 index 0000000000..60cdd36601 --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile @@ -0,0 +1,116 @@ +ENTRY=IotMqtt_UnsubscribeAsync + +################################################################ +# Proof assumptions + +# Bound the number of subscriptions in a list (BOUND = MAX-1) +SUBSCRIPTION_COUNT_MAX=2 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# Bound the number of operations in a list (BOUND = MAX-1) +OPERATION_COUNT_MAX=2 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# Bound the length of subscription topics (BOUND = MAX-1) +TOPIC_LENGTH_MAX=8 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +# A constant that should be 2*SUBSCRIPTION_COUNT_MAX-1 +SUBSCRIPTION_LIST_MAX=3 +DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) + +################################################################ +# MQTT configuration constants modified for testing + +# this should be smaller than topic_length_max for coverage +# _validateSubscription coverage at line 385 +AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=6 +DEF += -DAWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=$(AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH) + +# this should be smaller than topic_length_max for coverage +# _IotMqtt_SubscriptionPacketSize coverage at line 702 +MQTT_MAX_REMAINING_LENGTH=6 +DEF += -DMQTT_MAX_REMAINING_LENGTH=$(MQTT_MAX_REMAINING_LENGTH) + +################################################################ +# Function omitted from the proof + +ABSTRACTIONS += --remove-function-body IotLog_Generic + +# Unreachable functions removed for accurate coverage computation +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive +ABSTRACTIONS += --remove-function-body _IotMqtt_AddSubscriptions +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribe +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribeCommon +ABSTRACTIONS += --remove-function-body _matchEndWildcards +ABSTRACTIONS += --remove-function-body _matchWildcards +ABSTRACTIONS += --remove-function-body _mqttOperation_match +ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe +ABSTRACTIONS += --remove-function-body _packetMatch +ABSTRACTIONS += --remove-function-body _topicFilterMatch +ABSTRACTIONS += --remove-function-body _validateQos + +# Functions manually determined to be unreachable in their calling context +# See README.md for explanations of unreachability +ABSTRACTIONS += --remove-function-body _checkRetryLimit +ABSTRACTIONS += --remove-function-body _scheduleNextRetry +ABSTRACTIONS += --remove-function-body _IotMqtt_RemoveSubscriptionByPacket + +################################################################ + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +DEF += -DCBMC_CREATEOPERATION_INITIALIZE_CANT_FAIL=1 +DEF += -DCBMC_CREATEOPERATION_UNSUCCESSFUL_STATUS_MEANS_NULL_POINTER=1 +DEF += -DCBMC_FINDFIRSTMATCH_MATCH_IS_NONNULL=1 +DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 + +# allow mqtt mallocs to fail for coverage +DEF += -include mqtt_state.h +DEF += -DIotMqtt_MallocMessage=malloc_can_fail +DEF += -DIotMqtt_MallocOperation=malloc_can_fail + +################################################################ +# Loop unwinding + +LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link2.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link5.0:$(SUBSCRIPTION_COUNT_MAX) + +LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link2.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link3.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link4.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link5.0:$(SUBSCRIPTION_LIST_MAX) +LOOP += IotListDouble_RemoveAllMatches\$$link6.0:$(SUBSCRIPTION_LIST_MAX) + +LOOP += _IotMqtt_RemoveSubscriptionByTopicFilter.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_SerializeUnsubscribe.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_SerializeUnsubscribeCommon.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _encodeRemainingLength.0:2 +LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) +LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += harness.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) +LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +################################################################ + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md b/cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md new file mode 100644 index 0000000000..17f68c2d1c --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md @@ -0,0 +1,113 @@ +This is a memory safety proof for IotMqtt_UnsubscribeAsync. + +This proof attains 91% code coverage. The following comments explain +why the uncovered lines of code are unreachable code. + +Some functions contain unreachable blocks of code: + +* libraries/standard/common/include/iot_linear_containers.h + IotListDouble_FindFirstMatch + + * Always called with a nonnull match predicate + +* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_OperationType + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommon + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommonSetup + + * Always called with a nonnull operation reference + +* libraries/standard/mqtt/src/iot_mqtt_helper.c + \_IotMqtt_RemainingLengthEncodedSize + + * Proof assumption: Proof bounds on number and size of subscription topics limits the + outbound packet to less than 128 bytes. + +* libraries/standard/mqtt/src/iot_mqtt_helper.c + \_IotMqtt_SubscriptionPacketSize + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_helper.c \_encodeRemainingLength + + * Proof assumption: Proof bounds on number and size of subscription topics limits the + outbound packet to less than 128 bytes. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c + \_IotMqtt_CreateOperation + + * pOperation is null if status is not SUCCESS. This is because 1) + status is initialized to SUCCESS and is left untouched when operation + allocation succeeds (returns a nonnull value), and 2) status is set + to SUCCESS by \_initializeOperation because \_initializeOperation + always returns SUCCESS because an asynchronous operation is + unwaitable, so initialization can't fail. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c + \_IotMqtt_DecrementOperationReferences + + * Always called with cancelJob false. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c + \_IotMqtt_DestroyOperation + + * Notify calls DestroyOperation with a linked, unwaitable + (asynchronous) SUBSCRIBE operation. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_Notify + + * ProcessSend calls Notify with an unwaitable (asynchronous) SUBSCRIBE + operation. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_ProcessSend + + * ProcessSsend is called with an UNSUBSCRIBE operation + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_completePendingSend + + * Always called with an unwaitable (asynchronous) and nonPUBLISH + (UNSUBSCRIBE) operation + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_initializeOperation + + * Always called with an unwaitable (asynchronous) operation + +* libraries/standard/mqtt/src/iot_mqtt_subscription.c \_topicMatch + + * Always called with exactMatchOnly == true + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateListSize + + * Proof assumption: list of topics to unsubscribe is nonempty + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateString + + * Proof assumption: length of topic filter is always positive. + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateSubscription + + * Always called with UNSUBSCRIBE operation type. + +Some functions are simply unreachable: + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_checkRetryLimit + + * Unreachable: Only function call is in an unreachable block of code + in ProcessSend. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_scheduleNextRetry + + * Unreachable: Only function call is in an unreachable block of code + in completePendingSend + +* libraries/standard/mqtt/src/iot_mqtt_subscription.c + \_IotMqtt_RemoveSubscriptionByPacket + + * Unreachable: Only function call in from an unreachable block of + subscriptionCommon + diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml new file mode 100644 index 0000000000..87089222ce --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. \ No newline at end of file diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json new file mode 100644 index 0000000000..e0236e7737 --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json @@ -0,0 +1,20 @@ +{ "expected-missing-functions": + [ + "IotClock_GetTimeMs", + "IotLog_Generic", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotSemaphore_Post", + "IotTaskPool_GetSystemTaskPool", + "IotTaskPool_strerror", + + "_IotMqtt_RemoveSubscriptionByPacket", + "_checkRetryLimit", + "_scheduleNextRetry" + ], + "proof-name": "IotMqtt_UnsubscribeAsync", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c b/cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c new file mode 100644 index 0000000000..aec76c571b --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c @@ -0,0 +1,161 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file IotMqtt_UnsubscribeSync_harness.c + * @brief Implements the proof harness for IotMqtt_UnsubscribeSync function. + */ + +#include "iot_config.h" +#include "private/iot_mqtt_internal.h" + +#include + +#include "mqtt_state.h" + +/****************************************************************/ + +/** + * We abstract this function for performance reasons. In its original + * implementation, CBMC ended up creating byte extracts for all possible + * objects, due to the polymorphic nature of the linked list. We assume + * that the function is memory safe, and we free and havoc the list to + * demonstrate that no subsequent code makes use of the values in the list. + */ + +typedef bool ( *MatchFunction_t )( const IotLink_t * const pOperationLink, + void * pCompare ); +typedef void ( *FreeElementFunction_t )( void * pData ); + +void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, + MatchFunction_t isMatch, + void * pMatch, + FreeElementFunction_t freeElement, + size_t linkOffset ) +{ + free_IotMqttSubscriptionList( pList ); + allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); +} + +/****************************************************************/ + +/** + * We constrain the return values of these functions because they + * are checked by assertions in the MQTT code. + */ + +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, + IotTaskPoolJobStatus_t * const pStatus ) +{ + assert( taskPool == NULL || job == NULL ); + return IOT_TASKPOOL_BAD_PARAMETER; +} + +/****************************************************************/ + +/** + * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives + * a volatile variable as input. Thus, CBMC will always consider that + * Atomic_Add_u32 will operate over nondetermistic values and it raises + * an unsigned integer overflow failure. However, developers have reported + * that the use of this overflow is part of the function implementation. + * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid + * spurious alarms, we stub out this function to always + * return a nondetermistic odd value. + */ +uint16_t _IotMqtt_NextPacketIdentifier( void ) +{ + uint16_t id; + + /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + __CPROVER_assume( id & 0x0001 == 1 ); + + return id; +} + +/****************************************************************/ + +/** + * We are abstracting the semaphores because we are doing sequential proof. + * But the semaphore API assures us that TimedWait called after Post will + * never fail. Our abstraction of the semaphores models this behavior. + */ +static unsigned int flagSemaphore; + +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) +{ + assert( pSemaphore != NULL ); + flagSemaphore++; +} + +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, + uint32_t timeoutMs ) +{ + assert( pSemaphore != NULL ); + + if( flagSemaphore > 0 ) + { + flagSemaphore--; + return true; + } + + return false; +} + +/****************************************************************/ + +void harness() +{ + size_t subscriptionCount; + + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + + IotMqttSubscription_t * subscriptionList = + allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); + __CPROVER_assume( valid_IotMqttSubscriptionArray( subscriptionList, + subscriptionCount ) ); + + IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); + __CPROVER_assume( mqttConnection != NULL ); + ensure_IotMqttConnection_has_lists( mqttConnection ); + __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection-> + pNetworkInterface ) ); + __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection-> + pNetworkInterface ) ); + + uint32_t flags; + uint32_t timeoutMs; + + /* initialize semaphore flag */ + flagSemaphore = 0; + + IotMqtt_UnsubscribeSync( mqttConnection, + subscriptionList, + subscriptionCount, + flags, + timeoutMs ); +} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile b/cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile new file mode 100644 index 0000000000..1c4abe61db --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile @@ -0,0 +1,106 @@ +ENTRY=IotMqtt_UnsubscribeSync + +################################################################ +# Proof assumptions + +# Bound the number of subscriptions in a list (BOUND = MAX-1) +SUBSCRIPTION_COUNT_MAX=2 +DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) + +# Bound the number of operations in a list (BOUND = MAX-1) +OPERATION_COUNT_MAX=2 +DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) + +# Bound the length of subscription topics (BOUND = MAX-1) +TOPIC_LENGTH_MAX=8 +DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) + +# A constant that should be 2*SUBSCRIPTION_COUNT_MAX-1 +SUBSCRIPTION_LIST_MAX=3 +DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) + +################################################################ +# MQTT configuration constants modified for testing + +# this should be smaller than topic_length_max for coverage +# _validateSubscription coverage at line 385 +AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=6 +DEF += -DAWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=$(AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH) + +# this should be smaller than topic_length_max for coverage +# _IotMqtt_SubscriptionPacketSize coverage at line 702 +MQTT_MAX_REMAINING_LENGTH=6 +DEF += -DMQTT_MAX_REMAINING_LENGTH=$(MQTT_MAX_REMAINING_LENGTH) + +################################################################ +# Function omitted from the proof + +ABSTRACTIONS += --remove-function-body IotLog_Generic + +# Unreachable functions removed for accurate coverage computation +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose +ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive +ABSTRACTIONS += --remove-function-body _IotMqtt_AddSubscriptions +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribe +ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribeCommon +ABSTRACTIONS += --remove-function-body _matchEndWildcards +ABSTRACTIONS += --remove-function-body _matchWildcards +ABSTRACTIONS += --remove-function-body _mqttOperation_match +ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe +ABSTRACTIONS += --remove-function-body _packetMatch +ABSTRACTIONS += --remove-function-body _topicFilterMatch +ABSTRACTIONS += --remove-function-body _validateQos + +# Functions manually determined to be unreachable in their calling context +# See README.md for explanations of unreachability +ABSTRACTIONS += --remove-function-body _IotMqtt_RemoveSubscriptionByPacket +ABSTRACTIONS += --remove-function-body _IotMqtt_ScheduleOperation +ABSTRACTIONS += --remove-function-body _checkRetryLimit +ABSTRACTIONS += --remove-function-body _scheduleCallback +ABSTRACTIONS += --remove-function-body _scheduleNextRetry + +################################################################ + +OBJS += $(ENTRY)_harness.goto +OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto +OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto + +DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 + +# allow mqtt mallocs to fail for coverage +DEF += -include mqtt_state.h +DEF += -DIotMqtt_MallocMessage=malloc_can_fail +DEF += -DIotMqtt_MallocOperation=malloc_can_fail + +################################################################ +# Loop unwinding + +LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link2.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link3.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link4.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += IotListDouble_FindFirstMatch\$$link5.0:$(SUBSCRIPTION_COUNT_MAX) + +LOOP += _IotMqtt_RemoveSubscriptionByTopicFilter.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_SerializeUnsubscribeCommon.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) +LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) +LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) +LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) + +UNWINDING += --unwind 1 +UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' + +################################################################ + +include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/README.md b/cbmc/proofs/IotMqtt_UnsubscribeSync/README.md new file mode 100644 index 0000000000..ae0cd683e8 --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeSync/README.md @@ -0,0 +1,133 @@ +This is a memory safety proof for IotMqtt_UnsubscribeAsync. + +This proof attains 88% code coverage. The following comments explain +why the uncovered lines of code are unreachable code. + +Some functions contain unreachable blocks of code: + +* libraries/standard/common/include/iot_linear_containers.h IotListDouble_FindFirstMatch + + * Always called with a nonnull match predicate + +* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_OperationType + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_Wait + + * Wait is called with a good operation in a connected state. + +* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_strerror + + * Called with only a subset of error codes. + +* libraries/standard/mqtt/src/iot_mqtt_api.c \_sendMqttMessage + + * A synchronous operation is a blocking operation + +* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommon + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommonSetup + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_api.c \_waitForOperation + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_helper.c \_IotMqtt_RemainingLengthEncodedSize + + * Proof assumption: Proof bounds on number and size of subscription topics limits the + outbound packet to less than 128 bytes. + +* libraries/standard/mqtt/src/iot_mqtt_helper.c \_IotMqtt_SubscriptionPacketSize + + * Always called with UNSUBSCRIBE operation type + +* libraries/standard/mqtt/src/iot_mqtt_helper.c \_encodeRemainingLength + + * Proof assumption: Proof bounds on number and size of subscription topics limits the + outbound packet to less than 128 bytes. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_CreateOperation + + * Waitable operation has no callback + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_DecrementOperationReferences + + * TODO: TryCancel always fails + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_DestroyOperation + + * Always called with a linked operation + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_Notify + + * TODO + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_ProcessSend + + * Always called with a UNSUBSCRIBE operation + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_completePendingSend + + * The operation.periodic.retry union member is not valid for UNSUBSCRIBE. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_initializeOperation + + * Sync operation is always waitable. + +* libraries/standard/mqtt/src/iot_mqtt_subscription.c \_topicMatch + + * Always called with exactMatchOnly == true + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_IotMqtt_ValidateOperation + + * Operation is nonnull and waitable. + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateListSize + + * Proof assumption: list of topics to unsubscribe is nonempty + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateString + + * Proof assumption: length of topic filter is always positive. + +* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateSubscription + + * Always called with UNSUBSCRIBE operation type. + +Some functions are simply unreachable: + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_ScheduleOperation + + * Unreachable: Only function calls are from unreachable + scheduleCallback, unreachable scheduleNextRetry, and an + unreachable block of sendMqttMessage. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_checkRetryLimit + + * Unreachable: Only function call is in an unreachable block of code + in ProcessSend. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_scheduleCallback + + * Unreachable: Only function call is from Notify, but waitable + operations have no callbacks. + +* libraries/standard/mqtt/src/iot_mqtt_operation.c \_scheduleNextRetry + + * Unreachable: Only function call is in an unreachable block of code + in completePendingSend + +* libraries/standard/mqtt/src/iot_mqtt_serialize.c \_IotMqtt_PublishSetDup + + * Unreachable: Only function call in from unreachable checkRetryLimit + +* libraries/standard/mqtt/src/iot_mqtt_subscription.c \_IotMqtt_RemoveSubscriptionByPacket + + * Unreachable: Only function call in from an unreachable block of + subscriptionCommon + diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml new file mode 100644 index 0000000000..87089222ce --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. \ No newline at end of file diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json new file mode 100644 index 0000000000..d31180ebeb --- /dev/null +++ b/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json @@ -0,0 +1,19 @@ +{ "expected-missing-functions": + [ + "IotClock_GetTimeMs", + "IotLog_Generic", + "IotMutex_Destroy", + "IotMutex_Lock", + "IotMutex_Unlock", + "IotSemaphore_Create", + "IotSemaphore_Destroy", + "IotTaskPool_GetSystemTaskPool", + + "_IotMqtt_RemoveSubscriptionByPacket", + "_checkRetryLimit", + "_scheduleCallback", + "_scheduleNextRetry" + ], + "proof-name": "IotMqtt_UnsubscribeSync", + "proof-root": "cbmc/proofs" +} diff --git a/cbmc/proofs/prepare.py b/cbmc/proofs/prepare.py index bc25a5e0b9..dd0fd385d4 100755 --- a/cbmc/proofs/prepare.py +++ b/cbmc/proofs/prepare.py @@ -57,7 +57,7 @@ def apply_patches(): if proc.returncode: logging.warning( "Patch checking failed. Check output:\n%s", - "\n".join([" %s" % line for line in proc.stdout])) + "\n".join([" %s" % line for line in proc.stdout.splitlines()])) logging.info("Applying patch '%s'", fyle.name) cmd = ["git", "apply", str(fyle)] From 1476e851428157d779a7779ed4a64cf8fcf81c36 Mon Sep 17 00:00:00 2001 From: "Felipe R. Monteiro" Date: Wed, 8 Apr 2020 19:35:00 -0400 Subject: [PATCH 453/844] Avoid null dereferences in IotMqtt_Connect (#858) * Check for consistency in userName and password on IotMqttConnectInfo_t Signed-off-by: Felipe R. Monteiro * Check for nullness in IotMqtt_Connect's parameters Signed-off-by: Felipe R. Monteiro --- libraries/standard/mqtt/src/iot_mqtt_api.c | 2 +- libraries/standard/mqtt/src/iot_mqtt_validate.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 319ca82772..1e55cb5920 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -1244,7 +1244,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, status = IOT_MQTT_NOT_INITIALIZED; } /* Validate network interface and connect info. */ - else if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) + else if( _IotMqtt_ValidateConnect( pConnectInfo ) == false || pMqttConnection == NULL ) { status = IOT_MQTT_BAD_PARAMETER; } diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 1799984b97..cbefb80ab2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -569,7 +569,9 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) bool status = true; /* Check for NULL. */ - if( pConnectInfo == NULL ) + if( pConnectInfo == NULL || + ( pConnectInfo->pUserName == NULL && pConnectInfo->userNameLength != 0 ) || + ( pConnectInfo->pPassword == NULL && pConnectInfo->passwordLength != 0 ) ) { IotLogError( "MQTT connection information cannot be NULL." ); From ea6c93d2148f5a4666eae703f624974786629223 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:02:37 -0700 Subject: [PATCH 454/844] Move the source and include variables in CMakeLists.txt to a common file (#859) --- libraries/standard/mqtt/CMakeLists.txt | 38 +++++--------------- libraries/standard/mqtt/filePaths.cmake | 47 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 libraries/standard/mqtt/filePaths.cmake diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index 256812b206..cc48c78b30 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -1,14 +1,5 @@ -# MQTT library source files. -set( MQTT_SOURCES - src/iot_mqtt_api.c - src/iot_mqtt_network.c - src/iot_mqtt_operation.c - src/iot_mqtt_serialize.c - src/iot_mqtt_lightweight_api.c - src/iot_mqtt_helper.c - src/iot_mqtt_static_memory.c - src/iot_mqtt_subscription.c - src/iot_mqtt_validate.c ) +# Include filepaths for source and include. +include(filePaths.cmake) # MQTT library target. add_library( iotmqtt @@ -20,7 +11,7 @@ add_library( iotmqtt src/private/iot_mqtt_internal.h ) # MQTT public include path. -target_include_directories( iotmqtt PUBLIC include ) +target_include_directories( iotmqtt PUBLIC ${MQTT_INCLUDE_PUBLIC_DIRS} ) # Link required libraries. target_link_libraries( iotmqtt PRIVATE iotbase ) @@ -46,41 +37,30 @@ if( ${IOT_BUILD_TESTS} ) # This test library is used to mock MQTT connections. add_library( iot_mqtt_mock - test/mock/iot_tests_mqtt_mock.c + ${MQTT_TEST_MOCK_SOURCES} test/mock/iot_tests_mqtt_mock.h ${CONFIG_HEADER_PATH}/iot_config.h ) # Organization of MQTT mock library in folders. set_property( TARGET iot_mqtt_mock PROPERTY FOLDER tests ) source_group( "" FILES - test/mock/iot_tests_mqtt_mock.c + ${MQTT_TEST_MOCK_SOURCES} test/mock/iot_tests_mqtt_mock.h ${CONFIG_HEADER_PATH}/iot_config.h ) # The MQTT mock needs the internal MQTT header. target_include_directories( iot_mqtt_mock - PRIVATE src - PUBLIC test/mock ) + PRIVATE ${MQTT_TEST_MOCK_INCLUDE_PRIVATE_DIRS} + PUBLIC ${MQTT_TEST_MOCK_INCLUDE_PUBLIC_DIRS} ) # Link required libraries for MQTT mock. target_link_libraries( iot_mqtt_mock PRIVATE iotbase iotmqtt unity ) - # MQTT system test sources. - set( MQTT_SYSTEM_TEST_SOURCES - test/system/iot_tests_mqtt_system.c ) - - # MQTT unit test sources. - set( MQTT_UNIT_TEST_SOURCES - test/unit/iot_tests_mqtt_api.c - test/unit/iot_tests_mqtt_platform.c - test/unit/iot_tests_mqtt_receive.c - test/unit/iot_tests_mqtt_subscription.c - test/unit/iot_tests_mqtt_validate.c ) - # MQTT tests executable. add_executable( iot_tests_mqtt ${MQTT_SYSTEM_TEST_SOURCES} ${MQTT_UNIT_TEST_SOURCES} + test/unit/iot_tests_mqtt_platform.c test/iot_tests_mqtt.c ${IOT_TEST_APP_SOURCE} ${CONFIG_HEADER_PATH}/iot_config.h ) @@ -90,7 +70,7 @@ if( ${IOT_BUILD_TESTS} ) -DRunTests=RunMqttTests ) # The MQTT tests need the internal MQTT header. - target_include_directories( iot_tests_mqtt PRIVATE src ) + target_include_directories( iot_tests_mqtt PRIVATE ${MQTT_TEST_INCLUDE_PRIVATE_DIRS} ) # MQTT tests library dependencies. target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt iotbase unity iot_mqtt_mock ) diff --git a/libraries/standard/mqtt/filePaths.cmake b/libraries/standard/mqtt/filePaths.cmake new file mode 100644 index 0000000000..d2acb3538a --- /dev/null +++ b/libraries/standard/mqtt/filePaths.cmake @@ -0,0 +1,47 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# MQTT library source files. +set( MQTT_SOURCES + src/iot_mqtt_api.c + src/iot_mqtt_network.c + src/iot_mqtt_operation.c + src/iot_mqtt_serialize.c + src/iot_mqtt_lightweight_api.c + src/iot_mqtt_helper.c + src/iot_mqtt_static_memory.c + src/iot_mqtt_subscription.c + src/iot_mqtt_validate.c ) + +# MQTT library Include directories. +set( MQTT_INCLUDE_PUBLIC_DIRS + include ) + +# MQTT system test source files. +set( MQTT_SYSTEM_TEST_SOURCES + test/system/iot_tests_mqtt_system.c ) + +# MQTT unit test sources. +set( MQTT_UNIT_TEST_SOURCES + test/unit/iot_tests_mqtt_api.c + test/unit/iot_tests_mqtt_receive.c + test/unit/iot_tests_mqtt_subscription.c + test/unit/iot_tests_mqtt_validate.c ) + +# MQTT test include directories. +set( MQTT_TEST_INCLUDE_PRIVATE_DIRS + src ) + +# MQTT mock source files. +set( MQTT_TEST_MOCK_SOURCES + test/mock/iot_tests_mqtt_mock.c ) + +# MQTT mock include directories. +set( MQTT_TEST_MOCK_INCLUDE_PRIVATE_DIRS + src ) +set( MQTT_TEST_MOCK_INCLUDE_PUBLIC_DIRS + test/mock ) From dbf1c138bf026fea20500df7306c4c8ca5e90c6e Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Thu, 9 Apr 2020 17:28:57 -0400 Subject: [PATCH 455/844] Update CBMC Makefile.common to use new viewer. (#861) Co-authored-by: Mark R Tuttle --- cbmc/proofs/Makefile.common | 140 +++++++++++++++--------------------- 1 file changed, 58 insertions(+), 82 deletions(-) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index dcf8c1ebe1..f28773ed38 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -1,3 +1,5 @@ +# -*- mode: makefile -*- + SHELL=/bin/bash default: report @@ -14,7 +16,6 @@ GOTO_ANALYZER ?= goto-analyzer BATCH ?= cbmc-batch VIEWER ?= cbmc-viewer - ################################################################ # Build goto binary for cbmc # Build goto binaries with options taken from top-level cmake @@ -39,6 +40,17 @@ DEF += \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 +# The --nondet-static flag causes cbmc to ignore static initialization +# and havoc static variables to give them unconstrained initial values. +# Use --nondet-static-exclude to remove a variable from the list of +# static variables to havoc. Use goto-instrument --list-symbols to +# get the fully-qualified name of the variable to remove. Then add +# --nondet-static-exclude to NONDET in the proof makefile. +# For example: +# NONDET += --nondet-static-exclude _IotMqtt_SerializePingreq::1::pPingreq + +NONDET += --nondet-static + %.goto : %.c $(GOTO_CC) -o $@ $(CFLAGS) $< @@ -47,12 +59,13 @@ $(MQTT)/build: > $(ENTRY)0.txt 2>&1 $(ENTRY)1.goto: $(MQTT)/build $(OBJS) - $(GOTO_CC) --function harness -o $@ $(OBJS) + $(GOTO_CC) --function harness -o $@ $(OBJS) \ > $(ENTRY)1.txt 2>&1 $(ENTRY)2.goto: $(ENTRY)1.goto $(GOTO_INSTRUMENT) \ $(ABSTRACTIONS) \ + $(NONDET) \ --drop-unused-functions \ --slice-global-inits $< $@ \ > $(ENTRY)2.txt 2>&1 @@ -60,7 +73,6 @@ $(ENTRY)2.goto: $(ENTRY)1.goto $(ENTRY).goto: $(ENTRY)2.goto cp $< $@ - ################################################################ # Set C compiler defines @@ -68,7 +80,6 @@ CBMC_OBJECT_BITS ?= 8 CBMCFLAGS += --object-bits $(CBMC_OBJECT_BITS) CBMC_MAX_OBJECT_SIZE = "(SIZE_MAX>>(CBMC_OBJECT_BITS+1))" - ################################################################ # Run cbmc and build html report @@ -81,7 +92,6 @@ CBMCFLAGS += \ --float-overflow-check \ --flush \ --nan-check \ - --nondet-static \ --pointer-check \ --pointer-overflow-check \ --signed-overflow-check \ @@ -92,15 +102,27 @@ CBMCFLAGS += \ goto: $(ENTRY).goto cbmc.txt: $(ENTRY).goto - @echo cbmc $(CBMCFLAGS) --trace $< 2>&1 | tee $@ - cbmc $(CBMCFLAGS) --trace $< 2>&1 | tee -a $@ + - cbmc $(CBMCFLAGS) --trace $< >$@ 2>&1 + +cbmc.xml: $(ENTRY).goto + - cbmc $(CBMCFLAGS) --trace --xml-ui $< >$@ 2>&1 + +cbmc.json: $(ENTRY).goto + - cbmc $(CBMCFLAGS) --trace --json-ui $< >$@ 2>&1 property.xml: $(ENTRY).goto - cbmc $(CBMCFLAGS) --show-properties --xml-ui $< 2>&1 > $@ + cbmc $(CBMCFLAGS) --show-properties --xml-ui $< >$@ 2>&1 + +property.json: $(ENTRY).goto + cbmc $(CBMCFLAGS) --show-properties --json-ui $< >$@ 2>&1 coverage.xml: $(ENTRY).goto cbmc $(filter-out --unwinding-assertions,$(CBMCFLAGS)) \ - --cover location --xml-ui $< 2>&1 > $@ + --cover location --xml-ui $< >$@ 2>&1 + +coverage.json: $(ENTRY).goto + cbmc $(filter-out --unwinding-assertions,$(CBMCFLAGS)) \ + --cover location --json-ui $< >$@ 2>&1 cbmc: cbmc.txt @@ -128,79 +150,7 @@ clean: veryclean: clean $(RM) -r html -.PHONY: cbmc property coverage report clean veryclean - -################################################################ -# Run cbmc under cbmc-batch - -BATCH ?= cbmc-batch -WS ?= ws -JOBOS ?= ubuntu16 - -define encode_options - '=$(shell echo $(1) | sed 's/ ,/ /g' | sed 's/ /;/g')=' -endef - -PROOFMEM ?= 32000 -CBMCPKG ?= cbmc -BATCHPKG ?= cbmc-batch -VIEWERPKG ?= cbmc-viewer - -SRC_ROOT ?= $(MQTT) -SRC_TARFILE ?= s3://cbmc/mqtt.tar.gz - -BATCHFLAGS ?= \ - --srcdir $(MQTT) \ - --wsdir $(WS) \ - --jobprefix $(ENTRY) \ - --no-build \ - --goto $(ENTRY).goto \ - --cbmcflags $(call encode_options,$(CBMCFLAGS)) \ - --property-memory $(PROPMEM) \ - --coverage-memory $(COVMEM) \ - --cbmcpkg $(CBMCPKG) \ - --batchpkg $(BATCHPKG) \ - --viewerpkg $(VIEWERPKG) \ - --no-copysrc \ - --srctarfile $(SRC_TARFILE) \ - --blddir $(MQTT) \ - --jobos $(JOBOS) \ - -define yaml_encode_options - "$(shell echo $(1) | sed 's/ ,/ /g' | sed 's/ /;/g')" -endef - -$(ENTRY).yaml: $(ENTRY).goto Makefile - echo 'jobos: $(JOBOS)' > $@ - echo 'cbmcpkg: $(CBMCPKG)' >> $@ - echo 'batchpkg: $(BATCHPKG)' >> $@ - echo 'viewerpkg: $(VIEWERPKG)' >> $@ - echo 'goto: $(ENTRY).goto' >> $@ - echo 'build: false' >> $@ - echo 'cbmcflags: $(call yaml_encode_options,$(CBMCFLAGS))' >> $@ - echo 'property_memory: $(PROOFMEM)' >> $@ - echo 'coverage_memory: $(PROOFMEM)' >> $@ - echo 'expected: "SUCCESSFUL"' >> $@ - -launch: $(ENTRY).goto Makefile - mkdir -p $(WS) - cp $(ENTRY).goto $(WS) - $(BATCH) $(BATCHFLAGS) - -launch-clean: - for d in $(ENTRY)*; do \ - if [ -d $$d ]; then \ - for f in $$d.json $$d.yaml Makefile-$$d; do \ - if [ -f $$f ]; then mv $$f $$d; fi \ - done\ - fi \ - done - $(RM) Makefile-$(ENTRY)-[0-9]*-[0-9]* - $(RM) $(ENTRY)-[0-9]*-[0-9]*.json $(ENTRY)-[0-9]*-[0-9]*.yaml - $(RM) -r $(WS) - -launch-veryclean: launch-clean - $(RM) -r $(ENTRY)-[0-9]*-[0-9]* +.PHONY: default goto cbmc property coverage report clean veryclean ################################################################ # Build configuration file to run cbmc under cbmc-batch in CI @@ -221,3 +171,29 @@ cbmc-batch.yaml: Makefile ../Makefile.common .PHONY: cbmc-batch.yaml ################################################################ +# Use the latest version of cbmc viewer + +VIEWER2=viewer +MAKE_SOURCES=make-sources + +sources.json: + - $(MAKE_SOURCES) --root $(MQTT) --build . > $@ + +report2: sources.json cbmc.xml property.xml coverage.xml + - $(VIEWER2) \ + --viewer-sources sources.json \ + --goto $(ENTRY).goto \ + --srcdir $(MQTT) \ + --htmldir html \ + --result cbmc.xml \ + --property property.xml \ + --coverage coverage.xml + +clean2: clean + $(RM) cbmc.xml sources.json + +veryclean2: veryclean + $(RM) cbmc.xml sources.json + $(RM) viewer-*.json + +.PHONY: report2 clean2 veryclean2 From 1555140532d19800ccb2f90ca1eaf4b830fa6014 Mon Sep 17 00:00:00 2001 From: Carl Lundin <53273776+lundinc2@users.noreply.github.com> Date: Thu, 9 Apr 2020 15:32:37 -0700 Subject: [PATCH 456/844] Set mbedTLS version to 2.16.5 (#860) --- .gitmodules | 1 + third_party/mbedtls/mbedtls | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 7dc15df6f1..5c06a58594 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,4 @@ [submodule "third_party/mbedtls/mbedtls"] path = third_party/mbedtls/mbedtls url = https://github.com/ARMmbed/mbedtls.git + branch = mbedtls-2.16.5 diff --git a/third_party/mbedtls/mbedtls b/third_party/mbedtls/mbedtls index 535ee4a35b..0fce215851 160000 --- a/third_party/mbedtls/mbedtls +++ b/third_party/mbedtls/mbedtls @@ -1 +1 @@ -Subproject commit 535ee4a35b9c4ddd059451b8fa5b201bfc89fbcf +Subproject commit 0fce215851cc069c5b5def12fcc18725055fa6cf From 9a8a8ac37ec3d39024fa037492539e0f9074e040 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 9 Apr 2020 15:41:43 -0700 Subject: [PATCH 457/844] Create development branch --- .gitignore | 6 - .gitmodules | 7 - .travis.yml | 72 - CMakeLists.txt | 117 - README.md | 84 +- cbmc/.gitignore | 2 - cbmc/README.md | 18 - ...et-CBMC-proofs-stub-out-RemoveAllMat.patch | 37 - ...ary-code-for-CBMC-Unsubscribe-proofs.patch | 87 - cbmc/patches/README.md | 6 - .../IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c | 23 - cbmc/proofs/IotMqtt_Cleanup/Makefile | 12 - cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml | 4 - cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json | 22 - .../IotMqtt_DeserializeConnack/Makefile | 10 - .../_IotMqtt_DeserializeConnack_harness.c | 15 - .../cbmc-batch.yaml | 4 - .../cbmc-viewer.json | 22 - .../IotMqtt_DeserializePingresp/Makefile | 10 - .../_IotMqtt_DeserializePingresp_harness.c | 15 - .../cbmc-batch.yaml | 4 - .../cbmc-viewer.json | 22 - .../proofs/IotMqtt_DeserializePuback/Makefile | 10 - .../_IotMqtt_DeserializePuback_harness.c | 15 - .../IotMqtt_DeserializePuback/cbmc-batch.yaml | 4 - .../cbmc-viewer.json | 22 - .../IotMqtt_DeserializePublish/Makefile | 10 - .../_IotMqtt_DeserializePublish_harness.c | 24 - .../cbmc-batch.yaml | 4 - .../cbmc-viewer.json | 22 - .../proofs/IotMqtt_DeserializeSuback/Makefile | 50 - .../_IotMqtt_DeserializeSuback_harness.c | 86 - .../IotMqtt_DeserializeSuback/cbmc-batch.yaml | 4 - .../cbmc-viewer.json | 22 - .../IotMqtt_DeserializeUnsuback/Makefile | 10 - .../_IotMqtt_DeserializeUnsuback_harness.c | 15 - .../cbmc-batch.yaml | 4 - .../cbmc-viewer.json | 22 - .../IotMqtt_Init/IotMqtt_Init_harness.c | 11 - cbmc/proofs/IotMqtt_Init/Makefile | 12 - cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml | 4 - cbmc/proofs/IotMqtt_Init/cbmc-viewer.json | 22 - .../IotMqtt_IsSubscribed_harness.c | 31 - cbmc/proofs/IotMqtt_IsSubscribed/Makefile | 45 - .../IotMqtt_IsSubscribed/cbmc-batch.yaml | 4 - .../IotMqtt_IsSubscribed/cbmc-viewer.json | 22 - .../IotMqtt_OperationType_harness.c | 9 - cbmc/proofs/IotMqtt_OperationType/Makefile | 6 - .../IotMqtt_OperationType/cbmc-batch.yaml | 4 - .../IotMqtt_OperationType/cbmc-viewer.json | 22 - .../IotMqtt_PublishAsync_harness.c | 88 - cbmc/proofs/IotMqtt_PublishAsync/Makefile | 33 - .../IotMqtt_PublishAsync/cbmc-batch.yaml | 4 - .../IotMqtt_PublishAsync/cbmc-viewer.json | 22 - .../IotMqtt_PublishSync_harness.c | 174 - cbmc/proofs/IotMqtt_PublishSync/Makefile | 60 - .../IotMqtt_PublishSync/cbmc-batch.yaml | 4 - .../IotMqtt_PublishSync/cbmc-viewer.json | 16 - .../IotMqtt_SubscribeAsync_harness.c | 144 - cbmc/proofs/IotMqtt_SubscribeAsync/Makefile | 71 - .../IotMqtt_SubscribeAsync/cbmc-batch.yaml | 8 - .../IotMqtt_SubscribeAsync/cbmc-viewer.json | 18 - .../IotMqtt_SubscribeSync_harness.c | 157 - cbmc/proofs/IotMqtt_SubscribeSync/Makefile | 77 - .../IotMqtt_SubscribeSync/cbmc-batch.yaml | 2 - .../IotMqtt_SubscribeSync/cbmc-viewer.json | 19 - .../IotMqtt_UnsubscribeAsync_harness.c | 157 - cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile | 116 - .../proofs/IotMqtt_UnsubscribeAsync/README.md | 113 - .../IotMqtt_UnsubscribeAsync/cbmc-batch.yaml | 2 - .../IotMqtt_UnsubscribeAsync/cbmc-viewer.json | 20 - .../IotMqtt_UnsubscribeSync_harness.c | 161 - cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile | 106 - cbmc/proofs/IotMqtt_UnsubscribeSync/README.md | 133 - .../IotMqtt_UnsubscribeSync/cbmc-batch.yaml | 2 - .../IotMqtt_UnsubscribeSync/cbmc-viewer.json | 19 - .../IotMqtt_Wait/IotMqtt_Wait_harness.c | 32 - cbmc/proofs/IotMqtt_Wait/Makefile | 45 - cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml | 4 - cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json | 22 - .../IotMqtt_strerror_harness.c | 9 - cbmc/proofs/IotMqtt_strerror/Makefile | 6 - cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml | 4 - cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json | 22 - cbmc/proofs/Makefile.common | 199 -- cbmc/proofs/make_cbmc_batch_files.py | 53 - cbmc/proofs/mqtt_state.c | 779 ----- cbmc/proofs/mqtt_state.h | 236 -- cbmc/proofs/prepare.py | 86 - demos/CMakeLists.txt | 98 - demos/Makefile | 14 + demos/README.md | 13 - demos/app/iot_demo.c | 226 -- demos/app/iot_demo_arguments.c | 337 -- demos/include/iot_demo_arguments.h | 99 - demos/include/iot_demo_logging.h | 54 - demos/iot_config.h | 95 - demos/lexicon.txt | 48 - demos/mqtt_demo.c | 6 + demos/src/aws_iot_demo_defender.c | 314 -- demos/src/aws_iot_demo_jobs.c | 896 ----- demos/src/aws_iot_demo_shadow.c | 939 ------ demos/src/iot_demo_mqtt.c | 856 ----- doc/config/common | 117 - doc/config/defender | 33 - doc/config/html/footer.html | 35 - doc/config/html/header.html | 88 - doc/config/html/style.css | 132 - doc/config/jobs | 33 - doc/config/layout_library.xml | 197 -- doc/config/layout_main.xml | 210 -- doc/config/linear_containers | 23 - doc/config/logging | 26 - doc/config/main | 41 - doc/config/mqtt | 39 - doc/config/platform | 35 - doc/config/shadow | 35 - doc/config/static_memory | 25 - doc/config/taskpool | 33 - doc/guide/building.txt | 153 - doc/guide/developer/automated_tests.txt | 166 - doc/guide/developer/developer.txt | 12 - doc/guide/developer/porting.txt | 142 - doc/guide/developer/style.txt | 302 -- doc/guide/migration.txt | 16 - doc/guide/migration/jobs.txt | 90 - doc/guide/migration/mqtt.txt | 113 - doc/guide/migration/platform.txt | 288 -- doc/guide/migration/shadow.txt | 90 - doc/lib/defender.txt | 53 - doc/lib/jobs.txt | 273 -- doc/lib/linear_containers.txt | 28 - doc/lib/logging.txt | 118 - doc/lib/mqtt.txt | 454 --- doc/lib/platform.txt | 286 -- doc/lib/shadow.txt | 186 -- doc/lib/static_memory.txt | 44 - doc/lib/taskpool.txt | 139 - doc/mainpage.txt | 229 -- doc/plantuml/RecyclableJobStatus.pu | 46 - doc/plantuml/StaticJobStatus.pu | 32 - doc/plantuml/images/RecyclableJobStatus.png | Bin 56514 -> 0 bytes doc/plantuml/images/StaticJobStatus.png | Bin 31124 -> 0 bytes doc/plantuml/images/jobs_async_detail.png | Bin 155351 -> 0 bytes doc/plantuml/images/jobs_demo.png | Bin 48193 -> 0 bytes doc/plantuml/images/jobs_sync_detail.png | Bin 152414 -> 0 bytes doc/plantuml/images/mqtt_demo.png | Bin 48978 -> 0 bytes .../images/mqtt_design_typicaloperation.png | Bin 71961 -> 0 bytes doc/plantuml/images/mqtt_memory_example.png | Bin 32407 -> 0 bytes .../images/shadow_async_opertation_detail.png | Bin 127219 -> 0 bytes doc/plantuml/images/shadow_demo.png | Bin 42958 -> 0 bytes .../images/shadow_sync_opertation_detail.png | Bin 120850 -> 0 bytes .../taskpool_design_typicaloperation.png | Bin 72591 -> 0 bytes doc/plantuml/jobs_async_detail.pu | 67 - doc/plantuml/jobs_demo.pu | 39 - doc/plantuml/jobs_sync_detail.pu | 68 - doc/plantuml/mqtt_demo.pu | 38 - doc/plantuml/mqtt_design_typicaloperation.pu | 52 - .../shadow_async_opertation_detail.pu | 76 - doc/plantuml/shadow_demo.pu | 33 - doc/plantuml/shadow_sync_opertation_detail.pu | 78 - .../taskpool_design_typicaloperation.pu | 77 - libraries/aws/common/CMakeLists.txt | 57 - libraries/aws/common/include/aws_iot.h | 305 -- .../aws/common/include/aws_iot_doc_parser.h | 69 - libraries/aws/common/lexicon.txt | 32 - libraries/aws/common/src/aws_iot_doc_parser.c | 294 -- libraries/aws/common/src/aws_iot_operation.c | 153 - libraries/aws/common/src/aws_iot_parser.c | 181 - .../aws/common/src/aws_iot_subscription.c | 164 - libraries/aws/common/src/aws_iot_validate.c | 62 - .../aws/common/test/aws_iot_tests_common.c | 52 - .../test/unit/aws_iot_tests_doc_parser.c | 317 -- libraries/aws/defender/CMakeLists.txt | 65 - .../aws/defender/include/aws_iot_defender.h | 405 --- libraries/aws/defender/lexicon.txt | 64 - .../aws/defender/src/aws_iot_defender_api.c | 597 ---- .../defender/src/aws_iot_defender_collector.c | 441 --- .../aws/defender/src/aws_iot_defender_mqtt.c | 194 -- .../src/private/aws_iot_defender_internal.h | 351 -- .../defender/test/aws_iot_tests_defender.c | 55 - .../system/aws_iot_tests_defender_system.c | 755 ----- .../test/unit/aws_iot_tests_defender_unit.c | 277 -- libraries/aws/jobs/CMakeLists.txt | 75 - libraries/aws/jobs/include/aws_iot_jobs.h | 927 ----- .../jobs/include/types/aws_iot_jobs_types.h | 1011 ------ libraries/aws/jobs/lexicon.txt | 122 - libraries/aws/jobs/src/aws_iot_jobs_api.c | 1624 --------- .../aws/jobs/src/aws_iot_jobs_operation.c | 885 ----- .../aws/jobs/src/aws_iot_jobs_serialize.c | 1211 ------- .../aws/jobs/src/aws_iot_jobs_static_memory.c | 169 - .../aws/jobs/src/aws_iot_jobs_subscription.c | 581 ---- .../jobs/src/private/aws_iot_jobs_internal.h | 628 ---- libraries/aws/jobs/test/aws_iot_tests_jobs.c | 57 - .../test/system/aws_iot_tests_jobs_system.c | 867 ----- .../jobs/test/unit/aws_iot_tests_jobs_api.c | 988 ------ .../test/unit/aws_iot_tests_jobs_serialize.c | 808 ----- libraries/aws/shadow/CMakeLists.txt | 75 - libraries/aws/shadow/include/aws_iot_shadow.h | 909 ----- .../include/types/aws_iot_shadow_types.h | 641 ---- libraries/aws/shadow/lexicon.txt | 94 - libraries/aws/shadow/src/aws_iot_shadow_api.c | 1303 -------- .../aws/shadow/src/aws_iot_shadow_operation.c | 977 ------ .../aws/shadow/src/aws_iot_shadow_parser.c | 193 -- .../shadow/src/aws_iot_shadow_static_memory.c | 160 - .../shadow/src/aws_iot_shadow_subscription.c | 512 --- .../src/private/aws_iot_shadow_internal.h | 585 ---- .../aws/shadow/test/aws_iot_tests_shadow.c | 56 - .../test/system/aws_iot_tests_shadow_system.c | 762 ----- .../test/unit/aws_iot_tests_shadow_api.c | 695 ---- .../test/unit/aws_iot_tests_shadow_parser.c | 305 -- libraries/platform/iot_clock.h | 213 -- libraries/platform/iot_metrics.h | 100 - libraries/platform/iot_network.h | 368 -- libraries/platform/iot_threads.h | 354 -- libraries/platform/lexicon.txt | 64 - libraries/platform/types/iot_platform_types.h | 202 -- libraries/standard/common/CMakeLists.txt | 128 - libraries/standard/common/include/iot_error.h | 114 - libraries/standard/common/include/iot_init.h | 64 - .../common/include/iot_linear_containers.h | 958 ------ .../standard/common/include/iot_logging.h | 226 -- .../common/include/iot_logging_setup.h | 220 -- .../common/include/iot_static_memory.h | 200 -- .../standard/common/include/iot_taskpool.h | 554 --- .../common/include/types/iot_taskpool_types.h | 300 -- libraries/standard/common/lexicon.txt | 133 - libraries/standard/common/src/iot_init.c | 147 - libraries/standard/common/src/iot_logging.c | 451 --- .../common/src/iot_static_memory_common.c | 175 - libraries/standard/common/src/iot_taskpool.c | 1853 ---------- .../common/src/iot_taskpool_static_memory.c | 171 - .../src/private/iot_taskpool_internal.h | 313 -- .../standard/common/test/iot_tests_common.c | 53 - .../common/test/unit/iot_tests_atomic.c | 324 -- .../test/unit/iot_tests_linear_containers.c | 102 - .../common/test/unit/iot_tests_taskpool.c | 1837 ---------- libraries/standard/mqtt/CMakeLists.txt | 83 - libraries/standard/mqtt/filePaths.cmake | 47 - libraries/standard/mqtt/include/iot_mqtt.h | 868 ----- .../mqtt/include/iot_mqtt_lightweight.h | 702 ---- .../standard/mqtt/include/iot_mqtt_protocol.h | 140 - libraries/standard/mqtt/include/mqtt.h | 122 + .../standard/mqtt/include/mqtt_lightweight.h | 139 + .../mqtt/include/types/iot_mqtt_types.h | 1174 ------- libraries/standard/mqtt/lexicon.txt | 207 -- libraries/standard/mqtt/src/iot_mqtt_api.c | 1988 ----------- libraries/standard/mqtt/src/iot_mqtt_helper.c | 972 ------ .../mqtt/src/iot_mqtt_lightweight_api.c | 1126 ------- .../standard/mqtt/src/iot_mqtt_network.c | 874 ----- .../standard/mqtt/src/iot_mqtt_operation.c | 1362 -------- .../standard/mqtt/src/iot_mqtt_serialize.c | 1039 ------ .../mqtt/src/iot_mqtt_static_memory.c | 203 -- .../standard/mqtt/src/iot_mqtt_subscription.c | 721 ---- .../standard/mqtt/src/iot_mqtt_validate.c | 751 ----- libraries/standard/mqtt/src/mqtt.c | 58 + .../standard/mqtt/src/mqtt_lightweight.c | 95 + .../mqtt/src/private/iot_mqtt_helper.h | 249 -- .../mqtt/src/private/iot_mqtt_internal.h | 1025 ------ .../mqtt/test/access/iot_test_access_mqtt.h | 108 - .../test/access/iot_test_access_mqtt_api.c | 54 - .../access/iot_test_access_mqtt_network.c | 43 - .../iot_test_access_mqtt_subscription.c | 54 - libraries/standard/mqtt/test/iot_tests_mqtt.c | 60 - .../mqtt/test/mock/iot_tests_mqtt_mock.c | 414 --- .../mqtt/test/mock/iot_tests_mqtt_mock.h | 63 - .../mqtt/test/system/iot_tests_mqtt_system.c | 1093 ------ .../mqtt/test/unit/iot_tests_mqtt_api.c | 2968 ----------------- .../mqtt/test/unit/iot_tests_mqtt_platform.c | 1482 -------- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 1905 ----------- .../test/unit/iot_tests_mqtt_subscription.c | 873 ----- .../mqtt/test/unit/iot_tests_mqtt_validate.c | 428 --- libraries/standard/serializer/CMakeLists.txt | 55 - .../serializer/include/iot_serializer.h | 545 --- libraries/standard/serializer/lexicon.txt | 40 - .../cbor/iot_serializer_tinycbor_decoder.c | 599 ---- .../cbor/iot_serializer_tinycbor_encoder.c | 335 -- .../src/iot_serializer_static_memory.c | 229 -- .../serializer/test/iot_tests_serializer.c | 53 - .../test/unit/iot_tests_serializer_cbor.c | 687 ---- ports/README.md | 19 - ports/common/include/atomic/iot_atomic_gcc.h | 253 -- .../include/atomic/iot_atomic_generic.h | 359 -- ports/common/include/iot_atomic.h | 79 - ports/common/include/iot_network_mbedtls.h | 172 - ports/common/include/iot_network_metrics.h | 53 - ports/common/include/iot_network_openssl.h | 180 - ports/common/lexicon.txt | 64 - ports/common/src/iot_network_mbedtls.c | 1232 ------- ports/common/src/iot_network_metrics.c | 416 --- ports/common/src/iot_network_openssl.c | 1084 ------ .../posix/include/iot_platform_types_posix.h | 88 - ports/posix/lexicon.txt | 21 - ports/posix/posix.cmake | 93 - ports/posix/src/iot_clock_posix.c | 349 -- ports/posix/src/iot_threads_posix.c | 559 ---- .../include/iot_platform_types_template.h | 60 - ports/template/lexicon.txt | 29 - ports/template/src/iot_clock_template.c | 111 - ports/template/src/iot_threads_template.c | 177 - ports/template/template.cmake | 13 - scripts/ablexicon | 96 - scripts/ci_test_build.sh | 25 - scripts/ci_test_common.sh | 26 - scripts/ci_test_coverage.sh | 51 - scripts/ci_test_defender.sh | 6 - scripts/ci_test_doc.sh | 73 - scripts/ci_test_jobs.sh | 72 - scripts/ci_test_mqtt.sh | 48 - scripts/ci_test_quality.sh | 11 - scripts/ci_test_serializer.sh | 26 - scripts/ci_test_shadow.sh | 45 - scripts/ci_test_spelling.sh | 38 - scripts/coverity_misra.config | 46 - scripts/extract-comments | 41 - scripts/find-unknown-comment-words | 143 - scripts/setup/ci_setup_linux.sh | 52 - scripts/uncrustify.cfg | 160 - tests/README.md | 7 - tests/iot_config.h | 270 -- tests/iot_tests.c | 197 -- tests/lexicon.txt | 15 - third_party/mbedtls/CMakeLists.txt | 98 - third_party/mbedtls/iot_config_mbedtls.h | 137 - third_party/mbedtls/mbedtls | 1 - third_party/mbedtls/threading_alt.h | 42 - third_party/tinycbor/CMakeLists.txt | 62 - third_party/tinycbor/tinycbor | 1 - third_party/unity/CMakeLists.txt | 35 - .../unity/unity/fixture/unity_fixture.c | 472 --- .../unity/unity/fixture/unity_fixture.h | 83 - .../unity/fixture/unity_fixture_internals.h | 51 - .../fixture/unity_fixture_malloc_overrides.h | 55 - .../unity/unity/fixture/unity_memory_mt.c | 83 - third_party/unity/unity/unity.c | 1570 --------- third_party/unity/unity/unity.h | 503 --- third_party/unity/unity/unity_internals.h | 872 ----- 337 files changed, 436 insertions(+), 81478 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitmodules delete mode 100644 .travis.yml delete mode 100644 CMakeLists.txt delete mode 100644 cbmc/.gitignore delete mode 100644 cbmc/README.md delete mode 100644 cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch delete mode 100644 cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch delete mode 100644 cbmc/patches/README.md delete mode 100644 cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c delete mode 100644 cbmc/proofs/IotMqtt_Cleanup/Makefile delete mode 100644 cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_DeserializeConnack/Makefile delete mode 100644 cbmc/proofs/IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c delete mode 100644 cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_DeserializePingresp/Makefile delete mode 100644 cbmc/proofs/IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c delete mode 100644 cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_DeserializePuback/Makefile delete mode 100644 cbmc/proofs/IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c delete mode 100644 cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_DeserializePublish/Makefile delete mode 100644 cbmc/proofs/IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c delete mode 100644 cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_DeserializeSuback/Makefile delete mode 100644 cbmc/proofs/IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c delete mode 100644 cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile delete mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c delete mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c delete mode 100644 cbmc/proofs/IotMqtt_Init/Makefile delete mode 100644 cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_Init/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c delete mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/Makefile delete mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c delete mode 100644 cbmc/proofs/IotMqtt_OperationType/Makefile delete mode 100644 cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c delete mode 100644 cbmc/proofs/IotMqtt_PublishAsync/Makefile delete mode 100644 cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c delete mode 100644 cbmc/proofs/IotMqtt_PublishSync/Makefile delete mode 100644 cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c delete mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/Makefile delete mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c delete mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/Makefile delete mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/README.md delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c delete mode 100644 cbmc/proofs/IotMqtt_Wait/Makefile delete mode 100644 cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json delete mode 100644 cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c delete mode 100644 cbmc/proofs/IotMqtt_strerror/Makefile delete mode 100644 cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml delete mode 100644 cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json delete mode 100644 cbmc/proofs/Makefile.common delete mode 100644 cbmc/proofs/make_cbmc_batch_files.py delete mode 100644 cbmc/proofs/mqtt_state.c delete mode 100644 cbmc/proofs/mqtt_state.h delete mode 100755 cbmc/proofs/prepare.py delete mode 100644 demos/CMakeLists.txt create mode 100644 demos/Makefile delete mode 100644 demos/README.md delete mode 100644 demos/app/iot_demo.c delete mode 100644 demos/app/iot_demo_arguments.c delete mode 100644 demos/include/iot_demo_arguments.h delete mode 100644 demos/include/iot_demo_logging.h delete mode 100644 demos/iot_config.h delete mode 100644 demos/lexicon.txt create mode 100644 demos/mqtt_demo.c delete mode 100644 demos/src/aws_iot_demo_defender.c delete mode 100644 demos/src/aws_iot_demo_jobs.c delete mode 100644 demos/src/aws_iot_demo_shadow.c delete mode 100644 demos/src/iot_demo_mqtt.c delete mode 100644 doc/config/common delete mode 100644 doc/config/defender delete mode 100644 doc/config/html/footer.html delete mode 100644 doc/config/html/header.html delete mode 100644 doc/config/html/style.css delete mode 100644 doc/config/jobs delete mode 100644 doc/config/layout_library.xml delete mode 100644 doc/config/layout_main.xml delete mode 100644 doc/config/linear_containers delete mode 100644 doc/config/logging delete mode 100644 doc/config/main delete mode 100644 doc/config/mqtt delete mode 100644 doc/config/platform delete mode 100644 doc/config/shadow delete mode 100644 doc/config/static_memory delete mode 100644 doc/config/taskpool delete mode 100644 doc/guide/building.txt delete mode 100644 doc/guide/developer/automated_tests.txt delete mode 100644 doc/guide/developer/developer.txt delete mode 100644 doc/guide/developer/porting.txt delete mode 100644 doc/guide/developer/style.txt delete mode 100644 doc/guide/migration.txt delete mode 100644 doc/guide/migration/jobs.txt delete mode 100644 doc/guide/migration/mqtt.txt delete mode 100644 doc/guide/migration/platform.txt delete mode 100644 doc/guide/migration/shadow.txt delete mode 100644 doc/lib/defender.txt delete mode 100644 doc/lib/jobs.txt delete mode 100644 doc/lib/linear_containers.txt delete mode 100644 doc/lib/logging.txt delete mode 100644 doc/lib/mqtt.txt delete mode 100644 doc/lib/platform.txt delete mode 100644 doc/lib/shadow.txt delete mode 100644 doc/lib/static_memory.txt delete mode 100644 doc/lib/taskpool.txt delete mode 100644 doc/mainpage.txt delete mode 100644 doc/plantuml/RecyclableJobStatus.pu delete mode 100644 doc/plantuml/StaticJobStatus.pu delete mode 100644 doc/plantuml/images/RecyclableJobStatus.png delete mode 100644 doc/plantuml/images/StaticJobStatus.png delete mode 100644 doc/plantuml/images/jobs_async_detail.png delete mode 100644 doc/plantuml/images/jobs_demo.png delete mode 100644 doc/plantuml/images/jobs_sync_detail.png delete mode 100644 doc/plantuml/images/mqtt_demo.png delete mode 100644 doc/plantuml/images/mqtt_design_typicaloperation.png delete mode 100644 doc/plantuml/images/mqtt_memory_example.png delete mode 100644 doc/plantuml/images/shadow_async_opertation_detail.png delete mode 100644 doc/plantuml/images/shadow_demo.png delete mode 100644 doc/plantuml/images/shadow_sync_opertation_detail.png delete mode 100644 doc/plantuml/images/taskpool_design_typicaloperation.png delete mode 100644 doc/plantuml/jobs_async_detail.pu delete mode 100644 doc/plantuml/jobs_demo.pu delete mode 100644 doc/plantuml/jobs_sync_detail.pu delete mode 100644 doc/plantuml/mqtt_demo.pu delete mode 100644 doc/plantuml/mqtt_design_typicaloperation.pu delete mode 100644 doc/plantuml/shadow_async_opertation_detail.pu delete mode 100644 doc/plantuml/shadow_demo.pu delete mode 100644 doc/plantuml/shadow_sync_opertation_detail.pu delete mode 100644 doc/plantuml/taskpool_design_typicaloperation.pu delete mode 100644 libraries/aws/common/CMakeLists.txt delete mode 100644 libraries/aws/common/include/aws_iot.h delete mode 100644 libraries/aws/common/include/aws_iot_doc_parser.h delete mode 100644 libraries/aws/common/lexicon.txt delete mode 100644 libraries/aws/common/src/aws_iot_doc_parser.c delete mode 100644 libraries/aws/common/src/aws_iot_operation.c delete mode 100644 libraries/aws/common/src/aws_iot_parser.c delete mode 100644 libraries/aws/common/src/aws_iot_subscription.c delete mode 100644 libraries/aws/common/src/aws_iot_validate.c delete mode 100644 libraries/aws/common/test/aws_iot_tests_common.c delete mode 100644 libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c delete mode 100644 libraries/aws/defender/CMakeLists.txt delete mode 100644 libraries/aws/defender/include/aws_iot_defender.h delete mode 100644 libraries/aws/defender/lexicon.txt delete mode 100644 libraries/aws/defender/src/aws_iot_defender_api.c delete mode 100644 libraries/aws/defender/src/aws_iot_defender_collector.c delete mode 100644 libraries/aws/defender/src/aws_iot_defender_mqtt.c delete mode 100644 libraries/aws/defender/src/private/aws_iot_defender_internal.h delete mode 100644 libraries/aws/defender/test/aws_iot_tests_defender.c delete mode 100644 libraries/aws/defender/test/system/aws_iot_tests_defender_system.c delete mode 100644 libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c delete mode 100644 libraries/aws/jobs/CMakeLists.txt delete mode 100644 libraries/aws/jobs/include/aws_iot_jobs.h delete mode 100644 libraries/aws/jobs/include/types/aws_iot_jobs_types.h delete mode 100644 libraries/aws/jobs/lexicon.txt delete mode 100644 libraries/aws/jobs/src/aws_iot_jobs_api.c delete mode 100644 libraries/aws/jobs/src/aws_iot_jobs_operation.c delete mode 100644 libraries/aws/jobs/src/aws_iot_jobs_serialize.c delete mode 100644 libraries/aws/jobs/src/aws_iot_jobs_static_memory.c delete mode 100644 libraries/aws/jobs/src/aws_iot_jobs_subscription.c delete mode 100644 libraries/aws/jobs/src/private/aws_iot_jobs_internal.h delete mode 100644 libraries/aws/jobs/test/aws_iot_tests_jobs.c delete mode 100644 libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c delete mode 100644 libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c delete mode 100644 libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c delete mode 100644 libraries/aws/shadow/CMakeLists.txt delete mode 100644 libraries/aws/shadow/include/aws_iot_shadow.h delete mode 100644 libraries/aws/shadow/include/types/aws_iot_shadow_types.h delete mode 100644 libraries/aws/shadow/lexicon.txt delete mode 100644 libraries/aws/shadow/src/aws_iot_shadow_api.c delete mode 100644 libraries/aws/shadow/src/aws_iot_shadow_operation.c delete mode 100644 libraries/aws/shadow/src/aws_iot_shadow_parser.c delete mode 100644 libraries/aws/shadow/src/aws_iot_shadow_static_memory.c delete mode 100644 libraries/aws/shadow/src/aws_iot_shadow_subscription.c delete mode 100644 libraries/aws/shadow/src/private/aws_iot_shadow_internal.h delete mode 100644 libraries/aws/shadow/test/aws_iot_tests_shadow.c delete mode 100644 libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c delete mode 100644 libraries/aws/shadow/test/unit/aws_iot_tests_shadow_api.c delete mode 100644 libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c delete mode 100644 libraries/platform/iot_clock.h delete mode 100644 libraries/platform/iot_metrics.h delete mode 100644 libraries/platform/iot_network.h delete mode 100644 libraries/platform/iot_threads.h delete mode 100644 libraries/platform/lexicon.txt delete mode 100644 libraries/platform/types/iot_platform_types.h delete mode 100644 libraries/standard/common/CMakeLists.txt delete mode 100644 libraries/standard/common/include/iot_error.h delete mode 100644 libraries/standard/common/include/iot_init.h delete mode 100644 libraries/standard/common/include/iot_linear_containers.h delete mode 100644 libraries/standard/common/include/iot_logging.h delete mode 100644 libraries/standard/common/include/iot_logging_setup.h delete mode 100644 libraries/standard/common/include/iot_static_memory.h delete mode 100644 libraries/standard/common/include/iot_taskpool.h delete mode 100644 libraries/standard/common/include/types/iot_taskpool_types.h delete mode 100644 libraries/standard/common/lexicon.txt delete mode 100644 libraries/standard/common/src/iot_init.c delete mode 100644 libraries/standard/common/src/iot_logging.c delete mode 100644 libraries/standard/common/src/iot_static_memory_common.c delete mode 100644 libraries/standard/common/src/iot_taskpool.c delete mode 100644 libraries/standard/common/src/iot_taskpool_static_memory.c delete mode 100644 libraries/standard/common/src/private/iot_taskpool_internal.h delete mode 100644 libraries/standard/common/test/iot_tests_common.c delete mode 100644 libraries/standard/common/test/unit/iot_tests_atomic.c delete mode 100644 libraries/standard/common/test/unit/iot_tests_linear_containers.c delete mode 100644 libraries/standard/common/test/unit/iot_tests_taskpool.c delete mode 100644 libraries/standard/mqtt/CMakeLists.txt delete mode 100644 libraries/standard/mqtt/filePaths.cmake delete mode 100644 libraries/standard/mqtt/include/iot_mqtt.h delete mode 100644 libraries/standard/mqtt/include/iot_mqtt_lightweight.h delete mode 100644 libraries/standard/mqtt/include/iot_mqtt_protocol.h create mode 100644 libraries/standard/mqtt/include/mqtt.h create mode 100644 libraries/standard/mqtt/include/mqtt_lightweight.h delete mode 100644 libraries/standard/mqtt/include/types/iot_mqtt_types.h delete mode 100644 libraries/standard/mqtt/lexicon.txt delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_api.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_helper.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_network.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_operation.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_serialize.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_static_memory.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_subscription.c delete mode 100644 libraries/standard/mqtt/src/iot_mqtt_validate.c create mode 100644 libraries/standard/mqtt/src/mqtt.c create mode 100644 libraries/standard/mqtt/src/mqtt_lightweight.c delete mode 100644 libraries/standard/mqtt/src/private/iot_mqtt_helper.h delete mode 100644 libraries/standard/mqtt/src/private/iot_mqtt_internal.h delete mode 100644 libraries/standard/mqtt/test/access/iot_test_access_mqtt.h delete mode 100644 libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c delete mode 100644 libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c delete mode 100644 libraries/standard/mqtt/test/access/iot_test_access_mqtt_subscription.c delete mode 100644 libraries/standard/mqtt/test/iot_tests_mqtt.c delete mode 100644 libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c delete mode 100644 libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.h delete mode 100644 libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c delete mode 100644 libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c delete mode 100644 libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c delete mode 100644 libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c delete mode 100644 libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c delete mode 100644 libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c delete mode 100644 libraries/standard/serializer/CMakeLists.txt delete mode 100644 libraries/standard/serializer/include/iot_serializer.h delete mode 100644 libraries/standard/serializer/lexicon.txt delete mode 100644 libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c delete mode 100644 libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c delete mode 100644 libraries/standard/serializer/src/iot_serializer_static_memory.c delete mode 100644 libraries/standard/serializer/test/iot_tests_serializer.c delete mode 100644 libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c delete mode 100644 ports/README.md delete mode 100644 ports/common/include/atomic/iot_atomic_gcc.h delete mode 100644 ports/common/include/atomic/iot_atomic_generic.h delete mode 100644 ports/common/include/iot_atomic.h delete mode 100644 ports/common/include/iot_network_mbedtls.h delete mode 100644 ports/common/include/iot_network_metrics.h delete mode 100644 ports/common/include/iot_network_openssl.h delete mode 100644 ports/common/lexicon.txt delete mode 100644 ports/common/src/iot_network_mbedtls.c delete mode 100644 ports/common/src/iot_network_metrics.c delete mode 100644 ports/common/src/iot_network_openssl.c delete mode 100644 ports/posix/include/iot_platform_types_posix.h delete mode 100644 ports/posix/lexicon.txt delete mode 100644 ports/posix/posix.cmake delete mode 100644 ports/posix/src/iot_clock_posix.c delete mode 100644 ports/posix/src/iot_threads_posix.c delete mode 100644 ports/template/include/iot_platform_types_template.h delete mode 100644 ports/template/lexicon.txt delete mode 100644 ports/template/src/iot_clock_template.c delete mode 100644 ports/template/src/iot_threads_template.c delete mode 100644 ports/template/template.cmake delete mode 100755 scripts/ablexicon delete mode 100755 scripts/ci_test_build.sh delete mode 100755 scripts/ci_test_common.sh delete mode 100755 scripts/ci_test_coverage.sh delete mode 100755 scripts/ci_test_defender.sh delete mode 100755 scripts/ci_test_doc.sh delete mode 100755 scripts/ci_test_jobs.sh delete mode 100755 scripts/ci_test_mqtt.sh delete mode 100755 scripts/ci_test_quality.sh delete mode 100644 scripts/ci_test_serializer.sh delete mode 100755 scripts/ci_test_shadow.sh delete mode 100755 scripts/ci_test_spelling.sh delete mode 100644 scripts/coverity_misra.config delete mode 100755 scripts/extract-comments delete mode 100755 scripts/find-unknown-comment-words delete mode 100755 scripts/setup/ci_setup_linux.sh delete mode 100644 scripts/uncrustify.cfg delete mode 100644 tests/README.md delete mode 100644 tests/iot_config.h delete mode 100644 tests/iot_tests.c delete mode 100644 tests/lexicon.txt delete mode 100644 third_party/mbedtls/CMakeLists.txt delete mode 100644 third_party/mbedtls/iot_config_mbedtls.h delete mode 160000 third_party/mbedtls/mbedtls delete mode 100644 third_party/mbedtls/threading_alt.h delete mode 100644 third_party/tinycbor/CMakeLists.txt delete mode 160000 third_party/tinycbor/tinycbor delete mode 100644 third_party/unity/CMakeLists.txt delete mode 100644 third_party/unity/unity/fixture/unity_fixture.c delete mode 100644 third_party/unity/unity/fixture/unity_fixture.h delete mode 100644 third_party/unity/unity/fixture/unity_fixture_internals.h delete mode 100644 third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h delete mode 100644 third_party/unity/unity/fixture/unity_memory_mt.c delete mode 100644 third_party/unity/unity/unity.c delete mode 100644 third_party/unity/unity/unity.h delete mode 100644 third_party/unity/unity/unity_internals.h diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3ca54d3698..0000000000 --- a/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Ignore documentation output. -doc/output/* -doc/tag/* - -# Ignore CMake build directory. -build/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 5c06a58594..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,7 +0,0 @@ -[submodule "third_party/tinycbor/tinycbor"] - path = third_party/tinycbor/tinycbor - url = https://github.com/intel/tinycbor.git -[submodule "third_party/mbedtls/mbedtls"] - path = third_party/mbedtls/mbedtls - url = https://github.com/ARMmbed/mbedtls.git - branch = mbedtls-2.16.5 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9dcd5764d0..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,72 +0,0 @@ -# Only run on the v4 beta branch for now. -branches: - only: - - v4_beta - -# Build on Ubuntu 16.04 by default. -os: linux -dist: xenial - -# Use clang compiler (seems to provide more warnings than gcc). -language: c -compiler: - - clang - -# Matrix of tests to run. -jobs: - include: - # Code checks. - - stage: Code checks - # Build checks for pull requests. - if: type = pull_request - env: RUN_TEST=build - # Code quality checks for pull requests. - - if: type = pull_request - env: RUN_TEST=quality - # Documentation check for pull requests. - - if: type = pull_request - env: RUN_TEST=doc - # Spelling check for pull requests. - - if: type = pull_request - env: RUN_TEST=spelling - - # Library tests. - - stage: Tests - env: RUN_TEST=common - env: RUN_TEST=serializer - - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - - env: RUN_TEST=mqtt NETWORK_STACK=openssl - - env: RUN_TEST=shadow - - env: RUN_TEST=jobs - - if: type = push - compiler: gcc - env: RUN_TEST=coverage - -# Install dependencies. -install: - # Run the OS-specific CI setup script. - - source ./scripts/setup/ci_setup_$TRAVIS_OS_NAME.sh - -# Run the test script based on matrix environment variable. -script: - # Set identifier (client identifier OR Thing Name). - - export IOT_IDENTIFIER="$IOT_IDENTIFIER_PREFIX$RUN_TEST" - - if [ "$NETWORK_STACK" = "openssl" ]; then export IOT_IDENTIFIER="${IOT_IDENTIFIER}ossl"; fi - # Choose the network abstraction. - - if [ "$NETWORK_STACK" = "openssl" ]; then export IOT_NETWORK_USE_OPENSSL=1; else export IOT_NETWORK_USE_OPENSSL=0; fi - # Get AWS credentials when not a pull request build. - - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then mkdir credentials; fi - - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O credentials/AmazonRootCA1.pem; fi - - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then echo -e $AWS_IOT_CLIENT_CERT > credentials/clientCert.pem; fi - - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then echo -e $AWS_IOT_PRIVATE_KEY > credentials/privateKey.pem; fi - # Set credential defines passed to CMake when not a pull request build. - - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then export AWS_IOT_CREDENTIAL_DEFINES="-DIOT_TEST_SERVER=\"\\\"$AWS_IOT_ENDPOINT\\\"\" -DIOT_TEST_PORT=443 -DIOT_TEST_ROOT_CA=\"\\\"../credentials/AmazonRootCA1.pem\\\"\" -DIOT_TEST_CLIENT_CERT=\"\\\"../credentials/clientCert.pem\\\"\" -DIOT_TEST_PRIVATE_KEY=\"\\\"../credentials/privateKey.pem\\\"\""; fi - # Create build directory. - - mkdir build - - cd build - # Run test script. - - bash ../scripts/ci_test_$RUN_TEST.sh - -after_success: - # Submit coverage results. - - if [ "$RUN_TEST" = "coverage" ]; then bash <(curl -s https://codecov.io/bash) -f coverage.info ; fi diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 630d7d891f..0000000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,117 +0,0 @@ -# Project information. -cmake_minimum_required( VERSION 3.5.0 ) -project( AwsIotDeviceSdkEmbeddedC - VERSION 4.0.0 - LANGUAGES C ) - -# Allow the project to be organized into folders. -set_property( GLOBAL PROPERTY USE_FOLDERS ON ) - -# Use C99. -set( CMAKE_C_STANDARD 99 ) -set( CMAKE_C_STANDARD_REQUIRED ON ) - -# Do not allow in-source build. -if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) - message( FATAL_ERROR "In-source build is not allowed. Please build in a separate directory, such as ${PROJECT_SOURCE_DIR}/build." ) -endif() - -# Configure options to always show in CMake GUI. -option( IOT_ATOMIC_USE_PORT - "Set this to ON to use a custom atomic port. When OFF, the build system will choose an atomic port." - OFF ) -option( IOT_BUILD_TESTS - "Set this to ON to build both demo and test executables. When OFF, only demo executables are built." - OFF ) -option( IOT_BUILD_CLONE_SUBMODULES - "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." - ON ) - -# Unity test framework does not export the correct symbols for DLLs. -set( ALLOW_SHARED_LIBRARIES ON ) - -include( CMakeDependentOption ) -CMAKE_DEPENDENT_OPTION( BUILD_SHARED_LIBS - "Set this to ON to build all libraries as shared libraries. When OFF, libraries build as static libraries." - ON "${ALLOW_SHARED_LIBRARIES}" - OFF ) - -# Set the platform named based on the host OS if not defined. -if( NOT DEFINED IOT_PLATFORM_NAME ) - if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - set( IOT_PLATFORM_NAME "posix" CACHE STRING "Port to use for building the SDK." ) - - # Provide an option to use the OpenSSL network abstraction on Linux. - option( IOT_NETWORK_USE_OPENSSL - "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." - OFF ) - else() - message( FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not a supported platform." ) - endif() -endif() - -# Validate the platform name. -if( NOT DEFINED IOT_PLATFORM_NAME ) - message( FATAL_ERROR "IOT_PLATFORM_NAME was not set and could not be automatically determined." ) -endif() - -if( NOT EXISTS ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME} ) - message( FATAL_ERROR "A port for ${IOT_PLATFORM_NAME} does not exist." ) -endif() - -# Set output directories. -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/bin ) -set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib ) -set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib ) - -# Set the path to the config header. -if( ${IOT_BUILD_TESTS} ) - set( CONFIG_HEADER_PATH ${PROJECT_SOURCE_DIR}/tests ) - set( IOT_TEST_APP_SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) -else() - set( CONFIG_HEADER_PATH ${PROJECT_SOURCE_DIR}/demos ) -endif() - -# Build Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - add_subdirectory( third_party/unity ) -endif() - -# Build the demos. -add_subdirectory( demos ) - -# Common libraries and platform port. This creates the iotbase library target. -add_subdirectory( libraries/standard/common ) - -# MQTT library. -add_subdirectory( libraries/standard/mqtt ) - -# Serializer library. -add_subdirectory( libraries/standard/serializer ) - -# AWS IoT common libraries. -add_subdirectory( libraries/aws/common ) - -# AWS IoT Shadow library. -add_subdirectory( libraries/aws/shadow ) - -# AWS IoT Jobs library. -add_subdirectory( libraries/aws/jobs ) - -# AWS IoT Device Defender library. -add_subdirectory( libraries/aws/defender ) - -# TinyCBOR library (third-party). -add_subdirectory( third_party/tinycbor ) - -# mbed TLS library (third-party). This is only needed on ports using mbed TLS. -if( ${MBEDTLS_REQUIRED} ) - add_subdirectory( third_party/mbedtls ) -endif() - -# Set startup projects in Visual Studio. -if( ${IOT_BUILD_TESTS} ) - set_property( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT iot_tests_mqtt ) -else() - set_property( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT iot_demo_mqtt ) -endif() diff --git a/README.md b/README.md index 7342c9abad..ecf191c4ec 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,4 @@ # AWS IoT Device SDK C v4.0.0 -**[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/index.html)** - -[![Build Status](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C.svg?branch=v4_beta)](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) -[![codecov](https://codecov.io/gh/aws/aws-iot-device-sdk-embedded-C/branch/v4_beta/graph/badge.svg)](https://codecov.io/gh/aws/aws-iot-device-sdk-embedded-C) - -The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be built into firmware along with application code. - -This library supersedes both the AWS IoT Device SDK Embedded C and the libraries provided with FreeRTOS. - -## Features - -This library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with FreeRTOS. In addition, it provides the following new features: -- Asynchronous API for both MQTT and Thing Shadow. -- Multithreaded API by default (removed the `yield` function). -- More efficient platform layer (especially timers). -- Complete separation of MQTT and network stack, allowing MQTT to run over any network stack. -- Configurable memory allocation (static-only or dynamic). Memory allocation functions may also be set by the user. -- Network stack based on OpenSSL when building for Linux. -- MQTT persistent session support. -- Device Defender library. - -Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features were removed: -- Auto-reconnect for MQTT connections. -- Shadow JSON document generator. - -## Building and Running Demos - -**Main documentation page:** [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) - -This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. This repo contains a ready-to-use port for Linux. - -### Prerequisites -- CMake 3.5.0 or later and a C99 compiler. -- A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. For reference, the version used by this repo's Travis CI builds are listed in parentheses. - - Linux system with POSIX thread and timer APIs. (CI tests on Ubuntu 16.04).
- On Linux systems, the OpenSSL network implementation may be used instead of the default network implementation based on mbed TLS. This requires the installation of OpenSSL development libraries and header files, version 1.0.2g or later. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. - -### Build Steps -1. Clone the source code and submodules. This SDK uses third-party libraries as submodules in the `third_party` directory. - - If the source code is downloaded via `git clone`, nothing further needs to be done. The CMake build system can automatically clone submodules in this case. - - For any other download, the submodules must be downloaded and placed in their respective `third_party` directory. - - [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17) → `third_party/mbedtls/mbedtls` - - [tinyCBOR](https://github.com/intel/tinycbor) → `third_party/tinycbor/tinycbor` -2. Complete the first 6 steps in the guide [Getting Started with AWS IoT](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html). The guide mentions the AWS IoT Button, but you do not need one to use this SDK. - 1. [Sign in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) - 2. [Register a Device in the Registry](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html) - 3. [Create and Activate a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) - 4. [Create an AWS IoT Policy](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-policy.html) - 5. [Attach an AWS IoT Policy to a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/attach-policy-to-certificate.html) - 6. [Attach a Certificate to a Thing](https://docs.aws.amazon.com/iot/latest/developerguide/attach-cert-thing.html) -3. *Optional:* Set the following `#define` in [iot_config.h](demos/iot_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. - - Set `IOT_DEMO_IDENTIFIER` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. - - Set `IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. - - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. - - Set `IOT_DEMO_USER_NAME` to the username string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-m`. - - Set `IOT_DEMO_PASSWORD` to the password string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-w`. -4. Make a build directory in the SDK's root directory and `cd` into it. - ```sh - mkdir build - cd build - ``` -5. Run CMake from the build directory. - ```sh - cmake .. - ``` - CMake will generate a project based on the detected operating system. On Linux, the default project is a Makefile. To build the SDK with this Makefile, run `make`. The resulting binaries (the demo executables) and libraries will be placed in the `build/output` directory. - - You may also use CMake GUI. Specify the SDK's root directory as the source directory and the build directory created in step 4 as the build directory in CMake GUI. - -See the documentation page [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) for a list of options that can be used to configure the build system. - -## Porting the SDK - -Please refer to the [Porting Guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions on porting this SDK. - -Existing ports (which may be used as examples) are present in `ports`. A blank template for implementing new ports is in `ports/template`. - -## License - -This library is licensed under the [MIT License](LICENSE). +## Development branch +This branch currently hosts development of the next iteration of the AWS IoT Embedded C SDK version 4. It is currently a work in progress and should not be used to create any products. We will update this README when that status changes. diff --git a/cbmc/.gitignore b/cbmc/.gitignore deleted file mode 100644 index 031b1db6c9..0000000000 --- a/cbmc/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.csv -*.log diff --git a/cbmc/README.md b/cbmc/README.md deleted file mode 100644 index ed527dd78b..0000000000 --- a/cbmc/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# MQTT memory safety proofs - -This directory contains CBMC proofs of memory safety of MQTT entry points. [CBMC](http://www.cprover.org/cbmc/) is a bounded model checker for C available from the GitHub [repository](https://github.com/diffblue/cbmc). Each proof is in a separate subdirectory of proofs: - -* DeserializeConnack: _IotMqtt_DeserializeConnack is memory safe assuming: - * We abstract the IotLog_Generic logging function -* DeserializePingresp: _IotMqtt_DeserializePingresp is memory safe assuming: - * We abstract the IotLog_Generic logging function -* DeserializePuback: _IotMqtt_DeserializePuback is memory safe assuming: - * We abstract the IotLog_Generic logging function -* DeserializePublish: _IotMqtt_DeserializePublish is memory safe assuming: - * We abstract the IotLog_Generic logging function -* DeserializeSuback: _IotMqtt_DeserializeSuback is memory safe assuming: - * We abstract the IotLog_Generic logging function - * We abstract the _IotMqtt_RemoveSubscriptionByPacket function - * We bound the length of the buffer being parsed -* DeserializeUnsuback: _IotMqtt_DeserializeUnsuback is memory safe assuming: - * We abstract the IotLog_Generic logging function diff --git a/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch b/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch deleted file mode 100644 index 063f46309e..0000000000 --- a/cbmc/patches/0001-Modify-MQTT-to-let-CBMC-proofs-stub-out-RemoveAllMat.patch +++ /dev/null @@ -1,37 +0,0 @@ -From a1455991c02aa6d7db1d4333e2cdc5add392c105 Mon Sep 17 00:00:00 2001 -From: Mark R Tuttle -Date: Thu, 13 Feb 2020 17:39:51 +0000 -Subject: [PATCH] Modify MQTT to let CBMC proofs stub out RemoveAllMatches - ---- - libraries/standard/common/include/iot_linear_containers.h | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h -index 689ea5e..ffb3469 100644 ---- a/libraries/standard/common/include/iot_linear_containers.h -+++ b/libraries/standard/common/include/iot_linear_containers.h -@@ -719,6 +719,13 @@ static inline IotLink_t * IotListDouble_RemoveFirstMatch( const IotListDouble_t - * or its value is `0`. - */ - /* @[declare_linear_containers_list_double_removeallmatches] */ -+#ifdef CBMC_STUB_REMOVEALLMATCHES -+void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, -+ bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), -+ void * pMatch, -+ void ( *freeElement )( void * pData ), -+ size_t linkOffset ); -+#else - static inline void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), - void * pMatch, -@@ -754,6 +761,7 @@ static inline void IotListDouble_RemoveAllMatches( const IotListDouble_t * const - } - } while( pMatchedElement != NULL ); - } -+#endif - - /** - * @brief Create a new queue. --- -2.7.4 diff --git a/cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch b/cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch deleted file mode 100644 index e652d56664..0000000000 --- a/cbmc/patches/0001-Patch-MQTT-library-code-for-CBMC-Unsubscribe-proofs.patch +++ /dev/null @@ -1,87 +0,0 @@ -From 60ea7452b4097d9682b67e75d7b90bbc2092544e Mon Sep 17 00:00:00 2001 -From: Mark R Tuttle -Date: Mon, 30 Mar 2020 20:15:29 +0000 -Subject: [PATCH] Patch MQTT library code for CBMC Unsubscribe proofs - ---- - libraries/standard/common/include/iot_linear_containers.h | 5 +++++ - libraries/standard/mqtt/include/iot_mqtt_protocol.h | 2 ++ - libraries/standard/mqtt/src/iot_mqtt_operation.c | 10 ++++++++++ - libraries/standard/mqtt/src/private/iot_mqtt_internal.h | 2 ++ - 4 files changed, 19 insertions(+) - -diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h -index 689ea5e..841e4a0 100644 ---- a/libraries/standard/common/include/iot_linear_containers.h -+++ b/libraries/standard/common/include/iot_linear_containers.h -@@ -639,6 +639,11 @@ static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * - /* Iterate through the list to search for matches. */ - while( pCurrent != pList ) - { -+#ifdef CBMC_FINDFIRSTMATCH_MATCH_IS_NONNULL -+ __CPROVER_assert(isMatch != NULL, "FindFirstMatch called with match predicate"); -+ __CPROVER_assume(isMatch != NULL); -+#endif -+ - /* Call isMatch if provided. Otherwise, compare pointers. */ - if( isMatch != NULL ) - { -diff --git a/libraries/standard/mqtt/include/iot_mqtt_protocol.h b/libraries/standard/mqtt/include/iot_mqtt_protocol.h -index 0fedcea..ef9f8c0 100644 ---- a/libraries/standard/mqtt/include/iot_mqtt_protocol.h -+++ b/libraries/standard/mqtt/include/iot_mqtt_protocol.h -@@ -97,7 +97,9 @@ - * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT - * packet is this value. - */ -+#ifndef MQTT_MAX_REMAINING_LENGTH - #define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) -+#endif - - /** - * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. -diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c -index 5afce54..0e4d923 100644 ---- a/libraries/standard/mqtt/src/iot_mqtt_operation.c -+++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c -@@ -638,6 +638,10 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, - if( status == IOT_MQTT_SUCCESS ) - { - status = _initializeOperation( pMqttConnection, pOperation, flags, pCallbackInfo ); -+#ifdef CBMC_CREATEOPERATION_INITIALIZE_CANT_FAIL -+ __CPROVER_assert(status == IOT_MQTT_SUCCESS, -+ "An async _initializeOperation can't fail (unwaitable)"); -+#endif - } - - if( status == IOT_MQTT_SUCCESS ) -@@ -661,6 +665,12 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); - } - -+#ifdef CBMC_CREATEOPERATION_UNSUCCESSFUL_STATUS_MEANS_NULL_POINTER -+ __CPROVER_assert(pOperation == NULL, -+ "Bad status implies null pointer"); -+ __CPROVER_assume(pOperation == NULL); -+#endif -+ - if( pOperation != NULL ) - { - IotMqtt_FreeOperation( pOperation ); -diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h -index ccfb802..d1b540c 100644 ---- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h -+++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h -@@ -237,7 +237,9 @@ - * Used to validate parameters if when connecting to an AWS IoT MQTT server. - */ - #define AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ -+#ifndef AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH - #define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( ( uint16_t ) 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ -+#endif - #define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( ( size_t ) 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ - #define AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 131072 ) ) /**< @brief Maximum publish payload length accepted by AWS IoT. */ - --- -2.7.4 - diff --git a/cbmc/patches/README.md b/cbmc/patches/README.md deleted file mode 100644 index fbf485a1ef..0000000000 --- a/cbmc/patches/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Patches for Proofs -================== - -This directory contains patch files that must be applied to the codebase -before running CBMC proofs. The `cbmc/proofs/prepare.py` script takes -care of applying these patches. diff --git a/cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c b/cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c deleted file mode 100644 index c87fae1a33..0000000000 --- a/cbmc/proofs/IotMqtt_Cleanup/IotMqtt_Cleanup_harness.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include -#include - -void harness() -{ - /* - * The code specification says that IotMqtt_Cleanup must be - * called after IotMqtt_Init, since it is supposed to undo - * anything that IotMqtt_Init does. Both IotMqtt_Cleanup and - * IotMqtt_Init basically update the status of the same global - * variable _initCalled. This variable is declared as volatile, - * so CBMC will always treat it as non-deterministic. - * Thus, even if we called IotMqtt_Init first, it does not make - * a significant impact in this proof harness. However, since - * the developers may change the implementation of both - * functions in the future, we should keep both functions in the proof. - */ - IotMqtt_Init(); - IotMqtt_Cleanup(); -} diff --git a/cbmc/proofs/IotMqtt_Cleanup/Makefile b/cbmc/proofs/IotMqtt_Cleanup/Makefile deleted file mode 100644 index b8ffb296aa..0000000000 --- a/cbmc/proofs/IotMqtt_Cleanup/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -ENTRY=IotMqtt_Cleanup - -# We abstract all the log and concurrency related functions in this proof -# and assume their implementation is correct -ABSTRACTIONS += --remove-function-body IotLog_Generic - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto - -UNWINDING += --unwind 1 - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml deleted file mode 100644 index a4d18f6094..0000000000 --- a/cbmc/proofs/IotMqtt_Cleanup/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: '=--unwind;1;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static=' -goto: IotMqtt_Cleanup.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json b/cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json deleted file mode 100644 index f56ccaf7d0..0000000000 --- a/cbmc/proofs/IotMqtt_Cleanup/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_Cleanup", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_DeserializeConnack/Makefile b/cbmc/proofs/IotMqtt_DeserializeConnack/Makefile deleted file mode 100644 index 5a297f3c97..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeConnack/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ENTRY=_IotMqtt_DeserializeConnack - -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c b/cbmc/proofs/IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c deleted file mode 100644 index 5fe781252c..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeConnack/_IotMqtt_DeserializeConnack_harness.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -/*-----------------------------------------------------------*/ - -void harness() -{ - _mqttPacket_t connack; - - connack.pRemainingData = malloc( sizeof( uint8_t ) * connack.remainingLength ); - - _IotMqtt_DeserializeConnack( &connack ); -} diff --git a/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml deleted file mode 100644 index dc42695b88..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: _IotMqtt_DeserializeConnack.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json deleted file mode 100644 index 53858156a7..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeConnack/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "_IotMqtt_DeserializeConnack", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_DeserializePingresp/Makefile b/cbmc/proofs/IotMqtt_DeserializePingresp/Makefile deleted file mode 100644 index 5f172241bc..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePingresp/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ENTRY=_IotMqtt_DeserializePingresp - -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c b/cbmc/proofs/IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c deleted file mode 100644 index 2a60dbd3f4..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePingresp/_IotMqtt_DeserializePingresp_harness.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -/*-----------------------------------------------------------*/ - -void harness() -{ - _mqttPacket_t pingresp; - - pingresp.pRemainingData = malloc( sizeof( uint8_t ) * pingresp.remainingLength ); - - _IotMqtt_DeserializePingresp( &pingresp ); -} diff --git a/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml deleted file mode 100644 index 032b39a1c5..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: _IotMqtt_DeserializePingresp.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json deleted file mode 100644 index a9c8470c02..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePingresp/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "_IotMqtt_DeserializePingresp", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_DeserializePuback/Makefile b/cbmc/proofs/IotMqtt_DeserializePuback/Makefile deleted file mode 100644 index ef448863c2..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePuback/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ENTRY=_IotMqtt_DeserializePuback - -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c b/cbmc/proofs/IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c deleted file mode 100644 index 53181fd209..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePuback/_IotMqtt_DeserializePuback_harness.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -/*-----------------------------------------------------------*/ - -void harness() -{ - _mqttPacket_t puback; - - puback.pRemainingData = malloc( sizeof( uint8_t ) * puback.remainingLength ); - - _IotMqtt_DeserializePuback( &puback ); -} diff --git a/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml deleted file mode 100644 index 87c3065c33..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: _IotMqtt_DeserializePuback.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json deleted file mode 100644 index b38daecdfe..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePuback/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "_IotMqtt_DeserializePuback", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_DeserializePublish/Makefile b/cbmc/proofs/IotMqtt_DeserializePublish/Makefile deleted file mode 100644 index 9287861438..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePublish/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ENTRY=_IotMqtt_DeserializePublish - -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c b/cbmc/proofs/IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c deleted file mode 100644 index a0616940dd..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePublish/_IotMqtt_DeserializePublish_harness.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -/*-----------------------------------------------------------*/ - -void harness() -{ - IotMqttPublishInfo_t publishInfo; - - publishInfo.pTopicName = malloc( publishInfo.topicNameLength ); - publishInfo.pPayload = malloc( publishInfo.payloadLength ); - - _mqttOperation_t operation; - operation.u.publish.publishInfo = publishInfo; - - _mqttPacket_t publish; - publish.pRemainingData = malloc( sizeof( uint8_t ) * publish.remainingLength ); - publish.u.pIncomingPublish = &operation; - - - _IotMqtt_DeserializePublish( &publish ); -} diff --git a/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml deleted file mode 100644 index 81b52871a8..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: _IotMqtt_DeserializePublish.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json deleted file mode 100644 index cfe722fbc2..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializePublish/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "_IotMqtt_DeserializePublish", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile deleted file mode 100644 index 84bf676658..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeSuback/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -ENTRY=_IotMqtt_DeserializeSuback - -################################################################ -# Proof assumptions - -# Bound the number of subscriptions in a list (BOUND = MAX-1) -SUBSCRIPTION_COUNT_MAX=2 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# Bound the number of operations in a list (BOUND = MAX-1) -OPERATION_COUNT_MAX=2 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -BUFFER_SIZE = 15 -DEF += -DBUFFER_SIZE=$(BUFFER_SIZE) - -################################################################ -# Function omitted from the proof - -# We abstract all the log and concurrency related functions in this -# proof and assume their implementation is correct -ABSTRACTIONS += --remove-function-body IotLog_Generic - -# This define has the effect of removing RemoveAllMatches from the -# linear containers header so that our stub will be used. -DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 - -################################################################ -# Loop unwinding - -LOOP += _decodeSubackStatus.0:$(BUFFER_SIZE) -LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -################################################################ - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto - -################################################################ - -include ../Makefile.common - diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c b/cbmc/proofs/IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c deleted file mode 100644 index 3eaa8b3135..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeSuback/_IotMqtt_DeserializeSuback_harness.c +++ /dev/null @@ -1,86 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -#include "mqtt_state.h" - -/**************************************************************** - * Stub out RemoveAllMatches. - * - * The only invocation of RemoveAllMatches in DeserializeSuback is to - * remove subscriptions from the connection's subscription list. This - * stub abstracts RemoveAllMatches by replacing the list with an - * unconstrained list of subscriptions. We don't even bother to fill - * in the topic subscription filters. By design, any actual use of - * the list in subsequent code (there is none) will trigger pointer - * errors. -/****************************************************************/ - -void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - bool ( *isMatch )( const IotLink_t * const pOperationLink, - void * pCompare ), - void * pMatch, - void ( *freeElement )( void * pData ), - size_t linkOffset ) -{ - IotListDouble_t *sentinel = pList->pNext->pPrevious; - - size_t num_elts; - __CPROVER_assume(num_elts < SUBSCRIPTION_COUNT_MAX); - - // Allocate lists of length at most 3 - __CPROVER_assert(SUBSCRIPTION_COUNT_MAX <= 4, - "Subscription lists limited to 3 elements"); - - if (1 <= num_elts) - { - _mqttSubscription_t *pElt = malloc(sizeof(*pElt)); - sentinel->pNext = &pElt->link; - pElt->link.pPrevious = sentinel; - pElt->link.pNext = sentinel; - sentinel->pPrevious = &pElt->link; - } - if (2 <= num_elts) - { - _mqttSubscription_t *pElt = malloc(sizeof(*pElt)); - sentinel->pNext = &pElt->link; - pElt->link.pPrevious = sentinel; - pElt->link.pNext = sentinel; - sentinel->pPrevious = &pElt->link; - } - if (3 <= num_elts) - { - _mqttSubscription_t *pElt = malloc(sizeof(*pElt)); - sentinel->pNext = &pElt->link; - pElt->link.pPrevious = sentinel; - pElt->link.pNext = sentinel; - sentinel->pPrevious = &pElt->link; - } -} - -/**************************************************************** - * Proof harness - ****************************************************************/ - -void harness() -{ - _mqttPacket_t suback; - - // Create packet data - __CPROVER_assume( suback.remainingLength <= BUFFER_SIZE ); - suback.pRemainingData = malloc( sizeof( uint8_t ) - * suback.remainingLength ); - - // Create packet connection - IotMqttConnection_t pConn = allocate_IotMqttConnection(NULL); - __CPROVER_assume(pConn != NULL); - ensure_IotMqttConnection_has_lists(pConn); - __CPROVER_assume(valid_IotMqttConnection(pConn)); - suback.u.pMqttConnection = pConn; - - // Argument must be a nonnull pointer - _IotMqtt_DeserializeSuback( &suback ); -} - -/****************************************************************/ diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml deleted file mode 100644 index fefaa22dba..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--unwind;1;--unwindset;valid_IotMqttSubscriptionList.0:2,valid_IotMqttOperationList.0:2,_decodeSubackStatus.0:15;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: _IotMqtt_DeserializeSuback.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json deleted file mode 100644 index aa2b240a94..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeSuback/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "_IotMqtt_DeserializeSuback", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile b/cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile deleted file mode 100644 index 9790d0f2f7..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeUnsuback/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ENTRY=_IotMqtt_DeserializeUnsuback - -ABSTRACTIONS = \ - --remove-function-body IotLog_Generic \ - -OBJS = \ - $(ENTRY)_harness.goto \ - $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c b/cbmc/proofs/IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c deleted file mode 100644 index de6a5a7996..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeUnsuback/_IotMqtt_DeserializeUnsuback_harness.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -/*-----------------------------------------------------------*/ - -void harness() -{ - _mqttPacket_t unsuback; - - unsuback.pRemainingData = malloc( sizeof( uint8_t ) * unsuback.remainingLength ); - - _IotMqtt_DeserializeUnsuback( &unsuback ); -} diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml deleted file mode 100644 index 10adae41b1..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: _IotMqtt_DeserializeUnsuback.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json b/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json deleted file mode 100644 index eb1c975b0c..0000000000 --- a/cbmc/proofs/IotMqtt_DeserializeUnsuback/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "_IotMqtt_DeserializeUnsuback", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c b/cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c deleted file mode 100644 index d3f73e87bf..0000000000 --- a/cbmc/proofs/IotMqtt_Init/IotMqtt_Init_harness.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include -#include - -void harness() -{ - IotMqttError_t status = IotMqtt_Init(); - assert(status == IOT_MQTT_SUCCESS || status == IOT_MQTT_NOT_INITIALIZED); -} diff --git a/cbmc/proofs/IotMqtt_Init/Makefile b/cbmc/proofs/IotMqtt_Init/Makefile deleted file mode 100644 index 84491fbba2..0000000000 --- a/cbmc/proofs/IotMqtt_Init/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -ENTRY=IotMqtt_Init - -# We abstract all the log and concurrency related functions in this proof -# and assume their implementation is correct -ABSTRACTIONS += --remove-function-body IotLog_Generic - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto - -UNWINDING += --unwind 1 - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml deleted file mode 100644 index 0233491a13..0000000000 --- a/cbmc/proofs/IotMqtt_Init/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--unwind;1;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" -expected: SUCCESSFUL -goto: IotMqtt_Init.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_Init/cbmc-viewer.json b/cbmc/proofs/IotMqtt_Init/cbmc-viewer.json deleted file mode 100644 index e4ce3e59d8..0000000000 --- a/cbmc/proofs/IotMqtt_Init/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_Init", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c b/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c deleted file mode 100644 index 10c23f2388..0000000000 --- a/cbmc/proofs/IotMqtt_IsSubscribed/IotMqtt_IsSubscribed_harness.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include -#include - -#include "mqtt_state.h" - -void harness() -{ - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection(NULL); - __CPROVER_assume(mqttConnection); - ensure_IotMqttConnection_has_lists(mqttConnection); - __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); - - allocate_IotMqttSubscriptionList(&(mqttConnection->subscriptionList), 1); - __CPROVER_assume(valid_IotMqttSubscriptionList(&(mqttConnection->subscriptionList), 1)); - - uint16_t topicFilterLength; - __CPROVER_assume(topicFilterLength < TOPIC_LENGTH_MAX); - - char* TopicFilter = malloc_can_fail(topicFilterLength); - __CPROVER_assume( VALID_STRING(TopicFilter, topicFilterLength) ); - - IotMqttSubscription_t result; - - IotMqtt_IsSubscribed( mqttConnection, - nondet_bool() ? NULL : TopicFilter, - topicFilterLength, - nondet_bool() ? NULL : &result ); -} diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/Makefile b/cbmc/proofs/IotMqtt_IsSubscribed/Makefile deleted file mode 100644 index 7de04ac573..0000000000 --- a/cbmc/proofs/IotMqtt_IsSubscribed/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -ENTRY=IotMqtt_IsSubscribed - -ABSTRACTIONS += --remove-function-body IotLog_Generic - -# Remove for coverage -ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe -ABSTRACTIONS += --remove-function-body _matchEndWildcards -ABSTRACTIONS += --remove-function-body _matchWildcards -ABSTRACTIONS += --remove-function-body _packetMatch -ABSTRACTIONS += --remove-function-body _topicFilterMatch - - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto - -# One more than actual number of subscriptions in a subscription list -SUBSCRIPTION_COUNT_MAX=4 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# One more than actual number of operations in an operation list -OPERATION_COUNT_MAX=4 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# One more than actual length of topics -TOPIC_LENGTH_MAX=6 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -# Should be 2*SUBSCRIPTION_COUNT_MAX-1 -SUBSCRIPTION_LIST_MAX=6 -DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) - -LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += allocate_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttOperationList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml deleted file mode 100644 index a6b1ba20e3..0000000000 --- a/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -jobos: ubuntu16 -cbmcflags: "--object-bits;8;--unwind;1;--unwindset;allocate_IotMqttSubscriptionArray.0:4,valid_IotMqttSubscriptionArray.0:4,allocate_IotMqttSubscriptionList.0:4,valid_IotMqttSubscriptionList.0:4,valid_IotMqttOperationList.0:4,IotListDouble_FindFirstMatch$link1.0:4,strncmp.0:6;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" -goto: IotMqtt_IsSubscribed.goto -expected: SUCCESSFUL diff --git a/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json b/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json deleted file mode 100644 index 8a8e256886..0000000000 --- a/cbmc/proofs/IotMqtt_IsSubscribed/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_IsSubscribed", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c b/cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c deleted file mode 100644 index 98a730d596..0000000000 --- a/cbmc/proofs/IotMqtt_OperationType/IotMqtt_OperationType_harness.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -void harness() -{ - IotMqttOperationType_t operation; - const char *pMessage = IotMqtt_OperationType(operation); - assert(pMessage != NULL); -} diff --git a/cbmc/proofs/IotMqtt_OperationType/Makefile b/cbmc/proofs/IotMqtt_OperationType/Makefile deleted file mode 100644 index 94d3ce228b..0000000000 --- a/cbmc/proofs/IotMqtt_OperationType/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -ENTRY=IotMqtt_OperationType - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml deleted file mode 100644 index 61f0662cfb..0000000000 --- a/cbmc/proofs/IotMqtt_OperationType/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" -expected: SUCCESSFUL -goto: IotMqtt_OperationType.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json b/cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json deleted file mode 100644 index f7b64021e8..0000000000 --- a/cbmc/proofs/IotMqtt_OperationType/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_OperationType", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c b/cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c deleted file mode 100644 index 00c6189551..0000000000 --- a/cbmc/proofs/IotMqtt_PublishAsync/IotMqtt_PublishAsync_harness.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include -#include - -#include "mqtt_state.h" - -/** - * We abstract all functions related to concurrency and assume they are correct. - * Thus, we add these stub to increase coverage. - */ -IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJobStorage_t * const pJobStorage, - IotTaskPoolJob_t * const pJob ) -{ - if (userCallback == NULL || pJobStorage == NULL || pJob == NULL) - { - return IOT_TASKPOOL_BAD_PARAMETER; - } - /* _IotMqtt_ScheduleOperation asserts this */ - return IOT_TASKPOOL_SUCCESS; -} - -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - uint32_t timeMs ) -{ - IotTaskPoolError_t error; - - /* _IotMqtt_ScheduleOperation asserts this */ - __CPROVER_assume(error != IOT_TASKPOOL_BAD_PARAMETER && - error != IOT_TASKPOOL_ILLEGAL_OPERATION); - return error; -} - -/** - * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives - * a volatile variable as input. Thus, CBMC will always consider that - * Atomic_Add_u32 will operate over nondetermistic values and raises - * a unsigned integer overflow failure. However, developers have reported - * that the use of this overflow is part of the function implementation. - * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid - * spurious alarms, we stub out this function to always - * return a nondetermistic odd value. - */ -uint16_t _IotMqtt_NextPacketIdentifier( void ) { - uint16_t id; - - /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - __CPROVER_assume(id & 0x0001 == 1); - - return id; -} - -void harness() -{ - /* assume a valid MQTT connection */ - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection(NULL); - __CPROVER_assume(mqttConnection != NULL); - __CPROVER_assume(mqttConnection->pNetworkInterface != NULL); - __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); - ensure_IotMqttConnection_has_lists(mqttConnection); - __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); - - /* assume a valid publish info */ - IotMqttPublishInfo_t *pPublishInfo = allocate_IotMqttPublishInfo(NULL); - if (pPublishInfo != NULL) __CPROVER_assume(valid_IotMqttPublishInfo(pPublishInfo)); - - /* assume unconstrained inputs */ - uint32_t flags; - IotMqttCallbackInfo_t *callbackInfo = malloc_can_fail(sizeof (*callbackInfo) ); - - /* output */ - IotMqttOperation_t *publishOperation = malloc_can_fail(sizeof (*publishOperation) ); - - - /* function under verification */ - IotMqttError_t status = IotMqtt_PublishAsync( mqttConnection, /* always assume a valid connection */ - pPublishInfo, - flags, - callbackInfo, - publishOperation ); - /* assert post-conditions */ - assert(IMPLIES(status == IOT_MQTT_STATUS_PENDING, pPublishInfo->qos == IOT_MQTT_QOS_1)); - assert(IMPLIES(status == IOT_MQTT_SUCCESS, pPublishInfo->qos == IOT_MQTT_QOS_0 || pPublishInfo->qos == IOT_MQTT_QOS_1)); -} diff --git a/cbmc/proofs/IotMqtt_PublishAsync/Makefile b/cbmc/proofs/IotMqtt_PublishAsync/Makefile deleted file mode 100644 index b84411fc7c..0000000000 --- a/cbmc/proofs/IotMqtt_PublishAsync/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -ENTRY=IotMqtt_PublishAsync - -# We abstract all the log and concurrency related functions in this proof -# and assume their implementation is correct -ABSTRACTIONS += --remove-function-body IotLog_Generic -ABSTRACTIONS += --remove-function-body _IotMqtt_ProcessSend -ABSTRACTIONS += --remove-function-body _destroyMqttConnection - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -# One more than actual number of subscriptions -SUBSCRIPTION_COUNT_MAX=4 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# One more than actual number of operations -OPERATION_COUNT_MAX=4 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) -LOOP += _encodeRemainingLength.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml deleted file mode 100644 index 0b28bbe6b7..0000000000 --- a/cbmc/proofs/IotMqtt_PublishAsync/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--unwind;1;--unwindset;IotListDouble_RemoveAllMatches.0:4,valid_IotMqttSubscriptionList.0:4,valid_IotMqttOperationList.0:4,_encodeRemainingLength.0:4;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check;--flush" -expected: SUCCESSFUL -goto: IotMqtt_PublishAsync.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json deleted file mode 100644 index 8a9dedf46c..0000000000 --- a/cbmc/proofs/IotMqtt_PublishAsync/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_PublishAsync", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c b/cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c deleted file mode 100644 index c65451efb1..0000000000 --- a/cbmc/proofs/IotMqtt_PublishSync/IotMqtt_PublishSync_harness.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file IotMqtt_PublishSync_harness.c - * @brief Implements the proof harness for IotMqtt_PublishSync function. - */ - -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" -#include "platform/iot_threads.h" - -#include -#include - -#include "mqtt_state.h" - -typedef bool ( * MatchFunction_t )( const IotLink_t * const pOperationLink, - void * pCompare ); - -static unsigned int flagSemaphore; - -/** - * We constrain the return values of these functions because they - * are checked by assertions in the MQTT code. - */ -IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJobStorage_t * const pJobStorage, - IotTaskPoolJob_t * const pJob ) -{ - assert( userCallback != NULL ); - assert( pJobStorage != NULL ); - assert( pJob != NULL ); - - /* _IotMqtt_ScheduleOperation asserts this. */ - return IOT_TASKPOOL_SUCCESS; -} - -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - uint32_t timeMs ) -{ - IotTaskPoolError_t error; - - /* _IotMqtt_ScheduleOperation asserts this. */ - __CPROVER_assume( error != IOT_TASKPOOL_BAD_PARAMETER && - error != IOT_TASKPOOL_ILLEGAL_OPERATION ); - return error; -} - -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - IotTaskPoolJobStatus_t * const pStatus ) -{ - if( ( taskPool == NULL ) || ( job == NULL ) ) - { - return IOT_TASKPOOL_BAD_PARAMETER; - } - - return IOT_TASKPOOL_SUCCESS; -} - -/** - * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives - * a volatile variable as input. Thus, CBMC will always consider that - * Atomic_Add_u32 will operate over nondetermistic values and raise - * a unsigned integer overflow failure. However, developers have reported - * that the use of this overflow is part of the function implementation. - * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid - * spurious alarms, we stub out this function to always - * return a nondetermistic odd value. - */ -uint16_t _IotMqtt_NextPacketIdentifier( void ) -{ - uint16_t id; - - /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - __CPROVER_assume( id % 2 == 1 ); - - return id; -} - -/** - * We abstract this function for performance reasons. In its original - * implementation, CBMC ended up creating byte extracts for all possible - * objects, due to the polymorphic nature of the linked list. We assume - * that the function is memory safe, and we free and havoc the list to - * demonstrate that no subsequent code makes use of the values in the list. - */ -void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - MatchFunction_t isMatch, - void * pMatch, - void ( *freeElement )( void * pData ), - size_t linkOffset ) -{ - free_IotMqttSubscriptionList( pList ); - allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); -} - - -/** - * We are abstracting the semaphores because we are doing sequential proof. - * But the semaphore API assures us that TimedWait called after Post will - * never fail. Our abstraction of the semaphores models this behavior. - */ -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - assert( pSemaphore != NULL ); - flagSemaphore++; -} - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - assert( pSemaphore != NULL ); - - if( flagSemaphore > 0 ) - { - flagSemaphore--; - return true; - } - - return false; -} - -void harness() -{ - /* Assume a valid MQTT connection. */ - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); - - __CPROVER_assume( mqttConnection != NULL ); - __CPROVER_assume( mqttConnection->pNetworkInterface != NULL ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); - ensure_IotMqttConnection_has_lists( mqttConnection ); - __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); - - /* Assume a valid publish info. */ - IotMqttPublishInfo_t * pPublishInfo = allocate_IotMqttPublishInfo( NULL ); - __CPROVER_assume( IMPLIES( pPublishInfo != NULL, valid_IotMqttPublishInfo( pPublishInfo ) ) ); - - /* Assume unconstrained inputs. */ - uint32_t flags; - uint32_t timeoutMs; - - /* Initialize semaphore flag. */ - flagSemaphore = 0; - - /* Function under verification. */ - IotMqtt_PublishSync( mqttConnection, /* Always assume a valid connection. */ - pPublishInfo, - flags, - timeoutMs ); -} diff --git a/cbmc/proofs/IotMqtt_PublishSync/Makefile b/cbmc/proofs/IotMqtt_PublishSync/Makefile deleted file mode 100644 index db360e48d7..0000000000 --- a/cbmc/proofs/IotMqtt_PublishSync/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -ENTRY=IotMqtt_PublishSync - -# We abstract all the log and concurrency related functions in this proof -# and assume their implementation is correct -ABSTRACTIONS += --remove-function-body IotLog_Generic - -# We abstract this function because manual inspection demonstrates that it is unreachable -ABSTRACTIONS += --remove-function-body _IotMqtt_RemoveSubscriptionByPacket - -# We also abstract unreachable functions to improve coverage metrics -ABSTRACTIONS += --remove-function-body _IotMqtt_ProcessIncomingPublish -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -# One more than actual number of subscriptions -SUBSCRIPTION_COUNT_MAX=4 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# One more than actual number of operations -OPERATION_COUNT_MAX=4 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# One more than actual length of topics -TOPIC_LENGTH_MAX=6 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -# One more than actual length of payload -PAYLOAD_LENGTH_MAX=6 -DEF += -DPAYLOAD_LENGTH_MAX=$(PAYLOAD_LENGTH_MAX) - -# Enables CBMC stub for RemoveAllMatches function -DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 - -# Allow MQTT allocations to fail for coverage -DEF += -include mqtt_state.h -DEF += -DIotMqtt_MallocMessage=malloc_can_fail -DEF += -DIotMqtt_MallocOperation=malloc_can_fail - -LOOP += _encodeRemainingLength.0:$(PAYLOAD_LENGTH_MAX) -LOOP += _IotMqtt_InvokeSubscriptionCallback.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _matchWildcards.0:$(TOPIC_LENGTH_MAX) -LOOP += _topicFilterMatch.0:$(TOPIC_LENGTH_MAX) -LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_RemoveAllMatches$$link2.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml deleted file mode 100644 index 8fe378dfcd..0000000000 --- a/cbmc/proofs/IotMqtt_PublishSync/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--unwind;1;--unwindset;_encodeRemainingLength.0:6,_IotMqtt_InvokeSubscriptionCallback.0:4,_matchWildcards.0:6,_topicFilterMatch.0:6,IotListDouble_RemoveAllMatches.0:4,IotListDouble_RemoveAllMatches.0:4,valid_IotMqttOperationList.0:4,valid_IotMqttSubscriptionList.0:4;--bounds-check;--conversion-check;--div-by-zero-check;--float-overflow-check;--flush;--nan-check;--nondet-static;--pointer-check;--pointer-overflow-check;--signed-overflow-check;--undefined-shift-check;--unsigned-overflow-check;--unwinding-assertions" -expected: SUCCESSFUL -goto: IotMqtt_PublishSync.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json deleted file mode 100644 index 501be64da3..0000000000 --- a/cbmc/proofs/IotMqtt_PublishSync/cbmc-viewer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ "expected-missing-functions": - [ - "IotClock_GetTimeMs", - "IotLog_Generic", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_strerror", - "_IotMqtt_RemoveSubscriptionByPacket" - ], - "proof-name": "IotMqtt_PublishSync", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c b/cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c deleted file mode 100644 index 85f53b2356..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeAsync/IotMqtt_SubscribeAsync_harness.c +++ /dev/null @@ -1,144 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file IotMqtt_SubscribeAsync_harness.c - * @brief Implements the proof harness for IotMqtt_SubscribeAsync function. - */ - -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -#include "mqtt_state.h" - -typedef bool ( * MatchFunction_t )( const IotLink_t * const pOperationLink, - void * pCompare ); - -typedef void ( * FreeElementFunction_t )( void * pData ); - -/** - * We constrain the return values of these functions because - * they are checked by assertions in the MQTT code. - */ -IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJobStorage_t * const pJobStorage, - IotTaskPoolJob_t * const pJob ) -{ - assert( userCallback != NULL ); - assert( pJobStorage != NULL ); - assert( pJob != NULL ); - - /* _IotMqtt_ScheduleOperation asserts this. */ - return IOT_TASKPOOL_SUCCESS; -} - -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - uint32_t timeMs ) -{ - IotTaskPoolError_t error; - - /* _IotMqtt_ScheduleOperation asserts this. */ - __CPROVER_assume( error != IOT_TASKPOOL_BAD_PARAMETER && - error != IOT_TASKPOOL_ILLEGAL_OPERATION ); - return error; -} - -/** - * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives - * a volatile variable as input. Thus, CBMC will always consider that - * Atomic_Add_u32 will operate over nondetermistic values and raise - * an unsigned integer overflow failure. However, developers have reported - * that the use of this overflow is part of the function implementation. - * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid - * spurious alarms, we stub out this function to always - * return a nondetermistic odd value. - */ -uint16_t _IotMqtt_NextPacketIdentifier( void ) -{ - uint16_t id; - - /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - __CPROVER_assume( id & 0x0001 == 1 ); - - return id; -} - -/** - * We abstract these functions for performance reasons. In their original - * implementation, CBMC ended up creating byte extracts for all possible - * objects, due to the polymorphic nature of the linked list. We assume - * that the functions are memory safe, and we free and havoc the list to - * demonstrate that no subsequent code makes use of the values in the list. - */ -void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - MatchFunction_t isMatch, - void * pMatch, - FreeElementFunction_t freeElement, - size_t linkOffset ) -{ - free_IotMqttSubscriptionList( pList ); - allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); -} - -void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount ) -{ - free_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ) ); - allocate_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ), subscriptionCount ); -} - -void harness() -{ - /* Assume a valid MQTT connection. */ - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); - - __CPROVER_assume( mqttConnection != NULL ); - __CPROVER_assume( mqttConnection->pNetworkInterface != NULL ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); - ensure_IotMqttConnection_has_lists( mqttConnection ); - __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); - - /* Assume unconstrained inputs. */ - size_t subscriptionCount; - __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); - IotMqttSubscription_t * pSubscriptionList = allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); - __CPROVER_assume( valid_IotMqttSubscriptionArray( pSubscriptionList, subscriptionCount ) ); - uint32_t flags; - IotMqttCallbackInfo_t * pCallbackInfo = malloc_can_fail( sizeof( *pCallbackInfo ) ); - - /* Output. */ - IotMqttOperation_t pSubscribeOperation = allocate_IotMqttOperation( NULL, mqttConnection ); - - /* Function under verification. */ - IotMqttError_t status = IotMqtt_SubscribeAsync( mqttConnection, /* Always assume a valid connection. */ - pSubscriptionList, /* Always assume a valid subscription list. */ - subscriptionCount, - flags, - pCallbackInfo, - pSubscribeOperation ); -} diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/Makefile b/cbmc/proofs/IotMqtt_SubscribeAsync/Makefile deleted file mode 100644 index 52ca919a42..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeAsync/Makefile +++ /dev/null @@ -1,71 +0,0 @@ -ENTRY=IotMqtt_SubscribeAsync - -# We abstract this function because manual inspection demonstrates that it is unreachable -ABSTRACTIONS += --remove-function-body _checkRetryLimit -ABSTRACTIONS += --remove-function-body _scheduleNextRetry - -# We abstract all unreachable functions to improve coverage metrics -ABSTRACTIONS += --remove-function-body _IotMqtt_PublishSetDup -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribeCommon -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribe -ABSTRACTIONS += --remove-function-body _mqttOperation_match -ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe -ABSTRACTIONS += --remove-function-body _packetMatch -ABSTRACTIONS += --remove-function-body _topicFilterMatch -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -# One more than actual number of subscriptions in a subscription list -SUBSCRIPTION_COUNT_MAX=2 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# One more than actual number of operations in an operation list -OPERATION_COUNT_MAX=2 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# One more than actual length of topics -TOPIC_LENGTH_MAX=6 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -# Should be 2*SUBSCRIPTION_COUNT_MAX-1 -SUBSCRIPTION_LIST_MAX=3 -DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) - -# Enables CBMC stub for RemoveAllMatches function -DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 - -# Allow MQTT allocations to fail for coverage -DEF += -include mqtt_state.h -DEF += -DIotMqtt_MallocMessage=malloc_can_fail -DEF += -DIotMqtt_MallocOperation=malloc_can_fail -DEF += -DIotMqtt_MallocSubscription=malloc_can_fail - -LOOP += _IotMqtt_AddSubscriptions.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _IotMqtt_SerializeSubscribeCommon.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) -LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += allocate_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) -LOOP += valid_IotMqttOperationList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml deleted file mode 100644 index 16b101ea1f..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-batch.yaml +++ /dev/null @@ -1,8 +0,0 @@ -build_memory: 32000 -cbmcflags: "--object-bits;8;--unwind;1;--unwindset;_IotMqtt_AddSubscriptions.0:3,_IotMqtt_SerializeSubscribeCommon.0:3,_IotMqtt_SubscriptionPacketSize.0:3,_IotMqtt_ValidateSubscriptionList.0:3,_validateSubscription.0:6,allocate_IotMqttSubscriptionArray.0:2,allocate_IotMqttSubscriptionList.0:2,IotListDouble_FindFirstMatch.0:3,IotListDouble_FindFirstMatch$link1.0:2,IotListDouble_RemoveAllMatches.0:3,IotListDouble_RemoveAllMatches$link1.0:3,strncmp.0:6,valid_IotMqttOperationList.0:2,valid_IotMqttSubscriptionArray.0:2,valid_IotMqttSubscriptionList.0:2;--object-bits;8;--bounds-check;--conversion-check;--div-by-zero-check;--float-overflow-check;--flush;--nan-check;--nondet-static;--pointer-check;--pointer-overflow-check;--signed-overflow-check;--undefined-shift-check;--unsigned-overflow-check;--unwinding-assertions" -coverage_memory: 32000 -expected: SUCCESSFUL -goto: IotMqtt_SubscribeAsync.goto -jobos: ubuntu16 -property_memory: 32000 -report_memory: 32000 diff --git a/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json deleted file mode 100644 index dff93223e3..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeAsync/cbmc-viewer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ "expected-missing-functions": - [ - "IotClock_GetTimeMs", - "IotLog_Generic", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_strerror", - "_checkRetryLimit", - "_scheduleNextRetry" - ], - "proof-name": "IotMqtt_SubscribeAsync", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c b/cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c deleted file mode 100644 index fdf89eb2fa..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeSync/IotMqtt_SubscribeSync_harness.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file IotMqtt_SubscribeSync_harness.c - * @brief Implements the proof harness for IotMqtt_SubscribeSync function. - */ - -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -#include "mqtt_state.h" - -typedef bool ( * MatchFunction_t )( const IotLink_t * const pOperationLink, - void * pCompare ); - -typedef void ( * FreeElementFunction_t )( void * pData ); - -static unsigned int flagSemaphore; - -/** - * We constrain the return values of this function because it - * is checked by assertions in the MQTT code. - */ -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - IotTaskPoolJobStatus_t * const pStatus ) -{ - if( ( taskPool == NULL ) || ( job == NULL ) ) - { - return IOT_TASKPOOL_BAD_PARAMETER; - } - - return IOT_TASKPOOL_SUCCESS; -} - -/** - * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives - * a volatile variable as input. Thus, CBMC will always consider that - * Atomic_Add_u32 will operate over nondetermistic values and raise - * an unsigned integer overflow failure. However, developers have reported - * that the use of this overflow is part of the function implementation. - * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid - * spurious alarms, we stub out this function to always - * return a nondetermistic odd value. - */ -uint16_t _IotMqtt_NextPacketIdentifier( void ) -{ - uint16_t id; - - /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - __CPROVER_assume( id & 0x0001 == 1 ); - - return id; -} - -/** - * We abstract these functions for performance reasons. In their original - * implementation, CBMC ended up creating byte extracts for all possible - * objects, due to the polymorphic nature of the linked list. We assume - * that these functions are memory safe, and we free and havoc the list to - * demonstrate that no subsequent code makes use of the values in the list. - */ -void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - MatchFunction_t isMatch, - void * pMatch, - FreeElementFunction_t freeElement, - size_t linkOffset ) -{ - free_IotMqttSubscriptionList( pList ); - allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); -} - -void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount ) -{ - free_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ) ); - allocate_IotMqttSubscriptionList( &( pMqttConnection->subscriptionList ), subscriptionCount ); -} - -/** - * We are abstracting the semaphores because we are doing sequential proof. - * But the semaphore API assures us that TimedWait called after Post will - * never fail. Our abstraction of the semaphores models this behavior. - */ -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - assert( pSemaphore != NULL ); - flagSemaphore++; -} - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - assert( pSemaphore != NULL ); - - if( flagSemaphore > 0 ) - { - flagSemaphore--; - return true; - } - - return false; -} - -void harness() -{ - /* Assume a valid MQTT connection. */ - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); - - __CPROVER_assume( mqttConnection != NULL ); - __CPROVER_assume( mqttConnection->pNetworkInterface != NULL ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection->pNetworkInterface ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); - ensure_IotMqttConnection_has_lists( mqttConnection ); - __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); - - /* Assume unconstrained inputs. */ - size_t subscriptionCount; - __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); - IotMqttSubscription_t * pSubscriptionList = allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); - __CPROVER_assume( valid_IotMqttSubscriptionArray( pSubscriptionList, subscriptionCount ) ); - uint32_t flags; - uint32_t timeoutMs; - - /* Initialize semaphore flag. */ - flagSemaphore = 0; - - /* Function under verification. */ - IotMqttError_t status = IotMqtt_SubscribeSync( mqttConnection, /* Always assume a valid connection. */ - pSubscriptionList, /* Always assume a valid subscription list. */ - subscriptionCount, - flags, - timeoutMs ); -} diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/Makefile b/cbmc/proofs/IotMqtt_SubscribeSync/Makefile deleted file mode 100644 index d70925fa0a..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeSync/Makefile +++ /dev/null @@ -1,77 +0,0 @@ -ENTRY=IotMqtt_SubscribeSync - -# We abstract these functions because manual inspection demonstrates they are unreachable -ABSTRACTIONS += --remove-function-body _checkRetryLimit -ABSTRACTIONS += --remove-function-body _scheduleCallback -ABSTRACTIONS += --remove-function-body _scheduleNextRetry - -# We abstract all unreachable functions to improve coverage metrics -ABSTRACTIONS += --remove-function-body _IotMqtt_ScheduleOperation -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribe -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeUnsubscribeCommon -ABSTRACTIONS += --remove-function-body _mqttOperation_match -ABSTRACTIONS += --remove-function-body _mqttOperation_tryDestroy -ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe -ABSTRACTIONS += --remove-function-body _mqttSubscription_tryDestroy -ABSTRACTIONS += --remove-function-body _packetMatch -ABSTRACTIONS += --remove-function-body _topicFilterMatch -ABSTRACTIONS += --remove-function-body IotMqtt_strerror -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -# One more than actual number of subscriptions in a subscription list -SUBSCRIPTION_COUNT_MAX=2 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# One more than actual number of operations in an operation list -OPERATION_COUNT_MAX=2 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# One more than actual length of topics -TOPIC_LENGTH_MAX=6 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -# Should be 2*SUBSCRIPTION_COUNT_MAX-1 -SUBSCRIPTION_LIST_MAX=3 -DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) - -# Enables CBMC stub for RemoveAllMatches function -DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 - -# These functions default to malloc. The CBMC model of malloc never fails, -# so we replace the CBMC model with a model that can fail to improve -# coverage and exercise more code -DEF += -include mqtt_state.h -DEF += -DIotMqtt_MallocMessage=malloc_can_fail -DEF += -DIotMqtt_MallocOperation=malloc_can_fail -DEF += -DIotMqtt_MallocSubscription=malloc_can_fail - -LOOP += _IotMqtt_AddSubscriptions.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _IotMqtt_SerializeSubscribeCommon.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) -LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += allocate_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) -LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml deleted file mode 100644 index 7eeb12ad71..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-batch.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# This file marks this directory as containing a CBMC proof. -# The contents of this file will be generated by continuous integration. diff --git a/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json deleted file mode 100644 index f0873e2e28..0000000000 --- a/cbmc/proofs/IotMqtt_SubscribeSync/cbmc-viewer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ "expected-missing-functions": - [ - "IotClock_GetTimeMs", - "IotLog_Generic", - "IotMqtt_strerror", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotTaskPool_GetSystemTaskPool", - - "_checkRetryLimit", - "_scheduleCallback", - "_scheduleNextRetry" - ], - "proof-name": "IotMqtt_SubscribeSync", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c b/cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c deleted file mode 100644 index 270208987b..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeAsync/IotMqtt_UnsubscribeAsync_harness.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * @file IotMqtt_UnsubscribeAsync_harness.c - * @brief Implements the proof harness for IotMqtt_UnsubscribeAsync function. - */ - -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -#include "mqtt_state.h" - -/****************************************************************/ - -/** - * We abstract these functions for performance reasons. In their original - * implementation, CBMC ended up creating byte extracts for all possible - * objects, due to the polymorphic nature of the linked list. We assume - * that the functions are memory safe, and we free and havoc the list to - * demonstrate that no subsequent code makes use of the values in the list. - */ - -typedef bool ( *MatchFunction_t )( const IotLink_t * const pOperationLink, - void * pCompare ); -typedef void ( *FreeElementFunction_t )( void * pData ); - -void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - MatchFunction_t isMatch, - void * pMatch, - FreeElementFunction_t freeElement, - size_t linkOffset ) -{ - free_IotMqttSubscriptionList( pList ); - allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); -} - -/****************************************************************/ - -/** - * We constrain the return values of these functions because - * they are checked by assertions in the MQTT code. - */ - -IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJobStorage_t * const pJobStorage, - IotTaskPoolJob_t * const pJob ) -{ - assert( userCallback != NULL ); - assert( pJobStorage != NULL ); - assert( pJob != NULL ); - - /* _IotMqtt_ScheduleOperation asserts this */ - return IOT_TASKPOOL_SUCCESS; -} - -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - uint32_t timeMs ) -{ - IotTaskPoolError_t error; - - /* _IotMqtt_ScheduleOperation asserts this */ - __CPROVER_assume( error != IOT_TASKPOOL_BAD_PARAMETER && - error != IOT_TASKPOOL_ILLEGAL_OPERATION ); - return error; -} - -/****************************************************************/ - -/** - * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives - * a volatile variable as input. Thus, CBMC will always consider that - * Atomic_Add_u32 will operate over nondetermistic values and it raises - * an unsigned integer overflow failure. However, developers have reported - * that the use of this overflow is part of the function implementation. - * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid - * spurious alarms, we stub out this function to always - * return a nondetermistic odd value. - */ - -uint16_t _IotMqtt_NextPacketIdentifier( void ) -{ - uint16_t id; - - /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - __CPROVER_assume( id & 0x0001 == 1 ); - - return id; -} - -/****************************************************************/ - -void harness() -{ - size_t subscriptionCount; - - __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); - - IotMqttSubscription_t * subscriptionList = - allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); - __CPROVER_assume( valid_IotMqttSubscriptionArray( subscriptionList, - subscriptionCount ) ); - - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); - __CPROVER_assume( mqttConnection ); - ensure_IotMqttConnection_has_lists( mqttConnection ); - __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection-> - pNetworkInterface ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection-> - pNetworkInterface ) ); - - uint32_t flags; - - IotMqttCallbackInfo_t * pCallbackInfo = - malloc_can_fail( sizeof( *pCallbackInfo ) ); - __CPROVER_assume(pCallbackInfo != NULL); - - /* output */ - IotMqttOperation_t pUnsubscribeOperation = - allocate_IotMqttOperation( NULL, mqttConnection ); - - - - IotMqtt_UnsubscribeAsync( mqttConnection, - subscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pUnsubscribeOperation ); -} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile b/cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile deleted file mode 100644 index 60cdd36601..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeAsync/Makefile +++ /dev/null @@ -1,116 +0,0 @@ -ENTRY=IotMqtt_UnsubscribeAsync - -################################################################ -# Proof assumptions - -# Bound the number of subscriptions in a list (BOUND = MAX-1) -SUBSCRIPTION_COUNT_MAX=2 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# Bound the number of operations in a list (BOUND = MAX-1) -OPERATION_COUNT_MAX=2 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# Bound the length of subscription topics (BOUND = MAX-1) -TOPIC_LENGTH_MAX=8 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -# A constant that should be 2*SUBSCRIPTION_COUNT_MAX-1 -SUBSCRIPTION_LIST_MAX=3 -DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) - -################################################################ -# MQTT configuration constants modified for testing - -# this should be smaller than topic_length_max for coverage -# _validateSubscription coverage at line 385 -AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=6 -DEF += -DAWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=$(AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH) - -# this should be smaller than topic_length_max for coverage -# _IotMqtt_SubscriptionPacketSize coverage at line 702 -MQTT_MAX_REMAINING_LENGTH=6 -DEF += -DMQTT_MAX_REMAINING_LENGTH=$(MQTT_MAX_REMAINING_LENGTH) - -################################################################ -# Function omitted from the proof - -ABSTRACTIONS += --remove-function-body IotLog_Generic - -# Unreachable functions removed for accurate coverage computation -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive -ABSTRACTIONS += --remove-function-body _IotMqtt_AddSubscriptions -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribe -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribeCommon -ABSTRACTIONS += --remove-function-body _matchEndWildcards -ABSTRACTIONS += --remove-function-body _matchWildcards -ABSTRACTIONS += --remove-function-body _mqttOperation_match -ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe -ABSTRACTIONS += --remove-function-body _packetMatch -ABSTRACTIONS += --remove-function-body _topicFilterMatch -ABSTRACTIONS += --remove-function-body _validateQos - -# Functions manually determined to be unreachable in their calling context -# See README.md for explanations of unreachability -ABSTRACTIONS += --remove-function-body _checkRetryLimit -ABSTRACTIONS += --remove-function-body _scheduleNextRetry -ABSTRACTIONS += --remove-function-body _IotMqtt_RemoveSubscriptionByPacket - -################################################################ - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -DEF += -DCBMC_CREATEOPERATION_INITIALIZE_CANT_FAIL=1 -DEF += -DCBMC_CREATEOPERATION_UNSUCCESSFUL_STATUS_MEANS_NULL_POINTER=1 -DEF += -DCBMC_FINDFIRSTMATCH_MATCH_IS_NONNULL=1 -DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 - -# allow mqtt mallocs to fail for coverage -DEF += -include mqtt_state.h -DEF += -DIotMqtt_MallocMessage=malloc_can_fail -DEF += -DIotMqtt_MallocOperation=malloc_can_fail - -################################################################ -# Loop unwinding - -LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link2.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link5.0:$(SUBSCRIPTION_COUNT_MAX) - -LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link1.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link2.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link3.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link4.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link5.0:$(SUBSCRIPTION_LIST_MAX) -LOOP += IotListDouble_RemoveAllMatches\$$link6.0:$(SUBSCRIPTION_LIST_MAX) - -LOOP += _IotMqtt_RemoveSubscriptionByTopicFilter.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_SerializeUnsubscribe.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_SerializeUnsubscribeCommon.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _encodeRemainingLength.0:2 -LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) -LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += harness.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) -LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -################################################################ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md b/cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md deleted file mode 100644 index 17f68c2d1c..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeAsync/README.md +++ /dev/null @@ -1,113 +0,0 @@ -This is a memory safety proof for IotMqtt_UnsubscribeAsync. - -This proof attains 91% code coverage. The following comments explain -why the uncovered lines of code are unreachable code. - -Some functions contain unreachable blocks of code: - -* libraries/standard/common/include/iot_linear_containers.h - IotListDouble_FindFirstMatch - - * Always called with a nonnull match predicate - -* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_OperationType - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommon - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommonSetup - - * Always called with a nonnull operation reference - -* libraries/standard/mqtt/src/iot_mqtt_helper.c - \_IotMqtt_RemainingLengthEncodedSize - - * Proof assumption: Proof bounds on number and size of subscription topics limits the - outbound packet to less than 128 bytes. - -* libraries/standard/mqtt/src/iot_mqtt_helper.c - \_IotMqtt_SubscriptionPacketSize - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_helper.c \_encodeRemainingLength - - * Proof assumption: Proof bounds on number and size of subscription topics limits the - outbound packet to less than 128 bytes. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c - \_IotMqtt_CreateOperation - - * pOperation is null if status is not SUCCESS. This is because 1) - status is initialized to SUCCESS and is left untouched when operation - allocation succeeds (returns a nonnull value), and 2) status is set - to SUCCESS by \_initializeOperation because \_initializeOperation - always returns SUCCESS because an asynchronous operation is - unwaitable, so initialization can't fail. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c - \_IotMqtt_DecrementOperationReferences - - * Always called with cancelJob false. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c - \_IotMqtt_DestroyOperation - - * Notify calls DestroyOperation with a linked, unwaitable - (asynchronous) SUBSCRIBE operation. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_Notify - - * ProcessSend calls Notify with an unwaitable (asynchronous) SUBSCRIBE - operation. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_ProcessSend - - * ProcessSsend is called with an UNSUBSCRIBE operation - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_completePendingSend - - * Always called with an unwaitable (asynchronous) and nonPUBLISH - (UNSUBSCRIBE) operation - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_initializeOperation - - * Always called with an unwaitable (asynchronous) operation - -* libraries/standard/mqtt/src/iot_mqtt_subscription.c \_topicMatch - - * Always called with exactMatchOnly == true - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateListSize - - * Proof assumption: list of topics to unsubscribe is nonempty - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateString - - * Proof assumption: length of topic filter is always positive. - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateSubscription - - * Always called with UNSUBSCRIBE operation type. - -Some functions are simply unreachable: - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_checkRetryLimit - - * Unreachable: Only function call is in an unreachable block of code - in ProcessSend. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_scheduleNextRetry - - * Unreachable: Only function call is in an unreachable block of code - in completePendingSend - -* libraries/standard/mqtt/src/iot_mqtt_subscription.c - \_IotMqtt_RemoveSubscriptionByPacket - - * Unreachable: Only function call in from an unreachable block of - subscriptionCommon - diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml deleted file mode 100644 index 87089222ce..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-batch.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# This file marks this directory as containing a CBMC proof. -# The contents of this file will be generated by continuous integration. \ No newline at end of file diff --git a/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json deleted file mode 100644 index e0236e7737..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeAsync/cbmc-viewer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ "expected-missing-functions": - [ - "IotClock_GetTimeMs", - "IotLog_Generic", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_strerror", - - "_IotMqtt_RemoveSubscriptionByPacket", - "_checkRetryLimit", - "_scheduleNextRetry" - ], - "proof-name": "IotMqtt_UnsubscribeAsync", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c b/cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c deleted file mode 100644 index aec76c571b..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeSync/IotMqtt_UnsubscribeSync_harness.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * @file IotMqtt_UnsubscribeSync_harness.c - * @brief Implements the proof harness for IotMqtt_UnsubscribeSync function. - */ - -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -#include "mqtt_state.h" - -/****************************************************************/ - -/** - * We abstract this function for performance reasons. In its original - * implementation, CBMC ended up creating byte extracts for all possible - * objects, due to the polymorphic nature of the linked list. We assume - * that the function is memory safe, and we free and havoc the list to - * demonstrate that no subsequent code makes use of the values in the list. - */ - -typedef bool ( *MatchFunction_t )( const IotLink_t * const pOperationLink, - void * pCompare ); -typedef void ( *FreeElementFunction_t )( void * pData ); - -void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - MatchFunction_t isMatch, - void * pMatch, - FreeElementFunction_t freeElement, - size_t linkOffset ) -{ - free_IotMqttSubscriptionList( pList ); - allocate_IotMqttSubscriptionList( pList, SUBSCRIPTION_COUNT_MAX - 1 ); -} - -/****************************************************************/ - -/** - * We constrain the return values of these functions because they - * are checked by assertions in the MQTT code. - */ - -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - IotTaskPoolJobStatus_t * const pStatus ) -{ - assert( taskPool == NULL || job == NULL ); - return IOT_TASKPOOL_BAD_PARAMETER; -} - -/****************************************************************/ - -/** - * _IotMqtt_NextPacketIdentifier calls Atomic_Add_u32, which receives - * a volatile variable as input. Thus, CBMC will always consider that - * Atomic_Add_u32 will operate over nondetermistic values and it raises - * an unsigned integer overflow failure. However, developers have reported - * that the use of this overflow is part of the function implementation. - * In order to mirror _IotMqtt_NextPacketIdentifier behaviour and avoid - * spurious alarms, we stub out this function to always - * return a nondetermistic odd value. - */ -uint16_t _IotMqtt_NextPacketIdentifier( void ) -{ - uint16_t id; - - /* Packet identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - __CPROVER_assume( id & 0x0001 == 1 ); - - return id; -} - -/****************************************************************/ - -/** - * We are abstracting the semaphores because we are doing sequential proof. - * But the semaphore API assures us that TimedWait called after Post will - * never fail. Our abstraction of the semaphores models this behavior. - */ -static unsigned int flagSemaphore; - -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - assert( pSemaphore != NULL ); - flagSemaphore++; -} - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - assert( pSemaphore != NULL ); - - if( flagSemaphore > 0 ) - { - flagSemaphore--; - return true; - } - - return false; -} - -/****************************************************************/ - -void harness() -{ - size_t subscriptionCount; - - __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); - - IotMqttSubscription_t * subscriptionList = - allocate_IotMqttSubscriptionArray( NULL, subscriptionCount ); - __CPROVER_assume( valid_IotMqttSubscriptionArray( subscriptionList, - subscriptionCount ) ); - - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection( NULL ); - __CPROVER_assume( mqttConnection != NULL ); - ensure_IotMqttConnection_has_lists( mqttConnection ); - __CPROVER_assume( valid_IotMqttConnection( mqttConnection ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_SEND( mqttConnection-> - pNetworkInterface ) ); - __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection-> - pNetworkInterface ) ); - - uint32_t flags; - uint32_t timeoutMs; - - /* initialize semaphore flag */ - flagSemaphore = 0; - - IotMqtt_UnsubscribeSync( mqttConnection, - subscriptionList, - subscriptionCount, - flags, - timeoutMs ); -} diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile b/cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile deleted file mode 100644 index 1c4abe61db..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeSync/Makefile +++ /dev/null @@ -1,106 +0,0 @@ -ENTRY=IotMqtt_UnsubscribeSync - -################################################################ -# Proof assumptions - -# Bound the number of subscriptions in a list (BOUND = MAX-1) -SUBSCRIPTION_COUNT_MAX=2 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# Bound the number of operations in a list (BOUND = MAX-1) -OPERATION_COUNT_MAX=2 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# Bound the length of subscription topics (BOUND = MAX-1) -TOPIC_LENGTH_MAX=8 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -# A constant that should be 2*SUBSCRIPTION_COUNT_MAX-1 -SUBSCRIPTION_LIST_MAX=3 -DEF += -DSUBSCRIPTION_LIST_MAX=$(SUBSCRIPTION_LIST_MAX) - -################################################################ -# MQTT configuration constants modified for testing - -# this should be smaller than topic_length_max for coverage -# _validateSubscription coverage at line 385 -AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=6 -DEF += -DAWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH=$(AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH) - -# this should be smaller than topic_length_max for coverage -# _IotMqtt_SubscriptionPacketSize coverage at line 702 -MQTT_MAX_REMAINING_LENGTH=6 -DEF += -DMQTT_MAX_REMAINING_LENGTH=$(MQTT_MAX_REMAINING_LENGTH) - -################################################################ -# Function omitted from the proof - -ABSTRACTIONS += --remove-function-body IotLog_Generic - -# Unreachable functions removed for accurate coverage computation -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceReceive -ABSTRACTIONS += --remove-function-body _IotMqtt_AddSubscriptions -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribe -ABSTRACTIONS += --remove-function-body _IotMqtt_SerializeSubscribeCommon -ABSTRACTIONS += --remove-function-body _matchEndWildcards -ABSTRACTIONS += --remove-function-body _matchWildcards -ABSTRACTIONS += --remove-function-body _mqttOperation_match -ABSTRACTIONS += --remove-function-body _mqttSubscription_setUnsubscribe -ABSTRACTIONS += --remove-function-body _packetMatch -ABSTRACTIONS += --remove-function-body _topicFilterMatch -ABSTRACTIONS += --remove-function-body _validateQos - -# Functions manually determined to be unreachable in their calling context -# See README.md for explanations of unreachability -ABSTRACTIONS += --remove-function-body _IotMqtt_RemoveSubscriptionByPacket -ABSTRACTIONS += --remove-function-body _IotMqtt_ScheduleOperation -ABSTRACTIONS += --remove-function-body _checkRetryLimit -ABSTRACTIONS += --remove-function-body _scheduleCallback -ABSTRACTIONS += --remove-function-body _scheduleNextRetry - -################################################################ - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_helper.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -DEF += -DCBMC_STUB_REMOVEALLMATCHES=1 - -# allow mqtt mallocs to fail for coverage -DEF += -include mqtt_state.h -DEF += -DIotMqtt_MallocMessage=malloc_can_fail -DEF += -DIotMqtt_MallocOperation=malloc_can_fail - -################################################################ -# Loop unwinding - -LOOP += IotListDouble_FindFirstMatch.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link1.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link2.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link3.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link4.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_FindFirstMatch\$$link5.0:$(SUBSCRIPTION_COUNT_MAX) - -LOOP += _IotMqtt_RemoveSubscriptionByTopicFilter.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_SerializeUnsubscribeCommon.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_SubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _IotMqtt_ValidateSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += _validateSubscription.0:$(TOPIC_LENGTH_MAX) -LOOP += allocate_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += strncmp.0:$(TOPIC_LENGTH_MAX) -LOOP += valid_IotMqttOperationList.0:$(OPERATION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionArray.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -################################################################ - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/README.md b/cbmc/proofs/IotMqtt_UnsubscribeSync/README.md deleted file mode 100644 index ae0cd683e8..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeSync/README.md +++ /dev/null @@ -1,133 +0,0 @@ -This is a memory safety proof for IotMqtt_UnsubscribeAsync. - -This proof attains 88% code coverage. The following comments explain -why the uncovered lines of code are unreachable code. - -Some functions contain unreachable blocks of code: - -* libraries/standard/common/include/iot_linear_containers.h IotListDouble_FindFirstMatch - - * Always called with a nonnull match predicate - -* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_OperationType - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_Wait - - * Wait is called with a good operation in a connected state. - -* libraries/standard/mqtt/src/iot_mqtt_api.c IotMqtt_strerror - - * Called with only a subset of error codes. - -* libraries/standard/mqtt/src/iot_mqtt_api.c \_sendMqttMessage - - * A synchronous operation is a blocking operation - -* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommon - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_api.c \_subscriptionCommonSetup - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_api.c \_waitForOperation - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_helper.c \_IotMqtt_RemainingLengthEncodedSize - - * Proof assumption: Proof bounds on number and size of subscription topics limits the - outbound packet to less than 128 bytes. - -* libraries/standard/mqtt/src/iot_mqtt_helper.c \_IotMqtt_SubscriptionPacketSize - - * Always called with UNSUBSCRIBE operation type - -* libraries/standard/mqtt/src/iot_mqtt_helper.c \_encodeRemainingLength - - * Proof assumption: Proof bounds on number and size of subscription topics limits the - outbound packet to less than 128 bytes. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_CreateOperation - - * Waitable operation has no callback - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_DecrementOperationReferences - - * TODO: TryCancel always fails - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_DestroyOperation - - * Always called with a linked operation - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_Notify - - * TODO - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_ProcessSend - - * Always called with a UNSUBSCRIBE operation - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_completePendingSend - - * The operation.periodic.retry union member is not valid for UNSUBSCRIBE. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_initializeOperation - - * Sync operation is always waitable. - -* libraries/standard/mqtt/src/iot_mqtt_subscription.c \_topicMatch - - * Always called with exactMatchOnly == true - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_IotMqtt_ValidateOperation - - * Operation is nonnull and waitable. - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateListSize - - * Proof assumption: list of topics to unsubscribe is nonempty - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateString - - * Proof assumption: length of topic filter is always positive. - -* libraries/standard/mqtt/src/iot_mqtt_validate.c \_validateSubscription - - * Always called with UNSUBSCRIBE operation type. - -Some functions are simply unreachable: - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_IotMqtt_ScheduleOperation - - * Unreachable: Only function calls are from unreachable - scheduleCallback, unreachable scheduleNextRetry, and an - unreachable block of sendMqttMessage. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_checkRetryLimit - - * Unreachable: Only function call is in an unreachable block of code - in ProcessSend. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_scheduleCallback - - * Unreachable: Only function call is from Notify, but waitable - operations have no callbacks. - -* libraries/standard/mqtt/src/iot_mqtt_operation.c \_scheduleNextRetry - - * Unreachable: Only function call is in an unreachable block of code - in completePendingSend - -* libraries/standard/mqtt/src/iot_mqtt_serialize.c \_IotMqtt_PublishSetDup - - * Unreachable: Only function call in from unreachable checkRetryLimit - -* libraries/standard/mqtt/src/iot_mqtt_subscription.c \_IotMqtt_RemoveSubscriptionByPacket - - * Unreachable: Only function call in from an unreachable block of - subscriptionCommon - diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml deleted file mode 100644 index 87089222ce..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-batch.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# This file marks this directory as containing a CBMC proof. -# The contents of this file will be generated by continuous integration. \ No newline at end of file diff --git a/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json b/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json deleted file mode 100644 index d31180ebeb..0000000000 --- a/cbmc/proofs/IotMqtt_UnsubscribeSync/cbmc-viewer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ "expected-missing-functions": - [ - "IotClock_GetTimeMs", - "IotLog_Generic", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotTaskPool_GetSystemTaskPool", - - "_IotMqtt_RemoveSubscriptionByPacket", - "_checkRetryLimit", - "_scheduleCallback", - "_scheduleNextRetry" - ], - "proof-name": "IotMqtt_UnsubscribeSync", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c b/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c deleted file mode 100644 index 6957b1c11b..0000000000 --- a/cbmc/proofs/IotMqtt_Wait/IotMqtt_Wait_harness.c +++ /dev/null @@ -1,32 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include -#include - -#include "mqtt_state.h" - -void harness() -{ - IotMqttConnection_t mqttConnection = allocate_IotMqttConnection(NULL); - __CPROVER_assume(mqttConnection != NULL); - __CPROVER_assume(mqttConnection->pNetworkInterface != NULL); - __CPROVER_assume( IS_STUBBED_NETWORKIF_DESTROY( mqttConnection->pNetworkInterface ) ); - ensure_IotMqttConnection_has_lists(mqttConnection); - __CPROVER_assume(valid_IotMqttConnection(mqttConnection)); - __CPROVER_assume(mqttConnection->references > 0); - - IotMqttOperation_t publishOperation = allocate_IotMqttOperation(NULL, mqttConnection); - __CPROVER_assume(valid_IotMqttOperation(publishOperation)); - - IotListDouble_Create( &( publishOperation->link )); - - if (nondet_bool()) - { - IotListDouble_InsertHead( &( mqttConnection->pendingProcessing ), &( publishOperation->link )); - } - - uint32_t timeoutMs; - IotMqtt_Wait( nondet_bool() ? publishOperation : NULL, timeoutMs ); -} - diff --git a/cbmc/proofs/IotMqtt_Wait/Makefile b/cbmc/proofs/IotMqtt_Wait/Makefile deleted file mode 100644 index 0ab3658aa1..0000000000 --- a/cbmc/proofs/IotMqtt_Wait/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -ENTRY=IotMqtt_Wait - -# We abstract all the log and concurrency related functions in this proof -# and assume their implementation is correct -ABSTRACTIONS += --remove-function-body _IotMqtt_ProcessIncomingPublish -ABSTRACTIONS += --remove-function-body _matchEndWildcards -ABSTRACTIONS += --remove-function-body _matchWildcards -ABSTRACTIONS += --remove-function-body _mqttOperation_match -ABSTRACTIONS += --remove-function-body _mqttOperation_tryDestroy -ABSTRACTIONS += --remove-function-body _packetMatch -ABSTRACTIONS += --remove-function-body _topicFilterMatch -ABSTRACTIONS += --remove-function-body _topicMatch -ABSTRACTIONS += --remove-function-body IotListDouble_Remove -ABSTRACTIONS += --remove-function-body 'IotListDouble_Remove$$link3' -ABSTRACTIONS += --remove-function-body IotLog_Generic -ABSTRACTIONS += --remove-function-body IotNetworkInterfaceClose - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/cbmc/proofs/mqtt_state.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_operation.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_validate.goto - -# One more than actual number of subscriptions in a subscription list -SUBSCRIPTION_COUNT_MAX=4 -DEF += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) - -# One more than actual number of operations in an operation list -OPERATION_COUNT_MAX=4 -DEF += -DOPERATION_COUNT_MAX=$(OPERATION_COUNT_MAX) - -# One more than actual length of topics -TOPIC_LENGTH_MAX=6 -DEF += -DTOPIC_LENGTH_MAX=$(TOPIC_LENGTH_MAX) - -LOOP += valid_IotMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += valid_IotMqttOperationList.0:$(SUBSCRIPTION_COUNT_MAX) -LOOP += IotListDouble_RemoveAllMatches.0:$(SUBSCRIPTION_COUNT_MAX) - -UNWINDING += --unwind 1 -UNWINDING += --unwindset '$(shell echo $(LOOP) | sed 's/ /,/g')' - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml deleted file mode 100644 index 4c2535b0ae..0000000000 --- a/cbmc/proofs/IotMqtt_Wait/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--unwind;1;--unwindset;valid_IotMqttSubscriptionList.0:4,valid_IotMqttOperationList.0:4,IotListDouble_RemoveAllMatches.0:4;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" -expected: SUCCESSFUL -goto: IotMqtt_Wait.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json b/cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json deleted file mode 100644 index 4d21020e0e..0000000000 --- a/cbmc/proofs/IotMqtt_Wait/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_Wait", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c b/cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c deleted file mode 100644 index 64972421e8..0000000000 --- a/cbmc/proofs/IotMqtt_strerror/IotMqtt_strerror_harness.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -void harness() -{ - IotMqttError_t status; - const char *pMessage = IotMqtt_strerror(status); - assert(pMessage != NULL); -} diff --git a/cbmc/proofs/IotMqtt_strerror/Makefile b/cbmc/proofs/IotMqtt_strerror/Makefile deleted file mode 100644 index 1c15a306e2..0000000000 --- a/cbmc/proofs/IotMqtt_strerror/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -ENTRY=IotMqtt_strerror - -OBJS += $(ENTRY)_harness.goto -OBJS += $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_api.goto - -include ../Makefile.common diff --git a/cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml b/cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml deleted file mode 100644 index 3b3467c140..0000000000 --- a/cbmc/proofs/IotMqtt_strerror/cbmc-batch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cbmcflags: "--object-bits;8;--bounds-check;--pointer-check;--unwinding-assertions;--nondet-static;--div-by-zero-check;--float-overflow-check;--nan-check;--pointer-overflow-check;--undefined-shift-check;--signed-overflow-check;--unsigned-overflow-check" -expected: SUCCESSFUL -goto: IotMqtt_strerror.goto -jobos: ubuntu16 diff --git a/cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json b/cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json deleted file mode 100644 index f2edc1ee88..0000000000 --- a/cbmc/proofs/IotMqtt_strerror/cbmc-viewer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ "expected-missing-functions": - [ - "IotLog_Generic", - "IotMqtt_Wait", - "IotMutex_Create", - "IotMutex_Destroy", - "IotMutex_Lock", - "IotMutex_Unlock", - "IotSemaphore_Create", - "IotSemaphore_Destroy", - "IotSemaphore_Post", - "IotSemaphore_TimedWait", - "IotSemaphore_Wait", - "IotTaskPool_CreateJob", - "IotTaskPool_GetSystemTaskPool", - "IotTaskPool_ScheduleDeferred", - "IotTaskPool_strerror", - "IotTaskPool_TryCancel" - ], - "proof-name": "IotMqtt_strerror", - "proof-root": "cbmc/proofs" -} diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common deleted file mode 100644 index f28773ed38..0000000000 --- a/cbmc/proofs/Makefile.common +++ /dev/null @@ -1,199 +0,0 @@ -# -*- mode: makefile -*- - -SHELL=/bin/bash - -default: report - -################################################################ -# Define source location and cbmc binaries - -# MQTT source directory relative to proof subdirectories. -MQTT ?= $(abspath ../../..) - -GOTO_CC ?= goto-cc -GOTO_INSTRUMENT ?= goto-instrument -GOTO_ANALYZER ?= goto-analyzer -BATCH ?= cbmc-batch -VIEWER ?= cbmc-viewer - -################################################################ -# Build goto binary for cbmc -# Build goto binaries with options taken from top-level cmake - -INC += \ - -I$(MQTT)/cbmc/proofs \ - -I$(MQTT)/demos \ - -I$(MQTT)/libraries/ \ - -I$(MQTT)/libraries/platform \ - -I$(MQTT)/libraries/standard/common/include \ - -I$(MQTT)/libraries/standard/mqtt/include \ - -I$(MQTT)/libraries/standard/mqtt/src \ - -I$(MQTT)/libraries/standard/mqtt/src/private \ - -I$(MQTT)/ports/common/include \ - -DEF += \ - -DIOT_SYSTEM_TYPES_FILE=\"$(MQTT)/ports/posix/include/iot_platform_types_posix.h\" \ - -Diotmqtt_EXPORTS \ - -DCBMC_OBJECT_BITS=$(CBMC_OBJECT_BITS) \ - -DCBMC_MAX_OBJECT_SIZE=$(CBMC_MAX_OBJECT_SIZE) \ - -DCBMC=1 \ - -CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 - -# The --nondet-static flag causes cbmc to ignore static initialization -# and havoc static variables to give them unconstrained initial values. -# Use --nondet-static-exclude to remove a variable from the list of -# static variables to havoc. Use goto-instrument --list-symbols to -# get the fully-qualified name of the variable to remove. Then add -# --nondet-static-exclude to NONDET in the proof makefile. -# For example: -# NONDET += --nondet-static-exclude _IotMqtt_SerializePingreq::1::pPingreq - -NONDET += --nondet-static - -%.goto : %.c - $(GOTO_CC) -o $@ $(CFLAGS) $< - -$(MQTT)/build: - (mkdir -p $(MQTT)/build; cd $(MQTT)/build; cmake ..) \ - > $(ENTRY)0.txt 2>&1 - -$(ENTRY)1.goto: $(MQTT)/build $(OBJS) - $(GOTO_CC) --function harness -o $@ $(OBJS) \ - > $(ENTRY)1.txt 2>&1 - -$(ENTRY)2.goto: $(ENTRY)1.goto - $(GOTO_INSTRUMENT) \ - $(ABSTRACTIONS) \ - $(NONDET) \ - --drop-unused-functions \ - --slice-global-inits $< $@ \ - > $(ENTRY)2.txt 2>&1 - -$(ENTRY).goto: $(ENTRY)2.goto - cp $< $@ - -################################################################ -# Set C compiler defines - -CBMC_OBJECT_BITS ?= 8 -CBMCFLAGS += --object-bits $(CBMC_OBJECT_BITS) -CBMC_MAX_OBJECT_SIZE = "(SIZE_MAX>>(CBMC_OBJECT_BITS+1))" - -################################################################ -# Run cbmc and build html report - -CBMCFLAGS += \ - $(UNWINDING) \ - --object-bits $(CBMC_OBJECT_BITS) \ - --bounds-check \ - --conversion-check \ - --div-by-zero-check \ - --float-overflow-check \ - --flush \ - --nan-check \ - --pointer-check \ - --pointer-overflow-check \ - --signed-overflow-check \ - --undefined-shift-check \ - --unsigned-overflow-check \ - --unwinding-assertions \ - -goto: $(ENTRY).goto - -cbmc.txt: $(ENTRY).goto - - cbmc $(CBMCFLAGS) --trace $< >$@ 2>&1 - -cbmc.xml: $(ENTRY).goto - - cbmc $(CBMCFLAGS) --trace --xml-ui $< >$@ 2>&1 - -cbmc.json: $(ENTRY).goto - - cbmc $(CBMCFLAGS) --trace --json-ui $< >$@ 2>&1 - -property.xml: $(ENTRY).goto - cbmc $(CBMCFLAGS) --show-properties --xml-ui $< >$@ 2>&1 - -property.json: $(ENTRY).goto - cbmc $(CBMCFLAGS) --show-properties --json-ui $< >$@ 2>&1 - -coverage.xml: $(ENTRY).goto - cbmc $(filter-out --unwinding-assertions,$(CBMCFLAGS)) \ - --cover location --xml-ui $< >$@ 2>&1 - -coverage.json: $(ENTRY).goto - cbmc $(filter-out --unwinding-assertions,$(CBMCFLAGS)) \ - --cover location --json-ui $< >$@ 2>&1 - -cbmc: cbmc.txt - -property: property.xml - -coverage: coverage.xml - -report: cbmc.txt property.xml coverage.xml - $(VIEWER) \ - --goto $(ENTRY).goto \ - --srcdir $(MQTT) \ - --blddir $(MQTT) \ - --htmldir html \ - --srcexclude "(./verification|./tests|./tools|./lib/third_party)" \ - --result cbmc.txt \ - --property property.xml \ - --block coverage.xml - -clean: - $(RM) $(OBJS) $(ENTRY).goto - $(RM) $(ENTRY)[0-9].goto $(ENTRY)[0-9].txt - $(RM) cbmc.txt property.xml coverage.xml TAGS TAGS-* - $(RM) *~ \#* - -veryclean: clean - $(RM) -r html - -.PHONY: default goto cbmc property coverage report clean veryclean - -################################################################ -# Build configuration file to run cbmc under cbmc-batch in CI - -CBMC_FLAGS = '$(call encode_options,$(CBMCFLAGS))' -cbmc-batch.yaml: Makefile ../Makefile.common - @echo "Building $@" - @$(RM) $@ - @echo 'build_memory: $(PROOFMEM)' > $@ - @echo 'cbmcflags: $(strip $(call yaml_encode_options,$(CBMCFLAGS)))' >> $@ - @echo 'coverage_memory: $(PROOFMEM)' >> $@ - @echo "expected: SUCCESSFUL" >> $@ - @echo "goto: $(ENTRY).goto" >> $@ - @echo "jobos: $(JOBOS)" >> $@ - @echo 'property_memory: $(PROOFMEM)' >> $@ - @echo 'report_memory: $(PROOFMEM)' >> $@ - -.PHONY: cbmc-batch.yaml - -################################################################ -# Use the latest version of cbmc viewer - -VIEWER2=viewer -MAKE_SOURCES=make-sources - -sources.json: - - $(MAKE_SOURCES) --root $(MQTT) --build . > $@ - -report2: sources.json cbmc.xml property.xml coverage.xml - - $(VIEWER2) \ - --viewer-sources sources.json \ - --goto $(ENTRY).goto \ - --srcdir $(MQTT) \ - --htmldir html \ - --result cbmc.xml \ - --property property.xml \ - --coverage coverage.xml - -clean2: clean - $(RM) cbmc.xml sources.json - -veryclean2: veryclean - $(RM) cbmc.xml sources.json - $(RM) viewer-*.json - -.PHONY: report2 clean2 veryclean2 diff --git a/cbmc/proofs/make_cbmc_batch_files.py b/cbmc/proofs/make_cbmc_batch_files.py deleted file mode 100644 index 622e000c3a..0000000000 --- a/cbmc/proofs/make_cbmc_batch_files.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -# -# Generation of the cbmc-batch.yaml files for the CBMC proofs. -# -# Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import os -import platform -import subprocess - - -def remove_cbmc_yaml_files(): - for dyr, _, files in os.walk("."): - cbmc_batch_files = [os.path.join(os.path.abspath(dyr), file) - for file in files if file == "cbmc-batch.yaml"] - for file in cbmc_batch_files: - os.remove(file) - - -def create_cbmc_yaml_files(): - # The YAML files are only used by CI and are not needed on Windows. - if platform.system() == "Windows": - return - for dyr, _, files in os.walk("."): - harness = [file for file in files if file.endswith("_harness.c")] - if harness and "Makefile" in files: - subprocess.run(["make", "cbmc-batch.yaml"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=os.path.abspath(dyr), - check=True) - -if __name__ == '__main__': - remove_cbmc_yaml_files() - create_cbmc_yaml_files() diff --git a/cbmc/proofs/mqtt_state.c b/cbmc/proofs/mqtt_state.c deleted file mode 100644 index 83f655ba28..0000000000 --- a/cbmc/proofs/mqtt_state.c +++ /dev/null @@ -1,779 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -#include "mqtt_state.h" - -/**************************************************************** - * Bound the lengths of subscription and operation lists. - * - * Lists are empty by defaut. - * Set this variable to L+1 for lists of length <= L. - ****************************************************************/ - -#ifndef SUBSCRIPTION_COUNT_MAX - #define SUBSCRIPTION_COUNT_MAX 1 -#endif - -#ifndef OPERATION_COUNT_MAX - #define OPERATION_COUNT_MAX 1 -#endif - -/**************************************************************** - * Model a malloc that can fail and return NULL. - ****************************************************************/ - -void *malloc_can_fail( size_t size ) -{ - __CPROVER_assert( VALID_CBMC_SIZE( size ), "malloc_can_fail size too big" ); - return nondet_bool() ? NULL : malloc( size ); -} - -/**************************************************************** - * Model an opaque type as nothing but a valid pointer. - ****************************************************************/ - -void *allocate_opaque_type() -{ - return malloc( 1 ); // consider using malloc(0) -} - -/**************************************************************** - * MqttOperation - ****************************************************************/ - -IotMqttOperation_t allocate_IotMqttOperation( IotMqttOperation_t pOp, - IotMqttConnection_t pConn ) -{ - // assume a valid connection for the operation - assert( pConn != NULL ); - - if ( pOp == NULL ) pOp = malloc_can_fail( sizeof( *pOp ) ); - if ( pOp == NULL ) return NULL; - - pOp->pMqttConnection = pConn; - - if ( pOp->incomingPublish ) { - // allocate publish member of the union - - allocate_IotMqttPublishInfo( &(pOp->u.publish.publishInfo ) ); - - size_t length; - __CPROVER_assume( VALID_CBMC_SIZE( length ) ); - pOp->u.publish.pReceivedData = malloc_can_fail( length ); - - } else { - // allocate operation member of the union - - // assumption here is checked below in valid_* - __CPROVER_assume( VALID_CBMC_SIZE( pOp->u.operation.packetSize ) ); - pOp->u.operation.pMqttPacket = - malloc_can_fail( pOp->u.operation.packetSize ); - - } - - return pOp; -} - -// TODO: valid_ should have same signature as allocate_, -// should check that operation points to the connection - -bool valid_IotMqttOperation( const IotMqttOperation_t pOp ) -{ - if ( pOp == NULL ) return false; - - // publish member of the union should be valid (pOp->incomingPublish ) - - bool valid_publishinfo = - valid_IotMqttPublishInfo( &(pOp->u.publish.publishInfo) ); - - bool valid_publish_member = - IMPLIES( pOp->incomingPublish, valid_publishinfo ); - - // operation member of the union should be valid (!pOp->incomingPublish) - - bool valid_packet = - VALID_STRING( pOp->u.operation.pMqttPacket, pOp->u.operation.packetSize ) && -#ifdef PACKET_SIZE_MAX - // Some proofs iterate over a packet and must bound the packet size - pOp->u.operation.packetSize < PACKET_SIZE_MAX && -#endif - VALID_CBMC_SIZE( pOp->u.operation.packetSize ); - bool valid_pingreq_packet = - IMPLIES( pOp->u.operation.type == IOT_MQTT_PINGREQ, - IFF( pOp->u.operation.periodic.ping.keepAliveMs == 0, - pOp->u.operation.packetSize == 0 ) ); - bool valid_other_packet = - IMPLIES( pOp->u.operation.type != IOT_MQTT_PINGREQ, - pOp->u.operation.packetSize > 0 ); - bool waitable = - ( pOp->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE; - bool valid_jobReference = - // Async operations are waitable. Loosely speaking, an async operation - // is split into independent send and ack events, and an sync operation - // is not. - IMPLIES( waitable, pOp->u.operation.jobReference == 2 ) && - IMPLIES( !waitable, pOp->u.operation.jobReference == 1 ); - - bool valid_operation_member = - IMPLIES( !pOp->incomingPublish, - valid_packet && - valid_pingreq_packet && - valid_other_packet && - valid_jobReference ); - - return - // assume valid connection - valid_publish_member && - valid_operation_member; -} - -/**************************************************************** - * IotMqttOperation list - ****************************************************************/ - -IotListDouble_t *allocate_IotMqttOperationList( IotListDouble_t *pOp, - size_t length, - IotMqttConnection_t pConn ) -{ - // Allocate lists of length L <= 3 (MAX = L+1) - __CPROVER_assert(length < OPERATION_COUNT_MAX, - "Operation list length max is greater than MAX"); - __CPROVER_assert(length <= 3, - "Operation list length max is greater than 3"); - - if ( pOp == NULL ) pOp = malloc_can_fail( sizeof( *pOp ) ); - if ( pOp == NULL ) return NULL; - - IotListDouble_Create( pOp ); - - size_t numElts; - __CPROVER_assume(numElts <= length); - - IotMqttOperation_t pElt; - switch (numElts) { -#if 3 < OPERATION_COUNT_MAX - case 3: - pElt = allocate_IotMqttOperation( NULL, pConn ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pOp, &( pElt->link ) ); -#endif -#if 2 < OPERATION_COUNT_MAX - case 2: - pElt = allocate_IotMqttOperation( NULL, pConn ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pOp, &( pElt->link ) ); -#endif -#if 1 < OPERATION_COUNT_MAX - case 1: - pElt = allocate_IotMqttOperation( NULL, pConn ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pOp, &( pElt->link ) ); -#endif - default: - ; - } - - return pOp; -} - -bool valid_IotMqttOperationList( const IotListDouble_t *pOp, - const size_t length ) -{ - if ( pOp == NULL ) return false; - - // TODO: Consider replacing loop with straight line code - - IotListDouble_t *pLink; - IotContainers_ForEach( pOp, pLink ) { - IotMqttOperation_t *pElt = IotLink_Container( struct _mqttOperation, pLink, link ); - if (! valid_IotMqttOperation( pElt ) ) return false; - } - - return - // MAX is one greater than the maximum length - length < OPERATION_COUNT_MAX; -} - -/**************************************************************** - * IotMqttConnection - ****************************************************************/ - -IotMqttConnection_t allocate_IotMqttConnection( IotMqttConnection_t pConn ) -{ - if ( pConn == NULL ) pConn = malloc_can_fail( sizeof( *pConn ) ); - if ( pConn == NULL ) return NULL; - - pConn->pNetworkConnection = allocate_IotNetworkConnection(); - pConn->pNetworkInterface = allocate_IotNetworkInterface(); - allocate_IotMqttOperation( &(pConn->pingreq ), pConn ); - allocate_IotMqttCallbackInfo( &(pConn->disconnectCallback) ); - return pConn; -} - -IotMqttConnection_t ensure_IotMqttConnection_has_lists( IotMqttConnection_t pConn ) -{ - allocate_IotMqttOperationList( &pConn->pendingProcessing, - OPERATION_COUNT_MAX - 1, - pConn ); - allocate_IotMqttOperationList( &pConn->pendingResponse, - OPERATION_COUNT_MAX - 1, - pConn ); - allocate_IotMqttSubscriptionList( &pConn->subscriptionList, - SUBSCRIPTION_COUNT_MAX - 1 ); - return pConn; -} - -bool valid_IotMqttConnection( const IotMqttConnection_t pConn ) -{ - if ( pConn == NULL ) return false; - - // This is the number of callbacks and operations using the connection. - // It is a uint32 and must be bounded by a number smaller than the - // maximum value to avoid integer overflows. We expect to run out of - // memory before having 2^16 references on a device. - bool valid_references = pConn->references >= 0 && - pConn->references <= (1 << 16); - - bool valid_pingreq = - ( valid_IotMqttOperation( &(pConn->pingreq ) ) ) && - ( pConn->pingreq.u.operation.type == IOT_MQTT_PINGREQ ) && - ( pConn->pingreq.pMqttConnection == pConn ) && - ( !pConn->pingreq.incomingPublish ) ; - - return - valid_IotMqttOperationList( &pConn->pendingProcessing, - OPERATION_COUNT_MAX - 1 ) && - valid_IotMqttOperationList( &pConn->pendingResponse, - OPERATION_COUNT_MAX - 1 ) && - valid_IotMqttSubscriptionList( &pConn->subscriptionList, - SUBSCRIPTION_COUNT_MAX - 1 ) && - valid_IotNetworkInterface( pConn->pNetworkInterface ) && - valid_references && - valid_pingreq; -} - -/**************************************************************** - * IotMqttNetworkInfo - ****************************************************************/ - -IotMqttNetworkInfo_t *allocate_IotMqttNetworkInfo( IotMqttNetworkInfo_t *pInfo ) -{ - if ( pInfo == NULL ) pInfo = malloc_can_fail( sizeof( *pInfo ) ); - if ( pInfo == NULL ) return NULL; - - if ( pInfo->createNetworkConnection ) { - // allocate setup member of union - pInfo->u.setup.pNetworkServerInfo = allocate_opaque_type(); - pInfo->u.setup.pNetworkCredentialInfo = allocate_opaque_type(); - } else { - // allocate network member of union - pInfo->u.pNetworkConnection = allocate_IotNetworkConnection(); - } - pInfo->pNetworkInterface = allocate_IotNetworkInterface(); - return pInfo; -} - -bool valid_IotMqttNetworkInfo( const IotMqttNetworkInfo_t *pInfo ) -{ - return pInfo != NULL; -} - -/**************************************************************** - * IotMqttConnectInfo - ****************************************************************/ - -IotMqttConnectInfo_t *allocate_IotMqttConnectInfo( IotMqttConnectInfo_t *pInfo ) -{ - if ( pInfo == NULL ) pInfo = malloc_can_fail( sizeof( *pInfo ) ); - if ( pInfo == NULL ) return NULL; - - pInfo->pPreviousSubscriptions = - allocate_IotMqttSubscriptionArray( NULL, - pInfo->previousSubscriptionCount ); - pInfo->pWillInfo = allocate_IotMqttPublishInfo( NULL ); - pInfo->pClientIdentifier = malloc_can_fail( pInfo->clientIdentifierLength ); - pInfo->pUserName = malloc_can_fail( pInfo->userNameLength ); - pInfo->pPassword = malloc_can_fail( pInfo->passwordLength ); -} - -bool valid_IotMqttConnectInfo( const IotMqttConnectInfo_t *pInfo ) -{ - return - pInfo != NULL && - - VALID_STRING( pInfo->pClientIdentifier, pInfo->clientIdentifierLength ) && - VALID_CBMC_SIZE( pInfo->clientIdentifierLength ) && - - VALID_STRING( pInfo->pUserName, pInfo->userNameLength ) && - VALID_CBMC_SIZE( pInfo->userNameLength ) && - - VALID_STRING( pInfo->pPassword, pInfo->passwordLength ) && - VALID_CBMC_SIZE( pInfo->passwordLength ) && - - // MAX is one greater than the maximum length - pInfo->previousSubscriptionCount < SUBSCRIPTION_COUNT_MAX && - - IFF( pInfo->pPreviousSubscriptions == NULL, - pInfo->previousSubscriptionCount == 0 ) && - valid_IotMqttSubscriptionArray( pInfo->pPreviousSubscriptions, - pInfo->previousSubscriptionCount ); -} - -/**************************************************************** - * IotMqttSubscription - ****************************************************************/ - -IotMqttSubscription_t *allocate_IotMqttSubscription( IotMqttSubscription_t *pSub ) -{ - if ( pSub == NULL ) pSub = malloc_can_fail( sizeof( *pSub ) ); - if ( pSub == NULL ) return NULL; - - pSub->pTopicFilter = malloc_can_fail( pSub->topicFilterLength ); - return pSub; -} - -bool valid_IotMqttSubscription( const IotMqttSubscription_t *pSub ) -{ - return - pSub != NULL && - - VALID_STRING( pSub->pTopicFilter, pSub->topicFilterLength ) && - VALID_CBMC_SIZE( pSub->topicFilterLength ) - -#ifdef TOPIC_LENGTH_MAX - // MAX is one greater than the maximum length - && pSub->topicFilterLength < TOPIC_LENGTH_MAX -#endif - ; -} - -/**************************************************************** - * IotMqttSubscription array list - ****************************************************************/ - -IotMqttSubscription_t *allocate_IotMqttSubscriptionArray( IotMqttSubscription_t *pSub, - size_t length ) -{ - // Allocate lists of length L <= 3 (MAX = L+1) - __CPROVER_assert(length < SUBSCRIPTION_COUNT_MAX, - "Subscription array length max is greater than MAX"); - __CPROVER_assert(length <= 3, - "Subscription array length max is greater than 3"); - - if ( pSub == NULL ) pSub = malloc_can_fail( length * sizeof( *pSub ) ); - if ( pSub == NULL ) return NULL; - - switch (length) { -#if 3 < SUBSCRIPTION_COUNT_MAX - case 3: - allocate_IotMqttSubscription( pSub + 2 ); -#endif -#if 2 < SUBSCRIPTION_COUNT_MAX - case 2: - allocate_IotMqttSubscription( pSub + 1 ); -#endif -#if 1 < SUBSCRIPTION_COUNT_MAX - case 1: - allocate_IotMqttSubscription( pSub + 0 ); -#endif - default: - ; - } - - return pSub; -} -bool valid_IotMqttSubscriptionArray( const IotMqttSubscription_t *pSub, - const size_t length ) -{ - if ( !IFF( pSub == NULL, length == 0 ) ) return false; - if ( pSub == NULL ) return false; - - // TODO: Consider replacing loop with straight line code - - for ( size_t i = 0; i < length; i++ ) - if ( !valid_IotMqttSubscription( pSub + i ) ) return false; - return - // MAX is one greater than the maximum length - length < SUBSCRIPTION_COUNT_MAX; -} - -/**************************************************************** - * IotMqttSubscription array list - ****************************************************************/ - -// Subscription linked list elements - -_mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *pElt ) -{ - uint16_t length; - - if ( pElt == NULL ) pElt = malloc_can_fail( sizeof( *pElt ) + length ); - if ( pElt == NULL ) return NULL; - - // References must never be negative - __CPROVER_assume( pElt->references >= 0 ); - pElt->link.pPrevious = NULL; - pElt->link.pNext = NULL; - pElt->topicFilterLength = length; - // pElt->topicFilter is a flexible struct member following pElt - return pElt; -} - -bool valid_IotMqttSubscriptionListElt( const _mqttSubscription_t *pElt ) -{ - if ( pElt == NULL ) return false; - - return -#ifdef TOPIC_LENGTH_MAX - // MAX is one greater than the maximum length - pElt->topicFilterLength < TOPIC_LENGTH_MAX && -#endif - pElt->topicFilterLength < CBMC_MAX_OBJECT_SIZE - sizeof( *pElt ); -} - -// Subscription linked lists - -IotListDouble_t *allocate_IotMqttSubscriptionList( IotListDouble_t *pSub, - size_t length ) -{ - // Allocate lists of length L <= 3 (MAX = L+1) - __CPROVER_assert(length < SUBSCRIPTION_COUNT_MAX, - "Subscription list length max is greater than MAX"); - __CPROVER_assert(length <= 3, - "Subscription list length max is greater than 3"); - - if ( pSub == NULL ) pSub = malloc_can_fail( sizeof( *pSub ) ); - if ( pSub == NULL ) return NULL; - - IotListDouble_Create( pSub ); - - size_t numElts; - __CPROVER_assume(numElts <= length); - - _mqttSubscription_t *pElt; - switch (numElts) { -#if 3 < SUBSCRIPTION_COUNT_MAX - case 3: - pElt = allocate_IotMqttSubscriptionListElt( NULL ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pSub, &( pElt->link ) ); -#endif -#if 2 < SUBSCRIPTION_COUNT_MAX - case 2: - pElt = allocate_IotMqttSubscriptionListElt( NULL ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pSub, &( pElt->link ) ); -#endif -#if 1 < SUBSCRIPTION_COUNT_MAX - case 1: - pElt = allocate_IotMqttSubscriptionListElt( NULL ); - __CPROVER_assume( pElt ); - IotListDouble_InsertHead( pSub, &( pElt->link ) ); -#endif - default: - ; - } - - return pSub; -} -bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, - const size_t length ) -{ - if ( pSub == NULL ) return false; - - // TODO: Consider replacing loop with straight line code - - IotListDouble_t *pLink; - IotContainers_ForEach( pSub, pLink ) { - _mqttSubscription_t - *pElt = IotLink_Container( _mqttSubscription_t, pLink, link ); - if ( !valid_IotMqttSubscriptionListElt( pElt ) ) return false; - } - - return - // MAX is one greater than the maximum length - length < SUBSCRIPTION_COUNT_MAX; -} - -void *invalid_pointer() -{ - void *ptr; - return ptr; -} - -void free_IotMqttSubscriptionList( IotListDouble_t *pSub ) -{ - IotListDouble_t *pThis; - IotListDouble_t *pNext; - - if ( pSub == NULL ) return; - pThis = pSub->pNext; - pSub->pNext = invalid_pointer(); - pSub->pPrevious = invalid_pointer(); - -#if 3 < SUBSCRIPTION_COUNT_MAX - if ( pThis == pSub ) return; - pNext = pThis->pNext; - free( IotLink_Container( _mqttSubscription_t, pThis, link ) ); - pThis = pNext; -#endif - -#if 2 < SUBSCRIPTION_COUNT_MAX - if ( pThis == pSub ) return; - pNext = pThis->pNext; - free( IotLink_Container( _mqttSubscription_t, pThis, link ) ); - pThis = pNext; -#endif - -#if 1 < SUBSCRIPTION_COUNT_MAX - if ( pThis == pSub ) return; - pNext = pThis->pNext; - free( IotLink_Container( _mqttSubscription_t, pThis, link ) ); - pThis = pNext; -#endif - - /* The being freed could be longer than SUBSCRIPTION_COUNT_MAX: - * Allocate a list of maximal length, then add one subscription, then free it. */ - return; -} - - -/**************************************************************** - * IotMqttPublishInfo - ****************************************************************/ - -IotMqttPublishInfo_t *allocate_IotMqttPublishInfo( IotMqttPublishInfo_t *pInfo ) -{ - if ( pInfo == NULL ) pInfo = malloc_can_fail( sizeof( *pInfo ) ); - if ( pInfo == NULL ) return NULL; - - pInfo->pTopicName = malloc_can_fail( pInfo->topicNameLength ); - // assumption here is checked below in valid_ - __CPROVER_assume( VALID_CBMC_SIZE( pInfo->payloadLength ) ); - pInfo->pPayload = malloc_can_fail( pInfo->payloadLength ); - return pInfo; -} - -bool valid_IotMqttPublishInfo( const IotMqttPublishInfo_t *pInfo ) -{ - return - pInfo && - - VALID_QOS(pInfo->qos) && - - VALID_STRING( pInfo->pTopicName, pInfo->topicNameLength ) && - VALID_CBMC_SIZE( pInfo->topicNameLength ) && - pInfo->pTopicName != NULL && - - VALID_STRING( pInfo->pPayload, pInfo->payloadLength ) && - VALID_CBMC_SIZE( pInfo->payloadLength ) && -#ifdef PAYLOAD_LENGTH_MAX - // Some proofs iterate over it - pInfo->payloadLength < PAYLOAD_LENGTH_MAX && -#endif - - pInfo->retryMs <= IOT_MQTT_RETRY_MS_CEILING && - pInfo->retryMs > 0 && - - // TODO: experiment with removing these assumptions - // topicNameLength is a unint16 - pInfo->topicNameLength <= UINT16_MAX && - pInfo->payloadLength > 0; -} - -/**************************************************************** - * IotNetworkConnection - ****************************************************************/ - -void *allocate_IotNetworkConnection() -{ - return allocate_opaque_type(); -} - -/**************************************************************** - * IotNetworkInterface - ****************************************************************/ - -IotNetworkInterface_t *allocate_IotNetworkInterface() -{ - return malloc_can_fail( sizeof( IotNetworkInterface_t ) ); -} - -bool valid_IotNetworkInterface( const IotNetworkInterface_t *netif ) -{ - return ( netif != NULL ); -} - -bool stubbed_IotNetworkInterface( const IotNetworkInterface_t *netif ) -{ - return - IS_STUBBED_NETWORKIF_CREATE( netif ) && - IS_STUBBED_NETWORKIF_CLOSE( netif ) && - IS_STUBBED_NETWORKIF_SEND( netif ) && - IS_STUBBED_NETWORKIF_RECEIVE( netif ) && - IS_STUBBED_NETWORKIF_SETRECEIVECALLBACK( netif ) && - IS_STUBBED_NETWORKIF_SETCLOSECALLBACK( netif ) && - IS_STUBBED_NETWORKIF_DESTROY( netif ); -} - -/**************************************************************** - * IotNetworkInterface stubs - ****************************************************************/ - -IotNetworkError_t IotNetworkInterfaceCreate( void * pConnectionInfo, - void * pCredentialInfo, - void * pConnection ) -{ - // pCredentialInfo can be null - // create accepts NULL credentials when there is no TLS configuration. - __CPROVER_assert( pConnectionInfo != NULL, - "IotNetworkInterfaceCreate pConnectionInfo is not NULL" ); - __CPROVER_assert( pConnection != NULL, - "IotNetworkInterfaceCreate pConnection is not NULL" ); - - // create the connection - *(void ** )pConnection = allocate_IotNetworkConnection(); - - IotNetworkError_t error; - return error; -} - -#ifndef MAX_TRIES - #define MAX_TRIES 2 -#endif - -size_t IotNetworkInterfaceSend( void * pConnection, - const uint8_t * pMessage, - size_t messageLength ) -{ - __CPROVER_assert( pConnection != NULL, - "IotNetworkInterfaceSend pConnection is not NULL" ); - __CPROVER_assert( pMessage != NULL, - "IotNetworkInterfaceSend pMessage is not NULL" ); - - /**************************************************************** - * The send method sends some portion of the message and returns the - * total number of bytes in the prefix sent so far. The send method - * is used in a loop of the form - * - * while ( send( conn, msg, len ) < len ) { ... } - * - * We need to bound the number of loop iterations, so we need to - * bound the number of times it takes for send to finish sending the - * message. We use a static variable 'tries' to count the number of - * times send has tried to send the message, and we force send to - * finish the message after MAX_TRIES tries. - ****************************************************************/ - - // The number of tries to send the message before this invocation - static size_t tries; - - // The number of bytes considered sent after this invocation - size_t size; - - if ( tries >= MAX_TRIES || size > messageLength ) - { - tries = 0; - return messageLength; - } - - tries++; - return size; -} - -IotNetworkError_t IotNetworkInterfaceClose( void * pConnection ) -{ - __CPROVER_assert( pConnection != NULL, - "IotNetworkInterfaceClose pConnection is not NULL" ); - - IotNetworkError_t error; - return error; -} - -size_t IotNetworkInterfaceReceive( void * pConnection, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - __CPROVER_assert( pConnection, - "IotNetworkInterfaceReceive pConnection is not NULL" ); - __CPROVER_assert( pBuffer, - "IotNetworkInterfaceReceive pBuffer is not NULL" ); - - // Fill the memory object pointed to by pBuffer with unconstrained - // data. What follows a CBMC idiom to do that. - uint8_t byte; - __CPROVER_array_copy( pBuffer, &byte ); - - // Choose the number of bytes in pBuffer considered received. - size_t size; - __CPROVER_assume( size <= bytesRequested ); - - return size; -} - -IotNetworkError_t IotNetworkInterfaceReceiveCallback( void * pConnection, - IotNetworkReceiveCallback_t - receiveCallback, - void * pContext ) -{ - __CPROVER_assert( pConnection != NULL, - "IotNetworkInterfaceCallback pConnection is not NULL" ); - __CPROVER_assert( receiveCallback != NULL, - "IotNetworkInterfaceCallback receiveCallback is not NULL" ); - __CPROVER_assert( pContext != NULL, - "IotNetworkInterfaceCallback pContext is not NULL" ); - - IotNetworkError_t error; - return error; -} - -IotNetworkError_t IotNetworkInterfaceCloseCallback( void * pConnection, - IotNetworkCloseCallback_t - closeCallback, - void * pContext ) -{ - __CPROVER_assert( pConnection != NULL, - "IotNetworkInterfaceCallback pConnection is not NULL" ); - __CPROVER_assert( closeCallback != NULL, - "IotNetworkInterfaceCallback closeCallback is not NULL" ); - __CPROVER_assert( pContext != NULL, - "IotNetworkInterfaceCallback pContext is not NULL" ); - - IotNetworkError_t error; - return error; -} - -IotNetworkError_t IotNetworkInterfaceDestroy( void * pConnection ) -{ - __CPROVER_assert( pConnection != NULL, - "IotNetworkInterfaceDestroy pConnection is not NULL" ); - - IotNetworkError_t error; - return error; -} - -/****************************************************************/ - -IotMqttCallbackInfo_t *allocate_IotMqttCallbackInfo(IotMqttCallbackInfo_t *pCb) -{ - if ( pCb == NULL ) pCb = malloc_can_fail( sizeof( *pCb ) ); - if ( pCb == NULL ) return NULL; - - pCb->pCallbackContext = allocate_opaque_type(); - pCb->function = nondet_bool() ? NULL : IotUserCallback; - - return pCb; -} - -void IotUserCallback( void * pCallbackContext, - IotMqttCallbackParam_t * pCallbackParam ) -{ - __CPROVER_assert( pCallbackContext != NULL, - "IotUserCallback pCallbackContext is not NULL" ); - __CPROVER_assert( pCallbackParam != NULL, - "IotUserCallback pCallbackParam is not NULL" ); - return; -} diff --git a/cbmc/proofs/mqtt_state.h b/cbmc/proofs/mqtt_state.h deleted file mode 100644 index 237b8cb31b..0000000000 --- a/cbmc/proofs/mqtt_state.h +++ /dev/null @@ -1,236 +0,0 @@ -#include "iot_config.h" -#include "private/iot_mqtt_internal.h" - -#include - -/**************************************************************** - * Logical connectives useful in assuptions - ****************************************************************/ - -#define IMPLIES(a, b) (!(a) || (b)) -#define IFF(a, b) (IMPLIES(a, b) && IMPLIES(b, a)) -//#define IFF(a, b) ((a) == (b)) - -/**************************************************************** - * String pointers satisfy an invariant that the pointer is null - * iff the length is zero. The string and length are typically - * members of a struct, and this invariant is part of a validity - * condition for the struct. - ****************************************************************/ - -#define VALID_STRING(string, length) IFF((string) == NULL, (length) == 0) - -/**************************************************************** - * There is a bound on the size of an object that can be modeled in - * CBMC. A point in CBMC consists of an object id and an offset - * into the object. The top few bits of a pointer are used to encode - * the object id, leaving only the bottom remaining bits to encode - * the object offset. The number of bits used for the object id, - * that thus the bound on the size of the object, is defined in the - * Makefile. - ****************************************************************/ - -#define VALID_CBMC_SIZE(size) ((size) < CBMC_MAX_OBJECT_SIZE) - -/**************************************************************** - * According to the documentation, IOT_MQTT_QOS_2 is not entirely - * supported, but it is expected that all functions that deals - * with MQTT PUBLISH messages will be resilient to it. - ****************************************************************/ -#define VALID_QOS(qos) ( qos == IOT_MQTT_QOS_0 || \ - qos == IOT_MQTT_QOS_1 || \ - qos == IOT_MQTT_QOS_2 ) - -/**************************************************************** - * Model a malloc that can fail and return NULL. CBMC currently - * models malloc as an allocator that never fails. CBMC will soon - * have an option to let malloc fail. - ****************************************************************/ - -void *malloc_can_fail(size_t size ); - -/**************************************************************** - * Type allocators and validators - * - * For every type used by MQTT, like a connection or an operation, we - * provide an allocator and a validator. The purpose of the allocator - * is simply to allocate on the heap the space for an unconstrained - * value of the right shape. The purpose of the validator is to state - * restrictions on the possible values of the object. - * - * A type is typically a tree of structs and buffers and arrays. The - * allocator lays out space on the heap for this tree. The allocator - * may, however, prune the tree in arbitrary ways by inserting NULL - * into a pointer in a struct intended to point to the children of the - * struct. The allocator may even prune away the entire tree and - * return nothing but a NULL pointer. If a proof requires that some - * portion of the tree is not pruned away (that some pointer is not - * NULL), this assumption must be made explicitly, either in the - * validator or in the proof harness itself. - * - * Each allocator takes a pointer to the struct at the root of the - * tree. If that pointer is NULL, the allocator allocates the root - * struct and the result of the tree. If it points to an existing - * root struct, the allocators uses that root and fills in the rest of - * the tree. The pointer is usually NULL. In constrast, because a - * connection struct includes an ping request operation struct as a - * substruct, we allocate that ping request operation by passing a - * pointer to the connection's operation substruct. - ****************************************************************/ - -/**************************************************************** - * IotMqttConnection - ****************************************************************/ - -IotMqttConnection_t allocate_IotMqttConnection( IotMqttConnection_t pConn ); -bool valid_IotMqttConnection( const IotMqttConnection_t pConn ); -IotMqttConnection_t - ensure_IotMqttConnection_has_lists( IotMqttConnection_t pConn ); - -/**************************************************************** - * MqttOperation - * - * A pending operation includes a pointer to its connection. A - * connection includes a list of all of its pending operations. All - * operations pending on a connection include a pointer to this - * connection. For this reason, the allocator for an operation takes - * a reference to a connection. - ****************************************************************/ - -IotMqttOperation_t allocate_IotMqttOperation( IotMqttOperation_t pOp, - IotMqttConnection_t pConn ); -bool valid_IotMqttOperation( const IotMqttOperation_t pOp ); - -/**************************************************************** - * IotMqttNetworkInfo - ****************************************************************/ - -IotMqttNetworkInfo_t *allocate_IotMqttNetworkInfo( IotMqttNetworkInfo_t *pInfo ); -bool valid_IotMqttNetworkInfo( const IotMqttNetworkInfo_t *pInfo ); - -/**************************************************************** - * IotMqttConnectInfo - ****************************************************************/ - -IotMqttConnectInfo_t *allocate_IotMqttConnectInfo( IotMqttConnectInfo_t *pInfo ); -bool valid_IotMqttConnectInfo( const IotMqttConnectInfo_t *pInfo ); - -/**************************************************************** - * IotMqttSubscription - * - * A client subscribes to a topic represented by a string. For some - * proofs, we need to bound the length of this string (for example, - * when we have to unwind a loop in a function that iterates over the - * string). When we need to bound the length for a proof, we define - * TOPIC_LENGTH_MAX in the proof's Makefile. - ****************************************************************/ - -IotMqttSubscription_t *allocate_IotMqttSubscription( IotMqttSubscription_t *pSub ); -bool valid_IotMqttSubscription( const IotMqttSubscription_t *pSub ); - -/**************************************************************** - * IotMqttSubscription list - * - * There are two kinds of subscription lists in MQTT: array lists and - * linked lists. - * - * For some proofs, we need to bound the length of the list. For - * these proofs, we define SUBSCRIPTION_COUNT_MAX in the proof - * Makefile. - ****************************************************************/ - -// Array lists - -IotMqttSubscription_t *allocate_IotMqttSubscriptionArray( IotMqttSubscription_t *pSub, - size_t length ); -bool valid_IotMqttSubscriptionArray( const IotMqttSubscription_t *pSub, - const size_t length ); - -// Linked lists - -_mqttSubscription_t *allocate_IotMqttSubscriptionListElt( _mqttSubscription_t *pElt ); -bool valid_IotMqttSubscriptionListElt( const _mqttSubscription_t *pElt ); -IotListDouble_t *allocate_IotMqttSubscriptionList( IotListDouble_t *pSub, - size_t length ); -bool valid_IotMqttSubscriptionList( const IotListDouble_t *pSub, - const size_t length ); -void free_IotMqttSubscriptionList( IotListDouble_t *pSub ); - -/**************************************************************** - * IotMqttPublishInfo - ****************************************************************/ - -IotMqttPublishInfo_t *allocate_IotMqttPublishInfo( IotMqttPublishInfo_t *pInfo ); -bool valid_IotMqttPublishInfo( const IotMqttPublishInfo_t *pInfo ); - -/**************************************************************** - * IotNetworkConnection - ****************************************************************/ - -void *allocate_IotNetworkConnection(); - -/**************************************************************** - * IotNetworkInterface - * - * The network interface is a struct of fuction pointers that point to - * implementions of the network API. We define a collection of stubs - * for these implementations that do little more that check the - * validity of arguments and generate some minor side effects. - * - * The presence of these stubs can play havoc on CBMC function pointer - * elimination. CBMC considers all functions whose address has been - * taken to be a candiate for the value of a function pointer. So we - * have to be careful not to take the address of these stubs unless we - * have to. In particular, we don't assign them in the allocator or - * validator. - * - * Instead, when a proof requires a stub, we make an explicit - * assumption that the needed struct member is pointing to the correct - * stub. The macro IS_STUBBED_NETWORKIF_XXX(IF) states that the - * method XXX in the interface IF points to the correct stub. - ****************************************************************/ - -IotNetworkInterface_t *allocate_IotNetworkInterface(); -bool valid_IotNetworkInterface( const IotNetworkInterface_t *netif ); -bool stubbed_IotNetworkInterface( const IotNetworkInterface_t *netif ); - -#define IS_STUBBED_NETWORKIF_CREATE(netif) \ - (netif->create == IotNetworkInterfaceCreate) -#define IS_STUBBED_NETWORKIF_CLOSE(netif) \ - (netif->close == IotNetworkInterfaceClose) -#define IS_STUBBED_NETWORKIF_SEND(netif) \ - (netif->send == IotNetworkInterfaceSend) -#define IS_STUBBED_NETWORKIF_RECEIVE(netif) \ - (netif->receive == IotNetworkInterfaceReceive) -#define IS_STUBBED_NETWORKIF_SETRECEIVECALLBACK(netif) \ - (netif->setReceiveCallback == IotNetworkInterfaceReceiveCallback) -#define IS_STUBBED_NETWORKIF_SETCLOSECALLBACK(netif) \ - (netif->setCloseCallback == IotNetworkInterfaceCloseCallback) -#define IS_STUBBED_NETWORKIF_DESTROY(netif) \ - (netif->destroy == IotNetworkInterfaceDestroy) - -IotNetworkError_t IotNetworkInterfaceCreate( void * pConnectionInfo, - void * pCredentialInfo, - void * pConnection ); -size_t IotNetworkInterfaceSend( void * pConnection, - const uint8_t * pMessage, - size_t messageLength ); -IotNetworkError_t IotNetworkInterfaceClose( void * pConnection ); -size_t IotNetworkInterfaceReceive( void * pConnection, - uint8_t * pBuffer, - size_t bytesRequested ); -IotNetworkError_t IotNetworkInterfaceReceiveCallback( void * pConnection, - IotNetworkReceiveCallback_t - receiveCallback, - void * pContext ); -IotNetworkError_t IotNetworkInterfaceCloseCallback( void * pConnection, - IotNetworkCloseCallback_t - closeCallback, - void * pContext ); -IotNetworkError_t IotNetworkInterfaceDestroy( void * pConnection ); - -/****************************************************************/ - -IotMqttCallbackInfo_t *allocate_IotMqttCallbackInfo(IotMqttCallbackInfo_t *pCb); -void IotUserCallback( void * pCallbackContext, - IotMqttCallbackParam_t * pCallbackParam ); diff --git a/cbmc/proofs/prepare.py b/cbmc/proofs/prepare.py deleted file mode 100755 index dd0fd385d4..0000000000 --- a/cbmc/proofs/prepare.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python3 -# -# Python script for preparing the code base for the CBMC proofs. -# -# Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -import logging -import os -import pathlib -import subprocess -import sys -import textwrap - -from make_cbmc_batch_files import create_cbmc_yaml_files - - -def apply_patches(): - patch_dir = pathlib.Path(__file__).resolve().parent.parent / "patches" - if not patch_dir.is_dir(): - logging.error("Patches directory at '%s' is not a directory", patch_dir) - sys.exit(1) - - proj_dir = pathlib.Path(__file__).resolve().parent.parent.parent - if not proj_dir.is_dir(): - logging.error("Root of project at '%s' is not a directory", proj_dir) - sys.exit(1) - if not (proj_dir / "LICENSE").exists(): - logging.error( - "Directory '%s' doesn't seem to be root of project", proj_dir) - sys.exit(1) - - for fyle in sorted(patch_dir.glob("*.patch")): - logging.info("Checking patch '%s'", fyle.name) - cmd = ["git", "apply", "--check", str(fyle)] - proc = subprocess.run( - cmd, cwd=str(proj_dir), - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - universal_newlines=True) - if proc.returncode: - logging.warning( - "Patch checking failed. Check output:\n%s", - "\n".join([" %s" % line for line in proc.stdout.splitlines()])) - - logging.info("Applying patch '%s'", fyle.name) - cmd = ["git", "apply", str(fyle)] - proc = subprocess.run(cmd, cwd=str(proj_dir)) - if proc.returncode: - logging.error("Failed to apply patch '%s'", fyle.name) - sys.exit(1) - - -def build(): - try: - create_cbmc_yaml_files() - except subprocess.CalledProcessError as e: - logging.error(textwrap.dedent("""\ - An error occured during cbmc-batch generation. - The error message is: {} - """.format(str(e)))) - exit(1) - -################################################################ - -if __name__ == '__main__': - logging.basicConfig(format="{script}: %(levelname)s %(message)s".format( - script=os.path.basename(__file__))) - build() - apply_patches() diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt deleted file mode 100644 index cd8aa43a60..0000000000 --- a/demos/CMakeLists.txt +++ /dev/null @@ -1,98 +0,0 @@ -# Common application source files. -set( DEMO_APP_SOURCES - app/iot_demo.c - app/iot_demo_arguments.c ) - -# When testing the demos, add the test sources and redefine the demo main function. -if( ${IOT_BUILD_TESTS} ) - list( APPEND DEMO_APP_SOURCES ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) - set_property( SOURCE app/iot_demo.c PROPERTY COMPILE_DEFINITIONS main=DemoMain ) - set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) -else() - set( CONFIG_HEADER iot_config.h ) -endif() - -# Common demo header files. -set( DEMO_COMMON_HEADERS - include/iot_demo_arguments.h - include/iot_demo_logging.h ) - -# List all the demo functions, source files, and library dependencies here. -# -# The list order matters: Each demo must be at the same index in each list. -# For example, if the MQTT demo function is at DEMO_MAIN_FUNCTIONS[0], then -# the MQTT demo source must be at DEMO_SOURCES[0] and the MQTT library must -# be at DEMO_LIBRARY_DEPENDENCY[0]. -# -# If a demo has multiple dependencies, place a semicolon-separated list in -# DEMO_LIBRARY_DEPENDENCY. The semicolon itself must be escaped as \\\; -set( DEMO_MAIN_FUNCTIONS - RunMqttDemo - RunShadowDemo - RunDefenderDemo - RunJobsDemo ) -set( DEMO_SOURCES - src/iot_demo_mqtt.c - src/aws_iot_demo_shadow.c - src/aws_iot_demo_defender.c - src/aws_iot_demo_jobs.c ) -set( DEMO_LIBRARY_DEPENDENCY - iotmqtt - "awsiotshadow\\\;awsiotcommon" - awsiotdefender - "awsiotjobs\\\;awsiotcommon" ) - -# Get the list length to iterate over it. Since CMake indexes from 0, subtract -# 1 for the iteration range. -list( LENGTH DEMO_MAIN_FUNCTIONS DEMO_COUNT ) -math( EXPR DEMO_COUNT "${DEMO_COUNT}-1" ) - -# Go through the list of demos and create an executable for each one. -foreach( i RANGE ${DEMO_COUNT} ) - # Read the demo function, source, and dependency from each list. - list( GET DEMO_MAIN_FUNCTIONS ${i} CURRENT_DEMO_FUNCTION ) - list( GET DEMO_SOURCES ${i} CURRENT_DEMO_SOURCE ) - list( GET DEMO_LIBRARY_DEPENDENCY ${i} CURRENT_DEMO_LIBRARY ) - - # Get the name of the demo executable. This is the name of the demo source - # without the .c file extension. - get_filename_component( DEMO_EXE_NAME ${CURRENT_DEMO_SOURCE} NAME_WE ) - - # Add the demo executable. - add_executable( ${DEMO_EXE_NAME} - ${CURRENT_DEMO_SOURCE} - ${DEMO_APP_SOURCES} - ${DEMO_COMMON_HEADERS} - ${CONFIG_HEADER} ) - - # Set the demo function to run. - target_compile_definitions( ${DEMO_EXE_NAME} - PRIVATE RunDemo=${CURRENT_DEMO_FUNCTION} ) - - # Set additional defines when testing the demos. - if( ${IOT_BUILD_TESTS} ) - target_compile_definitions( ${DEMO_EXE_NAME} PRIVATE - IOT_TEST_DEMO=1 ) - endif() - - # Add the include path for the demo application. - target_include_directories( ${DEMO_EXE_NAME} PRIVATE include ) - - # Link iotbase. All demos require this. - target_link_libraries( ${DEMO_EXE_NAME} PRIVATE iotbase ) - - # Link the unique dependency of each demo. - target_link_libraries( ${DEMO_EXE_NAME} PRIVATE ${CURRENT_DEMO_LIBRARY} ) - - # When building tests, link the Unity test framework. - if( ${IOT_BUILD_TESTS} ) - target_link_libraries( ${DEMO_EXE_NAME} PRIVATE unity ) - endif() - - # Organize the demo into folders. - set_property( TARGET ${DEMO_EXE_NAME} PROPERTY FOLDER demos ) - source_group( app FILES ${DEMO_APP_SOURCES} ) - source_group( include FILES ${DEMO_COMMON_HEADERS} ) - source_group( src FILES ${CURRENT_DEMO_SOURCE} ) - source_group( "" FILES ${CONFIG_HEADER} ) -endforeach() diff --git a/demos/Makefile b/demos/Makefile new file mode 100644 index 0000000000..1a69ddbfb8 --- /dev/null +++ b/demos/Makefile @@ -0,0 +1,14 @@ +CC = gcc + +INCLUDE_DIRS = -I ../libraries/standard/mqtt/include + +SRC_FILES = $(shell find . -name '*.c') \ + $(shell find ../libraries/standard/mqtt/src -name '*.c') + +FLAGS = -g -O0 -Wall -Wextra -Wpedantic + +all: + $(CC) $(FLAGS) $(INCLUDE_DIRS) $(SRC_FILES) -o mqtt_demo + +clean: + rm mqtt_demo diff --git a/demos/README.md b/demos/README.md deleted file mode 100644 index 8181eff5f9..0000000000 --- a/demos/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Demo programs - -This directory contains source files for demo executables. Its subdirectories are organized as follows: -- `app`
- Source files for demo runner executables (i.e. the `main()` function). -- `include`
- Common headers for the demo executables. These headers handle argument parsing and logging which are common to all demos. -- `src`
- Platform-independent demo sources. The files in this directory contain the majority of the demo code. The demo runner in `app` calls demo functions implemented in this directory. - -The configuration file for the demos, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the demos and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all demos and libraries; see each library's documentation for library-specific settings. - -For information on building and running the demos, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html). diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c deleted file mode 100644 index 6bb69247b3..0000000000 --- a/demos/app/iot_demo.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo.c - * @brief Generic demo runner. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Common demo includes. */ -#include "iot_demo_arguments.h" -#include "iot_demo_logging.h" - -/* Choose the appropriate network header, initializers, and initialization - * function. */ -#if IOT_NETWORK_USE_OPENSSL == 1 - /* OpenSSL network include. */ - #include "iot_network_openssl.h" - - #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL - #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER - #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER - #define IOT_DEMO_ALPN_FOR_PASSWORD_AUTHENTICATION AWS_IOT_PASSWORD_ALPN_FOR_OPENSSL - - #define IotDemoNetwork_Init IotNetworkOpenssl_Init - #define IotDemoNetwork_Cleanup IotNetworkOpenssl_Cleanup -#else /* if IOT_NETWORK_USE_OPENSSL == 1 */ - /* mbed TLS network include. */ - #include "iot_network_mbedtls.h" - - #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS - #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER - #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER - #define IOT_DEMO_ALPN_FOR_PASSWORD_AUTHENTICATION AWS_IOT_PASSWORD_ALPN_FOR_MBEDTLS - - #define IotDemoNetwork_Init IotNetworkMbedtls_Init - #define IotDemoNetwork_Cleanup IotNetworkMbedtls_Cleanup -#endif /* if IOT_NETWORK_USE_OPENSSL == 1 */ - -/* This file calls a generic placeholder demo function. The build system selects - * the actual demo function to run by defining it. */ -#ifndef RunDemo - #error "Demo function undefined." -#endif - -/*-----------------------------------------------------------*/ - -/* Declaration of generic demo function. */ -extern int RunDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ); - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - /* Return value of this function and the exit status of this program. */ - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - - /* Status returned from network stack initialization. */ - IotNetworkError_t networkInitStatus = IOT_NETWORK_SUCCESS; - - /* Flags for tracking which cleanup functions must be called. */ - bool sdkInitialized = false, networkInitialized = false; - - /* Arguments for this demo. */ - IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; - - /* Network server info and credentials. */ - struct IotNetworkServerInfo serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; - struct IotNetworkCredentials credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, - * pCredentials = NULL; - - /* Parse and validate any command line arguments. */ - if( IotDemo_ParseArguments( argc, - argv, - &demoArguments ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Set the members of the server info. */ - serverInfo.pHostName = demoArguments.pHostName; - serverInfo.port = demoArguments.port; - - /* For a secured connection, set the members of the credentials. */ - if( demoArguments.securedConnection == true ) - { - /* Set credential information. */ - credentials.pClientCert = demoArguments.pClientCertPath; - credentials.pPrivateKey = demoArguments.pPrivateKeyPath; - credentials.pRootCa = demoArguments.pRootCaPath; - credentials.pUserName = NULL; - credentials.pPassword = NULL; - - /* Set the MQTT username, as long as it's not empty or NULL. */ - if( demoArguments.pUserName != NULL ) - { - credentials.userNameSize = strlen( demoArguments.pUserName ); - - if( credentials.userNameSize > 0 ) - { - credentials.pUserName = demoArguments.pUserName; - } - } - - /* Set the MQTT password, as long as it's not empty or NULL. */ - if( demoArguments.pPassword != NULL ) - { - credentials.passwordSize = strlen( demoArguments.pPassword ); - - if( credentials.passwordSize > 0 ) - { - credentials.pPassword = demoArguments.pPassword; - } - } - - /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Clear that value if another port is - * used. */ - if( demoArguments.port != 443 ) - { - credentials.pAlpnProtos = NULL; - } - - /* Per IANA standard: - * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml. */ - if( ( credentials.pUserName != NULL ) && - ( demoArguments.awsIotMqttMode == true ) ) - { - credentials.pAlpnProtos = IOT_DEMO_ALPN_FOR_PASSWORD_AUTHENTICATION; - } - - /* Set the pointer to the credentials. */ - pCredentials = &credentials; - } - - /* Call the SDK initialization function. */ - sdkInitialized = IotSdk_Init(); - - if( sdkInitialized == false ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Initialize the network stack. */ - networkInitStatus = IotDemoNetwork_Init(); - - if( networkInitStatus == IOT_NETWORK_SUCCESS ) - { - networkInitialized = true; - } - else - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Run the demo. */ - status = RunDemo( demoArguments.awsIotMqttMode, - demoArguments.pIdentifier, - &serverInfo, - pCredentials, - IOT_DEMO_NETWORK_INTERFACE ); - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Clean up the network stack if initialized. */ - if( networkInitialized == true ) - { - IotDemoNetwork_Cleanup(); - } - - /* Clean up the SDK if initialized. */ - if( sdkInitialized == true ) - { - IotSdk_Cleanup(); - } - - /* Log the demo status. */ - if( status == EXIT_SUCCESS ) - { - IotLogInfo( "Demo completed successfully." ); - } - else - { - IotLogError( "Error occurred while running demo." ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c deleted file mode 100644 index 4e05ac156c..0000000000 --- a/demos/app/iot_demo_arguments.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_arguments.c - * @brief Implements a function for retrieving command line arguments. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Error handling include. */ -#include "iot_error.h" - -/* Common demo includes. */ -#include "iot_demo_arguments.h" -#include "iot_demo_logging.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Set the default values of an #IotDemoArguments_t based on compile-time - * defined constants. - * - * @param[out] pArguments Default values will be placed here. - */ -static void _setDefaultArguments( IotDemoArguments_t * pArguments ) -{ - /* Default to AWS IoT MQTT mode. */ - pArguments->awsIotMqttMode = true; - - /* Set default identifier if defined. The identifier is used as either the - * MQTT client identifier or the Thing Name, which identifies this client to - * the cloud. */ - #ifdef IOT_DEMO_IDENTIFIER - pArguments->pIdentifier = IOT_DEMO_IDENTIFIER; - #endif - - /* Set default secured connection status if defined. */ - #ifdef IOT_DEMO_SECURED_CONNECTION - pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; - #endif - - /* Set default MQTT server if defined. */ - #ifdef IOT_DEMO_SERVER - pArguments->pHostName = IOT_DEMO_SERVER; - #endif - - /* Set default MQTT server port if defined. */ - #ifdef IOT_DEMO_PORT - pArguments->port = IOT_DEMO_PORT; - #endif - - /* Set default root CA path if defined. */ - #ifdef IOT_DEMO_ROOT_CA - pArguments->pRootCaPath = IOT_DEMO_ROOT_CA; - #endif - - /* Set default client certificate path if defined. */ - #ifdef IOT_DEMO_CLIENT_CERT - pArguments->pClientCertPath = IOT_DEMO_CLIENT_CERT; - #endif - - /* Set default client certificate private key path if defined. */ - #ifdef IOT_DEMO_PRIVATE_KEY - pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; - #endif - - /* Set default MQTT broker username if defined. */ - #ifdef IOT_DEMO_USER_NAME - pArguments->pUserName = IOT_DEMO_USER_NAME; - #endif - - /* Set default MQTT broker password if defined. */ - #ifdef IOT_DEMO_PASSWORD - pArguments->pPassword = IOT_DEMO_PASSWORD; - #endif -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Validates the members of an #IotDemoArguments_t. - * - * @param[in] pArguments The #IotDemoArguments_t to validate. - * - * @return `true` if every member of the #IotDemoArguments_t is valid; `false` - * otherwise. - */ -static bool _validateArguments( const IotDemoArguments_t * pArguments ) -{ - /* Declare a status variable for this function. */ - IOT_FUNCTION_ENTRY( bool, true ); - - /* Check that a server was set. */ - if( ( pArguments->pHostName == NULL ) || - ( strlen( pArguments->pHostName ) == 0 ) ) - { - IotLogError( "MQTT server not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a server port was set. */ - if( pArguments->port == 0 ) - { - IotLogError( "MQTT server port not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check credentials for a secured connection. */ - if( pArguments->securedConnection == true ) - { - /* Check that a root CA path was set. */ - if( ( pArguments->pRootCaPath == NULL ) || - ( strlen( pArguments->pRootCaPath ) == 0 ) ) - { - IotLogError( "Root CA path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* If the host is connecting to the MQTT broker hosted by AWS IoT Core, - * there must either be a set of X.509 credentials or a - * username/password. Therefore, check that here in order to facilitate - * debugging. For other MQTT brokers, assume that the CLI arguments are - * as intended. - */ - if( pArguments->awsIotMqttMode == true ) - { - if( ( pArguments->pUserName == NULL ) || - ( strlen( pArguments->pUserName ) == 0 ) || - ( pArguments->pPassword == NULL ) || - ( strlen( pArguments->pPassword ) == 0 ) ) - { - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Either username/password or client certificate path must be set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Either username/password or private key path must be set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - } - } - else - { - if( pArguments->awsIotMqttMode == true ) - { - IotLogError( "AWS IoT does not support unsecured connections." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* Check that the length of the encoded username, if any, will be within - * the specification of the MQTT standard. */ - if( pArguments->pUserName != NULL ) - { - if( strlen( pArguments->pUserName ) > UINT16_MAX ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* Check that the length of the encoded username, if any, will be within - * the specification of the MQTT standard. */ - if( pArguments->pPassword != NULL ) - { - if( strlen( pArguments->pPassword ) > UINT16_MAX ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* No cleanup is required for this function. */ - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -bool IotDemo_ParseArguments( int argc, - char ** argv, - IotDemoArguments_t * pArguments ) -{ - int i = 1; - const char * pOption = NULL; - unsigned long int port = 0; - size_t optionLength = 0; - - /* Load the default demo arguments from the demo config header. */ - _setDefaultArguments( pArguments ); - - for( i = 1; i < argc; i++ ) - { - /* Get argument string and length. */ - pOption = argv[ i ]; - optionLength = strlen( pOption ); - - /* Valid options have the format "-X", so they must be 2 characters long. */ - if( optionLength != 2 ) - { - IotLogWarn( "Ignoring invalid option %s.", pOption ); - - continue; - } - - /* The first character of a valid option must be '-'. */ - if( pOption[ 0 ] != '-' ) - { - IotLogWarn( "Ignoring invalid option %s.", pOption ); - - continue; - } - - switch( pOption[ 1 ] ) - { - /* Client certificate path. */ - case 'c': - i++; - pArguments->pClientCertPath = argv[ i ]; - break; - - /* Server. */ - case 'h': - i++; - pArguments->pHostName = argv[ i ]; - break; - - /* Client identifier or Thing Name. */ - case 'i': - i++; - pArguments->pIdentifier = argv[ i ]; - break; - - /* Client certificate private key path. */ - case 'k': - i++; - pArguments->pPrivateKeyPath = argv[ i ]; - break; - - /* Username for MQTT. */ - case 'm': - i++; - pArguments->pUserName = argv[ i ]; - break; - - /* MQTT server is not AWS. */ - case 'n': - pArguments->awsIotMqttMode = false; - break; - - /* Server port. */ - case 'p': - i++; - - /* Convert argument string to unsigned int. */ - port = strtoul( argv[ i ], NULL, 10 ); - - /* Check that port is valid. */ - if( ( port == 0 ) || ( port > UINT16_MAX ) ) - { - IotLogWarn( "Ignoring invalid port %lu.", port ); - } - else - { - pArguments->port = ( uint16_t ) port; - } - - break; - - /* Root CA path. */ - case 'r': - i++; - pArguments->pRootCaPath = argv[ i ]; - break; - - /* Secured connection. */ - case 's': - pArguments->securedConnection = true; - break; - - /* Unsecured connection. */ - case 'u': - pArguments->securedConnection = false; - break; - - /* Password for MQTT. */ - case 'w': - i++; - pArguments->pPassword = argv[ i ]; - break; - - default: - IotLogWarn( "Ignoring unknown option %s.", pOption ); - break; - } - } - - return _validateArguments( pArguments ); -} - -/*-----------------------------------------------------------*/ diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h deleted file mode 100644 index c42343de77..0000000000 --- a/demos/include/iot_demo_arguments.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_arguments.h - * @brief Declares the function and structure used for processing command line - * arguments - */ - -#ifndef IOT_DEMO_ARGUMENTS_H_ -#define IOT_DEMO_ARGUMENTS_H_ - -/* Standard includes. */ -#include -#include - -/** - * @brief Holds the arguments for a single demo. - * - * Each demo will use one of these structs to hold its arguments. - * - * The default values of this struct may be set using compile-time constants, - * either through the config file or a compiler option like `-D`. - * - * The default values may be overridden using command line arguments. If a default - * value was not set, then a valid value must be set using a command line argument. - * - * @initializer{IotDemoArguments_t,IOT_DEMO_ARGUMENTS_INITIALIZER} - */ -typedef struct IotDemoArguments -{ - bool awsIotMqttMode; /**< @brief Whether the demo is using the AWS IoT MQTT server. */ - bool securedConnection; /**< @brief Whether to secure the network connection with TLS. */ - const char * pHostName; /**< @brief The remote host name that the demo will connect to. */ - uint16_t port; /**< @brief The remote host port that the demo will connect to. */ - - /* These credentials are only used if securedConnection is true. */ - const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ - const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ - const char * pPrivateKeyPath; /**< @brief The path to the private key that matches the client certificate. */ - const char * pUserName; /**< @brief The username for authenticating to the MQTT broker. */ - const char * pPassword; /**< @brief The password for authenticating to the MQTT broker. */ - - const char * pIdentifier; /**< @brief The client identifier or Thing Name to use for demo. */ -} IotDemoArguments_t; - -/** - * @brief Provides default values for an #IotDemoArguments_t. - * - * All instances of #IotDemoArguments_t should be initialized with this - * constant. - * - * @code{c} - * IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; - * @endcode - * - * @warning Failing to initialize an #IotDemoArguments_t with this initializer - * may result in undefined behavior! - * @note This initializer may change at any time in future versions, but its - * names will remain the same. - */ -#define IOT_DEMO_ARGUMENTS_INITIALIZER { 0 } - -/** - * @brief Parses command line arguments. - * - * The functions for parsing command line arguments differ depending on the - * operating system. Therefore, this function is re-implemented for different - * platforms. - * - * @param[in] argc The argument count originally passed to main(). - * @param[in] argv The argument vector originally passed to main(). - * @param[out] pArguments Set to the arguments parsed from the command line. - * - * @return `true` if all arguments are valid; `false` otherwise. - */ -bool IotDemo_ParseArguments( int argc, - char ** argv, - IotDemoArguments_t * pArguments ); - -#endif /* ifndef IOT_DEMO_ARGUMENTS_H_ */ diff --git a/demos/include/iot_demo_logging.h b/demos/include/iot_demo_logging.h deleted file mode 100644 index b73ee11e4b..0000000000 --- a/demos/include/iot_demo_logging.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_logging.h - * @brief Sets the log level for all demos. - */ - -#ifndef IOT_DEMO_LOGGING_H_ -#define IOT_DEMO_LOGGING_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Configure logs for the demos. The demos will have a log level of: - * - IOT_LOG_LEVEL_DEMO if defined. - * - IOT_LOG_LEVEL_GLOBAL if defined and IOT_LOG_LEVEL_DEMO is undefined. - * - IOT_LOG_NONE if neither IOT_LOG_LEVEL_DEMO nor IOT_LOG_LEVEL_GLOBAL are defined. - */ -#ifdef IOT_LOG_LEVEL_DEMO - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_DEMO -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -/* Set the library name to print with the demos. */ -#define LIBRARY_LOG_NAME ( "DEMO" ) - -/* Include the logging setup header. This enables the logs. */ -#include "iot_logging_setup.h" - -#endif /* ifndef IOT_DEMO_LOGGING_H_ */ diff --git a/demos/iot_config.h b/demos/iot_config.h deleted file mode 100644 index 750b7ab9f9..0000000000 --- a/demos/iot_config.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* This file contains configuration settings for the demos. */ - -#ifndef IOT_CONFIG_H_ -#define IOT_CONFIG_H_ - -/* Server endpoints used for the demos. May be overridden with command line - * options at runtime. */ -#define IOT_DEMO_SECURED_CONNECTION ( true ) /* Command line: -s (secured) or -u (unsecured) */ -#define IOT_DEMO_SERVER "" /* Command line: -h */ -#define IOT_DEMO_PORT ( 443 ) /* Command line: -p */ - -/* Credential paths. May be overridden with command line options at runtime. */ -#define IOT_DEMO_ROOT_CA "" /* Command line: -r */ -#define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ -#define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ -#define IOT_DEMO_USER_NAME "" /* Command line: -m */ -#define IOT_DEMO_PASSWORD "" /* Command line: -w */ - -/* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at - * runtime with the command line option -i. Identifiers are optional for the - * MQTT demo, but required for demos requiring a Thing Name. (The MQTT demo will - * generate a unique identifier if no identifier is given). If a specific Thing Name - * is required please define the following line: - * #define IOT_DEMO_IDENTIFIER "" */ - -/* MQTT demo configuration. The demo publishes bursts of messages. */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ - -/* MQTT library configuration. */ -#ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES - #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) -#endif - -/* Shadow demo configuration. The demo publishes periodic Shadow updates and responds - * to changing Shadows. */ -#define AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ( 20 ) /* Number of updates to publish. */ -#define AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ( 3000 ) /* Period of Shadow updates. */ - -/* Enable asserts in the libraries. */ -#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) -#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) -#define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_JOBS_ENABLE_ASSERTS ( 1 ) - -/* Library logging configuration. IOT_LOG_LEVEL_GLOBAL provides a global log - * level for all libraries; the library-specific settings override the global - * setting. If both the library-specific and global settings are undefined, - * no logs will be printed. */ -#define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO -#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO -#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_NONE -#define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO -#define IOT_LOG_LEVEL_TASKPOOL IOT_LOG_NONE -#define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_DEFENDER IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_JOBS IOT_LOG_INFO - -/* Default assert and memory allocation functions. */ -#include -#include - -#define Iot_DefaultAssert assert -#define Iot_DefaultMalloc malloc -#define Iot_DefaultFree free - -/* The build system will choose the appropriate system types file for the platform - * layer based on the host operating system. */ -#include IOT_SYSTEM_TYPES_FILE - -#endif /* ifndef IOT_CONFIG_H_ */ diff --git a/demos/lexicon.txt b/demos/lexicon.txt deleted file mode 100644 index 39fe6a9356..0000000000 --- a/demos/lexicon.txt +++ /dev/null @@ -1,48 +0,0 @@ -alpn -api -ascii -aws -awsiotmqttmode -bool -ca -cert -cli -com -config -const -endcond -endif -extensiontype -gr -html -https -iana -ifndef -int -iot -jobscallback -json -lwt -mbed -mqtt -openssl -org -pclientcertpath -phostname -pidentifier -poweron -ppassword -pprivatekeypath -prootcapath -publishcount -pusername -sdk -securedconnection -snprintf -startnext -tcp -tls -uint -username -www -xhtml diff --git a/demos/mqtt_demo.c b/demos/mqtt_demo.c new file mode 100644 index 0000000000..e91a4942b1 --- /dev/null +++ b/demos/mqtt_demo.c @@ -0,0 +1,6 @@ +#include "mqtt.h" + +int main(int argc, char ** argv) +{ + return 0; +} diff --git a/demos/src/aws_iot_demo_defender.c b/demos/src/aws_iot_demo_defender.c deleted file mode 100644 index 3bb2fc3506..0000000000 --- a/demos/src/aws_iot_demo_defender.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* Demo configuration includes. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Demo logging include. */ -#include "iot_demo_logging.h" - -/* Platform includes for demo. */ -#include "platform/iot_clock.h" -#include "platform/iot_network.h" - -/* Defender includes. */ -#include "aws_iot_defender.h" - -/* Includes for initialization. */ -#include "iot_mqtt.h" -#include "iot_network_metrics.h" - -/** - * @brief The keep-alive interval used for this demo. - * - * An MQTT ping request will be sent periodically at this interval. - */ -#define KEEP_ALIVE_SECONDS ( ( uint16_t ) 60 ) - -/** - * @brief The timeout for Defender and MQTT operations in this demo. - */ -#define TIMEOUT_MS ( ( uint32_t ) 5000 ) - -/** - * @brief Defender metrics publish interval, 5 minutes (300 seconds) is minimum. - */ -#define DEFENDER_PUBLISH_INTERVAL ( ( uint32_t ) 300 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Callback used to get notification of defender's events. - * - * @param[in] pCallbackContext context pointer passed by the application - * when callback is registered in AwsIotDefender_Start() - * - * @param[in] pointer to AwsIotDefenderCallbackInfo_t containing status of - * publish - */ -void _defenderCallback( void * pCallbackContext, - AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) -{ - ( void ) pCallbackContext; - - IotLogInfo( "User's callback is invoked on event: %s.", AwsIotDefender_EventType( pCallbackInfo->eventType ) ); - - if( pCallbackInfo != NULL ) - { - /* Callback info processing example . */ - if( pCallbackInfo->pMetricsReport != NULL ) - { - IotLogInfo( "Published metrics report." ); - } - else - { - IotLogError( "No metrics report was generated." ); - } - - if( pCallbackInfo->pPayload != NULL ) - { - IotLogInfo( "Received MQTT message." ); - } - else - { - IotLogError( "No message has been returned from subscribed topic." ); - } - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Establish a new connection to the MQTT server for the Defender demo. - * - * @param[in] pIdentifier NULL-terminated MQTT client identifier. The Defender - * demo will use the Thing Name as the client identifier. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkInterface The network interface to use for this demo. - * @param[out] pMqttConnection Set to the handle to the new MQTT connection. - * - * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` - * otherwise. - */ -static int _establishMqttConnection( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface, - IotMqttConnection_t * pMqttConnection ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - - if( pIdentifier == NULL ) - { - IotLogError( "Defender Thing Name must be provided." ); - - status = EXIT_FAILURE; - } - - if( status == EXIT_SUCCESS ) - { - /* Set the members of the network info not set by the initializer. This - * struct provided information on the transport layer to the MQTT connection. */ - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; - networkInfo.pNetworkInterface = pNetworkInterface; - - /* Set the members of the connection info not set by the initializer. */ - connectInfo.awsIotMqttMode = true; - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; - - /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ - connectInfo.pClientIdentifier = pIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - - IotLogInfo( "Defender Thing Name is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); - - /* Establish the MQTT connection. */ - connectStatus = IotMqtt_Connect( &networkInfo, - &connectInfo, - TIMEOUT_MS, - pMqttConnection ); - - if( connectStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT CONNECT returned error %s.", - IotMqtt_strerror( connectStatus ) ); - - status = EXIT_FAILURE; - } - } - - return status; -} -/*-----------------------------------------------------------*/ - -/** - * @brief The function that runs the Defender demo, called by the demo runner. - * - * @param[in] awsIotMqttMode Ignored for the Defender demo. - * @param[in] pIdentifier NULL-terminated Defender Thing Name. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection for Defender. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection for Defender. - * @param[in] pNetworkInterface The network interface to use for this demo. - * - * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. - */ -int RunDefenderDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ) -{ - int status = EXIT_SUCCESS; - bool metricsInitStatus = false; - IotMqttError_t mqttStatus = IOT_MQTT_INIT_FAILED; - AwsIotDefenderError_t defenderResult = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - AwsIotDefenderStartInfo_t startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - const AwsIotDefenderCallback_t callback = { .function = _defenderCallback, .pCallbackContext = NULL }; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - /* Unused parameters. */ - ( void ) awsIotMqttMode; - - IotLogInfo( "----Device Defender Demo Start----" ); - - /* Check parameter(s). */ - if( ( pIdentifier == NULL ) || ( pIdentifier[ 0 ] == '\0' ) ) - { - IotLogError( "The length of the Thing Name (identifier) must be nonzero." ); - status = EXIT_FAILURE; - } - - if( status == EXIT_SUCCESS ) - { - /* Initialize the MQTT library. */ - mqttStatus = IotMqtt_Init(); - - if( mqttStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT Initialization Failed." ); - status = EXIT_FAILURE; - } - } - - if( status == EXIT_SUCCESS ) - { - /* Initialize Metrics. */ - metricsInitStatus = IotMetrics_Init(); - - if( !metricsInitStatus ) - { - IotLogError( "IOT Metrics Initialization Failed." ); - status = EXIT_FAILURE; - } - } - - if( status == EXIT_SUCCESS ) - { - /* Specify all metrics in "tcp connections" group */ - defenderResult = - AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ); - - if( defenderResult != AWS_IOT_DEFENDER_SUCCESS ) - { - status = EXIT_FAILURE; - } - } - - if( status == EXIT_SUCCESS ) - { - defenderResult = AwsIotDefender_SetPeriod( DEFENDER_PUBLISH_INTERVAL ); - - if( defenderResult != AWS_IOT_DEFENDER_SUCCESS ) - { - status = EXIT_FAILURE; - } - } - - if( status == EXIT_SUCCESS ) - { - /* Create MQTT Connection */ - mqttStatus = _establishMqttConnection( pIdentifier, - pNetworkServerInfo, - pNetworkCredentialInfo, - pNetworkInterface, - &mqttConnection ); - - if( mqttStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to Create MQTT Connection:%d", IotMqtt_strerror( mqttStatus ) ); - IotMqtt_Cleanup(); - defenderResult = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - } - else - { - /* Initialize start info and call defender Start API */ - startInfo.pClientIdentifier = pIdentifier; - startInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - startInfo.callback = callback; - startInfo.mqttConnection = mqttConnection; - defenderResult = AwsIotDefender_Start( &startInfo ); - } - - if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) - { - /* Let it run for 3 seconds */ - IotClock_SleepMs( 3000 ); - /* Stop the defender agent. */ - AwsIotDefender_Stop(); - /* Disconnect MQTT */ - IotMqtt_Disconnect( mqttConnection, false ); - } - } - - /* Cleanup. */ - if( metricsInitStatus ) - { - IotMetrics_Cleanup(); - } - - if( mqttStatus == IOT_MQTT_SUCCESS ) - { - IotMqtt_Cleanup(); - } - - IotLogInfo( "----Device Defender Demo End. Status: %s----.", AwsIotDefender_strerror( defenderResult ) ); - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/demos/src/aws_iot_demo_jobs.c b/demos/src/aws_iot_demo_jobs.c deleted file mode 100644 index d9b5f92dab..0000000000 --- a/demos/src/aws_iot_demo_jobs.c +++ /dev/null @@ -1,896 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_demo_jobs.c - * @brief Demonstrates use of the AWS IoT Jobs library. - * - * This program sets a Jobs Notify-Next callback and waits for Job documents to arrive. - * It will then take action based on the Job document. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Set up logging for this demo. */ -#include "iot_demo_logging.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Jobs include. */ -#include "aws_iot_jobs.h" - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -/* Atomics include. */ -#include "iot_atomic.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief The timeout for Jobs and MQTT operations in this demo. - */ -#define TIMEOUT_MS ( ( uint32_t ) 5000u ) - -/** - * @brief The keep-alive interval used for this demo. - * - * An MQTT ping request will be sent periodically at this interval. - */ -#define KEEP_ALIVE_SECONDS ( ( uint16_t ) 60u ) - -/** - * @brief The JSON key of the Job ID. - * - * Job documents are JSON documents received from the AWS IoT Jobs service. - * All Job documents will contain this key, whose value represents the unique - * identifier of a Job. - */ -#define JOB_ID_KEY "jobId" - -/** - * @brief The length of #JOB_ID_KEY. - */ -#define JOB_ID_KEY_LENGTH ( sizeof( JOB_ID_KEY ) - 1 ) - -/** - * @brief The JSON key of the Job document. - * - * Job documents are JSON documents received from the AWS IoT Jobs service. - * All Job documents will contain this key, whose value is an application-specific - * JSON document. - */ -#define JOB_DOC_KEY "jobDocument" - -/** - * @brief The length of #JOB_DOC_KEY. - */ -#define JOB_DOC_KEY_LENGTH ( sizeof( JOB_DOC_KEY ) - 1 ) - -/** - * @brief The JSON key whose value represents the action this demo should take. - * - * This demo program expects this key to be in the Job document. It is a key - * specific to this demo. - */ -#define JOB_ACTION_KEY "action" - -/** - * @brief The length of #JOB_ACTION_KEY. - */ -#define JOB_ACTION_KEY_LENGTH ( sizeof( JOB_ACTION_KEY ) - 1 ) - -/** - * @brief A message associated with the Job action. - * - * This demo program expects this key to be in the Job document if the "action" - * is either "publish" or "print". It represents the message that should be - * published or printed, respectively. - */ -#define JOB_MESSAGE_KEY "message" - -/** - * @brief The length of #JOB_MESSAGE_KEY. - */ -#define JOB_MESSAGE_KEY_LENGTH ( sizeof( JOB_MESSAGE_KEY ) - 1 ) - -/** - * @brief An MQTT topic associated with the Job "publish" action. - * - * This demo program expects this key to be in the Job document if the "action" - * is "publish". It represents the MQTT topic on which the message should be - * published. - */ -#define JOB_TOPIC_KEY "topic" - -/** - * @brief The length of #JOB_TOPIC_KEY. - */ -#define JOB_TOPIC_KEY_LENGTH ( sizeof( JOB_TOPIC_KEY ) - 1 ) - -/** - * @brief The minimum length of a string in a JSON Job document. - * - * At the very least the Job ID must have the quotes that identify it as a JSON - * string and 1 character for the string itself (the string must not be empty). - */ -#define JSON_STRING_MIN_LENGTH ( ( size_t ) 3 ) - -/** - * @brief The maximum length of a Job ID. - * - * This limit is defined by AWS service limits. See the following page for more - * information. - * - * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits - */ -#define JOB_ID_MAX_LENGTH ( ( size_t ) 64 ) - -/** - * @brief A value passed as context to #_operationCompleteCallback to specify that - * it should set the #JOBS_DEMO_FINISHED flag. - */ -#define JOBS_DEMO_SHOULD_EXIT ( ( void * ) ( ( intptr_t ) 1 ) ) - -/** - * @brief Flag value for signaling that the demo is still running. - * - * The initial value of #_exitFlag. - */ -#define JOBS_DEMO_RUNNING ( ( uint32_t ) 0 ) - -/** - * @brief Flag value for signaling that the demo is finished. - * - * #_exitFlag will be set to this when a Job document with { "action": "exit" } - * is received. - */ -#define JOBS_DEMO_FINISHED ( ( uint32_t ) 1 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Currently supported actions that a Job document can specify. - */ -typedef enum _jobAction -{ - JOB_ACTION_PRINT, /**< Print a message. */ - JOB_ACTION_PUBLISH, /**< Publish a message to an MQTT topic. */ - JOB_ACTION_EXIT, /**< Exit the demo. */ - JOB_ACTION_UNKNOWN /**< Unknown action. */ -} _jobAction_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Used to print log messages that do not contain any metadata. - */ -static IotLogConfig_t _logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; - -/** - * @brief A flag that signals the end of the demo. - * - * When a Job document is received with { "action": "exit" }, the demo will set - * this flag and exit. - */ -static uint32_t _exitFlag = 0; - -/*-----------------------------------------------------------*/ - -/** - * @brief Initialize the libraries required for this demo. - * - * Initialize the MQTT and Jobs libraries. If the Jobs library fails - * to initialize, the MQTT library is cleaned up. - * - * @return `EXIT_SUCCESS` if all initialization succeeds; `EXIT_FAILURE` otherwise. - */ -static int _initializeDemo( void ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; - AwsIotJobsError_t jobsInitStatus = AWS_IOT_JOBS_SUCCESS; - - mqttInitStatus = IotMqtt_Init(); - - if( mqttInitStatus != IOT_MQTT_SUCCESS ) - { - status = EXIT_FAILURE; - } - else - { - jobsInitStatus = AwsIotJobs_Init( 0 ); - - if( jobsInitStatus != AWS_IOT_JOBS_SUCCESS ) - { - IotMqtt_Cleanup(); - status = EXIT_FAILURE; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Clean up the libraries initialized by #_initializeDemo. - * - * @note Must not be called if #_initializeDemo was not successfully called. - */ -static void _cleanupDemo( void ) -{ - AwsIotJobs_Cleanup(); - IotMqtt_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Establish a new connection to the MQTT server for the Jobs demo. - * - * @param[in] pIdentifier NULL-terminated MQTT client identifier. The Jobs - * demo will use the Thing Name as the client identifier. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkInterface The network interface to use for this demo. - * @param[out] pMqttConnection Set to the handle to the new MQTT connection. - * - * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` - * otherwise. - */ -static int _establishMqttConnection( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface, - IotMqttConnection_t * const pMqttConnection ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* Set the members of the network info not set by the initializer. This - * struct provided information on the transport layer to the MQTT connection. */ - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; - networkInfo.pNetworkInterface = pNetworkInterface; - - /* Set the members of the connection info not set by the initializer. */ - connectInfo.awsIotMqttMode = true; - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; - - /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ - connectInfo.pClientIdentifier = pIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - - IotLogInfo( "Thing Name is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); - - /* Establish the MQTT connection. */ - connectStatus = IotMqtt_Connect( &networkInfo, &connectInfo, TIMEOUT_MS, pMqttConnection ); - - if( connectStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT CONNECT returned error %s.", IotMqtt_strerror( connectStatus ) ); - - status = EXIT_FAILURE; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Converts a string in a Job document to a #_jobAction_t. - * - * @param[in] pAction The Job action as a string. - * @param[in] actionLength The length of `pAction`. - * - * @return A #_jobAction_t equivalent to the given string. - */ -static _jobAction_t _getAction( const char * pAction, - size_t actionLength ) -{ - _jobAction_t action = JOB_ACTION_UNKNOWN; - - if( strncmp( pAction, "print", actionLength ) == 0 ) - { - action = JOB_ACTION_PRINT; - } - else if( strncmp( pAction, "publish", actionLength ) == 0 ) - { - action = JOB_ACTION_PUBLISH; - } - else if( strncmp( pAction, "exit", actionLength ) == 0 ) - { - action = JOB_ACTION_EXIT; - } - - return action; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Extracts a JSON string from the Job document. - * - * @param[in] pJsonDoc The JSON document to search. - * @param[in] jsonDocLength Length of `pJsonDoc`. - * @param[in] pKey The JSON key to search for. - * @param[in] keyLength Length of `pKey`. - * @param[out] pValue The extracted JSON value. - * @param[out] valueLength Length of pValue. - * - * @return `true` if the key was found and the value is valid; `false` otherwise. - */ -static bool _getJsonString( const char * pJsonDoc, - size_t jsonDocLength, - const char * pKey, - size_t keyLength, - const char ** pValue, - size_t * valueLength ) -{ - /* - * Note: This parser used is specific for parsing AWS IoT document received - * through a mutually authenticated connection. This parser will not check - * for the correctness of the document as it is designed for low memory - * footprint rather than checking for correctness of the document. This - * parser is not meant to be used as a general purpose JSON parser. - */ - bool keyFound = AwsIotDocParser_FindValue( pJsonDoc, - jsonDocLength, - pKey, - keyLength, - pValue, - valueLength ); - - if( keyFound == true ) - { - /* Exclude empty strings. */ - if( *valueLength < JSON_STRING_MIN_LENGTH ) - { - keyFound = false; - } - else - { - /* Adjust the value to remove the quotes. */ - ( *pValue )++; - ( *valueLength ) -= 2; - } - } - - return keyFound; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Job operation completion callback. This function is invoked when an - * asynchronous Job operation finishes. - * - * @param[in] pCallbackContext Set to a non-NULL value to exit the demo. - * @param[in] pCallbackParam Information on the Job operation that completed. - */ -static void _operationCompleteCallback( void * pCallbackContext, - AwsIotJobsCallbackParam_t * pCallbackParam ) -{ - /* This function is invoked when either a StartNext or Update completes. */ - if( pCallbackParam->callbackType == AWS_IOT_JOBS_START_NEXT_COMPLETE ) - { - IotLogInfo( "Job StartNext complete with result %s.", - AwsIotJobs_strerror( pCallbackParam->u.operation.result ) ); - } - else - { - IotLogInfo( "Job Update complete with result %s.", - AwsIotJobs_strerror( pCallbackParam->u.operation.result ) ); - } - - /* If a non-NULL context is given, set the flag to exit the demo. */ - if( pCallbackContext != NULL ) - { - ( void ) Atomic_CompareAndSwap_u32( &_exitFlag, JOBS_DEMO_FINISHED, JOBS_DEMO_RUNNING ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Process an action with a message, such as "print" or "publish". - * - * @param[in] mqttConnection The MQTT connection to use if the action is "publish". - * @param[in] action Either #JOB_ACTION_PRINT or #JOB_ACTION_PUBLISH. - * @param[in] pJobDoc A pointer to the Job document. - * @param[in] jobDocLength The length of the Job document. - * - * @return #AWS_IOT_JOB_STATE_SUCCEEDED on success; #AWS_IOT_JOB_STATE_FAILED otherwise. - */ -static AwsIotJobState_t _processMessage( IotMqttConnection_t mqttConnection, - _jobAction_t action, - const char * pJobDoc, - size_t jobDocLength ) -{ - AwsIotJobState_t status = AWS_IOT_JOB_STATE_SUCCEEDED; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - const char * pMessage = NULL, * pTopic = NULL; - size_t messageLength = 0, topicLength = 0; - - /* Both "print" and "publish" require a "message" key. Search the Job - * document for this key. */ - if( _getJsonString( pJobDoc, - jobDocLength, - JOB_MESSAGE_KEY, - JOB_MESSAGE_KEY_LENGTH, - &pMessage, - &messageLength ) == false ) - { - IotLogError( "Job document for \"print\" or \"publish\" does not contain a %s key.", - JOB_MESSAGE_KEY ); - - status = AWS_IOT_JOB_STATE_FAILED; - } - - if( status == AWS_IOT_JOB_STATE_SUCCEEDED ) - { - if( action == JOB_ACTION_PRINT ) - { - /* Print the given message if the action is "print". */ - IotLog( IOT_LOG_INFO, &_logHideAll, - "\r\n" - "/*-----------------------------------------------------------*/\r\n" - "\r\n" - "%.*s\r\n" - "\r\n" - "/*-----------------------------------------------------------*/\r\n" - "\r\n", messageLength, pMessage ); - } - else - { - /* Extract the topic if the action is "publish". */ - if( _getJsonString( pJobDoc, - jobDocLength, - JOB_TOPIC_KEY, - JOB_TOPIC_KEY_LENGTH, - &pTopic, - &topicLength ) == false ) - { - IotLogError( "Job document for action \"publish\" does not contain a %s key.", - JOB_TOPIC_KEY ); - - status = AWS_IOT_JOB_STATE_FAILED; - } - - if( status == AWS_IOT_JOB_STATE_SUCCEEDED ) - { - publishInfo.qos = IOT_MQTT_QOS_0; - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = ( uint16_t ) topicLength; - publishInfo.pPayload = pMessage; - publishInfo.payloadLength = messageLength; - - mqttStatus = IotMqtt_PublishAsync( mqttConnection, &publishInfo, 0, NULL, NULL ); - - if( mqttStatus != IOT_MQTT_SUCCESS ) - { - status = AWS_IOT_JOB_STATE_FAILED; - } - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Process a Job received from the Notify Next callback. - * - * @param[in] pJobInfo The parameter to the Notify Next callback that contains - * information about the received Job. - * @param[in] pJobId A pointer to the Job ID. - * @param[in] jobIdLength The length of the Job ID. - * @param[in] pJobDoc A pointer to the Job document. - * @param[in] jobDocLength The length of the Job document. - */ -static void _processJob( const AwsIotJobsCallbackParam_t * pJobInfo, - const char * pJobId, - size_t jobIdLength, - const char * pJobDoc, - size_t jobDocLength ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - const char * pAction = NULL; - size_t actionLength = 0; - _jobAction_t action = JOB_ACTION_UNKNOWN; - - IotLogInfo( "Job document received: %.*s", jobDocLength, pJobDoc ); - - /* Initialize the common parameter of Jobs requests. */ - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - - requestInfo.mqttConnection = pJobInfo->mqttConnection; - requestInfo.pThingName = pJobInfo->pThingName; - requestInfo.thingNameLength = pJobInfo->thingNameLength; - requestInfo.pJobId = pJobId; - requestInfo.jobIdLength = jobIdLength; - - /* Tell the Jobs service that the device has started working on the Job. - * Use the StartNext API to set the Job's status to IN_PROGRESS. */ - callbackInfo.function = _operationCompleteCallback; - - status = AwsIotJobs_StartNextAsync( &requestInfo, &updateInfo, 0, &callbackInfo, NULL ); - - IotLogInfo( "Jobs StartNext queued with result %s.", AwsIotJobs_strerror( status ) ); - - /* Get the action for this device. */ - if( _getJsonString( pJobDoc, - jobDocLength, - JOB_ACTION_KEY, - JOB_ACTION_KEY_LENGTH, - &pAction, - &actionLength ) == true ) - { - action = _getAction( pAction, actionLength ); - - switch( action ) - { - case JOB_ACTION_EXIT: - callbackInfo.pCallbackContext = JOBS_DEMO_SHOULD_EXIT; - updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; - break; - - case JOB_ACTION_PRINT: - case JOB_ACTION_PUBLISH: - updateInfo.newStatus = _processMessage( pJobInfo->mqttConnection, - action, - pJobDoc, - jobDocLength ); - break; - - default: - IotLogError( "Received Job document with unknown action %.*s.", - actionLength, - pAction ); - - updateInfo.newStatus = AWS_IOT_JOB_STATE_FAILED; - break; - } - } - else - { - IotLogError( "Received Job document does not contain an %s key.", - JOB_ACTION_KEY ); - - /* The given Job document is not valid for this demo. */ - updateInfo.newStatus = AWS_IOT_JOB_STATE_FAILED; - } - - IotLogInfo( "Setting state of %.*s to %s.", - jobIdLength, - pJobId, - AwsIotJobs_StateName( updateInfo.newStatus ) ); - - /* Tell the Jobs service that the device has finished the Job. */ - status = AwsIotJobs_UpdateAsync( &requestInfo, &updateInfo, 0, &callbackInfo, NULL ); - - IotLogInfo( "Jobs Update queued with result %s.", AwsIotJobs_strerror( status ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Jobs Notify Next callback. This function is invoked when a new Job is - * received from the Jobs service. - * - * @param[in] pCallbackContext Ignored. - * @param[in] pCallbackInfo Contains the received Job. - */ -static void _jobsCallback( void * pCallbackContext, - AwsIotJobsCallbackParam_t * pCallbackInfo ) -{ - /* Flags to track the contents of the received Job document. */ - bool idKeyFound = false, docKeyFound = false; - - /* The Job ID and length */ - const char * pJobId = NULL; - size_t jobIdLength = 0; - - /* The Job document (which contains the action) and length. */ - const char * pJobDoc = NULL; - size_t jobDocLength = 0; - - /* Silence warnings about unused parameters. */ - ( void ) pCallbackContext; - - /* Get the Job ID. */ - idKeyFound = _getJsonString( pCallbackInfo->u.callback.pDocument, - pCallbackInfo->u.callback.documentLength, - JOB_ID_KEY, - JOB_ID_KEY_LENGTH, - &pJobId, - &jobIdLength ); - - if( idKeyFound == true ) - { - if( jobIdLength > JOB_ID_MAX_LENGTH ) - { - IotLogError( "Received Job ID %.*s longer than %lu, which is the " - "maximum allowed by AWS IoT. Ignoring Job.", - jobIdLength, - pJobId, - ( unsigned long ) JOB_ID_MAX_LENGTH ); - - idKeyFound = false; - } - else - { - IotLogInfo( "Job %.*s received.", jobIdLength, pJobId ); - } - } - - /* Get the Job document. - * - * Note: This parser used is specific for parsing AWS IoT document received - * through a mutually authenticated connection. This parser will not check - * for the correctness of the document as it is designed for low memory - * footprint rather than checking for correctness of the document. This - * parser is not meant to be used as a general purpose JSON parser. - */ - docKeyFound = AwsIotDocParser_FindValue( pCallbackInfo->u.callback.pDocument, - pCallbackInfo->u.callback.documentLength, - JOB_DOC_KEY, - JOB_DOC_KEY_LENGTH, - &pJobDoc, - &jobDocLength ); - - /* When both the Job ID and Job document are available, process the Job. */ - if( ( idKeyFound == true ) && ( docKeyFound == true ) ) - { - /* Process the Job document. */ - _processJob( pCallbackInfo, - pJobId, - jobIdLength, - pJobDoc, - jobDocLength ); - } - else - { - /* The Jobs service sends an empty Job document when all Jobs are complete. */ - if( ( idKeyFound == false ) && ( docKeyFound == false ) ) - { - IotLog( IOT_LOG_INFO, &_logHideAll, - "\r\n" - "/*-----------------------------------------------------------*/\r\n" - "\r\n" - "All available Jobs complete.\r\n" - "\r\n" - "/*-----------------------------------------------------------*/\r\n" - "\r\n" ); - } - else - { - IotLogWarn( "Received an invalid Job document: %.*s", - pCallbackInfo->u.callback.documentLength, - pCallbackInfo->u.callback.pDocument ); - } - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief The function that runs the Jobs demo, called by the demo runner. - * - * @param[in] awsIotMqttMode Ignored for the Jobs demo. - * @param[in] pIdentifier NULL-terminated Jobs Thing Name. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection for Jobs. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection for Jobs. - * @param[in] pNetworkInterface The network interface to use for this demo. - * - * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. - */ -int RunJobsDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ) -{ - /* Return value of this function and the exit status of this program. */ - int status = EXIT_SUCCESS; - - /* Handle of the MQTT connection used in this demo. */ - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - /* Length of Jobs Thing Name. */ - size_t thingNameLength = 0; - - /* The function that will be set as the Jobs Notify Next callback. */ - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - - /* Status returned by the functions to set the Notify Next callback. */ - AwsIotJobsError_t callbackStatus = AWS_IOT_JOBS_SUCCESS; - - /* Flags for tracking which cleanup functions must be called. */ - bool initialized = false, connected = false; - - /* The first parameter of this demo function is not used. Jobs are specific - * to AWS IoT, so this value is hardcoded to true whenever needed. */ - ( void ) awsIotMqttMode; - - /* Determine the length of the Thing Name. */ - if( pIdentifier != NULL ) - { - thingNameLength = strlen( pIdentifier ); - - if( thingNameLength == 0 ) - { - IotLogError( "The length of the Thing Name (identifier) must be nonzero." ); - - status = EXIT_FAILURE; - } - } - else - { - IotLogError( "A Thing Name (identifier) must be provided for the Jobs demo." ); - - status = EXIT_FAILURE; - } - - /* Initialize the libraries required for this demo. */ - if( status == EXIT_SUCCESS ) - { - status = _initializeDemo(); - - if( status == EXIT_SUCCESS ) - { - initialized = true; - } - } - - /* Establish the MQTT connection used in this demo. */ - if( status == EXIT_SUCCESS ) - { - status = _establishMqttConnection( pIdentifier, - pNetworkServerInfo, - pNetworkCredentialInfo, - pNetworkInterface, - &mqttConnection ); - - if( status == EXIT_SUCCESS ) - { - connected = true; - } - } - - /* Set the Jobs Notify Next callback. This callback waits for the next available Job. */ - if( status == EXIT_SUCCESS ) - { - callbackInfo.function = _jobsCallback; - - callbackStatus = AwsIotJobs_SetNotifyNextCallback( mqttConnection, - pIdentifier, - thingNameLength, - 0, - &callbackInfo ); - - IotLogInfo( "Jobs NotifyNext callback for %.*s set with result %s.", - thingNameLength, - pIdentifier, - AwsIotJobs_strerror( callbackStatus ) ); - - if( callbackStatus != AWS_IOT_JOBS_SUCCESS ) - { - status = EXIT_FAILURE; - } - } - - /* Wait for incoming Jobs. */ - if( status == EXIT_SUCCESS ) - { - IotLog( IOT_LOG_INFO, &_logHideAll, - "\r\n" - "/*-----------------------------------------------------------*/\r\n" - "\r\n" - "The Jobs demo is now ready to accept Jobs.\r\n" - "Jobs may be created using the AWS IoT console or AWS CLI.\r\n" - "See the following link for more information.\r\n" - "\r\n" - "https://docs.aws.amazon.com/cli/latest/reference/iot/create-job.html\r\n" - "\r\n" - "This demo expects Job documents to have an \"action\" JSON key.\r\n" - "The following actions are currently supported:\r\n" - " - print\r\n" - " Logs a message to the local console. The Job document must also contain a \"message\".\r\n" - " For example: { \"action\": \"print\", \"message\": \"Hello world!\"} will cause\r\n" - " \"Hello world!\" to be printed on the console.\r\n" - " - publish\r\n" - " Publishes a message to an MQTT topic. The Job document must also contain a \"message\" and \"topic\".\r\n" - " For example: { \"action\": \"publish\", \"topic\": \"demo/jobs\", \"message\": \"Hello world!\"} will cause\r\n" - " \"Hello world!\" to be published to the topic \"demo/jobs\".\r\n" - " - exit\r\n" - " Exits the demo program. This program will run until { \"action\": \"exit\" } is received.\r\n" - "\r\n" - "/*-----------------------------------------------------------*/\r\n" ); - - /* Wait until a Job with { "action": "exit" } is received. */ - while( Atomic_CompareAndSwap_u32( &_exitFlag, 0, JOBS_DEMO_FINISHED ) == 0 ) - { - IotClock_SleepMs( 1000 ); - } - } - - /* Remove the Jobs Notify Next callback. */ - if( status == EXIT_SUCCESS ) - { - /* Specify that the _jobsCallback function should be replaced with NULL, - * i.e. removed. */ - callbackInfo.function = NULL; - callbackInfo.oldFunction = _jobsCallback; - - callbackStatus = AwsIotJobs_SetNotifyNextCallback( mqttConnection, - pIdentifier, - thingNameLength, - 0, - &callbackInfo ); - - IotLogInfo( "Jobs NotifyNext callback for %.*s removed with result %s.", - thingNameLength, - pIdentifier, - AwsIotJobs_strerror( callbackStatus ) ); - } - - /* Disconnect the MQTT connection and clean up the demo. */ - if( connected == true ) - { - IotMqtt_Disconnect( mqttConnection, 0 ); - } - - if( initialized == true ) - { - _cleanupDemo(); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/demos/src/aws_iot_demo_shadow.c b/demos/src/aws_iot_demo_shadow.c deleted file mode 100644 index e352db2aca..0000000000 --- a/demos/src/aws_iot_demo_shadow.c +++ /dev/null @@ -1,939 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_demo_shadow.c - * @brief Demonstrates usage of the Thing Shadow library. - * - * This program demonstrates the using Shadow documents to toggle a state called - * "powerOn" in a remote device. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include -#include - -/* Set up logging for this demo. */ -#include "iot_demo_logging.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Shadow include. */ -#include "aws_iot_shadow.h" - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration settings. - */ -#ifndef AWS_IOT_DEMO_SHADOW_UPDATE_COUNT - #define AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ( 20 ) -#endif -#ifndef AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS - #define AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ( 3000 ) -#endif -/** @endcond */ - -/* Validate Shadow demo configuration settings. */ -#if AWS_IOT_DEMO_SHADOW_UPDATE_COUNT <= 0 - #error "AWS_IOT_DEMO_SHADOW_UPDATE_COUNT cannot be 0 or negative." -#endif -#if AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS <= 0 - #error "AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS cannot be 0 or negative." -#endif - -/** - * @brief The keep-alive interval used for this demo. - * - * An MQTT ping request will be sent periodically at this interval. - */ -#define KEEP_ALIVE_SECONDS ( 60 ) - -/** - * @brief The timeout for Shadow and MQTT operations in this demo. - */ -#define TIMEOUT_MS ( 5000 ) - -/** - * @brief Format string representing a Shadow document with a "desired" state. - * - * Note the client token, which is required for all Shadow updates. The client - * token must be unique at any given time, but may be reused once the update is - * completed. For this demo, a timestamp is used for a client token. - */ -#define SHADOW_DESIRED_JSON \ - "{" \ - "\"state\":{" \ - "\"desired\":{" \ - "\"powerOn\":%01d" \ - "}" \ - "}," \ - "\"clientToken\":\"%06lu\"" \ - "}" - -/** - * @brief The expected size of #SHADOW_DESIRED_JSON. - * - * Because all the format specifiers in #SHADOW_DESIRED_JSON include a length, - * its full size is known at compile-time. - */ -#define EXPECTED_DESIRED_JSON_SIZE ( sizeof( SHADOW_DESIRED_JSON ) - 3 ) - -/** - * @brief Format string representing a Shadow document with a "reported" state. - * - * Note the client token, which is required for all Shadow updates. The client - * token must be unique at any given time, but may be reused once the update is - * completed. For this demo, a timestamp is used for a client token. - */ -#define SHADOW_REPORTED_JSON \ - "{" \ - "\"state\":{" \ - "\"reported\":{" \ - "\"powerOn\":%01d" \ - "}" \ - "}," \ - "\"clientToken\":\"%06lu\"" \ - "}" - -/** - * @brief The expected size of #SHADOW_REPORTED_JSON. - * - * Because all the format specifiers in #SHADOW_REPORTED_JSON include a length, - * its full size is known at compile-time. - */ -#define EXPECTED_REPORTED_JSON_SIZE ( sizeof( SHADOW_REPORTED_JSON ) - 3 ) - -/*-----------------------------------------------------------*/ - -/* Declaration of demo function. */ -int RunShadowDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Parses a key in the "state" section of a Shadow delta document. - * - * @param[in] pDeltaDocument The Shadow delta document to parse. - * @param[in] deltaDocumentLength The length of `pDeltaDocument`. - * @param[in] pDeltaKey The key in the delta document to find. Must be NULL-terminated. - * @param[out] pDelta Set to the first character in the delta key. - * @param[out] pDeltaLength The length of the delta key. - * - * @return `true` if the given delta key is found; `false` otherwise. - */ -static bool _getDelta( const char * pDeltaDocument, - size_t deltaDocumentLength, - const char * pDeltaKey, - const char ** pDelta, - size_t * pDeltaLength ) -{ - bool stateFound = false, deltaFound = false; - const size_t deltaKeyLength = strlen( pDeltaKey ); - const char * pState = NULL; - size_t stateLength = 0; - - /* Find the "state" key in the delta document. - * - * Note: This parser used is specific for parsing AWS IoT document received - * through a mutually authenticated connection. This parser will not check - * for the correctness of the document as it is designed for low memory - * footprint rather than checking for correctness of the document. This - * parser is not meant to be used as a general purpose JSON parser. - */ - stateFound = AwsIotDocParser_FindValue( pDeltaDocument, - deltaDocumentLength, - "state", - 5, - &pState, - &stateLength ); - - if( stateFound == true ) - { - /* Find the delta key within the "state" section. - * - * Note: This parser used is specific for parsing AWS IoT document received - * through a mutually authenticated connection. This parser will not check - * for the correctness of the document as it is designed for low memory - * footprint rather than checking for correctness of the document. This - * parser is not meant to be used as a general purpose JSON parser. - */ - deltaFound = AwsIotDocParser_FindValue( pState, - stateLength, - pDeltaKey, - deltaKeyLength, - pDelta, - pDeltaLength ); - } - else - { - IotLogWarn( "Failed to find \"state\" in Shadow delta document." ); - } - - return deltaFound; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Parses the "state" key from the "previous" or "current" sections of a - * Shadow updated document. - * - * @param[in] pUpdatedDocument The Shadow updated document to parse. - * @param[in] updatedDocumentLength The length of `pUpdatedDocument`. - * @param[in] pSectionKey Either "previous" or "current". Must be NULL-terminated. - * @param[out] pState Set to the first character in "state". - * @param[out] pStateLength Length of the "state" section. - * - * @return `true` if the "state" was found; `false` otherwise. - */ -static bool _getUpdatedState( const char * pUpdatedDocument, - size_t updatedDocumentLength, - const char * pSectionKey, - const char ** pState, - size_t * pStateLength ) -{ - bool sectionFound = false, stateFound = false; - const size_t sectionKeyLength = strlen( pSectionKey ); - const char * pSection = NULL; - size_t sectionLength = 0; - - /* Find the given section in the updated document. - * - * Note: This parser used is specific for parsing AWS IoT document received - * through a mutually authenticated connection. This parser will not check - * for the correctness of the document as it is designed for low memory - * footprint rather than checking for correctness of the document. This - * parser is not meant to be used as a general purpose JSON parser. - */ - sectionFound = AwsIotDocParser_FindValue( pUpdatedDocument, - updatedDocumentLength, - pSectionKey, - sectionKeyLength, - &pSection, - §ionLength ); - - if( sectionFound == true ) - { - /* Find the "state" key within the "previous" or "current" section. - * - * Note: This parser used is specific for parsing AWS IoT document received - * through a mutually authenticated connection. This parser will not check - * for the correctness of the document as it is designed for low memory - * footprint rather than checking for correctness of the document. This - * parser is not meant to be used as a general purpose JSON parser. - */ - stateFound = AwsIotDocParser_FindValue( pSection, - sectionLength, - "state", - 5, - pState, - pStateLength ); - } - else - { - IotLogWarn( "Failed to find section %s in Shadow updated document.", - pSectionKey ); - } - - return stateFound; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Shadow delta callback, invoked when the desired and updates Shadow - * states differ. - * - * This function simulates a device updating its state in response to a Shadow. - * - * @param[in] pCallbackContext Not used. - * @param[in] pCallbackParam The received Shadow delta document. - */ -static void _shadowDeltaCallback( void * pCallbackContext, - AwsIotShadowCallbackParam_t * pCallbackParam ) -{ - bool deltaFound = false; - const char * pDelta = NULL; - size_t deltaLength = 0; - IotSemaphore_t * pDeltaSemaphore = pCallbackContext; - int updateDocumentLength = 0; - AwsIotShadowError_t updateStatus = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - int32_t newState = 0; - - /* Stored state. */ - static int32_t currentState = 0; - - /* A buffer containing the update document. It has static duration to prevent - * it from being placed on the call stack. */ - static char pUpdateDocument[ EXPECTED_REPORTED_JSON_SIZE + 1 ] = { 0 }; - - /* Check if there is a different "powerOn" state in the Shadow. */ - deltaFound = _getDelta( pCallbackParam->u.callback.pDocument, - pCallbackParam->u.callback.documentLength, - "powerOn", - &pDelta, - &deltaLength ); - - if( deltaFound == true ) - { - /* Change the current state based on the value in the delta document. */ - if( *pDelta == '0' ) - { - newState = 0; - } - else if( *pDelta == '1' ) - { - newState = 1; - } - else - { - IotLogWarn( "Unknown powerOn state parsed from delta document." ); - - /* Set new state to current state to ignore the delta document. */ - newState = currentState; - } - - if( newState != currentState ) - { - /* Toggle state. */ - IotLogInfo( "%.*s changing state from %d to %d.", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName, - currentState, - newState ); - - currentState = newState; - - /* Set the common members to report the new state. */ - updateDocument.pThingName = pCallbackParam->pThingName; - updateDocument.thingNameLength = pCallbackParam->thingNameLength; - updateDocument.u.update.pUpdateDocument = pUpdateDocument; - updateDocument.u.update.updateDocumentLength = EXPECTED_REPORTED_JSON_SIZE; - - /* Generate a Shadow document for the reported state. To keep the client - * token within 6 characters, it is modded by 1000000. */ - updateDocumentLength = snprintf( pUpdateDocument, - EXPECTED_REPORTED_JSON_SIZE + 1, - SHADOW_REPORTED_JSON, - ( int ) currentState, - ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) ); - - if( ( size_t ) updateDocumentLength != EXPECTED_REPORTED_JSON_SIZE ) - { - IotLogError( "Failed to generate reported state document for Shadow update." ); - } - else - { - /* Send the Shadow update. Its result is not checked, as the Shadow updated - * callback will report if the Shadow was successfully updated. Because the - * Shadow is constantly updated in this demo, the "Keep Subscriptions" flag - * is passed to this function. */ - updateStatus = AwsIotShadow_UpdateAsync( pCallbackParam->mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - NULL, - NULL ); - - if( updateStatus != AWS_IOT_SHADOW_STATUS_PENDING ) - { - IotLogWarn( "%.*s failed to report new state, error %s.", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName, - AwsIotShadow_strerror( updateStatus ) ); - } - else - { - IotLogInfo( "%.*s sent new state report: %.*s", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName, - EXPECTED_REPORTED_JSON_SIZE, - pUpdateDocument ); - } - } - - /* Post to the delta semaphore to unblock the thread sending Shadow updates. */ - IotSemaphore_Post( pDeltaSemaphore ); - } - } - else - { - IotLogWarn( "Failed to parse powerOn state from delta document." ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Shadow updated callback, invoked when the Shadow document changes. - * - * This function reports when a Shadow has been updated. - * - * @param[in] pCallbackContext Not used. - * @param[in] pCallbackParam The received Shadow updated document. - */ -static void _shadowUpdatedCallback( void * pCallbackContext, - AwsIotShadowCallbackParam_t * pCallbackParam ) -{ - bool previousFound = false, currentFound = false; - const char * pPrevious = NULL, * pCurrent = NULL; - size_t previousLength = 0, currentLength = 0; - - /* Silence warnings about unused parameters. */ - ( void ) pCallbackContext; - - /* Find the previous Shadow document. */ - previousFound = _getUpdatedState( pCallbackParam->u.callback.pDocument, - pCallbackParam->u.callback.documentLength, - "previous", - &pPrevious, - &previousLength ); - - /* Find the current Shadow document. */ - currentFound = _getUpdatedState( pCallbackParam->u.callback.pDocument, - pCallbackParam->u.callback.documentLength, - "current", - &pCurrent, - ¤tLength ); - - /* Log the previous and current states. */ - if( ( previousFound == true ) && ( currentFound == true ) ) - { - IotLogInfo( "Shadow was updated!\r\n" - "Previous: {\"state\":%.*s}\r\n" - "Current: {\"state\":%.*s}", - previousLength, - pPrevious, - currentLength, - pCurrent ); - } - else - { - if( previousFound == false ) - { - IotLogWarn( "Previous state not found in Shadow updated document." ); - } - - if( currentFound == false ) - { - IotLogWarn( "Current state not found in Shadow updated document." ); - } - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Initialize the the MQTT library and the Shadow library. - * - * @return `EXIT_SUCCESS` if all libraries were successfully initialized; - * `EXIT_FAILURE` otherwise. - */ -static int _initializeDemo( void ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; - AwsIotShadowError_t shadowInitStatus = AWS_IOT_SHADOW_SUCCESS; - - /* Flags to track cleanup on error. */ - bool mqttInitialized = false; - - /* Initialize the MQTT library. */ - mqttInitStatus = IotMqtt_Init(); - - if( mqttInitStatus == IOT_MQTT_SUCCESS ) - { - mqttInitialized = true; - } - else - { - status = EXIT_FAILURE; - } - - /* Initialize the Shadow library. */ - if( status == EXIT_SUCCESS ) - { - /* Use the default MQTT timeout. */ - shadowInitStatus = AwsIotShadow_Init( 0 ); - - if( shadowInitStatus != AWS_IOT_SHADOW_SUCCESS ) - { - status = EXIT_FAILURE; - } - } - - /* Clean up on error. */ - if( status == EXIT_FAILURE ) - { - if( mqttInitialized == true ) - { - IotMqtt_Cleanup(); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Clean up the the MQTT library and the Shadow library. - */ -static void _cleanupDemo( void ) -{ - AwsIotShadow_Cleanup(); - IotMqtt_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Establish a new connection to the MQTT server for the Shadow demo. - * - * @param[in] pIdentifier NULL-terminated MQTT client identifier. The Shadow - * demo will use the Thing Name as the client identifier. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkInterface The network interface to use for this demo. - * @param[out] pMqttConnection Set to the handle to the new MQTT connection. - * - * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` - * otherwise. - */ -static int _establishMqttConnection( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface, - IotMqttConnection_t * pMqttConnection ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* Set the members of the network info not set by the initializer. This - * struct provided information on the transport layer to the MQTT connection. */ - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; - networkInfo.pNetworkInterface = pNetworkInterface; - - /* Set the members of the connection info not set by the initializer. */ - connectInfo.awsIotMqttMode = true; - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; - - /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ - connectInfo.pClientIdentifier = pIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - - IotLogInfo( "Shadow Thing Name is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); - - /* Establish the MQTT connection. */ - connectStatus = IotMqtt_Connect( &networkInfo, - &connectInfo, - TIMEOUT_MS, - pMqttConnection ); - - if( connectStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT CONNECT returned error %s.", - IotMqtt_strerror( connectStatus ) ); - - status = EXIT_FAILURE; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Set the Shadow callback functions used in this demo. - * - * @param[in] pDeltaSemaphore Used to synchronize Shadow updates with the delta - * callback. - * @param[in] mqttConnection The MQTT connection used for Shadows. - * @param[in] pThingName The Thing Name for Shadows in this demo. - * @param[in] thingNameLength The length of `pThingName`. - * - * @return `EXIT_SUCCESS` if all Shadow callbacks were set; `EXIT_FAILURE` - * otherwise. - */ -static int _setShadowCallbacks( IotSemaphore_t * pDeltaSemaphore, - IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength ) -{ - int status = EXIT_SUCCESS; - AwsIotShadowError_t callbackStatus = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER, - updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - - /* Set the functions for callbacks. */ - deltaCallback.pCallbackContext = pDeltaSemaphore; - deltaCallback.function = _shadowDeltaCallback; - updatedCallback.function = _shadowUpdatedCallback; - - /* Set the delta callback, which notifies of different desired and reported - * Shadow states. */ - callbackStatus = AwsIotShadow_SetDeltaCallback( mqttConnection, - pThingName, - thingNameLength, - 0, - &deltaCallback ); - - if( callbackStatus == AWS_IOT_SHADOW_SUCCESS ) - { - /* Set the updated callback, which notifies when a Shadow document is - * changed. */ - callbackStatus = AwsIotShadow_SetUpdatedCallback( mqttConnection, - pThingName, - thingNameLength, - 0, - &updatedCallback ); - } - - if( callbackStatus != AWS_IOT_SHADOW_SUCCESS ) - { - IotLogError( "Failed to set demo shadow callback, error %s.", - AwsIotShadow_strerror( callbackStatus ) ); - - status = EXIT_FAILURE; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Try to delete any Shadow document in the cloud. - * - * @param[in] mqttConnection The MQTT connection used for Shadows. - * @param[in] pThingName The Shadow Thing Name to delete. - * @param[in] thingNameLength The length of `pThingName`. - */ -static void _clearShadowDocument( IotMqttConnection_t mqttConnection, - const char * const pThingName, - size_t thingNameLength ) -{ - AwsIotShadowError_t deleteStatus = AWS_IOT_SHADOW_STATUS_PENDING; - - /* Delete any existing Shadow document so that this demo starts with an empty - * Shadow. */ - deleteStatus = AwsIotShadow_DeleteSync( mqttConnection, - pThingName, - thingNameLength, - 0, - TIMEOUT_MS ); - - /* Check for return values of "SUCCESS" and "NOT FOUND". Both of these values - * mean that the Shadow document is now empty. */ - if( ( deleteStatus == AWS_IOT_SHADOW_SUCCESS ) || ( deleteStatus == AWS_IOT_SHADOW_NOT_FOUND ) ) - { - IotLogInfo( "Successfully cleared Shadow of %.*s.", - thingNameLength, - pThingName ); - } - else - { - IotLogWarn( "Shadow of %.*s not cleared.", - thingNameLength, - pThingName ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Send the Shadow updates that will trigger the Shadow callbacks. - * - * @param[in] pDeltaSemaphore Used to synchronize Shadow updates with the delta - * callback. - * @param[in] mqttConnection The MQTT connection used for Shadows. - * @param[in] pThingName The Thing Name for Shadows in this demo. - * @param[in] thingNameLength The length of `pThingName`. - * - * @return `EXIT_SUCCESS` if all Shadow updates were sent; `EXIT_FAILURE` - * otherwise. - */ -static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, - IotMqttConnection_t mqttConnection, - const char * const pThingName, - size_t thingNameLength ) -{ - int status = EXIT_SUCCESS; - int32_t i = 0, desiredState = 0; - AwsIotShadowError_t updateStatus = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - - /* A buffer containing the update document. It has static duration to prevent - * it from being placed on the call stack. */ - static char pUpdateDocument[ EXPECTED_DESIRED_JSON_SIZE + 1 ] = { 0 }; - - /* Set the common members of the Shadow update document info. */ - updateDocument.pThingName = pThingName; - updateDocument.thingNameLength = thingNameLength; - updateDocument.u.update.pUpdateDocument = pUpdateDocument; - updateDocument.u.update.updateDocumentLength = EXPECTED_DESIRED_JSON_SIZE; - - /* Publish Shadow updates at a set period. */ - for( i = 1; i <= AWS_IOT_DEMO_SHADOW_UPDATE_COUNT; i++ ) - { - /* Toggle the desired state. */ - desiredState = !( desiredState ); - - /* Generate a Shadow desired state document, using a timestamp for the client - * token. To keep the client token within 6 characters, it is modded by 1000000. */ - status = snprintf( pUpdateDocument, - EXPECTED_DESIRED_JSON_SIZE + 1, - SHADOW_DESIRED_JSON, - ( int ) desiredState, - ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) ); - - /* Check for errors from snprintf. The expected value is the length of - * the desired JSON document less the format specifier for the state. */ - if( ( size_t ) status != EXPECTED_DESIRED_JSON_SIZE ) - { - IotLogError( "Failed to generate desired state document for Shadow update" - " %d of %d.", i, AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ); - - status = EXIT_FAILURE; - break; - } - else - { - status = EXIT_SUCCESS; - } - - IotLogInfo( "Sending Shadow update %d of %d: %s", - i, - AWS_IOT_DEMO_SHADOW_UPDATE_COUNT, - pUpdateDocument ); - - /* Send the Shadow update. Because the Shadow is constantly updated in - * this demo, the "Keep Subscriptions" flag is passed to this function. - * Note that this flag only needs to be passed on the first call, but - * passing it for subsequent calls is fine. - */ - updateStatus = AwsIotShadow_UpdateSync( mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - TIMEOUT_MS ); - - /* Check the status of the Shadow update. */ - if( updateStatus != AWS_IOT_SHADOW_SUCCESS ) - { - IotLogError( "Failed to send Shadow update %d of %d, error %s.", - i, - AWS_IOT_DEMO_SHADOW_UPDATE_COUNT, - AwsIotShadow_strerror( updateStatus ) ); - - status = EXIT_FAILURE; - break; - } - else - { - IotLogInfo( "Successfully sent Shadow update %d of %d.", - i, - AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ); - - /* Wait for the delta callback to change its state before continuing. */ - if( IotSemaphore_TimedWait( pDeltaSemaphore, TIMEOUT_MS ) == false ) - { - IotLogError( "Timed out waiting on delta callback to change state." ); - - status = EXIT_FAILURE; - break; - } - } - - IotClock_SleepMs( AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief The function that runs the Shadow demo, called by the demo runner. - * - * @param[in] awsIotMqttMode Ignored for the Shadow demo. - * @param[in] pIdentifier NULL-terminated Shadow Thing Name. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection for Shadows. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection for Shadows. - * @param[in] pNetworkInterface The network interface to use for this demo. - * - * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. - */ -int RunShadowDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ) -{ - /* Return value of this function and the exit status of this program. */ - int status = 0; - - /* Handle of the MQTT connection used in this demo. */ - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - /* Length of Shadow Thing Name. */ - size_t thingNameLength = 0; - - /* Allows the Shadow update function to wait for the delta callback to complete - * a state change before continuing. */ - IotSemaphore_t deltaSemaphore; - - /* Flags for tracking which cleanup functions must be called. */ - bool librariesInitialized = false, connectionEstablished = false, deltaSemaphoreCreated = false; - - /* The first parameter of this demo function is not used. Shadows are specific - * to AWS IoT, so this value is hardcoded to true whenever needed. */ - ( void ) awsIotMqttMode; - - /* Determine the length of the Thing Name. */ - if( pIdentifier != NULL ) - { - thingNameLength = strlen( pIdentifier ); - - if( thingNameLength == 0 ) - { - IotLogError( "The length of the Thing Name (identifier) must be nonzero." ); - - status = EXIT_FAILURE; - } - } - else - { - IotLogError( "A Thing Name (identifier) must be provided for the Shadow demo." ); - - status = EXIT_FAILURE; - } - - /* Initialize the libraries required for this demo. */ - if( status == EXIT_SUCCESS ) - { - status = _initializeDemo(); - } - - if( status == EXIT_SUCCESS ) - { - /* Mark the libraries as initialized. */ - librariesInitialized = true; - - /* Establish a new MQTT connection. */ - status = _establishMqttConnection( pIdentifier, - pNetworkServerInfo, - pNetworkCredentialInfo, - pNetworkInterface, - &mqttConnection ); - } - - if( status == EXIT_SUCCESS ) - { - /* Mark the MQTT connection as established. */ - connectionEstablished = true; - - /* Create the semaphore that synchronizes with the delta callback. */ - deltaSemaphoreCreated = IotSemaphore_Create( &deltaSemaphore, 0, 1 ); - - if( deltaSemaphoreCreated == false ) - { - status = EXIT_FAILURE; - } - } - - if( status == EXIT_SUCCESS ) - { - /* Set the Shadow callbacks for this demo. */ - status = _setShadowCallbacks( &deltaSemaphore, - mqttConnection, - pIdentifier, - thingNameLength ); - } - - if( status == EXIT_SUCCESS ) - { - /* Clear the Shadow document so that this demo starts with no existing - * Shadow. */ - _clearShadowDocument( mqttConnection, pIdentifier, thingNameLength ); - - /* Send Shadow updates. */ - status = _sendShadowUpdates( &deltaSemaphore, - mqttConnection, - pIdentifier, - thingNameLength ); - - /* Delete the Shadow document created by this demo to clean up. */ - _clearShadowDocument( mqttConnection, pIdentifier, thingNameLength ); - } - - /* Disconnect the MQTT connection if it was established. */ - if( connectionEstablished == true ) - { - IotMqtt_Disconnect( mqttConnection, 0 ); - } - - /* Clean up libraries if they were initialized. */ - if( librariesInitialized == true ) - { - _cleanupDemo(); - } - - /* Destroy the delta semaphore if it was created. */ - if( deltaSemaphoreCreated == true ) - { - IotSemaphore_Destroy( &deltaSemaphore ); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/demos/src/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c deleted file mode 100644 index d0f252d3ff..0000000000 --- a/demos/src/iot_demo_mqtt.c +++ /dev/null @@ -1,856 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_mqtt.c - * @brief Demonstrates usage of the MQTT library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include -#include - -/* Set up logging for this demo. */ -#include "iot_demo_logging.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration settings. - */ -#ifndef IOT_DEMO_MQTT_TOPIC_PREFIX - #define IOT_DEMO_MQTT_TOPIC_PREFIX "iotdemo" -#endif -#ifndef IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - #define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) -#endif -#ifndef IOT_DEMO_MQTT_PUBLISH_BURST_COUNT - #define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) -#endif -/** @endcond */ - -/* Validate MQTT demo configuration settings. */ -#if IOT_DEMO_MQTT_PUBLISH_BURST_SIZE <= 0 - #error "IOT_DEMO_MQTT_PUBLISH_BURST_SIZE cannot be 0 or negative." -#endif -#if IOT_DEMO_MQTT_PUBLISH_BURST_COUNT <= 0 - #error "IOT_DEMO_MQTT_PUBLISH_BURST_COUNT cannot be 0 or negative." -#endif - -/** - * @brief The first characters in the client identifier. A timestamp is appended - * to this prefix to create a unique client identifer. - * - * This prefix is also used to generate topic names and topic filters used in this - * demo. - */ -#define CLIENT_IDENTIFIER_PREFIX "iotdemo" - -/** - * @brief The longest client identifier that an MQTT server must accept (as defined - * by the MQTT 3.1.1 spec) is 23 characters. Add 1 to include the length of the NULL - * terminator. - */ -#define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) - -/** - * @brief The keep-alive interval used for this demo. - * - * An MQTT ping request will be sent periodically at this interval. - */ -#define KEEP_ALIVE_SECONDS ( 60 ) - -/** - * @brief The timeout for MQTT operations in this demo. - */ -#define MQTT_TIMEOUT_MS ( 5000 ) - -/** - * @brief The Last Will and Testament topic name in this demo. - * - * The MQTT server will publish a message to this topic name if this client is - * unexpectedly disconnected. - */ -#define WILL_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/will" - -/** - * @brief The length of #WILL_TOPIC_NAME. - */ -#define WILL_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( WILL_TOPIC_NAME ) - 1 ) ) - -/** - * @brief The message to publish to #WILL_TOPIC_NAME. - */ -#define WILL_MESSAGE "MQTT demo unexpectedly disconnected." - -/** - * @brief The length of #WILL_MESSAGE. - */ -#define WILL_MESSAGE_LENGTH ( ( size_t ) ( sizeof( WILL_MESSAGE ) - 1 ) ) - -/** - * @brief How many topic filters will be used in this demo. - */ -#define TOPIC_FILTER_COUNT ( 4 ) - -/** - * @brief The length of each topic filter. - * - * For convenience, all topic filters are the same length. - */ -#define TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1" ) - 1 ) ) - -/** - * @brief Format string of the PUBLISH messages in this demo. - */ -#define PUBLISH_PAYLOAD_FORMAT "Hello world %d!" - -/** - * @brief Size of the buffer that holds the PUBLISH messages in this demo. - */ -#define PUBLISH_PAYLOAD_BUFFER_LENGTH ( sizeof( PUBLISH_PAYLOAD_FORMAT ) + 2 ) - -/** - * @brief The maximum number of times each PUBLISH in this demo will be retried. - */ -#define PUBLISH_RETRY_LIMIT ( 10 ) - -/** - * @brief A PUBLISH message is retried if no response is received within this - * time. - */ -#define PUBLISH_RETRY_MS ( 1000 ) - -/** - * @brief The topic name on which acknowledgement messages for incoming publishes - * should be published. - */ -#define ACKNOWLEDGEMENT_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/acknowledgements" - -/** - * @brief The length of #ACKNOWLEDGEMENT_TOPIC_NAME. - */ -#define ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( ACKNOWLEDGEMENT_TOPIC_NAME ) - 1 ) ) - -/** - * @brief Format string of PUBLISH acknowledgement messages in this demo. - */ -#define ACKNOWLEDGEMENT_MESSAGE_FORMAT "Client has received PUBLISH %.*s from server." - -/** - * @brief Size of the buffers that hold acknowledgement messages in this demo. - */ -#define ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ( sizeof( ACKNOWLEDGEMENT_MESSAGE_FORMAT ) + 2 ) - -/*-----------------------------------------------------------*/ - -/* Declaration of demo function. */ -int RunMqttDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Called by the MQTT library when an operation completes. - * - * The demo uses this callback to determine the result of PUBLISH operations. - * @param[in] param1 The number of the PUBLISH that completed, passed as an intptr_t. - * @param[in] pOperation Information about the completed operation passed by the - * MQTT library. - */ -static void _operationCompleteCallback( void * param1, - IotMqttCallbackParam_t * const pOperation ) -{ - intptr_t publishCount = ( intptr_t ) param1; - - /* Silence warnings about unused variables. publishCount will not be used if - * logging is disabled. */ - ( void ) publishCount; - - /* Print the status of the completed operation. A PUBLISH operation is - * successful when transmitted over the network. */ - if( pOperation->u.operation.result == IOT_MQTT_SUCCESS ) - { - IotLogInfo( "MQTT %s %d successfully sent.", - IotMqtt_OperationType( pOperation->u.operation.type ), - ( int ) publishCount ); - } - else - { - IotLogError( "MQTT %s %d could not be sent. Error %s.", - IotMqtt_OperationType( pOperation->u.operation.type ), - ( int ) publishCount, - IotMqtt_strerror( pOperation->u.operation.result ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Called by the MQTT library when an incoming PUBLISH message is received. - * - * The demo uses this callback to handle incoming PUBLISH messages. This callback - * prints the contents of an incoming message and publishes an acknowledgement - * to the MQTT server. - * @param[in] param1 Counts the total number of received PUBLISH messages. This - * callback will increment this counter. - * @param[in] pPublish Information about the incoming PUBLISH message passed by - * the MQTT library. - */ -static void _mqttSubscriptionCallback( void * param1, - IotMqttCallbackParam_t * const pPublish ) -{ - int acknowledgementLength = 0; - size_t messageNumberIndex = 0, messageNumberLength = 1; - IotSemaphore_t * pPublishesReceived = ( IotSemaphore_t * ) param1; - const char * pPayload = pPublish->u.message.info.pPayload; - char pAcknowledgementMessage[ ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; - IotMqttPublishInfo_t acknowledgementInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Print information about the incoming PUBLISH message. */ - IotLogInfo( "Incoming PUBLISH received:\r\n" - "Subscription topic filter: %.*s\r\n" - "Publish topic name: %.*s\r\n" - "Publish retain flag: %d\r\n" - "Publish QoS: %d\r\n" - "Publish payload: %.*s", - pPublish->u.message.topicFilterLength, - pPublish->u.message.pTopicFilter, - pPublish->u.message.info.topicNameLength, - pPublish->u.message.info.pTopicName, - pPublish->u.message.info.retain, - pPublish->u.message.info.qos, - pPublish->u.message.info.payloadLength, - pPayload ); - - /* Find the message number inside of the PUBLISH message. */ - for( messageNumberIndex = 0; messageNumberIndex < pPublish->u.message.info.payloadLength; messageNumberIndex++ ) - { - /* The payload that was published contained ASCII characters, so find - * beginning of the message number by checking for ASCII digits. */ - if( ( pPayload[ messageNumberIndex ] >= '0' ) && - ( pPayload[ messageNumberIndex ] <= '9' ) ) - { - break; - } - } - - /* Check that a message number was found within the PUBLISH payload. */ - if( messageNumberIndex < pPublish->u.message.info.payloadLength ) - { - /* Compute the length of the message number. */ - while( ( messageNumberIndex + messageNumberLength < pPublish->u.message.info.payloadLength ) && - ( *( pPayload + messageNumberIndex + messageNumberLength ) >= '0' ) && - ( *( pPayload + messageNumberIndex + messageNumberLength ) <= '9' ) ) - { - messageNumberLength++; - } - - /* Generate an acknowledgement message. */ - acknowledgementLength = snprintf( pAcknowledgementMessage, - ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH, - ACKNOWLEDGEMENT_MESSAGE_FORMAT, - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); - - /* Check for errors from snprintf. */ - if( acknowledgementLength < 0 ) - { - IotLogWarn( "Failed to generate acknowledgement message for PUBLISH *.*s.", - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); - } - else - { - /* Set the members of the publish info for the acknowledgement message. */ - acknowledgementInfo.qos = IOT_MQTT_QOS_1; - acknowledgementInfo.pTopicName = ACKNOWLEDGEMENT_TOPIC_NAME; - acknowledgementInfo.topicNameLength = ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH; - acknowledgementInfo.pPayload = pAcknowledgementMessage; - acknowledgementInfo.payloadLength = ( size_t ) acknowledgementLength; - acknowledgementInfo.retryMs = PUBLISH_RETRY_MS; - acknowledgementInfo.retryLimit = PUBLISH_RETRY_LIMIT; - - /* Send the acknowledgement for the received message. This demo program - * will not be notified on the status of the acknowledgement because - * neither a callback nor IOT_MQTT_FLAG_WAITABLE is set. However, - * the MQTT library will still guarantee at-least-once delivery (subject - * to the retransmission strategy) because the acknowledgement message is - * sent at QoS 1. */ - if( IotMqtt_PublishAsync( pPublish->mqttConnection, - &acknowledgementInfo, - 0, - NULL, - NULL ) == IOT_MQTT_STATUS_PENDING ) - { - IotLogInfo( "Acknowledgement message for PUBLISH %.*s will be sent.", - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); - } - else - { - IotLogWarn( "Acknowledgement message for PUBLISH %.*s will NOT be sent.", - ( int ) messageNumberLength, - pPayload + messageNumberIndex ); - } - } - } - - /* Increment the number of PUBLISH messages received. */ - IotSemaphore_Post( pPublishesReceived ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Initialize the MQTT library. - * - * @return `EXIT_SUCCESS` if all libraries were successfully initialized; - * `EXIT_FAILURE` otherwise. - */ -static int _initializeDemo( void ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; - - mqttInitStatus = IotMqtt_Init(); - - if( mqttInitStatus != IOT_MQTT_SUCCESS ) - { - /* Failed to initialize MQTT library. */ - status = EXIT_FAILURE; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Clean up the MQTT library. - */ -static void _cleanupDemo( void ) -{ - IotMqtt_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Establish a new connection to the MQTT server. - * - * @param[in] awsIotMqttMode Specify if this demo is running with the AWS IoT - * MQTT server. Set this to `false` if using another MQTT server. - * @param[in] pIdentifier NULL-terminated MQTT client identifier. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkInterface The network interface to use for this demo. - * @param[out] pMqttConnection Set to the handle to the new MQTT connection. - * - * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` - * otherwise. - */ -static int _establishMqttConnection( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface, - IotMqttConnection_t * pMqttConnection ) -{ - int status = EXIT_SUCCESS; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - IotNetworkCredentials_t pCredentials = ( IotNetworkCredentials_t ) pNetworkCredentialInfo; - - /* Set the members of the network info not set by the initializer. This - * struct provided information on the transport layer to the MQTT connection. */ - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; - networkInfo.pNetworkInterface = pNetworkInterface; - - #if ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 ) && defined( IOT_DEMO_MQTT_SERIALIZER ) - networkInfo.pSerializer = IOT_DEMO_MQTT_SERIALIZER; - #endif - - /* Set the members of the connection info not set by the initializer. */ - connectInfo.awsIotMqttMode = awsIotMqttMode; - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; - connectInfo.pWillInfo = &willInfo; - - if( pCredentials != NULL ) - { - connectInfo.pUserName = pCredentials->pUserName; - connectInfo.userNameLength = ( uint16_t ) pCredentials->userNameSize; - connectInfo.pPassword = pCredentials->pPassword; - connectInfo.passwordLength = ( uint16_t ) pCredentials->passwordSize; - } - else - { - connectInfo.pUserName = NULL; - connectInfo.userNameLength = 0; - connectInfo.pPassword = NULL; - connectInfo.passwordLength = 0; - } - - /* Set the members of the Last Will and Testament (LWT) message info. The - * MQTT server will publish the LWT message if this client disconnects - * unexpectedly. */ - willInfo.pTopicName = WILL_TOPIC_NAME; - willInfo.topicNameLength = WILL_TOPIC_NAME_LENGTH; - willInfo.pPayload = WILL_MESSAGE; - willInfo.payloadLength = WILL_MESSAGE_LENGTH; - - /* Use the parameter client identifier if provided. Otherwise, generate a - * unique client identifier. */ - if( ( pIdentifier != NULL ) && ( pIdentifier[ 0 ] != '\0' ) ) - { - connectInfo.pClientIdentifier = pIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - } - else - { - /* Every active MQTT connection must have a unique client identifier. The demos - * generate this unique client identifier by appending a timestamp to a common - * prefix. */ - status = snprintf( pClientIdentifierBuffer, - CLIENT_IDENTIFIER_MAX_LENGTH, - CLIENT_IDENTIFIER_PREFIX "%lu", - ( long unsigned int ) IotClock_GetTimeMs() ); - - /* Check for errors from snprintf. */ - if( status < 0 ) - { - IotLogError( "Failed to generate unique client identifier for demo." ); - status = EXIT_FAILURE; - } - else - { - /* Set the client identifier buffer and length. */ - connectInfo.pClientIdentifier = pClientIdentifierBuffer; - connectInfo.clientIdentifierLength = ( uint16_t ) status; - - status = EXIT_SUCCESS; - } - } - - /* Establish the MQTT connection. */ - if( status == EXIT_SUCCESS ) - { - IotLogInfo( "MQTT demo client identifier is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); - - connectStatus = IotMqtt_Connect( &networkInfo, - &connectInfo, - MQTT_TIMEOUT_MS, - pMqttConnection ); - - if( connectStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT CONNECT returned error %s.", - IotMqtt_strerror( connectStatus ) ); - - status = EXIT_FAILURE; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Add or remove subscriptions by either subscribing or unsubscribing. - * - * @param[in] mqttConnection The MQTT connection to use for subscriptions. - * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. - * @param[in] pTopicFilters Array of topic filters for subscriptions. - * @param[in] pCallbackParameter The parameter to pass to the subscription - * callback. - * - * @return `EXIT_SUCCESS` if the subscription operation succeeded; `EXIT_FAILURE` - * otherwise. - */ -static int _modifySubscriptions( IotMqttConnection_t mqttConnection, - IotMqttOperationType_t operation, - const char ** pTopicFilters, - void * pCallbackParameter ) -{ - int status = EXIT_SUCCESS; - int32_t i = 0; - IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t pSubscriptions[ TOPIC_FILTER_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* Set the members of the subscription list. */ - for( i = 0; i < TOPIC_FILTER_COUNT; i++ ) - { - pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; - pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; - pSubscriptions[ i ].topicFilterLength = TOPIC_FILTER_LENGTH; - pSubscriptions[ i ].callback.pCallbackContext = pCallbackParameter; - pSubscriptions[ i ].callback.function = _mqttSubscriptionCallback; - } - - /* Modify subscriptions by either subscribing or unsubscribing. */ - if( operation == IOT_MQTT_SUBSCRIBE ) - { - subscriptionStatus = IotMqtt_SubscribeSync( mqttConnection, - pSubscriptions, - TOPIC_FILTER_COUNT, - 0, - MQTT_TIMEOUT_MS ); - - /* Check the status of SUBSCRIBE. */ - switch( subscriptionStatus ) - { - case IOT_MQTT_SUCCESS: - IotLogInfo( "All demo topic filter subscriptions accepted." ); - break; - - case IOT_MQTT_SERVER_REFUSED: - - /* Check which subscriptions were rejected before exiting the demo. */ - for( i = 0; i < TOPIC_FILTER_COUNT; i++ ) - { - if( IotMqtt_IsSubscribed( mqttConnection, - pSubscriptions[ i ].pTopicFilter, - pSubscriptions[ i ].topicFilterLength, - NULL ) == true ) - { - IotLogInfo( "Topic filter %.*s was accepted.", - pSubscriptions[ i ].topicFilterLength, - pSubscriptions[ i ].pTopicFilter ); - } - else - { - IotLogError( "Topic filter %.*s was rejected.", - pSubscriptions[ i ].topicFilterLength, - pSubscriptions[ i ].pTopicFilter ); - } - } - - status = EXIT_FAILURE; - break; - - default: - - status = EXIT_FAILURE; - break; - } - } - else if( operation == IOT_MQTT_UNSUBSCRIBE ) - { - subscriptionStatus = IotMqtt_UnsubscribeSync( mqttConnection, - pSubscriptions, - TOPIC_FILTER_COUNT, - 0, - MQTT_TIMEOUT_MS ); - - /* Check the status of UNSUBSCRIBE. */ - if( subscriptionStatus != IOT_MQTT_SUCCESS ) - { - status = EXIT_FAILURE; - } - } - else - { - /* Only SUBSCRIBE and UNSUBSCRIBE are valid for modifying subscriptions. */ - IotLogError( "MQTT operation %s is not valid for modifying subscriptions.", - IotMqtt_OperationType( operation ) ); - - status = EXIT_FAILURE; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Transmit all messages and wait for them to be received on topic filters. - * - * @param[in] mqttConnection The MQTT connection to use for publishing. - * @param[in] pTopicNames Array of topic names for publishing. These were previously - * subscribed to as topic filters. - * @param[in] pPublishReceivedCounter Counts the number of messages received on - * topic filters. - * - * @return `EXIT_SUCCESS` if all messages are published and received; `EXIT_FAILURE` - * otherwise. - */ -static int _publishAllMessages( IotMqttConnection_t mqttConnection, - const char ** pTopicNames, - IotSemaphore_t * pPublishReceivedCounter ) -{ - int status = EXIT_SUCCESS; - intptr_t publishCount = 0, i = 0; - IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttCallbackInfo_t publishComplete = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - char pPublishPayload[ PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; - - /* The MQTT library should invoke this callback when a PUBLISH message - * is successfully transmitted. */ - publishComplete.function = _operationCompleteCallback; - - /* Set the common members of the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.topicNameLength = TOPIC_FILTER_LENGTH; - publishInfo.pPayload = pPublishPayload; - publishInfo.retryMs = PUBLISH_RETRY_MS; - publishInfo.retryLimit = PUBLISH_RETRY_LIMIT; - - /* Loop to PUBLISH all messages of this demo. */ - for( publishCount = 0; - publishCount < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE * IOT_DEMO_MQTT_PUBLISH_BURST_COUNT; - publishCount++ ) - { - /* Announce which burst of messages is being published. */ - if( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) - { - IotLogInfo( "Publishing messages %d to %d.", - publishCount, - publishCount + IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ); - } - - /* Pass the PUBLISH number to the operation complete callback. */ - publishComplete.pCallbackContext = ( void * ) publishCount; - - /* Choose a topic name (round-robin through the array of topic names). */ - publishInfo.pTopicName = pTopicNames[ publishCount % TOPIC_FILTER_COUNT ]; - - /* Generate the payload for the PUBLISH. */ - status = snprintf( pPublishPayload, - PUBLISH_PAYLOAD_BUFFER_LENGTH, - PUBLISH_PAYLOAD_FORMAT, - ( int ) publishCount ); - - /* Check for errors from snprintf. */ - if( status < 0 ) - { - IotLogError( "Failed to generate MQTT PUBLISH payload for PUBLISH %d.", - ( int ) publishCount ); - status = EXIT_FAILURE; - - break; - } - else - { - publishInfo.payloadLength = ( size_t ) status; - status = EXIT_SUCCESS; - } - - /* PUBLISH a message. This is an asynchronous function that notifies of - * completion through a callback. */ - publishStatus = IotMqtt_PublishAsync( mqttConnection, - &publishInfo, - 0, - &publishComplete, - NULL ); - - if( publishStatus != IOT_MQTT_STATUS_PENDING ) - { - IotLogError( "MQTT PUBLISH %d returned error %s.", - ( int ) publishCount, - IotMqtt_strerror( publishStatus ) ); - status = EXIT_FAILURE; - - break; - } - - /* If a complete burst of messages has been published, wait for an equal - * number of messages to be received. Note that messages may be received - * out-of-order, especially if a message was lost and had to be retried. */ - if( ( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == - ( IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ) ) - { - IotLogInfo( "Waiting for %d publishes to be received.", - IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); - - for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) - { - if( IotSemaphore_TimedWait( pPublishReceivedCounter, - MQTT_TIMEOUT_MS ) == false ) - { - IotLogError( "Timed out waiting for incoming PUBLISH messages." ); - status = EXIT_FAILURE; - break; - } - } - - IotLogInfo( "%d publishes received.", - i ); - } - - /* Stop publishing if there was an error. */ - if( status == EXIT_FAILURE ) - { - break; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief The function that runs the MQTT demo, called by the demo runner. - * - * @param[in] awsIotMqttMode Specify if this demo is running with the AWS IoT - * MQTT server. Set this to `false` if using another MQTT server. - * @param[in] pIdentifier NULL-terminated MQTT client identifier. - * @param[in] pNetworkServerInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when - * establishing the MQTT connection. - * @param[in] pNetworkInterface The network interface to use for this demo. - * - * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. - */ -int RunMqttDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ) -{ - /* Return value of this function and the exit status of this program. */ - int status = EXIT_SUCCESS; - - /* Handle of the MQTT connection used in this demo. */ - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - /* Counts the number of incoming PUBLISHES received (and allows the demo - * application to wait on incoming PUBLISH messages). */ - IotSemaphore_t publishesReceived; - - /* Topics used as both topic filters and topic names in this demo. */ - const char * pTopics[ TOPIC_FILTER_COUNT ] = - { - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/3", - IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/4", - }; - - /* Flags for tracking which cleanup functions must be called. */ - bool librariesInitialized = false, connectionEstablished = false; - - /* Initialize the libraries required for this demo. */ - status = _initializeDemo(); - - if( status == EXIT_SUCCESS ) - { - /* Mark the libraries as initialized. */ - librariesInitialized = true; - - /* Establish a new MQTT connection. */ - status = _establishMqttConnection( awsIotMqttMode, - pIdentifier, - pNetworkServerInfo, - pNetworkCredentialInfo, - pNetworkInterface, - &mqttConnection ); - } - - if( status == EXIT_SUCCESS ) - { - /* Mark the MQTT connection as established. */ - connectionEstablished = true; - - /* Add the topic filter subscriptions used in this demo. */ - status = _modifySubscriptions( mqttConnection, - IOT_MQTT_SUBSCRIBE, - pTopics, - &publishesReceived ); - } - - if( status == EXIT_SUCCESS ) - { - /* Create the semaphore to count incoming PUBLISH messages. */ - if( IotSemaphore_Create( &publishesReceived, - 0, - IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == true ) - { - /* PUBLISH (and wait) for all messages. */ - status = _publishAllMessages( mqttConnection, - pTopics, - &publishesReceived ); - - /* Destroy the incoming PUBLISH counter. */ - IotSemaphore_Destroy( &publishesReceived ); - } - else - { - /* Failed to create incoming PUBLISH counter. */ - status = EXIT_FAILURE; - } - } - - if( status == EXIT_SUCCESS ) - { - /* Remove the topic subscription filters used in this demo. */ - status = _modifySubscriptions( mqttConnection, - IOT_MQTT_UNSUBSCRIBE, - pTopics, - NULL ); - } - - /* Disconnect the MQTT connection if it was established. */ - if( connectionEstablished == true ) - { - IotMqtt_Disconnect( mqttConnection, 0 ); - } - - /* Clean up libraries if they were initialized. */ - if( librariesInitialized == true ) - { - _cleanupDemo(); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/doc/config/common b/doc/config/common deleted file mode 100644 index 08dd8d5c01..0000000000 --- a/doc/config/common +++ /dev/null @@ -1,117 +0,0 @@ -# SDK version. -PROJECT_NUMBER = "4.0.0b1" - -# Doxygen layout file for libraries. -LAYOUT_FILE = doc/config/layout_library.xml - -# Documentation output directory. -OUTPUT_DIRECTORY = doc/output/ - -# Don't generate LaTeX documentation -GENERATE_LATEX = NO - -# Directories containing images. -IMAGE_PATH = doc/plantuml/images - -# Don't rearrange members in the input files. -SORT_MEMBER_DOCS = NO - -# Silence output (warnings only). -QUIET = YES - -# Define the following preprocessor constants when generating documentation. -PREDEFINED = "DOXYGEN=1" \ - "IOT_STATIC_MEMORY_ONLY=1" \ - "LIBRARY_LOG_LEVEL=IOT_LOG_DEBUG" \ - "LIBRARY_LOG_NAME=\"DOXYGEN\"" - -# Ignore the constants used for setting log levels and names. -EXCLUDE_SYMBOLS = "LIBRARY_LOG_*" - -# Configure Doxygen for C. -OPTIMIZE_OUTPUT_FOR_C = YES -TYPEDEF_HIDES_STRUCT = YES -EXTRACT_STATIC = YES - -# Disable the tab bar and use treeview instead. -DISABLE_INDEX = YES -GENERATE_TREEVIEW = YES - -# All files should have unique names, so showing the full path is unnecessary. -FULL_PATH_NAMES = NO - -# Disable the default Doxygen diagrams. -HAVE_DOT = NO - -# Disable the default Doxygen search engine (for now). -SEARCHENGINE = NO - -# Use custom header file, footer file, and stylesheet. -HTML_HEADER = doc/config/html/header.html -HTML_FOOTER = doc/config/html/footer.html -HTML_EXTRA_STYLESHEET = doc/config/html/style.css - -# Don't show external pages or groups. -EXTERNAL_GROUPS = NO -EXTERNAL_PAGES = NO - -# Enable expansion of Unity test macros. -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = YES -EXPAND_ONLY_PREDEF = YES - -# Include path for expanding Unity test macros. -INCLUDE_PATH = third_party/unity/unity/fixture - -# Expand the TEST macro, but ignore the _run functions generated from macro expansion. -EXPAND_AS_DEFINED = TEST -EXCLUDE_SYMBOLS += "TEST_*_run" - -# Alias for starting a dependencies section. -ALIASES += dependencies{2}="@section \1_dependencies Dependencies^^@brief Dependencies of the \2.^^^^" - -# Alias for starting a configuration settings page. -ALIASES += describeconfig="Configuration settings are C pre-processor constants. They can be set with a @c #`define` in the config file (`iot_config.h`) or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." -ALIASES += configpage{2}="@page \1_config Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^@par configpagemarker" -ALIASES += configpage{4}="@page \1_config \3 Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^^^The settings on this page only affect the [\2](@ref \1). In addition to the settings on this page, them \2 will also be affected by [settings that affect all \4](@ref global_\4_config).^^@par configpagemarker" -ALIASES += globalconfigpage{3}="@page global_\1_config Global \2 Configuration^^^^@describeconfig^^@brief Configuration settings that affect all \3.^^@par configpagemarker" - -# Aliases for "Possible values", "Recommended values", and "Default values" -# used in configuration setting pages. -ALIASES += configpossible="Possible values: " -ALIASES += configrecommended="Recommended values: " -ALIASES += configdefault="Default value (if undefined): " - -# Alias for starting a constants page. -ALIASES += constantspage{2}="@page \1_constants Constants^^@brief Defined constants of the \2.^^^^Libraries may @c #`define` constants in their headers with special meanings. This page describes the meanings and uses of any constants defined by the \2. Related constants are shown in a single section on this page.^^" - -# Alias for starting a functions page. -ALIASES += functionspage{2}="@page \1_functions Functions^^@brief Functions of the \2.^^^^The \2 consists of the following functions." -ALIASES += functionspage{3}="@page \1_functions \3^^@brief Functions of the \2.^^^^The \2 consists of the following functions." - -# Alias for listing a single function on a functions page. -ALIASES += functionname{1}="@subpage \1
^^ @copybrief \1^^" - -# Alias for creating a page for a single function. -ALIASES += functionpage{3}="@page \2_function_\3 \1^^^^@snippet this declare_\2_\3^^@copydoc \1" - -# Alias for starting a handles group. -ALIASES += handles{2}="@defgroup \1_datatypes_handles Handles^^@brief Opaque handles of the \2." - -# Alias for starting an enum group. -ALIASES += enums{2}="@defgroup \1_datatypes_enums Enumerated types^^@brief Enumerated types of the \2." - -# Alias for starting a function pointers group. -ALIASES += functionpointers{2}="@defgroup \1_datatypes_functionpointers Function pointers types^^@brief Function pointers types of the \2." - -# Alias for starting a structs group. -ALIASES += structs{2}="@defgroup \1_datatypes_structs Structured types^^@brief Structured types of the \2." - -# Alias for starting a parameter structures group. -ALIASES += paramstructs{2}="@defgroup \1_datatypes_paramstructs Parameter structures^^@brief Structures passed as parameters to [\2 functions](@ref \1_functions)^^^^These structures are passed as parameters to library functions. Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member." - -# Alias for "Parameter for". -ALIASES += paramfor="Parameter for: " - -# Alias for parameter structure initializers. -ALIASES += initializer{2}="All instances of #\1 should be initialized with #\2.^^" diff --git a/doc/config/defender b/doc/config/defender deleted file mode 100644 index b88a0ea76f..0000000000 --- a/doc/config/defender +++ /dev/null @@ -1,33 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Defender" -PROJECT_BRIEF = "AWS IoT Device Defender library" - -# Library documentation output directory. -HTML_OUTPUT = defender - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/defender.tag - -# Directories containing library source code. -INPUT = doc/lib/ \ - libraries/aws/defender/include \ - libraries/aws/defender/src \ - libraries/aws/defender/src/private \ - libraries/aws/defender/test \ - libraries/aws/defender/test/system - -# Library file names. -FILE_PATTERNS = *defender*.h *defender*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/mqtt.tag=../mqtt \ - doc/tag/logging.tag=../logging \ - doc/tag/static_memory.tag=../static_memory \ - doc/tag/taskpool.tag=../taskpool \ - doc/tag/platform.tag=../platform \ - doc/tag/linear_containers.tag=../linear_containers diff --git a/doc/config/html/footer.html b/doc/config/html/footer.html deleted file mode 100644 index 18c84eb786..0000000000 --- a/doc/config/html/footer.html +++ /dev/null @@ -1,35 +0,0 @@ - - - -
- - - - - - - - - diff --git a/doc/config/html/header.html b/doc/config/html/header.html deleted file mode 100644 index a60bb68d3d..0000000000 --- a/doc/config/html/header.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - $projectname: $title - - - - - $title - - - - - - $search - $mathjax - - $extrastylesheet - - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- AWS IoT Device SDK C: - $projectname -
- -
- $projectbrief -
- -
-
- $projectbrief -
-
- $searchbox -
- Return to main page ↑ -
-
- -$treeview - - - diff --git a/doc/config/html/style.css b/doc/config/html/style.css deleted file mode 100644 index 928a523942..0000000000 --- a/doc/config/html/style.css +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Stylesheet for Doxygen HTML output. - * - * This file defines styles for custom elements in the header/footer and - * overrides some of the default Doxygen styles. - * - * Styles in this file do not affect the treeview sidebar. - */ - -/* Set the margins to place a small amount of whitespace on the left and right - * side of the page. */ -div.contents { - margin-left:4em; - margin-right:4em; -} - -/* Justify text in paragraphs. */ -p { - text-align: justify; -} - -/* Style of section headings. */ -h1 { - border-bottom: 1px solid #879ECB; - color: #354C7B; - font-size: 160%; - font-weight: normal; - padding-bottom: 4px; - padding-top: 8px; -} - -/* Style of subsection headings. */ -h2:not(.memtitle):not(.groupheader) { - font-size: 125%; - margin-bottom: 0px; - margin-top: 16px; - padding: 0px; -} - -/* Style of paragraphs immediately after subsection headings. */ -h2 + p { - margin: 0px; - padding: 0px; -} - -/* Style of subsubsection headings. */ -h3 { - font-size: 100%; - margin-bottom: 0px; - margin-left: 2em; - margin-right: 2em; -} - -/* Style of paragraphs immediately after subsubsection headings. */ -h3 + p { - margin-top: 0px; - margin-left: 2em; - margin-right: 2em; -} - -/* Style of the prefix "AWS IoT Device SDK C" that appears in the header. */ -#csdkprefix { - color: #757575; -} - -/* Style of the "Return to main page" link that appears in the header. */ -#returntomain { - padding: 0.5em; -} - -/* Style of the dividers on Configuration Settings pages. */ -div.configpagedivider { - margin-left: 0px !important; - margin-right: 0px !important; - margin-top: 20px !important; -} - -/* Style of configuration setting names. */ -dl.section.user ~ h1 { - border-bottom: none; - color: #000000; - font-family: monospace, fixed; - font-size: 16px; - margin-bottom: 0px; - margin-left: 2em; - margin-top: 1.5em; -} - -/* Style of paragraphs on a configuration settings page. */ -dl.section.user ~ * { - margin-bottom: 10px; - margin-left: 4em; - margin-right: 4em; - margin-top: 0px; -} - -/* Hide the configuration setting marker. */ -dl.section.user { - display: none; -} - -/* Overrides for code fragments and lines. */ -div.fragment { - background: #ffffff; - border: none; - padding: 5px; -} - -div.line { - color: #3a3a3a; -} - -/* Overrides for code syntax highlighting colors. */ -span.comment { - color: #008000; -} - -span.keyword, span.keywordtype, span.keywordflow { - color: #0000ff; -} - -span.preprocessor { - color: #50015a; -} - -span.stringliteral, span.charliteral { - color: #800c0c; -} - -a.code, a.code:visited, a.line, a.line:visited { - color: #496194; -} diff --git a/doc/config/jobs b/doc/config/jobs deleted file mode 100644 index c86bbb0e56..0000000000 --- a/doc/config/jobs +++ /dev/null @@ -1,33 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Jobs" -PROJECT_BRIEF = "AWS IoT Jobs library" - -# Library documentation output directory. -HTML_OUTPUT = jobs - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/jobs.tag - -# Directories containing library source code. -INPUT = doc/lib/ \ - libraries/aws/jobs/include \ - libraries/aws/jobs/include/types \ - libraries/aws/jobs/src \ - libraries/aws/jobs/src/private \ - libraries/aws/jobs/test \ - demos/src - -# Library file names. -FILE_PATTERNS = *jobs*.h *jobs*.c *jobs*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/mqtt.tag=../mqtt \ - doc/tag/logging.tag=../logging \ - doc/tag/linear_containers.tag=../linear_containers \ - doc/tag/static_memory.tag=../static_memory \ - doc/tag/platform.tag=../platform diff --git a/doc/config/layout_library.xml b/doc/config/layout_library.xml deleted file mode 100644 index 7aebbfee9b..0000000000 --- a/doc/config/layout_library.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/config/layout_main.xml b/doc/config/layout_main.xml deleted file mode 100644 index e2014d4de6..0000000000 --- a/doc/config/layout_main.xml +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/config/linear_containers b/doc/config/linear_containers deleted file mode 100644 index 69de8f6f03..0000000000 --- a/doc/config/linear_containers +++ /dev/null @@ -1,23 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Linear Containers" -PROJECT_BRIEF = "Linked lists and Queues" - -# Library documentation output directory. -HTML_OUTPUT = linear_containers - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/linear_containers.tag - -# Directories containing library source code. -INPUT = doc/lib \ - libraries/standard/common/include/ - -# Library file names. -FILE_PATTERNS = *linear_containers*.h *linear_containers*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main diff --git a/doc/config/logging b/doc/config/logging deleted file mode 100644 index dc8fa54fe7..0000000000 --- a/doc/config/logging +++ /dev/null @@ -1,26 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Logging" -PROJECT_BRIEF = "Generate and print log messages" - -# Library documentation output directory. -HTML_OUTPUT = logging - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/logging.tag - -# Directories containing library source code. -INPUT = doc/lib \ - libraries/standard/common/include \ - libraries/standard/common/src - -# Library file names. -FILE_PATTERNS = *logging*.c *logging*.h *logging*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/static_memory.tag=../static_memory \ - doc/tag/platform.tag=../platform diff --git a/doc/config/main b/doc/config/main deleted file mode 100644 index 32b1919719..0000000000 --- a/doc/config/main +++ /dev/null @@ -1,41 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Main" - -# Library documentation output directory. -HTML_OUTPUT = main - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/main.tag - -# Input directories. -INPUT = doc/ \ - doc/guide \ - doc/guide/developer \ - doc/guide/migration - -# Path of CMake GUI image. -IMAGE_PATH += doc/guide - -# Library file names. -FILE_PATTERNS = *.txt - -# Don't automatically link to library symbols. -AUTOLINK_SUPPORT = NO - -# External tag files required by this library. -TAGFILES = doc/tag/logging.tag=../logging \ - doc/tag/platform.tag=../platform \ - doc/tag/mqtt.tag=../mqtt \ - doc/tag/shadow.tag=../shadow \ - doc/tag/jobs.tag=../jobs - -# Use the Main page layout file -LAYOUT_FILE = doc/config/layout_main.xml - -# Aliases for tables on the Style Guide page. -ALIASES += formattable{1}="This table contains the formats for the names of the most common SDK \1. It is not intended to be comprehensive. Bold text indicates a variable but required part of the \1 name. Italic text indicates a variable but optional part of the \1 name.^^^^| Format | Applies to | Example |^^| ------ | ---------- | ------- |" -ALIASES += formattableentry{3}="| \1 | \2 | \3 |" diff --git a/doc/config/mqtt b/doc/config/mqtt deleted file mode 100644 index 504700b415..0000000000 --- a/doc/config/mqtt +++ /dev/null @@ -1,39 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "MQTT" -PROJECT_BRIEF = "MQTT 3.1.1 client library" - -# Library documentation output directory. -HTML_OUTPUT = mqtt - -# Generate documentation for MQTT packet serializer overrides. -PREDEFINED += "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES=1" - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/mqtt.tag - -# Directories containing library source code. -INPUT = doc/lib \ - libraries/standard/mqtt/include \ - libraries/standard/mqtt/include/types \ - libraries/standard/mqtt/src \ - libraries/standard/mqtt/src/private \ - libraries/standard/mqtt/test \ - libraries/standard/mqtt/test/access \ - libraries/standard/mqtt/test/system \ - libraries/standard/mqtt/test/unit \ - demos/src - -# Library file names. -FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/logging.tag=../logging \ - doc/tag/static_memory.tag=../static_memory \ - doc/tag/platform.tag=../platform \ - doc/tag/linear_containers.tag=../linear_containers \ - doc/tag/taskpool.tag=../taskpool diff --git a/doc/config/platform b/doc/config/platform deleted file mode 100644 index a73537652c..0000000000 --- a/doc/config/platform +++ /dev/null @@ -1,35 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Platform" -PROJECT_BRIEF = "Platform portability layer" - -# Library documentation output directory. -HTML_OUTPUT = platform - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/platform.tag - -# Directories containing library source code. -INPUT = doc/lib \ - libraries/platform \ - libraries/platform/types \ - ports/common/include/atomic - -# Library file names. -FILE_PATTERNS = platform.txt \ - iot_clock.h \ - iot_threads.h \ - iot_network.h \ - iot_metrics.h \ - iot_platform_types.h \ - iot_atomic_generic.h - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/logging.tag=../logging \ - doc/tag/mqtt.tag=../mqtt \ - doc/tag/shadow.tag=../shadow \ - doc/tag/defender.tag=../defender diff --git a/doc/config/shadow b/doc/config/shadow deleted file mode 100644 index 8ceff85479..0000000000 --- a/doc/config/shadow +++ /dev/null @@ -1,35 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Shadow" -PROJECT_BRIEF = "AWS IoT Device Shadow library" - -# Library documentation output directory. -HTML_OUTPUT = shadow - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/shadow.tag - -# Directories containing library source code. -INPUT = doc/lib/ \ - libraries/aws/shadow/include \ - libraries/aws/shadow/include/types \ - libraries/aws/shadow/src \ - libraries/aws/shadow/src/private \ - libraries/aws/shadow/test \ - libraries/aws/shadow/test/system \ - libraries/aws/shadow/test/unit \ - demos/src - -# Library file names. -FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/mqtt.tag=../mqtt \ - doc/tag/logging.tag=../logging \ - doc/tag/static_memory.tag=../static_memory \ - doc/tag/platform.tag=../platform \ - doc/tag/linear_containers.tag=../linear_containers diff --git a/doc/config/static_memory b/doc/config/static_memory deleted file mode 100644 index 6e666e1024..0000000000 --- a/doc/config/static_memory +++ /dev/null @@ -1,25 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Static Memory" -PROJECT_BRIEF = "Statically-allocated buffer pools" - -# Library documentation output directory. -HTML_OUTPUT = static_memory - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/static_memory.tag - -# Directories containing library source code. -INPUT = doc/lib \ - libraries/standard/common/include \ - libraries/standard/common/src - -# Library file names. -FILE_PATTERNS = *static_memory*.h *static_memory_common*.c *static_memory*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/platform.tag=../platform diff --git a/doc/config/taskpool b/doc/config/taskpool deleted file mode 100644 index 36ec9b4071..0000000000 --- a/doc/config/taskpool +++ /dev/null @@ -1,33 +0,0 @@ -# Include common configuration options. -@INCLUDE_PATH = doc/config -@INCLUDE = common - -# Basic project information. -PROJECT_NAME = "Task Pool" -PROJECT_BRIEF = "Task pool library" - -# Library documentation output directory. -HTML_OUTPUT = taskpool - -# Generate Doxygen tag file for this library. -GENERATE_TAGFILE = doc/tag/taskpool.tag - -# Directories containing library source code. -INPUT = doc \ - doc/lib \ - libraries/standard/common/include \ - libraries/standard/common/include/types \ - libraries/standard/common/src \ - libraries/standard/common/src/private \ - libraries/standard/common/test \ - libraries/standard/common/test/unit - -# Library file names. -FILE_PATTERNS = *taskpool*.c *taskpool*.h *taskpool*.txt - -# External tag files required by this library. -TAGFILES = doc/tag/main.tag=../main \ - doc/tag/linear_containers.tag=../linear_containers \ - doc/tag/logging.tag=../logging \ - doc/tag/platform.tag=../platform \ - doc/tag/static_memory.tag=../static_memory \ diff --git a/doc/guide/building.txt b/doc/guide/building.txt deleted file mode 100644 index 7e2e8ea1aa..0000000000 --- a/doc/guide/building.txt +++ /dev/null @@ -1,153 +0,0 @@ -/** -@page building Building the SDK -@brief Guide for building the SDK. - -This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. - -@pre -- CMake version 3.5.0 or later and a C99 compiler. - -@pre -- A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. For reference, the version used by this repo's Travis CI builds are listed in parentheses. - - Linux system with POSIX thread and timer APIs. (CI tests on Ubuntu 16.04).
- On Linux systems, the OpenSSL network implementation may be used instead of the default network implementation based on mbed TLS. This requires the installation of OpenSSL development libraries and header files, version 1.0.2g or later. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. - -This SDK uses third-party libraries as Git submodules in the `third_party` directory. If the source code was downloaded via `git clone`, nothing further needs to be done. The CMake build system can automatically clone submodules in this case. However, for any other download, the submodules must be downloaded and placed in their respective `third_party` directory. -- [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17) → `third_party/mbedtls/mbedtls` -- [tinyCBOR](https://github.com/intel/tinycbor) → `third_party/tinycbor/tinycbor` - -In addition, credentials and Things for AWS IoT must be provisioned before running the demos. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html) for a tutorial on setting up AWS IoT. - -@section building_configuration Build system configuration -@brief Configuration of the CMake build system. - -The following options may be passed to the CMake build system to modify the build. Options are passed with `-D` on the command line, or selected through [CMake GUI](https://cmake.org/cmake/help/v3.0/manual/cmake-gui.1.html). If an option is not set, the default value will be used. -- [BUILD_SHARED_LIBS](https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html)
- Default: `ON`
- Set this to `ON` to build all libraries as shared libraries. When `OFF`, libraries build as static libraries. -- `IOT_ATOMIC_USE_PORT`
- Default: `OFF`
- Set this to `ON` to use a custom atomic port. When `OFF`, the build system will choose an atomic port. This option also sets the preprocessor setting @ref IOT_ATOMIC_USE_PORT. -- `IOT_BUILD_CLONE_SUBMODULES`
- Default: `OFF`
- Set this to `ON` to automatically clone any required Git submodules. When `OFF`, submodules must be manually cloned. -- `IOT_BUILD_TESTS`
- Default: `OFF`
- Set this to `ON` to build both demo and test executables. When `OFF`, only demo executables are built. If enabled, both demos and tests will use the settings defined in `tests/iot_config.h`. -- `IOT_NETWORK_USE_OPENSSL` [Linux only]
- Default: `OFF`
- Set this to `ON` to use a network implementation based on OpenSSL instead of the default mbed TLS network implementation. If this is set to `ON`, OpenSSL development libraries and headers must be installed. - -@section building_demo Building the demo applications -@brief How to build the demo applications. - -Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/iot_config.h`. Any undefined settings will use a default value when possible. - -@subsection building_demo_commmandline Command-line build -@brief How to build the demo applications on the command-line. - -In-source CMake builds are not allowed. A build directory should be created under the SDK root directory. Since CMake is cross-platform, build steps will vary depending on host OS. On Linux, the demo applications are built as follows: -@code{sh} -# Create build directory and change to build directory. -mkdir build -cd build - -# Configure build using CMake. -cmake .. - -# Build the demo applications. -make -@endcode - -Demo application executables will be placed in a `output/bin` directory of the CMake build directory, i.e. `build/output/bin` in the example above. The executables will be named `aws_iot_demo_library` or `iot_demo_library`, depending on whether the demo is specific to AWS IoT. For example, the MQTT demo application, which may be used with any MQTT server, will be named `iot_demo_mqtt`. The Thing Shadow demo, which is specific to AWS IoT, will be named `aws_iot_demo_shadow`. - -Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/iot_demo_config.h` is recommended over setting them using CMake. -@code{sh} -# Example: Set IOT_STATIC_MEMORY_ONLY using CMake -cmake .. -DCMAKE_C_FLAGS=-DIOT_STATIC_MEMORY_ONLY=1 -@endcode - -By default, CMake may build in `Release` configuration. To enable debugging symbols, set `CMAKE_BUILD_TYPE` to `Debug`. -@code{sh} -cmake .. -DCMAKE_BUILD_TYPE=Debug -@endcode - -Delete the build directory to remove all demo application executables and build files. - -@subsection building_demo_gui CMake GUI build -@brief How to build the demo applications with CMake GUI. - -Alternatively, CMake GUI may be used instead of the command line. Specify the SDK's root directory in Where is the source code and a build directory in Where to build the binaries. Click Configure, then Generate. You may modify @ref building_configuration in the CMake GUI window. - -@section demo_commandlineoptions Demo command line options -@brief Command line options of the demo applications. - -Demo applications accept the following command line options. - -@subsection demo_optionn -n -@brief Disable AWS IoT MQTT mode. - -By default, the demo applications expect to run with an AWS IoT MQTT server. Passing this option disables [AWS IoT MQTT mode](@ref IotMqttConnectInfo_t.awsIotMqttMode) and treats the remote MQTT server as a fully-compliant MQTT server. This option should not have an argument. - -Because Thing Shadows are specific to AWS IoT, this option is ignored by the [Shadow demo](@ref shadow_demo). - -@subsection demo_optionsu -s and -u -@brief Secured or unsecured demo connection, respectively. - -Neither `-s` nor `-u` should have an argument, and only one of the two should be used at a time. - -See @ref IOT_DEMO_SECURED_CONNECTION for the compile-time default setting of this option. - -Because Thing Shadows are specific to AWS IoT (which requires secured connections), this option is ignored by the [Shadow demo](@ref shadow_demo). - -@subsection demo_optionh -h host -@brief Remote host for demo application. - -Must be followed by a host name. - -See @ref IOT_DEMO_SERVER for the compile-time default setting of this option. - -@subsection demo_optionp -p port -@brief Remote port for demo application. - -Must be followed by a port in the range of `[1,65535]`. - -See @ref IOT_DEMO_PORT for the compile-time default setting of this option. - -@subsection demo_optionr -r rootCA, -c clientCert, -k privateKey -@brief Paths to trusted server root certificate, client certificate, and client certificate private key, respectively. - -Must be followed by a filesystem path to the respective PEM-encoded credential. - -See @ref IOT_DEMO_ROOT_CA, @ref IOT_DEMO_CLIENT_CERT, and @ref IOT_DEMO_PRIVATE_KEY for the compile-time default settings of these options. - -@subsection demo_optioni -i identifier -@brief MQTT client identifier OR Thing Name. - -Must be followed by a string representing either an MQTT client identifier (MQTT demo only) or a Thing Name (all other demos). Because AWS IoT recommends that MQTT client identifier and Thing Name match, demos will use the Thing Name as the MQTT client identifier when possible. - -See @ref IOT_DEMO_IDENTIFIER for the compile-time default settings of this option. - -@section building_tests Building and running the tests -@brief How to build and run the SDK tests. - -Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_config.h`. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. Note that enabling this option means that both demos and tests will use the settings defined in `tests/iot_config.h`. If distinct configurations for demos and tests are desired, they can be built in separate build directories. - -@code{sh} -# Create build directory and change to build directory. -mkdir build -cd build - -# Configure build using CMake. -cmake .. -DIOT_BUILD_TESTS=1 - -# Build both the demos and tests. -make -@endcode - -The `IOT_BUILD_TESTS` option can also be set by a checkbox in CMake GUI. - -Test executables will be placed in a `output/bin` directory of the CMake build directory (i.e. `build/output/bin`) alongside the demo applications. They will be named `aws_iot_tests_library` or `iot_tests_library`, depending on whether the tests are specific to AWS IoT. For example, the MQTT tests application, which may be used with any MQTT server, will be named `iot_tests_mqtt`. The Thing Shadow tests, which are specific to AWS IoT, will be named `aws_iot_tests_shadow`. - -By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. The command line option `-n` may be passed to the test executable to disable tests that require network connectivity. -*/ diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt deleted file mode 100644 index 4ede09d1be..0000000000 --- a/doc/guide/developer/automated_tests.txt +++ /dev/null @@ -1,166 +0,0 @@ -/** -@page guide_developer_automated_tests Automated Tests -@brief Guide to this SDK's automated testing setup. - -The SDK's GitHub repo relies on the following tools to provide automated testing of commits and pull requests. -- [Travis CI](https://travis-ci.org/) is the primary service used for testing.
- See: [aws-iot-device-sdk-embedded-C on Travis CI](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) -- [Codecov](https://codecov.io/) provides code coverage results.
- See: [aws-iot-device-sdk-embedded-C on Codecov](https://codecov.io/gh/aws/aws-iot-device-sdk-embedded-C) - -The following files on the GitHub repo control the automated tests: -- `.travis.yml`
- Provides the job matrix to run on Travis CI. -- `scripts/` directory
- Shell scripts (`sh`) that run on Travis CI servers. - - The scripts that test the libraries are named after the library they test. For example, `ci_test_mqtt.sh` tests the MQTT library. - - `ci_test_doc.sh` tests the documentation build. Runs for pull requests only. - - `ci_test_coverage.sh` generates and submits coverage results to Codecov. Runs for commits only. - -@section guide_developer_automated_tests_setup Setup -@brief How to set up the automated testing services. - -All of the automated testing services have web interfaces that can be connected to a GitHub account. - -@subsection guide_developer_automated_tests_setup_travis Travis CI -@brief How to set up Travis CI. - -Link to Travis CI: https://travis-ci.org/ - -1. Click Sign in with GitHub in the upper right corner to log in with your GitHub account. On your first login, Travis CI will prompt for GitHub permissions access. -2. Once signed in, hover over your GitHub account in the upper right corner. Click Settings in the menu that pops up. -3. On the Settings page, toggle the switch next to your fork of the C SDK to On. -4. Travis CI is now set up. See @ref guide_developer_automated_tests_credentials_policy_travis for adding commit credentials to Travis CI. - -@subsection guide_developer_automated_tests_setup_codecov Codecov -@brief How to set up Codecov. - -Link to Codecov: https://codecov.io/ - -1. Click Log In in the upper right corner. Then select Log in with GitHub to log in with your GitHub account. On your first login, Codecov will prompt for GitHub permissions access. -2. Navigate to codecov.io/gh/your GitHub username/aws-iot-device-sdk-embedded-C -3. Click on Settings, then copy the Repository Upload Token displayed. -4. Set the Travis CI environment variable `CODECOV_TOKEN` to the Repository Upload Token. Travis CI will automatically submit coverage data on commit check builds. - -@section guide_developer_automated_tests_commit_pr Commit vs. Pull Request -@brief The automated tests distinguish between GitHub commits and pull requests. Different sets of tests run on commits and pull requests. - -Because pull requests come from other repos and contain unknown code, Travis CI will not provide encrypted environment variables for pull requests. Therefore, pull requests are tested against unsecured servers. The following tests are run for pull requests. -- Common (`ci_test_common.sh`): this script tests common components like the task pool, atomics, etc. These tests do not use the network. -- MQTT (`ci_test_mqtt.sh`): the MQTT tests run on an unsecured connection against a locally-installed Mosquitto broker on the Travis CI VM. -- Shadow (`ci_test_shadow.sh`): only the Shadow unit tests are run for a pull request build. These tests do not use the network. -- Documentation (`ci_test_doc.sh`): checks that the documentation builds against the code in the pull request. - -For commits, the credentials necessary for connecting to AWS IoT are provided by Travis CI. The following tests are run for commits. -- Common (`ci_test_common.sh`): runs the same tests as the common pull request tests. -- MQTT (`ci_test_mqtt.sh`): runs the same MQTT tests as the MQTT pull request tests, but against the AWS IoT MQTT broker instead of a Mosquitto broker. -- Shadow (`ci_test_shadow.sh`): runs both the Shadow unit tests and the Shadow system tests. Requires connection to AWS IoT. -- Coverage (`ci_test_coverage.sh`): Gathers code coverage data and submits it to Codecov. - -@note Rarely, the Shadow tests on a GitHub fork will fail with a `429 TOO MANY REQUESTS` error. If this happens, restart the Shadow tests. The main C SDK GitHub repo has an increased limit for Shadow requests, so it should not encounter this error. - -Because different sets of tests are run for commits and pull requests, both commit and pull request checks should be used to validate code changes. The commit checks can be set up to run on a GitHub fork, where they will run whenever a new commit is pushed to the fork. When a pull request is made from the fork to the main repo, the pull request checks will run. - -@section guide_developer_automated_tests_credentials_policy AWS Credentials and Policy -@brief How to set up AWS IoT credentials for commit checks. - -Forking the main GitHub repo copies the Travis CI scripts, but does not copy any credentials, which are provided by Travis CI's [encrypted environment variable](https://docs.travis-ci.com/user/environment-variables/) feature. AWS IoT credentials are required to run the commit checks. No credentials are required to run pull request checks. - -@subsection guide_developer_automated_tests_credentials_policy_formatting Formatting the client certificate and private key -@brief The AWS IoT client certificate and private key must be formatted to be acceptable for use in a CI script. - -The credentials are written from Travis encrypted environment variables using the following command: -``` -echo -e $AWS_IOT_CLIENT_CERT > credentials/clientCert.pem -``` - -Therefore, all given credentials must be formatted for `echo -e`. As an example, consider the following certificate: -``` ------BEGIN CERTIFICATE----- -MIIB8TCCAZugAwIBAwIBADANBgkqhkiG9w0BAQQFADBfMRMwEQYDVQQDEwpuZXRh -cHAuY29tMQswCQYDVQQGEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNV -BAoTADEJMAcGA1UECxMAMQ8wDQYJKoZIhvcNAQkBFgAwHhcNMTAwNDI2MTk0OTI4 -WhcNMTAwNTI2MTk0OTI4WjBfMRMwEQYDVQQDEwpuZXRhcHAuY29tMQswCQYDVQQG -EwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNVBAoTADEJMAcGA1UECxMA -MQ8wDQYJKoZIhvcNAQkBFgAwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAyXrK2sry ------END CERTIFICATE----- -``` - -It should be formatted as follows for Travis CI: -``` ------BEGIN\ CERTIFICATE-----\\nMIIB8TCCAZugAwIBAwIBADANBgkqhkiG9w0BAQQFADBfMRMwEQYDVQQDEwpuZXRh\\ncHAuY29tMQswCQYDVQQGEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNV\\nBAoTADEJMAcGA1UECxMAMQ8wDQYJKoZIhvcNAQkBFgAwHhcNMTAwNDI2MTk0OTI4\\nWhcNMTAwNTI2MTk0OTI4WjBfMRMwEQYDVQQDEwpuZXRhcHAuY29tMQswCQYDVQQG\\nEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNVBAoTADEJMAcGA1UECxMA\\nMQ8wDQYJKoZIhvcNAQkBFgAwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAyXrK2sry\\n-----END\ CERTIFICATE----- -``` - -Note the spaces have an escape character `\` before them, as in `-----BEGIN\ CERTIFICATE-----`. Line breaks have been removed and replaced with `\\n`. - -Similar formatting should be applied to the matching private key. - -@subsection guide_developer_automated_tests_credentials_policy_travis Setting the credentials on Travis CI -@brief The AWS IoT endpoint, client certificate, and client certificate private key are set on Travis CI's "Settings" page. - -To edit the credentials, go to the "Settings" page on Travis CI. This is usually `/settings`; for example, `https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C/settings` for the main repo. Under "Environment Variables", set the following variables: -| Name | Value | Notes | -| --------------------- | ----- | ----- | -| AWS_IOT_CLIENT_CERT | [Formatted client certificate](@ref guide_developer_automated_tests_credentials_policy_formatting) | The AWS IoT client certificate.
This value does not need to be hidden in the build log. | -| AWS_IOT_ENDPOINT | `ABCDEFG1234567.iot..amazonaws.com` | The AWS IoT endpoint.
This value does not need to be hidden in the build log. | -| AWS_IOT_PRIVATE_KEY | [Formatted private key](@ref guide_developer_automated_tests_credentials_policy_formatting) | The private key matching the AWS IoT client certificate. @attention This value should be hidden in the build log! | -| IOT_IDENTIFIER_PREFIX | `CSDKCI` | This string is prepended to all MQTT client identifiers used by the CI.
It may be anything, but must match the client identifier in the AWS IoT policy.
The example policy below uses `CSDKCI`.
This value does not need to be hidden in the build log. | - -@subsection guide_developer_automated_tests_credentials_policy_aws Setting the AWS IoT policy -@brief The certificates used with Travis CI must have an appropriate policy attached. - -AWS recommends using the most restrictive policy possible. The following policy should be used for the CI. -@code{json} -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "iot:Connect", - "Resource": [ - "arn:aws:iot:::client/*" - ] - }, - { - "Effect": "Allow", - "Action": [ - "iot:Publish", - "iot:Receive" - ], - "Resource": [ - "arn:aws:iot:::topic/${iot:ClientId}/*", - "arn:aws:iot:::topic/*/LastWillAndTestament", - "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/shadow/*", - "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/jobs/*" - ] - }, - { - "Effect": "Allow", - "Action": "iot:Subscribe", - "Resource": [ - "arn:aws:iot:::topicfilter/${iot:ClientId}/*", - "arn:aws:iot:::topicfilter/*/LastWillAndTestament", - "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/shadow/*", - "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/jobs/*" - ] - } - ] -} -@endcode - -This policy only allows the CI to interact with topics that contain a matching client identifier (which is prefixed with the Travis CI variable `IOT_IDENTIFIER_PREFIX`). A few special topics used by the MQTT tests are also present. The policy will need to be updated if any new topics are used by the tests in the future. - -@section guide_developer_automated_tests_jobs Additional Travis CI setup for Jobs system tests -@brief Additional CI settings for running the [Jobs system tests](@ref jobs_tests). - -The Jobs system tests use AWS CLI to create and delete Jobs. Therefore, additional setup is needed in Travis CI. - -@pre See @ref jobs_system_tests_setup first to set up an AWS account for the Jobs system tests. Note that the coverage tests require an additional Thing to be registered (but can reuse the same AWS credentials). - -Under the "Environment Variables" section of the Travis CI "Settings" page, set the following additional environment variables. -| Name | Value | Notes | -| --------------------- | ----- | ----- | -| AWS_ACCESS_KEY_ID | The AWS access key for the Jobs tests IAM user | @attention This value should be hidden in the build log! | -| AWS_DEFAULT_REGION | The AWS region to use with the Jobs tests | Should be the region where the Jobs test Thing is registered.
This value does not need to be hidden in the build log. | -| AWS_SECRET_ACCESS_KEY | The AWS secret access key for the Jobs tests IAM user | @attention This value should be hidden in the build log! | -*/ diff --git a/doc/guide/developer/developer.txt b/doc/guide/developer/developer.txt deleted file mode 100644 index 9b45a2c295..0000000000 --- a/doc/guide/developer/developer.txt +++ /dev/null @@ -1,12 +0,0 @@ -/** -@page guide_developer Developer's Guide -@brief Guide for maintaining and contributing code to this project. - -This guide contains the following pages. All pages assume the reader has intermediate familiarity with this SDK. -- @subpage guide_developer_porting
- @copybrief guide_developer_porting -- @subpage guide_developer_styleguide
- @copybrief guide_developer_styleguide -- @subpage guide_developer_automated_tests
- @copybrief guide_developer_automated_tests -*/ diff --git a/doc/guide/developer/porting.txt b/doc/guide/developer/porting.txt deleted file mode 100644 index 02fc2aac0f..0000000000 --- a/doc/guide/developer/porting.txt +++ /dev/null @@ -1,142 +0,0 @@ -/** -@page guide_developer_porting Porting Guide -@brief Guide for porting this SDK to a new platform. - -This SDK has three components that must be ported to a new system: -1. [The build system](@ref guide_developer_porting_build) -2. [The config header](@ref guide_developer_config) -3. [The platform layer](@ref guide_developer_porting_platform) - -@section guide_developer_porting_build Porting the build system -@brief Guide for porting the SDK's build system. - -The CMake-based build system present in [the SDK's GitHub repo](https://github.com/aws/aws-iot-device-sdk-embedded-C) targets desktop systems and builds libraries into shared or static libraries. The build selects the [types of the platform layer](@ref platform_datatypes_handles) based on the detected host OS and automatically configures the platform layer. See @ref building for more information on the build system. - -In general, this SDK should build with C compilers in C99 mode. Currently, we do not guarantee builds with a C++ compiler. Compilers that provide intrinsics for atomic operations are recommended; see @ref platform_atomic for more information. - -@subsection guide_developer_porting_directory_layout Directory layout -@brief The following directories contain the SDK's source code and are relevant for porting. - -Of the directories listed below, only `ports/` should be modified during porting. Empty templates of a new port are placed in `ports/template`. Some directories present in the GitHub repo (such as `cbmc`, `doc`, and `scripts`) are not relevant for porting and therefore not listed. - -As relative paths from the SDK's root directory: -- `demos/`
- SDK sample applications. These files do not need to be ported, but are useful as a starting point. - - `app/`
- Contains demo applications for various systems, most importantly the `main()` functions. - - `include/`
- Headers only needed to build the demos. These do not need to be ported. - - `src/`
- Platform-independent demo sources. These do not need to be ported. - - `iot_config.h`
- The config header for the demo applications. Useful as an example. -- `libraries/`
- Platform-independent SDK files. This directory (and all its subdirectories) should be copied and not modified. - - `aws/`
- Client libraries for AWS IoT services. Individual library headers, source files, and tests are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. - - `defender/`
- Defender library headers, source files and tests. - - `include`
- Defender library header files. - - `src`
- Defender library source files. - - `test`
- Defender library tests. - - `common, jobs, shadow, ...`
- Other libraries have the same directory structure as the Defender library described above. - - `platform/`
- Interface of the platform layer, to be implemented in the `ports/` directory.
- - `types/`
- Platform types header. - - `standard`
- Libraries which are not specific to AWS IoT services. Headers, source files, and tests are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. - - `common, mqtt, serializer, ...`
- These libraries have the same directory structure as the Defender library described above. -- `ports/`
- This directory contains the desktop OS ports. Each port implements the functions of the platform layer interface for a specific desktop OS.
- - `common`
- Port files that are not specific to a single port. These headers are used across all of the desktop OS ports. - - `include/`
- Port headers that are not specific to a single port, such as the atomic and network headers. - - `atomic/`
- Implementations of atomic operations. See @ref platform_atomic for how to create a new port. New atomic ports should be placed here.
- - `src/`
- Port sources that are not specific to a single port, such as the network implementations. - - `template`
- Empty port sources containing stubbed-out functions. The files in this directory may be used as a starting point for a new port.
- - `posix, ...`
- Port sources and headers for a single implementation. The directory is named after the target OS.
-- `tests/`
- SDK test config file and test runner source. Individual library tests are in each library directory. When building tests, @ref IOT_BUILD_TESTS should be set to `1` globally. -- `third_party/`
- Third-party library code. This directory (and all its subdirectories) should be copied and not modified. - - `mbedtls/`
- [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17), consumed as a Git submodule. - - `tinycbor/`
- [Intel's TinyCBOR](https://github.com/intel/tinycbor), consumed as a Git submodule. - - `unity/`
- [Unity test framework](https://github.com/ThrowTheSwitch/Unity). Modifications were made to make its `malloc` overrides thread-safe. - -@subsection guide_developer_porting_include_paths Include paths -@brief The following paths should be passed as include paths to the compiler when building this SDK. - -SDK include paths that are always required: -- The path of the [config file](@ref global_config), `iot_config.h`. For example: - - In the SDK demos, `iot_config.h` is in `demos/`. - - In the SDK tests, `iot_config.h` is in `tests/`. -- The path of the port types file. For example, `ports/posix/include` when using the POSIX port. -- `libraries/platform/` - -Each library has its headers in a different `include` directory. Library include directories are in `libraries/aws` for AWS IoT client libraries, and `libraries/standard` for other libraries. For example, the MQTT library's include directory is `libraries/standard/mqtt/include/` and the AWS IoT Shadow library's include directory is `libraries/aws/shadow/include/`. Each library's include directory must be added to the include path if that library is being used. - -Third-party submodule include paths: -- TinyCBOR: `third_party/tinycbor/tinycbor/src`
- Required when building the serializer library and for the Defender tests. -- mbed TLS: `third_party/mbedtls` and `third_party/mbedtls/mbedtls/include`
- Required when building @ref platform_network with the mbed TLS implementation. - -Additional include paths required to build the demos: -- `demos/include` - -Additional include paths required to build the tests: -- `third_party/unity/unity` -- `third_party/unity/unity/fixture` -- `libraries/standard/mqtt/test/access` (when building the MQTT or Shadow tests) -- `libraries/standard/mqtt/test/mock` (when building tests that use the MQTT mocks, such as Shadow or Jobs) - -In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1` globally when building tests. - -@section guide_developer_config Config header -@brief Settings that must be set in the config header, iot_config.h - -@see [Global configuration](@ref global_config)
-In addition, each library has its own configuration settings. - -At the very least, the config header must contain the following defines: - - Memory allocation functions must be set in the config header. See @ref Iot_DefaultMalloc for more details. - - Assert functions must be set if asserts are enabled for a library. See @ref Iot_DefaultAssert for more details. - -The platform types must also be set. See @ref guide_developer_porting_platform for more details. - -@section guide_developer_porting_platform Porting the platform layer -@brief Guide for porting the SDK's [platform layer](@ref platform). - -@see [Platform layer](@ref platform) - -@subsection guide_developer_porting_platform_types Platform types -@brief [Types](@ref platform_datatypes_handles) that must be set in the platform layer. - -@see [Platform types](@ref platform_datatypes_handles) for a list of all types that must be set.
-`ports/posix/include/iot_platform_types_posix.h` for an example of setting the types on POSIX systems. - -The platform types should be set in the [config file](@ref global_config), `iot_config.h`, or in another header included by the config file. As an example, the header `iot_platform_types_posix.h` sets the platform types to the matching POSIX system types. This header is then included in `iot_config.h` by the provided CMake build system. - -@attention Any type configuration headers included by the config file must never include other SDK files. Since this file will be included by every SDK source file, take care not to include too many unnecessary symbols. - -@subsection guide_developer_porting_platform_functions Platform functions -@brief [Functions](@ref platform_functions) that must be implemented for the platform layer. - -@see [Platform functions](@ref platform_functions) for a list of all functions that must be implemented.
-`libraries/platform` for the interfaces of the platform functions.
-`ports/posix/src` for examples of functions implemented for POSIX systems. -*/ diff --git a/doc/guide/developer/style.txt b/doc/guide/developer/style.txt deleted file mode 100644 index 8efe6aa524..0000000000 --- a/doc/guide/developer/style.txt +++ /dev/null @@ -1,302 +0,0 @@ -/** -@page guide_developer_styleguide Style Guide -@brief Guide for the coding style used in this SDK. - -The goal of this style guide is to encourage a readable and consistent coding style across the entire SDK. - -@section guide_developer_styleguide_codingstyle Coding Style -@brief The coding style used in this SDK. - -The coding style aims to produce code that is readable and easy to debug. An example is provided in @ref guide_developer_styleguide_codingstyle_example. - -@subsection guide_developer_styleguide_codingstyle_generalguidelines General guidelines -@brief General guidelines for library style. -- Libraries should only use features from [C99](https://en.wikipedia.org/wiki/C99) and earlier. - -- Libraries should [log](@ref logging) extensively. -- Code should be well-commented. -- Only `/*` and @c *`/` should be used to start and end comments. -- All comments end with a period. -- Use of `goto` is discouraged. -- Only spaces should be used for indenting. A single indent is 4 spaces. No tab characters should be used. -- A parenthesis is usually followed by a space (see @ref guide_developer_styleguide_codingstyle_example). -- Lines of code should be less than 80 characters long, although longer lines are permitted. -- All local variables should be declared at the top of a function. -- All global variables should be declared at the top of a file. -- Variables are always initialized. -- A separator is placed between different sections of a file. The current separator is: -@code{c} -/*-----------------------------------------------------------*/ -@endcode -- All files must include `iot_config.h` at the top of the file before any other includes. -- `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. -- The [GNU complexity](https://www.gnu.org/software/complexity/manual/complexity.html) of every function should be less than 9. -- Deviations from [MISRA C:2012](https://en.wikipedia.org/wiki/MISRA_C) coding guidelines should be documented as comments in the code. - -@subsection guide_developer_styleguide_codingstyle_typeguidelines Type guidelines -@brief Guidelines for variable types. -- Only fixed-width integer types should be used. Exceptions for `bool` and types required by third-party APIs. -- The default integer in the libraries should be 32 bits wide, i.e. `int32_t` or `uint32_t`. -- Sizes and lengths should be represented with `size_t`, and Boolean variables with `bool`. - -@subsection guide_developer_styleguide_codingstyle_example Example File -@brief An example file that follows the coding style rules. - -See @ref guide_developer_styleguide_naming for how to name the functions, variables, and macros. - -@code{c} -/* Included headers are at the top of the file. The config file include is always first. */ -#include "iot_config.h" - -/* Standard includes are immediately after the config file. They are sorted alphabetically. - * They use angle brackets <> around the file name. */ - -/* Standard includes. */ -#include -#include -#include -#include - -/* Library internal headers are included next. They use quotes "" around the file name. */ - -/* Library internal include. */ -#include "private/iot_library_internal.h" - -/* Error handling include (include only when needed). */ -#include "private/iot_error.h" - -/* Library application-facing headers are included last. They use quotes "" around the file name. */ - -/* Library include. */ -#include "iot_library.h" - -/*-----------------------------------------------------------*/ - -/* Defined constants follow the included headers. */ - -/* When possible, parentheses () should be placed around constant values and a type - * should be specified. */ -#define LIBRARY_CONSTANT ( ( int32_t ) 10 ) - -#define LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ - { \ /* Function-like macros are surrounded by curly braces {}. */ - macro_body( ( argument ) ); \ /* Parentheses surround macro arguments for MISRA 20.7 */ - } - -/*-----------------------------------------------------------*/ - -/* Library typedefs follow the defined constants. */ - -/* Forward declarations are used only when necessary. They are placed before all - * other typedefs. */ - -typedef int32_t _type_t; - -typedef struct _structType /* Structs are named along with the typedef. */ -{ - int32_t member; - - /* As usage of unions violates MISRA rule 19.2, we must document our usage - * and justification. Unions are used here to reduce the size of this struct. */ - union /* Anonymous structs/unions are permitted only inside of other structs. */ - { - int32_t a; - int32_t b; - }; - - /* Although variable length arrays (a C99 feature) violate MISRA rule 18.7, - * they are permitted provided documentation is supplied in comments. */ - int32_t variableLengthMember[]; -} _structType_t; - -/*-----------------------------------------------------------*/ - -/* Declarations of static and extern functions follow the typedefs. */ - -static bool _libraryStaticFunction( void * pArgument, - size_t argumentLength ); - -/* External function declarations should be used sparingly (using an internal - * header file to declare functions is preferred). */ -extern int32_t IotLibrary_ExternalFunction( void * pArgument ); - -/*-----------------------------------------------------------*/ - -/* Declarations of global variables follow the static and extern function - * declarations. Global variables are permitted, but should be avoided when - * possible. */ - -/* Global variables are always initialized. */ -static int _globalVariable = 0; -static int _globalArray[ _LIBRARY_CONSTANT ] = { 0 }; - -/*-----------------------------------------------------------*/ - -/* Implementations of static functions follow the global variable declarations. */ - -static bool _libraryStaticFunction( void * pArgument, - size_t argumentLength ) -{ - /* All local variables are declared at the top of the function. Variables are - * always initialized. */ - size_t i = 0; - int32_t localVariable = 0; - int32_t * pLocalPointer = ( int32_t * ) pArgument; - bool status = true; - - /* All functions make generous use of the logging library. */ - IotLogInfo( "Performing calculation..." ); - - if( ( pArgument == NULL ) || ( argumentLength == 0 ) ) /* Note the parentheses and spacing in if statements */ - { - IotLogError( "Bad parameters." ); - - /* Local variable instead of a goto for error handling. */ - status = false; - } - else /* Note an else clause is used instead of goto for error handling */ - { - for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ - { - localVariable += IotLibrary_ExternalFunction( pArgument ); - - IotLogDebug( "Current value is %d.", ( int ) localVariable ); - } - - if( localVariable < 0 ) - { - IotLogWarn( "Failed to calculate positive value." ); - } - - IotLogInfo( "Calculation done." ); - } - - return status; -} - -/* A separator is placed between all function implementations. */ -/*-----------------------------------------------------------*/ - -/* Implementations of application-facing functions are at the bottom of the file. */ - -bool IotLibrary_ApplicationFunction( void ) /* Functions with no arguments have void in their argument list. */ -{ - LIBRARY_FUNCTION_MACRO( _globalArray ); - - return true; -} - -/* Separator and newline at end of file */ -/*-----------------------------------------------------------*/ - -@endcode - -@section guide_developer_styleguide_naming Naming -@brief Naming convention used in this SDK. - -The naming convention aims to differentiate this SDK's files, variables, and functions to avoid name collisions. In general: -- The first characters of all publicly visible names should identify the name as part of this SDK.
- Example: For general-purpose libraries (such as MQTT), names start with `iot_` or `Iot`.
- Example: For libraries specific to AWS IoT (such as Shadow), names start with `aws_iot_` or `AwsIot`. -- Words in names should be ordered with the most general word first and the most specific word last.
- Example: `iot_mqtt_api.c` identifies a file as part of the general MQTT library. `_IotMqtt_ValidateConnect` identifies a function as part of the Internal component of the general MQTT library. -- Names should avoid using abbreviations. - -@subsection guide_developer_styleguide_naming_definedconstantsandenumvalues Defined constants and enum values -@brief Naming convention for constants set using preprocessor @c #`define` and enum values. - -@formattable{defined constants and enum values} -@formattableentry{`IOT_`LIBRARY`_`DESCRIPTION
`AWS_IOT_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in application-facing library header files,`IOT_MQTT_SUCCESS` (iot_mqtt.h)
`AWS_IOT_SHADOW_SUCCESS` (aws_iot_shadow.h)} -@formattableentry{`IOT_DEMO_`DESCRIPTION
`IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests,`IOT_DEMO_MQTT_PUBLISH_BURST_SIZE`
`IOT_TEST_MQTT_THREADS`} -@formattableentry{`AWS_IOT_DEMO_`DESCRIPTION
`AWS_IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests specific to AWS IoT,`AWS_IOT_DEMO_THING_NAME`
`AWS_IOT_TEST_SHADOW_THING_NAME`} -@formattableentry{DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal names in AWS-specific libraries,`MQTT_PACKET_TYPE_CONNECT`
(iot_mqtt_internal.h)
`SHADOW_OPERATION_COUNT`
(aws_iot_shadow_internal.h)} - -Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `IOT_` or `AWS_IOT_`. - -@subsection guide_developer_styleguide_naming_files Files -@brief Naming convention for files. - -@formattable{files} -@formattableentry{`iot_`library`_`description`.extension`
`aws_iot_`library`_`description`.extension`,General library file,`iot_mqtt_api.c`
`aws_iot_shadow_api.c`} -@formattableentry{`iot_`library`_internal.h`
`aws_iot_`library`_internal.h`,Internal library header,`iot_mqtt_internal.h`
`aws_iot_shadow_internal.h`} -@formattableentry{`iot_demo_`library`.c`
`aws_iot_demo_`library`.c`,Library demo source,`iot_demo_mqtt.c`
`aws_iot_demo_shadow.c`} -@formattableentry{`iot_tests_`library`_`description`.c`
`aws_iot_tests_`library`_`description`.c`,Library test source,`iot_tests_mqtt_api.c`
`aws_iot_tests_shadow_api.c`} - -File names contain only lowercase letters and underscores. All file names should start with `iot_` or `aws_iot_` and be named according to their purpose. For example: -- `iot_mqtt_api.c`: A file in the MQTT library that implements the MQTT API functions. -- `iot_demo_mqtt.c`: A file in the Demos for the MQTT library. -- `iot_demo_mqtt_posix.c`: A file in the Demos for the MQTT library on POSIX systems. -- `iot_tests_mqtt_api.c`: A file in the Tests for the MQTT library. Since the tests currently only run on POSIX systems, test file names do not use the `_posix` suffix. - -Library file names should use one or two words to describe the functions implemented in that file. For example: -- `iot_mqtt_serialize.c`: Implements the MQTT library's packet serialization and deserialization functions. -- `iot_clock_posix.c`: Implements the platform clock component for POSIX systems. - -Declarations of internal functions, structures, macros, etc. of a library should be placed in a header file with an `_internal` suffix. The `_internal` header file should go in the `lib/include/private` directory. For example: -- `iot_mqtt_internal.h`: Declares the MQTT library's internal functions, structures, macros, etc. - -File names for tests and demos should all begin with `iot_demo_` and `iot_tests_`, respectively. The names should then specify the library being demoed or or tested; for example, the files names of the MQTT library's demos and tests start with `iot_demo_mqtt_` and `iot_tests_mqtt_`. Additionally, test file names should describe what tests are implemented in the file, such as `iot_tests_mqtt_api.c` for a file containing tests for the MQTT library API functions. - -@subsection guide_developer_styleguide_naming_functions Functions (and function-like macros) -@brief Naming convention of functions and function-like macros. - -@formattable{functions} -@formattableentry{`Iot`Library`_`Description
`AwsIot`Library`_`Description,Externally-visible library function,`IotMqtt_Publish`
`AwsIotShadow_Update`} -@formattableentry{`_Iot`Library`_`Description
`_AwsIot`Library`_`Description,Internal (but not `static`) library function,`_IotMqtt_ValidateNetIf`
`_AwsIotShadow_ParseShadowStatus`} -@formattableentry{`IotTest`Library`_`Description,Function only used in tests,`IotTest_NetworkConnect`
`IotTestMqtt_createMqttConnection`} -@formattableentry{`_`description,`static` function (never uses `Aws` prefix),`_createMqttConnection`
`_codeToShadowStatus`} -@formattableentry{`IotTest`Library`_`accessedFunction,Test access function,`IotTestMqtt_createMqttConnection`} - -Externally visible (i.e. not `static`) functions are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case) and must begin with `Iot` or `AwsIot` (public API functions); or `_Iot` or `_AwsIot` (internal functions). Function names should then specify their library name; followed by an underscore; followed by a brief description of what the function does. For example: -- `IotMqtt_Publish`: This function is part of the public MQTT API. It Publishes an MQTT message. -- `_IotMqtt_ValidateNetIf`: This function is internal to the MQTT library, but not `static`. It validates an `IotMqttNetIf_t`. - -Functions not visible outside their source file (i.e. `static` functions) have names that are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and begin with an underscore. These function names do not contain the library name or `Aws`. For example: -- `_createMqttConnection`: A `static` function in `iot_mqtt_api.c`. -- `_codeToShadowStatus`: A `static` function in `aws_iot_shadow_parser.c`. - -Test access functions begin with `IotTest`; followed by the library name; followed by an underscore; followed by the function that the test function accesses. Since the accessed function is always `static`, the accessed function will be [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). - -@subsection guide_developer_styleguide_naming_types Types -@brief Naming conventions of library `typedef` types. - -@formattable{types} -@formattableentry{`Iot`LibraryDescription`_t`
`AwsIot`LibraryDescription`_t`,General types in application-facing library header files,`IotMqttError_t` (iot_mqtt.h)
`AwsIotShadowError_t` (aws_iot_shadow.h)} -@formattableentry{`Iot`LibraryFunction`Info_t`
`AwsIot`LibraryFunction`Info_t`,Application-facing parameter structure,`IotMqttPublishInfo_t`
(Parameter structure to `IotMqtt_Publish`)
`AwsIotShadowDocumentInfo_t`
(Parameter structure for Shadow documents)} -@formattableentry{`_`libraryDescription`_t`,Type in an `internal` header,`_mqttOperation_t` (iot_mqtt_internal.h)
`_shadowOperation_t` (aws_iot_shadow_internal.h)} -@formattableentry{`_`description`_t`,Internal type in source file,`_topicMatchParams_t` (iot_mqtt_subscription.c)} - -Types intended for use in applications are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `Iot` or `AwsIot` and end with `_t`. Parameter structures should indicate their associated function: for example, `IotMqttPublishInfo_t` is passed as a parameter to `IotMqtt_Publish`. - -Types intended for internal library use defined in a header file are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with `_`, followed by the library name, and end with `_t`. Internal types defined in a library source file must start with `_`, end with `_t`, and not include the library name. - -`struct` typedefs should always be named along with the `typedef`. The struct name should be identical to the `typedef` name, but without the `_t` at the end. For example: -@code{c} -typedef struct IotLibraryStruct -{ - int32_t member; -} IotLibraryStruct_t; - -typedef struct _libraryInternalStruct -{ - int32_t member; -} _libraryInternalStruct_t; -@endcode - -A `struct` may contain anonymous `struct` or `union` members. - -@subsection guide_developer_styleguide_naming_variables Variables -@brief Naming conventions of variables. - -@formattable{variables} -@formattableentry{variableDescription,General local variable,`startTime`} -@formattableentry{`p`VariableDescription,Local variable pointers and arrays (including strings),`pSubscriptionList`} -@formattableentry{`_`variableDescription,Global variable that is `static`,`_connectMutex` (iot_mqtt_api.c)
`_pSamplePayload` (string)} -@formattableentry{`_Iot`LibraryDescription
`_AwsIot`LibraryDescription,Global variable that is NOT `static`,`_IotMqttTaskPool` (iot_mqtt_operation.c)
`_pIotSamplePayload` (string)} - -Local variable names are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and consist only of a description of the variable. Names like `i` or `j` are acceptable for loop counters, but all other variables should have a descriptive name. - -Global variable names always start with a `_`. Global variables that are `static` consist of only the description in [lowerCamelCase](https://en.wikipedia.org/wiki/Camel_case). Global variables that are not static consist of the library name and the description in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case); for example: `_IotLibraryNonStaticGlobalVariable`. - -All pointers, arrays, and strings (both global and local) start with `p` (local) or `_p` (global). -*/ diff --git a/doc/guide/migration.txt b/doc/guide/migration.txt deleted file mode 100644 index 6393883871..0000000000 --- a/doc/guide/migration.txt +++ /dev/null @@ -1,16 +0,0 @@ -/** -@page migration Migration Guide -@brief Migration guide from C SDK version 2 or 3 to version 4. - -Version 4 of the C SDK is a new design, and therefore NOT backwards compatible with previous versions. The pages below provide instructions on migrating from version 2 or 3 to version 4. - -In these pages, v3 refers to both previous versions 2 and 3, which are largely compatible with each other. -- @subpage migration_platform
- Migrating @ref platform from v3 to v4. This must be done before any other component. -- @subpage migration_mqtt
- Migrating an [MQTT](@ref mqtt) application from v3 to v4. -- @subpage migration_shadow
- Migrating a [Shadow](@ref shadow) application from v3 to v4. -- @subpage migration_jobs
- Migrating a [Jobs](@ref jobs) application from v3 to v4. -*/ diff --git a/doc/guide/migration/jobs.txt b/doc/guide/migration/jobs.txt deleted file mode 100644 index 47f1d96307..0000000000 --- a/doc/guide/migration/jobs.txt +++ /dev/null @@ -1,90 +0,0 @@ -/** -@page migration_jobs Jobs -@brief How to migrate a Jobs application from v3 to v4. - -The Jobs library has been redesigned in v4 as a thread-aware library supporting asynchronous operations. - -The features of the Jobs libraries in v3 and v4 are defined by the common AWS service; therefore, the two versions are similar. - -@section migration_jobs_functions Functions -@brief The following table lists equivalent API functions in v3 and v4. These functions are the API functions declared in: -- In v3: aws_iot_jobs_interface.h -- In v4: [Jobs functions](@ref jobs_functions) - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
- aws_iot_jobs_subscribe_to_job_messages
- aws_iot_jobs_subscribe_to_all_job_messages
- aws_iot_jobs_unsubscribe_from_job_messages
- aws_iot_jobs_send_query -
Specific Jobs operation function - These v3 functions for sending generic Jobs operations have been removed and replaced with individual functions for each supported Jobs operation.

- In v3, the `topicType` parameter determined the Jobs operation. The equivalent functions in v4 are: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
v3 topicTypev4 Function
JOB_UNRECOGNIZED_TOPICNone (not valid)
JOB_GET_PENDING_TOPIC@ref jobs_function_getpendingsync
JOB_START_NEXT_TOPIC@ref jobs_function_startnextsync
JOB_DESCRIBE_TOPIC@ref jobs_function_describesync
JOB_UPDATE_TOPIC@ref jobs_function_updatesync
JOB_NOTIFY_TOPIC@ref jobs_function_setnotifypendingcallback
JOB_NOTIFY_NEXT_TOPIC@ref jobs_function_setnotifynextcallback
JOB_WILDCARD_TOPICCombination of functions, based on what the wildcard represented
- - For example, a call to `aws_iot_jobs_send_query(..., topicType=JOB_GET_PENDING_TOPIC, ...)` should be replaced with a call to the v4 API @ref jobs_function_getpendingsync. -
aws_iot_jobs_start_next@ref jobs_function_startnextsync@ref jobs_function_startnextasync is the equivalent asynchronous function.
aws_iot_jobs_describe@ref jobs_function_describesync@ref jobs_function_describeasync is the equivalent asynchronous function.
aws_iot_jobs_update@ref jobs_function_updatesync@ref jobs_function_updateasync is the equivalent asynchronous function.
- -*/ diff --git a/doc/guide/migration/mqtt.txt b/doc/guide/migration/mqtt.txt deleted file mode 100644 index d93c586683..0000000000 --- a/doc/guide/migration/mqtt.txt +++ /dev/null @@ -1,113 +0,0 @@ -/** -@page migration_mqtt MQTT -@brief How to migrate an MQTT application from v3 to v4. - -The MQTT library has been redesigned in v4 as a thread-aware library supporting asynchronous operations. New features such as persistent session support and retry of QoS 1 publish messages have also been added. - -The features of the MQTT libraries in v3 and v4 are defined by the common MQTT 3.1.1 spec; therefore, the two versions are similar. - -@section migration_mqtt_removed Removed features -@brief The following features which were present in v3 were removed in v4. -- Auto-reconnect
- Version 3 has an auto-reconnect feature triggered through a call to `aws_iot_mqtt_yield`. In version 4, `aws_iot_mqtt_yield` has been removed and replaced with background tasks.
- Workaround: When a version 4 API call returns @ref IOT_MQTT_NETWORK_ERROR or the version 4 MQTT disconnect callback is invoked, call @ref mqtt_function_connect through the application. The application should employ an exponential backoff strategy when re-establishing connections. - -@section migration_mqtt_datatypes Data Types -@brief The following table lists equivalent data types in v3 and v4. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
enum QoS@ref IotMqttQos_tMQTT Quality of service.
AWS_IoT_Client@ref IotMqttConnection_tMQTT connection handle.
IoT_Publish_Message_Params@ref IotMqttPublishInfo_tParameters of an MQTT publish.
IoT_MQTT_Will_Options
IoT_Client_Connect_Params
@ref IotMqttConnectInfo_tParameters of an MQTT connect.
The two v3 structs are combined in v4.
IoT_Client_Init_ParamsNoneThe members of this struct in v3 handled setup of the network connection.
This has been moved to @ref platform_network in v4.
- -@section migration_mqtt_functions Functions -@brief The following table lists equivalent API functions in v3 and v4. These functions are the API functions declared in: -- In v3: aws_iot_mqtt_client_interface.h -- In v4: [MQTT functions](@ref mqtt_functions) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
aws_iot_mqtt_init@ref mqtt_function_initIn v3, this function initializes a single client.
In v4, this function initializes the entire library.
aws_iot_mqtt_free@ref mqtt_function_cleanupIn v3, this function frees a single client.
In v4, this function cleans up the entire library.
aws_iot_mqtt_connect@ref mqtt_function_connect
aws_iot_mqtt_publish@ref mqtt_function_publishsync@ref mqtt_function_publishasync is the equivalent asynchronous function.
aws_iot_mqtt_subscribe@ref mqtt_function_subscribesync@ref mqtt_function_subscribeasync is the equivalent asynchronous function.
aws_iot_mqtt_resubscribeNoneFunction removed because auto-reconnect was removed.
aws_iot_mqtt_unsubscribe@ref mqtt_function_unsubscribesync@ref mqtt_function_unsubscribeasync is the equivalent asynchronous function.
aws_iot_mqtt_disconnect@ref mqtt_function_disconnect
aws_iot_mqtt_yieldNoneRemoved in favor of background tasks.
Nothing needs to be changed in an application.
aws_iot_mqtt_attempt_reconnectNoneFunction removed because auto-reconnect was removed.
-*/ diff --git a/doc/guide/migration/platform.txt b/doc/guide/migration/platform.txt deleted file mode 100644 index 38f6624e13..0000000000 --- a/doc/guide/migration/platform.txt +++ /dev/null @@ -1,288 +0,0 @@ -/** -@page migration_platform Platform layer -@brief How to migrate the platform layer from v3 to v4. - -The platform layer provides portability across different operating systems. Both v3 and v4 provide a POSIX port as an example. If you are running on a v3 application on a POSIX system, you can switch the to provided v4 POSIX port and skip this page. This guide is intended for applications running on other operating systems that would like to move their porting layer from v3 to v4. - -@section migration_platform_mutex Mutex -@brief How to migrate the mutex type from v3 to v4. - -The mutexes used in v3 and v4 implement the same interface. In v4, recursive mutexes must be supported. - -The table below shows the equivalent mutex types for v3 and v4. - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
Namestruct _IoT_Mutex_t_IotSystemMutex_tIn v3, the mutex type must be a struct.
In v4, the mutex type may be any type.
Declare in filethreads_platform.hiot_config.h - In v3, the mutex type must be declared in a file called threads_platform.h. - Link to sample -
- In v4, we recommend declaring the mutex type in iot_config.h. -
- -@subsection migration_platform_mutex_interface Interface -@brief Equivalent functions of the mutex interface between v3 and v4. - -The following functions should be implemented for the mutex type: -- v3: See functions in threads_interface.h -- v4: IotMutex functions in @ref platform_threads_functions - -The table below shows the equivalent functions in v3 and v4. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
aws_iot_thread_mutex_init@ref platform_threads_function_mutexcreateThe v4 function must support recursive mutexes.
aws_iot_thread_mutex_lock@ref platform_threads_function_mutexlock
aws_iot_thread_mutex_trylock@ref platform_threads_function_mutextrylock
aws_iot_thread_mutex_unlock@ref platform_threads_function_mutexunlock
aws_iot_thread_mutex_destroy@ref platform_threads_function_mutexdestroy
- -@subsection migration_platform_mutex_example Example -@brief Example of migration between v3 and v4. - -The table below provides examples of the equivalent mutex types and interfaces implemented on v3 and v4. - - - - - - - - - - - - - - - - - -
Version 3Version 4
Declaration - @code{c} - /* In threads_platform.h */ - struct _IoT_Mutex_t { - pthread_mutex_t lock; - }; - @endcode - - @code{c} - /* In iot_config.h */ - typedef pthread_mutex_t _IotSystemMutex_t; - @endcode -
Sample implementation (POSIX)threads_pthread_wrapper.ciot_threads_posix.c
- -@section migration_platform_timer Timer -@brief Differences between v3 and v4 timers. - -The timers in v4 are more efficient than v3, but their interface is completely different. In v4, the timer interface is designed to be similar to the timer APIs provided by most operating systems. - -Because of the differences between the timers in v3 and v4, it is not possible to migrate a v3 timer implementation to v4. Therefore, a new timer implementation will need to be written for v4. See @ref platform_clock_functions for the list of functions that need to be implemented. - -@section migration_platform_semaphore Semaphores -@brief Details about counting semaphores needed in v4. - -Counting semaphores are a new addition to the platform layer in v4. They were not present in v3. See the IotSemaphore functions in @ref platform_threads_functions for the list of functions that need to be implemented. - -@section migration_platform_network Networking -@brief How to migrate the network implementation from v3 to v4. - -The networking functions are similar between v3 and v4. However, v4 requires some additional function to be implemented. - -The networking component requires a type to provide credentials for secured connections. The table below shows the equivalent credentials type for v3 and v4. - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
NameTLSDataParams_IotNetworkCredentials_tv4 provides @ref IotNetworkCredentials as a struct that satisfies most use cases.
Declare in filenetwork_platform.hiot_config.h - In v3, the credentials type must be declared in a file called network_platform.h. - Link to sample -
- In v4, we recommend declaring the credentials type in iot_config.h. -
- -Version 4 requires two additional types to be declared: -- `_IotNetworkServerInfo_t` which provides data on the remote server. @ref IotNetworkServerInfo should satisfy most use cases. -- `_IotNetworkConnection_t` to represent the handle of a network connection. This should be an opaque handle. - -@subsection migration_platform_networking_interface Interface -@brief Equivalent functions of the networking interface between v3 and v4. - -The following functions should be implemented for networking: -- v3: See functions in network_interface.h -- v4: Functions in @ref platform_network_functions - -The table below shows the equivalent functions in v3 and v4. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
iot_tls_initPlatform-specific init functions - The v4 networking interface does not define a function that must be implemented to initialize the network, as some network stacks do not require initialization. It may be implemented if needed.

- The parameters of iot_tls_init have been moved to @ref platform_network_function_create. -
iot_tls_connect@ref platform_network_function_create
iot_tls_write@ref platform_network_function_send
iot_tls_read@ref platform_network_function_receive
iot_tls_disconnect@ref platform_network_function_close
iot_tls_destroy@ref platform_network_function_destroyIn v3, this function destroys the entire network stack.
In v4, this function only destroys the connection passed to it.
- -Version 4 has the following new functions that must be implemented: -- @ref platform_network_function_setreceivecallback -- @ref platform_network_function_setclosecallback - -Version 4 requires the networking functions to be given as an @ref IotNetworkInterface_t. We recommend implementing a function that returns the @ref IotNetworkInterface_t containing the networking functions. - -@subsection migration_platform_networking_example Example -@brief Example of migration between v3 and v4. - -The table below provides examples of the equivalent networking types and interfaces implemented on v3 and v4. - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4
Credentials type declaration - @code{c} - /* In threads_platform.h */ - typedef struct _TLSDataParams { - /* Members omitted due to length. */ - } TLSDataParams; - @endcode - - @code{c} - /* In iot_config.h */ - - /* Forward declare standard type from v4. */ - struct IotNetworkCredentials; - - typedef struct IotNetworkCredentials * _IotNetworkCredentials_t; - @endcode -
Server info declarationNone - @code{c} - /* In iot_config.h */ - - /* Forward declare standard type from v4. */ - struct IotNetworkServerInfo; - - typedef struct IotNetworkServerInfo * _IotNetworkServerInfo_t; - @endcode -
Opaque network type declarationNone - @code{c} - /* In iot_config.h */ - - /* Forward declare opaque struct. */ - struct _networkConnection; - - typedef struct _networkConnection * _IotNetworkConnection_t; - @endcode -
Sample implementation (mbed TLS)network_mbedtls_wrapper.ciot_network_mbedtls.c
-*/ diff --git a/doc/guide/migration/shadow.txt b/doc/guide/migration/shadow.txt deleted file mode 100644 index d96d3c355a..0000000000 --- a/doc/guide/migration/shadow.txt +++ /dev/null @@ -1,90 +0,0 @@ -/** -@page migration_shadow Shadow -@brief How to migrate a Shadow application from v3 to v4. - -The Shadow library has been redesigned in v4 as a thread-aware library supporting asynchronous operations. - -The features of the Shadow libraries in v3 and v4 are defined by the common AWS service; therefore, the two versions are similar. - -@section migration_shadow_removed Removed features -@brief The following features which were present in v3 were removed in v4. -- Shadow JSON functions
- Version 3 has functions for generating and parsing JSON Shadow documents. These functions have been removed in v4 due to their high memory usage.
- Workaround: `snprintf` can be used to generate Shadow documents. JSON parsing can be done through third-party libraries. - -@section migration_shadow_functions Functions -@brief The following table lists equivalent API functions in v3 and v4. These functions are the API functions declared in: -- In v3: aws_iot_shadow_interface.h -- In v4: [Shadow functions](@ref shadow_functions) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Version 3Version 4Notes
aws_iot_shadow_init@ref shadow_function_initIn v3, this function initializes a single client.
In v4, this function initializes the entire library.
aws_iot_shadow_free@ref shadow_function_cleanupIn v3, this function frees a single client.
In v4, this function cleans up the entire library.
aws_iot_mqtt_connect
aws_iot_shadow_disconnect
None - These functions were thin wrappers for the MQTT connect and disconnect functions.
- Applications using these v3 functions should replace them with calls to:
- @ref mqtt_function_connect
- @ref mqtt_function_disconnect -
aws_iot_shadow_yieldNoneRemoved in favor of background tasks.
Nothing needs to be changed in an application.
aws_iot_shadow_update@ref shadow_function_updatesync@ref shadow_function_updateasync is the equivalent asynchronous function.
aws_iot_shadow_get@ref shadow_function_getsync@ref shadow_function_getasync is the equivalent asynchronous function.
aws_iot_shadow_delete@ref shadow_function_deletesync@ref shadow_function_deleteasync is the equivalent asynchronous function.
aws_iot_shadow_register_delta@ref shadow_function_setdeltacallback
- aws_iot_shadow_reset_last_received_version
- aws_iot_shadow_get_last_received_version
- aws_iot_shadow_enable_discard_old_delta_msgs
- aws_iot_shadow_disable_discard_old_delta_msgs
-
None - Removed due to removal of JSON parsing.
- The application should parse received Shadow documents and decide whether to discard them based on the version key. -
aws_iot_shadow_set_autoreconnect_statusNoneRemoved due to removal of auto-reconnect in the MQTT library.
-*/ diff --git a/doc/lib/defender.txt b/doc/lib/defender.txt deleted file mode 100644 index f3f3781248..0000000000 --- a/doc/lib/defender.txt +++ /dev/null @@ -1,53 +0,0 @@ -/** -@mainpage -@anchor defender -@brief AWS IoT Device Defender library. - -> AWS IoT Device Defender is a fully managed service that helps you secure your fleet of IoT devices. AWS IoT Device Defender continuously audits your IoT configurations to make sure that they aren’t deviating from security best practices. A configuration is a set of technical controls you set to help keep information secure when devices are communicating with each other and the cloud. AWS IoT Device Defender makes it easy to maintain and enforce IoT configurations, such as ensuring device identity, authenticating and authorizing devices, and encrypting device data. AWS IoT Device Defender continuously audits the IoT configurations on your devices against a set of predefined security best practices. AWS IoT Device Defender sends an alert if there are any gaps in your IoT configuration that might create a security risk, such as identity certificates being shared across multiple devices or a device with a revoked identity certificate trying to connect to AWS IoT Core. - -Description of Device Defender from [AWS IoT documentation](https://aws.amazon.com/iot-device-defender/)
- -This library provides an API for interacting with Device Defender. - -@dependencies{defender,Defender library} -@dot "Device Defender direct dependencies" -digraph defender_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - - defender[label="Device Defender", fillcolor="#cc00ccff"]; - - subgraph - { - mqtt[label="MQTT", fillcolor="#cc00ccff", URL="@ref mqtt"]; - } - subgraph - { - node[fillcolor="#aed8a9ff"]; - rank = same; - logging[label="Logging", URL="@ref logging"]; - static_memory[label="Static memory", URL="@ref static_memory"]; - linear_containers[label="List/Queue", URL="@ref linear_containers"]; - taskpool[label="Taskpool", URL="@ref taskpool"]; - } - subgraph - { - node[fillcolor="#e89025ff"]; - rank = same; - platform_threads[label="Thread", URL="@ref platform_threads"]; - platform_clock[label="Clock", URL="@ref platform_clock"]; - platform_metrics[label="Metrics", URL="@ref platform_metrics"]; - } - - defender -> mqtt; - defender -> linear_containers; - defender -> logging[label=" if logging enabled", style="dashed"]; - defender -> static_memory[label=" if static memory only", style="dashed"]; - defender -> taskpool; - defender -> platform_threads; - defender -> platform_clock; - defender -> platform_metrics; -} -@enddot -*/ diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt deleted file mode 100644 index 7b18b06f51..0000000000 --- a/doc/lib/jobs.txt +++ /dev/null @@ -1,273 +0,0 @@ -/** -@mainpage -@anchor jobs -@brief AWS IoT Device Jobs library. - -> AWS IoT jobs can be used to define a set of remote operations that are sent to and executed on one or more devices connected to AWS IoT. - -Description of Jobs from [AWS IoT documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html)
- -This library provides an API based on the [Jobs APIs available over MQTT](https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#jobs-mqtt-api). Features of this library include: -- Both fully asynchronous and blocking API functions. -- API functions for interacting with Jobs and registering notifications for pending Jobs. - -@dependencies{jobs,Jobs library} -@dot "Jobs direct dependencies" -digraph jobs_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph - { - jobs[label="Jobs", fillcolor="#cc00ccff"]; - mqtt[label="MQTT", fillcolor="#cc00ccff", URL="@ref mqtt"]; - } - subgraph - { - node[fillcolor="#aed8a9ff"]; - rank = same; - linear_containers[label="List/Queue", URL="@ref linear_containers"]; - logging[label="Logging", URL="@ref logging"]; - static_memory[label="Static memory", URL="@ref static_memory"]; - } - jobs -> mqtt; - jobs -> linear_containers; - jobs -> logging [label=" if logging enabled", style="dashed"]; - jobs -> static_memory [label=" if static memory only", style="dashed"]; -} -@enddot - -Currently, the Jobs library has the following dependencies: -- The MQTT library for sending the messages that interact with the Jobs service. See [this page](@ref mqtt_dependencies) for the dependencies of the MQTT library, which are not shown in the graph above. -- The queue library for maintaining the data structures for managing in-progress Jobs operations. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_JOBS is not #IOT_LOG_NONE. - -In addition to the components above, the Jobs library also depends on C standard library headers. -*/ - -/** -@page jobs_design Design -@brief Architecture behind the Jobs library. - -The Jobs library uses MQTT subscriptions and publishes to communicate with the AWS IoT Jobs Service. Jobs operations such as Get Pending, Start Next, Describe, and Update use MQTT subscriptions to two MQTT topics in order to receive their accepted/rejected status, and publish to the appropriate topic for the operation. The Jobs Notify Next callback uses an MQTT subscription to receive data from the AWS IoT Jobs Service when the information for the next job in the queue changes. The Notify Pending callbacks does the same for the case when any job changes. These callbacks are run in MQTT's taskpool context. - -@section jobs_synchronous_design Synchronous Design -@image html jobs_sync_detail.png width=100% - -@section jobs_asynchronous_design Asynchronous Design -@image html jobs_async_detail.png width=100% -*/ - -/** -@page jobs_demo Demo -@brief Demonstrates the usage of the Jobs library. - -This demo provides a simple example on using the Jobs library and working with AWS IoT Jobs. - -The Jobs demo establishes an MQTT connection and registers a [Jobs NotifyNext callback](@ref jobs_function_setnotifynextcallback). It then waits for new Jobs. When a Job arrives, the demo parses the Job document and takes action based on the document content. - -@section jobs_demo_setup Demo Setup -@brief How to set up Jobs for the Jobs demo. - -Because the Jobs MQTT API does not support creating Jobs, you must create Jobs for the demo separately. We recommend using AWS CLI to create Jobs. - -1. Create a user for AWS CLI. See steps 1 and 2 of @ref guide_developer_automated_tests_jobs for more information. -2. Build and run the demo as described [here](@ref building). -3. When the following message appears, you may begin creating Jobs for the demo. -@code{sh} -/*-----------------------------------------------------------*/ - -The Jobs demo is now ready to accept Jobs. -Jobs may be created using the AWS IoT console or AWS CLI. -See the following link for more information. - -https://docs.aws.amazon.com/cli/latest/reference/iot/create-job.html - -This demo expects Job documents to have an "action" JSON key. -The following actions are currently supported: - - print - Logs a message to the local console. The Job document must also contain a "message". - For example: { "action": "print", "message": "Hello world!"} will cause - "Hello world!" to be printed on the console. - - publish - Publishes a message to an MQTT topic. The Job document must also contain a "message" and "topic". - For example: { "action": "publish", "topic": "demo/jobs", "message": "Hello world!"} will cause - "Hello world!" to be published to the topic "demo/jobs". - - exit - Exits the demo program. This program will run until { "action": "exit" } is received. - -/*-----------------------------------------------------------*/ -@endcode - -The supported actions are described above. As an example, the command to create a "publish" Job with AWS CLI is below. Replace `UniqueId` with a Job ID that is unique, and `ThingARN` with the ARN of the target Thing. - -@code{sh} -aws iot create-job \ - --job-id UniqueId \ - --targets ThingARN \ - --document '{"action":"publish","message":"Hello world!","topic":"jobsdemo/1"}' -@endcode - -This will cause the Jobs demo to publish the message `Hello world!` on the topic `jobsdemo/1`. You may view this message by subscribing to the topic in AWS IoT Console. - -When the demo finishes with a Job, it will mark the Jobs as complete. Note that completed Jobs are not automatically removed from the AWS IoT Console. The following command can be used to remove completed Jobs. Replace `UniqueId` with the Job ID. - -@code{sh} -aws iot delete-job --job-id UniqueId -@endcode - -# Demo Structure -The Jobs demo uses the asynchronous API of the Jobs library. Most of the demo is run from the NotifyNext callback. - -@image html jobs_demo.png "Jobs Demo Execution Sequence" width=80% - -@note Messages from the Jobs service (which are sent at MQTT QoS 1) may be received multiple times. The Jobs NotifyNext callback must be able to handle duplicated Job documents. -*/ - -/** -@page jobs_tests Tests -@brief Tests written for the Jobs library. - -The Jobs tests reside in the `jobs/test` directory. They are divided into the following subdirectories: -- `system`: Jobs system tests. These tests require a network connection and AWS IoT credentials. They also need an AWS account and registered Thing; see @ref jobs_system_tests_setup for instructions to configure an AWS account to run these tests. The command line option `-n` may be passed to the test executable to disable these tests. -- `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) - -See @subpage jobs_tests_config for configuration settings that change the behavior of the Jobs system tests. The Jobs unit tests require no special configuration. - -The current Jobs tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. - -@section jobs_system_tests_setup Setting up Jobs system tests -@brief How to set up the Jobs system tests. - -@pre The steps below assume basic familiarity with AWS and AWS CLI. - -The Jobs system tests require Jobs to be created with an AWS account. AWS does not provide the functionality to create Jobs using the Device API; therefore, Jobs will have to be created using another method. - -See @ref guide_developer_automated_tests_jobs for additional setup for running the Jobs tests on Travis CI. - -1. Register a Thing for the tests. This needs to be done only once.
-Follow [this guide](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html) to register a new Thing. -2. Create an IAM user for the tests. This needs to be done only once.
-Follow [this guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) to create an IAM user for the Jobs tests. - - Only programmatic access is required. - - Save the access key and secret access key for this user. - - The following policy grants the necessary permissions for this user. Replace `` and `` with your AWS region and account number, respectively; replace `` with the name of the Thing registered in step 1. -``` -{ - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": [ - "iot:CreateJob", - "iot:DeleteJob", - "iot:DeleteJobExecution" - ], - "Resource": [ - "arn:aws:iot:::job/*", - "arn:aws:iot:::thing/" - ] - } -} -``` -3. Create two Jobs for the tests. This must be done every time the system tests are run.
-At least two new jobs should be in the `QUEUED` state each time the tests are run. To create a Job, the following AWS CLI command may be used. Replace `UniqueId` with a Job ID that is unique, and `ThingARN` with the ARN of the Thing registered in step 1. The `--document` argument can be followed by any JSON string. -```sh -aws iot create-job \ - --job-id UniqueId \ - --targets ThingARN \ - --document '{"action":"print","message":"Hello world!"}' -``` -When the Jobs tests are finished, the Jobs it uses are no longer needed. Jobs can be deleted with the following command, where `UniqueId` is the Job ID used to create the Job. -```sh -aws iot delete-job --job-id UniqueId --force -``` -*/ - -/** -@configpage{jobs_tests,Jobs system tests,Test,tests} - -@section AWS_IOT_TEST_JOBS_THING_NAME -@brief The Thing Name to use in the Jobs system tests. - -Thing Names are used to manage devices with AWS IoT. No default value is provided for Thing Names, so this constant must be defined. In addition to the Thing Name, AWS IoT credentials ([root CA certificate](@ref IOT_TEST_ROOT_CA), [client certificate](@ref IOT_TEST_CLIENT_CERT), and [client certificate private key](@ref IOT_TEST_PRIVATE_KEY)) must be provided to run the system tests. The [AWS IoT policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must also be properly configured. - -@configpossible A string representing an AWS IoT Thing Name. - -@section jobs_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S -@brief The keep-alive interval to use in the Jobs system tests. - -MQTT PINGREQ packets will be sent at this interval. - -@configpossible Any positive integer.
-@configrecommended This value should be the shortest keep-alive interval supported by the connection.
-@configdefault `30` -*/ - -/** -@configpage{jobs,Jobs library} - -@section AWS_IOT_JOBS_ENABLE_ASSERTS -@brief Set this to `1` to perform sanity checks when using the Jobs library. - -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotJobs_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. - -@configpossible `0` (asserts disabled) or `1` (asserts enabled)
-@configrecommended `1` when debugging; `0` in production code.
-@configdefault `0` - -@section AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS -@brief Set the default timeout (in milliseconds) for [MQTT library](@ref mqtt_functions) called by the Jobs library. - -If the `mqttTimeout` argument of @ref jobs_function_init is `0`, the Jobs library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync, and @ref mqtt_function_publishsync to limit amount of time an MQTT function may block. - -@configpossible Any positive integer.
-@configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
-@configdefault `5000` - -@section AWS_IOT_JOBS_NOTIFY_CALLBACKS -@brief Set the maximum number of Jobs Notify callbacks of each type for each Thing. - -This setting affects the behavior of @ref jobs_function_setnotifynextcallback and @ref jobs_function_setnotifypendingcallback. There is only one Job notification topic of each type for each Thing. This setting allows multiple callbacks to be registered for the single notification topic, allowing multiple applications to respond to the notifications received. - -All registered callbacks will be invoked when a notification arrives. The order in which the callbacks are invoked may vary. Therefore, it is the responsibility of each callback to ignore Job notifications that are not intended for it. - -@configpossible Any positive integer.
-@configdefault `1` - -@section AWS_IOT_LOG_LEVEL_JOBS -@brief Set the log level of the Jobs library. - -Log messages from the Jobs library at or below this setting will be printed. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section AwsIotJobs_Assert -@brief Assertion function used when @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`. - -@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault @ref Iot_DefaultAssert if @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the Jobs library will fail to build. - -@section jobs_config_memory Memory allocation -@brief The following functions may be re-implemented for the Jobs library. -- #AwsIotJobs_MallocOperation
- @copybrief AwsIotJobs_MallocOperation -- #AwsIotJobs_FreeOperation
- @copybrief AwsIotJobs_FreeOperation -- #AwsIotJobs_MallocString
- @copybrief AwsIotJobs_MallocString -- #AwsIotJobs_FreeString
- @copybrief AwsIotJobs_FreeString -- #AwsIotJobs_MallocSubscription
- @copybrief AwsIotJobs_MallocSubscription -- #AwsIotJobs_FreeSubscription
- @copybrief AwsIotJobs_FreeSubscription - -If a custom implementation is not set for a Jobs memory allocation function, @ref Iot_DefaultMalloc will be used. If @ref Iot_DefaultMalloc are not set, the Jobs library will fail to build. - -When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for Jobs. -- @anchor AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS
- Maximum number of Jobs operations that may be in-progress at any time. Defaults to `10` if undefined. -- @anchor AWS_IOT_JOBS_SUBSCRIPTIONS AWS_IOT_JOBS_SUBSCRIPTIONS
- Maximum number of Jobs subscriptions at any time. Defaults to `2` if undefined. -*/ diff --git a/doc/lib/linear_containers.txt b/doc/lib/linear_containers.txt deleted file mode 100644 index 455117ad96..0000000000 --- a/doc/lib/linear_containers.txt +++ /dev/null @@ -1,28 +0,0 @@ -/** -@mainpage -@anchor linear_containers -@brief Linked list and queue data structures and functions.

- -This library provides linear containers, such as linked lists and queues. Linked lists and queues may hold any data type that contain an #IotLink_t member. By default, these containers do not provide thread-safety. - -Currently, linear containers are implemented with `static inline` functions and have no dependencies other than C standard library types. -*/ - -/** -@configpage{linear_containers,linear containers library} - -@section IOT_CONTAINERS_ENABLE_ASSERTS -@brief Set this to `1` to perform sanity checks when using linear containers. - -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotContainers_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. - -@configpossible `0` (asserts disabled) or `1` (asserts enabled)
-@configrecommended `1` when debugging; `0` in production code.
-@configdefault `0` - -@section IotContainers_Assert -@brief Assertion function used when @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`. - -@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault @ref Iot_DefaultAssert if @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the containers library will fail to build. -*/ diff --git a/doc/lib/logging.txt b/doc/lib/logging.txt deleted file mode 100644 index 3c2ac42d6b..0000000000 --- a/doc/lib/logging.txt +++ /dev/null @@ -1,118 +0,0 @@ -/** -@mainpage Logging -@anchor logging -@brief Generate and print log messages.

- -This library allows other libraries to generate and print log messages, which can aid in debugging. Log messages are printed by passing strings to one of the [logging functions]( @ref logging_functions). The features of this library include: -- [Configurable levels](@ref logging_constants_levels) for log messages and libraries. -- Automatic printing of log level, library name, and time with every message. The information printed with each message [may be customized](@ref IotLogConfig_t). -- A [function to print the contents of buffers](@ref logging_function_printbuffer) when using the [debug](@ref IOT_LOG_DEBUG) log level. This allows the contents of memory to be easily examined. - -@dependencies{logging,logging library} - -@dot "Logging direct dependencies" -digraph logging_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - logging[label="Logging", fillcolor="#aed8a9ff"]; - subgraph - { - rank = same; - platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; - static_memory[label="Static memory", fillcolor="#e89025ff" URL="@ref static_memory"]; - } - standard_library[label="Standard library\nstdarg, stdbool, stddef,\nstdint, stdio, string", fillcolor="#d15555ff"]; - logging -> platform_clock; - logging -> static_memory [style="dashed", label=" if static memory only"]; - logging -> standard_library; -} -@enddot - -Currently, the logging library has the following dependencies: -- The [platform clock component](@ref platform_clock) for [generating the timestring](@ref platform_clock_function_gettimestring) to print in log messages. -- When @ref IOT_STATIC_MEMORY_ONLY is `1`, the logging library depends on the [platform static memory component](@ref static_memory) to allocate buffers for log messages. When @ref IOT_STATIC_MEMORY_ONLY is `0`, the logging library will default to using the standard library's [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) functions. The logging library's memory allocation functions [may always be overridden](@ref logging_config_memory). Note that the logging library will silently discard logs if it fails to allocate memory for the message. - -@section logging_setup_use Setup and use -@brief How to set up and use the logging library. - -The file iot_logging_setup.h should be included to configure logging for a single source file. Before including iot_logging_setup.h, the constants @ref LIBRARY_LOG_LEVEL and @ref LIBRARY_LOG_NAME must be defined. - -For example, to configure all the "SAMPLE" library to print all messages below the [info](@ref IOT_LOG_INFO) log level: - -@code{c} -// Print log messages up to the "info" level. -#define LIBRARY_LOG_LEVEL IOT_LOG_INFO - -// Print library name "SAMPLE". -#define LIBRARY_LOG_NAME "SAMPLE" - -// Including this header defines the logging macros using LIBRARY_LOG_LEVEL and -// LIBRARY_LOG_NAME. -#include "iot_logging_setup.h" - -int main( void ) -{ - // After including iot_logging_setup.h, the logging functions can be used. - IotLogError( "Error." ); // Will be printed because IOT_LOG_ERROR <= LIBRARY_LOG_LEVEL - IotLogWarn( "Warning. " ); // Will be printed because IOT_LOG_WARN <= LIBRARY_LOG_LEVEL - IotLogInfo( "Info." ); // Will be printed because IOT_LOG_INFO <= LIBRARY_LOG_LEVEL - IotLogDebug( "Debug." ); // Will not be printed because IOT_LOG_DEBUG > LIBRARY_LOG_LEVEL - - return 0; -} -@endcode - -The code above will print something like the following: - -@code -[ERROR][SAMPLE][2018-01-01 12:00:00] Error. -[WARN ][SAMPLE][2018-01-01 12:00:00] Warning. -[INFO ][SAMPLE][2018-01-01 12:00:00] Info. -@endcode -*/ - -/** -@configpage{logging,logging library} - -@section LIBRARY_LOG_LEVEL -@brief The log level for a source file. - -Sets the log level for a single source file. A log message will only be printed if its level is less than this setting. - -Both this constant and @ref LIBRARY_LOG_NAME must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. - -@configpossible One of the @ref logging_constants_levels. -@note This value must be defined if iot_logging_setup.h is included. The library will not provide a default value for this setting. - -@section LIBRARY_LOG_NAME -@brief The library name printed in log messages. - -Sets the library name for a single source file. By default, all log messages contain the library name. The library name may be disabled for a single log message by setting passing an #IotLogConfig_t with [hideLibraryName](@ref IotLogConfig_t.hideLibraryName) set to `true`. - -Both this constant and @ref LIBRARY_LOG_LEVEL must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. - -@configpossible Any string. -@note This value must be defined if iot_logging_setup.h is included. The library will not provide a default value for this setting. - -@section IotLogging_Puts -@brief Logging library output function. - -The logging library calls this function to print strings. Like the standard library's [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function, this function should write a newline after the string, as log messages may not end with a newline. This setting provided the flexibility to log over different channels. For example, if no `stdout` is available, this function may be set to log over other channels such as UART or a network. - -Although this function is supposed to return an `int`, the logging library does not check its return value. Therefore, a function with no return type is also acceptable. - -@configpossible Any function with the same parameter as the standard library's [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function. Since the logging library does not check the return value of this function, the return type may differ from [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html).
-@configdefault Standard library [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) function. - -@section logging_config_memory Memory allocation -@brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the logging library. -- #IotLogging_Malloc
- @copybrief IotLogging_Malloc -- #IotLogging_Free
- @copybrief IotLogging_Free -- #IotLogging_StaticBufferSize
- @copybrief IotLogging_StaticBufferSize - -Note that the logging library will silently discard logs if it fails to allocate memory for the message. -*/ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt deleted file mode 100644 index eb1a04ab45..0000000000 --- a/doc/lib/mqtt.txt +++ /dev/null @@ -1,454 +0,0 @@ -/** -@mainpage -@anchor mqtt -@brief MQTT 3.1.1 client library. - -> MQTT stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. The design principles are to minimise network bandwidth and device resource requirements whilst also attempting to ensure reliability and some degree of assurance of delivery. These principles also turn out to make the protocol ideal of the emerging "machine-to-machine" (M2M) or "Internet of Things" world of connected devices, and for mobile applications where bandwidth and battery power are at a premium. - -Official description of MQTT from [mqtt.org](http://mqtt.org)
- -This MQTT library implements a subset of the MQTT 3.1.1 standard. Features of this library include: -- Both fully asynchronous and blocking API functions. -- Scalable performance and footprint. The [configuration settings](@ref mqtt_config) allow this library to be tailored to a system's resources. - -@attention Currently, this library does not support QoS 2 MQTT messages. - -@dependencies{mqtt,MQTT library} -@dot "MQTT direct dependencies" -digraph mqtt_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph - { - mqtt[label="MQTT", fillcolor="#cc00ccff"]; - } - subgraph - { - node[fillcolor="#aed8a9ff"]; - rank = same; - linear_containers[label="Linear containers", URL="@ref linear_containers"]; - logging[label="Logging", URL="@ref logging"]; - static_memory[label="Static memory", URL="@ref static_memory"]; - } - subgraph - { - rank = same; - task_pool[label="Task pool", fillcolor="#e89025ff", URL="@ref taskpool"]; - platform_network[label="Network", fillcolor="#e89025ff", URL="@ref platform_network"]; - } - mqtt -> linear_containers; - mqtt -> logging [label=" if logging enabled", style="dashed"]; - mqtt -> task_pool; - mqtt -> platform_network; - mqtt -> static_memory [label=" if static memory only", style="dashed"]; - logging -> static_memory [label=" if static memory only", style="dashed"]; -} -@enddot - -Currently, the MQTT library has the following dependencies: -- The linear containers library for maintaining the data structures for managing in-progress MQTT operations. -- The logging library may be used if @ref IOT_LOG_LEVEL_MQTT is not #IOT_LOG_NONE. -- The task pool to submit background jobs. The MQTT library does not create or manage any threads, but relies on at least one thread being available in the task pool. - -In addition to the components above, the MQTT library also depends on C standard library headers and the [platform layer](@ref platform). - -@section mqtt_memory Memory requirements -@brief Memory requirements of the MQTT library. - -@subsection mqtt_memory_code Code size -@brief Code size of the MQTT library. - -The following measurements were taken with arm-none-eabi-gcc v9.2.1 (October 2019) with the [MQTT library from January 2020](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/f1a8b698b1d2fd5b8475b11a16d399181c1d867f). These values are rough estimates not tuned for any specific ARM processor. - -Minimal Size Build
-No logging, asserts, or diagnostic information. Compiled using the ARM Thumb instruction set (`-mthumb`) and optimized for size (`-Os`). Values are in bytes. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
objecttextdatabss
iot_mqtt_api270304
iot_mqtt_network88400
iot_mqtt_operation177300
iot_mqtt_serialize260640
iot_mqtt_subscription124400
iot_mqtt_validate44400
total965444
- -Debug Build
-All logging, asserts, and diagnostic information enabled. Debugging symbols available. Compiled using the ARM Thumb instruction set (`-mthumb`). Values are in bytes. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
objecttextdatabss
iot_mqtt_api1189704
iot_mqtt_network541200
iot_mqtt_operation1048400
iot_mqtt_serialize14454280
iot_mqtt_subscription388500
iot_mqtt_validate433000
total50462284
- -@subsection mqtt_memory_runtime Runtime memory requirements -@brief Runtime memory requirements of the MQTT library. - -The values given below do not consider any dependencies, such as the task pool or TLS stack. - -Approximate sizes of MQTT objects are given below. Note that the exact size depends on how certain platform types (such as mutexes) are implemented.
- - - - - - - - - - - - - -
Each MQTT connection250 bytes
Each MQTT operation150 bytes, plus the length of a topic name and message if publishing
Each MQTT subscription70 bytes, plus the length of the subscription topic filter
- -For example, consider an application that does the following: -1. Establishes an MQTT connection. -2. Subscribes to a topic. -3. Publishes a message to a topic. -4. Unsubscribes from a topic. -5. Closes the connection. - -This application is expected to have a peak runtime memory usage of about 600 bytes (not counting any usage by dependencies or the TLS stack). - -By default, this memory would be allocated from the heap using the [configured memory allocation functions](@ref mqtt_config_memory). If @ref IOT_STATIC_MEMORY_ONLY is defined to `1`, then the memory will be allocated at compile-time. - -@image html mqtt_memory_example.png width=80% -*/ - -/** -@page mqtt_design Design -@brief Architecture behind the MQTT library. - -@section mqtt_design_operations Asynchronous and synchronous operations -@brief This library provides both asynchronous and synchronous MQTT operations. Asynchronous operation functions end with Async, and their synchronous variants end with Sync. - -For example, @ref mqtt_function_publishasync and @ref mqtt_function_publishsync are the respective asynchronous and synchronous MQTT PUBLISH functions. The CONNECT and DISCONNECT operations only have synchronous functions. - -Asynchronous functions return immediately to the calling thread after allocating appropriate resources. The application thread can then continue executing while the MQTT operation is processed in the background. Once the MQTT operation is complete, the MQTT library will invoke a callback function provided by the application. The function @ref mqtt_function_wait can be used to wait for an asynchronous operation's completion. It can also be used to cancel asynchronous operations if passed a timeout of `0`. - -Synchronous functions block their calling thread until the MQTT operation is complete. Synchronous functions take a timeout parameter; if the operation does not complete within this timeout, the function returns @ref IOT_MQTT_TIMEOUT and cancels the operation. Synchronous operations do not use a callback; they return a value that represents the result of the operation to the calling thread. - -@section mqtt_design_asyncoperation Operation diagram -@brief The following diagram shows the workflow of an operation. - -MQTT relies on the [task pool library](@ref taskpool) to process asynchronous MQTT operations in the background. Each MQTT API [function](@ref mqtt_functions) allocates the required resources, then schedules a background task to send the MQTT packet and receive the server's response. Incoming responses are received through a [network receive callback](@ref platform_network_function_receivecallback), which schedules another job to process the response. - -Synchronous operations are implemented as a call to the corresponding asynchronous operation. The synchronous function then waits on a semaphore until it receives a notification of completion. The workflow of a synchronous operation is otherwise identical to an asynchronous operation. - -Some operations, such as QoS 1 PUBLISHes, may be retried. See @ref IotMqttPublishInfo_t for a description of the retry strategy. - -@image html mqtt_design_typicaloperation.png width=80% - -@section mqtt_design_subscriptions Subscriptions -@brief This section provides a description of how the library handles subscriptions. - -MQTT subscriptions are added with @ref mqtt_function_subscribeasync or @ref mqtt_function_subscribesync; subscriptions are removed with @ref mqtt_function_unsubscribesync or @ref mqtt_function_unsubscribeasync. Subscriptions are associated with a callback function that will be invoked by the library every time a message matching the associated topic filter is received. For example, if a connection has subscriptions for `#` (which matches everything) and `test`, and a message is received on `test`, the callback functions for both `#` and `test` will be invoked. Callbacks are invoked from the context of a background task pool threads. Because incoming messages are asynchronous (may arrive at any time), subscription callbacks are also asynchronous. - -This library supports the use of MQTT persistent sessions. Persistent sessions cause the MQTT server to store subscriptions and undelivered messages. When re-establishing a persistent session, the client should set @ref IotMqttConnectInfo_t.pPreviousSubscriptions and @ref IotMqttConnectInfo_t.previousSubscriptionCount to restore a list of sessions that were present in the persistent session. Setting these members does not send an MQTT SUBSCRIBE packet to the server, so they may only be used with topics that are active on the server. - -@section mqtt_design_keepalive Periodic keep-alive -@brief This section provides a description of the workflow of periodic keep-alive. - -The MQTT standard specifies a keep-alive mechanism to detect half-open or otherwise unusable network connections. An MQTT client will send periodic ping requests (PINGREQ) to the server if the connection is idle. The MQTT server must respond to ping requests with a ping response (PINGRESP). - -The standard does not specify how long the server has to respond to a ping request, noting only a "reasonable amount of time". In this library, the amount of time a server has to respond to a ping request is set with @ref IOT_MQTT_RESPONSE_WAIT_MS. - -The PINGREQ is allocated with the MQTT connection when it is created. A `failure` flag is also set to `0`. For simplicity, the diagram below only shows the portions of MQTT CONNECT relevant for keep-alive. - -Once MQTT CONNECT returns to the application, the keep-alive is processed entirely through background tasks. The period of the keep-alive is controlled with @ref IotMqttConnectInfo_t.keepAliveSeconds (represented by `keepAliveSec` in the diagram below). Every `keepAliveSec`, the following code is run by a background task in the task pool. -1. If another MQTT packet was sent within the keep-alive period, don't send the PINGREQ and reschedule for the next keep-alive period. For simplicity, this is not shown in the diagram below. -2. The `failure` flag is set to `1`. -3. A PINGREQ is sent to the server. -4. If the connection is alive, the server will respond with a PINGRESP. When the client receives the PINGRESP, it sets `failure` to `0`. -5. After @ref IOT_MQTT_RESPONSE_WAIT_MS, the client checks the `failure` flag. If `failure=0`, then a PINGRESP was received and the next PINGREQ is scheduled. Otherwise, if `failure=1`, then no PINGRESP was received and the connection terminates. -*/ - -/** -@page mqtt_demo Demo -@brief The MQTT demo demonstrates usage of the MQTT library. - -The MQTT demo demonstrates the subscribe-publish workflow of MQTT. After subscribing to multiple topic filters, it publishes [bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) of data to various topic names. The demo then waits for all messages in a burst to be received on a topic filter. As each message arrives, the demo publishes an acknowledgement message back to the MQTT server. It repeats this cycle @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times. - -Messages in this demo are sent at QoS 1, which the MQTT spec guarantees at least once delivery. However, for practical purposes, QoS 1 messages are subject to the retry policy described [here](@ref IotMqttPublishInfo_t). A QoS 1 message may fail to be delivered if all its retries are exhausted. - -@image html mqtt_demo.png "MQTT Demo Workflow" width=80% - -See @subpage mqtt_demo_config for configuration settings that change the behavior of the demo. - -The main MQTT demo file is iot_demo_mqtt.c, which contains platform-independent code. See @ref building_demo for instructions on building the MQTT demo. -*/ - -/** -@configpage{mqtt_demo,MQTT demo,Demo,demos} - -@section IOT_DEMO_MQTT_TOPIC_PREFIX -@brief The string prepended to topic filters in the demo. - -The string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the demo. - -@configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
-@configdefault `"iotmqttdemo"` - -@section IOT_DEMO_MQTT_PUBLISH_BURST_SIZE -@brief The number of messages published in each burst. - -Messages in a burst are rapidly published. After a complete burst is published, the demo waits for the messages to be received on a subscription topic filter. This value may be increased for higher throughput, at the expense of an increased chance of dropped messages. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. - -@configpossible Any positive integer.
-@configdefault `10` - -@section IOT_DEMO_MQTT_PUBLISH_BURST_COUNT -@brief The number of [publish bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) in this demo. - -Each burst will rapidly publish @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE messages. After publishing, the demo will wait for the published messages to be received on a subscription topic filter. - -This setting can be increased for a longer demo. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. - -@configpossible Any positive integer.
-@configdefault `10` -*/ - -/** -@page mqtt_tests Tests -@brief Tests written for the MQTT library. - -The MQTT tests reside in the `mqtt/test` directory. They are divided into the following subdirectories: -- `access`: Helper files that allow access to internal variables and functions of the MQTT library. -- `mock`: Simulates network responses to MQTT packets; used for testing other libraries that depend on MQTT. -- `system`: MQTT system and stress tests. These tests require a network connection. Stress tests may run for a long time, so they are not run unless the command line option `-l` is given to the test executable. -- `unit`: MQTT unit tests. These tests do not require a network connection. - -See @subpage mqtt_tests_config for configuration settings that change the behavior of the tests. - -The current MQTT tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. -*/ - -/** -@configpage{mqtt_tests,MQTT tests,Test,tests} - -@section IOT_TEST_MQTT_CLIENT_IDENTIFIER -@brief The MQTT client identifier to use for the tests. - -No two clients may connect using the same client identifier simultaneously. If this setting is undefined, the tests will generate a unique client identifier to use. - -@configpossible Any string representing an MQTT client identifier. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters.
-@configdefault The tests will generate a unique client identifier if this setting is undefined. - -@section IOT_TEST_MQTT_MOSQUITTO -@brief Test the MQTT library against the [a Mosquitto test server](https://test.mosquitto.org/). - -When this setting is `1`, the MQTT tests will be built to test against an unsecured [a Mosquitto test server](https://test.mosquitto.org/). This allows the MQTT library to be tested against a fully-compliant MQTT server. Because the connection is unsecured, no credentials are needed for testing against Mosquitto. A Mosquitto test server may also be installed locally. - -@configpossible `0` (use AWS IoT MQTT server) or `1` (use public Mosquitto server)
-@configdefault `0` - -
The settings below only affect the [system](@ref iot_tests_mqtt_system.c) tests.
- -@section IOT_TEST_MQTT_TIMEOUT_MS -@brief Timeout in milliseconds for MQTT operations. - -This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait (and similar functions requiring a timeout). Ensure that this value is large enough to accommodate delays caused by the network. - -@configpossible Any non-negative integer.
-@configrecommended This setting should be at least `1000`.
-@configdefault `5000` - -@section IOT_TEST_MQTT_CONNECT_RETRY_COUNT -@brief The number of connection attempts for the MQTT system tests. - -The MQTT system tests require a network connection to an MQTT server. If the network is unreliable, this setting may be used to enable retries when connecting to the MQTT server. - -This value represents a limit on the number of retries. When set to 1, the tests will perform one connection attempt. When set to a value greater than 1, the tests will attempt to reconnect with an exponential back-off strategy, up to the limit specified by this setting. - -@configpossible Any integer of at least 1.
-@configdefault `1` -*/ - -/** -@configpage{mqtt,MQTT library} - -@section IOT_MQTT_ENABLE_ASSERTS -@brief Set this to `1` to perform sanity checks when using the MQTT library. - -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotMqtt_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. - -@configpossible `0` (asserts disabled) or `1` (asserts enabled)
-@configrecommended `1` when debugging; `0` in production code.
-@configdefault `0` - -@section AWS_IOT_MQTT_ENABLE_METRICS -@brief Set this to `1` to enable anonymous metrics reporting to AWS IoT. - -Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SDK name and version are reported; no personal or identifying information is reported. - -@configpossible `0` (metrics reporting disabled) or `1` (metrics reporting enabled)
-@configrecommended `1`
-@configdefault `1` -@note This setting is only in effect for [MQTT connections with AWS IoT](@ref IotMqttConnectInfo_t.awsIotMqttMode). - -@section IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES -@brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. - -Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on FreeRTOS. See #IotMqttSerializer_t for a list of functions that can be overridden. If this setting is `1`, the [initialization](@ref mqtt_function_init) and [cleanup](@ref mqtt_function_cleanup) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default initialization and cleanup functions. They must have the following signatures: -@code{c} -#include - -// Returns true on success and false on failure. -bool _IotMqtt_InitSerializeAdditional( void ); -void _IotMqtt_CleanupSerializeAdditional( void ); -@endcode - -@configpossible `0` (serializer overrides disabled) or `1` (serializer overrides enabled)
-@configrecommended The default value is strongly recommended.
-@configdefault The default value of this setting depends on the platform. For example, when running on FreeRTOS with BLE support, this setting will be automatically enabled. Conversely, when running on Linux, this setting will be disabled. - -@section IOT_LOG_LEVEL_MQTT -@brief Set the log level of the MQTT library. - -Log messages from the MQTT library at or below this setting will be printed. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section IOT_MQTT_RESPONSE_WAIT_MS -@brief A "reasonable amount of time" to wait for keep-alive responses from the MQTT server. - -The MQTT spec states that if a response to a keep-alive request is not received within a "reasonable amount of time", the network connection should be closed. Since the meaning of "reasonable" depends heavily on use case, the amount of time to wait for keep-alive responses is defined by this setting. This setting is also used as the amount of time to wait for a final PUBACK to a QoS 1 PUBLISH message before returning #IOT_MQTT_RETRY_NO_RESPONSE. See #IotMqttPublishInfo_t for a description of QoS 1 PUBLISH retries. - -@configpossible Any positive integer.
-@configdefault `1000` - -@section IOT_MQTT_RETRY_MS_CEILING -@brief Controls the maximum [retry interval](@ref IotMqttPublishInfo_t.retryMs) of QoS 1 PUBLISH retransmissions. - -QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. The interval of time between retransmissions increases exponentially until @ref IOT_MQTT_RETRY_MS_CEILING (or the [retransmission limit)](@ref IotMqttPublishInfo_t.retryLimit) is reached, then increases by @ref IOT_MQTT_RETRY_MS_CEILING until the [retransmission limit](@ref IotMqttPublishInfo_t.retryLimit) is reached. - -@see #IotMqttPublishInfo_t for a detailed description of the QoS 1 PUBLISH retransmission strategy. - -@configpossible Any positive integer.
-@configdefault `60000` - -@section IotMqtt_Assert -@brief Assertion function used when @ref IOT_MQTT_ENABLE_ASSERTS is `1`. - -@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault @ref Iot_DefaultAssert if @ref IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the MQTT library will fail to build. - -@section mqtt_config_memory Memory allocation -@brief The following functions may be re-implemented for the MQTT library. -- #IotMqtt_MallocConnection
- @copybrief IotMqtt_MallocConnection -- #IotMqtt_FreeConnection
- @copybrief IotMqtt_FreeConnection -- #IotMqtt_MallocMessage
- @copybrief IotMqtt_MallocMessage -- #IotMqtt_FreeMessage
- @copybrief IotMqtt_FreeMessage -- #IotMqtt_MallocOperation
- @copybrief IotMqtt_MallocOperation -- #IotMqtt_FreeOperation
- @copybrief IotMqtt_FreeOperation -- #IotMqtt_MallocSubscription
- @copybrief IotMqtt_MallocSubscription -- #IotMqtt_FreeSubscription
- @copybrief IotMqtt_FreeSubscription - -If a custom implementation is not set for an MQTT memory allocation function, @ref Iot_DefaultMalloc will be used. If @ref Iot_DefaultMalloc are not set, the MQTT library will fail to build. - -When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for MQTT. -- @anchor IOT_MQTT_CONNECTIONS IOT_MQTT_CONNECTIONS
- Number of MQTT connections that may be opened. Defaults to `1` if undefined. -- @anchor IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS
- Maximum number of MQTT operations that may be in-progress at any time. Defaults to `10` if undefined. -- @anchor IOT_MQTT_SUBSCRIPTIONS IOT_MQTT_SUBSCRIPTIONS
- Maximum number of MQTT subscriptions at any time. Defaults to `8` if undefined. -*/ diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt deleted file mode 100644 index eee32b2aa8..0000000000 --- a/doc/lib/platform.txt +++ /dev/null @@ -1,286 +0,0 @@ -/** -@mainpage -@anchor platform -@brief The platform layer provides portability across different operating systems.

- -All system calls (including networking) used in this SDK's libraries go through a lightweight platform layer. The functions of the platform layer are intended to be easily implementable on a wide variety of operating systems. The current platform layer has the following components: -- @ref platform_clock
- @copybrief platform_clock -- @ref platform_threads
- @copybrief platform_threads -- @ref platform_network
- @copybrief platform_network -- @ref platform_metrics
- Only used by [Device Defender](@ref defender); does not need to be ported if not using Defender.
- @copybrief platform_metrics
-- @ref platform_atomic
- Porting is optional, as a generic implementation is provided.
- @copybrief platform_atomic - -The following implementations are provided with this SDK as porting samples: -Component | Supported platforms ---------- | ------------------- -@ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) -@ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) -@ref platform_network | [mbed TLS](https://tls.mbed.org/)
[Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX) -@ref platform_metrics | Sample implementation using the @ref platform_network abstraction.
This implementation is not intended for production use. -@ref platform_atomic | GNU compilers (gcc/clang)
Generic implementation using a @ref platform_threads mutex. -*/ - -/** -@configpage{platform,platform layer} - -@section IOT_ATOMIC_USE_PORT -@brief Use a custom atomics port, provided in the header `iot_atomic_port.h`. - -Set this to `1` to use a custom atomics port in place of any atomics port provided with the SDK. The custom atomics port should be implemented in `platform/include/atomic/iot_atomic_port.h`. - -See @ref platform_atomic and [atomics functions](@ref platform_atomic_functions) for details on writing an atomics port. - -@configpossible `0` (no atomic port) or `1` (use atomic port)
-@configdefault `0` - -@section IOT_LOG_LEVEL_PLATFORM -@brief Set the log level of all platform components except the [networking component](@ref platform_network). - -This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the platform layer components that it affects. All log messages with a level at or below this setting will be printed. The [platform networking component](@ref platform_network) is generally more verbose than others, so its logging is controlled separately by @ref IOT_LOG_LEVEL_NETWORK. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section IOT_LOG_LEVEL_NETWORK -@brief Set the log level of the [platform networking component](@ref platform_network). - -This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the [platform networking component](@ref platform_network). All log messages with a level at or below this setting will be printed. See @ref IOT_LOG_LEVEL_PLATFORM to set the log level of other platform components. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section platform_config_memory Memory allocation -@brief Memory allocation function overrides for the platform layer. - -Some platform layers are not affected by @ref IOT_STATIC_MEMORY_ONLY. Currently, the following platform implementations require memory allocation: -- POSIX
- This implementation is not affected by @ref IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - - `IotThreads_Malloc` and `IotThreads_Free`. - - `IotNetwork_Malloc` and `IotNetwork_Free`. - -@section platform_config_posixheaders POSIX headers -@brief The POSIX platform layer allows the standard POSIX header includes to be overridden. Overrides only affect the POSIX platform layer. - -Any substitute headers are expected to provide the same functionality as the standard POSIX headers. The POSIX header overrides should only be used if the system's headers do not follow the standard names for POSIX headers. The POSIX headers may be overridden by defining the following settings: -Standard name | Setting -------------- | ------- -[errno.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html) | `POSIX_ERRNO_HEADER` -[limits.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) | `POSIX_LIMITS_HEADER` -[pthread.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) | `POSIX_PTHREAD_HEADER` -[signal.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html) | `POSIX_SIGNAL_HEADER` -[semaphore.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html) | `POSIX_SEMAPHORE_HEADER` -[time.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html) | `POSIX_TIME_HEADER` -[sys/types.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html) | `POSIX_TYPES_HEADER` - -Example:
-To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HEADER` setting should be defined. -@code{c} -#define POSIX_ERRNO_HEADER "custom_errno.h" -@endcode - -@configpossible Strings representing file names. These files must be in the compiler's include path.
-@configdefault Standard POSIX header names. -*/ - -/** -@page platform_clock Clock -@brief @copybrief iot_clock.h - -The platform clock component provides other libraries with functions relating to timers and clocks. It interfaces directly with the operation system to provide: -- Clocks for reading the current time. -- Timers that create a notification thread when they expire. - -@dependencies{platform_clock,platform clock component} -@dot "Clock direct dependencies" -digraph clock_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff" URL="@ref logging"]; } - subgraph - { - platform_clock[label="Clock", fillcolor="#e89025ff"]; - } - subgraph - { - rank = same; - rankdir = LR; - operating_system[label="Operating system", fillcolor="#999999ff"] - standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; - } - platform_clock -> operating_system; - platform_clock -> standard_library; - platform_clock -> logging [label=" if logging enabled", style="dashed"]; -} -@enddot - -Currently, the platform clock component has the following dependencies: -- The operating system must provide the necessary APIs to implement the clock component's functions. -- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. -*/ - -/** -@page platform_threads Thread Management -@brief @copybrief iot_threads.h - -The platform thread management component provides other libraries with functions relating to threading and synchronization. It interfaces directly with the operating system to provide: -- A function to create new threads. -- Synchronization mechanisms such as [mutexes](@ref IotMutex_t) and [counting semaphores](@ref IotSemaphore_t). - -@dependencies{platform_threads,platform thread management component} -@dot "Thread management direct dependencies" -digraph threads_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff" URL="@ref logging"]; } - subgraph - { - platform_threads[label="Thread management", fillcolor="#e89025ff"]; - } - subgraph - { - rank = same; - operating_system[label="Operating system", fillcolor="#999999ff"] - standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; - } - platform_threads -> operating_system; - platform_threads -> standard_library; - platform_threads -> logging [label=" if logging enabled", style="dashed"]; -} -@enddot - -Currently, the platform thread management component has the following dependencies: -- The operating system must provide the necessary APIs to implement the [thread management functions](@ref platform_threads_functions). -- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. -*/ - -/** -@page platform_network Networking -@brief @copybrief iot_network.h - -The platform networking component provides other libraries with an abstraction for interacting with the network through an #IotNetworkInterface_t. Libraries that require the network will request an #IotNetworkInterface_t as a parameter and use those function pointers to access the network. This allows libraries to use different network stacks simultaneously. - -@dependencies{platform_network,platform networking component} -@dot "Networking direct dependencies" -digraph network_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff", URL="@ref logging"]; } - subgraph - { - rank = same; - platform_network[label="Networking", fillcolor="#e89025ff"]; - } - subgraph - { - rank = same; - rankdir = LR; - operating_system[label="Operating system", fillcolor="#999999ff"]; - security_library[label="Security library", fillcolor="#999999ff"]; - standard_library[label="Standard library", fillcolor="#d15555ff"]; - } - platform_network -> operating_system; - platform_network -> security_library [label=" secured connection", style="dashed"]; - platform_network -> standard_library; - platform_network -> logging [label=" if logging enabled", style="dashed"]; - security_library -> operating_system; -} -@enddot - -Functions should be implemented against the system's network stack to match the signatures given in an #IotNetworkInterface_t. -- The operating system must provide the necessary networking APIs, such as a sockets API. -- A third-party security library is needed to encrypt secured connections. -- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not @ref IOT_LOG_NONE. -*/ - -/** -@page platform_metrics Metrics -@brief @copybrief iot_metrics.h - -Device Defender reports metrics to monitor devices. This component of the platform layer is only required by Device Defender. It does not need to be implemented if Device Defender is not used. - -@dependencies{platform_metrics,platform metrics component} -@dot "Metrics direct dependencies" -digraph metrics_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph - { - platform_metrics[label="Metrics", fillcolor="#e89025ff"]; - } - subgraph - { - rank = same; - rankdir = LR; - operating_system[label="Operating system", fillcolor="#999999ff"] - standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; - } - platform_metrics -> operating_system; - platform_metrics -> standard_library; -} -@enddot - -The platform metrics component is meant to query its data directory from the operating system. - -@note Platform metrics implementations are generally not portable, since they depend on non-portable operating system APIs. Because maintaining OS-specific implementations is beyond the scope of this SDK, the provided metrics implementation is a sample that calls in to other platform components, instead of the operating system. -*/ - -/** -@page platform_atomic Atomics -@brief Atomic operations: small inlined functions used for atomically manipulating memory. - -Unlike the other components of the platform layer, which rely on APIs provided by the host operating system, the atomics component relies on facilities provided by compilers. Many compilers, particularly recent versions that support C11 atomic features, provide compiler intrinsics for atomic operations. The header `platform/include/iot_atomic.h` will select an atomic port to use based on the detected compiler. If no suitable atomic port is available, then a generic atomic port will be used. The generic atomic port is slower than native compiler ports, but will work on all systems. - -To provide a new compiler port, the preprocessor constant @ref IOT_ATOMIC_USE_PORT should be defined to `1` in the [config file](@ref global_config). When @ref IOT_ATOMIC_USE_PORT is `1`, a new compiler port should be implemented in a file named `iot_atomic_port.h`. This file should be created in `platform/include/atomic/`, as it will be included with the directive @c #`include "atomic/iot_atomic_port.h"`. - -@dependencies{platform_atomic,platform atomics component} -@dot "Atomics direct dependencies" -digraph atomic_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph - { - platform_atomic[label="Atomics", fillcolor="#e89025ff"]; - } - subgraph - { - rank = same; - rankdir = LR; - compiler[label="Compiler", fillcolor="#999999ff"] - } - platform_atomic -> compiler; -} -@enddot -*/ - -/** -@handles{platform,platform layer} -@enums{platform,platform layer} -@paramstructs{platform, platform layer} -*/ - -/** -@functionspage{platform,platform layer} - -- @subpage platform_clock_functions
- @copybrief platform_clock_functions -- @subpage platform_network_functions
- @copybrief platform_network_functions -- @subpage platform_threads_functions
- @copybrief platform_threads_functions -- @subpage platform_metrics_functions
- @copybrief platform_metrics_functions -- @subpage platform_atomic_functions
- @copybrief platform_atomic_functions -*/ diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt deleted file mode 100644 index 75685320f7..0000000000 --- a/doc/lib/shadow.txt +++ /dev/null @@ -1,186 +0,0 @@ -/** -@mainpage -@anchor shadow -@brief AWS IoT Device Shadow library. - -> A device's shadow is a JSON document that is used to store and retrieve current state information for a device. The Device Shadow service maintains a shadow for each device you connect to AWS IoT. You can use the shadow to get and set the state of a device over MQTT or HTTP, regardless of whether the device is connected to the Internet. Each device's shadow is uniquely identified by the name of the corresponding thing. - -Description of Device Shadows from [AWS IoT documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-device-shadows.html)
- -Thing Shadows are the always-available device state in the AWS cloud. They are stored as JSON documents, and available via AWS even if the associated device goes offline. Common use cases for Thing Shadows include backing up device state, or sending commands to devices. - -This library provides an API for interacting with AWS IoT Thing Shadows. Features of this library include: -- Both fully asynchronous and blocking API functions. -- API functions for modifying Thing Shadows and for registering notifications of a Thing Shadow change. - -@dependencies{shadow,Shadow library} -@dot "Shadow direct dependencies" -digraph shadow_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph - { - shadow[label="Shadow", fillcolor="#cc00ccff"]; - mqtt[label="MQTT", fillcolor="#cc00ccff", URL="@ref mqtt"]; - } - subgraph - { - node[fillcolor="#aed8a9ff"]; - rank = same; - linear_containers[label="List/Queue", URL="@ref linear_containers"]; - logging[label="Logging", URL="@ref logging"]; - static_memory[label="Static memory", URL="@ref static_memory"]; - } - shadow -> mqtt; - shadow -> linear_containers; - shadow -> logging [label=" if logging enabled", style="dashed"]; - shadow -> static_memory [label=" if static memory only", style="dashed"]; -} -@enddot - -Currently, the Shadow library has the following dependencies: -- The MQTT library for sending the messages that interact with the Thing Shadow service. See [this page](@ref mqtt_dependencies) for the dependencies of the MQTT library, which are not shown in the graph above. -- The queue library for maintaining the data structures for managing in-progress Shadow operations. -- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_SHADOW is not #IOT_LOG_NONE. - -In addition to the components above, the Shadow library also depends on C standard library headers. -*/ - -/** -@page shadow_design Design -@brief Architecture behind the Shadow library. - -Shadow library uses MQTT subscriptions and MQTT publishes for communicating with the AWS IoT Shadow Service. Shadow operations such as Shadow Delete, Shadow Get, and Shadow Update uses MQTT subscriptions to two MQTT topics to know the accepted or rejected status of the follwing MQTT publish and then publishes to the topic for the Shadow operation. Shadow Updated Callback and Shadow Delta Callback uses MQTT subscriptions to receive data from the AWS IoT Shadow Service. The callbacks will be run in MQTT's taskpool context. - -@section Synchronous_Design Synchronous Design -@image html shadow_sync_opertation_detail.png width=100% - -@section Asynchronous_Design Asynchronous Design -@image html shadow_async_opertation_detail.png width=100% -*/ - -/** -@page shadow_demo Demo -@brief The Shadow demo demonstrates usage of the Shadow library. - -The demo program uses [Shadow updates](@ref shadow_function_updateasync) and [delta callbacks](@ref shadow_function_setdeltacallback) to simulate toggling a remote device's state. It sends a Shadow update with a new desired state and waits for the device to change its reported state in response to the new desired state. In addition, a [Shadow updated callback](@ref shadow_function_setupdatedcallback) is used to print the changing Shadow states. - -See @subpage shadow_demo_config for configuration settings that change the behavior of the demo. - -The diagram below shows the workflow of the Shadow demo. The Shadow demo runs on an established MQTT connection; MQTT connection setup and teardown are not shown. - -@image html shadow_demo.png "Shadow Demo Workflow" width=80% -*/ - -/** -@configpage{shadow_demo,Shadow demo,Demo,demos} - -@section AWS_IOT_DEMO_SHADOW_UPDATE_COUNT -@brief The number of periodic Shadow updates to send in the demo. - -The Shadow demo sends a new update every @ref AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS milliseconds. Each update contains a new desired state, which causes a Shadow delta document to be generated and sent to the device. The device will respond to the delta document by changing its state and reporting its new state. - -@configpossible Any positive integer.
-@configdefault `20` - -@section AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS -@brief How often (in milliseconds) to send a periodic Shadow update. - -This value may be `0`, which causes a new Shadow update to be sent as soon as the device responds to the Shadow delta document. The total runtime of the demo will be @ref AWS_IOT_DEMO_SHADOW_UPDATE_COUNT `*` @ref AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS milliseconds. - -@configpossible Any non-negative integer.
-@configdefault `3000` -*/ - -/** -@page shadow_tests Tests -@brief Tests written for the Shadow library. - -The Shadow tests reside in the `shadow/test` directory. They are divided into the following subdirectories: -- `system`: Shadow system tests. These tests require a network connection and AWS IoT credentials. The command line option `-n` may be passed to the test executable to disable these tests. -- `unit`: Shadow unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) - -See @subpage shadow_tests_config for configuration settings that change the behavior of the Shadow system tests. The Shadow unit tests require no special configuration. - -The current Shadow tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. -*/ - -/** -@configpage{shadow_tests,Shadow system tests,Test,tests} - -@section AWS_IOT_TEST_SHADOW_THING_NAME -@brief The Thing Name to use in the Shadow system tests. - -Thing Names are used to manage devices with AWS IoT. No default value is provided for Thing Names, so this constant must be defined. In addition to the Thing Name, AWS IoT credentials ([root CA certificate](@ref IOT_TEST_ROOT_CA), [client certificate](@ref IOT_TEST_CLIENT_CERT), and [client certificate private key](@ref IOT_TEST_PRIVATE_KEY)) must be provided to run the system tests. The [AWS IoT policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must also be properly configured. - -@configpossible A string representing an AWS IoT Thing Name. - -@section shadow_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S -@brief The keep-alive interval to use in the Shadow system tests. - -MQTT PINGREQ packets will be sent at this interval. - -@configpossible Any positive integer.
-@configrecommended This value should be the shortest keep-alive interval supported by the connection.
-@configdefault `30` -*/ - -/** -@configpage{shadow,Shadow library} - -@section AWS_IOT_SHADOW_ENABLE_ASSERTS -@brief Set this to `1` to perform sanity checks when using the Shadow library. - -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotShadow_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. - -@configpossible `0` (asserts disabled) or `1` (asserts enabled)
-@configrecommended `1` when debugging; `0` in production code.
-@configdefault `0` - -@section AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS -@brief Set the default timeout (in milliseconds) for [MQTT library](@ref mqtt_functions) called by the Shadow library. - -If the `mqttTimeout` argument of @ref shadow_function_init is `0`, the Shadow library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync, and @ref mqtt_function_publishsync to limit amount of time an MQTT function may block. - -@configpossible Any positive integer.
-@configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
-@configdefault `5000` - -@section AWS_IOT_LOG_LEVEL_SHADOW -@brief Set the log level of the Shadow library. - -Log messages from the Shadow library at or below this setting will be printed. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section AwsIotShadow_Assert -@brief Assertion function used when @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`. - -@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault @ref Iot_DefaultAssert if @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the Shadow library will fail to build. - -@section shadow_config_memory Memory allocation -@brief The following functions may be re-implemented for the Shadow library. -- #AwsIotShadow_MallocOperation
- @copybrief AwsIotShadow_MallocOperation -- #AwsIotShadow_FreeOperation
- @copybrief AwsIotShadow_FreeOperation -- #AwsIotShadow_MallocString
- @copybrief AwsIotShadow_MallocString -- #AwsIotShadow_FreeString
- @copybrief AwsIotShadow_FreeString -- #AwsIotShadow_MallocSubscription
- @copybrief AwsIotShadow_MallocSubscription -- #AwsIotShadow_FreeSubscription
- @copybrief AwsIotShadow_FreeSubscription - -If a custom implementation is not set for a Shadow memory allocation function, @ref Iot_DefaultMalloc will be used. If @ref Iot_DefaultMalloc are not set, the Shadow library will fail to build. - -When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for Shadow. -- @anchor AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS
- Maximum number of Shadow operations that may be in-progress at any time. Defaults to `10` if undefined. -- @anchor AWS_IOT_SHADOW_SUBSCRIPTIONS AWS_IOT_SHADOW_SUBSCRIPTIONS
- Maximum number of Shadow subscriptions at any time. Defaults to `2` if undefined. -*/ diff --git a/doc/lib/static_memory.txt b/doc/lib/static_memory.txt deleted file mode 100644 index f333c2e4d2..0000000000 --- a/doc/lib/static_memory.txt +++ /dev/null @@ -1,44 +0,0 @@ -/** -@mainpage -@anchor static_memory -@brief @copybrief iot_static_memory.h

- -The static memory component manages statically-allocated buffers for other libraries that are used instead of dynamic memory allocation when @ref IOT_STATIC_MEMORY_ONLY is `1`. Using static memory only does not guarantee that memory allocation will always succeed; it's possible for all statically-allocated buffers to be in-use. However, static memory only can guarantee the availability of at least a certain amount of resources. Because space must be reserved for statically-allocated buffers, binaries compiled with static memory only will be larger. - -This component primarily provides functions for libraries to manage static buffers, @ref static_memory_function_findfree and @ref static_memory_function_returninuse. By itself it provides "message buffers", intended to hold strings, such as log messages or bytes transmitted over a network. - -@dependencies{static_memory,static memory component} -@dot "Static memory direct dependencies" -digraph static_memory_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - static_memory[label="Static memory", fillcolor="#aed8a9ff"]; - subgraph - { - rank = same; - platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; - standard_library[label="Standard library\nstdbool, stddef, string", fillcolor="#d15555ff"]; - } - static_memory -> platform_threads; - static_memory -> standard_library; -} -@enddot -*/ - -/** -@configpage{static_memory,statically-allocated buffer pools} - -@section IOT_MESSAGE_BUFFERS -@brief The number of statically-allocated message buffers. This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -Message buffers are fixed-size buffers used for strings, such as log messages or bytes transmitted over a network. Their size and number can be configured with the constants @ref IOT_MESSAGE_BUFFERS (number) and @ref IOT_MESSAGE_BUFFER_SIZE (size of each message buffer). Message buffers may be used by any library, and are analogous to the generic buffers allocated by [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) (though all message buffers are the same size). - -@configpossible Any positive integer.
-@configdefault `8` - -@section IOT_MESSAGE_BUFFER_SIZE -@brief The size (in bytes) of each statically-allocated message buffer. This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@configpossible Any positive integer.
-@configdefault `1024` -*/ diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt deleted file mode 100644 index c1dff7d3af..0000000000 --- a/doc/lib/taskpool.txt +++ /dev/null @@ -1,139 +0,0 @@ -/** -@mainpage -@anchor taskpool -@brief Task pool library. - -> A task pool is an adaptive set of threads that can grow and shrink to execute a user-provided callback through a user-defined job that can be scheduled with a non-blocking call. The design principles are to minimize the memory footprint while allowing non-blocking execution. The adaptive behavior allows serving jobs in a timely manner, without allocating system resources for the entire duration of the application, or causing recurring allocation/deallocation patterns. The user does not have to worry about synchronization and thread management other than within its own application. - -Features of this library include: -- Non-blocking API functions to schedule immediate and deferred jobs. -- Ability to create statically and dynamically allocated jobs. -- Scalable performance and footprint. The [configuration settings](@ref taskpool_config) allow this library to be tailored to a system's resources. -- Customizable caching for low memory overhead when creating jobs dynamically. - -This library uses a user-specified set of threads to process jobs specified by the user as a callback and a context. -Overall, the task pool hinges on two main data structures: the task pool Job (IotTaskPoolJob_t) and the task pool itself (IotTaskPool_t). A task pool job carries the information about the user callback and context, one flag to track the status and a link structure for moving the job in and out of the dispatch queue and cache. User can create two types of jobs: static and recyclable. Static jobs are intended for users that know exactly how many jobs they will schedule (e.g. see Defender scenario above) or for embedding in other data structures. Static jobs need no destruction, and creation simply sets the user callback and context. Recyclable jobs are intended for scenario where user cannot know ahead of time how many jobs she will need. Recyclable jobs are dynamically allocated, and can be either destroyed after use or recycled. If jobs are recycled they are maintained in a cache (IotTaskPoolCache_t) owned by the task pool itself, and re-used when user wants to create more recyclable jobs. The task pool cache has a compile time limit, and can be pre-populated with recyclable jobs by simply creating recyclable jobs and recycling them, in an effort to limit memory allocations at run-time. This is handy for scenarios where user is aware of the steady state requirements for his application. -User jobs are queued through a non-blocking call and processed asynchronously in the order they are received. -- [Task pool API functions](@ref taskpool_functions) Provides a set of functions to queue an asynchronous operation on the Dispatch Queue. API functions are non-blocking and return after successfully queuing an operation. -- Worker threads in the task pool are woken up when operations arrive in the dispatch queue. These threads remove operations from the dispatch queue in FIFO order and execute the user-provided callback. After executing the user callback, the task pool threads try and execute any remaining jobs in the dispatch queue. The task pool tries and execute a user job as soon as it is received and if there are no threads available it will try and create one, up to the maximum number of allowed threads. The user can specificy the minimum and maximum number of threads allowed when creating the task pool. -- The user can try and cancel a job after the task has been scheduled. Cancellation is only allowed before the task enters execution. - -Threads are created with @ref platform_threads_function_createdetachedthread. Because the platform layer may be re-implemented across systems, threads will be allocated for the task pool library on-the-go on some systems, while other systems may use an always-allocated thread pool. - -@dependencies{taskpool,task pool library} -@dot "Task pool direct dependencies" -digraph taskpool_dependencies -{ - node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - edge[fontname=Helvetica, fontsize=10]; - subgraph - { - taskpool[label="Task pool", fillcolor="#cc00ccff"]; - } - subgraph - { - node[fillcolor="#aed8a9ff"]; - rank = same; - linear_containers[label="List/Queue", URL="@ref linear_containers"]; - logging[label="Logging", URL="@ref logging"]; - static_memory[label="Static memory", URL="@ref static_memory"]; - } - subgraph - { - rank = same; - platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; - platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; - } - taskpool -> linear_containers; - taskpool -> platform_clock; - taskpool -> platform_threads; - taskpool -> static_memory [label=" if static memory only", style="dashed"]; - taskpool -> logging [label=" if logging enabled", style="dashed"]; - logging -> platform_clock; - logging -> static_memory [label=" if static memory only", style="dashed"]; -} -@enddot - -Currently, the task pool library has the following dependencies: -- The linear containers (list/queue) library for maintaining the data structures for scheduled and in-progress task pool operations. -- The logging library may be used if @ref IOT_LOG_LEVEL_TASKPOOL is not @ref IOT_LOG_NONE. -- The platform layer provides an interface to the operating system for thread management, timers, clock functions, etc. - -In addition to the components above, the task pool library may also depend on C standard library headers. -*/ - -/** -@page taskpool_design Design -@brief Architecture behind the task pool library. - -The sequence diagram below illustrates the workflow described above. The application thread is able to continue executing while the task pool library processes the operation. - -@image html taskpool_design_typicaloperation.png width=100% - -The state diagrams for statically allocated, non-recyclable jobs with all legal transitions is presented in the diagram below. A static job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Static jobs cannot be recycled and do no need to be destroyed. Static jobs are suitable for embedding on other data structures that own them. - -@image html StaticJobStatus.png width=40% - -The state diagram and legal transitions for all recyclable jobs is presented in the diagram below. A recyclable job is dynamically allocated. Just like a static job, a recyclable job can be created, schedule and canceled. Cancellation always succeeds, unless the job was already canceled, or completed (i.e. executed). Unlike static jobs, recyclable jobs can be recycled, or destroyed. Recycling a job effectively pushes a job to the task pool cache, where the task pool manages the lifetime of the job itself. The size of the cache is controlled via a compile time parameter. A user can get rid of a recyclable job by destroying it explicitly. Recyclable jobs should not be embedded in other data structures, but could be referenced from other data structures. - -@image html RecyclableJobStatus.png width=50% - -*/ - -/** -@page taskpool_tests Tests -@brief Tests written for the task pool library. - -The task pool tests reside in the `tests/common` directory. They are divided into the following subdirectories: -- `system`: task pool system and stress tests. Stress tests may run for a long time, so they are not run unless the `-l` option is passed to the test executable. -- `unit`: task pool unit tests. These tests do not require a network connection. - -The current task pool tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). -*/ - -/** -@configpage{taskpool,task pool library} - -@section IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS -@brief Set this to the desired wait timeout in milliseconds for a worker in the task pool to wait for an incoming job. - -If a worker in the task pool wakes up because of a timeout, then the worker will terminate if it exceeds the desired minimum thread quota, which the user can configure via @ref IotTaskPoolInfo_t.minThreads. - -@configdefault `1 minute` - -@section IOT_TASKPOOL_JOBS_RECYCLE_LIMIT -@brief Set this to the number of recyclable tasks for the task pool to cache. - -Caching dynamically allocated tasks (recyclable tasks) helps the application to limit the number of allocations at runtime. -Caching recyclable tasks may help making the application more responsive and predictable, by removing a potential -for memory allocation failures, but it may also have negative repercussions on the amount of memory available at any given time. -It is up to the application developer to strike the correct balance these competing needs. -The task pool will cache when the application calling @ref IotTaskPool_RecycleJob. Any recycled tasks in excess of @ref IOT_TASKPOOL_JOBS_RECYCLE_LIMIT will be destroyed and its memory will be release. - -@configdefault `8` - -@section IOT_TASKPOOL_ENABLE_ASSERTS -@brief Set this to `1` to perform sanity checks when using the task pool library. - -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotTaskPool_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. - -@configpossible `0` (asserts disabled) or `1` (asserts enabled)
-@configrecommended `1` when debugging; `0` in production code.
-@configdefault `0` - -@section IOT_LOG_LEVEL_TASKPOOL -@brief Set the log level of the task pool library. - -Log messages from the task pool library at or below this setting will be printed. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. -*/ - -/** -@enums{taskpool,task pool} -@paramstructs{taskpool,task pool} -@functionpointers{taskpool,task pool} -@structs{taskpool,task pool} -@handles{taskpool,task pool} -*/ diff --git a/doc/mainpage.txt b/doc/mainpage.txt deleted file mode 100644 index eff72d9037..0000000000 --- a/doc/mainpage.txt +++ /dev/null @@ -1,229 +0,0 @@ -/** -@mainpage Overview -@brief C SDK providing secured connections to the AWS IoT platform.

- -The AWS IoT Device SDK for C is a collection of [C99](https://en.wikipedia.org/wiki/C99) source files that allow applications to securely connect to the AWS IoT platform. It includes an [MQTT 3.1.1 client](../mqtt/index.html), as well as libraries specific to AWS IoT, such as [Thing Shadows](../shadow/index.html). It is distributed in source form and may be build into firmware along with application code. - -Design goals of this SDK include: -- Configurability
- Each library in this SDK has Configuration Settings that allow it to be tailored to systems ranging from microcontrollers to desktop computers. -- Portability
- All system calls go through a lightweight portability layer to allow this SDK to be easily ported to many systems. See [platform layer](../platform/index.html) for a list of functions that must be ported. -*/ - -/** -@page global_config Global Configuration - -Configuration settings should be set using compiler arguments (like `-D` in gcc) or through a config file. The config file for this SDK must be called `iot_config.h` and be present in the include path. - -The following pages describe configuration settings that affect multiple components of this SDK. -- @subpage global_library_config
- Settings that affect all libraries. -- @subpage global_demos_config
- Settings that affect all demos. -- @subpage global_tests_config
- Settings that affect all tests. -*/ - -/** -@globalconfigpage{library,Library,libraries} - -@section IOT_LOG_LEVEL_GLOBAL -@brief Set a default log level. - -Any libraries that do not define a log level will use this setting. If a both a global log level and a library log level are defined, the library log level overrides the global one. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_NONE - -@section IOT_STATIC_MEMORY_ONLY -@brief Set this to `1` to disable the usage of dynamic memory allocation ([malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html)) throughout the libraries in this SDK. - -When dynamic memory allocation is disabled, all of the memory allocation functions used in the libraries must be re-implemented. See each library's Configuration/Memory allocation section for a list of functions that must be re-implemented for that library. - -@configpossible `0` (dynamic memory allocation enabled) or `1` (dynamic memory allocation disabled)
-@configrecommended `0`
-@configdefault `0` - -@attention This settings has no effect on system calls or third-party libraries; it may also have no effect on [platform layer] (@ref platform) implementations. - -@section Iot_DefaultMalloc IotDefault_Malloc and Iot_DefaultFree -@brief Set these to the names of the default memory allocation functions to use across all libraries. - -No default value is provided for this setting. If these functions are not set, libraries will fail to build. - -All libraries allow each memory allocation function to be overridden with a custom implementation. If a function does not have a custom implementation set, then these functions will be used. - -@configpossible Functions that match the signatures of [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - -@section Iot_DefaultAssert -@brief Set this to the name of the default assert function to use across all libraries. - -No default value is provided for this setting. If this function is not set, libraries will fail to build when asserts are enabled. - -All libraries allow the assert function to be overridden with a custom implementation. If a custom implementation assert function is not set, then this functions will be used. - -@configpossible A function that matches the signature of [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html). -*/ - -/** -@globalconfigpage{demos,Demo,demos} - -@section IOT_DEMO_SECURED_CONNECTION -@brief Determines if the demos default to a TLS-secured connection with the remote host. - -When this setting is `true`, all demo applications will use a TLS-secured connection by default. The default credentials for connections are @ref IOT_DEMO_ROOT_CA, @ref IOT_DEMO_CLIENT_CERT, and @ref IOT_DEMO_PRIVATE_KEY. Any connection with AWS IoT must be secured. - -In demo applications, the default secured connection setting can be overridden using the command line options `-s` or `-u`. Neither of these command line options have an argument, and only one of the two should be used at a time. Passing `-s` will cause the demo application to use a secured connection, and passing `-u` will cause the demo application to use an unsecured connection. - -@configpossible `true` (secured connection) or `false` (unsecured connection)
-@configrecommended When using the demo applications with AWS IoT, connections must be secured. Therefore, the recommended value for this setting is `true`.
-@configdefault If this setting is undefined and neither `-s` nor `-u` are passed as command line options, then any connections will be unsecured. - -@section IOT_DEMO_SERVER -@brief The default remote host to use in the demos. - -All demo applications will open TCP connections to this host. When using the demos with AWS IoT, this should be set to the AWS account's custom IoT endpoint. This custom endpoint can be found under the Settings tab of the AWS IoT console. It has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`, where `ABCDEFG1234567` is a placeholder subdomain and `us-east-2` is the region. - -In demo applications, the default server can be overridden using the command line option `-h`. The command line option will override this setting. - -No default value is provided for this setting. If this setting is undefined and no command line option `-h` is given to the demo application, the demo will fail. - -@configpossible Any string representing a hostname, such as -- `ABCDEFG1234567.iot.us-east-2.amazonaws.com` -- `192.168.1.1` -- `localhost` - -@section IOT_DEMO_PORT -@brief The default remote port to use in the demos. - -All demo applications will open TCP connections to @ref IOT_DEMO_SERVER on this port. When using the demos with AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). - -In demo applications, the default server port may be overridden using the command line option `-p`. The command line option will override this setting. - -No default value is provided for this setting. If this setting is undefined and no command line option `-p` is given to the demo application, the demo will fail. - -@configpossible Any positive integer below `65536`.
-@configrecommended When using the demo applications with AWS IoT, port `443` is recommended. - -@section IOT_DEMO_ROOT_CA -@brief The path to the default trusted server root certificate to use in the demos. - -A trusted server root certificate only needs to be set for [secured connections](@ref IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. - -In demo applications, the default trusted server root certificate can be overridden using the command line option `-r`. The command line option will override this setting. - -No default value is provided for this setting. If this setting is undefined and no command line option `-r` is given to a demo application using a secured connection, the demo will fail. - -@configpossible Any string representing a filesystem path to a PEM-encoded trusted server root certificate.
-@configrecommended When using the demo applications with AWS IoT, one of [these root certificates](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication) should be used. - -@section IOT_DEMO_CLIENT_CERT -@brief The path to the default client certificate to use in the demos. - -A client certificate only needs to be set for [secured connections](@ref IOT_DEMO_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. - -In demo applications, the default client certificate can be overridden with the command line option `-c`. The command line option will override this setting. - -No default value is provided for this setting. If this setting is undefined and no command line option `-c` is given to a demo application using a secured connection, the demo will fail. - -@configpossible Any string representing a filesystem path to a PEM-encoded client certificate. - -@section IOT_DEMO_PRIVATE_KEY -@brief The path to the default client certificate private key to use in the demos. - -A client certificate private key only needs to be set for [secured connections](@ref IOT_DEMO_SECURED_CONNECTION). This private key must match the [client certificate](@ref IOT_DEMO_CLIENT_CERT). - -In demo applications, the default client certificate private key can be overridden with the command line option `-k`. The command line option will override this setting. - -No default value is provided for this setting. If this setting is undefined and no command line option `-k` is given to a demo application using a secured connection, the demo will fail. - -@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref IOT_DEMO_CLIENT_CERT). - -@section IOT_DEMO_IDENTIFIER -@brief A string that identifies this client to the cloud. - -Depending on the demo, this identifier is used for the client identifier (general MQTT demos) or the AWS IoT Thing Name (demos specific to AWS IoT). - -In demo applications, the default identifier can be overridden with the command line option `-i`. The command line option will override this setting. - -In the [general MQTT demos](@ref mqtt_demo), this identifier becomes the MQTT client identifier. No two clients may use the same identifier simultaneously. MQTT client identifiers may be subject to certain constraints; for example, servers are not obligated to accept client identifiers longer than 23 characters or client identifiers containing non-alphanumeric characters. If this setting is undefined and no command line option is provided at runtime, the MQTT demos may generate a unique client identifier for use. - -In the demos specific to AWS IoT, this identifier becomes the Thing Name, which is used to manage devices in AWS IoT. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-thing-management.html) for more information. No default value is provided for Thing Names. For demos requiring a Thing Name (such as Shadow) this setting must be defined (or a command line option `-i` given to the demo application); otherwise, the demo will fail. - -@configpossible Strings representing MQTT client identifiers or Thing Names. -*/ - -/** -@globalconfigpage{tests,Test,tests} - -@section IOT_BUILD_TESTS -@brief Specifies that the tests are being built. - -This setting modifies the behavior of the libraries under test. For example, it may expose internal variables or functions for testing. - -`IOT_BUILD_TESTS` will be automatically configured during build and generally does not need to be defined. - -@configpossible `1` (tests are being built) or `0` (tests are not being built)
-@configrecommended This must always be set to `1` if building any test files. Otherwise, it should be set to `0`.
-@configdefault `0` - -@section IOT_TEST_SECURED_CONNECTION -@brief Determines if the tests use a TLS-secured connection with the remote host. - -When this setting is `true`, all test applications will use a TLS-secured connection. The credentials for connections are @ref IOT_TEST_ROOT_CA, @ref IOT_TEST_CLIENT_CERT, and @ref IOT_TEST_PRIVATE_KEY. Any connection with AWS IoT must be secured. - -@configpossible `true` (secured connection) or `false` (unsecured connection)
-@configrecommended When testing against AWS IoT, connections must be secured. Therefore, the recommended value for this setting is `true`.
-@configdefault `true` - -@section IOT_TEST_SERVER -@brief The remote host to use in the tests. - -Tests using the network will open TCP connections to this host. When testing against AWS IoT, this should be set to the AWS account's custom IoT endpoint. This custom endpoint can be found under the Settings tab of the AWS IoT console. It has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`, where `ABCDEFG1234567` is a placeholder subdomain and `us-east-2` is the region. - -No default value is provided for this setting. If this setting is undefined, the tests will fail to compile. - -@configpossible Any string representing a hostname, such as -- `ABCDEFG1234567.iot.us-east-2.amazonaws.com` -- `192.168.1.1` -- `localhost` - -@section IOT_TEST_PORT -@brief The remote port to use in the tests. - -Tests using the network will open TCP connections to @ref IOT_TEST_SERVER on this port. When testing against AWS IoT, this should be set to either `443` or `8883`. Port `8883` provides a standard encrypted MQTT connection, while port `443` provides a secured MQTT connection using [ALPN](https://aws.amazon.com/about-aws/whats-new/2018/02/aws-iot-core-now-supports-mqtt-connections-with-certificate-based-client-authentication-on-port-443/). - -No default value is provided for this setting. If this setting is undefined, the tests will fail to compile. - -@configpossible Any positive integer below `65536`.
-@configrecommended When testing against AWS IoT, port `443` is recommended. - -@section IOT_TEST_ROOT_CA -@brief The path to the trusted server root certificate to use in the tests. - -A trusted server root certificate only needs to be set for [secured connections](@ref IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html) for more information about AWS IoT server certificates. - -No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. - -@configpossible Any string representing a filesystem path to a PEM-encoded trusted server root certificate.
-@configrecommended When testing against AWS IoT, one of [these root certificates](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication) should be used. - -@section IOT_TEST_CLIENT_CERT -@brief The path to the client certificate to use in the tests. - -A client certificate only needs to be set for [secured connections](@ref IOT_TEST_SECURED_CONNECTION). See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) for a tutorial on how to set up client certificates with AWS IoT. - -No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. - -@configpossible Any string representing a filesystem path to a PEM-encoded client certificate. - -@section IOT_TEST_PRIVATE_KEY -@brief The path to the client certificate private key to use in the tests. - -A client certificate private key only needs to be set for [secured connections](@ref IOT_TEST_SECURED_CONNECTION). This private key must match the [client certificate](@ref IOT_TEST_CLIENT_CERT). - -No default value is provided for this setting. If this setting is undefined, tests requiring secured connections will fail to compile. - -@configpossible Any string representing a filesystem path to a PEM-encoded private key matching the [client certificate](@ref IOT_TEST_CLIENT_CERT). -*/ diff --git a/doc/plantuml/RecyclableJobStatus.pu b/doc/plantuml/RecyclableJobStatus.pu deleted file mode 100644 index eded456e2b..0000000000 --- a/doc/plantuml/RecyclableJobStatus.pu +++ /dev/null @@ -1,46 +0,0 @@ -@startuml - -skinparam classFontSize 8 -skinparam classFontName Helvetica -skinparam state { - BackgroundColor<> Gray -} -state READY { -} -state SCHEDULED { -} -state DEFERRED { -} -state COMPLETED { -} -state CANCELED { -} -state UNDEFINED { -} -state CACHED <> { -} - -[*] --[#green]> READY : CreateRecyclableJob -READY --[#blue]> SCHEDULED : Schedule -READY --[#blue]> DEFERRED : ScheduleDeferred -DEFERRED --[#red]> SCHEDULED : -SCHEDULED --[#green]> COMPLETED : -COMPLETED -up[#blue]-> CACHED : RecycleJob -COMPLETED --[#blue]> UNDEFINED : DestroyRecyclableJob - -READY -right[#blue]-> CANCELED : TryCancel -DEFERRED -right[#blue]-> CANCELED : TryCancel -SCHEDULED -right[#blue]-> CANCELED : TryCancel - -CANCELED --[#blue]> CACHED : RecycleJob -CANCELED --[#blue]> UNDEFINED : DestroyRecyclableJob - -READY -up[#blue]-> CACHED : RecycleJob -DEFERRED -up[#blue]-> CACHED : RecycleJob -SCHEDULED -up[#blue]-> CACHED : RecycleJob - -READY --[#blue]> UNDEFINED : DestroyRecyclableJob -DEFERRED --[#blue]> UNDEFINED : DestroyRecyclableJob -SCHEDULED --[#blue]> UNDEFINED : DestroyRecyclableJob - -@enduml \ No newline at end of file diff --git a/doc/plantuml/StaticJobStatus.pu b/doc/plantuml/StaticJobStatus.pu deleted file mode 100644 index 38cfd5540d..0000000000 --- a/doc/plantuml/StaticJobStatus.pu +++ /dev/null @@ -1,32 +0,0 @@ -@startuml - -skinparam classFontSize 8 -skinparam classFontName Helvetica - -state READY { -} -state SCHEDULED { -} -state DEFERRED { -} -state COMPLETED { -} -state CANCELED { -} -state UNDEFINED { -} - -[*] --[#blue]> READY : CreateJob -READY --[#blue]> SCHEDULED : Schedule -READY --[#blue]> DEFERRED : ScheduleDeferred -DEFERRED --[#red]> SCHEDULED : -SCHEDULED --[#green]> COMPLETED : -COMPLETED --[#blue]> READY : CreateJob - -READY --[#blue]> CANCELED : TryCancel -DEFERRED --[#blue]> CANCELED : TryCancel -SCHEDULED --[#blue]> CANCELED : TryCancel - -CANCELED --[#blue]> READY : CreateJob - -@enduml \ No newline at end of file diff --git a/doc/plantuml/images/RecyclableJobStatus.png b/doc/plantuml/images/RecyclableJobStatus.png deleted file mode 100644 index f9adcd921f89c46316d7c0ceff6ffdb435f08b4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56514 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 diff --git a/doc/plantuml/images/StaticJobStatus.png b/doc/plantuml/images/StaticJobStatus.png deleted file mode 100644 index 44c4480230adacf41f567c178b625c07df180aa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31124 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 RJ#b2hq`0hD?qgl={{u+%9*h70 diff --git a/doc/plantuml/images/jobs_demo.png b/doc/plantuml/images/jobs_demo.png deleted file mode 100644 index 5a68b4ef176250c7b11bb01f3b012b55e48b3619..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48193 zcmbTdby$?`);>&k3eu%04WbCr-7P5H2r|IXAzdOJN_PuN2@FGbNXO7aOLupDH@ctq zdG_A#_xoc0;W#*E?pW7~bDis4A*#wUxL6ce2nYzcaXvpiW zfj`VnQd&-?wsvmTX68-^GG;br4#rMq@90e2=q#L^?D(HOv$Hm~adHM*bC}wK2|4+w z5D<`JKyS31{`x-xB5;`xso*zCws28`hSGK)GQI@KhjPA9d9MN(!51bZf0JPM!tt$^ zDU&^&E3wR`#tNQlzfRKW3fno4BV$9|pAko9h{LvNa6|HV{!PxhFW-NCN}$8C*-6vj z`0PVaA}pAE_+Sw+jGLeNLy-L*vK`;S$_M<5 zqq5|&SVesH34e^`iH|$6s>P-n_~t`1iM-$LS>}T_u2mTS%l(oZlZjyj4oJyDJ9!D4 z99`M7*cga-_2)=pNg=MSK+!(yrMzM5r(e&}-qGZuiuH<)*cdsHaVq8fa*3oH(bq|r6Qw*?+`jhOoJsnTk%2H<(mLfOyx-84T`>x5*Ic1oZ0 z>K!1p^2%{|w&JEDokRy5bI)#Y?Q!z3TF{cjJRcIYUf4)Gd*(4e;L$MB&I>48alL&p!N5R|8#1ar!3FMLxVM)z9`w{Nt+1ZOd^*lMAd|4&2XPwJiTL+Cnu2|bf38^V zn~&mMuB&%AwEy`5KRrr>jrT-@iD`+$pyOd+C@%NKa|lk@#~W1CPif_ZO?Mdx2)+n% zl45T@7;L9udg5wc{j@z5GEtB%B7F3~pFnck#L%j+;j}^ii5Q0Xb_kvLM@crq&sIv6 zv1-L>Se0+y#$|gIZZ+BJ7WdqU)a0*HJMO0HCCvyp?s=sTo-H}9CULC^Iu1ClUGFB= zyao;Z&!?~TjW{~RU!P4 zlc9Srz20IqV2j0~d5nq0SbU0sT6yas58XZ&=){>9^jukrQ^lixKrzZ~(Vh0xi94nl z=SxgHG;l8+$>QecT5)hmLvq!%$|<6ILb{&Pl)LE?R0NMI$2Bhe<|tu1H2v=CHs`p+ z#CVWiUbp=*lkTv>32d8+>igx`2Djy}NQI0MZd}Q^Vi}?(&DsRk!9Zg(vxnoAr26WN zH;fI)LB}ckJpcA^y9T>J%SlZ} zP0-w1HdbksqEiRlz&O@tJn1FsNVy-|(`90z!BV2RJ3~p1U)c=>-gR1+ejhWgP^+X6 zDD@Q6hbY4V;IbB)8=4U}izm zAhxjTZTrIXqFK3%Z?5c4w5# zi)*qX4lQJ%_R70ZCJIF{feFKWWvAoZ_CEKgCG7Fm)cU<|j!pVebK4L_S+%jC(~11u znMTmTwM8>mM>NKKj@zn#D)B8V6WJ+r@65b2X)BAmPP9gDI=DE4INFjjD#LcT2Ayha zd(|NSbjjJbV+!AOXn^zZ30=nm_qUv!|mRzEUKE7s26muxg+tDQFyJ?&D zf55)<3+eWZwDhraFr+^;b$Ya^q^?3?f}x*gPc=sW{exFH7GqGgDTkGr8OqB`Op1A~ z_m4h9U16?=HyO5b!^I#+UKAIl&AAlyf?|J)(Qj&C&Eso=CVpFpwGh-1e^N1nM(WhN zft{4c^=G?1H!Y|Q;Vv3VUD(3EzMxfQSsBwi@uGGp6Cy>x{S@qHPN?59lBHRF(O zFwVmxF&E<3pIvy`$9J8Rs;4IbcY#z?X@YF)(73HIPqGAEypcwy+*ZP{;=GKwimXb# ziSh+_`Taum%eN4k&RjKYeX-Jo-)=Ow*NPLqdMG*s0_D2ux(xw6B7}ofM|RKzEioOadR*-Ds(Bc#Y0cR!0!23sP^(%UU68CX$DEL zC|vwvXYQu?PUTjPpz=r3OB?yl@TvB0bd)^T3oO-^?m0yGwbG3+uTQ@Kk}{>$)>Hb< zQ)?15kAz0<$Wcb>DflD=1LNc6^!tuh^9D=qE`$BaX}C~7U;9{nMh$vv$>Lkgo`{=k zqMu4h@FQH+>2QvhfKy8rE5Py6X>ikh8T!Ant9%lVs6829%}7pyVD?l7P##4kS3Jk= zQG%0%bnUkN6m@gjHequ0c0`GtH+X%V!L1rY_29W(fqbF*MMG3@GP-ipCGLf?|K#H9 ztt!!$j#;6-ar*s$&9@D)&~RTuNz5Ob)GtH6qJRWA>ek~ot7n%o^%|w!yeq}J15knn zoA{eHQk0A}2T_Hb$Msx%lF|aGKbWVba$n6@tD5TdtNr|K$&g5tZrSUl+GACa8Am=n zKD!p$N*i{9*i_Y*Drr`5cgcai2i9({gJ+$U&(%UFOYvB-&dyQ40JJ|9dBw=EDJ7*m z(~#*^p?cIChGMYy)%-NXhvq!9==4rjrBv_kdOzrgtKBr-but~;)uN4K>DS>|7Li{5 z-v)D`;zSXozQm__$^FICq}V`5YLDeQr|YO1>`pqX8Enw>_C_u00Z0K4A2j`$>V@$z z)@0E`;=VoVNHx&j`Gd3`neF}0&+Sz)EXTi`Umc}anxTZ#$@~5;8&73*)gtROoRW}q z-EF@akwzcC{C2Uw+72?c&B@MXzw?`ozJ0WSNlQe-|73J{*8U=3#h*+b8>l3oAo{#c z#Kg5hP`XsS{L0IJt$EJW#MAvGX| z>qGW(2tx0=uY(D0F`vFzp4u#}-W)y_^g``e-9L3|&k0ttobK^`0AsDL#6 zV8b;2x8H}keX{B^%m&xyikxL5n6@#iu^Q-}$pBz6}w_4ewu%rh4 zT2=)LU-@2cb6Y;XV}e5Z1+S7)WV9!@xm*ro^Q>gmpHsd-MT}mfEh;LeN5czKR8Apl z!lFRIWOey4a8ep_ZUf_aT<>8UHy#Z6hZ6vg#X~fGY%r6;xi!i*FqithF`mgPrqz#&p2t!Za?SsiQwk}ER>S+WbIuSU14IfRmjxLU8_;}<3B7Po%bO+7$ z`&uBM_U7?{66H36+t+o%4cIGCFEmj;hNs9qBbp`8E(!2xF;8sPeA}4#uWcV4otwNsraJgi1F~^j2dXhxQM}v!w0OdLHrBbT0O9<-!#8;c# z^ozJ2J5nYSuY}W;YsnDtsg{l`u_&}$e;>-bkI#tCgB<;xr*6!~BtpH-hrflZuN4er zYG(<8=AYCuWNmF{YkIUvf5OaW(f^HV6r2_};eH@UmFeSJnoWz~Mxd7^4E z<~JG_ca`AbN_clMyHhN#HSAq#974;WXpEz7b*@dMd+E!s@Z=MUxQ)_j%z`TSj?+%} zgo$&lQRS3erl#S3A6EF)Uc*RY%j7+l;+^CtKK=+<&;adW#Ta@JZ2gYMW0WV>@NNYU zTW^UySWppm71Yx+?i72{eHGqQf41I4l6AD7YIT++Jc}V_(u`q3_ouF!1o9o1z&e2P5Ce_J@t}DPe~jTzmxuW~ zx0o}7BR&E7LS7eFUiTiuC;l!&pI{nTz@3=D+=mL&VXj9|T8U#BuW##K*Cx%crHJv( z%b#XC#!ua2^`+dM+ACId{C=r+t+NxBusEpCp4Xj7FPBnq)7j>&0HMi`wc!OANr{)L z^f9CD&*$#n2z+xwnP~goDTuDIt#RI$z4#+FxW9YOdz3|4p`xYkS*j70fo%26+;jwj zuw!4Qq&>}dL+@MYg7~7Ozd$oewQn+F_%KoiV>H%+*&ZU001@1B{jg{3>zH_n7VJ=-|!&$cryatDbg2(b`TvHE>`d<9D zAQLz&@X1_tn9GSJNtW zmP#o8{W-8;yl5#jXNy#sFLRQavpn(mp<^xJij0bAqVtc3!_5PiMK9~|zV8c9CI1}s zI7dY?pjN3`_ZZ!;pN_9z4Za!~Aq-*LXx}^`oyZTU^-6XO7G@USc9R4Z(siLQDj8N= zNL*$^*tFQksjiKBL5sA89l#;t0f*R}X!aX4tHMdJE@`cuNDT?EdHNw1Ne>$Ri<`>@DJq%jxzeZe(div*` zej#BYwL_W}p(tb(efOOPh5WS1qwb^!)Inc!K2U>^k)vefw`PFvcKw% z(D!-o_FHtN>?{a7Ni38JjBE1sDR`nJ!l2R;ZRZt=ZIh5zv2yNBU|(J~ivL2>osh?l z&hE|Cp0JO1(wr3r&a3cnG5YK4qYZz5p{5&`PjhCQ_2Ytv`JzQ^s6x}svx*%01L^(A zf^J3`!aFaxNQXUETKoRxsA{oL_W|c_PKzaL-~?E~#x)Ca3~TY+z8c z-cRG>BsN^fNx3*Ejyr0j^=oL9@=SVvn+XmrfvFp6djCz5B zhj&_G9+7$f%)cs8TE+_b9z8AuQc-ugt1cN3*UIq!edFU8wkiH-CmbRMKS8VAIO;Z zzHm4d*u%LvU&5(P9_Ca5VY_U=IDTDhm4>bK#AW5}gpq0@)S@Pv6#8BIor+vMiP>BB zK_X}0cMS<_-;eQ6lxKa%@$eGcf39J=6*FBhNqO&A4wr122RS!t-8we85QWzjG{?4k zWQbJmCoEkF^taUhXJRGe*cMLEfui04n@ zYE3^#wrwhen)q)4u*GU$L!qPP%AEdbpEobKFg=p`=YIHDQV!;V_V z&S)sVE?q>aE~$FuchZofD3Jk`)JfX!ZXc>!Ca8C(c*|7pu8#y?szHnD5;WcRr>CYi z-++L~5LXe~zd56AMQGg^iXciCszZ2>zGh-;J5g;vnw;~h+Nyj;P6`ADmy>g66o$OM{ZxRMv;|!r}F@t7squfn@g%Q1EoyaQ2|%3 zxP6vH5C2cGB4*ye$;s!9jlw)UG5OitcA3=-6!a7WOZpsA0 z%5_ZPdBo|?zqH7?^rsB&IOi2l2IWO3aiOAmT%K$pD0jJ>E6chLizh%<4AICVw%n5P ztU|r4QH?VpX0p0k{f}NP@JsK(+`L$G&9kf~;$s-`mm}Tx8gMzW@o5|L*B1Si`&HqQ z6l1KslT8}MEbUwl?^IM@Bcn^(!CbD-Uwu`kj!md?AIem|reyTTu1!h7##xDFM!q`R z!x$S#n^ty}A&7yRus3rsG6u=V^|X0Lr?BS8oPA2ttuQAE7KzsQ_RUdFsZ8}}ga>l$ zRIVJdgM-FPTLW5`VU~zyEtX?q6apSdMl@%fO)5f5b29EyYB~)%D@jq&+Th)uqA@N* zLY#7%j;H`9Hz&11f--VmSCbB(fIuq?@B4xE)14G??%fyd8N_KhC~b2Euk~&BLUpS+ z`UVXdH6q%zw=+M-=r?-SHsi-&-2RM;diWye;Z}0GeHxcS2KL?Y)Dl~~HC4wz&Oo&6 zl1a9VHC1~uMWNSAi}U?mbbN-krxCs{qL#B)6coz{&HH-u z6zVQd!RYMPGi2CqQaX(uX9OE~FQ`ZsOs*a(P^!^3l8d{o_8D_FMbS`?k>dxE5B>op zrL5o%VYa}&R6i;N0IoDs$m+}D*J7u^GzLG89=%YQAikQ!61@*YQCV389Mi${;%>80 ziS0}2LH~e&8x;{R$?MD%#w zpLTO3pHVCumu4n#`|@f}VZR|s1BBA4mX%NE8~3nwphrjX6`to6`1St2RI2suew$o% zrajB3@C>ajx2)$eah^!z;XzS=L<(h1e*zf&X2X}=rP zF0*vUqu|rNG^0xt*w5grC{`8JDJ$uIbWOfSFDu^S^U^WjaGN*mDJ8lG&BYH9((Cd8 zdjhM_aQqu#A`lS_+5BUjoE$w6W`+i|w^#EO+*X&o{BR#2-7A}v%V=2}U>OQWi|%&J z)qm4y(6jQnJ|K1~v!0sX_9h1%O&F4q4}D?r>|meU3#W+^cH<}d`e}}inhzM4E*TP0 zyI6T$fSjXxJLsd_?4I`Jh3;J&YGOVlu+)%K+Hoy%s!0SB4dWfIA-wf53f$~)*>AQs zunvm8ip{9N!BoXq^%C8BV*Zwi?Y3t92UvYgQ3kCrrb3fFA1A;mdQKF|p|dJfd;KnA7Fu@Zgo{sPC3&}Ifk z@ZIgzg97jB*48uM(a{={je%!jAXK~%*l7ShPm2KtH#?Zd_-ABx-6xZ*FJsvJD>_5p zU>E>|0qW68LQs}~YPQo-;Ili!IM0#C&0P}(i%LD$Tw{iC<>Z$LmS64?7hFOj)D{bS z>$SLr(06WcT(c-p==NRr=CC+2eQun~emnYxM7udO`U~5P-(^&VR&Dv(XTmR&YvT7Q z=`OV2tY#vi?`bv7xwU|!-F&4l25#HC($TM#7idAFz!P9 zLR0F@OIr~KWz}rVfx!XP7bpnw01}ySD(bQqolTzVmlXPn*@=k=Zpt@84-n+co|(^v=O$`#)9hCaH8?7{9@Yn zeWtmjq+@xvi26+)7|4ctQ7Coo(wHBXB2H&s@s(L#;_FnHi|ywf1<@)yq(5uq#i%85 zf0SY}9O|q@`qH+BZxYmOu_xV@Yf9uQjVcCyjku~`KO&s{7e)BNlOdFXIw8P&wmHTsz{fXJzTzN~wGdrtql;q?zdf>BlUy_^X zm_|8d0gS5-!X+7SKk=-|xHvzZ#;a^`C^^7UETg-M%T3d#*#o$ z5LzdHp63{0r4D5B~|eu z;xl)w47KiewmQ*Su+1eUp_wvfbgSIvco|5Vw=_5(%XE&I>R&&@&*=i$4$mF z3_XmHs}x254=9N%f0EYMC_ydg#AtEi-lG^O1dK3SMB z6#2AYZL3p?dUmokoRhO0h=9f~gZbfnU+6ZhioXyDh>rl?Z0UB03brSvEFLHU-KTqg zQem~uBnfL8@<+qI{*U6EBaw5y&3g-|`>Qj}Dw;Qw8F$w2%PFLwhjA>0sHLqZ@pw)i zBu@RY0?ZbY!<+aTH8l!}i57&Vvt%Z8n< z6xH-kk8CPC9e^G~O|=am906y`mYeg!q^!Z8Bzf$*a270pfWtHWrW#3?2z37gBAI!L zimW;nEl*z5s9wR0qiBlfJWAL1i0MkP|Gu1f@nH^QZ|cyRX&68k6^r5IfiVHohCV>S zX#FQ)XAKG?&W1>VSe;_O0HhCMcpehKxQYJ<;|7dYK~@A3AYCLf1yq31`uHD=mKZ1u zHcs~m21Nnt3&2TX{s$-J|IcS&!XO|*`ghI{HIH3hHy}|I?wt;y$d;LGW(r|{$d8H` z$&#(e8N-g)`R7y^qM_#pS&LsrT7OfMue851f(dViYu>CAqhMseyZ>fdMQWQKz_aNL z0Ctv>5V*b4B#%F_rKNuQ&MyW z6hL*mTW`Mwtr3v0Q`gxIBm!KZdba6D$9HA#Y^tpES9aTkqsRkPX4GLzKcypBN zZjQ=FfU=EX0jo!)YZrUtm-tGZnX6SjHfcU@W@^!D2?h(e2GGb+y={MT?&Bql#BIF>NBGDr+#n`=Sd6BmRkdn_zRM5LWSZJ%+w6b&{Es8}G zu%KMOTqE#$>^XU4@dku#xV{KSpWoJ8S#8Tx#e9O3Bqm^n-ia=a*@-1>y|yB7EX(5g zMv2sHHDJR(Ns)7}KKuP|9{gn*jOSKEu%K!x|Gf-#7}9e_`m&s>5#<`+Vc+L|pZyNB zZ;fiq~@mZ9Rg#z5{Wjm zGif2nD@)T&gYxy7Et(UXyPD6704ac(yl!-eiX)z%L46ZD>Bk%=qfk)36P+N&1MxuL z3~E8;3=G;+L5jC<76SDUqUU&t_`#2UKhL!07a#auq5<_|}NH+)gfcr(>|MKE`a2654Mc?FP z;45bMq5-h@h;Qv7Wa$6)N}fBi0&xN4Px7~W)J_oq1;kXMd}^?Lv!^JIU*5{YKeOnG zNQgQgpoQrvVf+7ZAuae8H3sFs-OX+%xb!nQ{GDd}IR&{%`tqzhag#(^v=`?Z+{?Wb zY07SUJ3gwky!WYuJ2r-i`5$$J9jMR_FoanGRNnV~;1!0LtGkK5W+7s%_0D>4IL3Fe zVzszmL`rigsLF(--MTCHCJz;b574{JBMWuf4(3?BSU|5i>8m`9V@WtVJ}g+k+>9?N z;>c50_-t9Z8=kTR{Llh$PKp z=pjbj_p$x&It{N2U6e~~SmG?m6QbvL&?IcysuhtN` z(O@wp-7-VWB3<8l$9ehdfcKW9?RZ_T?ELuP7}LZ-qn0yn4#_`}{9i+fIRAf5=cd1u zx!<81>w5&cY$IK=6)C9HA|aya{X~?=jCQ=Sio#0w%QC)h0LH!}L9xF4xDxPFawfaY zdEVDafR?AC)LTyQvOqB{?LjS$8@5j7jpy;kn?ZFW$!wAT!%cO0wkHGtnOR{z1l97D z1xobcSNvyaRo13!3kYxyhNl6%uV_T(>5AeSa&n?dX{lE9+Y&7576iHc1E}*c|&&<>=sqXUAxIYK{83K}*e#L*UEY%t-(8YhV zEq`(~`zhiS@V9}6S54nGHGkHb7f(W{F{jYQs>Hk=gZaQH&Z0cd6fZrOLa?#999{J; z^@<<1kKG_sRBik)+4~&MHQ~&oOi@-|ZaMuPA$VyP)ezI4in@+$ptT3?4jM34ODa%Y zCw%-(t;W0*g0SJX_!@Rjnwf|5^hC8pht()yx%VS+s-^seQBTg z7lhRfB+~v##Q&X-a0QgTa*H?faNQU`$&Q(M;I%PWM)_5&(6Iu5bU1CPbKsQQ6UIr<2l$|c$ku{R6_DntEmudl^C>{%Qy(mla%q#i#Y zYqI0BF6r}78Da)y!~~?xQ@w6bMqsuTc1As3?@HulYI^Q{FH>43*OSgS$w$ba&jo+< zm~v=5*?a{eZEHx0qgW}aBcNg_(jYAO;Kr!RHp?|j#o}>di~U!?fnSZkxk`_;c0no@U~F% zq);MQCqdY1$K0+2?ixEf%nTmWxmnh4BX#lXqOTGkE436zi@>HJ6n(=$EQ^EuwQuj201wxU}9nqwjnOU66;fsmJM99;f zzFCO=0&SC#c3{sO%?hhH>7}f0Nvg+DR-F8p?JuXah+t=Q_DT3uO9?Y({7J?@@a^BO z-}7Ik;P)A_yN#a$!raW&0=}@(XSRDvI`zqhj=euM#mba)(-nC*b^#({LQ=vE5Y+w zXlu()LsV%9h3+bI!LTeMyY2J9UT2={3IUk-S_{3d)2{K}ItKdx8UMET0<)If(g zol4C;0W>k_4p;NsI9}t&b!*A@u6kA;!XB!}ly)2?%l3FEi}WsO&ozHT74>SD_K7YW zFo2UhBl_NG*PCW+zRjU6f78SQuHcwq(H+JX%v61qUQI9w36#I`*(%vJGtakCx0(+| zy3l21s{Yw!kIXP=XXn6xu5pIH&SU+O*hTMh1`sRpwhzn+^A>Ttq7#2~1|pM(2f8er zJSv6)}%4f&b zS4!K155G?RlK7WvLDsWfSGziVTI2xH^jN?!@7u~MyiX{ z#Z7uX-7?($b3*0D^7{G(hWhy?(ie)y_AZV=ykk1UMHPg#0^U}cPGabFZm~~Aznkp_|{G5d^bF5b2~pljL%{;4>7|# z6+~C32BB$9%yzJ}P{QYSiLU)Rkc^1i{N~6fM>DwR1}$grQfu%I-xN1oDdRKHp7Vcp z-0=n!=T2895ks?&Fj|9tD}}TH9nN$w52K2{tL@W<-g|-DZ`e{urE{`Q34@x>+pAx1 zzJipOKen|>LZmy{rl#(8>;%B-@08sA80K%SE#T0bVSJ;&C*{oM^iBa|wYQg75J$*%CRU7&k{#yUK44_6Hm2h;hPLV*EdZ+_V z5*jDA{gwQ$3z?aMN?qx!{|#@4!+4Skbbpc%wAmDz*X<;ays`&CtVGK8J*HLX|8Ia^ z5BwnC29wV2;+4G_epJLgV1;S?0jzek%7BOoMa+P5%Ur<9)mPjlX@9g~m=BP3SWD%L z$_bf1_CNrZ3ak~MD!20*3DbRQh8>3fw%=5xY3C@R-0<-JRH@_sqkynvTFxc2je}_SgroaRJy53R=jo$PrvsNy2Jb9r9y`^-v zA399_*=fX|x}&$E{j(kEE}*VrBg-(Is9(oKnFH$>Qv-3-%urVjgw_4BM6dtv2dl{i z5AVU_t)%9^ass6K1d{ zI{9y=2vpDoo@9uj4$?8v6&v~AodN*EulGM9TUr8NiTi)wNc}I3$^XBO?0<2i)=K(D zX>*GheeL9Kt9Ohl>3aLgyDL!hiDC7V%((QHeV338|5zL@SwLV$b{XH{ zVA%nOeTH(zvi!B*^77?{_2SU$JZ5lPTbpU$=)yLdQiLRMBDo`mD-{I&MxR{4c|aEl z?5;@2Xw!i=U$J?CupjI*a&hwtT-rYejxHDFPrtJN9{e!!-a!Z9Jstj8wpkD_TnSJZ z0o#rnx10!Jvh?V2%*Y!>kkxxz+L>}( z;?ucn^NaLr0Cj)w==e2iW@T+{o%OXc0ylPJ+~U0dyW4DBCNm;G{VP5)7)c^jkUWpw z1Va^`9hdz#ACOSK9Luy=8=2T76-MIi?X3&728|Yc zPD#=I8A)wBTia2ow+U798fxiIYUB^gih|7AZ#WLJj6FufvzjVSh$*DJPI29OeYR+7 zRGJnbv9if67f3_@^IJzA`1-WId5Du~6g=b9U4pEdSpS4DvfR}PCGNg8?cz=`ssPaD zkHWq4Z+=R;@6wt&xO?{^Z91iyJLV0&7T!DnN4u9Brsn$#|#~ zt)YmnfWRI_<4AldY80^8b%qfh9#Uw)hyXwPuHyx;ML^+cdjufHkNTMcSvZIz#_Puz zos#+QM^mK$1gKG{U*(p+n+kc@o+%9qBf|k!z49#nhI)Bye9Ks(VzAT2wxGA)-}pIX z2zx!Ir%y{wEh;LCq~d$Lx)$l|;<7u}ARw#Dq>{)fHYvtD1#OQo>(K;o*w@kl6JXpO zqJxSYNJ=$MffeSuJ#DwmMhrBU5KomxVf-f;UA&I7ot->@@;lmzQ)5;p9JQNlD3_dS z8UUoMR$IV6(1p@LU5@S&!c+do?zaGb{v_2Y?iKB-O={y8dQzgz%J7ul@+KbG6AP1S ze7VLcsU}c;(Aqb{ACVkAGU~Q_O(SQeN~`nf^u95{F->@6+cH;nM3gD$I^x2Q1DA-x z-02o{MMle)o^^NRF&ncbEP=`E@}qGB8(>BYR__3VU`<5+Zu>aQx{Yf}Kik7xmO3L; zeU*OEJ~#O&&-%XOdZuh@YD#sk!Q)k|)o8&%`63Jg-V7)$v>L0;1p^K5y97}xrlaz6Kd*c3U?b*1z{_igT{Fq`oKS+?Y%Ia7u(U|72@ z3lIbgK{N@w!nxjj;Yc8bATf{i=UGBeJ{@2u6uRL*8G3G!BLxbh%lor*05rD$!ZNI+ zSYM#a_>Y}7M?=nIzI-vNc%36{;(c4D+UT;AEN^wTJL@s!vZ2-ORDAgsRJR0MQ!RN& ztmqOEoQ|K;eZhEf8P&zC4FAHYp+E-)(EfLpFT)MMJ{nRq+K9;I=vV{J`SdBd?EQPu z0it$15RAireKd<}?U-DE983LyLCs(;mxG$4IgFts4cMO`6>t>>K>xbnhunr7`j>S~ zR?j{|7hX3}Nkl`P$RI&9L~yPT?vTwD35@+`nk2vdm@W_HH$r3jow!3_8uWD#{U&cA z;_sTJ%GWUrO8)&cLhivA7eB@M7#Y!fl$DjAQF2hTmX*8((5#>QnC0Z;q;&E87l2u? z&_icj+xGV`IV?%d*93wOSXdjZwF@iE1W1w#)G+{LDRv20ukHsq@|cS~!EM#?V+1Ip zNmLp!bv;+r7@KGW6NrVwt9N*bf)aUgUG}<_u031hy0upXPLPeZu!%FCJ`?PI9>d7(NrQt%FfNSVQjj3*x zOo|)#300kL^xv3TU0E_o6Pzk;pzAf~0Ll+N~tvcCD zTV+{i2Kxcy5WKU! zBuWiHLXCkE0vnHr0|T~^KgywKh_j9JDL+_wz%s9w-jG3LI2j=5tA$tl&+UkDUQY`n zhyK#8F?f%2Z;dzqVU6W4&^VU2Y7O;Amo4bDC@3fe5HtrKX6BGl!XqLOB@K;@nHg#z z4;T)pJrnMrtWzkpP?S4|Qjpt59WSFr4!|wtv5vemym6SmNP!rtWS+6p8a^!* z+6WMyU72-<0JKPMAEtoa*NnzwkE2JLN=4rQBFHa#JwwbW#(8mk*T2-@K(AhDUjJ*` z&|+5S$J1sMvDEAMvC>WiX2S72K%w7G5WB!{3j2!*{R=Y0Ur~^Qfgr2{RGveHy@Rs{ z4L*0?8^R_E^QHaF!%>G$G=dK@X&*@`CM4n}apn`Us3@<{%Yt5)Z-xI&Qy;bw;T3#K z0+4hGLM#*bW=D*vBzwn=ewnO5W)jMFX;Ws=E{0*T2bQ5TL55^&J*uX|Hz^e=X zw&LGw4uD$#u+8*0YY*%?Gym24+!OZUe^w>_CwJVl=Xnitem`3N$&3RDbM=N|YPQZb zL(uKiQnJz4uqFJoeA8aSuP&ctM~Ir>d}uP{C_@xwwhVpi305pfwmMr(~(H& zoOg>-lG68!*8>-P0TcLW4ZuP)s#z_?XXwiJINb}2rw2p8+%C#$9hr};TU!SvJN!7c65S*dYnHz zL0A2+=8t!#tBS?}q3hZCf$1-Fkbw8~IZ)f&K74pnti#R46(f*ZkuK!HVNjnKdvtW9 zTWM)#YrD6!)@CM3DL1lU;!CeY>@z2{dW{d?pRnnX_S`U zg}Z<7cp{{fedZ7GGM%Et^#B@wsQcQwK(`>MZZ(KoSWNg9d`&MBZY|VUSqE(YN?Zo{ z)&{o1JQUvQC}?o|K1Zp1kL)Ngou9cM) zFVCx2Rp4o8rZ%9EekCR_lcsNh{&MRhCeiMElh5_}fuZ+>lyUjf0mD9AvCMmE1t2Mr> zGauARD#IHcx2XwOgnqdz`uJSxCP`5+0bv?^OgPRno@o_iO@_p+&srO)F@{jKzPya0 zqkeBJA)i}8s`U7H>1)bYHXr*H5;#DRY{&Ec#de>iPTc*y>8d?5a#Ky*MXlM{4 zF|7aaS~Y00#&In(P`kn$^Lj`;fkR(5X;d3(J(a}uUQ|WGEs!Nx-Q#Z7)oGyF7;uw! zF?XV^HM!Y8#1J#&W}*e&6UJhzLH+vzTjMu|bCtVkWTXEr%bAzJT$aZ_L-C+*{R`Qy ztsgZch~}~rOM@v<%X1x_-Ydgf7Z;cfb3F5E<1li{rTq#}K?0sf`bF6FYn@%>t&S0jE+XKC!XH`kD?nNbNWG}Kg9a^K`&UGvi^-rU?Q z?gNO*^X}%XRO7lT=la^yFJm6*+tSt+E)i z%>%h`;>mJHCsD-J#wB|$p1khwd@XzMJ71f}!(5!kErv3=lSj1<>`)sL0%D+|IgJt4 z+t%A@I~T6tdA+Nz3k35djVE;zRgRFbTV9HnB{4l;fzTvkP$a-(Re`4qdr>6A8@&V$ z-oJa-yb3IqaF>P-Y)zB~07n$@jPiT}r8M~=3CZf}YHf9OU%F6=W?@&Rl0}x5J&rSM zWTd7c8X;e!=xsy&MXhDulST;jTq6;4OdWJ%Btl|gSsPCV$&KUMC%nXc;@^q3DkJZhumIX*c$`{wS{y$S3;lIB`_MaN;^T0U%8 zh~^5R1eVR0eys!iC{AAS|5`{p8l|DTb3Ogt>JzgDoo=i8N%;>`OD8`z5Urbr1Q6kpHvLz5_fKL%@n2ncjB%7 z5ZCe@bEAR%-D4VYr?Ef6&~aZDi0elztJ^#dM1%tll(};UTvNr#$p~p_63@=n0ngnE zO7VHoC>0olX{O=Q5pi2nNDzykfx5o4-1z`-#mx7%1lSfaEt{Vu&y7-8x3ZeV^?PPg zdR6(C?ZB%(2nULbl#FZ|Y@8}8SVqsSGhN9NfdJrY zPVAG`bieXW;OL0gy4T4AGWG0xW!q#X@#5N0-03HM*ktjV?!Ht`Tl27)OKtacw8K(l zaG~L~d-McM7XIdw)P-PrkvQ|5U_rjd(!hrUT8H!bPQ@WjnN;`T;nMF|(4L0<1dhV~ zCu-Fz-Jp&Gv;}~@wQb+-wCqKmG>X<_J}wR zL*hMS$63lhyeBvt^SbwWSr!R>DmR|H0a>l&mcAyvh0mJ5 za&UgRK^jM6IjiHJe^5udoZ4^%m&X5K*8yQ3-l6?)<);Z{_`ktW)ZgL10&eozzd7Gu z?hviIx7Snm{onne-&BFlmrw;*xWI^m=KM-k^5wbg8pv9d_eKJ`N(2q9(v7gRie5JOZREk!ml#!9~wMV6=Lu>OR z-s%?-4D{01Unjul?U*vqRO&rliPfT-iPzP%tnGU+s-b#g}qDx$435lem5$+gXWiy>Y}>Nm-w0YA;s)7qjBm8 zeob}tvYP_iG4kKdfQ0}-3>-CIoEI!a>+&2TQ>BgsW>vkx#95e|)+?lfu;TZ}-ucbG zxYLjfSv?XsTvyj@?oytvYF+D9E9+VQA>_tIU05Sr*^8O!ndHvHYOgaM349sl2!bF8 z#BLQ6jpHhWVI*AAtS~uv!$GZ62TU8j0|fz=ps22+koS4buqo|r40;XLqb%otmc1=RF`$f$%HsBezRUCraXO@uVnpOSh4$NaEC^T$M$J)kGpJE zUsq}aFKo%d=-!lEgpi<+5H|7N!`+?j%JsoAUzj!&5)>pncyMWMfH4krM#00Wd_OPm zZ_1O;vGI zm0%uF!D6EPE5Gy^WWft}zBoZu#vneXc*RNl%C@<&1jS*E&Wf?a|ZGEA6;1!R*_QiL% z+7j=bYl$JIpx+lk;{W>fYbgI#kWx04#`H2zPLIai=V_7K`a-s88=LaySU}GHx1x43 z9#+eR3xNHZAP5s6u|9N|xV`=E&z2o3Wn3jlct(f+I0psGnTGXE;evrMR5%7g#_K$Vu1rJH zBwxz&ClBrETfq94?n!O0-)60T<0s7-(@@u(3QA8W-MTmR^*e$057J=e#OaxP#h+9= zCjYQToLTDHb4E1W#T|@j0{=t{L$)r$n7Du?(TpU-F9aOBTF#B3yf<(@_7g495364` ze-veZP`s6IKUEqS7)a2llV{_vwC^5inCt2+^NX&Yo{Dc+lxD3?YH?bc<)BP`ef>Gw zRRC_x_6j%+7IP_sV|bbEC#3&&eP*!A>L?ud3-iVw3&6FY2CB9Doh=GpdTwW_vq86S zG<$NIhS^OVq!;!WvWa-X(a4&Qkn!VUG>HaXoKt8uT~$3&$X+^^%|w>bv;fh0iJd)} zSteLOSa^4#o@-FheS>Ey*L~CB{+V;<2>9T`%g}z{F})85W5_C1(WaA6R5Zf;Sb@j3 zkU)nSZ}(j(DM?d=cRKSMsbBJ=qJdWL*P796^m+mdrw$UBz?6Q+=c#-qX6Lmcf$UBH zQRxrS8#D&f-$h-%4l``(-+#LFpdp}vS$LJ$&#!z43TiZUNR~qC{bWA7{WhmG2)-1K zjU)@v)fTO%yF}~*G8+`698gQZ2$D%UYmJ&&pO#Gj{5&j-&RJJNcjHbEe)CsU5Nt4r zy72rEab0QX?Bo{_+4|NPMo3857{1BZUt-GA2S5avj5d6@+JiybTC=)*1%Gv1S?~=OJ;=t`BaQC90bL<6+sM!m63yQC z3dPkHESrFs0)D_)?&Gv*Gh&hx2TdTQtnw;kwGrRG01{2&gFf-7p7hpHHIGw%rce^_ zo-{|O^AHwB1-o#M$RRQXkXs;Fq*F3S6S9oxLxq($uf8N2f(6SCi0h;62Gmzn^sP^+WJu%%BM(hF}#5E zm5KPS@|OJv>-isds8`{}08#^$ zNN7`VJ2Wm;IVPJo{h#t;5N6~4))d2IW^VwsKD|2k1e=ggkE3RGV>x}^GvoCn)Wq?N z-Ntw4dAR|n+}NZ$Wc$^|Y^5=&#Z=xugz~&VSdSv+;2spV33As|8o~q3OUSVUc%b?C zSZ$BJzI@umr?6aQmv3Es{4)0Sc=Opm=*)Zcy#Owa1Sy7$;w|c|%(TC~j*g#nB zS^>tyh3;=esG)Bf`5CuA_QjJeUT&&i)aw5SuK@c#SOoq5Va)z1#QtCXp}*GrS0iye zm0{0g0+5B^pTy8lz5c7pC5645oQ1u##DA>eZz$paf;aj8vVClfDueH5-wZKdWIneW zws3>997-WR04)Dn?MdoW!g!bRB3%7zGtPF6o&G68s%bhAya*LZNpCA>$V~)>`R`uS zzwjGhnh@pb!<}7KsGhb0z-gbAPyw&f21=9OL^B9=l9)?u)!PmbPHd1Jd?b<8V%m%C z3s2CP$p@;J;MvUlC9S;xzMVGvBa z(4aYG*7kLy+AhZ;Bfah1iF~d<&R$+h3UyzW8IFcTffok*eS(MFKq)p$Mtxq}=)^*^j;s^Y4zQoRa?R%u#37MB`O`vG@c$?W- zFQ*O(yN|idlYKm`Eu4Er;13w7g!p>t(?I3-p}tK40m0j3^eh4gW0Bx0j{X5DeXlp% zObb9{E930x5)|-Sc_CH1PyTBel~}QdcvRhXRJ-P~q-^9ViyZj=odc$0kDsNp^I(AT4ZXG)*AaqE_gvoj$v zF{8LgxDrh3wU&Alu&bmTn%Ugk6n9;DE{wf)_h&W1oaa9L8%%j1qwqk+p{MDxb35It zCC39X`_}XgqltBlj~!brx^F89eeD1KD4X?7y2DB8xy0Pm+&Cf`yEC5{o#3HH+KWwy3z}sx*I| zyjT!Pedve(hrA>EZirp+h-=avbeqDWgzY9;U?x2Q7$V?t&rcmN_cd0HerBSX+_?Q)Q>kB|9(;?&aht#w-ylvbY2lP6DdEc%3;=9#kB zOZ=<3f4d^Bd_){|~b z`_C`XjU;$aoDXACUPpeVJnyv^JO`#oum$4<|3FOr3Vnc+#3it?UE~%duX{rLG8x@` z!K>VXOS36QXB*t>l^QxxBwp9WHYUzB){l)WjMXb#;^0_XS)n+57f; za6rk&1Z}@5rRi*cc}7Ev%0_m4?f}Atd<>co=pQ79fZS-orKo4+R&cibyX@FqcEgI~ zrut~uFJ7F2oYXpSI)aVgPPRPYp)rDx5L&Ugh{D+T_R%%MdN!q%m5UErlg;dlv++%( z>TMYPFHb>-MX&BxG_v{X01ElMgrf5HF_q5?3dRuMb3K5} z6j-pJyxSBKVH=y9z6pSJM&&q92Ejw#obJv7j`xy!rk{W$4z~8Ypw1Na>#;~VISTr) z$VmCEU{k0;B#$_s+%3m@mAvtpy*FAp8X9KA+{mIwyv|s?Kq+(+FJ00Od}u1y+So4j zOf?y8mI>RqlCk7E`A)6Ov1k$S>|e*(JW6!_N^Js0nHQ)C@HXtGa(;g-4|@{y@{Sam zgS>NJ);Nhy-;scxp=$1$u6THjtBsGU?$Q+pp4HZ?rV?Yd$NxczN#TPCg5Bg4E?ZT7cd1Rj*XDI(B;`9$PgbfK zJ!2CR#NF2Bp@@iyiS?p-;j|I+MK^ivC&-PllkORBj^qd4`+l5sPmbj-P_zQ>$&RjKu=-2hN4atbfo&CThdfJsV+HHJ!15zah~i<6G!5B?t|6B!NGxLtiW-O52>+KL805<-_K-~ zcb%hB0VnN^-)jncb5E`7F2wp7c%!0(zRpb1dgI2b*f;SW>6(J=li}$h=0@O>rKKT( z)*%BX5Aqo{Uh8{1>}CG7&1*u;8#DxqX(c;sRxcUFi(z*kxKII7z{bccagw57z5jN( z{v0yP_LhS_6tVzbSP_R_0;H<^2OXe|tNR^UdA$c3(c$6YwO8YxuKX-)k2+=L2t$8E zlKgT3YR#-6^|_@rE5d&BQxZC79*;?oN{_v+2!ied%&fIy56|v#mp6w2RDQsN%3lB{ zD2Ac)C+C{rSsh`2mX9kUdnNf@E#QYCS!m_G)83o`89bP9kIIyOjY*oTGNPal(q^IG zkrh2AqpHC1JAMs5TttqkEIv{7XEygQPEwZ7bQ7tMF{TSFkY$I6?SIa-15Y#aEIaD1%^MTKK`2it0UY?SG;lhzYh5Y zqyse*E@VF$C@;3Y*eve9`P>9DJ=_;YpR}-Bh;J{oy`baw>mS~uB_qS*EdK02kIMk6 zbekY4O|R+d-yP_8_PS|}at}Ytbn`~gS+yhVx)m>tPbUrzdV6 zS2|8(fT~Pvo#1U%d-x=@OP`09O7`w-h^wWI;-G6c)%Z(W2a0nQvkgtfyW^{C4dHOj zMcDR7EDq=yF^Il-sAI;WeOo!*m_ZKiafoZ82`(-!-kdD}gs=|(+t~0aIAL+=41<`P zQ-7gz`BznCxa@&%BbE8`E;N+H7UAJhY(=1Gf*fPIBG|!^jUhBacB}RLF(!EY@mNQ% ztgyL`vU&W+)Nh*1r9CA!Q~a(}@fR#&r-GR3ZTpEQY`#Bk6dETVRsmuD1Qk_9kIr2~ z1B2YWylqf5%E_5OgPvM;&o3@b+7;_i_2bk&?WTP&CW$_tgU8Xhj39xdi=Z#O2e1{I zWh$(tAxus$WMXq%ukAL?gqbd~XO-bb$kihS`#Jn^B^BBzNi*0AbBj$4#-zaJq&ouv;CFWhEt z)`*m2i`^c{3btIbTjiJ=oS2|Fl31|Adz^^Zg3m#cKIyWHh54Pf%W4z6M#tK%9qfJ0 z+Eh}h5U-mDO|S6M@%R*d#!_emeMIqx&y7^S0yc1BYz+7VrDU)hdTeqE3DE}EL+V!Y zhYuev%K}_|So@aQplbz2DZ_#+i*v#$2GtI8+3X|ARVAsrvF?FU>DxgoapBT}d4kT$ ztF#!?lSrx;K3Ht~{W^7*q0gdnJiujPS{jBqK_F*|bZ?*Yk=|HIc{`~{^f&j`95L0Zek4Fx2Pjw`Bg5QFa z<`Q0tCoFUgPv_;~IiAuFq|oY24>2iJ6&A{q5)ptb?7x4#nbygVz@`}12m9oGYmK=L zlr!pH3=t3MrPtH?2!ybH<>SJ_Lhy9>w`LX+!0 zaSirQD($!LAYx)$tOR^NB9$90}6a<_YTOZhWzGLb)NDo5f;t)nXu*7{Xndt^+* zOpa$`;)Cxuy}h2auLURl<^J%E2K+b&N#%kW$U>H~FD$Sb$*zL(xm7;C4S7FarxIbY zZ9Nxw3K?4BZ$G&Ie)CHp3sSi3rFYqKX!g_%k`O~iABR$e&%Ku#+H2LbmD|-)dTFgx zgpIUl%!VrCoy)kWD28j;cdqW|61-{y#+doa6l}jWeDHCWoR>IYjBEyT?y{nyqSQK` z$6g-(@+?-)$!QH5DT;bX!D?!1>YWW|x%+}= z-Ok+Z4rMS@R}%Z3#mL>QzFrw?Y;fPV`JoD_b$Gl{ZeY>iqPy4*ElHGBeK4oNJ69&j zltz%cf@Y;KX!7sJuASyHxsu}lD6$#?x^$iA^UGSAK3l35xlU`VO%$hZW}j>8Z*rmw zj*-6|$UsnWfT~2^Z)}z{UpUBtiF}ygFk8^C6}>qP4Vw`7!tP4GaYnDqn}QuZx8YLP z+nbi~z5u2a;f2HaofPJ>aE$fCo9x5VZfvS3tr)&DE=XF|@B-6Ex{j<^tckCW!&w?m zUw6?uS4m!dWI}hlSJA@4!ofwK_vnuAb_%=Dlf}|=Fr>_OXFYmA926eT(TcMvxMS`N zo+EsVTHljH(=TnTruhW9^P`#(Af^u^B+k@&*G0OooKhc4L8*nsYPAHKBk05~g>&S% z6~uXpdS;677$&|b!@cYkI!0Yb9JGoWkvWNW57Mm`vvwa{RfxF_i%XN~q}U@`&CSgJ7 z(6k=*_0}7OZ->JHz}y_?hdJ-tcZA&uNr+`%+N^R=*0)h9>ONRV;dD;HaxqyAVf*r@ z9$c6e|LC%C_P7d)Vf0>^>*Q0kX0ujUmbELkV^cve*Yp`KS~g1V)xD8u!X_N%dv}_C zhnestH&!f@I7WSnRJJRH{FIF2EU8&$$Bvu{jMwXID(FW zK|B8L2cJ(kG7^%F>8$3D;+|sqm4_Y&?=aGTR!Tas`BBi;2x$J))YMAcQ}jACWi6tI z+Yrma9m%}O)b2w0GqIQ!A3H?Y631bZqR+0fE%JAqLicfU>|8Vqyhp=dOx1OhArpX6>;a-?6WDXV4WDeT+I*0y8VuBXcn|4i8VqOrZFC*(kZ z6kDO7+jHNTk246&g4vXxikNGp=@hXl#=mAI*wVUl=TOoBc!5Accw^%c0c%;1oBP_Z zm_;u9P=QtQmNrQ&nheXw!xO~uOua`3QgJN@K5{%mAI4&|3mkNGbXrv5_86OOT6p*F z9bqtyLyAt3P?up?NXcs^EK6TtOVLBe7xs%bzA^1j6FXQk8+TKXRyWm{>vfUr9ced$ z1ren6q-mE@T0hS}Ioj3trGhZ@*mT#0;#V(j^ zh(^zWY`m%WGojWu>FuL7bm)WK&fiypUy)kQF4V3qRjn-MtT7AnpC*w$l=nbHO4Rwu z*6RahCcgCFjZ*_79lIdcyM5Fs0($J-wUJj_N&C%v!P*Idm+~HG5`fX$p0A)R|N2%T zLlX?n9$H$xK7^#;y~3tFXdbwcgGcMTAR>V_OEW?*xw}2*nK!UKo!-33p)D7<^>AUhndXA1{3m|(v*t1eN3S6?y|BTuZ$MmCA;e!u zDKKA{_0sS=4vjc-=8Sf}9V9IayR)kvJ&H)y0EIwN#u@<@@Fa#`xH276sNXDf@x%Q* zw6dpDIR%=B)dKmBWFHEp!~x@uBG5X=m*N(licUotNN@MQxuir%dq*xVocYoH{ZZ(5 zPva_|yvHYk9GB2xC1rRk)q2I4A?WURr8$Q0*OsHaM>#|Y>sCHc{R7P^@TE0)+O4Gc zzGYo@y`8ThYQiM=&L)^%n$QcY!iN2G>XESS+q?C&s%dp%W<|DD{PavxTu{~@`s)$q zE4v-<_deGcR%Hx+!-*V>v^RF|l~9#ist@;meKy9XMZ)IOABRf6@%^q@!$*4sZhx!_ z1$>dc76M${DW?`6yy(uNkdAM`)kzZ6KEY!4p*ZObAIwR#6X91jsFD58pHQ;EC#L*+ zDK848H4SuA6>zSO(3LN|jhcuF;raQ|b1;*X@?FuvEcm+lpUZZ6fz7e=$@HZ*`_l|I zS6{Vi${eg}l|AT+m8=cgC_1N|c$()m08AN*f2`o;x2ofF(@(k+7u~NL_uL(C$JrID zST0Sl1pFVL)coS-c|PPLR-tN@K@)p9RAxPfKPjz){Y}T-r=47VX&3DRi^ev_AsDo` z6N21}2=3A0+mMS5?6Z6*8M<5%>fx_dWW_44uqw9q^BiIN^e!jt3gY5h0Wdi^Gc8rW z+0(>!@UI=wjzlWIBzl`QfBa&PG5OESgKB(kluReq7LlBVl$$nkSoY}t$k1rjiQR%8 zG3ahI`kgP)TPhTCzXslev;4Z?hlIntZiM!;YmQSrvVpU~`0q-BLeaw-nIK79@ZwA2 z%iX?8WV|g!=lcRf{La3HU?(O&e_`*yo6n=q_)+4Y=SgQ}C#swdr@)<5)VkguCjg|x zyPRK8oW-}|9e(ZpsEI=EvE5rn9$onD0S~`}@niaRPT7C=Xur-7N+fuw1lRqEy=_uggr8zHV}-}{ zQv|Hc*ZuQI3&I}%by-UWdH-g?n7;Y_zx&h749p)PLacCpF%Q=C?fa$jd+p#^*-pYJ z33P?1^!CcPo-KHH48%xAx8omR z-7l%=md1X!<)0Z;QJoRRS(fwUhn|E+sGLw__1T4`_)ccMfk#ERfIyb<@!J7yf+kqG4se?n+xG6W_;)hAY+UDlw&Q6+Ml$C8p3UV&4sCt?waD?NQ z)3?foT2qpLS2_ufG#d-CNq&#dMeeM0z92&wr*_{}wPhWl2zaX*k*|+5rBQ{?bW@{e zOmL3CPEa-(USgHRC%V}hww7)jaZDAuV|{)7ngwUyTv={yqaT&xiA<+i>uPGOY;89| zwf3Xak$&nJZtX@?ic&%7peoua?06NKm_-P$*xH0%`^`x|QlES}rm7+iZLz4b*yex| zb#rmiuL~_ygukcR^C!u0r?Yu&=@u(7acPF#cLti`*q$NS1T4yGYF#yN&gFIJYxaPh zTJMd)4@$Pjx1X}eh92ZRB;)*cn1LXah=)$=j}LM0vY?FgZ$8B3P*v02wTAxU<6T(o z$iBFo1wFMMmKh=P_dG@J1Cpt;v(RIC1dQsH!v{Ab4-1zEpOyyIQocL`I#tm z2I+9_LSC4|L0(ge;Lf&c$G$i6QL)9c-9X%1wUxcU@-SXDjEGd!Wy#C=JR4hGb#;QI zFH!X?#tSFFuO(tND8a?W1@fT=+H4!}UT<+!FsEj!X3k3vXgmeZp7r6F8z?D(mj)09 z7=>`CA0TaQZEd**uHL9O4Gj$keCxv@^Vt1HvF<8hM9F%9c@Nx|J31eD_JLUl{6oYC z4^H>wylStluP3FXbhNWuT3s!1-{fQUi?Q`a=@f;MKF`9~iMY9g^aJK9R%5&qNVF#`N~%8V@h?&`Ksk@ zASmK0G6Tb?QYdl$iMUD&?S|uRSj?SO%*uh?jv{Mahk)r#?nvPMRcJ1glFoU z-u+0MYDVwoN1=JqLBFopy5fHCL~{JSeX=o`P&0EdUjwajmt zYoU_k%zm-kX^XzH<>IG`r=pMAnzfpXmIb2ZE_V&T+c@_#)2NZ5Q#}1v*lG7ObiOT5ygpUatnN^NGH!X5mh97kpD))!pS!eq30Z9f;&7Py@Zh3ibcb4oBQsjaa3q~tK zxecGDrEw{FIh8uRVieyg^Ci}O3BTSr^Wm4LC-iL2*DsDYr)lMJcSfm+ghw^PD>0*@ zm@ZG~M(IU^!3HY4oGk$07+;Nk{{OS&Bto&wKl&u&-|1k^`#n zT=Ew2v0y{SvwD?tVuu_~1=q?vl;K`2=iVZ%C%ACCjGO+ZrISTjTTg<^9e1lLtxZ-w z^0FqD^XE&oE*m$6Ll5y5ernPG(Id#co=c}^WSpCxCL@jX*kH}(1ENE9{Piv?J3FZc z!Gi*151&a{Pme2fZ-bJ0jy5#GKlXXGo+CMEzsGAxT^?j=2EQJ7ANUU@<4ATAc~@}F+1OmP zGRe<|!U(%zmz$cO7t*1fu)Q?!hRvsz&MB7;8Y4{4(s^0 zbDF{h9#rv1WkSw7QU_)scvf0J?*%0$ayxM)j#PUeY=2<91Zx za#&GC!D>?HCteOmXT-s7!rt@;5==onrai19)8j4`1SeslFikO`38oLejg6u0zTfBO zniqXwW5azVR49UCrLn$#B%ac{wA}~Y2T8rq%IR{`0_zUOJv}ay54{71YEm(`k3W9! z=pfa}lP4GM=jG)Q64h%at8jK59KShxi>4+EGb$0n^?)T=*G$hI{vP37MA+N62X3<> zMij)NL^3eva$fo|oU?y(fZ&1=Qfm9d1>?6im(l4$W<3`@7^3907_%{-PyE@2u^IFc zGgjE2*fLxhr(EWWu&`>Qwrl*1_Bk+l&^GzHal?Lgso-@i-}k{M*Fv(K1l1qwES4(|A#5#mz)_%RD8!l}JiN61<>Qa?`$%W+S4C{1 zxlQP|vIDbZB5P`0e9@wKC&5*ZEhm`PNRW?w>s*%D3cf#%_Hcg7)BNr&B$SkMeFgmO zB@Q#RPQBcQbwG3z&|c6aR5@12l@kN|urlrXEG|ASu1F(J0W=IjK|#1ApklEw>mp&~ zPbT;g1p}KAa{Y&u5SS$CO<-KjfLFB0YAKqXq!Klgq?i!Z1b0%Dt83ND)@JHVYk2`C z;*-!}GJ3V1c;8T)o#;1jY?`BFd-HZ z!I1J%z}m-rj9=KiD;$$xMB)2xxZGhIyTj0&=guPI<$_nIMSZSU zNLgFfpuzYEH?4wk)$>zFUfn~mKYm2TLy^!1g&(G$u90$TWG4b$?d&PnfSc{fp?sO? zxx4d)Ptu$70Qk&RL^R$kany2EHuv=~^iIC*k*P9#p}B6rd@`4XAk0a5Rbxf>)DPQm zq^wgYe&0`vZMJo{Cd3eg9#lG%vd@1@D7h8)S#z&D$-J@wYBU<(QUlP$L=>E&?Qg1L zf&%a{hNVS3FOI*L2iYDknnbXRwdfLDf?2@n!vPJL&%N~Bj-;%dnI;68oCL(VP| zjzWdGc6YP(fU;xOX?#RHwpa5NyV8=c*$0}ErBo$CGa&&XK9g>vsSB#hT%KEqovrnj zn%j)-m5rp!lO18)h9v5E^5)qyQgwK<2%Z~~5?@RD>j0SmrV4JRKk>X#yk;)}$P1Kxri2GD4v5zr;4gyq@2d){(}?r4kXQU^ycOIxj(PY;Ce#>ZsE z$jdU$n=NuRrXADp&sM}jP*QJ4KtbN0?9U3P8tSB0hO7bI@Jwmz2KEQ@^7hn5wHy8Q zx^?@F%2n?b(+3v`_vqSQ+%Uqxtoj$z-{nk5R`}1_C4>t(G^6sHv#|a)xxA z$Qq_Tw_y7p2Mu&#agkBf<^0+Q|IQBa-F5jg&)`k)a!Z~mIfx&rqd9d|_~W0H(tT@p z5M>I?Ng{dFewch&cvLS~P3_2kX~#okpn`e#ec{^T7pdkz6IWu6qAZ?S9eV9cj0`(# zAy4AqielTBjk*7qhS@fDVpm;rdEYpxSVwm^lrD7v{wM>PpEM7iVEUbo^?kvtw|vbx z>4RNfX6d?4fddlY6PHfmktXts-S_ueUxJ=v>VAKL&0xsOJq<2UMO~!x7d-hAr!%*f zJGTv2Fr zWl#2jztIVQbjQ+H5Su(#>V?aPE!s%_B$Voj9?)G|`wxZ;er$MdKtk8|ckS-4j{PlG z<-mVsgw!;3GVtt2(tz++|4HDYl7RA3_{Wn&z;xSxVbA`(+8%RvXg&i6^4CIt!Tpin z^%u7{)_)YQbK{>HWb5MNueA3_n516ZXL|U%!&nv8J%WX)uJktq+8-1V637aS8qPD0)%bP%ggLpb< zu^vWrH&o2Ajl#mdqd#VwT))W{`IxRMf4tNgo5(_la}0D z*ry?sAj)+q)aBdAD>UXvd&!S|jvkr`@2ivWXRYsSkv&0KjMo0RC??UVciCCp#+0TL zzTJ25&-9i(vrSf1P4N%4$_mHF!0>9I>XNN za$0w!_;DoyM2~gp5w#ZWU0jThUlSWW`W)aril2f{Yg6xT0WY6`vfma z-H=pbSm4W+2$_<#M83FHpJ*93T}99$5E>LC);zoy9lifoR@mLUdQVhe8|ESRo#W& z-_F5)k@QeF#)iSsq#O!on4?QYV-ZKor^PVS1X9Lk25xu0HC2Hl(+A2(9sUp8!j}6# zxCNj2KfMansMge$eG7|qnLjD8-!O4Ve43!aw~EZ^>nm~erOmBjOI)ZKuucwqR9^F@ z&AF^&>Ey`{&8kz`uV$6p-_^onWLCX+F=~iTJ{&~)K<6JaU#|&rMS2Iu)Y$Akne&N0 zUmuR{bK27C(2#s_Rw$**R$#lqey26M5t`*PizeLb{i#Q(KR;@J6qE3=u>F%F|6_6o zsm*HRE$yYd2vTA+I%|r@O09je{k0Ki8U5VG^rc)i0TOeK_ohv%B>c-0 zAmM+C;l-W6Nf_y<)Bog||C1uG)aY{ljtP0dEN>Y(Gj7?cxlDvWEZ)D~A90h6F;`EG zu!p8(lga6Znq03ZJ)fkou4vif56z2Tag#T9Dk(Xdn|!M-RHmL!e)BA(VzRE6Qa7(& z^IWO#*Mf;>nV6$R8-ZgT&KIK5@9j0q-j(C!$k=cXrZZD@Ma6Fv!?pWr*u*trw9955 zs~!3I>2fe^x1pN-(JAR$lzu9!Ht7ErvBUxzC)iZ(cDXh*7MYJ=7^H@t$I7OMz|ne# zXlJ>RCfi2m_7OT9bF;4)#j5URDMLYQFue&JV1pBPIa8XnvecWiF&2iRYb8Z+=rXf3 zPh~cUn>01v#yB{@89x_%yy;EQA__VB$?!<}ZD%e4^%#&l*^fN){TKE5_>@|%J-^$n z&z(pK%i+O~Xm@@+0;SWuZ|{o2){zGT&~eGztd}Zd%PoxHmmVEe_m}L1bqGx304iF=(^{U3&c{%58xw3ns zcXcxlQ)nc)H2M`)Gu1%J5eU8{yVLU+x`?5RKME+zbq4F7pd-*?4Ae%*nthMRH33dT z;9raKwQWwMB*q@-5o6F<7b`nX-Ul_JlCq2o0okT&W#Lx+PBSC~Fa9k;G|H8(( z#5#hd94e{zda9zlgvz9dIWK4`ldnM*li12dHspvDD`5*1Js>h5>gYXOwW=;^_e&p9as82%VRSohbZD{pTUr zQ?YW73%#wv_PhD?(HWR2vj3@S`LCDu#iLDc z)g1O9b>}=wj3{XOc@zHz2ki+Ff}%0@aTS7!`1$%mYW%V)JCOv4GPcer(`}oG5lw&tp%=y^{upVB?!E5d|wSD$y3l@1I6cG`& zblORm?Q#yCJ^Ig0`iv=a&|(9X28YVy|L5MoGqk?|At15eL6ZMK*1y4eznYjmbr>|P_CLj0 zrQg`p{#2EIBR(QriwN|5@#e=r;paQo+yCiqd2C;{2c%(#{e5K*`CKHxKm6kwnt~wI zw*Em}NH!e)&p&-%h68fk=4jjw-wqIoU^hN41!hjn?Z;z#JNGYmX_SsJU?=gvc0%5l z<#y}J9T{-fBpwT@!;5#Ig;^j`!@S&?XkhlU6fOo`J4@6$e8`m6h#ZTB*9-hk@s`++ zAPt-%Jx&t$dhqHUVY20`RyOc)XMTR4e=X}L1uSzm#*7HX`T1bnKmMx{5}B2iH2^P! zgMS{TR*q3q&V9$*zyg{p)Fz7b){Jx^1KV3Tt+B?*P60x&q&+_ot z;mxm&BIKvI56aE2%=GveF4oxfA@YOQuOX6+T5zAF75ulEK3_{1tz$_N zyBP0LB`8MzpHVXqU_{TrK*=aZMs%F|w20F_f0|2L6QC!81GKd5`DxKmC#;lqHFasan~Gv)cD5hJZQ#Aw_dn1Rity0W=o5rEY_81k54ceU7ubDY7z=q& ztimh6l{g3n{jo8afnA0Sot;_uZNMMRFD=pYnRh{Iiaeh;uVsH>UUD+K-L)Gx{N7&| zvKhSt{AyuQ(a>WcQO^07m6f${I2#eT91e#0uo{+_2;Jy@>YM{P=g{<_2DYCY&B@8BlsnhpB5~vmK5yq?k+Ur$(|_15x-0jj=%?0eYi|{RgSdFGfO31E@p(&(6Vb(fi(fWB3_Y-8*(-X7C6eJ$khFk{jK@V&Y)&t@-N~$?qZc z@gQ$sUteC&rzkK};!d6>VopAH>v{T>u3j3hSJ$4iyk`pwNi~6}jr0alLADIq)_3fx z{Vr3SbQJ_;TYXOuubw~m8-s&Tz5wT6FVDO?6Mi*8iq8z@%D300if^fJy`+a1nBGj) zYM41Sb1=qVe?))j@hwtG-PPzLP5voQZ!TY}#ECi0d%0-}I4_*{+dm+b>oJmCV^llg z#pplg<>f{5H9zDuhezg<$W+u;E5Aa%Nk&iueKBF7&IGcymxaJ9+D%~G}vMHV? zIYUx7`Z_03Rd@>TKduCD?MR(Kkb`Xpb#?))^caOcOe(V{S~$nHu@ z<@~vk4$HKs>nm#=}z2(zg^t^2NLYNvH=u48OrF-9La=yAmf%yWI z%;iTHrWIrYftN|z$I*c6FY$~NiHE=vON;)(Q-!UoPggNh3n_dk4fG@gdT9+og)JL;bH`z}cYW`xzQ~OOVWRv%mx&tu z%5|uP6*F?0Js^|#MEZ6>`~gHEc2+Ur&WO6LSyeth%F4=m`z8Hha&mv6+S*z`-5&00 zv#l}287_QWuCMwIZ(YlMoKzJ@=e&tY@o=Fu+M7+U{5^6wumG`06=!U!??U4VsKoCE$<*8yO!BsQc^PZ2>t2R zOj<#bA3UQU>cId&!xecKvLLdT%z(5u>CMe}F?6(HV!~u$Yofo%HLuSUu~|N!eJPI- z-l16piJRrufB^5VJgh-+=umo2PR^CBBV}tpMrzM}v@#p34|#Nn&{oiCUbl;ukx|~^ z=+wb{DE#;$R!+mkDE2Ld^BkLp_Y$VZqfB41=wOs|A8V=;gtMmYc(fXC)G-xGUS zn1i)O+4!CQm}lg#Q~OW9Sh@&8=HTo+n0uQe#c3>FN5NsS7B5nYEiLHHwf1%5l7l;D z7Yf-LGjl3e$=2Jg9~&@i(<`r#u?!O&th=lQ z?MO$p@a%EKvdTjwiu_-g;sBP~|JnzoxCm{YZ+|V0ks*D^QV*Y{VDbg%%A=* zw*P7hyy}Zp3HL(W!4E zLs!Qw1sot4o>A?kMg&{pRiJ(^f_QUToi`%xp=OUh{i?mFN#_~M>tuF$`|{PlKa$9P zihq`S%#ifh-<_q{?4{Gfhvo9xN~6KIzWO3uA5_uvwrNpvTeH3j#uLv4eHQSZsE^dw zgTeZ)T4I>{@cUO7)DogNIZSipPVeb<@7m(=#KWh@ia^%}kIR{5z3NbMT0sFJaKC3? z6b(E6RK4e)G;43`3rsjTJe72dvC>Jl+ka)NiWzd0ya@jh)&D6t^xS5EB70GxaBI#rvZKyHSq~z5hH; zfA^CrrqA@sURIWCy2MAsYjWa^{n%-3a;;AAs=bVpA!t)UUGNX5V$QJ#2K4OMR!Qge zh$xmuTMH4tF`)2DXSa-o)IbLiD9ArdJDSC@c_f=i<>bp_XrU9qRI7|Mh*KQ#$CH|Z>A_bs$B9X}i(#S79? z?_{FjoHdrurZp2NI*~fpb*0og4Cd-vK1qZE%H{>m4;)!GfJdqDj7T_w%MV~;ZNSoh6ah?l!`|0Gm zS52983hGZHqYj1IsGxF4jFYlPB=4DV?>`r??<~1AGc5gW1Sssy7m+0OqWv$AE4f;3 zemtDP&i_#$683{RWXj)NpI6*YCQ3E(y#@bshyckyT)qQVA0=Ig^fQIn?Et zn1!cVRe)w4VB4>dNCQKSjnYpV6VZm13ipqnV>}`lh3+`NMPwd@kEiD;oN73`yK{xnDiCOT%$18#a#k>y8e3RD_$Hk#vB3gU(?x

aLZSAQP6rf+pul_QfED}7;3;&3u3S7iy*E{rj+qK}@vk2hhooY|+2P`9|P zY9~!T@I0toFo_}>Ju1VJ^u1`#ccT2MR}Nk$amE;#Wl)sby;fs#iMW%U+S5NsP^XYm zf14^c%66-ZME0PibhByykPY1pBF}(z_GFu3SgfA%lP@JJg^&B{^iL1(Tk~@Ha5(Woc$XvOTpD zK+d1_&g>wRuF2si?smRzUj!2Zy|$@TY_!Y#gf=CukjnT5?=<+nQ*`fy%eJT9N!{tf z)J+*3!Rb1>umoZ*!E9-cFX=K>(tH;3=cs zfq9;ex%vfwAv>No=S`yp2SIb{H$<&i(Y`~dy}mZ~YC&M!=$mhM<1dQ^@f ztw2$PM9S?!?$F+6nV9KepRWY}5Sv{u&^mux$chwNV-;rI{WrniDQGMpxv9`yB)3)yEYBYF9CqaJ*=Y zF6mt*TZ7hm?4_Ir3N{V?Y$h`E#W|Ye@I06;huH!NX2?Yhglipf*#is^k{1QX#0gGz~^&mIj32S&MnK~JImC2+mA1e z4*YwJ({TjHG+fI%iz%EZ8B!ACA6Ne8Pf!oz(8~i?+!jV>f@Wxlw0aL-ksmbJXi znSg3*#%63_kdd=53QG|vgRBiqVn>`fn5l=0TN7S;}6M1HnS{8hF6FU#o9 zUWGqKus{J1=A;d534vT|cR|3#F$)X(CD;mbA7D&aP}ss1xerd=ZT3}=C)*8z25fX* zUOs3%kQm!)P)-gGHU(bb;#>(V<0iG3+h_BGc# z=?|Njk{MxYR4W_&q!!~* z_Y8l4tYH@NHN-mU&*Z5gRkl*Z8fQgsk3AsLSZ{lVfD6nkBTX6qjS4ED<1;Xnh`UG> z@DCder_R>1G0IftiD=*BXo1)~`;XD#TR1j^FO8EwtLX`n$D$ISGY7@xl<>Vyhm4O5G%}nucc{GLam(DfX%N~# zmZz>bST&{6F-6h1G=^U-xm8xJ;rBhSFTCW|Yz3j*B_0RG*jU~;7Rjj{3~>KTyp4d- z%U3e~$MA~j&U+8X<;%<796m)#cgV`oNq6Mf6<3|-E_Z9nDAyXIO4f56*5ca~b&?Z z2U!lL1JIKK0tKuzGKoSX&4M8$s|y6nx-XjZojA|mp?tU@>=b;1b)wMhVESPA*R5%Q zbffvKpL`|th$&^WPg8JhX6Z{R5s8cWns+=sS^6dFGOxb+??{DUrG$`ld%lhJfVU-D ziiCHDQtbxaASs7Y@M$V()$U7gQs&@TJaQP8moNc-lA{qaMPTI5M&Co98_PyT*)OG@ zu-YXD_QX8Pl5HXf7&14=z-4~81isV~Zib;!*O(a=a*k&ec<;%h}HD{Gml<4vkG8^J~Kd^*P^4r(Za5Qb0cRk@NmIf5c zc1KI4W4uuX_NuN(!(&w;?Y*~1E;G3-ZRw~~ghOawT)>9J7^q-&z`Nf+HFWAf`d2*C zoP>>;ptJ zm)++5VyF+K8z|*-Mkp)^vjdt%cr2i7BJP9C4+r#N*dtW48KJ}wE;OC#W)5LHx_#Hu zpeli)jFgMlz5v3^c^v5C#>_h%U2N&&@xG>dD`lal7NXW2=6PuO`;Mv|=AB>Z)=igd zahi|3l~^GW%b?q+RFa?KZ%psUEG<=)W@37pB8Z0iR}n)ji_r5BpWnSBFwr^%#E+%YCK?7TSb9js=vrA2<51~U}ney0qY2`_(P^bzKx)qo}N#2 ztK9#mNI}vf`~p+%B54@8C>DZjv*0Moek|(St)hk3w6Mf9Dtp|5FfUZi3dOjv8*Ey6!QgGaJW<23vPNM=L%%v4NW@X7w7^!frRqt5Q2HPkr~_< z!skG8#^6Zgsjw&k3u%!gj>@b~e82fyd4cChsn*UTAm0wZG6K-2$WWPERCd5hmk?1y zZa+T45@f?|q}F0EpYh1#+eb6AwHme4H;MD#@1=}6{m?yBhk*W$n9Fp_Il}7cm{qhf z@)p|LZ^9BsHrum(k8s`Fx2O1rFYcfzAo~|%HEV+{uSVb1 z*pDJO49t6I3;@9}ay@Sodkeo4?cW`|?KOPLF|qeme^A_YU$p(JxPzh79=N97NlcF6 z3HW<9G5{{X!Gh$gXxph9Mh4cel%HgE_vdV|K_7At;I0}*;3>B^puqe!s+Z$(7Ryu4 zc1J{vaiOKYXOtf`<=_RZE=j*}5U^3^@MlXVMcS{3P5?9J2=-gJ;x09?K+W-m?(bjW z!!U2B7KlKP5{a<<$p6yB2Jah?L))~E?^zUh)nNqpJlHO)2fx@^z1XFKS-u!CkpKU} z(7>}I_xnG<%RhAsJQh3`CKEy>@l)yqqy-Row7T?W7cLVAy=^xeX;e0etdN=;Bfbm5 zkb{T9vsmTTu8hUT4*VM$^1My(?wJ2>%yyXHAj#-bn$y_N=(c0&3QeV=AxGXANznzg z-cg$9k;@LgmEymw$B@6KU(a_@WXZPX-GvL*50z^7c@XH31eTljy>dzV@d^z>5oDVt zK1NEj;git;?2m!txXPd{4C&Q%W&_8y&FrF%ill{26MJ`HO4f+d=m*QfM1(Vvs@wE@ zA{S%&hcW7$6m7a{8A*mmomS2sWLKs6ju;|d+p(KoF2=L#zs?oWx4$*eR!NI&Ey^yu zUo9~h?!&qe7Wlz|x8*Hzb5PK$ik_h26J$C-(M?~}N8WH0My|@%=3gE52#YW!67hGm zCcZzp`JKtHWI5d79jvLZ?oLFD1&6TPbkbWbLz1DMpw?bG=lYgh;YZKM#kRpG)*(UR zIV3GS3f4CSO`k6Y@ArrS%&gW>IVlI&d&-KDq}a7kfhD0*6ObGCjk5asX6saR^5-)U z9zth8Sh5ZJA=E(P6^7UwrH;j)V%K?S&xn_`6*nhDHxjA)f90cPR%c|X>?DOUxasN^ zV1x%rRpyr!mE51YiGn66o+qGC6mKL>2KnDdRQ zu5ODbdi_gEEk%xCwt0p+prby_%8}7aM$vKh{*4M6tLOeplw19qnRsb#u)Hu)3q##R z)MvSk#&H1Z6I<=H9O&*>nJcT(4EX+b{kMOXKAv;oceZ$^uaSeR84_Gpx3hIz)AODp z;7@L*pzA&B*1|7ho>4CzUsZC)aFglRLZ?u8jhC0j$|0Y|moWK2zKu1j*Hbeg=zAOA z@kM6i+WFL^{s}o-Sc!#BP)@RyToP+foyZCz>8O`u_QKDR7a{WOgw%GjYkgY5b{^&N z@q1HI#sJyjRdlkVFeaexbP;P*pzBgxcw7Dxqxalb@%bmAAv1jA;NxfGi%%NtSpPy51Qr%o#UR5z6z&S*7 zb^OG!nDvltTp5jwvgT>EUa|(CQM&54(TmY{znxSocJ0irtZw5qtGdp}yHvM-AjX}@ zKCw`V5GwaQ>;9+NzlLLArjpZcxQ3ksu-f&z+>x~IQvUlI zgr@a|Ap=xY*V!HobJWFWPr@EH#R)wSWSUKj_NurtY1$$$IjvDm#1S-14H>;k3apgM zP=%M9F{UjKYHdtxB;VFvq};kenU~Sq2k=_L4mM&N5e-M6q9~DP+S+TORRdWx69N<3v49H%j;-cgu3IBGUxEwgi6)`-#f82pzy>Nu=pC!U_ zX6B6D+x84*mmjeZVt<~VkC)S~KFDQMS{B2?;y3F#K-wmKmUu9+U1!GOp8+N(85Wk6 zc7o=fWx3R1S6BJc#n-c;o`g%q+Yj)k&g@VgWpP%_pYvH~tLmVQA@ZiWYn)9tfaT(D zO;WMmaQaDOeEsEWLH=xGwvBgKdHP^R727w6v(u4gow-gBd12kkor+@eP%1fpo{(UI z$-^f<<$wp#$+P%HWuj0ArX{IfTP}&n5pp#+-7K4P7FMLrd*4-qZsA@r8G;i-%nGaC zjzFVZiTb+pZ{@UAe@GAC-??PZ>6_TD@p5ver$+$_X>k=NVlmMrLVdHJhzEK{x@4Nq zEWE&IQDR|^`=OQWlRIQDHQi;YHa?pqK0)0mt$<`nvKYh^re=Em^Ch_ zm&5Zf9hhmiblmEwe46}MgKsA)ANQfDUdEZZWG614SF2E*0mO%g$=7;W@WJh_GK{6= zXh@_Mwa&Cu?8uDgET{0RVv}P4uw=;!LE@(#qKqi%44E)7YX=_Yhl&!-$c8RYz z1m)XhOwaxJ;Ln(2-TG!ABqO1FNqV#Pgt`no=yo3l0te^(yNS*rGR=#u_)?!|Jh#~L zXC>m9R(ulhHC|%1gjFv{SzL&9N@gqDS*=eEC^e9hWL^Q( zfDp9jcMvr5u=zFv44;H6+lb@TXGiap*?1Vx6ulG z2dH;}^TLjnbn6-sd@{mAupS2`Z3cqsZOtH-aN7g#_TC)a7zWb)*<+Sx|0Al-FslV5 z#8Yiuqf}H``3eV~4L+8MhbTiLeqbmosQXB9qZTgONK~u8aK69%A#}n>CDrAw>`*Jw zyI9tm;P)U5jz;vn+v>nJOTs8|9MD{y(KT-CunK87xJ1FT@o?)+I&-!>AAiBu!^mSK zTEb&&uUt4IrhrRb_@V8M(vkA?;|n5O?ZV%`?_~Km9ekMH6zQ4j4p8}u;jQB1B|ruY zzHw0ZpU#+6hyn7@+VH&0c}Zv^*Yy_khg#~((LK*?Nek~PwUMfh37 z&?-}0r-k=@BX!X>HyII-C?tn=b>9ymQ_f;Tjgg`Ht+MrE?8&D7Q_&St@(2f&w%a;t z@;*K+9i_YOVWHGdEOt*=zcF*VwS@n&KuQSL*Go3l_9q{Mm${}n&h%(Dw|xGM4UR3+ZkiOx`IS7RUujD$+st= zG0^Zh`)gfOjj?V+O2RXMg@Zw-b{_*1V%kQ@zJJ6&8UF3F^!X7R0G*vTG+se5%bE<8 zF_!Is&QYtxR6=x0WB3yTK10IPumI~@q8IvluA32R97W{HFD0SK#_ln|Uq9Gxh2`IA z=jbm=87+DIrHL_Pbt6CUbIbdyfqL;QiRZbTR5<*a-${6QM(|3>cAFRHh{{+I0q2vz z3vas>yKbY@rrQb=ke()stW8dl-ECL}Vw0;!4iJ@Iis7A|lRWGp)~bbBCw*0Qjc6*sdO)w5gmXC7pv3Jb%N*SA}$ z{{)$w^*SuIUrc9o=mA$V9pP+wi=AnwBhMyb2)7F)jQi3_U0Bamff}L(w!Z2~fm#Ns zUu#4VjrxDL%rIo|jYiDYM@$pDn*%N&cox*cd(W?h=i=1ejv9AC41-7s;AfbhV3s!U ze|d-|{R#SElGo|RAkqDt=a4{kayL-npe!#o_fVhy`2g%HyW!82aAJ_m_BI{d`5zhq zPOdr9O!Ex8iGn-$0Kv!MH20r!u(uZSPe6 z4yKCU=7?1f5JtK^FTXtjd~BG7w~m~9DSMnK$|Xl-(|s(@v~N(S#C`0G=M*giWOeL3t%6Ds?_f93*a5B~d8ix;r1 zGmKWB{p)8vnvs9ypMQmTVZ8|_LCl$tlQ3u;K$a+Qw9+8APAu;N<{>oWJJzaBd=W!(BAG z%IEQfCf!y6&Elc|e#WWUC;Pn9Sm8$!Kj^mSbjJOJJn+||MmU8_y=3319KOK<_|*^O zo}y6lPB34F&VU@AlIEs)%jYW)1)%dVdg3VSAfzM6P;ymQId1$s?0ak-$5m;vCE79e z=`$N2yP_h7vNuTKL9J2}4aGFx5}{u&d3kJW2$F*SvJc%#6q>agD+L=*L#j(fYA_@` zSPisLs2ujc4yau2j-OU2D~A-N#Y6}lTXTMZd49>~ivd=0CCtpw?EUJTD0C5+eXj}? z0%$uINrfjkzs)|cL6ZF?UNr-fJ4z$VsTkV~1LsmU-VqrYwh5N)H6F`0jbs41L`(+Y zegI3)HC%ca-u&UbS*9?njHYW4y|_Gp3zykxY=u^^&3kyJEE7HhasaBkF$Jx(uEsn-N^^ak-&~Cnq-khI>Pi|8;(2lhtgc{qJXa2pl>dMkHWu>?nL=waV zZ>xFzbQSeva_-Ree>r({lwOW}Hp%Mi{K$Z`_;w;1#*Bp99Pw?=yn9B*|6r64FFGte zq=@lQpu{~9%|al_8#l=SIK2Me#v%D3{Iu~!+`X0_dH0YGzSyL$^dRWx1&(+GyV|ulv1KHm32OQ zh4xHo|Ku^CGXhSkGA8F`e$dkKhc{8ReKH!IhHcqqOyiFjw+Ou?eN=$iHNbUGL>KTN zc*}AI`Oako^ z_6$frv2zp{>&UipQsJVNat&xCYqwH4@DcJop&Aw;Mf?M1f!{ z$6bhswUyM^sSE$h$yQbX#TAa|&$R`DJ8;-8r3ExEJ)0_ZpsMy!vwE6~s@$l>z|O#U$#jWuOa-`;+Myh4*hD{nD_yFcXG1D*RmYWv!2!4>h(!BUEKe7D1bZX zZN59iadNc4-Sr8y*~H$@&IXMZ^osW#XNVdc7f^3^4ADRVuql8zzFTr63TZJZ?DeTl zoI9MYjqGnxsMMjqKTV~YYl?7# zLyYZ~hV^-?09Ur2+x<06cVgiI=rb6(^MiZuDwN$oHj;bu@;O~}fI4M+Un1HFu#~bM zDhI@CXKN3`6d0aUgom%^4%WNS-%yf3utka?taqkB#aVb?59|M!KjAC?TFSt}K_H=d zjpgfisz_|DgFpz3LM`;;66K>yzsbP62hFb8>G{J4h*UCrt@^k?Cf9l))r(!42wA8a zL%X+vLJgTBjp}DlpZ&W>j(M#Ux6xnFQEu>4C3L3VnO1+P_g?hI9K;3tYj<}XC8I)A zNs~yk85+mHA#6StVzL>(YD#GGZW(C-L1O6ING-kFR#vX{cME%^0w^hMJ3J`6cE%JK z(FroRNcw|17FGjE-FGKpc;Q7Dht-8al?K@&y_tf1>o3q+)D@ji%ge*?>c z5skkZnQ{If@4F+~E(n{T71VAv9iGc)3j3a<{HQP$Pliti)7rSk6fK!S9g)#TMJH4ofCbP{4c4R%clm4f$*pimtt32f-ICWdH9V zxA52`d;0v1kOMjYgYKRC#CDnxfBTR%AxOpaZh$xq6c~4>YJMdR#9Rg4*)CMqZbmla zCrWmM3dm*T7+WrbsCN+VGrq;;s&lVM=34EiKUX|Nt;>fDxb(wSA?0eZzqPnS)=G(tm_a$m+Kh%s!L9m}q$lxIgLf%?|5z!#VxZydlfQ|&WCT$vr ztt8wMv8uB*Eio4A zwfkgPtxf7F@RJ6bbS>H!99W5?5pJ4tV95BuzBT*;9G4H^_)rf#izG8P%@=9fb-E?5 z2e+)(ZEhM0qlTZ_ME_B=!ShQaPt>o}Fu8D=9+)68V39;#4V71IW{s9WaG|tl~QR@t_Aj>0~3@bRr2nFLsU~_#g`7tb>^hpnjNcq@SLTc|k8&5uDZD=UkQz3QRlEThHc<$17b#tuy7YkdU^47l*9 zs5;M{rE;D~yMeD)mo1XTA@5P$%hSXn(t=NFZGjoGOi2n%f_^i|4cgcf;Af0#0)xrd`$6mtuSI-jCjz)6-Bi@LhXYV z)$Ja3Pi$Q$eebd*sPZ{MXBo_Kx%PIY{e*<|0gqGW!H1_K242dXDQUT&vvX6mKR%|I zrxbp8rF`Sm%CL!x!!M-;frU?d}jUK z1MmH?k2{nQ`*wU{Vzc6^cL@Pmg{T@|4%?NMhh@r>=OFdRuGI< zsu9sZOfMS?Ns^J%jmjQ$847rxqK^boXc9Oqc}>BxS)8!R*LE3tH>8s}d-sWN9+kl6 z2j5l$i{{kSGiyuH=Oympj@p4HLq@~4xIWOR`WZ>b-YHB9!vRj8qjJ}=CzQb#Mua_(~E_!`h*iG(n z2uG0k`&fgNhQf&0HY*^?Y+iu%akL+nPTAYC*wTKyxmu-aW2x~;mN7~*nZz?mpLNRB zbk&YfbUF2V(?#?1zk$tZi8L3Qo~_k)|Wz&U{;x*lQ>(W1wD7eVMCsK=CXlKLnbqlI8i7#|0v@9qRp7<%yAb|Ok?`M6EJx^ z$A%-ukIDN1;t?HPYi6AnZJLZ3VpLD z=n8@n$9FVmL$Bm$l>2q@LAsUsg$4hVjEtQ1fEx;>0{LskQL_!Xj`jm(`(2+ZRem6x zZLAj1YJm+T#LDbZUP-y_8f$#qw%&2KwutFNd4y2@ZK`=$AN{eh@aBXtGVo#IDW>rR zcpBagr*?5vjJ@}bFJb5-W&P{7oSFI1v|Cip%a}`$8wfg?7-Gs;4mVozMVlJtoX6~bq(8&0=Ov}$vSn?r%gY%SN9ya(o>RPPNB1M6N?AAJ zSMwB~z3u79Oc_4n3z9Bd8~QG5?M$NuUAYeDH%=nPx;~M1jbn$D66__~Idp`7SQ<4S z95r1yhPs#yx7=9tDxc3`Q2-ojE?d*?wpO#qe4M3eI3+$@C7MH%!)E5_l2-LU`)>CR zGdu%csff)4nf4XgI>RV;F-BxV>U6ee2-HW*$dtA;PW1|`4&%}t$=IZK++4pC#eu(~ zu(6Tv`DwjhXoN;Ea1FxWd4YW`U-d%U9G)1z9GjTvs!I`LH8N^GET`1f;Pd)Qooy{1 z(+3aQAy%*1a~?2Dr>Q2j)Iwt0=boexG&ahEnOj>{aUy$6I_lfptOrY-ze^O&NBtaq z^{<1IM4kBpy5R0TJoqsotn=+@lhpK_Jucq+`?sMzv=|;qCxH7**w8S!&O0J-Yb89b ztbR>7k=ciK?rZfRqZ5ivQbGp(@Pkg)t%E1d>QJ<*$I{m%#9DV+MIVPe#1EUzlYV`3$4^H2OB5Qjd;F74s7yY)rBjR}sD&fJ&v3D{p+pVb zwH75W8qs$Y4(9)qncXoTy;>#ucf>0=M-0HPhG5arc6Rb3VN`(ctP#@%Tv?t7 zCDCzBdD#Y?tMM1rTK8UQvy=^crQH@OB;iCsaE6cb&x(pKPV#rkexvp*eL<>)h8tS( z2&~XbKf7@xUU^DIoUwCkeu$??^!C>Mc3`%MX%@lk?k%RxBrdb?20Fv#piSw3i+0|LmtN z<0Z&Ngx)mwn>)6PCAY_T_+_xG*||4i!MV^o?Rcr0PWftnd`Ra#!yL;Qap}7Q`%0=vI_% z!|X@amW0n<4Y||O0(Aiz1m=$E?3pJW9UYTt#zlTW0y)#D#S=5tjmA)&1QJFkgFf<> z(XUi63hC%_P+zv`xY;zh3j7W*1S@d#P?p2fr_ZX@gI7>X3c%w+IlMSOJ32Yx$H{1g zXyL!uq{ek?76a*AnaPEJK5ZYc8) z6XWW@8ZSa)BSlZ`%0$UbwNzNX?a!8euXgn|4Hm|Yx{h~gl^Icnz#jn@Y)g}fkX_oJ zDwz#=T{zB|EnXe``(58jt82hbGVP#bo0vfIs zx>FjF=|Gc!o%>utx+K?59yr?=z37Ix z`FBnzhglr^bG(%vmK3CYjO9kYiaodhT-9C=V7xqKT0BX--teN!`zj0I-dVqc#F49amaEV$=K-~N5OCm%5 zyMZ?}>^>FT%^jDfD}M!I|1&9;3$3AKuM?Q=ei?%fXX$mLfof=4rv#*ui!Svv zbjmk*uxq{s(oLBvspdG>Wu=6Qx9j)0#__$L2XT>4X<5y+@y!w%#5rbPv(Yo{zC1xv z(<$3#2l@^S!39;)<19RnDZeza)r{>nuG_G@OuT+x*4FG)v%r&ADT~wJ9y2^i+Nz1FrauMlFhA=W-((C6a%hEnWnZAOVnB z3UcbY5zNx&wv3TRZ6rr2SQ)fk{ez4ugL=yzN9xpatsRNm{BDffKsU)-cU~TGf=qnH znhhpYOWITnDLS>qZDdqf+9qG#F#$UrOB$>B!_u=lVJVz_8*#>(Iih);gu1!7aC!u0 zbq@a|f~vQG5v0pRA4%5jaqrmtb2qe+yjnJRh$Rjzt3d5qMa!Lv=G@bC$UZJal-}RwqtT)8n*sDyd0m}KQ6#) zdgI&b6p&)NgDIR*A>t|>;3|RmAkJlcUBX&C>ZBXPE6Gs*3-t!n1j%aq#zgf=bWC0c z`2LUsF9N&Qq`~MUywpD33=uAA;&-IYY0PoWq{lex73vOgnSw3CG?W2eBW1LP;kfsM`0<}A+^nvP!8`}JJVL{=l7D~t=y;NawhAgtZ4bZ zhW)g4d$KB$crM0&vg8OpE|W^eVu(?nDi^EFfao%WmDDP>;i{(BG5(Rtr7x}gN}&~= zf^U@DI2-mLO4fSOK=z?lLYB|};B@fKjqy)A7j4oFp~^goIJt=q#9D`8QocmyL|Y=L zYP%PePCq+!C@EfviN-z9oH>TYDLKbwf@;Of zDH<4O81;6n`x2zGI)^6-uRc03k4!Rlijc=YpRFo)Di*lC8SrGB--zRtxu-aE&K^@p z6HRKtzUy-+YoUKQ^kVO+2L(75gk|3S=kSXp0*`~Pb_6YZ&&li=t&eZnCpgRQU9hCY zr}aes@h`B_G>&;Ozi5KwReG4t zOi`3QF`(1#f(a>Dn?i2Owr!6EqluzV%>BL-X<3QZwV+dt<2!I#q2jd)BX65PQFca& zaQK$S%CSPQ7VBQnwJE=MZU9UqaiXs&4tCz6S9y7savT{7-KWs!$#z&Pp@5(PzV4uk zj=O!sRFc^a5;jmhoE^_Mqu<+hYEX*%*$|!p|&GaMs zGpHCRB$FDKy#l=_U%GL`E)(J+WycQL7p#u6bO)hnfywG{^0`+t{?^#c=A!V@Q?jI| z|8K4eKAB41{&#ogY7PvjxRHh5X5$MRd06|mSs1Pkr$c~W+BP}ab%2k3NbZ++h1=^MRc$r--MPV&S?}wO$R@ zT)X2SwnXeE@FeH^nlT{^$Mt$$=XK8WJkRr-3#J%cSAT5! zC653^C|OMZJN{lwZ?@-#B6VHDl}8Wq>1f-UCcn)-Woh}33gl%kB z+ExcBp_vsL&lI78eDYc}bJqyB{KL||iHP<1l@V`HZIFIKfq(0QlGEoKtu*6~)|uFh zgETziLr?ydDxrrdVI1p=XB5#SuVeCVaEx&6Pc!8c$LsS9pXXYdo?p5 z>W(zb`vnw(kPqQe_+jfAhYpJczzj|fs8-2|~dXx#E- zgvOdb;V50eFHDWH8^Pe@8*0?fb;-*beAxSlk!=%1KuMOzDUgO!5aTG@6!44nZE8;{ zhew^C+z?kd^5zxM8D0LTCv^8!Fo(;-J?k#-AT?Bi|cuqyPpV?Ku?};<$ zv(E9K#GcxV-|uXC_&md2oBI-#U2;8P#i>T4O5#3WdPj$chc`V)9k?c{@A)eWTh1L1FNc)R*$=VtKGW$naVfwEQO0Mj}$(iw0l#ui+>yg{tWFvG>GmDk|^76NmhMEXME3?+iVW z^s`{3Y$o{wS~B52oBsfLv9e8VE=RrABUu&nTQ()NjRzRQM-8!>ss5ar^khkBD}i9E zXQ^Rf6L(Bk_9(b9-ItBSDw(epu#*{91jvkXk{2pUSWY+Cs%YT1@=J6S;^VxlqWnNngzF+f$G(FkuXo8%&fE{kEmh%fx+Ry~UKH2$ z>el+G^PYUuqhi?;AL3r#7vPNMyJGZ;N2G0klvD&__m3gS2Om~;lW z&+KkUm+Rtq94*}_N*Lcd$Ck2qLs&kSvQJ&^`*ANBC8KA$mnVBildv)9=jT%kwc2Ai z)TX_{EJ=K7&E8LFH>6@3^P`d%A?P%05B$j`D0koUKmrPEu1e^LslxX!H}Iv3R;&(4 z3mhL2Ot>t8-%ZYwnQ#oCp`?`M)VD5Oun;V~)Ui&edk^^<@6 z>}6+RIcJJms#2R|E^bH5kpU9Q0!fyL`%h^{?JJzjGa7ow&SUHvN=xokbJQ#y?j|`k zw{`?5IFd*7*2B!ac*$w_=xCe1FOk~lRvkm}KeGQ>tJ|7H$l(2I0W5azW|sFAZS;>5 zl!u4!iNA15yJMbvAUw*+Sun-`L&)VJ6yI}1v_>sUHS4qciyGxj%SB!d@jmPqE!J9^ z78x_a{remm2p#aBB&Bz)%wu9`a9#gUH${q{czE`zM!x#*ipB(WlkoSxXmD6x%ckZ% z+mAMRuxs!(7!%6<<4mMfuQhR^zp9GV@O4#J-%s}gl1<-T>t8-TB1T>R?8VF1VfFs1 zZntlVent~Y$jj#5PpMj_n~CLl-8$#MBgD(A(5$qi>b-LA$>ZFWjb}-sKg>yml{zk| zw>Q)%rnadIH1e==&|Qyk^m8A(+EU&lf9NzunxIVM8D~8pzF>Ly| zfcIjA#O_nK7RN&599kYWS#@;C5o_x_TJ#8uXFZ(UWp|4V9qOm#c%vm<=* zql~X_{LH};z@+D|?PDwJjy_DYzWdRBI0p?{k3=;MC3S-)@GbbBO~o531RY3`Gf1QV zxIoH6Li6^V!_haiG^qqSwKVVi-ZC_J92A!%zZRb=d@!Bi>xDXc0u_%UQsms0sv$w>e4eBJdfkJXjY;NO>HeOmHUZ)uJ>d_FieC@hM;RK zmifl$eEg)ZZ%34Ml$-GJF39}m~}+l>C8N8H>O9B&Ai z0MIsKWIM1Vwszrm7ycCGzdzQcMjkIEDOsHXk3B*^o@>@brqgiw>(&MRy(acbl(Zi$ z^QMXCHwguE=iOw^e_R4=A>A9ia2IsM$hR+kt#17t7Qzw%S9~opIdXL16OqVq>FOC|r(w|r4X8|}f*V`v0i4jtdeDM6pWmY^ z7ag%~P>Ad!PJV!phEpTcl%4jA<<{`^nvRw*!x#tdwl>qb97BNG{Jtt-?yz*V-2()D zTko41sI$T=ARxKud>cb>w<&1SfAdn&4uemOvw6$e)pRLcYzmvIdY$1Fuj9K)ZY-(Rw-A~3w11VQsj{?j(DrCYbZ+t&cTqdO1_hbj!b6OOE+!gDpL=QWi?^OAP}rdo#8sHOi?30@n||+!ku5Bjw+X z?idC2HW^(~A_=2>BtA1ePwf3H7~Ca?+UddDW^08e|3V98ccYs_{NBo$3}Z5@^iyNL zR~%!KJ$8hw^9)UqQ{Pb9eQgqo(0co2o@AcU}~=$kw6#<15kYy362Wl31|Ei{u*gySphaVtXKT<5EdvC*<)nxv&pL zyL*iT1s)g+n*}IIvAbJ%zwx}xek%R)W>e0@i)l%(?*-q{^ai&F-mBQroa4I1?3%RJiB}Kgu*=-$P3l)PLzC~i zxs=k+%=HV#=tU+M3UzI;ojt>3-&qwjulQ&g-0?thdXsunGu4rfj2ukCN8!iC)?S5! zYy9z?+R4h~6f_!bN^+^bW=2g>8KyI*FoINz&ciR+SQ?JU!f#16Sz$Xh+ONb0>E6=1 zc+IlfJSH|ZAVJ@Hy`upM5m(hFRT~CH3EdBhzb5Bj$wNObE;Ef~QJDV}%!C|(?RZa= z52X_1bj>7^iWO2eqb|U1*022xxJWwUA$5-TtVK9`^opMl^V$;u&VXRvh!N9{_V&B! zdm1|3g)FN0Om9<4Er~o&I>&q62GclIUm>4%mHm{dX)~KHU%r;>9XaxI@m$9J3p|M> zSJCEuy>B%w5^HHLJH9bnWV(7^ODyO$BaJ@-3u}3<8d`|Nu#*|Jk{vphY*mpt*2EUa z^hv#G-tgHh$?Nyc>XUi4^kg_Vcwep%6D*VwGf3W6%T*;Dkeg25tx8h)V|2KR3#n-wS)-~YZw*3e= zy!b)hV|o3A(Vj&{Ey9e0(jiQHto9e?P2u32aTh9nb0h$(+Ta&-nDOQ+u}EVviSUP{ zNW-tqGz*O;dgndnvg)1cKEqTnw36}rE{oJ#V8bPi9{&WiPhz|4Q7`Q6XBy8YHJ*3SBqFB)okqPm-K zbdw-y@^E~1qn%W?$YC9(!N#1{!2!cJ!z4*>Bk#!$UXnZ@jN*@GKNY1R&BMv5A1IwD zl{U)Slyq_2TjRixpXhj8`My^|^OyFNBa7CJ86O?Y>7Qa`T=EvU@;~8MZNRV9b~STu zmn?r|=9GWWnMhehm*(E_iZ~jKth~DN>)Y&3#NR}Bw{W(;9Z5P*wMgeRL{?~Bb=9js z=xSfq+j8*E^REuY*7#MQKbwh(u76lS5CNVKkZBkI9oPZ{-_?FZyNJOw++;k(6n*Pn z^YJZs+Vu+qa;zMk3Khc((I!`-c=h_?``sD(S5H)NCeUk!(wE(D zEFSW1x3E$}1xLK@pmU5Yy7j?v^01Aq>ik1g_LEEtL5AYni(||ozNkd0>=ln3IdISc z9zTzIj9@xiWW2t!r(^~Ukc+ud96A=9rk)ZSZ|Hl7wuxE)egt9;w^}wzKSyZPM;r0i zNA))05tIGL?#NvM=M&4r#O0{kX6to_qrs*pZT+WaDCECa)GD$%cgGShNBIp_j>cQ1 zCSeu5N9lh=stWa*HjlMk6hn|scPKpW0*C5wpwrMBTWj53{Ehay!{qSA(>vgy=P5j1 zWJGMdzX|c(lBI8g1&@Na6_&VTzVr%4*63Jb9~6|+UN@gi_>HRnKw@i7Ktw6cz`y+k ziNo*ay8FA4@7tomaES?8zgtZF7yjGwAzQE+=PCj306KEk55EA+E11ZOQ^ofj$>Ah^m}s zh-`-3bt)JK_|I%nliQFEW-!Ra2wkjCui8b+G1Q$SR*!#f@Fzz+om~Ce_S|m~v&6kf7Se&+t!*>G z0cX5gdF<3=6}9Z+B->(tp8Y$3HIi9L!qI*Iw;7d|AsYj??H9s_ACUXFBNzC^to}}Z zahU>=XLTn;P2yP-Z^8AcCp$bv{G!A?UA(P7>^Lpq=XY&oh`3Eau;g}L1amgG>=ain zf&2a|1wahyhA}ZMC$j&w(jGG00`}``6AmYBRtd?O2UvOS629hW0su*N0E~Ux@L5m? z3y%}ZLiDzAfP@lZSjEu0{j+5Gd9U14+TKU@4j!eWyl7Xng}9r!i%U;%6kjehm?+)@ z+mKl*4$AN?fCVW4eR*XDpr*cLO{rDd@5+rY`eJ+ivIq%J#~2&!h;{w;Z>4x6~IoHsI)|G|q+_K?1k! z5G=wPCYcY>g>Q~JPVW9H6-b)Skl>U(YNT|H4|~m|ywU}z0{272=cQb6>>Yd2M0H)^&p}*g%RaJR%XJq_CG%BTlgJ zVYbWq#z>SVZ&66~vp`_Z;%s_L-`xK60}8Rp)hmK^tL>RN0RNL{WG2PRE&gPva4Ijw z%kZFY3h7l5(u!a=nMo&(*v@LyeirBU5XzmTjFxILSa-5(s5b{J4$Sh18sV9irhTvf zFD$6-G9)sI@Wg=3mS(w&cdAq!x<0&4ZxSWUZFu+}_;mpA>pZ3rb+|tZJLjHjD>=u@ z0VwKXzn+l7BtDqdjyzk@3rX;uW$omLJxvsPsv)70ULQ-Q2P^?Nb|Z@R1a z;ZrCV+U{|1s7~V^vZU<-SZ~bg zJR81OKv8TFo4EPaXqa+vfUCH?r#g=mI> zM+w;`TEoTwJIiJ)aIiD0>Xt_m-rS+%u{1BeiiMhi<;w*C#gk@c{S<>24cLEFM(J?{ z1ay6?$tw%D9337n?CNk)LCG~vG;uh@FA84X#-RVfaCln~CtmVrlOZLR0G^T^vZGr4 z?g#6n#t!4Il)MrbnjmMtT7nW5xo+|Gg2(gwp=uF;m2X1{Va;~|PC&%r*MJ=HkNF>; z@O3x2gI~A?)~3f!Xd_K3@L+7A;tNBSZBwkD%9{B~I(gI3el*wE{{c#x?Vv&*kt!9O zFE14cqzGqB1Jpd=;T*&c@uSn>YR#FU=F~ zd|`f_doORkmcVZ1G8L;unKBB>*7Y8S5wSg^i?pxf`ef*2H7*LTX6>FFjUbD(TQCIv zvkmyqjh^x21ZP_MBj6@s3^^>7v>+dg1p1kYK37Z!ylhq>nyUbAhuvf48xqFlo>{h$ z7`j_<(!u#OCuO%BdI-V6>*Cs9t%|Xi zoQSo^?^0Jo2G}64h9nx#csApi(jH!&wXEv~3DnN@5+3vhZJiz2TGfeJVjP=VWt~e+*QDqDm?KObDC|hvj%5U})F=lR zrcVklC%j@blTG=qSd5CxRUhrSKXwWa&~ZERyfPg7W?Y3@Tdg`~RHXFUUuA$V^ZG;@ zRRCu=Rk(NBd8>wC8GW^bg(mc_{IN_OYeB(3J&EnQn7K9xEe`_<`Hc~72o!m30JJG8 zE{RaLIlOwPZJ+6}zGHF)17@jf(iOh&rViUB>AGpYvE=2W`3NXRP$&*!m%#7{y%+0= z99_(lTr1@UtX?D^s9g9FR`U({*aa6 zb(bPA2~Tl+8P`9DNH1~V=?_-=$B)QeLy#15Cu-Y-y71u_|NdNWThe}C0=Tc;jl1!X z@7o54OC^Nx%kWotc%9qxG;oVCCLUwKyN5i{5k75;_yyH!0wH|M)?!?RUKZ_!cj}xr z9>K@+vGywYO%az|Q5MK|iYfd)Fgh5pRNDf?S@!EfY5RpYhE=rPHDHR@f3D%i4*|L2 zOqffso}5@^;g$0^BkraCX*A|aR70PKYd?^I&W|9@S{TYry{%=E@=Dr9VlrP z^eEsEv1y5Uflu=1~m3?wCCkWMXMdyA6a{*hIP^KsAK)cu}3EzPAUvgL;9Hg zJ~!Do?vAce#^XC;V{6tfc%A(%_)tmU@iH8^|jZL#E(z&4@YFfxZ#Kjq{ z`FKxPE90zsKxBtIA~jc_h_q1xH(-mo24)&gNq$!ZFkO~&ZyU^A3L$Av_X-oW19l6t zbg3r~J}rspx%)mBPid26@g95fW+KhRR=sX|wq8D$p3g$m{r&l~n8YarCwMx^9rb>P zmbUc^=DmrzK>8KKB&#=8Y;N?23LCQr)nUG(F8C{jVYrszt4^O`&HP8 zki=D04CC}`_e0q;vJk?G$^$Wrl(v~ydR5{oNrYDWo37|Q`uUw#c`Xwnj~;}q%jS35 zD#z_Eu&7|o-rr+a$4Ct~0c?zxRJHrwa&*v-a@A<1Y5#q&`54Sw``M35q!gCB@G>`$ zuU$l*Zlb)KZVB(YMFBxW&%qZ4>ptrXSA&K#UMyxy9QCSEl~TJiHF+!-GIFoyXs&R% z5xvM+QE#(K3;;!SaT+WXH``E+`BKWe-S*&3ks)n@xVw(X>2-Et4*?6fk)~#mS6zHR zyl79!vcifYo_1ee;OJHN+qasz>(pcFlaMHBLA-MNNbE(_0w2ppC`4f9@2o7qhqj?| z6~V*8=|n6S@c=ldSAt@X(HFiK7G5qZuPUuao%(Qm!l7PXMM{@C6)Lfmx=glN;nUKOK+V!3w+SA%*?`NrzXvH^@X1H?Y!dyq#(R&VX&vw2Yy z;$zUuuLMuu0jB^NETpKo??i|WC_En<+$!ni0>2n|q9el!$cl9Xx0?%R0#7s~)eN{Y zG=74LGn=?w(B0UJ#ocmkM?D-E5boWC!j7+fPmVk*XMlk9I87hIx#dF=ynGv^p z9yt#WEl134D|>rEydTE$xahwsPYy14W>}X^!dvAfI{(RyY6hymE5su9z2+$yvJ&H# zn+H1auO9>N)_oZH+~2uasJ?K}YXyruL&#WCTzx`;pu2gl`g?~j+ccvb8owA83m$LV z-5V31Lr46qx6pR|Fy6OU7i#CQ;pN-D_}7+|G3OHKyFMsr!PTU9zM0sZIA0GPKT0_l za&9h)?J&YZZ2uifZFuOOX63Te4O$`^h7+*>(?*D>{mK21goDxcUZA|~#sxdte=R{E zs#Ab720xP)d+8!W%@?dy#ndqi7uRqCpSj6nOCZOvt$r$*z($3=<~&Ht2E>cbIcQ#U%Y&HLrif4cvuC?1wg4Dwq4Dc-Ei5 z&VPd#9O1DYOu)SU@nh`?>;s+^4S?(z8qgEGN#Ul}k}uED(nd&<-@+kEUrz@>vzTl${e& zaEOBDZC1jE5mS{OJ5(V7NobmBhe4-U`9|h|=C5HQ#>AahR$#hZ=~8@Vn6fX=teY~A zU|a7M@{al==KriE8O^Hdz2vqv4Uiyp`^S1~x44O%E>wXD<|?mAe2QxgobQ!WH}ahX zfA}?Ew_Q9U4LwCr(Y9}{p1x1U z5>w=e9{vQu>o^S`t2dmi`*Dwko+nxQik;36kA`GQu&Z5-8;kn)ktMi@J#>QlR8}T? zpEhWVlJx9H-pG)Ohq8FriQfylWRzw?Tlmy~ft9@+l{+HxIcAik>T<}5_Oo*X_i~qX z2dDP&NHS-1PFm#_!xPN^9TGm6633n@$)jYoPmKi(?>h~tU=B5}W^f$95M|SRQ(`DU z^wDufIr)85?dtpZJyiZqW1R}y!gq)S|N2OTjW;HiMbn==FTXyjTIslX&xH(SIcYaR z#dYp3X?u8_6^W08+#V>)NTuqZ_|5bKxoecg5PNj~^F9)+Xznm2YzDeC2!UL*(-6b9 z3)gtg9(nGNO3kWvCRaN0+;ahEQ}i_^)C|(77#?8k%jSe)zbk_I&+M~}G}&KnS(;`B z$yn>&vrS!HPfr1>Vv9}+g~T0da56hmJ06qmaFy^y|569nBG%9oBp5p?)!}#Z-2sk4 zSxPK{8j6x#PV`WV@<~k<@J?~psI_`s+YnO@)m)uGC%8Sh@6LXu6?;=L&PjQ)zCpF>rkS9#Hwu|bG-wFXWdR;b`Q_Fc@wsE zn@ z_`lHdZ**>hMO{1Z9Lu&VJ-Eej8VWR@|Kail8gktM9EyMY3dz|HiqkoJf-?Q;aRLI5 z7Oo{G)(aNzgeaaum?{?nG&I6-=5nss&V5+2*(S zxMtnjox>j}%!7ZF9Y0SxN~x(V24!|&DivZuf+G?{j-#@`yCQjmj|s8v6*lA~zh-qS z%+NP>oa_18Fj$-}b569Z6qVeXg$?DZen<}maM=K9QH^PT1N@t%E0Q+0aQ@&>FH&Ax z`YPB;qAwD-nb^BG%XRMm4I=&HWw6Zr^mhtm3KZ=eC^D6hT4Ed>XGkSzxbbjD)b z-g(_W z9|eWizO#5WPyP{NU=~&tYR{g%sU3x>5Ytw!%pRemtP=aj@$y!Oy9${K&~Cj)JwUJE z5&*B*PFHZL)UO};ai9-=+D@=aT|v+U|NozIN#h`uvXM8Hmg8-Z$#lN+<7)4zcY#%z z=g+M*H8N5_xeoF$s6w9mrZhIdPnoT!4^}O1uale_rY5xM48^l-C*w3u)x*659I-iQ z|NU@F!kPchO}j_{?j;%d~j_7L_ z`Z|0RM1B^%U9th_)b=86`L?5MX3RjgVBwr*K11+eo)%WsbCP=TNTD7R*JTe!M95%OVQ|A zqs7#C3Ji|lZmI!E%x+hKzF`2cO}7;6e2UgJMd3+H1pyqRSrs$fD}>*iG?P9jrf{6W zXd7ABxV;8ngt-C-6ta$asyFAwngjUTS8t>ztAln`4WHhuT0W13jjKE}ha||TPHBE< z3nG{EJitV~sM>=|^a0o+`1-mAoaw7SVRY(FeXk-spD#{~nAIE9hfp+N&}M%7Z&G_d z&$eFp>}Ajk4?2Aul%ez2cC+vn4SAEsUcA&_=~{k-%iy+FQ$H7D#(Y2N-;PI5z4BAs zU)tZr(Em^EFCz9ZbS&hqv>`QC*$wfcn;8aPZm9`z9`It$Nj{SsPgJ}Q zJ-*)6!aEI$isA?N6S49*$xs4@mrri96}aPNL#L)z$YOE#md!Y-kRFWRXrgah=9@T= z8A(adH_pQ8eDU$Zy~Db2{ihK7bjHy)Mb|C)UffC8a16i2I1odyFnR2}ff^s*GUNP* z3$946Oukta1#D(oe3HdwBgqp+?M_S(j0NwqBn4$Yc+O*?yx4M$r|ut8Ua#S%FSY{a zK$cgVy3B0^*moAj(Wox}@qYA$BGd8oM8(>@ZnRln!2 zN2Oh@oC8{``iQwEzk-pWgf{WKM7dcuXcbns-0Iw?>Yl@Txlik7<#38o zz|1Kfg*7clm*>-3iOdNto0KJY@psMLRCDf5978yNL^%OR@mJb|Lq!jLUWS2KyUZ-^ zdNIHEAx7_tp%crDF#@*N! zP;1*nMs@1(X7^7J^3`+D57$lWITLia0NP-4*h=+Q zJU@YYEt!AejsLN~Y4tzObsNfT5rW1)%Jm*Q*gFAG& zI~YMEEri?o*Bjig@*&w2ez-T1Kwkg3^KAZ2V>)M#Pyz_~C1Xr(v^^u}ATAW~KgE(! zh7nsE83$<+Y`dGkDKVz7Aifa_q{l}HdeIr3QZZr>8c>gWwhfdXIjodeNcP>J zgd`OXG?U?GxB8Vlg5D^pBUE%3pdftdTJ&cX*Lq?S2Yuob+`}993@21zmhg5B7Rqql zl*$-)Q13%mfjIOYIED1Qq)q#APxVzf5-zAAT$~(-`f-%HZM^<`%sHdqW7N&&PJk?dN1&f*vj4j+bNU*X)~s5q z9-yak|Qn(7VZ~?Zr1>ip#|gLYz#2HuLhI%s--S<&NGz$BX(^8wJgod@eyl$}7J_ zN#=BD*^p~S-;|XE)#|q{(3dihYjQvKWl9`cve9K~f5jT~AZ;1#Z*)0McQ6l)V=Yz? z*1}E|QG~mY+T(@xP|6y1H5ds;I{t(VJ~1ZXaYuyvi2|t6?~wB~w)~OvQJR%T=n)?z zkrW`P5Kkcp)^GdMB;s4S)g*Ga>cPK?qiM#(u}X*A&o>e`FHSlov51-1 zPDCY=)d$^W_bKw~)%C%G-Js1Pg5*I`u4?2r-+E7+oh11IVkx)_sq54-GrPW5&Xwte zc5R)Pg_C6SQRdt5{5OQ}M>scxGKHs94^f8xkOyJZ_az_O|7i25ht?43AJ@zVFy3(Q zNo11HeKKy7$_=ww0h~y?U6g12~`9Cvbg9s93ACwDNO$H&b0)nFrV>xHwc? zbqDWWW+`AU`WpK%pFFuDo7{A?$$=W8y{5s1 zOr)pgL~+G~0+FXlf?q|O@Q$W0>$xcAE?FJ0xBN~3`cEb^K#rm3?d%{*l9^Fjg#@8X zGiPH@ki!g6S`$Z@GKi}Rg=hI-ONw?GMUJeuRg#M(imRy0;e)(E|5#os7FqriCyLjkKw!yRu?`3AO8tQ^tQlgbs=WV#W z?W!G=3sw~?kz1TB@bj)!t>!J8 zrTFv-jSJ)kl2}xu-8d;X^1!h_Rl;DntO1ylt3GrHscAxO^c)m4p*-sKa??d+PbU+l zqpw7#!5xK?-N&N_w zu?N3KXW6{=96m^>zbfBIwfaHsE0@c6@k)qzLw^1)GUDMe(^Gnqw0}5A<1}GRUzUO) zm}uICmoj^RXuC`d%*BGE#n1IN&%xQGmySaNgBEb(w#$r=B-Z+dAyXyc4sR)fa5o)U zC-S2!gAa74qCk2FZzv(!p(x*CsJH%r$QOS(+O0+%2(gHc>vyRK*QK~6nfs+|_1V&Jc0RjEC?v zE^)^deh^g5?ybM%gvO_z+tWD{>sWqx^oen()_jNQC~zKhbyq{tRM_kMrvk{k;3#ddZ`xaEsqoytb+A z4uE-(^7v@{>mN}%9aBMHLIpT*GR*Bt8cuhIoTgpalkpL!i0B4K2>!WK$O_!_Yc)8G zEp{U!n%_k&5W{8eho?PMe1cq^hIj3jMx~g-lh09V_Ls$TidXb{+Vd9OJXTkiGAHYz zW{Fr2T9`rah{bOISoMtcimu`^4WEwnd2sP<`l0^EPHxpGK3B_Jc;b7aSG+L&|B-Tx)ROIJ>FgVCGHNz$)iE` zg5}uwX)H=UqhBtlmUxFIQpW-*pTDm}DHn&f7TUc!)@0RwxaFPGJ0Y;tmvG6C7L)dHeg$e*fK<6BO*`HueV5Fy=~l*-qx3~h4VwFxVqWhC2oY%JdN z@u3arlSqI8gzY_LlW!>8iM=EP`bUk<##2lbao>?0No?=P&{hs$ah#dkO<11vJbmG& z>nTmqTenK6j9%2~zwWmh>ANq*){HcS{Ji&dy~C=eN^wp=Df`7pT~x*}$f@@ETt6~6 z*>$toZl>=M&vb-`<0-S3luZI^J`XUYcNrGOsnBTr%chfsDi#5j6Vw;=?Wan)=dF0| zV0Fd{W?3w(y_qZ%Z0E4W_ntrBf4S&R&H!ch20_QK+zfHo1yc^cB z(ms0XS_2OfdSa&A^iNObo;L2lP;ClsemA;Zg=pO_NRZOnD>tFbN7zqm)GmxCB$|fu zI)t0yZ?1Onovj(^lVs3gNH5ZiVSSx@h~;hwCF{Cxb%iHWGpu`<1!ZVmm>N4HOqLMJt_sH{aFka5H4(j=wIwtRTF^zJfGjJyojjqkAm zy0#+4p(hR>hC!V~G1GjT870U~R_D1IeT(e8MZV%|TVdv|CM|c5)lmv|Hc3=;^<-Pt zTfLwOfrjU?p9?k=Jury1Vq8X12cZv#ZH9Fj4`o^mx`CvLosMo z+LVMqEy?r|uGYv?>r-XJ9r=U`zWM2x;9$Z~>&Ti8D-Y=TIM9w8k`AM%5u*wz(F7F+WgXN_ru1!oD(i}xVdRn@9H##BmAUtj@=;$LmyEJaR@Rj> z=>7I1o5%hOjSC4XZVPU{2Ds_;fr{{!F^js>O2c`DRU@aM4X2Ho$2@8J*n4_so}?Bj z>J*OHazsBwmzt~Cz7jjVVv46ty$~_{slUp}aczmT+}&5rX+G&zM+{6^+$(xxh)U!g zx~|2hK}8QUJL-U<8lV(i9BW+~?MiM{e0N_WIDa+eNmTLRhrLKF+Agv}45fQhLZ$2~ z9h6$|X6({*e}G5A;ZrenQ9|0lb6AYh`qs-`uv`yY=vtpBw>tDf4U0t$HYQB#)#BSo zJC&(vHFVvos5o(EbiHOXIE7`VE7#;HEl+9Yv-!nIxy2{Bt;9A5MnM;#IEps)rs z+F(L$259^DzN8M+p-#PN@K|^F^kSEj$7f#lkoXB3n2V|vuNFg3#7@$XYuw`_N=)Y%!1CN_+N;y{8Gc|-kB0`g z#mwuoPd!#yRfA6orV1Wj{m~UGc9`}^{d5-g_Q>-QSt6@WiURGLYk!s(EvL8VKr5e~ z_XrVD)b06K&(k%s*e-Q=#2$6SU^tPZNOOK|CSV=y&`Y0Rw7fCXw zFLZD(JZD&-*dF}2g%5HeD&c;yjh4_Z@5ulZKukdp1mGzopZeu(kvh7oBA@}J@ZR!j z>kts68gCzh=G1dH^a-#y>HcpB3+*2+ai?U08%?F^L{#MN8KN{bn|&PVEbs4zW-r77 zf6FIeDuPd+pXY8)L2kKX)w%lSU5w-1{?-Q5mFn2nLTa|l{Q)zoHku7u3o^T36^kra z`4)>WUu3?vV)s(^pP*yv;MECk)82abk4G3sz|%ZcQolRGDyA$%8&W)!w_L-jl5R`9 znuuS~6}_fe*%i0uXUJ{h5_SAf$1;Lt39@$8oZJi{ZI+{ZV>(gZM7;c?$6vnH>XF-M zMbv%m>sRJm^8$+%KNsoY&2TU}1mg}-l(WM@VA!$)JpQ>p7JYx!``CP5WQbv zNDQBo3wTgl(Ab#QH^wpWyydj}O!?QsI%Le;qK^Nmh~QZhNPRpWwj9-Hx>v}FRgk9r zNa}L?Dg*3I-1XV9`?Fz6bCqO&eFXEWPt7$&xP!QIB3Ke$reDn{8AIu}&#j1PkrDmi zw^$Ma3NE{WJC1`D2h|C8bRKUvUS<>_c}({Q$+pOcDHK}o6;4z2 z4+N*vfjVzIp;xi#qJF#HV$NQirJqFbk~5hZG1M?{RK$t}^g%*d9%OjMJRKzaiHu?c zshpwoJ>~(VPRfX%!9)sV54hwIX@x`DNLoAE@k+{U zB0gFZZ;}@pUH+mpkzN-rNVgf2HgnWWC!*}=gtINCC=kh?W93%eV}u`Xbi0rC^f-_+ zJSz(U6x&5so<#MuJsN!8SBkgGGuNR!Q>=N?ho$}1m9rOTm zspURYE=qD6HMsoKQJk{aYox<|5W6?rFAM_M_W3*}h|D6g`8kC{IvF`ZMu)YcBs1{pHC9^eg#%4j(kn zGH!^=?|NH48EI=bw1(MEOOdh~viBFz`j^NsT+l}9HImcG3H6Kjzl#)dZke-3m@=Q4 zRP47p_nsxAbEeb02W=~3gzQ_pGUJxLf{z4U9Maei=2P7@&Y|j(x~Z*d^j&Q-7|5dQ zml^bR9Zf(J5sS(gKBCNgS4(!YEES_X*u^MO^8q?QG4F456_r{uI|o%+!NqFlAs2gU z(GH$7EzbGJUcmTOhbXBhZ1TXw))FW0yoQ_eaZxWiZL8B#sWEfDn$fGnbji1|6MFCG z%%Rr@>Kn}d-Qj|B-=N&sIP|>Utk-$(j?o^&?Pbs=Dxv`zO(2BOWQLf*j!BVTtJkYM zpHR-)g;uzkrm$D=<^7a%ZU;x&96hS2J!@2<5C6^ct#a_k$k-IC>X_xvQayxw_G%}~ zC&fXZt`|Alh9E@ zhAwUTcz3a2l}C4t#kAN1$)Vw9;U-;x9k^m^Z4W#ab=XFOIAtGB@4M{{Ku}i_E;~ku zr(8x_*M$mazza3~GYMj=WDgbvr+sHA+A{SiT5N6l)Gqu#FkyS>5cCc@@BL>Kfhz!S zxXm7H-Fo;HUS{i`W-r(-+_m8Xs`N{F5*&gK@~TzcBdoLvHR6Sc6XFAvD#kQ zFGtA{+J2BQwCy~A=UsZS7cpQx6#<#yx*2qorh8@N?(HVToq-nr#%`5g?Sj4KRmd2Q z%JF|UD}I7rx@Wk~Lr|oXu_qU7M$bH})8xYxn~n$l$IBn?HP%NzEDau16}7=cua;>_ zk_M2jXevn5dtan%V_?tG`D^P+t3Yvy8#rRvv^A0APb>u2i)Um;?4`xhUcX6hXMeR`YnsxY=r++2PU_Z5%Js)=IT)-O7vUz~YAKfB5 z@5pR-WC!`tP4TyL(e{@LGEf|6qyldKCtz!}_dG)@XL6MPFTR z{^ZNy)=>tBm9$j6@cEN;bxnlxp?s4GB6f`4rznhxqcxRX3(jVpetL+1AN^Bn727sD zsWVrQ5h|I{`X7Fbi(O}~A3wt{95pMSZ#cGxpPIyz95B=Ru(*?Lm&4-OGiN@e?xRmx zU~kJagNLahEYK@Rf}}8;O%10y22kgsMQ%TM$Z+WA#!Y%3S!ro%9@{x_rr7 z#G^(8cz+3um3PzI3!mD9y|J%{z0DU*Z+m0zrxiIXPnx;lchBG|&OEGXLLf2}1L=`G z^aO#wxR>t3)-u>WZ2g$rrOD$ow|pFRu~}6`?E4GkRW)YBdV470lSBR5WQyT3;B8N> zTXm%!Dtv}%X4_v5l1&svQ2B?-zc=Ibcpr$+T%R&j%D5*BG;)3@`RLcWPE-CUf1ZPh zFRy9vO*L;wskW7j6A!y5DylanN|jx;4be(?U4vB2z>3lCahg{G1 zu))a38RV8n;G=u8hw*AGh`$$}f(ydEE9X0EZWL=&wgAw>pY?)*?|gnTTz`BZGgQTdM|K!4&d$zGPY-t$J6Tv{xvE`KWvjgt8IhizuB%dpRda#|4Lo*o+I&gFt)->q zxH_I=8#>$&S2gFdo6vFQD?>R0kU4{2zkW?Wx-vI>|Ni~nf~h>S!OD7!mSeQqP_IM`OfQphO=i?iXA(04>bsRcUb01X$*V|WE!&e zT`?czO&Y8`DByn3L^amrho(|6D`{G%#f()`!u_~s&z{k6TY~$BN+kcj?YR`nTD9Rf zV`5_JuQ_cLT$j5r_`_bM3?8T;z|T)Y!`1i{eKS#WS%#Ol@?#7=DkD_8tH?nrM@-|> zv%}>Kv9YnD0v9Zo8JluvKlUp~MT!Y{8U&u7oF`{dUi#_9`p9!mEw(~j7u(eI)m%4T zPwmT`BoTIMVNvpDNmE0}YI3OE&ri=;i`VLH0@2wWE!iy=tBaGxrv0Am81rGoCXkDT zbP6%951#hr@42lE?d=P$W3@6mfPA~Du5KrD8Q3+`i ztbdihDObGW*SNMKncu-eOh*^i*x2~-N;y#`e?k{RslpyC@4_$4R+zxb-&bq$LoNZ z_QEq8tZtee>nv2-|0GT0%SOX6_p!n15VLuj10LCJMp;=|wxay}PgZWbw!Q6uM~lFB z*FAf-PggC^v_HhE(`0Iwrlw|MSp8+WM_yq95FqhHZrzj?x0=Yv(l>5dxEI1DviifQ zSVr;Ycips6W8&YA;1|VSnqofQCKGBp)Atpe9cG5e#l@w5BXx5n_1rOHdV2cSaa#^! zwcN{B`@RKU;dZ1;@b}(L4F{s_aW=hxdt*}*McDabQJ%@(-YXAEoQcR;8b>J)5D{ti zP*6~m*PU%>Xjorg*Q8HhgXd~ck*lu^p;hu9@l||EPG%xxy#z-r(5$<}1sI*cq&V)y z);83&$G`;_wZ@2%2TIAxYEe_wH=)Z=FwqLSN$8&f8rW`|$VrE-`|#mINy!b=`S1k& z^NftIE3aI>oTf|Y`gkhLx=ge^TloY>Lv}Ca(yLp*bRGKmd;FI`)t76x1zd%?hL=*= zyA=#(Q{yNL(w%_7fgG-9iy?owCoPJDZ@`O}q7L!5J>H6&?zZJRH`up-31|x3 z(JTzd`yL_SVNJiE{*T%Jv_aD|GCp4ygdnhWa3R3xnxS28j_vFDuzocNnV-)IYb;K5u*KQeOH`S68aKTX5_)lhe)LV& zD62w6l$)EIRfk2u9X|7+Lr%en@d00on^3YVY!kQ9nMbfw^H{aPfq{F0Y=Y}tb_-(| z441BO50BG^eZ!ODWUMm^Sb-0d~UcwGw}20&ofM3Syhypw3-}j zY;1E;V*X68665G4%Wy7!rQ|)KgNCr9SVKIz zD`uDwPZP_{Cw5_N|Iu^80*<10C-lCSN5{tAd(S=$t_66zO~XXN7pgbZBx>O_!q$me z7^QIBqzrS~$YU>BoydRn>JV}%O%45VCie;haZw?@|HcaE<3!1?|ovEo=cp4?u_jfI@zgyX|M#BtkoZ9%^7nNTjl*Jp=MFO2_)Ys=8Ck=5Yx_wxgY7XO@=mv^O&upzQ-P64ci ztqiP-dEj!aco^@<53+K4(nbj{sU`>?wL^Q!SXG(4k22gFilDm_#&VIzV#JpnK^N4i zrH!aZnwla}xw-PqnrS`YD&=+yRCvDVg|vO&KaXttH?VrbgD0jsth&}`SB*|ZM@I{L zGvDW389I#;u(h@Q@{)$xQ?Eh034->)6}bkR7o2tr$;YU!2HhQyY`)>;8Xg{Q@FSa| z(D}|}XW{BlT~yQOedy2)Z1IN0SW6n4AZ=noLYgHptS3FNx7@p-ioIisZ*#c@0XyFg zpT2!!QosdAk|HPfBmmqtwEoF+VSUgh09jCg`PV%#jpQ@CTdEyjs_hB(K9@2Jdf3z!Y_^iOVC zqRq7BB8^Lq{JDd>CkLJ)p@ZZA4jJMqIlA2cgY$)J1RsfeAOIqKstydzN7+`FlX4VuB01*&p z9Q03LA>$wWoi=Bhgz}7vj&mxibWepDjcL;z`L)UakF__Cr*iMY#&tSr)P&TQG%AF` zCUa7Tgd$`)xB{JkRs~KJOpzU+2@Y z_r1U4TGzU+wbqR|{lg8W_z&~QgOXoR5!bQJ`#BsN>+zVStB-*t^jy8yvg2`yihn@W6GTEyiy`l6?^A@tD{YKkx%}=2ZRpv`yTfjqH zaQKNc-DW69cy$2D#p>JQPb*~^1gtX&={Lr7#k4~}SmyLWs)La2(25drD$E4A8^X`kHo+_}{M^Ex22Lk6+s zCrM{=GrY*+sZT=wvQUtw_>Cts;v!FG4p*LQpSf~O_Q2uBf4p8vGWLHHKXUHotFwQ7 z6Svjh$9enet-q^xUFDZL-Ff_>R|WME!>Bj=2g6;d@{Zk%+M|>U$>EP?YkM{iSr^z( zz30$nc>NtSw~{nn+0J8eYHcF;wZ+m!-6ce)fRQ6h51}&>`XeR)37Lt>AH-V%@cY_v zcpu7dS_nB8WU3+rwap!*hOb1@%F*w6t(bUtcrIOvQ^_$-R7}BY zOEEDskN;TxaLaR5DIjelt5Z*3KR$CTO5!!jK!&j4G@8GX=pQ&fYd$F0^jU*%yu-aP ztVf9?av*d7^!A|`La~DJ1s7_Ym7l-bnP*{UW`@CIlcl82o;{1o!^GvR@`cuaiUq5m%!y@O(}oU;8PK!H{X&^eh>I7x++Y-ey}oN760<(%g-*m zxo-560R`Wz0;=A2 zv~*$fLiCJneb+E~Z>kuZ4^mKcF&(a5fs%QKYKxK}sYcp&O{uyhCFkcWuj)W(!R3iFgrNr9kdW|bsw-qG zvx!u^yC(lws|*v>4l1t3Jg@74y>nVlm)?jxTK7fxN(OpH~=Lh zrXOaq#{vJL9JS}rA;n#z@OmZD_Jv}vt}DKh(GXBaM@tNk0SpMF;~6km6!PY?p3sAh zXyU*5%uK^;ybu5m6;`9fc=WcVQg+<2pzt!DMeE7(YnC~=K_WaOix2)7->sWB@5{?e z^o=zqqUo~g(>)4RBg%cl)0C=G^4E_4)YkQ8%d@vIde@$A97wP8`{a|=omUgNbEj%J z+!^Z}zFbAL{> zWdwFf;-ahWD=M0F=BB~rXT{_?5%SHldYsmh-zja1wVRtc;$670$TAp+tbYG1(4x{O zZj%d_9mV_r+7FnSv30bV<1b%S&Mqt`WT`T-v&&NG-j$cvmecj6Cs$iCcRj_dW2nv+ z@(rv+`Y9YYJ|>=J91dt1f>97FWKC*DeLd2OqJiU$kCcSO_vM^ZH|h&)rqv3pgZb`r z=u};^nd+>T9~>_K_ATB6?o_OFsDJmHS=^*_tX!AtlriHuSW+NjD$PcmH^;ZuN6sa5xi?r;&<-6 zv0}Jz;Y2vPHySgOn|lI>oGYA1Aj`CwGfR+cEoP)GBi7-$;h)p9Be!Yh;`S`DZpl4m zV}i|#pX?DsE+an>JO6eeU(uXBn1j=zypNpQLO)>)h*$_56nH0M%HW87M#*MWetv#t zX6C74J%mw4RIw+R-V`3PE+#Sni6Yf?IiE91q&%sa6`ntT{w5%xzKIlAU(c|n_-~yS zksFXKRuCpm6QvcT+OxB;J++VGFCxkze&BSSs>sf6{HJIrO(GpP_!4nkTmSS1DEz0? zi!>uA!+$~_Bf86{q>hG9bs-@g{;!4w8LL0pwM*cAktvXz@s_te8Gt+%Inb(FJBXA1 zquV!hnK((BLS2PgsZCG`Pr;zt3bennIT=u#lE;ccbJ$kVnCnqc+(m z7UjQxCQqV800a^xz!KDPs+G1=m|T?ZIl?d7RdL$30O#mBIUSFwM~o~nD*_uK{m)@O zL|!Q6p5O^Fg-VK@=w8T)iU~d0hqq?ue<+@FWt90?mYY z(vXvd{a?R=2>m+nmgj^7@Z@x-PEA?zt}Tqd4hR_R=}d9 zyB{5*@bJfY+7&uYj<}$Pa1l+45Zk!?i)~X99x+uHl-kqhinYq9}^ut zI5gCqYbH{Ohn&m8TLR0j4l1z=Q}CpO#6(QyeQ|Md$6~f4M~(<)x|A^Kx20O)|JrD8 zLnW7M{9H0i=W!sxFPJNw@_DfJ-`F>l<>i~fY%%nq98?UxJm0%$rzH3K_E<0h0AMm+ zZU0YthQZEf?^9Y|nrHdk7S|H6e^3L)>`U)Hwh zqLK}~^@F(TkCB=!C%FcCMvkvAsb4Ga5HO+r2 z(4Qx|Jt86kyl2*^uCl^|q1x)|$*zK&)o=%I1wBK0n*uF4BZ~}}j9SaURJE19LD0UC z9(J6s-lUN9lDqFoOHdX#i}?blBegT$CY~v<==-vZ@jbt-tj=qBiIw%0dBW;qDIyO& z(HZ*#yPgyC5kJJl#AL8E(FWv{`yu+J5tL5V+k0wiEJe9qUI)~1CuWa`$Q>Y{($ zwlvkn?CS4-bku+J8^|vyY6U}Pq3pL-@5%b9M>kcAx>lUEwY5zU;xTIHoOi1dcLd7i zqAR#yAS2rDw|3pM=z8P{G*3E>?1|vdoo@evaJYTA_2A&(^dw8|MWNOr)rd65&PgA~ zES4VCl8L0*3*bJaA^+Qe~ZbX3%NKO}|;gHA7S z{6o(oM(~pv#XAUI;^HpZ<3C&++iX!#P)JxS>d3;_OtfA_*Y}yW+MJujw|#j+Na$oS z&Ipn1)5Zl#)UxpMD%a}gQQL%Y;j6XKmxI`y`g&q&0;#hN@31NU0)^F!wz@3KIJLxg z7nPA!A4~Clz3lEgxTe>3dB^|)lXEatK=jtal>n#aI4)nV3KbwS1Bjg=W|I7a6<%dw z-AtvRps*U9R0Y|rb=T?=P^u-1(+Y$VjO(4YTr;g4|8J8+lH08NOA$MMXms4iVPR@JI9S11VX?8OHPM7849u3Fmhq!++`Jb-(lkAZ@_4 z5Y%{&LQtbn)mo^|h2Rctfk5QobQ~}qn>&(BDE}T(TK*v0fu1GgTRKWYLIQ%;`1p8P zX=&*|`FM5`|FY#}$_7zD(kdh?TTON5O`CL0727Qu5Ee}cRG)Y(hK{f009_gM1r)`F z<(bIHNU)F5@J6ps<@C;%T^-k!l$@e_oI=xc6XczooK#icOATKU5KwcZ*2p$qe^w;r z0uTkF=G%4&WhNCW3Z&a1Fh5lN`i8oCSms+}5i9$`g6OEfm=%)zo@G9f3T0t7Y znJ~CA37YHLHE@6jTy`3wqJSjj4cFAv#Iz(`6)gFrstEwnN>0WV$YT_@@%wI1;Ik{J zLZX}IQFcocj3$1+r~T8vperpUJMaHIYa4ogi;M>f10WExyR5awUz7;ulw&~m##`W#;38%W|Jl7|q%N5W8BodJy0!#&-jnlZR_l4Z@wTS~S zO^)X9Y#}%Flda9_1R3i^WnL&fNL?hL332&T-)qRLFkA}Ar=Y|{LA7(OoGy7b2e&& z_e(AR#;Y*n{}T(fwTu8|t`kE%BIf=DD{Dxw5?L>VM259MY|f$$DNsV@hDa&F(0lz8 zx{mR$Kk_gUOq5H$@ouN-D`==rgl4}$AaWasxE$^WGIv!_wf?|fTfc&g#BEyT_@2Fc z!F=RFGD7uwKtO;~XFTAiwhUc&cX!~V;MRdCaDrI}o4^|WAKVyM&CpczF@?uTZq<_i zianClTv!zq72&SNMn|_*(H}X|S03tb^e@=tDuy=&=RUfG+qmso8_EajS&sFjvM*b( z;)oR5Q2md{1ztw*8GWhi@X@3Fg!Qbiqn0S=17^30!=o?`Z9rh2Eaf? z0TFz=?JO-9(zS7gZ%}r(OPmNbVFJKARZDVVKgXcB*iPhur2)!nLg(Sbj~nki(ipwo z(zbLO=c`7dVZn(zy<>0-pqvKQQC?o&+Ir~?qg+!862n7|1XZ?4k02<|v?aa$s5v4C ziKztK0D1{f6s+8PF8Q3~IZHPLZc)kE%xt!=)V;`zA~138r+@Wh3a;i<+wbC{qM~48 zscuV3#!7|g81Fkbg=%Rf4T5uHTPJ*indw-hM}kjI9sGw^KC6fd=iFXeS}HHRn)&M0 ztL_4~u`vTRg>!TcCiW_yK|D4$HwT4~nYtAiwt^M9gJLXdE{<{!6iNpqUjjdqP!x!m zzM+^a{a8)!$rv13ndAC^(0_))?_4o=T^kx2-oAaCkiaBK5wW(?olCxU!Z0o_?#0WO zvf4qK=e%}Ghzkg9u3HR9-nw<`l*XJ>)Lt!gp~LrF^GUu=qxu}TMV)Fm=%jFxblLT8 zVuf3C%?5yhT}W9i8JQZw502wg;N_2C=7;AtZ3UEX-o6bTa4#+{fY5cGfgxe;J&%Rg z&g&qTeJd-obNC+PY%E6+667VW63=(-k&e<=C(mwfLn-`$tvio|QwTHx&-omLt^WRp z@?lrC${@4G7|Smt#L6lyWyu@5+PxdyAGz=}+I>0%Q&Caz02ul9$}9|CAw^vP({g!H z7EmM{5Y))*e63K>%gMG3v7+;o1yVSbDUU;`B=3DXqpTV;}G|oPBl9JMbuxiDqNc3BLq0+%0 zO|HJZqIm;|6Q;x?^Z=(2vZP0~G+Hu9R!&YtLqtY)R?Ykz+)8P8M)w#&3FYAVfo)nwkA z|FVXEeMYMa{v7VQcc|h3O=YD%^4e7*MSz$|GQoVeX;xMQVGWsvYU!Yh^Tt3^(wFSL7mCU!^&D-Uo}0p{(KkHVHZ?@{-(UpQ-MUaCr)08B zPy!cbVv{G+RdcuZ^H zDBep%I58Y*Hh;(3TZd>na9yfw1Q?KmY)(E&3r;bSPhFO zge)P6xFA-9Amac9>lZbIc&k4=^;V2uH}e198M&BX&s@ByYpK=$`B<0OPQ(Njrw zolDsOUEIgpn@-T-nvu57;#jllQ>Zi9iuXUeVD6?sCq%NxkUsX8;y-msKn#)Smd`f$ z!;p>9)^s*iba2K{;nm zW-S=xXb)D6;yb@SWHo|n@`KhkPs&6i>6#!PxsFOr1pEj@kDf1IMtOkgTxwR-sAvCG zCi)58^cvHep)2Fy;4olDVYgtYR&0w74yQkHV$}57*nDE_V(Psj7ptO#xjFM98bQOR zpE1{WS|<{TH?%p$&fN#AaJUX2a;Lfq25*`URh>{KM_89FPI3|68{KzmH@&KhWZS6a z?LIJ&;9nM(!kSA($CGzztWhBEI0GEOFpP@QdL0y$qu3c60Y>zWmyeGiB%>4vG`lV=SSmTp^nOm44sx8;iT$B<4241+Ig(~J zAga2PMuO-sh}9}2qv_24sKO6&4J7&?%OKtG(W6HKJ@~u#?>E73K%!-PF1Zbz+Nd<{ zD@38-FvQOQMyR!ggs{rW+w&<6S%DgZ{*p`#yGH*B8BR-*@IH26|Nhsbpcx5SPrRj4 zRefSxa@!<=URo#bv4KIt8(;9-Lc0&0nlP5JihwvFnS+t>dsmB*bwX6skEVDzSxEdl zhkXi69g(ryN+yHk_RSku7M<#MXuZ1AN-ce+O|h7}ABF9b(HO=QHJ90*#j&N`0w8@=rlVs8Rikql zaq$;vHgqVIL8B*^bV~g*_Sy}?+Hh5HBOKgd%}-BusOOWny)5|Fl}A3G5;&gOnO`1i zu85FzL=Nw|DMcN0haUT&(EU+R4USBN$;Sv$tR^J;v6}%K1;|bIINIQi82&@CbwmmJ z3b;--H4*P)UY?$EJvMi%Pf=4}poAzr%3-W29ui`6ClnzPRZ(>J{Y?($K z3YE@K+_%n*GT;;inuFYos|;ef7G#K43xvwA;ak!0Q3BK;1oD4O(@xTh6_0h0&0l)`t>Ue zt|}7qmd7Fngp6<6!oj(=^Fv@7D&MTHJAgp7#0M{sB_54!6Q!7K*wz5pBipDW;T{=a zG1X_{iWZPMOMyNfC3T1%C9`v`7>pQV>`6+v$j;vR;}oLLHg?dzYv=q&E$KCP^eLpk z4LNbAXSQ=cfW3ZtYbU4>T0gWVvzmCjB_%DRSMgD;7BsgbO1DaFWfJ1aE zHyzvzJP&yE51&;LLrr3Cdp{8Oo#fiClWqH6;{W_j2qR7&8O-(^5i^YVLEXK1K^b|ux~IM~>la3MC=$&Q@bFoBLT zw%lAdlFl7(4*9ZHR#tFr8b>)zy1E#=Fm;1KjgGF1M`UJNwrA=O1rxE&6SAFK67!Ro zF%+kCtOeZE)s1H}Gp6!>VGfTTMJ;0^fG`UJp;R+WX~3AW3_W5Fep6n_$<5U@SeJD1 zNtIr3B)z|&<8J8d>)*L^2Syt0W_s;pcLBr!oxumExzCIgoIft~*S|LY`Z;J(0owIN zsdrJ%&C3UKLo|X?G)ksIFf&e+20B(jH(?$i6(gz{91J*`ND_KqI0UxW2Y8ZHSI1!C zfCf(B*DCXZ{M@dIfGxcgG}4~6526ll!ZslyaIl~V3^C2QZ)dkUR2>q1nilPY|5R`Y zi8ii(P~YlO0ynAzQbw+;omA*i+ZdV%v>A*o!uW+bgRqSxeyYNPzJEVq$*YY-9L)zP z8SVfFB&&_?*McE(8MgL@U{X7u7euqF<-d9R_7Vpq-k%5;`TXbaYYk6ruGLk&?$wKfJ9o^1UaX=RDW zZTq7{fQel74H~3C4XN^h!}y0O0t<(1+kZkx;sy~D%isgVo!ClP`j3D9&R>G1`$HTZ zn_fLcVt8Pmf0OAV0tB>)=4k+27cLC;fAi#;he&X>Xl`w3@^$ibSAn98hld9b5Dyz! zadF~0kf>fS_d}jXL_8uP0g+80D_1%;12RUHl$1^_<8%EzC@)s3ySuqTD#<9FiKc4- zb^HBKg>P_F^F9TJYAs5@k~lQ0g%+7ym2{(^W$?_s(+SA%4mWtu<97RftK}oPGK1TO z=Jhx0t?+lzt5p_5+lGPUq+EQl!a<3Ni5VFg(ONnj&Npv9?_mbP;3tsMUbNiOGD_)> zB#e!*8&;44FgrWj5RI4n(X6q#ikJ0kzr6wmLblk6U^m24*w%xX*VtGfsBTxL|AdUF z80k` z#2+;xii={AKC4N4ow`WwnnQcxqs<|^&EVmx9lvZm5DuwX$;t~jjA zog^82@7zkJ?e~r9#SMUUX~8QTRqixOr|&ier=s(B#iou%sn_p>OZIZv1z zS`-)NjfO<6d)lL~fcNTHS0k$i#;a+wV=#7h{~i$9URTBm<|%hTCKse}EfA77?~93w zCbWUxs7)a_k@EhZrs_SS54E%~?K0WO&`om&xFp-~ZMb6=ip{k`n-0Ju*u8us6UHCU zO-D&NFn4RGQbtnAhMLO;vZ_6Ix%RJ)Bc3HeICfHS~V|ot|_PkG&(wXyAKplsE=uB1b_-j(x(^=LE?b} zB>Ny!?^Tl0KJW{2vC{GxbUfxaSDWwMyC*9W>;&+UZ5f{~$hz6H6ccVJw`1+<{?5r< zZ%k@RMi7fmKA8o?MILjp6u|<7KZ_Gm)E%? z(qa5c&dCD&{HoMYZxXY~JmrnKyQ!wAO_oE-es3Cty7~~WDmyci$AYKH>T?naAgH!kK--mo1+l&%OF=EYW!|_5D zj>B|gFICyT)(1O7=yH@QwL%^Bt`0EN0HKRQ*@3tWX>{>Vsl9t5t_TkIuc`Dj`8Bt= zU{Y5c^6kGDM;IPM-n#kQ@NZaB^z`H}J3(gHDn2#!%G!#R60f;nF8OG|ZC&}Ezh?=_ zCN(%J0oLo_v;pjREeXfm31+{5M$5~*Qcp9ZcvdjoU2tFPyZ;4x=k>JN3oBOjA8(;2 zFLvDj6pBvmF#{$`prtM3ScXX;UWEws@czI@eZtVR;6}7 zsP2o`J!C1BDu0?j|M>B25F;cHrXQ8BMRGvZiKvhHdGP6geQrRg$unc1BGwoQtiE`wih4nitVF5Wo7Q z7C|N?C0PV=eS!{c*xEGhWG@BntJ{9&+^XAt$WR&q<%#HVkyG|T2^5e2WF8A=5x_(` zgouGhJbo`uV;;AAa~(;_F`~R}=i32L<}b0%paMZd<7qQLYHtJAr}dYgcDcRhAr5Cd z5K<~|dl|inHFvDX@iGA?eYfVviZ;+yuhtFQSsus3lsoW zXe5q|jDUdMU4PwcL%4^Jn2^P4n|f<`7`Vnteu9axN^mg9SJ)lzsDE9BpZzGWH$g#A zegb`lCT-y-o_dzgqMQih8f@x?k0FsTGl1`hfiU*OkIFtMJXG}qI7sBEts#h?a5*G+ ziHXU7{|hPj=8l(Q_*4+IE=UF@fv?R@zlo*X{%T@MasRUeTPQDeAfr~0&*5jPDlZgV z+QS}Rf6`x%uAp8A=L>#HL`CT=ey@4fY5W26L1?6;&04V?Bl8unjxp!yU9+BeU~9Vq z-z5I(3{=PhS80a}WXTM;U>X~lz(xiPr)s|fwFt~%y19A5iQSnsF#*ONStu_*65Tvv5mAlppgj@pj_Vygl& ziU}nBo{a4D%qbCgK_;S7>K%uxBgbM=QnVvnq-@|Tf>V)XK2uay@y*R1nlW&_q=tZX zSFoR71j%Hn3OIjf&Z9-0pF3g_Cq;QQ4yiLBM2^$GoWE{up?&svzpDfB3nw_jYgENmj@&@i>fdLrZ+2b%xajGX$B4?V4bHLew%8qp zTjlf= zOAJ4wSN2+8rpm$m-Z7(Fd-VBV5uakR%&#?6BD9&evzA3L{n~hA(ga)jCBC)V}o>^Ul9sQ2tI-wpV8NM=906$s%^uTs3P?pf3crZE3I1D@04e0tI&P0 z9SZteH>|Ic??7Vh-({P`EXuk%tY$fz#?Z|h|5@P)rnI&s;(f!PRi?l6?6c23&qE+Y zszpM!54qbna*-|p5L^LP*)mSQo{B`9h(XZvkDNYy^vYJ{>fT>n9FT9p$Q#i~w$lS= zX7|>#B=g;}l+gGYqTF(Rx^C~Z?i9D4-cTZ~8bggnhH%Wd2Hx4y^2g2rmmBTkQ8Q)q zK8d3&g=N|VJ3C4DNGsEs7i(q3O6n0B?6rp7?=N3q$zCFu+en_cXv}BFYgL)KM90UY z%u}#1f*u`R8L~Bu;q>6FH-POy)`(OS)Z~F_HJy2D`DBL~X!6^q+5WRxG;{aEhlt3zJS?Bf^W3zbdR7lMxx1s`Hn400e+ zv1eesjJ2#SB|~o}UKcwSS@CHPi4Cz`HY1J{lcCcXl`fZ^SNniD`jc0sf z7jEa+iaR_jPCi}G8`@#K`RdMr@sk@1o+-ym^gc}AzQQ(`?qOvw^eSM4QPyd&Yo{Fr zGIutqxI!iMzK0?Pqo{pC0`yc3F``)EW44c`p2Y`C7R6t`-{YiT`^c{CR*q@_XIE-C zL(aj2Q`Ray)GHVA>Bzm0GaAuS(xjB+ob}Y@5ATXmTg#p-W!LD+wbT+GuE4U_eq8J* zeE)epEFn6q%8v1){h@*PvOC%`Ie{QbA-35@e1Bb%SkIZ~!pT4?O6?L;itR=$kbzLTPBHjK6uF;L^=NS!*?QjRf5EjbK7g7>?+UO~1 zO!Asw^A^--m#FvbH5xZpe8ycWlH2*^pRh|tiBo95035^mAd0^@Hox->{|)cYX_F)R z|9gDZBp5i*)J7qI9oieJS+VV070GWSV-25rL4 z0|PNVms9+ltnP}3_|;{DnGnqGb?wuV9jic#4-o{p3o$nF8S4%PZA>z{WSuqjrrfw#>UpyVM`SqwWwJR3%nwL(h=>kt;;vY+i8zJaTZxDqSN^jy84wTVGJ;IbUr`e& zb>n_PET1MpmK}Vn2hZ%Fgo<#7+?2kY@4hCZb{ZRp{P!ELt@N-wIcGdPJ*O`~+#{sJ z7Z8Ur!$`*pKT%Hg(xGJ=FAYC>_Vf=uE-OZ2k+jJ6{9fPq0LA zD|0&^73>CO@0)+tBZKq%s=71h{`SE@O4uRQ@?VyE&#yKJ2o@umOa$uu%9#GuIRrUn zM2C7G9P0Uhwhr4-qAEv9`TSj0e4P~U!2#Mnqraz!bUfJ-lm6oidQRIo@M{US&sB;~ zB~vfWQtN5KA}&!~dcoYa`>PJYzfk0&rEN!jW4v5U)^zLl`pULwu@niJ+GX<##YXLd z<3hZ={l#g?E-ruqy&V2or**!jP6MpVGDc3tW_&@%PEBlDk;SGCx5(LK%r*ZzuP&~+ z=Pv8By#Pa_t`@j~ttpePKEBlW2uf>wnUfsdg_10CStWxuz9$7)zrS{I@Yzc?#JuPq z#;1c4df^7_(C{#PA33VM_FdBjGa7V_>9e4J5umVs69 zQdBiyq%q^SgEvhSS|_uMX%C)y{e^k@l;5t5{oVI8xJTN@xfm~3b@l5F8jhMSjM0Z? zX$ML6$Z+xMeH9ND=1MlUG?_irbO0RrchN4Z(+C_kTDzj;0B*wIqah)?!}+}27hZQE zLO=7;+IYJD8r|q@>5j3beOic*QiRvHTfr7hALqK!l4RnVYjOXh;X?#s?z}PdzWC9t zpi@PcG2`iLTv_rdg29WjHmDBkgx86M+nfEYo`dQjP>NgY4bQtuc zz0Q1FMAf)!ez>}8$^sY}QfvV^0z?DW>xtPj-R{`cEfbXrsDJd^=qRAS)z)V;08PVr zwcky-C`m8qxGu{`2X#kM#jvP%Gk(N@Ta8|CN|$F>>w5bE-h$HoSkfxhTcGsnA*rwc zf_?L?JMZ4=Y_KhWPk9~9D`p*_EI`73sI}mM*xEPyB=PrqHV!xE^z09@zOA|Oj>4!t zE2j-TA0sgJB2PGKAes3!xL3m)EsP=P$uawv3CMKIL3vj*3 z+vQN4^4Xk{mBYTocj%>3@&2-Bwnr9yDsD199z{(Enfz9~ArsC|2L zRs7rB#{wU;AUpxkFR3~HN-m#Pho?S-TBkVws-V?-RHfz@N|{68p3T7%0|q(o+WI^S z0+1kWZ|+?qix4L;2uLti$lz+Si&cd4<88V)MD6^EZ=B(s9u zZWM2Sb{|u59bZGSR%}U$M|rK~9^X2Pgh{&zF8jv2ODuQK;s43o8x&mBBWCgKa?dWU z&xJ3N?#k;o_PvJ8leJpf-|SQ!C(-IP=&89ZRH>|+JbGz+`0oLs=aXwXQwoJn4VxCD6M|IOjCKhCaA|#ho)OmfXO2dV zoc2H!hvE;*mm~cR%Z&oI&86JS2ed!p)X0R-7#Z5d638h6l7-+DEjqeS+Ekl2a`Ca7 z4RsE7czUB>Y0$r)^}>E?IT!opNu7*29+6}%};#5md5YCl=26#n~B0R+PLY(`_* z*1b^zpVg)_A=(BoA9ygq>@FEjEu%BD+9ys|y?AbF$6_LtKfO~Eyc?}Va4CvPM|;z0 zqUnIZji(7K5SL90U29P_!nLDpdWy3luVCxKOO|)LJ7zl5Ou!ptKDKLc4M$^A*9-GH z9N}^!<_501ks7{)}InG+T}tGdujq4erASKdy8) zpa{If1?32l9ct4H5Ysnw7&c-Fncr|*TXoiU;83}k;1%^T28o|-H_?xuVn4lBV*9?J zDI}$^xvsc8k6be;ab|r9&7^E?Bkuwe4Hb}U%tEJPRE)nwYfYC5wwLT*f;jmVlhwf|o@Zb}fi zl`{}rw@pF#;NA1!e*sR|0|YaUKp&-BN6$!=qd5&9yFh11QbRa0{NdDX&mOh6i^vB3 zcDtnRBg8&8MD~eDMSNN0{WtVPuymgZ4Jko^gc}g5X$$(Mo!PrL7h2;WnEdZQ(lJfF zZ;20#Db(+Qv$5xl6d~>_l2eNeHMfl9A6w8=fXBWBW#eQHKKRw3hIpV5;4RAxui%+< zc}9O8cYqIHQu^Px^T-VcOA6QUb?xD2ozJEYRY-E*DmBUZqYVy#O&Fej3&Moz5}!u5 zT>bsf%UTnTu^2s5pVk$-tLA=01iDp%?2!jeWzIxF0y9U+db7!E`kteL3?k4LL z)^6(`e&!!Tm^A?B2p&Tg0|10x>kRpkZLA1iKYwK!)7m~7%RP;dL%c_jI&uaHML-NkZ}xzr*;c6Tr|+UNe)f+(oV71xBjh27em)Szse02S%EOlxqtC zr*`i&f#_SkFLpSZU4}*FLQ3ULla>i7sedt%(ZUU3-zNaDb>IIKbHR+_cjzf{`d3H^ zxLAxi7H4$OsKdP(S`h)S=QW(MNX01go=36v^5CeX!f~2cP6c-sf-rZ@vW?Zw;XgF+ zUTY26*P00(YyU{Cfk;!F!<+)ZWVFC*{k$3o*1LI_?&a5s^iwXdkT*y;v5!Z;m=bsd z)P6_PS8g2h!~TAN_f_gE2?YmN@{7Vdt=D`Ps2EGYzX({QY}BtxQrd+U?e3m}WY3?Y zw(*+YsxFu3Dmr(8*Rzw`5fb>7hzk+dfyDUL73jju@gi0MJ1d`wcf8u|7UOg3ohw); z#+5!XAeHa$gLeC`6|FAX0vFq`S0mFYWJ9L7Y6alqqmNZOjc2Cq%Rlr`PX>I!eWM4o zY-Az#1V^#UY31NI@C|aOY?DL>(3irTW~oh>{%Q2@_F@%A&NpI+kkFCLjDo3c*EByM z1%!Tc+-PIgkf$UvdI9tZqj$q5q}lK9GGprp!=~tCrQrV!d4IrEx`FuE*D$Ooawf*J&M?Z-{Pq&e*kCGA z#rhkI)Ke*U{^inh07T||!}snrDi|vA_2yU}qfefS_zyx30+pN-Z$9h1*&ULci)`nX zDL(VhA9U-bE~im_+Sn0ciuO#5Nl#YG;6Zqo>vea4p&d3AyAKp$fUIBqL}9mDLNE_I zMLUPJFN2fIF|ac6!<|0)9Nu&|?yF+Azz1hFLd^!CSX?wjUa`~DsVPsSB!S}njTG-* zbQa->Cbsb#u%sYSTKZG&Vof#|8fZ2|9jk@CzkGMqNMFyp(|@+4n$eYKEh+cLFqhuIoZBw(;z)y+YH7$IH4sv;rT{J{`K(bwjyl5MXvU%;j&=Kb z@tQJ^Jy2pcJ09edFMfSFgYAM)3STTz+}C@RF;Ti0*B-^-LCGK?b>l`yjKFK;#JTxO z#Gk{TewA?BReSo!upCYyk9M$c((Fr7s5qYXyuhYadB*rY zp_TnW6M>2h`N!Kzj`(Epomqo>#RC_2eV*M#pszf1BJZ3P7EHr0aTf9vNRIxw-zuI0 zQURZJl+jp9q~%ou+whAmI8zL^TAV{hxI$dMH8210pMUBEPZ!_kNI{F66A`1yl=@kK z`5Dv)!=C!7q!U3S6HoLNL>7ZYTs^#idS=p9QOAz$b*Hq(I$-b~Y_yX-tR&3vIS5)# zY_~OCd>xu-y-rXkU*={9Z}Yo?RfTC=>WV*9pSlqm5*X1eGhy-wM6X}ThKR$2(|_ix zXNvShO72RO$DxhR=LoDKF-pMU!L38)kC?n)VJU)?kdFXONEGjhFkyQ@)d+Ti72dKC z$m0v8e2{2T{`^9<$pYPtzZkYUxDs3E!yJ)EWv$XyGJ5~n8unKu z{}vGZ1H*0kbE2sCLMFEUm_kzq#*Zc8%iN~Aahv^wt!{03POg*hX-B#IJNS}f(|*_1 z-C8O+`Yf-$m`D>uQ#vKrI|de=T&I@W?eV?Km20ZN9 z66ygSr`B?;@RG|;)l*CPN4oL7X$e+isKQQO><$Qww~4TZ2hx$=pa+dVt6bQ9Oz4VP z^G(p}zSlC0uSBo+R_^xpyMow(0Qo+S~1TM6h=G3v3EO76M&Xz5KYIZf?D}ey- z0Zs=LOq%TD$Em3Y^=8>*CLF%Bx@WGiy4+E9urzD>a0!OCe%-llU1*=HLtU2omqNHdT6qSNy2aU;Q6wrP+d(PNs6A=Ni6nD_A69b;moiK* zbZ#)Nn1Cn``v55EHDZgxG{NVByFr|8WPqI1n`iz<5F*BQA9|SZy3ywSwHy!xK_Gdp zd{bUgkxlRFb>5dl*z{_lO#1_)S2rV9WFIf}W;EG*_wmypU5M{|I8__6Tx)88U7rokgnk++$zVPzIDRjtfQ+s<>J zcWg*8n!DTNzR0TTh@`uoSq*6_t?FpTfK~ zIlJ;|sX$$<5nbg2pKC z-o6!2p(RFxL`K-p&4wcXz0JtYc8=iL_<@hBOC0&+^HuD_5O)FvdM3iiSuqoDE-j!g zBBfS3PhFd1+^C#iENtX*Y(qc7Yy%8~{N{7~ci!OUs|i|ASEHnI84hp!e!1w7u4rc( z0TD?tN!&MgFWR#iPyz2S;i{ehf;7{I;;_@+AExF^@lOKJmaVa2S``-uCE0Qa!YZS! zlIW<31J;$by-S~OQ#{}b#VJ&I56o5U4e82sK39d+gvvO`o`mAD*a2_EVE=N7MPZ@< zGfIR-HYbDT!&HKnSBpy_tdEAIfi3z+1AjWtsMRR#P}vwHEiSCFPd9?%Xx2j~6iR6H zNMsme6Z|=WN-wEUSVoUrJiSnu5o?HP&(@dpVXMbwkJgoSy^Ag8vHE$gbn)ie+PO|C zXM&<{Ue&qKb$2}f$zvymyfJ>2IMz6Ko5#~AeuXT!p#wp_C4}ydX#Oi^A2>ak614*? z@3MdCQcy*epR_5^-xPh|Ly@Od?gJHhaNOL$y>!Y7ZzX|)I>nU2OuH-`PvJYW$foZ%j5Kh`cwTI>p4O}=_tBS*VF>Xu12}r4Gl(`@H1)a zrdrg$x|L-~VZiw`GfRIj$I*r3hxc!0-p!wi3_qD1RK!P^YSYe$zLN1c2KSi4;5cqr z&{n#3#o^n$tl#aW;=~++dg3dTw(pe5k$61Ac$NqaNh}l4s_THbOt|Av?&c^z z?98d5qp-%D<)5BSKBr!BOuKC(a*Y-mXLP?zGxJL8lp1K6?0#`<)4ZI{<|L2#*Kg>P z*Gx}exZqmMP1FRpQ*%Bw##vKvao4V0$oCynG32}{P_AK=HP+8W@OC5fA$&jb?r$h! zUK}3pDPGFCjdTS19Yu4o;_u@A_QLEjrspp(w3gPcd7G!@=0yw5pBO$vlBX@R4SD&d zQ7ye01)A2;{gGQy`DMU zK{oR`vBe_&X)BoGf^`arND%c2B1=dn1p$qs2I+Hhg4a$g@tUp5{*z|*?m`NUNSBj; z>6{Ar5sQ1Qd_|rPTtw$1*p$dC?m1(%M&kmxNT+wPxF>#rJ}19XmZ`?hzGCvYzSFOw zoiCYg5Mtau&e-=%`OBJj)Cv-e)aQ#*pry?=j6`sq-V9Q!Ue|O>L>QOr-#xIegJE6s z#t@8e-I;rXt+wz-5;3y_fpiezh=>RxbuChFI6`{lb2yqtM2UWY1UiZBLvzVf^CKgY zRJ$ufKhL6zULnKY+A?CihuxvNvV=>K!FH|+>9RIC$^U=6eR(|8@AtM+R7@(Z_7r8W zY)Q5#B9gLi36ZhyyGll-BH79wMaI7GLS{=+@B2RII@dY(b&z-767MG#VST&)H$39}bEELrDsGWY1lr|ScYr@>Nd(-&iu|`) zvb8J!L$**6`@d63_Pm)t^GxG~^FQlvJh52o&#ha7T7#cfagZuDR9Gp1&)8EOf_g48Z z%U_9Ogyh>n)Ix#bx34avkNxIToNQ6R$;&(0r$t2EOix+**dTzSeuNd8JAs_PYj8SVo_y*Iphd$^KVje4g?&laUg_uYQAmL!z9k+Bk z7b9##gdt(Jh@?88Sju^ThEsLu2TSM=8dF3$<17-wbF3~Ysf84<+`IE8kL{TiF7=Cj z^AJtaYlv?Cl;rX4A;_S_dxS@AHuoYqCySMzuYqU)ArxS`C(>!gK5k?`oP6lOpgCKn zz4Ywm9s+N)ta*VB91&h#PaWmU^C8oW@jYI{J^LQ6xBK9iT(p{g!I<^)wrg7(ABq^Z zLT5J=_&eZuilCO~FBKGjcuY11s>=4q1j^3)J!-LmRO2%q!{Yp}19H8-e0qtPV4h>D z$!^{STq|vKfdn_8Ae%PoBpobyY@3nQKHG1_v!nw`0bXt zv+Lw>%5vt>Sn(SYLluk|vF)T6-coXVJ-g{1@Of6fWvQsv%6Y}XqOZR0Qc&nfQk>GM zqA*c6bA`TD(J@h6&G_bgr|Et-vdg9y=N@FQJujbGqNGqA3VHowWj9ms@L~J$UTn8A z>vG;z-fJpjFN>8NEGxgH`z-ILpJ{*EMyW{N2PNdLBP_`eKJxn=+%|iBca%f{Sv2nc zDV_TI=J$sxoo1dLJX~DNwa{POS<;r}!r1c6*Pm1lqbr0vWBNVwH5*6Eji>dxKJx49 zH=FOYCM>*_6e%xjmGbQ{WRxopnrV+wsk&71F^Z*YuJt+DgQQN*&dj1eBYJXF@ZLuL z^J?=^Bjqy}!}KaTC$M{3x0GRaArU&sQ56#Y5gJ)BhzwSG=9;_tW%hP+XQF?2rMwGU zs`}j9FUcvdW$?t_N1}b!_=u|NRPr|UiLWgaUahYKZBWA3wMT_dHr179q<#KAH6eIc z<>7@)W+zDnaTyMwM{5C+YVuKyA6$6UhZyM&lvpR7z+M~1@ZG~WQ7oQn;^oxZ+ll^2 zy<*{W#+8dwxJ^e_IsPu;9so`pp$bSorf!eljZ@TcOw7F;V45npbf#z5$F9=^4u;7R zRSS0&V^v5Lui_L}e&{Z7SFxEdW+vwI(CBbQQEhXi1r;hmVCUfG!6~?HN}}kXC3&wu z&3`||)9x~#i{w2=P~4<=b766E9*R-5$enGWi3;b zm(BYhBRT+((jZ){AR%ENfTD3e0=te718@()q##8vxUX?_iJxz{`1gxp5F}?3NfOY- zn4Aq)k7BamZI`F}p`Cp%?2cNHa)+rbw(?ub?IZ!|mg!uwto4*W$^B~w`y{s^vwbiF z_rCWb0TAJBO8JcgiWf(|AHK0W|H z@Cg(DEB-!kQQEbC;%vr&N7mZq zc&di0ososdrD?+EdtWztuhwfs=uESB?V-PisbYu86V`bc^mSoTE)3F;L`~lgd?5F9 zdVG5NCtmqe)2Gkl0^~dvk=ELh3k$hpKEd~DGWOD2^u5!}!(P)cZ=!sqjI8vZjrZ|5 z?%Qzxhw9}xo~2_4k5}2x%pO;26T{ptNOA8^92%1_>-;bmmbnPlK!!!mhQiFZH~;1c z8_)amr};Bkj!#`M6?X5m(PQszGfAj_)wK( z#X97pVTF)APa@s}C2mGykwIY>1|6tdQf|C58jLv4;kEO1$Jge8Yikb!(P}?evfs|* zZaLK^K5CK^3>rSH(Zdv_Rx(3>HDujWGIk?4W4>)lCg%K3JXkGfx5rkW!G-Te28n>LE5}WERb}w4d^dS zGt#}SR%yO6ddlC={%){aqHbXNa-FrRkZALx;$wT0n^cpR_qs?YYE@iNLIirEiajp; zwnn$cXsR`7#OaTztR(Uu7vZ?*oQcQcb!}a`&_mxTbXDv59r+g1S2ha;G^HXX_S5>h zMsaxz%&vmvR~EW^x6pli<-0tu@cboqhrp}ugdO4>o6V%o=7 z?x|Ez2Vby(AicjFE*rh_b{w zS+D)mJ>Hut@i7O(OAd#wtj4|noS-~*+1nZ8g?+-1Q6n0J;p$71PV)D1e;^^3J%pu2 zZP(0F&%EaXTXxRo6&$N3>a*reYj(7OJ&g?sw;j)yzsSO=9!h{NgT&if#0^!4!Z!JC zYUI1;tnoRZ{7G{trZimp(QG4b)@cqu_sLNoQGeTWXP>~u&d;sN1!(a$XTC*eJ-fR@ zTIKaOkF4x{Vmm);YMSP_y7K&WVAS3unO;BYeND1^kBbfMQ=vX>JKig&vP|y%DMf3d zlKoZQarrlTy_eRXEKpJed*W$kA8m9oQ|eOu_p!-SVw=kJKbM{`^eoEr9y`5d`VhK$ z-wrSn;w;|EV-+4f1t;Tj=N*qtE?q94qhB~c@LP%%Vj#J&W$;L%UMPVFGpO}vPC5RW z>N$rw-r&Z-hKnx8TTQ|(=ld(xQ)NWILLwf1gR=8ECD+@h!Y{beFS>8OAd@@#7Mv5t z;-QVMPQK+7W}(O9Z3?21{>uoH;)fYj8e|XhyUgLXlZ2eJ`1~eVaO1emW@O%dL`vnC zXS1I{pMG8VHh0YLZQt6d&^fwWEw;10X5^!E#+9{hPWQ^*vN#)56&^zS>vn|&sirT7 z`;IdIGCYVA3*b$}n=jwATB^ESGUlNxnClfo8}%Rq#^l7&7W*uOTkKH!;LvjqnHr9& z?#+>I>P>I%?Ign(19RZ=AD}JCa38r2k?@~M+;9o?Tw!>sN-@e}91=UTaZ^L5ik91L zFz!;2!Sv?*u!Cr|$PbS_^;++INbb)R4hlovCeb?|qE%V4u$e6uds5!pU2c2u%oW7z zAihBYIcCn7)^&>yYS~2#>l6!z{`pIAIUkR118asnq8-W65w*pn-}t~WABDS&JAwfY z*?>ws`~!}R;P|#c2=X1#sUaR1`6Ar$B{)3zhWH#BXYckwb^*}Tv;8EN59M|wKgdHG zn-P zEBb+*TdMx5TBxoSC>u*g9;)q#pC28w0)rUD$_@5k{!K|`sVuvLF}bj=5BZOgA51#@ zq9?bJ=Y{J6k48^qC5k z3f-HKArYT zS!i=%2;BQg-WhsPsp=J%4YQ?Ip0KQoBJuLV$`P-!MN^e&KN#FIkJcl7Ul?-E9PbJx zdQ(=IAo>eto3mjD1sIma+0`{&)vEJO)nhUz5ZAK(Lu%URCIGh@rJ`7}Ew5+b8KKud z-mwjPH)py{@Hx_v6P8*kWsUk55Wk6{L`S zoTbMboTjwh;QE!9UEZY{H`_Pd?bR5$m}3xV$`%+x*SRKjZ5W+8ekI9tW-=3%n}R%u zt=+7+nk+$4l}!GeCeB=MU!mK&dy#l#N!vxHMk^fCv0N3EX6AM|hv1p^=Au55DIBbz z38pfj&H^Ltt8!E5S*S<}SZ_9Ol&?-Ym+l@nZz<6q3r9Otv0Az%!8yJr_t#ol?oWNi z9cFYluS$@Pq+>Novb}!-)pmI0C;7pMb*()G^;&0zGMAr(^x>jrwnm76%U>rEqY>dI zU2}=(o|Ts~k}$@mY3HYDY6Tlld?;@9STw9!Z_abn_IIj^6nW!(wWGC1+g;WqNlF}` zVyn%LbbQvjAuFnJTK>${Y=B!!RC~|fOQkcf_F?P)8l&kjp2*eG_g>H1c%J&K18N95 z&j@RI+FUasSP<_0xnx^_t}_p#vVQF8qXo{z>yBP{i+O9CZ-@&o;|F9!3$AFB%89M! z0KcRGI9^zK<-X%Dm8R)M2VXl?B_#H#(M-b8P%a6o!cz9(N7Ea}xk=2P?kOj8m|lEL zw>~skXjX9@O%Y1IBY{?YPlLO;yYVaP50B8NmnJPyc6w^t5DWLSUalB8GJ^ToTvtAz ze>EYCZMSu=@Ch%k6blNs+!TC??G)&HK6rbw2o5(#Kh4RBh}Y?5CjASaYg#B@D(y09 zR&bYf(y&g@z3pc(*|}rS2cexx;JBk&8PA&gzp0aGGvTEf4HYr{&L@<7Aqx=Rotqvh zcdw`7Zo^5JpK*mC9lhQ{sT7fK`Q+dgvCW6e(eethkV2f@HxUAkp{`+-EL^ z#t!BG0K7fBQ*=e7drB$2s`ZhNhp2CJqPXI`pX<^bR zs3|}UQE!^e;umRoIV8eUipWaB!g2XGjo?UF)wCH?vI5a# zjbwCVq^O@XVVMy-k9e{82&@zMeJ<@1P4~Haa&d-#dKKA>nSjFqhgSCnW>3|L)OIn5 zyM?vvxqlWkfSHkql>o-<9s=yicc@2Sb}x@r2IPRK5R? zy1|xKJLvWm2-|SyFM0m?VS}MI3ogJg;7mDr+i-CQbc)~+xLjm-b;MQ zSpnFdc$DDl$|mbX*08#%7dOz`ND{~x>*1G{DAY+!0dMXy{(UHC^GXO>IU#^vDq!xb zWZvvA(V=-m*m}5AkSJu{Sf9@6tt*xzj`!7xgw-@5|F;X`%W@ z=+wn^`@#8^=d5(mJ@u6C#i8=Now`VwdU!o~%-jaUf6PXWKUc zZ$r>}DETPNNb;{8w!*g+f5KSqQzL6ne0+p;`I}=vet`U{NSfe(iQf`RMt&m!;w=8t z#UrI4OqM9+e9T-B8HBq3bml)ll7I9*iOkgU3G#khFbw$_@P&>2_*+{IS?=i zZ?|ZTrzeSfKtwA9=Suv17@6YCRTr%7_d$hczdsQ|M<*>w5XaqfL+-5QH*qAI+Zs5$WBoAA$$|Bk@$dZVEBFbh0~yoD9oi&d z?7m&p-+@z<)+%pIqvn;}Yeav##(ilJPgOs4!NyBW!#Ly*g+PpH5dMPu*?uiadMniR zE4~UfTrxbT-OT#FPC8s4c3V5k?8(G{QFg!h^OjVEDp*F?#>eT z+8(4&F@_EJZYd`K_2JJ<0{G<{&Ex6<%*(SeIz5GEtwG(H-4CDnmv_Bzt7hZ^I7VE) zla~=(1rR6D0R;kP?I9U%b;DuG9l}i%vKf6MRe!zo+8uI)KU%bYb~GOo#HCqX23_(J zbVWo&hEyFK92O_wPWliI)Iwja<$WzFzgSV<&=5=tO|~eHo*^K2gueK_tt<~vYL|m5 zr=gy^dvSo++HCoi%_jpDz8um_h8>iOf1_=-0j+AmO))9y%A z$uj_aIbD*VUnTG&vmd$|-!KGCc^6nt2osQx z%sMc4x0?4L^4xEDM@0LDoYlv0GV&Swt=*zJSQ{>^T7BZmGr!22@2}kmH8e1Y_p0z^ z%o#aCXN4Mnxy>xE8j_;n&7~%RpYME)e3{PRU*zKAf@Rs**bsJ@&NK_P=alY@3Phm= zOg%k8MDsK}t6gu~BBB*lfD}5}V z%ud=9A6DkOxxBLb5U7YLhvwzwRi)%?&?{S^N=r*uT=rxNZ>MuW!5L8HO6!~i$wFvH z1Ec+FuBRyMzP^YXSA)U4$dS;^PU_I1r7KK1KCqY=FvLN9nCo?@e?F5L7txlyV;T2c zDO>Zp{dk=D6`fIM*u9(n?=J@9>D-jR_MQmiM9xD0Of)!Cuub0{ zIynym8Sl~gc`C8BGaMW>Its}Qf{hG(X99Nm9N`H{zx8%lRxR69{jzpBNZk(M)Viq4miU)M0 z9AJ;>EBo}VF3RzoPC8`rqmiV357>vHbS&;M<@}7Z`DW5!#CIIwBiDc#(LE^EaBm z=zE!`BtQLjd^@RsGU;|7`0+!M6$-BBoEIA7?p*ou?2UMfX`)&hK!!gIxwz5}1W*{! zo;b$h-E2*vVpip5H!o1-5{-Fj9$X(BEUxp|tU`3?KYVG+>RErgD9)lNTV;h}gF>{x zh&bv*4|g~Z&x68!U1H}e{)?Yfeb4F~FvB~uN=72B{A)p>#l&2=KdrrY&Q`^6a{@WEDa#Lt&yrXG< zaZVsQ)u~m!xc+05xW^D>E0TBt9Ftf{uOK$wYI9&W8qOKM91PZk?j|$T>TKnf0Gum| zA#rJZD5zYZX6mmNI4`p2Z^M0~On#urzO@|2ObFG~qK;$I;&3m9_lvQr`APsVNMZ@z z1Nk^I+#>>-_?;^P*AQWNJm}$Z$6Mp>AV|0Ajlk<+V-#*-oYb34} z{Hb`614__>^*K#zM|sRuEZZ*2eDb2*_2B^tmYT( zc(~|S>xCOK&3mq83%NQz^rUUa0?LWEpK@K$w4XR^Ia1GzeUBfbPMlIw+;_r^;bLx5 z%ZKpF$NTBu`S+I#8#{zU6)k-l{1!MzN0jI^t%L2axj7m93*UYhomc+|9F$@_{5)Y8qb zKB4dasFUS$*h}5)!=VM4H%U|HZ0_=OTsC=yN1vK&RppV1j$&dx`{RaLOh;Nqrs)S|WxxFVncLBOBq|WwuHd!Yzp(RO-mTSs@8-c=)>WEfrGyABO$?7(V@ffg zFhu&ez=2W7dJr@*!P%^i#YM{0AnDWHjp9TiB+6We88F4RZ4CaS8e9vDcB&LZaiZ5Z zM#PR78%`CIx^&rlQ1&WP|L z>7xnQ@I%hFm-cifQCn-hazp8|sfR`~&JVtt3j22KX<=a@076h91#sww?=S+ePkK)X zN!0Ez&fVmVrbZ5vB010g7X@TKLxRH@B%>CZXSzRLw8+VM;FP7E|5H;$5*n%SERKFH zD`eoxeAQ&Z#^$Ec8D$hP#vT}&)A7Sz_EoY@coJ8Mi-Xm@F*WJEM(&*DZc{}{0~HQ0 z>smzU7lC&IM3HccO(9Zry-u*tF9@kAMuHzCoNST9-Tql(%g87gI4(EWo%kAUmq%8C z)y}v5_Jn#9Z7#FC7^q5S9J)Th$iu!Z+U?~dd(wv@_vYI5kJkiyZ~pKdDE@gT*(sFX z!bqt}e1UJqKD7 zW(wCPGtR>;+1T)vdo-;JJB00}Vd8W$GfDd z9^ui=jlPkIEc9t^?l!S2R*U1xP1cBMxKDMJ1ZeI1&SNwuu^ua9-Y18q+qk|pUpF$= z{WJ?3z@Wb|dTQ~Q`yp*fuY@8i{ocWQ}tgpZ0A zdfw`dk*1JW8s%6UV63?Mbb4R_|An?Zb4b5pLq!ZF#nS0CH<;nJvZLlplBQr+x?%2e zKP81X)pb8N>1U(y;XeJvn3RBQJeVn2us_>z-%K-RNcgT1GYaugwN;@l=_VC9?rDR- zm)Y=Of^l2?_8!~~)TM2)7#GEHQ4Cj7-$o3-Tg(|Eq8H?a-GufcL~a$a|Aa)%iw*}8 z8df4N(Nc%_Py&lIJjiWvZ;P(rdnES=WEWy7sC-#ZT(K>*($|r!xyK=r61)5YQtMNPt*}{tO@GxJix}I zeF3h_e53iBc8RCQHD-SBVecz^&38vi`bhAXa&HH}bPX`)j_P;P*51PhRJ@S?%FcHB z@sC;KbB>nK2vPnP#k(`M#x5N{t4?*fZLYhT)g)=gbY{)J9D=8E=0o%Bs0vqK<~>L6 zy>(M&oL)C#F34doNv?~oS-!cNl|XHf!BY~(;gwSC+#l`j3a=PpF*;ck5Xl&`sFO!^ zbJQaX$D8dIKV|I5&FWp?Y-c~yD*@y8z^jK93y|xomwTPMF3NGJdhVk*NU}z%Pz*`M z4uMshdmF;I3n%`^Ltz7nkK>~$3EzVW2AFT@)G)bzoaX6eJs>yJ#ck6(&;_+;NVE9=fIR1-O-}tc+(Q2aoR(SFj7LmKteL& zfa>g-6LHRLynZ?!x9@O`MW*IZ>-uKho4f>%P|cyH&YnF(+%47k@l28Yq8+`K`YtXV z<{uK8&UsY%DVvz(rrC1`Mx;a^=rMHU9`;_HtI3lO&6n}#DRLfb;&d~6QId{7{Qe!v zdH8iQxC^u$jlEnR4>KWcj;z%A476gy-gu8mj?B&EF6MiArrqi9f>o--Hh#Q?4z;(V z;<5{>+YR-rZpmwT>V>mP=k)Yg>g(z{ba`PkO_ZP0uXcU}(b^ z`Oz7_;nATa#_?b(8a2UpF~2B?)|`{#dRPd%7wndmWg63*mW0lHepzTJCn+zWaLv_r zalBLsOghxa-A*wwT~!qO@n9-1z$G^^QHCupjw(B;u2i+nG8&MlT=3m>*dr^)IARl5 z0i8KzzMe3aCqy(o&BZ z$*iSldrhex0-|A{{J@OZOk#l_WS&3ri_0(dH@zdBkXeQIRR{}#er?}_b=_UpescdZ z&X_~QB#m-eUEM=Y<8N+iO6T5d3o{D&L`!yId1eSzXuYjl(FDZCEnJ9c9>(k-A^z$3 z3SCzXJ{*Z+3Gvs+_auv+3lE!srfoB=?+D2#YS%e< zXnpCFUejefuGe>!_N(`y84+#d%>eRk`Jfh0IjJjE{XhdNYg+39C#+v@FWIY>sbzg} z`E}k2YRVhh20R94D=Q29DM?Ot&DaIy%PtM;2uo84x-c9=Idf0J-o@xRY9mOB=!28&Zv~OU6gPu_oe*^ zQ)U&apa0O%peA_2%7d9e*;yj>fXIHhZ}Sgs0DeN;Nn3@7#C{W_B7c30i=CoLi~JW@DN-oifKv4Y)a|N=^t?bRV9~{;{27;tc;g8;)gJ_sz214ZQ_KL^Z=B9 z=qs%=9gv`VjF6PbS8}wymZwU6e&C@{#x5h%NE+i*YcP&2vR)16B22mbX3u^bROqhx!H6 zX*S{dUg0hiT0_)Y=gJr^dHGJAWIq-Eq6PcZS*IN_W|7%g?&S1@#_^V0GgX~Hs;>aI zzOpjiS5lIgm}ocA#?8$w?aQFt^K-yg3|fLJ1%U+r)s9c}GSFEs4q}Fto*Fxdt9ze8 zQ!8y|W@huA{C8#%zatLICR z9HoAP;xkP_?u*=7>of;x0xX#%_M|PRP4`ZB;(6_B6{%Qdl9TB^g}K=Txa}kRviDZr z`a<`Z=Ln^JH$SC34Ld5N$RYa{dV>nN~9 zcU0K2rw{i!ejA&Nb4qm&#$iIZcm^h-Q?i_&ZFqC@eS+T)ZHz-y&OovRR?36(RhCko z$<>wMvEiF?_+wp0Fa`O$Q7u~K&{ZkRU;`XLn(QR$_U*U&A#ww1emJ?*e#u(fGbLpP zCKSsg=H}{}X;!?B*JSH1a#1Z&MEiGoL6q@;YU?<_nZe`>>w129!_y`ecSY_k!$BH@ z&`m7r61lgA7N@z6xsL*8df<`gT+cbq>Uu__Q?;=@-xQ~DYKeuZ>9A#1SXAQP=aETL zL<#QFP~$vogZow)OR0igC^2!64B_E3C4-tNQpPQE2}$JIweZWhi!vKZY3X&(&DWrs z-KVO2szO;;cdW+iX2o%_iwMSgCYNHpREL*dE2%9l*$ufHz7MPXl=!6c0QRO$BfoLZ zp@50K?9eFC-A?cmO!N&u`n}ma7_AShG}}o@lk6837cYZgHWEzy&g)7-(^~Fn?hl)< z*q7J733;NLH=-J&etvgk8;!eh6rRkGSD;czn*NnlX&_K>Oi45ucb^@^)Y2;@qbuRm z+NHE@eLiGpmR1%gmR2+3Pjhl6c4|wSnoeq36tQ^4Nc)bDpk<)*IdmNFeYlgtG;{&7 z4Ojbr{urnQ%O6kvADf*c`FQdf?$dmgJzZ|y`Bs>Fn-9}>mTEtw)ue%wE_)%qf;wNy z_9Y9P?I73u!O}d;0#CCr)%Ya!B(KfLwe|Uf_`PRE9P+b2jknO*qM-E~^bWL!9AQVs z2xJhjJ^bB;PP~v&825elti^-svVy{k(9kZ(bhe`Qsz9MSHT8)C+nL^Vkd-$^%Bhhr zkfZA3KfmpDpHaS3o=9#lcBnAt#tDCq-@mp*@jocYacTM*o2aQ?et7SMNWS22bc6lXec3w>y|zX6{U* znRflbRW#Me(5HxhiL2$BJbz1Nw-7D06XqdPSliakfnPNyR4x1lj8}V>Yk6HRl24kS zjR&GvM1IAhYw^H5zu#?Yh+v3wf?}^jHYl6JwQBpRUmZDToYU}8M28fvI=E;tdNjWM zt0EDpSNKK~*392Rz=~S{1lANZ(H|}G$3B0?W-Myyiw=2Df_J2B&o|m%1SKMWcz9z= zAf6@d>T#B{fouKxc)l^+lToymC@IO`#6x`aOtjir7YCox5_hW4!^;Z{NI~b;+Bm^2 zC%4(ao^756o|8pBVoU3 zTasOIQ}a6;G`XJD`34TLdTY*qrkTg{nY2{8CYiN&)f6UJzwC({Q4mZ?{i5Ybr*Q~8 zrC~uTE3M)y@+7GimCZjaLF0YIM3axuoP3Ffcg-(JX%3PRiq(AVgqdkA!?Gur_TE)) z*@NHv^}R#rvzc?_57CXxXU?SBYfI8oxoukNN-k>sbE%?Pwac4pA|m{RBs7_a;y>RQ z>J3r!)hY-3dP3<+3mYz~Vc800mlXc@PE)o+)zovNd$3vAQq^*z+HY9~DXI@w9`Nz` zjP=5`B;8!RE*_6+D!DeZeCs+OrjOp+bzPS(Cw0CSHgXnN3ZOTd37^VEr}oHoT~Q}3 z$#!4KLKmyeEB=&}Uk`oJaMWUM*Oe|&GgWOZL*;0DliufAz0MO{Ts1w=_uJRk7yKDC z`mCw0*2poR`u^Q|Vf02;sjt|^dFw$L^#jn9(-51yF=gQauDMUPe{QHosr6K(EAtSo zpt9X&|xyPpa zpe{sbXV=DAv3RppxEJ&7uk9Hf1Z^)&n+xjRQv1ZF{w)6t)_h^NlcKR=*@GSYPdp29 z3X)EM51RE_=aJECi%?Hb%Pwt` z4KuvizRF&mAGl0gIw~rpYqY7UsUdL^4Bcc?Q&XXbHv~3U4{&@)?7ThQohNAC?e}o7 z6`B&NXmhQ~%JO(YRy@nJ!y4KnDfJe+6>#PVYCjCPsic(n;?58aARI%_!orI}`B!-< zvHn~c>FEa1rgukaOdW0*9jADAdz9w;z`(n+zc3N|*ZQ|_-$D_BOdj+Tf}(}sGH3|A zkn(U!WG{ZNNv}&Tndt1_xJWp;-vR4!Dlh+F*+7{z_c8TpzUM1huq9*@EDDAu1a*0q zs&;AOZWja>-!5nqZq%d~S+B937g!=-aRTx?9MJUgZ^xpeAm#iyBEsxADEk71N|l946w zu0UdfQrmNiTPfOLW;}UEIqUMuN&;#yPG{fwUWKfJ6E{^&3|+|iXGF9ld)~N_f4oOD z#vgYRj_DMQijLaY_}CjjaiPJQA8_VedKg~W0d zZ$_@|Nq6~$VU~|5pUc}sS>?`-6kV~gx6mirb+J`-I4Rvh@i&?nk4GO}9am$kNPL{8 zG5vkoe?{zre*;wnubPM813dq0Qhjn(dU^j#a9Gw|QTzimvC8^w&t8`KH1#0>!Ll4r@Vu!6RIxob#k3W*lY%VV! zfO;v1ncmjxr}Wyz@Oz*B%4kc{+oA3SH^)ML$r?Lk9@xa^APw0YhsG))5r2-*^} zR9Qk>DjwZp)2=S)ne*y-LzqxZv4KSO5gwh)1Oup^GB-DeN)YH&)#1J{`gQ!vHWzpt zvToE;UD;-Gva$dT)8(Y3q@cRX@JRXYBdrdal?hN>hD~`CZ!AnjcfK(TtqSeT!5+L` z@-6cGPW0Xo2dHv28Ry$=oU@*FMF@EwMK52SAINXop9<0Q02F;dV{y@y(HO8tMRV00P(?z_c5IP()H;M3PnS=_6)Rbb z=2cU-V;cE?N{-{eG^O60>f^BzhA$F z4_5^K_wXTcoBY3(BSU()aa8PUdsF-8o_9;5z1WoG=cYJwG=>N$>Z?iRJv->Pde(Zda;9Xn*FI2LZlORp z{YM}N;xD0cQX|`R`3tQ@$c)k=L$2GZy^@lWxcGh3W}r1QGx^L~fhMXIj_pPP@%Inx zJ?@QetOoc3QQ+#cshgyz=|$U=<2K$4t_gAOdT_8e@f%?80)h}8VLfK5!sr=~L*T|i zc4vDN(xfHRd8e*VI+vPEfjUZ=H_01c5-2i^ZTc6(gC`lQ7J zI)nn);nK`*M@wcc+z-2?pis|Kvc^1A;4t%kO}HiD<45s%UUmo4f6;{)=V6+I^4)JP zj<&tmRzUZr=bhYy^(rZub1_>6ia`3^^CowBq1R0b(pb11I!Z&?kZheS!O{gxSnDWF zfVtm}VrOkhSd%MPF!%{CiX#l}Kl>93$X+p|YB+)u)Ya7`zh3<^;|qVw3?ya4$cXze zjIdMB;+U38uyI&+p3|C*`KsvOSUL+?jU|g2o*`f-)Vtc7&c|w27D~>ykd5iScX&R@ zUQ+;!K)N0}=4OmH7ai-)Wg+V-88asz1+Jh-UGrR@D)tKKY&w}RN;7HZ3YCUn=c{s{ zZGGmc?FKclOtw(#s0F#zd(Go^D5yf%7{{j2U7x}Bk^-f!UF4iqMS5Z!cU^RO@TD}v z%NKhnDP1R$a!dsqRg(kz+*i#yKHo`OnCzH`>fVZ9rZSr0qU2fgp4{17%SAL1*s_5! z&vz+6cQv%1Juy%~$p(Z*wM1=)pmYbh-tEbc%12bM>N!s@l-iKdX1C>J&7^14#HKFP zDnWeEY5k$GclVpbbCD^fy&WAkDAtTa=l$~^QK--%HAj$?`=d?aN z(HR6-HQ~}1hhx1Ddb5?Q@Kj!6vFn{qibiIbO4$N3X}(AO9m=b2sN&*c*uggi*+y>~ zXEnl&%Br_<@}YPjQdMT$P=Rm@VRGA4wim#wkdUMIJ_3(en*$9Bsf&RWF6myG+2dG?sb>j z@)N&I7Q)jm_}-fY3YbjG(;f2|*0Rgo;~zj1iex(EMBX+fszaT2egALV(4Zv=5#1k{ z8I5ji+W3p-qf`eCHw(1xRQ;~30?r$}QhQ$uONnh8oGi-dS}>2PX2qI(=5G%@_7%dK z^&0JT=PGY{ThsxI;^p@zdxzftP%5$Sdvt>ODz{pyxR_upUjf7cRJP_aLTGI0dCF7K zqqvklfN212W?NNtfyV`7(5Ya0yr(tYP)!&_-5H{7$FFu$E~rVt5sq3 zT9y$V5Y1XndhZV2oecT9@fELP_d@2_kAl{c42|>bW&v-GTQ4sUf$hk^*DCe#_2b%j z1jJO#^wSfDwacL@fy=3QEwhJHKtM(QG}DJ%_hK^X^d;n27S%b7b>+;^S`;3KtbC5N zlvF^MY1JUhDKXnozx>ZpK&u1v!RDjSj6DE49{R#g;8Kv{IpL;GC?KA?LN(Qk>R2b@ zQF&R}rt1|^W=w`-l&kYfK-6_{*6$1ZBY=??{L^{ z4%@~m?etg_L*nRxua|=!*;1&_jUB*V&|EZrHyqfKu@SnMo~B|H+G}R8G|Uxxy)rDS zueO(+d<0|L$RASsEIv|G0xBDli=F20LMv_4eaDV{*ksrP*Y5{MV1orLbj7kDAl9`D z%*X`py36@8SLP}(H?i;tn_^*)S@h&*vD-*qXeR}QI)J2sSIBnk0Bj!5>wi?xJS;Rc zl%4UsrMSYUiVCCV_wt>`7idq1Ho^{sC7dWJdJB_R%1AuNNB{4Db2WKe^_0ll=$&xR zi6uC2rih83e~`snrKvRE{(Ca&SLOzx3;$vV!*G8nHUtcP>YBE$ApeG;TeVI=Y5gKR|DeS!Fv|09FxoFsfO#I7QqvK1 zQVcFQmu{73@&zDQc&RE0++)ujKU2RVhqIu({wqm^BLRF#E2bMvg2YT z+$`A2Q{6}3ug=9xp7^kQLm!_jsxwH#TCmY0Z6g>VNudK_|TAlsBn1~()>c5I+7og$=7i*+GZXtmZY ze;N|929i8(WYc)M*$FpPvM)lDBIM;ifb$vM@={fM(fL}2oYbC;>j-s;%8%KfUw8y+5KyM&7|2)1%#Vvv^vx{yc zb=vEBNuMeQQDC}Q0a4Ub>vi&&?f5IY-$%cU$1=VOb zT-4Rl(k_pY#>E^FnR(@7x7LcwNo81HM$W;ny$2sF$sbnm?nzBkcNwUMjDPi+9$-|g z&|Tk?wGUp0?8sl~*|2Kf?^Bh5ZfN2B#V&s6}-t~-MqGp*Ezv(8*5k)uJ2ERj7ZT#R&A3R!={{$-igzT zGPH2}{na@E+V|`DHzr*N%2yV9?yPF4WhrvB3&*$WB47!4abk9Y$`w!VRxfZ@<ZdST_GC8_$)ZhvK^ zIL@;mI)=>41=X1UdCkr?);nnj z@|zq>&#IG3Q%#G5`)dr8eW%D)R7+`v+L>@gMEI9~EblA|3HMc+J|rH+vv%(G03P zp040>@YFV(qerm193TB}>JXYt>I1F0UHrZ&0dJQi$AivJDl@-m_Z zREMyLZ3CdBhHmJ`I+| zV^Q);=DoPnMLx^yiQf9k1(Ys@8wLH09(Wa4Qbc)zkQ34fp>49)n|_j^)ay7%x`; zRNcLoL_nQMY-R9_kV>exdjZ^5A71P_e3E~WYVXS+bRg|C+VNnyjo_rte6r0T^3)^H zXC{?i1+$qtW|~^0KUkT2eBV!{iq?A4jmk?8R{4%vNwGpX?o!{S3YXji$q$@n5W`HA zEg_;0nhQgsYgDaG?@)zE{Y+!yG+ZGkma07X6he21P!ICUM3N>)ziBjwfcf~XcXG6j za_|SRvxs&d`TrlMLNoi{0nGnUO(Qv_TZct|5uMT`+qP9zgN%Y$rxTAM`}vc6HewId zs1s9)2w#Hurdy%tZ?vcfM2LdGkq5me5PogBgzmw?VS+9H()7s?^ggmJCGKWp3tX1m zN$n%@Z(|SL%KPfEeTJld$9ykRT1?>4e|sTki8Dm&2s&6?@>-c*a3reAK4sCqWo>|+ zGE0F>RnZ8!ZNF93BcLr=^?4$j0<=pM_Iy54j5h@-VdPZ=kVGi_*`8eL8ohCsoQO$~ zza!*|0pEaZ#f>F?<37IVq(Cnsbxxf9!`tSSPxCN08tPtN0?7_gVaTGTgYyuilYd+; zlgm&K&XW@_snNs#e6E1#+{Bah6gr)csN&ouV_HyqAHKQR6!QT`f#uEOD#u9I-0dD9 zQNUa5yReN=W552#-G>*v(MSC!%7dX1@8JJc9hIz8msP?1ud=7VP)Z{p{;hQ-EXQAN z``7Xi9@6BuQ(Zn)P7=@qideH9SrR(0_WZa%`(>|S@lwh%-t?5g=sn+(-R zV;xQEe^~4jW!sL_@Dac$@#w{eW0wPt-274(!LxJugGha2x@6oGW5la6kj9Xy&&&Rq z8enrh40R7Dfhcr<*3WaUAp2pb3^!P%gk{9>wvV5!N%cJ%wjq8VrK0J$~|fF zFP~wvR|q<{=DSbCNYOa`MFfA9oar{X$~AY z%Eu+7T+NeMA22ljWk+BIHzhA4dQuf#BeLJ6=Z>SIB#w#e z<=xmL^!_oSf@*b-(+o6=O`}pyxes;1flXVY^vZM{Y#FK5%$i*^`#-&1WmuG3v_?@B za6myp2~jBl0cmOJ7)p^Ex?|{WL=Z)~rIqeRx1!SPZ`T)>&$`djaE^C8yjo(BSo~HLMg0n$;HbB%P3+z;hCJ0or zU7zW1$C?Db1~WA9Z-Sgi9+kiOaXKrsB4V7Kj>T!SxAaDzsC&%R17UfA8)$kJ zwu|A{oB|=Sun4evqfmR0g47srz%=8vQgTJiIT_Jc10$>^sTeB7Lqr|nDB?f zc>m{d;ozb}=`gRDZp0_HR?EpI0zYj&el1~t(@2+}Iph+xPT3?Q^&0mOa#7T!|6$Nr zhJ;RHc$f5@L)F@fggbAJ0J6|-dX+&3@sx0vceqXl8b!1#95UkFTi7>>B&AEGH1rR? z_+~#TIpVKoC-c;%8mmB%TkinQ!7rrzx%8&@Z+`SR+N$-Cr8h>>y!b}n%TFY>Ha6Ez zU$2qEF;N~qjjGEecRoIfUIm3sLR&s@AQ&c**y>cBYr#O-H6tSjW#rsN?b9TSzvN=k ze>*7HS~K{YMJM$q;C%vii$WJ+01@w`k!!73i^mRS&mJMs%VpQ}GEEzA;zzRRn0L7t zu4|rlBdJ|$Vt5-gs8*NPYnS4KJ}|oGCdLS~fq513b#X!SDSt_;uecyXUDg6k5e_L> z)EM-ptx;WLdm@gWQs9hmAL-MZ%xGm9TIGB27BD^K&cknV*{*ChP4Bl$Uq}w{wNofE zD+cNPdz6r}Pu*OrvX#fjS{?ySE(JV1ZxMS@5)75UX?5VOzs@HoDqbrn%ms|riM4$KKtHZ|IK(HAWW1})YxT2D{opOxu8@kt z?^2Fd1_@H&eD!4>8>bIl9sE{jhUB7G?bq(tFpVDN((}DefpM-Fb6w_AU#+p9ZPpi)_PiF#0z>=OP$t%-Exxc?zukFzaVXMGuEFy zC#1hYp$!C!m$hEdp)FrW>-m!_)FZ5h9`)*Z&8G3_&@CM7L9EwYm(eDA|6H$sjr&>v z56$ihnh|Qd@;CbPRQ&R%K;+8K5<>Djr8(;k@~%IzX?yvRjE=h#I6nl>K3t=fS(Qh; z$dkMJ>yvhdU&f<*T~_!!RjQ{QT~u~AiB=wPG@D#~PI?I~25e7VgoiO@aH9uh0r$Aw zVY3A;jaHD|`k^LjK0}A^6|%fls^<)z7moYS{i*QbvmcoZ4eI(?u68}870r_kY^)*s zxs&Erzlpdm*5a&u@&rxNacr7+TQ*xKcV}{#P23Dd_tviA64!1AunbTM14)m&58RSyUmpwwN&(|E7KB@!VoBE!a~BoHsKbJtZYwe3yj?z#t>z zG&JNGtKA$fb(xk|$WMdm5rs&Vt~Z9}oZNRi%9N6z zn2&-!GHADtHdZaiaH?44Db+jw_>QdDF?Xpd2yv0*#9_-JX7g?xXRof zLPjEKr=wX^VCtiB3Q(CLIzr_!u7{YXr%ugXX9W3dZiI&`Z%XfN-Fh+st80sfKL#JI z>iA49Ho2?iP4f7Z+D|rRpK(sf_|TZgxk}buYg1!=ELThYpZ(_tyM&yoF)#4{z)W60 zdX8#-4Y@Rx92tuT&g8uG*fftZPATo2{}{bLS`22)IH$Q_5Z`GIDEsV^)`TB1Y$2xi zgQ7aE0~2g3hF-HErasbKl`F$LII>eK+b`hW`xc+P@Wd>}7}*Dk>0Vl=7Ix7ic(oYX zDc8YFe?33(N5?EvRc)dJG3?jOtDisb3r$u8U7@etd3E<{xjWSxCCCegpE-%~DmnL@z(kQigog%u{-4G+KKa>6PC;Yd?n1lB?e6 zeNPtpsKPSe)OZut@6hW}qumn_o26lQ7*Xpim+FcIM?5&zj8uPs8HG)ESCc{Bt@PuY z>NeX#^r$?63FxGy_P-{~oR}78lhlPoc8~ACKg^~x6(i~eOdbl6 zi==yaxSa>{o;yAEk{$ihxDHX8^yY474fXhWk_#y0GdJwquH}r_$muh5b%gm^y_1PI zWRNYihcM@^;f?JAWXwUzik71vZj!w5I zM^bCfp^^%8M4?+e%ryFG7il*}8cKhh`V?k+!Lhi?x6n`=hbViGf^UY7zVtb+?c2M< zLA9?Z^{H6N4{(uM_a|FpAN$^A(WomE=?sRCjajPQlP;irMEyk|2rGd%&~~=e3SXwF3AT&?8R-gk7Ad= z4SyE*zn;T2T%sElN}E3Oq<=8SeGO>S{^4dYx=ZFuqd1>TYHTjwbLK;ve}OiDtbsu; z_JD1S>0jnX(?GxQY8rogJRe&JFKf#bv z)?@$euZRYG`cf~+{bwN2(fQCYB#<(F`WL6#15D(ABVYO(r@j#J4^CaI_J4l`CCeX& z{%4Z`QQr&ThySc(F9z=dg!Y6RIc0C4IYM5c{J*0Bm}=#&d7mR5n(}>7nJZr2T&E#R zb3DQC%#0%!adzJ5hm82HHpJ_+Z&uhY^?|&8P-Os0D?kxme0ccIOh>VT+kC>VOT-6I zH)Y$GA_2C0-QC>*Bhujspb2R1*w4-x?x23mhpx> z?s(5;OyK=-=ezkHtu&}WWZnG>a z=Ei*Ivx3|>Km#fb2K_bFWkgT4x)&4mE^g+-WFgRjp2--JFn!TTQAruN>PnthW!E@A z!jaI&{g4rHRs_vw2$eZgw?3~rZr?i<-Rb9ea%C4H$rJk+^Kj&}X+4e1aIDBmmmna- z`-G%QFUMWG?~p>`iIa_QHFRM5acLO*NKF^^bjHx?iErVswX2|@U}#uad}kua4h9l~ zEHfOEXP=mnJu_k5P9KVY`*sN&!4Ls*Vl@Ma*xI_g^9u^(x+^JdK$=`p>WRd-&Qq=W z*Zk*)-J#Ds&Z_0#eJo+`35n zH{@PU`NmPNyDytZ(Dd($CAz%AlU;L?#JpM+FYauzK2VyN8?o~%voYN?K1ulb@Qk*J z*R2q8s@y~hUy#IH>(Ee>Mn?TftbQx3o${6@zK30R_AWMAx4Rl1zLM+??jBj8Zk^76ix(#oxMnptV%fvmE`3x$V&Q3Qx+F4aK&u-%3ZEdwP zLUll@A>i+#wb@%8Sz1~uN?jF}tso~S-`?7y*!+sk*b&Dmj|b99=$-LCyWzDD3{( z{oA)G!VYEPxg>rd5K8WC-!wWG^xxU<%ejIa`p=&~ckLNeJAK~Me#9O-6PcFEcBBHUq^S~DGb0t;xIkHuYHNukXa&PJ#Grt1cH7J5*Pmv&fWAT=j z?ak0gl2SoEpz~rW!Z#`{f>W*q5;=N|ozkuqX{AvzRboW%jjCUrT39e->c7u!It=m| zz>iU*I)lZ_%WKdYGm1gIFBxJJKD>$s%s55H0mjh`BHC5-BFA z(D-In$h(aHfa+_Ep2JE#dnLtncB!*T9UMoK%QD$+>*>}=zWF8BC&YZ1SnX6abFG4Z z{Y;bM{40a}7bQAj$s}Odw&eR}@SBdWAzP_@TZ*u$grX9ds7K`Fhp@UP|L`#;l808mRWu#-}a6MsTLi;PBQ=_+vd+ja);+>d1-Fx zES!!>?<5!;9rvu#XLgcuAE?upRSyS`QxYs~a1>rqv~z|x>A1ye-cST<(Y!1?0ApM{0{kbiJbDJ^O#OK|IX5sd zH~04^L5W4zt8TBwkzzv?mFUe@9DqBAN*sVvwNmD1cFW5>czpH7T)uqG&QI9UvAh~d#20zH3G{|NP|_y-2TvqdYT-}U z@e%bug3%^!{38-#NQ4{Ret91JV=iDM&u5p3VSjua-kvDrd1LXCe^|%*dmCsIo z-$qasKMy;ym>ZnGGKCHz^FQnL2K0lH{Uy*%HML-$My6MIUCzi5Fx7#%=S2Zd6F^Nr zw9am1UcOfj;I|G+7e=4@HW@OtEoG_=%G`%cuBBPIbbE5V-y`cDsm( zK!sLW`hG0Ga(sO0b9bcYL}pE7C|(4zQ@4m%Yr1Iz>1&r|4&eq)PZ0kW0%i^(Iisl` z!Qcfoy7Ri<>)0?}+lGOaPFC#pjI8(E_ zv2rm4sFov*1c!Td@bo!E9>IM37{a*h*c?HK0uO07i#E$q{ttl zXEh(*A`x9vHMuyB2z#&D0PSLckumsk9lPFqZ7O%V6vMiu zi+0P$pE3@p>N=<`MD69J=cOoYNgAw0XP^{|?IZ(NaYhxp=WinUmeGI5k;rN2sKwdT z;Rf}q3<$3?h-|hF|4-KuRWJ17BGJSD3&heg6G!_U#jAoSUZ@7)bLFB^4%wi8G7 zb{gEU3bk~<9C7=#Vz!I|IQ=kzt3i<=`j!ITof<~2J@M78m^m7<6hQvlWDD^`e%fn}f2jE_Q0 znCbXHcakf7*7D<*@x~PJ1jh5*dCvl^w)g?41~8Y=Lwm=m!X?TL*IDLCh8NEBGa+>! z2fnx+n{(_)(2wLZv*0RCI(Oe~1(0FLNx47iG9}k)wa~XPo7n)pB+YSdrYVSpDAb;VSb#XP80NG52mjn2PxhDVFe&%eDi%wd*Yt^?DPQN4$1E`LK zqB}sdUK!+jdJ*Jw1|zUoqS6l>Fm0>B(k%TuWedB>OiP3@xd)Skh2;vbZn*RN?h!Ni zjF1w<|HH>7G~}H0RK!N0l^m91BxRQS)aQ+ZMnfO1%;AIHVv#x6{eB(bGWmIK1zly( zoyGqApJVAmq*CzM&aZ502#4oO(LuWkc=70YL&S@4lJfJ*ckeI-Ttdy@T!OqyM%z{p zVSH-{owXkTX7fBC0BOy?NrUg0Ow)tFQJx*8j8iucR)nz`cO_Nb_b<=eKHZFmLhUEH1L*AX8I_+>KK(^YmdIY?;vg#(mwQfOXe`7Pgi$*dNG?{S|Lz?Q zui-uct?8Gc+S)ZLX`=1#gkm|9@y1fc#HCd{As`BOsSp!P(>FS@OV2VX4quB@#c*DJ zb{DtQknUN;?dr5aBKFlLz#M21rShUsW-B09%=M(>K5t$G5iS4B&$QFijeJgfvnI>K zbkQY_aR-R>LIG>r-LyQnkvX`QIzcy+RRu0Numgg5NQk@tC*CUTgjy#m`_7{|@B2Ka z2VERpui}nvf94z__dW1k3=ae0LU}Fpk-|Fe6&l3M1#pMmRbOd^{Jp9rf*LD;w%Y_I zyfSDgXP@2C&mQ;@ESo33=6nh~jZs^25{-P*{6hZGhJyl|X+4U?rN742-aXJkO>a6rS%sKb@=_ zfgS40C{5EhBcKgREVa$R%MK9p1IIpeAHTYuPmhf}#cb)ETm z-bfAi;BZ3=Nk@0Lk_NN&Y;K~fwVbu{?s8faKHxWhV|q9rdo}cIwYV{KL%D_jbV)Ag zJ|au0m7T!t;~5MT!n@@O;8j%6t;vWtTNL8q8y+7!gm2T+F29(O$;x)PdR3j**64k) zx=P$=TDm-bKDWJC(>l2_nF8Co$4}pDb8e||%|Qz)8$E@=1TKn7s^SJlMw3b!t)D~R z>{s}(!3o-UR2F6z@+HMTre^z}*o{|;*yo3?DdoK1nMyfo+ljTah6K1POA(X#eR%<6 z8N3ll>e8%gvG!%ySJ%t<$HwAGJU2c%dVZ*Z%|vx;Tb*N;Cz}q8HflVsoqWm^w+rXZ zreLf>_B?Pa2ERzK44=i;vlLzv73r!x4-O_yaI>%7xtZp+S8nBy87N)J#9K_noZ=xM zP}C9V-5EQRLZ)y?%+3=Q%B;j(ckr_%-eDq#`|ds776X&xj*is6I}-D{LA-K6TkyVr zH)ie$ag9*)C>7#LPlXzaKjL#w`yR1e1MoSBC$GJCc&5a9Y-45xX_UI z37?}Sd}CrfGX;#zbaIf!T|U?MU6{Car>Fb-%d|G_^ymR)7>`rdp~q7K2uuB=POb_c zQT``s0YM_8cL@lNMhxE#4wCh!@#+=g8$#05>yo$U8YM>`dxUy)zPT#TGh9!^L?6*^ z-Zgj2^2@aqjhTZ@o*6=b(14`oo=2i07B!`+T+pRdJ(KW=J03emd4<{i7~@IUWh1cc zZ#|zMI!{nm4UXd89}`=EJ^~g?Vs$L~nadu9L`U2`CjB;Lfxf=5){~x1dgr|xXTKUx zm;KacqRQ+MZzK~r47(c3F&OP3Y1Ap39`xwX?Bu<^K8q=FF^lgq>xaa+k6ZP#eI!s2 z{m?{iTqx8p2wPRbNHgJSaSs-5C!k1+$V1UjQ#o9PsiCb$(LS*Dk zumK9o_0JDtYT!NCOqJ2N^%>$1$LoC{-Tq|!f>bi_p(iEluLt+&6ouO;#GD1;T3MEBH zQs|qqN}m8?-2wfpeS8_%IXQGuuUv6uvhtPdRFqH+F5I*#HXHcPGVa@s(Qn?}#93h+ ztFnPH^i!0dqlz<1tUf-lL%AS~+HcNs=?u`_vFakGm}JfXik|g^z1Vae<4~nGQ_y-* zLtaCF^b6?%-LX&^mHGgGOc4a+<*SYhK zSHJtJ@=zV;t!yF#8*jm@m%lC}9{7_ut@Nd`q}X#$cqRfXlL|1j1eOKu(#wfbtvt+` zwV|g5>gqz|_X!M<>g%{Zwq2=jvs2L&UvBzz?dXY9t|Y*|^CjynIf;}FQ>{ljD|jw< z?PvvV;nCAZsT zE$TGHii%iS^{059AIzXH-;yWhXzGaN7^$QVd*}zVSEdqa^cm0C#>NU&%8f}LDkElF zUTC`Hy{br09v)Jt>`WfP813n)BoPRM|3H+*&jdeus2w@;bpWWyD?0UQuwq>{*SHs& z)%%esH!DoK?}%21;~yQhxsF$w5Pe(OZNXT5;11m*U_Y`HaS++A_i#(w1-V9xS93wo z^4-}0bQ}&MIJ#oM0A8cr$YOu^Qa&Xn`;JB`Uz;tAD)?nZ?eRK&kW^N_UY!aqqDT4N zca<64UiQHIZljuKTOV#qRo zoB&YY?E7)v@CUFYc*YugZC%{-R44wqeNkT?(aGtU-@C^9nS(dKjJ6lkF6lSK&Ra8| z1y4Ya+@ot+b|i>x4S7d;lL1u~&k|G;szl5Y*6Y9eZL%);1{F!-Fh$Gav_swTXZM=D zA2_Gq_)705z?j=wf*+m+1kDV=X(WQ)f+#ZMTXA<+)WgiPFVuYQ2I(^HTiO0ZgUgcu zy)s<*Qrw?OgCMQa$)L*AyVvjTv$5M{7)f`Qi6ttM zN2*z)=J`=p+(KGz58&2xmg*SLsHv1N3mtl%mCmXbs6;(z@zRa*xYN1Zr-|!H7ZJ+) z>KccoK4qR8!e)wD|5nmaVhLeZ!o^v~yKQS{yE9|`{6C%}>e_Tmh-q{B2jF`Nrio~? zCK)krL~(FcSZ>zU*;MLfpD28x*&~b4w5u+d7i_H}TrzKHAAsP~PNcE!?AY7R?V0Tg#RxNpP6%t!Xi)su zXJ|H@X-xIV72&5r6HkRLta6MEbmWF3>?8cy;WfK`@0ncuK;-zPntlE7W4zG7MZ$S* zI)~h1UZL)t`M(zipP0&;4#crRJU_8P)K*M8M~^dhciJFAE{fgsW2s;aER`4w&d{@Q z8Mzwx{n4*%J%N{yVA06}#)n}kC%>|Vrb-n`8UA9aFOzso95i&YOFnDOGFHauPib|C z(Bl%HQ(gsp?dWTFk%&p7hg#zpWQ)KMFLW#iuPD{D&TK44p>N7yA^py&D3Kb2*?&YN z1tq)y;pwr1I0ZeCYHyJoiwUbveg>BD8oMsGHT4uSHQ{wFClN+V$1W znU7uHr!vdg2Tud0Pp;6?pyetyW~XGrKQX;Kiq|T~_zG2TJoaFSRd5EFu4}g_wH?-v}QW%e@KIV3K;lX*aejf~`HC`ZM z*HnwG@bG=aWH;YN$&wJ@#6`{?hyfYny!Z%4Y6i5fF6<{zZc>Z1gX0Rq!8;JjF9m(& zhEOQffsoJ!_bRK{P^1FDG$_nFo!H5tO%VD>!#d4T1)R4eHS!R+g%vGyxm;N04yX2PNk|y=wC&|Q&tJ5NUyXOH&XNZrc&?fIJDx_#l2#lS za@==1>d6gj8^t}J{@}AA;w9`pZ9e4VJueQX=fE2*P7D2S!J#4JIw3)f*989iVS2Sj zfXO89i-_GBT=qGLb3}_8_~4WDnA2=_R)Q-ESh4C(w(CyUvdZ{Ll*Z0*j$_}uZe*w@ zPamsN4~RfU?dLZ;phkguwYIL~3Fyp<8K?KDSjxUXBGbfqLxRQmU_kRXb~IQj2`l%R zV{6w9`Zk2bLqrb!z)5iFAL5=`|7d9q+Y-P6O%z1PDccAT^{{cbA-W|xNbf$hP=DsA z({S=FX^hA0-WIll#b|Uoqlq}(Q9!k@hPVTE=Nq6Ct@mOzoE*(mB5n}Do@{N-J!ttH zj4pUO_j_)sXQ#Y{i*rR$urz&dCTJ%45Q0hZ8an$b1YcB!nA52biNM~}SR|61sCf*@ zs3b98@v@oCEx!M;l#hk`w8CszgH56(-}s(NcCBWnfTlPVV@lOq15bb4t*ptgF!q&} zSY{og!j|SJuLti0ajx%1{RMvIh7#ln%zpqFRZ9xfwC$UY`R!t% zDNNG&JSS?x;nj9c=(4$OEgH4v{U0_$gik7^o4AMcbZe1Eyr8`%cneSmqi3kcgn<3q z&YEoJ+A`>x{Xwl}AoDTtV?0@zJ#;lDK+sG_U34q(XGzlJ<7gBUO zoH_e-A#AJtszge}E34zjNK6x%tcObc=YLtaA_pQShF;w2aNG58eNhsQNVmQ@hmb?Hg?PEn4!=<1h&78yOIA|b^^g?UqCbX1X!2<>zYvG6z6T!lm9*S7KI1@5gFAk{UFcgV3~{p-&;|eF38SZm{2}`atDJ|wt1wXc zIDnK@n~=?4HAP)$L|xWwWFPBKGm(b8=TiicB-f;TZDtayR!NG36wLoG6#l|~zuSu7 zHW3l3;-;0<%rq1A3ktU_hgvfmP^1uS;19hbdF-_Rh!j1U$urI$pE5*|^1aU5eqgwe1|bxPxVSZ{$= zG695p@>%iPU|AVY^y5R3wGHLOxYD$rBCWz4sHgHgRhW>j&MVc(aaT%0%Q4sJV?vg7 ziBSuWT0E?pal7HLwDNO*%Z~qeXr?Xt_-_F(ro6uc9|P%Dx%2F|gyO^uKcW&NFui)i zlYT@HS$k+yNegsucCT?~*`i0WUwjNXU8jgho*he!v~X@Qq&^H<<=phFbrJOQfobr3 zIee=d>tUmZpbu(|tTK0J-F}PELtjrpJ^Opw4a*Z$R4TGw2^!x%Qkh|fz*xm~Mzbvz zNZedTd&4-dO+2og=NsYMNEqoR<>~tRPo48gl&dv@uHiG7l_{-ql`N#M2oQ;QSz|5B zR*ZB#JGeGjEY;26kF%&5=;e8_SVMzT?G-7>^Jhw+tcLr1{Kd-H$F z<;qAz8+iK5yPa}m{k;zvZ1CLli;a~}^_O9S$go=Cf4sBzr+VY(u+&S*a5A3FMag{; zuXyv}-Il%|=~_qQ@$uU{m8fkRRD6(hwaN#dEPUeG+&gNu+U>&GVPA}fAEZX(a+c?p zM`WjWae0-jll4+*t&3VG89amufe|5e;P zs7texdQOSFcUn@^F_Vq;?jl#)f|B=ZwwhO(d*JgGAB=TWD?m0GFdNkkY`TTkXNBj=mGBsj6*SnaG)ni zw{X|7E>spZOucyO7dz*_cYjcCM`fk`lfW6oziBfUR_l{Q!*f!GeN54tQ3y~)jyfX{ q`EM^X`o$4=7yso={r~po%sIN?3PST8S%wf&dI=F3;XFaDH~#~0hnoNZ diff --git a/doc/plantuml/images/shadow_demo.png b/doc/plantuml/images/shadow_demo.png deleted file mode 100644 index 779b9a3de3bc22a19fce4d00be097f2f1e068342..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42958 zcmcG$by!qi_dbkD3xWb7p)^CczyQ+H3y5u0hdJl$z4qE`-S=AeKB1}*S$te_Tr@N^e0e!(H8iwaL}+L? zzF^-3zDWsji~{~)c6$24$=J@`-P+X52~F12))Z>!WNJcf0@D(g0Ex?gxTAvb8!jRO~ z{YrgvQ!%@T9_xkjMo*a~vLqEAE2&k_&iV1@&xWm^4Gntt!xXYBDrLezU5+f4m<|f> z78UGC$_=nrWc6M7q>e*ZF-hK8hN~8#PatoahL840eiol?SMm#?xKAhe;j`^v>X%!j8-kNzX43b<*lV`Lb4)J7_-tZz zO}gDJ=XTA-8S}Lp>%=g53)=MX1dAZkbrw|(;jD<3+!(oGrBmRl4{Y++5~6zSK2O7M1bek-EojO>9k;e_4b~5csxRrM z2YNR5IvT_+1&r8M>B36}EghS<@|{M$EKS>jS8}E_;}fLKhToS^&)mo5CGC2@PI@o! zjE%8)%vNX~K4Lp!UPc`Bf@Ser^qNNElu=XyI!C{`)_@}q|2?aE9(`)iEyEwEag5@T z*mKMxn#x$gp7c&MG(R+XX$cLtmm6@b7sRp@-SKl05*XRoZ^JN1NGK+sW=<;dksH5% zqKPj2LRCXUoXB&_h~XI#5m6>LCXwex*9$qmapK1(8KKbm27%h*h8oA9M$;{~tD&oJ zNO&R~4lmy`cKsRgB~6uy7&k~V9gm0(Pdxqif8?0X8{%NsxDODFX)$xy_gRE;~2nAh}}bd1`LMOt1~aYDJ9Ks z!>`^|hDu}>weQ~Hrq6A2;!cgmYkv>hOBd{AVYi!qt|kY;ozBd{!V9m7Z(@%qS17c% zc)<-m@sLWuiOz{&-5lJa7n~RexkVRMFc|Ff{XPrF2XKPs8 zwsbJU_*A_s=RVUmW+L}4^UtUxp#@__nx~EX4)YQ3EQoG2h_;H3a6pJank`nWA11#I zv|-sk#O4w>ZLuKbBxQOzlO^a-E?J2i5}X~QJ}r01FPKTOcSO#3bjxf^JjYeZN2B#i z>nD^nGkD6RPq>5457zUu`nFg+2gOYq{5cC*$?=B`qt!!b&1tVd=J9^erTK>fre{G0 za$8Y$7 zR3JkjDAhpVsM+^tWDI>}5A$V0$mva%4-&&e_&DzBw0-IPQWId;P4|<-!R;f8&&Kwd65)Ozy?1rvo(+%Uq03c?gm)4J1%<^g2Z9BX;e}`1(TJt| zD~Pw8W`&}1wDpF7G1=yu!dOM-b8|?AZNHUhxqE)8T10V#M3hAP(Vbj#MOPD$%S+t% zc*L^5mpTb{sXHWEdgEaN)BCTV>2r5B6F=U4pL_dE)!hB+Ja0{6ssYsg=w&(|Z^=Yq zrcdvyBrQV`SfZ6gKh_osM2Fg+<}s6IW$cf4a4_jT<2Ih+`Jh?;?cJQ|h;&f-bTg;9 zwe^d6gSMaJ_{N#kR1(M^3?WJ~jjbs?V^R62p{ygL6uX`ew4`yv!Io;F4z6b8T1Kw( zR*_U^?QAX+a}g)bUtTX9cjKDr3FjDDb`6BGuS_6LRZP-9y1v!M1N+~2P2JJ@#bNqH z&mM_uJ@G@t{y6x1e6o`lQMK;Tnxfi0C~m%mF^hUv}n(4EhNE~_XbdREkq;`lGDPZBlR{lt2SBzMj?OD?s!x~%bb@=BR=Z$Y9 z$aepBO<&K$_Mr%I(MZ4OqSz={Kh5J9q6ejzk{j@ik*(}Eidr1_4eX1${Y~J8L0#up z!a2Tp^71{*X*wYZ{#(srC#6&c;&jp$%HVqXX@Lx}+VJu(VqXZ9`OC$(64uH>?vI z-Wf3TO!w&ZnS|b`bR?$O-SW7fB_1POQTc^6Y1XU~wQv1UoxyJw^0VW-E`V%2dbXIf z>+@vKc|uC+$@k|3)3Q7&nq`Ey>iM)@SSujX@$ULtj|LuYgq~W2YzGK)3S}Dw;-(^PZWB>X z8Ga8}%YCAc#c%M1Jww>di?_VwK`t(bsL;DKAUT5Rc5_ZY8w!f#PZa`&0-w!@r=JB! z1aIOfPAXAb?X9N1az(Zh(O;1zi$>Roq{ktTNZZX!ZJW0hccM4Kg)KXC;381kkT+-t z$DnNU!Vx*ha%3+eU)>7y^+(J7Th(~4ghSuV&KRr=`pSc<53Bz^wefz1id6d5Py;W4 zi_}{Akg9ID4aW2>{)$BLQJAjdE7!HUeEO@qsS%MiA~(j6;M|qAloq~}rj*hr7H4TE z*Uzlbz7TxltL9OyX3LX>1GYk$({+{wS&-bpR|Mt8-P)y^v-c1V&L$gBbfO-L@i9oD zO6Gl@(B303x$R0`K7H1`0-~davOM-B?XeV?K9~%V_boEZ^y_-tl-U3CBYUgu7DeB( z=GPeJZ>_XF=HDm9uNK>Q?I*Eq$=$0PuGn8(w zeUs;*L5-Sj%S|#S*4BDP<^fwuo-uQo9kBYX+`-_@0_V&aE!*I5wM&0g*?n0E${s|j z)hfS=T;q$50cSy>i(=nbg>qS&3+>5|Hfv8f5uu+kJk1%-dRvhri6)Xg%=|R>tK}I7 zADui~b*;J*=2hBS+8|;k>l+d*QQv=TQ5TYovhtr_LxM@XVL7XZC<~g=Tr8 zyvY|9Hn_N6rwLZu7qz7uuA4Ggw2q+x}QV%h_YYn(i9&`CBge?M@L! zdm;M64dHN?E_=!r366VkgK3_)$3Fh46D{Z zMshMGxYrfG72$w7xnNq|rFu;bmKSxLN?;$Xpk6|{E#j))h2<&0F(W^%!bE97!;qXmNHH~R^hG3QCE24n338e$-YHcfd;)Plh}N;P&icX?QNCoazj=1#2(vn z!j;e-u`30V9n_b*Pq4|C0IN>v!)MY;arcI+pIAlhJ3|;_v1UFg64;Oq7*IfNW{-o} zr&340HPQr|u<~EK5Cph0St{@4m^qT8G}Y9Y6z#c@UBB|2-Az57gY(h3K987AsXctg z-2H~tCX&gC-g9S&#N#(V)?rFa1huv$^;zA~Rp01Ut>Efk5m{#KNo3LN^A7S3?+?i) z7vOI?%dAR&Ly(Kz{`%GpXm;*vWC#ZuVG74a1#LJV*oF#W>sD939y6F-#yOD?k3z@0 z+jR$fb{*Ce#&ju^E3~vrwJq|ZHznyuh<3mB;ek}sF>4_nGdMQAz(r#02R|Fh#V$?X zOOw^;g$%*w3BfBp6PsG<>{sv+6*46w%49zu+S^sX&=z`mS(WqNK3={?_Vn|24B@0^__Q!U%W_hHrABZ3#xYw-%JE7|MZr8UMVE&>^{^)?+jAc9zqvn00 zuFJdJmVjDKqQkS!A7V|6OOM)-B|llTvZux!Pn*f^A>?}(8JS)MzuG+pIG;9$*dqHzl1|zHE-*wx|{2$*O*;>0YlcNVQze`T@BMU%GWNJke zu|+HbrvGa&dW7Z6L(=z$u96*l1~=GKs_|V%AhX{lfADX1J~Ptg29pe9Sl(o|Yu


b@+5}zz9)uMWz9Z>UgeKOkZ9Z$n6Qys-M+7!9NYRQT0*Rs+7tH1AWTHQfv>1+MQb_G6#B%TonsS8R6tM2Q z99$PRj;SN`|6{o_oHF}F{5hMJpvc4qHKBe;+;nLPsLZ4B|RRqq&G zv|V6sJ-{=y-+?-?FY9b=dgv+SmxmVBK%B<2=9qSPLAxYL*=-8odLhiGnE3O;u$O4H zLiZrjNq(s`OXTw9_cL_@1@J=_N%lC$y?tEoVHA(WdHc+ZWV z3;*tM$2t?Ml`C3>ptY(V&J4cQV@A4*eoKJ7h@&4)rf2$t$gBENANj#1Qw8!{E~|=`AOd*+FSc z#sl-60OZJ zGq?GbOCTY(-PbaTt31~(dIy}25&rq;=C+2_O!#S!j zvzyT9)BIQd7#M^Ct%Z2`6LQf}-CFAr`n1n@D|QWnBjk*I*UP=C@^kQfr9j&v4I}&saY@LCMQsW6Q&?2_fU7 z-m82SYS3;>y~`aJM_>5uXE<1s250CTG%D)YU}uF*^y$X?M@E$x39jaI=kd+sH<@y3 ztclk@5y2nl3jn!5O^k%CG!1YSw>X*ua(SNJV=JIGL2os>k}1d7__6dksRpl{9-0xr zOn^-FSbL6!Q66%czOm=a4aSEj%DWgZ3h_)~;R$oh`grbl_?6-g7H=qj#Ln@$e0Dwt zUbIZ!xdh#DCl#e3@vb)jisy_3oa!0RaMPFj^VymPp) z2lALd3)1n}QZCjHCKmjH%j znKM6{y_){&nCa8!`;JgYTIbGjmCW7q%;Q4GYwPK+I0d~h|8Sm~2+(W@&qgE6drb-i zVxs5}suzr&wItp19Ry&o3G03Ldu9t;Ghh0;YG|Bki{+&<&{5K1s2@qb2+aH9Gw1<$ z*~L)?!IcJI<%qE&0MB#!2Jj=mD0dH#xepg>oEN)kP|O(p!+S@M0&yC~biH5IC}?9(a>5({7|0lM50Du zRfeznm47Ydem_f(z{pQhu{dGJ~nx`M97oU)l#mvaWIdi%d=;Qa1C-yt3^*JD|F^w7S{;mdRfQGEl$o7pbGH zW8JV_@ycvA-@8ZRCGqYAGPdNt<$@3hNMwgE^w(hly&>%`4NY(OQ))v1?u?u ze@ydUWDJ0CGcLdn=O{A%30QELT+rTpKkhH&=>-jYH~`Ic3lh{0;Pu+F*Y(t;&`stJ z>E74x8-JA6mGwQQ%UgJ`F;JK;16>q6w)vb?lBMPo%t6c-`FMWSbp-KF@=_={ElC2i z*bzuJ0Z%kngmM)&gmM|T1QOxGo@*!hjqwM|%WnU}Rt(gjR-xRi82N$V$0PqlASWm- z$)GPU>;K3Gpxu@n4RJzX6Dubqu*!}(IyfE?;8UD^W5EsqSJ|ZA`1!KaPizeU+e)d# z(4^+0R~AdyShBL%C3i;x#VYQVfIS{DTFM%8a_~^sc^xYrrTt`UOc=T&TVBQ}rG+)*n?Fqw~)aGf?NkXUx|Fq~I|9-skxr z%MEkqZoW;O8 zB;8!O{Exm)R@Jg@%S!8^CyFwo_(aXU#1GIsL7vzIcJ~KUwL{fQa@qnni8j73pj2tI zq({<~Z@iHshQnIWaF-}PxoVR)@9KVb>;|c^e79 zZ*uU`)v1CtMfvNxnEENDe0hb~Ui~Na+-LcP8T;$3V^d4bU(09Y^=*FN^^P66?VIMN z02$H(0QT`g?@dBM&Q*zOF|kanrJudEx=)t-~j4@un7IxG9NLWr8)QWg3@oO>g#9c`@=+Qxy^TehFWN!w<;gznvEI* z;W`3#sc-1Yi6FB1ls?z7SCjbOrlV#sDc8&a)eHs(aDI;o~RZHm3Q0 zuvbl_HRMx#oO8SqdhJ0!*beaVtL3Jcv2th~FftBKeWF9$F8O21!jH$b_q5CJa4+?& zLYF^#VpR;Y>@U4{*Or55nC1t=>?uixvufzTt0cPgbD!Qn9I}rQ&2jv7*oLhwI z5wL}V-Caof^B)ZcrZnI$*^i)Y4jSSya}lU4t((xc2=iKbsu8enGGbDuBlddGKxbyp zQ+GM5zi#@_%;&swaj03}{doVnD6gz7-Z$+UM;sk9maup?AsUet!sCA@}O0I%xmhP?*Xjoa?aVgHE=emq?OP{#a4Bf=!0GtKW)zF=U=Q4K0nPP1A$jtbXJL6?k!L;LO#F(B=xlTF&yAd6D)#a~DcB{Wd{u?-z@8^0Av1GM&Tf)6C(} zWPqOVlsM z?5jKHpT;Xd54x4j1bU{z<*Qq-V;0-Uiya*BWg|8an`@stlgH#BGV)6(>8`syb92Qeh(+aO8TOYV zJM+FD-l>NTP)P^aT8h$vajq){)Znoq-RJhV^_zV#ojR{BwuBvw!tg(nB#%E;7FKxG zo=eetZo@b-ooByAkn@nC)IUz;+6*tg#i%38v-S1iSLb^a_jWgs+y(|z*nEL7T-#eA zg&Q)^a<=?b7`Z?(2E|Zn>hZw@C%<*V-3LFWOAWJVDzsX+-ZMTx$iMo9<6@K(n+Hrh zC`f)@v{q&+@n$arQ7uRXA}+^u_>n`ImPXj6pVdGVTjs7Tn_e*JctQQgEb3?%e~xrT!~HQ8eH{O1_u=4b~!Zr>1fhjZGVaijt>lA?4B&VanS3{RcTgH z^6DNM>i+U2-DR`az^6`05A&xOTE;t4*Jro{&s5uf&gx1gCiCShbAqyqnMkjL#z#pA z%=gri!07|BLN;1oA@v((RIRRBJ<)umyNAb3JY941?z}vf5P}lMZ?Ww_E}Y#R5HS8G zW-Ms6F)33MYX$NBoWvMiKGwLFq2TY}k@KEL|D>obM!Wj4_;XnYQF z!u+%BdP;6nAfT<^$_~N6Fck=FYv#mgp=B_R zH3a&B4C6>oS9f}>Fjv%Lf53IV=xE`A&WOr-9l_01c!w&9O6_HZF* zE|WXmmj_ydt#4ScS+n>idW+ZLpc96x<5BEiB*5 zZG4J0xPtn0wpk;ko`V+<;kG&T$bq|K2$lud&c2>Ok+HQ-4m%qL>vVovXHO4mEEj~{ zblsS&guu@YpRV!-pQ|wcgm{4%4zOm11_zJ#hU}WIeA*XU^$#9FXh1&_?WL|Rj!M#D zk?b?W2g{7l(IQ}I39o`N-Fn;2A()CB&}b{w-iM(TwM7{Ev0uwh?o&RkQ&h+8-^x?d zk4U)QYWw^wGiPt2f4g=9OAn5H>%hR$Ox>o~)n@|1{lm=*Aq39(kl6fV48pG4RDkONK3Se1S!&yhgyBUp zL=%bMnz^yJe?TVn^SygUdN-ME6$b}I{BTiJB>|>&MRhF@Pe8;D?E4%>S}L$Jv_&CPyvNU%Rh> z8Z?Qf`rygwrsYU*F!Kdr}B0}|DD8#M0*|jlNafuyuSE~ zI;NIylan4Z9Om3$d6GvNi{CvRp6x>w#{Nz}C|W&VZO;Al6mMyeX$*cW>JTsh^IYx~ zShB|j#<13|cT5Q(cd|`J1m>a%wj4BM;JS zSL>z|*)LmP)i4IXJ8LM!)8+KfoAES;ubRB)p$I^(Qi_N0ngV%SyCsX52J-5d$qjsdu zWmd;@XrKf~ouAAa4X1IOgJfWxDEjAa62#fC*!m1Tw@1-2H_D}8!x~ z`=t;z9ljeDai3J9e!2SBarY9}XCB?l$-cxF9PG~*L|)2fJAsYN9@V8Qcle?nmHkt9v>h+jcu+H>*#+gta#afR=IDkwoV! zt}BD2!vbkU{1&PYmq8w>2v2Xqod$+R;hiTAM$OR-?|jd9&SgI|fnBWWv`gj1V3Lsc zE}=iDd?^_fr4c

8LU2U#WSH>o2K{n3v>191Uf#wG~Q*q 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 diff --git a/doc/plantuml/images/jobs_sync_detail.png b/doc/plantuml/images/jobs_sync_detail.png deleted file mode 100644 index ca8410299806a12f5ec3930804d2082c832046b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152414 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ë}4tIx4 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 EKhpi6$^ZZW diff --git a/doc/plantuml/images/mqtt_demo.png b/doc/plantuml/images/mqtt_demo.png deleted file mode 100644 index bc3cc8819c731cfd0fd92551bf14d50dcacb13f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48978 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ℜ`0dkw3fwe}oZUlc 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%0DDeJX~eaAwPKryM??CK_68K;Xp$ml+o zI4CNA zprVG_R_uQ9=4xjqu&uKcHmWonMtwY)zoT}k$~gEWKpc{p@*#uF#I_lP#dtP| zrCG{~eN6seS63bm<=Vzyj3k7QB3a5gAyalI8j&U2VT}4}Q1&f4j%aLSn{*^0vYe7_ zteHe74u`RvDO*yO1{Gm^62>l5)|}^^nH;|BdawCou6OQvp67n=XYS|s``!06%@Xk$ z|I<9)k9{n$5{l+a1}+vF;lO>zv5?gw_cvGKn=hK|k+YaYMY!dV8l^93t*heSK<900 zS{?xW;PVJ#DEx$>vR=qyZ@K%=v15Iu2R+?SIrn#0Kk=%x%#d4Jr?$6=U&2qG!AsTV z&r^7fUZ8Ty9Bb`WVASb`+-qsAh_t_+Zq2e+>{Mg_#@G0zvK%RX*N;WEa*7hN-nw*=W`v1AM2|z$<3} zcfgB<5bn(=5scw@ASXb-h@zetV+uc}g{$YV6PMbq?!nwpjfF0wsrr;yB$|-yKQ-EA zmo6O>XjMxrxgbx}!=%Hm(DLl~j4FC;ujf4JZSFwbue5Cv4O4`jlSso>iL2(l#X8on zug%S^gOqvW05-Wt>=JqH*Eh{66Up9a?0{haWe0iTLgdyfhhH6++hERsZu zk;R7SW4G7%(5D5mK%(!jt`ize;dk4chvO;c8WbigzT!{S9qlFm^R{NT((mRj4KEkxN9K)^j@b-W+ z#lzo4m)Y~O(9yWNm?2zi%s-$T?=MB%i!+&M=z?7`6X(?zDxdNqlL7*sn%`O}kN#y7 zqzZ1H%06Ahn+(6pgMQ;3?rDl?!&N@t($#DA)>TSEzuw8K16vO`T$^rIB|1bl-n1o= z_FY!qXhw}5Yz#C3sMghvMSC6v=d91`cL0*B5V_5fe{J5{;mH4jGz}zni&U%7;gIpk zRYnOsXM;*_G4WyXh=t~8IKq07k~-B5=4q+&Ig&>%Bv5!W^?>8@vvpKXIs4j~(R=Sl zE9IQL4l0U2zFIup$)saDW)KR^7Hb*_{;&P!67!9=x%BnAM-gMF?MF3vP@I1yXn0Ut zyT$@++IE%RM!aN(pE$0SG35#1v_aW3d#LB;Cm%kXhz4hJMH>}#k4B$k-SWZ%O(aOY zyH>GA2{F6v{v)czF^+jerkCDGtx#%<$VkT0B* z0RMF`-s3fWpOU0i`bJ$yW2t(!pZ+E?Pov_vqb{1gpCdtfQw5r}SY)lq#98I{ z-)Sm9te_}x2Q~`3w;S}~IV+oiK5&4-NeJ4-Zs;NvWDL8D>_GgT3jRYDpZTvnX&v##(Z>aGIgnLf_JU&%n?I78he3tA$ zrh_Lg19W#etpYDkA&TRNP^$T_2hJUF{Uz>vO6^&>86UZzLcZMg+_;mvPlyZ`j(^uR z2aeSDYHsij8jqHl@#XyKkgJfrvOS~8P-Gma*M<1ubpSF(z<{_}nf7ZGogC`|==v88 z953!LP8(+pGY881w%iujZ#mIMkDvJ$b&}EW2BlT6VAO5%sOingv2!4hNyS%@r2E{2JZ9VP*NBU{)EHhb}^aB=sxEtB|uG3ux7 zqXdJo9tv~9KL?yms&VL+MW9B*?jgjnr4xiTj6voXFA4XS(IFX=qve0+UMWBpP8F79 z1If$4?Mznm z|LH+B5Ilry@r{bN%d^EH>n2I!P%48ALBg)mPD!||F~p(WQOxVMFx+|xp${(1F+e@C z-i>krQc>2sLZWd%?Z&#DXak~&d9M(nIS&*&vy3S02h;{^0bEZ(3&iiH&ur-VEKMJo TDtY+^Q0I`@8MINEfm75!)kKvH diff --git a/doc/plantuml/images/mqtt_memory_example.png b/doc/plantuml/images/mqtt_memory_example.png deleted file mode 100644 index dca790c6056a0c4faba2b0675e611b4fa4be2313..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32407 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 adSew~^{1k4I8U-4`t4xnWLskG8T&VB3<>H0 diff --git a/doc/plantuml/images/shadow_sync_opertation_detail.png b/doc/plantuml/images/shadow_sync_opertation_detail.png deleted file mode 100644 index 5b91efff80d2fb8782e0ddd0fa42c54a798cf068..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120850 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

F_ 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

{@+{JP^?P6lqXvY*bji=~$z_tCk! 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�qw$XSA^5;MH*-gs0K2ld!^>xv5VAy%S?WeM$TP;0`Tx+ z)Bpd|U;Ww`IARoHsonkdJ3`%EqyL9ASS%a1fj3Dg!AujU?T+Z8C?gE;{ zzeL2_{=okI-Rj}LVv}EF9vea?dA3Mp@3ZHqz%fynx~21qZ>KX)J1O4P60nn5FV{<- zu$zMZv>=vYRY#Ogy-qt0xg`k+302;)KU1tBtP3+Y@d5y3xbRDw2Ku=pGME#CN1sf?OhM-Uiuluqd@jYf69w8 zLS?P=+7<3>+f^!MnFz#HCN)zWhwDi`4?rZ-5>2+=oNHyAcpV=fACfcg-n~oJp1$D@ z*~sdP>gwug@#Ik>>r0c61bW=AFugea+WCooOZ;B0cg0yvXne(EPuK_`y$qG0#G!Svyi7 zbKUHAdxUuWd<$lNg!Y0H0sGg5|%B}^y2FB}M$7gu^OVv#iblVk57tAu8 zPWM1VCdB<9t`N!OZ0q9-O*?IP30w0yPFpL!t?j1OQ7dfRw_bf5^l)*6$EV~Sjj~l^ z!+UQDtB5pndZ|4xxu@+lb0f`j2$*Eb0JaCiqJ}W!MJ)jr``n$mjn&onWi9{Y&k~uh zUKM6EN3@KIl8+9F(8QLEjg5tbgtWKISC*HRRU}EPH2TnpA__VO(V@1xo+w>nBwO4_ z-1+=WnkQih{+e4ND>qYVE7GP7=OtnM@Fe{{7-6(Xnk{BHCZnz-6dIYSq@oy9tMUP1%3E9G+BMsin|S~$IY zbiXILZu)J$jJ|9jknug}qu9Amtlv^-6tK}wq-12!4BTODUeiLNcrv*#NtVY1C1+=6 zrbJ08D;{OmX@IS7>XH^OHzV#h>x%DN2iG) zkek4jx*!hoPmb5WHaAOG4h;`SM@8vOqGJP2qfqzm+_}Zpm69$SFVmA7nQYLm09VQ! z!Sds7_Tn|4{w+B5{{T0?g^kmN`xEp1KtF)U2+!=w2xZi&zW7Dd2~a{^)wc6xyD8;{ z@92b8v%7GfqO?HwIo)x*B+qITV(o~-TnZNY(Xvj>3-eY?qU6*pJ7qmDd6ul>^gM=C zS6A_U-NwU-2ii#!!A&A^!#B{M?&lYI^!D~Ph(Ac(gUebrR%S^^NHRMN^xKkJj<&Fz z>rB&qq@F^mcx3QLbm{tl5X-cTbgJgAfvV3W)TbH#xN#C zI6Zjohs#XoMHpGWgp+yu>1?m^Bhqy(@(i|X*Tf6$pWoL9eakv{E#$fdWKJm^G{z3% z#cm<&zmETstYr zgIP3N{K^bH2*uH{?=qlxz~2N;G0*$dr&Vc-y0%doRdIApY+|CFfqstk2z>tiEpTLG z%91_9Q}td^(%tV4c%R0kTkL@kG&sU@{QkDbs3JP3BH*)3Gycv8{|YK9TtwB;XsxO+ z3Avkst5^U(49Un?D`ct?4DJWZ#{aPV?@CL$_vQbQ58sOpf1Sd;Kxan-$UZZ6nrdiY z`kTVUy%s@Kya^;9e)036mR&XEKg6Ev$W3_qESJF#$+CuZA=&YzkH>Fcv=Sq=x(&m% zX#G2B;a`Dpg<-aOR)dcCjE7OSi#S84njX9lmJ}vj*3smpy@=T`biaqCH z?4NqSd~%Q<4678ImOeYZ;O^DilVTj{4432IPCWltI+@-<_rNr%+ip@?`Ngl*+|?0Y zUHHpmFCnecg9sk~{d)c28A$d|SjB54u3hrJG{*+WIX_3kLg-YcW@`NTltRD2IC zcqm;=X8NTgmv@66Lg{2xb6~|DPi72`)LEHc8Rkm!3{8%Xvkr{^pv>CNAcOSOsB(h(QAKUohY`Z z3?e_(2rg~fNPo?Ro+o(7%}Sg~IRUBMrG~0vR+kkqQ8e2Y0s3r$$Su>}AQnJE1g(arx^{IH;f{YwPRNOQ_6iHZLeiuoG7qN_#y9nd-K)y>43Ypom z%dTxcI&Dk*+UHgt!q7}6_gZz!s;*A?Bowt8$~fNpR=qD#p;{be*sIy@Oi&;FRV z?n@$1&l|irMgP9ryjbo$gc&f!yWQnLP5`W>rNeSdRP$m*d3l`blW!95&dA@Oi)Swr z7ia!LV5z0n8y|d>uP|!1*_cT+R<-##zog_U{_>e=)-Y+rYxs&Ruor%21jtpNy+vpgXwcJ> zF2Oh5hD;`>m+oZm87ODPSbUt_!Ki-UJ0I^e=W>BH)UXx0#|L$lI%K=D9e2d+4deFB zKO)=bC8UaZIu_(wO=Yd|G9>-zwz6)Dtd9Ob;UqSh8oRJ~7dOr|R>E~7knh!r$0N}y z*!w^0-?Zf1^Fo<_8lBN0M*Rl_j$uBdm0Z&}K0e$h8x?c)j5r2?_Q8xLusouuY?)SEdM5B~;o9mare8 z>K56lx2!}mq+f&z-NP|ic~H%32)Jr{J@L4zNe0p9!G{laPOEx$$#BnFKc!3CASuj9 zd!hE6Ybi_BYkRl2m9A^;=7Hv~6ma^hHXCTKNFBpUPVA27cO@dpCt3oo#iWAzq69+) zRth&3RC-QYsv1x440T!%>jm8=Fco?rhIR)E&rYvXDz~JP5EXB6o`wu~Za`eHaYyz& zBMsLlq>37ydHdRt^`PMn@Pp0tMDuYOq|XXEZij_Xm&?S+uC7I|F80OAkc6;i?Xr7l z&J8&PeudvMj?}m(7Od&0<4Z>8j$!7$^Z5tA(>f09;?@t8R`fRsNsTj!2?MVx^x!v` z^G6+wmP&V4({)6&>V%JSkt2W+(9Y=w@*?6=gz zs)ZwP?h@G`kL~WV?g5eut`|AE3DT2y#gZsX+bUo0Ex6$ zHVTT*c)4HW*tYrk^Nwug|dWk64`+*5=sLFLdQW5zC8+@X<1d^S5m8=A9zRl#G zrh97r*BrY;bV}^_W!GMk*^adEapH&{a5IL`b0pE8F}{Cg5&B6Yrf65qHW7hI$rT<* zlAWVcXs0Gk#FNBmsns>soNnDTS}L#_!}UNxpc8VYngy_P42Wjs z)i#2zeL&8JaiotLa%C;7u3%8fpAD_OvVB_lU`im|3MdjoWLbO1W?nLNEA+>ALPByr zrKf#mxq-~k-mywpws@9h{?(Fj<%Gj*g56{(e7w!8Nwx7VEX0gO=H)$KnZ4;^Nl?m{ z{NtKRT96Gw!69>w6YO#C)@<77uf#0{@)8nmAzsUlp!5eLjnl2_DMaIdSUudr+JUMt zmSyhC{(hGIcov8{7}gEJP`eeAipq~V*F*a$fJp##k$*>I;;J5atFQN@jK9QZMRJ$$ zc%f(L?^E2Qd5JS?)Mi343@HkBq%4xffi>|9PBjq^QwJ%w=~p)n@ zrr*|H`#ULnAp4mK`__E!mM`S9D=4m{O!@U`8T7tO|2X8IFElJF)myGQCsfjR1!m@P zGkuc~DHhLv>2QX@K$>5PU-N;_guTistBXHsl5YT&=sLkd2;l3cPQ&AI^1G*k&S;QW z2Hj9IY_2es!?Qmew?)<-Us)ZH+?U7Au3A7nJjItWyv4HlZf=FZUU=V`^s2>oWoUlQ zt!Mc+omv-+xx=MQgpkp13 zwg?^)mHIY9(ec%tEq{aVzs$iTDvGfqZzxIW>Vu%2y3J@O1*FFx++%H@FAhQp6#Ee9 zwAfAxg-_nzit|O_5c^ zhYv-s0t!-Ttwcw1x`)X#w5G2jSFQg&7c|??5_@xOzmMEqm>-R%6jrvky^h!yF)g(c zb6wM>Q4y`0n$d5yp~>uokm&J!TmZ!!>AXWjB=xeDwWg+QJ|_>0XbpVYgw)gL63ftB zB>!mW`r>O4B-Omy&^l|Mbw#7S;zF_vG=~PCq#s3|#={MgznF6hLGPc<@*iX@!ma*2 zXspIO@&qN!`UC~#Ee&rif6-v0;5*e2m(H8lcJ1cX)h5+u3*wPs%OLnI|Bym;-cW0K zG_BVEsp~t#nrfPc6-AJ!pkip!MDzwi5ky3k5|!Qr0Rspq5Nbr4AXTc3Dn*Jk=^X+D z=>isd3lMrhq=hD-7sEiyb zl%B54Y*nR$%(#bQN=J~s=Z=&6YJ1O})f{zz9%6pq!V$dDtIuf6whi-mX)o}q*{k|!3B3m*Wc9y7Sjt zlI#F&1Lq)lLo>2gar(AtnaCKTVv;UI1+x@Q_B;;Jg#c9bu5ua4ti4pfwS?oBAQFdw zvwrIIn!xpMnIzr(aq?ybq}m4ZDZcKAT8zj|8B#fElav^}C$qnjt@;x#comYWAS3F% zA9*kJLIOKnU9L7Yh>gh)k!6QWVXgIH;rO`W{p{!Pb~w*`&T2g14Cb<*rWd-2q-~ib zOI}gw5f|f_jg@fY-SsMAF~^QjncB;B%I#|L(B?~L^s4q>J5Dt|IJufJk4)KcQpsms zvfg+F1iUBUr5i5Yi7eUoer48`o`mR4xB-`V+&0aC{#K|5Qhq-hqR+gXHzLF%wJc%l zpPo_%Xxw;gH=5tD*wGm5F}0Y1zSN6zC~Jw(!sZ9FX6K$cpLB!oj5%29T9vS(1C&6QHW#h$*=^FQ!Qo~Wr zc+5nSwjgO?U_EG*FJ?1mCtBt?(bU1vYDR(N?LNbKcFObniWw{yWnmzJi4n3N?MI0# zm(GVidT3xV`eAV>P$ah)HI$uOrs)}3E8KkAR4m+`NfEN#(b@&D%mZ~hxy z!R`tt))Mjdo^(Xq?+LV7(&~KebGOZr)sY0oiYlB`VGIbloZ;Z8lQG`hyFQH`Gx?>i zZ;MD!;ug@>T9WY?P~r{P5`(0tx0>nZ;jUg~oSG5t(UJEu(8XLBe@NV^uEqsErMq?W zH}Vd0BAOG5{r3GMDzaZy+^+8`B!;gHdU;bLSbQ{u%ddcLR`B9{zI;9tEPC7 zyy|8PCcfkrRSJfIz{RZbk&nLx=&z?YZC`2n#}$YL(|nUnxv{%4>0M+$d1j7Y)qU0? z@wuV1X^++1^WHQi87pgjVMp#q!x-@%o1FEjsoXbj#muKNlQ#B#Fhw}KTrj169_!|* zqZ8tE6HQEipSZEzVwO@?b|+9AZ6uhBKgRX&o_YrrqN)9%rF}<*t+AVN8~l7cHB0+Z zCdElo8RJD3TO}oMmVv)&ce?F7uIZ3^f2-y2W9^g{jFmm>w)6K-!_ZU<>}~GU=bt(r~!jzB3YYoQ{MrjrHkF*VAx{ zB>pT6s}x>rWQYrxn3LcUg_Q3V#axqyqD-x=TP`O#<%&red9F*o4feMdIkLlEW3>Ok zifu}dXk6knSIuo}5*)J>g_5vvKMF_HSK_WeYcuZ!$?AL&hh8;a>3V&(D%~bhTImw} zl)@7`yZmBP?6Z-Njq1ufar&;aDJyQ#yzza7Ng@*pZZ#n49M1z;U#EV{)EONR0KozW z32zg6%|Mjx#QrM~n!7(G?0w(3hqW%|%_w@*PqOd=3_Tc|1|ZH1&CXhR?#4X|He z{x$Zh1v3Yc_ZmHpg~cr81VMrodLH{_NLdw|vjpaJU)H-VTOCKet(Vayiqf4qn_W>{ zK$Y_3q$BvO>tqUoInhh(Ldnxs`nOh-rrp;Fc5_p*b8P)FPcGi~W6A04F`n~Y`D*WM z-3-VF<8D6OKFHytX;3sPzO&0V)9Ia<%dHp$yp&a6L~ut$j%g{T8XMIT1}&1pthB7= zMCX@uYovvj#bbLElQMB^Q59=KZZ%8JLBee3M{?!Q@$qdYNlxCoHLv0AmK5YewUJ-q z_`TGMex5T^!}|_Cw8eE}Jm!i^vy+N(*lWZql@gOGBjxwS_x-hwgh!*^b`ul}q#ngl zW7jkAFJhl`Ib9aSz{K@E&EF^0y;7!Iv4&!7o3%1eDDEb!C!z}swN_pN&awTCOe1qn z^!T^SZJ2w@iF5L{Y;7+D*(}C0P{`hsd0%eK_^m(o=BD@8^-+wMgyZE87 zX&f+;+*|z!<}^`e89P1ps%?>Y```BvzmI!%Y1xL zFKlF}@$t}>|KK%NjcnR6#WZBSE_6iohz$yATO~Qn!+=ykR&#z~GCu#loOyZE2f=mx z5!B;1Q+4drl2l?C{qHj_H@mNS`|U-KG5)YUaZx++MF|f6&>`FV{H+~z&nsz_FF)w3 ztEqL*EHqon7HTL};9Z51mOk`glj3AZ&z*(JZ)6(?vQ@a1Xc20OC)dvY&kVO7jcz| z8V{>1b&9RzJO`Aqfk z-R%?*l)|QRu zcb>TqzZ9(T z@o*~t`jd?X<7R_Ps$1c;uivj|4lkKy32tsmv|Q_p=;=L_piF0P1(n`icopf0)txma zxd{u*N9`_E=~+jV9@Dy&K0znF`cEa_+S85rdock9&JWsK#@32TSh(X*M-$)-%Fzhapb^}JzqDjp2){} zwFtIbhl7vF>v#ziTGhC_-Tq!Y9l(3V0?EBkIbXprKENlm&zYf~N$%+C_PAUpPTN3UO>lIoU;w{3`yAir z%~5eE$S0=s(A3$iG+B_`F!@UPx2>4tfRS9qAYht_r(a$IbaS}Z3NrPUnD$7=A!YmL zT{ncui7#o?|0K!sQtN{q*CI18aQMbiFBAB;UunnYHs&3&(9Ms)*=e-GoIXVw4)nRi zVWBUsFhoF;J*c7>Ez3k8rKZ5V^4c0I+B(J4F~t;!IQ%n;xD#gqJ*Tv)KdRY-)`-(n zH#qc7_FvE%U|k`Hkv>m5;74a*pQd2~*-nfLPYVid@&Pmi84U%uO;oHOedRr&MNAJ z2t*!TiH@#%UzlSMHPaTs!t+8<>wem2#p3XW2=7arU>o}CQAzSo`1~lgPf`*PP|ao) z&OOm%ZtH_YB}d}uH9|3)g@7TB?p@&Y=t%wP$Kh{&kFv#iXgx9a&@@CO6g4FY)5x6) zIL|;3{)6jqT_s3K*y#HjwC5BNT4&hgJWVz514k#=nm(X7!FL`;cEUa$hS3(>{#|AC z0DHsQdVF0bHDhNv|LN}ww9XB1wVJn?>TsCWdZwwl`>E!p&m?vg9aO^w@7nW6B^e!L zv+5!>lHXBvS>M&?6>MuTxQy2yEg2GK8fn$Lns=z^?wdD^%zW3hT;B+vBOmj7#SR*$ zVH7!yE-gSg%F8Vtu_AW2pY4f{W-RTkEY-HO1a9@^p3AVv(uIifCbl_4?n{6R^p$PD z?k}m0onpCg5k{P8l?%JgP2<0#LgxQN`MPO3Jq<9%Q_z+@kt$)STmhAgw}&u__rj@o zzRJ^SG)J8wue#IizPk(NRziKRQBq#<+?4zajNjM-^DE#C2Jgy zkm#v^t8%3t)+0UVD-B89H1`Pmzi`VX*7!r^tLWV}nFwLWyN3+YvJ`#7AM*rcE<}j_ z>6O5pHkUVmuMtC4csNaA3z>9u>vS;U%Qw(r2ADqA^w{c{Wi7HHlPjeNSZyWf!6L}8 zimdnCKZ}F9T8t|FSk-q zi=B_(gkCE>j@JCKG>+CF59&`S>=_(vi?s@R%#j-TSru|MO3)HH`_h&b z81?>EMI29NPUY^AH961;v71;3D|EcddfYW#{@R83cPAaZ70549+$XdEQ{)0Ue2Cn_ z!G}u#G9V(!0h^R(WD_|hgA}=R@LFeJvech(|L>>d%%NZs$QRjtK*`NWDUprl6kL!? z6yy3q;!jYb#0rxaVM@h6Il}(?z(Aw_zD6EAxk%dzOUnme*2T8|N5^n~4<{XIy<&@T z)Bk{kq4_wZGp`F-zq>U*JWNkM%L-fvFbB{s6^+-`|k@i;k$-}B6m(zbQ)#+#f+l~8GJZbZ4cC?hjIH0 zrrpTkTIby-K(Q!YLR7?)6h3l{26~w65!B%x=i#J^e>> z(EM|R(4<^R2kTufwBeKc zE%P<|M(a|_Y6#^jdk&A*O5e1MqP3c-Z%~4!|4jeHvb%KkkJvS|+leoUp#?c-p{2S5 z&yveOef54BFr=_oXvMm@(6M2}%XA&}r!#=^cfXJmskviTEJVJ<6~_&ar%J{&DP_J% zexAdp)=fq&&uTLaDen`dW6UBCT&sb{A4LYE;= z%_kb7R)^ppw%Xr!l=2+i5nQ`U9xvcPvhs5cGB!BBNAKr4doKOlh=nkXB{ibx)?pA2 zjO(V4_u4?a-S?Mn_rV?zEyWRK@73Wefy!TQk`$z+D)rrC?r=UysObnvJ*i=S|C-C4 zH6wES2O{RnUJ3V_Gs9th?!DDhAEoerX&dnS3KGhAZ$zNpHS`$u0nJmG^xXp_d3x?L zYJJ`^e4Qmh0q<}SXk+u7i;?lUskXU| zt(hrSZ6Yf6qIa!e~xtuG~*EE|4dzaKUgEybm68`WFTFMqYT^oJ zFY|86S!#6QQf&NS~}KDf+p5562SVT+8kAFRMSERqR$ ze1zKU!fQO4rDl^XDTjH4?ZhXYLKr{EhShot`@D#)b$9>^ZIl?Xe;+z zL{Kfh6bVYeX>7sM$X{*MxRTXg6NmZMxd-pW*Ey}@TWyPEWJn6fgG+M*i0iMA`T|TwY1Ov-Its|J)5{>A$Mjd7eXS>H#0l3bvGfxpYaSPkSP@GyZF{Cm}mEN_r@-6FRvCioAWK-C;A&H z=ZwUIvp(E%Nuz4HJ=*s*63dsyN;8kdMCMT%xxi%1l}FcYJLRz5qz+D>b!A~q#d4vm zqFF{$H>LGd-NSOwT|XlSGyg6`MxkkM;4Am?ovZ!ng6raipR|Sqoo^zyfA+mj-tyje zt&*KE=l+T(a%RCy1uw18kgZ%sEXbra<)X>Z?azu|`E>%5(>YjeOXI(O;&n|YOVc(t znaxy_OR6xBdwz8@Ww-c-4F8Hi=h3(bM^gXY!hT|0bF8DH;@R*E*IB(jPzz&^f7BNz zYD}Y>@a&Cn9+JpOuqYKE3Hvs3vyIdKKz8uetcz$~u1Zb2fBWP8^VwtN+bdq~0ZKOY5DLuR+B>L)2`eJnj4xiY2M7zVdA|t`Dwz_;u z5YeFjnP2KZBW~5buN-B+Z54!f^v2>|%4-t}5yk%2t3nlLzi3t(5|4b2HmI!Bc9B|o zGkIQgBI~;_$5CqA%1F%$>lJ2k{n`({+Y(fYuVCepac%$>&v z7bv|hk5OoAdBV)Q~e@Y`zrX>LzKJ zkBacf?S{7e!Fvrt!h9+@wt@6Ei+#caieAd5I~7dh68IXMH7&yY+>&9~JS=LwEVCcz zRt@^+&q=p5y*uaF>B5VUz(HJ6V#2}#e z*A+}4HMeDU1D1BX&n53Zr^nq)hg_$xl8sGSZfh$ow?8^ZqH26ERQR5Pp9f2GfpS9| zE!`N!XtIk}w3NG=pCe4msL{m}|7tf|D79Nze06~)O9OOQRr7q_&}9_wZQRwQ9Vb-8 zWfU>Ap5PBT3Iyq3PBMyaZ+!Y{G2?mu*nIQW3}HPQXO~uFiRZ@Ut2RZD9Q~|$`qy$+{{!u&G-uhmc%X6;X zFjYOGzQ%Z1cnk;gdXKb^7h5<_@FA*U<8Pbry8I*q3E3{j3jeX8%TI9rnLMw{9 zc_y`GzICfg-!~MhOs?;wx4?4*j-TU$pl3{5D(5GZ*|D;Cqq<7?;@N~W%;W%-fn z>ai6UPJ)ow@AaMWb1AC%bcH14aVFTB8OW`o;Cw)FaJYL6Qg0x}H($Qa{UcOrB&rHxrk9%+iF|VO0$Hko#A0MBU zwOad*8YxD^sPWl%KMT|O##`0X8s@mPrBUiKJmm$ZvO5gGwz>Byp9p0j+lien-OP-; zc^zI2?Ab0IP8Y-H-tk3JHQyX2pYrV+pZy}~zq_Ow#p&E!@4>kv^zdH&P@ciYq4EFh zQP$y(<6L*zHTjgUU%#fLaJlZX{2vX4h91=WJ_D^V+|^Jj(s?$yhXbqd71jC0WLV{G zEQ2Bjeyg|io9-G)C|*=ZNGLHeF*GzZNh&)2ub~lXnm4Q}wy3`2r%iwEW~n^9DDdA_ zh~Zz5VAdljuSkJ0d%$!cUk8jtJ`29@?|f;R|@pt8|LaeXW=xE0V*+V z=fRF@+(VGy3h>P}=vuc2rz?CfKxVe2Y^kT`CL_~2U{S6S*^@?EVP28u<;7ylqo5PS zUh_UVwWDKt3~f&%=|s;}Wcz#>Z>ZS5zreB#8>T@^t3P?m)*Q_cn`7Q`5zcm_>gm7-}yDV=9FXemoP*clj^5@AIs(06s$CS*zS+HXu*SZ{Q+n)BjhiJw;-g!Z^2oHM7tl%Rg}4oU&J*yI#^n2h+Qhz9E=l~@6WXc!KC2S63lrI zmNxR!{cze)xi4Ne;XJ!O+>YJ*AVoAe6HIDk$-3Yh0Re$^q*`p42h6$kHDeivTUQEE zE-MO__V{ z+{K5;{+!HJ!K{PtFAs8#Y(ns=Cbdo6V8Lga4NKo~|8X$W<)tDpXM>E&+n%E@rMt1}=JnWvFEuU`fko$J! z(=}>hwxF_-(9j*m@+jFB#5@X(=jRRUNrG_~2VK&{VPL#nGd^e0L#%P0CfsLqCvr=H zP?L|)nQiVMC0kAheHPBPL^V;WQFIe-QPr$*h3bR z*+tG^DfnoOUzqvf)B4;iB+ld2{%h*^)R5P?S?!ysDtn~3HRd-yDfDgW=I;8;J=gao z5qhPYBhEB)3xtB3Y!+_M2=Cnst40rfL2S<23R|wI^(khV_OZ+$N%v^Xl+|SOWJ#tc zOK({y^q+5E`syZs#HQSWKy5@=l;&pX)TnlfaY{4UagMt0j}x?Sm9Ew^_iL;U)6eM@ znA$%|j%wi>m_+s1DeLUM^73-H;gRYrqf^7D>vp#Ce0u=R(zE7a`%(im&3$tv7E}RW zT5sPg-R&pO9Vz?BGy~`=an~BYPpm%RDRY$8Tbb0!G{_!K=Yq2$Br=fUTOFjDfz7)V zmEq-~S6*k^SBHc+Hwu&a5%JS9x@<5oE>5)jN@AunqFj;w>RnoT-1gj*`s<1c3AgBk zBqT_}U5nj*>Tc(aRh-PHov`)h=Vpcng=|wzk+p;HS-7XlZSgh&qZJ|e>Ll>K*HlshRp7iOH7kIgEw@OBSD9U! zE!d~`%}-O><=z-h4lKDgC!Xg1zB@LXuNgIr^q(QhAyKo!x1yO6eqegbHmB03Cy<0a z=aB!gS^F3RWNB-(UvqKmmS6o`eUi0DpLyxl!-r2lYGSNyrOEl8A0tJJ6We>YE@jNt z1RL$IX?Yr+fIq{M6WP8x&yd!uoAa}K)4$I*@n|Gd=L7v~u9Tz5Aa^uLmphl?(ax`K zt(HYIwCCgS9jum&xP@+2Q{08qa|7&B@J}Ue5t^lti^S7GgQd6n`Q5EQR#cR~Ys&i_ zQQ9GE`Vj%M!5rBUcF!`5fw#Z$gHeJo!8^CsWitWQZm#PFEnOK1`rM>L0jF*h?FMb~ z5l7f)~kC=M~gr~WtT?M}$rS&f2Aj%Tur&smaSrcsxoZf^krpF^^epc|uKAa5qjxG-6wkUNP(q`XV6)R?+9|u01+mevl?+*XB-<<%WXB-%R8Sa~|-GRu}PRJR43FTnGQr zr>C{D`aBie+R@)p3*Ei!;oQl>oH&osiVbOFy&NsQy{CuHXb?}T-#R|;&3L>knm5q` z+=trcPIHR-5#$k4HxcvmqS5MF7cQ)*oZ9H|9zAvgSP1%u`s^E-6S+Mx&#WtJ=d8g- z&~$x9XFT!yGDpoS}ED3sBy|0dzPvpA-0>mZfF)qbx9F*(&HQ_;9nxSU47=vHmQ-INnpkJha1q9*n*QXfQ`2|l5{Za=O#4## zky~c?y*!H%4kNXVO&Zqutp5bB_ljQG4d0PITEp^2*^w@reGInpJ^MGad$X076f==e zcruVPy1G%Hif$G!?T~Epdo#MjsV|2wy=~S=TOH;#>sMk;2rfzHZ2qd-GtEJAzo6Ka zIv;sMQ1r^`s>TE5@d_OYa|LpjwY;FQQb9HkS_Q&{CaTn^5*-C522J)QI_ndHSI z0#}HjRwHP) z_MQ83Xu)zXC9wouD};30fbc5I3z+jJ^?2E~>Za|+hGKgJoDlv^6~4K#EhF94%SRw8 zqLYEtbl+j`5%Umi_*ai&h1y(EOORXbVdDPIvm+Dk~LH7k_d9`IRC970j;QZb*%HyXm(7 z9m%|uRaYIhTz|BaMY@ZEf}RCcD#Ag-Co)pQl3|w~EQyMqqQ@%G&6{ zfz3unhRFAtw=MG55EJj8k{I1GqA{X3%IexneRp@v;3N-5wP5n#uP~5daam2g#t{HH zm(llIuR^KBD-ySlQ_5oG5EmT9VX+k_G+8|1zq#waJPix}r%78Me!=+Hv9HQ%382K1 zj|56A;}B8Y_EHLQMMXs^DJfax%6k**_1*#(;X+{^;w$BcyK~jbLjHN1H#3mD@B&pZ zt?9#>_boC(&*|xhD3=P{f(jF8cZ46$Q~jO|kO$uev*8lYxEi;Z_}J+Zg_FQaq5Z|0 zlQB?T0R6qL%Hg`Zp6sM+v>csu6aQp#j`l{5hB;EdwsmWqL7)uI+0TJg-O*azep|pX zT>aTjS;^4ucS&n1B9_yU++!V86 zG|2+wFLaWhv+zR9yI%-GUVZXQL*y3w9i7`K;#LylK#wzs#*EO*PqvN;kT6_g1#GZkwDZ1g3;+!d2?m*e( zcjXn7a&{PldXYg3`Rql`&p(xzbjbdBUIy~{#cZ=vh(;R@uggV5PvjxT1Zcy57$1m8 z!!SM=S_lmHAKDTyWZ!*xr8jr8-d)QQ5x7%a5akyH@k)>fczz0McfOQUL9o%|)2M^Z z6T$@5AJ-+lzElAh@*UV+sA$D0__wAdv%7F935lrl8x2GDBmS^!M~fObl5c=V`Mym8 ze~M4XftY7T&@l4{Hm>h9QTY9PBX^J`i_1Q5doJ7)G8Ds*8pQM3 zdH28jmhuh|A_ld#d?_TNv$M3M+}^#m1WZrrmLjtTtwfvP@2jyg#&&TLgBb{(+Z%Df zFI;76{==B4y@v=i44;7o76aQC7D&v5R6d+_gs)Cdz?vBu8U|cnn*{%!GBBGYelWl{ z84ng%-dD-N!N)fr$TNHTw7M_bV6Klm`k*}>8#{BAo-Hy>Ia@zDIazMAKk5N+(HJ)F zZFwdV3)Kq}m$L;O1f<+{^X-Y^4dIO8_umHx8#5La+b@B6UJo|wuJ-M+va`#hCm`dx z4jC)7Uf|j18uQL6S$a0+vD(fmIDXB@XcfW&TXj|S|w^bHl=Y; zQ&aQss5L)eW|l7x9|hbj*btaSHSqw1yURrZIANxZzP)ie`>=z6>yF+2_R6Y(o7-W3 zu8E%ZY-h4@_He1Q#bALfVE`F7&72MrNm|<0SB60KE^jaN=f0mT-=Ab$D%<-;Kj=6e z-Csu4DEBodhvmJeW|56i_1ha+JXRAT8?Eaz9g{6lQBisgVRarn#%06z;cgS}guh69 z2o4TzPgBhYZa$R@R45H|lyZ?%U1zbdMm}EXV}^Gx&J8<^`>#%iYr4^i(cz9I_J>i$ zU!#?p@5@g5SXKtD~QM?88ROE^~>;1-Bh+&-4($%=+rKO z%*@Q$OXo@lxK)=HOUHiv_~GH<0cb^EUtchpR+6W&9wkfyq6-aN(5)A!?acv;a38+C zdFF7Bdo;;Mnz~JHL=0+o-T9WkpK;z=>{^P~w989}!P$7qMD+a#?{~(U_2(!T+bv8U z9b_)pkGS^4TX7#Qy`Zm+otgudg|)0W;p!_ba|Y`XmaB);Sk;@=pe!+KAJe|9#C^w^ z_B+K*Po6yKbh4%BZS{>_FcDIVCOi7^3YUc4oRrro2X%pfpl1`dbA4{HEx|_>L3-_4 zLOCsJsL3ub25J;5_S50J_V)Jr`g$-TIUEsvyW*Dpwxn1$E;!tz!sA>g>``gyJ+N=C z7GIKZto;Bxnq+jp(lOVW+<{Hua;rbtyb&_6fU&J!oxr+SVEFttzk+txvxZ54a}0`% zOdSz&tyOj{=>v;j)wR5Ru)Fd8RjKoq+j^UD*7LoE{JE`Y9>*;D#%S)IB3No+!6aSh zmXD^>%$>E3#bLNc0f&8mW8V$=FGT4rt)0|2J?Ts(gG8_^W3i+(Dhf~yM%jEI-=Bg{8-%_!Bt`|=S zCsx>hVfV7=Q6?)|8GKOa5W27K3j^&*>=7~alM@qm_HymLNWj?co}U51zBxwJD? z2LhvlUSS$+(B!ocI*cm$v4hro*oo_v8gf=->xrS*5*jp$2UUzLD={&#*ZdQNalKf) z7hko8K2cz57=juO)A}70SgtEEY5UUrj+CHeg%}K~V_d`!>DTCEI)Xd= z+kTYE1n-<`nU&==lrxP1Y@GKPdzHwIcvjZJDthd)!QEPDy!V4<&QnC;O?8Y{MI-a6 zVmqZj=Os2LP9+{HE~QU#-G&@Arc9VI<}C_1biaE0H!*rd*IVk~w>PdOn3i zbT{$uYwKnYx~TL|fLhGcSz5UPxoNpIxg(N*n7=MP4tR40_%PWl(8zUmRe-ZZZYIZy zn2AS=H;EsF@7E+=c(BK?8WsEC>8H~_i*#t6w07x9%c6JMS_!v~?B zm8CDbZ@oZ*BcX@sS{{T3F8YccDz-dh9&T4X3WBa#b5mMdhqqK`vCDJ z{Fc~q^vAh;&HLY)CnoCNdtwvdU%2qb-#=6(MXx5QOZ3cGA15*4p!j`17;`u1RuK`1 zQ=q(i^A*e;_?Wu7I+_%nZ!B$XZMKPzno&xsWp)cHj*dHDWfNw*eOqGr0m)a>-Y&;^ z_<`Br>eZ{&=WM@9s&aIT9*p;KMraDz4rCppw>W3WZ~=yrRmvn5KRP_%%|q4*17g-U zC@82XH>DjBlKpQQ2uYfU2_+Sts6z!R|<6s07eEX}D z4G7dW@YbM{6%`etqN1~Bio9c!a`)Csw}$6Bk^<*pWMrjo2Tr+G)6LOY)%w}!nwq77 z{1)>X5EKDEAt49B)I?ibyScfhPs|!VH(0m^ocAnh9Kl@<5Hwb%{&&j69l790aS?zKUb46nMET`nmW?d#p*j4gBBtJg3a$a6i|=*g|`T)Hbv!sC!(dX0kOs7;vS zLx_|nt+(@s#>degKi(q|p9B!8Gj&?4_i6b>V~ZioB3V+mSZun69-TQm<8{x#jpUKc+iuOuOAitu_Kq)<>n=Ti8dq{l18f*@&WF^qCRH>rITxbjJ*6qm!^Yd)LtCYY} zN9ifOI?ZBKoj$}}J=w=UHNb5PQ8d# z&6FF9-T(aMP1=p2Q7Ys3WHA*tvC0+FvRuy&ec%!(?u! z;P|Ne&p$8ULZIBzx54dA7Wiu&bY7R29C1P)fdm&*UIvi~RiM2Z>8i=gw=vfPFGu!@ z;Xo0;j`#xb*%43+ueW?}90v$YI{E@8kI8tT=_?}gfH6P)0Z;_~g9tO+3?$Tp--*ee zlo^wjVPZVwJ&yZ*l3Je(6_UG99;+`9^W_f)2(^BD=VQEb5~uFtT_;|KqUdY)W8xwM zc^rZNA}-)x9?}8gBCLihJ+vGa8`KLlL_fbs0=y8Y+IiuINQUwH3fzl$EE||p>K7TdNWH3S0P$p@bw`pIcM-*-nC{ZzCBJHXY%*!PX>o{6ZNgP(}2S#PvPek@InVV6pP(e^2e zgB3DyV^{GLkqc)1a_g&gVh0Bg*<{mm`fwZ59DpcF&wXF*woFyy1a4`6*4@RZ`1@H8 z^mb;auH091!{!)0yq>9?%d@TNuDra+qZ$J>TtjnvcLO`($P$1@S2aHOL1Grfg7m8+{HH>Y|P z(&#|({}dMd+q2yfZl#jckxzSSQo7Ha#@^N)Xsk&SMJ~llEX8PE>NqrQOW6oah$4_8 z8LkR2y_Wa|d*J!{=W(Vze2K7xH&;!jQRM2bpHv9hD2^nqX@zIo)qQw9$YnmGM6&CZ z|60Mhj`?Fxu(oKL1;DHjC5*A|-nF-pD_J6nbH5@l61Zx)IvMyON)}76CB#ODMLf(g zOKP4fMx}8v4D?FsrnOb+Tk|j-kg3$@)Wp+8Ht~K*>cRm2T$FvFdT?`|!FfCIzV9ilr)DhG=1&n*rN`;O9Rbbu17bsQh#Ah)C z#BGiCq6PeV{l2Wb)2t=osnQK!4L64R{cHFv?T!lP17i&uTVT^Y+Z4H7%(W4+dZUVtA4)oNPYc{ZjvlD+%a=9LEe}9dsUvM{X>4 zH-gQq+jK`28$EZad^hzzv75Nva{1xIqv(XJ2aYQ#+)f*|JWPF)nROj{+P~6c?RfMD zq@ueNLl(-;9kiy$1iu1n{W(CAnN!r&azbLtte*zg+Bff4xuU5#dR0(9EIsU;Bh`WS zq)S!U1{@D#k2ejV+~*kVz7x0@0NeYnlmujKkB>+K(}2N+fbG73fJAvLS&o8!D~wId znMl^2@F7l=^OE3F)k`X$g{9Z3n^SHz*W|A~6drOB?+$jUoJqGREfv2ODH-so>1X1X zwy?E%_a!acj@~}eSwCv&PK#DoD^ku+cJf1sFh7N(`7%nYpv(?^Ba*v91bDdaPUZoQ z80+{$p+U~`1&rA=H^h7QownOx1j}K_0(aTuWc;cp?JtdEe(<|5W(dsPkSSk%-6xkJ zufgWonx#(!%xY=OF=A z8b(I${Y>$dPx5z;ukWG2VC!tND^E&3S49pCmKXMvhYDW0N49U;_fdV4wprym0V=}& z;tBC4j!8y#X0AVZJs=iUx>!T-WX>GF_zwB1;qDrvltzZ`;g=EF*b@2Um<^>>sEAB_ z$}0O1r2`Gi+95l3pP}5bw{#t}jUS=J``%>qX=p++j#J>bwm6i050}=<#uMe&>hB$u zhK;?1tx3HE@gGETE0Ky!WHpyp(D&$89Qf49onyP67JFIAHwOBLla< zLU=(F0yP^X0Dgnw6NsOqpgS0!1GNaA3u;fpbjKEiiGXSinax1@ICkx7B%2B0V9pHC z#C}WTGB4;`&Bx!ABE3716HsO}_%__c@!{Msx2%s4?!}9%+ig!t7^64e?jwz=szT!Y zr+GGrJj2Bn5_-T8pw^*Y!0qrbL2Zvy!Od>uvh0{*ehJ+b$6t;;ya59!J{i}Jw1w=j ziSUD!wSD`5;kMMJYs}L8q!dMk2WF|8FGo(3Ui)SH9m@uZ^?LN?uM)Ml4 zaF{RX*{9IZ{#>j>xfG`e^q8+)c++3gk2fbaa(1^?gLkP&m@SO@t2Es}SBcoXSu}#; zYuGD2i^XuC&!xJBKZkVMe|ckPIzL@7)JbY`0t`v2m3@Df%v(f;9)jvgtF$(H>%BfpaIiR>y894ND@Kt1q!?QZ< zBt1vqcI8aP2x%rK5_$ME%v$s0r#XdGb+zkPyQp3U#2Xfb56Saz=&Q}VVU7>5U!UFU zotS}QcZeuAyi^{~nW}O^*009Wgt1tjmGhLn^i$)fh-StTs;;lSYCO&E}0eWBmx{s6%kcN4K2D>drmQ3c-5ztNR4S#8F?)c_*3Et{y zB9XV>wa#gEw@B@^F4y(m!`EJ<|7xxQ|_ze z9$Lz*R?LLSw$V`NcKHQ`ma>uW391slx3Oah4^-g^P<8H$HdZG?5au{%kKU(}oIPF= zloJZ}QMpF$1o?b*PYZpZ$vK<#G^ z&;5n+-pH|*fXOw}8+lo`wU3Oro4HAkky%1pdyfqhiOHJd;>#2vJ@mK$2qg^z)=K4x zUqj;B4Oue#a&CA-3h$Cs5dE74+bkL3BiyX(8uk>qWyPTu-@8Y(H{cugl4NuBec}2* zJahPi!}94_xqcQFdbKdHdufh*vEUXqj+d3o=3XiosoCP@hfNzLexujW**&UnL)3@O zo*9+v6;j>$NdP!Fh>$x4-(154o8k>;l6v1Q#)GnHjow7K%mZb)6b4kHny2} zkB5tLZl}Z+>=veV(#q1C-M;J;M~x(i`EG4J?T+#z~RKHF955Rfc(fqdq`Sa!BIhbsr!f)B?Pa6ibQ zZyd^aws$&BTb(movQ1%wOvTae{Sr)^Ja)HTGCxo8XoZQ!#5*TfBn$0n*Z|b@nKm_i z1kLj9Oa(c}xt<7mNM%kh!fFg3u$$$fE?0upOgNu{+zkd^9#`K;AL&GcJq-bE^M01w zIfd;HDgi`Kkl%`-x(fV;RAlM*)?eMF_VM;(kNUnGkXc$O<*tIsj;Cs(R(b1g6q9H9 z%4e&wh4-W6Tmm7P|+Efgw)dhy`9i z%*VII6xoF3dlJvbQLo1`h1Z0U869iyhF3&NPKVuq7XSgL39PU0*|Sg zu9TrV7*Usa&8h+4z0p(^3T2nDPMHBf-~q!P6i)7$zh`78T_^2^K#Jcj9G#AI=NnE_ zt@#T$0HOsT4w?(IWh+x(>{ZKc-yX+ZEX2IUnR6h^5Nrrb)MLp1?dzb59LR?$$`wgn zwgSTj))#|ooyE=Fo;OTP&T3>ffC4Inb_y~pq3$pb5CT|E!w~9VojMV$zZF8GSNn1G znn4=-Nu91t9h0@XeRuY)5;HmptSf+oMra`O%^LG1rWH8fJ5Z*1n zSN!SXZw4OB9z;L=Q^f^I3>fflKH~Scn9BQa3=v9o+{r!0Ke$SU?l%K}%>TDPdbvVD z2^f-ph67;}kkKEf-M@Kf8Mw*~g1*B6r_kaTZyD#NWl*7*I zydkVLSx133h$>*>Dz~Ufr5hjmHK!F5ss2l||Q|`Ke*49$-t1 z4-sT1w0r{0WCVVAuw-&Lsc9TSGV=Tk(iBsT00#-cRmTPfXs%RNBF65<2-%LJjleDc zo^Od4(-4plqqn4Urlp+2u_AaU(ygW-2<)#8KqG3QTOHDr#<$ATjXJzXfYX7d^jNcT zY$vCT(EVxSlMN=#F^N>rlKjmwU3NfRs-*tOm$D@TdGrbc^8eMyze*$jllj6lg)zXB$N#0x7RL+@iAT=cm6Tw%N z-c>B7eK=A&EC%Hz^UQyZ_j637obn<-_FUZauiw^5>y>{w*!dY@Y3aI`1R$R(bs)a` z>W%OuqCOg5XAHmwEZgz?1yy&2Q00v2k5xD0@2Tvl@)ujY-ZTJEHOsF7^eZWChW%IB z&Yc1En3!>O!(-qNBreB;X#XzRXRno2pzMc8Gh;v7uR-aO4JcZ|YAqq|V;)d*UtQ}}@?7ri<&t(AM3Iz3cQbQqUk5Z z;$WdXTXvr*s1pGCyz-dJIWbL#OuD@hZjKV636M@zJrQarCuMG4sZG}DfX)iy-dMBl zIv@C%pWAWep0WerTs~Ut_=( zhzmwsx1vpBgMuEoAG#C^eBggKU#wkuUYQPe3MhU1CKdGTW$pVO0hpAoS*}57w=_JI zsUv)nb{_915m%PUsqxr1pWje&^M$CZeJc}*ig>6n_-u%<45UvX9XH)}`(?eOl{0w~ zimu#0npH1aue$M!%6cH=oTW-}sI07P{71Xu9OEvrv=DbN-MQ-D)C!SSV4i*7ypgRb zaoNe*apZEgTPVvk{X{4yC-*dv{Jt29mkbJhSjV9cvx$Swn~J?-8NjRXCa?v^A`i{l zQgUlkRIEY9hSo_iFB7s+iPUpi&+e#-0yqpvA2;T54HV(`RWirLBAfKH{E8?{vWl7E zb}kC;6a%}CH~EV5S;)}pRm1DyCkInBEE7A{}Y#X*O%X%JdgddzAO44Lr zJ|bxY#ylKF5goMQGF?1A-h^InAK%dWQ(v1SuPoOtBp`MRDOO>BwU$S!xK)j`h z!|o1iYr%FwS3ClSZ}7k`O&XulWcg=4Yf{%JA9g>^oC~R`;jYhE@cs#yI;M3qR@NTB zb0ixh@NfPwP@SXTagZV0TpUVuO?Lp2sM)wm5AdR2xdHsv9#96|k*^JxPoV*RWFTO?!k!#mEJw{18txj8g#eh@KA~u8z012I) z3bOT!L&X%lP6}xy&qtpCT?a}66pva?tCPUPXoGdCtEqG)& zOI_WG3Qz2G_2SJRxQuT>LGUODrBKcTK}_(U78;^{plbNKG@OBvaj7*<0BBFFtX6l{ zrZ)(hV)^-5SZu&zLybeEM5B@qByBVl<3BoU`w2r30jmOd`A=;FU@{E%1QdwdQu1u3 z$6O4mmlXrej)Tt%gi$eAV~p!7RDl0?Py$+u-==y}x(8I?|MNu$r!Y|HxBBszK>917 zog^)PV&X`z0pxJaI9+ER!3NmAp^GSwssKD=FXHX}X$^ z>aX59LFkHq=`}mQ(8WeCt1U_kn_VnTnxH-CIv5R}IwSVu5IrCU>9`@C38wsV(sm`g zUgh}{=I59zJ#i*wiu?6HHHk*{AJzGLvX5*o2bLL&_hx;qA`<_P5W``;bZo;S96wUy zD&Q=e14F!y#1mjT$8yqB*bu>WqJEvV#-F|4v6dTIoyZ8vQ2*zI^n~cmjSQ1ASEZIK z{_gD`v|-dK>3Gn+BA(i?NmhdyJK$m=Z16bom&R|(`cwwQG6JZ!`095GGmHU~f5V@D z7uo)W@&D4j{tx}ezxVlZ|4%Av{~v&+;hS^1>5L?tlzyEL`_Pk@>M#6O>;n7mwT4Q7 zW8OFCUbmfloaP9muAWwyU35YuZXz%lMyVP4&6_tJ->DbN_RRm$F`jx?dCZ0H<-z=h z)XxV7rBwRPMF;&7 ziT0H9)(0XXMg~=dGVv;hp;3Yx3D^G7_$p zcAM()^i*;rjBM%#dgac}PWdFMt|>2Y)<8`APmvLr8$t~mp+;K+lnTb2+}x^RKKNw5 z;55Ps?gl&+7RC*MN+iMsXmym$yHPhl(u$A(b>E*bI6gSOzDy0u|9X0QKsMv)=_$l< z?b@|K3VsGS|K5DI4~3gjH;ctE4==C!+s-00x8(|KE7Cs2fWBkBszFQqLb27PSork3 zA^OIR8>6G6R^A{ILGMQ>HbDpStk#Cm$pa(+uuON6tqG?!!#(gsS(FOXdq42{3VumK zy&>O$ToYPc_nijKVb?8n<$|?lZoSFvm2o{zetvg=jj|p9`RyEH5a@ONN!aUu08!9H zTtoyhc=#qvn{Zk}T%4J6J5fAbzcGTcfm%2~fj}~rPyMj5u1-16^aFiu!qgmZfufp{ zl9G&!jDmuKtgPkwFIk5BpeI6Q2b-!OI|UF|2cFkwnsu3+oS}i&Y5lVQ{+J(QX95t> zCZP6L8yJOz^&Urc7r`_SN1K~QSnHN%0k=4<0cl?_Sp?vAPAjDDRtL+6lg*UQ;lPVX&UM}ZWo2*hcqD|s(c=Y za{T!c8;bJ0#@qC}Ne){>_V@4QKXLu6JRot%qS*rcF4hZa8P$(wsW~}0Sy@>?^m_et z@Q0yAX7}}(q}b;AdX)^VQ?`@AFlp(8pE9)R^_-5c4vE4`n|f(uNcYyO-V*6MK3{%G zM{q+WbN5pAu6IV!nZZ0W)r}w&fEDAv+7k5i92^QzMnK9+-=NPx*51Yz>ZZAS>L4MY zC7%5L-Mg3nt6CifQ3ID~S}Ky0Tc>_~-H4e+k8!y{{2jCzv)+Z^{GY1OM3{6m*RwtE z<<#`DdksMU8#3v3uv@nZ)XDt2zo?Z94mJn$CUpXNX&QcOS0}P<;Rp?AVs|o81c>k?`$WXf9t%amiwy>6I+COxKgwd+mRb{LW@a{M zj#5@o5L7?aIgt9$VvF*~uVHv#*t zV>lIv;rbVs23Up@Cg_A-v*cA;0Y@Ns@pzu3;KONtix?kGoRsm(%+NC8fYmy#0>jU{ zoQHpdVR(4~e_N2x0$`tL1tJ7?;-%89icTOu^~bUg!l(GBDTw`njtJCn;fmYqg5aR6 zybNCRFy>H|CXy)HC3;Sb^;?Qd?0-EqTX!9JorIfZU(`NTW-&Ow@J98W*!z_C7ekEI zO3Od~&l?6@4Z-UyR*L5A->zN!>w0jOr}`Iwk0+$rv1Yng33^!tQxTBD5{4~auAnv- zx+j?;bx$V6va;>{+qOUP5Q?{Wn0Qp-V!enZ)xxQM98$UVl(4|uNH zG|*4iCJcc90>58~b8(iOC!$;GQW0Rczramy@CR|gn=%(xf@jVy5 ze+k*LInOq%0|bK_IY>v3TgR^>dQn~f{pFcba}4y@zw*rW(~3yz{p@J!z{B+<{)L`& z%_mPz+v+6(@eo!i9#(59mgEJEL~zPsLl;dI6n=1p?j}Wo{hTxUpErDbGgbzavct>P z0W7Q`+}02o=a5Y3+)yb2D0sXaK9BVRAUv<{xnsb!DPrEC(s0KTa1S8qxd{-;jO)!9 z=tNb;sb6N`(sPDlU$Nnf;94P}S87*<14!Scj*X%H@BliPYld3I`qQxuEi_;YBH7FU zS&*s*>+0$P8o^56QY5;6U%&q&5DzA^#=0LC7W*fW)h|X&fj77q#YWhh2phC4`VZ66 z(kAKD1OR>Ol`B_ts^0=7``eoEg0d3ZfEw7ZzJMubWgh)#(yC?Tc`ei#EE~p0@*Ybn zmLNUqNmHdos>#YyUbWm^H$wILfm+N?1vZ7Dai-J8Y_%4`zruHIZEdm^Xo7*Lx4$`> zTdz>h3Gb_{&-E+;o$;{z7+`|?l~z8BzMp6TU)7Z-JoRt4poW7NdZ5oA?N9SZ zrgt(%@w@ZU(a~{ma4;||f{@0_KS&qa9m)#SrX*~pI2SIojf`~=0tO>ECWZk!9Pcxt z9AJS3S)+oCjErBuet~qJI1Ft*GWN-B3Y(P0FbDEk-1If4%_;*?!BCB2lda8gveU#y*-5yu!DNZv^0nQ%)(2z-Mu<-HmY2Z_l z)F!7C4+bp6+eL9$#z(#78Tuj_85JEf+A$N`W0~m4l*ZzuqVf?HKK;y0SLrT_n0O8{ zEn6Y^(+D+b1dr>kjdJq|ylISJs&px3LqQyv-e_hG`C9N*i-Ei}V6uQ6SZ(wL$uLbk zjD#e$S=mLP+i;+^qRXs(VnTN67rdoZ~X9J zk5RqowtLEvB!}Dn_M2<8y4sKsw85mn&pX?7!T8ZWB2B?e+kQ1GVV9?bRE#j>J9nO6 zzihy*emY#MJH#==KzYgJ!etF-WBd~ObY$mD|N6T3tJM^!uJhYuPQee8V1DfNtKs~& zaKUm;&2^P&x~#lXp&%z;$ zb*9N4F#Fck%D=9Tzzywh>s#r{YSL$>_Zup!c+Ahl{}Y_jatDVmP2gnjXoH2*0ner6 zJbQ$nlhi6B%*+VZwtG&j>xFvT{5>8frWL0$3hY0;_OCIda{Kfq2hp_q$CoXsHQRW8<{7FJxZw z3RJw-H#QQoP9;G1no0Mw&6FmFNfsHmG1SK8X$YS))pL5w2{-4C8V4URFfd?MdHX)+ z3|YEzHY)H7X&OH^hTp-y#~Rl5!uU&W6UB+c_#^|lrJg07Mg{I#m1HCwde4IFmAFcR zW3piP?%=88rnrO;0`D@<3{W^<_ z*T(SaN1ul|C&0QZD09)>s|MQ+30CMRgq66h!EjCITGuknN@{ZKC1z$tPQ|wABgvJO zmDSV&>E;d8tq!qA-d$Z;Des4hHLW12DyvD?0UZZ&#e36uSo~_VDmsL z|DQ}uA`m(@Fhspx&u&&Li2=o8tc^;mEn*pCp)%TXygQSBQo$4r!I;ww{KUq+Q zhlV;W-_;SkutON$t)e<%Qxl_xccD^6dR(AO z1za^m2BaFsA_ix>Aqqz~P*|n^2pC(RUl1+dT#%W)OFmZZfST6ivlbl4-7dT9vc%(Ow;P5eFv3XU zCfsg39;H@=Wb=Of_pS%~4)jp2NJvO@gfbMh=0P<#G(0?zj62P|dJHk}{PrR1FNWiI zZRjst(1uD%zR~d-K6bX&M~99B!tYPiLBu*)(T5G|^Nb~hk=~a^27iC5tm)ZFni1-? z^~vnXudby(x^a}k|Ln$rm_U=8u7u!*ANcMZ9W5>;1WetFx_~xsKYJP~(!|6DRy`hr zAD@nlj0_A6BqY${E+|#XOLuRr^|{RACR!wxgT~b=+{@IE?mSi9ysn|);g#ub!$RGg zHyfeHFy?52{l|duVbjFoceXPb3{uQ96_DXJ)2pg}mEXpqwW4k^FHAq7PS z=!$VG@p&^rtRIiV3ANd)x-H}!BplRw3kDYy+$GIZDRZ=QcrYQc2 z4-W#vyh=9%0Tgskl{d!H~Qp&P|wDcDU#l*ZTHfH#@ z-as5CDJ`AbEhc*N8vYHg<`Gxs*aS%NB~F8*X?-NACM)YFHL)abSQK8aurLK7>Nj&; zGIKVj{FblJ>1E{PLZ5B(&Y+v7NV^tSE+3Fsz}(T{6k*l9e8}W;1&XzUxb#N$jkti!@GkJzhebASL^pvf-VIW$q`t%9w5VA;d z#XK76Vy5zCNHuIukXr95fY70h{bIe9L}g$HSyH;I=g}nO1#R&Z z7A3HU#~~>A(J=HvFKKtqrQX5;>1q8npkZK!O`Okt@SWTOhGt446>`!G*IXHtFBOn!Iu zyzBb%bh>-9YOL}`QAwV0M}X;SjHck2p=Kd&c3~auzEz_1_v(JvHy&kCPRehe@k~Tg z@)>jIT4oH92512=ONNLt-`rpwg@|Zc$iXa`c*upCm)Zj9jgt3dR<39;2!6vT*gkvl z5xd^!e}D9C-Q9K#hz|T5JJkjohWlygaDX)zH;byP>!t*io8E0cw-}NceFve}W(z6L zBJ-x%w92EQHHs;*8Hh7!(~cvbPx7`x)heIcQ{7nJ(6J!*{7Z&I;d~wLZ)A4jO-dG1 zv$Db-OLpUC`?WSkit~)G@a9tfF*@?F7k!?)ruJ*dp7%O{dl;byK}X7w>{C}OBwd$H zM;lmz=^Qbk5IzBJZeHIYo@(ba!p2 z_60}!{@&sH(untEnA?Mo_J;|&7*kUlUl+|Tu?nW=7Ig0H=Su>Ye3gd(^@_Bjpg$i@ZmfsKDKk3%`HSFK(0y2wZ!mOrUA0g6JVB8x^-1{uRK-`&Af zy<`^gG+zH}2U*s+=29m4A-%2;OFNo}mdb zYX+~|%fBx85y0$C;ve}w@-zo20OI-``M?|?jIY1q6od)>6O2G!!!9}MkGP{`bGTw# zdiF8!=JyhN#Dd?eE;p{W4h>cts|-I5m;^CY7k}S%d6Rpt*P-QEO>v*x6IzP6Ncq_V zczZDgfEx%a`@{_v6K;49_EujR zGRoZo%}bJ5_e<)m$C-3wf8HFx5Co&!$?T`NzyM6MRto6wcyj=pHQjI)+bU>H#XONcBNtoIBn#ASJ z%2DeFS<#OU{C;%%Zl6&uS%I&cw+z=7>I{vGMYhH(woPuEnid^FZGvp%Hl=R%GKkGh zy)Yo-w`ab~d+5kdpaf#o0-~rIPGAg}Yt|89g)26CX#xeB+Y*VQlC7=c<4IhySBjzE z8Tcz8T>*kLXU=EK!<7HNK~HC9=cr~ZWqZ16(Pc)4@jf;8@^|1E)6%}&@+<4u7#Saz z(Kpc7pMQ4<58UvNC^S|jhdyPzNxpQY@5F}>a)$|wv)w+bp4(a-M#l=i(N8oTC~^_2 zrpfhfSPniXXr7Tlk!gP`h2R9`dSPFgMG)wgMP=XJI9@}rcRgPO!ajp`gHwrq59~MXlJdsltAe*sg{$Q@?>G z&^DBEl(u4Rj2HL);Q?LudEf5d+#v#T9ul9N73VmJd8P6pK?~|&`D4Lkg%Sl)^_RT8 zEJg|)wq{!w)8tA|L0`pkgyZJe)#O{+FQAU z+H&QI6y)UaP6VxEYXba(M;-c{WZNs&v9C6EJeK{Fj#g3El;Y zz@3y^b==H1jg0ZxU*7xY-{XpB!-ub*2(R0DaizC6rYnzBh|;s)V!GPd&CsLNMYu`8 zWv7SJ<=i{DhDCpi8{U^?h6z1ZU0xWrMQ~3hy?a*?oY-Z<#N}3KFI(SNqStW6~f4$;Yki^7Uu$ z`~|*u_z7mJ#IXZwL(|1&10IP*z2Ngn7i?;>W6OTL4p@>-`>r2VnU~B|u}DTu`qRgl zuNbwBXHf>E4u!?a)Z(ibFrPeh_w9f|e`cp;ejojdZUXX|JJSRA(zk1Trj{9-8Dp9K zH)bnFUr3DA_2--Dwnk&T605_M#vdN2Yb*4vSk8a8(kDZeMs${lXu7{b%PrttK|w)c zqP}c;lMfM-9u*T46GPiZaI)Dx%gh6KA!pBntqJrd@_N$Q>faR`%TO(ruguTCBW36L zw5#lBeCZ|Qw5{A1nzk^eI(gx;)%=dzjd4v}g*sM>g;Dk9={O$mujd4cKHlin@96W` zv1-$NXB+m8oY^MLWf)7Gv;MxIet9g)gNZXk-J#=hY=P44+tete@~Eb^4gxe+cz6Pg zaNa&XK{V~{?c$7vHT#MH2mNyVUodkL#??a)zu@;f(kShe>DZAkfB#mKn>#bF;riiG z6w@VCS|S%s=%>*--ZZm9`IBvDhC{1yTIB5p&kLBD9=(il%jS8Eyi`KrO(mR|PUJb~ z?*(^D&INtqpLO-K2{HUX;-sVZ02e=`@LO97p_#S>g>b`2W*|qXg9g?iE(u5>K||SqKcJ?Nqeq- zq0b}Gn=#$uOsST6^O~Z6b1YhTYl2Gf?9Q}Bg+mRcwoYj7s|A(Fb545Y%TN0{G-Xa! z6CBN%?peEnDOlpXIBRz`NoXZnW@p5>sm*!gT`qIF8}a&ZAZNwA|3y{oa<^#PaK2uU zXFW|2lx91~urr1C^Xh4IgB#T?2)?z`msGfKy&l}+Xh~6@_ddO)wQXGyVPVH@A&Za> zvLO3i*#89ZtsJxtJucF0ek3Ug*U{nA-Mz86unt1hW0f4!{Y@pOc&mIKnt*pG#fv|f z;fT&u!B^N~zRZz9`8-oS+`NZGxU^cL+DAV6yS&_V98adlCqEM*0XyRzT9>f)fjKk_ z-tV9KmZQUdt;FpMxkbWIB}VyGI7^Ar#>YiRwfVlXQF(#+L6*WzWlPJQ5XQKEYT_&6 z-uoW>{^g8}2qW8MZS{HiN4cSgOXP$%wRf64+%qqD9Kr-=mT`P{s-;*nX&zJgW^ZBx zYdhr(vW9YoybY@{Wkb)N^$iSclUs~vvag2lP*DFi5TASwRQcqu1)eKgE*na{vym(> z;m2zP7-0ShJO#}M3j2YuAkV>s62y|W-nu3581tZZ@oTC`OA>gVq@<*roEI6thp3~~ ze?V53&wqWEh)VFwED>R5h)whUzVitLKkg-PHCH%SIoBfBi>)$+;~gsFzQt^Re)>*} zypx4abu{_``q%1z{PN{XP&yhK8tQ*fa{fsB^qWjYs*H$W;^%;{>>83BDpe~tx3a0* zs;ZHz-N!GdeZCNSfXPewgtd&!@wOvOV+n_T{qU3nikbh8x;m2!h!|S^D^A3lV0~|N z?u&@Ig+=nZAD@7kFM40|;h$EMk%fgNR~U!GadUH9d`O}8Qaq7Cp~?F>CMHIaYT!Ta zThQLu$BJq1>|DLzcgb3E^!=B?(8sQj;-wK=AU$f^QW8QtX1cG0)%YIM~XJl>;Y7e;t zZvhTzZ$~{=JkB9g5D;*}j7Q!RecW@=Bt2J!*}g=On>#-<(=6pkjVtZlyLY>~x^gza1rRRo}5P@Y`mv%l2XO9OreUf}s2ah=&H* zLQ_1Sl%wP7eO|vSOzjvqH#aD+0d_!kcS5bq*-k`6#KIyQ?c(grLjVRmnJBQ@L-ddP zp=5ESrRyr0C^_DQ3p*6Wjs7u2OaM`THxBLAbg;AQX&>W2WOkv&sj=Y!W|KRvtfoT*(mBSe8_n!vG!cde{93 zW$wTID_Fg&<>7k(4Ap>jNNOZYUOO zY<4rej!qDWPtEOzX)2%Z1J^aNv+`k4#fYF1=SW^Ls7=uvF?-?|J5`=^l(wJKSBg3I z8qmUMj8O-?&i2Ic;^Los^^RL9it2@|6@z{OPgH|~5tnZ5=g-ewl{ms;V`t<#dz`Cy z11Wf^{Q+`*{`}uIM$DV3@4$KlX^?Mh5pi)9Kq9Wpue}eP>wHkN37%iQDk%s$ogAKU z5&|6jf>hctYSZ@sqX+R16*4%~AeRP%fr=yTiufZ$$8J+f$S)M?H$uyuJ$=XnWudMpRq>oGzaYq*T{!g_;+8%pvuYJ2%jV$n`bpF zE)|r!58fabdl-~H*iY*xzwE}JINgeo0j4(;2Z+P(^pCY^vRt|(Eh%ZKena%|dHdG3 zHhqqBR7WOiQ|}~K9S#l-79-n{_^PaA2>6a#RQGeVwuwm?uhN^65`Jpx>-hlWx83LG z=XXQvzElMo;VRk zJ3)Sp%)X?zN@YptO|Jz-t53CIfqCyc<2HEt_owM56sbb&u2O(0!8`&*LXN0+^WRH! zUC0A#&&weSpJyK>r4>}`u=i(e4}7NzpXR}{@cNe5Vky{nS-|fNzkybU<5za_vG;S< z^>gX7f4=F;%jtm)1}*A2%b|Bgb&noBdTWJT-{9M2s!XVGvR=7TSX#OXibb(nr}_#l zwDj~6+(k$Yp^C_cGQA4(lPY0gQ&MbP=)Qf8t&%_3)93!`hk<9${=vmBWo5C-&reQF z%ujJ?1oE1DSxM?H%aRwem#EIqeKHL?AT-tqz@cKYy%yQ;a3x2Ft1BxT>O|ybg79M`Cp~>qe7uOdhP@up{H*8lc|$+&Q3QbC zkRw^rpmV8z|L*EMek4}JjKd(5hodz<0I>&xH6u#A_koYk@wTz?X5W-kP~ai6ARTzhEWa|ETtt=MY*L&KNx^^v&eWly#=47}QTrh~V3#e$ zzR5C=jEIQvydoF3?KB*!j4U$6V|$)1vgb%J`r(7|xj}cYH#M1_jxl(^7HV8-B=Ux?tK4$@$L$45NnaxV{Gm0Kr9vX%AP)XV%k%bQ&`wxIX^Zw z)|Re(gz&t-?^(8hZueSJo6hY0c!qRHzI34N2r{VpW_lJDxtmg6gjB4|%psqE8z2~7 zW;@XgHm9zxZexA@MidkjfIdnSv;DRM*z-RxH$3&Br9hIL1c%P)GL>eoc1%1;MKxPI z1!WjOYt7x{MMNIj>QqB|)+mj<2xMux+g_F;A0Xf`Wtstd3lx>ux=&SAMQ(cUAn<@v zP^@NJKJ2Lx>*5TQiH^=|d*!YbUV<`Z9;_{&e&drTPatZUZnGJ0ti6|T$KX5x-c_c3 zdy8I$-RMB6i05-22rq)}KrE@Mub;?O#VZ)jJ%#R`rq7CsjO^;^VfKee6dYiDBk%yB z!fS0FCJq%k(4|~X%Mb}E2cQ1GZwom(h{g?-v4+`^-$h zvu7mOXipRc8QG=fZs9rYS@6o!N{GX`M)q?{D7e94M~59B<8zvU6(K0&qNf+{ZEU-5 zW){P366QU5c;c-JvuI4qS09`&IbU&n*@0hk&0slTq@FYiGS<&!7XbfOS0SZl=3xs$Dy*$&3R ziIIUpV#jO{jJioT3*)y~lj#i@dURMAN=4-&fvPu1kbxR%$S07mBd7?&|GkmVn>|3! zzvN+%j|F@^eiaMnP3a%D@GZ>G_YQ$t0zX9nL|J2_qnRo>*UM-FF8RvrK-rw@@$~oa zlCrWP+lVpo^xS=Q9G*{}9E@z4u*JrSu%!F@_*fTfKvbWw@7I*=uI^7XW>7f)WLU8( zrt^S(=u5K4=J&OZ65+{#jH<{-d*)^LsU+6d*Q?5$Jc-|Tep9<^lXa8 zWRdp`vG`?t)^qOTN;SF4(}Wr=!{oE+3Z zFQHY?lg(9xU6#019<=#MS-Es4z27oBg7Xj(@+i1%9*->$+s#FE6RNU1!xj72zNZK1 zSKM72_Vwyt^C;U1U-%fgfA79~sm;E|8kf2@7M-mIZRDW?h>*a0&xGKepSx87%^K)x z^DADAY|$WPjdZOjQXwOHcgTp|wyWa`c zVace$WdFL+Ae&%g$UF4)^$&qjotZJO6ocgjrs^x6eaTj5j_$U-ELxC^7pmK*-OhTV z19j28gPACG>D!G4d6{bYUslBM3%?h-U<M7YPh*R#G+>VMt{1YrZIe1ud1*SEyz=`Rpm!HE?}WJ zI9?w~GhI-?lib2c3{t+Ygo^n*wl@I}+kV*H$}tX6d_r$e&lF?Zg0YoVPOo`*a8ibZ zopaV#_2gM`y`l?Srt8DHMJ$DDz%UdAK9{Iy-Ip&wf1w~EN}RDtBh?EBj5Lu!K%n&P zTLn9Mdiu1eX<$)G$;sg;;@9}fro-26_buNd3%Z;e(oK(kE8SS6!Rn;s$zm3~6;w%g zL^>BzAITln`;iSgIaO6v`0HIR`61o^aN}Pw)~VhuNbdV&A%s^b$Oo)!@O}D|YRhC} zj|cCKY+alc!kr5VwL}fcitL$9WHM;w$jcZ$i{e(_IdiLrUv+aa_!8Y5-DpT2f3wX6cQHZlbhCvK*R=Nyk}X|5}2#ao{UW{ z6=;pgL{dZ|3&s5U@pKA0?$5h!&J>2uyST8Rr^FFea8xy!<2_Ve8CS?#eaa5CTD}o| zr|jys(%QY+aAcr>pCiz;5Zqr-3oRA?5gO8JMtou81=XB! z&r%kBc*~I`@7|#cBvS*2K_(e?))z>+_g-ViocNI0X*Cs~!zqf^vrtoi)H8zy6k~ia z?`np(Z#QGx(yJTLnfTp44dc;8E_@05?#HPE#Dpin6u-&bCF*(xxdvsEl7?C$n>s=`bhywyxay>(I{ z?s_;yN_IAtHxaqHtz$n^Ijf7Ykh{eJlIHFz*UJm#(3C7FDA+tPEoBfLdi4&{MYL-} zBQ#K@up4n`7Q4E-?v{d{@+jh`#1}8KYV!?l zaX7OoxW}@Bnwq+`P#a!DPXW^rOJGuh>?fSiR_Pz`nkWz#`!cY<8?VQH{7Wd4F%!mL zztVM%CjtVn=xB=IxY9qY*EP)hk=&u=SZ8UB%i+2C@JO(TBXO8JsW|09SmxMtKaRX* zmY!)4(T(Jk*p^nORbc%p7DOPC4;Y2fXI0fKj2!BIr_ucznCy+u$8e{HiZ2u1wdR^m)y+&h|<=@Sh-Ch!#oQvpZd5`j+b)nA&HDUu8u=VAtAe%aSiG4Fn;% zWf=@`RVH^9+1yhbVc&$Z^p%fHmB1Tmbv;>uxwAO$LjIt8{soKlFpzgKX9EcqKXl(4 ziMcq#GVAc$-vf&HcF5$}VLsb#EK;amvu@&yyQdim_S(4L zJtI3i+zGl-){n$~V48+54w8VhSFf&#=en+0+p%?YA0yx=@Rua$9<;nTex>VqEbw!k z+Vs>#bAj?~J%4)sIdqX2#Xx)sq0tnTzfha^!K0W-7y;d-gx_dq>hle$M42Nvdw4^c-X6e!J(`|igCAoJk5bW zoHL-G(CW4Ktsm$o=uQ1f4G}mP4)^#g#M z9g$cWz<{4%{O)e}zsHS8u=zI*Ur|GY!`8=qT3-Maa(d4Gq1XK2^gKEAcBZh{V*~_ZlwI6EqG(7Qnn*tG=IOp@ zKwFD}Q5}UxZjbd22lmCF*c8j62?c;mK4|5G4g)4Z z@V`%=KBdRrFF!}R2qNl`D*}Vm#MoHTP+dK$Wg?^tblmkMF+;#Jyl~+H^&&*b04N0b z_%vsKaTWC}F}QluLS1+-vPy)+-R0p=tVI-AQ&~0`l4EbZi8|Wa8o)`&a`5w)l$Mqj z6tv&@CoeY_>PKL665_+_A;OVLCP}idRHfK}Q-+%_vak@{!F}@)>a)^95b?BFQ zKsHarJygb3=uXwt)U>wF{r+0zb=1GW7<{&hBkcLuCX=+zlgrj;vYj^W3>=SEV%!r? z&$OFs>B=*JxCSdlT70(bz^|Dwo5Z*c-`W}B{g0Hi)`uI;nxG^i8l``-K6{S*UDkG9 zJ8&!S<{K*kw|y2h)n$~UB1`;@u-4Mb0yVXkHkd`gce64R8Ux6GtsBTt-XDCc<5f9m zugr7xYNy;96f_{z0vA-R&l(B+L&e3#qoboh`X+&Jq|bo>kmV~b`tLcWZh@EI8^2bu zwHVaMpS~#jWiI2_R}Ui5(tD2#IFkk`c4oQVg(0)01q(H3P@~VJ`m@BR1=z))-9F;s(c6>8Z4vx3d*4zU_=4MA(Yti z{-02i7nbwbo&y;+ss^M57Ajyv#J+T}EP=O*?Mv1C2VkKP098my8KZc1zFRvv6-$~* zN`403nz&ANt{9HakoN_wA0yFFZOP=8iB{o5&a^1gA`{Qyr2@MbBj5?c>)N`yq>x`F zIjStQH7wCo(YK{~1c?!^zCiNtxx@1<=#$#*LIM=`9J{&QUyxj6yBv!~bx5AqjLBIF zl-PNUnC@uFJsf9M27?4daF|MVw#DLD1Jnf`+M?y`<@nOm(i}aB6*TWfuQ^&2S6xrK z0u&!@mv7)tki2Z*#-rU`Kf;$R2ZHku;D9kM-W?8-{WjSH-s@#UkJ~qT%}<<0(16pmBYY9a z``Eph<7n~11QmI~*)2m|ra#}-?x#rk{|A2Zr&K_fP%;!2Yb2{->AFc3sV` z+X2t{v!o4h#eS5)ITSN#?TA;4OGT#)Zr@F{U9jNk!gYkGL&!8U-CH%H+``Qc)7Bz? zR>3Wc=I@_1cxR64QQ54zTor-kG*65UCjRrmli1NZYcA!NMXOdN$J#+wWqElSYGk(o zJpAjIKKU^C64fmY9uM>-p;{j#mO8El_j^nPmHJ<`YG4J__~@_SYwGtUQ8pY|r~dRG zwRezPP(U%?sa!mu6TO@#QVkE}^EVC?QOlO0l9X2=wIb%1D)e6J_-`M{bmAJ*@-4p_ z^g3kPwRCM+>FF|(l5e}Gd7{i3UUGv6DUIVb+J{uVcf+-V0Yr7w^o<7FX@wI*)gB~+ z__}<(4q77$XNz5V891yx3zgwSZ4bX^|D5Y{F5ObD9@m-L;{T+p=tf3H7Jbw3D_)vJ zF3NQk*uoIYHb=(BHf}^BL;~>SNbGifKqY65OMvMFVVuxM(og26*xW{5tcl*m;yR!q zp!ofW5>>}U@wuZuYfJ4FGu&r}+0%qh&4d3eBPuB~F6?#^M&@pJ1MD$((5-VQ{!1+; z*@+`#LYG?;3oPV2bB0AMvY{~8T4VPzyJl{&jfjW{*afjV0^)59babO|zK18ub#v8f z!Vju&r7H~b-9x{K1|LWAb>tM()$`7|S{Af94+Fi%IL)wH9m_Hv#}_P?M&?Ed>~LWCgWXZw{m-h^qXS6l#)v?ZCq3@-Pu-W z)o{G7uF=k8(RcO)g-W^g=>B}qSy#hYKe>ii`J-(eT3WmbxK(qciPKX@AgQgU#Rn3` zvBnst81VGLyR?ujyB{m!iIjHr#Dv3aZ&ZRG7mHc^GM3Q&-|m*h5%1F`dWV~;tMP#2 zH|6c>jGcUR=!!4#B`e?wMMk_WH!D&Lt{sfHw9M*%rBBk|$66Ah(QSPGOlsUB5h#@8 zHoFq(EvgTNw3j3`$LAm2EF&`?r^S_-8kV zmZy(mC^0qXHY`f)1xI+IrqnFoD&eDco@InGofQBURMZE9Jjvmbe*g9vTHlsFJLMuS zAfmON*;*K-plhT4gf(d{&o-1qWi&;s^toi0mAP$ZIs*2-(DeE9XDH(Al50#Te{Apz zmw!|6R4;UV_!YduuDZ>0;H~JJ|Mj&aGjy zlcZRNl$F{3Y$1ue>gwIi%^Z!WO*rVI%d`Mmc;Il&TGcPG@s^tc_Pt!;0yK%YI+wfJ^9O4Ruq9KFK^Eo(ZrzAlb)!nr3?-=^vFx`Fe? z6D8I=8kSnT=5diSU&Z_@pO`0{Q`X(wS+;RuI!9#)t6s>8GuUWOW!WC4f@f-klY3s5 z3M6CCRJbj7>0|e)G#m^ zQzq{4FjI*V!Q^J7=-MxrVembHQkbbxe63Zn{S~vCoAera|Fcsjp|Sm2tZ;t0=t#D$ z(E4{uY>o(=`ew;P7jGwA|P9D-%esFr5G@%esl54Qd5bs9Rr;P(^2Y z9L{%kZ@W^#TeEne7JIVAq30xhIm2;dLPKH~#r`xUDn;VInR`UFt~0#h+qG@6>9}P( zebO~dK6~!yD=TlR{A`n zS2N7x%V#ZDcGU#6?z3GlU`G#q!n`Ta+rm!tlxNpaWfYZ_?Np!L*x97hA4sVF}3PC2rYc z-nYY5C24EJN!hyjPrF+%1Ux^?^qoFO8lp9PQzjw%46PuYFCp9H=0(dL=Cj!uBP?sm z;QD^}SRiD{iZdA;GiB;4DcsDdA*+~sx?_^#>h9r)ZQUNY7vlK&zZ~~bgKFWUUr_WJ zj5#mQd6e#q3s)glsDiOHvudHI4IozDYAnFrEH^)kZfbsv-a zkT;=W({}dTx7%}bO_`m>(`+oGTUXJy8>No#mXP+ya_lV!f!y@%&DLc5-3`8Zv%j0LTG}PN(d6oMS(=$;`n`Jn zdg%P`FZT7_{Le2Ag@qA!*#m)0POd?HU_~;5R^=4OQ4K>whBm|2-$5@BlY{GgsxvU% z-C{XpSt zA_TO46rH=DS*KXLetYqKX zVQ^>?TmhENjspokuST$;yiM6}M&jjD(COt(v{6FjdrkA$4ZV)l?N6O znFM;?+3A4}xoBRS3=|x_W`neYTH^x{As%}dlZ4YH;TaR<=&wq_e}9^BUoHl|D!q#n zuwK0QykJRE{X~APr1TEhb=cmLen!efvSuIC*bgfIhkkTj)QV2^={_IOBoT(*M^FHQirl0j=ae#3 zewt;j^xC!vuJqVI=`ZKbP=}_AStp8uii&MIDT&_b%9SfJ>drl5x6Gm%k}B9by1I&i z+B5+K2S`34amGXkJ+!j&P$C7br8k|ODv_Lmg1w8HRzPuprpq?tP*OcrZW5d^@W*Uf zHHsA4zY1G2DfM0FFoe$RTen_xKUY&%KYi-dNE^I@cklSj=w7FCBr^jS%+!$r+iAnLZv)|fEe%!6;$p4Xyp5}$? z3HV1y=ulESYyTXVC#B@>^?y*k1A~bq#KomIhVYu7J$rU}R=EyQd`JUY zJE?AR74CM`_UHli{3u+PlZ6**T328s)+eb35>ic=S$6ck>?c4uWu zv%GF+6GuBO8hpDDe8H*vj?uFavT`hsKt4<5&B|L&ua_lVRqWF;Gvx|OoN>l-BeKgM z73O5cIlgB(HPU+zI=w>6QT*d`C+91iMlAUfU!B}f`>UJ4s%K5tW%;){zo_+KSfVpj zy56+2?QqR@u%*vZi7oF8X_YeO%%U=SWCh6(#)x^L@sWI;EBltm7{*Y{F#u52)z(Hr zJ!A=b5c^wvL(+j~rd`_&lw5Jb1v3}B;_5>dYY{diY_Z0aGel2zBR(UuFU!`@hEgo=_-_J*?- zr=!fHv2xxUUaRQLr6pUVbha(&Kr}E@Ls_W!Y$c<#{KUw5dwYi%K$7bPQr-s~kj0-) zCM;srd(g`$-*esE2C`-e39n@q4IEM?&#eJP7ddn*v7lFi5Q&Dm`sDIatVr7}H!_Wl ze=PuxD@5i$YYmod8mF+Z)^@RO%?X@5PQ;<9>~asKnHfC|X)KWovfZ$!TbWQ9B*jE^ z(u!ZfM0+{U2~!zMdGj@5h0o&u_l80gm5l)COPwX$nKBgSgf+B1=Oqk!ec8@JE#KWO zonbLiU#?6SGAF)Z!0#}lfqNr@5sd<{7Iw^({RW&6iC%-a6eIw$-HsI|-0+M)$43{y z(Aox8BM5f?_`_x%ZGv|quTwoR?Qb(x3QhsUz47`HYm^On{Q9f> z+CF^)gDF_4R=bpH4zw=SyVZ;0SuPi_^uI?sjv7I7~@DC{8~zL!=?)j@$lyKZ!_tbr_@yHIT4JP5Qt8!Dfz{61 zQrrMc2&mIQ;5NuhOiC)q&j-zo$+v2m;9nj(yyxfR^W&!ZEX!4|HM+}WsJFX>bY8#< z7|@*3Z$lFiC#y0Ena@_w*=y>iYV$19-f?XU^E1xWbqYwttO z7LWMuQfkDtwBp_o38ZXzpCWJMiJz~w*gJvXCT(4ADu*y2+MYA^V2vT3Yx(DIFdXpnL zE8R4Z^Nc8Xt!bfKLu=)vRj^xlj5S|OZW<8cria@Xu%LB7%&KuQ#*<2D{O4ei7lhz& z5BfvT${&d4Gz?gR^S=x8@@ym#jp^USNYl1R;PELbwRLnb5P)2hM$ZawuP555g}y6g z8&xoP8#G|dU0qA-ixIwpam7kWxtfQ#xMz_MmtF@_Y*E%foqOq;ou5{>8^{(f~1XVzJ36qX-)BWE+(6ci>OVn*z08EqZ`Bq|IF*{>|LS8py6lDa~ zUlWZ@OfpgFDY@WJ;zUZ$nswIhlCUBsTdzI-(O2_+Bu8g@EX|u}QuuX7FL&GNlPBZN z)9vl;oz?W^^zfY&qM>!YuP)kIIdtQ`ERS!O-zgXI#ijb_Ay ztx$aP5E7_E1igvz0h4mSuzPZHQbt-j!3JsuV;@zOl;{@gv~VDcR*z6BMK3XW2L>v~ zfQklGw?f5l-V7+naQ-K(^G$h+rB(kpv?dUQ^SLaWYG_1XbbzRCxlE_)zOr(7tucsJ z9y@-#&EpuHuUGn`&eAj24FV2-KS-9SQ!&PU?W3>7mwu7VqWE~2Q~;(99XZ1EVUefe z`py%>_&0B8Ad2IwzO1j5c4x^NB&Kp3K{Dn(`E7>?$`dufV?y7otagjDQGi8qiI;b6 zm(g$g{VS9Ql?;I5^l7c6;n(NKxL$)yZsl_gCbZ%NR+1tmt3dTKJq0L_WK5-=cVD z>e*mT5W~T}ipzSSug#CkL-RN~sCG*O16M}%XOCM5?if?Nn^+Y_~msV&dR! z;kUv>cX)3!yEOIP^2q~sMmFP1Tg_MUd4l_I{+#=2;0z!%ay*jhgRa^z`E3V)^GU@C#a0X>6%rpSn& zKp>Mp5@}=<7e}Mfe=si6(71ez$bt5(VqJYeIO;dPAjeka*Km0nVF%_(1nG%)hhFI znJRATl<@6er4yjYe(|Qh0SBrBu?s5er(Qrc9Pp6cdkQX0HQk?{Kl=5%IXWrn(}@2- z@liKMs**B$7rcR#?0(m5hIOgB%~u4&&jW6q@kC>CWRSL5yin-U@cfc@@56rf+b4&8 ze2x`6o0^&;diEKeQ!lsQ!6`rNDf8%|Nf-PBxP(z*bKX{{kpg0M3#dDO$^ZISNh0&| zFOw!T==zazCdtRe#>8aCHN4eg*=UwwgDh4Kve+hQ5~{iBt>rWt`a(U&`13q8TR|$= zUuY3m*a-hXK)TD6r=&Ob`P-HmU0HdGMl92WS2fBXfJ}ZD00*? zIUX~2x3(sfy$lPZj{!Z#5bhOHab@l!s%@zb<87(upx2Hy$~rk)Y;oRy&E(Z47Wq;9I0=0E|SC6Sv?QOPEHaK zzAC)?HM`v`o1o)O47w*Eq9DlPh?^>LBv3oD-Erwio+K|S`ndFxM6IWpf-c!Hg8~{2XPgb?ZM(72im1$TNol&clUCxb1qXk?6(n6-`(bY>9s0;^XoJljV)fSZTzjW_gDmd<`eLKc;^4m?dzJShPkwyENo>V@4s>J!F z7Wuk#c|lwV8uuM6@>j#;PWA z-f!sZy`XsL+S?(JM>5i)xCXjfC&#voyPAYHWN}Rq?7CAS0Bd?e0pT$=jEkArxA5i` zrHtKyJ5hPr*-d8Z#$)CC1PT*plz9qCF7I%?%!fTgw%JDoomHl&F4ftjyMND`Xc2rysk+n~r-+E?Dua~~ zzH?S*OUtnClmOuyZRu5x1Y{e* z(HiqY8ABk5ph17o%domnGcOARkhS{s0xmXIALto4dkCm>3XFi) z?P|XqWP-+)ul=XPWZ;4Grx@Z~U$28;#n?9`o!ZRE7Q@Ze8sT%|uLHmObY!T$5i)iG z*;pfH)!eF)cm1Q*sZB9yO-b)Ec|aW1%~YlJ78&%mVbZ~fefbXRPTX4x>WXzf?`t_=xeTZ$(;vcoGN^XohDYMk|nmzT^2&?Z0^ zpQFd55_g}0rHKYIctF5V=}K-VR0|TR;M9`3Hij_$?EEzX3H__5`p1{pS>pmL5^g-b z77$T*hAfRJ%Cu1+*v*ygmt6zOnbmofdC-1KHBbFtiLdm}#8)`Co=OuLxIP{>{?5?d z*T=ATKJ;a6M7?}jHD6W2!NDP*%v6_@knlN8&$Fng2!i%$rEikH)zmRaW34KL!BH*3 z-&3G}h>J^c?4(?fQ&oT-9mt-7)9|EOSw^#vMwsDQU6?jfj|KFIY{{kCS@rfd{lJ8FatZemtm2o+ZI=CSnE3elM_JyO z7i#wR&Q4E<%E3uHhc%7h=;GhI+S|CAW^>Dt|& zcw;$ZQS}Juyc?iA2~ybm{9tQu{H+R^jGenkwz~U;pM55t2Gys@a374)n&!R=BZa_U z0Sa6?Pn$pFZTJ0WfB(;v6zG&d6Z;8&?`GVPP5-(O$!&kdT|atN;Pjw%{D((Z(3v~$ zHa#TCU;Sz@L7u;_O#)Rn!hkSJ>ZFeU*^Qgw_f{*{c$=lbM;2aO_Q}bjnCF4l+ab3R%bcUH3U!@Av2X`F$VX{?xhe`!%om zyq?#qjN&4D%4Xa6(+9XP19Bo75LEx`7e`QW{^%}+wIu+QASrB8<_2Dii~>2(Ijd$2 z2L?_(#Xvy2v$GS3+G_v%3U)NsXC{HsJoxg;$@gG^ZE{u+Eds=NL69WIX*X!NGe1?& zivzg?WO<2Jp%3yEC52r~Ftj1TaoVrmRoYhzQjG{BIFaE<%CJ*A+ zBFA(MaMmL?R_j-lEShnC8)x4hU9}N(S!x8jn~aXw*p0)~28`mUb-yFd8-X)Nl4U?+ zH;?2w5vA zWap&a(N}aXc*n7pRLBgdiaIzrXlp0IP9zg6hDu3|Nj-Q z0^!o$+T;4c2m#PrPnM)%OmFGh_dW@H`tq`mfPj@(UTTAQ-7Yh}I8AgtGJak)rzJvx9$wzU*n6rPJwf{ z(fnTK9&QFqC71kuz>R`j^*Q9XO-c?NYKL52pdpNijpYdN_a{${lhOiO&3TjRhK62{ zsD&L+mjgk#!!$JM`mQ-TUov?bpl-(Ky-6{fY7+b_E9*2X;F*J75y>}IRgXe#h#(R4 zh!p5RU1Fq44m+pB<*Urj#<|!&Gj+`(+h*<#gpwfA|=d}J^Bcr_q%I@Di z+JUo3E=J|lIR{QouT*tq7X@t7EiD@MV%0CBZ#+;}ubMJ5V=azlws!s~>rEEF_UVGt z4{jr9<&uj42WY1&H;Q*<^R7?!R-n8l%nC(T`=*m`1;}gqKH7yIKb$Z**72;Pi4`(P z^CwF?u0fK%&~)DD`}{afO*#$Nx(7ke0giZ0`s`wD7^BajhUi64Kw^07;Z_!>X@Z^S z>iJDfOh9%3y~2ktzBmg=drYL(_F#*Pij;d<`-_b6Y(~{ujkgh7)R%W*zsp%nRz&{> z1mp;KK$oU^Ju-nHPY^mLG&CQ=UN?2jPWPah{9XnZpE!4H;n-Bx9nrEaP%863uNDHs zSwQ{IFdX;%_D&g%U(8o1WljfxEEv~)HRJO$9{%>utlhG=DLGH@;zmuUX^`+juid0m zwVCJ1oG4!)UWAUDGEEMn$rc-5cjl<2IArmP`RfY<7qW;Hh7Pg@YylST&Y22M`rV?g zXCfX-Y8ir;Sq^wStHOSW)4^^N%yH(iQ3T&@Y%=(U#3`2aPjJbh=)_h+S%lmZr!&*& zD=vj+$*12Z0TeeiX|E;&`wI060PRav6mbO@F^7AOa*mq?3H|4cw05TAof0KeI-8S7 z+*%zWFMp88wU(#s6X9%pv4_Y@MvTNbS()1rx58bynfSYmuT2>1@ zFp%CFi$7T-6(k-hu0DURE)pV5`R5JopUAvVr3FE4zBMFly0eH2IOg?~z@Y!mU;*qA z=H}*(j?12VX+CBwA!S5l9Tdh@`Z80e%jkC^+QIKAu}&7WADZ3mrFtd_Bcx=Ts3a&e zN4jk%V_P?dpXWCkt}^@4R-Jt zU)LR;Xq>f2sVHYN&|_^RM^tbGz$qUb6i^xl@Jhp_vKzF3AG^D39XNKlp|{X1mDk#a zQ9i5ov@m!n6h`f))Ml|wd9KcHAIHmU{DXseZa9~D}WD9?)~WXKS|@legvHqzv4O&pt!c^8>Yk{ zOi=laPGSoKbW#d0@BRGF6hQb5=)B4;UN@=gAa4rS7eAGkH;p$q3x{SD0Cl(GA5dq+ z@=x#Gxj$1r{tY_4eC`vM+xIJ8MPDj_o2yan>7;$7blH6Cf#vkLqyl>j1?9FKjk?~t zY5rn#m=(p6s4En5CL{5cz!(m7+nh>_rPQLW41~cls!C|B^~Ej;3VEpAl|B69&CWaHP&96SlxlEkh=16@tx3i8>Qdmvx9s7^^sbaM z8f#6tP8J-#seZFI_Tt914YE6CI|j>7#y1ELH;I&V*c9|sWUkJ9czUvkXLz#6yW;!R zT*5UgH@D{tkBp##ui;C+?3d4=yA=6-@sawkgM-h#3&1B9mX`7RUN^C?8)UNGt5Ke2 znM&p$Mw|J#gmc)M};`@_f{ zw-xT2Q`)PVGlr^GD`5+-He&5pvMgvA!;Pk=7Zh$3xAtw~4Tz=}guiApH0JYOkBpy@ z1yM22JpuBa(cvp<>_g`JUgB4svs^6*Tq)ib`zvSSE%MECD%RhOD;~{@~n@D@2dQu6+V<>>F*Y_G$F1#n(M7d*X|2KV+PXe>&Pe zBwzF>WnG6&3UbtcS&jG)OO!1)*7+zpeci2BT$11Lug+1Vy+PpV@Q-)o;ZNK0-1bLQ z>CU*IjiH(BJ8w-ZvyDUM08tfr4X#@d9|J`%!>4H~ zn~bVI9grLIC0 zvBZ7o`0kKx>A=DE+j`%15Pjp&ZCv6tP#f4+SnQ2gas^&kZF7 zFZ*vMCMS!xszU(%)~&hLcf&MVkDO6oAAi1n@AQpBSO{H0x^Hu;C&g5_C4s1mR!4Pm za`HI_U|@{0E_P~_3)-(wBm|R50xAor-v@u&_g5cMg+Q}L^5NUoe-OwKeX<+#yY*x@ zRt03$euZl{JqbVq{dTU1J4$3=`{{1|Z-4oP-X@;b)Jt3T{E5>d9bp|Q0Ty6#*hDRF z`VgY5!+)e5iSEqbvFXjOq=^T{YBRC;metxLv zSEhx4h-}I&189Z{cpghkRUrLEDBV=TTsGHk5 zlKIC2P|8Fdo^$eB7loSa;+~a!iZo!IlT>9Rpl#Z4ZUH9D~ zE!uj#gPmge*r7wu>(vz%q3q|GmKo4~8Q-l&Ac{2>|HqS~=@65Xs zbQdPQ5ATboc>>vq77?5?z#o!tYG_PCj_w2>-vT5P(l|O5uOqX0-Omc5 zyMAg+4>c=5PF%hEpx(gd0-b<>03b(13;F>NVN~bTA_BQJJx_Exe)O)>B@Y7NKOhkf zko`^mscV>+Un3>nHh-$B?-@()SXiE~pJyX!7Z#4M6jwK+5JW#*G5+%H&nadFT1E{k zy^i+lSr>8M`W+f&(SPFNH-Gth7~a)dUcve8>0{@(G8SuzZvh)F13kTy#M@6qHf*^{ zy3lx*$BpLu+?l3Pi_;|{-;_^WDY-(a;e+JENR&#OQu~s~CFW*1^Z2YwtgdyHJG>TpN8kHGi3vHu@LbM1GT-`s?quCnJh=k$VL>K&&FyA{e3gS0{ zhKDyAmifQ+9M=(=d-{}8!~Z|u-XY>dD&zo}miA$?b!MHg20@U;y_;#M&3nkKG3mB0 z5~nQJ)4%9@|3Ck0G-+PhR52c_5pU-PAr^&+_@@whIqSP5G;SRTfx!- zv~&{<^1uBF#0-#DB30Z-jd0?Jz^%j?Z$^NTT;%3wpS}87v<=Cyp#j`R=tJ zA#nl(P!LeWrlxW`Ebe$(?hbg1{4YXBo7|3m%F3wK$+cf4ZPBr zAU>3s%-w@kF=5>0i{jt_s386&CZ`p`GR{L!??9t~0IHy9U^A#q+Ue`f@-i0R`k}#swiE zA#e~SIE+k8Yy@Kg#&qJu6<5oS?;%a*8$wVzYHEsQXX-@y48;QWvPm~bMMp;mr0-DS zSCR~-$WRGYI+oq(&=MFC9`3d_uGO~R`Uo0phVV@fJ}F>gW|qQ?t5>*`M9x9PwV)wJ zx(pIHw6pdQt6{b=jy|Ve!sV?|?ljdU@a6GcV@B4+!sw<-=(z^TDfI|p9{>4Wi$7`M zHp5ac?DlVrj0&UGaebDLsvh}1YB-|i&1VDfjZm^dfzi>?o*N%CSOf(LNNMc>Eu>;J zHdjEu8p7-fg)caM@k-L~LYlj_L6<0s4~cfGm|j9_Uc;q}Bge}riqq4_0T6+Za1!nS zAr(&*iHIa)4!S5by~JWe%WuU3ITw$$(c!~~p}ACzo~)UT7iw`WyRQG__vi4yvyH5q zK=aYv?W(a*eYRCMKSdG9+;n#A`gu2u7awP+bU+RZa-mJvWn|6=BeasiA?x?cQlonJ z{Yfk-Gd4MiuT&%Ikgo!2;+CHeA3S|fxoYMmJx$HVSC9QW=#z?M9=!$bv}7J;-r2spRN*&R(;yS>Kq9-K(yszh3-h*TI{T`ZXUX(!6{P!h@e4 zRp2~WVo;b_k~tnV=&8b_&`h>l@|5ViV?77YGx>;71TY#HD6*qEqsEOf?c3X)71(yK zkCwjQNVjla;Dy`Mcj=>0!|+Pqob}L0f$71Ii?14Do}N?FC+9aW?<`19&3$kS)tc70 z#KA*1zQdUH%MP;0k7S}G+4{x1OXD1BVR2Ep_Vo2P-%({9wN6Eco%0QG&v{|=&_Qp{ zzJ09>Pdvx+a!=;Kf^|@e*Xf_PEpK!);iD)@4KglfZ=p3;kgS?SXlhQM7Rxg~rVSyE z)*h+s-bZKRe?Z+Ym_||jw3(^Xaw`FC&pQh|WskL={y&cf&EMMuF@THjJ8HLV*)nDw zk7f7LZS>Wrf%FY>gWXn(*Z3VEOWK)wi)D>6c@}z!73tvk=5JC&Faxsboe>kglFC?O z<%a@0oI+3{DjW9&+{b?*>5EuHU|+(BPSXS-6=>y}P!tZbOUn;ne#W{5Sp zdr#Hke_EozprDA{ky?a*=b7u%W_dL+G@eFm@18yR=UrV~e$XgSQYlU#4Fv&7@8{#w z$N=lsQ(zL&5xS=I2B4ZiliHq2)!8C(Ug+e>ejJTfeyU!;R3Q~K7T%Xt8^SC@Se;7!of>6_~WH$`%C6KJo3DK#{r&R7MlSKetrL=;G zX4r#(-j3_ifK+}N^(eq0aY3m88uYZgOm$r2!JWnViDg12D&HG8&0QtW0RAxdeDVzf zi|@vxF2MqwXoBJ5Git^>m;s^O~Z9mwhl6Ox$;r+Km;nB z14UGPw*BvRoB8xb5rTp{O%cC@AXS>|zOjxVVI_;|u8GkD5|o`jc`ZA^;dm5{(y|MP1$D=Lye!J@u^&T?u1Vpt6U3c z+l%n#c^x7d2a#O?RArZ|05$y>n0g_$!dKqCi{pvLORaO6-OYRVynC6xwwl)K&liYJz3BVOEk$?4Va5hXylEn0L>m77gy__J-#6WjQ&%!7FZ}E4#Q$0Y zp0(cgqv2)n$IDJz{@f3k zzc&ThW6eJX2&fg)d+_e-T$>w>OimX^_SeI(s?lCsvuO4mBRgLHhai)wy5F|7$c ztgb}6H#zgtT`(oP_v0UOCL~4cfysFlxx39jC{7Dmlh?2aNi?^i`ue@^u`b_+`1mWu zYSqQ2O4L01z;9!X6_^#Ax8SFB3^+$5!#|6XQhwS&3{-?=56 zmeK9M7PTn1f5T^HX*k9gaHW!ae@>5R{;A}Yspc%)G)^HEcKx+Z=m{^h=}rG}e#8^1 zo|&uKi{A$xx;i+FKia4W6hraBH^M_e)jhW zaq&bB^RZ})7q(+99jR0sa{l!0ABroi7a<-AED{uxML@Ae9e9jCFogPS+ZNcRv!UdO z_AN`Hik6tfUO4koiC)>Vz+r7(g2Ao0EK$OuG*!cB)Bdy#>p`t;YgeE zRQSy)sJktv0xqfqFizIj*R|Z&&a-gb&IG!2C+#ywNvCXlQnjGdl=7POEO2PsUvz3| zn4X=k@XISsZE4B&-0R&kGKYtfW55n9=%{OzIKO*z5U6@{i~1o|qY40S$eKn%uyk?QZowe?{2pq*L|daTgcPvc(>g0=WxyiO`S!T~E1(UIO;^#*fuiiEpeuJtCv{ z@szP!?>hKz;`ac)H63GKqR_f~k8y!{x5ol%ghCb7X#pKm_U?6Ob)K1@uUIFbdK?CB zPNq%N^V>h3gv1v<-c3-F!{z&%=vJ)HtF?u6F@5Ds|Hmu~BL~k6`und8HrfowjkY(s zFHULQ9cxkJn}4&j65ZhREjU`=MB#COw8cv^29Zl&Dg+i#FUM*$OKnXyYEe~sx6~*& z)R34k1+Li5rl_215!&K~(+N9;{(EOCspKLp{YIYiG|ddG7zSOM5!155+4mehg=!bk z_#s!485w7oR9@j5;Y%rXg5{3G<$GB^yT|LiqHPaD!oxyb2d~-VG|oN0{cBTG?;~%& z5b42?Cp&!bCT+GjX`ly{Jh`mXxL>$M=^-FJQ(C-B_cPFO?M+Oy2)V%X5t(jJ}3bujaw_MbJC*H15|&=UDl>TVyi~iXSM-#c|FDn+4JzJDFy&_BJ;c z!r856J+GFb^!p6RWG*fd=z@DnWyYJ)?aT&}r2E%IogY#anY1b7%V=;Rolv?}(An;U zt^2!{>a-T1E;~9d(XVu<&3j(Is$S6d1H*V~=8v&~VUg(M^J4aV2$G{lQCEiL zu2OATU+HyEnOZ-uS8c0MUF%E0XE_-)JfTXm9J+zEHfS6TL!B~Z=*}NaV2K+^*H9?b z>Z7ULrCCncQXt<|Y(M=hswY8h!)awCF$kDuXy5pTJbCi17tC*n$_)vUflTsxLX_P> z**fL1Y~FYCKM$EVjoGw8xxnYC3(4-`*vFx~*FSzAioU17%G_sO}Y=&YLj$Gu^gHvR<7>jPGZP@5TW7gL%5g1WVG^(%W z7&?|Z`S$I}{oZL2k47D)3JBL|Y4PPZ?<$5ev3Y?pAh3q|jBDBbkh`p_1fUr-LHG!0 z!0TM(OhjeCppEMfAA37dV$*Qum|~ExLYn9O`)+|BWf(MI-jh4Me>{gaHp;jU`&}jr z4!W<6pSD3!+vb;4o93xX^i8f&+J<5JJ@&hm8#Rm8_7!+aNAzrb7S%{Td*_qJ=;&gq zC){o*qt0TJroiVpE$$=C)}gN;0d70~2UI{Oq`IDg#eVzm90uOcqu&U|7!?vn*uW|gUXY~&)6lCF%+VwYAgPW#_vOh-qDIV|-}5oS9rL>9X3txmS< ze(<_(*6VD69hn079%jD8Fvp3t3+@4AfBJj6OA8umj=B+k+?{M5@Ru(82X)Ftv=!fsqskzmpO=@uv9fH!f}R){(A}6rI%|~d12y8Akdy_elXf!* z>G+I5P7;0iE(p3GV9s$(c9qmOxY>EErG(-+UO4~Qb@)pN%NWxw*JSin7lp&hE!@Iu zm&~*XRH2K7M$I~dwY%10(pM{c;iKvsrE*#m9u%F z1wks(s-eZ8K`K}EgUmV^!Y8YH@G^O)ziHWSn3+YxCFew;q{6H7-nkW|k1U|hh=#i- zQ|9I+iglO}66NQ0aMQ1Db(iEhSlC|Kg6M#%P1Q|w_su_v(05tpSoS@Jz_xO_k_OhH zK58{0=RvM$$hWW;JAps`;hMDX@`-)Y|4aLgXOToW~C5Smo_hl?1%~?%gzd zXO%63W>0CRC)LchWeeF&q$tE)T8lSt)~vNwK;5 z4X5L`J!Z**;sklRpvz|_c$C)ZQ|Agaa9&waE=xX=UbOW`^%KC7j@}1(02PoSyvWJ= z_3ROpojb}-Oi5{e;tPJVbL0EXG28L3Nh<9PyL;>h83B)bhzYuJu}g0KYbo4+=rBj= zwsiQOU!UGcyLi_!z8AFv(e#LUM%?4fTWvMjHD7BE)Lu3>Q!6x0T}zA;D}OZshy5H+ zTIJFM9=oW9gyFl;j>-!4OF{2ln*C!v}pzV0J>pL{J%x6;Aw|jSLDpM5Y;lnM4 z^eSbp+6a2>qt7<7YQ)32KO`JlYcY>V+{7^AgSP>$1z6GoCIyHgWND!?do#|zt002@ zhQ$6MP4@B}LoGPoF@m;I7C69X;k8f<(6BXu!fm#O1sq&_`1NH7s&!mrL!qFc4EGY) zZCkuU0HL+MJRGmAMR;fj@sQ->D*><)wqE<_+4NU5Mojc8udK|UJ9jRtY)Viy1sFv+ zc)L79q3A3bVhcTBt#?e+qkjXxxD&A9l=O4yx`ktf1vm|8O}j!j)PJ_fA!CQ@toM0{WtAu9j6Fk9JU z86ShXJ;Wfj&B;{CUz=^Y9Tn*37h>JRlyrPU&>9EmkZTeW(%QA5tN=1`@-}VDj4gg{ zJ#p_I8S7zc>Ok7_QhR2V-zn_5fB(MmAQXs-@vPv0X$&L?UwumcOp{ zWDJ;O0!}|(_MDi1lA76N%)|O3?FU}kyQrwBgsRixb8T&)9U^Ft1xI1PQrmMOg9@n# zWI6QhvDa4O-TC|x+uqiuY=P@9XU@EE`}MuXL1YWK^L9PEg|f@voBICQ6%W9j5CTlwv#;y{WJ*VoB^(%i&-{Ge`7Be_Zg-Qqa1Llv zwBS55Gt=m{T4U)U3vKymB!U>AwcZ8<_n=xgf0}B1QNLKVYL8+3S-5hQeaeYQ38O&p zac^Ea(!DS+z@7946bsx=IN2qrxC>TSYd-`r7O83i29BDVIqfI|L#FE-`miDz>Fh#K z4&M(wwp$Ng#8ZzQ<<@!F@GZs8M;I zP9Ua54T)VXP3j{_)7v;;yhD>GNz?8li9e$BrGw=6V@{(BB#}(K4oIS9b=MR$dnHO? z;weRdx<4mHd`^WMMoYTDNP7T?TQIMHcS{uP6vCl*(klew*1T46r2qRRh8}AQ8J?+Y zn{wbdegl2srhM0F+bYseycoZlV>V?~BE?{3;6}z$gpH=z`Rfc%mhN^3zDPT^`C$jA z(r-drh;z8CyH9_)gWh7BNa-Mpef8U}duO3kh<^Gk%pL|6Nx9qldewcR5O4Cs>>q5x zXL$?9HP4#cTB-EUqudKKCd(-@-hH$qE#mKmkP!)$IYxfq-&ZI}w1nAY_*+t&#ggOt z`J0~h>vqu_$rgvTs0lSulRp@UBaFGO{?D(~<9#ddHs8v4=}1I={YKo|i*9oSrTxd> zZ_1r5B;|CSi-DiGWKj#X$M{9kF7>&v>!MsA{iQuTfAm|4F5@xo)zhoDJ=QhhnBAkY zIl1|Y*U8?qrg;7jJk}L!{U|3=P*V2vrX~e@90@I%3!*CeeNj_JmxTIlZ6D}PR?UQLrng`M(`jDl<4>Sf5eSuAL&ZN1y+|Y zgF75Ku@$l4e*3e;iv)}H+Zl)73=rs~RDLfaa`_`C{a;(428)SbW#QTR0gy`82 z*T3 z^G(Jl2w8u-?%+_|#?AWM^Aec~K}_mz6Dal!D!deB6S?-Mc?tL)Ts%oqVR* z5lGvO=YN7eeB^X69pt!^HYv*bjJ{h>rMr7M6?6bfn*}wUbO5+X=0cmAnjm&yvSByf z;|iqlNICWsXsiHPzW3ahVMGuC4kiQZmZY&3L%>m?FSN4F}k_vgjx6t(*0unFL&KqlsD+`n30|Q{99mFOF zRUsGDk%HB)hjQuh%re<d)5+QgkBlV)Pfx=9~j-)V_Ekqx$h~nn|y+QRbCFD3kW=U!x4+Q z-bMXe*5KvGw%fM6ys1Eaq6^r54!`>_(H0La^N^fUw%ue0;C@*F;c*8Pu0lJcjEb!W zT8x3m7Q&ukSIx~c9bS|x(V`6yTg$X|2g{AfXBk4e@5ygk&B#@;jq;mR4kby3yt7C? z9`Ho%>GXBA)8<^`P#6zIMD3=vE)ug{0mt}jud+sBauUm>l?F$jPvt&PJ9A8Vn)P~o z`r>iq6jS=Jys-XtBJaOPiUex`XU3faG%zsKB?6%%tJ0$P610-NX&78OCLQ4kmOzG{ zoPS4`8enl?Z^o|Xuj0${?RvRvI(gO4ojogW)?6D1O(1o1S?B0&C)#hC-O{+vpPG_b zZqgQ@zg_8(zkg^26;om5+)?5oy0Xvcf@KUzSUz>1nj3`)J{H#h>2kzqNAfT?e?9oo6P#GmZP)c3ArlUu3*Qtg4}h%!g(j!DGkh- z?w6NLx?jjdgKQ%n{RebSmR~QBbTVy~FR((L+KV%yi05%zU6Kr82GYvW2!HyEd^|h~ z7N36S9&J(kcI}dX!4b&8pu&a5YS?GY2ePYK$;&@>x^;OzPEweDat#Ex(%!9Gx6aFj zGMThxPPU{{{%2|whPbUe$iL6cU3B_k2su9XyLb7GtTb$O+01+heL8E03g~m*L9)vE z=?}2HE~T3ebdH(igNa7q>T1RLihtctcj34vw8}E>MP)X^5OVT)IXE&1P0CsIri{_~IB=evF=4z!#V(JBBBk#fiB0w6!hH zHHfrv1>bQ6apGh{ow#?`E+CN1j;H}A<%pY62`nKy`wc$GUS?Jb*l1vvT2gO~)ivpO zKz&s~d$2evoW6$dqV3pejXQ42XC<6z<{_-KX`A8S0_mYv8Xy2UP$*&2kjNnRu<77w zL_vElqj*2Q{C=-iPNLXE{ZGRe zqBW*FQ9BUZW%S9W=}t7<{}7VnwFh&+?_xvdMUl?FqRiZap%z^laoFD%b2~~ykEb0$JU2n;lk&@pC`?kcT_&J zrb9r0HEYhJMEPO)n0wF@doTG9f3enjHk5P%r!^c!${D$d#bK}+B-{3?)66^K zJR9?s?KDNyHF($c>g@;zHa(%A<`!d=J)M034xC+wQ-r|V4)-bK0mvSP-wb=1x$?k{ucYWZ37t_#?9JS!*XC#W(47mhIG-#l$0 zv@($gqqKS)S^H=b;=hOP}hJYJay{S&X^qAZ_~fc z(W}3oR8sPUCjkxDG!J+Y$he6kkKTFnU_02RPz)me(^C=M9q{O7_^t@Ui0U@**^YJk zc8`AWrhNq1haTHD4f`I)JBEfM^zgL(Yp4#fn1=q?(;x^o!$W^O4gN27)%=#L8aHW; zFV7LWUl4M17FT9YeBHN1R`0_gylKQW(R($?G>metm&wfQ;-(e{MCBtceubVTm*zg6 zg9Or#uv>iVE_*1vOG6lFDlga+tavt?&XpN8yVI=vh$)dY|KjrC_;s#_JNG5WG(T^v$?=tfRY`|zNa8om+(a6OKEvG_99Ek0VqEKM2CrrItEVh`+JwHVw zn^#}_A>LtPh9%y?;Q#MEv4ZCTH98of^&$&cg~t$o6EFjRCm%IHE%T{_Pq7YlJ*tX6q1m_JIZ#Si=*R#efhr~`;l zA;U(N?7MOxiRJ+@GX|~g$WR8Eq6fB(dXgfi88qHp1*Ua zW8tm7bB{)OrK#@d&7qlXY~sW~Eex9sVg)%VDl_H5RZr0RDS8&ptVNgKIjlJ|atE=< zvyxWywQt`}2$~>2UC~0h9XFn8QnERhC+6Q=NR!#4^iX4~;q!CvVglqR} z-&4;SoV08992`he`jRSeQTedz>%V@Ajr0$KaRTz;ltiFy^laD2Jtx0+on8IO= zzs7kRr{{t#-i=jR*{;FJ7!PFWX5FQDs+PvahSgS`Q|N{M$;s|q{W@?PkWgvzTNUp6 zQU{6g>a8cesZK%231uFRX7>zXk-PN$_11px>GDNQnOMt)IN_~3_nBT0Hh(rerhHvW z>d9=jc2%~{KVffu5gY*$W{5rl2D^$?h)T{A46DdOyKcZ}CZ`S#;_(XRKre~D@k~L5 z8;+G|Kt}ssGO}k+Z=2(gS}bdT6SP#u18;QIei8QP?P*HsU_<)w*S>|^0K|}2mX@k# zVQ(1(?^X{{eo8yQz|d1>Z&47-dh12LdAAxL6y7F=-yLt2X@#!yVY6>Mb|$k+vv&+3 ziR_iIa%p;}`LVF*XcHCGnhn$rjO9-*piQ;RLI%$l^h+=}o`z=65b;OCa-BsHQ8t$U zklov>gZC}5+C@gj8xbw^G&+IM1`)4-M34?J zxa)L%8)0o=ZWpq${3a@!;V|Bsp}=t7IFzvHi^_cLKIvKjd zm6C=fHG|ud_VVKWoeYmF73{t}UML(LA5S+<&b;kBT5-fwB^@dxd>mjc)32c?UUmeA zxAm)8jnx^Q>!(b4S0BZzyO5u>Z0=Zmh~bImE--;H+bFz8%mSvb1i;Nzu{uQ*j>Y$} zDyPpuq1?9FZu?%P{laiZX5|3`%ziUoF5I)I+b%j3S^+sj6BpJlAo1zg8HtyOFkAf& zu-;&IDEw@pv5I$|5-3pDQ*vf>0!O+vL&b6Q;`FOioh=OKv0)1%5Y|b491{Z$9lcU# zqct}2r^w+_K%>!68M=+3W^QsNT7>Y9QtlB|otSS$n(mz@%j}-`pUSOq!0jTzhczTl zy3n!zabHlBN)F-PU9ceV9I;edE;G1yK9raO^ruZ#K>Y zJzw=LQ#-xqxq)euhK?NArDSoxJIwWdaUmDZYioABP5$rdW&l_4bLi22#NqVlrJ^Vi zT4>M;biI@kgtR~quWy{(IXmYviT1;SPfcZ@qmz@dS3Tva5B~i5{mfz-UzIo~jf#XR zQErYkxl7yk4rr=e7#P%-9JlG3wPX@HU4DREzqxQ1?Y_oNS`4=XkE}a~Fqm<|!sXf7 zkAJOOiMv3bTOV)d4WH=q=e6Th!s{s+V`;|)(dw$Ij^Dq&L1(Bvb$96~&v39B8grE9 z8Ro=@S8Eb;WiANByz>&x!NglDmaHqobgfEL778M z%RJk74)D-n*6+qR3jNZV#5LbEZ#E64tz`c;jKIn`XU2qBiGP?AH!!;&94|kBSVZ@z zoXNr4%+`64vF*xZ5Fbd48%Rqa=6f5r`8$$|M&E#|`r7wt;UUCKM0D=Ienxs7qQ-x{ zjij5qX%OA~(-QkX47Ip`DgtN}JUzi_StbwykDeQXpjxi{I^5$Gt*d*006q52%&@-Q zHa7@~b{H?TOkCzy)9MU`T%DrZx9VU}_2=DoKW!D$(gy1Ts^iqieOvcaMDKm|DymS2 zkb!^BQ;;b)xqy%C{NVeP2APF6fD>N(zQJb{-)V^JuR?mEv$7uC8F^Ow0lPn>D`z_( zx{{^I;d%6&L5t!Mc;}e(Svu7mbcJOfME0g$-b@_p_ASUglT&QZ@vgmsQC+NOtO)s* z`7;w`Xkh=R&3ms@Ehk{>qZP>nz)dS$Lc*WW8}~DKpQUH?MKL*7yDIvyDn2=&*e($!6=Wq@XIQ0s6aj0LO(|F>s%#f+0qK=uj@i1EX1h4*V=PccY~Va8!O~ zGty#|*eeq=47du)9Po-1fxyAkY~*u-f}NscwA-Oc& z6gkE@saoo9B~4=xs%+K8EqvgBa!1#qc2f>OKtODf5EOe1{jrgH%)zwX6w;XW(6-!T z=l<49x>{TkyMCV8`$#6uTpxGmQ_N^bLHdQ)kbWQ3vf5w%?TF?nJUa4%RX^08gcZHq ze;ZSRhS1?FK-#lHz-Y&|Z7)4@Pw8I)_xR?M7s5B$sE-{pDL#L*-{0RK47JFX4?0Zq z-TF-r!M0e7tDQKQh71csmkKOGOEN6}U5>rHWt`JV&Ozj!?(7(p${ zVnx5C4xK$u*jBJ0uc!A7stv@QKgEk=x!(mqKLn}0lxeo7-rE&aDi@hZP{AgNqxO-L zD{kkOw6*O{nkD3>c_b3P0VVT^BbQu?n=zg{w{K5nj=$tu%77Z0wsquSGsrt{CNV^n zXYJ6^-=6JA01!ozB~%E_c_h5}*ZNNB;&SB;Hev;B1s!LPK=KOLZ@%1xYe-sO9w3l$V#+qo%LZ zPmZ6e7#<%-tutdj^WqN@irAuF>BtrtsBlYQUJm5kT=aNTd+@Rn~guQuDi_KH*If;Y^QxKp3p?mX# zZ^7(rrZ_G}Ek_bJ5wG+?x5^;TnmpYE*I|}9KDg`6pW^L}4A$Z8!`(ddZBs@`k-(_K zuI{=!rSjwHtIy*&dtSPpxV24T<$7Mi?WN^qdSNF;b@7dYc4&tK=l#o>mkWlFCV_s$ z6N<{@O2$if&gL(d%6w&>yJ;Wd9y;npqjhNRBeeMLVo zQc_a$?(WypE7`aGP_W~@h1tU^-Q=obOhQ8HWscL24xTb~W2?D#w$P%NGw#&$ zu;Ac?%4NjSf-TiIiF#lh1n_MUXu_PDd8GA&WkR!-LDT16*%GJ|aRl>;Z7(EC0_g;F z6dYYeM(ppXFkV?R&L^;89hvYNv1QO~G9)B%Bho5S=|<2W11HykRFtV>d$#u2b1Pwn z6Tfr<;++?JPMk}n|a7UhXuX0G-#)qh_EQs#;L;cCThWr%x zUjqXJsYlRVirjbby~zzOYA%drBgYVZ_S;Z!-d1sqS5jtmbrk`+i)8iW7n|a+dGu)OTgR{;9>rT7wI?RK?X zO1uu~bHvBnd`tACs@}wAQ8lPokMT@1x)ke1Y}_4LVqCsxKoBWG-#|u2`Vyj!Ep1R} zsfo_n6&T6Nvb+26=jlVO>%zll5wk#^bu+{fbid5({s*<0Z{OSvBOVp_RUvvEY;$a+ z4+XOFWW|+K8{eIA6}_G6biZbE!<4AwU)N_i3|3^lzhvX&*r>euqhD3o7|HN>3#~}W?O9B&1;&0Z)Nr1z zbIp7;)NxY|XS83y$=4O@zLDK$J&rN__|iG!$E4XazEXp>gTKQ@u&(Hd=Qgn>=3|_f zVk&HUA1L=8NZ7+DpvIxK`hLA<>(oN&!@j90H_N3ENv^z$vMe7eXRRq+o6%z1&U;b5PZQ5~<9+8b zum6lfVTI=-!7`=MK4XpGSu!N0`$eW^O3e z|9YFXQ$)Jg*wvYS{>g$^6^C?e)^wvZ^^Uu{T#Vo7gk(m?KIKT)7@Bl7EBcx=p{{oi zIkv=rPcUTQRdT9IKE3c@^gHS+FLNWtg3H>g%$eK1s2bxPs4N~Bd)DR)VS83Zf_v-t z4=JWAa}g(XHbaqVCJ%y&t1c4NoYH>!zLW-uSU9UOUs0QpR}XoMxBY9?H1r zwo4nSr_!A!g9*tDF=I0e)SB}Rh^O4fhJ&~zl7I4tTqc-_ zpMsumJWkNLRn+YqUygg~e$sxuHMU=OR?O-wQ_Ps^*vk9BZS9uYm%T6BW@UlnP%xO$ zx#Q7<{3sw zX#7#BuT=N1jCJNk?{mX)w1q0AV%5&%E=H@`tWfNWnB~~Ib3?}%%Z4e54Xd%CSk$k4 z(=Ps0`_wgHWJ|9-U+x!q3&q!E-PYrp{OMdohE`6I80Jg%xZ-sTq~iF?jj^0RXN_+{`c&EvrU+V(KyfIh*{nS3S8jnwWG{w`cWT{YYLx0>n zX5=6p+m`$4(ltfiKc~tMBM~R`VqXdAUea zium5O`k1X*rGg+6>H|^-ead%sWpvXJl3!g_y`s?-hg|ye61K)T=7#fK5q&#C_=hBw z3ih-0@>pgIjdz&8@#}530C@ZYl$=&^G^iJ9-Yrs!E`*Dfl406z+f<4O#>7(?d8Zo( zUWR(VD|GeNbiIUHe63Bo;9Gv`3>@VEUg@H~df`%cxmi^-$<6%b>G}=(%)E{9(_x=! zP7sje>R#y4BKf9vq9 z`{&b7uD`ln^(D=M+}pb8qd@a8m(-V4LDJ6wm-)fB+Y-^*R}KtupV#jBh(pl~mHQyx z3sF7g=x`3fe?Hdino`Tz5XQI({=i3y%i)AhB~n8uaXDQF-pTVQhl?ZFNn zy=!=I1hny1n<+Pi@NW$3R~e!%xd)SfnVaO&q(h&OHsI7`^(a0&U9V)Z0X849Tr__s zMQ%1b%U9CoUGMf#^9!xds&gn`vdmvqIwAeeJ7VI}aj6&$Cvwtbkd{GmCfi1nzp&*% zFJ9`c`F>S#j+Q_ROUyfv7SEw)XUZadt)s!0!sjB^|N76(D8jG(*h^B=ImcfAq4i&8 zRX-*0kmhNQ|6{oy(FU;pclwZF|0#O{NnXS8as)7m7(T=_0nwPa;)K8v5fi}fCQ&VJ zZZ(86h%t;Eo3n$Y6OvblAO1X1o?pKLK#Q21F%dR%<+iuKKJuEH14GC+l2%=-c zFhRy)^m|3uolj{p1uy3ai~ITS7G0k=-QyJ$B~oPPjJ-K$Qp7dRVfLsoHH-eB3ilP# z01>U-rZ%YhB8+hZ|1LL$S6LM2H6h= zh^7%wP>wWl<;*`DrC$%M&It2dxOQ4lAAC z%tn@%6pLqqI9>Bp7MS+xZzE2I%_y~La`l6gSh0K+g%&A z0ZNKC@h-}(9wi0LZg&WEs6rPN&cPK1dP-sU;90@Rxz7FmN3)DirU`)6wV*WSqT5!Y znk99DQ`a_~&&)&*=j9wKk<8J#zMAat412rjfCQR-J~ZWG>0|R|mlfi?a^G4Q)Q8=v zNnGHtZ5akdQgbV*eD*F%BgHN>+b;dEK}PkHFb+O>yVGl|sOxXKd?_%-t}_3im`Os4 z6lb%IDqLXXQN#`uIz!)=&%RVeW^L=gVnth4XMxHf#l&E1cexbZAqOaCV*k}%~Wy_Th zhQlOg>}7*;dc7<`1LqE?VH!)F|xt4E> zXn%oC?O#u$manrmnaVY{&}wdoz4UxyxEE0ib6l-sbsgf@51x&u=6E-#w|Bwog&jA+ z{@jjh`^I5yqttTx7$|6H`QDB4WvJxf;#$1d_hhWO#V(Xo++@~Z(Jr=6p=c4Qdxas# zsFhuU7~`fFVDClJ{>`nR{e6rHgaRk9@|LfT+jsK3`X}|OshyNmRCQqqx`w(%{JN@^ zzbuB~klS$WwdEi2wUbZ za)sWE?z`W0B{&ME=U}by1v-_65~o_d2i>i?_k%HyGKyMEmjm88-jdxg~4W#1x1wq(sR7+ZE_$r4iv8QEob z+f|yOv2R1gSW{u_gPR*=9ZSgmT)$CI_vd+@_m6k};4}Qz>pIst=X=igoQtV*?&BN~ z?lz10)Qn`e7dNed=vA*yzYOp&z-F%7U%smlf*G~!f2Qk5E-v1EkOfE#X=CaVx$~d>A8xy9`okXqPNWAx!xfEv$0A3GPs^ApKAy?5=UOY+zF(8 zb0tdtd+MdUQ*tO>Vr3YQ`OBg|i8dos^S6^yR||Ii2~)K3Yy?|OLeQLA{hilQ{l$S< zRvOqMbxXow>Pr+nx&IbJ-t+jt=Q+WVu33Ejq#&Q2tx&gflEw^(A%34FmaVcHRtpck zb;xa~n)(dz1JIaOMFumyZN+u~8aR#4y|`7MfvIC~;sD+gqZPf&pNphg%DhkNIv4@3 zd{~-S)7-*~7(I9gmoqi1@Q2H2+~zF_o0GLOGr$vD+XBj=TC{ww0{FuwIVWngE#w9| zUDih~4aLwpW^lR>UW=KLSqP7gc%B-QiOoDa{JDM=4BQldSsjnAAkJkp%P8Y>o18nK z?e4EIs(0wBq`$MzyLtyiAyz9D>9z^O`HmtAt|Y#3;8t)OIXj5m9SV`a{Sr37O@Mpj zXzrkPmx0vW&`w5JnCdBfg{a4kQH?(Dr+}}~x$!>DKEcJGEck1s;AkL<7<|nJ4!LBM zvGBxN%ZdjiIN#cP??!iQ{(2{Su|>8x`JC-S|NV_e{0ddpPG4W~^HWbLYd52-h8Xtk zU`NisL+`J2t~xA4gT_c&>dK1x!iJ0yL&5E%Y<+v%YBNnG2BW}Fm}~rvig8G~Vq_5K zGb*&Y=vU2*dS2U=Ssxydl=Qi2*XEWjabB7wVx?u$P-1-KNJ1X>!1OaW%+Xtotu5ij&L zbhg2T>g%?&I^?ZPuP~}JCoh3wIip~{Y6<;3hCdKlPp2%ODH18O1UIs|c6s?h%ma8Ri0BYk~oj{;*a?p$~WXzN5lO%08>V36NlEB*AnKL=#|z!N-E{Uq-B2gv$>_ZP7G z72H3%2-VgFOOV{3@Wo7mtvDfzzCZl0&QfIB)rkOD?DPjC(daJ0T99x7u|qx%F~_r% z01z}7KVt#zI`D*-&o;ehh|^#*Dsj5kWQe?efs%*XA8AUM2n>Oa{DyLF-{@$4X(@46 z*OmZ^%NF{QDd{ihK-W&?pGmTNtDqjP(H~TvXIk`w^aki51Go?TrsXX;U?UQ~bUKK; z1H|Sjh`iV#LEtjfC2+ebHb?!*9NjGT{NK8%P|o228<*tX|%0 zgnAHpqMU#DG5GzLqG7Fvy;qHQ4+7D1ATA zBZsA{7g7H$mX%l-nRF*$$OZKJWV8k8+ZjV_!GpQXJx7RK_!hZhsFNaPzqze)In~1c zg4!u%wtvg+^#GH5W~87R^6r<>y=|Sq<%ZR&KLC%v2o_rI{Ey`GQmK%|@K2=b$_f9z)tdAth zY%>-k=luz8c(0{1_ud%x;Qb6LT*8_5K}q=;Fba$Z1DD3`LVD^Mo!~&gwRp*z?@aWH zV5tu=P*gdUQ7N)4x1d0pe;&mcAu94s3E&B^o}eWNr;5TMBPvD;7D&mU)EUIuey!9+ zHF&Q8o4t)bYUv2uF+li&=8-bXzLb0|_y%bkNd1^Sr4ZTX-T#@|+fTCcYn@G6JPqYp zN&dXa7p;Jb>HY_5q9}oDZr|kogW#4a@tyrQ500{s&6r(NmC2nT$mIVD+}iK_K5Om3 zKi}p5{1Or}IXb$%0ywvS;g-m#w5)L1m)2O~arh_x4If}L!|cvh)$b);<{S%A6uAH7 z`Fl?B&PPxWMk15$5#LE4XZ?k(L*^4wY)*NG#<(=3)T2}*=k`GQCl^S9Eh?_j!c2|% z%(uXBhM1TvS#%*bXR!S2FqH2`9&>9dwRen+7iQJOir{#r;o#_VgsZXeSd*cqfMf5u zCyLGm2cassS9!2@joB?Sou7UhsU}Z#dFrwGgf^QK9^5ab(fyr7P9lq;W+e_57oOtP zWO*vp@fuWC_jWubFLqe4=n7z8!Qr6%#8^dz0JyBv7b@nAXUjgIlN+zrB$^J+l@im^ z9foKU=Y1cn?iK^5@bncR{$}T`g`R1NWm4wxUdL^9?ePyFwEzeqU!A#pB|%Jsd!XdDxZ6k? zC|l3h2NV`vT}8$!+h&s@nBhmR`~f@(5viV543=#dggh?n0iwNQxlmo-0q^{+(n~d*n;78Fz^P< z3BWYq)Cc&gQt3a&2zyL0j0fbECnY5%8tN1^e$+@Xdkw7<_ z;tKwu6#*rXop=hjIYEVcO?8&yGBly5jlmS#-F&JCU^286DE1Q8jRV8?ku?$M#y@`f z7Vvy)sK8K409as1`vH^DF@y%xGFET}h%-*nJrYTe_y2u;rDQxiTT@W5NMh6sk=TcI zexH9UXK~fVs{XQ3w8T~$(P;I%uEA<=sYBF}BW3GLRkik?StML;zE%nC(;#4+b4RfK z)7rBYKw4V4n%bX*NJf`N`1@OcO>aPnLV!hp0nwe>1uJoTwlC2NA?8q)GjC0JYHxzm zVu+3Vo5hXKA(I{#NY&-8-qejT<`K1ch%?<c^$CK`7mX$M3ehNqADmm&v(i{o z2}bbZsf(a1Y<>1BeEX|H-cULIsw451QTcdE2=q{5hMJZH9zi)>B-jZU&m@tbXGthy zYLxQe!GHwpmV~q)@!jK!#>VHp2^8}+#FNG={>hdOT6cEV1Q^WCd*NZ;*~Z4*wF;%3 zaKlDEp~d4UltQcR9(fN>_N>xUWObYCr=Gmpi{J7h*VQ2Sw`VOLD67!M2FuGQwlDV~ zOz*DLrEArJ?YYwC)OtB&e~8a(-MFL3TC9;6IWm$sP>UiZ0VDSnIZ#79&oz+`wrI)2 z3O!iJee|gJ_Pj$kd$+7L z8{P`L*VpNsr8--KfoYJZM;Yb@R+=7++<8+k$rt4)H^*Bsdr3P|8!ndanXI#Tf_gB} zDMZH0GXYHG&6k?K`jHXZ>4DV_dgyKkE+EO*yNz+VuvNrxniTznN}&#Iz}5&~l{(^M zv6;XBXB#laHg2=MM{c)#)}cFDhv1*mYe{vWr0;^ByBDn^sWS9qg{9=R!QKh8$#1no?uO1H;~`oNXqIj?8=G2) z;Nh3lu^nr+sUmnG1M` zq}JE4)Q=2~rFC#rkule;D||=Pu?3<8SP!3?Nhi*l`el{5!aWgLED_mdEB{`U^=>Lw zYVdW=AcGI zouD$Rwy>t{Z;Puv#26UH~VULX+>|e&yT(rdl;;t8XlD) z>F4=$Lb0Jc#IW}aIwwF33&i=gL@xcAKff5=-n+-w5Lc)L7z`<2l~>q3JZE~=>&F;Q zP^+=a`7FX5h~a{}g-h7}@n=$K41<_0nh9fhZhck#$L`lmTKcj@6S4wjG`g8Pf< zo<5N?wPvyPOt?qmUB(e+C#{gO3n-47|!k?buzG~Z}nl+xBKeRlb1Ki z*To}YrmXiGwt|raS{Fj(Nv;OQpzcGh6R@#wLT`P1h`djv@w17uu3s3#IM16I-^ARh_HNmqJ>c~2rkgo`u4X=Ji1!`@r=R}ncpdK1YD_huOA2ZNK}8P-YZ?y_>PL;N}B|jB_5PM;Z=xvA-x?_*PQ1+J(E1AbyG&WTEA`=-Pi3 zQz;De?PHrCO74D~$+Z_)*{B%kg9w_odqE>FscJqhW9- zZ8kPbuuZYaq1EP)u}&xi^X0H1pNO5utaTWp#ddnN#PeEtYPvemITJnceQ~a&o=q6} zEP7<}q!@UXyu*08yd?DokH%RJo~8qrO?GUAIqLF&s|XG+s<_^;LKGXH?I zjm%D|Q{UQrMPJf-l9ghCk-=($v+_)4fOT^xw1g~lTDLeJMh{Z9lj?+0-}OW$Jj>WC7iSS$kiST=(ppStSQu`50O4?m9z?J?DD zV?SO?`|}(d-ei&_XWb;}b%R^#^}wiZE6-QS6n>&-0AmF*Sy`Pvf#1K^MxCF>fAF`2 zNlU*gE{W?&?{|)*ZP?eR$1s&T8Lorr=Sxos$C>5aepFiyJmOPliSpuWMuRe$JV2us zFU4FW$Mse5W_Yu(#Na^zw+X{d0l@KpT)s>rC_6bjq=Z+zyC)-e#=2SFXRd#(sR9{B z2)8$TV1DU!2>^V%9)ew0y|x19^|1@mL-`T)r7EB72RrCG=u6YKffQUzxok>r9v_EW zi_T#e z!4HBIDeuru>lgp-SgM!<`+OHYg+JgkG^;lxk#f%2UNPE|qC|Q8W6*={dg`YeFZaC>7=7*qQJ(nPV)NzOV5fdGU zYFg5zDF{a_Z0wO6LQn$086!9kquA_kOhfEK%ZX#!S;nZ?!eO9+PPQ+e1kj2(HtFs^ zF`V$;ZgX;50O}TQYtld}jHbjs4*(b)gf;u=S#il8E*pR(=*;`q5oa;iFBQ0u8~s zRa{4Ly-t%^hL~^w?$qBDe-$ua{SXVg-^FbvZu7_{$>yz1i{aUTN4HM9GW@)v^~s-C zgu}rGD{bxV^e!#SOu4u&8;8oP_1W1$0{6+0SY7PfaX|D2FmKAvu65dbBv$i-?R6@( z5Opez5NE&x=R&)ea57dYf=OZVpa9XCMx1ps;QA>ufx;ROSt$rePtC=P3XBz>Kg89*^@RI+Iqo~} z|G1(OS5`s6P3J67?vT{X8S#QgqakktP^R;KJ)^O9;q2&^+NWYTT0Xj){~s5e)M<|4 z{m>C)HbDiU_Ky^)KXBlc1SKqXG5f!|@+0|!7YG88jA`av!TBB<@xhC+!J p$_MHLMEQVJ>;C8eamcOuZB)BV&XG5|FOEYetE*@$7b;l>{R_(vTR#8* diff --git a/doc/plantuml/jobs_async_detail.pu b/doc/plantuml/jobs_async_detail.pu deleted file mode 100644 index 457207f7bf..0000000000 --- a/doc/plantuml/jobs_async_detail.pu +++ /dev/null @@ -1,67 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica - -box "Application Thread" #LightGreen -actor Application as app -participant "Jobs API function" as jobs -participant "Internal Jobs functions" as internal -end box - -box "Task Pool" #LightBlue -participant "MQTT subscription callback" as callback -end box - -app -> jobs: Call Jobs API\nInput: AwsIotJobsRequestInfo_t, callback function\nAdditional input for Update and StartNext: AwsIotJobsUpdateInfo_t -activate jobs - -note over internal: _validateRequestInfo\n_validateUpdateInfo -jobs -> internal: Input: AwsIotJobsRequestInfo_t\nAwsIotJobsUpdateInfo_t -deactivate jobs -activate internal -internal -> internal: Check AwsIotJobsRequestInfo_t -internal -> internal: Check AwsIotJobsUpdateInfo_t -return Return SUCCESS or BAD_PARAMETER -activate jobs - -note over internal: _AwsIotJobs_CreateOperation -jobs -> internal: Input: AwsIotJobsRequestInfo_t -deactivate jobs -activate internal -internal -> internal: Calculate required memory for _jobsOperation_t -internal -> internal: Malloc _jobsOperation_t -internal -> internal: Setup _jobsOperation_t, save callback function -internal -> internal: Convert parameters to JSON -return Return _jobsOperation_t -activate jobs - -note over internal: _AwsIotJobs_ProcessOperation -jobs -> internal: Input: AwsIotJobsRequestInfo_t\n_jobsOperation_t -deactivate jobs -activate internal -internal -> internal: Generate topic for operation -alt Topic has subscription -internal -> internal: Retrieve subscription -else -internal -> internal: Create subscription -end alt -internal -> internal: Create MQTT PUBLISH command -internal -> internal: Add _jobsOperation_t to pending list -internal -> : Send MQTT PUBLISH -return Return SUCCESS or error - -jobs -> app: Return AwsIotJobsError_t -activate app - -== Wait for response == - -note over callback: _commonOperationCallback\nResponse received: Topic, Message -activate callback -callback -> callback: Parse Thing Name in Topic -callback -> callback: Match Thing Name with pending operation -callback -> callback: Parse ACCEPTED or REJECTED Status -callback -> app: Invoke app callback for completed operation\nInput: Thing Name, Status, Message\nApp callback runs from task pool -callback -> callback: Destroy _jobsOperation_t -deactivate callback - -@enduml diff --git a/doc/plantuml/jobs_demo.pu b/doc/plantuml/jobs_demo.pu deleted file mode 100644 index 57990246fe..0000000000 --- a/doc/plantuml/jobs_demo.pu +++ /dev/null @@ -1,39 +0,0 @@ -@startuml "jobs_demo" -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -participant "Main Thread" as main -participant "Completed Operation Callback" as operation -participant "NotifyNext Callback" as notify -participant "Jobs Library" as lib - -box "Demo Code" #LightBlue - participant main - participant operation - participant notify -end box -box "Library" #LightGreen - participant lib -end box - -box "Jobs Service" #LightPink - participant "Server" as server -end box - -main -> lib: Register NotifyNext callback - -loop #transparent Until "exit" action succeeds - server -> lib: New Job is created - lib -> notify: Callback invoked - notify -// lib: StartNextAsync - notify -> notify: Execute action from Job document - notify -// lib: UpdateAsync with result of action - lib -> operation: Result of Update - - group #transparent "exit" command successfully updated - operation -> main: Signal end of demo - end -end - -@enduml diff --git a/doc/plantuml/jobs_sync_detail.pu b/doc/plantuml/jobs_sync_detail.pu deleted file mode 100644 index 63c0e2e11e..0000000000 --- a/doc/plantuml/jobs_sync_detail.pu +++ /dev/null @@ -1,68 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica - -box "Application Thread" #LightGreen -actor Application as app -participant "Jobs API function" as jobs -participant "Internal Jobs functions" as internal -end box - -box "Task Pool" #LightBlue -participant "MQTT subscription callback" as callback -end box - -app -> jobs: Call Jobs API\nInput: AwsIotJobsRequestInfo_t, callback function\nAdditional input for Update and StartNext: AwsIotJobsUpdateInfo_t -activate jobs - -note over internal: _validateRequestInfo\n_validateUpdateInfo -jobs -> internal: Input: AwsIotJobsRequestInfo_t\nAwsIotJobsUpdateInfo_t -deactivate jobs -activate internal -internal -> internal: Check AwsIotJobsRequestInfo_t -internal -> internal: Check AwsIotJobsUpdateInfo_t -return Return SUCCESS or BAD_PARAMETER -activate jobs - -note over internal: _AwsIotJobs_CreateOperation -jobs -> internal: Input: AwsIotJobsRequestInfo_t -deactivate jobs -activate internal -internal -> internal: Calculate required memory for _jobsOperation_t -internal -> internal: Malloc _jobsOperation_t -internal -> internal: Setup _jobsOperation_t, save callback function -internal -> internal: Convert parameters to JSON -return Return _jobsOperation_t -activate jobs - -note over internal: _AwsIotJobs_ProcessOperation -jobs -> internal: Input: AwsIotJobsRequestInfo_t\n_jobsOperation_t -deactivate jobs -activate internal -internal -> internal: Generate topic for operation -alt Topic has subscription -internal -> internal: Retrieve subscription -else -internal -> internal: Create subscription -end alt -internal -> internal: Create MQTT PUBLISH command -internal -> internal: Add _jobsOperation_t to pending list -internal -> : Send MQTT PUBLISH -return Return SUCCESS or error - -activate jobs -jobs -> jobs: Wait on a semaphore for MQTT response - - -note over callback: _commonOperationCallback\nResponse received: Topic, Message -activate callback -callback -> callback: Parse Thing Name in Topic -callback -> callback: Match Thing Name with pending operation -callback -> callback: Parse ACCEPTED or REJECTED Status -callback -> jobs: Post semaphore to unblock the wait -callback -> callback: Destroy _jobsOperation_t -deactivate callback -jobs -> app: Return AwsIotJobsError_t -deactivate jobs - -@enduml diff --git a/doc/plantuml/mqtt_demo.pu b/doc/plantuml/mqtt_demo.pu deleted file mode 100644 index bfb9ee29d3..0000000000 --- a/doc/plantuml/mqtt_demo.pu +++ /dev/null @@ -1,38 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -participant "Main Demo Thread" as main -participant "Incoming PUBLISH callback" as callback - -box "MQTT Client" #LightBlue - participant main - participant callback -end box - -participant "MQTT Server" as server - -main -> server: Subscribe to topic filters -server -> main: Subscription ACK - -loop IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times - loop IOT_DEMO_MQTT_PUBLISH_BURST_SIZE times - main -> server: Publish "Hello world n!" - server -> main: Publish ACK - server -> server: Check for\nmatching subscriptions - server -> callback: Publish to\nmatching topic filter - callback -> server: Publish "Received message n" - callback -> main: Notify "Publish received" - server -> callback: Publish ACK - end - - main -> main: Wait for\nIOT_DEMO_MQTT_BURST_SIZE\nmessages to be received -end - -main -> server: Unsubscribe from topic filters -server -> main: Unsubscribe ACK - -main -> server: Disconnect - -@enduml diff --git a/doc/plantuml/mqtt_design_typicaloperation.pu b/doc/plantuml/mqtt_design_typicaloperation.pu deleted file mode 100644 index 5bbe065d70..0000000000 --- a/doc/plantuml/mqtt_design_typicaloperation.pu +++ /dev/null @@ -1,52 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -box "Application" #LightGreen - participant "Application thread" as application - create participant "MQTT API\nfunction" as api -end box - -box "Task pool\nThe task pool processes\nbackground work without\nblocking the application.\nIt also invokes asynchronous\ncallbacks.\n" #LightBlue - participant "Task pool jobs" as task_pool -end box - -box "Network stack" #Orange - participant "Receive\ncallback" as receive_callback - participant "Network IO" as network -end box - -== Send MQTT packet == -activate application -application -> api: Call MQTT\noperation\nfunction -deactivate application -activate api -api -> api: Allocate resources\nand generate\nMQTT packet -api -> task_pool: Post send job -activate task_pool -api -> application: Return\nSTATUS_PENDING -destroy api -activate application -task_pool -> network: Transmit\n MQTT packet -activate network -task_pool -> task_pool: Mark operation\nas awaiting response -network -> : Send data to server -deactivate task_pool - -network <- : Server response - -== Parse Server Response == -network -> receive_callback: Notify of response -deactivate network -activate receive_callback -receive_callback -> receive_callback: Parse incoming packet -receive_callback -> task_pool: Post notify job -deactivate receive_callback -activate task_pool -task_pool -> application: Notify of result - -deactivate task_pool -deactivate application - -@enduml diff --git a/doc/plantuml/shadow_async_opertation_detail.pu b/doc/plantuml/shadow_async_opertation_detail.pu deleted file mode 100644 index 85df24856d..0000000000 --- a/doc/plantuml/shadow_async_opertation_detail.pu +++ /dev/null @@ -1,76 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica - -box "Application Task" #LightBlue -actor Application as app -participant "Shadow API" as shadow -participant "Internal Shadow functions" as internal -participant "MQTT API" as mqtt -end box - - -box "Task Pool" #LightGreen -participant "MQTT subscription callback" as callback -end box - -app -> shadow: Call Shadow Operation\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags,\nCallback -activate shadow - -shadow -> internal: Validate arguments\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags,\nCallback -deactivate shadow -activate internal -note over internal: _validateThingNameFlags\n_validateDocumentInfo -internal -> internal: Check ThingNameFlags -internal -> internal: Check AwsIotShadowDocumentInfo_t -return Return SUCCESS or BAD_PARAMETER -activate shadow - -shadow -> internal: Create operation\nInput:\nOperation,\nFlags,\nCallback -deactivate shadow -activate internal -note over internal: _AwsIotShadow_CreateOperation -internal -> internal: Calculate required memory for AwsIotShadowOperation_t -internal -> internal: Malloc AwsIotShadowOperation_t -internal -> internal: Setup AwsIotShadowOperation_t, save Callback -return Return AwsIotShadowOperation_t -activate shadow - -shadow -> internal: Process operation\nInput:\nIotMqttConnection_t\nThingName\nAwsIotShadowOperation_t\nAwsIotShadowDocumentInfo_t -deactivate shadow -activate internal -note over internal: _AwsIotShadow_ProcessOperation -internal -> internal: Generate topic for operation -alt Topic has subscription -internal -> internal: Retrieve subscription -else -internal -> internal: Create subscription for 'accepted' and 'rejected' topics -internal -> mqtt: Subscribe for 'accepted' and 'rejected' topics for Shadow -mqtt -> internal: Return SUCCESS or error -alt If error for subscription -internal -> shadow: Return error -shadow -> app: Return AWS_IOT_SHADOW_MQTT_ERROR -end alt -end alt -internal -> internal: Create MQTT PUBLISH command -internal -> internal: Add AwsIotShadowOperation_t to pending list -internal -> mqtt: Send MQTT PUBLISH -mqtt -> : Transmit MQTT packet using network stack -mqtt -> internal: Return SUCCESS or error -internal -> shadow: Return SUCCESS or error -deactivate internal -shadow -> app: Return AwsIotShadowError_t - - -== Wait for response == - -note over callback: _commonOperationCallback\nResponse received: Topic, Message -activate callback -callback -> callback: Parse Thing Name in Topic -callback -> callback: Match Thing Name with pending operation -callback -> callback: Parse ACCEPTED or REJECTED Status -callback -> app: Invoke app callback for completed operation\nInput:\nAwsIotShadowCallbackParam_t\nApp callback runs from task pool -callback -> callback: Destroy AwsIotShadowOperation_t -deactivate callback - -@enduml diff --git a/doc/plantuml/shadow_demo.pu b/doc/plantuml/shadow_demo.pu deleted file mode 100644 index 0b1e7d0d60..0000000000 --- a/doc/plantuml/shadow_demo.pu +++ /dev/null @@ -1,33 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -box "Demo Device 1\n(part of demo application)" #LightBlue - participant "Main demo thread" as main -end box - -box "Demo Device 2\n(part of demo application)" #LightGreen - participant "Delta callback" as delta_callback -end box - -box "Shadow Service" #Orange - participant "Update topic" as update_topic - participant "Delta topic" as delta_topic -end box - -main -> delta_topic: SUBSCRIBE (set delta callback) -delta_topic -> delta_callback: SUBACK (delta callback set) - -loop AWS_IOT_DEMO_SHADOW_UPDATE_COUNT times - loop every AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS - main -> update_topic: New "desired" state - update_topic -> delta_topic: Generate delta\ndocument - delta_topic -> delta_callback: Send delta document - delta_callback -> delta_callback: Change state - delta_callback -> update_topic: Report state change - delta_callback -> main: Notify state change done - end -end - -@enduml diff --git a/doc/plantuml/shadow_sync_opertation_detail.pu b/doc/plantuml/shadow_sync_opertation_detail.pu deleted file mode 100644 index 3ace6f22b4..0000000000 --- a/doc/plantuml/shadow_sync_opertation_detail.pu +++ /dev/null @@ -1,78 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica - -box "Application Task" #LightBlue -actor Application as app -participant "Shadow API" as shadow -participant "Internal Shadow functions" as internal -participant "MQTT API" as mqtt -end box - - -box "Task Pool" #LightGreen -participant "MQTT subscription callback" as callback -end box - -app -> shadow: Call Shadow Operation\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags -activate shadow - -shadow -> internal: Validate arguments\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags -deactivate shadow -activate internal -note over internal: _validateThingNameFlags\n_validateDocumentInfo -internal -> internal: Check ThingNameFlags -internal -> internal: Check AwsIotShadowDocumentInfo_t -return Return SUCCESS or BAD_PARAMETER -activate shadow - -shadow -> internal: Create operation\nInput:\nOperation,\nFlags -deactivate shadow -activate internal -note over internal: _AwsIotShadow_CreateOperation -internal -> internal: Calculate required memory for AwsIotShadowOperation_t -internal -> internal: Malloc AwsIotShadowOperation_t -internal -> internal: Setup AwsIotShadowOperation_t -return Return AwsIotShadowOperation_t -activate shadow - -shadow -> internal: Process operation\nInput:\nIotMqttConnection_t\nThingName\nAwsIotShadowOperation_t\nAwsIotShadowDocumentInfo_t -deactivate shadow -activate internal -note over internal: _AwsIotShadow_ProcessOperation -internal -> internal: Generate topic for operation -alt Topic has subscription -internal -> internal: Retrieve subscription -else -internal -> internal: Create subscription for 'accepted' and 'rejected' topics -internal -> mqtt: Subscribe for 'accepted' and 'rejected' topics for Shadow -mqtt -> internal: Return SUCCESS or error -alt If error for subscription -internal -> shadow: Return error -shadow -> app: Return AWS_IOT_SHADOW_MQTT_ERROR -end alt -end alt -internal -> internal: Create MQTT PUBLISH command -internal -> internal: Add AwsIotShadowOperation_t to pending list -internal -> mqtt: Send MQTT PUBLISH -mqtt -> : Transmit MQTT packet using network stack -mqtt -> internal: Return SUCCESS or error -internal -> shadow: Return SUCCESS or error -deactivate internal -activate shadow -shadow -> shadow: Wait on a semaphore for MQTT response - - -note over callback: _commonOperationCallback\nResponse received: Topic, Message -activate callback -callback -> callback: Parse Thing Name in Topic -callback -> callback: Match Thing Name with pending operation -callback -> callback: Parse ACCEPTED or REJECTED Status -callback -> shadow: Post semaphore to unblock the wait. -callback -> callback: Destroy AwsIotShadowOperation_t -deactivate callback -shadow -> app: Return AwsIotShadowError_t -deactivate shadow - - -@enduml \ No newline at end of file diff --git a/doc/plantuml/taskpool_design_typicaloperation.pu b/doc/plantuml/taskpool_design_typicaloperation.pu deleted file mode 100644 index 50afed070a..0000000000 --- a/doc/plantuml/taskpool_design_typicaloperation.pu +++ /dev/null @@ -1,77 +0,0 @@ -@startuml -skinparam classFontSize 8 -skinparam classFontName Helvetica -autonumber - -participant "Application" as app -participant "User Callback" as callback -participant "Job" as job - -participant "Task Pool public API" as TP -participant "Dispatch queue" as queue -participant "Worker Threads" as workers - -box "Task Pool" #LightBlue - participant TP - participant queue - participant workers -end box - -== Create Task Pool == - -activate app - -app -[#blue]> TP: IotTaskPool_Create: create a Task -TP -> queue: Initialize dispatch queue -activate queue -TP -> workers: Create minimum number of worker threads -activate workers -TP --[#blue]> app -activate TP -workers -> workers: Wait on incoming jobs - -== Use Task Pool == - -loop Application loop: Create and schedule jobs - app -[#blue]> TP: IotTaskPool_CreateRecyclableJob: create a job - TP --[#blue]> app - activate job - note left: job status: //ready// - - app -[#blue]> TP: IotTaskPool_Schedule: schedule a job - TP -> queue: Queue job - TP -> TP: Grow pool up to maximum threads, if all threads are busy - TP -> workers: Signal incoming job - TP --[#blue]> app - note left: job status: //scheduled// - - loop Outer dispatch loop: Wait on incoming jobs - workers -> queue: Dequeue next job - loop Inner dispatch loop: Execute any queue jobs in order - workers -[#green]> job: Invoke user callback - note left: job status: //executing// - job -[#green]> callback: Invoke - activate callback - callback -[#blue]> TP: IotTaskPool_RecycleJob: recycles job - TP --[#blue]> callback - note left: job status: //completed// - deactivate job - deactivate callback - workers -> workers: Move to next job - end - workers -> workers: Wait on incoming jobs - end - -end - -== Destroy Task Pool == - - app -[#blue]> TP: IotTaskPool_Destroy: destroy the task pool - TP -> workers: Shutdown worker threads - deactivate workers - TP -> queue: Destroy all jobs in the dispatch queue - deactivate queue - TP --[#blue]> app - deactivate TP - -@enduml diff --git a/libraries/aws/common/CMakeLists.txt b/libraries/aws/common/CMakeLists.txt deleted file mode 100644 index 86458417f6..0000000000 --- a/libraries/aws/common/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -# AWS IoT common library source files. -set( AWS_IOT_COMMON_SOURCES - src/aws_iot_doc_parser.c - src/aws_iot_operation.c - src/aws_iot_parser.c - src/aws_iot_subscription.c - src/aws_iot_validate.c ) - -# AWS IoT common library target. -add_library( awsiotcommon - ${CONFIG_HEADER_PATH}/iot_config.h - ${AWS_IOT_COMMON_SOURCES} - include/aws_iot.h - include/aws_iot_doc_parser.h ) - -# AWS IoT common public include path. -target_include_directories( awsiotcommon PUBLIC include ) - -# Link required libraries. -target_link_libraries( awsiotcommon PRIVATE iotbase iotmqtt ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotcommon PRIVATE unity ) -endif() - -# Organization of AWS IoT common in folders. -set_property( TARGET awsiotcommon PROPERTY FOLDER libraries/aws ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES include/aws_iot.h include/aws_iotdoc_parser.h ) -source_group( src FILES ${AWS_IOT_COMMON_SOURCES} ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # AWS IoT Common unit test sources. - set( AWS_IOT_COMMON_UNIT_TEST_SOURCES - test/unit/aws_iot_tests_doc_parser.c ) - - # AWS IoT Common tests executable. - add_executable( aws_iot_tests_common - ${AWS_IOT_COMMON_UNIT_TEST_SOURCES} - test/aws_iot_tests_common.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( aws_iot_tests_common PRIVATE - -DRunTests=RunAwsIotCommonTests ) - - # AWS IoT Common tests library dependencies. - target_link_libraries( aws_iot_tests_common PRIVATE awsiotcommon iotbase unity ) - - # Organization of AWS IoT Common tests in folders. - set_property( TARGET aws_iot_tests_common PROPERTY FOLDER tests ) - source_group( unit FILES ${AWS_IOT_COMMON_UNIT_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_common.c ) -endif() \ No newline at end of file diff --git a/libraries/aws/common/include/aws_iot.h b/libraries/aws/common/include/aws_iot.h deleted file mode 100644 index 0a1bdcb44d..0000000000 --- a/libraries/aws/common/include/aws_iot.h +++ /dev/null @@ -1,305 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot.h - * @brief Provides routines and constants that are common to AWS IoT libraries. - * This header should not be included in typical application code. - */ - -#ifndef AWS_IOT_H_ -#define AWS_IOT_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Platform types include. */ -#include "types/iot_platform_types.h" - -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/** - * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT - * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). - */ -#define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) - -/** - * @brief The common prefix of all AWS IoT MQTT topics. - */ -#define AWS_IOT_TOPIC_PREFIX "$aws/things/" - -/** - * @brief The length of #AWS_IOT_TOPIC_PREFIX. - */ -#define AWS_IOT_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) ) - -/** - * @brief The suffix for an AWS IoT operation "accepted" topic. - */ -#define AWS_IOT_ACCEPTED_SUFFIX "/accepted" - -/** - * @brief The length of #AWS_IOT_ACCEPTED_SUFFIX. - */ -#define AWS_IOT_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_ACCEPTED_SUFFIX ) - 1 ) ) - -/** - * @brief The suffix for an AWS IoT operation "rejected" topic. - */ -#define AWS_IOT_REJECTED_SUFFIX "/rejected" - -/** - * @brief The length of #AWS_IOT_REJECTED_SUFFIX. - */ -#define AWS_IOT_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) ) - -/** - * @brief The JSON key used to represent client tokens for AWS IoT. - */ -#define AWS_IOT_CLIENT_TOKEN_KEY "clientToken" - -/** - * @brief The length of #AWS_IOT_CLIENT_TOKEN_KEY. - */ -#define AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ( sizeof( AWS_IOT_CLIENT_TOKEN_KEY ) - 1 ) - -/** - * @brief The length of the longest client token allowed by AWS IoT. - */ -#define AWS_IOT_CLIENT_TOKEN_MAX_LENGTH ( 64 ) - -/** - * @brief A flag to represent persistent subscriptions in a subscriptions - * object. - * - * Its value is negative to distinguish it from valid subscription counts, which - * are 0 or positive. - */ -#define AWS_IOT_PERSISTENT_SUBSCRIPTION ( -1 ) - -/** - * @brief Function pointer representing an MQTT blocking operation. - * - * Currently, this is used to represent @ref mqtt_function_subscribesync or - * @ref mqtt_function_unsubscribesync. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription. - * @param[in] pSubscriptionList Pointer to the first element in the array of - * subscriptions. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * Currently, flags are ignored by this function; this parameter is for - * future-compatibility. - * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within - * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_TIMEOUT - * - #IOT_MQTT_SERVER_REFUSED - */ -typedef IotMqttError_t ( * AwsIotMqttFunction_t )( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ); - -/** - * @brief Function pointer representing an MQTT library callback function. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). - */ -typedef void ( * AwsIotMqttCallbackFunction_t )( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Enumerations representing each of the statuses that may be parsed - * from a topic. - */ -typedef enum AwsIotStatus -{ - AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */ - AWS_IOT_REJECTED = 1, /**< Operation rejected. */ - AWS_IOT_UNKNOWN = 2 /**< Unknown status (neither accepted nor rejected). */ -} AwsIotStatus_t; - -/** - * @brief Information required to generate a topic for AWS IoT. - */ -typedef struct AwsIotTopicInfo -{ - const char * pThingName; /**< @brief The Thing Name associated with the operation. */ - size_t thingNameLength; /**< @brief The length of `pThingName`. */ - const char * pOperationName; /**< @brief The operation name to place in the topic. */ - uint16_t operationNameLength; /**< @brief The length of `pOperationName`. */ - uint16_t longestSuffixLength; /**< @brief The length of longest suffix that will be placed at the end of the topic. */ - void * ( *mallocString )( size_t size ); /**< @brief Function used to allocate a string, if needed. */ -} AwsIotTopicInfo_t; - -/** - * @brief Information needed to modify AWS IoT subscription topics. - * - * @warning The buffer passed as `pTopicFilterBase` must be large enough to - * accommodate the "/accepted" and "/rejected" suffixes. - */ -typedef struct AwsIotSubscriptionInfo_t -{ - IotMqttConnection_t mqttConnection; /**< @brief The MQTT connection to use. */ - AwsIotMqttCallbackFunction_t callbackFunction; /**< @brief Callback function for MQTT subscribe. */ - uint32_t timeout; /**< @brief Timeout for MQTT function. */ - - /* Topic filter. */ - char * pTopicFilterBase; /**< @brief Contains the base topic filter, without "/accepted" or "/rejected". */ - uint16_t topicFilterBaseLength; /**< @brief Length of the base topic filter. */ -} AwsIotSubscriptionInfo_t; - -/** - * @brief Thing Name and length, used to match subscriptions. - */ -typedef struct AwsIotThingName -{ - const char * pThingName; /**< @brief Thing Name to compare. */ - size_t thingNameLength; /**< @brief Length of `pThingName`. */ -} AwsIotThingName_t; - -/** - * @brief Initializes the lists used by AWS IoT operations. - * - * @param[in] pPendingOperationsList The list that holds pending operations for - * a library. - * @param[in] pSubscriptionsList The list that holds subscriptions for a library. - * @param[in] pPendingOperationsMutex The mutex that guards the pending operations - * list. - * @param[in] pSubscriptionsMutex The mutex that guards the subscriptions list. - * - * @return `true` if all initialization succeeded; `false` otherwise. - */ -bool AwsIot_InitLists( IotListDouble_t * pPendingOperationsList, - IotListDouble_t * pSubscriptionsList, - IotMutex_t * pPendingOperationsMutex, - IotMutex_t * pSubscriptionsMutex ); - -/** - * @brief Checks that a Thing Name is valid for AWS IoT. - * - * @param[in] pThingName Thing Name to validate. - * @param[in] thingNameLength Length of `pThingName`. - * - * @return `true` if `pThingName` is valid; `false` otherwise. - */ -bool AwsIot_ValidateThingName( const char * pThingName, - size_t thingNameLength ); - -/** - * @brief Extracts the client token from a JSON document. - * - * The client token is used to differentiate AWS IoT operations. It is unique per - * operation. - * - * @param[in] pJsonDocument The JSON document to parse. - * @param[in] jsonDocumentLength The length of `pJsonDocument`. - * @param[out] pClientToken Set to point to the client token in `pJsonDocument`. - * @param[out] pClientTokenLength Set to the length of the client token. - * - * @return `true` if the client token was found; `false` otherwise. The output - * parameters are only valid if this function returns `true`. - */ -bool AwsIot_GetClientToken( const char * pJsonDocument, - size_t jsonDocumentLength, - const char ** pClientToken, - size_t * pClientTokenLength ); - -/** - * @brief Parse the Thing Name from an MQTT topic. - * - * @param[in] pTopicName The topic to parse. - * @param[in] topicNameLength The length of `pTopicName`. - * @param[out] pThingName Set to point to the Thing Name. - * @param[out] pThingNameLength Set to the length of the Thing Name. - * - * @return `true` if a Thing Name was successfully parsed; `false` otherwise. The output - * parameters are only valid if this function returns `true`. - */ -bool AwsIot_ParseThingName( const char * pTopicName, - uint16_t topicNameLength, - const char ** pThingName, - size_t * pThingNameLength ); - -/** - * @brief Parse the operation status (accepted or rejected) from an MQTT topic. - * - * @param[in] pTopicName The topic to parse. - * @param[in] topicNameLength The length of `pTopicName`. - * - * @return Any #AwsIotStatus_t. - */ -AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, - uint16_t topicNameLength ); - -/** - * @brief Generate a topic to use for an AWS IoT operation. - * - * @param[in] pTopicInfo Information needed to generate an AWS IoT topic. - * @param[in,out] pTopicBuffer Where to place the generated topic. An existing - * buffer may be passed in. If `NULL`, this function will attempt to allocate a - * new buffer. - * @param[out] pOperationTopicLength Set to the length of the generated topic. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must be long enough to accommodate the Thing Name, operation name, and - * any other suffixes. - * - * @return `true` if the topic was successfully generated; `false` otherwise. - * This function will always succeed when an input buffer is provided. - */ -bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, - char ** pTopicBuffer, - uint16_t * pOperationTopicLength ); - -/** - * @brief Add or remove subscriptions for AWS IoT operations. - * - * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or - * @ref mqtt_function_unsubscribesync. - * @param[in] pSubscriptionInfo Information needed to process an MQTT - * operation. - * - * @return See the return values of @ref mqtt_function_subscribesync or - * @ref mqtt_function_unsubscribesync. - */ -IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, - const AwsIotSubscriptionInfo_t * pSubscriptionInfo ); - -#endif /* ifndef AWS_IOT_H_ */ diff --git a/libraries/aws/common/include/aws_iot_doc_parser.h b/libraries/aws/common/include/aws_iot_doc_parser.h deleted file mode 100644 index 193bdeb6ef..0000000000 --- a/libraries/aws/common/include/aws_iot_doc_parser.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_doc_parser.h - * @brief Parser for AWS IoT Services Documents. This is a JSON parser - * specifically designed to process and retrieve a value from a AWS IoT JSON - * document, used in AWS IoT libraries such as Shadow and Jobs. Given a key and - * a JSON document, AwsIotDocParser_FindValue() will find the first occurrence - * of the key and return its respective value. The design goal of this parser - * is to be light weight and to be of low memory footprint. However, it does - * not check the correctness of the JSON documents. Hence, this parser is not - * meant to be used for general purpose JSON parsing. - */ - -#ifndef AWS_IOT_DOC_PARSER_H_ -#define AWS_IOT_DOC_PARSER_H_ - -/* Standard includes. */ -#include -#include - -/** - * @brief Find a value for a key from a AWS IoT service JSON document. - * - * @warning The parsing will not check for the correctness of the JSON document. - * It is designed to be light weight and to be of low memory footprint rather - * than checking for the correctness of the JSON document. Hence this is not - * meant to be used for a general purpose JSON parsing. This is recommended to - * be used only with mutually authenticated AWS IoT services such as Shadow and - * Jobs where the document will always be a well formatted JSON. - * - * @param[in] pAwsIotJsonDocument Pointer to AWS IoT Service JSON document. - * @param[in] awsIotJsonDocumentLength Length of AWS IoT Service JSON document. - * @param[in] pAwsIotJsonKey JSON key for finding the associated value. - * @param[in] awsIotJsonKeyLength Length of the JSON key. - * @param[out] pAwsIotJsonValue Pointer to the pointer of value found. - * @param[out] pAwsIotJsonValueLength Pointer to the length of the value found. - * - * @returns `true` if a value is found, `false` if a value cannot be found. If - * returns `false`, the values in out pointers will not be valid. - */ -bool AwsIotDocParser_FindValue( const char * pAwsIotJsonDocument, - size_t awsIotJsonDocumentLength, - const char * pAwsIotJsonKey, - size_t awsIotJsonKeyLength, - const char ** pAwsIotJsonValue, - size_t * pAwsIotJsonValueLength ); - -#endif /* ifndef AWS_IOT_DOC_PARSER_H_ */ diff --git a/libraries/aws/common/lexicon.txt b/libraries/aws/common/lexicon.txt deleted file mode 100644 index 36f8d53ae0..0000000000 --- a/libraries/aws/common/lexicon.txt +++ /dev/null @@ -1,32 +0,0 @@ -aws -awsiotjsondocumentlength -awsiotmqttcallbackfunction -bool -callbackfunction -com -config -const -endif -gr -html -https -ifndef -int -iot -iotmqttconnection -isn -json -longestsuffixlength -mallocstring -mqtt -mqttconnection -mutex -operationnamelength -poperationname -pthingname -ptopicfilterbase -qos -thingnamelength -topicfilterbaselength -uint -unescaped diff --git a/libraries/aws/common/src/aws_iot_doc_parser.c b/libraries/aws/common/src/aws_iot_doc_parser.c deleted file mode 100644 index 1256dd5f2d..0000000000 --- a/libraries/aws/common/src/aws_iot_doc_parser.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_doc_parser.c - * @brief Implements the functions in aws_iot_doc_parser.h - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -#define IS_QUOTE( str, idx ) \ - ( ( str )[ ( idx ) ] == '"' && ( ( idx ) == 0 || ( str )[ ( idx ) - 1 ] != '\\' ) ) -#define IS_WHITESPACE( str, idx ) \ - ( ( str )[ ( idx ) ] == ' ' || ( str )[ ( idx ) ] == '\n' || ( str )[ ( idx ) ] == '\r' || ( str )[ ( idx ) ] == '\t' ) - -/*-----------------------------------------------------------*/ - -bool AwsIotDocParser_FindValue( const char * pAwsIotJsonDocument, - size_t awsIotJsonDocumentLength, - const char * pAwsIotJsonKey, - size_t awsIotJsonKeyLength, - const char ** pAwsIotJsonValue, - size_t * pAwsIotJsonValueLength ) -{ - size_t i = 0; - size_t jsonValueLength = 0; - char openCharacter = '\0', closeCharacter = '\0'; - int nestingLevel = 0; - bool isWithinQuotes = false; - - /* Validate all the arguments.*/ - if( ( pAwsIotJsonDocument == NULL ) || ( pAwsIotJsonKey == NULL ) || - ( awsIotJsonDocumentLength == 0 ) || ( awsIotJsonKeyLength == 0 ) ) - { - return false; - } - - /* Ensure the JSON document is long enough to contain the key/value pair. At - * the very least, a JSON key/value pair must contain the key and the 6 - * characters {":""} */ - if( awsIotJsonDocumentLength < awsIotJsonKeyLength + 6 ) - { - return false; - } - - /* Search the characters in the JSON document for the key. The end of the JSON - * document does not have to be searched once too few characters remain to hold a - * value. */ - while( i < awsIotJsonDocumentLength - awsIotJsonKeyLength - 3 ) - { - /* If the first character in the key is found and there's an unescaped double - * quote after the key length, do a string compare for the key. */ - if( ( IS_QUOTE( pAwsIotJsonDocument, i ) ) && - ( IS_QUOTE( pAwsIotJsonDocument, i + 1 + awsIotJsonKeyLength ) ) && - ( pAwsIotJsonDocument[ i + 1 ] == pAwsIotJsonKey[ 0 ] ) && - ( strncmp( pAwsIotJsonDocument + i + 1, - pAwsIotJsonKey, - awsIotJsonKeyLength ) == 0 ) ) - { - /* Key found; this is a potential match. */ - - /* Skip the characters in the JSON key and closing double quote. */ - /* While loop guarantees that i < awsIotJsonDocumentLength - 1 */ - i += awsIotJsonKeyLength + 2; - - /* Skip all whitespace characters between the closing " and the : */ - while( IS_WHITESPACE( pAwsIotJsonDocument, i ) ) - { - i++; - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - } - - /* The character immediately following a key (and any whitespace) must be a : - * If it's another character, then this string is a JSON value; skip it. */ - if( pAwsIotJsonDocument[ i ] != ':' ) - { - continue; - } - else - { - /* Skip the : */ - i++; - } - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - - /* Skip all whitespace characters between : and the first character in the value. */ - while( IS_WHITESPACE( pAwsIotJsonDocument, i ) ) - { - i++; - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - } - - /* Value found. Set the output parameter. */ - if( pAwsIotJsonValue != NULL ) - { - *pAwsIotJsonValue = pAwsIotJsonDocument + i; - } - - /* Calculate the value's length. */ - switch( pAwsIotJsonDocument[ i ] ) - { - /* Calculate length of a JSON string. */ - case '\"': - /* Include the length of the opening and closing double quotes. */ - jsonValueLength = 2; - - /* Skip the opening double quote. */ - i++; - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - - /* Add the length of all characters in the JSON string. */ - while( pAwsIotJsonDocument[ i ] != '\"' ) - { - /* Ignore escaped double quotes. */ - if( ( pAwsIotJsonDocument[ i ] == '\\' ) && - ( i + 1 < awsIotJsonDocumentLength ) && - ( pAwsIotJsonDocument[ i + 1 ] == '\"' ) ) - { - /* Skip the characters \" */ - i += 2; - jsonValueLength += 2; - } - else - { - /* Add the length of a single character. */ - i++; - jsonValueLength++; - } - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - } - - break; - - /* Set the matching opening and closing characters of a JSON object or array. - * The length calculation is performed below. */ - case '{': - openCharacter = '{'; - closeCharacter = '}'; - break; - - case '[': - openCharacter = '['; - closeCharacter = ']'; - break; - - /* Calculate the length of a JSON primitive. */ - default: - - /* Skip the characters in the JSON value. The JSON value ends with a , or } */ - while( pAwsIotJsonDocument[ i ] != ',' && - pAwsIotJsonDocument[ i ] != '}' ) - { - /* Any whitespace before a , or } means the JSON document is invalid. */ - if( IS_WHITESPACE( pAwsIotJsonDocument, i ) ) - { - return false; - } - - i++; - jsonValueLength++; - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - } - - break; - } - - /* Calculate the length of a JSON object or array. */ - if( ( openCharacter != '\0' ) && ( closeCharacter != '\0' ) ) - { - /* Include the length of the opening and closing characters. */ - jsonValueLength = 2; - - /* Skip the opening character. */ - i++; - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - - /* Add the length of all characters in the JSON object or array. This - * includes the length of nested objects. */ - while( pAwsIotJsonDocument[ i ] != closeCharacter || - ( pAwsIotJsonDocument[ i ] == closeCharacter && ( nestingLevel != 0 || isWithinQuotes ) ) ) - { - /* Check if its a quote so as to avoid considering the - * nested levels for opening and closing characters within - * quotes. - */ - if( IS_QUOTE( pAwsIotJsonDocument, i ) ) - { - /*Toggle the flag*/ - isWithinQuotes = !isWithinQuotes; - } - - /* Calculate the nesting levels only if not in quotes. */ - if( !isWithinQuotes ) - { - /* An opening character starts a nested object. */ - if( pAwsIotJsonDocument[ i ] == openCharacter ) - { - nestingLevel++; - } - /* A closing character ends a nested object. */ - else if( pAwsIotJsonDocument[ i ] == closeCharacter ) - { - nestingLevel--; - } - } - - i++; - jsonValueLength++; - - /* If the end of the document is reached, this isn't a match. */ - if( i >= awsIotJsonDocumentLength ) - { - return false; - } - } - } - - /* JSON value length calculated; set the output parameter. */ - if( pAwsIotJsonValueLength != NULL ) - { - *pAwsIotJsonValueLength = jsonValueLength; - } - - return true; - } - - i++; - } - - return false; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/src/aws_iot_operation.c b/libraries/aws/common/src/aws_iot_operation.c deleted file mode 100644 index 37da478589..0000000000 --- a/libraries/aws/common/src/aws_iot_operation.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_operation.c - * @brief Functions for common AWS IoT operations. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* AWS IoT include. */ -#include "aws_iot.h" - -/* Error handling include. */ -#include "iot_error.h" - -/*-----------------------------------------------------------*/ - -bool AwsIot_InitLists( IotListDouble_t * pPendingOperationsList, - IotListDouble_t * pSubscriptionsList, - IotMutex_t * pPendingOperationsMutex, - IotMutex_t * pSubscriptionsMutex ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - - /* Flags to track cleanup. */ - bool operationsMutexCreated = false; - bool subscriptionsMutexCreated = false; - - /* Create the mutex guarding the pending operations list. */ - operationsMutexCreated = IotMutex_Create( pPendingOperationsMutex, false ); - - if( operationsMutexCreated == false ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Create the mutex guarding the subscriptions list. */ - subscriptionsMutexCreated = IotMutex_Create( pSubscriptionsMutex, false ); - - if( subscriptionsMutexCreated == false ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Initialize lists. */ - IotListDouble_Create( pPendingOperationsList ); - IotListDouble_Create( pSubscriptionsList ); - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Clean up on error. */ - if( status == false ) - { - if( operationsMutexCreated == true ) - { - IotMutex_Destroy( pPendingOperationsMutex ); - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, - char ** pTopicBuffer, - uint16_t * pOperationTopicLength ) -{ - bool status = true; - uint16_t bufferLength = 0; - uint16_t operationTopicLength = 0; - char * pBuffer = NULL; - - /* Calculate the required topic buffer length. */ - bufferLength = ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH + - pTopicInfo->thingNameLength + - pTopicInfo->operationNameLength + - pTopicInfo->longestSuffixLength ); - - /* Allocate memory for the topic buffer if no topic buffer is given. */ - if( *pTopicBuffer == NULL ) - { - pBuffer = pTopicInfo->mallocString( ( size_t ) bufferLength ); - - if( pBuffer == NULL ) - { - status = false; - } - } - /* Otherwise, use the given topic buffer. */ - else - { - pBuffer = *pTopicBuffer; - } - - if( status == true ) - { - /* Copy the AWS IoT topic prefix into the topic buffer. */ - ( void ) memcpy( pBuffer, AWS_IOT_TOPIC_PREFIX, AWS_IOT_TOPIC_PREFIX_LENGTH ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_TOPIC_PREFIX_LENGTH ); - - /* Copy the Thing Name into the topic buffer. */ - ( void ) memcpy( pBuffer + operationTopicLength, - pTopicInfo->pThingName, - pTopicInfo->thingNameLength ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + pTopicInfo->thingNameLength ); - - /* Copy the Shadow operation string into the topic buffer. */ - ( void ) memcpy( pBuffer + operationTopicLength, - pTopicInfo->pOperationName, - pTopicInfo->operationNameLength ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + pTopicInfo->operationNameLength ); - - /* Set the output parameters. */ - if( *pTopicBuffer == NULL ) - { - *pTopicBuffer = pBuffer; - } - - *pOperationTopicLength = operationTopicLength; - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/src/aws_iot_parser.c b/libraries/aws/common/src/aws_iot_parser.c deleted file mode 100644 index fad00856bb..0000000000 --- a/libraries/aws/common/src/aws_iot_parser.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_parser.c - * @brief Parses topics for Thing Name and status. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* AWS IoT include. */ -#include "aws_iot.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* AWS Parser include. */ -#include "aws_iot_doc_parser.h" - -/** - * @brief Minimum allowed topic length for an AWS IoT status topic. - * - * Topics must contain at least: - * - The common prefix - * - The suffix "/accepted" or "/rejected" - * - 1 character for the Thing Name - * - 2 characters for the operation name and the enclosing slashes - */ -#define MINIMUM_TOPIC_NAME_LENGTH \ - ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH + \ - AWS_IOT_ACCEPTED_SUFFIX_LENGTH + \ - 1 + 2 ) - -/** - * @brief The longest client token accepted by AWS IoT service, per AWS IoT - * service limits. - */ -#define MAX_CLIENT_TOKEN_LENGTH ( 64 ) - -/*-----------------------------------------------------------*/ - -bool AwsIot_GetClientToken( const char * pJsonDocument, - size_t jsonDocumentLength, - const char ** pClientToken, - size_t * pClientTokenLength ) -{ - /* Extract the client token from the JSON document. */ - bool status = AwsIotDocParser_FindValue( pJsonDocument, - jsonDocumentLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - pClientToken, - pClientTokenLength ); - - if( status == true ) - { - /* Check that the length of the client token is valid. */ - if( ( *pClientTokenLength < 2 ) || - ( *pClientTokenLength > MAX_CLIENT_TOKEN_LENGTH ) ) - { - status = false; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool AwsIot_ParseThingName( const char * pTopicName, - uint16_t topicNameLength, - const char ** pThingName, - size_t * pThingNameLength ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - const char * pThingNameStart = NULL; - size_t thingNameLength = 0; - - /* Check that the topic name is at least as long as the minimum allowed. */ - if( topicNameLength < MINIMUM_TOPIC_NAME_LENGTH ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that the given topic starts with the common prefix. */ - if( strncmp( AWS_IOT_TOPIC_PREFIX, - pTopicName, - AWS_IOT_TOPIC_PREFIX_LENGTH ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* The Thing Name starts immediately after the topic prefix. */ - pThingNameStart = pTopicName + AWS_IOT_TOPIC_PREFIX_LENGTH; - - /* Calculate the length of the Thing Name, which is terminated with a '/'. */ - while( ( thingNameLength + AWS_IOT_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) && - ( pThingNameStart[ thingNameLength ] != '/' ) ) - { - thingNameLength++; - } - - /* The end of the topic name was reached without finding a '/'. The topic - * name is invalid. */ - if( thingNameLength + AWS_IOT_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Set the output parameters. */ - *pThingName = pThingNameStart; - *pThingNameLength = thingNameLength; - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, - uint16_t topicNameLength ) -{ - IOT_FUNCTION_ENTRY( AwsIotStatus_t, AWS_IOT_UNKNOWN ); - const char * pSuffixStart = NULL; - - /* Both 'accepted' and 'rejected' topics are of the same length - * The below is a defensive check at run time to ensure that. - */ - Iot_DefaultAssert( AWS_IOT_ACCEPTED_SUFFIX_LENGTH == AWS_IOT_REJECTED_SUFFIX_LENGTH ); - - /* Check that the status topic name is at least as long as the - * "accepted" suffix. This length check will be good for rejected also - * as both are of 8 characters in length. */ - if( topicNameLength > AWS_IOT_ACCEPTED_SUFFIX_LENGTH ) - { - /* Calculate where the "accepted" suffix should start. */ - pSuffixStart = pTopicName + topicNameLength - AWS_IOT_ACCEPTED_SUFFIX_LENGTH; - - /* Check if the end of the status topic name is "/accepted". */ - if( strncmp( pSuffixStart, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ) == 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_ACCEPTED ); - } - - /* Check if the end of the status topic name is "/rejected". */ - if( strncmp( pSuffixStart, - AWS_IOT_REJECTED_SUFFIX, - AWS_IOT_REJECTED_SUFFIX_LENGTH ) == 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_REJECTED ); - } - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/src/aws_iot_subscription.c b/libraries/aws/common/src/aws_iot_subscription.c deleted file mode 100644 index 312197e473..0000000000 --- a/libraries/aws/common/src/aws_iot_subscription.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_subscription.c - * @brief Functions for common AWS IoT subscriptions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* AWS IoT include. */ -#include "aws_iot.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/** - * @brief Modify subscriptions, either by unsubscribing or subscribing. - * - * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or @ref - * mqtt_function_unsubscribesync. - * @param[in] pSubscriptionInfo Information needed to process an MQTT - * operation. - * @param[in] pTopicFilter The topic filter to modify. - * @param[in] topicFilterLength The length of `pTopicFilter`. - * - * @return See the return values of @ref mqtt_function_subscribesync or - * @ref mqtt_function_unsubscribesync. - */ -static IotMqttError_t _modifySubscriptions( AwsIotMqttFunction_t mqttOperation, - const AwsIotSubscriptionInfo_t * pSubscriptionInfo, - const char * pTopicFilter, - uint16_t topicFilterLength ); - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _modifySubscriptions( AwsIotMqttFunction_t mqttOperation, - const AwsIotSubscriptionInfo_t * pSubscriptionInfo, - const char * pTopicFilter, - uint16_t topicFilterLength ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - - /* Per the AWS IoT documentation, topic subscriptions are QoS 1. */ - subscription.qos = IOT_MQTT_QOS_1; - - /* Set the members of the subscription parameter. */ - subscription.pTopicFilter = pTopicFilter; - subscription.topicFilterLength = topicFilterLength; - subscription.callback.pCallbackContext = NULL; - subscription.callback.function = pSubscriptionInfo->callbackFunction; - - /* Call the MQTT operation function. - * Subscription count is 1 in this case. - * None of the flags are set in this call. Hence 0 is passed for flags. - * Please refer to documentation for AwsIotMqttFunction_t for more details. - */ - status = mqttOperation( pSubscriptionInfo->mqttConnection, - &subscription, - 1, - 0, - pSubscriptionInfo->timeout ); - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, - const AwsIotSubscriptionInfo_t * pSubscriptionInfo ) -{ - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_STATUS_PENDING ); - IotMqttError_t acceptedStatus = IOT_MQTT_STATUS_PENDING; - uint16_t topicFilterLength = 0; - - /* Place the topic "accepted" suffix at the end of the topic buffer. */ - ( void ) memcpy( pSubscriptionInfo->pTopicFilterBase - + pSubscriptionInfo->topicFilterBaseLength, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength - + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - - /* Modify the subscription to the "accepted" topic. */ - acceptedStatus = _modifySubscriptions( mqttOperation, - pSubscriptionInfo, - pSubscriptionInfo->pTopicFilterBase, - topicFilterLength ); - - if( acceptedStatus != IOT_MQTT_SUCCESS ) - { - IOT_SET_AND_GOTO_CLEANUP( acceptedStatus ); - } - - /* Place the topic "rejected" suffix at the end of the topic buffer. */ - ( void ) memcpy( pSubscriptionInfo->pTopicFilterBase - + pSubscriptionInfo->topicFilterBaseLength, - AWS_IOT_REJECTED_SUFFIX, - AWS_IOT_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength - + AWS_IOT_REJECTED_SUFFIX_LENGTH ); - - /* Modify the subscription to the "rejected" topic. */ - status = _modifySubscriptions( mqttOperation, - pSubscriptionInfo, - pSubscriptionInfo->pTopicFilterBase, - topicFilterLength ); - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Clean up on error. */ - if( status != IOT_MQTT_SUCCESS ) - { - /* Remove the subscription to the "accepted" topic if the subscription - * to the "rejected" topic failed. */ - if( ( mqttOperation == IotMqtt_SubscribeSync ) && - ( acceptedStatus == IOT_MQTT_SUCCESS ) ) - { - /* Place the topic "accepted" suffix at the end of the topic buffer. */ - ( void ) memcpy( pSubscriptionInfo->pTopicFilterBase - + pSubscriptionInfo->topicFilterBaseLength, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength - + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - - ( void ) _modifySubscriptions( IotMqtt_UnsubscribeSync, - pSubscriptionInfo, - pSubscriptionInfo->pTopicFilterBase, - topicFilterLength ); - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/src/aws_iot_validate.c b/libraries/aws/common/src/aws_iot_validate.c deleted file mode 100644 index fef3320e15..0000000000 --- a/libraries/aws/common/src/aws_iot_validate.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_validate.c - * @brief Validates Thing Names and other parameters to AWS IoT. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* AWS IoT include. */ -#include "aws_iot.h" - -/* Error handling include. */ -#include "iot_error.h" - -/*-----------------------------------------------------------*/ - -bool AwsIot_ValidateThingName( const char * pThingName, - size_t thingNameLength ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - - if( pThingName == NULL ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - if( thingNameLength == 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - if( thingNameLength > AWS_IOT_MAX_THING_NAME_LENGTH ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/test/aws_iot_tests_common.c b/libraries/aws/common/test/aws_iot_tests_common.c deleted file mode 100644 index 956362e204..0000000000 --- a/libraries/aws/common/test/aws_iot_tests_common.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_common.c - * @brief Test runner for AWS IoT Common tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the AWS IoT Common test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunAwsIotCommonTests( bool disableNetworkTests, - bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableNetworkTests; - ( void ) disableLongTests; - - RUN_TEST_GROUP( Aws_Iot_Doc_Unit_Parser ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c b/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c deleted file mode 100644 index c721b33c82..0000000000 --- a/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * AWS IoT Common V1.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_doc_parser.c - * @brief Tests for the AWS IoT Parser. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* AWS IoT parser include. */ -#include "aws_iot_doc_parser.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Wrapper for parsing AWS IoT JSON documents and checking the result. - */ -static void _parseJson( bool expectedResult, - const char * pJsonDocument, - size_t jsonDocumentLength, - const char * pJsonKey, - const char * pExpectedJsonValue, - size_t expectedJsonValueLength ) -{ - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - TEST_ASSERT_EQUAL_INT( expectedResult, - AwsIotDocParser_FindValue( pJsonDocument, - jsonDocumentLength, - pJsonKey, - strlen( pJsonKey ), - &pJsonValue, - &jsonValueLength ) ); - - if( expectedResult == true ) - { - TEST_ASSERT_EQUAL( expectedJsonValueLength, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( pExpectedJsonValue, pJsonValue, jsonValueLength ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Shadow parser tests. - */ -TEST_GROUP( Aws_Iot_Doc_Unit_Parser ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Shadow parser tests. - */ -TEST_SETUP( Aws_Iot_Doc_Unit_Parser ) -{ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Shadow parser tests. - */ -TEST_TEAR_DOWN( Aws_Iot_Doc_Unit_Parser ) -{ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Shadow parser tests. - */ -TEST_GROUP_RUNNER( Aws_Iot_Doc_Unit_Parser ) -{ - RUN_TEST_CASE( Aws_Iot_Doc_Unit_Parser, JsonValid ); - RUN_TEST_CASE( Aws_Iot_Doc_Unit_Parser, JsonInvalid ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing valid JSON documents. - */ -TEST( Aws_Iot_Doc_Unit_Parser, JsonValid ) -{ - /* Parse JSON document with string, int, bool, and null. */ - { - const char pJsonDocument[ 82 ] = "{\"name\" \n\r:\n \"John Smith\", \"age\" :\n\r 30, \n \"isAlive\" : true, \r \"spouse\":null}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( true, pJsonDocument, jsonDocumentLength, "name", "\"John Smith\"", 12 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "age", "30", 2 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "isAlive", "true", 4 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "spouse", "null", 4 ); - - /* Attempt to find a key not in the JSON document. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "address", NULL, 0 ); - } - - /* Parse JSON document with objects and arrays. */ - { - const char pJsonDocument[ 91 ] = "{\"object\" : { \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( true, pJsonDocument, jsonDocumentLength, "object", "{ \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}", 76 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "nestedObject", "{ \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}", 57 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "array", "[[1,2,3],[1,2,3],[1,2,3]]", 25 ); - } - - /* JSON document with escape sequences. */ - { - const char pJsonDocument[ 40 ] = "{\"key\": \"value\", \"ke\\\"y2\": \"\\\"value\\\"\"}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a JSON key that is actually a value. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "value", NULL, 0 ); - - /* Attempt to find a JSON key that is a substring of a key. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "ke", NULL, 0 ); - - /* Find a key and string that contain escaped quotes. */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "ke\\\"y2", "\"\\\"value\\\"\"", 11 ); - } - - /* Short JSON document. */ - { - const char pJsonDocument[ 16 ] = "{\"key\":\"value\"}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a key longer than the JSON document. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "longlonglonglongkey", NULL, 0 ); - } - - /* JSON with '{' '}' in value*/ - { - const char pJsonDocument[ 18 ] = "{\"key\":\"valu}{e\"}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a value with '{' '}' */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"valu}{e\"", 9 ); - } - - /* JSON with '{' '}' in value in nested level*/ - { - const char pJsonDocument[ 42 ] = "{\"key\":{\"key2\":\"{{{{}}\", \"key3\":\"value\"}}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a value which has value a string with '{' '}' */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "{\"key2\":\"{{{{}}\", \"key3\":\"value\"}", 33 ); - } - - /* JSON with objects elements of the array */ - { - const char pJsonDocument[ 63 ] = "{\"key\":\"value\", \"arr\": [{\"key1\":\"value1\"}, {\"key2\":\"value2\"}]}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a value with objects as members of the array */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "arr", "[{\"key1\":\"value1\"}, {\"key2\":\"value2\"}]", 38 ); - } - - /* JSON with with false in the value */ - { - const char pJsonDocument[ 33 ] = "{\"key\":\"value\", \"keybool\":false}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a boolean value */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "keybool", "false", 5 ); - } - - /* JSON with with different elements in the array */ - { - const char pJsonDocument[ 85 ] = "{\"key\":\"value\", \"arr\":[4, \"string\", true, {\"key1\":\"value1\", \"arr1\":[1,2,3]}, 99]}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - /* Attempt to find a value with different elements in the array */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "arr", "[4, \"string\", true, {\"key1\":\"value1\", \"arr1\":[1,2,3]}, 99]", 58 ); - } -} - - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that parsing invalid JSON documents does not read out-of-bounds - * memory. - */ -TEST( Aws_Iot_Doc_Unit_Parser, JsonInvalid ) -{ - /* JSON key not followed by a : */ - { - const char pJsonDocument[ 16 ] = "{\"string\" "; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* JSON value not followed by a , */ - { - const char pJsonDocument[ 30 ] = "{\"int\": 10 \"string\": \"hello\"}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); - } - - /* JSON key with no value. */ - { - const char pJsonDocument[ 18 ] = "{\"string\": "; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* Unterminated JSON primitive. */ - { - const char pJsonDocument[ 12 ] = "{\"int\":1000"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); - } - - /* Unterminated JSON string (ending is an escaped quote). */ - { - const char pJsonDocument[ 15 ] = "{\"string\": \"\\\""; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* Unterminated JSON string (ending is not a quote). */ - { - const char pJsonDocument[ 15 ] = "{\"string\": \" \\"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* Unterminated JSON object. */ - { - const char pJsonDocument[ 42 ] = "{\"object\": {\"key\": { \"nestedKey\":\"value\"}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "object", NULL, 0 ); - } - - /* Unterminated JSON array. */ - { - const char pJsonDocument[ 27 ] = "{\"array\": [[1,2,3],[1,2,3]"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( false, pJsonDocument, jsonDocumentLength, "array", NULL, 0 ); - } - - /* Invalid JSON not validated for correctness. Incorrect value. */ - { - const char pJsonDocument[ 30 ] = "{\"key\": \"value\", \"key2\": {]}}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); - } - - /* Invalid JSON not validated for correctness. Incorrect parenthesis. */ - { - const char pJsonDocument[ 30 ] = "{\"key\": \"value\", \"key2\": {}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); - } - - /* Invalid JSON not validated for correctness. Incorrect `,`s. */ - { - const char pJsonDocument[ 52 ] = "{\"key\": \"value\", \"key2\": \"value1\" \"key3\": \"value3\"}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); - } - - /* Invalid JSON not validated for correctness. Incorrect nesting level. - * Missing `:`. - */ - { - const char pJsonDocument[ 43 ] = "{\"key\": \"value\", \"key2\":{\"key3\" \"value3\"}}"; - size_t jsonDocumentLength = strlen( pJsonDocument ); - - _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/CMakeLists.txt b/libraries/aws/defender/CMakeLists.txt deleted file mode 100644 index 5c8b49b65f..0000000000 --- a/libraries/aws/defender/CMakeLists.txt +++ /dev/null @@ -1,65 +0,0 @@ -# Defender library source files. -set( DEFENDER_SOURCES - src/aws_iot_defender_api.c - src/aws_iot_defender_collector.c - src/aws_iot_defender_mqtt.c ) - -# Defender library target. -add_library( awsiotdefender - ${CONFIG_HEADER_PATH}/iot_config.h - ${DEFENDER_SOURCES} - include/aws_iot_defender.h - src/private/aws_iot_defender_internal.h ) - -# Defender public include path. -target_include_directories( awsiotdefender PUBLIC include ) - -# Link required libraries. -target_link_libraries( awsiotdefender PRIVATE awsiotcommon iotserializer iotbase ) - -# Defender is currently implemented on MQTT, so link MQTT as a public dependency. -target_link_libraries( awsiotdefender PUBLIC iotmqtt ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotdefender PRIVATE unity ) -endif() - -# Organization of Defender in folders. -set_property( TARGET awsiotdefender PROPERTY FOLDER libraries/aws ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES include/aws_iot_defender.h ) -source_group( src\\private src/private/aws_iot_defender_internal.h ) -source_group( src FILES ${DEFENDER_SOURCES} ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # Defender system test sources. - set( DEFENDER_SYSTEM_TEST_SOURCES - test/unit/aws_iot_tests_defender_unit.c - test/system/aws_iot_tests_defender_system.c ) - - # Defender tests executable. - add_executable( aws_iot_tests_defender - ${DEFENDER_SYSTEM_TEST_SOURCES} - test/aws_iot_tests_defender.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( aws_iot_tests_defender PRIVATE - -DRunTests=RunDefenderTests ) - - # The Defender tests need the internal Defender header. - target_include_directories( aws_iot_tests_defender PRIVATE src ) - - # Defender tests library dependencies. - target_link_libraries( aws_iot_tests_defender PRIVATE - awsiotdefender awsiotcommon iotserializer iotbase - unity tinycbor iot_mqtt_mock ) - - # Organization of Defender tests in folders. - set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER tests ) - source_group( system FILES ${DEFENDER_SYSTEM_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_defender.c ) -endif() diff --git a/libraries/aws/defender/include/aws_iot_defender.h b/libraries/aws/defender/include/aws_iot_defender.h deleted file mode 100644 index b56a3ec5ff..0000000000 --- a/libraries/aws/defender/include/aws_iot_defender.h +++ /dev/null @@ -1,405 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_defender.h - * @brief User-facing functions and structs of AWS IoT Device Defender library. - */ - -#ifndef AWS_IOT_DEFENDER_H_ -#define AWS_IOT_DEFENDER_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* MQTT include. */ -#include "iot_mqtt.h" - -/** - * @page Defender_constants Constants - * @brief Defined constants of the Defender library. - * - @ref DefenderFormat "Serialization Format" - * - @ref DefenderMetricsFlags "Metrics Flags" - * - @ref DefenderInitializers "Initializers" - */ - -/** - * @anchor DefenderFormat - * @name Serialization Format - * - * @brief Format constants: Cbor or Json. - * @warning JSON format is not supported for now. - */ -/**@{ */ -#define AWS_IOT_DEFENDER_FORMAT_CBOR 1 /**< CBOR format. */ -#define AWS_IOT_DEFENDER_FORMAT_JSON 2 /**< JSON format (NOT supported). */ -/**@} */ - -/** - * @anchor DefenderMetricsFlags - * @name Metrics Flags - * - * @brief Bit flags or metrics used by @ref defender_function_setmetrics function. - * - * These metrics are subset of metrics supported by AWS IoT Device Defender service. For details, - * refer to developer document of [AWS IoT Device Defender](https://docs.aws.amazon.com/iot/latest/developerguide/device-defender-detect.html#DetectMetricsMessages). - */ -/**@{ */ -#define AWS_IOT_DEFENDER_METRICS_ALL 0xffffffff /**< Flag to indicate including all metrics. */ - -#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL 0x00000001 /**< Total count of established TCP connections. */ -#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR 0x00000004 /**< Remote address (IP:port) of established TCP connections. For example, 192.168.0.1:8000. */ - -/** - * Connections metrics including only remote address. Local port number is not supported. - * - */ -#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS \ - ( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) \ - - -/** - * Established connections metrics including connections metrics and total count. - */ -#define AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED \ - ( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS | AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) \ - -/**@} end of DefenderMetricsFlags */ - -/** - * @anchor DefenderInitializers - * @name Initializers - * - * @brief Initializers of data handles. - */ -/**@{ */ -#define AWS_IOT_DEFENDER_CALLBACK_INITIALIZER \ - { \ - .pCallbackContext = NULL, \ - .function = NULL \ - } /**< Initializer of #AwsIotDefenderCallback_t. */ -#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER \ - { .pClientIdentifier = NULL, \ - .clientIdentifierLength = 0, \ - .mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER, \ - .callback = AWS_IOT_DEFENDER_CALLBACK_INITIALIZER \ - } /**< Initializer of #AwsIotDefenderStartInfo_t. */ -/**@} */ - -/** - * @defgroup Defender_datatypes_enums Enumerated types - * @brief Enumerated types of the Defender library. - * - * @defgroup Defender_datatypes_paramstructs Parameter structures - * @brief Structures passed as parameters to [Defender functions](@ref defender_functions) - * - * These structures are passed as parameters to library functions. - * Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. - */ - -/** - * @ingroup Defender_datatypes_enums - * @brief Metrics group options for AwsIotDefender_SetMetrics() function. - * - * Metrics group is defined based on the "metrics blocks" listed in [AWS IoT Defender document] - * (https://docs.aws.amazon.com/iot/latest/developerguide/device-defender-detect.html#DetectMetricsMessages) - */ -typedef enum -{ - AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, /**< TCP connection metrics group. */ -} AwsIotDefenderMetricsGroup_t; - -/** - * @ingroup Defender_datatypes_enums - * @brief Return codes of defender functions. - */ -typedef enum -{ - AWS_IOT_DEFENDER_SUCCESS = 0, /**< Defender operation completed successfully. */ - AWS_IOT_DEFENDER_INVALID_INPUT, /**< At least one input parameter is invalid. */ - AWS_IOT_DEFENDER_ALREADY_STARTED, /**< Defender has been already started. */ - AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, /**< Given period is too short. */ - AWS_IOT_DEFENDER_ERROR_NO_MEMORY, /**< Defender operation failed due to memory allocation failure. */ - AWS_IOT_DEFENDER_INTERNAL_FAILURE /**< Defender operation failed due to internal unexpected cause. */ -} AwsIotDefenderError_t; - -/** - * @ingroup Defender_datatypes_enums - * @brief Event codes passed into AwsIotDefenderCallbackInfo_t - */ -typedef enum -{ - AWS_IOT_DEFENDER_METRICS_ACCEPTED = 0, /**< Metrics report was accepted by defender service. */ - AWS_IOT_DEFENDER_METRICS_REJECTED, /**< Metrics report was rejected by defender service. */ - AWS_IOT_DEFENDER_FAILURE_MQTT, /**< Defender failed to perform MQTT operation. */ - AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT /**< Defender failed to create metrics report. */ -} AwsIotDefenderEventType_t; - -/** - * @defgroup Defender_datatypes_paramstructs Parameter structures - * @brief Structures passed as parameters to [Defender functions](@ref defender_functions) - * - * These structures are passed as parameters to library functions. - * Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. - */ - -/** - * @ingroup Defender_datatypes_paramstructs - * @brief Callback parameters. - */ -typedef struct AwsIotDefenderCallbackInfo -{ - const uint8_t * pMetricsReport; /**< The published metrics report(could be NULL). */ - size_t metricsReportLength; /**< Length of the published metrics report. */ - const uint8_t * pPayload; /**< The received message if there is one(could be NULL). */ - size_t payloadLength; /**< Length of the message. */ - AwsIotDefenderEventType_t eventType; /**< Event code(always valid). */ -} AwsIotDefenderCallbackInfo_t; - -/** - * @ingroup Defender_datatypes_paramstructs - * @brief User provided callback handle. - */ -typedef struct AwsIotDefenderCallback -{ - void * pCallbackContext; /**< The callback context for caller's use (optional). */ - void ( * function )( void *, - AwsIotDefenderCallbackInfo_t * const ); /**< Callback function signature(optional). */ -} AwsIotDefenderCallback_t; - -/** - * @ingroup Defender_datatypes_paramstructs - * @brief Parameters of AwsIotDefender_Start function. - */ -typedef struct AwsIotDefenderStartInfo -{ - const char * pClientIdentifier; /**< @brief MQTT client identifier (required). */ - uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier (required). */ - IotMqttConnection_t mqttConnection; /**< @brief MQTT connection used by defender (required). */ - AwsIotDefenderCallback_t callback; /**< Callback function parameter (optional). */ -} AwsIotDefenderStartInfo_t; - -/** - * @functionspage{defender,Device Defender library} - * - @functionname{defender_function_setmetrics} - * - @functionname{defender_function_start} - * - @functionname{defender_function_stop} - * - @functionname{defender_function_setperiod} - * - @functionname{defender_function_getperiod} - * - @functionname{defender_function_strerror} - * - @functionname{defender_function_EventType} - */ - -/** - * @functionpage{AwsIotDefender_SetMetrics,defender,setmetrics} - * @functionpage{AwsIotDefender_Start,defender,start} - * @functionpage{AwsIotDefender_Stop,defender,stop} - * @functionpage{AwsIotDefender_SetPeriod,defender,setperiod} - * @functionpage{AwsIotDefender_GetPeriod,defender,getperiod} - * @functionpage{AwsIotDefender_strerror,defender,strerror} - * @functionpage{AwsIotDefender_EventType,defender,EventType} - */ - -/** - * @brief Set metrics that defender agent needs to collect for a metrics group. - * - * * If defender agent is not started, this function will provide the metrics to be collected. - * * If defender agent is started, this function will update the metrics and take effect in defender agent's next iteration. - * - * @param[in] metricsGroup Metrics group defined in #AwsIotDefenderMetricsGroup_t - * @param[in] metrics Bit-flags to specify what metrics to collect. - * If all metrics in a group is needed, simply set metrics to #AWS_IOT_DEFENDER_METRICS_ALL. - * See @ref DefenderMetricsFlags "Metrics flags" for details. - * - * @return - * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. - * * If metricsGroup is invalid, #AWS_IOT_DEFENDER_INVALID_INPUT is returned. - * @note This function is thread safe. - * @note @ref defender_function_stop will clear the metrics. - */ - -/* @[declare_defender_setmetrics] */ -AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, - uint32_t metrics ); -/* @[declare_defender_setmetrics] */ - -/** - * @brief Start the defender agent. - * - * @param[in] pStartInfo Pointer of parameters of start function - * - * Periodically, defender agent collects metrics and publish to specific AWS reserved MQTT topic. - * - * @return - * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. - * * If pStartInfo is invalid, #AWS_IOT_DEFENDER_INVALID_INPUT is returned. - * * If memory allocation fails, #AWS_IOT_DEFENDER_ERROR_NO_MEMORY is returned. - * * If defender is already started, #AWS_IOT_DEFENDER_ALREADY_STARTED is returned. - * - * @warning This function is not thread safe. - * - * @note No need to manage the memory allocated for #AwsIotDefenderCallbackInfo_t. This function save the information internally. - * - * Example: - * - * @code{c} - * - * // assume valid IotMqttConnection_t is created and available. - * const IotMqttConnection_t _mqttConnection; - * // use AWS thing name as client identifier - * const char * pClientIdentifier = "AwsThingName"; - * - * void logDefenderCallback( void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) - * { - * if ( pCallbackInfo->eventType == AWS_IOT_DEFENDER_METRICS_ACCEPTED ) - * { - * // log info: metrics report accepted by defender service is a happy case - * } - * else - * { - * // log error: pCallbackInfo->eventType - * } - * - * if ( pCallbackInfo->pPayload != NULL ) - * { - * // log info: pCallbackInfo->pPayload with length pCallbackInfo->payloadLength - * } - * - * if ( pCallbackInfo->pMetricsReport != NULL ) - * { - * // log info: pCallbackInfo->pMetricsReport with length pCallbackInfo->metricsReportLength - * } - * } - * - * void startDefender() - * { - * // define a simple callback function which simply logs - * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback, .pCallbackContext = NULL }; - * - * // define parameters of AwsIotDefender_Start function - * // Note: This example assumes MQTT connection is already established and metrics library is initialized. - * const AwsIotDefenderStartInfo_t startInfo = - * { - * .pClientIdentifier = pClientIdentifier, - * .clientIdentifierLength = strlen( pClientIdentifier ), - * .mqttConnection = _mqttConnection, - * .callback = callback - * }; - * - * // specify two TCP connections metrics: total count and local port - * AwsIotDefenderError_t error = AwsIotDefender_SetMetrics(AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - * AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL | - * AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ); - * - * if ( error == AWS_IOT_DEFENDER_SUCCESS ) - * { - * // set metrics report period to 10 minutes (600 seconds) - * error = AwsIotDefender_SetPeriod( 600 ); - * } - * - * if (error == AWS_IOT_DEFENDER_SUCCESS) - * { - * // start the defender - * error = AwsIotDefender_Start( &startInfo ); - * } - * - * if ( error != AWS_IOT_DEFENDER_SUCCESS ) - * { - * const char * pError = AwsIotDefender_strerror( error ); - * // log error: pError - * } - * } - * - * void stopDefender() - * { - * //stop the defender - * AwsIotDefender_Stop(); - * } - * - * @endcode - */ -/* @[declare_defender_start] */ -AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ); -/* @[declare_defender_start] */ - -/** - * @brief Stop the defender agent. - * - * It waits for the current metrics-publishing iteration to finish before freeing the resource allocated. - * It also clears the metrics set previously so that user is expected to SetMetrics again before restarting defender agent. - * - * @warning This function must be called after successfully calling @ref defender_function_start. - * @warning This function is not thread safe. - */ -/* @[declare_defender_stop] */ -void AwsIotDefender_Stop( void ); -/* @[declare_defender_stop] */ - -/** - * @brief Set period in seconds. - * - * - * @param[in] periodSeconds Period is specified in seconds. Minimum is 300 (5 minutes) - * - * @return - * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. - * * If defender is not started yet, AWS_IOT_DEFENDER_NOT_STARTED is returned. - * - * @warning This function is not thread safe. - * - * @note If this function is called when defender agent is started, the period is re-calculated and updated in next iteration. - */ -/* @[declare_defender_setperiod] */ -AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ); -/* @[declare_defender_setperiod] */ - -/** - * @brief Get period in seconds. - * - * @return Current period in seconds - */ -/* @[declare_defender_getperiod] */ -uint32_t AwsIotDefender_GetPeriod( void ); -/* @[declare_defender_getperiod] */ - -/** - * @brief Return a string that describes #AwsIotDefenderError_t - * - * @return A string that describes given #AwsIotDefenderError_t - */ -/* @[declare_defender_strerror] */ -const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); -/* @[declare_defender_strerror] */ - -/** - * @brief Return a string that describes #AwsIotDefenderEventType_t - * - * @return A string that describes given #AwsIotDefenderEventType_t - */ -/* @[declare_defender_EventType] */ -const char * AwsIotDefender_EventType( AwsIotDefenderEventType_t eventType ); -/* @[declare_defender_EventType] */ -#endif /* AWS_IOT_DEFENDER_H_ */ diff --git a/libraries/aws/defender/lexicon.txt b/libraries/aws/defender/lexicon.txt deleted file mode 100644 index 2e056f26e4..0000000000 --- a/libraries/aws/defender/lexicon.txt +++ /dev/null @@ -1,64 +0,0 @@ -addr -alpn -aws -awsiotdefender -awsiotdefendercallback -awsiotdefendercallbackinfo -awsiotdefendereventtype -awsiotdefendermetricsgroup -awsiotdefenderstartinfo -callbackinfo -cbor -clientidentifierlength -com -config -const -defendermetricsflags -detectmetricsmessages -developerguide -doesn -endif -enum -eventtype -getperiod -html -http -https -ifndef -iot -iotmqttconnectinfo -iotmqttconnection -iotserializerencoderobject -ip -json -malloc -metricsflag -metricsflagsnapshot -metricsreportlength -mqtt -mqttconnection -mutex -onlinepubs -opengroup -org -payloadlength -pcallbackcontext -pcallbackinfo -pclientidentifier -pdatabuffer -periodmillisecond -perror -pmetricsreport -ppayload -pre -reportaccepted -setmetrics -setperiod -startinfo -stdout -strerror -tcp -thingname -todo -uint -xffffffff diff --git a/libraries/aws/defender/src/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c deleted file mode 100644 index 07cb6fe4f3..0000000000 --- a/libraries/aws/defender/src/aws_iot_defender_api.c +++ /dev/null @@ -1,597 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" -/* Error handling include. */ -#include "iot_error.h" - -/* Defender internal include. */ -#include "private/aws_iot_defender_internal.h" - -/* Task pool include. */ -#include "iot_taskpool.h" - -/* Clock include. */ -#include "platform/iot_clock.h" - -#define WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) -#define MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ ( ( uint32_t ) 1 ) -#define MAX_CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) 128 ) - -#if WAIT_METRICS_JOB_MAX_SECONDS < AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS - #error "_WAIT_METRICS_JOB_MAX_SECONDS must be greater than AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS." -#endif - -/** - * callback registered on accept topic. - */ -void _acceptCallback( void * pArgument, - IotMqttCallbackParam_t * const pPublish ); - -/** - * callback registered on reject topic. - */ -void _rejectCallback( void * pArgument, - IotMqttCallbackParam_t * const pPublish ); - -/** - * subscribe to defender MQTT topics. - */ -static IotMqttError_t _metricsSubscribeRoutine(); - -/** - * Publish to defender MQTT topic. - */ -static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ); - -/* Unsubscribe from defender MQTT topic. */ -static void _unsubscribeMqtt(); - -/* Code to handle application callback */ -void _handleApplicationCallback( AwsIotDefenderEventType_t event, - IotMqttCallbackParam_t * const pPublish ); - - -/*------------------- Below are global variables. ---------------------------*/ - -/* Restart defender state machine unless there is an error */ - - -/* Define global metrics and initialize metrics flags array to zero. */ -_defenderMetrics_t _AwsIotDefenderMetrics = -{ - .metricsFlag = { 0 } -}; -/* MQTT callback info for reporting accept or reject status for subscribe / unsubscribe */ -IotMqttCallbackInfo_t _acceptCallbackInfo = { .function = _acceptCallback, .pCallbackContext = NULL }; -IotMqttCallbackInfo_t _rejectCallbackInfo = { .function = _rejectCallback, .pCallbackContext = NULL }; - -/** - * Period between reports in milliseconds. - * Set default value. - */ -static uint32_t _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); - -static IotTaskPoolJobStorage_t _metricsPublishJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; -static IotTaskPoolJob_t _metricsPublishJob = IOT_TASKPOOL_JOB_INITIALIZER; - -/* Semaphore to prevent stop during publish */ -static IotSemaphore_t _doneSem; - -/* - * State variable to indicate if defender has been started successfully, initialized to false. - * There is no lock to protect it so the functions referencing it are not thread safe. - */ -static bool _started = false; - -/* Internal copy of startInfo so that user's input doesn't have to be valid all the time. */ -AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - -/*-----------------------------------------------------------*/ - -AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, - uint32_t metrics ) -{ - if( metricsGroup >= ( AwsIotDefenderMetricsGroup_t ) DEFENDER_METRICS_GROUP_COUNT ) - { - IotLogError( "Input metrics group is invalid. Please use AwsIotDefenderMetricsGroup_t data type." ); - - return AWS_IOT_DEFENDER_INVALID_INPUT; - } - - /* If started, it needs to lock the metrics to protect concurrent read from metrics timer callback. */ - if( _started ) - { - IotMutex_Lock( &_AwsIotDefenderMetrics.mutex ); - - _AwsIotDefenderMetrics.metricsFlag[ metricsGroup ] = metrics; - - IotMutex_Unlock( &_AwsIotDefenderMetrics.mutex ); - } - else - { - _AwsIotDefenderMetrics.metricsFlag[ metricsGroup ] = metrics; - } - - IotLogInfo( "Metrics are successfully updated." ); - - return AWS_IOT_DEFENDER_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ) -{ - IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - IOT_FUNCTION_ENTRY( AwsIotDefenderError_t, AWS_IOT_DEFENDER_SUCCESS ); - - if( ( pStartInfo == NULL ) || - ( pStartInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) || - ( pStartInfo->pClientIdentifier == NULL ) || - ( pStartInfo->clientIdentifierLength > MAX_CLIENT_IDENTIFIER_LENGTH ) ) - { - IotLogError( "Input startInfo is invalid." ); - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_DEFENDER_INVALID_INPUT ); - } - - /* Assert system task pool is pre-created! */ - AwsIotDefender_Assert( IOT_SYSTEM_TASKPOOL != NULL ); - - /* Silence warnings when asserts are disabled. */ - ( void ) taskPoolError; - - /* Initialize flow control states to false. */ - bool buildTopicsNamesSuccess = false, - doneSemaphoreCreateSuccess = false, - metricsMutexCreateSuccess = false; - - if( !_started ) - { - /* Get the pointers to the encoder function tables. */ - #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - _pAwsIotDefenderDecoder = IotSerializer_GetCborDecoder(); - _pAwsIotDefenderEncoder = IotSerializer_GetCborEncoder(); - #else - #error "AWS IOT Defender library supports only CBOR encoder." - #endif - - /* copy input start info into global variable _startInfo */ - _startInfo = *pStartInfo; - - status = AwsIotDefenderInternal_BuildTopicsNames(); - - buildTopicsNamesSuccess = ( status == AWS_IOT_DEFENDER_SUCCESS ); - - if( buildTopicsNamesSuccess ) - { - /* Create a binary semaphore with initial value 1. */ - doneSemaphoreCreateSuccess = IotSemaphore_Create( &_doneSem, MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ, 1 ); - } - else - { - status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - } - - if( doneSemaphoreCreateSuccess ) - { - metricsMutexCreateSuccess = IotMutex_Create( &_AwsIotDefenderMetrics.mutex, false ); - } - else - { - status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - } - - if( metricsMutexCreateSuccess ) - { - /* Subscribe to Metrics Publish topic */ - mqttError = _metricsSubscribeRoutine(); - - if( mqttError != IOT_MQTT_SUCCESS ) - { - status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - } - } - else - { - status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - } - - if( status == AWS_IOT_DEFENDER_SUCCESS ) - { - /* Create metrics publish job, which will periodically publish metrics */ - taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); - /* Silence warnings when asserts are disabled. */ - ( void ) taskPoolError; - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - /* Schedule Publish Job */ - taskPoolError = IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, 0 ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - _started = true; - status = AWS_IOT_DEFENDER_SUCCESS; - IotLogInfo( "Defender agent has successfully started." ); - } - - /* Do the cleanup jobs if not success. */ - if( status != AWS_IOT_DEFENDER_SUCCESS ) - { - /* reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ - _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - - if( buildTopicsNamesSuccess ) - { - AwsIotDefenderInternal_DeleteTopicsNames(); - } - - if( doneSemaphoreCreateSuccess ) - { - IotSemaphore_Destroy( &_doneSem ); - } - - if( metricsMutexCreateSuccess ) - { - IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); - } - - IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( status ) ); - } - } - else - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_DEFENDER_ALREADY_STARTED ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -void AwsIotDefender_Stop( void ) -{ - if( !_started ) - { - IotLogWarn( "Defender has not started yet." ); - } - else - { - /* Wait for all the metrics processing to be done, if there are outstanding requests */ - IotSemaphore_Wait( &_doneSem ); - - IotTaskPoolJobStatus_t status; - IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, &status ); - - /* If cancel failed, let it sleep for a while and hope everything finishes. */ - if( taskPoolError != IOT_TASKPOOL_SUCCESS ) - { - IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); - IotClock_SleepMs( WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); - } - - _unsubscribeMqtt(); - - /* Destroy metrics' mutex. */ - IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); - - /* Post so that taken semaphore is returned */ - IotSemaphore_Post( &_doneSem ); - /* Destroy 'done' semaphore. */ - IotSemaphore_Destroy( &_doneSem ); - - /* Delete topics names. */ - AwsIotDefenderInternal_DeleteTopicsNames(); - - /* Delete report if it was created */ - AwsIotDefenderInternal_DeleteReport(); - - /* Reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ - _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - - /* Reset _periodMilliSecond to default value. */ - _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); - - /* Reset metrics flag array to 0. */ - memset( _AwsIotDefenderMetrics.metricsFlag, 0, sizeof( _AwsIotDefenderMetrics.metricsFlag ) ); - - /* Set to not started. */ - _started = false; - - IotLogInfo( "Defender agent has stopped." ); - } -} - -/*-----------------------------------------------------------*/ - -AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ) -{ - AwsIotDefenderError_t defenderError = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - - /* period can not be too short unless this is test mode. */ - if( periodSeconds < AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ) - { - defenderError = AWS_IOT_DEFENDER_PERIOD_TOO_SHORT; - IotLogError( "Input period is too short. It must be greater than %d seconds.", AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); - } - else - { - _periodMilliSecond = _defenderToMilliseconds( periodSeconds ); - - defenderError = AWS_IOT_DEFENDER_SUCCESS; - IotLogInfo( "Period has been set to %d seconds successfully.", periodSeconds ); - } - - return defenderError; -} - -/*-----------------------------------------------------------*/ - -uint32_t AwsIotDefender_GetPeriod( void ) -{ - return _defenderToSeconds( _periodMilliSecond ); -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) -{ - const char * pMessage = NULL; - - switch( error ) - { - case AWS_IOT_DEFENDER_SUCCESS: - pMessage = "SUCCESS"; - break; - - case AWS_IOT_DEFENDER_INVALID_INPUT: - pMessage = "INVALID INPUT"; - break; - - case AWS_IOT_DEFENDER_ALREADY_STARTED: - pMessage = "ALREADY STARTED"; - break; - - case AWS_IOT_DEFENDER_PERIOD_TOO_SHORT: - pMessage = "PERIOD TOO SHORT"; - break; - - case AWS_IOT_DEFENDER_ERROR_NO_MEMORY: - pMessage = "NO MEMORY"; - break; - - case AWS_IOT_DEFENDER_INTERNAL_FAILURE: - pMessage = "INTERNAL FAILURE"; - break; - - default: - pMessage = "INVALID STATUS"; - break; - } - - return pMessage; -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotDefender_EventType( AwsIotDefenderEventType_t eventType ) -{ - const char * pEvent = NULL; - - /* Convert defender event to string */ - switch( eventType ) - { - case AWS_IOT_DEFENDER_METRICS_ACCEPTED: - pEvent = "Defender Metrics accepted"; - break; - - case AWS_IOT_DEFENDER_METRICS_REJECTED: - pEvent = "Defender Metrics rejected"; - break; - - case AWS_IOT_DEFENDER_FAILURE_MQTT: - pEvent = "Defender MQTT operation failed"; - break; - - case AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT: /**< Defender failed to create metrics report. */ - pEvent = "Defender failed to create metrics Report"; - break; - - default: - pEvent = "Defender Unknown Event"; - } - - return pEvent; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _metricsSubscribeRoutine() -{ - IotLogDebug( "Metrics Subscribe starts." ); - - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - /* Subscribe to accept/reject MQTT topics. */ - mqttError = AwsIotDefenderInternal_MqttSubscribe(); - - if( mqttError != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to perform MQTT operations, with error %s.", IotMqtt_strerror( mqttError ) ); - _unsubscribeMqtt(); - } - - return mqttError; -} - -/*-----------------------------------------------------------*/ - -static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ) -{ - /* Unused parameter; silence the compiler. */ - ( void ) pTaskPool; - ( void ) pJob; - ( void ) pUserContext; - - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - bool reportCreated = false; - IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; - - if( !IotSemaphore_TryWait( &_doneSem ) ) - { - IotLogError( "Defender has been stopped or the previous metrics is in process. No further action." ); - } - else - { - /* Create serialized metrics report. */ - reportCreated = AwsIotDefenderInternal_CreateReport(); - - /* If Report is created successfully. */ - if( reportCreated ) - { - /* Publish report to defender topic. */ - mqttError = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), - AwsIotDefenderInternal_GetReportBufferSize() ); - - if( mqttError == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "Metrics report has been published successfully." ); - } - else - { - IotLogError( "Failed to perform MQTT publish, with error %s.", IotMqtt_strerror( mqttError ) ); - } - } - else - { - IotLogError( "Failed to create report" ); - } - - if( ( mqttError != IOT_MQTT_SUCCESS ) || ( !reportCreated ) ) - { - if( reportCreated ) - { - AwsIotDefenderInternal_DeleteReport(); - } - - _unsubscribeMqtt(); - /* Invoke user's callback if there is. */ - _handleApplicationCallback( AWS_IOT_DEFENDER_FAILURE_MQTT, NULL ); - } - else - { - /* Re-schedule metrics job with period as deferred interval. */ - taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, _periodMilliSecond ); - } - - /* Give Done semaphore so AwsIotDefender_Stop() can proceed */ - IotSemaphore_Post( &_doneSem ); - } - - IotLogDebug( "Publish job ends." ); -} - -/*-----------------------------------------------------------*/ - -void _unsubscribeMqtt() -{ - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - mqttError = AwsIotDefenderInternal_MqttUnSubscribe(); - - if( mqttError != IOT_MQTT_SUCCESS ) - { - IotLogError( "Unsubscribe failed for defender agent" ); - } -} - -/*-----------------------------------------------------------*/ - -void _acceptCallback( void * pArgument, - IotMqttCallbackParam_t * const pPublish ) -{ - ( void ) pArgument; - - IotLogInfo( "Metrics report was accepted by defender service." ); - - /* In accepted case, report and MQTT message must exist. */ - AwsIotDefender_Assert( AwsIotDefenderInternal_GetReportBuffer() ); - AwsIotDefender_Assert( pPublish->u.message.info.pPayload ); - - /* Invoke user's callback with accept event. */ - _handleApplicationCallback( AWS_IOT_DEFENDER_METRICS_ACCEPTED, pPublish ); - /* Delete report if exists */ - AwsIotDefenderInternal_DeleteReport(); -} - -/*-----------------------------------------------------------*/ - -void _rejectCallback( void * pArgument, - IotMqttCallbackParam_t * const pPublish ) -{ - ( void ) pArgument; - - IotLogError( "Metrics report was rejected by defender service." ); - - /* In rejected case, MQTT message must exist. */ - AwsIotDefender_Assert( pPublish->u.message.info.pPayload ); - - /* Invoke user's callback with rejected event. */ - _handleApplicationCallback( AWS_IOT_DEFENDER_METRICS_REJECTED, pPublish ); - /* Delete report if exists */ - AwsIotDefenderInternal_DeleteReport(); -} - -/*-----------------------------------------------------------*/ - -void _handleApplicationCallback( AwsIotDefenderEventType_t event, - IotMqttCallbackParam_t * const pPublish ) -{ - /* Invoke user's callback with event. */ - AwsIotDefenderCallbackInfo_t callbackInfo; - - if( _startInfo.callback.function != NULL ) - { - callbackInfo.eventType = event; - - callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); - callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); - - if( pPublish == NULL ) - { - callbackInfo.pPayload = NULL; - callbackInfo.payloadLength = 0; - } - else - { - callbackInfo.pPayload = pPublish->u.message.info.pPayload; - callbackInfo.payloadLength = pPublish->u.message.info.payloadLength; - } - - _startInfo.callback.function( _startInfo.callback.pCallbackContext, &callbackInfo ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/src/aws_iot_defender_collector.c b/libraries/aws/defender/src/aws_iot_defender_collector.c deleted file mode 100644 index d1ffde8f12..0000000000 --- a/libraries/aws/defender/src/aws_iot_defender_collector.c +++ /dev/null @@ -1,441 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* Standard includes */ -#include - -/* Defender internal include. */ -#include "private/aws_iot_defender_internal.h" - -#include "platform/iot_metrics.h" - -#include "platform/iot_clock.h" - -/* Used in debugging. It will decode the report with cbor format and print to stdout. */ -#define DEBUG_CBOR_PRINT 0 - -#define HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) -#define REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) -#define VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) -#define VERSION_1_0 "1.0" /* Used by defender service to indicate the schema change of report, e.g. adding new field. */ -#define METRICS_TAG AwsIotDefenderInternal_SelectTag( "metrics", "met" ) - -#define TCP_CONN_TAG AwsIotDefenderInternal_SelectTag( "tcp_connections", "tc" ) -#define EST_CONN_TAG AwsIotDefenderInternal_SelectTag( "established_connections", "ec" ) -#define TOTAL_TAG AwsIotDefenderInternal_SelectTag( "total", "t" ) -#define CONN_TAG AwsIotDefenderInternal_SelectTag( "connections", "cs" ) -#define REMOTE_ADDR_TAG AwsIotDefenderInternal_SelectTag( "remote_addr", "rad" ) - -/** - * Structure to hold a metrics report. - */ -typedef struct _metricsReport -{ - IotSerializerEncoderObject_t object; /* Encoder object handle. */ - uint8_t * pDataBuffer; /* Raw data buffer to be published with MQTT. */ - size_t size; /* Raw data size. */ -} _metricsReport_t; - -/* Initialize metrics report. */ -static _metricsReport_t _report = -{ - .object = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM, - .pDataBuffer = NULL, - .size = 0 -}; - -/* Define a "snapshot" global array of metrics flag. */ -static uint32_t _metricsFlagSnapshot[ DEFENDER_METRICS_GROUP_COUNT ]; - -/* Report id integer. */ -static uint64_t _AwsIotDefenderReportId = 0; - -const IotSerializerEncodeInterface_t * _pAwsIotDefenderEncoder = NULL; -const IotSerializerDecodeInterface_t * _pAwsIotDefenderDecoder = NULL; - -/*---------------------- Helper Functions -------------------------*/ - -static void _assertSuccess( IotSerializerError_t error ); - -static void _assertSuccessOrBufferToSmall( IotSerializerError_t error ); - -static void _copyMetricsFlag( void ); - -static void _serialize( void ); - -static void _serializeTcpConnections( void * param1, - const IotListDouble_t * pTcpConnectionsMetricsList ); - -#if DEBUG_CBOR_PRINT == 1 - static void _printReport(); -#endif - -/*-----------------------------------------------------------*/ - -void _assertSuccess( IotSerializerError_t error ) -{ - ( void ) error; - AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -void _assertSuccessOrBufferToSmall( IotSerializerError_t error ) -{ - ( void ) error; - AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS || error == IOT_SERIALIZER_BUFFER_TOO_SMALL ); -} - -/*-----------------------------------------------------------*/ - -uint8_t * AwsIotDefenderInternal_GetReportBuffer( void ) -{ - return _report.pDataBuffer; -} - -/*-----------------------------------------------------------*/ - -size_t AwsIotDefenderInternal_GetReportBufferSize( void ) -{ - /* Encoder might over-calculate the needed size. Therefor encoded size might be smaller than buffer size: _report.size. */ - return _report.pDataBuffer == NULL ? 0 - : _pAwsIotDefenderEncoder->getEncodedSize( &_report.object, _report.pDataBuffer ); -} - -/*-----------------------------------------------------------*/ - -bool AwsIotDefenderInternal_CreateReport( void ) -{ - /* Assert report buffer is not allocated. */ - AwsIotDefender_Assert( _report.pDataBuffer == NULL && _report.size == 0 ); - - bool result = true; - - IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); - - size_t dataSize = 0; - uint8_t * pReportBuffer = NULL; - - /* Copy the metrics flag user specified. */ - _copyMetricsFlag(); - - /* Generate report id based on current time. */ - _AwsIotDefenderReportId = IotClock_GetTimeMs(); - - /* Dry-run serialization to calculate the required size. */ - _serialize(); - - /* Get the calculated required size. */ - dataSize = _pAwsIotDefenderEncoder->getExtraBufferSizeNeeded( pEncoderObject ); - - /* Clean the encoder object handle. */ - _pAwsIotDefenderEncoder->destroy( pEncoderObject ); - - /* Allocate memory once. */ - pReportBuffer = AwsIotDefender_MallocReport( dataSize * sizeof( uint8_t ) ); - - if( pReportBuffer != NULL ) - { - _report.pDataBuffer = pReportBuffer; - _report.size = dataSize; - - /* Actual serialization. */ - _serialize(); - - /* Output the report to stdout if debugging mode is enabled. */ - #if DEBUG_CBOR_PRINT == 1 - _printReport(); - #endif - } - else - { - result = false; - } - - return result; -} - -/*-----------------------------------------------------------*/ - -void AwsIotDefenderInternal_DeleteReport( void ) -{ - /* Destroy the encoder object. */ - _pAwsIotDefenderEncoder->destroy( &( _report.object ) ); - - /* Free the memory of data buffer. */ - AwsIotDefender_FreeReport( _report.pDataBuffer ); - - /* Reset report members. */ - _report.pDataBuffer = NULL; - _report.size = 0; - _report.object = ( IotSerializerEncoderObject_t ) IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM; -} - -/* - * report: - * { - * "header": { - * "report_id": 1530304554, - * "version": "1.0" - * }, - * "metrics": { - * ... - * } - * } - */ -static void _serialize( void ) -{ - IotSerializerScalarData_t scalarData = { 0 }; - IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; - - IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); - - IotSerializerEncoderObject_t reportMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerEncoderObject_t headerMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerEncoderObject_t metricsMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - - /* Define an assert function for serialization returned error. */ - void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? _assertSuccessOrBufferToSmall - : _assertSuccess; - - uint8_t metricsGroupCount = 0; - uint32_t i = 0; - - serializerError = _pAwsIotDefenderEncoder->init( pEncoderObject, _report.pDataBuffer, _report.size ); - assertNoError( serializerError ); - - /* Create the outermost map with 2 keys: "header", "metrics". */ - serializerError = _pAwsIotDefenderEncoder->openContainer( pEncoderObject, &reportMap, 2 ); - assertNoError( serializerError ); - - /* Create the "header" map with 2 keys: "report_id", "version". */ - serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &reportMap, - HEADER_TAG, - &headerMap, - 2 ); - assertNoError( serializerError ); - - /* Append key-value pair of "report_Id" which uses clock time. */ - scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; - scalarData.value.u.signedInt = ( int64_t ) _AwsIotDefenderReportId; - - serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &headerMap, - REPORTID_TAG, - scalarData ); - assertNoError( serializerError ); - - /* Append key-value pair of "version". */ - scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - scalarData.value.u.string.pString = ( uint8_t * ) VERSION_1_0; - scalarData.value.u.string.length = sizeof( VERSION_1_0 ) - 1; - - serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &headerMap, - VERSION_TAG, - scalarData ); - assertNoError( serializerError ); - - /* Close the "header" map. */ - serializerError = _pAwsIotDefenderEncoder->closeContainer( &reportMap, &headerMap ); - assertNoError( serializerError ); - - /* Count how many metrics groups user specified. */ - for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) - { - metricsGroupCount += _metricsFlagSnapshot[ i ] > 0; - } - - /* Create the "metrics" map with number of keys as the number of metrics groups. */ - serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &reportMap, - METRICS_TAG, - &metricsMap, - metricsGroupCount ); - assertNoError( serializerError ); - - for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) - { - /* Skip if this metrics group has 0 metrics flag. */ - if( _metricsFlagSnapshot[ i ] ) - { - switch( i ) - { - case AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS: - IotMetrics_GetTcpConnections( ( void * ) &metricsMap, _serializeTcpConnections ); - break; - - default: - /* The index of metricsFlagSnapshot must be one of the metrics group. */ - AwsIotDefender_Assert( 0 ); - } - } - } - - /* Close the "metrics" map. */ - serializerError = _pAwsIotDefenderEncoder->closeContainer( &reportMap, &metricsMap ); - assertNoError( serializerError ); - - /* Close the "report" map. */ - serializerError = _pAwsIotDefenderEncoder->closeContainer( pEncoderObject, &reportMap ); - assertNoError( serializerError ); -} - -/*-----------------------------------------------------------*/ - -static void _copyMetricsFlag( void ) -{ - /* Copy the metrics flags to snapshot so that it is unlocked quicker. */ - IotMutex_Lock( &_AwsIotDefenderMetrics.mutex ); - - /* Memory copy from the metricsFlag array to metricsFlagSnapshot array. */ - memcpy( _metricsFlagSnapshot, _AwsIotDefenderMetrics.metricsFlag, sizeof( _metricsFlagSnapshot ) ); - - IotMutex_Unlock( &_AwsIotDefenderMetrics.mutex ); -} - -/*-----------------------------------------------------------*/ - -static void _serializeTcpConnections( void * param1, - const IotListDouble_t * pTcpConnectionsMetricsList ) -{ - IotSerializerScalarData_t scalarData = { 0 }; - IotSerializerEncoderObject_t * pMetricsObject = ( IotSerializerEncoderObject_t * ) param1; - - AwsIotDefender_Assert( pMetricsObject != NULL ); - - IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; - - IotSerializerEncoderObject_t tcpConnectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerEncoderObject_t establishedMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerEncoderObject_t connectionsArray = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; - - IotLink_t * pListIterator = NULL; - IotMetricsTcpConnection_t * pMetricsTcpConnection = NULL; - - size_t total = IotListDouble_Count( pTcpConnectionsMetricsList ); - - uint32_t tcpConnFlag = _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ]; - - uint8_t hasEstablishedConnections = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) > 0; - /* Whether "connections" should show up is not only determined by user input, but also if there is at least 1 connection. */ - uint8_t hasConnections = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) > 0 && - ( total > 0 ); - uint8_t hasTotal = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) > 0; - uint8_t hasRemoteAddr = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) > 0; - - void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? _assertSuccessOrBufferToSmall - : _assertSuccess; - - /* Create the "tcp_connections" map with 1 key "established_connections" */ - serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( pMetricsObject, - TCP_CONN_TAG, - &tcpConnectionMap, - 1 ); - assertNoError( serializerError ); - - /* if user specify any metrics under "established_connections" */ - if( hasEstablishedConnections ) - { - /* Create the "established_connections" map with "total" and/or "connections". */ - serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &tcpConnectionMap, - EST_CONN_TAG, - &establishedMap, - hasConnections + hasTotal ); - assertNoError( serializerError ); - - /* if user specify any metrics under "connections" and there are at least one connection */ - if( hasConnections ) - { - /* create array "connections" under "established_connections" */ - serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &establishedMap, - CONN_TAG, - &connectionsArray, - total ); - assertNoError( serializerError ); - - IotContainers_ForEach( pTcpConnectionsMetricsList, pListIterator ) - { - IotSerializerEncoderObject_t connectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - - /* open a map under "connections" */ - serializerError = _pAwsIotDefenderEncoder->openContainer( &connectionsArray, - &connectionMap, - hasRemoteAddr ); - assertNoError( serializerError ); - - /* add remote address */ - if( hasRemoteAddr ) - { - pMetricsTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, pListIterator, link ); - - scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - scalarData.value.u.string.pString = ( uint8_t * ) pMetricsTcpConnection->pRemoteAddress; - scalarData.value.u.string.length = pMetricsTcpConnection->addressLength; - - serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &connectionMap, REMOTE_ADDR_TAG, - scalarData ); - assertNoError( serializerError ); - } - - serializerError = _pAwsIotDefenderEncoder->closeContainer( &connectionsArray, &connectionMap ); - assertNoError( serializerError ); - } - - serializerError = _pAwsIotDefenderEncoder->closeContainer( &establishedMap, &connectionsArray ); - assertNoError( serializerError ); - } - - if( hasTotal ) - { - scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; - scalarData.value.u.signedInt = ( int64_t ) total; - - serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &establishedMap, - TOTAL_TAG, - scalarData ); - assertNoError( serializerError ); - } - - serializerError = _pAwsIotDefenderEncoder->closeContainer( &tcpConnectionMap, &establishedMap ); - assertNoError( serializerError ); - } - - serializerError = _pAwsIotDefenderEncoder->closeContainer( pMetricsObject, &tcpConnectionMap ); - assertNoError( serializerError ); -} - -#if DEBUG_CBOR_PRINT == 1 - #include "cbor.h" - /*-----------------------------------------------------------*/ - - static void _printReport() - { - CborParser cborParser; - CborValue cborValue; - - cbor_parser_init( - _report.pDataBuffer, - _report.size, - 0, - &cborParser, - &cborValue ); - cbor_value_to_pretty( stdout, &cborValue ); - } -#endif /* if DEBUG_CBOR_PRINT == 1 */ diff --git a/libraries/aws/defender/src/aws_iot_defender_mqtt.c b/libraries/aws/defender/src/aws_iot_defender_mqtt.c deleted file mode 100644 index c692cc9828..0000000000 --- a/libraries/aws/defender/src/aws_iot_defender_mqtt.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -/* Defender internal include. */ -#include "private/aws_iot_defender_internal.h" - -/* Define topics segments used by defender. */ -#define TOPIC_PREFIX "$aws/things/" - -#define TOPIC_SUFFIX_PUBLISH "/defender/metrics/" DEFENDER_FORMAT - -#define TOPIC_SUFFIX_ACCEPTED TOPIC_SUFFIX_PUBLISH "/accepted" - -#define TOPIC_SUFFIX_REJECTED TOPIC_SUFFIX_PUBLISH "/rejected" - -/*-----------------------------------------------------------*/ - -extern IotMqttCallbackInfo_t _acceptCallbackInfo; -extern IotMqttCallbackInfo_t _rejectCallbackInfo; -extern AwsIotDefenderStartInfo_t _startInfo; - -static char * _pPublishTopic = NULL; -static size_t _publishTopicLength = 0; - -static char * _pAcceptTopic = NULL; -static size_t _acceptTopicLength = 0; - -static char * _pRejectTopic = NULL; -static size_t _rejectTopicLength = 0; - -/*-----------------------------------------------------------*/ - -AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) -{ - AwsIotDefenderError_t returnedError = AWS_IOT_DEFENDER_SUCCESS; - - const char * pThingName = _startInfo.pClientIdentifier; - uint16_t thingNameLength = _startInfo.clientIdentifierLength; - size_t topicPrefixLength = strlen( TOPIC_PREFIX ); - - /* Calculate topics lengths. Plus one for string terminator. */ - size_t publishTopicLength = topicPrefixLength + thingNameLength + strlen( TOPIC_SUFFIX_PUBLISH ) + 1; - size_t acceptTopicLength = topicPrefixLength + thingNameLength + strlen( TOPIC_SUFFIX_ACCEPTED ) + 1; - size_t rejectTopicLength = topicPrefixLength + thingNameLength + strlen( TOPIC_SUFFIX_REJECTED ) + 1; - - /* Allocate memory for each of them. */ - char * pPublishTopic = AwsIotDefender_MallocTopic( publishTopicLength * sizeof( char ) ); - char * pAcceptTopic = AwsIotDefender_MallocTopic( acceptTopicLength * sizeof( char ) ); - char * pRejectTopic = AwsIotDefender_MallocTopic( rejectTopicLength * sizeof( char ) ); - - /* Free memory if any allocation failed. */ - if( ( pPublishTopic == NULL ) || ( pAcceptTopic == NULL ) || ( pRejectTopic == NULL ) ) - { - /* Null pointer is safe for "free" function. */ - AwsIotDefender_FreeTopic( pPublishTopic ); - AwsIotDefender_FreeTopic( pAcceptTopic ); - AwsIotDefender_FreeTopic( pRejectTopic ); - returnedError = AWS_IOT_DEFENDER_ERROR_NO_MEMORY; - } - else - { - _pPublishTopic = pPublishTopic; - _pAcceptTopic = pAcceptTopic; - _pRejectTopic = pRejectTopic; - - snprintf( _pPublishTopic, publishTopicLength, "%s%s%s", - TOPIC_PREFIX, - pThingName, - TOPIC_SUFFIX_PUBLISH ); - - snprintf( _pAcceptTopic, acceptTopicLength, "%s%s%s", - TOPIC_PREFIX, - pThingName, - TOPIC_SUFFIX_ACCEPTED ); - - snprintf( _pRejectTopic, rejectTopicLength, "%s%s%s", - TOPIC_PREFIX, - pThingName, - TOPIC_SUFFIX_REJECTED ); - - _publishTopicLength = publishTopicLength -1; - _acceptTopicLength = acceptTopicLength -1 ; - _rejectTopicLength = rejectTopicLength -1 ; - } - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -void AwsIotDefenderInternal_DeleteTopicsNames( void ) -{ - AwsIotDefender_FreeTopic( _pPublishTopic ); - AwsIotDefender_FreeTopic( _pAcceptTopic ); - AwsIotDefender_FreeTopic( _pRejectTopic ); - _pPublishTopic = NULL; - _pAcceptTopic = NULL; - _pRejectTopic = NULL; - - _publishTopicLength = 0; - _acceptTopicLength = 0; - _rejectTopicLength = 0; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( void ) -{ - /* subscribe to two topics: accept and reject. */ - IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - subscriptions[ 0 ].qos = IOT_MQTT_QOS_0; - subscriptions[ 0 ].pTopicFilter = _pAcceptTopic; - subscriptions[ 0 ].topicFilterLength = ( uint16_t ) _acceptTopicLength; - subscriptions[ 0 ].callback = _acceptCallbackInfo; - - subscriptions[ 1 ].qos = IOT_MQTT_QOS_0; - subscriptions[ 1 ].pTopicFilter = _pRejectTopic; - subscriptions[ 1 ].topicFilterLength = ( uint16_t ) _rejectTopicLength; - subscriptions[ 1 ].callback = _rejectCallbackInfo; - - return IotMqtt_SubscribeSync( _startInfo.mqttConnection, - subscriptions, - 2, - 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ); -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, - size_t dataLength ) -{ - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - publishInfo.qos = IOT_MQTT_QOS_0; - publishInfo.pTopicName = _pPublishTopic; - publishInfo.topicNameLength = ( uint16_t ) _publishTopicLength; - publishInfo.pPayload = pData; - publishInfo.payloadLength = dataLength; - /* Publish Defender Report */ - return IotMqtt_PublishSync( _startInfo.mqttConnection, - &publishInfo, - 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ); -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t AwsIotDefenderInternal_MqttUnSubscribe( void ) -{ - /* unsubscribe to two topics: accept and reject. */ - IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - subscriptions[ 0 ].qos = IOT_MQTT_QOS_0; - subscriptions[ 0 ].pTopicFilter = _pAcceptTopic; - subscriptions[ 0 ].topicFilterLength = ( uint16_t ) _acceptTopicLength; - subscriptions[ 0 ].callback = _acceptCallbackInfo; - - subscriptions[ 1 ].qos = IOT_MQTT_QOS_0; - subscriptions[ 1 ].pTopicFilter = _pRejectTopic; - subscriptions[ 1 ].topicFilterLength = ( uint16_t ) _rejectTopicLength; - subscriptions[ 1 ].callback = _rejectCallbackInfo; - - return IotMqtt_UnsubscribeSync( _startInfo.mqttConnection, - subscriptions, - 2, - 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/src/private/aws_iot_defender_internal.h b/libraries/aws/defender/src/private/aws_iot_defender_internal.h deleted file mode 100644 index e46281a5db..0000000000 --- a/libraries/aws/defender/src/private/aws_iot_defender_internal.h +++ /dev/null @@ -1,351 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * Internal header of Defender library. This header should not be included in - * typical application code. - */ - -#ifndef AWS_IOT_DEFENDER_INTERNAL_H_ -#define AWS_IOT_DEFENDER_INTERNAL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Defender include. */ -#include "aws_iot_defender.h" - -/* Serializer include. */ -#include "iot_serializer.h" - -/* Platform thread include. */ -#include "platform/iot_threads.h" - -/* Double linked list include. */ -#include "iot_linear_containers.h" - -/** - * @def AwsIotDefender_Assert( expression ) - * @brief Assertion macro for the Defender library. - * - * Set @ref AWS_IOT_DEFENDER_ENABLE_ASSERTS to `1` to enable assertions in the Defender - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 - #ifndef AwsIotDefender_Assert - #ifdef Iot_DefaultAssert - #define AwsIotDefender_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for Defender, but AwsIotDefender_Assert is not defined" - #endif - #endif -#else /* if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 */ - #define AwsIotDefender_Assert( expression ) -#endif /* if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 */ - -/* Configure logs for Defender functions. */ -#ifdef AWS_IOT_LOG_LEVEL_DEFENDER - #define LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEFENDER -#else - #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "Defender" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "iot_static_memory.h" - -/** - * @brief Allocate a Defender report. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define AwsIotDefender_MallocReport Iot_MallocMessageBuffer - -/** - * @brief Free a Defender report. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define AwsIotDefender_FreeReport Iot_FreeMessageBuffer - -/** - * @brief Allocate a Defender topic. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define AwsIotDefender_MallocTopic Iot_MallocMessageBuffer - -/** - * @brief Free a Defender topic. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define AwsIotDefender_FreeTopic Iot_FreeMessageBuffer -#else /* if IOT_STATIC_MEMORY_ONLY */ - #ifndef AwsIotDefender_MallocReport - #ifdef Iot_DefaultMalloc - #define AwsIotDefender_MallocReport Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotDefender_MallocReport" - #endif - #endif - - #ifndef AwsIotDefender_FreeReport - #ifdef Iot_DefaultFree - #define AwsIotDefender_FreeReport Iot_DefaultFree - #else - #error "No free function defined for AwsIotDefender_FreeReport" - #endif - #endif - - #ifndef AwsIotDefender_MallocTopic - #ifdef Iot_DefaultMalloc - #define AwsIotDefender_MallocTopic Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotDefender_MallocTopic" - #endif - #endif - - #ifndef AwsIotDefender_FreeTopic - #ifdef Iot_DefaultFree - #define AwsIotDefender_FreeTopic Iot_DefaultFree - #else - #error "No free function defined for AwsIotDefender_FreeTopic" - #endif - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY */ - -/** - * @page Defender_Config Configuration - * @brief Configuration settings of the Defender library - * @par configpagemarker - * - * @section AWS_IOT_SECURE_SOCKETS_METRICS_ENABLED - * @brief Enable secure sockets metrics - * Possible values: 0 or 1
- * Recommended values: 1
- * Default value (if undefined): 0
- * - * This macro must be defined for device defender library to collect sockets metrics correctly. - * Without defining it, the behavior is unknown. - * - * @code{c} - * #define AWS_IOT_SECURE_SOCKETS_METRICS_ENABLED (1) - * @endcode - * - * @section AWS_IOT_DEFENDER_FORMAT - * @brief Default format for metrics data serialization. - * - * Possible values: #AWS_IOT_DEFENDER_FORMAT_CBOR (JSON is not supported for now)
- * Recommended values: Cbor is more compact than Json, thus more efficient.
- * Default value (if undefined): #AWS_IOT_DEFENDER_FORMAT_CBOR
- * - * @section AWS_IOT_DEFENDER_USE_LONG_TAG - * @brief Use long tag or short tag for metrics report. - * - * Possible values: `0` or `1`
- * Recommended values: 0 to use short tag to reduce network transmit cost.
- * Default value (if undefined): `0`
- * - * @section AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS - * @brief Default period constants if users don't provide their own. - * - * If metrics is sent faster than 5 minutes for one "thing", it may be throttled. - * - * Possible values: greater than or equal to `300`
- * Recommended values: greater than or equal to `300` seconds; defender service might throttle if the period is too short
- * Default value (if undefined): `300`
- * - * @section AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS - * @brief Default MQTT connect timeout. - * - * Possible values: greater than 0
- * Default value (if undefined): `10`
- * - * @section AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS - * @brief Default MQTT subscribe timeout. - * - * Possible values: greater than 0
- * Default value (if undefined): `10`
- * - * @section AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS - * @brief Default MQTT publish timeout. - * - * Possible values: greater than 0
- * Default value (if undefined): `10`
- */ - -#ifndef AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS - #define AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ( 300 ) -#endif - -#ifndef AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS - #define AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS ( 3 ) -#endif - -#ifndef AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS - #define AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ( 10U ) -#endif - -#ifndef AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS - #define AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ( 10U ) -#endif - -#ifndef AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS - #define AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ( 10U ) -#endif - -#ifndef AWS_IOT_DEFENDER_FORMAT - #define AWS_IOT_DEFENDER_FORMAT AWS_IOT_DEFENDER_FORMAT_CBOR -#endif - -/* In current release, JSON format is not supported. */ -#if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - #error "AWS_IOT_DEFENDER_FORMAT_JSON is not supported." -#endif - -/* Default to short tag to save memory and network. */ -#ifndef AWS_IOT_DEFENDER_USE_LONG_TAG - #define AWS_IOT_DEFENDER_USE_LONG_TAG ( 0 ) -#endif - -/*----------------- Below this line is INTERNAL used only --------------------*/ - -/* This MUST be consistent with enum AwsIotDefenderMetricsGroup_t. */ -#define DEFENDER_METRICS_GROUP_COUNT 1 - -/** - * Define encoder/decoder based on configuration AWS_IOT_DEFENDER_FORMAT. - */ -#if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - #define DEFENDER_FORMAT "cbor" -#else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ - #error "AWS_IOT_DEFENDER_FORMAT must be AWS_IOT_DEFENDER_FORMAT_CBOR." -#endif /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ - -/** - * Define a helper macro to select long tag or short tag based on configuration AWS_IOT_DEFENDER_USE_LONG_TAG. - */ -#if AWS_IOT_DEFENDER_USE_LONG_TAG == 1 - #define AwsIotDefenderInternal_SelectTag( long_tag, short_tag ) ( long_tag ) -#else - #define AwsIotDefenderInternal_SelectTag( long_tag, short_tag ) ( short_tag ) -#endif - -/** - * Convert seconds to milliseconds and vice versa. - */ -#define _defenderToMilliseconds( secondValue ) ( secondValue ) * 1000 -#define _defenderToSeconds( millisecondValue ) \ - ( millisecondValue ) / 1000 \ - - -/** - * Structure to hold the metrics. - */ -typedef struct _defenderMetrics -{ - /** - * Array of bit-flag of metrics. The index is enum value of AwsIotDefenderMetricsGroup_t. - */ - uint32_t metricsFlag[ DEFENDER_METRICS_GROUP_COUNT ]; - - /** - * Mutex to protect _AwsIotDefenderMetricsFlag referenced by: - * - metrics timer callback - * - SetMetrics API - */ - IotMutex_t mutex; -} _defenderMetrics_t; - -/** - * Create a report, memory is allocated inside the function. - */ -bool AwsIotDefenderInternal_CreateReport( void ); - -/** - * Get the buffer pointer of report. - */ -uint8_t * AwsIotDefenderInternal_GetReportBuffer( void ); - -/** - * Get the buffer size of report. - */ -size_t AwsIotDefenderInternal_GetReportBufferSize( void ); - -/** - * Delete a report when it is useless. Internally, memory will be freed. - */ -void AwsIotDefenderInternal_DeleteReport( void ); - -/** - * Build three topics names used by defender library. - */ -AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ); - -/** - * Free the memory of three topics names. - */ -void AwsIotDefenderInternal_DeleteTopicsNames( void ); - -/** - * Subscribe accept/reject defender topics. - */ - -IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( void ); - -/** - * Publish metrics data to defender topic. - */ -IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, - size_t dataLength ); - -/** - * Unsubscribe accept/reject defender topics. - */ -IotMqttError_t AwsIotDefenderInternal_MqttUnSubscribe( void ); - -/** - * Disconnect with AWS MQTT. - */ -void AwsIotDefenderInternal_MqttDisconnect( void ); - -/*----------------- Below this line are INTERNAL global variables --------------------*/ - -extern _defenderMetrics_t _AwsIotDefenderMetrics; - -extern const IotSerializerEncodeInterface_t * _pAwsIotDefenderEncoder; -extern const IotSerializerDecodeInterface_t * _pAwsIotDefenderDecoder; - -#endif /* ifndef AWS_IOT_DEFENDER_INTERNAL_H_ */ diff --git a/libraries/aws/defender/test/aws_iot_tests_defender.c b/libraries/aws/defender/test/aws_iot_tests_defender.c deleted file mode 100644 index 0ee1d1e205..0000000000 --- a/libraries/aws/defender/test/aws_iot_tests_defender.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_defender.c - * @brief Test runner for Defender tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the Defender test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunDefenderTests( bool disableNetworkTests, bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableLongTests; - - RUN_TEST_GROUP( Defender_Unit ); - - if( disableNetworkTests == false ) - { - RUN_TEST_GROUP( Defender_System ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c deleted file mode 100644 index a0be9ddec0..0000000000 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ /dev/null @@ -1,755 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -#include "platform/iot_clock.h" - -/* Defender internal includes. */ -#include "private/aws_iot_defender_internal.h" - -#include "iot_network_metrics.h" - -#include "iot_init.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* Serializer includes. */ -#include "iot_serializer.h" - -#include "cbor.h" - -#include "unity_fixture.h" - -/* Total time to wait for a state to be true. */ -#define WAIT_STATE_TOTAL_SECONDS 10 - -/* Time interval for defender agent to publish metrics. It will be throttled if too frequent. */ -/* TODO: if we can change "thingname" in each test, this can be lowered. */ -#define DEFENDER_PUBLISH_INTERVAL_SECONDS 20 - -/* Estimated max size of message payload received in MQTT callback. */ -#define PAYLOAD_MAX_SIZE 200 - -/* Estimated max size of metrics report defender published. */ -#define METRICS_MAX_SIZE 200 - -/* Max size of address: IP + port. */ -#define MAX_ADDRESS_LENGTH 25 - -/* Use a big number to represent no event happened in defender. */ -#define NO_EVENT 10000 - -/* Empty callback structure passed to startInfo. */ -static const AwsIotDefenderCallback_t _emptyCallback = { .function = NULL, .pCallbackContext = NULL }; - -static struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; -static struct IotNetworkCredentials _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*------------------ global variables -----------------------------*/ - -static uint8_t _payloadBuffer[ PAYLOAD_MAX_SIZE ]; -static uint8_t _metricsBuffer[ METRICS_MAX_SIZE ]; - -static AwsIotDefenderCallback_t _testCallback; - -static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - -/* - * Waiting for it indicates waiting for any event to happen - * Posting it indicates any event happened - */ -static IotSemaphore_t _callbackInfoSem; - -static AwsIotDefenderCallbackInfo_t _callbackInfo; - -static IotSerializerDecoderObject_t _decoderObject; -static IotSerializerDecoderObject_t _metricsObject; - -static bool _mqttConnectionStarted = false; -/*------------------ Functions -----------------------------*/ - -/* Copy data from MQTT callback to local buffer. */ -static void _copyDataCallbackFunction( void * param1, - AwsIotDefenderCallbackInfo_t * const pCallbackInfo ); - -static bool _waitForAnyEvent( uint32_t timeoutSec ); - -/* Wait for metrics to be accepted by defender service, for maximum timeout. */ -static void _waitForMetricsAccepted( uint32_t timeoutSec ); - -/* Verify common section of metrics report. */ -static void _verifyMetricsCommon( void ); - -/* Verify tcp connections in metrics report. */ -static void _verifyTcpConnections( int total ); - -static void _resetCalbackInfo( void ); - -static IotMqttError_t _startMqttConnection( void ); -static void _stopMqttConnection( void ); - -TEST_GROUP( Defender_System ); - -TEST_SETUP( Defender_System ) -{ - if( IotSdk_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); - } - - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize network stack." ); - } - - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT." ); - } - - if( IotMetrics_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize metrics." ); - } - - /* Create a binary semaphore with initial value 0. */ - if( !IotSemaphore_Create( &_callbackInfoSem, 0, 1 ) ) - { - TEST_FAIL_MESSAGE( "Fail to create semaphore for callback info." ); - } - - _resetCalbackInfo(); - - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - _decoderObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - _metricsObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - /* Reset test callback. */ - _testCallback = ( AwsIotDefenderCallback_t ) { - .function = _copyDataCallbackFunction, .pCallbackContext = NULL - }; - - /* By default IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER enables ALPN. ALPN - * must be used with port 443; disable ALPN if another port is being used. */ - if( _serverInfo.port != 443 ) - { - _credential.pAlpnProtos = NULL; - } - - /* Reset server info. */ - _serverInfo = ( struct IotNetworkServerInfo ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - - /* Set fields of start info. */ - _startInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; - _startInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_DEFENDER_THING_NAME ); - _startInfo.callback = _emptyCallback; -} - -TEST_TEAR_DOWN( Defender_System ) -{ - AwsIotDefender_Stop(); - - /* Actually get defender callback. */ - if( ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_ACCEPTED ) || - ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) ) - { - IotClock_SleepMs( DEFENDER_PUBLISH_INTERVAL_SECONDS * 1000 ); - } - - IotSemaphore_Destroy( &_callbackInfoSem ); - _stopMqttConnection(); - IotMetrics_Cleanup(); - IotMqtt_Cleanup(); - IotTestNetwork_Cleanup(); - IotSdk_Cleanup(); -} - -TEST_GROUP_RUNNER( Defender_System ) -{ - /* - * Setup: not set any metrics; register test callback - * Action: call Start API - * Expectation: metrics are accepted by defender service - */ - RUN_TEST_CASE( Defender_System, Metrics_empty_are_published ); - - /* - * Setup: set "tcp connections" with "all metrics"; register test callback - * Action: call Start API - * Expectation: - * - metrics are accepted by defender service - * - verify metrics report has correct content - */ - RUN_TEST_CASE( Defender_System, Metrics_TCP_connections_all_are_published ); - - /* - * Setup: set "tcp connections" with "total count"; register test callback - * Action: call Start API - * Expectation: - * - metrics are accepted by defender service - * - verify metrics report has correct content - */ - RUN_TEST_CASE( Defender_System, Metrics_TCP_connections_total_are_published ); - - /* - * Setup: set "tcp connections" with "remote address"; register test callback - * Action: call Start API - * Expectation: - * - metrics are accepted by defender service - * - verify metrics report has correct content - */ - RUN_TEST_CASE( Defender_System, Metrics_TCP_connections_remote_addr_are_published ); - - /* - * Setup: set "tcp connections" with "total count"; register test callback; call Start API - * Action: call Stop API; set "tcp connections" with "all metrics"; call Start again - * Expectation: - * - metrics are accepted by defender service in both times - * - verify metrics report has correct content respectively in both times - */ - RUN_TEST_CASE( Defender_System, Restart_and_updated_metrics_are_published ); -} - - -TEST( Defender_System, Metrics_empty_are_published ) -{ - AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - /* Set test callback to verify report. */ - _startInfo.callback = _testCallback; - - /* start actual MQTT connection */ - mqttError = _startMqttConnection(); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); - _startInfo.mqttConnection = _mqttConnection; - - /* Start defender. */ - error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); - - _verifyMetricsCommon(); - _verifyTcpConnections( 0 ); -} - -TEST( Defender_System, Metrics_TCP_connections_all_are_published ) -{ - AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - /* start actual MQTT connection */ - mqttError = _startMqttConnection(); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); - _startInfo.mqttConnection = _mqttConnection; - - /* Set "all metrics" for TCP connections metrics group. */ - error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_ALL ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Set test callback to verify report. */ - _startInfo.callback = _testCallback; - - /* Start defender. */ - error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); - - _verifyMetricsCommon(); - _verifyTcpConnections( 1 ); -} - -TEST( Defender_System, Metrics_TCP_connections_total_are_published ) -{ - AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - /* Set "total count" for TCP connections metrics group. */ - error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* start actual MQTT connection */ - mqttError = _startMqttConnection(); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); - _startInfo.mqttConnection = _mqttConnection; - - /* Set test callback to verify report. */ - _startInfo.callback = _testCallback; - - /* Start defender. */ - error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); - - _verifyMetricsCommon(); - _verifyTcpConnections( 1 ); -} - -TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) -{ - AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - /* Set "remote address" for TCP connections metrics group. */ - error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* start actual MQTT connection */ - mqttError = _startMqttConnection(); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); - _startInfo.mqttConnection = _mqttConnection; - - /* Set test callback to verify report. */ - _startInfo.callback = _testCallback; - - /* Start defender. */ - error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); - - _verifyMetricsCommon(); - _verifyTcpConnections( 1 ); -} - -TEST( Defender_System, Restart_and_updated_metrics_are_published ) -{ - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - - /* Set "total count" for TCP connections metrics group. */ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, - AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) ); - - /* start actual MQTT connection */ - mqttError = _startMqttConnection(); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); - _startInfo.mqttConnection = _mqttConnection; - - /* Set test callback to verify report. */ - _startInfo.callback = _testCallback; - - /* Start defender. */ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); - - /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); - - _verifyMetricsCommon(); - _verifyTcpConnections( 1 ); - - AwsIotDefender_Stop(); - - /* Reset _callbackInfo before restarting. */ - _resetCalbackInfo(); - - IotClock_SleepMs( DEFENDER_PUBLISH_INTERVAL_SECONDS ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, - AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ) ); - - /* Restart defender. */ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); - - /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); - - _verifyMetricsCommon(); - _verifyTcpConnections( 1 ); -} - -/*-----------------------------------------------------------*/ - -static void _copyDataCallbackFunction( void * param1, - AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) -{ - /* Silence the compiler. */ - ( void ) param1; - - /* Print out rejected message to stdout. */ - if( pCallbackInfo->eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) - { - CborParser cborParser; - CborValue cborValue; - cbor_parser_init( pCallbackInfo->pPayload, pCallbackInfo->payloadLength, 0, &cborParser, &cborValue ); - cbor_value_to_pretty( stdout, &cborValue ); - } - - /* Copy data from pCallbackInfo to _callbackInfo. */ - if( pCallbackInfo != NULL ) - { - _callbackInfo.eventType = pCallbackInfo->eventType; - _callbackInfo.metricsReportLength = pCallbackInfo->metricsReportLength; - _callbackInfo.payloadLength = pCallbackInfo->payloadLength; - - if( _callbackInfo.payloadLength > 0 ) - { - memcpy( ( uint8_t * ) _callbackInfo.pPayload, pCallbackInfo->pPayload, _callbackInfo.payloadLength ); - } - - if( _callbackInfo.metricsReportLength > 0 ) - { - memcpy( ( uint8_t * ) _callbackInfo.pMetricsReport, pCallbackInfo->pMetricsReport, _callbackInfo.metricsReportLength ); - } - } - - IotSemaphore_Post( &_callbackInfoSem ); -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _startMqttConnection( void ) -{ - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - IotMqttNetworkInfo_t mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; - - if( !_mqttConnectionStarted ) - { - mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; - mqttNetworkInfo.createNetworkConnection = true; - mqttNetworkInfo.u.setup.pNetworkServerInfo = &_serverInfo; - mqttNetworkInfo.u.setup.pNetworkCredentialInfo = &_credential; - - mqttNetworkInfo.pNetworkInterface = IotNetworkMetrics_GetInterface(); - - mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* Set MQTT connection information. */ - mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; - mqttConnectionInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_DEFENDER_THING_NAME ); - - mqttError = IotMqtt_Connect( &mqttNetworkInfo, - &mqttConnectionInfo, - 1000, - &_mqttConnection ); - - if( mqttError == IOT_MQTT_SUCCESS ) - { - _mqttConnectionStarted = true; - } - } - - return mqttError; -} - -/*-----------------------------------------------------------*/ - -static void _stopMqttConnection( void ) -{ - if( _mqttConnectionStarted ) - { - IotMqtt_Disconnect( _mqttConnection, false ); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - _mqttConnectionStarted = false; - } -} - -/*-----------------------------------------------------------*/ - -static void _resetCalbackInfo( void ) -{ - /* Clean data buffer. */ - memset( _payloadBuffer, 0, PAYLOAD_MAX_SIZE ); - memset( _metricsBuffer, 0, METRICS_MAX_SIZE ); - - /* Reset callback info. */ - _callbackInfo = ( AwsIotDefenderCallbackInfo_t ) { - .pMetricsReport = _metricsBuffer, - .metricsReportLength = 0, - .pPayload = _payloadBuffer, - .payloadLength = 0, - .eventType = NO_EVENT - }; -} - - -/*-----------------------------------------------------------*/ - -static bool _waitForAnyEvent( uint32_t timeoutSec ) -{ - return IotSemaphore_TimedWait( &_callbackInfoSem, timeoutSec * 1000 ); -} - -/*-----------------------------------------------------------*/ - -/* Assert the cause of rejection is throttle. */ -static void _assertRejectDueToThrottle( void ) -{ - TEST_ASSERT_NOT_NULL( _callbackInfo.pPayload ); - TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.payloadLength ); - - IotSerializerDecoderObject_t decoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t statusDetailsObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t errorCodeObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - char errorCode[ 12 ] = ""; - - IotSerializerError_t error = _pAwsIotDefenderDecoder->init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, decoderObject.type ); - - error = _pAwsIotDefenderDecoder->find( &decoderObject, "statusDetails", &statusDetailsObject ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, statusDetailsObject.type ); - - errorCodeObject.u.value.u.string.pString = ( uint8_t * ) errorCode; - errorCodeObject.u.value.u.string.length = 12; - - error = _pAwsIotDefenderDecoder->find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, errorCodeObject.type ); - - TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) errorCodeObject.u.value.u.string.pString, "Throttled", errorCodeObject.u.value.u.string.length ) ); - - _pAwsIotDefenderDecoder->destroy( &statusDetailsObject ); - _pAwsIotDefenderDecoder->destroy( &decoderObject ); -} - -/*-----------------------------------------------------------*/ - -static void _waitForMetricsAccepted( uint32_t timeoutSec ) -{ - /* If not event has occurred, simply fail the test. */ - if( !_waitForAnyEvent( timeoutSec ) ) - { - TEST_FAIL_MESSAGE( "No event has occurred within timeout." ); - } - - if( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) - { - _assertRejectDueToThrottle(); - - return; - } - - /* Assert metrics is accepted. */ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ACCEPTED, _callbackInfo.eventType ); - - TEST_ASSERT_NOT_NULL( _callbackInfo.pPayload ); - TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.payloadLength ); - - IotSerializerDecoderObject_t decoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - IotSerializerError_t error = _pAwsIotDefenderDecoder->init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, decoderObject.type ); - - IotSerializerDecoderObject_t statusObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - char status[ 10 ] = ""; - statusObject.u.value.u.string.pString = ( uint8_t * ) status; - statusObject.u.value.u.string.length = 10; - - error = _pAwsIotDefenderDecoder->find( &decoderObject, "status", &statusObject ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, statusObject.type ); - - TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) statusObject.u.value.u.string.pString, "ACCEPTED", statusObject.u.value.u.string.length ) ); - - _pAwsIotDefenderDecoder->destroy( &statusObject ); - _pAwsIotDefenderDecoder->destroy( &decoderObject ); -} - -/*-----------------------------------------------------------*/ - -static void _verifyMetricsCommon( void ) -{ - TEST_ASSERT_NOT_NULL( _callbackInfo.pMetricsReport ); - TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.metricsReportLength ); - - IotSerializerError_t error = _pAwsIotDefenderDecoder->init( &_decoderObject, _callbackInfo.pMetricsReport, _callbackInfo.metricsReportLength ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, _decoderObject.type ); - - error = _pAwsIotDefenderDecoder->find( &_decoderObject, "metrics", &_metricsObject ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, _metricsObject.type ); -} - -/*-----------------------------------------------------------*/ - -static void _verifyTcpConnections( int total ) -{ - uint8_t i = 0; - - uint32_t tcpConnFlag = _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ]; - - /* Assert find a "tcp_connections" map in "metrics" */ - IotSerializerDecoderObject_t tcpConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - IotSerializerError_t error = _pAwsIotDefenderDecoder->find( &_metricsObject, "tcp_connections", &tcpConnObject ); - - /* If any TCP connections flag is specified. */ - if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_ALL ) - { - /* Assert found the "tcp_connections" map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, tcpConnObject.type ); - - IotSerializerDecoderObject_t estConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - error = _pAwsIotDefenderDecoder->find( &tcpConnObject, "established_connections", &estConnObject ); - - if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) - { - /* Assert found a "established_connections" map in "tcp_connections" */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, estConnObject.type ); - - IotSerializerDecoderObject_t totalObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - error = _pAwsIotDefenderDecoder->find( &estConnObject, "total", &totalObject ); - - if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) - { - /* Assert find a "total" integer with value 1 in "established_connections" */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_SIGNED_INT, totalObject.type ); - - TEST_ASSERT_EQUAL( total, totalObject.u.value.u.signedInt ); - } - else - { - /* Assert not found the "total". */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); - } - - IotSerializerDecoderObject_t connsObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderIterator_t connIterator = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - - error = _pAwsIotDefenderDecoder->find( &estConnObject, "connections", &connsObject ); - - if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) - { - /* Assert find a "connections" array in "established_connections" */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_ARRAY, connsObject.type ); - - error = _pAwsIotDefenderDecoder->stepIn( &connsObject, &connIterator ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - for( i = 0; i < total; i++ ) - { - /* Assert find one "connection" map in "connections" */ - IotSerializerDecoderObject_t connMap = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _pAwsIotDefenderDecoder->get( connIterator, &connMap ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, connMap.type ); - - IotSerializerDecoderObject_t remoteAddrObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - - error = _pAwsIotDefenderDecoder->find( &connMap, "remote_addr", &remoteAddrObject ); - - if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) - { - /* Assert find a "remote_addr" string in "connection" */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, remoteAddrObject.type ); - } - else - { - /* Assert not found the "remote_addr". */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); - } - - error = _pAwsIotDefenderDecoder->next( connIterator ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - - _pAwsIotDefenderDecoder->destroy( &connMap ); - } - - TEST_ASSERT_TRUE( _pAwsIotDefenderDecoder->isEndOfContainer( connIterator ) ); - - _pAwsIotDefenderDecoder->stepOut( connIterator, &connsObject ); - } - else - { - /* Assert not found the "connections". */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); - } - - _pAwsIotDefenderDecoder->destroy( &connsObject ); - } - else - { - /* Assert not found the "established_connections" map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); - } - - _pAwsIotDefenderDecoder->destroy( &estConnObject ); - } - else - { - /* Assert not found the "tcp_connections" map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); - } - - _pAwsIotDefenderDecoder->destroy( &tcpConnObject ); - _pAwsIotDefenderDecoder->destroy( &_metricsObject ); - _pAwsIotDefenderDecoder->destroy( &_decoderObject ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c b/libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c deleted file mode 100644 index 6e111a63a6..0000000000 --- a/libraries/aws/defender/test/unit/aws_iot_tests_defender_unit.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * AWS IoT Defender V3.0.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Defender internal includes. */ -#include "private/aws_iot_defender_internal.h" - -/* MQTT Mock include */ -#include "iot_tests_mqtt_mock.h" - -#include "iot_network_metrics.h" -#include "iot_init.h" -#include "unity_fixture.h" - -/* Empty callback structure passed to startInfo. */ -static const AwsIotDefenderCallback_t _emptyCallback = { .function = NULL, .pCallbackContext = NULL }; - -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*------------------ global variables -----------------------------*/ - -static AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - -static bool _mockedMqttConnection = false; -/*------------------ Functions -----------------------------*/ - -TEST_GROUP( Defender_Unit ); - -TEST_SETUP( Defender_Unit ) -{ - if( IotSdk_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); - } - - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT." ); - } - - if( IotMetrics_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize metrics." ); - } - - /* Set fields of start info. */ - _startInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; - _startInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_DEFENDER_THING_NAME ); - _startInfo.callback = _emptyCallback; -} - -TEST_TEAR_DOWN( Defender_Unit ) -{ - AwsIotDefender_Stop(); - - if (_mockedMqttConnection) - { - IotTest_MqttMockCleanup(); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - _mockedMqttConnection = false; - } - - IotMetrics_Cleanup(); - IotMqtt_Cleanup(); - IotSdk_Cleanup(); -} - -TEST_GROUP_RUNNER( Defender_Unit ) -{ - /* - * Setup: none - * Action: call Start API with invalid MQTT connection - * Expectation: Start API returns failure - */ - RUN_TEST_CASE( Defender_Unit, Start_with_invalid_mqtt_connection ); - - /* - * Setup: defender not started yet - * Action: call SetMetrics API with an invalid big integer as metrics group - * Expectation: - * - SetMetrics API return invalid input - * - global metrics flag array are untouched - */ - RUN_TEST_CASE( Defender_Unit, SetMetrics_with_invalid_metrics_group ); - - /* - * Setup: defender not started yet - * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value - * Expectation: - * - SetMetrics API return success - * - global metrics flag array are updated correctly - */ - RUN_TEST_CASE( Defender_Unit, SetMetrics_with_TCP_connections_all ); - - /* - * Setup: defender is started - * Action: call SetMetrics API with Tcp connections group and "All Metrics" flag value - * Expectation: - * - SetMetrics API return success - * - global metrics flag array are updated correctly - */ - RUN_TEST_CASE( Defender_Unit, SetMetrics_after_defender_started ); - - /* - * Setup: defender not started yet - * Action: call SetPeriod API with small value less than 300 - * Expectation: - * - SetPeriod API return "period too short" error - */ - RUN_TEST_CASE( Defender_Unit, SetPeriod_too_short ); - - /* - * Setup: defender not started yet - * Action: call SetPeriod API with 301 - * Expectation: - * - SetPeriod API return success - */ - RUN_TEST_CASE( Defender_Unit, SetPeriod_with_proper_value ); - - /* - * Setup: defender is started - * Action: call SetPeriod API with 600 - * Expectation: - * - SetPeriod API return success - */ - RUN_TEST_CASE( Defender_Unit, SetPeriod_after_started ); - - /* - * Setup: kept from publishing metrics report - * Action: call Start API with correct network information - * Expectation: Start API return success - */ - RUN_TEST_CASE( Defender_Unit, Start_should_return_success ); - - /* - * Setup: call Start API the first time; kept from publishing metrics report - * Action: call Start API second time - * Expectation: Start API return "already started" error - */ - RUN_TEST_CASE( Defender_Unit, Start_should_return_err_if_already_started ); -} - -TEST( Defender_Unit, SetMetrics_with_invalid_metrics_group ) -{ - uint8_t i = 0; - - /* Input a dummy, invalid metrics group. */ - AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( 10000, - AWS_IOT_DEFENDER_METRICS_ALL ); - - /* SetMetrics should return "invalid input". */ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); - - /* Assert metrics flag in each metrics group remains 0. */ - for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) - { - TEST_ASSERT_EQUAL( 0, _AwsIotDefenderMetrics.metricsFlag[ i ] ); - } -} - -TEST( Defender_Unit, SetMetrics_with_TCP_connections_all ) -{ - /* Set "all metrics" for TCP connections metrics group. */ - AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_ALL ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); -} - -TEST( Defender_Unit, SetMetrics_after_defender_started ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Set "all metrics" for TCP connections metrics group. */ - error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, - AWS_IOT_DEFENDER_METRICS_ALL ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); -} - -TEST( Defender_Unit, Start_with_invalid_mqtt_connection ) -{ - /* Set uninitialized MQTT connection */ - _startInfo.mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); -} - -TEST( Defender_Unit, Start_should_return_success ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); -} - -TEST( Defender_Unit, Start_should_return_err_if_already_started ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - /* Start defender for a second time. */ - error = AwsIotDefender_Start( &_startInfo ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_ALREADY_STARTED, error ); -} - -TEST( Defender_Unit, SetPeriod_too_short ) -{ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, AwsIotDefender_SetPeriod( 299 ) ); -} - -TEST( Defender_Unit, SetPeriod_with_proper_value ) -{ - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 301 ) ); - - TEST_ASSERT_EQUAL( 301, AwsIotDefender_GetPeriod() ); -} - -TEST( Defender_Unit, SetPeriod_after_started ) -{ - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); - _mockedMqttConnection = true; - _startInfo.mqttConnection = _mqttConnection; - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, - AwsIotDefender_Start( &_startInfo ) ); - - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 600 ) ); - - TEST_ASSERT_EQUAL( 600, AwsIotDefender_GetPeriod() ); -} \ No newline at end of file diff --git a/libraries/aws/jobs/CMakeLists.txt b/libraries/aws/jobs/CMakeLists.txt deleted file mode 100644 index 0f35e7429d..0000000000 --- a/libraries/aws/jobs/CMakeLists.txt +++ /dev/null @@ -1,75 +0,0 @@ -# Jobs library source files. -set( JOBS_SOURCES - src/aws_iot_jobs_api.c - src/aws_iot_jobs_serialize.c - src/aws_iot_jobs_operation.c - src/aws_iot_jobs_static_memory.c - src/aws_iot_jobs_subscription.c ) - -# Jobs library target. -add_library( awsiotjobs - ${CONFIG_HEADER_PATH}/iot_config.h - ${JOBS_SOURCES} - include/aws_iot_jobs.h - include/types/aws_iot_jobs_types.h - src/private/aws_iot_jobs_internal.h ) - -# Jobs public include path. -target_include_directories( awsiotjobs PUBLIC include ) - -# Link required libraries. -target_link_libraries( awsiotjobs PRIVATE awsiotcommon iotserializer iotbase ) - -# Jobs is currently implemented on MQTT, so link MQTT as a public dependency. -target_link_libraries( awsiotjobs PUBLIC iotmqtt ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotjobs PRIVATE unity ) -endif() - -# Organization of Jobs in folders. -set_property( TARGET awsiotjobs PROPERTY FOLDER libraries/aws ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES include/aws_iot_jobs.h ) -source_group( include\\types include/types/aws_iot_jobs_types.h ) -source_group( src\\private src/private/aws_iot_jobs_internal.h ) -source_group( src FILES ${JOBS_SOURCES} ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # Jobs system test sources. - set( JOBS_SYSTEM_TEST_SOURCES - test/system/aws_iot_tests_jobs_system.c ) - - # Jobs unit test sources. - set( JOBS_UNIT_TEST_SOURCES - test/unit/aws_iot_tests_jobs_api.c - test/unit/aws_iot_tests_jobs_serialize.c ) - - # Jobs tests executable. - add_executable( aws_iot_tests_jobs - ${JOBS_SYSTEM_TEST_SOURCES} - ${JOBS_UNIT_TEST_SOURCES} - test/aws_iot_tests_jobs.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( aws_iot_tests_jobs PRIVATE - -DRunTests=RunJobsTests ) - - # The Jobs tests need the internal Jobs header. - target_include_directories( aws_iot_tests_jobs PRIVATE src ) - - # Jobs tests library dependencies. - target_link_libraries( aws_iot_tests_jobs PRIVATE - awsiotjobs awsiotcommon iotserializer iotbase - unity iot_mqtt_mock ) - - # Organization of Jobs tests in folders. - set_property( TARGET aws_iot_tests_jobs PROPERTY FOLDER tests ) - source_group( system FILES ${JOBS_SYSTEM_TEST_SOURCES} ) - source_group( unit FILES ${JOBS_UNIT_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_jobs.c ) -endif() diff --git a/libraries/aws/jobs/include/aws_iot_jobs.h b/libraries/aws/jobs/include/aws_iot_jobs.h deleted file mode 100644 index 95e26f7026..0000000000 --- a/libraries/aws/jobs/include/aws_iot_jobs.h +++ /dev/null @@ -1,927 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs.h - * @brief User-facing functions of the Jobs library. - */ - -#ifndef AWS_IOT_JOBS_H_ -#define AWS_IOT_JOBS_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Jobs types include. */ -#include "types/aws_iot_jobs_types.h" - -/*------------------------- Jobs library functions --------------------------*/ - -/** - * @functionspage{jobs,Jobs library} - * - @functionname{jobs_function_init} - * - @functionname{jobs_function_cleanup} - * - @functionname{jobs_function_getpendingasync} - * - @functionname{jobs_function_getpendingsync} - * - @functionname{jobs_function_startnextasync} - * - @functionname{jobs_function_startnextsync} - * - @functionname{jobs_function_describeasync} - * - @functionname{jobs_function_describesync} - * - @functionname{jobs_function_updateasync} - * - @functionname{jobs_function_updatesync} - * - @functionname{jobs_function_wait} - * - @functionname{jobs_function_setnotifypendingcallback} - * - @functionname{jobs_function_setnotifynextcallback} - * - @functionname{jobs_function_removepersistentsubscriptions} - * - @functionname{jobs_function_strerror} - * - @functionname{jobs_function_statename} - */ - -/** - * @functionpage{AwsIotJobs_Init,jobs,init} - * @functionpage{AwsIotJobs_Cleanup,jobs,cleanup} - * @functionpage{AwsIotJobs_GetPendingAsync,jobs,getpendingasync} - * @functionpage{AwsIotJobs_GetPendingSync,jobs,getpendingsync} - * @functionpage{AwsIotJobs_StartNextAsync,jobs,startnextasync} - * @functionpage{AwsIotJobs_StartNextSync,jobs,startnextsync} - * @functionpage{AwsIotJobs_DescribeAsync,jobs,describeasync} - * @functionpage{AwsIotJobs_DescribeSync,jobs,describesync} - * @functionpage{AwsIotJobs_UpdateAsync,jobs,updateasync} - * @functionpage{AwsIotJobs_UpdateSync,jobs,updatesync} - * @functionpage{AwsIotJobs_Wait,jobs,wait} - * @functionpage{AwsIotJobs_SetNotifyPendingCallback,jobs,setnotifypendingcallback} - * @functionpage{AwsIotJobs_SetNotifyNextCallback,jobs,setnotifynextcallback} - * @functionpage{AwsIotJobs_RemovePersistentSubscriptions,jobs,removepersistentsubscriptions} - * @functionpage{AwsIotJobs_strerror,jobs,strerror} - * @functionpage{AwsIotJobs_StateName,jobs,statename} - */ - -/** - * @brief One-time initialization function for the Jobs library. - * - * This function performs internal setup of the Jobs library. It must be - * called once (and only once) before calling any other Jobs function. - * Calling this function more than once without first calling @ref - * jobs_function_cleanup may result in a crash. - * - * @param[in] mqttTimeoutMs The amount of time (in milliseconds) that the Jobs - * library will wait for MQTT operations. Optional; set this to `0` to use - * @ref AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_INIT_FAILED - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see @ref jobs_function_cleanup - */ -/* @[declare_jobs_init] */ -AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ); -/* @[declare_jobs_init] */ - -/** - * @brief One-time deinitialization function for the Jobs library. - * - * This function frees resources taken in @ref jobs_function_init and deletes - * any [persistent subscriptions.](@ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS) - * It should be called to clean up the Jobs library. After this function returns, - * @ref jobs_function_init must be called again before calling any other Jobs - * function. - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see @ref jobs_function_init - */ -/* @[declare_jobs_cleanup] */ -void AwsIotJobs_Cleanup( void ); -/* @[declare_jobs_cleanup] */ - -/** - * @brief Get the list of all pending jobs for a Thing and receive an asynchronous - * notification when the response arrives. - * - * This function implements the [GetPendingJobExecutions] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-getpendingjobexecutions) - * command of the Jobs API, which gets the list of all Jobs for a Thing that are - * not in a terminal state. The list of retrieved Jobs is returned as the `pResponse` - * member in #AwsIotJobsCallbackParam_t, or through the #AwsIotJobsResponse_t - * parameter of @ref jobs_function_wait. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pGetPendingOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Jobs operation - * completes. - * - * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully - * queuing the Jobs operation. - * @return If this function fails before queuing the Jobs operation, it will return one of: - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t - * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. - * @return Should the Jobs operation fail, the status will be one of: - * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - * - * @see @ref jobs_function_getpendingsync for a blocking variant of this function. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * // Signature of Jobs callback function. - * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); - * - * AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * // Set the request info. - * requestInfo.mqttConnection = _mqttConnection; - * requestInfo.pThingName = THING_NAME; - * requestInfo.thingNameLength = THING_NAME_LENGTH; - * - * // Set the callback function to invoke. - * callbackInfo.function = _jobsCallback; - * - * // Queue Jobs GET PENDING. - * AwsIotJobsError_t getPendingResult = AwsIotJobs_GetPendingAsync( &requestInfo, - * 0, - * &callbackInfo, - * &getPendingOperation ); - * - * // GET PENDING should have returned AWS_IOT_JOBS_STATUS_PENDING. The function - * // _jobsCallback will be invoked once the Jobs response is received. - * @endcode - */ -/* @[declare_jobs_getpendingasync] */ -AwsIotJobsError_t AwsIotJobs_GetPendingAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pGetPendingOperation ); -/* @[declare_jobs_getpendingasync] */ - -/** - * @brief Get the list of all pending jobs for a Thing with a timeout for receiving - * the response. - * - * This function queues a Jobs GET PENDING, then waits for the result. Internally, - * this function is a call to @ref jobs_function_getpendingasync followed by - * @ref jobs_function_wait. See @ref jobs_function_getpendingasync for more information - * on the Jobs GET PENDING command. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] timeoutMs If a response is not received within this timeout, this - * function returns #AWS_IOT_JOBS_TIMEOUT. - * @param[out] pJobsResponse The response received from the Jobs service. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - #AWS_IOT_JOBS_TIMEOUT - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - */ -/* @[declare_jobs_getpendingsync] */ -AwsIotJobsError_t AwsIotJobs_GetPendingSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_getpendingsync] */ - -/** - * @brief Start the next pending job execution for a Thing and receive an asynchronous - * notification when the response arrives. - * - * This function implements the [StartNextPendingJobExecution] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-startnextpendingjobexecution) - * command of the Jobs API, which gets and starts the next pending job execution - * for a Thing. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pStartNextOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Jobs operation - * completes. - * - * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully - * queuing the Jobs operation. - * @return If this function fails before queuing the Jobs operation, it will return one of: - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t - * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. - * @return Should the Jobs operation fail, the status will be one of: - * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - * - * @see @ref jobs_function_startnextsync for a blocking variant of this function. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * // Signature of Jobs callback function. - * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); - * - * AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * // Set the request info. The update info generally does not need to be - * // changed, as its defaults are suitable. - * requestInfo.mqttConnection = _mqttConnection; - * requestInfo.pThingName = THING_NAME; - * requestInfo.thingNameLength = THING_NAME_LENGTH; - * - * // Set the callback function to invoke. - * callbackInfo.function = _jobsCallback; - * - * // Queue Jobs START NEXT. - * AwsIotJobsError_t startNextResult = AwsIotJobs_StartNextAsync( &requestInfo, - * &updateInfo, - * 0, - * &callbackInfo, - * &startNextOperation ); - * - * // START NEXT should have returned AWS_IOT_JOBS_STATUS_PENDING. The function - * // _jobsCallback will be invoked once the Jobs response is received. - * @endcode - */ -/* @[declare_jobs_startnextasync] */ -AwsIotJobsError_t AwsIotJobs_StartNextAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pStartNextOperation ); -/* @[declare_jobs_startnextasync] */ - -/** - * @brief Start the next pending job execution for a Thing with a timeout for - * receiving the response. - * - * This function queues a Jobs START NEXT, then waits for the result. Internally, - * this function is a call to @ref jobs_function_startnextasync followed by - * @ref jobs_function_wait. See @ref jobs_function_startnextasync for more information - * on the Jobs START NEXT command. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] timeoutMs If a response is not received within this timeout, this - * function returns #AWS_IOT_JOBS_TIMEOUT. - * @param[out] pJobsResponse The response received from the Jobs service. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - #AWS_IOT_JOBS_TIMEOUT - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - */ -/* @[declare_jobs_startnextsync] */ -AwsIotJobsError_t AwsIotJobs_StartNextSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_startnextsync] */ - -/** - * @brief Get detailed information about a job execution and receive an asynchronous - * notification when the response arrives. - * - * This function implements the [DescribeJobExecution] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution) - * command of the Jobs API, which gets detailed information about a job execution. - * The description is returned as the `pResponse` member in #AwsIotJobsCallbackParam_t, - * or through the #AwsIotJobsResponse_t parameter of @ref jobs_function_wait. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] executionNumber The execution number to describe. Optional; pass - * #AWS_IOT_JOBS_NO_EXECUTION_NUMBER to ignore. - * @param[in] includeJobDocument Whether the response should include the full - * Job document. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pDescribeOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Jobs operation - * completes. - * - * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully - * queuing the Jobs operation. - * @return If this function fails before queuing the Jobs operation, it will return one of: - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t - * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. - * @return Should the Jobs operation fail, the status will be one of: - * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - * - * @see @ref jobs_function_describesync for a blocking variant of this function. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * // Signature of Jobs callback function. - * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); - * - * AwsIotJobsOperation_t describeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * // Set the request info. - * requestInfo.mqttConnection = _mqttConnection; - * requestInfo.pThingName = THING_NAME; - * requestInfo.thingNameLength = THING_NAME_LENGTH; - * - * // Describe the next Job. Or, this may be set to a specific Job ID. - * requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - * requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - * - * // Set the callback function to invoke. - * callbackInfo.function = _jobsCallback; - * - * // Queue Jobs DESCRIBE. - * AwsIotJobsError_t describeResult = AwsIotJobs_DescribeAsync( &requestInfo, - * AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - * false, - * 0, - * &callbackInfo, - * &describeOperation ); - * - * // DESCRIBE should have returned AWS_IOT_JOBS_STATUS_PENDING. The function - * // _jobsCallback will be invoked once the Jobs response is received. - * @endcode - */ -/* @[declare_jobs_describeasync] */ -AwsIotJobsError_t AwsIotJobs_DescribeAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pDescribeOperation ); -/* @[declare_jobs_describeasync] */ - -/** - * @brief Get detailed information about a job execution with a timeout for receiving - * the response. - * - * This function queues a Jobs DESCRIBE, then waits for the result. Internally, - * this function is a call to @ref jobs_function_describeasync followed by - * @ref jobs_function_wait. See @ref jobs_function_describeasync for more information - * on the Jobs DESCRIBE command. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] executionNumber The execution number to describe. Optional; pass - * #AWS_IOT_JOBS_NO_EXECUTION_NUMBER to ignore. - * @param[in] includeJobDocument Whether the response should include the full - * Job document. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] timeoutMs If a response is not received within this timeout, this - * function returns #AWS_IOT_JOBS_TIMEOUT. - * @param[out] pJobsResponse The response received from the Jobs service. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - #AWS_IOT_JOBS_TIMEOUT - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - */ -/* @[declare_jobs_describesync] */ -AwsIotJobsError_t AwsIotJobs_DescribeSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_describesync] */ - -/** - * @brief Update the status of a job execution and receive an asynchronous - * notification when the Job update completes. - * - * This function implements the [UpdateJobExecution] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-updatejobexecution) - * command of the Jobs API, which updates the status of a Job execution. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pUpdateOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Jobs operation - * completes. - * - * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully - * queuing the Jobs operation. - * @return If this function fails before queuing the Jobs operation, it will return one of: - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t - * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. - * @return Should the Jobs operation fail, the status will be one of: - * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - * - * @see @ref jobs_function_updatesync for a blocking variant of this function. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * // Signature of Jobs callback function. - * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); - * - * AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * // Set the request info. - * requestInfo.mqttConnection = _mqttConnection; - * requestInfo.pThingName = THING_NAME; - * requestInfo.thingNameLength = THING_NAME_LENGTH; - * - * // A Job ID must be set. AWS_IOT_JOBS_NEXT_JOB is not valid for UPDATE. - * requestInfo.pJobId = "job-id"; - * requestInfo.jobIdLength = 6; - * - * // Set the update info. - * updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; - * - * // Set the callback function to invoke. - * callbackInfo.function = _jobsCallback; - * - * // Queue Jobs UPDATE. - * AwsIotJobsError_t updateResult = AwsIotJobs_UpdateAsync( &requestInfo, - * &updateInfo, - * 0, - * &callbackInfo, - * &updateOperation ); - * - * // UPDATE should have returned AWS_IOT_JOBS_STATUS_PENDING. The function - * // _jobsCallback will be invoked once the Jobs response is received. - * @endcode - */ -/* @[declare_jobs_updateasync] */ -AwsIotJobsError_t AwsIotJobs_UpdateAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pUpdateOperation ); -/* @[declare_jobs_updateasync] */ - -/** - * @brief Update the status of a job execution with a timeout for receiving the - * response. - * - * This function queues a Jobs UPDATE, then waits for the result. Internally, - * this function is a call to @ref jobs_function_updateasync followed by - * @ref jobs_function_wait. See @ref jobs_function_updateasync for more information - * on the Jobs UPDATE command. - * - * @param[in] pRequestInfo Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. - * @param[in] flags Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - * @param[in] timeoutMs If a response is not received within this timeout, this - * function returns #AWS_IOT_JOBS_TIMEOUT. - * @param[out] pJobsResponse The response received from the Jobs service. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - #AWS_IOT_JOBS_TIMEOUT - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - */ -/* @[declare_jobs_updatesync] */ -AwsIotJobsError_t AwsIotJobs_UpdateSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_updatesync] */ - -/** - * @brief Wait for a Jobs operation to complete. - * - * This function blocks to wait for a [GET PENDING](@ref jobs_function_getpendingasync), - * [START NEXT](@ref jobs_function_startnextasync), [DESCRIBE](@ref jobs_function_describeasync), - * or [UPDATE](@ref jobs_function_updateasync) operation to complete. These operations are - * by default asynchronous; the function calls queue an operation for processing, - * and a callback is invoked once the operation is complete. - * - * To use this function, the flag #AWS_IOT_JOBS_FLAG_WAITABLE must have been - * set in the operation's function call. Additionally, this function must always - * be called with any waitable operation to clean up resources. - * - * Regardless of its return value, this function always clean up resources used - * by the waitable operation. This means `operation` is invalidated as soon as - * this function returns, even if it returns #AWS_IOT_JOBS_TIMEOUT or another - * error. - * - * @param[in] operation Reference to the Jobs operation to wait for. The flag - * #AWS_IOT_JOBS_FLAG_WAITABLE must have been set for this operation. - * @param[in] timeoutMs How long to wait before returning #AWS_IOT_JOBS_TIMEOUT. - * @param[out] pJobsResponse The response received from the Jobs service. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_BAD_RESPONSE - * - #AWS_IOT_JOBS_TIMEOUT - * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. - * - * Example: - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - * AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - * - * // Set the request info. - * requestInfo.mqttConnection = _mqttConnection; - * requestInfo.pThingName = THING_NAME; - * requestInfo.thingNameLength = THING_NAME_LENGTH; - * - * // Set the function used to allocate memory for an incoming response. - * requestInfo.mallocResponse = malloc; - * - * // A Job ID must be set. AWS_IOT_JOBS_NEXT_JOB is not valid for UPDATE. - * requestInfo.pJobId = "job-id"; - * requestInfo.jobIdLength = 6; - * - * // Set the update info. - * updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; - * - * // Queue Jobs UPDATE. - * AwsIotJobsError_t updateResult = AwsIotJobs_UpdateAsync( &requestInfo, - * &updateInfo, - * AWS_IOT_JOBS_FLAG_WAITABLE, - * NULL, - * &updateOperation ); - * - * // UPDATE should have returned AWS_IOT_JOBS_STATUS_PENDING. The call to wait - * // returns once the result of the UPDATE is available or the timeout expires. - * if( updateResult == AWS_IOT_JOBS_STATUS_PENDING ) - * { - * updateResult = AwsIotJobs_Wait( updateOperation, 5000, &jobsResponse ); - * - * if( updateResult == AWS_IOT_JOBS_SUCCESS ) - * { - * // Jobs operation succeeded. Do something with the Jobs response. - * - * // Once the Jobs response is no longer needed, free it. - * free( jobsResponse.pJobsResponse ); - * } - * else - * { - * // Jobs operation failed. - * } - * } - * @endcode - */ -/* @[declare_jobs_wait] */ -AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_wait] */ - -/** - * @brief Set a callback to be invoked when the list of pending Jobs changes. - * - * The Jobs service publishes a [JobExecutionsChanged] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-jobexecutionschanged) - * message to the `jobs/notify` topic whenever a Job execution is added to or - * removed from the list of pending Job executions for a Thing. The message sent is - * useful for monitoring the list of pending Job executions. - * - * A NOTIFY PENDING callback may be invoked whenever a message is published - * to `jobs/notify`. Each Thing may have up to @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS - * NOTIFY PENDING callbacks set. This function modifies the NOTIFY PENDING callback - * for a specific Thing depending on the `pNotifyPendingCallback` parameter and the - * presence of any existing NOTIFY PENDING callback. - * - When no existing NOTIFY PENDING callback exists for a specific Thing, a new - * callback is added. - * - If there is an existing NOTIFY PENDING callback and `pNotifyPendingCallback` is not `NULL`, - * then the existing callback function and parameter are replaced with `pNotifyPendingCallback`. - * - If there is an existing NOTIFY PENDING callback and `pNotifyPendingCallback` is `NULL`, - * then the callback is removed. - * - * The member @ref AwsIotJobsCallbackInfo_t.oldFunction must be used to select an - * already-registered callback function for replacement or removal when @ref - * AWS_IOT_JOBS_NOTIFY_CALLBACKS is greater than `1`. When multiple callbacks are - * set, all of them will be invoked when a message is received. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription to `jobs/notify`. - * @param[in] pThingName The subscription to `jobs/notify` will be added for - * this Thing Name. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags This parameter is for future-compatibility. Currently, flags are - * not supported for this function and this parameter is ignored. - * @param[in] pNotifyPendingCallback Callback function to invoke for incoming messages. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_TIMEOUT - * - * @see @ref jobs_function_setnotifynextcallback for the function to register callbacks - * for next Job changes. - * - * Example: - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * AwsIotJobsError_t result = AWS_IOT_JOBS_STATUS_PENDING; - * AwsIotJobsCallbackInfo_t notifyPendingCallback = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * // _jobsCallback will be invoked when any messages are received. - * notifyPendingCallback.function = _jobsCallback; - * - * // Set the NOTIFY PENDING callback for the Thing "Test_device". - * result = AwsIotJobs_SetNotifyPendingCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * ¬ifyPendingCallback ); - * - * // Check if the callback was successfully set. - * if( status == AWS_IOT_JOBS_SUCCESS ) - * { - * // The callback will now be invoked whenever the list of pending Job - * // executions changes. - * - * // Once the callback is no longer needed, it may be removed by passing - * // NULL as the callback function and specifying the function to remove. - * notifyPendingCallback.function = NULL; - * notifyPendingCallback.oldFunction = _jobsCallback; - * - * status = AwsIotJobs_SetNotifyPendingCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * ¬ifyPendingCallback ); - * - * // The return value from removing a callback should always be success. - * assert( status == AWS_IOT_JOBS_SUCCESS ); - * } - * @endcode - */ -/* @[declare_jobs_setnotifypendingcallback] */ -AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pNotifyPendingCallback ); -/* @[declare_jobs_setnotifypendingcallback] */ - -/** - * @brief Set a callback to be invoked when the next pending Job changes. - * - * The Jobs service publishes a [NextJobExecutionChanged] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-nextjobexecutionchanged) - * message to the `jobs/notify-next` topic whenever the next Job execution in - * the list of pending Job executions changes for a Thing. The message sent is - * useful for being notified of changes to the next Job. - * - * A NOTIFY NEXT callback may be invoked whenever a message is published - * to `jobs/notify-next`. Each Thing may have up to @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS - * NOTIFY NEXT callbacks set. This function modifies the NOTIFY NEXT callback for - * a specific Thing depending on the `pNotifyNextCallback` parameter and the presence - * of any existing NOTIFY NEXT callback. - * - When no existing NOTIFY NEXT callback exists for a specific Thing, a new - * callback is added. - * - If there is an existing NOTIFY NEXT callback and `pNotifyNextCallback` is not `NULL`, - * then the existing callback function and parameter are replaced with `pNotifyNextCallback`. - * - If there is an existing NOTIFY NEXT callback and `pNotifyNextCallback` is `NULL`, - * then the callback is removed. - * - * The member @ref AwsIotJobsCallbackInfo_t.oldFunction must be used to select an - * already-registered callback function for replacement or removal when @ref - * AWS_IOT_JOBS_NOTIFY_CALLBACKS is greater than `1`. When multiple callbacks are - * set, all of them will be invoked when a message is received. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription to `jobs/notify-next`. - * @param[in] pThingName The subscription to `jobs/notify-next` will be added for - * this Thing Name. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags This parameter is for future-compatibility. Currently, flags are - * not supported for this function and this parameter is ignored. - * @param[in] pNotifyNextCallback Callback function to invoke for incoming messages. - * - * @return One of the following: - * - #AWS_IOT_JOBS_SUCCESS - * - #AWS_IOT_JOBS_NOT_INITIALIZED - * - #AWS_IOT_JOBS_BAD_PARAMETER - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - #AWS_IOT_JOBS_TIMEOUT - * - * @see @ref jobs_function_setnotifypendingcallback for the function to register callbacks - * for all pending Job changes. - * - * Example: - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * AwsIotJobsError_t result = AWS_IOT_JOBS_STATUS_PENDING; - * AwsIotJobsCallbackInfo_t notifyNextCallback = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * // _jobsCallback will be invoked when any messages are received. - * notifyNextCallback.function = _jobsCallback; - * - * // Set the NOTIFY NEXT callback for the Thing "Test_device". - * result = AwsIotJobs_SetNotifyNextCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * ¬ifyNextCallback ); - * - * // Check if the callback was successfully set. - * if( status == AWS_IOT_JOBS_SUCCESS ) - * { - * // The callback will now be invoked whenever the next pending Job - * // execution changes. - * - * // Once the callback is no longer needed, it may be removed by passing - * // NULL as the callback function and specifying the function to remove. - * notifyNextCallback.function = NULL; - * notifyNextCallback.oldFunction = _jobsCallback; - * - * status = AwsIotJobs_SetNotifyNextCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * ¬ifyNextCallback ); - * - * // The return value from removing a callback should always be success. - * assert( status == AWS_IOT_JOBS_SUCCESS ); - * } - * @endcode - */ -/* @[declare_jobs_setnotifynextcallback] */ -AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pNotifyNextCallback ); -/* @[declare_jobs_setnotifynextcallback] */ - -/** - * @brief Remove persistent Jobs operation topic subscriptions. - * - * Passing the flag @ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS to @ref jobs_function_getpendingasync, - * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, @ref jobs_function_updateasync, - * or their blocking versions causes the Jobs operation topic subscriptions to be - * maintained for future calls to the same function. If a persistent subscription for a - * Jobs topic are no longer needed, this function may be used to remove it. - * - * @param[in] pRequestInfo Jobs request info. Only the [pThingName] - * (@ref #AwsIotJobsRequestInfo_t.pThingName), [thingNameLength] - * (@ref #AwsIotJobsRequestInfo_t.thingNameLength), and [mqttConnection] - * (@ref #AwsIotJobsRequestInfo_t.mqttConnection) members need to be set for this - * function. - * @param[in] flags Flags that determine which subscriptions to remove. Valid values are - * the bitwise OR of the following individual flags: - * - @ref AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS - * - @ref AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS - * - @ref AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS - * - @ref AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS - * - * @return On success: - * - #AWS_IOT_JOBS_SUCCESS - * @return If an MQTT UNSUBSCRIBE packet cannot be sent, one of the following: - * - #AWS_IOT_JOBS_NO_MEMORY - * - #AWS_IOT_JOBS_MQTT_ERROR - * - * @note @ref jobs_function_cleanup removes persistent sessions as well. - * - * @warning This function is not safe to call with any in-progress operations! - * It also does not affect NOTIFY PENDING and NOTIFY NEXT callbacks registered - * with @ref jobs_function_setnotifypendingcallback and - * @ref jobs_function_setnotifynextcallback, respectively. (See documentation for - * those functions on how to remove their callbacks). - */ -/* @[declare_jobs_removepersistentsubscriptions] */ -AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags ); -/* @[declare_jobs_removepersistentsubscriptions] */ - -/*-------------------------- Jobs helper functions --------------------------*/ - -/** - * @brief Returns a string that describes an #AwsIotJobsError_t. - * - * Like POSIX's `strerror`, this function returns a string describing a return - * code. In this case, the return code is a Jobs library error code, `status`. - * - * The string returned by this function MUST be treated as read-only: any - * attempt to modify its contents may result in a crash. Therefore, this function - * is limited to usage in logging. - * - * @param[in] status The status to describe. - * - * @return A read-only string that describes `status`. - * - * @warning The string returned by this function must never be modified. - */ -/* @[declare_jobs_strerror] */ -const char * AwsIotJobs_strerror( AwsIotJobsError_t status ); -/* @[declare_jobs_strerror] */ - -/** - * @brief Returns a string that describes an #AwsIotJobState_t. - * - * This function returns a string describing a Job state, `state`. - * - * The string returned by this function MUST be treated as read-only: any - * attempt to modify its contents may result in a crash. Therefore, this function - * is limited to usage in logging. - * - * @param[in] state The job state to describe. - * - * @return A read-only string that describes `state`. - * - * @warning The string returned by this function must never be modified. - */ -/* @[declare_jobs_statename] */ -const char * AwsIotJobs_StateName( AwsIotJobState_t state ); -/* @[declare_jobs_statename] */ - -#endif /* ifndef AWS_IOT_JOBS_H_ */ diff --git a/libraries/aws/jobs/include/types/aws_iot_jobs_types.h b/libraries/aws/jobs/include/types/aws_iot_jobs_types.h deleted file mode 100644 index 823bb69145..0000000000 --- a/libraries/aws/jobs/include/types/aws_iot_jobs_types.h +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_types.h - * @brief Types of the Jobs library. - */ - -#ifndef AWS_IOT_JOBS_TYPES_H_ -#define AWS_IOT_JOBS_TYPES_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/*---------------------------- Jobs handle types ----------------------------*/ - -/** - * @handles{jobs,Jobs library} - */ - -/** - * @ingroup jobs_datatypes_handles - * @brief Opaque handle that references an in-progress Jobs operation. - * - * Set as an output parameter of @ref jobs_function_getpendingasync, @ref jobs_function_startnextasync, - * @ref jobs_function_describeasync, and @ref jobs_function_updateasync. These functions send a - * message to the Jobs service requesting a Jobs operation; the result of this operation - * is unknown until the Jobs service sends a response. Therefore, this handle serves as a - * reference to Jobs operations awaiting a response from the Jobs service. - * - * This reference will be valid from the successful return of @ref jobs_function_getpendingasync, - * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, and @ref jobs_function_updateasync. - * The reference becomes invalid once the [completion callback](@ref AwsIotJobsCallbackInfo_t) - * is invoked, or @ref jobs_function_wait returns. - * - * @initializer{AwsIotJobsOperation_t,AWS_IOT_JOBS_OPERATION_INITIALIZER} - * - * @see @ref jobs_function_wait and #AWS_IOT_JOBS_FLAG_WAITABLE for waiting on - * a reference; or #AwsIotJobsCallbackInfo_t and #AwsIotJobsCallbackParam_t for an - * asynchronous notification of completion. - */ -typedef struct _jobsOperation * AwsIotJobsOperation_t; - -/*-------------------------- Jobs enumerated types --------------------------*/ - -/** - * @enums{jobs,Jobs library} - */ - -/** - * @ingroup jobs_datatypes_enums - * @brief Return codes of [Jobs functions](@ref jobs_functions). - * - * The function @ref jobs_function_strerror can be used to get a return code's - * description. - * - * The values between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE - * may be returned by the Jobs service upon failure of a Jobs operation. See [this page] - * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#jobs-mqtt-error-response) - * for more information. - */ -typedef enum AwsIotJobsError -{ - /** - * @brief Jobs operation completed successfully. - * - * Functions that may return this value: - * - @ref jobs_function_init - * - @ref jobs_function_wait - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_setnotifypendingcallback - * - @ref jobs_function_setnotifynextcallback - * - @ref jobs_function_removepersistentsubscriptions - * - * Will also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * when successful. - */ - AWS_IOT_JOBS_SUCCESS = 0, - - /** - * @brief Jobs operation queued, awaiting result. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingasync - * - @ref jobs_function_startnextasync - * - @ref jobs_function_describeasync - * - @ref jobs_function_updateasync - */ - AWS_IOT_JOBS_STATUS_PENDING = 1, - - /** - * @brief Initialization failed. - * - * Functions that may return this value: - * - @ref jobs_function_init - */ - AWS_IOT_JOBS_INIT_FAILED = 2, - - /** - * @brief At least one parameter is invalid. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync - * - @ref jobs_function_describeasync and @ref jobs_function_describesync - * - @ref jobs_function_updateasync and @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - @ref jobs_function_setnotifypendingcallback - * - @ref jobs_function_setnotifynextcallback - * - @ref jobs_function_removepersistentsubscriptions - */ - AWS_IOT_JOBS_BAD_PARAMETER = 3, - - /** - * @brief Jobs operation failed because of memory allocation failure. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync - * - @ref jobs_function_describeasync and @ref jobs_function_describesync - * - @ref jobs_function_updateasync and @ref jobs_function_updatesync - * - @ref jobs_function_setnotifypendingcallback - * - @ref jobs_function_setnotifynextcallback - */ - AWS_IOT_JOBS_NO_MEMORY = 4, - - /** - * @brief Jobs operation failed because of failure in MQTT library. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync - * - @ref jobs_function_describeasync and @ref jobs_function_describesync - * - @ref jobs_function_updateasync and @ref jobs_function_updatesync - * - @ref jobs_function_setnotifypendingcallback - * - @ref jobs_function_setnotifynextcallback - * - @ref jobs_function_removepersistentsubscriptions - */ - AWS_IOT_JOBS_MQTT_ERROR = 5, - - /** - * @brief Response received from Jobs service not understood. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). - */ - AWS_IOT_JOBS_BAD_RESPONSE = 7, - - /** - * @brief A blocking Jobs operation timed out. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - @ref jobs_function_setnotifypendingcallback - * - @ref jobs_function_setnotifynextcallback - */ - AWS_IOT_JOBS_TIMEOUT = 8, - - /** - * @brief An API function was called before @ref jobs_function_init. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync - * - @ref jobs_function_describeasync and @ref jobs_function_describesync - * - @ref jobs_function_updateasync and @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - @ref jobs_function_setnotifypendingcallback - * - @ref jobs_function_setnotifynextcallback - */ - AWS_IOT_JOBS_NOT_INITIALIZED = 11, - - /** - * @brief Jobs operation failed: A request was sent to an unknown topic. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). - */ - AWS_IOT_JOBS_INVALID_TOPIC = 12, - - /** - * @brief Jobs operation failed: The contents of the request were not understood. - * - * Jobs requests must be UTF-8 encoded JSON documents. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). - */ - AWS_IOT_JOBS_INVALID_JSON = 13, - - /** - * @brief Jobs operation failed: The contents of the request were invalid. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). - */ - AWS_IOT_JOBS_INVALID_REQUEST = 14, - - /** - * @brief Jobs operation failed: An update attempted to change the job execution - * to an invalid state. - * - * Functions that may return this value: - * - @ref jobs_function_startnextsync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_startnextasync or @ref jobs_function_updateasync. - */ - AWS_IOT_JOBS_INVALID_STATE = 15, - - /** - * @brief Jobs operation failed: The specified job execution does not exist. - * - * * Functions that may return this value: - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_describeasync or @ref jobs_function_updateasync. - */ - AWS_IOT_JOBS_NOT_FOUND = 16, - - /** - * @brief Jobs operation failed: The Jobs service expected a version that did - * not match what was in the request. - * - * * Functions that may return this value: - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_updateasync. - */ - AWS_IOT_JOBS_VERSION_MISMATCH = 17, - - /** - * @brief Jobs operation failed: The Jobs service encountered an internal error. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). - */ - AWS_IOT_JOBS_INTERNAL_ERROR = 18, - - /** - * @brief Jobs operation failed: The request was throttled. - * - * Functions that may return this value: - * - @ref jobs_function_getpendingsync - * - @ref jobs_function_startnextsync - * - @ref jobs_function_describesync - * - @ref jobs_function_updatesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). - */ - AWS_IOT_JOBS_THROTTLED = 19, - - /** - * @brief Jobs operation failed: Attempt to describe a Job in a terminal state. - * - * Functions that may return this value: - * - @ref jobs_function_describesync - * - @ref jobs_function_wait - * - * May also be the value of a Jobs operation completion callback's
- * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_describeasync. - */ - AWS_IOT_JOBS_TERMINAL_STATE = 20 -} AwsIotJobsError_t; - -/** - * @ingroup jobs_datatypes_enums - * @brief Possible states of jobs. - * - * The function @ref jobs_function_statename can be used to get a state's - * description. - * - * See [this page] - * (https://docs.aws.amazon.com/iot/latest/apireference/API_iot-jobs-data_JobExecutionState.html) - * for more information on Job states. - */ -typedef enum AwsIotJobState -{ - /** - * @brief A Job is queued and awaiting execution. - */ - AWS_IOT_JOB_STATE_QUEUED, - - /** - * @brief A Job is currently executing. - */ - AWS_IOT_JOB_STATE_IN_PROGRESS, - - /** - * @brief A Job has failed. This is a terminal state. - */ - AWS_IOT_JOB_STATE_FAILED, - - /** - * @brief A Job has succeeded. This is a terminal state. - */ - AWS_IOT_JOB_STATE_SUCCEEDED, - - /** - * @brief A Job was canceled. This is a terminal state. - */ - AWS_IOT_JOB_STATE_CANCELED, - - /** - * @brief A Job's timer has expired. This is a terminal state. - * - * Jobs are considered timed out if they remain [IN_PROGRESS] - * (@ref AWS_IOT_JOB_STATE_IN_PROGRESS) for more than their - * `inProgressTimeoutInMinutes` property or a [Job update](@ref jobs_function_updateasync) - * was not sent within [stepTimeoutInMinutes](@ref AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes). - */ - AWS_IOT_JOB_STATE_TIMED_OUT, - - /** - * @brief A Job was rejected by the device. This is a terminal state. - */ - AWS_IOT_JOB_STATE_REJECTED, - - /** - * @brief A Job was removed. This is a terminal state. - */ - AWS_IOT_JOB_STATE_REMOVED -} AwsIotJobState_t; - -/** - * @ingroup jobs_datatypes_enums - * @brief Types of Jobs library callbacks. - * - * One of these values will be placed in #AwsIotJobsCallbackParam_t.callbackType - * to identify the reason for invoking a callback function. - */ -typedef enum AwsIotJobsCallbackType -{ - AWS_IOT_JOBS_GET_PENDING_COMPLETE = 0, /**< Callback invoked because a [Jobs get pending](@ref jobs_function_getpendingasync) completed. */ - AWS_IOT_JOBS_START_NEXT_COMPLETE = 1, /**< Callback invoked because a [Jobs start next](@ref jobs_function_startnextasync) completed. */ - AWS_IOT_JOBS_DESCRIBE_COMPLETE = 2, /**< Callback invoked because a [Jobs describe](@ref jobs_function_describeasync) completed. */ - AWS_IOT_JOBS_UPDATE_COMPLETE = 3, /**< Callback invoked because a [Jobs update](@ref jobs_function_updateasync) completed. */ - AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK = 4, /**< Callback invoked for an incoming message on a [Jobs notify-pending](@ref jobs_function_setnotifypendingcallback) topic. */ - AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK = 5 /**< Callback invoked for an incoming message on a [Jobs notify-next](@ref jobs_function_setnotifynextcallback) topic. */ -} AwsIotJobsCallbackType_t; - -/*-------------------------- Jobs parameter structs -------------------------*/ - -/** - * @paramstructs{jobs,Jobs library} - */ - -/** - * @ingroup jobs_datatypes_paramstructs - * @brief Parameter to a Jobs callback function. - * - * @paramfor Jobs callback functions - * - * The Jobs library passes this struct to a callback function whenever a - * Jobs operation completes or a message is received on a Jobs notify-pending - * or notify-next topic. - * - * The valid members of this struct are different based on - * #AwsIotJobsCallbackParam_t.callbackType. If the callback type is - * #AWS_IOT_JOBS_GET_PENDING_COMPLETE, #AWS_IOT_JOBS_START_NEXT_COMPLETE, - * #AWS_IOT_JOBS_DESCRIBE_COMPLETE, or #AWS_IOT_JOBS_UPDATE_COMPLETE, then - * #AwsIotJobsCallbackParam_t.operation is valid. Otherwise, if the callback type - * is #AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK or #AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK, - * then #AwsIotJobsCallbackParam_t.callback is valid. - * - * @attention Any pointers in this callback parameter may be freed as soon as the - * [callback function](@ref AwsIotJobsCallbackInfo_t.function) returns. Therefore, - * data must be copied if it is needed after the callback function returns. - * @attention The Jobs library may set strings that are not NULL-terminated. - * - * @see #AwsIotJobsCallbackInfo_t for the signature of a callback function. - */ -typedef struct AwsIotJobsCallbackParam -{ - AwsIotJobsCallbackType_t callbackType; /**< @brief Reason for invoking the Jobs callback function to provide context. */ - - const char * pThingName; /**< @brief The Thing Name associated with this Jobs callback. */ - size_t thingNameLength; /**< @brief Length of #AwsIotJobsCallbackParam_t.pThingName. */ - - IotMqttConnection_t mqttConnection; /**< @brief The MQTT connection associated with the Jobs callback. */ - - union - { - /* Valid for completed Jobs operations. */ - struct - { - AwsIotJobsError_t result; /**< @brief Result of Jobs operation, e.g. succeeded or failed. */ - AwsIotJobsOperation_t reference; /**< @brief Reference to the Jobs operation that completed. */ - - const char * pResponse; /**< @brief Response retrieved from the Jobs service. */ - size_t responseLength; /**< @brief Length of retrieved response. */ - } operation; /**< @brief Information on a completed Jobs operation. */ - - /* Valid for a message on a Jobs notify-pending or notify-next topic. */ - struct - { - const char * pDocument; /**< @brief Job execution document received on callback. */ - size_t documentLength; /**< @brief Length of job execution document. */ - } callback; /**< @brief Jobs document from an incoming delta or updated topic. */ - } u; /**< @brief Valid member depends on callback type. */ -} AwsIotJobsCallbackParam_t; - -/** - * @ingroup jobs_datatypes_paramstructs - * @brief Information on a user-provided Jobs callback function. - * - * @paramfor @ref jobs_function_getpendingasync, @ref jobs_function_startnextasync, - * @ref jobs_function_describeasync, @ref jobs_function_updateasync, - * @ref jobs_function_setnotifypendingcallback, @ref jobs_function_setnotifynextcallback - * - * Provides a function to be invoked when a Jobs operation completes or when a - * Jobs document is received on a callback topic (notify-pending or notify-next). - * - * @initializer{AwsIotJobsCallbackInfo_t,AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER} - */ -typedef struct AwsIotJobsCallbackInfo -{ - void * pCallbackContext; /**< @brief The first parameter to pass to the callback function. */ - - /** - * @brief User-provided callback function signature. - * - * @param[in] void* #AwsIotJobsCallbackInfo_t.pCallbackContext - * @param[in] AwsIotJobsCallbackParam_t* Details on the outcome of the Jobs - * operation or an incoming Job document. - * - * @see #AwsIotJobsCallbackParam_t for more information on the second parameter. - */ - void ( * function )( void *, - AwsIotJobsCallbackParam_t * ); - - /** - * @brief Callback function to replace when passed to @ref jobs_function_setnotifynextcallback - * or @ref jobs_function_setnotifypendingcallback. - * - * This member is ignored by Jobs operation functions. - * - * The number of callbacks of each type that may be registered for each Thing - * is limited by @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS. If @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS - * is `2`, that means that a maximum of `2` NOTIFY PENDING and `2` NOTIFY NEXT callbacks - * (`4` total callbacks) may be set. This member is used to replace an existing callback - * with a new one. - * - * To add a new callback: - * @code{c} - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * callbackInfo.function = _newCallback; - * callbackInfo.oldFunction = NULL; - * @endcode - * - * For example, if the function `_oldCallback()` is currently registered: - * - To replace `_oldCallback()` with a new callback function `_newCallback()`: - * @code{c} - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * callbackInfo.function = _newCallback; - * callbackInfo.oldFunction = _oldCallback; - * @endcode - * - To remove `_oldCallback()`: - * @code{c} - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * - * callbackInfo.function = NULL; - * callbackInfo.oldFunction = _oldCallback; - * @endcode - */ - void ( * oldFunction )( void *, - AwsIotJobsCallbackParam_t * ); -} AwsIotJobsCallbackInfo_t; - -/** - * @ingroup jobs_datatypes_paramstructs - * @brief Common information provided to Jobs requests. - * - * @paramfor @ref jobs_function_getpendingasync, @ref jobs_function_getpendingsync, - * @ref jobs_function_startnextasync, @ref jobs_function_startnextsync - * @ref jobs_function_describeasync, @ref jobs_function_describesync, - * @ref jobs_function_updateasync, @ref jobs_function_updatesync - * - * @initializer{AwsIotJobsRequestInfo_t,AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER} - */ -typedef struct AwsIotJobsRequestInfo -{ - /** - * @brief The MQTT connection to use for the Jobs request. - */ - IotMqttConnection_t mqttConnection; - - /* These members allow Jobs commands to be retried. Be careful that duplicate - * commands do no cause unexpected application behavior. Use of QoS 0 is recommended. */ - IotMqttQos_t qos; /**< @brief QoS when sending the Jobs command. See #IotMqttPublishInfo_t.qos. */ - uint32_t retryLimit; /**< @brief Maximum number of retries for the Jobs command. See #IotMqttPublishInfo_t.retryLimit. */ - uint32_t retryMs; /**< @brief First retry time for the Jobs command. See IotMqttPublishInfo_t.retryMs. */ - - /** - * @brief Function to allocate memory for an incoming response. - * - * This only needs to be set if #AWS_IOT_JOBS_FLAG_WAITABLE is passed. - */ - void * ( *mallocResponse )( size_t ); - - /** - * @brief The Thing Name associated with the Job. - */ - const char * pThingName; - - /** - * @brief Length of #AwsIotJobsRequestInfo_t.pThingName. - */ - size_t thingNameLength; - - /** - * @brief The Job ID to update. - * - * This may be set to #AWS_IOT_JOBS_NEXT_JOB to update the next pending Job. - * When using #AWS_IOT_JOBS_NEXT_JOB, #AwsIotJobsRequestInfo_t.jobIdLength - * should be set to #AWS_IOT_JOBS_NEXT_JOB_LENGTH. - * - * This parameter is ignored for calls to @ref jobs_function_getpendingasync, - * @ref jobs_function_getpendingsync, @ref jobs_function_startnextasync, - * and @ref jobs_function_startnextsync. - */ - const char * pJobId; - - /** - * @brief Length of #AwsIotJobsRequestInfo_t.pJobId. - * - * This parameter is ignored for calls to @ref jobs_function_getpendingasync, - * @ref jobs_function_getpendingsync, @ref jobs_function_startnextasync, - * and @ref jobs_function_startnextsync. - */ - size_t jobIdLength; - - /** - * @brief An arbitrary string that identifies this Job update to the Jobs - * service. - * - * The recommended value is #AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE. - */ - const char * pClientToken; - - /** - * @brief Length of #AwsIotJobsRequestInfo_t.pClientToken. - */ - size_t clientTokenLength; -} AwsIotJobsRequestInfo_t; - -/** - * @ingroup jobs_datatypes_paramstructs - * @brief Output parameter of blocking Jobs API functions. - * - * @paramfor @ref jobs_function_getpendingsync, @ref jobs_function_startnextsync, - * @ref jobs_function_describesync, @ref jobs_function_updatesync, - * @ref jobs_function_wait - * - * Provides the response received from the Jobs service. The buffer for the - * response is allocated with #AwsIotJobsRequestInfo_t.mallocResponse. - * - * @initializer{AwsIotJobsResponse_t,AWS_IOT_JOBS_RESPONSE_INITIALIZER} - */ -typedef struct AwsIotJobsResponse -{ - const char * pJobsResponse; /**< @brief JSON response received from the Jobs service. */ - size_t jobsResponseLength; /**< @brief Length of #AwsIotJobsResponse_t.pJobsResponse. */ -} AwsIotJobsResponse_t; - -/** - * @ingroup jobs_datatypes_paramstructs - * @brief Information on a Job update for @ref jobs_function_startnextasync and - * @ref jobs_function_updateasync. These functions modify a Job's state. - * - * @paramfor @ref jobs_function_startnextasync, @ref jobs_function_startnextsync, - * @ref jobs_function_updateasync, and @ref jobs_function_updatesync. - * - * @initializer{AwsIotJobsUpdateInfo_t,AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER} - */ -typedef struct AwsIotJobsUpdateInfo -{ - /** - * @brief The new status to report as a Job execution update. - * - * Valid values are: - * - #AWS_IOT_JOB_STATE_IN_PROGRESS - * - #AWS_IOT_JOB_STATE_FAILED - * - #AWS_IOT_JOB_STATE_SUCCEEDED - * - #AWS_IOT_JOB_STATE_REJECTED - * - * This parameter is ignored for calls to @ref jobs_function_startnextasync and - * @ref jobs_function_startnextsync. These functions always set the state - * to #AWS_IOT_JOB_STATE_IN_PROGRESS. - */ - AwsIotJobState_t newStatus; - - /** - * @brief The expected current version of job execution. - * - * Each time a Job update is sent (for the same `JobId`), the version stored - * on the AWS IoT Jobs service is updated. If this value does not match the - * value stored by the Jobs service, the Job update is rejected with the code - * #AWS_IOT_JOBS_VERSION_MISMATCH. - * - * This value is useful for ensuring the order of Job updates, i.e. that the - * Jobs service does not overwrite a later update with a previous one. If not - * needed, it can be set to #AWS_IOT_JOBS_NO_VERSION. - * - * This parameter is ignored for calls to @ref jobs_function_startnextasync and - * @ref jobs_function_startnextsync. - */ - uint32_t expectedVersion; - - /** - * @brief An application-defined value that identifies a Job execution on a - * specific device. - * - * The Jobs service provides commands for tracking the status of Job execution - * on a specific target. Therefore, this value is used to provide a unique - * identifier of a specific Job execution on a specific target. - * - * This value is optional. It may be set to #AWS_IOT_JOBS_NO_EXECUTION_NUMBER - * if not needed. - * - * This parameter is ignored for calls to @ref jobs_function_startnextasync and - * @ref jobs_function_startnextsync. - */ - int32_t executionNumber; - - /** - * @brief The amount of time (in minutes) before a new Job update must be reported. - * - * If this timeout expires without a new Job update being reported (for the same - * `jobId`), the Job's status is set to #AWS_IOT_JOB_STATE_TIMED_OUT. Sending a - * new Job update will reset this step timeout; a value of #AWS_IOT_JOBS_NO_TIMEOUT - * will clear any previous step timeout. - * - * Valid values are between 1 and 10,080 (7 days). This value is optional. It may - * be set to #AWS_IOT_JOBS_NO_TIMEOUT if not needed. - */ - int32_t stepTimeoutInMinutes; - - /** - * @brief Whether the Job response document should contain the `JobExecutionState`. - * - * The default value is `false`. - * - * This parameter is ignored for calls to @ref jobs_function_startnextasync and - * @ref jobs_function_startnextsync. - */ - bool includeJobExecutionState; - - /** - * @brief Whether the Job response document should contain the `JobDocument`. - * - * The default value is `false`. - * - * This parameter is ignored for calls to @ref jobs_function_startnextasync and - * @ref jobs_function_startnextsync. - */ - bool includeJobDocument; - - /** - * @brief An application-defined set of JSON name-value pairs that describe - * the status of Job execution. - * - * This value is optional. It may be set to #AWS_IOT_JOBS_NO_STATUS_DETAILS - * if not needed. - */ - const char * pStatusDetails; - - /** - * @brief Length of #AwsIotJobsUpdateInfo_t.pStatusDetails. - */ - size_t statusDetailsLength; -} AwsIotJobsUpdateInfo_t; - -/*------------------------- Jobs defined constants --------------------------*/ - -/** - * @brief Set #AwsIotJobsRequestInfo_t.pJobId to this value to use the next pending - * Job as the Job ID. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_NEXT_JOB ( "$next" ) - -/** - * @brief Length of #AWS_IOT_JOBS_NEXT_JOB. - * - * Set #AwsIotJobsRequestInfo_t.jobIdLength to this value when using - * #AWS_IOT_JOBS_NEXT_JOB. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_NEXT_JOB_LENGTH ( sizeof( AWS_IOT_JOBS_NEXT_JOB ) - 1 ) - -/** - * @brief Set #AwsIotJobsRequestInfo_t.pClientToken to this value to automatically - * generate a client token for the Jobs request. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ( NULL ) - -/** - * @brief Set #AwsIotJobsUpdateInfo_t.expectedVersion to this value to omit the - * version in the Jobs request. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_NO_VERSION ( 0 ) - -/** - * @brief Set #AwsIotJobsUpdateInfo_t.executionNumber to this value or pass it to - * @ref jobs_function_describeasync to omit the execution number in the Jobs request. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_NO_EXECUTION_NUMBER ( -1 ) - -/** - * @brief Set #AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes to this value to omit the - * step timeout in the Jobs request. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_NO_TIMEOUT ( 0 ) - -/** - * @brief Set #AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes to this value to cancel - * any previously set step timeout. - * - * The Jobs service will return an (InvalidRequest)[@ref AWS_IOT_JOBS_INVALID_REQUEST] - * error if this value is used without an existing step timeout. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_CANCEL_TIMEOUT ( -1 ) - -/** - * @brief Set #AwsIotJobsUpdateInfo_t.pStatusDetails to this value to omit the - * status details in the Jobs request. - * - * @note The value of this constant may change at any time in future versions, but - * its name will remain the same. - */ -#define AWS_IOT_JOBS_NO_STATUS_DETAILS ( NULL ) - -/** - * @constantspage{jobs,Jobs library} - * - * @section jobs_constants_initializers Jobs Initializers - * @brief Provides default values for the data types of the Jobs library. - * - * @snippet this define_jobs_initializers - * - * All user-facing data types of the Jobs library should be initialized - * using one of the following. - * - * @warning Failing to initialize a Jobs data type with the appropriate - * initializer may result in undefined behavior! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - * - * Example - * @code{c} - * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - * AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - * AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - * @endcode - * - * @section jobs_constants_flags Jobs Function Flags - * @brief Flags that modify the behavior of Jobs library functions. - * - * Flags should be bitwise-ORed with each other to change the behavior of - * Jobs library functions. - * - * The following flags are valid for the Jobs operation functions: - * @ref jobs_function_getpendingasync, @ref jobs_function_startnextasync, - * @ref jobs_function_describeasync, @ref jobs_function_updateasync, - * and their blocking versions. - * - #AWS_IOT_JOBS_FLAG_WAITABLE
- * @copybrief AWS_IOT_JOBS_FLAG_WAITABLE - * - #AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS
- * @copybrief AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS - * - * The following flags are valid for @ref jobs_function_removepersistentsubscriptions. - * These flags are not valid for the Jobs operation functions. - * - #AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS
- * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS - * - #AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS
- * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS - * - #AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS
- * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS - * - #AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS
- * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS - * - * @note The values of the flags may change at any time in future versions, but - * their names will remain the same. Additionally, flags which may be used at - * the same time will be bitwise-exclusive of each other. - */ - -/* @[define_jobs_initializers] */ -#define AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotJobsCallbackInfo_t. */ -/** @brief Initializer for #AwsIotJobsRequestInfo_t. */ -#define AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER \ - { .pClientToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE } -/** @brief Initializer for #AwsIotJobsUpdateInfo_t. */ -#define AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER \ - { .newStatus = AWS_IOT_JOB_STATE_IN_PROGRESS, \ - .expectedVersion = AWS_IOT_JOBS_NO_VERSION, \ - .executionNumber = AWS_IOT_JOBS_NO_EXECUTION_NUMBER, \ - .stepTimeoutInMinutes = AWS_IOT_JOBS_NO_TIMEOUT, \ - .includeJobExecutionState = false, \ - .includeJobDocument = false, \ - .pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS } -#define AWS_IOT_JOBS_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ -#define AWS_IOT_JOBS_RESPONSE_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotJobsResponse_t. */ -/* @[define_jobs_initializers] */ - -/** - * @brief Allows the use of @ref jobs_function_wait for blocking until completion. - * - * This flag is only valid if passed to the functions @ref jobs_function_getpendingasync, - * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, or @ref jobs_function_updateasync. - * - * An #AwsIotJobsOperation_t MUST be provided if this flag is set. - * Additionally, an #AwsIotJobsCallbackInfo_t MUST NOT be provided. - * - * When this flag is set, #AwsIotJobsRequestInfo_t.mallocResponse must be set - * to a function that can be used to allocate memory to hold an incoming response. - * - * @note If this flag is set, @ref jobs_function_wait MUST be called to - * clean up resources. - */ -#define AWS_IOT_JOBS_FLAG_WAITABLE ( 0x00000001 ) - -/** - * @brief Maintain the subscriptions for the Jobs operation topics, even after - * this function returns. - * - * This flag is only valid if passed to the functions @ref jobs_function_getpendingasync, - * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, or @ref jobs_function_updateasync, - * and their blocking versions. - * - * The Jobs service reports results of Jobs operations by publishing - * messages to MQTT topics. By default, the Job operation functions subscribe to the - * necessary topics, wait for the Jobs service to publish the result of the - * Jobs operation, then unsubscribe from those topics. This workflow is suitable - * for infrequent Jobs operations, but is inefficient for frequent, periodic - * Jobs operations (where subscriptions for the Jobs operation topics would be - * constantly added and removed). - * - * This flag causes the Jobs operation functions to maintain Jobs operation - * topic subscriptions, even after the function returns. These subscriptions - * may then be used by a future call to the same function. - * - * This flags only needs to be set once, after which subscriptions are maintained - * and reused for a specific Thing Name and Jobs function. The function @ref - * jobs_function_removepersistentsubscriptions may be used to remove - * subscriptions maintained by this flag. - */ -#define AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) - -/** - * @brief Remove the persistent subscriptions from a Jobs get pending operation. - * - * This flag is only valid if passed to the function @ref - * jobs_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref jobs_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_getpendingasync or @ref jobs_function_getpendingsync. - * - * @warning Do not call @ref jobs_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Jobs get pending operations. - */ -#define AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ( 0x00000001 ) - -/** - * @brief Remove the persistent subscriptions from a Jobs start next operation. - * - * This flag is only valid if passed to the function @ref - * jobs_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref jobs_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_startnextasync or @ref jobs_function_startnextsync. - * - * @warning Do not call @ref jobs_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Jobs start next operations. - */ -#define AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS ( 0x00000002 ) - -/** - * @brief Remove the persistent subscriptions from a Jobs describe operation. - * - * This flag is only valid if passed to the function @ref - * jobs_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref jobs_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_describeasync or @ref jobs_function_describesync. - * - * @warning Do not call @ref jobs_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Jobs describe operations. - */ -#define AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ( 0x00000004 ) - -/** - * @brief Remove the persistent subscriptions from a Jobs update operation. - * - * This flag is only valid if passed to the function @ref - * jobs_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref jobs_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_updateasync or @ref jobs_function_updatesync. - * - * @warning Do not call @ref jobs_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Jobs update operations. - */ -#define AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000008 ) - -#endif /* ifndef AWS_IOT_JOBS_TYPES_H_ */ diff --git a/libraries/aws/jobs/lexicon.txt b/libraries/aws/jobs/lexicon.txt deleted file mode 100644 index d44508df38..0000000000 --- a/libraries/aws/jobs/lexicon.txt +++ /dev/null @@ -1,122 +0,0 @@ -api -apireference -autogenerated -aws -awsiotjobs -awsiotjobscallbackinfo -awsiotjobscallbackparam -awsiotjobscallbacktype -awsiotjobserror -awsiotjobsoperation -awsiotjobsrequestinfo -awsiotjobsresponse -awsiotjobsupdateinfo -bool -callbackreferences -callbacktype -clienttoken -clienttokenlength -com -config -const -describeasync -describejobexecution -describesync -developerguide -documentlength -doesn -endcond -endif -executionnumber -expectedresult -expectedtype -getpendingasync -getpendingjobexecutions -getpendingsync -gr -html -http -https -ifndef -includejobdocument -includejobexecutionstate -init -int -internalerror -invalidjson -invalidrequest -invalidstatetransition -invalidtopic -iot -iotlink -iotmqttconnection -iotmqttpublishinfo -iotmqttqos -iotsemaphore -isn -jobexecutionschanged -jobexecutionstate -jobidlength -jobscallback -jobsoperation -jobsoperationtype -jobsrequestlength -jobsresponselength -jobssubscription -json -malloc -mqtt -mqttconnection -mutex -mutexes -nextjobexecutionchanged -notifynextcallbackwrapper -notifypendingcallbackwrapper -oldfunction -onlinepubs -opengroup -operationmatchparams -operationreferences -org -param -pcallbackcontext -pclienttoken -pdocument -pinusejobsoperations -pinusejobssubscriptions -pjobid -pjobsoperations -pjobsrequest -pjobsresponse -pjobssubscriptions -presponse -psubscription -pthingname -pupdateinfo -qos -removepersistentsubscriptions -requestthrottled -resourcenotfound -responselength -retrylimit -retryms -sdk -setnotifynextcallback -setnotifypendingcallback -startnextasync -startnextpendingjobexecution -startnextsync -statename -strerror -structs -suback -terminalstatereached -thingnamelength -uint -updateasync -updatejobexecution -updatesync -versionmismatch -waitable -waitsem -waitsemaphore diff --git a/libraries/aws/jobs/src/aws_iot_jobs_api.c b/libraries/aws/jobs/src/aws_iot_jobs_api.c deleted file mode 100644 index 13ded7b0d3..0000000000 --- a/libraries/aws/jobs/src/aws_iot_jobs_api.c +++ /dev/null @@ -1,1624 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_api.c - * @brief Implements the user-facing functions of the Jobs library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Validate Jobs configuration settings. */ -#if AWS_IOT_JOBS_ENABLE_ASSERTS != 0 && AWS_IOT_JOBS_ENABLE_ASSERTS != 1 - #error "AWS_IOT_JOBS_ENABLE_ASSERTS must be 0 or 1." -#endif -#if AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS <= 0 - #error "AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS cannot be 0 or negative." -#endif -#if AWS_IOT_JOBS_NOTIFY_CALLBACKS <= 0 - #error "AWS_IOT_JOBS_NOTIFY_CALLBACKS cannot be 0 or negative." -#endif - -/** - * @brief Returned by @ref _getCallbackIndex when there's no space in the callback array. - */ -#define NO_SPACE_FOR_CALLBACK ( -1 ) - -/** - * @brief Returned by @ref _getCallbackIndex when a searching for an oldCallback that - * does not exist. - */ -#define OLD_CALLBACK_NOT_FOUND ( -2 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Check if the library is initialized. - * - * @return `true` if AwsIotJobs_Init was called; `false` otherwise. - */ -static bool _checkInit( void ); - -/** - * @brief Validate the #AwsIotJobsRequestInfo_t passed to a Jobs API function. - * - * @param[in] type The Jobs API function type. - * @param[in] pRequestInfo The request info passed to a Jobs API function. - * @param[in] flags Flags used by the Jobs API function. - * @param[in] pCallbackInfo The callback info passed with the request info. - * @param[in] pOperation Operation reference pointer passed to a Jobs API function. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_BAD_PARAMETER. - */ -static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - const AwsIotJobsOperation_t * pOperation ); - -/** - * @brief Validate the #AwsIotJobsUpdateInfo_t passed to a Jobs API function. - * - * @param[in] type The Jobs API function type. - * @param[in] pUpdateInfo The update info passed to a Jobs API function. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_BAD_PARAMETER. - */ -static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, - const AwsIotJobsUpdateInfo_t * pUpdateInfo ); - -/** - * @brief Gets an element of the callback array to modify. - * - * @param[in] type The type of callback to modify. - * @param[in] pSubscription Subscription object that holds the callback array. - * @param[in] pCallbackInfo User provided callback info. - * - * @return The index of the callback array to modify; on error: - * - #NO_SPACE_FOR_CALLBACK if no free spaces are available - * - #OLD_CALLBACK_NOT_FOUND if an old callback to remove was specified, but that function does not exist. - * - * @note This function should be called with the subscription list mutex locked. - */ -static int32_t _getCallbackIndex( _jobsCallbackType_t type, - _jobsSubscription_t * pSubscription, - const AwsIotJobsCallbackInfo_t * pCallbackInfo ); - -/** - * @brief Common function for setting Jobs callbacks. - * - * @param[in] mqttConnection The MQTT connection to use. - * @param[in] type Type of Jobs callback. - * @param[in] pThingName Thing Name for Jobs callback. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] pCallbackInfo Callback information to set. - * - * @return #AWS_IOT_JOBS_SUCCESS, #AWS_IOT_JOBS_BAD_PARAMETER, - * #AWS_IOT_JOBS_NO_MEMORY, or #AWS_IOT_JOBS_MQTT_ERROR. - */ -static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, - _jobsCallbackType_t type, - const char * pThingName, - size_t thingNameLength, - const AwsIotJobsCallbackInfo_t * pCallbackInfo ); - -/** - * @brief Change the subscriptions for Jobs callbacks, either by subscribing - * or unsubscribing. - * - * @param[in] mqttConnection The MQTT connection to use. - * @param[in] type Type of Jobs callback. - * @param[in] pSubscription Jobs subscriptions object for callback. - * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or - * @ref mqtt_function_unsubscribesync. - * - * @return #AWS_IOT_JOBS_SUCCESS, #AWS_IOT_JOBS_NO_MEMORY, or - * #AWS_IOT_JOBS_MQTT_ERROR. - */ -static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, - _jobsCallbackType_t type, - _jobsSubscription_t * pSubscription, - AwsIotMqttFunction_t mqttOperation ); - -/** - * @brief Invoked when a document is received on the Jobs NOTIFY PENDING callback. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage The received Jobs document (as an MQTT PUBLISH message). - */ -static void _notifyPendingCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a document is received on the Jobs NOTIFY NEXT callback. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage The received Jobs document (as an MQTT PUBLISH message). - */ -static void _notifyNextCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Common function for incoming Jobs callbacks. - * - * @param[in] type Jobs callback type. - * @param[in] pMessage The received Jobs callback document (as an MQTT PUBLISH - * message). - */ -static void _callbackWrapperCommon( _jobsCallbackType_t type, - IotMqttCallbackParam_t * pMessage ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Tracks whether @ref jobs_function_init has been called. - * - * API functions will fail if @ref jobs_function_init was not called. - */ -static uint32_t _initCalled = 0; - -/** - * @brief Timeout used for MQTT operations. - */ -uint32_t _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; - -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - -/** - * @brief Printable names for the Jobs callbacks. - */ - const char * const _pAwsIotJobsCallbackNames[] = - { - "NOTIFY PENDING", - "NOTIFY NEXT" - }; -#endif - -/*-----------------------------------------------------------*/ - -static bool _checkInit( void ) -{ - bool status = true; - - if( _initCalled == 0 ) - { - IotLogError( "AwsIotJobs_Init was not called." ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - const AwsIotJobsOperation_t * pOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - - /* Check that the given MQTT connection is valid. */ - if( pRequestInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) - { - IotLogError( "MQTT connection is not initialized for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* Check Thing Name. */ - if( AwsIot_ValidateThingName( pRequestInfo->pThingName, - pRequestInfo->thingNameLength ) == false ) - { - IotLogError( "Thing Name is not valid for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* Checks for waitable operations. */ - if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) - { - if( pOperation == NULL ) - { - IotLogError( "Reference must be provided for a waitable Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( pRequestInfo->mallocResponse == NULL ) - { - IotLogError( "Memory allocation function must be set for waitable Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( pCallbackInfo != NULL ) - { - IotLogError( "Callback should not be set for a waitable Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - /* Check that a callback function is set. */ - if( pCallbackInfo != NULL ) - { - if( pCallbackInfo->function == NULL ) - { - IotLogError( "Callback function must be set for Jobs %s callback.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - /* Check client token length. */ - if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - if( pRequestInfo->clientTokenLength == 0 ) - { - IotLogError( "Client token length must be set for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( pRequestInfo->clientTokenLength > AWS_IOT_CLIENT_TOKEN_MAX_LENGTH ) - { - IotLogError( "Client token for Jobs %s cannot be longer than %d.", - _pAwsIotJobsOperationNames[ type ], - AWS_IOT_CLIENT_TOKEN_MAX_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - /* Check Job ID for DESCRIBE and UPDATE. */ - if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) - { - if( ( pRequestInfo->pJobId == NULL ) || ( pRequestInfo->jobIdLength == 0 ) ) - { - IotLogError( "Job ID must be set for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( pRequestInfo->jobIdLength > JOBS_MAX_ID_LENGTH ) - { - IotLogError( "Job ID for Jobs %s cannot be longer than %d.", - _pAwsIotJobsOperationNames[ type ], - JOBS_MAX_ID_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - /* A Job ID (not $next job) must be specified for UPDATE. */ - if( type == JOBS_UPDATE ) - { - if( ( pRequestInfo->jobIdLength == AWS_IOT_JOBS_NEXT_JOB_LENGTH ) && - ( strncmp( AWS_IOT_JOBS_NEXT_JOB, - pRequestInfo->pJobId, - AWS_IOT_JOBS_NEXT_JOB_LENGTH ) == 0 ) ) - { - IotLogError( "Job ID $next is not valid for Jobs UPDATE." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, - const AwsIotJobsUpdateInfo_t * pUpdateInfo ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - - /* Only START NEXT and UPDATE operations need an update info. */ - AwsIotJobs_Assert( ( type == JOBS_START_NEXT ) || ( type == JOBS_UPDATE ) ); - - /* Check that Job status to report is valid for Jobs UPDATE. */ - if( type == JOBS_UPDATE ) - { - switch( pUpdateInfo->newStatus ) - { - case AWS_IOT_JOB_STATE_IN_PROGRESS: - case AWS_IOT_JOB_STATE_FAILED: - case AWS_IOT_JOB_STATE_SUCCEEDED: - case AWS_IOT_JOB_STATE_REJECTED: - break; - - default: - IotLogError( "Job state %s is not valid for Jobs UPDATE.", - AwsIotJobs_StateName( pUpdateInfo->newStatus ) ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - /* Check that step timeout is valid. */ - if( ( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) && - ( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_CANCEL_TIMEOUT ) ) - { - if( pUpdateInfo->stepTimeoutInMinutes < 1 ) - { - IotLogError( "Step timeout for Jobs %s must be at least 1.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - else if( pUpdateInfo->stepTimeoutInMinutes > JOBS_MAX_TIMEOUT ) - { - IotLogError( "Step timeout for Jobs %s cannot exceed %d.", - _pAwsIotJobsOperationNames[ type ], - JOBS_MAX_TIMEOUT ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - /* Check status details. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) - { - if( pUpdateInfo->statusDetailsLength == 0 ) - { - IotLogError( "Status details length not set for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( pUpdateInfo->statusDetailsLength > JOBS_MAX_STATUS_DETAILS_LENGTH ) - { - IotLogError( "Status details length for Jobs %s cannot exceed %d.", - _pAwsIotJobsOperationNames[ type ], - JOBS_MAX_STATUS_DETAILS_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -static int32_t _getCallbackIndex( _jobsCallbackType_t type, - _jobsSubscription_t * pSubscription, - const AwsIotJobsCallbackInfo_t * pCallbackInfo ) -{ - int32_t callbackIndex = 0; - - /* Find the matching oldFunction. */ - if( pCallbackInfo->oldFunction != NULL ) - { - for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) - { - if( pSubscription->callbacks[ type ][ callbackIndex ].function == pCallbackInfo->oldFunction ) - { - /* oldFunction found. */ - break; - } - } - - if( callbackIndex == AWS_IOT_JOBS_NOTIFY_CALLBACKS ) - { - /* oldFunction not found. */ - callbackIndex = OLD_CALLBACK_NOT_FOUND; - } - } - /* Find space for a new callback. */ - else - { - for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) - { - if( pSubscription->callbacks[ type ][ callbackIndex ].function == NULL ) - { - break; - } - } - - if( callbackIndex == AWS_IOT_JOBS_NOTIFY_CALLBACKS ) - { - /* No memory for new callback. */ - callbackIndex = NO_SPACE_FOR_CALLBACK; - } - } - - return callbackIndex; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, - _jobsCallbackType_t type, - const char * pThingName, - size_t thingNameLength, - const AwsIotJobsCallbackInfo_t * pCallbackInfo ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - bool subscriptionMutexLocked = false; - _jobsSubscription_t * pSubscription = NULL; - int32_t callbackIndex = 0; - - /* Check that AwsIotJobs_Init was called. */ - if( _checkInit() == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); - } - - /* Validate Thing Name. */ - if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == false ) - { - IotLogError( "Thing Name for Jobs %s callback is not valid.", - _pAwsIotJobsCallbackNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* Check that a callback parameter is provided. */ - if( pCallbackInfo == NULL ) - { - IotLogError( "Callback parameter must be provided for Jobs %s callback.", - _pAwsIotJobsCallbackNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* The oldFunction member must be set when removing or replacing a callback. */ - if( pCallbackInfo->function == NULL ) - { - if( pCallbackInfo->oldFunction == NULL ) - { - IotLogError( "Both oldFunction and function pointers cannot be NULL for Jobs %s callback.", - _pAwsIotJobsCallbackNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - IotLogInfo( "(%.*s) Modifying Jobs %s callback.", - thingNameLength, - pThingName, - _pAwsIotJobsCallbackNames[ type ] ); - - /* Lock the subscription list mutex to check for an existing subscription - * object. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - subscriptionMutexLocked = true; - - /* Check for an existing subscription. This function will attempt to allocate - * a new subscription if not found. */ - pSubscription = _AwsIotJobs_FindSubscription( pThingName, thingNameLength, true ); - - if( pSubscription == NULL ) - { - /* No existing subscription was found, and no new subscription could be - * allocated. */ - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); - } - - /* Get the index of the callback element to modify. */ - callbackIndex = _getCallbackIndex( type, pSubscription, pCallbackInfo ); - - switch( callbackIndex ) - { - case NO_SPACE_FOR_CALLBACK: - IotLogError( "No memory for a new Jobs %s callback.", - _pAwsIotJobsCallbackNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); - - case OLD_CALLBACK_NOT_FOUND: - IotLogWarn( "Requested replacement function for Jobs %s callback not found.", - _pAwsIotJobsCallbackNames[ type ] ); - - /* A subscription may have been allocated, but the callback operation can't - * proceed. Check if the subscription should be removed. */ - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - - default: - break; - } - - /* Check for an existing callback. */ - if( pSubscription->callbacks[ type ][ callbackIndex ].function != NULL ) - { - /* Replace existing callback. */ - if( pCallbackInfo->function != NULL ) - { - IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", - thingNameLength, - pThingName, - _pAwsIotJobsCallbackNames[ type ] ); - - pSubscription->callbacks[ type ][ callbackIndex ] = *pCallbackInfo; - } - /* Remove existing callback. */ - else - { - IotLogInfo( "(%.*s) Removing existing %s callback.", - thingNameLength, - pThingName, - _pAwsIotJobsCallbackNames[ type ] ); - - /* Clear the callback information and unsubscribe. */ - ( void ) memset( &( pSubscription->callbacks[ type ][ callbackIndex ] ), - 0x00, - sizeof( AwsIotJobsCallbackInfo_t ) ); - ( void ) _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_UnsubscribeSync ); - - /* Check if this subscription object can be removed. */ - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - } - } - /* No existing callback. */ - else - { - /* Add new callback. */ - IotLogInfo( "(%.*s) Adding new %s callback.", - thingNameLength, - pThingName, - _pAwsIotJobsCallbackNames[ type ] ); - - status = _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_SubscribeSync ); - - if( status == AWS_IOT_JOBS_SUCCESS ) - { - pSubscription->callbacks[ type ][ callbackIndex ] = *pCallbackInfo; - } - else - { - /* On failure, check if this subscription can be removed. */ - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - } - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( subscriptionMutexLocked == true ) - { - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - } - - IotLogInfo( "(%.*s) Jobs %s callback operation complete with result %s.", - thingNameLength, - pThingName, - _pAwsIotJobsCallbackNames[ type ], - AwsIotJobs_strerror( status ) ); - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, - _jobsCallbackType_t type, - _jobsSubscription_t * pSubscription, - AwsIotMqttFunction_t mqttOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - int32_t i = 0; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - AwsIotTopicInfo_t topicInfo = { 0 }; - char * pTopicFilter = NULL; - uint16_t topicFilterLength = 0; - - /* Lookup table for Jobs callback suffixes. */ - const char * const pCallbackSuffix[ JOBS_CALLBACK_COUNT ] = - { - JOBS_NOTIFY_PENDING_CALLBACK_STRING, /* Notify pending callback. */ - JOBS_NOTIFY_NEXT_CALLBACK_STRING /* Notify next callback. */ - }; - - /* Lookup table for Jobs callback suffix lengths. */ - const uint16_t pCallbackSuffixLength[ JOBS_CALLBACK_COUNT ] = - { - JOBS_NOTIFY_PENDING_CALLBACK_STRING_LENGTH, /* Notify pending callback. */ - JOBS_NOTIFY_NEXT_CALLBACK_STRING_LENGTH /* Notify next callback. */ - }; - - /* Lookup table for Jobs callback function wrappers. */ - const AwsIotMqttCallbackFunction_t pCallbackWrapper[ JOBS_CALLBACK_COUNT ] = - { - _notifyPendingCallbackWrapper, /* Notify pending callback. */ - _notifyNextCallbackWrapper, /* Notify next callback. */ - }; - - /* MQTT operation may only be subscribe or unsubscribe. */ - AwsIotJobs_Assert( ( mqttOperation == IotMqtt_SubscribeSync ) || - ( mqttOperation == IotMqtt_UnsubscribeSync ) ); - - /* Check if any subscriptions are currently registered for this type. */ - for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) - { - if( pSubscription->callbacks[ type ][ i ].function != NULL ) - { - /* No action is needed when another callback exists. */ - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_SUCCESS ); - } - } - - /* Use the subscription's topic buffer if available. */ - if( pSubscription->pTopicBuffer != NULL ) - { - pTopicFilter = pSubscription->pTopicBuffer; - } - - /* Generate the topic for the MQTT subscription. */ - topicInfo.pThingName = pSubscription->pThingName; - topicInfo.thingNameLength = pSubscription->thingNameLength; - topicInfo.longestSuffixLength = JOBS_LONGEST_SUFFIX_LENGTH; - topicInfo.mallocString = AwsIotJobs_MallocString; - topicInfo.pOperationName = pCallbackSuffix[ type ]; - topicInfo.operationNameLength = pCallbackSuffixLength[ type ]; - - if( AwsIot_GenerateOperationTopic( &topicInfo, - &pTopicFilter, - &topicFilterLength ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); - } - - IotLogDebug( "%s subscription for %.*s", - mqttOperation == IotMqtt_SubscribeSync ? "Adding" : "Removing", - topicFilterLength, - pTopicFilter ); - - /* Set the members of the MQTT subscription. */ - subscription.qos = IOT_MQTT_QOS_1; - subscription.pTopicFilter = pTopicFilter; - subscription.topicFilterLength = topicFilterLength; - subscription.callback.pCallbackContext = NULL; - subscription.callback.function = pCallbackWrapper[ type ]; - - /* Call the MQTT operation function. */ - mqttStatus = mqttOperation( mqttConnection, - &subscription, - 1, - 0, - _AwsIotJobsMqttTimeoutMs ); - - /* Check the result of the MQTT operation. */ - if( mqttStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == IotMqtt_SubscribeSync ? "subscribe to" : "unsubscribe from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotJobsCallbackNames[ type ], - IotMqtt_strerror( mqttStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Jobs "NO MEMORY" error. */ - if( mqttStatus == IOT_MQTT_NO_MEMORY ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); - } - else - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_MQTT_ERROR ); - } - } - - IotLogDebug( "Successfully %s %.*s Jobs %s callback.", - mqttOperation == IotMqtt_SubscribeSync ? "subscribed to" : "unsubscribed from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotJobsCallbackNames[ type ] ); - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* MQTT subscribe should check the subscription topic buffer. */ - if( mqttOperation == IotMqtt_SubscribeSync ) - { - /* If the current subscription has no topic buffer, assign it the current - * topic buffer. */ - if( pSubscription->pTopicBuffer == NULL ) - { - pSubscription->pTopicBuffer = pTopicFilter; - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -static void _notifyPendingCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - _callbackWrapperCommon( NOTIFY_PENDING_CALLBACK, - pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _notifyNextCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - _callbackWrapperCommon( NOTIFY_NEXT_CALLBACK, - pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _callbackWrapperCommon( _jobsCallbackType_t type, - IotMqttCallbackParam_t * pMessage ) -{ - int32_t callbackIndex = 0; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - AwsIotJobsCallbackParam_t callbackParam = { .callbackType = ( AwsIotJobsCallbackType_t ) 0 }; - _jobsSubscription_t * pSubscription = NULL; - const char * pThingName = NULL; - size_t thingNameLength = 0; - - /* Parse the Thing Name from the topic. */ - if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength, - &pThingName, - &thingNameLength ) == false ) - { - IOT_GOTO_CLEANUP(); - } - - /* Search for a matching subscription. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - - pSubscription = _AwsIotJobs_FindSubscription( pThingName, - thingNameLength, - false ); - - if( pSubscription != NULL ) - { - /* Increment callback reference count to prevent the subscription object from being - * freed while callbacks are running. */ - pSubscription->callbackReferences++; - } - - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - - if( pSubscription != NULL ) - { - /* Invoke all callbacks for this Thing. */ - for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) - { - /* Copy the callback function and parameter, as it may be modified at any time. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - callbackInfo = pSubscription->callbacks[ type ][ callbackIndex ]; - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - - if( callbackInfo.function != NULL ) - { - /* Set the callback type. Jobs callbacks are enumerated after the operations. */ - callbackParam.callbackType = ( AwsIotJobsCallbackType_t ) ( type + JOBS_OPERATION_COUNT ); - - /* Set the remaining members of the callback param. */ - callbackParam.mqttConnection = pMessage->mqttConnection; - callbackParam.pThingName = pThingName; - callbackParam.thingNameLength = thingNameLength; - callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; - callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; - - /* Invoke the callback function. */ - callbackInfo.function( callbackInfo.pCallbackContext, - &callbackParam ); - } - } - - /* Callbacks are finished. Decrement reference count and check if subscription - * object should be destroyed. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - - pSubscription->callbackReferences--; - AwsIotJobs_Assert( pSubscription->callbackReferences >= 0 ); - - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - } - - /* This function uses cleanup sections to exit on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - bool listInitStatus = false; - - if( _initCalled == 0 ) - { - listInitStatus = AwsIot_InitLists( &_AwsIotJobsPendingOperations, - &_AwsIotJobsSubscriptions, - &_AwsIotJobsPendingOperationsMutex, - &_AwsIotJobsSubscriptionsMutex ); - - if( listInitStatus == false ) - { - IotLogInfo( "Failed to create Jobs lists." ); - - status = AWS_IOT_JOBS_INIT_FAILED; - } - else - { - /* Save the MQTT timeout option. */ - if( mqttTimeoutMs != 0 ) - { - _AwsIotJobsMqttTimeoutMs = mqttTimeoutMs; - } - - /* Set the flag that specifies initialization is complete. */ - _initCalled = 1; - - IotLogInfo( "Jobs library successfully initialized." ); - } - } - else - { - IotLogWarn( "AwsIotJobs_Init called with library already initialized." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void AwsIotJobs_Cleanup( void ) -{ - if( _initCalled == 1 ) - { - _initCalled = 0; - - /* Remove and free all items in the Jobs pending operation list. */ - IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); - IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, - _AwsIotJobs_DestroyOperation, - offsetof( _jobsOperation_t, link ) ); - IotMutex_Unlock( &_AwsIotJobsPendingOperationsMutex ); - - /* Remove and free all items in the Jobs subscription list. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - IotListDouble_RemoveAll( &_AwsIotJobsSubscriptions, - _AwsIotJobs_DestroySubscription, - offsetof( _jobsSubscription_t, link ) ); - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - - /* Restore the default MQTT timeout. */ - _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; - - /* Destroy Jobs library mutexes. */ - IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); - IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); - - IotLogInfo( "Jobs library cleanup done." ); - } - else - { - IotLogWarn( "AwsIotJobs_Init was not called before AwsIotShadow_Cleanup." ); - } -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_GetPendingAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pGetPendingOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - _jobsOperation_t * pOperation = NULL; - - /* Check that AwsIotJobs_Init was called. */ - if( _checkInit() == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); - } - - /* Check request info. */ - status = _validateRequestInfo( JOBS_GET_PENDING, - pRequestInfo, - flags, - pCallbackInfo, - pGetPendingOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Allocate a new Jobs operation. */ - status = _AwsIotJobs_CreateOperation( JOBS_GET_PENDING, - pRequestInfo, - NULL, - flags, - pCallbackInfo, - &pOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - /* No memory for Jobs operation. */ - IOT_GOTO_CLEANUP(); - } - - /* Set the reference if provided. This must be done before the Jobs operation - * is processed. */ - if( pGetPendingOperation != NULL ) - { - *pGetPendingOperation = pOperation; - } - - /* Process the Jobs operation. This subscribes to any required topics and - * sends the MQTT message for the Jobs operation. */ - status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); - - /* If the Jobs operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pGetPendingOperation != NULL ) ) - { - *pGetPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_GetPendingSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_JOBS_FLAG_WAITABLE; - - /* Call the asynchronous Jobs Get Pending function. */ - status = AwsIotJobs_GetPendingAsync( pRequestInfo, - flags, - NULL, - &getPendingOperation ); - - /* Wait for the Jobs Get Pending operation to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - status = AwsIotJobs_Wait( getPendingOperation, - timeoutMs, - pJobsResponse ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_StartNextAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pStartNextOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - _jobsOperation_t * pOperation = NULL; - _jsonRequestContents_t requestContents = { 0 }; - - /* Check that AwsIotJobs_Init was called. */ - if( _checkInit() == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); - } - - /* Check request info. */ - status = _validateRequestInfo( JOBS_START_NEXT, - pRequestInfo, - flags, - pCallbackInfo, - pStartNextOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Check update info. */ - status = _validateUpdateInfo( JOBS_START_NEXT, - pUpdateInfo ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Allocate a new Jobs operation. */ - requestContents.pUpdateInfo = pUpdateInfo; - - status = _AwsIotJobs_CreateOperation( JOBS_START_NEXT, - pRequestInfo, - &requestContents, - flags, - pCallbackInfo, - &pOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - /* No memory for Jobs operation. */ - IOT_GOTO_CLEANUP(); - } - - /* Set the reference if provided. This must be done before the Jobs operation - * is processed. */ - if( pStartNextOperation != NULL ) - { - *pStartNextOperation = pOperation; - } - - /* Process the Jobs operation. This subscribes to any required topics and - * sends the MQTT message for the Jobs operation. */ - status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); - - /* If the Jobs operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pStartNextOperation != NULL ) ) - { - *pStartNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_StartNextSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_JOBS_FLAG_WAITABLE; - - /* Call the asynchronous Jobs Start Next function. */ - status = AwsIotJobs_StartNextAsync( pRequestInfo, - pUpdateInfo, - flags, - NULL, - &startNextOperation ); - - /* Wait for the Jobs Start Next operation to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - status = AwsIotJobs_Wait( startNextOperation, - timeoutMs, - pJobsResponse ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_DescribeAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pDescribeOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - _jobsOperation_t * pOperation = NULL; - _jsonRequestContents_t requestContents = { 0 }; - - /* Check that AwsIotJobs_Init was called. */ - if( _checkInit() == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); - } - - /* Check request info. */ - status = _validateRequestInfo( JOBS_DESCRIBE, - pRequestInfo, - flags, - pCallbackInfo, - pDescribeOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Allocate a new Jobs operation. */ - requestContents.describe.executionNumber = executionNumber; - requestContents.describe.includeJobDocument = includeJobDocument; - - status = _AwsIotJobs_CreateOperation( JOBS_DESCRIBE, - pRequestInfo, - &requestContents, - flags, - pCallbackInfo, - &pOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - /* No memory for Jobs operation. */ - IOT_GOTO_CLEANUP(); - } - - /* Set the reference if provided. This must be done before the Jobs operation - * is processed. */ - if( pDescribeOperation != NULL ) - { - *pDescribeOperation = pOperation; - } - - /* Process the Jobs operation. This subscribes to any required topics and - * sends the MQTT message for the Jobs operation. */ - status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); - - /* If the Jobs operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pDescribeOperation != NULL ) ) - { - *pDescribeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_DescribeSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t describeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_JOBS_FLAG_WAITABLE; - - /* Call the asynchronous Jobs Describe function. */ - status = AwsIotJobs_DescribeAsync( pRequestInfo, - executionNumber, - includeJobDocument, - flags, - NULL, - &describeOperation ); - - /* Wait for the Jobs Describe operation to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - status = AwsIotJobs_Wait( describeOperation, - timeoutMs, - pJobsResponse ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_UpdateAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pUpdateOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - _jobsOperation_t * pOperation = NULL; - _jsonRequestContents_t requestContents = { 0 }; - - /* Check that AwsIotJobs_Init was called. */ - if( _checkInit() == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); - } - - /* Check request info. */ - status = _validateRequestInfo( JOBS_UPDATE, - pRequestInfo, - flags, - pCallbackInfo, - pUpdateOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Check update info. */ - status = _validateUpdateInfo( JOBS_UPDATE, - pUpdateInfo ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Allocate a new Jobs operation. */ - requestContents.pUpdateInfo = pUpdateInfo; - - status = _AwsIotJobs_CreateOperation( JOBS_UPDATE, - pRequestInfo, - &requestContents, - flags, - pCallbackInfo, - &pOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - /* No memory for Jobs operation. */ - IOT_GOTO_CLEANUP(); - } - - /* Set the reference if provided. This must be done before the Jobs operation - * is processed. */ - if( pUpdateOperation != NULL ) - { - *pUpdateOperation = pOperation; - } - - /* Process the Jobs operation. This subscribes to any required topics and - * sends the MQTT message for the Jobs operation. */ - status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); - - /* If the Jobs operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pUpdateOperation != NULL ) ) - { - *pUpdateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_UpdateSync( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_JOBS_FLAG_WAITABLE; - - /* Call the asynchronous Jobs Update function. */ - status = AwsIotJobs_UpdateAsync( pRequestInfo, - pUpdateInfo, - flags, - NULL, - &updateOperation ); - - /* Wait for the Jobs Update operation to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - status = AwsIotJobs_Wait( updateOperation, - timeoutMs, - pJobsResponse ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - - /* Check that AwsIotJobs_Init was called. */ - if( _checkInit() == false ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); - } - - /* Check that reference is set. */ - if( operation == NULL ) - { - IotLogError( "Operation reference cannot be NULL." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* Check that reference is waitable. */ - if( ( operation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 ) - { - IotLogError( "Operation is not waitable." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* Check that output parameter is set. */ - if( pJobsResponse == NULL ) - { - IotLogError( "Output Jobs response parameter must be set for Jobs %s.", - _pAwsIotJobsOperationNames[ operation->type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - /* Wait for a response to the Jobs operation. */ - if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), - timeoutMs ) == true ) - { - status = operation->status; - } - else - { - status = AWS_IOT_JOBS_TIMEOUT; - } - - /* Remove the completed operation from the pending operation list. */ - IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); - IotListDouble_Remove( &( operation->link ) ); - IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); - - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - _AwsIotJobs_DecrementReferences( operation, - operation->pSubscription->pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - - /* Set the output parameters. Jobs responses are available on success or - * when the Jobs service returns an error document. */ - if( ( status == AWS_IOT_JOBS_SUCCESS ) || ( status > AWS_IOT_JOBS_INVALID_TOPIC ) ) - { - AwsIotJobs_Assert( operation->pJobsResponse != NULL ); - AwsIotJobs_Assert( operation->jobsResponseLength > 0 ); - - pJobsResponse->pJobsResponse = operation->pJobsResponse; - pJobsResponse->jobsResponseLength = operation->jobsResponseLength; - } - - /* Destroy the Jobs operation. */ - _AwsIotJobs_DestroyOperation( operation ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pNotifyPendingCallback ) -{ - /* Flags are not currently used by this function. */ - ( void ) flags; - - return _setCallbackCommon( mqttConnection, - NOTIFY_PENDING_CALLBACK, - pThingName, - thingNameLength, - pNotifyPendingCallback ); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pNotifyNextCallback ) -{ - /* Flags are not currently used by this function. */ - ( void ) flags; - - return _setCallbackCommon( mqttConnection, - NOTIFY_NEXT_CALLBACK, - pThingName, - thingNameLength, - pNotifyNextCallback ); -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotJobs_strerror( AwsIotJobsError_t status ) -{ - const char * pMessage = NULL; - - switch( status ) - { - case AWS_IOT_JOBS_SUCCESS: - pMessage = "SUCCESS"; - break; - - case AWS_IOT_JOBS_STATUS_PENDING: - pMessage = "STATUS PENDING"; - break; - - case AWS_IOT_JOBS_INIT_FAILED: - pMessage = "INIT FAILED"; - break; - - case AWS_IOT_JOBS_BAD_PARAMETER: - pMessage = "BAD PARAMETER"; - break; - - case AWS_IOT_JOBS_NO_MEMORY: - pMessage = "NO MEMORY"; - break; - - case AWS_IOT_JOBS_MQTT_ERROR: - pMessage = "MQTT ERROR"; - break; - - case AWS_IOT_JOBS_BAD_RESPONSE: - pMessage = "BAD RESPONSE"; - break; - - case AWS_IOT_JOBS_TIMEOUT: - pMessage = "TIMEOUT"; - break; - - case AWS_IOT_JOBS_NOT_INITIALIZED: - pMessage = "NOT INITIALIZED"; - break; - - case AWS_IOT_JOBS_INVALID_TOPIC: - pMessage = "FAILED: INVALID TOPIC"; - break; - - case AWS_IOT_JOBS_INVALID_JSON: - pMessage = "FAILED: INVALID JSON"; - break; - - case AWS_IOT_JOBS_INVALID_REQUEST: - pMessage = "FAILED: INVALID REQUEST"; - break; - - case AWS_IOT_JOBS_INVALID_STATE: - pMessage = "FAILED: INVALID STATE TRANSITION"; - break; - - case AWS_IOT_JOBS_NOT_FOUND: - pMessage = "FAILED: NOT FOUND"; - break; - - case AWS_IOT_JOBS_VERSION_MISMATCH: - pMessage = "FAILED: VERSION MISMATCH"; - break; - - case AWS_IOT_JOBS_INTERNAL_ERROR: - pMessage = "FAILED: INTERNAL ERROR"; - break; - - case AWS_IOT_JOBS_THROTTLED: - pMessage = "FAILED: THROTTLED"; - break; - - case AWS_IOT_JOBS_TERMINAL_STATE: - pMessage = "FAILED: TERMINAL STATE"; - break; - - default: - pMessage = "INVALID STATUS"; - break; - } - - return pMessage; -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotJobs_StateName( AwsIotJobState_t state ) -{ - const char * pMessage = NULL; - - switch( state ) - { - case AWS_IOT_JOB_STATE_QUEUED: - pMessage = "QUEUED"; - break; - - case AWS_IOT_JOB_STATE_IN_PROGRESS: - pMessage = "IN PROGRESS"; - break; - - case AWS_IOT_JOB_STATE_FAILED: - pMessage = "FAILED"; - break; - - case AWS_IOT_JOB_STATE_SUCCEEDED: - pMessage = "SUCCEEDED"; - break; - - case AWS_IOT_JOB_STATE_CANCELED: - pMessage = "CANCELED"; - break; - - case AWS_IOT_JOB_STATE_TIMED_OUT: - pMessage = "TIMED OUT"; - break; - - case AWS_IOT_JOB_STATE_REJECTED: - pMessage = "REJECTED"; - break; - - case AWS_IOT_JOB_STATE_REMOVED: - pMessage = "REMOVED"; - break; - - default: - pMessage = "INVALID STATE"; - break; - } - - return pMessage; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/src/aws_iot_jobs_operation.c b/libraries/aws/jobs/src/aws_iot_jobs_operation.c deleted file mode 100644 index a3142f4aeb..0000000000 --- a/libraries/aws/jobs/src/aws_iot_jobs_operation.c +++ /dev/null @@ -1,885 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_operation.c - * @brief Implements functions that process Jobs operations. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/*-----------------------------------------------------------*/ - -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - -/** - * @brief Printable names for each of the Jobs operations. - */ - const char * const _pAwsIotJobsOperationNames[] = - { - "GET PENDING", - "START NEXT", - "DESCRIBE", - "UPDATE", - "SET NOTIFY-PENDING", - "SET NOTIFY-NEXT" - }; -#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - -/*-----------------------------------------------------------*/ - -/** - * @brief First parameter to #_jobsOperation_match. - */ -typedef struct _operationMatchParams -{ - _jobsOperationType_t type; /**< @brief GET PENDING, START NEXT, DESCRIBE, or UPDATE. */ - const char * pThingName; /**< @brief Thing Name of Jobs operation. */ - size_t thingNameLength; /**< @brief Length of #_operationMatchParams_t.pThingName. */ - const char * pResponse; /**< @brief Jobs response document. */ - size_t responseLength; /**< @brief Length of #_operationMatchParams_t.pResponse. */ -} _operationMatchParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Match a received Jobs response with a Jobs operation awaiting a - * response. - * - * @param[in] pOperationLink Pointer to the link member of the #_jobsOperation_t - * to check. - * @param[in] pMatch Pointer to an #_operationMatchParams_t. - * - * @return `true` if `pMatch` matches the received response; `false` otherwise. - */ -static bool _jobsOperation_match( const IotLink_t * pOperationLink, - void * pMatch ); - -/** - * @brief Invoked when a Jobs response is received for Jobs GET PENDING. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). - */ -static void _getPendingCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a Jobs response is received for a Jobs START NEXT. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). - */ -static void _startNextCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a Jobs response is received for Jobs DESCRIBE. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). - */ -static void _describeCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a Jobs response is received for a Jobs UPDATE. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). - */ -static void _updateCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Common function for processing received Jobs responses. - * - * @param[in] type GET PENDING, START NEXT, DESCRIBE, or UPDATE. - * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). - */ -static void _commonOperationCallback( _jobsOperationType_t type, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Notify of a completed Jobs operation. - * - * @param[in] pOperation The operation which completed. - * - * Depending on the parameters passed to a user-facing Jobs function, the - * notification will cause @ref jobs_function_wait to return or invoke a - * user-provided callback. - */ -static void _notifyCompletion( _jobsOperation_t * pOperation ); - -/** - * @brief Get a Jobs subscription to use with a Jobs operation. - * - * This function may use an existing Jobs subscription, or it may allocate a - * new one. - * - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pTopicBuffer Contains the topic to use for subscribing. - * @param[in] operationTopicLength The length of the base topic in `pTopicBuffer`. - * @param[in] pOperation Jobs operation that needs a subscription. - * @param[out] pFreeTopicBuffer Whether the caller may free `pTopicBuffer` - * (which may be assigned to a subscription). - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY - */ -static AwsIotJobsError_t _findSubscription( const AwsIotJobsRequestInfo_t * pRequestInfo, - char * pTopicBuffer, - uint16_t operationTopicLength, - _jobsOperation_t * pOperation, - bool * pFreeTopicBuffer ); - -/*-----------------------------------------------------------*/ - -/** - * @brief List of active Jobs operations awaiting a response from the Jobs - * service. - */ -IotListDouble_t _AwsIotJobsPendingOperations = { 0 }; - -/** - * @brief Protects #_AwsIotJobsPendingOperations from concurrent access. - */ -IotMutex_t _AwsIotJobsPendingOperationsMutex; - -/*-----------------------------------------------------------*/ - -static bool _jobsOperation_match( const IotLink_t * pOperationLink, - void * pMatch ) -{ - /* Because this function is called from a container function, the given link - * must never be NULL. */ - AwsIotJobs_Assert( pOperationLink != NULL ); - - _jobsOperation_t * pOperation = IotLink_Container( _jobsOperation_t, - pOperationLink, - link ); - _operationMatchParams_t * pParam = ( _operationMatchParams_t * ) pMatch; - _jobsSubscription_t * pSubscription = pOperation->pSubscription; - const char * pClientToken = NULL; - size_t clientTokenLength = 0; - - /* Check for matching Thing Name and operation type. */ - bool match = ( pOperation->type == pParam->type ) && - ( pParam->thingNameLength == pSubscription->thingNameLength ) && - ( strncmp( pParam->pThingName, - pSubscription->pThingName, - pParam->thingNameLength ) == 0 ); - - if( match == true ) - { - IotLogDebug( "Verifying client tokens for Jobs %s.", - _pAwsIotJobsOperationNames[ pParam->type ] ); - - /* Check the response for a client token. */ - match = AwsIot_GetClientToken( pParam->pResponse, - pParam->responseLength, - &pClientToken, - &clientTokenLength ); - - /* If the response contains a client token, check for a match. */ - if( match == true ) - { - match = ( pOperation->clientTokenLength == clientTokenLength ) && - ( strncmp( pOperation->pClientToken, pClientToken, clientTokenLength ) == 0 ); - } - else - { - IotLogWarn( "Received a Jobs %s response with no client token. " - "This is possibly a response to a bad JSON document:\r\n%.*s", - _pAwsIotJobsOperationNames[ pParam->type ], - pParam->responseLength, - pParam->pResponse ); - } - } - - return match; -} - -/*-----------------------------------------------------------*/ - -static void _getPendingCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( JOBS_GET_PENDING, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _startNextCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( JOBS_START_NEXT, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _describeCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( JOBS_DESCRIBE, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _updateCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( JOBS_UPDATE, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _commonOperationCallback( _jobsOperationType_t type, - IotMqttCallbackParam_t * pMessage ) -{ - _jobsOperation_t * pOperation = NULL; - IotLink_t * pOperationLink = NULL; - _operationMatchParams_t param = { 0 }; - AwsIotStatus_t status = AWS_IOT_UNKNOWN; - uint32_t flags = 0; - - /* Set operation type and response. */ - param.type = type; - param.pResponse = pMessage->u.message.info.pPayload; - param.responseLength = pMessage->u.message.info.payloadLength; - - /* Parse the Thing Name from the MQTT topic name. */ - if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength, - &( param.pThingName ), - &( param.thingNameLength ) ) == false ) - { - IOT_GOTO_CLEANUP(); - } - - /* Lock the pending operations list for exclusive access. */ - IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); - - /* Search for a matching pending operation. */ - pOperationLink = IotListDouble_FindFirstMatch( &_AwsIotJobsPendingOperations, - NULL, - _jobsOperation_match, - ¶m ); - - /* Find and remove the first Jobs operation of the given type. */ - if( pOperationLink == NULL ) - { - /* Operation is not pending. It may have already been processed. Return - * without doing anything */ - IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); - - IotLogWarn( "Jobs %s callback received an unknown operation.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_GOTO_CLEANUP(); - } - else - { - pOperation = IotLink_Container( _jobsOperation_t, pOperationLink, link ); - - /* Copy the flags from the Jobs operation. */ - flags = pOperation->flags; - - /* Remove a non-waitable operation from the pending operation list. - * Waitable operations are removed by the Wait function. */ - if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 ) - { - IotListDouble_Remove( &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); - } - } - - /* Parse the status from the topic name. */ - status = AwsIot_ParseStatus( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength ); - - switch( status ) - { - case AWS_IOT_ACCEPTED: - case AWS_IOT_REJECTED: - _AwsIotJobs_ParseResponse( status, - pMessage->u.message.info.pPayload, - pMessage->u.message.info.payloadLength, - pOperation ); - break; - - default: - IotLogWarn( "Unknown status for %s of %.*s Jobs. Ignoring message.", - _pAwsIotJobsOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); - - pOperation->status = AWS_IOT_JOBS_BAD_RESPONSE; - - break; - } - - _notifyCompletion( pOperation ); - - /* For waitable operations, unlock the pending operation list mutex to allow - * the Wait function to run. */ - if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) - { - IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); - } - - /* This function has no return value and no cleanup, but uses the cleanup - * label to exit on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); -} - -/*-----------------------------------------------------------*/ - -static void _notifyCompletion( _jobsOperation_t * pOperation ) -{ - AwsIotJobsCallbackParam_t callbackParam = { .callbackType = ( AwsIotJobsCallbackType_t ) 0 }; - _jobsSubscription_t * pSubscription = pOperation->pSubscription, - * pRemovedSubscription = NULL; - - /* If the operation is waiting, post to its wait semaphore and return. */ - if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) - { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - } - else - { - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - _AwsIotJobs_DecrementReferences( pOperation, - pSubscription->pTopicBuffer, - &pRemovedSubscription ); - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - - /* Set the subscription pointer used for the user callback based on whether - * a subscription was removed from the list. */ - if( pRemovedSubscription != NULL ) - { - pSubscription = pRemovedSubscription; - } - - AwsIotJobs_Assert( pSubscription != NULL ); - - /* Invoke the user callback if provided. */ - if( pOperation->notify.callback.function != NULL ) - { - /* Set the common members of the callback parameter. */ - callbackParam.callbackType = ( AwsIotJobsCallbackType_t ) pOperation->type; - callbackParam.mqttConnection = pOperation->mqttConnection; - callbackParam.pThingName = pSubscription->pThingName; - callbackParam.thingNameLength = pSubscription->thingNameLength; - callbackParam.u.operation.result = pOperation->status; - callbackParam.u.operation.reference = pOperation; - callbackParam.u.operation.pResponse = pOperation->pJobsResponse; - callbackParam.u.operation.responseLength = pOperation->jobsResponseLength; - - pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, - &callbackParam ); - } - - /* Destroy a removed subscription. */ - if( pRemovedSubscription != NULL ) - { - _AwsIotJobs_DestroySubscription( pRemovedSubscription ); - } - - _AwsIotJobs_DestroyOperation( pOperation ); - } -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _findSubscription( const AwsIotJobsRequestInfo_t * pRequestInfo, - char * pTopicBuffer, - uint16_t operationTopicLength, - _jobsOperation_t * pOperation, - bool * pFreeTopicBuffer ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - _jobsSubscription_t * pSubscription = NULL; - - /* Lookup table for Jobs operation callbacks. */ - const AwsIotMqttCallbackFunction_t jobsCallbacks[ JOBS_OPERATION_COUNT ] = - { - _getPendingCallback, - _startNextCallback, - _describeCallback, - _updateCallback - }; - - /* Lock the subscriptions mutex for exclusive access. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - - /* Check for an existing subscription. This function will attempt to allocate - * a new subscription if not found. */ - pSubscription = _AwsIotJobs_FindSubscription( pRequestInfo->pThingName, - pRequestInfo->thingNameLength, - true ); - - if( pSubscription == NULL ) - { - status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - /* Ensure that the subscription Thing Name matches. */ - AwsIotJobs_Assert( pSubscription != NULL ); - AwsIotJobs_Assert( pSubscription->thingNameLength == pRequestInfo->thingNameLength ); - AwsIotJobs_Assert( strncmp( pSubscription->pThingName, - pRequestInfo->pThingName, - pRequestInfo->thingNameLength ) == 0 ); - - /* Set the subscription object for the Jobs operation. */ - pOperation->pSubscription = pSubscription; - - /* Assign the topic buffer to the subscription to use for unsubscribing if - * the subscription has no topic buffer. */ - if( pSubscription->pTopicBuffer == NULL ) - { - pSubscription->pTopicBuffer = pTopicBuffer; - - /* Don't free the topic buffer if it was allocated to the subscription. */ - *pFreeTopicBuffer = false; - } - else - { - *pFreeTopicBuffer = true; - } - - /* Increment the reference count for this Jobs operation's - * subscriptions. */ - status = _AwsIotJobs_IncrementReferences( pOperation, - pTopicBuffer, - operationTopicLength, - jobsCallbacks[ pOperation->type ] ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - /* Failed to add subscriptions for a Jobs operation. The reference - * count was not incremented. Check if this subscription should be - * deleted. */ - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - } - } - - /* Unlock the Jobs subscription list mutex. */ - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - const _jsonRequestContents_t * pRequestContents, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - _jobsOperation_t ** pNewOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - size_t operationSize = sizeof( _jobsOperation_t ); - _jobsOperation_t * pOperation = NULL; - - IotLogDebug( "Creating operation record for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - /* The Job ID must be saved for DESCRIBE and UPDATE operations. */ - if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) - { - /* Ensure a valid Job ID is provided. */ - AwsIotJobs_Assert( pRequestInfo->pJobId != NULL ); - AwsIotJobs_Assert( pRequestInfo->jobIdLength > 1 ); - AwsIotJobs_Assert( pRequestInfo->jobIdLength <= JOBS_MAX_ID_LENGTH ); - - operationSize += pRequestInfo->jobIdLength; - } - - /* Allocate memory for a new Jobs operation. */ - pOperation = AwsIotJobs_MallocOperation( operationSize ); - - if( pOperation == NULL ) - { - IotLogError( "Failed to allocate memory for Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); - } - - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _jobsOperation_t ) ); - - /* Set the remaining common members of the Jobs operation. */ - pOperation->type = type; - pOperation->flags = flags; - pOperation->status = AWS_IOT_JOBS_STATUS_PENDING; - pOperation->mallocResponse = pRequestInfo->mallocResponse; - - /* Save the Job ID for DESCRIBE and UPDATE operations. */ - if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) - { - pOperation->jobIdLength = pRequestInfo->jobIdLength; - - ( void ) memcpy( pOperation->pJobId, pRequestInfo->pJobId, pRequestInfo->jobIdLength ); - } - - /* Generate a Jobs request document. */ - status = _AwsIotJobs_GenerateJsonRequest( type, - pRequestInfo, - pRequestContents, - pOperation ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) - { - if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) - { - IotLogError( "Failed to create semaphore for waitable Jobs %s.", - _pAwsIotJobsOperationNames[ type ] ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); - } - } - else - { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) - { - pOperation->notify.callback = *pCallbackInfo; - } - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Clean up on error. */ - if( status != AWS_IOT_JOBS_SUCCESS ) - { - if( pOperation != NULL ) - { - if( pOperation->pJobsRequest != NULL ) - { - AwsIotJobs_FreeString( ( void * ) ( pOperation->pJobsRequest ) ); - } - - AwsIotJobs_FreeOperation( pOperation ); - } - } - else - { - /* Set the output parameter. */ - *pNewOperation = pOperation; - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -void _AwsIotJobs_DestroyOperation( void * pData ) -{ - _jobsOperation_t * pOperation = ( _jobsOperation_t * ) pData; - - AwsIotJobs_Assert( pOperation != NULL ); - - IotLogDebug( "Destroying Jobs operation %s.", - _pAwsIotJobsOperationNames[ pOperation->type ] ); - - /* Check if a wait semaphore was created for this operation. */ - if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) - { - /* Destroy the wait semaphore */ - IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); - } - - /* Free any Jobs request documents. */ - if( pOperation->pJobsRequest != NULL ) - { - AwsIotJobs_Assert( pOperation->jobsRequestLength > 0 ); - - AwsIotJobs_FreeString( ( void * ) ( pOperation->pJobsRequest ) ); - } - - /* Free the memory used to hold operation data. */ - AwsIotJobs_FreeOperation( pOperation ); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t _AwsIotJobs_GenerateJobsTopic( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - char ** pTopicBuffer, - uint16_t * pOperationTopicLength ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - AwsIotTopicInfo_t topicInfo = { 0 }; - char pJobOperationName[ JOBS_LONGEST_SUFFIX_LENGTH ] = { 0 }; - uint16_t operationNameLength = 0; - - /* Lookup table for Jobs operation strings. */ - const char * const pOperationString[ JOBS_OPERATION_COUNT ] = - { - JOBS_GET_PENDING_OPERATION_STRING, /* Jobs get pending operation. */ - JOBS_START_NEXT_OPERATION_STRING, /* Jobs start next operation. */ - JOBS_DESCRIBE_OPERATION_STRING, /* Jobs describe operation */ - JOBS_UPDATE_OPERATION_STRING /* Jobs update operation. */ - }; - - /* Lookup table for Jobs operation string lengths. */ - const uint16_t pOperationStringLength[ JOBS_OPERATION_COUNT ] = - { - JOBS_GET_PENDING_OPERATION_STRING_LENGTH, /* Jobs get pending operation */ - JOBS_START_NEXT_OPERATION_STRING_LENGTH, /* Jobs start next operation. */ - JOBS_DESCRIBE_OPERATION_STRING_LENGTH, /* Jobs describe operation */ - JOBS_UPDATE_OPERATION_STRING_LENGTH /* Jobs update operation. */ - }; - - /* Ensure type is valid. */ - AwsIotJobs_Assert( ( type == JOBS_GET_PENDING ) || ( type == JOBS_START_NEXT ) || - ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ); - - /* Set the members needed to generate an operation topic. */ - topicInfo.pThingName = pRequestInfo->pThingName; - topicInfo.thingNameLength = pRequestInfo->thingNameLength; - topicInfo.longestSuffixLength = JOBS_LONGEST_SUFFIX_LENGTH; - topicInfo.mallocString = AwsIotJobs_MallocString; - - /* Job operations that require a Job ID require additional processing to - * create an operation name with the Job ID. */ - if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) - { - /* Ensure Job ID length is valid. */ - AwsIotJobs_Assert( pRequestInfo->jobIdLength > 0 ); - AwsIotJobs_Assert( pRequestInfo->jobIdLength <= JOBS_MAX_ID_LENGTH ); - - /* Construct the Jobs operation name with the Job ID. */ - ( void ) memcpy( pJobOperationName, "/jobs/", 6 ); - operationNameLength = 6; - - ( void ) memcpy( pJobOperationName + operationNameLength, - pRequestInfo->pJobId, - pRequestInfo->jobIdLength ); - operationNameLength = ( uint16_t ) ( pRequestInfo->jobIdLength + operationNameLength ); - - ( void ) memcpy( pJobOperationName + operationNameLength, - pOperationString[ type ], - pOperationStringLength[ type ] ); - operationNameLength = ( uint16_t ) ( operationNameLength + pOperationStringLength[ type ] ); - - topicInfo.pOperationName = pJobOperationName; - topicInfo.operationNameLength = operationNameLength; - } - else - { - topicInfo.pOperationName = pOperationString[ type ]; - topicInfo.operationNameLength = pOperationStringLength[ type ]; - } - - if( AwsIot_GenerateOperationTopic( &topicInfo, - pTopicBuffer, - pOperationTopicLength ) == false ) - { - status = AWS_IOT_JOBS_NO_MEMORY; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * pRequestInfo, - _jobsOperation_t * pOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - char * pTopicBuffer = NULL; - uint16_t operationTopicLength = 0; - bool freeTopicBuffer = true; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; - - IotLogDebug( "Processing Jobs operation %s for Thing %.*s.", - _pAwsIotJobsOperationNames[ pOperation->type ], - pRequestInfo->thingNameLength, - pRequestInfo->pThingName ); - - /* Set the operation's MQTT connection. */ - pOperation->mqttConnection = pRequestInfo->mqttConnection; - - /* Generate the operation topic buffer. */ - status = _AwsIotJobs_GenerateJobsTopic( pOperation->type, - pRequestInfo, - &pTopicBuffer, - &operationTopicLength ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IotLogError( "No memory for Jobs operation topic buffer." ); - - IOT_GOTO_CLEANUP(); - } - - /* Get a subscription object for this Jobs operation. */ - status = _findSubscription( pRequestInfo, - pTopicBuffer, - operationTopicLength, - pOperation, - &freeTopicBuffer ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - /* No subscription was found and no subscription could be allocated. */ - IOT_GOTO_CLEANUP(); - } - - /* Set the members for PUBLISH retry. */ - publishInfo.qos = pRequestInfo->qos; - publishInfo.retryLimit = pRequestInfo->retryLimit; - publishInfo.retryMs = pRequestInfo->retryMs; - - /* Set the payload as the Jobs request. */ - publishInfo.pPayload = pOperation->pJobsRequest; - publishInfo.payloadLength = pOperation->jobsRequestLength; - - /* Set the operation topic name. */ - publishInfo.pTopicName = pTopicBuffer; - publishInfo.topicNameLength = operationTopicLength; - - IotLogDebug( "Jobs %s message will be published to topic %.*s", - _pAwsIotJobsOperationNames[ pOperation->type ], - publishInfo.topicNameLength, - publishInfo.pTopicName ); - - /* Add Jobs operation to the pending operations list. */ - IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); - IotListDouble_InsertHead( &( _AwsIotJobsPendingOperations ), - &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); - - /* Publish to the Jobs topic name. */ - publishStatus = IotMqtt_PublishSync( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotJobsMqttTimeoutMs ); - - if( publishStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to publish MQTT message to %s %.*s Jobs, error %s.", - _pAwsIotJobsOperationNames[ pOperation->type ], - pRequestInfo->thingNameLength, - pRequestInfo->pThingName, - IotMqtt_strerror( publishStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Jobs "NO MEMORY" error. */ - if( publishStatus == IOT_MQTT_NO_MEMORY ) - { - status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - status = AWS_IOT_JOBS_MQTT_ERROR; - } - - /* If the "keep subscriptions" flag is not set, decrement the reference - * count. */ - if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) - { - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - _AwsIotJobs_DecrementReferences( pOperation, - pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - } - - /* Remove Jobs operation from the pending operations list. */ - IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); - IotListDouble_Remove( &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); - } - else - { - IotLogDebug( "Jobs %s PUBLISH message successfully sent.", - _pAwsIotJobsOperationNames[ pOperation->type ] ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Free the topic buffer used by this function if it was not assigned to a - * subscription. */ - if( ( freeTopicBuffer == true ) && ( pTopicBuffer != NULL ) ) - { - AwsIotJobs_FreeString( pTopicBuffer ); - } - - /* Destroy the Jobs operation on failure. */ - if( status != AWS_IOT_JOBS_SUCCESS ) - { - _AwsIotJobs_DestroyOperation( pOperation ); - } - else - { - /* Convert successful return code to "status pending", as the Jobs - * library is now waiting for a response from the service. */ - status = AWS_IOT_JOBS_STATUS_PENDING; - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/src/aws_iot_jobs_serialize.c b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c deleted file mode 100644 index 0b4338abcc..0000000000 --- a/libraries/aws/jobs/src/aws_iot_jobs_serialize.c +++ /dev/null @@ -1,1211 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_serialize.c - * @brief Implements functions that generate and parse Jobs JSON documents. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -/** - * @brief Minimum length of a Jobs request. - * - * At the very least, the request will contain: {"clientToken":""} - */ -#define MINIMUM_REQUEST_LENGTH ( AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7 ) - -/** - * @brief The length of client tokens generated by this library. - */ -#define CLIENT_TOKEN_AUTOGENERATE_LENGTH ( 8 ) - -/** - * @brief JSON key representing Jobs status. - */ -#define STATUS_KEY "status" - -/** - * @brief Length of #STATUS_KEY. - */ -#define STATUS_KEY_LENGTH ( sizeof( STATUS_KEY ) - 1 ) - -/** - * @brief JSON key representing Jobs status details. - */ -#define STATUS_DETAILS_KEY "statusDetails" - -/** - * @brief Length of #STATUS_DETAILS_KEY. - */ -#define STATUS_DETAILS_KEY_LENGTH ( sizeof( STATUS_DETAILS_KEY ) - 1 ) - -/** - * @brief JSON key representing Jobs expected version. - */ -#define EXPECTED_VERSION_KEY "expectedVersion" - -/** - * @brief Length of #EXPECTED_VERSION_KEY. - */ -#define EXPECTED_VERSION_KEY_LENGTH ( sizeof( EXPECTED_VERSION_KEY ) - 1 ) - -/** - * @brief Maximum length of the expected version when represented as a string. - * - * The expected version is a 32-bit unsigned integer. This can be represented in - * 10 digits plus a NULL-terminator. - */ -#define EXPECTED_VERSION_STRING_LENGTH ( 11 ) - -/** - * @brief JSON key representing Jobs step timeout. - */ -#define STEP_TIMEOUT_KEY "stepTimeoutInMinutes" - -/** - * @brief Length of #STEP_TIMEOUT_KEY. - */ -#define STEP_TIMEOUT_KEY_LENGTH ( sizeof( STEP_TIMEOUT_KEY ) - 1 ) - -/** - * @brief Maximum length of the step timeout when represented as a string. - * - * The step timeout is in the range of [-1,10080]. This can be represented as - * 5 digits plus a NULL-terminator. - */ -#define STEP_TIMEOUT_STRING_LENGTH ( 6 ) - -/** - * @brief JSON key representing the "include Job document" flag. - */ -#define INCLUDE_JOB_DOCUMENT_KEY "includeJobDocument" - -/** - * @brief JSON key representing the "include Job Execution state" flag. - */ -#define INCLUDE_JOB_EXECUTION_STATE_KEY "includeJobExecutionState" - -/** - * @brief Length of #INCLUDE_JOB_EXECUTION_STATE_KEY. - */ -#define INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH ( sizeof( INCLUDE_JOB_EXECUTION_STATE_KEY ) - 1 ) - -/** - * @brief Length of #INCLUDE_JOB_DOCUMENT_KEY. - */ -#define INCLUDE_JOB_DOCUMENT_KEY_LENGTH ( sizeof( INCLUDE_JOB_DOCUMENT_KEY ) - 1 ) - -/** - * @brief JSON key representing the Jobs execution number. - */ -#define EXECUTION_NUMBER_KEY "executionNumber" - -/** - * @brief Length of #EXECUTION_NUMBER_KEY. - */ -#define EXECUTION_NUMBER_KEY_LENGTH ( sizeof( EXECUTION_NUMBER_KEY ) - 1 ) - -/** - * @brief Maximum length of the execution number when represented as a string. - * - * The execution number is a 32-bit integer. This can be represented in 10 digits, - * plus 1 for a possible negative sign, plus a NULL-terminator. - */ -#define EXECUTION_NUMBER_STRING_LENGTH ( 12 ) - -/** - * @brief JSON key representing Jobs error code in error responses. - */ -#define CODE_KEY "code" - -/** - * @brief Length of #CODE_KEY. - */ -#define CODE_KEY_LENGTH ( sizeof( CODE_KEY ) - 1 ) - -/** - * @brief Append a string to a buffer. - * - * Also updates `copyOffset` with `stringLength`. - * - * @param[in] pBuffer Start of a buffer. - * @param[in] copyOffset Offset in `pBuffer` where `pString` will be placed. - * @param[in] pString The string to append. - * @param[in] stringLength Length of `pString`. - */ -#define APPEND_STRING( pBuffer, copyOffset, pString, stringLength ) \ - ( void ) memcpy( pBuffer + copyOffset, pString, stringLength ); \ - copyOffset += ( size_t ) stringLength; - -/*-----------------------------------------------------------*/ - -/** - * @brief Place a JSON boolean flag in the given buffer. - * - * @param[in] pBuffer The buffer where the flag is placed. - * @param[in] copyOffset Offset in `pBuffer` where the flag is placed. - * @param[in] pFlagName Either #INCLUDE_JOB_DOCUMENT_KEY or #INCLUDE_JOB_EXECUTION_STATE_KEY. - * @param[in] flagNameLength Either #INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH or - * #INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH - * @param[in] value Either `true` or `false`. - * - * @warning This function does not check the length of `pBuffer`! Any provided - * buffer must be large enough to accommodate the flag and value. - * - * @return A value of `copyOffset` after the flag. - */ -static size_t _appendFlag( char * pBuffer, - size_t copyOffset, - const char * pFlagName, - size_t flagNameLength, - bool value ); - -/** - * @brief Place Job status details in the given buffer. - * - * @param[in] pBuffer The buffer where the status details are placed. - * @param[in] copyOffset Offset in `pBuffer` where the status details are placed. - * @param[in] pStatusDetails The status details to place in the buffer. - * @param[in] statusDetailsLength Length of `pStatusDetails`. - * - * @warning This function does not check the length of `pBuffer`! Any provided - * buffer must be large enough to accommodate the status details. - * - * @return A value of `copyOffset` after the status details. - */ -static size_t _appendStatusDetails( char * pBuffer, - size_t copyOffset, - const char * pStatusDetails, - size_t statusDetailsLength ); - -/** - * @brief Place Job execution number in the given buffer. - * - * @param[in] pBuffer The buffer where the execution number is placed. - * @param[in] copyOffset Offset in `pBuffer` where the execution number is placed. - * @param[in] pExecutionNumber The execution number to place in the buffer. - * @param[in] executionNumberLength Length of `pExecutionNumber`. - * - * @warning This function does not check the length of `pBuffer`! Any provided - * buffer must be large enough to accommodate the execution number. - * - * @return A value of `copyOffset` after the execution number. - */ -static size_t _appendExecutionNumber( char * pBuffer, - size_t copyOffset, - const char * pExecutionNumber, - size_t executionNumberLength ); - -/** - * @brief Place Job step timeout in the given buffer. - * - * @param[in] pBuffer The buffer where the step timeout is placed. - * @param[in] copyOffset Offset in `pBuffer` where the step timeout is placed. - * @param[in] pStepTimeout The step timeout to place in the buffer. - * @param[in] stepTimeoutLength Length of `pStepTimeout`. - * - * @warning This function does not check the length of `pBuffer`! Any provided - * buffer must be large enough to accommodate the step timeout. - * - * @return A value of `copyOffset` after the step timeout. - */ -static size_t _appendStepTimeout( char * pBuffer, - size_t copyOffset, - const char * pStepTimeout, - size_t stepTimeoutLength ); - -/** - * @brief Place a client token in the given buffer. - * - * @param[in] pBuffer The buffer where the client token is placed. - * @param[in] copyOffset Offset in `pBuffer` where client token is placed. - * @param[in] pRequestInfo Contains information on a client token to place. - * @param[out] pOperation Location and length of client token are written here. - * - * @warning This function does not check the length of `pBuffer`! Any provided - * buffer must be large enough to accommodate #CLIENT_TOKEN_AUTOGENERATE_LENGTH - * characters. - * - * @return A value of `copyOffset` after the client token. - */ -static size_t _appendClientToken( char * pBuffer, - size_t copyOffset, - const AwsIotJobsRequestInfo_t * pRequestInfo, - _jobsOperation_t * pOperation ); - -/** - * @brief Generates a request JSON for a GET PENDING operation. - * - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pOperation Operation associated with the Jobs request. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY - */ -static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - _jobsOperation_t * pOperation ); - -/** - * @brief Generates a request JSON for a START NEXT operation. - * - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. - * @param[in] pOperation Operation associated with the Jobs request. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY - */ -static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - _jobsOperation_t * pOperation ); - -/** - * @brief Generates a request JSON for a DESCRIBE operation. - * - * @param[in] pRequestInfo Common jobs request parameters. - * @param[in] executionNumber Job execution number to include in request. - * @param[in] includeJobDocument Whether the response should include the Job document. - * @param[in] pOperation Operation associated with the Jobs request. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY. - */ -static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - _jobsOperation_t * pOperation ); - -/** - * @brief Generates a request JSON for an UPDATE operation. - * - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. - * @param[in] pOperation Operation associated with the Jobs request. - */ -static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - _jobsOperation_t * pOperation ); - -/** - * @brief Parse an error from a Jobs error document. - * - * @param[in] pErrorDocument Jobs error document. - * @param[in] errorDocumentLength Length of `pErrorDocument`. - * - * @return A Jobs error code between #AWS_IOT_JOBS_INVALID_TOPIC and - * #AWS_IOT_JOBS_TERMINAL_STATE. - */ -static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, - size_t errorDocumentLength ); - -/*-----------------------------------------------------------*/ - -static size_t _appendFlag( char * pBuffer, - size_t copyOffset, - const char * pFlagName, - size_t flagNameLength, - bool value ) -{ - if( value == true ) - { - APPEND_STRING( pBuffer, - copyOffset, - pFlagName, - flagNameLength ); - APPEND_STRING( pBuffer, copyOffset, "\":true,\"", 8 ); - } - else - { - APPEND_STRING( pBuffer, - copyOffset, - pFlagName, - flagNameLength ); - APPEND_STRING( pBuffer, copyOffset, "\":false,\"", 9 ); - } - - return copyOffset; -} - -/*-----------------------------------------------------------*/ - -static size_t _appendStatusDetails( char * pBuffer, - size_t copyOffset, - const char * pStatusDetails, - size_t statusDetailsLength ) -{ - APPEND_STRING( pBuffer, copyOffset, STATUS_DETAILS_KEY, STATUS_DETAILS_KEY_LENGTH ); - APPEND_STRING( pBuffer, copyOffset, "\":", 2 ); - APPEND_STRING( pBuffer, - copyOffset, - pStatusDetails, - statusDetailsLength ); - APPEND_STRING( pBuffer, copyOffset, ",\"", 2 ); - - return copyOffset; -} - -/*-----------------------------------------------------------*/ - -static size_t _appendExecutionNumber( char * pBuffer, - size_t copyOffset, - const char * pExecutionNumber, - size_t executionNumberLength ) -{ - APPEND_STRING( pBuffer, - copyOffset, - EXECUTION_NUMBER_KEY, - EXECUTION_NUMBER_KEY_LENGTH ); - APPEND_STRING( pBuffer, - copyOffset, - "\":", - 2 ); - APPEND_STRING( pBuffer, - copyOffset, - pExecutionNumber, - executionNumberLength ); - APPEND_STRING( pBuffer, copyOffset, ",\"", 2 ); - - return copyOffset; -} - -/*-----------------------------------------------------------*/ - -static size_t _appendStepTimeout( char * pBuffer, - size_t copyOffset, - const char * pStepTimeout, - size_t stepTimeoutLength ) -{ - APPEND_STRING( pBuffer, - copyOffset, - STEP_TIMEOUT_KEY, - STEP_TIMEOUT_KEY_LENGTH ); - APPEND_STRING( pBuffer, copyOffset, "\":", 2 ); - APPEND_STRING( pBuffer, copyOffset, pStepTimeout, stepTimeoutLength ); - APPEND_STRING( pBuffer, copyOffset, ",\"", 2 ); - - return copyOffset; -} - -/*-----------------------------------------------------------*/ - -static size_t _appendClientToken( char * pBuffer, - size_t copyOffset, - const AwsIotJobsRequestInfo_t * pRequestInfo, - _jobsOperation_t * pOperation ) -{ - int clientTokenLength = 0; - uint32_t clientToken = 0; - - /* Place the client token key in the buffer. */ - APPEND_STRING( pBuffer, - copyOffset, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); - APPEND_STRING( pBuffer, copyOffset, "\":\"", 3 ); - - /* Set the pointer to the client token. */ - pOperation->pClientToken = pBuffer + copyOffset - 1; - - if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - /* Take the address of the given buffer, truncated to 8 characters. This - * provides a client token that is very likely to be unique while in use. */ - clientToken = ( uint32_t ) ( ( uint64_t ) pBuffer % 100000000ULL ); - - clientTokenLength = snprintf( pBuffer + copyOffset, - CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1, - "%08u", clientToken ); - AwsIotJobs_Assert( clientTokenLength == CLIENT_TOKEN_AUTOGENERATE_LENGTH ); - - copyOffset += ( size_t ) clientTokenLength; - pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2; - } - else - { - APPEND_STRING( pBuffer, - copyOffset, - pRequestInfo->pClientToken, - pRequestInfo->clientTokenLength ); - - pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; - } - - return copyOffset; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - _jobsOperation_t * pOperation ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - char * pJobsRequest = NULL; - size_t copyOffset = 0; - size_t requestLength = MINIMUM_REQUEST_LENGTH; - - /* Add the length of the client token. */ - if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); - - requestLength += pRequestInfo->clientTokenLength; - } - else - { - requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; - } - - /* Allocate memory for the request JSON. */ - pJobsRequest = AwsIotJobs_MallocString( requestLength ); - - if( pJobsRequest == NULL ) - { - IotLogError( "No memory for Jobs GET PENDING request." ); - status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON, which consists of just a clientToken key. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; - - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); - - IotLogDebug( "Jobs GET PENDING request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - _jobsOperation_t * pOperation ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - char * pJobsRequest = NULL; - size_t copyOffset = 0; - size_t requestLength = MINIMUM_REQUEST_LENGTH; - char pStepTimeout[ STEP_TIMEOUT_STRING_LENGTH ] = { 0 }; - int stepTimeoutLength = 0; - - /* Add the length of status details if provided. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) - { - /* Add 4 for the 2 quotes, colon, and comma. */ - requestLength += STATUS_DETAILS_KEY_LENGTH + 4; - requestLength += pUpdateInfo->statusDetailsLength; - } - - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) - { - /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ - requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; - - if( pUpdateInfo->stepTimeoutInMinutes == AWS_IOT_JOBS_CANCEL_TIMEOUT ) - { - /* Step timeout will be set to -1. */ - pStepTimeout[ 0 ] = '-'; - pStepTimeout[ 1 ] = '1'; - stepTimeoutLength = 2; - } - else - { - /* Convert the step timeout to a string. */ - stepTimeoutLength = snprintf( pStepTimeout, - STEP_TIMEOUT_STRING_LENGTH, - "%d", - pUpdateInfo->stepTimeoutInMinutes ); - AwsIotJobs_Assert( stepTimeoutLength > 0 ); - AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); - } - - requestLength += ( size_t ) stepTimeoutLength; - } - - /* Add the length of the client token. */ - if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); - - requestLength += pRequestInfo->clientTokenLength; - } - else - { - requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; - } - - /* Allocate memory for the request JSON. */ - pJobsRequest = AwsIotJobs_MallocString( requestLength ); - - if( pJobsRequest == NULL ) - { - IotLogError( "No memory for Jobs START NEXT request." ); - status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - - /* Add status details if present. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) - { - copyOffset = _appendStatusDetails( pJobsRequest, - copyOffset, - pUpdateInfo->pStatusDetails, - pUpdateInfo->statusDetailsLength ); - } - - /* Add step timeout if present. */ - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) - { - copyOffset = _appendStepTimeout( pJobsRequest, - copyOffset, - pStepTimeout, - stepTimeoutLength ); - } - - /* Add client token. */ - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; - - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); - - IotLogDebug( "Jobs START NEXT request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - _jobsOperation_t * pOperation ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - char * pJobsRequest = NULL; - size_t copyOffset = 0; - size_t requestLength = MINIMUM_REQUEST_LENGTH; - char pExecutionNumber[ EXECUTION_NUMBER_STRING_LENGTH ] = { 0 }; - int executionNumberLength = 0; - - /* Add the "include job document" flag if false. The default value is true, - * so the flag is not needed if true. */ - if( includeJobDocument == false ) - { - /* Add the length of "includeJobDocument" plus 4 for 2 quotes, a colon, - * and a comma. */ - requestLength += INCLUDE_JOB_DOCUMENT_KEY_LENGTH + 4; - - /* Add the length of "false". */ - requestLength += 5; - } - - /* Add the length of the execution number if present. */ - if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) - { - /* Convert the execution number to a string. */ - executionNumberLength = snprintf( pExecutionNumber, - EXECUTION_NUMBER_STRING_LENGTH, - "%d", - executionNumber ); - AwsIotJobs_Assert( executionNumberLength > 0 ); - AwsIotJobs_Assert( executionNumberLength < EXECUTION_NUMBER_STRING_LENGTH ); - - requestLength += EXECUTION_NUMBER_KEY_LENGTH + 4; - requestLength += ( size_t ) executionNumberLength; - } - - /* Add the length of the client token. */ - if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); - - requestLength += pRequestInfo->clientTokenLength; - } - else - { - requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; - } - - /* Allocate memory for the request JSON. */ - pJobsRequest = AwsIotJobs_MallocString( requestLength ); - - if( pJobsRequest == NULL ) - { - IotLogError( "No memory for Jobs DESCRIBE request." ); - status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - - /* Add the "include job document" flag if false. */ - if( includeJobDocument == false ) - { - copyOffset = _appendFlag( pJobsRequest, - copyOffset, - INCLUDE_JOB_DOCUMENT_KEY, - INCLUDE_JOB_DOCUMENT_KEY_LENGTH, - false ); - } - - /* Add the length of the execution number if present. */ - if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) - { - copyOffset = _appendExecutionNumber( pJobsRequest, - copyOffset, - pExecutionNumber, - ( size_t ) executionNumberLength ); - } - - /* Add client token. */ - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; - - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); - - IotLogDebug( "Jobs DESCRIBE request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - _jobsOperation_t * pOperation ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; - char * pJobsRequest = NULL; - size_t copyOffset = 0; - size_t requestLength = MINIMUM_REQUEST_LENGTH; - const char * pStatus = NULL; - size_t statusLength = 0; - char pExpectedVersion[ EXPECTED_VERSION_STRING_LENGTH ] = { 0 }; - char pExecutionNumber[ EXECUTION_NUMBER_STRING_LENGTH ] = { 0 }; - char pStepTimeout[ STEP_TIMEOUT_STRING_LENGTH ] = { 0 }; - int expectedVersionLength = 0, executionNumberLength = 0, stepTimeoutLength = 0; - - /* Determine the status string and length to report to the Jobs service. - * Add 6 for the 4 quotes, colon, and comma. */ - requestLength += STATUS_KEY_LENGTH + 6; - - switch( pUpdateInfo->newStatus ) - { - case AWS_IOT_JOB_STATE_IN_PROGRESS: - pStatus = "IN_PROGRESS"; - break; - - case AWS_IOT_JOB_STATE_FAILED: - pStatus = "FAILED"; - break; - - case AWS_IOT_JOB_STATE_SUCCEEDED: - pStatus = "SUCCEEDED"; - break; - - default: - /* The only remaining valid state is REJECTED. */ - AwsIotJobs_Assert( pUpdateInfo->newStatus == AWS_IOT_JOB_STATE_REJECTED ); - pStatus = "REJECTED"; - break; - } - - statusLength = strlen( pStatus ); - requestLength += statusLength; - - /* Add the length of status details if provided. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) - { - /* Add 4 for the 2 quotes, colon, and comma. */ - requestLength += STATUS_DETAILS_KEY_LENGTH + 4; - requestLength += pUpdateInfo->statusDetailsLength; - } - - /* Add the expected version if provided. */ - if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION ) - { - /* Convert the expected version to a string. */ - expectedVersionLength = snprintf( pExpectedVersion, - EXPECTED_VERSION_STRING_LENGTH, - "%u", - pUpdateInfo->expectedVersion ); - AwsIotJobs_Assert( expectedVersionLength > 0 ); - AwsIotJobs_Assert( expectedVersionLength < EXPECTED_VERSION_STRING_LENGTH ); - - /* Add 6 for the 4 quotes, colon, and comma. */ - requestLength += EXPECTED_VERSION_KEY_LENGTH + 6; - requestLength += ( size_t ) expectedVersionLength; - } - - /* Add the length of the execution number if present. */ - if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) - { - /* Convert the execution number to a string. */ - executionNumberLength = snprintf( pExecutionNumber, - EXECUTION_NUMBER_STRING_LENGTH, - "%d", - pUpdateInfo->executionNumber ); - AwsIotJobs_Assert( executionNumberLength > 0 ); - AwsIotJobs_Assert( executionNumberLength < EXECUTION_NUMBER_STRING_LENGTH ); - - requestLength += EXECUTION_NUMBER_KEY_LENGTH + 4; - requestLength += ( size_t ) executionNumberLength; - } - - /* Add the flags if true. The default values are false, so the flags are not - * needed if false. */ - if( pUpdateInfo->includeJobExecutionState == true ) - { - /* Add the length of "includeJobExecutionState" plus 4 for 2 quotes, a colon, - * and a comma. */ - requestLength += INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH + 4; - - /* Add the length of "true". */ - requestLength += 4; - } - - if( pUpdateInfo->includeJobDocument == true ) - { - /* Add the length of "includeJobDocument" plus 4 for 2 quotes, a colon, - * and a comma. */ - requestLength += INCLUDE_JOB_DOCUMENT_KEY_LENGTH + 4; - - /* Add the length of "true". */ - requestLength += 4; - } - - /* Add the step timeout if provided. */ - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) - { - /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ - requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; - - if( pUpdateInfo->stepTimeoutInMinutes == AWS_IOT_JOBS_CANCEL_TIMEOUT ) - { - /* Step timeout will be set to -1. */ - pStepTimeout[ 0 ] = '-'; - pStepTimeout[ 1 ] = '1'; - stepTimeoutLength = 2; - } - else - { - /* Convert the step timeout to a string. */ - stepTimeoutLength = snprintf( pStepTimeout, - STEP_TIMEOUT_STRING_LENGTH, - "%d", - pUpdateInfo->stepTimeoutInMinutes ); - AwsIotJobs_Assert( stepTimeoutLength > 0 ); - AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); - } - - requestLength += ( size_t ) stepTimeoutLength; - } - - /* Add the length of the client token. */ - if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); - - requestLength += pRequestInfo->clientTokenLength; - } - else - { - requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; - } - - /* Allocate memory for the request JSON. */ - pJobsRequest = AwsIotJobs_MallocString( requestLength ); - - if( pJobsRequest == NULL ) - { - IotLogError( "No memory for Jobs UPDATE request." ); - status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - /* Clear the request JSON. */ - ( void ) memset( pJobsRequest, 0x00, requestLength ); - - /* Construct the request JSON. */ - APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - - /* Add the status. */ - APPEND_STRING( pJobsRequest, copyOffset, STATUS_KEY, STATUS_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); - APPEND_STRING( pJobsRequest, copyOffset, pStatus, statusLength ); - APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); - - /* Add status details if present. */ - if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) - { - copyOffset = _appendStatusDetails( pJobsRequest, - copyOffset, - pUpdateInfo->pStatusDetails, - pUpdateInfo->statusDetailsLength ); - } - - /* Add expected version. */ - if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION ) - { - APPEND_STRING( pJobsRequest, - copyOffset, - EXPECTED_VERSION_KEY, - EXPECTED_VERSION_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); - APPEND_STRING( pJobsRequest, copyOffset, pExpectedVersion, expectedVersionLength ); - APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); - } - - /* Add execution number. */ - if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) - { - copyOffset = _appendExecutionNumber( pJobsRequest, - copyOffset, - pExecutionNumber, - executionNumberLength ); - } - - /* Add flags if not default values. */ - if( pUpdateInfo->includeJobExecutionState == true ) - { - copyOffset = _appendFlag( pJobsRequest, - copyOffset, - INCLUDE_JOB_EXECUTION_STATE_KEY, - INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH, - true ); - } - - if( pUpdateInfo->includeJobDocument == true ) - { - copyOffset = _appendFlag( pJobsRequest, - copyOffset, - INCLUDE_JOB_DOCUMENT_KEY, - INCLUDE_JOB_DOCUMENT_KEY_LENGTH, - true ); - } - - /* Add step timeout if provided. */ - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) - { - copyOffset = _appendStepTimeout( pJobsRequest, - copyOffset, - pStepTimeout, - stepTimeoutLength ); - } - - /* Add the client token. */ - copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - - APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - - /* Set the output parameters. */ - pOperation->pJobsRequest = pJobsRequest; - pOperation->jobsRequestLength = requestLength; - - /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset == requestLength ); - AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); - AwsIotJobs_Assert( pOperation->pClientToken < - pOperation->pJobsRequest + pOperation->jobsRequestLength ); - - IotLogDebug( "Jobs UPDATE request: %.*s", - pOperation->jobsRequestLength, - pOperation->pJobsRequest ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, - size_t errorDocumentLength ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_BAD_RESPONSE; - const char * pCode = NULL; - size_t codeLength = 0; - - /* Find the error code. */ - if( AwsIotDocParser_FindValue( pErrorDocument, - errorDocumentLength, - CODE_KEY, - CODE_KEY_LENGTH, - &pCode, - &codeLength ) == true ) - { - switch( codeLength ) - { - /* InvalidJson */ - case 13: - - if( strncmp( "\"InvalidJson\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_JSON; - } - - break; - - /* InvalidTopic */ - case 14: - - if( strncmp( "\"InvalidTopic\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_TOPIC; - } - - break; - - /* InternalError */ - case 15: - - if( strncmp( "\"InternalError\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INTERNAL_ERROR; - } - - break; - - /* InvalidRequest */ - case 16: - - if( strncmp( "\"InvalidRequest\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_REQUEST; - } - - break; - - /* VersionMismatch */ - case 17: - - if( strncmp( "\"VersionMismatch\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_VERSION_MISMATCH; - } - - break; - - /* ResourceNotFound, RequestThrottled */ - case 18: - - if( strncmp( "\"ResourceNotFound\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_NOT_FOUND; - } - else if( strncmp( "\"RequestThrottled\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_THROTTLED; - } - - break; - - /* TerminalStateReached */ - case 22: - - if( strncmp( "\"TerminalStateReached\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_TERMINAL_STATE; - } - - break; - - /* InvalidStateTransition */ - case 24: - - if( strncmp( "\"InvalidStateTransition\"", pCode, codeLength ) == 0 ) - { - status = AWS_IOT_JOBS_INVALID_STATE; - } - - break; - - default: - - /* Assume bad response status unless matched.*/ - break; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - const _jsonRequestContents_t * pRequestContents, - _jobsOperation_t * pOperation ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - - /* Generate request based on the Job operation type. */ - switch( type ) - { - case JOBS_GET_PENDING: - status = _generateGetPendingRequest( pRequestInfo, pOperation ); - break; - - case JOBS_START_NEXT: - status = _generateStartNextRequest( pRequestInfo, - pRequestContents->pUpdateInfo, - pOperation ); - break; - - case JOBS_DESCRIBE: - status = _generateDescribeRequest( pRequestInfo, - pRequestContents->describe.executionNumber, - pRequestContents->describe.includeJobDocument, - pOperation ); - break; - - default: - /* The only remaining valid type is UPDATE. */ - AwsIotJobs_Assert( type == JOBS_UPDATE ); - - status = _generateUpdateRequest( pRequestInfo, - pRequestContents->pUpdateInfo, - pOperation ); - break; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _AwsIotJobs_ParseResponse( AwsIotStatus_t status, - const char * pResponse, - size_t responseLength, - _jobsOperation_t * pOperation ) -{ - AwsIotJobs_Assert( pOperation->status == AWS_IOT_JOBS_STATUS_PENDING ); - - /* A non-waitable operation can re-use the pointers from the publish info, - * since those are guaranteed to be in-scope throughout the user callback. - * But a waitable operation must copy the data from the publish info because - * AwsIotJobs_Wait may be called after the MQTT library frees the publish - * info. */ - if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 ) - { - pOperation->pJobsResponse = pResponse; - pOperation->jobsResponseLength = responseLength; - } - else - { - IotLogDebug( "Allocating new buffer for waitable Jobs %s.", - _pAwsIotJobsOperationNames[ pOperation->type ] ); - - /* Parameter validation should not have allowed a NULL malloc function. */ - AwsIotJobs_Assert( pOperation->mallocResponse != NULL ); - - /* Allocate a buffer for the retrieved document. */ - pOperation->pJobsResponse = pOperation->mallocResponse( responseLength ); - - if( pOperation->pJobsResponse == NULL ) - { - IotLogError( "Failed to allocate buffer for retrieved Jobs %s response.", - _pAwsIotJobsOperationNames[ pOperation->type ] ); - - pOperation->status = AWS_IOT_JOBS_NO_MEMORY; - } - else - { - /* Copy the response. */ - ( void ) memcpy( ( void * ) pOperation->pJobsResponse, pResponse, responseLength ); - pOperation->jobsResponseLength = responseLength; - } - } - - /* Set the status of the Jobs operation. */ - if( pOperation->status == AWS_IOT_JOBS_STATUS_PENDING ) - { - if( status == AWS_IOT_ACCEPTED ) - { - pOperation->status = AWS_IOT_JOBS_SUCCESS; - } - else - { - pOperation->status = _parseErrorDocument( pResponse, responseLength ); - } - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/src/aws_iot_jobs_static_memory.c b/libraries/aws/jobs/src/aws_iot_jobs_static_memory.c deleted file mode 100644 index 6a48df2fee..0000000000 --- a/libraries/aws/jobs/src/aws_iot_jobs_static_memory.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_static_memory.c - * @brief Implementation of Jobs static memory functions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include -#include - -/* Static memory include. */ -#include "iot_static_memory.h" - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS - #define AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ( 10 ) -#endif -#ifndef AWS_IOT_JOBS_SUBSCRIPTIONS - #define AWS_IOT_JOBS_SUBSCRIPTIONS ( 2 ) -#endif -/** @endcond */ - -/* Validate static memory configuration settings. */ -#if AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS <= 0 - #error "AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." -#endif -#if AWS_IOT_JOBS_SUBSCRIPTIONS <= 0 - #error "AWS_IOT_JOBS_SUBSCRIPTIONS cannot be 0 or negative." -#endif - -/** - * @brief The size of a static memory Jobs operation. - * - * Since the pJobId member of #_jobsOperation_t is variable-length, - * the constant `JOBS_MAX_ID_LENGTH` is used for the length of - * #_jobsOperation_t.pJobId. - */ -#define JOBS_OPERATION_SIZE ( sizeof( _jobsOperation_t ) + JOBS_MAX_ID_LENGTH ) - -/** - * @brief The size of a static memory Jobs subscription. - * - * Since the pThingName member of #_jobsSubscription_t is variable-length, - * the constant `AWS_IOT_MAX_THING_NAME_LENGTH` is used for the length of - * #_jobsSubscription_t.pThingName. - */ -#define JOBS_SUBSCRIPTION_SIZE ( sizeof( _jobsSubscription_t ) + AWS_IOT_MAX_THING_NAME_LENGTH ) - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static uint32_t _pInUseJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief Jobs operation in-use flags. */ -static char _pJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ][ JOBS_OPERATION_SIZE ] = { { 0 } }; /**< @brief Jobs operations. */ - -static uint32_t _pInUseJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ] = { 0U }; /**< @brief Jobs subscription in-use flags. */ -static char _pJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ][ JOBS_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Jobs subscriptions. */ - -/*-----------------------------------------------------------*/ - -void * AwsIotJobs_MallocOperation( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewOperation = NULL; - - /* Check size argument. */ - if( size <= JOBS_OPERATION_SIZE ) - { - /* Find a free Jobs operation. */ - freeIndex = IotStaticMemory_FindFree( _pInUseJobsOperations, - AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ); - - if( freeIndex != -1 ) - { - pNewOperation = &( _pJobsOperations[ freeIndex ] ); - } - } - - return pNewOperation; -} - -/*-----------------------------------------------------------*/ - -void AwsIotJobs_FreeOperation( void * ptr ) -{ - /* Return the in-use Jobs operation. */ - IotStaticMemory_ReturnInUse( ptr, - _pJobsOperations, - _pInUseJobsOperations, - AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS, - JOBS_OPERATION_SIZE ); -} - -/*-----------------------------------------------------------*/ - -void * AwsIotJobs_MallocSubscription( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewSubscription = NULL; - - if( size <= JOBS_SUBSCRIPTION_SIZE ) - { - /* Get the index of a free Jobs subscription. */ - freeIndex = IotStaticMemory_FindFree( _pInUseJobsSubscriptions, - AWS_IOT_JOBS_SUBSCRIPTIONS ); - - if( freeIndex != -1 ) - { - pNewSubscription = &( _pJobsSubscriptions[ freeIndex ][ 0 ] ); - } - } - - return pNewSubscription; -} - -/*-----------------------------------------------------------*/ - -void AwsIotJobs_FreeSubscription( void * ptr ) -{ - /* Return the in-use Jobs subscription. */ - IotStaticMemory_ReturnInUse( ptr, - _pJobsSubscriptions, - _pInUseJobsSubscriptions, - AWS_IOT_JOBS_SUBSCRIPTIONS, - JOBS_SUBSCRIPTION_SIZE ); -} - -/*-----------------------------------------------------------*/ - -#endif diff --git a/libraries/aws/jobs/src/aws_iot_jobs_subscription.c b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c deleted file mode 100644 index d5cb9d6212..0000000000 --- a/libraries/aws/jobs/src/aws_iot_jobs_subscription.c +++ /dev/null @@ -1,581 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_subscription.c - * @brief Implements functions for interacting with the Jobs library's - * subscription list. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Match two #_jobsSubscription_t by Thing Name. - * - * @param[in] pSubscriptionLink Pointer to the link member of a #_jobsSubscription_t - * containing the Thing Name to check. - * @param[in] pMatch Pointer to a `AwsIotThingName_t`. - * - * @return `true` if the Thing Names match; `false` otherwise. - */ -static bool _jobsSubscription_match( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -/*-----------------------------------------------------------*/ - -/** - * @brief List of active Jobs subscriptions objects. - */ -IotListDouble_t _AwsIotJobsSubscriptions = { 0 }; - -/** - * @brief Protects #_AwsIotJobsSubscriptions from concurrent access. - */ -IotMutex_t _AwsIotJobsSubscriptionsMutex; - -/*-----------------------------------------------------------*/ - -static bool _jobsSubscription_match( const IotLink_t * pSubscriptionLink, - void * pMatch ) -{ - bool match = false; - - /* Because this function is called from a container function, the given link - * must never be NULL. */ - AwsIotJobs_Assert( pSubscriptionLink != NULL ); - - const _jobsSubscription_t * pSubscription = IotLink_Container( _jobsSubscription_t, - pSubscriptionLink, - link ); - const AwsIotThingName_t * pThingName = ( AwsIotThingName_t * ) pMatch; - - if( pThingName->thingNameLength == pSubscription->thingNameLength ) - { - /* Check for matching Thing Names. */ - match = ( strncmp( pThingName->pThingName, - pSubscription->pThingName, - pThingName->thingNameLength ) == 0 ); - } - - return match; -} - -/*-----------------------------------------------------------*/ - -_jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, - size_t thingNameLength, - bool createIfNotFound ) -{ - _jobsSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = { 0 }; - - thingName.pThingName = pThingName; - thingName.thingNameLength = thingNameLength; - - /* Search the list for an existing subscription for Thing Name. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotJobsSubscriptions ), - NULL, - _jobsSubscription_match, - &thingName ); - - /* Check if a subscription was found. */ - if( pSubscriptionLink == NULL ) - { - if( createIfNotFound == true ) - { - /* No subscription found. Allocate a new subscription. */ - pSubscription = AwsIotJobs_MallocSubscription( sizeof( _jobsSubscription_t ) + thingNameLength ); - - if( pSubscription != NULL ) - { - /* Clear the new subscription. */ - ( void ) memset( pSubscription, 0x00, sizeof( _jobsSubscription_t ) + thingNameLength ); - - /* Set the Thing Name length and copy the Thing Name into the new subscription. */ - pSubscription->thingNameLength = thingNameLength; - ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); - - /* Add the new subscription to the subscription list. */ - IotListDouble_InsertHead( &( _AwsIotJobsSubscriptions ), - &( pSubscription->link ) ); - - IotLogDebug( "Created new Jobs subscriptions object for %.*s.", - thingNameLength, - pThingName ); - } - else - { - IotLogError( "Failed to allocate memory for %.*s Jobs subscriptions.", - thingNameLength, - pThingName ); - } - } - } - else - { - IotLogDebug( "Found existing Jobs subscriptions object for %.*s.", - thingNameLength, - pThingName ); - - pSubscription = IotLink_Container( _jobsSubscription_t, pSubscriptionLink, link ); - } - - return pSubscription; -} - -/*-----------------------------------------------------------*/ - -void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, - _jobsSubscription_t ** pRemovedSubscription ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - int32_t i = 0, callbackIndex = 0; - - IotLogDebug( "Checking if subscription object for %.*s can be removed.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - /* Check for active operations. If any Jobs operation's subscription - * reference count is not 0, then the subscription cannot be removed. */ - for( i = 0; i < JOBS_OPERATION_COUNT; i++ ) - { - if( pSubscription->operationReferences[ i ] > 0 ) - { - IotLogDebug( "Reference count %ld for %.*s subscription object. " - "Subscription cannot be removed yet.", - ( long int ) pSubscription->operationReferences[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else if( pSubscription->operationReferences[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " - "Subscription will not be removed.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* Check for active subscriptions. If any Jobs callbacks are active, then the - * subscription cannot be removed. */ - if( pSubscription->callbackReferences > 0 ) - { - IotLogDebug( "Notify callbacks are using %.*s subscription object. " - "Subscription cannot be removed yet.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - for( i = 0; i < JOBS_CALLBACK_COUNT; i++ ) - { - for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) - { - if( pSubscription->callbacks[ i ][ callbackIndex ].function != NULL ) - { - IotLogDebug( "Found active Jobs %s callback for %.*s subscription object. " - "Subscription cannot be removed yet.", - _pAwsIotJobsCallbackNames[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - } - - /* Remove the subscription if unused. */ - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status == true ) - { - /* No Jobs operation subscription references or active Jobs callbacks. - * Remove the subscription object. */ - IotListDouble_Remove( &( pSubscription->link ) ); - - IotLogDebug( "Removed subscription object for %.*s.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - /* If the caller requested the removed subscription, set the output parameter. - * Otherwise, free the memory used by the subscription. */ - if( pRemovedSubscription != NULL ) - { - *pRemovedSubscription = pSubscription; - } - else - { - _AwsIotJobs_DestroySubscription( pSubscription ); - } - } -} - -/*-----------------------------------------------------------*/ - -void _AwsIotJobs_DestroySubscription( void * pData ) -{ - _jobsSubscription_t * pSubscription = ( _jobsSubscription_t * ) pData; - - /* Free the topic buffer if allocated. */ - if( pSubscription->pTopicBuffer != NULL ) - { - AwsIotJobs_FreeString( pSubscription->pTopicBuffer ); - } - - /* Free memory used by subscription. */ - AwsIotJobs_FreeSubscription( pSubscription ); -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation, - char * pTopicBuffer, - uint16_t operationTopicLength, - AwsIotMqttCallbackFunction_t callback ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - const _jobsOperationType_t type = pOperation->type; - _jobsSubscription_t * pSubscription = pOperation->pSubscription; - IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; - AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; - - /* Do nothing if this operation has persistent subscriptions. */ - if( pSubscription->operationReferences[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - IotLogDebug( "Jobs %s for %.*s has persistent subscriptions. Reference " - "count will not be incremented.", - _pAwsIotJobsOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - - IOT_GOTO_CLEANUP(); - } - - /* When persistent subscriptions are not present, the reference count must - * not be negative. */ - AwsIotJobs_Assert( pSubscription->operationReferences[ type ] >= 0 ); - - /* Check if there are any existing references for this operation. */ - if( pSubscription->operationReferences[ type ] == 0 ) - { - /* Set the parameters needed to add subscriptions. */ - subscriptionInfo.mqttConnection = pOperation->mqttConnection; - subscriptionInfo.callbackFunction = callback; - subscriptionInfo.timeout = _AwsIotJobsMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; - - subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync, - &subscriptionInfo ); - - /* Convert MQTT return code to Jobs return code. */ - switch( subscriptionStatus ) - { - case IOT_MQTT_SUCCESS: - status = AWS_IOT_JOBS_SUCCESS; - break; - - case IOT_MQTT_NO_MEMORY: - status = AWS_IOT_JOBS_NO_MEMORY; - break; - - default: - status = AWS_IOT_JOBS_MQTT_ERROR; - break; - } - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - } - - /* Increment the number of subscription references for this operation when - * the keep subscriptions flag is not set. */ - if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) - { - ( pSubscription->operationReferences[ type ] )++; - - IotLogDebug( "Jobs %s subscriptions for %.*s now has count %d.", - _pAwsIotJobsOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName, - pSubscription->operationReferences[ type ] ); - } - /* Otherwise, set the persistent subscriptions flag. */ - else - { - pSubscription->operationReferences[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; - - IotLogDebug( "Set persistent subscriptions flag for Jobs %s of %.*s.", - _pAwsIotJobsOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, - char * pTopicBuffer, - _jobsSubscription_t ** pRemovedSubscription ) -{ - const _jobsOperationType_t type = pOperation->type; - _jobsSubscription_t * pSubscription = pOperation->pSubscription; - uint16_t operationTopicLength = 0; - AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - - /* Do nothing if this Jobs operation has persistent subscriptions. */ - if( pSubscription->operationReferences[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - /* Decrement the number of subscription references for this operation. - * Ensure that it's positive. */ - ( pSubscription->operationReferences[ type ] )--; - AwsIotJobs_Assert( pSubscription->operationReferences[ type ] >= 0 ); - - /* Check if the number of references has reached 0. */ - if( pSubscription->operationReferences[ type ] == 0 ) - { - IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotJobsOperationNames[ type ] ); - - /* Subscription must have a topic buffer. */ - AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); - - /* Set the parameters needed to generate a Jobs topic. */ - requestInfo.pThingName = pSubscription->pThingName; - requestInfo.thingNameLength = pSubscription->thingNameLength; - requestInfo.pJobId = pOperation->pJobId; - requestInfo.jobIdLength = pOperation->jobIdLength; - - /* Generate the prefix of the Jobs topic. This function will not - * fail when given a buffer. */ - ( void ) _AwsIotJobs_GenerateJobsTopic( ( _jobsOperationType_t ) type, - &requestInfo, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); - - /* Set the parameters needed to remove subscriptions. */ - subscriptionInfo.mqttConnection = pOperation->mqttConnection; - subscriptionInfo.timeout = _AwsIotJobsMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; - - ( void ) AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, - &subscriptionInfo ); - } - - /* Check if this subscription should be deleted. */ - _AwsIotJobs_RemoveSubscription( pSubscription, - pRemovedSubscription ); - } - else - { - IotLogDebug( "Jobs %s for %.*s has persistent subscriptions. Reference " - "count will not be decremented.", - _pAwsIotJobsOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - } -} - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags ) -{ - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - int32_t i = 0; - uint16_t operationTopicLength = 0; - IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING; - AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; - _jobsSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = { 0 }; - - thingName.pThingName = pRequestInfo->pThingName; - thingName.thingNameLength = pRequestInfo->thingNameLength; - - IotLogInfo( "Removing persistent subscriptions for %.*s.", - pRequestInfo->thingNameLength, - pRequestInfo->pThingName ); - - /* Check parameters. */ - if( pRequestInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) - { - IotLogError( "MQTT connection is not initialized." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( AwsIot_ValidateThingName( pRequestInfo->pThingName, - pRequestInfo->thingNameLength ) == false ) - { - IotLogError( "Thing Name is not valid." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( ( ( flags & AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ) != 0 ) || - ( ( flags & AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ) != 0 ) ) - { - if( ( pRequestInfo->pJobId == NULL ) || ( pRequestInfo->jobIdLength == 0 ) ) - { - IotLogError( "Job ID must be set." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - - if( pRequestInfo->jobIdLength > JOBS_MAX_ID_LENGTH ) - { - IotLogError( "Job ID cannot be longer than %d.", - JOBS_MAX_ID_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); - } - } - - IotMutex_Lock( &( _AwsIotJobsSubscriptionsMutex ) ); - - /* Search the list for an existing subscription for Thing Name. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotJobsSubscriptions ), - NULL, - _jobsSubscription_match, - &thingName ); - - if( pSubscriptionLink != NULL ) - { - IotLogDebug( "Found subscription object for %.*s. Checking for persistent " - "subscriptions to remove.", - pRequestInfo->thingNameLength, - pRequestInfo->pThingName ); - - pSubscription = IotLink_Container( _jobsSubscription_t, pSubscriptionLink, link ); - - for( i = 0; i < JOBS_OPERATION_COUNT; i++ ) - { - if( ( flags & ( 0x1UL << i ) ) != 0 ) - { - IotLogDebug( "Removing %.*s %s persistent subscriptions.", - pRequestInfo->thingNameLength, - pRequestInfo->pThingName, - _pAwsIotJobsOperationNames[ i ] ); - - /* Subscription must have a topic buffer. */ - AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); - - if( pSubscription->operationReferences[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - /* Generate the prefix of the Jobs topic. This function will not - * fail when given a buffer. */ - ( void ) _AwsIotJobs_GenerateJobsTopic( ( _jobsOperationType_t ) i, - pRequestInfo, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); - - /* Set the parameters needed to remove subscriptions. */ - subscriptionInfo.mqttConnection = pRequestInfo->mqttConnection; - subscriptionInfo.timeout = _AwsIotJobsMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; - - unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, - &subscriptionInfo ); - - /* Convert MQTT return code to Shadow return code. */ - switch( unsubscribeStatus ) - { - case IOT_MQTT_SUCCESS: - status = AWS_IOT_JOBS_SUCCESS; - break; - - case IOT_MQTT_NO_MEMORY: - status = AWS_IOT_JOBS_NO_MEMORY; - break; - - default: - status = AWS_IOT_JOBS_MQTT_ERROR; - break; - } - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - break; - } - - /* Clear the persistent subscriptions flag and check if the - * subscription can be removed. */ - pSubscription->operationReferences[ i ] = 0; - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - } - else - { - IotLogDebug( "%.*s %s does not have persistent subscriptions.", - pRequestInfo->thingNameLength, - pRequestInfo->pThingName, - _pAwsIotJobsOperationNames[ i ] ); - } - } - } - } - else - { - IotLogWarn( "No subscription object found for %.*s", - pRequestInfo->thingNameLength, - pRequestInfo->pThingName ); - } - - IotMutex_Unlock( &( _AwsIotJobsSubscriptionsMutex ) ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h b/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h deleted file mode 100644 index a5d43ef51a..0000000000 --- a/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h +++ /dev/null @@ -1,628 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_internal.h - * @brief Internal header of Jobs library. This header should not be included in - * typical application code. - */ - -#ifndef AWS_IOT_JOBS_INTERNAL_H_ -#define AWS_IOT_JOBS_INTERNAL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Linear containers (lists and queues) include. */ -#include "iot_linear_containers.h" - -/* Jobs include. */ -#include "aws_iot_jobs.h" - -/* AWS IoT include. */ -#include "aws_iot.h" - -/** - * @def AwsIotJobs_Assert( expression ) - * @brief Assertion macro for the Jobs library. - * - * Set @ref AWS_IOT_JOBS_ENABLE_ASSERTS to `1` to enable assertions in the Jobs - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if AWS_IOT_JOBS_ENABLE_ASSERTS == 1 - #ifndef AwsIotJobs_Assert - #ifdef Iot_DefaultAssert - #define AwsIotJobs_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for Jobs, but AwsIotJobs_Assert is not defined" - #endif - #endif -#else - #define AwsIotJobs_Assert( expression ) -#endif - -/* Configure logs for Jobs functions. */ -#ifdef AWS_IOT_LOG_LEVEL_JOBS - #define LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_JOBS -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "Jobs" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "iot_static_memory.h" - -/** - * @brief Allocate a #_jobsOperation_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * AwsIotJobs_MallocOperation( size_t size ); - -/** - * @brief Free a #_jobsOperation_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void AwsIotJobs_FreeOperation( void * ptr ); - -/** - * @brief Allocate a buffer for a short string, used for topic names or client - * tokens. This function should have the same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define AwsIotJobs_MallocString Iot_MallocMessageBuffer - -/** - * @brief Free a string. This function should have the same signature as - * [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define AwsIotJobs_FreeString Iot_FreeMessageBuffer - -/** - * @brief Allocate a #_jobsSubscription_t. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * AwsIotJobs_MallocSubscription( size_t size ); - -/** - * @brief Free a #_jobsSubscription_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void AwsIotJobs_FreeSubscription( void * ptr ); -#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #ifndef AwsIotJobs_MallocOperation - #ifdef Iot_DefaultMalloc - #define AwsIotJobs_MallocOperation Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotJobs_MallocOperation" - #endif - #endif - - #ifndef AwsIotJobs_FreeOperation - #ifdef Iot_DefaultFree - #define AwsIotJobs_FreeOperation Iot_DefaultFree - #else - #error "No free function defined for AwsIotJobs_FreeOperation" - #endif - #endif - - #ifndef AwsIotJobs_MallocString - #ifdef Iot_DefaultMalloc - #define AwsIotJobs_MallocString Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotJobs_MallocString" - #endif - #endif - - #ifndef AwsIotJobs_FreeString - #ifdef Iot_DefaultFree - #define AwsIotJobs_FreeString Iot_DefaultFree - #else - #error "No free function defined for AwsIotJobs_FreeString" - #endif - #endif - - #ifndef AwsIotJobs_MallocSubscription - #ifdef Iot_DefaultMalloc - #define AwsIotJobs_MallocSubscription Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotJobs_MallocSubscription" - #endif - #endif - - #ifndef AwsIotJobs_FreeSubscription - #ifdef Iot_DefaultFree - #define AwsIotJobs_FreeSubscription Iot_DefaultFree - #else - #error "No free function defined for AwsIotJobs_FreeSubscription" - #endif - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS - #define AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS ( 5000 ) -#endif -#ifndef AWS_IOT_JOBS_NOTIFY_CALLBACKS - #define AWS_IOT_JOBS_NOTIFY_CALLBACKS ( 1 ) -#endif -/** @endcond */ - -/** - * @brief The number of currently available Jobs operations. - * - * The 4 Jobs operations are GET PENDING, START NEXT, DESCRIBE, and UPDATE. - */ -#define JOBS_OPERATION_COUNT ( 4 ) - -/** - * @brief The number of currently available Jobs callbacks. - * - * The 2 Jobs callbacks are `jobs/notify` (AKA "Notify Pending") and - * `/jobs/notify-next` (AKA "Notify Next"). - */ -#define JOBS_CALLBACK_COUNT ( 2 ) - -/** - * @brief The string representing a Jobs GET PENDING operation in a Jobs MQTT topic. - */ -#define JOBS_GET_PENDING_OPERATION_STRING "/jobs/get" - -/** - * @brief The length of #JOBS_GET_PENDING_OPERATION_STRING. - */ -#define JOBS_GET_PENDING_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_GET_PENDING_OPERATION_STRING ) - 1 ) ) - -/** - * @brief The string representing a Jobs START NEXT operation in a Jobs MQTT topic. - */ -#define JOBS_START_NEXT_OPERATION_STRING "/jobs/start-next" - -/** - * @brief The length of #JOBS_START_NEXT_OPERATION_STRING. - */ -#define JOBS_START_NEXT_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_START_NEXT_OPERATION_STRING ) - 1 ) ) - -/** - * @brief The string representing a Jobs DESCRIBE operation in a Jobs MQTT topic. - * - * This string should be placed after a Job ID. - */ -#define JOBS_DESCRIBE_OPERATION_STRING "/get" - -/** - * @brief The length of #JOBS_DESCRIBE_OPERATION_STRING. - */ -#define JOBS_DESCRIBE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_DESCRIBE_OPERATION_STRING ) - 1 ) ) - -/** - * @brief The string representing a Jobs UPDATE operation in a Jobs MQTT topic. - * - * This string should be placed after a Job ID. - */ -#define JOBS_UPDATE_OPERATION_STRING "/update" - -/** - * @brief The length of #JOBS_UPDATE_OPERATION_STRING. - * - * This length excludes the length of the placeholder %s. - */ -#define JOBS_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_UPDATE_OPERATION_STRING ) - 1 ) ) - -/** - * @brief The string representing the Jobs MQTT topic for receiving notifications - * of pending Jobs. - */ -#define JOBS_NOTIFY_PENDING_CALLBACK_STRING "/jobs/notify" - -/** - * @brief The length of #JOBS_NOTIFY_PENDING_CALLBACK_STRING. - */ -#define JOBS_NOTIFY_PENDING_CALLBACK_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_NOTIFY_PENDING_CALLBACK_STRING ) - 1 ) ) - -/** - * @brief The string representing the Jobs MQTT topic for receiving notifications - * of the next pending Job. - */ -#define JOBS_NOTIFY_NEXT_CALLBACK_STRING "/jobs/notify-next" - -/** - * @brief The length of #JOBS_NOTIFY_NEXT_CALLBACK_STRING. - */ -#define JOBS_NOTIFY_NEXT_CALLBACK_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_NOTIFY_NEXT_CALLBACK_STRING ) - 1 ) ) - -/** - * @brief The maximum length of a Job ID, per AWS IoT Service Limits. - * - * See https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits - */ -#define JOBS_MAX_ID_LENGTH ( 64 ) - -/** - * @brief The maximum value of the Jobs step timeout, per AWS IoT Service Limits. - * - * See https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits - */ -#define JOBS_MAX_TIMEOUT ( 10080 ) - -/** - * @brief A limit on the maximum length of a Jobs status details, per AWS IoT - * Service Limits. - * - * See https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits - * - * This is actually the limit on the length of an entire Jobs document; but the - * status details must also not exceed this length, - */ -#define JOBS_MAX_STATUS_DETAILS_LENGTH ( 32768 ) - -/** - * @brief The length of the longest Jobs topic suffix. - * - * This is the length of the longest Job ID, plus the length of the "UPDATE" - * operation suffix, plus the length of "/jobs/". - */ -#define JOBS_LONGEST_SUFFIX_LENGTH ( JOBS_MAX_ID_LENGTH + JOBS_UPDATE_OPERATION_STRING_LENGTH + 6 ) - -/*------------------------ Jobs internal data types -------------------------*/ - -/** - * @brief Enumerations representing each of the Jobs library's API functions. - */ -typedef enum _jobsOperationType -{ - /* Jobs operations. */ - JOBS_GET_PENDING = 0, /**< @ref jobs_function_getpendingasync */ - JOBS_START_NEXT = 1, /**< @ref jobs_function_startnextasync */ - JOBS_DESCRIBE = 2, /**< @ref jobs_function_describeasync */ - JOBS_UPDATE = 3, /**< @ref jobs_function_updateasync */ - - /* Jobs callbacks. */ - SET_NOTIFY_PENDING_CALLBACK = 4, /**< @ref jobs_function_setnotifypendingcallback */ - SET_NOTIFY_NEXT_CALLBACK = 5 /**< @ref jobs_function_setnotifynextcallback */ -} _jobsOperationType_t; - -/** - * @brief Enumerations representing each of the Jobs callback functions. - */ -typedef enum _jobsCallbackType -{ - NOTIFY_PENDING_CALLBACK = 0, /**< Pending Job notification callback. */ - NOTIFY_NEXT_CALLBACK = 1 /**< Next Job notification callback. */ -} _jobsCallbackType_t; - -/** - * @brief Parameter to #_AwsIotJobs_GenerateJsonRequest. - */ -typedef union _jsonRequestContents -{ - const AwsIotJobsUpdateInfo_t * pUpdateInfo; /**< @brief Valid for #JOBS_START_NEXT and #JOBS_UPDATE. */ - - struct - { - int32_t executionNumber; /**< @brief Execution number. */ - bool includeJobDocument; /**< @brief Whether the response should include the Job document. */ - } describe; /**< @brief Valid for #JOBS_DESCRIBE. */ -} _jsonRequestContents_t; - -/** - * @brief Represents a Jobs subscriptions object. - * - * These structures are stored in a list. - */ -typedef struct _jobsSubscription -{ - IotLink_t link; /**< @brief List link member. */ - - int32_t operationReferences[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counters for Jobs operation topics. */ - int32_t callbackReferences; /**< @brief Reference counter for Jobs callbacks. */ - - /** @brief Jobs callbacks for this Thing. */ - AwsIotJobsCallbackInfo_t callbacks[ JOBS_CALLBACK_COUNT ][ AWS_IOT_JOBS_NOTIFY_CALLBACKS ]; - - /** - * @brief Buffer allocated for removing Jobs topics. - * - * This buffer is pre-allocated to ensure that memory is available when - * unsubscribing. - */ - char * pTopicBuffer; - - size_t thingNameLength; /**< @brief Length of Thing Name. */ - char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ -} _jobsSubscription_t; - -/** - * @brief Internal structure representing a single Jobs operation. - * - * A list of these structures keeps track of all in-progress Jobs operations. - */ -typedef struct _jobsOperation -{ - IotLink_t link; /**< @brief List link member. */ - - /* Basic operation information. */ - _jobsOperationType_t type; /**< @brief Operation type. */ - uint32_t flags; /**< @brief Flags passed to operation API function. */ - AwsIotJobsError_t status; /**< @brief Status of operation. */ - - IotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ - _jobsSubscription_t * pSubscription; /**< @brief Jobs subscriptions object associated with this operation. */ - - /* Jobs request information. */ - const char * pJobsRequest; /**< @brief JSON document to send to the Jobs service. */ - size_t jobsRequestLength; /**< @brief Length of #_jobsOperation_t.pJobsRequest. */ - - const char * pClientToken; /**< @brief Client token sent with request. */ - size_t clientTokenLength; /**< @brief Length of #_jobsOperation_t.pClientToken. */ - - /* Jobs response information. */ - const char * pJobsResponse; /**< @brief Response received from the Jobs service. */ - size_t jobsResponseLength; /**< @brief Length of #_jobsOperation_t.pJobsResponse. */ - - /** - * @brief Function to allocate memory for an incoming Jobs response. - * - * Only used when the flag #AWS_IOT_JOBS_FLAG_WAITABLE is set. - */ - void * ( *mallocResponse )( size_t ); - - /* How to notify of an operation's completion. */ - union - { - IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref jobs_function_wait. */ - AwsIotJobsCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ - } notify; /**< @brief How to notify of an operation's completion. */ - - size_t jobIdLength; /**< @brief Length of #_jobsOperation_t.pJobId. */ - char pJobId[]; /**< @brief Job ID, saved for DESCRIBE and UPDATE operations. */ -} _jobsOperation_t; - -/* Declarations of names printed in logs. */ -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - extern const char * const _pAwsIotJobsOperationNames[]; - extern const char * const _pAwsIotJobsCallbackNames[]; -#endif - -/* Declarations of variables for internal Jobs files. */ -extern uint32_t _AwsIotJobsMqttTimeoutMs; -extern IotListDouble_t _AwsIotJobsPendingOperations; -extern IotListDouble_t _AwsIotJobsSubscriptions; -extern IotMutex_t _AwsIotJobsPendingOperationsMutex; -extern IotMutex_t _AwsIotJobsSubscriptionsMutex; - -/*------------------------ Jobs operation functions -------------------------*/ - -/** - * @brief Create a record for a new in-progress Jobs operation. - * - * @param[in] type The type of Jobs operation for the request. - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pRequestContents Additional values to place in the JSON document, - * depending on `type`. - * @param[in] flags Flags variables passed to a user-facing Jobs function. - * @param[in] pCallbackInfo User-provided callback function and parameter. - * @param[out] pNewOperation Set to point to the new operation on success. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY - */ -AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - const _jsonRequestContents_t * pRequestContents, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - _jobsOperation_t ** pNewOperation ); - -/** - * @brief Free resources used to record a Jobs operation. This is called when - * the operation completes. - * - * @param[in] pData The operation which completed. This parameter is of type - * `void*` to match the signature of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -void _AwsIotJobs_DestroyOperation( void * pData ); - -/** - * @brief Fill a buffer with a Jobs topic. - * - * @param[in] type One of: GET PENDING, START NEXT, DESCRIBE, or UPDATE. - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[out] pTopicBuffer Address of the buffer for the Jobs topic. If the - * pointer at this address is `NULL`, this function will allocate a new buffer; - * otherwise, it will use the provided buffer. - * @param[out] pOperationTopicLength Length of the Jobs operation topic (excluding - * any suffix) placed in `pTopicBuffer`. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must be large enough to accommodate the full Jobs topic, plus - * #JOBS_LONGEST_SUFFIX_LENGTH. - * - * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY. This function - * will not return #AWS_IOT_JOBS_NO_MEMORY when a buffer is provided. - */ -AwsIotJobsError_t _AwsIotJobs_GenerateJobsTopic( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - char ** pTopicBuffer, - uint16_t * pOperationTopicLength ); - -/** - * @brief Process a Jobs operation by sending the necessary MQTT packets. - * - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pOperation Operation data to process. - * - * @return #AWS_IOT_JOBS_STATUS_PENDING on success. On error, one of - * #AWS_IOT_JOBS_NO_MEMORY or #AWS_IOT_JOBS_MQTT_ERROR. - */ -AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * pRequestInfo, - _jobsOperation_t * pOperation ); - -/*----------------------- Jobs subscription functions -----------------------*/ - -/** - * @brief Find a Jobs subscription object. May create a new subscription object - * and adds it to the subscription list if not found. - * - * @param[in] pThingName Thing Name in the subscription object. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] createIfNotFound If `true`, attempt to create a new subscription - * object if no match is found. - * - * @return Pointer to a Jobs subscription object, either found or newly - * allocated. Returns `NULL` if no subscription object is found and a new - * subscription object could not be allocated. - * - * @note This function should be called with the subscription list mutex locked. - */ -_jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, - size_t thingNameLength, - bool createIfNotFound ); - -/** - * @brief Remove a Jobs subscription object from the subscription list if - * unreferenced. - * - * @param[in] pSubscription Subscription object to check. If this object has no - * active references, it is removed from the subscription list. - * @param[out] pRemovedSubscription Removed subscription object, if any. Optional; - * pass `NULL` to ignore. If not `NULL`, this parameter will be set to the removed - * subscription and that subscription will not be destroyed. - * - * @note This function should be called with the subscription list mutex locked. - */ -void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, - _jobsSubscription_t ** pRemovedSubscription ); - -/** - * @brief Free resources used for a Jobs subscription object. - * - * @param[in] pData The subscription object to destroy. This parameter is of type - * `void*` to match the signature of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -void _AwsIotJobs_DestroySubscription( void * pData ); - -/** - * @brief Increment the reference count of a Jobs subscriptions object. - * - * Also adds MQTT subscriptions if necessary. - * - * @param[in] pOperation The operation for which the reference count should be - * incremented. - * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if - * subscriptions need to be added. - * @param[in] operationTopicLength The length of the operation topic in `pTopicBuffer`. - * @param[in] callback MQTT callback function for when this operation completes. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must already contain the Jobs operation topic, plus enough space for the - * status suffix. - * - * @return #AWS_IOT_JOBS_SUCCESS on success. On error, one of - * #AWS_IOT_JOBS_NO_MEMORY or #AWS_IOT_JOBS_MQTT_ERROR. - * - * @note This function should be called with the subscription list mutex locked. - */ -AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation, - char * pTopicBuffer, - uint16_t operationTopicLength, - AwsIotMqttCallbackFunction_t callback ); - -/** - * @brief Decrement the reference count of a Jobs subscriptions object. - * - * Also removed MQTT subscriptions and deletes the subscription object if necessary. - * - * @param[in] pOperation The operation for which the reference count should be - * decremented. - * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if - * subscriptions need to be removed. - * @param[out] pRemovedSubscription Set to point to a removed subscription. - * Optional; pass `NULL` to ignore. If not `NULL`, this function will not destroy - * a removed subscription. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must be large enough to accommodate the full Jobs topic, plus - * #JOBS_LONGEST_SUFFIX_LENGTH. - * - * @note This function should be called with the subscription list mutex locked. - */ -void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, - char * pTopicBuffer, - _jobsSubscription_t ** pRemovedSubscription ); - -/*------------------------ Jobs serializer functions ------------------------*/ - -/** - * @brief Generates a Jobs JSON request document from an #AwsIotJobsRequestInfo_t - * and an #AwsIotJobsUpdateInfo_t. - * - * @param[in] type The type of Jobs operation for the request. - * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pRequestContents Additional values to place in the JSON document, - * depending on `type`. - * @param[in] pOperation Operation associated with the Jobs request. - * - * @return #AWS_IOT_JOBS_SUCCESS on success; otherwise, #AWS_IOT_JOBS_NO_MEMORY. - */ -AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - const _jsonRequestContents_t * pRequestContents, - _jobsOperation_t * pOperation ); - -/** - * @brief Parse a response received from the Jobs service. - * - * @param[in] status Either ACCEPTED or REJECTED. - * @param[in] pResponse The response received from the Jobs service. - * @param[in] responseLength Length of `pResponse`. - * @param[out] pOperation Associated Jobs operation, where parse results are - * written. - */ -void _AwsIotJobs_ParseResponse( AwsIotStatus_t status, - const char * pResponse, - size_t responseLength, - _jobsOperation_t * pOperation ); - -#endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/libraries/aws/jobs/test/aws_iot_tests_jobs.c b/libraries/aws/jobs/test/aws_iot_tests_jobs.c deleted file mode 100644 index d072bce191..0000000000 --- a/libraries/aws/jobs/test/aws_iot_tests_jobs.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_jobs.c - * @brief Test runner for Jobs tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the Jobs test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunJobsTests( bool disableNetworkTests, - bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableLongTests; - - RUN_TEST_GROUP( Jobs_Unit_API ); - RUN_TEST_GROUP( Jobs_Unit_Serialize ); - - if( disableNetworkTests == false ) - { - RUN_TEST_GROUP( Jobs_System ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c deleted file mode 100644 index 0cfe67211b..0000000000 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ /dev/null @@ -1,867 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_jobs_system.c - * @brief Full system tests for the AWS IoT Jobs library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Jobs include. */ -#include "private/aws_iot_jobs_internal.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S - #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) -#endif -#ifndef AWS_IOT_TEST_JOBS_TIMEOUT - #define AWS_IOT_TEST_JOBS_TIMEOUT ( 5000 ) -#endif -/** @endcond */ - -/* Thing Name must be defined for these tests. */ -#ifndef AWS_IOT_TEST_JOBS_THING_NAME - #error "Please define AWS_IOT_TEST_JOBS_THING_NAME." -#endif - -/* Require Jobs library asserts to be enabled for these tests. The Jobs - * assert function is used to abort the tests on failure from the Jobs operation - * complete callback. */ -#if AWS_IOT_JOBS_ENABLE_ASSERTS == 0 - #error "Jobs system tests require AWS_IOT_JOBS_ENABLE_ASSERTS to be 1." -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Parameter 1 of #_operationComplete. - */ -typedef struct _operationCompleteParams -{ - AwsIotJobsCallbackType_t expectedType; /**< @brief Expected callback type. */ - AwsIotJobsError_t expectedResult; /**< @brief Expected operation result. */ - IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ - AwsIotJobsOperation_t operation; /**< @brief Reference to expected completed operation. */ -} _operationCompleteParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Network server info to share among the tests. - */ -static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - -/** - * @brief Network credential info to share among the tests. - */ -#if IOT_TEST_SECURED_CONNECTION == 1 - static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -#endif - -/** - * @brief An MQTT connection to share among the tests. - */ -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief Job IDs retrieved from the AWS IoT Jobs service. - */ -static char _pJobIds[ 2 ][ JOBS_MAX_ID_LENGTH + 1 ] = { { 0 } }; - -/** - * @brief Lengths of the Job IDs retrieved from the AWS IoT Jobs service. - */ -static size_t _pJobIdLengths[ 2 ] = { 0 }; - -/** - * @brief Identifies which of the two Jobs is set to status IN_PROGRESS. - */ -static uint32_t _inProgressJob = 0; - -/*-----------------------------------------------------------*/ - -/** - * @brief Jobs operation completion callback function. Checks parameters - * and unblocks the main test thread. - */ -static void _operationComplete( void * pArgument, - AwsIotJobsCallbackParam_t * pOperation ) -{ - _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; - - /* Silence warnings when asserts are enabled. */ - ( void ) pOperation; - - /* Check parameters against expected values. */ - AwsIotJobs_Assert( pParams->expectedType == pOperation->callbackType ); - AwsIotJobs_Assert( pParams->operation == pOperation->u.operation.reference ); - AwsIotJobs_Assert( pParams->expectedResult == pOperation->u.operation.result ); - - AwsIotJobs_Assert( pOperation->mqttConnection == _mqttConnection ); - AwsIotJobs_Assert( pOperation->thingNameLength == sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - AwsIotJobs_Assert( strncmp( pOperation->pThingName, - AWS_IOT_TEST_JOBS_THING_NAME, - pOperation->thingNameLength ) == 0 ); - - AwsIotJobs_Assert( pOperation->u.operation.pResponse != NULL ); - AwsIotJobs_Assert( pOperation->u.operation.responseLength > 0 ); - - /* Unblock the main test thread. */ - IotSemaphore_Post( &( pParams->waitSem ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Jobs GET PENDING and GET NEXT callback. Checks parameters and unblocks - * the main test thread. - */ -static void _jobsCallback( void * pArgument, - AwsIotJobsCallbackParam_t * pCallbackParam ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - uint32_t checkJobId = 0; - const char * pJobId = NULL; - size_t jobIdLength = 0; - - /* Silence warnings when asserts are disabled. */ - ( void ) pCallbackParam; - ( void ) pJobId; - ( void ) jobIdLength; - - /* Check parameters against expected values. */ - AwsIotJobs_Assert( pCallbackParam->mqttConnection == _mqttConnection ); - AwsIotJobs_Assert( pCallbackParam->thingNameLength == sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - AwsIotJobs_Assert( strncmp( pCallbackParam->pThingName, - AWS_IOT_TEST_JOBS_THING_NAME, - pCallbackParam->thingNameLength ) == 0 ); - - /* Check the Job ID that was not previously IN_PROGRESS. */ - if( _inProgressJob == 0 ) - { - checkJobId = 1; - } - else - { - checkJobId = 0; - } - - /* Parse the next Job ID. */ - AwsIotJobs_Assert( AwsIotDocParser_FindValue( pCallbackParam->u.callback.pDocument, - pCallbackParam->u.callback.documentLength, - "jobId", - 5, - &pJobId, - &jobIdLength ) == true ); - - /* Verify that the previously queued Job is next. */ - AwsIotJobs_Assert( jobIdLength - 2 == _pJobIdLengths[ checkJobId ] ); - AwsIotJobs_Assert( strncmp( _pJobIds[ checkJobId ], - pJobId + 1, - _pJobIdLengths[ checkJobId ] ) == 0 ); - - /* Unblock the main test thread. */ - IotSemaphore_Post( pWaitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Parses Job IDs from a GET PENDING Jobs response. - */ -static void _parseJobIds( const AwsIotJobsResponse_t * pJobsResponse ) -{ - bool status = true; - int32_t i = 0; - const char * pInProgressJobs = NULL, * pParseStart = NULL, * pJobId = NULL; - size_t inProgressJobsLength = 0, parseLength = 0, jobIdLength = 0; - - /* In-progress Jobs for this device will interfere with the tests; fail if - * any in-progress Jobs are present. */ - status = AwsIotDocParser_FindValue( pJobsResponse->pJobsResponse, - pJobsResponse->jobsResponseLength, - "inProgressJobs", 14, - &pInProgressJobs, - &inProgressJobsLength ); - TEST_ASSERT_EQUAL_INT( true, status ); - TEST_ASSERT_NOT_NULL( pInProgressJobs ); - TEST_ASSERT_EQUAL_MESSAGE( 2, inProgressJobsLength, "In-progress Jobs detected. Tests will not run." ); - - /* Parse for the list of queued Jobs. This is where parsing for Job IDs will - * start. */ - status = AwsIotDocParser_FindValue( pJobsResponse->pJobsResponse, - pJobsResponse->jobsResponseLength, - "queuedJobs", - 10, - &pParseStart, - &parseLength ); - TEST_ASSERT_EQUAL_INT_MESSAGE( true, status, "Response did not contain any queued Jobs." ); - TEST_ASSERT_NOT_NULL( pParseStart ); - TEST_ASSERT_GREATER_THAN( 0, parseLength ); - - /* Parse the Job IDs of the first two queued Jobs. */ - for( i = 0; i < 2; i++ ) - { - status = AwsIotDocParser_FindValue( pParseStart, - parseLength, - "jobId", - 5, - &pJobId, - &jobIdLength ); - TEST_ASSERT_EQUAL_INT_MESSAGE( true, status, "Response did not contain enough queued Jobs." ); - TEST_ASSERT_NOT_NULL( pJobId ); - TEST_ASSERT_GREATER_THAN( 0, jobIdLength ); - TEST_ASSERT_LESS_THAN_MESSAGE( JOBS_MAX_ID_LENGTH, - jobIdLength - 2, - "Response contains a Job ID that is too long." ); - - /* Job ID must start and end with quotes, as it is a string. */ - TEST_ASSERT_EQUAL( '"', *pJobId ); - TEST_ASSERT_EQUAL( '"', *( pJobId + jobIdLength - 1 ) ); - - /* Copy the Job ID, excluding the quotes. Save its length too. */ - ( void ) memcpy( _pJobIds[ i ], pJobId + 1, jobIdLength - 2 ); - _pJobIdLengths[ i ] = jobIdLength - 2; - - /* To find the next Job ID, it's sufficient to search again after the current one. */ - parseLength -= ( pJobId - pParseStart ); - pParseStart = pJobId; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Common code of the Jobs Async tests. - */ -static void _jobsAsyncTest( _jobsOperationType_t type, - AwsIotJobsError_t expectedResult ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { .expectedType = ( AwsIotJobsCallbackType_t ) 0 }; - - /* Initialize the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Set the callback information. */ - callbackParam.expectedType = ( AwsIotJobsCallbackType_t ) type; - callbackParam.expectedResult = expectedResult; - callbackInfo.function = _operationComplete; - callbackInfo.pCallbackContext = &callbackParam; - - /* Set the Jobs request parameters. */ - requestInfo.mqttConnection = _mqttConnection; - requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; - requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - - /* Call Jobs function. */ - switch( type ) - { - case JOBS_GET_PENDING: - status = AwsIotJobs_GetPendingAsync( &requestInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - break; - - case JOBS_START_NEXT: - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - break; - - case JOBS_DESCRIBE: - status = AwsIotJobs_DescribeAsync( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - true, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - break; - - default: - /* The only remaining valid type is UPDATE. */ - TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); - - /* Set a Job ID that doesn't exist. */ - requestInfo.pJobId = _pJobIds[ 0 ]; - requestInfo.jobIdLength = _pJobIdLengths[ 0 ]; - - status = AwsIotJobs_UpdateAsync( &requestInfo, - &updateInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - break; - } - - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_STATUS_PENDING, status ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for pending Jobs." ); - } - } - - IotSemaphore_Destroy( &( callbackParam.waitSem ) ); -} - -/*-----------------------------------------------------------*/ - -static void _jobsBlockingTest( _jobsOperationType_t type, - AwsIotJobsError_t expectedResult ) -{ - int32_t i = 0; - bool jobIdMatch = false; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - const char * pJobId = NULL; - size_t jobIdLength = 0; - - /* Set the Jobs request parameters. */ - requestInfo.mqttConnection = _mqttConnection; - requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; - requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - requestInfo.mallocResponse = IotTest_Malloc; - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - - /* Call Jobs function. */ - switch( type ) - { - case JOBS_GET_PENDING: - status = AwsIotJobs_GetPendingSync( &requestInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); - break; - - case JOBS_START_NEXT: - status = AwsIotJobs_StartNextSync( &requestInfo, - &updateInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); - break; - - case JOBS_DESCRIBE: - status = AwsIotJobs_DescribeSync( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - true, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); - break; - - default: - /* The only remaining valid type is UPDATE. */ - TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); - - requestInfo.pJobId = _pJobIds[ _inProgressJob ]; - requestInfo.jobIdLength = _pJobIdLengths[ _inProgressJob ]; - - status = AwsIotJobs_UpdateSync( &requestInfo, - &updateInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); - break; - } - - TEST_ASSERT_EQUAL( expectedResult, status ); - - /* Check the Jobs response. */ - TEST_ASSERT_NOT_NULL( jobsResponse.pJobsResponse ); - TEST_ASSERT_GREATER_THAN( 0, jobsResponse.jobsResponseLength ); - - /* Save the list of queued Jobs. */ - if( type == JOBS_GET_PENDING ) - { - _parseJobIds( &jobsResponse ); - - /* Job IDs must be unique; check that the parsed IDs are different. */ - if( _pJobIdLengths[ 0 ] == _pJobIdLengths[ 1 ] ) - { - TEST_ASSERT_NOT_EQUAL( 0, strncmp( _pJobIds[ 0 ], - _pJobIds[ 1 ], - _pJobIdLengths[ 0 ] ) ); - } - } - else - { - /* Check that the Job ID matches one of the queued jobs. Don't check for - * UPDATE; its response does not include the Job ID. */ - if( type != JOBS_UPDATE ) - { - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( jobsResponse.pJobsResponse, - jobsResponse.jobsResponseLength, - "jobId", - 5, - &pJobId, - &jobIdLength ) ); - - for( i = 0; i < 2; i++ ) - { - if( _pJobIdLengths[ i ] == jobIdLength - 2 ) - { - if( strncmp( _pJobIds[ i ], pJobId + 1, jobIdLength - 2 ) == 0 ) - { - jobIdMatch = true; - - /* Mark which Job was started. */ - if( type == JOBS_START_NEXT ) - { - _inProgressJob = i; - } - - break; - } - } - } - - TEST_ASSERT_EQUAL_INT( true, jobIdMatch ); - } - } - - /* Free the allocated Jobs response. */ - IotTest_Free( ( void * ) jobsResponse.pJobsResponse ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Initializes libraries and establishes an MQTT connection for the Jobs tests. - */ -static void _setupJobsTests() -{ - int32_t i = 0; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* Initialize SDK and libraries. */ - AwsIotJobs_Assert( IotSdk_Init() == true ); - AwsIotJobs_Assert( IotTestNetwork_Init() == IOT_NETWORK_SUCCESS ); - AwsIotJobs_Assert( IotMqtt_Init() == IOT_MQTT_SUCCESS ); - AwsIotJobs_Assert( AwsIotJobs_Init( 0 ) == AWS_IOT_JOBS_SUCCESS ); - - /* Set the MQTT network setup parameters. */ - ( void ) memset( &networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - - #if IOT_TEST_SECURED_CONNECTION == 1 - networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; - #endif - - #ifdef IOT_TEST_MQTT_SERIALIZER - networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; - #endif - - /* Set the members of the connect info. Use the Jobs Thing Name as the MQTT - * client identifier. */ - connectInfo.awsIotMqttMode = true; - connectInfo.pClientIdentifier = AWS_IOT_TEST_JOBS_THING_NAME; - connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - - /* Establish an MQTT connection. Allow up to 3 attempts with a 5 second wait - * if the connection fails. */ - for( i = 0; i < 3; i++ ) - { - connectStatus = IotMqtt_Connect( &networkInfo, - &connectInfo, - AWS_IOT_TEST_JOBS_TIMEOUT, - &_mqttConnection ); - - if( ( connectStatus == IOT_MQTT_TIMEOUT ) || ( connectStatus == IOT_MQTT_NETWORK_ERROR ) ) - { - IotClock_SleepMs( 5000 ); - } - else - { - break; - } - } - - AwsIotJobs_Assert( connectStatus == IOT_MQTT_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Cleans up libraries and closes the MQTT connection for the Jobs tests. - */ -static void _cleanupJobsTests() -{ - /* Disconnect the MQTT connection if it was created. */ - if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) - { - IotMqtt_Disconnect( _mqttConnection, 0 ); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - } - - /* Clean up the Jobs library. */ - AwsIotJobs_Cleanup(); - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); - - /* Clean up the network stack. */ - IotTestNetwork_Cleanup(); - - /* Clean up SDK. */ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Jobs system tests. - */ -TEST_GROUP( Jobs_System ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Jobs system tests. - */ -TEST_SETUP( Jobs_System ) -{ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Jobs system tests. - */ -TEST_TEAR_DOWN( Jobs_System ) -{ - /* Cool down time to avoid making too many requests. */ - IotClock_SleepMs( 100 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Jobs system tests. - */ -TEST_GROUP_RUNNER( Jobs_System ) -{ - _setupJobsTests(); - - /* The tests for Get Pending must run first, as they retrieve the list of - * Jobs for the other tests. */ - RUN_TEST_CASE( Jobs_System, GetPendingAsync ); - RUN_TEST_CASE( Jobs_System, GetPendingBlocking ); - - /* Only run the following tests if 2 queued Jobs are available. */ - if( ( _pJobIdLengths[ 0 ] > 0 ) && ( _pJobIdLengths[ 1 ] > 0 ) ) - { - RUN_TEST_CASE( Jobs_System, StartNextAsync ); - RUN_TEST_CASE( Jobs_System, StartNextBlocking ); - RUN_TEST_CASE( Jobs_System, DescribeAsync ); - RUN_TEST_CASE( Jobs_System, DescribeBlocking ); - RUN_TEST_CASE( Jobs_System, UpdateAsync ); - RUN_TEST_CASE( Jobs_System, UpdateBlocking ); - RUN_TEST_CASE( Jobs_System, JobsCallbacks ); - } - - RUN_TEST_CASE( Jobs_System, PersistentSubscriptions ); - - _cleanupJobsTests(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Retrieves a list of Jobs using @ref jobs_function_getpendingasync. - */ -TEST( Jobs_System, GetPendingAsync ) -{ - _jobsAsyncTest( JOBS_GET_PENDING, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Retrieves a list of Jobs using @ref jobs_function_getpendingsync. - */ -TEST( Jobs_System, GetPendingBlocking ) -{ - _jobsBlockingTest( JOBS_GET_PENDING, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Starts the next Job using @ref jobs_function_startnextasync. - */ -TEST( Jobs_System, StartNextAsync ) -{ - _jobsAsyncTest( JOBS_START_NEXT, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Starts the next Job using @ref jobs_function_startnextsync. - */ -TEST( Jobs_System, StartNextBlocking ) -{ - _jobsBlockingTest( JOBS_START_NEXT, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Describe a Job using @ref jobs_function_describeasync. - */ -TEST( Jobs_System, DescribeAsync ) -{ - _jobsAsyncTest( JOBS_DESCRIBE, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Describe a Job using @ref jobs_function_describesync. - */ -TEST( Jobs_System, DescribeBlocking ) -{ - _jobsBlockingTest( JOBS_DESCRIBE, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Update a Job status using @ref jobs_function_updateasync. - */ -TEST( Jobs_System, UpdateAsync ) -{ - _jobsAsyncTest( JOBS_UPDATE, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Update a Job status using @ref jobs_function_updatesync. - */ -TEST( Jobs_System, UpdateBlocking ) -{ - _jobsBlockingTest( JOBS_UPDATE, AWS_IOT_JOBS_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests a Job operation with perisistent subscriptions. - */ -TEST( Jobs_System, PersistentSubscriptions ) -{ - uint64_t startTime = 0, elapsedTime1 = 0, elapsedTime2 = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - - /* Set the Jobs request parameters. */ - requestInfo.mqttConnection = _mqttConnection; - requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; - requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - requestInfo.mallocResponse = IotTest_Malloc; - - /* Time a Jobs function that sets persistent subscriptions. */ - startTime = IotClock_GetTimeMs(); - status = AwsIotJobs_GetPendingSync( &requestInfo, - AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS, - AWS_IOT_TEST_JOBS_TIMEOUT, - &response ); - elapsedTime1 = IotClock_GetTimeMs() - startTime; - - /* Check results. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - TEST_ASSERT_NOT_NULL( response.pJobsResponse ); - TEST_ASSERT_GREATER_THAN( 0, response.jobsResponseLength ); - - IotTest_Free( ( void * ) response.pJobsResponse ); - - /* Time a Jobs functions that has persistent subscriptions set. */ - startTime = IotClock_GetTimeMs(); - status = AwsIotJobs_GetPendingSync( &requestInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &response ); - elapsedTime2 = IotClock_GetTimeMs() - startTime; - - /* Check results */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - TEST_ASSERT_NOT_NULL( response.pJobsResponse ); - TEST_ASSERT_GREATER_THAN( 0, response.jobsResponseLength ); - - IotTest_Free( ( void * ) response.pJobsResponse ); - - /* Because the second operation has persistent subscriptions and does not - * need to subscribe to anything, it should be significantly faster. */ - TEST_ASSERT_LESS_THAN( elapsedTime1, elapsedTime2 ); - - /* Remove persistent subscriptions. */ - status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, - AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the Jobs callbacks. - */ -TEST( Jobs_System, JobsCallbacks ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - IotSemaphore_t waitSem; - - /* Create the wait semaphore. */ - IotSemaphore_Create( &waitSem, 0, 2 ); - - /* Set the callback function and context. */ - callbackInfo.pCallbackContext = &waitSem; - callbackInfo.function = _jobsCallback; - - /* Set the Jobs callbacks to notify of Jobs changes. */ - status = AwsIotJobs_SetNotifyNextCallback( _mqttConnection, - AWS_IOT_TEST_JOBS_THING_NAME, - sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, - 0, - &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - status = AwsIotJobs_SetNotifyPendingCallback( _mqttConnection, - AWS_IOT_TEST_JOBS_THING_NAME, - sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, - 0, - &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Set a Job status to completed. This should trigger both Jobs - * callbacks. */ - requestInfo.pJobId = _pJobIds[ _inProgressJob ]; - requestInfo.jobIdLength = _pJobIdLengths[ _inProgressJob ]; - requestInfo.mallocResponse = IotTest_Malloc; - requestInfo.mqttConnection = _mqttConnection; - requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; - requestInfo.thingNameLength = sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1; - - updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; - - status = AwsIotJobs_UpdateSync( &requestInfo, - &updateInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &response ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - IotTest_Free( ( void * ) response.pJobsResponse ); - - /* Wait for both callbacks to be invoked. */ - if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for Jobs callback." ); - } - - if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for Jobs callback." ); - } - - /* Remove Jobs callbacks. */ - callbackInfo.function = NULL; - callbackInfo.oldFunction = _jobsCallback; - - status = AwsIotJobs_SetNotifyNextCallback( _mqttConnection, - AWS_IOT_TEST_JOBS_THING_NAME, - sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, - 0, - &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - status = AwsIotJobs_SetNotifyPendingCallback( _mqttConnection, - AWS_IOT_TEST_JOBS_THING_NAME, - sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, - 0, - &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Destroy the wait semaphore. */ - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c deleted file mode 100644 index 81f3a6e807..0000000000 --- a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c +++ /dev/null @@ -1,988 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_jobs_api.c - * @brief Tests for the user-facing API functions (declared in aws_iot_jobs.h). - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* MQTT mock include. */ -#include "iot_tests_mqtt_mock.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/** - * @brief Whether to check the number of MQTT library errors in the malloc - * failure tests. - * - * Should only be checked if malloc overrides are available and not testing for - * code coverage. In static memory mode, there should be no MQTT library errors. - */ -#if ( IOT_TEST_COVERAGE == 1 ) || ( IOT_TEST_NO_MALLOC_OVERRIDES == 1 ) - #define CHECK_MQTT_ERROR_COUNT( expected, actual ) -#else - #if ( IOT_STATIC_MEMORY_ONLY == 1 ) - #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( 0, actual ) - #else - #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( expected, actual ) - #endif -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief The Thing Name shared among all the tests. - */ -#define TEST_THING_NAME "TestThingName" - -/** - * @brief The length of #TEST_THING_NAME. - */ -#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) - -/** - * @brief A non-NULL callback function that can be tested by Jobs, but is not - * expected to be invoked. - */ -#define JOBS_CALLBACK_FUNCTION ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x01 ) - -/** - * @brief Another non-NULL callback function that can be tested by Jobs, but is not - * expected to be invoked. - */ -#define JOBS_CALLBACK_FUNCTION_2 ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x02 ) - -/** - * @brief A third non-NULL callback function that can be tested by Jobs, but is not - * expected to be invoked. - */ -#define JOBS_CALLBACK_FUNCTION_3 ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x03 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief The MQTT connection shared among the tests. - */ -static IotMqttConnection_t _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*-----------------------------------------------------------*/ - -/** - * @brief Common code of the MallocFail tests. - */ -static void _jobsMallocFail( _jobsOperationType_t type ) -{ - int32_t i = 0, mqttErrorCount = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - - /* Set a short timeout so this test runs faster. */ - _AwsIotJobsMqttTimeoutMs = 75; - - /* Set the members of the request info. */ - requestInfo.mqttConnection = _pMqttConnection; - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - requestInfo.mallocResponse = IotTest_Malloc; - requestInfo.pJobId = "jobid"; - requestInfo.jobIdLength = 5; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Jobs operation. Memory allocation will fail at various times - * during this call. */ - switch( type ) - { - case JOBS_GET_PENDING: - status = AwsIotJobs_GetPendingAsync( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); - break; - - case JOBS_START_NEXT: - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); - break; - - case JOBS_DESCRIBE: - status = AwsIotJobs_DescribeAsync( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - false, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); - break; - - default: - /* The only remaining valid type is update. */ - TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); - - status = AwsIotJobs_UpdateAsync( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); - break; - } - - /* Once the Jobs operation call succeeds, wait for it to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - /* No response will be received from the network, so the Jobs operation - * is expected to time out. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, - AwsIotJobs_Wait( operation, - 0, - &jobsResponse ) ); - break; - } - - /* Count the number of MQTT library errors. Otherwise, check that the error - * is a "No memory" error. */ - if( status == AWS_IOT_JOBS_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); - } - } - - /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH). */ - CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Searches a Job subscription object for a callback function. - */ -static bool _checkForCallback( const _jobsSubscription_t * pSubscription, - _jobsCallbackType_t type, - void ( *callbackFunction )( void *, AwsIotJobsCallbackParam_t * ) ) -{ - int32_t i = 0; - bool callbackFound = false; - - for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) - { - if( pSubscription->callbacks[ type ][ i ].function == callbackFunction ) - { - callbackFound = true; - break; - } - } - - return callbackFound; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Jobs API tests. - */ -TEST_GROUP( Jobs_Unit_API ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Jobs API tests. - */ -TEST_SETUP( Jobs_Unit_API ) -{ - /* Initialize SDK. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - - /* Initialize the Jobs library. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); - - /* Initialize MQTT mock. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_pMqttConnection ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Jobs API tests. - */ -TEST_TEAR_DOWN( Jobs_Unit_API ) -{ - /* Clean up MQTT mock. */ - IotTest_MqttMockCleanup(); - - /* Clean up libraries. */ - AwsIotJobs_Cleanup(); - IotMqtt_Cleanup(); - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Jobs API tests. - */ -TEST_GROUP_RUNNER( Jobs_Unit_API ) -{ - RUN_TEST_CASE( Jobs_Unit_API, Init ); - RUN_TEST_CASE( Jobs_Unit_API, StringCoverage ); - RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidRequestInfo ); - RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidUpdateInfo ); - RUN_TEST_CASE( Jobs_Unit_API, WaitInvalidParameters ); - RUN_TEST_CASE( Jobs_Unit_API, GetPendingMallocFail ); - RUN_TEST_CASE( Jobs_Unit_API, StartNextMallocFail ); - RUN_TEST_CASE( Jobs_Unit_API, DescribeMallocFail ); - RUN_TEST_CASE( Jobs_Unit_API, UpdateMallocFail ); - RUN_TEST_CASE( Jobs_Unit_API, RemovePersistentSubscriptions ); - RUN_TEST_CASE( Jobs_Unit_API, SetCallback ); - RUN_TEST_CASE( Jobs_Unit_API, SetCallbackMultiple ); - RUN_TEST_CASE( Jobs_Unit_API, SetCallbackMallocFail ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the function @ref jobs_function_init. - */ -TEST( Jobs_Unit_API, Init ) -{ - int32_t i = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; - - /* Check that test set up set the default value. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotJobsMqttTimeoutMs ); - - /* The Jobs library was already initialized by test set up. Clean it up - * before running this test. */ - AwsIotJobs_Cleanup(); - - /* Calling cleanup twice should not crash. */ - AwsIotJobs_Cleanup(); - - /* Set a MQTT timeout. */ - AwsIotJobs_Init( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + 1 ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotJobsMqttTimeoutMs ); - - /* Cleanup should restore the default MQTT timeout. */ - AwsIotJobs_Cleanup(); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotJobsMqttTimeoutMs ); - - /* Calling API functions without calling AwsIotJobs_Init should fail. */ - requestInfo.mqttConnection = _pMqttConnection; - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - status = AwsIotJobs_GetPendingAsync( &requestInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - status = AwsIotJobs_StartNextAsync( &requestInfo, &updateInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - status = AwsIotJobs_DescribeAsync( &requestInfo, AWS_IOT_JOBS_NO_EXECUTION_NUMBER, false, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - status = AwsIotJobs_UpdateAsync( &requestInfo, &updateInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - status = AwsIotJobs_Wait( operation, 500, &response ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - status = AwsIotJobs_SetNotifyPendingCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); - - /* Test Jobs initialization with mutex creation failures. */ - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - status = AwsIotJobs_Init( 0 ); - - /* Check that the status is either success or "INIT FAILED". */ - if( status == AWS_IOT_JOBS_SUCCESS ) - { - break; - } - - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_INIT_FAILED, status ); - } - - /* Initialize the Jobs library for test clean up. Calling init twice should not crash. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Provides code coverage of the Jobs enum-to-string functions, - * @ref jobs_function_strerror and @ref jobs_function_statename. - */ -TEST( Jobs_Unit_API, StringCoverage ) -{ - size_t i = 0; - const char * pMessage = NULL; - - const char * pInvalidStatus = "INVALID STATUS"; - size_t invalidStatusLength = strlen( pInvalidStatus ); - - /* For each Jobs Error, check the returned string. */ - const AwsIotJobsError_t pApiErrors[] = - { - AWS_IOT_JOBS_SUCCESS, AWS_IOT_JOBS_STATUS_PENDING, AWS_IOT_JOBS_INIT_FAILED, - AWS_IOT_JOBS_BAD_PARAMETER, AWS_IOT_JOBS_NO_MEMORY, AWS_IOT_JOBS_MQTT_ERROR, - AWS_IOT_JOBS_BAD_RESPONSE, AWS_IOT_JOBS_TIMEOUT, AWS_IOT_JOBS_NOT_INITIALIZED, - AWS_IOT_JOBS_INVALID_TOPIC, AWS_IOT_JOBS_INVALID_JSON, AWS_IOT_JOBS_INVALID_REQUEST, - AWS_IOT_JOBS_INVALID_STATE, AWS_IOT_JOBS_NOT_FOUND, AWS_IOT_JOBS_VERSION_MISMATCH, - AWS_IOT_JOBS_INTERNAL_ERROR, AWS_IOT_JOBS_THROTTLED, AWS_IOT_JOBS_TERMINAL_STATE - }; - - for( i = 0; i < ( sizeof( pApiErrors ) / sizeof( pApiErrors[ 0 ] ) ); i++ ) - { - pMessage = AwsIotJobs_strerror( pApiErrors[ i ] ); - TEST_ASSERT_NOT_NULL( pMessage ); - - TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); - } - - /* Check an invalid status. */ - pMessage = AwsIotJobs_strerror( ( AwsIotJobsError_t ) -1 ); - TEST_ASSERT_EQUAL_STRING( pInvalidStatus, pMessage ); - - /* For each Jobs State, check the returned string. */ - i = 0; - const char * pInvalidState = "INVALID STATE"; - size_t invalidStateLength = strlen( pInvalidState ); - - while( true ) - { - pMessage = AwsIotJobs_StateName( ( AwsIotJobState_t ) i ); - TEST_ASSERT_NOT_NULL( pMessage ); - - if( strncmp( pInvalidState, pMessage, invalidStateLength ) == 0 ) - { - break; - } - - i++; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of Jobs operation functions with various - * invalid #AwsIotJobsRequestInfo_t. - */ -TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - - /* Uninitialized MQTT connection. */ - status = AwsIotJobs_GetPendingAsync( &requestInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.mqttConnection = _pMqttConnection; - - /* Invalid Thing Name. */ - status = AwsIotJobs_GetPendingAsync( &requestInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - - /* No reference with waitable operation. */ - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Malloc function not set. */ - status = AwsIotJobs_GetPendingAsync( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.mallocResponse = IotTest_Malloc; - - /* Both callback and waitable flag set. */ - status = AwsIotJobs_GetPendingAsync( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - &callbackInfo, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Callback function not set. */ - status = AwsIotJobs_GetPendingAsync( &requestInfo, - 0, - &callbackInfo, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Client token length not set. */ - requestInfo.pClientToken = "test"; - requestInfo.clientTokenLength = 0; - status = AwsIotJobs_GetPendingAsync( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - 0, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Client token too long. */ - requestInfo.clientTokenLength = AWS_IOT_CLIENT_TOKEN_MAX_LENGTH + 1; - - status = AwsIotJobs_GetPendingAsync( &requestInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.pClientToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE; - - /* Job ID not set. */ - status = AwsIotJobs_DescribeAsync( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - false, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Job ID too long. */ - requestInfo.pJobId = "jobid"; - requestInfo.jobIdLength = JOBS_MAX_ID_LENGTH + 1; - - status = AwsIotJobs_UpdateAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Using $next with UPDATE is invalid. */ - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - - status = AwsIotJobs_UpdateAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of Jobs operation functions with various - * invalid #AwsIotJobsUpdateInfo_t. - */ -TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - - /* Set the members of the request info. */ - requestInfo.mqttConnection = _pMqttConnection; - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - requestInfo.mallocResponse = IotTest_Malloc; - - /* Negative, invalid step timeout. */ - updateInfo.stepTimeoutInMinutes = -5; - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Step timeout too large. */ - updateInfo.stepTimeoutInMinutes = JOBS_MAX_TIMEOUT + 1; - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_NO_TIMEOUT; - - /* Status details length not set. */ - updateInfo.pStatusDetails = "test"; - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Status details too large. */ - updateInfo.statusDetailsLength = JOBS_MAX_STATUS_DETAILS_LENGTH + 1; - - status = AwsIotJobs_StartNextAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - updateInfo.pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS; - - /* Invalid UPDATE state. */ - updateInfo.newStatus = AWS_IOT_JOB_STATE_QUEUED; - requestInfo.pJobId = "jobid"; - requestInfo.jobIdLength = 5; - - status = AwsIotJobs_UpdateAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - updateInfo.newStatus = AWS_IOT_JOB_STATE_IN_PROGRESS; - - /* Invalid UPDATE Job ID. */ - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - - status = AwsIotJobs_UpdateAsync( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref jobs_function_wait with various - * invalid parameters. - */ -TEST( Jobs_Unit_API, WaitInvalidParameters ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - - /* NULL reference. */ - status = AwsIotJobs_Wait( NULL, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* No waitable flag set. */ - status = AwsIotJobs_Wait( &operation, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* NULL output parameter. */ - operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; - status = AwsIotJobs_Wait( &operation, 0, NULL ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref jobs_function_getpendingasync when memory - * allocation fails at various points. - */ -TEST( Jobs_Unit_API, GetPendingMallocFail ) -{ - _jobsMallocFail( JOBS_GET_PENDING ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref jobs_function_startnextasync when memory - * allocation fails at various points. - */ -TEST( Jobs_Unit_API, StartNextMallocFail ) -{ - _jobsMallocFail( JOBS_START_NEXT ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref jobs_function_describeasync when memory - * allocation fails at various points. - */ -TEST( Jobs_Unit_API, DescribeMallocFail ) -{ - _jobsMallocFail( JOBS_DESCRIBE ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref jobs_function_updateasync when memory - * allocation fails at various points. - */ -TEST( Jobs_Unit_API, UpdateMallocFail ) -{ - _jobsMallocFail( JOBS_UPDATE ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref jobs_function_removepersistentsubscriptions - * with various parameters. - */ -TEST( Jobs_Unit_API, RemovePersistentSubscriptions ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - - /* MQTT connection not set. */ - status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, - AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.mqttConnection = _pMqttConnection; - - /* Thing Name not set. */ - status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, - AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - - /* Job ID not set for DESCRIBE/UPDATE. */ - status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, - AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - - /* Job ID too long. */ - requestInfo.jobIdLength = JOBS_MAX_ID_LENGTH + 1; - status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, - AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - - /* No subscription present. */ - status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, - AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of the Jobs callback functions. - */ -TEST( Jobs_Unit_API, SetCallback ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - _jobsSubscription_t * pSubscription = NULL; - - /* Callback info must be provided. */ - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Thing Name must be valid. */ - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, NULL, 0, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Request to remove an unspecified callback. */ - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Request to remove a callback that doesn't exist. */ - callbackInfo.function = NULL; - callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - - /* Set new callback. */ - callbackInfo.function = JOBS_CALLBACK_FUNCTION; - callbackInfo.oldFunction = NULL; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that new callback was set. */ - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION ) ); - - /* Replace existing function. */ - callbackInfo.function = JOBS_CALLBACK_FUNCTION_2; - callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that callback was replaced. */ - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION ) ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION_2 ) ); - - /* Remove callback function. */ - callbackInfo.function = NULL; - callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_2; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the subscription object was deleted. */ - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NULL( pSubscription ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of the Jobs multiple callback functions. - */ -TEST( Jobs_Unit_API, SetCallbackMultiple ) -{ - intptr_t i = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - _jobsSubscription_t * pSubscription = NULL; - - /* This test requires AWS_IOT_JOBS_NOTIFY_CALLBACKS > 1 to allow multiple callbacks. */ - #if AWS_IOT_JOBS_NOTIFY_CALLBACKS < 2 - #error "Jobs SetCallbackMultiple test requires AWS_IOT_JOBS_NOTIFY_CALLBACKS to be at least 2." - #endif - - /* Set two Jobs callbacks. */ - callbackInfo.function = JOBS_CALLBACK_FUNCTION; - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - callbackInfo.function = JOBS_CALLBACK_FUNCTION_2; - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Ensure that both callbacks were set. */ - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION ) ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION_2 ) ); - - /* Fill all of the subscription object's callbacks. */ - for( i = 2; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) - { - /* A non-NULL function that is not expected to be invoked. */ - void ( * callbackFunction )( void *, - AwsIotJobsCallbackParam_t * ) = ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) )( i + 2 ); - - callbackInfo.function = callbackFunction; - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - callbackFunction ) ); - } - - /* Try to set more callbacks than allowed. */ - callbackInfo.function = JOBS_CALLBACK_FUNCTION_3; - callbackInfo.oldFunction = NULL; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); - - /* Clear the subscription object (except for 2 callbacks). */ - for( i = 2; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) - { - void ( * callbackFunction )( void *, - AwsIotJobsCallbackParam_t * ) = ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) )( i + 2 ); - - callbackInfo.function = NULL; - callbackInfo.oldFunction = callbackFunction; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - callbackFunction ) ); - } - - /* Replace a callback. */ - callbackInfo.function = JOBS_CALLBACK_FUNCTION_3; - callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION_3 ) ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION_2 ) ); - TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION ) ); - - /* Remove a callback. */ - callbackInfo.function = NULL; - callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_2; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION_3 ) ); - TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, - NOTIFY_NEXT_CALLBACK, - JOBS_CALLBACK_FUNCTION_2 ) ); - - /* The MQTT connection should still have a subscription for the Jobs topic, since one - * callback is still using it. */ - const char * const pNotifyTopic = AWS_IOT_TOPIC_PREFIX TEST_THING_NAME JOBS_NOTIFY_NEXT_CALLBACK_STRING; - const uint16_t notifyTopicLength = ( uint16_t ) strlen( pNotifyTopic ); - - TEST_ASSERT_EQUAL_INT( true, IotMqtt_IsSubscribed( _pMqttConnection, - pNotifyTopic, - notifyTopicLength, - NULL ) ); - - /* Remove the second callback. */ - callbackInfo.function = NULL; - callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_3; - - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); - TEST_ASSERT_EQUAL_INT( AWS_IOT_JOBS_SUCCESS, status ); - - /* The subscription object should now be destroyed, and the MQTT subscription should - * be gone. */ - pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); - TEST_ASSERT_NULL( pSubscription ); - - TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, - pNotifyTopic, - notifyTopicLength, - NULL ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of the Jobs set callback functions when memory - * allocation fails at various points. - */ -TEST( Jobs_Unit_API, SetCallbackMallocFail ) -{ - int32_t i = 0, mqttErrorCount = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - - /* Set a short timeout so this test runs faster. */ - _AwsIotJobsMqttTimeoutMs = 75; - - /* A non-NULL callback function. */ - callbackInfo.function = JOBS_CALLBACK_FUNCTION; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Jobs set callback. Memory allocation will fail at various times - * during this call. */ - status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - 0, - &callbackInfo ); - - if( status == AWS_IOT_JOBS_SUCCESS ) - { - break; - } - else if( status == AWS_IOT_JOBS_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); - } - } - - /* Allow 1 MQTT error, caused by failure to allocate memory for a SUBACK. */ - CHECK_MQTT_ERROR_COUNT( 1, mqttErrorCount ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c deleted file mode 100644 index fa0cfa545a..0000000000 --- a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c +++ /dev/null @@ -1,808 +0,0 @@ -/* - * AWS IoT Jobs V1.0.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_jobs_serialize.c - * @brief Tests for the Jobs JSON functions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Jobs Serialize tests. - */ -TEST_GROUP( Jobs_Unit_Serialize ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Jobs Serialize tests. - */ -TEST_SETUP( Jobs_Unit_Serialize ) -{ - /* Initialize SDK. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - /* Initialize the Jobs library. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Jobs Serialize tests. - */ -TEST_TEAR_DOWN( Jobs_Unit_Serialize ) -{ - /* Clean up libraries. */ - AwsIotJobs_Cleanup(); - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Jobs Serialize tests. - */ -TEST_GROUP_RUNNER( Jobs_Unit_Serialize ) -{ - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeGetPending ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextClientToken ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeDescribe ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateStatus ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateNumbers ); - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateFlags ); - RUN_TEST_CASE( Jobs_Unit_Serialize, ParseErrorResponse ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of JSON documents for GET PENDING requests. - */ -TEST( Jobs_Unit_Serialize, SerializeGetPending ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - const char * pClientToken = NULL; - size_t clientTokenLength = 0; - - /* Generate request with autogenerated client token. */ - status = _AwsIotJobs_GenerateJsonRequest( JOBS_GET_PENDING, - &requestInfo, - NULL, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ) ); - TEST_ASSERT_EQUAL_PTR( pClientToken, operation.pClientToken ); - TEST_ASSERT_EQUAL( clientTokenLength, operation.clientTokenLength ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request with custom client token. */ - requestInfo.pClientToken = "test"; - requestInfo.clientTokenLength = 4; - status = _AwsIotJobs_GenerateJsonRequest( JOBS_GET_PENDING, - &requestInfo, - NULL, - &operation ); - - /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ) ); - TEST_ASSERT_EQUAL_PTR( pClientToken, operation.pClientToken ); - TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", operation.pClientToken, 6 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of client token in JSON documents for START NEXT - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - requestContents.pUpdateInfo = &updateInfo; - - /* Generate request with only autogenerated client token. */ - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); - TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request with custom client token. */ - requestInfo.pClientToken = "test"; - requestInfo.clientTokenLength = 4; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); - TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of step timeout in JSON documents for START NEXT - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - requestContents.pUpdateInfo = &updateInfo; - - /* Step timeout should not be present if not provided. */ - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request using the "cancel timeout" value. */ - updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_CANCEL_TIMEOUT; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the step timeout is -1. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 2, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request with step timeout. */ - updateInfo.stepTimeoutInMinutes = 3600; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the step timeout is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 4, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "3600", pJsonValue, 4 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of status details in JSON documents for START NEXT - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - requestContents.pUpdateInfo = &updateInfo; - - /* Generate request with status details. */ - updateInfo.pStatusDetails = "{\"status\":0}"; - updateInfo.statusDetailsLength = 12; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the status details are present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "statusDetails", - 13, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 12, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request with status details, step timeout, and custom client token. */ - updateInfo.stepTimeoutInMinutes = 10080; - requestInfo.pClientToken = "test"; - requestInfo.clientTokenLength = 4; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the status details are present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "statusDetails", - 13, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 12, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); - - /* Check that step timeout is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 5, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "10080", pJsonValue, 5 ); - - /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); - TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of status details in JSON documents for DESCRIBE - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeDescribe ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - /* Generate request with only autogenerated client token. */ - requestContents.describe.includeJobDocument = true; - requestContents.describe.executionNumber = AWS_IOT_JOBS_NO_EXECUTION_NUMBER; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_DESCRIBE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); - TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request with all parameters. */ - requestContents.describe.includeJobDocument = false; - requestContents.describe.executionNumber = 555555555; - requestInfo.pClientToken = "test"; - requestInfo.clientTokenLength = 4; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_DESCRIBE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); - TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of status in JSON documents for UPDATE - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeUpdateStatus ) -{ - uint32_t i = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - /* Valid statuses to check. */ - const AwsIotJobState_t pValidStatus[ 4 ] = - { - AWS_IOT_JOB_STATE_IN_PROGRESS, AWS_IOT_JOB_STATE_FAILED, - AWS_IOT_JOB_STATE_SUCCEEDED, AWS_IOT_JOB_STATE_REJECTED - }; - - const char * const pValidStatusStrings[ 4 ] = - { - "\"IN_PROGRESS\"", "\"FAILED\"", "\"SUCCEEDED\"", "\"REJECTED\"" - }; - - requestContents.pUpdateInfo = &updateInfo; - - /* Generate request documents with valid statuses. */ - for( i = 0; i < ( sizeof( pValidStatus ) / sizeof( pValidStatus[ 0 ] ) ); i++ ) - { - updateInfo.newStatus = pValidStatus[ i ]; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check the status value. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "status", - 6, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( strlen( pValidStatusStrings[ i ] ), jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( pValidStatusStrings[ i ], pJsonValue, jsonValueLength ); - - /* Check that the step timeout is not present. */ - TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - - /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); - TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - } - - /* Generate request with status details. */ - updateInfo.pStatusDetails = "{\"status\":0}"; - updateInfo.statusDetailsLength = 12; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that the status details are present. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "statusDetails", - 13, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 12, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of numbers in JSON documents for UPDATE - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - requestContents.pUpdateInfo = &updateInfo; - - /* Generate request document with expected version. */ - updateInfo.expectedVersion = 1; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check the expected version. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "expectedVersion", - 15, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 3, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "\"1\"", pJsonValue, 3 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request document with execution number. */ - updateInfo.executionNumber = 555555555; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check the execution number. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "executionNumber", - 15, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 9, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "555555555", pJsonValue, 9 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request document with step timeout. */ - updateInfo.stepTimeoutInMinutes = 10080; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check the step timeout. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 5, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "10080", pJsonValue, 5 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate request using the "cancel timeout" value. */ - updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_CANCEL_TIMEOUT; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that step timeout is -1. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 2, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests generation of flags in JSON documents for UPDATE - * requests. - */ -TEST( Jobs_Unit_Serialize, SerializeUpdateFlags ) -{ - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - _jobsOperation_t operation = { .link = { 0 } }; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - _jsonRequestContents_t requestContents = { 0 }; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - requestContents.pUpdateInfo = &updateInfo; - - /* Generate a request without any flags set. */ - requestInfo.pClientToken = "test"; - requestInfo.clientTokenLength = 4; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that no flags are present. */ - TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobDocument", - 18, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobExecutionState", - 24, - &pJsonValue, - &jsonValueLength ) ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); - - /* Generate a request with flags set. */ - updateInfo.includeJobDocument = true; - updateInfo.includeJobExecutionState = true; - - status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, - &requestInfo, - &requestContents, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - - /* Check that flags are set. */ - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobDocument", - 18, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 4, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "true", pJsonValue, 4 ); - - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobExecutionState", - 24, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 4, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "true", pJsonValue, 4 ); - - /* Free request document generated by serializer. */ - AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing of Jobs error responses. - */ -TEST( Jobs_Unit_Serialize, ParseErrorResponse ) -{ - size_t i = 0; - _jobsOperation_t operation = { .link = { 0 } }; - - /* Test for failure to allocate memory for response. */ - #if IOT_TEST_NO_MALLOC_OVERRIDES != 1 - operation.status = AWS_IOT_JOBS_STATUS_PENDING; - operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; - operation.mallocResponse = IotTest_Malloc; - - UnityMalloc_MakeMallocFailAfterCount( 0 ); - - _AwsIotJobs_ParseResponse( AWS_IOT_ACCEPTED, "{}", 2, &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, operation.status ); - UnityMalloc_MakeMallocFailAfterCount( -1 ); - operation.flags = 0; - #endif /* if IOT_TEST_NO_MALLOC_OVERRIDES != 1 */ - - /* Test parsing of error responses. */ - const char * pErrorResponses[] = - { - "{\"code\": \"InvalidTopic\"}", - "{\"code\": \"InvalidJson\"}", - "{\"code\": \"InvalidRequest\"}", - "{\"code\": \"InvalidStateTransition\"}", - "{\"code\": \"ResourceNotFound\"}", - "{\"code\": \"VersionMismatch\"}", - "{\"code\": \"InternalError\"}", - "{\"code\": \"RequestThrottled\"}", - "{\"code\": \"TerminalStateReached\"}" - }; - const AwsIotJobsError_t pExpectedResponse[] = - { - AWS_IOT_JOBS_INVALID_TOPIC, - AWS_IOT_JOBS_INVALID_JSON, - AWS_IOT_JOBS_INVALID_REQUEST, - AWS_IOT_JOBS_INVALID_STATE, - AWS_IOT_JOBS_NOT_FOUND, - AWS_IOT_JOBS_VERSION_MISMATCH, - AWS_IOT_JOBS_INTERNAL_ERROR, - AWS_IOT_JOBS_THROTTLED, - AWS_IOT_JOBS_TERMINAL_STATE - }; - - for( i = 0; i < ( sizeof( pErrorResponses ) / sizeof( pErrorResponses[ 0 ] ) ); i++ ) - { - operation.status = AWS_IOT_JOBS_STATUS_PENDING; - - _AwsIotJobs_ParseResponse( AWS_IOT_REJECTED, - pErrorResponses[ i ], - strlen( pErrorResponses[ i ] ), - &operation ); - TEST_ASSERT_EQUAL( pExpectedResponse[ i ], operation.status ); - } - - /* Test parsing of invalid code. */ - operation.status = AWS_IOT_JOBS_STATUS_PENDING; - _AwsIotJobs_ParseResponse( AWS_IOT_REJECTED, - "{\"code\": \"NotAnErrorCodeNotAnErrorCode\"}", - 40, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_RESPONSE, operation.status ); - - operation.status = AWS_IOT_JOBS_STATUS_PENDING; - _AwsIotJobs_ParseResponse( AWS_IOT_REJECTED, - "{}", - 2, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_RESPONSE, operation.status ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/CMakeLists.txt b/libraries/aws/shadow/CMakeLists.txt deleted file mode 100644 index d26cd46280..0000000000 --- a/libraries/aws/shadow/CMakeLists.txt +++ /dev/null @@ -1,75 +0,0 @@ -# Shadow library source files. -set( SHADOW_SOURCES - src/aws_iot_shadow_api.c - src/aws_iot_shadow_operation.c - src/aws_iot_shadow_parser.c - src/aws_iot_shadow_static_memory.c - src/aws_iot_shadow_subscription.c ) - -# Shadow library target. -add_library( awsiotshadow - ${CONFIG_HEADER_PATH}/iot_config.h - ${SHADOW_SOURCES} - include/aws_iot_shadow.h - include/types/aws_iot_shadow_types.h - src/private/aws_iot_shadow_internal.h ) - -# Shadow public include path. -target_include_directories( awsiotshadow PUBLIC include ) - -# Link required libraries. -target_link_libraries( awsiotshadow PRIVATE awsiotcommon iotserializer iotbase ) - -# Shadow is currently implemented on MQTT, so link MQTT as a public dependency. -target_link_libraries( awsiotshadow PUBLIC iotmqtt ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotshadow PRIVATE unity ) -endif() - -# Organization of Shadow in folders. -set_property( TARGET awsiotshadow PROPERTY FOLDER libraries/aws ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES include/aws_iot_shadow.h ) -source_group( include\\types include/types/aws_iot_shadow_types.h ) -source_group( src\\private src/private/aws_iot_shadow_internal.h ) -source_group( src FILES ${SHADOW_SOURCES} ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # Shadow system test sources. - set( SHADOW_SYSTEM_TEST_SOURCES - test/system/aws_iot_tests_shadow_system.c ) - - # Shadow unit test sources. - set( SHADOW_UNIT_TEST_SOURCES - test/unit/aws_iot_tests_shadow_api.c - test/unit/aws_iot_tests_shadow_parser.c ) - - # Shadow tests executable. - add_executable( aws_iot_tests_shadow - ${SHADOW_SYSTEM_TEST_SOURCES} - ${SHADOW_UNIT_TEST_SOURCES} - test/aws_iot_tests_shadow.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( aws_iot_tests_shadow PRIVATE - -DRunTests=RunShadowTests ) - - # The Shadow tests need the internal Shadow header. - target_include_directories( aws_iot_tests_shadow PRIVATE src ) - - # Shadow tests library dependencies. - target_link_libraries( aws_iot_tests_shadow PRIVATE - awsiotshadow awsiotcommon iotserializer iotbase - unity iot_mqtt_mock ) - - # Organization of Shadow tests in folders. - set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER tests ) - source_group( system FILES ${SHADOW_SYSTEM_TEST_SOURCES} ) - source_group( unit FILES ${SHADOW_UNIT_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_shadow.c ) -endif() diff --git a/libraries/aws/shadow/include/aws_iot_shadow.h b/libraries/aws/shadow/include/aws_iot_shadow.h deleted file mode 100644 index cdfa70d443..0000000000 --- a/libraries/aws/shadow/include/aws_iot_shadow.h +++ /dev/null @@ -1,909 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow.h - * @brief User-facing functions of the Shadow library. - */ - -#ifndef AWS_IOT_SHADOW_H_ -#define AWS_IOT_SHADOW_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Shadow types include. */ -#include "types/aws_iot_shadow_types.h" - -/*------------------------ Shadow library functions -------------------------*/ - -/** - * @functionspage{shadow,Shadow library} - * - @functionname{shadow_function_init} - * - @functionname{shadow_function_cleanup} - * - @functionname{shadow_function_deleteasync} - * - @functionname{shadow_function_deletesync} - * - @functionname{shadow_function_getasync} - * - @functionname{shadow_function_getsync} - * - @functionname{shadow_function_updateasync} - * - @functionname{shadow_function_updatesync} - * - @functionname{shadow_function_wait} - * - @functionname{shadow_function_setdeltacallback} - * - @functionname{shadow_function_setupdatedcallback} - * - @functionname{shadow_function_removepersistentsubscriptions} - * - @functionname{shadow_function_strerror} - */ - -/** - * @functionpage{AwsIotShadow_Init,shadow,init} - * @functionpage{AwsIotShadow_Cleanup,shadow,cleanup} - * @functionpage{AwsIotShadow_DeleteAsync,shadow,deleteasync} - * @functionpage{AwsIotShadow_DeleteSync,shadow,deletesync} - * @functionpage{AwsIotShadow_GetAsync,shadow,getasync} - * @functionpage{AwsIotShadow_GetSync,shadow,getsync} - * @functionpage{AwsIotShadow_UpdateAsync,shadow,updateasync} - * @functionpage{AwsIotShadow_UpdateSync,shadow,updatesync} - * @functionpage{AwsIotShadow_Wait,shadow,wait} - * @functionpage{AwsIotShadow_SetDeltaCallback,shadow,setdeltacallback} - * @functionpage{AwsIotShadow_SetUpdatedCallback,shadow,setupdatedcallback} - * @functionpage{AwsIotShadow_RemovePersistentSubscriptions,shadow,removepersistentsubscriptions} - * @functionpage{AwsIotShadow_strerror,shadow,strerror} - */ - -/** - * @brief One-time initialization function for the Shadow library. - * - * This function performs internal setup of the Shadow library. It must be - * called once (and only once) before calling any other Shadow function. - * Calling this function more than once without first calling @ref - * shadow_function_cleanup may result in a crash. - * - * @param[in] mqttTimeoutMs The amount of time (in milliseconds) that the Shadow - * library will wait for MQTT operations. Optional; set this to `0` to use - * @ref AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_INIT_FAILED - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see @ref shadow_function_cleanup - */ -/* @[declare_shadow_init] */ -AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ); -/* @[declare_shadow_init] */ - -/** - * @brief One-time deinitialization function for the Shadow library. - * - * This function frees resources taken in @ref shadow_function_init and deletes - * any [persistent subscriptions.](@ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS) - * It should be called to clean up the Shadow library. After this function returns, - * @ref shadow_function_init must be called again before calling any other Shadow - * function. - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see @ref shadow_function_init - */ -/* @[declare_shadow_cleanup] */ -void AwsIotShadow_Cleanup( void ); -/* @[declare_shadow_cleanup] */ - -/** - * @brief Delete a Thing Shadow and receive an asynchronous notification when - * the Delete completes. - * - * This function deletes any existing Shadow document for the given Thing Name. - * If the given Thing has no Shadow and this function is called, the result will - * be #AWS_IOT_SHADOW_NOT_FOUND. - * - * Deleting a Shadow involves sending an MQTT message to AWS IoT and waiting on - * a response. This message will always be sent at [MQTT QoS 0](@ref #IOT_MQTT_QOS_0). - * - * @param[in] mqttConnection The MQTT connection to use for Shadow delete. - * @param[in] pThingName The Thing Name associated with the Shadow to delete. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pDeleteOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Shadow delete - * completes. - * - * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully - * queuing a Shadow delete. - * @return If this function fails before queuing a Shadow delete, it will return one of: - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * @return Upon successful completion of the Shadow delete (either through an #AwsIotShadowCallbackInfo_t - * or #AwsIotShadow_Wait), the status will be #AWS_IOT_SHADOW_SUCCESS. - * @return Should the Shadow delete fail, the status will be one of: - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - * - * @see @ref shadow_function_deletesync for a blocking variant of this function. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * // Shadow operation handle. - * AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - * - * // Queue a Shadow delete. - * AwsIotShadowError_t deleteResult = AwsIotShadow_DeleteAsync( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * AWS_IOT_SHADOW_FLAG_WAITABLE, - * NULL, - * &deleteOperation ); - * - * // Shadow delete should return AWS_IOT_SHADOW_STATUS_PENDING upon success. - * if( deleteResult == AWS_IOT_SHADOW_STATUS_PENDING ) - * { - * // Wait for the Shadow delete to complete. - * deleteResult = AwsIotShadow_Wait( deleteOperation, 5000 ); - * - * // Delete result should be AWS_IOT_SHADOW_SUCCESS upon successfully - * // deleting an existing Shadow. - * } - * @endcode - */ -/* @[declare_shadow_deleteasync] */ -AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pDeleteOperation ); -/* @[declare_shadow_deleteasync] */ - -/** - * @brief Delete a Thing Shadow with a timeout. - * - * This function queues a Shadow delete, then waits for the result. Internally, this - * function is a call to @ref shadow_function_deleteasync followed by @ref shadow_function_wait. - * See @ref shadow_function_deleteasync for more information on the Shadow delete operation. - * - * @param[in] mqttConnection The MQTT connection to use for Shadow delete. - * @param[in] pThingName The Thing Name associated with the Shadow to delete. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. - * @param[in] timeoutMs If the Shadow service does not respond to the Shadow delete - * within this timeout, this function returns #AWS_IOT_SHADOW_TIMEOUT. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - #AWS_IOT_SHADOW_TIMEOUT - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - */ -/* @[declare_shadow_deletesync] */ -AwsIotShadowError_t AwsIotShadow_DeleteSync( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_shadow_deletesync] */ - -/** - * @brief Retrieve a Thing Shadow and receive an asynchronous notification when - * the Shadow document is received. - * - * This function retrieves the Thing Shadow document currently stored by the - * Shadow service. If a given Thing has no Shadow and this function is called, - * the result will be #AWS_IOT_SHADOW_NOT_FOUND. - * - * Shadow documents may be large, and their size is not known beforehand. - * Therefore, this function works best when memory is dynamically allocated. - * Because the Shadow document is retrieved in an MQTT PUBLISH packet, the MQTT - * library will allocate a buffer for the Shadow document using #IotMqtt_MallocMessage. - * - * The MQTT library may free the buffer for a retrieved Shadow document as soon - * as the [Shadow completion callback](@ref AwsIotShadowCallbackInfo_t) returns. - * Therefore, any data needed later must be copied from the Shadow document. - * Similarly, if the flag #AWS_IOT_SHADOW_FLAG_WAITABLE is given to this function - * (which indicates that the Shadow document will be needed after the Shadow - * operation completes), #AwsIotShadowDocumentInfo_t.mallocDocument must be - * provided to allocate a longer-lasting buffer. - * - * @note Because of the potentially large size of complete Shadow documents, it is more - * memory-efficient for most applications to use [delta callbacks] - * (@ref shadow_function_setdeltacallback) to retrieve Shadows from - * the Shadow service. - * - * @param[in] mqttConnection The MQTT connection to use for Shadow get. - * @param[in] pGetInfo Shadow document parameters. - * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pGetOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Shadow get - * completes. - * - * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully - * queuing a Shadow get. - * @return If this function fails before queuing a Shadow get, it will return one of: - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * @return Upon successful completion of the Shadow get (either through an #AwsIotShadowCallbackInfo_t - * or #AwsIotShadow_Wait), the status will be #AWS_IOT_SHADOW_SUCCESS. - * @return Should the Shadow get fail, the status will be one of: - * - #AWS_IOT_SHADOW_NO_MEMORY (Memory could not be allocated for incoming document) - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - * - * @see @ref shadow_function_getsync for a blocking variant of this function. - * - * Example - * @code{c} - * // Shadow get completion callback. The retrieved document will be in - * // pCallbackParam. Any data in the retrieved document needed after this - * // function returns must be copied. - * void _processRetrievedDocument( void * pCallbackContext, - * AwsIotShadowCallbackParam_t * pCallbackParam ); - * - * // Parameters and return value of Shadow get. - * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; - * AwsIotShadowDocumentInfo_t getInfo = { ... }; - * uint32_t timeout = 5000; // 5 seconds - * - * // Callback for get completion. - * AwsIotShadowCallbackInfo_t getCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - * getCallback.function = _processRetrievedDocument; - * - * // Shadow get operation. - * result = AwsIotShadow_GetAsync( mqttConnection, - * &getInfo, - * 0, - * &getCallback, - * NULL ); - * - * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The function - * // _processRetrievedDocument will be invoked once the Shadow get completes. - * @endcode - * - * See @ref shadow_function_wait Example 2 for an example of using this - * function with #AWS_IOT_SHADOW_FLAG_WAITABLE and @ref shadow_function_wait. - */ -/* @[declare_shadow_getasync] */ -AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pGetOperation ); -/* @[declare_shadow_getasync] */ - -/** - * @brief Retrieve a Thing Shadow with a timeout. - * - * This function queues a Shadow get, then waits for the result. Internally, this - * function is a call to @ref shadow_function_getasync followed by @ref shadow_function_wait. - * See @ref shadow_function_getasync for more information on the Shadow get operation. - * - * @param[in] mqttConnection The MQTT connection to use for Shadow get. - * @param[in] pGetInfo Shadow document parameters. - * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. - * @param[in] timeoutMs If the Shadow service does not respond to the Shadow get - * within this timeout, this function returns #AWS_IOT_SHADOW_TIMEOUT. - * @param[out] pShadowDocument A pointer to a buffer containing the Shadow document - * retrieved by a Shadow get is placed here. The buffer was allocated with the function - * `pGetInfo->get.mallocDocument`. This output parameter is only valid if this function - * returns #AWS_IOT_SHADOW_SUCCESS. - * @param[out] pShadowDocumentLength The length of the Shadow document in - * `pShadowDocument` is placed here. This output parameter is only valid if this function - * returns #AWS_IOT_SHADOW_SUCCESS. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - #AWS_IOT_SHADOW_TIMEOUT - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - */ -/* @[declare_shadow_getsync] */ -AwsIotShadowError_t AwsIotShadow_GetSync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - uint32_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ); -/* @[declare_shadow_getsync] */ - -/** - * @brief Send a Thing Shadow update and receive an asynchronous notification when - * the Shadow Update completes. - * - * This function modifies the Thing Shadow document stored by the Shadow service. - * If a given Thing has no Shadow and this function is called, then a new Shadow - * is created. - * - * New JSON keys in the Shadow document will be appended. For example, if the Shadow service - * currently has a document containing key `example1` and this function sends a document - * only containing key `example2`, then the resulting document in the Shadow service - * will contain both `example1` and `example2`. - * - * Existing JSON keys in the Shadow document will be replaced. For example, if the Shadow - * service currently has a document containing `"example1": [0,1,2]` and this function sends - * a document containing key `"example1": [1,2,3]`, then the resulting document in the Shadow - * service will contain `"example1": [1,2,3]`. - * - * Successful Shadow updates will trigger the [Shadow updated callback] - * (@ref shadow_function_setupdatedcallback). If the resulting Shadow document contains - * different `desired` and `reported` keys, then the [Shadow delta callback] - * (@ref shadow_function_setdeltacallback) will be triggered as well. - * - * @attention All documents passed to this function must contain a `clientToken`. - * The [client token] - * (https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-document.html#client-token) - * is a string used to distinguish between Shadow updates. They are limited to 64 - * characters; attempting to use a client token longer than 64 characters will - * cause the Shadow update to fail. They must be unique at any given time, i.e. - * they may be reused as long as no two Shadow updates are using the same - * client token at the same time. - * - * @param[in] mqttConnection The MQTT connection to use for Shadow update. - * @param[in] pUpdateInfo Shadow document parameters. - * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. - * @param[out] pUpdateOperation Set to a handle by which this operation may be referenced - * after this function returns. This reference is invalidated once the Shadow update - * completes. - * - * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully - * queuing a Shadow update. - * @return If this function fails before queuing a Shadow update, it will return one of: - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * @return Upon successful completion of the Shadow update (either through an #AwsIotShadowCallbackInfo_t - * or #AwsIotShadow_Wait), the status will be #AWS_IOT_SHADOW_SUCCESS. - * @return Should the Shadow update fail, the status will be one of: - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - * - * @see @ref shadow_function_updatesync for a blocking variant of this function. - * - * Example - * @code{c} - * // Shadow update completion callback. - * void _updateComplete( void * pCallbackContext, - * AwsIotShadowCallbackParam_t * pCallbackParam ); - * - * // Parameters and return value of Shadow update. - * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; - * AwsIotShadowDocumentInfo_t updateInfo = { ... }; - * uint32_t timeout = 5000; // 5 seconds - * - * // Set Shadow document to send. - * updateInfo.update.pUpdateDocument = "{...}"; // Must contain clientToken - * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); - * - * // Callback for update completion. - * AwsIotShadowCallbackInfo_t updateCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - * updateCallback.function = _updateComplete; - * - * // Shadow update operation. - * result = AwsIotShadow_UpdateAsync( mqttConnection, - * &updateInfo, - * 0, - * &updateCallback, - * NULL ); - * - * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The function - * // _updateComplete will be invoked once the Shadow update completes. - * @endcode - * - * See @ref shadow_function_wait Example 1 for an example of using this - * function with #AWS_IOT_SHADOW_FLAG_WAITABLE and @ref shadow_function_wait. - */ -/* @[declare_shadow_updateasync] */ -AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pUpdateOperation ); -/* @[declare_shadow_updateasync] */ - -/** - * @brief Send a Thing Shadow update with a timeout. - * - * This function queues a Shadow update, then waits for the result. Internally, this - * function is a call to @ref shadow_function_updateasync followed by @ref shadow_function_wait. - * See @ref shadow_function_updateasync for more information on the Shadow update operation. - * - * @param[in] mqttConnection The MQTT connection to use for Shadow update. - * @param[in] pUpdateInfo Shadow document parameters. - * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. - * @param[in] timeoutMs If the Shadow service does not respond to the Shadow update - * within this timeout, this function returns #AWS_IOT_SHADOW_TIMEOUT. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - #AWS_IOT_SHADOW_TIMEOUT - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - */ -/* @[declare_shadow_updatesync] */ -AwsIotShadowError_t AwsIotShadow_UpdateSync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_shadow_updatesync] */ - -/** - * @brief Wait for a Shadow operation to complete. - * - * This function blocks to wait for a [delete](@ref shadow_function_deleteasync), - * [get](@ref shadow_function_getasync), or [update](@ref shadow_function_updateasync) to - * complete. These operations are by default asynchronous; the function calls - * queue an operation for processing, and a callback is invoked once the operation - * is complete. - * - * To use this function, the flag #AWS_IOT_SHADOW_FLAG_WAITABLE must have been - * set in the operation's function call. Additionally, this function must always - * be called with any waitable operation to clean up resources. - * - * Regardless of its return value, this function always clean up resources used - * by the waitable operation. This means `operation` is invalidated as soon as - * this function returns, even if it returns #AWS_IOT_SHADOW_TIMEOUT or another - * error. - * - * @param[in] operation Reference to the Shadow operation to wait for. The flag - * #AWS_IOT_SHADOW_FLAG_WAITABLE must have been set for this operation. - * @param[in] timeoutMs How long to wait before returning #AWS_IOT_SHADOW_TIMEOUT. - * @param[out] pShadowDocument A pointer to a buffer containing the Shadow document - * retrieved by a [Shadow get](@ref shadow_function_getasync) is placed here. The buffer - * was allocated with the function #AwsIotShadowDocumentInfo_t.mallocDocument passed - * to @ref shadow_function_getasync. This parameter is only valid for a [Shadow get] - * (@ref shadow_function_getasync) and ignored for other Shadow operations. This output - * parameter is only valid if this function returns #AWS_IOT_SHADOW_SUCCESS. - * @param[out] pShadowDocumentLength The length of the Shadow document in - * `pShadowDocument` is placed here. This parameter is only valid for a [Shadow get] - * (@ref shadow_function_getasync) and ignored for other Shadow operations. This output - * parameter is only valid if this function returns #AWS_IOT_SHADOW_SUCCESS. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_BAD_RESPONSE - * - #AWS_IOT_SHADOW_TIMEOUT - * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) - * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) - * - * Example 1 (Shadow Update) - * @code{c} - * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; - * AwsIotShadowDocumentInfo_t updateInfo = { ... }; - * - * // Reference and timeout. - * AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - * uint32_t timeout = 5000; // 5 seconds - * - * // Shadow update operation. - * result = AwsIotShadow_UpdateAsync( mqttConnection, - * &updateInfo, - * AWS_IOT_SHADOW_FLAG_WAITABLE, - * NULL, - * &updateOperation ); - * - * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait - * // returns once the result of the update is available or the timeout expires. - * if( result == AWS_IOT_SHADOW_STATUS_PENDING ) - * { - * // The last two parameters are ignored for a Shadow update. - * result = AwsIotShadow_Wait( updateOperation, timeout, NULL, NULL ); - * - * // After the call to wait, the result of the update is known - * // (not AWS_IOT_SHADOW_STATUS_PENDING). - * assert( result != AWS_IOT_SHADOW_STATUS_PENDING ); - * } - * @endcode - * - * Example 2 (Shadow Get) - * @code{c} - * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; - * AwsIotShadowDocumentInfo_t getInfo = { ... }; - * - * // Reference and timeout. - * AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - * uint32_t timeout = 5000; // 5 seconds - * - * // Buffer pointer and size for retrieved Shadow document. - * const char * pShadowDocument = NULL; - * size_t documentLength = 0; - * - * // Buffer allocation function must be set for a waitable Shadow get. - * getInfo.get.mallocDocument = malloc; - * - * // Shadow get operation. - * result = AwsIotShadow_GetAsync( mqttConnection, - * &getInfo, - * AWS_IOT_SHADOW_FLAG_WAITABLE, - * NULL, - * &getOperation ); - * - * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait - * // returns once the result of the get is available or the timeout expires. - * if( result == AWS_IOT_SHADOW_STATUS_PENDING ) - * { - * // The last two parameters must be set for a Shadow get. - * result = AwsIotShadow_Wait( getOperation, timeout, &pShadowDocument, &documentLength ); - * - * // After the call to wait, the result of the get is known - * // (not AWS_IOT_SHADOW_STATUS_PENDING). - * assert( result != AWS_IOT_SHADOW_STATUS_PENDING ); - * - * // The retrieved Shadow document is only valid for a successful Shadow get. - * if( result == AWS_IOT_SHADOW_SUCCESS ) - * { - * // Do something with the Shadow document... - * - * // Free the Shadow document when finished. - * free( pShadowDocument ); - * } - * } - * @endcode - */ -/* @[declare_shadow_wait] */ -AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, - uint32_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ); -/* @[declare_shadow_wait] */ - -/** - * @brief Set a callback to be invoked when the Thing Shadow `desired` and `reported` - * states differ. - * - * A Thing Shadow contains `reported` and `desired` states, meant to represent - * the current device status and some desired status, respectively. When the - * `reported` and `desired` states differ, the Thing Shadow service generates a - * delta document and publishes it to the topic `update/delta`. Devices - * with a subscription for this topic will receive the delta document and may act - * based on the different `reported` and `desired` states. See [this page] - * (https://docs.aws.amazon.com/iot/latest/developerguide/using-device-shadows.html#delta-state) - * for more information about using delta documents. - * - * A delta callback may be invoked whenever a delta document is generated. - * Each Thing may have a single delta callback set. This function modifies the delta - * callback for a specific Thing depending on the `pDeltaCallback` parameter and - * the presence of any existing delta callback: - * - When no existing delta callback exists for a specific Thing, a new delta - * callback is added. - * - If there is an existing delta callback and `pDeltaCallback` is not `NULL`, then - * the existing callback function and parameter are replaced with `pDeltaCallback`. - * - If there is an existing subscription and `pDeltaCallback` is `NULL`, then the - * delta callback is removed. - * - * This function is always blocking; it may block for up to the default MQTT - * timeout. This timeout is set as a parameter to @ref shadow_function_init, - * and defaults to @ref AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS if not set. If - * this function's underlying MQTT operations fail to complete within this - * timeout, then this function returns #AWS_IOT_SHADOW_TIMEOUT. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription to - * `update/delta`. - * @param[in] pThingName The subscription to `update/delta` will be added for - * this Thing Name. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags This parameter is for future-compatibility. Currently, flags - * are not supported for this function and this parameter is ignored. - * @param[in] pDeltaCallback Callback function to invoke for incoming delta - * documents. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_TIMEOUT - * - * @return This function always returns #AWS_IOT_SHADOW_SUCCESS when replacing or - * removing existing delta callbacks. - * - * @see @ref shadow_function_setupdatedcallback for the function to register - * callbacks for all Shadow updates. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; - * AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - * - * // _deltaCallbackFunction will be invoked when a delta document is received. - * deltaCallback.function = _deltaCallbackFunction; - * - * // Set the delta callback for the Thing "Test_device". - * result = AwsIotShadow_SetDeltaCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * &deltaCallback ); - * - * // Check if callback was successfully set. - * if( result == AWS_IOT_SHADOW_SUCCESS ) - * { - * AwsIotShadowDocumentInfo_t updateInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - * - * // Set the Thing Name for Shadow update. - * updateInfo.pThingName = THING_NAME; - * updateInfo.thingNameLength = THING_NAME_LENGTH; - * - * // Set the Shadow document to send. This document has different "reported" - * // and "desired" states. It represents a scenario where a device is currently - * // off, but is being ordered to turn on. - * updateInfo.update.pUpdateDocument = - * "{" - * "\"state\": {" - * "\"reported\": { \"deviceOn\": false }," - * "\"desired\": { \"deviceOn\": true }" - * "}" - * "}"; - * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); - * - * // Send the Shadow document with different "reported" and desired states. - * result = AwsIotShadow_UpdateSync( mqttConnection, - * &updateInfo, - * 0, - * 0, - * 5000 ); - * - * // After the update is successfully sent, the function _deltaCallbackFunction - * // will be invoked once the Shadow service generates and sends a delta document. - * // The delta document will contain the different "deviceOn" states, as well as - * // metadata. - * - * // Once the delta callback is no longer needed, it may be removed by passing - * // NULL as pDeltaCallback. - * result = AwsIotShadow_SetDeltaCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * NULL ); - * - * // The return value from removing a delta callback should always be success. - * assert( result == AWS_IOT_SHADOW_SUCCESS ); - * } - * @endcode - */ -/* @[declare_shadow_setdeltacallback] */ -AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pDeltaCallback ); -/* @[declare_shadow_setdeltacallback] */ - -/** - * @brief Set a callback to be invoked when a Thing Shadow changes. - * - * The Shadow service publishes a state document to the `update/documents` topic - * whenever a Thing Shadow is successfully updated. This document reports the - * complete previous and current Shadow documents in `previous` and `current` - * sections, respectively. Therefore, the `update/documents` topic is useful - * for monitoring Shadow updates. - * - * An updated callback may be invoked whenever a document is published to - * `update/documents`. Each Thing may have a single updated callback set. This function - * modifies the updated callback for a specific Thing depending on the `pUpdatedCallback` - * parameter and the presence of any existing updated callback. - * - When no existing updated callback exists for a specific Thing, a new updated - * callback is added. - * - If there is an existing updated callback and `pUpdatedCallback` is not `NULL`, - * then the existing callback function and parameter are replaced with `pUpdatedCallback`. - * - If there is an existing updated callback and `pUpdatedCallback` is `NULL`, - * then the updated callback is removed. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription to `update/documents`. - * @param[in] pThingName The subscription to `update/documents` will be added for - * this Thing Name. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags This parameter is for future-compatibility. Currently, flags are - * not supported for this function and this parameter is ignored. - * @param[in] pUpdatedCallback Callback function to invoke for incoming updated documents. - * - * @return One of the following: - * - #AWS_IOT_SHADOW_SUCCESS - * - #AWS_IOT_SHADOW_NOT_INITIALIZED - * - #AWS_IOT_SHADOW_BAD_PARAMETER - * - #AWS_IOT_SHADOW_NO_MEMORY - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - #AWS_IOT_SHADOW_TIMEOUT - * - * @note Documents published to `update/documents` will be large, as they contain 2 - * complete Shadow state documents. If an updated callback is used, ensure that the - * device has sufficient memory for incoming documents. - * - * @see @ref shadow_function_setdeltacallback for the function to register callbacks - * for delta documents. - * - * Example - * @code{c} - * #define THING_NAME "Test_device" - * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) - * - * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; - * AwsIotShadowCallbackInfo_t updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - * - * // _updatedCallbackFunction will be invoked when an updated document is received. - * updatedCallback.function = _updatedCallbackFunction; - * - * // Set the updated callback for the Thing "Test_device". - * result = AwsIotShadow_SetUpdatedCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * 0, - * &updatedCallback ); - * - * // Check if the callback was successfully set. - * if( result == AWS_IOT_SHADOW_SUCCESS ) - * { - * AwsIotShadowDocumentInfo_t updateInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - * - * // Set the Thing Name for Shadow update. - * updateInfo.pThingName = THING_NAME; - * updateInfo.thingNameLength = THING_NAME_LENGTH; - * - * // Set the Shadow document to send. Any Shadow update will trigger the - * // updated callback. - * updateInfo.update.pUpdateDocument = - * "{" - * "\"state\": {" - * "\"reported\": { \"deviceOn\": false }" - * "}" - * "}"; - * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); - * - * // Send the Shadow document. A successful update will trigger the updated callback. - * result = AwsIotShadow_UpdateSync( mqttConnection, - * &updateInfo, - * 0, - * 0, - * 5000 ); - * - * // After a successful Shadow update, the updated callback will be invoked. - * - * // Once the updated callback is no longer needed, it may be removed by - * // passing NULL as pUpdatedCallback. - * result = AwsIotShadow_SetUpdatedCallback( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * NULL ); - * - * // The return value from removing an updated callback should always be - * // success. - * assert( result == AWS_IOT_SHADOW_SUCCESS ); - * } - * @endcode - */ -/* @[declare_shadow_setupdatedcallback] */ -AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pUpdatedCallback ); -/* @[declare_shadow_setupdatedcallback] */ - -/** - * @brief Remove persistent Thing Shadow operation topic subscriptions. - * - * Passing the flag @ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS to @ref shadow_function_deleteasync, - * @ref shadow_function_getasync, @ref shadow_function_updateasync, or their blocking versions. - * causes the Shadow operation topic subscriptions to be maintained for future calls to the - * same function. If a persistent subscription for a Shadow topic are no longer needed, - * this function may be used to remove it. - * - * @param[in] mqttConnection The MQTT connection associated with the persistent subscription. - * @param[in] pThingName The Thing Name associated with the persistent subscription. - * @param[in] thingNameLength The length of `pThingName`. - * @param[in] flags Flags that determine which subscriptions to remove. Valid values are - * the bitwise OR of the following individual flags: - * - @ref AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS - * - @ref AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS - * - @ref AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS - * - * @return On success: - * - #AWS_IOT_SHADOW_SUCCESS - * @return If an MQTT UNSUBSCRIBE packet cannot be sent, one of the following: - * - #AWS_IOT_SHADOW_NO_MEMORY - * - #AWS_IOT_SHADOW_MQTT_ERROR - * - * @note @ref shadow_function_cleanup removes all persistent subscriptions as well. - * - * @warning This function is not safe to call with any in-progress operations! - * It also does not affect delta and updated callbacks registered with @ref - * shadow_function_setdeltacallback and @ref shadow_function_setupdatedcallback, - * respectively. (See documentation for those functions on how to remove their - * callbacks). - */ -/* @[declare_shadow_removepersistentsubscriptions] */ -AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags ); -/* @[declare_shadow_removepersistentsubscriptions] */ - -/*------------------------- Shadow helper functions -------------------------*/ - -/** - * @brief Returns a string that describes an #AwsIotShadowError_t. - * - * Like POSIX's `strerror`, this function returns a string describing a return - * code. In this case, the return code is a Shadow library error code, `status`. - * - * The string returned by this function MUST be treated as read-only: any - * attempt to modify its contents may result in a crash. Therefore, this function - * is limited to usage in logging. - * - * @param[in] status The status to describe. - * - * @return A read-only string that describes `status`. - * - * @warning The string returned by this function must never be modified. - */ -/* @[declare_shadow_strerror] */ -const char * AwsIotShadow_strerror( AwsIotShadowError_t status ); -/* @[declare_shadow_strerror] */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Backwards compatibility macros for previous function names. - */ -#define AwsIotShadow_Delete AwsIotShadow_DeleteAsync -#define AwsIotShadow_TimedDelete AwsIotShadow_DeleteSync -#define AwsIotShadow_Get AwsIotShadow_GetAsync -#define AwsIotShadow_TimedGet AwsIotShadow_GetSync -#define AwsIotShadow_Update AwsIotShadow_UpdateAsync -#define AwsIotShadow_TimedUpdate AwsIotShadow_UpdateSync -/** @endcond */ - -#endif /* ifndef AWS_IOT_SHADOW_H_ */ diff --git a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h deleted file mode 100644 index 6b0e7fc572..0000000000 --- a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h +++ /dev/null @@ -1,641 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_types.h - * @brief Types of the Thing Shadow library. - */ - -#ifndef AWS_IOT_SHADOW_TYPES_H_ -#define AWS_IOT_SHADOW_TYPES_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/*--------------------------- Shadow handle types ---------------------------*/ - -/** - * @handles{shadow,Shadow library} - */ - -/** - * @ingroup shadow_datatypes_handles - * @brief Opaque handle that references an in-progress Shadow operation. - * - * Set as an output parameter of @ref shadow_function_deleteasync, @ref shadow_function_getasync, - * and @ref shadow_function_updateasync. These functions send a message to the Shadow - * service requesting a Shadow operation; the result of this operation is unknown - * until the Shadow service sends a response. Therefore, this handle serves as a - * reference to Shadow operations awaiting a response from the Shadow service. - * - * This reference will be valid from the successful return of @ref shadow_function_deleteasync, - * @ref shadow_function_getasync, or @ref shadow_function_updateasync. The reference becomes - * invalid once the [completion callback](@ref AwsIotShadowCallbackInfo_t) is invoked, - * or @ref shadow_function_wait returns. - * - * @initializer{AwsIotShadowOperation_t,AWS_IOT_SHADOW_OPERATION_INITIALIZER} - * - * @see @ref shadow_function_wait and #AWS_IOT_SHADOW_FLAG_WAITABLE for waiting on - * a reference; or #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an - * asynchronous notification of completion. - */ -typedef struct _shadowOperation * AwsIotShadowOperation_t; - -/*------------------------- Shadow enumerated types -------------------------*/ - -/** - * @enums{shadow,Shadow library} - */ - -/** - * @ingroup shadow_datatypes_enums - * @brief Return codes of [Shadow functions](@ref shadow_functions). - * - * The function @ref shadow_function_strerror can be used to get a return code's - * description. - * - * The values between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) and 500 - * (#AWS_IOT_SHADOW_SERVER_ERROR) may be returned by the Shadow service when it - * rejects a Shadow operation. See [this page] - * (https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-error-messages.html) - * for more information. - */ -typedef enum AwsIotShadowError -{ - /** - * @brief Shadow operation completed successfully. - * - * Functions that may return this value: - * - @ref shadow_function_init - * - @ref shadow_function_wait - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - * - @ref shadow_function_removepersistentsubscriptions - * - * Will also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - * when successful. - */ - AWS_IOT_SHADOW_SUCCESS = 0, - - /** - * @brief Shadow operation queued, awaiting result. - * - * Functions that may return this value: - * - @ref shadow_function_deleteasync - * - @ref shadow_function_getasync - * - @ref shadow_function_updateasync - */ - AWS_IOT_SHADOW_STATUS_PENDING = 1, - - /** - * @brief Initialization failed. - * - * Functions that may return this value: - * - @ref shadow_function_init - */ - AWS_IOT_SHADOW_INIT_FAILED = 2, - - /** - * @brief At least one parameter is invalid. - * - * Functions that may return this value: - * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync - * - @ref shadow_function_getasync and @ref shadow_function_getsync - * - @ref shadow_function_updateasync and @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_BAD_PARAMETER = 3, - - /** - * @brief Shadow operation failed because of memory allocation failure. - * - * Functions that may return this value: - * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync - * - @ref shadow_function_getasync and @ref shadow_function_getsync - * - @ref shadow_function_updateasync and @ref shadow_function_updatesync - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_NO_MEMORY = 4, - - /** - * @brief Shadow operation failed because of failure in MQTT library. - * - * Check the Shadow library logs for the error code returned by the MQTT - * library. - * - * Functions that may return this value: - * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync - * - @ref shadow_function_getasync and @ref shadow_function_getsync - * - @ref shadow_function_updateasync and @ref shadow_function_updatesync - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - * - @ref shadow_function_removepersistentsubscriptions - */ - AWS_IOT_SHADOW_MQTT_ERROR = 5, - - /** - * @brief Response received from Shadow service not understood. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_BAD_RESPONSE = 7, - - /** - * @brief A blocking Shadow operation timed out. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_TIMEOUT = 8, - - /** - * @brief An API function was called before @ref shadow_function_init. - * - * Functions that may return this value: - * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync - * - @ref shadow_function_getasync and @ref shadow_function_getsync - * - @ref shadow_function_updateasync and @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - @ref shadow_function_setdeltacallback - * - @ref shadow_function_setupdatedcallback - */ - AWS_IOT_SHADOW_NOT_INITIALIZED = 11, - - /** - * @brief Shadow operation rejected: Bad request. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_BAD_REQUEST = 400, - - /** - * @brief Shadow operation rejected: Unauthorized. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_UNAUTHORIZED = 401, - - /** - * @brief Shadow operation rejected: Forbidden. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_FORBIDDEN = 403, - - /** - * @brief Shadow operation rejected: Thing not found. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_NOT_FOUND = 404, - - /** - * @brief Shadow operation rejected: Version conflict. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_CONFLICT = 409, - - /** - * @brief Shadow operation rejected: The payload exceeds the maximum size - * allowed. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_TOO_LARGE = 413, - - /** - * @brief Shadow operation rejected: Unsupported document encoding; supported - * encoding is UTF-8. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_UNSUPPORTED = 415, - - /** - * @brief Shadow operation rejected: The Device Shadow service will generate - * this error message when there are more than 10 in-flight requests. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_TOO_MANY_REQUESTS = 429, - - /** - * @brief Shadow operation rejected: Internal service failure. - * - * Functions that may return this value: - * - @ref shadow_function_deletesync - * - @ref shadow_function_getsync - * - @ref shadow_function_updatesync - * - @ref shadow_function_wait - * - * May also be the value of a Shadow operation completion callback's
- * [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) - */ - AWS_IOT_SHADOW_SERVER_ERROR = 500, -} AwsIotShadowError_t; - -/** - * @ingroup shadow_datatypes_enums - * @brief Types of Shadow library callbacks. - * - * One of these values will be placed in #AwsIotShadowCallbackParam_t.callbackType - * to identify the reason for invoking a callback function. - */ -typedef enum AwsIotShadowCallbackType -{ - AWS_IOT_SHADOW_DELETE_COMPLETE, /**< Callback invoked because a [Shadow delete](@ref shadow_function_deleteasync) completed. */ - AWS_IOT_SHADOW_GET_COMPLETE, /**< Callback invoked because a [Shadow get](@ref shadow_function_getasync) completed. */ - AWS_IOT_SHADOW_UPDATE_COMPLETE, /**< Callback invoked because a [Shadow update](@ref shadow_function_updateasync) completed. */ - AWS_IOT_SHADOW_DELTA_CALLBACK, /**< Callback invoked for an incoming message on a [Shadow delta](@ref shadow_function_setdeltacallback) topic. */ - AWS_IOT_SHADOW_UPDATED_CALLBACK /**< Callback invoked for an incoming message on a [Shadow updated](@ref shadow_function_setupdatedcallback) topic. */ -} AwsIotShadowCallbackType_t; - -/*------------------------- Shadow parameter structs ------------------------*/ - -/** - * @paramstructs{shadow,Shadow} - */ - -/** - * @ingroup shadow_datatypes_paramstructs - * @brief Parameter to a Shadow callback function. - * - * @paramfor Shadow callback functions - * - * The Shadow library passes this struct to a callback function whenever a - * Shadow operation completes or a message is received on a Shadow delta or - * updated topic. - * - * The valid members of this struct are different based on - * #AwsIotShadowCallbackParam_t.callbackType. If the callback type is - * #AWS_IOT_SHADOW_DELETE_COMPLETE, #AWS_IOT_SHADOW_GET_COMPLETE, or - * #AWS_IOT_SHADOW_UPDATE_COMPLETE, then #AwsIotShadowCallbackParam_t.operation - * is valid. Otherwise, if the callback type is #AWS_IOT_SHADOW_DELTA_CALLBACK - * or #AWS_IOT_SHADOW_UPDATED_CALLBACK, then #AwsIotShadowCallbackParam_t.callback - * is valid. - * - * @attention Any pointers in this callback parameter may be freed as soon as the - * [callback function](@ref AwsIotShadowCallbackInfo_t.function) returns. Therefore, - * data must be copied if it is needed after the callback function returns. - * @attention The Shadow library may set strings that are not NULL-terminated. - * - * @see #AwsIotShadowCallbackInfo_t for the signature of a callback function. - */ -typedef struct AwsIotShadowCallbackParam -{ - AwsIotShadowCallbackType_t callbackType; /**< @brief Reason for invoking the Shadow callback function to provide context. */ - - const char * pThingName; /**< @brief The Thing Name associated with this Shadow callback. */ - size_t thingNameLength; /**< @brief Length of #AwsIotShadowCallbackParam_t.pThingName. */ - - IotMqttConnection_t mqttConnection; /**< @brief The MQTT connection associated with the Shadow callback. */ - - union - { - /* Valid for completed Shadow operations. */ - struct - { - /* Valid for a completed Shadow GET operation. */ - struct - { - const char * pDocument; /**< @brief Retrieved Shadow document. */ - size_t documentLength; /**< @brief Length of retrieved Shadow document. */ - } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_getasync). */ - - AwsIotShadowError_t result; /**< @brief Result of Shadow operation, e.g. succeeded or failed. */ - AwsIotShadowOperation_t reference; /**< @brief Reference to the Shadow operation that completed. */ - } operation; /**< @brief Information on a completed Shadow operation. */ - - /* Valid for a message on a Shadow delta or updated topic. */ - struct - { - const char * pDocument; /**< @brief Shadow delta or updated document. */ - size_t documentLength; /**< @brief Length of Shadow delta or updated document. */ - } callback; /**< @brief Shadow document from an incoming delta or updated topic. */ - } u; /**< @brief Valid member depends on callback type. */ -} AwsIotShadowCallbackParam_t; - -/** - * @ingroup shadow_datatypes_paramstructs - * @brief Information on a user-provided Shadow callback function. - * - * @paramfor @ref shadow_function_deleteasync, @ref shadow_function_getasync, @ref - * shadow_function_updateasync, @ref shadow_function_setdeltacallback, @ref - * shadow_function_setupdatedcallback - * - * Provides a function to be invoked when a Shadow operation completes or when a - * Shadow document is received on a callback topic (delta or updated). - * - * @initializer{AwsIotShadowCallbackInfo_t,AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER} - */ -typedef struct AwsIotShadowCallbackInfo -{ - void * pCallbackContext; /**< @brief The first parameter to pass to the callback function. */ - - /** - * @brief User-provided callback function signature. - * - * @param[in] pCallbackContext #AwsIotShadowCallbackInfo_t.pCallbackContext - * @param[in] pCallbackParam Details on the outcome of the Shadow - * operation or an incoming Shadow document. - * - * @see #AwsIotShadowCallbackParam_t for more information on the second parameter. - */ - void ( * function )( void * pCallbackContext, - AwsIotShadowCallbackParam_t * pCallbackParam ); -} AwsIotShadowCallbackInfo_t; - -/** - * @ingroup shadow_datatypes_paramstructs - * @brief Information on a Shadow document for @ref shadow_function_getasync or @ref - * shadow_function_updateasync. - * - * @paramfor @ref shadow_function_getasync, @ref shadow_function_updateasync - * - * The valid members of this struct are different based on whether this struct - * is passed to @ref shadow_function_getasync or @ref shadow_function_updateasync. When - * passed to @ref shadow_function_getasync, the `get` member is valid. When passed to - * @ref shadow_function_updateasync, the `update` member is valid. All other members - * must always be set. - * - * @initializer{AwsIotShadowDocumentInfo_t,AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER} - */ -typedef struct AwsIotShadowDocumentInfo -{ - const char * pThingName; /**< @brief The Thing Name associated with this Shadow document. */ - size_t thingNameLength; /**< @brief Length of #AwsIotShadowDocumentInfo_t.pThingName. */ - - IotMqttQos_t qos; /**< @brief QoS when sending a Shadow get or update message. See #IotMqttPublishInfo_t.qos. */ - uint32_t retryLimit; /**< @brief Maximum number of retries for a Shadow get or update message. See #IotMqttPublishInfo_t.retryLimit. */ - uint32_t retryMs; /**< @brief First retry time for a Shadow get or update message. See IotMqttPublishInfo_t.retryMs. */ - - union - { - /* Valid for Shadow get. */ - struct - { - /** - * @brief Function to allocate memory for an incoming Shadow document. - * - * @param[in] documentLength Length of the document that needs to - * be allocated. - * This only needs to be set if #AWS_IOT_SHADOW_FLAG_WAITABLE is passed to - * @ref shadow_function_getasync. - */ - void *( *mallocDocument )( size_t documentLength ); - } get; /**< @brief Valid members for @ref shadow_function_getasync. */ - - /* Valid for Shadow update. */ - struct - { - const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ - size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ - } update; /**< @brief Valid members for @ref shadow_function_updateasync. */ - } u; /**< @brief Valid member depends on operation type. */ -} AwsIotShadowDocumentInfo_t; - -/*------------------------ Shadow defined constants -------------------------*/ - -/** - * @constantspage{shadow,Shadow library} - * - * @section shadow_constants_initializers Shadow Initializers - * @brief Provides default values for the data types of the Shadow library. - * - * @snippet this define_shadow_initializers - * - * All user-facing data types of the Shadow library should be initialized - * using one of the following. - * - * @warning Failing to initialize a Shadow data type with the appropriate - * initializer may result in undefined behavior! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - * - * Example - * @code{c} - * AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - * AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - * AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - * @endcode - * - * @section shadow_constants_flags Shadow Function Flags - * @brief Flags that modify the behavior of Shadow library functions. - * - * Flags should be bitwise-ORed with each other to change the behavior of - * Shadow library functions. - * - * The following flags are valid for the Shadow operation functions: - * @ref shadow_function_deleteasync, @ref shadow_function_getasync, @ref shadow_function_updateasync, - * and their blocking versions. - * - #AWS_IOT_SHADOW_FLAG_WAITABLE
- * @copybrief AWS_IOT_SHADOW_FLAG_WAITABLE - * - #AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS - * - * The following flags are valid for @ref shadow_function_removepersistentsubscriptions. - * These flags are not valid for the Shadow operation functions. - * - #AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS - * - #AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS - * - #AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS
- * @copybrief AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS - * - * @note The values of the flags may change at any time in future versions, but - * their names will remain the same. Additionally, flags which may be used at - * the same time will be bitwise-exclusive of each other. - */ - -/* @[define_shadow_initializers] */ -#define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ -#define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ -#define AWS_IOT_SHADOW_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowOperation_t. */ -/* @[define_shadow_initializers] */ - -/** - * @brief Allows the use of @ref shadow_function_wait for blocking until completion. - * - * This flag is only valid if passed to the functions @ref shadow_function_deleteasync, - * @ref shadow_function_getasync, or @ref shadow_function_updateasync. - * - * An #AwsIotShadowOperation_t MUST be provided if this flag is set. - * Additionally, an #AwsIotShadowCallbackInfo_t MUST NOT be provided. - * - * @note If this flag is set, @ref shadow_function_wait MUST be called to - * clean up resources. - */ -#define AWS_IOT_SHADOW_FLAG_WAITABLE ( 0x00000001U ) - -/** - * @brief Maintain the subscriptions for the Shadow operation topics, even after - * this function returns. - * - * This flag is only valid if passed to the functions @ref shadow_function_deleteasync, - * @ref shadow_function_getasync, @ref shadow_function_updateasync, or their blocking versions. - * - * The Shadow service reports results of Shadow operations by publishing - * messages to MQTT topics. By default, the functions @ref shadow_function_deleteasync, - * @ref shadow_function_getasync, and @ref shadow_function_updateasync subscribe to the - * necessary topics, wait for the Shadow service to publish the result of the - * Shadow operation, then unsubscribe from those topics. This workflow is suitable - * for infrequent Shadow operations, but is inefficient for frequent, periodic - * Shadow operations (where subscriptions for the Shadow operation topics would be - * constantly added and removed). - * - * This flag causes @ref shadow_function_deleteasync, @ref shadow_function_getasync, or - * @ref shadow_function_updateasync to maintain Shadow operation topic subscriptions, - * even after the function returns. These subscriptions may then be used by a - * future call to the same function. - * - * This flags only needs to be set once, after which subscriptions are maintained - * and reused for a specific Thing Name and Shadow function. The function @ref - * shadow_function_removepersistentsubscriptions may be used to remove - * subscriptions maintained by this flag. - */ -#define AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002UL ) - -/** - * @brief Remove the persistent subscriptions from a Shadow delete operation. - * - * This flag is only valid if passed to the function @ref - * shadow_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref shadow_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_deleteasync or @ref shadow_function_deletesync. - * - * @warning Do not call @ref shadow_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Shadow delete operations. - */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS ( 0x00000001UL ) - -/** - * @brief Remove the persistent subscriptions from a Shadow get operation. - * - * This flag is only valid if passed to the function @ref - * shadow_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref shadow_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_getasync or @ref shadow_function_getsync. - * - * @warning Do not call @ref shadow_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Shadow get operations. - */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS ( 0x00000002UL ) - -/** - * @brief Remove the persistent subscriptions from a Shadow update operation. - * - * This flag is only valid if passed to the function @ref - * shadow_function_removepersistentsubscriptions. - * - * This flag may be passed to @ref shadow_function_removepersistentsubscriptions - * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_updateasync or @ref shadow_function_updatesync. - * - * @warning Do not call @ref shadow_function_removepersistentsubscriptions with - * this flag for Thing Names with any in-progress Shadow update operations. - */ -#define AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000004UL ) - -#endif /* ifndef AWS_IOT_SHADOW_TYPES_H_ */ diff --git a/libraries/aws/shadow/lexicon.txt b/libraries/aws/shadow/lexicon.txt deleted file mode 100644 index a115c8cd22..0000000000 --- a/libraries/aws/shadow/lexicon.txt +++ /dev/null @@ -1,94 +0,0 @@ -api -aws -awsiotshadow -awsiotshadowcallbackinfo -awsiotshadowcallbackparam -awsiotshadowcallbacktype -awsiotshadowdocumentinfo -awsiotshadowerror -awsiotshadowoperation -callbacktype -clienttoken -clienttokenlength -com -config -const -coverity -deleteasync -deletesync -deltacallbackfunction -deltacallbackwrapper -developerguide -deviceon -documentlength -endcond -endif -expectedtype -getasync -getsync -html -http -https -ifndef -init -int -iot -iotlink -iotlogdebug -iotmqttconnection -iotmqttpublishinfo -iotmqttqos -iotsemaphore -isn -json -malloc -metadata -mqtt -mqttconnection -mutex -mutexes -onlinepubs -opengroup -operationmatchparams -org -param -pcallbackcontext -pcallbackparam -pclienttoken -pdeltacallback -pdocument -pinuseshadowoperations -pinuseshadowsubscriptions -processretrieveddocument -pshadowoperations -pshadowsubscriptions -psubscription -pthingname -pupdatedcallback -pupdatedocument -qos -removepersistentsubscriptions -retrylimit -retryms -sdk -setdeltacallback -setupdatedcallback -shadowoperation -shadowoperationtype -shadowsubscription -strerror -structs -suback -thingnamelength -uint -updateasync -updatecomplete -updatedcallbackfunction -updatedcallbackwrapper -updatedocumentlength -updateinfo -updatesync -vsnprintf -waitable -waitsem -waitsemaphore diff --git a/libraries/aws/shadow/src/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c deleted file mode 100644 index 2441b28ff4..0000000000 --- a/libraries/aws/shadow/src/aws_iot_shadow_api.c +++ /dev/null @@ -1,1303 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_api.c - * @brief Implements the user-facing functions of the Shadow library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Validate Shadow configuration settings. */ -#if AWS_IOT_SHADOW_ENABLE_ASSERTS != 0 && AWS_IOT_SHADOW_ENABLE_ASSERTS != 1 - #error "AWS_IOT_SHADOW_ENABLE_ASSERTS must be 0 or 1." -#endif -#if AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS <= 0 - #error "AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS cannot be 0 or negative." -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Check if the library is initialized. - * - * @return `true` if AwsIotShadow_Init was called; `false` otherwise. - */ -static bool _checkInit( void ); - -/** - * @brief Checks Thing Name and flags parameters passed to Shadow API functions. - * - * @param[in] type The Shadow API function type. - * @param[in] pThingName Thing Name passed to Shadow API function. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] flags Flags passed to Shadow API function. - * @param[in] pCallbackInfo Callback info passed to Shadow API function. - * @param[in] pOperation Operation reference pointer passed to Shadow API function. - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_PARAMETER. - */ -static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - const AwsIotShadowOperation_t * pOperation ); - -/** - * @brief Checks document info parameter passed to Shadow API functions. - * - * @param[in] type The Shadow API function type. - * @param[in] flags Flags passed to Shadow API function. - * @param[in] pDocumentInfo Document info passed to Shadow API function. - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_PARAMETER. - */ -static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, - uint32_t flags, - const AwsIotShadowDocumentInfo_t * pDocumentInfo ); - -/** - * @brief Common function for setting Shadow callbacks. - * - * @param[in] mqttConnection The MQTT connection to use. - * @param[in] type Type of Shadow callback. - * @param[in] pThingName Thing Name for Shadow callback. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] pCallbackInfo Callback information to set. - * - * @return #AWS_IOT_SHADOW_SUCCESS, #AWS_IOT_SHADOW_BAD_PARAMETER, - * #AWS_IOT_SHADOW_NO_MEMORY, or #AWS_IOT_SHADOW_MQTT_ERROR. - */ -static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, - _shadowCallbackType_t type, - const char * pThingName, - size_t thingNameLength, - const AwsIotShadowCallbackInfo_t * pCallbackInfo ); - -/** - * @brief Change the subscriptions for Shadow callbacks, either by subscribing - * or unsubscribing. - * - * @param[in] mqttConnection The MQTT connection to use. - * @param[in] type Type of Shadow callback. - * @param[in] pSubscription Shadow subscriptions object for callback. - * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or - * @ref mqtt_function_unsubscribesync. - * - * @return #AWS_IOT_SHADOW_SUCCESS, #AWS_IOT_SHADOW_NO_MEMORY, or - * #AWS_IOT_SHADOW_MQTT_ERROR. - */ -static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, - _shadowCallbackType_t type, - _shadowSubscription_t * pSubscription, - AwsIotMqttFunction_t mqttOperation ); - -/** - * @brief Common function for incoming Shadow callbacks. - * - * @param[in] type Shadow callback type. - * @param[in] pMessage The received Shadow callback document (as an MQTT PUBLISH - * message). - */ -static void _callbackWrapperCommon( _shadowCallbackType_t type, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a document is received on the Shadow DELTA callback. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). - */ -static void _deltaCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a document is received on the Shadow UPDATED callback. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage The received UPDATED document (as an MQTT PUBLISH message). - */ -static void _updatedCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Tracks whether @ref shadow_function_init has been called. - * - * API functions will fail if @ref shadow_function_init was not called. - */ -static uint32_t _initCalled = 0U; - -/** - * @brief Timeout used for MQTT operations. - */ -uint32_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; - -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - -/** - * @brief Printable names for the Shadow callbacks. - */ - const char * const _pAwsIotShadowCallbackNames[] = - { - "DELTA", - "UPDATED" - }; -#endif - -/*-----------------------------------------------------------*/ - -static bool _checkInit( void ) -{ - bool status = true; - - if( _initCalled == 0U ) - { - IotLogError( "AwsIotShadow_Init was not called." ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - const AwsIotShadowOperation_t * pOperation ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - - /* Type is not used when logging is disabled. */ - ( void ) type; - - /* Check Thing Name. */ - if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == true ) - { - /* Check the waitable operation flag. */ - if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - /* Check that a reference pointer is provided for a waitable operation. */ - if( pOperation != NULL ) - { - /* A callback should not be set for a waitable operation. */ - if( pCallbackInfo != NULL ) - { - IotLogError( "Callback should not be set for a waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - } - else - { - IotLogError( "Reference must be set for a waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* A callback info must be passed to a non-waitable GET. */ - if( ( type == SHADOW_GET ) && - ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) && - ( pCallbackInfo == NULL ) ) - { - IotLogError( "Callback info must be provided for non-waitable Shadow GET." ); - - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - else - { - /* Check that a callback function is set. */ - if( ( pCallbackInfo != NULL ) && - ( pCallbackInfo->function == NULL ) ) - { - IotLogError( "Callback function must be set for Shadow %s callback.", - _pAwsIotShadowOperationNames[ type ] ); - - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - } - } - } - else - { - IotLogError( "Thing Name for Shadow %s is not valid.", - _pAwsIotShadowOperationNames[ type ] ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, - uint32_t flags, - const AwsIotShadowDocumentInfo_t * pDocumentInfo ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - - /* This function should only be called for Shadow GET or UPDATE. */ - AwsIotShadow_Assert( ( type == SHADOW_GET ) || ( type == SHADOW_UPDATE ) ); - - /* Check QoS. */ - if( ( pDocumentInfo->qos != IOT_MQTT_QOS_0 ) && ( pDocumentInfo->qos != IOT_MQTT_QOS_1 ) ) - { - IotLogError( "QoS for Shadow %d must be 0 or 1.", - _pAwsIotShadowOperationNames[ type ] ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - /* Check the retry parameters. */ - else if( ( pDocumentInfo->retryLimit > 0U ) && ( pDocumentInfo->retryMs == 0U ) ) - { - IotLogError( "Retry time of Shadow %s must be positive.", - _pAwsIotShadowOperationNames[ type ] ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - else - { - /* Check members relevant to a Shadow GET. */ - if( type == SHADOW_GET ) - { - /* Check memory allocation function for waitable GET. */ - if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && - ( pDocumentInfo->u.get.mallocDocument == NULL ) ) - { - IotLogError( "Memory allocation function must be set for waitable Shadow GET." ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - } - /* Check members relevant to a Shadow UPDATE. */ - else - { - /* Check UPDATE document pointer and length. */ - if( ( pDocumentInfo->u.update.pUpdateDocument == NULL ) || - ( pDocumentInfo->u.update.updateDocumentLength == 0U ) ) - { - IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" - " have length 0." ); - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, - _shadowCallbackType_t type, - const char * pThingName, - size_t thingNameLength, - const AwsIotShadowCallbackInfo_t * pCallbackInfo ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - bool subscriptionMutexLocked = false; - _shadowSubscription_t * pSubscription = NULL; - - /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == true ) - { - /* Check parameters. */ - status = _validateThingNameFlags( _AwsIotShadow_IntToShadowOperationType( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ), - pThingName, - thingNameLength, - 0, - pCallbackInfo, - NULL ); - } - else - { - status = AWS_IOT_SHADOW_NOT_INITIALIZED; - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - IotLogInfo( "(%.*s) Modifying Shadow %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - /* Lock the subscription list mutex to check for an existing subscription - * object. */ - IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); - subscriptionMutexLocked = true; - - /* Check for an existing subscription. This function will attempt to allocate - * a new subscription if not found. */ - pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength, - true ); - - if( pSubscription == NULL ) - { - /* No existing subscription was found, and no new subscription could be - * allocated. */ - status = AWS_IOT_SHADOW_NO_MEMORY; - } - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Check for an existing callback. */ - if( pSubscription->callbacks[ type ].function != NULL ) - { - /* Replace existing callback. */ - if( pCallbackInfo != NULL ) - { - IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - pSubscription->callbacks[ type ] = *pCallbackInfo; - } - /* Remove existing callback. */ - else - { - IotLogInfo( "(%.*s) Removing existing %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - /* Unsubscribe, then clear the callback information. */ - ( void ) _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_UnsubscribeSync ); - ( void ) memset( &( pSubscription->callbacks[ type ] ), - 0x00, - sizeof( AwsIotShadowCallbackInfo_t ) ); - - /* Check if this subscription object can be removed. */ - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); - } - } - /* No existing callback. */ - else - { - /* Add new callback. */ - if( pCallbackInfo != NULL ) - { - IotLogInfo( "(%.*s) Adding new %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - status = _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_SubscribeSync ); - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - pSubscription->callbacks[ type ] = *pCallbackInfo; - } - else - { - /* On failure, check if this subscription can be removed. */ - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); - } - } - } - } - - if( subscriptionMutexLocked == true ) - { - IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); - } - - IotLogInfo( "(%.*s) Shadow %s callback operation complete with result %s.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ], - AwsIotShadow_strerror( status ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, - _shadowCallbackType_t type, - _shadowSubscription_t * pSubscription, - AwsIotMqttFunction_t mqttOperation ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - char * pTopicFilter = NULL; - uint16_t operationTopicLength = 0; - - /* Lookup table for Shadow callback suffixes. */ - const char * const pCallbackSuffix[ SHADOW_CALLBACK_COUNT ] = - { - SHADOW_DELTA_SUFFIX, /* Delta callback. */ - SHADOW_UPDATED_SUFFIX /* Updated callback. */ - }; - - /* Lookup table for Shadow callback suffix lengths. */ - const uint16_t pCallbackSuffixLength[ SHADOW_CALLBACK_COUNT ] = - { - SHADOW_DELTA_SUFFIX_LENGTH, /* Delta callback. */ - SHADOW_UPDATED_SUFFIX_LENGTH /* Updated callback. */ - }; - - /* Lookup table for Shadow callback function wrappers. */ - const AwsIotMqttCallbackFunction_t pCallbackWrapper[ SHADOW_CALLBACK_COUNT ] = - { - _deltaCallbackWrapper, /* Delta callback. */ - _updatedCallbackWrapper, /* Updated callback. */ - }; - - /* MQTT operation may only be subscribe or unsubscribe. */ - AwsIotShadow_Assert( ( mqttOperation == IotMqtt_SubscribeSync ) || - ( mqttOperation == IotMqtt_UnsubscribeSync ) ); - - /* Use the subscription's topic buffer if available. */ - if( pSubscription->pTopicBuffer != NULL ) - { - pTopicFilter = pSubscription->pTopicBuffer; - } - - /* Generate the prefix portion of the Shadow callback topic filter. Both - * callbacks share the same callback as the Shadow Update operation. */ - status = _AwsIotShadow_GenerateShadowTopic( SHADOW_UPDATE, - pSubscription->pThingName, - pSubscription->thingNameLength, - &pTopicFilter, - &operationTopicLength ); - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Place the callback suffix in the topic filter. */ - ( void ) memcpy( pTopicFilter + operationTopicLength, - pCallbackSuffix[ type ], - pCallbackSuffixLength[ type ] ); - - IotLogDebug( "%s subscription for %.*s", - mqttOperation == IotMqtt_SubscribeSync ? "Adding" : "Removing", - operationTopicLength + pCallbackSuffixLength[ type ], - pTopicFilter ); - - /* Set the members of the MQTT subscription. */ - subscription.qos = IOT_MQTT_QOS_1; - subscription.pTopicFilter = pTopicFilter; - subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); - subscription.callback.pCallbackContext = NULL; - subscription.callback.function = pCallbackWrapper[ type ]; - - /* Call the MQTT operation function. */ - mqttStatus = mqttOperation( mqttConnection, - &subscription, - 1, - 0, - _AwsIotShadowMqttTimeoutMs ); - - /* Check the result of the MQTT operation. */ - if( mqttStatus == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "Successfully %s %.*s Shadow %s callback.", - mqttOperation == IotMqtt_SubscribeSync ? "subscribed to" : "unsubscribed from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - } - else - { - IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == IotMqtt_SubscribeSync ? "subscribe to" : "unsubscribe from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ], - IotMqtt_strerror( mqttStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( mqttStatus ); - } - } - - /* MQTT subscribe should check the subscription topic buffer. */ - if( mqttOperation == IotMqtt_SubscribeSync ) - { - /* If the current subscription has no topic buffer, assign it the current - * topic buffer. */ - if( pSubscription->pTopicBuffer == NULL ) - { - pSubscription->pTopicBuffer = pTopicFilter; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _callbackWrapperCommon( _shadowCallbackType_t type, - IotMqttCallbackParam_t * pMessage ) -{ - AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - AwsIotShadowCallbackParam_t callbackParam = { .callbackType = AWS_IOT_SHADOW_DELETE_COMPLETE }; - _shadowSubscription_t * pSubscription = NULL; - const char * pThingName = NULL; - size_t thingNameLength = 0; - - /* Parse the Thing Name from the topic. */ - if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength, - &pThingName, - &thingNameLength ) == true ) - { - /* Search for a matching subscription. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - - pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength, - false ); - - if( pSubscription == NULL ) - { - /* No subscription found. */ - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - } - else - { - /* Ensure that a callback function is set. */ - AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); - - /* Copy the subscription callback info, as the subscription may be modified - * when the subscriptions mutex is released. */ - callbackInfo = pSubscription->callbacks[ type ]; - - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - /* Set the callback type. Shadow callbacks are enumerated after the operations. */ - callbackParam.callbackType = _AwsIotShadow_IntToShadowCallbackType( ( ( int32_t ) type ) + SHADOW_OPERATION_COUNT ); - - /* Set the remaining members of the callback param. */ - callbackParam.mqttConnection = pMessage->mqttConnection; - callbackParam.pThingName = pThingName; - callbackParam.thingNameLength = thingNameLength; - callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; - callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; - - /* Invoke the callback function. */ - callbackInfo.function( callbackInfo.pCallbackContext, - &callbackParam ); - } - } -} - -/*-----------------------------------------------------------*/ - -static void _deltaCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - _callbackWrapperCommon( DELTA_CALLBACK, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _updatedCallbackWrapper( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - _callbackWrapperCommon( UPDATED_CALLBACK, pMessage ); -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - bool listInitStatus = false; - - if( _initCalled == 0U ) - { - /* Initialize Shadow lists and mutexes. */ - listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, - &_AwsIotShadowSubscriptions, - &_AwsIotShadowPendingOperationsMutex, - &_AwsIotShadowSubscriptionsMutex ); - - if( listInitStatus == false ) - { - IotLogInfo( "Failed to create Shadow lists." ); - - status = AWS_IOT_SHADOW_INIT_FAILED; - } - else - { - /* Save the MQTT timeout option. */ - if( mqttTimeoutMs != 0U ) - { - _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; - } - - /* Set the flag that specifies initialization is complete. */ - _initCalled = 1U; - - IotLogInfo( "Shadow library successfully initialized." ); - } - } - else - { - IotLogWarn( "AwsIotShadow_Init called with library already initialized." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void AwsIotShadow_Cleanup( void ) -{ - if( _initCalled == 1U ) - { - _initCalled = 0U; - - /* Remove and free all items in the Shadow pending operation list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), - _AwsIotShadow_DestroyOperation, - offsetof( _shadowOperation_t, link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Remove and free all items in the Shadow subscription list. */ - IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); - IotListDouble_RemoveAll( &( _AwsIotShadowSubscriptions ), - _AwsIotShadow_DestroySubscription, - offsetof( _shadowSubscription_t, link ) ); - IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); - - /* Destroy Shadow library mutexes. */ - IotMutex_Destroy( &( _AwsIotShadowPendingOperationsMutex ) ); - IotMutex_Destroy( &( _AwsIotShadowSubscriptionsMutex ) ); - - /* Restore the default MQTT timeout. */ - _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; - - IotLogInfo( "Shadow library cleanup done." ); - } - else - { - IotLogWarn( "AwsIotShadow_Init was not called before AwsIotShadow_Cleanup." ); - } -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pDeleteOperation ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - _shadowOperation_t * pOperation = NULL; - - /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == true ) - { - /* Validate the Thing Name and flags for Shadow DELETE. */ - status = _validateThingNameFlags( SHADOW_DELETE, - pThingName, - thingNameLength, - flags, - pCallbackInfo, - pDeleteOperation ); - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Allocate a new Shadow operation for DELETE. */ - status = _AwsIotShadow_CreateOperation( &pOperation, - SHADOW_DELETE, - flags, - pCallbackInfo ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Check the members set by Shadow operation creation. */ - AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == SHADOW_DELETE ); - AwsIotShadow_Assert( pOperation->flags == flags ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - - /* Set the reference if provided. This must be done before the Shadow operation - * is processed. */ - if( pDeleteOperation != NULL ) - { - *pDeleteOperation = pOperation; - } - - /* Process the Shadow operation. This subscribes to any required topics and - * sends the MQTT message for the Shadow operation. */ - status = _AwsIotShadow_ProcessOperation( mqttConnection, - pThingName, - thingNameLength, - pOperation, - NULL ); - - /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pDeleteOperation != NULL ) ) - { - *pDeleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - } - } - } - else - { - status = AWS_IOT_SHADOW_NOT_INITIALIZED; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_DeleteSync( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - uint32_t timeoutMs ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; - - /* Call the asynchronous Shadow delete function. */ - status = AwsIotShadow_DeleteAsync( mqttConnection, - pThingName, - thingNameLength, - flags, - NULL, - &deleteOperation ); - - /* Wait for the Shadow delete operation to complete. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - status = AwsIotShadow_Wait( deleteOperation, timeoutMs, NULL, NULL ); - } - - /* Ensure that a status was set. */ - AwsIotShadow_Assert( status != AWS_IOT_SHADOW_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pGetOperation ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - _shadowOperation_t * pOperation = NULL; - - /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == false ) - { - status = AWS_IOT_SHADOW_NOT_INITIALIZED; - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Validate the Thing Name and flags for Shadow GET. */ - status = _validateThingNameFlags( SHADOW_GET, - pGetInfo->pThingName, - pGetInfo->thingNameLength, - flags, - pCallbackInfo, - pGetOperation ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Validate the document info for Shadow GET. */ - status = _validateDocumentInfo( SHADOW_GET, - flags, - pGetInfo ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Allocate a new Shadow operation for GET. */ - status = _AwsIotShadow_CreateOperation( &pOperation, - SHADOW_GET, - flags, - pCallbackInfo ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Check the members set by Shadow operation creation. */ - AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == SHADOW_GET ); - AwsIotShadow_Assert( pOperation->flags == flags ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - - /* Copy the memory allocation function. */ - pOperation->u.get.mallocDocument = pGetInfo->u.get.mallocDocument; - - /* Set the reference if provided. This must be done before the Shadow operation - * is processed. */ - if( pGetOperation != NULL ) - { - *pGetOperation = pOperation; - } - - /* Process the Shadow operation. This subscribes to any required topics and - * sends the MQTT message for the Shadow operation. */ - status = _AwsIotShadow_ProcessOperation( mqttConnection, - pGetInfo->pThingName, - pGetInfo->thingNameLength, - pOperation, - pGetInfo ); - - /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pGetOperation != NULL ) ) - { - *pGetOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_GetSync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - uint32_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; - - /* Call the asynchronous Shadow get function. */ - status = AwsIotShadow_GetAsync( mqttConnection, - pGetInfo, - flags, - NULL, - &getOperation ); - - /* Wait for the Shadow get operation to complete. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - status = AwsIotShadow_Wait( getOperation, - timeoutMs, - pShadowDocument, - pShadowDocumentLength ); - } - - /* Ensure that a status was set. */ - AwsIotShadow_Assert( status != AWS_IOT_SHADOW_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pUpdateOperation ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - _shadowOperation_t * pOperation = NULL; - const char * pClientToken = NULL; - size_t clientTokenLength = 0; - - /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == false ) - { - status = AWS_IOT_SHADOW_NOT_INITIALIZED; - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Validate the Thing Name and flags for Shadow UPDATE. */ - status = _validateThingNameFlags( SHADOW_UPDATE, - pUpdateInfo->pThingName, - pUpdateInfo->thingNameLength, - flags, - pCallbackInfo, - pUpdateOperation ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Validate the document info for Shadow UPDATE. */ - status = _validateDocumentInfo( SHADOW_UPDATE, - flags, - pUpdateInfo ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Check UPDATE document for a client token. */ - if( AwsIot_GetClientToken( pUpdateInfo->u.update.pUpdateDocument, - pUpdateInfo->u.update.updateDocumentLength, - &pClientToken, - &clientTokenLength ) == false ) - { - IotLogError( "Shadow document for Shadow UPDATE does not contain a valid client token." ); - - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Allocate a new Shadow operation for UPDATE. */ - status = _AwsIotShadow_CreateOperation( &pOperation, - SHADOW_UPDATE, - flags, - pCallbackInfo ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Check the members set by Shadow operation creation. */ - AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == SHADOW_UPDATE ); - AwsIotShadow_Assert( pOperation->flags == flags ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - - /* Allocate memory for the client token. */ - pOperation->u.update.pClientToken = AwsIotShadow_MallocString( clientTokenLength ); - - if( pOperation->u.update.pClientToken == NULL ) - { - IotLogError( "Failed to allocate memory for Shadow update client token." ); - _AwsIotShadow_DestroyOperation( pOperation ); - - status = AWS_IOT_SHADOW_NO_MEMORY; - } - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Copy the client token. The client token must be copied in case the application - * frees the buffer containing it. */ - ( void ) memcpy( ( void * ) pOperation->u.update.pClientToken, - pClientToken, - clientTokenLength ); - pOperation->u.update.clientTokenLength = clientTokenLength; - - /* Set the reference if provided. This must be done before the Shadow operation - * is processed. */ - if( pUpdateOperation != NULL ) - { - *pUpdateOperation = pOperation; - } - - /* Process the Shadow operation. This subscribes to any required topics and - * sends the MQTT message for the Shadow operation. */ - status = _AwsIotShadow_ProcessOperation( mqttConnection, - pUpdateInfo->pThingName, - pUpdateInfo->thingNameLength, - pOperation, - pUpdateInfo ); - - /* If the Shadow operation failed, clear the now invalid reference. */ - if( ( status != AWS_IOT_SHADOW_STATUS_PENDING ) && ( pUpdateOperation != NULL ) ) - { - *pUpdateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_UpdateSync( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Set the waitable flag. */ - flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; - - /* Call the asynchronous Shadow update function. */ - status = AwsIotShadow_UpdateAsync( mqttConnection, - pUpdateInfo, - flags, - NULL, - &updateOperation ); - - /* Wait for the Shadow update operation to complete. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - status = AwsIotShadow_Wait( updateOperation, timeoutMs, NULL, NULL ); - } - - /* Ensure that a status was set. */ - AwsIotShadow_Assert( status != AWS_IOT_SHADOW_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, - uint32_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - - /* Check that AwsIotShadow_Init was called. */ - if( _checkInit() == false ) - { - status = AWS_IOT_SHADOW_NOT_INITIALIZED; - } - /* Check that reference is set. */ - else if( operation == NULL ) - { - IotLogError( "Operation reference cannot be NULL." ); - - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - /* Check that reference is waitable. */ - else if( ( operation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) - { - IotLogError( "Operation is not waitable." ); - - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - /* Check that output parameters are set for a Shadow GET. */ - else if( ( operation->type == SHADOW_GET ) && ( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) ) - { - IotLogError( "Output buffer and size pointer must be set for Shadow GET." ); - - status = AWS_IOT_SHADOW_BAD_PARAMETER; - } - else - { - /* Wait for a response to the Shadow operation. */ - if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), - timeoutMs ) == true ) - { - status = operation->status; - } - else - { - status = AWS_IOT_SHADOW_TIMEOUT; - } - - /* Remove the completed operation from the pending operation list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_Remove( &( operation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( operation, - operation->pSubscription->pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - /* Set the output parameters for Shadow GET. */ - if( ( operation->type == SHADOW_GET ) && - ( status == AWS_IOT_SHADOW_SUCCESS ) ) - { - *pShadowDocument = operation->u.get.pDocument; - *pShadowDocumentLength = operation->u.get.documentLength; - } - - /* Destroy the Shadow operation. */ - _AwsIotShadow_DestroyOperation( operation ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pDeltaCallback ) -{ - /* Flags are currently not used by this function. */ - ( void ) flags; - - return _setCallbackCommon( mqttConnection, - DELTA_CALLBACK, - pThingName, - thingNameLength, - pDeltaCallback ); -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pUpdatedCallback ) -{ - /* Flags are currently not used by this function. */ - ( void ) flags; - - return _setCallbackCommon( mqttConnection, - UPDATED_CALLBACK, - pThingName, - thingNameLength, - pUpdatedCallback ); -} - -/*-----------------------------------------------------------*/ - -const char * AwsIotShadow_strerror( AwsIotShadowError_t status ) -{ - const char * pMessage = NULL; - - switch( status ) - { - case AWS_IOT_SHADOW_SUCCESS: - pMessage = "SUCCESS"; - break; - - case AWS_IOT_SHADOW_STATUS_PENDING: - pMessage = "STATUS PENDING"; - break; - - case AWS_IOT_SHADOW_INIT_FAILED: - pMessage = "INITIALIZATION FAILED"; - break; - - case AWS_IOT_SHADOW_BAD_PARAMETER: - pMessage = "BAD PARAMETER"; - break; - - case AWS_IOT_SHADOW_NO_MEMORY: - pMessage = "NO MEMORY"; - break; - - case AWS_IOT_SHADOW_MQTT_ERROR: - pMessage = "MQTT LIBRARY ERROR"; - break; - - case AWS_IOT_SHADOW_BAD_RESPONSE: - pMessage = "BAD RESPONSE RECEIVED"; - break; - - case AWS_IOT_SHADOW_TIMEOUT: - pMessage = "TIMEOUT"; - break; - - case AWS_IOT_SHADOW_NOT_INITIALIZED: - pMessage = "NOT INITIALIZED"; - break; - - case AWS_IOT_SHADOW_BAD_REQUEST: - pMessage = "REJECTED: 400 BAD REQUEST"; - break; - - case AWS_IOT_SHADOW_UNAUTHORIZED: - pMessage = "REJECTED: 401 UNAUTHORIZED"; - break; - - case AWS_IOT_SHADOW_FORBIDDEN: - pMessage = "REJECTED: 403 FORBIDDEN"; - break; - - case AWS_IOT_SHADOW_NOT_FOUND: - pMessage = "REJECTED: 404 NOT FOUND"; - break; - - case AWS_IOT_SHADOW_CONFLICT: - pMessage = "REJECTED: 409 VERSION CONFLICT"; - break; - - case AWS_IOT_SHADOW_TOO_LARGE: - pMessage = "REJECTED: 413 PAYLOAD TOO LARGE"; - break; - - case AWS_IOT_SHADOW_UNSUPPORTED: - pMessage = "REJECTED: 415 UNSUPPORTED ENCODING"; - break; - - case AWS_IOT_SHADOW_TOO_MANY_REQUESTS: - pMessage = "REJECTED: 429 TOO MANY REQUESTS"; - break; - - case AWS_IOT_SHADOW_SERVER_ERROR: - pMessage = "500 SERVER ERROR"; - break; - - default: - pMessage = "INVALID STATUS"; - break; - } - - return pMessage; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_operation.c b/libraries/aws/shadow/src/aws_iot_shadow_operation.c deleted file mode 100644 index 07abc2a25e..0000000000 --- a/libraries/aws/shadow/src/aws_iot_shadow_operation.c +++ /dev/null @@ -1,977 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_operation.c - * @brief Implements functions that process Shadow operations. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief First parameter to #_shadowOperationMatch. - */ -typedef struct _operationMatchParams -{ - _shadowOperationType_t type; /**< @brief DELETE, GET, or UPDATE. */ - const char * pThingName; /**< @brief Thing Name of Shadow operation. */ - size_t thingNameLength; /**< @brief Length of #_operationMatchParams_t.pThingName. */ - const char * pDocument; /**< @brief Shadow UPDATE response document. */ - size_t documentLength; /**< @brief Length of #_operationMatchParams_t.pDocument. */ -} _operationMatchParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Match a received Shadow response with a Shadow operation awaiting a - * response. - * - * @param[in] pOperationLink Pointer to the link member of the #_shadowOperation_t - * to check. - * @param[in] pMatch Pointer to an #_operationMatchParams_t. - * - * @return `true` if `pMatch` matches the received response; `false` otherwise. - */ -static bool _shadowOperationMatch( const IotLink_t * pOperationLink, - void * pMatch ); - -/** - * @brief Common function for processing received Shadow responses. - * - * @param[in] type DELETE, GET, or UPDATE. - * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). - */ -static void _commonOperationCallback( _shadowOperationType_t type, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a Shadow response is received for Shadow DELETE. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). - */ -static void _deleteCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Invoked when a Shadow response is received for a Shadow GET. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). - */ -static void _getCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Process an incoming Shadow document received when a Shadow GET is - * accepted. - * - * @param[in] pOperation The GET operation associated with the incoming Shadow - * document. - * @param[in] pPublishInfo The received Shadow document (as an MQTT PUBLISH - * message). - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. Memory allocation - * only happens for a waitable `pOperation`. - */ -static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, - const IotMqttPublishInfo_t * pPublishInfo ); - -/** - * @brief Invoked when a Shadow response is received for a Shadow UPDATE. - * - * @param[in] pArgument Ignored. - * @param[in] pMessage Received Shadow response (as an MQTT PUBLISH message). - */ -static void _updateCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ); - -/** - * @brief Notify of a completed Shadow operation. - * - * @param[in] pOperation The operation which completed. - * - * Depending on the parameters passed to a user-facing Shadow function, the - * notification will cause @ref shadow_function_wait to return or invoke a - * user-provided callback. - */ -static void _notifyCompletion( _shadowOperation_t * pOperation ); - -/** - * @brief Get a Shadow subscription to use with a Shadow operation. - * - * This function may use an existing Shadow subscription, or it may allocate a - * new one. - * - * @param[in] pThingName Thing Name associated with operation. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] pTopicBuffer Contains the topic to use for subscribing. - * @param[in] operationTopicLength The length of the base topic in `pTopicBuffer`. - * @param[in] pOperation Shadow operation that needs a subscription. - * @param[out] pFreeTopicBuffer Whether the caller may free `pTopicBuffer` - * (which may be assigned to a subscription). - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY - */ -static AwsIotShadowError_t _findSubscription( const char * pThingName, - size_t thingNameLength, - char * pTopicBuffer, - uint16_t operationTopicLength, - _shadowOperation_t * pOperation, - bool * pFreeTopicBuffer ); - -/*-----------------------------------------------------------*/ - -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - -/** - * @brief Printable names for each of the Shadow operations. - */ - const char * const _pAwsIotShadowOperationNames[] = - { - "DELETE", - "GET", - "UPDATE", - "SET DELTA", - "SET UPDATED" - }; -#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - -/** - * @brief List of active Shadow operations awaiting a response from the Shadow - * service. - */ -IotListDouble_t _AwsIotShadowPendingOperations = { 0 }; - -/** - * @brief Protects #_AwsIotShadowPendingOperations from concurrent access. - */ -IotMutex_t _AwsIotShadowPendingOperationsMutex; - -/*-----------------------------------------------------------*/ - -_shadowOperationType_t _AwsIotShadow_IntToShadowOperationType( uint32_t n ) -{ - _shadowOperationType_t val = SHADOW_DELETE; - - switch( n ) - { - case 0: - val = SHADOW_DELETE; - break; - - case 1: - val = SHADOW_GET; - break; - - case 2: - val = SHADOW_UPDATE; - break; - - case 3: - val = SET_DELTA_CALLBACK; - break; - - case 4: - val = SET_UPDATED_CALLBACK; - break; - - default: - AwsIotShadow_Assert( n < 5U ); - break; - } - - return val; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowCallbackType_t _AwsIotShadow_IntToShadowCallbackType( uint32_t n ) -{ - AwsIotShadowCallbackType_t val = AWS_IOT_SHADOW_DELETE_COMPLETE; - - switch( n ) - { - case 0: - val = AWS_IOT_SHADOW_DELETE_COMPLETE; - break; - - case 1: - val = AWS_IOT_SHADOW_GET_COMPLETE; - break; - - case 2: - val = AWS_IOT_SHADOW_UPDATE_COMPLETE; - break; - - case 3: - val = AWS_IOT_SHADOW_DELTA_CALLBACK; - break; - - case 4: - val = AWS_IOT_SHADOW_UPDATED_CALLBACK; - break; - - default: - AwsIotShadow_Assert( n < 5U ); - break; - } - - return val; -} - -/*-----------------------------------------------------------*/ - -static bool _shadowOperationMatch( const IotLink_t * pOperationLink, - void * pMatch ) -{ - /* Because this function is called from a container function, the given link - * must never be NULL. */ - AwsIotShadow_Assert( pOperationLink != NULL ); - - _shadowOperation_t * pOperation = IotLink_Container( _shadowOperation_t, - pOperationLink, - link ); - _operationMatchParams_t * pParam = ( _operationMatchParams_t * ) pMatch; - _shadowSubscription_t * pSubscription = pOperation->pSubscription; - const char * pClientToken = NULL; - size_t clientTokenLength = 0; - - /* Check for matching Thing Name and operation type. */ - bool match = ( pOperation->type == pParam->type ) && - ( pParam->thingNameLength == pSubscription->thingNameLength ) && - ( strncmp( pParam->pThingName, - pSubscription->pThingName, - pParam->thingNameLength ) == 0 ); - - /* For a Shadow UPDATE operation, compare the client tokens. */ - if( ( match == true ) && ( pOperation->type == SHADOW_UPDATE ) ) - { - /* Check document pointers. */ - AwsIotShadow_Assert( pParam->pDocument != NULL ); - AwsIotShadow_Assert( pParam->documentLength > 0U ); - AwsIotShadow_Assert( pOperation->u.update.pClientToken != NULL ); - AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0U ); - - IotLogDebug( "Verifying client tokens for Shadow UPDATE." ); - - /* Check for the client token in the UPDATE response document. */ - match = AwsIot_GetClientToken( pParam->pDocument, - pParam->documentLength, - &pClientToken, - &clientTokenLength ); - - /* If the UPDATE response document has a client token, check that it - * matches. */ - if( match == true ) - { - match = ( clientTokenLength == pOperation->u.update.clientTokenLength ) && - ( strncmp( pClientToken, - pOperation->u.update.pClientToken, - clientTokenLength ) == 0 ); - } - else - { - IotLogWarn( "Received a Shadow UPDATE response with no client token. " - "This is possibly a response to a bad JSON document:\r\n%.*s", - pParam->documentLength, - pParam->pDocument ); - } - } - - return match; -} - -/*-----------------------------------------------------------*/ - -static void _commonOperationCallback( _shadowOperationType_t type, - IotMqttCallbackParam_t * pMessage ) -{ - _shadowOperation_t * pOperation = NULL; - IotLink_t * pOperationLink = NULL; - AwsIotStatus_t status = AWS_IOT_UNKNOWN; - _operationMatchParams_t param = { .type = SHADOW_DELETE }; - uint32_t flags = 0; - - /* Set operation type to search. */ - param.type = type; - - /* Set the response document for a Shadow UPDATE. */ - if( type == SHADOW_UPDATE ) - { - param.pDocument = pMessage->u.message.info.pPayload; - param.documentLength = pMessage->u.message.info.payloadLength; - } - - /* Parse the Thing Name from the MQTT topic name. */ - if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength, - &( param.pThingName ), - &( param.thingNameLength ) ) == true ) - { - /* Lock the pending operations list for exclusive access. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Search for a matching pending operation. */ - pOperationLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowPendingOperations ), - NULL, - _shadowOperationMatch, - ¶m ); - - /* Find and remove the first Shadow operation of the given type. */ - if( pOperationLink == NULL ) - { - /* Operation is not pending. It may have already been processed. Return - * without doing anything */ - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - - IotLogWarn( "Shadow %s callback received an unknown operation.", - _pAwsIotShadowOperationNames[ type ] ); - } - else - { - pOperation = IotLink_Container( _shadowOperation_t, pOperationLink, link ); - - /* Remove a non-waitable operation from the pending operation list. - * Waitable operations are removed by the Wait function. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) - { - IotListDouble_Remove( &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - } - - /* Check that the Shadow operation type and status. */ - AwsIotShadow_Assert( pOperation->type == type ); - AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); - - IotLogDebug( "Received Shadow response on topic %.*s", - pMessage->u.message.info.topicNameLength, - pMessage->u.message.info.pTopicName ); - - /* Parse the status from the topic name. */ - status = AwsIot_ParseStatus( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength ); - - switch( status ) - { - case AWS_IOT_ACCEPTED: - IotLogInfo( "Shadow %s of %.*s was ACCEPTED.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); - - /* Process the retrieved document for a Shadow GET. Otherwise, set - * status to success. */ - if( type == SHADOW_GET ) - { - pOperation->status = _processAcceptedGet( pOperation, - &( pMessage->u.message.info ) ); - } - else - { - pOperation->status = AWS_IOT_SHADOW_SUCCESS; - } - - break; - - case AWS_IOT_REJECTED: - IotLogWarn( "Shadow %s of %.*s was REJECTED.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); - - pOperation->status = _AwsIotShadow_ParseErrorDocument( pMessage->u.message.info.pPayload, - pMessage->u.message.info.payloadLength ); - break; - - default: - IotLogWarn( "Unknown status for %s of %.*s Shadow. Ignoring message.", - _pAwsIotShadowOperationNames[ type ], - pOperation->pSubscription->thingNameLength, - pOperation->pSubscription->pThingName ); - - pOperation->status = AWS_IOT_SHADOW_BAD_RESPONSE; - } - - /* Copy the flags from the Shadow operation. The notify function may delete the operation. */ - flags = pOperation->flags; - - /* Notify of operation completion. */ - _notifyCompletion( pOperation ); - - /* For waitable operations, unlock the pending operation list mutex to allow - * the Wait function to run. */ - if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - } - } - } -} - -/*-----------------------------------------------------------*/ - -static void _deleteCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( SHADOW_DELETE, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _getCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( SHADOW_GET, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, - const IotMqttPublishInfo_t * pPublishInfo ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - - /* A non-waitable operation can re-use the pointers from the publish info, - * since those are guaranteed to be in-scope throughout the user callback. - * But a waitable operation must copy the data from the publish info because - * AwsIotShadow_Wait may be called after the MQTT library frees the publish - * info. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0U ) - { - pOperation->u.get.pDocument = pPublishInfo->pPayload; - pOperation->u.get.documentLength = pPublishInfo->payloadLength; - } - else - { - IotLogDebug( "Allocating new buffer for waitable Shadow GET." ); - - /* Parameter validation should not have allowed a NULL malloc function. */ - AwsIotShadow_Assert( pOperation->u.get.mallocDocument != NULL ); - - /* Allocate a buffer for the retrieved document. */ - pOperation->u.get.pDocument = pOperation->u.get.mallocDocument( pPublishInfo->payloadLength ); - - if( pOperation->u.get.pDocument == NULL ) - { - IotLogError( "Failed to allocate buffer for retrieved Shadow document." ); - - status = AWS_IOT_SHADOW_NO_MEMORY; - } - else - { - /* Copy the retrieved document. */ - ( void ) memcpy( ( void * ) pOperation->u.get.pDocument, - pPublishInfo->pPayload, - pPublishInfo->payloadLength ); - pOperation->u.get.documentLength = pPublishInfo->payloadLength; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _updateCallback( void * pArgument, - IotMqttCallbackParam_t * pMessage ) -{ - /* Silence warnings about unused parameter. */ - ( void ) pArgument; - - _commonOperationCallback( SHADOW_UPDATE, pMessage ); -} - -/*-----------------------------------------------------------*/ - -static void _notifyCompletion( _shadowOperation_t * pOperation ) -{ - AwsIotShadowCallbackParam_t callbackParam = { .callbackType = AWS_IOT_SHADOW_DELETE_COMPLETE }; - _shadowSubscription_t * pSubscription = pOperation->pSubscription, - * pRemovedSubscription = NULL; - - /* If the operation is waiting, post to its wait semaphore and return. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - } - else - { - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pSubscription->pTopicBuffer, - &pRemovedSubscription ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - /* Set the subscription pointer used for the user callback based on whether - * a subscription was removed from the list. */ - if( pRemovedSubscription != NULL ) - { - pSubscription = pRemovedSubscription; - } - - AwsIotShadow_Assert( pSubscription != NULL ); - - /* Invoke the user callback if provided. */ - if( pOperation->notify.callback.function != NULL ) - { - /* Set the common members of the callback parameter. */ - callbackParam.callbackType = _AwsIotShadow_IntToShadowCallbackType( ( uint32_t ) pOperation->type ); - callbackParam.mqttConnection = pOperation->mqttConnection; - callbackParam.u.operation.result = pOperation->status; - callbackParam.u.operation.reference = pOperation; - callbackParam.pThingName = pSubscription->pThingName; - callbackParam.thingNameLength = pSubscription->thingNameLength; - - /* Set the members of the callback parameter for a received document. */ - if( pOperation->type == SHADOW_GET ) - { - callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; - callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; - } - - pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, - &callbackParam ); - } - - /* Destroy a removed subscription. */ - if( pRemovedSubscription != NULL ) - { - _AwsIotShadow_DestroySubscription( pRemovedSubscription ); - } - - _AwsIotShadow_DestroyOperation( pOperation ); - } -} - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _findSubscription( const char * pThingName, - size_t thingNameLength, - char * pTopicBuffer, - uint16_t operationTopicLength, - _shadowOperation_t * pOperation, - bool * pFreeTopicBuffer ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - _shadowSubscription_t * pSubscription = NULL; - - /* Lookup table for Shadow operation callbacks. */ - const AwsIotMqttCallbackFunction_t shadowCallbacks[ SHADOW_OPERATION_COUNT ] = - { - _deleteCallback, - _getCallback, - _updateCallback - }; - - /* Lock the subscriptions mutex for exclusive access. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - - /* Check for an existing subscription. This function will attempt to allocate - * a new subscription if not found. */ - pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength, - true ); - - if( pSubscription == NULL ) - { - status = AWS_IOT_SHADOW_NO_MEMORY; - } - else - { - /* Ensure that the subscription Thing Name matches. */ - AwsIotShadow_Assert( pSubscription != NULL ); - AwsIotShadow_Assert( pSubscription->thingNameLength == thingNameLength ); - AwsIotShadow_Assert( strncmp( pSubscription->pThingName, - pThingName, - thingNameLength ) == 0 ); - - /* Set the subscription object for the Shadow operation. */ - pOperation->pSubscription = pSubscription; - - /* Assign the topic buffer to the subscription to use for unsubscribing if - * the subscription has no topic buffer. */ - if( pSubscription->pTopicBuffer == NULL ) - { - pSubscription->pTopicBuffer = pTopicBuffer; - - /* Don't free the topic buffer if it was allocated to the subscription. */ - *pFreeTopicBuffer = false; - } - else - { - *pFreeTopicBuffer = true; - } - - /* Increment the reference count for this Shadow operation's - * subscriptions. */ - status = _AwsIotShadow_IncrementReferences( pOperation, - pTopicBuffer, - operationTopicLength, - shadowCallbacks[ pOperation->type ] ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - /* Failed to add subscriptions for a Shadow operation. The reference - * count was not incremented. Check if this subscription should be - * deleted. */ - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); - } - } - - /* Unlock the Shadow subscription list mutex. */ - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOperation, - _shadowOperationType_t type, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - _shadowOperation_t * pOperation = NULL; - - IotLogDebug( "Creating operation record for Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); - - /* Allocate memory for a new Shadow operation. */ - pOperation = AwsIotShadow_MallocOperation( sizeof( _shadowOperation_t ) ); - - if( pOperation != NULL ) - { - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _shadowOperation_t ) ); - - /* Set the remaining common members of the Shadow operation. */ - pOperation->type = type; - pOperation->flags = flags; - pOperation->status = AWS_IOT_SHADOW_STATUS_PENDING; - - /* Set the output parameter. */ - *pNewOperation = pOperation; - - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) - { - IotLogError( "Failed to create semaphore for waitable Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); - - *pNewOperation = NULL; - AwsIotShadow_FreeOperation( pOperation ); - status = AWS_IOT_SHADOW_NO_MEMORY; - } - } - else - { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) - { - pOperation->notify.callback = *pCallbackInfo; - } - } - } - else - { - IotLogError( "Failed to allocate memory for Shadow %s.", - _pAwsIotShadowOperationNames[ type ] ); - - status = AWS_IOT_SHADOW_NO_MEMORY; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _AwsIotShadow_DestroyOperation( void * pData ) -{ - _shadowOperation_t * pOperation = ( _shadowOperation_t * ) pData; - - /* The Shadow operation pointer must not be NULL. */ - AwsIotShadow_Assert( pOperation != NULL ); - - IotLogDebug( "Destroying Shadow operation %s.", - _pAwsIotShadowOperationNames[ pOperation->type ] ); - - /* Check if a wait semaphore was created for this operation. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - /* Destroy the wait semaphore */ - IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); - } - - /* If this is a Shadow update, free any allocated client token. */ - if( ( pOperation->type == SHADOW_UPDATE ) && - ( pOperation->u.update.pClientToken != NULL ) ) - { - AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0U ); - - AwsIotShadow_FreeString( ( void * ) ( pOperation->u.update.pClientToken ) ); - } - - /* Free the memory used to hold operation data. */ - AwsIotShadow_FreeOperation( pOperation ); -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t type, - const char * pThingName, - size_t thingNameLength, - char ** pTopicBuffer, - uint16_t * pOperationTopicLength ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - AwsIotTopicInfo_t topicInfo = { 0 }; - - /* Lookup table for Shadow operation strings. */ - const char * const pOperationString[ SHADOW_OPERATION_COUNT ] = - { - SHADOW_DELETE_OPERATION_STRING, /* Shadow delete operation. */ - SHADOW_GET_OPERATION_STRING, /* Shadow get operation. */ - SHADOW_UPDATE_OPERATION_STRING /* Shadow update operation. */ - }; - - /* Lookup table for Shadow operation string lengths. */ - const uint16_t pOperationStringLength[ SHADOW_OPERATION_COUNT ] = - { - SHADOW_DELETE_OPERATION_STRING_LENGTH, /* Shadow delete operation. */ - SHADOW_GET_OPERATION_STRING_LENGTH, /* Shadow get operation. */ - SHADOW_UPDATE_OPERATION_STRING_LENGTH /* Shadow update operation. */ - }; - - /* Only Shadow delete, get, and update operation types should be passed to this - * function. */ - AwsIotShadow_Assert( ( type == SHADOW_DELETE ) || - ( type == SHADOW_GET ) || - ( type == SHADOW_UPDATE ) ); - - /* Set the members needed to generate an operation topic. */ - topicInfo.pThingName = pThingName; - topicInfo.thingNameLength = thingNameLength; - topicInfo.pOperationName = pOperationString[ type ]; - topicInfo.operationNameLength = pOperationStringLength[ type ]; - topicInfo.longestSuffixLength = SHADOW_LONGEST_SUFFIX_LENGTH; - topicInfo.mallocString = AwsIotShadow_MallocString; - - if( AwsIot_GenerateOperationTopic( &topicInfo, - pTopicBuffer, - pOperationTopicLength ) == false ) - { - status = AWS_IOT_SHADOW_NO_MEMORY; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - _shadowOperation_t * pOperation, - const AwsIotShadowDocumentInfo_t * pDocumentInfo ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; - char * pTopicBuffer = NULL; - uint16_t operationTopicLength = 0; - bool freeTopicBuffer = true; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - IotLogDebug( "Processing Shadow operation %s for Thing %.*s.", - _pAwsIotShadowOperationNames[ pOperation->type ], - thingNameLength, - pThingName ); - - /* Set the operation's MQTT connection. */ - pOperation->mqttConnection = mqttConnection; - - /* Generate the operation topic buffer. */ - status = _AwsIotShadow_GenerateShadowTopic( pOperation->type, - pThingName, - thingNameLength, - &pTopicBuffer, - &operationTopicLength ); - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Get a subscription object for this Shadow operation. */ - status = _findSubscription( pThingName, - thingNameLength, - pTopicBuffer, - operationTopicLength, - pOperation, - &freeTopicBuffer ); - } - else - { - IotLogError( "No memory for Shadow operation topic buffer." ); - - status = AWS_IOT_SHADOW_NO_MEMORY; - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Set the operation topic name. */ - publishInfo.pTopicName = pTopicBuffer; - publishInfo.topicNameLength = operationTopicLength; - - IotLogDebug( "Shadow %s message will be published to topic %.*s", - _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.topicNameLength, - publishInfo.pTopicName ); - - /* Set the document info if this operation is not a Shadow DELETE. */ - if( pOperation->type != SHADOW_DELETE ) - { - publishInfo.qos = pDocumentInfo->qos; - publishInfo.retryLimit = pDocumentInfo->retryLimit; - publishInfo.retryMs = pDocumentInfo->retryMs; - - IotLogDebug( "Shadow %s message will be published at QoS %d with " - "retryLimit %d and retryMs %llu.", - _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.qos, - publishInfo.retryLimit, - publishInfo.retryMs ); - } - - /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ - if( pOperation->type == SHADOW_UPDATE ) - { - publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; - publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; - } - - /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, - * per the Shadow spec. */ - else - { - publishInfo.pPayload = ""; - publishInfo.payloadLength = 0; - } - - /* Add Shadow operation to the pending operations list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), - &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Publish to the Shadow topic name. */ - publishStatus = IotMqtt_PublishSync( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotShadowMqttTimeoutMs ); - - if( publishStatus == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "Shadow %s PUBLISH message successfully sent.", - _pAwsIotShadowOperationNames[ pOperation->type ] ); - } - else - { - IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", - _pAwsIotShadowOperationNames[ pOperation->type ], - thingNameLength, - pThingName, - IotMqtt_strerror( publishStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( publishStatus ); - - /* If the "keep subscriptions" flag is not set, decrement the reference - * count. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0U ) - { - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - } - - /* Remove Shadow operation from the pending operations list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_Remove( &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - } - } - else - { - /* No subscription was found and no subscription could be allocated. */ - } - - /* Free the topic buffer used by this function if it was not assigned to a - * subscription. */ - if( ( freeTopicBuffer == true ) && ( pTopicBuffer != NULL ) ) - { - AwsIotShadow_FreeString( pTopicBuffer ); - } - - /* Destroy the Shadow operation on failure. */ - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - _AwsIotShadow_DestroyOperation( pOperation ); - } - else - { - /* Convert successful return code to "status pending", as the Shadow - * library is now waiting for a response from the service. */ - status = AWS_IOT_SHADOW_STATUS_PENDING; - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_parser.c b/libraries/aws/shadow/src/aws_iot_shadow_parser.c deleted file mode 100644 index 286f9cdb17..0000000000 --- a/libraries/aws/shadow/src/aws_iot_shadow_parser.c +++ /dev/null @@ -1,193 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_parser.c - * @brief Implements JSON parsing functions of the Shadow library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* AWS Parser include. */ -#include "aws_iot_doc_parser.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief The JSON key for the error code in a Shadow error document. - */ -#define ERROR_DOCUMENT_CODE_KEY "code" - -/** - * @brief The length of #ERROR_DOCUMENT_CODE_KEY. - */ -#define ERROR_DOCUMENT_CODE_KEY_LENGTH ( sizeof( ERROR_DOCUMENT_CODE_KEY ) - 1 ) - -/** - * @brief The JSON key for the error message in a Shadow error document. - */ -#define ERROR_DOCUMENT_MESSAGE_KEY "message" - -/** - * @brief The length of #ERROR_DOCUMENT_MESSAGE_KEY. - */ -#define ERROR_DOCUMENT_MESSAGE_KEY_LENGTH ( sizeof( ERROR_DOCUMENT_MESSAGE_KEY ) - 1 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Converts a `unsigned long` to an `AwsIotShadowError_t`. - * - * @param[in] code A value between 400 and 500 to convert. - * - * @return A corresponding #AwsIotShadowError_t; #AWS_IOT_SHADOW_BAD_RESPONSE - * if `code` is unknown. - */ -static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ); - -/*-----------------------------------------------------------*/ - -static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ) -{ - AwsIotShadowError_t errorCode = AWS_IOT_SHADOW_STATUS_PENDING; - - /* Convert the Shadow response code to an AwsIotShadowError_t. */ - switch( code ) - { - case 400UL: - errorCode = AWS_IOT_SHADOW_BAD_REQUEST; - break; - - case 401UL: - errorCode = AWS_IOT_SHADOW_UNAUTHORIZED; - break; - - case 403UL: - errorCode = AWS_IOT_SHADOW_FORBIDDEN; - break; - - case 404UL: - errorCode = AWS_IOT_SHADOW_NOT_FOUND; - break; - - case 409UL: - errorCode = AWS_IOT_SHADOW_CONFLICT; - break; - - case 413UL: - errorCode = AWS_IOT_SHADOW_TOO_LARGE; - break; - - case 415UL: - errorCode = AWS_IOT_SHADOW_UNSUPPORTED; - break; - - case 429UL: - errorCode = AWS_IOT_SHADOW_TOO_MANY_REQUESTS; - break; - - case 500UL: - errorCode = AWS_IOT_SHADOW_SERVER_ERROR; - break; - - default: - errorCode = AWS_IOT_SHADOW_BAD_RESPONSE; - break; - } - - return errorCode; -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, - size_t errorDocumentLength ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - const char * pCode = NULL, * pMessage = NULL; - size_t codeLength = 0, messageLength = 0; - uint32_t code = 0; - - /* Parse the code from the error document. */ - if( AwsIotDocParser_FindValue( pErrorDocument, - errorDocumentLength, - ERROR_DOCUMENT_CODE_KEY, - ERROR_DOCUMENT_CODE_KEY_LENGTH, - &pCode, - &codeLength ) == true ) - { - /* Code must be in error document. */ - AwsIotShadow_Assert( ( pCode >= pErrorDocument ) && - ( pCode + codeLength <= pErrorDocument + errorDocumentLength ) ); - - /* Convert the code to an unsigned integer value. */ - code = ( uint32_t ) strtoul( pCode, NULL, 10 ); - - /* Parse the error message and print it. An error document must always contain - * a message. */ - if( AwsIotDocParser_FindValue( pErrorDocument, - errorDocumentLength, - ERROR_DOCUMENT_MESSAGE_KEY, - ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, - &pMessage, - &messageLength ) == true ) - { - IotLogWarn( "Code %lu: %.*s.", - code, - messageLength, - pMessage ); - - /* Convert a successfully parsed JSON code to a Shadow status. */ - status = _codeToShadowStatus( code ); - } - else - { - IotLogWarn( "Code %lu; failed to parse message from error document.\n%.*s", - code, - errorDocumentLength, - pErrorDocument ); - - status = AWS_IOT_SHADOW_BAD_RESPONSE; - } - } - else - { - /* Error parsing JSON document, or no "code" key was found. */ - IotLogWarn( "Failed to parse code from error document.\n%.*s", - errorDocumentLength, - pErrorDocument ); - - status = AWS_IOT_SHADOW_BAD_RESPONSE; - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c b/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c deleted file mode 100644 index 1eee1469a5..0000000000 --- a/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_static_memory.c - * @brief Implementation of Shadow static memory functions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include -#include - -/* Static memory include. */ -#include "iot_static_memory.h" - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS - #define AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ( 10 ) -#endif -#ifndef AWS_IOT_SHADOW_SUBSCRIPTIONS - #define AWS_IOT_SHADOW_SUBSCRIPTIONS ( 2 ) -#endif -/** @endcond */ - -/* Validate static memory configuration settings. */ -#if AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS <= 0 - #error "AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." -#endif -#if AWS_IOT_SHADOW_SUBSCRIPTIONS <= 0 - #error "AWS_IOT_SHADOW_SUBSCRIPTIONS cannot be 0 or negative." -#endif - -/** - * @brief The size of a static memory Shadow subscription. - * - * Since the pThingName member of #_shadowSubscription_t is variable-length, - * the constant `AWS_IOT_MAX_THING_NAME_LENGTH` is used for the length of - * #_shadowSubscription_t.pThingName. - */ -#define SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + ( size_t ) AWS_IOT_MAX_THING_NAME_LENGTH ) - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static uint32_t _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief Shadow operation in-use flags. */ -static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Shadow operations. */ - -static uint32_t _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0U }; /**< @brief Shadow subscription in-use flags. */ -static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ SHADOW_SUBSCRIPTION_SIZE ] = { { '\0' } }; /**< @brief Shadow subscriptions. */ - -/*-----------------------------------------------------------*/ - -void * AwsIotShadow_MallocOperation( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewOperation = NULL; - - /* Check size argument. */ - if( size == sizeof( _shadowOperation_t ) ) - { - /* Find a free Shadow operation. */ - freeIndex = IotStaticMemory_FindFree( _pInUseShadowOperations, - AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ); - - if( freeIndex != -1 ) - { - pNewOperation = &( _pShadowOperations[ freeIndex ] ); - } - } - - return pNewOperation; -} - -/*-----------------------------------------------------------*/ - -void AwsIotShadow_FreeOperation( void * ptr ) -{ - /* Return the in-use Shadow operation. */ - IotStaticMemory_ReturnInUse( ptr, - _pShadowOperations, - _pInUseShadowOperations, - AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _shadowOperation_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * AwsIotShadow_MallocSubscription( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewSubscription = NULL; - - if( size <= SHADOW_SUBSCRIPTION_SIZE ) - { - /* Get the index of a free Shadow subscription. */ - freeIndex = IotStaticMemory_FindFree( _pInUseShadowSubscriptions, - AWS_IOT_SHADOW_SUBSCRIPTIONS ); - - if( freeIndex != -1 ) - { - pNewSubscription = &( _pShadowSubscriptions[ freeIndex ][ 0 ] ); - } - } - - return pNewSubscription; -} - -/*-----------------------------------------------------------*/ - -void AwsIotShadow_FreeSubscription( void * ptr ) -{ - /* Return the in-use Shadow subscription. */ - IotStaticMemory_ReturnInUse( ptr, - _pShadowSubscriptions, - _pInUseShadowSubscriptions, - AWS_IOT_SHADOW_SUBSCRIPTIONS, - SHADOW_SUBSCRIPTION_SIZE ); -} - -/*-----------------------------------------------------------*/ - -#endif diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c deleted file mode 100644 index a5a2a2d6d0..0000000000 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ /dev/null @@ -1,512 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_subscription.c - * @brief Implements functions for interacting with the Shadow library's - * subscription list. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Match two #_shadowSubscription_t by Thing Name. - * - * @param[in] pSubscriptionLink Pointer to the link member of a #_shadowSubscription_t - * containing the Thing Name to check. - * @param[in] pMatch Pointer to an `AwsIotThingName_t`. - * - * @return `true` if the Thing Names match; `false` otherwise. - */ -static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -/*-----------------------------------------------------------*/ - -/** - * @brief List of active Shadow subscriptions objects. - */ -IotListDouble_t _AwsIotShadowSubscriptions = { 0 }; - -/** - * @brief Protects #_AwsIotShadowSubscriptions from concurrent access. - */ -IotMutex_t _AwsIotShadowSubscriptionsMutex; - -/*-----------------------------------------------------------*/ - -static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, - void * pMatch ) -{ - bool match = false; - - /* Because this function is called from a container function, the given link - * must never be NULL. */ - AwsIotShadow_Assert( pSubscriptionLink != NULL ); - - const _shadowSubscription_t * pSubscription = IotLink_Container( _shadowSubscription_t, - pSubscriptionLink, - link ); - const AwsIotThingName_t * pThingName = ( AwsIotThingName_t * ) pMatch; - - if( pThingName->thingNameLength == pSubscription->thingNameLength ) - { - /* Check for matching Thing Names. */ - match = ( strncmp( pThingName->pThingName, - pSubscription->pThingName, - pThingName->thingNameLength ) == 0 ); - } - - return match; -} - -/*-----------------------------------------------------------*/ - -_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, - size_t thingNameLength, - bool createIfNotFound ) -{ - _shadowSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = { 0 }; - - thingName.pThingName = pThingName; - thingName.thingNameLength = thingNameLength; - - /* Search the list for an existing subscription for Thing Name. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), - NULL, - _shadowSubscription_match, - &thingName ); - - /* Check if a subscription was found. */ - if( pSubscriptionLink == NULL ) - { - if( createIfNotFound == true ) - { - /* No subscription found. Allocate a new subscription. */ - pSubscription = AwsIotShadow_MallocSubscription( sizeof( _shadowSubscription_t ) + thingNameLength ); - - if( pSubscription != NULL ) - { - /* Clear the new subscription. */ - ( void ) memset( pSubscription, 0x00, sizeof( _shadowSubscription_t ) + thingNameLength ); - - /* Set the Thing Name length and copy the Thing Name into the new subscription. */ - pSubscription->thingNameLength = thingNameLength; - ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); - - /* Add the new subscription to the subscription list. */ - IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ), - &( pSubscription->link ) ); - - IotLogDebug( "Created new Shadow subscriptions object for %.*s.", - thingNameLength, - pThingName ); - } - else - { - IotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.", - thingNameLength, - pThingName ); - } - } - } - else - { - IotLogDebug( "Found existing Shadow subscriptions object for %.*s.", - thingNameLength, - pThingName ); - - pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); - } - - return pSubscription; -} - -/*-----------------------------------------------------------*/ - -void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, - _shadowSubscription_t ** pRemovedSubscription ) -{ - int32_t i = 0; - bool removeSubscription = true; - - IotLogDebug( "Checking if subscription object for %.*s can be removed.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - /* Check for active operations. If any Shadow operation's subscription - * reference count is not 0, then the subscription cannot be removed. */ - for( i = 0; i < ( int32_t ) SHADOW_OPERATION_COUNT; i++ ) - { - if( pSubscription->references[ i ] > 0 ) - { - /* In some implementations IotLogDebug() maps to C standard printing API - * that needs specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "Reference count %ld for %.*s subscription object. " - "Subscription cannot be removed yet.", - ( long int ) pSubscription->references[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - - removeSubscription = false; - } - else if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " - "Subscription will not be removed.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - removeSubscription = false; - } - - if( removeSubscription == false ) - { - break; - } - } - - /* Check for active subscriptions. If any Shadow callbacks are active, then the - * subscription cannot be removed. */ - if( removeSubscription == true ) - { - for( i = 0; i < SHADOW_CALLBACK_COUNT; i++ ) - { - if( pSubscription->callbacks[ i ].function != NULL ) - { - IotLogDebug( "Found active Shadow %s callback for %.*s subscription object. " - "Subscription cannot be removed yet.", - _pAwsIotShadowCallbackNames[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - - removeSubscription = false; - break; - } - } - } - - /* Remove the subscription if unused. */ - if( removeSubscription == true ) - { - /* No Shadow operation subscription references or active Shadow callbacks. - * Remove the subscription object. */ - IotListDouble_Remove( &( pSubscription->link ) ); - - IotLogDebug( "Removed subscription object for %.*s.", - pSubscription->thingNameLength, - pSubscription->pThingName ); - - /* If the caller requested the removed subscription, set the output parameter. - * Otherwise, free the memory used by the subscription. */ - if( pRemovedSubscription != NULL ) - { - *pRemovedSubscription = pSubscription; - } - else - { - _AwsIotShadow_DestroySubscription( pSubscription ); - } - } -} - -/*-----------------------------------------------------------*/ - -void _AwsIotShadow_DestroySubscription( void * pData ) -{ - _shadowSubscription_t * pSubscription = ( _shadowSubscription_t * ) pData; - - /* Free the topic buffer if allocated. */ - if( pSubscription->pTopicBuffer != NULL ) - { - AwsIotShadow_FreeString( pSubscription->pTopicBuffer ); - } - - /* Free memory used by subscription. */ - AwsIotShadow_FreeSubscription( pSubscription ); -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation, - char * pTopicBuffer, - uint16_t operationTopicLength, - AwsIotMqttCallbackFunction_t callback ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - const _shadowOperationType_t type = pOperation->type; - _shadowSubscription_t * pSubscription = pOperation->pSubscription; - IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; - AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; - - /* Do nothing if this operation has persistent subscriptions. */ - if( pSubscription->references[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " - "count will not be incremented.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - } - else - { - /* When persistent subscriptions are not present, the reference count must - * not be negative. */ - AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); - - /* Check if there are any existing references for this operation. */ - if( pSubscription->references[ type ] == 0 ) - { - /* Set the parameters needed to add subscriptions. */ - subscriptionInfo.mqttConnection = pOperation->mqttConnection; - subscriptionInfo.callbackFunction = callback; - subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; - - subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync, - &subscriptionInfo ); - - /* Convert MQTT return code to Shadow return code. */ - status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( subscriptionStatus ); - } - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - /* Increment the number of subscription references for this operation when - * the keep subscriptions flag is not set. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0U ) - { - ( pSubscription->references[ type ] )++; - - IotLogDebug( "Shadow %s subscriptions for %.*s now has count %d.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName, - pSubscription->references[ type ] ); - } - /* Otherwise, set the persistent subscriptions flag. */ - else - { - pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; - - IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, - char * pTopicBuffer, - _shadowSubscription_t ** pRemovedSubscription ) -{ - const _shadowOperationType_t type = pOperation->type; - _shadowSubscription_t * pSubscription = pOperation->pSubscription; - uint16_t operationTopicLength = 0; - AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; - - /* Do nothing if this Shadow operation has persistent subscriptions. */ - if( pSubscription->references[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - /* Decrement the number of subscription references for this operation. - * Ensure that it's positive. */ - ( pSubscription->references[ type ] )--; - AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); - - /* Check if the number of references has reached 0. */ - if( pSubscription->references[ type ] == 0 ) - { - IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowOperationNames[ type ] ); - - /* Subscription must have a topic buffer. */ - AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); - - /* Generate the prefix of the Shadow topic. This function will not - * fail when given a buffer. */ - ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) type, - pSubscription->pThingName, - pSubscription->thingNameLength, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); - - /* Set the parameters needed to remove subscriptions. */ - subscriptionInfo.mqttConnection = pOperation->mqttConnection; - subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; - - ( void ) AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, - &subscriptionInfo ); - } - - /* Check if this subscription should be deleted. */ - _AwsIotShadow_RemoveSubscription( pSubscription, - pRemovedSubscription ); - } - else - { - IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " - "count will not be decremented.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - } -} - -/*-----------------------------------------------------------*/ - -AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags ) -{ - uint32_t i = 0; - uint16_t operationTopicLength = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING; - AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; - _shadowSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = { 0 }; - - thingName.pThingName = pThingName; - thingName.thingNameLength = thingNameLength; - - IotLogInfo( "Removing persistent subscriptions for %.*s.", - thingNameLength, - pThingName ); - - IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); - - /* Search the list for an existing subscription for Thing Name. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), - NULL, - _shadowSubscription_match, - &thingName ); - - /* Unsubscribe from operation subscriptions if found. */ - if( pSubscriptionLink != NULL ) - { - IotLogDebug( "Found subscription object for %.*s. Checking for persistent " - "subscriptions to remove.", - thingNameLength, - pThingName ); - - pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); - - for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) - { - if( ( flags & ( 0x1UL << i ) ) != 0U ) - { - IotLogDebug( "Removing %.*s %s persistent subscriptions.", - thingNameLength, - pThingName, - _pAwsIotShadowOperationNames[ i ] ); - - /* Subscription must have a topic buffer. */ - AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); - - if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) - { - /* Generate the prefix of the Shadow topic. This function will not - * fail when given a buffer. */ - ( void ) _AwsIotShadow_GenerateShadowTopic( _AwsIotShadow_IntToShadowOperationType( i ), - pThingName, - thingNameLength, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); - - /* Set the parameters needed to remove subscriptions. */ - subscriptionInfo.mqttConnection = mqttConnection; - subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; - subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer; - subscriptionInfo.topicFilterBaseLength = operationTopicLength; - - unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, - &subscriptionInfo ); - - /* Convert MQTT return code to Shadow return code. */ - status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( unsubscribeStatus ); - - if( status != AWS_IOT_SHADOW_SUCCESS ) - { - break; - } - - /* Clear the persistent subscriptions flag and check if the - * subscription can be removed. */ - pSubscription->references[ i ] = 0; - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); - } - else - { - IotLogDebug( "%.*s %s does not have persistent subscriptions.", - thingNameLength, - pThingName, - _pAwsIotShadowOperationNames[ i ] ); - } - } - } - } - else - { - IotLogWarn( "No subscription object found for %.*s", - thingNameLength, - pThingName ); - } - - IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h deleted file mode 100644 index b276a5e901..0000000000 --- a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h +++ /dev/null @@ -1,585 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_shadow_internal.h - * @brief Internal header of Shadow library. This header should not be included in - * typical application code. - */ - -#ifndef AWS_IOT_SHADOW_INTERNAL_H_ -#define AWS_IOT_SHADOW_INTERNAL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Linear containers (lists and queues) include. */ -#include "iot_linear_containers.h" - -/* Platform layer types include. */ -#include "types/iot_platform_types.h" - -/* Shadow include. */ -#include "aws_iot_shadow.h" - -/* AWS IoT include. */ -#include "aws_iot.h" - -/** - * @def AwsIotShadow_Assert( expression ) - * @brief Assertion macro for the Shadow library. - * - * Set @ref AWS_IOT_SHADOW_ENABLE_ASSERTS to `1` to enable assertions in the Shadow - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 - #ifndef AwsIotShadow_Assert - #ifdef Iot_DefaultAssert - #define AwsIotShadow_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for Shadow, but AwsIotShadow_Assert is not defined" - #endif - #endif -#else /* if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 */ - #define AwsIotShadow_Assert( expression ) -#endif /* if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 */ - -/* Configure logs for Shadow functions. */ -#ifdef AWS_IOT_LOG_LEVEL_SHADOW - #define LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_SHADOW -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "Shadow" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "iot_static_memory.h" - -/** - * @brief Allocate a #_shadowOperation_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * AwsIotShadow_MallocOperation( size_t size ); - -/** - * @brief Free a #_shadowOperation_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void AwsIotShadow_FreeOperation( void * ptr ); - -/** - * @brief Allocate a buffer for a short string, used for topic names or client - * tokens. This function should have the same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define AwsIotShadow_MallocString Iot_MallocMessageBuffer - -/** - * @brief Free a string. This function should have the same signature as - * [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define AwsIotShadow_FreeString Iot_FreeMessageBuffer - -/** - * @brief Allocate a #_shadowSubscription_t. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * AwsIotShadow_MallocSubscription( size_t size ); - -/** - * @brief Free a #_shadowSubscription_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void AwsIotShadow_FreeSubscription( void * ptr ); -#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #ifndef AwsIotShadow_MallocOperation - #ifdef Iot_DefaultMalloc - #define AwsIotShadow_MallocOperation Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotShadow_MallocOperation" - #endif - #endif - - #ifndef AwsIotShadow_FreeOperation - #ifdef Iot_DefaultFree - #define AwsIotShadow_FreeOperation Iot_DefaultFree - #else - #error "No free function defined for AwsIotShadow_FreeOperation" - #endif - #endif - - #ifndef AwsIotShadow_MallocString - #ifdef Iot_DefaultMalloc - #define AwsIotShadow_MallocString Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotShadow_MallocString" - #endif - #endif - - #ifndef AwsIotShadow_FreeString - #ifdef Iot_DefaultFree - #define AwsIotShadow_FreeString Iot_DefaultFree - #else - #error "No free function defined for AwsIotShadow_FreeString" - #endif - #endif - - #ifndef AwsIotShadow_MallocSubscription - #ifdef Iot_DefaultMalloc - #define AwsIotShadow_MallocSubscription Iot_DefaultMalloc - #else - #error "No malloc function defined for AwsIotShadow_MallocSubscription" - #endif - #endif - - #ifndef AwsIotShadow_FreeSubscription - #ifdef Iot_DefaultFree - #define AwsIotShadow_FreeSubscription Iot_DefaultFree - #else - #error "No free function defined for AwsIotShadow_FreeSubscription" - #endif - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS - #define AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS ( 5000 ) -#endif -/** @endcond */ - -/** - * @brief The number of currently available Shadow operations. - * - * The 3 Shadow operations are DELETE, GET, and UPDATE. - */ -#define SHADOW_OPERATION_COUNT ( 3U ) - -/** - * @brief The number of currently available Shadow callbacks. - * - * The 2 Shadow callbacks are `update/delta` (AKA "Delta") and `/update/documents` - * (AKA "Updated"). - */ -#define SHADOW_CALLBACK_COUNT ( 2 ) - -/** - * @brief The string representing a Shadow DELETE operation in a Shadow MQTT topic. - */ -#define SHADOW_DELETE_OPERATION_STRING "/shadow/delete" - -/** - * @brief The length of #SHADOW_DELETE_OPERATION_STRING. - */ -#define SHADOW_DELETE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELETE_OPERATION_STRING ) - 1U ) ) - -/** - * @brief The string representing a Shadow GET operation in a Shadow MQTT topic. - */ -#define SHADOW_GET_OPERATION_STRING "/shadow/get" - -/** - * @brief The length of #SHADOW_GET_OPERATION_STRING. - */ -#define SHADOW_GET_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_GET_OPERATION_STRING ) - 1U ) ) - -/** - * @brief The string representing a Shadow UPDATE operation in a Shadow MQTT topic. - */ -#define SHADOW_UPDATE_OPERATION_STRING "/shadow/update" - -/** - * @brief The length of #SHADOW_UPDATE_OPERATION_STRING. - */ -#define SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATE_OPERATION_STRING ) - 1U ) ) - -/** - * @brief The suffix for a Shadow delta topic. - */ -#define SHADOW_DELTA_SUFFIX "/delta" - -/** - * @brief The length of #SHADOW_DELTA_SUFFIX. - */ -#define SHADOW_DELTA_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELTA_SUFFIX ) - 1U ) ) - -/** - * @brief The suffix for a Shadow updated topic. - */ -#define SHADOW_UPDATED_SUFFIX "/documents" - -/** - * @brief The length of #SHADOW_UPDATED_SUFFIX. - */ -#define SHADOW_UPDATED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATED_SUFFIX ) - 1U ) ) - -/** - * @brief The length of the longest Shadow suffix. - */ -#define SHADOW_LONGEST_SUFFIX_LENGTH SHADOW_UPDATED_SUFFIX_LENGTH - -/** - * @brief The macro to convert MQTT error codes to Shadow error codes. - * Below are the conversions happening. - * IOT_MQTT_SUCCESS to AWS_IOT_SHADOW_SUCCESS - * IOT_MQTT_NO_MEMORY to AWS_IOT_SHADOW_NO_MEMORY - * all other error codes to AWS_IOT_SHADOW_MQTT_ERROR - */ -#define SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( X ) \ - ( ( X ) == IOT_MQTT_SUCCESS ) ? AWS_IOT_SHADOW_SUCCESS : \ - ( ( X ) == IOT_MQTT_NO_MEMORY ) ? AWS_IOT_SHADOW_NO_MEMORY : \ - AWS_IOT_SHADOW_MQTT_ERROR - -/*----------------------- Shadow internal data types ------------------------*/ - -/** - * @brief Enumerations representing each of the Shadow library's API functions. - */ -typedef enum _shadowOperationType -{ - /* Shadow operations. */ - SHADOW_DELETE = 0, /**< @ref shadow_function_deleteasync */ - SHADOW_GET = 1, /**< @ref shadow_function_getasync */ - SHADOW_UPDATE = 2, /**< @ref shadow_function_updateasync */ - - /* Shadow callbacks. */ - SET_DELTA_CALLBACK = 3, /**< @ref shadow_function_setdeltacallback */ - SET_UPDATED_CALLBACK = 4 /**< @ref shadow_function_setupdatedcallback */ -} _shadowOperationType_t; - -/** - * @brief Enumerations representing each of the Shadow callback functions. - */ -typedef enum _shadowCallbackType -{ - DELTA_CALLBACK = 0, /**< Delta callback. */ - UPDATED_CALLBACK = 1 /**< Updated callback. */ -} _shadowCallbackType_t; - -/** - * @brief Represents a Shadow subscriptions object. - * - * These structures are stored in a list. - */ -typedef struct _shadowSubscription -{ - IotLink_t link; /**< @brief List link member. */ - - int32_t references[ SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ - AwsIotShadowCallbackInfo_t callbacks[ SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ - - /** - * @brief Buffer allocated for removing Shadow topics. - * - * This buffer is pre-allocated to ensure that memory is available when - * unsubscribing. - */ - char * pTopicBuffer; - - size_t thingNameLength; /**< @brief Length of Thing Name. */ - char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ -} _shadowSubscription_t; - -/** - * @brief Internal structure representing a single Shadow operation (DELETE, - * GET, or UPDATE). - * - * A list of these structures keeps track of all in-progress Shadow operations. - */ -typedef struct _shadowOperation -{ - IotLink_t link; /**< @brief List link member. */ - - /* Basic operation information. */ - _shadowOperationType_t type; /**< @brief Operation type. */ - uint32_t flags; /**< @brief Flags passed to operation API function. */ - AwsIotShadowError_t status; /**< @brief Status of operation. */ - - IotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ - _shadowSubscription_t * pSubscription; /**< @brief Shadow subscriptions object associated with this operation. */ - - union - { - /* Members valid only for a GET operation. */ - struct - { - /** - * @brief Function to allocate memory for an incoming Shadow document. - * - * Only used when the flag #AWS_IOT_SHADOW_FLAG_WAITABLE is set. - */ - void *( *mallocDocument )( size_t ); - - const char * pDocument; /**< @brief Retrieved Shadow document. */ - size_t documentLength; /**< @brief Length of retrieved Shadow document. */ - } get; - - /* Members valid only for an UPDATE operation. */ - struct - { - const char * pClientToken; /**< @brief Client token in update document. */ - size_t clientTokenLength; /**< @brief Length of client token. */ - } update; - } u; /**< @brief Valid member depends on _shadowOperation_t.type. */ - - /* How to notify of an operation's completion. */ - union - { - IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref shadow_function_wait. */ - AwsIotShadowCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ - } notify; /**< @brief How to notify of an operation's completion. */ -} _shadowOperation_t; - -/* Declarations of names printed in logs. */ -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - extern const char * const _pAwsIotShadowOperationNames[]; - extern const char * const _pAwsIotShadowCallbackNames[]; -#endif - -/* Declarations of variables for internal Shadow files. */ -extern uint32_t _AwsIotShadowMqttTimeoutMs; -extern IotListDouble_t _AwsIotShadowPendingOperations; -extern IotListDouble_t _AwsIotShadowSubscriptions; -extern IotMutex_t _AwsIotShadowPendingOperationsMutex; -extern IotMutex_t _AwsIotShadowSubscriptionsMutex; - -/*----------------------- Shadow operation functions ------------------------*/ - -/** - * @brief Create a record for a new in-progress Shadow operation. - * - * @param[out] pNewOperation Set to point to the new operation on success. - * @param[in] type The type of Shadow operation. - * @param[in] flags Flags variables passed to a user-facing Shadow function. - * @param[in] pCallbackInfo User-provided callback function and parameter. - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY - */ -AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOperation, - _shadowOperationType_t type, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo ); - -/** - * @brief Free resources used to record a Shadow operation. This is called when - * the operation completes. - * - * @param[in] pData The operation which completed. This parameter is of type - * `void*` to match the signature of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -void _AwsIotShadow_DestroyOperation( void * pData ); - -/** - * @brief Fill a buffer with a Shadow topic. - * - * @param[in] type One of: DELETE, GET, UPDATE. - * @param[in] pThingName Thing Name to place in the topic. - * @param[in] thingNameLength Length of `pThingName`. - * @param[out] pTopicBuffer Address of the buffer for the Shadow topic. If the - * pointer at this address is `NULL`, this function will allocate a new buffer; - * otherwise, it will use the provided buffer. - * @param[out] pOperationTopicLength Length of the Shadow operation topic (excluding - * any suffix) placed in `pTopicBuffer`. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must be large enough to accommodate the full Shadow topic, plus - * #SHADOW_LONGEST_SUFFIX_LENGTH. - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. This function - * will not return #AWS_IOT_SHADOW_NO_MEMORY when a buffer is provided. - */ -AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t type, - const char * pThingName, - size_t thingNameLength, - char ** pTopicBuffer, - uint16_t * pOperationTopicLength ); - -/** - * @brief Process a Shadow operation by sending the necessary MQTT packets. - * - * @param[in] mqttConnection The MQTT connection to use. - * @param[in] pThingName Thing Name for the Shadow operation. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] pOperation Operation data to process. - * @param[in] pDocumentInfo Information on the Shadow document for GET or UPDATE - * operations. - * - * @return #AWS_IOT_SHADOW_STATUS_PENDING on success. On error, one of - * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. - */ -AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - _shadowOperation_t * pOperation, - const AwsIotShadowDocumentInfo_t * pDocumentInfo ); - - - -/** - * @brief Convert an integer to the shadow operation type. - * - * @param[in] n The integer to convert. - * - * @return The enum value associated with the input. - */ -_shadowOperationType_t _AwsIotShadow_IntToShadowOperationType( uint32_t n ); - -/** - * @brief Convert an integer to the shadow callback type. - * - * @param[in] n The integer to convert. - * - * @return The enum value associated with the input. - */ -AwsIotShadowCallbackType_t _AwsIotShadow_IntToShadowCallbackType( uint32_t n ); - -/*---------------------- Shadow subscription functions ----------------------*/ - -/** - * @brief Find a Shadow subscription object. May create a new subscription object - * and adds it to the subscription list if not found. - * - * @param[in] pThingName Thing Name in the subscription object. - * @param[in] thingNameLength Length of `pThingName`. - * @param[in] createIfNotFound If `true`, attempt to create a new subscription - * object if no match is found. - * - * @return Pointer to a Shadow subscription object, either found or newly - * allocated. Returns `NULL` if no subscription object is found and a new - * subscription object could not be allocated. - * - * @note This function should be called with the subscription list mutex locked. - */ -_shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, - size_t thingNameLength, - bool createIfNotFound ); - -/** - * @brief Remove a Shadow subscription object from the subscription list if - * unreferenced. - * - * @param[in] pSubscription Subscription object to check. If this object has no - * active references, it is removed from the subscription list. - * @param[out] pRemovedSubscription Removed subscription object, if any. Optional; - * pass `NULL` to ignore. If not `NULL`, this parameter will be set to the removed - * subscription and that subscription will not be destroyed. - * - * @note This function should be called with the subscription list mutex locked. - */ -void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, - _shadowSubscription_t ** pRemovedSubscription ); - -/** - * @brief Free resources used for a Shadow subscription object. - * - * @param[in] pData The subscription object to destroy. This parameter is of type - * `void*` to match the signature of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -void _AwsIotShadow_DestroySubscription( void * pData ); - -/** - * @brief Increment the reference count of a Shadow subscriptions object. - * - * Also adds MQTT subscriptions if necessary. - * - * @param[in] pOperation The operation for which the reference count should be - * incremented. - * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if - * subscriptions need to be added. - * @param[in] operationTopicLength The length of the operation topic in `pTopicBuffer`. - * @param[in] callback MQTT callback function for when this operation completes. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must already contain the Shadow operation topic, plus enough space for the - * status suffix. - * - * @return #AWS_IOT_SHADOW_SUCCESS on success. On error, one of - * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. - * - * @note This function should be called with the subscription list mutex locked. - */ -AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation, - char * pTopicBuffer, - uint16_t operationTopicLength, - AwsIotMqttCallbackFunction_t callback ); - -/** - * @brief Decrement the reference count of a Shadow subscriptions object. - * - * Also removed MQTT subscriptions and deletes the subscription object if necessary. - * - * @param[in] pOperation The operation for which the reference count should be - * decremented. - * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if - * subscriptions need to be removed. - * @param[out] pRemovedSubscription Set to point to a removed subscription. - * Optional; pass `NULL` to ignore. If not `NULL`, this function will not destroy - * a removed subscription. - * - * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must be large enough to accommodate the full Shadow topic, plus - * #SHADOW_LONGEST_SUFFIX_LENGTH. - * - * @note This function should be called with the subscription list mutex locked. - */ -void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, - char * pTopicBuffer, - _shadowSubscription_t ** pRemovedSubscription ); - -/*------------------------- Shadow parser functions -------------------------*/ - -/** - * @brief Parse a Shadow error document. - * - * @param[in] pErrorDocument The error document to parse. - * @param[in] errorDocumentLength The length of `pErrorDocument`. - * - * @return One of the #AwsIotShadowError_t ranging from 400 to 500 on success. - * #AWS_IOT_SHADOW_BAD_RESPONSE on error. - */ -AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, - size_t errorDocumentLength ); - -#endif /* ifndef AWS_IOT_SHADOW_INTERNAL_H_ */ diff --git a/libraries/aws/shadow/test/aws_iot_tests_shadow.c b/libraries/aws/shadow/test/aws_iot_tests_shadow.c deleted file mode 100644 index efb74f1903..0000000000 --- a/libraries/aws/shadow/test/aws_iot_tests_shadow.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_shadow.c - * @brief Test runner for Shadow tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the Shadow test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunShadowTests( bool disableNetworkTests, bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableLongTests; - - RUN_TEST_GROUP( Shadow_Unit_Parser ); - RUN_TEST_GROUP( Shadow_Unit_API ); - - if( disableNetworkTests == false ) - { - RUN_TEST_GROUP( Shadow_System ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c deleted file mode 100644 index dd5bf63945..0000000000 --- a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c +++ /dev/null @@ -1,762 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_shadow_system.c - * @brief Full system tests for the AWS IoT Shadow library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* JSON utilities include. */ -#include "aws_iot_doc_parser.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* Require Shadow library asserts to be enabled for these tests. The Shadow - * assert function is used to abort the tests on failure from the Shadow operation - * complete callback. */ -#if AWS_IOT_SHADOW_ENABLE_ASSERTS == 0 - #error "Shadow system tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." -#endif - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S - #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) -#endif -#ifndef AWS_IOT_TEST_SHADOW_TIMEOUT - #define AWS_IOT_TEST_SHADOW_TIMEOUT ( 5000 ) -#endif -/** @endcond */ - -/* Thing Name must be defined for these tests. */ -#ifndef AWS_IOT_TEST_SHADOW_THING_NAME - #error "Please define AWS_IOT_TEST_SHADOW_THING_NAME." -#endif - -/** - * @brief The length of @ref AWS_IOT_TEST_SHADOW_THING_NAME. - */ -#define THING_NAME_LENGTH ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ) - -/** - * @brief The Shadow document used for these tests. - */ -#define TEST_SHADOW_DOCUMENT "{\"state\":{\"reported\":{\"key\":\"value\"}},\"clientToken\":\"shadowtest\"}" - -/** - * @brief The length of #TEST_SHADOW_DOCUMENT. - */ -#define TEST_SHADOW_DOCUMENT_LENGTH ( sizeof( TEST_SHADOW_DOCUMENT ) - 1 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Parameter 1 of #_operationComplete. - */ -typedef struct _operationCompleteParams -{ - AwsIotShadowCallbackType_t expectedType; /**< @brief Expected callback type. */ - IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ - AwsIotShadowOperation_t operation; /**< @brief Reference to expected completed operation. */ -} _operationCompleteParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Network server info to share among the tests. - */ -static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - -/** - * @brief Network credential info to share among the tests. - */ -#if IOT_TEST_SECURED_CONNECTION == 1 - static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -#endif - -/** - * @brief An MQTT connection to share among the tests. - */ -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*-----------------------------------------------------------*/ - -/** - * @brief Shadow operation completion callback function. Checks parameters - * and unblocks the main test thread. - */ -static void _operationComplete( void * pArgument, - AwsIotShadowCallbackParam_t * pOperation ) -{ - _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - /* Silence warnings when asserts are disabled. */ - ( void ) pJsonValue; - ( void ) jsonValueLength; - - /* Check parameters against received operation information. */ - AwsIotShadow_Assert( pOperation->callbackType == pParams->expectedType ); - AwsIotShadow_Assert( pOperation->mqttConnection == _mqttConnection ); - AwsIotShadow_Assert( pOperation->u.operation.result == AWS_IOT_SHADOW_SUCCESS ); - AwsIotShadow_Assert( pOperation->u.operation.reference == pParams->operation ); - AwsIotShadow_Assert( pOperation->thingNameLength == THING_NAME_LENGTH ); - AwsIotShadow_Assert( strncmp( pOperation->pThingName, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH ) == 0 ); - - /* Check the retrieved Shadow document. */ - if( pOperation->callbackType == AWS_IOT_SHADOW_GET_COMPLETE ) - { - AwsIotShadow_Assert( pOperation->u.operation.get.documentLength > 0 ); - - AwsIotShadow_Assert( AwsIotDocParser_FindValue( pOperation->u.operation.get.pDocument, - pOperation->u.operation.get.documentLength, - "key", - 3, - &pJsonValue, - &jsonValueLength ) == true ); - AwsIotShadow_Assert( jsonValueLength == 7 ); - AwsIotShadow_Assert( strncmp( pJsonValue, "\"value\"", 7 ) == 0 ); - } - - /* Unblock the main test thread. */ - IotSemaphore_Post( &( pParams->waitSem ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Shadow delta callback. Checks parameters and unblocks the main test - * thread. - */ -static void _deltaCallback( void * pArgument, - AwsIotShadowCallbackParam_t * pCallback ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - const char * pValue = NULL, * pClientToken = NULL; - size_t valueLength = 0, clientTokenLength = 0; - - /* Silence warnings when asserts are disabled. */ - ( void ) pCallback; - ( void ) pValue; - ( void ) pClientToken; - ( void ) valueLength; - ( void ) clientTokenLength; - - /* Check callback type and MQTT connection. */ - AwsIotShadow_Assert( pCallback->callbackType == AWS_IOT_SHADOW_DELTA_CALLBACK ); - AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); - - /* Check delta document state. */ - AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "key", - 3, - &pValue, - &valueLength ) == true ); - AwsIotShadow_Assert( valueLength == 4 ); - AwsIotShadow_Assert( strncmp( pValue, "true", valueLength ) == 0 ); - - /* Check delta document client token. */ - AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "clientToken", - 11, - &pClientToken, - &clientTokenLength ) == true ); - AwsIotShadow_Assert( clientTokenLength == 12 ); - AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); - - /* Unblock the main test thread. */ - IotSemaphore_Post( pWaitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Shadow updated callback. Checks parameters and unblocks the main test - * thread. - */ -static void _updatedCallback( void * pArgument, - AwsIotShadowCallbackParam_t * pCallback ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - const char * pPrevious = NULL, * pCurrent = NULL, * pClientToken = NULL; - size_t previousStateLength = 0, currentStateLength = 0, clientTokenLength = 0; - - /* Silence warnings when asserts are disabled. */ - ( void ) pCallback; - ( void ) pPrevious; - ( void ) pCurrent; - ( void ) pClientToken; - ( void ) previousStateLength; - ( void ) currentStateLength; - ( void ) clientTokenLength; - - /* Check MQTT connection. */ - AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); - - /* Check updated document previous state. */ - AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "previous", - 8, - &pPrevious, - &previousStateLength ) == true ); - AwsIotShadow_Assert( previousStateLength > 0 ); - AwsIotShadow_Assert( strncmp( "{\"state\":{},\"metadata\":{},", - pPrevious, - 26 ) == 0 ); - - /* Check updated document current state. */ - AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "current", - 7, - &pCurrent, - ¤tStateLength ) == true ); - AwsIotShadow_Assert( currentStateLength > 0 ); - AwsIotShadow_Assert( strncmp( "{\"state\":{\"desired\":{\"key\":true}}", - pCurrent, - 33 ) == 0 ); - - /* Check updated document client token. */ - AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "clientToken", - 11, - &pClientToken, - &clientTokenLength ) == true ); - AwsIotShadow_Assert( clientTokenLength == 12 ); - AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); - - /* Unblock the main test thread. */ - IotSemaphore_Post( pWaitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Run the Update-Get-Delete asynchronous tests at various QoS. - */ -static void _updateGetDeleteAsync( IotMqttQos_t qos ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { .expectedType = ( AwsIotShadowCallbackType_t ) 0 }; - - /* Initialize the members of the operation callback info. */ - callbackInfo.pCallbackContext = &callbackParam; - callbackInfo.function = _operationComplete; - - /* Initialize the common members of the Shadow document info. */ - documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - documentInfo.thingNameLength = THING_NAME_LENGTH; - documentInfo.qos = qos; - - /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Set the expected callback type to UPDATE. */ - callbackParam.expectedType = AWS_IOT_SHADOW_UPDATE_COMPLETE; - - /* Set the members of the Shadow document info for UPDATE. */ - documentInfo.u.update.pUpdateDocument = TEST_SHADOW_DOCUMENT; - documentInfo.u.update.updateDocumentLength = TEST_SHADOW_DOCUMENT_LENGTH; - - /* Create a new Shadow document. */ - status = AwsIotShadow_UpdateAsync( _mqttConnection, - &documentInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting to update Shadow document." ); - } - - /* Set the expected callback type to GET. */ - callbackParam.expectedType = AWS_IOT_SHADOW_GET_COMPLETE; - - /* Retrieve the Shadow document. */ - status = AwsIotShadow_GetAsync( _mqttConnection, - &documentInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_STATUS_PENDING, status ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting to retrieve Shadow document." ); - } - - /* Set the expected callback type to DELETE. */ - callbackParam.expectedType = AWS_IOT_SHADOW_DELETE_COMPLETE; - - /* Delete the Shadow document. */ - status = AwsIotShadow_DeleteAsync( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting to delete Shadow document." ); - } - } - - IotSemaphore_Destroy( &( callbackParam.waitSem ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Run the Update-Get-Delete blocking tests at various QoS. - */ -static void _updateGetDeleteBlocking( IotMqttQos_t qos ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - const char * pShadowDocument = NULL, * pJsonValue = NULL; - size_t shadowDocumentLength = 0, jsonValueLength = 0; - - /* Initialize the common members of the Shadow document info. */ - documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - documentInfo.thingNameLength = THING_NAME_LENGTH; - documentInfo.qos = qos; - - /* Set the members of the Shadow document info for UPDATE. */ - documentInfo.u.update.pUpdateDocument = TEST_SHADOW_DOCUMENT; - documentInfo.u.update.updateDocumentLength = TEST_SHADOW_DOCUMENT_LENGTH; - - /* Create a new Shadow document. */ - status = AwsIotShadow_UpdateSync( _mqttConnection, - &documentInfo, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Set the members of the Shadow document info for GET. */ - documentInfo.u.get.mallocDocument = IotTest_Malloc; - - /* Retrieve the Shadow document. */ - status = AwsIotShadow_GetSync( _mqttConnection, - &documentInfo, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT, - &pShadowDocument, - &shadowDocumentLength ); - TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Check the retrieved Shadow document. */ - TEST_ASSERT_GREATER_THAN( 0, shadowDocumentLength ); - TEST_ASSERT_NOT_NULL( pShadowDocument ); - TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( pShadowDocument, - shadowDocumentLength, - "key", - 3, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 7, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "\"value\"", pJsonValue, jsonValueLength ); - - /* Free the retrieved Shadow document. */ - IotTest_Free( ( void * ) pShadowDocument ); - - /* Delete the Shadow document. */ - status = AwsIotShadow_DeleteSync( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Initializes libraries and establishes an MQTT connection for the Shadow tests. - */ -static void _setupShadowTests() -{ - int32_t i = 0; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* Initialize SDK and libraries. */ - AwsIotShadow_Assert( IotSdk_Init() == true ); - AwsIotShadow_Assert( IotTestNetwork_Init() == IOT_NETWORK_SUCCESS ); - AwsIotShadow_Assert( IotMqtt_Init() == IOT_MQTT_SUCCESS ); - AwsIotShadow_Assert( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ); - - /* Set the MQTT network setup parameters. */ - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - - #if IOT_TEST_SECURED_CONNECTION == 1 - networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; - #endif - - #ifdef IOT_TEST_MQTT_SERIALIZER - networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; - #endif - - /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT - * client identifier. */ - connectInfo.awsIotMqttMode = true; - connectInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; - connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); - connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - - /* Establish an MQTT connection. Allow up to 3 attempts with a 5 second wait - * if the connection fails. */ - for( i = 0; i < 3; i++ ) - { - connectStatus = IotMqtt_Connect( &networkInfo, - &connectInfo, - AWS_IOT_TEST_SHADOW_TIMEOUT, - &_mqttConnection ); - - if( ( connectStatus == IOT_MQTT_TIMEOUT ) || ( connectStatus == IOT_MQTT_NETWORK_ERROR ) ) - { - IotClock_SleepMs( 5000 ); - } - else - { - break; - } - } - - AwsIotShadow_Assert( connectStatus == IOT_MQTT_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Cleans up libraries and closes the MQTT connection for the Shadow tests. - */ -static void _cleanupShadowTests() -{ - /* Disconnect the MQTT connection if it was created. */ - if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) - { - IotMqtt_Disconnect( _mqttConnection, 0 ); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - } - - /* Clean up the Shadow library. */ - AwsIotShadow_Cleanup(); - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); - - /* Clean up the network stack. */ - IotTestNetwork_Cleanup(); - - /* Clean up SDK. */ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Shadow system tests. - */ -TEST_GROUP( Shadow_System ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Shadow system tests. - */ -TEST_SETUP( Shadow_System ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - - /* Delete any existing Shadow so all tests start with no Shadow. */ - status = AwsIotShadow_DeleteSync( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - - /* Acceptable statuses are SUCCESS and NOT FOUND. Both of these statuses allow - * the tests to start with no Shadow. */ - if( ( status != AWS_IOT_SHADOW_SUCCESS ) && ( status != AWS_IOT_SHADOW_NOT_FOUND ) ) - { - TEST_FAIL_MESSAGE( "Failed to delete shadow in test set up." ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Shadow system tests. - */ -TEST_TEAR_DOWN( Shadow_System ) -{ - /* Cool down time to avoid making too many requests. */ - IotClock_SleepMs( 100 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Shadow system tests. - */ -TEST_GROUP_RUNNER( Shadow_System ) -{ - _setupShadowTests(); - - RUN_TEST_CASE( Shadow_System, UpdateGetDeleteAsyncQoS0 ); - RUN_TEST_CASE( Shadow_System, UpdateGetDeleteAsyncQoS1 ); - RUN_TEST_CASE( Shadow_System, UpdateGetDeleteBlockingQoS0 ); - RUN_TEST_CASE( Shadow_System, UpdateGetDeleteBlockingQoS1 ); - RUN_TEST_CASE( Shadow_System, DeltaCallback ); - RUN_TEST_CASE( Shadow_System, UpdatedCallback ); - - _cleanupShadowTests(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Update-Get-Delete asynchronous (QoS 0). - */ -TEST( Shadow_System, UpdateGetDeleteAsyncQoS0 ) -{ - _updateGetDeleteAsync( IOT_MQTT_QOS_0 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Update-Get-Delete asynchronous (QoS 1). - */ -TEST( Shadow_System, UpdateGetDeleteAsyncQoS1 ) -{ - _updateGetDeleteAsync( IOT_MQTT_QOS_1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Update-Get-Delete blocking (QoS 0). - */ -TEST( Shadow_System, UpdateGetDeleteBlockingQoS0 ) -{ - _updateGetDeleteBlocking( IOT_MQTT_QOS_0 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Update-Get-Delete blocking (QoS 1). - */ -TEST( Shadow_System, UpdateGetDeleteBlockingQoS1 ) -{ - _updateGetDeleteBlocking( IOT_MQTT_QOS_1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the Shadow delta callback. - */ -TEST( Shadow_System, DeltaCallback ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* Create a semaphore to wait on. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 2 ) ); - - /* Set the delta callback information. */ - deltaCallback.pCallbackContext = &waitSem; - deltaCallback.function = _deltaCallback; - - /* Set a desired state in the Update document. */ - updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - updateDocument.thingNameLength = THING_NAME_LENGTH; - updateDocument.u.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; - updateDocument.u.update.updateDocumentLength = 65; - - if( TEST_PROTECT() ) - { - /* Set the delta callback. */ - status = AwsIotShadow_SetDeltaCallback( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - &deltaCallback ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Create a Shadow document with a desired state. */ - status = AwsIotShadow_UpdateSync( _mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Set a different reported state in the Update document. */ - updateDocument.u.update.pUpdateDocument = "{\"state\": {\"reported\": {\"key\": false}}, \"clientToken\":\"shadowtest\"}"; - updateDocument.u.update.updateDocumentLength = 67; - - /* Create a Shadow document with a reported state. */ - status = AwsIotShadow_UpdateSync( _mqttConnection, - &updateDocument, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Block on the wait semaphore until the delta callback is invoked. */ - if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for delta callback." ); - } - - /* Remove the delta callback. */ - status = AwsIotShadow_SetDeltaCallback( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Remove persistent subscriptions for Shadow Update. */ - status = AwsIotShadow_RemovePersistentSubscriptions( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the Shadow updated callback. - */ -TEST( Shadow_System, UpdatedCallback ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowCallbackInfo_t updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* Create a semaphore to wait on. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - /* Set the delta callback information. */ - updatedCallback.pCallbackContext = &waitSem; - updatedCallback.function = _updatedCallback; - - /* Set a desired state in the Update document. */ - updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - updateDocument.thingNameLength = THING_NAME_LENGTH; - updateDocument.u.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; - updateDocument.u.update.updateDocumentLength = 65; - - if( TEST_PROTECT() ) - { - /* Set the updated callback. */ - status = AwsIotShadow_SetUpdatedCallback( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - &updatedCallback ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Create a Shadow document. */ - status = AwsIotShadow_UpdateSync( _mqttConnection, - &updateDocument, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - - /* Block on the wait semaphore until the updated callback is invoked. */ - if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for updated callback." ); - } - - /* Remove the updated callback. */ - status = AwsIotShadow_SetUpdatedCallback( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_api.c b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_api.c deleted file mode 100644 index c28bd61762..0000000000 --- a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_api.c +++ /dev/null @@ -1,695 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_shadow_api.c - * @brief Tests for the user-facing API functions (declared in aws_iot_shadow.h). - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* MQTT mock include. */ -#include "iot_tests_mqtt_mock.h" - -/** - * @brief Whether to check the number of MQTT library errors in the malloc - * failure tests. - * - * Should only be checked if malloc overrides are available and not testing for - * code coverage. In static memory mode, there should be no MQTT library errors. - */ -#if ( IOT_TEST_COVERAGE == 1 ) || ( IOT_TEST_NO_MALLOC_OVERRIDES == 1 ) - #define CHECK_MQTT_ERROR_COUNT( expected, actual ) -#else - #if ( IOT_STATIC_MEMORY_ONLY == 1 ) - #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( 0, actual ) - #else - #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( expected, actual ) - #endif -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief The Thing Name shared among all the tests. - */ -#define TEST_THING_NAME "TestThingName" - -/** - * @brief The length of #TEST_THING_NAME. - */ -#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief The MQTT connection shared among the tests. - */ -static IotMqttConnection_t _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Shadow API tests. - */ -TEST_GROUP( Shadow_Unit_API ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Shadow API tests. - */ -TEST_SETUP( Shadow_Unit_API ) -{ - /* Initialize SDK. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - - /* Initialize the Shadow library. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); - - /* Initialize MQTT mock. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_pMqttConnection ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Shadow API tests. - */ -TEST_TEAR_DOWN( Shadow_Unit_API ) -{ - /* Clean up MQTT mock. */ - IotTest_MqttMockCleanup(); - - /* Clean up the Shadow library. */ - AwsIotShadow_Cleanup(); - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); - - /* Clean up SDK. */ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Shadow API tests. - */ -TEST_GROUP_RUNNER( Shadow_Unit_API ) -{ - RUN_TEST_CASE( Shadow_Unit_API, Init ); - RUN_TEST_CASE( Shadow_Unit_API, StringCoverage ); - RUN_TEST_CASE( Shadow_Unit_API, OperationInvalidParameters ); - RUN_TEST_CASE( Shadow_Unit_API, DocumentInvalidParameters ); - RUN_TEST_CASE( Shadow_Unit_API, WaitInvalidParameters ); - RUN_TEST_CASE( Shadow_Unit_API, DeleteMallocFail ); - RUN_TEST_CASE( Shadow_Unit_API, GetMallocFail ); - RUN_TEST_CASE( Shadow_Unit_API, UpdateMallocFail ); - RUN_TEST_CASE( Shadow_Unit_API, SetCallbackMallocFail ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the function @ref shadow_function_init. - */ -TEST( Shadow_Unit_API, Init ) -{ - int32_t i = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Check that test set up set the default value. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); - - /* The Shadow library was already initialized by test set up. Clean it up - * before running this test. */ - AwsIotShadow_Cleanup(); - - /* Calling cleanup twice should not crash. */ - AwsIotShadow_Cleanup(); - - /* Set a MQTT timeout. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1 ) ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotShadowMqttTimeoutMs ); - - /* Cleanup should restore the default MQTT timeout. */ - AwsIotShadow_Cleanup(); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); - - /* Calling API functions without calling AwsIotShadow_Init should fail. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); - - documentInfo.pThingName = TEST_THING_NAME; - documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; - status = AwsIotShadow_GetAsync( _pMqttConnection, &documentInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); - - status = AwsIotShadow_UpdateAsync( _pMqttConnection, &documentInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); - - status = AwsIotShadow_Wait( operation, 500, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); - - status = AwsIotShadow_SetDeltaCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); - - status = AwsIotShadow_SetUpdatedCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); - - /* Test Shadow initialization with mutex creation failures. */ - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - status = AwsIotShadow_Init( 0 ); - - /* Check that the status is either success or "INIT FAILED". */ - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - break; - } - - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_INIT_FAILED, status ); - } - - /* Initialize the Shadow library for test clean up. Calling init twice should not crash. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Provides code coverage of the Shadow enum-to-string function, - * @ref shadow_function_strerror. - */ -TEST( Shadow_Unit_API, StringCoverage ) -{ - size_t i = 0; - const char * pMessage = NULL; - - const char * pInvalidStatus = "INVALID STATUS"; - size_t invalidStatusLength = strlen( pInvalidStatus ); - - /* For each Shadow Error, check the returned string. */ - const AwsIotShadowError_t pApiErrors[] = - { - AWS_IOT_SHADOW_SUCCESS, AWS_IOT_SHADOW_STATUS_PENDING, AWS_IOT_SHADOW_INIT_FAILED, - AWS_IOT_SHADOW_BAD_PARAMETER, AWS_IOT_SHADOW_NO_MEMORY, AWS_IOT_SHADOW_MQTT_ERROR, - AWS_IOT_SHADOW_BAD_RESPONSE, AWS_IOT_SHADOW_TIMEOUT, AWS_IOT_SHADOW_NOT_INITIALIZED - }; - - for( i = 0; i < ( sizeof( pApiErrors ) / sizeof( pApiErrors[ 0 ] ) ); i++ ) - { - pMessage = AwsIotShadow_strerror( pApiErrors[ i ] ); - TEST_ASSERT_NOT_NULL( pMessage ); - - TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); - } - - /* For each rejection reason (from the Shadow service) check the returned string. */ - const AwsIotShadowError_t pRejectionReasons[] = - { - AWS_IOT_SHADOW_BAD_REQUEST, AWS_IOT_SHADOW_UNAUTHORIZED, AWS_IOT_SHADOW_FORBIDDEN, - AWS_IOT_SHADOW_NOT_FOUND, AWS_IOT_SHADOW_CONFLICT, AWS_IOT_SHADOW_TOO_LARGE, - AWS_IOT_SHADOW_UNSUPPORTED, AWS_IOT_SHADOW_TOO_MANY_REQUESTS, AWS_IOT_SHADOW_SERVER_ERROR - }; - - for( i = 0; i < ( sizeof( pRejectionReasons ) / sizeof( pRejectionReasons[ 0 ] ) ); i++ ) - { - pMessage = AwsIotShadow_strerror( pRejectionReasons[ i ] ); - TEST_ASSERT_NOT_NULL( pMessage ); - - TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); - } - - /* Check an invalid status. */ - pMessage = AwsIotShadow_strerror( ( AwsIotShadowError_t ) -1 ); - TEST_ASSERT_EQUAL_STRING( pInvalidStatus, pMessage ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of Shadow operation functions with various - * invalid parameters. - */ -TEST( Shadow_Unit_API, OperationInvalidParameters ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - - /* Missing Thing Name. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - NULL, - 0, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - TEST_THING_NAME, - 0, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - status = AwsIotShadow_UpdateAsync( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* Thing Name too long. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - TEST_THING_NAME, - AWS_IOT_MAX_THING_NAME_LENGTH + 1, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* No reference with waitable operation. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* Both callback and waitable flag set. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_WAITABLE, - &callbackInfo, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* No callback for non-waitable GET. */ - documentInfo.pThingName = TEST_THING_NAME; - documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; - status = AwsIotShadow_GetAsync( _pMqttConnection, - &documentInfo, - 0, - NULL, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* Callback function not set. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - 0, - &callbackInfo, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of Shadow operation functions with an invalid - * document info parameter. - */ -TEST( Shadow_Unit_API, DocumentInvalidParameters ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Missing Thing Name. */ - status = AwsIotShadow_GetAsync( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - documentInfo.pThingName = TEST_THING_NAME; - documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; - - /* Invalid QoS. */ - documentInfo.qos = ( IotMqttQos_t ) 3; - status = AwsIotShadow_GetAsync( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - documentInfo.qos = IOT_MQTT_QOS_0; - - /* Invalid retry parameters. */ - documentInfo.retryLimit = 1; - status = AwsIotShadow_GetAsync( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - documentInfo.retryLimit = 0; - - /* Waitable Shadow get with no memory allocation function. */ - status = AwsIotShadow_GetAsync( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* Update with no document. */ - status = AwsIotShadow_UpdateAsync( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* Update with no client token. */ - documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}}"; - documentInfo.u.update.updateDocumentLength = 29; - status = AwsIotShadow_UpdateAsync( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* Client token too long. */ - documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}},\"clientToken\": " - "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; - documentInfo.u.update.updateDocumentLength = 146; - status = AwsIotShadow_UpdateAsync( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref shadow_function_wait with various - * invalid parameters. - */ -TEST( Shadow_Unit_API, WaitInvalidParameters ) -{ - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - _shadowOperation_t operation = { .link = { 0 } }; - - /* NULL reference. */ - status = AwsIotShadow_Wait( NULL, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* No waitable flag set. */ - status = AwsIotShadow_Wait( &operation, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - - /* NULL output parameters for Shadow GET. */ - operation.flags = AWS_IOT_SHADOW_FLAG_WAITABLE; - operation.type = SHADOW_GET; - status = AwsIotShadow_Wait( &operation, 0, NULL, NULL ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref shadow_function_deleteasync when memory - * allocation fails at various points. - */ -TEST( Shadow_Unit_API, DeleteMallocFail ) -{ - int32_t i = 0, mqttErrorCount = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Set a short timeout so this test runs faster. */ - _AwsIotShadowMqttTimeoutMs = 75; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Shadow DELETE. Memory allocation will fail at various times - * during this call. */ - status = AwsIotShadow_DeleteAsync( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &deleteOperation ); - - /* Once the Shadow DELETE call succeeds, wait for it to complete. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - /* No response will be received from the network, so the Shadow DELETE - * is expected to time out. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, - AwsIotShadow_Wait( deleteOperation, 0, NULL, NULL ) ); - break; - } - - /* Count the number of MQTT library errors. Otherwise, check that the error - * is a "No memory" error. */ - if( status == AWS_IOT_SHADOW_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); - } - } - - /* Allow 2 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, UNSUBSCRIBE). */ - CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref shadow_function_getasync when memory - * allocation fails at various points. - */ -TEST( Shadow_Unit_API, GetMallocFail ) -{ - int32_t i = 0, mqttErrorCount = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - const char * pRetrievedDocument = NULL; - size_t retrievedDocumentSize = 0; - - /* Set a short timeout so this test runs faster. */ - _AwsIotShadowMqttTimeoutMs = 75; - - /* Set the members of the document info. */ - documentInfo.pThingName = TEST_THING_NAME; - documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; - documentInfo.qos = IOT_MQTT_QOS_1; - documentInfo.u.get.mallocDocument = IotTest_Malloc; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Shadow GET. Memory allocation will fail at various times - * during this call. */ - status = AwsIotShadow_GetAsync( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &getOperation ); - - /* Once the Shadow GET call succeeds, wait for it to complete. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - /* No response will be received from the network, so the Shadow GET - * is expected to time out. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, - AwsIotShadow_Wait( getOperation, - 0, - &pRetrievedDocument, - &retrievedDocumentSize ) ); - break; - } - - /* Count the number of MQTT library errors. Otherwise, check that the error - * is a "No memory" error. */ - if( status == AWS_IOT_SHADOW_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); - } - } - - /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). */ - CHECK_MQTT_ERROR_COUNT( 3, mqttErrorCount ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref shadow_function_updateasync when memory - * allocation fails at various points. - */ -TEST( Shadow_Unit_API, UpdateMallocFail ) -{ - int32_t i = 0, mqttErrorCount = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; - - /* Set a short timeout so this test runs faster. */ - _AwsIotShadowMqttTimeoutMs = 75; - - /* Set the members of the document info. */ - documentInfo.pThingName = TEST_THING_NAME; - documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; - documentInfo.qos = IOT_MQTT_QOS_1; - documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}},\"clientToken\":\"TEST\"}"; - documentInfo.u.update.updateDocumentLength = 50; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Shadow UPDATE. Memory allocation will fail at various times - * during this call. */ - status = AwsIotShadow_UpdateAsync( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &updateOperation ); - - /* Once the Shadow UPDATE call succeeds, wait for it to complete. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - /* No response will be received from the network, so the Shadow UPDATE - * is expected to time out. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_TIMEOUT, - AwsIotShadow_Wait( updateOperation, - 0, - NULL, - NULL ) ); - break; - } - - /* Count the number of MQTT library errors. Otherwise, check that the error - * is a "No memory" error. */ - if( status == AWS_IOT_SHADOW_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); - } - } - - /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH, UNSUBSCRIBE). */ - CHECK_MQTT_ERROR_COUNT( 3, mqttErrorCount ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of the Shadow set callback functions when memory - * allocation fails at various points. - */ -TEST( Shadow_Unit_API, SetCallbackMallocFail ) -{ - int32_t i = 0, mqttErrorCount = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; - - /* Set a short timeout so this test runs faster. */ - _AwsIotShadowMqttTimeoutMs = 75; - - /* A non-NULL callback function. */ - callbackInfo.function = ( void ( * )( void *, AwsIotShadowCallbackParam_t * ) ) 0x01; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Shadow set callback. Memory allocation will fail at various times - * during this call. */ - status = AwsIotShadow_SetDeltaCallback( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - 0, - &callbackInfo ); - - if( status == AWS_IOT_SHADOW_SUCCESS ) - { - break; - } - else if( status == AWS_IOT_SHADOW_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); - } - } - - /* Allow 1 MQTT error, caused by failure to allocate memory for a SUBACK. */ - CHECK_MQTT_ERROR_COUNT( 1, mqttErrorCount ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c deleted file mode 100644 index 26da044388..0000000000 --- a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c +++ /dev/null @@ -1,305 +0,0 @@ -/* - * AWS IoT Shadow V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_tests_shadow_parser.c - * @brief Tests for the Shadow topic name and JSON parser functions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Shadow internal include. */ -#include "private/aws_iot_shadow_internal.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* SDK initialization include. */ -#include "iot_init.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief The size of the buffers allocated for holding Shadow error documents. - */ -#define ERROR_DOCUMENT_BUFFER_SIZE ( 128 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Wrapper for generating and parsing error documents. - */ -static void _generateParseErrorDocument( char * pErrorDocument, - AwsIotShadowError_t expectedCode, - const char * pFormat, - ... ) -{ - int errorDocumentLength = 0; - va_list arguments; - - /* Generate an error document. */ - va_start( arguments, pFormat ); - errorDocumentLength = vsnprintf( pErrorDocument, - ERROR_DOCUMENT_BUFFER_SIZE, - pFormat, - arguments ); - va_end( arguments ); - - /* Check for errors from vsnprintf. */ - TEST_ASSERT_GREATER_THAN_INT( 0, errorDocumentLength ); - TEST_ASSERT_LESS_THAN_INT( ERROR_DOCUMENT_BUFFER_SIZE, errorDocumentLength ); - - /* Parse the error document and check the result. */ - TEST_ASSERT_EQUAL( expectedCode, - _AwsIotShadow_ParseErrorDocument( pErrorDocument, - ( size_t ) errorDocumentLength ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Wrapper for parsing Shadow Thing Names and checking the result. - */ -static void _parseThingName( const char * pTopicName, - bool expectedResult, - const char * pExpectedThingName ) -{ - bool status = true; - uint16_t topicNameLength = ( uint16_t ) strlen( pTopicName ); - const char * pThingName = NULL; - size_t thingNameLength = 0; - - status = AwsIot_ParseThingName( pTopicName, - topicNameLength, - &pThingName, - &thingNameLength ); - TEST_ASSERT_EQUAL( expectedResult, status ); - - if( expectedResult == true ) - { - TEST_ASSERT_EQUAL( strlen( pExpectedThingName ), thingNameLength ); - TEST_ASSERT_EQUAL_STRING_LEN( pExpectedThingName, pThingName, thingNameLength ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for Shadow parser tests. - */ -TEST_GROUP( Shadow_Unit_Parser ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Shadow parser tests. - */ -TEST_SETUP( Shadow_Unit_Parser ) -{ - /* Initialize SDK. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - /* Initialize the Shadow library. */ - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for Shadow parser tests. - */ -TEST_TEAR_DOWN( Shadow_Unit_Parser ) -{ - /* Clean up the Shadow library. */ - AwsIotShadow_Cleanup(); - - /* Clean up SDK. */ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for Shadow parser tests. - */ -TEST_GROUP_RUNNER( Shadow_Unit_Parser ) -{ - RUN_TEST_CASE( Shadow_Unit_Parser, StatusValid ); - RUN_TEST_CASE( Shadow_Unit_Parser, StatusInvalid ); - RUN_TEST_CASE( Shadow_Unit_Parser, ErrorDocument ); - RUN_TEST_CASE( Shadow_Unit_Parser, ErrorDocumentInvalid ); - RUN_TEST_CASE( Shadow_Unit_Parser, ThingName ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing Shadow status from a valid topic name. - */ -TEST( Shadow_Unit_Parser, StatusValid ) -{ - AwsIotStatus_t status = AWS_IOT_UNKNOWN; - - /* Parse "accepted" status. */ - status = AwsIot_ParseStatus( "$aws/things/Test_device/shadow/accepted", - 39 ); - TEST_ASSERT_EQUAL( AWS_IOT_ACCEPTED, status ); - - /* Parse "rejected" status. */ - status = AwsIot_ParseStatus( "$aws/things/Test_device/shadow/rejected", - 39 ); - TEST_ASSERT_EQUAL( AWS_IOT_REJECTED, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing Shadow status from invalid topic names. - */ -TEST( Shadow_Unit_Parser, StatusInvalid ) -{ - /* Topic too short. */ - TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, - AwsIot_ParseStatus( "accepted", - 8 ) ); - - /* Topic missing last character. */ - TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, - AwsIot_ParseStatus( "$aws/things/Test_device/shadow/accepte", - 38 ) ); - - /* Topic missing level separator. */ - TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, - AwsIot_ParseStatus( "$aws/things/Test_device/shadowaccepted", - 38 ) ); - - /* Topic suffix isn't "accepted" or "rejected". */ - TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, - AwsIot_ParseStatus( "$aws/things/Test_device/shadow/unknown", - 38 ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing valid Shadow error documents. - */ -TEST( Shadow_Unit_Parser, ErrorDocument ) -{ - size_t i = 0; - char pErrorDocument[ ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; - AwsIotShadowError_t pValidErrorCodes[] = - { - AWS_IOT_SHADOW_BAD_REQUEST, - AWS_IOT_SHADOW_UNAUTHORIZED, - AWS_IOT_SHADOW_FORBIDDEN, - AWS_IOT_SHADOW_NOT_FOUND, - AWS_IOT_SHADOW_CONFLICT, - AWS_IOT_SHADOW_TOO_LARGE, - AWS_IOT_SHADOW_UNSUPPORTED, - AWS_IOT_SHADOW_TOO_MANY_REQUESTS, - AWS_IOT_SHADOW_SERVER_ERROR - }; - - /* Generate an error document for every valid error code and parse it. */ - for( i = 0; i < ( sizeof( pValidErrorCodes ) / sizeof( pValidErrorCodes[ 0 ] ) ); i++ ) - { - _generateParseErrorDocument( pErrorDocument, - pValidErrorCodes[ i ], - "{\"code\": %d, \"message\": \"%s\"}", - ( int ) pValidErrorCodes[ i ], - "Test" ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing invalid Shadow error documents. - */ -TEST( Shadow_Unit_Parser, ErrorDocumentInvalid ) -{ - char pErrorDocument[ ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; - - /* Parse an error document with an unknown code. */ - _generateParseErrorDocument( pErrorDocument, - AWS_IOT_SHADOW_BAD_RESPONSE, - "{\"code\": %d, \"message\": \"%s\"}", - -1, - "Test" ); - - /* Parse an error document missing the "code" key. */ - _generateParseErrorDocument( pErrorDocument, - AWS_IOT_SHADOW_BAD_RESPONSE, - "{\"message\": \"Test\"}" ); - - /* Parse an error document missing the "message" key. */ - _generateParseErrorDocument( pErrorDocument, - AWS_IOT_SHADOW_BAD_RESPONSE, - "{\"code\": 400}" ); - - /* Parse a malformed error document where the code is unterminated. */ - _generateParseErrorDocument( pErrorDocument, - AWS_IOT_SHADOW_BAD_RESPONSE, - "{\"code\": 400" ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests parsing both valid and invalid Shadow topics. - */ -TEST( Shadow_Unit_Parser, ThingName ) -{ - /* Valid operation topic. */ - _parseThingName( "$aws/things/TEST/shadow/get/accepted", - true, - "TEST" ); - - /* Valid callback topic. */ - _parseThingName( "$aws/things/TEST/shadow/update/delta", - true, - "TEST" ); - - /* Topic too short. */ - _parseThingName( "$aws/things/TEST/", - false, - "TEST" ); - - /* Incorrect prefix. */ - _parseThingName( "$awsshadow/TEST/shadow/update/accepted", - false, - "TEST" ); - - /* Thing Name unterminated. */ - _parseThingName( "$aws/things/TESTTESTTESTTESTTESTTEST", - false, - "TESTTESTTESTTESTTESTTEST" ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/platform/iot_clock.h b/libraries/platform/iot_clock.h deleted file mode 100644 index 98e69d2165..0000000000 --- a/libraries/platform/iot_clock.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * IoT Platform V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_clock.h - * @brief Time-related functions used by libraries in this SDK. - */ - -#ifndef IOT_CLOCK_H_ -#define IOT_CLOCK_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Platform layer types include. */ -#include "types/iot_platform_types.h" - -/** - * @functionspage{platform_clock,platform clock component,Clock} - * - @functionname{platform_clock_function_gettimestring} - * - @functionname{platform_clock_function_gettimems} - * - @functionname{platform_clock_function_sleepms} - * - @functionname{platform_clock_function_timercreate} - * - @functionname{platform_clock_function_timerdestroy} - * - @functionname{platform_clock_function_timerarm} - */ - -/** - * @functionpage{IotClock_GetTimestring,platform_clock,gettimestring} - * @functionpage{IotClock_GetTimeMs,platform_clock,gettimems} - * @functionpage{IotClock_SleepMs,platform_clock,sleepms} - * @functionpage{IotClock_TimerCreate,platform_clock,timercreate} - * @functionpage{IotClock_TimerDestroy,platform_clock,timerdestroy} - * @functionpage{IotClock_TimerArm,platform_clock,timerarm} - */ - -/** - * @brief Generates a human-readable timestring, such as "01 Jan 2018 12:00". - * - * This function uses the system clock to generate a human-readable timestring. - * This timestring is printed by the [logging functions](@ref logging_functions). - * - * @param[out] pBuffer A buffer to store the timestring in. - * @param[in] bufferSize The size of `pBuffer`. - * @param[out] pTimestringLength The actual length of the timestring stored in - * `pBuffer`. - * - * @return `true` if a timestring was successfully generated; `false` otherwise. - * - * @warning The implementation of this function must not call any [logging functions] - * (@ref logging_functions). - * - * Example - * @code{c} - * char timestring[ 32 ]; - * size_t timestringLength = 0; - * - * if( IotClock_GetTimestring( timestring, 32, ×tringLength ) == true ) - * { - * printf( "Timestring: %.*s", timestringLength, timestring ); - * } - * @endcode - */ -/* @[declare_platform_clock_gettimestring] */ -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ); -/* @[declare_platform_clock_gettimestring] */ - -/** - * @brief Returns a nonzero, monotonically-increasing system time in milliseconds. - * - * This function reads a millisecond-resolution system clock. The clock should - * always be monotonically-increasing; therefore, real-time clocks that may be - * set by another process are not suitable for this function's implementation. - * - * @return The value of the system clock. This function is not expected to fail. - * - * Example - * @code{c} - * // Get current time. - * uint64_t currentTime = IotClock_GetTimeMs(); - * @endcode - */ -/* @[declare_platform_clock_gettimems] */ -uint64_t IotClock_GetTimeMs( void ); -/* @[declare_platform_clock_gettimems] */ - -/** - * @brief Delay for the given number of milliseconds. - * - * This function suspends its calling thread for at least `sleepTimeMs` milliseconds. - * - * @param[in] sleepTimeMs Sleep time (in milliseconds). - */ -/* @[declare_platform_clock_sleepms] */ -void IotClock_SleepMs( uint32_t sleepTimeMs ); -/* @[declare_platform_clock_sleepms] */ - -/** - * @brief Create a new timer. - * - * This function creates a new, unarmed timer. It must be called on an uninitialized - * #IotTimer_t. This function must not be called on an already-initialized #IotTimer_t. - * - * @param[out] pNewTimer Set to a new timer handle on success. - * @param[in] expirationRoutine The function to run when this timer expires. This - * function should be called in its own detached thread. - * @param[in] pArgument The argument to pass to `expirationRoutine`. - * - * @return `true` if the timer is successfully created; `false` otherwise. - * - * @see @ref platform_clock_function_timerdestroy, @ref platform_clock_function_timerarm - */ -/* @[declare_platform_clock_timercreate] */ -bool IotClock_TimerCreate( IotTimer_t * pNewTimer, - IotThreadRoutine_t expirationRoutine, - void * pArgument ); -/* @[declare_platform_clock_timercreate] */ - -/** - * @brief Free resources used by a timer. - * - * This function frees resources used by a timer. It must be called on an initialized - * #IotTimer_t. No other timer functions should be called on `pTimer` after calling - * this function (unless the timer is re-created). - * - * This function will stop the `pTimer` if it is armed. - * - * @param[in] pTimer The timer to destroy. - * - * @see @ref platform_clock_function_timercreate, @ref platform_clock_function_timerarm - */ -/* @[declare_platform_clock_timerdestroy] */ -void IotClock_TimerDestroy( IotTimer_t * pTimer ); -/* @[declare_platform_clock_timerdestroy] */ - -/** - * @brief Arm a timer to expire at the given relative timeout. - * - * This function arms a timer to run its expiration routine at the given time. - * - * If `periodMs` is nonzero, the timer should expire periodically at intervals - * such as: - * - `relativeTimeoutMs` - * - `relativeTimeoutMs + periodMs` - * - `relativeTimeoutMs + 2 * periodMs` - * - Etc. (subject to some jitter). - * - * Setting `periodMs` to `0` arms a one-shot, non-periodic timer. - * - * @param[in] pTimer The timer to arm. - * @param[in] relativeTimeoutMs When the timer should expire, relative to the time - * this function is called. - * @param[in] periodMs How often the timer should expire again after `relativeTimerMs`. - * - * @return `true` if the timer was successfully armed; `false` otherwise. - * - * @see @ref platform_clock_function_timercreate, @ref platform_clock_function_timerdestroy - * - * Example - * @code{c} - * - * void timerExpirationRoutine( void * pArgument ); - * - * void timerExample( void ) - * { - * IotTimer_t timer; - * - * if( IotClock_TimerCreate( &timer, timerExpirationRoutine, NULL ) == true ) - * { - * // Set the timer to periodically expire every 10 seconds. - * if( IotClock_TimerArm( &timer, 10000, 10000 ) == true ) - * { - * // Wait for timer to expire. - * } - * - * IotClock_TimerDestroy( &timer ); - * } - * } - * @endcode - */ -/* @[declare_platform_clock_timerarm] */ -bool IotClock_TimerArm( IotTimer_t * pTimer, - uint32_t relativeTimeoutMs, - uint32_t periodMs ); -/* @[declare_platform_clock_timerarm] */ - -#endif /* ifndef IOT_CLOCK_H_ */ diff --git a/libraries/platform/iot_metrics.h b/libraries/platform/iot_metrics.h deleted file mode 100644 index 7cd5741f1a..0000000000 --- a/libraries/platform/iot_metrics.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * IoT Platform V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_metrics.h - * @brief Functions for retrieving [Device Defender](@ref defender) metrics. - * - * The functions in this header are only required by Device Defender. They do not - * need to be implemented if Device Defender is not used. - */ - -#ifndef IOT_METRICS_H_ -#define IOT_METRICS_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Linear containers (lists and queues) include. */ -#include "iot_linear_containers.h" - -/** - * @functionspage{platform_metrics,platform metrics component,Metrics} - * - @functionname{platform_metrics_function_init} - * - @functionname{platform_metrics_function_cleanup} - * - @functionname{platform_metrics_function_gettcpconnections} - */ - -/** - * @functionpage{IotMetrics_Init,platform_metrics,init} - * @functionpage{IotMetrics_Cleanup,platform_metrics,cleanup} - * @functionpage{IotMetrics_GetTcpConnections,platform_metrics,gettcpconnections} - */ - -/** - * @brief One-time initialization function for the platform metrics component. - * - * This function initializes the platform metrics component. It must be called - * once (and only once) before calling any other metrics or [Device Defender function] - * (@ref defender_functions). Calling this function more than once without first - * calling @ref platform_metrics_function_cleanup may result in a crash. - * - * @return `true` is initialization succeeded; `false` otherwise. - * - * @warning No thread-safety guarantees are provided for this function. - */ -/* @[declare_platform_metrics_init] */ -bool IotMetrics_Init( void ); -/* @[declare_platform_metrics_init] */ - -/** - * @brief One-time deinitialization function for the platform metrics component. - * - * This function frees resources taken in @ref platform_metrics_function_init. - * No other metrics or [Device Defender functions](@ref defender_functions) may - * be called unless @ref platform_metrics_function_init is called again. - * - * @warning No thread-safety guarantees are provided for this function. - */ -/* @[declare_platform_metrics_cleanup] */ -void IotMetrics_Cleanup( void ); -/* @[declare_platform_metrics_cleanup] */ - -/** - * @brief Retrieve a list of active TCP connections from the system. - * - * The provided connections are reported by Device Defender. - * - * @param[in] pContext Context passed as the first parameter of `metricsCallback`. - * @param[in] metricsCallback Called by this function to provide the list of TCP - * connections. The list given by this function is should not be used after the - * callback returns. - */ -/* @[declare_platform_metrics_gettcpconnections] */ -void IotMetrics_GetTcpConnections( void * pContext, - void ( * metricsCallback )( void *, const IotListDouble_t * ) ); -/* @[declare_platform_metrics_gettcpconnections] */ - -#endif /* ifndef IOT_METRICS_H_ */ diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h deleted file mode 100644 index d0e809d904..0000000000 --- a/libraries/platform/iot_network.h +++ /dev/null @@ -1,368 +0,0 @@ -/* - * IoT Platform V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network.h - * @brief Abstraction of network functions used by libraries in this SDK. - */ - -#ifndef IOT_NETWORK_H_ -#define IOT_NETWORK_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Platform types include. */ -#include "types/iot_platform_types.h" - -/** - * @ingroup platform_datatypes_enums - * @brief Return codes for [network functions](@ref platform_network_functions). - */ -typedef enum IotNetworkError -{ - IOT_NETWORK_SUCCESS = 0, /**< Function successfully completed. */ - IOT_NETWORK_FAILURE, /**< Generic failure not covered by other values. */ - IOT_NETWORK_BAD_PARAMETER, /**< At least one parameter was invalid. */ - IOT_NETWORK_NO_MEMORY, /**< Memory allocation failed. */ - IOT_NETWORK_SYSTEM_ERROR /**< An error occurred when calling a system API. */ -} IotNetworkError_t; - -/** - * @ingroup platform_datatypes_enums - * @brief Disconnect reasons for [the network close callback](@ref platform_network_function_closecallback). - */ -typedef enum IotNetworkCloseReason -{ - IOT_NETWORK_NOT_CLOSED = 0, /**< Not closed, still open */ - IOT_NETWORK_SERVER_CLOSED, /**< Server closed connection. */ - IOT_NETWORK_TRANSPORT_FAILURE, /**< Transport failed. */ - IOT_NETWORK_CLIENT_CLOSED, /**< Client closed connection. */ - IOT_NETWORK_UNKNOWN_CLOSED /**< Unknown close reason. */ -} IotNetworkCloseReason_t; - -/** - * @page platform_network_functions Networking - * @brief Functions of the network abstraction component. - * - * The network abstraction component does not declare functions, but uses function - * pointers instead. This allows multiple network stacks to be used at the same time. - * Libraries that require the network will request an #IotNetworkInterface_t - * parameter. The members of the #IotNetworkInterface_t will be called whenever - * the library interacts with the network. - * - * The following function pointers are associated with an #IotNetworkInterface_t. - * Together, they represent a network stack. - * - @functionname{platform_network_function_create} - * - @functionname{platform_network_function_setreceivecallback} - * - @functionname{platform_network_function_setclosecallback} - * - @functionname{platform_network_function_send} - * - @functionname{platform_network_function_receive} - * - @functionname{platform_network_function_close} - * - @functionname{platform_network_function_destroy} - * - @functionname{platform_network_function_receivecallback} - * - @functionname{platform_network_function_closecallback} - */ - -/** - * @functionpage{IotNetworkInterface_t::create,platform_network,create} - * @functionpage{IotNetworkInterface_t::setReceiveCallback,platform_network,setreceivecallback} - * @functionpage{IotNetworkInterface_t::setCloseCallback,platform_network,setclosecallback} - * @functionpage{IotNetworkInterface_t::send,platform_network,send} - * @functionpage{IotNetworkInterface_t::receive,platform_network,receive} - * @functionpage{IotNetworkInterface_t::close,platform_network,close} - * @functionpage{IotNetworkInterface_t::destroy,platform_network,destroy} - * @functionpage{IotNetworkReceiveCallback_t,platform_network,receivecallback} - * @functionpage{IotNetworkReceiveCallback_t,platform_network,closecallback} - */ - -/** - * @brief Provide an asynchronous notification of incoming network data. - * - * A function with this signature may be set with @ref platform_network_function_setreceivecallback - * to be invoked when data is available on the network. - * - * @param[in] pConnection The connection on which data is available, defined by - * the network stack. - * @param[in] pContext The third argument passed to @ref platform_network_function_setreceivecallback. - */ -/* @[declare_platform_network_receivecallback] */ -typedef void ( * IotNetworkReceiveCallback_t )( IotNetworkConnection_t pConnection, - void * pContext ); -/* @[declare_platform_network_receivecallback] */ - -/** - * @brief Provide an asynchronous notification of network closing - * - * A function with this signature may be set with @ref platform_network_function_setclosecallback - * to be invoked when the network connection is closed. - * - * @param[in] pConnection The connection that was closed, defined by - * the network stack. - * @param[in] reason The reason the connection was closed - * @param[in] pContext The third argument passed to @ref platform_network_function_setclosecallback. - */ -/* @[declare_platform_network_closecallback] */ -typedef void ( * IotNetworkCloseCallback_t )( IotNetworkConnection_t pConnection, - IotNetworkCloseReason_t reason, - void * pContext ); -/* @[declare_platform_network_closecallback] */ - -/** - * @brief Create a new network connection. - * - * This function allocates resources and establishes a new network connection. - * @param[in] pServerInfo Represents information needed to set up the - * new connection, defined by the network stack. - * @param[in] pCredentialInfo Represents information needed to secure the - * new connection, defined by the network stack. - * @param[out] pConnection Set to represent a new connection, defined by the - * network stack. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - */ -/* @[declare_platform_network_create] */ -typedef IotNetworkError_t ( * IotNetworkCreate_t )( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ); -/* @[declare_platform_network_create] */ - -/** - * @brief Register an @ref platform_network_function_receivecallback. - * - * Sets an @ref platform_network_function_receivecallback to be called - * asynchronously when data arrives on the network. The network stack - * should invoke this function "as if" it were the thread routine of a - * detached thread. - * - * Each network connection may only have one receive callback at any time. - * @ref platform_network_function_close is expected to remove any active - * receive callbacks. - * - * @param[in] pConnection The connection to associate with the receive callback. - * @param[in] receiveCallback The function to invoke for incoming network data. - * @param[in] pContext A value to pass as the first parameter to the receive callback. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @see platform_network_function_receivecallback - */ -/* @[declare_platform_network_setreceivecallback] */ -typedef IotNetworkError_t ( * IotNetworkSetReceiveCallback_t )( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ); -/* @[declare_platform_network_setreceivecallback] */ - -/** - * @brief Register an @ref platform_network_function_closecallback. - * - * Sets an @ref platform_network_function_closecallback to be called - * asynchronously when the network connection closes. The network stack - * should invoke this function "as if" it were the thread routine of a - * detached thread. - * - * Each network connection may only have one close callback at any time. - * @ref platform_network_function_close is expected to remove any active - * close callbacks. - * - * @param[in] pConnection The connection to associate with the close callback. - * @param[in] receiveCallback The function to invoke for incoming network close events. - * @param[in] pContext A value to pass as the first parameter to the close callback. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @see platform_network_function_closecallback - */ -/* @[declare_platform_network_setclosecallback] */ -typedef IotNetworkError_t ( * IotNetworkSetCloseCallback_t )( IotNetworkConnection_t pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ); -/* @[declare_platform_network_setclosecallback] */ - -/** - * @brief Send data over a return connection. - * - * Attempts to transmit `messageLength` bytes of `pMessage` across the - * connection represented by `pConnection`. Returns the number of bytes - * actually sent, `0` on failure. - * - * @param[in] pConnection The connection used to send data, defined by the - * network stack. - * @param[in] pMessage The message to send. - * @param[in] messageLength The length of `pMessage`. - * - * @return The number of bytes successfully sent, `0` on failure. - */ -/* @[declare_platform_network_send] */ -typedef size_t ( * IotNetworkSend_t )( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ); -/* @[declare_platform_network_send] */ - -/** - * @brief Block and wait for incoming network data. - * - * Wait for a message of size `bytesRequested` to arrive on the network and - * place it in `pBuffer`. - * - * @param[in] pConnection The connection to wait on, defined by the network - * stack. - * @param[out] pBuffer Where to place the incoming network data. This buffer - * must be at least `bytesRequested` in size. - * @param[in] bytesRequested How many bytes to wait for. `pBuffer` must be at - * least this size. - * - * @return The number of bytes successfully received. This should be - * `bytesRequested` when successful. Any other value may indicate an error. - */ -/* @[declare_platform_network_receive] */ -typedef size_t ( * IotNetworkReceive_t )( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ); -/* @[declare_platform_network_receive] */ - -/** - * @brief Close a network connection. - * - * This function closes the connection, but does not release the resources - * used by the connection. This allows calls to other networking functions - * to return an error and handle a closed connection without the risk of - * crashing. Once it can be guaranteed that `pConnection` will no longer be - * used, the connection can be destroyed with @ref platform_network_function_destroy. - * - * In addition to closing the connection, this function should also remove - * any active [receive callback](@ref platform_network_function_receivecallback). - * - * @param[in] pConnection The network connection to close, defined by the - * network stack. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @note It must be safe to call this function on an already-closed connection. - */ -/* @[declare_platform_network_close] */ -typedef IotNetworkError_t ( * IotNetworkClose_t )( IotNetworkConnection_t pConnection ); -/* @[declare_platform_network_close] */ - -/** - * @brief Free resources used by a network connection. - * - * This function releases the resources of a closed connection. It should be - * called after @ref platform_network_function_close. - * - * @param[in] pConnection The network connection to destroy, defined by - * the network stack. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @attention No function should be called on the network connection after - * calling this function. This function must be safe to call from a - * [receive callback](@ref platform_network_function_receivecallback). - */ -/* @[declare_platform_network_destroy] */ -typedef IotNetworkError_t ( * IotNetworkDestroy_t )( IotNetworkConnection_t pConnection ); -/* @[declare_platform_network_destroy] */ - -/** - * @ingroup platform_datatypes_paramstructs - * @brief Represents the functions of a network stack. - * - * Functions that match these signatures should be implemented against a system's - * network stack. See the `platform` directory for existing implementations. - */ -typedef struct IotNetworkInterface -{ - IotNetworkCreate_t create; /**< @brief Create network connection. */ - IotNetworkSetReceiveCallback_t setReceiveCallback; /**< @brief Set receive callback. */ - IotNetworkSetCloseCallback_t setCloseCallback; /**< @brief Set close callback. */ - IotNetworkSend_t send; /**< @brief Send data. */ - IotNetworkReceive_t receive; /**< @brief Block and wait for receive data. */ - IotNetworkClose_t close; /**< @brief Close network connection. */ - IotNetworkDestroy_t destroy; /**< @brief Destroy network connection. */ -} IotNetworkInterface_t; - -/** - * @ingroup platform_datatypes_paramstructs - * @brief Information on the remote server for connection setup. - * - * May be passed to #IotNetworkInterface_t.create as `pConnectionInfo`. This - * structure contains commonly-used parameters, but may be replaced with an - * alternative. - */ -struct IotNetworkServerInfo -{ - const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ - uint16_t port; /**< @brief Server port in host-order. */ -}; - -/** - * @ingroup platform_datatypes_paramstructs - * @brief Contains the credentials necessary for connection setup. - * - * May be passed to #IotNetworkInterface_t.create as `pCredentialInfo`. This - * structure contains commonly-used parameters, but may be replaced with an - * alternative. - */ -struct IotNetworkCredentials -{ - /** - * @brief Set this to a non-NULL value to use ALPN. - * - * This string must be NULL-terminated. - * - * See [this link] - * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) - * for more information. - */ - const char * pAlpnProtos; - - /** - * @brief Set this to a non-zero value to use TLS max fragment length - * negotiation (TLS MFLN). - * - * @note The network stack may have a minimum value for this parameter and - * may return an error if this parameter is too small. - */ - size_t maxFragmentLength; - - /** - * @brief Disable server name indication (SNI) for a TLS session. - */ - bool disableSni; - - const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ - size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials.pRootCa. */ - const char * pClientCert; /**< @brief String representing the client certificate. */ - size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ - const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ - size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ - const char * pUserName; /**< @brief String representing the username for MQTT. */ - size_t userNameSize; /**< @brief Size associated with #IotNetworkCredentials.pUserName. */ - const char * pPassword; /**< @brief String representing the password for MQTT. */ - size_t passwordSize; /**< @brief Size associated with #IotNetworkCredentials.pPassword. */ -}; - -#endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/platform/iot_threads.h b/libraries/platform/iot_threads.h deleted file mode 100644 index 72dd04d2d6..0000000000 --- a/libraries/platform/iot_threads.h +++ /dev/null @@ -1,354 +0,0 @@ -/* - * IoT Platform V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_threads.h - * @brief Threading and synchronization functions used by libraries in this SDK. - */ - -#ifndef IOT_THREADS_H_ -#define IOT_THREADS_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Platform layer types include. */ -#include "types/iot_platform_types.h" - -/** - * @functionspage{platform_threads,platform thread management,Thread Management} - * - @functionname{platform_threads_function_createdetachedthread} - * - @functionname{platform_threads_function_mutexcreate} - * - @functionname{platform_threads_function_mutexdestroy} - * - @functionname{platform_threads_function_mutexlock} - * - @functionname{platform_threads_function_mutextrylock} - * - @functionname{platform_threads_function_mutexunlock} - * - @functionname{platform_threads_function_semaphorecreate} - * - @functionname{platform_threads_function_semaphoredestroy} - * - @functionname{platform_threads_function_semaphoregetcount} - * - @functionname{platform_threads_function_semaphorewait} - * - @functionname{platform_threads_function_semaphoretrywait} - * - @functionname{platform_threads_function_semaphoretimedwait} - * - @functionname{platform_threads_function_semaphorepost} - */ - -/** - * @functionpage{Iot_CreateDetachedThread,platform_threads,createdetachedthread} - * @functionpage{IotMutex_Create,platform_threads,mutexcreate} - * @functionpage{IotMutex_Destroy,platform_threads,mutexdestroy} - * @functionpage{IotMutex_Lock,platform_threads,mutexlock} - * @functionpage{IotMutex_TryLock,platform_threads,mutextrylock} - * @functionpage{IotMutex_Unlock,platform_threads,mutexunlock} - * @functionpage{IotSemaphore_Create,platform_threads,semaphorecreate} - * @functionpage{IotSemaphore_Destroy,platform_threads,semaphoredestroy} - * @functionpage{IotSemaphore_GetCount,platform_threads,semaphoregetcount} - * @functionpage{IotSemaphore_Wait,platform_threads,semaphorewait} - * @functionpage{IotSemaphore_TryWait,platform_threads,semaphoretrywait} - * @functionpage{IotSemaphore_TimedWait,platform_threads,semaphoretimedwait} - * @functionpage{IotSemaphore_Post,platform_threads,semaphorepost} - */ - -/** - * @brief Create a new detached thread, i.e. a thread that cleans up after itself. - * - * This function creates a new thread. Threads created by this function exit - * upon returning from the thread routine. Any resources taken must be freed - * by the exiting thread. - * - * @param[in] threadRoutine The function this thread should run. - * @param[in] pArgument The argument passed to `threadRoutine`. - * @param[in] priority Represents the priority of the new thread, as defined by - * the system. The value #IOT_THREAD_DEFAULT_PRIORITY (i.e. `0`) must be used to - * represent the system default for thread priority. #IOT_THREAD_IGNORE_PRIORITY - * should be passed if this parameter is not relevant for the port implementation. - * @param[in] stackSize Represents the stack size of the new thread, as defined - * by the system. The value #IOT_THREAD_DEFAULT_STACK_SIZE (i.e. `0`) must be used - * to represent the system default for stack size. #IOT_THREAD_IGNORE_STACK_SIZE - * should be passed if this parameter is not relevant for the port implementation. - * - * @return `true` if the new thread was successfully created; `false` otherwise. - * - * @code{c} - * // Thread routine. - * void threadRoutine( void * pArgument ); - * - * // Run threadRoutine in a detached thread, using default priority and stack size. - * if( Iot_CreateDetachedThread( threadRoutine, - * NULL, - * IOT_THREAD_DEFAULT_PRIORITY, - * IOT_THREAD_DEFAULT_STACK_SIZE ) == true ) - * { - * // Success - * } - * else - * { - * // Failure, no thread was created. - * } - * @endcode - */ -/* @[declare_platform_threads_createdetachedthread] */ -bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument, - int32_t priority, - size_t stackSize ); -/* @[declare_platform_threads_createdetachedthread] */ - -/** - * @brief Create a new mutex. - * - * This function creates a new, unlocked mutex. It must be called on an uninitialized - * #IotMutex_t. This function must not be called on an already-initialized #IotMutex_t. - * - * @param[in] pNewMutex Pointer to the memory that will hold the new mutex. - * @param[in] recursive Set to `true` to create a recursive mutex, i.e. a mutex that - * may be locked multiple times by the same thread. If the system does not support - * recursive mutexes, this function should do nothing and return `false`. - * - * @return `true` if mutex creation succeeds; `false` otherwise. - * - * @see @ref platform_threads_function_mutexdestroy - * - * Example - * @code{c} - * IotMutex_t mutex; - * - * // Create non-recursive mutex. - * if( IotMutex_Create( &mutex, false ) == true ) - * { - * // Lock and unlock the mutex... - * - * // Destroy the mutex when it's no longer needed. - * IotMutex_Destroy( &mutex ); - * } - * @endcode - */ -/* @[declare_platform_threads_mutexcreate] */ -bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ); -/* @[declare_platform_threads_mutexcreate] */ - -/** - * @brief Free resources used by a mutex. - * - * This function frees resources used by a mutex. It must be called on an initialized - * #IotMutex_t. No other mutex functions should be called on `pMutex` after calling - * this function (unless the mutex is re-created). - * - * @param[in] pMutex The mutex to destroy. - * - * @warning This function must not be called on a locked mutex. - * @see @ref platform_threads_function_mutexcreate - */ -/* @[declare_platform_threads_mutexdestroy] */ -void IotMutex_Destroy( IotMutex_t * pMutex ); -/* @[declare_platform_threads_mutexdestroy] */ - -/** - * @brief Lock a mutex. This function should only return when the mutex is locked; - * it is not expected to fail. - * - * This function blocks and waits until a mutex is available. It waits forever - * (deadlocks) if `pMutex` is already locked and never unlocked. - * - * @param[in] pMutex The mutex to lock. - * - * @see @ref platform_threads_function_mutextrylock for a nonblocking lock. - */ -/* @[declare_platform_threads_mutexlock] */ -void IotMutex_Lock( IotMutex_t * pMutex ); -/* @[declare_platform_threads_mutexlock] */ - -/** - * @brief Attempt to lock a mutex. Return immediately if the mutex is not available. - * - * If `pMutex` is available, this function immediately locks it and returns. - * Otherwise, this function returns without locking `pMutex`. - * - * @param[in] pMutex The mutex to lock. - * - * @return `true` if the mutex was successfully locked; `false` if the mutex was - * not available. - * - * @see @ref platform_threads_function_mutexlock for a blocking lock. - */ -/* @[declare_platform_threads_mutextrylock] */ -bool IotMutex_TryLock( IotMutex_t * pMutex ); -/* @[declare_platform_threads_mutextrylock] */ - -/** - * @brief Unlock a mutex. This function should only return when the mutex is unlocked; - * it is not expected to fail. - * - * Unlocks a locked mutex. `pMutex` must have been locked by the thread calling - * this function. - * - * @param[in] pMutex The mutex to unlock. - * - * @note This function should not be called on a mutex that is already unlocked. - */ -/* @[declare_platform_threads_mutexunlock] */ -void IotMutex_Unlock( IotMutex_t * pMutex ); -/* @[declare_platform_threads_mutexunlock] */ - -/** - * @brief Create a new counting semaphore. - * - * This function creates a new counting semaphore with a given initial and - * maximum value. It must be called on an uninitialized #IotSemaphore_t. - * This function must not be called on an already-initialized #IotSemaphore_t. - * - * @param[in] pNewSemaphore Pointer to the memory that will hold the new semaphore. - * @param[in] initialValue The semaphore should be initialized with this value. - * @param[in] maxValue The maximum value the semaphore will reach. - * - * @return `true` if semaphore creation succeeds; `false` otherwise. - * - * @see @ref platform_threads_function_semaphoredestroy - * - * Example - * @code{c} - * IotSemaphore_t sem; - * - * // Create a locked binary semaphore. - * if( IotSemaphore_Create( &sem, 0, 1 ) == true ) - * { - * // Unlock the semaphore. - * IotSemaphore_Post( &sem ); - * - * // Destroy the semaphore when it's no longer needed. - * IotSemaphore_Destroy( &sem ); - * } - * @endcode - */ -/* @[declare_platform_threads_semaphorecreate] */ -bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ); -/* @[declare_platform_threads_semaphorecreate] */ - -/** - * @brief Free resources used by a semaphore. - * - * This function frees resources used by a semaphore. It must be called on an initialized - * #IotSemaphore_t. No other semaphore functions should be called on `pSemaphore` after - * calling this function (unless the semaphore is re-created). - * - * @param[in] pSemaphore The semaphore to destroy. - * - * @warning This function must not be called on a semaphore with waiting threads. - * @see @ref platform_threads_function_semaphorecreate - */ -/* @[declare_platform_threads_semaphoredestroy] */ -void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ); -/* @[declare_platform_threads_semaphoredestroy] */ - -/** - * @brief Query the current count of the semaphore. - * - * This function queries a counting semaphore for its current value. A counting - * semaphore's value is always 0 or positive. - * - * @param[in] pSemaphore The semaphore to query. - * - * @return The current count of the semaphore. This function should not fail. - */ -/* @[declare_platform_threads_semaphoregetcount] */ -uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ); -/* @[declare_platform_threads_semaphoregetcount] */ - -/** - * @brief Wait on (lock) a semaphore. This function should only return when the - * semaphore wait succeeds; it is not expected to fail. - * - * This function blocks and waits until a counting semaphore is positive. It - * waits forever (deadlocks) if `pSemaphore` has a count `0` that is never - * [incremented](@ref platform_threads_function_semaphorepost). - * - * @param[in] pSemaphore The semaphore to lock. - * - * @see @ref platform_threads_function_semaphoretrywait for a nonblocking wait; - * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. - */ -/* @[declare_platform_threads_semaphorewait] */ -void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ); -/* @[declare_platform_threads_semaphorewait] */ - -/** - * @brief Attempt to wait on (lock) a semaphore. Return immediately if the semaphore - * is not available. - * - * If the count of `pSemaphore` is positive, this function immediately decrements - * the semaphore and returns. Otherwise, this function returns without decrementing - * `pSemaphore`. - * - * @param[in] pSemaphore The semaphore to lock. - * - * @return `true` if the semaphore wait succeeded; `false` if the semaphore has - * a count of `0`. - * - * @see @ref platform_threads_function_semaphorewait for a blocking wait; - * @ref platform_threads_function_semaphoretimedwait for a wait with timeout. - */ -/* @[declare_platform_threads_semaphoretrywait] */ -bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ); -/* @[declare_platform_threads_semaphoretrywait] */ - -/** - * @brief Attempt to wait on (lock) a semaphore with a timeout. - * - * This function blocks and waits until a counting semaphore is positive - * or its timeout expires (whichever is sooner). It decrements - * `pSemaphore` and returns `true` if the semaphore is positive at some - * time during the wait. If `pSemaphore` is always `0` during the wait, - * this function returns `false`. - * - * @param[in] pSemaphore The semaphore to lock. - * @param[in] timeoutMs Relative timeout of semaphore lock. This function returns - * false if the semaphore couldn't be locked within this timeout. - * - * @return `true` if the semaphore wait succeeded; `false` if it timed out. - * - * @see @ref platform_threads_function_semaphoretrywait for a nonblocking wait; - * @ref platform_threads_function_semaphorewait for a blocking wait. - */ -/* @[declare_platform_threads_semaphoretimedwait] */ -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ); -/* @[declare_platform_threads_semaphoretimedwait] */ - -/** - * @brief Post to (unlock) a semaphore. This function should only return when the - * semaphore post succeeds; it is not expected to fail. - * - * This function increments the count of a semaphore. Any thread may call this - * function to increment a semaphore's count. - * - * @param[in] pSemaphore The semaphore to unlock. - */ -/* @[declare_platform_threads_semaphorepost] */ -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ); -/* @[declare_platform_threads_semaphorepost] */ - -#endif /* ifndef IOT_THREADS_H_ */ diff --git a/libraries/platform/lexicon.txt b/libraries/platform/lexicon.txt deleted file mode 100644 index 309feb0a90..0000000000 --- a/libraries/platform/lexicon.txt +++ /dev/null @@ -1,64 +0,0 @@ -addresslength -api -aws -clientcertsize -closecallback -com -config -const -createdetachedthread -endif -gettcpconnections -gettimems -gettimestring -https -ifndef -init -iot -iotlink -iotmetricstcpconnection -iotnetworkclose -iotnetworkcreate -iotnetworkcredentials -iotnetworkdestroy -iotnetworkreceive -iotnetworksend -iotnetworksetclosecallback -iotnetworksetreceivecallback -mqtt -mutex -mutexcreate -mutexdestroy -mutexlock -mutextrylock -mutexunlock -passwordsize -pclientcert -phostname -pnetworkcontext -ppassword -pprivatekey -premoteaddress -privatekeysize -prootca -pusername -receivecallback -rootcasize -semaphorecreate -semaphoredestroy -semaphoregetcount -semaphorepost -semaphoretimedwait -semaphoretrywait -semaphorewait -setclosecallback -setreceivecallback -sleepms -threadroutine -timerarm -timercreate -timerdestroy -tls -uint -username -usernamesize diff --git a/libraries/platform/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h deleted file mode 100644 index 31d36bed27..0000000000 --- a/libraries/platform/types/iot_platform_types.h +++ /dev/null @@ -1,202 +0,0 @@ -/* - * IoT Platform V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_platform_types.h - * @brief Types of the platform layer. - */ - -#ifndef IOT_PLATFORM_TYPES_H_ -#define IOT_PLATFORM_TYPES_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Linear containers (lists and queues) include for metrics types. */ -#include "iot_linear_containers.h" - -/*------------------------- Thread management types -------------------------*/ - -/** - * @brief Placeholder value that should cause implementations of - * @ref platform_threads_function_createdetachedthread to ignore the priority argument. - */ -#define IOT_THREAD_IGNORE_PRIORITY ( -1 ) - -/** - * @brief Placeholder value that should cause implementations of - * @ref platform_threads_function_createdetachedthread to ignore the stack size argument. - */ -#define IOT_THREAD_IGNORE_STACK_SIZE ( 0 ) - -/** - * @brief A value representing the system default for new thread priority. - */ -#ifndef IOT_THREAD_DEFAULT_PRIORITY - #define IOT_THREAD_DEFAULT_PRIORITY IOT_THREAD_IGNORE_PRIORITY -#endif - -/** - * @brief A value representhing the system default for new thread stack size. - */ -#ifndef IOT_THREAD_DEFAULT_STACK_SIZE - #define IOT_THREAD_DEFAULT_STACK_SIZE IOT_THREAD_IGNORE_STACK_SIZE -#endif - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent mutexes, configured with the type - * `_IotSystemMutex_t`. - * - * For the provided ports, `_IotSystemMutex_t` will be automatically configured. - * For other ports, `_IotSystemMutex_t` should be set in `iot_config.h`. - * - * Mutexes should only be released by the threads that take them. - * - * Example
- * To change the type of #IotMutex_t to `long`: - * @code{c} - * typedef long _IotSystemMutex_t; - * #include "iot_threads.h" - * @endcode - */ -typedef _IotSystemMutex_t IotMutex_t; - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent semaphores, configured with the type - * `_IotSystemSemaphore_t`. - * - * For the provided ports, `_IotSystemSemaphore_t` will be automatically configured. - * For other ports, `_IotSystemSemaphore_t` should be set in `iot_config.h`. - * - * Semaphores must be counting, and any thread may take (wait on) or release - * (post to) a semaphore. - * - * Example
- * To change the type of #IotSemaphore_t to `long`: - * @code{c} - * typedef long _IotSystemSemaphore_t; - * #include "iot_threads.h" - * @endcode - */ -typedef _IotSystemSemaphore_t IotSemaphore_t; - -/** - * @brief Thread routine function. - * - * @param[in] pArgument The argument passed to the @ref - * platform_threads_function_createdetachedthread. For application use. - */ -typedef void ( * IotThreadRoutine_t )( void * pArgument ); - -/*-------------------------- Clock and timer types --------------------------*/ - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent timers, configured with the type - * `_IotSystemTimer_t`. - * - * For the provided ports, `_IotSystemTimer_t` will be automatically configured. - * For other ports, `_IotSystemTimer_t` should be set in `iot_config.h`. - * - * Example
- * To change the type of #IotTimer_t to `long`: - * @code{c} - * typedef long _IotSystemTimer_t; - * #include "iot_clock.h" - * @endcode - */ -typedef _IotSystemTimer_t IotTimer_t; - -/*--------------------------- Network stack types ---------------------------*/ - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent network server info, configured with the - * type `_IotNetworkServerInfo_t`. - * - * For the provided ports, `_IotNetworkServerInfo_t` will be automatically configured. - * For other ports, `_IotNetworkServerInfo_t` should be set in `iot_config.h`. - * - * All of the provided ports configure this to #IotNetworkServerInfo, which provides - * the necessary information to connect to a TCP peer. For other network protocols, - * this type should be set to an alternate structure as needed by the other protocol. - */ -typedef _IotNetworkServerInfo_t IotNetworkServerInfo_t; - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent network credentials, configured with the - * type `_IotNetworkCredentials_t`. - * - * For the provided ports, `_IotNetworkCredentials_t` will be automatically configured. - * For other ports, `_IotNetworkCredentials_t` should be set in `iot_config.h`. - * - * All of the provided ports configure this to #IotNetworkCredentials, which provides - * the necessary information to connect to a TLS server over TCP. For other network - * protocols, this type should be set to an alternate structure as needed by the other - * protocol. - */ -typedef _IotNetworkCredentials_t IotNetworkCredentials_t; - -/** - * @ingroup platform_datatypes_handles - * @brief The type used to represent network connections, configured with the - * type `_IotNetworkConnection_t`. - * - * For the provided ports, `_IotNetworkConnection_t` will be automatically configured. - * For other ports, `_IotNetworkConnection_t` should be set in `iot_config.h`. - */ -typedef _IotNetworkConnection_t IotNetworkConnection_t; - -/*------------------------------ Metrics types ------------------------------*/ - -/** - * @brief The length of the buffer used to store IP addresses for metrics. - * - * This is the length of the longest IPv6 address plus space for the port number - * and NULL terminator. - */ -#define IOT_METRICS_IP_ADDRESS_LENGTH 54 - -/** - * @brief Represents a TCP connection to a remote IPv4 server. - * - * A list of these is provided by @ref platform_metrics_function_gettcpconnections. - */ -typedef struct IotMetricsTcpConnection -{ - IotLink_t link; /**< @brief List link member. */ - void * pNetworkContext; /**< @brief Context that may be used by metrics or Defender. */ - size_t addressLength; /**< @brief The length of the address stored in #IotMetricsTcpConnection_t.pRemoteAddress. */ - - /** - * @brief NULL-terminated IP address and port in text format. - * - * IPv4 addresses will be in the format `xxx.xxx.xxx.xxx:port`. - * IPv6 addresses will be in the format `[xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:port`. - */ - char pRemoteAddress[ IOT_METRICS_IP_ADDRESS_LENGTH ]; -} IotMetricsTcpConnection_t; - -#endif /* ifndef IOT_PLATFORM_TYPES_H_ */ diff --git a/libraries/standard/common/CMakeLists.txt b/libraries/standard/common/CMakeLists.txt deleted file mode 100644 index 8883d6052f..0000000000 --- a/libraries/standard/common/CMakeLists.txt +++ /dev/null @@ -1,128 +0,0 @@ -# Common libraries source files. -set( COMMON_SOURCES - src/iot_init.c - src/iot_logging.c - src/iot_static_memory_common.c - src/iot_taskpool.c - src/iot_taskpool_static_memory.c ) - -# Lists of common header files. These are only used for directory organization. -set( COMMON_PUBLIC_HEADERS - include/iot_error.h - include/iot_init.h - include/iot_linear_containers.h - include/iot_logging.h - include/iot_logging_setup.h - include/iot_static_memory.h - include/iot_taskpool.h ) -set( COMMON_PRIVATE_HEADERS - src/private/iot_taskpool_internal.h ) -set( COMMON_TYPES_HEADERS - include/types/iot_taskpool_types.h ) - -# List of the platform public headers. -set( PLATFORM_INTERFACE_DIRECTORY ${PROJECT_SOURCE_DIR}/libraries/platform ) -set( PORTS_DIRECTORY ${PROJECT_SOURCE_DIR}/ports ) - -set( PLATFORM_INTERFACE_HEADERS - ${PLATFORM_INTERFACE_DIRECTORY}/iot_clock.h - ${PLATFORM_INTERFACE_DIRECTORY}/iot_metrics.h - ${PLATFORM_INTERFACE_DIRECTORY}/iot_network.h - ${PLATFORM_INTERFACE_DIRECTORY}/iot_threads.h ) -set( PLATFORM_TYPES_HEADER - ${PLATFORM_INTERFACE_DIRECTORY}/types/iot_platform_types.h ) -set( PLATFORM_COMMON_HEADERS - ${PORTS_DIRECTORY}/common/include/iot_atomic.h - ${PORTS_DIRECTORY}/common/include/iot_network_metrics.h ) - -# Include the CMake file for this port. -include( ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME}/${IOT_PLATFORM_NAME}.cmake ) - -# The port CMake file must specify the types header and the platform sources. -if( NOT DEFINED PORT_TYPES_HEADER ) - message( FATAL_ERROR "Port CMake file did not set PORT_TYPES_HEADER." ) -endif() - -if( NOT DEFINED PLATFORM_SOURCES ) - message( FATAL_ERROR "Port CMake file did not set PLATFORM_SOURCES." ) -endif() - -# iotbase library target. This is a combination of the platform port and the -# common libraries. -add_library( iotbase - ${CONFIG_HEADER_PATH}/iot_config.h - ${COMMON_SOURCES} - ${COMMON_PUBLIC_HEADERS} - ${COMMON_PRIVATE_HEADERS} - ${COMMON_TYPES_HEADERS} - ${PLATFORM_INTERFACE_HEADERS} - ${PLATFORM_TYPES_HEADER} - ${PLATFORM_COMMON_HEADERS} - ${PLATFORM_SOURCES} - ${PORT_TYPES_HEADER} ) - -# Set the types header for the port. -target_compile_definitions( iotbase PUBLIC -DIOT_SYSTEM_TYPES_FILE="${PORT_TYPES_HEADER}" ) - -# Specify if OpenSSL is being used in a compiler define. -if( ${IOT_NETWORK_USE_OPENSSL} ) - target_compile_definitions( iotbase PUBLIC -DIOT_NETWORK_USE_OPENSSL=1 ) -endif() - -# Set the include directories for the common and platform libraries. -target_include_directories( iotbase - PUBLIC - ${CONFIG_HEADER_PATH} - ${PLATFORM_INTERFACE_DIRECTORY}/.. - ${PLATFORM_INTERFACE_DIRECTORY} - ${PORTS_DIRECTORY}/common/include - include/ ) - -# Link any dependencies required by this port. -target_link_libraries( iotbase PRIVATE ${PLATFORM_DEPENDENCIES} ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotbase PRIVATE unity ) -endif() - -# Organization of iotbase in folders. -set_property( TARGET iotbase PROPERTY FOLDER libraries/standard ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( common\\include FILES ${COMMON_PUBLIC_HEADERS} ) -source_group( common\\include\\types FILES ${COMMON_TYPES_HEADERS} ) -source_group( common\\src FILES ${COMMON_SOURCES} ) -source_group( common\\src\\private FILES ${COMMON_PRIVATE_HEADERS} ) -source_group( platform\\include FILES ${PLATFORM_INTERFACE_HEADERS} ${PLATFORM_COMMON_HEADERS} ) -source_group( platform\\include\\types FILES ${PLATFORM_TYPES_HEADER} ${PORT_TYPES_HEADER} ) -source_group( platform\\src FILES ${PLATFORM_SOURCES} ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # Common unit test sources. - set( COMMON_UNIT_TEST_SOURCES - test/unit/iot_tests_linear_containers.c - test/unit/iot_tests_taskpool.c - test/unit/iot_tests_atomic.c ) - - # Common tests executable. - add_executable( iot_tests_common - ${COMMON_UNIT_TEST_SOURCES} - test/iot_tests_common.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( iot_tests_common PRIVATE - -DRunTests=RunCommonTests ) - - # The tests use internal headers. Add the include path for internal headers. - target_include_directories( iot_tests_common PRIVATE src ) - - # Common tests library dependencies. - target_link_libraries( iot_tests_common PRIVATE iotbase unity ) - - # Organization of common tests in folders. - set_property( TARGET iot_tests_common PROPERTY FOLDER tests ) - source_group( unit FILES ${COMMON_UNIT_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/iot_tests_common.c ) -endif() diff --git a/libraries/standard/common/include/iot_error.h b/libraries/standard/common/include/iot_error.h deleted file mode 100644 index 3b8dda2b8b..0000000000 --- a/libraries/standard/common/include/iot_error.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_error.h - * @brief Provides macros for error checking and function cleanup. - * - * The macros in this file are generic. They may be customized by each library - * by setting the library prefix. - */ - -#ifndef IOT_ERROR_H_ -#define IOT_ERROR_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/** - * @brief Declare the status variable and an initial value. - * - * This macro should be at the beginning of any functions that use cleanup sections. - * - * @param[in] statusType The type of the status variable for this function. - * @param[in] initialValue The initial value to assign to the status variable. - */ -#define IOT_FUNCTION_ENTRY( statusType, initialValue ) statusType status = initialValue - -/** - * @brief Declares the label that begins a cleanup section. - * - * This macro should be placed at the end of a function and followed by - * #IOT_FUNCTION_CLEANUP_END. - */ -#define IOT_FUNCTION_CLEANUP_BEGIN() iotCleanup: - -/** - * @brief Declares the end of a cleanup section. - * - * This macro should be placed at the end of a function and preceded by - * #IOT_FUNCTION_CLEANUP_BEGIN. - */ -#define IOT_FUNCTION_CLEANUP_END() return status - -/** - * @brief Declares an empty cleanup section. - * - * This macro should be placed at the end of a function to exit on error if no - * cleanup is required. - */ -#define IOT_FUNCTION_EXIT_NO_CLEANUP() IOT_FUNCTION_CLEANUP_BEGIN(); IOT_FUNCTION_CLEANUP_END() - -/** - * @brief Jump to the cleanup section. - */ -#define IOT_GOTO_CLEANUP() goto iotCleanup - -/** - * @brief Assign a value to the status variable and jump to the cleanup section. - * - * @param[in] statusValue The value to assign to the status variable. - */ -#define IOT_SET_AND_GOTO_CLEANUP( statusValue ) { status = ( statusValue ); IOT_GOTO_CLEANUP(); } - -/** - * @brief Jump to the cleanup section if a condition is `false`. - * - * This macro may be used in place of `assert` to exit a function is a condition - * is `false`. - * - * @param[in] condition The condition to check. - */ -#define IOT_GOTO_CLEANUP_IF_FALSE( condition ) { if( ( condition ) == false ) { IOT_GOTO_CLEANUP(); } } - -/** - * @brief Assign a value to the status variable and jump to the cleanup section - * if a condition is `false`. - * - * @param[in] statusValue The value to assign to the status variable. - * @param[in] condition The condition to check. - */ -#define IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( statusValue, condition ) \ - if( ( condition ) == false ) \ - IOT_SET_AND_GOTO_CLEANUP( statusValue ) - -/** - * @brief Check a condition; if `false`, assign the "Bad parameter" status value - * and jump to the cleanup section. - * - * @param[in] libraryPrefix The library prefix of the status variable. - * @param[in] condition The condition to check. - */ -#define IOT_VALIDATE_PARAMETER( libraryPrefix, condition ) \ - IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( libraryPrefix ## _BAD_PARAMETER, condition ) - -#endif /* ifndef IOT_ERROR_H_ */ diff --git a/libraries/standard/common/include/iot_init.h b/libraries/standard/common/include/iot_init.h deleted file mode 100644 index 1c38c6ab78..0000000000 --- a/libraries/standard/common/include/iot_init.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_init.h - * @brief Provides function signatures for common initialization and cleanup of - * this SDK. - */ - -#ifndef IOT_INIT_H_ -#define IOT_INIT_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/** - * @brief One-time initialization function for this SDK. - * - * This function initializes common libraries, such as static memory and task - * pool. It must be called once (and only once) before calling any other - * function in this SDK. Calling this function more than once without first - * calling `IotSdk_Cleanup` may result in a crash. - * - * @return `true` if initialization succeeded; `false` otherwise. Logs may be - * printed in case of failure. - * - * @warning No thread-safety guarantees are provided for this function. - */ -bool IotSdk_Init( void ); - -/** - * @brief One-time deinitialization function for all common libraries. - * - * This function frees resources taken in `IotSdk_Init`. No other function - * in this SDK may be called after calling this function unless `IotSdk_Init` - * is called again. - * - * @warning No thread-safety guarantees are provided for this function. - */ -void IotSdk_Cleanup( void ); - -#endif /* IOT_INIT_H_ */ diff --git a/libraries/standard/common/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h deleted file mode 100644 index 689ea5e9d0..0000000000 --- a/libraries/standard/common/include/iot_linear_containers.h +++ /dev/null @@ -1,958 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_linear_containers.h - * @brief Declares and implements doubly-linked lists and queues. - */ - -#ifndef IOT_LINEAR_CONTAINERS_H_ -#define IOT_LINEAR_CONTAINERS_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/** - * @defgroup linear_containers_datatypes_listqueue List and queue - * @brief Structures that represent a list or queue. - */ - -/** - * @ingroup linear_containers_datatypes_listqueue - * @brief Link member placed in structs of a list or queue. - * - * All elements in a list or queue must contain one of these members. The macro - * #IotLink_Container can be used to calculate the starting address of the - * link's container. - */ -typedef struct IotLink -{ - struct IotLink * pPrevious; /**< @brief Pointer to the previous element. */ - struct IotLink * pNext; /**< @brief Pointer to the next element. */ -} IotLink_t; - -/** - * @ingroup linear_containers_datatypes_listqueue - * @brief Represents a doubly-linked list. - */ -typedef IotLink_t IotListDouble_t; - -/** - * @ingroup linear_containers_datatypes_listqueue - * @brief Represents a queue. - */ -typedef IotLink_t IotDeQueue_t; - -/** - * @constantspage{linear_containers,linear containers library} - * - * @section linear_containers_constants_initializers Linear Containers Initializers - * @brief Provides default values for initializing the linear containers data types. - * - * @snippet this define_linear_containers_initializers - * - * All user-facing data types of the linear containers library should be initialized - * using one of the following. - * - * @warning Failure to initialize a linear containers data type with the appropriate - * initializer may result in a runtime error! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - */ -/* @[define_linear_containers_initializers] */ -#define IOT_LINK_INITIALIZER { 0 } /**< @brief Initializer for an #IotLink_t. */ -#define IOT_LIST_DOUBLE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotListDouble_t. */ -#define IOT_DEQUEUE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotDeQueue_t. */ -/* @[define_linear_containers_initializers] */ - -/** - * @def IotContainers_Assert( expression ) - * @brief Assertion macro for the linear containers library. - * - * Set @ref IOT_CONTAINERS_ENABLE_ASSERTS to `1` to enable assertions in the linear - * containers library. - * - * @param[in] expression Expression to be evaluated. - */ -#if IOT_CONTAINERS_ENABLE_ASSERTS == 1 - #ifndef IotContainers_Assert - #ifdef Iot_DefaultAssert - #define IotContainers_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for containers, but IotContainers_Assert is not defined" - #endif - #endif -#else /* if IOT_CONTAINERS_ENABLE_ASSERTS == 1 */ - #define IotContainers_Assert( expression ) -#endif /* if IOT_CONTAINERS_ENABLE_ASSERTS == 1 */ - -/** - * @brief Calculates the starting address of a containing struct. - * - * @param[in] type Type of the containing struct. - * @param[in] pLink Pointer to a link member. - * @param[in] linkName Name of the #IotLink_t in the containing struct. - */ -#define IotLink_Container( type, pLink, linkName ) \ - ( ( type * ) ( void * ) ( ( ( uint8_t * ) ( pLink ) ) - offsetof( type, linkName ) ) ) - -/** - * @brief Iterates through all elements of a linear container. - * - * Container elements must not be freed or removed while iterating. - * - * @param[in] pStart The first element to iterate from. - * @param[out] pLink Pointer to a container element. - */ -#define IotContainers_ForEach( pStart, pLink ) \ - for( ( pLink ) = ( pStart )->pNext; \ - ( pLink ) != ( pStart ); \ - ( pLink ) = ( pLink )->pNext ) - -/** - * @functionspage{linear_containers,linear containers library} - * - @functionname{linear_containers_function_link_islinked} - * - @functionname{linear_containers_function_list_double_create} - * - @functionname{linear_containers_function_list_double_count} - * - @functionname{linear_containers_function_list_double_isempty} - * - @functionname{linear_containers_function_list_double_peekhead} - * - @functionname{linear_containers_function_list_double_peektail} - * - @functionname{linear_containers_function_list_double_inserthead} - * - @functionname{linear_containers_function_list_double_inserttail} - * - @functionname{linear_containers_function_list_double_insertbefore} - * - @functionname{linear_containers_function_list_double_insertafter} - * - @functionname{linear_containers_function_list_double_insertsorted} - * - @functionname{linear_containers_function_list_double_remove} - * - @functionname{linear_containers_function_list_double_removehead} - * - @functionname{linear_containers_function_list_double_removetail} - * - @functionname{linear_containers_function_list_double_removeall} - * - @functionname{linear_containers_function_list_double_findfirstmatch} - * - @functionname{linear_containers_function_list_double_removefirstmatch} - * - @functionname{linear_containers_function_list_double_removeallmatches} - * - @functionname{linear_containers_function_queue_create} - * - @functionname{linear_containers_function_queue_count} - * - @functionname{linear_containers_function_queue_isempty} - * - @functionname{linear_containers_function_queue_peekhead} - * - @functionname{linear_containers_function_queue_peektail} - * - @functionname{linear_containers_function_queue_enqueuehead} - * - @functionname{linear_containers_function_queue_dequeuehead} - * - @functionname{linear_containers_function_queue_enqueuetail} - * - @functionname{linear_containers_function_queue_dequeuetail} - * - @functionname{linear_containers_function_queue_remove} - * - @functionname{linear_containers_function_queue_removeall} - * - @functionname{linear_containers_function_queue_removeallmatches} - */ - -/** - * @functionpage{IotLink_IsLinked,linear_containers,link_islinked} - * @functionpage{IotListDouble_Create,linear_containers,list_double_create} - * @functionpage{IotListDouble_Count,linear_containers,list_double_count} - * @functionpage{IotListDouble_IsEmpty,linear_containers,list_double_isempty} - * @functionpage{IotListDouble_PeekHead,linear_containers,list_double_peekhead} - * @functionpage{IotListDouble_PeekTail,linear_containers,list_double_peektail} - * @functionpage{IotListDouble_InsertHead,linear_containers,list_double_inserthead} - * @functionpage{IotListDouble_InsertTail,linear_containers,list_double_inserttail} - * @functionpage{IotListDouble_InsertBefore,linear_containers,list_double_insertbefore} - * @functionpage{IotListDouble_InsertAfter,linear_containers,list_double_insertafter} - * @functionpage{IotListDouble_InsertSorted,linear_containers,list_double_insertsorted} - * @functionpage{IotListDouble_Remove,linear_containers,list_double_remove} - * @functionpage{IotListDouble_RemoveHead,linear_containers,list_double_removehead} - * @functionpage{IotListDouble_RemoveTail,linear_containers,list_double_removetail} - * @functionpage{IotListDouble_RemoveAll,linear_containers,list_double_removeall} - * @functionpage{IotListDouble_FindFirstMatch,linear_containers,list_double_findfirstmatch} - * @functionpage{IotListDouble_RemoveFirstMatch,linear_containers,list_double_removefirstmatch} - * @functionpage{IotListDouble_RemoveAllMatches,linear_containers,list_double_removeallmatches} - * @functionpage{IotDeQueue_Create,linear_containers,queue_create} - * @functionpage{IotDeQueue_Count,linear_containers,queue_count} - * @functionpage{IotDeQueue_IsEmpty,linear_containers,queue_isempty} - * @functionpage{IotDeQueue_PeekHead,linear_containers,queue_peekhead} - * @functionpage{IotDeQueue_PeekTail,linear_containers,queue_peektail} - * @functionpage{IotDeQueue_EnqueueHead,linear_containers,queue_enqueuehead} - * @functionpage{IotDeQueue_DequeueHead,linear_containers,queue_dequeuehead} - * @functionpage{IotDeQueue_EnqueueTail,linear_containers,queue_enqueuetail} - * @functionpage{IotDeQueue_DequeueTail,linear_containers,queue_dequeuetail} - * @functionpage{IotDeQueue_Remove,linear_containers,queue_remove} - * @functionpage{IotDeQueue_RemoveAll,linear_containers,queue_removeall} - * @functionpage{IotDeQueue_RemoveAllMatches,linear_containers,queue_removeallmatches} - */ - -/** - * @brief Check if an #IotLink_t is linked in a list or queue. - * - * @param[in] pLink The link to check. - * - * @return `true` if `pCurrent` is linked in a list or queue; `false` otherwise. - */ -/* @[declare_linear_containers_link_islinked] */ -static inline bool IotLink_IsLinked( const IotLink_t * const pLink ) -/* @[declare_linear_containers_link_islinked] */ -{ - bool isLinked = false; - - if( pLink != NULL ) - { - isLinked = ( pLink->pNext != NULL ) && ( pLink->pPrevious != NULL ); - } - - return isLinked; -} - -/** - * @brief Create a new doubly-linked list. - * - * This function initializes a new doubly-linked list. It must be called on an - * uninitialized #IotListDouble_t before calling any other doubly-linked list - * function. This function must not be called on an already-initialized - * #IotListDouble_t. - * - * This function will not fail. The function @ref linear_containers_function_list_double_removeall - * may be called to destroy a list. - * - * @param[in] pList Pointer to the memory that will hold the new doubly-linked list. - */ -/* @[declare_linear_containers_list_double_create] */ -static inline void IotListDouble_Create( IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_create] */ -{ - /* This function must not be called with a NULL parameter. */ - IotContainers_Assert( pList != NULL ); - - /* An empty list is a link pointing to itself. */ - pList->pPrevious = pList; - pList->pNext = pList; -} - -/** - * @brief Return the number of elements contained in an #IotListDouble_t. - * - * @param[in] pList The doubly-linked list with the elements to count. - * - * @return The number of elements in the doubly-linked list. - */ -/* @[declare_linear_containers_list_double_count] */ -static inline size_t IotListDouble_Count( const IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_count] */ -{ - size_t count = 0; - - if( pList != NULL ) - { - /* Get the list head. */ - const IotLink_t * pCurrent = pList->pNext; - - /* Iterate through the list to count the elements. */ - while( pCurrent != pList ) - { - count++; - pCurrent = pCurrent->pNext; - } - } - - return count; -} - -/** - * @brief Check if a doubly-linked list is empty. - * - * @param[in] pList The doubly-linked list to check. - * - * @return `true` if the list is empty; `false` otherwise. - */ -/* @[declare_linear_containers_list_double_isempty] */ -static inline bool IotListDouble_IsEmpty( const IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_isempty] */ -{ - /* An empty list is NULL link, or a link pointing to itself. */ - return( ( pList == NULL ) || ( pList->pNext == pList ) ); -} - -/** - * @brief Return an #IotLink_t representing the first element in a doubly-linked list - * without removing it. - * - * @param[in] pList The list to peek. - * - * @return Pointer to an #IotLink_t representing the element at the head of the - * list; `NULL` if the list is empty. The macro #IotLink_Container may be used to - * determine the address of the link's container. - */ -/* @[declare_linear_containers_list_double_peekhead] */ -static inline IotLink_t * IotListDouble_PeekHead( const IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_peekhead] */ -{ - IotLink_t * pHead = NULL; - - if( pList != NULL ) - { - if( IotListDouble_IsEmpty( pList ) == false ) - { - pHead = pList->pNext; - } - } - - return pHead; -} - -/** - * @brief Return an #IotLink_t representing the last element in a doubly-linked - * list without removing it. - * - * @param[in] pList The list to peek. - * - * @return Pointer to an #IotLink_t representing the element at the tail of the - * list; `NULL` if the list is empty. The macro #IotLink_Container may be used to - * determine the address of the link's container. - */ -/* @[declare_linear_containers_list_double_peektail] */ -static inline IotLink_t * IotListDouble_PeekTail( const IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_peektail] */ -{ - IotLink_t * pTail = NULL; - - if( pList != NULL ) - { - if( IotListDouble_IsEmpty( pList ) == false ) - { - pTail = pList->pPrevious; - } - } - - return pTail; -} - -/** - * @brief Insert an element at the head of a doubly-linked list. - * - * @param[in] pList The doubly-linked list that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - */ -/* @[declare_linear_containers_list_double_inserthead] */ -static inline void IotListDouble_InsertHead( IotListDouble_t * const pList, - IotLink_t * const pLink ) -/* @[declare_linear_containers_list_double_inserthead] */ -{ - /* This function must not be called with NULL parameters. */ - IotContainers_Assert( pList != NULL ); - IotContainers_Assert( pLink != NULL ); - - /* Save current list head. */ - IotLink_t * pHead = pList->pNext; - - /* Place new element before list head. */ - pLink->pNext = pHead; - pLink->pPrevious = pList; - - /* Assign new list head. */ - pHead->pPrevious = pLink; - pList->pNext = pLink; -} - -/** - * @brief Insert an element at the tail of a doubly-linked list. - * - * @param[in] pList The double-linked list that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - */ -/* @[declare_linear_containers_list_double_inserttail] */ -static inline void IotListDouble_InsertTail( IotListDouble_t * const pList, - IotLink_t * const pLink ) -/* @[declare_linear_containers_list_double_inserttail] */ -{ - /* This function must not be called with NULL parameters. */ - IotContainers_Assert( pList != NULL ); - IotContainers_Assert( pLink != NULL ); - - /* Save current list tail. */ - IotLink_t * pTail = pList->pPrevious; - - pLink->pNext = pList; - pLink->pPrevious = pTail; - - pList->pPrevious = pLink; - pTail->pNext = pLink; -} - -/** - * @brief Insert an element before another element in a doubly-linked list. - * - * @param[in] pElement The new element will be placed before this element. - * @param[in] pLink Pointer to the new element's link member. - */ -/* @[declare_linear_containers_list_double_insertbefore] */ -static inline void IotListDouble_InsertBefore( IotLink_t * const pElement, - IotLink_t * const pLink ) -/* @[declare_linear_containers_list_double_insertbefore] */ -{ - IotListDouble_InsertTail( pElement, pLink ); -} - -/** - * @brief Insert an element after another element in a doubly-linked list. - * - * @param[in] pElement The new element will be placed after this element. - * @param[in] pLink Pointer to the new element's link member. - */ -/* @[declare_linear_containers_list_double_insertafter] */ -static inline void IotListDouble_InsertAfter( IotLink_t * const pElement, - IotLink_t * const pLink ) -/* @[declare_linear_containers_list_double_insertafter] */ -{ - IotListDouble_InsertHead( pElement, pLink ); -} - -/** - * @brief Insert an element in a sorted doubly-linked list. - * - * Places an element into a list by sorting it into order. The function - * `compare` is used to determine where to place the new element. - * - * @param[in] pList The list that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - * @param[in] compare Determines the order of the list. Returns a negative - * value if its first argument is less than its second argument; returns - * zero if its first argument is equal to its second argument; returns a - * positive value if its first argument is greater than its second argument. - * The parameters to this function are #IotLink_t, so the macro #IotLink_Container - * may be used to determine the address of the link's container. - */ -/* @[declare_linear_containers_list_double_insertsorted] */ -static inline void IotListDouble_InsertSorted( IotListDouble_t * const pList, - IotLink_t * const pLink, - int32_t ( *compare )( const IotLink_t * const pParam1, const IotLink_t * const pParam2 ) ) -/* @[declare_linear_containers_list_double_insertsorted] */ -{ - /* This function must not be called with NULL parameters. */ - IotContainers_Assert( pList != NULL ); - IotContainers_Assert( pLink != NULL ); - IotContainers_Assert( compare != NULL ); - - /* Insert at head for empty list. */ - if( IotListDouble_IsEmpty( pList ) == true ) - { - IotListDouble_InsertHead( pList, pLink ); - } - else - { - bool inserted = false; - IotLink_t * pCurrent = pList->pNext; - - /* Iterate through the list to find the correct position. */ - while( pCurrent != pList ) - { - /* Comparing for '<' preserves the order of insertion. */ - if( compare( pLink, pCurrent ) < 0 ) - { - IotListDouble_InsertBefore( pCurrent, pLink ); - inserted = true; - - break; - } - - pCurrent = pCurrent->pNext; - } - - /* New element is greater than all elements in list. Insert at tail. */ - if( inserted == false ) - { - IotListDouble_InsertTail( pList, pLink ); - } - } -} - -/** - * @brief Remove a single element from a doubly-linked list. - * - * @param[in] pLink The element to remove. - */ -/* @[declare_linear_containers_list_double_remove] */ -static inline void IotListDouble_Remove( IotLink_t * const pLink ) -/* @[declare_linear_containers_list_double_remove] */ -{ - /* This function must not be called with a NULL parameter. */ - IotContainers_Assert( pLink != NULL ); - - /* This function must be called on a linked element. */ - IotContainers_Assert( IotLink_IsLinked( pLink ) == true ); - - pLink->pPrevious->pNext = pLink->pNext; - pLink->pNext->pPrevious = pLink->pPrevious; - pLink->pPrevious = NULL; - pLink->pNext = NULL; -} - -/** - * @brief Remove the element at the head of a doubly-linked list. - * - * @param[in] pList The doubly-linked list that holds the element to remove. - * - * @return Pointer to an #IotLink_t representing the removed list head; `NULL` - * if the list is empty. The macro #IotLink_Container may be used to determine - * the address of the link's container. - */ -/* @[declare_linear_containers_list_double_removehead] */ -static inline IotLink_t * IotListDouble_RemoveHead( const IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_removehead] */ -{ - IotLink_t * pHead = NULL; - - if( IotListDouble_IsEmpty( pList ) == false ) - { - pHead = pList->pNext; - IotListDouble_Remove( pHead ); - } - - return pHead; -} - -/** - * @brief Remove the element at the tail of a doubly-linked list. - * - * @param[in] pList The doubly-linked list that holds the element to remove. - * - * @return Pointer to an #IotLink_t representing the removed list tail; `NULL` - * if the list is empty. The macro #IotLink_Container may be used to determine - * the address of the link's container. - */ -/* @[declare_linear_containers_list_double_removetail] */ -static inline IotLink_t * IotListDouble_RemoveTail( const IotListDouble_t * const pList ) -/* @[declare_linear_containers_list_double_removetail] */ -{ - IotLink_t * pTail = NULL; - - if( IotListDouble_IsEmpty( pList ) == false ) - { - pTail = pList->pPrevious; - IotListDouble_Remove( pTail ); - } - - return pTail; -} - -/** - * @brief Remove all elements in a doubly-linked list. - * - * @param[in] pList The list to empty. - * @param[in] freeElement A function to free memory used by each removed list - * element. Optional; pass `NULL` to ignore. - * @param[in] linkOffset Offset in bytes of a link member in its container, used - * to calculate the pointer to pass to `freeElement`. This value should be calculated - * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` - * or its value is `0`. - */ -/* @[declare_linear_containers_list_double_removeall] */ -static inline void IotListDouble_RemoveAll( const IotListDouble_t * const pList, - void ( *freeElement )( void * pData ), - size_t linkOffset ) -/* @[declare_linear_containers_list_double_removeall] */ -{ - /* This function must not be called with a NULL pList parameter. */ - IotContainers_Assert( pList != NULL ); - - /* Get the list head. */ - IotLink_t * pCurrent = pList->pNext; - - /* Iterate through the list and remove all elements. */ - while( pCurrent != pList ) - { - /* Save a pointer to the next list element. */ - IotLink_t * pNext = pCurrent->pNext; - - /* Remove and free the current list element. */ - IotListDouble_Remove( pCurrent ); - - if( freeElement != NULL ) - { - freeElement( ( ( uint8_t * ) pCurrent ) - linkOffset ); - } - - /* Move the iterating pointer to the next list element. */ - pCurrent = pNext; - } -} - -/** - * @brief Search a doubly-linked list for the first matching element. - * - * If a match is found, the matching element is not removed from the list. - * See @ref linear_containers_function_list_double_removefirstmatch for the function - * that searches and removes. - * - * @param[in] pList The doubly-linked list to search. - * @param[in] pStartPoint An element in `pList`. Only elements between this one and - * the list tail are checked. Pass `NULL` to search from the beginning of the list. - * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to - * search using the address `pMatch`, i.e. `element == pMatch`. - * @param[in] pMatch If `isMatch` is `NULL`, each element in the list is compared - * to this address to find a match. Otherwise, it is passed as the second argument - * to `isMatch`. - * - * @return Pointer to an #IotLink_t representing the first matched element; `NULL` - * if no match is found. The macro #IotLink_Container may be used to determine the - * address of the link's container. - */ -/* @[declare_linear_containers_list_double_findfirstmatch] */ -static inline IotLink_t * IotListDouble_FindFirstMatch( const IotListDouble_t * const pList, - const IotLink_t * const pStartPoint, - bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), - void * pMatch ) -/* @[declare_linear_containers_list_double_findfirstmatch] */ -{ - /* The const must be cast away to match this function's return value. Nevertheless, - * this function will respect the const-ness of pStartPoint. */ - /* coverity[misra_c_2012_rule_11_8_violation] */ - IotLink_t * pCurrent = ( IotLink_t * ) pStartPoint, * pMatchedLink = NULL; - bool matchFound = false; - - /* This function must not be called with a NULL pList parameter. */ - IotContainers_Assert( pList != NULL ); - - /* Search starting from list head if no start point is given. */ - if( pStartPoint == NULL ) - { - pCurrent = pList->pNext; - } - - /* Iterate through the list to search for matches. */ - while( pCurrent != pList ) - { - /* Call isMatch if provided. Otherwise, compare pointers. */ - if( isMatch != NULL ) - { - matchFound = isMatch( pCurrent, pMatch ); - } - else - { - matchFound = ( pCurrent == pMatch ); - } - - if( matchFound == true ) - { - pMatchedLink = pCurrent; - break; - } - - pCurrent = pCurrent->pNext; - } - - /* Return match if found, else NULL. */ - return pMatchedLink; -} - -/** - * @brief Search a doubly-linked list for the first matching element and remove - * it. - * - * An #IotLink_t may be passed as `pList` to start searching after the head of a - * doubly-linked list. - * - * @param[in] pList The doubly-linked list to search. - * @param[in] pStartPoint An element in `pList`. Only elements between this one and - * the list tail are checked. Pass `NULL` to search from the beginning of the list. - * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to - * search using the address `pMatch`, i.e. `element == pMatch`. - * @param[in] pMatch If `isMatch` is `NULL`, each element in the list is compared - * to this address to find a match. Otherwise, it is passed as the second argument - * to `isMatch`. - * - * @return Pointer to an #IotLink_t representing the matched and removed element; - * `NULL` if no match is found. The macro #IotLink_Container may be used to determine - * the address of the link's container. - */ -/* @[declare_linear_containers_list_double_removefirstmatch] */ -static inline IotLink_t * IotListDouble_RemoveFirstMatch( const IotListDouble_t * const pList, - const IotLink_t * const pStartPoint, - bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), - void * pMatch ) -/* @[declare_linear_containers_list_double_removefirstmatch] */ -{ - IotLink_t * pMatchedElement = IotListDouble_FindFirstMatch( pList, - pStartPoint, - isMatch, - pMatch ); - - if( pMatchedElement != NULL ) - { - IotListDouble_Remove( pMatchedElement ); - } - - return pMatchedElement; -} - -/** - * @brief Remove all matching elements from a doubly-linked list. - * - * @param[in] pList The doubly-linked list to search. - * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to - * search using the address `pMatch`, i.e. `element == pMatch`. - * @param[in] pMatch If `isMatch` is `NULL`, each element in the list is compared - * to this address to find a match. Otherwise, it is passed as the second argument - * to `isMatch`. - * @param[in] freeElement A function to free memory used by each removed list - * element. Optional; pass `NULL` to ignore. - * @param[in] linkOffset Offset in bytes of a link member in its container, used - * to calculate the pointer to pass to `freeElement`. This value should be calculated - * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` - * or its value is `0`. - */ -/* @[declare_linear_containers_list_double_removeallmatches] */ -static inline void IotListDouble_RemoveAllMatches( const IotListDouble_t * const pList, - bool ( *isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), - void * pMatch, - void ( *freeElement )( void * pData ), - size_t linkOffset ) -/* @[declare_linear_containers_list_double_removeallmatches] */ -{ - IotLink_t * pMatchedElement = NULL, * pNextElement = NULL; - - /* Search the list for all matching elements. */ - do - { - pMatchedElement = IotListDouble_FindFirstMatch( pList, - pMatchedElement, - isMatch, - pMatch ); - - if( pMatchedElement != NULL ) - { - /* Save pointer to next element. */ - pNextElement = pMatchedElement->pNext; - - /* Match found; remove and free. */ - IotListDouble_Remove( pMatchedElement ); - - if( freeElement != NULL ) - { - freeElement( ( ( uint8_t * ) pMatchedElement ) - linkOffset ); - } - - /* Continue search from next element. */ - pMatchedElement = pNextElement; - } - } while( pMatchedElement != NULL ); -} - -/** - * @brief Create a new queue. - * - * This function initializes a new double-ended queue. It must be called on an uninitialized - * #IotDeQueue_t before calling any other queue function. This function must not be - * called on an already-initialized #IotDeQueue_t. - * - * This function will not fail. - * - * @param[in] pQueue Pointer to the memory that will hold the new queue. - */ -/* @[declare_linear_containers_queue_create] */ -static inline void IotDeQueue_Create( IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_create] */ -{ - IotListDouble_Create( pQueue ); -} - -/** - * @brief Return the number of elements contained in an #IotDeQueue_t. - * - * @param[in] pQueue The queue with the elements to count. - * - * @return The number of items elements in the queue. - */ -/* @[declare_linear_containers_queue_count] */ -static inline size_t IotDeQueue_Count( const IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_count] */ -{ - return IotListDouble_Count( pQueue ); -} - -/** - * @brief Check if a queue is empty. - * - * @param[in] pQueue The queue to check. - * - * @return `true` if the queue is empty; `false` otherwise. - * - */ -/* @[declare_linear_containers_queue_isempty] */ -static inline bool IotDeQueue_IsEmpty( const IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_isempty] */ -{ - return IotListDouble_IsEmpty( pQueue ); -} - -/** - * @brief Return an #IotLink_t representing the element at the front of the queue - * without removing it. - * - * @param[in] pQueue The queue to peek. - * - * @return Pointer to an #IotLink_t representing the element at the head of the - * queue; `NULL` if the queue is empty. The macro #IotLink_Container may be used - * to determine the address of the link's container. - */ -/* @[declare_linear_containers_queue_peekhead] */ -static inline IotLink_t * IotDeQueue_PeekHead( const IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_peekhead] */ -{ - return IotListDouble_PeekHead( pQueue ); -} - -/** - * @brief Return an #IotLink_t representing the element at the back of the queue - * without removing it. - * - * @param[in] pQueue The queue to peek. - * - * @return Pointer to an #IotLink_t representing the element at the head of the - * queue; `NULL` if the queue is empty. The macro #IotLink_Container may be used - * to determine the address of the link's container. - */ -/* @[declare_linear_containers_queue_peektail] */ -static inline IotLink_t * IotDeQueue_PeekTail( const IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_peektail] */ -{ - return IotListDouble_PeekTail( pQueue ); -} - -/** - * @brief Add an element at the head of the queue. - * - * @param[in] pQueue The queue that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - */ -/* @[declare_linear_containers_queue_enqueuehead] */ -static inline void IotDeQueue_EnqueueHead( IotDeQueue_t * const pQueue, - IotLink_t * const pLink ) -/* @[declare_linear_containers_queue_enqueuehead] */ -{ - IotListDouble_InsertHead( pQueue, pLink ); -} - -/** - * @brief Remove an element at the head of the queue. - * - * @param[in] pQueue The queue that holds the element to remove. - * - * @return Pointer to an #IotLink_t representing the removed queue element; `NULL` - * if the queue is empty. The macro #IotLink_Container may be used to determine - * the address of the link's container. - */ -/* @[declare_linear_containers_queue_dequeuehead] */ -static inline IotLink_t * IotDeQueue_DequeueHead( const IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_dequeuehead] */ -{ - return IotListDouble_RemoveHead( pQueue ); -} - -/** - * @brief Add an element at the tail of the queue. - * - * @param[in] pQueue The queue that will hold the new element. - * @param[in] pLink Pointer to the new element's link member. - */ -/* @[declare_linear_containers_queue_enqueuetail] */ -static inline void IotDeQueue_EnqueueTail( IotDeQueue_t * const pQueue, - IotLink_t * const pLink ) -/* @[declare_linear_containers_queue_enqueuetail] */ -{ - IotListDouble_InsertTail( pQueue, pLink ); -} - -/** - * @brief Remove an element at the tail of the queue. - * - * @param[in] pQueue The queue that holds the element to remove. - * - * @return Pointer to an #IotLink_t representing the removed queue element; `NULL` - * if the queue is empty. The macro #IotLink_Container may be used to determine - * the address of the link's container. - */ -/* @[declare_linear_containers_queue_dequeuetail] */ -static inline IotLink_t * IotDeQueue_DequeueTail( const IotDeQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_dequeuetail] */ -{ - return IotListDouble_RemoveTail( pQueue ); -} - -/** - * @brief Remove a single element from a queue. - * - * @param[in] pLink The element to remove. - */ -/* @[declare_linear_containers_queue_remove] */ -static inline void IotDeQueue_Remove( IotLink_t * const pLink ) -/* @[declare_linear_containers_queue_remove] */ -{ - IotListDouble_Remove( pLink ); -} - -/** - * @brief Remove all elements in a queue. - * - * @param[in] pQueue The queue to empty. - * @param[in] freeElement A function to free memory used by each removed queue - * element. Optional; pass `NULL` to ignore. - * @param[in] linkOffset Offset in bytes of a link member in its container, used - * to calculate the pointer to pass to `freeElement`. This value should be calculated - * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` - * or its value is `0`. - */ -/* @[declare_linear_containers_queue_removeall] */ -static inline void IotDeQueue_RemoveAll( const IotDeQueue_t * const pQueue, - void ( * freeElement )( void * pData ), - size_t linkOffset ) -/* @[declare_linear_containers_queue_removeall] */ -{ - IotListDouble_RemoveAll( pQueue, freeElement, linkOffset ); -} - -/** - * @brief Remove all matching elements from a queue. - * - * @param[in] pQueue The queue to search. - * @param[in] isMatch Function to determine if an element matches. Pass `NULL` to - * search using the address `pMatch`, i.e. `element == pMatch`. - * @param[in] pMatch If `isMatch` is `NULL`, each element in the queue is compared - * to this address to find a match. Otherwise, it is passed as the second argument - * to `isMatch`. - * @param[in] freeElement A function to free memory used by each removed queue - * element. Optional; pass `NULL` to ignore. - * @param[in] linkOffset Offset in bytes of a link member in its container, used - * to calculate the pointer to pass to `freeElement`. This value should be calculated - * with the C `offsetof` macro. This parameter is ignored if `freeElement` is `NULL` - * or its value is `0`. - */ -/* @[declare_linear_containers_queue_removeallmatches] */ -static inline void IotDeQueue_RemoveAllMatches( const IotDeQueue_t * const pQueue, - bool ( * isMatch )( const IotLink_t * const pOperationLink, void * pCompare ), - void * pMatch, - void ( * freeElement )( void * pData ), - size_t linkOffset ) -/* @[declare_linear_containers_queue_removeallmatches] */ -{ - IotListDouble_RemoveAllMatches( pQueue, isMatch, pMatch, freeElement, linkOffset ); -} - -#endif /* IOT_LINEAR_CONTAINERS_H_ */ diff --git a/libraries/standard/common/include/iot_logging.h b/libraries/standard/common/include/iot_logging.h deleted file mode 100644 index 8875747290..0000000000 --- a/libraries/standard/common/include/iot_logging.h +++ /dev/null @@ -1,226 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_logging.h - * @brief Generic logging function header file. - * - * Declares the generic logging function and the log levels. This file never - * needs to be included in source code. The header iot_logging_setup.h should - * be included instead. - * - * @see iot_logging_setup.h - */ - -#ifndef IOT_LOGGING_H_ -#define IOT_LOGGING_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/** - * @constantspage{logging,logging library} - * - * @section logging_constants_levels Log levels - * @brief Log levels for the libraries in this SDK. - * - * Each library should specify a log level by setting @ref LIBRARY_LOG_LEVEL. - * All log messages with a level at or below the specified level will be printed - * for that library. - * - * Currently, there are 4 log levels. In the order of lowest to highest, they are: - * - #IOT_LOG_NONE
- * @copybrief IOT_LOG_NONE - * - #IOT_LOG_ERROR
- * @copybrief IOT_LOG_ERROR - * - #IOT_LOG_WARN
- * @copybrief IOT_LOG_WARN - * - #IOT_LOG_INFO
- * @copybrief IOT_LOG_INFO - * - #IOT_LOG_DEBUG
- * @copybrief IOT_LOG_DEBUG - */ - -/** - * @brief No log messages. - * - * Log messages with this level will be silently discarded. When @ref - * LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no [logging functions] - * (@ref logging_functions) can be called. - */ -#define IOT_LOG_NONE 0 - -/** - * @brief Only critical, unrecoverable errors. - * - * Log messages with this level will be printed when a library encounters an - * error from which it cannot easily recover. - */ -#define IOT_LOG_ERROR 1 - -/** - * @brief Message about an abnormal but recoverable event. - * - * Log messages with this level will be printed when a library encounters an - * abnormal event that may be indicative of an error. Libraries should continue - * execution after logging a warning. - */ -#define IOT_LOG_WARN 2 - -/** - * @brief A helpful, informational message. - * - * Log messages with this level may indicate the normal status of a library - * function. They should be used to track how far a program has executed. - */ -#define IOT_LOG_INFO 3 - -/** - * @brief Detailed and excessive debug information. - * - * Log messages with this level are intended for developers. They may contain - * excessive information such as internal variables, buffers, or other specific - * information. - */ -#define IOT_LOG_DEBUG 4 - -/** - * @paramstructs{logging,logging} - */ - -/** - * @ingroup logging_datatypes_paramstructs - * @brief Log message configuration struct. - * - * @paramfor @ref logging_function_log, @ref logging_function_generic - * - * By default, log messages print the library name, log level, and a timestring. - * This struct can be passed to @ref logging_function_generic to disable one of - * the above components in the log message. - * - * Example: - * - * @code{c} - * IotLog_Generic( IOT_LOG_DEBUG, "SAMPLE", IOT_LOG_DEBUG, NULL, "Hello world!" ); - * @endcode - * The code above prints the following message: - * @code - * [DEBUG][SAMPLE][2018-01-01 12:00:00] Hello world! - * @endcode - * - * The timestring can be disabled as follows: - * @code - * IotLogConfig_t logConfig = { .hideLogLevel = false, .hideLibraryName = false, .hideTimestring = true}; - * IotLog_Generic( IOT_LOG_DEBUG, "SAMPLE", IOT_LOG_DEBUG, &logConfig, "Hello world!" ); - * @endcode - * The resulting log message will be: - * @code - * [DEBUG][SAMPLE] Hello world! - * @endcode - */ -typedef struct IotLogConfig -{ - bool hideLogLevel; /**< @brief Don't print the log level string for this message. */ - bool hideLibraryName; /**< @brief Don't print the library name for this message. */ - bool hideTimestring; /**< @brief Don't print the timestring for this message. */ -} IotLogConfig_t; - -/** - * @functionspage{logging,logging library} - * - * - @functionname{logging_function_log} - * - @functionname{logging_function_printbuffer} - * - @functionname{logging_function_generic} - * - @functionname{logging_function_genericprintbuffer} - */ - -/** - * @functionpage{IotLog_Generic,logging,generic} - * @functionpage{IotLog_PrintBuffer,logging,genericprintbuffer} - */ - -/** - * @brief Generic logging function that prints a single message. - * - * This function is the generic logging function shared across all libraries. - * The library-specific logging function @ref logging_function_log is implemented - * using this function. Like @ref logging_function_log, this function is only - * available when @ref LIBRARY_LOG_LEVEL is #IOT_LOG_NONE. - * - * In most cases, the library-specific logging function @ref logging_function_log - * should be called instead of this function. - * - * @param[in] libraryLogSetting The log level setting of the library, used to - * determine if the log message should be printed. Must be one of the @ref - * logging_constants_levels. - * @param[in] pLibraryName The library name to print. See @ref LIBRARY_LOG_NAME. - * @param[in] messageLevel The log level of the this message. See @ref LIBRARY_LOG_LEVEL. - * @param[in] pLogConfig Pointer to a #IotLogConfig_t. Optional; pass `NULL` to ignore. - * @param[in] pFormat Format string for the log message. - * @param[in] ... Arguments for format specification. - * - * @return No return value. On errors, it prints nothing. - */ -/* @[declare_logging_generic] */ -void IotLog_Generic( int32_t libraryLogSetting, - const char * const pLibraryName, - int32_t messageLevel, - const IotLogConfig_t * const pLogConfig, - const char * const pFormat, - ... ); -/* @[declare_logging_generic] */ - -/** - * @brief Generic function to log the contents of a buffer as bytes. - * - * This function is the generic buffer logging function shared across all libraries. - * The library-specific buffer logging function @ref logging_function_printbuffer is - * implemented using this function. Like @ref logging_function_printbuffer, this - * function is only available when @ref LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. - * - * In most cases, the library-specific buffer logging function @ref - * logging_function_printbuffer should be called instead of this function. - * - * @param[in] pLibraryName The library name to print with the log. See @ref LIBRARY_LOG_NAME. - * @param[in] pHeader A message to print before printing the buffer. - * @param[in] pBuffer The buffer to print. - * @param[in] bufferSize The number of bytes in `pBuffer` to print. - * - * @return No return value. On errors, it prints nothing. - * - * @note To conserve memory, this function only allocates enough memory for a - * single line of output. Therefore, in multithreaded systems, its output may - * appear "fragmented" if other threads are logging simultaneously. - */ -/* @[declare_logging_genericprintbuffer] */ -void IotLog_GenericPrintBuffer( const char * const pLibraryName, - const char * const pHeader, - const uint8_t * const pBuffer, - size_t bufferSize ); -/* @[declare_logging_genericprintbuffer] */ - -#endif /* ifndef IOT_LOGGING_H_ */ diff --git a/libraries/standard/common/include/iot_logging_setup.h b/libraries/standard/common/include/iot_logging_setup.h deleted file mode 100644 index 76e414a3f2..0000000000 --- a/libraries/standard/common/include/iot_logging_setup.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_logging_setup.h - * @brief Defines the logging macro #IotLog. - */ - -#ifndef IOT_LOGGING_SETUP_H_ -#define IOT_LOGGING_SETUP_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Logging include. Because it's included here, iot_logging.h never needs - * to be included in source. */ -#include "iot_logging.h" - -/** - * @functionpage{IotLog,logging,log} - * @functionpage{IotLog_PrintBuffer,logging,printbuffer} - */ - -/** - * @def IotLog( messageLevel, pLogConfig, ... ) - * @brief Logging function for a specific library. In most cases, this is the - * logging function to call. - * - * This function prints a single log message. It is available when @ref - * LIBRARY_LOG_LEVEL is not #IOT_LOG_NONE. Log messages automatically - * include the [log level](@ref logging_constants_levels), [library name] - * (@ref LIBRARY_LOG_NAME), and time. An optional @ref IotLogConfig_t may - * be passed to this function to hide information for a single log message. - * - * The logging library must be set up before this function may be called. See - * @ref logging_setup_use for more information. - * - * This logging function also has the following abbreviated forms that can be used - * when an #IotLogConfig_t isn't needed. - * - * Name | Equivalent to - * ---- | ------------- - * #IotLogError | @code{c} IotLog( IOT_LOG_ERROR, NULL, ... ) @endcode - * #IotLogWarn | @code{c} IotLog( IOT_LOG_WARN, NULL, ... ) @endcode - * #IotLogInfo | @code{c} IotLog( IOT_LOG_INFO, NULL, ... ) @endcode - * #IotLogDebug | @code{c} IotLog( IOT_LOG_DEBUG, NULL, ... ) @endcode - * - * @param[in] messageLevel Log level of this message. Must be one of the - * @ref logging_constants_levels. - * @param[in] pLogConfig Pointer to an #IotLogConfig_t. Optional; pass `NULL` - * to ignore. - * @param[in] ... Message and format specification. - * - * @return No return value. On errors, it prints nothing. - * - * @note This function may be implemented as a macro. - * @see @ref logging_function_generic for the generic (not library-specific) - * logging function. - */ - -/** - * @def IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) - * @brief Log the contents of buffer as bytes. Only available when @ref - * LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. - * - * This function prints the bytes located at a given memory address. It is - * intended for debugging only, and is therefore only available when @ref - * LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. - * - * Log messages printed by this function always include the [log level] - * (@ref logging_constants_levels), [library name](@ref LIBRARY_LOG_NAME), - * and time. In addition, this function may print an optional header `pHeader` - * before it prints the contents of the buffer. This function does not have an - * #IotLogConfig_t parameter. - * - * The logging library must be set up before this function may be called. See - * @ref logging_setup_use for more information. - * - * @param[in] pHeader A message to log before the buffer. Optional; pass `NULL` - * to ignore. - * @param[in] pBuffer Pointer to start of buffer. - * @param[in] bufferSize Size of `pBuffer`. - * - * @return No return value. On errors, it prints nothing. - * - * @note This function may be implemented as a macro. - * @note To conserve memory, @ref logging_function_genericprintbuffer (the underlying - * implementation) only allocates enough memory for a single line of output. Therefore, - * in multithreaded systems, its output may appear "fragmented" if other threads are - * logging simultaneously. - * @see @ref logging_function_genericprintbuffer for the generic (not library-specific) - * buffer logging function. - * - * Example - * @code{c} - * const uint8_t pBuffer[] = { 0x00, 0x01, 0x02, 0x03 }; - * - * IotLog_PrintBuffer( "This buffer contains:", - * pBuffer, - * 4 ); - * @endcode - * The code above prints something like the following: - * @code{c} - * [DEBUG][LIB_NAME][2018-01-01 12:00:00] This buffer contains: - * 00 01 02 03 - * @endcode - */ - -/** - * @def IotLogError( ... ) - * @brief Abbreviated logging macro for level #IOT_LOG_ERROR. - * - * Equivalent to: - * @code{c} - * IotLog( IOT_LOG_ERROR, NULL, ... ) - * @endcode - */ - -/** - * @def IotLogWarn( ... ) - * @brief Abbreviated logging macro for level #IOT_LOG_WARN. - * - * Equivalent to: - * @code{c} - * IotLog( IOT_LOG_WARN, NULL, ... ) - * @endcode - */ - -/** - * @def IotLogInfo( ... ) - * @brief Abbreviated logging macro for level #IOT_LOG_INFO. - * - * Equivalent to: - * @code{c} - * IotLog( IOT_LOG_INFO, NULL, ... ) - * @endcode - */ - -/** - * @def IotLogDebug( ... ) - * @brief Abbreviated logging macro for level #IOT_LOG_DEBUG. - * - * Equivalent to: - * @code{c} - * IotLog( IOT_LOG_DEBUG, NULL, ... ) - * @endcode - */ - -/* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ -#if !defined( LIBRARY_LOG_LEVEL ) || \ - ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) - #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." -/* Check that LIBRARY_LOG_NAME is defined and has a valid value. */ -#elif !defined( LIBRARY_LOG_NAME ) - #error "Please define LIBRARY_LOG_NAME." -#else - /* Define IotLog if the log level is greater than "none". */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - #define IotLog( messageLevel, pLogConfig, ... ) \ - IotLog_Generic( LIBRARY_LOG_LEVEL, \ - LIBRARY_LOG_NAME, \ - messageLevel, \ - pLogConfig, \ - __VA_ARGS__ ) - - /* Define the abbreviated logging macros. */ - #define IotLogError( ... ) IotLog( IOT_LOG_ERROR, NULL, __VA_ARGS__ ) - #define IotLogWarn( ... ) IotLog( IOT_LOG_WARN, NULL, __VA_ARGS__ ) - #define IotLogInfo( ... ) IotLog( IOT_LOG_INFO, NULL, __VA_ARGS__ ) - #define IotLogDebug( ... ) IotLog( IOT_LOG_DEBUG, NULL, __VA_ARGS__ ) - - /* If log level is DEBUG, enable the function to print buffers. */ - #if LIBRARY_LOG_LEVEL >= IOT_LOG_DEBUG - #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) \ - IotLog_GenericPrintBuffer( LIBRARY_LOG_NAME, \ - pHeader, \ - pBuffer, \ - bufferSize ) - #else - #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) - #endif - /* Remove references to IotLog from the source code if logging is disabled. */ - #else - /* @[declare_logging_log] */ - #define IotLog( messageLevel, pLogConfig, ... ) - /* @[declare_logging_log] */ - /* @[declare_logging_printbuffer] */ - #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) - /* @[declare_logging_printbuffer] */ - #define IotLogError( ... ) - #define IotLogWarn( ... ) - #define IotLogInfo( ... ) - #define IotLogDebug( ... ) - #endif -#endif - -#endif /* ifndef IOT_LOGGING_SETUP_H_ */ diff --git a/libraries/standard/common/include/iot_static_memory.h b/libraries/standard/common/include/iot_static_memory.h deleted file mode 100644 index 6537506693..0000000000 --- a/libraries/standard/common/include/iot_static_memory.h +++ /dev/null @@ -1,200 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_static_memory.h - * @brief Common functions for managing static buffers. Only used when - * @ref IOT_STATIC_MEMORY_ONLY is `1`. - */ - -#ifndef IOT_STATIC_MEMORY_H_ -#define IOT_STATIC_MEMORY_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* The functions in this file should only exist in static memory only mode. */ -#if ( IOT_STATIC_MEMORY_ONLY == 1 ) - - -/* Standard includes. */ - #include - #include - -/** - * @functionspage{static_memory,static memory component} - * - @functionname{static_memory_function_findfree} - * - @functionname{static_memory_function_returninuse} - * - @functionname{static_memory_function_messagebuffersize} - * - @functionname{static_memory_function_mallocmessagebuffer} - * - @functionname{static_memory_function_freemessagebuffer} - */ - -/*------------------------- Buffer allocation and free ----------------------*/ - -/** - * @functionpage{IotStaticMemory_FindFree,static_memory,findfree} - * @functionpage{IotStaticMemory_ReturnInUse,static_memory,returninuse} - */ - -/** - * @brief Find a free buffer using the "in-use" flags. - * - * If a free buffer is found, this function marks the buffer in-use. This function - * is common to the static memory implementation. - * - * @param[in] pInUse The "in-use" flags to search. - * @param[in] limit How many flags to check, i.e. the size of `pInUse`. - * - * @return The index of a free buffer; `-1` if no free buffers are available. - * - * Example: - * @code{c} - * // To use this function, first declare two arrays. One provides the statically-allocated - * // objects, the other provides flags to determine which objects are in-use. - * #define NUMBER_OF_OBJECTS ... - * #define OBJECT_SIZE ... - * static uint32_t _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; - * static uint8_t _pObjects[ NUMBER_OF_OBJECTS ][ OBJECT_SIZE ] = { { 0 } }; // Placeholder for objects. - * - * // The function to statically allocate objects. Must have the same signature - * // as malloc(). - * void * Iot_MallocObject( size_t size ) - * { - * int32_t freeIndex = -1; - * void * pNewObject = NULL; - * - * // Check that sizes match. - * if( size != OBJECT_SIZE ) - * { - * // Get the index of a free object. - * freeIndex = IotStaticMemory_FindFree( _pInUseMessageBuffers, - * IOT_MESSAGE_BUFFERS ); - * - * if( freeIndex != -1 ) - * { - * pNewBuffer = &( _pMessageBuffers[ freeIndex ][ 0 ] ); - * } - * } - * - * return pNewBuffer; - * } - * @endcode - */ -/* @[declare_static_memory_findfree] */ - int32_t IotStaticMemory_FindFree( uint32_t * pInUse, - size_t limit ); -/* @[declare_static_memory_findfree] */ - -/** - * @brief Return an "in-use" buffer. - * - * This function is common to the static memory implementation. - * - * @param[in] ptr Pointer to the buffer to return. - * @param[in] pPool The pool of buffers that the in-use buffer was allocated from. - * @param[in] pInUse The "in-use" flags for pPool. - * @param[in] limit How many buffers (and flags) to check while searching for ptr. - * @param[in] elementSize The size of a single element in pPool. - * - * Example: - * @code{c} - * // To use this function, first declare two arrays. One provides the statically-allocated - * // objects, the other provides flags to determine which objects are in-use. - * #define NUMBER_OF_OBJECTS ... - * #define OBJECT_SIZE ... - * static uint32_t _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; - * static uint8_t _pObjects[ NUMBER_OF_OBJECTS ][ OBJECT_SIZE ] = { { 0 } }; // Placeholder for objects. - * - * // The function to free statically-allocated objects. Must have the same signature - * // as free(). - * void Iot_FreeObject( void * ptr ) - * { - * IotStaticMemory_ReturnInUse( ptr, - * _pObjects, - * _pInUseObjects, - * NUMBER_OF_OBJECTS, - * OBJECT_SIZE ); - * } - * @endcode - */ -/* @[declare_static_memory_returninuse] */ - void IotStaticMemory_ReturnInUse( void * ptr, - void * pPool, - uint32_t * pInUse, - size_t limit, - size_t elementSize ); -/* @[declare_static_memory_returninuse] */ - -/*------------------------ Message buffer management ------------------------*/ - -/** - * @functionpage{Iot_MessageBufferSize,static_memory,messagebuffersize} - * @functionpage{Iot_MallocMessageBuffer,static_memory,mallocmessagebuffer} - * @functionpage{Iot_FreeMessageBuffer,static_memory,freemessagebuffer} - */ - -/** - * @brief Get the fixed size of a message buffer. - * - * The size of the message buffers are known at compile time, but it is a [constant] - * (@ref IOT_MESSAGE_BUFFER_SIZE) that may not be visible to all source files. - * This function allows other source files to know the size of a message buffer. - * - * @return The size, in bytes, of a single message buffer. - */ -/* @[declare_static_memory_messagebuffersize] */ - size_t Iot_MessageBufferSize( void ); -/* @[declare_static_memory_messagebuffersize] */ - -/** - * @brief Get an empty message buffer. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for message buffers. - * - * @param[in] size Requested size for a message buffer. - * - * @return Pointer to the start of a message buffer. If the `size` argument is larger - * than the [fixed size of a message buffer](@ref IOT_MESSAGE_BUFFER_SIZE) - * or no message buffers are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocmessagebuffer] */ - void * Iot_MallocMessageBuffer( size_t size ); -/* @[declare_static_memory_mallocmessagebuffer] */ - -/** - * @brief Free an in-use message buffer. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for message buffers. - * - * @param[in] ptr Pointer to the message buffer to free. - */ -/* @[declare_static_memory_freemessagebuffer] */ - void Iot_FreeMessageBuffer( void * ptr ); -/* @[declare_static_memory_freemessagebuffer] */ - -#endif /* if ( IOT_STATIC_MEMORY_ONLY == 1 ) */ -#endif /* ifndef IOT_STATIC_MEMORY_H_ */ diff --git a/libraries/standard/common/include/iot_taskpool.h b/libraries/standard/common/include/iot_taskpool.h deleted file mode 100644 index c4980d26a6..0000000000 --- a/libraries/standard/common/include/iot_taskpool.h +++ /dev/null @@ -1,554 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_taskpool.h - * @brief User-facing functions of the task pool library. - */ - -#ifndef IOT_TASKPOOL_H_ -#define IOT_TASKPOOL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Task pool types. */ -#include "types/iot_taskpool_types.h" - -/*------------------------- Task Pool library functions --------------------------*/ - -/** - * @functionspage{taskpool,task pool library} - * - @functionname{taskpool_function_createsystemtaskpool} - * - @functionname{taskpool_function_getsystemtaskpool} - * - @functionname{taskpool_function_create} - * - @functionname{taskpool_function_destroy} - * - @functionname{taskpool_function_setmaxthreads} - * - @functionname{taskpool_function_createjob} - * - @functionname{taskpool_function_createrecyclablejob} - * - @functionname{taskpool_function_destroyrecyclablejob} - * - @functionname{taskpool_function_recyclejob} - * - @functionname{taskpool_function_schedule} - * - @functionname{taskpool_function_scheduledeferred} - * - @functionname{taskpool_function_getstatus} - * - @functionname{taskpool_function_trycancel} - * - @functionname{taskpool_function_getjobstoragefromhandle} - * - @functionname{taskpool_function_strerror} - */ - -/** - * @functionpage{IotTaskPool_CreateSystemTaskPool,taskpool,createsystemtaskpool} - * @functionpage{IotTaskPool_GetSystemTaskPool,taskpool,getsystemtaskpool} - * @functionpage{IotTaskPool_Create,taskpool,create} - * @functionpage{IotTaskPool_Destroy,taskpool,destroy} - * @functionpage{IotTaskPool_SetMaxThreads,taskpool,setmaxthreads} - * @functionpage{IotTaskPool_CreateJob,taskpool,createjob} - * @functionpage{IotTaskPool_CreateRecyclableJob,taskpool,createrecyclablejob} - * @functionpage{IotTaskPool_DestroyRecyclableJob,taskpool,destroyrecyclablejob} - * @functionpage{IotTaskPool_RecycleJob,taskpool,recyclejob} - * @functionpage{IotTaskPool_Schedule,taskpool,schedule} - * @functionpage{IotTaskPool_ScheduleDeferred,taskpool,scheduledeferred} - * @functionpage{IotTaskPool_GetStatus,taskpool,getstatus} - * @functionpage{IotTaskPool_TryCancel,taskpool,trycancel} - * @functionpage{IotTaskPool_GetJobStorageFromHandle,taskpool,getjobstoragefromhandle} - * @functionpage{IotTaskPool_strerror,taskpool,strerror} - */ - -/** - * @brief Creates the one single instance of the system task pool. - * - * This function should be called once by the application to initialize the one single instance of the system task pool. - * An application should initialize the system task pool early in the boot sequence, before initializing any other library - * that uses the system task pool, such as MQTT, Shadow, Defender, etc. An application should also initialize the system - * task pool before posting any jobs. Early initialization is typically easy to accomplish by creating the system task pool - * before the scheduler is started. - * - * This function does not allocate memory to hold the task pool data structures and state, but it - * may allocate memory to hold the dependent entities and data structures, e.g. the threads of the task - * pool. The system task pool handle is recoverable for later use by calling @ref IotTaskPool_GetSystemTaskPool or - * the shortcut @ref IOT_SYSTEM_TASKPOOL. - * - * @param[in] pInfo A pointer to the task pool initialization data. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_NO_MEMORY - * - * @warning This function should be called only once. Calling this function more that once will result in - * undefined behavior. - * - */ -/* @[declare_taskpool_createsystemtaskpool] */ -IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * pInfo ); -/* @[declare_taskpool_createsystemtaskpool] */ - -/** - * @brief Retrieves the one and only instance of a system task pool - * - * This function retrieves the system task pool created with @ref IotTaskPool_CreateSystemTaskPool, and it is functionally - * equivalent to using the shortcut @ref IOT_SYSTEM_TASKPOOL. - * - * @return The system task pool handle. - * - * @warning This function should be called after creating the system task pool with @ref IotTaskPool_CreateSystemTaskPool. - * Calling this function before creating the system task pool may return a pointer to an uninitialized task pool, NULL, or otherwise - * fail with undefined behaviour. - * - */ -/* @[declare_taskpool_getsystemtaskpool] */ -IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ); -/* @[declare_taskpool_getsystemtaskpool] */ - -/** - * @brief Creates one instance of a task pool. - * - * This function should be called by the user to initialize one instance of a task - * pool. The task pool instance will be created around the storage pointed to by the `pTaskPool` - * parameter. This function will create the minimum number of threads requested by the user - * through an instance of the #IotTaskPoolInfo_t type specified with the `pInfo` parameter. - * This function does not allocate memory to hold the task pool data structures and state, but it - * may allocates memory to hold the dependent data structures, e.g. the threads of the task - * pool. - * - * @param[in] pInfo A pointer to the task pool initialization data. - * @param[out] pTaskPool A pointer to the task pool handle to be used after initialization. - * The pointer `pTaskPool` will hold a valid handle only if (@ref IotTaskPool_Create) - * completes successfully. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_NO_MEMORY - * - */ -/* @[declare_taskpool_create] */ -IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, - IotTaskPool_t * const pTaskPool ); -/* @[declare_taskpool_create] */ - -/** - * @brief Destroys a task pool instance and collects all memory associated with a task pool and its - * satellite data structures. - * - * This function should be called to destroy one instance of a task pool previously created with a call - * to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * Calling this function release all underlying resources. After calling this function, any job scheduled but not yet executed - * will be canceled and destroyed. - * The `taskPool` instance will no longer be valid after this function returns. - * - * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or - * @ref IotTaskPool_CreateSystemTaskPool. The `taskPool` instance will no longer be valid after this function returns. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - */ -/* @[declare_taskpool_destroy] */ -IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t taskPool ); -/* @[declare_taskpool_destroy] */ - -/** - * @brief Sets the maximum number of threads for one instance of a task pool. - * - * This function sets the maximum number of threads for the task pool - * pointed to by `taskPool`. - * - * If the number of currently active threads in the task pool is greater than `maxThreads`, this - * function causes the task pool to shrink the number of active threads. - * - * @param[in] taskPool A handle to the task pool that must have been previously initialized with - * a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * @param[in] maxThreads The maximum number of threads for the task pool. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - */ -/* @[declare_taskpool_setmaxthreads] */ -IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPool, - uint32_t maxThreads ); -/* @[declare_taskpool_setmaxthreads] */ - -/** - * @brief Creates a job for the task pool around a user-provided storage. - * - * This function may allocate memory to hold the state for a job. - * - * @param[in] userCallback A user-specified callback for the job. - * @param[in] pUserContext A user-specified context for the callback. - * @param[in,out] pJobStorage The storage for the job data structure. - * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this - * function returns successfully. This handle can be used to inspect the job status with - * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - * - */ -/* @[declare_taskpool_createjob] */ -IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJobStorage_t * const pJobStorage, - IotTaskPoolJob_t * const pJob ); -/* @[declare_taskpool_createjob] */ - -/** - * @brief Creates a job for the task pool by allocating the job dynamically. - * - * A recyclable job does not need to be allocated twice, but it can rather be reused through - * subsequent calls to @ref IotTaskPool_CreateRecyclableJob. - * - * @param[in] taskPool A handle to the task pool for which to create a recyclable job. - * @param[in] userCallback A user-specified callback for the job. - * @param[in] pUserContext A user-specified context for the callback. - * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this - * function returns successfully. This handle can be used to inspect the job status with - * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_NO_MEMORY - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - * @warning A recyclable job should be recycled with a call to @ref IotTaskPool_RecycleJob rather than destroyed. - * - */ -/* @[declare_taskpool_createrecyclablejob] */ -IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPool, - IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJob_t * const pJob ); -/* @[declare_taskpool_createrecyclablejob] */ - -/** - * @brief This function un-initializes a job. - * - * This function will destroy a job created with @ref IotTaskPool_CreateRecyclableJob. - * A job should not be destroyed twice. A job that was previously scheduled but has not completed yet should not be destroyed, - * but rather the application should attempt to cancel it first by calling @ref IotTaskPool_TryCancel. - * An attempt to destroy a job that was scheduled but not yet executed or canceled, may result in a - * @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. - * - * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * @param[in] job A handle to a job that was create with a call to @ref IotTaskPool_CreateJob. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_ILLEGAL_OPERATION - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - * @warning The task pool will try and prevent destroying jobs that are currently queued for execution, but does - * not enforce strict ordering of operations. It is up to the user to make sure @ref IotTaskPool_DestroyRecyclableJob is not called - * our of order. - * - * @warning Calling this function on job that was not previously created with @ref IotTaskPool_CreateRecyclableJob - * will result in a @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. - * - */ -/* @[declare_taskpool_destroyrecyclablejob] */ -IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPool, - IotTaskPoolJob_t job ); -/* @[declare_taskpool_destroyrecyclablejob] */ - -/** - * @brief Recycles a job into the task pool job cache. - * - * This function will try and recycle the job into the task pool cache. If the cache is full, - * the job memory is destroyed as if the user called @ref IotTaskPool_DestroyRecyclableJob. The job should be recycled into - * the task pool instance from where it was allocated. - * Failure to do so will yield undefined results. A job should not be recycled twice. A job - * that was previously scheduled but not completed or canceled cannot be safely recycled. An attempt to do so will result - * in an @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. - * - * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create. - * @param[out] job A pointer to a job that was create with a call to @ref IotTaskPool_CreateJob. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_ILLEGAL_OPERATION - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - * @warning The `taskPool` used in this function should be the same - * used to create the job pointed to by `job`, or the results will be undefined. - * - * @warning Attempting to call this function on a statically allocated job will result in @ref IOT_TASKPOOL_ILLEGAL_OPERATION - * error. - * - * @warning This function should be used to recycle a job in the task pool cache when after the job executed. - * Failing to call either this function or @ref IotTaskPool_DestroyRecyclableJob will result is a memory leak. Statically - * allocated jobs do not need to be recycled or destroyed. - * - */ -/* @[declare_taskpool_recyclejob] */ -IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, - IotTaskPoolJob_t job ); -/* @[declare_taskpool_recyclejob] */ - -/** - * @brief This function schedules a job created with @ref IotTaskPool_CreateJob or @ref IotTaskPool_CreateRecyclableJob - * against the task pool pointed to by `taskPool`. - * - * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool - * library. - * - * @param[in] taskPool A handle to the task pool that must have been previously initialized with. - * a call to @ref IotTaskPool_Create. - * @param[in] job A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. - * @param[in] flags Flags to be passed by the user, e.g. to identify the job as high priority by specifying #IOT_TASKPOOL_JOB_HIGH_PRIORITY. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_ILLEGAL_OPERATION - * - #IOT_TASKPOOL_NO_MEMORY - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - * - * @note This function will not allocate memory, so it is guaranteed to succeed if the paramters are correct and the task pool - * was correctly initialized, and not yet destroyed. - * - * @warning The `taskPool` used in this function should be the same used to create the job pointed to by `job`, or the - * results will be undefined. - * - * Example - * @code{c} - * // An example of a user context to pass to a callback through a task pool thread. - * typedef struct JobUserContext - * { - * uint32_t counter; - * } JobUserContext_t; - * - * // An example of a user callback to invoke through a task pool thread. - * static void ExecutionCb( IotTaskPool_t taskPool, IotTaskPoolJob_t job, void * context ) - * { - * ( void )taskPool; - * ( void )job; - * - * JobUserContext_t * pUserContext = ( JobUserContext_t * )context; - * - * pUserContext->counter++; - * } - * - * void TaskPoolExample( ) - * { - * JobUserContext_t userContext = { 0 }; - * IotTaskPoolJob_t job; - * IotTaskPool_t taskPool; - * - * // Configure the task pool to hold at least two threads and three at the maximum. - * // Provide proper stack size and priority per the application needs. - * - * const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = 512, .priority = 0 }; - * - * // Create a task pool. - * IotTaskPool_Create( &tpInfo, &taskPool ); - * - * // Statically allocate one job, schedule it. - * IotTaskPool_CreateJob( &ExecutionCb, &userContext, &job ); - * - * IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, &job, 0 ); - * - * switch ( errorSchedule ) - * { - * case IOT_TASKPOOL_SUCCESS: - * break; - * case IOT_TASKPOOL_BAD_PARAMETER: // Invalid parameters, such as a NULL handle, can trigger this error. - * case IOT_TASKPOOL_ILLEGAL_OPERATION: // Scheduling a job that was previously scheduled or destroyed could trigger this error. - * case IOT_TASKPOOL_NO_MEMORY: // Scheduling a with flag #IOT_TASKPOOL_JOB_HIGH_PRIORITY could trigger this error. - * case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: // Scheduling a job after trying to destroy the task pool could trigger this error. - * // ASSERT - * break; - * default: - * // ASSERT - * } - * - * // - * // ... Perform other operations ... - * // - * - * IotTaskPool_Destroy( taskPool ); - * } - * @endcode - */ -/* @[declare_taskpool_schedule] */ -IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - uint32_t flags ); -/* @[declare_taskpool_schedule] */ - -/** - * @brief This function schedules a job created with @ref IotTaskPool_CreateJob against the task pool - * pointed to by `taskPool` to be executed after a user-defined time interval. - * - * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool - * library. - * - * @param[in] taskPool A handle to the task pool that must have been previously initialized with. - * a call to @ref IotTaskPool_Create. - * @param[in] job A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. - * @param[in] timeMs The time in milliseconds to wait before scheduling the job. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_ILLEGAL_OPERATION - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - * - * @note This function will not allocate memory. - * - * @warning The `taskPool` used in this function should be the same - * used to create the job pointed to by `job`, or the results will be undefined. - * - */ -/* @[declare_taskpool_scheduledeferred] */ -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - uint32_t timeMs ); -/* @[declare_taskpool_scheduledeferred] */ - -/** - * @brief This function retrieves the current status of a job. - * - * @param[in] taskPool A handle to the task pool that must have been previously initialized with - * a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * @param[in] job The job to cancel. - * @param[out] pStatus The status of the job at the time of cancellation. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - * @warning This function is not thread safe and the job status returned in `pStatus` may be invalid by the time - * the calling thread has a chance to inspect it. - */ -/* @[declare_taskpool_getstatus] */ -IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - IotTaskPoolJobStatus_t * const pStatus ); -/* @[declare_taskpool_getstatus] */ - -/** - * @brief This function tries to cancel a job that was previously scheduled with @ref IotTaskPool_Schedule. - * - * A job can be canceled only if it is not yet executing, i.e. if its status is - * @ref IOT_TASKPOOL_STATUS_READY or @ref IOT_TASKPOOL_STATUS_SCHEDULED. Calling - * @ref IotTaskPool_TryCancel on a job whose status is @ref IOT_TASKPOOL_STATUS_COMPLETED, - * or #IOT_TASKPOOL_STATUS_CANCELED will yield a #IOT_TASKPOOL_CANCEL_FAILED return result. - * - * @param[in] taskPool A handle to the task pool that must have been previously initialized with - * a call to @ref IotTaskPool_Create. - * @param[in] job The job to cancel. - * @param[out] pStatus The status of the job at the time of cancellation. - * - * @return One of the following: - * - #IOT_TASKPOOL_SUCCESS - * - #IOT_TASKPOOL_BAD_PARAMETER - * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS - * - #IOT_TASKPOOL_CANCEL_FAILED - * - * @warning The `taskPool` used in this function should be the same - * used to create the job pointed to by `job`, or the results will be undefined. - * - */ -/* @[declare_taskpool_trycancel] */ -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, - IotTaskPoolJob_t job, - IotTaskPoolJobStatus_t * const pStatus ); -/* @[declare_taskpool_trycancel] */ - -/** - * @brief Returns a pointer to the job storage from an instance of a job handle - * of type @ref IotTaskPoolJob_t. This function is guaranteed to succeed for a - * valid job handle. - * - * @param[in] job The job handle. - * - * @return A pointer to the storage associated with the job handle `job`. - * - * @warning If the `job` handle used is invalid, the results will be undefined. - */ -/* @[declare_taskpool_getjobstoragefromhandle] */ -IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t job ); -/* @[declare_taskpool_getjobstoragefromhandle] */ - -/** - * @brief Returns a string that describes an @ref IotTaskPoolError_t. - * - * Like the POSIX `strerror`, this function returns a string describing a - * return code. In this case, the return code is a task pool library error code, - * `status`. - * - * The string returned by this function MUST be treated as read-only: any - * attempt to modify its contents may result in a crash. Therefore, this function - * is limited to usage in logging. - * - * @param[in] status The status to describe. - * - * @return A read-only string that describes `status`. - * - * @warning The string returned by this function must never be modified. - */ -/* @[declare_taskpool_strerror] */ -const char * IotTaskPool_strerror( IotTaskPoolError_t status ); -/* @[declare_taskpool_strerror] */ - -/** - * @brief The maximum number of task pools to be created when using - * a memory pool. - */ -#ifndef IOT_TASKPOOLS -#define IOT_TASKPOOLS ( 4 ) -#endif - -/** - * @brief The maximum number of jobs to cache. - */ -#ifndef IOT_TASKPOOL_JOBS_RECYCLE_LIMIT - #define IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ( 8UL ) -#endif - -/** - * @brief The maximum timeout in milliseconds to wait for a job to be scheduled before waking up a worker thread. - * A worker thread that wakes up as a result of a timeout may exit to allow the task pool to fold back to its - * minimum number of threads. - */ -#ifndef IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS - #define IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS ( 60 * 1000UL ) -#endif - -#endif /* ifndef IOT_TASKPOOL_H_ */ diff --git a/libraries/standard/common/include/types/iot_taskpool_types.h b/libraries/standard/common/include/types/iot_taskpool_types.h deleted file mode 100644 index 6416968da0..0000000000 --- a/libraries/standard/common/include/types/iot_taskpool_types.h +++ /dev/null @@ -1,300 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_taskpool_types.h - * @brief Types of the task pool. - */ - -#ifndef IOT_TASKPOOL_TYPES_H_ -#define IOT_TASKPOOL_TYPES_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Platform types includes. */ -#include "types/iot_platform_types.h" - -/* Linear containers (lists and queues) include. */ -#include "iot_linear_containers.h" - -/*-------------------------- Task pool enumerated types --------------------------*/ - -/** - * @ingroup taskpool_datatypes_enums - * @brief Return codes of [task pool functions](@ref taskpool_functions). - */ -typedef enum IotTaskPoolError -{ - /** - * @brief Task pool operation completed successfully. - */ - IOT_TASKPOOL_SUCCESS = 0, - - /** - * @brief Task pool operation failed because at least one parameter is invalid. - */ - IOT_TASKPOOL_BAD_PARAMETER, - - /** - * @brief Task pool operation failed because it is illegal. - */ - IOT_TASKPOOL_ILLEGAL_OPERATION, - - /** - * @brief Task pool operation failed because allocating memory failed. - */ - IOT_TASKPOOL_NO_MEMORY, - - /** - * @brief Task pool operation failed because of an invalid parameter. - */ - IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS, - - /** - * @brief Task pool cancellation failed. - */ - IOT_TASKPOOL_CANCEL_FAILED, - - /** - * @brief Task pool operation general failure. - */ - IOT_TASKPOOL_GENERAL_FAILURE, -} IotTaskPoolError_t; - -/** - * @enums{taskpool,Task pool library} - */ - -/** - * @ingroup taskpool_datatypes_enums - * @brief Status codes of [task pool Job](@ref IotTaskPoolJob_t). - * - */ -typedef enum IotTaskPoolJobStatus -{ - /** - * @brief Job is ready to be scheduled. - * - */ - IOT_TASKPOOL_STATUS_READY = 0, - - /** - * @brief Job has been queued for execution. - * - */ - IOT_TASKPOOL_STATUS_SCHEDULED, - - /** - * @brief Job has been scheduled for deferred execution. - * - */ - IOT_TASKPOOL_STATUS_DEFERRED, - - /** - * @brief Job is executing. - * - */ - IOT_TASKPOOL_STATUS_COMPLETED, - - /** - * @brief Job has been canceled before executing. - * - */ - IOT_TASKPOOL_STATUS_CANCELED, - - /** - * @brief Job status is undefined. - * - */ - IOT_TASKPOOL_STATUS_UNDEFINED, -} IotTaskPoolJobStatus_t; - -/*------------------------- Task pool types and handles --------------------------*/ - -/** - * @ingroup taskpool_datatypes_handles - * @brief Opaque handle of a Task Pool instance. - * - * This type identifies a Task Pool instance, which is valid after a successful call - * to @ref taskpool_function_createsystemtaskpool or @ref taskpool_function_create. A - * variable of this type is passed as the first - * argument to [Task Pool library functions](@ref taskpool_functions) to identify which - * task pool that function acts on. - * - * A call to @ref taskpool_function_destroy makes a task pool handle invalid. Once - * @ref taskpool_function_destroy returns, the task handle should no longer - * be used. - * - * @initializer{IotTaskPool_t,IOT_TASKPOOL_INITIALIZER} - */ -typedef struct _taskPool * IotTaskPool_t; - -/** - * @ingroup taskpool_datatypes_structs - * @brief The job storage data structure provides the storage for a statically allocated Task Pool Job instance. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct IotTaskPoolJobStorage -{ - IotLink_t link; /**< @brief Placeholder. */ - void * dummy2; /**< @brief Placeholder. */ - void * dummy3; /**< @brief Placeholder. */ - uint32_t dummy4; /**< @brief Placeholder. */ - IotTaskPoolJobStatus_t status; /**< @brief Placeholder. */ -} IotTaskPoolJobStorage_t; - -/** - * @ingroup taskpool_datatypes_handles - * @brief Opaque handle of a Task Pool Job. - * - * This type identifies a Task Pool Job instance, which is valid after a successful call - * to @ref taskpool_function_createjob or @ref taskpool_function_createrecyclablejob. - * - * A call to @ref taskpool_function_recyclejob or @ref taskpool_function_destroyrecyclablejob makes a - * task pool job handle invalid. Once @ref taskpool_function_recyclejob or - * @ref taskpool_function_destroyrecyclablejob returns, the task job handle should no longer be used. - * - * @initializer{IotTaskPoolJob_t,IOT_TASKPOOL_JOB_INITIALIZER} - * - */ -typedef struct _taskPoolJob * IotTaskPoolJob_t; - -/*------------------------- Task pool parameter structs --------------------------*/ - -/** - * @ingroup taskpool_datatypes_functionpointers - * @brief Callback type for a user callback. - * - * This type identifies the user callback signature to execute a task pool job. This callback will be invoked - * by the task pool threads with the `pUserContext` parameter, as specified by the user when - * calling @ref IotTaskPool_Schedule. - * - */ -typedef void ( * IotTaskPoolRoutine_t )( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ); - -/** - * @ingroup taskpool_datatypes_paramstructs - * @brief Initialization information to create one task pool instance. - * - * @paramfor @ref taskpool_function_createsystemtaskpool @ref taskpool_function_create. - * - * Passed as an argument to @ref taskpool_function_create. - * - * @initializer{IotTaskPoolInfo_t,IOT_TASKPOOL_INFO_INITIALIZER} - */ -typedef struct IotTaskPoolInfo -{ - /** - * @brief Specifies the operating parameters for a task pool. - * - * @attention #IotTaskPoolInfo_t.minThreads MUST be at least 1. - * #IotTaskPoolInfo_t.maxThreads MUST be greater or equal to #IotTaskPoolInfo_t.minThreads. - * If the minimum number of threads is same as the maximum, then the task pool will not try and grow the - * number of worker threads at run time. - */ - - uint32_t minThreads; /**< @brief Minimum number of threads in a task pool. These threads will be created when the task pool is first created with @ref taskpool_function_create. */ - uint32_t maxThreads; /**< @brief Maximum number of threads in a task pool. A task pool may try and grow the number of active threads up to #IotTaskPoolInfo_t.maxThreads. */ - uint32_t stackSize; /**< @brief Stack size for every task pool thread. The stack size for each thread is fixed after the task pool is created and cannot be changed. */ - int32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ -} IotTaskPoolInfo_t; - -/*------------------------- TASKPOOL defined constants --------------------------*/ - -/** - * @constantspage{taskpool,task pool library} - * - * @section taskpool_constants_initializers Task pool Initializers - * @brief Provides default values for initializing the data types of the task pool library. - * - * @snippet this define_taskpool_initializers - * - * All user-facing data types of the task pool library can be initialized using - * one of the following. - * - * @warning Failure to initialize a task pool data type with the appropriate initializer - * may result in a runtime error! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - * - * Example - * @code{c} - * - * IotTaskPool_t * pTaskPool; - * - * const IotTaskPoolInfo_t tpInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; - * - * IotTaskPoolError_t error = IotTaskPool_Create( &tpInfo, &pTaskPool ); - * - * // Use the task pool - * // ... - * - * @endcode - * - */ -/* @[define_taskpool_initializers] */ -/** @brief Initializer for a small #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_SMALL { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } -/** @brief Initializer for a medium #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } -/** @brief Initializer for a large #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_LARGE { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } -/** @brief Initializer for a very large #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_XLARGE { .minThreads = 2, .maxThreads = 4, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } -/** @brief Initializer for a typical #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM -/** @brief Initializer for a #IotTaskPool_t. */ -#define IOT_TASKPOOL_INITIALIZER NULL -/** @brief Initializer for a #IotTaskPoolJobStorage_t. */ -#define IOT_TASKPOOL_JOB_STORAGE_INITIALIZER { { NULL, NULL }, NULL, NULL, 0, IOT_TASKPOOL_STATUS_UNDEFINED } -/** @brief Initializer for a #IotTaskPoolJob_t. */ -#define IOT_TASKPOOL_JOB_INITIALIZER NULL -/* @[define_taskpool_initializers] */ - -/** - * @brief Flag for scheduling a job to execute immediately, even if the maximum number of threads in the - * task pool was reached already. - * - * @warning This flag may cause the task pool to create a worker to serve the job immediately, and - * therefore using this flag may incur in additional memory usage and potentially fail scheduling the job. - */ -#define IOT_TASKPOOL_JOB_HIGH_PRIORITY ( ( uint32_t ) 0x00000001 ) - -/** - * @brief Allows the use of the handle to the system task pool. - * - * @warning The system task pool handle is not valid unless @ref IotTaskPool_CreateSystemTaskPool is - * called before the handle is used. - */ -#define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) - -#endif /* ifndef IOT_TASKPOOL_TYPES_H_ */ diff --git a/libraries/standard/common/lexicon.txt b/libraries/standard/common/lexicon.txt deleted file mode 100644 index e1354049cd..0000000000 --- a/libraries/standard/common/lexicon.txt +++ /dev/null @@ -1,133 +0,0 @@ -activejobs -activethreads -bool -cancelable -cas -config -const -coverity -createjob -createrecyclablejob -createsystemtaskpool -de -debuggability -dec -dequeuehead -dequeuetail -destroyrecyclablejob -dispatchqueue -dispatchsignal -endcond -endif -enqueuehead -enqueuetail -expirationtime -fifo -findfirstmatch -findfree -freecount -freelist -freemessagebuffer -genericprintbuffer -getjobstoragefromhandle -getstatus -getsystemtaskpool -hidelibraryname -hideloglevel -hidetimestring -html -http -iaddend -ifndef -inc -init -insertafter -insertbefore -inserthead -insertsorted -inserttail -int -iot -iotdequeue -iotlink -iotlistdouble -iotlog -iotmutex -iotsemaphore -iottaskpool -iottaskpoolinfo -iottaskpooljob -iottaskpooljobstatus -iottaskpooljobstorage -iottaskpoolroutine -iottimer -isempty -islinked -ismatch -isn -jobscache -malloc -mallocmessagebuffer -maxthreads -mem -messagebuffersize -min -minthreads -mutex -nand -ok -onlinepubs -opengroup -org -pbuffer -peekhead -peektail -pheader -pinusemessagebuffers -pinusetaskpooljobs -pinusetaskpools -pinusetaskpooltimerevents -pjob -plist -pmessagebuffer -pmessagebuffers -pnext -pobjects -ppool -pprevious -printbuffer -ptaskpool -ptaskpooljobs -ptaskpools -ptaskpooltimerevents -pusercontext -recyclejob -removeall -removeallmatches -removefirstmatch -removehead -removetail -returninuse -scheduledeferred -sdk -setmaxthreads -shouldn -stacksize -startstopsignal -stdout -strerror -struct -structs -synch -taskpool -taskpoolcache -taskpooljob -taskpools -taskpooltimerevent -timereventslist -timestring -trycancel -uaddend -uint -usercallback -xor diff --git a/libraries/standard/common/src/iot_init.c b/libraries/standard/common/src/iot_init.c deleted file mode 100644 index 9226bacad3..0000000000 --- a/libraries/standard/common/src/iot_init.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_init.c - * @brief Implements functions for common initialization and cleanup of this SDK. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Task pool include. */ -#include "iot_taskpool.h" - -/* Atomic include. */ -#include "iot_atomic.h" - -/* Static memory include (if dynamic memory allocation is disabled). */ -#include "iot_static_memory.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL -#else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE -#endif - -#define LIBRARY_LOG_NAME ( "INIT" ) -#include "iot_logging_setup.h" - -/*-----------------------------------------------------------*/ - -/* A mutex for critical sections is needed for the generic atomic implementation. */ -#if ( IOT_ATOMIC_GENERIC == 1 ) - /* Platform threads include. */ - #include "platform/iot_threads.h" - -/** - * @brief Provides critical sections. - */ - static IotMutex_t _atomicMutex; - -/*-----------------------------------------------------------*/ - - void Iot_EnterCritical( void ) - { - IotMutex_Lock( &_atomicMutex ); - } - -/*-----------------------------------------------------------*/ - - void Iot_ExitCritical( void ) - { - IotMutex_Unlock( &_atomicMutex ); - } - -#endif /* if ( IOT_ATOMIC_GENERIC == 1 ) */ - -/*-----------------------------------------------------------*/ - -bool IotSdk_Init( void ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; - - /* Initialize the mutex for atomic operations if needed. */ - #if ( IOT_ATOMIC_GENERIC == 1 ) - bool atomicInitialized = IotMutex_Create( &_atomicMutex, false ); - - if( atomicInitialized == false ) - { - IotLogError( "Failed to initialize atomic operations." ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - #endif - - /* Create system task pool. */ - taskPoolStatus = IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ); - - if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) - { - IotLogError( "Failed to create system task pool." ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status == false ) - { - #if IOT_ATOMIC_GENERIC == 1 - if( atomicInitialized == true ) - { - IotMutex_Destroy( &_atomicMutex ); - } - #endif - } - else - { - IotLogInfo( "SDK successfully initialized." ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -void IotSdk_Cleanup( void ) -{ - IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); - - /* This log message must be printed before static memory management is - * cleaned up. */ - IotLogInfo( "SDK cleanup done." ); - - /* Clean up the mutex for generic atomic operations if needed. */ - #if ( IOT_ATOMIC_GENERIC == 1 ) - IotMutex_Destroy( &_atomicMutex ); - #endif -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/common/src/iot_logging.c b/libraries/standard/common/src/iot_logging.c deleted file mode 100644 index 876dc4ffb7..0000000000 --- a/libraries/standard/common/src/iot_logging.c +++ /dev/null @@ -1,451 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_logging.c - * @brief Implementation of logging functions from iot_logging.h - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Platform clock include. */ -#include "platform/iot_clock.h" - -/* Logging includes. */ -#include "iot_logging.h" - -/*-----------------------------------------------------------*/ - -/* This implementation assumes the following values for the log level constants. - * Ensure that the values have not been modified. */ -#if IOT_LOG_NONE != 0 - #error "IOT_LOG_NONE must be 0." -#endif -#if IOT_LOG_ERROR != 1 - #error "IOT_LOG_ERROR must be 1." -#endif -#if IOT_LOG_WARN != 2 - #error "IOT_LOG_WARN must be 2." -#endif -#if IOT_LOG_INFO != 3 - #error "IOT_LOG_INFO must be 3." -#endif -#if IOT_LOG_DEBUG != 4 - #error "IOT_LOG_DEBUG must be 4." -#endif - -/** - * @def IotLogging_Puts( message ) - * @brief Function the logging library uses to print a line. - * - * This function can be set by using a define. By default, the standard library - * [puts](http://pubs.opengroup.org/onlinepubs/9699919799/functions/puts.html) - * function is used. - */ -#ifndef IotLogging_Puts - #define IotLogging_Puts puts -#endif - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - /* Static memory allocation header. */ - #include "iot_static_memory.h" - -/** - * @brief Allocate a new logging buffer. This function must have the same - * signature as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef IotLogging_Malloc - #define IotLogging_Malloc Iot_MallocMessageBuffer - #endif - -/** - * @brief Free a logging buffer. This function must have the same signature - * as [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef IotLogging_Free - #define IotLogging_Free Iot_FreeMessageBuffer - #endif - -/** - * @brief Get the size of a logging buffer. Statically-allocated buffers - * should all have the same size. - */ - #ifndef IotLogging_StaticBufferSize - #define IotLogging_StaticBufferSize Iot_MessageBufferSize - #endif -#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #ifndef IotLogging_Malloc - #include - #define IotLogging_Malloc malloc - #endif - - #ifndef IotLogging_Free - #include - #define IotLogging_Free free - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ - -/** - * @brief A guess of the maximum length of a timestring. - * - * There's no way for this logging library to know the length of a timestring - * before it's generated. Therefore, the logging library will assume a maximum - * length of any timestring it may get. This value should be generous enough - * to accommodate the vast majority of timestrings. - * - * @see @ref platform_clock_function_gettimestring - */ -#define MAX_TIMESTRING_LENGTH ( 64 ) - -/** - * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate - * `[]` and a null-terminator. - */ -#define MAX_LOG_LEVEL_LENGTH ( 8 ) - -/** - * @brief How many bytes @ref logging_function_genericprintbuffer should output on - * each line. - */ -#define BYTES_PER_LINE ( 16 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Lookup table for log levels. - * - * Converts one of the @ref logging_constants_levels to a string. - */ -static const char * const _pLogLevelStrings[ 5 ] = -{ - "", /* IOT_LOG_NONE */ - "ERROR", /* IOT_LOG_ERROR */ - "WARN ", /* IOT_LOG_WARN */ - "INFO ", /* IOT_LOG_INFO */ - "DEBUG" /* IOT_LOG_DEBUG */ -}; - -/*-----------------------------------------------------------*/ - -#if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) - static bool _reallocLoggingBuffer( void ** pOldBuffer, - size_t newSize, - size_t oldSize ) - { - bool status = false; - - /* Allocate a new, larger buffer. */ - void * pNewBuffer = IotLogging_Malloc( newSize ); - - /* Ensure that memory allocation succeeded. */ - if( pNewBuffer != NULL ) - { - /* Copy the data from the old buffer to the new buffer. */ - ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); - - /* Free the old buffer and update the pointer. */ - IotLogging_Free( *pOldBuffer ); - *pOldBuffer = pNewBuffer; - - status = true; - } - - return status; - } -#endif /* if !defined( IOT_STATIC_MEMORY_ONLY ) || ( IOT_STATIC_MEMORY_ONLY == 0 ) */ - -/*-----------------------------------------------------------*/ - -void IotLog_Generic( int32_t libraryLogSetting, - const char * const pLibraryName, - int32_t messageLevel, - const IotLogConfig_t * const pLogConfig, - const char * const pFormat, - ... ) -{ - int requiredMessageSize = 0; - size_t bufferSize = 0, - bufferPosition = 0, timestringLength = 0; - char * pLoggingBuffer = NULL; - va_list args; - - /* If the library's log level setting is lower than the message level, - * return without doing anything. */ - if( ( messageLevel == 0 ) || ( messageLevel > libraryLogSetting ) ) - { - return; - } - - if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) - { - /* Add length of log level if requested. */ - bufferSize += MAX_LOG_LEVEL_LENGTH; - } - - /* Estimate the amount of buffer needed for this log message. */ - if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) - { - /* Add size of library name if requested. Add 2 to accommodate "[]". */ - bufferSize += strlen( pLibraryName ) + 2; - } - - if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) - { - /* Add length of timestring if requested. */ - bufferSize += MAX_TIMESTRING_LENGTH; - } - - /* Add 64 as an initial (arbitrary) guess for the length of the message. */ - bufferSize += 64; - - /* In static memory mode, check that the log message will fit in the a - * static buffer. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - if( bufferSize >= IotLogging_StaticBufferSize() ) - { - /* If the static buffers are likely too small to fit the log message, - * return. */ - return; - } - - /* Otherwise, update the buffer size to the size of a static buffer. */ - bufferSize = IotLogging_StaticBufferSize(); - #endif - - /* Allocate memory for the logging buffer. */ - pLoggingBuffer = ( char * ) IotLogging_Malloc( bufferSize ); - - if( pLoggingBuffer == NULL ) - { - return; - } - - /* Print the message log level if requested. */ - if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) - { - /* Ensure that message level is valid. */ - if( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ) - { - /* Add the log level string to the logging buffer. */ - requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - "[%s]", - _pLogLevelStrings[ messageLevel ] ); - - /* Check for encoding errors. */ - if( requiredMessageSize <= 0 ) - { - IotLogging_Free( pLoggingBuffer ); - - return; - } - - /* Update the buffer position. */ - bufferPosition += ( size_t ) requiredMessageSize; - } - } - - /* Print the library name if requested. */ - if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) - { - /* Add the library name to the logging buffer. */ - requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - "[%s]", - pLibraryName ); - - /* Check for encoding errors. */ - if( requiredMessageSize <= 0 ) - { - IotLogging_Free( pLoggingBuffer ); - - return; - } - - /* Update the buffer position. */ - bufferPosition += ( size_t ) requiredMessageSize; - } - - /* Print the timestring if requested. */ - if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) - { - /* Add the opening '[' enclosing the timestring. */ - pLoggingBuffer[ bufferPosition ] = '['; - bufferPosition++; - - /* Generate the timestring and add it to the buffer. */ - if( IotClock_GetTimestring( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - ×tringLength ) == true ) - { - /* If the timestring was successfully generated, add the closing "]". */ - bufferPosition += timestringLength; - pLoggingBuffer[ bufferPosition ] = ']'; - bufferPosition++; - } - else - { - /* Sufficient memory for a timestring should have been allocated. A timestring - * probably failed to generate due to a clock read error; remove the opening '[' - * from the logging buffer. */ - bufferPosition--; - pLoggingBuffer[ bufferPosition ] = '\0'; - } - } - - /* Add a padding space between the last closing ']' and the message, unless - * the logging buffer is empty. */ - if( bufferPosition > 0 ) - { - pLoggingBuffer[ bufferPosition ] = ' '; - bufferPosition++; - } - - va_start( args, pFormat ); - - /* Add the log message to the logging buffer. */ - requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - pFormat, - args ); - - va_end( args ); - - /* If the logging buffer was too small to fit the log message, reallocate - * a larger logging buffer. */ - if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition ) - { - #if IOT_STATIC_MEMORY_ONLY == 1 - - /* There's no point trying to allocate a larger static buffer. Return - * immediately. */ - IotLogging_Free( pLoggingBuffer ); - - return; - #else - if( _reallocLoggingBuffer( ( void ** ) &pLoggingBuffer, - ( size_t ) requiredMessageSize + bufferPosition + 1, - bufferSize ) == false ) - { - /* If buffer reallocation failed, return. */ - IotLogging_Free( pLoggingBuffer ); - - return; - } - - /* Reallocation successful, update buffer size. */ - bufferSize = ( size_t ) requiredMessageSize + bufferPosition + 1; - - /* Add the log message to the buffer. Now that the buffer has been - * reallocated, this should succeed. */ - va_start( args, pFormat ); - requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - pFormat, - args ); - va_end( args ); - #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ - } - - /* Check for encoding errors. */ - if( requiredMessageSize <= 0 ) - { - IotLogging_Free( pLoggingBuffer ); - - return; - } - - /* Print the logging buffer to stdout. */ - IotLogging_Puts( pLoggingBuffer ); - - /* Free the logging buffer. */ - IotLogging_Free( pLoggingBuffer ); -} - -/*-----------------------------------------------------------*/ - -void IotLog_GenericPrintBuffer( const char * const pLibraryName, - const char * const pHeader, - const uint8_t * const pBuffer, - size_t bufferSize ) -{ - size_t i = 0, offset = 0; - - /* Allocate memory to hold each line of the log message. Since each byte - * of pBuffer is printed in 4 characters (2 digits, a space, and a null- - * terminator), the size of each line is 4 * BYTES_PER_LINE. */ - char * pMessageBuffer = IotLogging_Malloc( 4 * BYTES_PER_LINE ); - - /* Exit if no memory is available. */ - if( pMessageBuffer == NULL ) - { - return; - } - - /* Print pHeader before printing pBuffer. */ - if( pHeader != NULL ) - { - IotLog_Generic( IOT_LOG_DEBUG, - pLibraryName, - IOT_LOG_DEBUG, - NULL, - pHeader ); - } - - /* Print each byte in pBuffer. */ - for( i = 0; i < bufferSize; i++ ) - { - /* Print a line if BYTES_PER_LINE is reached. But don't print a line - * at the beginning (when i=0). */ - if( ( i % BYTES_PER_LINE == 0 ) && ( i != 0 ) ) - { - IotLogging_Puts( pMessageBuffer ); - - /* Reset offset so that pMessageBuffer is filled from the beginning. */ - offset = 0; - } - - /* Print a single byte into pMessageBuffer. */ - ( void ) snprintf( pMessageBuffer + offset, 4, "%02x ", pBuffer[ i ] ); - - /* Move the offset where the next character is printed. */ - offset += 3; - } - - /* Print the final line of bytes. This line isn't printed by the for-loop above. */ - IotLogging_Puts( pMessageBuffer ); - - /* Free memory used by this function. */ - IotLogging_Free( pMessageBuffer ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/common/src/iot_static_memory_common.c b/libraries/standard/common/src/iot_static_memory_common.c deleted file mode 100644 index 003e3358c9..0000000000 --- a/libraries/standard/common/src/iot_static_memory_common.c +++ /dev/null @@ -1,175 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_static_memory_common.c - * @brief Implementation of common static memory functions in iot_static_memory.h - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include -#include -#include - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* Static memory include. */ -#include "iot_static_memory.h" - -/* Atomic include. */ -#include "iot_atomic.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef IOT_MESSAGE_BUFFERS - #define IOT_MESSAGE_BUFFERS ( 8 ) -#endif -#ifndef IOT_MESSAGE_BUFFER_SIZE - #define IOT_MESSAGE_BUFFER_SIZE ( 1024 ) -#endif -/** @endcond */ - -/* Validate static memory configuration settings. */ -#if IOT_MESSAGE_BUFFERS <= 0 - #error "IOT_MESSAGE_BUFFERS cannot be 0 or negative." -#endif -#if IOT_MESSAGE_BUFFER_SIZE <= 0 - #error "IOT_MESSAGE_BUFFER_SIZE cannot be 0 or negative." -#endif - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static uint32_t _pInUseMessageBuffers[ IOT_MESSAGE_BUFFERS ] = { 0U }; /**< @brief Message buffer in-use flags. */ -static char _pMessageBuffers[ IOT_MESSAGE_BUFFERS ][ IOT_MESSAGE_BUFFER_SIZE ] = { { 0 } }; /**< @brief Message buffers. */ - -/*-----------------------------------------------------------*/ - -int32_t IotStaticMemory_FindFree( uint32_t * pInUse, - size_t limit ) -{ - size_t i = 0; - int32_t freeIndex = -1; - - for( i = 0; i < limit; i++ ) - { - if( Atomic_CompareAndSwap_u32( &( pInUse[ i ] ), 1U, 0U ) == 1U ) - { - freeIndex = ( int32_t ) i; - break; - } - } - - return freeIndex; -} - -/*-----------------------------------------------------------*/ - -void IotStaticMemory_ReturnInUse( void * ptr, - void * pPool, - uint32_t * pInUse, - size_t limit, - size_t elementSize ) -{ - size_t i = 0; - uint8_t * pElement = NULL; - - /* Clear memory region at this address. */ - ( void ) memset( ptr, 0x00, elementSize ); - - for( i = 0; i < limit; i++ ) - { - /* Calculate address of the i-th element in pPool. */ - pElement = ( ( uint8_t * ) pPool ) + elementSize * i; - - /* Check for a match. */ - if( pElement == ptr ) - { - if( Atomic_CompareAndSwap_u32( &( pInUse[ i ] ), 0, 1 ) == 1 ) - { - break; - } - } - } -} - -/*-----------------------------------------------------------*/ - -size_t Iot_MessageBufferSize( void ) -{ - return ( size_t ) IOT_MESSAGE_BUFFER_SIZE; -} - -/*-----------------------------------------------------------*/ - -void * Iot_MallocMessageBuffer( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewBuffer = NULL; - - /* Check that size is within the fixed message buffer size. */ - if( size <= IOT_MESSAGE_BUFFER_SIZE ) - { - /* Get the index of a free message buffer. */ - freeIndex = IotStaticMemory_FindFree( _pInUseMessageBuffers, - IOT_MESSAGE_BUFFERS ); - - if( freeIndex != -1 ) - { - pNewBuffer = &( _pMessageBuffers[ freeIndex ][ 0 ] ); - } - } - - return pNewBuffer; -} - -/*-----------------------------------------------------------*/ - -void Iot_FreeMessageBuffer( void * ptr ) -{ - /* Return the in-use message buffer. */ - IotStaticMemory_ReturnInUse( ptr, - _pMessageBuffers, - _pInUseMessageBuffers, - IOT_MESSAGE_BUFFERS, - IOT_MESSAGE_BUFFER_SIZE ); -} - -/*-----------------------------------------------------------*/ - -#endif diff --git a/libraries/standard/common/src/iot_taskpool.c b/libraries/standard/common/src/iot_taskpool.c deleted file mode 100644 index d21faa6da9..0000000000 --- a/libraries/standard/common/src/iot_taskpool.c +++ /dev/null @@ -1,1853 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_taskpool.c - * @brief Implements the task pool functions in iot_taskpool.h - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include -#include - -/* Platform layer includes. */ -#include "platform/iot_threads.h" -#include "platform/iot_clock.h" - -/* Task pool internal include. */ -#include "private/iot_taskpool_internal.h" - -/** - * @brief Enter a critical section by locking a mutex. - * - */ -#define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( pTaskPool->lock ) ) - -/** - * @brief Exit a critical section by unlocking a mutex. - * - */ -#define TASKPOOL_EXIT_CRITICAL() IotMutex_Unlock( &( pTaskPool->lock ) ) - -/** - * @brief Maximum semaphore value for wait operations. - */ -#define TASKPOOL_MAX_SEM_VALUE 0xFFFF - -/** - * @brief Reschedule delay in milliseconds for deferred jobs. - */ -#define TASKPOOL_JOB_RESCHEDULE_DELAY_MS ( 10ULL ) - -/* ---------------------------------------------------------------------------------- */ - -/** - * Doxygen should ignore this section. - * - * @brief The system task pool handle for all libraries to use. - * User application can use the system task pool as well knowing that the usage will be shared with - * the system libraries as well. The system task pool needs to be initialized before any library is used or - * before any code that posts jobs to the task pool runs. - */ -_taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER }; - -/* -------------- Convenience functions to create/recycle/destroy jobs -------------- */ - -/** - * @brief Initializes one instance of a Task pool cache. - * - * @param[in] pCache The pre-allocated instance of the cache to initialize. - */ -static void _initJobsCache( _taskPoolCache_t * const pCache ); - -/** - * @brief Initialize a job. - * - * @param[in] pJob The job to initialize. - * @param[in] userCallback The user callback for the job. - * @param[in] pUserContext The context tp be passed to the callback. - * @param[in] isStatic A flag to indicate whether the job is statically or dynamically allocated. - */ -static void _initializeJob( _taskPoolJob_t * const pJob, - IotTaskPoolRoutine_t userCallback, - void * pUserContext, - bool isStatic ); - -/** - * @brief Extracts and initializes one instance of a job from the cache or, if there is none available, it allocates and initialized a new one. - * - * @param[in] pCache The instance of the cache to extract the job from. - */ -static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache ); - -/** - * Recycles one instance of a job into the cache or, if the cache is full, it destroys it. - * - * @param[in] pCache The instance of the cache to recycle the job into. - * @param[in] pJob The job to recycle. - * - */ -static void _recycleJob( _taskPoolCache_t * const pCache, - _taskPoolJob_t * const pJob ); - -/** - * Destroys one instance of a job. - * - * @param[in] pJob The job to destroy. - * - */ -static void _destroyJob( _taskPoolJob_t * const pJob ); - -/* -------------- The worker thread procedure for a task pool thread -------------- */ - -/** - * The procedure for a task pool worker thread. - * - * @param[in] pUserContext The user context. - * - */ -static void _taskPoolWorker( void * pUserContext ); - -/* -------------- Convenience functions to handle timer events -------------- */ - -/** - * Comparer for the time list. - * - * param[in] pTimerEventLink1 The link to the first timer event. - * param[in] pTimerEventLink1 The link to the first timer event. - */ -static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, - const IotLink_t * const pTimerEventLink2 ); - -/** - * Reschedules the timer for handling deferred jobs to the next timeout. - * - * param[in] pTimer The timer to reschedule. - * param[in] pFirstTimerEvent The timer event that carries the timeout and job information. - */ -static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, - _taskPoolTimerEvent_t * const pFirstTimerEvent ); - -/** - * The task pool timer procedure for scheduling deferred jobs. - * - * param[in] pArgument An opaque pointer for timer procedure context. - */ -static void _timerThread( void * pArgument ); - -/* -------------- Convenience functions to create/initialize/destroy the task pool -------------- */ - -/** - * Parameter validation for a task pool initialization. - * - * @param[in] pInfo The initialization information for the task pool. - * - */ -static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ); - -/** - * Initializes a pre-allocated instance of a task pool. - * - * @param[in] pInfo The initialization information for the task pool. - * @param[in] pTaskPool The pre-allocated instance of the task pool to initialize. - * - */ -static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, - _taskPool_t * const pTaskPool ); - -/** - * Initializes a pre-allocated instance of a task pool. - * - * @param[in] pInfo The initialization information for the task pool. - * @param[out] pTaskPool A pointer to the task pool data structure to initialize. - * - */ -static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, - _taskPool_t * const pTaskPool ); - -/** - * Destroys one instance of a task pool. - * - * @param[in] pTaskPool The task pool to destroy. - * - */ -static void _destroyTaskPool( _taskPool_t * const pTaskPool ); - -/** - * Check for the exit condition. - * - * @param[in] pTaskPool The task pool to destroy. - * - */ -static bool _IsShutdownStarted( const _taskPool_t * const pTaskPool ); - -/** - * Set the exit condition. - * - * @param[in] pTaskPool The task pool to destroy. - * @param[in] threads The number of threads active in the task pool at shutdown time. - * - */ -static void _signalShutdown( _taskPool_t * const pTaskPool, - uint32_t threads ); - -/** - * Places a job in the dispatch queue. - * - * @param[in] pTaskPool The task pool to schedule the job with. - * @param[in] pJob The job to schedule. - * @param[in] flags The job flags. - * - */ -static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, - _taskPoolJob_t * const pJob, - uint32_t flags ); - -/** - * Matches a deferred job in the timer queue with its timer event wrapper. - * - * @param[in] pLink A pointer to the timer event link in the timer queue. - * @param[in] pMatch A pointer to the job to match. - * - */ -static bool _matchJobByPointer( const IotLink_t * const pLink, - void * pMatch ); - -/** - * Tries to cancel a job. - * - * @param[in] pTaskPool The task pool to cancel an operation against. - * @param[in] pJob The job to cancel. - * @param[out] pStatus The status of the job at the time of cancellation. - * - */ -static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, - _taskPoolJob_t * const pJob, - IotTaskPoolJobStatus_t * const pStatus ); - -/** - * Try to safely cancel and/or remove a job from the cache when the user calls API out of order. - * - * @param[in] pTaskPool The task pool to safely extract a job from. - * @param[in] pJob The job to extract. - * @param[in] atCompletion A flag to indicate whether the job is being scheduled or - * was completed already. - * - */ -static IotTaskPoolError_t _trySafeExtraction( _taskPool_t * const pTaskPool, - _taskPoolJob_t * const pJob, - bool atCompletion ); - -/* ---------------------------------------------------------------------------------------------- */ - -IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ) -{ - return &_IotSystemTaskPool; -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * pInfo ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - /* Parameter checking. */ - TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) ); - - /* Create the system task pool pool. */ - TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, &_IotSystemTaskPool ) ); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, - IotTaskPool_t * const pTaskPool ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - _taskPool_t * pTempTaskPool = NULL; - - /* Verify that the task pool storage is valid. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - - /* Parameter checking. */ - TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) ); - - /* Allocate the memory for the task pool */ - pTempTaskPool = ( _taskPool_t * ) IotTaskPool_MallocTaskPool( sizeof( _taskPool_t ) ); - - if( pTempTaskPool == NULL ) - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - memset( pTempTaskPool, 0x00, sizeof( _taskPool_t ) ); - - TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTempTaskPool ) ); - - TASKPOOL_FUNCTION_CLEANUP(); - - if( TASKPOOL_FAILED( status ) ) - { - if( pTempTaskPool != NULL ) - { - IotTaskPool_FreeTaskPool( pTempTaskPool ); - } - } - else - { - *pTaskPool = pTempTaskPool; - } - - TASKPOOL_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t taskPoolHandle ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - uint32_t count; - bool completeShutdown = true; - - _taskPool_t * pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - /* Track how many threads the task pool owns. */ - uint32_t activeThreads; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - - /* Destroying the task pool should be safe, and therefore we will grab the task pool lock. - * No worker thread or application thread should access any data structure - * in the task pool while the task pool is being destroyed. */ - TASKPOOL_ENTER_CRITICAL(); - { - IotLink_t * pItemLink; - - /* Record how many active threads in the task pool. */ - activeThreads = pTaskPool->activeThreads; - - /* Destroying a Task pool happens in six (6) stages: First, (1) we clear the job queue and (2) the timer queue. - * Then (3) we clear the jobs cache. We will then (4) wait for all worker threads to signal exit, - * before (5) setting the exit condition and wake up all active worker threads. Finally (6) destroying - * all task pool data structures and release the associated memory. - */ - - /* (1) Clear the job queue. */ - do - { - pItemLink = NULL; - - pItemLink = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); - - if( pItemLink != NULL ) - { - _taskPoolJob_t * pJob = IotLink_Container( _taskPoolJob_t, pItemLink, link ); - - _destroyJob( pJob ); - } - } while( pItemLink ); - - /* (2) Clear the timer queue. */ - { - _taskPoolTimerEvent_t * pTimerEvent; - - /* A deferred job may have fired already. Since deferred jobs will go through the same mutex - * the shutdown sequence is holding at this stage, there is no risk for race conditions. Yet, we - * need to let the deferred job to destroy the task pool. */ - - pItemLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); - - if( pItemLink != NULL ) - { - uint64_t now = IotClock_GetTimeMs(); - - pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pItemLink, link ); - - if( pTimerEvent->expirationTime <= now ) - { - IotLogDebug( "Shutdown will be deferred to the timer thread" ); - - /* Timer may have fired already! Let the timer thread destroy - * complete the taskpool destruction sequence. */ - completeShutdown = false; - } - - /* Remove all timers from the timeout list. */ - for( ; ; ) - { - pItemLink = IotListDouble_RemoveHead( &pTaskPool->timerEventsList ); - - if( pItemLink == NULL ) - { - break; - } - - pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pItemLink, link ); - - _destroyJob( pTimerEvent->pJob ); - - IotTaskPool_FreeTimerEvent( pTimerEvent ); - } - } - } - - /* (3) Clear the job cache. */ - do - { - pItemLink = NULL; - - pItemLink = IotListDouble_RemoveHead( &pTaskPool->jobsCache.freeList ); - - if( pItemLink != NULL ) - { - _taskPoolJob_t * pJob = IotLink_Container( _taskPoolJob_t, pItemLink, link ); - - _destroyJob( pJob ); - } - } while( pItemLink ); - - /* (4) Set the exit condition. */ - _signalShutdown( pTaskPool, activeThreads ); - } - TASKPOOL_EXIT_CRITICAL(); - - /* (5) Wait for all active threads to reach the end of their life-span. */ - for( count = 0; count < activeThreads; ++count ) - { - IotSemaphore_Wait( &pTaskPool->startStopSignal ); - } - - IotTaskPool_Assert( IotSemaphore_GetCount( &pTaskPool->startStopSignal ) == 0 ); - IotTaskPool_Assert( pTaskPool->activeThreads == 0 ); - - /* (6) Destroy all signaling objects. */ - if( completeShutdown == true ) - { - _destroyTaskPool( pTaskPool ); - - /* Do not free the system task pool which is statically allocated. */ - if( pTaskPool != &_IotSystemTaskPool ) - { - IotTaskPool_FreeTaskPool( pTaskPool ); - } - } - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPoolHandle, - uint32_t maxThreads ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - uint32_t count, i; - - _taskPool_t * pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); - TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( maxThreads < 1UL ); - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); - } - - uint32_t previousMaxThreads = pTaskPool->maxThreads; - - /* Reset the max threads counter. */ - pTaskPool->maxThreads = maxThreads; - - count = previousMaxThreads - maxThreads; - - /* If the number of maximum threads in the pool is set to be smaller than the current value, - * then we need to signal all redundant threads to exit. - */ - if( maxThreads < previousMaxThreads ) - { - IotLogDebug( "Setting max threads caused %d thread to exit.", count ); - - i = count; - - while( i > 0UL ) - { - IotSemaphore_Post( &pTaskPool->dispatchSignal ); - - --i; - } - } - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJobStorage_t * const pJobStorage, - IotTaskPoolJob_t * const ppJob ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobStorage ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); - - /* Build a job around the user-provided storage. */ - _initializeJob( ( _taskPoolJob_t * ) pJobStorage, userCallback, pUserContext, true ); - - *ppJob = ( IotTaskPoolJob_t ) pJobStorage; - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle, - IotTaskPoolRoutine_t userCallback, - void * pUserContext, - IotTaskPoolJob_t * const ppJob ) -{ - _taskPool_t * pTaskPool = NULL; - - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - { - _taskPoolJob_t * pTempJob = NULL; - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); - } - - pTempJob = _fetchOrAllocateJob( &pTaskPool->jobsCache ); - } - TASKPOOL_EXIT_CRITICAL(); - - if( pTempJob == NULL ) - { - IotLogInfo( "Failed to allocate a job." ); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - _initializeJob( pTempJob, userCallback, pUserContext, false ); - - *ppJob = pTempJob; - } - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPoolHandle, - IotTaskPoolJob_t pJobHandle ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _taskPool_t * pTaskPool = NULL; - _taskPoolJob_t * pJob1 = NULL; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobHandle ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - pJob1 = ( _taskPoolJob_t * ) pJobHandle; - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; - } - /* Do not destroy statically allocated jobs. */ - else if( ( pJob1->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == IOT_TASK_POOL_INTERNAL_STATIC ) - { - IotLogWarn( "Attempt to destroy a statically allocated job." ); - - status = IOT_TASKPOOL_ILLEGAL_OPERATION; - } - else - { - status = _trySafeExtraction( pTaskPool, pJob1, true ); - } - } - TASKPOOL_EXIT_CRITICAL(); - - if( TASKPOOL_SUCCEEDED( status ) ) - { - /* At this point, the job must not be in any queue or list. */ - IotTaskPool_Assert( IotLink_IsLinked( &pJob1->link ) == false ); - - _destroyJob( pJob1 ); - } - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPoolHandle, - IotTaskPoolJob_t pJob ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _taskPool_t * pTaskPool = NULL; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; - } - /* Do not recycle statically allocated jobs. */ - else if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0UL ) - { - status = _trySafeExtraction( pTaskPool, pJob, true ); - } - else - { - IotLogWarn( "Attempt to recycle a statically allocated job." ); - - status = IOT_TASKPOOL_ILLEGAL_OPERATION; - } - - /* If all safety checks completed, proceed. */ - if( TASKPOOL_SUCCEEDED( status ) ) - { - /* At this point, the job must not be in any queue or list. */ - IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); - - _recycleJob( &pTaskPool->jobsCache, pJob ); - } - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPoolHandle, - IotTaskPoolJob_t pJob, - uint32_t flags ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _taskPool_t * pTaskPool = NULL; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; - } - else - { - status = _trySafeExtraction( pTaskPool, pJob, false ); - } - - /* If all safety checks completed, proceed. */ - if( TASKPOOL_SUCCEEDED( status ) ) - { - status = _scheduleInternal( pTaskPool, pJob, flags ); - } - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, - IotTaskPoolJob_t pJob, - uint32_t timeMs ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _taskPool_t * pTaskPool = NULL; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - if( timeMs == 0UL ) - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); - } - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); - } - - /* If all safety checks completed, proceed. */ - if( TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, false ) ) ) - { - IotLink_t * pTimerEventLink; - uint64_t now; - - _taskPoolTimerEvent_t * pTimerEvent = ( _taskPoolTimerEvent_t * ) IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); - - if( pTimerEvent == NULL ) - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - memset( pTimerEvent, 0x00, sizeof( _taskPoolTimerEvent_t ) ); - - now = IotClock_GetTimeMs(); - - pTimerEvent->link.pNext = NULL; - pTimerEvent->link.pPrevious = NULL; - pTimerEvent->expirationTime = now + timeMs; - pTimerEvent->pJob = ( _taskPoolJob_t * ) pJob; - - /* Append the timer event to the timer list. */ - IotListDouble_InsertSorted( &pTaskPool->timerEventsList, &pTimerEvent->link, _timerEventCompare ); - - /* Update the job status to 'scheduled'. */ - pJob->status = IOT_TASKPOOL_STATUS_DEFERRED; - - /* Peek the first event in the timer event list. There must be at least one, - * since we just inserted it. */ - pTimerEventLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); - IotTaskPool_Assert( pTimerEventLink != NULL ); - - /* If the event we inserted is at the front of the queue, then - * we need to reschedule the underlying timer. */ - if( pTimerEventLink == &pTimerEvent->link ) - { - pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ); - - _rescheduleDeferredJobsTimer( &pTaskPool->timer, pTimerEvent ); - } - } - else - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); - } - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPoolHandle, - IotTaskPoolJob_t pJob, - IotTaskPoolJobStatus_t * const pStatus ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _taskPool_t * pTaskPool = NULL; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; - - TASKPOOL_ENTER_CRITICAL(); - { - /* Bail out early if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); - } - - *pStatus = pJob->status; - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle, - IotTaskPoolJob_t pJob, - IotTaskPoolJobStatus_t * const pStatus ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - _taskPool_t * pTaskPool = NULL; - - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - - pTaskPool = ( _taskPool_t * ) taskPoolHandle; - - if( pStatus != NULL ) - { - *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; - } - - TASKPOOL_ENTER_CRITICAL(); - { - /* Check again if this task pool is shutting down. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ); - } - - status = _tryCancelInternal( pTaskPool, pJob, pStatus ); - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t pJob ) -{ - return ( IotTaskPoolJobStorage_t * ) pJob; -} - -/*-----------------------------------------------------------*/ - -const char * IotTaskPool_strerror( IotTaskPoolError_t status ) -{ - const char * pMessage = NULL; - - switch( status ) - { - case IOT_TASKPOOL_SUCCESS: - pMessage = "SUCCESS"; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - pMessage = "BAD PARAMETER"; - break; - - case IOT_TASKPOOL_ILLEGAL_OPERATION: - pMessage = "ILLEGAL OPERATION"; - break; - - case IOT_TASKPOOL_NO_MEMORY: - pMessage = "NO MEMORY"; - break; - - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - pMessage = "SHUTDOWN IN PROGRESS"; - break; - - case IOT_TASKPOOL_CANCEL_FAILED: - pMessage = "CANCEL FAILED"; - break; - - case IOT_TASKPOOL_GENERAL_FAILURE: - pMessage = "GENERAL FAILURE"; - break; - - default: - pMessage = "INVALID STATUS"; - break; - } - - return pMessage; -} - -/* ---------------------------------------------------------------------------------------------- */ -/* ---------------------------------------------------------------------------------------------- */ -/* ---------------------------------------------------------------------------------------------- */ - -static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - /* Check input values for consistency. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo ); - TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads ); - TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < 1UL ); - TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < 1UL ); - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, - _taskPool_t * const pTaskPool ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - bool semStartStopInit = false; - bool lockInit = false; - bool semDispatchInit = false; - bool timerInit = false; - - /* Zero out all data structures. */ - memset( ( void * ) pTaskPool, 0x00, sizeof( IotTaskPool_t ) ); - - /* Initialize a job data structures that require no de-initialization. - * All other data structures carry a value of 'NULL' before initialization. - */ - IotDeQueue_Create( &pTaskPool->dispatchQueue ); - IotListDouble_Create( &pTaskPool->timerEventsList ); - - pTaskPool->minThreads = pInfo->minThreads; - pTaskPool->maxThreads = pInfo->maxThreads; - pTaskPool->stackSize = pInfo->stackSize; - pTaskPool->priority = pInfo->priority; - - _initJobsCache( &pTaskPool->jobsCache ); - - /* Initialize the semaphore to ensure all threads have started. */ - if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, TASKPOOL_MAX_SEM_VALUE ) == true ) - { - semStartStopInit = true; - - if( IotMutex_Create( &pTaskPool->lock, true ) == true ) - { - lockInit = true; - - /* Initialize the semaphore for waiting for incoming work. */ - if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, TASKPOOL_MAX_SEM_VALUE ) == true ) - { - semDispatchInit = true; - - /* Create the timer mutex for a new connection. */ - if( IotClock_TimerCreate( &( pTaskPool->timer ), _timerThread, pTaskPool ) == true ) - { - timerInit = true; - } - else - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - } - else - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - } - else - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - } - else - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - TASKPOOL_FUNCTION_CLEANUP(); - - if( TASKPOOL_FAILED( status ) ) - { - if( semStartStopInit == true ) - { - IotSemaphore_Destroy( &pTaskPool->startStopSignal ); - } - - if( lockInit == true ) - { - IotMutex_Destroy( &pTaskPool->lock ); - } - - if( semDispatchInit == true ) - { - IotSemaphore_Destroy( &pTaskPool->dispatchSignal ); - } - - if( timerInit == true ) - { - IotClock_TimerDestroy( &pTaskPool->timer ); - } - } - - TASKPOOL_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, - _taskPool_t * const pTaskPool ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - uint32_t count; - uint32_t threadsCreated = 0; - bool controlInit = false, threadCreated = false; - - /* Initialize all internal data structure prior to creating all threads. */ - TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); - - controlInit = true; - - IotTaskPool_Assert( pInfo->minThreads == pTaskPool->minThreads ); - IotTaskPool_Assert( pInfo->maxThreads == pTaskPool->maxThreads ); - - /* The task pool will initialize the minimum number of threads requested by the user upon start. */ - /* When a thread is created, it will signal a semaphore to signify that it is about to wait on incoming */ - /* jobs. A thread can be woken up for exit or for new jobs only at that point in time. */ - /* The exit condition is setting the maximum number of threads to 0. */ - - TASKPOOL_ENTER_CRITICAL(); - { - /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ - for( ; threadsCreated < pTaskPool->minThreads; ) - { - /* Create one thread. */ - threadCreated = Iot_CreateDetachedThread( _taskPoolWorker, - pTaskPool, - pTaskPool->priority, - pTaskPool->stackSize ); - - if( threadCreated == true ) - { - /* Upon successful thread creation, increase the number of active threads. */ - pTaskPool->activeThreads++; - } - else - { - TASKPOOL_EXIT_CRITICAL(); - - IotLogError( "Could not create worker thread! Exiting..." ); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - ++threadsCreated; - } - } - TASKPOOL_EXIT_CRITICAL(); - - TASKPOOL_FUNCTION_CLEANUP(); - - /* Wait for threads to be ready to wait on the condition, so that threads are actually able to receive messages. */ - for( count = 0; count < threadsCreated; ++count ) - { - IotSemaphore_Wait( &pTaskPool->startStopSignal ); - } - - /* In case of failure, wait on the created threads to exit. */ - if( TASKPOOL_FAILED( status ) ) - { - /* Set the exit condition for the newly created threads. */ - _signalShutdown( pTaskPool, threadsCreated ); - - /* Signal all threads to exit. */ - for( count = 0; count < threadsCreated; ++count ) - { - IotSemaphore_Wait( &pTaskPool->startStopSignal ); - } - - if( controlInit == true ) - { - _destroyTaskPool( pTaskPool ); - } - } - - TASKPOOL_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -static void _destroyTaskPool( _taskPool_t * const pTaskPool ) -{ - IotClock_TimerDestroy( &pTaskPool->timer ); - IotSemaphore_Destroy( &pTaskPool->dispatchSignal ); - IotSemaphore_Destroy( &pTaskPool->startStopSignal ); - IotMutex_Destroy( &pTaskPool->lock ); -} - -/* ---------------------------------------------------------------------------------------------- */ - -static void _taskPoolWorker( void * pUserContext ) -{ - IotTaskPool_Assert( pUserContext != NULL ); - - IotTaskPoolRoutine_t userCallback = NULL; - bool running = true; - - /* Extract pTaskPool pointer from context. */ - _taskPool_t * pTaskPool = ( _taskPool_t * ) pUserContext; - - /* Signal that this worker completed initialization and it is ready to receive notifications. */ - IotSemaphore_Post( &pTaskPool->startStopSignal ); - - /* OUTER LOOP: it controls the lifetime of the worker thread: exit condition for a worker thread - * is setting maxThreads to zero. A worker thread is running until the maximum number of allowed - * threads is not zero and the active threads are less than the maximum number of allowed threads. - */ - do - { - bool jobAvailable; - IotLink_t * pFirst; - _taskPoolJob_t * pJob = NULL; - - /* Wait on incoming notifications. If waiting on the semaphore return with timeout, then - * it means that this thread should consider shutting down for the task pool to fold back - * to its minimum number of threads. */ - jobAvailable = IotSemaphore_TimedWait( &pTaskPool->dispatchSignal, IOT_TASKPOOL_JOB_WAIT_TIMEOUT_MS ); - - /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified, - * or before waiting for incoming notifications. - */ - TASKPOOL_ENTER_CRITICAL(); - { - /* If the exit condition is verified, update the number of active threads and exit the loop. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - IotLogDebug( "Worker thread exiting because shutdown condition was set." ); - - /* Decrease the number of active threads. */ - pTaskPool->activeThreads--; - - TASKPOOL_EXIT_CRITICAL(); - - /* Signal that this worker is exiting. */ - IotSemaphore_Post( &pTaskPool->startStopSignal ); - - /* On shutdown, abandon the OUTER LOOP immediately. */ - break; - } - - /* Check if this thread needs to exit because 'max threads' quota was exceeded. - * In that case, let is run once, so we can support the case for scheduling 'high priority' - * jobs that causes exceeding the max threads quota for the purpose of executing - * the high-priority task. */ - if( pTaskPool->activeThreads > pTaskPool->maxThreads ) - { - IotLogDebug( "Worker thread will exit because maximum quota was exceeded." ); - - /* Decrease the number of active threads pro-actively. */ - pTaskPool->activeThreads--; - - /* Mark this thread as dead. */ - running = false; - } - /* Check if this thread needs to exit because the worker woke up after a timeout. */ - else if( jobAvailable == false ) - { - /* If there was a timeout, shrink back the task pool to the minimum number of threads. */ - if( pTaskPool->activeThreads > pTaskPool->minThreads ) - { - /* After waking up from a timeout, the thread will try and pick up a new job, but . */ - IotLogDebug( "Worker will exit because task pool is shrinking." ); - - /* Decrease the number of active threads pro-actively. */ - pTaskPool->activeThreads--; - - /* Mark this thread as dead. */ - running = false; - } - } - - /* Only look for a job if waiting did not timed out. */ - if( jobAvailable == true ) - { - /* Dequeue the first job in FIFO order. */ - pFirst = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); - - /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */ - if( pFirst != NULL ) - { - /* Extract the job from its link. */ - pJob = IotLink_Container( _taskPoolJob_t, pFirst, link ); - - /* Update status to 'executing'. */ - pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; - userCallback = pJob->userCallback; - } - } - } - TASKPOOL_EXIT_CRITICAL(); - - /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */ - while( pJob != NULL ) - { - /* Process the job by invoking the associated callback with the user context. - * This task pool thread will not be available until the user callback returns. - */ - { - IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); - IotTaskPool_Assert( userCallback != NULL ); - - userCallback( pTaskPool, pJob, pJob->pUserContext ); - - /* This job is finished, clear its pointer. */ - pJob = NULL; - userCallback = NULL; - - /* If this thread exceeded the quota, then let it terminate. */ - if( running == false ) - { - /* Abandon the INNER LOOP. Execution will transfer back to the OUTER LOOP condition. */ - break; - } - } - - /* Acquire the lock before updating the job status. */ - TASKPOOL_ENTER_CRITICAL(); - { - /* Update the number of busy threads, so new requests can be served by creating new threads, up to maxThreads. */ - pTaskPool->activeJobs--; - - /* Try and dequeue the next job in the dispatch queue. */ - IotLink_t * pItem = NULL; - - /* Dequeue the next job from the dispatch queue. */ - pItem = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); - - /* If there is no job left in the dispatch queue, update the worker status and leave. */ - if( pItem == NULL ) - { - TASKPOOL_EXIT_CRITICAL(); - - /* Abandon the INNER LOOP. Execution will transfer back to the OUTER LOOP condition. */ - break; - } - else - { - pJob = IotLink_Container( _taskPoolJob_t, pItem, link ); - - userCallback = pJob->userCallback; - } - - pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; - } - TASKPOOL_EXIT_CRITICAL(); - } - } while( running == true ); -} - -/* ---------------------------------------------------------------------------------------------- */ - -static void _initJobsCache( _taskPoolCache_t * const pCache ) -{ - IotDeQueue_Create( &pCache->freeList ); - - pCache->freeCount = 0; -} - -/*-----------------------------------------------------------*/ - -static void _initializeJob( _taskPoolJob_t * const pJob, - IotTaskPoolRoutine_t userCallback, - void * pUserContext, - bool isStatic ) -{ - pJob->link.pNext = NULL; - pJob->link.pPrevious = NULL; - pJob->userCallback = userCallback; - pJob->pUserContext = pUserContext; - - if( isStatic ) - { - pJob->flags = IOT_TASK_POOL_INTERNAL_STATIC; - pJob->status = IOT_TASKPOOL_STATUS_READY; - } - else - { - pJob->status = IOT_TASKPOOL_STATUS_READY; - } -} - -/*-----------------------------------------------------------*/ - -static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache ) -{ - _taskPoolJob_t * pJob = NULL; - IotLink_t * pLink = IotListDouble_RemoveHead( &( pCache->freeList ) ); - - if( pLink != NULL ) - { - pJob = IotLink_Container( _taskPoolJob_t, pLink, link ); - } - - /* If there is no available job in the cache, then allocate one. */ - if( pJob == NULL ) - { - pJob = ( _taskPoolJob_t * ) IotTaskPool_MallocJob( sizeof( _taskPoolJob_t ) ); - - if( pJob != NULL ) - { - memset( pJob, 0x00, sizeof( _taskPoolJob_t ) ); - } - else - { - /* Log allocation failure for troubleshooting purposes. */ - IotLogInfo( "Failed to allocate job." ); - } - } - /* If there was a job in the cache, then make sure we keep the counters up-to-date. */ - else - { - IotTaskPool_Assert( pCache->freeCount > 0 ); - - pCache->freeCount--; - } - - return pJob; -} - -/*-----------------------------------------------------------*/ - -static void _recycleJob( _taskPoolCache_t * const pCache, - _taskPoolJob_t * const pJob ) -{ - /* We should never try and recycling a job that is linked into some queue. */ - IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); - - /* We will recycle the job if there is space in the cache. */ - if( pCache->freeCount < IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ) - { - /* Destroy user data, for added safety&security. */ - pJob->userCallback = NULL; - pJob->pUserContext = NULL; - - /* Reset the status for added debuggability. */ - pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED; - - IotListDouble_InsertTail( &pCache->freeList, &pJob->link ); - - pCache->freeCount++; - - IotTaskPool_Assert( pCache->freeCount >= 1 ); - } - else - { - _destroyJob( pJob ); - } -} - -/*-----------------------------------------------------------*/ - -static void _destroyJob( _taskPoolJob_t * const pJob ) -{ - /* Destroy user data, for added safety & security. */ - pJob->userCallback = NULL; - pJob->pUserContext = NULL; - - /* Reset the status for added debuggability. */ - pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED; - - /* Only dispose of dynamically allocated jobs. */ - if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0UL ) - { - IotTaskPool_FreeJob( pJob ); - } -} - -/* ---------------------------------------------------------------------------------------------- */ - -static bool _IsShutdownStarted( const _taskPool_t * const pTaskPool ) -{ - return( pTaskPool->maxThreads == 0UL ); -} - -/*-----------------------------------------------------------*/ - -static void _signalShutdown( _taskPool_t * const pTaskPool, - uint32_t threads ) -{ - uint32_t count; - - /* Set the exit condition. */ - pTaskPool->maxThreads = 0; - - /* Broadcast to all active threads to wake-up. Active threads do check the exit condition right after waking up. */ - for( count = 0; count < threads; ++count ) - { - IotSemaphore_Post( &pTaskPool->dispatchSignal ); - } -} - -/* ---------------------------------------------------------------------------------------------- */ - -static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, - _taskPoolJob_t * const pJob, - uint32_t flags ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - bool mustGrow = false; - bool shouldGrow = false; - - /* Update the job status to 'scheduled'. */ - pJob->status = IOT_TASKPOOL_STATUS_SCHEDULED; - - /* Update the number of active jobs optimistically, so new requests can be served by creating new threads. */ - pTaskPool->activeJobs++; - - /* If all threads are busy, try and create a new one. Failing to create a new thread - * only has performance implications on correctly executing th scheduled job. - */ - uint32_t activeThreads = pTaskPool->activeThreads; - - if( activeThreads <= pTaskPool->activeJobs ) - { - /* If the job scheduling is tagged as high priority, then we must grow the task pool, - * no matter how many threads are active already. */ - if( ( flags & IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_JOB_HIGH_PRIORITY ) - { - mustGrow = true; - } - - /* Grow the task pool up to the maximum number of threads indicated by the user. - * Growing the taskpool can safely fail, the existing threads will eventually pick up - * the job sometimes later. */ - else if( activeThreads < pTaskPool->maxThreads ) - { - shouldGrow = true; - } - else - { - /* Nothing to do. */ - } - - if( ( mustGrow == true ) || ( shouldGrow == true ) ) - { - IotLogInfo( "Growing a Task pool with a new worker thread..." ); - - if( Iot_CreateDetachedThread( _taskPoolWorker, - pTaskPool, - pTaskPool->priority, - pTaskPool->stackSize ) ) - { - IotSemaphore_Wait( &pTaskPool->startStopSignal ); - - pTaskPool->activeThreads++; - } - else - { - /* Failure to create a worker thread may not hinder functional correctness, but rather just responsiveness. */ - IotLogWarn( "Task pool failed to create a worker thread." ); - - /* Failure to create a worker thread for a high priority job is considered a failure. */ - if( mustGrow ) - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - } - } - } - - TASKPOOL_FUNCTION_CLEANUP(); - - if( TASKPOOL_SUCCEEDED( status ) ) - { - /* Append the job to the dispatch queue. - * Put the job at the front, if it is a high priority job. */ - if( mustGrow == true ) - { - IotLogDebug( "High priority job: placing job at the head of the queue." ); - - IotDeQueue_EnqueueHead( &pTaskPool->dispatchQueue, &pJob->link ); - } - else - { - IotDeQueue_EnqueueTail( &pTaskPool->dispatchQueue, &pJob->link ); - } - - /* Signal a worker to pick up the job. */ - IotSemaphore_Post( &pTaskPool->dispatchSignal ); - } - else - { - /* Scheduling can only fail to allocate a new worker, which is an error - * only for high priority tasks. */ - IotTaskPool_Assert( mustGrow == true ); - - /* Revert updating the number of active jobs. */ - pTaskPool->activeJobs--; - } - - TASKPOOL_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -static bool _matchJobByPointer( const IotLink_t * const pLink, - void * pMatch ) -{ - const _taskPoolJob_t * const pJob = ( _taskPoolJob_t * ) pMatch; - - const _taskPoolTimerEvent_t * const pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); - - if( pJob == pTimerEvent->pJob ) - { - return true; - } - - return false; -} - -/*-----------------------------------------------------------*/ - -static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, - _taskPoolJob_t * const pJob, - IotTaskPoolJobStatus_t * const pStatus ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - bool cancelable = false; - - /* We can only cancel jobs that are either 'ready' (waiting to be scheduled). 'deferred', or 'scheduled'. */ - - IotTaskPoolJobStatus_t currentStatus = pJob->status; - - switch( currentStatus ) - { - case IOT_TASKPOOL_STATUS_READY: - case IOT_TASKPOOL_STATUS_DEFERRED: - case IOT_TASKPOOL_STATUS_SCHEDULED: - case IOT_TASKPOOL_STATUS_CANCELED: - cancelable = true; - break; - - case IOT_TASKPOOL_STATUS_COMPLETED: - /* Log message for debug purposes. */ - IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." ); - break; - - default: - /* Log message for debug purposes. */ - IotLogError( "Attempt to cancel a job with an undefined state." ); - break; - } - - /* Update the returned status to the current status of the job. */ - if( pStatus != NULL ) - { - *pStatus = currentStatus; - } - - if( cancelable == false ) - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_CANCEL_FAILED ); - } - else - { - /* Update the status of the job. */ - pJob->status = IOT_TASKPOOL_STATUS_CANCELED; - - /* If the job is cancelable and its current status is 'scheduled' then unlink it from the dispatch - * queue and signal any waiting threads. */ - if( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) - { - /* A scheduled work items must be in the dispatch queue. */ - IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) ); - - IotDeQueue_Remove( &pJob->link ); - } - - /* If the job current status is 'deferred' then the job has to be pending - * in the timeouts queue. */ - else if( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED ) - { - /* Find the timer event associated with the current job. There MUST be one, hence assert if not. */ - IotLink_t * pTimerEventLink = IotListDouble_FindFirstMatch( &pTaskPool->timerEventsList, NULL, _matchJobByPointer, pJob ); - IotTaskPool_Assert( pTimerEventLink != NULL ); - - if( pTimerEventLink != NULL ) - { - bool shouldReschedule = false; - - /* If the job being canceled was at the head of the timeouts queue, then we need to reschedule the timer - * with the next job timeout */ - IotLink_t * pHeadLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); - - if( pHeadLink == pTimerEventLink ) - { - shouldReschedule = true; - } - - /* Remove the timer event associated with the canceled job and free the associated memory. */ - IotListDouble_Remove( pTimerEventLink ); - IotTaskPool_FreeTimerEvent( IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ) ); - - if( shouldReschedule ) - { - IotLink_t * pNextTimerEventLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); - - if( pNextTimerEventLink != NULL ) - { - _rescheduleDeferredJobsTimer( &pTaskPool->timer, IotLink_Container( _taskPoolTimerEvent_t, pNextTimerEventLink, link ) ); - } - } - } - } - else - { - /* A cancelable job status should be either 'scheduled' or 'deferred'. */ - IotTaskPool_Assert( ( currentStatus == IOT_TASKPOOL_STATUS_READY ) || ( currentStatus == IOT_TASKPOOL_STATUS_CANCELED ) ); - } - } - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -static IotTaskPoolError_t _trySafeExtraction( _taskPool_t * const pTaskPool, - _taskPoolJob_t * const pJob, - bool atCompletion ) -{ - TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolJobStatus_t currentStatus = pJob->status; - - /* if the job is executing, we cannot touch it. */ - if( ( atCompletion == false ) && ( currentStatus == IOT_TASKPOOL_STATUS_COMPLETED ) ) - { - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_ILLEGAL_OPERATION ); - } - /* Do not destroy a job in the dispatch queue or the timer queue without cancelling first. */ - else if( ( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) || ( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED ) ) - { - IotTaskPoolJobStatus_t statusAtCancellation; - - /* Cancellation can fail, e.g. if a job is being executed when we are trying to cancel it. */ - status = _tryCancelInternal( pTaskPool, pJob, &statusAtCancellation ); - - switch( status ) - { - case IOT_TASKPOOL_SUCCESS: - /* Nothing to do. */ - break; - - case IOT_TASKPOOL_CANCEL_FAILED: - IotLogWarn( "Removing a scheduled job failed because the job could not be canceled, error %s.", - IotTaskPool_strerror( status ) ); - status = IOT_TASKPOOL_ILLEGAL_OPERATION; - break; - - default: - /* Nothing to do. */ - break; - } - } - else if( IotLink_IsLinked( &pJob->link ) ) - { - /* If the job is not in the dispatch or timer queue, it must be in the cache. */ - IotTaskPool_Assert( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0 ); - - IotListDouble_Remove( &pJob->link ); - } - else - { - /* Nothing to do */ - } - - TASKPOOL_NO_FUNCTION_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, - const IotLink_t * const pTimerEventLink2 ) -{ - const _taskPoolTimerEvent_t * const pTimerEvent1 = IotLink_Container( _taskPoolTimerEvent_t, - pTimerEventLink1, - link ); - const _taskPoolTimerEvent_t * const pTimerEvent2 = IotLink_Container( _taskPoolTimerEvent_t, - pTimerEventLink2, - link ); - - if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) - { - return -1; - } - - if( pTimerEvent1->expirationTime > pTimerEvent2->expirationTime ) - { - return 1; - } - - return 0; -} - -/*-----------------------------------------------------------*/ - -static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, - _taskPoolTimerEvent_t * const pFirstTimerEvent ) -{ - uint64_t delta = 0; - uint64_t now = IotClock_GetTimeMs(); - - if( pFirstTimerEvent->expirationTime > now ) - { - delta = pFirstTimerEvent->expirationTime - now; - } - - if( delta < TASKPOOL_JOB_RESCHEDULE_DELAY_MS ) - { - delta = TASKPOOL_JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ - } - - IotTaskPool_Assert( delta > 0 ); - - if( IotClock_TimerArm( pTimer, ( uint32_t ) delta, 0 ) == false ) - { - IotLogWarn( "Failed to re-arm timer for task pool" ); - } -} - -/*-----------------------------------------------------------*/ - -static void _timerThread( void * pArgument ) -{ - _taskPool_t * pTaskPool = ( _taskPool_t * ) pArgument; - _taskPoolTimerEvent_t * pTimerEvent = NULL; - - IotLogDebug( "Timer thread started for task pool %p.", pTaskPool ); - - /* Attempt to lock the timer mutex. Return immediately if the mutex cannot be locked. - * If this mutex cannot be locked it means that another thread is manipulating the - * timeouts list, and will reset the timer to fire again, although it will be late. - */ - TASKPOOL_ENTER_CRITICAL(); - { - /* Check again for shutdown and bail out early in case. */ - if( _IsShutdownStarted( pTaskPool ) ) - { - TASKPOOL_EXIT_CRITICAL(); - - /* Complete the shutdown sequence. */ - _destroyTaskPool( pTaskPool ); - - IotTaskPool_FreeTaskPool( pTaskPool ); - - return; - } - - /* Dispatch all deferred job whose timer expired, then reset the timer for the next - * job down the line. */ - for( ; ; ) - { - /* Peek the first event in the timer event list. */ - IotLink_t * pLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); - - /* Check if the timer misfired for any reason. */ - if( pLink != NULL ) - { - /* Record the current time. */ - uint64_t now = IotClock_GetTimeMs(); - - /* Extract the job from its envelope. */ - pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); - - /* Check if the first event should be processed now. */ - if( pTimerEvent->expirationTime <= now ) - { - /* Remove the timer event for immediate processing. */ - IotListDouble_Remove( &( pTimerEvent->link ) ); - } - else - { - /* The first element in the timer queue shouldn't be processed yet. - * Arm the timer for when it should be processed and leave altogether. */ - _rescheduleDeferredJobsTimer( &pTaskPool->timer, pTimerEvent ); - - break; - } - } - /* If there are no timer events to process, terminate this thread. */ - else - { - IotLogDebug( "No further timer events to process. Exiting timer thread." ); - - break; - } - - IotLogDebug( "Scheduling job from timer event." ); - - /* Queue the job associated with the received timer event. */ - ( void ) _scheduleInternal( pTaskPool, pTimerEvent->pJob, 0 ); - - /* Free the timer event. */ - IotTaskPool_FreeTimerEvent( pTimerEvent ); - } - } - TASKPOOL_EXIT_CRITICAL(); -} diff --git a/libraries/standard/common/src/iot_taskpool_static_memory.c b/libraries/standard/common/src/iot_taskpool_static_memory.c deleted file mode 100644 index fbab2e5723..0000000000 --- a/libraries/standard/common/src/iot_taskpool_static_memory.c +++ /dev/null @@ -1,171 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_taskpool_static_memory.c - * @brief Implementation of task pool static memory functions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include - -/* Static memory include. */ -#include "iot_static_memory.h" - -/* Task pool internal include. */ -#include "private/iot_taskpool_internal.h" - -/*-----------------------------------------------------------*/ - -/* Validate static memory configuration settings. */ -#if IOT_TASKPOOL_JOBS_RECYCLE_LIMIT <= 0 - #error "IOT_TASKPOOL_JOBS_RECYCLE_LIMIT cannot be 0 or negative." -#endif - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static uint32_t _pInUseTaskPools[ IOT_TASKPOOLS ] = { 0U }; /**< @brief Task pools in-use flags. */ -static _taskPool_t _pTaskPools[ IOT_TASKPOOLS ] = { { .dispatchQueue = IOT_DEQUEUE_INITIALIZER } }; /**< @brief Task pools. */ - -static uint32_t _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0U }; /**< @brief Task pool jobs in-use flags. */ -static _taskPoolJob_t _pTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = IOT_LINK_INITIALIZER } }; /**< @brief Task pool jobs. */ - -static uint32_t _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0U }; /**< @brief Task pool timer event in-use flags. */ -static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = { 0 } } }; /**< @brief Task pool timer events. */ - -/*-----------------------------------------------------------*/ - -void * IotTaskPool_MallocTaskPool( size_t size ) -{ - int freeIndex = -1; - void * pNewTaskPool = NULL; - - /* Check size argument. */ - if( size == sizeof( _taskPool_t ) ) - { - /* Find a free task pool job. */ - freeIndex = IotStaticMemory_FindFree( _pInUseTaskPools, IOT_TASKPOOLS ); - - if( freeIndex != -1 ) - { - pNewTaskPool = &( _pTaskPools[ freeIndex ] ); - } - } - - return pNewTaskPool; -} - -/*-----------------------------------------------------------*/ - -void IotTaskPool_FreeTaskPool( void * ptr ) -{ - /* Return the in-use task pool job. */ - IotStaticMemory_ReturnInUse( ptr, - _pTaskPools, - _pInUseTaskPools, - IOT_TASKPOOLS, - sizeof( _taskPool_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotTaskPool_MallocJob( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewJob = NULL; - - /* Check size argument. */ - if( size == sizeof( _taskPoolJob_t ) ) - { - /* Find a free task pool job. */ - freeIndex = IotStaticMemory_FindFree( _pInUseTaskPoolJobs, - IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ); - - if( freeIndex != -1 ) - { - pNewJob = &( _pTaskPoolJobs[ freeIndex ] ); - } - } - - return pNewJob; -} - -/*-----------------------------------------------------------*/ - -void IotTaskPool_FreeJob( void * ptr ) -{ - /* Return the in-use task pool job. */ - IotStaticMemory_ReturnInUse( ptr, - _pTaskPoolJobs, - _pInUseTaskPoolJobs, - IOT_TASKPOOL_JOBS_RECYCLE_LIMIT, - sizeof( _taskPoolJob_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotTaskPool_MallocTimerEvent( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewTimerEvent = NULL; - - /* Check size argument. */ - if( size == sizeof( _taskPoolTimerEvent_t ) ) - { - /* Find a free task pool timer event. */ - freeIndex = IotStaticMemory_FindFree( _pInUseTaskPoolTimerEvents, - IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ); - - if( freeIndex != -1 ) - { - pNewTimerEvent = &( _pTaskPoolTimerEvents[ freeIndex ] ); - } - } - - return pNewTimerEvent; -} - -/*-----------------------------------------------------------*/ - -void IotTaskPool_FreeTimerEvent( void * ptr ) -{ - /* Return the in-use task pool timer event. */ - IotStaticMemory_ReturnInUse( ptr, - _pTaskPoolTimerEvents, - _pInUseTaskPoolTimerEvents, - IOT_TASKPOOL_JOBS_RECYCLE_LIMIT, - sizeof( _taskPoolTimerEvent_t ) ); -} - -/*-----------------------------------------------------------*/ - -#endif diff --git a/libraries/standard/common/src/private/iot_taskpool_internal.h b/libraries/standard/common/src/private/iot_taskpool_internal.h deleted file mode 100644 index d8e779197c..0000000000 --- a/libraries/standard/common/src/private/iot_taskpool_internal.h +++ /dev/null @@ -1,313 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_taskpool_internal.h - * @brief Internal header of task pool library. This header should not be included in - * typical application code. - */ - -#ifndef IOT_TASKPOOL_INTERNAL_H_ -#define IOT_TASKPOOL_INTERNAL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Task pool include. */ -#include "iot_error.h" -#include "iot_taskpool.h" - -/* Establish a few convenience macros to handle errors in a standard way. */ - -/** - * @brief Every public API return an enumeration value with an underlying value of 0 in case of success. - */ -#define TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) - -/** - * @brief Every public API returns an enumeration value with an underlying value different than 0 in case of success. - */ -#define TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) - -/** - * @brief Jump to the cleanup area. - */ -#define TASKPOOL_GOTO_CLEANUP() IOT_GOTO_CLEANUP() - -/** - * @brief Declare the storage for the error status variable. - */ -#define TASKPOOL_FUNCTION_ENTRY( result ) IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) - -/** - * @brief Check error and leave in case of failure. - */ -#define TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) \ - { if( TASKPOOL_FAILED( status = ( expr ) ) ) { IOT_GOTO_CLEANUP(); } \ - } - -/** - * @brief Exit if an argument is NULL. - */ -#define TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) - -/** - * @brief Exit if an argument is NULL. - */ -#define TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) - -/** - * @brief Set error and leave. - */ -#define TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) IOT_SET_AND_GOTO_CLEANUP( expr ) - -/** - * @brief Initialize error and declare start of cleanup area. - */ -#define TASKPOOL_FUNCTION_CLEANUP() IOT_FUNCTION_CLEANUP_BEGIN() - -/** - * @brief Initialize error and declare end of cleanup area. - */ -#define TASKPOOL_FUNCTION_CLEANUP_END() IOT_FUNCTION_CLEANUP_END() - -/** - * @brief Create an empty cleanup area. - */ -#define TASKPOOL_NO_FUNCTION_CLEANUP() IOT_FUNCTION_EXIT_NO_CLEANUP() - -/** - * @brief Does not create a cleanup area. - */ -#define TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL() return status - -/** - * @def IotTaskPool_Assert( expression ) - * @brief Assertion macro for the Task pool library. - * - * Set @ref IOT_TASKPOOL_ENABLE_ASSERTS to `1` to enable assertions in the Task pool - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if IOT_TASKPOOL_ENABLE_ASSERTS == 1 - #ifndef IotTaskPool_Assert - #ifdef Iot_DefaultAssert - #define IotTaskPool_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for Task Pool, but IotTaskPool_Assert is not defined" - #endif - #endif -#else /* if IOT_TASKPOOL_ENABLE_ASSERTS == 1 */ - #define IotTaskPool_Assert( expression ) -#endif /* if IOT_TASKPOOL_ENABLE_ASSERTS == 1 */ - -/* Configure logs for TASKPOOL functions. */ -#ifdef IOT_LOG_LEVEL_TASKPOOL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_TASKPOOL -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "TASKPOOL" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "iot_static_memory.h" - -/** - * @brief Allocate an #_taskPool_t. This function should have the - * same signature as [malloc]. - */ - void * IotTaskPool_MallocTaskPool( size_t size ); - -/** - * @brief Free an #_taskPool_t. This function should have the - * same signature as [malloc]. - */ - void IotTaskPool_FreeTaskPool( void * ptr ); - -/** - * @brief Allocate an #IotTaskPoolJob_t. This function should have the - * same signature as [malloc]. - */ - void * IotTaskPool_MallocJob( size_t size ); - -/** - * @brief Free an #IotTaskPoolJob_t. This function should have the same - * same signature as [malloc]. - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void IotTaskPool_FreeJob( void * ptr ); - -/** - * @brief Allocate an #_taskPoolTimerEvent_t. This function should have the - * same signature as [malloc]. - */ - void * IotTaskPool_MallocTimerEvent( size_t size ); - -/** - * @brief Free an #_taskPoolTimerEvent_t. This function should have the - * same signature as[ free ]. - */ - void IotTaskPool_FreeTimerEvent( void * ptr ); - -#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #include - - #ifndef IotTaskPool_MallocTaskPool - #ifdef Iot_DefaultMalloc - #define IotTaskPool_MallocTaskPool Iot_DefaultMalloc - #else - #error "No malloc function defined for IotTaskPool_MallocTaskPool" - #endif - #endif - - #ifndef IotTaskPool_FreeTaskPool - #ifdef Iot_DefaultFree - #define IotTaskPool_FreeTaskPool Iot_DefaultFree - #else - #error "No free function defined for IotTaskPool_FreeTaskPool" - #endif - #endif - - #ifndef IotTaskPool_MallocJob - #ifdef Iot_DefaultMalloc - #define IotTaskPool_MallocJob Iot_DefaultMalloc - #else - #error "No malloc function defined for IotTaskPool_MallocJob" - #endif - #endif - - #ifndef IotTaskPool_FreeJob - #ifdef Iot_DefaultFree - #define IotTaskPool_FreeJob Iot_DefaultFree - #else - #error "No free function defined for IotTaskPool_FreeJob" - #endif - #endif - - #ifndef IotTaskPool_MallocTimerEvent - #ifdef Iot_DefaultMalloc - #define IotTaskPool_MallocTimerEvent Iot_DefaultMalloc - #else - #error "No malloc function defined for IotTaskPool_MallocTimerEvent" - #endif - #endif - - #ifndef IotTaskPool_FreeTimerEvent - #ifdef Iot_DefaultFree - #define IotTaskPool_FreeTimerEvent Iot_DefaultFree - #else - #error "No free function defined for IotTaskPool_FreeTimerEvent" - #endif - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ - -/* ---------------------------------------------------------------------------------------------- */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * A macros to manage task pool memory allocation. - */ -#define IOT_TASK_POOL_INTERNAL_STATIC ( ( uint32_t ) 0x00000001 ) /* Flag to mark a job as user-allocated. */ -/** @endcond */ - -/** - * @brief Task pool jobs cache. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct _taskPoolCache -{ - IotListDouble_t freeList; /**< @brief A list of hold cached jobs. */ - - uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ -} _taskPoolCache_t; - -/** - * @brief The task pool data structure keeps track of the internal state and the signals for the dispatcher threads. - * The task pool is a thread safe data structure. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct _taskPool -{ - IotDeQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ - IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ - _taskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ - uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ - uint32_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ - uint32_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ - uint32_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ - uint32_t stackSize; /**< @brief The stack size for all task pool threads. */ - int32_t priority; /**< @brief The priority for all task pool threads. */ - IotSemaphore_t dispatchSignal; /**< @brief The synchronization object on which threads are waiting for incoming jobs. */ - IotSemaphore_t startStopSignal; /**< @brief The synchronization object for threads to signal start and stop condition. */ - IotTimer_t timer; /**< @brief The timer for deferred jobs. */ - IotMutex_t lock; /**< @brief The lock to protect the task pool data structure access. */ -} _taskPool_t; - -/** - * @brief The job data structure keeps track of the user callback and context, as well as the status of the job. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct _taskPoolJob -{ - IotLink_t link; /**< @brief The link to insert the job in the dispatch queue. */ - IotTaskPoolRoutine_t userCallback; /**< @brief The user provided callback. */ - void * pUserContext; /**< @brief The user provided context. */ - uint32_t flags; /**< @brief Internal flags. */ - IotTaskPoolJobStatus_t status; /**< @brief The status for the job. */ -} _taskPoolJob_t; - -/** - * @brief Represents an operation that is subject to a timer. - * - * These events are queued per task pool. They are sorted by their - * expiration time. - */ -typedef struct _taskPoolTimerEvent -{ - IotLink_t link; /**< @brief List link member. */ - uint64_t expirationTime; /**< @brief When this event should be processed. */ - _taskPoolJob_t * pJob; /**< @brief The task pool job associated with this event. */ -} _taskPoolTimerEvent_t; - -#endif /* ifndef IOT_TASKPOOL_INTERNAL_H_ */ diff --git a/libraries/standard/common/test/iot_tests_common.c b/libraries/standard/common/test/iot_tests_common.c deleted file mode 100644 index d59efe6162..0000000000 --- a/libraries/standard/common/test/iot_tests_common.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_common.c - * @brief Test runner for common tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the common test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunCommonTests( bool disableNetworkTests, bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableNetworkTests; - ( void ) disableLongTests; - - RUN_TEST_GROUP( Common_Unit_Linear_Containers ); - RUN_TEST_GROUP( Common_Unit_Task_Pool ); - RUN_TEST_GROUP( Common_Unit_Atomic ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/common/test/unit/iot_tests_atomic.c b/libraries/standard/common/test/unit/iot_tests_atomic.c deleted file mode 100644 index e07a785913..0000000000 --- a/libraries/standard/common/test/unit/iot_tests_atomic.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_atomic.c - * @brief Simple API tests for atomic interfaces. - * - * Tests in this file do not check extensively for atomicity, - * but only guarantee APIs at least do what they supposed to do. - * - * Atomic APIs are wrapped with asm tag, so that objdump disassembly - * can be examined. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Atomic include. */ -#include "iot_atomic.h" - -/* Test framework includes. */ -#include "unity.h" -#include "unity_fixture.h" - -/* Magic numbers. */ -#define MAGIC_NUMBER_32BIT_1 ( 0xA5A5A5A5 ) -#define MAGIC_NUMBER_32BIT_2 ( 0xF0F0F0F0 ) -#define MAGIC_NUMBER_32BIT_3 ( 0x0000000F ) - -#define MAGIC_NUMBER_8BIT_1 ( 0xA5 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for atomic tests. - */ -TEST_GROUP( Common_Unit_Atomic ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for atomic tests. - */ -TEST_SETUP( Common_Unit_Atomic ) -{ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for atomic tests. - */ -TEST_TEAR_DOWN( Common_Unit_Atomic ) -{ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for atomic tests. - */ -TEST_GROUP_RUNNER( Common_Unit_Atomic ) -{ - RUN_TEST_CASE( Common_Unit_Atomic, AtomicCasHappyPath ); - RUN_TEST_CASE( Common_Unit_Atomic, AtomicArithmeticHappyPath ); - RUN_TEST_CASE( Common_Unit_Atomic, AtomicBitwiseHappyPath ); - RUN_TEST_CASE( Common_Unit_Atomic, AtomicCasFailToSwap ); -} - - -TEST( Common_Unit_Atomic, AtomicCasHappyPath ) -{ - uint32_t ulCasDestination_32; - uint32_t ulCasComparator_32; - uint32_t ulCasNewValue_32; - uint32_t ulReturnValue_32; - - uint32_t * pSwapDestination_32; - uint32_t * pSwapNewValue_32; - uint32_t * pReturnValue_32; - - uint8_t uCasDestination_8 = MAGIC_NUMBER_8BIT_1; - uint8_t uCasComparator_8 = MAGIC_NUMBER_8BIT_1; - - uint8_t * pSwapDestination_8 = &uCasDestination_8; - uint8_t * pSwapNewValue_8 = &uCasComparator_8; - uint8_t * pReturnValue_8 = NULL; - - /* #1 -- CAS */ - ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; - ulCasComparator_32 = MAGIC_NUMBER_32BIT_1; - ulCasNewValue_32 = MAGIC_NUMBER_32BIT_2; - - ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); - - TEST_ASSERT_MESSAGE( ulCasDestination_32 == ulCasNewValue_32, "Atomic_CompareAndSwap_u32 -- did not swap." ); - TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); - - /* #2 -- CAS, comparator from the same mem location. */ - ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; - - ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, MAGIC_NUMBER_32BIT_2, ulCasDestination_32 ); - - TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_2, "Atomic_CompareAndSwap_u32 -- did not swap." ); - TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); - - /* #3 -- swap */ - pSwapDestination_32 = &ulCasDestination_32; - pSwapNewValue_32 = &ulCasNewValue_32; - pReturnValue_32 = NULL; - - pReturnValue_32 = Atomic_Swap_Pointer( ( void ** ) &pSwapDestination_32, pSwapNewValue_32 ); - - TEST_ASSERT_MESSAGE( pSwapDestination_32 == &ulCasNewValue_32, "Atomic_SwapPointers_p32 -- did not swap." ); - TEST_ASSERT_MESSAGE( pReturnValue_32 == &ulCasDestination_32, "Atomic_SwapPointers_p32 -- expected to return previous value." ); - - /* #4 -- swap, pointer to variable of uint8_t type. */ - pSwapDestination_8 = &uCasDestination_8; - pSwapNewValue_8 = &uCasComparator_8; - pReturnValue_8 = NULL; - - pReturnValue_8 = Atomic_Swap_Pointer( ( void ** ) &pSwapDestination_8, pSwapNewValue_8 ); - - TEST_ASSERT_MESSAGE( pSwapDestination_8 == &uCasComparator_8, "Atomic_SwapPointers_p32 -- did not swap." ); - TEST_ASSERT_MESSAGE( pReturnValue_8 == &uCasDestination_8, "Atomic_SwapPointers_p32 -- expected to return previous value." ); - - /* #5 -- CAS pointers. */ - pSwapDestination_32 = &ulCasDestination_32; - pSwapNewValue_32 = &ulCasNewValue_32; - - ulReturnValue_32 = Atomic_CompareAndSwap_Pointer( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); - - TEST_ASSERT_MESSAGE( ( intptr_t ) pSwapDestination_32 == ( intptr_t ) pSwapNewValue_32, "Atomic_CompareAndSwapPointers_p32 -- did not swap." ); - TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwapPointers_p32 -- expected return value true." ); - - return; -} - -TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) -{ - uint32_t uAddend_32; - int32_t iAddend_32; - - uint32_t uDelta_32; - - uint32_t uReturnValue_32; - int32_t iReturnValue_32; - - uint8_t uAddend_8; - - uAddend_32 = ( uint32_t ) 0xFFFFFFFE; /* signed 0xFFFFFFFE is -2, 2's complement. */ - - uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); - TEST_ASSERT_MESSAGE( uAddend_32 == UINT32_MAX, "Atomic_Add_u32 -- did not add correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == ( uint32_t ) 0xFFFFFFFE, "Atomic_Add_u32 -- expected return value (UINT32_MAX - 1)." ); - - iAddend_32 = ( int32_t ) uAddend_32; - iReturnValue_32 = ( int32_t ) uReturnValue_32; - TEST_ASSERT_MESSAGE( iAddend_32 == -1, "Atomic_Add_u32 -- did not cast correctly." ); - TEST_ASSERT_MESSAGE( iReturnValue_32 == -2, "Atomic_Add_u32 -- expected return value -2." ); - - - iAddend_32 = INT32_MIN + 1; /* unsigned 0x80000001, 2's complement. */ - - iReturnValue_32 = ( uint32_t ) Atomic_Subtract_u32( ( uint32_t * ) &iAddend_32, 1 ); - TEST_ASSERT_MESSAGE( iAddend_32 == INT32_MIN, "Atomic_Subtract_u32 -- did not subtract correctly." ); - TEST_ASSERT_MESSAGE( iReturnValue_32 == ( INT32_MIN + 1 ), "Atomic_Subtract_u32 -- expected return value (INT32_MIN + 1)." ); - - uAddend_32 = ( uint32_t ) iAddend_32; - uReturnValue_32 = ( uint32_t ) iReturnValue_32; - TEST_ASSERT_MESSAGE( uAddend_32 == ( uint32_t ) 0x80000000, "Atomic_Subtract_u32 -- did not subtract correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == ( uint32_t ) 0x80000001, "Atomic_Add_u32 -- expected return value (INT32_MIN + 1)." ); - - /* #1 -- add register */ - uAddend_32 = 0; - uDelta_32 = 1; - - uReturnValue_32 = Atomic_Add_u32( &uAddend_32, uDelta_32 ); - - TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); - - /* #2 -- add immediate */ - uAddend_32 = 0; - - uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); - - TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add immediate number correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); - - /* #3 -- add, 8-bit casting */ - uAddend_8 = 1; - uAddend_32 = ( uint32_t ) uAddend_8; - - uReturnValue_32 = Atomic_Add_u32( &uAddend_32, UINT8_MAX ); - - TEST_ASSERT_MESSAGE( ( uint8_t ) uReturnValue_32 == 1, "Atomic_Add_u32 -- did not roll over correctly." ); - - /* #4 -- sub, almost but not underflow */ - uAddend_32 = 1; - - uReturnValue_32 = Atomic_Subtract_u32( &uAddend_32, 1 ); - - TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Subtract_u32 -- did not subtract correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Subtract_u32 -- expected return value 1." ); - - /* #5 -- inc, sanity check */ - uAddend_32 = 0; - - uReturnValue_32 = Atomic_Increment_u32( &uAddend_32 ); - - TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Increment_u32 -- did not increment correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Increment_u32 -- expected return value 0." ); - - /* #6 -- dec, sanity check */ - uAddend_32 = 1; - - uReturnValue_32 = Atomic_Decrement_u32( &uAddend_32 ); - - TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Decrement_u32 -- did not decrement correctly." ); - TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Decrement_u32 -- expected return value 1." ); -} - -TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) -{ - uint32_t ulOp1; - uint32_t ulOp2; - uint32_t ulReturnValue; - - /* #1 -- and */ - ulOp1 = MAGIC_NUMBER_32BIT_1; - ulOp2 = MAGIC_NUMBER_32BIT_2; - - ulReturnValue = Atomic_AND_u32( &ulOp1, ulOp2 ); - - TEST_ASSERT_MESSAGE( ulOp1 == 0xA0A0A0A0, "Atomic_AND_u32 -- did not ANDed correctly." ); - TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_AND_u32 -- expected return value 0xA5A5A5A5." ); - - /* #2 -- or */ - ulOp1 = MAGIC_NUMBER_32BIT_2; - ulOp2 = MAGIC_NUMBER_32BIT_3; - - ulReturnValue = Atomic_OR_u32( &ulOp1, ulOp2 ); - - TEST_ASSERT_MESSAGE( ulOp1 == 0xF0F0F0FF, "Atomic_OR_u32 -- did not ORed correctly." ); - TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_2, "Atomic_AND_u32 -- expected return value 0xF0F0F0F0." ); - - /* #3 -- nand */ - ulOp1 = MAGIC_NUMBER_32BIT_1; - ulOp2 = MAGIC_NUMBER_32BIT_2; - - ulReturnValue = Atomic_NAND_u32( &ulOp1, ulOp2 ); - - TEST_ASSERT_MESSAGE( ulOp1 == 0x5F5F5F5F, "Atomic_NAND_u32 -- did not NANDed correctly." ); - TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_NAND_u32 -- expected return value 0xA5A5A5A5." ); - - /* #4 -- xor */ - ulOp1 = MAGIC_NUMBER_32BIT_1; - ulOp2 = MAGIC_NUMBER_32BIT_2; - - ulReturnValue = Atomic_XOR_u32( &ulOp1, ulOp2 ); - - TEST_ASSERT_MESSAGE( ulOp1 == 0x55555555, "Atomic_XOR_u32 -- did not XORed correctly." ); - TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_XOR_u32 -- expected return value 0xA5A5A5A5." ); -} - -TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) -{ - uint32_t ulCasDestination_32; - uint32_t ulCasComparator_32; - uint32_t ulCasNewValue_32; - uint32_t ulReturnValue_32; - - uint32_t * pCasDestination_32; - uint32_t * pCasComparator_32; - uint32_t * pCasNewValue_32; - - /* #1 -- CAS, not equal, don't swap. */ - ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; - ulCasComparator_32 = MAGIC_NUMBER_32BIT_2; - ulCasNewValue_32 = MAGIC_NUMBER_32BIT_3; - - ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); - - TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should not swap." ); - TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwap_u32 -- should not swap." ); - - /* #2 -- CAS, pointers not equal, don't swap. */ - pCasDestination_32 = &ulCasDestination_32; - pCasComparator_32 = &ulCasComparator_32; - pCasNewValue_32 = &ulCasNewValue_32; - - ulReturnValue_32 = Atomic_CompareAndSwap_Pointer( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); - - TEST_ASSERT_MESSAGE( ( intptr_t ) pCasDestination_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); - TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); -} diff --git a/libraries/standard/common/test/unit/iot_tests_linear_containers.c b/libraries/standard/common/test/unit/iot_tests_linear_containers.c deleted file mode 100644 index a518305250..0000000000 --- a/libraries/standard/common/test/unit/iot_tests_linear_containers.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_linear_containers.c - * @brief Tests for linear containers (lists and queues). - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Linear containers include. */ -#include "iot_linear_containers.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for linear containers tests. - */ -TEST_GROUP( Common_Unit_Linear_Containers ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for linear containers tests. - */ -TEST_SETUP( Common_Unit_Linear_Containers ) -{ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for linear containers. - */ -TEST_TEAR_DOWN( Common_Unit_Linear_Containers ) -{ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for linear containers. - */ -TEST_GROUP_RUNNER( Common_Unit_Linear_Containers ) -{ - RUN_TEST_CASE( Common_Unit_Linear_Containers, ListQueueEmpty ); -} - -/*-----------------------------------------------------------*/ - -TEST( Common_Unit_Linear_Containers, ListQueueEmpty ) -{ - IotListDouble_t list = IOT_LIST_DOUBLE_INITIALIZER; - IotDeQueue_t queue = IOT_DEQUEUE_INITIALIZER; - - /* Create an empty list. */ - IotListDouble_Create( &list ); - - /* Check appropriate return values for an empty list. */ - TEST_ASSERT_EQUAL( 0, IotListDouble_Count( &list ) ); - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &list ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_PeekHead( &list ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_PeekTail( &list ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveHead( &list ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveTail( &list ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_FindFirstMatch( &list, NULL, NULL, 0 ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveFirstMatch( &list, NULL, NULL, 0 ) ); - - /* Create an empty queue. */ - IotDeQueue_Create( &queue ); - - /* Check appropriate return values for an empty queue. */ - TEST_ASSERT_EQUAL( 0, IotDeQueue_Count( &queue ) ); - TEST_ASSERT_EQUAL_INT( true, IotDeQueue_IsEmpty( &queue ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotDeQueue_PeekHead( &queue ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotDeQueue_DequeueHead( &queue ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/common/test/unit/iot_tests_taskpool.c b/libraries/standard/common/test/unit/iot_tests_taskpool.c deleted file mode 100644 index 41af486bc8..0000000000 --- a/libraries/standard/common/test/unit/iot_tests_taskpool.c +++ /dev/null @@ -1,1837 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_taskpool.c - * @brief Tests for task pool. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" -#include "platform/iot_clock.h" - -/* Task pool internal include. */ -#include "private/iot_taskpool_internal.h" - -/* Task pool include. */ -#include "iot_taskpool.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief A simple user context to prove all callbacks are called. - */ -typedef struct JobUserContext -{ - IotMutex_t lock; /**< @brief Protection from concurrent updates. */ - uint32_t counter; /**< @brief A counter to keep track of callback invocations. */ -} JobUserContext_t; - -/** - * @brief The initializer for the user context. - */ -#define IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER { .counter = 0 }; - -/** - * @brief A simple user context to prove the taskpool grows as expected. - */ -typedef struct JobBlockingUserContext -{ - IotSemaphore_t signal; /**< @brief A synch object to signal. */ - IotSemaphore_t block; /**< @brief A synch object to wait on. */ -} JobBlockingUserContext_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for task pool tests. - */ -TEST_GROUP( Common_Unit_Task_Pool ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for task pool tests. - */ -TEST_SETUP( Common_Unit_Task_Pool ) -{ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for task pool tests. - */ -TEST_TEAR_DOWN( Common_Unit_Task_Pool ) -{ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for task pool. - */ -TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) -{ - RUN_TEST_CASE( Common_Unit_Task_Pool, Error ); - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyMaxThreads ); - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyJobError ); - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ); - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasksError ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_Grow ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ); - RUN_TEST_CASE( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Number of iterations for each test loop. - */ -#ifndef TEST_TASKPOOL_ITERATIONS - #define TEST_TASKPOOL_ITERATIONS ( 200 ) -#endif - -/** - * @brief Define the stress job max duration time (emulated duration). - */ -#ifndef TEST_TASKPOOL_WORK_ITEM_DURATION_MAX - #define TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ( 55 ) -#endif - -/** - * @brief Define the number of long running jobs. - */ -#ifndef TEST_TASKPOOL_LONG_JOBS_NUMBER - #define TEST_TASKPOOL_LONG_JOBS_NUMBER 3 -#endif - -/** - * @brief Define the number of running jobs to grow the taskpool for. - */ -#ifndef TEST_TASKPOOL_NUMBER_OF_JOBS - #define TEST_TASKPOOL_NUMBER_OF_JOBS 4 -#endif - -/** - * @brief Define the number of threads to grow the taskpool to. - */ -#ifndef TEST_TASKPOOL_NUMBER_OF_THREADS - #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TASKPOOL_NUMBER_OF_JOBS - 1 ) -#endif - -/** - * @brief Define the number of threads to grow the taskpool to. - */ -#ifndef TEST_TASKPOOL_MAX_THREADS - #define TEST_TASKPOOL_MAX_THREADS 7 -#endif - -/** - * @brief Define the number of long running jobs. - */ -#ifndef TEST_TASKPOOL_LONG_JOBS_NUMBER - #define TEST_TASKPOOL_LONG_JOBS_NUMBER 3 -#endif - -/** - * @brief Define the number of running jobs to grow the taskpool for. - */ -#ifndef TEST_TASKPOOL_NUMBER_OF_JOBS - #define TEST_TASKPOOL_NUMBER_OF_JOBS 4 -#endif - -/** - * @brief Define the number of threads to grow the taskpool to. - */ -#ifndef TEST_TASKPOOL_NUMBER_OF_THREADS - #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TASKPOOL_NUMBER_OF_JOBS - 1 ) -#endif - -/** - * @brief Define the number of threads to grow the taskpool to. - */ -#ifndef TEST_TASKPOOL_MAX_THREADS - #define TEST_TASKPOOL_MAX_THREADS 7 -#endif - -/** - * @brief One hour in milliseconds. - */ -#define ONE_HOUR_FROM_NOW_MS ( 3600 * 1000 ) - -/* ---------------------------------------------------------- */ - -/** - * @brief A function that emulates some work in the task pool execution by sleeping. - */ -static void EmulateWork() -{ - IotClock_SleepMs( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ); -} - -/** - * @brief A function that emulates some work in the task pool execution by sleeping. - */ -static void EmulateWorkLong() -{ - IotClock_SleepMs( 2000 + ( rand() % TEST_TASKPOOL_WORK_ITEM_DURATION_MAX ) ); -} - -/** - * @brief A callback that does not recycle its job. - */ -static void ExecutionWithoutDestroyCb( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pContext ) -{ - JobUserContext_t * pUserContext; - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t status; - - //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - - error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); - TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); - TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); - - EmulateWork(); - - pUserContext = ( JobUserContext_t * ) pContext; - - IotMutex_Lock( &pUserContext->lock ); - pUserContext->counter++; - IotMutex_Unlock( &pUserContext->lock ); -} - -/** - * @brief A callback that blocks. - */ -static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pContext ) -{ - JobBlockingUserContext_t * pUserContext; - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t status; - - //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - - error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); - TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); - TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); - - pUserContext = ( JobBlockingUserContext_t * ) pContext; - - /* Signal that the callback has been called. */ - IotSemaphore_Post( &pUserContext->signal ); - - /* This callback will emulate a blocking wait, for the sole purpose of stealing a task pool - * thread and test that the taskpool can actually grow as expected. */ - IotSemaphore_Wait( &pUserContext->block ); -} - -/** - * @brief A callback that recycles its job. - */ -static void ExecutionWithRecycleCb( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pContext ) -{ - JobUserContext_t * pUserContext; - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t status; - - //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - - error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); - TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); - TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); - - EmulateWork(); - - pUserContext = ( JobUserContext_t * ) pContext; - - IotMutex_Lock( &pUserContext->lock ); - pUserContext->counter++; - IotMutex_Unlock( &pUserContext->lock ); - - IotTaskPool_RecycleJob( pTaskPool, pJob ); -} - -/** - * @brief A callback that takes a long time and does not recycle its job. - */ -static void ExecutionLongWithoutDestroyCb( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pContext ) -{ - JobUserContext_t * pUserContext; - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t status; - - //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - - error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); - TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); - TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); - - EmulateWorkLong(); - - pUserContext = ( JobUserContext_t * ) pContext; - - IotMutex_Lock( &pUserContext->lock ); - pUserContext->counter++; - IotMutex_Unlock( &pUserContext->lock ); -} - -/** - * @brief A callback that does not recycle its job. - */ -static void BlankExecution( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pContext ) -{ - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t status; - - ( void ) pTaskPool; - ( void ) pJob; - ( void ) pContext; - - //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); - - error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); - TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); - TEST_ASSERT( ( error == IOT_TASKPOOL_SUCCESS ) || ( error == IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS ) ); -} - -/* ---------------------------------------------------------------------------------------------- */ -/* ---------------------------------------------------------------------------------------------- */ -/* ---------------------------------------------------------------------------------------------- */ - -/** - * @brief Number of legal task pool initialization configurations. - */ -#define LEGAL_INFOS 3 - -/** - * @brief Number of illegal task pool initialization configurations. - */ -#define ILLEGAL_INFOS 3 - -/** - * @brief Legal initialization configurations. - */ -static IotTaskPoolInfo_t tpInfoLegal[ LEGAL_INFOS ] = -{ - { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, - { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, - { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } -}; - -/** - * @brief Illegal initialization configurations. - */ -static IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = -{ - { .minThreads = 0, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, - { .minThreads = 1, .maxThreads = 0, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, - { .minThreads = 2, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } -}; - -/*-----------------------------------------------------------*/ - -/** - * @brief Test retrieving error string for each task pool status error. - */ -TEST( Common_Unit_Task_Pool, Error ) -{ - IotTaskPoolError_t error; - const char * errorString = NULL; - - /* Set error to all possible values, and test that the corresponding string is as expected. */ - /* NOTE: by convention, 'success' value must be equal to zero (0). */ - error = IOT_TASKPOOL_SUCCESS; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "SUCCESS", strlen( "SUCCESS" ) ) ); - - error = IOT_TASKPOOL_BAD_PARAMETER; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "BAD PARAMETER", strlen( "BAD PARAMETER" ) ) ); - - error = IOT_TASKPOOL_ILLEGAL_OPERATION; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "ILLEGAL OPERATION", strlen( "ILLEGAL OPERATION" ) ) ); - - error = IOT_TASKPOOL_NO_MEMORY; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "NO MEMORY", strlen( "NO MEMORY" ) ) ); - - error = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "SHUTDOWN IN PROGRESS", strlen( "SHUTDOWN IN PROGRESS" ) ) ); - - error = IOT_TASKPOOL_CANCEL_FAILED; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "CANCEL FAILED", strlen( "CANCEL FAILED" ) ) ); - - error = ( IotTaskPoolError_t ) -1; - errorString = IotTaskPool_strerror( error ); - TEST_ASSERT( 0 == strncmp( errorString, "INVALID STATUS", strlen( "INVALID STATUS" ) ) ); -} - -/** - * @brief Test task pool dynamic memory creation and destruction, with both legal and illegal information. - */ -TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) -{ - uint32_t count; - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - - /* Some legal and illegal create/destroy patterns. */ - for( count = 0; count < LEGAL_INFOS; ++count ) - { - TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ count ], &taskPool ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - } - - for( count = 0; count < ILLEGAL_INFOS; ++count ) - { - TEST_ASSERT( IotTaskPool_Create( &tpInfoIllegal[ count ], &taskPool ) == IOT_TASKPOOL_BAD_PARAMETER ); - } - - TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ 0 ], NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); - TEST_ASSERT( IotTaskPool_Create( NULL, &taskPool ) == IOT_TASKPOOL_BAD_PARAMETER ); - - /* Create a task pool a tweak max threads up & down. */ - { - IotTaskPoolJobStorage_t jobsStorage[ 2 * TEST_TASKPOOL_MAX_THREADS ]; - IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ]; - IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 5 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 3 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 4 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 7 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ - - /* Initialize more jobs than max threads. */ - for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) - { - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - /* Schedule all jobs to make the task pool grow. */ - for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) - { - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test task pool job static and dynamic memory creation with bogus parameters. - */ -TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Trivial parameter validation. */ - { - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job; - - /* NULL callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &jobStorage, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL storage pointer. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); - } - - /* Create/Destroy. */ - { - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job; - - /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - /* Illegally recycle legal static job. */ - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); - } - - /* Create/Destroy. */ - { - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job; - IotTaskPoolJobStatus_t jobStatusAtCancellation; - - /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule deferred, then try to illegally destroy, then cancel */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( taskPool, job, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); - TEST_ASSERT( IotTaskPool_TryCancel( taskPool, job, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); - } - - /* Create/Destroy. */ - { - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job; - - /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule immediate, then try to illegally destroy it. */ - TEST_ASSERT( IotTaskPool_Schedule( taskPool, job, 0 ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test task pool job static and dynamic memory creation with bogus parameters. - */ -TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Trivial parameter validation jobs. */ - { - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - /* NULL callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, NULL, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL engine handle. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( NULL, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); - } - - /* Create/Destroy. */ - { - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Recycle the job. */ - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } - - /* Create/Schedule/Destroy. */ - { - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule deferred, then try to destroy it. */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } - - /* Create/Recycle. */ - { - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Illegally recycle legal static job. */ - TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } - - /* Create/Schedule/Cancel/Recycle. */ - { - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - IotTaskPoolJobStatus_t jobStatusAtCancellation; - - /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - /* Schedule deferred, then try to cancel it and finally recycle it. */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_TryCancel( taskPool, pJob, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); - TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test task pool job static and dynamic memory creation with bogus parameters. - */ -TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Recyclable jobs. */ - { - uint32_t count, jobLimit; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - jobLimit = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - #else - jobLimit = 2 * IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t pJobs[ 2 * TEST_TASKPOOL_ITERATIONS ]; - #endif - - for( count = 0; count < jobLimit; ++count ) - { - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, &pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( pJobs[ count ] != NULL ); - } - - for( count = 0; count < jobLimit; ++count ) - { - TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a job with bad parameters. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasksError ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job; - - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - - /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of jobs: static allocation, bulk execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - IotTaskPoolJob_t pRecyclableJob = IOT_TASKPOOL_JOB_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPoolJobStorage_t tpJobsStorage[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; - IotTaskPoolJobStorage_t tpDeferredJobsStorage[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; - IotTaskPoolJob_t tpDeferredJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Create a recyclable job we will never schedule, just to have it in the cache for code coverage purposes. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, &pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated jobs, schedule all, then wait all. */ - { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolError_t errorSchedule; - - for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobsStorage[ count ], &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - } - } - - /* Destroy the taskpool. It will empty all queues. */ - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a job with bad parameters. - */ -TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); - - /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); - /* Recycle the job, so we do not leak it. */ - TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that the taskpool actually grows the number of tasks as expected. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TASKPOOL_NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobBlockingUserContext_t userContext; - - /* Initialize user context. */ - TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); - TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated job, schedule one, then wait. */ - { - uint32_t count; - IotTaskPoolJobStorage_t jobsStorage[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; - IotTaskPoolJob_t jobs[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; - - /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - /* The callback will block indefinitely, stealing a task pool thread. The task pool will need to grow to pass this test. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) - { - TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); - } - - count = 0; - - while( true ) - { - /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ - IotSemaphore_Wait( &userContext.signal ); - - ++count; - - if( count == TEST_TASKPOOL_NUMBER_OF_JOBS ) - { - break; - } - } - - /* Signal all taskpool threads to exit. */ - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) - { - IotSemaphore_Post( &userContext.block ); - } - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotSemaphore_Destroy( &userContext.signal ); - IotSemaphore_Destroy( &userContext.block ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - - /* Use a taskpool with not enough threads. */ - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TASKPOOL_NUMBER_OF_THREADS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobBlockingUserContext_t userContext; - - /* Initialize user context. */ - TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); - TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated job, schedule one, then wait. */ - { - uint32_t count; - IotTaskPoolJobStorage_t jobsStorage[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; - IotTaskPoolJob_t jobs[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; - - /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - /* The callback will block indefinitely, stealing a task pool thread. The task pool will need to grow to pass this test. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high priority task can make it grow more. */ - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_THREADS; ++count ) - { - TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); - } - - /*Schedule a high priority task can make it grow more. */ - TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); - - count = 0; - - while( true ) - { - /* Wait for the callback to signal the semaphore. It must happen exactly _NUMBER_OF_JOBS times. */ - IotSemaphore_Wait( &userContext.signal ); - - ++count; - - if( count == TEST_TASKPOOL_NUMBER_OF_JOBS ) - { - break; - } - } - - /* Signal all taskpool threads to exit. */ - for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) - { - IotSemaphore_Post( &userContext.block ); - } - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotSemaphore_Destroy( &userContext.signal ); - IotSemaphore_Destroy( &userContext.block ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT_TRUE( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated job, schedule one, then wait. */ - { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job = IOT_TASKPOOL_JOB_INITIALIZER; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, job, 0 ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - TEST_ASSERT( false ); - break; - - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - - /* Ensure callback actually executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT( userContext.counter == scheduled ); - } - - /* Since jobs were build from a static buffer and scheduled one-by-one, we - * should have received all callbacks. - */ - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of non-recyclable jobs: static allocation, sequential execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated job, schedule one, then wait. */ - { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJobStorage_t jobStorage; - IotTaskPoolJob_t job; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, job, 10 + ( rand() % 50 ) ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - TEST_ASSERT( false ); - break; - - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - - /* Ensure callback actually executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT( userContext.counter == scheduled ); - } - - /* Since jobs were build from a static buffer and scheduled one-by-one, we - * should have received all callbacks. - */ - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of recyclable jobs: dynamic allocation, sequential execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Dynamically allocated job, schedule one, then wait. */ - { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) - { - /* Schedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, pJob, 0 ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - - /* Ensure callback actually executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT( userContext.counter == scheduled ); - } - - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); - - /* Since jobs were build from a static buffer and scheduled one-by-one, we - * should have received all callbacks. - */ - TEST_ASSERT( scheduled == TEST_TASKPOOL_ITERATIONS ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of jobs: static allocation, bulk execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated jobs, schedule all, then wait all. */ - { - uint32_t count; - uint32_t scheduled = 0; - IotTaskPoolJobStorage_t tpJobsStorage[ TEST_TASKPOOL_ITERATIONS ]; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; - - for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) - { - /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of jobs: static allocation, bulk execution. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated jobs, schedule all, then wait all. */ - { - uint32_t count, maxJobs; - uint32_t scheduled = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; - #endif - - for( count = 0; count < maxJobs; ++count ) - { - /* Schedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling a set of deferred jobs. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated jobs, schedule all, then wait all. */ - { - uint32_t count, maxJobs; - uint32_t scheduled = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; - #endif - - for( count = 0; count < maxJobs; ++count ) - { - /* Schedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling and re-scheduling (without canceling first) a set of jobs. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated jobs, schedule all, then wait all. */ - { - uint32_t count, maxJobs; - uint32_t scheduled = 0, rescheduled = 0, failedReschedule = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJobStorage_t tpJobsStorage[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - #else - maxJobs = 10; - IotTaskPoolJobStorage_t tpJobsStorage[ 10 ]; - IotTaskPoolJob_t tpJobs[ 10 ]; - #endif - - /* Create all jobs. */ - for( count = 0; count < maxJobs; ++count ) - { - /* Schedule the job to be recycled in the callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - } - - /* Schedule all jobs. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorSchedule; - - /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - /* Give a chance to some jobs to start execution. */ - IotClock_SleepMs( 50 ); - - /* Reschedule all. Some will fail to be rescheduled... */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorReSchedule; - - errorReSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); - - switch( errorReSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - rescheduled++; - break; - - case IOT_TASKPOOL_ILLEGAL_OPERATION: - /* Job already executed. */ - failedReschedule++; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - TEST_ASSERT_TRUE( ( rescheduled + failedReschedule ) == scheduled ); - - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling and re-scheduling (without canceling first) a set of deferred jobs. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) -{ - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Statically allocated jobs, schedule all, then wait all. */ - { - uint32_t count, maxJobs; - uint32_t scheduled = 0, rescheduled = 0; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJobStorage_t tpJobsStorage[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJobStorage_t tpJobsStorage[ TEST_TASKPOOL_ITERATIONS ]; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; - #endif - - /* Schedule all jobs. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorSchedule; - - /* Schedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - - /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], ONE_HOUR_FROM_NOW_MS ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - /* Reschedule all. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorReSchedule; - - errorReSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); - - switch( errorReSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++rescheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - TEST_ASSERT_TRUE( rescheduled == scheduled ); - - /* Wait until callback is executed. */ - while( true ) - { - IotClock_SleepMs( 50 ); - - IotMutex_Lock( &userContext.lock ); - - if( userContext.counter == scheduled ) - { - IotMutex_Unlock( &userContext.lock ); - - break; - } - - IotMutex_Unlock( &userContext.lock ); - } - - TEST_ASSERT_TRUE( userContext.counter == scheduled ); - } - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test scheduling and canceling jobs. - */ -TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) -{ - uint32_t count, maxJobs; - IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - uint32_t canceled = 0; - uint32_t scheduled = 0; - - JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; - - /* In static memory mode, only the recyclable job limit may be allocated. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJobStorage_t jobsStorage[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; - #else - maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJobStorage_t jobsStorage[ TEST_TASKPOOL_ITERATIONS ]; - IotTaskPoolJob_t jobs[ TEST_TASKPOOL_ITERATIONS ]; - #endif - - /* Initialize user context. */ - TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); - - TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); - - if( TEST_PROTECT() ) - { - /* Create and schedule loop. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t errorSchedule; - - IotTaskPoolError_t errorCreate = IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ); - - switch( errorCreate ) - { - case IOT_TASKPOOL_SUCCESS: - break; - - case IOT_TASKPOOL_NO_MEMORY: /* OK. */ - continue; - - case IOT_TASKPOOL_BAD_PARAMETER: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - - errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, jobs[ count ], 10 + ( rand() % 20 ) ); - - switch( errorSchedule ) - { - case IOT_TASKPOOL_SUCCESS: - ++scheduled; - break; - - case IOT_TASKPOOL_BAD_PARAMETER: - case IOT_TASKPOOL_ILLEGAL_OPERATION: - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - } - } - - /* Cancellation loop. */ - for( count = 0; count < maxJobs; ++count ) - { - IotTaskPoolError_t error; - IotTaskPoolJobStatus_t statusAtCancellation = IOT_TASKPOOL_STATUS_READY; - IotTaskPoolJobStatus_t statusAfterCancellation = IOT_TASKPOOL_STATUS_READY; - - error = IotTaskPool_TryCancel( taskPool, jobs[ count ], &statusAtCancellation ); - - switch( error ) - { - case IOT_TASKPOOL_SUCCESS: - canceled++; - - TEST_ASSERT( - ( statusAtCancellation == IOT_TASKPOOL_STATUS_READY ) || - ( statusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ) || - ( statusAtCancellation == IOT_TASKPOOL_STATUS_SCHEDULED ) || - ( statusAtCancellation == IOT_TASKPOOL_STATUS_CANCELED ) - ); - - TEST_ASSERT( IotTaskPool_GetStatus( taskPool, jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( statusAfterCancellation == IOT_TASKPOOL_STATUS_CANCELED ); - break; - - case IOT_TASKPOOL_CANCEL_FAILED: - TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); - TEST_ASSERT( IotTaskPool_GetStatus( taskPool, jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); - break; - - case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: - /* This must be a test issue. */ - TEST_ASSERT( false ); - break; - - default: - TEST_ASSERT( false ); - break; - } - } - - /* Wait until callback is executed. */ - while( ( scheduled - canceled ) != userContext.counter ) - { - IotClock_SleepMs( 50 ); - } - - TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); - } - - TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); - - /* Destroy user context. */ - IotMutex_Destroy( &userContext.lock ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt deleted file mode 100644 index cc48c78b30..0000000000 --- a/libraries/standard/mqtt/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -# Include filepaths for source and include. -include(filePaths.cmake) - -# MQTT library target. -add_library( iotmqtt - ${CONFIG_HEADER_PATH}/iot_config.h - ${MQTT_SOURCES} - include/iot_mqtt.h - include/iot_mqtt_lightweight.h - include/types/iot_mqtt_types.h - src/private/iot_mqtt_internal.h ) - -# MQTT public include path. -target_include_directories( iotmqtt PUBLIC ${MQTT_INCLUDE_PUBLIC_DIRS} ) - -# Link required libraries. -target_link_libraries( iotmqtt PRIVATE iotbase ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotmqtt PRIVATE unity ) -endif() - -# Organization of MQTT in folders. -set_property( TARGET iotmqtt PROPERTY FOLDER libraries/standard ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES include/iot_mqtt.h ) -source_group( include\\types FILES include/types/iot_mqtt_types.h ) -source_group( src FILES ${MQTT_SOURCES} ) -source_group( src\\private FILES src/private/iot_mqtt_internal.h ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # Enable test access in MQTT. - target_compile_definitions( iotmqtt PRIVATE -DIOT_BUILD_TESTS=1 ) - target_include_directories( iotmqtt PUBLIC test/access ) - - # This test library is used to mock MQTT connections. - add_library( iot_mqtt_mock - ${MQTT_TEST_MOCK_SOURCES} - test/mock/iot_tests_mqtt_mock.h - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Organization of MQTT mock library in folders. - set_property( TARGET iot_mqtt_mock PROPERTY FOLDER tests ) - source_group( "" FILES - ${MQTT_TEST_MOCK_SOURCES} - test/mock/iot_tests_mqtt_mock.h - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # The MQTT mock needs the internal MQTT header. - target_include_directories( iot_mqtt_mock - PRIVATE ${MQTT_TEST_MOCK_INCLUDE_PRIVATE_DIRS} - PUBLIC ${MQTT_TEST_MOCK_INCLUDE_PUBLIC_DIRS} ) - - # Link required libraries for MQTT mock. - target_link_libraries( iot_mqtt_mock PRIVATE iotbase iotmqtt unity ) - - # MQTT tests executable. - add_executable( iot_tests_mqtt - ${MQTT_SYSTEM_TEST_SOURCES} - ${MQTT_UNIT_TEST_SOURCES} - test/unit/iot_tests_mqtt_platform.c - test/iot_tests_mqtt.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( iot_tests_mqtt PRIVATE - -DRunTests=RunMqttTests ) - - # The MQTT tests need the internal MQTT header. - target_include_directories( iot_tests_mqtt PRIVATE ${MQTT_TEST_INCLUDE_PRIVATE_DIRS} ) - - # MQTT tests library dependencies. - target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt iotbase unity iot_mqtt_mock ) - - # Organization of MQTT tests in folders. - set_property( TARGET iot_tests_mqtt PROPERTY FOLDER tests ) - source_group( system FILES ${MQTT_SYSTEM_TEST_SOURCES} ) - source_group( unit FILES ${MQTT_UNIT_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/iot_tests_mqtt.c ) -endif() diff --git a/libraries/standard/mqtt/filePaths.cmake b/libraries/standard/mqtt/filePaths.cmake deleted file mode 100644 index d2acb3538a..0000000000 --- a/libraries/standard/mqtt/filePaths.cmake +++ /dev/null @@ -1,47 +0,0 @@ -# This file is to add source files and include directories -# into variables so that it can be reused from different repositories -# in their Cmake based build system by including this file. -# -# Files specific to the repository such as test runner, platform tests -# are not added to the variables. - -# MQTT library source files. -set( MQTT_SOURCES - src/iot_mqtt_api.c - src/iot_mqtt_network.c - src/iot_mqtt_operation.c - src/iot_mqtt_serialize.c - src/iot_mqtt_lightweight_api.c - src/iot_mqtt_helper.c - src/iot_mqtt_static_memory.c - src/iot_mqtt_subscription.c - src/iot_mqtt_validate.c ) - -# MQTT library Include directories. -set( MQTT_INCLUDE_PUBLIC_DIRS - include ) - -# MQTT system test source files. -set( MQTT_SYSTEM_TEST_SOURCES - test/system/iot_tests_mqtt_system.c ) - -# MQTT unit test sources. -set( MQTT_UNIT_TEST_SOURCES - test/unit/iot_tests_mqtt_api.c - test/unit/iot_tests_mqtt_receive.c - test/unit/iot_tests_mqtt_subscription.c - test/unit/iot_tests_mqtt_validate.c ) - -# MQTT test include directories. -set( MQTT_TEST_INCLUDE_PRIVATE_DIRS - src ) - -# MQTT mock source files. -set( MQTT_TEST_MOCK_SOURCES - test/mock/iot_tests_mqtt_mock.c ) - -# MQTT mock include directories. -set( MQTT_TEST_MOCK_INCLUDE_PRIVATE_DIRS - src ) -set( MQTT_TEST_MOCK_INCLUDE_PUBLIC_DIRS - test/mock ) diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h deleted file mode 100644 index 0b46f3ecb2..0000000000 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ /dev/null @@ -1,868 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt.h - * @brief User-facing functions of the MQTT 3.1.1 library. - */ - -#ifndef IOT_MQTT_H_ -#define IOT_MQTT_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/*------------------------- MQTT library functions --------------------------*/ - -/** - * @functionspage{mqtt,MQTT library} - * - @functionname{mqtt_function_init} - * - @functionname{mqtt_function_cleanup} - * - @functionname{mqtt_function_receivecallback} - * - @functionname{mqtt_function_connect} - * - @functionname{mqtt_function_disconnect} - * - @functionname{mqtt_function_subscribeasync} - * - @functionname{mqtt_function_subscribesync} - * - @functionname{mqtt_function_unsubscribeasync} - * - @functionname{mqtt_function_unsubscribesync} - * - @functionname{mqtt_function_publishasync} - * - @functionname{mqtt_function_publishsync} - * - @functionname{mqtt_function_wait} - * - @functionname{mqtt_function_strerror} - * - @functionname{mqtt_function_operationtype} - * - @functionname{mqtt_function_issubscribed} - */ - -/** - * @functionpage{IotMqtt_Init,mqtt,init} - * @functionpage{IotMqtt_Cleanup,mqtt,cleanup} - * @functionpage{IotMqtt_ReceiveCallback,mqtt,receivecallback} - * @functionpage{IotMqtt_Connect,mqtt,connect} - * @functionpage{IotMqtt_Disconnect,mqtt,disconnect} - * @functionpage{IotMqtt_SubscribeAsync,mqtt,subscribeasync} - * @functionpage{IotMqtt_SubscribeSync,mqtt,subscribesync} - * @functionpage{IotMqtt_UnsubscribeAsync,mqtt,unsubscribeasync} - * @functionpage{IotMqtt_UnsubscribeSync,mqtt,unsubscribesync} - * @functionpage{IotMqtt_PublishAsync,mqtt,publishasync} - * @functionpage{IotMqtt_PublishSync,mqtt,publishsync} - * @functionpage{IotMqtt_Wait,mqtt,wait} - * @functionpage{IotMqtt_strerror,mqtt,strerror} - * @functionpage{IotMqtt_OperationType,mqtt,operationtype} - * @functionpage{IotMqtt_IsSubscribed,mqtt,issubscribed} - */ - -/** - * @brief One-time initialization function for the MQTT library. - * - * This function performs setup of the MQTT library. It must be called - * once (and only once) before calling any other MQTT function. Calling this - * function more than once without first calling @ref mqtt_function_cleanup - * may result in a crash. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NOT_INITIALIZED - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see @ref mqtt_function_cleanup - */ -/* @[declare_mqtt_init] */ -IotMqttError_t IotMqtt_Init( void ); -/* @[declare_mqtt_init] */ - -/** - * @brief One-time deinitialization function for the MQTT library. - * - * This function frees resources taken in @ref mqtt_function_init. It should be - * called after [closing all MQTT connections](@ref mqtt_function_disconnect) to - * clean up the MQTT library. After this function returns, @ref mqtt_function_init - * must be called again before calling any other MQTT function. - * - * @warning No thread-safety guarantees are provided for this function. Do not - * call this function if any MQTT connections are open! - * - * @see @ref mqtt_function_init - */ -/* @[declare_mqtt_cleanup] */ -void IotMqtt_Cleanup( void ); -/* @[declare_mqtt_cleanup] */ - -/** - * @brief Network receive callback for the MQTT library. - * - * This function should be called by the system whenever data is available for - * the MQTT library. - * - * @param[in] pNetworkConnection The network connection associated with the MQTT - * connection, passed by the network stack. - * @param[in] pReceiveContext A pointer to the MQTT connection handle for which - * the packet was received. - */ -/* @[declare_mqtt_receivecallback] */ -void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, - void * pReceiveContext ); -/* @[declare_mqtt_receivecallback] */ - -/** - * @brief Establish a new MQTT connection. - * - * This function opens a connection between a new MQTT client and an MQTT server - * (also called a broker). MQTT connections are established on top of transport - * layer protocols (such as TCP/IP), and optionally, application layer security - * protocols (such as TLS). The MQTT packet that establishes a connection is called - * the MQTT CONNECT packet. After @ref mqtt_function_init, this function must be - * called before any other MQTT library function. - * - * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `true`, - * this function establishes a clean MQTT session. Subscriptions and unacknowledged - * PUBLISH messages will be discarded when the connection is closed. - * - * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `false`, - * this function establishes (or re-establishes) a persistent MQTT session. The parameters - * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions) - * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) - * may be used to restore subscriptions present in a re-established persistent session. - * Any restored subscriptions MUST have been present in the persistent session; - * this function does not send an MQTT SUBSCRIBE packet! - * - * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions) - * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) can - * also be used to pass a list of subscriptions to be stored locally without a SUBSCRIBE packet being - * sent to the broker. These subscriptions are useful to invoke application level callbacks for messages received - * on unsolicited topics from the broker. - * - * This MQTT library is network agnostic, meaning it has no knowledge of the - * underlying network protocol carrying the MQTT packets. It interacts with the - * network through a network abstraction layer, allowing it to be used with many - * different network stacks. The network abstraction layer is established - * per-connection, allowing every #IotMqttConnection_t to use a different network - * stack. The parameter `pNetworkInterface` sets up the network abstraction layer - * for an MQTT connection; see the documentation on #IotMqttNetworkInfo_t for details - * on its members. - * - * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet. - * Most members [are defined by the MQTT spec.](@ref IotMqttConnectInfo_t). The - * [pConnectInfo->pWillInfo](@ref IotMqttConnectInfo_t.pWillInfo) member provides - * information on a Last Will and Testament (LWT) message to be published if the - * MQTT connection is closed without [sending a DISCONNECT packet] - * (@ref mqtt_function_disconnect). Unlike other PUBLISH - * messages, a LWT message payload is limited to 65535 bytes in length. Additionally, - * the retry [interval](@ref IotMqttPublishInfo_t.retryMs) and [limit] - * (@ref IotMqttPublishInfo_t.retryLimit) members of #IotMqttPublishInfo_t - * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL. - * - * MQTT keep-alive is configured by @ref IotMqttConnectInfo_t.keepAliveSeconds. - * Keep-alive is used to detect half-open or otherwise unusable network connections. - * An MQTT client will send periodic ping requests (PINGREQ) to the server if the - * connection is idle. The MQTT server must respond to ping requests with a ping - * response (PINGRESP). The standard does not specify how long the server has to - * respond to a ping request, noting only a "reasonable amount of time". In this library, - * the amount of time a server has to respond to a ping request is set with - * @ref IOT_MQTT_RESPONSE_WAIT_MS. - * - * Unlike @ref mqtt_function_publishasync, @ref mqtt_function_subscribeasync, and - * @ref mqtt_function_unsubscribeasync, this function is always blocking. Additionally, - * because the MQTT connection acknowledgement packet (CONNACK packet) does not - * contain any information on which CONNECT packet it acknowledges, only one - * CONNECT operation may be in progress at any time. This means that parallel - * threads making calls to @ref mqtt_function_connect will be serialized to send - * their CONNECT packets one-by-one. - * - * @param[in] pNetworkInfo Information on the transport-layer network connection - * to use with the MQTT connection. - * @param[in] pConnectInfo MQTT connection setup parameters. - * @param[in] timeoutMs If the MQTT server does not accept the connection within - * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. - * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle - * if this function succeeds. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_TIMEOUT - * - #IOT_MQTT_SERVER_REFUSED - * - * Example - * @code{c} - * - * // Callback function to receive messages from the broker on an unsolicited topic. - * void unsolicitedMessageCallback( void * pArgument, IotMqttCallbackParam_t * pPublish ); - * - * // Parameters to MQTT connect. - * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - * - * // A local subscription to receive messages from the broker on an unsolicited topic. - * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - * - * // Example network abstraction types. - * IotNetworkServerInfo_t serverInfo = { ... }; - * IotNetworkCredentials_t credentialInfo = { ... }; - * IotNetworkInterface_t networkInterface = { ... }; - * - * // Example using a generic network implementation. - * networkInfo.createNetworkConnection = true; - * networkInfo.u.setup.pNetworkServerInfo = &serverInfo; - * networkInfo.u.setup.pNetworkCredentialInfo = &credentialInfo; - * networkInfo.pNetworkInterface = &networkInterface; - * - * // Set the members of the connection info (password and username not used). - * connectInfo.cleanSession = true; - * connectInfo.keepAliveSeconds = 30; - * connectInfo.pClientIdentifier = "uniqueclientidentifier"; - * connectInfo.clientIdentifierLength = 22; - * - * // Set the members of the will info (retain and retry not used). - * willInfo.qos = IOT_MQTT_QOS_1; - * willInfo.pTopicName = "will/topic/name"; - * willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); - * willInfo.pPayload = "MQTT client unexpectedly disconnected."; - * willInfo.payloadLength = strlen( willInfo.pPayload ); - * - * // Set the pointer to the will info. - * connectInfo.pWillInfo = &willInfo; - * - * // [Optional] Set a local subscription to receive the broker messages on an unsolicited topic. - * subscription.qos = IOT_MQTT_QOS_0; - * subscription.pTopicFilter = "some/unsolicited/topic"; - * subscription.topicLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - * subscription.callback.function = unsolicitedMessageCallback; - * connectInfo.pPreviousSubscriptions = &subscription; - * connectInfo.previousSubscriptionCount = 1; - * - * - * // Call CONNECT with a 5 second block time. Should return - * // IOT_MQTT_SUCCESS when successful. - * IotMqttError_t result = IotMqtt_Connect( &networkInfo, - * &connectInfo, - * 5000, - * &mqttConnection ); - * - * if( result == IOT_MQTT_SUCCESS ) - * { - * // Do something with the MQTT connection... - * - * // Clean up and close the MQTT connection once it's no longer needed. - * IotMqtt_Disconnect( mqttConnection, 0 ); - * } - * @endcode - */ -/* @[declare_mqtt_connect] */ -IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, - const IotMqttConnectInfo_t * pConnectInfo, - uint32_t timeoutMs, - IotMqttConnection_t * const pMqttConnection ); -/* @[declare_mqtt_connect] */ - -/** - * @brief Closes an MQTT connection and frees resources. - * - * This function closes an MQTT connection and should only be called once - * the MQTT connection is no longer needed. Its exact behavior depends on the - * `flags` parameter. - * - * Normally, `flags` should be `0`. This gracefully shuts down an MQTT - * connection by sending an MQTT DISCONNECT packet. Any [network close function] - * (@ref IotNetworkInterface_t::close) provided [when the connection was established] - * (@ref mqtt_function_connect) will also be called. Note that because the MQTT server - * will not acknowledge a DISCONNECT packet, the client has no way of knowing if - * the server received the DISCONNECT packet. In the case where the DISCONNECT - * packet is lost in transport, any Last Will and Testament (LWT) message established - * with the connection may be published. However, if the DISCONNECT reaches the - * MQTT server, the LWT message will be discarded and not published. - * - * Should the underlying network connection become unusable, this function should - * be called with `flags` set to #IOT_MQTT_FLAG_CLEANUP_ONLY. In this case, no - * DISCONNECT packet will be sent, though the [network close function](@ref IotNetworkInterface_t::close) - * will still be called. This function will only free the resources used by the MQTT - * connection; it still must be called even if the network is offline to avoid leaking - * resources. - * - * @ref mqtt_function_disconnect modifies `mqttConnection`, so it shouldn't - * be used after calling this function. - * - * @param[in] mqttConnection The MQTT connection to close and clean up. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - */ -/* @[declare_mqtt_disconnect] */ -void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, - uint32_t flags ); -/* @[declare_mqtt_disconnect] */ - -/** - * @brief Subscribes to the given array of topic filters and optionally - * receive an asynchronous notification when the subscribe completes. - * - * This function sends an MQTT SUBSCRIBE packet to the server. A SUBSCRIBE - * packet notifies the server to send any matching PUBLISH messages to this client. - * A single SUBSCRIBE packet may carry more than one topic filter, hence the - * parameters to this function include an array of [subscriptions] - * (@ref IotMqttSubscription_t). - * - * An MQTT subscription has two pieces: - * 1. The subscription topic filter registered with the MQTT server. The MQTT - * SUBSCRIBE packet sent from this client to server notifies the server to send - * messages matching the given topic filters to this client. - * 2. The [callback function](@ref IotMqttCallbackInfo_t.function) that this - * client will invoke when an incoming message is received. The callback function - * notifies applications of an incoming PUBLISH message. - * - * The helper function @ref mqtt_function_issubscribed can be used to check if a - * [callback function](@ref IotMqttCallbackInfo_t.function) is registered for - * a particular topic filter. - * - * To modify an already-registered subscription callback, call this function with - * a new `pSubscriptionList`. Any topic filters in `pSubscriptionList` that already - * have a registered callback will be replaced with the new values in `pSubscriptionList`. - * - * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid - * for subscription QoS. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription. - * @param[in] pSubscriptionList Pointer to the first element in the array of - * subscriptions. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable). - * @param[out] pSubscribeOperation Set to a handle by which this operation may be - * referenced after this function returns. This reference is invalidated once - * the subscription operation completes. - * - * @return This function will return #IOT_MQTT_STATUS_PENDING upon success. - * @return Upon completion of the subscription (either through an - * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_SERVER_REFUSED - * @return If this function fails before queuing a subscribe operation, it will return - * one of: - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - * @see @ref mqtt_function_subscribesync for a blocking variant of this function. - * @see @ref mqtt_function_unsubscribeasync for the function that removes subscriptions. - * - * Example - * @code{c} - * #define NUMBER_OF_SUBSCRIPTIONS ... - * - * // Subscription callback function. - * void subscriptionCallback( void * pArgument, IotMqttCallbackParam_t * pPublish ); - * - * // An initialized and connected MQTT connection. - * IotMqttConnection_t mqttConnection; - * - * // Subscription information. - * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - * IotMqttOperation_t lastOperation = IOT_MQTT_OPERATION_INITIALIZER; - * - * // Set the subscription information. - * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) - * { - * pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; - * pSubscriptions[ i ].pTopicFilter = "some/topic/filter"; - * pSubscriptions[ i ].topicLength = ( uint16_t ) strlen( pSubscriptions[ i ].pTopicFilter ); - * pSubscriptions[ i ].callback.function = subscriptionCallback; - * } - * - * IotMqttError_t result = IotMqtt_SubscribeAsync( mqttConnection, - * pSubscriptions, - * NUMBER_OF_SUBSCRIPTIONS, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &lastOperation ); - * - * // Subscribe returns IOT_MQTT_STATUS_PENDING when successful. Wait up to - * // 5 seconds for the operation to complete. - * if( result == IOT_MQTT_STATUS_PENDING ) - * { - * result = IotMqtt_Wait( subscriptionRef, 5000 ); - * } - * - * // Check that the subscriptions were successful. - * if( result == IOT_MQTT_SUCCESS ) - * { - * // Wait for messages on the subscription topic filters... - * - * // Unsubscribe once the subscriptions are no longer needed. - * result = IotMqtt_UnsubscribeAsync( mqttConnection, - * pSubscriptions, - * NUMBER_OF_SUBSCRIPTIONS, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &lastOperation ); - * - * // UNSUBSCRIBE returns IOT_MQTT_STATUS_PENDING when successful. - * // Wait up to 5 seconds for the operation to complete. - * if( result == IOT_MQTT_STATUS_PENDING ) - * { - * result = IotMqtt_Wait( lastOperation, 5000 ); - * } - * } - * // Check which subscriptions were rejected by the server. - * else if( result == IOT_MQTT_SERVER_REFUSED ) - * { - * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) - * { - * if( IotMqtt_IsSubscribed( mqttConnection, - * pSubscriptions[ i ].pTopicFilter, - * pSubscriptions[ i ].topicFilterLength, - * NULL ) == false ) - * { - * // This subscription was rejected. - * } - * } - * } - * @endcode - */ -/* @[declare_mqtt_subscribeasync] */ -IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pSubscribeOperation ); -/* @[declare_mqtt_subscribeasync] */ - -/** - * @brief Subscribes to the given array of topic filters with a timeout. - * - * This function sends an MQTT SUBSCRIBE packet to the server, then waits for - * a server response to the packet. Internally, this function is a call to @ref - * mqtt_function_subscribeasync followed by @ref mqtt_function_wait. See @ref - * mqtt_function_subscribeasync for more information about the MQTT SUBSCRIBE operation. - * - * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid - * for subscription QoS. - * - * @param[in] mqttConnection The MQTT connection to use for the subscription. - * @param[in] pSubscriptionList Pointer to the first element in the array of - * subscriptions. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * Currently, flags are ignored by this function; this parameter is for - * future-compatibility. - * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within - * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_TIMEOUT - * - #IOT_MQTT_SERVER_REFUSED - */ -/* @[declare_mqtt_subscribesync] */ -IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_mqtt_subscribesync] */ - -/** - * @brief Unsubscribes from the given array of topic filters and optionally - * receive an asynchronous notification when the unsubscribe completes. - * - * This function sends an MQTT UNSUBSCRIBE packet to the server. An UNSUBSCRIBE - * packet removes registered topic filters from the server. After unsubscribing, - * the server will no longer send messages on these topic filters to the client. - * - * Corresponding [subscription callback functions](@ref IotMqttCallbackInfo_t.function) - * are also removed from the MQTT connection. These subscription callback functions - * will be removed even if the MQTT UNSUBSCRIBE packet fails to send. - * - * @param[in] mqttConnection The MQTT connection used for the subscription. - * @param[in] pSubscriptionList Pointer to the first element in the array of - * subscriptions. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable). - * @param[out] pUnsubscribeOperation Set to a handle by which this operation may be - * referenced after this function returns. This reference is invalidated once - * the unsubscribe operation completes. - * - * @return This function will return #IOT_MQTT_STATUS_PENDING upon success. - * @return Upon completion of the unsubscribe (either through an - * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * @return If this function fails before queuing an unsubscribe operation, it will return - * one of: - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - * @see @ref mqtt_function_unsubscribesync for a blocking variant of this function. - * @see @ref mqtt_function_subscribeasync for the function that adds subscriptions. - */ -/* @[declare_mqtt_unsubscribeasync] */ -IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pUnsubscribeOperation ); -/* @[declare_mqtt_unsubscribeasync] */ - -/** - * @brief Unsubscribes from a given array of topic filters with a timeout. - * - * This function sends an MQTT UNSUBSCRIBE packet to the server, then waits - * for a server response to the packet. Internally, this function is a call to - * @ref mqtt_function_unsubscribeasync followed by @ref mqtt_function_wait. See @ref - * mqtt_function_unsubscribeasync for more information about the MQTT UNSUBSCRIBE - * operation. - * - * @param[in] mqttConnection The MQTT connection used for the subscription. - * @param[in] pSubscriptionList Pointer to the first element in the array of - * subscriptions. - * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * Flags are currently ignored but reserved for future use. - * @param[in] timeoutMs If the MQTT server does not acknowledge the UNSUBSCRIBE within - * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - */ -/* @[declare_mqtt_unsubscribesync] */ -IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_mqtt_unsubscribesync] */ - -/** - * @brief Publishes a message to the given topic name and optionally - * receive an asynchronous notification when the publish completes. - * - * This function sends an MQTT PUBLISH packet to the server. A PUBLISH packet - * contains a payload and a topic name. Any clients with a subscription on a - * topic filter matching the PUBLISH topic name will receive a copy of the - * PUBLISH packet from the server. - * - * If a PUBLISH packet fails to reach the server and it is not a QoS 0 message, - * it will be retransmitted. See #IotMqttPublishInfo_t for a description - * of the retransmission strategy. - * - * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid - * for message QoS. - * - * @param[in] mqttConnection The MQTT connection to use for the publish. - * @param[in] pPublishInfo MQTT publish parameters. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable). - * @param[out] pPublishOperation Set to a handle by which this operation may be - * referenced after this function returns. This reference is invalidated once - * the publish operation completes. - * - * @return This function will return #IOT_MQTT_STATUS_PENDING upon success for - * QoS 1 publishes. For a QoS 0 publish it returns #IOT_MQTT_SUCCESS upon - * success. - * @return Upon completion of a QoS 1 publish (either through an - * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) - * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). - * @return If this function fails before queuing an publish operation (regardless - * of QoS), it will return one of: - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - * @note The parameters `pCallbackInfo` and `pPublishOperation` should only be used for QoS - * 1 publishes. For QoS 0, they should both be `NULL`. - * - * @see @ref mqtt_function_publishsync for a blocking variant of this function. - * - * Example - * @code{c} - * // An initialized and connected MQTT connection. - * IotMqttConnection_t mqttConnection; - * - * // Publish information. - * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - * - * // Set the publish information. QoS 0 example (retain not used): - * publishInfo.qos = IOT_MQTT_QOS_0; - * publishInfo.pTopicName = "some/topic/name"; - * publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - * publishInfo.pPayload = "payload"; - * publishInfo.payloadLength = strlen( publishInfo.pPayload ); - * - * // QoS 0 publish should return IOT_MQTT_SUCCESS upon success. - * IotMqttError_t qos0Result = IotMqtt_PublishAsync( mqttConnection, - * &publishInfo, - * 0, - * NULL, - * NULL ); - * - * // QoS 1 with retry example (using same topic name and payload as QoS 0 example): - * IotMqttOperation_t qos1Operation = IOT_MQTT_OPERATION_INITIALIZER; - * publishInfo.qos = IOT_MQTT_QOS_1; - * publishInfo.retryMs = 1000; // Retry if no response is received in 1 second. - * publishInfo.retryLimit = 5; // Retry up to 5 times. - * - * // QoS 1 publish should return IOT_MQTT_STATUS_PENDING upon success. - * IotMqttError_t qos1Result = IotMqtt_PublishAsync( mqttConnection, - * &publishInfo, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &qos1Operation ); - * - * // Wait up to 5 seconds for the publish to complete. - * if( qos1Result == IOT_MQTT_STATUS_PENDING ) - * { - * qos1Result = IotMqtt_Wait( qos1Operation, 5000 ); - * } - * @endcode - */ -/* @[declare_mqtt_publishasync] */ -IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pPublishOperation ); -/* @[declare_mqtt_publishasync] */ - -/** - * @brief Publish a message to the given topic name with a timeout. - * - * This function sends an MQTT PUBLISH packet to the server, then waits for - * a server response to the packet. Internally, this function is a call to @ref - * mqtt_function_publishasync followed by @ref mqtt_function_wait. See @ref - * mqtt_function_publishasync for more information about the MQTT PUBLISH operation. - * - * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid - * for message QoS. - * - * @param[in] mqttConnection The MQTT connection to use for the publish. - * @param[in] pPublishInfo MQTT publish parameters. - * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * Currently, flags are ignored by this function; this parameter is for - * future-compatibility. - * @param[in] timeoutMs If the MQTT server does not acknowledge a QoS 1 PUBLISH - * within this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. - * This parameter is ignored for QoS 0 PUBLISH messages. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_NOT_INITIALIZED - * - #IOT_MQTT_BAD_PARAMETER - * - #IOT_MQTT_NO_MEMORY - * - #IOT_MQTT_NETWORK_ERROR - * - #IOT_MQTT_SCHEDULING_ERROR - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) - * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). - */ -/* @[declare_mqtt_publishsync] */ -IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_mqtt_publishsync] */ - -/** - * @brief Waits for an operation to complete. - * - * This function blocks to wait for a [subscribe](@ref mqtt_function_subscribeasync), - * [unsubscribe](@ref mqtt_function_unsubscribeasync), or [publish] - * (@ref mqtt_function_publishasync) to complete. These operations are by default - * asynchronous; the function calls queue an operation for processing, and a - * callback is invoked once the operation is complete. - * - * To use this function, the flag #IOT_MQTT_FLAG_WAITABLE must have been - * set in the operation's function call. Additionally, this function must always - * be called with any waitable operation to clean up resources. - * - * Regardless of its return value, this function always clean up resources used - * by the waitable operation. This means `reference` is invalidated as soon as - * this function returns, even if it returns #IOT_MQTT_TIMEOUT or another error. - * - * @param[in] operation Reference to the operation to wait for. The flag - * #IOT_MQTT_FLAG_WAITABLE must have been set for this operation. - * @param[in] timeoutMs How many milliseconds to wait before returning - * #IOT_MQTT_TIMEOUT. - * - * @return The return value of this function depends on the MQTT operation associated - * with `reference`. See #IotMqttError_t for possible return values. - * - * Example - * @code{c} - * // Operation reference and timeout. - * IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - * uint32_t timeoutMs = 5000; // 5 seconds - * - * // MQTT operation to wait for. - * IotMqttError_t result = IotMqtt_PublishAsync( mqttConnection, - * &publishInfo, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &publishOperation ); - * - * // Publish should have returned IOT_MQTT_STATUS_PENDING. The call to wait - * // returns once the result of the publish is available or the timeout expires. - * if( result == IOT_MQTT_STATUS_PENDING ) - * { - * result = IotMqtt_Wait( publishOperation, timeoutMs ); - * - * // After the call to wait, the result of the publish is known - * // (not IOT_MQTT_STATUS_PENDING). - * assert( result != IOT_MQTT_STATUS_PENDING ); - * } - * @endcode - */ -/* @[declare_mqtt_wait] */ -IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, - uint32_t timeoutMs ); -/* @[declare_mqtt_wait] */ - -/*-------------------------- MQTT helper functions --------------------------*/ - -/** - * @brief Returns a string that describes an #IotMqttError_t. - * - * Like the POSIX `strerror`, this function returns a string describing a - * return code. In this case, the return code is an MQTT library error code, - * `status`. - * - * The string returned by this function MUST be treated as read-only: any - * attempt to modify its contents may result in a crash. Therefore, this function - * is limited to usage in logging. - * - * @param[in] status The status to describe. - * - * @return A read-only string that describes `status`. - * - * @warning The string returned by this function must never be modified. - */ -/* @[declare_mqtt_strerror] */ -const char * IotMqtt_strerror( IotMqttError_t status ); -/* @[declare_mqtt_strerror] */ - -/** - * @brief Returns a string that describes an #IotMqttOperationType_t. - * - * This function returns a string describing an MQTT operation type, `operation`. - * - * The string returned by this function MUST be treated as read-only: any - * attempt to modify its contents may result in a crash. Therefore, this function - * is limited to usage in logging. - * - * @param[in] operation The operation to describe. - * - * @return A read-only string that describes `operation`. - * - * @warning The string returned by this function must never be modified. - */ -/* @[declare_mqtt_operationtype] */ -const char * IotMqtt_OperationType( IotMqttOperationType_t operation ); -/* @[declare_mqtt_operationtype] */ - -/** - * @brief Check if an MQTT connection has a subscription for a topic filter. - * - * This function checks whether an MQTT connection `mqttConnection` has a - * subscription callback registered for a topic filter `pTopicFilter`. If a - * subscription callback is found, its details are copied into the output parameter - * `pCurrentSubscription`. This subscription callback will be invoked for incoming - * PUBLISH messages on `pTopicFilter`. - * - * The check for a matching subscription is only performed client-side; - * therefore, this function should not be relied upon for perfect accuracy. For - * example, this function may return an incorrect result if the MQTT server - * crashes and drops subscriptions without informing the client. - * - * Note that an MQTT connection's subscriptions might change between the time this - * function checks the subscription list and its caller tests the return value. - * This function certainly should not be used concurrently with any pending SUBSCRIBE - * or UNSUBSCRIBE operations. - * - * One suitable use of this function is to check which subscriptions were rejected - * if @ref mqtt_function_subscribeasync returns #IOT_MQTT_SERVER_REFUSED; that return - * code only means that at least one subscription was rejected. - * - * @param[in] mqttConnection The MQTT connection to check. - * @param[in] pTopicFilter The topic filter to check. - * @param[in] topicFilterLength Length of `pTopicFilter`. - * @param[out] pCurrentSubscription If a subscription is found, its details are - * copied here. This output parameter is only valid if this function returns `true` (`NULL` to disable). - * - * @return `true` if a subscription was found; `false` otherwise. - * - * @note The subscription QoS is not stored by the MQTT library; therefore, - * `pCurrentSubscription->qos` will always be set to #IOT_MQTT_QOS_0. - */ -/* @[declare_mqtt_issubscribed] */ -bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, - const char * pTopicFilter, - uint16_t topicFilterLength, - IotMqttSubscription_t * const pCurrentSubscription ); -/* @[declare_mqtt_issubscribed] */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Backwards compatibility macros for previous function names. - */ -#define IotMqtt_Subscribe IotMqtt_SubscribeAsync -#define IotMqtt_TimedSubscribe IotMqtt_SubscribeSync -#define IotMqtt_Unsubscribe IotMqtt_UnsubscribeAsync -#define IotMqtt_TimedUnsubscribe IotMqtt_UnsubscribeSync -#define IotMqtt_Publish IotMqtt_PublishAsync -#define IotMqtt_TimedPublish IotMqtt_PublishSync -/** @endcond */ - -#endif /* ifndef IOT_MQTT_H_ */ diff --git a/libraries/standard/mqtt/include/iot_mqtt_lightweight.h b/libraries/standard/mqtt/include/iot_mqtt_lightweight.h deleted file mode 100644 index 6a38f060b4..0000000000 --- a/libraries/standard/mqtt/include/iot_mqtt_lightweight.h +++ /dev/null @@ -1,702 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_lightweight.h - * @brief User-facing functions for serializing MQTT 3.1.1 packets. This header should - * be included for building a single threaded light-weight MQTT client bypassing - * stateful CSDK MQTT library. - */ - -#ifndef _IOT_MQTT_LIGHTWEIGHT_H_ -#define _IOT_MQTT_LIGHTWEIGHT_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/*------------------------- MQTT library functions --------------------------*/ - -/** - * @functionspage{mqtt,MQTT library} - * - @functionname{mqtt_function_getconnectpacketsize} - * - @functionname{mqtt_function_serializeconnect} - * - @functionname{mqtt_function_getsubscriptionpacketsize} - * - @functionname{mqtt_function_serializesubscribe} - * - @functionname{mqtt_function_serializeunsubscribe} - * - @functionname{mqtt_function_getpublishpacketsize} - * - @functionname{mqtt_function_serializepublish} - * - @functionname{mqtt_function_serializedisconnect} - * - @functionname{mqtt_function_serializepingreq} - * - @functionname{mqtt_function_getincomingmqttpackettypeandlength} - * - @functionname{mqtt_function_deserializeresponse} - * - @functionname{mqtt_function_deserializepublish} - */ - -/** - * @functionpage{IotMqtt_GetConnectPacketSize,mqtt,getconnectpacketsize} - * @functionpage{IotMqtt_SerializeConnect,mqtt,serializeconnect} - * @functionpage{IotMqtt_GetSubscriptionPacketSize,mqtt,getsubscriptionpacketsize} - * @functionpage{IotMqtt_SerializeSubscribe,mqtt,serializesubscribe} - * @functionpage{IotMqtt_SerializeUnsubscribe,mqtt,serializeunsubscribe} - * @functionpage{IotMqtt_GetPublishPacketSize,mqtt,getpublishpacketsize} - * @functionpage{IotMqtt_SerializePublish,mqtt,serializepublish} - * @functionpage{IotMqtt_SerializeDisconnect,mqtt,serializedisconnect} - * @functionpage{IotMqtt_SerializePingreq,mqtt,serializepingreq} - * @functionpage{IotMqtt_GetIncomingMQTTPacketTypeAndLength,mqtt,getincomingmqttpackettypeandlength} - * @functionpage{IotMqtt_DeserializeResponse,mqtt,deserializeresponse} - * @functionpage{IotMqtt_DeserializePublish,mqtt,deserializepublish} - */ - -/** - * @brief Calculate the size and "Remaining length" of a CONNECT packet generated - * from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information struct. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return IOT_MQTT_SUCCESS if the packet is within the length allowed by MQTT 3.1.1 spec; - * IOT_MQTT_BAD_PARAMETER otherwise. If this function returns `IOT_MQTT_BAD_PARAMETER`, - * the output parameters should be ignored. - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how IotMqtt_GetConnectPacketSize() should be used to calculate - * // the size of connect request. - * - * IotMqttConnectInfo_t xConnectInfo; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * IotMqttError_t xResult; - * - * // start with everything set to zero - * memset( ( void * ) &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); - * - * // Initialize connection info, details are out of scope for this example. - * _initializeConnectInfo( &xConnectInfo ); - * // Get size requirement for the connect packet - * xResult = IotMqtt_GetConnectPacketSize( &xConnectInfo, &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * - * // Application should allocate buffer with size == xPacketSize or use static buffer - * // with size >= xPacketSize to serialize connect request. - * @endcode - */ -/* @[declare_mqtt_getconnectpacketsize] */ -IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); -/* @[declare_mqtt_getconnectpacketsize] */ - -/** - * @brief Generate a CONNECT packet from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[in] remainingLength remaining length of the packet to be serialized. - * @param[in, out] pBuffer User provided buffer where the CONNECT packet is written. - * @param[in] bufferSize Size of the buffer pointed to by pBuffer. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - * - * @note pBuffer must be allocated by caller. Use @ref mqtt_function_getconnectpacketsize - * to determine the required size. - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how IotMqtt_SerializeConnect() should be used to serialize - * // MQTT connect packet and send it to MQTT broker. - * // Example uses static memory but dynamically allocated memory can be used as well. - * // Get size requirement for the connect packet. - * - * #define mqttexampleSHARED_BUFFER_SIZE 100 - * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; - * void sendConnectPacket( int xMQTTSocket ) - * { - * IotMqttConnectInfo_t xConnectInfo; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * IotMqttError_t xResult; - * size_t xSentBytes = 0; - * // Get size requirement for MQTT connect packet. - * xResult = IotMqtt_GetConnectPacketSize( &xConnectInfo, &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // Make sure the packet size is less than static buffer size - * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * // Serialize MQTT connect packet into provided buffer - * xResult = IotMqtt_SerializeConnect( &xConnectInfo, xRemainingLength, ucSharedBuffer, xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); - * IotMqtt_Assert( xSentBytes == xPacketSize ); - * } - * @endcode - */ -/* @[declare_mqtt_serializeconnect] */ -IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pBuffer, - size_t bufferSize ); -/* @[declare_mqtt_serializeconnect] */ - -/** - * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE - * packet generated from the given parameters. - * - * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return IOT_MQTT_SUCCESS if the packet is within the length allowed by MQTT 3.1.1 spec; - * IOT_MQTT_BAD_PARAMETER otherwise. If this function returns IOT_MQTT_BAD_PARAMETER, - * the output parameters should be ignored. - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how IotMqtt_GetSubscriptionPacketSize() should be used to calculate - * // the size of subscribe or unsubscribe request. - * - * IotMqttError_t xResult; - * IotMqttSubscription_t xMQTTSubscription[ 1 ]; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * - * // Initialize Subscribe parameters. Details are out of scope for this example. - * // It will involve setting QOS, topic filter and topic filter length. - * _initializeSubscribe( xMQTTSubscription ); - * - * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - * xMQTTSubscription, - * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), - * &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * - * // Application should allocate buffer with size == xPacketSize or use static buffer - * // with size >= xPacketSize to serialize connect request. - * @endcode - */ -/* @[declare_mqtt_getsubscriptionpacketsize] */ -IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ); -/* @[declare_mqtt_getsubscriptionpacketsize] */ - -/** - * @brief Generate a SUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[in] remainingLength remaining length of the packet to be serialized. - * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. - * @param[in, out] pBuffer User provide buffer where the SUBSCRIBE packet is written. - * @param[in] bufferSize Size of the buffer pointed to by pBuffer. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - * - * @note pBuffer must be allocated by caller. - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * Example - * @code{c} - * // Example code below shows how IotMqtt_SerializeSubscribe() should be used to serialize - * // MQTT Subscribe packet and send it to MQTT broker. - * // Example uses static memory, but dynamically allocated memory can be used as well. - * // Get size requirement for the MQTT subscribe packet. - * - * #define mqttexampleSHARED_BUFFER_SIZE 100 - * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; - * void sendSubscribePacket( int xMQTTSocket ) - * { - * IotMqttSubscription_t xMQTTSubscription[ 1 ]; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * IotMqttError_t xResult; - * size_t xSentBytes = 0; - * - * // Initialize Subscribe parameters. Details are out of scope for this example. - * // It will involve setting QOS, topic filter and topic filter length. - * _initializeSubscribe( xMQTTSubscription ); - * // Get size requirement for MQTT Subscribe packet. - * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - * xMQTTSubscription, - * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), - * &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // Make sure the packet size is less than static buffer size. - * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * - * // Serialize subscribe into statically allocated ucSharedBuffer. - * xResult = IotMqtt_SerializeSubscribe( xMQTTSubscription, - * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), - * xRemainingLength, - * &usPacketIdentifier, - * ucSharedBuffer, - * xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); - * IotMqtt_Assert( xSentBytes == xPacketSize ); - * } - * @endcode - */ -/* @[declare_mqtt_serializesubscribe] */ -IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t bufferSize ); -/* @[declare_mqtt_serializesubscribe] */ - -/** - * @brief Generate a UNSUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions to remove. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[in] remainingLength remaining length of the packet to be serialized. - * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. - * @param[in, out] pBuffer User provide buffer where the UNSUBSCRIBE packet is written. - * @param[in] bufferSize Size of the buffer pointed to by pBuffer. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - * - * @note pBuffer must be allocated by caller. - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how IotMqtt_SerializeUnsubscribe() should be used to serialize - * // MQTT unsubscribe packet and send it to MQTT broker. - * // Example uses static memory, but dynamically allocated memory can be used as well. - * // Get size requirement for the Unsubscribe packet. - * - * #define mqttexampleSHARED_BUFFER_SIZE 100 - * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; - * void sendUnsubscribePacket( int xMQTTSocket ) - * { - * // Following example shows one topic example. - * IotMqttSubscription_t xMQTTSubscription[ 1 ]; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * IotMqttError_t xResult; - * size_t xSentBytes = 0; - * // Get size requirement for MQTT unsubscribe packet. - * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, - * xMQTTSubscription, - * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), - * &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // Make sure the packet size is less than static buffer size. - * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * // Serialize subscribe into statically allocated ucSharedBuffer. - * xResult = IotMqtt_SerializeUnsubscribe( xMQTTSubscription, - * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), - * xRemainingLength, - * &usPacketIdentifier, - * ucSharedBuffer, - * xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); - * IotMqtt_Assert( xSentBytes == xPacketSize ); - * } - * @endcode - */ -/* @[declare_mqtt_serializeunsubscribe] */ -IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t bufferSize ); -/* @[declare_mqtt_serializeunsubscribe] */ - -/** - * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated - * from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information struct. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return IOT_MQTT_SUCCESS if the packet is within the length allowed by MQTT 3.1.1 spec; - * IOT_MQTT_BAD_PARAMETER otherwise. If this function returns IOT_MQTT_BAD_PARAMETER, - * the output parameters should be ignored. - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how IotMqtt_GetPublishPacketSize() should be used to calculate - * // the size of MQTT publish request. - * - * IotMqttError_t xResult; - * IotMqttPublishInfo_t xMQTTPublishInfo; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * - * // Initialize Publish parameters. Details are out of scope for this example. - * // It will involve setting QOS, topic filter, topic filter length, payload - * // payload length - * _initializePublish( &xMQTTPublishInfo ); - * - * // Find out length of Publish packet size. - * xResult = IotMqtt_GetPublishPacketSize( &xMQTTPublishInfo, &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * - * // Application should allocate buffer with size == xPacketSize or use static buffer - * // with size >= xPacketSize to serialize connect request. - * @endcode - */ -/* @[declare_mqtt_getpublishpacketsize] */ -IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); -/* @[declare_mqtt_getpublishpacketsize] */ - -/** - * @brief Generate a PUBLISH packet from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information. - * @param[in] remainingLength remaining length of the packet to be serialized. - * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. - * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier - * is written. - * @param[in, out] pBuffer User provide buffer where the PUBLISH packet is written. - * @param[in] bufferSize Size of the buffer pointed to by pBuffer. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER. - * - * @note pBuffer must be allocated by caller. - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how IotMqtt_SerializePublish() should be used to serialize - * // MQTT Publish packet and send it to broker. - * // Example uses static memory, but dynamically allocated memory can be used as well. - * - * #define mqttexampleSHARED_BUFFER_SIZE 100 - * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; - * void sendUnsubscribePacket( int xMQTTSocket ) - * { - * IotMqttError_t xResult; - * IotMqttPublishInfo_t xMQTTPublishInfo; - * size_t xRemainingLength = 0; - * size_t xPacketSize = 0; - * size_t xSentBytes = 0; - * uint16_t usPacketIdentifier; - * uint8_t * pusPacketIdentifierHigh; - * - * // Initialize Publish parameters. Details are out of scope for this example. - * // It will involve setting QOS, topic filter, topic filter length, payload - * // payload length. - * _initializePublish( &xMQTTPublishInfo ); - * - * // Find out length of Publish packet size. - * xResult = IotMqtt_GetPublishPacketSize( &xMQTTPublishInfo, &xRemainingLength, &xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * // Make sure the packet size is less than static buffer size - * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * - * xResult = IotMqtt_SerializePublish( &xMQTTPublishInfo, - * xRemainingLength, - * &usPacketIdentifier, - * &pusPacketIdentifierHigh, - * ucSharedBuffer, - * xPacketSize ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); - * IotMqtt_Assert( xSentBytes == xPacketSize ); - * } - * @endcode - */ -/* @[declare_mqtt_serializepublish] */ -IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, - size_t bufferSize ); -/* @[declare_mqtt_serializepublish] */ - -/** - * @brief Generate a DISCONNECT packet - * - * @param[in, out] pBuffer User provide buffer where the DISCONNECT packet is written. - * @param[in] bufferSize Size of the buffer pointed to by pBuffer. - * - * @return returns #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example below shows how IotMqtt_SerializeDisconnect() should be used. - * - * #define mqttexampleSHARED_BUFFER_SIZE 100 - * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; - * void sendDisconnectRequest( int xMQTTSocket ) - * { - * size_t xSentBytes = 0; - * - * // Disconnect is fixed length packet, therefore there is no need to calculate the size, - * // just makes sure static buffer can accommodate disconnect request. - * IotMqtt_Assert( MQTT_PACKET_DISCONNECT_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); - * - * // Serialize Disconnect packet into static buffer (dynamically allocated buffer can be used as well) - * xResult = IotMqtt_SerializeDisconnect( ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * xSentByte = send( xMQTTSocket, ( void * ) ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE, 0 ); - * IotMqtt_Assert( xSentByte == MQTT_PACKET_DISCONNECT_SIZE ); - * } - * - * @endcode - */ -/* @[declare_mqtt_serializedisconnect] */ -IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, - size_t bufferSize ); -/* @[declare_mqtt_serializedisconnect] */ - -/** - * @brief Generate a PINGREQ packet. - * - * @param[in, out] pBuffer User provide buffer where the PINGREQ packet is written. - * @param[in] bufferSize Size of the buffer pointed to by pBuffer. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER. - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example below shows how IotMqtt_SerializePingReq() should be used. - * - * #define mqttexampleSHARED_BUFFER_SIZE 100 - * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; - * void sendPingRequest( int xMQTTSocket ) - * { - * size_t xSentBytes = 0; - * - * // PingReq is fixed length packet, therefore there is no need to calculate the size, - * // just makes sure static buffer can accommodate Ping request. - * IotMqtt_Assert( MQTT_PACKET_PINGREQ_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); - * - * xResult = IotMqtt_SerializePingreq( ucSharedBuffer, MQTT_PACKET_PINGREQ_SIZE ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * xSentByte = send( xMQTTSocket, ( void * ) ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE, 0 ); - * IotMqtt_Assert( xSentByte == MQTT_PACKET_PINGREQ_SIZE); - * } - * @endcode - */ -/* @[declare_mqtt_serializepingreq] */ -IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, - size_t bufferSize ); -/* @[declare_mqtt_serializepingreq] */ - -/** - * @brief Extract MQTT packet type and length from incoming packet - * - * @param[in, out] pIncomingPacket Pointer to IotMqttPacketInfo_t structure - * where type, remaining length and packet identifier are stored. - * @param[in] getNextByte Pointer to platform specific function which is used - * to extract type and length from incoming received stream (see example ). - * @param[in] pNetworkConnection Pointer to platform specific network connection - * which is used by getNextByte to receive network data - * - * @return #IOT_MQTT_SUCCESS on successful extraction of type and length, - * #IOT_MQTT_BAD_RESPONSE on failure. - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example code below shows how to implement getNextByte function with posix sockets. - * // Note: IotMqttGetNextByte_t typedef IotMqttError_t (* IotMqttGetNextByte_t)( IotNetworkConnection_t pNetworkContext, - * // uint8_t * pNextByte ); - * // Note: It is assumed that socket is already created and connected, - * - * IotMqttError_t getNextByte( IotNetworkConnection_t pContext, - * uint8_t * pNextByte ) - * { - * int socket = ( int ) ( *pvContext ); - * int receivedBytes; - * IotMqttError_t result; - * - * receivedBytes = recv( socket, ( void * ) pNextByte, sizeof( uint8_t ), 0 ); - * - * if( receivedBytes == sizeof( uint8_t ) ) - * { - * result = IOT_MQTT_SUCCESS; - * } - * else - * { - * result = IOT_MQTT_TIMEOUT; - * } - * - * return result; - * } - * - * // Example below shows how IotMqtt_GetIncomingMQTTPacketTypeAndLength() is used to extract type - * // and length from incoming ping response. - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * void getTypeAndLengthFromIncomingMQTTPingResponse( int xMQTTSocket ) - * { - * IotMqttPacketInfo_t xIncomingPacket; - * IotMqttError_t xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( IotNetworkConnection_t ) xMQTTSocket ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * IotMqtt_Assert( xIncomingPacket.type == MQTT_PACKET_TYPE_PINGRESP ); - * } - * @endcode - * - */ -/* @[declare_mqtt_getincomingmqttpackettypeandlength] */ -IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, - IotMqttGetNextByte_t getNextByte, - IotNetworkConnection_t pNetworkConnection ); -/* @[declare_mqtt_getincomingmqttpackettypeandlength] */ - -/** - * @brief Deserialize incoming publish packet. - * - * @param[in, out] pMqttPacket The caller of this API sets type, remainingLength and pRemainingData. - * On success, packetIdentifier and pubInfo will be set by the function. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_SERVER_REFUSED - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example below shows how IotMqtt_DeserializePublish() used to extract contents of incoming - * // Publish. xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * void processIncomingPublish( int xMQTTSocket ) - * { - * IotMqttError_t xResult; - * IotMqttPacketInfo_t xIncomingPacket; - * - * xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * IotMqtt_Assert( ( xIncomingPacket.type & 0xf0 ) == MQTT_PACKET_TYPE_PUBLISH ); - * IotMqtt_Assert( xIncomingPacket.remainingLength <= mqttexampleSHARED_BUFFER_SIZE ); - * - * // Receive the remaining bytes. - * if( recv( xMQTTSocket, ( void * ) ucSharedBuffer, xIncomingPacket.remainingLength, 0 ) == xIncomingPacket.remainingLength ) - * { - * xIncomingPacket.pRemainingData = ucSharedBuffer; - * - * if( IotMqtt_DeserializePublish( &xIncomingPacket ) != IOT_MQTT_SUCCESS ) - * { - * xResult = IOT_MQTT_BAD_RESPONSE; - * } - * else - * { - * // Process incoming Publish. - * IotLogInfo( "Incoming QOS : %d\n", xIncomingPacket.pubInfo.qos ); - * IotLogInfo( "Incoming Publish Topic Name: %.*s\n", xIncomingPacket.pubInfo.topicNameLength, xIncomingPacket.pubInfo.pTopicName ); - * IotLogInfo( "Incoming Publish Message : %.*s\n", xIncomingPacket.pubInfo.payloadLength, xIncomingPacket.pubInfo.pPayload ); - * } - * } - * else - * { - * xResult = IOT_MQTT_NETWORK_ERROR; - * } - * - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * } - * @endcode - */ -/* @[declare_mqtt_deserializepublish] */ -IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ); -/* @[declare_mqtt_deserializepublish] */ - -/** - * @brief Deserialize incoming ack packets. - * - * @param[in, out] pMqttPacket The caller of this API sets type, remainingLength and pRemainingData. - * On success, packetIdentifier will be set. - * - * @return One of the following: - * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_BAD_RESPONSE - * - #IOT_MQTT_SERVER_REFUSED - * - * @note This call is part of serializer API used for implementing light-weight MQTT client. - * - * Example - * @code{c} - * // Example below shows how IotMqtt_DeserializeResponse() is used to process unsubscribe ack. - * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. - * void processUnsubscribeAck( int xMQTTSocket ) - * { - * IotMqttError_t xResult; - * IotMqttPacketInfo_t xIncomingPacket; - * - * xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * IotMqtt_Assert( xIncomingPacket.type == MQTT_PACKET_TYPE_UNSUBACK ); - * IotMqtt_Assert( xIncomingPacket.remainingLength <= sizeof( ucSharedBuffer ) ); - * - * // Receive the remaining bytes. - * if( recv( xMQTTSocket, ( void * ) ucSharedBuffer, xIncomingPacket.remainingLength, 0 ) == xIncomingPacket.remainingLength ) - * { - * xIncomingPacket.pRemainingData = ucSharedBuffer; - * - * if( IotMqtt_DeserializeResponse( &xIncomingPacket ) != IOT_MQTT_SUCCESS ) - * { - * xResult = IOT_MQTT_BAD_RESPONSE; - * } - * } - * else - * { - * xResult = IOT_MQTT_NETWORK_ERROR; - * } - * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); - * } - * @endcode - */ -/* @[declare_mqtt_deserializeresponse] */ -IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ); -/* @[declare_mqtt_deserializeresponse] */ - - - -#endif /* ifndef IOT_MQTT_SERIALIZE_H_ */ diff --git a/libraries/standard/mqtt/include/iot_mqtt_protocol.h b/libraries/standard/mqtt/include/iot_mqtt_protocol.h deleted file mode 100644 index 0fedceaab3..0000000000 --- a/libraries/standard/mqtt/include/iot_mqtt_protocol.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_protocol.h - * @brief This file contains MQTT 3.1.1 specific defines. This is a common header - * to be included for building a single threaded light-weight MQTT client as well - * as stateful CSDK MQTT library. - */ - -#ifndef IOT_MQTT_PROTOCOL_H_ -#define IOT_MQTT_PROTOCOL_H_ - -/* - * MQTT control packet type and flags. Always the first byte of an MQTT - * packet. - * - * For details, see - * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 - */ -#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ -#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ -#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ -#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ -#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ -#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ - -/* - * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT - * packet. - */ -#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ -#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ -#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ -#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ -#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ -#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ -#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ - -/* - * Positions of each flag in the first byte of an MQTT PUBLISH packet's - * fixed header. - */ -#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ -#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ -#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ -#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ - - -/** - * @brief A value that represents an invalid remaining length. - * - * This value is greater than what is allowed by the MQTT specification. - */ -#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) - -/** - * @brief The maximum possible size of a CONNECT packet. - * - * All strings in a CONNECT packet are constrained to 2-byte lengths, giving a - * maximum length smaller than the max "Remaining Length" constant above. - */ -#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) - -/** - * @brief The minimum remaining length for a QoS 0 PUBLISH. - * - * Includes two bytes for topic name length and one byte for topic name. - */ -#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) - -/** - * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT - * packet is this value. - */ -#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) - -/** - * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. - */ -#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) - -/* - * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ -#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ - -/* - * Constants relating to PUBLISH and PUBACK packets, defined by MQTT - * 3.1.1 spec. - */ -#define MQTT_PACKET_PUBACK_SIZE ( 4U ) /**< @brief A PUBACK packet is always 4 bytes in size. */ -#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ - -/* - * Constants relating to SUBACK and UNSUBACK packets, defined by MQTT - * 3.1.1 spec. - */ -#define MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5U ) /**< @brief The size of the smallest valid SUBACK packet. */ -#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ - -/* - * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ -#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ - -/* - * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_DISCONNECT_SIZE ( 2U ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ - - - -#endif /* ifndef _IOT_MQTT_PROTOCOL_H_ */ diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h new file mode 100644 index 0000000000..8f0d3c20d7 --- /dev/null +++ b/libraries/standard/mqtt/include/mqtt.h @@ -0,0 +1,122 @@ +#include "mqtt_lightweight.h" + +#warning "Temporary workaround, remove following line" +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 + +struct MQTTApplicationCallbacks; +typedef struct MQTTApplicationCallbacks MQTTApplicationCallbacks_t; + +struct MQTTPubAckInfo; +typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; + +struct MQTTContext; +typedef struct MQTTContext MQTTContext_t; + +struct MQTTTransportInterface; +typedef struct MQTTTransportInterface MQTTTransportInterface_t; + +typedef int32_t (* TransportWriteFunc_t )( MQTTNetworkContext_t context, + void * pBuffer, + size_t bytesToWrite ); + +typedef uint32_t (* GetCurrentTimeFunc_t )( void ); + +typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo ); + +typedef enum MQTTConnectionStatus +{ + MQTTNotConnected, + MQTTConnectionInProgress, + MQTTConnected +} MQTTConnectionStatus_t; + +typedef enum MQTTPublishState +{ + MQTTPublishSend, + MQTTPubAckSend, + MQTTPubRecSend, + MQTTPubRelSend, + MQTTPubCompSend, + MQTTPubAckPending, + MQTTPubRelPending, + MQTTPubRecPending, + MQTTPubCompPending, + MQTTPublishDone +} MQTTPublishState_t; + +typedef enum MQTTPubAckType +{ + MQTTPuback, + MQTTPubrec, + MQTTPubrel, + MQTTPubcomp +} MQTTPubAckType_t; + +struct MQTTTransportInterface +{ + TransportWriteFunc_t write; + TransportReadFunc_t read; + MQTTNetworkContext_t * networkContext; +}; + +struct MQTTApplicationCallbacks +{ + GetCurrentTimeFunc_t getTime; + MQTTEventCallback_t appCallback; +}; + +struct MQTTPubAckInfo +{ + uint16_t packetId; + MQTTPubAckType_t ackType; + MQTTPublishState_t publishState; +}; + +struct MQTTContext +{ + MQTTPubAckInfo_t outgoingPublishRecords[ MQTT_MAX_QUEUED_PUBLISH_MESSAGES ]; + size_t outgoingPublishCount; + MQTTPubAckInfo_t incomingPublishRecords[ MQTT_MAX_QUEUED_PUBLISH_MESSAGES ]; + size_t incomingPublishCount; + + MQTTTransportInterface_t transportInterface; + uint16_t nextPacketId; + + MQTTFixedBuffer_t txBuffer; + MQTTFixedBuffer_t rxBuffer; + + MQTTConnectionStatus_t connectStatus; + MQTTApplicationCallbacks_t callbacks; + bool controlPacketSent; +}; + +void MQTT_Init( MQTTContext_t * const pContext, + const MQTTTransportInterface_t * const pTransportInterface, + const MQTTApplicationCallbacks_t * const pCallbacks, + const MQTTFixedBuffer_t * const pTxBuffer, + const MQTTFixedBuffer_t * const pRxBuffer ); + +MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, + const MQTTConnectInfo_t * const pConnectInfo, + bool * const pSessionPresent ); + +MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, + const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount ); + +MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, + const MQTTPublishInfo_t * const pPublishInfo ); + +MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ); + +MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, + const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount ); + +MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); + +MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, + uint32_t timeoutMs ); + +uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ); diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h new file mode 100644 index 0000000000..3d5557b04c --- /dev/null +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -0,0 +1,139 @@ +#include +#include +#include + +#warning "Temporary workaround, remove following line" +typedef int MQTTNetworkContext_t; + +struct MQTTFixedBuffer; +typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; + +struct MQTTConnectInfo; +typedef struct MQTTConnectInfo MQTTConnectInfo_t; + +struct MQTTSubscribeInfo; +typedef struct MQTTSubscribeInfo MQTTSubscribeInfo_t; + +struct MqttPublishInfo; +typedef struct MqttPublishInfo MQTTPublishInfo_t; + +struct MQTTPacketInfo; +typedef struct MQTTPacketInfo MQTTPacketInfo_t; + +typedef int32_t (* TransportReadFunc_t )( MQTTNetworkContext_t context, + void * pBuffer, + size_t bytesToRead ); + +typedef enum MQTTStatus +{ + MQTTSuccess = 0, + MQTTBadParameter, + MQTTNoMemory, + MQTTBadResponse, + MQTTServerRefused +} MQTTStatus_t; + +typedef enum MQTTQoS +{ + MQTTQoS0 = 0, + MQTTQoS1 = 1, + MQTTQoS2 = 2 +} MQTTQoS_t; + +struct MQTTFixedBuffer +{ + uint8_t * pBuffer; + size_t size; +}; + +struct MQTTConnectInfo +{ + bool cleanSession; + uint16_t keepAliveSeconds; + const char * pClientIdentifier; + uint16_t clientIdentifierLength; + const char * pUserName; + uint16_t userNameLength; + const char * pPassword; + uint16_t passwordLength; +}; + +struct MQTTSubscribeInfo +{ + MQTTQoS_t qos; + const char * pTopicFilter; + uint16_t topicFilterLength; +}; + +struct MqttPublishInfo +{ + MQTTQoS_t qos; + bool retain; + const char * pTopicName; + uint16_t topicNameLength; + const void * pPayload; + size_t payloadLength; +}; + +struct MQTTPacketInfo +{ + uint8_t type; + uint16_t packetIdentifier; + uint8_t * pRemainingData; + size_t remainingLength; +}; + +MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ); + +MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ); + +MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ); + +MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ); + +MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ); + +MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ); + +MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ); + +MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer, + size_t * const pHeaderSize ); + +MQTTStatus_t MQTT_SerializeDisconnect( MQTTFixedBuffer_t * const pBuffer ); + +MQTTStatus_t MQTT_SerializePingreq( MQTTFixedBuffer_t * const pBuffer ); + +MQTTStatus_t MQTT_GetPacket( TransportReadFunc_t readFunc, + MQTTPacketInfo_t * const pIncomingPacket ); + +MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, + uint16_t * const pPacketId, + MQTTPublishInfo_t * const pPublishInfo ); + +MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, + uint16_t * const pPacketId, + bool * const pSessionPresent ); diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h deleted file mode 100644 index 92a71ff374..0000000000 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ /dev/null @@ -1,1174 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_types.h - * @brief MQTT library types. - */ - -#ifndef IOT_MQTT_TYPES_H_ -#define IOT_MQTT_TYPES_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Type includes. */ -#include "types/iot_platform_types.h" - -/* Platform network include. */ -#include "platform/iot_network.h" - -/*---------------------------- MQTT handle types ----------------------------*/ - -/** - * @handles{mqtt,MQTT library} - */ - -/** - * @ingroup mqtt_datatypes_handles - * @brief Opaque handle of an MQTT connection. - * - * MQTT connection handle type. MQTT connection handles are created by - * successful calls to @ref mqtt_function_connect and are used to refer to - * the connection when calling MQTT library functions. - * - * A call to @ref mqtt_function_disconnect makes a connection handle invalid. Once - * @ref mqtt_function_disconnect returns, the connection handle should no longer - * be used. - * - * @initializer{IotMqttConnection_t,IOT_MQTT_CONNECTION_INITIALIZER} - */ -typedef struct _mqttConnection * IotMqttConnection_t; - -/** - * @ingroup mqtt_datatypes_handles - * @brief Opaque handle that references an in-progress MQTT operation. - * - * Set as an output parameter of @ref mqtt_function_publishasync, @ref mqtt_function_subscribeasync, - * and @ref mqtt_function_unsubscribeasync. These functions queue an MQTT operation; the result - * of the operation is unknown until a response from the MQTT server is received. Therefore, - * this handle serves as a reference to MQTT operations awaiting MQTT server response. - * - * This reference will be valid from the successful return of @ref mqtt_function_publishasync, - * @ref mqtt_function_subscribeasync, or @ref mqtt_function_unsubscribeasync. The reference becomes - * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or - * @ref mqtt_function_wait returns. - * - * @initializer{IotMqttOperation_t,IOT_MQTT_OPERATION_INITIALIZER} - * - * @see @ref mqtt_function_wait and #IOT_MQTT_FLAG_WAITABLE for waiting on a reference. - * #IotMqttCallbackInfo_t and #IotMqttCallbackParam_t for an asynchronous notification - * of completion. - */ -typedef struct _mqttOperation * IotMqttOperation_t; - -/*-------------------------- MQTT enumerated types --------------------------*/ - -/** - * @enums{mqtt,MQTT library} - */ - -/** - * @ingroup mqtt_datatypes_enums - * @brief Return codes of [MQTT functions](@ref mqtt_functions). - * - * The function @ref mqtt_function_strerror can be used to get a return code's - * description. - */ -typedef enum IotMqttError -{ - /** - * @brief MQTT operation completed successfully. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_publishasync with QoS 0 parameter - * - @ref mqtt_function_wait - * - @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishsync - * - * Will also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result when successful. - */ - IOT_MQTT_SUCCESS = 0, - - /** - * @brief MQTT operation queued, awaiting result. - * - * Functions that may return this value: - * - @ref mqtt_function_subscribeasync - * - @ref mqtt_function_unsubscribeasync - * - @ref mqtt_function_publishasync with QoS 1 parameter - */ - IOT_MQTT_STATUS_PENDING = 1, - - /** - * @brief Initialization failed. - * - * Functions that may return this value: - * - @ref mqtt_function_init - */ - IOT_MQTT_INIT_FAILED = 2, - - /** - * @brief At least one parameter is invalid. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribeasync and @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync - * - @ref mqtt_function_wait - */ - IOT_MQTT_BAD_PARAMETER = 3, - - /** - * @brief MQTT operation failed because of memory allocation failure. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribeasync and @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync - */ - IOT_MQTT_NO_MEMORY = 4, - - /** - * @brief MQTT operation failed because the network was unusable. - * - * This return value may indicate that the network is disconnected. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait - * - @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishsync - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result. - */ - IOT_MQTT_NETWORK_ERROR = 5, - - /** - * @brief MQTT operation could not be scheduled, i.e. enqueued for sending. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribeasync and @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync - */ - IOT_MQTT_SCHEDULING_ERROR = 6, - - /** - * @brief MQTT response packet received from the network is malformed. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait - * - @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishsync - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result. - * - * @note If this value is received, the network connection has been closed. - */ - IOT_MQTT_BAD_RESPONSE = 7, - - /** - * @brief A blocking MQTT operation timed out. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait - * - @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishsync - */ - IOT_MQTT_TIMEOUT = 8, - - /** - * @brief A CONNECT or at least one subscription was refused by the server. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter - * is associated with a SUBSCRIBE operation. - * - @ref mqtt_function_subscribesync - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result for a SUBSCRIBE. - * - * @note If this value is returned and multiple subscriptions were passed to - * @ref mqtt_function_subscribeasync (or @ref mqtt_function_subscribesync), it's - * still possible that some of the subscriptions succeeded. This value only - * signifies that AT LEAST ONE subscription was rejected. The function @ref - * mqtt_function_issubscribed can be used to determine which subscriptions - * were accepted or rejected. - */ - IOT_MQTT_SERVER_REFUSED = 9, - - /** - * @brief A QoS 1 PUBLISH received no response and [the retry limit] - * (#IotMqttPublishInfo_t.retryLimit) was reached. - * - * Functions that may return this value: - * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter - * is associated with a QoS 1 PUBLISH operation - * - @ref mqtt_function_publishsync - * - * May also be the value of an operation completion callback's - * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. - */ - IOT_MQTT_RETRY_NO_RESPONSE = 10, - - /** - * @brief An API function was called before @ref mqtt_function_init. - * - * Functions that may return this value: - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribeasync - * - @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribeasync - * - @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishasync - * - @ref mqtt_function_publishsync - * - @ref mqtt_function_wait - */ - IOT_MQTT_NOT_INITIALIZED = 11 -} IotMqttError_t; - -/** - * @ingroup mqtt_datatypes_enums - * @brief Types of MQTT operations. - * - * The function @ref mqtt_function_operationtype can be used to get an operation - * type's description. - */ -typedef enum IotMqttOperationType -{ - IOT_MQTT_CONNECT, /**< Client-to-server CONNECT. */ - IOT_MQTT_PUBLISH_TO_SERVER, /**< Client-to-server PUBLISH. */ - IOT_MQTT_PUBACK, /**< Client-to-server PUBACK. */ - IOT_MQTT_SUBSCRIBE, /**< Client-to-server SUBSCRIBE. */ - IOT_MQTT_UNSUBSCRIBE, /**< Client-to-server UNSUBSCRIBE. */ - IOT_MQTT_PINGREQ, /**< Client-to-server PINGREQ. */ - IOT_MQTT_DISCONNECT /**< Client-to-server DISCONNECT. */ -} IotMqttOperationType_t; - -/** - * @ingroup mqtt_datatypes_enums - * @brief Quality of service levels for MQTT PUBLISH messages. - * - * All MQTT PUBLISH messages, including Last Will and Testament and messages - * received on subscription filters, have an associated Quality of Service, - * which defines any delivery guarantees for that message. - * - QoS 0 messages will be delivered at most once. This is a "best effort" - * transmission with no retransmissions. - * - QoS 1 messages will be delivered at least once. See #IotMqttPublishInfo_t - * for the retransmission strategy this library uses to redeliver messages - * assumed to be lost. - * - * @attention QoS 2 is not supported by this library and should not be used. - */ -typedef enum IotMqttQos -{ - IOT_MQTT_QOS_0 = 0, /**< Delivery at most once. */ - IOT_MQTT_QOS_1 = 1, /**< Delivery at least once. See #IotMqttPublishInfo_t for client-side retry strategy. */ - IOT_MQTT_QOS_2 = 2 /**< Delivery exactly once. Unsupported, but enumerated for completeness. */ -} IotMqttQos_t; - -/** - * @ingroup mqtt_datatypes_enums - * @brief The reason that an MQTT connection (and its associated network connection) - * was disconnected. - * - * When an MQTT connection is closed, its associated [disconnect callback] - * (@ref IotMqttNetworkInfo_t::disconnectCallback) will be invoked. This type - * is passed inside of an #IotMqttCallbackParam_t to provide a reason for the - * disconnect. - */ -typedef enum IotMqttDisconnectReason -{ - IOT_MQTT_DISCONNECT_CALLED, /**< @ref mqtt_function_disconnect was invoked. */ - IOT_MQTT_BAD_PACKET_RECEIVED, /**< An invalid packet was received from the network. */ - IOT_MQTT_KEEP_ALIVE_TIMEOUT /**< Keep-alive response was not received within @ref IOT_MQTT_RESPONSE_WAIT_MS. */ -} IotMqttDisconnectReason_t; - -/*------------------------- MQTT parameter structs --------------------------*/ - -/** - * @paramstructs{mqtt,MQTT} - */ - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a PUBLISH message. - * - * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publishasync - * - * Passed to @ref mqtt_function_publishasync as the message to publish and @ref - * mqtt_function_connect as the Last Will and Testament (LWT) message. - * - * @initializer{IotMqttPublishInfo_t,IOT_MQTT_PUBLISH_INFO_INITIALIZER} - * - * #IotMqttPublishInfo_t.retryMs and #IotMqttPublishInfo_t.retryLimit are only - * relevant to QoS 1 PUBLISH messages. They are ignored for QoS 0 PUBLISH - * messages and LWT messages. These members control retransmissions of QoS 1 - * messages under the following rules: - * - Retransmission is disabled when #IotMqttPublishInfo_t.retryLimit is 0. - * After sending the PUBLISH, the library will wait indefinitely for a PUBACK. - * - If #IotMqttPublishInfo_t.retryLimit is greater than 0, then QoS 1 publishes - * that do not receive a PUBACK within #IotMqttPublishInfo_t.retryMs will be - * retransmitted, up to #IotMqttPublishInfo_t.retryLimit times. - * - * Retransmission follows a truncated exponential backoff strategy. The constant - * @ref IOT_MQTT_RETRY_MS_CEILING controls the maximum time between retransmissions. - * - * After #IotMqttPublishInfo_t.retryLimit retransmissions are sent, the MQTT - * library will wait @ref IOT_MQTT_RESPONSE_WAIT_MS before a final check - * for a PUBACK. If no PUBACK was received within this time, the QoS 1 PUBLISH - * fails with the code #IOT_MQTT_RETRY_NO_RESPONSE. - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - * - * @note The AWS IoT MQTT broker does not support the DUP bit. More - * information about connecting to AWS IoT via MQTT is available - * [here](https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html). - * - * Example - * - * Consider a situation where - * - @ref IOT_MQTT_RETRY_MS_CEILING is 60000 - * - #IotMqttPublishInfo_t.retryMs is 2000 - * - #IotMqttPublishInfo_t.retryLimit is 20 - * - * A PUBLISH message will be retransmitted at the following times after the initial - * transmission if no PUBACK is received: - * - 2000 ms (2000 ms after previous transmission) - * - 6000 ms (4000 ms after previous transmission) - * - 14000 ms (8000 ms after previous transmission) - * - 30000 ms (16000 ms after previous transmission) - * - 62000 ms (32000 ms after previous transmission) - * - 122000 ms, 182000 ms, 242000 ms... (every 60000 ms until 20 transmissions have been sent) - * - * After the 20th retransmission, the MQTT library will wait - * @ref IOT_MQTT_RESPONSE_WAIT_MS before checking a final time for a PUBACK. - */ -typedef struct IotMqttPublishInfo -{ - IotMqttQos_t qos; /**< @brief QoS of message. Must be 0 or 1. */ - bool retain; /**< @brief MQTT message retain flag. */ - - const char * pTopicName; /**< @brief Topic name of PUBLISH. */ - uint16_t topicNameLength; /**< @brief Length of #IotMqttPublishInfo_t.pTopicName. */ - - const void * pPayload; /**< @brief Payload of PUBLISH. */ - size_t payloadLength; /**< @brief Length of #IotMqttPublishInfo_t.pPayload. For LWT messages, this is limited to 65535. */ - - uint32_t retryMs; /**< @brief If no response is received within this time, the message is retransmitted. */ - uint32_t retryLimit; /**< @brief How many times to attempt retransmission. */ -} IotMqttPublishInfo_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Parameter to an MQTT callback function. - * - * @paramfor MQTT callback functions - * - * The MQTT library passes this struct to a registered callback whenever an - * operation completes, a message is received on a topic filter, or an MQTT - * connection is disconnected. - * - * The members of this struct are different based on the callback trigger. If the - * callback function was triggered for completed operation, the `operation` - * member is valid. Otherwise, if the callback was triggered because of a - * server-to-client PUBLISH, the `message` member is valid. Finally, if the callback - * was triggered because of a disconnect, the `disconnectReason` member is valid. - * - * For an incoming PUBLISH, the `message.pTopicFilter` parameter provides the - * subscription topic filter that matched the topic name in the PUBLISH. Because - * topic filters may use MQTT wildcards, the topic filter may be different from the - * topic name. This pointer must be treated as read-only; the topic filter must not - * be modified. Additionally, the topic filter may go out of scope as soon as the - * callback function returns, so it must be copied if it is needed at a later time. - * - * @attention Any pointers in this callback parameter may be freed as soon as - * the [callback function](@ref IotMqttCallbackInfo_t.function) returns. - * Therefore, data must be copied if it is needed after the callback function - * returns. - * @attention The MQTT library may set strings that are not NULL-terminated. - * - * @see #IotMqttCallbackInfo_t for the signature of a callback function. - */ -typedef struct IotMqttCallbackParam -{ - /** - * @brief The MQTT connection associated with this completed operation, - * incoming PUBLISH, or disconnect. - * - * [MQTT API functions](@ref mqtt_functions) are safe to call from a callback - * for completed operations or incoming PUBLISH messages. However, blocking - * function calls (including @ref mqtt_function_wait) are not recommended - * (though still safe). Do not call any API functions from a disconnect - * callback. - */ - IotMqttConnection_t mqttConnection; - - /* MISRA rule 19.2 doesn't allow usage of union - * but it is intentionally used here to reduce the size of struct. */ - /* coverity[misra_c_2012_rule_19_2_violation] */ - union - { - /* Valid for completed operations. */ - struct - { - IotMqttOperationType_t type; /**< @brief Type of operation that completed. */ - IotMqttOperation_t reference; /**< @brief Reference to the operation that completed. */ - IotMqttError_t result; /**< @brief Result of operation, e.g. succeeded or failed. */ - } operation; - - /* Valid for incoming PUBLISH messages. */ - struct - { - const char * pTopicFilter; /**< @brief Topic filter that matched the message. */ - uint16_t topicFilterLength; /**< @brief Length of `pTopicFilter`. */ - IotMqttPublishInfo_t info; /**< @brief PUBLISH message received from the server. */ - } message; - - /* Valid when a connection is disconnected. */ - IotMqttDisconnectReason_t disconnectReason; /**< @brief Why the MQTT connection was disconnected. */ - } u; /**< @brief Valid member depends on callback type. */ -} IotMqttCallbackParam_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief MQTT callback function and context. - * - * @paramfor @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, - * and @ref mqtt_function_publishasync. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. - * - * Specifies a function to be invoked with optional context when an operation - * completes or when a server-to-client PUBLISH is received. - * - * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} - * - * Below is an example for receiving an asynchronous notification on operation - * completion. See @ref mqtt_function_subscribeasync for an example of using this struct - * with for incoming PUBLISH messages. - * - * @code{c} - * // Operation completion callback. - * void operationComplete( void * pArgument, IotMqttCallbackParam_t * pOperation ); - * - * // Callback information. - * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - * callbackInfo.function = operationComplete; - * - * // Operation to wait for. - * IotMqttError_t result = IotMqtt_PublishAsync( &mqttConnection, - * &publishInfo, - * 0, - * &callbackInfo, - * &reference ); - * - * // Publish should have returned IOT_MQTT_STATUS_PENDING. Once a response - * // is received, operationComplete is executed with the actual status passed - * // in pOperation. - * @endcode - */ -typedef struct IotMqttCallbackInfo -{ - void * pCallbackContext; /**< @brief The first parameter to pass to the callback function to provide context. */ - - /** - * @brief User-provided callback function signature. - * - * @param[in] pCallbackContext #IotMqttCallbackInfo_t.pCallbackContext. - * @param[in] pCallbackParam Details on the outcome of the MQTT operation - * or an incoming MQTT PUBLISH. - * - * @see #IotMqttCallbackParam_t for more information on the second parameter. - */ - void ( * function )( void * pCallbackContext, - IotMqttCallbackParam_t * pCallbackParam ); -} IotMqttCallbackInfo_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief MQTT subscription. - * - * @paramfor @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, - * @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync - * - * An array of these is passed to @ref mqtt_function_subscribeasync and @ref - * mqtt_function_unsubscribeasync. However, #IotMqttSubscription_t.callback and - * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribeasync. - * - * @initializer{IotMqttSubscription_t,IOT_MQTT_SUBSCRIPTION_INITIALIZER} - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - * @see #IotMqttCallbackInfo_t for details on setting a callback function. - */ -typedef struct IotMqttSubscription -{ - /** - * @brief QoS of messages delivered on subscription. - * - * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribeasync. - */ - IotMqttQos_t qos; - - const char * pTopicFilter; /**< @brief Topic filter of subscription. */ - uint16_t topicFilterLength; /**< @brief Length of #IotMqttSubscription_t.pTopicFilter. */ - - /** - * @brief Callback to invoke when a message is received. - * - * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribeasync. - */ - IotMqttCallbackInfo_t callback; -} IotMqttSubscription_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief MQTT connection details. - * - * @paramfor @ref mqtt_function_connect - * - * Passed as an argument to @ref mqtt_function_connect. Most members of this struct - * correspond to the content of an [MQTT CONNECT packet.] - * (http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349764) - * - * @initializer{IotMqttConnectInfo_t,IOT_MQTT_CONNECT_INFO_INITIALIZER} - * - * @note The lengths of the strings in this struct should not include the NULL - * terminator. Strings in this struct do not need to be NULL-terminated. - */ -typedef struct IotMqttConnectInfo -{ - /** - * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. - * - * Set this member to `true` when connecting to the AWS IoT MQTT broker or - * `false` otherwise. Additional details about connecting to AWS IoT - * via MQTT are available [here] - * (https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html) - * - * @attention This setting MUST be `true` when using the AWS IoT MQTT - * server; it MUST be `false` otherwise. - * @note Currently, @ref IOT_MQTT_CONNECT_INFO_INITIALIZER sets this - * this member to `true`. - */ - bool awsIotMqttMode; - - /** - * @brief Whether this connection is a clean session. - * - * MQTT servers can maintain and topic filter subscriptions and unacknowledged - * PUBLISH messages. These form part of an MQTT session, which is identified by - * the [client identifier](@ref IotMqttConnectInfo_t.pClientIdentifier). - * - * Setting this value to `true` establishes a clean session, which causes - * the MQTT server to discard any previous session data for a client identifier. - * When the client disconnects, the server discards all session data. If this - * value is `true`, #IotMqttConnectInfo_t.pPreviousSubscriptions and - * #IotMqttConnectInfo_t.previousSubscriptionCount are ignored. - * - * Setting this value to `false` does one of the following: - * - If no previous session exists, the MQTT server will create a new - * persistent session. The server may maintain subscriptions and - * unacknowledged PUBLISH messages after a client disconnects, to be restored - * once the same client identifier reconnects. - * - If a previous session exists, the MQTT server restores all of the session's - * subscriptions for the client identifier and may immediately transmit any - * unacknowledged PUBLISH packets to the client. - * - * When a client with a persistent session disconnects, the MQTT server - * continues to maintain all subscriptions and unacknowledged PUBLISH messages. - * The client must also remember the session subscriptions to restore them - * upon reconnecting. #IotMqttConnectInfo_t.pPreviousSubscriptions and - * #IotMqttConnectInfo_t.previousSubscriptionCount are used to restore a - * previous session's subscriptions client-side. - */ - bool cleanSession; - - /** - * @brief An array of MQTT subscriptions present in a previous session, if any. - * - * Pointer to the start of an array of subscriptions present a previous session, - * if any. These subscriptions will be immediately restored upon reconnecting. - * - * [Optional] The field can also be used to pass a list of subscriptions to be - * stored locally without a SUBSCRIBE packet being sent to the broker. These subscriptions - * are useful to invoke application level callbacks for messages received on unsolicited - * topics from the broker. - * - * This member is ignored if it is `NULL`. If this member is not `NULL`, - * #IotMqttConnectInfo_t.previousSubscriptionCount must be nonzero. - */ - const IotMqttSubscription_t * pPreviousSubscriptions; - - /** - * @brief The number of MQTT subscriptions present in a previous session, if any. - * - * Number of subscriptions contained in the array - * #IotMqttConnectInfo_t.pPreviousSubscriptions. - * - * This value is ignored if #IotMqttConnectInfo_t.pPreviousSubscriptions - * is `NULL`. If #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, - * this value must be nonzero. - */ - size_t previousSubscriptionCount; - - /** - * @brief A message to publish if the new MQTT connection is unexpectedly closed. - * - * A Last Will and Testament (LWT) message may be published if this connection is - * closed without sending an MQTT DISCONNECT packet. This pointer should be set to - * an #IotMqttPublishInfo_t representing any LWT message to publish. If an LWT - * is not needed, this member must be set to `NULL`. - * - * Unlike other PUBLISH messages, an LWT message is limited to 65535 bytes in - * length. Additionally, [pWillInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) - * and [pWillInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) will - * be ignored. - */ - const IotMqttPublishInfo_t * pWillInfo; - - uint16_t keepAliveSeconds; /**< @brief Period of keep-alive messages. Set to 0 to disable keep-alive. */ - - const char * pClientIdentifier; /**< @brief MQTT client identifier. */ - uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - - /** - * @brief Username for MQTT connection. - * - * The MQTT username and password can be used for AWS IoT Enhanced Custom - * Authentication as described [here] - * (https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html). - */ - const char * pUserName; - uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ - const char * pPassword; /**< @brief Password for MQTT connection. */ - uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ -} IotMqttConnectInfo_t; - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief MQTT packet details. - * - * @paramfor @ref mqtt_function_deserializeresponse @ref mqtt_function_deserializepublish - * - * Passed as an argument to public low level mqtt deserialize functions. - * - * @initializer{IotMqttPacketInfo_t,IOT_MQTT_PACKET_INFO_INITIALIZER} - * - * @note This structure should be only used while accessing low level MQTT deserialization API. - * The low level serialization/ deserialization API should be only used for implementing - * light weight single threaded mqtt client. - */ -typedef struct IotMqttPacketInfo -{ - uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ - size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ - uint16_t packetIdentifier; /**< @brief (Output) MQTT packet identifier. */ - uint8_t type; /**< @brief (Input) A value identifying the packet type. */ - IotMqttPublishInfo_t pubInfo; /**< @brief (Output) Publish info in case of deserializing PUBLISH. */ -} IotMqttPacketInfo_t; - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declaration of the internal MQTT packet structure. - */ -struct _mqttPacket; -/** @endcond */ - -/** - * @brief Get the MQTT packet type from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - */ -typedef uint8_t ( * IotMqttGetPacketType_t )( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); - -/** - * @brief Get the remaining length from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - */ -typedef size_t ( * IotMqttGetRemainingLength_t )( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); - -/** - * @brief Free a packet generated by the serializer. - * - * This function pointer must be set if any other serializer override is set. - * @param[in] uint8_t* The packet to free. - */ -typedef void ( * IotMqttFreePacket_t )( uint8_t * pPacket ); - -/** - * @brief CONNECT packet serializer function. - * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. - * @param[out] uint8_t** Where the CONNECT packet is written. - * @param[out] size_t* Size of the CONNECT packet. - */ -typedef IotMqttError_t ( * IotMqttSerializeConnect_t )( const IotMqttConnectInfo_t * pConnectInfo, - uint8_t ** pConnectPacket, - size_t * pPacketSize ); - -/** - * @brief PINGREQ packet serializer function. - * @param[out] uint8_t** Where the PINGREQ packet is written. - * @param[out] size_t* Size of the PINGREQ packet. - */ -typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( uint8_t ** pPingreqPacket, - size_t * pPacketSize ); - -/** - * @brief PUBLISH packet serializer function. - * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. - * @param[out] uint8_t** Where the PUBLISH packet is written. - * @param[out] size_t* Size of the PUBLISH packet. - * @param[out] uint16_t* The packet identifier generated for this PUBLISH. - * @param[out] uint8_t** Where the high byte of the packet identifier - * is written. - */ -typedef IotMqttError_t ( * IotMqtt_SerializePublish_t )( const IotMqttPublishInfo_t * pPublishInfo, - uint8_t ** pPublishPacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh ); - -/** - * @brief SUBSCRIBE/UNSUBSCRIBE packet serializer function. - * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. - * @param[in] size_t Number of elements in the subscription array. - * @param[out] uint8_t** Where the SUBSCRIBE packet is written. - * @param[out] size_t* Size of the SUBSCRIBE packet. - * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. - */ -typedef IotMqttError_t ( * IotMqttSerializeSubscribe_t )( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ); - -/** - * @brief DISCONNECT packet serializer function. - * @param[out] uint8_t** Where the DISCONNECT packet is written. - * @param[out] size_t* Size of the DISCONNECT packet. - */ -typedef IotMqttError_t ( * IotMqttSerializeDisconnect_t )( uint8_t ** pDisconnectPacket, - size_t * pPacketSize ); - -/** - * @brief MQTT packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure - */ -typedef IotMqttError_t ( * IotMqttDeserialize_t )( struct _mqttPacket * pMqttPacket ); - -/** - * @brief PUBACK packet serializer function. - * @param[in] uint16_t The packet identifier to place in PUBACK. - * @param[out] uint8_t** Where the PUBACK packet is written. - * @param[out] size_t* Size of the PUBACK packet. - */ -typedef IotMqttError_t ( * IotMqttSerializePuback_t )( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ); - -/** - * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. - * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. - * @param[in] uint8_t* The high byte of any packet identifier to modify. - * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). - */ -typedef void ( * IotMqttPublishSetDup_t )( uint8_t * pPublishPacket, - uint8_t * pPacketIdentifierHigh, - uint16_t * pNewPacketIdentifier ); - -/** - * @brief Function pointer to read the next available byte on a network connection. - * @param[in] pNetworkContext reference to network connection like socket. - * @param[out] pNextByte Pointer to the byte read from the network. - */ -typedef IotMqttError_t (* IotMqttGetNextByte_t)( IotNetworkConnection_t pNetworkContext, - uint8_t * pNextByte ); - -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief Function pointers for MQTT packet serializer overrides. - * - * These function pointers allow the MQTT serialization and deserialization functions - * to be overridden for an MQTT connection. The compile-time setting - * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be `1` to enable this functionality. - * See the #IotMqttSerializer_t::serialize and #IotMqttSerializer_t::deserialize - * members for a list of functions that can be overridden. In addition, the functions - * for freeing packets and determining the packet type can also be overridden. If - * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES is `1`, the serializer initialization and - * cleanup functions may be extended. See documentation of - * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES for more information. - * - * If any function pointers that are `NULL`, then the default implementation of that - * function will be used. - */ - typedef struct IotMqttSerializer - { - /** - * @brief Get the MQTT packet type from a stream of bytes off the network. - * Default implementation: #_IotMqtt_GetPacketType - */ - IotMqttGetPacketType_t getPacketType; - - /** - * @brief Get the remaining length from a stream of bytes off the network. - * Default implementation: #_IotMqtt_GetRemainingLength - */ - IotMqttGetRemainingLength_t getRemainingLength; - - /** - * @brief Free a packet generated by the serializer. - * - * Default implementation: #_IotMqtt_FreePacket - */ - IotMqttFreePacket_t freePacket; - - struct - { - /** - * @brief CONNECT packet serializer function. - * - * Default implementation: #_IotMqtt_SerializeConnect - */ - IotMqttSerializeConnect_t connect; - - /** - * @brief PUBLISH packet serializer function. - * - * Default implementation: #_IotMqtt_SerializePublish - */ - IotMqtt_SerializePublish_t publish; - - /** - * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. - * - * Default implementation: #_IotMqtt_PublishSetDup - */ - IotMqttPublishSetDup_t publishSetDup; - - /** - * @brief PUBACK packet serializer function. - * - * Default implementation: #_IotMqtt_SerializePuback - */ - IotMqttSerializePuback_t puback; - - /** - * @brief SUBSCRIBE packet serializer function. - * - * Default implementation: #_IotMqtt_SerializeSubscribe - */ - IotMqttSerializeSubscribe_t subscribe; - - /** - * @brief UNSUBSCRIBE packet serializer function. - * - * Default implementation: #_IotMqtt_SerializeUnsubscribe - */ - IotMqttSerializeSubscribe_t unsubscribe; - - /** - * @brief PINGREQ packet serializer function. - * - * Default implementation: #_IotMqtt_SerializePingreq - */ - IotMqttSerializePingreq_t pingreq; - - /** - * @brief DISCONNECT packet serializer function. - * - * Default implementation: #_IotMqtt_SerializeDisconnect - */ - IotMqttSerializeDisconnect_t disconnect; - } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ - - struct - { - /** - * @brief CONNACK packet deserializer function. - * - * Default implementation: #_IotMqtt_DeserializeConnack - */ - IotMqttDeserialize_t connack; - - /** - * @brief PUBLISH packet deserializer function. - * - * Default implementation: #_IotMqtt_DeserializePublish - */ - IotMqttDeserialize_t publish; - - /** - * @brief PUBACK packet deserializer function. - * - * Default implementation: #_IotMqtt_DeserializePuback - */ - IotMqttDeserialize_t puback; - - /** - * @brief SUBACK packet deserializer function. - * - * Default implementation: #_IotMqtt_DeserializeSuback - */ - IotMqttDeserialize_t suback; - - /** - * @brief UNSUBACK packet deserializer function. - * - * Default implementation: #_IotMqtt_DeserializeUnsuback - */ - IotMqttDeserialize_t unsuback; - - /** - * @brief PINGRESP packet deserializer function. - * - * Default implementation: #_IotMqtt_DeserializePingresp - */ - IotMqttDeserialize_t pingresp; - } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ - } IotMqttSerializer_t; - -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - -/* When MQTT packet serializer overrides are disabled, this struct is an - * incomplete type. */ - typedef struct IotMqttSerializer IotMqttSerializer_t; - -#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - -/** - * @ingroup mqtt_datatypes_paramstructs - * @brief MQTT network connection details. - * - * @paramfor @ref mqtt_function_connect - * - * The MQTT library needs to be able to send and receive data over a network. - * This struct provides an interface for interacting with the network. - * - * @initializer{IotMqttNetworkInfo_t,IOT_MQTT_NETWORK_INFO_INITIALIZER} - */ -typedef struct IotMqttNetworkInfo -{ - /** - * @brief Whether a new network connection should be created. - * - * When this value is `true`, a new transport-layer network connection will - * be created along with the MQTT connection. #IotMqttNetworkInfo_t::pNetworkServerInfo - * and #IotMqttNetworkInfo_t::pNetworkCredentialInfo are valid when this value - * is `true`. - * - * When this value is `false`, the MQTT connection will use a transport-layer - * network connection that has already been established. The MQTT library will - * still set the appropriate receive callback even if the network connection - * has been established. - * #IotMqttNetworkInfo_t::pNetworkConnection, which represents an established - * network connection, is valid when this value is `false`. - */ - bool createNetworkConnection; - - /* MISRA rule 19.2 doesn't allow usage of union - * but it is intentionally used here to reduce the size of struct. */ - /* coverity[misra_c_2012_rule_19_2_violation] */ - union - { - struct - { - /** - * @brief Information on the MQTT server, passed as `pServerInfo` to - * #IotNetworkInterface_t::create. - * - * This member is opaque to the MQTT library. It is passed to the network - * interface when creating a new network connection. It is only valid when - * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. - */ - IotNetworkServerInfo_t pNetworkServerInfo; - - /** - * @brief Credentials for the MQTT server, passed as `pCredentialInfo` to - * #IotNetworkInterface_t::create. - * - * This member is opaque to the MQTT library. It is passed to the network - * interface when creating a new network connection. It is only valid when - * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. - */ - IotNetworkCredentials_t pNetworkCredentialInfo; - } setup; - - /** - * @brief An established transport-layer network connection. - * - * This member is opaque to the MQTT library. It is passed to the network - * interface to reference an established network connection. It is only - * valid when #IotMqttNetworkInfo_t::createNetworkConnection is `false`. - */ - IotNetworkConnection_t pNetworkConnection; - } u /**< @brief Valid member depends of IotMqttNetworkInfo_t.createNetworkConnection. */; - - /** - * @brief The network functions used by the new MQTT connection. - * - * @attention The function pointers of the network interface must remain valid - * for the lifetime of the MQTT connection. - */ - const IotNetworkInterface_t * pNetworkInterface; - - /** - * @brief A callback function to invoke when this MQTT connection is disconnected. - */ - IotMqttCallbackInfo_t disconnectCallback; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - - /** - * @brief MQTT packet serializer overrides used by the new MQTT connection. - * - * @attention The function pointers of the MQTT serializer overrides must - * remain valid for the lifetime of the MQTT connection. - */ - const IotMqttSerializer_t * pMqttSerializer; - #endif -} IotMqttNetworkInfo_t; - -/*------------------------- MQTT defined constants --------------------------*/ - -/** - * @constantspage{mqtt,MQTT library} - * - * @section mqtt_constants_initializers MQTT Initializers - * @brief Provides default values for the data types of the MQTT library. - * - * @snippet this define_mqtt_initializers - * - * All user-facing data types of the MQTT library should be initialized using - * one of the following. - * - * @warning Failing to initialize an MQTT data type with the appropriate initializer - * may result in undefined behavior! - * @note The initializers may change at any time in future versions, but their - * names will remain the same. - * - * Example - * @code{c} - * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - * IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; - * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - * IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - * IotMqttConnection_t connection = IOT_MQTT_CONNECTION_INITIALIZER; - * IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; - * @endcode - * - * @section mqtt_constants_flags MQTT Function Flags - * @brief Flags that modify the behavior of MQTT library functions. - * - #IOT_MQTT_FLAG_WAITABLE
- * @copybrief IOT_MQTT_FLAG_WAITABLE - * - #IOT_MQTT_FLAG_CLEANUP_ONLY
- * @copybrief IOT_MQTT_FLAG_CLEANUP_ONLY - * - * Flags should be bitwise-ORed with each other to change the behavior of - * @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, - * @ref mqtt_function_publishasync, their blocking versions; or @ref mqtt_function_disconnect. - * - * @note The values of the flags may change at any time in future versions, but - * their names will remain the same. Additionally, flags that may be used together - * will be bitwise-exclusive of each other. - */ - -/* @[define_mqtt_initializers] */ -/** @brief Initializer for #IotMqttNetworkInfo_t. */ -#define IOT_MQTT_NETWORK_INFO_INITIALIZER { .createNetworkConnection = true } -/** @brief Initializer for #IotMqttSerializer_t. */ -#define IOT_MQTT_SERIALIZER_INITIALIZER { 0 } -/** @brief Initializer for #IotMqttConnectInfo_t. */ -#define IOT_MQTT_CONNECT_INFO_INITIALIZER { .cleanSession = true } -/** @brief Initializer for #IotMqttPublishInfo_t. */ -#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { .qos = IOT_MQTT_QOS_0 } -/** @brief Initializer for #IotMqttSubscription_t. */ -#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { .qos = IOT_MQTT_QOS_0 } -/** @brief Initializer for #IotMqttCallbackInfo_t. */ -#define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } -/** @brief Initializer for #IotMqttConnection_t. */ -#define IOT_MQTT_CONNECTION_INITIALIZER NULL -/** @brief Initializer for #IotMqttOperation_t. */ -#define IOT_MQTT_OPERATION_INITIALIZER NULL -/** @brief Initializer for #IotMqttPacketInfo_t. */ -#define IOT_MQTT_PACKET_INFO_INITIALIZER { .pRemainingData = NULL, remainingLength = 0, packetIdentifier = 0, .type = 0 } -/* @[define_mqtt_initializers] */ - -/** - * @brief Allows the use of @ref mqtt_function_wait for blocking until completion. - * - * This flag is always valid for @ref mqtt_function_subscribeasync and - * @ref mqtt_function_unsubscribeasync. If passed to @ref mqtt_function_publishasync, - * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. - * - * An #IotMqttOperation_t MUST be provided if this flag is set. Additionally, an - * #IotMqttCallbackInfo_t MUST NOT be provided. - * - * @note If this flag is set, @ref mqtt_function_wait MUST be called to clean up - * resources. - */ -#define IOT_MQTT_FLAG_WAITABLE ( 0x00000001U ) - -/** - * @brief Causes @ref mqtt_function_disconnect to only free memory and not send - * an MQTT DISCONNECT packet. - * - * This flag is only valid for @ref mqtt_function_disconnect. It should be passed - * to @ref mqtt_function_disconnect if the network goes offline or is otherwise - * unusable. - */ -#define IOT_MQTT_FLAG_CLEANUP_ONLY ( 0x00000001UL ) - -#endif /* ifndef IOT_MQTT_TYPES_H_ */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt deleted file mode 100644 index 4b33cc3627..0000000000 --- a/libraries/standard/mqtt/lexicon.txt +++ /dev/null @@ -1,207 +0,0 @@ -ack -api -app -aws -awsiotmqttmode -bool -caretline -clientid -clientidentifierlength -com -config -connack -connectserializeroverride -const -coverity -createnetworkconnection -dataindex -datalength -deserialization -deserialize -deserialized -deserializepublish -deserializer -deserializeresponse -deserializing -developerguide -disconnectcallback -disconnectreason -disconnectserializeroverride -doesn -doxygen -dup -dupchecker -endcond -endif -exactmatchonly -expectedoperation -freepacket -freepacketoverride -getconnectpacketsize -getincomingmqttpackettypeandlength -getnextbyte -getpublishpacketsize -getsubscriptionpacketsize -gr -hasn -html -http -https -ifdef -ifndef -incomingpublish -init -initserializeadditional -int -iot -iotlink -iotlistdouble -iotlog -iotlogdebug -iotlogerror -iotmqtt -iotmqttcallbackinfo -iotmqttconnectinfo -iotmqttconnection -iotmqttdisconnectreason -iotmqtterror -iotmqttgetnextbyte -iotmqttnetworkinfo -iotmqttoperation -iotmqttoperationtype -iotmqttpacketinfo -iotmqttpublishinfo -iotmqttqos -iotmqttserializer -iotmqttsubscription -iotmutex -iotnetworkinterface -iotsemaphore -iottaskpooljob -iottaskpooljobstorage -isn -issubscribed -jobreference -jobstorage -keepalivems -keepaliveseconds -lastmessagetime -lu -lwt -malloc -memcpy -misra -mqtt -mqttconnection -mqttoperation -mqttpacketinfo -mqttsubscription -mutex -mutexes -nextperiodms -offsetof -onlinepubs -opengroup -operationcomplete -operationtype -org -ownnetworkconnection -packetidentifier -packetinfo -packetsize -paramameters -passwordlength -payloadlength -pbuffer -pcallbackcontext -pclientidentifier -pdata -pdestination -pendingprocessing -pendingresponse -pingreq -pingresp -pinusemqttconnections -pinusemqttoperations -pinusemqttsubscriptions -pmqttconnection -pmqttconnections -pmqttoperations -pmqttpacket -pmqttsubscriptions -pnetworkconnection -pnetworkcontext -pnetworkinterface -pnextbyte -poperation -poperationlink -posix -ppacketidentifier -ppacketidentifierhigh -ppassword -ppayload -ppublish -ppublishinfo -pre -preceiveddata -premainingdata -pserializer -psubscriptionlink -ptopicfilter -ptopicname -puback -pubackserializeroverride -pubinfo -publishasync -publishinfo -publishserializeroverride -publishsync -pusername -qos -receivecallback -referencesmutex -remaininglength -retrylimit -retryms -sdk -serializeconnect -serializedisconnect -serializepingreq -serializepublish -serializesubscribe -serializeunsubscribe -sizeof -sp -standalone -strerror -struct -structs -suback -subacks -subscribeasync -subscribeserializeroverride -subscribesync -subscriptionlist -subscriptionmutex -tcp -timeoutms -toc -topicfilterlength -topicmatchparams -topicnamelength -ucsharedbuffer -uint -unsuback -unsubscribeasync -unsubscribeserializeroverride -unsubscribesync -username -usernamelength -utf -waitable -waitsem -waitsemaphore -wasn -weren -xmqttsocket -xpacketsize diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c deleted file mode 100644 index 1e55cb5920..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ /dev/null @@ -1,1988 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_api.c - * @brief Implements most user-facing functions of the MQTT library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Atomics include. */ -#include "iot_atomic.h" - -/* Validate MQTT configuration settings. */ -#if ( IOT_MQTT_ENABLE_ASSERTS != 0 ) && ( IOT_MQTT_ENABLE_ASSERTS != 1 ) - #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." -#endif -#if ( AWS_IOT_MQTT_ENABLE_METRICS != 0 ) && ( AWS_IOT_MQTT_ENABLE_METRICS != 1 ) - #error "AWS_IOT_MQTT_ENABLE_METRICS must be 0 or 1." -#endif -#if ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 0 ) && ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES != 1 ) - #error "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be 0 or 1." -#endif -#if IOT_MQTT_RESPONSE_WAIT_MS <= 0 - #error "IOT_MQTT_RESPONSE_WAIT_MS cannot be 0 or negative." -#endif -#if IOT_MQTT_RETRY_MS_CEILING <= 0 - #error "IOT_MQTT_RETRY_MS_CEILING cannot be 0 or negative." -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Uninitialized value for @ref _initCalled. - */ -#define MQTT_LIBRARY_UNINITIALIZED ( ( uint32_t ) 0 ) - -/** - * @brief Initialized value for @ref _initCalled. - */ -#define MQTT_LIBRARY_INITIALIZED ( ( uint32_t ) 1 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Check if the library is initialized. - * - * @return `true` if IotMqtt_Init was called; `false` otherwise. - */ -static bool _checkInit( void ); - -/** - * @brief Set the unsubscribed flag of an MQTT subscription. - * - * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. - * @param[in] pMatch Not used. - * - * @return Always returns `true`. - */ -static bool _mqttSubscription_setUnsubscribe( const IotLink_t * const pSubscriptionLink, - void * pMatch ); - -/** - * @brief Destroy an MQTT subscription if its reference count is 0. - * - * @param[in] pData The subscription to destroy. This parameter is of type - * `void*` for compatibility with [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -static void _mqttSubscription_tryDestroy( void * pData ); - -/** - * @brief Decrement the reference count of an MQTT operation and attempt to - * destroy it. - * - * @param[in] pData The operation data to destroy. This parameter is of type - * `void*` for compatibility with [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ -static void _mqttOperation_tryDestroy( void * pData ); - -/** - * @brief Initialize the keep-alive operation for an MQTT connection. - * - * @param[in] pNetworkInfo User-provided network information for the new - * connection. - * @param[in] keepAliveSeconds User-provided keep-alive interval. - * @param[out] pMqttConnection The MQTT connection associated with the keep-alive. - * - * @return `true` if the keep-alive job was successfully created; `false` otherwise. - */ -static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds, - _mqttConnection_t * pMqttConnection ); - -/** - * @brief Initialize a network connection, creating it if necessary. - * - * @param[in] pNetworkInfo User-provided network information for the connection - * connection. - * @param[out] pNetworkConnection On success, the created and/or initialized network connection. - * @param[out] pCreatedNewNetworkConnection On success, `true` if a new network connection was created; `false` if an existing one will be used. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - */ -static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - IotNetworkConnection_t * pNetworkConnection, - bool * pCreatedNewNetworkConnection ); - -/** - * @brief Creates a new MQTT connection and initializes its members. - * - * @param[in] awsIotMqttMode Specifies if this connection is to an AWS IoT MQTT server. - * @param[in] pNetworkInfo User-provided network information for the new - * connection. - * @param[in] keepAliveSeconds User-provided keep-alive interval for the new connection. - * - * @return Pointer to a newly-created MQTT connection; `NULL` on failure. - */ -static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, - const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds ); - -/** - * @brief Destroys the members of an MQTT connection. - * - * @param[in] pMqttConnection Which connection to destroy. - */ -static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); - -/** - * @brief Common setup function for subscribe and unsubscribe operations. - * - * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a - * description of the parameters and return values. - */ -static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttOperation_t * const pOperationReference ); - -/** - * @brief Utility function for creating and serializing subscription requests. - * - * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a - * description of the parameters and return values. - */ -static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - IotMqttSerializeSubscribe_t serializeSubscription, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - _mqttOperation_t ** ppSubscriptionOperation ); - -/** - * @brief Utility function for sending/scheduling a subscribe, unsubscribe or publish message. - * - * @param[in] pMqttOperation Reference to MQTT operation. - * @param[in] flags Flags which modify the behavior of this function. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_SCHEDULING_ERROR. - */ -static IotMqttError_t _sendMqttMessage( _mqttOperation_t * pMqttOperation, - uint32_t flags ); - -/** - * @brief The common component of both @ref mqtt_function_subscribeasync and @ref - * mqtt_function_unsubscribeasync. - * - * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a - * description of the parameters and return values. - */ -static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - IotMqttSerializeSubscribe_t serializeSubscription, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pOperationReference ); - -/** - * @brief Set an operation reference if provided. - * - * @param[out] pOperationReference Operation reference provided by the application. - * @param[in] pNewOperation Operation to set. - */ -static void _setOperationReference( IotMqttOperation_t * const pOperationReference, - _mqttOperation_t * pNewOperation ); - -/** - * @brief Wait for an MQTT operation to complete. - * - * See @ref mqtt_function_wait for a description of the parameters and return values. - */ -static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, - uint32_t timeoutMs ); - -/** - * @brief Utility function for scheduling ping request after connection with - * with the broker is established. - * - * @param[in] pMqttConnection MQTT connection reference. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SCHEDULING_ERROR. - */ -static IotMqttError_t _scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); - -/** - * @brief Utility function for sending connect request. - * - * @param[in] pOperation CONNECT operation reference. - * @param[in] timeoutMs How many milliseconds to wait for CONN_ACK. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_PARAMETER, #IOT_MQTT_NETWORK_ERROR. - */ -static IotMqttError_t _sendConnectRequest( _mqttOperation_t * pOperation, - uint32_t timeoutMs ); - -/** - * @brief Utility function for adding subscriptions to connect request. - * - * @param[in] pOperation CONNECT operation reference. - * @param[in] pMqttConnection MQTT connection reference. - * @param[in] pConnectInfo MQTT connection information. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY - */ -static IotMqttError_t _addSubscriptions( _mqttOperation_t * pOperation, - IotMqttConnection_t pMqttConnection, - const IotMqttConnectInfo_t * pConnectInfo ); - -/** - * @brief Utility function for handling MQTT connect failure. - * - * @param[in] pMqttConnection MQTT connection reference. - * @param[in] pNetworkConnection Network connection reference. - * @param[in] pNetworkInfo User-provided network information. - * @param[in] pOperation CONNECT operation reference. - * @param[in] ownNetworkConnection if true, connection needs to be closed. - * - */ -static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, - IotNetworkConnection_t pNetworkConnection, - const IotMqttNetworkInfo_t * pNetworkInfo, - _mqttOperation_t * pOperation, - bool ownNetworkConnection ); - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Declaration of local MQTT serializer override selectors - */ -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePingreq_t, - _getMqttPingreqSerializer, - _IotMqtt_SerializePingreq, - serialize.pingreq ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqtt_SerializePublish_t, - _getMqttPublishSerializer, - _IotMqtt_SerializePublish, - serialize.publish ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, - _getMqttSubscribeSerializer, - _IotMqtt_SerializeSubscribe, - serialize.subscribe ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, - _getMqttUnsubscribeSerializer, - _IotMqtt_SerializeUnsubscribe, - serialize.unsubscribe ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeConnect_t, - _getMqttConnectSerializer, - _IotMqtt_SerializeConnect, - serialize.connect ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeDisconnect_t, - _getMqttDisconnectSerializer, - _IotMqtt_SerializeDisconnect, - serialize.disconnect ) -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - #define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq - #define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish - #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket - #define _getMqttSubscribeSerializer( pSerializer ) _IotMqtt_SerializeSubscribe - #define _getMqttUnsubscribeSerializer( pSerializer ) _IotMqtt_SerializeUnsubscribe - #define _getMqttConnectSerializer( pSerializer ) _IotMqtt_SerializeConnect - #define _getMqttDisconnectSerializer( pSerializer ) _IotMqtt_SerializeDisconnect -#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -/** @endcond */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Place holder packet identifier used when - * _IotMqtt_AddSubscriptions is called with previous subscriptions lists. - * Any non-zero value is acceptable, since this value is never sent out to - * the broker. - */ -#define IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID 1 - -/*-----------------------------------------------------------*/ - -/** - * @brief Tracks whether @ref mqtt_function_init has been called. - * - * API functions will fail if @ref mqtt_function_init was not called. - */ -static volatile uint32_t _initCalled = MQTT_LIBRARY_UNINITIALIZED; - -/*-----------------------------------------------------------*/ - -static bool _checkInit( void ) -{ - bool status = true; - - if( _initCalled == MQTT_LIBRARY_UNINITIALIZED ) - { - IotLogError( "IotMqtt_Init was not called." ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _mqttSubscription_setUnsubscribe( const IotLink_t * const pSubscriptionLink, - void * pMatch ) -{ - /* Because this function is called from a container function, the given link - * must never be NULL. */ - IotMqtt_Assert( pSubscriptionLink != NULL ); - - /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the - * starting address of the struct and does not modify the link it points to. - * Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_11_8_violation] */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - pSubscriptionLink, - link ); - - /* Silence warnings about unused parameters. */ - ( void ) pMatch; - - /* Set the unsubscribed flag. */ - pSubscription->unsubscribed = true; - - return true; -} - -/*-----------------------------------------------------------*/ - -static void _mqttSubscription_tryDestroy( void * pData ) -{ - _mqttSubscription_t * pSubscription = ( _mqttSubscription_t * ) pData; - - /* Reference count must not be negative. */ - IotMqtt_Assert( pSubscription->references >= 0 ); - - /* Unsubscribed flag should be set. */ - IotMqtt_Assert( pSubscription->unsubscribed == true ); - - /* Free the subscription if it has no references. */ - if( pSubscription->references == 0 ) - { - IotMqtt_FreeSubscription( pSubscription ); - } -} - -/*-----------------------------------------------------------*/ - -static void _mqttOperation_tryDestroy( void * pData ) -{ - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pData; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - - /* Incoming PUBLISH operations may always be freed. */ - if( pOperation->incomingPublish == true ) - { - /* Cancel the incoming PUBLISH operation's job. */ - taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pOperation->job, - NULL ); - - /* If the operation's job was not canceled, it must be already executing. - * Any other return value is invalid. */ - IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || - ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); - - /* Check if the incoming PUBLISH job was canceled. */ - if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) - { - /* Job was canceled. Process incoming PUBLISH now to clean up. */ - _IotMqtt_ProcessIncomingPublish( IOT_SYSTEM_TASKPOOL, - pOperation->job, - pOperation ); - } - } - else - { - /* Decrement reference count and destroy operation if possible. */ - if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) - { - _IotMqtt_DestroyOperation( pOperation ); - } - } -} - -/*-----------------------------------------------------------*/ - -static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds, - _mqttConnection_t * pMqttConnection ) -{ - bool status = true; - IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; - IotTaskPoolError_t jobStatus = IOT_TASKPOOL_SUCCESS; - - /* Network information is not used when MQTT packet serializers are disabled. */ - ( void ) pNetworkInfo; - - /* Set PINGREQ operation members. */ - pMqttConnection->pingreq.u.operation.type = IOT_MQTT_PINGREQ; - - /* Convert the keep-alive interval to milliseconds. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = ( uint32_t ) keepAliveSeconds * 1000U; - pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = ( uint32_t ) keepAliveSeconds * 1000U; - - /* Generate a PINGREQ packet. */ - serializeStatus = _getMqttPingreqSerializer( pNetworkInfo->pMqttSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), - &( pMqttConnection->pingreq.u.operation.packetSize ) ); - - if( serializeStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to allocate PINGREQ packet for new connection." ); - - status = false; - } - else - { - /* Create the task pool job that processes keep-alive. Task pool job - * creation for a pre-allocated job should never fail. */ - jobStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, - pMqttConnection, - &( pMqttConnection->pingreq.jobStorage ), - &( pMqttConnection->pingreq.job ) ); - IotMqtt_Assert( jobStatus == IOT_TASKPOOL_SUCCESS ); - - /* Keep-alive references its MQTT connection, so increment reference. */ - ( pMqttConnection->references )++; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - IotNetworkConnection_t * pNetworkConnection, - bool * pCreatedNewNetworkConnection ) -{ - IotNetworkError_t status = IOT_NETWORK_SUCCESS; - - /* Network info must not be NULL. */ - if( pNetworkInfo == NULL ) - { - IotLogError( "Network information cannot be NULL." ); - - status = IOT_NETWORK_BAD_PARAMETER; - } - else if( pNetworkInfo->createNetworkConnection == true ) - { - /* Create a new network connection if requested. Otherwise, copy the existing - * network connection. */ - status = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->u.setup.pNetworkServerInfo, - pNetworkInfo->u.setup.pNetworkCredentialInfo, - pNetworkConnection ); - - if( status == IOT_NETWORK_SUCCESS ) - { - /* This MQTT connection owns the network connection it created and - * should destroy it on cleanup. */ - *pCreatedNewNetworkConnection = true; - } - else - { - IotLogError( "Failed to create network connection: %d", status ); - } - } - else - { - /* A connection already exists; the caller should not destroy - * it on cleanup. */ - *pNetworkConnection = pNetworkInfo->u.pNetworkConnection; - *pCreatedNewNetworkConnection = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, - const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds ) -{ - bool status = true; - _mqttConnection_t * pMqttConnection = NULL; - bool referencesMutexCreated = false, subscriptionMutexCreated = false; - - /* Allocate memory for the new MQTT connection. */ - pMqttConnection = IotMqtt_MallocConnection( sizeof( _mqttConnection_t ) ); - - if( pMqttConnection == NULL ) - { - IotLogError( "Failed to allocate memory for new connection." ); - - status = false; - } - else - { - /* Clear the MQTT connection, then copy the MQTT server mode, network - * interface, and disconnect callback. */ - ( void ) memset( pMqttConnection, 0x00, sizeof( _mqttConnection_t ) ); - pMqttConnection->awsIotMqttMode = awsIotMqttMode; - pMqttConnection->pNetworkInterface = pNetworkInfo->pNetworkInterface; - pMqttConnection->disconnectCallback = pNetworkInfo->disconnectCallback; - - /* Start a new MQTT connection with a reference count of 1. */ - pMqttConnection->references = 1; - - /* Create the references mutex for a new connection. It is a recursive mutex. */ - referencesMutexCreated = IotMutex_Create( &( pMqttConnection->referencesMutex ), true ); - - if( referencesMutexCreated == false ) - { - IotLogError( "Failed to create references mutex for new connection." ); - - status = false; - } - } - - if( status == true ) - { - /* Create the subscription mutex for a new connection. */ - subscriptionMutexCreated = IotMutex_Create( &( pMqttConnection->subscriptionMutex ), false ); - - if( subscriptionMutexCreated == false ) - { - IotLogError( "Failed to create subscription mutex for new connection." ); - - status = false; - } - else - { - /* Create the new connection's subscription and operation lists. */ - IotListDouble_Create( &( pMqttConnection->subscriptionList ) ); - IotListDouble_Create( &( pMqttConnection->pendingProcessing ) ); - IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); - - /* Check if keep-alive is active for this connection. */ - if( keepAliveSeconds != 0U ) - { - status = _createKeepAliveOperation( pNetworkInfo, - keepAliveSeconds, - pMqttConnection ); - } - } - } - - /* Clean up mutexes and connection if this function failed. */ - if( status == false ) - { - if( subscriptionMutexCreated == true ) - { - IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); - } - - if( referencesMutexCreated == true ) - { - IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); - } - - if( pMqttConnection != NULL ) - { - IotMqtt_FreeConnection( pMqttConnection ); - pMqttConnection = NULL; - } - } - - return pMqttConnection; -} - -/*-----------------------------------------------------------*/ - -static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) -{ - IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - - /* Clean up keep-alive if still allocated. */ - if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) - { - IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); - - _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pMqttConnection->pingreq.u.operation.pMqttPacket ); - - /* Clear data about the keep-alive. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; - pMqttConnection->pingreq.u.operation.pMqttPacket = NULL; - pMqttConnection->pingreq.u.operation.packetSize = 0; - - /* Decrement reference count. */ - pMqttConnection->references--; - } - - /* A connection to be destroyed should have no keep-alive and at most 1 - * reference. */ - IotMqtt_Assert( pMqttConnection->references <= 1 ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs == 0U ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.pMqttPacket == NULL ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize == 0U ); - - /* Remove all subscriptions. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _mqttSubscription_setUnsubscribe, - NULL, - _mqttSubscription_tryDestroy, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - /* Destroy an owned network connection. */ - if( pMqttConnection->ownNetworkConnection == true ) - { - networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "(MQTT connection %p) Failed to destroy network connection.", - pMqttConnection ); - } - else - { - IotLogInfo( "(MQTT connection %p) Network connection destroyed.", - pMqttConnection ); - } - } - - /* Destroy mutexes. */ - IotMutex_Destroy( &( pMqttConnection->referencesMutex ) ); - IotMutex_Destroy( &( pMqttConnection->subscriptionMutex ) ); - - IotLogDebug( "(MQTT connection %p) Connection destroyed.", pMqttConnection ); - - /* Free connection. */ - IotMqtt_FreeConnection( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttOperation_t * const pOperationReference ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* This function should only be called for subscribe or unsubscribe. */ - IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || - ( operation == IOT_MQTT_UNSUBSCRIBE ) ); - - /* Check that IotMqtt_Init was called. */ - if( _checkInit() == false ) - { - status = IOT_MQTT_NOT_INITIALIZED; - } - else - { - /* Check that all elements in the subscription list are valid. */ - if( _IotMqtt_ValidateSubscriptionList( operation, - mqttConnection->awsIotMqttMode, - pSubscriptionList, - subscriptionCount ) == false ) - { - status = IOT_MQTT_BAD_PARAMETER; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) - { - if( pOperationReference == NULL ) - { - IotLogError( "Reference must be provided for a waitable %s.", - IotMqtt_OperationType( operation ) ); - - status = IOT_MQTT_BAD_PARAMETER; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - IotMqttSerializeSubscribe_t serializeSubscription, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - _mqttOperation_t ** ppSubscriptionOperation ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttOperation_t * pSubscriptionOperation = NULL; - - /* Create a subscription operation. */ - status = _IotMqtt_CreateOperation( mqttConnection, - flags, - pCallbackInfo, - ppSubscriptionOperation ); - - if( status == IOT_MQTT_SUCCESS ) - { - pSubscriptionOperation = ( *ppSubscriptionOperation ); - - - /* Check the subscription operation data and set the operation type. */ - IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.periodic.retry.limit == 0U ); - pSubscriptionOperation->u.operation.type = operation; - - /* Generate a subscription packet from the subscription list. */ - status = serializeSubscription( pSubscriptionList, - subscriptionCount, - &( pSubscriptionOperation->u.operation.pMqttPacket ), - &( pSubscriptionOperation->u.operation.packetSize ), - &( pSubscriptionOperation->u.operation.packetIdentifier ) ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0U ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _sendMqttMessage( _mqttOperation_t * pMqttOperation, - uint32_t flags ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - IotMqtt_Assert( pMqttOperation != NULL ); - - /* Send the SUBSCRIBE packet. */ - if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) - { - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pMqttOperation->job, pMqttOperation ); - } - else - { - status = _IotMqtt_ScheduleOperation( pMqttOperation, - _IotMqtt_ProcessSend, - 0 ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - IotMqttSerializeSubscribe_t serializeSubscription, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pOperationReference ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttOperation_t * pSubscriptionOperation = NULL; - - /* Create and serialize the subscription operation. */ - status = _subscriptionCreateAndSerialize( operation, - mqttConnection, - serializeSubscription, - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - &pSubscriptionOperation ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Add the subscription list for a SUBSCRIBE. */ - if( operation == IOT_MQTT_SUBSCRIBE ) - { - status = _IotMqtt_AddSubscriptions( mqttConnection, - pSubscriptionOperation->u.operation.packetIdentifier, - pSubscriptionList, - subscriptionCount ); - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Set the reference, if provided. */ - _setOperationReference( pOperationReference, pSubscriptionOperation ); - - /* Send or schedule subscribe request. */ - status = _sendMqttMessage( pSubscriptionOperation, flags ); - - if( status != IOT_MQTT_SUCCESS ) - { - IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", - mqttConnection, - IotMqtt_OperationType( operation ) ); - - if( operation == IOT_MQTT_SUBSCRIBE ) - { - _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, - pSubscriptionOperation->u.operation.packetIdentifier, - MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - } - - /* Clear the previously set (and now invalid) reference. */ - _setOperationReference( pOperationReference, IOT_MQTT_OPERATION_INITIALIZER ); - } - } - - /* Clean up if this function failed. */ - if( status != IOT_MQTT_SUCCESS ) - { - if( pSubscriptionOperation != NULL ) - { - _IotMqtt_DestroyOperation( pSubscriptionOperation ); - } - } - else - { - status = IOT_MQTT_STATUS_PENDING; - - IotLogInfo( "(MQTT connection %p) %s operation scheduled.", - mqttConnection, - IotMqtt_OperationType( operation ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _setOperationReference( IotMqttOperation_t * const pOperationReference, - _mqttOperation_t * pNewOperation ) -{ - if( pOperationReference != NULL ) - { - *pOperationReference = pNewOperation; - } -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _waitForOperation( IotMqttOperation_t operation, - uint32_t timeoutMs ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( IotSemaphore_TimedWait( &( operation->u.operation.notify.waitSemaphore ), - timeoutMs ) == false ) - { - status = IOT_MQTT_TIMEOUT; - - /* Attempt to cancel the job of the timed out operation. */ - ( void ) _IotMqtt_DecrementOperationReferences( operation, true ); - - /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ - if( operation->u.operation.type == IOT_MQTT_SUBSCRIBE ) - { - IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" - " subscriptions of timed-out SUBSCRIBE.", - operation->pMqttConnection, - operation ); - - _IotMqtt_RemoveSubscriptionByPacket( operation->pMqttConnection, - operation->u.operation.packetIdentifier, - MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - } - } - else - { - /* Retrieve the status of the completed operation. */ - status = operation->u.operation.status; - } - - /* Coverity finds a MISRA 13.2 violation in this log statement as the order - * of evaluation for IotMqtt_OperationType and IotMqtt_strerror is not - * defined. This is not an issue as these functions do not change data and - * only convert codes into constant strings. */ - /* coverity[misra_c_2012_rule_13_2_violation] */ - IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", - operation->pMqttConnection, - IotMqtt_OperationType( operation->u.operation.type ), - operation, - IotMqtt_strerror( status ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _scheduleKeepAlive( IotMqttConnection_t pMqttConnection ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - - /* Check if a keep-alive job should be scheduled. */ - if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) - { - IotLogDebug( "Scheduling first MQTT keep-alive job." ); - - taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - pMqttConnection->pingreq.job, - pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ); - } - - if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) - { - status = IOT_MQTT_SCHEDULING_ERROR; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _sendConnectRequest( _mqttOperation_t * pOperation, - uint32_t timeoutMs ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - IotMqtt_Assert( pOperation != NULL ); - - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); - - /* Send the CONNECT packet. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - - /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - status = IotMqtt_Wait( pOperation, timeoutMs ); - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _addSubscriptions( _mqttOperation_t * pOperation, - IotMqttConnection_t pMqttConnection, - const IotMqttConnectInfo_t * pConnectInfo ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - IotMqtt_Assert( pOperation != NULL ); - IotMqtt_Assert( pConnectInfo != NULL ); - - /* Ensure the members set by operation creation and serialization - * are appropriate for a blocking CONNECT. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0U ); - - /* Set the operation type. */ - pOperation->u.operation.type = IOT_MQTT_CONNECT; - - /* Add previous session subscriptions. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) - { - /* Previous subscription count should have been validated as nonzero. */ - IotMqtt_Assert( pConnectInfo->previousSubscriptionCount > 0U ); - - status = _IotMqtt_AddSubscriptions( pMqttConnection, - IOT_MQTT_PREVIOUS_SUBSCRIPTIONS_PACKET_ID, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _handleConnectFailure( IotMqttConnection_t pMqttConnection, - IotNetworkConnection_t pNetworkConnection, - const IotMqttNetworkInfo_t * pNetworkInfo, - _mqttOperation_t * pOperation, - bool ownNetworkConnection ) -{ - IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - - /* The network connection must be closed if it was created. */ - if( ownNetworkConnection == true ) - { - networkStatus = pNetworkInfo->pNetworkInterface->close( pNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "Failed to close network connection." ); - } - else - { - IotLogInfo( "Network connection closed on error." ); - } - } - - if( pOperation != NULL ) - { - _IotMqtt_DestroyOperation( pOperation ); - } - - if( pMqttConnection != NULL ) - { - _destroyMqttConnection( pMqttConnection ); - } -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ) -{ - bool disconnected = false; - - /* Lock the mutex protecting the reference count. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - /* Reference count must not be negative. */ - IotMqtt_Assert( pMqttConnection->references >= 0 ); - - /* Read connection status. */ - disconnected = pMqttConnection->disconnected; - - /* Increment the connection's reference count if it is not disconnected. */ - if( disconnected == false ) - { - ( pMqttConnection->references )++; - - /* In some implementations IotLogDebug() maps to C standard printing API - * that needs specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", - pMqttConnection, - ( long int ) pMqttConnection->references - 1, - ( long int ) pMqttConnection->references ); - } - else - { - IotLogWarn( "(MQTT connection %p) Attempt to use closed connection.", pMqttConnection ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - return( disconnected == false ); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ) -{ - bool destroyConnection = false; - - /* Lock the mutex protecting the reference count. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - /* Decrement reference count. It must not be negative. */ - ( pMqttConnection->references )--; - IotMqtt_Assert( pMqttConnection->references >= 0 ); - - /* In some implementations IotLogDebug() maps to C standard printing API - * that needs specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite stdint.h - * being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p) Reference count changed from %ld to %ld.", - pMqttConnection, - ( long int ) pMqttConnection->references + 1, - ( long int ) pMqttConnection->references ); - - /* Check if this connection may be destroyed. */ - if( pMqttConnection->references == 0 ) - { - destroyConnection = true; - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Destroy an unreferenced MQTT connection. */ - if( destroyConnection == true ) - { - IotLogDebug( "(MQTT connection %p) Connection will be destroyed now.", - pMqttConnection ); - _destroyMqttConnection( pMqttConnection ); - } -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_Init( void ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint32_t allowInitialization = Atomic_CompareAndSwap_u32( &_initCalled, - MQTT_LIBRARY_INITIALIZED, - MQTT_LIBRARY_UNINITIALIZED ); - - if( allowInitialization == 1U ) - { - /* Call any additional serializer initialization function if serializer - * overrides are enabled. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef _IotMqtt_InitSerializeAdditional - if( _IotMqtt_InitSerializeAdditional() == false ) - { - /* Log initialization status. */ - IotLogError( "Failed to initialize MQTT library serializer. " ); - - status = IOT_MQTT_INIT_FAILED; - } - - if( status == IOT_MQTT_SUCCESS ) - #endif /* ifdef _IotMqtt_InitSerializeAdditional */ - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - { - IotLogInfo( "MQTT library successfully initialized." ); - } - } - else - { - IotLogWarn( "IotMqtt_Init called with library already initialized." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotMqtt_Cleanup( void ) -{ - uint32_t allowCleanup = Atomic_CompareAndSwap_u32( &_initCalled, - MQTT_LIBRARY_UNINITIALIZED, - MQTT_LIBRARY_INITIALIZED ); - - if( allowCleanup == 1U ) - { - /* Call any additional serializer cleanup initialization function if serializer - * overrides are enabled. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef _IotMqtt_CleanupSerializeAdditional - _IotMqtt_CleanupSerializeAdditional(); - #endif - #endif - - IotLogInfo( "MQTT library cleanup done." ); - } - else - { - IotLogWarn( "IotMqtt_Init was not called before IotMqtt_Cleanup." ); - } -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, - const IotMqttConnectInfo_t * pConnectInfo, - uint32_t timeoutMs, - IotMqttConnection_t * const pMqttConnection ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - bool ownNetworkConnection = false; - IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - IotNetworkConnection_t pNetworkConnection = NULL; - _mqttOperation_t * pOperation = NULL; - _mqttConnection_t * pNewMqttConnection = NULL; - - /* Check that IotMqtt_Init was called. */ - if( _checkInit() == false ) - { - status = IOT_MQTT_NOT_INITIALIZED; - } - /* Validate network interface and connect info. */ - else if( _IotMqtt_ValidateConnect( pConnectInfo ) == false || pMqttConnection == NULL ) - { - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - networkStatus = _createNetworkConnection( pNetworkInfo, - &pNetworkConnection, - &ownNetworkConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - status = IOT_MQTT_NETWORK_ERROR; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogInfo( "Establishing new MQTT connection." ); - - /* Initialize a new MQTT connection object. */ - pNewMqttConnection = _createMqttConnection( pConnectInfo->awsIotMqttMode, - pNetworkInfo, - pConnectInfo->keepAliveSeconds ); - - if( pNewMqttConnection == NULL ) - { - status = IOT_MQTT_NO_MEMORY; - } - } - - /* Set the MQTT receive callback. */ - if( status == IOT_MQTT_SUCCESS ) - { - /* Set the network connection associated with the MQTT connection. */ - pNewMqttConnection->pNetworkConnection = pNetworkConnection; - pNewMqttConnection->ownNetworkConnection = ownNetworkConnection; - - /* Set the MQTT packet serializer overrides. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - pNewMqttConnection->pSerializer = pNetworkInfo->pMqttSerializer; - #else - pNewMqttConnection->pSerializer = NULL; - #endif - - networkStatus = pNewMqttConnection->pNetworkInterface->setReceiveCallback( pNetworkConnection, - IotMqtt_ReceiveCallback, - pNewMqttConnection ); - - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogError( "Failed to set MQTT network receive callback." ); - - status = IOT_MQTT_NETWORK_ERROR; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Create a CONNECT operation. */ - status = _IotMqtt_CreateOperation( pNewMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Add subscriptions to the operation */ - status = _addSubscriptions( pOperation, - pNewMqttConnection, - pConnectInfo ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ - status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( pConnectInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Send the CONNECT packet */ - status = _sendConnectRequest( pOperation, timeoutMs ); - - /* The call to wait inside _sendConnectRequest cleans up - * the CONNECT operation, so set the pointer to NULL. */ - pOperation = NULL; - - /* When a connection is successfully established, schedule keep-alive job. */ - if( status == IOT_MQTT_SUCCESS ) - { - status = _scheduleKeepAlive( pNewMqttConnection ); - } - } - - if( status != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to establish new MQTT connection, error %s.", - IotMqtt_strerror( status ) ); - - _handleConnectFailure( pNewMqttConnection, - pNetworkConnection, - pNetworkInfo, - pOperation, - ownNetworkConnection ); - } - else - { - IotLogInfo( "New MQTT connection %p established.", pMqttConnection ); - - /* Set the output parameter. */ - *pMqttConnection = pNewMqttConnection; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, - uint32_t flags ) -{ - bool disconnected = false, initCalled = false; - IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttOperation_t * pOperation = NULL; - - /* Check that IotMqtt_Init was called. */ - initCalled = _checkInit(); - - if( initCalled == false ) - { - status = IOT_MQTT_STATUS_PENDING; - } - else - { - /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" - * flag is not set. */ - if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == IOT_MQTT_FLAG_CLEANUP_ONLY ) - { - status = IOT_MQTT_STATUS_PENDING; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Read the connection status. */ - IotMutex_Lock( &( mqttConnection->referencesMutex ) ); - disconnected = mqttConnection->disconnected; - IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - - if( disconnected == true ) - { - status = IOT_MQTT_STATUS_PENDING; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); - - /* Create a DISCONNECT operation. This function blocks until the DISCONNECT - * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ - status = _IotMqtt_CreateOperation( mqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Ensure that the members set by operation creation and serialization - * are appropriate for a blocking DISCONNECT. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0U ); - - /* Set the operation type. */ - pOperation->u.operation.type = IOT_MQTT_DISCONNECT; - - /* Generate a DISCONNECT packet. */ - status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); - - /* Send the DISCONNECT packet. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - - /* Wait a short time for the DISCONNECT packet to be transmitted. */ - status = IotMqtt_Wait( pOperation, - IOT_MQTT_RESPONSE_WAIT_MS ); - - /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, - * or NETWORK ERROR. */ - if( status == IOT_MQTT_SUCCESS ) - { - IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); - } - else - { - IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || - ( status == IOT_MQTT_NETWORK_ERROR ) ); - - IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", - mqttConnection, - IotMqtt_strerror( status ) ); - } - } - - if( initCalled == true ) - { - /* Close the underlying network connection. This also cleans up keep-alive. - * Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. - * - * This error is triggered by a dereference of 'mqttConnection' in - * '_IotMqtt_CloseNetworkConnection'. Coverity assumes that 'mqttConnection' - * was freed in '_IotMqtt_CreateOperation' above, where cleanup code will - * free 'pNewMqttConnection' upon allocation failure. - * - * This will never happen as a valid MQTT connection passed to this - * function always has a positive reference count; therefore, - * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT - * connections will be freed. - * - * The annotation below suppresses this Coverity error. - */ - /* coverity[deref_arg] */ - _IotMqtt_CloseNetworkConnection( IOT_MQTT_DISCONNECT_CALLED, - mqttConnection ); - - /* Check if the connection may be destroyed. */ - IotMutex_Lock( &( mqttConnection->referencesMutex ) ); - - /* At this point, the connection should be marked disconnected. */ - IotMqtt_Assert( mqttConnection->disconnected == true ); - - /* Attempt cancel and destroy each operation in the connection's lists. */ - IotListDouble_RemoveAll( &( mqttConnection->pendingProcessing ), - _mqttOperation_tryDestroy, - offsetof( _mqttOperation_t, link ) ); - - IotListDouble_RemoveAll( &( mqttConnection->pendingResponse ), - _mqttOperation_tryDestroy, - offsetof( _mqttOperation_t, link ) ); - - IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - - /* Decrement the connection reference count and destroy it if possible. */ - _IotMqtt_DecrementConnectionReferences( mqttConnection ); - } -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pSubscribeOperation ) -{ - IotMqttError_t status = _subscriptionCommonSetup( IOT_MQTT_SUBSCRIBE, - mqttConnection, - pSubscriptionList, - subscriptionCount, - flags, - pSubscribeOperation ); - - if( IOT_MQTT_SUCCESS == status ) - { - status = _subscriptionCommon( IOT_MQTT_SUBSCRIBE, - mqttConnection, - _getMqttSubscribeSerializer( mqttConnection->pSerializer ), - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pSubscribeOperation ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Flags are not used, but the parameter is present for future compatibility. */ - ( void ) flags; - - /* Call the asynchronous SUBSCRIBE function. */ - status = IotMqtt_SubscribeAsync( mqttConnection, - pSubscriptionList, - subscriptionCount, - IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, - NULL, - &subscribeOperation ); - - /* Wait for the SUBSCRIBE operation to complete. */ - if( status == IOT_MQTT_STATUS_PENDING ) - { - status = IotMqtt_Wait( subscribeOperation, timeoutMs ); - } - - /* Ensure that a status was set. */ - IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pUnsubscribeOperation ) -{ - IotMqttError_t status = _subscriptionCommonSetup( IOT_MQTT_UNSUBSCRIBE, - mqttConnection, - pSubscriptionList, - subscriptionCount, - flags, - pUnsubscribeOperation ); - - if( IOT_MQTT_SUCCESS == status ) - { - /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ - _IotMqtt_RemoveSubscriptionByTopicFilter( mqttConnection, - pSubscriptionList, - subscriptionCount ); - - status = _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, - mqttConnection, - _getMqttUnsubscribeSerializer( mqttConnection->pSerializer ), - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pUnsubscribeOperation ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Flags are not used, but the parameter is present for future compatibility. */ - ( void ) flags; - - /* Call the asynchronous UNSUBSCRIBE function. */ - status = IotMqtt_UnsubscribeAsync( mqttConnection, - pSubscriptionList, - subscriptionCount, - IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, - NULL, - &unsubscribeOperation ); - - /* Wait for the UNSUBSCRIBE operation to complete. */ - if( status == IOT_MQTT_STATUS_PENDING ) - { - status = IotMqtt_Wait( unsubscribeOperation, timeoutMs ); - } - - /* Ensure that a status was set. */ - IotMqtt_Assert( status != IOT_MQTT_STATUS_PENDING ); - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pPublishOperation ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttOperation_t * pOperation = NULL; - uint8_t ** pPacketIdentifierHigh = NULL; - - /* Check that IotMqtt_Init was called. */ - if( _checkInit() == false ) - { - status = IOT_MQTT_NOT_INITIALIZED; - } - else if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, - pPublishInfo, - flags, - pCallbackInfo, - pPublishOperation ) == false ) - { - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Create a PUBLISH operation. */ - status = _IotMqtt_CreateOperation( mqttConnection, - flags, - pCallbackInfo, - &pOperation ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Check the PUBLISH operation data and set the operation type. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - - /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ - if( mqttConnection->awsIotMqttMode == true ) - { - pPacketIdentifierHigh = &( pOperation->u.operation.pPacketIdentifierHigh ); - } - - /* Generate a PUBLISH packet from pPublishInfo. */ - status = _getMqttPublishSerializer( mqttConnection->pSerializer )( pPublishInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ), - &( pOperation->u.operation.packetIdentifier ), - pPacketIdentifierHigh ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0U ); - - /* Initialize PUBLISH retry if retryLimit is set. */ - if( pPublishInfo->retryLimit > 0U ) - { - /* A QoS 0 PUBLISH may not be retried. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) - { - pOperation->u.operation.periodic.retry.limit = pPublishInfo->retryLimit; - pOperation->u.operation.periodic.retry.nextPeriodMs = pPublishInfo->retryMs; - } - } - - /* Set the reference, if provided. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) - { - _setOperationReference( pPublishOperation, pOperation ); - } - - /* Send the PUBLISH packet. */ - status = _sendMqttMessage( pOperation, flags ); - - if( status != IOT_MQTT_SUCCESS ) - { - IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", - mqttConnection ); - - /* Clear the previously set (and now invalid) reference. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) - { - _setOperationReference( pPublishOperation, IOT_MQTT_OPERATION_INITIALIZER ); - } - } - } - - /* Clean up the PUBLISH operation if this function fails. Otherwise, set the - * appropriate return code based on QoS. */ - - if( status != IOT_MQTT_SUCCESS ) - { - if( pOperation != NULL ) - { - _IotMqtt_DestroyOperation( pOperation ); - } - } - else - { - if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) - { - status = IOT_MQTT_STATUS_PENDING; - } - - IotLogInfo( "(MQTT connection %p) MQTT PUBLISH operation queued.", - mqttConnection ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - uint32_t timeoutMs ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, - * pPublishOperation = NULL; - /* Set only the "serial" flag. */ - uint32_t syncFlags = MQTT_INTERNAL_FLAG_BLOCK_ON_SEND; - - /* Flags are currently ignored. */ - ( void ) flags; - - if( pPublishInfo == NULL ) - { - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Set the waitable flag and reference for QoS 1 PUBLISH. */ - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) - { - syncFlags |= IOT_MQTT_FLAG_WAITABLE; - pPublishOperation = &publishOperation; - } - - /* Call the asynchronous PUBLISH function. */ - status = IotMqtt_PublishAsync( mqttConnection, - pPublishInfo, - syncFlags, - NULL, - pPublishOperation ); - - /* Wait for a queued QoS 1 PUBLISH to complete. */ - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) - { - if( status == IOT_MQTT_STATUS_PENDING ) - { - status = IotMqtt_Wait( publishOperation, timeoutMs ); - } - } - } - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, - uint32_t timeoutMs ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttConnection_t * pMqttConnection = NULL; - - /* Check that IotMqtt_Init was called. */ - if( _checkInit() == false ) - { - status = IOT_MQTT_NOT_INITIALIZED; - } - /* Validate the given operation reference. */ - else if( _IotMqtt_ValidateOperation( operation ) == false ) - { - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Check the MQTT connection status. */ - pMqttConnection = operation->pMqttConnection; - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( pMqttConnection->disconnected == true ) - { - IotLogError( "(MQTT connection %p, %s operation %p) MQTT connection is closed. " - "Operation cannot be waited on.", - pMqttConnection, - IotMqtt_OperationType( operation->u.operation.type ), - operation ); - - status = IOT_MQTT_NETWORK_ERROR; - } - else - { - IotLogInfo( "(MQTT connection %p, %s operation %p) Waiting for operation completion.", - pMqttConnection, - IotMqtt_OperationType( operation->u.operation.type ), - operation ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Only wait on an operation if the MQTT connection is active. */ - if( status == IOT_MQTT_SUCCESS ) - { - status = _waitForOperation( operation, timeoutMs ); - } - - /* Wait is finished; decrement operation reference count. */ - if( _IotMqtt_DecrementOperationReferences( operation, false ) == true ) - { - _IotMqtt_DestroyOperation( operation ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -const char * IotMqtt_strerror( IotMqttError_t status ) -{ - const char * pMessage = NULL; - - switch( status ) - { - case IOT_MQTT_SUCCESS: - pMessage = "SUCCESS"; - break; - - case IOT_MQTT_STATUS_PENDING: - pMessage = "PENDING"; - break; - - case IOT_MQTT_INIT_FAILED: - pMessage = "INITIALIZATION FAILED"; - break; - - case IOT_MQTT_BAD_PARAMETER: - pMessage = "BAD PARAMETER"; - break; - - case IOT_MQTT_NO_MEMORY: - pMessage = "NO MEMORY"; - break; - - case IOT_MQTT_NETWORK_ERROR: - pMessage = "NETWORK ERROR"; - break; - - case IOT_MQTT_SCHEDULING_ERROR: - pMessage = "SCHEDULING ERROR"; - break; - - case IOT_MQTT_BAD_RESPONSE: - pMessage = "BAD RESPONSE RECEIVED"; - break; - - case IOT_MQTT_TIMEOUT: - pMessage = "TIMEOUT"; - break; - - case IOT_MQTT_SERVER_REFUSED: - pMessage = "SERVER REFUSED"; - break; - - case IOT_MQTT_RETRY_NO_RESPONSE: - pMessage = "NO RESPONSE"; - break; - - case IOT_MQTT_NOT_INITIALIZED: - pMessage = "NOT INITIALIZED"; - break; - - default: - pMessage = "INVALID STATUS"; - break; - } - - return pMessage; -} - -/*-----------------------------------------------------------*/ - -const char * IotMqtt_OperationType( IotMqttOperationType_t operation ) -{ - const char * pMessage = NULL; - - switch( operation ) - { - case IOT_MQTT_CONNECT: - pMessage = "CONNECT"; - break; - - case IOT_MQTT_PUBLISH_TO_SERVER: - pMessage = "PUBLISH"; - break; - - case IOT_MQTT_PUBACK: - pMessage = "PUBACK"; - break; - - case IOT_MQTT_SUBSCRIBE: - pMessage = "SUBSCRIBE"; - break; - - case IOT_MQTT_UNSUBSCRIBE: - pMessage = "UNSUBSCRIBE"; - break; - - case IOT_MQTT_PINGREQ: - pMessage = "PINGREQ"; - break; - - case IOT_MQTT_DISCONNECT: - pMessage = "DISCONNECT"; - break; - - default: - pMessage = "INVALID OPERATION"; - break; - } - - return pMessage; -} - -/*-----------------------------------------------------------*/ - -/* Provide access to internal functions and variables if testing. */ -/* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ -/* coverity[misra_c_2012_rule_20_9_violation] */ -/* coverity[caretline] */ -#if IOT_BUILD_TESTS == 1 - #include "iot_test_access_mqtt_api.c" -#endif diff --git a/libraries/standard/mqtt/src/iot_mqtt_helper.c b/libraries/standard/mqtt/src/iot_mqtt_helper.c deleted file mode 100644 index 505de80a9a..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_helper.c +++ /dev/null @@ -1,972 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_helper.c - * @brief Implements helper functions for the MQTT library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -/* Default assert and memory allocation functions. */ -#include -#include - -/* For use of atomics library */ -#include "iot_atomic.h" - -/* MQTT helper header. */ -#include "private/iot_mqtt_helper.h" - -/*-----------------------------------------------------------*/ - -/* Configure logs for MQTT functions. */ -#ifdef IOT_LOG_LEVEL_MQTT - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "MQTT" ) -#include "iot_logging_setup.h" - -/*-----------------------------------------------------------*/ - -/** - * @def IotMqtt_Assert( expression ) - * @brief Assertion macro for the MQTT library. - * - * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if IOT_MQTT_ENABLE_ASSERTS == 1 - #ifndef IotMqtt_Assert - #ifdef Iot_DefaultAssert - #define IotMqtt_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for MQTT, but IotMqtt_Assert is not defined" - #endif - #endif -#else - #define IotMqtt_Assert( expression ) -#endif - - -/*-----------------------------------------------------------*/ - -/* Username for metrics with AWS IoT. */ -#if ( AWS_IOT_MQTT_ENABLE_METRICS == 1 ) || ( DOXYGEN == 1 ) - #ifndef AWS_IOT_METRICS_USERNAME - -/** - * @brief Specify C SDK and version. - */ - #define AWS_IOT_METRICS_USERNAME "?SDK=C&Version=4.0.0" - -/** - * @brief The length of #AWS_IOT_METRICS_USERNAME. - */ - #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1U ) - #endif /* ifndef AWS_IOT_METRICS_USERNAME */ -#endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Encode both connection and metrics username into a buffer, - * if they will fit. - * - * @param[in] pDestination Buffer to write username into. - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[out] pEncodedUserName Whether the username was written into the buffer. - * - * @return Pointer to the end of encoded string, which will be identical to - * `pDestination` if nothing was encoded. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold `pConnectInfo->userNameLength` + - * #AWS_IOT_METRICS_USERNAME_LENGTH bytes to avoid a buffer overflow. - */ - -static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo, - bool * pEncodedUserName ); - -/** - * @brief Encode a username into a CONNECT packet, if necessary. - * - * @param[out] pDestination Buffer for the CONNECT packet. - * @param[in] pConnectInfo User-provided CONNECT information. - * - * @return Pointer to the end of the encoded string, which will be identical to - * `pDestination` if nothing was encoded. - * - * @warning This function does not check the size of `pDestination`! To avoid a - * buffer overflow, ensure that `pDestination` is large enough to hold - * `pConnectInfo->userNameLength` bytes if a username is supplied, and/or - * #AWS_IOT_METRICS_USERNAME_LENGTH bytes if metrics are enabled. - */ -static uint8_t * _encodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ); - -/** - * @brief Encode a C string as a UTF-8 string, per MQTT 3.1.1 spec. - * - * @param[out] pDestination Where to write the encoded string. - * @param[in] source The string to encode. - * @param[in] sourceLength The length of source. - * - * @return Pointer to the end of the encoded string, which is `sourceLength+2` - * bytes greater than `pDestination`. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold `sourceLength+2` bytes to avoid a buffer - * overflow. - */ -static uint8_t * _encodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ); - -/** - * @brief Encode the "Remaining length" field per MQTT spec. - * - * @param[out] pDestination Where to write the encoded "Remaining length". - * @param[in] length The "Remaining length" to encode. - * - * @return Pointer to the end of the encoded "Remaining length", which is 1-4 - * bytes greater than `pDestination`. - * - * @warning This function does not check the size of `pDestination`! Ensure that - * `pDestination` is large enough to hold the encoded "Remaining length" using - * the function #_IotMqtt_RemainingLengthEncodedSize to avoid buffer overflows. - */ -static uint8_t * _encodeRemainingLength( uint8_t * pDestination, - size_t length ); - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeUserNameAndMetrics( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo, - bool * pEncodedUserName ) -{ - uint8_t * pBuffer = pDestination; - - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - const char * pMetricsUserName = AWS_IOT_METRICS_USERNAME; - - /* Only include metrics if it will fit within the encoding - * standard. */ - if( ( ( size_t ) pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( size_t ) ( UINT16_MAX ) ) ) - { - /* Write the high byte of the combined length. */ - pBuffer[ 0 ] = UINT16_HIGH_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - - /* Write the low byte of the combined length. */ - pBuffer[ 1 ] = UINT16_LOW_BYTE( ( pConnectInfo->userNameLength + - AWS_IOT_METRICS_USERNAME_LENGTH ) ); - pBuffer += 2; - - /* Write the identity portion of the username. - * As the types of char and uint8_t are of the same size, this memcpy - * is acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, pConnectInfo->pUserName, pConnectInfo->userNameLength ); - pBuffer += pConnectInfo->userNameLength; - - /* Write the metrics portion of the username. - * As the types of char and uint8_t are of the same size, this memcpy - * is acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, pMetricsUserName, AWS_IOT_METRICS_USERNAME_LENGTH ); - pBuffer += AWS_IOT_METRICS_USERNAME_LENGTH; - - *pEncodedUserName = true; - } - else - { - IotLogWarn( "Username length of %lu is larger than maximum %lu.", - ( pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ), - UINT16_MAX ); - } - #else /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ - /* Avoid unused variable warnings when AWS_IOT_MQTT_ENABLE_METRICS is set to 0. */ - ( void ) pBuffer; - ( void ) pConnectInfo; - ( void ) pEncodedUserName; - #endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ - - return pBuffer; -} - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeUserName( uint8_t * pDestination, - const IotMqttConnectInfo_t * pConnectInfo ) -{ - bool encodedUserName = false; - uint8_t * pBuffer = pDestination; - - /* If metrics are enabled, write the metrics username into the CONNECT packet. - * Otherwise, write the username and password only when not connecting to the - * AWS IoT MQTT server. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - IotLogInfo( "Anonymous metrics (SDK language, SDK version) will be provided to AWS IoT. " - "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); - - /* Determine if the Connect packet should use a combination of the username - * for authentication plus the SDK version string. */ - if( pConnectInfo->pUserName != NULL ) - { - /* Encode username and metrics if they will fit. */ - pBuffer = _encodeUserNameAndMetrics( pBuffer, pConnectInfo, &encodedUserName ); - } - else - { - /* The username is not being used for authentication, but - * metrics are enabled. */ - pBuffer = _encodeString( pBuffer, - AWS_IOT_METRICS_USERNAME, - AWS_IOT_METRICS_USERNAME_LENGTH ); - - encodedUserName = true; - } - #endif /* #if AWS_IOT_MQTT_ENABLE_METRICS == 1 */ - } - - /* Encode the username if there is one and it hasn't already been done. */ - if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pUserName, - pConnectInfo->userNameLength ); - } - - return pBuffer; -} -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeString( uint8_t * pDestination, - const char * source, - uint16_t sourceLength ) -{ - uint8_t * pBuffer = pDestination; - - /* The first byte of a UTF-8 string is the high byte of the string length. */ - *pBuffer = UINT16_HIGH_BYTE( sourceLength ); - pBuffer++; - - /* The second byte of a UTF-8 string is the low byte of the string length. */ - *pBuffer = UINT16_LOW_BYTE( sourceLength ); - pBuffer++; - - /* Copy the string into pBuffer. - * A precondition of this function is that pBuffer can hold sourceLength+2 - * bytes. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, source, sourceLength ); - - /* Return the pointer to the end of the encoded string. */ - pBuffer += sourceLength; - - return pBuffer; -} - -/*-----------------------------------------------------------*/ - -static uint8_t * _encodeRemainingLength( uint8_t * pDestination, - size_t length ) -{ - uint8_t lengthByte = 0, * pLengthEnd = pDestination; - size_t remainingLength = length; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - lengthByte = ( uint8_t ) ( remainingLength % 128U ); - remainingLength = remainingLength / 128U; - - /* Set the high bit of this byte, indicating that there's more data. */ - if( remainingLength > 0U ) - { - UINT8_SET_BIT( lengthByte, 7 ); - } - - /* Output a single encoded byte. */ - *pLengthEnd = lengthByte; - pLengthEnd++; - } while( remainingLength > 0U ); - - return pLengthEnd; -} - -/*-----------------------------------------------------------*/ - -size_t _IotMqtt_RemainingLengthEncodedSize( size_t length ) -{ - size_t encodedSize = 0; - - /* length should have already been checked before calling this function. */ - IotMqtt_Assert( length <= MQTT_MAX_REMAINING_LENGTH ); - - /* Determine how many bytes are needed to encode length. - * The values below are taken from the MQTT 3.1.1 spec. */ - - /* 1 byte is needed to encode lengths between 0 and 127. */ - if( length < 128U ) - { - encodedSize = 1; - } - /* 2 bytes are needed to encode lengths between 128 and 16,383. */ - else if( length < 16384U ) - { - encodedSize = 2; - } - /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ - else if( length < 2097152U ) - { - encodedSize = 3; - } - /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ - else - { - encodedSize = 4; - } - - return encodedSize; -} - -/*-----------------------------------------------------------*/ - -uint16_t _IotMqtt_NextPacketIdentifier( void ) -{ - /* MQTT specifies 2 bytes for the packet identifier; however, operating on - * 32-bit integers is generally faster. */ - static uint32_t nextPacketIdentifier = 1; - - /* The next packet identifier will be greater by 2. This prevents packet - * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet - * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - bool encodedUserName = false; - size_t connectPacketSize = 0, remainingLength = 0; - - /* The CONNECT packet will always include a 10-byte variable header. */ - connectPacketSize += 10U; - - /* Add the length of the client identifier if provided. */ - connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); - - /* Add the lengths of the will message and topic name if provided. */ - if( pConnectInfo->pWillInfo != NULL ) - { - connectPacketSize += pConnectInfo->pWillInfo->topicNameLength + sizeof( uint16_t ) + - pConnectInfo->pWillInfo->payloadLength + sizeof( uint16_t ); - } - - /* Depending on the status of metrics, add the length of the metrics username - * or the user-provided username. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - if( ( ( size_t ) pConnectInfo->userNameLength + AWS_IOT_METRICS_USERNAME_LENGTH ) <= ( ( size_t ) ( UINT16_MAX ) ) ) - { - connectPacketSize += ( AWS_IOT_METRICS_USERNAME_LENGTH + - ( size_t ) ( pConnectInfo->userNameLength ) + sizeof( uint16_t ) ); - - encodedUserName = true; - } - #endif - } - - if( ( pConnectInfo->pUserName != NULL ) && ( encodedUserName == false ) ) - { - connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); - } - - if( pConnectInfo->pPassword != NULL ) - { - connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); - } - - /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has - * been calculated. */ - remainingLength = connectPacketSize; - - /* Calculate the full size of the MQTT CONNECT packet by adding the size of - * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ - connectPacketSize += 1U + _IotMqtt_RemainingLengthEncodedSize( connectPacketSize ); - - /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ - if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) - { - status = false; - } - else - { - *pRemainingLength = remainingLength; - *pPacketSize = connectPacketSize; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pPacket, - size_t connectPacketSize ) -{ - uint8_t connectFlags = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) connectPacketSize; - - /* The first byte in the CONNECT packet is the control packet type. */ - *pBuffer = MQTT_PACKET_TYPE_CONNECT; - pBuffer++; - - /* The remaining length of the CONNECT packet is encoded starting from the - * second byte. The remaining length does not include the length of the fixed - * header or the encoding of the remaining length. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable - * header. This string is 4 bytes long. */ - pBuffer = _encodeString( pBuffer, "MQTT", 4 ); - - /* The MQTT protocol version is the second byte of the variable header. */ - *pBuffer = MQTT_VERSION_3_1_1; - pBuffer++; - - /* Set the CONNECT flags based on the given parameters. */ - if( pConnectInfo->cleanSession == true ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); - } - - /* Username and password depend on MQTT mode. */ - if( ( pConnectInfo->pUserName == NULL ) && - ( pConnectInfo->awsIotMqttMode == true ) ) - { - /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server - * never uses a password. */ - #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); - #endif - } - else - { - /* Set the flags for username and password if provided. */ - if( pConnectInfo->pUserName != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); - } - - if( pConnectInfo->pPassword != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); - } - } - - /* Set will flag if an LWT is provided. */ - if( pConnectInfo->pWillInfo != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); - - /* Flags only need to be changed for will QoS 1 and 2. */ - switch( pConnectInfo->pWillInfo->qos ) - { - case IOT_MQTT_QOS_1: - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); - break; - - case IOT_MQTT_QOS_2: - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); - break; - - default: - /* Empty default MISRA 16.4 */ - break; - } - - if( pConnectInfo->pWillInfo->retain == true ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); - } - } - - *pBuffer = connectFlags; - pBuffer++; - - /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ - *pBuffer = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); - pBuffer += 2; - - /* Write the client identifier into the CONNECT packet. */ - pBuffer = _encodeString( pBuffer, - pConnectInfo->pClientIdentifier, - pConnectInfo->clientIdentifierLength ); - - /* Write the will topic name and message into the CONNECT packet if provided. */ - if( pConnectInfo->pWillInfo != NULL ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pWillInfo->pTopicName, - pConnectInfo->pWillInfo->topicNameLength ); - - pBuffer = _encodeString( pBuffer, - pConnectInfo->pWillInfo->pPayload, - ( uint16_t ) pConnectInfo->pWillInfo->payloadLength ); - } - - /* Encode the username if there is one or metrics are enabled. */ - pBuffer = _encodeUserName( pBuffer, pConnectInfo ); - - /* Encode the password field, if requested by the app. */ - if( pConnectInfo->pPassword != NULL ) - { - pBuffer = _encodeString( pBuffer, - pConnectInfo->pPassword, - pConnectInfo->passwordLength ); - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == connectPacketSize ); - - /* Print out the serialized CONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT CONNECT packet:", pPacket, connectPacketSize ); -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_IncomingPacketValid( uint8_t packetType ) -{ - bool status = true; - - /* Check packet type. Mask out lower bits to ignore flags. */ - switch( packetType & 0xf0U ) - { - /* Valid incoming packet types. */ - case MQTT_PACKET_TYPE_CONNACK: - case MQTT_PACKET_TYPE_PUBLISH: - case MQTT_PACKET_TYPE_PUBACK: - case MQTT_PACKET_TYPE_SUBACK: - case MQTT_PACKET_TYPE_UNSUBACK: - case MQTT_PACKET_TYPE_PINGRESP: - break; - - /* Any other packet type is invalid. */ - default: - status = false; - break; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t subscribePacketSize ) -{ - uint16_t packetIdentifier = 0; - size_t i = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) subscribePacketSize; - - /* The first byte in SUBSCRIBE is the packet type. */ - *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; - pBuffer++; - - /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _IotMqtt_NextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0U ); - - /* Place the packet identifier into the SUBSCRIBE packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; - - /* Serialize each subscription topic filter and QoS. */ - for( i = 0; i < subscriptionCount; i++ ) - { - pBuffer = _encodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); - - /* Place the QoS in the SUBSCRIBE packet. */ - *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); - pBuffer++; - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == subscribePacketSize ); - - /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pPacket, subscribePacketSize ); -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - size_t i = 0, subscriptionPacketSize = 0; - - /* Only SUBSCRIBE and UNSUBSCRIBE operations should call this function. */ - IotMqtt_Assert( ( type == IOT_MQTT_SUBSCRIBE ) || ( type == IOT_MQTT_UNSUBSCRIBE ) ); - - /* The variable header of a subscription packet consists of a 2-byte packet - * identifier. */ - subscriptionPacketSize += sizeof( uint16_t ); - - /* Sum the lengths of all subscription topic filters; add 1 byte for each - * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ - for( i = 0; i < subscriptionCount; i++ ) - { - /* Add the length of the topic filter. */ - subscriptionPacketSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); - - /* Only SUBSCRIBE packets include the QoS. */ - if( type == IOT_MQTT_SUBSCRIBE ) - { - subscriptionPacketSize += 1U; - } - } - - /* At this point, the "Remaining length" has been calculated. Return error - * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, - * set the output parameter.*/ - if( subscriptionPacketSize > MQTT_MAX_REMAINING_LENGTH ) - { - status = false; - } - else - { - *pRemainingLength = subscriptionPacketSize; - - /* Calculate the full size of the subscription packet by adding the size of the - * "Remaining length" field plus 1 byte for the "Packet type" field. Set the - * pPacketSize output parameter. */ - subscriptionPacketSize += 1U + _IotMqtt_RemainingLengthEncodedSize( subscriptionPacketSize ); - *pPacketSize = subscriptionPacketSize; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - bool status = true; - size_t publishPacketSize = 0, payloadLimit = 0; - - /* The variable header of a PUBLISH packet always contains the topic name. */ - publishPacketSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); - - /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte - * packet identifier. */ - if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) - { - publishPacketSize += sizeof( uint16_t ); - } - - /* Calculate the maximum allowed size of the payload for the given parameters. - * This calculation excludes the "Remaining length" encoding, whose size is not - * yet known. */ - payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1U; - - /* Ensure that the given payload fits within the calculated limit. */ - if( pPublishInfo->payloadLength > payloadLimit ) - { - status = false; - } - else - { - /* Add the length of the PUBLISH payload. At this point, the "Remaining length" - * has been calculated. */ - publishPacketSize += pPublishInfo->payloadLength; - - /* Now that the "Remaining length" is known, recalculate the payload limit - * based on the size of its encoding. */ - payloadLimit -= _IotMqtt_RemainingLengthEncodedSize( publishPacketSize ); - - /* Check that the given payload fits within the size allowed by MQTT spec. */ - if( pPublishInfo->payloadLength > payloadLimit ) - { - status = false; - } - else - { - /* Set the "Remaining length" output parameter and calculate the full - * size of the PUBLISH packet. */ - *pRemainingLength = publishPacketSize; - - publishPacketSize += 1U + _IotMqtt_RemainingLengthEncodedSize( publishPacketSize ); - *pPacketSize = publishPacketSize; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pPacket, - size_t publishPacketSize ) -{ - uint8_t publishFlags = 0; - uint16_t packetIdentifier = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) publishPacketSize; - - /* The first byte of a PUBLISH packet contains the packet type and flags. */ - publishFlags = MQTT_PACKET_TYPE_PUBLISH; - - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); - } - else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); - } - else - { - /* Empty else MISRA 15.7 */ - } - - if( pPublishInfo->retain == true ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - } - - *pBuffer = publishFlags; - pBuffer++; - - /* The "Remaining length" is encoded from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* The topic name is placed after the "Remaining length". */ - pBuffer = _encodeString( pBuffer, - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); - - /* A packet identifier is required for QoS 1 and 2 messages. */ - if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) - { - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _IotMqtt_NextPacketIdentifier(); - IotMqtt_Assert( packetIdentifier != 0U ); - - /* Set the packet identifier output parameters. */ - *pPacketIdentifier = packetIdentifier; - - if( pPacketIdentifierHigh != NULL ) - { - *pPacketIdentifierHigh = pBuffer; - } - - /* Place the packet identifier into the PUBLISH packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; - } - - /* The payload is placed after the packet identifier. */ - if( pPublishInfo->payloadLength > 0U ) - { - /* This memcpy intentionally copies bytes from a void * buffer into - * a uint8_t * buffer. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); - pBuffer += pPublishInfo->payloadLength; - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == publishPacketSize ); - - /* Print out the serialized PUBLISH packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPacket, publishPacketSize ); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t unsubscribePacketSize ) -{ - uint16_t packetIdentifier = 0; - size_t i = 0; - uint8_t * pBuffer = pPacket; - - /* Avoid unused variable warning when logging and asserts are disabled. */ - ( void ) pPacket; - ( void ) unsubscribePacketSize; - - /* The first byte in UNSUBSCRIBE is the packet type. */ - *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; - pBuffer++; - - /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); - - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _IotMqtt_NextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0U ); - - /* Place the packet identifier into the UNSUBSCRIBE packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; - - /* Serialize each subscription topic filter. */ - for( i = 0; i < subscriptionCount; i++ ) - { - pBuffer = _encodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); - } - - /* Ensure that the difference between the end and beginning of the buffer - * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( ( size_t ) ( pBuffer - pPacket ) ) == unsubscribePacketSize ); - - /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pPacket, unsubscribePacketSize ); -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_ProcessPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check for QoS 2. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) - { - /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - IotLogDebug( "Bad QoS: 3." ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - pOutput->qos = IOT_MQTT_QOS_2; - } - } - /* Check for QoS 1. */ - else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - pOutput->qos = IOT_MQTT_QOS_1; - } - /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ - else - { - pOutput->qos = IOT_MQTT_QOS_0; - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "QoS is %d.", pOutput->qos ); - - /* Parse the Retain bit. */ - pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - - IotLogDebug( "Retain bit is %d.", pOutput->retain ); - - /* Parse the DUP bit. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) - { - IotLogDebug( "DUP is 1." ); - } - else - { - IotLogDebug( "DUP is 0." ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c b/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c deleted file mode 100644 index 94b8d4d9ec..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_lightweight_api.c +++ /dev/null @@ -1,1126 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_lightweight_api.c - * @brief Implements most user-facing functions of the MQTT library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* MQTT include. */ -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" -#include "private/iot_mqtt_helper.h" -#include "iot_mqtt_lightweight.h" - -/*-----------------------------------------------------------*/ - -/* Configure logs for MQTT functions. */ -#ifdef IOT_LOG_LEVEL_MQTT - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "MQTT" ) -#include "iot_logging_setup.h" - - -/*-----------------------------------------------------------*/ - -/** - * @def IotMqtt_Assert( expression ) - * @brief Assertion macro for the MQTT library. - * - * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if IOT_MQTT_ENABLE_ASSERTS == 1 - #ifndef IotMqtt_Assert - #ifdef Iot_DefaultAssert - #define IotMqtt_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for MQTT, but IotMqtt_Assert is not defined" - #endif - #endif -#else - #define IotMqtt_Assert( expression ) -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Get the remaining length from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] getNextByte Function pointer used to interact with the - * network to get next byte. - * - * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. - * - * @note This function is similar to _IotMqtt_GetRemainingLength() but it uses - * user provided getNextByte function to parse the stream instead of using - * _IotMqtt_GetNextByte(). pNetworkConnection is implementation dependent and - * user provided function makes use of it. - * - */ -static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, - IotMqttGetNextByte_t getNextByte ); - -/** - * @brief Deserialize a CONNACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t. Also - * prints out debug log messages about the packet. - * - * @param[in,out] pConnack Pointer to an MQTT packet struct representing a CONNACK. - * - * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; - * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; - * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. - */ -static IotMqttError_t _deserializeConnack( IotMqttPacketInfo_t * pConnack ); - -/** - * @brief Deserialize a SUBACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in,out] pSuback Pointer to an MQTT packet struct representing a SUBACK. - * - * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE - * if the SUBACK packet doesn't follow MQTT spec. - */ -static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ); - -/** - * @brief Deserialize a PUBACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in,out] pPuback Pointer to an MQTT packet struct representing a PUBACK. - * - * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE - * if the PUBACK packet doesn't follow MQTT spec. - */ -static IotMqttError_t _deserializePuback( IotMqttPacketInfo_t * pPuback ); - -/** - * @brief Deserialize a PINGRESP packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t. Also - * prints out debug log messages about the packet. - * - * @param[in,out] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. - * - * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE - * if the PINGRESP packet doesn't follow MQTT spec. - */ -static IotMqttError_t _deserializePingresp( IotMqttPacketInfo_t * pPingresp ); - -/** - * @brief Deserialize a UNSUBACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in,out] pUnsuback Pointer to an MQTT packet struct representing an UNSUBACK. - * - * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE - * if the UNSUBACK packet doesn't follow MQTT spec. - */ -static IotMqttError_t _deserializeUnsuback( IotMqttPacketInfo_t * pUnsuback ); - -/** - * @brief Deserialize a PUBLISH packet received from the server. - * - * Converts the packet from a stream of bytes to an #IotMqttPublishInfo_t and - * extracts the packet identifier. Also prints out debug log messages about the - * packet. - * - * @param[in,out] pPublish Pointer to an MQTT packet struct representing a PUBLISH. - * - * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE - * if the PUBLISH packet doesn't follow MQTT spec. - */ -static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ); - -/** - * @brief Decode the status bytes of a SUBACK packet. - * - * @param[in] statusCount Number of status bytes in the SUBACK. - * @param[in] pStatusStart The first status byte in the SUBACK. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SERVER_REFUSED, or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _readSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ); - -/** - * @brief Check the remaining length of incoming Publish against some value for - * QoS 0 or QoS 1/2. - * - * The remaining length for a QoS 1/2 will always be two greater than for a QoS 0. - * - * @param[in] pPublish Pointer to an MQTT packet struct representing a PUBLISH. - * @param[in] qos The QoS of the PUBLISH. - * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ); - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _readSubackStatus( size_t statusCount, - const uint8_t * pStatusStart ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t subscriptionStatus = 0; - size_t i = 0; - - /* Iterate through each status byte in the SUBACK packet. */ - for( i = 0; i < statusCount; i++ ) - { - /* Read a single status byte in SUBACK. */ - subscriptionStatus = *( pStatusStart + i ); - - /* MQTT 3.1.1 defines the following values as status codes. */ - switch( subscriptionStatus ) - { - case 0x00: - case 0x01: - case 0x02: - - /* In some implementations IotLogDebug() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); - break; - - case 0x80: - - /* In some implementations IotLogDebug() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "Topic filter %lu refused.", ( unsigned long ) i ); - - /* Application should remove subscription from the list */ - status = IOT_MQTT_SERVER_REFUSED; - - break; - - default: - IotLogDebug( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); - - status = IOT_MQTT_BAD_RESPONSE; - - break; - } - - /* Stop parsing the subscription statuses if a bad response was received. */ - if( status == IOT_MQTT_BAD_RESPONSE ) - { - break; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, - IotMqttGetNextByte_t getNextByte ) -{ - uint8_t encodedByte = 0; - size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - if( multiplier > 2097152U ) /* 128 ^ 3 */ - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) - { - remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; - multiplier *= 128U; - bytesDecoded++; - } - else - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - } - - if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - break; - } - } while( ( encodedByte & 0x80U ) != 0U ); - - /* Check that the decoded remaining length conforms to the MQTT specification. */ - if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) - { - expectedSize = _IotMqtt_RemainingLengthEncodedSize( remainingLength ); - - if( bytesDecoded != expectedSize ) - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4U ); - } - } - - return remainingLength; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializeConnack( IotMqttPacketInfo_t * pConnack ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - const uint8_t * pRemainingData = pConnack->pRemainingData; - - /* If logging is enabled, declare the CONNACK response code strings. The - * fourth byte of CONNACK indexes into this array for the corresponding response. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - static const char * const pConnackResponses[ 6 ] = - { - "Connection accepted.", /* 0 */ - "Connection refused: unacceptable protocol version.", /* 1 */ - "Connection refused: identifier rejected.", /* 2 */ - "Connection refused: server unavailable", /* 3 */ - "Connection refused: bad user name or password.", /* 4 */ - "Connection refused: not authorized." /* 5 */ - }; - #endif - - /* According to MQTT 3.1.1, the second byte of CONNACK must specify a - * "Remaining length" of 2. */ - if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) - { - IotLogError( "CONNACK does not have remaining length of %d.", - MQTT_PACKET_CONNACK_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - - /* Check the reserved bits in CONNACK. The high 7 bits of the second byte - * in CONNACK must be 0. */ - else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) - { - IotLogError( "Reserved bits in CONNACK incorrect." ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Determine if the "Session Present" bit is set. This is the lowest bit of - * the second byte in CONNACK. */ - if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - { - IotLogError( "CONNACK session present bit set." ); - - /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the - * "Session Present" bit is set. */ - if( pRemainingData[ 1 ] != 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - else - { - IotLogError( "CONNACK session present bit not set." ); - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ - if( pRemainingData[ 1 ] > 5U ) - { - IotLogError( "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Print the appropriate message for the CONNACK response code if logs are - * enabled. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - IotLogError( "%s", pConnackResponses[ pRemainingData[ 1 ] ] ); - #endif - - /* A nonzero CONNACK response code means the connection was refused. */ - if( pRemainingData[ 1 ] > 0U ) - { - status = IOT_MQTT_SERVER_REFUSED; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializeSuback( IotMqttPacketInfo_t * pSuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t remainingLength = pSuback->remainingLength; - const uint8_t * pVariableHeader = pSuback->pRemainingData; - - /* A SUBACK must have a remaining length of at least 3 to accommodate the - * packet identifier and at least 1 return code. */ - if( remainingLength < 3U ) - { - IotLogDebug( "SUBACK cannot have a remaining length less than 3." ); - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ - pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); - - IotLogDebug( "Packet identifier %hu.", pSuback->packetIdentifier ); - - status = _readSubackStatus( remainingLength - sizeof( uint16_t ), - pVariableHeader + sizeof( uint16_t ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializeUnsuback( IotMqttPacketInfo_t * pUnsuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ - if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) - { - IotLogError( "UNSUBACK does not have remaining length of %d.", - MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ - pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); - - /* Packet identifier cannot be 0. */ - if( pUnsuback->packetIdentifier == 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "Packet identifier %hu.", pUnsuback->packetIdentifier ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializePingresp( IotMqttPacketInfo_t * pPingresp ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check the "Remaining length" (second byte) of the received PINGRESP. */ - if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) - { - IotLogError( "PINGRESP does not have remaining length of %d.", - MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializePuback( IotMqttPacketInfo_t * pPuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check the "Remaining length" of the received PUBACK. */ - if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) - { - IotLogError( "PUBACK does not have remaining length of %d.", - MQTT_PACKET_PUBACK_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ - pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); - - IotLogDebug( "Packet identifier %hu.", pPuback->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPuback->packetIdentifier == 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializePublish( IotMqttPacketInfo_t * pPublish ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - IotMqttPublishInfo_t * pOutput = &( pPublish->pubInfo ); - uint8_t publishFlags = 0; - const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; - - /* The flags are the lower 4 bits of the first byte in PUBLISH. */ - publishFlags = pPublish->type; - - status = _IotMqtt_ProcessPublishFlags( publishFlags, pOutput ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining - * length of at least 3 to accommodate topic name length (2 bytes) and topic - * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of - * at least 5 for the packet identifier in addition to the topic name length and - * topic name. */ - status = _checkPublishRemainingLength( pPublish, pOutput->qos, MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Extract the topic name starting from the first byte of the variable header. - * The topic name string starts at byte 3 in the variable header. */ - pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); - - /* Sanity checks for topic name length and "Remaining length". The remaining - * length must be at least as large as the variable length header. */ - status = _checkPublishRemainingLength( pPublish, - pOutput->qos, - pOutput->topicNameLength + sizeof( uint16_t ) ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Parse the topic. */ - pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); - - IotLogDebug( "Topic name length %hu: %.*s", - pOutput->topicNameLength, - pOutput->topicNameLength, - pOutput->pTopicName ); - - /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet - * identifier starts immediately after the topic name. */ - pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); - - if( pOutput->qos > IOT_MQTT_QOS_0 ) - { - pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); - - IotLogDebug( "Packet identifier %hu.", pPublish->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPublish->packetIdentifier == 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain - * a packet identifier, but QoS 0 PUBLISH packets do not. */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) - { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh; - } - else - { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2U * sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); - } - - IotLogDebug( "Payload length %hu.", pOutput->payloadLength ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _checkPublishRemainingLength( const IotMqttPacketInfo_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Sanity checks for "Remaining length". */ - if( qos == IOT_MQTT_QOS_0 ) - { - /* Check that the "Remaining length" is greater than the minimum. */ - if( pPublish->remainingLength < qos0Minimum ) - { - IotLogDebug( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum ); - - status = IOT_MQTT_BAD_RESPONSE; - } - } - else - { - /* Check that the "Remaining length" is greater than the minimum. For - * QoS 1 or 2, this will be two bytes greater than for QoS 0 due to the - * packet identifier. */ - if( pPublish->remainingLength < ( qos0Minimum + 2U ) ) - { - IotLogDebug( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum + 2U ); - - status = IOT_MQTT_BAD_RESPONSE; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/* Lightweight Public API Functions */ - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) - { - IotLogError( "IotMqtt_GetConnectPacketSize() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _IotMqtt_ConnectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) - { - IotLogError( "Connect packet length exceeds %lu, which is the maximum" - " size allowed by MQTT 3.1.1.", - MQTT_PACKET_CONNECT_MAX_SIZE ); - - status = IOT_MQTT_BAD_PARAMETER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pConnectInfo == NULL ) ) - { - IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( pConnectInfo->pClientIdentifier == NULL ) - { - IotLogError( "Client identifier must be set." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( pConnectInfo->clientIdentifierLength == 0U ) - { - IotLogError( "Client identifier length must be set." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( " Serialize Connect packet remaining length (%lu) exceeds buffer size (%lu)", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _IotMqtt_SerializeConnectCommon( pConnectInfo, - remainingLength, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) - { - IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) - { - IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with unknown type." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( subscriptionCount == 0U ) - { - IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - if( _IotMqtt_SubscriptionPacketSize( type, - pSubscriptionList, - subscriptionCount, - pRemainingLength, - pPacketSize ) == false ) - { - IotLogError( "Subscription packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - status = IOT_MQTT_BAD_PARAMETER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) || ( pPacketIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_SerializeSubscribe() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( subscriptionCount == 0U ) - { - IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( " Subscribe packet remaining length (%lu) exceeds buffer size (%lu).", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _IotMqtt_SerializeSubscribeCommon( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, - IotMqttGetNextByte_t getNextByte, - IotNetworkConnection_t pNetworkConnection ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Read the packet type, which is the first byte available. */ - if( getNextByte( pNetworkConnection, &( pIncomingPacket->type ) ) == IOT_MQTT_SUCCESS ) - { - /* Check that the incoming packet type is valid. */ - if( _IotMqtt_IncomingPacketValid( pIncomingPacket->type ) == false ) - { - IotLogError( "(MQTT connection) Unknown packet type %02x received.", - pIncomingPacket->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Read the remaining length. */ - pIncomingPacket->remainingLength = _getRemainingLength( pNetworkConnection, - getNextByte ); - - if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - } - else - { - status = IOT_MQTT_NETWORK_ERROR; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_GetPublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) - { - IotLogError( "IotMqtt_GetPublishPacketSize() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) - { - IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _IotMqtt_PublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) - { - IotLogError( "Publish packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_PARAMETER; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pBuffer, - size_t bufferSize ) - -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pPacketIdentifier == NULL ) ) - { - IotLogError( "IotMqtt_SerializePublish() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) - { - IotLogError( "IotMqtt_SerializePublish() called with no topic." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( "Publish packet remaining length (%lu) exceeds buffer size (%lu).", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _IotMqtt_SerializePublishCommon( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( ( pBuffer == NULL ) || ( pPacketIdentifier == NULL ) || ( pSubscriptionList == NULL ) ) - { - IotLogError( "IotMqtt_SerializeUnsubscribe() called with required parameter(s) set to NULL." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( subscriptionCount == 0U ) - { - IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( remainingLength > bufferSize ) - { - IotLogError( "Unsubscribe packet remaining length (%lu) exceeds buffer size (%lu).", - remainingLength, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - _IotMqtt_SerializeUnsubscribeCommon( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - bufferSize ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( pBuffer == NULL ) - { - IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - - else if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) - { - IotLogError( "Disconnect packet length (%lu) exceeds buffer size (%lu).", - MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Disconnect packets are always the same. */ - pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; - pBuffer[ 1 ] = 0x00; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, - size_t bufferSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - if( pBuffer == NULL ) - { - IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) - { - IotLogError( "Pingreq length (%lu) exceeds buffer size (%lu).", - MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Ping request packets are always the same. */ - pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; - pBuffer[ 1 ] = 0x00; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Internal MQTT packet structure */ - IotMqttPacketInfo_t mqttPacket = { 0 }; - - if( pMqttPacket == NULL ) - { - IotLogError( "IotMqtt_DeserializePublish()called with NULL pMqttPacket pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( ( pMqttPacket->type & 0xf0U ) != MQTT_PACKET_TYPE_PUBLISH ) - { - IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Set internal mqtt packet parameters. */ - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; - mqttPacket.remainingLength = pMqttPacket->remainingLength; - mqttPacket.type = pMqttPacket->type; - status = _deserializePublish( &mqttPacket ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - pMqttPacket->pubInfo = mqttPacket.pubInfo; - pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Internal MQTT packet structure */ - IotMqttPacketInfo_t mqttPacket = { 0 }; - - if( pMqttPacket == NULL ) - { - IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else if( pMqttPacket->pRemainingData == NULL ) - { - IotLogError( "IotMqtt_DeserializeResponse() called with NULL pRemainingLength." ); - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Set internal mqtt packet parameters. */ - mqttPacket.pRemainingData = pMqttPacket->pRemainingData; - mqttPacket.remainingLength = pMqttPacket->remainingLength; - mqttPacket.type = pMqttPacket->type; - - /* Make sure response packet is a valid packet */ - switch( pMqttPacket->type & 0xf0U ) - { - case MQTT_PACKET_TYPE_CONNACK: - status = _deserializeConnack( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_SUBACK: - status = _deserializeSuback( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_UNSUBACK: - status = _deserializeUnsuback( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_PINGRESP: - status = _deserializePingresp( &mqttPacket ); - break; - - case MQTT_PACKET_TYPE_PUBACK: - status = _deserializePuback( &mqttPacket ); - break; - - /* Any other packet type is invalid. */ - default: - IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); - status = IOT_MQTT_BAD_RESPONSE; - break; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Set packetIdentifier only if success is returned. */ - pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c deleted file mode 100644 index f39eb98853..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ /dev/null @@ -1,874 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_network.c - * @brief Implements functions involving transport layer connections. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* Atomics include. */ -#include "iot_atomic.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Check if an incoming packet type is valid. - * - * @param[in] packetType The packet type to check. - * - * @return `true` if the packet type is valid; `false` otherwise. - */ -static bool _incomingPacketValid( uint8_t packetType ); - -/** - * @brief Allocate space for an incoming MQTT packet received from the network. - * - * @param[in] pNetworkConnection Network connection to be used for receive. - * @param[in] pMqttConnection The associated MQTT connection. - * @param[out] pIncomingPacket Output parameter for the incoming packet. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _allocateAndReceivePacket( IotNetworkConnection_t pNetworkConnection, - const _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ); - -/** - * @brief Get an incoming MQTT packet from the network. - * - * @param[in] pNetworkConnection Network connection to use for receive, which - * may be different from the network connection associated with the MQTT connection. - * @param[in] pMqttConnection The associated MQTT connection. - * @param[out] pIncomingPacket Output parameter for the incoming packet. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnection, - const _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ); - -/** - * @brief Deserialize a CONNACK, PUBACK, SUBACK, or UNSUBACK packet. - * - * @param[in] pMqttConnection The associated MQTT connection. - * @param[in] pIncomingPacket The packet received from the network. - * @param[in] _deserializer The deserialization function for the packet. - * @param[in] opType The type of operation corresponding to the packet. - * @param[in] pPacketIdentifier Address of incoming packet's packet identifier; - * `NULL` for a CONNACK. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE, or #IOT_MQTT_SERVER_REFUSED. - */ -static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket, - IotMqttDeserialize_t _deserializer, - IotMqttOperationType_t opType, - const uint16_t * pPacketIdentifier ); - -/** - * @brief Deserialize a PUBLISH packet. - * - * @param[in] pMqttConnection The associated MQTT connection. - * @param[in] pIncomingPacket The packet received from the network. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY, #IOT_MQTT_NETWORK_ERROR, - * or #IOT_MQTT_SCHEDULING_ERROR. - */ -static IotMqttError_t _deserializePublishPacket( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ); - -/** - * @brief Deserialize a PINGRESP packet. - * - * @param[in] pMqttConnection The associated MQTT connection. - * @param[in] pIncomingPacket The packet received from the network. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _deserializePingResp( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ); - -/** - * @brief Deserialize a packet received from the network. - * - * @param[in] pMqttConnection The associated MQTT connection. - * @param[in] pIncomingPacket The packet received from the network. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_NO_MEMORY, #IOT_MQTT_NETWORK_ERROR, - * #IOT_MQTT_SCHEDULING_ERROR, #IOT_MQTT_BAD_RESPONSE, or #IOT_MQTT_SERVER_REFUSED. - */ -static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ); - -/** - * @brief Send a PUBACK for a received QoS 1 PUBLISH packet. - * - * @param[in] pMqttConnection Which connection the PUBACK should be sent over. - * @param[in] packetIdentifier Which packet identifier to include in PUBACK. - */ -static void _sendPuback( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier ); - -/** - * @brief Flush a packet from the stream of incoming data. - * - * This function is called when memory for a packet cannot be allocated. The - * packet is flushed from the stream of incoming data so that the next packet - * may be read. - * - * @param[in] pNetworkConnection Network connection to use for receive, which - * may be different from the network connection associated with the MQTT connection. - * @param[in] pMqttConnection The associated MQTT connection. - * @param[in] length The length of the packet to flush. - */ -static void _flushPacket( IotNetworkConnection_t pNetworkConnection, - const _mqttConnection_t * pMqttConnection, - size_t length ); - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Declaration of local MQTT serializer override selectors - */ -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetPacketType_t, - _getPacketTypeFunc, - _IotMqtt_GetPacketType, - getPacketType ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetRemainingLength_t, - _getRemainingLengthFunc, - _IotMqtt_GetRemainingLength, - getRemainingLength ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getConnackDeserializer, - _IotMqtt_DeserializeConnack, - deserialize.connack ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getPublishDeserializer, - _IotMqtt_DeserializePublish, - deserialize.publish ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getPubackDeserializer, - _IotMqtt_DeserializePuback, - deserialize.puback ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getSubackDeserializer, - _IotMqtt_DeserializeSuback, - deserialize.suback ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getUnsubackDeserializer, - _IotMqtt_DeserializeUnsuback, - deserialize.unsuback ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, - _getPingrespDeserializer, - _IotMqtt_DeserializePingresp, - deserialize.pingresp ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePuback_t, - _getMqttPubackSerializer, - _IotMqtt_SerializePuback, - serialize.puback ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket ) -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - #define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType - #define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength - #define _getConnackDeserializer( pSerializer ) _IotMqtt_DeserializeConnack - #define _getPublishDeserializer( pSerializer ) _IotMqtt_DeserializePublish - #define _getPubackDeserializer( pSerializer ) _IotMqtt_DeserializePuback - #define _getSubackDeserializer( pSerializer ) _IotMqtt_DeserializeSuback - #define _getUnsubackDeserializer( pSerializer ) _IotMqtt_DeserializeUnsuback - #define _getPingrespDeserializer( pSerializer ) _IotMqtt_DeserializePingresp - #define _getMqttPubackSerializer( pSerializer ) _IotMqtt_SerializePuback - #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket -#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -/** @endcond */ - -/*-----------------------------------------------------------*/ - -static bool _incomingPacketValid( uint8_t packetType ) -{ - bool status = true; - - /* Check packet type. Mask out lower bits to ignore flags. */ - switch( packetType & 0xf0U ) - { - /* Valid incoming packet types. */ - case MQTT_PACKET_TYPE_CONNACK: - case MQTT_PACKET_TYPE_PUBLISH: - case MQTT_PACKET_TYPE_PUBACK: - case MQTT_PACKET_TYPE_SUBACK: - case MQTT_PACKET_TYPE_UNSUBACK: - case MQTT_PACKET_TYPE_PINGRESP: - break; - - /* Any other packet type is invalid. */ - default: - status = false; - break; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _allocateAndReceivePacket( IotNetworkConnection_t pNetworkConnection, - const _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t dataBytesRead = 0; - - IotMqtt_Assert( pMqttConnection != NULL ); - IotMqtt_Assert( pIncomingPacket != NULL ); - - /* Allocate a buffer for the remaining data and read the data. */ - if( pIncomingPacket->remainingLength > 0U ) - { - pIncomingPacket->pRemainingData = IotMqtt_MallocMessage( pIncomingPacket->remainingLength ); - - if( pIncomingPacket->pRemainingData == NULL ) - { - /* In some implementations IotLogError() maps to C standard printing API - * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite stdint.h - * being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " - "%lu for incoming packet type %lu.", - pMqttConnection, - ( unsigned long ) pIncomingPacket->remainingLength, - ( unsigned long ) pIncomingPacket->type ); - - _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); - - status = IOT_MQTT_NO_MEMORY; - } - - if( status == IOT_MQTT_SUCCESS ) - { - dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, - pIncomingPacket->pRemainingData, - pIncomingPacket->remainingLength ); - - if( dataBytesRead != pIncomingPacket->remainingLength ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _getIncomingPacket( IotNetworkConnection_t pNetworkConnection, - const _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* No buffer for remaining data should be allocated. */ - IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); - IotMqtt_Assert( pIncomingPacket->remainingLength == 0U ); - - /* Read the packet type, which is the first byte available. */ - pIncomingPacket->type = _getPacketTypeFunc( pMqttConnection->pSerializer )( pNetworkConnection, - pMqttConnection->pNetworkInterface ); - - /* Check that the incoming packet type is valid. */ - if( _incomingPacketValid( pIncomingPacket->type ) == false ) - { - IotLogError( "(MQTT connection %p) Unknown packet type %02x received.", - pMqttConnection, - pIncomingPacket->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Read the remaining length. */ - pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( pNetworkConnection, - pMqttConnection->pNetworkInterface ); - - if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - status = _allocateAndReceivePacket( pNetworkConnection, - pMqttConnection, - pIncomingPacket ); - } - - /* Clean up on error. */ - if( status != IOT_MQTT_SUCCESS ) - { - if( pIncomingPacket->pRemainingData != NULL ) - { - IotMqtt_FreeMessage( pIncomingPacket->pRemainingData ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializeAck( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket, - IotMqttDeserialize_t _deserializer, - IotMqttOperationType_t opType, - const uint16_t * pPacketIdentifier ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pOperation = NULL; - - status = _deserializer( pIncomingPacket ); - - pOperation = _IotMqtt_FindOperation( pMqttConnection, - opType, - pPacketIdentifier ); - - if( pOperation != NULL ) - { - pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializePublishPacket( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pOperation = NULL; - - /* Allocate memory to handle the incoming PUBLISH. */ - pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - if( pOperation == NULL ) - { - IotLogWarn( "Failed to allocate memory for incoming PUBLISH." ); - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* Set the members of the incoming PUBLISH operation. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - pOperation->incomingPublish = true; - pOperation->pMqttConnection = pMqttConnection; - pIncomingPacket->u.pIncomingPublish = pOperation; - /* Deserialize incoming PUBLISH. */ - status = _getPublishDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Send a PUBACK for QoS 1 PUBLISH. */ - if( pOperation->u.publish.publishInfo.qos == IOT_MQTT_QOS_1 ) - { - _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); - } - - /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ - pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; - pIncomingPacket->pRemainingData = NULL; - - /* Add the PUBLISH to the list of operations pending processing. - * Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. - * - * This error is triggered by a dereference of 'pMqttConnection' in - * 'IotMutex_Lock'. Coverity assumes that 'pMqttConnection' was freed in - * '_IotMqtt_CreateOperation', which was invoked in '_sendPuback'. - * - * This will never happen as a valid MQTT connection passed to this - * function always has a positive reference count; therefore, - * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT - * connections will be freed. Coverity also evaluates an incorrect - * path by assuming a waitable (1) value for flags, while the flags - * passed to this function were explicitly 0. - * - * The annotation below suppresses this Coverity error. - */ - /* coverity[deref_after_free] */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Increment the MQTT connection reference count before scheduling an - * incoming PUBLISH. */ - if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == true ) - { - /* Schedule PUBLISH for callback invocation. */ - status = _IotMqtt_ScheduleOperation( pOperation, _IotMqtt_ProcessIncomingPublish, 0 ); - } - else - { - status = IOT_MQTT_NETWORK_ERROR; - } - } - - /* Free PUBLISH operation on error. */ - if( ( status != IOT_MQTT_SUCCESS ) && ( pOperation != NULL ) ) - { - /* Check ownership of the received MQTT packet. */ - if( pOperation->u.publish.pReceivedData != NULL ) - { - /* Retrieve the pointer MQTT packet pointer so it may be freed later. */ - IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); - pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->u.publish.pReceivedData; - } - - /* Remove operation from pending processing list. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - IotMqtt_Assert( pOperation != NULL ); - IotMqtt_FreeOperation( pOperation ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializePingResp( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - - status = _getPingrespDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); - - if( status == IOT_MQTT_SUCCESS ) - { - if( Atomic_CompareAndSwap_u32( &( pMqttConnection->pingreq.u.operation.periodic.ping.failure ), - 0U, - 1U ) == 1U ) - { - IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", - pMqttConnection ); - } - else - { - IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", - pMqttConnection ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConnection, - _mqttPacket_t * pIncomingPacket ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - - /* A buffer for remaining data must be allocated if remaining length is not 0. */ - IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0U ) == - ( pIncomingPacket->pRemainingData != NULL ) ); - - /* Only valid packets should be given to this function. */ - IotMqtt_Assert( _incomingPacketValid( pIncomingPacket->type ) == true ); - - /* Mask out the low bits of packet type to ignore flags. */ - switch( ( pIncomingPacket->type & 0xf0U ) ) - { - case MQTT_PACKET_TYPE_CONNACK: - IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); - - /* Deserialize CONNACK and notify of result. */ - status = _deserializeAck( pMqttConnection, - pIncomingPacket, - _getConnackDeserializer( pMqttConnection->pSerializer ), - IOT_MQTT_CONNECT, - NULL ); - - break; - - case MQTT_PACKET_TYPE_PUBLISH: - IotLogDebug( "(MQTT connection %p) PUBLISH in data stream.", pMqttConnection ); - - /* Deserialize PUBLISH. */ - status = _deserializePublishPacket( pMqttConnection, pIncomingPacket ); - - break; - - case MQTT_PACKET_TYPE_PUBACK: - IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); - - /* Deserialize PUBACK and notify of result. */ - status = _deserializeAck( pMqttConnection, - pIncomingPacket, - _getPubackDeserializer( pMqttConnection->pSerializer ), - IOT_MQTT_PUBLISH_TO_SERVER, - &( pIncomingPacket->packetIdentifier ) ); - - break; - - case MQTT_PACKET_TYPE_SUBACK: - IotLogDebug( "(MQTT connection %p) SUBACK in data stream.", pMqttConnection ); - - /* Deserialize SUBACK and notify of result. */ - pIncomingPacket->u.pMqttConnection = pMqttConnection; - - status = _deserializeAck( pMqttConnection, - pIncomingPacket, - _getSubackDeserializer( pMqttConnection->pSerializer ), - IOT_MQTT_SUBSCRIBE, - &( pIncomingPacket->packetIdentifier ) ); - - break; - - case MQTT_PACKET_TYPE_UNSUBACK: - IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); - - /* Deserialize UNSUBACK and notify of result. */ - status = _deserializeAck( pMqttConnection, - pIncomingPacket, - _getUnsubackDeserializer( pMqttConnection->pSerializer ), - IOT_MQTT_UNSUBSCRIBE, - &( pIncomingPacket->packetIdentifier ) ); - - break; - - default: - /* The only remaining valid type is PINGRESP. */ - IotMqtt_Assert( ( pIncomingPacket->type & 0xf0U ) == MQTT_PACKET_TYPE_PINGRESP ); - - IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); - - /* Deserialize PINGRESP. */ - status = _deserializePingResp( pMqttConnection, pIncomingPacket ); - - break; - } - - if( status != IOT_MQTT_SUCCESS ) - { - /* Coverity finds a USE_AFTER_FREE error at this line. This is a false positive. - * - * This error is triggered by passing a freed argument 'pMqttConnection' - * to 'IotLogError'. Coverity assumes that 'pMqttConnection' was freed in - * '_IotMqtt_CreateOperation', which was invoked in '_deserializePublishPacket'. - * - * This will never happen as a valid MQTT connection passed to this - * function always has a positive reference count; therefore, - * '_IotMqtt_CreateOperation' will not free it. Only unreferenced MQTT - * connections will be freed. Coverity also evaluates an incorrect - * path by assuming a waitable (1) value for flags, while the flags - * passed to this function were explicitly 0. - * - * The annotation below suppresses this Coverity error. - */ - /* coverity[pass_freed_arg] */ - IotLogError( "(MQTT connection %p) Packet parser status %s.", - pMqttConnection, - IotMqtt_strerror( status ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static void _sendPuback( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pPubackOperation = NULL; - - IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", - pMqttConnection, - packetIdentifier ); - - /* Create a PUBACK operation. */ - status = _IotMqtt_CreateOperation( pMqttConnection, - 0, - NULL, - &pPubackOperation ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Set the operation type. */ - pPubackOperation->u.operation.type = IOT_MQTT_PUBACK; - - /* Generate a PUBACK packet from the packet identifier. */ - status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( packetIdentifier, - &( pPubackOperation->u.operation.pMqttPacket ), - &( pPubackOperation->u.operation.packetSize ) ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Add the PUBACK operation to the send queue for network transmission. */ - status = _IotMqtt_ScheduleOperation( pPubackOperation, - _IotMqtt_ProcessSend, - 0 ); - - if( status != IOT_MQTT_SUCCESS ) - { - IotLogError( "(MQTT connection %p) Failed to enqueue PUBACK for sending.", - pMqttConnection ); - } - } - } - - if( status != IOT_MQTT_SUCCESS ) - { - if( pPubackOperation != NULL ) - { - _IotMqtt_DestroyOperation( pPubackOperation ); - } - } -} - -/*-----------------------------------------------------------*/ - -static void _flushPacket( IotNetworkConnection_t pNetworkConnection, - const _mqttConnection_t * pMqttConnection, - size_t length ) -{ - size_t bytesFlushed = 0; - uint8_t receivedByte = 0; - - for( bytesFlushed = 0; bytesFlushed < length; bytesFlushed++ ) - { - ( void ) _IotMqtt_GetNextByte( pNetworkConnection, - pMqttConnection->pNetworkInterface, - &receivedByte ); - } -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_GetNextByte( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface, - uint8_t * pIncomingByte ) -{ - bool status = false; - uint8_t incomingByte = 0; - size_t bytesReceived = 0; - - /* Attempt to read 1 byte. */ - bytesReceived = pNetworkInterface->receive( pNetworkConnection, - &incomingByte, - 1 ); - - /* Set the output parameter and return success if 1 byte was read. */ - if( bytesReceived == 1U ) - { - *pIncomingByte = incomingByte; - status = true; - } - else - { - /* Network receive must return 0 on failure. */ - IotMqtt_Assert( bytesReceived == 0U ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, - _mqttConnection_t * pMqttConnection ) -{ - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; - IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; - IotNetworkConnection_t pNetworkConnection = NULL; - void * pDisconnectCallbackContext = NULL; - - /* Disconnect callback function. */ - void ( * disconnectCallback )( void * pContext, - IotMqttCallbackParam_t * pParam ) = NULL; - - /* Network close function. */ - IotNetworkError_t ( * closeConnection) ( IotNetworkConnection_t pConnection ) = NULL; - - /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pMqttConnection->disconnected = true; - - if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0U ) - { - /* Keep-alive must have a PINGREQ allocated. */ - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize != 0U ); - - /* PINGREQ provides a reference to the connection, so reference count must - * be nonzero. */ - IotMqtt_Assert( pMqttConnection->references > 0 ); - - /* Attempt to cancel the keep-alive job. */ - taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pMqttConnection->pingreq.job, - NULL ); - - /* Clean up keep-alive if its job was successfully canceled. Otherwise, - * the executing keep-alive job will clean up itself. */ - if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) - { - /* Free the packet */ - _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pMqttConnection->pingreq.u.operation.pMqttPacket ); - - /* Clear data about the keep-alive. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0U; - pMqttConnection->pingreq.u.operation.pMqttPacket = NULL; - pMqttConnection->pingreq.u.operation.packetSize = 0U; - - /* Keep-alive is cleaned up; decrement reference count. Since this - * function must be followed with a call to DISCONNECT, a check to - * destroy the connection is not done here. */ - pMqttConnection->references--; - - IotLogDebug( "(MQTT connection %p) Keep-alive job canceled and cleaned up.", - pMqttConnection ); - } - } - - /* Copy the function pointers and contexts, as the MQTT connection may be - * modified after the mutex is released. */ - disconnectCallback = pMqttConnection->disconnectCallback.function; - pDisconnectCallbackContext = pMqttConnection->disconnectCallback.pCallbackContext; - - closeConnection = pMqttConnection->pNetworkInterface->close; - pNetworkConnection = pMqttConnection->pNetworkConnection; - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Close the network connection. */ - if( closeConnection != NULL ) - { - closeStatus = closeConnection( pNetworkConnection ); - - if( closeStatus == IOT_NETWORK_SUCCESS ) - { - IotLogInfo( "(MQTT connection %p) Network connection closed.", pMqttConnection ); - } - else - { - IotLogWarn( "(MQTT connection %p) Failed to close network connection, error %d.", - pMqttConnection, - closeStatus ); - } - } - else - { - IotLogWarn( "(MQTT connection %p) No network close function was set. Network connection" - " not closed.", pMqttConnection ); - } - - /* Invoke the disconnect callback. */ - if( disconnectCallback != NULL ) - { - /* Set the members of the callback parameter. */ - callbackParam.mqttConnection = pMqttConnection; - callbackParam.u.disconnectReason = disconnectReason; - - disconnectCallback( pDisconnectCallbackContext, - &callbackParam ); - } -} - -/*-----------------------------------------------------------*/ - -void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, - void * pReceiveContext ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttPacket_t incomingPacket = { .u.pMqttConnection = NULL }; - - /* Cast context to correct type. */ - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pReceiveContext; - - /* Read an MQTT packet from the network. */ - status = _getIncomingPacket( pNetworkConnection, - pMqttConnection, - &incomingPacket ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Deserialize the received packet. */ - status = _deserializeIncomingPacket( pMqttConnection, - &incomingPacket ); - - /* Free any buffers allocated for the MQTT packet. */ - if( incomingPacket.pRemainingData != NULL ) - { - IotMqtt_FreeMessage( incomingPacket.pRemainingData ); - } - } - - /* Close the network connection on a bad response. */ - if( status == IOT_MQTT_BAD_RESPONSE ) - { - IotLogError( "(MQTT connection %p) Error processing incoming data. Closing connection.", - pMqttConnection ); - - _IotMqtt_CloseNetworkConnection( IOT_MQTT_BAD_PACKET_RECEIVED, - pMqttConnection ); - } -} - -/*-----------------------------------------------------------*/ - -/* Provide access to internal functions and variables if testing. */ -/* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ -/* coverity[misra_c_2012_rule_20_9_violation] */ -/* coverity[caretline] */ -#if IOT_BUILD_TESTS == 1 - #include "iot_test_access_mqtt_network.c" -#endif diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c deleted file mode 100644 index 5afce540cb..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ /dev/null @@ -1,1362 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_operation.c - * @brief Implements functions that process MQTT operations. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Atomics include. */ -#include "iot_atomic.h" - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Declaration of local MQTT serializer override selectors - */ -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - SERIALIZER_OVERRIDE_SELECTOR( IotMqttPublishSetDup_t, - _getMqttPublishSetDupFunc, - _IotMqtt_PublishSetDup, - serialize.publishSetDup ) - SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket ) -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket - #define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup -#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -/** @endcond */ - -/*-----------------------------------------------------------*/ - -/** - * @brief First parameter to #_mqttOperation_match. - */ -typedef struct _operationMatchParam -{ - IotMqttOperationType_t type; /**< @brief The type of operation to look for. */ - const uint16_t * pPacketIdentifier; /**< @brief The packet identifier associated with the operation. - * Set to `NULL` to ignore packet identifier. */ -} _operationMatchParam_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Match an MQTT operation by type and packet identifier. - * - * @param[in] pOperationLink Pointer to the link member of an #_mqttOperation_t. - * @param[in] pMatch Pointer to an #_operationMatchParam_t. - * - * @return `true` if the operation matches the parameters in `pArgument`; `false` - * otherwise. - */ -static bool _mqttOperation_match( const IotLink_t * const pOperationLink, - void * pMatch ); - -/** - * @brief Check if an operation with retry has exceeded its retry limit. - * - * If a PUBLISH operation is available for retry, this function also sets any - * necessary DUP flags. - * - * @param[in] pOperation The operation to check. - * - * @return `true` if the operation may be retried; `false` otherwise. - */ -static bool _checkRetryLimit( _mqttOperation_t * pOperation ); - -/** - * @brief Schedule the next send of an operation with retry. - * - * @param[in] pOperation The operation to schedule. - * - * @return `true` if the reschedule succeeded; `false` otherwise. - */ -static bool _scheduleNextRetry( _mqttOperation_t * pOperation ); - -/** - * @brief Schedule a callback for a completed MQTT operation. - * - * @param[in] pOperation The completed MQTT operation. - * - * @return `IOT_MQTT_SUCCESS` if the schedule was successful; - * `IOT_MQTT_SCHEDULING_ERROR` otherwise. - */ -static IotMqttError_t _scheduleCallback( _mqttOperation_t * pOperation ); - -/** - * @brief Complete a pending send operation. - * - * @param[in] pOperation The pending MQTT send operation. - * @param[out] pDestroyOperation Whether the operation should be destroyed afterwards. - * - * @return `true` if the operation is awaiting a response from the network; - * `false` if not. - */ -static bool _completePendingSend( _mqttOperation_t * pOperation, - bool * pDestroyOperation ); - -/** - * @brief Initialize newly created MQTT operation. - * - * @param[in] pMqttConnection The MQTT connection associated with the operation. - * @param[in] pOperation pointer to the new operation. - * @param[in] flags Flags variable passed to a user-facing MQTT function. - * @param[in] pCallbackInfo User-provided callback function and parameter. - * - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -static IotMqttError_t _initializeOperation( _mqttConnection_t * pMqttConnection, - _mqttOperation_t * pOperation, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo ); - -/** - * @brief Send MQTT Ping Request to the broker. - * - * @param[in] pMqttConnection The MQTT connection associated with the request. - * - * @return `true` if send is successful; `false` otherwise. - */ -static bool _sendPingRequest( _mqttConnection_t * pMqttConnection ); - -/*-----------------------------------------------------------*/ - -static bool _mqttOperation_match( const IotLink_t * const pOperationLink, - void * pMatch ) -{ - bool match = false; - - /* Because this function is called from a container function, the given link - * must never be NULL. */ - IotMqtt_Assert( pOperationLink != NULL ); - - /* Casting `pOperationLink` to uint8_t * is done only to calculate the - * starting address of the struct and does not modify the link it points to. - * Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_11_8_violation] */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - const _mqttOperation_t * pOperation = IotLink_Container( _mqttOperation_t, - pOperationLink, - link ); - const _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; - - /* Check for matching operations. */ - if( pParam->type == pOperation->u.operation.type ) - { - /* Check for matching packet identifiers. */ - if( pParam->pPacketIdentifier == NULL ) - { - match = true; - } - else - { - match = ( *( pParam->pPacketIdentifier ) == pOperation->u.operation.packetIdentifier ); - } - } - - return match; -} - -/*-----------------------------------------------------------*/ - -static bool _checkRetryLimit( _mqttOperation_t * pOperation ) -{ - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - bool status = true, setDup = false; - - /* Only PUBLISH may be retried. */ - IotMqtt_Assert( pOperation->u.operation.type == IOT_MQTT_PUBLISH_TO_SERVER ); - - /* Check if the retry limit is exhausted. */ - if( pOperation->u.operation.periodic.retry.count > pOperation->u.operation.periodic.retry.limit ) - { - /* The retry count may be at most one more than the retry limit, which - * accounts for the final check for a PUBACK. */ - IotMqtt_Assert( pOperation->u.operation.periodic.retry.count == - ( pOperation->u.operation.periodic.retry.limit + 1U ) ); - - IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", - pMqttConnection, - pOperation, - pOperation->u.operation.periodic.retry.limit ); - - status = false; - } - else - { - if( pOperation->u.operation.periodic.retry.count == 1U ) - { - /* The DUP flag should always be set on the first retry. */ - setDup = true; - } - else if( pMqttConnection->awsIotMqttMode == true ) - { - /* In AWS IoT MQTT mode, the DUP flag (really a change to the packet - * identifier) must be reset on every retry. */ - setDup = true; - } - else - { - setDup = false; - } - - if( setDup == true ) - { - /* In AWS IoT MQTT mode, the references mutex must be locked to - * prevent the packet identifier from being read while it is being - * changed. */ - if( pMqttConnection->awsIotMqttMode == true ) - { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - } - - /* Set the DUP flag */ - _getMqttPublishSetDupFunc( pMqttConnection->pSerializer )( pOperation->u.operation.pMqttPacket, - pOperation->u.operation.pPacketIdentifierHigh, - &( pOperation->u.operation.packetIdentifier ) ); - - if( pMqttConnection->awsIotMqttMode == true ) - { - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) -{ - bool firstRetry = false; - uint32_t scheduleDelay = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - - /* This function should never be called with retry count greater than - * retry limit. */ - IotMqtt_Assert( pOperation->u.operation.periodic.retry.count <= - pOperation->u.operation.periodic.retry.limit ); - - /* Increment the retry count. */ - ( pOperation->u.operation.periodic.retry.count )++; - - /* Check for a response shortly for the final retry. Otherwise, calculate the - * next retry period. */ - if( pOperation->u.operation.periodic.retry.count > - pOperation->u.operation.periodic.retry.limit ) - { - scheduleDelay = IOT_MQTT_RESPONSE_WAIT_MS; - - IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Final retry was sent. Will check " - "for response in %d ms.", - pMqttConnection, - pOperation, - IOT_MQTT_RESPONSE_WAIT_MS ); - } - else - { - scheduleDelay = pOperation->u.operation.periodic.retry.nextPeriodMs; - - /* Double the retry period, subject to a ceiling value. */ - pOperation->u.operation.periodic.retry.nextPeriodMs *= 2U; - - if( pOperation->u.operation.periodic.retry.nextPeriodMs > IOT_MQTT_RETRY_MS_CEILING ) - { - pOperation->u.operation.periodic.retry.nextPeriodMs = IOT_MQTT_RETRY_MS_CEILING; - } - - /* In some implementations IotLogDebug() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", - pMqttConnection, - pOperation, - ( unsigned long ) pOperation->u.operation.periodic.retry.count, - ( unsigned long ) pOperation->u.operation.periodic.retry.limit, - ( unsigned long ) scheduleDelay ); - - /* Check if this is the first retry. */ - firstRetry = ( pOperation->u.operation.periodic.retry.count == 1U ); - - /* On the first retry, the PUBLISH will be moved from the pending processing - * list to the pending responses list. Lock the connection references mutex - * to manipulate the lists. */ - if( firstRetry == true ) - { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - } - } - - /* Reschedule the PUBLISH for another send. */ - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - scheduleDelay ); - - /* Check for successful reschedule. */ - if( status == IOT_MQTT_SUCCESS ) - { - /* Move a successfully rescheduled PUBLISH from the pending processing - * list to the pending responses list on the first retry. */ - if( firstRetry == true ) - { - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), - &( pOperation->link ) ); - } - } - - /* The references mutex only needs to be unlocked on the first retry, since - * only the first retry manipulates the connection lists. */ - if( firstRetry == true ) - { - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - - return( status == IOT_MQTT_SUCCESS ); -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _scheduleCallback( _mqttOperation_t * pOperation ) -{ - IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - - /* Non-waitable operation should have job reference of 1. */ - IotMqtt_Assert( pOperation->u.operation.jobReference == 1 ); - - /* Schedule an invocation of the callback. */ - if( pOperation->u.operation.notify.callback.function != NULL ) - { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessCompletedOperation, - 0 ); - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - - /* Place the scheduled operation back in the list of operations pending - * processing. */ - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - } - else - { - IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _completePendingSend( _mqttOperation_t * pOperation, - bool * pDestroyOperation ) -{ - bool networkPending = false, waitable = false; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - - /* Check if this operation is waitable. */ - waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; - - /* Check if this operation should be scheduled for retransmission. */ - if( pOperation->u.operation.periodic.retry.limit > 0U ) - { - if( _scheduleNextRetry( pOperation ) == false ) - { - pOperation->u.operation.status = IOT_MQTT_SCHEDULING_ERROR; - } - else - { - /* A successfully scheduled PUBLISH retry is awaiting a response - * from the network. */ - networkPending = true; - } - } - else - { - /* Decrement reference count to signal completion of send job. Check - * if the operation should be destroyed. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( waitable == true ) - { - *pDestroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); - } - - /* If the operation should not be destroyed, transfer it from the - * pending processing to the pending response list. */ - if( *pDestroyOperation == false ) - { - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), - &( pOperation->link ) ); - - /* This operation is now awaiting a response from the network. */ - networkPending = true; - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - - return networkPending; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _initializeOperation( _mqttConnection_t * pMqttConnection, - _mqttOperation_t * pOperation, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - - IotMqtt_Assert( pMqttConnection != NULL ); - IotMqtt_Assert( pOperation != NULL ); - - /* Clear the operation data. */ - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - - /* Initialize some members of the new operation. */ - pOperation->pMqttConnection = pMqttConnection; - pOperation->u.operation.jobReference = 1; - pOperation->u.operation.flags = flags; - pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; - - /* Check if the waitable flag is set. If it is, create a semaphore to - * wait on. */ - if( waitable == true ) - { - /* Create a semaphore to wait on for a waitable operation. */ - if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) - { - IotLogError( "(MQTT connection %p) Failed to create semaphore for " - "waitable operation.", - pMqttConnection ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* A waitable operation is created with an additional reference for the - * Wait function. */ - ( pOperation->u.operation.jobReference )++; - } - } - else - { - /* If the waitable flag isn't set but a callback is, copy the callback - * information. */ - if( pCallbackInfo != NULL ) - { - pOperation->u.operation.notify.callback = *pCallbackInfo; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _sendPingRequest( _mqttConnection_t * pMqttConnection ) -{ - size_t bytesSent = 0; - bool status = true; - uint32_t swapStatus = 0; - _mqttOperation_t * pPingreqOperation = NULL; - - IotMqtt_Assert( pMqttConnection != NULL ); - - IotLogDebug( "(MQTT connection %p) Sending PINGREQ.", pMqttConnection ); - - pPingreqOperation = &( pMqttConnection->pingreq ); - - /* Because PINGREQ may be used to keep the MQTT connection alive, it is - * more important than other operations. Bypass the queue of jobs for - * operations by directly sending the PINGREQ in this job. */ - bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, - pPingreqOperation->u.operation.pMqttPacket, - pPingreqOperation->u.operation.packetSize ); - - if( bytesSent != pPingreqOperation->u.operation.packetSize ) - { - IotLogError( "(MQTT connection %p) Failed to send PINGREQ.", pMqttConnection ); - status = false; - } - else - { - /* Update the timestamp of the last message on successful transmission. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pMqttConnection->lastMessageTime = IotClock_GetTimeMs(); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Assume the keep-alive will fail. The network receive callback will - * clear the failure flag upon receiving a PINGRESP. */ - swapStatus = Atomic_CompareAndSwap_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), - 1, - 0 ); - IotMqtt_Assert( swapStatus == 1U ); - - /* Set the period for scheduling a PINGRESP check. */ - pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; - - IotLogDebug( "(MQTT connection %p) PINGREQ sent.", pMqttConnection ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - _mqttOperation_t ** pNewOperation ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - bool decrementOnError = false; - _mqttOperation_t * pOperation = NULL; - bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - - /* If the waitable flag is set, make sure that there's no callback. */ - if( waitable == true ) - { - if( pCallbackInfo != NULL ) - { - IotLogError( "Callback should not be set for a waitable operation." ); - - status = IOT_MQTT_BAD_PARAMETER; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "(MQTT connection %p) Creating new operation record.", - pMqttConnection ); - - /* Increment the reference count for the MQTT connection when creating a new - * operation. */ - if( _IotMqtt_IncrementConnectionReferences( pMqttConnection ) == false ) - { - IotLogError( "(MQTT connection %p) New operation record cannot be created" - " for a closed connection", - pMqttConnection ); - - status = IOT_MQTT_NETWORK_ERROR; - } - else - { - /* Reference count will need to be decremented on error. */ - decrementOnError = true; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Allocate memory for a new operation. */ - pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - if( pOperation == NULL ) - { - IotLogError( "(MQTT connection %p) Failed to allocate memory for new " - "operation record.", - pMqttConnection ); - - status = IOT_MQTT_NO_MEMORY; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - status = _initializeOperation( pMqttConnection, pOperation, flags, pCallbackInfo ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Add this operation to the MQTT connection's operation list. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pOperation->link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Set the output parameter. */ - *pNewOperation = pOperation; - } - - /* Clean up operation and decrement reference count if this function failed. */ - - if( status != IOT_MQTT_SUCCESS ) - { - if( decrementOnError == true ) - { - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); - } - - if( pOperation != NULL ) - { - IotMqtt_FreeOperation( pOperation ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, - bool cancelJob ) -{ - bool destroyOperation = false; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - - /* Attempt to cancel the operation's job. */ - if( cancelJob == true ) - { - taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pOperation->job, - NULL ); - - if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Job canceled.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - } - - /* Decrement job reference count. */ - if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) - { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pOperation->u.operation.jobReference--; - - /* In some implementations IotLogDebug() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" - " from %d to %d.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation, - ( int ) ( pOperation->u.operation.jobReference + 1 ), - ( int ) ( pOperation->u.operation.jobReference ) ); - - /* The job reference count must be 0 or 1 after the decrement. */ - IotMqtt_Assert( ( pOperation->u.operation.jobReference == 0 ) || - ( pOperation->u.operation.jobReference == 1 ) ); - - /* This operation may be destroyed if its reference count is 0. */ - if( pOperation->u.operation.jobReference == 0 ) - { - destroyOperation = true; - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - - return destroyOperation; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) -{ - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - - IotLogDebug( "(MQTT connection %p, %s operation %p) Destroying operation.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - - /* The job reference count must be between 0 and 2. */ - IotMqtt_Assert( ( pOperation->u.operation.jobReference >= 0 ) && - ( pOperation->u.operation.jobReference <= 2 ) ); - - /* Jobs to be destroyed should be removed from the MQTT connection's - * lists. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Removed operation from connection lists.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation, - pMqttConnection ); - - IotListDouble_Remove( &( pOperation->link ) ); - } - else - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Operation was not present in connection lists.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - /* Free any allocated MQTT packet. */ - if( pOperation->u.operation.pMqttPacket != NULL ) - { - _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pOperation->u.operation.pMqttPacket ); - - IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - else - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Operation has no allocated MQTT packet.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - - /* Check if a wait semaphore was created for this operation. */ - if( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) - { - IotSemaphore_Destroy( &( pOperation->u.operation.notify.waitSemaphore ) ); - - IotLogDebug( "(MQTT connection %p, %s operation %p) Wait semaphore destroyed.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - - IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - - /* Free the memory used to hold operation data. */ - IotMqtt_FreeOperation( pOperation ); - - /* Decrement the MQTT connection's reference count after destroying an - * operation. */ - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pKeepAliveJob, - void * pContext ) -{ - bool status = true; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - uint32_t scheduleDelay = 0; - uint64_t elapsedTime = 0; - - /* Retrieve the MQTT connection from the context. */ - _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; - _mqttOperation_t * pPingreqOperation = &( pMqttConnection->pingreq ); - - /* Check parameters. */ - IotMqtt_Assert( pKeepAliveJob == pPingreqOperation->job ); - - /* Check that keep-alive interval is valid. The MQTT spec states its maximum - * value is 65,535 seconds. */ - IotMqtt_Assert( pPingreqOperation->u.operation.periodic.ping.keepAliveMs <= 65535000U ); - - /* Only two values are valid for the next keep alive job delay. */ - IotMqtt_Assert( ( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == - pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) || - ( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs - == IOT_MQTT_RESPONSE_WAIT_MS ) ); - - IotLogDebug( "(MQTT connection %p) Keep-alive job started.", pMqttConnection ); - - /* Determine whether to send a PINGREQ or check for PINGRESP. */ - if( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == - pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) - { - /* Only send the PINGREQ if the keep-alive period has elapsed since the connection - * was last used. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - elapsedTime = IotClock_GetTimeMs() - pMqttConnection->lastMessageTime; - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - if( elapsedTime < ( uint64_t ) pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs ) - { - /* In some implementations IotLogDebug() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p) Connection was last used %llu ms ago, which " - "is less than keep-alive period %lu ms. PINGREQ will not be sent.", - pMqttConnection, - ( unsigned long long ) elapsedTime, - ( unsigned long ) pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs ); - - /* Schedule the next keep-alive job one keep-alive period after the last packet was sent. */ - scheduleDelay = pPingreqOperation->u.operation.periodic.ping.keepAliveMs - ( ( uint32_t ) elapsedTime ); - } - else - { - status = _sendPingRequest( pMqttConnection ); - } - } - else - { - IotLogDebug( "(MQTT connection %p) Checking for PINGRESP.", pMqttConnection ); - - if( Atomic_Add_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), 0U ) == 0U ) - { - IotLogDebug( "(MQTT connection %p) PINGRESP was received.", pMqttConnection ); - - /* PINGRESP was received. Schedule the next PINGREQ transmission. */ - pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = - pPingreqOperation->u.operation.periodic.ping.keepAliveMs; - } - else - { - IotLogError( "(MQTT connection %p) Failed to receive PINGRESP within %d ms.", - pMqttConnection, - IOT_MQTT_RESPONSE_WAIT_MS ); - - /* The network receive callback did not clear the failure flag. */ - status = false; - } - } - - /* Reschedule this job. When a PINGREQ is sent, schedule a check for PINGRESP. - * When PINGREQ is not sent (because the connection was recently used) schedule - * another PINGREQ after the keep-alive period. */ - if( status == true ) - { - if( scheduleDelay == 0U ) - { - scheduleDelay = pPingreqOperation->u.operation.periodic.ping.nextPeriodMs; - } - - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - /* Re-create the keep-alive job for rescheduling. This should never fail. */ - taskPoolStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, - pContext, - IotTaskPool_GetJobStorageFromHandle( pKeepAliveJob ), - &pKeepAliveJob ); - IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); - - IotMqtt_Assert( scheduleDelay > 0 ); - taskPoolStatus = IotTaskPool_ScheduleDeferred( pTaskPool, - pKeepAliveJob, - scheduleDelay ); - - if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) - { - /* In some implementations IotLogDebug() maps to a C standard printing API - * that need specific primitive types for format specifiers. Also, - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p) Next keep-alive job in %lu ms.", - pMqttConnection, - ( unsigned long ) scheduleDelay ); - } - else - { - IotLogError( "(MQTT connection %p) Failed to reschedule keep-alive job, error %s.", - pMqttConnection, - IotTaskPool_strerror( taskPoolStatus ) ); - - status = false; - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - } - - /* Close the connection on failures. */ - if( status == false ) - { - _IotMqtt_CloseNetworkConnection( IOT_MQTT_KEEP_ALIVE_TIMEOUT, - pMqttConnection ); - - /* Keep-alive has failed and will no longer use this MQTT connection. */ - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); - } -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pPublishJob, - void * pContext ) -{ - _mqttOperation_t * pOperation = pContext; - IotMqttCallbackParam_t callbackParam = { .mqttConnection = NULL }; - - /* Check parameters. The task pool and job parameter is not used when asserts - * are disabled. */ - ( void ) pTaskPool; - ( void ) pPublishJob; - IotMqtt_Assert( pOperation->incomingPublish == true ); - IotMqtt_Assert( pPublishJob == pOperation->job ); - - /* Remove the operation from the pending processing list. */ - IotMutex_Lock( &( pOperation->pMqttConnection->referencesMutex ) ); - - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) - { - IotListDouble_Remove( &( pOperation->link ) ); - } - - IotMutex_Unlock( &( pOperation->pMqttConnection->referencesMutex ) ); - - /* Process the current PUBLISH. */ - callbackParam.u.message.info = pOperation->u.publish.publishInfo; - - _IotMqtt_InvokeSubscriptionCallback( pOperation->pMqttConnection, - &callbackParam ); - - /* Free buffers associated with the current PUBLISH message. */ - IotMqtt_Assert( pOperation->u.publish.pReceivedData != NULL ); - IotMqtt_FreeMessage( pOperation->u.publish.pReceivedData ); - - /* Free the incoming PUBLISH operation. */ - IotMqtt_FreeOperation( pOperation ); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pSendJob, - void * pContext ) -{ - size_t bytesSent = 0; - bool destroyOperation = false, waitable = false, networkPending = false; - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; - _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - - /* Check parameters. The task pool and job parameter is not used when asserts - * are disabled. */ - ( void ) pTaskPool; - ( void ) pSendJob; - IotMqtt_Assert( pSendJob == pOperation->job ); - - /* The given operation must have an allocated packet and be waiting for a status. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize != 0U ); - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - - /* Check if this operation is waitable. */ - waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; - - /* Check PUBLISH retry counts and limits. */ - if( pOperation->u.operation.periodic.retry.limit > 0U ) - { - if( _checkRetryLimit( pOperation ) == false ) - { - pOperation->u.operation.status = IOT_MQTT_RETRY_NO_RESPONSE; - } - } - - /* Send an operation that is waiting for a response. */ - if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Sending MQTT packet.", - pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - - /* Transmit the MQTT packet from the operation over the network. */ - bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, - pOperation->u.operation.pMqttPacket, - pOperation->u.operation.packetSize ); - - /* Check transmission status. */ - if( bytesSent != pOperation->u.operation.packetSize ) - { - pOperation->u.operation.status = IOT_MQTT_NETWORK_ERROR; - } - else - { - /* Update the timestamp of the last message on successful transmission. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pMqttConnection->lastMessageTime = IotClock_GetTimeMs(); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) - { - /* DISCONNECT operations are always waitable. */ - IotMqtt_Assert( waitable == true ); - - /* DISCONNECT operations are considered successful upon successful transmission. */ - pOperation->u.operation.status = IOT_MQTT_SUCCESS; - } - /* Non-waitable operations with no callback are also considered successful. */ - else if( waitable == false ) - { - if( pOperation->u.operation.notify.callback.function == NULL ) - { - pOperation->u.operation.status = IOT_MQTT_SUCCESS; - } - } - else - { - /* Empty else MISRA 15.7 */ - } - } - } - - /* Check if this operation requires further processing. */ - if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) - { - networkPending = _completePendingSend( pOperation, &destroyOperation ); - } - - /* Destroy the operation or notify of completion if necessary. */ - if( destroyOperation == true ) - { - _IotMqtt_DestroyOperation( pOperation ); - } - else - { - /* Do not check the operation status if a network response is pending, - * since a network response could modify the status. */ - if( networkPending == false ) - { - /* Operations that are not waiting for a network response either failed or - * completed successfully. Check that a status was set. */ - IotMqtt_Assert( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ); - - /* Notify of operation completion if this job set a status. */ - _IotMqtt_Notify( pOperation ); - } - } -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pOperationJob, - void * pContext ) -{ - bool destroyOperation = false; - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; - IotMqttCallbackParam_t callbackParam = { 0 }; - - /* Check parameters. The task pool and job parameter is not used when asserts - * are disabled. */ - ( void ) pTaskPool; - ( void ) pOperationJob; - ( void ) destroyOperation; - IotMqtt_Assert( pOperationJob == pOperation->job ); - - /* The operation's callback function and status must be set. */ - IotMqtt_Assert( pOperation->u.operation.notify.callback.function != NULL ); - IotMqtt_Assert( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ); - - callbackParam.mqttConnection = pOperation->pMqttConnection; - callbackParam.u.operation.type = pOperation->u.operation.type; - callbackParam.u.operation.reference = pOperation; - callbackParam.u.operation.result = pOperation->u.operation.status; - - /* Invoke the user callback function. */ - pOperation->u.operation.notify.callback.function( pOperation->u.operation.notify.callback.pCallbackContext, - &callbackParam ); - - /* Decrement the operation reference count. This function is at the end of the - * operation lifecycle, so the operation must be destroyed here. */ - destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); - IotMqtt_Assert( destroyOperation == true ); - _IotMqtt_DestroyOperation( pOperation ); -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, - IotTaskPoolRoutine_t jobRoutine, - uint32_t delay ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - - /* Check that job routine is valid. */ - IotMqtt_Assert( ( jobRoutine == _IotMqtt_ProcessSend ) || - ( jobRoutine == _IotMqtt_ProcessCompletedOperation ) || - ( jobRoutine == _IotMqtt_ProcessIncomingPublish ) ); - - /* Creating a new job should never fail when parameters are valid. */ - taskPoolStatus = IotTaskPool_CreateJob( jobRoutine, - pOperation, - &( pOperation->jobStorage ), - &( pOperation->job ) ); - IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); - - /* Schedule the new job with a delay. */ - taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - pOperation->job, - delay ); - - if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) - { - /* Scheduling a newly-created job should never be invalid or illegal. */ - IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_BAD_PARAMETER ); - IotMqtt_Assert( taskPoolStatus != IOT_TASKPOOL_ILLEGAL_OPERATION ); - - /* Coverity finds a MISRA 13.2 violation in this log statement as the order - * of evaluation for IotMqtt_OperationType and IotTaskPool_strerror is not - * defined. This is not an issue as these functions do not change data and - * only convert codes into constant strings. */ - /* coverity[misra_c_2012_rule_13_2_violation] */ - IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule operation job, error %s.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation, - IotTaskPool_strerror( taskPoolStatus ) ); - - status = IOT_MQTT_SCHEDULING_ERROR; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, - IotMqttOperationType_t type, - const uint16_t * pPacketIdentifier ) -{ - bool waitable = false; - IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - _mqttOperation_t * pResult = NULL; - IotLink_t * pResultLink = NULL; - _operationMatchParam_t operationMatchParams; - - ( void ) memset( &operationMatchParams, 0, sizeof( _operationMatchParam_t ) ); - - /* Set the members of the search parameter. */ - operationMatchParams.type = type; - operationMatchParams.pPacketIdentifier = pPacketIdentifier; - - IotLogDebug( "(MQTT connection %p) Searching for operation %s " - "with packet identifier %hu.", - pMqttConnection, - IotMqtt_OperationType( type ), - ( pPacketIdentifier == NULL ) ? 0U : *pPacketIdentifier ); - - /* Find and remove the first matching element in the list. */ - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pResultLink = IotListDouble_FindFirstMatch( &( pMqttConnection->pendingResponse ), - NULL, - _mqttOperation_match, - &operationMatchParams ); - - /* Check if a match was found. */ - if( pResultLink != NULL ) - { - /* Get operation pointer and check if it is waitable. */ - - /* Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - pResult = IotLink_Container( _mqttOperation_t, pResultLink, link ); - waitable = ( pResult->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; - - /* Check if the matched operation is a PUBLISH with retry. If it is, cancel - * the retry job. */ - if( pResult->u.operation.periodic.retry.limit > 0U ) - { - taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pResult->job, - NULL ); - - /* If the retry job could not be canceled, then it is currently - * executing. Ignore the operation. */ - if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) - { - pResult = NULL; - } - } - else - { - /* An operation with no retry in the pending responses list should - * always have a job reference of 1. */ - IotMqtt_Assert( pResult->u.operation.jobReference == 1 ); - - /* Increment job references of a waitable operation to prevent Wait from - * destroying this operation if it times out. */ - if( waitable == true ) - { - ( pResult->u.operation.jobReference )++; - - /* In some implementations IotLogDebug() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %d to %d.", - pMqttConnection, - IotMqtt_OperationType( type ), - pResult, - ( int ) ( pResult->u.operation.jobReference - 1 ), - ( int ) ( pResult->u.operation.jobReference ) ); - } - } - } - - if( pResult != NULL ) - { - IotLogDebug( "(MQTT connection %p) Found operation %s.", - pMqttConnection, - IotMqtt_OperationType( type ) ); - - /* Remove the matched operation from the list. */ - IotListDouble_Remove( &( pResult->link ) ); - } - else - { - IotLogDebug( "(MQTT connection %p) Operation %s not found.", - pMqttConnection, - IotMqtt_OperationType( type ) ); - } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - - return pResult; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_Notify( _mqttOperation_t * pOperation ) -{ - IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; - - /* Check if operation is waitable. */ - bool waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; - - /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected - * subscriptions are removed by the deserializer, so not removed here. */ - if( pOperation->u.operation.type == IOT_MQTT_SUBSCRIBE ) - { - switch( pOperation->u.operation.status ) - { - case IOT_MQTT_SUCCESS: - break; - - case IOT_MQTT_SERVER_REFUSED: - break; - - default: - _IotMqtt_RemoveSubscriptionByPacket( pOperation->pMqttConnection, - pOperation->u.operation.packetIdentifier, - -1 ); - break; - } - } - - /* Schedule callback invocation for non-waitable operation. */ - if( waitable == false ) - { - status = _scheduleCallback( pOperation ); - } - - /* Operations that weren't scheduled may be destroyed. */ - if( status == IOT_MQTT_SCHEDULING_ERROR ) - { - /* Decrement reference count of operations not scheduled. */ - if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) - { - _IotMqtt_DestroyOperation( pOperation ); - } - else - { - /* Only waitable operations will have a reference count greater than 1. - * Non-waitable operations will not reach this block. */ - IotMqtt_Assert( waitable == true ); - - /* Post to a waitable operation's semaphore. */ - IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " - "notified of completion.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - - IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); - } - } - else - { - IotMqtt_Assert( status == IOT_MQTT_SUCCESS ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c deleted file mode 100644 index 016111e7fd..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ /dev/null @@ -1,1039 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_serialize.c - * @brief Implements functions that generate and decode MQTT network packets. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* MQTT internal includes. */ -#include "private/iot_mqtt_internal.h" -#include "private/iot_mqtt_helper.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* Atomic operations. */ -#include "iot_atomic.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Decode the status bytes of a SUBACK packet. - * - * @param[in] statusCount Number of status bytes in the SUBACK. - * @param[in] pStatusStart The first status byte in the SUBACK. - * @param[in] pSuback The SUBACK packet received from the network. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_SERVER_REFUSED, or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _decodeSubackStatus( size_t statusCount, - const uint8_t * pStatusStart, - const _mqttPacket_t * pSuback ); - -/** - * @brief Check the remaining length against some value for QoS 0 or QoS 1/2. - * - * The remaining length for a QoS 1/2 will always be two greater than for a QoS 0. - * - * @param[in] pPublish Pointer to an MQTT packet struct representing a PUBLISH. - * @param[in] qos The QoS of the PUBLISH. - * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_RESPONSE. - */ -static IotMqttError_t _checkRemainingLength( const _mqttPacket_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ); - -/*-----------------------------------------------------------*/ - -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - -/** - * @brief If logging is enabled, define a log configuration that only prints the log - * string. This is used when printing out details of deserialized MQTT packets. - */ - static const IotLogConfig_t _logHideAll = - { - .hideLibraryName = ( bool ) ( true ), - .hideLogLevel = ( bool ) ( true ), - .hideTimestring = ( bool ) ( true ) - }; -#endif - - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _decodeSubackStatus( size_t statusCount, - const uint8_t * pStatusStart, - const _mqttPacket_t * pSuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t subscriptionStatus = 0; - size_t i = 0; - - /* Iterate through each status byte in the SUBACK packet. */ - for( i = 0; i < statusCount; i++ ) - { - /* Read a single status byte in SUBACK. */ - subscriptionStatus = *( pStatusStart + i ); - - /* MQTT 3.1.1 defines the following values as status codes. */ - switch( subscriptionStatus ) - { - case 0x00: - case 0x01: - case 0x02: - - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); - break; - - case 0x80: - - /* In some implementations IotLog() maps to C standard printing API - * that need specific primitive types for format specifiers. Also - * inttypes.h may not be available on some C99 compilers, despite - * stdint.h being available. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu refused.", ( unsigned long ) i ); - - /* Remove a rejected subscription from the subscription manager. */ - _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, - pSuback->packetIdentifier, - ( int32_t ) i ); - - status = IOT_MQTT_SERVER_REFUSED; - - break; - - default: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); - - status = IOT_MQTT_BAD_RESPONSE; - - break; - } - - /* Stop parsing the subscription statuses if a bad response was received. */ - if( status == IOT_MQTT_BAD_RESPONSE ) - { - break; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotMqttError_t _checkRemainingLength( const _mqttPacket_t * pPublish, - IotMqttQos_t qos, - size_t qos0Minimum ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Sanity checks for "Remaining length". */ - if( qos == IOT_MQTT_QOS_0 ) - { - /* Check that the "Remaining length" is greater than the minimum. */ - if( pPublish->remainingLength < qos0Minimum ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS 0 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum ); - - status = IOT_MQTT_BAD_RESPONSE; - } - } - else - { - /* Check that the "Remaining length" is greater than the minimum. For - * QoS 1 or 2, this will be two bytes greater than for QoS due to the - * packet identifier. */ - if( pPublish->remainingLength < ( qos0Minimum + 2U ) ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum + 2U ); - - status = IOT_MQTT_BAD_RESPONSE; - } - } - - return status; -} - - -/*-----------------------------------------------------------*/ - -uint8_t _IotMqtt_GetPacketType( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ) -{ - uint8_t packetType = 0xff; - - /* The MQTT packet type is in the first byte of the packet. */ - ( void ) _IotMqtt_GetNextByte( pNetworkConnection, - pNetworkInterface, - &packetType ); - - return packetType; -} - -/*-----------------------------------------------------------*/ - -size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ) -{ - uint8_t encodedByte = 0; - size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - if( multiplier > 2097152U ) /* 128 ^ 3 */ - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - if( _IotMqtt_GetNextByte( pNetworkConnection, - pNetworkInterface, - &encodedByte ) == true ) - { - remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; - multiplier *= 128U; - bytesDecoded++; - } - else - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - } - - if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) - { - break; - } - } while( ( encodedByte & 0x80U ) != 0U ); - - /* Check that the decoded remaining length conforms to the MQTT specification. */ - if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) - { - expectedSize = _IotMqtt_RemainingLengthEncodedSize( remainingLength ); - - if( bytesDecoded != expectedSize ) - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4U ); - } - } - - return remainingLength; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - uint8_t ** pConnectPacket, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t remainingLength = 0, connectPacketSize = 0; - uint8_t * pBuffer = NULL; - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _IotMqtt_ConnectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) - { - IotLogError( "Connect packet length exceeds %lu, which is the maximum" - " size allowed by MQTT 3.1.1.", - MQTT_PACKET_CONNECT_MAX_SIZE ); - - status = IOT_MQTT_BAD_PARAMETER; - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Total size of the connect packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( connectPacketSize > remainingLength ); - - /* Allocate memory to hold the CONNECT packet. */ - pBuffer = IotMqtt_MallocMessage( connectPacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for CONNECT packet." ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* Set the output parameters. The remainder of this function always succeeds. */ - *pConnectPacket = pBuffer; - *pPacketSize = connectPacketSize; - - _IotMqtt_SerializeConnectCommon( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - const uint8_t * pRemainingData = pConnack->pRemainingData; - - /* If logging is enabled, declare the CONNACK response code strings. The - * fourth byte of CONNACK indexes into this array for the corresponding response. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - static const char * const pConnackResponses[ 6 ] = - { - "Connection accepted.", /* 0 */ - "Connection refused: unacceptable protocol version.", /* 1 */ - "Connection refused: identifier rejected.", /* 2 */ - "Connection refused: server unavailable", /* 3 */ - "Connection refused: bad user name or password.", /* 4 */ - "Connection refused: not authorized." /* 5 */ - }; - #endif - - /* Check that the control packet type is 0x20. */ - if( pConnack->type != MQTT_PACKET_TYPE_CONNACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pConnack->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - - /* According to MQTT 3.1.1, the second byte of CONNACK must specify a - * "Remaining length" of 2. */ - else if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "CONNACK does not have remaining length of %d.", - MQTT_PACKET_CONNACK_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - - /* Check the reserved bits in CONNACK. The high 7 bits of the second byte - * in CONNACK must be 0. */ - else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Reserved bits in CONNACK incorrect." ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Determine if the "Session Present" bit is set. This is the lowest bit of - * the second byte in CONNACK. */ - if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit set." ); - - /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the - * "Session Present" bit is set. */ - if( pRemainingData[ 1 ] != 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - else - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit not set." ); - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ - if( pRemainingData[ 1 ] > 5U ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Print the appropriate message for the CONNACK response code if logs are - * enabled. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "%s", - pConnackResponses[ pRemainingData[ 1 ] ] ); - #endif - - /* A nonzero CONNACK response code means the connection was refused. */ - if( pRemainingData[ 1 ] > 0U ) - { - status = IOT_MQTT_SERVER_REFUSED; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, - uint8_t ** pPublishPacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t remainingLength = 0, publishPacketSize = 0; - uint8_t * pBuffer = NULL; - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _IotMqtt_PublishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) - { - IotLogError( "Publish packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_PARAMETER; - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Total size of the publish packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( publishPacketSize > remainingLength ); - - /* Allocate memory to hold the PUBLISH packet. */ - pBuffer = IotMqtt_MallocMessage( publishPacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for PUBLISH packet." ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* Set the output parameters. The remainder of this function always succeeds. */ - *pPublishPacket = pBuffer; - *pPacketSize = publishPacketSize; - - /* Serialize publish into buffer pointed to by pBuffer */ - _IotMqtt_SerializePublishCommon( pPublishInfo, - remainingLength, - pPacketIdentifier, - pPacketIdentifierHigh, - pBuffer, - publishPacketSize ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, - uint8_t * pPacketIdentifierHigh, - uint16_t * pNewPacketIdentifier ) -{ - uint16_t newPacketIdentifier = 0; - - /* For an AWS IoT MQTT server, change the packet identifier. */ - if( pPacketIdentifierHigh != NULL ) - { - /* Output parameter for new packet identifier must be provided. */ - IotMqtt_Assert( pNewPacketIdentifier != NULL ); - - /* Generate a new packet identifier. */ - newPacketIdentifier = _IotMqtt_NextPacketIdentifier(); - - IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", - UINT16_DECODE( pPacketIdentifierHigh ), - newPacketIdentifier ); - - /* Replace the packet identifier. */ - *pPacketIdentifierHigh = UINT16_HIGH_BYTE( newPacketIdentifier ); - *( pPacketIdentifierHigh + 1 ) = UINT16_LOW_BYTE( newPacketIdentifier ); - *pNewPacketIdentifier = newPacketIdentifier; - } - else - { - /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ - UINT8_SET_BIT( *pPublishPacket, MQTT_PUBLISH_FLAG_DUP ); - - IotLogDebug( "PUBLISH DUP flag set." ); - } -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); - uint8_t publishFlags = 0; - const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; - - /* The flags are the lower 4 bits of the first byte in PUBLISH. */ - publishFlags = pPublish->type; - - status = _IotMqtt_ProcessPublishFlags( publishFlags, pOutput ); - - if( status == IOT_MQTT_SUCCESS ) - { - /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining - * length of at least 3 to accommodate topic name length (2 bytes) and topic - * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of - * at least 5 for the packet identifier in addition to the topic name length and - * topic name. */ - status = _checkRemainingLength( pPublish, pOutput->qos, MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Extract the topic name starting from the first byte of the variable header. - * The topic name string starts at byte 3 in the variable header. */ - pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); - - /* Sanity checks for topic name length and "Remaining length". The remaining - * length must be at least as large as the variable length header. */ - status = _checkRemainingLength( pPublish, - pOutput->qos, - pOutput->topicNameLength + sizeof( uint16_t ) ); - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Parse the topic. */ - pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic name length %hu: %.*s", - pOutput->topicNameLength, - pOutput->topicNameLength, - pOutput->pTopicName ); - - /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet - * identifier starts immediately after the topic name. */ - pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); - - if( pOutput->qos > IOT_MQTT_QOS_0 ) - { - pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pPublish->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPublish->packetIdentifier == 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain - * a packet identifier, but QoS 0 PUBLISH packets do not. */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) - { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh; - } - else - { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2U * sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); - } - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Payload length %hu.", pOutput->payloadLength ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Allocate memory for PUBACK. */ - uint8_t * pBuffer = IotMqtt_MallocMessage( MQTT_PACKET_PUBACK_SIZE ); - - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for PUBACK packet" ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* Set the output parameters. The remainder of this function always succeeds. */ - *pPubackPacket = pBuffer; - *pPacketSize = MQTT_PACKET_PUBACK_SIZE; - - /* Set the 4 bytes in PUBACK. */ - pBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBACK; - pBuffer[ 1 ] = MQTT_PACKET_PUBACK_REMAINING_LENGTH; - pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetIdentifier ); - pBuffer[ 3 ] = UINT16_LOW_BYTE( packetIdentifier ); - - /* Print out the serialized PUBACK packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, MQTT_PACKET_PUBACK_SIZE ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check the "Remaining length" of the received PUBACK. */ - if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "PUBACK does not have remaining length of %d.", - MQTT_PACKET_PUBACK_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ - pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pPuback->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPuback->packetIdentifier == 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Check that the control packet type is 0x40 (this must be done after the - * packet identifier is parsed). */ - if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pPuback->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t subscribePacketSize = 0, remainingLength = 0; - uint8_t * pBuffer = NULL; - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _IotMqtt_SubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &subscribePacketSize ) == false ) - { - IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Total size of the subscribe packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( subscribePacketSize > remainingLength ); - - /* Allocate memory to hold the SUBSCRIBE packet. */ - pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* Set the output parameters. The remainder of this function always succeeds. */ - *pSubscribePacket = pBuffer; - *pPacketSize = subscribePacketSize; - - /* Serialize subscribe into buffer pointed to by pBuffer */ - _IotMqtt_SerializeSubscribeCommon( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - subscribePacketSize ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t remainingLength = pSuback->remainingLength; - const uint8_t * pVariableHeader = pSuback->pRemainingData; - - /* A SUBACK must have a remaining length of at least 3 to accommodate the - * packet identifier and at least 1 return code. */ - if( remainingLength < 3U ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "SUBACK cannot have a remaining length less than 3." ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ - pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pSuback->packetIdentifier ); - - /* Check that the control packet type is 0x90 (this must be done after the - * packet identifier is parsed). */ - if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pSuback->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - status = _decodeSubackStatus( remainingLength - sizeof( uint16_t ), - pVariableHeader + sizeof( uint16_t ), - pSuback ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pUnsubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t unsubscribePacketSize = 0, remainingLength = 0; - uint8_t * pBuffer = NULL; - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _IotMqtt_SubscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &unsubscribePacketSize ) == false ) - { - IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_PARAMETER; - } - else - { - /* Total size of the unsubscribe packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( unsubscribePacketSize > remainingLength ); - - /* Allocate memory to hold the UNSUBSCRIBE packet. */ - pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - - status = IOT_MQTT_NO_MEMORY; - } - else - { - /* Set the output parameters. The remainder of this function always succeeds. */ - *pUnsubscribePacket = pBuffer; - *pPacketSize = unsubscribePacketSize; - - /* Serialize unsubscribe into buffer pointed to by pBuffer */ - _IotMqtt_SerializeUnsubscribeCommon( pSubscriptionList, - subscriptionCount, - remainingLength, - pPacketIdentifier, - pBuffer, - unsubscribePacketSize ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ - if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "UNSUBACK does not have remaining length of %d.", - MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ - pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); - - /* Packet identifier cannot be 0. */ - if( pUnsuback->packetIdentifier == 0U ) - { - status = IOT_MQTT_BAD_RESPONSE; - } - } - - if( status == IOT_MQTT_SUCCESS ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pUnsuback->packetIdentifier ); - - /* Check that the control packet type is 0xb0 (this must be done after the - * packet identifier is parsed). */ - if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pUnsuback->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ) -{ - /* PINGREQ packets are always the same. */ - - /* It is not necessary to make this array const. Since there are other - * types of MQTT packets that are not constant, this array would be - * cast to remove the const qualifier anyway. */ - /* coverity[misra_c_2012_rule_8_13_violation] */ - static uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = - { - MQTT_PACKET_TYPE_PINGREQ, - 0x00 - }; - - /* Set the output parameters. */ - *pPingreqPacket = ( uint8_t * ) pPingreq; - *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; - - /* Print out the PINGREQ packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, MQTT_PACKET_PINGREQ_SIZE ); - - return IOT_MQTT_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Check that the control packet type is 0xd0. */ - if( pPingresp->type != MQTT_PACKET_TYPE_PINGRESP ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pPingresp->type ); - - status = IOT_MQTT_BAD_RESPONSE; - } - /* Check the "Remaining length" (second byte) of the received PINGRESP. */ - else if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "PINGRESP does not have remaining length of %d.", - MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); - - status = IOT_MQTT_BAD_RESPONSE; - } - else - { - /* Empty else MISRA 15.7 */ - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, - size_t * pPacketSize ) -{ - /* DISCONNECT packets are always the same. */ - - /* It is not necessary to make this array const. Since there are other - * types of MQTT packets that are not constant, this array would be - * cast to remove the const qualifier anyway. */ - /* coverity[misra_c_2012_rule_8_13_violation] */ - static uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = - { - MQTT_PACKET_TYPE_DISCONNECT, - 0x00 - }; - - /* Set the output parameters. */ - *pDisconnectPacket = ( uint8_t * ) pDisconnect; - *pPacketSize = MQTT_PACKET_DISCONNECT_SIZE; - - /* Print out the DISCONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, MQTT_PACKET_DISCONNECT_SIZE ); - - return IOT_MQTT_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_FreePacket( uint8_t * pPacket ) -{ - uint8_t packetType = *pPacket; - - /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static - * memory. */ - if( packetType != MQTT_PACKET_TYPE_DISCONNECT ) - { - if( packetType != MQTT_PACKET_TYPE_PINGREQ ) - { - IotMqtt_FreeMessage( pPacket ); - } - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_static_memory.c b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c deleted file mode 100644 index 400270a63b..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_static_memory.c +++ /dev/null @@ -1,203 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_static_memory.c - * @brief Implementation of MQTT static memory functions. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include - -/* Static memory include. */ -#include "iot_static_memory.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef IOT_MQTT_CONNECTIONS - #define IOT_MQTT_CONNECTIONS ( 1 ) -#endif -#ifndef IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS - #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) -#endif -#ifndef IOT_MQTT_SUBSCRIPTIONS - #define IOT_MQTT_SUBSCRIPTIONS ( 8 ) -#endif -/** @endcond */ - -/* Validate static memory configuration settings. */ -#if IOT_MQTT_CONNECTIONS <= 0 - #error "IOT_MQTT_CONNECTIONS cannot be 0 or negative." -#endif -#if IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS <= 0 - #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." -#endif -#if IOT_MQTT_SUBSCRIPTIONS <= 0 - #error "IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." -#endif - -/** - * @brief The size of a static memory MQTT subscription. - * - * Since the pTopic member of #_mqttSubscription_t is variable-length, the constant - * #AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of - * #_mqttSubscription_t.pTopicFilter. - */ -#define MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static uint32_t _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ]; /**< @brief MQTT connection in-use flags. */ -static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ]; /**< @brief MQTT connections. */ - -static uint32_t _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ]; /**< @brief MQTT operation in-use flags. */ -static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ]; /**< @brief MQTT operations. */ - -static uint32_t _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ]; /**< @brief MQTT subscription in-use flags. */ -static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ]; /**< @brief MQTT subscriptions. */ - -/*-----------------------------------------------------------*/ - -void * IotMqtt_MallocConnection( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewConnection = NULL; - - /* Check size argument. */ - if( size == sizeof( _mqttConnection_t ) ) - { - /* Find a free MQTT connection. */ - freeIndex = IotStaticMemory_FindFree( _pInUseMqttConnections, - IOT_MQTT_CONNECTIONS ); - - if( freeIndex != -1 ) - { - pNewConnection = &( _pMqttConnections[ freeIndex ] ); - } - } - - return pNewConnection; -} - -/*-----------------------------------------------------------*/ - -void IotMqtt_FreeConnection( void * ptr ) -{ - /* Return the in-use MQTT connection. */ - IotStaticMemory_ReturnInUse( ptr, - _pMqttConnections, - _pInUseMqttConnections, - IOT_MQTT_CONNECTIONS, - sizeof( _mqttConnection_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotMqtt_MallocOperation( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewOperation = NULL; - - /* Check size argument. */ - if( size == sizeof( _mqttOperation_t ) ) - { - /* Find a free MQTT operation. */ - freeIndex = IotStaticMemory_FindFree( _pInUseMqttOperations, - IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ); - - if( freeIndex != -1 ) - { - pNewOperation = &( _pMqttOperations[ freeIndex ] ); - } - } - - return pNewOperation; -} - -/*-----------------------------------------------------------*/ - -void IotMqtt_FreeOperation( void * ptr ) -{ - /* Return the in-use MQTT operation. */ - IotStaticMemory_ReturnInUse( ptr, - _pMqttOperations, - _pInUseMqttOperations, - IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _mqttOperation_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotMqtt_MallocSubscription( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewSubscription = NULL; - - if( size <= MQTT_SUBSCRIPTION_SIZE ) - { - /* Get the index of a free MQTT subscription. */ - freeIndex = IotStaticMemory_FindFree( _pInUseMqttSubscriptions, - IOT_MQTT_SUBSCRIPTIONS ); - - if( freeIndex != -1 ) - { - pNewSubscription = &( _pMqttSubscriptions[ freeIndex ][ 0 ] ); - } - } - - return pNewSubscription; -} - -/*-----------------------------------------------------------*/ - -void IotMqtt_FreeSubscription( void * ptr ) -{ - /* Return the in-use MQTT subscription. */ - IotStaticMemory_ReturnInUse( ptr, - _pMqttSubscriptions, - _pInUseMqttSubscriptions, - IOT_MQTT_SUBSCRIPTIONS, - MQTT_SUBSCRIPTION_SIZE ); -} - -/*-----------------------------------------------------------*/ - -#endif diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c deleted file mode 100644 index 678b2db20a..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ /dev/null @@ -1,721 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_subscription.c - * @brief Implements functions that manage subscriptions for an MQTT connection. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief First parameter to #_topicMatch. - */ -typedef struct _topicMatchParams -{ - const char * pTopicName; /**< @brief The topic name to parse. */ - uint16_t topicNameLength; /**< @brief Length of #_topicMatchParams_t.pTopicName. */ - bool exactMatchOnly; /**< @brief Whether to allow wildcards or require exact matches. */ -} _topicMatchParams_t; - -/** - * @brief First parameter to #_packetMatch. - */ -typedef struct _packetMatchParams -{ - uint16_t packetIdentifier; /**< Packet identifier to match. */ - int32_t order; /**< Order to match. Set to #MQTT_REMOVE_ALL_SUBSCRIPTIONS to ignore. */ -} _packetMatchParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Handle special corner cases regarding wildcards at the end of topic - * filters, as documented by the MQTT protocol spec. - * - * @param[in] pTopicFilter The topic filter containing the wildcard. - * @param[in] nameIndex Index of the topic name being examined. - * @param[in] filterIndex Index of the topic filter being examined. - * @param[in] topicNameLength Length of the topic name being examined. - * @param[in] topicFilterLength Length of the topic filter being examined. - * @param[out] pMatch Whether the topic filter and topic name match. - * - * @return `true` if the caller of this function should exit; `false` if the caller - * should continue parsing the topics. - */ -static bool _matchEndWildcards( const char * pTopicFilter, - uint16_t topicNameLength, - uint16_t topicFilterLength, - uint16_t nameIndex, - uint16_t filterIndex, - bool * pMatch ); - -/** - * @brief Attempt to match characters in a topic filter by wildcards. - * - * @param[in] pTopicFilter The topic filter containing the wildcard. - * @param[in] pTopicName The topic name to check. - * @param[in] topicNameLength Length of the topic name. - * @param[in] filterIndex Index of the wildcard in the topic filter. - * @param[in,out] pNameIndex Index of character in topic name. This variable is - * advanced for `+` wildcards. - * @param[out] pMatch Whether the topic filter and topic name match. - * - * @return `true` if the caller of this function should exit; `false` if the caller - * should continue parsing the topics. - */ -static bool _matchWildcards( const char * pTopicFilter, - const char * pTopicName, - uint16_t topicNameLength, - uint16_t filterIndex, - uint16_t * pNameIndex, - bool * pMatch ); - -/** - * @brief Match a topic name and topic filter while allowing the use of wildcards. - * - * @param[in] pTopicName The topic name to check. - * @param[in] topicNameLength Length of `pTopicName`. - * @param[in] pTopicFilter The topic filter to check. - * @param[in] topicFilterLength Length of `pTopicFilter`. - * - * @return `true` if the topic name and topic filter match; `false` otherwise. - */ -static bool _topicFilterMatch( const char * pTopicName, - uint16_t topicNameLength, - const char * pTopicFilter, - uint16_t topicFilterLength ); - -/** - * @brief Matches a topic name (from a publish) with a topic filter (from a - * subscription). - * - * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. - * @param[in] pMatch Pointer to a #_topicMatchParams_t. - * - * @return `true` if the arguments match the subscription topic filter; `false` - * otherwise. - */ -static bool _topicMatch( const IotLink_t * const pSubscriptionLink, - void * pMatch ); - -/** - * @brief Matches a packet identifier and order. - * - * @param[in] pSubscriptionLink Pointer to the link member of an #_mqttSubscription_t. - * @param[in] pMatch Pointer to a #_packetMatchParams_t. - * - * @return `true` if the arguments match the subscription's packet info; `false` - * otherwise. - */ -static bool _packetMatch( const IotLink_t * const pSubscriptionLink, - void * pMatch ); - -/*-----------------------------------------------------------*/ - -static bool _matchEndWildcards( const char * pTopicFilter, - uint16_t topicNameLength, - uint16_t topicFilterLength, - uint16_t nameIndex, - uint16_t filterIndex, - bool * pMatch ) -{ - bool status = false, endChar = false; - - /* Determine if the last character is reached for both topic name and topic - * filter for the '#' wildcard. */ - endChar = ( nameIndex == ( topicNameLength - 1U ) ) && ( filterIndex == ( topicFilterLength - 3U ) ); - - if( endChar == true ) - { - /* Determine if the topic filter ends with the '#' wildcard. */ - status = ( pTopicFilter[ filterIndex + 2U ] == '#' ); - } - - if( status == false ) - { - /* Determine if the last character is reached for both topic name and topic - * filter for the '+' wildcard. */ - endChar = ( nameIndex == ( topicNameLength - 1U ) ) && ( filterIndex == ( topicFilterLength - 2U ) ); - - if( endChar == true ) - { - /* Filter "sport/+" also matches the "sport/" but not "sport". */ - status = ( pTopicFilter[ filterIndex + 1U ] == '+' ); - } - } - - *pMatch = status; - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _matchWildcards( const char * pTopicFilter, - const char * pTopicName, - uint16_t topicNameLength, - uint16_t filterIndex, - uint16_t * pNameIndex, - bool * pMatch ) -{ - bool status = false; - - /* Check for wildcards. */ - if( pTopicFilter[ filterIndex ] == '+' ) - { - /* Move topic name index to the end of the current level. - * This is identified by '/'. */ - while( ( *pNameIndex < topicNameLength ) && ( pTopicName[ *pNameIndex ] != '/' ) ) - { - ( *pNameIndex )++; - } - - ( *pNameIndex )--; - } - else if( pTopicFilter[ filterIndex ] == '#' ) - { - /* Subsequent characters don't need to be checked for the - * multi-level wildcard. */ - *pMatch = true; - status = true; - } - else - { - /* Any character mismatch other than '+' or '#' means the topic - * name does not match the topic filter. */ - *pMatch = false; - status = true; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _topicFilterMatch( const char * pTopicName, - uint16_t topicNameLength, - const char * pTopicFilter, - uint16_t topicFilterLength ) -{ - bool status = false, matchFound = false; - uint16_t nameIndex = 0, filterIndex = 0; - - while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) - { - /* Check if the character in the topic name matches the corresponding - * character in the topic filter string. */ - if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) - { - /* Handle special corner cases regarding wildcards at the end of - * topic filters, as documented by the MQTT protocol spec. */ - matchFound = _matchEndWildcards( pTopicFilter, - topicNameLength, - topicFilterLength, - nameIndex, - filterIndex, - &status ); - } - else - { - /* Check for matching wildcards. */ - matchFound = _matchWildcards( pTopicFilter, - pTopicName, - topicNameLength, - filterIndex, - &nameIndex, - &status ); - } - - if( matchFound == true ) - { - break; - } - - /* Increment indexes. */ - nameIndex++; - filterIndex++; - } - - if( status == false ) - { - /* If the end of both strings has been reached, they match. */ - status = ( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _topicMatch( const IotLink_t * const pSubscriptionLink, - void * pMatch ) -{ - bool status = false; - - /* This function is called from a container function; the caller - * will never pass NULL. */ - IotMqtt_Assert( pSubscriptionLink != NULL ); - - /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the - * starting address of the struct and does not modify the link it points to. - * Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_11_8_violation] */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - const _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - pSubscriptionLink, - link ); - const _topicMatchParams_t * pParam = ( _topicMatchParams_t * ) pMatch; - - /* Extract the relevant strings and lengths from parameters. */ - const char * pTopicName = pParam->pTopicName; - const char * pTopicFilter = pSubscription->pTopicFilter; - const uint16_t topicNameLength = pParam->topicNameLength; - const uint16_t topicFilterLength = pSubscription->topicFilterLength; - - /* Check for an exact match. */ - if( topicNameLength == topicFilterLength ) - { - status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); - } - - /* If an exact match is required, return the result of the comparison above. - * Otherwise, attempt to match with MQTT wildcards in topic filters. */ - if( pParam->exactMatchOnly == false ) - { - status = _topicFilterMatch( pTopicName, topicNameLength, pTopicFilter, topicFilterLength ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _packetMatch( const IotLink_t * const pSubscriptionLink, - void * pMatch ) -{ - bool match = false; - - /* Because this function is called from a container function, the given link - * must never be NULL. */ - IotMqtt_Assert( pSubscriptionLink != NULL ); - - /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the - * starting address of the struct and does not modify the link it points to. - * Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_11_8_violation] */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - pSubscriptionLink, - link ); - const _packetMatchParams_t * pParam = ( _packetMatchParams_t * ) pMatch; - - /* Compare packet identifiers. */ - if( pParam->packetIdentifier == pSubscription->packetInfo.identifier ) - { - /* Compare orders if order is not MQTT_REMOVE_ALL_SUBSCRIPTIONS. */ - if( pParam->order == MQTT_REMOVE_ALL_SUBSCRIPTIONS ) - { - match = true; - } - else - { - match = ( ( size_t ) pParam->order ) == pSubscription->packetInfo.order; - } - } - - /* If this subscription should be removed, check the reference count. */ - if( match == true ) - { - /* Reference count must not be negative. */ - IotMqtt_Assert( pSubscription->references >= 0 ); - - /* If the reference count is positive, this subscription cannot be - * removed yet because there are subscription callbacks using it. */ - if( pSubscription->references > 0 ) - { - match = false; - - /* Set the unsubscribed flag. The last active subscription callback - * will remove and clean up this subscription. */ - pSubscription->unsubscribed = true; - } - } - - return match; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, - uint16_t subscribePacketIdentifier, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - size_t i = 0; - _mqttSubscription_t * pNewSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - _topicMatchParams_t topicMatchParams = { .exactMatchOnly = true }; - - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - - for( i = 0; i < subscriptionCount; i++ ) - { - /* Check if this topic filter is already registered. */ - topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; - topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; - pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ); - - if( pSubscriptionLink != NULL ) - { - /* Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - pNewSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - - /* The lengths of exactly matching topic filters must match. */ - IotMqtt_Assert( pNewSubscription->topicFilterLength == pSubscriptionList[ i ].topicFilterLength ); - - /* Replace the callback and packet info with the new parameters. */ - pNewSubscription->callback = pSubscriptionList[ i ].callback; - pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; - pNewSubscription->packetInfo.order = i; - } - else - { - /* Allocate memory for a new subscription. */ - pNewSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + - pSubscriptionList[ i ].topicFilterLength ); - - if( pNewSubscription == NULL ) - { - status = IOT_MQTT_NO_MEMORY; - break; - } - - /* Clear the new subscription. */ - ( void ) memset( pNewSubscription, - 0x00, - sizeof( _mqttSubscription_t ) + pSubscriptionList[ i ].topicFilterLength ); - - /* Set the members of the new subscription and add it to the list. */ - pNewSubscription->packetInfo.identifier = subscribePacketIdentifier; - pNewSubscription->packetInfo.order = i; - pNewSubscription->callback = pSubscriptionList[ i ].callback; - pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; - ( void ) memcpy( pNewSubscription->pTopicFilter, - pSubscriptionList[ i ].pTopicFilter, - ( size_t ) ( pSubscriptionList[ i ].topicFilterLength ) ); - - IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), - &( pNewSubscription->link ) ); - } - } - - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - /* If memory allocation failed, remove all previously added subscriptions. */ - if( status != IOT_MQTT_SUCCESS ) - { - _IotMqtt_RemoveSubscriptionByTopicFilter( pMqttConnection, - pSubscriptionList, - i ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, - IotMqttCallbackParam_t * pCallbackParam ) -{ - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pCurrentLink = NULL, * pNextLink = NULL; - void * pCallbackContext = NULL; - - void ( * callbackFunction )( void * pContext, - IotMqttCallbackParam_t * pParam ) = NULL; - _topicMatchParams_t topicMatchParams = { 0 }; - - /* Set the members of the search parameter. */ - topicMatchParams.pTopicName = pCallbackParam->u.message.info.pTopicName; - topicMatchParams.topicNameLength = pCallbackParam->u.message.info.topicNameLength; - topicMatchParams.exactMatchOnly = false; - - /* Prevent any other thread from modifying the subscription list while this - * function is searching. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - - /* Search the subscription list for all matching subscriptions starting at - * the list head. */ - while( true ) - { - pCurrentLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - pCurrentLink, - _topicMatch, - &topicMatchParams ); - - /* No subscription found. Exit loop. */ - if( pCurrentLink == NULL ) - { - break; - } - - /* Subscription found. Calculate pointer to subscription object. */ - - /* Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - pSubscription = IotLink_Container( _mqttSubscription_t, pCurrentLink, link ); - - /* Subscription validation should not have allowed a NULL callback function. */ - IotMqtt_Assert( pSubscription->callback.function != NULL ); - - /* Increment the subscription's reference count. */ - ( pSubscription->references )++; - - /* Copy the necessary members of the subscription before releasing the - * subscription list mutex. */ - pCallbackContext = pSubscription->callback.pCallbackContext; - callbackFunction = pSubscription->callback.function; - - /* Unlock the subscription list mutex. */ - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - /* Set the members of the callback parameter. */ - pCallbackParam->mqttConnection = pMqttConnection; - pCallbackParam->u.message.pTopicFilter = pSubscription->pTopicFilter; - pCallbackParam->u.message.topicFilterLength = pSubscription->topicFilterLength; - - /* Invoke the subscription callback. */ - callbackFunction( pCallbackContext, pCallbackParam ); - - /* Lock the subscription list mutex to decrement the reference count. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - - /* Decrement the reference count. It must still be positive. */ - ( pSubscription->references )--; - IotMqtt_Assert( pSubscription->references >= 0 ); - - /* Save the pointer to the next link in case this subscription is freed. */ - pNextLink = pCurrentLink->pNext; - - /* Remove this subscription if it has no references and the unsubscribed - * flag is set. */ - if( pSubscription->unsubscribed == true ) - { - /* An unsubscribed subscription should have been removed from the list. */ - IotMqtt_Assert( IotLink_IsLinked( &( pSubscription->link ) ) == false ); - - /* Free subscriptions with no references. */ - if( pSubscription->references == 0 ) - { - IotMqtt_FreeSubscription( pSubscription ); - } - } - - /* Move current link pointer. */ - pCurrentLink = pNextLink; - } - - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - - _IotMqtt_DecrementConnectionReferences( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier, - int32_t order ) -{ - _packetMatchParams_t packetMatchParams = { 0 }; - - /* Set the members of the search parameter. */ - packetMatchParams.packetIdentifier = packetIdentifier; - packetMatchParams.order = order; - - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), - _packetMatch, - ( void * ) ( &packetMatchParams ), - IotMqtt_FreeSubscription, - offsetof( _mqttSubscription_t, link ) ); - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount ) -{ - size_t i = 0; - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - _topicMatchParams_t topicMatchParams = { 0 }; - - /* Prevent any other thread from modifying the subscription list while this - * function is running. */ - IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); - - /* Find and remove each topic filter from the list. */ - for( i = 0; i < subscriptionCount; i++ ) - { - topicMatchParams.pTopicName = pSubscriptionList[ i ].pTopicFilter; - topicMatchParams.topicNameLength = pSubscriptionList[ i ].topicFilterLength; - topicMatchParams.exactMatchOnly = true; - - pSubscriptionLink = IotListDouble_FindFirstMatch( &( pMqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ); - - if( pSubscriptionLink != NULL ) - { - /* Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - - /* Reference count must not be negative. */ - IotMqtt_Assert( pSubscription->references >= 0 ); - - /* Remove subscription from list. */ - IotListDouble_Remove( pSubscriptionLink ); - - /* Check the reference count. This subscription cannot be removed if - * there are subscription callbacks using it. */ - if( pSubscription->references > 0 ) - { - /* Set the unsubscribed flag. The last active subscription callback - * will remove and clean up this subscription. */ - pSubscription->unsubscribed = true; - } - else - { - /* Free a subscription with no references. */ - IotMqtt_FreeSubscription( pSubscription ); - } - } - } - - IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); -} - -/*-----------------------------------------------------------*/ - -bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, - const char * pTopicFilter, - uint16_t topicFilterLength, - IotMqttSubscription_t * const pCurrentSubscription ) -{ - bool status = false; - const _mqttSubscription_t * pSubscription = NULL; - const IotLink_t * pSubscriptionLink = NULL; - _topicMatchParams_t topicMatchParams = { 0 }; - - /* Set the members of the search parameter. */ - topicMatchParams.pTopicName = pTopicFilter; - topicMatchParams.topicNameLength = topicFilterLength; - topicMatchParams.exactMatchOnly = true; - - /* Prevent any other thread from modifying the subscription list while this - * function is running. */ - IotMutex_Lock( &( mqttConnection->subscriptionMutex ) ); - - if( pTopicFilter == NULL ) - { - IotLogError( "Topic filter must be set." ); - } - else - { - /* Search for a matching subscription. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( mqttConnection->subscriptionList ), - NULL, - _topicMatch, - &topicMatchParams ); - } - - /* Check if a matching subscription was found. */ - if( pSubscriptionLink != NULL ) - { - /* Casting `pSubscriptionLink` to uint8_t * is done only to calculate the - * starting address of the struct and does not modify the link it points to. - * Adding parentheses to parameters of IotLink_Container is not applicable - * because it uses type-casting and offsetof, and would cause compiling errors. */ - /* coverity[misra_c_2012_rule_11_8_violation] */ - /* coverity[misra_c_2012_rule_20_7_violation] */ - /* coverity[caretline] */ - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - - /* Copy the matching subscription to the output parameter. */ - if( pCurrentSubscription != NULL ) - { - pCurrentSubscription->pTopicFilter = pTopicFilter; - pCurrentSubscription->topicFilterLength = topicFilterLength; - pCurrentSubscription->qos = IOT_MQTT_QOS_0; - pCurrentSubscription->callback = pSubscription->callback; - } - - status = true; - } - - IotMutex_Unlock( &( mqttConnection->subscriptionMutex ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -/* Provide access to internal functions and variables if testing. */ -/* IOT_BUILD_TESTS is defined outside the code base, e.g. passed in by build command. */ -/* coverity[misra_c_2012_rule_20_9_violation] */ -/* coverity[caretline] */ -#if IOT_BUILD_TESTS == 1 - #include "iot_test_access_mqtt_subscription.c" -#endif diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c deleted file mode 100644 index cbefb80ab2..0000000000 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ /dev/null @@ -1,751 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_validate.c - * @brief Implements functions that validate the structs of the MQTT library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/** - * @brief Check that an #IotMqttPublishInfo_t is valid. - * - * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. - * @param[in] maximumPayloadLength Maximum payload length. - * @param[in] pPublishTypeDescription String describing the publish type. - * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. - * - * @return `true` if `pPublishInfo` is valid; `false` otherwise. - */ -static bool _validatePublish( bool awsIotMqttMode, - size_t maximumPayloadLength, - const char * pPublishTypeDescription, - const IotMqttPublishInfo_t * pPublishInfo ); - -/** - * @brief Check that the payload inside #IotMqttPublishInfo_t is valid. - * - * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. - * @param[in] maximumPayloadLength Maximum payload length. - * @param[in] pPublishTypeDescription String describing the publish type. - * - * @return `true` if payload is valid; `false` otherwise - */ -static bool _validatePublishPayload( const IotMqttPublishInfo_t * pPublishInfo, - size_t maximumPayloadLength, - const char * pPublishTypeDescription ); - - -/** - * @brief Check that an #IotMqttQos_t is valid. - * - * @param[in] qos The QoS to check. - * - * @return `true` if `qos` is valid; `false` otherwise. - */ -static bool _validateQos( IotMqttQos_t qos ); - -/** - * @brief Check that a string is valid. - * - * @param[in] pString The string to check. - * @param[in] length Length of string to check. - * - * @return `true` if `pString` is valid; `false` otherwise. - */ -static bool _validateString( const char * pString, - uint16_t length ); - -/** - * @brief Check that a list of subscriptions is valid. - * - * @param[in] awsIotMqttMode Whether to enforce list length restrictions from AWS IoT. - * @param[in] pListStart First element of the list. - * @param[in] listSize Length of the list. - * - * @return `true` if `pListStart` is valid; `false` otherwise. - */ -static bool _validateListSize( bool awsIotMqttMode, - const IotMqttSubscription_t * pListStart, - size_t listSize ); - -/** - * @brief Check that a single subscription is valid. - * - * @param[in] awsIotMqttMode Whether to enforce the topic filter restrictions from AWS IoT. - * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. - * @param[in] pSubscription The subscription to check. - * - * @return `true` if `pSubscription` is valid; `false` otherwise. - */ -static bool _validateSubscription( bool awsIotMqttMode, - IotMqttOperationType_t operation, - const IotMqttSubscription_t * pSubscription ); - -/** - * @brief Check that the MQTT `+` wildcard is being used correctly. - * - * @param[in] index Index of `+` in the topic filter. - * @param[in] pSubscription Subscription with the topic filter to check. - * - * @return `true` if the `+` wildcard is valid; `false` otherwise. - */ -static bool _validateWildcardPlus( uint16_t index, - const IotMqttSubscription_t * pSubscription ); - -/** - * @brief Check that the MQTT `#` wildcard is being used correctly. - * - * @param[in] index Index of `#` in the topic filter. - * @param[in] pSubscription Subscription with the topic filter to check. - * - * @return `true` if the `#` wildcard is valid; `false` otherwise. - */ -static bool _validateWildcardHash( uint16_t index, - const IotMqttSubscription_t * pSubscription ); - -/** - * @brief Validate the MQTT client identifier. - * - * @param[in] pConnectInfo The #IotMqttConnectInfo_t containing the client identifier - * to validate. - * - * @return `true` if client identifier is valid, `false` otherwise. - */ -static bool _validateClientId( const IotMqttConnectInfo_t * pConnectInfo ); - -/*-----------------------------------------------------------*/ - -static bool _validateQos( IotMqttQos_t qos ) -{ - bool status = false; - - switch( qos ) - { - case IOT_MQTT_QOS_0: - case IOT_MQTT_QOS_1: - status = true; - break; - - default: - IotLogError( "QoS must be either 0 or 1." ); - - break; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validateString( const char * pString, - uint16_t length ) -{ - bool status = true; - - if( pString == NULL ) - { - status = false; - } - else if( length == 0U ) - { - status = false; - } - else - { - status = true; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validatePublishPayload( const IotMqttPublishInfo_t * pPublishInfo, - size_t maximumPayloadLength, - const char * pPublishTypeDescription ) -{ - bool status = true; - - /* This parameter is not used when logging is disabled. */ - ( void ) pPublishTypeDescription; - - if( pPublishInfo->payloadLength != 0U ) - { - if( pPublishInfo->payloadLength > maximumPayloadLength ) - { - IotLogError( "%s payload size of %zu exceeds maximum length of %zu.", - pPublishTypeDescription, - pPublishInfo->payloadLength, - maximumPayloadLength ); - - status = false; - } - else if( pPublishInfo->pPayload == NULL ) - { - IotLogError( "Nonzero payload length cannot have a NULL payload." ); - - status = false; - } - else - { - /* Empty else MISRA 15.7 */ - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validatePublish( bool awsIotMqttMode, - size_t maximumPayloadLength, - const char * pPublishTypeDescription, - const IotMqttPublishInfo_t * pPublishInfo ) -{ - bool status = true; - - /* Check for NULL. */ - if( pPublishInfo == NULL ) - { - IotLogError( "Publish information cannot be NULL." ); - - status = false; - } - /* Check topic name for NULL or zero-length. */ - else - { - status = _validateString( pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); - - if( status != true ) - { - IotLogError( "Publish topic name must be set." ); - } - } - - if( status == true ) - { - status = _validatePublishPayload( pPublishInfo, - maximumPayloadLength, - pPublishTypeDescription ); - } - - if( status == true ) - { - /* Check for a valid QoS. */ - status = _validateQos( pPublishInfo->qos ); - } - - if( status == true ) - { - /* Check the retry parameters. */ - if( pPublishInfo->retryLimit > 0U ) - { - if( pPublishInfo->retryMs == 0U ) - { - IotLogError( "Publish retry time must be positive." ); - - status = false; - } - } - } - - if( status == true ) - { - /* Check for compatibility with AWS IoT MQTT server. */ - if( awsIotMqttMode == true ) - { - /* Check for retained message. */ - if( pPublishInfo->retain == true ) - { - IotLogError( "AWS IoT does not support retained publish messages." ); - - status = false; - } - - /* Check topic name length. */ - if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - { - IotLogError( "AWS IoT does not support topic names longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validateListSize( bool awsIotMqttMode, - const IotMqttSubscription_t * pListStart, - size_t listSize ) -{ - bool status = true; - - /* Check for empty list. */ - if( pListStart == NULL ) - { - IotLogError( "Subscription list pointer cannot be NULL." ); - - status = false; - } - else if( listSize == 0U ) - { - IotLogError( "Empty subscription list." ); - - status = false; - } - else - { - /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ - if( awsIotMqttMode == true ) - { - if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) - { - IotLogError( "AWS IoT does not support more than %d topic filters per " - "subscription request.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); - - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validateSubscription( bool awsIotMqttMode, - IotMqttOperationType_t operation, - const IotMqttSubscription_t * pSubscription ) -{ - bool status = true; - uint16_t i = 0; - - /* Check for a valid QoS and callback function when subscribing. */ - if( operation == IOT_MQTT_SUBSCRIBE ) - { - if( pSubscription->callback.function == NULL ) - { - IotLogError( "Callback function must be set." ); - - status = false; - } - else - { - status = _validateQos( pSubscription->qos ); - } - } - - /* Check subscription topic filter. */ - if( status == true ) - { - status = _validateString( pSubscription->pTopicFilter, pSubscription->topicFilterLength ); - - if( status == false ) - { - IotLogError( "Subscription topic filter must be set." ); - } - } - - /* Check topic filter length compatibility with AWS IoT MQTT server. */ - if( status == true ) - { - if( awsIotMqttMode == true ) - { - if( pSubscription->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - { - IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - - status = false; - } - } - } - - if( status == true ) - { - /* Check that the wildcards '+' and '#' are being used correctly. */ - for( i = 0; i < pSubscription->topicFilterLength; i++ ) - { - if( pSubscription->pTopicFilter[ i ] == '+' ) - { - status = _validateWildcardPlus( i, pSubscription ); - } - else if( pSubscription->pTopicFilter[ i ] == '#' ) - { - status = _validateWildcardHash( i, pSubscription ); - } - else - { - /* Empty else MISRA 15.7 */ - } - - if( status == false ) - { - break; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validateWildcardPlus( uint16_t index, - const IotMqttSubscription_t * pSubscription ) -{ - bool status = true; - - /* Unless '+' is the first character in the filter, it must be preceded by '/'. */ - if( index > 0U ) - { - if( pSubscription->pTopicFilter[ index - 1U ] != '/' ) - { - IotLogError( "Invalid topic filter %.*s -- '+' must be preceded by '/'.", - pSubscription->topicFilterLength, - pSubscription->pTopicFilter ); - - status = false; - } - } - - if( status == true ) - { - /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ - if( index < ( pSubscription->topicFilterLength - 1U ) ) - { - if( pSubscription->pTopicFilter[ index + 1U ] != '/' ) - { - IotLogError( "Invalid topic filter %.*s -- '+' must be succeeded by '/'.", - pSubscription->topicFilterLength, - pSubscription->pTopicFilter ); - - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validateWildcardHash( uint16_t index, - const IotMqttSubscription_t * pSubscription ) -{ - bool status = true; - - /* '#' must be the last character in the filter. */ - if( index != ( pSubscription->topicFilterLength - 1U ) ) - { - IotLogError( "Invalid topic filter %.*s -- '#' must be the last character.", - pSubscription->topicFilterLength, - pSubscription->pTopicFilter ); - - status = false; - } - - if( status == true ) - { - /* Unless '#' is standalone, it must be preceded by '/'. */ - if( pSubscription->topicFilterLength > 1U ) - { - if( pSubscription->pTopicFilter[ index - 1U ] != '/' ) - { - IotLogError( "Invalid topic filter %.*s -- '#' must be preceded by '/'.", - pSubscription->topicFilterLength, - pSubscription->pTopicFilter ); - - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static bool _validateClientId( const IotMqttConnectInfo_t * pConnectInfo ) -{ - bool status = true; - uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; - bool enforceMaxClientIdLength = false; - - /* Check that a client identifier was set. */ - if( pConnectInfo->pClientIdentifier == NULL ) - { - IotLogError( "Client identifier must be set." ); - - status = false; - } - - /* Check for a zero-length client identifier. Zero-length client identifiers - * are not allowed with persistent sessions. */ - if( status == true ) - { - if( pConnectInfo->clientIdentifierLength == 0U ) - { - IotLogWarn( "A zero-length client identifier was provided." ); - - if( pConnectInfo->cleanSession == false ) - { - IotLogError( "A zero-length client identifier cannot be used with a persistent session." ); - - status = false; - } - } - } - - /* The AWS IoT MQTT service enforces a client ID length limit. */ - if( pConnectInfo->awsIotMqttMode == true ) - { - maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; - enforceMaxClientIdLength = true; - } - - if( status == true ) - { - if( pConnectInfo->clientIdentifierLength > maxClientIdLength ) - { - if( enforceMaxClientIdLength == false ) - { - IotLogWarn( "A client identifier length of %hu is longer than %hu, " - "which is " - "the longest client identifier a server must accept.", - pConnectInfo->clientIdentifierLength, - maxClientIdLength ); - } - else - { - IotLogError( "A client identifier length of %hu exceeds the " - "maximum supported length of %hu.", - pConnectInfo->clientIdentifierLength, - maxClientIdLength ); - - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) -{ - bool status = true; - - /* Check for NULL. */ - if( pConnectInfo == NULL || - ( pConnectInfo->pUserName == NULL && pConnectInfo->userNameLength != 0 ) || - ( pConnectInfo->pPassword == NULL && pConnectInfo->passwordLength != 0 ) ) - { - IotLogError( "MQTT connection information cannot be NULL." ); - - status = false; - } - else - { - /* Check client identifier.*/ - status = _validateClientId( pConnectInfo ); - } - - if( status == true ) - { - /* Check that the number of persistent session subscriptions is valid. */ - if( pConnectInfo->pPreviousSubscriptions != NULL ) - { - status = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ); - } - } - - if( status == true ) - { - /* If will info is provided, check that it is valid. */ - if( pConnectInfo->pWillInfo != NULL ) - { - status = _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, - pConnectInfo->pWillInfo ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - const IotMqttOperation_t * const pPublishOperation ) -{ - bool status = true; - size_t maximumPayloadLength = MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; - - if( awsIotMqttMode == true ) - { - maximumPayloadLength = AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; - } - - status = _validatePublish( awsIotMqttMode, - maximumPayloadLength, - "Publish", - pPublishInfo ); - - if( status == true ) - { - /* Check that no notification is requested for a QoS 0 publish. */ - if( pPublishInfo->qos == IOT_MQTT_QOS_0 ) - { - if( pCallbackInfo != NULL ) - { - IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - - status = false; - } - else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0U ) - { - IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - - status = false; - } - else - { - /* Empty else MISRA 15.7 */ - } - - if( pPublishOperation != NULL ) - { - IotLogWarn( "Ignoring reference parameter for QoS 0 publish." ); - } - } - - /* Check that a reference pointer is provided for a waitable operation. */ - if( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) - { - if( pPublishOperation == NULL ) - { - IotLogError( "Reference must be provided for a waitable PUBLISH." ); - - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_ValidateLwtPublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pLwtPublishInfo ) -{ - return _validatePublish( awsIotMqttMode, - MQTT_SERVER_MAX_LWT_PAYLOAD_LENGTH, - "LWT", - pLwtPublishInfo ); -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) -{ - bool status = true; - - /* Check for NULL. */ - if( operation == NULL ) - { - IotLogError( "Operation reference cannot be NULL." ); - - status = false; - } - else - { - /* Check that reference is waitable. */ - if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) - { - IotLogError( "Operation is not waitable." ); - - status = false; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, - bool awsIotMqttMode, - const IotMqttSubscription_t * pListStart, - size_t listSize ) -{ - bool status = true; - size_t i = 0; - - /* Operation must be either subscribe or unsubscribe. */ - IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || - ( operation == IOT_MQTT_UNSUBSCRIBE ) ); - - /* Check that subscription list is valid. */ - status = _validateListSize( awsIotMqttMode, - pListStart, - listSize ); - - if( status == true ) - { - /* Check each member of the subscription list. */ - for( i = 0; i < listSize; i++ ) - { - status = _validateSubscription( awsIotMqttMode, - operation, - &( pListStart[ i ] ) ); - - if( status == false ) - { - break; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c new file mode 100644 index 0000000000..af545a1be0 --- /dev/null +++ b/libraries/standard/mqtt/src/mqtt.c @@ -0,0 +1,58 @@ +#include "mqtt.h" + +void MQTT_Init( MQTTContext_t * const pContext, + const MQTTTransportInterface_t * const pTransportInterface, + const MQTTApplicationCallbacks_t * const pCallbacks, + const MQTTFixedBuffer_t * const pTxBuffer, + const MQTTFixedBuffer_t * const pRxBuffer ) +{ + +} + +MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, + const MQTTConnectInfo_t * const pConnectInfo, + bool * const pSessionPresent ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, + const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, + const MQTTPublishInfo_t * const pPublishInfo ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, + const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, + uint32_t timeoutMs ) +{ + return MQTTSuccess; +} + +uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) +{ + return ( uint16_t) 1; +} diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c new file mode 100644 index 0000000000..6175c501c4 --- /dev/null +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -0,0 +1,95 @@ +#include "mqtt_lightweight.h" + +MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, + uint16_t packetId, + size_t remainingLength, + MQTTFixedBuffer_t * const pBuffer, + size_t * const pHeaderSize ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializeDisconnect( MQTTFixedBuffer_t * const pBuffer ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_SerializePingreq( MQTTFixedBuffer_t * const pBuffer ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_GetPacket( TransportReadFunc_t readFunc, + MQTTPacketInfo_t * const pIncomingPacket ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, + uint16_t * const pPacketId, + MQTTPublishInfo_t * const pPublishInfo ) +{ + return MQTTSuccess; +} + +MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, + uint16_t * const pPacketId, + bool * const pSessionPresent ) +{ + return MQTTSuccess; +} diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h b/libraries/standard/mqtt/src/private/iot_mqtt_helper.h deleted file mode 100644 index 3b417738f7..0000000000 --- a/libraries/standard/mqtt/src/private/iot_mqtt_helper.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_helper.c - * @brief Implements internal helper functions for the MQTT library. - */ - -#ifndef IOT_MQTT_HELPER_H_ -#define IOT_MQTT_HELPER_H_ - -/* Standard includes. */ -#include -#include -/* Default assert and memory allocation functions. */ -#include -#include - -/* MQTT Protocol Specific defines */ -#include "iot_mqtt_protocol.h" -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/*-----------------------------------------------------------*/ - -/* - * Macros for reading the high and low byte of a 2-byte unsigned int. - */ -#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) /**< @brief Get high byte. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) /**< @brief Get low byte. */ - -/** - * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. - * - * @param[in] ptr A uint8_t* that points to the high byte. - */ -#define UINT16_DECODE( ptr ) \ - ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ - ( ( uint16_t ) ( *( ( ptr ) + 1 ) ) ) ) - -/** - * @brief Macro for setting a bit in a 1-byte unsigned int. - * - * @param[in] x The unsigned int to set. - * @param[in] position Which bit to set. - */ -#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) - -/** - * @brief Macro for checking if a bit is set in a 1-byte unsigned int. - * - * @param[in] x The unsigned int to check. - * @param[in] position Which bit to check. - */ -#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) &( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_MQTT_ENABLE_METRICS - #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) -#endif -/** @endcond */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Calculate the number of bytes required to encode an MQTT - * "Remaining length" field. - * - * @param[in] length The value of the "Remaining length" to encode. - * - * @return The size of the encoding of length. This is always `1`, `2`, `3`, or `4`. - */ -size_t _IotMqtt_RemainingLengthEncodedSize( size_t length ); - -/** - * @brief Calculate the size and "Remaining length" of a CONNECT packet generated - * from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information struct. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` - * otherwise. If this function returns `false`, the output parameters should be ignored. - */ -bool _IotMqtt_ConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); - - -/** - * @brief Generate a CONNECT packet from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[in] remainingLength User provided remaining length. - * @param[in, out] pPacket User provided buffer where the CONNECT packet is written. - * @param[in] connectPacketSize Size of the buffer pointed to by `pPacket`. - * - */ -void _IotMqtt_SerializeConnectCommon( const IotMqttConnectInfo_t * pConnectInfo, - size_t remainingLength, - uint8_t * pPacket, - size_t connectPacketSize ); - -/** - * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE - * packet generated from the given parameters. - * - * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` - * otherwise. If this function returns `false`, the output parameters should be ignored. - */ -bool _IotMqtt_SubscriptionPacketSize( IotMqttOperationType_t type, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t * pRemainingLength, - size_t * pPacketSize ); - -/** - * @brief Generate a SUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[in] remainingLength User provided remaining length. - * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. - * @param[in, out] pPacket User provided buffer where the SUBSCRIBE packet is written. - * @param[in] subscribePacketSize Size of the buffer pointed to by `pPacket`. - * - */ -void _IotMqtt_SerializeSubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t subscribePacketSize ); - -/** - * @brief Generate an UNSUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions to remove. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[in] remainingLength User provided remaining length. - * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. - * @param[in, out] pPacket User provided buffer where the UNSUBSCRIBE packet is written. - * @param[in] unsubscribePacketSize size of the buffer pointed to by `pPacket`. - * - */ -void _IotMqtt_SerializeUnsubscribeCommon( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t * pPacket, - size_t unsubscribePacketSize ); - -/** - * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated - * from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information struct. - * @param[out] pRemainingLength Output for calculated "Remaining length" field. - * @param[out] pPacketSize Output for calculated total packet size. - * - * @return `true` if the packet is within the length allowed by MQTT 3.1.1 spec; `false` - * otherwise. If this function returns `false`, the output parameters should be ignored. - */ -bool _IotMqtt_PublishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, - size_t * pRemainingLength, - size_t * pPacketSize ); - -/** - * @brief Generate a PUBLISH packet from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information. - * @param[in] remainingLength User provided remaining length. - * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. - * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier - * is written. - * @param[in, out] pPacket User provided buffer where the PUBLISH packet is written. - * @param[in] publishPacketSize Size of buffer pointed to by `pPacket`. - * - */ -void _IotMqtt_SerializePublishCommon( const IotMqttPublishInfo_t * pPublishInfo, - size_t remainingLength, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh, - uint8_t * pPacket, - size_t publishPacketSize ); - -/** - * @brief Check if an incoming packet type is valid. - * - * @param[in] packetType The packet type to check. - * - * @return `true` if the packet type is valid; `false` otherwise. - */ -bool _IotMqtt_IncomingPacketValid( uint8_t packetType ); - -/** - * @brief Generate and return a 2-byte packet identifier. - * - * This packet identifier will be nonzero. - * - * @return The packet identifier. - */ -uint16_t _IotMqtt_NextPacketIdentifier( void ); - -/** - * @brief Process incoming publish flags. - * - * @param[in] publishFlags Incoming publish flags. - * @param[in, out] pOutput Pointer to #IotMqttPublishInfo_t struct. - * where output will be written. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_RESPONSE. - */ - -IotMqttError_t _IotMqtt_ProcessPublishFlags( uint8_t publishFlags, - IotMqttPublishInfo_t * pOutput ); - -#endif /* ifndef IOT_MQTT_HELPER_H_ */ diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h deleted file mode 100644 index ccfb802839..0000000000 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ /dev/null @@ -1,1025 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_mqtt_internal.h - * @brief Internal header of MQTT library. This header should not be included in - * typical application code. - */ - -#ifndef IOT_MQTT_INTERNAL_H_ -#define IOT_MQTT_INTERNAL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Linear containers (lists and queues) include. */ -#include "iot_linear_containers.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* Task pool include. */ -#include "iot_taskpool.h" - -/** - * @def IotMqtt_Assert( expression ) - * @brief Assertion macro for the MQTT library. - * - * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT - * library. - * - * @param[in] expression Expression to be evaluated. - */ -#if IOT_MQTT_ENABLE_ASSERTS == 1 - #ifndef IotMqtt_Assert - #ifdef Iot_DefaultAssert - #define IotMqtt_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for MQTT, but IotMqtt_Assert is not defined" - #endif - #endif -#else - #define IotMqtt_Assert( expression ) -#endif - -/* Configure logs for MQTT functions. */ -#ifdef IOT_LOG_LEVEL_MQTT - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "MQTT" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "iot_static_memory.h" - -/** - * @brief Allocate an #_mqttConnection_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotMqtt_MallocConnection( size_t size ); - -/** - * @brief Free an #_mqttConnection_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotMqtt_FreeConnection( void * ptr ); - -/** - * @brief Allocate memory for an MQTT packet. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotMqtt_MallocMessage Iot_MallocMessageBuffer - -/** - * @brief Free an MQTT packet. This function should have the same signature - * as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotMqtt_FreeMessage Iot_FreeMessageBuffer - -/** - * @brief Allocate an #_mqttOperation_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotMqtt_MallocOperation( size_t size ); - -/** - * @brief Free an #_mqttOperation_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotMqtt_FreeOperation( void * ptr ); - -/** - * @brief Allocate an #_mqttSubscription_t. This function should have the - * same signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotMqtt_MallocSubscription( size_t size ); - -/** - * @brief Free an #_mqttSubscription_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotMqtt_FreeSubscription( void * ptr ); -#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #ifndef IotMqtt_MallocConnection - #ifdef Iot_DefaultMalloc - #define IotMqtt_MallocConnection Iot_DefaultMalloc - #else - #error "No malloc function defined for IotMqtt_MallocConnection" - #endif - #endif - - #ifndef IotMqtt_FreeConnection - #ifdef Iot_DefaultFree - #define IotMqtt_FreeConnection Iot_DefaultFree - #else - #error "No free function defined for IotMqtt_FreeConnection" - #endif - #endif - - #ifndef IotMqtt_MallocMessage - #ifdef Iot_DefaultMalloc - #define IotMqtt_MallocMessage Iot_DefaultMalloc - #else - #error "No malloc function defined for IotMqtt_MallocMessage" - #endif - #endif - - #ifndef IotMqtt_FreeMessage - #ifdef Iot_DefaultFree - #define IotMqtt_FreeMessage Iot_DefaultFree - #else - #error "No free function defined for IotMqtt_FreeMessage" - #endif - #endif - - #ifndef IotMqtt_MallocOperation - #ifdef Iot_DefaultMalloc - #define IotMqtt_MallocOperation Iot_DefaultMalloc - #else - #error "No malloc function defined for IotMqtt_MallocOperation" - #endif - #endif - - #ifndef IotMqtt_FreeOperation - #ifdef Iot_DefaultFree - #define IotMqtt_FreeOperation Iot_DefaultFree - #else - #error "No free function defined for IotMqtt_FreeOperation" - #endif - #endif - - #ifndef IotMqtt_MallocSubscription - #ifdef Iot_DefaultMalloc - #define IotMqtt_MallocSubscription Iot_DefaultMalloc - #else - #error "No malloc function defined for IotMqtt_MallocSubscription" - #endif - #endif - - #ifndef IotMqtt_FreeSubscription - #ifdef Iot_DefaultFree - #define IotMqtt_FreeSubscription Iot_DefaultFree - #else - #error "No free function defined for IotMqtt_FreeSubscription" - #endif - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values for undefined configuration constants. - */ -#ifndef AWS_IOT_MQTT_ENABLE_METRICS - #define AWS_IOT_MQTT_ENABLE_METRICS ( 1 ) -#endif -#ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES - #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 0 ) -#endif -#ifndef IOT_MQTT_RESPONSE_WAIT_MS - #define IOT_MQTT_RESPONSE_WAIT_MS ( 1000U ) -#endif -#ifndef IOT_MQTT_RETRY_MS_CEILING - #define IOT_MQTT_RETRY_MS_CEILING ( 60000U ) -#endif -/** @endcond */ - -#define MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 23 ) /**< @brief Optional maximum length of client identifier specified by MQTT 3.1.1. */ -#define MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 268435456 ) ) /**< @brief Maximum publish payload length supported by MQTT 3.1.1. */ -#define MQTT_SERVER_MAX_LWT_PAYLOAD_LENGTH ( ( size_t ) UINT16_MAX ) /**< @brief Maximum LWT payload length supported by MQTT 3.1.1. */ - -/* - * Constants related to limits defined in AWS Service Limits. - * - * For details, see - * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html - * - * Used to validate parameters if when connecting to an AWS IoT MQTT server. - */ -#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( ( uint16_t ) 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( ( size_t ) 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ -#define AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 131072 ) ) /**< @brief Maximum publish payload length accepted by AWS IoT. */ - -/* - * MQTT control packet type and flags. Always the first byte of an MQTT - * packet. - * - * For details, see - * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 - */ -#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ -#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ -#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ -#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ -#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ -#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ - -/** - * @brief A value that represents an invalid remaining length. - * - * This value is greater than what is allowed by the MQTT specification. - */ -#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) - -/** - * @brief When this flag is passed, MQTT functions will execute jobs on the calling - * thread, bypassing the task pool. - * - * This flag is used along with @ref mqtt_constants_flags, but is intended for internal - * use only. Nevertheless, its value must be bitwise exclusive of all conflicting - * @ref mqtt_constants_flags. - */ -#define MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ( 0x80000000U ) - -/** - * @brief When calling #_IotMqtt_RemoveSubscriptionByPacket, use this value - * for `order` to delete all subscriptions for the packet. - * - * This flag is used along with @ref mqtt_constants_flags, but is intended for internal - * use only. - * - * @ref mqtt_constants_flags. - */ -#define MQTT_REMOVE_ALL_SUBSCRIPTIONS ( -1 ) - -/*---------------------- MQTT internal data structures ----------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declaration of MQTT connection type. - */ -struct _mqttConnection; -/** @endcond */ - -/** - * @brief Internal structure representing a single MQTT operation, such as - * CONNECT, SUBSCRIBE, PUBLISH, etc. - * - * Queues of these structures keeps track of all in-progress MQTT operations. - */ -typedef struct _mqttOperation -{ - /* Pointers to neighboring queue elements. */ - IotLink_t link; /**< @brief List link member. */ - - bool incomingPublish; /**< @brief Set to true if this operation is an incoming PUBLISH. */ - struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ - - IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ - IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ - - /* MISRA rule 19.2 doesn't allow usage of union - * but it is intentionally used here to reduce the size of struct. */ - /* coverity[misra_c_2012_rule_19_2_violation] */ - union - { - /* If incomingPublish is false, this struct is valid. */ - struct - { - /* Basic operation information. */ - int32_t jobReference; /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */ - IotMqttOperationType_t type; /**< @brief What operation this structure represents. */ - uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ - uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ - - /* Serialized packet and size. */ - uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ - uint8_t * pPacketIdentifierHigh; /**< @brief The location of the high byte of the packet identifier in the MQTT packet. */ - size_t packetSize; /**< @brief Size of `pMqttPacket`. */ - - /* How to notify of an operation's completion. */ - - /* MISRA rule 19.2 doesn't allow usage of union - * but it is intentionally used here to reduce the size of struct. */ - /* coverity[misra_c_2012_rule_19_2_violation] */ - union - { - IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref mqtt_function_wait. */ - IotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ - } notify; /**< @brief How to notify of this operation's completion. */ - IotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ - - /* MISRA rule 19.2 doesn't allow usage of union - * but it is intentionally used here to reduce the size of struct. */ - /* coverity[misra_c_2012_rule_19_2_violation] */ - union - { - struct - { - uint32_t count; /**< @brief Current number of retries. */ - uint32_t limit; /**< @brief Maximum number of retries allowed. */ - uint32_t nextPeriodMs; /**< @brief Next retry period. */ - } retry; /**< @brief Additional information for PUBLISH retry. */ - - struct - { - uint32_t failure; /**< @brief Flag tracking keep-alive status. */ - uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ - uint32_t nextPeriodMs; /**< @brief Relative delay for next keep-alive job. */ - } ping; /**< @brief Additional information for keep-alive pings. */ - } periodic; /**< @brief Additional information for periodic operations. */ - } operation; - - /* If incomingPublish is true, this struct is valid. */ - struct - { - IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ - void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ - } publish; - } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ -} _mqttOperation_t; - -/** - * @brief Represents an MQTT connection. - */ -typedef struct _mqttConnection -{ - bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ - bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ - IotNetworkConnection_t pNetworkConnection; /**< @brief References the transport-layer network connection. */ - const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ - IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ - - const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ - - bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ - IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ - int32_t references; /**< @brief Counts callbacks and operations using this connection. */ - IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ - IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ - - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ - - uint64_t lastMessageTime; /**< @brief When the most recent message was transmitted. */ - _mqttOperation_t pingreq; /**< @brief Operation used for MQTT keep-alive. */ -} _mqttConnection_t; - -/** - * @brief Represents a subscription stored in an MQTT connection. - */ -typedef struct _mqttSubscription -{ - IotLink_t link; /**< @brief List link member. */ - - int32_t references; /**< @brief How many subscription callbacks are using this subscription. */ - - /** - * @brief Tracks whether an unsubscribe function has been called for - * this subscription. - * - * If there are active subscription callbacks, this subscription cannot be removed. - * Instead, this flag will be set, which schedules the removal of this subscription - * once all subscription callbacks terminate. - */ - bool unsubscribed; - - struct - { - uint16_t identifier; /**< @brief Packet identifier. */ - size_t order; /**< @brief Order in the packet's list of subscriptions. */ - } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ - - IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ - - uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ - - /* A flexible length array is used here so that the topic filter may - * be of an arbitrary length. */ - /* coverity[misra_c_2012_rule_18_7_violation] */ - char pTopicFilter[]; /**< @brief The subscription topic filter. */ -} _mqttSubscription_t; - -/** - * @brief Represents an MQTT packet received from the network. - * - * This struct is used to hold parameters for the deserializers so that all - * deserializers have the same function signature. - */ -typedef struct _mqttPacket -{ - /* MISRA rule 19.2 doesn't allow usage of union - * but it is intentionally used here to reduce the size of struct. */ - /* coverity[misra_c_2012_rule_19_2_violation] */ - union - { - /** - * @brief (Input) MQTT connection associated with this packet. Only used - * when deserializing SUBACKs. - */ - _mqttConnection_t * pMqttConnection; - - /** - * @brief (Output) Operation representing an incoming PUBLISH. Only used - * when deserializing PUBLISHes. - */ - _mqttOperation_t * pIncomingPublish; - } u; /**< @brief Valid member depends on packet being decoded. */ - - uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ - size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ - uint16_t packetIdentifier; /**< @brief (Output) MQTT packet identifier. */ - uint8_t type; /**< @brief (Input) A value identifying the packet type. */ -} _mqttPacket_t; - -/*-------------------- MQTT struct validation functions ---------------------*/ - -/** - * @brief Check that an #IotMqttConnectInfo_t is valid. - * - * @param[in] pConnectInfo The #IotMqttConnectInfo_t to validate. - * - * @return `true` if `pConnectInfo` is valid; `false` otherwise. - */ -bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ); - -/** - * @brief Check that parameters for an MQTT PUBLISH are valid. - * - * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. - * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. - * @param[in] flags Behavior modification flags to validate. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo #IotMqttCallbackInfo_t to validate. - * @param[in] pPublishOperation Handle to a PUBLISH operation. - * - * @return `true` if all parameters are valid; `false` otherwise. - */ -bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - const IotMqttOperation_t * const pPublishOperation ); - -/** - * @brief Check that an #IotMqttPublishInfo_t is valid for an LWT publish - * - * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to - * an AWS IoT MQTT server. - * @param[in] pLwtPublishInfo The #IotMqttPublishInfo_t to validate. - * - * @return `true` if `pLwtPublishInfo` is valid; `false` otherwise. - */ -bool _IotMqtt_ValidateLwtPublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pLwtPublishInfo ); - -/** - * @brief Check that an #IotMqttOperation_t is valid and waitable. - * - * @param[in] operation The #IotMqttOperation_t to validate. - * - * @return `true` if `operation` is valid; `false` otherwise. - */ -bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ); - -/** - * @brief Check that a list of #IotMqttSubscription_t is valid. - * - * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE. - * Some parameters are not validated for #IOT_MQTT_UNSUBSCRIBE. - * @param[in] awsIotMqttMode Specifies if this SUBSCRIBE packet is being sent to - * an AWS IoT MQTT server. - * @param[in] pListStart First element of the list to validate. - * @param[in] listSize Number of elements in the subscription list. - * - * @return `true` if every element in the list is valid; `false` otherwise. - */ -bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, - bool awsIotMqttMode, - const IotMqttSubscription_t * pListStart, - size_t listSize ); - -/*-------------------- MQTT packet serializer functions ---------------------*/ - -/** - * @brief Get the MQTT packet type from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - * - * @return One of the server-to-client MQTT packet types. - * - * @note This function is only used for incoming packets, and may not work - * correctly for outgoing packets. - */ -uint8_t _IotMqtt_GetPacketType( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); - -/** - * @brief Get the remaining length from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - * - * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. - */ -size_t _IotMqtt_GetRemainingLength( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); -/** - * @brief Generate a CONNECT packet from the given parameters. - * - * @param[in] pConnectInfo User-provided CONNECT information. - * @param[out] pConnectPacket Where the CONNECT packet is written. - * @param[out] pPacketSize Size of the packet written to `pConnectPacket`. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - uint8_t ** pConnectPacket, - size_t * pPacketSize ); - -/** - * @brief Deserialize a CONNACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t. Also - * prints out debug log messages about the packet. - * - * @param[in,out] pConnack Pointer to an MQTT packet struct representing a CONNACK. - * - * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted; - * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected; - * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec. - */ -IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ); - -/** - * @brief Generate a PUBLISH packet from the given parameters. - * - * @param[in] pPublishInfo User-provided PUBLISH information. - * @param[out] pPublishPacket Where the PUBLISH packet is written. - * @param[out] pPacketSize Size of the packet written to `pPublishPacket`. - * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. - * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier - * is written. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, - uint8_t ** pPublishPacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh ); - -/** - * @brief Set the DUP bit in a QoS 1 PUBLISH packet. - * - * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify. - * @param[in] pPacketIdentifierHigh The high byte of any packet identifier to modify. - * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag, - * a new packet identifier is generated and should be written here. This parameter - * is only used when connected to an AWS IoT MQTT server. - * - * @note See #IotMqttPublishInfo_t for caveats with retransmission to the - * AWS IoT MQTT server. - */ -void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, - uint8_t * pPacketIdentifierHigh, - uint16_t * pNewPacketIdentifier ); - -/** - * @brief Deserialize a PUBLISH packet received from the server. - * - * Converts the packet from a stream of bytes to an #IotMqttPublishInfo_t and - * extracts the packet identifier. Also prints out debug log messages about the - * packet. - * - * @param[in,out] pPublish Pointer to an MQTT packet struct representing a PUBLISH. - * - * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE - * if the PUBLISH packet doesn't follow MQTT spec. - */ -IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ); - -/** - * @brief Generate a PUBACK packet for the given packet identifier. - * - * @param[in] packetIdentifier The packet identifier to place in PUBACK. - * @param[out] pPubackPacket Where the PUBACK packet is written. - * @param[out] pPacketSize Size of the packet written to `pPubackPacket`. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ); - -/** - * @brief Deserialize a PUBACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in,out] pPuback Pointer to an MQTT packet struct representing a PUBACK. - * - * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE - * if the PUBACK packet doesn't follow MQTT spec. - */ -IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ); - -/** - * @brief Generate a SUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pSubscribePacket Where the SUBSCRIBE packet is written. - * @param[out] pPacketSize Size of the packet written to `pSubscribePacket`. - * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ); - -/** - * @brief Deserialize a SUBACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in,out] pSuback Pointer to an MQTT packet struct representing a SUBACK. - * - * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE - * if the SUBACK packet doesn't follow MQTT spec. - */ -IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ); - -/** - * @brief Generate an UNSUBSCRIBE packet from the given parameters. - * - * @param[in] pSubscriptionList User-provided array of subscriptions to remove. - * @param[in] subscriptionCount Size of `pSubscriptionList`. - * @param[out] pUnsubscribePacket Where the UNSUBSCRIBE packet is written. - * @param[out] pPacketSize Size of the packet written to `pUnsubscribePacket`. - * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pUnsubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ); - -/** - * @brief Deserialize a UNSUBACK packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts - * the packet identifier. Also prints out debug log messages about the packet. - * - * @param[in,out] pUnsuback Pointer to an MQTT packet struct representing an UNSUBACK. - * - * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE - * if the UNSUBACK packet doesn't follow MQTT spec. - */ -IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ); - -/** - * @brief Generate a PINGREQ packet. - * - * @param[out] pPingreqPacket Where the PINGREQ packet is written. - * @param[out] pPacketSize Size of the packet written to `pPingreqPacket`. - * - * @return Always returns #IOT_MQTT_SUCCESS. - */ -IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ); - -/** - * @brief Deserialize a PINGRESP packet. - * - * Converts the packet from a stream of bytes to an #IotMqttError_t. Also - * prints out debug log messages about the packet. - * - * @param[in,out] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. - * - * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE - * if the PINGRESP packet doesn't follow MQTT spec. - */ -IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ); - -/** - * @brief Generate a DISCONNECT packet. - * - * @param[out] pDisconnectPacket Where the DISCONNECT packet is written. - * @param[out] pPacketSize Size of the packet written to `pDisconnectPacket`. - * - * @return Always returns #IOT_MQTT_SUCCESS. - */ -IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, - size_t * pPacketSize ); - -/** - * @brief Free a packet generated by the serializer. - * - * @param[in] pPacket The packet to free. - */ -void _IotMqtt_FreePacket( uint8_t * pPacket ); - -/*-------------------- MQTT operation record functions ----------------------*/ - -/** - * @brief Create a record for a new in-progress MQTT operation. - * - * @param[in] pMqttConnection The MQTT connection to associate with the operation. - * @param[in] flags Flags variable passed to a user-facing MQTT function. - * @param[in] pCallbackInfo User-provided callback function and parameter. - * @param[out] pNewOperation Set to point to the new operation on success. - * - * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_PARAMETER, or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - _mqttOperation_t ** pNewOperation ); - -/** - * @brief Decrement the job reference count of an MQTT operation and optionally - * cancel its job. - * - * Checks if the operation may be destroyed afterwards. - * - * @param[in] pOperation The MQTT operation with the job to cancel. - * @param[in] cancelJob Whether to attempt cancellation of the operation's job. - * - * @return `true` if the the operation may be safely destroyed; `false` otherwise. - */ -bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, - bool cancelJob ); - -/** - * @brief Free resources used to record an MQTT operation. This is called when - * the operation completes. - * - * @param[in] pOperation The operation which completed. - */ -void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); - -/** - * @brief Task pool routine for processing an MQTT connection's keep-alive. - * - * @param[in] pTaskPool Pointer to the system task pool. - * @param[in] pKeepAliveJob Pointer the an MQTT connection's keep-alive job. - * @param[in] pContext Pointer to an MQTT connection, passed as an opaque context. - */ -void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pKeepAliveJob, - void * pContext ); - -/** - * @brief Task pool routine for processing an incoming PUBLISH message. - * - * @param[in] pTaskPool Pointer to the system task pool. - * @param[in] pPublishJob Pointer to the incoming PUBLISH operation's job. - * @param[in] pContext Pointer to the incoming PUBLISH operation, passed as an - * opaque context. - */ -void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pPublishJob, - void * pContext ); - -/** - * @brief Task pool routine for processing an MQTT operation to send. - * - * @param[in] pTaskPool Pointer to the system task pool. - * @param[in] pSendJob Pointer to an operation's job. - * @param[in] pContext Pointer to the operation to send, passed as an opaque - * context. - */ -void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pSendJob, - void * pContext ); - -/** - * @brief Task pool routine for processing a completed MQTT operation. - * - * @param[in] pTaskPool Pointer to the system task pool. - * @param[in] pOperationJob Pointer to the completed operation's job. - * @param[in] pContext Pointer to the completed operation, passed as an opaque - * context. - */ -void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pOperationJob, - void * pContext ); - -/** - * @brief Schedule an operation for immediate processing. - * - * @param[in] pOperation The operation to schedule. - * @param[in] jobRoutine The routine to run for the job. Must be either - * #_IotMqtt_ProcessSend, #_IotMqtt_ProcessCompletedOperation, or - * #_IotMqtt_ProcessIncomingPublish. - * @param[in] delay A delay before the operation job should be executed. Pass - * `0` to execute ASAP. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_SCHEDULING_ERROR. - */ -IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, - IotTaskPoolRoutine_t jobRoutine, - uint32_t delay ); - -/** - * @brief Search a list of MQTT operations pending responses using an operation - * name and packet identifier. Removes a matching operation from the list if found. - * - * @param[in] pMqttConnection The connection associated with the operation. - * @param[in] type The operation type to look for. - * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore. - * - * @return Pointer to any matching operation; `NULL` if no match was found. - */ -_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, - IotMqttOperationType_t type, - const uint16_t * pPacketIdentifier ); - -/** - * @brief Notify of a completed MQTT operation. - * - * @param[in] pOperation The MQTT operation which completed. - * - * Depending on the parameters passed to a user-facing MQTT function, the - * notification will cause @ref mqtt_function_wait to return or invoke a - * user-provided callback. - */ -void _IotMqtt_Notify( _mqttOperation_t * pOperation ); - -/*----------------- MQTT subscription management functions ------------------*/ - -/** - * @brief Add an array of subscriptions to the subscription manager. - * - * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. - * @param[in] subscribePacketIdentifier Packet identifier for the subscriptions' - * SUBSCRIBE packet. - * @param[in] pSubscriptionList The first element in the array. - * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. - */ -IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, - uint16_t subscribePacketIdentifier, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount ); - -/** - * @brief Process a received PUBLISH from the server, invoking any subscription - * callbacks that have a matching topic filter. - * - * @param[in] pMqttConnection The MQTT connection associated with the received - * PUBLISH. - * @param[in] pCallbackParam The parameter to pass to a PUBLISH callback. - */ -void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, - IotMqttCallbackParam_t * pCallbackParam ); - -/** - * @brief Remove a single subscription from the subscription manager by - * packetIdentifier and order. - * - * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. - * @param[in] packetIdentifier The packet identifier associated with the subscription's - * SUBSCRIBE packet. - * @param[in] order The order of the subscription in the SUBSCRIBE packet. - * Pass #MQTT_REMOVE_ALL_SUBSCRIPTIONS to ignore order and remove all subscriptions for `packetIdentifier`. - */ -void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier, - int32_t order ); - -/** - * @brief Remove an array of subscriptions from the subscription manager by - * topic filter. - * - * @param[in] pMqttConnection The MQTT connection associated with the subscriptions. - * @param[in] pSubscriptionList The first element in the array. - * @param[in] subscriptionCount Number of elements in `pSubscriptionList`. - */ -void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount ); - -/*------------------ MQTT connection management functions -------------------*/ - -/** - * @brief Attempt to increment the reference count of an MQTT connection. - * - * @param[in] pMqttConnection The referenced MQTT connection. - * - * @return `true` if the reference count was incremented; `false` otherwise. The - * reference count will not be incremented for a disconnected connection. - */ -bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection ); - -/** - * @brief Decrement the reference count of an MQTT connection. - * - * Also destroys an unreferenced MQTT connection. - * - * @param[in] pMqttConnection The referenced MQTT connection. - */ -void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ); - -/** - * @brief Read the next available byte on a network connection. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - * @param[out] pIncomingByte The byte read from the network. - * - * @return `true` if a byte was successfully received from the network; `false` - * otherwise. - */ -bool _IotMqtt_GetNextByte( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface, - uint8_t * pIncomingByte ); - -/** - * @brief Closes the network connection associated with an MQTT connection. - * - * A network disconnect function must be set in the network interface for the - * network connection to be closed. - * - * @param[in] disconnectReason A reason to pass to the connection's disconnect - * callback. - * @param[in] pMqttConnection The MQTT connection with the network connection - * to close. - */ -void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, - _mqttConnection_t * pMqttConnection ); - -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - -/** - * @brief Utility macro for creating serializer override selector functions - */ - #define SERIALIZER_OVERRIDE_SELECTOR( funcType_t, funcName, defaultFunc, serializerMember ) \ - static funcType_t funcName( const IotMqttSerializer_t * pSerializer ); \ - static funcType_t funcName( const IotMqttSerializer_t * pSerializer ) \ - { \ - funcType_t returnValue = defaultFunc; \ - if( pSerializer != NULL ) \ - { \ - if( pSerializer->serializerMember != NULL ) \ - { \ - returnValue = pSerializer->serializerMember; \ - } \ - } \ - \ - return returnValue; \ - } -#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - -#endif /* ifndef IOT_MQTT_INTERNAL_H_ */ diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h deleted file mode 100644 index d1eca6cd66..0000000000 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_test_access_mqtt.h - * @brief Declares the functions that provide access to the internal functions - * and variables of the MQTT library. - */ - -#ifndef IOT_TEST_ACCESS_MQTT_H_ -#define IOT_TEST_ACCESS_MQTT_H_ - -/*--------------------------- iot_mqtt_api.c ---------------------------*/ - -/** - * @brief Test access function for #_createMqttConnection. - * - * @see #_createMqttConnection. - */ -_mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds ); - -/** - * @brief Test access function for #_scheduleKeepAlive. - * - * @see #_scheduleKeepAlive. - */ -IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); - -/*-------------------------- iot_mqtt_network.c -------------------------*/ - -/** - * @brief Test access function for #_sendPuback. - * - * @see #_sendPuback. - */ -void IotTestMqtt_sendPuback( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier ); - -/*------------------------- iot_mqtt_serialize.c ------------------------*/ - -/* - * Macros for reading the high and low byte of a 2-byte unsigned int. - */ -#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ - -/** - * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. - * - * @param[in] ptr A uint8_t* that points to the high byte. - */ -#define UINT16_DECODE( ptr ) \ - ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ - ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) - -/*----------------------- iot_mqtt_subscription.c -----------------------*/ - -/* Internal data structures of iot_mqtt_subscription.c, redefined for the tests. */ -typedef struct _topicMatchParams -{ - const char * pTopicName; - uint16_t topicNameLength; - bool exactMatchOnly; -} _topicMatchParams_t; -typedef struct _packetMatchParams -{ - uint16_t packetIdentifier; - int32_t order; -} _packetMatchParams_t; - -/** - * @brief Test access function for #_topicMatch. - * - * @see #_topicMatch. - */ -bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -/** - * @brief Test access function for #_packetMatch. - * - * @see #_packetMatch. - */ -bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -#endif /* ifndef IOT_TEST_ACCESS_MQTT_H_ */ diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c deleted file mode 100644 index a349e9b19a..0000000000 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_test_access_mqtt_api.c - * @brief Provides access to the internal functions and variables of - * iot_mqtt_api.c - * - * This file should only be included at the bottom of iot_mqtt_api.c and never - * compiled by itself. - */ - -_mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds ); - -IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ); - -/*-----------------------------------------------------------*/ - -_mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, - const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds ) -{ - return _createMqttConnection( awsIotMqttMode, pNetworkInfo, keepAliveSeconds ); -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t IotTestMqtt_scheduleKeepAlive( IotMqttConnection_t pMqttConnection ) -{ - return _scheduleKeepAlive( pMqttConnection ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c deleted file mode 100644 index cfeda02ee7..0000000000 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_network.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_test_access_mqtt_network.c - * @brief Provides access to the internal functions and variables of - * iot_mqtt_network.c - * - * This file should only be included at the bottom of iot_mqtt_network.c - * and never compiled by itself. - */ - -void IotTestMqtt_sendPuback( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier ); - -/*-----------------------------------------------------------*/ - -void IotTestMqtt_sendPuback( _mqttConnection_t * pMqttConnection, - uint16_t packetIdentifier ) -{ - _sendPuback( pMqttConnection, packetIdentifier ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_subscription.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_subscription.c deleted file mode 100644 index f898764754..0000000000 --- a/libraries/standard/mqtt/test/access/iot_test_access_mqtt_subscription.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_test_access_mqtt_subscription.c - * @brief Provides access to the internal functions and variables of - * iot_mqtt_subscription.c - * - * This file should only be included at the bottom of iot_mqtt_subscription.c - * and never compiled by itself. - */ - -bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ); - -/*-----------------------------------------------------------*/ - -bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ) -{ - return _topicMatch( pSubscriptionLink, pMatch ); -} - -/*-----------------------------------------------------------*/ - -bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, - void * pMatch ) -{ - return _packetMatch( pSubscriptionLink, pMatch ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/iot_tests_mqtt.c b/libraries/standard/mqtt/test/iot_tests_mqtt.c deleted file mode 100644 index ef5762be01..0000000000 --- a/libraries/standard/mqtt/test/iot_tests_mqtt.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt.c - * @brief Test runner for MQTT tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the MQTT test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunMqttTests( bool disableNetworkTests, bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableLongTests; - - RUN_TEST_GROUP( MQTT_Unit_Subscription ); - RUN_TEST_GROUP( MQTT_Unit_Validate ); - RUN_TEST_GROUP( MQTT_Unit_Receive ); - RUN_TEST_GROUP( MQTT_Unit_Platform ); - RUN_TEST_GROUP( MQTT_Unit_API ); - - if( disableNetworkTests == false ) - { - RUN_TEST_GROUP( MQTT_System_Platform ); - RUN_TEST_GROUP( MQTT_System ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c deleted file mode 100644 index e5f5002e5a..0000000000 --- a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c +++ /dev/null @@ -1,414 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_mock.c - * @brief Implements functions to mock MQTT network responses. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* MQTT mock include. */ -#include "iot_tests_mqtt_mock.h" - -/* MQTT test access include. */ -#include "iot_test_access_mqtt.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief A delay that simulates the time required for an MQTT packet to be sent - * to the server and for the server to send a response. - */ -#define NETWORK_ROUND_TRIP_TIME_MS ( 25 ) - -/** - * @brief The maximum size of any MQTT acknowledgement packet (e.g. SUBACK, - * PUBACK, UNSUBACK) used in these tests. - */ -#define ACKNOWLEDGEMENT_PACKET_SIZE ( 5 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Context for calls to the network receive function. - */ -typedef struct _receiveContext -{ - const uint8_t * pData; /**< @brief The data to receive. */ - size_t dataLength; /**< @brief Length of data. */ - size_t dataIndex; /**< @brief Next byte of data to read. */ -} _receiveContext_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief The MQTT connection object shared among all the tests. - */ -static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief The #IotNetworkInterface_t to share among the tests. - */ -static IotNetworkInterface_t _networkInterface = { 0 }; - -/** - * @brief Timer used to simulate a response from the network. - */ -static IotTimer_t _receiveTimer; - -/** - * @brief Synchronizes the MQTT send and receive threads in these tests. - */ -static IotMutex_t _lastPacketMutex; - -/** - * @brief The type of the last packet sent by the send thread. - * - * Must be one of: PUBLISH, SUBSCRIBE, UNSUBSCRIBE. - */ -static uint8_t _lastPacketType = 0; - -/** - * @brief The packet identifier of the last packet send by the send thread. - */ -static uint16_t _lastPacketIdentifier = 0; - -/*-----------------------------------------------------------*/ - -/** - * @brief Invokes the MQTT receive callback to simulate a response received from - * the network. - */ -static void _receiveThread( void * pArgument ) -{ - uint8_t pReceivedData[ ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; - _receiveContext_t receiveContext = { 0 }; - - receiveContext.pData = pReceivedData; - receiveContext.dataLength = ACKNOWLEDGEMENT_PACKET_SIZE; - - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - /* Lock mutex to read and process the last packet sent. */ - IotMutex_Lock( &_lastPacketMutex ); - - /* Ensure that the last packet type and identifier were set. */ - IotTest_Assert( _lastPacketType != 0 ); - IotTest_Assert( _lastPacketIdentifier != 0 ); - - /* Set the packet identifier in the ACK packet. */ - pReceivedData[ 2 ] = UINT16_HIGH_BYTE( _lastPacketIdentifier ); - pReceivedData[ 3 ] = UINT16_LOW_BYTE( _lastPacketIdentifier ); - - /* Create the corresponding ACK packet based on the last packet type. */ - switch( _lastPacketType ) - { - case MQTT_PACKET_TYPE_PUBLISH: - - pReceivedData[ 0 ] = MQTT_PACKET_TYPE_PUBACK; - pReceivedData[ 1 ] = 2; - receiveContext.dataLength = 4; - break; - - case MQTT_PACKET_TYPE_SUBSCRIBE: - - pReceivedData[ 0 ] = MQTT_PACKET_TYPE_SUBACK; - pReceivedData[ 1 ] = 3; - pReceivedData[ 4 ] = 1; - receiveContext.dataLength = 5; - break; - - case MQTT_PACKET_TYPE_UNSUBSCRIBE: - - pReceivedData[ 0 ] = MQTT_PACKET_TYPE_UNSUBACK; - pReceivedData[ 1 ] = 2; - receiveContext.dataLength = 4; - break; - - default: - - /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and - * UNSUBSCRIBE. Abort if any other packet is found. */ - IotTest_Assert( 0 ); - } - - /* Call the MQTT receive callback to process the ACK packet. */ - IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, - _pMqttConnection ); - - IotMutex_Unlock( &_lastPacketMutex ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A send function that always "succeeds". It also sets the receive - * timer to respond with an ACK when necessary. - */ -static size_t _sendSuccess( IotNetworkConnection_t pNetworkConnection, - const uint8_t * pMessage, - size_t messageLength ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t deserializedPublish = { .link = { 0 } }; - _mqttPacket_t mqttPacket = { .u.pMqttConnection = NULL }; - _receiveContext_t receiveContext = { 0 }; - - /* Ignore the network connection. */ - ( void ) pNetworkConnection; - - /* Read the packet type, which is the first byte in the message. */ - mqttPacket.type = *pMessage; - - /* Set the members of the receive context. */ - receiveContext.pData = pMessage + 1; - receiveContext.dataLength = messageLength; - - /* Lock the mutex to modify the information on the last packet sent. */ - IotMutex_Lock( &_lastPacketMutex ); - - /* Read the remaining length. */ - mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ); - IotTest_Assert( mqttPacket.remainingLength != MQTT_REMAINING_LENGTH_INVALID ); - - /* Set the last packet type based on the outgoing message. */ - switch( mqttPacket.type & 0xf0 ) - { - case MQTT_PACKET_TYPE_PUBLISH: - - /* Only set the last packet type to PUBLISH for QoS 1. */ - if( ( ( *pMessage & 0x06 ) >> 1 ) == 1 ) - { - _lastPacketType = MQTT_PACKET_TYPE_PUBLISH; - } - else - { - _lastPacketType = 0; - _lastPacketIdentifier = 0; - } - - break; - - case ( MQTT_PACKET_TYPE_SUBSCRIBE & 0xf0 ): - _lastPacketType = MQTT_PACKET_TYPE_SUBSCRIBE; - break; - - case ( MQTT_PACKET_TYPE_UNSUBSCRIBE & 0xf0 ): - _lastPacketType = MQTT_PACKET_TYPE_UNSUBSCRIBE; - break; - - case ( MQTT_PACKET_TYPE_DISCONNECT & 0xf0 ): - _lastPacketType = 0; - _lastPacketIdentifier = 0; - break; - - default: - - /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, - * UNSUBSCRIBE and DISCONNECT. Abort if any other packet is found. */ - IotTest_Assert( 0 ); - } - - /* Check if a network response is needed. */ - if( _lastPacketType != 0 ) - { - /* Save the packet identifier as the last packet identifier. */ - if( _lastPacketType != MQTT_PACKET_TYPE_PUBLISH ) - { - _lastPacketIdentifier = UINT16_DECODE( receiveContext.pData + receiveContext.dataIndex ); - status = IOT_MQTT_SUCCESS; - } - else - { - mqttPacket.u.pIncomingPublish = &deserializedPublish; - mqttPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - mqttPacket.remainingLength ); - - status = _IotMqtt_DeserializePublish( &mqttPacket ); - _lastPacketIdentifier = mqttPacket.packetIdentifier; - } - - IotTest_Assert( status == IOT_MQTT_SUCCESS ); - IotTest_Assert( _lastPacketIdentifier != 0 ); - - /* Set the receive thread to run after a "network round-trip". */ - ( void ) IotClock_TimerArm( &_receiveTimer, - NETWORK_ROUND_TRIP_TIME_MS, - 0 ); - } - - IotMutex_Unlock( &_lastPacketMutex ); - - /* Return the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Simulates a network receive function. - */ -static size_t _receive( IotNetworkConnection_t pNetworkConnection, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - size_t bytesReceived = 0; - _receiveContext_t * pReceiveContext = ( _receiveContext_t * ) pNetworkConnection; - - IotTest_Assert( bytesRequested != 0 ); - IotTest_Assert( pReceiveContext->dataIndex < pReceiveContext->dataLength ); - - /* Calculate how much data to copy. */ - const size_t dataAvailable = pReceiveContext->dataLength - pReceiveContext->dataIndex; - - if( bytesRequested > dataAvailable ) - { - bytesReceived = dataAvailable; - } - else - { - bytesReceived = bytesRequested; - } - - /* Copy data into given buffer. */ - if( bytesReceived > 0 ) - { - ( void ) memcpy( pBuffer, - pReceiveContext->pData + pReceiveContext->dataIndex, - bytesReceived ); - - pReceiveContext->dataIndex += bytesReceived; - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) -{ - bool status = true; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - - /* Flags to track clean up */ - bool packetMutexCreated = false, timerCreated = false; - - /* Set the network interface send and receive functions. */ - ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); - _networkInterface.send = _sendSuccess; - _networkInterface.receive = _receive; - networkInfo.pNetworkInterface = &_networkInterface; - - /* Create the mutex that synchronizes the receive callback and send thread. */ - packetMutexCreated = IotMutex_Create( &_lastPacketMutex, false ); - - if( packetMutexCreated == false ) - { - status = false; - goto cleanup; - } - - /* Create the receive thread timer. */ - timerCreated = IotClock_TimerCreate( &_receiveTimer, - _receiveThread, - NULL ); - - if( timerCreated == false ) - { - status = false; - goto cleanup; - } - - /* Initialize the MQTT connection object. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( false, - &networkInfo, - 0 ); - - if( _pMqttConnection == NULL ) - { - status = false; - goto cleanup; - } - -cleanup: - - /* Clean up on error. */ - if( status == false ) - { - if( packetMutexCreated == true ) - { - IotMutex_Destroy( &_lastPacketMutex ); - } - - if( timerCreated == true ) - { - IotClock_TimerDestroy( &_receiveTimer ); - } - } - else - { - /* Set the output parameter. */ - *pMqttConnection = _pMqttConnection; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotTest_MqttMockCleanup( void ) -{ - /* Clean up the MQTT connection object. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Destroy the receive thread timer. */ - IotClock_TimerDestroy( &_receiveTimer ); - - /* Wait a short time for the timer thread to finish. */ - IotClock_SleepMs( NETWORK_ROUND_TRIP_TIME_MS * 2 ); - - /* Clear the last packet type and identifier. */ - IotMutex_Lock( &_lastPacketMutex ); - - _lastPacketType = 0; - _lastPacketIdentifier = 0; - - IotMutex_Unlock( &_lastPacketMutex ); - - /* Destroy the last packet mutex. */ - IotMutex_Destroy( &_lastPacketMutex ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.h b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.h deleted file mode 100644 index 5f913c5453..0000000000 --- a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_mock.h - * @brief Declares functions to mock MQTT network responses. - * - * The function in this file are not thread safe; only one MQTT operation - * should be mocked at any time. - */ - -#ifndef IOT_TESTS_MQTT_MOCK_H_ -#define IOT_TESTS_MQTT_MOCK_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* MQTT types include. */ -#include "types/iot_mqtt_types.h" - -/* Define an assertion function to use. */ -#ifndef IotTest_Assert - #include - #define IotTest_Assert assert -#endif - -/** - * @brief Initialize an MQTT connection to mock. - * - * @param[out] pMqttConnection Set to an MQTT connection handle on success. - * - * @return `true` if all initialization succeeded; `false` otherwise. - */ -bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ); - -/** - * @brief Clean up the MQTT connection mocking. - */ -void IotTest_MqttMockCleanup( void ); - -#endif /* ifndef IOT_TESTS_MQTT_MOCK_H_ */ diff --git a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c deleted file mode 100644 index a7c87af73a..0000000000 --- a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c +++ /dev/null @@ -1,1093 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_system.c - * @brief Full system tests for the MQTT library. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef IOT_TEST_MQTT_TIMEOUT_MS - #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) -#endif -#ifndef IOT_TEST_MQTT_CONNECT_RETRY_COUNT - #define IOT_TEST_MQTT_CONNECT_RETRY_COUNT ( 1 ) -#endif -#ifndef IOT_TEST_MQTT_PUBLISH_RETRY_COUNT - #define IOT_TEST_MQTT_PUBLISH_RETRY_COUNT ( 3 ) -#endif -/** @endcond */ - -#if IOT_TEST_MQTT_CONNECT_RETRY_COUNT < 1 - #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 1." -#endif -#if IOT_TEST_MQTT_PUBLISH_RETRY_COUNT < 0 - #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 0." -#endif - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false -#endif - -/** - * @brief The maximum length of an MQTT client identifier. - * - * When @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER is defined, this value must - * accommodate the length of @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER plus 4 - * to accommodate the Last Will and Testament test. Otherwise, this value is - * set to 24, which is the longest client identifier length an MQTT server is - * obligated to accept plus a NULL terminator. - */ -#ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) -#else - #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) -#endif - -/** - * @brief Generates a topic by suffixing the client identifier with a suffix. - * - * @param[in] bufferName The name of the buffer for the topic. - * @param[in] suffix The suffix to place at the end of the client identifier. - */ -#define GENERATE_TOPIC_WITH_SUFFIX( bufferName, suffix ) \ - char bufferName[ CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ) ] = { 0 }; \ - ( void ) snprintf( bufferName, \ - CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ), \ - "%s%s", \ - _pClientIdentifier, \ - suffix ); - - -/*-----------------------------------------------------------*/ - -/** - * @brief Parameter 1 of #_operationComplete. - */ -typedef struct _operationCompleteParams -{ - IotMqttOperationType_t expectedOperation; /**< @brief Expected completed operation. */ - IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ - IotMqttOperation_t operation; /**< @brief Reference to expected completed operation. */ -} _operationCompleteParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Network server info to share among the tests. - */ -static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - -/** - * @brief Network credential info to share among the tests. - */ -#if IOT_TEST_SECURED_CONNECTION == 1 - static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -#endif - -/** - * @brief An MQTT network setup parameter to share among the tests. - */ -static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - -#ifdef IOT_TEST_MQTT_SERIALIZER - -/** - * @brief Provide a pointer to a serializer that may be overridden. - */ - static const IotMqttSerializer_t * _pMqttSerializer = NULL; -#else - -/** - * @brief Function pointers to the default MQTT serializers. - */ - static const IotMqttSerializer_t _mqttSerializer = - { - .getPacketType = _IotMqtt_GetPacketType, - .getRemainingLength = _IotMqtt_GetRemainingLength, - .freePacket = _IotMqtt_FreePacket, - .serialize = - { - .connect = _IotMqtt_SerializeConnect, - .publish = _IotMqtt_SerializePublish, - .publishSetDup = _IotMqtt_PublishSetDup, - .puback = _IotMqtt_SerializePuback, - .subscribe = _IotMqtt_SerializeSubscribe, - .unsubscribe = _IotMqtt_SerializeUnsubscribe, - .pingreq = _IotMqtt_SerializePingreq, - .disconnect = _IotMqtt_SerializeDisconnect - }, - .deserialize = - { - .connack = _IotMqtt_DeserializeConnack, - .publish = _IotMqtt_DeserializePublish, - .puback = _IotMqtt_DeserializePuback, - .suback = _IotMqtt_DeserializeSuback, - .unsuback = _IotMqtt_DeserializeUnsuback, - .pingresp = _IotMqtt_DeserializePingresp - } - }; - -/** - * @brief The MQTT serializers to use in these tests. - */ - static const IotMqttSerializer_t * _pMqttSerializer = &_mqttSerializer; -#endif /* ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER */ - -/** - * @brief An MQTT connection to share among the tests. - */ -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief Filler text to publish. - */ -static const char _pSamplePayload[] = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " - "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " - "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" - " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" - " culpa qui officia deserunt mollit anim id est laborum." - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " - "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " - "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" - " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" - " culpa qui officia deserunt mollit anim id est laborum."; - -/** - * @brief Length of #_pSamplePayload. - */ -static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; - -/** - * @brief Buffer holding the client identifier used for the tests. - */ -static char _pClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - -/* - * Track if the serializer overrides were called for a test. - */ -static bool _freePacketOverride = false; /**< @brief Tracks if #_freePacket was called. */ -static bool _connectSerializerOverride = false; /**< @brief Tracks if #_connectSerializerOverride was called. */ -static bool _publishSerializerOverride = false; /**< @brief Tracks if #_publishSerializerOverride was called. */ -static bool _pubackSerializerOverride = false; /**< @brief Tracks if #_pubackSerializerOverride was called. */ -static bool _subscribeSerializerOverride = false; /**< @brief Tracks if #_subscribeSerializerOverride was called. */ -static bool _unsubscribeSerializerOverride = false; /**< @brief Tracks if #_unsubscribeSerializerOverride was called. */ -static bool _disconnectSerializerOverride = false; /**< @brief Tracks if #_disconnectSerializerOverride was called. */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Establish an MQTT connection. Retry if enabled. - */ -static IotMqttError_t _mqttConnect( const IotMqttNetworkInfo_t * pNetworkInfo, - const IotMqttConnectInfo_t * pConnectInfo, - uint32_t timeoutMs, - IotMqttConnection_t * const pMqttConnection ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - int32_t retryCount = 0; - - for( ; retryCount < IOT_TEST_MQTT_CONNECT_RETRY_COUNT; retryCount++ ) - { - status = IotMqtt_Connect( pNetworkInfo, pConnectInfo, timeoutMs, pMqttConnection ); - - #if ( IOT_TEST_MQTT_CONNECT_RETRY_COUNT > 1 ) - if( ( status == IOT_MQTT_NETWORK_ERROR ) || ( status == IOT_MQTT_TIMEOUT ) ) - { - /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. - * Initially wait until 1100 ms have elapsed since the last connection, then - * increase exponentially. */ - IotClock_SleepMs( 1100 << retryCount ); - } - else - { - break; - } - #endif - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Packet free function override - */ -static void _freePacket( uint8_t * pPacket ) -{ - _freePacketOverride = true; - - _pMqttSerializer->freePacket( pPacket ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for CONNECT. - */ -static IotMqttError_t _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - uint8_t ** pConnectPacket, - size_t * pPacketSize ) -{ - _connectSerializerOverride = true; - - return _pMqttSerializer->serialize.connect( pConnectInfo, - pConnectPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PUBLISH. - */ -static IotMqttError_t _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, - uint8_t ** pPublishPacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh ) -{ - _publishSerializerOverride = true; - - return _pMqttSerializer->serialize.publish( pPublishInfo, - pPublishPacket, - pPacketSize, - pPacketIdentifier, - pPacketIdentifierHigh ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PUBACK. - */ -static IotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) -{ - _pubackSerializerOverride = true; - - return _pMqttSerializer->serialize.puback( packetIdentifier, - pPubackPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for SUBSCRIBE. - */ -static IotMqttError_t _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ) -{ - _subscribeSerializerOverride = true; - - return _pMqttSerializer->serialize.subscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for UNSUBSCRIBE. - */ -static IotMqttError_t _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ) -{ - _unsubscribeSerializerOverride = true; - - return _pMqttSerializer->serialize.unsubscribe( pSubscriptionList, - subscriptionCount, - pSubscribePacket, - pPacketSize, - pPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for DISCONNECT. - */ -static IotMqttError_t _serializeDisconnect( uint8_t ** pDisconnectPacket, - size_t * pPacketSize ) -{ - _disconnectSerializerOverride = true; - - return _pMqttSerializer->serialize.disconnect( pDisconnectPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscription callback function. Checks for valid parameters and unblocks - * the main test thread. - */ -static void _publishReceived( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - - /* If the received messages matches what was sent, unblock the waiting thread. */ - if( ( pPublish->u.message.info.payloadLength == _samplePayloadLength ) && - ( strncmp( pPublish->u.message.info.pPayload, - _pSamplePayload, - _samplePayloadLength ) == 0 ) ) - { - IotSemaphore_Post( pWaitSem ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Operation completion callback function. Checks for valid parameters - * and unblocks the main test thread. - */ -static void _operationComplete( void * pArgument, - IotMqttCallbackParam_t * pOperation ) -{ - _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; - - /* If the operation information matches the parameters and the operation was - * successful, unblock the waiting thread. */ - if( ( pParams->expectedOperation == pOperation->u.operation.type ) && - ( pParams->operation == pOperation->u.operation.reference ) && - ( pOperation->u.operation.result == IOT_MQTT_SUCCESS ) ) - { - IotSemaphore_Post( &( pParams->waitSem ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Run the subscribe-publish-wait tests at various QoS. - */ -static void _subscribePublishWait( IotMqttQos_t qos ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t networkInfo = _networkInfo; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* The serializer must be static so that it remains in scope for the lifetime - * of the MQTT connection (after this function returns). */ - static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; - - /* Initialize the serializer on the first run, when any function pointer - * in the serializer is NULL. */ - if( serializer.freePacket == NULL ) - { - serializer = *_pMqttSerializer; - serializer.freePacket = _freePacket; - serializer.serialize.connect = _serializeConnect; - serializer.serialize.publish = _serializePublish; - serializer.serialize.puback = _serializePuback; - serializer.serialize.subscribe = _serializeSubscribe; - serializer.serialize.unsubscribe = _serializeUnsubscribe; - serializer.serialize.disconnect = _serializeDisconnect; - } - - /* Set the serializer function pointers. */ - networkInfo.pMqttSerializer = &serializer; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/SubscribePublishWait" ); - - /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Set the members of the MQTT connection info. */ - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - /* Establish the MQTT connection. */ - status = _mqttConnect( &networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Set the members of the subscription. */ - subscription.qos = qos; - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = _publishReceived; - subscription.callback.pCallbackContext = &waitSem; - - /* Subscribe to the test topic filter using the blocking SUBSCRIBE - * function. */ - status = IotMqtt_SubscribeSync( _mqttConnection, - &subscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Set the members of the publish info. */ - publishInfo.qos = qos; - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; - publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; - - /* Publish the message. */ - status = IotMqtt_PublishSync( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - - /* Wait for the message to be received. */ - if( IotSemaphore_TimedWait( &waitSem, - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); - } - - /* Unsubscribe from the test topic filter. */ - status = IotMqtt_UnsubscribeSync( _mqttConnection, - &subscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - } - - /* Close the MQTT connection. */ - IotMqtt_Disconnect( _mqttConnection, 0 ); - } - - IotSemaphore_Destroy( &waitSem ); - - /* Check that the serializer overrides were called. */ - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, _freePacketOverride ); - TEST_ASSERT_EQUAL_INT( true, _connectSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _publishSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _subscribeSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _unsubscribeSerializerOverride ); - TEST_ASSERT_EQUAL_INT( true, _disconnectSerializerOverride ); - - if( qos == IOT_MQTT_QOS_1 ) - { - TEST_ASSERT_EQUAL_INT( true, _pubackSerializerOverride ); - } - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT system tests. - */ -TEST_GROUP( MQTT_System ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT system tests. - */ -TEST_SETUP( MQTT_System ) -{ - /* Clear the serializer override flags. */ - _freePacketOverride = false; - _connectSerializerOverride = false; - _publishSerializerOverride = false; - _pubackSerializerOverride = false; - _subscribeSerializerOverride = false; - _unsubscribeSerializerOverride = false; - _disconnectSerializerOverride = false; - - /* Initialize SDK. */ - if( IotSdk_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); - } - - /* Call the network stack initialization function. */ - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize network stack." ); - } - - /* Initialize the MQTT library. */ - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - - /* Generate a new, unique client identifier based on the time if no client - * identifier is defined. Otherwise, copy the provided client identifier. */ - #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER - ( void ) snprintf( _pClientIdentifier, - CLIENT_IDENTIFIER_MAX_LENGTH, - "iot%llu", - ( long long unsigned int ) IotClock_GetTimeMs() ); - #else - ( void ) strncpy( _pClientIdentifier, - IOT_TEST_MQTT_CLIENT_IDENTIFIER, - CLIENT_IDENTIFIER_MAX_LENGTH ); - #endif - - /* Set the overrides for the default serializers. */ - #ifdef IOT_TEST_MQTT_SERIALIZER - _pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; - #endif - - /* Set the MQTT network setup parameters. */ - ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - _networkInfo.createNetworkConnection = true; - _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - _networkInfo.pMqttSerializer = _pMqttSerializer; - - #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; - #endif -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT system tests. - */ -TEST_TEAR_DOWN( MQTT_System ) -{ - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); - - /* Clean up the network stack. */ - IotTestNetwork_Cleanup(); - - /* Clean up SDK. */ - IotSdk_Cleanup(); - - /* Clear the connection pointer. */ - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT system tests. - */ -TEST_GROUP_RUNNER( MQTT_System ) -{ - RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS0 ); - RUN_TEST_CASE( MQTT_System, SubscribePublishWaitQoS1 ); - RUN_TEST_CASE( MQTT_System, SubscribePublishAsync ); - RUN_TEST_CASE( MQTT_System, LastWillAndTestament ); - RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); - RUN_TEST_CASE( MQTT_System, WaitAfterDisconnect ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscribe-publish-wait (QoS 0). - */ -TEST( MQTT_System, SubscribePublishWaitQoS0 ) -{ - _subscribePublishWait( IOT_MQTT_QOS_0 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscribe-publish-wait (QoS 1). - */ -TEST( MQTT_System, SubscribePublishWaitQoS1 ) -{ - _subscribePublishWait( IOT_MQTT_QOS_1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscribe-publish asynchronous (QoS 1). - */ -TEST( MQTT_System, SubscribePublishAsync ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { .expectedOperation = ( IotMqttOperationType_t ) 0 }; - IotSemaphore_t publishWaitSem; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/SubscribePublishAsync" ); - - /* Initialize members of the operation callback info. */ - callbackInfo.function = _operationComplete; - callbackInfo.pCallbackContext = &callbackParam; - - /* Initialize members of the subscription. */ - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = _publishReceived; - subscription.callback.pCallbackContext = &publishWaitSem; - - /* Initialize members of the connect info. */ - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - /* Initialize members of the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - - /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Create the wait semaphore for subscriptions. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &publishWaitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Establish the MQTT connection. */ - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Subscribe to the test topic filter. */ - callbackParam.expectedOperation = IOT_MQTT_SUBSCRIBE; - status = IotMqtt_SubscribeAsync( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for SUBSCRIBE to complete." ); - } - - /* Publish the message. */ - callbackParam.expectedOperation = IOT_MQTT_PUBLISH_TO_SERVER; - status = IotMqtt_PublishAsync( _mqttConnection, - &publishInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for PUBLISH to complete." ); - } - - /* Wait for the message to be received. */ - if( IotSemaphore_TimedWait( &publishWaitSem, - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for subscription." ); - } - - /* Unsubscribe from the test topic filter. */ - callbackParam.expectedOperation = IOT_MQTT_UNSUBSCRIBE; - status = IotMqtt_UnsubscribeAsync( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for UNSUBSCRIBE to complete." ); - } - } - - IotMqtt_Disconnect( _mqttConnection, 0 ); - } - - IotSemaphore_Destroy( &publishWaitSem ); - } - - IotSemaphore_Destroy( &( callbackParam.waitSem ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that a LWT is published if an MQTT connection is unexpectedly closed. - */ -TEST( MQTT_System, LastWillAndTestament ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttNetworkInfo_t lwtNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - char pLwtListenerClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - IotMqttConnection_t lwtListener = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttConnectInfo_t lwtConnectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER, - connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t willSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/LastWillAndTestament" ); - - /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - /* Generate a client identifier for LWT listener. */ - #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER - ( void ) snprintf( pLwtListenerClientIdentifier, - CLIENT_IDENTIFIER_MAX_LENGTH, - "iotlwt%llu", - ( long long unsigned int ) IotClock_GetTimeMs() ); - #else - ( void ) strncpy( pLwtListenerClientIdentifier, - IOT_TEST_MQTT_CLIENT_IDENTIFIER "LWT", - CLIENT_IDENTIFIER_MAX_LENGTH ); - #endif - - /* Establish an independent MQTT over TCP connection to receive a Last - * Will and Testament message. */ - lwtNetworkInfo.createNetworkConnection = true; - lwtNetworkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - - #if IOT_TEST_SECURED_CONNECTION == 1 - lwtNetworkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; - #endif - - lwtNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - - lwtConnectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - lwtConnectInfo.cleanSession = true; - lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; - lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); - - if( TEST_PROTECT() ) - { - status = _mqttConnect( &lwtNetworkInfo, - &lwtConnectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &lwtListener ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Register a subscription for the LWT. */ - willSubscription.pTopicFilter = pTopic; - willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); - willSubscription.callback.function = _publishReceived; - willSubscription.callback.pCallbackContext = &waitSem; - - status = IotMqtt_SubscribeSync( lwtListener, - &willSubscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Create a connection that requests the LWT. */ - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - connectInfo.pWillInfo = &willInfo; - - willInfo.pTopicName = pTopic; - willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); - willInfo.pPayload = _pSamplePayload; - willInfo.payloadLength = _samplePayloadLength; - - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Abruptly close the MQTT connection. This should cause the LWT - * to be sent to the LWT listener. */ - IotMqtt_Disconnect( _mqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Check that the LWT was received. */ - if( IotSemaphore_TimedWait( &waitSem, - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for Last Will and Testament." ); - } - } - - IotMqtt_Disconnect( lwtListener, 0 ); - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that subscriptions from a previous session are restored. - */ -TEST( MQTT_System, RestorePreviousSession ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/RestorePreviousSession" ); - - /* Create the wait semaphore for operations. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - /* Set the members of the connection info for a persistent session. */ - connectInfo.cleanSession = false; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - if( TEST_PROTECT() ) - { - /* Establish a persistent MQTT connection. */ - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Add a subscription. */ - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.pCallbackContext = &waitSem; - subscription.callback.function = _publishReceived; - - status = IotMqtt_SubscribeSync( _mqttConnection, - &subscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Disconnect the MQTT connection and clean up network connection. */ - IotMqtt_Disconnect( _mqttConnection, 0 ); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - /* Re-establish the MQTT connection with a previous session. */ - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.cleanSession = false; - connectInfo.pPreviousSubscriptions = &subscription; - connectInfo.previousSubscriptionCount = 1; - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Publish a message to the subscription added in the previous session. */ - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - - status = IotMqtt_PublishSync( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Wait for the message to be received. */ - if( IotSemaphore_TimedWait( &waitSem, - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for message." ); - } - - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _mqttConnection, 0 ); - } - else - { - /* Close MQTT connection on test failure. */ - if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) - { - IotMqtt_Disconnect( _mqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - } - } - - IotSemaphore_Destroy( &waitSem ); - - if( TEST_PROTECT() ) - { - /* After this test is finished, establish one more connection with a clean - * session to clean up persistent sessions on the MQTT server created by this - * test. */ - connectInfo.cleanSession = true; - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - - if( status == IOT_MQTT_SUCCESS ) - { - IotMqtt_Disconnect( _mqttConnection, 0 ); - } - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that Wait can be safely invoked after Disconnect. - */ -TEST( MQTT_System, WaitAfterDisconnect ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttOperation_t pPublishOperation[ 3 ] = { IOT_MQTT_OPERATION_INITIALIZER }; - - /* Set the client identifier and length. */ - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/WaitAfterDisconnect" ); - - /* Set the members of the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; - publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; - - /* Establish the MQTT connection. */ - status = _mqttConnect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - if( TEST_PROTECT() ) - { - /* Publish a sequence of messages. */ - for( i = 0; i < 3; i++ ) - { - status = IotMqtt_PublishAsync( _mqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &( pPublishOperation[ i ] ) ); - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); - } - } - - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _mqttConnection, 0 ); - - if( TEST_PROTECT() ) - { - /* Waiting on operations after a connection is disconnected should not crash. - * The actual statuses of the PUBLISH operations may vary depending on the - * timing of publish versus disconnect, so the statuses are not checked. */ - for( i = 0; i < 3; i++ ) - { - ( void ) IotMqtt_Wait( pPublishOperation[ i ], 100 ); - } - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c deleted file mode 100644 index ca963f9783..0000000000 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ /dev/null @@ -1,2968 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_api.c - * @brief Tests for the user-facing API functions (declared in iot_mqtt.h). - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* MQTT protocol include. */ -#include "iot_mqtt_protocol.h" - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* MQTT test access include. */ -#include "iot_test_access_mqtt.h" - -/* MQTT lightweight API include */ -#include "iot_mqtt_lightweight.h" - -/* MQTT mock include. */ -#include "iot_tests_mqtt_mock.h" - -/* Atomics include. */ -#include "iot_atomic.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false -#endif - -/** - * @brief Timeout to use for the tests. This can be short, but should allow time - * for other threads to run. - */ -#define TIMEOUT_MS ( 400 ) - -/** - * @brief A short keep-alive interval to use for the keep-alive tests. It may be - * shorter than the minimum 1 second specified by the MQTT spec. - */ -#define SHORT_KEEP_ALIVE_MS ( 100 ) - -/** - * @brief The number of times the periodic keep-alive should run. - */ -#define KEEP_ALIVE_COUNT ( 10 ) - -/* - * Client identifier and length to use for the MQTT API tests. - */ -#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ -#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ - -/* - * Will topic name and length to use for the MQTT API tests. - */ -#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ -#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ - -/** - * @brief A non-NULL function pointer to use for subscription callback. This - * "function" should cause a crash if actually called. - */ -#define SUBSCRIPTION_CALLBACK \ - ( ( void ( * )( void *, \ - IotMqttCallbackParam_t * ) ) 0x01 ) - -/** - * @brief How many times #TEST_MQTT_Unit_API_DisconnectMallocFail_ will test - * malloc failures. - * - * The DISCONNECT function provides no mechanism to wait on its successful - * completion. Therefore, this function simply uses the value below as an estimate - * for the maximum number of times DISCONNECT will use malloc. - */ -#define DISCONNECT_MALLOC_LIMIT ( 20 ) - -/* - * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. - */ -#define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ -#define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ -#define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. - * Duplicates are sent using an exponential backoff strategy. */ -/** @brief The minimum amount of time the test can take. */ -#define DUP_CHECK_MINIMUM_WAIT \ - ( DUP_CHECK_RETRY_MS + \ - 2 * DUP_CHECK_RETRY_MS + \ - 4 * DUP_CHECK_RETRY_MS + \ - IOT_MQTT_RESPONSE_WAIT_MS ) - -/** - * @brief Length of an arbitrary packet for testing. A buffer will be allocated - * for it, but its contents don't matter. - */ -#define PACKET_LENGTH ( 32 ) - -/** - * @brief How many operations to use for the OperationFindMatch test. - */ -#define OPERATION_COUNT ( 2 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Tracks whether #_publishSetDup has been called. - */ -static bool _publishSetDupCalled = false; - -/** - * @brief Counts how many time #_sendPingreq has been called. - */ -static uint32_t _pingreqSendCount = 0; - -/** - * @brief Counts how many times #_close has been called. - */ -static uint32_t _closeCount = 0; - -/** - * @brief Counts how many times #_disconnectCallback has been called. - */ -static uint32_t _disconnectCallbackCount = 0; - -/** - * @brief An MQTT connection to share among the tests. - */ -static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief An #IotMqttNetworkInfo_t to share among the tests. - */ -static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - -/** - * @brief An #IotNetworkInterface_t to share among the tests. - */ -static IotNetworkInterface_t _networkInterface = { 0 }; - -/** - * @brief A packet allocated by _serializePingreq. - */ -static uint8_t * _pAllocatedPingreq = NULL; - -/*-----------------------------------------------------------*/ - -/** - * @brief A thread routine that simulates an incoming PINGRESP. - */ -static void _incomingPingresp( void * pArgument ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - /* This test will not work if the response wait time is too small. */ - #if IOT_MQTT_RESPONSE_WAIT_MS < ( 2 * SHORT_KEEP_ALIVE_MS + 100 ) - #error "IOT_MQTT_RESPONSE_WAIT_MS too small for keep-alive tests." - #endif - - static int32_t invokeCount = 0; - static uint64_t lastInvokeTime = 0; - uint64_t currentTime = IotClock_GetTimeMs(); - - /* Increment invoke count for this function. */ - invokeCount++; - - /* Sleep to simulate the network round-trip time. */ - IotClock_SleepMs( 2 * SHORT_KEEP_ALIVE_MS ); - - /* Respond with a PINGRESP. */ - if( invokeCount <= KEEP_ALIVE_COUNT ) - { - /* Log a status with Unity, as this test may take a while. */ - UnityPrint( "KeepAlivePeriodic " ); - UnityPrintNumber( ( UNITY_INT ) invokeCount ); - UnityPrint( " of " ); - UnityPrintNumber( ( UNITY_INT ) KEEP_ALIVE_COUNT ); - UnityPrint( " DONE at " ); - UnityPrintNumber( ( UNITY_INT ) IotClock_GetTimeMs() ); - UnityPrint( " ms" ); - - if( invokeCount > 1 ) - { - UnityPrint( " (+" ); - UnityPrintNumber( ( UNITY_INT ) ( currentTime - lastInvokeTime ) ); - UnityPrint( " ms)." ); - } - else - { - UnityPrint( "." ); - } - - UNITY_PRINT_EOL(); - lastInvokeTime = currentTime; - - IotMqtt_ReceiveCallback( NULL, - _pMqttConnection ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief PUBLISH set DUP function override. - */ -static void _publishSetDup( uint8_t * pPublishPacket, - uint8_t * pPacketIdentifierHigh, - uint16_t * pNewPacketIdentifier ) -{ - _publishSetDupCalled = true; - - _IotMqtt_PublishSetDup( pPublishPacket, - pPacketIdentifierHigh, - pNewPacketIdentifier ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A send function that always "succeeds". May report that it was invoked - * through a semaphore. - */ -static size_t _sendSuccess( IotNetworkConnection_t pSendContext, - const uint8_t * pMessage, - size_t messageLength ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pSendContext; - - /* Silence warnings about unused parameters. */ - ( void ) pMessage; - - /* Post to the wait semaphore if given. */ - if( pWaitSem != NULL ) - { - IotSemaphore_Post( pWaitSem ); - - /* Yield the processor to context switch. */ - IotClock_SleepMs( 10 ); - } - - /* This function returns the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A send function for PINGREQ that responds with a PINGRESP. - */ -static size_t _sendPingreq( IotNetworkConnection_t pSendContext, - const uint8_t * pMessage, - size_t messageLength ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pSendContext; - ( void ) pMessage; - - /* Create a thread that responds with PINGRESP, then increment the PINGREQ - * send counter if successful. */ - if( Iot_CreateDetachedThread( _incomingPingresp, - NULL, - IOT_THREAD_DEFAULT_PRIORITY, - IOT_THREAD_DEFAULT_STACK_SIZE ) == true ) - { - _pingreqSendCount++; - } - - /* This function returns the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A send function that delays. - */ -static size_t _sendDelay( IotNetworkConnection_t pSendContext, - const uint8_t * pMessage, - size_t messageLength ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pSendContext; - - /* Silence warnings about unused parameters. */ - ( void ) pMessage; - - /* Post to the wait semaphore. */ - IotSemaphore_Post( pWaitSem ); - - /* Delay for 2 seconds. */ - IotClock_SleepMs( 2000 ); - - /* This function returns the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief This send function checks that a duplicate outgoing message differs from - * the original. - */ -static size_t _dupChecker( IotNetworkConnection_t pSendContext, - const uint8_t * pMessage, - size_t messageLength ) -{ - static int32_t runCount = 0; - static bool status = true; - bool * pDupCheckResult = ( bool * ) pSendContext; - uint8_t publishFlags = *pMessage; - - /* Declare the remaining variables required to check packet identifier - * for the AWS IoT MQTT server. */ - #if AWS_IOT_MQTT_SERVER == true - static uint16_t lastPacketIdentifier = 0; - _mqttPacket_t publishPacket = { .u.pMqttConnection = NULL }; - _mqttOperation_t publishOperation = { .link = { 0 } }; - - publishPacket.type = publishFlags; - publishPacket.u.pIncomingPublish = &publishOperation; - publishPacket.remainingLength = 8 + TEST_TOPIC_NAME_LENGTH; - publishPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - publishPacket.remainingLength ); - #endif - - /* Ignore any MQTT packet that's not a PUBLISH. */ - if( ( publishFlags & 0xf0 ) != MQTT_PACKET_TYPE_PUBLISH ) - { - return messageLength; - } - - runCount++; - - /* Check how many times this function has been called. */ - if( runCount == 1 ) - { - #if AWS_IOT_MQTT_SERVER == true - /* Deserialize the PUBLISH to read the packet identifier. */ - if( _IotMqtt_DeserializePublish( &publishPacket ) != IOT_MQTT_SUCCESS ) - { - status = false; - } - else - { - lastPacketIdentifier = publishPacket.packetIdentifier; - } - #else /* if AWS_IOT_MQTT_SERVER == true */ - /* DUP flag should not be set on this function's first run. */ - if( ( publishFlags & 0x08 ) == 0x08 ) - { - status = false; - } - #endif /* if AWS_IOT_MQTT_SERVER == true */ - } - else - { - /* Only check the packet again if the previous run checks passed. */ - if( status == true ) - { - #if AWS_IOT_MQTT_SERVER == true - /* Deserialize the PUBLISH to read the packet identifier. */ - if( _IotMqtt_DeserializePublish( &publishPacket ) != IOT_MQTT_SUCCESS ) - { - status = false; - } - else - { - /* Check that the packet identifier is different. */ - status = ( publishPacket.packetIdentifier != lastPacketIdentifier ); - lastPacketIdentifier = publishPacket.packetIdentifier; - } - #else /* if AWS_IOT_MQTT_SERVER == true */ - /* DUP flag should be set when this function runs again. */ - if( ( publishFlags & 0x08 ) != 0x08 ) - { - status = false; - } - #endif /* if AWS_IOT_MQTT_SERVER == true */ - } - - /* Write the check result on the last expected run of this function. */ - if( runCount == DUP_CHECK_RETRY_LIMIT ) - { - *pDupCheckResult = status; - } - } - - /* Return the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A network receive function that simulates receiving a PINGRESP. - */ -static size_t _receivePingresp( IotNetworkConnection_t pReceiveContext, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - size_t bytesReceived = 0; - static size_t receiveIndex = 0; - const uint8_t pPingresp[ 2 ] = { MQTT_PACKET_TYPE_PINGRESP, 0x00 }; - - /* Silence warnings about unused parameters. */ - ( void ) pReceiveContext; - - /* Receive of PINGRESP should only ever request 1 byte. */ - if( bytesRequested == 1 ) - { - /* Write a byte of PINGRESP. */ - *pBuffer = pPingresp[ receiveIndex ]; - bytesReceived = 1; - - /* Alternate the byte of PINGRESP to write. */ - receiveIndex = ( receiveIndex + 1 ) % 2; - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A function for setting the receive callback that just returns success. - */ -static IotNetworkError_t _setReceiveCallback( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pReceiveContext ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pConnection; - ( void ) receiveCallback; - ( void ) pReceiveContext; - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A network close function that counts how many times it was invoked. - */ -static IotNetworkError_t _close( IotNetworkConnection_t pCloseContext ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pCloseContext; - - Atomic_Increment_u32( &_closeCount ); - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief An MQTT disconnect callback that counts how many times it was invoked. - */ -static void _disconnectCallback( void * pCallbackContext, - IotMqttCallbackParam_t * pCallbackParam ) -{ - IotMqttDisconnectReason_t * pExpectedReason = ( IotMqttDisconnectReason_t * ) pCallbackContext; - - /* Only increment counter if the reasons match. */ - if( pCallbackParam->u.disconnectReason == *pExpectedReason ) - { - Atomic_Increment_u32( &_disconnectCallbackCount ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A task pool job routine that decrements an MQTT operation's job - * reference count. - */ -static void _decrementReferencesJob( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pContext ) -{ - _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; - - /* Silence warnings about unused parameters. */ - ( void ) pTaskPool; - ( void ) pJob; - - /* Decrement an operation's reference count. */ - if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == false ) - { - /* Unblock the main test thread. */ - IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Get next byte mock function to test MQTT serializer API. - */ -static IotMqttError_t _getNextByte( IotNetworkConnection_t pNetworkInterface, - uint8_t * nextByte ) -{ - uint8_t * buffer; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Treat network interface as pointer to buffer for mocking */ - /* Send next byte */ - if( ( pNetworkInterface != NULL ) && ( nextByte != NULL ) ) - { - buffer = ( *( uint8_t ** ) pNetworkInterface ); - /* read single byte */ - *nextByte = *buffer; - /* Move stream by 1 byte */ - ( *( uint8_t ** ) pNetworkInterface ) = ++buffer; - } - else - { - status = IOT_MQTT_NETWORK_ERROR; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Get next byte mock function to test MQTT serializer API that fails when - * reading the remaining length. - */ -static IotMqttError_t _getNextByteFailure( IotNetworkConnection_t pNetworkInterface, - uint8_t * nextByte ) -{ - IotMqttError_t status = IOT_MQTT_NETWORK_ERROR; - static int32_t invokeCount = 0; - - ( void ) pNetworkInterface; - ( void ) nextByte; - - /* Return a valid packet type on the first invocation. */ - if( invokeCount == 0 ) - { - status = IOT_MQTT_SUCCESS; - *nextByte = MQTT_PACKET_TYPE_CONNACK; - } - - invokeCount++; - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A PINGREQ serializer that attempts to allocate memory (unlike the default). - */ -static IotMqttError_t _serializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - - TEST_ASSERT_NULL( _pAllocatedPingreq ); - _pAllocatedPingreq = IotTest_Malloc( PACKET_LENGTH ); - - if( _pAllocatedPingreq != NULL ) - { - *_pAllocatedPingreq = MQTT_PACKET_TYPE_PINGREQ; - *pPingreqPacket = _pAllocatedPingreq; - *pPacketSize = PACKET_LENGTH; - } - else - { - status = IOT_MQTT_NO_MEMORY; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A completion callback that does nothing. - */ -static void _completionCallback( void * pContext, - IotMqttCallbackParam_t * pCallbackParam ) -{ - ( void ) pContext; - ( void ) pCallbackParam; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT API tests. - */ -TEST_GROUP( MQTT_Unit_API ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT API tests. - */ -TEST_SETUP( MQTT_Unit_API ) -{ - _publishSetDupCalled = false; - _pingreqSendCount = 0; - - /* Reset the network info and interface. */ - ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); - _networkInterface.setReceiveCallback = _setReceiveCallback; - _networkInfo.pNetworkInterface = &_networkInterface; - - /* Reset the counters. */ - _pingreqSendCount = 0; - _closeCount = 0; - _disconnectCallbackCount = 0; - - /* Initialize libraries. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT API tests. - */ -TEST_TEAR_DOWN( MQTT_Unit_API ) -{ - IotMqtt_Cleanup(); - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT API tests. - */ -TEST_GROUP_RUNNER( MQTT_Unit_API ) -{ - RUN_TEST_CASE( MQTT_Unit_API, Init ); - RUN_TEST_CASE( MQTT_Unit_API, StringCoverage ); - RUN_TEST_CASE( MQTT_Unit_API, OperationCreateDestroy ); - RUN_TEST_CASE( MQTT_Unit_API, OperationWaitTimeout ); - RUN_TEST_CASE( MQTT_Unit_API, OperationFindMatch ); - RUN_TEST_CASE( MQTT_Unit_API, OperationLists ); - RUN_TEST_CASE( MQTT_Unit_API, ConnectParameters ); - RUN_TEST_CASE( MQTT_Unit_API, ConnectMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, ConnectRestoreSessionMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, DisconnectMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, DisconnectAlreadyDisconnected ); - RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0Parameters ); - RUN_TEST_CASE( MQTT_Unit_API, PublishQoS0MallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, PublishQoS1 ); - RUN_TEST_CASE( MQTT_Unit_API, PublishRetryPeriod ); - RUN_TEST_CASE( MQTT_Unit_API, PublishDuplicates ); - RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); - RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, UnsubscribeMallocFail ); - RUN_TEST_CASE( MQTT_Unit_API, KeepAlivePeriodic ); - RUN_TEST_CASE( MQTT_Unit_API, KeepAliveJobCleanup ); - RUN_TEST_CASE( MQTT_Unit_API, GetConnectPacketSizeChecks ); - RUN_TEST_CASE( MQTT_Unit_API, SerializeConnectChecks ); - RUN_TEST_CASE( MQTT_Unit_API, GetSubscribePacketSizeChecks ); - RUN_TEST_CASE( MQTT_Unit_API, SerializeSubscribeChecks ); - RUN_TEST_CASE( MQTT_Unit_API, SerializeUnsubscribeChecks ); - RUN_TEST_CASE( MQTT_Unit_API, GetPublishPacketSizeChecks ); - RUN_TEST_CASE( MQTT_Unit_API, SerializePublishChecks ); - RUN_TEST_CASE( MQTT_Unit_API, SerializeDisconnectChecks ); - RUN_TEST_CASE( MQTT_Unit_API, SerializePingReqChecks ); - RUN_TEST_CASE( MQTT_Unit_API, LightweightConnack ); - RUN_TEST_CASE( MQTT_Unit_API, LightweightSuback ); - RUN_TEST_CASE( MQTT_Unit_API, LightweightUnsuback ); - RUN_TEST_CASE( MQTT_Unit_API, LightweightPingresp ); - RUN_TEST_CASE( MQTT_Unit_API, LightweightPuback ); - RUN_TEST_CASE( MQTT_Unit_API, DeserializePublishChecks ); - RUN_TEST_CASE( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the function @ref mqtt_function_init. - */ -TEST( MQTT_Unit_API, Init ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Initialization was done in test set up. Clean up here before running this test. */ - IotMqtt_Cleanup(); - - /* Calling cleanup twice should not crash. */ - IotMqtt_Cleanup(); - - /* Calling API functions without calling IotMqtt_Init should fail. */ - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); - - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - status = IotMqtt_SubscribeAsync( _pMqttConnection, &subscription, 1, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); - - status = IotMqtt_UnsubscribeAsync( _pMqttConnection, &subscription, 1, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); - - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); - - status = IotMqtt_Wait( operation, TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); - - IotMqtt_Disconnect( _pMqttConnection, 0 ); - - /* Reinitialize for test cleanup. Calling init twice should not crash. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Provides code coverage of the MQTT enum-to-string functions, - * @ref mqtt_function_strerror and @ref mqtt_function_operationtype. - */ -TEST( MQTT_Unit_API, StringCoverage ) -{ - int32_t i = 0; - const char * pMessage = NULL; - - /* For each MQTT Error, check the returned string. */ - const char * pExitString = "INVALID STATUS"; - size_t exitStringLength = strlen( pExitString ); - - while( true ) - { - pMessage = IotMqtt_strerror( ( IotMqttError_t ) i ); - TEST_ASSERT_NOT_NULL( pMessage ); - - if( strncmp( pExitString, pMessage, exitStringLength ) == 0 ) - { - break; - } - - i++; - } - - /* For each MQTT Operation Type, check the returned string. */ - i = 0; - pExitString = "INVALID OPERATION"; - exitStringLength = strlen( pExitString ); - - while( true ) - { - pMessage = IotMqtt_OperationType( ( IotMqttOperationType_t ) i ); - TEST_ASSERT_NOT_NULL( pMessage ); - - if( strncmp( pExitString, pMessage, exitStringLength ) == 0 ) - { - break; - } - - i++; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test reference counts as MQTT operations are created and destroyed. - */ -TEST( MQTT_Unit_API, OperationCreateDestroy ) -{ - _mqttOperation_t * pOperation = NULL; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Adjustment to reference count based on keep-alive status. */ - const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) ? 1 : 0 ); - - /* A new MQTT connection should only have a possible reference for keep-alive. */ - TEST_ASSERT_EQUAL_INT32( keepAliveReference, _pMqttConnection->references ); - - /* Create a new operation referencing the MQTT connection. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ) ); - - /* Check reference counts and list placement. */ - TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, _pMqttConnection->references ); - TEST_ASSERT_EQUAL_INT32( 2, pOperation->u.operation.jobReference ); - TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( _pMqttConnection->pendingProcessing ), - NULL, - NULL, - &( pOperation->link ) ) ); - - /* Schedule a job that destroys the operation. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _decrementReferencesJob, - pOperation, - &( pOperation->jobStorage ), - &( pOperation->job ) ) ); - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, - pOperation->job, - 0 ) ); - - /* Wait for the job to complete. */ - IotSemaphore_Wait( &( pOperation->u.operation.notify.waitSemaphore ) ); - - /* Check reference counts after job completion. */ - TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, _pMqttConnection->references ); - TEST_ASSERT_EQUAL_INT32( 1, pOperation->u.operation.jobReference ); - TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( _pMqttConnection->pendingProcessing ), - NULL, - NULL, - &( pOperation->link ) ) ); - - /* Disconnect the MQTT connection, then call Wait to clean up the operation. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - IotMqtt_Wait( pOperation, 0 ); - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Allocate an operation for an incoming publish. */ - pOperation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - TEST_ASSERT_NOT_NULL( pOperation ); - ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - - pOperation->incomingPublish = true; - pOperation->pMqttConnection = _pMqttConnection; - pOperation->u.publish.publishInfo.pTopicName = TEST_TOPIC_NAME; - pOperation->u.publish.publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - - pOperation->u.publish.publishInfo.payloadLength = PACKET_LENGTH; - pOperation->u.publish.pReceivedData = IotMqtt_MallocMessage( pOperation->u.publish.publishInfo.payloadLength ); - pOperation->u.publish.publishInfo.pPayload = pOperation->u.publish.pReceivedData; - - /* Increment the MQTT connection's reference count to prevent it from being destroyed - * until the test is over. */ - _pMqttConnection->references += 2; - - /* Set an invalid job status, which will cause cancellation of the job to fail. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _IotMqtt_ProcessIncomingPublish, - NULL, - &( pOperation->jobStorage ), - &( pOperation->job ) ) ); - pOperation->jobStorage.status = IOT_TASKPOOL_STATUS_COMPLETED; - - /* Insert the publish into the list of operations pending processing. Cancellation - * failure will cause it to be removed from the list, but it will not be destroyed. */ - IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), &( pOperation->link ) ); - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - TEST_ASSERT_EQUAL_INT( false, IotLink_IsLinked( &( pOperation->link ) ) ); - - /* Set a valid job status to test behavior when job cancellation succeeds. This - * should free everything allocated by this test. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _IotMqtt_ProcessIncomingPublish, - NULL, - &( pOperation->jobStorage ), - &( pOperation->job ) ) ); - IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), &( pOperation->link ) ); - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that an operation is correctly cleaned up if @ref mqtt_function_wait - * times out while its job is executing. - */ -TEST( MQTT_Unit_API, OperationWaitTimeout ) -{ - _mqttOperation_t * pOperation = NULL; - IotSemaphore_t waitSem; - - /* An arbitrary MQTT packet for this test. */ - static uint8_t pPacket[ 2 ] = { MQTT_PACKET_TYPE_PINGREQ, 0x00 }; - - /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Set the network interface send function. */ - _networkInterface.send = _sendDelay; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set parameter to network send function. */ - _pMqttConnection->pNetworkConnection = ( IotNetworkConnection_t ) &waitSem; - - /* Create a new operation referencing the MQTT connection. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ) ); - - /* Set an arbitrary MQTT packet for the operation. */ - pOperation->u.operation.type = IOT_MQTT_PINGREQ; - pOperation->u.operation.pMqttPacket = pPacket; - pOperation->u.operation.packetSize = 2; - - /* Schedule the send job. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - 0 ) ); - - /* Wait for the send job to begin. */ - IotSemaphore_Wait( &waitSem ); - - /* Wait on the MQTT operation with a short timeout. This should cause a - * timeout while the send job is still executing. */ - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( pOperation, 10 ) ); - - /* Wait with an invalid operation */ - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, IotMqtt_Wait( NULL, 10 ) ); - - /* Check reference count after a timed out wait. */ - IotMutex_Lock( &( _pMqttConnection->referencesMutex ) ); - TEST_ASSERT_EQUAL_INT32( 1, pOperation->u.operation.jobReference ); - IotMutex_Unlock( &( _pMqttConnection->referencesMutex ) ); - - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Clean up the MQTT library, which waits for the send job to finish. The - * library must be re-initialized so that test tear down does not crash. */ - IotMqtt_Cleanup(); - IotMqtt_Init(); - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test edge cases when searching for operations. - */ -TEST( MQTT_Unit_API, OperationFindMatch ) -{ - int32_t i = 0; - uint16_t packetIdentifier = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t * pMatchedOperation = NULL; - _mqttOperation_t * pOperation[ OPERATION_COUNT ] = { NULL, NULL }; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set up operations. */ - for( i = 0; i < OPERATION_COUNT; i++ ) - { - status = _IotMqtt_CreateOperation( _pMqttConnection, 0, NULL, &( pOperation[ i ] ) ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _IotMqtt_ProcessCompletedOperation, - pOperation[ i ], - &( pOperation[ i ]->jobStorage ), - &( pOperation[ i ]->job ) ) ); - - IotListDouble_Remove( &( pOperation[ i ]->link ) ); - IotListDouble_InsertHead( &( _pMqttConnection->pendingResponse ), &( pOperation[ i ]->link ) ); - - pOperation[ i ]->u.operation.packetIdentifier = ( uint16_t ) ( i + 1 ); - pOperation[ i ]->u.operation.periodic.retry.nextPeriodMs = DUP_CHECK_RETRY_MS; - pOperation[ i ]->u.operation.periodic.retry.limit = DUP_CHECK_RETRY_LIMIT; - } - - pOperation[ 0 ]->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - pOperation[ 1 ]->u.operation.type = IOT_MQTT_SUBSCRIBE; - - /* Set one operation's job to an invalid state, then try to find it. The invalid state - * will cause that job to be ignored. */ - packetIdentifier = 1; - pOperation[ 0 ]->jobStorage.status = IOT_TASKPOOL_STATUS_COMPLETED; - pMatchedOperation = _IotMqtt_FindOperation( _pMqttConnection, - IOT_MQTT_PUBLISH_TO_SERVER, - &packetIdentifier ); - TEST_ASSERT_NULL( pMatchedOperation ); - - /* Clean up operations. */ - for( i = 0; i < OPERATION_COUNT; i++ ) - { - TEST_ASSERT_EQUAL_INT( true, _IotMqtt_DecrementOperationReferences( pOperation[ i ], false ) ); - _IotMqtt_DestroyOperation( pOperation[ i ] ); - } - - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of send and notify with different link statuses. - */ -TEST( MQTT_Unit_API, OperationLists ) -{ - _mqttOperation_t * pOperation = NULL; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Create a new MQTT connection. */ - _networkInterface.send = _sendSuccess; - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Create a new MQTT operation. */ - callbackInfo.function = _completionCallback; - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, - 0, - &callbackInfo, - &pOperation ) ); - TEST_ASSERT_NOT_NULL( pOperation ); - pOperation->u.operation.pMqttPacket = IotMqtt_MallocMessage( PACKET_LENGTH ); - pOperation->u.operation.packetSize = PACKET_LENGTH; - - /* Process a send with operation unlinked. Check that operation gets linked afterwards. */ - IotListDouble_Remove( &( pOperation->link ) ); - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - TEST_ASSERT_EQUAL_INT( true, IotLink_IsLinked( &( pOperation->link ) ) ); - - /* Notify with the operation linked. */ - pOperation->u.operation.status = IOT_MQTT_SUCCESS; - _IotMqtt_Notify( pOperation ); - - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect with various - * invalid parameters. - */ -TEST( MQTT_Unit_API, ConnectParameters ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - - _networkInterface.send = _sendSuccess; - _networkInterface.close = _close; - - /* Check that the network interface is validated. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Check that the connection info is validated. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - - /* Connect with bad previous session subscription. */ - connectInfo.cleanSession = false; - connectInfo.pPreviousSubscriptions = &subscription; - connectInfo.previousSubscriptionCount = 1; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Connect with bad subscription count. */ - connectInfo.previousSubscriptionCount = 0; - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - connectInfo.previousSubscriptionCount = 1; - - /* Check that the will info is validated when it's provided. */ - connectInfo.pWillInfo = &willInfo; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - willInfo.pTopicName = TEST_TOPIC_NAME; - willInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - - /* Check that a will message longer than 65535 is not allowed. */ - willInfo.pPayload = ""; - willInfo.payloadLength = 65536; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - willInfo.payloadLength = 0; - - /* Check connect returns error if network info is invalid. */ - status = IotMqtt_Connect( NULL, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); - - /* Check that passing a wait time of 0 returns immediately. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - 0, - &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, status ); - - /* Check detection of packets that are too large. */ - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - willInfo.payloadLength = MQTT_PACKET_CONNECT_MAX_SIZE + 1; - status = _IotMqtt_SerializeConnect( &connectInfo, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, ConnectMallocFail ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - _networkInterface.close = _close; - connectInfo.keepAliveSeconds = 100; - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - - serializer.serialize.pingreq = _serializePingreq; - _networkInfo.pMqttSerializer = &serializer; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call CONNECT. Memory allocation will fail at various times during - * this call. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - - /* Free any allocated PINGREQ. */ - if( _pAllocatedPingreq != NULL ) - { - IotTest_Free( _pAllocatedPingreq ); - _pAllocatedPingreq = NULL; - } - - /* If the return value is timeout, then all memory allocation succeeded - * and the loop can exit. The expected return value is timeout (and not - * success) because the receive callback is never invoked. */ - if( status == IOT_MQTT_TIMEOUT ) - { - break; - } - - /* If the return value isn't timeout, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect when memory - * allocation fails at various points for a persistent session. - */ -TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - _networkInterface.close = _close; - connectInfo.cleanSession = false; - connectInfo.keepAliveSeconds = 100; - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - - connectInfo.pPreviousSubscriptions = &subscription; - connectInfo.previousSubscriptionCount = 1; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call CONNECT with a previous session. Memory allocation will fail at - * various times during this call. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - TIMEOUT_MS, - &_pMqttConnection ); - - /* If the return value is timeout, then all memory allocation succeeded - * and the loop can exit. The expected return value is timeout (and not - * success) because the receive callback is never invoked. */ - if( status == IOT_MQTT_TIMEOUT ) - { - break; - } - - /* If the return value isn't timeout, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_disconnect when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, DisconnectMallocFail ) -{ - int32_t i = 0; - IotMqttDisconnectReason_t expectedReason = IOT_MQTT_DISCONNECT_CALLED; - - /* Set the members of the network interface. */ - _networkInterface.send = _sendSuccess; - _networkInterface.close = _close; - _networkInfo.createNetworkConnection = false; - _networkInfo.disconnectCallback.pCallbackContext = &expectedReason; - _networkInfo.disconnectCallback.function = _disconnectCallback; - - for( i = 0; i < DISCONNECT_MALLOC_LIMIT; i++ ) - { - /* Allow unlimited use of malloc during connection initialization. */ - UnityMalloc_MakeMallocFailAfterCount( -1 ); - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set malloc to eventually fail. */ - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call DISCONNECT; this function should always perform cleanup regardless - * of memory allocation errors. */ - IotMqtt_Disconnect( _pMqttConnection, 0 ); - TEST_ASSERT_EQUAL_INT( 1, _closeCount ); - TEST_ASSERT_EQUAL_INT( 1, _disconnectCallbackCount ); - _closeCount = 0; - _disconnectCallbackCount = 0; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_disconnect when - * disconnected mqtt connection is passed. - */ -TEST( MQTT_Unit_API, DisconnectAlreadyDisconnected ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - - /* Increment the MQTT connection's reference count to prevent it from being destroyed - * until the test is over. */ - _pMqttConnection->references++; - - /* Call Disconnect, reference count should decrement. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->references ); - /* 'disconnected' flag should be set */ - TEST_ASSERT_EQUAL( true, _pMqttConnection->disconnected ); - - /* Attempt to use a closed connection. */ - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - publishInfo.pPayload = ""; - publishInfo.payloadLength = 0; - - status = IotMqtt_PublishSync( _pMqttConnection, &publishInfo, 0, TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); - - /* Disconnect and clean up test. */ - IotMqtt_Disconnect( _pMqttConnection, 0 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 0) with various - * valid and invalid parameters. - */ -TEST( MQTT_Unit_API, PublishQoS0Parameters ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Parameters of PUBLISH serialization. */ - uint8_t * pPublishPacket = NULL; - size_t packetSize = 0; - uint16_t packetIdentifier = 0; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - if( TEST_PROTECT() ) - { - /* Check that the publish info is validated. */ - status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - - /* Check that a QoS 0 publish is refused if a notification is requested. */ - status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* If valid parameters are passed, QoS 0 publish should always return success. */ - status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, 0, &publishOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Check detection of packets that are too large. */ - publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH; - status = _IotMqtt_SerializePublish( &publishInfo, &pPublishPacket, &packetSize, &packetIdentifier, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - } - - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 0) when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, PublishQoS0MallocFail ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the necessary members of publish info. */ - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - - if( TEST_PROTECT() ) - { - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call PUBLISH. Memory allocation will fail at various times during - * this call. */ - status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); - - /* Once PUBLISH succeeds, the loop can exit. */ - if( status == IOT_MQTT_SUCCESS ) - { - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - } - } - - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 1) with various - * invalid parameters. Also tests the behavior of @ref mqtt_function_publishasync - * (QoS 1) when memory allocation fails at various points. - */ -TEST( MQTT_Unit_API, PublishQoS1 ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - - if( TEST_PROTECT() ) - { - /* Setting the waitable flag with no reference is not allowed. */ - status = IotMqtt_PublishAsync( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Setting both the waitable flag and callback info is not allowed. */ - status = IotMqtt_PublishAsync( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - &callbackInfo, - &publishOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Check QoS 1 PUBLISH behavior with malloc failures. */ - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call PUBLISH. Memory allocation will fail at various times during - * this call. */ - status = IotMqtt_PublishAsync( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishOperation ); - - /* If the PUBLISH succeeded, the loop can exit after waiting for the QoS - * 1 PUBLISH to be cleaned up. */ - if( status == IOT_MQTT_STATUS_PENDING ) - { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( publishOperation, TIMEOUT_MS ) ); - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - } - } - - /* Clean up MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that PUBLISH retry periods are calculated correctly. - */ -TEST( MQTT_Unit_API, PublishRetryPeriod ) -{ - _mqttOperation_t * pOperation = NULL; - uint32_t periodMs = IOT_MQTT_RETRY_MS_CEILING / 2; - - /* Create a new MQTT connection. */ - _networkInterface.send = _sendSuccess; - _pMqttConnection = IotTestMqtt_createMqttConnection( false, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Create a PUBLISH with retry operation. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( _pMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ) ); - TEST_ASSERT_NOT_NULL( pOperation ); - pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - pOperation->u.operation.pMqttPacket = IotMqtt_MallocMessage( PACKET_LENGTH ); - pOperation->u.operation.packetSize = PACKET_LENGTH; - pOperation->u.operation.periodic.retry.limit = DUP_CHECK_RETRY_LIMIT; - pOperation->u.operation.periodic.retry.nextPeriodMs = periodMs; - IotListDouble_Remove( &( pOperation->link ) ); - - /* Simulate send of PUBLISH. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - - /* Immediately cancel retried PUBLISH, then check statuses set by send. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pOperation->job, - NULL ) ); - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, pOperation->u.operation.status ); - TEST_ASSERT_EQUAL( 1, pOperation->u.operation.periodic.retry.count ); - TEST_ASSERT_EQUAL( 2 * periodMs, pOperation->u.operation.periodic.retry.nextPeriodMs ); - - /* Simulate another send. Check that the retry ceiling is respected. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - - /* Immediately cancel retried PUBLISH, then check statuses set by send. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pOperation->job, - NULL ) ); - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, pOperation->u.operation.status ); - TEST_ASSERT_EQUAL( 2, pOperation->u.operation.periodic.retry.count ); - TEST_ASSERT_EQUAL( IOT_MQTT_RETRY_MS_CEILING, pOperation->u.operation.periodic.retry.nextPeriodMs ); - - /* Clean up. */ - TEST_ASSERT_EQUAL_INT( false, _IotMqtt_DecrementOperationReferences( pOperation, false ) ); - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that duplicate QoS 1 PUBLISH packets are different from the - * original. - * - * For non-AWS IoT MQTT servers, checks that the DUP flag is set. For - * AWS IoT MQTT servers, checks that the packet identifier is different. - */ -TEST( MQTT_Unit_API, PublishDuplicates ) -{ - static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - bool dupCheckResult = false; - uint64_t startTime = 0; - - /* Initializer parameters. */ - serializer.serialize.publishSetDup = _publishSetDup; - _networkInterface.send = _dupChecker; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the serializers and parameter to the send function. */ - _pMqttConnection->pNetworkConnection = ( IotNetworkConnection_t ) &dupCheckResult; - _pMqttConnection->pSerializer = &serializer; - - /* Set the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - publishInfo.pPayload = "test"; - publishInfo.payloadLength = 4; - publishInfo.retryMs = DUP_CHECK_RETRY_MS; - publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; - - startTime = IotClock_GetTimeMs(); - - if( TEST_PROTECT() ) - { - /* Send a PUBLISH with retransmissions enabled. */ - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, - IotMqtt_PublishAsync( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishOperation ) ); - - /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is - * expected. */ - TEST_ASSERT_EQUAL( IOT_MQTT_RETRY_NO_RESPONSE, - IotMqtt_Wait( publishOperation, DUP_CHECK_TIMEOUT ) ); - - /* Check the result of the DUP check. */ - TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); - - /* Check that at least the minimum wait time elapsed. */ - TEST_ASSERT_TRUE( startTime + DUP_CHECK_MINIMUM_WAIT <= IotClock_GetTimeMs() ); - } - - /* Clean up MQTT connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Check that the set DUP override was called. */ - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, _publishSetDupCalled ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_subscribeasync and - * @ref mqtt_function_unsubscribeasync with various invalid parameters. - */ -TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Check that subscription info is validated. */ - status = IotMqtt_SubscribeAsync( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_UnsubscribeAsync( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - - /* A reference must be provided for a waitable SUBSCRIBE. */ - status = IotMqtt_SubscribeAsync( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_UnsubscribeAsync( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_subscribeasync when memory allocation - * fails at various points. - */ -TEST( MQTT_Unit_API, SubscribeMallocFail ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Initializer parameters. */ - _networkInterface.send = _sendSuccess; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the necessary members of the subscription. */ - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - - if( TEST_PROTECT() ) - { - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call SUBSCRIBE. Memory allocation will fail at various times during - * this call. */ - status = IotMqtt_SubscribeAsync( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeOperation ); - - /* If the SUBSCRIBE succeeded, the loop can exit after waiting for - * the SUBSCRIBE to be cleaned up. */ - if( status == IOT_MQTT_STATUS_PENDING ) - { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( subscribeOperation, TIMEOUT_MS ) ); - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - } - - /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - } - - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_unsubscribeasync when memory - * allocation fails at various points. - */ -TEST( MQTT_Unit_API, UnsubscribeMallocFail ) -{ - int32_t i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the necessary members of the subscription. */ - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK; - - if( TEST_PROTECT() ) - { - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call UNSUBSCRIBE. Memory allocation will fail at various times during - * this call. */ - status = IotMqtt_UnsubscribeAsync( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeOperation ); - - /* If the UNSUBSCRIBE succeeded, the loop can exit after waiting for - * the UNSUBSCRIBE to be cleaned up. */ - if( status == IOT_MQTT_STATUS_PENDING ) - { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( unsubscribeOperation, TIMEOUT_MS ) ); - break; - } - - /* If the return value isn't success, check that it is memory allocation - * failure. */ - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - } - - /* No lingering subscriptions should be in the MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - } - - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests keep-alive handling and ensures that it is periodic. - */ -TEST( MQTT_Unit_API, KeepAlivePeriodic ) -{ - IotTaskPoolJobStatus_t cancelStatus = IOT_TASKPOOL_STATUS_UNDEFINED; - - /* The expected disconnect reason for this test's disconnect callback. */ - IotMqttDisconnectReason_t expectedReason = IOT_MQTT_KEEP_ALIVE_TIMEOUT; - - /* An estimate for the amount of time this test requires. */ - const uint32_t sleepTimeMs = ( KEEP_ALIVE_COUNT * SHORT_KEEP_ALIVE_MS ) + - ( IOT_MQTT_RESPONSE_WAIT_MS * KEEP_ALIVE_COUNT ) + 2500; - - /* Print a newline so this test may log its status. */ - UNITY_PRINT_EOL(); - - /* Initialize parameters. */ - _networkInterface.send = _sendPingreq; - _networkInterface.receive = _receivePingresp; - _networkInterface.close = _close; - _networkInfo.disconnectCallback.pCallbackContext = &expectedReason; - _networkInfo.disconnectCallback.function = _disconnectCallback; - - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 1 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Check that PINGREQ is not sent when the connection was used recently. */ - _pMqttConnection->lastMessageTime = IotClock_GetTimeMs(); - _IotMqtt_ProcessKeepAlive( IOT_SYSTEM_TASKPOOL, _pMqttConnection->pingreq.job, _pMqttConnection ); - TEST_ASSERT_EQUAL_INT( 0, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); - TEST_ASSERT_EQUAL_INT( _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs, - _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ); - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - _pMqttConnection->pingreq.job, - &cancelStatus ) ); - TEST_ASSERT_EQUAL( IOT_TASKPOOL_STATUS_DEFERRED, cancelStatus ); - - /* Set a short keep-alive interval so this test runs faster. */ - _pMqttConnection->lastMessageTime = 0; - _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; - _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; - - /* Schedule the initial PINGREQ. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, - IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - _pMqttConnection->pingreq.job, - _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ) ); - - /* Sleep to allow ample time for periodic PINGREQ sends and PINGRESP responses. */ - IotClock_SleepMs( sleepTimeMs ); - - /* Disconnect the connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Check the counters for PINGREQ send and close. */ - TEST_ASSERT_EQUAL_INT32( KEEP_ALIVE_COUNT + 1, _pingreqSendCount ); - TEST_ASSERT_EQUAL_INT32( 2, _closeCount ); - - /* Check that the disconnect callback was invoked once (with reason - * "keep-alive timeout"). */ - TEST_ASSERT_EQUAL_INT32( 1, Atomic_Add_u32( &_disconnectCallbackCount, 0 ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that the keep-alive job cleans up the MQTT connection after a call - * to @ref mqtt_function_disconnect. - */ -TEST( MQTT_Unit_API, KeepAliveJobCleanup ) -{ - IotSemaphore_t waitSem; - - /* Initialize parameters. */ - _networkInterface.send = _sendSuccess; - - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 1 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the parameter to the send function. */ - _pMqttConnection->pNetworkConnection = ( IotNetworkConnection_t ) &waitSem; - - /* Set a short keep-alive interval so this test runs faster. */ - _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; - _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; - - /* Schedule the initial PINGREQ. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, - IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - _pMqttConnection->pingreq.job, - _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ) ); - - /* Wait for the keep-alive job to send a PINGREQ. */ - IotSemaphore_Wait( &waitSem ); - - /* Immediately disconnect the connection. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/* Tests for public serializer API */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_GetConnectPacketSize works as intended. - * to @ref mqtt_function_getconnectpacketsize. - */ - -TEST( MQTT_Unit_API, GetConnectPacketSizeChecks ) -{ - IotMqttConnectInfo_t connectInfo; - size_t remainingLength = 0; - size_t packetSize = 0; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Call IotMqtt_GetConnectPacketSize() with various combinations of - * incorrect paramters */ - - status = IotMqtt_GetConnectPacketSize( NULL, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetConnectPacketSize( &connectInfo, NULL, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, NULL ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Verify empty connect info fails. */ - memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Verify empty client identifier fails. */ - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = 0; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - connectInfo.pClientIdentifier = NULL; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Verify good case */ - memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = "TEST"; - connectInfo.clientIdentifierLength = 4; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure remaining size returned is 16. */ - TEST_ASSERT_EQUAL_INT( 16, remainingLength ); - /* Make sure packet size is 18. */ - TEST_ASSERT_EQUAL_INT( 18, packetSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_SerializeConnect works as intended. - * to @ref mqtt_function_serializeconnect. - */ -TEST( MQTT_Unit_API, SerializeConnectChecks ) -{ - IotMqttConnectInfo_t connectInfo; - IotMqttPublishInfo_t willInfo; - size_t remainingLength = 0; - uint8_t buffer[ 70 ]; - size_t bufferSize = sizeof( buffer ); - size_t packetSize = bufferSize; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Verify bad parameter errors. */ - status = IotMqtt_SerializeConnect( NULL, remainingLength, buffer, packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, NULL, packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); - status = IotMqtt_SerializeConnect( &connectInfo, 120, buffer, packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - status = IotMqtt_SerializeConnect( &connectInfo, 120, buffer, packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Connect packet too large. */ - memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = UINT16_MAX; - connectInfo.pPassword = ""; - connectInfo.passwordLength = UINT16_MAX; - connectInfo.pUserName = ""; - connectInfo.userNameLength = UINT16_MAX; - willInfo.pTopicName = TEST_TOPIC_NAME; - willInfo.topicNameLength = UINT16_MAX; - willInfo.payloadLength = UINT16_MAX + 2; - connectInfo.pWillInfo = &willInfo; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Good case succeeds */ - /* Calculate packet size. */ - memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = "TEST"; - connectInfo.clientIdentifierLength = 4; - connectInfo.pUserName = "USER"; - connectInfo.userNameLength = 4; - connectInfo.pPassword = "PASS"; - connectInfo.passwordLength = 4; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure buffer has enough space */ - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - /* Make sure test succeeds. */ - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Encode user name in AWS mode. */ - connectInfo.awsIotMqttMode = true; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Serialize connect with LWT. */ - ( void ) memset( &willInfo, 0x00, sizeof( IotMqttPublishInfo_t ) ); - willInfo.retain = true; - willInfo.qos = IOT_MQTT_QOS_1; - willInfo.pTopicName = "test"; - willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); - willInfo.pPayload = "test"; - willInfo.payloadLength = ( uint16_t ) strlen( willInfo.pPayload ); - connectInfo.pWillInfo = &willInfo; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - willInfo.qos = IOT_MQTT_QOS_2; - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* For this example, IotMqtt_GetConnectPacketSize() will return - * packetSize = remainingLength +2 (two byte fixed header). - * Make sure IotMqtt_SerializeConnect() - * fails when remaining length is more than packet size. */ - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength + 4, buffer, packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_GetSubscribePacketSize works as intended. - * to @ref mqtt_function_getsubscriptionpacketsize. - */ -TEST( MQTT_Unit_API, GetSubscribePacketSizeChecks ) -{ - IotMqttSubscription_t subscriptionList; - size_t subscriptionCount = 0; - size_t remainingLength = 0; - size_t packetSize = 0; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Verify parameters. */ - - status = IotMqtt_GetSubscriptionPacketSize( 100, - &subscriptionList, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - NULL, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - &subscriptionList, - subscriptionCount, - NULL, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - &subscriptionList, - subscriptionCount, - &remainingLength, - NULL ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - - /* Verify empty subscription list fails. */ - memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); - subscriptionCount = 0; - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - &subscriptionList, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Verify good case. */ - memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); - subscriptionList.qos = IOT_MQTT_QOS_0; - subscriptionList.pTopicFilter = "/example/topic"; - subscriptionList.topicFilterLength = sizeof( "/example/topic" ); - subscriptionCount = sizeof( subscriptionList ) / sizeof( IotMqttSubscription_t ); - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - &subscriptionList, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_GREATER_THAN( remainingLength, packetSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_SerializeSubscribe works as intended. - * to @ref mqtt_function_serializesubscribe. - */ -TEST( MQTT_Unit_API, SerializeSubscribeChecks ) -{ - IotMqttSubscription_t subscriptionList; - size_t subscriptionCount = 0; - size_t remainingLength = 0; - uint16_t packetIdentifier; - uint8_t buffer[ 25 ]; - size_t bufferSize = sizeof( buffer ); - size_t packetSize = bufferSize; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Verify bad parameters fail. */ - status = IotMqtt_SerializeSubscribe( NULL, - subscriptionCount, - remainingLength, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_SerializeSubscribe( &subscriptionList, - subscriptionCount, - remainingLength, - NULL, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_SerializeSubscribe( &subscriptionList, - subscriptionCount, - remainingLength, - &packetIdentifier, - NULL, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Get correct values of packet size and remaining length. */ - memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); - subscriptionList.qos = IOT_MQTT_QOS_0; - subscriptionList.pTopicFilter = "/example/topic"; - subscriptionList.topicFilterLength = sizeof( "/example/topic" ); - subscriptionCount = sizeof( subscriptionList ) / sizeof( IotMqttSubscription_t ); - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - &subscriptionList, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure buffer has enough space */ - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - - /* Make sure subscription count of zero fails. */ - status = IotMqtt_SerializeSubscribe( &subscriptionList, - 0, - remainingLength, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Make sure success is returned for good case. */ - status = IotMqtt_SerializeSubscribe( &subscriptionList, - subscriptionCount, - remainingLength, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* For this example, IotMqtt_GetSubscriptionPacketSize() will return - * packetSize = remainingLength +2 (two byte fixed header). - * Make sure IotMqtt_SerializeSubscribe() - * fails when remaining length is more than packet size. */ - status = IotMqtt_SerializeSubscribe( &subscriptionList, - subscriptionCount, - remainingLength + 4, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_SerializeUnsubscribe works as intended. - * to @ref mqtt_function_serializeunsubscribe. - */ -TEST( MQTT_Unit_API, SerializeUnsubscribeChecks ) -{ - IotMqttSubscription_t subscriptionList; - size_t subscriptionCount = 0; - size_t remainingLength = 0; - uint16_t packetIdentifier; - uint8_t buffer[ 25 ]; - size_t bufferSize = sizeof( buffer ); - size_t packetSize = bufferSize; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - status = IotMqtt_SerializeUnsubscribe( NULL, - subscriptionCount, - remainingLength, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_SerializeUnsubscribe( &subscriptionList, - subscriptionCount, - remainingLength, - NULL, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_SerializeUnsubscribe( &subscriptionList, - subscriptionCount, - remainingLength, - &packetIdentifier, - NULL, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Get correct values of packetsize and remaining length. */ - memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); - subscriptionList.qos = IOT_MQTT_QOS_0; - subscriptionList.pTopicFilter = "/example/topic"; - subscriptionList.topicFilterLength = sizeof( "/example/topic" ); - subscriptionCount = sizeof( subscriptionList ) / sizeof( IotMqttSubscription_t ); - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, - &subscriptionList, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure buffer has enough space */ - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - - /* Make sure subscription count of zero fails. */ - status = IotMqtt_SerializeUnsubscribe( &subscriptionList, - 0, - remainingLength, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Make sure success it returned for good case. */ - status = IotMqtt_SerializeUnsubscribe( &subscriptionList, - subscriptionCount, - remainingLength, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* For this example, IotMqtt_GetSubscriptionPacketSize() will return - * packetSize = remainingLength +2, make sure IotMqtt_SerializeUnsubscribe() - * fails when remaining length is more than packet size. */ - status = IotMqtt_SerializeUnsubscribe( &subscriptionList, - subscriptionCount, - remainingLength + 4, - &packetIdentifier, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_GetPublishPacketSize works as intended. - * to @ref mqtt_function_getpublishpacketsize. - */ -TEST( MQTT_Unit_API, GetPublishPacketSizeChecks ) -{ - IotMqttPublishInfo_t publishInfo; - size_t remainingLength = 0; - size_t packetSize; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Verify bad paramameters fail. */ - status = IotMqtt_GetPublishPacketSize( NULL, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetPublishPacketSize( &publishInfo, NULL, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Empty topic must fail. */ - memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); - publishInfo.pTopicName = NULL; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = 0; - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Packet too large. */ - memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); - publishInfo.pTopicName = "/test/topic"; - publishInfo.topicNameLength = sizeof( "/test/topic" ); - publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH; - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH - publishInfo.topicNameLength - sizeof( uint16_t ) - 1; - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Good case succeeds. */ - publishInfo.pTopicName = "/test/topic"; - publishInfo.topicNameLength = sizeof( "/test/topic" ); - publishInfo.pPayload = ""; - publishInfo.payloadLength = 0; - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_GetPublishPacketSize works as intended. - * to @ref mqtt_function_serializepublish. - */ -TEST( MQTT_Unit_API, SerializePublishChecks ) -{ - IotMqttPublishInfo_t publishInfo; - size_t remainingLength = 98; - uint16_t packetIdentifier; - uint8_t * pPacketIdentifierHigh; - uint8_t buffer[ 100 ]; - size_t bufferSize = sizeof( buffer ); - size_t packetSize = bufferSize; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Verify bad parameters fail. */ - memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); - publishInfo.pTopicName = "/test/topic"; - publishInfo.topicNameLength = sizeof( "/test/topic" ); - - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - NULL, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_SerializePublish( NULL, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - NULL, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Empty topic fails. */ - publishInfo.pTopicName = NULL; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = 0; - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Remaining length larger than buffer size. */ - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - status = IotMqtt_SerializePublish( &publishInfo, - 10, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - 5 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Good case succeeds */ - publishInfo.qos = IOT_MQTT_QOS_2; - publishInfo.retain = true; - publishInfo.pTopicName = "/test/topic"; - publishInfo.topicNameLength = sizeof( "/test/topic" ); - /* Calculate exact packet size and remaining length. */ - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure buffer has enough space */ - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_SerializeDisconnect works as intended. - * to @ref mqtt_function_serializedisconnect. - */ -TEST( MQTT_Unit_API, SerializeDisconnectChecks ) -{ - uint8_t buffer[ 10 ]; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Buffer size less than disconnect request fails. */ - status = IotMqtt_SerializeDisconnect( buffer, 1 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* NULL buffer fails. */ - status = IotMqtt_SerializeDisconnect( NULL, 10 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Good case succeeds. */ - status = IotMqtt_SerializeDisconnect( buffer, 2 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_SerializePingReq works as intended. - * to @ref mqtt_function_serializepingreq. - */ -TEST( MQTT_Unit_API, SerializePingReqChecks ) -{ - uint8_t buffer[ 10 ]; - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Buffer size less than disconnect request fails. */ - status = IotMqtt_SerializePingreq( buffer, 1 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* NULL buffer fails. */ - status = IotMqtt_SerializePingreq( NULL, 10 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Good case succeeds. */ - status = IotMqtt_SerializePingreq( buffer, 2 ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_GetIncomingMQTTPacketTypeAndLength works as intended. - */ -TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) -{ - IotMqttError_t status = IOT_MQTT_SUCCESS; - IotMqttPacketInfo_t mqttPacket; - uint8_t buffer[ 10 ]; - uint8_t * bufPtr = buffer; - - /* Dummy network interface - pointer to pointer to a buffer. */ - IotNetworkConnection_t pNetworkInterface = ( IotNetworkConnection_t ) &bufPtr; - - buffer[ 0 ] = 0x20; /* CONN ACK */ - buffer[ 1 ] = 0x02; /* Remaining length. */ - - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_EQUAL_INT( 0x20, mqttPacket.type ); - TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); - - /* Test with NULL network interface */ - bufPtr = buffer; - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); - - /* Test with incorrect packet type. */ - bufPtr = buffer; - buffer[ 0 ] = 0x10; /* INVALID */ - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); - - /* Test with invalid remaining length. */ - bufPtr = buffer; - buffer[ 0 ] = 0x20; /* CONN ACK */ - - /* To generate invalid remaining length response, - * four bytes need to have MSB (or continuation bit, 0x80) set */ - buffer[ 1 ] = 0xFF; - buffer[ 2 ] = 0xFF; - buffer[ 3 ] = 0xFF; - buffer[ 4 ] = 0xFF; - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); - - /* Check with an encoding that does not conform to the MQTT spec. */ - bufPtr = buffer; - buffer[ 1 ] = 0x80; - buffer[ 2 ] = 0x80; - buffer[ 3 ] = 0x80; - buffer[ 4 ] = 0x00; - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); - - /* Check when network receive fails. */ - memset( buffer, 0x00, 10 ); - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByteFailure, pNetworkInterface ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_DeserializeResponse works as intended with a CONNACK. - */ -TEST( MQTT_Unit_API, LightweightConnack ) -{ - IotMqttPacketInfo_t mqttPacketInfo; - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t buffer[ 10 ]; - - /* Verify parameters */ - status = IotMqtt_DeserializeResponse( NULL ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Bad packet type. */ - mqttPacketInfo.type = 0x01; - mqttPacketInfo.pRemainingData = buffer; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK; - mqttPacketInfo.remainingLength = MQTT_PACKET_CONNACK_REMAINING_LENGTH - 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Incorrect reserved bits. */ - mqttPacketInfo.remainingLength = MQTT_PACKET_CONNACK_REMAINING_LENGTH; - buffer[ 0 ] = 0xf; - buffer[ 1 ] = 0; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Session present but nonzero return code. */ - buffer[ 0 ] = MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK; - buffer[ 1 ] = 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Invalid response code. */ - buffer[ 0 ] = 0; - buffer[ 1 ] = 6; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Valid packet with rejected code. */ - buffer[ 1 ] = 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SERVER_REFUSED, status ); - - /* Valid packet with success code. */ - buffer[ 0 ] = 1; - buffer[ 1 ] = 0; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_DeserializeResponse works as intended with a SUBACK. - */ -TEST( MQTT_Unit_API, LightweightSuback ) -{ - IotMqttPacketInfo_t mqttPacketInfo; - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t buffer[ 10 ] = { 0 }; - - /* Bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK; - mqttPacketInfo.pRemainingData = buffer; - mqttPacketInfo.remainingLength = 2; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Set packet identifier. */ - buffer[ 0 ] = 0; - buffer[ 1 ] = 1; - - /* Bad response code. */ - mqttPacketInfo.remainingLength = 3; - buffer[ 2 ] = 5; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Process a valid SUBACK with server refused response code. */ - mqttPacketInfo.remainingLength = 3; - buffer[ 2 ] = 0x80; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SERVER_REFUSED, status ); - - /* Process a valid SUBACK with various server acceptance codes. */ - mqttPacketInfo.remainingLength = 5; - buffer[ 2 ] = 0x00; - buffer[ 3 ] = 0x01; - buffer[ 4 ] = 0x02; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_DeserializeResponse works as intended with an UNSUBACK. - */ -TEST( MQTT_Unit_API, LightweightUnsuback ) -{ - IotMqttPacketInfo_t mqttPacketInfo; - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t buffer[ 10 ] = { 0 }; - - /* Bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_UNSUBACK; - mqttPacketInfo.pRemainingData = buffer; - mqttPacketInfo.remainingLength = MQTT_PACKET_UNSUBACK_REMAINING_LENGTH - 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Packet identifier 0 is not valid (per spec). */ - buffer[ 0 ] = 0; - buffer[ 1 ] = 0; - mqttPacketInfo.remainingLength = MQTT_PACKET_UNSUBACK_REMAINING_LENGTH; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Process a valid UNSUBACK. */ - buffer[ 1 ] = 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_DeserializeResponse works as intended with a PINGRESP. - */ -TEST( MQTT_Unit_API, LightweightPingresp ) -{ - IotMqttPacketInfo_t mqttPacketInfo; - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t buffer[ 10 ] = { 0 }; - - /* Bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_PINGRESP; - mqttPacketInfo.pRemainingData = buffer; - mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH + 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Process a valid PINGRESP. */ - mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_DeserializeResponse works as intended with a PUBACK. - */ -TEST( MQTT_Unit_API, LightweightPuback ) -{ - IotMqttPacketInfo_t mqttPacketInfo; - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t buffer[ 10 ] = { 0 }; - - /* Bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBACK; - mqttPacketInfo.pRemainingData = buffer; - mqttPacketInfo.remainingLength = MQTT_PACKET_PUBACK_REMAINING_LENGTH - 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Packet identifier 0 is not valid (per spec). */ - buffer[ 0 ] = 0; - buffer[ 1 ] = 0; - mqttPacketInfo.remainingLength = MQTT_PACKET_PUBACK_REMAINING_LENGTH; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Process a valid PUBACK. */ - buffer[ 1 ] = 1; - status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that IotMqtt_DeserializePublish works as intended. - */ -TEST( MQTT_Unit_API, DeserializePublishChecks ) -{ - IotMqttPacketInfo_t mqttPacketInfo; - IotMqttPublishInfo_t publishInfo; - IotMqttError_t status = IOT_MQTT_SUCCESS; - uint8_t buffer[ 100 ]; - size_t bufferSize = sizeof( buffer ); - size_t packetSize = bufferSize; - - size_t remainingLength = 0; - uint16_t packetIdentifier; - uint8_t * pPacketIdentifierHigh; - uint8_t * pNetworkInterface; - - /* Verify parameters. */ - status = IotMqtt_DeserializePublish( NULL ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); - - /* Bad Packet Type. */ - mqttPacketInfo.type = 0x01; - mqttPacketInfo.pRemainingData = buffer; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); - - /* Incorrect flags. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH | 0xf; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* QoS 0 bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH; - mqttPacketInfo.remainingLength = 0; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* QoS 1 bad remaining length. */ - mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH | 0x2; - mqttPacketInfo.remainingLength = 0; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* QoS 1 invalid packet identifier. */ - mqttPacketInfo.remainingLength = 5; - buffer[ 0 ] = 0; - buffer[ 1 ] = 1; - buffer[ 2 ] = ( uint8_t )'a'; - buffer[ 3 ] = 0; - buffer[ 4 ] = 0; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_RESPONSE, status ); - - /* Create a PUBLISH packet to test. */ - memset( &publishInfo, 0x00, sizeof( publishInfo ) ); - publishInfo.pTopicName = "/test/topic"; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = "Hello World"; - publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); - - /* Test serialization and deserialization of a QoS 0 PUBLISH. */ - publishInfo.qos = IOT_MQTT_QOS_0; - - /* Generate QoS 0 packet. */ - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Deserialize QoS 0 packet. */ - pNetworkInterface = buffer; - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacketInfo, _getNextByte, ( void * ) &pNetworkInterface ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - mqttPacketInfo.pRemainingData = &buffer[ 2 ]; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Test serialization and deserialization of a QoS 1 PUBLISH. */ - publishInfo.qos = IOT_MQTT_QOS_1; - - status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); - - status = IotMqtt_SerializePublish( &publishInfo, - remainingLength, - &packetIdentifier, - &pPacketIdentifierHigh, - buffer, - packetSize ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - pNetworkInterface = buffer; - status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacketInfo, _getNextByte, ( void * ) &pNetworkInterface ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - mqttPacketInfo.pRemainingData = &buffer[ 2 ]; - status = IotMqtt_DeserializePublish( &mqttPacketInfo ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c deleted file mode 100644 index b3120897aa..0000000000 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_platform.c +++ /dev/null @@ -1,1482 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_platform.c - * @brief Tests interaction of MQTT with the lower layers, such as network and task pool. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* SDK initialization include. */ -#include "iot_init.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* MQTT lightweight includes. */ -#include "iot_mqtt_protocol.h" -#include "iot_mqtt_lightweight.h" - -/* Allow these tests to manipulate the task pool and create failures by including - * the task pool internal header. */ -#undef LIBRARY_LOG_LEVEL -#undef LIBRARY_LOG_NAME -#include "../src/private/iot_taskpool_internal.h" - -/* MQTT test access include. */ -#include "iot_test_access_mqtt.h" - -/* MQTT mock include. */ -#include "iot_tests_mqtt_mock.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Timeout to use for the tests. This can be short, but should allow time - * for other threads to run. - */ -#define TIMEOUT_MS ( 400 ) - -/* - * Client identifier and length to use for the MQTT API tests. - */ -#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ -#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ - -/** - * @brief A non-NULL function pointer to use for subscription callback. This - * "function" should cause a crash if actually called. - */ -#define SUBSCRIPTION_CALLBACK_FUNCTION \ - ( ( void ( * )( void *, \ - IotMqttCallbackParam_t * ) ) 0x1 ) - -/** - * @brief Length of an arbitrary packet for testing. A buffer will be allocated - * for it, but its contents don't matter. - */ -#define PACKET_LENGTH ( 1 ) - -/** - * @brief A short keep-alive interval to use for the keep-alive tests. It may be - * shorter than the minimum 1 second specified by the MQTT spec. - */ -#define SHORT_KEEP_ALIVE_MS ( 100 ) - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef IOT_TEST_MQTT_TIMEOUT_MS - #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) -#endif -#ifndef IOT_TEST_MQTT_CONNECT_RETRY_COUNT - #define IOT_TEST_MQTT_CONNECT_RETRY_COUNT ( 1 ) -#endif -#ifndef IOT_TEST_MQTT_PUBLISH_RETRY_COUNT - #define IOT_TEST_MQTT_PUBLISH_RETRY_COUNT ( 3 ) -#endif -/** @endcond */ - -#if IOT_TEST_MQTT_CONNECT_RETRY_COUNT < 1 - #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 1." -#endif -#if IOT_TEST_MQTT_PUBLISH_RETRY_COUNT < 0 - #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 0." -#endif - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false -#endif - -/** - * @brief The maximum length of an MQTT client identifier. - * - * When @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER is defined, this value must - * accommodate the length of @ref IOT_TEST_MQTT_CLIENT_IDENTIFIER plus 4 - * to accommodate the Last Will and Testament test. Otherwise, this value is - * set to 24, which is the longest client identifier length an MQTT server is - * obligated to accept plus a NULL terminator. - */ -#ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) -#else - #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) -#endif - -/** - * @brief Generates a topic by suffixing the client identifier with a suffix. - * - * @param[in] bufferName The name of the buffer for the topic. - * @param[in] suffix The suffix to place at the end of the client identifier. - */ -#define GENERATE_TOPIC_WITH_SUFFIX( bufferName, suffix ) \ - char bufferName[ CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ) ] = { 0 }; \ - ( void ) snprintf( bufferName, \ - CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ), \ - "%s%s", \ - _pClientIdentifier, \ - suffix ); - -/* - * Will topic name and length to use for the MQTT API tests. - */ -#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ -#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ - -/* - * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. - */ -#define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ -#define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ -#define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. - * Duplicates are sent using an exponential backoff strategy. */ - -/*-----------------------------------------------------------*/ - -/** - * @brief An #IotMqttNetworkInfo_t to share among the tests. - */ -static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - -/** - * @brief An #IotNetworkInterface_t to share among the tests. - */ -static IotNetworkInterface_t _networkInterface = { 0 }; - -/** - * @brief An MQTT connection to share among the tests. - */ -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief An #IotMqttNetworkInfo_t to use for the re-entrancy tests. - */ -static IotMqttNetworkInfo_t _reentrantNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - -/** - * @brief Network server info to use for the re-entrancy tests. - */ -static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - -/** - * @brief Network credential info to use for the re-entrancy tests. - */ -#if IOT_TEST_SECURED_CONNECTION == 1 - static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -#endif - -#ifdef IOT_TEST_MQTT_SERIALIZER - -/** - * @brief Provide a pointer to a serializer that may be overridden. - */ - static const IotMqttSerializer_t * _pMqttSerializer = NULL; -#else - -/** - * @brief Function pointers to the default MQTT serializers. - */ - static const IotMqttSerializer_t _mqttSerializer = - { - .getPacketType = _IotMqtt_GetPacketType, - .getRemainingLength = _IotMqtt_GetRemainingLength, - .freePacket = _IotMqtt_FreePacket, - .serialize = - { - .connect = _IotMqtt_SerializeConnect, - .publish = _IotMqtt_SerializePublish, - .publishSetDup = _IotMqtt_PublishSetDup, - .puback = _IotMqtt_SerializePuback, - .subscribe = _IotMqtt_SerializeSubscribe, - .unsubscribe = _IotMqtt_SerializeUnsubscribe, - .pingreq = _IotMqtt_SerializePingreq, - .disconnect = _IotMqtt_SerializeDisconnect - }, - .deserialize = - { - .connack = _IotMqtt_DeserializeConnack, - .publish = _IotMqtt_DeserializePublish, - .puback = _IotMqtt_DeserializePuback, - .suback = _IotMqtt_DeserializeSuback, - .unsuback = _IotMqtt_DeserializeUnsuback, - .pingresp = _IotMqtt_DeserializePingresp - } - }; - -/** - * @brief The MQTT serializers to use in these tests. - */ - static const IotMqttSerializer_t * _pMqttSerializer = &_mqttSerializer; -#endif /* ifdef IOT_TEST_MQTT_SERIALIZER_INITIALIZER */ - -/* - * Return values of the mocked network functions. - */ -static IotNetworkError_t _createStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkCreate. */ -static IotNetworkError_t _setReceiveCallbackStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkSetReceiveCallback. */ -static IotNetworkError_t _sendStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkSend. */ -static IotNetworkError_t _closeStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkClose. */ -static IotNetworkError_t _destroyStatus = IOT_NETWORK_SUCCESS; /**< @brief Return value for #_networkDestroy. */ - -/** - * @brief Filler text to publish. - */ -static const char _pSamplePayload[] = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " - "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " - "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" - " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" - " culpa qui officia deserunt mollit anim id est laborum." - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " - "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " - "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" - " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" - " culpa qui officia deserunt mollit anim id est laborum."; - -/** - * @brief Length of #_pSamplePayload. - */ -static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; - -/** - * @brief Buffer holding the client identifier used for the tests. - */ -static char _pClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - -/*-----------------------------------------------------------*/ - -/** - * @brief Establish an MQTT connection. Retry if enabled. - */ -static IotMqttError_t _mqttConnect( const IotMqttNetworkInfo_t * pNetworkInfo, - const IotMqttConnectInfo_t * pConnectInfo, - uint32_t timeoutMs, - IotMqttConnection_t * const pMqttConnection ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - int32_t retryCount = 0; - - for( ; retryCount < IOT_TEST_MQTT_CONNECT_RETRY_COUNT; retryCount++ ) - { - status = IotMqtt_Connect( pNetworkInfo, pConnectInfo, timeoutMs, pMqttConnection ); - - #if ( IOT_TEST_MQTT_CONNECT_RETRY_COUNT > 1 ) - if( ( status == IOT_MQTT_NETWORK_ERROR ) || ( status == IOT_MQTT_TIMEOUT ) ) - { - /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. - * Initially wait until 1100 ms have elapsed since the last connection, then - * increase exponentially. */ - IotClock_SleepMs( 1100 << retryCount ); - } - else - { - break; - } - #endif - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Mocked network create function. - */ -static IotNetworkError_t _networkCreate( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ) -{ - ( void ) pServerInfo; - ( void ) pCredentialInfo; - - *pConnection = NULL; - - return _createStatus; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Mocked network set receive callback function. - */ -static IotNetworkError_t _networkSetReceiveCallback( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ) -{ - ( void ) pConnection; - ( void ) receiveCallback; - ( void ) pContext; - - return _setReceiveCallbackStatus; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Mocked network send function. - */ -static size_t _networkSend( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ) -{ - size_t bytesSent = 0; - - ( void ) pConnection; - ( void ) pMessage; - - if( _sendStatus == IOT_NETWORK_SUCCESS ) - { - bytesSent = messageLength; - } - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Mocked network close function. - */ -static IotNetworkError_t _networkClose( IotNetworkConnection_t pConnection ) -{ - ( void ) pConnection; - - return _closeStatus; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Mocked network destroy function. - */ -static IotNetworkError_t _networkDestroy( IotNetworkConnection_t pConnection ) -{ - ( void ) pConnection; - - return _destroyStatus; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscription callback function. Checks for valid parameters and unblocks - * the main test thread. - */ -static void _publishReceived( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - - /* If the received messages matches what was sent, unblock the waiting thread. */ - if( ( pPublish->u.message.info.payloadLength == _samplePayloadLength ) && - ( strncmp( pPublish->u.message.info.pPayload, - _pSamplePayload, - _samplePayloadLength ) == 0 ) ) - { - IotSemaphore_Post( pWaitSem ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A subscription callback function that blocks on a semaphore until signaled. - */ -static void _blockingCallback( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - IotSemaphore_t * pSemaphore = ( IotSemaphore_t * ) pArgument; - - /* Silence warnings about unused parameters. */ - ( void ) pPublish; - - /* Wait until signaled. */ - IotSemaphore_Wait( pSemaphore ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Callback that makes additional MQTT API calls. - */ -static void _reentrantCallback( void * pArgument, - IotMqttCallbackParam_t * pOperation ) -{ - bool status = true; - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; - IotSemaphore_t * pWaitSemaphores = ( IotSemaphore_t * ) pArgument; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); - const uint16_t topicLength = ( uint16_t ) strlen( pTopic ); - - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = pTopic; - publishInfo.topicNameLength = topicLength; - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; - publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; - - mqttStatus = IotMqtt_PublishSync( pOperation->mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - - if( mqttStatus == IOT_MQTT_SUCCESS ) - { - status = IotSemaphore_TimedWait( &( pWaitSemaphores[ 0 ] ), - IOT_TEST_MQTT_TIMEOUT_MS ); - } - else - { - status = false; - } - - /* Remove subscription and disconnect. */ - if( status == true ) - { - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = topicLength; - - mqttStatus = IotMqtt_UnsubscribeAsync( pOperation->mqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeOperation ); - - if( mqttStatus == IOT_MQTT_STATUS_PENDING ) - { - /* Disconnect the MQTT connection. */ - IotMqtt_Disconnect( pOperation->mqttConnection, 0 ); - - /* Waiting on an operation whose connection is closed should return - * "Network Error". */ - mqttStatus = IotMqtt_Wait( unsubscribeOperation, - 500 ); - - status = ( mqttStatus == IOT_MQTT_NETWORK_ERROR ); - } - else - { - status = false; - } - } - - /* Disconnect and unblock the main test thread. */ - if( status == true ) - { - IotSemaphore_Post( &( pWaitSemaphores[ 1 ] ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PUBACK that always fails. - */ -static IotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) -{ - ( void ) packetIdentifier; - ( void ) pPubackPacket; - ( void ) pPacketSize; - - return IOT_MQTT_NO_MEMORY; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Wait for a reference count to reach a target value, subject to a timeout. - */ -static bool _waitForCount( IotMutex_t * pMutex, - const int32_t * pReferenceCount, - int32_t target ) -{ - bool status = false; - int32_t referenceCount = 0; - uint32_t sleepCount = 0; - - /* Calculate limit on the number of times to sleep for 100 ms. */ - const uint32_t sleepLimit = ( IOT_TEST_MQTT_TIMEOUT_MS / 100 ) + - ( ( IOT_TEST_MQTT_TIMEOUT_MS % 100 ) != 0 ); - - /* Wait for the reference count to reach the target value. */ - for( sleepCount = 0; sleepCount < sleepLimit; sleepCount++ ) - { - /* Read reference count. */ - IotMutex_Lock( pMutex ); - referenceCount = *pReferenceCount; - IotMutex_Unlock( pMutex ); - - /* Exit if target value is reached. Otherwise, sleep. */ - if( referenceCount == target ) - { - status = true; - break; - } - else - { - IotClock_SleepMs( 100 ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT platform tests. - */ -TEST_GROUP( MQTT_Unit_Platform ); - -/** - * @brief Test group for MQTT platform tests requiring the network. - */ -TEST_GROUP( MQTT_System_Platform ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT platform tests. - */ -TEST_SETUP( MQTT_Unit_Platform ) -{ - /* Reset the network info and interface. */ - ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); - - _createStatus = IOT_NETWORK_SUCCESS; - _setReceiveCallbackStatus = IOT_NETWORK_SUCCESS; - _sendStatus = IOT_NETWORK_SUCCESS; - _closeStatus = IOT_NETWORK_SUCCESS; - _destroyStatus = IOT_NETWORK_SUCCESS; - - _networkInterface.create = _networkCreate; - _networkInterface.setReceiveCallback = _networkSetReceiveCallback; - _networkInterface.send = _networkSend; - _networkInterface.close = _networkClose; - _networkInterface.destroy = _networkDestroy; - - _networkInfo.pNetworkInterface = &_networkInterface; - - /* Initialize libraries. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT system platform tests. - */ -TEST_SETUP( MQTT_System_Platform ) -{ - /* Generate a new, unique client identifier based on the time if no client - * identifier is defined. Otherwise, copy the provided client identifier. */ - #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER - ( void ) snprintf( _pClientIdentifier, - CLIENT_IDENTIFIER_MAX_LENGTH, - "iot%llu", - ( long long unsigned int ) IotClock_GetTimeMs() ); - #else - ( void ) strncpy( _pClientIdentifier, - IOT_TEST_MQTT_CLIENT_IDENTIFIER, - CLIENT_IDENTIFIER_MAX_LENGTH ); - #endif - - /* Set the overrides for the default serializers. */ - #ifdef IOT_TEST_MQTT_SERIALIZER - _pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; - #endif - - /* Set the MQTT network setup parameters. */ - ( void ) memset( &_reentrantNetworkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - _reentrantNetworkInfo.createNetworkConnection = true; - _reentrantNetworkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - _reentrantNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - _reentrantNetworkInfo.pMqttSerializer = _pMqttSerializer; - - #if IOT_TEST_SECURED_CONNECTION == 1 - _reentrantNetworkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; - #endif - - /* Initialize libraries. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - TEST_ASSERT_EQUAL( IOT_NETWORK_SUCCESS, IotTestNetwork_Init() ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT platform tests. - */ -TEST_TEAR_DOWN( MQTT_Unit_Platform ) -{ - IotMqtt_Cleanup(); - IotSdk_Cleanup(); - - /* Clear the connection pointer. */ - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT system platform tests. - */ -TEST_TEAR_DOWN( MQTT_System_Platform ) -{ - IotMqtt_Cleanup(); - IotTestNetwork_Cleanup(); - IotSdk_Cleanup(); - - /* Clear the connection pointer. */ - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT platform tests. - */ -TEST_GROUP_RUNNER( MQTT_Unit_Platform ) -{ - RUN_TEST_CASE( MQTT_Unit_Platform, ConnectNetworkFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, ConnectScheduleFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, DisconnectNetworkFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, PingreqSendFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, PublishScheduleFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, PublishRetryScheduleFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, PubackScheduleSerializeFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionScheduleFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, NotifyScheduleFailure ); - RUN_TEST_CASE( MQTT_Unit_Platform, SingleThreaded ); - RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionReferences ); - RUN_TEST_CASE( MQTT_Unit_Platform, SubscriptionListTooLarge ); - RUN_TEST_CASE( MQTT_Unit_Platform, LongUserName ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT system platform tests. - */ -TEST_GROUP_RUNNER( MQTT_System_Platform ) -{ - RUN_TEST_CASE( MQTT_System_Platform, SubscribeCompleteReentrancy ); - RUN_TEST_CASE( MQTT_System_Platform, IncomingPublishReentrancy ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect when the network fails. - */ -TEST( MQTT_Unit_Platform, ConnectNetworkFailure ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - - /* Set test client identifier. */ - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - - /* Network connection creation failure. */ - _networkInfo.createNetworkConnection = true; - _createStatus = IOT_NETWORK_FAILURE; - status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); - - /* Set receive callback failure. */ - _createStatus = IOT_NETWORK_SUCCESS; - _setReceiveCallbackStatus = IOT_NETWORK_FAILURE; - status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); - - /* Failure in set receive callback, close, and destroy. */ - _closeStatus = IOT_NETWORK_FAILURE; - _destroyStatus = IOT_NETWORK_FAILURE; - status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); - - /* Failure to send MQTT Connect. */ - _setReceiveCallbackStatus = IOT_NETWORK_SUCCESS; - _closeStatus = IOT_NETWORK_SUCCESS; - _destroyStatus = IOT_NETWORK_SUCCESS; - _sendStatus = IOT_NETWORK_FAILURE; - status = IotMqtt_Connect( &_networkInfo, &connectInfo, TIMEOUT_MS, &mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_NETWORK_ERROR, status ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_connect when the keep-alive - * job fails to schedule. - */ -TEST( MQTT_Unit_Platform, ConnectScheduleFailure ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttConnection_t * pMqttConnection = NULL; - - /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Set an invalid status for the keep-alive job, preventing it from being - * scheduled. */ - pMqttConnection->pingreq.jobStorage.status = IOT_TASKPOOL_STATUS_COMPLETED; - status = IotTestMqtt_scheduleKeepAlive( pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, status ); - - /* Clean up. */ - pMqttConnection->references--; - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_disconnect when the network fails. - */ -TEST( MQTT_Unit_Platform, DisconnectNetworkFailure ) -{ - _mqttConnection_t * pMqttConnection = NULL; - - /* Call disconnect with a failing send. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - _sendStatus = IOT_NETWORK_FAILURE; - IotMqtt_Disconnect( pMqttConnection, 0 ); - _sendStatus = IOT_NETWORK_SUCCESS; - - /* Call disconnect with a failing close. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 100 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - _closeStatus = IOT_NETWORK_FAILURE; - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior when a PINGREQ cannot be sent. - */ -TEST( MQTT_Unit_Platform, PingreqSendFailure ) -{ - _mqttConnection_t * pMqttConnection = NULL; - - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 1 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Set a short keep alive period and sleep for at least that period. Otherwise, - * the PINGREQ will not be sent. */ - pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; - pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; - IotClock_SleepMs( SHORT_KEEP_ALIVE_MS * 2 ); - - _sendStatus = IOT_NETWORK_FAILURE; - _IotMqtt_ProcessKeepAlive( IOT_SYSTEM_TASKPOOL, pMqttConnection->pingreq.job, pMqttConnection ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_publishasync when scheduling fails. - */ -TEST( MQTT_Unit_Platform, PublishScheduleFailure ) -{ - IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; - IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - uint32_t maxThreads = 0; - - /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Set the task pool to an invalid state and cause all further scheduling to fail. */ - maxThreads = taskPool->maxThreads; - taskPool->maxThreads = 0; - - /* Send a QoS 0 publish that fails to schedule. */ - publishInfo.pTopicName = "test/"; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = ""; - publishInfo.payloadLength = 0; - - status = IotMqtt_PublishAsync( pMqttConnection, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, status ); - - /* Send a QoS 1 publish that fails to schedule. */ - publishInfo.qos = IOT_MQTT_QOS_1; - status = IotMqtt_PublishAsync( pMqttConnection, &publishInfo, 0, NULL, &publishOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, status ); - TEST_ASSERT_EQUAL( NULL, publishOperation ); - - /* Restore the task pool to a valid state. */ - taskPool->maxThreads = maxThreads; - - /* Clean up. */ - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior when a client-to-server PUBLISH retry fails to - * schedule. - */ -TEST( MQTT_Unit_Platform, PublishRetryScheduleFailure ) -{ - IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - _mqttOperation_t * pOperation = NULL; - IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; - uint32_t maxThreads = 0; - - /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Create a new PUBLISH with retry operation. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ) ); - TEST_ASSERT_NOT_NULL( pOperation ); - pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - pOperation->u.operation.periodic.retry.limit = 3; - pOperation->u.operation.periodic.retry.nextPeriodMs = TIMEOUT_MS; - pOperation->u.operation.pMqttPacket = IotMqtt_MallocMessage( PACKET_LENGTH ); - pOperation->u.operation.packetSize = PACKET_LENGTH; - - /* Set the task pool to an invalid state and cause all further scheduling to fail. */ - maxThreads = taskPool->maxThreads; - taskPool->maxThreads = 0; - - /* Send the MQTT PUBLISH. Retry will fail to schedule. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - TEST_ASSERT_EQUAL( IOT_MQTT_SCHEDULING_ERROR, pOperation->u.operation.status ); - TEST_ASSERT_EQUAL_UINT32( 1, IotSemaphore_GetCount( &( pOperation->u.operation.notify.waitSemaphore ) ) ); - TEST_ASSERT_EQUAL_UINT32( 1, pOperation->u.operation.periodic.retry.count ); - - /* Restore the task pool to a valid state. */ - taskPool->maxThreads = maxThreads; - - /* Clean up. */ - TEST_ASSERT_EQUAL_INT( true, _IotMqtt_DecrementOperationReferences( pOperation, false ) ); - _IotMqtt_DestroyOperation( pOperation ); - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of the client-to-server PUBACK when scheduling and - * serializing fail. - */ -TEST( MQTT_Unit_Platform, PubackScheduleSerializeFailure ) -{ - IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; - IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; - uint32_t maxThreads = 0; - - /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Set the task pool to an invalid state and cause all further scheduling to fail. */ - maxThreads = taskPool->maxThreads; - taskPool->maxThreads = 0; - - /* Call the function to send a PUBACK with scheduling failure. The failed PUBACK - * should be cleaned up and not create memory leaks. */ - IotTestMqtt_sendPuback( pMqttConnection, 1 ); - - /* Restore the task pool to a valid state. */ - taskPool->maxThreads = maxThreads; - - /* Call the function to send PUBACK with serializer failure. The failed PUBACK - * should be cleaned up and not create memory leaks. */ - serializer.serialize.puback = _serializePuback; - pMqttConnection->pSerializer = &serializer; - IotTestMqtt_sendPuback( pMqttConnection, 1 ); - - /* Clean up. */ - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_subscribeasync and - * @ref mqtt_function_unsubscribeasync when scheduling fails. - */ -TEST( MQTT_Unit_Platform, SubscriptionScheduleFailure ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; - IotMqttOperation_t subscriptionOperation = IOT_MQTT_OPERATION_INITIALIZER; - uint32_t maxThreads = 0; - - /* Set subscription parameters. */ - subscription.pTopicFilter = "test/"; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - - /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Set the task pool to an invalid state and cause all further scheduling to fail. */ - maxThreads = taskPool->maxThreads; - taskPool->maxThreads = 0; - - /* Send a SUBSCRIBE that fails to schedule. */ - status = IotMqtt_SubscribeAsync( pMqttConnection, &subscription, 1, 0, NULL, &subscriptionOperation ); - TEST_ASSERT_EQUAL( status, IOT_MQTT_SCHEDULING_ERROR ); - - /* Send an UNSUBSCRIBE that fails to schedule. */ - status = IotMqtt_UnsubscribeAsync( pMqttConnection, &subscription, 1, 0, NULL, &subscriptionOperation ); - TEST_ASSERT_EQUAL( status, IOT_MQTT_SCHEDULING_ERROR ); - - /* Restore the task pool to a valid state. */ - taskPool->maxThreads = maxThreads; - - /* Clean up. */ - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of #_IotMqtt_Notify when scheduling fails. - */ -TEST( MQTT_Unit_Platform, NotifyScheduleFailure ) -{ - IotMqttConnection_t pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - _mqttOperation_t * pOperation = NULL; - IotTaskPool_t taskPool = IOT_SYSTEM_TASKPOOL; - uint32_t maxThreads = 0; - - /* Create a new MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( false, &_networkInfo, 0 ); - TEST_ASSERT_NOT_NULL( pMqttConnection ); - - /* Create a new MQTT operation. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_CreateOperation( pMqttConnection, 0, NULL, &pOperation ) ); - TEST_ASSERT_NOT_NULL( pOperation ); - pOperation->u.operation.notify.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - - /* Set the task pool to an invalid state and cause all further scheduling to fail. */ - maxThreads = taskPool->maxThreads; - taskPool->maxThreads = 0; - - _IotMqtt_Notify( pOperation ); - - /* Restore the task pool to a valid state. */ - taskPool->maxThreads = maxThreads; - - /* Clean up. */ - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that MQTT can work in a single thread without the task pool. - */ -TEST( MQTT_Unit_Platform, SingleThreaded ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_SMALL; - - /* Shut down the system task pool to test if MQTT works without it. */ - IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); - - /* Set the members of the subscription. */ - subscription.pTopicFilter = TEST_TOPIC_NAME; - subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - - /* Set the members of the publish info. */ - publishInfo.pTopicName = TEST_TOPIC_NAME; - publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; - publishInfo.pPayload = "test"; - publishInfo.payloadLength = 4; - publishInfo.qos = IOT_MQTT_QOS_1; - - if( TEST_PROTECT() ) - { - /* Set up a mocked MQTT connection. */ - TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); - - /* Add a subscription. */ - status = IotMqtt_SubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Transmit a message with no retry. */ - status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Remove the subscription. */ - status = IotMqtt_UnsubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - /* Re-initialize the system task pool. The task pool must be available to - * send messages with a retry. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); - - /* Transmit a message with a retry. */ - publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; - publishInfo.retryMs = DUP_CHECK_RETRY_MS; - status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); - TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - - IotTest_MqttMockCleanup(); - } - else - { - /* Re-initialize the system task pool for test tear down. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that subscriptions are properly reference counted. - */ -TEST( MQTT_Unit_Platform, SubscriptionReferences ) -{ - int32_t i = 0; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - _mqttOperation_t * pIncomingPublish[ 3 ] = { NULL }; - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink; - IotSemaphore_t waitSem; - _mqttConnection_t * pMqttConnection = NULL; - - /* Create an MQTT connection. */ - pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &_networkInfo, - 0 ); - - /* Adjustment to reference count based on keep-alive status. */ - const int32_t keepAliveReference = 1 + ( ( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) ? 1 : 0 ); - - #if ( IOT_STATIC_MEMORY_ONLY == 1 ) && ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS < 3 ) - #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS must be at least 3 for SubscriptionReferences test." - #endif - - /* The MQTT task pool must support at least 3 threads for this test to run successfully. */ - TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_SetMaxThreads( IOT_SYSTEM_TASKPOOL, 4 ) ); - - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 3 ) ); - - /* Set the subscription info. */ - subscription.pTopicFilter = "/test"; - subscription.topicFilterLength = 5; - subscription.callback.function = _blockingCallback; - subscription.callback.pCallbackContext = &waitSem; - - /* Add the subscriptions. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( pMqttConnection, - 1, - &subscription, - 1 ) ); - - /* Get the pointer to the subscription in the MQTT connection. */ - pSubscriptionLink = IotListDouble_PeekHead( &( pMqttConnection->subscriptionList ) ); - TEST_ASSERT_NOT_NULL( pSubscriptionLink ); - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - TEST_ASSERT_NOT_NULL( pSubscription ); - - /* Create 3 incoming PUBLISH messages that match the subscription. */ - for( i = 0; i < 3; i++ ) - { - pIncomingPublish[ i ] = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - TEST_ASSERT_NOT_NULL( pIncomingPublish ); - - ( void ) memset( pIncomingPublish[ i ], 0x00, sizeof( _mqttOperation_t ) ); - pIncomingPublish[ i ]->incomingPublish = true; - pIncomingPublish[ i ]->pMqttConnection = pMqttConnection; - pIncomingPublish[ i ]->u.publish.publishInfo.pTopicName = "/test"; - pIncomingPublish[ i ]->u.publish.publishInfo.topicNameLength = 5; - pIncomingPublish[ i ]->u.publish.publishInfo.pPayload = ""; - pIncomingPublish[ i ]->u.publish.pReceivedData = IotMqtt_MallocMessage( 1 ); - - IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), - &( pIncomingPublish[ i ]->link ) ); - } - - if( TEST_PROTECT() ) - { - /* Schedule 3 callback invocations for the incoming PUBLISH. */ - for( i = 0; i < 3; i++ ) - { - TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( pMqttConnection ) ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pIncomingPublish[ i ], - _IotMqtt_ProcessIncomingPublish, - 0 ) ); - } - - /* Wait for the connection reference count to reach 3 (adjusted for possible keep-alive). */ - TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( pMqttConnection->referencesMutex ), - &( pMqttConnection->references ), - 3 + keepAliveReference ) ); - - /* Check that the subscription also has a reference count of 3. */ - TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( pMqttConnection->subscriptionMutex ), - &( pSubscription->references ), - 3 ) ); - - /* Post to the wait semaphore, which unblocks one subscription callback. */ - IotSemaphore_Post( &waitSem ); - - /* Wait for the connection reference count to decrease to 2 (adjusted for - * possible keep-alive). Check that the subscription reference count also - * decreases to 2. */ - TEST_ASSERT_EQUAL_INT( true, _waitForCount( &( pMqttConnection->referencesMutex ), - &( pMqttConnection->references ), - 2 + keepAliveReference ) ); - TEST_ASSERT_EQUAL_INT32( true, _waitForCount( &( pMqttConnection->subscriptionMutex ), - &( pSubscription->references ), - 2 ) ); - - /* Shut down the MQTT connection. */ - IotMqtt_Disconnect( pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - - /* Post twice to the wait semaphore, which unblocks the remaining blocking - * callbacks. */ - IotSemaphore_Post( &waitSem ); - IotSemaphore_Post( &waitSem ); - - /* Wait for the callbacks to exit. */ - while( IotSemaphore_GetCount( &waitSem ) > 0 ) - { - IotClock_SleepMs( 100 ); - } - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test the behavior when the subscription list exceeds the size of an MQTT - * packet. Requires a large amount of memory not available on smaller systems. - */ -TEST( MQTT_Unit_Platform, SubscriptionListTooLarge ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - size_t subscriptionCount = MQTT_MAX_REMAINING_LENGTH / UINT16_MAX + 1, i = 0; - size_t remainingLength = 0, packetSize = 0; - uint16_t packetIdentifier = 0; - uint8_t * pPacket = NULL; - IotMqttSubscription_t * pSubscriptionList = IotTest_Malloc( subscriptionCount * sizeof( IotMqttSubscription_t ) ); - - TEST_ASSERT_NOT_NULL( pSubscriptionList ); - ( void ) memset( pSubscriptionList, 0x00, subscriptionCount * sizeof( IotMqttSubscription_t ) ); - - for( i = 0; i < subscriptionCount; i++ ) - { - pSubscriptionList[ i ].topicFilterLength = UINT16_MAX; - } - - status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - /* Attempt to serialize SUBSCRIBE and UNSUBSCRIBE when the subscription list is too large. */ - status = _IotMqtt_SerializeSubscribe( pSubscriptionList, - subscriptionCount, - &pPacket, - &packetSize, - &packetIdentifier ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - status = _IotMqtt_SerializeUnsubscribe( pSubscriptionList, - subscriptionCount, - &pPacket, - &packetSize, - &packetIdentifier ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - - IotTest_Free( pSubscriptionList ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test the behavior when the maximum length user name. Requires a large - * amount of memory not available on smaller systems. - */ -TEST( MQTT_Unit_Platform, LongUserName ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - size_t remainingLength = 0, packetSize = 0; - uint8_t * pConnectPacket = NULL; - - char * pUserName = IotTest_Malloc( UINT16_MAX ); - - TEST_ASSERT_NOT_NULL( pUserName ); - - ( void ) memset( pUserName, ( int ) 'a', UINT16_MAX ); - - connectInfo.awsIotMqttMode = true; - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - connectInfo.pUserName = pUserName; - connectInfo.userNameLength = UINT16_MAX; - - status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - TEST_ASSERT_NOT_EQUAL( 0, packetSize ); - - pConnectPacket = IotTest_Malloc( packetSize ); - TEST_ASSERT_NOT_NULL( pConnectPacket ); - status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, pConnectPacket, packetSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - IotTest_Free( pConnectPacket ); - IotTest_Free( pUserName ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that API functions can be invoked from a callback for a completed - * subscription operation. - */ -TEST( MQTT_System_Platform, SubscribeCompleteReentrancy ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - - /* Two semaphores are needed for this test: one for incoming PUBLISH and one - * for test completion. */ - IotSemaphore_t pWaitSemaphores[ 2 ]; - - /* The topic to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); - - /* Create the semaphores. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - if( TEST_PROTECT() ) - { - /* Establish the MQTT connection. */ - status = _mqttConnect( &_reentrantNetworkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Subscribe with a completion callback. */ - subscription.qos = IOT_MQTT_QOS_1; - subscription.pTopicFilter = pTopic; - subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); - subscription.callback.function = _publishReceived; - subscription.callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); - - callbackInfo.function = _reentrantCallback; - callbackInfo.pCallbackContext = pWaitSemaphores; - - status = IotMqtt_SubscribeAsync( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); - - /* Wait for the reentrant callback to complete. */ - if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); - } - } - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that API functions can be invoked from a callback for an incoming - * PUBLISH. - */ -TEST( MQTT_System_Platform, IncomingPublishReentrancy ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t pSubscription[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Two semaphores are needed for this test: one for incoming PUBLISH and one - * for test completion. */ - IotSemaphore_t pWaitSemaphores[ 2 ]; - - /* The topics to use for this test. */ - GENERATE_TOPIC_WITH_SUFFIX( pTopic1, "/IncomingPublishReentrancy" ); - GENERATE_TOPIC_WITH_SUFFIX( pTopic2, "/Reentrancy" ); - - /* Create the semaphores. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 1 ] ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - if( TEST_PROTECT() ) - { - /* Establish the MQTT connection. */ - status = _mqttConnect( &_reentrantNetworkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Subscribe with to the test topics. */ - pSubscription[ 0 ].qos = IOT_MQTT_QOS_1; - pSubscription[ 0 ].pTopicFilter = pTopic1; - pSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 0 ].pTopicFilter ); - pSubscription[ 0 ].callback.function = _reentrantCallback; - pSubscription[ 0 ].callback.pCallbackContext = pWaitSemaphores; - - pSubscription[ 1 ].qos = IOT_MQTT_QOS_1; - pSubscription[ 1 ].pTopicFilter = pTopic2; - pSubscription[ 1 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 1 ].pTopicFilter ); - pSubscription[ 1 ].callback.function = _publishReceived; - pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); - - status = IotMqtt_SubscribeSync( _mqttConnection, - pSubscription, - 2, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Publish a message to the test topic. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = pTopic1; - publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryLimit = IOT_TEST_MQTT_PUBLISH_RETRY_COUNT; - publishInfo.retryMs = IOT_TEST_MQTT_TIMEOUT_MS; - - status = IotMqtt_PublishSync( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Wait for the reentrant callback to complete. */ - if( IotSemaphore_TimedWait( &( pWaitSemaphores[ 1 ] ), - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for reentrant callback." ); - } - } - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 1 ] ) ); - } - - IotSemaphore_Destroy( &( pWaitSemaphores[ 0 ] ) ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c deleted file mode 100644 index 91b2c63cb1..0000000000 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ /dev/null @@ -1,1905 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_receive.c - * @brief Tests for the function @ref mqtt_function_receivecallback. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* MQTT test access include. */ -#include "iot_test_access_mqtt.h" - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false -#endif - -/** @brief Default CONNACK packet for the receive tests. */ -static const uint8_t _pConnackTemplate[] = { 0x20, 0x02, 0x00, 0x00 }; -/** @brief Default PUBLISH packet for the receive tests. */ -static const uint8_t _pPublishTemplate[] = -{ - 0x30, 0x8d, 0x02, 0x00, 0x0b, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x74, 0x6f, 0x70, 0x69, 0x63, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, - 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, - 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, - 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, - 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, - 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, - 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff -}; -/** @brief Default PUBACK packet for the receive tests. */ -static const uint8_t _pPubackTemplate[] = { 0x40, 0x02, 0x00, 0x01 }; -/** @brief Default SUBACK packet for the receive tests. */ -static const uint8_t _pSubackTemplate[] = { 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02 }; -/** @brief Default UNSUBACK packet for the receive tests. */ -static const uint8_t _pUnsubackTemplate[] = { 0xb0, 0x02, 0x00, 0x01 }; -/** @brief Default PINGRESP packet for the receive tests. */ -static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; - -/*-----------------------------------------------------------*/ - -/** - * @brief Topic name and filter used in the tests. - */ -#define TEST_TOPIC_NAME "/test/topic" - -/** - * @brief Length of #TEST_TOPIC_NAME. - */ -#define TEST_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) - -/** - * @brief Timeout for waiting on a PUBLISH callback. - */ -#define PUBLISH_CALLBACK_TIMEOUT ( 1000 ) - -/** - * @brief Declare a buffer holding a packet and its size. - */ -#define DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ - uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ - const size_t sizeName = sizeof( pTemplate ); \ - ( void ) memcpy( bufferName, pTemplate, sizeName ); - -/** - * @brief Initializer for operations in the tests. - */ -#define INITIALIZE_OPERATION( name ) \ - { \ - .link = { 0 }, .incomingPublish = false, .pMqttConnection = NULL, \ - .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ - .u.operation = \ - { \ - .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ - .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ - .notify = { .callback = { 0 } },.status = IOT_MQTT_STATUS_PENDING, \ - .periodic = { .retry = { 0 } } \ - } \ - } - -/*-----------------------------------------------------------*/ - -/** - * @brief Context for calls to the network receive function. - */ -typedef struct _receiveContext -{ - const uint8_t * pData; /**< @brief The data to receive. */ - size_t dataLength; /**< @brief Length of data. */ - size_t dataIndex; /**< @brief Next byte of data to read. */ -} _receiveContext_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief The MQTT connection shared by all the tests. - */ -static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief The network interface shared by all the tests. - */ -static IotNetworkInterface_t _networkInterface = { 0 }; - -/** - * @brief The subscription shared by all the tests. - */ -static IotMqttSubscription_t _subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - -/** - * @brief Tracks whether a deserializer override was called for a test. - */ -static bool _deserializeOverrideCalled = false; - -/** - * @brief Tracks whether #_getPacketType has been called. - */ -static bool _getPacketTypeCalled = false; - -/** - * @brief Tracks whether #_getRemainingLength has been called. - */ -static bool _getRemainingLengthCalled = false; - -/** - * @brief Tracks whether #_close has been called. - */ -static bool _networkCloseCalled = false; - -/** - * @brief Tracks whether #_disconnectCallback has been called. - */ -static bool _disconnectCallbackCalled = false; - -/*-----------------------------------------------------------*/ - -/** - * @brief Get packet type function override. - */ -static uint8_t _getPacketType( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ) -{ - _getPacketTypeCalled = true; - - return _IotMqtt_GetPacketType( pNetworkConnection, pNetworkInterface ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Get remaining length function override. - */ -static size_t _getRemainingLength( IotNetworkConnection_t pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ) -{ - _getRemainingLengthCalled = true; - - return _IotMqtt_GetRemainingLength( pNetworkConnection, pNetworkInterface ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Serializer override for PUBACK. - */ -static IotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) -{ - return _IotMqtt_SerializePuback( packetIdentifier, - pPubackPacket, - pPacketSize ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Deserializer override for CONNACK. - */ -static IotMqttError_t _deserializeConnack( _mqttPacket_t * pConnack ) -{ - _deserializeOverrideCalled = true; - - return _IotMqtt_DeserializeConnack( pConnack ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Deserializer override for PUBLISH. - */ -static IotMqttError_t _deserializePublish( _mqttPacket_t * pPublish ) -{ - _deserializeOverrideCalled = true; - - return _IotMqtt_DeserializePublish( pPublish ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Deserializer override for PUBACK. - */ -static IotMqttError_t _deserializePuback( _mqttPacket_t * pPuback ) -{ - _deserializeOverrideCalled = true; - - return _IotMqtt_DeserializePuback( pPuback ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Deserializer override for SUBACK. - */ -static IotMqttError_t _deserializeSuback( _mqttPacket_t * pSuback ) -{ - _deserializeOverrideCalled = true; - - return _IotMqtt_DeserializeSuback( pSuback ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Deserializer override for UNSUBACK. - */ -static IotMqttError_t _deserializeUnsuback( _mqttPacket_t * pUnsuback ) -{ - _deserializeOverrideCalled = true; - - return _IotMqtt_DeserializeUnsuback( pUnsuback ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Deserializer override for PINGRESP. - */ -static IotMqttError_t _deserializePingresp( _mqttPacket_t * pPingresp ) -{ - _deserializeOverrideCalled = true; - - return _IotMqtt_DeserializePingresp( pPingresp ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Reset the status of an #_mqttOperation_t and push it to the list of - * MQTT operations awaiting network response. - */ -static void _operationResetAndPush( _mqttOperation_t * pOperation ) -{ - pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; - pOperation->u.operation.jobReference = 1; - IotListDouble_InsertHead( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Process a non-PUBLISH buffer and check the result. - */ -static bool _processBuffer( const _mqttOperation_t * pOperation, - const uint8_t * pBuffer, - size_t bufferSize, - IotMqttError_t expectedResult ) -{ - bool status = true; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pBuffer; - receiveContext.dataLength = bufferSize; - - /* Call the receive callback on pBuffer. */ - IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, - _pMqttConnection ); - - /* Check expected result if operation is given. */ - if( pOperation != NULL ) - { - status = ( expectedResult == pOperation->u.operation.status ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Process a PUBLISH message and check the result. - */ -static bool _processPublish( const uint8_t * pPublish, - size_t publishSize, - uint32_t expectedInvokeCount ) -{ - IotSemaphore_t invokeCount; - uint32_t finalInvokeCount = 0, i = 0; - bool waitResult = true; - _receiveContext_t receiveContext = { 0 }; - - /* Create a semaphore that counts how many times the publish callback is invoked. */ - if( expectedInvokeCount > 0 ) - { - if( IotSemaphore_Create( &invokeCount, 0, expectedInvokeCount ) == false ) - { - return false; - } - } - - /* Set the subscription parameter. */ - if( IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) == false ) - { - _mqttSubscription_t * pSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ), - link ); - pSubscription->callback.pCallbackContext = &invokeCount; - } - - /* Set the members of the receive context. */ - receiveContext.pData = pPublish; - receiveContext.dataLength = publishSize; - - /* Call the receive callback on pPublish. */ - IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, - _pMqttConnection ); - - /* Check how many times the publish callback is invoked. */ - for( i = 0; i < expectedInvokeCount; i++ ) - { - waitResult = IotSemaphore_TimedWait( &invokeCount, - PUBLISH_CALLBACK_TIMEOUT ); - - if( waitResult == false ) - { - break; - } - } - - /* Ensure that the invoke count semaphore has a value of 0, then destroy the - * semaphore. */ - if( expectedInvokeCount > 0 ) - { - finalInvokeCount = IotSemaphore_GetCount( &invokeCount ); - IotSemaphore_Destroy( &invokeCount ); - } - - /* Check results against expected values. */ - return ( finalInvokeCount == 0 ) && - ( waitResult == true ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Called when a PUBLISH message is "received". - */ -static void _publishCallback( void * pCallbackContext, - IotMqttCallbackParam_t * pPublish ) -{ - IotSemaphore_t * pInvokeCount = ( IotSemaphore_t * ) pCallbackContext; - - /* QoS 2 is valid for these tests, but currently unsupported by the MQTT library. - * Change the QoS to 0 so that QoS validation passes. */ - pPublish->u.message.info.qos = IOT_MQTT_QOS_0; - - /* Check that the parameters to this function are valid. */ - if( ( _IotMqtt_ValidatePublish( AWS_IOT_MQTT_SERVER, - &( pPublish->u.message.info ), - 0, - NULL, - NULL ) == true ) && - ( pPublish->u.message.info.topicNameLength == TEST_TOPIC_LENGTH ) && - ( strncmp( TEST_TOPIC_NAME, pPublish->u.message.info.pTopicName, TEST_TOPIC_LENGTH ) == 0 ) ) - { - IotSemaphore_Post( pInvokeCount ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Simulates a network receive function. - */ -static size_t _receive( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - size_t bytesReceived = 0; - _receiveContext_t * pReceiveContext = ( _receiveContext_t * ) pConnection; - - if( pReceiveContext->dataIndex != pReceiveContext->dataLength ) - { - TEST_ASSERT_NOT_EQUAL( 0, bytesRequested ); - TEST_ASSERT_LESS_THAN( pReceiveContext->dataLength, pReceiveContext->dataIndex ); - - /* Calculate how much data to copy. */ - const size_t dataAvailable = pReceiveContext->dataLength - pReceiveContext->dataIndex; - - if( bytesRequested > dataAvailable ) - { - bytesReceived = dataAvailable; - } - else - { - bytesReceived = bytesRequested; - } - - /* Copy data into given buffer. */ - if( bytesReceived > 0 ) - { - ( void ) memcpy( pBuffer, - pReceiveContext->pData + pReceiveContext->dataIndex, - bytesReceived ); - - pReceiveContext->dataIndex += bytesReceived; - } - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A network send function that checks the message is a PUBACK. - */ -static size_t _checkPuback( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ) -{ - _mqttPacket_t pubackPacket = { .u = { 0 } }; - IotMqttError_t deserializeStatus = IOT_MQTT_STATUS_PENDING; - - /* Silence warnings about unused parameters. */ - ( void ) pConnection; - - /* All PUBACK packets should be 4 bytes long. */ - TEST_ASSERT_EQUAL( 4, messageLength ); - - /* Deserialize the PUBACK. */ - pubackPacket.type = MQTT_PACKET_TYPE_PUBACK; - pubackPacket.remainingLength = 2; - pubackPacket.pRemainingData = ( uint8_t * ) ( pMessage + 2 ); - deserializeStatus = _IotMqtt_DeserializePuback( &pubackPacket ); - - /* Check the results of the PUBACK deserialization. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, deserializeStatus ); - TEST_ASSERT_EQUAL( 1, pubackPacket.packetIdentifier ); - - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A network close function that reports if it was invoked. - */ -static IotNetworkError_t _close( IotNetworkConnection_t pConnection ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pConnection; - - /* Report that this function was called. */ - _networkCloseCalled = true; - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A disconnect callback function that checks for a "bad packet" reason - * and reports if it was invoked. - */ -static void _disconnectCallback( void * pCallbackContext, - IotMqttCallbackParam_t * pCallbackParam ) -{ - /* Silence warnings about unused parameters. */ - ( void ) pCallbackContext; - - if( pCallbackParam->u.disconnectReason == IOT_MQTT_BAD_PACKET_RECEIVED ) - { - _disconnectCallbackCalled = true; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Common code for PUBLISH malloc failure tests. - */ -static void _publishMallocFail( IotMqttQos_t qos ) -{ - int32_t i = 0; - bool status = false; - - for( i = 0; ; i++ ) - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - - if( qos == IOT_MQTT_QOS_1 ) - { - pPublish[ 0 ] = 0x32; - } - - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Attempt to process a PUBLISH. Memory allocation will fail at various - * times during this call. */ - status = _processPublish( pPublish, publishSize, 1 ); - - /* Exit once the publish is successfully processed. */ - if( status == true ) - { - break; - } - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); - } - - UnityMalloc_MakeMallocFailAfterCount( -1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT Receive tests. - */ -TEST_GROUP( MQTT_Unit_Receive ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT Receive tests. - */ -TEST_SETUP( MQTT_Unit_Receive ) -{ - static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - - /* Initialize SDK. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - - /* Set the deserializer overrides. */ - serializer.serialize.puback = _serializePuback; - serializer.deserialize.connack = _deserializeConnack; - serializer.deserialize.publish = _deserializePublish; - serializer.deserialize.puback = _deserializePuback; - serializer.deserialize.suback = _deserializeSuback; - serializer.deserialize.unsuback = _deserializeUnsuback; - serializer.deserialize.pingresp = _deserializePingresp; - serializer.getPacketType = _getPacketType; - serializer.getRemainingLength = _getRemainingLength; - - _networkInterface.send = _checkPuback; - _networkInterface.receive = _receive; - _networkInterface.close = _close; - networkInfo.pNetworkInterface = &_networkInterface; - networkInfo.disconnectCallback.function = _disconnectCallback; - - /* Initialize the MQTT connection used by the tests. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - /* Set the MQTT serializer overrides. */ - _pMqttConnection->pSerializer = &serializer; - - /* Set the members of the subscription. */ - _subscription.pTopicFilter = TEST_TOPIC_NAME; - _subscription.topicFilterLength = TEST_TOPIC_LENGTH; - _subscription.callback.function = _publishCallback; - - /* Add the subscription to the MQTT connection. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - &_subscription, - 1 ) ); - - /* Clear functions called flags. */ - _deserializeOverrideCalled = false; - _getPacketTypeCalled = false; - _getRemainingLengthCalled = false; - _networkCloseCalled = false; - _disconnectCallbackCalled = false; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT Receive tests. - */ -TEST_TEAR_DOWN( MQTT_Unit_Receive ) -{ - /* Clean up resources taken in test setup. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - IotMqtt_Cleanup(); - IotSdk_Cleanup(); - - /* Check that the tests used a deserializer override. */ - TEST_ASSERT_EQUAL_INT( true, _deserializeOverrideCalled ); - TEST_ASSERT_EQUAL_INT( true, _getPacketTypeCalled ); - TEST_ASSERT_EQUAL_INT( true, _getRemainingLengthCalled ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT Receive tests. - */ -TEST_GROUP_RUNNER( MQTT_Unit_Receive ) -{ - RUN_TEST_CASE( MQTT_Unit_Receive, DecodeRemainingLength ); - RUN_TEST_CASE( MQTT_Unit_Receive, InvalidPacket ); - RUN_TEST_CASE( MQTT_Unit_Receive, ReceiveMallocFail ); - RUN_TEST_CASE( MQTT_Unit_Receive, ConnackValid ); - RUN_TEST_CASE( MQTT_Unit_Receive, ConnackInvalid ); - RUN_TEST_CASE( MQTT_Unit_Receive, PublishValid ); - RUN_TEST_CASE( MQTT_Unit_Receive, PublishInvalid ); - RUN_TEST_CASE( MQTT_Unit_Receive, PublishResourceFailure ); - RUN_TEST_CASE( MQTT_Unit_Receive, PubackValid ); - RUN_TEST_CASE( MQTT_Unit_Receive, PubackInvalid ); - RUN_TEST_CASE( MQTT_Unit_Receive, SubackValid ); - RUN_TEST_CASE( MQTT_Unit_Receive, SubackInvalid ); - RUN_TEST_CASE( MQTT_Unit_Receive, UnsubackValid ); - RUN_TEST_CASE( MQTT_Unit_Receive, UnsubackInvalid ); - RUN_TEST_CASE( MQTT_Unit_Receive, Pingresp ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the function for decoding MQTT remaining length. - */ -TEST( MQTT_Unit_Receive, DecodeRemainingLength ) -{ - /* Decode 0, which has a 1-byte representation. */ - { - uint8_t pRemainingLength[ 4 ] = { 0 }; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pRemainingLength; - receiveContext.dataLength = 4; - - TEST_ASSERT_EQUAL( 0, - _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ) ); - } - - /* Decode 129, which has a 2-byte representation. */ - { - uint8_t pRemainingLength[ 4 ] = { 0x81, 0x01, 0x00, 0x00 }; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pRemainingLength; - receiveContext.dataLength = 4; - - TEST_ASSERT_EQUAL( 129, - _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ) ); - } - - /* Decode 16,386, which has a 3-byte representation. */ - { - uint8_t pRemainingLength[ 4 ] = { 0x82, 0x80, 0x01, 0x00 }; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pRemainingLength; - receiveContext.dataLength = 4; - - TEST_ASSERT_EQUAL( 16386, - _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ) ); - } - - /* Decode 268,435,455, which has a 4-byte representation. This value is the - * largest representable value. */ - { - uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x7f }; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pRemainingLength; - receiveContext.dataLength = 4; - - TEST_ASSERT_EQUAL( 268435455, - _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ) ); - } - - /* Attempt to decode an invalid value where all continuation bits are set. */ - { - uint8_t pRemainingLength[ 4 ] = { 0xff, 0xff, 0xff, 0x8f }; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pRemainingLength; - receiveContext.dataLength = 4; - - TEST_ASSERT_EQUAL( MQTT_REMAINING_LENGTH_INVALID, - _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ) ); - } - - /* Attempt to decode a 4-byte representation of 0. According to the spec, - * this representation is not valid. */ - { - uint8_t pRemainingLength[ 4 ] = { 0x80, 0x80, 0x80, 0x00 }; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = pRemainingLength; - receiveContext.dataLength = 4; - - TEST_ASSERT_EQUAL( MQTT_REMAINING_LENGTH_INVALID, - _IotMqtt_GetRemainingLength( ( IotNetworkConnection_t ) &receiveContext, - &_networkInterface ) ); - } - - /* Test tear down for this test group checks that deserializer overrides - * were called. However, this test does not use any deserializer overrides; - * set these values to true so that the checks pass. */ - _deserializeOverrideCalled = true; - _getPacketTypeCalled = true; - _getRemainingLengthCalled = true; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with an - * invalid control packet type. - */ -TEST( MQTT_Unit_Receive, InvalidPacket ) -{ - uint8_t invalidPacket = 0xf0; - _receiveContext_t receiveContext = { 0 }; - - /* Set the members of the receive context. */ - receiveContext.pData = &invalidPacket; - receiveContext.dataLength = 1; - - /* Processing a control packet 0xf is a protocol violation. */ - IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, - _pMqttConnection ); - - /* Processing an invalid packet should cause the network connection to be closed. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - - /* This test should not have called any deserializer. Set the deserialize - * override called flag to true so that the check for it passes. */ - TEST_ASSERT_EQUAL_INT( false, _deserializeOverrideCalled ); - _deserializeOverrideCalled = true; - TEST_ASSERT_EQUAL_INT( false, _getRemainingLengthCalled ); - _getRemainingLengthCalled = true; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback when memory - * allocation fails. - */ -TEST( MQTT_Unit_Receive, ReceiveMallocFail ) -{ - /* Logging uses malloc and will interfere with this test. Only run if logging - * is disabled. */ - #if ( LIBRARY_LOG_LEVEL == IOT_LOG_NONE ) - _receiveContext_t receiveContext = { 0 }; - - /* Data stream to process. Contains 2 SUBACKs. */ - const uint8_t pDataStream[] = - { - 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02, - 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02 - }; - - /* Set the members of the receive context. */ - receiveContext.pData = pDataStream; - receiveContext.dataLength = sizeof( pDataStream ); - - /* Set malloc to fail and process the first SUBACK. */ - UnityMalloc_MakeMallocFailAfterCount( 0 ); - IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, - _pMqttConnection ); - - /* Allow the use of malloc and process the second SUBACK. */ - UnityMalloc_MakeMallocFailAfterCount( -1 ); - IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, - _pMqttConnection ); - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); - #else /* if ( LIBRARY_LOG_LEVEL == IOT_LOG_NONE ) */ - - /* Test tear down for this test group checks that deserializer overrides - * were called. Set these values to true so that the checks pass. */ - _deserializeOverrideCalled = true; - _getPacketTypeCalled = true; - _getRemainingLengthCalled = true; - #endif /* if LIBRARY_LOG_LEVEL != IOT_LOG_NONE */ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a spec-compliant - * CONNACK. - */ -TEST( MQTT_Unit_Receive, ConnackValid ) -{ - uint8_t i = 0; - _mqttOperation_t connect = INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); - - connect.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - /* Even though no CONNECT is in the receive queue, 4 bytes should still be - * processed (should not crash). */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_STATUS_PENDING ) ); - } - - /* Process a valid, successful CONNACK with no SP flag. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - _operationResetAndPush( &connect ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_SUCCESS ) ); - } - - /* Process a valid, successful CONNACK with SP flag set. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 2 ] = 0x01; - _operationResetAndPush( &connect ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_SUCCESS ) ); - } - - /* Check each of the CONNACK failure codes, which range from 1 to 5. */ - for( i = 0x01; i < 0x06; i++ ) - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - - /* Set the CONNECT state and code. */ - _operationResetAndPush( &connect ); - pConnack[ 3 ] = i; - - /* Push a CONNECT operation to the queue and process a CONNACK. */ - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_SERVER_REFUSED ) ); - } - - IotSemaphore_Destroy( &( connect.u.operation.notify.waitSemaphore ) ); - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a - * CONNACK that doesn't comply to MQTT spec. - */ -TEST( MQTT_Unit_Receive, ConnackInvalid ) -{ - _mqttOperation_t connect = INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); - - connect.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - /* An incomplete CONNACK should not be processed, and no status should be set. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - _operationResetAndPush( &connect ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize - 1, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The CONNACK control packet type must be 0x20. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 0 ] = 0x21; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* A CONNACK must have a remaining length of 2. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 1 ] = 0x01; - _operationResetAndPush( &connect ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The reserved bits in CONNACK must be 0. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 2 ] = 0x80; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The fourth byte of CONNACK must be 0 if the SP flag is set. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 2 ] = 0x01; - pConnack[ 3 ] = 0x01; - _operationResetAndPush( &connect ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* CONNACK return codes cannot be above 5. */ - { - DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); - pConnack[ 3 ] = 0x06; - _operationResetAndPush( &connect ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, - pConnack, - connackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - IotSemaphore_Destroy( &( connect.u.operation.notify.waitSemaphore ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a spec-compliant - * PUBLISH. - */ -TEST( MQTT_Unit_Receive, PublishValid ) -{ - /* Process a valid QoS 0 PUBLISH. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 1 ) ); - } - - /* Process a valid QoS 1 PUBLISH. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 0 ] = 0x32; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 1 ) ); - } - - /* Process a valid QoS 2 PUBLISH. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 0 ] = 0x34; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 1 ) ); - } - - /* Process a valid PUBLISH with DUP flag set. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 0 ] = 0x38; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 1 ) ); - } - - /* Remove the subscription. Even though there is no matching subscription, - * all bytes of the PUBLISH should still be processed (should not crash). */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - - _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, - &_subscription, - 1 ); - - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 0 ) ); - } - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a PUBLISH - * that doesn't comply to MQTT spec. - */ -TEST( MQTT_Unit_Receive, PublishInvalid ) -{ - /* Attempting to process a packet smaller than 5 bytes should result in no - * bytes processed. 5 bytes is the minimum size of a PUBLISH. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - 4, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The PUBLISH cannot have a QoS of 3. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 0 ] = 0x36; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a PUBLISH with an invalid "Remaining length". */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 1 ] = 0xff; - pPublish[ 2 ] = 0xff; - pPublish[ 3 ] = 0xff; - pPublish[ 4 ] = 0xff; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a PUBLISH where some bytes could not be received. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 2 ] = 0x03; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a PUBLISH with a "Remaining length" smaller than the - * spec allows. */ - { - /* QoS 0. */ - DECLARE_PACKET( _pPublishTemplate, pPublish0, publish0Size ); - pPublish0[ 1 ] = 0x02; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish0, - publish0Size, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - _networkCloseCalled = false; - - /* QoS 1. */ - DECLARE_PACKET( _pPublishTemplate, pPublish1, publish1Size ); - pPublish1[ 0 ] = 0x32; - pPublish1[ 1 ] = 0x04; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish1, - publish1Size, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a PUBLISH with a "Remaining length" smaller than the - * end of the variable header. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 1 ] = 0x0a; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a PUBLISH with packet identifier 0. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - pPublish[ 0 ] = 0x32; - pPublish[ 17 ] = 0x00; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, - publishSize, - 0 ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with errors - * such as memory allocation failure and closed connections. - */ -TEST( MQTT_Unit_Receive, PublishResourceFailure ) -{ - /* Test the behavior when memory allocation fails for QoS 0 and 1 PUBLISH. */ - _publishMallocFail( IOT_MQTT_QOS_0 ); - _publishMallocFail( IOT_MQTT_QOS_1 ); - - /* Test PUBACK generation when malloc fails. */ - { - #if ( IOT_TEST_NO_MALLOC_OVERRIDES != 1 ) && ( IOT_STATIC_MEMORY_ONLY != 1 ) - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - uint8_t * pPubackPacket = NULL; - size_t pubackSize = 0; - - /* Set malloc to fail, then attempt to generate a PUBACK. */ - UnityMalloc_MakeMallocFailAfterCount( 0 ); - status = _IotMqtt_SerializePuback( 1, &pPubackPacket, &pubackSize ); - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - - /* Reset malloc failure. */ - UnityMalloc_MakeMallocFailAfterCount( -1 ); - #endif /* if ( IOT_TEST_NO_MALLOC_OVERRIDES != 1 ) && ( IOT_STATIC_MEMORY_ONLY != 1 ) */ - } - - /* Test the behavior when a closed connection is used. */ - { - DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); - - /* Mark the connection as closed, then attempt to process a PUBLISH with a closed connection. */ - _pMqttConnection->disconnected = true; - TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, 0 ) ); - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a - * spec-compliant PUBACK. - */ -TEST( MQTT_Unit_Receive, PubackValid ) -{ - _mqttOperation_t publish = INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); - - publish.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - /* Even though no PUBLISH is in the receive queue, 4 bytes should still be - * processed (should not crash). */ - { - DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, - pPuback, - pubackSize, - IOT_MQTT_STATUS_PENDING ) ); - } - - /* Process a valid PUBACK. */ - { - DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - _operationResetAndPush( &publish ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, - pPuback, - pubackSize, - IOT_MQTT_SUCCESS ) ); - } - - IotSemaphore_Destroy( &( publish.u.operation.notify.waitSemaphore ) ); - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a PUBACK - * that doesn't comply to MQTT spec. - */ -TEST( MQTT_Unit_Receive, PubackInvalid ) -{ - _mqttOperation_t publish = INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); - - publish.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - _operationResetAndPush( &publish ); - - /* An incomplete PUBACK should not be processed, and no status should be set. */ - { - DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, - pPuback, - pubackSize - 1, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The PUBACK control packet type must be 0x40. */ - { - DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - pPuback[ 0 ] = 0x41; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, - pPuback, - pubackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - _operationResetAndPush( &publish ); - - /* A PUBACK must have a remaining length of 2. */ - { - DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - pPuback[ 1 ] = 0x01; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, - pPuback, - pubackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The packet identifier in PUBACK cannot be 0. No status should be set if - * packet identifier 0 is received. */ - { - DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); - pPuback[ 3 ] = 0x00; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, - pPuback, - pubackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Remove unprocessed PUBLISH if present. */ - if( IotLink_IsLinked( &( publish.link ) ) == true ) - { - IotDeQueue_Remove( &( publish.link ) ); - } - - IotSemaphore_Destroy( &( publish.u.operation.notify.waitSemaphore ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a - * spec-compliant SUBACK. - */ -TEST( MQTT_Unit_Receive, SubackValid ) -{ - _mqttSubscription_t * pNewSubscription = NULL; - _mqttOperation_t subscribe = INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); - IotMqttSubscription_t pSubscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - subscribe.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - /* Add 2 additional subscriptions to the MQTT connection. */ - pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_1; - pSubscriptions[ 0 ].callback.function = _publishCallback; - pSubscriptions[ 0 ].pTopicFilter = TEST_TOPIC_NAME "1"; - pSubscriptions[ 0 ].topicFilterLength = TEST_TOPIC_LENGTH + 1; - - pSubscriptions[ 1 ].qos = IOT_MQTT_QOS_1; - pSubscriptions[ 1 ].callback.function = _publishCallback; - pSubscriptions[ 1 ].pTopicFilter = TEST_TOPIC_NAME "2"; - pSubscriptions[ 1 ].topicFilterLength = TEST_TOPIC_LENGTH + 1; - - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - pSubscriptions, - 2 ) ); - - /* Set orders 2 and 1 for the new subscriptions. */ - pNewSubscription = IotLink_Container( _mqttSubscription_t, - IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ), - link ); - pNewSubscription->packetInfo.order = 2; - - pNewSubscription = IotLink_Container( _mqttSubscription_t, - pNewSubscription->link.pNext, - link ); - pNewSubscription->packetInfo.order = 1; - - /* Even though no SUBSCRIBE is in the receive queue, all bytes of the SUBACK - * should still be processed (should not crash). */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_STATUS_PENDING ) ); - } - - /* Process a valid SUBACK where all subscriptions are successful. */ - { - IotMqttSubscription_t currentSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - _operationResetAndPush( &subscribe ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_SUCCESS ) ); - - /* Test the subscription check function. QoS is not tested. */ - TEST_ASSERT_EQUAL_INT( true, IotMqtt_IsSubscribed( _pMqttConnection, - pSubscriptions[ 0 ].pTopicFilter, - pSubscriptions[ 0 ].topicFilterLength, - ¤tSubscription ) ); - TEST_ASSERT_EQUAL_UINT16( currentSubscription.topicFilterLength, - pSubscriptions[ 0 ].topicFilterLength ); - TEST_ASSERT_EQUAL_STRING_LEN( currentSubscription.pTopicFilter, - pSubscriptions[ 0 ].pTopicFilter, - currentSubscription.topicFilterLength ); - TEST_ASSERT_EQUAL_PTR( currentSubscription.callback.function, - pSubscriptions[ 0 ].callback.function ); - TEST_ASSERT_EQUAL_PTR( currentSubscription.callback.pCallbackContext, - pSubscriptions[ 0 ].callback.pCallbackContext ); - } - - /* Process a valid SUBACK where some subscriptions were rejected. */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - pSuback[ 4 ] = 0x80; - pSuback[ 6 ] = 0x80; - _operationResetAndPush( &subscribe ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_SERVER_REFUSED ) ); - - /* Check that rejected subscriptions were removed from the subscription - * list. */ - TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, - TEST_TOPIC_NAME, - TEST_TOPIC_LENGTH, - NULL ) ); - TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, - pSubscriptions[ 1 ].pTopicFilter, - pSubscriptions[ 1 ].topicFilterLength, - NULL ) ); - } - - IotSemaphore_Destroy( &( subscribe.u.operation.notify.waitSemaphore ) ); - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a - * SUBACK that doesn't comply to MQTT spec. - */ -TEST( MQTT_Unit_Receive, SubackInvalid ) -{ - _mqttOperation_t subscribe = INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); - - subscribe.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - _operationResetAndPush( &subscribe ); - - /* Attempting to process a packet smaller than 5 bytes should result in no - * bytes processed. 5 bytes is the minimum size of a SUBACK. */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - 4, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a SUBACK with an invalid "Remaining length". */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - pSuback[ 1 ] = 0xff; - pSuback[ 2 ] = 0xff; - pSuback[ 3 ] = 0xff; - pSuback[ 4 ] = 0xff; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a SUBACK larger than the size of the data stream. */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - pSuback[ 1 ] = 0x52; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a SUBACK with a "Remaining length" smaller than the - * spec allows. */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - pSuback[ 1 ] = 0x02; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Attempt to process a SUBACK with a bad return code. */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - pSuback[ 6 ] = 0xff; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The SUBACK control packet type must be 0x90. */ - { - DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); - pSuback[ 0 ] = 0x91; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, - pSuback, - subackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - IotSemaphore_Destroy( &( subscribe.u.operation.notify.waitSemaphore ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a - * spec-compliant UNSUBACK. - */ -TEST( MQTT_Unit_Receive, UnsubackValid ) -{ - _mqttOperation_t unsubscribe = INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); - - unsubscribe.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - /* Even though no UNSUBSCRIBE is in the receive queue, 4 bytes should still be - * processed (should not crash). */ - { - DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, - pUnsuback, - unsubackSize, - IOT_MQTT_STATUS_PENDING ) ); - } - - /* Process a valid UNSUBACK. */ - { - DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - _operationResetAndPush( &unsubscribe ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, - pUnsuback, - unsubackSize, - IOT_MQTT_SUCCESS ) ); - } - - IotSemaphore_Destroy( &( unsubscribe.u.operation.notify.waitSemaphore ) ); - - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with an - * UNSUBACK that doesn't comply to MQTT spec. - */ -TEST( MQTT_Unit_Receive, UnsubackInvalid ) -{ - _mqttOperation_t unsubscribe = INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); - - unsubscribe.pMqttConnection = _pMqttConnection; - - /* Create the wait semaphore so notifications don't crash. The value of - * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.u.operation.notify.waitSemaphore ), - 0, - 10 ) ); - - _operationResetAndPush( &unsubscribe ); - - /* An incomplete UNSUBACK should not be processed, and no status should be set. */ - { - DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, - pUnsuback, - unsubackSize - 1, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The UNSUBACK control packet type must be 0xb0. */ - { - DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - pUnsuback[ 0 ] = 0xb1; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, - pUnsuback, - unsubackSize, - IOT_MQTT_BAD_RESPONSE ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - _operationResetAndPush( &unsubscribe ); - - /* An UNSUBACK must have a remaining length of 2. */ - { - DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - pUnsuback[ 1 ] = 0x01; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, - pUnsuback, - unsubackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* The packet identifier in UNSUBACK cannot be 0. No status should be set if - * packet identifier 0 is received. */ - { - DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); - pUnsuback[ 3 ] = 0x00; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, - pUnsuback, - unsubackSize, - IOT_MQTT_STATUS_PENDING ) ); - - /* Network close should have been called for invalid packet. */ - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* Remove unprocessed UNSUBSCRIBE if present. */ - if( IotLink_IsLinked( &( unsubscribe.link ) ) == true ) - { - IotDeQueue_Remove( &( unsubscribe.link ) ); - } - - IotSemaphore_Destroy( &( unsubscribe.u.operation.notify.waitSemaphore ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests the behavior of @ref mqtt_function_receivecallback when receiving - * a PINGRESP packet (both compliant and non-compliant packets). - */ -TEST( MQTT_Unit_Receive, Pingresp ) -{ - /* Even though no PINGREQ is expected, the keep-alive failure flag should - * be cleared (should not crash). */ - { - _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 0; - - DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, - pPingresp, - pingrespSize, - IOT_MQTT_SUCCESS ) ); - - TEST_ASSERT_EQUAL_INT( 0, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); - } - - /* Process a valid PINGRESP. */ - { - _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; - - DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, - pPingresp, - pingrespSize, - IOT_MQTT_SUCCESS ) ); - - TEST_ASSERT_EQUAL_INT( 0, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); - } - - /* An incomplete PINGRESP should not be processed, and the keep-alive failure - * flag should not be cleared. */ - { - _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; - - DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, - pPingresp, - pingrespSize - 1, - IOT_MQTT_SUCCESS ) ); - - TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } - - /* A PINGRESP should have a remaining length of 0. */ - { - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttPacket_t pingresp; - - ( void ) memset( &pingresp, 0x00, sizeof( _mqttPacket_t ) ); - pingresp.type = MQTT_PACKET_TYPE_PINGRESP; - pingresp.remainingLength = 1; - - status = _IotMqtt_DeserializePingresp( &pingresp ); - TEST_ASSERT_EQUAL( IOT_MQTT_BAD_RESPONSE, status ); - } - - /* The PINGRESP control packet type must be 0xd0. */ - { - _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; - - DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); - pPingresp[ 0 ] = 0xd1; - TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, - pPingresp, - pingrespSize, - IOT_MQTT_SUCCESS ) ); - - TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); - TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); - _networkCloseCalled = false; - _disconnectCallbackCalled = false; - } -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c deleted file mode 100644 index 86e9ba7cde..0000000000 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c +++ /dev/null @@ -1,873 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_subscription.c - * @brief Tests for the functions in iot_mqtt_subscription.c - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Platform layer includes. */ -#include "platform/iot_threads.h" -#include "platform/iot_clock.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* MQTT test access include. */ -#include "iot_test_access_mqtt.h" - -/*-----------------------------------------------------------*/ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef IOT_TEST_MQTT_TIMEOUT_MS - #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) -#endif -/** @endcond */ - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false -#endif - -/* - * Constants relating to the test subscription list. - */ -#define LIST_ITEM_COUNT ( 10 ) /**< @brief Number of subscriptions. */ -#define TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ -#define TEST_TOPIC_FILTER_LENGTH ( sizeof( TEST_TOPIC_FILTER_FORMAT ) + 1 ) /**< @brief Maximum length of each topic filter. */ - -/** - * @brief A non-NULL function pointer to use for subscription callback. This - * "function" should cause a crash if actually called. - */ -#define SUBSCRIPTION_CALLBACK_FUNCTION \ - ( ( void ( * )( void *, \ - IotMqttCallbackParam_t * ) ) 0x1 ) - -/** - * @brief All test topic filters in the tests #TEST_MQTT_Unit_Subscription_TopicFilterMatchTrue_ - * and #TEST_MQTT_Unit_Subscription_TopicFilterMatchFalse_ should be shorter than this - * length. - */ -#define TOPIC_FILTER_MATCH_MAX_LENGTH ( 32 ) - -/** - * @brief Macro to check a single topic name against a topic filter. - * - * @param[in] topicNameString The topic name to check. - * @param[in] topicFilterString The topic filter to check. - * @param[in] exactMatch Whether an exact match is required. If this is false, - * wildcard matching is allowed. - * @param[in] expectedResult Whether the topic name and filter are expected to match. - * - * @note This macro may only be used when a #_mqttSubscription_t pointer named pTopicFilter - * is in scope. - */ -#define TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ - { \ - _topicMatchParams_t _topicMatchParams = { 0 }; \ - _topicMatchParams.pTopicName = topicNameString; \ - _topicMatchParams.topicNameLength = ( uint16_t ) strlen( _topicMatchParams.pTopicName ); \ - _topicMatchParams.exactMatchOnly = exactMatch; \ - \ - pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ - TOPIC_FILTER_MATCH_MAX_LENGTH, \ - topicFilterString ); \ - \ - TEST_ASSERT_EQUAL_INT( expectedResult, \ - IotTestMqtt_topicMatch( &( pTopicFilter->link ), &_topicMatchParams ) ); \ - } - -/*-----------------------------------------------------------*/ - -/** - * @brief Tracks whether the global MQTT connection has been created. - */ -static bool _connectionCreated = false; - -/** - * @brief The MQTT connection shared by all tests. - */ -static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/*-----------------------------------------------------------*/ - -/** - * @brief Places dummy subscriptions in the subscription list of #_pMqttConnection. - */ -static void _populateList( void ) -{ - size_t i = 0; - _mqttSubscription_t * pSubscription = NULL; - - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - pSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + TEST_TOPIC_FILTER_LENGTH ); - TEST_ASSERT_NOT_NULL( pSubscription ); - - ( void ) memset( pSubscription, 0x00, sizeof( _mqttSubscription_t ) + TEST_TOPIC_FILTER_LENGTH ); - pSubscription->packetInfo.identifier = 1; - pSubscription->packetInfo.order = i; - pSubscription->callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - pSubscription->topicFilterLength = ( uint16_t ) snprintf( pSubscription->pTopicFilter, - TEST_TOPIC_FILTER_LENGTH, - TEST_TOPIC_FILTER_FORMAT, - ( unsigned long ) i ); - - IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), - &( pSubscription->link ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A subscription callback function that unlinks its subscription. - */ -static void _removalCallback( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - _mqttSubscription_t * pSubscription = pArgument; - - /* Silence warnings about unused parameters. */ - ( void ) pPublish; - - IotListDouble_Remove( &( pSubscription->link ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A subscription callback function that only reports whether it was invoked. - */ -static void _publishCallback( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - uint16_t i = 0; - bool * pCallbackInvoked = ( bool * ) pArgument; - - /* Notify the caller that this callback was invoked. */ - *pCallbackInvoked = true; - - /* If the topic filter doesn't match the topic name, ensure that the topic - * filter contains a wildcard. */ - if( pPublish->u.message.topicFilterLength != pPublish->u.message.info.topicNameLength ) - { - for( i = 0; i < pPublish->u.message.topicFilterLength; i++ ) - { - if( ( pPublish->u.message.pTopicFilter[ i ] == '+' ) || - ( pPublish->u.message.pTopicFilter[ i ] == '#' ) ) - { - break; - } - } - - TEST_ASSERT_LESS_THAN_UINT16( pPublish->u.message.topicFilterLength, i ); - } - - /* Ensure that the MQTT connection was set correctly. */ - TEST_ASSERT_EQUAL_PTR( pPublish->mqttConnection, _pMqttConnection ); - - /* Ensure that publish info is valid. */ - TEST_ASSERT_EQUAL_INT( true, - _IotMqtt_ValidatePublish( AWS_IOT_MQTT_SERVER, - &( pPublish->u.message.info ), - 0, - NULL, - NULL ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT subscription tests. - */ -TEST_GROUP( MQTT_Unit_Subscription ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT subscription tests. - */ -TEST_SETUP( MQTT_Unit_Subscription ) -{ - static IotNetworkInterface_t networkInterface = { 0 }; - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - - /* Initialize SDK. */ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - /* Initialize the MQTT library. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); - - networkInfo.pNetworkInterface = &networkInterface; - - /* Create an MQTT connection with empty network info. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, - &networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); - - _connectionCreated = true; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT subscription tests. - */ -TEST_TEAR_DOWN( MQTT_Unit_Subscription ) -{ - /* Destroy the MQTT connection used for the tests. */ - if( _connectionCreated == true ) - { - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - _connectionCreated = false; - } - - /* Clean up libraries. */ - IotMqtt_Cleanup(); - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT subscription tests. - */ -TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) -{ - RUN_TEST_CASE( MQTT_Unit_Subscription, ListInsertRemove ); - RUN_TEST_CASE( MQTT_Unit_Subscription, ListFindByTopicFilter ); - RUN_TEST_CASE( MQTT_Unit_Subscription, ListFindByPacket ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddDuplicate ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddMallocFail ); - RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublish ); - RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublishMultiple ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionUnsubscribe ); - RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchTrue ); - RUN_TEST_CASE( MQTT_Unit_Subscription, TopicFilterMatchFalse ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests simple insertion and removal of elements from the subscription list. - */ -TEST( MQTT_Unit_Subscription, ListInsertRemove ) -{ - _mqttSubscription_t node1; - _mqttSubscription_t node2; - _mqttSubscription_t node3; - - ( void ) memset( &node1, 0x00, sizeof( _mqttSubscription_t ) ); - ( void ) memset( &node2, 0x00, sizeof( _mqttSubscription_t ) ); - ( void ) memset( &node3, 0x00, sizeof( _mqttSubscription_t ) ); - - IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), - &( node1.link ) ); - IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), - &( node2.link ) ); - IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), - &( node3.link ) ); - - IotListDouble_Remove( &( node1.link ) ); - IotListDouble_Remove( &( node2.link ) ); - IotListDouble_Remove( &( node3.link ) ); - - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests searching the subscription list using a topic filter. - */ -TEST( MQTT_Unit_Subscription, ListFindByTopicFilter ) -{ - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - _topicMatchParams_t topicMatchParams = { 0 }; - - topicMatchParams.pTopicName = "/test0"; - topicMatchParams.topicNameLength = 6; - - /* On empty list. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_topicMatch, - &topicMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); - - _populateList(); - - /* Topic filter present. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_topicMatch, - &topicMatchParams ); - TEST_ASSERT_NOT_EQUAL( NULL, pSubscriptionLink ); - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); - - /* Topic filter not present. */ - topicMatchParams.pTopicName = "/notpresent"; - topicMatchParams.topicNameLength = 11; - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_topicMatch, - &topicMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests searching the subscription list using a packet identifier. - */ -TEST( MQTT_Unit_Subscription, ListFindByPacket ) -{ - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - _packetMatchParams_t packetMatchParams = { 0 }; - - packetMatchParams.packetIdentifier = 1; - packetMatchParams.order = 0; - - /* On empty list. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_packetMatch, - &packetMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); - - _populateList(); - - /* Packet and order present. */ - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_packetMatch, - &packetMatchParams ); - TEST_ASSERT_NOT_EQUAL( NULL, pSubscriptionLink ); - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); - - /* Packet present, order not present. */ - packetMatchParams.order = LIST_ITEM_COUNT; - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_packetMatch, - &packetMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); - - /* Packet not present, order present. */ - packetMatchParams.packetIdentifier = 0; - packetMatchParams.order = 0; - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_packetMatch, - &packetMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); - - /* Packet and order not present. */ - packetMatchParams.packetIdentifier = 0; - packetMatchParams.order = LIST_ITEM_COUNT; - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_packetMatch, - &packetMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests removing subscriptions by packet identifier. - */ -TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) -{ - int32_t i = 0; - - /* On empty list (should not crash). */ - _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, - 1, - 0 ); - - _populateList(); - - /* Remove all subscriptions by packet one-by-one. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, - 1, - i ); - } - - /* List should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - - /* Remove all subscriptions for a packet one-shot. */ - _populateList(); - _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, - 1, - MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests removing subscriptions by a topic filter. - */ -TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) -{ - size_t i = 0; - char pTopicFilters[ LIST_ITEM_COUNT ][ TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* On empty list (should not crash). */ - subscription[ 0 ].pTopicFilter = "/topic"; - subscription[ 0 ].topicFilterLength = 6; - _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, - &( subscription[ 0 ] ), - 1 ); - - _populateList(); - subscription[ 0 ].pTopicFilter = pTopicFilters[ 0 ]; - - /* Removal one-by-one. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - subscription[ 0 ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ 0 ], - TEST_TOPIC_FILTER_LENGTH, - TEST_TOPIC_FILTER_FORMAT, - ( unsigned long ) i ); - - _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, - &( subscription[ 0 ] ), - 1 ); - } - - /* List should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - - /* Refill the list. */ - _populateList(); - TEST_ASSERT_EQUAL_INT( false, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - - /* Removal all at once. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - subscription[ i ].pTopicFilter = pTopicFilters[ i ]; - subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - TEST_TOPIC_FILTER_LENGTH, - TEST_TOPIC_FILTER_FORMAT, - ( unsigned long ) i ); - } - - _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, - subscription, - LIST_ITEM_COUNT ); - - /* List should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests adding duplicate subscriptions. - */ -TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) -{ - size_t i = 0; - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink = NULL; - _topicMatchParams_t topicMatchParams = { 0 }; - char pTopicFilters[ LIST_ITEM_COUNT ][ TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* Set valid values in the subscription list. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - subscription[ i ].callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - subscription[ i ].pTopicFilter = pTopicFilters[ i ]; - subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - TEST_TOPIC_FILTER_LENGTH, - TEST_TOPIC_FILTER_FORMAT, - ( unsigned long ) i ); - } - - /* Add all subscriptions to the list. */ - status = _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - subscription, - LIST_ITEM_COUNT ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Change the callback information, but not the topic filter. */ - subscription[ 1 ].callback.function = _publishCallback; - subscription[ 1 ].callback.pCallbackContext = _pMqttConnection; - - /* Add the duplicate subscription. */ - status = _IotMqtt_AddSubscriptions( _pMqttConnection, - 3, - &( subscription[ 1 ] ), - 1 ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); - - /* Find the subscription that was just modified. */ - topicMatchParams.pTopicName = "/test1"; - topicMatchParams.topicNameLength = 6; - topicMatchParams.exactMatchOnly = true; - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_topicMatch, - &topicMatchParams ); - TEST_ASSERT_NOT_EQUAL( NULL, pSubscriptionLink ); - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); - - /* Check that the information was changed. */ - TEST_ASSERT_EQUAL_UINT16( 3, pSubscription->packetInfo.identifier ); - TEST_ASSERT_EQUAL( 0, pSubscription->packetInfo.order ); - TEST_ASSERT_EQUAL_PTR( _publishCallback, pSubscription->callback.function ); - TEST_ASSERT_EQUAL_PTR( _pMqttConnection, pSubscription->callback.pCallbackContext ); - - /* Check that a duplicate entry wasn't created. */ - IotListDouble_Remove( &( pSubscription->link ) ); - IotMqtt_FreeSubscription( pSubscription ); - pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), - NULL, - IotTestMqtt_topicMatch, - &topicMatchParams ); - TEST_ASSERT_EQUAL_PTR( NULL, pSubscriptionLink ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests adding subscriptions when memory allocation fails at various points. - */ -TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) -{ - size_t i = 0; - char pTopicFilters[ LIST_ITEM_COUNT ][ TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* Set valid values in the subscription list. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - subscription[ i ].callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - subscription[ i ].pTopicFilter = pTopicFilters[ i ]; - subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - TEST_TOPIC_FILTER_LENGTH, - TEST_TOPIC_FILTER_FORMAT, - ( unsigned long ) i ); - } - - /* Set malloc to fail at various points. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( ( int ) i ); - - status = _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - subscription, - LIST_ITEM_COUNT ); - - if( status == IOT_MQTT_SUCCESS ) - { - break; - } - - TEST_ASSERT_EQUAL( IOT_MQTT_NO_MEMORY, status ); - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests invoking subscription callbacks with PUBLISH messages. - */ -TEST( MQTT_Unit_Subscription, ProcessPublish ) -{ - bool callbackInvoked = false; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; - - /* Set the subscription and corresponding publish info. */ - subscription.pTopicFilter = "/test"; - subscription.topicFilterLength = 5; - subscription.callback.function = _publishCallback; - subscription.callback.pCallbackContext = &callbackInvoked; - - callbackParam.u.message.info.pTopicName = "/test"; - callbackParam.u.message.info.topicNameLength = 5; - callbackParam.u.message.info.pPayload = ""; - callbackParam.u.message.info.payloadLength = 0; - - /* Add the subscription. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - &subscription, - 1 ) ); - - /* Increment connection reference count for processing subscription callbacks. */ - TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( _pMqttConnection ) ); - - /* Find the subscription and invoke its callback. */ - _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, - &callbackParam ); - - /* Check that the callback was invoked. */ - TEST_ASSERT_EQUAL_INT( true, callbackInvoked ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that all matching subscription callbacks are invoked for a - * PUBLISH. - */ -TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) -{ - bool callbackInvoked[ 3 ] = { false }; - IotMqttSubscription_t subscription[ 3 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; - - /* Set the subscription info. */ - subscription[ 0 ].pTopicFilter = "/test"; - subscription[ 0 ].topicFilterLength = 5; - subscription[ 0 ].callback.function = _publishCallback; - subscription[ 0 ].callback.pCallbackContext = &( callbackInvoked[ 0 ] ); - - subscription[ 1 ].pTopicFilter = "/+"; - subscription[ 1 ].topicFilterLength = 2; - subscription[ 1 ].callback.function = _publishCallback; - subscription[ 1 ].callback.pCallbackContext = &( callbackInvoked[ 1 ] ); - - subscription[ 2 ].pTopicFilter = "/#"; - subscription[ 2 ].topicFilterLength = 2; - subscription[ 2 ].callback.function = _publishCallback; - subscription[ 2 ].callback.pCallbackContext = &( callbackInvoked[ 2 ] ); - - /* Create a PUBLISH that matches all 3 subscriptions. */ - callbackParam.u.message.info.pTopicName = "/test"; - callbackParam.u.message.info.topicNameLength = 5; - callbackParam.u.message.info.pPayload = ""; - callbackParam.u.message.info.payloadLength = 0; - - /* Add the subscriptions. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - &( subscription[ 0 ] ), - 3 ) ); - - /* Increment connection reference count for processing subscription callbacks. */ - TEST_ASSERT_EQUAL_INT( true, _IotMqtt_IncrementConnectionReferences( _pMqttConnection ) ); - - /* Invoke subscription callbacks. */ - _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, - &callbackParam ); - - /* Check that all 3 callbacks were invoked. */ - TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 0 ] ); - TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 1 ] ); - TEST_ASSERT_EQUAL_INT( true, callbackInvoked[ 2 ] ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that the unsubscribed flag is respected for subscriptions. - */ -TEST( MQTT_Unit_Subscription, SubscriptionUnsubscribe ) -{ - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - _mqttSubscription_t * pSubscription = NULL; - IotLink_t * pSubscriptionLink; - IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; - - /* Set the subscription and corresponding publish info. */ - subscription.pTopicFilter = "/test"; - subscription.topicFilterLength = 5; - subscription.callback.function = _removalCallback; - - callbackParam.u.message.info.pTopicName = "/test"; - callbackParam.u.message.info.topicNameLength = 5; - callbackParam.u.message.info.pPayload = ""; - callbackParam.u.message.info.payloadLength = 0; - - /* Add the subscription. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - &subscription, - 1 ) ); - - /* Increment reference count of subscription and connection. */ - pSubscriptionLink = IotListDouble_PeekHead( &( _pMqttConnection->subscriptionList ) ); - pSubscription = IotLink_Container( _mqttSubscription_t, pSubscriptionLink, link ); - pSubscription->references++; - _pMqttConnection->references++; - - /* Attempt to remove the subscription. */ - _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, 1, MQTT_REMOVE_ALL_SUBSCRIPTIONS ); - - /* Check that the subscription wasn't removed and the unsubscribed flag was set. */ - TEST_ASSERT_EQUAL_INT( true, IotLink_IsLinked( &( pSubscription->link ) ) ); - TEST_ASSERT_EQUAL_INT( true, pSubscription->unsubscribed ); - - /* Invoke the publish callback. This should remove the subscription. */ - pSubscription->callback.pCallbackContext = pSubscription; - _IotMqtt_InvokeSubscriptionCallback( _pMqttConnection, &callbackParam ); - TEST_ASSERT_EQUAL_INT( false, IotLink_IsLinked( &( pSubscription->link ) ) ); - - /* Put the subscription back in the list. */ - IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), pSubscriptionLink ); - - /* Disconnect the MQTT connection. As the subscription had its reference count - * incremented by this test, it should not be freed by disconnect. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); - _connectionCreated = false; - - IotMqtt_FreeSubscription( pSubscription ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests result of matching topic filters and topic names. - */ -TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) -{ - _mqttSubscription_t * pTopicFilter = - IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + TOPIC_FILTER_MATCH_MAX_LENGTH ); - - TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); - - if( TEST_PROTECT() ) - { - /* Exact matching. */ - TEST_TOPIC_MATCH( "/exact", "/exact", true, true ); - TEST_TOPIC_MATCH( "/exact", "/exact", false, true ); - - /* Topic level wildcard matching. */ - TEST_TOPIC_MATCH( "/aws", "/+", false, true ); - TEST_TOPIC_MATCH( "/aws/iot", "/aws/+", false, true ); - TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/shadow", false, true ); - TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/+", false, true ); - TEST_TOPIC_MATCH( "aws/", "aws/+", false, true ); - TEST_TOPIC_MATCH( "/aws", "+/+", false, true ); - TEST_TOPIC_MATCH( "aws//iot", "aws/+/iot", false, true ); - TEST_TOPIC_MATCH( "aws//iot", "aws//+", false, true ); - TEST_TOPIC_MATCH( "aws///iot", "aws/+/+/iot", false, true ); - - /* Multi level wildcard matching. */ - TEST_TOPIC_MATCH( "/aws/iot/shadow", "#", false, true ); - TEST_TOPIC_MATCH( "aws/iot/shadow", "#", false, true ); - TEST_TOPIC_MATCH( "/aws/iot/shadow", "/#", false, true ); - TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/iot/#", false, true ); - TEST_TOPIC_MATCH( "aws/iot/shadow/thing", "aws/iot/#", false, true ); - TEST_TOPIC_MATCH( "aws", "aws/#", false, true ); - - /* Both topic level and multi level wildcard. */ - TEST_TOPIC_MATCH( "aws/iot/shadow/thing/temp", "aws/+/shadow/#", false, true ); - } - - IotMqtt_FreeSubscription( pTopicFilter ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests result of matching topic filters and topic names that don't - * match. - */ -TEST( MQTT_Unit_Subscription, TopicFilterMatchFalse ) -{ - _mqttSubscription_t * pTopicFilter = - IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + TOPIC_FILTER_MATCH_MAX_LENGTH ); - - TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); - - if( TEST_PROTECT() ) - { - /* Topic filter longer than filter name. */ - TEST_TOPIC_MATCH( "/short", "/toolong", true, false ); - TEST_TOPIC_MATCH( "/short", "/toolong", false, false ); - - /* Case mismatch. */ - TEST_TOPIC_MATCH( "/exact", "/eXaCt", true, false ); - TEST_TOPIC_MATCH( "/exact", "/ExAcT", false, false ); - - /* Substrings should not match. */ - TEST_TOPIC_MATCH( "aws/", "aws/iot", true, false ); - TEST_TOPIC_MATCH( "aws/", "aws/iot", false, false ); - - /* Topic level wildcard matching. */ - TEST_TOPIC_MATCH( "aws", "aws/", false, false ); - TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+", false, false ); - TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+/thing", false, false ); - TEST_TOPIC_MATCH( "/aws", "+", false, false ); - - /* Multi level wildcard matching. */ - TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/#", false, false ); - TEST_TOPIC_MATCH( "aws/iot", "/#", false, false ); - - /* Both topic level and multi level wildcard. */ - TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/+/#", false, false ); - } - - IotMqtt_FreeSubscription( pTopicFilter ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c deleted file mode 100644 index 8dd22789c2..0000000000 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c +++ /dev/null @@ -1,428 +0,0 @@ -/* - * IoT MQTT V2.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_validate.c - * @brief Tests for the functions in iot_mqtt_validate.c - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false -#endif - -/** - * @brief Length of the subscription array used in - * #TEST_MQTT_Unit_Validate_ValidateSubscriptionList_. - */ -#define SUBSCRIPTION_COUNT ( AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) - -/** - * @brief A non-NULL function pointer. - */ -#define FUNCTION_POINTER \ - ( ( void ( * )( void *, \ - IotMqttCallbackParam_t * ) ) 0x1 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT validate tests. - */ -TEST_GROUP( MQTT_Unit_Validate ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT validate tests. - */ -TEST_SETUP( MQTT_Unit_Validate ) -{ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT validate tests. - */ -TEST_TEAR_DOWN( MQTT_Unit_Validate ) -{ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT validate tests. - */ -TEST_GROUP_RUNNER( MQTT_Unit_Validate ) -{ - RUN_TEST_CASE( MQTT_Unit_Validate, ValidateConnectInfo ); - RUN_TEST_CASE( MQTT_Unit_Validate, ValidatePublish ); - RUN_TEST_CASE( MQTT_Unit_Validate, ValidateOperation ); - RUN_TEST_CASE( MQTT_Unit_Validate, ValidateSubscriptionList ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test validation of an #IotMqttConnectInfo_t. - */ -TEST( MQTT_Unit_Validate, ValidateConnectInfo ) -{ - bool validateStatus = false; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - - /* NULL parameter. */ - validateStatus = _IotMqtt_ValidateConnect( NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Uninitialized parameter. */ - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Zero-length client identifier with persistent session. */ - connectInfo.cleanSession = false; - connectInfo.pClientIdentifier = ""; - connectInfo.clientIdentifierLength = 0; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Zero-length client identifier with clean session. */ - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = ""; - connectInfo.clientIdentifierLength = 0; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Client identifier longer than the MQTT 3.1.1 recommended maximum length. */ - connectInfo.pClientIdentifier = "longlongclientidentifier"; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( connectInfo.pClientIdentifier ); - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Client identifier too long for AWS. */ - connectInfo.awsIotMqttMode = true; - connectInfo.clientIdentifierLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH + 1; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test validation of an #IotMqttPublishInfo_t. - */ -TEST( MQTT_Unit_Validate, ValidatePublish ) -{ - bool validateStatus = false; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; - - /* NULL parameter. */ - validateStatus = _IotMqtt_ValidatePublish( false, NULL, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Zero-length topic name. */ - publishInfo.pTopicName = ""; - publishInfo.topicNameLength = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - publishInfo.pTopicName = "/test"; - publishInfo.topicNameLength = 5; - - /* Zero-length/NULL payload. */ - publishInfo.pPayload = NULL; - publishInfo.payloadLength = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* NULL payload only allowed with length 0. */ - publishInfo.pPayload = NULL; - publishInfo.payloadLength = 1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.payloadLength = 0; - - /* Negative QoS or QoS > 2. */ - publishInfo.qos = ( IotMqttQos_t ) -1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.qos = ( IotMqttQos_t ) 3; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.qos = IOT_MQTT_QOS_0; - - /* Positive retry limit with no period. */ - publishInfo.retryLimit = 1; - publishInfo.retryMs = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Positive retry limit with positive period. */ - publishInfo.retryLimit = 1; - publishInfo.retryMs = 1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Retry limit 0. */ - publishInfo.retryLimit = 0; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Invalid flags. */ - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &operation ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Invalid callback. */ - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, 0, &callbackInfo, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Invalid operation. */ - publishInfo.qos = IOT_MQTT_QOS_1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, IOT_MQTT_FLAG_WAITABLE, &callbackInfo, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Valid flags, callback, and operation. */ - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo, IOT_MQTT_FLAG_WAITABLE, &callbackInfo, &operation ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* AWS IoT MQTT service limit tests. */ - #if AWS_IOT_MQTT_SERVER == true - /* QoS 2. */ - publishInfo.qos = IOT_MQTT_QOS_2; - validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.qos = IOT_MQTT_QOS_0; - - /* Retained message. */ - publishInfo.retain = true; - validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.retain = false; - - /* Topic name too long. */ - publishInfo.topicNameLength = AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; - validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - #endif /* if AWS_IOT_MQTT_SERVER == true */ -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test validation of an #IotMqttOperation_t. - */ -TEST( MQTT_Unit_Validate, ValidateOperation ) -{ - bool validateStatus = false; - IotMqttOperation_t operation = IotMqtt_MallocOperation( sizeof( _mqttOperation_t ) ); - - TEST_ASSERT_NOT_NULL( operation ); - - if( TEST_PROTECT() ) - { - ( void ) memset( operation, 0x00, sizeof( _mqttOperation_t ) ); - - /* NULL parameter. */ - validateStatus = _IotMqtt_ValidateOperation( NULL ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Non-waitable reference. */ - operation->u.operation.flags = 0; - validateStatus = _IotMqtt_ValidateOperation( operation ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Waitable (valid) reference. */ - operation->u.operation.flags = IOT_MQTT_FLAG_WAITABLE; - validateStatus = _IotMqtt_ValidateOperation( operation ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - } - - IotMqtt_FreeOperation( operation ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test validation of a list of #IotMqttSubscription_t. - */ -TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) -{ - size_t i = 0; - bool validateStatus = false; - IotMqttSubscription_t pSubscriptions[ SUBSCRIPTION_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* NULL parameter. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, NULL, 1 ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Zero parameter. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, 0 ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Uninitialized subscriptions. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* Initialize all subscriptions to valid values. */ - for( i = 0; i < SUBSCRIPTION_COUNT; i++ ) - { - pSubscriptions[ i ].pTopicFilter = "/test"; - pSubscriptions[ i ].topicFilterLength = 5; - pSubscriptions[ i ].callback.function = FUNCTION_POINTER; - } - - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* One subscription with invalid QoS. */ - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) -1; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) 3; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - /* QoS is not validated for UNSUBSCRIBE. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].qos = IOT_MQTT_QOS_0; - - /* One subscription with no callback. */ - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].callback.function = NULL; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - /* Callback is not validated for UNSUBSCRIBE. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].callback.function = FUNCTION_POINTER; - - /* Valid subscription filters. */ - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 1; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/#"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+/"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/+/+/+"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 7; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Invalid subscription filters. */ - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/#"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a#"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a+"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+a"; - pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* AWS IoT MQTT service limit tests. */ - #if AWS_IOT_MQTT_SERVER == true - /* Too many subscriptions. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - true, - pSubscriptions, - AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE + 1 ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - - /* QoS 2. */ - pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - true, - pSubscriptions, - SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_0; - - /* Topic filter too long. */ - pSubscriptions[ 0 ].topicFilterLength = AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - true, - pSubscriptions, - SUBSCRIPTION_COUNT ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - #endif /* if AWS_IOT_MQTT_SERVER == true */ -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/serializer/CMakeLists.txt b/libraries/standard/serializer/CMakeLists.txt deleted file mode 100644 index 15ab4e2bab..0000000000 --- a/libraries/standard/serializer/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -# Serializer library source files. -set( SERIALIZER_SOURCES - src/iot_serializer_static_memory.c - src/cbor/iot_serializer_tinycbor_decoder.c - src/cbor/iot_serializer_tinycbor_encoder.c ) - -# Serializer library target. -add_library( iotserializer - ${CONFIG_HEADER_PATH}/iot_config.h - ${SERIALIZER_SOURCES} - include/iot_serializer.h ) - -# Serializer public include path. -target_include_directories( iotserializer PUBLIC include ) - -# Link required libraries. -target_link_libraries( iotserializer PRIVATE iotbase tinycbor ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotserializer PRIVATE unity ) -endif() - -# Organization of Serializer in folders. -set_property( TARGET iotserializer PROPERTY FOLDER libraries/standard ) -source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES - include/iot_serializer.h ) -source_group( src FILES ${SERIALIZER_SOURCES} ) - -# Build the test executable if needed. -if( ${IOT_BUILD_TESTS} ) - # Serializer unit test sources. - set( SERIALIZER_UNIT_TEST_SOURCES - test/unit/iot_tests_serializer_cbor.c ) - - # Serializer tests executable. - add_executable( iot_tests_serializer - ${SERIALIZER_UNIT_TEST_SOURCES} - test/iot_tests_serializer.c - ${IOT_TEST_APP_SOURCE} - ${CONFIG_HEADER_PATH}/iot_config.h ) - - # Define the test to run. - target_compile_definitions( iot_tests_serializer PRIVATE - -DRunTests=RunSerializerTests ) - - # Serializer tests library dependencies. - target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotbase unity tinycbor ) - - # Organization of serializer tests in folders. - set_property( TARGET iot_tests_serializer PROPERTY FOLDER tests ) - source_group( unit FILES ${SERIALIZER_UNIT_TEST_SOURCES} ) - source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/iot_tests_serializer.c ) -endif() diff --git a/libraries/standard/serializer/include/iot_serializer.h b/libraries/standard/serializer/include/iot_serializer.h deleted file mode 100644 index b3f4be1e38..0000000000 --- a/libraries/standard/serializer/include/iot_serializer.h +++ /dev/null @@ -1,545 +0,0 @@ -/* - * IoT Serializer V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * This library is to provide a consistent layer for serializing JSON-like format. - * The implementations can be CBOR or JSON. - */ - -#ifndef IOT_SERIALIZER_H_ -#define IOT_SERIALIZER_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include -#include - -#if IOT_SERIALIZER_ENABLE_ASSERTS == 1 - #ifndef IotSerializer_Assert - #ifdef Iot_DefaultAssert - #define IotSerializer_Assert( expression ) Iot_DefaultAssert( expression ) - #else - #error "Asserts are enabled for serializer, but IotSerializer_Assert is not defined" - #endif - #endif -#else /* if IOT_SERIALIZER_ENABLE_ASSERTS == 1 */ - #define IotSerializer_Assert( expression ) -#endif /* if IOT_SERIALIZER_ENABLE_ASSERTS == 1 */ - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "iot_static_memory.h" - -/** - * @brief Allocate an array of uint8_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotSerializer_MallocCborEncoder( size_t size ); - -/** - * @brief Free an array of uint8_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotSerializer_FreeCborEncoder( void * ptr ); - -/** - * @brief Allocate an array of uint8_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotSerializer_MallocCborParser( size_t size ); - -/** - * @brief Free an array of uint8_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotSerializer_FreeCborParser( void * ptr ); - -/** - * @brief Allocate an array of uint8_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotSerializer_MallocCborValue( size_t size ); - -/** - * @brief Free an array of uint8_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotSerializer_FreeCborValue( void * ptr ); - -/** - * @brief Allocate an array of uint8_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - void * IotSerializer_MallocDecoderObject( size_t size ); - -/** - * @brief Free an array of uint8_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - void IotSerializer_FreeDecoderObject( void * ptr ); -#else /* if IOT_STATIC_MEMORY_ONLY */ - #ifndef IotSerializer_MallocCborEncoder - #ifdef Iot_DefaultMalloc - #define IotSerializer_MallocCborEncoder Iot_DefaultMalloc - #else - #error "No malloc function defined for IotSerializer_MallocCborEncoder" - #endif - #endif - - #ifndef IotSerializer_FreeCborEncoder - #ifdef Iot_DefaultFree - #define IotSerializer_FreeCborEncoder Iot_DefaultFree - #else - #error "No free function defined for IotSerializer_FreeCborEncoder" - #endif - #endif - - #ifndef IotSerializer_MallocCborParser - #ifdef Iot_DefaultMalloc - #define IotSerializer_MallocCborParser Iot_DefaultMalloc - #else - #error "No malloc function defined for IotSerializer_MallocCborParser" - #endif - #endif - - #ifndef IotSerializer_FreeCborParser - #ifdef Iot_DefaultFree - #define IotSerializer_FreeCborParser Iot_DefaultFree - #else - #error "No free function defined for IotSerializer_FreeCborParser" - #endif - #endif - - #ifndef IotSerializer_MallocCborValue - #ifdef Iot_DefaultMalloc - #define IotSerializer_MallocCborValue Iot_DefaultMalloc - #else - #error "No malloc function defined for IotSerializer_MallocCborValue" - #endif - #endif - - #ifndef IotSerializer_FreeCborValue - #ifdef Iot_DefaultFree - #define IotSerializer_FreeCborValue Iot_DefaultFree - #else - #error "No free function defined for IotSerializer_FreeCborValue" - #endif - #endif - - #ifndef IotSerializer_MallocDecoderObject - #ifdef Iot_DefaultMalloc - #define IotSerializer_MallocDecoderObject Iot_DefaultMalloc - #else - #error "No malloc function defined for IotSerializer_MallocDecoderObject" - #endif - #endif - - #ifndef IotSerializer_FreeDecoderObject - #ifdef Iot_DefaultFree - #define IotSerializer_FreeDecoderObject Iot_DefaultFree - #else - #error "No free function defined for IotSerializer_FreeDecoderObject" - #endif - #endif -#endif /* if IOT_STATIC_MEMORY_ONLY */ - -#define IOT_SERIALIZER_INDEFINITE_LENGTH 0xffffffff - -#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM \ - { .pHandle = NULL, .type = \ - IOT_SERIALIZER_CONTAINER_STREAM } - -#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP \ - { .pHandle = NULL, .type = \ - IOT_SERIALIZER_CONTAINER_MAP } - -#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY \ - { .pHandle = NULL, .type = \ - IOT_SERIALIZER_CONTAINER_ARRAY } - -#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED } - -#define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL - -/* helper macro to create scalar data */ -#define IotSerializer_ScalarSignedInt( signedIntValue ) \ - ( IotSerializerScalarData_t ) { .value = { .u.signedInt = ( signedIntValue ) }, .type = \ - IOT_SERIALIZER_SCALAR_SIGNED_INT } \ - -#define IotSerializer_ScalarTextString( pTextStringValue ) \ - ( IotSerializerScalarData_t ) { .value = { .u.string.pString = \ - ( ( uint8_t * ) pTextStringValue ), \ - .u.string.length \ - = strlen( pTextStringValue ) }, \ - .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ - -#define IotSerializer_ScalarByteString( pByteStringValue, pByteStringLength ) \ - ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( pByteStringValue ), \ - .u.string.length \ - = ( pByteStringLength ) }, .type = \ - IOT_SERIALIZER_SCALAR_BYTE_STRING } \ - -/* Determine if an object is a container. */ -#define IotSerializer_IsContainer( object ) \ - ( ( object ) \ - && ( ( object )->type == IOT_SERIALIZER_CONTAINER_STREAM \ - || ( object )->type == IOT_SERIALIZER_CONTAINER_ARRAY \ - || ( object )->type == IOT_SERIALIZER_CONTAINER_MAP ) ) - -/* error code returned by serializer function interface */ -typedef enum -{ - IOT_SERIALIZER_SUCCESS = 0, - IOT_SERIALIZER_BUFFER_TOO_SMALL, - IOT_SERIALIZER_OUT_OF_MEMORY, - IOT_SERIALIZER_INVALID_INPUT, - IOT_SERIALIZER_UNDEFINED_TYPE, - IOT_SERIALIZER_NOT_SUPPORTED, - IOT_SERIALIZER_NOT_FOUND, - IOT_SERIALIZER_INTERNAL_FAILURE -} IotSerializerError_t; - -/* two categories: - * 1. scalar: int, string, byte, ... - * 2. container: array, map, none - */ -typedef enum -{ - IOT_SERIALIZER_UNDEFINED = 0, - IOT_SERIALIZER_SCALAR_NULL, - IOT_SERIALIZER_SCALAR_BOOL, - IOT_SERIALIZER_SCALAR_SIGNED_INT, - IOT_SERIALIZER_SCALAR_TEXT_STRING, - IOT_SERIALIZER_SCALAR_BYTE_STRING, - /* below is container */ - IOT_SERIALIZER_CONTAINER_STREAM, - IOT_SERIALIZER_CONTAINER_ARRAY, - IOT_SERIALIZER_CONTAINER_MAP, -} IotSerializerDataType_t; - -/* encapsulate data value of different types */ -typedef struct IotSerializerScalarValue -{ - union - { - int64_t signedInt; - struct - { - uint8_t * pString; - size_t length; - } string; - bool booleanValue; - } u; -} IotSerializerScalarValue_t; - -/* scalar data handle used in encoder */ -typedef struct IotSerializerScalarData -{ - IotSerializerDataType_t type; - IotSerializerScalarValue_t value; -} IotSerializerScalarData_t; - -/* container data handle used in encoder */ -typedef struct IotSerializerEncoderObject -{ - IotSerializerDataType_t type; - void * pHandle; -} IotSerializerEncoderObject_t; - - -/* data handle used in decoder: either container or scalar */ -typedef struct IotSerializerDecoderObject -{ - IotSerializerDataType_t type; - union - { - /* Useful if the type is a container. */ - void * pHandle; - /* if the type is a container, the scalarValue is not useful */ - IotSerializerScalarValue_t value; - } u; -} IotSerializerDecoderObject_t; - -typedef void * IotSerializerDecoderIterator_t; - -/** - * @brief Table containing function pointers for encoder APIs. - */ -typedef struct IotSerializerEncodeInterface -{ - /** - * @brief Return the actual total size after encoding finished. - * - * @param[in] pEncoderObject: the outermost object pointer; behavior is undefined for any other object - * @param[in] pDataBuffer: the buffer pointer passed into init; behavior is undefined for any other buffer pointer - */ - size_t ( * getEncodedSize )( IotSerializerEncoderObject_t * pEncoderObject, - uint8_t * pDataBuffer ); - - /** - * @brief Return the extra size needed when the data to encode exceeds the maximum length of underlying buffer. - * When no exceeding, this should return 0. - * - * @param[in] pEncoderObject: the outermost object pointer; behavior is undefined for any other object - */ - size_t ( * getExtraBufferSizeNeeded )( IotSerializerEncoderObject_t * pEncoderObject ); - - /** - * @brief Initialize the object's handle with specified buffer and max size. - * - * @param[in] pEncoderObject Pointer of Encoder Object. After init, its type will be set to - * IOT_SERIALIZER_CONTAINER_STREAM. - * @param[in] pDataBuffer Pointer to allocated buffer by user; - * NULL pDataBuffer is valid, used to calculate needed size by calling getExtraBufferSizeNeeded. - * @param[in] maxSize Allocated buffer size - */ - IotSerializerError_t ( * init )( IotSerializerEncoderObject_t * pEncoderObject, - uint8_t * pDataBuffer, - size_t maxSize ); - - /** - * @brief Clean the object's handle - * - * @param[in] pEncoderObject The outermost object pointer; behavior is undefined for any other object - */ - void ( * destroy )( IotSerializerEncoderObject_t * pEncoderObject ); - - /** - * @brief Open a child container object. - * - * @param[in] pEncoderObject: the parent object. It must be a container object - * @param[in] pNewEncoderObject: the child object to create. It must be a container object - * @param[in] length: pre-known length of the container or IOT_SERIALIZER_INDEFINITE_LENGTH - * map: the length is number of key-value pairs - * array: the length is number of elements - * none: the length is unuseful - */ - IotSerializerError_t ( * openContainer )( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerEncoderObject_t * pNewEncoderObject, - size_t length ); - - /** - * @brief Open a child container object with string key. - * - * @param[in] pEncoderObject the parent object. It must be a container object - * @param[in] pKey Key for the child container - * @param[in] pNewEncoderObject the child object to create. It must be a container object - * @param[in] length: pre-known length of the container or IOT_SERIALIZER_INDEFINITE_LENGTH - * map: the length is number of key-value pairs - * array: the length is number of elements - * none: the length is unuseful - */ - IotSerializerError_t ( * openContainerWithKey )( IotSerializerEncoderObject_t * pEncoderObject, - const char * pKey, - IotSerializerEncoderObject_t * - pNewEncoderObject, - size_t length ); - - /** - * @brief Close a child container object. - * pEncoderObject and pNewEncoderObject must be parent-child relationship and are in open state - * - * @param[in] pEncoderObject: the parent object. It must be a container object - * @param[in] pNewEncoderObject: the child object to create. It must be a container object - */ - IotSerializerError_t ( * closeContainer )( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerEncoderObject_t * pNewEncoderObject ); - - /** - * @brief Append a scalar data to a container, with type array or none. Map container is invalid - * - * @param[in] pEncoderObject: the parent object. It must be a container object, with type array or none. - * @param[in] scalarData: the scalar data to encode - */ - IotSerializerError_t ( * append )( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerScalarData_t scalarData ); - - /** - * @brief Append a key-value pair to a map container. The key is string type and value is a scalar. - * - * @param[in] pEncoderObject: the parent object. It must be a map container object. - * @param[in] pKey: text string representing key - * @param[in] scalarData: the scalar data to encode - */ - IotSerializerError_t ( * appendKeyValue )( IotSerializerEncoderObject_t * pEncoderObject, - const char * pKey, - IotSerializerScalarData_t scalarData ); -} IotSerializerEncodeInterface_t; - -/** - * @brief Table containing function pointers for decoder APIs. - */ -typedef struct IotSerializerDecodeInterface -{ - /** - * @brief Initialize decoder object with specified buffer. - * - * @param pDecoderObject Pointer to the decoder object allocated by user. - * @param pDataBuffer Pointer to the buffer containing data to be decoded. - * @param maxSize Maximum length of the buffer containing data to be decoded. - * - * @return IOT_SERIALIZER_SUCCESS if successful - */ - IotSerializerError_t ( * init )( IotSerializerDecoderObject_t * pDecoderObject, - const uint8_t * pDataBuffer, - size_t maxSize ); - - /** - * @brief Destroy the decoder object handle - * @param pDecoderObject Pointer to the decoder object - */ - void ( * destroy )( IotSerializerDecoderObject_t * pDecoderObject ); - - - /** - * @brief Steps into a container and creates an iterator to traverse through the container. - * Container can be of type array, map or stream. - * - * @param[in] pDecoderObject Pointer to the decoder object representing the container - * @param[out] pIterator Pointer to iterator which can be used for the traversing the container by calling next() - * - * @return IOT_SERIALIZER_SUCCESS if successful - */ - IotSerializerError_t ( * stepIn )( const IotSerializerDecoderObject_t * pDecoderObject, - IotSerializerDecoderIterator_t * pIterator ); - - - /** - * @brief Gets the object value currently pointed to by an iterator. - * @param iterator The iterator handle - * @param pValueObject Value of the object pointed to by the iterator. - * - * @return IOT_SERIALIZER_SUCCESS if successful - */ - IotSerializerError_t ( * get )( IotSerializerDecoderIterator_t iterator, - IotSerializerDecoderObject_t * pValueObject ); - - /** - * - * @brief Find an object by key within a container. - * Container should always be of type MAP. - * - * @param[in] pDecoderObject Pointer to the decoder object representing container - * @param[in] pKey Pointer to the key for which value needs to be found. - * @param[out] pValueObject Pointer to the value object for the key. - * - * @return IOT_SERIALIZER_SUCCESS if successful - */ - IotSerializerError_t ( * find )( const IotSerializerDecoderObject_t * pDecoderObject, - const char * pKey, - IotSerializerDecoderObject_t * pValueObject ); - - - /* - * Find the next object in the same value and save it to pNewDecoderObject - * - */ - - /** - * @brief Moves the iterator to next object within the container - * If the container is a map, it skips either a key or the value at a time. - * If the container is an array, it skips by one array element. - * - * @param[in] iterator Pointer to iterator - * - * @return IOT_SERIALIZER_SUCCESS if successful - */ - IotSerializerError_t ( * next )( IotSerializerDecoderIterator_t iterator ); - - /** - * @brief Function to check if the iterator reached end of container - * @param[in] iterator Pointer to iterator for the container - * @return IOT_SERIALIZER_SUCCESS if successful - */ - bool ( * isEndOfContainer )( IotSerializerDecoderIterator_t iterator ); - - /** - * @brief Function to obtain the starting buffer address of the raw encoded data (scalar or container type) - * represented by the passed decoder object. - * Container SHOULD be of type array or map. - * - * @param[in] pDecoderObject The decoder object whose underlying data's starting location in the buffer - * is to be returned. - * @param[out] pEncodedDataStartAddr This will be populated with the starting location of the encoded object - * in the data buffer. - * - * @return #IOT_SERIALIZER_SUCCESS if successful; otherwise #IOT_SERIALIZER_NOT_SUPPORTED - * for a non-container type iterator. - */ - IotSerializerError_t ( * getBufferAddress )( const IotSerializerDecoderObject_t * - pDecoderObject, - const uint8_t ** - pEncodedDataStartAddr ); - - - /** - * @brief Function to get the size of the raw encoded data in the buffer (ONLY of container type object) - * that is represented by the passed decoder object. - * Container SHOULD be of type array or map. - * @param[in] pDecoderObject The decoder objects whose underlying buffer data's length needs to be - * calculated. - * @param[out] The length of the underlying data in the buffer represented by the decoder object. - * @return #IOT_SERIALIZER_SUCCESS if successful; otherwise #IOT_SERIALIZER_NOT_SUPPORTED - * for a non-container type iterator. - */ - IotSerializerError_t ( * getSizeOfEncodedData )( const IotSerializerDecoderObject_t * pDecoderObject, - size_t * pEncodedDataLength ); - - - /** - * @brief Steps out of the container by updating the decoder object to next byte position - * after the container. - * The iterator **should** point to the end of the container when calling this function. - * - * @param[in] iterator Pointer to iterator for the container. - * @param[in] The outer decoder object to the same container. - * @return IOT_SERIALIZER_SUCCESS if successful - */ - IotSerializerError_t ( * stepOut )( IotSerializerDecoderIterator_t iterator, - IotSerializerDecoderObject_t * pDecoderObject ); -} IotSerializerDecodeInterface_t; - -/* Global reference of CBOR/JSON encoder and decoder. */ -const IotSerializerEncodeInterface_t * IotSerializer_GetCborEncoder( void ); - -const IotSerializerDecodeInterface_t * IotSerializer_GetCborDecoder( void ); - -const IotSerializerEncodeInterface_t * IotSerializer_GetJsonEncoder( void ); - -const IotSerializerDecodeInterface_t * IotSerializer_GetJsonDecoder( void ); - -#endif /* ifndef IOT_SERIALIZER_H_ */ diff --git a/libraries/standard/serializer/lexicon.txt b/libraries/standard/serializer/lexicon.txt deleted file mode 100644 index 94b0b880ea..0000000000 --- a/libraries/standard/serializer/lexicon.txt +++ /dev/null @@ -1,40 +0,0 @@ -api -aws -bool -cbor -cborencoder -cborerror -cborvalue -com -config -decoderobject -endif -freertos -getbufferaddress -getsizeofencodeddata -html -http -ifndef -init -iot -isoutermost -json -malloc -memcpy -nextvalue -onlinepubs -opengroup -org -outmost -pdecoderobject -pencoderobject -phandle -pserializererror -pvalue -pvalueobject -scalarvalue -sdk -sizeof -tinycbor -todo -www diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c deleted file mode 100644 index 4349829d1a..0000000000 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c +++ /dev/null @@ -1,599 +0,0 @@ -/* - * IoT Serializer V1.1.0 - * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_serializer_tinycbor_decoder.c - * @brief Implements APIs to parse and decode data from CBOR format. The file relies on tiny - * CBOR library to parse and decode data from CBOR format. Supports all major data types within - * the CBOR format. - * The file implements the decoder interface in aws_iot_serialize.h. - */ - -#include "iot_serializer.h" -#include "cbor.h" - -#define _castDecoderObjectToCborValue( \ - pDecoderObject ) ( ( pDecoderObject )->u.pHandle ) - -#define _castDecoderIteratorToCborValue( \ - iterator ) ( ( ( IotSerializerDecoderObject_t * ) iterator )->u. \ - pHandle ) - -#define _isArrayOrMap( dataType ) \ - ( ( ( dataType ) == \ - IOT_SERIALIZER_CONTAINER_ARRAY ) || ( ( dataType ) == IOT_SERIALIZER_CONTAINER_MAP ) ) - -static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject, - const uint8_t * pDataBuffer, - size_t maxSize ); -static IotSerializerError_t _get( IotSerializerDecoderIterator_t iterator, - IotSerializerDecoderObject_t * pValueObject ); - -static IotSerializerError_t _find( const IotSerializerDecoderObject_t * pDecoderObject, - const char * pKey, - IotSerializerDecoderObject_t * pValueObject ); -static IotSerializerError_t _stepIn( const IotSerializerDecoderObject_t * pDecoderObject, - IotSerializerDecoderIterator_t * pIterator ); -static IotSerializerError_t _stepOut( IotSerializerDecoderIterator_t iterator, - IotSerializerDecoderObject_t * pDecoderObject ); - -static IotSerializerError_t _next( IotSerializerDecoderIterator_t iterator ); - -static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ); - -static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ); - -static IotSerializerError_t _getBufferAddress( const IotSerializerDecoderObject_t * pDecoderObject, - const uint8_t ** pEncodedDataStartAddr ); - - - -static IotSerializerError_t _getSizeOfEncodedData( const IotSerializerDecoderObject_t * pDecoderObject, - size_t * pEncodedDataLength ); - - -/*-----------------------------------------------------------*/ - -/** - * @brief Utility to calculates the length of the raw encoded data represented by the - * passed `pValue` object. - */ -static size_t calculateSizeOfCborObject( CborValue * pValue ); - -/*-----------------------------------------------------------*/ - -static const IotSerializerDecodeInterface_t _cborDecoder = -{ - .init = _init, - .get = _get, - .find = _find, - .stepIn = _stepIn, - .stepOut = _stepOut, - .next = _next, - .isEndOfContainer = _isEndOfContainer, - .getBufferAddress = _getBufferAddress, - .getSizeOfEncodedData = _getSizeOfEncodedData, - .destroy = _destroy -}; - -/* Wrapper CborValue with additional fields. */ -typedef struct _cborValueWrapper -{ - CborValue cborValue; - bool isOutermost; -} _cborValueWrapper_t; - -/*-----------------------------------------------------------*/ - -static size_t calculateSizeOfCborObject( CborValue * pValue ) -{ - IotSerializer_Assert( pValue != NULL ); - CborValue nextValue; - CborError status = CborNoError; - - /* Copy the contents of the current CBOR object so that we can advance to the next object. */ - /* memcpy( &nextValue, &pValue->cborValue, sizeof( CborValue ) ); */ - nextValue = *pValue; - - /* Advance to the next element in the container. */ - status = cbor_value_advance( &nextValue ); - IotSerializer_Assert( status == CborNoError ); - - /* Suppress compiler warning of "unused" variable. */ - ( void ) status; - - return( ( size_t ) ( nextValue.ptr - pValue->ptr ) ); -} - -/*-----------------------------------------------------------*/ - - -static void _translateErrorCode( CborError cborError, - IotSerializerError_t * pSerializerError ) -{ - /* TODO: assert cborError == 0 || *pSerializerError == 0 */ - - /* Only translate if there is no error on serializer currently. */ - if( *pSerializerError == IOT_SERIALIZER_SUCCESS ) - { - switch( cborError ) - { - case CborNoError: - *pSerializerError = IOT_SERIALIZER_SUCCESS; - break; - - case CborErrorOutOfMemory: - *pSerializerError = IOT_SERIALIZER_BUFFER_TOO_SMALL; - break; - - default: - *pSerializerError = IOT_SERIALIZER_INTERNAL_FAILURE; - } - } -} - -/*-----------------------------------------------------------*/ - -static IotSerializerDataType_t _toSerializerType( CborType type ) -{ - switch( type ) - { - case CborIntegerType: - - return IOT_SERIALIZER_SCALAR_SIGNED_INT; - - case CborBooleanType: - - return IOT_SERIALIZER_SCALAR_BOOL; - - case CborByteStringType: - - return IOT_SERIALIZER_SCALAR_BYTE_STRING; - - case CborTextStringType: - - return IOT_SERIALIZER_SCALAR_TEXT_STRING; - - case CborArrayType: - - return IOT_SERIALIZER_CONTAINER_ARRAY; - - case CborMapType: - - return IOT_SERIALIZER_CONTAINER_MAP; - - default: - - return IOT_SERIALIZER_UNDEFINED; - } -} - -/*-----------------------------------------------------------*/ - -/* Construct DecoderObject based on the wrapper of CborValue. */ -static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborValueWrapper, - IotSerializerDecoderObject_t * pDecoderObject ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - CborValue * pCborValue = &pCborValueWrapper->cborValue; - CborValue next = { 0 }; - - /* Get serializer data type from CborValue. */ - IotSerializerDataType_t dataType = _toSerializerType( cbor_value_get_type( pCborValue ) ); - - if( dataType == IOT_SERIALIZER_UNDEFINED ) - { - return IOT_SERIALIZER_UNDEFINED_TYPE; - } - else - { - /* Set type to decoder object as long as it is defined. */ - pDecoderObject->type = dataType; - } - - /* If this is a map or array. */ - if( _isArrayOrMap( dataType ) ) - { - /* Save to decoder object's handle. */ - pDecoderObject->u.pHandle = IotSerializer_MallocCborValue( - sizeof( _cborValueWrapper_t ) ); - - if( pDecoderObject->u.pHandle == NULL ) - { - return IOT_SERIALIZER_OUT_OF_MEMORY; - } - - memcpy( pDecoderObject->u.pHandle, pCborValueWrapper, - sizeof( _cborValueWrapper_t ) ); - } - else /* Create scalar object. */ - { - switch( dataType ) - { - case IOT_SERIALIZER_SCALAR_BOOL: - { - bool value = false; - cborError = cbor_value_get_boolean( pCborValue, &( value ) ); - - if( cborError == CborNoError ) - { - pDecoderObject->u.value.u.booleanValue = value; - } - else - { - returnedError = IOT_SERIALIZER_INTERNAL_FAILURE; - } - - break; - } - - case IOT_SERIALIZER_SCALAR_SIGNED_INT: - { - int i = 0; - cborError = cbor_value_get_int( pCborValue, &i ); - - if( cborError == CborNoError ) - { - pDecoderObject->u.value.u.signedInt = i; - } - else - { - returnedError = IOT_SERIALIZER_INTERNAL_FAILURE; - } - - break; - } - - case IOT_SERIALIZER_SCALAR_BYTE_STRING: - case IOT_SERIALIZER_SCALAR_TEXT_STRING: - - if( dataType == IOT_SERIALIZER_SCALAR_BYTE_STRING ) - { - cborError = cbor_value_copy_byte_string( - pCborValue, - pDecoderObject->u.value.u.string.pString, - &( pDecoderObject->u.value.u.string.length ), - &next ); - } - else - { - cborError = cbor_value_copy_text_string( - pCborValue, - ( char * ) pDecoderObject->u.value.u.string.pString, - &( pDecoderObject->u.value.u.string.length ), - &next ); - } - - if( cborError != CborNoError ) - { - if( ( cborError == CborErrorOutOfMemory ) && - ( pDecoderObject->u.value.u.string.pString == NULL ) && - ( cbor_value_is_length_known( pCborValue ) ) ) - - { - /* - * If its a finite length text/byte string, and user have passed a null length buffer, - * we avoid copying the string by storing pointer to the start of the string. - */ - pDecoderObject->u.value.u.string.pString = - ( uint8_t * ) ( cbor_value_get_next_byte( &next ) - - ( pDecoderObject->u.value.u.string.length ) ); - } - else - { - returnedError = IOT_SERIALIZER_INTERNAL_FAILURE; - } - } - - break; - - default: - /* Other scalar types are not supported. */ - returnedError = IOT_SERIALIZER_NOT_SUPPORTED; - } - } - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject, - const uint8_t * pDataBuffer, - size_t maxSize ) -{ - CborParser * pCborParser = IotSerializer_MallocCborParser( sizeof( CborParser ) ); - _cborValueWrapper_t cborValueWrapper = { .isOutermost = 0 }; - - if( pCborParser == NULL ) - { - return IOT_SERIALIZER_OUT_OF_MEMORY; - } - - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - cborError = cbor_parser_init( - pDataBuffer, - maxSize, - 0, - pCborParser, - &cborValueWrapper.cborValue ); - - /* If init succeeds, create the decoder object. */ - if( cborError == CborNoError ) - { - cborValueWrapper.isOutermost = true; - - returnedError = _createDecoderObject( &cborValueWrapper, pDecoderObject ); - } - - /* If there is any error or decoder object is a scalar type, free the cbor resources. */ - if( cborError || returnedError ) - { - /* pDecoderObject is untouched. */ - - IotSerializer_FreeCborParser( pCborParser ); - - if( _isArrayOrMap( pDecoderObject->type ) && - ( pDecoderObject->u.pHandle != NULL ) ) - { - IotSerializer_FreeCborValue( pDecoderObject->u.pHandle ); - } - } - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ) -{ - /* If it is not an container, no action. */ - if( !IotSerializer_IsContainer( pDecoderObject ) ) - { - return; - } - - _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); - - /* If this is outmost object, the parser's memory needs to be freed. */ - if( pCborValueWrapper->isOutermost ) - { - IotSerializer_FreeCborParser( ( void * ) ( pCborValueWrapper->cborValue.parser ) ); - } - - IotSerializer_FreeCborValue( pCborValueWrapper ); - - /* Reset decoder object's handle to NULL. */ - pDecoderObject->u.pHandle = NULL; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _get( IotSerializerDecoderIterator_t iterator, - IotSerializerDecoderObject_t * pValueObject ) -{ - _cborValueWrapper_t * pCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); - - return _createDecoderObject( pCborValueWrapper, pValueObject ); -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _find( const IotSerializerDecoderObject_t * pDecoderObject, - const char * pKey, - IotSerializerDecoderObject_t * pValueObject ) -{ - _cborValueWrapper_t newCborValueWrapper; - - if( pDecoderObject->type != IOT_SERIALIZER_CONTAINER_MAP ) - { - return IOT_SERIALIZER_INVALID_INPUT; - } - - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); - - /* Set this object not to be outermost one. */ - newCborValueWrapper.isOutermost = false; - - cborError = cbor_value_map_find_value( - &pCborValueWrapper->cborValue, - pKey, - &newCborValueWrapper.cborValue ); - - if( cborError == CborNoError ) - { - /* If not found the element in map. */ - if( newCborValueWrapper.cborValue.type == CborInvalidType ) - { - /* pValueObject is untouched. */ - - returnedError = IOT_SERIALIZER_NOT_FOUND; - } - else - { - returnedError = _createDecoderObject( &newCborValueWrapper, pValueObject ); - } - } - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _stepIn( const IotSerializerDecoderObject_t * pDecoderObject, - IotSerializerDecoderIterator_t * pIterator ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); - _cborValueWrapper_t * pNewCborValueWrapper = IotSerializer_MallocCborValue( - sizeof( _cborValueWrapper_t ) ); - - cborError = cbor_value_enter_container( - &pCborValueWrapper->cborValue, - &pNewCborValueWrapper->cborValue ); - - if( cborError == CborNoError ) - { - IotSerializerDecoderObject_t * pNewObject = IotSerializer_MallocDecoderObject( - sizeof( IotSerializerDecoderObject_t ) ); - - pNewObject->type = pDecoderObject->type; - - pNewCborValueWrapper->isOutermost = false; - pNewObject->u.pHandle = ( void * ) pNewCborValueWrapper; - - *pIterator = ( IotSerializerDecoderIterator_t ) pNewObject; - - returnedError = IOT_SERIALIZER_SUCCESS; - } - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _stepOut( IotSerializerDecoderIterator_t iterator, - IotSerializerDecoderObject_t * pDecoderObject ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - _cborValueWrapper_t * pOuterCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); - _cborValueWrapper_t * pInnerCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); - - - CborValue outerCborValueCopy; - - /* Clone the underlying CborValue object of the parent container, so that the original CborValue object's state/data - * is unaffected with this function's operation. This allows the container's decoder object re-usable for accessor - * and iteration operations. - * - * Note: By performing a cbor_value_leave_container() operation on the cloned copy, only the state/data of the copy - * is changed.*/ - memcpy( &outerCborValueCopy, &pOuterCborValueWrapper->cborValue, sizeof( CborValue ) ); - - cborError = cbor_value_leave_container( - &outerCborValueCopy, - &pInnerCborValueWrapper->cborValue ); - - if( cborError == CborNoError ) - { - IotSerializer_FreeCborValue( pInnerCborValueWrapper ); - IotSerializer_FreeDecoderObject( iterator ); - } - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _next( IotSerializerDecoderIterator_t iterator ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - _cborValueWrapper_t * pCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); - - cborError = cbor_value_advance( &pCborValueWrapper->cborValue ); - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ) -{ - _cborValueWrapper_t * pCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); - - return cbor_value_at_end( &pCborValueWrapper->cborValue ); -} -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _getBufferAddress( const IotSerializerDecoderObject_t * pDecoderObject, - const uint8_t ** pEncodedDataStartAddr ) -{ - IotSerializer_Assert( pDecoderObject != NULL ); - - IotSerializerError_t status = IOT_SERIALIZER_SUCCESS; - - if( IotSerializer_IsContainer( pDecoderObject ) ) - { - *pEncodedDataStartAddr = - ( ( _cborValueWrapper_t * ) ( pDecoderObject->u.pHandle ) )->cborValue.ptr; - } - else - { - status = IOT_SERIALIZER_NOT_SUPPORTED; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotSerializerError_t _getSizeOfEncodedData( const IotSerializerDecoderObject_t * pDecoderObject, - size_t * pEncodedDataLength ) -{ - IotSerializer_Assert( pDecoderObject != NULL ); - IotSerializerError_t status = IOT_SERIALIZER_SUCCESS; - - if( IotSerializer_IsContainer( pDecoderObject ) ) - { - *pEncodedDataLength = calculateSizeOfCborObject( - &( ( ( _cborValueWrapper_t * ) pDecoderObject->u.pHandle )->cborValue ) ); - } - else - { - status = IOT_SERIALIZER_NOT_SUPPORTED; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -const IotSerializerDecodeInterface_t * IotSerializer_GetCborDecoder( void ) -{ - return &_cborDecoder; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c deleted file mode 100644 index ec02e948d0..0000000000 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c +++ /dev/null @@ -1,335 +0,0 @@ -/* - * IoT Serializer V1.1.0 - * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_serializer_tinycbor_encoder.c - * @brief Implements APIs to serialize data in CBOR format. The file relies on tiny - * CBOR library to serialize data into CBOR format. Supports all major data types within - * the CBOR format. - * The file implements the encoder interface in aws_iot_serialize.h. - */ - -#include "iot_serializer.h" -#include "cbor.h" - -/* Translate cbor error to serializer error. */ -static void _translateErrorCode( CborError cborError, - IotSerializerError_t * pSerializerError ); - -static size_t _getEncodedSize( IotSerializerEncoderObject_t * pEncoderObject, - uint8_t * pDataBuffer ); -static size_t _getExtraBufferSizeNeeded( IotSerializerEncoderObject_t * pEncoderObject ); -static IotSerializerError_t _init( IotSerializerEncoderObject_t * pEncoderObject, - uint8_t * pDataBuffer, - size_t maxSize ); -static void _destroy( IotSerializerEncoderObject_t * pEncoderObject ); -static IotSerializerError_t _openContainer( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerEncoderObject_t * pNewEncoderObject, - size_t length ); - -static IotSerializerError_t _openContainerWithKey( IotSerializerEncoderObject_t * pEncoderObject, - const char * pKey, - IotSerializerEncoderObject_t * pNewEncoderObject, - size_t length ); -static IotSerializerError_t _closeContainer( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerEncoderObject_t * pNewEncoderObject ); -static IotSerializerError_t _append( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerScalarData_t scalarData ); -static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEncoderObject, - const char * pKey, - IotSerializerScalarData_t scalarData ); - - -static const IotSerializerEncodeInterface_t _cborEncoder = -{ - .getEncodedSize = _getEncodedSize, - .getExtraBufferSizeNeeded = _getExtraBufferSizeNeeded, - .init = _init, - .destroy = _destroy, - .openContainer = _openContainer, - .openContainerWithKey = _openContainerWithKey, - .closeContainer = _closeContainer, - .append = _append, - .appendKeyValue = _appendKeyValue, -}; - -/*-----------------------------------------------------------*/ - -static void _translateErrorCode( CborError cborError, - IotSerializerError_t * pSerializerError ) -{ - /* This function translate cbor error to serializer error. - * It doesn't make sense that both of them are of error (greater than 0). - */ - IotSerializer_Assert( cborError == 0 || *pSerializerError == 0 ); - - /* Only translate if there is no error on serializer currently. */ - if( *pSerializerError == IOT_SERIALIZER_SUCCESS ) - { - switch( cborError ) - { - case CborNoError: - *pSerializerError = IOT_SERIALIZER_SUCCESS; - break; - - case CborErrorOutOfMemory: - *pSerializerError = IOT_SERIALIZER_BUFFER_TOO_SMALL; - break; - - default: - *pSerializerError = IOT_SERIALIZER_INTERNAL_FAILURE; - } - } -} - -/*-----------------------------------------------------------*/ - -static size_t _getEncodedSize( IotSerializerEncoderObject_t * pEncoderObject, - uint8_t * pDataBuffer ) -{ - CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - - return cbor_encoder_get_buffer_size( pCborEncoder, pDataBuffer ); -} - -/*-----------------------------------------------------------*/ - -static size_t _getExtraBufferSizeNeeded( IotSerializerEncoderObject_t * pEncoderObject ) -{ - CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - - return cbor_encoder_get_extra_bytes_needed( pCborEncoder ); -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _init( IotSerializerEncoderObject_t * pEncoderObject, - uint8_t * pDataBuffer, - size_t maxSize ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - - /* Unused flags for tinycbor init. */ - int unusedCborFlags = 0; - - CborEncoder * pCborEncoder = IotSerializer_MallocCborEncoder( sizeof( CborEncoder ) ); - - if( pCborEncoder != NULL ) - { - /* Store the CborEncoder pointer to handle. */ - pEncoderObject->pHandle = pCborEncoder; - - /* Always set outmost type to IOT_SERIALIZER_CONTAINER_STREAM. */ - pEncoderObject->type = IOT_SERIALIZER_CONTAINER_STREAM; - - /* Perform the tinycbor init. */ - cbor_encoder_init( pCborEncoder, pDataBuffer, maxSize, unusedCborFlags ); - } - else - { - /* pEncoderObject is untouched. */ - - returnedError = IOT_SERIALIZER_OUT_OF_MEMORY; - } - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static void _destroy( IotSerializerEncoderObject_t * pEncoderObject ) -{ - CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - - /* Free the memory allocated in init function. */ - IotSerializer_FreeCborEncoder( pCborEncoder ); - - /* Reset pHandle to be NULL. */ - pEncoderObject->pHandle = NULL; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _openContainer( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerEncoderObject_t * pNewEncoderObject, - size_t length ) -{ - /* New object must be a container of map or array. */ - if( ( pNewEncoderObject->type != IOT_SERIALIZER_CONTAINER_ARRAY ) && - ( pNewEncoderObject->type != IOT_SERIALIZER_CONTAINER_MAP ) ) - { - return IOT_SERIALIZER_INVALID_INPUT; - } - - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - CborEncoder * pOuterEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - CborEncoder * pInnerEncoder = IotSerializer_MallocCborEncoder( sizeof( CborEncoder ) ); - - if( pInnerEncoder != NULL ) - { - /* Store the CborEncoder pointer to handle. */ - pNewEncoderObject->pHandle = pInnerEncoder; - - switch( pNewEncoderObject->type ) - { - case IOT_SERIALIZER_CONTAINER_MAP: - cborError = cbor_encoder_create_map( pOuterEncoder, pInnerEncoder, length ); - break; - - case IOT_SERIALIZER_CONTAINER_ARRAY: - cborError = cbor_encoder_create_array( pOuterEncoder, pInnerEncoder, length ); - break; - - default: - IotSerializer_Assert( 0 ); - } - } - else - { - /* pEncoderObject is untouched. */ - returnedError = IOT_SERIALIZER_OUT_OF_MEMORY; - } - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _openContainerWithKey( IotSerializerEncoderObject_t * pEncoderObject, - const char * pKey, - IotSerializerEncoderObject_t * pNewEncoderObject, - size_t length ) -{ - IotSerializerScalarData_t keyScalarData = { 0 }; - - keyScalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - keyScalarData.value.u.string.pString = ( uint8_t * ) pKey; - keyScalarData.value.u.string.length = strlen( pKey ); - - IotSerializerError_t returnedError = _append( pEncoderObject, keyScalarData ); - - /* Buffer too small is a special error case that serialization should continue. */ - if( ( returnedError == IOT_SERIALIZER_SUCCESS ) || ( returnedError == IOT_SERIALIZER_BUFFER_TOO_SMALL ) ) - { - returnedError = _openContainer( pEncoderObject, pNewEncoderObject, length ); - } - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _closeContainer( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerEncoderObject_t * pNewEncoderObject ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - CborEncoder * pOuterEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - CborEncoder * pInnerEncoder = ( CborEncoder * ) pNewEncoderObject->pHandle; - - cborError = cbor_encoder_close_container( pOuterEncoder, pInnerEncoder ); - - /* Free inner encoder's memory regardless the result of "close container". */ - IotSerializer_FreeCborEncoder( pInnerEncoder ); - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _append( IotSerializerEncoderObject_t * pEncoderObject, - IotSerializerScalarData_t scalarData ) -{ - IotSerializerError_t returnedError = IOT_SERIALIZER_SUCCESS; - CborError cborError = CborNoError; - - CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - - switch( scalarData.type ) - { - case IOT_SERIALIZER_SCALAR_SIGNED_INT: - cborError = cbor_encode_int( pCborEncoder, scalarData.value.u.signedInt ); - break; - - case IOT_SERIALIZER_SCALAR_TEXT_STRING: - cborError = cbor_encode_text_string( pCborEncoder, ( char * ) scalarData.value.u.string.pString, scalarData.value.u.string.length ); - break; - - case IOT_SERIALIZER_SCALAR_BYTE_STRING: - cborError = cbor_encode_byte_string( pCborEncoder, scalarData.value.u.string.pString, scalarData.value.u.string.length ); - break; - - case IOT_SERIALIZER_SCALAR_BOOL: - cborError = cbor_encode_boolean( pCborEncoder, scalarData.value.u.booleanValue ); - break; - - case IOT_SERIALIZER_SCALAR_NULL: - cborError = cbor_encode_null( pCborEncoder ); - break; - - default: - returnedError = IOT_SERIALIZER_UNDEFINED_TYPE; - } - - _translateErrorCode( cborError, &returnedError ); - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEncoderObject, - const char * pKey, - IotSerializerScalarData_t scalarData ) -{ - IotSerializerScalarData_t keyScalarData = { 0 }; - - keyScalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - keyScalarData.value.u.string.pString = ( uint8_t * ) pKey; - keyScalarData.value.u.string.length = strlen( pKey ); - - IotSerializerError_t returnedError = _append( pEncoderObject, keyScalarData ); - - /* Buffer too small is a special error case that serialization should continue. */ - if( ( returnedError == IOT_SERIALIZER_SUCCESS ) || ( returnedError == IOT_SERIALIZER_BUFFER_TOO_SMALL ) ) - { - returnedError = _append( pEncoderObject, scalarData ); - } - - return returnedError; -} - -/*-----------------------------------------------------------*/ - -const IotSerializerEncodeInterface_t * IotSerializer_GetCborEncoder( void ) -{ - return &_cborEncoder; -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/serializer/src/iot_serializer_static_memory.c b/libraries/standard/serializer/src/iot_serializer_static_memory.c deleted file mode 100644 index bf43906c5c..0000000000 --- a/libraries/standard/serializer/src/iot_serializer_static_memory.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * IoT Serializer V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ -#include -#include - -/* Static memory include. */ -#include "iot_static_memory.h" - -/* Metrics include. */ -#include "iot_serializer.h" - -/* TinyCBOR include. */ -#include "cbor.h" - -#ifndef IOT_SERIALIZER_CBOR_ENCODERS - #define IOT_SERIALIZER_CBOR_ENCODERS ( 10 ) -#endif - -#ifndef IOT_SERIALIZER_CBOR_PARSERS - #define IOT_SERIALIZER_CBOR_PARSERS ( 10 ) -#endif - -#ifndef IOT_SERIALIZER_CBOR_VALUES - #define IOT_SERIALIZER_CBOR_VALUES ( 10 ) -#endif - -#ifndef IOT_SERIALIZER_DECODER_OBJECTS - #define IOT_SERIALIZER_DECODER_OBJECTS ( 10 ) -#endif - -/* Validate static memory configuration settings. */ -#if IOT_SERIALIZER_CBOR_ENCODERS <= 0 - #error "IOT_SERIALIZER_CBOR_ENCODERS cannot be 0 or negative." -#endif - -#if IOT_SERIALIZER_CBOR_PARSERS <= 0 - #error "IOT_SERIALIZER_CBOR_PARSERS cannot be 0 or negative." -#endif - -#if IOT_SERIALIZER_CBOR_VALUES <= 0 - #error "IOT_SERIALIZER_CBOR_VALUES cannot be 0 or negative." -#endif - -#if IOT_SERIALIZER_DECODER_OBJECTS <= 0 - #error "IOT_SERIALIZER_DECODER_OBJECTS cannot be 0 or negative." -#endif - -/** - * @todo Placeholder. - */ -typedef struct _cborValueWrapper -{ - CborValue cborValue; /**< @brief Placeholder. */ - bool isOutermost; /**< @brief Placeholder. */ -} _cborValueWrapper_t; - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ -static uint32_t _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0U }; -static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { .data = { 0 } } }; - -static uint32_t _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0U }; -static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; - -static uint32_t _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0U }; -static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { .isOutermost = false } }; - -static uint32_t _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0U }; -static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; - -/*-----------------------------------------------------------*/ - -void * IotSerializer_MallocCborEncoder( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewCborEncoder = NULL; - - if( size == sizeof( CborEncoder ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseCborEncoders, - IOT_SERIALIZER_CBOR_ENCODERS ); - - if( freeIndex != -1 ) - { - pNewCborEncoder = &( _cborEncoders[ freeIndex ] ); - } - } - - return pNewCborEncoder; -} - -/*-----------------------------------------------------------*/ - -void IotSerializer_FreeCborEncoder( void * ptr ) -{ - IotStaticMemory_ReturnInUse( ptr, - _cborEncoders, - _inUseCborEncoders, - IOT_SERIALIZER_CBOR_ENCODERS, - sizeof( CborEncoder ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotSerializer_MallocCborParser( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewCborParser = NULL; - - if( size == sizeof( CborParser ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseCborParsers, - IOT_SERIALIZER_CBOR_PARSERS ); - - if( freeIndex != -1 ) - { - pNewCborParser = &( _cborParsers[ freeIndex ] ); - } - } - - return pNewCborParser; -} - -/*-----------------------------------------------------------*/ - -void IotSerializer_FreeCborParser( void * ptr ) -{ - IotStaticMemory_ReturnInUse( ptr, - _cborParsers, - _inUseCborParsers, - IOT_SERIALIZER_CBOR_PARSERS, - sizeof( CborParser ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotSerializer_MallocCborValue( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewCborValue = NULL; - - if( size == sizeof( _cborValueWrapper_t ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseCborValues, - IOT_SERIALIZER_CBOR_VALUES ); - - if( freeIndex != -1 ) - { - pNewCborValue = &( _cborValues[ freeIndex ] ); - } - } - - return pNewCborValue; -} - -/*-----------------------------------------------------------*/ - -void IotSerializer_FreeCborValue( void * ptr ) -{ - IotStaticMemory_ReturnInUse( ptr, - _cborValues, - _inUseCborValues, - IOT_SERIALIZER_CBOR_VALUES, - sizeof( _cborValueWrapper_t ) ); -} - -/*-----------------------------------------------------------*/ - -void * IotSerializer_MallocDecoderObject( size_t size ) -{ - int32_t freeIndex = -1; - void * pNewDecoderObject = NULL; - - if( size == sizeof( IotSerializerDecoderObject_t ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseDecoderObjects, - IOT_SERIALIZER_DECODER_OBJECTS ); - - if( freeIndex != -1 ) - { - pNewDecoderObject = &( _decoderObjects[ freeIndex ] ); - } - } - - return pNewDecoderObject; -} - -/*-----------------------------------------------------------*/ - -void IotSerializer_FreeDecoderObject( void * ptr ) -{ - IotStaticMemory_ReturnInUse( ptr, - _decoderObjects, - _inUseDecoderObjects, - IOT_SERIALIZER_DECODER_OBJECTS, - sizeof( IotSerializerDecoderObject_t ) ); -} - -#endif diff --git a/libraries/standard/serializer/test/iot_tests_serializer.c b/libraries/standard/serializer/test/iot_tests_serializer.c deleted file mode 100644 index 0833fbdfa4..0000000000 --- a/libraries/standard/serializer/test/iot_tests_serializer.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * IoT Serializer V1.1.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_serializer.c - * @brief Test runner for Serializer tests. - */ - -/* Standard includes. */ -#include - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Runs the Serializer test groups. - * - * @param[in] disableNetworkTests Whether tests that require the network should run. - * @param[in] disableLongTests Whether tests that take a long time should run. - */ -void RunSerializerTests( bool disableNetworkTests, - bool disableLongTests ) -{ - /* Silence warnings about unused parameters. */ - ( void ) disableNetworkTests; - ( void ) disableLongTests; - - RUN_TEST_GROUP( Serializer_Unit_CBOR ); - RUN_TEST_GROUP( Serializer_Decoder_Unit_CBOR ); -} - -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c deleted file mode 100644 index 2544441560..0000000000 --- a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c +++ /dev/null @@ -1,687 +0,0 @@ -/* - * IoT Serializer V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* Standard includes. */ -#include -#include -#include -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Unity framework includes. */ -#include "unity_fixture.h" -#include "unity.h" - -/* Serializer and CBOR includes. */ -#include "iot_serializer.h" -#include "cbor.h" - -#define BUFFER_SIZE 100 - -static const IotSerializerEncodeInterface_t * _pCborEncoder = NULL; -static const IotSerializerDecodeInterface_t * _pCborDecoder = NULL; - -static IotSerializerEncoderObject_t _encoderObject; - -static uint8_t _buffer[ BUFFER_SIZE ]; - -TEST_GROUP( Serializer_Unit_CBOR ); - -TEST_SETUP( Serializer_Unit_CBOR ) -{ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - _pCborEncoder = IotSerializer_GetCborEncoder(); - _pCborDecoder = IotSerializer_GetCborDecoder(); - - /* Reset buffer to zero. */ - memset( _buffer, 0, BUFFER_SIZE ); - - /* Init encoder object with buffer. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->init( &_encoderObject, _buffer, BUFFER_SIZE ) ); -} - -TEST_TEAR_DOWN( Serializer_Unit_CBOR ) -{ - /* Destroy encoder object. */ - _pCborEncoder->destroy( &_encoderObject ); - - TEST_ASSERT_NULL( _encoderObject.pHandle ); - - IotSdk_Cleanup(); -} - -/* TODO: - * - append NULL - * - append bool - */ -TEST_GROUP_RUNNER( Serializer_Unit_CBOR ) -{ - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ); - - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_append_integer ); - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_append_text_string ); - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_append_byte_string ); - - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_open_a_scalar ); - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_open_map ); - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_open_array ); - - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_map_nest_map ); - RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_map_nest_array ); -} - -TEST( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ) -{ - IotSerializerEncoderObject_t encoderObject = { .type = ( IotSerializerDataType_t ) 0 }; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->init( &encoderObject, NULL, 0 ) ); - - /* Set the type to stream. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_STREAM, encoderObject.type ); - - /* Assigned value to handle pointer. */ - TEST_ASSERT_NOT_NULL( encoderObject.pHandle ); - - /* Append an integer. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_BUFFER_TOO_SMALL, _pCborEncoder->append( &encoderObject, - IotSerializer_ScalarSignedInt( - 1 ) ) ); - - /* Needed buffer size is 1 to encode integer "1". */ - TEST_ASSERT_EQUAL( 1, _pCborEncoder->getExtraBufferSizeNeeded( &encoderObject ) ); - - _pCborEncoder->destroy( &encoderObject ); - - TEST_ASSERT_NULL( encoderObject.pHandle ); -} - -TEST( Serializer_Unit_CBOR, Encoder_append_integer ) -{ - int64_t value = 6; - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; - scalarData.value.u.signedInt = value; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &_encoderObject, scalarData ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermostValue; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &outermostValue ) ); - - int64_t result = 0; - TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &outermostValue, &result ) ); - - TEST_ASSERT_EQUAL( value, result ); - - /* Encoded size is 1. */ - TEST_ASSERT_EQUAL( 1, _pCborEncoder->getEncodedSize( &_encoderObject, _buffer ) ); -} - -TEST( Serializer_Unit_CBOR, Encoder_append_text_string ) -{ - char * str = "hello world"; - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - scalarData.value.u.string.pString = ( uint8_t * ) str; - scalarData.value.u.string.length = strlen( str ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &_encoderObject, scalarData ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermostValue; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &outermostValue ) ); - - bool equal = false; - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_text_string_equals( &outermostValue, str, &equal ) ); - - TEST_ASSERT_TRUE( equal ); -} - -TEST( Serializer_Unit_CBOR, Encoder_append_byte_string ) -{ - uint8_t inputBytes[] = "hello world"; - size_t inputLength = strlen( ( const char * ) inputBytes ); - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_BYTE_STRING; - scalarData.value.u.string.pString = ( uint8_t * ) inputBytes; - scalarData.value.u.string.length = inputLength; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &_encoderObject, scalarData ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermostValue; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborByteStringType, cbor_value_get_type( &outermostValue ) ); - - uint8_t outputBytes[ 20 ]; - size_t outputLength = 20; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_copy_byte_string( &outermostValue, outputBytes, &outputLength, - NULL ) ); - - TEST_ASSERT_EQUAL( inputLength, outputLength ); - - TEST_ASSERT_EQUAL( 0, strcmp( ( const char * ) inputBytes, ( const char * ) outputBytes ) ); -} - -TEST( Serializer_Unit_CBOR, Encoder_open_a_scalar ) -{ - IotSerializerEncoderObject_t integerObject = - { - .pHandle = NULL, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT - }; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_INVALID_INPUT, - _pCborEncoder->openContainer( &_encoderObject, &integerObject, 1 ) ); -} - -TEST( Serializer_Unit_CBOR, Encoder_open_map ) -{ - IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - scalarData.value.u.string.pString = ( uint8_t * ) "value"; - scalarData.value.u.string.length = 5; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainer( &_encoderObject, &mapObject, 1 ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->appendKeyValue( &mapObject, "key", scalarData ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->closeContainer( &_encoderObject, &mapObject ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermostValue, value; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_map_find_value( &outermostValue, "key", &value ) ); - - TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &value ) ); - - bool equal = false; - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_text_string_equals( &value, "value", &equal ) ); - - TEST_ASSERT_TRUE( equal ); -} - -TEST( Serializer_Unit_CBOR, Encoder_open_array ) -{ - uint8_t i = 0; - IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; - - int64_t numberArray[] = { 3, 2, 1 }; - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainer( &_encoderObject, &arrayObject, 3 ) ); - - for( i = 0; i < 3; i++ ) - { - scalarData.value.u.signedInt = numberArray[ i ]; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &arrayObject, scalarData ) ); - } - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->closeContainer( &_encoderObject, &arrayObject ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermostValue, arrayValue; - int64_t number = 0; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborArrayType, cbor_value_get_type( &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_enter_container( &outermostValue, &arrayValue ) ); - - for( i = 0; i < 3; i++ ) - { - TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &arrayValue ) ); - TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &arrayValue, &number ) ); - TEST_ASSERT_EQUAL( numberArray[ i ], number ); - - TEST_ASSERT_EQUAL( CborNoError, cbor_value_advance( &arrayValue ) ); - } - - TEST_ASSERT_TRUE( cbor_value_at_end( &arrayValue ) ); -} - -TEST( Serializer_Unit_CBOR, Encoder_map_nest_map ) -{ - IotSerializerEncoderObject_t mapObject_1 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerEncoderObject_t mapObject_2 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; - scalarData.value.u.string.pString = ( uint8_t * ) "value"; - scalarData.value.u.string.length = 5; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainer( &_encoderObject, &mapObject_1, 1 ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainerWithKey( &mapObject_1, "map1", &mapObject_2, - 1 ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->appendKeyValue( &mapObject_2, "key", scalarData ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->closeContainer( &mapObject_1, &mapObject_2 ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->closeContainer( &_encoderObject, &mapObject_1 ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermostValue, map1, str; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermostValue ) ); - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_map_find_value( &outermostValue, "map1", &map1 ) ); - - TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &map1 ) ); - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_map_find_value( &map1, "key", &str ) ); - - TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &str ) ); - - bool equal = false; - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_text_string_equals( &str, "value", &equal ) ); - - TEST_ASSERT_TRUE( equal ); -} - -TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) -{ - uint8_t i = 0; - IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; - IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; - IotSerializerScalarData_t scalarData = { 0 }; - - scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; - - int64_t numberArray[] = { 3, 2, 1 }; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainer( &_encoderObject, &mapObject, 1 ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainerWithKey( &mapObject, "array", &arrayObject, - 3 ) ); - - for( i = 0; i < 3; i++ ) - { - scalarData.value.u.signedInt = numberArray[ i ]; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &arrayObject, scalarData ) ); - } - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->closeContainer( &mapObject, &arrayObject ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->closeContainer( &_encoderObject, &mapObject ) ); - - /* --- Verification --- */ - - CborParser parser; - CborValue outermost, array, arrayElement; - int64_t number = 0; - - TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermost ) ); - - TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermost ) ); - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_map_find_value( &outermost, "array", &array ) ); - - TEST_ASSERT_EQUAL( CborArrayType, cbor_value_get_type( &array ) ); - - TEST_ASSERT_EQUAL( CborNoError, - cbor_value_enter_container( &array, &arrayElement ) ); - - for( i = 0; i < 3; i++ ) - { - TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &arrayElement ) ); - TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &arrayElement, &number ) ); - TEST_ASSERT_EQUAL( numberArray[ i ], number ); - - TEST_ASSERT_EQUAL( CborNoError, cbor_value_advance( &arrayElement ) ); - } - - TEST_ASSERT_TRUE( cbor_value_at_end( &arrayElement ) ); -} - -static const uint8_t _testEncodedNestedMap[] = -{ - 0xA2, /* # map(2) */ - 0x61, /* # text(1) */ - 0x31, /* # "1" */ - 0xA1, /* # map(1) */ - 0x61, /* # text(1) */ - 0x41, /* # "A" */ - 0x0A, /* # unsigned(10) */ - 0x61, /* # text(1) */ - 0x33, /* # "3" */ - 0xF4, /* # false */ -}; - -TEST_GROUP( Serializer_Decoder_Unit_CBOR ); - -TEST_SETUP( Serializer_Decoder_Unit_CBOR ) -{ - TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); - - _pCborDecoder = IotSerializer_GetCborDecoder(); -} - -TEST_TEAR_DOWN( Serializer_Decoder_Unit_CBOR ) -{ - IotSdk_Cleanup(); -} - -TEST_GROUP_RUNNER( Serializer_Decoder_Unit_CBOR ) -{ - RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderObjectWithNestedMap ); - RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderIteratorWithNestedMap ); - RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderObjectReuseAfterIteration ); -} - -TEST( Serializer_Decoder_Unit_CBOR, TestDecoderObjectWithNestedMap ) -{ - IotSerializerDecoderObject_t outermostDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - const uint8_t * pDecoderObjectStartAddr = NULL; - size_t decoderDataLength = 0; - IotSerializerDecoderObject_t outerMapDecoder1 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t innerMapDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t outerMapDecoder2 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - size_t unsupportedTypeDecoderObjectLength = 0; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &outermostDecoder, - _testEncodedNestedMap, - sizeof( _testEncodedNestedMap ) ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, outermostDecoder.type ); - - /* Make sure that the getBufferAddress() API returns the first location of the buffer for the - * outermost decoder object.*/ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborDecoder->getBufferAddress( &outermostDecoder, - &pDecoderObjectStartAddr ) ); - TEST_ASSERT_EQUAL_PTR( &_testEncodedNestedMap[ 0 ], pDecoderObjectStartAddr ); - - /* Verify that the getSizeOfEncodedData() correctly calculates the length of the outermost decoder - * data. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->getSizeOfEncodedData( - &outermostDecoder, &decoderDataLength ) ); - TEST_ASSERT_EQUAL( sizeof( _testEncodedNestedMap ), decoderDataLength ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outermostDecoder, "1", - &outerMapDecoder1 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, outerMapDecoder1.type ); - - /* Make sure that the getBufferAddress() API returns the first location in the buffer to the value - * for the entry keyed by "1".*/ - TEST_ASSERT_EQUAL_PTR( IOT_SERIALIZER_SUCCESS, - _pCborDecoder->getBufferAddress( &outerMapDecoder1, - &pDecoderObjectStartAddr ) ); - TEST_ASSERT_EQUAL_PTR( &_testEncodedNestedMap[ 3 ], pDecoderObjectStartAddr ); - - /* Verify that the getSizeOfEncodedData() correctly calculates the length of the container type - * value of the entry keyed by "1" */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->getSizeOfEncodedData( - &outerMapDecoder1, &decoderDataLength ) ); - TEST_ASSERT_EQUAL( 4u, decoderDataLength ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outerMapDecoder1, "A", - &innerMapDecoder ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_SIGNED_INT, innerMapDecoder.type ); - - /* Make sure that the getBufferAddress() API does not support getting buffer address of value in the - * the nested entry keyed by "A".*/ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, - _pCborDecoder->getBufferAddress( &innerMapDecoder, - &pDecoderObjectStartAddr ) ); - - /* Verify that the getSizeOfEncodedData() does not support calculation of non-container type data.*/ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, - _pCborDecoder->getSizeOfEncodedData( - &innerMapDecoder, &unsupportedTypeDecoderObjectLength ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outermostDecoder, "3", - &outerMapDecoder2 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_BOOL, outerMapDecoder2.type ); - - /* Make sure that the getBufferAddress() API does not give the buffer address of the value data - * in the entry keyed by "3".*/ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, - _pCborDecoder->getBufferAddress( &outerMapDecoder2, - &pDecoderObjectStartAddr ) ); - - /* Verify that the getSizeOfEncodedData() does not support calculation of non-container type data.*/ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, - _pCborDecoder->getSizeOfEncodedData( - &outerMapDecoder2, &unsupportedTypeDecoderObjectLength ) ); - - _pCborDecoder->destroy( &outerMapDecoder1 ); - _pCborDecoder->destroy( &outerMapDecoder2 ); - _pCborDecoder->destroy( &outermostDecoder ); -} - -TEST( Serializer_Decoder_Unit_CBOR, TestDecoderIteratorWithNestedMap ) -{ - IotSerializerDecoderObject_t outerDecoder1 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderIterator_t outerIter = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - const uint8_t * pDecoderObjectStartAddr = NULL; - IotSerializerDecoderObject_t iterToDecoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t iterToDecoderObject2 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t iterToDecoderObject3 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t outerDecoder2 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderObject_t nestedMapDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderIterator_t nestedMapIter = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &outerDecoder1, - _testEncodedNestedMap, - sizeof( _testEncodedNestedMap ) ) ); - - /* Obtain an iterator to the contents of the map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &outerDecoder1, - &outerIter ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, - &iterToDecoderObject ) ); - /* Validate that we can obtain the key data of the first entry in the outer map. */ - TEST_ASSERT_EQUAL_STRING_LEN( "1", iterToDecoderObject.u.value.u.string.pString, - iterToDecoderObject.u.value.u.string.length ); - - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, - &iterToDecoderObject ) ); - /* Validate that we can obtain the value data (i.e. nested map) of the first entry in the parent/outer map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->getBufferAddress( - &iterToDecoderObject, &pDecoderObjectStartAddr ) ); - TEST_ASSERT_EQUAL_PTR( &_testEncodedNestedMap[ 3 ], pDecoderObjectStartAddr ); - _pCborDecoder->destroy( &iterToDecoderObject ); - - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, - &iterToDecoderObject2 ) ); - /* Validate that we can obtain the key data of the second entry in the outer map. */ - TEST_ASSERT_EQUAL_STRING_LEN( "3", iterToDecoderObject2.u.value.u.string.pString, - iterToDecoderObject2.u.value.u.string.length ); - _pCborDecoder->destroy( &iterToDecoderObject2 ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, - &iterToDecoderObject2 ) ); - /* Validate that we can obtain the boolean value data of the second entry in the outer map. */ - TEST_ASSERT_EQUAL( false, iterToDecoderObject2.u.value.u.booleanValue ); - _pCborDecoder->destroy( &iterToDecoderObject2 ); - - /* Iterate to the end of the outer map container. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( outerIter, - &outerDecoder1 ) ); - _pCborDecoder->destroy( &outerDecoder1 ); - - - /* Test with iterating in the nested map in the entry keyed by "1" */ - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &outerDecoder2, - _testEncodedNestedMap, - sizeof( _testEncodedNestedMap ) ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outerDecoder2, "1", - &nestedMapDecoder ) ); - - /* Obtain an iterator to the contents of the nested map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &nestedMapDecoder, - &nestedMapIter ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( nestedMapIter, - &iterToDecoderObject3 ) ); - /* Validate that we can obtain the key data of the only entry in the nested map. */ - TEST_ASSERT_EQUAL_STRING_LEN( "A", iterToDecoderObject3.u.value.u.string.pString, - iterToDecoderObject3.u.value.u.string.length ); - - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( nestedMapIter ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( nestedMapIter, - &iterToDecoderObject3 ) ); - /* Validate that we can obtain the integer value of the only entry in the nested map. */ - TEST_ASSERT_EQUAL_INT( 10, iterToDecoderObject3.u.value.u.signedInt ); - - /* Iterate to the end of the nested map container. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( nestedMapIter ) ); - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( nestedMapIter, - &nestedMapDecoder ) ); - _pCborDecoder->destroy( &nestedMapDecoder ); - _pCborDecoder->destroy( &outerDecoder2 ); -} - -/* Verifies that a container decoder object remains valid for re-use after a complete round of iterating - * through its contents */ -TEST( Serializer_Decoder_Unit_CBOR, TestDecoderObjectReuseAfterIteration ) -{ - IotSerializerDecoderObject_t mapDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderIterator_t iterator1 = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - IotSerializerDecoderObject_t valueDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerDecoderIterator_t iterator2 = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &mapDecoder, - _testEncodedNestedMap, - sizeof( _testEncodedNestedMap ) ) ); - - /* Obtain an iterator to the contents of the map. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &mapDecoder, - &iterator1 ) ); - - - /* Undergo one round of iteration through the map */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); - - /* End the iteration by invalidating the iterator */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator1, &mapDecoder ) ); - - /* NOW check that the decoder object of the map is still valid! */ - /* Sanity check with "find()" function". */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &mapDecoder, "3", &valueDecoder ) ); - TEST_ASSERT_EQUAL( false, valueDecoder.u.value.u.booleanValue ); - - /* Sanity check with another round of iteration! */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &mapDecoder, - &iterator2 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); - - /* End the second round of iteration */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator2, &mapDecoder ) ); - - _pCborDecoder->destroy( &valueDecoder ); - _pCborDecoder->destroy( &mapDecoder ); -} diff --git a/ports/README.md b/ports/README.md deleted file mode 100644 index f085a374e1..0000000000 --- a/ports/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Platform ports - -This directory contains the desktop OS ports. Each port implements the functions of the platform layer interface for a specific desktop OS. See [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/index.html) for details on the platform layer interface. - -Its subdirectories are organized as follows: -- `common`
- Port files that are not specific to a single port. These headers are used across all of the desktop OS ports. - - `include`
- Port headers that are not specific to a single port, such as the atomic and network headers. - - `atomic`
- Implementations of atomic operations. - - `src`
- Port sources that are not specific to a single port, such as the network implementations. -- `template`
- Empty port sources containing stubbed-out functions. The files in this directory may be used as a starting point for a new port. -- `posix`
- Port sources and headers for a single implementation. The directory is named after the target OS. - -See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions on how to create a new port. diff --git a/ports/common/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h deleted file mode 100644 index 89fb55366a..0000000000 --- a/ports/common/include/atomic/iot_atomic_gcc.h +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_atomic_gcc.h - * @brief Atomic operations implemented on GCC extensions. - * - * Compatible with GCC and clang. - */ - -#ifndef IOT_ATOMIC_GCC_H_ -#define IOT_ATOMIC_GCC_H_ - -/* Standard includes. */ -#include -#include - -/** - * @brief GCC function attribute to always inline a function. - */ - -/* This header file is intended to be used with only the gcc compiler - * which will have the __attribute__ language extension available. */ -/* coverity[misra_banned_extension_origin] */ -#define FORCE_INLINE inline __attribute__( ( always_inline ) ) - -/*---------------- Swap and compare-and-swap ------------------*/ - -/** - * @brief Implementation of atomic compare-and-swap for gcc. - */ -static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestination, - uint32_t newValue, - uint32_t comparand ) -{ - uint32_t swapped = 0; - - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. - * This routine is built into gcc and defined to return a bool - * type. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_8_5_violation] */ - /* coverity[misra_c_2012_rule_10_4_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - if( __atomic_compare_exchange( pDestination, - &comparand, - &newValue, - false, - __ATOMIC_SEQ_CST, - __ATOMIC_SEQ_CST ) == ( ( bool ) ( true ) ) ) - { - swapped = 1; - } - - return swapped; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic pointer swap for gcc. - */ -static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, - void * pNewValue ) -{ - void * pOldValue = NULL; - - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_17_7_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); - - return pOldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic pointer compare-and-swap for gcc. - */ -static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pDestination, - void * pNewValue, - void * pComparand ) -{ - uint32_t swapped = 0; - - /* This routine is built into gcc and defined to return a bool - * type. */ - /* coverity[misra_c_2012_rule_10_4_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - /* coverity[other_declaration] */ - if( __atomic_compare_exchange( pDestination, - &pComparand, - &pNewValue, - false, - __ATOMIC_SEQ_CST, - __ATOMIC_SEQ_CST ) == ( ( bool ) ( true ) ) ) - { - swapped = 1; - } - - return swapped; -} - -/*----------------------- Arithmetic --------------------------*/ - -/** - * @brief Implementation of atomic addition for gcc. - */ -static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, - uint32_t addend ) -{ - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_8_5_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - return ( uint32_t ) ( __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic subtraction for gcc. - */ -static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, - uint32_t subtrahend ) -{ - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_8_5_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic increment for gcc. - */ -static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) -{ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - /* coverity[other_declaration] */ - return ( uint32_t ) ( __atomic_fetch_add( pAugend, 1U, __ATOMIC_SEQ_CST ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic decrement for gcc. - */ -static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend ) -{ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - /* coverity[other_declaration] */ - return ( uint32_t ) ( __atomic_fetch_sub( pMinuend, 1U, __ATOMIC_SEQ_CST ) ); -} - -/*--------------------- Bitwise logic -------------------------*/ - -/** - * @brief Implementation of atomic OR for gcc. - */ -static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, - uint32_t mask ) -{ - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - return ( uint32_t ) ( __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic XOR for gcc. - */ -static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, - uint32_t mask ) -{ - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - return ( uint32_t ) ( __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic AND for gcc. - */ -static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, - uint32_t mask ) -{ - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - return ( uint32_t ) ( __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Implementation of atomic NAND for gcc. - */ -static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, - uint32_t mask ) -{ - /* This header file is intended to be used with only the gcc compiler - * which requires an int parameter for this routine. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ - /* coverity[misra_c_2012_rule_17_3_violation] */ - /* coverity[caretline] */ - return ( uint32_t ) ( __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ) ); -} - -#endif /* IOT_ATOMIC_GCC_H_ */ diff --git a/ports/common/include/atomic/iot_atomic_generic.h b/ports/common/include/atomic/iot_atomic_generic.h deleted file mode 100644 index 45cad30ac0..0000000000 --- a/ports/common/include/atomic/iot_atomic_generic.h +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_atomic_generic.h - * @brief Generic implementation of atomic operations. - * - * This implementation is less efficient than the specific atomic implementations, - * but should work on all platforms. - */ - -#ifndef IOT_ATOMIC_GENERIC_H_ -#define IOT_ATOMIC_GENERIC_H_ - -/* Standard includes. */ -#include - -/* Atomic include. */ -#include "iot_atomic.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Ensure that this header is only included when generic atomics are enabled. */ -#if IOT_ATOMIC_GENERIC != 1 - #error "Generic atomic implementation is not enabled." -#endif - -/** - * @functionspage{platform_atomic,platform atomics component,Atomics} - * - @functionname{platform_atomic_function_compareandswap_u32} - * - @functionname{platform_atomic_function_swap_pointer} - * - @functionname{platform_atomic_function_compareandswap_pointer} - * - @functionname{platform_atomic_function_add_u32} - * - @functionname{platform_atomic_function_subtract_u32} - * - @functionname{platform_atomic_function_increment_u32} - * - @functionname{platform_atomic_function_decrement_u32} - * - @functionname{platform_atomic_function_or_u32} - * - @functionname{platform_atomic_function_xor_u32} - * - @functionname{platform_atomic_function_and_u32} - * - @functionname{platform_atomic_function_nand_u32} - */ - -/** - * @functionpage{Atomic_CompareAndSwap_u32,platform_atomic,compareandswap_u32} - * @functionpage{Atomic_Swap_Pointer,platform_atomic,swap_pointer} - * @functionpage{Atomic_CompareAndSwap_Pointer,platform_atomic,compareandswap_pointer} - * @functionpage{Atomic_Add_u32,platform_atomic,add_u32} - * @functionpage{Atomic_Subtract_u32,platform_atomic,subtract_u32} - * @functionpage{Atomic_Increment_u32,platform_atomic,increment_u32} - * @functionpage{Atomic_Decrement_u32,platform_atomic,decrement_u32} - * @functionpage{Atomic_OR_u32,platform_atomic,or_u32} - * @functionpage{Atomic_XOR_u32,platform_atomic,xor_u32} - * @functionpage{Atomic_AND_u32,platform_atomic,and_u32} - * @functionpage{Atomic_NAND_u32,platform_atomic,nand_u32} - */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - */ -extern void Iot_EnterCritical( void ); -extern void Iot_ExitCritical( void ); -/** @endcond */ - -/*---------------- Swap and compare-and-swap ------------------*/ - -/** - * @brief Performs an atomic compare-and-swap operation on the given values. - * - * @param[in,out] pDestination Pointer to memory location from where value is to - * be loaded and checked. - * @param[in] newValue This value will be written to memory if the comparand matches - * the value at `pDestination`. - * @param[in] comparand This value is compared to the value at `pDestination`. - * - * @return `1` if the `newValue` was written to `pDestination`; `0` otherwise. - */ -/* @[declare_platform_atomic_compareandswap_u32] */ -static inline uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestination, - uint32_t newValue, - uint32_t comparand ) -/* @[declare_platform_atomic_compareandswap_u32] */ -{ - uint32_t swapped = 0; - - Iot_EnterCritical(); - - if( *pDestination == comparand ) - { - *pDestination = newValue; - swapped = 1; - } - - Iot_ExitCritical(); - - return swapped; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Atomically writes a pointer value to memory. - * - * @param[in,out] pDestination Where `pNewValue` will be written. - * @param[in] pNewValue The value to write to `pDestination`. - * - * @return The initial value at `pDestination`. - */ -/* @[declare_platform_atomic_swap_pointer] */ -static inline void * Atomic_Swap_Pointer( void * volatile * pDestination, - void * pNewValue ) -/* @[declare_platform_atomic_swap_pointer] */ -{ - void * pOldValue = NULL; - - Iot_EnterCritical(); - pOldValue = *pDestination; - *pDestination = pNewValue; - Iot_ExitCritical(); - - return pOldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Performs an atomic compare-and-swap operation on the given pointers. - * - * @param[in,out] pDestination Pointer to the memory location to - * be loaded and checked. - * @param[in] pNewValue This value will be written to memory if the comparand matches - * the value at `pDestination`. - * @param[in] pComparand This value is compared to the value at `pDestination`. - * - * @return `1` if the `newValue` was written to `pDestination`; `0` otherwise. - */ -/* @[declare_platform_atomic_compareandswap_pointer] */ -static inline uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pDestination, - void * pNewValue, - void * pComparand ) -/* @[declare_platform_atomic_compareandswap_pointer] */ -{ - uint32_t swapped = 0; - - Iot_EnterCritical(); - - if( *pDestination == pComparand ) - { - *pDestination = pNewValue; - swapped = 1; - } - - Iot_ExitCritical(); - - return swapped; -} - -/*----------------------- Arithmetic --------------------------*/ - -/** - * @brief Performs an atomic addition of the given values. - * - * @param[in,out] pAugend Pointer to the augend and where the sum is stored. - * @param[in] addend Value to add to the augend. - * - * @return The initial value at `pAugend`. - */ -/* @[declare_platform_atomic_add_u32] */ -static inline uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, - uint32_t addend ) -/* @[declare_platform_atomic_add_u32] */ -{ - uint32_t oldValue = 0; - - Iot_EnterCritical(); - oldValue = *pAugend; - *pAugend = oldValue + addend; - Iot_ExitCritical(); - - return oldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Performs an atomic subtraction of the given values. - * - * @param[in,out] pMinuend Pointer to the minuend and where the difference is stored. - * @param[in] subtrahend Value to subtract from the minuend. - * - * @return The initial value at `pMinuend`. - */ -/* @[declare_platform_atomic_subtract_u32] */ -static inline uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, - uint32_t subtrahend ) -/* @[declare_platform_atomic_subtract_u32] */ -{ - uint32_t oldValue = 0; - - Iot_EnterCritical(); - oldValue = *pMinuend; - *pMinuend = oldValue - subtrahend; - Iot_ExitCritical(); - - return oldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Atomically adds 1 to the given value. - * - * @param[in,out] pAugend Pointer to the augend and where the sum is stored. - * - * @return The initial value at `pAugend`. - */ -/* @[declare_platform_atomic_increment_u32] */ -static inline uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) -/* @[declare_platform_atomic_increment_u32] */ -{ - return Atomic_Add_u32( pAugend, 1 ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Atomically subtracts 1 from the given value. - * - * @param[in,out] pMinuend Pointer to the minuend and where the difference is stored. - * - * @return The initial value at `pMinuend`. - */ -/* @[declare_platform_atomic_decrement_u32] */ -static inline uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend ) -/* @[declare_platform_atomic_decrement_u32] */ -{ - return Atomic_Subtract_u32( pMinuend, 1 ); -} - -/*--------------------- Bitwise logic -------------------------*/ - -/** - * @brief Performs an atomic bitwise OR of the given values. - * - * @param[in,out] pOperand Pointer to operand and where the result is stored. - * @param[in] mask Mask to OR with the operand. - * - * @return The initial value at `pOperand`. - */ -/* @[declare_platform_atomic_or_u32] */ -static inline uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, - uint32_t mask ) -/* @[declare_platform_atomic_or_u32] */ -{ - uint32_t oldValue = 0; - - Iot_EnterCritical(); - oldValue = *pOperand; - *pOperand = ( oldValue | mask ); - Iot_ExitCritical(); - - return oldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Performs an atomic bitwise XOR of the given values. - * - * @param[in,out] pOperand Pointer to operand and where the result is stored. - * @param[in] mask Mask to XOR with the operand. - * - * @return The initial value at `pOperand`. - */ -/* @[declare_platform_atomic_xor_u32] */ -static inline uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, - uint32_t mask ) -/* @[declare_platform_atomic_xor_u32] */ -{ - uint32_t oldValue = 0; - - Iot_EnterCritical(); - oldValue = *pOperand; - *pOperand = ( oldValue ^ mask ); - Iot_ExitCritical(); - - return oldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Performs an atomic bitwise AND of the given values. - * - * @param[in,out] pOperand Pointer to operand and where the result is stored. - * @param[in] mask Mask to AND with the operand. - * - * @return The initial value at `pOperand`. - */ -/* @[declare_platform_atomic_and_u32] */ -static inline uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, - uint32_t mask ) -/* @[declare_platform_atomic_and_u32] */ -{ - uint32_t oldValue = 0; - - Iot_EnterCritical(); - oldValue = *pOperand; - *pOperand = ( oldValue & mask ); - Iot_ExitCritical(); - - return oldValue; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Performs an atomic bitwise NAND of the given values. - * - * @param[in,out] pOperand Pointer to operand and where the result is stored. - * @param[in] mask Mask to NAND with the operand. - * - * @return The initial value at `pOperand`. - */ -/* @[declare_platform_atomic_nand_u32] */ -static inline uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, - uint32_t mask ) -/* @[declare_platform_atomic_nand_u32] */ -{ - uint32_t oldValue = 0; - - Iot_EnterCritical(); - oldValue = *pOperand; - *pOperand = ~( oldValue & mask ); - Iot_ExitCritical(); - - return oldValue; -} - -#endif /* ifndef IOT_ATOMIC_GENERIC_H_ */ diff --git a/ports/common/include/iot_atomic.h b/ports/common/include/iot_atomic.h deleted file mode 100644 index 286f4ad436..0000000000 --- a/ports/common/include/iot_atomic.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_atomic.h - * @brief Chooses the appropriate atomic operations header. - * - * This file first checks if an atomic port is provided. - * - * Otherwise, this file checks the compiler and chooses an appropriate atomic - * header depending on the compiler. - * - * If no supported compilers are available, then a "generic" atomic implementation - * is used. The generic implementation is less efficient, but will work on all - * platforms. - */ - -#ifndef IOT_ATOMIC_H_ -#define IOT_ATOMIC_H_ - -#ifndef IOT_ATOMIC_USE_PORT - #define IOT_ATOMIC_USE_PORT ( 0 ) -#endif - -/* Use an atomic port if provided. */ -#if IOT_ATOMIC_USE_PORT == 1 - #include "atomic/iot_atomic_port.h" -#elif defined( __GNUC__ ) - /* Both clang and gcc define __GNUC__, but only clang defines __clang__ */ - #ifdef __clang__ - /* clang versions 3.1.0 and greater have built-in atomic support. */ - #define CLANG_VERSION ( ( __clang_major__ * 100 ) + __clang_minor__ ) - #if CLANG_VERSION > 301 - /* clang is compatible with gcc atomic extensions. */ - #include "atomic/iot_atomic_gcc.h" - #else - #define IOT_ATOMIC_GENERIC 1 - #endif - #else - /* GCC versions 4.7.0 and greater have built-in atomic support. */ - #define GCC_VERSION ( ( __GNUC__ * 100 ) + __GNUC_MINOR__ ) - #if GCC_VERSION >= 407 - #include "atomic/iot_atomic_gcc.h" - #else - #define IOT_ATOMIC_GENERIC 1 - #endif - #endif /* ifdef __clang__ */ -#else /* if IOT_ATOMIC_USE_PORT == 1 */ - #define IOT_ATOMIC_GENERIC 1 -#endif /* if IOT_ATOMIC_USE_PORT == 1 */ - -#ifndef IOT_ATOMIC_GENERIC - #define IOT_ATOMIC_GENERIC ( 0 ) -#endif - -/* Include the generic atomic header if no supported compiler was found. */ -#if ( IOT_ATOMIC_GENERIC == 1 ) - #include "atomic/iot_atomic_generic.h" -#endif - -#endif /* ifndef IOT_ATOMIC_H_ */ diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h deleted file mode 100644 index 18ca5ebca2..0000000000 --- a/ports/common/include/iot_network_mbedtls.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network_mbedtls.h - * @brief Declares the network stack functions specified in iot_network.h for - * mbed TLS. - */ - -#ifndef IOT_NETWORK_MBEDTLS_H_ -#define IOT_NETWORK_MBEDTLS_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Platform types include. */ -#include "types/iot_platform_types.h" - -/* Platform network include. */ -#include "platform/iot_network.h" - -/** - * @brief Provides a default value for an #IotNetworkServerInfo. - * - * All instances of #IotNetworkServerInfo should be initialized with - * this constant when using this mbed TLS network stack. - * - * @warning Failing to initialize an #IotNetworkServerInfo may result in - * a crash! - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER { 0 } - -/** - * @brief Initialize an #IotNetworkCredentials for AWS IoT for using TLS mutual - * authentication with certificates, TCP port 443, and mbedTLS. - * - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER \ - { \ - .pAlpnProtos = "x-amzn-mqtt-ca" \ - } - -/** - * @brief This is the ALPN (Application-Layer Protocol Negotiation) string - * required by AWS IoT for password-based authentication to the MQTT broker, - * TCP port 443, and mbedTLS. - */ -#define AWS_IOT_PASSWORD_ALPN_FOR_MBEDTLS "mqtt" - -/** - * @brief Generic initializer for an #IotNetworkCredentials when using this - * mbed TLS network stack. - * - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER { 0 } - -/** - * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions - * declared in this file. - */ -#define IOT_NETWORK_INTERFACE_MBEDTLS ( IotNetworkMbedtls_GetInterface() ) - -/** - * @brief Retrieve the network interface using the functions in this file. - */ -const IotNetworkInterface_t * IotNetworkMbedtls_GetInterface( void ); - -/** - * @brief One-time initialization function for this network stack. - * - * This function performs internal setup of this network stack. It must be - * called once (and only once) before calling any other function in this network - * stack. Calling this function more than once without first calling - * #IotNetworkMbedtls_Cleanup may result in a crash. - * - * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_FAILURE. - * - * @warning No thread-safety guarantees are provided for this function. - */ -IotNetworkError_t IotNetworkMbedtls_Init( void ); - -/** - * @brief One-time deinitialization function for this network stack. - * - * This function frees resources taken in #IotNetworkMbedtls_Init. It should be - * called after destroying all network connections to clean up this network - * stack. After this function returns, #IotNetworkMbedtls_Init must be called - * again before calling any other function in this network stack. - * - * @warning No thread-safety guarantees are provided for this function. Do not - * call this function if any network connections exist! - */ -void IotNetworkMbedtls_Cleanup( void ); - -/** - * @brief An implementation of #IotNetworkInterface_t::create for mbed TLS. - */ -IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ); - -/** - * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for - * mbed TLS. - */ -IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ); - -/** - * @brief An implementation of #IotNetworkInterface_t::setCloseCallback for - * mbed TLS. - */ -IotNetworkError_t IotNetworkMbedtls_SetCloseCallback( IotNetworkConnection_t pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ); - -/** - * @brief An implementation of #IotNetworkInterface_t::send for mbed TLS. - */ -size_t IotNetworkMbedtls_Send( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ); - -/** - * @brief An implementation of #IotNetworkInterface_t::receive for mbed TLS. - */ -size_t IotNetworkMbedtls_Receive( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ); - -/** - * @brief An implementation of #IotNetworkInterface_t::close for mbed TLS. - */ -IotNetworkError_t IotNetworkMbedtls_Close( IotNetworkConnection_t pConnection ); - -/** - * @brief An implementation of #IotNetworkInterface_t::destroy for mbed TLS. - */ -IotNetworkError_t IotNetworkMbedtls_Destroy( IotNetworkConnection_t pConnection ); - -/** - * @brief Used by metrics to retrieve the socket (file descriptor) associated with - * a connection. - */ -int IotNetworkMbedtls_GetSocket( IotNetworkConnection_t pConnection ); - -#endif /* ifndef IOT_NETWORK_MBEDTLS_H_ */ diff --git a/ports/common/include/iot_network_metrics.h b/ports/common/include/iot_network_metrics.h deleted file mode 100644 index 4acb9eb73c..0000000000 --- a/ports/common/include/iot_network_metrics.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network_metrics.h - * @brief Declares the network stack functions with Device Defender metrics. - * - * This file does not provide a different networking implementation; it only wraps - * existing network implementations with the necessary metrics functions. - */ - -#ifndef IOT_NETWORK_METRICS_H_ -#define IOT_NETWORK_METRICS_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Platform metrics header. */ -#include "platform/iot_metrics.h" - -/* Platform network include. */ -#include "platform/iot_network.h" - -/** - * @brief Provides a pointer to an #IotNetworkInterface_t that uses that provides - * metrics. - */ -#define IOT_NETWORK_INTERFACE_METRICS ( IotNetworkMetrics_GetInterface() ) - -/** - * @brief Retrieve the network interface with metrics. - */ -const IotNetworkInterface_t * IotNetworkMetrics_GetInterface( void ); - -#endif /* ifndef IOT_NETWORK_METRICS_H_ */ diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h deleted file mode 100644 index 648bf5b41f..0000000000 --- a/ports/common/include/iot_network_openssl.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network_openssl.h - * @brief Declares the network stack functions specified in iot_network.h for - * systems with OpenSSL. - */ - -#ifndef IOT_NETWORK_OPENSSL_H_ -#define IOT_NETWORK_OPENSSL_H_ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard bool include. */ -#include - -/* Platform types include. */ -#include "types/iot_platform_types.h" - -/* Platform network include. */ -#include "platform/iot_network.h" - -/** - * @brief Provides a default value for an #IotNetworkServerInfo. - * - * All instances of #IotNetworkServerInfo should be initialized with - * this constant when using this OpenSSL network stack. - * - * @warning Failing to initialize an #IotNetworkServerInfo may result in - * a crash! - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER { 0 } - -/** - * @brief Initialize an #IotNetworkCredentials for AWS IoT for using TLS mutual - * authentication with certificates, TCP port 443, and OpenSSL. - * - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER \ - { \ - .pAlpnProtos = "\x0ex-amzn-mqtt-ca" \ - } - -/** - * @brief This is the ALPN (Application-Layer Protocol Negotiation) string - * required by AWS IoT for password-based authentication to the MQTT broker, - * TCP port 443, and OpenSSL. - */ -#define AWS_IOT_PASSWORD_ALPN_FOR_OPENSSL "\x04mqtt" - -/** - * @brief Generic initializer for an #IotNetworkCredentials when using this - * OpenSSL network stack. - * - * @note This initializer may change at any time in future versions, but its - * name will remain the same. - */ -#define IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER { 0 } - -/** - * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions - * declared in this file. - */ -#define IOT_NETWORK_INTERFACE_OPENSSL ( IotNetworkOpenssl_GetInterface() ) - -/** - * @brief Retrieve the network interface using the functions in this file. - */ -const IotNetworkInterface_t * IotNetworkOpenssl_GetInterface( void ); - -/** - * @brief One-time initialization function for this network stack. - * - * This function performs internal setup of this network stack. It must be - * called once (and only once) before calling any other function in this network - * stack. Calling this function more than once without first calling - * #IotNetworkOpenssl_Cleanup may result in a crash. - * - * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_FAILURE. - * - * @warning No thread-safety guarantees are provided for this function. - */ -IotNetworkError_t IotNetworkOpenssl_Init( void ); - -/** - * @brief One-time deinitialization function for this network stack. - * - * This function frees resources taken in #IotNetworkOpenssl_Init. It should be - * called after destroying all network connections to clean up this network - * stack. After this function returns, #IotNetworkOpenssl_Init must be called - * again before calling any other function in this network stack. - * - * @warning No thread-safety guarantees are provided for this function. Do not - * call this function if any network connections exist! - */ -void IotNetworkOpenssl_Cleanup( void ); - -/** - * @brief An implementation of #IotNetworkInterface_t::create for systems - * with OpenSSL. - */ -IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ); - -/** - * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for - * systems with OpenSSL. - */ -IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ); - -/** - * @brief An implementation of #IotNetworkInterface_t::setCloseCallback for - * systems with OpenSSL. - */ -IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( IotNetworkConnection_t pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ); - -/** - * @brief An implementation of #IotNetworkInterface_t::send for systems - * with OpenSSL. - */ -size_t IotNetworkOpenssl_Send( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ); - -/** - * @brief An implementation of #IotNetworkInterface_t::receive for systems - * with OpenSSL. - */ -size_t IotNetworkOpenssl_Receive( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ); - -/** - * @brief An implementation of #IotNetworkInterface_t::close for systems - * with OpenSSL. - */ -IotNetworkError_t IotNetworkOpenssl_Close( IotNetworkConnection_t pConnection ); - -/** - * @brief An implementation of #IotNetworkInterface_t::destroy for systems - * with OpenSSL. - */ -IotNetworkError_t IotNetworkOpenssl_Destroy( IotNetworkConnection_t pConnection ); - -/** - * @brief Used by metrics to retrieve the socket (file descriptor) associated with - * a connection. - */ -int IotNetworkOpenssl_GetSocket( IotNetworkConnection_t pConnection ); - -#endif /* ifndef IOT_NETWORK_OPENSSL_H_ */ diff --git a/ports/common/lexicon.txt b/ports/common/lexicon.txt deleted file mode 100644 index 1646aac9ac..0000000000 --- a/ports/common/lexicon.txt +++ /dev/null @@ -1,64 +0,0 @@ -alpn -authmode -bool -ca -callbackmutex -caretline -clientcert -closecallback -compareandswap -config -contextmutex -coverity -crt -destroynotification -dns -endcond -endif -gcc -gnuc -html -http -ifdef -ifndef -int -io -iot -iotmutex -iotnetworkclosecallback -iotnetworkreceivecallback -iotsemaphore -ip -malloc -mbed -mbedtls -mfln -mutex -nand -networkcontext -onlinepubs -opengroup -openssl -org -paddressinfo -pclosecontext -phostname -posix -preceivecontext -privatekey -psslcontext -pthread -receivecallback -receivethread -receivethreadcreated -recv -rng -rootca -sigpipe -sni -ssl -sslmutex -tcp -tls -uint -xor diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c deleted file mode 100644 index c11820e623..0000000000 --- a/ports/common/src/iot_network_mbedtls.c +++ /dev/null @@ -1,1232 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network_mbedtls.c - * @brief Implementation of the network interface functions in iot_network.h - * for mbed TLS. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* mbed TLS network include. */ -#include "iot_network_mbedtls.h" - -/* mbed TLS includes. */ -#include -#include -#include -#include -#include -#include -#include -#include - -/* Error handling include. */ -#include "iot_error.h" - -/* Platform clock include. */ -#include "platform/iot_clock.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Atomic include. */ -#include "iot_atomic.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_NETWORK - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "NET" ) -#include "iot_logging_setup.h" - -/* Logging macro for mbed TLS errors. */ -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - #define _logLibraryError( error, pMessage ) \ - { \ - char pErrorMessage[ 80 ] = { 0 }; \ - mbedtls_strerror( error, pErrorMessage, 80 ); \ - \ - IotLogError( "%s error: %s. ", \ - pMessage, \ - pErrorMessage ); \ - } - - #define _logConnectionError( error, pConnection, pMessage ) \ - { \ - char pErrorMessage[ 80 ] = { 0 }; \ - mbedtls_strerror( error, pErrorMessage, 80 ); \ - \ - IotLogError( "(Network connection %p) %s error: %s. ", \ - pConnection, \ - pMessage, \ - pErrorMessage ); \ - } -#else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - #define _logLibraryError( error, pMessage ) - #define _logConnectionError( error, pConnection, pMessage ) -#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - -/* - * Provide default values for undefined memory allocation functions. - */ -#ifndef IotNetwork_Malloc - #include - -/** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotNetwork_Malloc malloc -#endif -#ifndef IotNetwork_Free - #include - -/** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotNetwork_Free free -#endif - -/** - * @brief The timeout for the mbed TLS poll call in the receive thread. - * - * After the timeout expires, the receive thread will check will queries the - * connection flags to ensure that the connection is still open. Therefore, - * this flag represents the maximum time it takes for the receive thread to - * detect a closed connection. - * - * This timeout is also used to wait for all receive threads to exit during - * global cleanup. - * - * Since this value only affects the shutdown sequence, it generally does not - * need to be changed. - */ -#ifndef IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS - #define IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ( 1000UL ) -#endif - -/* Flags to track connection state. */ -#define FLAG_SECURED ( 0x00000001UL ) /**< @brief Secured connection. */ -#define FLAG_HAS_RECEIVE_CALLBACK ( 0x00000002UL ) /**< @brief Connection has receive callback. */ -#define FLAG_CONNECTION_DESTROYED ( 0x00000004UL ) /**< @brief Connection has been destroyed. */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Represents a network connection. - */ -typedef struct _networkConnection -{ - uint32_t flags; /**< @brief Connection state flags. */ - mbedtls_net_context networkContext; /**< @brief mbed TLS wrapper for system's sockets. */ - IotMutex_t contextMutex; /**< @brief Protects this network context from concurrent access. */ - - IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ - void * pReceiveContext; /**< @brief The context for the receive callback. */ - IotNetworkCloseCallback_t closeCallback; /**< @brief Network close callback, if any. */ - void * pCloseContext; /**< @brief The context for the close callback. */ - IotMutex_t callbackMutex; /**< @brief Synchronizes the receive callback with calls to destroy. */ - IotSemaphore_t destroyNotification; /**< @brief Notifies the receive callback that the connection was destroyed. */ - - /** - * @brief Secured connection context. Valid if #FLAG_SECURED is set. - */ - struct - { - /* ALPN protocols formatted for mbed TLS. The second element of this array - * is always NULL. */ - const char * pAlpnProtos[ 2 ]; - - mbedtls_ssl_config config; /**< @brief SSL connection configuration. */ - mbedtls_ssl_context context; /**< @brief SSL connection context. */ - - /** - * @brief Credentials for SSL connection. - */ - struct - { - mbedtls_x509_crt rootCa; /**< @brief Root CA certificate. */ - mbedtls_x509_crt clientCert; /**< @brief Client certificate. */ - mbedtls_pk_context privateKey; /**< @brief Client certificate private key. */ - } credentials; - } ssl; -} _networkConnection_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief mbed TLS entropy context for generation of random numbers. - */ -static mbedtls_entropy_context _entropyContext; - -/** - * @brief mbed TLS CTR DRBG context for generation of random numbers. - */ -static mbedtls_ctr_drbg_context _ctrDrbgContext; - -/** - * @brief Tracks the number of active receive threads. - */ -static uint32_t _receiveThreadCount = 0; - -/** - * @brief An #IotNetworkInterface_t that uses the functions in this file. - */ -static const IotNetworkInterface_t _networkMbedtls = -{ - .create = IotNetworkMbedtls_Create, - .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, - .send = IotNetworkMbedtls_Send, - .receive = IotNetworkMbedtls_Receive, - .close = IotNetworkMbedtls_Close, - .destroy = IotNetworkMbedtls_Destroy -}; - -/*-----------------------------------------------------------*/ - -/** - * @brief Initializes a new mutex. Used by mbed TLS to provide thread-safety. - * - * Sets the valid member of `mbedtls_threading_mutex_t`. - * - * @param[in] pMutex The mutex to initialize. - */ -static void _mbedtlsMutexInit( mbedtls_threading_mutex_t * pMutex ) -{ - pMutex->valid = IotMutex_Create( &( pMutex->mutex ), false ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Frees a mutex. Used by mbed TLS to provide thread-safety. - * - * @param[in] pMutex The mutex to destroy. - */ -static void _mbedtlsMutexFree( mbedtls_threading_mutex_t * pMutex ) -{ - if( pMutex->valid == true ) - { - IotMutex_Destroy( &( pMutex->mutex ) ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Locks a mutex. Used by mbed TLS to provide thread-safety. - * - * @param[in] pMutex The mutex to lock. - * - * @return `0` on success; one of `MBEDTLS_ERR_THREADING_BAD_INPUT_DATA` - * or `MBEDTLS_ERR_THREADING_MUTEX_ERROR` on error. - */ -static int _mbedtlsMutexLock( mbedtls_threading_mutex_t * pMutex ) -{ - int status = 0; - - if( pMutex->valid == false ) - { - status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; - } - else - { - IotMutex_Lock( &( pMutex->mutex ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Unlocks a mutex. Used by mbed TLS to provide thread-safety. - * - * @param[in] pMutex The mutex to unlock. - * - * @return `0` on success; one of `MBEDTLS_ERR_THREADING_BAD_INPUT_DATA` - * or `MBEDTLS_ERR_THREADING_MUTEX_ERROR` on error. - */ -static int _mbedtlsMutexUnlock( mbedtls_threading_mutex_t * pMutex ) -{ - int status = 0; - - if( pMutex->valid == false ) - { - status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; - } - else - { - IotMutex_Unlock( &( pMutex->mutex ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Initialize the mbed TLS structures in a network connection. - * - * @param[in] pConnection The network connection to initialize. - */ -static void _sslContextInit( _networkConnection_t * pConnection ) -{ - mbedtls_ssl_config_init( &( pConnection->ssl.config ) ); - mbedtls_x509_crt_init( &( pConnection->ssl.credentials.rootCa ) ); - mbedtls_x509_crt_init( &( pConnection->ssl.credentials.clientCert ) ); - mbedtls_pk_init( &( pConnection->ssl.credentials.privateKey ) ); - mbedtls_ssl_init( &( pConnection->ssl.context ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Free the mbed TLS structures in a network connection. - * - * @param[in] pConnection The network connection with the contexts to free. - */ -static void _sslContextFree( _networkConnection_t * pConnection ) -{ - mbedtls_ssl_free( &( pConnection->ssl.context ) ); - mbedtls_pk_free( &( pConnection->ssl.credentials.privateKey ) ); - mbedtls_x509_crt_free( &( pConnection->ssl.credentials.clientCert ) ); - mbedtls_x509_crt_free( &( pConnection->ssl.credentials.rootCa ) ); - mbedtls_ssl_config_free( &( pConnection->ssl.config ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Destroy a network connection. - * - * @param[in] pConnection The network connection to destroy. - */ -static void _destroyConnection( _networkConnection_t * pConnection ) -{ - /* Clean up the SSL context of secured connections. */ - if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) - { - _sslContextFree( pConnection ); - } - - /* Destroy synchronization objects. */ - IotMutex_Destroy( &( pConnection->contextMutex ) ); - IotMutex_Destroy( &( pConnection->callbackMutex ) ); - - if( ( pConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == FLAG_HAS_RECEIVE_CALLBACK ) - { - IotSemaphore_Destroy( &( pConnection->destroyNotification ) ); - } - - /* Free memory. */ - IotNetwork_Free( pConnection ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Network receive thread. - * - * This thread polls the network socket and reads data when data is available. - * It then invokes the receive callback, if any. - * - * @param[in] pArgument The connection associated with this receive thread. - */ -static void _receiveThread( void * pArgument ) -{ - int pollStatus = 0; - mbedtls_net_context context; - - /* Cast function parameter to correct type. */ - _networkConnection_t * pConnection = pArgument; - - /* Record the context to poll. */ - IotMutex_Lock( &( pConnection->contextMutex ) ); - context = pConnection->networkContext; - IotMutex_Unlock( &( pConnection->contextMutex ) ); - - /* Continuously poll the network connection for events. */ - while( true ) - { - pollStatus = mbedtls_net_poll( &context, - MBEDTLS_NET_POLL_READ, - IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); - - if( pollStatus < 0 ) - { - /* Error during poll. */ - _logConnectionError( pollStatus, pConnection, "Error polling network connection." ); - break; - } - else - { - /* Invoke receive callback if data is available. */ - if( pollStatus == MBEDTLS_NET_POLL_READ ) - { - IotMutex_Lock( &( pConnection->callbackMutex ) ); - - /* Only run the receive callback if the connection has not been - * destroyed. */ - if( ( pConnection->flags & FLAG_CONNECTION_DESTROYED ) == 0 ) - { - pConnection->receiveCallback( pConnection, - pConnection->pReceiveContext ); - } - - IotMutex_Unlock( &( pConnection->callbackMutex ) ); - } - } - } - - /** - * If a close callback has been defined, invoke it now; since we - * don't know what caused the close, use "unknown" as the reason. - */ - if( pConnection->closeCallback != NULL ) - { - pConnection->closeCallback( pConnection, - IOT_NETWORK_UNKNOWN_CLOSED, - pConnection->pCloseContext ); - } - - /* Wait for the call to network destroy, then destroy the connection. */ - IotSemaphore_Wait( &( pConnection->destroyNotification ) ); - _destroyConnection( pConnection ); - - IotLogDebug( "(Network connection %p) Receive thread terminating.", pConnection ); - - ( void ) Atomic_Decrement_u32( &_receiveThreadCount ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Reads credentials from the filesystem. - * - * Uses mbed TLS to import the root CA certificate, client certificate, and - * client certificate private key. - * @param[in] pConnection Network connection for the imported credentials. - * @param[in] pRootCaPath Path to the root CA certificate. - * @param[in] pClientCertPath Path to the client certificate. - * @param[in] pCertPrivateKeyPath Path to the client certificate private key. - * - * @return `true` if all credentials were successfully read; `false` otherwise. - */ -static bool _readCredentials( _networkConnection_t * pConnection, - const char * pRootCaPath, - const char * pClientCertPath, - const char * pCertPrivateKeyPath ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - int mbedtlsError = 0; - - if( ( pRootCaPath != NULL ) && - ( strlen( pRootCaPath ) != 0 ) ) - { - /* Read the root CA certificate. */ - mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.rootCa ), - pRootCaPath ); - - if( mbedtlsError < 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to read root CA certificate file." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else if( mbedtlsError > 0 ) - { - IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", - pRootCaPath, - mbedtlsError ); - } - else - { - /* Set the root certificate in the SSL configuration. */ - mbedtls_ssl_conf_ca_chain( &( pConnection->ssl.config ), - &( pConnection->ssl.credentials.rootCa ), - NULL ); - } - } - - if( ( pClientCertPath != NULL ) && - ( strlen( pClientCertPath ) != 0 ) && - ( pCertPrivateKeyPath != NULL ) && - ( strlen( pCertPrivateKeyPath ) != 0 ) ) - { - /* Read the client certificate. */ - mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.clientCert ), - pClientCertPath ); - - if( mbedtlsError < 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate file." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else if( mbedtlsError > 0 ) - { - IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", - pClientCertPath, - mbedtlsError ); - } - - /* Read the client certificate private key. */ - mbedtlsError = mbedtls_pk_parse_keyfile( &( pConnection->ssl.credentials.privateKey ), - pCertPrivateKeyPath, - NULL ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate private key file." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Set the client credential to the SSL context configuration. */ - mbedtlsError = mbedtls_ssl_conf_own_cert( &( pConnection->ssl.config ), - &( pConnection->ssl.credentials.clientCert ), - &( pConnection->ssl.credentials.privateKey ) ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to configure credentials." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Set up TLS on a TCP connection. - * - * @param[in] pConnection An established TCP connection. - * @param[in] pServerName Remote host name, used for server name indication. - * @param[in] pMbedtlsCredentials TLS setup parameters. - * - * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. - */ -static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, - const char * pServerName, - const IotNetworkCredentials_t pMbedtlsCredentials ) -{ - IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - int mbedtlsError = 0; - bool fragmentLengthValid = true; - unsigned char mflCode = 0; - uint32_t verifyResult = 0; - - /* Flags to track initialization. */ - bool sslContextInitialized = false; - - /* Initialize SSL configuration. */ - _sslContextInit( pConnection ); - sslContextInitialized = true; - - mbedtlsError = mbedtls_ssl_config_defaults( &( pConnection->ssl.config ), - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to set default SSL configuration." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Set SSL authmode and the RNG context. */ - mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); - mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); - - /* Setup TLS authentication. */ - if( _readCredentials( pConnection, - pMbedtlsCredentials->pRootCa, - pMbedtlsCredentials->pClientCert, - pMbedtlsCredentials->pPrivateKey ) == false ) - { - IotLogError( "(Network connection %p) Failed to read credentials.", - pConnection ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Set up ALPN if requested. */ - if( pMbedtlsCredentials->pAlpnProtos != NULL ) - { - /* mbed TLS expects a NULL-terminated array of protocols. These pointers - * must remain in-scope for the lifetime of the connection, so they are - * stored as part of the connection context. */ - pConnection->ssl.pAlpnProtos[ 0 ] = pMbedtlsCredentials->pAlpnProtos; - pConnection->ssl.pAlpnProtos[ 1 ] = NULL; - - mbedtlsError = mbedtls_ssl_conf_alpn_protocols( &( pConnection->ssl.config ), - pConnection->ssl.pAlpnProtos ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to set ALPN protocols." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - } - - /* Set TLS MFLN if requested. */ - if( pMbedtlsCredentials->maxFragmentLength > 0 ) - { - /* Check for a supported fragment length. mbed TLS only supports 4 values. */ - switch( pMbedtlsCredentials->maxFragmentLength ) - { - case 512UL: - mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_512; - break; - - case 1024UL: - mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_1024; - break; - - case 2048UL: - mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_2048; - break; - - case 4096UL: - mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_4096; - break; - - default: - IotLogWarn( "Ignoring unsupported max fragment length %lu. Supported " - "values are 512, 1024, 2048, or 4096.", - pMbedtlsCredentials->maxFragmentLength ); - fragmentLengthValid = false; - break; - } - - /* Set MFLN if a valid fragment length is given. */ - if( fragmentLengthValid == true ) - { - mbedtlsError = mbedtls_ssl_conf_max_frag_len( &( pConnection->ssl.config ), - mflCode ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to set TLS MFLN." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - } - } - - /* Initialize the mbed TLS secured connection context. */ - mbedtlsError = mbedtls_ssl_setup( &( pConnection->ssl.context ), - &( pConnection->ssl.config ) ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to set up mbed TLS SSL context." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Set the underlying IO for the TLS connection. */ - mbedtls_ssl_set_bio( &( pConnection->ssl.context ), - &( pConnection->networkContext ), - mbedtls_net_send, - NULL, - mbedtls_net_recv_timeout ); - - /* Enable SNI if requested. */ - if( pMbedtlsCredentials->disableSni == false ) - { - mbedtlsError = mbedtls_ssl_set_hostname( &( pConnection->ssl.context ), - pServerName ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to set server name." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - } - - /* Perform the TLS handshake. */ - do - { - mbedtlsError = mbedtls_ssl_handshake( &( pConnection->ssl.context ) ); - } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to perform SSL handshake." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Check result of certificate verification. */ - verifyResult = mbedtls_ssl_get_verify_result( &( pConnection->ssl.context ) ); - - if( verifyResult != 0 ) - { - IotLogError( "Failed to verify server certificate, result %lu.", - verifyResult ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Clean up on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status != IOT_NETWORK_SUCCESS ) - { - if( sslContextInitialized == true ) - { - _sslContextFree( pConnection ); - } - } - else - { - /* TLS setup succeeded; set the secured flag. */ - pConnection->flags |= FLAG_SECURED; - - IotLogInfo( "(Network connection %p) TLS handshake successful.", - pConnection ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -const IotNetworkInterface_t * IotNetworkMbedtls_GetInterface( void ) -{ - return &_networkMbedtls; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkMbedtls_Init( void ) -{ - IotNetworkError_t status = IOT_NETWORK_SUCCESS; - int mbedtlsError = 0; - - /* Clear the counter of receive threads. */ - _receiveThreadCount = 0; - - /* Set the mutex functions for mbed TLS thread safety. */ - mbedtls_threading_set_alt( _mbedtlsMutexInit, - _mbedtlsMutexFree, - _mbedtlsMutexLock, - _mbedtlsMutexUnlock ); - - /* Initialize contexts for random number generation. */ - mbedtls_entropy_init( &_entropyContext ); - mbedtls_ctr_drbg_init( &_ctrDrbgContext ); - - /* Seed the random number generator. */ - mbedtlsError = mbedtls_ctr_drbg_seed( &_ctrDrbgContext, - mbedtls_entropy_func, - &_entropyContext, - NULL, - 0 ); - - if( mbedtlsError != 0 ) - { - _logLibraryError( mbedtlsError, "Failed to seed PRNG in initialization." ); - status = IOT_NETWORK_FAILURE; - } - else - { - IotLogInfo( "Network library initialized." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotNetworkMbedtls_Cleanup( void ) -{ - /* Atomically read the receive thread count by adding 0 to it. Sleep and - * wait for all receive threads to exit. */ - while( Atomic_Add_u32( &_receiveThreadCount, 0 ) > 0 ) - { - IotClock_SleepMs( IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); - } - - /* Free the contexts for random number generation. */ - mbedtls_ctr_drbg_free( &_ctrDrbgContext ); - mbedtls_entropy_free( &_entropyContext ); - - /* Clear the mutex functions for mbed TLS thread safety. */ - mbedtls_threading_free_alt(); - - IotLogInfo( "Network library cleanup done." ); -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ) -{ - IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - int mbedtlsError = 0; - _networkConnection_t * pNewNetworkConnection = NULL; - char pServerPort[ 6 ] = { 0 }; - - /* Flags to track initialization. */ - bool networkContextInitialized = false, networkMutexCreated = false, callbackMutexCreated = false; - - /* Allocate memory for a new connection. */ - pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); - - if( pNewNetworkConnection == NULL ) - { - IotLogError( "Failed to allocate memory for new network connection." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_NO_MEMORY ); - } - - /* Clear connection data. */ - memset( pNewNetworkConnection, 0x00, sizeof( _networkConnection_t ) ); - - /* Initialize the network context mutex. */ - networkMutexCreated = IotMutex_Create( &( pNewNetworkConnection->contextMutex ), - false ); - - if( networkMutexCreated == false ) - { - IotLogError( "Failed to create mutex for network context." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Initialize the mutex for the receive callback. */ - callbackMutexCreated = IotMutex_Create( &( pNewNetworkConnection->callbackMutex ), - true ); - - if( callbackMutexCreated == false ) - { - IotLogError( "Failed to create mutex for receive callback." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Initialize mbed TLS network context. */ - mbedtls_net_init( &( pNewNetworkConnection->networkContext ) ); - networkContextInitialized = true; - - /* mbed TLS expects the port to be a decimal string. */ - mbedtlsError = snprintf( pServerPort, 6, "%hu", pServerInfo->port ); - - if( mbedtlsError < 0 ) - { - IotLogError( "Failed to convert port %hu to decimal string.", - pServerInfo->port ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Establish a TCP connection. */ - mbedtlsError = mbedtls_net_connect( &( pNewNetworkConnection->networkContext ), - pServerInfo->pHostName, - pServerPort, - MBEDTLS_NET_PROTO_TCP ); - - if( mbedtlsError != 0 ) - { - _logLibraryError( mbedtlsError, "Failed to establish connection." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Set the mbed TLS network context to blocking mode. */ - mbedtlsError = mbedtls_net_set_block( &( pNewNetworkConnection->networkContext ) ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pNewNetworkConnection, "Failed to set blocking mode." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Set up TLS if credentials are given. */ - if( pCredentialInfo != NULL ) - { - status = _tlsSetup( pNewNetworkConnection, - pServerInfo->pHostName, - pCredentialInfo ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Clean up on error. */ - if( status != IOT_NETWORK_SUCCESS ) - { - if( networkMutexCreated == true ) - { - IotMutex_Destroy( &( pNewNetworkConnection->contextMutex ) ); - } - - if( callbackMutexCreated == true ) - { - IotMutex_Destroy( &( pNewNetworkConnection->callbackMutex ) ); - } - - if( networkContextInitialized == true ) - { - mbedtls_net_free( &( pNewNetworkConnection->networkContext ) ); - } - - if( pNewNetworkConnection != NULL ) - { - IotNetwork_Free( pNewNetworkConnection ); - } - } - else - { - IotLogInfo( "(Network connection %p) New network connection established.", - pNewNetworkConnection ); - - /* Set the output parameter. */ - *pConnection = pNewNetworkConnection; - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ) -{ - IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - - /* Flags to track initialization. */ - bool notifyInitialized = false, countIncremented = false; - - /* Initialize the semaphore that notifies the receive thread of connection - * destruction. */ - notifyInitialized = IotSemaphore_Create( &( pConnection->destroyNotification ), - 0, - 1 ); - - if( notifyInitialized == false ) - { - IotLogError( "(Network connection %p) Failed to create semaphore for " - "receive thread.", pConnection ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - /* Set the callback (must be non-NULL) and parameter. */ - if( receiveCallback == NULL ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_BAD_PARAMETER ); - } - - pConnection->receiveCallback = receiveCallback; - pConnection->pReceiveContext = pContext; - - /* Set the receive callback flag and increment the count of receive threads. */ - pConnection->flags |= FLAG_HAS_RECEIVE_CALLBACK; - ( void ) Atomic_Increment_u32( &_receiveThreadCount ); - countIncremented = true; - - /* Create the thread to receive incoming data. */ - if( Iot_CreateDetachedThread( _receiveThread, - pConnection, - IOT_THREAD_DEFAULT_PRIORITY, - IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) - { - IotLogError( "(Network connection %p) Failed to create thread for receiving data.", - pConnection ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - /* Clean up on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status != IOT_NETWORK_SUCCESS ) - { - if( notifyInitialized == true ) - { - IotSemaphore_Destroy( &( pConnection->destroyNotification ) ); - } - - if( countIncremented == true ) - { - pConnection->flags &= ~FLAG_HAS_RECEIVE_CALLBACK; - ( void ) Atomic_Decrement_u32( &_receiveThreadCount ); - } - } - else - { - IotLogDebug( "(Network connection %p) Receive callback set.", - pConnection ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkMbedtls_SetCloseCallback( IotNetworkConnection_t pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ) -{ - IotNetworkError_t status = IOT_NETWORK_BAD_PARAMETER; - - if( closeCallback != NULL ) - { - /* Set the callback and parameter. */ - pConnection->closeCallback = closeCallback; - pConnection->pCloseContext = pContext; - - status = IOT_NETWORK_SUCCESS; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -size_t IotNetworkMbedtls_Send( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ) -{ - int mbedtlsError = 0; - size_t bytesSent = 0; - - IotLogDebug( "(Network connection %p) Sending %lu bytes.", - pConnection, - ( unsigned long ) messageLength ); - - IotMutex_Lock( &( pConnection->contextMutex ) ); - - /* Check that it's possible to send right now. */ - mbedtlsError = mbedtls_net_poll( &( pConnection->networkContext ), - MBEDTLS_NET_POLL_WRITE, - 0 ); - - if( mbedtlsError == MBEDTLS_NET_POLL_WRITE ) - { - /* Choose the send function based on state of the SSL context. */ - if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) - { - /* Secured send. */ - while( bytesSent < messageLength ) - { - mbedtlsError = mbedtls_ssl_write( &( pConnection->ssl.context ), - pMessage + bytesSent, - messageLength - bytesSent ); - - if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) ) - { - /* Call SSL write again with the same arguments. */ - continue; - } - else if( mbedtlsError < 0 ) - { - /* Error sending, exit. */ - break; - } - else - { - bytesSent += ( size_t ) mbedtlsError; - } - } - } - else - { - /* Unsecured send. */ - mbedtlsError = mbedtls_net_send( &( pConnection->networkContext ), - pMessage, - messageLength ); - - if( mbedtlsError > 0 ) - { - bytesSent = ( size_t ) mbedtlsError; - } - } - - /* Log errors. */ - if( mbedtlsError < 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to send." ); - bytesSent = 0; - } - } - else - { - _logConnectionError( mbedtlsError, pConnection, "Cannot send right now." ); - } - - IotMutex_Unlock( &( pConnection->contextMutex ) ); - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -size_t IotNetworkMbedtls_Receive( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - int mbedtlsError = 0; - size_t bytesReceived = 0; - - while( bytesReceived < bytesRequested ) - { - IotMutex_Lock( &( pConnection->contextMutex ) ); - - /* Choose the receive function based on state of the SSL context. */ - if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) - { - mbedtlsError = mbedtls_ssl_read( &( pConnection->ssl.context ), - pBuffer + bytesReceived, - bytesRequested - bytesReceived ); - } - else - { - mbedtlsError = mbedtls_net_recv_timeout( &( pConnection->networkContext ), - pBuffer + bytesReceived, - bytesRequested - bytesReceived, - IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); - } - - IotMutex_Unlock( &( pConnection->contextMutex ) ); - - if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_TIMEOUT ) ) - { - /* Call receive again with the same arguments. */ - continue; - } - else if( mbedtlsError < 0 ) - { - /* Error receiving, exit. */ - _logConnectionError( mbedtlsError, pConnection, "Failed to receive." ); - break; - } - else - { - bytesReceived += ( size_t ) mbedtlsError; - } - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkMbedtls_Close( IotNetworkConnection_t pConnection ) -{ - int mbedtlsError = 0; - - IotMutex_Lock( &( pConnection->contextMutex ) ); - - /* Notify the server that the SSL connection is being closed. */ - if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) - { - do - { - mbedtlsError = mbedtls_ssl_close_notify( &( pConnection->ssl.context ) ); - } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); - - if( mbedtlsError != 0 ) - { - _logConnectionError( mbedtlsError, pConnection, "Failed to notify peer of SSL connection close." ); - } - else - { - IotLogInfo( "(Network connection %p) TLS session terminated.", - pConnection ); - } - } - - /* Shutdown and close the network connection. */ - mbedtls_net_free( &( pConnection->networkContext ) ); - - IotMutex_Unlock( &( pConnection->contextMutex ) ); - - IotLogInfo( "(Network connection %p) Connection closed.", pConnection ); - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkMbedtls_Destroy( IotNetworkConnection_t pConnection ) -{ - /* Shutdown and close the network connection. */ - IotMutex_Lock( &( pConnection->callbackMutex ) ); - mbedtls_net_free( &( pConnection->networkContext ) ); - pConnection->flags |= FLAG_CONNECTION_DESTROYED; - IotMutex_Unlock( &( pConnection->callbackMutex ) ); - - /* Check if this connection has a receive callback. If it does not, it can - * be destroyed here. Otherwise, notify the receive callback that destroy - * has been called and rely on the receive callback to clean up. */ - if( ( pConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == 0 ) - { - _destroyConnection( pConnection ); - } - else - { - IotSemaphore_Post( &( pConnection->destroyNotification ) ); - } - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -int IotNetworkMbedtls_GetSocket( IotNetworkConnection_t pConnection ) -{ - return pConnection->networkContext.fd; -} - -/*-----------------------------------------------------------*/ diff --git a/ports/common/src/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c deleted file mode 100644 index 304ec8008b..0000000000 --- a/ports/common/src/iot_network_metrics.c +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network_metrics.c - * @brief Implementation of the functions in iot_metrics.h that wraps functions - * from the networking abstraction. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Metrics networking include. */ -#include "iot_network_metrics.h" - -/* System headers for retrieving socket info. */ -#if !defined( _WIN32 ) && !defined( _WIN64 ) - #include - #include - #include -#endif - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_NETWORK - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "NET" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions. - */ -#ifndef IotNetwork_Malloc - #include - -/** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotNetwork_Malloc malloc -#endif -#ifndef IotNetwork_Free - #include - -/** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotNetwork_Free free -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps the network connection creation function with metrics. - */ -static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ); - -/** - * @brief Wraps the network connection close function with metrics. - */ -static IotNetworkError_t _metricsNetworkClose( IotNetworkConnection_t pConnection ); - -/** - * @brief Used to match metrics connection records by network connection. - * - * @param[in] pConnectionLink Pointer to a metrics connection record's link element. - * @param[in] pContext The network connection to match. - * - * @return `true` if the given metrics connection record matches the given - * network connection; `false` otherwise. - */ -static bool _connectionMatch( const IotLink_t * pConnectionLink, - void * pContext ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Holds a list of active TCP connections. - */ -static IotListDouble_t _connectionList = IOT_LIST_DOUBLE_INITIALIZER; - -/** - * @brief Protects #_connectionList from concurrent access. - */ -static IotMutex_t _connectionListMutex; - -/*-----------------------------------------------------------*/ - -/* Choose the appropriate network abstraction implementation. */ -#if IOT_NETWORK_USE_OPENSSL == 1 - /* OpenSSL networking include. */ - #include "iot_network_openssl.h" - -/** - * @brief Pointer to the metrics-wrapped network creation function. - */ - static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t, - IotNetworkCredentials_t, - IotNetworkConnection_t * ) = IotNetworkOpenssl_Create; - -/** - * @brief Pointer to the metrics-wrapped network close function. - */ - static IotNetworkError_t ( * _networkClose )( IotNetworkConnection_t ) = IotNetworkOpenssl_Close; - -/** - * @brief Pointer to the function that retrieves the socket for a connection. - */ - static int ( * _getSocket )( IotNetworkConnection_t ) = IotNetworkOpenssl_GetSocket; - -/** - * @brief An #IotNetworkInterface_t that wraps network abstraction functions with - * metrics. - */ - static const IotNetworkInterface_t _networkMetrics = - { - .create = _metricsNetworkCreate, - .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, - .setCloseCallback = IotNetworkOpenssl_SetCloseCallback, - .send = IotNetworkOpenssl_Send, - .receive = IotNetworkOpenssl_Receive, - .close = _metricsNetworkClose, - .destroy = IotNetworkOpenssl_Destroy - }; -#else /* if IOT_NETWORK_USE_OPENSSL == 1 */ - /* mbed TLS networking include. */ - #include "iot_network_mbedtls.h" - -/** - * @brief Pointer to the metrics-wrapped network creation function. - */ - static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t, - IotNetworkCredentials_t, - IotNetworkConnection_t * ) = IotNetworkMbedtls_Create; - -/** - * @brief Pointer to the metrics-wrapped network close function. - */ - static IotNetworkError_t ( * _networkClose )( IotNetworkConnection_t ) = IotNetworkMbedtls_Close; - -/** - * @brief Pointer to the function that retrieves the socket for a connection. - */ - static int ( * _getSocket )( IotNetworkConnection_t ) = IotNetworkMbedtls_GetSocket; - -/** - * @brief An #IotNetworkInterface_t that wraps network abstraction functions with - * metrics. - */ - static const IotNetworkInterface_t _networkMetrics = - { - .create = _metricsNetworkCreate, - .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, - .setCloseCallback = IotNetworkMbedtls_SetCloseCallback, - .send = IotNetworkMbedtls_Send, - .receive = IotNetworkMbedtls_Receive, - .close = _metricsNetworkClose, - .destroy = IotNetworkMbedtls_Destroy - }; -#endif /* if IOT_NETWORK_USE_OPENSSL == 1 */ - -/*-----------------------------------------------------------*/ - -static bool _connectionMatch( const IotLink_t * pConnectionLink, - void * pContext ) -{ - /* Retrieve the pointer the the TCP connection record. The given link - * pointer is never NULL. */ - IotMetricsTcpConnection_t * pTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, - pConnectionLink, - link ); - - return( pTcpConnection->pNetworkContext == pContext ); -} - -/*-----------------------------------------------------------*/ - -static void _getServerInfo( int socket, - IotMetricsTcpConnection_t * pServerInfo ) -{ - int status = 0, portLength = 0; - struct sockaddr_storage server = { 0 }; - socklen_t length = sizeof( struct sockaddr_storage ); - const void * pServerAddress = NULL; - char * pAddressStart = NULL; - const char * pPortFormat = NULL; - uint16_t remotePort = 0; - size_t addressLength = 0; - - /* Get peer info. */ - status = getpeername( socket, - ( struct sockaddr * ) &server, - &length ); - - if( status == 0 ) - { - /* Calculate the pointer to the IP address and get the remote port based - * on protocol version. */ - if( server.ss_family == AF_INET ) - { - /* IPv4. */ - pServerAddress = &( ( ( struct sockaddr_in * ) &server )->sin_addr ); - remotePort = ntohs( ( ( struct sockaddr_in * ) &server )->sin_port ); - - /* Print the IPv4 address at the start of the address buffer. */ - pAddressStart = pServerInfo->pRemoteAddress; - addressLength = IOT_METRICS_IP_ADDRESS_LENGTH; - pPortFormat = ":%hu"; - } - else - { - /* IPv6. */ - pServerAddress = &( ( ( struct sockaddr_in6 * ) &server )->sin6_addr ); - remotePort = ntohs( ( ( struct sockaddr_in6 * ) &server )->sin6_port ); - - /* Enclose the IPv6 address with []. */ - pServerInfo->pRemoteAddress[ 0 ] = '['; - pAddressStart = pServerInfo->pRemoteAddress + 1; - addressLength = IOT_METRICS_IP_ADDRESS_LENGTH - 1; - pPortFormat = "]:%hu"; - } - - /* Convert IP address to text. */ - if( inet_ntop( server.ss_family, - pServerAddress, - pAddressStart, - addressLength ) != NULL ) - { - /* Add the port to the end of the address. */ - addressLength = strlen( pServerInfo->pRemoteAddress ); - - portLength = snprintf( &( pServerInfo->pRemoteAddress[ addressLength ] ), - 7, - pPortFormat, - remotePort ); - - if( portLength > 0 ) - { - pServerInfo->addressLength = addressLength + ( size_t ) portLength; - - IotLogInfo( "(Socket %d) Collecting network metrics for %s.", - socket, - pServerInfo->pRemoteAddress ); - } - else - { - IotLogError( "(Socket %d) Failed to add port to IP address buffer." ); - } - } - else - { - IotLogError( "(Socket %d) Failed to convert IP address to text format.", - socket ); - } - } - else - { - IotLogError( "(Socket %d) Failed to query peer name.", - socket ); - } -} - -/*-----------------------------------------------------------*/ - -static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ) -{ - int socket = 0; - IotMetricsTcpConnection_t * pTcpConnection = NULL; - IotNetworkError_t status = IOT_NETWORK_SUCCESS; - - /* Allocate memory for a new metrics connection. */ - pTcpConnection = IotNetwork_Malloc( sizeof( IotMetricsTcpConnection_t ) ); - - if( pTcpConnection != NULL ) - { - ( void ) memset( pTcpConnection, 0x00, sizeof( IotMetricsTcpConnection_t ) ); - - /* Call the wrapped network create function. */ - status = _networkCreate( pServerInfo, - pCredentialInfo, - pConnection ); - - /* Add the new metrics connection to the list of connections. */ - if( status == IOT_NETWORK_SUCCESS ) - { - pTcpConnection->pNetworkContext = *pConnection; - - /* Get the connection's address and port. */ - socket = _getSocket( pTcpConnection->pNetworkContext ); - _getServerInfo( socket, pTcpConnection ); - - /* Add the new connection to the list of connections. */ - IotMutex_Lock( &_connectionListMutex ); - IotListDouble_InsertHead( &_connectionList, &( pTcpConnection->link ) ); - IotMutex_Unlock( &_connectionListMutex ); - } - else - { - /* Free the new metrics connection on error. */ - IotNetwork_Free( pTcpConnection ); - } - } - else - { - status = IOT_NETWORK_NO_MEMORY; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static IotNetworkError_t _metricsNetworkClose( IotNetworkConnection_t pConnection ) -{ - IotLink_t * pConnectionLink = NULL; - IotMetricsTcpConnection_t * pTcpConnection = NULL; - - /* Call the wrapped network close function. */ - IotNetworkError_t status = _networkClose( pConnection ); - - /* Remove the connection from the list of active connections and free it. */ - if( status == IOT_NETWORK_SUCCESS ) - { - IotMutex_Lock( &_connectionListMutex ); - pConnectionLink = IotListDouble_RemoveFirstMatch( &_connectionList, - NULL, - _connectionMatch, - pConnection ); - IotMutex_Unlock( &_connectionListMutex ); - - if( pConnectionLink != NULL ) - { - pTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, - pConnectionLink, - link ); - - IotNetwork_Free( pTcpConnection ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -const IotNetworkInterface_t * IotNetworkMetrics_GetInterface( void ) -{ - return &_networkMetrics; -} - -/*-----------------------------------------------------------*/ - -bool IotMetrics_Init( void ) -{ - IotListDouble_Create( &_connectionList ); - - return IotMutex_Create( &_connectionListMutex, false ); -} - -/*-----------------------------------------------------------*/ - -void IotMetrics_Cleanup( void ) -{ - IotMutex_Destroy( &_connectionListMutex ); -} - -/*-----------------------------------------------------------*/ - -void IotMetrics_GetTcpConnections( void * pContext, - void ( * metricsCallback )( void *, const IotListDouble_t * ) ) -{ - /* Provide the connection list. Ensure that it is not modified elsewhere by - * locking the connection list mutex. */ - IotMutex_Lock( &_connectionListMutex ); - metricsCallback( pContext, &_connectionList ); - IotMutex_Unlock( &_connectionListMutex ); -} - -/*-----------------------------------------------------------*/ diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c deleted file mode 100644 index a57fa0b1a6..0000000000 --- a/ports/common/src/iot_network_openssl.c +++ /dev/null @@ -1,1084 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_network_openssl.c - * @brief Implementation of the network interface functions in iot_network.h - * for systems with OpenSSL. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* POSIX includes. */ -#include -#include -#include -#include -#include - -/* Sockets includes. */ -#include -#include -#include - -/* OpenSSL includes. */ -#include -#include -#include - -/* OpenSSL network include. */ -#include "iot_network_openssl.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_NETWORK - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "NET" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions. - */ -#ifndef IotNetwork_Malloc - #include - -/** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotNetwork_Malloc malloc -#endif -#ifndef IotNetwork_Free - #include - -/** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotNetwork_Free free -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Represents a network connection. - */ -typedef struct _networkConnection -{ - int socket; /**< @brief Socket associated with this connection. */ - SSL * pSslContext; /**< @brief SSL context for connection. */ - IotMutex_t sslMutex; /**< @brief Prevents concurrent use of the SSL context. */ - - bool receiveThreadCreated; /**< @brief Whether a receive thread exists for this connection. */ - pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ - - IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ - void * pReceiveContext; /**< @brief The context for the receive callback. */ - IotNetworkCloseCallback_t closeCallback; /**< @brief Network close callback, if any. */ - void * pCloseContext; /**< @brief The context for the close callback. */ -} _networkConnection_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief An #IotNetworkInterface_t that uses the functions in this file. - */ -static const IotNetworkInterface_t _networkOpenssl = -{ - .create = IotNetworkOpenssl_Create, - .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, - .setCloseCallback = IotNetworkOpenssl_SetCloseCallback, - .send = IotNetworkOpenssl_Send, - .receive = IotNetworkOpenssl_Receive, - .close = IotNetworkOpenssl_Close, - .destroy = IotNetworkOpenssl_Destroy -}; - -/*-----------------------------------------------------------*/ - -/** - * @brief Network receive thread. - * - * This thread polls the network socket and reads data when data is available. - * It then invokes the receive callback, if any. - * - * @param[in] pArgument The connection associated with this receive thread. - */ -static void * _networkReceiveThread( void * pArgument ) -{ - int pollStatus = 0; - struct pollfd fileDescriptor = - { - .events = POLLIN | POLLPRI, - .revents = 0 - }; - - /* Cast function parameter to correct type. */ - _networkConnection_t * pConnection = pArgument; - - /* Set the file descriptor for poll. */ - fileDescriptor.fd = pConnection->socket; - - /* Continuously poll the network socket for events. */ - while( true ) - { - pollStatus = poll( &fileDescriptor, 1, -1 ); - - if( pollStatus == -1 ) - { - IotLogError( "Failed to poll socket %d. errno=%d.", - pConnection->socket, - errno ); - break; - } - - /* If an error event is detected, terminate this thread. */ - if( ( fileDescriptor.revents & POLLERR ) || - ( fileDescriptor.revents & POLLHUP ) || - ( fileDescriptor.revents & POLLNVAL ) ) - { - IotLogError( "Detected error on socket %d, receive thread exiting.", - pConnection->socket ); - break; - } - - /* Invoke the callback function. */ - pConnection->receiveCallback( pConnection, - pConnection->pReceiveContext ); - } - - /** - * If a close callback has been defined, invoke it now; since we - * don't know what caused the close, use "unknown" as the reason. - */ - if( pConnection->closeCallback != NULL ) - { - pConnection->closeCallback( pConnection, - IOT_NETWORK_UNKNOWN_CLOSED, - pConnection->pCloseContext ); - } - - IotLogDebug( "Network receive thread for socket %d terminating.", - pConnection->socket ); - - return NULL; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Perform a DNS lookup of a host name and establish a TCP connection. - * - * @param[in] pServerInfo Server host name and port. - * - * @return A connected TCP socket number; `-1` if the DNS lookup failed. - */ -static int _dnsLookup( IotNetworkServerInfo_t pServerInfo ) -{ - IOT_FUNCTION_ENTRY( int, 0 ); - int tcpSocket = -1; - const uint16_t netPort = htons( pServerInfo->port ); - struct addrinfo * pListHead = NULL, * pAddressInfo = NULL; - struct sockaddr * pServer = NULL; - socklen_t serverLength = 0; - - /* Perform a DNS lookup of host name. */ - IotLogDebug( "Performing DNS lookup of %s", pServerInfo->pHostName ); - status = getaddrinfo( pServerInfo->pHostName, NULL, NULL, &pListHead ); - - if( status != 0 ) - { - IotLogError( "DNS lookup failed. %s.", gai_strerror( status ) ); - - IOT_SET_AND_GOTO_CLEANUP( -1 ); - } - - IotLogDebug( "Successfully received DNS records." ); - - /* Go through the list of DNS records until a successful connection is made. */ - for( pAddressInfo = pListHead; pAddressInfo != NULL; pAddressInfo = pAddressInfo->ai_next ) - { - /* Open a socket using the information in the DNS record. */ - IotLogDebug( "Creating socket for domain: %d, type: %d, protocol: %d.", - pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); - tcpSocket = socket( pAddressInfo->ai_family, pAddressInfo->ai_socktype, pAddressInfo->ai_protocol ); - - /* Check if the socket was successfully opened. */ - if( tcpSocket == -1 ) - { - IotLogDebug( "Could not open socket for record at %p. Trying next.", - pAddressInfo ); - continue; - } - - IotLogDebug( "Socket creation successful. Attempting connection." ); - - /* Set the port for the connection based on protocol. */ - pServer = pAddressInfo->ai_addr; - - if( pServer->sa_family == AF_INET ) - { - /* IPv4. */ - ( ( struct sockaddr_in * ) pServer )->sin_port = netPort; - serverLength = sizeof( struct sockaddr_in ); - } - else - { - /* IPv6. */ - ( ( struct sockaddr_in6 * ) pServer )->sin6_port = netPort; - serverLength = sizeof( struct sockaddr_in6 ); - } - - /* Attempt to connect. */ - if( connect( tcpSocket, - pServer, - serverLength ) == -1 ) - { - /* Connect failed; close socket and try next record. */ - if( close( tcpSocket ) != 0 ) - { - IotLogWarn( "Failed to close socket %d. errno=%d.", - tcpSocket, - errno ); - } - - IotLogDebug( "Socket connection failed. Trying next." ); - } - else - { - /* Connection successful; stop searching the list. */ - IotLogDebug( "Socket connection successful." ); - break; - } - } - - /* If pAddressInfo is NULL, then the entire list of records was parsed but none - * of them provided a successful connection. */ - if( pAddressInfo == NULL ) - { - IotLogError( "Failed to connect to all retrieved DNS records." ); - - IOT_SET_AND_GOTO_CLEANUP( -1 ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Free DNS records. */ - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - /* Return the socket descriptor on success. */ - if( status == 0 ) - { - status = tcpSocket; - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Reads credentials from the filesystem. - * - * Uses OpenSSL to import the root CA certificate, client certificate, and - * client certificate private key. - * @param[in] pSslContext Destination for the imported credentials. - * @param[in] pRootCaPath Path to the root CA certificate. - * @param[in] pClientCertPath Path to the client certificate. - * @param[in] pCertPrivateKeyPath Path to the client certificate private key. - * - * @return `true` if all credentials were successfully read; `false` otherwise. - */ -static bool _readCredentials( SSL_CTX * pSslContext, - const char * pRootCaPath, - const char * pClientCertPath, - const char * pCertPrivateKeyPath ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - X509 * pRootCa = NULL; - - /* OpenSSL does not provide a single function for reading and loading certificates - * from files into stores, so the file API must be called. Start with the - * root certificate. */ - if( ( pRootCaPath != NULL ) && - ( strlen( pRootCaPath ) != 0 ) ) - { - IotLogDebug( "Opening root certificate %s", pRootCaPath ); - FILE * pRootCaFile = fopen( pRootCaPath, "r" ); - - if( pRootCaFile == NULL ) - { - IotLogError( "Failed to open %s", pRootCaPath ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Read the root CA into an X509 object, then close its file handle. */ - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - - if( fclose( pRootCaFile ) != 0 ) - { - IotLogWarn( "Failed to close file %s", pRootCaPath ); - } - - if( pRootCa == NULL ) - { - IotLogError( "Failed to parse root CA." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Add the root CA to certificate store. */ - if( X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), - pRootCa ) != 1 ) - { - IotLogError( "Failed to add root CA to certificate store." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IotLogInfo( "Successfully imported root CA." ); - } - - if( ( pClientCertPath != NULL ) && - ( strlen( pClientCertPath ) != 0 ) && - ( pCertPrivateKeyPath != NULL ) && - ( strlen( pCertPrivateKeyPath ) != 0 ) ) - { - /* Import the client certificate. */ - if( SSL_CTX_use_certificate_chain_file( pSslContext, - pClientCertPath ) != 1 ) - { - IotLogError( "Failed to import client certificate at %s", - pClientCertPath ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IotLogInfo( "Successfully imported client certificate." ); - - /* Import the client certificate private key. */ - if( SSL_CTX_use_PrivateKey_file( pSslContext, - pCertPrivateKeyPath, - SSL_FILETYPE_PEM ) != 1 ) - { - IotLogError( "Failed to import client certificate private key at %s", - pCertPrivateKeyPath ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IotLogInfo( "Successfully imported client certificate private key." ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Free the root CA object. */ - if( pRootCa != NULL ) - { - X509_free( pRootCa ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Set up TLS on a TCP connection. - * - * @param[in] pConnection An established TCP connection. - * @param[in] pServerName Remote host name, used for server name indication. - * @param[in] pOpensslCredentials TLS setup parameters. - * - * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. - */ -static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, - const char * pServerName, - const IotNetworkCredentials_t pOpensslCredentials ) -{ - IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - SSL_CTX * pSslContext = NULL; - - /* Create a new SSL context. */ - #if OPENSSL_VERSION_NUMBER < 0x10100000L - pSslContext = SSL_CTX_new( TLSv1_2_client_method() ); - #else - pSslContext = SSL_CTX_new( TLS_client_method() ); - #endif - - if( pSslContext == NULL ) - { - IotLogError( "Failed to create new SSL context." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The - * mask returned by SSL_CTX_set_mode does not need to be checked. */ - IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); - ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); - - /* Setup authentication. */ - if( _readCredentials( pSslContext, - pOpensslCredentials->pRootCa, - pOpensslCredentials->pClientCert, - pOpensslCredentials->pPrivateKey ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - - /* Create a new SSL connection context */ - pConnection->pSslContext = SSL_new( pSslContext ); - - if( pConnection->pSslContext == NULL ) - { - IotLogError( "Failed to create new SSL connection context." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - /* Enable SSL peer verification. */ - IotLogDebug( "Setting SSL_VERIFY_PEER." ); - SSL_set_verify( pConnection->pSslContext, SSL_VERIFY_PEER, NULL ); - - /* Set the socket for the SSL connection. */ - if( SSL_set_fd( pConnection->pSslContext, - pConnection->socket ) != 1 ) - { - IotLogError( "Failed to set SSL socket %d.", pConnection->socket ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - /* Set up ALPN if requested. */ - if( pOpensslCredentials->pAlpnProtos != NULL ) - { - IotLogDebug( "Setting ALPN protos." ); - - if( ( SSL_set_alpn_protos( pConnection->pSslContext, - ( const unsigned char * ) pOpensslCredentials->pAlpnProtos, - ( unsigned int ) strlen( pOpensslCredentials->pAlpnProtos ) ) != 0 ) ) - { - IotLogError( "Failed to set ALPN protos." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - } - - /* Set TLS MFLN if requested. */ - if( pOpensslCredentials->maxFragmentLength > 0 ) - { - IotLogDebug( "Setting max send fragment length %lu.", - ( unsigned long ) pOpensslCredentials->maxFragmentLength ); - - /* Set the maximum send fragment length. */ - if( SSL_set_max_send_fragment( pConnection->pSslContext, - ( long ) pOpensslCredentials->maxFragmentLength ) != 1 ) - { - IotLogError( "Failed to set max send fragment length %lu.", - ( unsigned long ) pOpensslCredentials->maxFragmentLength ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - /* In supported versions of OpenSSL, change the size of the read buffer - * to match the maximum fragment length + some extra bytes for overhead. - * Note that OpenSSL ignores this setting if it's smaller than the default. - */ - #if OPENSSL_VERSION_NUMBER > 0x10100000L - SSL_set_default_read_buffer_len( pConnection->pSslContext, - pOpensslCredentials->maxFragmentLength + - SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); - #endif - } - - /* Enable SNI if requested. */ - if( pOpensslCredentials->disableSni == false ) - { - IotLogDebug( "Setting server name %s for SNI.", pServerName ); - - if( SSL_set_tlsext_host_name( pConnection->pSslContext, - pServerName ) != 1 ) - { - IotLogError( "Failed to set server name %s for SNI.", pServerName ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - } - - /* Perform the TLS handshake. */ - if( SSL_connect( pConnection->pSslContext ) != 1 ) - { - IotLogError( "TLS handshake failed." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - IotLogInfo( "TLS handshake succeeded." ); - - /* Verify the peer certificate. */ - if( SSL_get_verify_result( pConnection->pSslContext ) != X509_V_OK ) - { - IotLogError( "Peer certificate verification failed." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - IotLogInfo( "Peer certificate verified. TLS connection established." ); - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Free the SSL context. */ - if( pSslContext != NULL ) - { - SSL_CTX_free( pSslContext ); - } - - /* Clean up on error. */ - if( status != IOT_NETWORK_SUCCESS ) - { - if( pConnection->pSslContext != NULL ) - { - SSL_free( pConnection->pSslContext ); - pConnection->pSslContext = NULL; - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Close a TLS connection. - * - * @param[in] pConnection The TLS connection to close. - */ -static void _tlsClose( _networkConnection_t * pConnection ) -{ - /* Shut down the TLS connection. */ - IotLogInfo( "Shutting down TLS connection." ); - - /* Lock the SSL context mutex. */ - IotMutex_Lock( &( pConnection->sslMutex ) ); - - /* SSL shutdown should be called twice: once to send "close notify" and once - * more to receive the peer's "close notify". */ - if( SSL_shutdown( pConnection->pSslContext ) == 0 ) - { - IotLogDebug( "\"Close notify\" sent. Awaiting peer response." ); - - /* The previous call to SSL_shutdown marks the SSL connection as closed. - * SSL_shutdown must be called again to read the peer response. */ - if( SSL_shutdown( pConnection->pSslContext ) != 1 ) - { - IotLogWarn( "No response from peer." ); - } - else - { - IotLogDebug( "Peer response to \"close notify\" received." ); - } - } - - /* Unlock the SSL context mutex. */ - IotMutex_Unlock( &( pConnection->sslMutex ) ); - - IotLogInfo( "TLS connection closed." ); -} - -/*-----------------------------------------------------------*/ - -const IotNetworkInterface_t * IotNetworkOpenssl_GetInterface( void ) -{ - return &_networkOpenssl; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkOpenssl_Init( void ) -{ - IotNetworkError_t status = IOT_NETWORK_SUCCESS; - - /* Details on SIGPIPE handling. */ - struct sigaction sigpipeAction; - - ( void ) memset( &sigpipeAction, 0x00, sizeof( struct sigaction ) ); - - /* Ignore SIGPIPE, which may be raised if the peer closes the connection. */ - sigpipeAction.sa_handler = SIG_IGN; - - if( sigaction( SIGPIPE, &sigpipeAction, NULL ) != 0 ) - { - IotLogError( "Failed to set SIGPIPE handler. errno=%d.", errno ); - - status = IOT_NETWORK_FAILURE; - } - - if( status == IOT_NETWORK_SUCCESS ) - { - /* Per the OpenSSL docs, the return value of this function is meaningless. - * This function is also deprecated, but it's called to retain compatibility - * with v1.0.2. */ - ( void ) SSL_library_init(); - - IotLogInfo( "Network library initialized." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotNetworkOpenssl_Cleanup( void ) -{ - /* Call the necessary OpenSSL functions to prevent memory leaks. */ - #if OPENSSL_VERSION_NUMBER < 0x10100000L - ERR_remove_thread_state( NULL ); - #endif - CRYPTO_cleanup_all_ex_data(); - EVP_cleanup(); - SSL_COMP_free_compression_methods(); - - IotLogInfo( "Network library cleanup done." ); -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ) -{ - IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - int tcpSocket = -1; - bool sslMutexCreated = false; - _networkConnection_t * pNewNetworkConnection = NULL; - - /* Allocate memory for a new connection. */ - pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); - - if( pNewNetworkConnection == NULL ) - { - IotLogError( "Failed to allocate memory for new network connection." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_NO_MEMORY ); - } - - /* Clear connection data. */ - ( void ) memset( pNewNetworkConnection, 0x00, sizeof( _networkConnection_t ) ); - - /* Perform a DNS lookup of pHostName. This also establishes a TCP socket. */ - tcpSocket = _dnsLookup( pServerInfo ); - - if( tcpSocket == -1 ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } - else - { - IotLogInfo( "TCP connection successful." ); - } - - /* Set the socket in the network connection. */ - pNewNetworkConnection->socket = tcpSocket; - - /* Set up TLS if credentials are provided. */ - if( pCredentialInfo != NULL ) - { - IotLogInfo( "Setting up TLS." ); - - /* Create the mutex that protects the SSL context. */ - sslMutexCreated = IotMutex_Create( &( pNewNetworkConnection->sslMutex ), true ); - - if( sslMutexCreated == false ) - { - IotLogError( "Failed to create network send mutex." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); - } - - status = _tlsSetup( pNewNetworkConnection, - pServerInfo->pHostName, - pCredentialInfo ); - } - - /* Clean up on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status != IOT_NETWORK_SUCCESS ) - { - if( tcpSocket != -1 ) - { - ( void ) close( tcpSocket ); - } - - if( sslMutexCreated == true ) - { - IotMutex_Destroy( &( pNewNetworkConnection->sslMutex ) ); - } - - if( pNewNetworkConnection != NULL ) - { - IotNetwork_Free( pNewNetworkConnection ); - } - } - else - { - /* Set the output parameter. */ - *pConnection = pNewNetworkConnection; - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ) -{ - IotNetworkError_t status = IOT_NETWORK_BAD_PARAMETER; - int posixError = 0; - - /* The receive callback must be non-NULL) */ - if( receiveCallback != NULL ) - { - /* Set the callback and parameter. */ - pConnection->receiveCallback = receiveCallback; - pConnection->pReceiveContext = pContext; - - posixError = pthread_create( &pConnection->receiveThread, - NULL, - _networkReceiveThread, - pConnection ); - - if( posixError != 0 ) - { - IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pConnection->socket, - posixError ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - else - { - pConnection->receiveThreadCreated = true; - status = IOT_NETWORK_SUCCESS; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( IotNetworkConnection_t pConnection, - IotNetworkCloseCallback_t closeCallback, - void * pContext ) -{ - IotNetworkError_t status = IOT_NETWORK_BAD_PARAMETER; - - if( closeCallback != NULL ) - { - /* Set the callback and parameter. */ - pConnection->closeCallback = closeCallback; - pConnection->pCloseContext = pContext; - - status = IOT_NETWORK_SUCCESS; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -size_t IotNetworkOpenssl_Send( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ) -{ - int bytesSent = 0; - struct pollfd fileDescriptor = - { - .events = POLLOUT, - .revents = 0 - }; - - IotLogDebug( "Sending %lu bytes over socket %d.", - ( unsigned long ) messageLength, - pConnection->socket ); - - /* Set the file descriptor for poll. */ - fileDescriptor.fd = pConnection->socket; - - /* The SSL mutex must be locked to protect an SSL context from concurrent - * access. */ - if( pConnection->pSslContext != NULL ) - { - IotMutex_Lock( &( pConnection->sslMutex ) ); - } - - /* Check that it's possible to send data right now. */ - if( poll( &fileDescriptor, 1, 0 ) == 1 ) - { - /* Use OpenSSL's SSL_write() function or the POSIX send() function based on - * whether the SSL connection context is set up. */ - if( pConnection->pSslContext != NULL ) - { - bytesSent = SSL_write( pConnection->pSslContext, - pMessage, - ( int ) messageLength ); - } - else - { - bytesSent = ( int ) send( pConnection->socket, - pMessage, - messageLength, - 0 ); - } - } - else - { - IotLogError( "Not possible to send on socket %d.", pConnection->socket ); - } - - /* Unlock the SSL context mutex. */ - if( pConnection->pSslContext != NULL ) - { - IotMutex_Unlock( &( pConnection->sslMutex ) ); - } - - /* Log the number of bytes sent. */ - if( bytesSent <= 0 ) - { - IotLogError( "Send failure." ); - - bytesSent = 0; - } - else if( ( size_t ) bytesSent != messageLength ) - { - IotLogWarn( "Failed to send %lu bytes, %d bytes sent instead.", - ( unsigned long ) messageLength, - bytesSent ); - } - else - { - IotLogDebug( "Successfully sent %lu bytes.", - ( unsigned long ) messageLength ); - } - - return ( size_t ) bytesSent; -} - -/*-----------------------------------------------------------*/ - -size_t IotNetworkOpenssl_Receive( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - int receiveStatus = 0; - size_t bytesRead = 0, bytesRemaining = bytesRequested; - - IotLogDebug( "Blocking to wait for %lu bytes on socket %d.", - ( unsigned long ) bytesRequested, - pConnection->socket ); - - /* The SSL mutex must be locked to protect an SSL context from concurrent - * access. */ - if( pConnection->pSslContext != NULL ) - { - IotMutex_Lock( &( pConnection->sslMutex ) ); - } - - /* Loop until all bytes are received. */ - while( bytesRemaining > 0 ) - { - /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on - * whether the SSL connection context is set up. */ - if( pConnection->pSslContext != NULL ) - { - receiveStatus = SSL_read( pConnection->pSslContext, - pBuffer + bytesRead, - ( int ) bytesRemaining ); - } - else - { - receiveStatus = ( int ) recv( pConnection->socket, - pBuffer + bytesRead, - bytesRemaining, - 0 ); - } - - /* Check receive status. */ - if( receiveStatus <= 0 ) - { - IotLogError( "Error receiving from socket %d.", - pConnection->socket ); - - break; - } - else - { - bytesRead += ( size_t ) receiveStatus; - bytesRemaining -= ( size_t ) receiveStatus; - } - } - - /* Unlock the SSL context mutex. */ - if( pConnection->pSslContext != NULL ) - { - IotMutex_Unlock( &( pConnection->sslMutex ) ); - } - - /* Check how many bytes were read. */ - if( bytesRead < bytesRequested ) - { - IotLogWarn( "Receive requested %lu bytes, but only %lu bytes received.", - ( unsigned long ) bytesRequested, - ( unsigned long ) bytesRead ); - } - else - { - IotLogDebug( "Successfully received %lu bytes.", - ( unsigned long ) bytesRequested ); - } - - return bytesRead; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkOpenssl_Close( IotNetworkConnection_t pConnection ) -{ - /* If TLS was set up for this connection, clean it up. */ - if( pConnection->pSslContext != NULL ) - { - _tlsClose( pConnection ); - } - - /* Shut down the connection. */ - if( shutdown( pConnection->socket, SHUT_RDWR ) != 0 ) - { - IotLogWarn( "Could not shut down socket %d. errno=%d.", - pConnection->socket, - errno ); - } - else - { - IotLogInfo( "Connection (socket %d) shut down.", - pConnection->socket ); - } - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -IotNetworkError_t IotNetworkOpenssl_Destroy( IotNetworkConnection_t pConnection ) -{ - int posixError = 0; - - /* Check if a receive thread needs to be cleaned up. */ - if( pConnection->receiveThreadCreated == true ) - { - if( pthread_self() == pConnection->receiveThread ) - { - /* If this function is being called from the receive thread, detach - * it so no other thread needs to clean it up. */ - posixError = pthread_detach( pConnection->receiveThread ); - - if( posixError != 0 ) - { - IotLogWarn( "Failed to detach receive thread for socket %d. errno=%d.", - pConnection->socket, - posixError ); - } - } - else - { - /* Wait for the receive thread to exit. */ - posixError = pthread_join( pConnection->receiveThread, NULL ); - - if( posixError != 0 ) - { - IotLogWarn( "Failed to join receive thread for socket %d. errno=%d.", - pConnection->socket, - posixError ); - } - } - } - - /* Free the memory used by the TLS connection, if any. */ - if( pConnection->pSslContext != NULL ) - { - SSL_free( pConnection->pSslContext ); - - /* Destroy the SSL context mutex. */ - IotMutex_Destroy( &( pConnection->sslMutex ) ); - } - - /* Close the socket file descriptor. */ - if( close( pConnection->socket ) != 0 ) - { - IotLogWarn( "Could not close socket %d. errno=%d.", - pConnection->socket, - errno ); - } - else - { - IotLogInfo( "(Socket %d) Socket closed.", - pConnection->socket ); - } - - /* Free the connection. */ - IotNetwork_Free( pConnection ); - - return IOT_NETWORK_SUCCESS; -} - -/*-----------------------------------------------------------*/ - -int IotNetworkOpenssl_GetSocket( IotNetworkConnection_t pConnection ) -{ - return pConnection->socket; -} - -/*-----------------------------------------------------------*/ diff --git a/ports/posix/include/iot_platform_types_posix.h b/ports/posix/include/iot_platform_types_posix.h deleted file mode 100644 index 40be7ff077..0000000000 --- a/ports/posix/include/iot_platform_types_posix.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_platform_types_posix.h - * @brief Definitions of platform layer types on POSIX systems. - */ - -#ifndef IOT_PLATFORM_TYPES_POSIX_H_ -#define IOT_PLATFORM_TYPES_POSIX_H_ - -/* POSIX includes. Allow the default POSIX headers to be overridden. */ -#ifdef POSIX_TYPES_HEADER - #include POSIX_TYPES_HEADER -#else - #include -#endif -#ifdef POSIX_SEMAPHORE_HEADER - #include POSIX_SEMAPHORE_HEADER -#else - #include -#endif - -/** - * @brief The native mutex type on POSIX systems. - */ -typedef pthread_mutex_t _IotSystemMutex_t; - -/** - * @brief The native semaphore type on POSIX systems. - */ -typedef sem_t _IotSystemSemaphore_t; - -/** - * @brief Represents an #IotTimer_t on POSIX systems. - */ -typedef struct _IotSystemTimer -{ - timer_t timer; /**< @brief Underlying POSIX timer. */ - void * pArgument; /**< @brief First argument to threadRoutine. */ - void ( * threadRoutine )( void * pArg ); /**< @brief Thread function to run on timer expiration. */ -} _IotSystemTimer_t; - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declarations of the network types. - */ -struct IotNetworkServerInfo; -struct IotNetworkCredentials; -struct _networkConnection; -/** @endcond */ - -/** - * @brief The format for remote server host and port on this system. - */ -typedef struct IotNetworkServerInfo * _IotNetworkServerInfo_t; - -/** - * @brief The format for network credentials on this system. - */ -typedef struct IotNetworkCredentials * _IotNetworkCredentials_t; - -/** - * @brief The handle of a network connection on this system. - */ -typedef struct _networkConnection * _IotNetworkConnection_t; - -#endif /* ifndef IOT_PLATFORM_TYPES_POSIX_H_ */ diff --git a/ports/posix/lexicon.txt b/ports/posix/lexicon.txt deleted file mode 100644 index ae0ad258e9..0000000000 --- a/ports/posix/lexicon.txt +++ /dev/null @@ -1,21 +0,0 @@ -config -endcond -endif -html -http -ifndef -iot -iotthreadroutine -localtime -malloc -mutex -onlinepubs -opengroup -org -parg -pargument -posix -strftime -struct -threadroutine -timespec diff --git a/ports/posix/posix.cmake b/ports/posix/posix.cmake deleted file mode 100644 index 71f85a8f30..0000000000 --- a/ports/posix/posix.cmake +++ /dev/null @@ -1,93 +0,0 @@ -include( CheckTypeSize ) -include( CheckFunctionExists ) - -# Check that the POSIX realtime library is available. -find_library( LIB_REALTIME rt ) - -if( ${LIB_REALTIME} STREQUAL "LIB_REALTIME-NOTFOUND" ) - message( FATAL_ERROR "POSIX realtime library (librt) is not available." ) -endif() - -unset( LIB_REALTIME CACHE ) - -# Check for POSIX threads. -find_package( Threads REQUIRED ) - -if( NOT ${CMAKE_USE_PTHREADS_INIT} ) - message( FATAL_ERROR "POSIX threads required." ) -endif() - -# Check for required POSIX types. -set( CMAKE_EXTRA_INCLUDE_FILES "pthread.h" "time.h" "semaphore.h" ) -list( APPEND REQUIRED_POSIX_TYPES - pthread_t - pthread_attr_t - pthread_mutex_t - sem_t - timer_t ) - -foreach( POSIX_TYPE ${REQUIRED_POSIX_TYPES} ) - check_type_size( ${POSIX_TYPE} SIZEOF_${POSIX_TYPE} ) - - if( NOT HAVE_SIZEOF_${POSIX_TYPE} ) - message( FATAL_ERROR "Required type ${POSIX_TYPE} not found." ) - endif() -endforeach() - -# Check for some required POSIX functions. This is not intended to be a comprehensive list. -set( CMAKE_REQUIRED_LIBRARIES rt Threads::Threads ) -list( APPEND REQUIRED_POSIX_FUNCTIONS - clock_gettime time localtime_r strftime timer_create timer_delete - timer_settime pthread_create pthread_attr_init pthread_attr_setdetachstate - pthread_mutex_init pthread_mutex_lock pthread_mutex_trylock pthread_mutex_unlock - sem_init sem_getvalue sem_wait sem_trywait sem_timedwait sem_post ) - -foreach( POSIX_FUNCTION ${REQUIRED_POSIX_FUNCTIONS} ) - check_function_exists( ${POSIX_FUNCTION} HAVE_${POSIX_FUNCTION} ) - - if( NOT HAVE_${POSIX_FUNCTION} ) - message( FATAL_ERROR "Required function ${POSIX_FUNCTION} not found." ) - endif() -endforeach() - -# Choose either OpenSSL or mbed TLS. -if( ${IOT_NETWORK_USE_OPENSSL} ) - # Check for OpenSSL. - find_package( OpenSSL ) - - # Minimum supported OpenSSL version is 1.0.2g. - if( ${OPENSSL_FOUND} ) - if( ${OPENSSL_VERSION} STRLESS "1.0.2g" ) - message( FATAL_ERROR "OpenSSL 1.0.2g or later required, found ${OPENSSL_VERSION}." ) - endif() - - # Choose OpenSSL network source file. - set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_openssl.h ) - set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/common/src/iot_network_openssl.c ) - - # Link OpenSSL. - set( PLATFORM_DEPENDENCIES OpenSSL::SSL OpenSSL::Crypto ) - endif() -else() - set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) - set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c ) - set( PLATFORM_DEPENDENCIES mbedtls ) - set( MBEDTLS_REQUIRED TRUE PARENT_SCOPE ) -endif() - -# Add the network header for this platform. -list( APPEND PLATFORM_COMMON_HEADERS - ${NETWORK_HEADER} ) - -# Platform libraries source files. -set( PLATFORM_SOURCES - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c - ${NETWORK_SOURCE_FILE} ) - -# Set the types header for this port. -set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) - -# Link POSIX threads and real-time library. -set( PLATFORM_DEPENDENCIES ${PLATFORM_DEPENDENCIES} Threads::Threads rt ) diff --git a/ports/posix/src/iot_clock_posix.c b/ports/posix/src/iot_clock_posix.c deleted file mode 100644 index d1fcee0214..0000000000 --- a/ports/posix/src/iot_clock_posix.c +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_clock_posix.c - * @brief Implementation of the functions in iot_clock.h for POSIX systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* POSIX includes. Allow the default POSIX headers to be overridden. */ -#ifdef POSIX_ERRNO_HEADER - #include POSIX_ERRNO_HEADER -#else - #include -#endif -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif -#ifdef POSIX_SIGNAL_HEADER - #include POSIX_SIGNAL_HEADER -#else - #include -#endif -#ifdef POSIX_TIME_HEADER - #include POSIX_TIME_HEADER -#else - #include -#endif - -/* Platform clock include. */ -#include "platform/iot_clock.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "CLOCK" ) -#include "iot_logging_setup.h" - -/* When building tests, the Unity framework's malloc overrides are used to track - * calls to platform resource creation and destruction. This ensures that all - * platform resources are destroyed before the tests finish. When not testing, - * define the Unity malloc functions to nothing. */ -#if IOT_BUILD_TESTS != 1 - #define UnityMalloc_AllocateResource() true - #define UnityMalloc_FreeResource() -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief The format of timestrings printed in logs. - * - * For more information on timestring formats, see [this link.] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) - */ -#define TIMESTRING_FORMAT ( "%F %R:%S" ) - -/* - * Time conversion constants. - */ -#define NANOSECONDS_PER_SECOND ( 1000000000 ) /**< @brief Nanoseconds per second. */ -#define NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ -#define MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. - * - * @param[in] argument The value passed as `sigevent.sigev_value`. - */ -static void _timerExpirationWrapper( union sigval argument ); - -/** - * @brief Convert a relative timeout in milliseconds to an absolute timeout - * represented as a struct timespec. - * - * This function is not included in iot_clock.h because it's platform-specific. - * But it may be called by other POSIX platform files. - * @param[in] timeoutMs The relative timeout. - * @param[out] pOutput Where to store the resulting `timespec`. - * - * @return `true` if `timeoutMs` was successfully converted; `false` otherwise. - */ -bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, - struct timespec * pOutput ); - -/*-----------------------------------------------------------*/ - -static void _timerExpirationWrapper( union sigval argument ) -{ - IotTimer_t * pTimer = ( IotTimer_t * ) argument.sival_ptr; - - /* Call the wrapped thread routine. */ - pTimer->threadRoutine( pTimer->pArgument ); -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, - struct timespec * pOutput ) -{ - bool status = true; - struct timespec systemTime = { 0 }; - - if( clock_gettime( CLOCK_REALTIME, &systemTime ) == 0 ) - { - /* Add the nanoseconds value to the time. */ - systemTime.tv_nsec += ( long ) ( ( timeoutMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND ); - - /* Check for overflow of nanoseconds value. */ - if( systemTime.tv_nsec >= NANOSECONDS_PER_SECOND ) - { - systemTime.tv_nsec -= NANOSECONDS_PER_SECOND; - systemTime.tv_sec++; - } - - /* Add the seconds value to the timeout. */ - systemTime.tv_sec += ( time_t ) ( timeoutMs / MILLISECONDS_PER_SECOND ); - - /* Set the output parameter. */ - *pOutput = systemTime; - } - else - { - IotLogError( "Failed to read system time. errno=%d", errno ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ) -{ - bool status = true; - const time_t unixTime = time( NULL ); - struct tm localTime = { 0 }; - size_t timestringLength = 0; - - /* localtime_r is the thread-safe variant of localtime. Its return value - * should be the pointer to the localTime struct. */ - if( localtime_r( &unixTime, &localTime ) != &localTime ) - { - status = false; - } - - if( status == true ) - { - /* Convert the localTime struct to a string. */ - timestringLength = strftime( pBuffer, bufferSize, TIMESTRING_FORMAT, &localTime ); - - /* Check for error from strftime. */ - if( timestringLength == 0 ) - { - status = false; - } - else - { - /* Set the output parameter. */ - *pTimestringLength = timestringLength; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -uint64_t IotClock_GetTimeMs( void ) -{ - struct timespec currentTime = { 0 }; - - if( clock_gettime( CLOCK_MONOTONIC, ¤tTime ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to read time from CLOCK_MONOTONIC. errno=%d", - errno ); - - abort(); - } - - return ( ( uint64_t ) currentTime.tv_sec ) * MILLISECONDS_PER_SECOND + - ( ( uint64_t ) currentTime.tv_nsec ) / NANOSECONDS_PER_MILLISECOND; -} - -/*-----------------------------------------------------------*/ - -void IotClock_SleepMs( uint32_t sleepTimeMs ) -{ - /* Convert parameter to timespec. */ - struct timespec sleepTime = - { - .tv_sec = sleepTimeMs / MILLISECONDS_PER_SECOND, - .tv_nsec = ( sleepTimeMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND - }; - - if( nanosleep( &sleepTime, NULL ) == -1 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Sleep failed. errno=%d.", errno ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerCreate( IotTimer_t * pNewTimer, - IotThreadRoutine_t expirationRoutine, - void * pArgument ) -{ - bool status = UnityMalloc_AllocateResource(); - struct sigevent expirationNotification = - { - .sigev_notify = SIGEV_THREAD, - .sigev_signo = 0, - .sigev_value.sival_ptr = pNewTimer, - .sigev_notify_function = _timerExpirationWrapper, - .sigev_notify_attributes = NULL - }; - - if( status == true ) - { - IotLogDebug( "Creating new timer %p.", pNewTimer ); - - /* Set the timer expiration routine and argument. */ - pNewTimer->threadRoutine = expirationRoutine; - pNewTimer->pArgument = pArgument; - - /* Create the underlying POSIX timer. */ - if( timer_create( CLOCK_REALTIME, - &expirationNotification, - &( pNewTimer->timer ) ) != 0 ) - { - IotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); - UnityMalloc_FreeResource(); - status = false; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotClock_TimerDestroy( IotTimer_t * pTimer ) -{ - IotLogDebug( "Destroying timer %p.", pTimer ); - - /* Decrement the number of platform resources in use. */ - UnityMalloc_FreeResource(); - - /* Destroy the underlying POSIX timer. */ - if( timer_delete( pTimer->timer ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerArm( IotTimer_t * pTimer, - uint32_t relativeTimeoutMs, - uint32_t periodMs ) -{ - bool status = true; - struct itimerspec timerExpiration = - { - .it_value = { 0 }, - .it_interval = { 0 } - }; - - IotLogDebug( "Arming timer %p with timeout %lu and period %lu.", - pTimer, - relativeTimeoutMs, - periodMs ); - - /* Calculate the initial timer expiration. */ - if( IotClock_TimeoutToTimespec( relativeTimeoutMs, - &( timerExpiration.it_value ) ) == false ) - { - IotLogError( "Invalid relative timeout." ); - - status = false; - } - - if( status == true ) - { - /* Calculate the timer expiration period. */ - if( periodMs > 0 ) - { - timerExpiration.it_interval.tv_sec = ( time_t ) ( periodMs / MILLISECONDS_PER_SECOND ); - timerExpiration.it_interval.tv_nsec = ( long ) ( ( periodMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND ); - } - - /* Arm the underlying POSIX timer. */ - if( timer_settime( pTimer->timer, TIMER_ABSTIME, &timerExpiration, NULL ) != 0 ) - { - IotLogError( "Failed to arm timer %p. errno=%d.", pTimer, errno ); - - status = false; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/ports/posix/src/iot_threads_posix.c b/ports/posix/src/iot_threads_posix.c deleted file mode 100644 index 83d907e8c1..0000000000 --- a/ports/posix/src/iot_threads_posix.c +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_threads_posix.c - * @brief Implementation of the functions in iot_threads.h for POSIX systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* POSIX includes. Allow the default POSIX headers to be overridden. */ -#ifdef POSIX_ERRNO_HEADER - #include POSIX_ERRNO_HEADER -#else - #include -#endif -#ifdef POSIX_LIMITS_HEADER - #include POSIX_LIMITS_HEADER -#else - #include -#endif -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif -#ifdef POSIX_SEMAPHORE_HEADER - #include POSIX_SEMAPHORE_HEADER -#else - #include -#endif - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "THREAD" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions. - */ -#ifndef IotThreads_Malloc - #include - -/** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotThreads_Malloc malloc -#endif -#ifndef IotThreads_Free - #include - -/** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotThreads_Free free -#endif - -/* When building tests, the Unity framework's malloc overrides are used to track - * calls to platform resource creation and destruction. This ensures that all - * platform resources are destroyed before the tests finish. When not testing, - * define the Unity malloc functions to nothing. */ -#if IOT_BUILD_TESTS != 1 - #define UnityMalloc_AllocateResource() true - #define UnityMalloc_FreeResource() -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Holds information about an active detached thread. - */ -typedef struct _threadInfo -{ - void * pArgument; /**< @brief First argument to `threadRoutine`. */ - IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ -} _threadInfo_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. - * - * @param[in] pArgument The value passed as `arg` to `pthread_create`. - * - * @return Always returns `NULL`. - */ -static void * _threadRoutineWrapper( void * pArgument ); - -/* Platform-specific function implemented in iot_clock_posix.c */ -extern bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, - struct timespec * pOutput ); - -/*-----------------------------------------------------------*/ - -static void * _threadRoutineWrapper( void * pArgument ) -{ - _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; - - /* Read thread routine and argument, then free thread info. */ - IotThreadRoutine_t threadRoutine = pThreadInfo->threadRoutine; - void * pThreadRoutineArgument = pThreadInfo->pArgument; - - IotThreads_Free( pThreadInfo ); - - /* Run the thread routine. */ - threadRoutine( pThreadRoutineArgument ); - - return NULL; -} - -/*-----------------------------------------------------------*/ - -bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument, - int32_t priority, - size_t stackSize ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - int posixErrno = 0; - bool threadAttributesCreated = false; - _threadInfo_t * pThreadInfo = NULL; - pthread_t newThread; - pthread_attr_t threadAttributes; - struct sched_param priorityParam; - - /* Allocate memory for the new thread info. */ - pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); - - if( pThreadInfo == NULL ) - { - IotLogError( "Failed to allocate memory for new thread." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Set up thread attributes object. */ - posixErrno = pthread_attr_init( &threadAttributes ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to initialize thread attributes. errno=%d.", - posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - threadAttributesCreated = true; - - /* Set the new thread to detached. */ - posixErrno = pthread_attr_setdetachstate( &threadAttributes, - PTHREAD_CREATE_DETACHED ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set detached thread attribute. errno=%d.", - posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - if( stackSize != IOT_THREAD_IGNORE_STACK_SIZE ) - { - posixErrno = pthread_attr_setstacksize( &threadAttributes, stackSize ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set thread stack size. errno=%d.", - posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - if( priority != IOT_THREAD_IGNORE_PRIORITY ) - { - priorityParam.sched_priority = priority; - posixErrno = pthread_attr_setschedparam( &threadAttributes, - &priorityParam ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set thread priority. errno=%d.", - posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* Set the thread routine and argument. */ - pThreadInfo->threadRoutine = threadRoutine; - pThreadInfo->pArgument = pArgument; - - /* Create the underlying POSIX thread. */ - posixErrno = pthread_create( &newThread, - &threadAttributes, - _threadRoutineWrapper, - pThreadInfo ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Destroy thread attributes object. */ - if( threadAttributesCreated == true ) - { - posixErrno = pthread_attr_destroy( &threadAttributes ); - - if( posixErrno != 0 ) - { - IotLogWarn( "Failed to destroy thread attributes. errno=%d.", - posixErrno ); - } - } - - /* Clean up on error. */ - if( status == false ) - { - if( pThreadInfo != NULL ) - { - IotThreads_Free( pThreadInfo ); - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_Create( IotMutex_t * pNewMutex, - bool recursive ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - int mutexError = 0; - pthread_mutexattr_t mutexAttributes, * pMutexAttributes = NULL; - - if( recursive == true ) - { - /* Create new mutex attributes object. */ - mutexError = pthread_mutexattr_init( &mutexAttributes ); - - if( mutexError != 0 ) - { - IotLogError( "Failed to initialize mutex attributes. errno=%d.", - mutexError ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - pMutexAttributes = &mutexAttributes; - - /* Set recursive mutex type. */ - mutexError = pthread_mutexattr_settype( &mutexAttributes, - PTHREAD_MUTEX_RECURSIVE ); - - if( mutexError != 0 ) - { - IotLogError( "Failed to set recursive mutex type. errno=%d.", - mutexError ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - status = UnityMalloc_AllocateResource(); - - if( status == true ) - { - mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); - - if( mutexError != 0 ) - { - IotLogError( "Failed to create new mutex %p. errno=%d.", - pNewMutex, - mutexError ); - - UnityMalloc_FreeResource(); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Destroy any created mutex attributes. */ - if( pMutexAttributes != NULL ) - { - ( void ) pthread_mutexattr_destroy( &mutexAttributes ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Destroy( IotMutex_t * pMutex ) -{ - /* Decrement the number of platform resources in use. */ - UnityMalloc_FreeResource(); - - int mutexError = pthread_mutex_destroy( pMutex ); - - if( mutexError != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to destroy mutex %p. errno=%d.", - pMutex, - mutexError ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Lock( IotMutex_t * pMutex ) -{ - int mutexError = pthread_mutex_lock( pMutex ); - - if( mutexError != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to lock mutex %p. errno=%d.", - pMutex, - mutexError ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_TryLock( IotMutex_t * pMutex ) -{ - bool status = true; - int mutexError = pthread_mutex_trylock( pMutex ); - - if( mutexError != 0 ) - { - IotLogDebug( "Mutex mutex %p is not available. errno=%d.", - pMutex, - mutexError ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Unlock( IotMutex_t * pMutex ) -{ - int mutexError = pthread_mutex_unlock( pMutex ); - - if( mutexError != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to unlock mutex %p. errno=%d.", - pMutex, - mutexError ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ) -{ - bool status = true; - - if( maxValue > ( uint32_t ) SEM_VALUE_MAX ) - { - IotLogError( "%lu is larger than the maximum value a semaphore may" - " have on this system.", maxValue ); - - status = false; - } - else - { - status = UnityMalloc_AllocateResource(); - - if( status == true ) - { - if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) - { - IotLogError( "Failed to create new semaphore %p. errno=%d.", - pNewSemaphore, - errno ); - - UnityMalloc_FreeResource(); - status = false; - } - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) -{ - int count = 0; - - if( sem_getvalue( pSemaphore, &count ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to query semaphore count of %p. errno=%d.", - pSemaphore, - errno ); - - abort(); - } - - return ( uint32_t ) count; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) -{ - /* Decrement the number of platform resources in use. */ - UnityMalloc_FreeResource(); - - if( sem_destroy( pSemaphore ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to destroy semaphore %p. errno=%d.", - pSemaphore, - errno ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) -{ - if( sem_wait( pSemaphore ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to wait on semaphore %p. errno=%d.", - pSemaphore, - errno ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) -{ - bool status = true; - - if( sem_trywait( pSemaphore ) != 0 ) - { - IotLogDebug( "Semaphore %p is not available. errno=%d.", - pSemaphore, - errno ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - bool status = true; - struct timespec timeout = { 0 }; - - if( IotClock_TimeoutToTimespec( timeoutMs, &timeout ) == false ) - { - IotLogError( "Invalid timeout." ); - - status = false; - } - else - { - if( sem_timedwait( pSemaphore, &timeout ) != 0 ) - { - IotLogDebug( "Semaphore %p is not available. errno=%d.", - pSemaphore, - errno ); - - status = false; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - if( sem_post( pSemaphore ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to post to semaphore %p. errno=%d.", - pSemaphore, - errno ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ diff --git a/ports/template/include/iot_platform_types_template.h b/ports/template/include/iot_platform_types_template.h deleted file mode 100644 index d795d6fa1e..0000000000 --- a/ports/template/include/iot_platform_types_template.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_platform_types_template.h - * @brief Template definitions of platform layer types. - */ - -#ifndef IOT_PLATFORM_TYPES_TEMPLATE_H_ -#define IOT_PLATFORM_TYPES_TEMPLATE_H_ - -/** - * @brief Set this to the target system's mutex type. - */ -typedef void * _IotSystemMutex_t; - -/** - * @brief Set this to the target system's semaphore type. - */ -typedef void * _IotSystemSemaphore_t; - -/** - * @brief Set this to the target system's timer type. - */ -typedef void * _IotSystemTimer_t; - -/** - * @brief The format for remote server host and port on this system. - */ -typedef void * _IotNetworkServerInfo_t; - -/** - * @brief The format for network credentials on this system. - */ -typedef void * _IotNetworkCredentials_t; - -/** - * @brief The handle of a network connection on this system. - */ -typedef void * _IotNetworkConnection_t; - -#endif /* ifndef IOT_PLATFORM_TYPES_TEMPLATE_H_ */ diff --git a/ports/template/lexicon.txt b/ports/template/lexicon.txt deleted file mode 100644 index c7789e9353..0000000000 --- a/ports/template/lexicon.txt +++ /dev/null @@ -1,29 +0,0 @@ -aws -com -config -createdetachedthread -endif -freertos -gettimems -gettimestring -html -https -ifndef -iot -mutexcreate -mutexdestroy -mutexlock -mutextrylock -mutexunlock -sdk -semaphorecreate -semaphoredestroy -semaphoregetcount -semaphorepost -semaphoretimedwait -semaphoretrywait -semaphorewait -sleepms -timerarm -timercreate -timerdestroy diff --git a/ports/template/src/iot_clock_template.c b/ports/template/src/iot_clock_template.c deleted file mode 100644 index 7ab136dc35..0000000000 --- a/ports/template/src/iot_clock_template.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_clock_template.c - * @brief Template implementation of the functions in iot_clock.h - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Platform clock include. */ -#include "platform/iot_clock.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "CLOCK" ) -#include "iot_logging_setup.h" - -/*-----------------------------------------------------------*/ - -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_gettimestring.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -uint64_t IotClock_GetTimeMs( void ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_gettimems.html - */ - return 0; -} - -/*-----------------------------------------------------------*/ - -void IotClock_SleepMs( uint32_t sleepTimeMs ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_sleepms.html - */ -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerCreate( IotTimer_t * pNewTimer, - IotThreadRoutine_t expirationRoutine, - void * pArgument ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_timercreate.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -void IotClock_TimerDestroy( IotTimer_t * pTimer ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_timerdestroy.html - */ -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerArm( IotTimer_t * pTimer, - uint32_t relativeTimeoutMs, - uint32_t periodMs ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_timerarm.html - */ - return false; -} - -/*-----------------------------------------------------------*/ diff --git a/ports/template/src/iot_threads_template.c b/ports/template/src/iot_threads_template.c deleted file mode 100644 index 72a9b53412..0000000000 --- a/ports/template/src/iot_threads_template.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_threads_template.c - * @brief Template implementation of the functions in iot_threads.h - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "THREAD" ) -#include "iot_logging_setup.h" - -/*-----------------------------------------------------------*/ - -bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument, - int32_t priority, - size_t stackSize ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_createdetachedthread.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexcreate.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Destroy( IotMutex_t * pMutex ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexdestroy.html - */ -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Lock( IotMutex_t * pMutex ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexlock.html - */ -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_TryLock( IotMutex_t * pMutex ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutextrylock.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Unlock( IotMutex_t * pMutex ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexunlock.html - */ -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphorecreate.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoredestroy.html - */ -} - -/*-----------------------------------------------------------*/ - -uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoregetcount.html - */ - return 0; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphorewait.html - */ -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoretrywait.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoretimedwait.html - */ - return false; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphorepost.html - */ -} - -/*-----------------------------------------------------------*/ diff --git a/ports/template/template.cmake b/ports/template/template.cmake deleted file mode 100644 index f33a79ebaf..0000000000 --- a/ports/template/template.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# This CMakeLists is a template for new ports. It provides the minimal -# configuration for building, but nothing more. - -# Warn that the template port only builds. It will not create usable libraries. -message( WARNING "This is a template port that contains only stubs. Libraries built with this port will not work!") - -# Template platform sources. A network implementation (not listed below) is also needed. -set( PLATFORM_SOURCES - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c ) - -# Set the types header for this port. -set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) diff --git a/scripts/ablexicon b/scripts/ablexicon deleted file mode 100755 index c79c018d91..0000000000 --- a/scripts/ablexicon +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash -# -# ablexicon - Compare an input list of words against a dictionary and -# optional lexicon. If any words are in neither the dictionary nor the -# lexicon, log them to stdout. -# -set -e -set -f - -function usage () { - echo "Find occurrences of non-dictionary/lexicon words" - echo "" - echo "Usage:" - echo " ${0##*/} [options]" - echo "" - echo "Options:" - echo " -f, --file source text (defaults to /dev/fd/0)" - echo " -l, --lexicon lexicon file (one word per line)" - echo " -h, --help display this help" - exit 1 -} - -# -# Verify that required commands are present -# -REQUIRED=( "spell" "getopt" ) -for i in "${REQUIRED[@]}" -do - command -v $i"" >/dev/null - if [ $? -ne "0" ] - then - echo "'"$i"' must be installed, exiting...">&2 - exit 1 - fi -done - -GETOPT_OUT=`getopt -o hf:l: --long help,file:,lexicon: -n "${0##*/}" -- "$@"` -if [ $? != 0 ] -then - echo "Exiting..." >&2 - exit 1 -fi - -eval set -- "$GETOPT_OUT" - -INFILE=/dev/fd/0 -LEXICON=/dev/null -while true; do - case "$1" in - -h | --help ) usage $0 ;; - -f | --file ) INFILE="$2"; shift 2 ;; - -l | --lexicon ) LEXICON="$2"; shift 2 ;; - -- ) shift; break ;; - * ) break ;; - esac -done - -if [ ! -f $INFILE"" ] && [ $INFILE"" != /dev/fd/0 ] -then - echo "Invalid input file" - usage -fi -# -# Read the lexicon into an array -# -readarray -t lexicon < $LEXICON"" -lexicon_size="${#lexicon[@]}" - -# -# Search for all input words in the dictionary -# and sort the output -# -for word in `cat $INFILE"" | spell | sort -u` -do - # - # Search for each remaining word in the lexicon - # - found="false" - i="0" - while [[ "$i" -lt "$lexicon_size" ]] && [ "$found" == "false" ] - do - if [ "${lexicon[i]}" == "$word" ] - then - found="true" - fi - i=$((i+1)) - done - if [ $found"" == "false" ] - then - # - # The word is neither in the dictionary nor the lexicon, send - # it to stdout. - # - echo $word - fi -done diff --git a/scripts/ci_test_build.sh b/scripts/ci_test_build.sh deleted file mode 100755 index b2da6663ab..0000000000 --- a/scripts/ci_test_build.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test various build configurations. - -# Exit on any nonzero return code. -set -e - -# Treat warnings as errors. -if [ "$TRAVIS_COMPILER" = "clang" ]; then - COMPILER_OPTIONS+=" -Werror" -fi - -# Build demos. -cmake .. -DCMAKE_C_FLAGS="$COMPILER_OPTIONS" -make -j2 - -# Build tests. Enable all logging. -rm -rf * -cmake .. -DCMAKE_BUILD_TYPE=Debug -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" -make -j2 - -# Release build with logging disabled. -rm -rf * -cmake .. -DCMAKE_BUILD_TYPE=Release -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_NONE" -make -j2 diff --git a/scripts/ci_test_common.sh b/scripts/ci_test_common.sh deleted file mode 100755 index 8c41191954..0000000000 --- a/scripts/ci_test_common.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test the common libraries. - -# Exit on any nonzero return code. -set -e - -# CMake compiler flags for building common libraries. -CMAKE_FLAGS="$COMPILER_OPTIONS" - -# Build executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 iot_tests_common - -# Run common tests. -./output/bin/iot_tests_common - -# Don't reconfigure CMake if script is invoked for coverage build. -if [ "$RUN_TEST" != "coverage" ]; then - # Rebuild in static memory mode. - cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" - make -j2 iot_tests_common - - # Run common tests in static memory mode. - ./output/bin/iot_tests_common -fi \ No newline at end of file diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh deleted file mode 100755 index 3c58af13d0..0000000000 --- a/scripts/ci_test_coverage.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to build an submit code coverage results. -# It does not run tests that require the network. - -# Exit on any nonzero return code. -set -e - -# Function that generates code coverage report from the gcov files for a library. (it ignores all non-production code) -function generate_coverage() { - if [ $# -ne 1 ]; then - echo '"generate_coverage" requires input argument of coverage filename.' - exit 1 - fi - - # Generate code coverage results, but only for files in libraries/. - lcov --rc lcov_branch_coverage=1 --directory . --capture --output-file $1 - lcov --rc lcov_branch_coverage=1 --remove $1 '*demo*' --output-file $1 - lcov --rc lcov_branch_coverage=1 --remove $1 '*ports*' --output-file $1 - lcov --rc lcov_branch_coverage=1 --remove $1 '*test*' --output-file $1 - lcov --rc lcov_branch_coverage=1 --remove $1 '*third_party*' --output-file $1 -} - -# Overwrite the value of the COMPILER_OPTIONS variable to remove any thread sanitizer flags, and replace with coverage flags. -export COMPILER_OPTIONS="-DIOT_BUILD_TESTS=1 -DIOT_TEST_COVERAGE=1 --coverage -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" - -SCRIPTS_FOLDER_PATH=../scripts - -# Run common tests with code coverage. -$SCRIPTS_FOLDER_PATH/ci_test_common.sh -generate_coverage common.info - -# Run MQTT tests against AWS IoT with code coverage. -$SCRIPTS_FOLDER_PATH/ci_test_mqtt.sh -generate_coverage mqtt.info - -# Run Shadow tests with code coverage. -$SCRIPTS_FOLDER_PATH/ci_test_shadow.sh -generate_coverage shadow.info - -# Run Jobs tests with code coverage. -$SCRIPTS_FOLDER_PATH/ci_test_jobs.sh -generate_coverage jobs.info - -# Combine the coverage files of all libraries into a single master coverage file. -lcov --rc lcov_branch_coverage=1 \ - --add-tracefile common.info \ - --add-tracefile mqtt.info \ - --add-tracefile shadow.info \ - --add-tracefile jobs.info \ - --output-file coverage.info diff --git a/scripts/ci_test_defender.sh b/scripts/ci_test_defender.sh deleted file mode 100755 index 7df44ae4cd..0000000000 --- a/scripts/ci_test_defender.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test the Defender library. - -# Exit on any nonzero return code. -set -e diff --git a/scripts/ci_test_doc.sh b/scripts/ci_test_doc.sh deleted file mode 100755 index 0c3486b806..0000000000 --- a/scripts/ci_test_doc.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh - -# Check arguments when running locally. If running on CI, install the correct -# version of doxygen. -if [ -z "$TRAVIS_PULL_REQUEST" ]; then - if [ $# -ne 1 ]; then - echo "Usage: ./generate_doc.sh sdk_root_directory" - exit 1 - fi - - # Change to SDK root directory. - cd $1 -else - set -ev - wget -O doxygen_source.tar.gz https://downloads.sourceforge.net/project/doxygen/rel-1.8.14/doxygen-1.8.14.src.tar.gz - tar xf doxygen_source.tar.gz - cmake doxygen-1.8.14 -DCMAKE_CXX_FLAGS="-w" - make -j2 - sudo make install - cd .. -fi - -# Check for doxygen. -command -v doxygen > /dev/null || { echo "Doxygen not found. Exiting."; exit 1; } - -# Create tag directory if needed. -mkdir -p doc/tag - -# Doxygen must be run twice: once to generate tags and once more to link tags. -i=0; while [ $i -le 1 ]; do - # Run for each library config file. - for file in doc/config/*; do - # Ignore directories. - if [ -d $file ]; then - continue - fi - - # Ignore xml files. - if [ ${file##*.} = "xml" ]; then - continue - fi - - # Ignore the common configuration file. - if [ $file = "doc/config/common" ]; then - continue - fi - - # Generate Doxygen tags first. Once tags are generated, generate documentation. - if [ $i -eq 0 ]; then - echo "Generating Doxygen tags for $file..." - doxygen $file 2> /dev/null - else - echo "Generating documentation for $file..." - - # Redirect errors to file when running under Travis CI. - if [ -z "$TRAVIS" ]; then - doxygen $file - else - doxygen $file 2>>doxygen_warnings.txt - fi - fi - done - - i=$(($i+1)); -done - -echo "Documentation written to doc/output" - -# Print any doxygen errors or warnings and exit with a nonzero value. -if [ -s doxygen_warnings.txt ]; then - cat doxygen_warnings.txt - exit 1 -fi diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh deleted file mode 100755 index 5b38786600..0000000000 --- a/scripts/ci_test_jobs.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test the Jobs library. - -# Exit on any nonzero return code. -set -e - -# Query the AWS account ID. -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account') -fi - -# Prefix of Job IDs used in the tests, set by the create_jobs function. -JOB_PREFIX="" - -# Function for running the existing test executables. -run_tests() { - # For commit builds, run the full Jobs tests. For pull request builds, - # run only the unit tests (credentials are not available for pull request builds). - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - ./output/bin/aws_iot_tests_jobs - else - # Run only Jobs unit tests. - ./output/bin/aws_iot_tests_jobs -n - fi -} - -# Function to create Jobs to use in the tests. -create_jobs() { - JOB_PREFIX=$IOT_IDENTIFIER-$(jot -r 1 1 100000) - aws iot create-job \ - --job-id $JOB_PREFIX-1 \ - --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ - --document '{"action":"print","message":"Hello world!"}' - aws iot create-job \ - --job-id $JOB_PREFIX-2 \ - --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ - --document '{"action":"print","message":"Hello world!"}' -} - -# Function to delete Jobs and clean up the tests. -delete_jobs() { - aws iot delete-job --job-id $JOB_PREFIX-1 --force - aws iot delete-job --job-id $JOB_PREFIX-2 --force -} - -# CMake compiler flags for building Jobs. -CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_JOBS_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" - -# Build executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 aws_iot_tests_jobs - -# Run tests. -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then trap "delete_jobs" EXIT; fi -run_tests -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi - -# Don't reconfigure CMake if script is invoked for coverage build. -if [ "$RUN_TEST" != "coverage" ]; then - # Rebuild in static memory mode. - cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" - make -j2 aws_iot_tests_jobs - - # Run tests in static memory mode. - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi - run_tests - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi -fi - -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then trap - EXIT; fi diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh deleted file mode 100755 index 8e4adc3839..0000000000 --- a/scripts/ci_test_mqtt.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test the MQTT library. - -# Exit on any nonzero return code. -set -e - -CMAKE_NON_CREDENTIAL_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" -TEST_OPTIONS="" - -DEMO_OPTIONS="-i $IOT_IDENTIFIER" - -# For pull request builds on Linux, run against a local Mosquitto broker. -# -# For commit builds all platforms will test against AWS IoT. -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - if [ "$TRAVIS_OS_NAME" = "linux" ]; then - # Set the flags and options for a local Mosquitto broker on Linux. - CMAKE_CREDENTIAL_FLAGS+=" -DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\"" - DEMO_OPTIONS+=" -n -u -h localhost -p 1883" - fi -else - # Set credentials for AWS IoT. - CMAKE_CREDENTIAL_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES" -fi - -CMAKE_FLAGS="$CMAKE_NON_CREDENTIAL_FLAGS $CMAKE_CREDENTIAL_FLAGS" - -# Build and run executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" - -make -j2 iot_tests_mqtt iot_demo_mqtt - -./output/bin/iot_tests_mqtt $TEST_OPTIONS - -# Don't reconfigure CMake if script is invoked for coverage build. -if [ "$RUN_TEST" != "coverage" ]; then - if [ "$TRAVIS_OS_NAME" = "linux" ]; then - ./output/bin/iot_demo_mqtt $DEMO_OPTIONS - fi - - # Rebuild and run tests in static memory mode. - cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" - - make -j2 iot_tests_mqtt iot_demo_mqtt - - ./output/bin/iot_tests_mqtt $TEST_OPTIONS -fi diff --git a/scripts/ci_test_quality.sh b/scripts/ci_test_quality.sh deleted file mode 100755 index 8149bd7892..0000000000 --- a/scripts/ci_test_quality.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test code quality. - -# Exit on any nonzero return code. -set -e - -# Get the list of files to check. Only check library files and exclude tests. -# Run complexity with a threshold of 8. -find ../libraries/ \( -name '*.c' ! -name *tests*.c \) -type f | \ -xargs complexity --scores --threshold=8 #--horrid-threshold=8 diff --git a/scripts/ci_test_serializer.sh b/scripts/ci_test_serializer.sh deleted file mode 100644 index 28c2f3bc42..0000000000 --- a/scripts/ci_test_serializer.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test the serializer library. - -# Exit on any nonzero return code. -set -e - -# CMake compiler flags for building serializer libraries. -CMAKE_FLAGS="$COMPILER_OPTIONS" - -# Build executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 iot_tests_serializer - -# Run serializer tests. -./output/bin/iot_tests_serializer - -# Don't reconfigure CMake if script is invoked for coverage build. -if [ "$RUN_TEST" != "coverage" ]; then - # Rebuild in static memory mode. - cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" - make -j2 iot_tests_serializer - - # Run serializer tests in static memory mode. - ./output/bin/iot_tests_serializer -fi \ No newline at end of file diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh deleted file mode 100755 index df56f4b0d1..0000000000 --- a/scripts/ci_test_shadow.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to test the Shadow library. - -# Exit on any nonzero return code. -set -e - -# Function for running the existing test and demo executables. -run_tests_and_demos() { - # For commit builds, run the full Shadow demo and tests. For pull request builds, - # run only the unit tests (credentials are not available for pull request builds). - # Sleep for 1.1 seconds between the runs to respect AWS service limits. - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - ./output/bin/aws_iot_tests_shadow - sleep 1.1 - - # Don't reconfigure CMake if script is invoked for coverage build. - if [ "$RUN_TEST" != "coverage" ]; then - ./output/bin/aws_iot_demo_shadow - fi - else - # Run only Shadow unit tests. - ./output/bin/aws_iot_tests_shadow -n - fi -} - -# CMake compiler flags for building Shadow. -CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS=1 $COMPILER_OPTIONS" - -# Build executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 aws_iot_tests_shadow aws_iot_demo_shadow - -# Run tests and demos. -run_tests_and_demos - -# Don't reconfigure CMake if script is invoked for coverage build. -if [ "$RUN_TEST" != "coverage" ]; then - # Rebuild in static memory mode. - cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" - make -j2 aws_iot_tests_shadow aws_iot_demo_shadow - - # Run tests and demos in static memory mode. - run_tests_and_demos -fi \ No newline at end of file diff --git a/scripts/ci_test_spelling.sh b/scripts/ci_test_spelling.sh deleted file mode 100755 index 1e9428e6ee..0000000000 --- a/scripts/ci_test_spelling.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -# Check arguments when running locally. If running on CI, install dependencies. -if [ -z "$TRAVIS_PULL_REQUEST" ]; then - if [ $# -ne 1 ]; then - echo "Usage: ${0##*/} sdk_root_directory" - exit 1 - fi - - # Change to SDK root directory. - cd $1 -else - set -ev - cd .. # change to SDK root directory -fi -PATH=$PATH:$PWD/scripts - -# Check for find-unknown-comment-words. -command -v find-unknown-comment-words > /dev/null || { echo "Can't find spellcheck script, exiting."; exit 1; } - -STATUS= -# -# Check spelling in all directories with a 'lexicon.txt' file. -# -for lexfile in `find . -name lexicon.txt` -do - dir=${lexfile%/lexicon.txt} - find-unknown-comment-words --directory $dir - if [ $? -ne "0" ] - then - STATUS=1 - fi -done - -if [ $STATUS"" = "1" ] -then - exit 1 -fi diff --git a/scripts/coverity_misra.config b/scripts/coverity_misra.config deleted file mode 100644 index 2ef1337996..0000000000 --- a/scripts/coverity_misra.config +++ /dev/null @@ -1,46 +0,0 @@ -// MISRA C-2012 Rules - -{ - version : "2.0", - standard : "c2012", - title: "Coverity MISRA Configuration", - deviations : [ - // Disable the following rules. - { - deviation: "Directive 4.5", - reason: "Allow names that MISRA considers ambiguous (such as enum IOT_MQTT_CONNECT and function IotMqtt_Connect)." - }, - { - deviation: "Directive 4.8", - reason: "Allow inclusion of unused types. Header files for a specific port, which are needed by all files, may define types that are not used by a specific file." - }, - { - deviation: "Directive 4.9", - reason: "Allow inclusion of function like macros. Logging is done using function like macros." - }, - { - deviation: "Rule 2.4", - reason: "Allow unused tags. Some compilers warn if types are not tagged." - }, - { - deviation: "Rule 2.5", - reason: "Allow unused macros. Library headers may define macros intended for the application's use, but not used by a specific file." - }, - { - deviation: "Rule 3.1", - reason: "Allow nested comments. Documentation blocks contain comments for example code." - }, - { - deviation: "Rule 11.5", - reason: "Allow casts from void *. Contexts are passed as void * and must be cast to the correct data type before use." - }, - { - deviation: "Rule 21.1", - reason: "Allow use of all names." - }, - { - deviation: "Rule 21.2", - reason: "Allow use of all names." - } - ] -} diff --git a/scripts/extract-comments b/scripts/extract-comments deleted file mode 100755 index 861e77e5d1..0000000000 --- a/scripts/extract-comments +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# -# Extract comments from C/C++ files -# -set -e -set -f - -function usage () { - echo "Extract comments from C/C++ files" - echo "" - echo "usage: "${0##*/}" file-list" - exit 1 -} - -if [ $# -lt 1 ] -then - usage $0 -fi - -if [ $1 = "-h" ] || [ $1 == "--help" ] -then - usage $0 -fi - -while test $# -gt 0 -do - if [ ! -f $1 ] - then - echo $0": '"$1"' is not a file." 2>/dev/null - exit 1 - fi -# -# Extract all words from C/C++ language comments; add line -# numbers to aid in searching. -# -# NOTE: This has some limitations. For example, it prints -# non-comment text at the beginning of a comment line. -# - nl -ba $1 | awk '/\/\// {print $0}; /\/\*/ {comment=1; if(comment) print $0}; /\*\// {comment=0}' - shift -done diff --git a/scripts/find-unknown-comment-words b/scripts/find-unknown-comment-words deleted file mode 100755 index 028df0cc83..0000000000 --- a/scripts/find-unknown-comment-words +++ /dev/null @@ -1,143 +0,0 @@ -#!/bin/bash -# -# Locate unknown words in C/C++ comments. Uses "extract-comments" -# and "ablexicon" scripts. -# -set -o nounset -set -o pipefail -set -o errexit -set -f - -BLUE="\e[1;34m" -GREEN="\e[1;32m" -DEFAULTFG="\e[39m" - -function usage () { - echo "Find unknown words in C/C++ comments" - echo "" - echo "Usage:" - echo " ${0##*/} [options]" - echo "" - echo "Options:" - echo " -d, --directory directory to scan (defaults to .)" - echo " -l, --lexicon lexicon file (one word per line, default 'lexicon.txt')" - echo " -t, --terse terse output only (enabled if no lexicon available)" - echo " -h, --help display this help" - exit 1 -} - -# -# Verify that required commands are present -# -REQUIRED=( "extract-comments" "ablexicon" "getopt" ) -for i in "${REQUIRED[@]}" -do - command -v $i"" >/dev/null - if [ $? -ne "0" ] - then - echo "Can't find '"$i"' , exiting...">&2 - exit 1 - fi -done - -GETOPT_OUT=`getopt -o htd:l: --long help,terse,directory:,lexicon: -n "${0##*/}" -- "$@"` -if [ $? != 0 ] -then - echo "Exiting..." >&2 - exit 1 -fi - -eval set -- "$GETOPT_OUT" - -DIRNAME=/dev/fd/0 -LEXICON= -STATUS= -TERSE= -while true; do - case "$1" in - -h | --help ) usage $0 ;; - -t | --terse ) TERSE=1; shift ;; - -d | --directory ) DIRNAME="$2"; shift 2 ;; - -l | --lexicon ) LEXICON="$2"; shift 2 ;; - -- ) shift; break ;; - * ) break ;; - esac -done - -if [ ! -d $DIRNAME"" ] -then - echo "Invalid directory: "$DIRNAME - usage -fi - -if [ $LEXICON"" = "" ] -then - if [ -f $DIRNAME/lexicon.txt ] - then - LEXICON=$DIRNAME/lexicon.txt - else - LEXICON=/dev/null - TERSE=1 - fi -fi - -TMPFILE=${0##*/}-$USER-$RANDOM -unknowns=( "not-used" ) # get around empty array with nounset -extract-comments `find $DIRNAME -name \*.[ch]` | - tr [:upper:] [:lower:] | - grep -o -E '[a-zA-Z]+' | - ablexicon -l $LEXICON > $TMPFILE -readarray -O 1 -t unknowns < $TMPFILE -rm -f $TMPFILE - -for word in "${unknowns[@]}" -do - if [ $word"" == "not-used" ] - then - continue - fi - - if [ $TERSE"" != "" ] - then - echo $word - continue - fi - - for file in `find $DIRNAME -name \*.[ch]` - do - # Disable errexit here, extract-comments can return non-zero - set +e - # - # A little inefficient here; we will grep twice, once to detect - # the unknown word and another to print it with color highlighting. - # If there's a way to preserve ANSI color output with the first - # search and reuse it within the if statement (I gave up trying - # to find one after a few minutes), that would be nice. - # - extract-comments $file | grep -iw $word > /dev/null - if [ $? == "0" ] - then - if [ $STATUS"" != "1" ] - then - echo -e $GREEN"############################################################################"$DEFAULTFG - echo -e $GREEN"#"$DEFAULTFG - echo -e $GREEN"# Unknown word(s) found. Please either correct the spelling or add them"$DEFAULTFG - echo -e $GREEN"# to the lexicon file '"$LEXICON"'".$DEFAULTFG - echo -e $GREEN"#"$DEFAULTFG - echo -e $GREEN"############################################################################"$DEFAULTFG - STATUS=1 # Return non-zero status if any unidentified words are found - fi - echo "" - echo -e $BLUE$file$DEFAULTFG - echo "" - extract-comments $file | grep --color=always -iw $word | GREP_COLORS="mt=01;32" grep --color=always -E -e '^[ \t]*[0-9]+' - fi - # Re-enable errexit - set -o errexit - done -done - -if [ $STATUS"" = "1" ] -then - exit 1 -fi diff --git a/scripts/setup/ci_setup_linux.sh b/scripts/setup/ci_setup_linux.sh deleted file mode 100755 index 3932e40644..0000000000 --- a/scripts/setup/ci_setup_linux.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to set up the test environment on Linux. - -# Update repositories. -sudo apt-get update - -# Install OpenSSL if needed. -if [ "$NETWORK_STACK" = "openssl" ]; then - sudo apt-get install -y libssl-dev; -fi - -# Set up for pull request builds. -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - # Install Mosquitto for MQTT pull request builds. - if [ "$RUN_TEST" = "mqtt" ]; then - sudo apt-get install -y mosquitto; - fi - - # Install graphviz for documentation builds. - if [ "$RUN_TEST" = "doc" ]; then - sudo apt-get install -y graphviz; - fi - - # Install util-linux and spell for spelling checks. - if [ "$RUN_TEST" = "spelling" ]; then - sudo apt-get -y install util-linux # for gnu getopt - sudo apt-get -y install spell # for spell - fi - - # Install complexity for complexity checks. - if [ "$RUN_TEST" = "quality" ]; then - sudo apt-get -y install complexity - fi -# Set up for coverage builds. -else - # Install dependencies for Jobs tests. - # Coverage needs these too since it runs the Jobs tests. - if [ "$RUN_TEST" = "jobs" ] || [ "$RUN_TEST" = "coverage" ]; then - sudo apt-get install -y python3-setuptools python3-pip athena-jot; - pip3 install --user wheel; - pip3 install --user awscli; - fi - - # Install dependencies for coverage builds. - if [ "$RUN_TEST" = "coverage" ]; then - sudo apt-get install -y lcov; - fi -fi - -# Set default compiler options for Linux. Individual test scripts may override this. -export COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" diff --git a/scripts/uncrustify.cfg b/scripts/uncrustify.cfg deleted file mode 100644 index 0cb7d3fbd6..0000000000 --- a/scripts/uncrustify.cfg +++ /dev/null @@ -1,160 +0,0 @@ -# Uncrustify-0.67 -input_tab_size = 4 # unsigned number -output_tab_size = 4 # unsigned number -sp_arith = force # ignore/add/remove/force -sp_assign = force # ignore/add/remove/force -sp_assign_default = force # ignore/add/remove/force -sp_before_assign = force # ignore/add/remove/force -sp_after_assign = force # ignore/add/remove/force -sp_enum_assign = force # ignore/add/remove/force -sp_enum_before_assign = force # ignore/add/remove/force -sp_enum_after_assign = force # ignore/add/remove/force -sp_pp_stringify = add # ignore/add/remove/force -sp_bool = force # ignore/add/remove/force -sp_compare = force # ignore/add/remove/force -sp_inside_paren = force # ignore/add/remove/force -sp_paren_paren = force # ignore/add/remove/force -sp_paren_brace = force # ignore/add/remove/force -sp_before_ptr_star = force # ignore/add/remove/force -sp_before_unnamed_ptr_star = force # ignore/add/remove/force -sp_between_ptr_star = remove # ignore/add/remove/force -sp_after_ptr_star = force # ignore/add/remove/force -sp_before_byref = force # ignore/add/remove/force -sp_after_byref = remove # ignore/add/remove/force -sp_after_byref_func = remove # ignore/add/remove/force -sp_before_angle = remove # ignore/add/remove/force -sp_inside_angle = remove # ignore/add/remove/force -sp_after_angle = force # ignore/add/remove/force -sp_before_sparen = remove # ignore/add/remove/force -sp_inside_sparen = force # ignore/add/remove/force -sp_after_sparen = force # ignore/add/remove/force -sp_sparen_brace = force # ignore/add/remove/force -sp_before_semi_for = remove # ignore/add/remove/force -sp_before_semi_for_empty = add # ignore/add/remove/force -sp_after_semi_for_empty = force # ignore/add/remove/force -sp_before_square = remove # ignore/add/remove/force -sp_before_squares = remove # ignore/add/remove/force -sp_inside_square = force # ignore/add/remove/force -sp_after_comma = force # ignore/add/remove/force -sp_after_cast = force # ignore/add/remove/force -sp_inside_paren_cast = force # ignore/add/remove/force -sp_sizeof_paren = remove # ignore/add/remove/force -sp_inside_braces_enum = force # ignore/add/remove/force -sp_inside_braces_struct = force # ignore/add/remove/force -sp_inside_braces = force # ignore/add/remove/force -sp_inside_braces_empty = remove # ignore/add/remove/force -sp_type_func = force # ignore/add/remove/force -sp_func_proto_paren = remove # ignore/add/remove/force -sp_func_def_paren = remove # ignore/add/remove/force -sp_inside_fparens = remove # ignore/add/remove/force -sp_inside_fparen = force # ignore/add/remove/force -sp_fparen_brace = add # ignore/add/remove/force -sp_func_call_paren = remove # ignore/add/remove/force -sp_func_class_paren = remove # ignore/add/remove/force -sp_return_paren = remove # ignore/add/remove/force -sp_attribute_paren = remove # ignore/add/remove/force -sp_defined_paren = remove # ignore/add/remove/force -sp_macro = force # ignore/add/remove/force -sp_macro_func = force # ignore/add/remove/force -sp_brace_typedef = force # ignore/add/remove/force -sp_before_dc = remove # ignore/add/remove/force -sp_after_dc = remove # ignore/add/remove/force -sp_cond_colon = force # ignore/add/remove/force -sp_cond_question = force # ignore/add/remove/force -sp_case_label = force # ignore/add/remove/force -sp_endif_cmt = force # ignore/add/remove/force -sp_before_tr_emb_cmt = force # ignore/add/remove/force -sp_num_before_tr_emb_cmt = 1 # unsigned number -indent_columns = 4 # unsigned number -indent_with_tabs = 0 # unsigned number -indent_align_string = true # false/true -indent_class = true # false/true -indent_class_colon = true # false/true -indent_member = 3 # unsigned number -indent_switch_case = 4 # unsigned number -indent_case_brace = 3 # number -nl_assign_leave_one_liners = true # false/true -nl_class_leave_one_liners = true # false/true -nl_start_of_file = remove # ignore/add/remove/force -nl_end_of_file = force # ignore/add/remove/force -nl_end_of_file_min = 1 # unsigned number -nl_assign_brace = add # ignore/add/remove/force -nl_func_var_def_blk = 1 # unsigned number -nl_fcall_brace = add # ignore/add/remove/force -nl_enum_brace = force # ignore/add/remove/force -nl_struct_brace = force # ignore/add/remove/force -nl_union_brace = force # ignore/add/remove/force -nl_if_brace = add # ignore/add/remove/force -nl_brace_else = add # ignore/add/remove/force -nl_else_brace = add # ignore/add/remove/force -nl_getset_brace = force # ignore/add/remove/force -nl_for_brace = add # ignore/add/remove/force -nl_while_brace = add # ignore/add/remove/force -nl_do_brace = add # ignore/add/remove/force -nl_switch_brace = add # ignore/add/remove/force -nl_multi_line_define = true # false/true -nl_before_case = true # false/true -nl_after_case = true # false/true -nl_func_type_name = remove # ignore/add/remove/force -nl_func_proto_type_name = remove # ignore/add/remove/force -nl_func_paren = remove # ignore/add/remove/force -nl_func_def_paren = remove # ignore/add/remove/force -nl_func_decl_start = remove # ignore/add/remove/force -nl_func_def_start = remove # ignore/add/remove/force -nl_func_decl_args = add # ignore/add/remove/force -nl_func_def_args = add # ignore/add/remove/force -nl_func_decl_end = remove # ignore/add/remove/force -nl_func_def_end = remove # ignore/add/remove/force -nl_fdef_brace = add # ignore/add/remove/force -nl_after_semicolon = true # false/true -nl_after_brace_open = true # false/true -nl_after_brace_close = true # false/true -nl_squeeze_ifdef = true # false/true -nl_before_if = force # ignore/add/remove/force -nl_after_if = force # ignore/add/remove/force -nl_before_for = force # ignore/add/remove/force -nl_after_for = force # ignore/add/remove/force -nl_before_while = force # ignore/add/remove/force -nl_after_while = force # ignore/add/remove/force -nl_before_switch = force # ignore/add/remove/force -nl_after_switch = force # ignore/add/remove/force -nl_before_do = force # ignore/add/remove/force -nl_after_do = force # ignore/add/remove/force -nl_max = 4 # unsigned number -nl_after_func_proto_group = 1 # unsigned number -nl_after_func_body_class = 2 # unsigned number -nl_before_block_comment = 2 # unsigned number -eat_blanks_after_open_brace = true # false/true -eat_blanks_before_close_brace = true # false/true -nl_after_return = true # false/true -pos_bool = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force -align_var_def_amp_style = 1 # unsigned number -align_var_def_thresh = 16 # unsigned number -align_assign_thresh = 12 # unsigned number -align_struct_init_span = 3 # unsigned number -align_typedef_gap = 3 # unsigned number -align_typedef_span = 5 # unsigned number -align_typedef_star_style = 1 # unsigned number -align_typedef_amp_style = 1 # unsigned number -align_right_cmt_span = 3 # unsigned number -align_nl_cont = true # false/true -align_pp_define_gap = 4 # unsigned number -align_pp_define_span = 3 # unsigned number -cmt_cpp_to_c = true # false/true -cmt_star_cont = true # false/true -mod_full_brace_do = add # ignore/add/remove/force -mod_full_brace_for = add # ignore/add/remove/force -mod_full_brace_if = add # ignore/add/remove/force -mod_full_brace_while = add # ignore/add/remove/force -mod_full_paren_if_bool = true # false/true -mod_remove_extra_semicolon = true # false/true -mod_add_long_ifdef_endif_comment = 10 # unsigned number -mod_add_long_ifdef_else_comment = 10 # unsigned number -mod_case_brace = remove # ignore/add/remove/force -mod_remove_empty_return = true # false/true -pp_indent = force # ignore/add/remove/force -pp_indent_at_level = true # false/true -pp_indent_count = 4 # unsigned number -pp_space = remove # ignore/add/remove/force -pp_if_indent_code = true # false/true -# option(s) with 'not default' value: 158 diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 409789a5d6..0000000000 --- a/tests/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Test programs - -This directory contains source files for the test executables. The test executable source (i.e. the `main()` function) is `iot_tests.c` and the configuration header used when building the tests is `iot_config.h`. The configuration header is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. - -The test code sources are placed with their libraries and not in this directory. For example, the sources for the MQTT tests are in `libraries/standard/mqtt/test`. - -For information on building and running the tests, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html). diff --git a/tests/iot_config.h b/tests/iot_config.h deleted file mode 100644 index 2d0a9cb8a1..0000000000 --- a/tests/iot_config.h +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* This file contains configuration settings for the tests. */ - -#ifndef IOT_CONFIG_H_ -#define IOT_CONFIG_H_ - -/* The build system will choose the appropriate system types file for the platform - * layer based on the host operating system. */ -#include IOT_SYSTEM_TYPES_FILE - -/* Test framework include. */ -#include "unity_fixture_malloc_overrides.h" - -/* MQTT server endpoints used for the tests. */ -#if IOT_TEST_MQTT_MOSQUITTO == 1 - /* Mosquitto test server. */ - #define IOT_TEST_SECURED_CONNECTION ( 0 ) - - #ifndef IOT_TEST_SERVER - #define IOT_TEST_SERVER "test.mosquitto.org" - #endif - #ifndef IOT_TEST_PORT - #define IOT_TEST_PORT ( 1883 ) - #endif -#else - /* AWS IoT MQTT server. */ - #define IOT_TEST_SECURED_CONNECTION ( 1 ) - - /* AWS IoT endpoint and credentials. */ - #ifndef IOT_TEST_SERVER - #define IOT_TEST_SERVER "" - #endif - #ifndef IOT_TEST_PORT - #define IOT_TEST_PORT ( 443 ) - #endif - #ifndef IOT_TEST_ROOT_CA - #define IOT_TEST_ROOT_CA "" - #endif - #ifndef IOT_TEST_CLIENT_CERT - #define IOT_TEST_CLIENT_CERT "" - #endif - #ifndef IOT_TEST_PRIVATE_KEY - #define IOT_TEST_PRIVATE_KEY "" - #endif - #ifndef IOT_TEST_USER_NAME - #define IOT_TEST_USER_NAME "" - #endif - #ifndef IOT_TEST_PASSWORD - #define IOT_TEST_PASSWORD "" - #endif -#endif /* if IOT_TEST_MQTT_MOSQUITTO == 1 */ - -/* Shadow tests configuration. */ -#ifndef AWS_IOT_TEST_SHADOW_THING_NAME - #define AWS_IOT_TEST_SHADOW_THING_NAME "" -#endif - -/* Jobs tests configuration. */ -#ifndef AWS_IOT_TEST_JOBS_THING_NAME - #define AWS_IOT_TEST_JOBS_THING_NAME "" -#endif - -/* Defender tests configuration. */ -#ifndef AWS_IOT_TEST_DEFENDER_THING_NAME - #define AWS_IOT_TEST_DEFENDER_THING_NAME "" -#endif - -/* Log level for testing the demos. */ -#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO - -/* Set the equivalent demo defines. */ -#ifdef IOT_TEST_SECURED_CONNECTION - #define IOT_DEMO_SECURED_CONNECTION IOT_TEST_SECURED_CONNECTION -#endif -#ifdef IOT_TEST_SERVER - #define IOT_DEMO_SERVER IOT_TEST_SERVER -#endif -#ifdef IOT_TEST_PORT - #define IOT_DEMO_PORT IOT_TEST_PORT -#endif -#ifdef IOT_TEST_ROOT_CA - #define IOT_DEMO_ROOT_CA IOT_TEST_ROOT_CA -#endif -#ifdef IOT_TEST_CLIENT_CERT - #define IOT_DEMO_CLIENT_CERT IOT_TEST_CLIENT_CERT -#endif -#ifdef IOT_TEST_PRIVATE_KEY - #define IOT_DEMO_PRIVATE_KEY IOT_TEST_PRIVATE_KEY -#endif -#ifdef IOT_TEST_USER_NAME - #define IOT_DEMO_USER_NAME IOT_TEST_USER_NAME -#endif -#ifdef IOT_TEST_PASSWORD - #define IOT_DEMO_PASSWORD IOT_TEST_PASSWORD -#endif -#if defined( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) - #define IOT_DEMO_IDENTIFIER IOT_TEST_MQTT_CLIENT_IDENTIFIER -#elif defined( AWS_IOT_TEST_SHADOW_THING_NAME ) - #define IOT_DEMO_IDENTIFIER AWS_IOT_TEST_SHADOW_THING_NAME -#endif - -/* Enable asserts in the libraries. */ -#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) -#define IOT_MQTT_ENABLE_ASSERTS ( ! IOT_TEST_COVERAGE ) -#define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) -#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_JOBS_ENABLE_ASSERTS ( 1 ) - -/* MQTT library configuration. */ -#define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) - -/* Defender library configuration. */ -#define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) - -/* Allow the use of multiple Jobs callbacks. */ -#define AWS_IOT_JOBS_NOTIFY_CALLBACKS ( 4 ) - -/* Static memory resource settings for the tests. These values must be large - * enough to support the stress tests. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #define IOT_MESSAGE_BUFFERS ( 16 ) - #define IOT_MQTT_CONNECTIONS ( 2 ) - #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) - #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) - #define IOT_TASKPOOLS ( 4 ) -#endif - -/* Default assert function. */ -#include -#define Iot_DefaultAssert assert - -/* Memory allocation function configuration. Note that these functions will not - * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define IotThreads_Malloc unity_malloc_mt -#define IotThreads_Free unity_free_mt -#define IotNetwork_Malloc unity_malloc_mt -#define IotNetwork_Free unity_free_mt -#define IotLogging_Malloc unity_malloc_mt -#define IotLogging_Free unity_free_mt -/* #define IotLogging_StaticBufferSize */ -#define IotTest_Malloc unity_malloc_mt -#define IotTest_Free unity_free_mt - -/* Memory allocation function configuration for libraries affected by - * IOT_STATIC_MEMORY_ONLY. */ -#if IOT_STATIC_MEMORY_ONLY == 0 - #define Iot_DefaultMalloc unity_malloc_mt - #define Iot_DefaultFree unity_free_mt -#endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ - -/* Choose the appropriate network abstraction implementation. */ -#if IOT_NETWORK_USE_OPENSSL == 1 - /* OpenSSL network include. */ - #define IOT_TEST_NETWORK_HEADER "iot_network_openssl.h" - - #define IOT_TEST_ALPN_PROTOS "\x0ex-amzn-mqtt-ca" - #define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL - - #define IotTestNetwork_Init IotNetworkOpenssl_Init - #define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup -#else - /* mbed TLS network include. */ - #define IOT_TEST_NETWORK_HEADER "iot_network_mbedtls.h" - - #define IOT_TEST_ALPN_PROTOS "x-amzn-mqtt-ca" - #define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS - - #define IotTestNetwork_Init IotNetworkMbedtls_Init - #define IotTestNetwork_Cleanup IotNetworkMbedtls_Cleanup -#endif - -/* Initializers for the tests' network types. */ -#define IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER \ - { \ - .pHostName = IOT_TEST_SERVER, \ - .port = IOT_TEST_PORT \ - } - -#if IOT_TEST_SECURED_CONNECTION == 1 - #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER \ - { \ - .pAlpnProtos = IOT_TEST_ALPN_PROTOS, \ - .pRootCa = IOT_TEST_ROOT_CA, \ - .pClientCert = IOT_TEST_CLIENT_CERT, \ - .pPrivateKey = IOT_TEST_PRIVATE_KEY, \ - .pUserName = IOT_TEST_USER_NAME, \ - .pPassword = IOT_TEST_PASSWORD \ - } -#else - #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER { 0 } -#endif - -/* Configure code coverage testing if enabled. */ -#if IOT_TEST_COVERAGE == 1 - #ifndef __GNUC__ - #error "Unsupported compiler. Only gcc and clang are supported for coverage." - #endif - - /* Define a custom logging puts function. This function allows coverage - * testing of logging functions, but prevents excessive logs from being - * printed. */ - #define IotLogging_Puts _coveragePuts - - /* Includes for coverage logging puts. */ - #include - #include - #include - - /* Logging output function that only prints messages from demo executables. - * May be unused, hence the gcc unused attribute (not portable!) */ - static int __attribute__( ( unused ) ) _coveragePuts( const char * pMessage ) - { - bool printMessage = false; - - /* Name of this executable, available through glibc (not portable!) */ - extern const char * __progname; - - /* Check if this is a demo executable. */ - if( strstr( __progname, "demo" ) != NULL ) - { - /* Always print messages from the demo executables. */ - if( strstr( pMessage, "[DEMO]" ) != NULL ) - { - printMessage = true; - } - /* Always print errors in demo executables. */ - else if( strstr( pMessage, "[ERROR]" ) != NULL ) - { - printMessage = true; - } - /* Always print warnings in demo executables. */ - else if( strstr( pMessage, "[WARN ]" ) != NULL ) - { - printMessage = true; - } - } - - if( printMessage == true ) - { - puts( pMessage ); - } - - /* Puts should return a nonzero value. */ - return 1; - } -#endif /* if IOT_TEST_COVERAGE == 1 */ - -#endif /* ifndef IOT_CONFIG_H_ */ diff --git a/tests/iot_tests.c b/tests/iot_tests.c deleted file mode 100644 index d284071634..0000000000 --- a/tests/iot_tests.c +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests.c - * @brief Common test runner. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Error handling include. */ -#include "iot_error.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/*-----------------------------------------------------------*/ - -/* This file calls a generic placeholder test runner function. The build system selects - * the actual function by defining it. When testing demos applications, the demo main - * function is called. */ -#if IOT_TEST_DEMO == 1 - extern int DemoMain( int argc, - char ** argv ); -#else - extern void RunTests( bool disableNetworkTests, - bool disableLongTests ); -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Used to provide unity_malloc with critical sections. - */ -static IotMutex_t _unityMallocMutex; - -/*-----------------------------------------------------------*/ - -/** - * @brief Enter a critical section for unity_malloc. - */ -static void IotTest_EnterCritical( void ) -{ - IotMutex_Lock( &_unityMallocMutex ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Exit a critical section for unity_malloc. - */ -static void IotTest_ExitCritical( void ) -{ - IotMutex_Unlock( &_unityMallocMutex ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Parses command line arguments. - * - * @param[in] argc Number of arguments passed to main(). - * @param[in] argv Arguments vector passed to main(). - * @param[out] pDisableNetworkTests Set to `true` if `-n` is given, `false` otherwise. - * @param[out] pDisableLongTests Set to `true` if `-l` is not given, `true` otherwise. - */ -#if IOT_TEST_DEMO != 1 - static void _parseArguments( int argc, - char ** argv, - bool * pDisableNetworkTests, - bool * pDisableLongTests ) - { - int i = 1; - const char * pOption = NULL; - size_t optionLength = 0; - - /* Set default values. */ - *pDisableNetworkTests = false; - *pDisableLongTests = true; - - for( i = 1; i < argc; i++ ) - { - /* Get argument string and length. */ - pOption = argv[ i ]; - optionLength = strlen( pOption ); - - /* Valid options have the format "-X", so they must be 2 characters long. */ - if( optionLength != 2 ) - { - continue; - } - - /* The first character of a valid option must be '-'. */ - if( pOption[ 0 ] != '-' ) - { - continue; - } - - switch( pOption[ 1 ] ) - { - /* Disable tests requiring network if -n is given. */ - case 'n': - *pDisableNetworkTests = true; - break; - - /* Enable long tests if -l is given. */ - case 'l': - *pDisableLongTests = false; - break; - - default: - break; - } - } - } -#endif /* if IOT_TEST_DEMO == 1 */ - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - bool mallocMutexCreated = false; - - /* Create the mutex that guards the Unity malloc overrides. */ - mallocMutexCreated = IotMutex_Create( &_unityMallocMutex, false ); - - if( mallocMutexCreated == false ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Provide Unity with functions for critical sections. */ - unity_provide_critical_section( IotTest_EnterCritical, IotTest_ExitCritical ); - - /* This file is also used to test the demos. When testing demos, the demo main - * function is called. */ - #if IOT_TEST_DEMO == 1 - status = DemoMain( argc, argv ); - #else - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - /* Parse command-line arguments for the tests. */ - bool disableNetworkTests = false, disableLongTests = false; - _parseArguments( argc, argv, &disableNetworkTests, &disableLongTests ); - - /* Call the test runner function. */ - RunTests( disableNetworkTests, disableLongTests ); - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - #endif /* if IOT_TEST_DEMO == 1 */ - - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( mallocMutexCreated == true ) - { - IotMutex_Destroy( &_unityMallocMutex ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ diff --git a/tests/lexicon.txt b/tests/lexicon.txt deleted file mode 100644 index 70ac516544..0000000000 --- a/tests/lexicon.txt +++ /dev/null @@ -1,15 +0,0 @@ -aws -config -endif -glibc -ifndef -iot -iotlogging -malloc -mbed -mosquitto -mqtt -mutex -openssl -staticbuffersize -tls diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt deleted file mode 100644 index 14158457af..0000000000 --- a/third_party/mbedtls/CMakeLists.txt +++ /dev/null @@ -1,98 +0,0 @@ -# Check if the mbed TLS source directory exists. -if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/library/ ) - # Attempt to clone the mbed TLS submodule. - if( ${IOT_BUILD_CLONE_SUBMODULES} ) - find_package( Git REQUIRED ) - - message( "Cloning submodule mbed TLS." ) - execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init third_party/mbedtls/mbedtls - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE MBEDTLS_CLONE_RESULT ) - - if( NOT ${MBEDTLS_CLONE_RESULT} STREQUAL "0" ) - message( FATAL_ERROR "Failed to clone mbed TLS submodule." ) - endif() - else() - message( FATAL_ERROR "The required submodule mbed TLS does not exist. Either clone it manually, or set IOT_BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) - endif() -endif() - -# mbed TLS source files. -set( MBEDTLS_SOURCES - mbedtls/library/aes.c - mbedtls/library/aesni.c - mbedtls/library/asn1parse.c - mbedtls/library/asn1write.c - mbedtls/library/base64.c - mbedtls/library/bignum.c - mbedtls/library/ecdh.c - mbedtls/library/ecdsa.c - mbedtls/library/ecp.c - mbedtls/library/ecp_curves.c - mbedtls/library/entropy.c - mbedtls/library/entropy_poll.c - mbedtls/library/error.c - mbedtls/library/cipher.c - mbedtls/library/cipher_wrap.c - mbedtls/library/ctr_drbg.c - mbedtls/library/hmac_drbg.c - mbedtls/library/md.c - mbedtls/library/md_wrap.c - mbedtls/library/net_sockets.c - mbedtls/library/oid.c - mbedtls/library/pem.c - mbedtls/library/pk.c - mbedtls/library/pkparse.c - mbedtls/library/pk_wrap.c - mbedtls/library/platform.c - mbedtls/library/platform_util.c - mbedtls/library/rsa.c - mbedtls/library/rsa_internal.c - mbedtls/library/sha256.c - mbedtls/library/sha512.c - mbedtls/library/ssl_ciphersuites.c - mbedtls/library/ssl_cli.c - mbedtls/library/ssl_tls.c - mbedtls/library/timing.c - mbedtls/library/threading.c - mbedtls/library/x509.c - mbedtls/library/x509_crt.c ) - -# mbed TLS headers (for folder organization only). -file( GLOB MBEDTLS_HEADERS "mbedtls/include/mbedtls/*.h" ) - -# mbed TLS library target. -add_library( mbedtls - ${MBEDTLS_SOURCES} ${MBEDTLS_HEADERS} - iot_config_mbedtls.h threading_alt.h ) - -# mbed TLS config header and include directories. -target_include_directories( mbedtls SYSTEM - PUBLIC mbedtls/include . ) -target_compile_definitions( mbedtls - PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) - -# The system types header is needed for the mbed TLS threading port. -target_include_directories( mbedtls - PRIVATE ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME}/include ) -target_compile_definitions( mbedtls - PRIVATE -DIOT_SYSTEM_TYPES_FILE="iot_platform_types_${IOT_PLATFORM_NAME}.h" ) - -# Link the Unity test framework when testing. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( mbedtls PRIVATE unity ) -endif() - -# Disable all warnings for this third-party library. -target_compile_options( mbedtls - PRIVATE - $<$,$,$>: - -w> - $<$: - /W0 /D_CRT_SECURE_NO_WARNINGS> ) - -# Organization of mbed TLS in folders. -set_property( TARGET mbedtls PROPERTY FOLDER third_party ) -source_group( include\\mbedtls FILES ${MBEDTLS_HEADERS} ) -source_group( source FILES ${MBEDTLS_SOURCES} ) -source_group( "" FILES iot_config_mbedtls.h threading_alt.h iot_mbedtls_threading.c ) diff --git a/third_party/mbedtls/iot_config_mbedtls.h b/third_party/mbedtls/iot_config_mbedtls.h deleted file mode 100644 index fa9fb0c805..0000000000 --- a/third_party/mbedtls/iot_config_mbedtls.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* This file configures the mbed TLS library built with this SDK. */ - -#ifndef IOT_CONFIG_MBEDTLS_H_ -#define IOT_CONFIG_MBEDTLS_H_ - -/* System support for assembly and time. */ -#define MBEDTLS_HAVE_ASM -#define MBEDTLS_HAVE_TIME -#define MBEDTLS_HAVE_TIME_DATE - -/* Remove deprecated functions to prevent their use. */ -#define MBEDTLS_DEPRECATED_REMOVED - -/* Enabled block cipher modes of operation. */ -#define MBEDTLS_CIPHER_MODE_CBC -#define MBEDTLS_CIPHER_MODE_CFB -#define MBEDTLS_CIPHER_MODE_CTR -#define MBEDTLS_CIPHER_MODE_OFB -#define MBEDTLS_CIPHER_MODE_XTS - -/* Enabled block cipher padding modes. */ -#define MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS -#define MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN -#define MBEDTLS_CIPHER_PADDING_ZEROS - -/* Disable weak cipher suites. */ -#define MBEDTLS_REMOVE_ARC4_CIPHERSUITES -#define MBEDTLS_REMOVE_3DES_CIPHERSUITES - -/* Enabled eliptic curves. */ -#define MBEDTLS_ECP_DP_SECP192R1_ENABLED -#define MBEDTLS_ECP_DP_SECP224R1_ENABLED -#define MBEDTLS_ECP_DP_SECP256R1_ENABLED -#define MBEDTLS_ECP_DP_SECP384R1_ENABLED -#define MBEDTLS_ECP_DP_SECP521R1_ENABLED -#define MBEDTLS_ECP_DP_SECP192K1_ENABLED -#define MBEDTLS_ECP_DP_SECP224K1_ENABLED -#define MBEDTLS_ECP_DP_SECP256K1_ENABLED -#define MBEDTLS_ECP_DP_BP256R1_ENABLED -#define MBEDTLS_ECP_DP_BP384R1_ENABLED -#define MBEDTLS_ECP_DP_BP512R1_ENABLED -#define MBEDTLS_ECP_DP_CURVE25519_ENABLED -#define MBEDTLS_ECP_DP_CURVE448_ENABLED - -/* Enable NIST curves optimization and deterministic ECDSA. */ -#define MBEDTLS_ECP_NIST_OPTIM -#define MBEDTLS_ECDSA_DETERMINISTIC - -/* Enable TLS cipher suites supported by AWS IoT. */ -#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED -#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED - -/* Enable use of the filesystem. */ -#define MBEDTLS_FS_IO - -/* Enable Encrypt-then-MAC and Extended Master Secret. */ -#define MBEDTLS_SSL_ENCRYPT_THEN_MAC -#define MBEDTLS_SSL_EXTENDED_MASTER_SECRET - -/* Enable hardware acceleration in the SSL module. */ -#define MBEDTLS_SSL_HW_RECORD_ACCEL - -/* Enable SSL max fragment length, ALPN, and SNI. */ -#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH -#define MBEDTLS_SSL_ALPN -#define MBEDTLS_SSL_SERVER_NAME_INDICATION - -/* Enable TLS v1.2 only. */ -#define MBEDTLS_SSL_PROTO_TLS1_2 - -/* Enable verification of key usage and extended key usage. */ -#define MBEDTLS_X509_CHECK_KEY_USAGE -#define MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE - -/* Use x86 AES-NI instructions. */ -#define MBEDTLS_AESNI_C - -/* Enabled mbed TLS modules. */ -#define MBEDTLS_ASN1_PARSE_C -#define MBEDTLS_ASN1_WRITE_C -#define MBEDTLS_AES_C -#define MBEDTLS_BASE64_C -#define MBEDTLS_BIGNUM_C -#define MBEDTLS_CIPHER_C -#define MBEDTLS_CTR_DRBG_C -#define MBEDTLS_ECDSA_C -#define MBEDTLS_ECDH_C -#define MBEDTLS_ECP_C -#define MBEDTLS_ENTROPY_C -#define MBEDTLS_ERROR_C -#define MBEDTLS_HMAC_DRBG_C -#define MBEDTLS_MD_C -#define MBEDTLS_NET_C -#define MBEDTLS_OID_C -#define MBEDTLS_PEM_PARSE_C -#define MBEDTLS_PK_C -#define MBEDTLS_PK_PARSE_C -#define MBEDTLS_PKCS1_V15 -#define MBEDTLS_PLATFORM_C -#define MBEDTLS_RSA_C -#define MBEDTLS_SHA256_C -#define MBEDTLS_SHA512_C -#define MBEDTLS_SSL_CLI_C -#define MBEDTLS_SSL_TLS_C -#define MBEDTLS_TIMING_C -#define MBEDTLS_X509_USE_C -#define MBEDTLS_X509_CRT_PARSE_C - -/* Use platform mutexes in mbed TLS. */ -#define MBEDTLS_THREADING_C -#define MBEDTLS_THREADING_ALT - -/* Validate mbed TLS configuration. */ -#include "mbedtls/check_config.h" - -#endif diff --git a/third_party/mbedtls/mbedtls b/third_party/mbedtls/mbedtls deleted file mode 160000 index 0fce215851..0000000000 --- a/third_party/mbedtls/mbedtls +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0fce215851cc069c5b5def12fcc18725055fa6cf diff --git a/third_party/mbedtls/threading_alt.h b/third_party/mbedtls/threading_alt.h deleted file mode 100644 index 84f8abab79..0000000000 --- a/third_party/mbedtls/threading_alt.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* This file declares mutex functions for mbed TLS using platform mutexes. */ - -#ifndef THREADING_ALT_H_ -#define THREADING_ALT_H_ - -/* Standard includes. */ -#include - -/* System types include. */ -#include IOT_SYSTEM_TYPES_FILE - -/* Represents a mutex used by mbed TLS. */ -typedef struct mbedtls_threading_mutex -{ - /* Whether this mutex is valid. */ - bool valid; - /* The wrapped platform mutex. */ - _IotSystemMutex_t mutex; -} mbedtls_threading_mutex_t; - -#endif /* ifndef THREADING_ALT_H_ */ diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt deleted file mode 100644 index 4eaadb1823..0000000000 --- a/third_party/tinycbor/CMakeLists.txt +++ /dev/null @@ -1,62 +0,0 @@ -# Check if the TinyCBOR source directory exists. -if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src/ ) - # Attempt to clone the TinyCBOR submodule. - if( ${IOT_BUILD_CLONE_SUBMODULES} ) - find_package( Git REQUIRED ) - - message( "Cloning submodule TinyCBOR." ) - execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init third_party/tinycbor/tinycbor - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE TINYCBOR_CLONE_RESULT ) - - if( NOT ${TINYCBOR_CLONE_RESULT} STREQUAL "0" ) - message( FATAL_ERROR "Failed to clone TinyCBOR submodule." ) - endif() - else() - message( FATAL_ERROR "The required submodule TinyCBOR does not exist. Either clone it manually, or set IOT_BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) - endif() -endif() - -# TinyCBOR library source files. -set( TINYCBOR_SOURCES - tinycbor/src/cborencoder.c - tinycbor/src/cborencoder_close_container_checked.c - tinycbor/src/cborerrorstrings.c - tinycbor/src/cborparser.c - tinycbor/src/cborparser_dup_string.c - tinycbor/src/cborpretty.c - tinycbor/src/cborpretty_stdio.c ) - -# TinyCBOR headers (for folder organization only). -file( GLOB TINYCBOR_HEADERS "tinycbor/src/*.h" ) - -# TinyCBOR library target. -add_library( tinycbor - ${TINYCBOR_SOURCES} ${TINYCBOR_HEADERS} ) - -# TinyCBOR include path. -target_include_directories( tinycbor SYSTEM - PUBLIC tinycbor/src ) - - -# Link libm (required for math functions on some systems) if available. -find_library( LIB_MATH m ) - -if( NOT ${LIB_MATH} STREQUAL "LIB_MATH-NOTFOUND" ) - target_link_libraries( tinycbor PRIVATE m ) -endif() - -unset( LIB_MATH CACHE ) - -# Disable all warnings for this third-party library. -target_compile_options( tinycbor - PRIVATE - $<$,$,$>: - -w> - $<$: - /W0 /D_CRT_SECURE_NO_WARNINGS> ) - -# Organization of TinyCBOR in folders. -set_property( TARGET tinycbor PROPERTY FOLDER third_party ) -source_group( include FILES ${TINYCBOR_HEADERS} ) -source_group( source FILES ${TINYCBOR_SOURCES} ) diff --git a/third_party/tinycbor/tinycbor b/third_party/tinycbor/tinycbor deleted file mode 160000 index d94ca09aa9..0000000000 --- a/third_party/tinycbor/tinycbor +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d94ca09aa91f5b3c581527aa8bca179a82b79874 diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt deleted file mode 100644 index a7ef3c59c4..0000000000 --- a/third_party/unity/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# Unity test framework source files. -set( UNITY_SOURCES - unity/unity.c - unity/fixture/unity_fixture.c - unity/fixture/unity_memory_mt.c ) - -# Unity test framework headers (for folder organization only). -set( UNITY_HEADERS - unity/unity.h - unity/unity_internals.h - unity/fixture/unity_fixture.h - unity/fixture/unity_fixture_internals.h - unity/fixture/unity_fixture_malloc_overrides.h ) - -# Unity test framework target. -add_library( unity - ${UNITY_SOURCES} ${UNITY_HEADERS} ) - -# Unity test framework include paths. -target_include_directories( unity SYSTEM - PUBLIC unity - PUBLIC unity/fixture ) - -# Disable all warnings for this third-party library. -target_compile_options( unity - PRIVATE - $<$,$,$>: - -w> - $<$: - /W0 /D_CRT_SECURE_NO_WARNINGS> ) - -# Organization of Unity in folders. -set_property( TARGET unity PROPERTY FOLDER third_party ) -source_group( include FILES ${UNITY_HEADERS} ) -source_group( source FILES ${UNITY_SOURCES} ) diff --git a/third_party/unity/unity/fixture/unity_fixture.c b/third_party/unity/unity/fixture/unity_fixture.c deleted file mode 100644 index 725e44b996..0000000000 --- a/third_party/unity/unity/fixture/unity_fixture.c +++ /dev/null @@ -1,472 +0,0 @@ -/* Copyright (c) 2010 James Grenning and Contributed to Unity Project - * ========================================== - * Unity Project - A Test Framework for C - * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams - * [Released under MIT License. Please refer to license.txt for details] - * ========================================== */ - -#include "unity_fixture.h" -#include "unity_internals.h" -#include - -struct UNITY_FIXTURE_T UnityFixture; - -/* If you decide to use the function pointer approach. - * Build with -D UNITY_OUTPUT_CHAR=outputChar and include - * int (*outputChar)(int) = putchar; */ - -#if !defined(UNITY_WEAK_ATTRIBUTE) && !defined(UNITY_WEAK_PRAGMA) -void setUp(void) { /*does nothing*/ } -void tearDown(void) { /*does nothing*/ } -#endif - -static void announceTestRun(unsigned int runNumber) -{ - UnityPrint("Unity test run "); - UnityPrintNumberUnsigned(runNumber+1); - UnityPrint(" of "); - UnityPrintNumberUnsigned(UnityFixture.RepeatCount); - UNITY_PRINT_EOL(); -} - -int UnityMain(int argc, const char* argv[], void (*runAllTests)(void)) -{ - int result = UnityGetCommandLineOptions(argc, argv); - unsigned int r; - if (result != 0) - return result; - - for (r = 0; r < UnityFixture.RepeatCount; r++) - { - UnityBegin(argv[0]); - announceTestRun(r); - runAllTests(); - if (!UnityFixture.Verbose) UNITY_PRINT_EOL(); - UnityEnd(); - } - - return (int)Unity.TestFailures; -} - -static int selected(const char* filter, const char* name) -{ - if (filter == 0) - return 1; - return strstr(name, filter) ? 1 : 0; -} - -static int testSelected(const char* test) -{ - return selected(UnityFixture.NameFilter, test); -} - -static int groupSelected(const char* group) -{ - return selected(UnityFixture.GroupFilter, group); -} - -void UnityTestRunner(unityfunction* setup, - unityfunction* testBody, - unityfunction* teardown, - const char* printableName, - const char* group, - const char* name, - const char* file, - unsigned int line) -{ - if (testSelected(name) && groupSelected(group)) - { - Unity.TestFile = file; - Unity.CurrentTestName = printableName; - Unity.CurrentTestLineNumber = line; - if (!UnityFixture.Verbose) - UNITY_OUTPUT_CHAR('.'); - else - { - UnityPrint(printableName); - #ifndef UNITY_REPEAT_TEST_NAME - Unity.CurrentTestName = NULL; - #endif - } - - Unity.NumberOfTests++; - UnityMalloc_StartTest(); - UnityPointer_Init(); - - if (TEST_PROTECT()) - { - setup(); - testBody(); - } - if (TEST_PROTECT()) - { - teardown(); - } - if (TEST_PROTECT()) - { - UnityPointer_UndoAllSets(); - if (!Unity.CurrentTestFailed) - UnityMalloc_EndTest(); - } - UnityConcludeFixtureTest(); - } -} - -void UnityIgnoreTest(const char* printableName, const char* group, const char* name) -{ - if (testSelected(name) && groupSelected(group)) - { - Unity.NumberOfTests++; - Unity.TestIgnores++; - if (!UnityFixture.Verbose) - UNITY_OUTPUT_CHAR('!'); - else - { - UnityPrint(printableName); - UNITY_PRINT_EOL(); - } - } -} - - -/*------------------------------------------------- */ -/* Malloc and free stuff */ -#define MALLOC_DONT_FAIL -1 -static int malloc_count; -static int malloc_fail_countdown = MALLOC_DONT_FAIL; - -void UnityMalloc_StartTest(void) -{ - unity_enter_critical_section(); - malloc_count = 0; - malloc_fail_countdown = MALLOC_DONT_FAIL; - unity_exit_critical_section(); -} - -void UnityMalloc_EndTest(void) -{ - unity_enter_critical_section(); - malloc_fail_countdown = MALLOC_DONT_FAIL; - if (malloc_count != 0) - { - unity_exit_critical_section(); - UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "This test leaks!"); - } - - unity_exit_critical_section(); -} - -void UnityMalloc_MakeMallocFailAfterCount(int countdown) -{ - unity_enter_critical_section(); - malloc_fail_countdown = countdown; - unity_exit_critical_section(); -} - -bool UnityMalloc_AllocateResource(void) -{ - bool status = true; - - unity_enter_critical_section(); - - switch(malloc_fail_countdown) - { - case MALLOC_DONT_FAIL: - malloc_count++; - break; - case 0: - status = false; - break; - default: - malloc_count++; - malloc_fail_countdown--; - break; - } - - unity_exit_critical_section(); - - return status; -} - -void UnityMalloc_FreeResource(void) -{ - unity_enter_critical_section(); - malloc_count--; - unity_exit_critical_section(); -} - -/* These definitions are always included from unity_fixture_malloc_overrides.h */ -/* We undef to use them or avoid conflict with per the C standard */ -#undef malloc -#undef free -#undef calloc -#undef realloc - -#ifdef UNITY_EXCLUDE_STDLIB_MALLOC -static unsigned char unity_heap[UNITY_INTERNAL_HEAP_SIZE_BYTES]; -static size_t heap_index; -#else -#include -#endif - -typedef struct GuardBytes -{ - size_t size; - size_t guard_space; -} Guard; - - -static const char end[] = "END"; - -void* unity_malloc(size_t size) -{ - char* mem; - Guard* guard; - size_t total_size = size + sizeof(Guard) + sizeof(end); - - if (malloc_fail_countdown != MALLOC_DONT_FAIL) - { - if (malloc_fail_countdown == 0) - return NULL; - malloc_fail_countdown--; - } - - if (size == 0) return NULL; -#ifdef UNITY_EXCLUDE_STDLIB_MALLOC - if (heap_index + total_size > UNITY_INTERNAL_HEAP_SIZE_BYTES) - { - guard = NULL; - } - else - { - guard = (Guard*)&unity_heap[heap_index]; - heap_index += total_size; - } -#else - guard = (Guard*)UNITY_FIXTURE_MALLOC(total_size); -#endif - if (guard == NULL) return NULL; - malloc_count++; - guard->size = size; - guard->guard_space = 0; - mem = (char*)&(guard[1]); - memcpy(&mem[size], end, sizeof(end)); - - return (void*)mem; -} - -static int isOverrun(void* mem) -{ - Guard* guard = (Guard*)mem; - char* memAsChar = (char*)mem; - guard--; - - return guard->guard_space != 0 || strcmp(&memAsChar[guard->size], end) != 0; -} - -static void release_memory(void* mem) -{ - Guard* guard = (Guard*)mem; - guard--; - - malloc_count--; -#ifdef UNITY_EXCLUDE_STDLIB_MALLOC - if (mem == unity_heap + heap_index - guard->size - sizeof(end)) - { - heap_index -= (guard->size + sizeof(Guard) + sizeof(end)); - } -#else - UNITY_FIXTURE_FREE(guard); -#endif -} - -void unity_free(void* mem) -{ - int overrun; - - if (mem == NULL) - { - return; - } - - overrun = isOverrun(mem); - release_memory(mem); - if (overrun) - { - UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "Buffer overrun detected during free()"); - } -} - -void* unity_calloc(size_t num, size_t size) -{ - void* mem = unity_malloc(num * size); - if (mem == NULL) return NULL; - memset(mem, 0, num * size); - return mem; -} - -void* unity_realloc(void* oldMem, size_t size) -{ - Guard* guard = (Guard*)oldMem; - void* newMem; - - if (oldMem == NULL) return unity_malloc(size); - - guard--; - if (isOverrun(oldMem)) - { - release_memory(oldMem); - UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "Buffer overrun detected during realloc()"); - } - - if (size == 0) - { - release_memory(oldMem); - return NULL; - } - - if (guard->size >= size) return oldMem; - -#ifdef UNITY_EXCLUDE_STDLIB_MALLOC /* Optimization if memory is expandable */ - if (oldMem == unity_heap + heap_index - guard->size - sizeof(end) && - heap_index + size - guard->size <= UNITY_INTERNAL_HEAP_SIZE_BYTES) - { - release_memory(oldMem); /* Not thread-safe, like unity_heap generally */ - return unity_malloc(size); /* No memcpy since data is in place */ - } -#endif - newMem = unity_malloc(size); - if (newMem == NULL) return NULL; /* Do not release old memory */ - memcpy(newMem, oldMem, guard->size); - release_memory(oldMem); - return newMem; -} - - -/*-------------------------------------------------------- */ -/*Automatic pointer restoration functions */ -struct PointerPair -{ - void** pointer; - void* old_value; -}; - -static struct PointerPair pointer_store[UNITY_MAX_POINTERS]; -static int pointer_index = 0; - -void UnityPointer_Init(void) -{ - pointer_index = 0; -} - -void UnityPointer_Set(void** pointer, void* newValue, UNITY_LINE_TYPE line) -{ - if (pointer_index >= UNITY_MAX_POINTERS) - { - UNITY_TEST_FAIL(line, "Too many pointers set"); - } - else - { - pointer_store[pointer_index].pointer = pointer; - pointer_store[pointer_index].old_value = *pointer; - *pointer = newValue; - pointer_index++; - } -} - -void UnityPointer_UndoAllSets(void) -{ - while (pointer_index > 0) - { - pointer_index--; - *(pointer_store[pointer_index].pointer) = - pointer_store[pointer_index].old_value; - } -} - -int UnityGetCommandLineOptions(int argc, const char* argv[]) -{ - int i; - UnityFixture.Verbose = 0; - UnityFixture.GroupFilter = 0; - UnityFixture.NameFilter = 0; - UnityFixture.RepeatCount = 1; - - if (argc == 1) - return 0; - - for (i = 1; i < argc; ) - { - if (strcmp(argv[i], "-v") == 0) - { - UnityFixture.Verbose = 1; - i++; - } - else if (strcmp(argv[i], "-g") == 0) - { - i++; - if (i >= argc) - return 1; - UnityFixture.GroupFilter = argv[i]; - i++; - } - else if (strcmp(argv[i], "-n") == 0) - { - i++; - if (i >= argc) - return 1; - UnityFixture.NameFilter = argv[i]; - i++; - } - else if (strcmp(argv[i], "-r") == 0) - { - UnityFixture.RepeatCount = 2; - i++; - if (i < argc) - { - if (*(argv[i]) >= '0' && *(argv[i]) <= '9') - { - unsigned int digit = 0; - UnityFixture.RepeatCount = 0; - while (argv[i][digit] >= '0' && argv[i][digit] <= '9') - { - UnityFixture.RepeatCount *= 10; - UnityFixture.RepeatCount += (unsigned int)argv[i][digit++] - '0'; - } - i++; - } - } - } - else - { - /* ignore unknown parameter */ - i++; - } - } - return 0; -} - -void UnityConcludeFixtureTest(void) -{ - if (Unity.CurrentTestIgnored) - { - Unity.TestIgnores++; - UNITY_PRINT_EOL(); - } - else if (!Unity.CurrentTestFailed) - { - if (UnityFixture.Verbose) - { - UnityPrint(" PASS"); - UNITY_PRINT_EOL(); - } - } - else /* Unity.CurrentTestFailed */ - { - Unity.TestFailures++; - UNITY_PRINT_EOL(); - } - - Unity.CurrentTestFailed = 0; - Unity.CurrentTestIgnored = 0; -} diff --git a/third_party/unity/unity/fixture/unity_fixture.h b/third_party/unity/unity/fixture/unity_fixture.h deleted file mode 100644 index 2dcf473c7f..0000000000 --- a/third_party/unity/unity/fixture/unity_fixture.h +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright (c) 2010 James Grenning and Contributed to Unity Project - * ========================================== - * Unity Project - A Test Framework for C - * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams - * [Released under MIT License. Please refer to license.txt for details] - * ========================================== */ - -#ifndef UNITY_FIXTURE_H_ -#define UNITY_FIXTURE_H_ - -#include "unity.h" -#include "unity_internals.h" -#include "unity_fixture_malloc_overrides.h" -#include "unity_fixture_internals.h" - -int UnityMain(int argc, const char* argv[], void (*runAllTests)(void)); - - -#define TEST_GROUP(group)\ - static const char* TEST_GROUP_##group = #group - -#define TEST_SETUP(group) void TEST_##group##_SETUP(void);\ - void TEST_##group##_SETUP(void) - -#define TEST_TEAR_DOWN(group) void TEST_##group##_TEAR_DOWN(void);\ - void TEST_##group##_TEAR_DOWN(void) - - -#define TEST(group, name) \ - void TEST_##group##_##name##_(void);\ - void TEST_##group##_##name##_run(void);\ - void TEST_##group##_##name##_run(void)\ - {\ - UnityTestRunner(TEST_##group##_SETUP,\ - TEST_##group##_##name##_,\ - TEST_##group##_TEAR_DOWN,\ - "TEST(" #group ", " #name ")",\ - TEST_GROUP_##group, #name,\ - __FILE__, __LINE__);\ - }\ - void TEST_##group##_##name##_(void) - -#define IGNORE_TEST(group, name) \ - void TEST_##group##_##name##_(void);\ - void TEST_##group##_##name##_run(void);\ - void TEST_##group##_##name##_run(void)\ - {\ - UnityIgnoreTest("IGNORE_TEST(" #group ", " #name ")", TEST_GROUP_##group, #name);\ - }\ - void TEST_##group##_##name##_(void) - -/* Call this for each test, insider the group runner */ -#define RUN_TEST_CASE(group, name) \ - { void TEST_##group##_##name##_run(void);\ - TEST_##group##_##name##_run(); } - -/* This goes at the bottom of each test file or in a separate c file */ -#define TEST_GROUP_RUNNER(group)\ - void TEST_##group##_GROUP_RUNNER(void);\ - void TEST_##group##_GROUP_RUNNER(void) - -/* Call this from main */ -#define RUN_TEST_GROUP(group)\ - { void TEST_##group##_GROUP_RUNNER(void);\ - TEST_##group##_GROUP_RUNNER(); } - -/* CppUTest Compatibility Macros */ -#ifndef UNITY_EXCLUDE_CPPUTEST_ASSERTS -/* Sets a pointer and automatically restores it to its old value after teardown */ -#define UT_PTR_SET(ptr, newPointerValue) UnityPointer_Set((void**)&(ptr), (void*)(newPointerValue), __LINE__) -#define TEST_ASSERT_POINTERS_EQUAL(expected, actual) TEST_ASSERT_EQUAL_PTR((expected), (actual)) -#define TEST_ASSERT_BYTES_EQUAL(expected, actual) TEST_ASSERT_EQUAL_HEX8(0xff & (expected), 0xff & (actual)) -#define FAIL(message) TEST_FAIL_MESSAGE((message)) -#define CHECK(condition) TEST_ASSERT_TRUE((condition)) -#define LONGS_EQUAL(expected, actual) TEST_ASSERT_EQUAL_INT((expected), (actual)) -#define STRCMP_EQUAL(expected, actual) TEST_ASSERT_EQUAL_STRING((expected), (actual)) -#define DOUBLES_EQUAL(expected, actual, delta) TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual)) -#endif - -/* You must compile with malloc replacement, as defined in unity_fixture_malloc_overrides.h */ -void UnityMalloc_MakeMallocFailAfterCount(int countdown); - -#endif /* UNITY_FIXTURE_H_ */ diff --git a/third_party/unity/unity/fixture/unity_fixture_internals.h b/third_party/unity/unity/fixture/unity_fixture_internals.h deleted file mode 100644 index 00cee88307..0000000000 --- a/third_party/unity/unity/fixture/unity_fixture_internals.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (c) 2010 James Grenning and Contributed to Unity Project - * ========================================== - * Unity Project - A Test Framework for C - * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams - * [Released under MIT License. Please refer to license.txt for details] - * ========================================== */ - -#ifndef UNITY_FIXTURE_INTERNALS_H_ -#define UNITY_FIXTURE_INTERNALS_H_ - -#ifdef __cplusplus -extern "C" -{ -#endif - -struct UNITY_FIXTURE_T -{ - int Verbose; - unsigned int RepeatCount; - const char* NameFilter; - const char* GroupFilter; -}; -extern struct UNITY_FIXTURE_T UnityFixture; - -typedef void unityfunction(void); -void UnityTestRunner(unityfunction* setup, - unityfunction* testBody, - unityfunction* teardown, - const char* printableName, - const char* group, - const char* name, - const char* file, unsigned int line); - -void UnityIgnoreTest(const char* printableName, const char* group, const char* name); -void UnityMalloc_StartTest(void); -void UnityMalloc_EndTest(void); -int UnityGetCommandLineOptions(int argc, const char* argv[]); -void UnityConcludeFixtureTest(void); - -void UnityPointer_Set(void** pointer, void* newValue, UNITY_LINE_TYPE line); -void UnityPointer_UndoAllSets(void); -void UnityPointer_Init(void); -#ifndef UNITY_MAX_POINTERS -#define UNITY_MAX_POINTERS 5 -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* UNITY_FIXTURE_INTERNALS_H_ */ diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h deleted file mode 100644 index 2356faa00b..0000000000 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright (c) 2010 James Grenning and Contributed to Unity Project - * ========================================== - * Unity Project - A Test Framework for C - * Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams - * [Released under MIT License. Please refer to license.txt for details] - * ========================================== */ - -#ifndef UNITY_FIXTURE_MALLOC_OVERRIDES_H_ -#define UNITY_FIXTURE_MALLOC_OVERRIDES_H_ - -#include -#include - -#ifdef UNITY_EXCLUDE_STDLIB_MALLOC -/* Define this macro to remove the use of stdlib.h, malloc, and free. - * Many embedded systems do not have a heap or malloc/free by default. - * This internal unity_malloc() provides allocated memory deterministically from - * the end of an array only, unity_free() only releases from end-of-array, - * blocks are not coalesced, and memory not freed in LIFO order is stranded. */ - #ifndef UNITY_INTERNAL_HEAP_SIZE_BYTES - #define UNITY_INTERNAL_HEAP_SIZE_BYTES 4096 - #endif -#endif - -/* These functions are used by the Unity Fixture to allocate and release memory - * on the heap and can be overridden with platform-specific implementations. - * For example, when using FreeRTOS UNITY_FIXTURE_MALLOC becomes pvPortMalloc() - * and UNITY_FIXTURE_FREE becomes vPortFree(). */ -#if !defined(UNITY_FIXTURE_MALLOC) || !defined(UNITY_FIXTURE_FREE) - #include - #define UNITY_FIXTURE_MALLOC(size) malloc(size) - #define UNITY_FIXTURE_FREE(ptr) free(ptr) -#else - extern void* UNITY_FIXTURE_MALLOC(size_t size); - extern void UNITY_FIXTURE_FREE(void* ptr); -#endif - -#define malloc unity_malloc_mt -#define calloc unity_calloc_mt -#define realloc unity_realloc_mt -#define free unity_free_mt - -bool UnityMalloc_AllocateResource(void); -void UnityMalloc_FreeResource(void); - -void unity_provide_critical_section(void(*start)(void), void(*end)(void)); -void unity_enter_critical_section(void); -void unity_exit_critical_section(void); - -void* unity_malloc_mt(size_t size); -void* unity_calloc_mt(size_t num, size_t size); -void* unity_realloc_mt(void * oldMem, size_t size); -void unity_free_mt(void * mem); - -#endif /* UNITY_FIXTURE_MALLOC_OVERRIDES_H_ */ diff --git a/third_party/unity/unity/fixture/unity_memory_mt.c b/third_party/unity/unity/fixture/unity_memory_mt.c deleted file mode 100644 index 7905a3dfbc..0000000000 --- a/third_party/unity/unity/fixture/unity_memory_mt.c +++ /dev/null @@ -1,83 +0,0 @@ -/* Wrappers that make the unity memory functions thread-safe. */ - -#include - -/* unity memory functions. */ -extern void* unity_malloc(size_t size); -extern void* unity_calloc(size_t num, size_t size); -extern void* unity_realloc(void* oldMem, size_t size); -extern void unity_free_mt(void* mem); - -/* Function pointers to begin/end critical section functions. */ -static void(*unity_critical_section_start)(void) = NULL; -static void(*unity_critical_section_end)(void) = NULL; - -void unity_provide_critical_section(void(*start)(void), void(*end)(void)) -{ - unity_critical_section_start = start; - unity_critical_section_end = end; -} - -void unity_enter_critical_section(void) -{ - if(unity_critical_section_start != NULL) - { - unity_critical_section_start(); - } -} - -void unity_exit_critical_section(void) -{ - if(unity_critical_section_end != NULL) - { - unity_critical_section_end(); - } -} - -void* unity_malloc_mt(size_t size) -{ - void* mem = NULL; - - unity_enter_critical_section(); - - mem = unity_malloc(size); - - unity_exit_critical_section(); - - return mem; -} - -void* unity_calloc_mt(size_t num, size_t size) -{ - void* mem = NULL; - - unity_enter_critical_section(); - - mem = unity_calloc(num, size); - - unity_exit_critical_section(); - - return mem; -} - -void* unity_realloc_mt(void * oldMem, size_t size) -{ - void* mem = NULL; - - unity_enter_critical_section(); - - mem = unity_realloc(oldMem, size); - - unity_exit_critical_section(); - - return mem; -} - -void unity_free_mt(void * mem) -{ - unity_enter_critical_section(); - - unity_free(mem); - - unity_exit_critical_section(); -} diff --git a/third_party/unity/unity/unity.c b/third_party/unity/unity/unity.c deleted file mode 100644 index 44a8003222..0000000000 --- a/third_party/unity/unity/unity.c +++ /dev/null @@ -1,1570 +0,0 @@ -/* ========================================================================= - Unity Project - A Test Framework for C - Copyright (c) 2007-14 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -============================================================================ */ - -#define UNITY_INCLUDE_SETUP_STUBS -#include "unity.h" -#include - -/* If omitted from header, declare overrideable prototypes here so they're ready for use */ -#ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION -void UNITY_OUTPUT_CHAR(int); -#endif - -/* Helpful macros for us to use here in Assert functions */ -#define UNITY_FAIL_AND_BAIL { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } -#define UNITY_IGNORE_AND_BAIL { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } -#define RETURN_IF_FAIL_OR_IGNORE if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) return - -struct UNITY_STORAGE_T Unity; - -#ifdef UNITY_OUTPUT_COLOR -static const char UnityStrOk[] = "\033[42mOK\033[00m"; -static const char UnityStrPass[] = "\033[42mPASS\033[00m"; -static const char UnityStrFail[] = "\033[41mFAIL\033[00m"; -static const char UnityStrIgnore[] = "\033[43mIGNORE\033[00m"; -#else -static const char UnityStrOk[] = "OK"; -static const char UnityStrPass[] = "PASS"; -static const char UnityStrFail[] = "FAIL"; -static const char UnityStrIgnore[] = "IGNORE"; -#endif -static const char UnityStrNull[] = "NULL"; -static const char UnityStrSpacer[] = ". "; -static const char UnityStrExpected[] = " Expected "; -static const char UnityStrWas[] = " Was "; -static const char UnityStrGt[] = " to be greater than "; -static const char UnityStrLt[] = " to be less than "; -static const char UnityStrOrEqual[] = "or equal to "; -static const char UnityStrElement[] = " Element "; -static const char UnityStrByte[] = " Byte "; -static const char UnityStrMemory[] = " Memory Mismatch."; -static const char UnityStrDelta[] = " Values Not Within Delta "; -static const char UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; -static const char UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; -static const char UnityStrNullPointerForActual[] = " Actual pointer was NULL"; -#ifndef UNITY_EXCLUDE_FLOAT -static const char UnityStrNot[] = "Not "; -static const char UnityStrInf[] = "Infinity"; -static const char UnityStrNegInf[] = "Negative Infinity"; -static const char UnityStrNaN[] = "NaN"; -static const char UnityStrDet[] = "Determinate"; -static const char UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; -#endif -const char UnityStrErrFloat[] = "Unity Floating Point Disabled"; -const char UnityStrErrDouble[] = "Unity Double Precision Disabled"; -const char UnityStrErr64[] = "Unity 64-bit Support Disabled"; -static const char UnityStrBreaker[] = "-----------------------"; -static const char UnityStrResultsTests[] = " Tests "; -static const char UnityStrResultsFailures[] = " Failures "; -static const char UnityStrResultsIgnored[] = " Ignored "; -static const char UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; -static const char UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; - -/*----------------------------------------------- - * Pretty Printers & Test Result Output Handlers - *-----------------------------------------------*/ - -void UnityPrint(const char* string) -{ - const char* pch = string; - - if (pch != NULL) - { - while (*pch) - { - /* printable characters plus CR & LF are printed */ - if ((*pch <= 126) && (*pch >= 32)) - { - UNITY_OUTPUT_CHAR(*pch); - } - /* write escaped carriage returns */ - else if (*pch == 13) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('r'); - } - /* write escaped line feeds */ - else if (*pch == 10) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('n'); - } -#ifdef UNITY_OUTPUT_COLOR - /* print ANSI escape code */ - else if (*pch == 27 && *(pch + 1) == '[') - { - while (*pch && *pch != 'm') - { - UNITY_OUTPUT_CHAR(*pch); - pch++; - } - UNITY_OUTPUT_CHAR('m'); - } -#endif - /* unprintable characters are shown as codes */ - else - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)*pch, 2); - } - pch++; - } - } -} - -void UnityPrintLen(const char* string, const UNITY_UINT32 length) -{ - const char* pch = string; - - if (pch != NULL) - { - while (*pch && (UNITY_UINT32)(pch - string) < length) - { - /* printable characters plus CR & LF are printed */ - if ((*pch <= 126) && (*pch >= 32)) - { - UNITY_OUTPUT_CHAR(*pch); - } - /* write escaped carriage returns */ - else if (*pch == 13) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('r'); - } - /* write escaped line feeds */ - else if (*pch == 10) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('n'); - } - /* unprintable characters are shown as codes */ - else - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)*pch, 2); - } - pch++; - } - } -} - -/*-----------------------------------------------*/ -void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style) -{ - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - UnityPrintNumber(number); - } - else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT) - { - UnityPrintNumberUnsigned((UNITY_UINT)number); - } - else - { - UNITY_OUTPUT_CHAR('0'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2)); - } -} - -/*-----------------------------------------------*/ -void UnityPrintNumber(const UNITY_INT number_to_print) -{ - UNITY_UINT number = (UNITY_UINT)number_to_print; - - if (number_to_print < 0) - { - /* A negative number, including MIN negative */ - UNITY_OUTPUT_CHAR('-'); - number = (UNITY_UINT)(-number_to_print); - } - UnityPrintNumberUnsigned(number); -} - -/*----------------------------------------------- - * basically do an itoa using as little ram as possible */ -void UnityPrintNumberUnsigned(const UNITY_UINT number) -{ - UNITY_UINT divisor = 1; - - /* figure out initial divisor */ - while (number / divisor > 9) - { - divisor *= 10; - } - - /* now mod and print, then divide divisor */ - do - { - UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10))); - divisor /= 10; - } while (divisor > 0); -} - -/*-----------------------------------------------*/ -void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print) -{ - int nibble; - char nibbles = nibbles_to_print; - if ((unsigned)nibbles > (2 * sizeof(number))) - nibbles = 2 * sizeof(number); - - while (nibbles > 0) - { - nibbles--; - nibble = (int)(number >> (nibbles * 4)) & 0x0F; - if (nibble <= 9) - { - UNITY_OUTPUT_CHAR((char)('0' + nibble)); - } - else - { - UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble)); - } - } -} - -/*-----------------------------------------------*/ -void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number) -{ - UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1); - UNITY_INT32 i; - - for (i = 0; i < UNITY_INT_WIDTH; i++) - { - if (current_bit & mask) - { - if (current_bit & number) - { - UNITY_OUTPUT_CHAR('1'); - } - else - { - UNITY_OUTPUT_CHAR('0'); - } - } - else - { - UNITY_OUTPUT_CHAR('X'); - } - current_bit = current_bit >> 1; - } -} - -/*-----------------------------------------------*/ -#ifndef UNITY_EXCLUDE_FLOAT_PRINT -/* This function prints a floating-point value in a format similar to - * printf("%.6g"). It can work with either single- or double-precision, - * but for simplicity, it prints only 6 significant digits in either case. - * Printing more than 6 digits accurately is hard (at least in the single- - * precision case) and isn't attempted here. */ -void UnityPrintFloat(const UNITY_DOUBLE input_number) -{ - UNITY_DOUBLE number = input_number; - - /* print minus sign (including for negative zero) */ - if (number < 0.0f || (number == 0.0f && 1.0f / number < 0.0f)) - { - UNITY_OUTPUT_CHAR('-'); - number = -number; - } - - /* handle zero, NaN, and +/- infinity */ - if (number == 0.0f) UnityPrint("0"); - else if (isnan(number)) UnityPrint("nan"); - else if (isinf(number)) UnityPrint("inf"); - else - { - int exponent = 0; - int decimals, digits; - UNITY_INT32 n; - char buf[16]; - - /* scale up or down by powers of 10 */ - while (number < 100000.0f / 1e6f) { number *= 1e6f; exponent -= 6; } - while (number < 100000.0f) { number *= 10.0f; exponent--; } - while (number > 1000000.0f * 1e6f) { number /= 1e6f; exponent += 6; } - while (number > 1000000.0f) { number /= 10.0f; exponent++; } - - /* round to nearest integer */ - n = ((UNITY_INT32)(number + number) + 1) / 2; - if (n > 999999) - { - n = 100000; - exponent++; - } - - /* determine where to place decimal point */ - decimals = (exponent <= 0 && exponent >= -9) ? -exponent : 5; - exponent += decimals; - - /* truncate trailing zeroes after decimal point */ - while (decimals > 0 && n % 10 == 0) - { - n /= 10; - decimals--; - } - - /* build up buffer in reverse order */ - digits = 0; - while (n != 0 || digits < decimals + 1) - { - buf[digits++] = (char)('0' + n % 10); - n /= 10; - } - while (digits > 0) - { - if(digits == decimals) UNITY_OUTPUT_CHAR('.'); - UNITY_OUTPUT_CHAR(buf[--digits]); - } - - /* print exponent if needed */ - if (exponent != 0) - { - UNITY_OUTPUT_CHAR('e'); - - if(exponent < 0) - { - UNITY_OUTPUT_CHAR('-'); - exponent = -exponent; - } - else - { - UNITY_OUTPUT_CHAR('+'); - } - - digits = 0; - while (exponent != 0 || digits < 2) - { - buf[digits++] = (char)('0' + exponent % 10); - exponent /= 10; - } - while (digits > 0) - { - UNITY_OUTPUT_CHAR(buf[--digits]); - } - } - } -} -#endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */ - -/*-----------------------------------------------*/ -static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line) -{ - UnityPrint(file); - UNITY_OUTPUT_CHAR(':'); - UnityPrintNumber((UNITY_INT)line); - UNITY_OUTPUT_CHAR(':'); - UnityPrint(Unity.CurrentTestName); - UNITY_OUTPUT_CHAR(':'); -} - -/*-----------------------------------------------*/ -static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line) -{ - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint(UnityStrFail); - UNITY_OUTPUT_CHAR(':'); -} - -/*-----------------------------------------------*/ -void UnityConcludeTest(void) -{ - if (Unity.CurrentTestIgnored) - { - Unity.TestIgnores++; - } - else if (!Unity.CurrentTestFailed) - { - UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber); - UnityPrint(UnityStrPass); - } - else - { - Unity.TestFailures++; - } - - Unity.CurrentTestFailed = 0; - Unity.CurrentTestIgnored = 0; - UNITY_PRINT_EOL(); - UNITY_FLUSH_CALL(); -} - -/*-----------------------------------------------*/ -static void UnityAddMsgIfSpecified(const char* msg) -{ - if (msg) - { - UnityPrint(UnityStrSpacer); -#ifndef UNITY_EXCLUDE_DETAILS - if (Unity.CurrentDetail1) - { - UnityPrint(UnityStrDetail1Name); - UnityPrint(Unity.CurrentDetail1); - if (Unity.CurrentDetail2) - { - UnityPrint(UnityStrDetail2Name); - UnityPrint(Unity.CurrentDetail2); - } - UnityPrint(UnityStrSpacer); - } -#endif - UnityPrint(msg); - } -} - -/*-----------------------------------------------*/ -static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual) -{ - UnityPrint(UnityStrExpected); - if (expected != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrint(expected); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } - UnityPrint(UnityStrWas); - if (actual != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrint(actual); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } -} - -/*-----------------------------------------------*/ -static void UnityPrintExpectedAndActualStringsLen(const char* expected, - const char* actual, - const UNITY_UINT32 length) -{ - UnityPrint(UnityStrExpected); - if (expected != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrintLen(expected, length); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } - UnityPrint(UnityStrWas); - if (actual != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrintLen(actual, length); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } -} - -/*----------------------------------------------- - * Assertion & Control Helpers - *-----------------------------------------------*/ - -static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_LINE_TYPE lineNumber, - const char* msg) -{ - if (expected == actual) return 0; /* Both are NULL or same pointer */ - - /* print and return true if just expected is NULL */ - if (expected == NULL) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrNullPointerForExpected); - UnityAddMsgIfSpecified(msg); - return 1; - } - - /* print and return true if just actual is NULL */ - if (actual == NULL) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrNullPointerForActual); - UnityAddMsgIfSpecified(msg); - return 1; - } - - return 0; /* return false if neither is NULL */ -} - -/*----------------------------------------------- - * Assertion Functions - *-----------------------------------------------*/ - -void UnityAssertBits(const UNITY_INT mask, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if ((mask & expected) != (mask & actual)) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected); - UnityPrint(UnityStrWas); - UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualNumber(const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if (expected != actual) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expected, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, - const UNITY_INT actual, - const UNITY_COMPARISON_T compare, - const char *msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style) -{ - int failed = 0; - RETURN_IF_FAIL_OR_IGNORE; - - if (threshold == actual && compare & UNITY_EQUAL_TO) return; - if (threshold == actual) failed = 1; - - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - if (actual > threshold && compare & UNITY_SMALLER_THAN) failed = 1; - if (actual < threshold && compare & UNITY_GREATER_THAN) failed = 1; - } - else /* UINT or HEX */ - { - if ((UNITY_UINT)actual > (UNITY_UINT)threshold && compare & UNITY_SMALLER_THAN) failed = 1; - if ((UNITY_UINT)actual < (UNITY_UINT)threshold && compare & UNITY_GREATER_THAN) failed = 1; - } - - if (failed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(actual, style); - if (compare & UNITY_GREATER_THAN) UnityPrint(UnityStrGt); - if (compare & UNITY_SMALLER_THAN) UnityPrint(UnityStrLt); - if (compare & UNITY_EQUAL_TO) UnityPrint(UnityStrOrEqual); - UnityPrintNumberByStyle(threshold, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -#define UnityPrintPointlessAndBail() \ -{ \ - UnityTestResultsFailBegin(lineNumber); \ - UnityPrint(UnityStrPointless); \ - UnityAddMsgIfSpecified(msg); \ - UNITY_FAIL_AND_BAIL; } - -/*-----------------------------------------------*/ -void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - unsigned int length = style & 0xF; - - RETURN_IF_FAIL_OR_IGNORE; - - if (num_elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) return; /* Both are NULL or same pointer */ - if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) - UNITY_FAIL_AND_BAIL; - - while ((elements > 0) && elements--) - { - UNITY_INT expect_val; - UNITY_INT actual_val; - switch (length) - { - case 1: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; - break; - case 2: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; - break; -#ifdef UNITY_SUPPORT_64 - case 8: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; - break; -#endif - default: /* length 4 bytes */ - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; - length = 4; - break; - } - - if (expect_val != actual_val) - { - if (style & UNITY_DISPLAY_RANGE_UINT && length < sizeof(expect_val)) - { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ - UNITY_INT mask = 1; - mask = (mask << 8 * length) - 1; - expect_val &= mask; - actual_val &= mask; - } - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expect_val, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual_val, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - if (flags == UNITY_ARRAY_TO_ARRAY) - { - expected = (UNITY_INTERNAL_PTR)(length + (const char*)expected); - } - actual = (UNITY_INTERNAL_PTR)(length + (const char*)actual); - } -} - -/*-----------------------------------------------*/ -#ifndef UNITY_EXCLUDE_FLOAT -/* Wrap this define in a function with variable types as float or double */ -#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ - if (isinf(expected) && isinf(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ - if (UNITY_NAN_CHECK) return 1; \ - (diff) = (actual) - (expected); \ - if ((diff) < 0) (diff) = -(diff); \ - if ((delta) < 0) (delta) = -(delta); \ - return !(isnan(diff) || isinf(diff) || ((diff) > (delta))) - /* This first part of this condition will catch any NaN or Infinite values */ -#ifndef UNITY_NAN_NOT_EQUAL_NAN - #define UNITY_NAN_CHECK isnan(expected) && isnan(actual) -#else - #define UNITY_NAN_CHECK 0 -#endif - -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ - { \ - UnityPrint(UnityStrExpected); \ - UnityPrintFloat(expected); \ - UnityPrint(UnityStrWas); \ - UnityPrintFloat(actual); } -#else - #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ - UnityPrint(UnityStrDelta) -#endif /* UNITY_EXCLUDE_FLOAT_PRINT */ - -static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual) -{ - UNITY_FLOAT diff; - UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); -} - -void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; - - RETURN_IF_FAIL_OR_IGNORE; - - if (elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) return; /* Both are NULL or same pointer */ - if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) - UNITY_FAIL_AND_BAIL; - - while (elements--) - { - if (!UnityFloatsWithin(*ptr_expected * UNITY_FLOAT_PRECISION, *ptr_expected, *ptr_actual)) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - if (flags == UNITY_ARRAY_TO_ARRAY) - { - ptr_expected++; - } - ptr_actual++; - } -} - -/*-----------------------------------------------*/ -void UnityAssertFloatsWithin(const UNITY_FLOAT delta, - const UNITY_FLOAT expected, - const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - RETURN_IF_FAIL_OR_IGNORE; - - - if (!UnityFloatsWithin(delta, expected, actual)) - { - UnityTestResultsFailBegin(lineNumber); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertFloatSpecial(const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style) -{ - const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; - UNITY_INT should_be_trait = ((UNITY_INT)style & 1); - UNITY_INT is_trait = !should_be_trait; - UNITY_INT trait_index = (UNITY_INT)(style >> 1); - - RETURN_IF_FAIL_OR_IGNORE; - - switch (style) - { - case UNITY_FLOAT_IS_INF: - case UNITY_FLOAT_IS_NOT_INF: - is_trait = isinf(actual) && (actual > 0); - break; - case UNITY_FLOAT_IS_NEG_INF: - case UNITY_FLOAT_IS_NOT_NEG_INF: - is_trait = isinf(actual) && (actual < 0); - break; - - case UNITY_FLOAT_IS_NAN: - case UNITY_FLOAT_IS_NOT_NAN: - is_trait = isnan(actual) ? 1 : 0; - break; - - case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ - case UNITY_FLOAT_IS_NOT_DET: - is_trait = !isinf(actual) && !isnan(actual); - break; - - default: - trait_index = 0; - trait_names[0] = UnityStrInvalidFloatTrait; - break; - } - - if (is_trait != should_be_trait) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - if (!should_be_trait) - UnityPrint(UnityStrNot); - UnityPrint(trait_names[trait_index]); - UnityPrint(UnityStrWas); -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - UnityPrintFloat((UNITY_DOUBLE)actual); -#else - if (should_be_trait) - UnityPrint(UnityStrNot); - UnityPrint(trait_names[trait_index]); -#endif - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -#endif /* not UNITY_EXCLUDE_FLOAT */ - -/*-----------------------------------------------*/ -#ifndef UNITY_EXCLUDE_DOUBLE -static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual) -{ - UNITY_DOUBLE diff; - UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); -} - -void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; - - RETURN_IF_FAIL_OR_IGNORE; - - if (elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) return; /* Both are NULL or same pointer */ - if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) - UNITY_FAIL_AND_BAIL; - - while (elements--) - { - if (!UnityDoublesWithin(*ptr_expected * UNITY_DOUBLE_PRECISION, *ptr_expected, *ptr_actual)) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - if (flags == UNITY_ARRAY_TO_ARRAY) - { - ptr_expected++; - } - ptr_actual++; - } -} - -/*-----------------------------------------------*/ -void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, - const UNITY_DOUBLE expected, - const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if (!UnityDoublesWithin(delta, expected, actual)) - { - UnityTestResultsFailBegin(lineNumber); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ - -void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style) -{ - const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; - UNITY_INT should_be_trait = ((UNITY_INT)style & 1); - UNITY_INT is_trait = !should_be_trait; - UNITY_INT trait_index = (UNITY_INT)(style >> 1); - - RETURN_IF_FAIL_OR_IGNORE; - - switch (style) - { - case UNITY_FLOAT_IS_INF: - case UNITY_FLOAT_IS_NOT_INF: - is_trait = isinf(actual) && (actual > 0); - break; - case UNITY_FLOAT_IS_NEG_INF: - case UNITY_FLOAT_IS_NOT_NEG_INF: - is_trait = isinf(actual) && (actual < 0); - break; - - case UNITY_FLOAT_IS_NAN: - case UNITY_FLOAT_IS_NOT_NAN: - is_trait = isnan(actual) ? 1 : 0; - break; - - case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ - case UNITY_FLOAT_IS_NOT_DET: - is_trait = !isinf(actual) && !isnan(actual); - break; - - default: - trait_index = 0; - trait_names[0] = UnityStrInvalidFloatTrait; - break; - } - - if (is_trait != should_be_trait) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - if (!should_be_trait) - UnityPrint(UnityStrNot); - UnityPrint(trait_names[trait_index]); - UnityPrint(UnityStrWas); -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - UnityPrintFloat(actual); -#else - if (should_be_trait) - UnityPrint(UnityStrNot); - UnityPrint(trait_names[trait_index]); -#endif - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -#endif /* not UNITY_EXCLUDE_DOUBLE */ - -/*-----------------------------------------------*/ -void UnityAssertNumbersWithin(const UNITY_UINT delta, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - if (actual > expected) - Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(actual - expected) > delta); - else - Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(expected - actual) > delta); - } - else - { - if ((UNITY_UINT)actual > (UNITY_UINT)expected) - Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(actual - expected) > delta); - else - Unity.CurrentTestFailed = (UNITY_UINT)((UNITY_UINT)(expected - actual) > delta); - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrDelta); - UnityPrintNumberByStyle((UNITY_INT)delta, style); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expected, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualString(const char* expected, - const char* actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - UNITY_UINT32 i; - - RETURN_IF_FAIL_OR_IGNORE; - - /* if both pointers not null compare the strings */ - if (expected && actual) - { - for (i = 0; expected[i] || actual[i]; i++) - { - if (expected[i] != actual[i]) - { - Unity.CurrentTestFailed = 1; - break; - } - } - } - else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expected != actual) - { - Unity.CurrentTestFailed = 1; - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrintExpectedAndActualStrings(expected, actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualStringLen(const char* expected, - const char* actual, - const UNITY_UINT32 length, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - UNITY_UINT32 i; - - RETURN_IF_FAIL_OR_IGNORE; - - /* if both pointers not null compare the strings */ - if (expected && actual) - { - for (i = 0; (i < length) && (expected[i] || actual[i]); i++) - { - if (expected[i] != actual[i]) - { - Unity.CurrentTestFailed = 1; - break; - } - } - } - else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expected != actual) - { - Unity.CurrentTestFailed = 1; - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrintExpectedAndActualStringsLen(expected, actual, length); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, - const char** actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 i = 0; - UNITY_UINT32 j = 0; - const char* expd = NULL; - const char* act = NULL; - - RETURN_IF_FAIL_OR_IGNORE; - - /* if no elements, it's an error */ - if (num_elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if ((const void*)expected == (const void*)actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - if (flags != UNITY_ARRAY_TO_ARRAY) - { - expd = (const char*)expected; - } - - do - { - act = actual[j]; - if (flags == UNITY_ARRAY_TO_ARRAY) - { - expd = ((const char* const*)expected)[j]; - } - - /* if both pointers not null compare the strings */ - if (expd && act) - { - for (i = 0; expd[i] || act[i]; i++) - { - if (expd[i] != act[i]) - { - Unity.CurrentTestFailed = 1; - break; - } - } - } - else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expd != act) - { - Unity.CurrentTestFailed = 1; - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - if (num_elements > 1) - { - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(j); - } - UnityPrintExpectedAndActualStrings(expd, act); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - } while (++j < num_elements); -} - -/*-----------------------------------------------*/ -void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 length, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; - UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual; - UNITY_UINT32 elements = num_elements; - UNITY_UINT32 bytes; - - RETURN_IF_FAIL_OR_IGNORE; - - if ((elements == 0) || (length == 0)) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) return; /* Both are NULL or same pointer */ - if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) - UNITY_FAIL_AND_BAIL; - - while (elements--) - { - bytes = length; - while (bytes--) - { - if (*ptr_exp != *ptr_act) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrMemory); - if (num_elements > 1) - { - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - } - UnityPrint(UnityStrByte); - UnityPrintNumberUnsigned(length - bytes - 1); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - ptr_exp++; - ptr_act++; - } - if (flags == UNITY_ARRAY_TO_VAL) - { - ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; - } - } -} - -/*-----------------------------------------------*/ - -static union -{ - UNITY_INT8 i8; - UNITY_INT16 i16; - UNITY_INT32 i32; -#ifdef UNITY_SUPPORT_64 - UNITY_INT64 i64; -#endif -#ifndef UNITY_EXCLUDE_FLOAT - float f; -#endif -#ifndef UNITY_EXCLUDE_DOUBLE - double d; -#endif -} UnityQuickCompare; - -UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size) -{ - switch(size) - { - case 1: - UnityQuickCompare.i8 = (UNITY_INT8)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8); - - case 2: - UnityQuickCompare.i16 = (UNITY_INT16)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16); - -#ifdef UNITY_SUPPORT_64 - case 8: - UnityQuickCompare.i64 = (UNITY_INT64)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64); -#endif - default: /* 4 bytes */ - UnityQuickCompare.i32 = (UNITY_INT32)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32); - } -} - -#ifndef UNITY_EXCLUDE_FLOAT -UNITY_INTERNAL_PTR UnityFloatToPtr(const float num) -{ - UnityQuickCompare.f = num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f); -} -#endif - -#ifndef UNITY_EXCLUDE_DOUBLE -UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) -{ - UnityQuickCompare.d = num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d); -} -#endif - -/*----------------------------------------------- - * Control Functions - *-----------------------------------------------*/ - -void UnityFail(const char* msg, const UNITY_LINE_TYPE line) -{ - RETURN_IF_FAIL_OR_IGNORE; - - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint(UnityStrFail); - if (msg != NULL) - { - UNITY_OUTPUT_CHAR(':'); - -#ifndef UNITY_EXCLUDE_DETAILS - if (Unity.CurrentDetail1) - { - UnityPrint(UnityStrDetail1Name); - UnityPrint(Unity.CurrentDetail1); - if (Unity.CurrentDetail2) - { - UnityPrint(UnityStrDetail2Name); - UnityPrint(Unity.CurrentDetail2); - } - UnityPrint(UnityStrSpacer); - } -#endif - if (msg[0] != ' ') - { - UNITY_OUTPUT_CHAR(' '); - } - UnityPrint(msg); - } - - UNITY_FAIL_AND_BAIL; -} - -/*-----------------------------------------------*/ -void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line) -{ - RETURN_IF_FAIL_OR_IGNORE; - - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint(UnityStrIgnore); - if (msg != NULL) - { - UNITY_OUTPUT_CHAR(':'); - UNITY_OUTPUT_CHAR(' '); - UnityPrint(msg); - } - UNITY_IGNORE_AND_BAIL; -} - -/*-----------------------------------------------*/ -void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum) -{ - Unity.CurrentTestName = FuncName; - Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum; - Unity.NumberOfTests++; - UNITY_CLR_DETAILS(); - if (TEST_PROTECT()) - { - setUp(); - Func(); - } - if (TEST_PROTECT()) - { - tearDown(); - } - UnityConcludeTest(); -} - -/*-----------------------------------------------*/ -void UnityBegin(const char* filename) -{ - Unity.TestFile = filename; - Unity.CurrentTestName = NULL; - Unity.CurrentTestLineNumber = 0; - Unity.NumberOfTests = 0; - Unity.TestFailures = 0; - Unity.TestIgnores = 0; - Unity.CurrentTestFailed = 0; - Unity.CurrentTestIgnored = 0; - - UNITY_CLR_DETAILS(); - UNITY_OUTPUT_START(); -} - -/*-----------------------------------------------*/ -int UnityEnd(void) -{ - UNITY_PRINT_EOL(); - UnityPrint(UnityStrBreaker); - UNITY_PRINT_EOL(); - UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests)); - UnityPrint(UnityStrResultsTests); - UnityPrintNumber((UNITY_INT)(Unity.TestFailures)); - UnityPrint(UnityStrResultsFailures); - UnityPrintNumber((UNITY_INT)(Unity.TestIgnores)); - UnityPrint(UnityStrResultsIgnored); - UNITY_PRINT_EOL(); - if (Unity.TestFailures == 0U) - { - UnityPrint(UnityStrOk); - } - else - { - UnityPrint(UnityStrFail); -#ifdef UNITY_DIFFERENTIATE_FINAL_FAIL - UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D'); -#endif - } - UNITY_PRINT_EOL(); - UNITY_FLUSH_CALL(); - UNITY_OUTPUT_COMPLETE(); - return (int)(Unity.TestFailures); -} - -/*----------------------------------------------- - * Command Line Argument Support - *-----------------------------------------------*/ -#ifdef UNITY_USE_COMMAND_LINE_ARGS - -char* UnityOptionIncludeNamed = NULL; -char* UnityOptionExcludeNamed = NULL; -int UnityVerbosity = 1; - -int UnityParseOptions(int argc, char** argv) -{ - UnityOptionIncludeNamed = NULL; - UnityOptionExcludeNamed = NULL; - - for (int i = 1; i < argc; i++) - { - if (argv[i][0] == '-') - { - switch (argv[i][1]) - { - case 'l': /* list tests */ - return -1; - case 'n': /* include tests with name including this string */ - case 'f': /* an alias for -n */ - if (argv[i][2] == '=') - UnityOptionIncludeNamed = &argv[i][3]; - else if (++i < argc) - UnityOptionIncludeNamed = argv[i]; - else - { - UnityPrint("ERROR: No Test String to Include Matches For"); - UNITY_PRINT_EOL(); - return 1; - } - break; - case 'q': /* quiet */ - UnityVerbosity = 0; - break; - case 'v': /* verbose */ - UnityVerbosity = 2; - break; - case 'x': /* exclude tests with name including this string */ - if (argv[i][2] == '=') - UnityOptionExcludeNamed = &argv[i][3]; - else if (++i < argc) - UnityOptionExcludeNamed = argv[i]; - else - { - UnityPrint("ERROR: No Test String to Exclude Matches For"); - UNITY_PRINT_EOL(); - return 1; - } - break; - default: - UnityPrint("ERROR: Unknown Option "); - UNITY_OUTPUT_CHAR(argv[i][1]); - UNITY_PRINT_EOL(); - return 1; - } - } - } - - return 0; -} - -int IsStringInBiggerString(const char* longstring, const char* shortstring) -{ - const char* lptr = longstring; - const char* sptr = shortstring; - const char* lnext = lptr; - - if (*sptr == '*') - return 1; - - while (*lptr) - { - lnext = lptr + 1; - - /* If they current bytes match, go on to the next bytes */ - while (*lptr && *sptr && (*lptr == *sptr)) - { - lptr++; - sptr++; - - /* We're done if we match the entire string or up to a wildcard */ - if (*sptr == '*') - return 1; - if (*sptr == ',') - return 1; - if (*sptr == '"') - return 1; - if (*sptr == '\'') - return 1; - if (*sptr == ':') - return 2; - if (*sptr == 0) - return 1; - } - - /* Otherwise we start in the long pointer 1 character further and try again */ - lptr = lnext; - sptr = shortstring; - } - return 0; -} - -int UnityStringArgumentMatches(const char* str) -{ - int retval; - const char* ptr1; - const char* ptr2; - const char* ptrf; - - /* Go through the options and get the substrings for matching one at a time */ - ptr1 = str; - while (ptr1[0] != 0) - { - if ((ptr1[0] == '"') || (ptr1[0] == '\'')) - ptr1++; - - /* look for the start of the next partial */ - ptr2 = ptr1; - ptrf = 0; - do - { - ptr2++; - if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')) - ptrf = &ptr2[1]; - } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')); - while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ','))) - ptr2++; - - /* done if complete filename match */ - retval = IsStringInBiggerString(Unity.TestFile, ptr1); - if (retval == 1) - return retval; - - /* done if testname match after filename partial match */ - if ((retval == 2) && (ptrf != 0)) - { - if (IsStringInBiggerString(Unity.CurrentTestName, ptrf)) - return 1; - } - - /* done if complete testname match */ - if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1) - return 1; - - ptr1 = ptr2; - } - - /* we couldn't find a match for any substrings */ - return 0; -} - -int UnityTestMatches(void) -{ - /* Check if this test name matches the included test pattern */ - int retval; - if (UnityOptionIncludeNamed) - { - retval = UnityStringArgumentMatches(UnityOptionIncludeNamed); - } - else - retval = 1; - - /* Check if this test name matches the excluded test pattern */ - if (UnityOptionExcludeNamed) - { - if (UnityStringArgumentMatches(UnityOptionExcludeNamed)) - retval = 0; - } - return retval; -} - -#endif /* UNITY_USE_COMMAND_LINE_ARGS */ -/*-----------------------------------------------*/ diff --git a/third_party/unity/unity/unity.h b/third_party/unity/unity/unity.h deleted file mode 100644 index a0c301d25a..0000000000 --- a/third_party/unity/unity/unity.h +++ /dev/null @@ -1,503 +0,0 @@ -/* ========================================== - Unity Project - A Test Framework for C - Copyright (c) 2007-14 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -========================================== */ - -#ifndef UNITY_FRAMEWORK_H -#define UNITY_FRAMEWORK_H -#define UNITY - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include "unity_internals.h" - -/*------------------------------------------------------- - * Test Setup / Teardown - *-------------------------------------------------------*/ - -/* These functions are intended to be called before and after each test. */ -void setUp(void); -void tearDown(void); - -/* These functions are intended to be called at the beginning and end of an - * entire test suite. suiteTearDown() is passed the number of tests that - * failed, and its return value becomes the exit code of main(). */ -void suiteSetUp(void); -int suiteTearDown(int num_failures); - -/* If the compiler supports it, the following block provides stub - * implementations of the above functions as weak symbols. Note that on - * some platforms (MinGW for example), weak function implementations need - * to be in the same translation unit they are called from. This can be - * achieved by defining UNITY_INCLUDE_SETUP_STUBS before including unity.h. */ -#ifdef UNITY_INCLUDE_SETUP_STUBS - #ifdef UNITY_WEAK_ATTRIBUTE - UNITY_WEAK_ATTRIBUTE void setUp(void) { } - UNITY_WEAK_ATTRIBUTE void tearDown(void) { } - UNITY_WEAK_ATTRIBUTE void suiteSetUp(void) { } - UNITY_WEAK_ATTRIBUTE int suiteTearDown(int num_failures) { return num_failures; } - #elif defined(UNITY_WEAK_PRAGMA) - #pragma weak setUp - void setUp(void) { } - #pragma weak tearDown - void tearDown(void) { } - #pragma weak suiteSetUp - void suiteSetUp(void) { } - #pragma weak suiteTearDown - int suiteTearDown(int num_failures) { return num_failures; } - #endif -#endif - -/*------------------------------------------------------- - * Configuration Options - *------------------------------------------------------- - * All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above. - - * Integers/longs/pointers - * - Unity attempts to automatically discover your integer sizes - * - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in - * - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in - * - If you cannot use the automatic methods above, you can force Unity by using these options: - * - define UNITY_SUPPORT_64 - * - set UNITY_INT_WIDTH - * - set UNITY_LONG_WIDTH - * - set UNITY_POINTER_WIDTH - - * Floats - * - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons - * - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT - * - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats - * - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons - * - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default) - * - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE - * - define UNITY_DOUBLE_TYPE to specify something other than double - * - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors - - * Output - * - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired - * - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure - - * Optimization - * - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge - * - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests. - - * Test Cases - * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script - - * Parameterized Tests - * - you'll want to create a define of TEST_CASE(...) which basically evaluates to nothing - - * Tests with Arguments - * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity - - *------------------------------------------------------- - * Basic Fail and Ignore - *-------------------------------------------------------*/ - -#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message)) -#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL) -#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message)) -#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL) -#define TEST_ONLY() - -/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails. - * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */ -#define TEST_PASS() TEST_ABORT() - -/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out - * which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */ -#define TEST_FILE(a) - -/*------------------------------------------------------- - * Test Asserts (simple) - *-------------------------------------------------------*/ - -/* Boolean */ -#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE") -#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE") -#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE") -#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE") -#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL") -#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL") - -/* Integers (of all sizes) */ -#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") -#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, NULL) -#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, NULL) -#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, NULL) -#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, NULL) - -/* Integer Greater Than/ Less Than (of all sizes) */ -#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL) - -#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL) - -#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) - -#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) - -/* Integer Ranges (of all sizes) */ -#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL) - -/* Structs and Strings */ -#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL) - -/* Arrays */ -#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL) - -/* Arrays Compared To Single Value */ -#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL) - -/* Floating Point (If Enabled) */ -#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL) - -/* Double (If Enabled) */ -#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL) - -/*------------------------------------------------------- - * Test Asserts (with additional messages) - *-------------------------------------------------------*/ - -/* Boolean */ -#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) -#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) -#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) -#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) -#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message)) -#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message)) - -/* Integers (of all sizes) */ -#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) -#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message)) -#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) -#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message)) - -/* Integer Greater Than/ Less Than (of all sizes) */ -#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message)) - -#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message)) - -#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) - -#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) - -/* Integer Ranges (of all sizes) */ -#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message)) - -/* Structs and Strings */ -#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message)) - -/* Arrays */ -#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message)) - -/* Arrays Compared To Single Value*/ -#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message)) - -/* Floating Point (If Enabled) */ -#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message)) - -/* Double (If Enabled) */ -#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message)) - -/* end of UNITY_FRAMEWORK_H */ -#ifdef __cplusplus -} -#endif -#endif diff --git a/third_party/unity/unity/unity_internals.h b/third_party/unity/unity/unity_internals.h deleted file mode 100644 index 415a29b0cc..0000000000 --- a/third_party/unity/unity/unity_internals.h +++ /dev/null @@ -1,872 +0,0 @@ -/* ========================================== - Unity Project - A Test Framework for C - Copyright (c) 2007-14 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -========================================== */ - -#ifndef UNITY_INTERNALS_H -#define UNITY_INTERNALS_H - -#ifdef UNITY_INCLUDE_CONFIG_H -#include "unity_config.h" -#endif - -#ifndef UNITY_EXCLUDE_SETJMP_H -#include -#endif - -#ifndef UNITY_EXCLUDE_MATH_H -#include -#endif - -/* Unity Attempts to Auto-Detect Integer Types - * Attempt 1: UINT_MAX, ULONG_MAX in , or default to 32 bits - * Attempt 2: UINTPTR_MAX in , or default to same size as long - * The user may override any of these derived constants: - * UNITY_INT_WIDTH, UNITY_LONG_WIDTH, UNITY_POINTER_WIDTH */ -#ifndef UNITY_EXCLUDE_STDINT_H -#include -#endif - -#ifndef UNITY_EXCLUDE_LIMITS_H -#include -#endif - -/*------------------------------------------------------- - * Guess Widths If Not Specified - *-------------------------------------------------------*/ - -/* Determine the size of an int, if not already specified. - * We cannot use sizeof(int), because it is not yet defined - * at this stage in the translation of the C program. - * Therefore, infer it from UINT_MAX if possible. */ -#ifndef UNITY_INT_WIDTH - #ifdef UINT_MAX - #if (UINT_MAX == 0xFFFF) - #define UNITY_INT_WIDTH (16) - #elif (UINT_MAX == 0xFFFFFFFF) - #define UNITY_INT_WIDTH (32) - #elif (UINT_MAX == 0xFFFFFFFFFFFFFFFF) - #define UNITY_INT_WIDTH (64) - #endif - #else /* Set to default */ - #define UNITY_INT_WIDTH (32) - #endif /* UINT_MAX */ -#endif - -/* Determine the size of a long, if not already specified. */ -#ifndef UNITY_LONG_WIDTH - #ifdef ULONG_MAX - #if (ULONG_MAX == 0xFFFF) - #define UNITY_LONG_WIDTH (16) - #elif (ULONG_MAX == 0xFFFFFFFF) - #define UNITY_LONG_WIDTH (32) - #elif (ULONG_MAX == 0xFFFFFFFFFFFFFFFF) - #define UNITY_LONG_WIDTH (64) - #endif - #else /* Set to default */ - #define UNITY_LONG_WIDTH (32) - #endif /* ULONG_MAX */ -#endif - -/* Determine the size of a pointer, if not already specified. */ -#ifndef UNITY_POINTER_WIDTH - #ifdef UINTPTR_MAX - #if (UINTPTR_MAX <= 0xFFFF) - #define UNITY_POINTER_WIDTH (16) - #elif (UINTPTR_MAX <= 0xFFFFFFFF) - #define UNITY_POINTER_WIDTH (32) - #elif (UINTPTR_MAX <= 0xFFFFFFFFFFFFFFFF) - #define UNITY_POINTER_WIDTH (64) - #endif - #else /* Set to default */ - #define UNITY_POINTER_WIDTH UNITY_LONG_WIDTH - #endif /* UINTPTR_MAX */ -#endif - -/*------------------------------------------------------- - * Int Support (Define types based on detected sizes) - *-------------------------------------------------------*/ - -#if (UNITY_INT_WIDTH == 32) - typedef unsigned char UNITY_UINT8; - typedef unsigned short UNITY_UINT16; - typedef unsigned int UNITY_UINT32; - typedef signed char UNITY_INT8; - typedef signed short UNITY_INT16; - typedef signed int UNITY_INT32; -#elif (UNITY_INT_WIDTH == 16) - typedef unsigned char UNITY_UINT8; - typedef unsigned int UNITY_UINT16; - typedef unsigned long UNITY_UINT32; - typedef signed char UNITY_INT8; - typedef signed int UNITY_INT16; - typedef signed long UNITY_INT32; -#else - #error Invalid UNITY_INT_WIDTH specified! (16 or 32 are supported) -#endif - -/*------------------------------------------------------- - * 64-bit Support - *-------------------------------------------------------*/ - -#ifndef UNITY_SUPPORT_64 - #if UNITY_LONG_WIDTH == 64 || UNITY_POINTER_WIDTH == 64 - #define UNITY_SUPPORT_64 - #endif -#endif - -#ifndef UNITY_SUPPORT_64 - /* No 64-bit Support */ - typedef UNITY_UINT32 UNITY_UINT; - typedef UNITY_INT32 UNITY_INT; -#else - - /* 64-bit Support */ - #if (UNITY_LONG_WIDTH == 32) - typedef unsigned long long UNITY_UINT64; - typedef signed long long UNITY_INT64; - #elif (UNITY_LONG_WIDTH == 64) - typedef unsigned long UNITY_UINT64; - typedef signed long UNITY_INT64; - #else - #error Invalid UNITY_LONG_WIDTH specified! (32 or 64 are supported) - #endif - typedef UNITY_UINT64 UNITY_UINT; - typedef UNITY_INT64 UNITY_INT; - -#endif - -/*------------------------------------------------------- - * Pointer Support - *-------------------------------------------------------*/ - -#if (UNITY_POINTER_WIDTH == 32) -#define UNITY_PTR_TO_INT UNITY_INT32 -#define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX32 -#elif (UNITY_POINTER_WIDTH == 64) -#define UNITY_PTR_TO_INT UNITY_INT64 -#define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX64 -#elif (UNITY_POINTER_WIDTH == 16) -#define UNITY_PTR_TO_INT UNITY_INT16 -#define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX16 -#else - #error Invalid UNITY_POINTER_WIDTH specified! (16, 32 or 64 are supported) -#endif - -#ifndef UNITY_PTR_ATTRIBUTE -#define UNITY_PTR_ATTRIBUTE -#endif - -#ifndef UNITY_INTERNAL_PTR -#define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void* -#endif - -/*------------------------------------------------------- - * Float Support - *-------------------------------------------------------*/ - -#ifdef UNITY_EXCLUDE_FLOAT - -/* No Floating Point Support */ -#ifndef UNITY_EXCLUDE_DOUBLE -#define UNITY_EXCLUDE_DOUBLE /* Remove double when excluding float support */ -#endif -#ifndef UNITY_EXCLUDE_FLOAT_PRINT -#define UNITY_EXCLUDE_FLOAT_PRINT -#endif - -#else - -/* Floating Point Support */ -#ifndef UNITY_FLOAT_PRECISION -#define UNITY_FLOAT_PRECISION (0.00001f) -#endif -#ifndef UNITY_FLOAT_TYPE -#define UNITY_FLOAT_TYPE float -#endif -typedef UNITY_FLOAT_TYPE UNITY_FLOAT; - -/* isinf & isnan macros should be provided by math.h */ -#ifndef isinf -/* The value of Inf - Inf is NaN */ -#define isinf(n) (isnan((n) - (n)) && !isnan(n)) -#endif - -#ifndef isnan -/* NaN is the only floating point value that does NOT equal itself. - * Therefore if n != n, then it is NaN. */ -#define isnan(n) ((n != n) ? 1 : 0) -#endif - -#endif - -/*------------------------------------------------------- - * Double Float Support - *-------------------------------------------------------*/ - -/* unlike float, we DON'T include by default */ -#if defined(UNITY_EXCLUDE_DOUBLE) || !defined(UNITY_INCLUDE_DOUBLE) - - /* No Floating Point Support */ - #ifndef UNITY_EXCLUDE_DOUBLE - #define UNITY_EXCLUDE_DOUBLE - #else - #undef UNITY_INCLUDE_DOUBLE - #endif - - #ifndef UNITY_EXCLUDE_FLOAT - #ifndef UNITY_DOUBLE_TYPE - #define UNITY_DOUBLE_TYPE double - #endif - typedef UNITY_FLOAT UNITY_DOUBLE; - /* For parameter in UnityPrintFloat(UNITY_DOUBLE), which aliases to double or float */ - #endif - -#else - - /* Double Floating Point Support */ - #ifndef UNITY_DOUBLE_PRECISION - #define UNITY_DOUBLE_PRECISION (1e-12) - #endif - - #ifndef UNITY_DOUBLE_TYPE - #define UNITY_DOUBLE_TYPE double - #endif - typedef UNITY_DOUBLE_TYPE UNITY_DOUBLE; - -#endif - -/*------------------------------------------------------- - * Output Method: stdout (DEFAULT) - *-------------------------------------------------------*/ -#ifndef UNITY_OUTPUT_CHAR - /* Default to using putchar, which is defined in stdio.h */ - #include - #define UNITY_OUTPUT_CHAR(a) (void)putchar(a) -#else - /* If defined as something else, make sure we declare it here so it's ready for use */ - #ifdef UNITY_OUTPUT_CHAR_HEADER_DECLARATION - extern void UNITY_OUTPUT_CHAR_HEADER_DECLARATION; - #endif -#endif - -#ifndef UNITY_OUTPUT_FLUSH - #ifdef UNITY_USE_FLUSH_STDOUT - /* We want to use the stdout flush utility */ - #include - #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) - #else - /* We've specified nothing, therefore flush should just be ignored */ - #define UNITY_OUTPUT_FLUSH() - #endif -#else - /* If defined as something else, make sure we declare it here so it's ready for use */ - #ifdef UNITY_OUTPUT_FLUSH_HEADER_DECLARATION - extern void UNITY_OUTPUT_FLUSH_HEADER_DECLARATION; - #endif -#endif - -#ifndef UNITY_OUTPUT_FLUSH -#define UNITY_FLUSH_CALL() -#else -#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() -#endif - -#ifndef UNITY_PRINT_EOL -#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') -#endif - -#ifndef UNITY_OUTPUT_START -#define UNITY_OUTPUT_START() -#endif - -#ifndef UNITY_OUTPUT_COMPLETE -#define UNITY_OUTPUT_COMPLETE() -#endif - -/*------------------------------------------------------- - * Footprint - *-------------------------------------------------------*/ - -#ifndef UNITY_LINE_TYPE -#define UNITY_LINE_TYPE UNITY_UINT -#endif - -#ifndef UNITY_COUNTER_TYPE -#define UNITY_COUNTER_TYPE UNITY_UINT -#endif - -/*------------------------------------------------------- - * Language Features Available - *-------------------------------------------------------*/ -#if !defined(UNITY_WEAK_ATTRIBUTE) && !defined(UNITY_WEAK_PRAGMA) -# if defined(__GNUC__) || defined(__ghs__) /* __GNUC__ includes clang */ -# if !(defined(__WIN32__) && defined(__clang__)) && !defined(__TMS470__) -# define UNITY_WEAK_ATTRIBUTE __attribute__((weak)) -# endif -# endif -#endif - -#ifdef UNITY_NO_WEAK -# undef UNITY_WEAK_ATTRIBUTE -# undef UNITY_WEAK_PRAGMA -#endif - - -/*------------------------------------------------------- - * Internal Structs Needed - *-------------------------------------------------------*/ - -typedef void (*UnityTestFunction)(void); - -#define UNITY_DISPLAY_RANGE_INT (0x10) -#define UNITY_DISPLAY_RANGE_UINT (0x20) -#define UNITY_DISPLAY_RANGE_HEX (0x40) - -typedef enum -{ -UNITY_DISPLAY_STYLE_INT = sizeof(int)+ UNITY_DISPLAY_RANGE_INT, - UNITY_DISPLAY_STYLE_INT8 = 1 + UNITY_DISPLAY_RANGE_INT, - UNITY_DISPLAY_STYLE_INT16 = 2 + UNITY_DISPLAY_RANGE_INT, - UNITY_DISPLAY_STYLE_INT32 = 4 + UNITY_DISPLAY_RANGE_INT, -#ifdef UNITY_SUPPORT_64 - UNITY_DISPLAY_STYLE_INT64 = 8 + UNITY_DISPLAY_RANGE_INT, -#endif - -UNITY_DISPLAY_STYLE_UINT = sizeof(unsigned) + UNITY_DISPLAY_RANGE_UINT, - UNITY_DISPLAY_STYLE_UINT8 = 1 + UNITY_DISPLAY_RANGE_UINT, - UNITY_DISPLAY_STYLE_UINT16 = 2 + UNITY_DISPLAY_RANGE_UINT, - UNITY_DISPLAY_STYLE_UINT32 = 4 + UNITY_DISPLAY_RANGE_UINT, -#ifdef UNITY_SUPPORT_64 - UNITY_DISPLAY_STYLE_UINT64 = 8 + UNITY_DISPLAY_RANGE_UINT, -#endif - - UNITY_DISPLAY_STYLE_HEX8 = 1 + UNITY_DISPLAY_RANGE_HEX, - UNITY_DISPLAY_STYLE_HEX16 = 2 + UNITY_DISPLAY_RANGE_HEX, - UNITY_DISPLAY_STYLE_HEX32 = 4 + UNITY_DISPLAY_RANGE_HEX, -#ifdef UNITY_SUPPORT_64 - UNITY_DISPLAY_STYLE_HEX64 = 8 + UNITY_DISPLAY_RANGE_HEX, -#endif - - UNITY_DISPLAY_STYLE_UNKNOWN -} UNITY_DISPLAY_STYLE_T; - -typedef enum -{ - UNITY_EQUAL_TO = 1, - UNITY_GREATER_THAN = 2, - UNITY_GREATER_OR_EQUAL = 2 + UNITY_EQUAL_TO, - UNITY_SMALLER_THAN = 4, - UNITY_SMALLER_OR_EQUAL = 4 + UNITY_EQUAL_TO -} UNITY_COMPARISON_T; - -#ifndef UNITY_EXCLUDE_FLOAT -typedef enum UNITY_FLOAT_TRAIT -{ - UNITY_FLOAT_IS_NOT_INF = 0, - UNITY_FLOAT_IS_INF, - UNITY_FLOAT_IS_NOT_NEG_INF, - UNITY_FLOAT_IS_NEG_INF, - UNITY_FLOAT_IS_NOT_NAN, - UNITY_FLOAT_IS_NAN, - UNITY_FLOAT_IS_NOT_DET, - UNITY_FLOAT_IS_DET, - UNITY_FLOAT_INVALID_TRAIT -} UNITY_FLOAT_TRAIT_T; -#endif - -typedef enum -{ - UNITY_ARRAY_TO_VAL = 0, - UNITY_ARRAY_TO_ARRAY -} UNITY_FLAGS_T; - -struct UNITY_STORAGE_T -{ - const char* TestFile; - const char* CurrentTestName; -#ifndef UNITY_EXCLUDE_DETAILS - const char* CurrentDetail1; - const char* CurrentDetail2; -#endif - UNITY_LINE_TYPE CurrentTestLineNumber; - UNITY_COUNTER_TYPE NumberOfTests; - UNITY_COUNTER_TYPE TestFailures; - UNITY_COUNTER_TYPE TestIgnores; - UNITY_COUNTER_TYPE CurrentTestFailed; - UNITY_COUNTER_TYPE CurrentTestIgnored; -#ifndef UNITY_EXCLUDE_SETJMP_H - jmp_buf AbortFrame; -#endif -}; - -extern struct UNITY_STORAGE_T Unity; - -/*------------------------------------------------------- - * Test Suite Management - *-------------------------------------------------------*/ - -void UnityBegin(const char* filename); -int UnityEnd(void); -void UnityConcludeTest(void); -void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum); - -/*------------------------------------------------------- - * Details Support - *-------------------------------------------------------*/ - -#ifdef UNITY_EXCLUDE_DETAILS -#define UNITY_CLR_DETAILS() -#define UNITY_SET_DETAIL(d1) -#define UNITY_SET_DETAILS(d1,d2) -#else -#define UNITY_CLR_DETAILS() { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } -#define UNITY_SET_DETAIL(d1) { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } -#define UNITY_SET_DETAILS(d1,d2) { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } - -#ifndef UNITY_DETAIL1_NAME -#define UNITY_DETAIL1_NAME "Function" -#endif - -#ifndef UNITY_DETAIL2_NAME -#define UNITY_DETAIL2_NAME "Argument" -#endif -#endif - -/*------------------------------------------------------- - * Test Output - *-------------------------------------------------------*/ - -void UnityPrint(const char* string); -void UnityPrintLen(const char* string, const UNITY_UINT32 length); -void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number); -void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style); -void UnityPrintNumber(const UNITY_INT number_to_print); -void UnityPrintNumberUnsigned(const UNITY_UINT number); -void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print); - -#ifndef UNITY_EXCLUDE_FLOAT_PRINT -void UnityPrintFloat(const UNITY_DOUBLE input_number); -#endif - -/*------------------------------------------------------- - * Test Assertion Functions - *------------------------------------------------------- - * Use the macros below this section instead of calling - * these directly. The macros have a consistent naming - * convention and will pull in file and line information - * for you. */ - -void UnityAssertEqualNumber(const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style); - -void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, - const UNITY_INT actual, - const UNITY_COMPARISON_T compare, - const char *msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style); - -void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style, - const UNITY_FLAGS_T flags); - -void UnityAssertBits(const UNITY_INT mask, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualString(const char* expected, - const char* actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualStringLen(const char* expected, - const char* actual, - const UNITY_UINT32 length, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualStringArray( UNITY_INTERNAL_PTR expected, - const char** actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertEqualMemory( UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 length, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertNumbersWithin(const UNITY_UINT delta, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style); - -void UnityFail(const char* msg, const UNITY_LINE_TYPE line); - -void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line); - -#ifndef UNITY_EXCLUDE_FLOAT -void UnityAssertFloatsWithin(const UNITY_FLOAT delta, - const UNITY_FLOAT expected, - const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertFloatSpecial(const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style); -#endif - -#ifndef UNITY_EXCLUDE_DOUBLE -void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, - const UNITY_DOUBLE expected, - const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style); -#endif - -/*------------------------------------------------------- - * Helpers - *-------------------------------------------------------*/ - -UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size); -#ifndef UNITY_EXCLUDE_FLOAT -UNITY_INTERNAL_PTR UnityFloatToPtr(const float num); -#endif -#ifndef UNITY_EXCLUDE_DOUBLE -UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num); -#endif - -/*------------------------------------------------------- - * Error Strings We Might Need - *-------------------------------------------------------*/ - -extern const char UnityStrErrFloat[]; -extern const char UnityStrErrDouble[]; -extern const char UnityStrErr64[]; - -/*------------------------------------------------------- - * Test Running Macros - *-------------------------------------------------------*/ - -#ifndef UNITY_EXCLUDE_SETJMP_H -#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0) -#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) -#else -#define TEST_PROTECT() 1 -#define TEST_ABORT() return -#endif - -/* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */ -#ifndef RUN_TEST -#ifdef __STDC_VERSION__ -#if __STDC_VERSION__ >= 199901L -#define RUN_TEST(...) UnityDefaultTestRun(RUN_TEST_FIRST(__VA_ARGS__), RUN_TEST_SECOND(__VA_ARGS__)) -#define RUN_TEST_FIRST(...) RUN_TEST_FIRST_HELPER(__VA_ARGS__, throwaway) -#define RUN_TEST_FIRST_HELPER(first, ...) (first), #first -#define RUN_TEST_SECOND(...) RUN_TEST_SECOND_HELPER(__VA_ARGS__, __LINE__, throwaway) -#define RUN_TEST_SECOND_HELPER(first, second, ...) (second) -#endif -#endif -#endif - -/* If we can't do the tricky version, we'll just have to require them to always include the line number */ -#ifndef RUN_TEST -#ifdef CMOCK -#define RUN_TEST(func, num) UnityDefaultTestRun(func, #func, num) -#else -#define RUN_TEST(func) UnityDefaultTestRun(func, #func, __LINE__) -#endif -#endif - -#define TEST_LINE_NUM (Unity.CurrentTestLineNumber) -#define TEST_IS_IGNORED (Unity.CurrentTestIgnored) -#define UNITY_NEW_TEST(a) \ - Unity.CurrentTestName = (a); \ - Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)(__LINE__); \ - Unity.NumberOfTests++; - -#ifndef UNITY_BEGIN -#define UNITY_BEGIN() UnityBegin(__FILE__) -#endif - -#ifndef UNITY_END -#define UNITY_END() UnityEnd() -#endif - -/*----------------------------------------------- - * Command Line Argument Support - *-----------------------------------------------*/ - -#ifdef UNITY_USE_COMMAND_LINE_ARGS -int UnityParseOptions(int argc, char** argv); -int UnityTestMatches(void); -#endif - -/*------------------------------------------------------- - * Basic Fail and Ignore - *-------------------------------------------------------*/ - -#define UNITY_TEST_FAIL(line, message) UnityFail( (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_IGNORE(line, message) UnityIgnore( (message), (UNITY_LINE_TYPE)(line)) - -/*------------------------------------------------------- - * Test Asserts - *-------------------------------------------------------*/ - -#define UNITY_TEST_ASSERT(condition, line, message) if (condition) {} else {UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), (message));} -#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (UNITY_LINE_TYPE)(line), (message)) - -#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_EQUAL_INT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_EQUAL_INT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_EQUAL_UINT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_EQUAL_UINT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_EQUAL_HEX8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_EQUAL_HEX16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_EQUAL_HEX32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_BITS(mask, expected, actual, line, message) UnityAssertBits((UNITY_INT)(mask), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line)) - -#define UNITY_TEST_ASSERT_GREATER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) - -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) - -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) - -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) - -#define UNITY_TEST_ASSERT_INT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_INT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_INT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_INT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_UINT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_UINT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_UINT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_UINT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_HEX8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_HEX16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_HEX32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) - -#define UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_PTR_TO_INT)(expected), (UNITY_PTR_TO_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER) -#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, message) UnityAssertEqualString((const char*)(expected), (const char*)(actual), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, message) UnityAssertEqualStringLen((const char*)(expected), (const char*)(actual), (UNITY_UINT32)(len), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_MEMORY(expected, actual, len, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), 1, (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) - -#define UNITY_TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) - -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), sizeof(int)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), sizeof(unsigned int)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT16)(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT32)(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_PTR_TO_INT) (expected), sizeof(int*)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) - -#ifdef UNITY_SUPPORT_64 -#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)expected, 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT64)expected, 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)expected, 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#else -#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#endif - -#ifdef UNITY_EXCLUDE_FLOAT -#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#else -#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray((UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray(UnityFloatToPtr(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) -#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) -#endif - -#ifdef UNITY_EXCLUDE_DOUBLE -#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#else -#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)line) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual, (UNITY_LINE_TYPE)(line), message) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray((UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)line, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray(UnityDoubleToPtr(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)line, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) -#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) -#endif - -/* End of UNITY_INTERNALS_H */ -#endif From 79a3c032d56a8013c4d34408e14415f38efe88d4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 10 Apr 2020 11:35:01 -0700 Subject: [PATCH 458/844] Add config file --- demos/Makefile | 3 ++- demos/config.h | 4 ++++ libraries/standard/mqtt/include/mqtt.h | 4 +--- libraries/standard/mqtt/include/mqtt_lightweight.h | 3 +-- 4 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 demos/config.h diff --git a/demos/Makefile b/demos/Makefile index 1a69ddbfb8..5c98b7632a 100644 --- a/demos/Makefile +++ b/demos/Makefile @@ -1,6 +1,7 @@ CC = gcc -INCLUDE_DIRS = -I ../libraries/standard/mqtt/include +INCLUDE_DIRS = -I . \ + -I ../libraries/standard/mqtt/include SRC_FILES = $(shell find . -name '*.c') \ $(shell find ../libraries/standard/mqtt/src -name '*.c') diff --git a/demos/config.h b/demos/config.h new file mode 100644 index 0000000000..fc12f98c19 --- /dev/null +++ b/demos/config.h @@ -0,0 +1,4 @@ +/* Set network context to socket (int). */ +typedef int MQTTNetworkContext_t; + +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 8f0d3c20d7..5fc21bf419 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -1,8 +1,6 @@ +#include "config.h" #include "mqtt_lightweight.h" -#warning "Temporary workaround, remove following line" -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 - struct MQTTApplicationCallbacks; typedef struct MQTTApplicationCallbacks MQTTApplicationCallbacks_t; diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 3d5557b04c..6e61fc5e68 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -2,8 +2,7 @@ #include #include -#warning "Temporary workaround, remove following line" -typedef int MQTTNetworkContext_t; +#include "config.h" struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; From 6e92ba66c323bb76e4bf6b8f98ef3e9163b2c8eb Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 14 Apr 2020 14:46:30 -0700 Subject: [PATCH 459/844] Add MQTT lightweight files with CONNECT implementation (#866) --- .../standard/mqtt/include/mqtt_lightweight.h | 55 +++- .../standard/mqtt/src/mqtt_lightweight.c | 287 +++++++++++++++++- 2 files changed, 321 insertions(+), 21 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 6e61fc5e68..702a2e76d2 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -1,9 +1,35 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_LIGHTWEIGHT_H +#define MQTT_LIGHTWEIGHT_H + #include #include #include #include "config.h" +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) + struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -19,15 +45,17 @@ typedef struct MqttPublishInfo MQTTPublishInfo_t; struct MQTTPacketInfo; typedef struct MQTTPacketInfo MQTTPacketInfo_t; -typedef int32_t (* TransportReadFunc_t )( MQTTNetworkContext_t context, +typedef int32_t (* TransportRecvFunc_t )( MQTTNetworkContext_t context, void * pBuffer, - size_t bytesToRead ); + size_t bytesToRecv ); typedef enum MQTTStatus { MQTTSuccess = 0, MQTTBadParameter, MQTTNoMemory, + MQTTSendFailed, + MQTTRecvFailed, MQTTBadResponse, MQTTServerRefused } MQTTStatus_t; @@ -83,12 +111,14 @@ struct MQTTPacketInfo }; MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, size_t * const pPacketSize ); MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, @@ -99,13 +129,13 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, size_t * const pRemainingLength, @@ -114,20 +144,21 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer, + const MQTTFixedBuffer_t * const pBuffer, size_t * const pHeaderSize ); -MQTTStatus_t MQTT_SerializeDisconnect( MQTTFixedBuffer_t * const pBuffer ); +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); -MQTTStatus_t MQTT_SerializePingreq( MQTTFixedBuffer_t * const pBuffer ); +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ); -MQTTStatus_t MQTT_GetPacket( TransportReadFunc_t readFunc, - MQTTPacketInfo_t * const pIncomingPacket ); +MQTTStatus_t MQTT_GetIncomingPacket( TransportRecvFunc_t recvFunc, + MQTTNetworkContext_t networkContext, + MQTTPacketInfo_t * const pIncomingPacket ); MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, @@ -136,3 +167,5 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, bool * const pSessionPresent ); + +#endif diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 6175c501c4..f8e52e70c8 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1,16 +1,281 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + #include "mqtt_lightweight.h" +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) + +#define MQTT_PACKET_CONNECT_HEADER_SIZE ( 10UL ) +#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) + +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) +#define MQTT_CONNECT_FLAG_WILL ( 2 ) +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) + +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) + +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) + +static size_t remainingLengthEncodedSize( size_t length ) +{ + size_t encodedSize; + + /* Determine how many bytes are needed to encode length. + * The values below are taken from the MQTT 3.1.1 spec. */ + + /* 1 byte is needed to encode lengths between 0 and 127. */ + if( length < 128U ) + { + encodedSize = 1U; + } + /* 2 bytes are needed to encode lengths between 128 and 16,383. */ + else if( length < 16384U ) + { + encodedSize = 2U; + } + /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */ + else if( length < 2097152U ) + { + encodedSize = 3U; + } + /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */ + else + { + encodedSize = 4U; + } + + return encodedSize; +} + +static uint8_t * encodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte; + uint8_t * pLengthEnd = pDestination; + size_t remainingLength = length; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = ( uint8_t ) ( remainingLength % 128U ); + remainingLength = remainingLength / 128U; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( remainingLength > 0U ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( remainingLength > 0U ); + + return pLengthEnd; +} + + +static uint8_t * encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) +{ + uint8_t * pBuffer = pDestination; + + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; + + /* Copy the string into pBuffer. */ + ( void ) memcpy( pBuffer, source, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pBuffer += sourceLength; + + return pBuffer; +} + MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, size_t * const pPacketSize ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + size_t remainingLength; + + /* The CONNECT packet will always include a 10-byte variable header. */ + size_t connectPacketSize = MQTT_PACKET_CONNECT_HEADER_SIZE; + + /* Add the length of the client identifier. */ + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); + + /* Add the lengths of the will message and topic name if provided. */ + if( pWillInfo != NULL ) + { + connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) + + pWillInfo->payloadLength + sizeof( uint16_t ); + } + + /* Add the lengths of the user name and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + } + + if( pConnectInfo->pPassword != NULL ) + { + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + } + + /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has + * been calculated. */ + remainingLength = connectPacketSize; + + /* Calculate the full size of the MQTT CONNECT packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ + connectPacketSize += 1U + remainingLengthEncodedSize( connectPacketSize ); + + /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ + if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) + { + status = MQTTBadParameter; + } + else + { + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; + } + + return status; } MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * const pBuffer ) { + uint8_t connectFlags = 0; + uint8_t * pIndex = pBuffer->pBuffer; + + /* The first byte in the CONNECT packet is the control packet type. */ + *pIndex = MQTT_PACKET_TYPE_CONNECT; + pIndex++; + + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + pIndex = encodeRemainingLength( pIndex, remainingLength ); + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + pIndex = encodeString( pIndex, "MQTT", 4 ); + + /* The MQTT protocol version is the second field of the variable header. */ + *pIndex = MQTT_VERSION_3_1_1; + pIndex++; + + /* Set the clean session flag if needed. */ + if( pConnectInfo->cleanSession == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + } + + /* Set the flags for username and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + } + + if( pConnectInfo->pPassword != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + } + + /* Set will flag if a Last Will and Testament is provided. */ + if( pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for Will QoS 1 or 2. */ + if( pWillInfo->qos == MQTTQoS1 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + } + else if( pWillInfo->qos == MQTTQoS2 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + } + + if( pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + } + + *pIndex = connectFlags; + pIndex++; + + /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ + *pIndex = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pIndex + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + pIndex += 2; + + /* Write the client identifier into the CONNECT packet. */ + pIndex = encodeString( pIndex, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + + /* Write the will topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) + { + pIndex = encodeString( pIndex, + pWillInfo->pTopicName, + pWillInfo->topicNameLength ); + pIndex = encodeString( pIndex, + pWillInfo->pPayload, + ( uint16_t ) pWillInfo->payloadLength ); + } + + /* Encode the user name if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength ); + } + + /* Encode the password if provided. */ + if( pConnectInfo->pPassword != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); + } + return MQTTSuccess; } @@ -26,7 +291,7 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; } @@ -35,7 +300,7 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; } @@ -50,7 +315,7 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; } @@ -58,24 +323,25 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, - MQTTFixedBuffer_t * const pBuffer, + const MQTTFixedBuffer_t * const pBuffer, size_t * const pHeaderSize ) { return MQTTSuccess; } -MQTTStatus_t MQTT_SerializeDisconnect( MQTTFixedBuffer_t * const pBuffer ) +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; } -MQTTStatus_t MQTT_SerializePingreq( MQTTFixedBuffer_t * const pBuffer ) +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; } -MQTTStatus_t MQTT_GetPacket( TransportReadFunc_t readFunc, - MQTTPacketInfo_t * const pIncomingPacket ) +MQTTStatus_t MQTT_GetIncomingPacket( TransportRecvFunc_t recvFunc, + MQTTNetworkContext_t networkContext, + MQTTPacketInfo_t * const pIncomingPacket ) { return MQTTSuccess; } @@ -91,5 +357,6 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket uint16_t * const pPacketId, bool * const pSessionPresent ) { + return MQTTSuccess; } From 4379d0b9bd515b9c4a5f2e810a01b2350efc66ea Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 15 Apr 2020 15:50:47 -0700 Subject: [PATCH 460/844] Add MQTT connect API implementation (#870) --- demos/config.h | 26 +++ libraries/standard/mqtt/include/mqtt.h | 54 ++++--- .../standard/mqtt/include/mqtt_lightweight.h | 9 +- libraries/standard/mqtt/src/mqtt.c | 108 ++++++++++++- .../standard/mqtt/src/mqtt_lightweight.c | 150 ++++++++++-------- 5 files changed, 252 insertions(+), 95 deletions(-) diff --git a/demos/config.h b/demos/config.h index fc12f98c19..b7e68203f4 100644 --- a/demos/config.h +++ b/demos/config.h @@ -1,4 +1,30 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + /* Set network context to socket (int). */ typedef int MQTTNetworkContext_t; #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 + +#endif diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 5fc21bf419..aab9118e9e 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -1,3 +1,24 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + #include "config.h" #include "mqtt_lightweight.h" @@ -13,11 +34,11 @@ typedef struct MQTTContext MQTTContext_t; struct MQTTTransportInterface; typedef struct MQTTTransportInterface MQTTTransportInterface_t; -typedef int32_t (* TransportWriteFunc_t )( MQTTNetworkContext_t context, - void * pBuffer, - size_t bytesToWrite ); +typedef int32_t (* MQTTTransportSendFunc_t )( MQTTNetworkContext_t context, + const void * pMessage, + size_t bytesToSend ); -typedef uint32_t (* GetCurrentTimeFunc_t )( void ); +typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ); @@ -25,7 +46,6 @@ typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, typedef enum MQTTConnectionStatus { MQTTNotConnected, - MQTTConnectionInProgress, MQTTConnected } MQTTConnectionStatus_t; @@ -53,14 +73,14 @@ typedef enum MQTTPubAckType struct MQTTTransportInterface { - TransportWriteFunc_t write; - TransportReadFunc_t read; - MQTTNetworkContext_t * networkContext; + MQTTTransportSendFunc_t send; + MQTTTransportRecvFunc_t recv; + MQTTNetworkContext_t networkContext; }; struct MQTTApplicationCallbacks { - GetCurrentTimeFunc_t getTime; + MQTTGetCurrentTimeFunc_t getTime; MQTTEventCallback_t appCallback; }; @@ -78,25 +98,23 @@ struct MQTTContext MQTTPubAckInfo_t incomingPublishRecords[ MQTT_MAX_QUEUED_PUBLISH_MESSAGES ]; size_t incomingPublishCount; - MQTTTransportInterface_t transportInterface; - uint16_t nextPacketId; - - MQTTFixedBuffer_t txBuffer; - MQTTFixedBuffer_t rxBuffer; + const MQTTTransportInterface_t * pTransportInterface; + const MQTTFixedBuffer_t * pNetworkBuffer; + uint16_t nextPacketId; MQTTConnectionStatus_t connectStatus; - MQTTApplicationCallbacks_t callbacks; - bool controlPacketSent; + const MQTTApplicationCallbacks_t * pCallbacks; + uint32_t lastPacketTime; }; void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, - const MQTTFixedBuffer_t * const pTxBuffer, - const MQTTFixedBuffer_t * const pRxBuffer ); + const MQTTFixedBuffer_t * const pNetworkBuffer ); MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, bool * const pSessionPresent ); MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 702a2e76d2..cb1696c925 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -29,6 +29,7 @@ #include "config.h" #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -45,9 +46,9 @@ typedef struct MqttPublishInfo MQTTPublishInfo_t; struct MQTTPacketInfo; typedef struct MQTTPacketInfo MQTTPacketInfo_t; -typedef int32_t (* TransportRecvFunc_t )( MQTTNetworkContext_t context, - void * pBuffer, - size_t bytesToRecv ); +typedef int32_t (* MQTTTransportRecvFunc_t )( MQTTNetworkContext_t context, + void * pBuffer, + size_t bytesToRecv ); typedef enum MQTTStatus { @@ -156,7 +157,7 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ); -MQTTStatus_t MQTT_GetIncomingPacket( TransportRecvFunc_t recvFunc, +MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, MQTTNetworkContext_t networkContext, MQTTPacketInfo_t * const pIncomingPacket ); diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index af545a1be0..504709fd24 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1,19 +1,110 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + #include "mqtt.h" void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, - const MQTTFixedBuffer_t * const pTxBuffer, - const MQTTFixedBuffer_t * const pRxBuffer ) + const MQTTFixedBuffer_t * const pNetworkBuffer ) { + memset( pContext, 0x00, sizeof( MQTTContext_t ) ); + pContext->connectStatus = MQTTNotConnected; + pContext->pTransportInterface = pTransportInterface; + pContext->pCallbacks = pCallbacks; + pContext->pNetworkBuffer = pNetworkBuffer; + + /* Zero is not a valid packet ID per MQTT spec. Start from 1. */ + pContext->nextPacketId = 1; } MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, bool * const pSessionPresent ) { - return MQTTSuccess; + size_t remainingLength, packetSize; + int32_t bytesSent; + uint32_t sendTime; + MQTTPacketInfo_t incomingPacket; + + MQTTStatus_t status = MQTT_GetConnectPacketSize( pConnectInfo, + pWillInfo, + &remainingLength, + &packetSize ); + + if( status == MQTTSuccess ) + { + status = MQTT_SerializeConnect( pConnectInfo, + pWillInfo, + remainingLength, + pContext->pNetworkBuffer ); + } + + if( status == MQTTSuccess ) + { + sendTime = pContext->pCallbacks->getTime(); + + bytesSent= pContext->pTransportInterface->send( pContext->pTransportInterface->networkContext, + pContext->pNetworkBuffer->pBuffer, + packetSize ); + + if( ( bytesSent > 0 ) && ( ( size_t ) bytesSent == packetSize ) ) + { + pContext->lastPacketTime = sendTime; + } + else + { + status = MQTTSendFailed; + } + } + + if( status == MQTTSuccess ) + { + status = MQTT_GetIncomingPacket( pContext->pTransportInterface->recv, + pContext->pTransportInterface->networkContext, + &incomingPacket ); + } + + if( status == MQTTSuccess ) + { + if( incomingPacket.type == MQTT_PACKET_TYPE_CONNACK ) + { + status = MQTT_DeserializeAck( &incomingPacket, NULL, pSessionPresent ); + } + else + { + status = MQTTBadResponse; + } + } + + if( status == MQTTSuccess ) + { + pContext->connectStatus = MQTTConnected; + } + + return status; } MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, @@ -54,5 +145,14 @@ MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) { - return ( uint16_t) 1; + uint16_t packetId = pContext->nextPacketId; + + pContext->nextPacketId++; + + if( pContext->nextPacketId == 0 ) + { + pContext->nextPacketId = 1; + } + + return packetId; } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index f8e52e70c8..a99ea3bc22 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -182,101 +182,113 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ) { + MQTTStatus_t status = MQTTSuccess; uint8_t connectFlags = 0; uint8_t * pIndex = pBuffer->pBuffer; - /* The first byte in the CONNECT packet is the control packet type. */ - *pIndex = MQTT_PACKET_TYPE_CONNECT; - pIndex++; - - /* The remaining length of the CONNECT packet is encoded starting from the - * second byte. The remaining length does not include the length of the fixed - * header or the encoding of the remaining length. */ - pIndex = encodeRemainingLength( pIndex, remainingLength ); - - /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable - * header. This string is 4 bytes long. */ - pIndex = encodeString( pIndex, "MQTT", 4 ); + /* Check that the full packet size fits within the given buffer. */ + size_t connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; - /* The MQTT protocol version is the second field of the variable header. */ - *pIndex = MQTT_VERSION_3_1_1; - pIndex++; - - /* Set the clean session flag if needed. */ - if( pConnectInfo->cleanSession == true ) + if( connectPacketSize > pBuffer->size ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + status = MQTTNoMemory; } - /* Set the flags for username and password if provided. */ - if( pConnectInfo->pUserName != NULL ) + if( status == MQTTSuccess ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); - } + /* The first byte in the CONNECT packet is the control packet type. */ + *pIndex = MQTT_PACKET_TYPE_CONNECT; + pIndex++; - if( pConnectInfo->pPassword != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); - } + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + pIndex = encodeRemainingLength( pIndex, remainingLength ); - /* Set will flag if a Last Will and Testament is provided. */ - if( pWillInfo != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + pIndex = encodeString( pIndex, "MQTT", 4 ); - /* Flags only need to be changed for Will QoS 1 or 2. */ - if( pWillInfo->qos == MQTTQoS1 ) + /* The MQTT protocol version is the second field of the variable header. */ + *pIndex = MQTT_VERSION_3_1_1; + pIndex++; + + /* Set the clean session flag if needed. */ + if( pConnectInfo->cleanSession == true ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); } - else if( pWillInfo->qos == MQTTQoS2 ) + + /* Set the flags for username and password if provided. */ + if( pConnectInfo->pUserName != NULL ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); } - if( pWillInfo->retain == true ) + if( pConnectInfo->pPassword != NULL ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); } - } - *pIndex = connectFlags; - pIndex++; + /* Set will flag if a Last Will and Testament is provided. */ + if( pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for Will QoS 1 or 2. */ + if( pWillInfo->qos == MQTTQoS1 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + } + else if( pWillInfo->qos == MQTTQoS2 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + } + + if( pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + } - /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ - *pIndex = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); - *( pIndex + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); - pIndex += 2; + *pIndex = connectFlags; + pIndex++; - /* Write the client identifier into the CONNECT packet. */ - pIndex = encodeString( pIndex, - pConnectInfo->pClientIdentifier, - pConnectInfo->clientIdentifierLength ); + /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ + *pIndex = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pIndex + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + pIndex += 2; - /* Write the will topic name and message into the CONNECT packet if provided. */ - if( pWillInfo != NULL ) - { - pIndex = encodeString( pIndex, - pWillInfo->pTopicName, - pWillInfo->topicNameLength ); + /* Write the client identifier into the CONNECT packet. */ pIndex = encodeString( pIndex, - pWillInfo->pPayload, - ( uint16_t ) pWillInfo->payloadLength ); - } + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); - /* Encode the user name if provided. */ - if( pConnectInfo->pUserName != NULL ) - { - pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength ); - } + /* Write the will topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) + { + pIndex = encodeString( pIndex, + pWillInfo->pTopicName, + pWillInfo->topicNameLength ); + pIndex = encodeString( pIndex, + pWillInfo->pPayload, + ( uint16_t ) pWillInfo->payloadLength ); + } - /* Encode the password if provided. */ - if( pConnectInfo->pPassword != NULL ) - { - pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); + /* Encode the user name if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength ); + } + + /* Encode the password if provided. */ + if( pConnectInfo->pPassword != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); + } } - return MQTTSuccess; + return status; } MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, @@ -339,7 +351,7 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) return MQTTSuccess; } -MQTTStatus_t MQTT_GetIncomingPacket( TransportRecvFunc_t recvFunc, +MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, MQTTNetworkContext_t networkContext, MQTTPacketInfo_t * const pIncomingPacket ) { From 60f64c6fbee2c59c7d4c22e13586ef95076083ce Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 16 Apr 2020 16:00:39 -0700 Subject: [PATCH 461/844] Add API header file and skeleton of definitions. (#873) * Add API header file and skeleton of definitions. Add unit testing skeleton for one example function. The instructions for the unit testing are in the README.md. --- libraries/standard/http/include/http_client.h | 426 ++++++++++++++++++ libraries/standard/http/src/http_client.c | 42 ++ libraries/standard/http/test/Makefile | 30 ++ libraries/standard/http/test/README.md | 17 + libraries/standard/http/test/common.h | 4 + .../http/test/test-HTTPClient_AddHeader.c | 9 + third_party/http_parser/http_parser | 1 + 7 files changed, 529 insertions(+) create mode 100644 libraries/standard/http/include/http_client.h create mode 100644 libraries/standard/http/src/http_client.c create mode 100644 libraries/standard/http/test/Makefile create mode 100644 libraries/standard/http/test/README.md create mode 100644 libraries/standard/http/test/common.h create mode 100644 libraries/standard/http/test/test-HTTPClient_AddHeader.c create mode 160000 third_party/http_parser/http_parser diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h new file mode 100644 index 0000000000..e7afb6fc82 --- /dev/null +++ b/libraries/standard/http/include/http_client.h @@ -0,0 +1,426 @@ +#ifndef HTTP_CLIENT_H_ +#define HTTP_CLIENT_H_ + +#include +#include + +/** + * @brief Maximum size, in bytes, of headers allowed from the server. + * + * If the total size in bytes of the headers sent from this server exceeds this + * configuration, then the status code #HTTP_SECURITY_ALERT_HEADERS is + * returned from #HTTPClient_Send. + */ +#ifndef HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES + #define HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES 2048U +#endif + +/** + * @brief The HTTP header "User-Agent" value. + * + * The following headerline is automatically written to + * #HTTPRequestHeaders_t.pBuffer: + * "User-Agent: my-platform-name\r\n" + */ +#ifndef HTTP_USER_AGENT_VALUE + #define HTTP_USER_AGENT_VALUE "my-platform-name" +#endif + +/** + * Supported HTTP request methods. + */ +#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ +#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ +#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ +#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ + +/** + * Flags for #HTTPRequestInfo_t.flags. + * These flags control what headers are written or not to the + * #HttpRequestHeaders_t.pBuffer. + */ +/** + * @brief Set this flag to indicate the request is for a persistent connection. + * + * Setting this will cause a "Connection: Keep-Alive" to be written to the + * request. + */ +#define HTTP_REQUEST_KEEP_ALIVE_FLAG 0x1U +/** + * @brief Set this flag to disable automatically writing the Content-Length + * header. + */ +#define HTTP_REQUEST_DISABLE_CONTENT_LENGTH_FLAG 0x2U + +/** + * Flags for #HTTPResponse_t.flags. + * These flags are populated in #HTTPResponse_t.flags by the #HTTPClient_Send() + * function. + */ + /** + * @brief This will be set to true if header "Connection: close" is found. + * + * If a "Connection: close" header is present the application should always + * close the connection. + */ +#define HTTP_RESPONSE_CONNECTION_CLOSE_FLAG 0x1U +/** + * @brief This will be set to true if header "Connection: Keep-Alive" is found. + */ +#define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U + +/** + * @brief The HTTPNetworkContext is an incomplete type. The application must + * define HTTPNetworkContext to the type of their network context. This context + * is passed into the network interface functions. + */ +struct HTTPNetworkContext; +typedef struct HTTPNetworkContext HTTPNetworkContext_t; + +/** + * @brief Transport interface for sending data over the network. + * + * If the number of bytes written returned is less than bytesToWrite, then + * #HTTPClient_Send will return HTTP_NETWORK_ERROR. If a negative value is + * returned then this #HTTPClient_Send will also return #HTTP_NETWORK_ERROR. + * + * @param[in] context User defined context. + * @param[in] pBuffer Buffer to write to the network stack. + * @param[in] bytesToWrite Number of bytes to write to the network. + * @return The number of bytes written or a negative network error code. + */ +typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t *pContext, + const void * pBuffer, + size_t bytesToWrite ); + +/** + * @brief Transport interface for reading data on the network. + * + * This function will read up to bytesToRead amount of data from the network. + * If this function returns a value less than zero, then #HTTPClient_Send will + * return #HTTP_NETWORK_ERROR. If this function returns less than the + * bytesToRead and greater than zero, then this function will be invoked again + * if the data in pBuffer contains a partial HTTP response message. + * + * @param[in] context User defined context. + * @param[in] pBuffer Buffer to read network data into. + * @param[in] bytesToRead Number of bytes requested from the network. + * @return The number of bytes read or a negative error code. + */ +typedef int32_t (* HTTPTransportRecv_t )( HTTPNetworkContext_t *pContext, + const void * pBuffer, + size_t bytesToRead ); + +/** + * @brief The HTTP Client library transport layer interface. + */ +typedef struct HTTPTransportInterface +{ + HTTPTransportRecv_t recv; + HTTPTransportSend_t send; + HTTPNetworkContext_t* pContext; +} HTTPTransportInterface_t; + +/** + * @brief The HTTP Client library return status. + */ +typedef enum HTTPStatus +{ + HTTP_SUCCESS = 0, + HTTP_INVALID_PARAMETER, + HTTP_NETWORK_ERROR, + HTTP_NOT_SUPPORTED, + HTTP_PARTIAL_RESPONSE, + HTTP_INSUFFICIENT_MEMORY, + HTTP_INTERNAL_ERROR, + HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED, + HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER, + HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, + /* TODO: Add return codes as implementation continues. */ +} HTTPStatus_t; + +/** + * @brief Represents header data that will be sent in an HTTP request. + * + * The memory for the header data buffer is supplied by the user. Information in + * the buffer will be filled by calling #HTTPClient_InitializeHeaders. + */ +typedef struct HTTPRequestHeaders +{ + + /** + * @brief Buffer to hold the raw HTTP request headers. + * + * This buffer is supplied by the application. + * + * This buffer is owned by the library during #HTTPClient_AddHeader, + * #HTTPClient_AddRangeHeader, #HTTPClient_InitializeRequestHeaders, and + * #HTTPClient_Send. This buffer should not be modifed until + * after these functions return. + * + * For optimization this buffer may be re-used with the response. The user + * can re-use this buffer for the storing the response from the server in + * #HTTPResponse_t.pBuffer. + */ + uint8_t* pBuffer; + size_t bufferLen; /**< The length of pBuffer in bytes. */ + + /** + * @brief The actual size in bytes of headers in the buffer. This field + * is updated by the HTTP Client library functions #HTTPClient_AddHeader, + * and #HTTPClient_InitializeRequestHeaders. + */ + size_t headersLen; +} HTTPRequestHeaders_t; + +/** + * @brief Configurations of the initial request headers. + */ +typedef struct HTTPRequestInfo +{ + /** + * @brief The HTTP request method e.g. "GET", "POST", "PUT", or "HEAD". + */ + const char* method; + size_t methodLen; /**< The length of the method in bytes. */ + + /** + * @brief The Request-URI to the objects of interest, e.g. "/path/to/item.txt". + */ + const char* pPath; + size_t pathLen; /**< The length of the path in bytes. */ + + /** + * @brief The server's host name, e.g. "my-storage.my-cloud.com". + * + * The host does not have a "https://" or "http://" prepending. + */ + const char* pHost; + size_t hostLen; /**< The length of the host in bytes. */ + + /** + * @brief Flags to activate other request header configurations. + */ + uint32_t flags; +} HTTPRequestInfo_t; + + + +/** + * @brief Callback to intercept headers during the first parse through of the + * response as it is received from the network. + */ +typedef struct httpResponseParsingCallback { + /** + * @brief Invoked when both a header field and its associated header value are found. + * @param[in] pContext User context. + * @param[in] fieldLoc Location of the header field name in the response buffer. + * @param[in] fieldLen Length in bytes of the field name. + * @param[in] valueLoc Location of the header value in the response buffer. + * @param[in] valueLen Length in bytes of the value. + * @param[in] statusCode The HTTP response status-code. + */ + void (*onHeaderCallback)( void* pContext, const char* fieldLoc, size_t fieldLen, const char* valueLoc, size_t valueLen, uint16_t statusCode ); + + /* Private context for the application. */ + void *pContext; +} HTTPClient_HeaderParsingCallback_t; + +/** + * @brief Represents an HTTP response. + */ +typedef struct HTTPResponse +{ + /** + * @brief Buffer for both the raw HTTP header and body. + * + * This buffer is supplied by the application. + * + * This buffer is owned by the library during #HTTPClient_Send and + * #HTTPClient_ReadHeader. This buffer should not be modifed until after + * these functions return. + * + * For optimization this buffer may be used with the request headers. The + * request header buffer is configured in #HTTPRequestHeaders_t.pBuffer. + * When the same buffer is used for the request headers, #HTTPClient_Send + * will send the headers in the buffer first, then fill the buffer with + * the response message. + */ + uint8_t* pBuffer; + size_t bufferLen; /**< The length of the response buffer in bytes. */ + + /** + * @brief Optional callback for intercepting the header during the first + * parse through of the response as is it receive from the network. + * Set to NULL to disable. + */ + HTTPClient_HeaderParsingCallback_t* pHeaderParsingCallback; + + /** + * @brief The starting location of the response headers in pBuffer. + * + * This is updated by #HTTPClient_Send. + */ + uint8_t* pHeaders; + + /** + * @brief Byte length of the response headers in pBuffer. + * + * This is updated by #HTTPClient_Send. + */ + size_t headersLen; + + /** + * @brief The starting location of the response body in pBuffer. + * + * This is updated by #HTTPClient_Send. + */ + uint8_t* pBody; + + /** + * @brief Byte length of the body in pBuffer. + * + * This is updated by #HTTPClient_Send. + */ + size_t bodyLen; + + /* Useful HTTP header values found. */ + + /** + * @brief The HTTP response Status-Code. + * + * This is updated by #HTTPClient_Send. + */ + uint16_t statusCode; + + /** + * @brief The value in the "Content-Length" header is returned here. + * + * This is updated by #HTTPClient_Send. + */ + size_t contentLength; + + /** + * @brief Count of the headers sent by the server. + * + * This is updated by #HTTPClient_Send. + */ + size_t headerCount; + + /** + * @brief Flags of useful headers found in the response. + * + * This is updated by #HTTPClient_Send. + */ + uint32_t flags; +} HTTPResponse_t; + +/** + * @brief Initialize the request headers, stored in + * #HTTPRequestHeaders_t.pBuffer, with initial configurations from + * #HTTPRequestInfo_t. + * + * Upon return, #HTTPRequestHeaders_t.headersLen will be updated with the number + * of bytes written. + * + * TODO: Expand documentation. + * + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] pRequestInfo Initial request header configurations. + * @return #HTTP_SUCCESS if successful, an error code otherwise. + * TODO: Update for exact error codes returned. + */ +HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, + const HTTPRequestInfo_t * pRequestInfo ); + +/** + * @brief Add a header to the request headers stored in + * #HTTPRequestHeaders_t.pBuffer. + * + * Upon return, pRequestHeaders->headersLen will be updated with the number of + * bytes written. + * + * TODO: Expand documentation. + * + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] pName The header field name to write. + * @param[in] nameLen The byte length of the header field name. + * @param[in] pValue The header value to write. + * @param[in] valueLen The byte length of the header field value. + * @return #HTTP_SUCCESS if successful, an error code otherwise. + * TODO: Update for exact error codes returned. + */ +HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, + const char* pName, + size_t nameLen, + const char * pValue, + size_t valueLen ); + +/** + * @brief Add the byte range request header to the request headers store in + * #HTTPRequestHeaders_t.pBuffer. + * + * For example, if requesting for the first 1kB of a file the following would be + * written "Range: bytes=0-1024\r\n". + * + * TODO: Add documentation about rangeStart and rangeEnd configuration. + * + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] rangeStart The starting range for the requested file. + * @param[in] rangeEnd The ending range for the requested file. + * @return #HTTP_SUCCESS if successful, an error code otherwise. + * TODO: Update for exact error codes returned. + */ +HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t *pRequestHeaders, + int32_t rangeStart, + int32_t rangeEnd ); + +/** + * @brief Send the request headers in #HTTPRequestHeaders_t and request body in + * parameter pRequestBodyBuf over the transport. The response is received in + * #HTTPResponse_t. + * + * TODO: Expand documentation. + * + * @param[in] pTransport Transport interface, see #HTTPTransportInterface_t for + * more information. + * @param[in] pRequestHeaders Request configuration containing the buffer of + * headers to send. + * @param[in] pRequestBodyBuf Request entity body. + * @param[in] reqBodyBufLen The length of the request entity in bytes. + * @param[in] pResponse The response message and some notable response + * parameters will be returned here on success. + * @return #HTTP_SUCCESS if successful, an error code otherwise. + * TODO: Update for exact error codes returned. + */ +HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t* pTransport, + const HTTPRequestHeaders_t* pRequestHeaders, + const uint8_t* pRequestBodyBuf, + size_t reqBodyBufLen, + HTTPResponse_t* pResponse ); + +/** + * @brief Read a header from the completed response #HTTPResponse_t. This will + * return the response header value location within #HTTPResponse_t.pBuffer. + * + * This function should be used only a completed response. A #HTTPResponse_t is + * not complete until #HTTPClient_Send returns. + * + * TODO: Expand documentation. + * + * @param[in] pResponse Completed response. + * @param[in] pName The header field name to read. + * @param[in] nameLen The length of the header field name in bytes. + * @param[out] pValue The location of the header value in + * #HTTPResponse_t.pBuffer. + * @param[out] valueLen The length of the header value in bytes. + * @return #HTTP_SUCCESS if successful, an error code otherwise. + * TODO: Update for exact error codes returned. + */ +HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t* pResponse, + const char* pName, + size_t nameLen, + char **pValue, + size_t* valueLen ); + +#endif \ No newline at end of file diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c new file mode 100644 index 0000000000..7dd91015a4 --- /dev/null +++ b/libraries/standard/http/src/http_client.c @@ -0,0 +1,42 @@ +#include "http_client.h" + +HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, + const HTTPRequestInfo_t * pRequestInfo ) +{ + return HTTP_NOT_SUPPORTED; +} + + +HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, + const char* pName, + size_t nameLen, + const char * pValue, + size_t valueLen ) +{ + return HTTP_NOT_SUPPORTED; +} + +HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t *pRequestHeaders, + int32_t rangeStart, + int32_t rangeEnd ) +{ + return HTTP_NOT_SUPPORTED; +} + +HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t* pTransport, + const HTTPRequestHeaders_t* pRequestHeaders, + const uint8_t* pRequestBodyBuf, // For a PUT or POST request. + size_t reqBodyBufLen, + httpResponse_t* pResponse ) +{ + return HTTP_NOT_SUPPORTED; +} + +HTTPStatus_t HTTPClient_ReadHeader( httpResponse_t* pResponse, + const char* pName, + size_t nameLen, + char **pValue, + size_t* valueLen ) +{ + return HTTP_NOT_SUPPORTED; +} \ No newline at end of file diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile new file mode 100644 index 0000000000..8db1401ef5 --- /dev/null +++ b/libraries/standard/http/test/Makefile @@ -0,0 +1,30 @@ +# the path to be indexed (default: ..) +# Directory indexing includes subdirectories. +SOURCE = ../src + +# arguments given to the compiler (default: see the included file) +#CFLAGS = std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb + +# include directories given to the compiler (default: none) +INCLUDE = ../include + +# the default goal (default: help) +# Set to build or test as desired. +.DEFAULT_GOAL := test + +# names of unit test programs +# Values based on test source file names will be dynamically added. +#TESTS = + +# functions to dump into separate source files +# Values based on test source file names will be dynamically added. +#FUNCTIONS = a_private_func + +# Changes to the above variables should remain above this include. +include Mock4thewin.mk + +# additional dependencies for all tests +$(TESTS): common.h + +# additional dependency for a specific test +#api_func.c: a_private_func.c diff --git a/libraries/standard/http/test/README.md b/libraries/standard/http/test/README.md new file mode 100644 index 0000000000..1487560534 --- /dev/null +++ b/libraries/standard/http/test/README.md @@ -0,0 +1,17 @@ +# Instructions for running the tests: + +1. In your GNU compatible environment like WSL, mingw, Linux OS, MAC OS, etc., open a terminal. +1. To get all the required installation scripts, clone this repo: https://github.com/dan4thewin/mock4thewin. +1. Go to the repo directory: `cd \{mock4thewin repo}` +1. Run this command to install: `sudo ./install /usr/local` +1. To get another required tool, clone this repo https://github.com/dan4thewin/ctags-xref and follow the instructions in the root README.md. +1. Go back to the HTTP Client test folder: `cd {CSDK_ROOT}/libraries/standard/http/test` +1. Run this command to build and run the tests: `make test` +1. This framework automatically generates test-\[function_name\] function_name.c files for ease in mocking. Also auto-generated are *gcov* related files for coverage information. + +# Writing Unit Tests +1. Make a file under {CSDK_ROOT}/libraries/standard/http/test named "test-\[function_name\].c" + For example {CSDK_ROOT}/libraries/standard/http/test/test-HTTPClient_AddHeader.c +1. See https://gist.github.com/dan4thewin/6f708bf635f6cb647d9f7bc7f55e4706#the-complete-unit-test for an example unit tests and how to test assert. +1. Any mocked external functions should go into "common.h". +1. Build, run, and get coverage with: `make test` diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h new file mode 100644 index 0000000000..a33ece27c7 --- /dev/null +++ b/libraries/standard/http/test/common.h @@ -0,0 +1,4 @@ +#include "tap.h" + +/* Include paths for public enums, structures, and macros. */ +#include "http_client.h" \ No newline at end of file diff --git a/libraries/standard/http/test/test-HTTPClient_AddHeader.c b/libraries/standard/http/test/test-HTTPClient_AddHeader.c new file mode 100644 index 0000000000..b675b3bfa7 --- /dev/null +++ b/libraries/standard/http/test/test-HTTPClient_AddHeader.c @@ -0,0 +1,9 @@ +#include "common.h" + +/* Functions are pulled out into their own C files to be tested as a unit. */ +#include "HTTPClient_AddHeader.c" + +int main() +{ + return 0; +} \ No newline at end of file diff --git a/third_party/http_parser/http_parser b/third_party/http_parser/http_parser new file mode 160000 index 0000000000..5c17dad400 --- /dev/null +++ b/third_party/http_parser/http_parser @@ -0,0 +1 @@ +Subproject commit 5c17dad400e45c5a442a63f250fff2638d144682 From 2d5dbaabf2c090c9bacaf6b2f1e236e07a1f1c22 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Fri, 17 Apr 2020 11:06:08 -0700 Subject: [PATCH 462/844] Bring uncrustify.cfg + delete accidentally added gitsubmodule (#880) * Bring uncrustify.cfg from amazon-freertos/tools * Delete accidentally added git submodule for http-parser. --- third_party/http_parser/http_parser | 1 - tools/uncrustify.cfg | 160 ++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) delete mode 160000 third_party/http_parser/http_parser create mode 100644 tools/uncrustify.cfg diff --git a/third_party/http_parser/http_parser b/third_party/http_parser/http_parser deleted file mode 160000 index 5c17dad400..0000000000 --- a/third_party/http_parser/http_parser +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5c17dad400e45c5a442a63f250fff2638d144682 diff --git a/tools/uncrustify.cfg b/tools/uncrustify.cfg new file mode 100644 index 0000000000..0cb7d3fbd6 --- /dev/null +++ b/tools/uncrustify.cfg @@ -0,0 +1,160 @@ +# Uncrustify-0.67 +input_tab_size = 4 # unsigned number +output_tab_size = 4 # unsigned number +sp_arith = force # ignore/add/remove/force +sp_assign = force # ignore/add/remove/force +sp_assign_default = force # ignore/add/remove/force +sp_before_assign = force # ignore/add/remove/force +sp_after_assign = force # ignore/add/remove/force +sp_enum_assign = force # ignore/add/remove/force +sp_enum_before_assign = force # ignore/add/remove/force +sp_enum_after_assign = force # ignore/add/remove/force +sp_pp_stringify = add # ignore/add/remove/force +sp_bool = force # ignore/add/remove/force +sp_compare = force # ignore/add/remove/force +sp_inside_paren = force # ignore/add/remove/force +sp_paren_paren = force # ignore/add/remove/force +sp_paren_brace = force # ignore/add/remove/force +sp_before_ptr_star = force # ignore/add/remove/force +sp_before_unnamed_ptr_star = force # ignore/add/remove/force +sp_between_ptr_star = remove # ignore/add/remove/force +sp_after_ptr_star = force # ignore/add/remove/force +sp_before_byref = force # ignore/add/remove/force +sp_after_byref = remove # ignore/add/remove/force +sp_after_byref_func = remove # ignore/add/remove/force +sp_before_angle = remove # ignore/add/remove/force +sp_inside_angle = remove # ignore/add/remove/force +sp_after_angle = force # ignore/add/remove/force +sp_before_sparen = remove # ignore/add/remove/force +sp_inside_sparen = force # ignore/add/remove/force +sp_after_sparen = force # ignore/add/remove/force +sp_sparen_brace = force # ignore/add/remove/force +sp_before_semi_for = remove # ignore/add/remove/force +sp_before_semi_for_empty = add # ignore/add/remove/force +sp_after_semi_for_empty = force # ignore/add/remove/force +sp_before_square = remove # ignore/add/remove/force +sp_before_squares = remove # ignore/add/remove/force +sp_inside_square = force # ignore/add/remove/force +sp_after_comma = force # ignore/add/remove/force +sp_after_cast = force # ignore/add/remove/force +sp_inside_paren_cast = force # ignore/add/remove/force +sp_sizeof_paren = remove # ignore/add/remove/force +sp_inside_braces_enum = force # ignore/add/remove/force +sp_inside_braces_struct = force # ignore/add/remove/force +sp_inside_braces = force # ignore/add/remove/force +sp_inside_braces_empty = remove # ignore/add/remove/force +sp_type_func = force # ignore/add/remove/force +sp_func_proto_paren = remove # ignore/add/remove/force +sp_func_def_paren = remove # ignore/add/remove/force +sp_inside_fparens = remove # ignore/add/remove/force +sp_inside_fparen = force # ignore/add/remove/force +sp_fparen_brace = add # ignore/add/remove/force +sp_func_call_paren = remove # ignore/add/remove/force +sp_func_class_paren = remove # ignore/add/remove/force +sp_return_paren = remove # ignore/add/remove/force +sp_attribute_paren = remove # ignore/add/remove/force +sp_defined_paren = remove # ignore/add/remove/force +sp_macro = force # ignore/add/remove/force +sp_macro_func = force # ignore/add/remove/force +sp_brace_typedef = force # ignore/add/remove/force +sp_before_dc = remove # ignore/add/remove/force +sp_after_dc = remove # ignore/add/remove/force +sp_cond_colon = force # ignore/add/remove/force +sp_cond_question = force # ignore/add/remove/force +sp_case_label = force # ignore/add/remove/force +sp_endif_cmt = force # ignore/add/remove/force +sp_before_tr_emb_cmt = force # ignore/add/remove/force +sp_num_before_tr_emb_cmt = 1 # unsigned number +indent_columns = 4 # unsigned number +indent_with_tabs = 0 # unsigned number +indent_align_string = true # false/true +indent_class = true # false/true +indent_class_colon = true # false/true +indent_member = 3 # unsigned number +indent_switch_case = 4 # unsigned number +indent_case_brace = 3 # number +nl_assign_leave_one_liners = true # false/true +nl_class_leave_one_liners = true # false/true +nl_start_of_file = remove # ignore/add/remove/force +nl_end_of_file = force # ignore/add/remove/force +nl_end_of_file_min = 1 # unsigned number +nl_assign_brace = add # ignore/add/remove/force +nl_func_var_def_blk = 1 # unsigned number +nl_fcall_brace = add # ignore/add/remove/force +nl_enum_brace = force # ignore/add/remove/force +nl_struct_brace = force # ignore/add/remove/force +nl_union_brace = force # ignore/add/remove/force +nl_if_brace = add # ignore/add/remove/force +nl_brace_else = add # ignore/add/remove/force +nl_else_brace = add # ignore/add/remove/force +nl_getset_brace = force # ignore/add/remove/force +nl_for_brace = add # ignore/add/remove/force +nl_while_brace = add # ignore/add/remove/force +nl_do_brace = add # ignore/add/remove/force +nl_switch_brace = add # ignore/add/remove/force +nl_multi_line_define = true # false/true +nl_before_case = true # false/true +nl_after_case = true # false/true +nl_func_type_name = remove # ignore/add/remove/force +nl_func_proto_type_name = remove # ignore/add/remove/force +nl_func_paren = remove # ignore/add/remove/force +nl_func_def_paren = remove # ignore/add/remove/force +nl_func_decl_start = remove # ignore/add/remove/force +nl_func_def_start = remove # ignore/add/remove/force +nl_func_decl_args = add # ignore/add/remove/force +nl_func_def_args = add # ignore/add/remove/force +nl_func_decl_end = remove # ignore/add/remove/force +nl_func_def_end = remove # ignore/add/remove/force +nl_fdef_brace = add # ignore/add/remove/force +nl_after_semicolon = true # false/true +nl_after_brace_open = true # false/true +nl_after_brace_close = true # false/true +nl_squeeze_ifdef = true # false/true +nl_before_if = force # ignore/add/remove/force +nl_after_if = force # ignore/add/remove/force +nl_before_for = force # ignore/add/remove/force +nl_after_for = force # ignore/add/remove/force +nl_before_while = force # ignore/add/remove/force +nl_after_while = force # ignore/add/remove/force +nl_before_switch = force # ignore/add/remove/force +nl_after_switch = force # ignore/add/remove/force +nl_before_do = force # ignore/add/remove/force +nl_after_do = force # ignore/add/remove/force +nl_max = 4 # unsigned number +nl_after_func_proto_group = 1 # unsigned number +nl_after_func_body_class = 2 # unsigned number +nl_before_block_comment = 2 # unsigned number +eat_blanks_after_open_brace = true # false/true +eat_blanks_before_close_brace = true # false/true +nl_after_return = true # false/true +pos_bool = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force +align_var_def_amp_style = 1 # unsigned number +align_var_def_thresh = 16 # unsigned number +align_assign_thresh = 12 # unsigned number +align_struct_init_span = 3 # unsigned number +align_typedef_gap = 3 # unsigned number +align_typedef_span = 5 # unsigned number +align_typedef_star_style = 1 # unsigned number +align_typedef_amp_style = 1 # unsigned number +align_right_cmt_span = 3 # unsigned number +align_nl_cont = true # false/true +align_pp_define_gap = 4 # unsigned number +align_pp_define_span = 3 # unsigned number +cmt_cpp_to_c = true # false/true +cmt_star_cont = true # false/true +mod_full_brace_do = add # ignore/add/remove/force +mod_full_brace_for = add # ignore/add/remove/force +mod_full_brace_if = add # ignore/add/remove/force +mod_full_brace_while = add # ignore/add/remove/force +mod_full_paren_if_bool = true # false/true +mod_remove_extra_semicolon = true # false/true +mod_add_long_ifdef_endif_comment = 10 # unsigned number +mod_add_long_ifdef_else_comment = 10 # unsigned number +mod_case_brace = remove # ignore/add/remove/force +mod_remove_empty_return = true # false/true +pp_indent = force # ignore/add/remove/force +pp_indent_at_level = true # false/true +pp_indent_count = 4 # unsigned number +pp_space = remove # ignore/add/remove/force +pp_if_indent_code = true # false/true +# option(s) with 'not default' value: 158 From a002ada7473e9867108b1863799fd1948d78d5b0 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 17 Apr 2020 15:39:03 -0700 Subject: [PATCH 463/844] New branch/add logging utility (#876) * Add logging framework from v4_beta modified to support metadata logging * Add a .gitignore to the branch * Separate singular and multi-argument logging macros; update iot_logging.c source file * Expose IotLog macro in config.h as interface hook * Update config headers in logging files * Move default use of IotLog_Generic to config file for better visibility * Move iot_logging implementation files to port folder * Add stripped down version of iot_clock files needed by logging library * Address review comments * Simplify doc for IotLog in config file; remove all IotLog_GenericPrintBuffer macros * Minor hygiene updates * Address second round of review comments * Minor pruning of unused constants and fix of header include path in POSIX clock. * Remove scripts/uncrustify.cfg --- .gitignore | 6 + demos/config.h | 19 +- .../utilities/include/iot_logging_setup.h | 283 ++++++++++++++++++ platform/include/iot_clock.h | 80 +++++ platform/include/iot_logging.h | 69 +++++ platform/posix/iot_clock_posix.c | 103 +++++++ platform/posix/iot_logging.c | 229 ++++++++++++++ 7 files changed, 787 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 libraries/standard/utilities/include/iot_logging_setup.h create mode 100644 platform/include/iot_clock.h create mode 100644 platform/include/iot_logging.h create mode 100644 platform/posix/iot_clock_posix.c create mode 100644 platform/posix/iot_logging.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..3ca54d3698 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Ignore documentation output. +doc/output/* +doc/tag/* + +# Ignore CMake build directory. +build/ diff --git a/demos/config.h b/demos/config.h index b7e68203f4..90947166df 100644 --- a/demos/config.h +++ b/demos/config.h @@ -22,9 +22,24 @@ #ifndef CONFIG_H #define CONFIG_H + /* Set network context to socket (int). */ typedef int MQTTNetworkContext_t; -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 + +/* Include file for POSIX reference implementation. */ +#include "port/posix/iot_logging.h" + +/* Define the IotLog logging interface to enabling logging. + * This demo maps the macro to the reference POSIX implementation for logging. */ +#define IotLog( messageLevel, pFormat, ... ) \ + IotLog_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) + -#endif +#endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/utilities/include/iot_logging_setup.h b/libraries/standard/utilities/include/iot_logging_setup.h new file mode 100644 index 0000000000..7e2a78a593 --- /dev/null +++ b/libraries/standard/utilities/include/iot_logging_setup.h @@ -0,0 +1,283 @@ +/* + * IoT Common V1.1.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_logging_setup.h + * @brief Defines the logging macro #IotLog. + */ + +#ifndef IOT_LOGGING_SETUP_H_ +#define IOT_LOGGING_SETUP_H_ + +/* The config header is always included first. */ +#include "config.h" + +/** + * @constantspage{logging,logging library} + * + * @section logging_constants_levels Log levels + * @brief Log levels for the libraries in this SDK. + * + * Each library should specify a log level by setting @ref LIBRARY_LOG_LEVEL. + * All log messages with a level at or below the specified level will be printed + * for that library. + * + * Currently, there are 4 log levels. In the order of lowest to highest, they are: + * - #IOT_LOG_NONE
+ * @copybrief IOT_LOG_NONE + * - #IOT_LOG_ERROR
+ * @copybrief IOT_LOG_ERROR + * - #IOT_LOG_WARN
+ * @copybrief IOT_LOG_WARN + * - #IOT_LOG_INFO
+ * @copybrief IOT_LOG_INFO + * - #IOT_LOG_DEBUG
+ * @copybrief IOT_LOG_DEBUG + */ + +/** + * @brief No log messages. + * + * When @ref LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no + * logging messages are printed. + */ +#define IOT_LOG_NONE 0 + +/** + * @brief Represents erroneous application state or event. + * + * These messages describe the situations when a library encounters an error from + * which it cannot recover. + * + * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either + * of #IOT_LOG_ERROR, #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + */ +#define IOT_LOG_ERROR 1 + +/** + * @brief Message about an abnormal event. + * + * These messages describe the situations when a library encounters + * abnormal event that may be indicative of an error. Libraries continue + * execution after logging a warning. + * + * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either + * of #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + */ +#define IOT_LOG_WARN 2 + +/** + * @brief A helpful, informational message. + * + * These messages describe normal execution of a library. They provide + * the progress of the program at a coarse-grained level. + * + * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either + * of #IOT_LOG_INFO or #IOT_LOG_DEBUG. + */ +#define IOT_LOG_INFO 3 + +/** + * @brief Detailed and excessive debug information. + * + * Debug log messages are used to provide the + * progress of the program at a fine-grained level. These are mostly used + * for debugging and may contain excessive information such as internal + * variables, buffers, or other specific information. + * + * These messages are only printed when @ref LIBRARY_LOG_LEVEL is defined as + * #IOT_LOG_DEBUG. + */ +#define IOT_LOG_DEBUG 4 + +/** + * @functionpage{IotLog,logging,log} + */ + +/** + * @def IotLog( messageLevel, pLogConfig, ... ) + * @brief The common logging interface for all libraries. + * + * This acts as a hook for supplying a logging implementation stack + * for all libraries that log through this macro interface. + * This macro should be mapped to the platform's logging library. + * + * @param[in] messageLevel The integer code for the log level of the message. + * Must be one of #IOT_LOG_ERROR, #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + * Must not be #IOT_LOG_NONE. + * @param[in] pFormat The format string for the log message. + * @param[in] ... The variadic argument list for the format string. + * + * @return No return value. + */ + +/** + * @def IotLogError( message ) + * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_ERROR. + * + * Equivalent to: + * @code{c} + * IotLogWith( IOT_LOG_ERROR, "%s" , message ) + * @endcode + */ + +/** + * @def IotLogErrorWithArgs( pFormat, ... ) + * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_ERROR. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_ERROR, pFormat, ... ) + * @endcode + */ + +/** + * @def IotLogWarn( message ) + * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_WARN. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_WARN, "%s" , message ) + * @endcode + */ + +/** + * @def IotLogWarnWithArgs( pFormat, ... ) + * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_WARN. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_WARN, pFormat, ... ) + * @endcode + */ + +/** + * @def IotLogInfo( message ) + * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_INFO. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_INFO, "%s" , message ) + * @endcode + */ + +/** + * @def IotLogInfoWithArgs( pFormat, ... ) + * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_INFO. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_INFO, pFormat, ... ) + * @endcode + */ + +/** + * @def IotLogDebug( message ) + * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_DEBUG. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_DEBUG, "%s" , message ) + * @endcode + */ + +/** + * @def IotLogDebugWithArgs( pFormat, ... ) + * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_DEBUG. + * + * Equivalent to: + * @code{c} + * IotLog( IOT_LOG_DEBUG, pFormat, ... ) + * @endcode + */ + +/* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ +#if !defined( LIBRARY_LOG_LEVEL ) || \ + ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) + #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." +/* Check that LIBRARY_LOG_NAME is defined and has a valid value. */ +#elif !defined( LIBRARY_LOG_NAME ) + #error "Please define LIBRARY_LOG_NAME." +#else + #if LIBRARY_LOG_LEVEL != IOT_LOG_NONE + #if !defined( IotLog ) + #error "Please define the common logging interface macro, IotLog(messageLevel, pFormat, ...)." + #endif + #endif + + #if LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG + /* All log level messages will logged. */ + #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) + #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define IotLogWarn( message ) IotLog( IOT_LOG_WARN, "%s", message ) + #define IotLogWarnWithArgs( pFormat, ... ) IotLog( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define IotLogInfo( message ) IotLog( IOT_LOG_INFO, "%s", message ) + #define IotLogInfoWithArgs( pFormat, ... ) IotLog( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) + #define IotLogDebug( message ) IotLog( IOT_LOG_DEBUG, "%s", message ) + #define IotLogDebugWithArgs( pFormat, ... ) IotLog( IOT_LOG_DEBUG, pFormat, __VA_ARGS__ ) + + #elif LIBRARY_LOG_LEVEL == IOT_LOG_INFO + /* Only INFO, WARNING and ERROR messages will be logged. */ + #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) + #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define IotLogWarn( message ) IotLog( IOT_LOG_WARN, "%s", message ) + #define IotLogWarnWithArgs( pFormat, ... ) IotLog( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define IotLogInfo( message ) IotLog( IOT_LOG_INFO, "%s", message ) + #define IotLogInfoWithArgs( pFormat, ... ) IotLog( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) + #define IotLogDebug( message ) + #define IotLogDebugWithArgs( pFormat, ... ) + + #elif LIBRARY_LOG_LEVEL == IOT_LOG_WARN + /* Only WARNING and ERROR messages will be logged.*/ + #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) + #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define IotLogWarn( message ) IotLog( IOT_LOG_WARN, "%s", message ) + #define IotLogWarnWithArgs( pFormat, ... ) IotLog( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define IotLogInfo( message ) + #define IotLogInfoWithArgs( pFormat, ... ) + #define IotLogDebug( message ) + #define IotLogDebugWithArgs( pFormat, ... ) + + #elif LIBRARY_LOG_LEVEL == IOT_LOG_ERROR + /* Only ERROR messages will be logged. */ + #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) + #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define IotLogWarn( message ) + #define IotLogWarnWithArgs( pFormat, ... ) + #define IotLogInfo( message ) + #define IotLogInfoWithArgs( pFormat, ... ) + #define IotLogDebug( message ) + #define IotLogDebugWithArgs( pFormat, ... ) + + #else /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ + #define IotLogError( ... ) + #define IotLogWarn( ... ) + #define IotLogInfo( ... ) + #define IotLogDebug( ... ) + #endif /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ +#endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) */ + +#endif /* ifndef IOT_LOGGING_SETUP_H_ */ diff --git a/platform/include/iot_clock.h b/platform/include/iot_clock.h new file mode 100644 index 0000000000..7bb762d518 --- /dev/null +++ b/platform/include/iot_clock.h @@ -0,0 +1,80 @@ +/* + * IoT Platform V1.1.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_clock.h + * @brief Time-related functions used by libraries in this SDK. + */ + +#ifndef IOT_CLOCK_H_ +#define IOT_CLOCK_H_ + +/* The config header is always included first. */ +#include "config.h" + +/* Standard includes. */ +#include +#include +#include + +/** + * @functionspage{platform_clock,platform clock component,Clock} + * - @functionname{platform_clock_function_gettimestring} + */ + +/** + * @functionpage{IotClock_GetTimestring,platform_clock,gettimestring} + */ + +/** + * @brief Generates a human-readable timestring, such as "01 Jan 2018 12:00". + * + * This function uses the system clock to generate a human-readable timestring. + * This timestring is printed by the [logging functions](@ref logging_functions). + * + * @param[out] pBuffer A buffer to store the timestring in. + * @param[in] bufferSize The size of `pBuffer`. + * @param[out] pTimestringLength The actual length of the timestring stored in + * `pBuffer`. + * + * @return `true` if a timestring was successfully generated; `false` otherwise. + * + * @warning The implementation of this function must not call any [logging functions] + * (@ref logging_functions). + * + * Example + * @code{c} + * char timestring[ 32 ]; + * size_t timestringLength = 0; + * + * if( IotClock_GetTimestring( timestring, 32, ×tringLength ) == true ) + * { + * printf( "Timestring: %.*s", timestringLength, timestring ); + * } + * @endcode + */ +/* @[declare_platform_clock_gettimestring] */ +bool IotClock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ); + +#endif /* ifndef IOT_CLOCK_H_ */ diff --git a/platform/include/iot_logging.h b/platform/include/iot_logging.h new file mode 100644 index 0000000000..b50f32e84f --- /dev/null +++ b/platform/include/iot_logging.h @@ -0,0 +1,69 @@ +/* + * IoT Common V1.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_logging.h + * @brief Generic logging function header file. + * + * Declares the generic logging function. + * + * @see iot_logging_setup.h + */ + +#ifndef IOT_LOGGING_H_ +#define IOT_LOGGING_H_ + +/* Standard includes. */ +#include +#include +#include + +/** + * @functionspage{logging,logging library} + * + * - @functionname{logging_function_generic} + */ + +/** + * @functionpage{IotLog_Generic,logging,generic} + */ + +/** + * @brief Generic logging function that prints a single message. + * + * This function represents a reference implementation for the + * @ref logging_function_log logging interface. + * + * @param[in] messageLevel The log level of the this message. See @ref LIBRARY_LOG_LEVEL. + * @param[in] pFormat Format string for the log message. + * @param[in] ... Arguments for format specification. + * + * @return No return value. On errors, it prints nothing. + */ +/* @[declare_logging_generic] */ +void IotLog_Generic( int32_t messageLevel, + const char * const pFormat, + ... ); +/* @[declare_logging_generic] */ + + +#endif /* ifndef IOT_LOGGING_H_ */ diff --git a/platform/posix/iot_clock_posix.c b/platform/posix/iot_clock_posix.c new file mode 100644 index 0000000000..61f0ec38d8 --- /dev/null +++ b/platform/posix/iot_clock_posix.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_clock_posix.c + * @brief Implementation of the functions in iot_clock.h for POSIX systems. + */ + +/* The config header is always included first. */ +#include "config.h" + +/* Standard includes. */ +#include + +/* POSIX include. Allow the default POSIX header to be overridden. */ +#ifdef POSIX_TIME_HEADER + #include POSIX_TIME_HEADER +#else + #include +#endif + +/* Platform clock include. */ +#include "platform/include/iot_clock.h" + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "CLOCK" ) +#include "iot_logging_setup.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief The format of timestrings printed in logs. + * + * For more information on timestring formats, see [this link.] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) + */ +#define TIMESTRING_FORMAT ( "%F %R:%S" ) + +/*-----------------------------------------------------------*/ + +bool IotClock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ) +{ + bool status = true; + const time_t unixTime = time( NULL ); + struct tm localTime = { 0 }; + size_t timestringLength = 0; + + /* localtime_r is the thread-safe variant of localtime. Its return value + * should be the pointer to the localTime struct. */ + if( localtime_r( &unixTime, &localTime ) != &localTime ) + { + status = false; + } + + if( status == true ) + { + /* Convert the localTime struct to a string. */ + timestringLength = strftime( pBuffer, bufferSize, TIMESTRING_FORMAT, &localTime ); + + /* Check for error from strftime. */ + if( timestringLength == 0 ) + { + status = false; + } + else + { + /* Set the output parameter. */ + *pTimestringLength = timestringLength; + } + } + + return status; +} diff --git a/platform/posix/iot_logging.c b/platform/posix/iot_logging.c new file mode 100644 index 0000000000..709dff13f3 --- /dev/null +++ b/platform/posix/iot_logging.c @@ -0,0 +1,229 @@ +/* + * IoT Common V1.1.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_logging.c + * @brief Implementation of the generic logging function. + */ + +/* Standard includes. */ +#include +#include +#include + +/* Platform clock include. */ +#include "platform/iot_clock.h" + +/* Logging includes. */ +#include "iot_logging.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief A guess of the maximum length of a timestring. + * + * There's no way for this logging library to know the length of a timestring + * before it's generated. Therefore, the logging library will assume a maximum + * length of any timestring it may get. This value should be generous enough + * to accommodate the vast majority of timestrings. + * + * @see @ref platform_clock_function_gettimestring + */ +#define MAX_TIMESTRING_LENGTH ( 64 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Lookup table for log levels. + * + * Converts one of the @ref logging_constants_levels to a string. + */ +static const char * const _pLogLevelStrings[] = +{ + "ERROR", /* IOT_LOG_ERROR */ + "WARN ", /* IOT_LOG_WARN */ + "INFO ", /* IOT_LOG_INFO */ + "DEBUG" /* IOT_LOG_DEBUG */ +}; + +/*-----------------------------------------------------------*/ + +static bool _reallocLoggingBuffer( void ** pOldBuffer, + size_t newSize, + size_t oldSize ) +{ + bool status = false; + + /* Allocate a new, larger buffer. */ + void * pNewBuffer = malloc( newSize ); + + /* Ensure that memory allocation succeeded. */ + if( pNewBuffer != NULL ) + { + /* Copy the data from the old buffer to the new buffer. */ + ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); + + /* Free the old buffer and update the pointer. */ + IotLogging_Free( *pOldBuffer ); + *pOldBuffer = pNewBuffer; + + status = true; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotLog_Generic( int32_t messageLevel, + const char * const pFormat, + ... ) +{ + assert( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ); + + int requiredMessageSize = 0; + size_t bufferSize = 0, + bufferPosition = 0, timestringLength = 0; + char * pLoggingBuffer = NULL; + va_list args; + + /* Add length of log level if requested. */ + bufferSize += MAX_LOG_LEVEL_LENGTH; + + /* Add length of timestring. */ + bufferSize += MAX_TIMESTRING_LENGTH; + + /* Add 64 as an initial (arbitrary) guess for the length of the message. */ + bufferSize += 64; + + /* Allocate memory for the logging buffer. */ + pLoggingBuffer = ( char * ) malloc( bufferSize ); + + if( pLoggingBuffer == NULL ) + { + return; + } + + /* Print the message log level. */ + + /* Add the log level string to the logging buffer. */ + requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + "[%s]", + _pLogLevelStrings[ messageLevel ] ); + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Update the buffer position. */ + bufferPosition += ( size_t ) requiredMessageSize; + + /* Print the timestring if requested. */ + /* Add the opening '[' enclosing the timestring. */ + pLoggingBuffer[ bufferPosition ] = '['; + bufferPosition++; + + /* Generate the timestring and add it to the buffer. */ + if( IotClock_GetTimestring( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + ×tringLength ) == true ) + { + /* If the timestring was successfully generated, add the closing "]". */ + bufferPosition += timestringLength; + pLoggingBuffer[ bufferPosition ] = ']'; + bufferPosition++; + } + else + { + /* Sufficient memory for a timestring should have been allocated. A timestring + * probably failed to generate due to a clock read error; remove the opening '[' + * from the logging buffer. */ + bufferPosition--; + pLoggingBuffer[ bufferPosition ] = '\0'; + } + + /* Add a padding space between the last closing ']' and the message, unless + * the logging buffer is empty. */ + if( bufferPosition > 0 ) + { + pLoggingBuffer[ bufferPosition ] = ' '; + bufferPosition++; + } + + va_start( args, pFormat ); + + /* Add the log message to the logging buffer. */ + requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + pFormat, + args ); + + va_end( args ); + + /* If the logging buffer was too small to fit the log message, reallocate + * a larger logging buffer. */ + if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition ) + { + if( _reallocLoggingBuffer( ( void ** ) &pLoggingBuffer, + ( size_t ) requiredMessageSize + bufferPosition + 1, + bufferSize ) == false ) + { + /* If buffer reallocation failed, return. */ + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Reallocation successful, update buffer size. */ + bufferSize = ( size_t ) requiredMessageSize + bufferPosition + 1; + + /* Add the log message to the buffer. Now that the buffer has been + * reallocated, this should succeed. */ + va_start( args, pFormat ); + requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, + bufferSize - bufferPosition, + pFormat, + args ); + va_end( args ); + } + + /* Check for encoding errors. */ + if( requiredMessageSize <= 0 ) + { + IotLogging_Free( pLoggingBuffer ); + + return; + } + + /* Print the logging buffer to stdout. */ + IotLogging_Puts( pLoggingBuffer ); + + /* Free the logging buffer. */ + IotLogging_Free( pLoggingBuffer ); +} + +/*-----------------------------------------------------------*/ From 345f7b2e1652e7759440756e5d65e0e17cf5c349 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 20 Apr 2020 09:48:10 -0700 Subject: [PATCH 464/844] Add plaintext transport implementation (#872) --- demos/Makefile | 4 +- demos/{ => include}/config.h | 0 demos/mqtt_demo.c | 6 - demos/src/mqtt_demo_plaintext.c | 193 ++++++++++++++++++ libraries/standard/mqtt/include/mqtt.h | 6 +- libraries/standard/mqtt/src/mqtt.c | 63 ++++-- .../standard/mqtt/src/mqtt_lightweight.c | 29 +++ 7 files changed, 273 insertions(+), 28 deletions(-) rename demos/{ => include}/config.h (100%) delete mode 100644 demos/mqtt_demo.c create mode 100644 demos/src/mqtt_demo_plaintext.c diff --git a/demos/Makefile b/demos/Makefile index 5c98b7632a..bb2135e7ba 100644 --- a/demos/Makefile +++ b/demos/Makefile @@ -1,9 +1,9 @@ CC = gcc -INCLUDE_DIRS = -I . \ +INCLUDE_DIRS = -I include \ -I ../libraries/standard/mqtt/include -SRC_FILES = $(shell find . -name '*.c') \ +SRC_FILES = $(shell find src -name '*.c') \ $(shell find ../libraries/standard/mqtt/src -name '*.c') FLAGS = -g -O0 -Wall -Wextra -Wpedantic diff --git a/demos/config.h b/demos/include/config.h similarity index 100% rename from demos/config.h rename to demos/include/config.h diff --git a/demos/mqtt_demo.c b/demos/mqtt_demo.c deleted file mode 100644 index e91a4942b1..0000000000 --- a/demos/mqtt_demo.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "mqtt.h" - -int main(int argc, char ** argv) -{ - return 0; -} diff --git a/demos/src/mqtt_demo_plaintext.c b/demos/src/mqtt_demo_plaintext.c new file mode 100644 index 0000000000..99818e0bc9 --- /dev/null +++ b/demos/src/mqtt_demo_plaintext.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include + +#include +#include + +#include "mqtt.h" + +#define SERVER "test.mosquitto.org" +#define PORT 1883 + +#define NETWORK_BUFFER_SIZE ( 1024U ) + +#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) + +static int connectToServer( const char * pServer, uint16_t port ) +{ + int status, tcpSocket = -1; + struct addrinfo * pListHead = NULL, * pIndex; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + + status = getaddrinfo( pServer, NULL, NULL, &pListHead ); + + if( status != -1 ) + { + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + tcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( tcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + } + + status = connect( tcpSocket, pServerInfo, serverInfoLength ); + + if( status == -1 ) + { + close( tcpSocket ); + } + else + { + break; + } + } + + if( pIndex == NULL ) + { + status = -1; + } + else + { + status = tcpSocket; + } + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return status; +} + +static int32_t transportSend( int tcpSocket, const void * pMessage, size_t bytesToSend ) +{ + return ( int32_t ) send( tcpSocket, pMessage, bytesToSend, 0 ); +} + +static int32_t transportRecv( int tcpSocket, void * pBuffer, size_t bytesToRecv ) +{ + return ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); +} + +static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ) +{ + +} + +static uint32_t getTime( void ) +{ + return 0; +} + +static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTConnectInfo_t connectInfo; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + /* The network buffer must remain valid for the lifetime of the MQTT context. */ + static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + + /* Initialize MQTT context. */ + transport.networkContext = tcpSocket; + transport.send = transportSend; + transport.recv = transportRecv; + + networkBuffer.pBuffer = buffer; + networkBuffer.size = NETWORK_BUFFER_SIZE; + + callbacks.appCallback = eventCallback; + callbacks.getTime = getTime; + + MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); + + /* Establish MQTT session with a CONNECT packet. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + connectInfo.keepAliveSeconds = 0; + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0; + + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, NULL ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + } + + return status; +} + +int main( int argc, char ** argv ) +{ + int status; + MQTTContext_t context; + int tcpSocket = connectToServer( SERVER, PORT ); + + if( tcpSocket != -1 ) + { + status = establishMqttSession( &context, tcpSocket ); + } + else + { + status = EXIT_FAILURE; + } + + if( tcpSocket != -1 ) + { + shutdown( tcpSocket, SHUT_RDWR ); + close( tcpSocket ); + } + + return status; +} diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index aab9118e9e..70505accb2 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -98,12 +98,12 @@ struct MQTTContext MQTTPubAckInfo_t incomingPublishRecords[ MQTT_MAX_QUEUED_PUBLISH_MESSAGES ]; size_t incomingPublishCount; - const MQTTTransportInterface_t * pTransportInterface; - const MQTTFixedBuffer_t * pNetworkBuffer; + MQTTTransportInterface_t transportInterface; + MQTTFixedBuffer_t networkBuffer; uint16_t nextPacketId; MQTTConnectionStatus_t connectStatus; - const MQTTApplicationCallbacks_t * pCallbacks; + MQTTApplicationCallbacks_t callbacks; uint32_t lastPacketTime; }; diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 504709fd24..12f8cc85cd 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -23,6 +23,44 @@ #include "mqtt.h" +static int32_t sendPacket( MQTTContext_t * pContext, size_t bytesToSend ) +{ + const uint8_t * pIndex = pContext->networkBuffer.pBuffer; + size_t bytesRemaining = bytesToSend; + int32_t totalBytesSent = 0, bytesSent; + + /* Record the time of transmission. */ + uint32_t sendTime = pContext->callbacks.getTime(); + + /* Loop until the entire packet is sent. */ + while( bytesRemaining > 0 ) + { + bytesSent = pContext->transportInterface.send( pContext->transportInterface.networkContext, + pIndex, + bytesRemaining ); + + if( bytesSent > 0 ) + { + bytesRemaining -= ( size_t ) bytesSent; + totalBytesSent += bytesSent; + pIndex += bytesSent; + } + else + { + totalBytesSent = -1; + break; + } + } + + /* Update time of last transmission if the entire packet was successfully sent. */ + if( totalBytesSent > -1 ) + { + pContext->lastPacketTime = sendTime; + } + + return totalBytesSent; +} + void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, @@ -31,9 +69,9 @@ void MQTT_Init( MQTTContext_t * const pContext, memset( pContext, 0x00, sizeof( MQTTContext_t ) ); pContext->connectStatus = MQTTNotConnected; - pContext->pTransportInterface = pTransportInterface; - pContext->pCallbacks = pCallbacks; - pContext->pNetworkBuffer = pNetworkBuffer; + pContext->transportInterface = *pTransportInterface; + pContext->callbacks = *pCallbacks; + pContext->networkBuffer = *pNetworkBuffer; /* Zero is not a valid packet ID per MQTT spec. Start from 1. */ pContext->nextPacketId = 1; @@ -46,7 +84,6 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, { size_t remainingLength, packetSize; int32_t bytesSent; - uint32_t sendTime; MQTTPacketInfo_t incomingPacket; MQTTStatus_t status = MQTT_GetConnectPacketSize( pConnectInfo, @@ -59,22 +96,14 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, status = MQTT_SerializeConnect( pConnectInfo, pWillInfo, remainingLength, - pContext->pNetworkBuffer ); + &( pContext->networkBuffer ) ); } if( status == MQTTSuccess ) { - sendTime = pContext->pCallbacks->getTime(); - - bytesSent= pContext->pTransportInterface->send( pContext->pTransportInterface->networkContext, - pContext->pNetworkBuffer->pBuffer, - packetSize ); + bytesSent = sendPacket( pContext, packetSize ); - if( ( bytesSent > 0 ) && ( ( size_t ) bytesSent == packetSize ) ) - { - pContext->lastPacketTime = sendTime; - } - else + if( bytesSent < 0 ) { status = MQTTSendFailed; } @@ -82,8 +111,8 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - status = MQTT_GetIncomingPacket( pContext->pTransportInterface->recv, - pContext->pTransportInterface->networkContext, + status = MQTT_GetIncomingPacket( pContext->transportInterface.recv, + pContext->transportInterface.networkContext, &incomingPacket ); } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index a99ea3bc22..4c8f4cb3ed 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -123,6 +123,35 @@ static uint8_t * encodeString( uint8_t * pDestination, return pBuffer; } +static int32_t recvExact( MQTTTransportRecvFunc_t recvFunc, + MQTTNetworkContext_t networkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + uint8_t * pIndex = pBuffer; + size_t bytesRemaining = bytesToRecv; + int32_t totalBytesRecvd = 0, bytesRecvd; + + while( bytesRemaining > 0 ) + { + bytesRecvd = recvFunc( networkContext, pIndex, bytesRemaining ); + + if( bytesRecvd > 0 ) + { + bytesRemaining -= ( size_t ) bytesRecvd; + totalBytesRecvd += ( int32_t ) bytesRecvd; + pIndex += bytesRecvd; + } + else + { + totalBytesRecvd = -1; + break; + } + } + + return totalBytesRecvd; +} + MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, From 286ea6bd7dc2c993dfb8461b41c3b93981d9ad51 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 20 Apr 2020 13:30:42 -0700 Subject: [PATCH 465/844] Hygiene fixes to Logging Framework (#881) --- demos/include/config.h | 7 ++++--- libraries/standard/utilities/include/iot_logging_setup.h | 7 ++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/demos/include/config.h b/demos/include/config.h index 90947166df..7ffc6768fe 100644 --- a/demos/include/config.h +++ b/demos/include/config.h @@ -29,10 +29,12 @@ typedef int MQTTNetworkContext_t; #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 /* Include file for POSIX reference implementation. */ -#include "port/posix/iot_logging.h" +#include "platform/include/iot_logging.h" /* Define the IotLog logging interface to enabling logging. - * This demo maps the macro to the reference POSIX implementation for logging. */ + * This demo maps the macro to the reference POSIX implementation for logging. + * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the + * log, as metadata in each log message. */ #define IotLog( messageLevel, pFormat, ... ) \ IotLog_Generic( messageLevel, \ "[%s:%d] [%s] "pFormat, \ @@ -41,5 +43,4 @@ typedef int MQTTNetworkContext_t; LIBRARY_LOG_NAME, \ __VA_ARGS__ ) - #endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/utilities/include/iot_logging_setup.h b/libraries/standard/utilities/include/iot_logging_setup.h index 7e2a78a593..5ef71421d5 100644 --- a/libraries/standard/utilities/include/iot_logging_setup.h +++ b/libraries/standard/utilities/include/iot_logging_setup.h @@ -114,7 +114,7 @@ */ /** - * @def IotLog( messageLevel, pLogConfig, ... ) + * @def IotLog( messageLevel, pFormat, ... ) * @brief The common logging interface for all libraries. * * This acts as a hook for supplying a logging implementation stack @@ -136,7 +136,7 @@ * * Equivalent to: * @code{c} - * IotLogWith( IOT_LOG_ERROR, "%s" , message ) + * IotLog( IOT_LOG_ERROR, "%s" , message ) * @endcode */ @@ -218,9 +218,6 @@ ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && \ ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." -/* Check that LIBRARY_LOG_NAME is defined and has a valid value. */ -#elif !defined( LIBRARY_LOG_NAME ) - #error "Please define LIBRARY_LOG_NAME." #else #if LIBRARY_LOG_LEVEL != IOT_LOG_NONE #if !defined( IotLog ) From 4b3730206ca0d396911e9545fba645946a68fda6 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Wed, 22 Apr 2020 11:07:18 -0700 Subject: [PATCH 466/844] Uncrustify on http_client.h and http_client.c. (#889) This is to keep new change diffs focused only the current change. --- libraries/standard/http/include/http_client.h | 241 +++++++++--------- libraries/standard/http/src/http_client.c | 42 +-- 2 files changed, 146 insertions(+), 137 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index e7afb6fc82..78e5f4e305 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -6,71 +6,75 @@ /** * @brief Maximum size, in bytes, of headers allowed from the server. - * + * * If the total size in bytes of the headers sent from this server exceeds this * configuration, then the status code #HTTP_SECURITY_ALERT_HEADERS is * returned from #HTTPClient_Send. */ #ifndef HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES - #define HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES 2048U + #define HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES 2048U #endif /** * @brief The HTTP header "User-Agent" value. - * + * * The following headerline is automatically written to * #HTTPRequestHeaders_t.pBuffer: * "User-Agent: my-platform-name\r\n" - */ + */ #ifndef HTTP_USER_AGENT_VALUE - #define HTTP_USER_AGENT_VALUE "my-platform-name" + #define HTTP_USER_AGENT_VALUE "my-platform-name" #endif /** * Supported HTTP request methods. */ -#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ -#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ -#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ -#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ +#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ +#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ +#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ +#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ /** * Flags for #HTTPRequestInfo_t.flags. - * These flags control what headers are written or not to the + * These flags control what headers are written or not to the * #HttpRequestHeaders_t.pBuffer. */ + /** * @brief Set this flag to indicate the request is for a persistent connection. - * + * * Setting this will cause a "Connection: Keep-Alive" to be written to the * request. */ -#define HTTP_REQUEST_KEEP_ALIVE_FLAG 0x1U -/** +#define HTTP_REQUEST_KEEP_ALIVE_FLAG 0x1U + +/** * @brief Set this flag to disable automatically writing the Content-Length - * header. + * header. */ -#define HTTP_REQUEST_DISABLE_CONTENT_LENGTH_FLAG 0x2U +#define HTTP_REQUEST_DISABLE_CONTENT_LENGTH_FLAG 0x2U /** * Flags for #HTTPResponse_t.flags. * These flags are populated in #HTTPResponse_t.flags by the #HTTPClient_Send() * function. */ - /** - * @brief This will be set to true if header "Connection: close" is found. - * - * If a "Connection: close" header is present the application should always - * close the connection. - */ -#define HTTP_RESPONSE_CONNECTION_CLOSE_FLAG 0x1U + +/** + * @brief This will be set to true if header "Connection: close" is found. + * + * If a "Connection: close" header is present the application should always + * close the connection. + */ +#define HTTP_RESPONSE_CONNECTION_CLOSE_FLAG 0x1U + /** * @brief This will be set to true if header "Connection: Keep-Alive" is found. */ -#define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U +#define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U /** - * @brief The HTTPNetworkContext is an incomplete type. The application must + * @brief The HTTPNetworkContext is an incomplete type. The application must * define HTTPNetworkContext to the type of their network context. This context * is passed into the network interface functions. */ @@ -79,36 +83,36 @@ typedef struct HTTPNetworkContext HTTPNetworkContext_t; /** * @brief Transport interface for sending data over the network. - * - * If the number of bytes written returned is less than bytesToWrite, then - * #HTTPClient_Send will return HTTP_NETWORK_ERROR. If a negative value is + * + * If the number of bytes written returned is less than bytesToWrite, then + * #HTTPClient_Send will return HTTP_NETWORK_ERROR. If a negative value is * returned then this #HTTPClient_Send will also return #HTTP_NETWORK_ERROR. - * + * * @param[in] context User defined context. * @param[in] pBuffer Buffer to write to the network stack. * @param[in] bytesToWrite Number of bytes to write to the network. * @return The number of bytes written or a negative network error code. */ -typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t *pContext, - const void * pBuffer, +typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, + const void * pBuffer, size_t bytesToWrite ); /** * @brief Transport interface for reading data on the network. - * + * * This function will read up to bytesToRead amount of data from the network. * If this function returns a value less than zero, then #HTTPClient_Send will - * return #HTTP_NETWORK_ERROR. If this function returns less than the + * return #HTTP_NETWORK_ERROR. If this function returns less than the * bytesToRead and greater than zero, then this function will be invoked again * if the data in pBuffer contains a partial HTTP response message. - * + * * @param[in] context User defined context. * @param[in] pBuffer Buffer to read network data into. * @param[in] bytesToRead Number of bytes requested from the network. * @return The number of bytes read or a negative error code. */ -typedef int32_t (* HTTPTransportRecv_t )( HTTPNetworkContext_t *pContext, - const void * pBuffer, +typedef int32_t (* HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, + const void * pBuffer, size_t bytesToRead ); /** @@ -118,7 +122,7 @@ typedef struct HTTPTransportInterface { HTTPTransportRecv_t recv; HTTPTransportSend_t send; - HTTPNetworkContext_t* pContext; + HTTPNetworkContext_t * pContext; } HTTPTransportInterface_t; /** @@ -140,31 +144,30 @@ typedef enum HTTPStatus } HTTPStatus_t; /** - * @brief Represents header data that will be sent in an HTTP request. - * + * @brief Represents header data that will be sent in an HTTP request. + * * The memory for the header data buffer is supplied by the user. Information in * the buffer will be filled by calling #HTTPClient_InitializeHeaders. */ typedef struct HTTPRequestHeaders { - /** * @brief Buffer to hold the raw HTTP request headers. - * + * * This buffer is supplied by the application. - * - * This buffer is owned by the library during #HTTPClient_AddHeader, + * + * This buffer is owned by the library during #HTTPClient_AddHeader, * #HTTPClient_AddRangeHeader, #HTTPClient_InitializeRequestHeaders, and - * #HTTPClient_Send. This buffer should not be modifed until + * #HTTPClient_Send. This buffer should not be modifed until * after these functions return. - * - * For optimization this buffer may be re-used with the response. The user - * can re-use this buffer for the storing the response from the server in + * + * For optimization this buffer may be re-used with the response. The user + * can re-use this buffer for the storing the response from the server in * #HTTPResponse_t.pBuffer. */ - uint8_t* pBuffer; + uint8_t * pBuffer; size_t bufferLen; /**< The length of pBuffer in bytes. */ - + /** * @brief The actual size in bytes of headers in the buffer. This field * is updated by the HTTP Client library functions #HTTPClient_AddHeader, @@ -179,27 +182,27 @@ typedef struct HTTPRequestHeaders typedef struct HTTPRequestInfo { /** - * @brief The HTTP request method e.g. "GET", "POST", "PUT", or "HEAD". + * @brief The HTTP request method e.g. "GET", "POST", "PUT", or "HEAD". */ - const char* method; + const char * method; size_t methodLen; /**< The length of the method in bytes. */ - + /** * @brief The Request-URI to the objects of interest, e.g. "/path/to/item.txt". */ - const char* pPath; + const char * pPath; size_t pathLen; /**< The length of the path in bytes. */ /** * @brief The server's host name, e.g. "my-storage.my-cloud.com". - * + * * The host does not have a "https://" or "http://" prepending. */ - const char* pHost; + const char * pHost; size_t hostLen; /**< The length of the host in bytes. */ - + /** - * @brief Flags to activate other request header configurations. + * @brief Flags to activate other request header configurations. */ uint32_t flags; } HTTPRequestInfo_t; @@ -207,10 +210,11 @@ typedef struct HTTPRequestInfo /** - * @brief Callback to intercept headers during the first parse through of the + * @brief Callback to intercept headers during the first parse through of the * response as it is received from the network. */ -typedef struct httpResponseParsingCallback { +typedef struct httpResponseParsingCallback +{ /** * @brief Invoked when both a header field and its associated header value are found. * @param[in] pContext User context. @@ -220,10 +224,15 @@ typedef struct httpResponseParsingCallback { * @param[in] valueLen Length in bytes of the value. * @param[in] statusCode The HTTP response status-code. */ - void (*onHeaderCallback)( void* pContext, const char* fieldLoc, size_t fieldLen, const char* valueLoc, size_t valueLen, uint16_t statusCode ); + void ( * onHeaderCallback )( void * pContext, + const char * fieldLoc, + size_t fieldLen, + const char * valueLoc, + size_t valueLen, + uint16_t statusCode ); /* Private context for the application. */ - void *pContext; + void * pContext; } HTTPClient_HeaderParsingCallback_t; /** @@ -232,84 +241,84 @@ typedef struct httpResponseParsingCallback { typedef struct HTTPResponse { /** - * @brief Buffer for both the raw HTTP header and body. - * + * @brief Buffer for both the raw HTTP header and body. + * * This buffer is supplied by the application. - * + * * This buffer is owned by the library during #HTTPClient_Send and * #HTTPClient_ReadHeader. This buffer should not be modifed until after * these functions return. - * + * * For optimization this buffer may be used with the request headers. The * request header buffer is configured in #HTTPRequestHeaders_t.pBuffer. * When the same buffer is used for the request headers, #HTTPClient_Send - * will send the headers in the buffer first, then fill the buffer with + * will send the headers in the buffer first, then fill the buffer with * the response message. */ - uint8_t* pBuffer; + uint8_t * pBuffer; size_t bufferLen; /**< The length of the response buffer in bytes. */ /** - * @brief Optional callback for intercepting the header during the first + * @brief Optional callback for intercepting the header during the first * parse through of the response as is it receive from the network. * Set to NULL to disable. */ - HTTPClient_HeaderParsingCallback_t* pHeaderParsingCallback; - + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback; + /** * @brief The starting location of the response headers in pBuffer. - * + * * This is updated by #HTTPClient_Send. */ - uint8_t* pHeaders; + uint8_t * pHeaders; /** * @brief Byte length of the response headers in pBuffer. - * + * * This is updated by #HTTPClient_Send. */ - size_t headersLen; + size_t headersLen; /** * @brief The starting location of the response body in pBuffer. - * + * * This is updated by #HTTPClient_Send. */ - uint8_t* pBody; + uint8_t * pBody; /** * @brief Byte length of the body in pBuffer. - * + * * This is updated by #HTTPClient_Send. */ size_t bodyLen; - + /* Useful HTTP header values found. */ /** * @brief The HTTP response Status-Code. - * + * * This is updated by #HTTPClient_Send. */ uint16_t statusCode; - + /** * @brief The value in the "Content-Length" header is returned here. - * + * * This is updated by #HTTPClient_Send. */ size_t contentLength; /** * @brief Count of the headers sent by the server. - * + * * This is updated by #HTTPClient_Send. */ size_t headerCount; /** * @brief Flags of useful headers found in the response. - * + * * This is updated by #HTTPClient_Send. */ uint32_t flags; @@ -317,31 +326,31 @@ typedef struct HTTPResponse /** * @brief Initialize the request headers, stored in - * #HTTPRequestHeaders_t.pBuffer, with initial configurations from + * #HTTPRequestHeaders_t.pBuffer, with initial configurations from * #HTTPRequestInfo_t. - * + * * Upon return, #HTTPRequestHeaders_t.headersLen will be updated with the number * of bytes written. - * + * * TODO: Expand documentation. - * + * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pRequestInfo Initial request header configurations. * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ -HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, +HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, const HTTPRequestInfo_t * pRequestInfo ); /** - * @brief Add a header to the request headers stored in + * @brief Add a header to the request headers stored in * #HTTPRequestHeaders_t.pBuffer. - * + * * Upon return, pRequestHeaders->headersLen will be updated with the number of * bytes written. - * + * * TODO: Expand documentation. - * + * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pName The header field name to write. * @param[in] nameLen The byte length of the header field name. @@ -350,38 +359,38 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ -HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char* pName, - size_t nameLen, - const char * pValue, +HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, + const char * pName, + size_t nameLen, + const char * pValue, size_t valueLen ); /** * @brief Add the byte range request header to the request headers store in * #HTTPRequestHeaders_t.pBuffer. - * - * For example, if requesting for the first 1kB of a file the following would be + * + * For example, if requesting for the first 1kB of a file the following would be * written "Range: bytes=0-1024\r\n". - * + * * TODO: Add documentation about rangeStart and rangeEnd configuration. - * + * * @param[in] pRequestHeaders Request header buffer information. * @param[in] rangeStart The starting range for the requested file. * @param[in] rangeEnd The ending range for the requested file. * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ -HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t *pRequestHeaders, +HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, int32_t rangeStart, int32_t rangeEnd ); /** * @brief Send the request headers in #HTTPRequestHeaders_t and request body in - * parameter pRequestBodyBuf over the transport. The response is received in + * parameter pRequestBodyBuf over the transport. The response is received in * #HTTPResponse_t. - * + * * TODO: Expand documentation. - * + * * @param[in] pTransport Transport interface, see #HTTPTransportInterface_t for * more information. * @param[in] pRequestHeaders Request configuration containing the buffer of @@ -393,34 +402,34 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t *pRequestHeaders, * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ -HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t* pTransport, - const HTTPRequestHeaders_t* pRequestHeaders, - const uint8_t* pRequestBodyBuf, +HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, + const HTTPRequestHeaders_t * pRequestHeaders, + const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, - HTTPResponse_t* pResponse ); + HTTPResponse_t * pResponse ); /** * @brief Read a header from the completed response #HTTPResponse_t. This will * return the response header value location within #HTTPResponse_t.pBuffer. - * + * * This function should be used only a completed response. A #HTTPResponse_t is * not complete until #HTTPClient_Send returns. - * + * * TODO: Expand documentation. - * + * * @param[in] pResponse Completed response. * @param[in] pName The header field name to read. * @param[in] nameLen The length of the header field name in bytes. - * @param[out] pValue The location of the header value in + * @param[out] pValue The location of the header value in * #HTTPResponse_t.pBuffer. * @param[out] valueLen The length of the header value in bytes. * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ -HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t* pResponse, - const char* pName, +HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, + const char * pName, size_t nameLen, - char **pValue, - size_t* valueLen ); + char ** pValue, + size_t * valueLen ); -#endif \ No newline at end of file +#endif /* ifndef HTTP_CLIENT_H_ */ diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 7dd91015a4..5992cdfe80 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1,42 +1,42 @@ #include "http_client.h" -HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, - const HTTPRequestInfo_t * pRequestInfo ) +HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, + const HTTPRequestInfo_t * pRequestInfo ) { return HTTP_NOT_SUPPORTED; } -HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char* pName, - size_t nameLen, - const char * pValue, - size_t valueLen ) +HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, + const char * pName, + size_t nameLen, + const char * pValue, + size_t valueLen ) { return HTTP_NOT_SUPPORTED; } -HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t *pRequestHeaders, - int32_t rangeStart, - int32_t rangeEnd ) +HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, + int32_t rangeStart, + int32_t rangeEnd ) { return HTTP_NOT_SUPPORTED; } -HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t* pTransport, - const HTTPRequestHeaders_t* pRequestHeaders, - const uint8_t* pRequestBodyBuf, // For a PUT or POST request. - size_t reqBodyBufLen, - httpResponse_t* pResponse ) +HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, + const HTTPRequestHeaders_t * pRequestHeaders, + const uint8_t * pRequestBodyBuf, + size_t reqBodyBufLen, + HTTPResponse_t * pResponse ) { return HTTP_NOT_SUPPORTED; } -HTTPStatus_t HTTPClient_ReadHeader( httpResponse_t* pResponse, - const char* pName, - size_t nameLen, - char **pValue, - size_t* valueLen ) +HTTPStatus_t HTTPClient_ReadHeader( HTTPResponse_t * pResponse, + const char * pName, + size_t nameLen, + char ** pValue, + size_t * valueLen ) { return HTTP_NOT_SUPPORTED; -} \ No newline at end of file +} From ac4d06ffbd8849d3fe504a05d656b44e041941f3 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 22 Apr 2020 12:46:11 -0700 Subject: [PATCH 467/844] Fix build warnings of logging library (#888) --- .../utilities/include/iot_logging_levels.h | 109 ++++++++++++++++++ .../utilities/include/iot_logging_setup.h | 94 ++------------- platform/include/iot_clock.h | 3 - platform/posix/iot_logging.c | 67 ++++++++--- 4 files changed, 171 insertions(+), 102 deletions(-) create mode 100644 libraries/standard/utilities/include/iot_logging_levels.h diff --git a/libraries/standard/utilities/include/iot_logging_levels.h b/libraries/standard/utilities/include/iot_logging_levels.h new file mode 100644 index 0000000000..d3cd716340 --- /dev/null +++ b/libraries/standard/utilities/include/iot_logging_levels.h @@ -0,0 +1,109 @@ +/* + * Logging Level Macros + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_logging_levels.h + * @brief Defines the logging level macros. + */ + +#ifndef IOT_LOGGING_LEVELS_H_ +#define IOT_LOGGING_LEVELS_H_ + +/** + * @constantspage{logging,logging library} + * + * @section logging_constants_levels Log levels + * @brief Log levels for the libraries in this SDK. + * + * Each library should specify a log level by setting @ref LIBRARY_LOG_LEVEL. + * All log messages with a level at or below the specified level will be printed + * for that library. + * + * Currently, there are 4 log levels. In the order of lowest to highest, they are: + * - #IOT_LOG_NONE
+ * @copybrief IOT_LOG_NONE + * - #IOT_LOG_ERROR
+ * @copybrief IOT_LOG_ERROR + * - #IOT_LOG_WARN
+ * @copybrief IOT_LOG_WARN + * - #IOT_LOG_INFO
+ * @copybrief IOT_LOG_INFO + * - #IOT_LOG_DEBUG
+ * @copybrief IOT_LOG_DEBUG + */ + +/** + * @brief No log messages. + * + * When @ref LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no + * logging messages are printed. + */ +#define IOT_LOG_NONE 0 + +/** + * @brief Represents erroneous application state or event. + * + * These messages describe the situations when a library encounters an error from + * which it cannot recover. + * + * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either + * of #IOT_LOG_ERROR, #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + */ +#define IOT_LOG_ERROR 1 + +/** + * @brief Message about an abnormal event. + * + * These messages describe the situations when a library encounters + * abnormal event that may be indicative of an error. Libraries continue + * execution after logging a warning. + * + * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either + * of #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + */ +#define IOT_LOG_WARN 2 + +/** + * @brief A helpful, informational message. + * + * These messages describe normal execution of a library. They provide + * the progress of the program at a coarse-grained level. + * + * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either + * of #IOT_LOG_INFO or #IOT_LOG_DEBUG. + */ +#define IOT_LOG_INFO 3 + +/** + * @brief Detailed and excessive debug information. + * + * Debug log messages are used to provide the + * progress of the program at a fine-grained level. These are mostly used + * for debugging and may contain excessive information such as internal + * variables, buffers, or other specific information. + * + * These messages are only printed when @ref LIBRARY_LOG_LEVEL is defined as + * #IOT_LOG_DEBUG. + */ +#define IOT_LOG_DEBUG 4 + +#endif /* ifndef IOT_LOGGING_LEVELS_H_ */ diff --git a/libraries/standard/utilities/include/iot_logging_setup.h b/libraries/standard/utilities/include/iot_logging_setup.h index 5ef71421d5..20006663ed 100644 --- a/libraries/standard/utilities/include/iot_logging_setup.h +++ b/libraries/standard/utilities/include/iot_logging_setup.h @@ -1,5 +1,5 @@ /* - * IoT Common V1.1.0 + * Common Logging Framework * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -22,7 +22,7 @@ /** * @file iot_logging_setup.h - * @brief Defines the logging macro #IotLog. + * @brief Defines the common logging framework that calls #IotLog interface. */ #ifndef IOT_LOGGING_SETUP_H_ @@ -31,83 +31,9 @@ /* The config header is always included first. */ #include "config.h" -/** - * @constantspage{logging,logging library} - * - * @section logging_constants_levels Log levels - * @brief Log levels for the libraries in this SDK. - * - * Each library should specify a log level by setting @ref LIBRARY_LOG_LEVEL. - * All log messages with a level at or below the specified level will be printed - * for that library. - * - * Currently, there are 4 log levels. In the order of lowest to highest, they are: - * - #IOT_LOG_NONE
- * @copybrief IOT_LOG_NONE - * - #IOT_LOG_ERROR
- * @copybrief IOT_LOG_ERROR - * - #IOT_LOG_WARN
- * @copybrief IOT_LOG_WARN - * - #IOT_LOG_INFO
- * @copybrief IOT_LOG_INFO - * - #IOT_LOG_DEBUG
- * @copybrief IOT_LOG_DEBUG - */ - -/** - * @brief No log messages. - * - * When @ref LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no - * logging messages are printed. - */ -#define IOT_LOG_NONE 0 - -/** - * @brief Represents erroneous application state or event. - * - * These messages describe the situations when a library encounters an error from - * which it cannot recover. - * - * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either - * of #IOT_LOG_ERROR, #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. - */ -#define IOT_LOG_ERROR 1 - -/** - * @brief Message about an abnormal event. - * - * These messages describe the situations when a library encounters - * abnormal event that may be indicative of an error. Libraries continue - * execution after logging a warning. - * - * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either - * of #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. - */ -#define IOT_LOG_WARN 2 +/* Include header for logging level macros. */ +#include "iot_logging_levels.h" -/** - * @brief A helpful, informational message. - * - * These messages describe normal execution of a library. They provide - * the progress of the program at a coarse-grained level. - * - * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either - * of #IOT_LOG_INFO or #IOT_LOG_DEBUG. - */ -#define IOT_LOG_INFO 3 - -/** - * @brief Detailed and excessive debug information. - * - * Debug log messages are used to provide the - * progress of the program at a fine-grained level. These are mostly used - * for debugging and may contain excessive information such as internal - * variables, buffers, or other specific information. - * - * These messages are only printed when @ref LIBRARY_LOG_LEVEL is defined as - * #IOT_LOG_DEBUG. - */ -#define IOT_LOG_DEBUG 4 /** * @functionpage{IotLog,logging,log} @@ -270,10 +196,14 @@ #define IotLogDebugWithArgs( pFormat, ... ) #else /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ - #define IotLogError( ... ) - #define IotLogWarn( ... ) - #define IotLogInfo( ... ) - #define IotLogDebug( ... ) + #define IotLogError( message ) + #define IotLogErrorWithArgs( pFormat, ... ) + #define IotLogWarn( message ) + #define IotLogWarnWithArgs( pFormat, ... ) + #define IotLogInfo( message ) + #define IotLogInfoWithArgs( pFormat, ... ) + #define IotLogDebug( message ) + #define IotLogDebugWithArgs( pFormat, ... ) #endif /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ #endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) */ diff --git a/platform/include/iot_clock.h b/platform/include/iot_clock.h index 7bb762d518..2a99809b6b 100644 --- a/platform/include/iot_clock.h +++ b/platform/include/iot_clock.h @@ -28,9 +28,6 @@ #ifndef IOT_CLOCK_H_ #define IOT_CLOCK_H_ -/* The config header is always included first. */ -#include "config.h" - /* Standard includes. */ #include #include diff --git a/platform/posix/iot_logging.c b/platform/posix/iot_logging.c index 709dff13f3..c41c14c07c 100644 --- a/platform/posix/iot_logging.c +++ b/platform/posix/iot_logging.c @@ -28,13 +28,18 @@ /* Standard includes. */ #include #include +#include #include +#include /* Platform clock include. */ -#include "platform/iot_clock.h" +#include "platform/include/iot_clock.h" + +/* Include header for logging level macros. */ +#include "iot_logging_levels.h" /* Logging includes. */ -#include "iot_logging.h" +#include "platform/include/iot_logging.h" /*-----------------------------------------------------------*/ @@ -50,20 +55,48 @@ */ #define MAX_TIMESTRING_LENGTH ( 64 ) +/** + * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate + * `[]` and a null-terminator. + */ +#define MAX_LOG_LEVEL_LENGTH ( 8 ) + /*-----------------------------------------------------------*/ /** - * @brief Lookup table for log levels. + * @brief Log level code to string conversion utility. * - * Converts one of the @ref logging_constants_levels to a string. + * @param[in] messageLevel The log level to convert to string. + * + * @return The constant string representation of the log level. */ -static const char * const _pLogLevelStrings[] = +static const char * _log_level_strerror( int32_t messageLevel ) { - "ERROR", /* IOT_LOG_ERROR */ - "WARN ", /* IOT_LOG_WARN */ - "INFO ", /* IOT_LOG_INFO */ - "DEBUG" /* IOT_LOG_DEBUG */ -}; + assert( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ); + + const char * retVal = NULL; + + switch( messageLevel ) + { + case IOT_LOG_ERROR: + retVal = "ERROR"; + break; + + case IOT_LOG_WARN: + retVal = "WARN"; + break; + + case IOT_LOG_INFO: + retVal = "INFO"; + break; + + case IOT_LOG_DEBUG: + retVal = "DEBUG"; + break; + } + + return retVal; +} /*-----------------------------------------------------------*/ @@ -83,7 +116,7 @@ static bool _reallocLoggingBuffer( void ** pOldBuffer, ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); /* Free the old buffer and update the pointer. */ - IotLogging_Free( *pOldBuffer ); + free( *pOldBuffer ); *pOldBuffer = pNewBuffer; status = true; @@ -129,12 +162,12 @@ void IotLog_Generic( int32_t messageLevel, requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, bufferSize - bufferPosition, "[%s]", - _pLogLevelStrings[ messageLevel ] ); + _log_level_strerror( messageLevel ) ); /* Check for encoding errors. */ if( requiredMessageSize <= 0 ) { - IotLogging_Free( pLoggingBuffer ); + free( pLoggingBuffer ); return; } @@ -193,7 +226,7 @@ void IotLog_Generic( int32_t messageLevel, bufferSize ) == false ) { /* If buffer reallocation failed, return. */ - IotLogging_Free( pLoggingBuffer ); + free( pLoggingBuffer ); return; } @@ -214,16 +247,16 @@ void IotLog_Generic( int32_t messageLevel, /* Check for encoding errors. */ if( requiredMessageSize <= 0 ) { - IotLogging_Free( pLoggingBuffer ); + free( pLoggingBuffer ); return; } /* Print the logging buffer to stdout. */ - IotLogging_Puts( pLoggingBuffer ); + puts( pLoggingBuffer ); /* Free the logging buffer. */ - IotLogging_Free( pLoggingBuffer ); + free( pLoggingBuffer ); } /*-----------------------------------------------------------*/ From c36c71a67e71dc405cdd652a1adc802166949c3b Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 23 Apr 2020 11:50:39 -0700 Subject: [PATCH 468/844] HTTPClient_Send(): Send Request ONLY (#890) * Check for NULL parameters related to sending and invoke transport->send(). * Add more files to the gitignore. * Update testing makefile for latest in Mock4thewin tool. --- .gitignore | 8 + libraries/standard/http/include/http_client.h | 40 ++- libraries/standard/http/src/http_client.c | 176 +++++++++++++- .../http/src/private/http_client_internal.h | 31 +++ libraries/standard/http/test/Makefile | 13 +- libraries/standard/http/test/README.md | 4 +- libraries/standard/http/test/common.h | 5 +- libraries/standard/http/test/config.h | 23 ++ .../standard/http/test/test-HTTPClient_Send.c | 228 ++++++++++++++++++ 9 files changed, 518 insertions(+), 10 deletions(-) create mode 100644 libraries/standard/http/src/private/http_client_internal.h create mode 100644 libraries/standard/http/test/config.h create mode 100644 libraries/standard/http/test/test-HTTPClient_Send.c diff --git a/.gitignore b/.gitignore index 3ca54d3698..ffb79c4814 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,11 @@ doc/tag/* # Ignore CMake build directory. build/ + +#Ignore build artifacts +*.o + +# Ignore code coverage artifacts +*.gcda +*.gcno +*.gcov diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 78e5f4e305..577bb74df9 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -2,7 +2,7 @@ #define HTTP_CLIENT_H_ #include -#include +#include "config.h" /** * @brief Maximum size, in bytes, of headers allowed from the server. @@ -91,6 +91,7 @@ typedef struct HTTPNetworkContext HTTPNetworkContext_t; * @param[in] context User defined context. * @param[in] pBuffer Buffer to write to the network stack. * @param[in] bytesToWrite Number of bytes to write to the network. + * * @return The number of bytes written or a negative network error code. */ typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, @@ -109,10 +110,11 @@ typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, * @param[in] context User defined context. * @param[in] pBuffer Buffer to read network data into. * @param[in] bytesToRead Number of bytes requested from the network. + * * @return The number of bytes read or a negative error code. */ typedef int32_t (* HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, - const void * pBuffer, + void * pBuffer, size_t bytesToRead ); /** @@ -130,9 +132,38 @@ typedef struct HTTPTransportInterface */ typedef enum HTTPStatus { + /** + * @brief The HTTP Client library function completed successfully. + * + * Functions that may return this value: + * - #HTTPClient_InitializeRequestHeaders + * - #HTTPClient_AddHeader + * - #HTTPClient_AddRangeHeader + * - #HTTPClient_Send + * - #HTTPClient_ReadHeader + */ HTTP_SUCCESS = 0, + + /** + * @brief The HTTP Client library function input an invalid parameter. + * + * Functions that may return this value: + * - #HTTPClient_InitializeRequestHeaders + * - #HTTPClient_AddHeader + * - #HTTPClient_AddRangeHeader + * - #HTTPClient_Send + * - #HTTPClient_ReadHeader + */ HTTP_INVALID_PARAMETER, + + /** + * @brief A network error was returned from the transport interface. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ HTTP_NETWORK_ERROR, + HTTP_NOT_SUPPORTED, HTTP_PARTIAL_RESPONSE, HTTP_INSUFFICIENT_MEMORY, @@ -336,6 +367,7 @@ typedef struct HTTPResponse * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pRequestInfo Initial request header configurations. + * * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ @@ -356,6 +388,7 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques * @param[in] nameLen The byte length of the header field name. * @param[in] pValue The header value to write. * @param[in] valueLen The byte length of the header field value. + * * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ @@ -377,6 +410,7 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, * @param[in] pRequestHeaders Request header buffer information. * @param[in] rangeStart The starting range for the requested file. * @param[in] rangeEnd The ending range for the requested file. + * * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ @@ -399,6 +433,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * @param[in] reqBodyBufLen The length of the request entity in bytes. * @param[in] pResponse The response message and some notable response * parameters will be returned here on success. + * * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ @@ -423,6 +458,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, * @param[out] pValue The location of the header value in * #HTTPResponse_t.pBuffer. * @param[out] valueLen The length of the header value in bytes. + * * @return #HTTP_SUCCESS if successful, an error code otherwise. * TODO: Update for exact error codes returned. */ diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 5992cdfe80..fe1f400308 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1,4 +1,35 @@ #include "http_client.h" +#include "private/http_client_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Send the HTTP headers over the transport send interface. + * + * @param pTransport Transport interface. + * @param pRequestHeaders Request headers to send, it includes the buffer and length. + * + * @return #HTTP_SUCCESS if successful. If there was a network error or less + * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. + */ +static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTransport, + const HTTPRequestHeaders_t * pRequestHeaders ); + +/** + * @brief Send the HTTP body over the transport send interface. + * + * @param pTransport Transport interface. + * @param pRequestBodyBuf Request body buffer. + * @param reqBodyLen Length of the request body buffer. + * + * @return #HTTP_SUCCESS if successful. If there was a network error or less + * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. + */ +static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, + const uint8_t * pRequestBodyBuf, + size_t reqBodyBufLen ); + +/*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, const HTTPRequestInfo_t * pRequestInfo ) @@ -6,6 +37,7 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques return HTTP_NOT_SUPPORTED; } +/*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, const char * pName, @@ -16,6 +48,8 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, return HTTP_NOT_SUPPORTED; } +/*-----------------------------------------------------------*/ + HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, int32_t rangeStart, int32_t rangeEnd ) @@ -23,15 +57,153 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, return HTTP_NOT_SUPPORTED; } +/*-----------------------------------------------------------*/ + +static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTransport, + const HTTPRequestHeaders_t * pRequestHeaders ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + int32_t transportStatus = 0; + + /* Send the HTTP headers over the network. */ + transportStatus = pTransport->send( pTransport->pContext, + pRequestHeaders->pBuffer, + pRequestHeaders->headersLen ); + + if( transportStatus < 0 ) + { + IotLogErrorWithArgs( "Error in sending the HTTP headers over the transport " + "interface: Transport status %d.", + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + else if( transportStatus != pRequestHeaders->headersLen ) + { + IotLogErrorWithArgs( "Failure in sending HTTP headers: Transport layer " + "did not send the required bytes: Required Bytes=%d" + ", Sent Bytes=%d.", + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + else + { + /* Empty else MISRA 15.7 */ + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, + const uint8_t * pRequestBodyBuf, + size_t reqBodyBufLen ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + int32_t transportStatus = 0; + + /* Send the HTTP body over the network. */ + if( pRequestBodyBuf != NULL ) + { + transportStatus = pTransport->send( pTransport->pContext, + pRequestBodyBuf, + reqBodyBufLen ); + + if( transportStatus < 0 ) + { + IotLogErrorWithArgs( "Error in sending the HTTP body over the " + "transport interface. Transport status %d.", + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + else if( transportStatus != reqBodyBufLen ) + { + IotLogErrorWithArgs( "Failure in sending HTTP headers: Transport layer " + "did not send the required bytes: Required Bytes=%d" + ", Sent Bytes=%d.", + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, + HTTPResponse_t * pResponse ) +{ + /* TODO: Receive the HTTP response with parsing. */ + return HTTP_SUCCESS; +} + +/*-----------------------------------------------------------*/ + HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, const HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, HTTPResponse_t * pResponse ) { - return HTTP_NOT_SUPPORTED; + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + if( pTransport == NULL ) + { + IotLogError( "Parameter check failed: pTransport interface is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pTransport->send == NULL ) + { + IotLogError( "Parameter check failed: pTransport->send is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pTransport->recv == NULL ) + { + IotLogError( "Parameter check failed: pTransport->recv is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestHeaders == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestHeaders->pBuffer == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else + { + /* Empty else MISRA 15.7 */ + } + + /* Send the headers, which are at one location in memory. */ + if( returnStatus == HTTP_SUCCESS ) + { + returnStatus = _sendHttpHeaders( pTransport, + pRequestHeaders ); + } + + /* Send the body, which is at another location in memory. */ + if( returnStatus == HTTP_SUCCESS ) + { + returnStatus = _sendHttpBody( pTransport, + pRequestBodyBuf, + reqBodyBufLen ); + } + + if( returnStatus == HTTP_SUCCESS ) + { + returnStatus = _receiveHttpResponse( pTransport, + pResponse ); + } + + return returnStatus; } +/*-----------------------------------------------------------*/ + HTTPStatus_t HTTPClient_ReadHeader( HTTPResponse_t * pResponse, const char * pName, size_t nameLen, @@ -40,3 +212,5 @@ HTTPStatus_t HTTPClient_ReadHeader( HTTPResponse_t * pResponse, { return HTTP_NOT_SUPPORTED; } + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h new file mode 100644 index 0000000000..7bed7b8937 --- /dev/null +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -0,0 +1,31 @@ +#ifndef HTTP_CLIENT_INTERNAL_H_ +#define HTTP_CLIENT_INTERNAL_H_ + +/** + * AWS IoT Embedded C SDK optional specific logging setup. + */ +#ifdef USE_AWS_IOT_CSDK_LOGGING + #ifdef IOT_LOG_LEVEL_HTTP + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_HTTP + #else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif + #endif + #define LIBRARY_LOG_NAME ( "HTTP" ) + #include "iot_logging_setup.h" +#else /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +/* Otherwise please define logging macros in config.h. */ + #define IotLogError( message ) + #define IotLogErrorWithArgs( format, ... ) + #define IotLogWarn( message ) + #define IotLogWarnWithArgs( format, ... ) + #define IotLogInfo( message ) + #define IotLogInfoWithArgs( format, ... ) + #define IotLogDebug( message ) + #define IotLogDebugWithArgs( format, ... ) +#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ + +#endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 8db1401ef5..c1672306d1 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -1,3 +1,5 @@ +CSDK_ROOT = ../../../.. + # the path to be indexed (default: ..) # Directory indexing includes subdirectories. SOURCE = ../src @@ -6,7 +8,7 @@ SOURCE = ../src #CFLAGS = std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb # include directories given to the compiler (default: none) -INCLUDE = ../include +INCLUDE = . ../include $(CSDK_ROOT) ../../utilities/include ../src # the default goal (default: help) # Set to build or test as desired. @@ -18,13 +20,16 @@ INCLUDE = ../include # functions to dump into separate source files # Values based on test source file names will be dynamically added. -#FUNCTIONS = a_private_func +FUNCTIONS = _sendHttpHeaders _sendHttpBody _receiveHttpResponse # Changes to the above variables should remain above this include. include Mock4thewin.mk # additional dependencies for all tests -$(TESTS): common.h +$(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/posix/iot_logging.o # additional dependency for a specific test -#api_func.c: a_private_func.c +HTTPClient_Send.c: _sendHttpHeaders.c _sendHttpBody.c _receiveHttpResponse.c + +# additional header dependencies for all tests +COMMON = common.h diff --git a/libraries/standard/http/test/README.md b/libraries/standard/http/test/README.md index 1487560534..acb36b246a 100644 --- a/libraries/standard/http/test/README.md +++ b/libraries/standard/http/test/README.md @@ -1,7 +1,7 @@ # Instructions for running the tests: 1. In your GNU compatible environment like WSL, mingw, Linux OS, MAC OS, etc., open a terminal. -1. To get all the required installation scripts, clone this repo: https://github.com/dan4thewin/mock4thewin. +1. To get all the required installation scripts, clone the LATEST from this repo: https://github.com/dan4thewin/mock4thewin. 1. Go to the repo directory: `cd \{mock4thewin repo}` 1. Run this command to install: `sudo ./install /usr/local` 1. To get another required tool, clone this repo https://github.com/dan4thewin/ctags-xref and follow the instructions in the root README.md. @@ -13,5 +13,5 @@ 1. Make a file under {CSDK_ROOT}/libraries/standard/http/test named "test-\[function_name\].c" For example {CSDK_ROOT}/libraries/standard/http/test/test-HTTPClient_AddHeader.c 1. See https://gist.github.com/dan4thewin/6f708bf635f6cb647d9f7bc7f55e4706#the-complete-unit-test for an example unit tests and how to test assert. -1. Any mocked external functions should go into "common.h". +1. Any shared mocked external functions should go into "common.h". 1. Build, run, and get coverage with: `make test` diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h index a33ece27c7..d34a8572ba 100644 --- a/libraries/standard/http/test/common.h +++ b/libraries/standard/http/test/common.h @@ -1,4 +1,7 @@ #include "tap.h" /* Include paths for public enums, structures, and macros. */ -#include "http_client.h" \ No newline at end of file +#include "http_client.h" + +/* Private includes for internal macros. */ +#include "private/http_client_internal.h" \ No newline at end of file diff --git a/libraries/standard/http/test/config.h b/libraries/standard/http/test/config.h new file mode 100644 index 0000000000..3c68d8df57 --- /dev/null +++ b/libraries/standard/http/test/config.h @@ -0,0 +1,23 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define USE_AWS_IOT_CSDK_LOGGING + +#define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG + +/* Include file for POSIX reference implementation. */ +#include "platform/include/iot_logging.h" + +/* Define the IotLog logging interface to enabling logging. + * This demo maps the macro to the reference POSIX implementation for logging. + * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the + * log, as metadata in each log message. */ +#define IotLog( messageLevel, pFormat, ... ) \ + IotLog_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) + +#endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/http/test/test-HTTPClient_Send.c b/libraries/standard/http/test/test-HTTPClient_Send.c new file mode 100644 index 0000000000..1111309f50 --- /dev/null +++ b/libraries/standard/http/test/test-HTTPClient_Send.c @@ -0,0 +1,228 @@ +#include +#include +#include "common.h" + + +/* Functions are pulled out into their own C files to be tested as a unit. */ +#include "_sendHttpHeaders.c" +#include "_sendHttpBody.c" +#include "_receiveHttpResponse.c" +#include "HTTPClient_Send.c" + + +/* Template HTTP successful response with no body. */ +#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Length: 43\r\n" \ + "Date: Sun, 14 Jul 2019 06:07:52 GMT\r\n" \ + "ETag: \"3356-5233\"\r\n" \ + "Vary: *\r\n" \ + "P3P: CP=\"This is not a P3P policy\"\r\n" \ + "xserver: www1021\r\n\r\n" +#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1 + +/* Template HTTP request for a head request. */ +#define HTTP_TEST_REQUEST_HEAD \ + "HEAD /somedir/somepage.html HTTP/1.1\r\n" \ + "test-header0: test-value0\r\n" \ + "test-header1: test-value1\r\n" \ + "test-header2: test-value2\r\n" \ + "test-header3: test-value0\r\n" \ + "test-header4: test-value1\r\n" \ + "test-header5: test-value2\r\n" \ + "\r\n" +#define HTTP_TEST_REQUEST_HEAD_LENGTH sizeof( HTTP_TEST_REQUEST_HEAD ) - 1 + +/* Test buffer to share among the test. */ +static uint8_t httpBuffer[ 1024 ] = { 0 }; + +/* Mocked successful transport send. */ +static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + return bytesToWrite; +} + +/* Mocked successful transport read. */ +static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + ( void ) pBuffer; + + memcpy( pBuffer, + HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY, + sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1 ); + + return sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1; +} + +/* Mocked network error returning transport send. */ +static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + ( void ) bytesToWrite; + + return -1; +} + +/* Mocked transport send that returns less bytes than expected. */ +static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + ( void ) bytesToWrite; + + return bytesToWrite - 1; +} + + +int main() +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + HTTPResponse_t response = { 0 }; + + response.pBuffer = httpBuffer; + response.bufferLen = sizeof( httpBuffer ); + + HTTPTransportInterface_t transportInterface = + { + .recv = transportRecvSuccess, + .send = transportSendSuccess, + .pContext = NULL + }; + + HTTPRequestHeaders_t requestHeadersHead = + { + .pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_HEAD ), + .bufferLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1, + .headersLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1 + }; + +#define reset() \ + do { \ + transportInterface.recv = transportRecvSuccess; \ + transportInterface.send = transportSendSuccess; \ + transportInterface.pContext = NULL; \ + requestHeadersHead.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_HEAD ); \ + requestHeadersHead.bufferLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1; \ + requestHeadersHead.headersLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1; \ + } while( 0 ) + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Happy Path testing. Test sending a request and successfully receiving a + * response that is within the bounds of the response buffer. */ + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_SUCCESS ); + /* TODO: Check the response is parsed successfully. */ + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test an error returned from a unsuccessful transport send. */ + transportInterface.send = transportSendNetworkError; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_NETWORK_ERROR ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test less bytes than expected returned from transport send. */ + transportInterface.send = transportSendLessThanBytesToWrite; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_NETWORK_ERROR ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test a NULL transport interface. */ + returnStatus = HTTPClient_Send( NULL, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test a NULL transport interface send. */ + transportInterface.send = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test a NULL transport interface recv. */ + transportInterface.recv = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test NULL request headers structure. */ + returnStatus = HTTPClient_Send( &transportInterface, + NULL, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test NULL request header buffer. */ + requestHeadersHead.pBuffer = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeadersHead, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_INVALID_PARAMETER ); + reset(); + + return grade(); +} From 2f8959bfe73c2bb56f7e2807e181ad46d8710eed Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:04:19 -0700 Subject: [PATCH 469/844] Fix Makefile build errors (#891) * Fix Makefile build errors * Update include paths in platform dir --- demos/Makefile | 3 ++- demos/include/config.h | 2 +- platform/posix/iot_clock_posix.c | 2 +- platform/posix/iot_logging.c | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/demos/Makefile b/demos/Makefile index bb2135e7ba..514d40f855 100644 --- a/demos/Makefile +++ b/demos/Makefile @@ -1,7 +1,8 @@ CC = gcc INCLUDE_DIRS = -I include \ - -I ../libraries/standard/mqtt/include + -I ../libraries/standard/mqtt/include \ + -I ../platform/include SRC_FILES = $(shell find src -name '*.c') \ $(shell find ../libraries/standard/mqtt/src -name '*.c') diff --git a/demos/include/config.h b/demos/include/config.h index 7ffc6768fe..2bb83d60bc 100644 --- a/demos/include/config.h +++ b/demos/include/config.h @@ -29,7 +29,7 @@ typedef int MQTTNetworkContext_t; #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 /* Include file for POSIX reference implementation. */ -#include "platform/include/iot_logging.h" +#include "iot_logging.h" /* Define the IotLog logging interface to enabling logging. * This demo maps the macro to the reference POSIX implementation for logging. diff --git a/platform/posix/iot_clock_posix.c b/platform/posix/iot_clock_posix.c index 61f0ec38d8..578ce30c75 100644 --- a/platform/posix/iot_clock_posix.c +++ b/platform/posix/iot_clock_posix.c @@ -38,7 +38,7 @@ #endif /* Platform clock include. */ -#include "platform/include/iot_clock.h" +#include "iot_clock.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM diff --git a/platform/posix/iot_logging.c b/platform/posix/iot_logging.c index c41c14c07c..fbc2634e47 100644 --- a/platform/posix/iot_logging.c +++ b/platform/posix/iot_logging.c @@ -33,13 +33,13 @@ #include /* Platform clock include. */ -#include "platform/include/iot_clock.h" +#include "iot_clock.h" /* Include header for logging level macros. */ #include "iot_logging_levels.h" /* Logging includes. */ -#include "platform/include/iot_logging.h" +#include "iot_logging.h" /*-----------------------------------------------------------*/ From 1220f3e85268b28ee532a77a233b5dafd8346a6b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 24 Apr 2020 14:32:28 -0700 Subject: [PATCH 470/844] Implement MQTT Disconnect (#885) --- demos/mqtt/mqtt_demo_plaintext/Makefile | 15 +++++++ .../mqtt_demo_plaintext}/config.h | 16 -------- .../mqtt_demo_plaintext.c | 39 +++++++++++++++++-- .../standard/mqtt/include/mqtt_lightweight.h | 3 ++ libraries/standard/mqtt/src/mqtt.c | 27 ++++++++++++- .../standard/mqtt/src/mqtt_lightweight.c | 32 ++++++++++++++- 6 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 demos/mqtt/mqtt_demo_plaintext/Makefile rename demos/{include => mqtt/mqtt_demo_plaintext}/config.h (67%) rename demos/{src => mqtt/mqtt_demo_plaintext}/mqtt_demo_plaintext.c (88%) diff --git a/demos/mqtt/mqtt_demo_plaintext/Makefile b/demos/mqtt/mqtt_demo_plaintext/Makefile new file mode 100644 index 0000000000..2f28de5231 --- /dev/null +++ b/demos/mqtt/mqtt_demo_plaintext/Makefile @@ -0,0 +1,15 @@ +CC = gcc + +INCLUDE_DIRS = -I . \ + -I ../../../libraries/standard/mqtt/include + +SRC_FILES = mqtt_demo_plaintext.c \ + $(shell find ../../../libraries/standard/mqtt/src -name '*.c') + +FLAGS = -g -O0 -Wall -Wextra -Wpedantic + +all: + $(CC) $(FLAGS) $(INCLUDE_DIRS) $(SRC_FILES) -o mqtt_demo_plaintext + +clean: + rm mqtt_demo diff --git a/demos/include/config.h b/demos/mqtt/mqtt_demo_plaintext/config.h similarity index 67% rename from demos/include/config.h rename to demos/mqtt/mqtt_demo_plaintext/config.h index 2bb83d60bc..2fa7151673 100644 --- a/demos/include/config.h +++ b/demos/mqtt/mqtt_demo_plaintext/config.h @@ -22,25 +22,9 @@ #ifndef CONFIG_H #define CONFIG_H - /* Set network context to socket (int). */ typedef int MQTTNetworkContext_t; #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 -/* Include file for POSIX reference implementation. */ -#include "iot_logging.h" - -/* Define the IotLog logging interface to enabling logging. - * This demo maps the macro to the reference POSIX implementation for logging. - * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the - * log, as metadata in each log message. */ -#define IotLog( messageLevel, pFormat, ... ) \ - IotLog_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) - #endif /* ifndef CONFIG_H */ diff --git a/demos/src/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c similarity index 88% rename from demos/src/mqtt_demo_plaintext.c rename to demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 99818e0bc9..7c2e90f16e 100644 --- a/demos/src/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -168,19 +168,52 @@ static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) return status; } +static int disconnectMqttSession( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + + MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + } + + return status; +} + int main( int argc, char ** argv ) { + bool mqttSessionEstablished = false; int status; MQTTContext_t context; + int tcpSocket = connectToServer( SERVER, PORT ); - if( tcpSocket != -1 ) + if( tcpSocket == -1 ) { - status = establishMqttSession( &context, tcpSocket ); + status = EXIT_FAILURE; } else { - status = EXIT_FAILURE; + status = establishMqttSession( &context, tcpSocket ); + + if( status == EXIT_SUCCESS ) + { + mqttSessionEstablished = true; + } + } + + if( mqttSessionEstablished == true ) + { + if( status == EXIT_FAILURE ) + { + ( void ) disconnectMqttSession( &context ); + } + else + { + status = disconnectMqttSession( &context ); + } } if( tcpSocket != -1 ) diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index cb1696c925..ec5b88bcf5 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -30,6 +30,7 @@ #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) #define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -153,6 +154,8 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli const MQTTFixedBuffer_t * const pBuffer, size_t * const pHeaderSize ); +MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); + MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ); diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 12f8cc85cd..4049dd140b 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -163,7 +163,32 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) { - return MQTTSuccess; + size_t packetSize; + int32_t bytesSent; + + MQTTStatus_t status = MQTT_GetDisconnectPacketSize( &packetSize ); + + if( status == MQTTSuccess ) + { + status = MQTT_SerializeDisconnect( &( pContext->networkBuffer ) ); + } + + if( status == MQTTSuccess ) + { + bytesSent = sendPacket( pContext, packetSize ); + + if( bytesSent < 0 ) + { + status = MQTTSendFailed; + } + } + + if( status == MQTTSuccess ) + { + pContext->connectStatus = MQTTNotConnected; + } + + return status; } MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 4c8f4cb3ed..743a92432c 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -36,6 +36,9 @@ #define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) #define MQTT_CONNECT_FLAG_USERNAME ( 7 ) +#define MQTT_DISCONNECT_PACKET_SIZE ( 2UL ) +#define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) + #define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) #define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) @@ -370,11 +373,38 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli return MQTTSuccess; } -MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) +MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) { + /* MQTT DISCONNECT packets always have the same size. */ + *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE; + return MQTTSuccess; } +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) +{ + MQTTStatus_t status = MQTTSuccess; + size_t disconnectPacketSize; + + status = MQTT_GetDisconnectPacketSize( &disconnectPacketSize ); + + if( status == MQTTSuccess ) + { + if( pBuffer->size < disconnectPacketSize ) + { + status = MQTTNoMemory; + } + } + + if( status == MQTTSuccess ) + { + pBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + pBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH; + } + + return status; +} + MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; From ce5680411b6b47fa47c808ea4a64abed0486cf0c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 27 Apr 2020 14:30:50 -0700 Subject: [PATCH 471/844] Add documentation of MQTT plain text demo (#897) --- demos/mqtt/mqtt_demo_plaintext/config.h | 10 ++ .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 110 +++++++++++++++++- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/demos/mqtt/mqtt_demo_plaintext/config.h b/demos/mqtt/mqtt_demo_plaintext/config.h index 2fa7151673..e190dce294 100644 --- a/demos/mqtt/mqtt_demo_plaintext/config.h +++ b/demos/mqtt/mqtt_demo_plaintext/config.h @@ -25,6 +25,16 @@ /* Set network context to socket (int). */ typedef int MQTTNetworkContext_t; +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 #endif /* ifndef CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 7c2e90f16e..5acd4fc692 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -19,24 +19,61 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* Standard includes. */ #include +/* POSIX socket includes. */ #include #include #include #include +/* MQTT API header. */ #include "mqtt.h" +/** + * @brief MQTT server host name. + * + * This demo uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + */ #define SERVER "test.mosquitto.org" + +/** + * @brief MQTT server port number. + * + * In general, port 1883 is for unsecured MQTT connections. + */ #define PORT 1883 +/** + * @brief Size of the network buffer for MQTT packets. + */ #define NETWORK_BUFFER_SIZE ( 1024U ) +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ #define CLIENT_IDENTIFIER "testclient" + +/** + * @brief Length of client identifier. + */ #define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a TCP connection to the given server. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * + * @return A file descriptor representing the TCP socket; -1 on failure. + */ static int connectToServer( const char * pServer, uint16_t port ) { int status, tcpSocket = -1; @@ -45,10 +82,12 @@ static int connectToServer( const char * pServer, uint16_t port ) uint16_t netPort = htons( port ); socklen_t serverInfoLength; + /* Perform a DNS lookup on the given host name. */ status = getaddrinfo( pServer, NULL, NULL, &pListHead ); if( status != -1 ) { + /* Attempt to connect to one of the retrieved DNS records. */ for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) { tcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); @@ -87,6 +126,7 @@ static int connectToServer( const char * pServer, uint16_t port ) if( pIndex == NULL ) { + /* Fail if no connection could be established. */ status = -1; } else @@ -103,26 +143,72 @@ static int connectToServer( const char * pServer, uint16_t port ) return status; } +/*-----------------------------------------------------------*/ + +/** + * @brief The transport send function provided to the MQTT context. + * + * @param[in] tcpSocket TCP socket. + * @param[in] pMessage Data to send. + * @param[in] bytesToSend Length of data to send. + * + * @return Number of bytes sent; negative value on error. + */ static int32_t transportSend( int tcpSocket, const void * pMessage, size_t bytesToSend ) { return ( int32_t ) send( tcpSocket, pMessage, bytesToSend, 0 ); } +/*-----------------------------------------------------------*/ + +/** + * @brief The transport receive function provided to the MQTT context. + * + * @param[in] tcpSocket TCP socket. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToSend Size of pBuffer. + * + * @return Number of bytes received; negative value on error. + */ static int32_t transportRecv( int tcpSocket, void * pBuffer, size_t bytesToRecv ) { return ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); } -static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ) -{ - -} +/*-----------------------------------------------------------*/ +/** + * @brief The timer query function provided to the MQTT context. + * + * Currently not implemented. + */ static uint32_t getTime( void ) { return 0; } +/*-----------------------------------------------------------*/ + +/** + * @brief MQTT event callback. + * + * Currently not implemented. + */ +static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ) +{ + +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish an MQTT session over a TCP connection by sending MQTT CONNECT. + * + * @param[in] pContext MQTT context. + * @param[in] tcpSocket TCP socket. + * + * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. + */ static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) { int status = EXIT_SUCCESS; @@ -168,6 +254,15 @@ static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) return status; } +/*-----------------------------------------------------------*/ + +/** + * @brief Close an MQTT session by sending MQTT DISCONNECT. + * + * @param[in] pContext MQTT context. + * + * @return EXIT_SUCCESS if DISCONNECT was successfully sent; EXIT_FAILURE otherwise. + */ static int disconnectMqttSession( MQTTContext_t * pContext ) { int status = EXIT_SUCCESS; @@ -182,6 +277,11 @@ static int disconnectMqttSession( MQTTContext_t * pContext ) return status; } +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + */ int main( int argc, char ** argv ) { bool mqttSessionEstablished = false; @@ -224,3 +324,5 @@ int main( int argc, char ** argv ) return status; } + +/*-----------------------------------------------------------*/ From ca9cc67ca3371e391bf23143f1034bb867109d37 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 28 Apr 2020 16:52:52 -0700 Subject: [PATCH 472/844] Implement and test HTTPClient_AddHeader method (#892) * Uncrustify * Commit before deciding to check line length later on * Add _writeToBuffer * Implement HTTPClient_InitializeRequestHeaders * Implement HTTPClient_AddRangeHeader and HTTPClient_AddHeader * Remove log comment from AddHeader calls * Add happy path test for HTTPClient_AddHeader * Fix itoaLength to support negative int32_t * Address PR comments * Add back HTTP_REQUEST_DISABLE_CONTENT_LENGTH_FLAG feature * Address PR comments * Fix test to include actual happy path for AddHeader * Achieve 100% test coverage for HTTPClient_AddHeader.c * Remove implementation for functions other than HTTPClient_AddHeader * Add checks for length of header * Add comment for AddHeader test * Rebase origin development * Remove tests for http private functions * Add log where comment exists for TODO: Add log. * Remove isNullParam private method * Add documentation for HTTPClient_AddHeader and HTTP_INSUFFICIENT_MEMORY * Add helper for filling header struct * Fix MISRA violations * Fix more MISRA violations involving sign of 0 * Use 0U instead of 0UL * Remove extra comment to doc * Add method dividers * Add doc for private _addHeader * Change INT32_STRING_MAX_LEN to 10 * Remove uint8_t cast in macro lengths to prevent compiler warnings * Fix Log Error in _sendHttpBody from using search and replace * Remove unused Range macros * Group all the input-validation if-checks within the same if-else-if construct * Remove checks for headers added in HTTPClient_InitializeRequestHeaders * Add HTTPClient_AddHeader to HTTPRequestHeaders brief * Address PR comments * Change returnStatus to HTTP_SUCCESS instead of HTTP_INTERNAL_ERROR * Address PR comments * Do away with prematurely updating length of headers * Use snprintf instead of memcpy * Fix backtrackHeaderLen not being decremented properly * Address PR comments before having more beer :) * Refactor _addHeader private method * Add assert statements and check for encoding errors in snprintf * Refactor tests so that there is a reset() after each one * Change memcpy to snprintf * Address PR comments * Add macro for snprintf in _addHeader * Check return value from snprintf is expected * Remove magic numbers * Use macros for format strings in snprintf * Add separate macro for _DIR * Remove interface log * Mock assert in common.h --- libraries/standard/http/include/http_client.h | 57 ++-- libraries/standard/http/src/http_client.c | 151 ++++++++++- .../http/src/private/http_client_internal.h | 57 ++++ libraries/standard/http/test/Makefile | 5 +- libraries/standard/http/test/common.h | 4 +- libraries/standard/http/test/config.h | 2 +- .../http/test/test-HTTPClient_AddHeader.c | 248 +++++++++++++++++- 7 files changed, 498 insertions(+), 26 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 577bb74df9..0a1247a10e 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -27,12 +27,12 @@ #endif /** - * Supported HTTP request methods. + * @brief Supported HTTP request methods. */ -#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ -#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ -#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ -#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ +#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ +#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ +#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ +#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ /** * Flags for #HTTPRequestInfo_t.flags. @@ -166,6 +166,15 @@ typedef enum HTTPStatus HTTP_NOT_SUPPORTED, HTTP_PARTIAL_RESPONSE, + + /** + * @brief The application buffer was not large enough for HTTP headers or body. + * + * Functions that may return this value: + * - #HTTPClient_InitializeRequestHeaders + * - #HTTPClient_AddHeader + * - #HTTPClient_AddRangeHeader + */ HTTP_INSUFFICIENT_MEMORY, HTTP_INTERNAL_ERROR, HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED, @@ -178,7 +187,8 @@ typedef enum HTTPStatus * @brief Represents header data that will be sent in an HTTP request. * * The memory for the header data buffer is supplied by the user. Information in - * the buffer will be filled by calling #HTTPClient_InitializeHeaders. + * the buffer will be filled by calling #HTTPClient_InitializeRequestHeaders and + * #HTTPClient_AddHeader. */ typedef struct HTTPRequestHeaders { @@ -363,13 +373,21 @@ typedef struct HTTPResponse * Upon return, #HTTPRequestHeaders_t.headersLen will be updated with the number * of bytes written. * - * TODO: Expand documentation. + * Each line in the header is listed below and written in this order: + * <#HTTPRequestInfo_t.method> <#HTTPRequestInfo_t.pPath> + * User-Agent: + * Host: <#HTTPRequestInfo_t.pHost> + * Connection: close + * + * Note that Connection header value can be changed to keep-alive by setting + * the HTTP_REQUEST_KEEP_ALIVE_FLAG in #HTTPRequestInfo_t.flags. * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pRequestInfo Initial request header configurations. - * - * @return #HTTP_SUCCESS if successful, an error code otherwise. - * TODO: Update for exact error codes returned. + * @return One of the following: + * - #HTTP_SUCCESS (If successful) + * - #HTTP_INVALID_PARAMETER (If any provided parameters or their members are invalid.) + * - #HTTP_INSUFFICIENT_MEMORY (If provided buffer size is not large enough to hold headers.) */ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, const HTTPRequestInfo_t * pRequestInfo ); @@ -381,20 +399,25 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques * Upon return, pRequestHeaders->headersLen will be updated with the number of * bytes written. * - * TODO: Expand documentation. + * Headers are written in the following format: + * : \r\n\r\n + * The trailing \r\n that denotes the end of the header lines is overwritten, + * if it already exists in the buffer. * * @param[in] pRequestHeaders Request header buffer information. - * @param[in] pName The header field name to write. - * @param[in] nameLen The byte length of the header field name. + * @param[in] pField The header field name to write. + * @param[in] fieldLen The byte length of the header field name. * @param[in] pValue The header value to write. * @param[in] valueLen The byte length of the header field value. * - * @return #HTTP_SUCCESS if successful, an error code otherwise. - * TODO: Update for exact error codes returned. + * @return One of the following: + * - #HTTP_SUCCESS (If successful.) + * - #HTTP_INVALID_PARAMETER (If any provided parameters or their members are invalid.) + * - #HTTP_INSUFFICIENT_MEMORY (If application-provided buffer is not large enough to hold headers.) */ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char * pName, - size_t nameLen, + const char * pField, + size_t fieldLen, const char * pValue, size_t valueLen ); diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index fe1f400308..6ded72a5cd 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1,3 +1,8 @@ +#include +#include +#include +#include + #include "http_client.h" #include "private/http_client_internal.h" @@ -29,6 +34,102 @@ static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen ); +/** + * @brief Write header based on parameters. This method also adds a trailing "\r\n". + * If a trailing "\r\n" already exists in the HTTP header, this method backtracks + * in order to write over it and updates the length accordingly. + * + * @param pRequestHeaders Request header buffer information. + * @param pField The header field name to write. + * @param fieldLen The byte length of the header field name. + * @param pValue The header value to write. + * @param valueLen The byte length of the header field value. + * + * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the + * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. + */ +static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, + const char * pField, + size_t fieldLen, + const char * pValue, + size_t valueLen ); + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, + const char * pField, + size_t fieldLen, + const char * pValue, + size_t valueLen ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + uint8_t * pBufferCur = pRequestHeaders->pBuffer + pRequestHeaders->headersLen; + size_t toAddLen = 0; + size_t backtrackHeaderLen = pRequestHeaders->headersLen; + int32_t bytesWritten = 0; + + assert( pRequestHeaders != NULL ); + assert( pRequestHeaders->pBuffer != NULL ); + assert( pField != NULL ); + assert( pValue != NULL ); + assert( fieldLen != 0u ); + assert( valueLen != 0u ); + + /* Backtrack before trailing "\r\n" (HTTP header end) if it's already written. + * Note that this method also writes trailing "\r\n" before returning. + * The first condition prevents reading before start of the header. */ + if( ( HTTP_HEADER_END_INDICATOR_LEN <= pRequestHeaders->headersLen ) && + ( strncmp( ( char * ) pBufferCur - HTTP_HEADER_END_INDICATOR_LEN, + HTTP_HEADER_END_INDICATOR, HTTP_HEADER_END_INDICATOR_LEN ) == 0 ) ) + { + backtrackHeaderLen -= HTTP_HEADER_LINE_SEPARATOR_LEN; + pBufferCur -= HTTP_HEADER_LINE_SEPARATOR_LEN; + } + + /* Check if there is enough space in buffer for additional header. */ + toAddLen = fieldLen + HTTP_HEADER_FIELD_SEPARATOR_LEN + valueLen + + HTTP_HEADER_LINE_SEPARATOR_LEN + + HTTP_HEADER_LINE_SEPARATOR_LEN; + + /* If we have enough room for the new header line, then write it to the header buffer. */ + if( ( backtrackHeaderLen + toAddLen ) <= pRequestHeaders->bufferLen ) + { + /* Write "Field: Value \r\n" to headers. */ + bytesWritten = snprintf( ( char * ) pBufferCur, + toAddLen, + HTTP_HEADER_ADD_FORMAT, + ( int32_t ) fieldLen, pField, + ( int32_t ) valueLen, pValue ); + + if( ( bytesWritten + HTTP_HEADER_LINE_SEPARATOR_LEN ) != toAddLen ) + { + IotLogErrorWithArgs( "Internal error in snprintf() in _addHeader(). " + "Bytes written: %d.", bytesWritten ); + } + else + { + pBufferCur += bytesWritten; + + /* HTTP_HEADER_LINE_SEPARATOR cannot be written above because snprintf + * writes an extra null byte at the end. */ + memcpy( pBufferCur, HTTP_HEADER_LINE_SEPARATOR, HTTP_HEADER_LINE_SEPARATOR_LEN ); + pRequestHeaders->headersLen = backtrackHeaderLen + toAddLen; + returnStatus = HTTP_SUCCESS; + } + } + else + { + IotLogErrorWithArgs( "Unable to add header in buffer: " + "Buffer has insufficient memory: " + "RequiredBytes=%d, RemainingBufferSize=%d", + toAddLen, + ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ); + returnStatus = HTTP_INSUFFICIENT_MEMORY; + } + + return returnStatus; +} + /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, @@ -40,12 +141,56 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char * pName, - size_t nameLen, + const char * pField, + size_t fieldLen, const char * pValue, size_t valueLen ) { - return HTTP_NOT_SUPPORTED; + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + /* Check for NULL parameters. */ + if( pRequestHeaders == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestHeaders->pBuffer == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( ( pField == NULL ) ) + { + IotLogError( "Parameter check failed: pField is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( ( pValue == NULL ) ) + { + IotLogError( "Parameter check failed: pValue is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( fieldLen == 0u ) + { + IotLogError( "Parameter check failed: fieldLen must be greater than 0." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( valueLen == 0u ) + { + IotLogError( "Parameter check failed: valueLen must be greater than 0." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( returnStatus == HTTP_SUCCESS ) + { + returnStatus = _addHeader( pRequestHeaders, + pField, fieldLen, pValue, valueLen ); + } + + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 7bed7b8937..208f28b3ce 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -28,4 +28,61 @@ #define IotLogDebugWithArgs( format, ... ) #endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +/** + * @brief The HTTP protocol version of this library is HTTP/1.1. + */ +#define HTTP_PROTOCOL_VERSION "HTTP/1.1" +#define HTTP_PROTOCOL_VERSION_LEN ( sizeof( HTTP_PROTOCOL_VERSION ) - 1 ) + +/** + * @brief Default value when pRequestInfo->pPath == NULL. + */ +#define HTTP_EMPTY_PATH "/" +#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1 ) + +/** + * @brief Consants for HTTP header formatting + */ +#define HTTP_HEADER_LINE_SEPARATOR "\r\n" +#define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1 ) +#define HTTP_HEADER_END_INDICATOR "\r\n\r\n" +#define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1 ) +#define HTTP_HEADER_ADD_FORMAT "%.*s" HTTP_HEADER_FIELD_SEPARATOR "%.*s" HTTP_HEADER_LINE_SEPARATOR +#define CARRIAGE_RETURN_CHARACTER '\r' +#define NEWLINE_CHARACTER '\n' +#define HTTP_HEADER_FIELD_SEPARATOR ": " +#define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1 ) +#define COLON_CHARACTER ":" +#define COLON_CHARACTER_LEN ( sizeof( COLON_CHARACTER ) - 1 ) +#define SPACE_CHARACTER " " +#define SPACE_CHARACTER_LEN ( sizeof( SPACE_CHARACTER ) - 1 ) +#define EQUAL_CHARACTER "=" +#define EQUAL_CHARACTER_LEN ( sizeof( EQUAL_CHARACTER ) - 1 ) +#define DASH_CHARACTER "-" +#define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1 ) + +/** + * @brief Constants for header fields added automatically during the request initialization. + */ +#define HTTP_USER_AGENT_FIELD "User-Agent" +#define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1 ) +#define HTTP_HOST_FIELD "Host" +#define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1 ) + +/** + * @brief Constants for header fields added based on flags. + */ +#define HTTP_CONNECTION_FIELD "Connection" +#define HTTP_CONNECTION_FIELD_LEN ( sizeof( HTTP_CONNECTION_FIELD ) - 1 ) +#define HTTP_CONTENT_LENGTH_FIELD "Content-Length" +#define HTTP_CONTENT_LENGTH_FIELD_LEN ( sizeof( HTTP_CONTENT_LENGTH_FIELD ) - 1 ) + +/** + * @brief Constants for header values added based on flags. + */ +#define HTTP_CONNECTION_KEEP_ALIVE_VALUE "keep-alive" +#define HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ( sizeof( HTTP_CONNECTION_KEEP_ALIVE_VALUE ) - 1 ) +#define HTTP_CONNECTION_CLOSE_VALUE "close" +#define HTTP_CONNECTION_CLOSE_VALUE_LEN ( sizeof( HTTP_CONNECTION_CLOSE_VALUE ) - 1 ) + #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index c1672306d1..9ce7b089d8 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -8,7 +8,7 @@ SOURCE = ../src #CFLAGS = std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb # include directories given to the compiler (default: none) -INCLUDE = . ../include $(CSDK_ROOT) ../../utilities/include ../src +INCLUDE = . ../include $(CSDK_ROOT)/platform/include ../../utilities/include ../src # the default goal (default: help) # Set to build or test as desired. @@ -20,7 +20,7 @@ INCLUDE = . ../include $(CSDK_ROOT) ../../utilities/include ../src # functions to dump into separate source files # Values based on test source file names will be dynamically added. -FUNCTIONS = _sendHttpHeaders _sendHttpBody _receiveHttpResponse +FUNCTIONS = _sendHttpHeaders _sendHttpBody _receiveHttpResponse _addHeader # Changes to the above variables should remain above this include. include Mock4thewin.mk @@ -30,6 +30,7 @@ $(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/po # additional dependency for a specific test HTTPClient_Send.c: _sendHttpHeaders.c _sendHttpBody.c _receiveHttpResponse.c +HTTPClient_AddHeader.c: _addHeader.c # additional header dependencies for all tests COMMON = common.h diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h index d34a8572ba..8e2d51d490 100644 --- a/libraries/standard/http/test/common.h +++ b/libraries/standard/http/test/common.h @@ -4,4 +4,6 @@ #include "http_client.h" /* Private includes for internal macros. */ -#include "private/http_client_internal.h" \ No newline at end of file +#include "private/http_client_internal.h" + +#define assert( x ) diff --git a/libraries/standard/http/test/config.h b/libraries/standard/http/test/config.h index 3c68d8df57..6fdd720dc8 100644 --- a/libraries/standard/http/test/config.h +++ b/libraries/standard/http/test/config.h @@ -6,7 +6,7 @@ #define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG /* Include file for POSIX reference implementation. */ -#include "platform/include/iot_logging.h" +#include "iot_logging.h" /* Define the IotLog logging interface to enabling logging. * This demo maps the macro to the reference POSIX implementation for logging. diff --git a/libraries/standard/http/test/test-HTTPClient_AddHeader.c b/libraries/standard/http/test/test-HTTPClient_AddHeader.c index b675b3bfa7..c1a5662e88 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddHeader.c @@ -1,9 +1,253 @@ +#include + #include "common.h" /* Functions are pulled out into their own C files to be tested as a unit. */ +#include "_addHeader.c" #include "HTTPClient_AddHeader.c" +/* Template HTTP header fields and values. */ +#define HTTP_TEST_HEADER_FIELD "Authorization" +#define HTTP_TEST_HEADER_FIELD_LEN ( sizeof( HTTP_TEST_HEADER_FIELD ) - 1 ) +#define HTTP_TEST_HEADER_VALUE "None" +#define HTTP_TEST_HEADER_VALUE_LEN ( sizeof( HTTP_TEST_HEADER_VALUE ) - 1 ) +/* Template for first line of HTTP header. */ +#define HTTP_TEST_HEADER_REQUEST_LINE "GET / HTTP/1.1 \r\n" +#define HTTP_TEST_HEADER_REQUEST_LINE_LEN ( sizeof( HTTP_TEST_HEADER_REQUEST_LINE ) - 1 ) +#define HTTP_REQUEST_HEADERS_INITIALIZER { 0 } +/* Template for snprintf(...) strings. */ +#define HTTP_TEST_SINGLE_HEADER_FORMAT "%s%s: %s\r\n\r\n" +#define HTTP_TEST_DOUBLE_HEADER_FORMAT "%s%s: %s\r\n%s: %s\r\n\r\n" + +/* Length of the following template HTTP header. + * \r\n + * : \r\n + * \r\n + * This is used to initialize the expectedHeader string. */ +#define HTTP_TEST_TEMPLATE_HEADER_LEN \ + ( HTTP_TEST_HEADER_REQUEST_LINE_LEN + \ + HTTP_TEST_HEADER_FIELD_LEN + \ + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_TEST_HEADER_VALUE_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN ) + +/* The longest possible header used for these unit tests. */ +#define HTTP_TEST_MAX_HEADER_LEN \ + ( HTTP_TEST_TEMPLATE_HEADER_LEN + \ + HTTP_TEST_HEADER_FIELD_LEN + \ + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_TEST_HEADER_VALUE_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN ) + +/* Add 1 because snprintf(...) writes a null byte at the end. */ +#define HTTP_TEST_BUFFER_SIZE ( HTTP_TEST_MAX_HEADER_LEN + 1 ) + int main() { - return 0; -} \ No newline at end of file + HTTPRequestHeaders_t reqHeaders = HTTP_REQUEST_HEADERS_INITIALIZER; + HTTPRequestHeaders_t reqHeadersDflt = HTTP_REQUEST_HEADERS_INITIALIZER; + HTTPStatus_t test_err = HTTP_NOT_SUPPORTED; + uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; + char expectedHeader[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; + size_t expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; + struct Header + { + char field[ HTTP_TEST_HEADER_FIELD_LEN ]; + size_t fieldLen; + char value[ HTTP_TEST_HEADER_VALUE_LEN ]; + size_t valueLen; + } + header; + +/* Write template header field and value to a struct to pass as + * parameters to HTTPClient_AddHeader() method. */ +#define fillHeaderStructTemplate() \ + do { \ + memcpy( header.field, \ + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN ); \ + header.fieldLen = HTTP_TEST_HEADER_FIELD_LEN; \ + memcpy( header.value, \ + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); \ + header.valueLen = HTTP_TEST_HEADER_VALUE_LEN; \ + } \ + while( 0 ) + +#define reset() \ + do { \ + test_err = HTTP_NOT_SUPPORTED; \ + reqHeaders = reqHeadersDflt; \ + memset( buffer, 0, HTTP_TEST_BUFFER_SIZE ); \ + memset( expectedHeader, 0, HTTP_TEST_TEMPLATE_HEADER_LEN ); \ + memset( header.field, 0, HTTP_TEST_BUFFER_SIZE ); \ + header.fieldLen = 0; \ + memset( header.value, 0, HTTP_TEST_BUFFER_SIZE ); \ + header.valueLen = 0; \ + fillHeaderStructTemplate(); \ + reqHeaders.pBuffer = buffer; \ + } \ + while( 0 ) + + plan( 23 ); + + /* Happy Path testing. Prefill the user buffer with HTTP_TEST_HEADER_REQUEST_LINE + * and call HTTPClient_AddHeader using the field and value in the header struct. */ + reset(); + /* Add 1 because snprintf(...) writes a null byte at the end. */ + ok( snprintf( expectedHeader, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) + == HTTP_TEST_TEMPLATE_HEADER_LEN ); + expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; + /* Set parameters for reqHeaders. */ + ok( snprintf( ( char * ) reqHeaders.pBuffer, + HTTP_TEST_HEADER_REQUEST_LINE_LEN + 1, + HTTP_TEST_HEADER_REQUEST_LINE ) + == HTTP_TEST_HEADER_REQUEST_LINE_LEN ); + reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; + reqHeaders.headersLen = HTTP_TEST_HEADER_REQUEST_LINE_LEN; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + header.value, header.valueLen ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + expectedHeader, expectedHeaderLen ) == 0 ); + ok( reqHeaders.headersLen == expectedHeaderLen ); + ok( test_err == HTTP_SUCCESS ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test adding extra header with insufficient memory. */ + ok( snprintf( expectedHeader, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) + == HTTP_TEST_TEMPLATE_HEADER_LEN ); + expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; + /* Prefill the buffer with a request line and header. */ + ok( snprintf( ( char * ) reqHeaders.pBuffer, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) + == HTTP_TEST_TEMPLATE_HEADER_LEN ); + reqHeaders.headersLen = HTTP_TEST_TEMPLATE_HEADER_LEN; + reqHeaders.bufferLen = reqHeaders.headersLen; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + header.value, header.valueLen ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + expectedHeader, expectedHeaderLen ) == 0 ); + ok( reqHeaders.headersLen == expectedHeaderLen ); + ok( test_err == HTTP_INSUFFICIENT_MEMORY ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test adding extra header with sufficient memory. */ + expectedHeaderLen = HTTP_TEST_MAX_HEADER_LEN; + ok( snprintf( expectedHeader, expectedHeaderLen + 1, + HTTP_TEST_DOUBLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) + == expectedHeaderLen ); + /* Prefill the buffer with a request line and header. */ + ok( snprintf( ( char * ) reqHeaders.pBuffer, + HTTP_TEST_TEMPLATE_HEADER_LEN + 1, + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) + == HTTP_TEST_TEMPLATE_HEADER_LEN ); + reqHeaders.headersLen = HTTP_TEST_TEMPLATE_HEADER_LEN; + reqHeaders.bufferLen = expectedHeaderLen; + + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + header.value, header.valueLen ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + expectedHeader, expectedHeaderLen ) == 0 ); + ok( reqHeaders.headersLen == expectedHeaderLen ); + ok( test_err == HTTP_SUCCESS ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test a NULL request headers interface. */ + test_err = HTTPClient_AddHeader( NULL, + header.field, header.fieldLen, + header.value, header.valueLen ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test a NULL pBuffer member of request headers. */ + reqHeaders.pBuffer = NULL; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + header.value, header.valueLen ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test NULL header field. */ + reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; + test_err = HTTPClient_AddHeader( &reqHeaders, + NULL, header.fieldLen, + header.value, header.valueLen ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test NULL header value. */ + reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + NULL, header.valueLen ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test that fieldLen > 0. */ + reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, 0, + header.value, header.valueLen ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test that valueLen > 0. */ + reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + header.value, 0 ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test HTTP_INSUFFICIENT_MEMORY error from having buffer size less than + * what is required to fit HTTP headers. */ + ok( snprintf( ( char * ) buffer, + HTTP_TEST_HEADER_REQUEST_LINE_LEN + 1, + HTTP_TEST_HEADER_REQUEST_LINE ) + == HTTP_TEST_HEADER_REQUEST_LINE_LEN ); + reqHeaders.headersLen = HTTP_TEST_HEADER_REQUEST_LINE_LEN; + reqHeaders.pBuffer = buffer; + reqHeaders.bufferLen = HTTP_TEST_TEMPLATE_HEADER_LEN - 1; + test_err = HTTPClient_AddHeader( &reqHeaders, + header.field, header.fieldLen, + header.value, header.valueLen ); + ok( test_err == HTTP_INSUFFICIENT_MEMORY ); + reset(); + + /* -----------------------------------------------------------------------*/ + + return grade(); +} From a13ea65bc70a0aa66f361f40ee97023154b16801 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 29 Apr 2020 14:36:05 -0700 Subject: [PATCH 473/844] Document MQTT lightweight and API functions (#902) --- libraries/standard/mqtt/include/mqtt.h | 28 +++ .../standard/mqtt/include/mqtt_lightweight.h | 185 ++++++++++++++++-- libraries/standard/mqtt/src/mqtt.c | 22 +++ .../standard/mqtt/src/mqtt_lightweight.c | 81 +++++++- 4 files changed, 293 insertions(+), 23 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 70505accb2..b9b94787e0 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -107,11 +107,32 @@ struct MQTTContext uint32_t lastPacketTime; }; +/** + * @brief Initialize an MQTT context. + * + * This function must be called on an MQTT context before any other function. + * + * @brief param[in] pContext The context to initialize. + * @brief param[in] pTransportInterface The transport interface to use with the context. + * @brief param[in] pCallbacks Callbacks to use with the context. + * @brief param[in] pNetworkBuffer Network buffer provided for the context. + */ void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, const MQTTFixedBuffer_t * const pNetworkBuffer ); +/** + * @brief Establish a MQTT session. + * + * @brief param[in] pContext Initialized MQTT context. + * @brief param[in] pConnectInfo MQTT CONNECT packet parameters. + * @brief param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @brief param[out] pSessionPresent Whether a previous session was present. + * Only relevant if not establishing a clean session. + * + * @return See #MQTTStatus_t. + */ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, @@ -130,6 +151,13 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount ); +/** + * @brief Disconnect an MQTT session. + * + * @param[in] pContext Initialized and connected MQTT context. + * + * @return See #MQTTStatus_t. + */ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index ec5b88bcf5..1a93821033 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -28,9 +28,10 @@ #include "config.h" -#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) -#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) -#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) +/* MQTT packet types. */ +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -47,76 +48,215 @@ typedef struct MqttPublishInfo MQTTPublishInfo_t; struct MQTTPacketInfo; typedef struct MQTTPacketInfo MQTTPacketInfo_t; +/** + * @brief Signature of the transport interface receive function. + * + * A function with this signature must be provided to the MQTT library to read + * data off the network. + * + * @param[in] context The network context provided with this function. + * @param[out] pBuffer Buffer to receive network data. + * @param[in] bytesToRecv Bytes to receive from the network. pBuffer must be at + * least this size. + * + * @return The number of bytes received; negative value on failure. + */ typedef int32_t (* MQTTTransportRecvFunc_t )( MQTTNetworkContext_t context, void * pBuffer, size_t bytesToRecv ); +/** + * @brief Return codes from MQTT functions. + */ typedef enum MQTTStatus { - MQTTSuccess = 0, - MQTTBadParameter, - MQTTNoMemory, - MQTTSendFailed, - MQTTRecvFailed, - MQTTBadResponse, - MQTTServerRefused + MQTTSuccess = 0, /**< Function completed successfully. */ + MQTTBadParameter, /**< At least one parameter was invalid. */ + MQTTNoMemory, /**< A provided buffer was too small. */ + MQTTSendFailed, /**< The transport send function failed. */ + MQTTRecvFailed, /**< The transport receive function failed. */ + MQTTBadResponse, /**< An invalid packet was received from the server. */ + MQTTServerRefused /**< The server refused a CONNECT or SUBSCRIBE. */ } MQTTStatus_t; +/** + * @brief MQTT Quality of Service values. + */ typedef enum MQTTQoS { - MQTTQoS0 = 0, - MQTTQoS1 = 1, - MQTTQoS2 = 2 + MQTTQoS0 = 0, /**< Delivery at most once. */ + MQTTQoS1 = 1, /**< Delivery at least once. */ + MQTTQoS2 = 2 /**< Delivery exactly once. */ } MQTTQoS_t; +/** + * @brief Buffer passed to MQTT library. + * + * These buffers are not copied and must remain in scope for the duration of the + * MQTT operation. + */ struct MQTTFixedBuffer { - uint8_t * pBuffer; - size_t size; + uint8_t * pBuffer; /**< @brief Pointer to buffer. */ + size_t size; /**< @brief Size of buffer. */ }; +/** + * @brief MQTT CONNECT packet parameters. + */ struct MQTTConnectInfo { + /** + * @brief Whether to establish a new, clean session or resume a previous session. + */ bool cleanSession; + + /** + * @brief MQTT keep alive period. + */ uint16_t keepAliveSeconds; + + /** + * @brief MQTT client identifier. Must be unique per client. + */ const char * pClientIdentifier; + + /** + * @brief Length of the client identifier. + */ uint16_t clientIdentifierLength; + + /** + * @brief MQTT user name. Set to NULL if not used. + */ const char * pUserName; + + /** + * @brief Length of MQTT user name. Set to 0 if not used. + */ uint16_t userNameLength; + + /** + * @brief MQTT password. Set to NULL if not used. + */ const char * pPassword; + + /** + * @brief Length of MQTT password. Set to 0 if not used. + */ uint16_t passwordLength; }; +/** + * @brief MQTT SUBSCRIBE packet parameters. + */ struct MQTTSubscribeInfo { + /** + * @brief Quality of Service for subscription. + */ MQTTQoS_t qos; + + /** + * @brief Topic filter to subscribe to. + */ const char * pTopicFilter; + + /** + * @brief Length of subscription topic filter. + */ uint16_t topicFilterLength; }; +/** + * @brief MQTT PUBLISH packet parameters. + */ struct MqttPublishInfo { + /** + * @brief Quality of Service for message. + */ MQTTQoS_t qos; + + /** + * @brief Whether this is a retained message. + */ bool retain; + + /** + * @brief Topic name on which the message is published. + */ const char * pTopicName; + + /** + * @brief Length of topic name. + */ uint16_t topicNameLength; + + /** + * @brief Message payload. + */ const void * pPayload; + + /** + * @brief Message payload length. + */ size_t payloadLength; }; +/** + * @brief MQTT incoming packet parameters. + */ struct MQTTPacketInfo { + /** + * @brief Type of incoming MQTT packet. + */ uint8_t type; + + /** + * @brief Packet identifier of incoming MQTT packet. + */ uint16_t packetIdentifier; + + /** + * @brief Remaining serialized data in the MQTT packet. + */ uint8_t * pRemainingData; + + /** + * @brief Length of remaining serialized data. + */ size_t remainingLength; }; +/** + * @brief Get the size and Remaining Length of an MQTT CONNECT packet. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[out] pRemainingLength The Remaining Length of the MQTT CONNECT packet. + * @param[out] pPacketSize The total size of the MQTT CONNECT packet. + * + * @return #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, size_t * const pPacketSize ); +/** + * @brief Serialize an MQTT CONNECT packet in the given buffer. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. + * @param[out] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, size_t remainingLength, @@ -154,8 +294,23 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli const MQTTFixedBuffer_t * const pBuffer, size_t * const pHeaderSize ); +/** + * @brief Get the size of an MQTT DISCONNECT packet. + * + * @param[out] pPacketSize The size of the MQTT DISCONNECT packet. + * + * @return Always returns #MQTTSuccess. + */ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); +/** + * @brief Serialize an MQTT DISCONNECT packet into the given buffer. + * + * @param[out] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ); diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 4049dd140b..edeaa7e290 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -23,6 +23,8 @@ #include "mqtt.h" +/*-----------------------------------------------------------*/ + static int32_t sendPacket( MQTTContext_t * pContext, size_t bytesToSend ) { const uint8_t * pIndex = pContext->networkBuffer.pBuffer; @@ -61,6 +63,8 @@ static int32_t sendPacket( MQTTContext_t * pContext, size_t bytesToSend ) return totalBytesSent; } +/*-----------------------------------------------------------*/ + void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, @@ -77,6 +81,8 @@ void MQTT_Init( MQTTContext_t * const pContext, pContext->nextPacketId = 1; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, @@ -136,6 +142,8 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, return status; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount ) @@ -143,17 +151,23 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pPublishInfo ) { return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) { return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount ) @@ -161,6 +175,8 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) { size_t packetSize; @@ -191,12 +207,16 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) return status; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, uint32_t timeoutMs ) { return MQTTSuccess; } +/*-----------------------------------------------------------*/ + uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) { uint16_t packetId = pContext->nextPacketId; @@ -210,3 +230,5 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) return packetId; } + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 743a92432c..dfc8be8550 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -23,27 +23,57 @@ #include "mqtt_lightweight.h" +/** + * @brief MQTT protocol version 3.1.1. + */ #define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) +/** + * @brief Size of the fixed and variable header of a CONNECT packet. + */ #define MQTT_PACKET_CONNECT_HEADER_SIZE ( 10UL ) -#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) -#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) -#define MQTT_CONNECT_FLAG_WILL ( 2 ) -#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) -#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) -#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) -#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) -#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) +/** + * @brief Maximum size of an MQTT CONNECT packet, per MQTT spec. + */ +#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) +/* MQTT CONNECT flags. */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ + +/** + * @brief The size of MQTT DISCONNECT packets, per MQTT spec. + */ #define MQTT_DISCONNECT_PACKET_SIZE ( 2UL ) + +/** + * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec. + */ #define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) +/** + * @brief Set a bit in an 8-bit unsigned integer. + */ #define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) +/** + * @brief Get the high byte of a 16-bit unsigned integer. + */ #define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +/** + * @brief Get the low byte of a 16-bit unsigned integer. + */ #define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) +/*-----------------------------------------------------------*/ + static size_t remainingLengthEncodedSize( size_t length ) { size_t encodedSize; @@ -75,6 +105,8 @@ static size_t remainingLengthEncodedSize( size_t length ) return encodedSize; } +/*-----------------------------------------------------------*/ + static uint8_t * encodeRemainingLength( uint8_t * pDestination, size_t length ) { @@ -102,6 +134,7 @@ static uint8_t * encodeRemainingLength( uint8_t * pDestination, return pLengthEnd; } +/*-----------------------------------------------------------*/ static uint8_t * encodeString( uint8_t * pDestination, const char * source, @@ -126,6 +159,8 @@ static uint8_t * encodeString( uint8_t * pDestination, return pBuffer; } +/*-----------------------------------------------------------*/ + static int32_t recvExact( MQTTTransportRecvFunc_t recvFunc, MQTTNetworkContext_t networkContext, void * pBuffer, @@ -155,6 +190,8 @@ static int32_t recvExact( MQTTTransportRecvFunc_t recvFunc, return totalBytesRecvd; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, @@ -209,6 +246,8 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect return status; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, size_t remainingLength, @@ -323,6 +362,8 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo return status; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, @@ -331,6 +372,8 @@ MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSub return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, uint16_t packetId, @@ -340,6 +383,8 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, uint16_t packetId, @@ -349,6 +394,8 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, size_t * const pRemainingLength, size_t * const pPacketSize ) @@ -356,6 +403,8 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, @@ -364,6 +413,8 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, @@ -373,6 +424,8 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) { /* MQTT DISCONNECT packets always have the same size. */ @@ -381,6 +434,8 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -405,11 +460,15 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) return status; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) { return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, MQTTNetworkContext_t networkContext, MQTTPacketInfo_t * const pIncomingPacket ) @@ -417,6 +476,8 @@ MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, MQTTPublishInfo_t * const pPublishInfo ) @@ -424,6 +485,8 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa return MQTTSuccess; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, bool * const pSessionPresent ) @@ -431,3 +494,5 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket return MQTTSuccess; } + +/*-----------------------------------------------------------*/ From 0d3fb43a156507f4458de7533f2448cebf4523cd Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 30 Apr 2020 12:15:21 -0700 Subject: [PATCH 474/844] Incremental HTTPClient_Send(): receive functionality only. (#898) * Implement HTTPClient_Send() receive functionality only. * Unit tests for receive functionality are in this commit. * Next PR is for parsing. --- libraries/standard/http/include/http_client.h | 66 ++- libraries/standard/http/src/http_client.c | 339 ++++++++++++-- .../standard/http/src/http_client_parse.c | 17 + .../http/src/private/http_client_parse.h | 72 +++ libraries/standard/http/test/Makefile | 26 +- libraries/standard/http/test/common.h | 1 + .../http/test/test-HTTPClient_AddHeader.c | 2 +- .../standard/http/test/test-HTTPClient_Send.c | 417 +++++++++++++++--- 8 files changed, 820 insertions(+), 120 deletions(-) create mode 100644 libraries/standard/http/src/http_client_parse.c create mode 100644 libraries/standard/http/src/private/http_client_parse.h diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 0a1247a10e..7c56e32a20 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -84,8 +84,8 @@ typedef struct HTTPNetworkContext HTTPNetworkContext_t; /** * @brief Transport interface for sending data over the network. * - * If the number of bytes written returned is less than bytesToWrite, then - * #HTTPClient_Send will return HTTP_NETWORK_ERROR. If a negative value is + * If the number of bytes written returned is not equal to @p bytesToWrite, then + * #HTTPClient_Send will return #HTTP_NETWORK_ERROR. If a negative value is * returned then this #HTTPClient_Send will also return #HTTP_NETWORK_ERROR. * * @param[in] context User defined context. @@ -101,11 +101,15 @@ typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, /** * @brief Transport interface for reading data on the network. * - * This function will read up to bytesToRead amount of data from the network. + * This function will read up to @p bytesToRead amount of data from the network. + * * If this function returns a value less than zero, then #HTTPClient_Send will - * return #HTTP_NETWORK_ERROR. If this function returns less than the - * bytesToRead and greater than zero, then this function will be invoked again - * if the data in pBuffer contains a partial HTTP response message. + * return #HTTP_NETWORK_ERROR. + * + * If this function returns less than the bytesToRead and greater than zero, + * then this function will be invoked again if the data in @p pBuffer contains a + * partial HTTP response message and there is room left in the @p pBuffer. + * Repeated invocations will stop if this function returns zero. * * @param[in] context User defined context. * @param[in] pBuffer Buffer to read network data into. @@ -122,9 +126,9 @@ typedef int32_t (* HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, */ typedef struct HTTPTransportInterface { - HTTPTransportRecv_t recv; - HTTPTransportSend_t send; - HTTPNetworkContext_t * pContext; + HTTPTransportRecv_t recv; /**< Transport receive interface */ + HTTPTransportSend_t send; /**< Transport interface send interface. */ + HTTPNetworkContext_t * pContext; /**< User defined transport interface context. */ } HTTPTransportInterface_t; /** @@ -142,7 +146,7 @@ typedef enum HTTPStatus * - #HTTPClient_Send * - #HTTPClient_ReadHeader */ - HTTP_SUCCESS = 0, + HTTP_SUCCESS, /** * @brief The HTTP Client library function input an invalid parameter. @@ -164,23 +168,44 @@ typedef enum HTTPStatus */ HTTP_NETWORK_ERROR, - HTTP_NOT_SUPPORTED, + /** + * @brief Part of the HTTP response was received from the network. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ HTTP_PARTIAL_RESPONSE, /** - * @brief The application buffer was not large enough for HTTP headers or body. + * @brief No HTTP response was received from the network. + * + * This can occur only if there was no data received from the transport + * interface. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_NO_RESPONSE, + + /** + * @brief The application buffer was not large enough for the HTTP request + * headers or the HTTP response message. * * Functions that may return this value: * - #HTTPClient_InitializeRequestHeaders * - #HTTPClient_AddHeader * - #HTTPClient_AddRangeHeader + * - #HTTPClient_Send */ HTTP_INSUFFICIENT_MEMORY, + HTTP_INTERNAL_ERROR, HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED, HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER, HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, /* TODO: Add return codes as implementation continues. */ + /* Temporary error code while implementation is in progress. */ + HTTP_NOT_SUPPORTED, } HTTPStatus_t; /** @@ -446,19 +471,30 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * parameter pRequestBodyBuf over the transport. The response is received in * #HTTPResponse_t. * + * The application should close the connection with the server if any + * HTTP_SECURITY_ALERT_X errors are returned. + * TODO: List all the security alerts possible after parsing development. + * * TODO: Expand documentation. * * @param[in] pTransport Transport interface, see #HTTPTransportInterface_t for * more information. * @param[in] pRequestHeaders Request configuration containing the buffer of * headers to send. - * @param[in] pRequestBodyBuf Request entity body. + * @param[in] pRequestBodyBuf Optional Request entity body. Set to NULL if there + * is no request body. * @param[in] reqBodyBufLen The length of the request entity in bytes. * @param[in] pResponse The response message and some notable response * parameters will be returned here on success. * - * @return #HTTP_SUCCESS if successful, an error code otherwise. - * TODO: Update for exact error codes returned. + * @return One of the following: + * - #HTTP_SUCCESS (If successful.) + * - #HTTP_INVALID_PARAMETER (If any provided parameters or their members are invalid.) + * - #HTTP_NETWORK_ERROR (Errors in sending or receiving over the transport interface.) + * - #HTTP_PARTIAL_RESPONSE (Part of an HTTP response was received in a partially filled response buffer.) + * - #HTTP_NO_RESPONSE (No data was received from the transport interface.) + * - #HTTP_INSUFFICIENT_MEMORY (The response received could not fit into the response buffer.) + * TODO: Add more errors for parsing implementation. */ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, const HTTPRequestHeaders_t * pRequestHeaders, diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 6ded72a5cd..4fe4b62d6a 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1,18 +1,19 @@ #include #include -#include #include #include "http_client.h" #include "private/http_client_internal.h" +#include "private/http_client_parse.h" /*-----------------------------------------------------------*/ /** * @brief Send the HTTP headers over the transport send interface. * - * @param pTransport Transport interface. - * @param pRequestHeaders Request headers to send, it includes the buffer and length. + * @param[in] pTransport Transport interface. + * @param[in] pRequestHeaders Request headers to send, it includes the buffer + * and length. * * @return #HTTP_SUCCESS if successful. If there was a network error or less * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. @@ -23,9 +24,9 @@ static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTranspor /** * @brief Send the HTTP body over the transport send interface. * - * @param pTransport Transport interface. - * @param pRequestBodyBuf Request body buffer. - * @param reqBodyLen Length of the request body buffer. + * @param[in] pTransport Transport interface. + * @param[in] pRequestBodyBuf Request body buffer. + * @param[in] reqBodyLen Length of the request body buffer. * * @return #HTTP_SUCCESS if successful. If there was a network error or less * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. @@ -39,11 +40,11 @@ static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, * If a trailing "\r\n" already exists in the HTTP header, this method backtracks * in order to write over it and updates the length accordingly. * - * @param pRequestHeaders Request header buffer information. - * @param pField The header field name to write. - * @param fieldLen The byte length of the header field name. - * @param pValue The header value to write. - * @param valueLen The byte length of the header field value. + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] pField The header field name to write. + * @param[in] fieldLen The byte length of the header field name. + * @param[in] pValue The header value to write. + * @param[in] valueLen The byte length of the header field value. * * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. @@ -54,6 +55,59 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, const char * pValue, size_t valueLen ); +/** + * @brief Receive HTTP response from the transport recv interface. + * + * @param[in] pTransport Transport interface. + * @param[in] pResponse Response buffer. + * @param[in] bufferLen Length of the response buffer. + * @param[out] Bytes received from the transport interface. + * + * @return Returns #HTTP_SUCCESS if successful. If there was a network error or + * more bytes than what was specified were read, then #HTTP_NETWORK_ERROR is + * returned. + */ +HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ); + +/** + * @brief Get the status of the HTTP response given the parsing state and how + * much data is in the response buffer. + * + * @param[in] parsingState State of the parsing on the HTTP response. + * @param[in] totalReceived The amount of network data received in the response + * buffer. + * @param[in] responseBufferLen The length of the response buffer. + * + * @return Returns #HTTP_SUCCESS if the parsing state is complete. If + * the parsing state denotes it never started, then return #HTTP_NO_RESPONSE. If + * the parsing state is incomplete, then if the response buffer is not full + * #HTTP_PARTIAL_RESPONSE is returned. If the parsing state is incomplete, then + * if the response buffer is full #HTTP_INSUFFICIENT_MEMORY is returned. + */ +static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, + size_t totalReceived, + size_t responseBufferLen ); + +/** + * @brief Receive the HTTP response from the network and parse it. + * + * @param[in] pTransport Transport interface. + * @param[in] pResponse Response message to receive data from the network. + * + * @return Returns #HTTP_SUCCESS if successful. If there was an issue with receiving + * the response over the network interface, then #HTTP_NETWORK_ERROR is returned, + * please see #_receiveHttpResponse. If there was an issue with parsing, then the + * parsing error that occurred will be returned, please see + * #_HTTPClient_InitializeParsingContext and #_HTTPClient_ParseResponse. Please + * see #_getFinalResponseStatus for the status returned when there were no + * network or parsing errors. + */ +static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, + HTTPResponse_t * pResponse ); + /*-----------------------------------------------------------*/ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, @@ -104,7 +158,7 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, if( ( bytesWritten + HTTP_HEADER_LINE_SEPARATOR_LEN ) != toAddLen ) { IotLogErrorWithArgs( "Internal error in snprintf() in _addHeader(). " - "Bytes written: %d.", bytesWritten ); + "BytesWritten: %d.", bytesWritten ); } else { @@ -210,6 +264,10 @@ static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTranspor HTTPStatus_t returnStatus = HTTP_SUCCESS; int32_t transportStatus = 0; + assert( pTransport != NULL ); + assert( pTransport->send != NULL ); + assert( pRequestHeaders != NULL ); + /* Send the HTTP headers over the network. */ transportStatus = pTransport->send( pTransport->pContext, pRequestHeaders->pBuffer, @@ -217,22 +275,25 @@ static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTranspor if( transportStatus < 0 ) { - IotLogErrorWithArgs( "Error in sending the HTTP headers over the transport " - "interface: Transport status %d.", + IotLogErrorWithArgs( "Failed to send HTTP headers: Transport send()" + " returned error: TransportStatus=%d", transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } - else if( transportStatus != pRequestHeaders->headersLen ) + else if( ( size_t ) transportStatus != pRequestHeaders->headersLen ) { - IotLogErrorWithArgs( "Failure in sending HTTP headers: Transport layer " - "did not send the required bytes: Required Bytes=%d" - ", Sent Bytes=%d.", + IotLogErrorWithArgs( "Failed to send HTTP headers: Transport layer " + "did not send the required bytes: RequiredBytes=%d" + ", SentBytes=%d.", + pRequestHeaders->headersLen, transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } else { - /* Empty else MISRA 15.7 */ + IotLogDebugWithArgs( "Sent HTTP headers over the transport: BytesSent " + "=%d.", + transportStatus ); } return returnStatus; @@ -247,40 +308,203 @@ static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, HTTPStatus_t returnStatus = HTTP_SUCCESS; int32_t transportStatus = 0; - /* Send the HTTP body over the network. */ - if( pRequestBodyBuf != NULL ) + assert( pTransport != NULL ); + assert( pTransport->send != NULL ); + assert( pRequestBodyBuf != NULL ); + + transportStatus = pTransport->send( pTransport->pContext, + pRequestBodyBuf, + reqBodyBufLen ); + + if( transportStatus < 0 ) { - transportStatus = pTransport->send( pTransport->pContext, - pRequestBodyBuf, - reqBodyBufLen ); + IotLogErrorWithArgs( "Failed to send HTTP body: Transport send() " + " returned error: TransportStatus=%d", + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + else if( ( size_t ) transportStatus != reqBodyBufLen ) + { + IotLogErrorWithArgs( "Failed to send HTTP body: Transport send() " + "did not send the required bytes: RequiredBytes=%d" + ", Sent bytes=%d.", + reqBodyBufLen, + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + else + { + IotLogDebugWithArgs( "Sent HTTP body over the transport: BytesSent=%d.", + transportStatus ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + assert( pTransport != NULL ); + assert( pTransport->recv != NULL ); + assert( pBuffer != NULL ); + assert( pBytesReceived != NULL ); - if( transportStatus < 0 ) + int32_t transportStatus = pTransport->recv( pTransport->pContext, + pBuffer, + bufferLen ); + + /* A transport status of less than zero is an error. */ + if( transportStatus < 0 ) + { + IotLogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " + "returned error: TransportStatus=%d.", + transportStatus ); + returnStatus = HTTP_NETWORK_ERROR; + } + else if( ( size_t ) transportStatus > bufferLen ) + { + /* There is a bug in the transport recv if more bytes are reported + * to have been read than the bytes asked for. */ + IotLogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " + " read more bytes than requested: BytesRead=%d, " + "RequestedBytes=%d", + transportStatus, + bufferLen ); + returnStatus = HTTP_NETWORK_ERROR; + } + else if( transportStatus > 0 ) + { + /* Some or all of the specified data was received. */ + *pBytesReceived = ( size_t ) ( transportStatus ); + IotLogDebugWithArgs( "Received data from the transport: BytesReceived=%d.", + transportStatus ); + } + else + { + /* When a zero is returned from the transport recv it will not be + * invoked again. */ + IotLogDebug( "Received zero bytes from trasnport recv(). Receiving " + "transport data is complete." ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, + size_t totalReceived, + size_t responseBufferLen ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + assert( parsingState >= HTTP_PARSING_NONE && + parsingState <= HTTP_PARSING_COMPLETE ); + assert( totalReceived <= responseBufferLen ); + + /* If no parsing occurred, that means network data was never received. */ + if( parsingState == HTTP_PARSING_NONE ) + { + IotLogErrorWithArgs( "Response not received: Zero returned from " + "transport recv: totalReceived=%d", + totalReceived ); + returnStatus = HTTP_NO_RESPONSE; + } + else if( parsingState == HTTP_PARSING_INCOMPLETE ) + { + if( totalReceived == responseBufferLen ) { - IotLogErrorWithArgs( "Error in sending the HTTP body over the " - "transport interface. Transport status %d.", - transportStatus ); - returnStatus = HTTP_NETWORK_ERROR; + IotLogErrorWithArgs( "Cannot receive complete response from tansport" + " interface: Response buffer has insufficient " + "space: responseBufferLen=%d", + responseBufferLen ); + returnStatus = HTTP_INSUFFICIENT_MEMORY; } - else if( transportStatus != reqBodyBufLen ) + else { - IotLogErrorWithArgs( "Failure in sending HTTP headers: Transport layer " - "did not send the required bytes: Required Bytes=%d" - ", Sent Bytes=%d.", - transportStatus ); - returnStatus = HTTP_NETWORK_ERROR; + IotLogErrorWithArgs( "Received partial response from transport ", + "recv(): ResponseSize=%d, TotalBufferSize=%d", + totalReceived, + responseBufferLen - totalReceived ); + returnStatus = HTTP_PARTIAL_RESPONSE; } } + else + { + /* Empty else for MISRA 15.7 compliance. */ + } return returnStatus; } -/*-----------------------------------------------------------*/ - -static HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, - HTTPResponse_t * pResponse ) +static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, + HTTPResponse_t * pResponse ) { - /* TODO: Receive the HTTP response with parsing. */ - return HTTP_SUCCESS; + HTTPStatus_t returnStatus = HTTP_SUCCESS; + size_t totalReceived = 0; + size_t currentReceived = 0; + HTTPParsingContext_t parsingContext = { 0 }; + uint8_t shouldRecv = 0; + + assert( pTransport != NULL ); + assert( pTransport->recv != NULL ); + assert( pResponse != NULL ); + + /* Initialize the parsing context. */ + returnStatus = _HTTPClient_InitializeParsingContext( &parsingContext, + pResponse->pHeaderParsingCallback ); + + if( returnStatus == HTTP_SUCCESS ) + { + shouldRecv = 1; + } + + while( shouldRecv == 1 ) + { + /* Receive the HTTP response data into the pResponse->pBuffer. */ + returnStatus = _receiveHttpResponse( pTransport, + pResponse->pBuffer + totalReceived, + pResponse->bufferLen - totalReceived, + ¤tReceived ); + + if( returnStatus == HTTP_SUCCESS ) + { + if( currentReceived > 0 ) + { + totalReceived += currentReceived; + /* Data is received into the buffer and must be parsed. */ + returnStatus = _HTTPClient_ParseResponse( &parsingContext, + pResponse->pBuffer + totalReceived, + currentReceived ); + } + } + + /* While there are no errors in the transport recv or parsing, we received + * data over the transport, the response message is not finished, and + * there is room in the response buffer. */ + shouldRecv = ( uint8_t ) ( ( returnStatus == HTTP_SUCCESS ) && + ( currentReceived > 0 ) && + ( parsingContext.state != HTTP_PARSING_COMPLETE ) && + ( totalReceived < pResponse->bufferLen ) ); + } + + if( returnStatus == HTTP_SUCCESS ) + { + /* For no network or parsing errors, the final status of the response + * message is derived from the state of the parsing and how much data + * is in the buffer. */ + returnStatus = _getFinalResponseStatus( parsingContext.state, + totalReceived, + pResponse->bufferLen ); + } + + return returnStatus; } /*-----------------------------------------------------------*/ @@ -318,9 +542,14 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( ( pResponse != NULL ) && ( pResponse->pBuffer == NULL ) ) + { + IotLogError( "Parameter check failed: pResponse->pBuffer is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } else { - /* Empty else MISRA 15.7 */ + /* Empty else for MISRA 15.7 compliance. */ } /* Send the headers, which are at one location in memory. */ @@ -333,15 +562,31 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /* Send the body, which is at another location in memory. */ if( returnStatus == HTTP_SUCCESS ) { - returnStatus = _sendHttpBody( pTransport, - pRequestBodyBuf, - reqBodyBufLen ); + if( pRequestBodyBuf != NULL ) + { + returnStatus = _sendHttpBody( pTransport, + pRequestBodyBuf, + reqBodyBufLen ); + } + else + { + IotLogDebug( "A request body was not sent: pRequestBodyBuf is NULL." ); + } } if( returnStatus == HTTP_SUCCESS ) { - returnStatus = _receiveHttpResponse( pTransport, - pResponse ); + /* If the application chooses to receive a response, then pResponse + * will not be NULL. */ + if( pResponse != NULL ) + { + returnStatus = _receiveAndParseHttpResponse( pTransport, + pResponse ); + } + else + { + IotLogDebug( "Response ignored: pResponse is NULL. " ); + } } return returnStatus; diff --git a/libraries/standard/http/src/http_client_parse.c b/libraries/standard/http/src/http_client_parse.c new file mode 100644 index 0000000000..56dcb7edc9 --- /dev/null +++ b/libraries/standard/http/src/http_client_parse.c @@ -0,0 +1,17 @@ +#include "private/http_client_parse.h" + +HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) +{ + /* This function is to be implenmented. */ + return HTTP_SUCCESS; +} + +HTTPStatus_t _HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen ) +{ + /* This function is to be implemented. For now we return success. */ + pParsingContext->state = HTTP_PARSING_COMPLETE; + return HTTP_SUCCESS; +} diff --git a/libraries/standard/http/src/private/http_client_parse.h b/libraries/standard/http/src/private/http_client_parse.h new file mode 100644 index 0000000000..8af0923b70 --- /dev/null +++ b/libraries/standard/http/src/private/http_client_parse.h @@ -0,0 +1,72 @@ +#ifndef HTTP_CLIENT_PARSE_H_ +#define HTTP_CLIENT_PARSE_H_ + +#include "http_client.h" +/* #include "http_parser.h" */ + +/** + * @brief The state of the response message parsed after + * #HTTPClient_ParseResponse returns. + */ +typedef enum HTTPParsingState_t +{ + HTTP_PARSING_NONE = 0, /**< The parser has not started reading any response. */ + HTTP_PARSING_INCOMPLETE, /**< The parser found a partial reponse. */ + HTTP_PARSING_COMPLETE /**< The parser found the entire response. */ +} HTTPParsingState_t; + +/** + * @brief The HTTP response parsing context. + */ +typedef struct HTTPParsingContext +{ + /* http-parser dependencies will be added in the next incremental change. */ + /* Below will be un-commented when parsing is implemented. */ + #if 0 + http_parser httpParser; /**< Third-party http-parser context. */ + #endif + HTTPParsingState_t state; /**< The current state of the HTTP response parsed. */ + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback; /**< Callback to invoke with each header found. */ +} HTTPParsingContext_t; + +/** + * @brief Initialize the HTTP Client response parsing context. + * + * @param[in,out] pParsingContext The parsing context to initialize. + * @param[in] pHeaderParsingCallback Callback that will be invoked for each + * header and value pair found. + * + * @return One of the following: + * - #HTTP_SUCCESS + * - #HTTP_INVALID_PARAMETER + * TODO: Other return values will be added during implementation of the parsing. + */ +HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ); + +/** + * Parse the input HTTP response buffer. + * + * This function may be invoked multiple times for different parts of the the + * HTTP response. The state of what was last parsed in the response is kept in + * #HTTPParsingContext_t. + * + * The application should close the connection with the server if any + * HTTP_SECURITY_ALERT_X errors are returned. + * TODO: List all the security alerts possible after parsing development. + * + * @param[in,out] pParsingState The state of the response parsing. + * @param[in] pBuffer The buffer containing response message to parse. + * @param[in] bufferLen The length in the buffer to parse. + * + * @return One of the following: + * - #HTTP_SUCCESS + * - #HTTP_INVALID_PARAMETER + * - #HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER + * TODO: Other return values are to be added during implementation of parsing. + */ +HTTPStatus_t _HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen ); + +#endif /* ifndef HTTP_CLIENT_PARSE_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 9ce7b089d8..741bce2dc1 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -4,11 +4,18 @@ CSDK_ROOT = ../../../.. # Directory indexing includes subdirectories. SOURCE = ../src +# Show extra warnings. +CFLAGS += -Wextra + # arguments given to the compiler (default: see the included file) #CFLAGS = std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb # include directories given to the compiler (default: none) -INCLUDE = . ../include $(CSDK_ROOT)/platform/include ../../utilities/include ../src +INCLUDE = . \ + ../include \ + ../src \ + $(CSDK_ROOT)/platform/include \ + ../../utilities/include # the default goal (default: help) # Set to build or test as desired. @@ -20,7 +27,14 @@ INCLUDE = . ../include $(CSDK_ROOT)/platform/include ../../utilities/include ../ # functions to dump into separate source files # Values based on test source file names will be dynamically added. -FUNCTIONS = _sendHttpHeaders _sendHttpBody _receiveHttpResponse _addHeader +FUNCTIONS = _addHeader \ + _sendHttpHeaders \ + _sendHttpBody \ + _receiveHttpResponse \ + _receiveAndParseHttpResponse \ + _HTTPClient_InitializeParsingContext \ + _HTTPClient_ParseResponse \ + _getFinalResponseStatus # Changes to the above variables should remain above this include. include Mock4thewin.mk @@ -29,8 +43,14 @@ include Mock4thewin.mk $(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/posix/iot_logging.o # additional dependency for a specific test -HTTPClient_Send.c: _sendHttpHeaders.c _sendHttpBody.c _receiveHttpResponse.c HTTPClient_AddHeader.c: _addHeader.c +HTTPClient_Send.c: _sendHttpHeaders.c \ + _sendHttpBody.c \ + _receiveHttpResponse.c \ + _receiveAndParseHttpResponse.c \ + _HTTPClient_InitializeParsingContext.c \ + _HTTPClient_ParseResponse.c \ + _getFinalResponseStatus.c \ # additional header dependencies for all tests COMMON = common.h diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h index 8e2d51d490..5ccc0bf992 100644 --- a/libraries/standard/http/test/common.h +++ b/libraries/standard/http/test/common.h @@ -5,5 +5,6 @@ /* Private includes for internal macros. */ #include "private/http_client_internal.h" +#include "private/http_client_parse.h" #define assert( x ) diff --git a/libraries/standard/http/test/test-HTTPClient_AddHeader.c b/libraries/standard/http/test/test-HTTPClient_AddHeader.c index c1a5662e88..203ac71f72 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddHeader.c @@ -151,7 +151,7 @@ int main() HTTP_TEST_HEADER_REQUEST_LINE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == expectedHeaderLen ); + == ( int )expectedHeaderLen ); /* Prefill the buffer with a request line and header. */ ok( snprintf( ( char * ) reqHeaders.pBuffer, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, diff --git a/libraries/standard/http/test/test-HTTPClient_Send.c b/libraries/standard/http/test/test-HTTPClient_Send.c index 1111309f50..3f20d58ecf 100644 --- a/libraries/standard/http/test/test-HTTPClient_Send.c +++ b/libraries/standard/http/test/test-HTTPClient_Send.c @@ -2,13 +2,23 @@ #include #include "common.h" +/* THESE TESTS WILL MOVE TO THE UNITY FRAMEWORK. + * ok()'s will be replaced with proper unity macros. */ + /* Functions are pulled out into their own C files to be tested as a unit. */ #include "_sendHttpHeaders.c" #include "_sendHttpBody.c" #include "_receiveHttpResponse.c" +#include "_HTTPClient_InitializeParsingContext.c" +#include "_HTTPClient_ParseResponse.c" +#include "_getFinalResponseStatus.c" +#include "_receiveAndParseHttpResponse.c" #include "HTTPClient_Send.c" +/* HTTP OK Status-Line. */ +#define HTTP_STATUS_LINE_OK "HTTP/1.1 200 OK\r\n" +#define HTTP_STATUS_CODE_OK 200 /* Template HTTP successful response with no body. */ #define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY \ @@ -19,10 +29,12 @@ "Vary: *\r\n" \ "P3P: CP=\"This is not a P3P policy\"\r\n" \ "xserver: www1021\r\n\r\n" -#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1 +#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1U +#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT 6 + -/* Template HTTP request for a head request. */ -#define HTTP_TEST_REQUEST_HEAD \ +/* Template HTTP request for a HEAD request. */ +#define HTTP_TEST_REQUEST_HEAD_HEADERS \ "HEAD /somedir/somepage.html HTTP/1.1\r\n" \ "test-header0: test-value0\r\n" \ "test-header1: test-value1\r\n" \ @@ -31,10 +43,27 @@ "test-header4: test-value1\r\n" \ "test-header5: test-value2\r\n" \ "\r\n" -#define HTTP_TEST_REQUEST_HEAD_LENGTH sizeof( HTTP_TEST_REQUEST_HEAD ) - 1 +#define HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH sizeof( HTTP_TEST_REQUEST_HEAD_HEADERS ) - 1 + +/* Template HTTP request for a PUT request. */ +#define HTTP_TEST_REQUEST_PUT_HEADERS \ + "PUT /somedir/somepage.html HTTP/1.1\r\n" \ + "Content-Length: 26\r\n" \ + "test-header1: test-value1\r\n" \ + "test-header2: test-value2\r\n" \ + "test-header3: test-value0\r\n" \ + "test-header4: test-value1\r\n" \ + "test-header5: test-value2\r\n" \ + "\r\n" +#define HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH sizeof( HTTP_TEST_REQUEST_PUT_HEADERS ) - 1 +#define HTTP_TEST_REQUEST_PUT_BODY "abcdefghijklmnopqrstuvwxyz" +#define HTTP_TEST_REQUEST_PUT_BODY_LENGTH sizeof( HTTP_TEST_REQUEST_PUT_BODY ) - 1 /* Test buffer to share among the test. */ -static uint8_t httpBuffer[ 1024 ] = { 0 }; +#define HTTP_TEST_BUFFER_LENGTH 1024 +static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LENGTH ] = { 0 }; + +/* -----------------------------------------------------------------------*/ /* Mocked successful transport send. */ static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, @@ -46,13 +75,65 @@ static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, return bytesToWrite; } +/* -----------------------------------------------------------------------*/ + +/* This section contains all the support needed to mock the two different + * transport interface send cases. The three transport send error cases are: + * 1. A negative value returned. + * 2. Less than bytesToWrite returned. */ + +/* Transport send is called separately for the headers and the body, this counts + * the number of calls so far. */ +uint8_t sendCurrentCall; +uint8_t sendErrorCall; +static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + int32_t retVal = bytesToWrite; + + if( sendErrorCall == sendCurrentCall ) + { + retVal = -1; + } + + sendCurrentCall++; + return retVal; +} + +static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + int32_t retVal = bytesToWrite; + + if( sendErrorCall == sendCurrentCall ) + { + retVal -= 1; + } + + sendCurrentCall++; + return retVal; +} + +#define transportSendErrorReset() \ + do { \ + sendCurrentCall = 0; \ + sendErrorCall = 0; \ + } while( 0 ); + +/* -----------------------------------------------------------------------*/ + /* Mocked successful transport read. */ static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, void * pBuffer, size_t bytesToRead ) { ( void ) pContext; - ( void ) pBuffer; memcpy( pBuffer, HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY, @@ -61,85 +142,240 @@ static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, return sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1; } -/* Mocked network error returning transport send. */ -static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, - const void * pBuffer, - size_t bytesToWrite ) +/* -----------------------------------------------------------------------*/ + +/* This section contains all the support needed to mock an incremental transport + * read. The mocked transport receive will read 128 bytes at time from the + * response. */ + +/* Default increment read bytes transportRecvIncremental. */ +#define INCREMENT_BYTES_DEFAULT 0x80U + +uint32_t bytesRead; /* Total bytes read over multple calls. */ +uint8_t * pNetworkData; /* The network data to read. */ +size_t networkDataLen; /* The network data to read's length. */ +size_t incrementReadBytes; /* How many bytes of network data to read each call. */ +size_t stopReadBytes; /* Stop reading at >= this many bytes. */ +static int32_t transportRecvIncremental( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + + if( bytesRead >= stopReadBytes ) + { + return 0; + } + + uint8_t * pCopyLoc = pNetworkData + bytesRead; + size_t messageLenLeft = networkDataLen - bytesRead; + size_t copyLen = incrementReadBytes; + + /* If the amount requested to read is smaller than the increment, then + * copy that amount. */ + if( bytesToRead < copyLen ) + { + copyLen = bytesToRead; + } + + /* If there is less message left than the amount to read, then copy the + * rest. */ + if( messageLenLeft < copyLen ) + { + copyLen = messageLenLeft; + } + + memcpy( pBuffer, pCopyLoc, copyLen ); + bytesRead += copyLen; + return copyLen; +} +#define transportRecvIncrementalReset() \ + do { \ + bytesRead = 0; \ + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY; \ + networkDataLen = HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH; \ + incrementReadBytes = INCREMENT_BYTES_DEFAULT; \ + stopReadBytes = 0; \ + } while( 0 ); + + +/* -----------------------------------------------------------------------*/ + +/* Mocked network error returning transport recv. */ +static int32_t transportRecvNetworkError( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) { ( void ) pContext; ( void ) pBuffer; - ( void ) bytesToWrite; + ( void ) bytesToRead; return -1; } -/* Mocked transport send that returns less bytes than expected. */ -static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, - const void * pBuffer, - size_t bytesToWrite ) +/* -----------------------------------------------------------------------*/ + +/* Mocked transport recv function that returns more bytes than expected. */ +static int32_t transportRecvMoreThanBytesToRead( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) { ( void ) pContext; ( void ) pBuffer; - ( void ) bytesToWrite; - return bytesToWrite - 1; + return( bytesToRead + 1 ); } +/* -----------------------------------------------------------------------*/ int main() { HTTPStatus_t returnStatus = HTTP_SUCCESS; HTTPResponse_t response = { 0 }; + HTTPTransportInterface_t transportInterface = { 0 }; + HTTPRequestHeaders_t requestHeaders = { 0 }; + +/* Resets each test back to the original happy path state. */ +#define reset() \ + do { \ + transportInterface.recv = transportRecvSuccess; \ + transportInterface.send = transportSendSuccess; \ + transportInterface.pContext = NULL; \ + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_HEAD_HEADERS ); \ + requestHeaders.bufferLen = HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH; \ + requestHeaders.headersLen = HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH; \ + memset( &response, 0, sizeof( HTTPResponse_t ) ); \ + response.pBuffer = httpBuffer; \ + response.bufferLen = sizeof( httpBuffer ); \ + transportRecvIncrementalReset(); \ + transportSendErrorReset(); \ + } while( 0 ); + reset(); - response.pBuffer = httpBuffer; - response.bufferLen = sizeof( httpBuffer ); + /* -----------------------------------------------------------------------*/ - HTTPTransportInterface_t transportInterface = - { - .recv = transportRecvSuccess, - .send = transportSendSuccess, - .pContext = NULL - }; + /* Happy Path testing. Test sending a request and successfully receiving a + * response that is within the bounds of the response buffer. */ + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response ); - HTTPRequestHeaders_t requestHeadersHead = - { - .pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_HEAD ), - .bufferLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1, - .headersLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1 - }; + ok( returnStatus == HTTP_SUCCESS ); + /* Uncomment after parsing is implemented. */ + /* ok( response.body == NULL ); */ + /* ok( response.bodyLen == 0 ); */ + /* ok( response.headers == response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ) ); */ + /* ok( response.headersLen == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH ); */ + /* ok( response.statusCode == HTTP_STATUS_CODE_OK ); */ + /* ok( response.contentLength == 0 ); */ + /* ok( response.headerCount == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT ); */ + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Happy path testing. Test sending a non-null body buffer and received a full + * response. */ + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response ); -#define reset() \ - do { \ - transportInterface.recv = transportRecvSuccess; \ - transportInterface.send = transportSendSuccess; \ - transportInterface.pContext = NULL; \ - requestHeadersHead.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_HEAD ); \ - requestHeadersHead.bufferLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1; \ - requestHeadersHead.headersLen = sizeof( HTTP_TEST_REQUEST_HEAD ) - 1; \ - } while( 0 ) + ok( returnStatus == HTTP_SUCCESS ); + /* Uncomment after parsing is implemented. */ + /* ok( response.body == NULL ); */ + /* ok( response.bodyLen == 0 ); */ + /* ok( response.headers == response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ) ); */ + /* ok( response.headersLen == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH ); */ + /* ok( response.statusCode == HTTP_STATUS_CODE_OK ); */ + /* ok( response.contentLength == 0 ); */ + /* ok( response.headerCount == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT ); */ reset(); /* -----------------------------------------------------------------------*/ - /* Happy Path testing. Test sending a request and successfully receiving a - * response that is within the bounds of the response buffer. */ + /* Test receiving the response message with multiple calls to transport + * recv. */ + transportInterface.recv = transportRecvIncremental; + stopReadBytes = networkDataLen; /* Read the whole network data. */ returnStatus = HTTPClient_Send( &transportInterface, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); ok( returnStatus == HTTP_SUCCESS ); - /* TODO: Check the response is parsed successfully. */ + /* Uncomment after parsing is implemented. */ + /* ok( response.body == NULL ); */ + /* ok( response.bodyLen == 0 ); */ + /* ok( response.headers == response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ) ); */ + /* ok( response.headersLen == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH ); */ + /* ok( response.statusCode == HTTP_STATUS_CODE_OK ); */ + /* ok( response.contentLength == 0 ); */ + /* ok( response.headerCount == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT ); */ + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test receiving with a NULL response. */ + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + NULL ); + + ok( returnStatus == HTTP_SUCCESS ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test a network error returned from a transport send of the request + * headers. */ + sendErrorCall = 0; + transportInterface.send = transportSendNetworkError; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_NETWORK_ERROR ); reset(); /* -----------------------------------------------------------------------*/ - /* Test an error returned from a unsuccessful transport send. */ + /* Test a network error returned from a transport send of the request + * body. */ transportInterface.send = transportSendNetworkError; + sendErrorCall = 1; + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response ); + ok( returnStatus == HTTP_NETWORK_ERROR ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test less bytes than expected returned from a transport send of the + * request headers. */ + transportInterface.send = transportSendLessThanBytesToWrite; + sendErrorCall = 0U; returnStatus = HTTPClient_Send( &transportInterface, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); @@ -149,23 +385,81 @@ int main() /* -----------------------------------------------------------------------*/ - /* Test less bytes than expected returned from transport send. */ + /* Test less bytes than expected returned from a transport send of the + * request body. */ transportInterface.send = transportSendLessThanBytesToWrite; + sendErrorCall = 1; + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response ); + + ok( returnStatus == HTTP_NETWORK_ERROR ); + reset(); + + /* -----------------------------------------------------------------------*/ + /* Test a network error returned from a transport recv of the response. */ + transportInterface.recv = transportRecvNetworkError; returnStatus = HTTPClient_Send( &transportInterface, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); + ok( returnStatus = HTTP_NETWORK_ERROR ); + reset(); - ok( returnStatus == HTTP_NETWORK_ERROR ); + /* -----------------------------------------------------------------------*/ + + /* Test more bytes than expected returned from a transport recv of the + * response. */ + transportInterface.recv = transportRecvMoreThanBytesToRead; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response ); + ok( returnStatus = HTTP_NETWORK_ERROR ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test transport recv returning zero when first invoked. */ + transportInterface.recv = transportRecvIncremental; + stopReadBytes = 0; /* Stop immediately. */ + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response ); + ok( returnStatus = HTTP_NO_RESPONSE ); reset(); /* -----------------------------------------------------------------------*/ + /* Test transport recv returning zero in the middle of a response message. */ + /* This test will be invoked upon parsing implementation completion. */ + #if 0 + transportInterface.recv = transportRecvMoreThanBytesToRead; + stopReadBytes = incrementReadBytes; /* Stop after the first increment. */ + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response ); + ok( returnStatus = HTTP_PARTIAL_RESPONSE ); + reset(); + #endif + + /* -----------------------------------------------------------------------*/ + /* Test a NULL transport interface. */ returnStatus = HTTPClient_Send( NULL, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); @@ -178,7 +472,7 @@ int main() /* Test a NULL transport interface send. */ transportInterface.send = NULL; returnStatus = HTTPClient_Send( &transportInterface, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); @@ -191,7 +485,7 @@ int main() /* Test a NULL transport interface recv. */ transportInterface.recv = NULL; returnStatus = HTTPClient_Send( &transportInterface, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); @@ -214,9 +508,9 @@ int main() /* -----------------------------------------------------------------------*/ /* Test NULL request header buffer. */ - requestHeadersHead.pBuffer = NULL; + requestHeaders.pBuffer = NULL; returnStatus = HTTPClient_Send( &transportInterface, - &requestHeadersHead, + &requestHeaders, NULL, 0, &response ); @@ -224,5 +518,20 @@ int main() ok( returnStatus == HTTP_INVALID_PARAMETER ); reset(); + /* -----------------------------------------------------------------------*/ + + /* Test a NULL response buffer. */ + response.pBuffer = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response ); + + ok( returnStatus == HTTP_INVALID_PARAMETER ); + reset(); + + /* -----------------------------------------------------------------------*/ + return grade(); } From ea3deef6fbca46815fba4202626f9f31757fb9f9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 30 Apr 2020 12:19:36 -0700 Subject: [PATCH 475/844] Add basic MQTT TLS demo (#903) --- demos/Makefile | 16 - demos/mqtt/mqtt_demo_basic_tls/Makefile | 16 + demos/mqtt/mqtt_demo_basic_tls/config.h | 48 ++ .../mqtt_demo_basic_tls/mosquitto.org.crt | 18 + .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 468 ++++++++++++++++++ demos/mqtt/mqtt_demo_plaintext/Makefile | 2 +- demos/mqtt/mqtt_demo_plaintext/config.h | 7 + .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 23 +- libraries/standard/mqtt/src/mqtt.c | 2 +- 9 files changed, 572 insertions(+), 28 deletions(-) delete mode 100644 demos/Makefile create mode 100644 demos/mqtt/mqtt_demo_basic_tls/Makefile create mode 100644 demos/mqtt/mqtt_demo_basic_tls/config.h create mode 100644 demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt create mode 100644 demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c diff --git a/demos/Makefile b/demos/Makefile deleted file mode 100644 index 514d40f855..0000000000 --- a/demos/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -CC = gcc - -INCLUDE_DIRS = -I include \ - -I ../libraries/standard/mqtt/include \ - -I ../platform/include - -SRC_FILES = $(shell find src -name '*.c') \ - $(shell find ../libraries/standard/mqtt/src -name '*.c') - -FLAGS = -g -O0 -Wall -Wextra -Wpedantic - -all: - $(CC) $(FLAGS) $(INCLUDE_DIRS) $(SRC_FILES) -o mqtt_demo - -clean: - rm mqtt_demo diff --git a/demos/mqtt/mqtt_demo_basic_tls/Makefile b/demos/mqtt/mqtt_demo_basic_tls/Makefile new file mode 100644 index 0000000000..76c820089a --- /dev/null +++ b/demos/mqtt/mqtt_demo_basic_tls/Makefile @@ -0,0 +1,16 @@ +CC = gcc + +INCLUDE_DIRS = -I . \ + -I ../../../libraries/standard/mqtt/include + +SRC_FILES = mqtt_demo_basic_tls.c \ + $(shell find ../../../libraries/standard/mqtt/src -name '*.c') + +FLAGS = -g -O0 -Wall -Wextra -Wpedantic +LINK_FLAGS = -lssl -lcrypto + +all: + $(CC) $(FLAGS) $(INCLUDE_DIRS) $(SRC_FILES) -o mqtt_demo_basic_tls $(LINK_FLAGS) + +clean: + rm mqtt_demo_basic_tls diff --git a/demos/mqtt/mqtt_demo_basic_tls/config.h b/demos/mqtt/mqtt_demo_basic_tls/config.h new file mode 100644 index 0000000000..1f032801fa --- /dev/null +++ b/demos/mqtt/mqtt_demo_basic_tls/config.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef CONFIG_H +#define CONFIG_H + +/* Set network context to OpenSSL SSL context. */ +#include +typedef SSL * MQTTNetworkContext_t; + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient" + +#endif /* ifndef CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt b/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt new file mode 100644 index 0000000000..b8535e8872 --- /dev/null +++ b/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC8DCCAlmgAwIBAgIJAOD63PlXjJi8MA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD +VQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURlcmJ5 +MRIwEAYDVQQKDAlNb3NxdWl0dG8xCzAJBgNVBAsMAkNBMRYwFAYDVQQDDA1tb3Nx +dWl0dG8ub3JnMR8wHQYJKoZIhvcNAQkBFhByb2dlckBhdGNob28ub3JnMB4XDTEy +MDYyOTIyMTE1OVoXDTIyMDYyNzIyMTE1OVowgZAxCzAJBgNVBAYTAkdCMRcwFQYD +VQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwGA1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1v +c3F1aXR0bzELMAkGA1UECwwCQ0ExFjAUBgNVBAMMDW1vc3F1aXR0by5vcmcxHzAd +BgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hvby5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAMYkLmX7SqOT/jJCZoQ1NWdCrr/pq47m3xxyXcI+FLEmwbE3R9vM +rE6sRbP2S89pfrCt7iuITXPKycpUcIU0mtcT1OqxGBV2lb6RaOT2gC5pxyGaFJ+h +A+GIbdYKO3JprPxSBoRponZJvDGEZuM3N7p3S/lRoi7G5wG5mvUmaE5RAgMBAAGj +UDBOMB0GA1UdDgQWBBTad2QneVztIPQzRRGj6ZHKqJTv5jAfBgNVHSMEGDAWgBTa +d2QneVztIPQzRRGj6ZHKqJTv5jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA +A4GBAAqw1rK4NlRUCUBLhEFUQasjP7xfFqlVbE2cRy0Rs4o3KS0JwzQVBwG85xge +REyPOFdGdhBY2P1FNRy0MDr6xr+D2ZOwxs63dG1nnAnWZg7qwoLgpZ4fESPD3PkA +1ZgKJc2zbSQ9fCPxt2W3mdVav66c6fsb7els2W2Iz7gERJSX +-----END CERTIFICATE----- diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c new file mode 100644 index 0000000000..07e755450e --- /dev/null +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include + +/* POSIX socket includes. */ +#include +#include + +#include +#include + +/* MQTT API header. */ +#include "mqtt.h" + +/** + * @brief MQTT server host name. + * + * This demo uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + */ +#define SERVER "test.mosquitto.org" + +/** + * @brief MQTT server port number. + * + * In general, port 8883 is for secured MQTT connections. + */ +#define PORT 8883 + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate should be PEM-encoded. + */ +#define SERVER_CERT "mosquitto.org.crt" + +/** + * @brief Size of the network buffer for MQTT packets. + */ +#define NETWORK_BUFFER_SIZE ( 1024U ) + +/* Check that client identifier is defined. */ +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif + +/** + * @brief Length of client identifier. + */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a TCP connection to the given server. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * + * @return A file descriptor representing the TCP socket; -1 on failure. + */ +static int connectToServer( const char * pServer, uint16_t port ) +{ + int status, tcpSocket = -1; + struct addrinfo * pListHead = NULL, * pIndex; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + + /* Perform a DNS lookup on the given host name. */ + status = getaddrinfo( pServer, NULL, NULL, &pListHead ); + + if( status != -1 ) + { + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + tcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( tcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + } + + status = connect( tcpSocket, pServerInfo, serverInfoLength ); + + if( status == -1 ) + { + close( tcpSocket ); + } + else + { + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + status = -1; + } + else + { + status = tcpSocket; + } + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Set up a TLS connection over an existing TCP connection. + * + * @param[in] tcpSocket Existing TCP connection. + * + * @return An SSL connection context; NULL on failure. + */ +static SSL * tlsSetup( int tcpSocket ) +{ + int sslStatus = 0; + FILE * pRootCaFile = NULL; + X509 * pRootCa = NULL; + SSL * pSslContext = NULL; + + /* Setup for creating a TLS client. */ + SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); + + if( pSslSetup != NULL ) + { + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. + * The mask returned by SSL_CTX_set_mode does not need to be checked. */ + ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); + + /* OpenSSL does not provide a single function for reading and loading certificates + * from files into stores, so the file API must be called. */ + pRootCaFile = fopen( SERVER_CERT, "r" ); + + if( pRootCaFile != NULL ) + { + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + } + + if( pRootCa != NULL ) + { + sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), + pRootCa ); + } + } + + /* Set up the TLS connection. */ + if( sslStatus == 1 ) + { + /* Create a new SSL context. */ + pSslContext = SSL_new( pSslSetup ); + + if( pSslContext != NULL ) + { + /* Enable SSL peer verification. */ + SSL_set_verify( pSslContext, SSL_VERIFY_PEER, NULL ); + + sslStatus = SSL_set_fd( pSslContext, tcpSocket ); + } + else + { + sslStatus = 0; + } + + /* Perform the TLS handshake. */ + if( sslStatus == 1 ) + { + sslStatus = SSL_connect( pSslContext ); + } + + if( sslStatus == 1 ) + { + if( SSL_get_verify_result( pSslContext ) != X509_V_OK ) + { + sslStatus = 0; + } + } + + /* Clean up on error. */ + if( sslStatus == 0 ) + { + SSL_free( pSslContext ); + pSslContext = NULL; + } + } + + if( pRootCaFile != NULL ) + { + ( void ) fclose( pRootCaFile ); + } + + if( pRootCa != NULL ) + { + X509_free( pRootCa ); + } + + if( pSslSetup != NULL ) + { + SSL_CTX_free( pSslSetup ); + } + + return pSslContext; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The transport send function provided to the MQTT context. + * + * @param[in] pSslContext SSL context. + * @param[in] pMessage Data to send. + * @param[in] bytesToSend Length of data to send. + * + * @return Number of bytes sent; negative value on error. + */ +static int32_t transportSend( MQTTNetworkContext_t pSslContext, const void * pMessage, size_t bytesToSend ) +{ + return ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The transport receive function provided to the MQTT context. + * + * @param[in] pSslContext SSL context. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToSend Size of pBuffer. + * + * @return Number of bytes received; negative value on error. + */ +static int32_t transportRecv( MQTTNetworkContext_t pSslContext, void * pBuffer, size_t bytesToRecv ) +{ + return ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The timer query function provided to the MQTT context. + * + * Currently not implemented. + */ +static uint32_t getTime( void ) +{ + return 0; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief MQTT event callback. + * + * Currently not implemented. + */ +static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ) +{ + +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish an MQTT session over a TCP+TLS connection by sending MQTT CONNECT. + * + * @param[in] pContext MQTT context. + * @param[in] pSslContext SSL context. + * + * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. + */ +static int establishMqttSession( MQTTContext_t * pContext, SSL * pSslContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTConnectInfo_t connectInfo; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + /* The network buffer must remain valid for the lifetime of the MQTT context. */ + static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + + /* Initialize MQTT context. */ + transport.networkContext = pSslContext; + transport.send = transportSend; + transport.recv = transportRecv; + + networkBuffer.pBuffer = buffer; + networkBuffer.size = NETWORK_BUFFER_SIZE; + + callbacks.appCallback = eventCallback; + callbacks.getTime = getTime; + + MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); + + /* Establish MQTT session with a CONNECT packet. */ + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + connectInfo.keepAliveSeconds = 0; + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0; + + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, NULL ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Close an MQTT session by sending MQTT DISCONNECT. + * + * @param[in] pContext MQTT context. + * + * @return EXIT_SUCCESS if DISCONNECT was successfully sent; EXIT_FAILURE otherwise. + */ +static int disconnectMqttSession( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + + MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + */ +int main( int argc, char ** argv ) +{ + bool mqttSessionEstablished = false; + int status; + MQTTContext_t context; + SSL * pSslContext = NULL; + + /* Establish TCP connection and MQTT session. */ + int tcpSocket = connectToServer( SERVER, PORT ); + + if( tcpSocket == -1 ) + { + status = EXIT_FAILURE; + } + else + { + status = EXIT_SUCCESS; + } + + /* Establish TLS connection on top of TCP connection. */ + if( status == EXIT_SUCCESS ) + { + pSslContext = tlsSetup( tcpSocket ); + + if( pSslContext == NULL ) + { + status = EXIT_FAILURE; + } + } + + /* Establish MQTT session on top of TCP+TLS connection. */ + if( status == EXIT_SUCCESS ) + { + status = establishMqttSession( &context, pSslContext ); + + if( status == EXIT_SUCCESS ) + { + mqttSessionEstablished = true; + } + } + + /* Insert demo code here. */ + + /* Disconnect MQTT session if established. */ + if( mqttSessionEstablished == true ) + { + if( status == EXIT_FAILURE ) + { + ( void ) disconnectMqttSession( &context ); + } + else + { + status = disconnectMqttSession( &context ); + } + } + + /* Close TLS session if established. */ + if( pSslContext != NULL ) + { + /* SSL shutdown should be called twice: once to send "close notify" and + * once more to receive the peer's "close notify". */ + if( SSL_shutdown( pSslContext ) == 0 ) + { + ( void ) SSL_shutdown( pSslContext ); + } + + SSL_free( pSslContext ); + } + + /* Close TCP connection if established. */ + if( tcpSocket != -1 ) + { + shutdown( tcpSocket, SHUT_RDWR ); + close( tcpSocket ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/mqtt/mqtt_demo_plaintext/Makefile b/demos/mqtt/mqtt_demo_plaintext/Makefile index 2f28de5231..ab3012a5c8 100644 --- a/demos/mqtt/mqtt_demo_plaintext/Makefile +++ b/demos/mqtt/mqtt_demo_plaintext/Makefile @@ -12,4 +12,4 @@ all: $(CC) $(FLAGS) $(INCLUDE_DIRS) $(SRC_FILES) -o mqtt_demo_plaintext clean: - rm mqtt_demo + rm mqtt_demo_plaintext diff --git a/demos/mqtt/mqtt_demo_plaintext/config.h b/demos/mqtt/mqtt_demo_plaintext/config.h index e190dce294..f1767aa7de 100644 --- a/demos/mqtt/mqtt_demo_plaintext/config.h +++ b/demos/mqtt/mqtt_demo_plaintext/config.h @@ -37,4 +37,11 @@ typedef int MQTTNetworkContext_t; */ #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient" + #endif /* ifndef CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 5acd4fc692..9ffe0bf689 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -52,12 +52,10 @@ */ #define NETWORK_BUFFER_SIZE ( 1024U ) -/** - * @brief MQTT client identifier. - * - * No two clients may use the same client identifier simultaneously. - */ -#define CLIENT_IDENTIFIER "testclient" +/* Check that client identifier is defined. */ +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif /** * @brief Length of client identifier. @@ -154,7 +152,7 @@ static int connectToServer( const char * pServer, uint16_t port ) * * @return Number of bytes sent; negative value on error. */ -static int32_t transportSend( int tcpSocket, const void * pMessage, size_t bytesToSend ) +static int32_t transportSend( MQTTNetworkContext_t tcpSocket, const void * pMessage, size_t bytesToSend ) { return ( int32_t ) send( tcpSocket, pMessage, bytesToSend, 0 ); } @@ -170,7 +168,7 @@ static int32_t transportSend( int tcpSocket, const void * pMessage, size_t bytes * * @return Number of bytes received; negative value on error. */ -static int32_t transportRecv( int tcpSocket, void * pBuffer, size_t bytesToRecv ) +static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ) { return ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); } @@ -285,16 +283,19 @@ static int disconnectMqttSession( MQTTContext_t * pContext ) int main( int argc, char ** argv ) { bool mqttSessionEstablished = false; - int status; + int status = EXIT_SUCCESS; MQTTContext_t context; + /* Establish TCP connection. */ int tcpSocket = connectToServer( SERVER, PORT ); if( tcpSocket == -1 ) { status = EXIT_FAILURE; } - else + + /* Establish MQTT session on top of TCP connection. */ + if( status = EXIT_SUCCESS ) { status = establishMqttSession( &context, tcpSocket ); @@ -304,6 +305,7 @@ int main( int argc, char ** argv ) } } + /* Disconnect MQTT session if established. */ if( mqttSessionEstablished == true ) { if( status == EXIT_FAILURE ) @@ -316,6 +318,7 @@ int main( int argc, char ** argv ) } } + /* Close TCP connection if established. */ if( tcpSocket != -1 ) { shutdown( tcpSocket, SHUT_RDWR ); diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index edeaa7e290..e056c8ac19 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -90,7 +90,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, { size_t remainingLength, packetSize; int32_t bytesSent; - MQTTPacketInfo_t incomingPacket; + MQTTPacketInfo_t incomingPacket = { .type = ( ( uint8_t ) 0 ) }; MQTTStatus_t status = MQTT_GetConnectPacketSize( pConnectInfo, pWillInfo, From 3e89b6cd21e7f6ecad7e80b9d1e822b4b9c39fb0 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 1 May 2020 11:14:47 -0700 Subject: [PATCH 476/844] Add missing const to pResponse parameter in HTTPClient_ReadHeader (#913) --- libraries/standard/http/src/http_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 4fe4b62d6a..f0b283ab34 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -594,7 +594,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -HTTPStatus_t HTTPClient_ReadHeader( HTTPResponse_t * pResponse, +HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, const char * pName, size_t nameLen, char ** pValue, From c7075e5629a7fcea20715032394a930fceb90ad2 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 1 May 2020 11:15:01 -0700 Subject: [PATCH 477/844] HTTPClient_AddRangeHeader() Implementation (#899) --- libraries/standard/http/include/http_client.h | 59 ++- libraries/standard/http/src/http_client.c | 96 ++++- .../http/src/private/http_client_internal.h | 20 ++ libraries/standard/http/test/Makefile | 1 + libraries/standard/http/test/config.h | 20 +- .../test/test-HTTPClient_AddRangeHeader.c | 335 ++++++++++++++++++ 6 files changed, 511 insertions(+), 20 deletions(-) create mode 100644 libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 7c56e32a20..e04dd2472c 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -73,6 +73,20 @@ */ #define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U +/** + * @brief Flag that represents End of File byte in the range specification of + * a Range Request. + * This flag should be used ONLY for 2 kinds of range specifications when + * creating the Range Request header through the #HTTPClient_AddRangeHeader + * function: + * - When the requested range is all bytes from the starting range byte to + * the end of file. + * - When the requested range is for the last N bytes of the file. + * In both cases, this value should be used for the "rangeEnd" parameter. + */ +#define HTTP_RANGE_REQUEST_END_OF_FILE -1 + + /** * @brief The HTTPNetworkContext is an incomplete type. The application must * define HTTPNetworkContext to the type of their network context. This context @@ -451,19 +465,46 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, * #HTTPRequestHeaders_t.pBuffer. * * For example, if requesting for the first 1kB of a file the following would be - * written "Range: bytes=0-1024\r\n". - * - * TODO: Add documentation about rangeStart and rangeEnd configuration. + * written "Range: bytes=0-1023\r\n\r\n". + * + * The trailing "\r\n" that denotes the end of the header lines is overwritten, if it + * already exists in the buffer. + * + * @note There are 3 different forms of range specification, determined by the + * combination of @a rangeStartOrLastNBytes and @a rangeEnd parameter values: + * 1. Request containing both parameters for the byte range [rangeStart, rangeEnd] + * where @a rangeStartOrLastNBytes <= @a rangeEnd. + * Example request: "Range: bytes=0-1023\r\n" for requesting bytes in the range [0, 1023]. + * + * 2. Request for the last N bytes, represented by @p rangeStartOrlastNbytes. + * @p rangeStartOrlastNbytes should be negative and @p rangeEnd should be + * #HTTP_RANGE_REQUEST_END_OF_FILE. + * Example request: "Range: bytes=-512\r\n" for requesting the last 512 bytes + * (or bytes in the range [512, 1023] for a 1kB sized file). + * + * 3. Request for all bytes (till the end of byte sequence) from byte N, + * represented by @p rangeStartOrlastNbytes. + * @p rangeStartOrlastNbytes should be >= 0 and @p rangeEnd should be + * #HTTP_RANGE_REQUEST_END_OF_FILE. + * Example request: "Range: bytes=256-\r\n" for requesting all bytes after and + * including byte 256 (or bytes in the range [256,1023] for a 1kB sized file). * * @param[in] pRequestHeaders Request header buffer information. - * @param[in] rangeStart The starting range for the requested file. - * @param[in] rangeEnd The ending range for the requested file. - * - * @return #HTTP_SUCCESS if successful, an error code otherwise. - * TODO: Update for exact error codes returned. + * @param[in] rangeStartOrlastNbytes Represents either the starting byte + * for a range OR the last N number of bytes in the requested file. + * @param[in] rangeEnd The ending range for the requested file. For end of file + * byte in Range Specifications 2. and 3., #HTTP_RANGE_REQUEST_END_OF_FILE + * should be passed. + * + * @return Returns the following status codes: + * #HTTP_SUCCESS if successful. + * #HTTP_INVALID_PARAMETER, if input parameters are invalid, including when + * the @p rangeStartOrlastNbytes and @p rangeEnd parameter combination is invalid. + * #HTTP_INSUFFICIENT_MEMORY, if the passed #HTTPRequestHeaders_t.pBuffer + * contains insufficient remaining memory for storing the range request. */ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, - int32_t rangeStart, + int32_t rangeStartOrlastNbytes, int32_t rangeEnd ); /** diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index f0b283ab34..c090c6c2a2 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -250,10 +250,102 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, - int32_t rangeStart, + int32_t rangeStartOrlastNbytes, int32_t rangeEnd ) { - return HTTP_NOT_SUPPORTED; + HTTPStatus_t returnStatus = HTTP_SUCCESS; + /* Extra byte allocation ( + 1 ) for NULL character when using sprintf. */ + char rangeValueBuffer[ MAX_RANGE_REQUEST_VALUE_LEN + 1 ] = { 0 }; + size_t rangeValueLength = 0; + int stdRetVal = 0; + + if( pRequestHeaders == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestHeaders->pBuffer == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( rangeEnd < HTTP_RANGE_REQUEST_END_OF_FILE ) + { + IotLogErrorWithArgs( "Parameter check failed: rangeEnd is invalid: " + "rangeEnd should be >=-1: RangeEnd=%d", rangeEnd ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( ( rangeStartOrlastNbytes < 0 ) && + ( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) ) + { + IotLogErrorWithArgs( "Parameter check failed: Invalid range values: " + "rangeEnd should be -1 when rangeStart < 0: " + "RangeStart=%d, RangeEnd=%d", + rangeStartOrlastNbytes, rangeEnd ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( ( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) && + ( rangeStartOrlastNbytes > rangeEnd ) ) + { + IotLogErrorWithArgs( "Parameter check failed: Invalid range values: " + "rangeStart should be < rangeEnd when both are >= 0: " + "RangeStart=%d, RangeEnd=%d", + rangeStartOrlastNbytes, rangeEnd ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else + { + /* Populate the buffer with the value data for the Range Request.*/ + stdRetVal = snprintf( rangeValueBuffer, + sizeof( rangeValueBuffer ), + "%s%d", + RANGE_REQUEST_HEADER_VALUE_PREFIX, + rangeStartOrlastNbytes ); + assert( ( stdRetVal >= 0 ) && + stdRetVal <= ( int ) ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DIGITS ) ); + rangeValueLength += ( size_t ) stdRetVal; + + /* Add remaining value data depending on the range specification type. */ + + /* Add rangeEnd value if request is for [rangeStart, rangeEnd] byte range */ + if( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) + { + /* Add the rangeEnd value to the request range .*/ + stdRetVal = snprintf( rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength, + "%s%d", + DASH_CHARACTER, + rangeEnd ); + assert( ( stdRetVal >= 0 ) && + stdRetVal <= ( DASH_CHARACTER_LEN + MAX_INT32_NO_OF_DIGITS ) ); + rangeValueLength += ( size_t ) stdRetVal; + } + /* Case when request is for bytes in the range [rangeStart, ). */ + else if( rangeStartOrlastNbytes >= 0 ) + { + /* Add the "-" character.*/ + stdRetVal = snprintf( rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength, + "%s", + DASH_CHARACTER ); + /* Check that only a single character was written. */ + assert( stdRetVal == DASH_CHARACTER_LEN ); + rangeValueLength += ( size_t ) stdRetVal; + } + else + { + /* Empty else MISRA 15.7 */ + } + + /* Add the Range Request header field and value to the buffer. */ + returnStatus = _addHeader( pRequestHeaders, + RANGE_REQUEST_HEADER_FIELD, + RANGE_REQUEST_HEADER_FIELD_LEN, + rangeValueBuffer, + rangeValueLength ); + } + + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 208f28b3ce..4fb0a540db 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -85,4 +85,24 @@ #define HTTP_CONNECTION_CLOSE_VALUE "close" #define HTTP_CONNECTION_CLOSE_VALUE_LEN ( sizeof( HTTP_CONNECTION_CLOSE_VALUE ) - 1 ) + +/** + * @brief Constants relating to Range Requests. + */ +#define RANGE_REQUEST_HEADER_FIELD "Range" +#define RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( RANGE_REQUEST_HEADER_FIELD ) - 1 ) +#define RANGE_REQUEST_HEADER_VALUE_PREFIX "bytes=" +#define RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ( sizeof( RANGE_REQUEST_HEADER_VALUE_PREFIX ) - 1 ) + +/* Maximum value of a 32 bit signed integer is 2,147,483,647. Used for calculating buffer space for + * ASCII representation of range values. */ +#define MAX_INT32_NO_OF_DECIMAL_DIGITS 10 + +/* Maximum buffer space for storing a Range Request Value. + * Largest size is of the form "bytes=-<" */ +#define MAX_RANGE_REQUEST_VALUE_LEN \ + ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DECIMAL_DIGITS + \ + 1u /* Dash character '-' */ + MAX_INT32_NO_OF_DIGITS ) + + #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 741bce2dc1..2110af6eaf 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -44,6 +44,7 @@ $(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/po # additional dependency for a specific test HTTPClient_AddHeader.c: _addHeader.c +HTTPClient_AddRangeHeader.c: _addHeader.c HTTPClient_Send.c: _sendHttpHeaders.c \ _sendHttpBody.c \ _receiveHttpResponse.c \ diff --git a/libraries/standard/http/test/config.h b/libraries/standard/http/test/config.h index 6fdd720dc8..1dc37071eb 100644 --- a/libraries/standard/http/test/config.h +++ b/libraries/standard/http/test/config.h @@ -1,23 +1,25 @@ #ifndef CONFIG_H #define CONFIG_H -#define USE_AWS_IOT_CSDK_LOGGING - #define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG +#ifdef USE_AWS_IOT_CSDK_LOGGING + /* Include file for POSIX reference implementation. */ -#include "iot_logging.h" + #include "iot_logging.h" /* Define the IotLog logging interface to enabling logging. * This demo maps the macro to the reference POSIX implementation for logging. * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the * log, as metadata in each log message. */ -#define IotLog( messageLevel, pFormat, ... ) \ - IotLog_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ + #define IotLog( messageLevel, pFormat, ... ) \ + IotLog_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ __VA_ARGS__ ) +#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ + #endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c new file mode 100644 index 0000000000..5f3f85d847 --- /dev/null +++ b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c @@ -0,0 +1,335 @@ +#include +#include + +#include "common.h" + +/* Functions are pulled out into their own C files to be tested as a unit. */ +#include "_addHeader.c" +#include "HTTPClient_AddRangeHeader.c" + +/* Default size for request buffer. */ +#define HTTP_TEST_BUFFER_SIZE ( 100 ) + +/* Headers data with "\r\n\r\n" terminator to be pre-populated in buffer before + * call to AddRangeHeader(). */ +#define PREEXISTING_HEADER_DATA "POST / HTTP/1.1 \r\nAuthorization: None\r\n\r\n" +#define PREEXISTING_HEADER_DATA_LEN ( sizeof( PREEXISTING_HEADER_DATA ) - 1 ) + +/* Headers data without "\r\n\r\n" terminator to be pre-populated in buffer before + * call to AddRangeHeader(). */ +#define PREEXISTING_REQUEST_LINE "POST / HTTP/1.1 \r\n" +#define PREEXISTING_REQUEST_LINE_LEN ( sizeof( PREEXISTING_REQUEST_LINE ) - 1 ) + + +/* Range Request data that is common for all range specification types. */ +#define RANGE_REQUEST_HEADER_DATA_PREFIX "Range: bytes=" +#define RANGE_REQUEST_HEADER_DATA_PREFIX_LEN ( sizeof( RANGE_REQUEST_HEADER_DATA_PREFIX ) - 1 ) + +/* The range end value for representing end of file byte. */ +#define RANGE_REQUEST_END_OF_FILE -1 + +/* Type to store expected headers data. */ +typedef struct _headers +{ + uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ]; + size_t dataLen; +} _headers_t; + +#define setupBuffersWithPreexistingHeader( requestHeaders, \ + testBuffer, \ + expectedHeaders, \ + preexistingData ) \ + do { \ + size_t dataLen = strlen( preexistingData ); \ + requestHeaders.pBuffer = testBuffer; \ + requestHeaders.bufferLen = sizeof( testBuffer ); \ + /* We add 1 bytes as snprintf() writes a null byte at the end. */ \ + int numBytes = snprintf( ( char * ) requestHeaders.pBuffer, \ + dataLen + 1, \ + "%s", \ + preexistingData ); \ + ok( numBytes == dataLen ); \ + requestHeaders.headersLen = dataLen; \ + /* Fill the same data in the expected buffer as HTTPClient_AddRangeHeaders() + * is not expected to change it. */ \ + ok( memcpy( expectedHeaders.buffer, requestHeaders.pBuffer, \ + requestHeaders.headersLen ) \ + == expectedHeaders.buffer ); \ + expectedHeaders.dataLen = requestHeaders.headersLen; \ + } while( 0 ) + + +#define addRangeToExpectedHeaders( expectedHeaders, expectedRange, terminatorExists ) \ + do { \ + size_t rangeRequestLen = RANGE_REQUEST_HEADER_DATA_PREFIX_LEN + \ + strlen( expectedRange ) + \ + 2 * HTTP_HEADER_LINE_SEPARATOR_LEN; \ + int numBytes = \ + snprintf( ( char * ) expectedHeaders.buffer + \ + expectedHeaders.dataLen - \ + ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ), \ + /* We add 1 bytes as snprintf() writes a null byte at the end. */ \ + rangeRequestLen + 1, \ + "%s%s\r\n\r\n", \ + RANGE_REQUEST_HEADER_DATA_PREFIX, \ + expectedRange ); \ + ok( ( size_t ) numBytes == rangeRequestLen ); \ + expectedHeaders.dataLen += rangeRequestLen - \ + ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ); \ + } while( 0 ) + + +int main() +{ + HTTPRequestHeaders_t testHeaders = { 0 }; + HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; + uint8_t testBuffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; + _headers_t expectedHeaders = { 0 }; + int testRangeStart = 0; + int testRangeEnd = 0; + +#define reset() \ + do { \ + retCode = HTTP_NOT_SUPPORTED; \ + memset( &testHeaders, 0, sizeof( testHeaders ) ); \ + memset( testBuffer, 0, sizeof( testBuffer ) ); \ + memset( &expectedHeaders, 0, sizeof( expectedHeaders ) ); \ + } while( 0 ) + + plan( 62 ); + + /*************************** Test happy path. *****************************/ + + /* Case 1: Headers buffer contains header data ending with "\r\n\r\n". */ + + /* Range specification of the form [rangeStart, rangeEnd]. */ + /* Test with 0 as the range values */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 0; + testRangeEnd = 0; + addRangeToExpectedHeaders( expectedHeaders, + "0-0" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + /* Test for [0, eof) range */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 0; + testRangeEnd = RANGE_REQUEST_END_OF_FILE; + addRangeToExpectedHeaders( expectedHeaders, + "0-" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 10; + testRangeEnd = 100; + addRangeToExpectedHeaders( expectedHeaders, + "10-100" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + /* Range specification of the form [rangeStart,) + * i.e. for all bytes >= rangeStart. */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 100; + testRangeEnd = RANGE_REQUEST_END_OF_FILE; + addRangeToExpectedHeaders( expectedHeaders, + "100-" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + /* Range specification for the last N bytes. */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = -50; + testRangeEnd = RANGE_REQUEST_END_OF_FILE; + addRangeToExpectedHeaders( expectedHeaders, + "-50" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + /* Test with LARGE range values. */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = INT32_MAX; + testRangeEnd = INT32_MAX; + addRangeToExpectedHeaders( expectedHeaders, + "2147483647-2147483647" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + /* Case 2: Headers buffer does not contain data with trailing "\r\n\r\n". */ + + /* Range specification of the form [rangeStart, rangeEnd]. */ + /* Test with 0 as the range values */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_REQUEST_LINE ); + testRangeStart = 0; + testRangeEnd = 0; + addRangeToExpectedHeaders( expectedHeaders, + "0-0" /*expected range*/, + false ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_SUCCESS ); + /* Verify the the Range Request header data. */ + ok( testHeaders.headersLen == expectedHeaders.dataLen ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) + == 0 ); + /* Verify that the bufferLen data was not tampered with. */ + ok( testHeaders.bufferLen == sizeof( testBuffer ) ); + + /*************************** Test Failure Cases *****************************/ + + /* Request header parameter is NULL. */ + reset(); + retCode = HTTPClient_AddRangeHeader( NULL, + 0 /* rangeStart */, + 0 /* rageEnd */ ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Underlying buffer is NULL in request headers. */ + reset(); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 0 /* rangeStart */, + 0 /* rageEnd */ ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Request Header Size is zero. */ + reset(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 0 /* rangeStart */, + 0 /* rageEnd */ ); + ok( retCode == HTTP_INSUFFICIENT_MEMORY ); + + /* Test incorrect combinations of rangeStart and rangeEnd. */ + + /* rangeStart > rangeEnd */ + reset(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 10 /* rangeStart */, + 5 /* rageEnd */ ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* rangeStart is negative but rangeStart is non-End of File. */ + reset(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + -10 /* rangeStart */, + RANGE_REQUEST_END_OF_FILE + 1 /* rageEnd */ ); + ok( retCode == HTTP_INVALID_PARAMETER ); + reset(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + -50 /* rangeStart */, + -10 /* rageEnd */ ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Test Insufficient memory failure when the buffer has one less byte than required. */ + reset(); + setupBuffersWithPreexistingHeader( testHeaders, testBuffer, + expectedHeaders, + PREEXISTING_HEADER_DATA ); + size_t preHeadersLen = testHeaders.headersLen; + testRangeStart = 5; + testRangeEnd = 10; + addRangeToExpectedHeaders( expectedHeaders, + "5-10" /*expected range*/, + true ); + + /* Update headers buffer size to be one byte short of required size to add + * Range Request header. */ + testHeaders.bufferLen = expectedHeaders.dataLen - 1; + + /* Re-write the expected headers buffer to store a copy of the test headers + * to use for verification later. */ + memcpy( expectedHeaders.buffer, testHeaders.pBuffer, testHeaders.bufferLen ); + + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + ok( retCode == HTTP_INSUFFICIENT_MEMORY ); + /* Verify the headers input parameter is unaltered. */ + ok( testHeaders.headersLen == preHeadersLen ); + ok( testHeaders.bufferLen == expectedHeaders.dataLen - 1 ); + ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, testHeaders.bufferLen ) + == 0 ); + + return grade(); +} From 49c2d974059301f015f958358fbd66b1e25fdbab Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 1 May 2020 11:43:05 -0700 Subject: [PATCH 478/844] Update macro name causing build failure (#914) --- libraries/standard/http/src/private/http_client_internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 4fb0a540db..9062a20deb 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -102,7 +102,7 @@ * Largest size is of the form "bytes=-<" */ #define MAX_RANGE_REQUEST_VALUE_LEN \ ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DECIMAL_DIGITS + \ - 1u /* Dash character '-' */ + MAX_INT32_NO_OF_DIGITS ) + 1u /* Dash character '-' */ + MAX_INT32_NO_OF_DECIMAL_DIGITS ) #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ From c473e42de94730e302e8c64379d81e41d7895590 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 4 May 2020 11:08:07 -0700 Subject: [PATCH 479/844] Add logging macros for MQTT (#919) * Add logging macros for MQTT * Add a new line character at the end of the file --- .../standard/mqtt/src/private/mqtt_internal.h | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 libraries/standard/mqtt/src/private/mqtt_internal.h diff --git a/libraries/standard/mqtt/src/private/mqtt_internal.h b/libraries/standard/mqtt/src/private/mqtt_internal.h new file mode 100644 index 0000000000..c557969f2a --- /dev/null +++ b/libraries/standard/mqtt/src/private/mqtt_internal.h @@ -0,0 +1,31 @@ +#ifndef MQTT_INTERNAL_H_ +#define MQTT_INTERNAL_H_ + +/** + * AWS IoT Embedded C SDK optional specific logging setup. + */ +#ifdef USE_AWS_IOT_CSDK_LOGGING + #ifdef IOT_LOG_LEVEL_MQTT + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT + #else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif + #endif + #define LIBRARY_LOG_NAME ( "MQTT" ) + #include "iot_logging_setup.h" +#else /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +/* Otherwise please define logging macros in config.h. */ + #define IotLogError( message ) + #define IotLogErrorWithArgs( format, ... ) + #define IotLogWarn( message ) + #define IotLogWarnWithArgs( format, ... ) + #define IotLogInfo( message ) + #define IotLogInfoWithArgs( format, ... ) + #define IotLogDebug( message ) + #define IotLogDebugWithArgs( format, ... ) +#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ + +#endif /* ifndef MQTT_INTERNAL_H_ */ From 08f50789a34fd7505c1e294d803878298784230c Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 5 May 2020 15:00:46 -0700 Subject: [PATCH 480/844] Implement and test HTTPClient_InitializeRequestHeaders method (#894) * Refactor and add happy path test for HTTPClient_InitializeRequestHeaders * Achieve branch coverage for HTTPClient_InitializeRequestHeaders * Add documentation for _writeRequestLine private method * Add test for default PATH * Refactor tests * Add assert statements for _writeRequestLine * Use ternary operator isntead * Add comment for HTTP_TEST_BUFFER_SIZE * Address PR comments * Change number of planned tests * Fix trailing \ in Makefile * Add missing const to pResponse parameter in * Address PR comments * Change snprintf to memcpy * Achieve test coverage for _writeRequestLine * Remove unused HTTP_REQUEST_LINE_FORMAT macro --- libraries/standard/http/include/http_client.h | 3 +- libraries/standard/http/src/http_client.c | 183 ++++++++++++++- .../http/src/private/http_client_internal.h | 7 +- libraries/standard/http/test/Makefile | 4 +- ...test-HTTPClient_InitializeRequestHeaders.c | 220 ++++++++++++++++++ 5 files changed, 412 insertions(+), 5 deletions(-) create mode 100644 libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index e04dd2472c..57bd2a8ec0 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -407,7 +407,8 @@ typedef struct HTTPResponse /** * @brief Initialize the request headers, stored in * #HTTPRequestHeaders_t.pBuffer, with initial configurations from - * #HTTPRequestInfo_t. + * #HTTPRequestInfo_t. This method is expected to be called before sending a + * new request. * * Upon return, #HTTPRequestHeaders_t.headersLen will be updated with the number * of bytes written. diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index c090c6c2a2..554fc51c66 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -108,6 +108,25 @@ static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, HTTPResponse_t * pResponse ); +/** + * @brief This method writes the request line (first line) of the HTTP Header + * into #HTTPRequestHeaders_t.pBuffer and updates length accordingly. + * + * @param pRequestHeaders Request header buffer information. + * @param pMethod The HTTP request method e.g. "GET", "POST", "PUT", or "HEAD". + * @param methodLen The byte length of the request method. + * @param pPath The Request-URI to the objects of interest, e.g. "/path/to/item.txt". + * @param pathLen The byte length of the request path. + * + * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the + * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. + */ +static HTTPStatus_t _writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ); + /*-----------------------------------------------------------*/ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, @@ -186,10 +205,172 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ +static HTTPStatus_t _writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + uint8_t * pBufferCur = pRequestHeaders->pBuffer; + size_t toAddLen = methodLen + \ + SPACE_CHARACTER_LEN + \ + SPACE_CHARACTER_LEN + \ + HTTP_PROTOCOL_VERSION_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN; + int32_t bytesWritten = 0; + + assert( pRequestHeaders != NULL ); + assert( pRequestHeaders->pBuffer != NULL ); + assert( pMethod != NULL ); + assert( methodLen != 0u ); + + toAddLen += ( pPath == NULL || pathLen == 0 ) ? HTTP_EMPTY_PATH_LEN : pathLen; + + if( ( toAddLen + pRequestHeaders->headersLen ) > pRequestHeaders->bufferLen ) + { + returnStatus = HTTP_INSUFFICIENT_MEMORY; + } + + if( returnStatus == HTTP_SUCCESS ) + { + /* Write " HTTP/1.1\r\n" to start the HTTP header. */ + memcpy( pBufferCur, pMethod, methodLen ); + pBufferCur += methodLen; + memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); + + pBufferCur += SPACE_CHARACTER_LEN; + + /* Use "/" as default value if is NULL. */ + if( ( pPath == NULL ) || ( pathLen == 0 ) ) + { + memcpy( pBufferCur, HTTP_EMPTY_PATH, HTTP_EMPTY_PATH_LEN ); + pBufferCur += HTTP_EMPTY_PATH_LEN; + } + else + { + memcpy( pBufferCur, pPath, pathLen ); + pBufferCur += pathLen; + } + + memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); + pBufferCur += SPACE_CHARACTER_LEN; + + memcpy( pBufferCur, + HTTP_PROTOCOL_VERSION, HTTP_PROTOCOL_VERSION_LEN ); + pBufferCur += HTTP_PROTOCOL_VERSION_LEN; + memcpy( pBufferCur, + HTTP_HEADER_LINE_SEPARATOR, HTTP_HEADER_LINE_SEPARATOR_LEN ); + pRequestHeaders->headersLen = toAddLen; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders, const HTTPRequestInfo_t * pRequestInfo ) { - return HTTP_NOT_SUPPORTED; + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + /* Check for NULL parameters. */ + if( pRequestHeaders == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestHeaders->pBuffer == NULL ) + { + IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( ( pRequestInfo == NULL ) ) + { + IotLogError( "Parameter check failed: pRequestInfo is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( ( pRequestInfo->method == NULL ) ) + { + IotLogError( "Parameter check failed: pRequestInfo->method is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestInfo->pHost == NULL ) + { + IotLogError( "Parameter check failed: pRequestInfo->pHost is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestInfo->methodLen == 0 ) + { + IotLogError( "Parameter check failed: pRequestInfo->methodLen must be greater than 0." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pRequestInfo->hostLen == 0 ) + { + IotLogError( "Parameter check failed: pRequestInfo->hostLen must be greater than 0." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( returnStatus == HTTP_SUCCESS ) + { + /* Reset application-provided parameters. */ + pRequestHeaders->headersLen = 0; + + /* Write " HTTP/1.1\r\n" to start the HTTP header. */ + returnStatus = _writeRequestLine( pRequestHeaders, + pRequestInfo->method, + pRequestInfo->methodLen, + pRequestInfo->pPath, + pRequestInfo->pathLen ); + } + + if( returnStatus == HTTP_SUCCESS ) + { + /* Write "User-Agent: ". */ + returnStatus = _addHeader( pRequestHeaders, + HTTP_USER_AGENT_FIELD, + HTTP_USER_AGENT_FIELD_LEN, + HTTP_USER_AGENT_VALUE, + HTTP_USER_AGENT_VALUE_LEN ); + } + + if( returnStatus == HTTP_SUCCESS ) + { + /* Write "Host: ". */ + returnStatus = _addHeader( pRequestHeaders, + HTTP_HOST_FIELD, + HTTP_HOST_FIELD_LEN, + pRequestInfo->pHost, + pRequestInfo->hostLen ); + } + + if( returnStatus == HTTP_SUCCESS ) + { + if( HTTP_REQUEST_KEEP_ALIVE_FLAG & pRequestInfo->flags ) + { + /* Write "Connection: keep-alive". */ + returnStatus = _addHeader( pRequestHeaders, + HTTP_CONNECTION_FIELD, + HTTP_CONNECTION_FIELD_LEN, + HTTP_CONNECTION_KEEP_ALIVE_VALUE, + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ); + } + else + { + /* Write "Connection: close". */ + returnStatus = _addHeader( pRequestHeaders, + HTTP_CONNECTION_FIELD, + HTTP_CONNECTION_FIELD_LEN, + HTTP_CONNECTION_CLOSE_VALUE, + HTTP_CONNECTION_CLOSE_VALUE_LEN ); + } + } + + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 9062a20deb..4d529ef53d 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -48,8 +48,10 @@ #define HTTP_HEADER_END_INDICATOR "\r\n\r\n" #define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1 ) #define HTTP_HEADER_ADD_FORMAT "%.*s" HTTP_HEADER_FIELD_SEPARATOR "%.*s" HTTP_HEADER_LINE_SEPARATOR -#define CARRIAGE_RETURN_CHARACTER '\r' -#define NEWLINE_CHARACTER '\n' +#define CARRIAGE_RETURN_CHARACTER "\r" +#define CARRIAGE_RETURN_CHARACTER_LEN ( sizeof( CARRIAGE_RETURN_CHARACTER ) - 1 ) +#define NEWLINE_CHARACTER "\n" +#define NEWLINE_CHARACTER_LEN ( sizeof( NEWLINE_CHARACTER ) - 1 ) #define HTTP_HEADER_FIELD_SEPARATOR ": " #define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1 ) #define COLON_CHARACTER ":" @@ -68,6 +70,7 @@ #define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1 ) #define HTTP_HOST_FIELD "Host" #define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1 ) +#define HTTP_USER_AGENT_VALUE_LEN ( sizeof( HTTP_USER_AGENT_VALUE ) - 1 ) /** * @brief Constants for header fields added based on flags. diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 2110af6eaf..3faad2cc88 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -28,6 +28,7 @@ INCLUDE = . \ # functions to dump into separate source files # Values based on test source file names will be dynamically added. FUNCTIONS = _addHeader \ + _writeRequestLine \ _sendHttpHeaders \ _sendHttpBody \ _receiveHttpResponse \ @@ -51,7 +52,8 @@ HTTPClient_Send.c: _sendHttpHeaders.c \ _receiveAndParseHttpResponse.c \ _HTTPClient_InitializeParsingContext.c \ _HTTPClient_ParseResponse.c \ - _getFinalResponseStatus.c \ + _getFinalResponseStatus.c +HTTPClient_InitializeRequestHeaders.c: _writeRequestLine.c _addHeader.c # additional header dependencies for all tests COMMON = common.h diff --git a/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c b/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c new file mode 100644 index 0000000000..bb6fa845e8 --- /dev/null +++ b/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c @@ -0,0 +1,220 @@ +#include + +#include "common.h" + +/* Functions are pulled out into their own C files to be tested as a unit. */ +#include "_writeRequestLine.c" +#include "_addHeader.c" +#include "HTTPClient_InitializeRequestHeaders.c" + +#define HTTP_TEST_REQUEST_METHOD "GET" +#define HTTP_TEST_REQUEST_METHOD_LEN ( sizeof( HTTP_TEST_REQUEST_METHOD ) - 1 ) +#define HTTP_TEST_REQUEST_PATH "/robots.txt" +#define HTTP_TEST_REQUEST_PATH_LEN ( sizeof( HTTP_TEST_REQUEST_PATH ) - 1 ) +#define HTTP_TEST_HOST_VALUE "amazon.com" +#define HTTP_TEST_HOST_VALUE_LEN ( sizeof( HTTP_TEST_HOST_VALUE ) - 1 ) +#define HTTP_TEST_REQUEST_LINE \ + ( HTTP_TEST_REQUEST_METHOD " " \ + HTTP_TEST_REQUEST_PATH " " \ + HTTP_PROTOCOL_VERSION "\r\n" ) +#define HTTP_TEST_REQUEST_LINE_LEN ( sizeof( HTTP_TEST_REQUEST_LINE ) - 1 ) + +/* Used for format parameter in snprintf(...). */ +#define HTTP_TEST_HEADER_FORMAT \ + "%s %s %s\r\n" \ + "%s: %s\r\n" \ + "%s: %s\r\n" \ + "%s: %s\r\n\r\n" + +#define HTTP_REQUEST_HEADERS_INITIALIZER { 0 } +#define HTTP_REQUEST_INFO_INITIALIZER { 0 } + +/* Length of the following template HTTP header. + * \r\n + * : \r\n + * : \r\n + * : \r\n + * \r\n + * This is used to initialize the expectedHeader string. Note the missing + * . This is added later on depending on the + * value of HTTP_REQUEST_KEEP_ALIVE_FLAG in reqInfo->flags. */ +#define HTTP_TEST_PREFIX_HEADER_LEN \ + ( HTTP_TEST_REQUEST_METHOD_LEN + SPACE_CHARACTER_LEN + \ + HTTP_TEST_REQUEST_PATH_LEN + SPACE_CHARACTER_LEN + \ + HTTP_PROTOCOL_VERSION_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_USER_AGENT_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_USER_AGENT_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_HOST_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_TEST_HOST_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_CONNECTION_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN ) + +/* Add HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN to account for longest possible + * length of template header. */ +#define HTTP_TEST_MAX_HEADER_LEN \ + ( HTTP_TEST_PREFIX_HEADER_LEN + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ) + +/* Add 1 because snprintf(...) writes a null byte at the end. */ +#define HTTP_TEST_BUFFER_SIZE ( HTTP_TEST_MAX_HEADER_LEN + 1 ) + + +int main() +{ + HTTPRequestHeaders_t reqHeaders = HTTP_REQUEST_HEADERS_INITIALIZER; + HTTPRequestHeaders_t reqHeadersDflt = HTTP_REQUEST_HEADERS_INITIALIZER; + HTTPRequestInfo_t reqInfo = HTTP_REQUEST_INFO_INITIALIZER; + HTTPRequestInfo_t reqInfoDflt = HTTP_REQUEST_INFO_INITIALIZER; + HTTPStatus_t test_err = HTTP_NOT_SUPPORTED; + uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; + char expectedHeader[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; + size_t expectedHeaderLen = HTTP_TEST_MAX_HEADER_LEN; + +/* Write template reqInfo to pass as parameter to + * HTTPClient_InitializeRequestHeaders() method. */ +#define fillReqInfoTemplate() \ + do { \ + reqInfo.method = HTTP_TEST_REQUEST_METHOD; \ + reqInfo.methodLen = HTTP_TEST_REQUEST_METHOD_LEN; \ + reqInfo.pPath = HTTP_TEST_REQUEST_PATH; \ + reqInfo.pathLen = HTTP_TEST_REQUEST_PATH_LEN; \ + reqInfo.pHost = HTTP_TEST_HOST_VALUE; \ + reqInfo.hostLen = HTTP_TEST_HOST_VALUE_LEN; \ + reqInfo.flags = 0; \ + } \ + while( 0 ) + +#define reset() \ + do { \ + test_err = HTTP_NOT_SUPPORTED; \ + reqHeaders = reqHeadersDflt; \ + reqInfo = reqInfoDflt; \ + memset( buffer, 0, HTTP_TEST_BUFFER_SIZE ); \ + memset( expectedHeader, 0, HTTP_TEST_MAX_HEADER_LEN + 1 ); \ + } \ + while( 0 ) + + plan( 22 ); + + /* Happy Path testing. */ + reset(); + expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN + \ + HTTP_CONNECTION_CLOSE_VALUE_LEN; + ok( snprintf( expectedHeader, expectedHeaderLen + 1, + HTTP_TEST_HEADER_FORMAT, + HTTP_TEST_REQUEST_METHOD, HTTP_TEST_REQUEST_PATH, HTTP_PROTOCOL_VERSION, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ) + == ( int ) expectedHeaderLen ); + /* Set parameters for reqHeaders. */ + reqHeaders.pBuffer = buffer; + reqHeaders.bufferLen = expectedHeaderLen; + /* Set parameters for reqInfo. */ + fillReqInfoTemplate(); + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + printf( "%s\n", reqHeaders.pBuffer ); + printf( "%s\n", expectedHeader ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + expectedHeader, expectedHeaderLen ) == 0 ); + ok( reqHeaders.headersLen == expectedHeaderLen ); + ok( test_err == HTTP_SUCCESS ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test NULL parameters, following order of else-if blocks. */ + test_err = HTTPClient_InitializeRequestHeaders( NULL, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + /* reqInfo.pBuffer should be NULL after reset(). */ + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reqHeaders.pBuffer = buffer; + reqHeaders.bufferLen = HTTP_TEST_BUFFER_SIZE; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, NULL ); + ok( test_err == HTTP_INVALID_PARAMETER ); + /* reqInfo members should be NULL after reset(). */ + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reqInfo.method = HTTP_TEST_REQUEST_METHOD; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reqInfo.pHost = HTTP_TEST_HOST_VALUE; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reqInfo.pPath = HTTP_TEST_REQUEST_PATH; + reqInfo.pathLen = HTTP_TEST_REQUEST_PATH_LEN; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reqInfo.methodLen = HTTP_TEST_REQUEST_METHOD_LEN; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INVALID_PARAMETER ); + reqInfo.hostLen = HTTP_TEST_HOST_VALUE_LEN; + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test HTTP_REQUEST_KEEP_ALIVE_FLAG. */ + fillReqInfoTemplate(); + reqInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN + \ + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN; + ok( snprintf( expectedHeader, expectedHeaderLen + 1, + HTTP_TEST_HEADER_FORMAT, + HTTP_TEST_REQUEST_METHOD, HTTP_TEST_REQUEST_PATH, + HTTP_PROTOCOL_VERSION, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_KEEP_ALIVE_VALUE ) + == ( int ) expectedHeaderLen ); + reqHeaders.pBuffer = buffer; + reqHeaders.bufferLen = expectedHeaderLen; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + expectedHeader, expectedHeaderLen ) == 0 ); + ok( reqHeaders.headersLen == expectedHeaderLen ); + ok( test_err == HTTP_SUCCESS ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test default path "/" if path == NULL. */ + fillReqInfoTemplate(); + reqInfo.pPath = NULL; + expectedHeaderLen = ( HTTP_TEST_PREFIX_HEADER_LEN - HTTP_TEST_REQUEST_PATH_LEN ) + \ + HTTP_EMPTY_PATH_LEN + \ + HTTP_CONNECTION_CLOSE_VALUE_LEN; + ok( snprintf( expectedHeader, expectedHeaderLen + 1, + HTTP_TEST_HEADER_FORMAT, + HTTP_TEST_REQUEST_METHOD, HTTP_EMPTY_PATH, HTTP_PROTOCOL_VERSION, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ) + == ( int ) expectedHeaderLen ); + reqHeaders.pBuffer = buffer; + reqHeaders.bufferLen = expectedHeaderLen; + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + expectedHeader, expectedHeaderLen ) == 0 ); + ok( reqHeaders.headersLen == expectedHeaderLen ); + ok( test_err == HTTP_SUCCESS ); + reset(); + + /* -----------------------------------------------------------------------*/ + + /* Test HTTP_INSUFFICIENT_MEMORY from writing request line. */ + /* Set parameters for reqHeaders. */ + reqHeaders.pBuffer = buffer; + reqHeaders.bufferLen = HTTP_TEST_REQUEST_LINE_LEN - 1; + /* Set parameters for reqInfo. */ + fillReqInfoTemplate(); + test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); + ok( test_err == HTTP_INSUFFICIENT_MEMORY ); + ok( strncmp( ( char * ) reqHeaders.pBuffer, + HTTP_TEST_REQUEST_LINE, HTTP_TEST_REQUEST_LINE_LEN ) != 0 ); + reset(); + + /* -----------------------------------------------------------------------*/ + + return grade(); +} From 5a15c1dc5c2504143e90b5701c33d0211c21f3d3 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 5 May 2020 17:03:49 -0700 Subject: [PATCH 481/844] Bugfix/http build issues (#915) --- libraries/standard/http/include/http_client.h | 9 ++- libraries/standard/http/src/http_client.c | 68 ++++++++++--------- .../http/src/private/http_client_internal.h | 5 -- libraries/standard/http/test/common.h | 5 +- .../http/test/test-HTTPClient_AddHeader.c | 6 +- .../test/test-HTTPClient_AddRangeHeader.c | 2 +- 6 files changed, 50 insertions(+), 45 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 57bd2a8ec0..50e45a44c6 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -2,6 +2,7 @@ #define HTTP_CLIENT_H_ #include +#include #include "config.h" /** @@ -446,8 +447,12 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pField The header field name to write. + * The data should be ISO 8859-1 (Latin-1) encoded per the HTTP standard, + * but the API does not perform the character set validation. * @param[in] fieldLen The byte length of the header field name. * @param[in] pValue The header value to write. + * The data should be ISO 8859-1 (Latin-1) encoded per the HTTP standard, + * but the API does not perform the character set validation. * @param[in] valueLen The byte length of the header field value. * * @return One of the following: @@ -456,9 +461,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques * - #HTTP_INSUFFICIENT_MEMORY (If application-provided buffer is not large enough to hold headers.) */ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char * pField, + const uint8_t * pField, size_t fieldLen, - const char * pValue, + const uint8_t * pValue, size_t valueLen ); /** diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 554fc51c66..33d3b11df9 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -41,18 +41,18 @@ static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, * in order to write over it and updates the length accordingly. * * @param[in] pRequestHeaders Request header buffer information. - * @param[in] pField The header field name to write. + * @param[in] pField The ISO 8859-1 encoded header field name to write. * @param[in] fieldLen The byte length of the header field name. - * @param[in] pValue The header value to write. + * @param[in] pValue The ISO 8859-1 encoded header value to write. * @param[in] valueLen The byte length of the header field value. * * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. */ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char * pField, + const uint8_t * pField, size_t fieldLen, - const char * pValue, + const uint8_t * pValue, size_t valueLen ); /** @@ -130,16 +130,15 @@ static HTTPStatus_t _writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char * pField, + const uint8_t * pField, size_t fieldLen, - const char * pValue, + const uint8_t * pValue, size_t valueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; uint8_t * pBufferCur = pRequestHeaders->pBuffer + pRequestHeaders->headersLen; size_t toAddLen = 0; size_t backtrackHeaderLen = pRequestHeaders->headersLen; - int32_t bytesWritten = 0; assert( pRequestHeaders != NULL ); assert( pRequestHeaders->pBuffer != NULL ); @@ -167,28 +166,29 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, /* If we have enough room for the new header line, then write it to the header buffer. */ if( ( backtrackHeaderLen + toAddLen ) <= pRequestHeaders->bufferLen ) { - /* Write "Field: Value \r\n" to headers. */ - bytesWritten = snprintf( ( char * ) pBufferCur, - toAddLen, - HTTP_HEADER_ADD_FORMAT, - ( int32_t ) fieldLen, pField, - ( int32_t ) valueLen, pValue ); + /* Write ": \r\n" to the headers buffer. */ - if( ( bytesWritten + HTTP_HEADER_LINE_SEPARATOR_LEN ) != toAddLen ) - { - IotLogErrorWithArgs( "Internal error in snprintf() in _addHeader(). " - "BytesWritten: %d.", bytesWritten ); - } - else - { - pBufferCur += bytesWritten; + /* Copy the header name into the buffer. */ + memcpy( pBufferCur, pField, fieldLen ); + pBufferCur += fieldLen; - /* HTTP_HEADER_LINE_SEPARATOR cannot be written above because snprintf - * writes an extra null byte at the end. */ - memcpy( pBufferCur, HTTP_HEADER_LINE_SEPARATOR, HTTP_HEADER_LINE_SEPARATOR_LEN ); - pRequestHeaders->headersLen = backtrackHeaderLen + toAddLen; - returnStatus = HTTP_SUCCESS; - } + /* Copy the field separator, ": ", into the buffer. */ + memcpy( pBufferCur, + HTTP_HEADER_FIELD_SEPARATOR, + HTTP_HEADER_FIELD_SEPARATOR_LEN ); + pBufferCur += HTTP_HEADER_FIELD_SEPARATOR_LEN; + + /* Copy the header value into the buffer. */ + memcpy( pBufferCur, pValue, valueLen ); + pBufferCur += valueLen; + + /* Copy the header end indicator, "\r\n\r\n" into the buffer. */ + memcpy( pBufferCur, + HTTP_HEADER_END_INDICATOR, + HTTP_HEADER_END_INDICATOR_LEN ); + + /* Update the headers length value. */ + pRequestHeaders->headersLen = backtrackHeaderLen + toAddLen; } else { @@ -376,9 +376,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const char * pField, + const uint8_t * pField, size_t fieldLen, - const char * pValue, + const uint8_t * pValue, size_t valueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -483,7 +483,8 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, RANGE_REQUEST_HEADER_VALUE_PREFIX, rangeStartOrlastNbytes ); assert( ( stdRetVal >= 0 ) && - stdRetVal <= ( int ) ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DIGITS ) ); + stdRetVal <= ( int ) ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + + MAX_INT32_NO_OF_DECIMAL_DIGITS ) ); rangeValueLength += ( size_t ) stdRetVal; /* Add remaining value data depending on the range specification type. */ @@ -498,7 +499,8 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, DASH_CHARACTER, rangeEnd ); assert( ( stdRetVal >= 0 ) && - stdRetVal <= ( DASH_CHARACTER_LEN + MAX_INT32_NO_OF_DIGITS ) ); + stdRetVal <= ( int ) ( DASH_CHARACTER_LEN + + MAX_INT32_NO_OF_DECIMAL_DIGITS ) ); rangeValueLength += ( size_t ) stdRetVal; } /* Case when request is for bytes in the range [rangeStart, ). */ @@ -520,9 +522,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Add the Range Request header field and value to the buffer. */ returnStatus = _addHeader( pRequestHeaders, - RANGE_REQUEST_HEADER_FIELD, + ( uint8_t * ) RANGE_REQUEST_HEADER_FIELD, RANGE_REQUEST_HEADER_FIELD_LEN, - rangeValueBuffer, + ( uint8_t * ) rangeValueBuffer, rangeValueLength ); } diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 4d529ef53d..ecfa627b8b 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -47,11 +47,6 @@ #define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1 ) #define HTTP_HEADER_END_INDICATOR "\r\n\r\n" #define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1 ) -#define HTTP_HEADER_ADD_FORMAT "%.*s" HTTP_HEADER_FIELD_SEPARATOR "%.*s" HTTP_HEADER_LINE_SEPARATOR -#define CARRIAGE_RETURN_CHARACTER "\r" -#define CARRIAGE_RETURN_CHARACTER_LEN ( sizeof( CARRIAGE_RETURN_CHARACTER ) - 1 ) -#define NEWLINE_CHARACTER "\n" -#define NEWLINE_CHARACTER_LEN ( sizeof( NEWLINE_CHARACTER ) - 1 ) #define HTTP_HEADER_FIELD_SEPARATOR ": " #define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1 ) #define COLON_CHARACTER ":" diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h index 5ccc0bf992..ac461d5922 100644 --- a/libraries/standard/http/test/common.h +++ b/libraries/standard/http/test/common.h @@ -1,3 +1,4 @@ +#include #include "tap.h" /* Include paths for public enums, structures, and macros. */ @@ -7,4 +8,6 @@ #include "private/http_client_internal.h" #include "private/http_client_parse.h" -#define assert( x ) +static int _assertFailureCount; +#define assertReset() do { _assertFailureCount = 0; } while( 0 ) +#define assert( x ) do { if( !(x) ) { _assertFailureCount++; } } while( 0 ) diff --git a/libraries/standard/http/test/test-HTTPClient_AddHeader.c b/libraries/standard/http/test/test-HTTPClient_AddHeader.c index 203ac71f72..7369449f07 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddHeader.c @@ -53,9 +53,9 @@ int main() size_t expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; struct Header { - char field[ HTTP_TEST_HEADER_FIELD_LEN ]; + uint8_t field[ HTTP_TEST_HEADER_FIELD_LEN ]; size_t fieldLen; - char value[ HTTP_TEST_HEADER_VALUE_LEN ]; + uint8_t value[ HTTP_TEST_HEADER_VALUE_LEN ]; size_t valueLen; } header; @@ -151,7 +151,7 @@ int main() HTTP_TEST_HEADER_REQUEST_LINE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == ( int )expectedHeaderLen ); + == ( int ) expectedHeaderLen ); /* Prefill the buffer with a request line and header. */ ok( snprintf( ( char * ) reqHeaders.pBuffer, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, diff --git a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c index 5f3f85d847..ecc1652021 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c @@ -48,7 +48,7 @@ typedef struct _headers dataLen + 1, \ "%s", \ preexistingData ); \ - ok( numBytes == dataLen ); \ + ok( numBytes == ( int ) dataLen ); \ requestHeaders.headersLen = dataLen; \ /* Fill the same data in the expected buffer as HTTPClient_AddRangeHeaders() * is not expected to change it. */ \ From b16011cd65ce6b73a32ad37d1426bc06dc8aa386 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 5 May 2020 17:13:33 -0700 Subject: [PATCH 482/844] Http misra/fix violations (#920) --- libraries/standard/http/src/http_client.c | 165 ++++++++++++------ .../standard/http/src/http_client_parse.c | 7 + .../http/src/private/http_client_internal.h | 36 ++-- .../http/src/private/http_client_parse.h | 1 - libraries/standard/http/test/Makefile | 3 +- libraries/standard/http/test/common.h | 2 +- .../test/test-HTTPClient_AddRangeHeader.c | 1 + 7 files changed, 146 insertions(+), 69 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 33d3b11df9..24a363e445 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1,6 +1,5 @@ #include #include -#include #include "http_client.h" #include "private/http_client_internal.h" @@ -108,6 +107,18 @@ static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, HTTPResponse_t * pResponse ); +/** + * @brief Converts an integer value to its ASCII representation in the passed buffer. + * + * @param[in] value The value to convert to ASCII. + * @param[out] pBuffer The buffer to store the ASCII representation of the integer. + * + * @return Returns the number of bytes written to @p pBuffer. + */ +static uint8_t _convertInt32ToAscii( int32_t value, + uint8_t * pBuffer, + size_t bufferLength ); + /** * @brief This method writes the request line (first line) of the HTTP Header * into #HTTPRequestHeaders_t.pBuffer and updates length accordingly. @@ -129,6 +140,59 @@ static HTTPStatus_t _writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ +static uint8_t _convertInt32ToAscii( int32_t value, + uint8_t * pBuffer, + size_t bufferLength ) +{ + /* As input value may be altered and MISRA C 2012 rule 17.8 prevents modification + * of parameter, a local copy of the parameter is stored. */ + uint32_t absoluteValue = 0u; + uint8_t numOfDigits = 0u; + uint8_t index = 0u; + uint8_t isNegative = 0u; + + assert( pBuffer != NULL ); + assert( bufferLength >= MAX_INT32_NO_OF_DECIMAL_DIGITS ); + ( void ) bufferLength; + + /* If the value is negative, write the '-' (minus) character to the buffer. */ + if( value < 0 ) + { + isNegative = 1u; + + *pBuffer = ( uint8_t ) '-'; + + /* Convert the value to its absolute representation. */ + absoluteValue = ( uint32_t ) ( value * -1 ); + } + else + { + /* As the input integer value is positive, store is as it-is. */ + absoluteValue = ( uint32_t ) value; + } + + /* Write the absolute integer value in reverse ASCII representation. */ + do + { + pBuffer[ isNegative + numOfDigits ] = ( uint8_t ) ( absoluteValue % 10u ) + ( uint8_t ) '0'; + numOfDigits++; + absoluteValue /= 10u; + } while( absoluteValue != 0u ); + + /* Reverse the digits in the buffer to store the correct ASCII representation + * of the value. */ + for( index = 0u; index < ( numOfDigits / 2u ); index++ ) + { + pBuffer[ isNegative + index ] ^= pBuffer[ isNegative + numOfDigits - index - 1u ]; + pBuffer[ isNegative + numOfDigits - index - 1u ] ^= pBuffer[ isNegative + index ]; + pBuffer[ isNegative + index ] ^= pBuffer[ isNegative + numOfDigits - index - 1u ]; + } + + return( isNegative + numOfDigits ); +} + +/*-----------------------------------------------------------*/ + static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pField, size_t fieldLen, @@ -137,7 +201,7 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, { HTTPStatus_t returnStatus = HTTP_SUCCESS; uint8_t * pBufferCur = pRequestHeaders->pBuffer + pRequestHeaders->headersLen; - size_t toAddLen = 0; + size_t toAddLen = 0u; size_t backtrackHeaderLen = pRequestHeaders->headersLen; assert( pRequestHeaders != NULL ); @@ -174,7 +238,7 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Copy the field separator, ": ", into the buffer. */ memcpy( pBufferCur, - HTTP_HEADER_FIELD_SEPARATOR, + ( const uint8_t * ) HTTP_HEADER_FIELD_SEPARATOR, HTTP_HEADER_FIELD_SEPARATOR_LEN ); pBufferCur += HTTP_HEADER_FIELD_SEPARATOR_LEN; @@ -184,7 +248,7 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Copy the header end indicator, "\r\n\r\n" into the buffer. */ memcpy( pBufferCur, - HTTP_HEADER_END_INDICATOR, + ( const uint8_t * ) HTTP_HEADER_END_INDICATOR, HTTP_HEADER_END_INDICATOR_LEN ); /* Update the headers length value. */ @@ -435,10 +499,8 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, int32_t rangeEnd ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - /* Extra byte allocation ( + 1 ) for NULL character when using sprintf. */ - char rangeValueBuffer[ MAX_RANGE_REQUEST_VALUE_LEN + 1 ] = { 0 }; - size_t rangeValueLength = 0; - int stdRetVal = 0; + uint8_t rangeValueBuffer[ MAX_RANGE_REQUEST_VALUE_LEN ] = { 0 }; + size_t rangeValueLength = 0u; if( pRequestHeaders == NULL ) { @@ -476,44 +538,43 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, } else { - /* Populate the buffer with the value data for the Range Request.*/ - stdRetVal = snprintf( rangeValueBuffer, - sizeof( rangeValueBuffer ), - "%s%d", - RANGE_REQUEST_HEADER_VALUE_PREFIX, - rangeStartOrlastNbytes ); - assert( ( stdRetVal >= 0 ) && - stdRetVal <= ( int ) ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + - MAX_INT32_NO_OF_DECIMAL_DIGITS ) ); - rangeValueLength += ( size_t ) stdRetVal; + /* Generate the value data for the Range Request header.*/ + + /* Write the range value prefix in the buffer. */ + memcpy( rangeValueBuffer, + RANGE_REQUEST_HEADER_VALUE_PREFIX, + RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ); + rangeValueLength += RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN; + + /* Write the range start value in the buffer. */ + rangeValueLength += _convertInt32ToAscii( rangeStartOrlastNbytes, + rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength ); /* Add remaining value data depending on the range specification type. */ /* Add rangeEnd value if request is for [rangeStart, rangeEnd] byte range */ if( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) { - /* Add the rangeEnd value to the request range .*/ - stdRetVal = snprintf( rangeValueBuffer + rangeValueLength, - sizeof( rangeValueBuffer ) - rangeValueLength, - "%s%d", - DASH_CHARACTER, - rangeEnd ); - assert( ( stdRetVal >= 0 ) && - stdRetVal <= ( int ) ( DASH_CHARACTER_LEN + - MAX_INT32_NO_OF_DECIMAL_DIGITS ) ); - rangeValueLength += ( size_t ) stdRetVal; + /* Write the "-" character to the buffer.*/ + memcpy( rangeValueBuffer + rangeValueLength, + DASH_CHARACTER, + DASH_CHARACTER_LEN ); + rangeValueLength += DASH_CHARACTER_LEN; + + /* Write the rangeEnd value of the request range to the buffer .*/ + rangeValueLength += _convertInt32ToAscii( rangeEnd, + rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength ); } - /* Case when request is for bytes in the range [rangeStart, ). */ + /* Case when request is for bytes in the range [rangeStart, EoF). */ else if( rangeStartOrlastNbytes >= 0 ) { - /* Add the "-" character.*/ - stdRetVal = snprintf( rangeValueBuffer + rangeValueLength, - sizeof( rangeValueBuffer ) - rangeValueLength, - "%s", - DASH_CHARACTER ); - /* Check that only a single character was written. */ - assert( stdRetVal == DASH_CHARACTER_LEN ); - rangeValueLength += ( size_t ) stdRetVal; + /* Write the "-" character to the buffer.*/ + memcpy( rangeValueBuffer + rangeValueLength, + DASH_CHARACTER, + DASH_CHARACTER_LEN ); + rangeValueLength += DASH_CHARACTER_LEN; } else { @@ -522,9 +583,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Add the Range Request header field and value to the buffer. */ returnStatus = _addHeader( pRequestHeaders, - ( uint8_t * ) RANGE_REQUEST_HEADER_FIELD, + ( const uint8_t * ) RANGE_REQUEST_HEADER_FIELD, RANGE_REQUEST_HEADER_FIELD_LEN, - ( uint8_t * ) rangeValueBuffer, + rangeValueBuffer, rangeValueLength ); } @@ -722,10 +783,10 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t HTTPResponse_t * pResponse ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - size_t totalReceived = 0; - size_t currentReceived = 0; + size_t totalReceived = 0u; + size_t currentReceived = 0u; HTTPParsingContext_t parsingContext = { 0 }; - uint8_t shouldRecv = 0; + uint8_t shouldRecv = 0u; assert( pTransport != NULL ); assert( pTransport->recv != NULL ); @@ -737,10 +798,10 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t if( returnStatus == HTTP_SUCCESS ) { - shouldRecv = 1; + shouldRecv = 1u; } - while( shouldRecv == 1 ) + while( shouldRecv == 1u ) { /* Receive the HTTP response data into the pResponse->pBuffer. */ returnStatus = _receiveHttpResponse( pTransport, @@ -750,7 +811,7 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t if( returnStatus == HTTP_SUCCESS ) { - if( currentReceived > 0 ) + if( currentReceived > 0u ) { totalReceived += currentReceived; /* Data is received into the buffer and must be parsed. */ @@ -763,10 +824,10 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t /* While there are no errors in the transport recv or parsing, we received * data over the transport, the response message is not finished, and * there is room in the response buffer. */ - shouldRecv = ( uint8_t ) ( ( returnStatus == HTTP_SUCCESS ) && - ( currentReceived > 0 ) && - ( parsingContext.state != HTTP_PARSING_COMPLETE ) && - ( totalReceived < pResponse->bufferLen ) ); + shouldRecv = ( ( returnStatus == HTTP_SUCCESS ) && + ( currentReceived > 0u ) && + ( parsingContext.state != HTTP_PARSING_COMPLETE ) && + ( totalReceived < pResponse->bufferLen ) ) ? 1u : 0u; } if( returnStatus == HTTP_SUCCESS ) @@ -875,6 +936,12 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, char ** pValue, size_t * valueLen ) { + /* Disable unused parameter warnings. */ + ( void ) pResponse; + ( void ) pName; + ( void ) nameLen; + ( void ) pValue; + ( void ) valueLen; return HTTP_NOT_SUPPORTED; } diff --git a/libraries/standard/http/src/http_client_parse.c b/libraries/standard/http/src/http_client_parse.c index 56dcb7edc9..ed9db88216 100644 --- a/libraries/standard/http/src/http_client_parse.c +++ b/libraries/standard/http/src/http_client_parse.c @@ -3,6 +3,9 @@ HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) { + /* Disable unused parameter warnings. */ + ( void ) pParsingContext; + ( void ) pHeaderParsingCallback; /* This function is to be implenmented. */ return HTTP_SUCCESS; } @@ -11,6 +14,10 @@ HTTPStatus_t _HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, const uint8_t * pBuffer, size_t bufferLen ) { + /* Disable unused parameter warnings. */ + ( void ) pBuffer; + ( void ) bufferLen; + /* This function is to be implemented. For now we return success. */ pParsingContext->state = HTTP_PARSING_COMPLETE; return HTTP_SUCCESS; diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index ecfa627b8b..2c9ad99c04 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -1,6 +1,8 @@ #ifndef HTTP_CLIENT_INTERNAL_H_ #define HTTP_CLIENT_INTERNAL_H_ +#include "config.h" + /** * AWS IoT Embedded C SDK optional specific logging setup. */ @@ -32,37 +34,37 @@ * @brief The HTTP protocol version of this library is HTTP/1.1. */ #define HTTP_PROTOCOL_VERSION "HTTP/1.1" -#define HTTP_PROTOCOL_VERSION_LEN ( sizeof( HTTP_PROTOCOL_VERSION ) - 1 ) +#define HTTP_PROTOCOL_VERSION_LEN ( sizeof( HTTP_PROTOCOL_VERSION ) - 1u ) /** * @brief Default value when pRequestInfo->pPath == NULL. */ #define HTTP_EMPTY_PATH "/" -#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1 ) +#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1u ) /** * @brief Consants for HTTP header formatting */ #define HTTP_HEADER_LINE_SEPARATOR "\r\n" -#define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1 ) +#define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1u ) #define HTTP_HEADER_END_INDICATOR "\r\n\r\n" -#define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1 ) +#define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1u ) #define HTTP_HEADER_FIELD_SEPARATOR ": " -#define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1 ) +#define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1u ) #define COLON_CHARACTER ":" -#define COLON_CHARACTER_LEN ( sizeof( COLON_CHARACTER ) - 1 ) +#define COLON_CHARACTER_LEN ( sizeof( COLON_CHARACTER ) - 1u ) #define SPACE_CHARACTER " " -#define SPACE_CHARACTER_LEN ( sizeof( SPACE_CHARACTER ) - 1 ) +#define SPACE_CHARACTER_LEN ( sizeof( SPACE_CHARACTER ) - 1u ) #define EQUAL_CHARACTER "=" -#define EQUAL_CHARACTER_LEN ( sizeof( EQUAL_CHARACTER ) - 1 ) +#define EQUAL_CHARACTER_LEN ( sizeof( EQUAL_CHARACTER ) - 1u ) #define DASH_CHARACTER "-" -#define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1 ) +#define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1u ) /** * @brief Constants for header fields added automatically during the request initialization. */ #define HTTP_USER_AGENT_FIELD "User-Agent" -#define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1 ) +#define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1u ) #define HTTP_HOST_FIELD "Host" #define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1 ) #define HTTP_USER_AGENT_VALUE_LEN ( sizeof( HTTP_USER_AGENT_VALUE ) - 1 ) @@ -71,30 +73,30 @@ * @brief Constants for header fields added based on flags. */ #define HTTP_CONNECTION_FIELD "Connection" -#define HTTP_CONNECTION_FIELD_LEN ( sizeof( HTTP_CONNECTION_FIELD ) - 1 ) +#define HTTP_CONNECTION_FIELD_LEN ( sizeof( HTTP_CONNECTION_FIELD ) - 1u ) #define HTTP_CONTENT_LENGTH_FIELD "Content-Length" -#define HTTP_CONTENT_LENGTH_FIELD_LEN ( sizeof( HTTP_CONTENT_LENGTH_FIELD ) - 1 ) +#define HTTP_CONTENT_LENGTH_FIELD_LEN ( sizeof( HTTP_CONTENT_LENGTH_FIELD ) - 1u ) /** * @brief Constants for header values added based on flags. */ #define HTTP_CONNECTION_KEEP_ALIVE_VALUE "keep-alive" -#define HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ( sizeof( HTTP_CONNECTION_KEEP_ALIVE_VALUE ) - 1 ) +#define HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ( sizeof( HTTP_CONNECTION_KEEP_ALIVE_VALUE ) - 1u ) #define HTTP_CONNECTION_CLOSE_VALUE "close" -#define HTTP_CONNECTION_CLOSE_VALUE_LEN ( sizeof( HTTP_CONNECTION_CLOSE_VALUE ) - 1 ) +#define HTTP_CONNECTION_CLOSE_VALUE_LEN ( sizeof( HTTP_CONNECTION_CLOSE_VALUE ) - 1u ) /** * @brief Constants relating to Range Requests. */ #define RANGE_REQUEST_HEADER_FIELD "Range" -#define RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( RANGE_REQUEST_HEADER_FIELD ) - 1 ) +#define RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( RANGE_REQUEST_HEADER_FIELD ) - 1u ) #define RANGE_REQUEST_HEADER_VALUE_PREFIX "bytes=" -#define RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ( sizeof( RANGE_REQUEST_HEADER_VALUE_PREFIX ) - 1 ) +#define RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ( sizeof( RANGE_REQUEST_HEADER_VALUE_PREFIX ) - 1u ) /* Maximum value of a 32 bit signed integer is 2,147,483,647. Used for calculating buffer space for * ASCII representation of range values. */ -#define MAX_INT32_NO_OF_DECIMAL_DIGITS 10 +#define MAX_INT32_NO_OF_DECIMAL_DIGITS 10u /* Maximum buffer space for storing a Range Request Value. * Largest size is of the form "bytes=-<" */ diff --git a/libraries/standard/http/src/private/http_client_parse.h b/libraries/standard/http/src/private/http_client_parse.h index 8af0923b70..5b537e5a09 100644 --- a/libraries/standard/http/src/private/http_client_parse.h +++ b/libraries/standard/http/src/private/http_client_parse.h @@ -2,7 +2,6 @@ #define HTTP_CLIENT_PARSE_H_ #include "http_client.h" -/* #include "http_parser.h" */ /** * @brief The state of the response message parsed after diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 3faad2cc88..3182f46185 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -29,6 +29,7 @@ INCLUDE = . \ # Values based on test source file names will be dynamically added. FUNCTIONS = _addHeader \ _writeRequestLine \ + _convertInt32ToAscii \ _sendHttpHeaders \ _sendHttpBody \ _receiveHttpResponse \ @@ -45,7 +46,7 @@ $(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/po # additional dependency for a specific test HTTPClient_AddHeader.c: _addHeader.c -HTTPClient_AddRangeHeader.c: _addHeader.c +HTTPClient_AddRangeHeader.c: _addHeader.c _convertInt32ToAscii.c HTTPClient_Send.c: _sendHttpHeaders.c \ _sendHttpBody.c \ _receiveHttpResponse.c \ diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h index ac461d5922..503aaa17ce 100644 --- a/libraries/standard/http/test/common.h +++ b/libraries/standard/http/test/common.h @@ -10,4 +10,4 @@ static int _assertFailureCount; #define assertReset() do { _assertFailureCount = 0; } while( 0 ) -#define assert( x ) do { if( !(x) ) { _assertFailureCount++; } } while( 0 ) +#define assert( x ) do { if( !( x ) ) { _assertFailureCount++; } } while( 0 ) diff --git a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c index ecc1652021..a2960e734b 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c @@ -5,6 +5,7 @@ /* Functions are pulled out into their own C files to be tested as a unit. */ #include "_addHeader.c" +#include "_convertInt32ToAscii.c" #include "HTTPClient_AddRangeHeader.c" /* Default size for request buffer. */ From 297f48dc6f9c55cd799283b57ff8d6dd8b074667 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 5 May 2020 21:22:39 -0700 Subject: [PATCH 483/844] Implement publish deserializers (#911) * Add deserializers for publish and acks * Add ACK serializer --- .../standard/mqtt/include/mqtt_lightweight.h | 60 +- .../standard/mqtt/src/mqtt_lightweight.c | 726 +++++++++++++++++- 2 files changed, 783 insertions(+), 3 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 1a93821033..6bd4a9e139 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -31,6 +31,14 @@ /* MQTT packet types. */ #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ #define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBREC ( ( uint8_t ) 0x50U ) /**< @brief PUBREC (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBREL ( ( uint8_t ) 0x62U ) /**< @brief PUBREL (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBCOMP ( ( uint8_t ) 0x70U ) /**< @brief PUBCOMP (bidirectional). */ +#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ #define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ struct MQTTFixedBuffer; @@ -76,7 +84,8 @@ typedef enum MQTTStatus MQTTSendFailed, /**< The transport send function failed. */ MQTTRecvFailed, /**< The transport receive function failed. */ MQTTBadResponse, /**< An invalid packet was received from the server. */ - MQTTServerRefused /**< The server refused a CONNECT or SUBSCRIBE. */ + MQTTServerRefused, /**< The server refused a CONNECT or SUBSCRIBE. */ + MQTTNoDataAvailable } MQTTStatus_t; /** @@ -294,6 +303,21 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli const MQTTFixedBuffer_t * const pBuffer, size_t * const pHeaderSize ); +/** + * @brief Serialize an MQTT PUBACK, PUBREC, PUBREL, or PUBCOMP into the given + * buffer. + * + * @param[out] pBuffer Buffer for packet serialization. + * @param[in] packetType Byte of the corresponding packet fixed header per the + * MQTT spec. + * @param[in] packetId Packet ID of the publish. + * + * @return #MQTTBadParameter, #MQTTNoMemory, or #MQTTSuccess. + */ +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, + uint8_t packetType, + uint16_t packetId ); + /** * @brief Get the size of an MQTT DISCONNECT packet. * @@ -319,12 +343,46 @@ MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, MQTTNetworkContext_t networkContext, MQTTPacketInfo_t * const pIncomingPacket ); +/** + * @brief Deserialize an MQTT PUBLISH packet. + * + * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer. + * @param[out] pPacketId The packet ID obtained from the buffer. + * @param[out] pPublishInfo Struct containing information about the publish. + * + * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. + */ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, MQTTPublishInfo_t * const pPublishInfo ); +/** + * @brief Deserialize an MQTT CONNACK, SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL, + * PUBCOMP, or PINGRESP. + * + * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer. + * @param[out] pPacketId The packet ID of obtained from the buffer. Not used + * in CONNACK or PINGRESP. + * @param[out] pSessionPresent Boolean flag from a CONNACK indicating present session. + * + * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. + */ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, bool * const pSessionPresent ); +/** + * @brief Extract MQTT packet type and length from incoming packet. + * + * @param[in] readFunc Transport layer read function pointer. + * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. + * where type, remaining length and packet identifier are stored. + * + * @return #MQTTSuccess on successful extraction of type and length, + * #MQTTBadResponse on failure and #MQTTNoDataAvailable if there is nothing to read. + */ +MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, + MQTTNetworkContext_t networkContext, + MQTTPacketInfo_t * pIncomingPacket ); + #endif diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index dfc8be8550..5fd5d271fc 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -20,8 +20,10 @@ */ #include +#include #include "mqtt_lightweight.h" +#include "private/mqtt_internal.h" /** * @brief MQTT protocol version 3.1.1. @@ -47,6 +49,15 @@ #define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ #define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ +/* + * Positions of each flag in the first byte of an MQTT PUBLISH packet's + * fixed header. + */ +#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ +#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ +#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ + /** * @brief The size of MQTT DISCONNECT packets, per MQTT spec. */ @@ -57,11 +68,37 @@ */ #define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) +/* + * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ + +/** + * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. + */ +#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4U ) + +/* + * UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP always have a remaining length + * of 2. + */ +#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ + /** * @brief Set a bit in an 8-bit unsigned integer. */ #define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) +/** + * @brief Macro for checking if a bit is set in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to check. + * @param[in] position Which bit to check. + */ +#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + /** * @brief Get the high byte of a 16-bit unsigned integer. */ @@ -72,6 +109,29 @@ */ #define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) +/** + * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. + * + * @param[in] ptr A uint8_t* that points to the high byte. + */ +#define UINT16_DECODE( ptr ) \ + ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ + ( ( uint16_t ) ( *( ( ptr ) + 1 ) ) ) ) + +/** + * @brief A value that represents an invalid remaining length. + * + * This value is greater than what is allowed by the MQTT specification. + */ +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) + +/** + * @brief The minimum remaining length for a QoS 0 PUBLISH. + * + * Includes two bytes for topic name length and one byte for topic name. + */ +#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -192,6 +252,517 @@ static int32_t recvExact( MQTTTransportRecvFunc_t recvFunc, /*-----------------------------------------------------------*/ +static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, + MQTTNetworkContext_t networkContext ) +{ + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; + uint8_t encodedByte = 0; + int32_t bytesReceived = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152U ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + else + { + bytesReceived = recvFunc( networkContext, &encodedByte, 1U ); + if( bytesReceived == 1 ) + { + remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; + multiplier *= 128U; + bytesDecoded++; + } + else + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + } + if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + break; + } + } while ( ( encodedByte & 0x80U ) != 0U ); + + /* Check that the decoded remaining length conforms to the MQTT specification. */ + if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) + { + expectedSize = remainingLengthEncodedSize( remainingLength ); + if( bytesDecoded != expectedSize ) + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + } + + return remainingLength; +} + +/*-----------------------------------------------------------*/ + +static bool incomingPacketValid( uint8_t packetType ) +{ + bool status = false; + + /* Check packet type. Mask out lower bits to ignore flags. */ + switch( packetType & 0xF0U ) + { + /* Valid incoming packet types. */ + case MQTT_PACKET_TYPE_CONNACK: + case MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBREC: + case MQTT_PACKET_TYPE_PUBCOMP: + case MQTT_PACKET_TYPE_SUBACK: + case MQTT_PACKET_TYPE_UNSUBACK: + case MQTT_PACKET_TYPE_PINGRESP: + status = true; + break; + + case ( MQTT_PACKET_TYPE_PUBREL & 0xF0U ): + /* The second bit of a PUBREL must be set. */ + if( packetType & 0x02U ) + { + status = true; + } + break; + + /* Any other packet type is invalid. */ + default: + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, + MQTTQoS_t qos, + size_t qos0Minimum ) +{ + MQTTStatus_t status = MQTTSuccess; + + /* Sanity checks for "Remaining length". */ + if( qos == MQTTQoS0 ) + { + /* Check that the "Remaining length" is greater than the minimum. */ + if( remainingLength < qos0Minimum ) + { + IotLogDebugWithArgs( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum ); + + status = MQTTBadResponse; + } + } + else + { + /* Check that the "Remaining length" is greater than the minimum. For + * QoS 1 or 2, this will be two bytes greater than for QoS 0 due to the + * packet identifier. */ + if( remainingLength < ( qos0Minimum + 2U ) ) + { + IotLogDebugWithArgs( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum + 2U ); + + status = MQTTBadResponse; + } + } + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t processPublishFlags( uint8_t publishFlags, + MQTTPublishInfo_t * const pPublishInfo ) +{ + MQTTStatus_t status = MQTTSuccess; + + assert( pPublishInfo != NULL ); + + /* Check for QoS 2. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) ) + { + /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) ) + { + IotLogDebug( "Bad QoS: 3." ); + + status = MQTTBadResponse; + } + else + { + pPublishInfo->qos = MQTTQoS2; + } + } + /* Check for QoS 1. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) ) + { + pPublishInfo->qos = MQTTQoS1; + } + /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ + else + { + pPublishInfo->qos = MQTTQoS0; + } + + if( status == MQTTSuccess ) + { + IotLogDebugWithArgs( "QoS is %d.", pPublishInfo->qos ); + + /* Parse the Retain bit. */ + pPublishInfo->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + + IotLogDebugWithArgs( "Retain bit is %d.", pPublishInfo->retain ); + + /* Parse the DUP bit. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) ) + { + IotLogDebug( "DUP is 1." ); + } + else + { + IotLogDebug( "DUP is 0." ); + } + } + return status; +} + +/*-----------------------------------------------------------*/ + +static void logConnackResponse( uint8_t responseCode ) +{ + assert( responseCode <= 5 ); + /* Declare the CONNACK response code strings. The fourth byte of CONNACK + * indexes into this array for the corresponding response. This array + * does not need to be allocated if logs are not enabled. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + static const char * const pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; + IotLogErrorWithArgs( "%s", pConnackResponses[ responseCode ] ); + #endif +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, + bool * const pSessionPresent ) +{ + MQTTStatus_t status = MQTTSuccess; + + assert( pConnack != NULL ); + assert( pSessionPresent != NULL ); + const uint8_t * pRemainingData = pConnack->pRemainingData; + + /* According to MQTT 3.1.1, the second byte of CONNACK must specify a + * "Remaining length" of 2. */ + if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + { + IotLogErrorWithArgs( "CONNACK does not have remaining length of %d.", + MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + + status = MQTTBadResponse; + } + + /* Check the reserved bits in CONNACK. The high 7 bits of the second byte + * in CONNACK must be 0. */ + else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) + { + IotLogError( "Reserved bits in CONNACK incorrect." ); + + status = MQTTBadResponse; + } + else + { + /* Determine if the "Session Present" bit is set. This is the lowest bit of + * the second byte in CONNACK. */ + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + IotLogWarn( "CONNACK session present bit set." ); + *pSessionPresent = true; + + /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pRemainingData[ 1 ] != 0U ) + { + status = MQTTBadResponse; + } + } + else + { + IotLogInfo( "CONNACK session present bit not set." ); + } + } + + if( status == MQTTSuccess ) + { + /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ + if( pRemainingData[ 1 ] > 5U ) + { + IotLogErrorWithArgs( "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ); + + status = MQTTBadResponse; + } + else + { + /* Print the appropriate message for the CONNACK response code if logs are + * enabled. */ + logConnackResponse( pRemainingData[ 1 ] ); + + /* A nonzero CONNACK response code means the connection was refused. */ + if( pRemainingData[ 1 ] > 0U ) + { + status = MQTTServerRefused; + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t readSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ) +{ + MQTTStatus_t status = MQTTSuccess; + uint8_t subscriptionStatus = 0; + size_t i = 0; + + assert( pStatusStart != NULL ); + + /* Iterate through each status byte in the SUBACK packet. */ + for( i = 0; i < statusCount; i++ ) + { + /* Read a single status byte in SUBACK. */ + subscriptionStatus = pStatusStart[ i ]; + + /* MQTT 3.1.1 defines the following values as status codes. */ + switch( subscriptionStatus ) + { + case 0x00: + case 0x01: + case 0x02: + + IotLogDebugWithArgs( "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); + break; + + case 0x80: + + IotLogDebugWithArgs( "Topic filter %lu refused.", ( unsigned long ) i ); + + /* Application should remove subscription from the list */ + status = MQTTServerRefused; + + break; + + default: + IotLogDebugWithArgs( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + status = MQTTBadResponse; + + break; + } + + /* Stop parsing the subscription statuses if a bad response was received. */ + if( status == MQTTBadResponse ) + { + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, + uint16_t * pPacketIdentifier ) +{ + MQTTStatus_t status = MQTTSuccess; + + assert( pSuback != NULL ); + assert( pPacketIdentifier != NULL ); + + size_t remainingLength = pSuback->remainingLength; + const uint8_t * pVariableHeader = pSuback->pRemainingData; + + /* A SUBACK must have a remaining length of at least 3 to accommodate the + * packet identifier and at least 1 return code. */ + if( remainingLength < 3U ) + { + IotLogDebug( "SUBACK cannot have a remaining length less than 3." ); + status = MQTTBadResponse; + } + else + { + /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ + *pPacketIdentifier = UINT16_DECODE( pVariableHeader ); + + IotLogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); + + status = readSubackStatus( remainingLength - sizeof( uint16_t ), + pVariableHeader + sizeof( uint16_t ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, + uint16_t * const pPacketId, + MQTTPublishInfo_t * const pPublishInfo ) +{ + MQTTStatus_t status = MQTTSuccess; + + assert( pIncomingPacket != NULL ); + assert( pPacketId != NULL ); + assert( pPublishInfo != NULL ); + const uint8_t * pVariableHeader = pIncomingPacket->pRemainingData, * pPacketIdentifierHigh; + /* The flags are the lower 4 bits of the first byte in PUBLISH. */ + status = processPublishFlags( ( pIncomingPacket->type & 0x0FU ), pPublishInfo ); + + if( status == MQTTSuccess ) + { + /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining + * length of at least 3 to accommodate topic name length (2 bytes) and topic + * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of + * at least 5 for the packet identifier in addition to the topic name length and + * topic name. */ + status = checkPublishRemainingLength( pIncomingPacket->remainingLength, + pPublishInfo->qos, + MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ); + } + + if( status == MQTTSuccess ) + { + /* Extract the topic name starting from the first byte of the variable header. + * The topic name string starts at byte 3 in the variable header. */ + pPublishInfo->topicNameLength = UINT16_DECODE( pVariableHeader ); + + /* Sanity checks for topic name length and "Remaining length". The remaining + * length must be at least as large as the variable length header. */ + status = checkPublishRemainingLength( pIncomingPacket->remainingLength, + pPublishInfo->qos, + pPublishInfo->topicNameLength + sizeof( uint16_t ) ); + } + + if( status == MQTTSuccess ) + { + /* Parse the topic. */ + pPublishInfo->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + IotLogDebugWithArgs( "Topic name length %hu: %.*s", + pPublishInfo->topicNameLength, + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ); + + /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet + * identifier starts immediately after the topic name. */ + pPacketIdentifierHigh = ( const uint8_t * ) ( pPublishInfo->pTopicName + pPublishInfo->topicNameLength ); + + if( pPublishInfo->qos > MQTTQoS0 ) + { + *pPacketId = UINT16_DECODE( pPacketIdentifierHigh ); + + IotLogDebugWithArgs( "Packet identifier %hu.", *pPacketId ); + + /* Packet identifier cannot be 0. */ + if( *pPacketId == 0U ) + { + status = MQTTBadResponse; + } + } + } + + if( status == MQTTSuccess ) + { + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain + * a packet identifier, but QoS 0 PUBLISH packets do not. */ + if( pPublishInfo->qos == MQTTQoS0 ) + { + pPublishInfo->payloadLength = ( pIncomingPacket->remainingLength - pPublishInfo->topicNameLength - sizeof( uint16_t ) ); + pPublishInfo->pPayload = pPacketIdentifierHigh; + } + else + { + pPublishInfo->payloadLength = ( pIncomingPacket->remainingLength - pPublishInfo->topicNameLength - 2U * sizeof( uint16_t ) ); + pPublishInfo->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); + } + + IotLogDebugWithArgs( "Payload length %hu.", pPublishInfo->payloadLength ); + } + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, + uint16_t * pPacketIdentifier ) +{ + MQTTStatus_t status = MQTTSuccess; + + assert( pAck != NULL ); + assert( pPacketIdentifier != NULL ); + + /* Check that the "Remaining length" of the received ACK is 2. */ + if( pAck->remainingLength != MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ) + { + IotLogErrorWithArgs( "ACK does not have remaining length of %d.", + MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ); + + status = MQTTBadResponse; + } + else + { + /* Extract the packet identifier (third and fourth bytes) from ACK. */ + *pPacketIdentifier = UINT16_DECODE( pAck->pRemainingData ); + + IotLogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); + + /* Packet identifier cannot be 0. */ + if( *pPacketIdentifier == 0U ) + { + status = MQTTBadResponse; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * const pPingresp ) +{ + MQTTStatus_t status = MQTTSuccess; + + assert( pPingresp != NULL ); + + /* Check the "Remaining length" (second byte) of the received PINGRESP is 0. */ + if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + { + IotLogErrorWithArgs( "PINGRESP does not have remaining length of %d.", + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + + status = MQTTBadResponse; + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, size_t * const pRemainingLength, @@ -426,6 +997,50 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli /*-----------------------------------------------------------*/ +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, + uint8_t packetType, + uint16_t packetId ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pBuffer == NULL ) + { + IotLogError( "Provided buffer is NULL." ); + status = MQTTBadParameter; + } + /* The buffer must be able to fit 4 bytes for the packet. */ + else if( pBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) + { + IotLogError( "Insufficient memory for packet." ); + status = MQTTNoMemory; + } + else + { + switch( packetType ) + { + /* Only publish acks are serialized by the client. */ + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBREC: + case MQTT_PACKET_TYPE_PUBREL: + case MQTT_PACKET_TYPE_PUBCOMP: + pBuffer->pBuffer[0] = packetType; + pBuffer->pBuffer[1] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; + pBuffer->pBuffer[2] = UINT16_HIGH_BYTE( packetId ); + pBuffer->pBuffer[3] = UINT16_LOW_BYTE( packetId ); + break; + default: + IotLogErrorWithArgs( "Packet type is not a publish ACK: Packet type=%02x", + packetType ); + status = MQTTBadParameter; + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) { /* MQTT DISCONNECT packets always have the same size. */ @@ -482,7 +1097,29 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa uint16_t * const pPacketId, MQTTPublishInfo_t * const pPublishInfo ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + + if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " + "pPacketId=%p, pPublishInfo=%p", + pIncomingPacket, + pPacketId, + pPublishInfo ); + status = MQTTBadParameter; + } + else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) + { + IotLogErrorWithArgs( "Packet is not publish. Packet type: %hu.", + pIncomingPacket->type ); + status = MQTTBadParameter; + } + else + { + status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); + } + + return status; } /*-----------------------------------------------------------*/ @@ -491,8 +1128,93 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket uint16_t * const pPacketId, bool * const pSessionPresent ) { + MQTTStatus_t status = MQTTSuccess; - return MQTTSuccess; + if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pSessionPresent == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " + "pPacketId=%p, pSessionPresent=%p", + pIncomingPacket, + pPacketId, + pSessionPresent ); + status = MQTTBadParameter; + } + else if( pIncomingPacket->pRemainingData == NULL ) + { + IotLogError( "Remaining data of incoming packet is NULL." ); + status = MQTTBadParameter; + } + else + { + /* Make sure response packet is a valid ack. */ + switch( pIncomingPacket->type ) + { + case MQTT_PACKET_TYPE_CONNACK: + status = deserializeConnack( pIncomingPacket, pSessionPresent ); + break; + + case MQTT_PACKET_TYPE_SUBACK: + status = deserializeSuback( pIncomingPacket, pPacketId ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + status = deserializePingresp( pIncomingPacket ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBREC: + case MQTT_PACKET_TYPE_PUBREL: + case MQTT_PACKET_TYPE_PUBCOMP: + status = deserializeSimpleAck( pIncomingPacket, pPacketId ); + break; + + /* Any other packet type is invalid. */ + default: + IotLogErrorWithArgs( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ); + status = MQTTBadResponse; + break; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, + MQTTNetworkContext_t networkContext, + MQTTPacketInfo_t * pIncomingPacket ) +{ + MQTTStatus_t status = MQTTSuccess; + /* Read a single byte. */ + int32_t bytesReceived = readFunc( networkContext, &( pIncomingPacket->type ), 1U ); + if( bytesReceived == 1 ) + { + /* Check validity. */ + if( incomingPacketValid( pIncomingPacket->type ) ) + { + pIncomingPacket->remainingLength = getRemainingLength( readFunc, networkContext ); + + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + status = MQTTBadResponse; + } + } + else + { + IotLogErrorWithArgs( "Incoming packet invalid: Packet type=%u", + pIncomingPacket->type ); + status = MQTTBadResponse; + } + + } + else + { + status = MQTTNoDataAvailable; + } + + return status; } /*-----------------------------------------------------------*/ From 31afc9792e517ef069a94ccf616ab7bf2dc8606c Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 6 May 2020 10:52:57 -0700 Subject: [PATCH 484/844] Remove use of underscores from internal functions (#922) --- libraries/standard/http/src/http_client.c | 228 +++++++++--------- .../standard/http/src/http_client_parse.c | 10 +- .../http/src/private/http_client_parse.h | 10 +- libraries/standard/http/test/Makefile | 40 +-- .../http/test/test-HTTPClient_AddHeader.c | 2 +- .../test/test-HTTPClient_AddRangeHeader.c | 4 +- ...test-HTTPClient_InitializeRequestHeaders.c | 4 +- .../standard/http/test/test-HTTPClient_Send.c | 14 +- 8 files changed, 156 insertions(+), 156 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 24a363e445..4ae2009ff1 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -17,8 +17,8 @@ * @return #HTTP_SUCCESS if successful. If there was a network error or less * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. */ -static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTransport, - const HTTPRequestHeaders_t * pRequestHeaders ); +static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, + const HTTPRequestHeaders_t * pRequestHeaders ); /** * @brief Send the HTTP body over the transport send interface. @@ -30,9 +30,9 @@ static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTranspor * @return #HTTP_SUCCESS if successful. If there was a network error or less * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. */ -static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, - const uint8_t * pRequestBodyBuf, - size_t reqBodyBufLen ); +static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, + const uint8_t * pRequestBodyBuf, + size_t reqBodyBufLen ); /** * @brief Write header based on parameters. This method also adds a trailing "\r\n". @@ -48,11 +48,11 @@ static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. */ -static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, - const uint8_t * pField, - size_t fieldLen, - const uint8_t * pValue, - size_t valueLen ); +static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, + const uint8_t * pField, + size_t fieldLen, + const uint8_t * pValue, + size_t valueLen ); /** * @brief Receive HTTP response from the transport recv interface. @@ -66,10 +66,10 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, * more bytes than what was specified were read, then #HTTP_NETWORK_ERROR is * returned. */ -HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, - uint8_t * pBuffer, - size_t bufferLen, - size_t * pBytesReceived ); +HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ); /** * @brief Get the status of the HTTP response given the parsing state and how @@ -86,9 +86,9 @@ HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, * #HTTP_PARTIAL_RESPONSE is returned. If the parsing state is incomplete, then * if the response buffer is full #HTTP_INSUFFICIENT_MEMORY is returned. */ -static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, - size_t totalReceived, - size_t responseBufferLen ); +static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, + size_t totalReceived, + size_t responseBufferLen ); /** * @brief Receive the HTTP response from the network and parse it. @@ -98,14 +98,14 @@ static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, * * @return Returns #HTTP_SUCCESS if successful. If there was an issue with receiving * the response over the network interface, then #HTTP_NETWORK_ERROR is returned, - * please see #_receiveHttpResponse. If there was an issue with parsing, then the + * please see #receiveHttpResponse. If there was an issue with parsing, then the * parsing error that occurred will be returned, please see - * #_HTTPClient_InitializeParsingContext and #_HTTPClient_ParseResponse. Please - * see #_getFinalResponseStatus for the status returned when there were no + * #HTTPClient_InitializeParsingContext and HTTPClient_ParseResponse. Please + * see #getFinalResponseStatus for the status returned when there were no * network or parsing errors. */ -static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, - HTTPResponse_t * pResponse ); +static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, + HTTPResponse_t * pResponse ); /** * @brief Converts an integer value to its ASCII representation in the passed buffer. @@ -115,9 +115,9 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * * @return Returns the number of bytes written to @p pBuffer. */ -static uint8_t _convertInt32ToAscii( int32_t value, - uint8_t * pBuffer, - size_t bufferLength ); +static uint8_t convertInt32ToAscii( int32_t value, + uint8_t * pBuffer, + size_t bufferLength ); /** * @brief This method writes the request line (first line) of the HTTP Header @@ -132,17 +132,17 @@ static uint8_t _convertInt32ToAscii( int32_t value, * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. */ -static HTTPStatus_t _writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, - const char * pMethod, - size_t methodLen, - const char * pPath, - size_t pathLen ); +static HTTPStatus_t_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ); /*-----------------------------------------------------------*/ -static uint8_t _convertInt32ToAscii( int32_t value, - uint8_t * pBuffer, - size_t bufferLength ) +static uint8_t convertInt32ToAscii( int32_t value, + uint8_t * pBuffer, + size_t bufferLength ) { /* As input value may be altered and MISRA C 2012 rule 17.8 prevents modification * of parameter, a local copy of the parameter is stored. */ @@ -193,11 +193,11 @@ static uint8_t _convertInt32ToAscii( int32_t value, /*-----------------------------------------------------------*/ -static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, - const uint8_t * pField, - size_t fieldLen, - const uint8_t * pValue, - size_t valueLen ) +static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, + const uint8_t * pField, + size_t fieldLen, + const uint8_t * pValue, + size_t valueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; uint8_t * pBufferCur = pRequestHeaders->pBuffer + pRequestHeaders->headersLen; @@ -269,11 +269,11 @@ static HTTPStatus_t _addHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ -static HTTPStatus_t _writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, - const char * pMethod, - size_t methodLen, - const char * pPath, - size_t pathLen ) +static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; uint8_t * pBufferCur = pRequestHeaders->pBuffer; @@ -385,31 +385,31 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques pRequestHeaders->headersLen = 0; /* Write " HTTP/1.1\r\n" to start the HTTP header. */ - returnStatus = _writeRequestLine( pRequestHeaders, - pRequestInfo->method, - pRequestInfo->methodLen, - pRequestInfo->pPath, - pRequestInfo->pathLen ); + returnStatus = writeRequestLine( pRequestHeaders, + pRequestInfo->method, + pRequestInfo->methodLen, + pRequestInfo->pPath, + pRequestInfo->pathLen ); } if( returnStatus == HTTP_SUCCESS ) { /* Write "User-Agent: ". */ - returnStatus = _addHeader( pRequestHeaders, - HTTP_USER_AGENT_FIELD, - HTTP_USER_AGENT_FIELD_LEN, - HTTP_USER_AGENT_VALUE, - HTTP_USER_AGENT_VALUE_LEN ); + returnStatus = addHeader( pRequestHeaders, + HTTP_USER_AGENT_FIELD, + HTTP_USER_AGENT_FIELD_LEN, + HTTP_USER_AGENT_VALUE, + HTTP_USER_AGENT_VALUE_LEN ); } if( returnStatus == HTTP_SUCCESS ) { /* Write "Host: ". */ - returnStatus = _addHeader( pRequestHeaders, - HTTP_HOST_FIELD, - HTTP_HOST_FIELD_LEN, - pRequestInfo->pHost, - pRequestInfo->hostLen ); + returnStatus = addHeader( pRequestHeaders, + HTTP_HOST_FIELD, + HTTP_HOST_FIELD_LEN, + pRequestInfo->pHost, + pRequestInfo->hostLen ); } if( returnStatus == HTTP_SUCCESS ) @@ -417,20 +417,20 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques if( HTTP_REQUEST_KEEP_ALIVE_FLAG & pRequestInfo->flags ) { /* Write "Connection: keep-alive". */ - returnStatus = _addHeader( pRequestHeaders, - HTTP_CONNECTION_FIELD, - HTTP_CONNECTION_FIELD_LEN, - HTTP_CONNECTION_KEEP_ALIVE_VALUE, - HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ); + returnStatus = addHeader( pRequestHeaders, + HTTP_CONNECTION_FIELD, + HTTP_CONNECTION_FIELD_LEN, + HTTP_CONNECTION_KEEP_ALIVE_VALUE, + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ); } else { /* Write "Connection: close". */ - returnStatus = _addHeader( pRequestHeaders, - HTTP_CONNECTION_FIELD, - HTTP_CONNECTION_FIELD_LEN, - HTTP_CONNECTION_CLOSE_VALUE, - HTTP_CONNECTION_CLOSE_VALUE_LEN ); + returnStatus = addHeader( pRequestHeaders, + HTTP_CONNECTION_FIELD, + HTTP_CONNECTION_FIELD_LEN, + HTTP_CONNECTION_CLOSE_VALUE, + HTTP_CONNECTION_CLOSE_VALUE_LEN ); } } @@ -485,8 +485,8 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, if( returnStatus == HTTP_SUCCESS ) { - returnStatus = _addHeader( pRequestHeaders, - pField, fieldLen, pValue, valueLen ); + returnStatus = addHeader( pRequestHeaders, + pField, fieldLen, pValue, valueLen ); } return returnStatus; @@ -547,9 +547,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, rangeValueLength += RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN; /* Write the range start value in the buffer. */ - rangeValueLength += _convertInt32ToAscii( rangeStartOrlastNbytes, - rangeValueBuffer + rangeValueLength, - sizeof( rangeValueBuffer ) - rangeValueLength ); + rangeValueLength += convertInt32ToAscii( rangeStartOrlastNbytes, + rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength ); /* Add remaining value data depending on the range specification type. */ @@ -563,9 +563,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, rangeValueLength += DASH_CHARACTER_LEN; /* Write the rangeEnd value of the request range to the buffer .*/ - rangeValueLength += _convertInt32ToAscii( rangeEnd, - rangeValueBuffer + rangeValueLength, - sizeof( rangeValueBuffer ) - rangeValueLength ); + rangeValueLength += convertInt32ToAscii( rangeEnd, + rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength ); } /* Case when request is for bytes in the range [rangeStart, EoF). */ else if( rangeStartOrlastNbytes >= 0 ) @@ -582,11 +582,11 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, } /* Add the Range Request header field and value to the buffer. */ - returnStatus = _addHeader( pRequestHeaders, - ( const uint8_t * ) RANGE_REQUEST_HEADER_FIELD, - RANGE_REQUEST_HEADER_FIELD_LEN, - rangeValueBuffer, - rangeValueLength ); + returnStatus = addHeader( pRequestHeaders, + ( const uint8_t * ) RANGE_REQUEST_HEADER_FIELD, + RANGE_REQUEST_HEADER_FIELD_LEN, + rangeValueBuffer, + rangeValueLength ); } return returnStatus; @@ -594,8 +594,8 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ -static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTransport, - const HTTPRequestHeaders_t * pRequestHeaders ) +static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, + const HTTPRequestHeaders_t * pRequestHeaders ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; int32_t transportStatus = 0; @@ -637,9 +637,9 @@ static HTTPStatus_t _sendHttpHeaders( const HTTPTransportInterface_t * pTranspor /*-----------------------------------------------------------*/ -static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, - const uint8_t * pRequestBodyBuf, - size_t reqBodyBufLen ) +static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, + const uint8_t * pRequestBodyBuf, + size_t reqBodyBufLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; int32_t transportStatus = 0; @@ -679,10 +679,10 @@ static HTTPStatus_t _sendHttpBody( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, - uint8_t * pBuffer, - size_t bufferLen, - size_t * pBytesReceived ) +HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -734,9 +734,9 @@ HTTPStatus_t _receiveHttpResponse( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, - size_t totalReceived, - size_t responseBufferLen ) +static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, + size_t totalReceived, + size_t responseBufferLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -779,8 +779,8 @@ static HTTPStatus_t _getFinalResponseStatus( HTTPParsingState_t parsingState, return returnStatus; } -static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, - HTTPResponse_t * pResponse ) +static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, + HTTPResponse_t * pResponse ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; size_t totalReceived = 0u; @@ -793,8 +793,8 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t assert( pResponse != NULL ); /* Initialize the parsing context. */ - returnStatus = _HTTPClient_InitializeParsingContext( &parsingContext, - pResponse->pHeaderParsingCallback ); + returnStatus = HTTPClient_InitializeParsingContext( &parsingContext, + pResponse->pHeaderParsingCallback ); if( returnStatus == HTTP_SUCCESS ) { @@ -804,10 +804,10 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t while( shouldRecv == 1u ) { /* Receive the HTTP response data into the pResponse->pBuffer. */ - returnStatus = _receiveHttpResponse( pTransport, - pResponse->pBuffer + totalReceived, - pResponse->bufferLen - totalReceived, - ¤tReceived ); + returnStatus = receiveHttpResponse( pTransport, + pResponse->pBuffer + totalReceived, + pResponse->bufferLen - totalReceived, + ¤tReceived ); if( returnStatus == HTTP_SUCCESS ) { @@ -815,9 +815,9 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t { totalReceived += currentReceived; /* Data is received into the buffer and must be parsed. */ - returnStatus = _HTTPClient_ParseResponse( &parsingContext, - pResponse->pBuffer + totalReceived, - currentReceived ); + returnStatus = HTTPClient_ParseResponse( &parsingContext, + pResponse->pBuffer + totalReceived, + currentReceived ); } } @@ -835,9 +835,9 @@ static HTTPStatus_t _receiveAndParseHttpResponse( const HTTPTransportInterface_t /* For no network or parsing errors, the final status of the response * message is derived from the state of the parsing and how much data * is in the buffer. */ - returnStatus = _getFinalResponseStatus( parsingContext.state, - totalReceived, - pResponse->bufferLen ); + returnStatus = getFinalResponseStatus( parsingContext.state, + totalReceived, + pResponse->bufferLen ); } return returnStatus; @@ -891,8 +891,8 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /* Send the headers, which are at one location in memory. */ if( returnStatus == HTTP_SUCCESS ) { - returnStatus = _sendHttpHeaders( pTransport, - pRequestHeaders ); + returnStatus = sendHttpHeaders( pTransport, + pRequestHeaders ); } /* Send the body, which is at another location in memory. */ @@ -900,9 +900,9 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, { if( pRequestBodyBuf != NULL ) { - returnStatus = _sendHttpBody( pTransport, - pRequestBodyBuf, - reqBodyBufLen ); + returnStatus = sendHttpBody( pTransport, + pRequestBodyBuf, + reqBodyBufLen ); } else { @@ -916,8 +916,8 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, * will not be NULL. */ if( pResponse != NULL ) { - returnStatus = _receiveAndParseHttpResponse( pTransport, - pResponse ); + returnStatus = receiveAndParseHttpResponse( pTransport, + pResponse ); } else { diff --git a/libraries/standard/http/src/http_client_parse.c b/libraries/standard/http/src/http_client_parse.c index ed9db88216..83085329ee 100644 --- a/libraries/standard/http/src/http_client_parse.c +++ b/libraries/standard/http/src/http_client_parse.c @@ -1,7 +1,7 @@ #include "private/http_client_parse.h" -HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) +HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) { /* Disable unused parameter warnings. */ ( void ) pParsingContext; @@ -10,9 +10,9 @@ HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsi return HTTP_SUCCESS; } -HTTPStatus_t _HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen ) +HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen ) { /* Disable unused parameter warnings. */ ( void ) pBuffer; diff --git a/libraries/standard/http/src/private/http_client_parse.h b/libraries/standard/http/src/private/http_client_parse.h index 5b537e5a09..4e45991386 100644 --- a/libraries/standard/http/src/private/http_client_parse.h +++ b/libraries/standard/http/src/private/http_client_parse.h @@ -40,8 +40,8 @@ typedef struct HTTPParsingContext * - #HTTP_INVALID_PARAMETER * TODO: Other return values will be added during implementation of the parsing. */ -HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ); +HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ); /** * Parse the input HTTP response buffer. @@ -64,8 +64,8 @@ HTTPStatus_t _HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsi * - #HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER * TODO: Other return values are to be added during implementation of parsing. */ -HTTPStatus_t _HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen ); +HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen ); #endif /* ifndef HTTP_CLIENT_PARSE_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 3182f46185..2eab0a96c7 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -27,16 +27,16 @@ INCLUDE = . \ # functions to dump into separate source files # Values based on test source file names will be dynamically added. -FUNCTIONS = _addHeader \ - _writeRequestLine \ - _convertInt32ToAscii \ - _sendHttpHeaders \ - _sendHttpBody \ - _receiveHttpResponse \ - _receiveAndParseHttpResponse \ - _HTTPClient_InitializeParsingContext \ - _HTTPClient_ParseResponse \ - _getFinalResponseStatus +FUNCTIONS = addHeader \ + writeRequestLine \ + convertInt32ToAscii \ + sendHttpHeaders \ + sendHttpBody \ + receiveHttpResponse \ + receiveAndParseHttpResponse \ + HTTPClient_InitializeParsingContext \ + HTTPClient_ParseResponse \ + getFinalResponseStatus # Changes to the above variables should remain above this include. include Mock4thewin.mk @@ -45,16 +45,16 @@ include Mock4thewin.mk $(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/posix/iot_logging.o # additional dependency for a specific test -HTTPClient_AddHeader.c: _addHeader.c -HTTPClient_AddRangeHeader.c: _addHeader.c _convertInt32ToAscii.c -HTTPClient_Send.c: _sendHttpHeaders.c \ - _sendHttpBody.c \ - _receiveHttpResponse.c \ - _receiveAndParseHttpResponse.c \ - _HTTPClient_InitializeParsingContext.c \ - _HTTPClient_ParseResponse.c \ - _getFinalResponseStatus.c -HTTPClient_InitializeRequestHeaders.c: _writeRequestLine.c _addHeader.c +HTTPClient_AddHeader.c: addHeader.c +HTTPClient_AddRangeHeader.c: addHeader.c convertInt32ToAscii.c +HTTPClient_Send.c: sendHttpHeaders.c \ + sendHttpBody.c \ + receiveHttpResponse.c \ + receiveAndParseHttpResponse.c \ + HTTPClient_InitializeParsingContext.c \ + HTTPClient_ParseResponse.c \ + getFinalResponseStatus.c +HTTPClient_InitializeRequestHeaders.c: writeRequestLine.c addHeader.c # additional header dependencies for all tests COMMON = common.h diff --git a/libraries/standard/http/test/test-HTTPClient_AddHeader.c b/libraries/standard/http/test/test-HTTPClient_AddHeader.c index 7369449f07..f58dbdb651 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddHeader.c @@ -3,7 +3,7 @@ #include "common.h" /* Functions are pulled out into their own C files to be tested as a unit. */ -#include "_addHeader.c" +#include "addHeader.c" #include "HTTPClient_AddHeader.c" /* Template HTTP header fields and values. */ diff --git a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c index a2960e734b..d9cfeb5198 100644 --- a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c @@ -4,8 +4,8 @@ #include "common.h" /* Functions are pulled out into their own C files to be tested as a unit. */ -#include "_addHeader.c" -#include "_convertInt32ToAscii.c" +#include "addHeader.c" +#include "convertInt32ToAscii.c" #include "HTTPClient_AddRangeHeader.c" /* Default size for request buffer. */ diff --git a/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c b/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c index bb6fa845e8..398dc7776f 100644 --- a/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c +++ b/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c @@ -3,8 +3,8 @@ #include "common.h" /* Functions are pulled out into their own C files to be tested as a unit. */ -#include "_writeRequestLine.c" -#include "_addHeader.c" +#include "writeRequestLine.c" +#include "addHeader.c" #include "HTTPClient_InitializeRequestHeaders.c" #define HTTP_TEST_REQUEST_METHOD "GET" diff --git a/libraries/standard/http/test/test-HTTPClient_Send.c b/libraries/standard/http/test/test-HTTPClient_Send.c index 3f20d58ecf..8b3b4df6a8 100644 --- a/libraries/standard/http/test/test-HTTPClient_Send.c +++ b/libraries/standard/http/test/test-HTTPClient_Send.c @@ -7,13 +7,13 @@ /* Functions are pulled out into their own C files to be tested as a unit. */ -#include "_sendHttpHeaders.c" -#include "_sendHttpBody.c" -#include "_receiveHttpResponse.c" -#include "_HTTPClient_InitializeParsingContext.c" -#include "_HTTPClient_ParseResponse.c" -#include "_getFinalResponseStatus.c" -#include "_receiveAndParseHttpResponse.c" +#include "sendHttpHeaders.c" +#include "sendHttpBody.c" +#include "receiveHttpResponse.c" +#include "HTTPClient_InitializeParsingContext.c" +#include "HTTPClient_ParseResponse.c" +#include "getFinalResponseStatus.c" +#include "receiveAndParseHttpResponse.c" #include "HTTPClient_Send.c" /* HTTP OK Status-Line. */ From c953b9c82c1ad507d5ab7d60a92491cc7721c459 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 6 May 2020 14:48:43 -0700 Subject: [PATCH 485/844] Fix/http warnings (#921) --- libraries/standard/http/src/http_client.c | 27 +++++++++++------------ 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 4ae2009ff1..5522c98895 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -132,11 +132,11 @@ static uint8_t convertInt32ToAscii( int32_t value, * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. */ -static HTTPStatus_t_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, - const char * pMethod, - size_t methodLen, - const char * pPath, - size_t pathLen ); +static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ); /*-----------------------------------------------------------*/ @@ -282,7 +282,6 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, SPACE_CHARACTER_LEN + \ HTTP_PROTOCOL_VERSION_LEN + \ HTTP_HEADER_LINE_SEPARATOR_LEN; - int32_t bytesWritten = 0; assert( pRequestHeaders != NULL ); assert( pRequestHeaders->pBuffer != NULL ); @@ -396,9 +395,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques { /* Write "User-Agent: ". */ returnStatus = addHeader( pRequestHeaders, - HTTP_USER_AGENT_FIELD, + ( const uint8_t * ) HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_FIELD_LEN, - HTTP_USER_AGENT_VALUE, + ( const uint8_t * ) HTTP_USER_AGENT_VALUE, HTTP_USER_AGENT_VALUE_LEN ); } @@ -406,9 +405,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques { /* Write "Host: ". */ returnStatus = addHeader( pRequestHeaders, - HTTP_HOST_FIELD, + ( const uint8_t * ) HTTP_HOST_FIELD, HTTP_HOST_FIELD_LEN, - pRequestInfo->pHost, + ( const uint8_t * ) pRequestInfo->pHost, pRequestInfo->hostLen ); } @@ -418,18 +417,18 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques { /* Write "Connection: keep-alive". */ returnStatus = addHeader( pRequestHeaders, - HTTP_CONNECTION_FIELD, + ( const uint8_t * ) HTTP_CONNECTION_FIELD, HTTP_CONNECTION_FIELD_LEN, - HTTP_CONNECTION_KEEP_ALIVE_VALUE, + ( const uint8_t * ) HTTP_CONNECTION_KEEP_ALIVE_VALUE, HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ); } else { /* Write "Connection: close". */ returnStatus = addHeader( pRequestHeaders, - HTTP_CONNECTION_FIELD, + ( const uint8_t * ) HTTP_CONNECTION_FIELD, HTTP_CONNECTION_FIELD_LEN, - HTTP_CONNECTION_CLOSE_VALUE, + ( const uint8_t * ) HTTP_CONNECTION_CLOSE_VALUE, HTTP_CONNECTION_CLOSE_VALUE_LEN ); } } From 030f1616d75efe7c56f4b437da6aa3e8ee7ab5b0 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 7 May 2020 09:25:42 -0700 Subject: [PATCH 486/844] Add state engine (#874) * Add state engine * Remove packet ID collision check According to 2.3.1 of the MQTT spec, since client and server assign packet identifiers independently, it's possible for concurrent exchanges to use the same packet identifiers. * Refactor state engine for simplicity * Rename invalid state to null state * Add state select * Define bool for C89 * Add asserts to static functions * Remove leading underscore from functions --- demos/mqtt/mqtt_demo_plaintext/config.h | 4 +- libraries/standard/mqtt/include/mqtt.h | 7 +- .../standard/mqtt/include/mqtt_lightweight.h | 14 +- libraries/standard/mqtt/include/mqtt_state.h | 121 ++++ libraries/standard/mqtt/src/mqtt_state.c | 584 ++++++++++++++++++ 5 files changed, 723 insertions(+), 7 deletions(-) create mode 100644 libraries/standard/mqtt/include/mqtt_state.h create mode 100644 libraries/standard/mqtt/src/mqtt_state.c diff --git a/demos/mqtt/mqtt_demo_plaintext/config.h b/demos/mqtt/mqtt_demo_plaintext/config.h index f1767aa7de..3b5a6dc882 100644 --- a/demos/mqtt/mqtt_demo_plaintext/config.h +++ b/demos/mqtt/mqtt_demo_plaintext/config.h @@ -29,13 +29,13 @@ typedef int MQTTNetworkContext_t; * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. * - * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before * they can be completed. While they are awaiting the acknowledgement, the * client must maintain information about their state. The value of this * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. */ -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 +#define MQTT_STATE_ARRAY_MAX_COUNT 10U /** * @brief MQTT client identifier. diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index b9b94787e0..dbf2cfcbd7 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -51,6 +51,7 @@ typedef enum MQTTConnectionStatus typedef enum MQTTPublishState { + MQTTStateNull = 0, MQTTPublishSend, MQTTPubAckSend, MQTTPubRecSend, @@ -87,15 +88,15 @@ struct MQTTApplicationCallbacks struct MQTTPubAckInfo { uint16_t packetId; - MQTTPubAckType_t ackType; + MQTTQoS_t qos; MQTTPublishState_t publishState; }; struct MQTTContext { - MQTTPubAckInfo_t outgoingPublishRecords[ MQTT_MAX_QUEUED_PUBLISH_MESSAGES ]; + MQTTPubAckInfo_t outgoingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT ]; size_t outgoingPublishCount; - MQTTPubAckInfo_t incomingPublishRecords[ MQTT_MAX_QUEUED_PUBLISH_MESSAGES ]; + MQTTPubAckInfo_t incomingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT ]; size_t incomingPublishCount; MQTTTransportInterface_t transportInterface; diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 6bd4a9e139..b2ff70284f 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -22,7 +22,15 @@ #ifndef MQTT_LIGHTWEIGHT_H #define MQTT_LIGHTWEIGHT_H -#include +/* bools are only defined in C99+ */ +#if __STDC_VERSION__ >= 199901L + #include +#else + #define bool signed char + #define false 0 + #define true 1 +#endif + #include #include @@ -85,7 +93,9 @@ typedef enum MQTTStatus MQTTRecvFailed, /**< The transport receive function failed. */ MQTTBadResponse, /**< An invalid packet was received from the server. */ MQTTServerRefused, /**< The server refused a CONNECT or SUBSCRIBE. */ - MQTTNoDataAvailable + MQTTNoDataAvailable,/**< No data available from the transport interface. */ + MQTTIllegalState, /**< An illegal state in the state record. */ + MQTTStateCollision /**< A collision with an existing state record entry. */ } MQTTStatus_t; /** diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h new file mode 100644 index 0000000000..0f1b619ae4 --- /dev/null +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_STATE_H +#define MQTT_STATE_H + +#include "mqtt.h" + +#define MQTT_STATE_CURSOR_INITIALIZER ( size_t ) 0 + +/** + * @brief Value indicating either send or receive. + */ +typedef enum MQTTStateOperation +{ + MQTT_SEND, + MQTT_RECEIVE, +} MQTTStateOperation_t; + +/** + * @brief Cursor for iterating through state records. + */ +typedef size_t MQTTStateCursor_t; + +/** + * @brief Reserve an entry for an outgoing QoS 1/2 publish. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] packetId The ID of the publish packet. + * @param[in] qos 1 or 2. + * + * @return MQTTSuccess, MQTTNoMemory, or MQTTStateCollision. + */ +MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTQoS_t qos ); + +/** + * @brief Calculate the new state for a publish from its qos and operation type. + * + * @param[in] opType Send or Receive. + * @param[in] qos 0, 1, or 2. + * + * @return The calculated state. + */ +MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, + MQTTQoS_t qos ); + +/** + * @brief Update the state record for a PUBLISH packet. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] packetId ID of the PUBLISH packet. + * @param[in] opType Send or Receive. + * @param[in] qos 0, 1, or 2. + * + * @return The new state of the publish. + */ +MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTStateOperation_t opType, + MQTTQoS_t qos ); + +/** + * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP. + * + * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. + * @param[in] opType Send or Receive. + * @param[in] qos 1 or 2. + * + * @return The calculated state. + */ +MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, + MQTTStateOperation_t opType, + MQTTQoS_t qos ); + +/** + * @brief Update the state record for an ACKed publish. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] packetId ID of the ack packet. + * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. + * @param[in] opType Send or Receive. + * + * @return The new state of the publish. + */ +MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTPubAckType_t packetType, + MQTTStateOperation_t opType ); + +/** + * @brief Get the packet ID and index of a publish in a specified state. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] searchState The state to search for. + * @param[in,out] pCursor Index at which to start searching. + */ +uint16_t MQTT_StateSelect( MQTTContext_t * pMqttContext, + MQTTPublishState_t searchState, + MQTTStateCursor_t * pCursor ); + +#endif /* ifndef MQTT_STATE_H */ diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c new file mode 100644 index 0000000000..e8b4b525b5 --- /dev/null +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include "mqtt_state.h" + +#define MQTT_PACKET_ID_INVALID ( uint16_t ) 0U + +/** + * @brief Test if a transition to new state is possible, when dealing with PUBLISHes. + * + * @param[in] currentState The current state. + * @param[in] newState State to transition to. + * @param[in] opType Reserve, Send, or Receive. + * @param[in] qos 0, 1, or 2. + * + * @note This function does not validate the current state, or the new state + * based on either the operation type or QoS. It assumes the new state is valid + * given the opType and QoS, which will be the case if calculated by + * MQTT_CalculateStatePublish(). + * + * @return `true` if transition is possible, else `false` + */ +static bool validateTransitionPublish( MQTTPublishState_t currentState, + MQTTPublishState_t newState, + MQTTStateOperation_t opType, + MQTTQoS_t qos ); + +/** + * @brief Test if a transition to a new state is possible, when dealing with acks. + * + * @param[in] currentState The current state. + * @param[in] newState State to transition to. + * + * @return `true` if transition is possible, else `false`. + */ +static bool validateTransitionAck( MQTTPublishState_t currentState, + MQTTPublishState_t newState ); + +/** + * @brief Test if the publish corresponding to an ack is outgoing or incoming. + * + * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. + * @param[in] opType Send, or Receive. + * + * @return `true` if corresponds to outgoing publish, else `false`. + */ +static bool isPublishOutgoing( MQTTPubAckType_t packetType, + MQTTStateOperation_t opType ); + +/** + * @brief Find a packet ID in the state record. + * + * @param[in] records State record array. + * @param[in] recordCount Length of record array. + * @param[in] packetId packet ID to search for. + * @param[out] pQos QoS retrieved from record. + * @param[out] pCurrentState state retrieved from record. + * + * @return index of the packet id in the record if it exists, else the record length. + */ +static size_t findInRecord( const MQTTPubAckInfo_t * records, + size_t recordCount, + uint16_t packetId, + MQTTQoS_t * pQos, + MQTTPublishState_t * pCurrentState ); + +/** + * @brief Store a new entry in the state record. + * + * @param[in] records State record array. + * @param[in] recordCount Length of record array. + * @param[in] packetId, packet ID of new entry. + * @param[in] qos QoS of new entry. + * @param[in] publishState state of new entry. + * + * @return MQTTSuccess, MQTTNoMemory, MQTTStateCollision. + */ +static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, + size_t recordCount, + uint16_t packetId, + MQTTQoS_t qos, + MQTTPublishState_t publishState ); + +/** + * @brief Update and possibly delete an entry in the state record. + * + * @param[in] records State record array. + * @param[in] recordIndex index of record to update. + * @param[in] newState New state to update. + * @param[in] shouldDelete Whether an existing entry should be deleted. + */ +static void updateRecord( MQTTPubAckInfo_t * records, + size_t recordIndex, + MQTTPublishState_t newState, + bool shouldDelete ); + + +static bool validateTransitionPublish( MQTTPublishState_t currentState, + MQTTPublishState_t newState, + MQTTStateOperation_t opType, + MQTTQoS_t qos ) +{ + bool isValid = false; + switch( currentState ) + { + case MQTTStateNull: + /* Transitions from null occur when storing a new entry into the record. */ + if( opType == MQTT_RECEIVE ) + { + isValid = ( newState == MQTTPubAckSend ) || ( newState == MQTTPubRecSend ); + } + break; + case MQTTPublishSend: + /* Outgoing publish. All such publishes start in this state due to + * the reserve operation. */ + switch( qos ) + { + case MQTTQoS0: + isValid = ( newState == MQTTPublishDone ); + break; + case MQTTQoS1: + isValid = ( newState == MQTTPubAckPending ); + break; + case MQTTQoS2: + isValid = ( newState == MQTTPubRecPending ); + break; + default: + /* No other QoS value. */ + break; + } + break; + default: + /* For a PUBLISH, we should not start from any other state. */ + break; + } + return isValid; +} + +static bool validateTransitionAck( MQTTPublishState_t currentState, + MQTTPublishState_t newState ) +{ + bool isValid = false; + switch( currentState ) + { + case MQTTPubAckSend: + /* Incoming publish, QoS 1. */ + case MQTTPubAckPending: + /* Outgoing publish, QoS 1. */ + isValid = ( newState == MQTTPublishDone ); + break; + case MQTTPubRecSend: + /* Incoming publish, QoS 2. */ + isValid = ( newState == MQTTPubRelPending ); + break; + case MQTTPubRelPending: + /* Incoming publish, QoS 2. */ + isValid = ( newState == MQTTPubCompSend ); + break; + case MQTTPubCompSend: + /* Incoming publish, QoS 2. */ + isValid = ( newState == MQTTPublishDone ); + break; + case MQTTPubRecPending: + /* Outgoing publish, Qos 2. */ + isValid = ( newState == MQTTPubRelSend ); + break; + case MQTTPubRelSend: + /* Outgoing publish, Qos 2. */ + isValid = ( newState == MQTTPubCompPending ); + break; + case MQTTPubCompPending: + /* Outgoing publish, Qos 2. */ + isValid = ( newState == MQTTPublishDone ); + break; + case MQTTPublishDone: + /* Done state should transition to invalid since it will be removed from the record. */ + case MQTTPublishSend: + /* If an ack was sent/received we shouldn't have been in this state. */ + case MQTTStateNull: + /* If an ack was sent/received the record should exist. */ + default: + /* Invalid. */ + break; + } + + return isValid; +} + +static bool isPublishOutgoing( MQTTPubAckType_t packetType, + MQTTStateOperation_t opType ) +{ + bool isOutgoing = false; + switch( packetType ) + { + case MQTTPuback: + case MQTTPubrec: + case MQTTPubcomp: + isOutgoing = ( opType == MQTT_RECEIVE ); + break; + case MQTTPubrel: + isOutgoing = ( opType == MQTT_SEND ); + break; + default: + /* No other ack type. */ + break; + } + return isOutgoing; +} + +static size_t findInRecord( const MQTTPubAckInfo_t * records, + size_t recordCount, + uint16_t packetId, + MQTTQoS_t * pQos, + MQTTPublishState_t * pCurrentState ) +{ + size_t index = 0; + *pCurrentState = MQTTStateNull; + if( packetId == MQTT_PACKET_ID_INVALID ) + { + index = recordCount; + } + else + { + for( index = 0; index < recordCount; index++ ) + { + if( records[ index ].packetId == packetId ) + { + *pQos = records[ index ].qos; + *pCurrentState = records[ index ].publishState; + break; + } + } + } + return index; +} + +static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, + size_t recordCount, + uint16_t packetId, + MQTTQoS_t qos, + MQTTPublishState_t publishState ) +{ + MQTTStatus_t status = MQTTNoMemory; + int32_t index = 0; + size_t availableIndex = recordCount; + + assert( packetId != MQTT_PACKET_ID_INVALID ); + assert( qos != MQTTQoS0 ); + + /* Start from end so first available index will be populated. */ + for( index = ( (int32_t ) recordCount - 1 ); index >= 0; index-- ) + { + if( records[ index ].packetId == MQTT_PACKET_ID_INVALID ) + { + availableIndex = ( size_t ) index; + } + else if( records[ index ].packetId == packetId ) + { + /* Collision. */ + status = MQTTStateCollision; + availableIndex = recordCount; + break; + } + else + { + /* Empty else clause. */ + } + } + + if( availableIndex < recordCount ) + { + records[ availableIndex ].packetId = packetId; + records[ availableIndex ].qos = qos; + records[ availableIndex ].publishState = publishState; + status = MQTTSuccess; + } + + return status; +} + +static void updateRecord( MQTTPubAckInfo_t * records, + size_t recordIndex, + MQTTPublishState_t newState, + bool shouldDelete ) +{ + if( shouldDelete ) + { + records[ recordIndex ].packetId = MQTT_PACKET_ID_INVALID; + records[ recordIndex ].qos = MQTTQoS0; + records[ recordIndex ].publishState = MQTTStateNull; + } + else + { + records[ recordIndex ].publishState = newState; + } +} + +MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTQoS_t qos ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( qos == MQTTQoS0 ) + { + status = MQTTSuccess; + } + else if( packetId == MQTT_PACKET_ID_INVALID ) + { + status = MQTTBadParameter; + } + else + { + /* Collisions are detected when adding the record. */ + status = addRecord( pMqttContext->outgoingPublishRecords, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + qos, + MQTTPublishSend ); + } + + return status; +} + +MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, + MQTTQoS_t qos ) +{ + MQTTPublishState_t calculatedState = MQTTStateNull; + switch( qos ) + { + case MQTTQoS0: + calculatedState = MQTTPublishDone; + break; + case MQTTQoS1: + calculatedState = ( opType == MQTT_SEND )? MQTTPubAckPending : MQTTPubAckSend; + break; + case MQTTQoS2: + calculatedState = ( opType == MQTT_SEND )? MQTTPubRecPending : MQTTPubRecSend; + break; + default: + /* No other QoS values. */ + break; + } + return calculatedState; +} + +MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTStateOperation_t opType, + MQTTQoS_t qos ) +{ + MQTTPublishState_t newState = MQTTStateNull; + MQTTPublishState_t currentState = MQTTStateNull; + MQTTStatus_t mqttStatus = MQTTSuccess; + size_t recordIndex = MQTT_STATE_ARRAY_MAX_COUNT; + MQTTQoS_t foundQoS = MQTTQoS0; + bool isTransitionValid = false; + + if( qos == MQTTQoS0 ) + { + /* QoS 0 publish. Do nothing. */ + newState = MQTTPublishDone; + } + else if( ( packetId == MQTT_PACKET_ID_INVALID ) || ( pMqttContext == NULL ) ) + { + /* Publishes > QoS 0 need a valid packet ID. */ + mqttStatus = MQTTBadParameter; + } + else if( opType == MQTT_SEND ) + { + /* Search record for entry so we can check QoS. */ + recordIndex = findInRecord( pMqttContext->outgoingPublishRecords, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + &foundQoS, + ¤tState ); + if( foundQoS != qos ) + { + /* Entry should match with supplied QoS. */ + mqttStatus = MQTTBadParameter; + } + } + else + { + /* QoS 1 or 2 receive. Nothing to be done. */ + } + + if( ( qos != MQTTQoS0 ) && ( mqttStatus == MQTTSuccess ) ) + { + newState = MQTT_CalculateStatePublish( opType, qos ); + isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos ); + if( isTransitionValid ) + { + /* addRecord will check for collisions. */ + if( opType == MQTT_RECEIVE ) + { + mqttStatus = addRecord( pMqttContext->incomingPublishRecords, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + qos, + newState ); + } + /* Send operation. */ + else if( recordIndex < MQTT_STATE_ARRAY_MAX_COUNT ) + { + updateRecord( pMqttContext->outgoingPublishRecords, recordIndex, newState, false ); + } + else + { + /* Send operation with no record found. */ + mqttStatus = MQTTBadParameter; + } + } + else + { + mqttStatus = MQTTBadParameter; + } + } + + if( mqttStatus != MQTTSuccess ) + { + newState = MQTTStateNull; + } + + return newState; +} + +MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, + MQTTStateOperation_t opType, + MQTTQoS_t qos ) +{ + MQTTPublishState_t calculatedState = MQTTStateNull; + /* There are more QoS2 cases than QoS1, so initialize to that. */ + bool qosValid = ( qos == MQTTQoS2 ); + + switch( packetType ) + { + case MQTTPuback: + qosValid = ( qos == MQTTQoS1 ); + calculatedState = MQTTPublishDone; + break; + case MQTTPubrec: + /* Incoming publish: send PUBREC, PUBREL pending. + * Outgoing publish: receive PUBREC, send PUBREL. */ + calculatedState = ( opType == MQTT_SEND )? MQTTPubRelPending : MQTTPubRelSend; + break; + case MQTTPubrel: + /* Incoming publish: receive PUBREL, send PUBCOMP. + * Outgoing publish: send PUBREL, PUBCOMP pending. */ + calculatedState = ( opType == MQTT_SEND )? MQTTPubCompPending : MQTTPubCompSend; + break; + case MQTTPubcomp: + calculatedState = MQTTPublishDone; + break; + default: + /* No other ack type. */ + break; + } + /* Sanity check, make sure ack and QoS agree. */ + if( !qosValid ) + { + calculatedState = MQTTStateNull; + } + return calculatedState; +} + +MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTPubAckType_t packetType, + MQTTStateOperation_t opType ) +{ + MQTTPublishState_t newState = MQTTStateNull; + MQTTPublishState_t currentState = MQTTStateNull; + bool isOutgoingPublish = isPublishOutgoing( packetType, opType ); + bool shouldDeleteRecord = false; + bool isTransitionValid = false; + MQTTQoS_t qos = MQTTQoS0; + size_t recordIndex = MQTT_STATE_ARRAY_MAX_COUNT; + MQTTPubAckInfo_t * records = NULL; + + if( isOutgoingPublish ) + { + records = pMqttContext->outgoingPublishRecords; + } + else + { + records = pMqttContext->incomingPublishRecords; + } + recordIndex = findInRecord( records, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + &qos, + ¤tState ); + + if( recordIndex < MQTT_STATE_ARRAY_MAX_COUNT ) + { + newState = MQTT_CalculateStateAck( packetType, opType, qos ); + shouldDeleteRecord = ( newState == MQTTPublishDone ); + isTransitionValid = validateTransitionAck( currentState, newState ); + if( isTransitionValid ) + { + updateRecord( records, + recordIndex, + newState, + shouldDeleteRecord ); + } + else + { + newState = MQTTStateNull; + } + + } + return newState; +} + +uint16_t MQTT_StateSelect( MQTTContext_t * pMqttContext, + MQTTPublishState_t searchState, + MQTTStateCursor_t * pCursor ) +{ + uint16_t packetId = MQTT_PACKET_ID_INVALID; + MQTTPubAckInfo_t * records = NULL; + + /* Classify state into incoming or outgoing so we don't search both. */ + switch( searchState ) + { + case MQTTPubAckSend: + case MQTTPubRecSend: + case MQTTPubRelPending: + case MQTTPubCompSend: + if( pMqttContext != NULL ) + { + records = pMqttContext->incomingPublishRecords; + } + break; + case MQTTPublishSend: + case MQTTPubAckPending: + case MQTTPubRecPending: + case MQTTPubRelSend: + case MQTTPubCompPending: + if( pMqttContext != NULL ) + { + records = pMqttContext->outgoingPublishRecords; + } + break; + default: + /* NULL or done aren't valid entries to search for. */ + break; + } + + if( ( records != NULL ) && ( pCursor != NULL ) ) + { + while( *pCursor < MQTT_STATE_ARRAY_MAX_COUNT ) + { + if( records[ *pCursor ].publishState == searchState ) + { + packetId = records[ *pCursor ].packetId; + ( *pCursor )++; + break; + } + ( *pCursor )++; + } + } + + return packetId; +} From ea4a6d71848e3737958f8cad13ecce30b153b054 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 7 May 2020 10:15:01 -0700 Subject: [PATCH 487/844] Add http-parser as a git submodule. (#924) --- .gitmodules | 3 +++ libraries/standard/http/third_party/http_parser | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 libraries/standard/http/third_party/http_parser diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..b1f9d94da0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libraries/standard/http/third_party/http_parser"] + path = libraries/standard/http/third_party/http_parser + url = git@github.com:nodejs/http-parser.git diff --git a/libraries/standard/http/third_party/http_parser b/libraries/standard/http/third_party/http_parser new file mode 160000 index 0000000000..2343fd6b52 --- /dev/null +++ b/libraries/standard/http/third_party/http_parser @@ -0,0 +1 @@ +Subproject commit 2343fd6b5214b2ded2cdcf76de2bf60903bb90cd From 09340e7be97996d1c37b400927fa880bd5d4831b Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 7 May 2020 10:16:37 -0700 Subject: [PATCH 488/844] Implement MQTT Publish (#905) * Implement MQTT Publish The list of functions implemented are 1. MQTT_Publish 2. MQTT_GetPublishPacketSize 3. MQTT_SerializePublish 4. MQTT_SerializePublishHeader --- libraries/standard/mqtt/include/mqtt.h | 23 +- .../standard/mqtt/include/mqtt_lightweight.h | 95 +++- libraries/standard/mqtt/src/mqtt.c | 115 ++++- .../standard/mqtt/src/mqtt_lightweight.c | 442 ++++++++++++++++-- 4 files changed, 600 insertions(+), 75 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index dbf2cfcbd7..eb6fe58579 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -23,16 +23,16 @@ #include "mqtt_lightweight.h" struct MQTTApplicationCallbacks; -typedef struct MQTTApplicationCallbacks MQTTApplicationCallbacks_t; +typedef struct MQTTApplicationCallbacks MQTTApplicationCallbacks_t; struct MQTTPubAckInfo; -typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; +typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; struct MQTTContext; -typedef struct MQTTContext MQTTContext_t; +typedef struct MQTTContext MQTTContext_t; struct MQTTTransportInterface; -typedef struct MQTTTransportInterface MQTTTransportInterface_t; +typedef struct MQTTTransportInterface MQTTTransportInterface_t; typedef int32_t (* MQTTTransportSendFunc_t )( MQTTNetworkContext_t context, const void * pMessage, @@ -143,8 +143,21 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount ); +/** + * @brief Publishes a message to the given topic name. + * + * @brief param[in] pContext Initialized MQTT context. + * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @brief param[in] packetId packet ID generated by #MQTT_GetPacketId. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport write failed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, - const MQTTPublishInfo_t * const pPublishInfo ); + const MQTTPublishInfo_t * const pPublishInfo, + uint16_t packetId ); MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ); diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index b2ff70284f..f57f138c5c 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -37,32 +37,32 @@ #include "config.h" /* MQTT packet types. */ -#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ -#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBREC ( ( uint8_t ) 0x50U ) /**< @brief PUBREC (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBREL ( ( uint8_t ) 0x62U ) /**< @brief PUBREL (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBCOMP ( ( uint8_t ) 0x70U ) /**< @brief PUBCOMP (bidirectional). */ -#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ -#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBREC ( ( uint8_t ) 0x50U ) /**< @brief PUBREC (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBREL ( ( uint8_t ) 0x62U ) /**< @brief PUBREL (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBCOMP ( ( uint8_t ) 0x70U ) /**< @brief PUBCOMP (bidirectional). */ +#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ struct MQTTFixedBuffer; -typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; +typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; struct MQTTConnectInfo; -typedef struct MQTTConnectInfo MQTTConnectInfo_t; +typedef struct MQTTConnectInfo MQTTConnectInfo_t; struct MQTTSubscribeInfo; -typedef struct MQTTSubscribeInfo MQTTSubscribeInfo_t; +typedef struct MQTTSubscribeInfo MQTTSubscribeInfo_t; struct MqttPublishInfo; -typedef struct MqttPublishInfo MQTTPublishInfo_t; +typedef struct MqttPublishInfo MQTTPublishInfo_t; struct MQTTPacketInfo; -typedef struct MQTTPacketInfo MQTTPacketInfo_t; +typedef struct MQTTPacketInfo MQTTPacketInfo_t; /** * @brief Signature of the transport interface receive function. @@ -103,9 +103,9 @@ typedef enum MQTTStatus */ typedef enum MQTTQoS { - MQTTQoS0 = 0, /**< Delivery at most once. */ - MQTTQoS1 = 1, /**< Delivery at least once. */ - MQTTQoS2 = 2 /**< Delivery exactly once. */ + MQTTQoS0 = 0, /**< Delivery at most once. */ + MQTTQoS1 = 1, /**< Delivery at least once. */ + MQTTQoS2 = 2 /**< Delivery exactly once. */ } MQTTQoS_t; /** @@ -116,8 +116,8 @@ typedef enum MQTTQoS */ struct MQTTFixedBuffer { - uint8_t * pBuffer; /**< @brief Pointer to buffer. */ - size_t size; /**< @brief Size of buffer. */ + uint8_t * pBuffer; /**< @brief Pointer to buffer. */ + size_t size; /**< @brief Size of buffer. */ }; /** @@ -202,6 +202,11 @@ struct MqttPublishInfo */ bool retain; + /** + * @brief Whether this is a duplicate publish message. + */ + bool dup; + /** * @brief Topic name on which the message is published. */ @@ -298,15 +303,61 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ); +/** + * @brief Get the packet size and remaining length of an MQTT PUBLISH packet. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. + * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. + * + * @return #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec or if invalid parameters are passed; #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, size_t * const pRemainingLength, size_t * const pPacketSize ); +/** + * @brief Serialize an MQTT PUBLISH packet in the given buffer. + * + * This function will serialize complete MQTT PUBLISH packet into + * the given buffer. If the PUBLISH payload can be sent separately, + * consider using #MQTT_SerializePublishHeader, which will serialize + * only the PUBLISH header into the buffer. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. + * @param[out] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ); +/** + * @brief Serialize an MQTT PUBLISH packet header in the given buffer. + * + * This function serializes PUBLISH header in to the given buffer. Payload + * for PUBLISH will not be copied over to the buffer. This will help reduce + * the memory needed for the buffer and avoid an unwanted copy operataion of + * PUBLISH payload into the buffer. If payload also would need to be part of + * the serialized buffer, consider using #MQTT_SerializePublish. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. + * @param[out] pBuffer Buffer for packet serialization. + * @param[out] pHeaderSize Size of the serialized MQTT PUBLISH header. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId, size_t remainingLength, @@ -395,4 +446,4 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu MQTTNetworkContext_t networkContext, MQTTPacketInfo_t * pIncomingPacket ); -#endif +#endif /* ifndef MQTT_LIGHTWEIGHT_H */ diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index e056c8ac19..9caa6f7432 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -22,12 +22,22 @@ #include #include "mqtt.h" +#include "private/mqtt_internal.h" -/*-----------------------------------------------------------*/ - -static int32_t sendPacket( MQTTContext_t * pContext, size_t bytesToSend ) +/** + * @brief Sends provided buffer to network using transport send. + * + * @brief param[in] pContext Initialized MQTT context. + * @brief param[in] pBufferToSend Buffer to be sent to network. + * @brief param[in] bytesToSend Number of bytes to be sent. + * + * @return Total number of bytes sent; -1 if there is an error. + */ +static int32_t sendPacket( MQTTContext_t * pContext, + const uint8_t * pBufferToSend, + size_t bytesToSend ) { - const uint8_t * pIndex = pContext->networkBuffer.pBuffer; + const uint8_t * pIndex = pBufferToSend; size_t bytesRemaining = bytesToSend; int32_t totalBytesSent = 0, bytesSent; @@ -107,7 +117,9 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - bytesSent = sendPacket( pContext, packetSize ); + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + packetSize ); if( bytesSent < 0 ) { @@ -154,9 +166,94 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, - const MQTTPublishInfo_t * const pPublishInfo ) + const MQTTPublishInfo_t * const pPublishInfo, + uint16_t packetId ) { - return MQTTSuccess; + size_t remainingLength = 0, packetSize = 0, headerSize = 0; + int32_t bytesSent = 0; + MQTTStatus_t status = MQTTSuccess; + + /* Validate arguments. */ + if( ( pContext == NULL ) || ( pPublishInfo == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pPublishInfo=%p.", + pContext, + pPublishInfo ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) + { + IotLogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ); + status = MQTTBadParameter; + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( status == MQTTSuccess ) + { + /* Get the remaining length and packet size.*/ + status = MQTT_GetPublishPacketSize( pPublishInfo, + &remainingLength, + &packetSize ); + IotLogDebugWithArgs( "PUBLISH packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); + } + + if( status == MQTTSuccess ) + { + status = MQTT_SerializePublishHeader( pPublishInfo, + packetId, + remainingLength, + &( pContext->networkBuffer ), + &headerSize ); + IotLogDebugWithArgs( "Serialized PUBLISH header size is %lu.", + headerSize ); + } + + if( status == MQTTSuccess ) + { + /* Send header first. */ + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + headerSize ); + + if( bytesSent < 0 ) + { + IotLogError( "Transport send failed for PUBLISH header." ); + status = MQTTSendFailed; + } + else + { + IotLogDebugWithArgs( "Sent %d bytes of PUBLISH header.", + bytesSent ); + + /* Send Payload. */ + bytesSent = sendPacket( pContext, + pPublishInfo->pPayload, + pPublishInfo->payloadLength ); + + if( bytesSent < 0 ) + { + IotLogError( "Transport send failed for PUBLISH payload." ); + status = MQTTSendFailed; + } + else + { + IotLogDebugWithArgs( "Sent %d bytes of PUBLISH payload.", + bytesSent ); + } + } + } + + /* TODO - Update the state machine with the packet ID. This will have to + * be done once the state machine changes are available.*/ + + return status; } /*-----------------------------------------------------------*/ @@ -191,7 +288,9 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) if( status == MQTTSuccess ) { - bytesSent = sendPacket( pContext, packetSize ); + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + packetSize ); if( bytesSent < 0 ) { diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 5fd5d271fc..42610efaf8 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -28,45 +28,51 @@ /** * @brief MQTT protocol version 3.1.1. */ -#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) /** * @brief Size of the fixed and variable header of a CONNECT packet. */ -#define MQTT_PACKET_CONNECT_HEADER_SIZE ( 10UL ) +#define MQTT_PACKET_CONNECT_HEADER_SIZE ( 10UL ) /** * @brief Maximum size of an MQTT CONNECT packet, per MQTT spec. */ -#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) +#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) + +/** + * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT + * packet is this value. + */ +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) /* MQTT CONNECT flags. */ -#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ -#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ -#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */ -#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ -#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ -#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ -#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ /* * Positions of each flag in the first byte of an MQTT PUBLISH packet's * fixed header. */ -#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ -#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ -#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ -#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ +#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief MQTT PUBLISH retain flag. */ +#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief MQTT PUBLISH QoS1 flag. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief MQTT PUBLISH QoS1 flag. */ +#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief MQTT PUBLISH duplicate flag. */ /** * @brief The size of MQTT DISCONNECT packets, per MQTT spec. */ -#define MQTT_DISCONNECT_PACKET_SIZE ( 2UL ) +#define MQTT_DISCONNECT_PACKET_SIZE ( 2UL ) /** * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec. */ -#define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) +#define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) /* * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. @@ -77,19 +83,19 @@ /** * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. */ -#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4U ) +#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4U ) /* * UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP always have a remaining length * of 2. */ -#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ -#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ /** * @brief Set a bit in an 8-bit unsigned integer. */ -#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) /** * @brief Macro for checking if a bit is set in a 1-byte unsigned int. @@ -102,12 +108,12 @@ /** * @brief Get the high byte of a 16-bit unsigned integer. */ -#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) /** * @brief Get the low byte of a 16-bit unsigned integer. */ -#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) /** * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. @@ -123,14 +129,54 @@ * * This value is greater than what is allowed by the MQTT specification. */ -#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) /** * @brief The minimum remaining length for a QoS 0 PUBLISH. * * Includes two bytes for topic name length and one byte for topic name. */ -#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) +#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Serializes MQTT PUBLISH packet into the buffer provided. + * + * This function serializes MQTT PUBLISH packet into #pFixedBuffer.pBuffer. + * Copy of the payload into the buffer is done as part of the serialization + * only if #serializePayload is true. + * + * @brief param[in] pPublishInfo Publish information. + * @brief param[in] remainingLength Remaining length of the PUBLISH packet. + * @brief param[in] packetIdentifier Packet identifier of PUBLISH packet. + * @brief param[in, out] pFixedBuffer Buffer to which PUBLISH packet will be + * serialized. + * @brief param[in] serializePayload Copy payload to the serialized buffer + * only if true. Only PUBLISH header will be serialized if false. + * + * @return Total number of bytes sent; -1 if there is an error. + */ +static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t packetIdentifier, + const MQTTFixedBuffer_t * const pFixedBuffer, + bool serializePayload ); + +/** + * @brief Calculates the packet size and remaining length of an MQTT + * PUBLISH packet. + * + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. + * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. + * + * @return false if the packet would exceed the size allowed by the + * MQTT spec; true otherwise. + */ +static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); /*-----------------------------------------------------------*/ @@ -252,6 +298,177 @@ static int32_t recvExact( MQTTTransportRecvFunc_t recvFunc, /*-----------------------------------------------------------*/ +static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + bool status = true; + size_t packetSize = 0, payloadLimit = 0; + + assert( pPublishInfo != NULL ); + assert( pRemainingLength != NULL ); + assert( pPacketSize != NULL ); + + /* The variable header of a PUBLISH packet always contains the topic name. + * The first 2 bytes of UTF-8 string contains length of the string. + */ + packetSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); + + /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte + * packet identifier. */ + if( pPublishInfo->qos > MQTTQoS0 ) + { + packetSize += sizeof( uint16_t ); + } + + /* Calculate the maximum allowed size of the payload for the given parameters. + * This calculation excludes the "Remaining length" encoding, whose size is not + * yet known. */ + payloadLimit = MQTT_MAX_REMAINING_LENGTH - packetSize - 1U; + + /* Ensure that the given payload fits within the calculated limit. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + IotLogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + pPublishInfo->payloadLength, + payloadLimit, + MQTT_MAX_REMAINING_LENGTH ); + status = false; + } + else + { + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. */ + packetSize += pPublishInfo->payloadLength; + + /* Now that the "Remaining length" is known, recalculate the payload limit + * based on the size of its encoding. */ + payloadLimit -= remainingLengthEncodedSize( packetSize ); + + /* Check that the given payload fits within the size allowed by MQTT spec. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + IotLogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + pPublishInfo->payloadLength, + payloadLimit, + MQTT_MAX_REMAINING_LENGTH ); + status = false; + } + else + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = packetSize; + + packetSize += 1U + remainingLengthEncodedSize( packetSize ); + *pPacketSize = packetSize; + } + } + + IotLogDebugWithArgs( "PUBLISH packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ); + return status; +} + +/*-----------------------------------------------------------*/ + +static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t packetIdentifier, + const MQTTFixedBuffer_t * const pFixedBuffer, + bool serializePayload ) +{ + uint8_t * pIndex = NULL; + size_t minBufferSize = 0; + + /* The first byte of a PUBLISH packet contains the packet type and flags. */ + uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH; + + assert( pPublishInfo != NULL ); + assert( pFixedBuffer != NULL ); + /* Packet Id should be non zero for QoS1 and QoS2. */ + assert( pPublishInfo->qos == MQTTQoS0 || packetIdentifier != 0U ); + /* Duplicate flag should be set only for Qos1 or Qos2. */ + assert( ( !pPublishInfo->dup ) || ( pPublishInfo->qos > MQTTQoS0 ) ); + + /* Get the start address of the buffer. */ + pIndex = pFixedBuffer->pBuffer; + + if( pPublishInfo->qos == MQTTQoS1 ) + { + IotLogDebug( "Adding QoS as QoS1 in PUBLISH flags." ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); + } + else if( pPublishInfo->qos == MQTTQoS2 ) + { + IotLogDebug( "Adding QoS as QoS2 in PUBLISH flags." ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( pPublishInfo->retain ) + { + IotLogDebug( "Adding retain bit in PUBLISH flags." ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + } + + if( pPublishInfo->dup ) + { + IotLogDebug( "Adding dup bit in PUBLISH flags." ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ); + } + + *pIndex = publishFlags; + pIndex++; + + /* The "Remaining length" is encoded from the second byte. */ + pIndex = encodeRemainingLength( pIndex, remainingLength ); + + /* The topic name is placed after the "Remaining length". */ + pIndex = encodeString( pIndex, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + + /* A packet identifier is required for QoS 1 and 2 messages. */ + if( pPublishInfo->qos > MQTTQoS0 ) + { + IotLogDebug( "Adding packet Id in PUBLISH packet." ); + /* Place the packet identifier into the PUBLISH packet. */ + *pIndex = UINT16_HIGH_BYTE( packetIdentifier ); + *( pIndex + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pIndex += 2; + } + + /* The payload is placed after the packet identifier. + * Payload is copied over only if required by the flag serializePayload. + * This will help reduce an unnecessary copy of the payload into the buffer. + */ + if( ( pPublishInfo->payloadLength > 0U ) && + ( serializePayload ) ) + { + IotLogDebugWithArgs( "Copying PUBLISH payload of length =%lu to buffer", + pPublishInfo->payloadLength ); + + /* This memcpy intentionally copies bytes from a void * buffer into + * a uint8_t * buffer. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pIndex, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + pIndex += pPublishInfo->payloadLength; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is less than the buffer size. */ + assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size ); +} + static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, MQTTNetworkContext_t networkContext ) { @@ -269,6 +486,7 @@ static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, else { bytesReceived = recvFunc( networkContext, &encodedByte, 1U ); + if( bytesReceived == 1 ) { remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier; @@ -280,16 +498,18 @@ static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, remainingLength = MQTT_REMAINING_LENGTH_INVALID; } } + if( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { break; } - } while ( ( encodedByte & 0x80U ) != 0U ); + } while( ( encodedByte & 0x80U ) != 0U ); /* Check that the decoded remaining length conforms to the MQTT specification. */ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) { expectedSize = remainingLengthEncodedSize( remainingLength ); + if( bytesDecoded != expectedSize ) { remainingLength = MQTT_REMAINING_LENGTH_INVALID; @@ -319,13 +539,15 @@ static bool incomingPacketValid( uint8_t packetType ) case MQTT_PACKET_TYPE_PINGRESP: status = true; break; - + case ( MQTT_PACKET_TYPE_PUBREL & 0xF0U ): + /* The second bit of a PUBREL must be set. */ if( packetType & 0x02U ) { status = true; } + break; /* Any other packet type is invalid. */ @@ -369,6 +591,7 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, status = MQTTBadResponse; } } + return status; } @@ -426,6 +649,7 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, IotLogDebug( "DUP is 0." ); } } + return status; } @@ -434,6 +658,7 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, static void logConnackResponse( uint8_t responseCode ) { assert( responseCode <= 5 ); + /* Declare the CONNACK response code strings. The fourth byte of CONNACK * indexes into this array for the corresponding response. This array * does not need to be allocated if logs are not enabled. */ @@ -448,7 +673,7 @@ static void logConnackResponse( uint8_t responseCode ) "Connection refused: not authorized." /* 5 */ }; IotLogErrorWithArgs( "%s", pConnackResponses[ responseCode ] ); - #endif + #endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ } /*-----------------------------------------------------------*/ @@ -703,6 +928,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming IotLogDebugWithArgs( "Payload length %hu.", pPublishInfo->payloadLength ); } + return status; } @@ -843,8 +1069,8 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo pIndex++; /* The remaining length of the CONNECT packet is encoded starting from the - * second byte. The remaining length does not include the length of the fixed - * header or the encoding of the remaining length. */ + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ pIndex = encodeRemainingLength( pIndex, remainingLength ); /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable @@ -971,7 +1197,39 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish size_t * const pRemainingLength, size_t * const pPacketSize ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + + if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pPublishInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pPublishInfo, + pRemainingLength, + pPacketSize ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + { + IotLogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + status = MQTTBadParameter; + } + else + { + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( calculatePublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) + { + IotLogErrorWithArgs( "PUBLISH packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + status = MQTTBadParameter; + } + } + + return status; } /*-----------------------------------------------------------*/ @@ -981,7 +1239,56 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + size_t packetSize = 0UL; + + if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " + "pPublishInfo=%p.", + pBuffer, + pPublishInfo ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + { + IotLogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) + { + IotLogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ); + status = MQTTBadParameter; + } + + /* Check if the serialized packet can fit in the buffer. + * Length of serialized packet = First byte + Length of encoded remaining length + * + Remaining length. + */ + else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength ) + > pBuffer->size ) + { + IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH packet of size of %lu.", + pBuffer->size, + packetSize ); + status = MQTTNoMemory; + } + else + { + /* Serialize publish with header and payload. */ + serializePublishCommon( pPublishInfo, + remainingLength, + packetId, + pBuffer, + true ); + } + + return status; } /*-----------------------------------------------------------*/ @@ -992,7 +1299,61 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli const MQTTFixedBuffer_t * const pBuffer, size_t * const pHeaderSize ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + size_t packetSize = 0UL; + + if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || + ( pHeaderSize == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " + "pPublishInfo=%p, pHeaderSize=%p.", + pBuffer, + pPublishInfo, + pHeaderSize ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) + { + IotLogErrorWithArgs( "Invalid topic name for publish: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) + { + IotLogErrorWithArgs( "Packet Id is 0 for publish with QoS=%u.", + pPublishInfo->qos ); + status = MQTTBadParameter; + } + + /* Check if the serialized packet can fit in the buffer. + * Length of serialized packet = First byte + Length of encoded remaining length + * + Remaining length - Payload Length. + */ + else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength - pPublishInfo->payloadLength ) > pBuffer->size ) + { + IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH header packet of size of %lu.", + pBuffer->size, + packetSize ); + status = MQTTNoMemory; + } + else + { + /* Serialize publish without copying the payload. */ + serializePublishCommon( pPublishInfo, + remainingLength, + packetId, + pBuffer, + false ); + + /* Header size is the same as calculated packet size. */ + *pHeaderSize = packetSize; + } + + return status; } /*-----------------------------------------------------------*/ @@ -1023,11 +1384,12 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, case MQTT_PACKET_TYPE_PUBREC: case MQTT_PACKET_TYPE_PUBREL: case MQTT_PACKET_TYPE_PUBCOMP: - pBuffer->pBuffer[0] = packetType; - pBuffer->pBuffer[1] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; - pBuffer->pBuffer[2] = UINT16_HIGH_BYTE( packetId ); - pBuffer->pBuffer[3] = UINT16_LOW_BYTE( packetId ); + pBuffer->pBuffer[ 0 ] = packetType; + pBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; + pBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId ); + pBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId ); break; + default: IotLogErrorWithArgs( "Packet type is not a publish ACK: Packet type=%02x", packetType ); @@ -1118,7 +1480,7 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa { status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); } - + return status; } @@ -1168,7 +1530,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket case MQTT_PACKET_TYPE_PUBCOMP: status = deserializeSimpleAck( pIncomingPacket, pPacketId ); break; - + /* Any other packet type is invalid. */ default: IotLogErrorWithArgs( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ); @@ -1189,6 +1551,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu MQTTStatus_t status = MQTTSuccess; /* Read a single byte. */ int32_t bytesReceived = readFunc( networkContext, &( pIncomingPacket->type ), 1U ); + if( bytesReceived == 1 ) { /* Check validity. */ @@ -1207,13 +1570,12 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu pIncomingPacket->type ); status = MQTTBadResponse; } - } else { status = MQTTNoDataAvailable; } - + return status; } From 4d7c8a2de6b37fb833a293061ecc43b0a8937c52 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 7 May 2020 10:46:58 -0700 Subject: [PATCH 489/844] Update .gitmodules Update the http-parser path to the public path for CI Infrastructure. --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index b1f9d94da0..821dcb2a3d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libraries/standard/http/third_party/http_parser"] path = libraries/standard/http/third_party/http_parser - url = git@github.com:nodejs/http-parser.git + url = https://github.com/nodejs/http-parser.git From 6acde62b6fba971f068563f9f2744bef7216be83 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 7 May 2020 16:57:35 -0700 Subject: [PATCH 490/844] HTTPClient_ReadHeader() Implementation Part 1 (#923) --- libraries/standard/http/include/http_client.h | 63 +++-- libraries/standard/http/src/http_client.c | 177 +++++++++++++- .../standard/http/src/http_client_parse.c | 20 ++ .../http/src/private/http_client_internal.h | 1 - .../http/src/private/http_client_parse.h | 25 ++ libraries/standard/http/test/Makefile | 7 +- .../http/test/test-HTTPClient_ReadHeader.c | 231 ++++++++++++++++++ 7 files changed, 489 insertions(+), 35 deletions(-) create mode 100644 libraries/standard/http/test/test-HTTPClient_ReadHeader.c diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 50e45a44c6..8f1898e0b3 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -219,6 +219,14 @@ typedef enum HTTPStatus HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER, HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, /* TODO: Add return codes as implementation continues. */ + + /** + * @brief The requested header field was not found in the response buffer. + * + * Functions that may return this value: + * - #HTTPClient_ReadHeader + */ + HTTP_HEADER_NOT_FOUND, /* Temporary error code while implementation is in progress. */ HTTP_NOT_SUPPORTED, } HTTPStatus_t; @@ -306,9 +314,9 @@ typedef struct httpResponseParsingCallback * @param[in] statusCode The HTTP response status-code. */ void ( * onHeaderCallback )( void * pContext, - const char * fieldLoc, + const uint8_t * fieldLoc, size_t fieldLen, - const char * valueLoc, + const uint8_t * valueLoc, size_t valueLen, uint16_t statusCode ); @@ -550,28 +558,43 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, HTTPResponse_t * pResponse ); /** - * @brief Read a header from the completed response #HTTPResponse_t. This will - * return the response header value location within #HTTPResponse_t.pBuffer. + * @brief Read a header from a buffer containing a complete HTTP response. + * This will return the location of the response header value in the + * #HTTPResponse_t.pBuffer buffer. + * + * @note This function should only be called on a complete HTTP response. If the + * request is sent through the #HTTPClient_Send function, the #HTTPResponse_t is + * incomplete until #HTTPClient_Send returns. + * + * @param[in] pResponse The buffer containing the completed HTTP response. + * @param[in] pHeaderName The header field name to read. + * @param[in] headerNameLen The length of the header field name in bytes. + * @param[out] pHeaderValueLoc This will be populated with the location of the + * header value in the response buffer, #HTTPResponse_t.pBuffer. + * @param[out] headerValueLen This will be populated with the length of the + * header value in bytes. * - * This function should be used only a completed response. A #HTTPResponse_t is - * not complete until #HTTPClient_Send returns. + * @return One of the following: + * - #HTTP_SUCCESS (If successful.) + * - #HTTP_INVALID_PARAMETER (If any provided parameters or their members are invalid.) + * - #HTTP_PARTIAL_RESPONSE (Part of an HTTP response was received in a partially filled response buffer.) + * - #HTTP_HEADER_NOT_FOUND (Header is not found in the passed response buffer.) + */ +HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, + const uint8_t * pHeaderName, + size_t headerNameLen, + const uint8_t ** pHeaderValueLoc, + size_t * headerValueLen ); + +/** + * @brief Error code to string conversion utility for HTTP Client library. * - * TODO: Expand documentation. + * @note This returns constant strings, which should not be modified. * - * @param[in] pResponse Completed response. - * @param[in] pName The header field name to read. - * @param[in] nameLen The length of the header field name in bytes. - * @param[out] pValue The location of the header value in - * #HTTPResponse_t.pBuffer. - * @param[out] valueLen The length of the header value in bytes. + * @param[in] status The status code to convert to a string. * - * @return #HTTP_SUCCESS if successful, an error code otherwise. - * TODO: Update for exact error codes returned. + * @return The string representation of the status code. */ -HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, - const char * pName, - size_t nameLen, - char ** pValue, - size_t * valueLen ); +const char * HTTPClient_strerror( HTTPStatus_t status ); #endif /* ifndef HTTP_CLIENT_H_ */ diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 5522c98895..9f09de5fb5 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -920,7 +920,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, } else { - IotLogDebug( "Response ignored: pResponse is NULL. " ); + IotLogDebug( "Response ignored: pResponse is NULL." ); } } @@ -930,18 +930,171 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, - const char * pName, - size_t nameLen, - char ** pValue, - size_t * valueLen ) + const uint8_t * pHeaderName, + size_t headerNameLen, + const uint8_t ** pHeaderValueLoc, + size_t * pHeaderValueLen ) { - /* Disable unused parameter warnings. */ - ( void ) pResponse; - ( void ) pName; - ( void ) nameLen; - ( void ) pValue; - ( void ) valueLen; - return HTTP_NOT_SUPPORTED; + HTTPStatus_t returnStatus = HTTP_SUCCESS; + HTTPParsingContext_t parsingContext; + + memset( &parsingContext, 0, sizeof( HTTPParsingContext_t ) ); + + if( pResponse == NULL ) + { + IotLogError( "Parameter check failed: pResponse is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pResponse->pBuffer == NULL ) + { + IotLogError( "Parameter check failed: pResponse->pBuffer is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pResponse->bufferLen == 0u ) + { + IotLogError( "Parameter check failed: pResponse->bufferLen is 0: " + "Buffer len should be > 0." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pHeaderName == NULL ) + { + IotLogError( "Parameter check failed: Input header name is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( headerNameLen == 0u ) + { + IotLogError( "Parameter check failed: Input header name length is 0: " + "headerNameLen should be > 0." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pHeaderValueLoc == NULL ) + { + IotLogError( "Parameter check failed: Output parameter for header value location is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else if( pHeaderValueLen == NULL ) + { + IotLogError( "Parameter check failed: Output parameter for header value length is NULL." ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else + { + /* Empty else for MISRA 15.7 compliance. */ + } + + if( returnStatus == HTTP_SUCCESS ) + { + /* Initialize Parsing context with our callback. */ + returnStatus = HTTPClient_InitializeParsingContext( &parsingContext, NULL ); + } + + if( returnStatus == HTTP_SUCCESS ) + { + returnStatus = HTTPClient_FindHeaderInResponse( &parsingContext, + pResponse->pBuffer, + pResponse->bufferLen, + pHeaderName, + headerNameLen, + pHeaderValueLoc, + pHeaderValueLen ); + + if( returnStatus == HTTP_SUCCESS ) + { + /* Header value found present in buffer. */ + IotLogDebugWithArgs( "Found requested header in response: " + "HeaderName=%.*s, ValueLoc=%.*s", + headerNameLen, pHeaderName, + *pHeaderValueLen, *pHeaderValueLoc ); + } + else if( returnStatus == HTTP_HEADER_NOT_FOUND ) + { + /* Header is not present in buffer. */ + IotLogWarnWithArgs( "Header field not found in response buffer: " + "HeaderName=%.*s", headerNameLen, pHeaderName ); + } + else + { + IotLogErrorWithArgs( "Unable to read header from response: " + "Failure in parsing response for header field: " + "HeaderName=%.*s, ParserError=%s", + headerNameLen, pHeaderName, + HTTPClient_strerror( returnStatus ) ); + } + } + else + { + IotLogErrorWithArgs( "Failed to read header from response: " + "Unable to initialize parsing context: " + "HeaderName=%.*s", headerNameLen, pHeaderName ); + } + + return returnStatus; } +/*-----------------------------------------------------------*/ + +const char * HTTPClient_strerror( HTTPStatus_t status ) +{ + const char * str = NULL; + + switch( status ) + { + case HTTP_SUCCESS: + str = "HTTP_SUCCESS"; + break; + + case HTTP_INVALID_PARAMETER: + str = "HTTP_INVALID_PARAMETER"; + break; + + case HTTP_NETWORK_ERROR: + str = "HTTP_NETWORK_ERROR"; + break; + + case HTTP_PARTIAL_RESPONSE: + str = "HTTP_PARTIAL_RESPONSE"; + break; + + case HTTP_NO_RESPONSE: + str = "HTTP_NO_RESPONSE"; + break; + + case HTTP_INSUFFICIENT_MEMORY: + str = "HTTP_INSUFFICIENT_MEMORY"; + break; + + case HTTP_INTERNAL_ERROR: + str = "HTTP_INTERNAL_ERROR"; + break; + + case HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED: + str = "HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED"; + break; + + case HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER: + str = "HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER"; + break; + + case HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH: + str = "HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH"; + break; + + case HTTP_HEADER_NOT_FOUND: + str = "HTTP_HEADER_NOT_FOUND"; + break; + + case HTTP_NOT_SUPPORTED: + str = "HTTP_NOT_SUPPORTED"; + break; + + default: + IotLogWarnWithArgs( "Invalid status code received for string conversion: " + "StatusCode=%d", status ); + } + + return str; +} + + + /*-----------------------------------------------------------*/ diff --git a/libraries/standard/http/src/http_client_parse.c b/libraries/standard/http/src/http_client_parse.c index 83085329ee..bc5b8777a0 100644 --- a/libraries/standard/http/src/http_client_parse.c +++ b/libraries/standard/http/src/http_client_parse.c @@ -22,3 +22,23 @@ HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, pParsingContext->state = HTTP_PARSING_COMPLETE; return HTTP_SUCCESS; } + +HTTPStatus_t HTTPClient_FindHeaderInResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen, + const uint8_t * pField, + size_t fieldLen, + const uint8_t ** pValue, + size_t * valueLen ) +{ + /* Disable unused parameter warnings. */ + ( void ) pParsingContext; + ( void ) pBuffer; + ( void ) bufferLen; + ( void ) pField; + ( void ) fieldLen; + ( void ) pValue; + ( void ) valueLen; + + return HTTP_NOT_SUPPORTED; +} diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 2c9ad99c04..2df759aedc 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -104,5 +104,4 @@ ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DECIMAL_DIGITS + \ 1u /* Dash character '-' */ + MAX_INT32_NO_OF_DECIMAL_DIGITS ) - #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/src/private/http_client_parse.h b/libraries/standard/http/src/private/http_client_parse.h index 4e45991386..b681383d2a 100644 --- a/libraries/standard/http/src/private/http_client_parse.h +++ b/libraries/standard/http/src/private/http_client_parse.h @@ -68,4 +68,29 @@ HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, const uint8_t * pBuffer, size_t bufferLen ); +/** + * @brief Find the specified header field in the response buffer. + * + * @param[in] pParsingContext The state of the of the response parsing. + * @param[in] pBuffer The response buffer to parse. + * @param[in] bufferLen The length of the response buffer to parse. + * @param[in] pField The header field to search for. + * @param[in] fieldLen The length of pField. + * @param[out] pValue The location of the the header value found in pBuffer. + * @param[out] valueLen The length of pValue. + * + * @return One of the following: + * - #HTTP_SUCCESS when header is found in the response. + * - #HTTP_HEADER_NOT_FOUND if requested header is not found in response. + * - #HTTP_INTERNAL_ERROR for any parsing errors. + */ +HTTPStatus_t HTTPClient_FindHeaderInResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen, + const uint8_t * pField, + size_t fieldLen, + const uint8_t ** pValue, + size_t * valueLen ); + + #endif /* ifndef HTTP_CLIENT_PARSE_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 2eab0a96c7..5fb9f40027 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -5,7 +5,7 @@ CSDK_ROOT = ../../../.. SOURCE = ../src # Show extra warnings. -CFLAGS += -Wextra +CFLAGS += -Wextra -O0 -ggdb # arguments given to the compiler (default: see the included file) #CFLAGS = std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb @@ -36,7 +36,9 @@ FUNCTIONS = addHeader \ receiveAndParseHttpResponse \ HTTPClient_InitializeParsingContext \ HTTPClient_ParseResponse \ - getFinalResponseStatus + getFinalResponseStatus \ + readHeaderParsingCallback \ + HTTPClient_strerror # Changes to the above variables should remain above this include. include Mock4thewin.mk @@ -55,6 +57,7 @@ HTTPClient_Send.c: sendHttpHeaders.c \ HTTPClient_ParseResponse.c \ getFinalResponseStatus.c HTTPClient_InitializeRequestHeaders.c: writeRequestLine.c addHeader.c +HTTPClient_ReadHeader.c: HTTPClient_strerror.c # additional header dependencies for all tests COMMON = common.h diff --git a/libraries/standard/http/test/test-HTTPClient_ReadHeader.c b/libraries/standard/http/test/test-HTTPClient_ReadHeader.c new file mode 100644 index 0000000000..e8dfad19db --- /dev/null +++ b/libraries/standard/http/test/test-HTTPClient_ReadHeader.c @@ -0,0 +1,231 @@ +#include +#include + +#include "common.h" + +/* Functions are pulled out into their own C files to be tested as a unit. */ +#include "HTTPClient_strerror.c" +#include "HTTPClient_ReadHeader.c" + +/* Template HTTP request for a PUT request. */ +static const char * pTestResponse = "HTTP/1.1 200 OK" + "test-header0: test-value0\r\n" + "test-header1: test-value1\r\n" + "test-header2: test-value2\r\n" + "\r\n"; +static const size_t headerFieldInRespLoc = 15; +static const size_t headerFieldInRespLen = strlen( "test-header0" ); +static const size_t headerValInRespLoc = 29; +static const size_t headerValInRespLen = strlen( "test-value0" ); + +#define HEADER_IN_BUFFER "test-header0" +#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" + +/* Mocked out implementations of parser function dependencies. */ +static HTTPStatus_t initializeParsingContextRetCode = HTTP_SUCCESS; +HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, + HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) +{ + ( void ) pHeaderParsingCallback; + + /* Verify the input arguments. */ + ok( pParsingContext != NULL ); + + return initializeParsingContextRetCode; +} + +static HTTPStatus_t findHeaderInRespRetCode = HTTP_SUCCESS; +static const uint8_t * pExpectedField = NULL; +static size_t expectedFieldSize = 0u; +static const uint8_t * pValLocToReturn = NULL; +static size_t valLenToReturn = 0u; +HTTPStatus_t HTTPClient_FindHeaderInResponse( HTTPParsingContext_t * pParsingContext, + const uint8_t * pBuffer, + size_t bufferLen, + const uint8_t * pField, + size_t fieldLen, + const uint8_t ** pValue, + size_t * pValueLen ) +{ + /* Verify the input arguments. */ + ok( pParsingContext != NULL ); + ok( pBuffer == ( const uint8_t * ) pTestResponse ); + ok( bufferLen == strlen( pTestResponse ) ); + ok( bufferLen != 0u ); + ok( pField != NULL ); + ok( fieldLen == expectedFieldSize ); + ok( 0u == memcmp( pField, pExpectedField, fieldLen ) ); + ok( pValue != NULL ); + ok( pValueLen != NULL ); + + /* Side-effects of mock'd implementation. */ + *pValue = pValLocToReturn; + *pValueLen = valLenToReturn; + + return findHeaderInRespRetCode; +} + +int main() +{ + HTTPResponse_t testResponse = { 0 }; + HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; + const uint8_t * pValueLoc = NULL; + size_t valueLen = 0u; + +#define reset() \ + do { \ + retCode = HTTP_NOT_SUPPORTED; \ + memset( &testResponse, 0, sizeof( testResponse ) ); \ + pValueLoc = NULL; \ + valueLen = 0u; \ + initializeParsingContextRetCode = HTTP_SUCCESS; \ + findHeaderInRespRetCode = HTTP_SUCCESS; \ + pExpectedField = NULL; \ + expectedFieldSize = 0u; \ + pValLocToReturn = NULL; \ + valLenToReturn = 0u; \ + } while( 0 ) + + plan( 44 ); + + /*************************** Test Failure Cases *****************************/ + + /* Response parameter is NULL. */ + reset(); + retCode = HTTPClient_ReadHeader( NULL, + "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Underlying buffer is NULL in the response parameter. */ + reset(); + retCode = HTTPClient_ReadHeader( &testResponse, + "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Response buffer size is zero. */ + reset(); + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + retCode = HTTPClient_ReadHeader( &testResponse, + "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Header field name is NULL. */ + reset(); + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + NULL, + strlen( "Header" ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Header field length is 0. */ + reset(); + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + "Header", + 0u, + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Invalid output parameters. */ + reset(); + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + "Header", + strlen( "Header" ), + NULL, + &valueLen ); + ok( retCode == HTTP_INVALID_PARAMETER ); + reset(); + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + "Header", + strlen( "Header" ), + &pValueLoc, + NULL ); + ok( retCode == HTTP_INVALID_PARAMETER ); + + /* Test when HTTPClient_InitializeParsingContext returns failure. */ + reset(); + initializeParsingContextRetCode = HTTP_INTERNAL_ERROR; + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INTERNAL_ERROR ); + + /* Test when HTTPClient_ParseResponse returns failure. */ + reset(); + /* Configure the HTTPClient_FindResponseInHeader mock. */ + findHeaderInRespRetCode = HTTP_INTERNAL_ERROR; + pExpectedField = HEADER_IN_BUFFER; + expectedFieldSize = strlen( HEADER_IN_BUFFER ); + /* Call the function under test. */ + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INTERNAL_ERROR ); + + /* Test when requested header is not present in response. */ + + reset(); + /* Configure the HTTPClient_FindResponseInHeader mock. */ + findHeaderInRespRetCode = HTTP_HEADER_NOT_FOUND; + pExpectedField = HEADER_NOT_IN_BUFFER; + expectedFieldSize = strlen( HEADER_NOT_IN_BUFFER ); + /* Call the function under test. */ + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + HEADER_NOT_IN_BUFFER, + strlen( HEADER_NOT_IN_BUFFER ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_HEADER_NOT_FOUND ); + + /*************************** Test Happy-Path Cases *****************************/ + + /* Test when requested header in present response. */ + + reset(); + /* Configure the HTTPClient_FindResponseInHeader mock. */ + pExpectedField = HEADER_IN_BUFFER; + expectedFieldSize = strlen( HEADER_IN_BUFFER ); + pValLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valLenToReturn = headerValInRespLen; + /* Call the function under test. */ + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_SUCCESS ); + ok( pValueLoc == ( const uint8_t * ) &pTestResponse[ headerValInRespLoc ] ); + ok( valueLen == headerValInRespLen ); + + return grade(); +} From 6a0e4c3ec64ecd84b5783c65df12ef6e44413f59 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 8 May 2020 08:15:22 -0700 Subject: [PATCH 491/844] Implement MQTT SUBSCRIBE and UNSUBSCRIBE (#916) * Implement MQTT SUBSCRIBE and UNSUBSCRIBE Please find the list of public API functions implemented 1. MQTT_Subscribe 2. MQTT_Unsubscribe 3. MQTT_GetSubscribePacketSize 4. MQTT_SerializeSubscribe 5. MQTT_GetUnsubscribePacketSize 6. MQTT_SerializeUnsubscribe --- libraries/standard/mqtt/include/mqtt.h | 36 +- .../standard/mqtt/include/mqtt_lightweight.h | 105 ++++-- libraries/standard/mqtt/src/mqtt.c | 170 ++++++++- .../standard/mqtt/src/mqtt_lightweight.c | 354 +++++++++++++++++- 4 files changed, 623 insertions(+), 42 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index eb6fe58579..2637f518ce 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -139,9 +139,25 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pWillInfo, bool * const pSessionPresent ); +/** + * @brief Sends MQTT SUBSCRIBE for the given list of topic filters to + * the broker. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * + * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to + * hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport write failed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, - size_t subscriptionCount ); + size_t subscriptionCount, + uint16_t packetId ); /** * @brief Publishes a message to the given topic name. @@ -161,9 +177,25 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ); +/** + * @brief Sends MQTT UNSUBSCRIBE for the given list of topic filters to + * the broker. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * + * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to + * hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport write failed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, - size_t subscriptionCount ); + size_t subscriptionCount, + uint16_t packetId ); /** * @brief Disconnect an MQTT session. diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index f57f138c5c..46cf59a213 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -26,9 +26,9 @@ #if __STDC_VERSION__ >= 199901L #include #else - #define bool signed char - #define false 0 - #define true 1 + #define bool signed char + #define false 0 + #define true 1 #endif #include @@ -37,17 +37,19 @@ #include "config.h" /* MQTT packet types. */ -#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ -#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBREC ( ( uint8_t ) 0x50U ) /**< @brief PUBREC (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBREL ( ( uint8_t ) 0x62U ) /**< @brief PUBREL (bidirectional). */ -#define MQTT_PACKET_TYPE_PUBCOMP ( ( uint8_t ) 0x70U ) /**< @brief PUBCOMP (bidirectional). */ -#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */ -#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ -#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBREC ( ( uint8_t ) 0x50U ) /**< @brief PUBREC (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBREL ( ( uint8_t ) 0x62U ) /**< @brief PUBREL (bidirectional). */ +#define MQTT_PACKET_TYPE_PUBCOMP ( ( uint8_t ) 0x70U ) /**< @brief PUBCOMP (bidirectional). */ +#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xA2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -86,16 +88,16 @@ typedef int32_t (* MQTTTransportRecvFunc_t )( MQTTNetworkContext_t context, */ typedef enum MQTTStatus { - MQTTSuccess = 0, /**< Function completed successfully. */ - MQTTBadParameter, /**< At least one parameter was invalid. */ - MQTTNoMemory, /**< A provided buffer was too small. */ - MQTTSendFailed, /**< The transport send function failed. */ - MQTTRecvFailed, /**< The transport receive function failed. */ - MQTTBadResponse, /**< An invalid packet was received from the server. */ - MQTTServerRefused, /**< The server refused a CONNECT or SUBSCRIBE. */ - MQTTNoDataAvailable,/**< No data available from the transport interface. */ - MQTTIllegalState, /**< An illegal state in the state record. */ - MQTTStateCollision /**< A collision with an existing state record entry. */ + MQTTSuccess = 0, /**< Function completed successfully. */ + MQTTBadParameter, /**< At least one parameter was invalid. */ + MQTTNoMemory, /**< A provided buffer was too small. */ + MQTTSendFailed, /**< The transport send function failed. */ + MQTTRecvFailed, /**< The transport receive function failed. */ + MQTTBadResponse, /**< An invalid packet was received from the server. */ + MQTTServerRefused, /**< The server refused a CONNECT or SUBSCRIBE. */ + MQTTNoDataAvailable, /**< No data available from the transport interface. */ + MQTTIllegalState, /**< An illegal state in the state record. */ + MQTTStateCollision /**< A collision with an existing state record entry. */ } MQTTStatus_t; /** @@ -286,17 +288,70 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ); -MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, +/** + * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE packet. + * @param[out] pPacketSize The total size of the MQTT SUBSCRIBE packet. + * + * @return #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; #MQTTSuccess otherwise. + */ +MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ); +/** + * @brief Serialize an MQTT SUBSCRIBE packet in the given buffer. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetSubscribePacketSize. + * @param[out] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ); +/** + * @brief Get packet size and Remaining Length of an MQTT UNSUBSCRIBE packet. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[out] pRemainingLength The Remaining Length of the MQTT UNSUBSCRIBE packet. + * @param[out] pPacketSize The total size of the MQTT UNSUBSCRIBE packet. + * + * @return #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; #MQTTSuccess otherwise. + */ +MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ); + +/** + * @brief Serialize an MQTT UNSUBSCRIBE packet in the given buffer. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetUnsubscribePacketSize. + * @param[out] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, uint16_t packetId, diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 9caa6f7432..6202ecf1b5 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -24,6 +24,8 @@ #include "mqtt.h" #include "private/mqtt_internal.h" +/*-----------------------------------------------------------*/ + /** * @brief Sends provided buffer to network using transport send. * @@ -33,6 +35,28 @@ * * @return Total number of bytes sent; -1 if there is an error. */ +static int32_t sendPacket( MQTTContext_t * pContext, + const uint8_t * pBufferToSend, + size_t bytesToSend ); + +/** + * @brief Validates parameters of #MQTT_Subscribe or #MQTT_Unsubscribe. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId Packet identifier. + * + * @return #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * const pContext, + const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId ); + +/*-----------------------------------------------------------*/ + static int32_t sendPacket( MQTTContext_t * pContext, const uint8_t * pBufferToSend, size_t bytesToSend ) @@ -75,6 +99,42 @@ static int32_t sendPacket( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ +static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * const pContext, + const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId ) +{ + MQTTStatus_t status = MQTTSuccess; + + /* Validate all the parameters. */ + if( ( pContext == NULL ) || ( pSubscriptionList == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pSubscriptionList=%p.", + pContext, + pSubscriptionList ); + status = MQTTBadParameter; + } + else if( subscriptionCount == 0UL ) + { + IotLogError( "Subscription count is 0." ); + status = MQTTBadParameter; + } + else if( packetId == 0U ) + { + IotLogError( "Packet Id for subscription packet is 0." ); + status = MQTTBadParameter; + } + else + { + /* Empty else MISRA 15.7 */ + } + + return status; +} + +/*-----------------------------------------------------------*/ + void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, @@ -158,9 +218,60 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, - size_t subscriptionCount ) + size_t subscriptionCount, + uint16_t packetId ) { - return MQTTSuccess; + size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; + int32_t bytesSent = 0; + + /* Validate arguments. */ + MQTTStatus_t status = validateSubscribeUnsubscribeParams( pContext, + pSubscriptionList, + subscriptionCount, + packetId ); + + if( status == MQTTSuccess ) + { + /* Get the remaining length and packet size.*/ + status = MQTT_GetSubscribePacketSize( pSubscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + IotLogDebugWithArgs( "SUBSCRIBE packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); + } + + if( status == MQTTSuccess ) + { + /* Serialize MQTT SUBSCRIBE packet. */ + status = MQTT_SerializeSubscribe( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + &( pContext->networkBuffer ) ); + } + + if( status == MQTTSuccess ) + { + /* Send serialized MQTT SUBSCRIBE packet to transport layer. */ + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + packetSize ); + + if( bytesSent < 0 ) + { + IotLogError( "Transport send failed for SUBSCRIBE packet." ); + status = MQTTSendFailed; + } + else + { + IotLogDebugWithArgs( "Sent %d bytes of SUBSCRIBE packet.", + bytesSent ); + } + } + + return status; } /*-----------------------------------------------------------*/ @@ -267,9 +378,60 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, - size_t subscriptionCount ) + size_t subscriptionCount, + uint16_t packetId ) { - return MQTTSuccess; + size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; + int32_t bytesSent = 0; + + /* Validate arguments. */ + MQTTStatus_t status = validateSubscribeUnsubscribeParams( pContext, + pSubscriptionList, + subscriptionCount, + packetId ); + + if( status == MQTTSuccess ) + { + /* Get the remaining length and packet size.*/ + status = MQTT_GetUnsubscribePacketSize( pSubscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + IotLogDebugWithArgs( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); + } + + if( status == MQTTSuccess ) + { + /* Serialize MQTT UNSUBSCRIBE packet. */ + status = MQTT_SerializeUnsubscribe( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + &( pContext->networkBuffer ) ); + } + + if( status == MQTTSuccess ) + { + /* Send serialized MQTT UNSUBSCRIBE packet to transport layer. */ + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + packetSize ); + + if( bytesSent < 0 ) + { + IotLogError( "Transport send failed for UNSUBSCRIBE packet." ); + status = MQTTSendFailed; + } + else + { + IotLogDebugWithArgs( "Sent %d bytes of UNSUBSCRIBE packet.", + bytesSent ); + } + } + + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 42610efaf8..b9343ab528 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -40,12 +40,6 @@ */ #define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) -/** - * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT - * packet is this value. - */ -#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) - /* MQTT CONNECT flags. */ #define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ #define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ @@ -61,7 +55,7 @@ */ #define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief MQTT PUBLISH retain flag. */ #define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief MQTT PUBLISH QoS1 flag. */ -#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief MQTT PUBLISH QoS1 flag. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief MQTT PUBLISH QoS2 flag. */ #define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief MQTT PUBLISH duplicate flag. */ /** @@ -92,6 +86,12 @@ #define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ #define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +/** + * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT + * packet is this value. + */ +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) + /** * @brief Set a bit in an 8-bit unsigned integer. */ @@ -140,6 +140,15 @@ /*-----------------------------------------------------------*/ +/* MQTT Subscription packet types. */ +typedef enum MQTTSubscriptionType +{ + MQTT_SUBSCRIBE, + MQTT_UNSUBSCRIBE +} MQTTSubscriptionType_t; + +/*-----------------------------------------------------------*/ + /** * @brief Serializes MQTT PUBLISH packet into the buffer provided. * @@ -178,6 +187,47 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, size_t * pRemainingLength, size_t * pPacketSize ); +/** + * @brief Calculates the packet size and remaining length of an MQTT + * SUBSCRIBE or UNSUBSCRIBE packet. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE or + * UNSUBSCRIBE packet. + * @param[out] pPacketSize The total size of the MQTT MQTT SUBSCRIBE or + * UNSUBSCRIBE packet. + * @param[in] subscriptionType #MQTT_SUBSCRIBE or #MQTT_UNSUBSCRIBE. + * + * #MQTTBadParameter if the packet would exceed the size allowed by the + * MQTT spec; #MQTTSuccess otherwise. + */ +static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize, + MQTTSubscriptionType_t subscriptionType ); + +/** + * @brief Validates parameters of #MQTT_SerializeSubscribe or + * #MQTT_SerializeUnsubscribe. + * + * @param[in] pSubscriptionList List of MQTT subscription info. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] packetId Packet identifier. + * @param[in] remainingLength Remaining length of the packet. + * @param[in] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ); + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -757,6 +807,72 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, /*-----------------------------------------------------------*/ +static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize, + MQTTSubscriptionType_t subscriptionType ) +{ + MQTTStatus_t status = MQTTSuccess; + size_t i = 0, packetSize = 0; + + assert( pSubscriptionList != NULL ); + assert( subscriptionCount != 0U ); + assert( pRemainingLength != NULL ); + assert( pPacketSize != NULL ); + + /* The variable header of a subscription packet consists of a 2-byte packet + * identifier. */ + packetSize += sizeof( uint16_t ); + + /* Sum the lengths of all subscription topic filters; add 1 byte for each + * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ + for( i = 0; i < subscriptionCount; i++ ) + { + /* Add the length of the topic filter. MQTT strings are prepended + * with 2 byte string length field. Hence 2 bytes are added to size. */ + packetSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t ); + + /* Only SUBSCRIBE packets include the QoS. */ + if( subscriptionType == MQTT_SUBSCRIBE ) + { + packetSize += 1U; + } + } + + /* At this point, the "Remaining length" has been calculated. Return error + * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, + * set the output parameter.*/ + if( packetSize > MQTT_MAX_REMAINING_LENGTH ) + { + IotLogErrorWithArgs( "Subscription packet length of %lu exceeds" + "the MQTT 3.1.1 maximum packet length of %lu.", + packetSize, + MQTT_MAX_REMAINING_LENGTH ); + status = MQTTBadParameter; + } + else + { + *pRemainingLength = packetSize; + + /* Calculate the full size of the subscription packet by adding + * number of bytes required to encode the "Remaining length" field + * plus 1 byte for the "Packet type" field. */ + packetSize += 1U + remainingLengthEncodedSize( packetSize ); + + /*Set the pPacketSize output parameter. */ + *pPacketSize = packetSize; + } + + IotLogDebugWithArgs( "Subscription packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ); + + return status; +} + +/*-----------------------------------------------------------*/ + static MQTTStatus_t readSubackStatus( size_t statusCount, const uint8_t * pStatusStart ) { @@ -846,6 +962,59 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, /*-----------------------------------------------------------*/ +static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ) +{ + MQTTStatus_t status = MQTTSuccess; + size_t packetSize = 0UL; + + /* Validate all the parameters. */ + if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " + "pSubscriptionList=%p.", + pBuffer, + pSubscriptionList ); + status = MQTTBadParameter; + } + else if( subscriptionCount == 0U ) + { + IotLogError( "Subscription count is 0." ); + status = MQTTBadParameter; + } + else if( packetId == 0U ) + { + IotLogError( "Packet Id for subscription packet is 0." ); + status = MQTTBadParameter; + } + + /* Validate if the passed buffer can hold the serialized packet. + * The serialized packet size = First byte + * + length of encoded size of remaining length + * + remaining length. + */ + else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength ) > pBuffer->size ) + { + IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized packet of size of %lu.", + pBuffer->size, + packetSize ); + status = MQTTNoMemory; + } + else + { + /* Empty else MISRA 15.7 */ + } + + return status; +} + +/*-----------------------------------------------------------*/ + static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, uint16_t * const pPacketId, MQTTPublishInfo_t * const pPublishInfo ) @@ -1161,12 +1330,47 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SubscriptionPacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + + /* Validate parameters. */ + if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || + ( pPacketSize == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pSubscriptionList, + pRemainingLength, + pPacketSize ); + status = MQTTBadParameter; + } + else if( subscriptionCount == 0U ) + { + IotLogError( " subscriptionCount is 0." ); + status = MQTTBadParameter; + } + else + { + /* Calculate the MQTT UNSUBSCRIBE packet size. */ + status = calculateSubscriptionPacketSize( pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize, + MQTT_SUBSCRIBE ); + + if( status == MQTTBadParameter ) + { + IotLogErrorWithArgs( "SUBSCRIBE packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + } + } + + return status; } /*-----------------------------------------------------------*/ @@ -1177,7 +1381,95 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ) { - return MQTTSuccess; + size_t i = 0; + uint8_t * pIndex = NULL; + + /* Validate all the parameters. */ + MQTTStatus_t status = + validateSubscribeUnsubscribeParams( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + pBuffer ); + + if( status == MQTTSuccess ) + { + pIndex = pBuffer->pBuffer; + + /* The first byte in SUBSCRIBE is the packet type. */ + *pIndex = MQTT_PACKET_TYPE_SUBSCRIBE; + pIndex++; + + /* Encode the "Remaining length" starting from the second byte. */ + pIndex = encodeRemainingLength( pIndex, remainingLength ); + + /* Place the packet identifier into the SUBSCRIBE packet. */ + *pIndex = UINT16_HIGH_BYTE( packetId ); + *( pIndex + 1 ) = UINT16_LOW_BYTE( packetId ); + pIndex += 2; + + /* Serialize each subscription topic filter and QoS. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pIndex = encodeString( pIndex, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + /* Place the QoS in the SUBSCRIBE packet. */ + *pIndex = ( uint8_t ) ( pSubscriptionList[ i ].qos ); + pIndex++; + } + + IotLogDebugWithArgs( "Length of serialized SUBSCRIBE packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) +{ + MQTTStatus_t status = MQTTSuccess; + + /* Validate parameters. */ + if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || + ( pPacketSize == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pSubscriptionList, + pRemainingLength, + pPacketSize ); + status = MQTTBadParameter; + } + else if( subscriptionCount == 0U ) + { + IotLogError( "Subscription count is 0." ); + status = MQTTBadParameter; + } + else + { + /* Calculate the MQTT SUBSCRIBE packet size. */ + status = calculateSubscriptionPacketSize( pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize, + MQTT_SUBSCRIBE ); + + if( status == MQTTBadParameter ) + { + IotLogErrorWithArgs( "UNSUBSCRIBE packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + } + } + + return status; } /*-----------------------------------------------------------*/ @@ -1188,7 +1480,47 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + size_t i = 0; + uint8_t * pIndex = NULL; + + /* Validate all the parameters. */ + status = validateSubscribeUnsubscribeParams( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + pBuffer ); + + if( status == MQTTSuccess ) + { + /* Get the start of the buffer to the iterator variable. */ + pIndex = pBuffer->pBuffer; + + /* The first byte in UNSUBSCRIBE is the packet type. */ + *pIndex = MQTT_PACKET_TYPE_UNSUBSCRIBE; + pIndex++; + + /* Encode the "Remaining length" starting from the second byte. */ + pIndex = encodeRemainingLength( pBuffer, remainingLength ); + + /* Place the packet identifier into the UNSUBSCRIBE packet. */ + *pIndex = UINT16_HIGH_BYTE( packetId ); + *( pIndex + 1 ) = UINT16_LOW_BYTE( packetId ); + pIndex += 2; + + /* Serialize each subscription topic filter. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pIndex = encodeString( pIndex, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + } + + IotLogDebugWithArgs( "Length of serialized UNSUBSCRIBE packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + } + + return status; } /*-----------------------------------------------------------*/ From 32d68e85a084e021b4af5c28aa6af40ccecab9d1 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 8 May 2020 08:21:25 -0700 Subject: [PATCH 492/844] Implement MQTT Ping request. (#917) * Implement MQTT Ping request. The public APIs implemented are 1. MQTT_Ping 2. MQTT_SerializePingreq --- libraries/standard/mqtt/include/mqtt.h | 10 ++++++ .../standard/mqtt/include/mqtt_lightweight.h | 15 ++++++++ libraries/standard/mqtt/src/mqtt.c | 36 ++++++++++++++++++- .../standard/mqtt/src/mqtt_lightweight.c | 24 ++++++++++++- 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 2637f518ce..d35909339b 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -175,6 +175,16 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId ); +/** + * @brief Sends an MQTT PINGREQ to broker. + * + * @param[in] pContext Initialized and connected MQTT context. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport write failed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ); /** diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 46cf59a213..909de3eaa6 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -48,9 +48,15 @@ #define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ #define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xA2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ #define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xC0U ) /**< @brief PINGREQ (client-to-server). */ #define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ #define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ +/* + * Constant relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ + struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -453,6 +459,15 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); */ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); +/** + * @brief Serialize an MQTT PINGREQ packet into the given buffer. + * + * @param[out] pBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ); MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 6202ecf1b5..8e537b9bc3 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -371,7 +371,41 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) { - return MQTTSuccess; + int32_t bytesSent = 0; + MQTTStatus_t status = MQTTSuccess; + + if( pContext == NULL ) + { + IotLogError( "pContext is NULL." ); + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + /* Serialize MQTT PINGREQ. */ + status = MQTT_SerializePingreq( &( pContext->networkBuffer ) ); + } + + if( status == MQTTSuccess ) + { + /* Send the serialized PINGREQ packet to transport layer. */ + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + MQTT_PACKET_PINGREQ_SIZE ); + + if( bytesSent < 0 ) + { + IotLogError( "Transport send failed for PINGREQ packet." ); + status = MQTTSendFailed; + } + else + { + IotLogDebugWithArgs( "Sent %d bytes of PINGREQ packet.", + bytesSent ); + } + } + + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index b9343ab528..1604bd0145 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1773,7 +1773,29 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTSuccess; + + if( pBuffer == NULL ) + { + IotLogError( "pBuffer is NULL." ); + status = MQTTBadParameter; + } + else if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) + { + IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PINGREQ packet of size of %lu.", + pBuffer->size, + MQTT_PACKET_PINGREQ_SIZE ); + status = MQTTNoMemory; + } + else + { + /* Ping request packets are always the same. */ + pBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; + pBuffer->pBuffer[ 1 ] = 0x00; + } + + return status; } /*-----------------------------------------------------------*/ From b2bc7d27cbf1d26527b8d9d648addf8706355907 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 8 May 2020 10:39:51 -0700 Subject: [PATCH 493/844] Add missing logs and argument validations. (#925) --- libraries/standard/mqtt/include/mqtt.h | 12 +- .../standard/mqtt/include/mqtt_lightweight.h | 2 + libraries/standard/mqtt/src/mqtt.c | 81 ++++- .../standard/mqtt/src/mqtt_lightweight.c | 323 ++++++++++++------ 4 files changed, 298 insertions(+), 120 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index d35909339b..09178f8229 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -132,7 +132,11 @@ void MQTT_Init( MQTTContext_t * const pContext, * @brief param[out] pSessionPresent Whether a previous session was present. * Only relevant if not establishing a clean session. * - * @return See #MQTTStatus_t. + * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to + * hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport send failed; + * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, @@ -212,7 +216,11 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, * * @param[in] pContext Initialized and connected MQTT context. * - * @return See #MQTTStatus_t. + * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to + * hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSendFailed if transport send failed; + * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 909de3eaa6..eaf96c94a5 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -287,6 +287,7 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect * @param[out] pBuffer Buffer for packet serialization. * * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, @@ -455,6 +456,7 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); * @param[out] pBuffer Buffer for packet serialization. * * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 8e537b9bc3..8d1e58e813 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -20,6 +20,7 @@ */ #include +#include #include "mqtt.h" #include "private/mqtt_internal.h" @@ -64,9 +65,16 @@ static int32_t sendPacket( MQTTContext_t * pContext, const uint8_t * pIndex = pBufferToSend; size_t bytesRemaining = bytesToSend; int32_t totalBytesSent = 0, bytesSent; + uint32_t sendTime = 0U; + assert( pContext != NULL ); + assert( bytesToSend != 0 ); + + /* Point to the start of the network buffer from the context. */ + pIndex = pContext->networkBuffer.pBuffer; /* Record the time of transmission. */ - uint32_t sendTime = pContext->callbacks.getTime(); + sendTime = pContext->callbacks.getTime(); + bytesRemaining = bytesToSend; /* Loop until the entire packet is sent. */ while( bytesRemaining > 0 ) @@ -80,9 +88,15 @@ static int32_t sendPacket( MQTTContext_t * pContext, bytesRemaining -= ( size_t ) bytesSent; totalBytesSent += bytesSent; pIndex += bytesSent; + IotLogDebugWithArgs( "Bytes sent=%d, bytes remaining=%ul," + "total bytes sent=%d.", + bytesSent, + bytesRemaining, + totalBytesSent ); } else { + IotLogError( "Transport send failed." ); totalBytesSent = -1; break; } @@ -92,6 +106,8 @@ static int32_t sendPacket( MQTTContext_t * pContext, if( totalBytesSent > -1 ) { pContext->lastPacketTime = sendTime; + IotLogDebugWithArgs( "Successfully sent packet at time %u.", + sendTime ); } return totalBytesSent; @@ -160,12 +176,29 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, { size_t remainingLength, packetSize; int32_t bytesSent; + MQTTStatus_t status = MQTTSuccess; MQTTPacketInfo_t incomingPacket = { .type = ( ( uint8_t ) 0 ) }; - MQTTStatus_t status = MQTT_GetConnectPacketSize( pConnectInfo, - pWillInfo, - &remainingLength, - &packetSize ); + if( ( pContext == NULL ) || ( pConnectInfo == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pConnectInfo=%p.", + pContext, + pConnectInfo ); + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + /* Get MQTT connect packet size and remaining length. */ + status = MQTT_GetConnectPacketSize( pConnectInfo, + pWillInfo, + &remainingLength, + &packetSize ); + IotLogDebugWithArgs( "CONNECT packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); + } if( status == MQTTSuccess ) { @@ -183,12 +216,19 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( bytesSent < 0 ) { + IotLogError( "Transport send failed for CONNECT packet." ); status = MQTTSendFailed; } + else + { + IotLogDebugWithArgs( "Sent %d bytes of CONNECT packet.", + bytesSent ); + } } if( status == MQTTSuccess ) { + /* Transport read for incoming connack packet. */ status = MQTT_GetIncomingPacket( pContext->transportInterface.recv, pContext->transportInterface.networkContext, &incomingPacket ); @@ -196,18 +236,25 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { + /* Check if received packet type is CONNACK and deserialize it. */ if( incomingPacket.type == MQTT_PACKET_TYPE_CONNACK ) { + IotLogInfo( "Received MQTT CONNACK from broker." ); + + /* Deserialize CONNACK. */ status = MQTT_DeserializeAck( &incomingPacket, NULL, pSessionPresent ); } else { + IotLogErrorWithArgs( "Unexpected packet type %u received from network.", + incomingPacket.type ); status = MQTTBadResponse; } } if( status == MQTTSuccess ) { + IotLogInfo( "MQTT connection established with the broker." ); pContext->connectStatus = MQTTConnected; } @@ -474,11 +521,26 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) { size_t packetSize; int32_t bytesSent; + MQTTStatus_t status = MQTTSuccess; - MQTTStatus_t status = MQTT_GetDisconnectPacketSize( &packetSize ); + /* Validate arguments. */ + if( pContext == NULL ) + { + IotLogError( "pContext cannot be NULL." ); + status = MQTTBadParameter; + } if( status == MQTTSuccess ) { + /* Get MQTT DISCONNECT packet size. */ + status = MQTT_GetDisconnectPacketSize( &packetSize ); + IotLogDebugWithArgs( "MQTT DISCONNECT packet size is %lu.", + packetSize ); + } + + if( status == MQTTSuccess ) + { + /* Serialize MQTT DISCONNECT packet. */ status = MQTT_SerializeDisconnect( &( pContext->networkBuffer ) ); } @@ -490,12 +552,19 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) if( bytesSent < 0 ) { + IotLogError( "Transport send failed for DISCONNECT packet." ); status = MQTTSendFailed; } + else + { + IotLogDebugWithArgs( "Sent %d bytes of DISCONNECT packet.", + bytesSent ); + } } if( status == MQTTSuccess ) { + IotLogInfo( "Disconnected from the broker." ); pContext->connectStatus = MQTTNotConnected; } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 1604bd0145..0e03ed145b 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -228,6 +228,20 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_ size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ); +/** + * @brief Serialize an MQTT CONNECT packet in the given buffer. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. + * @param[out] pBuffer Buffer for packet serialization. + * + */ +static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ); + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -258,6 +272,10 @@ static size_t remainingLengthEncodedSize( size_t length ) encodedSize = 4U; } + IotLogDebugWithArgs( "Encoded size for length =%ul is %ul.", + length, + encodedSize ); + return encodedSize; } @@ -267,9 +285,13 @@ static uint8_t * encodeRemainingLength( uint8_t * pDestination, size_t length ) { uint8_t lengthByte; - uint8_t * pLengthEnd = pDestination; + uint8_t * pLengthEnd = NULL; size_t remainingLength = length; + assert( pDestination != NULL ); + + pLengthEnd = pDestination; + /* This algorithm is copied from the MQTT v3.1.1 spec. */ do { @@ -296,7 +318,11 @@ static uint8_t * encodeString( uint8_t * pDestination, const char * source, uint16_t sourceLength ) { - uint8_t * pBuffer = pDestination; + uint8_t * pBuffer = NULL; + + assert( pDestination != NULL ); + + pBuffer = pDestination; /* The first byte of a UTF-8 string is the high byte of the string length. */ *pBuffer = UINT16_HIGH_BYTE( sourceLength ); @@ -1158,171 +1184,228 @@ static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * const pPingres /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ) +static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ) { - MQTTStatus_t status = MQTTSuccess; - size_t remainingLength; + uint8_t connectFlags = 0; + uint8_t * pIndex = NULL; - /* The CONNECT packet will always include a 10-byte variable header. */ - size_t connectPacketSize = MQTT_PACKET_CONNECT_HEADER_SIZE; + assert( pConnectInfo != NULL ); + assert( pBuffer != NULL ); - /* Add the length of the client identifier. */ - connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); + pIndex = pBuffer->pBuffer; + /* The first byte in the CONNECT packet is the control packet type. */ + *pIndex = MQTT_PACKET_TYPE_CONNECT; + pIndex++; - /* Add the lengths of the will message and topic name if provided. */ - if( pWillInfo != NULL ) + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + pIndex = encodeRemainingLength( pIndex, remainingLength ); + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + pIndex = encodeString( pIndex, "MQTT", 4 ); + + /* The MQTT protocol version is the second field of the variable header. */ + *pIndex = MQTT_VERSION_3_1_1; + pIndex++; + + /* Set the clean session flag if needed. */ + if( pConnectInfo->cleanSession == true ) { - connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) + - pWillInfo->payloadLength + sizeof( uint16_t ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); } - /* Add the lengths of the user name and password if provided. */ + /* Set the flags for username and password if provided. */ if( pConnectInfo->pUserName != NULL ) { - connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); } if( pConnectInfo->pPassword != NULL ) { - connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + } + + /* Set will flag if a Last Will and Testament is provided. */ + if( pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for Will QoS 1 or 2. */ + if( pWillInfo->qos == MQTTQoS1 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + } + else if( pWillInfo->qos == MQTTQoS2 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + } + + if( pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } } - /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has - * been calculated. */ - remainingLength = connectPacketSize; + *pIndex = connectFlags; + pIndex++; - /* Calculate the full size of the MQTT CONNECT packet by adding the size of - * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ - connectPacketSize += 1U + remainingLengthEncodedSize( connectPacketSize ); + /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ + *pIndex = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pIndex + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + pIndex += 2; + + /* Write the client identifier into the CONNECT packet. */ + pIndex = encodeString( pIndex, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); - /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ - if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) + /* Write the will topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) { - status = MQTTBadParameter; + pIndex = encodeString( pIndex, + pWillInfo->pTopicName, + pWillInfo->topicNameLength ); + pIndex = encodeString( pIndex, + pWillInfo->pPayload, + ( uint16_t ) pWillInfo->payloadLength ); } - else + + /* Encode the user name if provided. */ + if( pConnectInfo->pUserName != NULL ) { - *pRemainingLength = remainingLength; - *pPacketSize = connectPacketSize; + pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength ); } - return status; + /* Encode the password if provided. */ + if( pConnectInfo->pPassword != NULL ) + { + pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); + } + + IotLogDebugWithArgs( "Length of serialized CONNECT packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + + /* Ensure that the difference between the end and beginning of the buffer + * is less than the buffer size. */ + assert( ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) <= pBuffer->size ); } /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, - size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) +MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, + size_t * const pRemainingLength, + size_t * const pPacketSize ) { MQTTStatus_t status = MQTTSuccess; - uint8_t connectFlags = 0; - uint8_t * pIndex = pBuffer->pBuffer; + size_t remainingLength; - /* Check that the full packet size fits within the given buffer. */ - size_t connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; + /* The CONNECT packet will always include a 10-byte variable header. */ + size_t connectPacketSize = MQTT_PACKET_CONNECT_HEADER_SIZE; - if( connectPacketSize > pBuffer->size ) + /* Validate arguments. */ + if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || + ( pPacketSize == NULL ) ) { - status = MQTTNoMemory; + IotLogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pConnectInfo, + pRemainingLength, + pPacketSize ); + status = MQTTBadParameter; } if( status == MQTTSuccess ) { - /* The first byte in the CONNECT packet is the control packet type. */ - *pIndex = MQTT_PACKET_TYPE_CONNECT; - pIndex++; + /* Add the length of the client identifier. */ + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); - /* The remaining length of the CONNECT packet is encoded starting from the - * second byte. The remaining length does not include the length of the fixed - * header or the encoding of the remaining length. */ - pIndex = encodeRemainingLength( pIndex, remainingLength ); - - /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable - * header. This string is 4 bytes long. */ - pIndex = encodeString( pIndex, "MQTT", 4 ); - - /* The MQTT protocol version is the second field of the variable header. */ - *pIndex = MQTT_VERSION_3_1_1; - pIndex++; - - /* Set the clean session flag if needed. */ - if( pConnectInfo->cleanSession == true ) + /* Add the lengths of the will message and topic name if provided. */ + if( pWillInfo != NULL ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) + + pWillInfo->payloadLength + sizeof( uint16_t ); } - /* Set the flags for username and password if provided. */ + /* Add the lengths of the user name and password if provided. */ if( pConnectInfo->pUserName != NULL ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t ); } if( pConnectInfo->pPassword != NULL ) { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t ); } - /* Set will flag if a Last Will and Testament is provided. */ - if( pWillInfo != NULL ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has + * been calculated. */ + remainingLength = connectPacketSize; - /* Flags only need to be changed for Will QoS 1 or 2. */ - if( pWillInfo->qos == MQTTQoS1 ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); - } - else if( pWillInfo->qos == MQTTQoS2 ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); - } + /* Calculate the full size of the MQTT CONNECT packet by adding the size of + * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ + connectPacketSize += 1U + remainingLengthEncodedSize( connectPacketSize ); - if( pWillInfo->retain == true ) - { - UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); - } + /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ + if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) + { + status = MQTTBadParameter; + } + else + { + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; } - *pIndex = connectFlags; - pIndex++; + IotLogDebugWithArgs( "CONNECT packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ); + } - /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ - *pIndex = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); - *( pIndex + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); - pIndex += 2; + return status; +} - /* Write the client identifier into the CONNECT packet. */ - pIndex = encodeString( pIndex, - pConnectInfo->pClientIdentifier, - pConnectInfo->clientIdentifierLength ); +/*-----------------------------------------------------------*/ - /* Write the will topic name and message into the CONNECT packet if provided. */ - if( pWillInfo != NULL ) - { - pIndex = encodeString( pIndex, - pWillInfo->pTopicName, - pWillInfo->topicNameLength ); - pIndex = encodeString( pIndex, - pWillInfo->pPayload, - ( uint16_t ) pWillInfo->payloadLength ); - } +MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ) +{ + MQTTStatus_t status = MQTTSuccess; - /* Encode the user name if provided. */ - if( pConnectInfo->pUserName != NULL ) - { - pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength ); - } + /* Calculate CONNECT packet size. */ + size_t connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; - /* Encode the password if provided. */ - if( pConnectInfo->pPassword != NULL ) - { - pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); - } + /* Validate arguments. */ + if( ( pConnectInfo == NULL ) || ( pBuffer == NULL ) ) + { + IotLogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " + "pBuffer=%p.", + pConnectInfo, + pBuffer ); + status = MQTTBadParameter; + } + /* Check that the full packet size fits within the given buffer. */ + else if( connectPacketSize > pBuffer->size ) + { + IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized CONNECT packet of size of %lu.", + pBuffer->size, + connectPacketSize ); + status = MQTTNoMemory; + } + else + { + serializeConnectPacket( pConnectInfo, + pWillInfo, + remainingLength, + pBuffer ); } return status; @@ -1750,12 +1833,28 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) MQTTStatus_t status = MQTTSuccess; size_t disconnectPacketSize; - status = MQTT_GetDisconnectPacketSize( &disconnectPacketSize ); + /* Validate arguments. */ + if( pBuffer == NULL ) + { + IotLogError( "pBuffer cannot be NULL." ); + status = MQTTBadParameter; + } + + if( status == MQTTSuccess ) + { + status = MQTT_GetDisconnectPacketSize( &disconnectPacketSize ); + IotLogDebugWithArgs( "MQTT DISCONNECT packet size is %ul.", + disconnectPacketSize ); + } if( status == MQTTSuccess ) { if( pBuffer->size < disconnectPacketSize ) { + IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized DISCONNECT packet of size of %lu.", + pBuffer->size, + disconnectPacketSize ); status = MQTTNoMemory; } } From bf889dd73c47ec5a03b13dd03e0d7ba8d6eb8641 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 12 May 2020 14:01:55 -0700 Subject: [PATCH 494/844] Implement HTTPClient_ReadHeader() Part 2 (#928) --- libraries/standard/http/include/http_client.h | 23 +- libraries/standard/http/src/http_client.c | 384 +++++++++++++++--- .../standard/http/src/http_client_parse.c | 25 +- .../http/src/private/http_client_internal.h | 12 + .../http/src/private/http_client_parse.h | 25 -- libraries/standard/http/test/Makefile | 16 +- .../http/test/test-HTTPClient_ReadHeader.c | 255 ++++++++---- 7 files changed, 559 insertions(+), 181 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 8f1898e0b3..7aa8b01a62 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -214,6 +214,14 @@ typedef enum HTTPStatus */ HTTP_INSUFFICIENT_MEMORY, + /** + * @brief Represents all errors not related to user-input or transport I/O, but + * errors internal to the implementation of the HTTP client library. + * + * Functions that may return this value: + * - #HTTPClient_Send + * - #HTTPClient_ReadHeader + */ HTTP_INTERNAL_ERROR, HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED, HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER, @@ -227,6 +235,15 @@ typedef enum HTTPStatus * - #HTTPClient_ReadHeader */ HTTP_HEADER_NOT_FOUND, + + /** + * @brief The HTTP response, provided for parsing, is either corrupt or incomplete. + * + * Functions that may return this value: + * - #HTTPClient_ReadHeader + */ + HTTP_INVALID_RESPONSE, + /* Temporary error code while implementation is in progress. */ HTTP_NOT_SUPPORTED, } HTTPStatus_t; @@ -571,20 +588,20 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, * @param[in] headerNameLen The length of the header field name in bytes. * @param[out] pHeaderValueLoc This will be populated with the location of the * header value in the response buffer, #HTTPResponse_t.pBuffer. - * @param[out] headerValueLen This will be populated with the length of the + * @param[out] pHeaderValueLen This will be populated with the length of the * header value in bytes. * * @return One of the following: * - #HTTP_SUCCESS (If successful.) * - #HTTP_INVALID_PARAMETER (If any provided parameters or their members are invalid.) - * - #HTTP_PARTIAL_RESPONSE (Part of an HTTP response was received in a partially filled response buffer.) * - #HTTP_HEADER_NOT_FOUND (Header is not found in the passed response buffer.) + * - #HTTP_INVALID_RESPONSE (Provided response is not a valid HTTP response for parsing.) */ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, const uint8_t * pHeaderName, size_t headerNameLen, const uint8_t ** pHeaderValueLoc, - size_t * headerValueLen ); + size_t * pHeaderValueLen ); /** * @brief Error code to string conversion utility for HTTP Client library. diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 9f09de5fb5..f46ac7553a 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -4,6 +4,24 @@ #include "http_client.h" #include "private/http_client_internal.h" #include "private/http_client_parse.h" +#include "http_parser/http_parser.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief An aggregator that represents the user-provided parameters to the + * #HTTPClient_ReadHeader API function. This will be used as context parameter + * for the parsing callbacks used by the API function. + */ +typedef struct findHeaderContext +{ + const uint8_t * pField; + size_t fieldLen; + const uint8_t ** pValueLoc; + size_t * pValueLen; + uint8_t fieldFound : 1; + uint8_t valueFound : 1; +} findHeaderContext_t; /*-----------------------------------------------------------*/ @@ -138,6 +156,77 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, const char * pPath, size_t pathLen ); +/** + * @brief Find the specified header field in the response buffer. + * + * @param[in] pBuffer The response buffer to parse. + * @param[in] bufferLen The length of the response buffer to parse. + * @param[in] pField The header field to search for. + * @param[in] fieldLen The length of pField. + * @param[out] pValue The location of the the header value found in pBuffer. + * @param[out] pValueLen The length of pValue. + * + * @return One of the following: + * - #HTTP_SUCCESS when header is found in the response. + * - #HTTP_HEADER_NOT_FOUND if requested header is not found in response. + * - #HTTP_INVALID_RESPONSE if passed response is invalid for parsing. + * - #HTTP_INTERNAL_ERROR for any parsing errors. + */ +static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, + size_t bufferLen, + const uint8_t * pField, + size_t fieldLen, + const uint8_t ** pValue, + size_t * pValueLen ); + +/** + * @brief The "on_header_field" callback for the HTTP parser used by the + * #findHeaderInResponse function. The callback checks whether the parser + * header field matched the header being searched for, and sets a flag to + * represent reception of the header accordingly. + * + * @param[in] pHttpParser The parser object to which this callback is registered. + * @param[in] pFieldLoc The location of the parsed header field in the response buffer. + * @param[in] fieldLen The length of the header field. + * + * @return Returns #HTTP_PARSER_CONTINUE_PARSING to indicate continuation with parsing. + */ +static int findHeaderFieldParserCallback( http_parser * pHttpParser, + const char * pFieldLoc, + size_t fieldLen ); + +/** + * @brief The "on_header_value" callback for the HTTP parser used by the + * #findHeaderInResponse function. The callback sets the user-provided output parameters + * for header value if the requested header's field was found in the + * @ref findHeaderFieldParserCallback function. + * + * @param[in] pHttpParser The parser object to which this callback is registered. + * @param[in] pVaLueLoc The location of the parsed header value in the response buffer. + * @param[in] valueLen The length of the header value. + * + * @return Returns #HTTP_PARSER_STOP_PARSING if the header field/value pair are found; otherwise, + * #HTTP_PARSING_CONTINUE_PARSING. + */ +static int findHeaderValueParserCallback( http_parser * pHttpParser, + const char * pVaLueLoc, + size_t valueLen ); + +/** + * @brief The "on_header_complete" callback for the HTTP parser used by the + * #findHeaderInResponse function. + * + * This callback will only be invoked if the requested header is not found in + * the response. This callback is used to signal the parser to halt execution + * if the requested header is not found. + * + * @param[in] pHttpParser The parser object to which this callback is registered. + * + * @return Returns #HTTP_PARSER_STOP_PARSING for the parser to halt further execution, + * as all headers have been parsed in the response. + */ +static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ); + /*-----------------------------------------------------------*/ static uint8_t convertInt32ToAscii( int32_t value, @@ -929,6 +1018,243 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ +/* The coverity violation is for using a non-const type for the "pHttpParser" parser + * instead of a const-type as the pointed to object is not modified by the function. + * We suppress this violations this violation as this function follows the function + * function signature of the "on_header_field" callback specified by the http-parser + * library. */ +/* coverity[misra_c_2012_rule_8_13_violation] */ +static int findHeaderFieldParserCallback( http_parser * pHttpParser, + const char * pFieldLoc, + size_t fieldLen ) +{ + findHeaderContext_t * pContext = ( findHeaderContext_t * ) pHttpParser->data; + + assert( pHttpParser != NULL ); + assert( pFieldLoc != NULL ); + assert( fieldLen > 0u ); + + assert( pContext->pField != NULL ); + assert( pContext->fieldLen > 0u ); + + /* The header found flags should not be set. */ + assert( pContext->fieldFound == 0u ); + assert( pContext->valueFound == 0u ); + + /* Check whether the parsed header matches the header we are looking for. */ + if( ( fieldLen == pContext->fieldLen ) && ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) + { + IotLogDebugWithArgs( "Found header field in response: " + "HeaderName=%.*s, HeaderLocation=0x%d", + fieldLen, pContext->pField ); + + /* Set the flag to indicate that header has been found in response. */ + pContext->fieldFound = 1u; + } + else + { + /* Empty else for MISRA 15.7 compliance. */ + } + + return HTTP_PARSER_CONTINUE_PARSING; +} + +/*-----------------------------------------------------------*/ + +/* The coverity violation is for using a non-const type for the "pHttpParser" parser + * instead of a const-type as the pointed to object is not modified by the function. + * We suppress this violations this violation as this function follows the function + * function signature of the "on_header_value" callback specified by the http-parser + * library. */ +/* coverity[misra_c_2012_rule_8_13_violation] */ +static int findHeaderValueParserCallback( http_parser * pHttpParser, + const char * pVaLueLoc, + size_t valueLen ) +{ + findHeaderContext_t * pContext = ( findHeaderContext_t * ) pHttpParser->data; + + /* The coverity violation is for using "int" instead of a type that specifies size + * and signedness information. We suppress this violation as this variable represents + * the return value type of this callback function, whose return type is defined by + * http-parser. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ + int retCode = HTTP_PARSER_CONTINUE_PARSING; + + assert( pHttpParser != NULL ); + assert( pVaLueLoc != NULL ); + assert( valueLen > 0u ); + + assert( pContext->pField != NULL ); + assert( pContext->fieldLen > 0u ); + assert( pContext->pValueLoc != NULL ); + assert( pContext->pValueLen != NULL ); + + /* The header value found flag should not be set. */ + assert( pContext->valueFound == 0u ); + + if( pContext->fieldFound == 1u ) + { + IotLogDebugWithArgs( "Found header value in response: " + "RequestedField=%.*s, ValueLocation=0x%d", + pContext->fieldLen, pContext->pField, pVaLueLoc ); + + /* Populate the output parameters with the location of the header value in the response buffer. */ + *pContext->pValueLoc = ( const uint8_t * ) pVaLueLoc; + *pContext->pValueLen = valueLen; + + /* Set the header value found flag. */ + pContext->valueFound = 1u; + + /* As we have found the value associated with the header, we don't need + * to parse the response any further. */ + retCode = HTTP_PARSER_STOP_PARSING; + } + else + { + /* Empty else for MISRA 15.7 compliance. */ + } + + return retCode; +} + +/*-----------------------------------------------------------*/ + +/* The coverity violation is for using a non-const type for the "pHttpParser" parser + * instead of a const-type as the pointed to object is not modified by the function. + * We suppress this violations this violation as this function follows the function + * function signature of the "on_headers_complete" callback specified by the http-parser + * library. */ +/* coverity[misra_c_2012_rule_8_13_violation] */ +static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) +{ + /* Disable unused parameter warning. */ + ( void ) pHttpParser; + + assert( pHttpParser != NULL ); + + /* If we have reached here, all headers in the response have been parsed but the requested + * header has not been found in the response buffer. */ + IotLogDebugWithArgs( "Reached end of header parsing: Header not found in response: " + "RequestedHeader=%.*s", + ( ( findHeaderContext_t * ) pHttpParser->data )->fieldLen, + ( ( findHeaderContext_t * ) pHttpParser->data )->pField ); + + /* No further parsing is required; thus, indicate the parser to stop parsing. */ + return HTTP_PARSER_STOP_PARSING; +} + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, + size_t bufferLen, + const uint8_t * pField, + size_t fieldLen, + const uint8_t ** pValueLoc, + size_t * pValueLen ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + http_parser parser = { 0 }; + http_parser_settings parserSettings = { 0 }; + findHeaderContext_t context = + { + .pField = pField, + .fieldLen = fieldLen, + .pValueLoc = pValueLoc, + .pValueLen = pValueLen, + .fieldFound = 0u, + .valueFound = 0u + }; + size_t numOfBytesParsed = 0u; + + /* Disable unused variable warning. */ + ( void ) numOfBytesParsed; + + http_parser_init( &parser, HTTP_RESPONSE ); + + /* Set the context for the parser. */ + parser.data = &context; + + /* The intention here to define callbacks just for searching the headers. We will + * need to create a private context in httpParser->data that has the field and + * value to update and pass back. */ + http_parser_settings_init( &parserSettings ); + parserSettings.on_header_field = findHeaderFieldParserCallback; + parserSettings.on_header_value = findHeaderValueParserCallback; + parserSettings.on_headers_complete = findHeaderOnHeaderCompleteCallback; + + /* Start parsing for the header! */ + numOfBytesParsed = http_parser_execute( &parser, + &parserSettings, + ( const char * ) pBuffer, + bufferLen ); + + IotLogDebugWithArgs( "Parsed response for header search: NumBytesParsed=%d", + numOfBytesParsed ); + + if( context.fieldFound == 0u ) + { + /* If header field is not found, then both the flags should be zero. */ + assert( context.valueFound == 0u ); + + /* Header is not present in buffer. */ + IotLogWarnWithArgs( "Header not found in response buffer: " + "RequestedHeader=%.*s", fieldLen, pField ); + + returnStatus = HTTP_HEADER_NOT_FOUND; + } + else if( context.valueFound == 0u ) + { + /* The response buffer is invalid as only the header field was found + * in the ": \r\n" format of an HTTP header. */ + IotLogErrorWithArgs( "Unable to find header value in response: " + "Response data is invalid: " + "RequestedHeader=%.*s, ParserError=%s", + fieldLen, pField, + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + returnStatus = HTTP_INVALID_RESPONSE; + } + else + { + /* Empty else (when assert and logging is disabled) for MISRA 15.7 compliance. */ + + /* Header is found. */ + assert( ( context.fieldFound == 1u ) && ( context.valueFound == 1u ) ); + + IotLogDebugWithArgs( "Found requested header in response: " + "HeaderName=%.*s, HeaderValue=%.*s", + fieldLen, pField, + *pValueLen, *pValueLoc ); + } + + /* If the header field-value pair is found in response, then the return value of "on_header_value" + * callback (related to the header value) should cause the http_parser.http_errno to be "CB_header_value". */ + if( ( returnStatus == HTTP_SUCCESS ) && + ( ( parser.http_errno != HPE_CB_header_value ) ) ) + { + IotLogErrorWithArgs( "Header found in response but http-parser returned error: ParserError=%s", + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + returnStatus = HTTP_INTERNAL_ERROR; + } + + /* If header was not found, then the "on_header_complete" callback is expected to be called which should + * cause the http_parser.http_errno to be "OK" */ + else if( ( returnStatus == HTTP_HEADER_NOT_FOUND ) && + ( ( parser.http_errno != HPE_OK ) ) ) + { + IotLogErrorWithArgs( "Header not found in response: http-parser returned error: ParserError=%s", + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + returnStatus = HTTP_INVALID_RESPONSE; + } + else + { + /* Empty else for MISRA 15.7 compliance. */ + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, const uint8_t * pHeaderName, size_t headerNameLen, @@ -936,9 +1262,6 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, size_t * pHeaderValueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - HTTPParsingContext_t parsingContext; - - memset( &parsingContext, 0, sizeof( HTTPParsingContext_t ) ); if( pResponse == NULL ) { @@ -984,48 +1307,12 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, if( returnStatus == HTTP_SUCCESS ) { - /* Initialize Parsing context with our callback. */ - returnStatus = HTTPClient_InitializeParsingContext( &parsingContext, NULL ); - } - - if( returnStatus == HTTP_SUCCESS ) - { - returnStatus = HTTPClient_FindHeaderInResponse( &parsingContext, - pResponse->pBuffer, - pResponse->bufferLen, - pHeaderName, - headerNameLen, - pHeaderValueLoc, - pHeaderValueLen ); - - if( returnStatus == HTTP_SUCCESS ) - { - /* Header value found present in buffer. */ - IotLogDebugWithArgs( "Found requested header in response: " - "HeaderName=%.*s, ValueLoc=%.*s", - headerNameLen, pHeaderName, - *pHeaderValueLen, *pHeaderValueLoc ); - } - else if( returnStatus == HTTP_HEADER_NOT_FOUND ) - { - /* Header is not present in buffer. */ - IotLogWarnWithArgs( "Header field not found in response buffer: " - "HeaderName=%.*s", headerNameLen, pHeaderName ); - } - else - { - IotLogErrorWithArgs( "Unable to read header from response: " - "Failure in parsing response for header field: " - "HeaderName=%.*s, ParserError=%s", - headerNameLen, pHeaderName, - HTTPClient_strerror( returnStatus ) ); - } - } - else - { - IotLogErrorWithArgs( "Failed to read header from response: " - "Unable to initialize parsing context: " - "HeaderName=%.*s", headerNameLen, pHeaderName ); + returnStatus = findHeaderInResponse( pResponse->pBuffer, + pResponse->bufferLen, + pHeaderName, + headerNameLen, + pHeaderValueLoc, + pHeaderValueLen ); } return returnStatus; @@ -1083,6 +1370,10 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) str = "HTTP_HEADER_NOT_FOUND"; break; + case HTTP_INVALID_RESPONSE: + str = "HTTP_INVALID_RESPONSE"; + break; + case HTTP_NOT_SUPPORTED: str = "HTTP_NOT_SUPPORTED"; break; @@ -1090,11 +1381,10 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) default: IotLogWarnWithArgs( "Invalid status code received for string conversion: " "StatusCode=%d", status ); + break; } return str; } - - /*-----------------------------------------------------------*/ diff --git a/libraries/standard/http/src/http_client_parse.c b/libraries/standard/http/src/http_client_parse.c index bc5b8777a0..65825f6798 100644 --- a/libraries/standard/http/src/http_client_parse.c +++ b/libraries/standard/http/src/http_client_parse.c @@ -1,4 +1,9 @@ +#include +#include + #include "private/http_client_parse.h" +#include "private/http_client_internal.h" +#include "http_parser/http_parser.h" HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) @@ -22,23 +27,3 @@ HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, pParsingContext->state = HTTP_PARSING_COMPLETE; return HTTP_SUCCESS; } - -HTTPStatus_t HTTPClient_FindHeaderInResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen, - const uint8_t * pField, - size_t fieldLen, - const uint8_t ** pValue, - size_t * valueLen ) -{ - /* Disable unused parameter warnings. */ - ( void ) pParsingContext; - ( void ) pBuffer; - ( void ) bufferLen; - ( void ) pField; - ( void ) fieldLen; - ( void ) pValue; - ( void ) valueLen; - - return HTTP_NOT_SUPPORTED; -} diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 2df759aedc..ef6b800ede 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -104,4 +104,16 @@ ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DECIMAL_DIGITS + \ 1u /* Dash character '-' */ + MAX_INT32_NO_OF_DECIMAL_DIGITS ) +/** + * @brief Return value for http_parser registered callback to signal halting further execution. + */ +#define HTTP_PARSER_STOP_PARSING 1 + +/** + * @brief Return value for http_parser registered callback to signal further continuation of + * HTTP response parsing. + */ +#define HTTP_PARSER_CONTINUE_PARSING 0 + + #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/src/private/http_client_parse.h b/libraries/standard/http/src/private/http_client_parse.h index b681383d2a..4e45991386 100644 --- a/libraries/standard/http/src/private/http_client_parse.h +++ b/libraries/standard/http/src/private/http_client_parse.h @@ -68,29 +68,4 @@ HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, const uint8_t * pBuffer, size_t bufferLen ); -/** - * @brief Find the specified header field in the response buffer. - * - * @param[in] pParsingContext The state of the of the response parsing. - * @param[in] pBuffer The response buffer to parse. - * @param[in] bufferLen The length of the response buffer to parse. - * @param[in] pField The header field to search for. - * @param[in] fieldLen The length of pField. - * @param[out] pValue The location of the the header value found in pBuffer. - * @param[out] valueLen The length of pValue. - * - * @return One of the following: - * - #HTTP_SUCCESS when header is found in the response. - * - #HTTP_HEADER_NOT_FOUND if requested header is not found in response. - * - #HTTP_INTERNAL_ERROR for any parsing errors. - */ -HTTPStatus_t HTTPClient_FindHeaderInResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen, - const uint8_t * pField, - size_t fieldLen, - const uint8_t ** pValue, - size_t * valueLen ); - - #endif /* ifndef HTTP_CLIENT_PARSE_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 5fb9f40027..1454779110 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -15,7 +15,8 @@ INCLUDE = . \ ../include \ ../src \ $(CSDK_ROOT)/platform/include \ - ../../utilities/include + ../../utilities/include \ + ../third_party # the default goal (default: help) # Set to build or test as desired. @@ -37,8 +38,11 @@ FUNCTIONS = addHeader \ HTTPClient_InitializeParsingContext \ HTTPClient_ParseResponse \ getFinalResponseStatus \ - readHeaderParsingCallback \ - HTTPClient_strerror + HTTPClient_strerror \ + findHeaderFieldParserCallback \ + findHeaderValueParserCallback \ + findHeaderOnHeaderCompleteCallback \ + findHeaderInResponse # Changes to the above variables should remain above this include. include Mock4thewin.mk @@ -57,7 +61,11 @@ HTTPClient_Send.c: sendHttpHeaders.c \ HTTPClient_ParseResponse.c \ getFinalResponseStatus.c HTTPClient_InitializeRequestHeaders.c: writeRequestLine.c addHeader.c -HTTPClient_ReadHeader.c: HTTPClient_strerror.c +HTTPClient_ReadHeader.c: HTTPClient_strerror.c \ + findHeaderFieldParserCallback.c \ + findHeaderValueParserCallback.c \ + findHeaderOnHeaderCompleteCallback.c \ + findHeaderInResponse.c # additional header dependencies for all tests COMMON = common.h diff --git a/libraries/standard/http/test/test-HTTPClient_ReadHeader.c b/libraries/standard/http/test/test-HTTPClient_ReadHeader.c index e8dfad19db..9c20449da8 100644 --- a/libraries/standard/http/test/test-HTTPClient_ReadHeader.c +++ b/libraries/standard/http/test/test-HTTPClient_ReadHeader.c @@ -2,69 +2,116 @@ #include #include "common.h" +#include "http_parser/http_parser.h" + +/* Mirror of the context type used to pass to http_parser. */ +typedef struct findHeaderContext +{ + const uint8_t * pField; + size_t fieldLen; + const uint8_t ** pValueLoc; + size_t * pValueLen; + uint8_t fieldFound : 1; + uint8_t valueFound : 1; +} findHeaderContext_t; /* Functions are pulled out into their own C files to be tested as a unit. */ #include "HTTPClient_strerror.c" +#include "findHeaderFieldParserCallback.c" +#include "findHeaderValueParserCallback.c" +#include "findHeaderOnHeaderCompleteCallback.c" +#include "findHeaderInResponse.c" #include "HTTPClient_ReadHeader.c" /* Template HTTP request for a PUT request. */ -static const char * pTestResponse = "HTTP/1.1 200 OK" +static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" "test-header0: test-value0\r\n" "test-header1: test-value1\r\n" "test-header2: test-value2\r\n" "\r\n"; -static const size_t headerFieldInRespLoc = 15; -static const size_t headerFieldInRespLen = strlen( "test-header0" ); -static const size_t headerValInRespLoc = 29; -static const size_t headerValInRespLen = strlen( "test-value0" ); +static const size_t headerFieldInRespLoc = 44; +static const size_t headerFieldInRespLen = sizeof( "test-header1" ) - 1u; +static const size_t headerValInRespLoc = 58; +static const size_t headerValInRespLen = sizeof( "test-value1" ) - 1u; -#define HEADER_IN_BUFFER "test-header0" +#define HEADER_IN_BUFFER "test-header1" #define HEADER_NOT_IN_BUFFER "header-not-in-buffer" -/* Mocked out implementations of parser function dependencies. */ -static HTTPStatus_t initializeParsingContextRetCode = HTTP_SUCCESS; -HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) +/* -------------------- Mock'd implementations of http_parser function dependencies. ------------------ */ + +void http_parser_init( http_parser * parser, + enum http_parser_type type ) { - ( void ) pHeaderParsingCallback; + ok( parser != NULL ); + ok( type == HTTP_RESPONSE ); +} + +void http_parser_settings_init( http_parser_settings * settings ) +{ + ok( settings != NULL ); +} +/* Variables for controlling behavior of the http_parser_execute() mock. */ +static const char * pExpectedBuffer = NULL; +static size_t expectedBufferSize = 0u; +bool invokeHeaderFieldCallback = false; +const char * pFieldLocToReturn = NULL; +size_t fieldLenToReturn = 0u; +bool invokeHeaderValueCallback = false; +const char * pValueLocToReturn = NULL; +static int expectedValCbRetVal = 0; +size_t valueLenToReturn = 0u; +bool invokeHeaderCompleteCallback = false; +unsigned int parserErrNo = 0; +size_t http_parser_execute( http_parser * parser, + const http_parser_settings * settings, + const char * data, + size_t len ) +{ + ok( parser != NULL ); + ok( settings != NULL ); + ok( len == expectedBufferSize ); + ok( data == pExpectedBuffer ); + + ok( settings->on_header_field != NULL ); + ok( settings->on_header_value != NULL ); + ok( settings->on_headers_complete != NULL ); - /* Verify the input arguments. */ - ok( pParsingContext != NULL ); + if( invokeHeaderFieldCallback == true ) + { + ok( HTTP_PARSER_CONTINUE_PARSING == + settings->on_header_field( parser, + pFieldLocToReturn, + fieldLenToReturn ) ); + } - return initializeParsingContextRetCode; + if( invokeHeaderValueCallback == true ) + { + ok( expectedValCbRetVal == + settings->on_header_value( parser, + pValueLocToReturn, + valueLenToReturn ) ); + } + + if( invokeHeaderCompleteCallback == true ) + { + ok( HTTP_PARSER_STOP_PARSING == + settings->on_headers_complete( parser ) ); + } + + /* Set the error value in the parser. */ + parser->http_errno = parserErrNo; + + return len; } -static HTTPStatus_t findHeaderInRespRetCode = HTTP_SUCCESS; -static const uint8_t * pExpectedField = NULL; -static size_t expectedFieldSize = 0u; -static const uint8_t * pValLocToReturn = NULL; -static size_t valLenToReturn = 0u; -HTTPStatus_t HTTPClient_FindHeaderInResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen, - const uint8_t * pField, - size_t fieldLen, - const uint8_t ** pValue, - size_t * pValueLen ) +const char * http_errno_description( enum http_errno err ) { - /* Verify the input arguments. */ - ok( pParsingContext != NULL ); - ok( pBuffer == ( const uint8_t * ) pTestResponse ); - ok( bufferLen == strlen( pTestResponse ) ); - ok( bufferLen != 0u ); - ok( pField != NULL ); - ok( fieldLen == expectedFieldSize ); - ok( 0u == memcmp( pField, pExpectedField, fieldLen ) ); - ok( pValue != NULL ); - ok( pValueLen != NULL ); - - /* Side-effects of mock'd implementation. */ - *pValue = pValLocToReturn; - *pValueLen = valLenToReturn; - - return findHeaderInRespRetCode; + ( void ) err; + return "test-error"; } +/* -------------------- End of http_parser function mocks. ------------------ */ + int main() { HTTPResponse_t testResponse = { 0 }; @@ -78,22 +125,26 @@ int main() memset( &testResponse, 0, sizeof( testResponse ) ); \ pValueLoc = NULL; \ valueLen = 0u; \ - initializeParsingContextRetCode = HTTP_SUCCESS; \ - findHeaderInRespRetCode = HTTP_SUCCESS; \ - pExpectedField = NULL; \ - expectedFieldSize = 0u; \ - pValLocToReturn = NULL; \ - valLenToReturn = 0u; \ + pExpectedBuffer = &pTestResponse[ 0 ]; \ + expectedBufferSize = strlen( pTestResponse ); \ + invokeHeaderFieldCallback = false; \ + pFieldLocToReturn = NULL; \ + fieldLenToReturn = 0u; \ + invokeHeaderValueCallback = false; \ + pValueLocToReturn = NULL; \ + expectedValCbRetVal = 0; \ + valueLenToReturn = 0u; \ + invokeHeaderCompleteCallback = false; \ } while( 0 ) - plan( 44 ); + plan( 69 ); /*************************** Test Failure Cases *****************************/ /* Response parameter is NULL. */ reset(); retCode = HTTPClient_ReadHeader( NULL, - "Header", + ( const uint8_t * ) "Header", strlen( "Header" ), &pValueLoc, &valueLen ); @@ -102,7 +153,7 @@ int main() /* Underlying buffer is NULL in the response parameter. */ reset(); retCode = HTTPClient_ReadHeader( &testResponse, - "Header", + ( const uint8_t * ) "Header", strlen( "Header" ), &pValueLoc, &valueLen ); @@ -112,7 +163,7 @@ int main() reset(); testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; retCode = HTTPClient_ReadHeader( &testResponse, - "Header", + ( const uint8_t * ) "Header", strlen( "Header" ), &pValueLoc, &valueLen ); @@ -134,7 +185,7 @@ int main() testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - "Header", + ( const uint8_t * ) "Header", 0u, &pValueLoc, &valueLen ); @@ -145,7 +196,7 @@ int main() testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - "Header", + ( const uint8_t * ) "Header", strlen( "Header" ), NULL, &valueLen ); @@ -154,72 +205,112 @@ int main() testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - "Header", + ( const uint8_t * ) "Header", strlen( "Header" ), &pValueLoc, NULL ); ok( retCode == HTTP_INVALID_PARAMETER ); - /* Test when HTTPClient_InitializeParsingContext returns failure. */ + /* Test when the header is present in response but http_parser_execute() + * does not set the expected errno value (of "CB_header_value") + * due to an internal error. */ reset(); - initializeParsingContextRetCode = HTTP_INTERNAL_ERROR; + /* Configure the http_parser_execute mock. */ + invokeHeaderFieldCallback = true; + invokeHeaderValueCallback = true; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + expectedValCbRetVal = HTTP_PARSER_STOP_PARSING; + parserErrNo = HPE_CB_chunk_complete; + /* Call the function under test. */ testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - "Header", - strlen( "Header" ), + ( const uint8_t * ) HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), &pValueLoc, &valueLen ); ok( retCode == HTTP_INTERNAL_ERROR ); - /* Test when HTTPClient_ParseResponse returns failure. */ + /* Test when requested header is not present in response. */ + reset(); - /* Configure the HTTPClient_FindResponseInHeader mock. */ - findHeaderInRespRetCode = HTTP_INTERNAL_ERROR; - pExpectedField = HEADER_IN_BUFFER; - expectedFieldSize = strlen( HEADER_IN_BUFFER ); + /* Configure the http_parser_execute mock. */ + invokeHeaderCompleteCallback = false; + parserErrNo = HPE_OK; /* Call the function under test. */ testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), + ( const uint8_t * ) HEADER_NOT_IN_BUFFER, + strlen( HEADER_NOT_IN_BUFFER ), &pValueLoc, &valueLen ); - ok( retCode == HTTP_INTERNAL_ERROR ); + ok( retCode == HTTP_HEADER_NOT_FOUND ); - /* Test when requested header is not present in response. */ + /* Test with invalid HTTP responses. */ + /* Test when invalid response only contains the header field for the requested header. */ + const char * pResponseWithoutValue = "HTTP/1.1 200 OK\r\n" + "test-header0: test-value0\r\n" + "test-header1:"; reset(); - /* Configure the HTTPClient_FindResponseInHeader mock. */ - findHeaderInRespRetCode = HTTP_HEADER_NOT_FOUND; - pExpectedField = HEADER_NOT_IN_BUFFER; - expectedFieldSize = strlen( HEADER_NOT_IN_BUFFER ); + /* Configure the http_parser_execute mock. */ + pExpectedBuffer = pResponseWithoutValue; + expectedBufferSize = strlen( pResponseWithoutValue ); + invokeHeaderFieldCallback = true; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; /* Call the function under test. */ - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); + testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutValue[ 0 ]; + testResponse.bufferLen = strlen( pResponseWithoutValue ); retCode = HTTPClient_ReadHeader( &testResponse, - HEADER_NOT_IN_BUFFER, + ( const uint8_t * ) HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), + &pValueLoc, + &valueLen ); + ok( retCode == HTTP_INVALID_RESPONSE ); + + /* Test when the invalid response does not contain the requested header. + * (Response is invalid as it doesn't end with "\r\n\r\n".) */ + reset(); + const char * pResponseWithoutHeaders = "HTTP/1.1 200 OK\r\n" + "test-header0:test-value0"; + /* Configure the http_parser_execute mock. */ + pExpectedBuffer = &pResponseWithoutHeaders[ 0 ]; + expectedBufferSize = strlen( pResponseWithoutHeaders ); + parserErrNo = HPE_UNKNOWN; + /* Call the function under test. */ + testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutHeaders[ 0 ]; + testResponse.bufferLen = strlen( pResponseWithoutHeaders ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) HEADER_NOT_IN_BUFFER, strlen( HEADER_NOT_IN_BUFFER ), &pValueLoc, &valueLen ); - ok( retCode == HTTP_HEADER_NOT_FOUND ); + ok( retCode == HTTP_INVALID_RESPONSE ); /*************************** Test Happy-Path Cases *****************************/ /* Test when requested header in present response. */ reset(); - /* Configure the HTTPClient_FindResponseInHeader mock. */ - pExpectedField = HEADER_IN_BUFFER; - expectedFieldSize = strlen( HEADER_IN_BUFFER ); - pValLocToReturn = &pTestResponse[ headerValInRespLoc ]; - valLenToReturn = headerValInRespLen; + /* Configure the http_parser_execute mock. */ + expectedValCbRetVal = HTTP_PARSER_STOP_PARSING; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + invokeHeaderFieldCallback = true; + invokeHeaderValueCallback = true; + parserErrNo = HPE_CB_header_value; /* Call the function under test. */ testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - HEADER_IN_BUFFER, + ( const uint8_t * ) HEADER_IN_BUFFER, strlen( HEADER_IN_BUFFER ), &pValueLoc, &valueLen ); From 5be7b556cd317f3f19c51d9217ef69b2e6c8e228 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 12 May 2020 14:26:57 -0700 Subject: [PATCH 495/844] Add CMake build for demos and unit tests (#926) * Add CMakeLists from v4 beta * More cmake changes * Successfully compile mqtt_demo_plaintext executable * Add filePaths.cmake for mqtt demos * Compile demos using cmake * Add unity and cmock as submodules * Add 3rdparty CMakeLists.txt * Add example utest for http with cmake file * Add tests directory for defining unit test * Do not add dependency if mock_name is empty * Make basic test finally work * Remove tests/CMakeLists.txt * Add separate folder for tests in bin * Fix format * Get logging to work * Remove include_dir from create_test.cmake * Remove duplicate unity 3rdparty library * Add mock for mqtt tests * Add README for unit testing * Update README * Compile all tests for coverage target * Remove accidental Makefile * Remove unity submodule * Add more docs * Update README * Change CMAKE_C_STANDARD * Update docs * Indent correctly * Add missing defines to config.h * Link cmock to test target * Remove double include of * Add comment for tools/cmock dir * Address PR comments * Move allutest README.md to a single docs folder in root * Add steps to build the demos * Update README.md with correct build steps * Address PR comments * Remove filePaths.cmake for demos * Remove BUILD_CLONE_SUBMODULES * Address PR comments * Add BUILD_CLONE_SUBMODULES option * Rename filepaths.cmake to filePaths.cmake * Remove platform/include from filePaths.cmake --- .gitmodules | 3 + CMakeLists.txt | 48 +++++ README.md | 16 ++ demos/CMakeLists.txt | 17 ++ demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 26 +++ demos/mqtt/mqtt_demo_basic_tls/config.h | 14 +- demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 21 +++ docs/utesting.md | 126 +++++++++++++ libraries/3rdparty/CMock | 1 + libraries/CMakeLists.txt | 72 ++++++++ libraries/standard/http/CMakeLists.txt | 6 + libraries/standard/http/filePaths.cmake | 22 +++ libraries/standard/http/test/config.h | 2 +- libraries/standard/http/utest/CMakeLists.txt | 73 ++++++++ libraries/standard/http/utest/config.h | 27 +++ libraries/standard/http/utest/http_utest.c | 37 ++++ libraries/standard/mqtt/CMakeLists.txt | 22 +++ libraries/standard/mqtt/filePaths.cmake | 20 +++ libraries/standard/mqtt/utest/CMakeLists.txt | 83 +++++++++ libraries/standard/mqtt/utest/config.h | 63 +++++++ libraries/standard/mqtt/utest/mqtt_utest.c | 37 ++++ tools/cmake/filePaths.cmake | 6 + tools/cmock/coverage.cmake | 70 ++++++++ tools/cmock/create_test.cmake | 170 ++++++++++++++++++ tools/cmock/project.yml | 35 ++++ 25 files changed, 1015 insertions(+), 2 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 demos/CMakeLists.txt create mode 100644 demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt create mode 100644 demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt create mode 100644 docs/utesting.md create mode 160000 libraries/3rdparty/CMock create mode 100644 libraries/CMakeLists.txt create mode 100644 libraries/standard/http/CMakeLists.txt create mode 100644 libraries/standard/http/filePaths.cmake create mode 100644 libraries/standard/http/utest/CMakeLists.txt create mode 100644 libraries/standard/http/utest/config.h create mode 100644 libraries/standard/http/utest/http_utest.c create mode 100644 libraries/standard/mqtt/CMakeLists.txt create mode 100644 libraries/standard/mqtt/filePaths.cmake create mode 100644 libraries/standard/mqtt/utest/CMakeLists.txt create mode 100644 libraries/standard/mqtt/utest/config.h create mode 100644 libraries/standard/mqtt/utest/mqtt_utest.c create mode 100644 tools/cmake/filePaths.cmake create mode 100644 tools/cmock/coverage.cmake create mode 100644 tools/cmock/create_test.cmake create mode 100644 tools/cmock/project.yml diff --git a/.gitmodules b/.gitmodules index 821dcb2a3d..75b7619c25 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libraries/standard/http/third_party/http_parser"] path = libraries/standard/http/third_party/http_parser url = https://github.com/nodejs/http-parser.git +[submodule "libraries/3rdparty/CMock"] + path = libraries/3rdparty/CMock + url = https://github.com/ThrowTheSwitch/CMock diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..0f10970f91 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +# Project information. +cmake_minimum_required( VERSION 3.5.0 ) +project( AwsIotDeviceSdkEmbeddedC + VERSION 4.0.2 + LANGUAGES C ) + +# Allow the project to be organized into folders. +set_property( GLOBAL PROPERTY USE_FOLDERS ON ) + +# Use C90. +set( CMAKE_C_STANDARD 90 ) +set( CMAKE_C_STANDARD_REQUIRED ON ) + +# Do not allow in-source build. +if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) + message( FATAL_ERROR "In-source build is not allowed. Please build in a separate directory, such as ${PROJECT_SOURCE_DIR}/build." ) +endif() + +# Import global configurations. +include("tools/cmake/filePaths.cmake") + +# Configure options to always show in CMake GUI. +option( BUILD_TESTS + "Set this to ON to build both demo and test executables. When OFF, only demo executables are built." + ON ) +option( BUILD_CLONE_SUBMODULES + "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." + ON ) + +# Unity test framework does not export the correct symbols for DLLs. +set( ALLOW_SHARED_LIBRARIES ON ) + +include( CMakeDependentOption ) +CMAKE_DEPENDENT_OPTION( BUILD_SHARED_LIBS + "Set this to ON to build all libraries as shared libraries. When OFF, libraries build as static libraries." + ON "${ALLOW_SHARED_LIBRARIES}" + OFF ) + +# Set output directories. +set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) +set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) +set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) + +# Add libraries. +add_subdirectory( libraries ) + +# Build the demos. +add_subdirectory( demos ) diff --git a/README.md b/README.md index ecf191c4ec..4d2fd7653d 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,19 @@ ## Development branch This branch currently hosts development of the next iteration of the AWS IoT Embedded C SDK version 4. It is currently a work in progress and should not be used to create any products. We will update this README when that status changes. + +## Building and Running Demos + +This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. + +### Prerequisites +- CMake 3.5.0 or later and a C90 compiler. +- A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. + - On Linux systems, installation of OpenSSL development libraries and header files, *version 1.0.2g or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + +### Build Steps +1. Go to the root directory of this repository. +1. Create build directory: `mkdir build && cd build` +1. Run *cmake* while inside build directory: `cmake ..` +1. Run this command to build the demos: `make` +1. Go to the `build/bin` directory to see executables. diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt new file mode 100644 index 0000000000..1675769aac --- /dev/null +++ b/demos/CMakeLists.txt @@ -0,0 +1,17 @@ +# Set the platform named based on the host OS if not defined. +if( NOT DEFINED PLATFORM_NAME ) + if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + set( PLATFORM_NAME "posix" CACHE STRING "Port to use for building the SDK." ) + else() + message( FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not a supported platform." ) + endif() +endif() + +# Include each subdirectory that has a CMakeLists.txt file in it +file(GLOB demo_dirs "${DEMOS_DIR}/*/*") +foreach(demo_dir IN LISTS demo_dirs) + if(IS_DIRECTORY "${demo_dir}" AND EXISTS "${demo_dir}/CMakeLists.txt") + get_filename_component( DEMO_EXE_NAME ${demo_dir} NAME ) + add_subdirectory(${demo_dir}) + endif() +endforeach() diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt new file mode 100644 index 0000000000..15943688e5 --- /dev/null +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -0,0 +1,26 @@ +set( DEMO_NAME "mqtt_demo_basic_tls" ) +# Demo target. +add_executable(${DEMO_NAME}) + +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL REQUIRED) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + mqtt + OpenSSL::Crypto + OpenSSL::SSL +) + +target_include_directories( + mqtt + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) diff --git a/demos/mqtt/mqtt_demo_basic_tls/config.h b/demos/mqtt/mqtt_demo_basic_tls/config.h index 1f032801fa..b7db274bd2 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/config.h @@ -26,6 +26,18 @@ #include typedef SSL * MQTTNetworkContext_t; +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT 10U + /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. @@ -43,6 +55,6 @@ typedef SSL * MQTTNetworkContext_t; * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER "testclient" #endif /* ifndef CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt new file mode 100644 index 0000000000..32c415c2da --- /dev/null +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -0,0 +1,21 @@ +set( DEMO_NAME "mqtt_demo_plaintext" ) +# Demo target. +add_executable(${DEMO_NAME}) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + mqtt +) + +target_include_directories( + mqtt + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) diff --git a/docs/utesting.md b/docs/utesting.md new file mode 100644 index 0000000000..ffc87ecb0b --- /dev/null +++ b/docs/utesting.md @@ -0,0 +1,126 @@ +# Running Unit Tests + +1. In your GNU compatible environment, open a terminal. +1. Install ruby: `sudo apt install ruby-full` +1. Install lcov: `sudo apt install lcov` +1. Go to the root directory of this repository. +1. Create build directory: `mkdir build && cd build` +1. Run cmake while inside build directory: `cmake ..` +1. Run this command to build and run the tests: `make coverage` +1. Go to `build` and open `*_utest_out.txt` to view logs +1. Go to `build/coverage` and open `index.html` to view test coverage results. + +# Writing Unit Tests + +### Introduction to CMock +**CMock** is a mocking framework, that automatically generates mocks. +It gives the ability to set expectations and implement stubs/callbacks. +This will allow you to call the functions to be tested and the **CMock** generated code will do all the rest for you. + +For example suppose your module accesses the internet using sockets, but you want to just test your module (unit) without testing/calling any socket API function as this could be unpredictable, slow, hard to simulate all possible +error scenarios and return codes. +In order to achieve that you tell **CMock** to mock the header file where the socket's APIs reside. For simplicity we will assume the only function used is *func*, **CMock** will generate the following mocks for you: + +``` +func_Ignore[AndReturn]([return_val]); +func_ExpectAnyArgs[AndReturn]([return_val]); +func_Expect[AndReturn](param1, param2,..., paramN ,[return_val]) +func_StubWithCallback(function_callback) +func_ExpectWithArray[AndReturn](param1, [depth1], param2, [depth2], ..., paramN, [depthN], [return_val]) +func_ReturnThruPtr_[parameter name](ret_pointer) +func_ReturnArrayThruPtr_parameeter(parameter, len) +func_ReturnMemThruPtr_[parameter name](size, ret_memory) +func_IgnoreArg_[parameter name]() +``` +For a detailed explanation about these mocks check the official documentation: +[The **CMock** official website](http://www.throwtheswitch.org/cmock) +[The **CMock** github repository](https://github.com/ThrowTheSwitch/CMock) + +### Setting up a new Unit Testing module in C SDK +To setup a module for Unit Testing, as an example we will follow a walkthrough approach for **mqtt** which is located in `libraries/standard/mqtt`. + +1. In the directory of the module to be tested, add the following to its CMakeLists.txt: +```cmake +if(BUILD_TESTS) + add_subdirectory(utest) +endif() +``` + +1. Create a new directory called `utest` + +1. For simplicity, copy the CMakeLists.txt file from `libraries/standard/mqtt` into the new Unit-Test Directory `utest` + +1. Create a test source file that ends with `_utest.c`, it is where your test code will live. You will end up with 2 files in the utest directory (project_name_utest.c and CMakeLists.txt) + +1. Edit the copied file (CMakeLists.txt) in the following way + 1. Modify the `project_name` at the top to match the new one to be tested + 1. Replace all headers in `mock_list` with the ones you the source file + under test uses. + ```cmake + list(APPEND mock_list + my_file.h + ) + ``` + 1. Replace all the include directories in `mock_include_list` with the + directories your mocks are using + ```cmake + list(APPEND mock_include_list + /my/file/dir/needs + ) + ``` + 1. Replace the definitions in `mock_define_list` list with the definitions you see fit for your source files + ```cmake + list(APPEND mock_define_list + -DMY_DEFINE=1 + ) + ``` + This will create header files in the following form: + my_file_mock.h which you will have to include from + your test code (project_name_utest.c) + This compilation step would create a shared object with the name `project_name_mock.so` + + 1. Replace all the files in `real_source_files` with the source files you will + be testing. This will create a library with the name `project_file_real.a` + ```cmake + list(APPEND real_source_files + code_to_test.c + ) + ``` + 1. Replace all the dirctories in `real_include_directories` with the + directories the source under test will include headers from + ```cmake + list(APPEND real_include_directories + /my/under/test/source/needs + ) + ``` + This compilation step would create an object library called `project_name_real.a` + 1. Replace all directories in `test_include_directories` with what the test + code needs (project_name_utest.c) + ```cmake + list(APPEND test_include_directories + /my/test/code/needs + ) + ``` + + 1. And that's it for this file + + 1. At the end, this makefile will create the mock files and 3 object files + `project_name_mock.so`, `project_name_real.a` and `project_name_utest` + + 1. If you'd like to automatically compile your unit tests upon `make coverage`, edit `CMakeLists.txt` in the root directory of this repo to include `project_name_utest`: + ```cmake + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake + DEPENDS cmock unity http_utest mqtt_utest project_name_utest + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + ``` + +1. Write your Unit Test code `project_name_utest.c` it is easier if you copy a + file from another place `mqtt` for example to reuse its skeleton + A few points to keep in mind: + * There is no need to write a main function, it is automatically generated + * Testing functions must start with `test_` + * We use unity to test and assert results +1. You should be ready to go by now + Always remember! If it is not tested, it doesn't work :( ... Happy Testing! diff --git a/libraries/3rdparty/CMock b/libraries/3rdparty/CMock new file mode 160000 index 0000000000..150573c742 --- /dev/null +++ b/libraries/3rdparty/CMock @@ -0,0 +1 @@ +Subproject commit 150573c742ce15061a0b675aa0f8e29c85008062 diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt new file mode 100644 index 0000000000..6dd4e44a1f --- /dev/null +++ b/libraries/CMakeLists.txt @@ -0,0 +1,72 @@ + +if( ${BUILD_TESTS} ) + # Check if the CMock source directory exists. + if( NOT EXISTS ${3RDPARTY_DIR}/CMock/src ) + # Attempt to clone CMock. + if( ${BUILD_CLONE_SUBMODULES} ) + find_package( Git REQUIRED ) + + message( "Cloning submodule CMock." ) + execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive libraries/3rdparty/CMock + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE CMOCK_CLONE_RESULT ) + + if( NOT ${CMOCK_CLONE_RESULT} STREQUAL "0" ) + message( FATAL_ERROR "Failed to clone CMock submodule." ) + endif() + else() + message( FATAL_ERROR "The required submodule CMock does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) + endif() + endif() + + include("${ROOT_DIR}/tools/cmock/create_test.cmake") + + include_directories("${3RDPARTY_DIR}/CMock/vendor/unity/src/" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src" + "${3RDPARTY_DIR}/CMock/src" + ) + link_directories("${CMAKE_BINARY_DIR}/lib" + ) + + add_library(cmock STATIC + "${ROOT_DIR}/libraries/3rdparty/CMock/src/cmock.c" + ) + + set_target_properties(cmock PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + COMPILE_FLAGS "-Og" + ) + + add_library(unity STATIC + "${3RDPARTY_DIR}/CMock/vendor/unity/src/unity.c" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src/unity_fixture.c" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src/unity_memory.c" + ) + set_target_properties(unity PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + ) + + target_include_directories(cmock PUBLIC + ${ROOT_DIR}/libraries/3rdparty/CMock/src + ${ROOT_DIR}/libraries/3rdparty/CMock/vendor/unity/src/ + ${ROOT_DIR}/libraries/3rdparty/CMock/examples + ) + target_link_libraries(cmock unity) + + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake + DEPENDS cmock unity http_utest mqtt_utest + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endif() + +# Add standard modules +file(GLOB standard_modules "${MODULES_DIR}/standard/*") +foreach(module IN LISTS standard_modules) + if(IS_DIRECTORY "${module}" AND EXISTS "${module}/CMakeLists.txt") + add_subdirectory(${module}) + endif() +endforeach() diff --git a/libraries/standard/http/CMakeLists.txt b/libraries/standard/http/CMakeLists.txt new file mode 100644 index 0000000000..ca307d3bde --- /dev/null +++ b/libraries/standard/http/CMakeLists.txt @@ -0,0 +1,6 @@ +# Include filepaths for source and include. +include(filePaths.cmake) + +if(BUILD_TESTS) + add_subdirectory(utest) +endif() diff --git a/libraries/standard/http/filePaths.cmake b/libraries/standard/http/filePaths.cmake new file mode 100644 index 0000000000..cbe2fed879 --- /dev/null +++ b/libraries/standard/http/filePaths.cmake @@ -0,0 +1,22 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# HTTP library source files. +set( HTTP_SOURCES + "${MODULES_DIR}/standard/http/src/http_client.c" + "${MODULES_DIR}/standard/http/src/http_client_parse.c" ) + +# HTTP library Include directories. +set( HTTP_INCLUDE_PUBLIC_DIRS + "${MODULES_DIR}/standard/http/include" + "${MODULES_DIR}/standard/utilities/include" + ) + +# HTTP test include directories. +set( HTTP_TEST_INCLUDE_PRIVATE_DIRS + "${MODULES_DIR}/standard/http/src" + ) diff --git a/libraries/standard/http/test/config.h b/libraries/standard/http/test/config.h index 1dc37071eb..1f158d0f56 100644 --- a/libraries/standard/http/test/config.h +++ b/libraries/standard/http/test/config.h @@ -8,7 +8,7 @@ /* Include file for POSIX reference implementation. */ #include "iot_logging.h" -/* Define the IotLog logging interface to enabling logging. +/* Define the IotLog logging interface to enable logging. * This demo maps the macro to the reference POSIX implementation for logging. * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the * log, as metadata in each log message. */ diff --git a/libraries/standard/http/utest/CMakeLists.txt b/libraries/standard/http/utest/CMakeLists.txt new file mode 100644 index 0000000000..02dd4a18d0 --- /dev/null +++ b/libraries/standard/http/utest/CMakeLists.txt @@ -0,0 +1,73 @@ +include("../filePaths.cmake") +project ("http unit test") +cmake_minimum_required (VERSION 3.13) + +# ==================== Define your project name (edit) ======================== +set(project_name "http") + +# ===================== Create your mock here (edit) ======================== + +# list the files to mock here +list(APPEND mock_list + "" + ) +# list the directories your mocks need +list(APPEND mock_include_list + "" + ) +#list the definitions of your mocks to control what to be included +list(APPEND mock_define_list + "" + ) + +# ================= Create the library under test here (edit) ================== + +# list the files you would like to test here +list(APPEND real_source_files + ${HTTP_SOURCES} + ) +# list the directories the module under test includes +list(APPEND real_include_directories + . + ${HTTP_INCLUDE_PUBLIC_DIRS} + ${HTTP_TEST_INCLUDE_PRIVATE_DIRS} + "${ROOT_DIR}/platform/include" + ) + +# ===================== Create UnitTest Code here (edit) ===================== + +# list the directories your test needs to include +list(APPEND test_include_directories + . + ${HTTP_INCLUDE_PUBLIC_DIRS} + ${HTTP_TEST_INCLUDE_PRIVATE_DIRS} + "${ROOT_DIR}/platform/include" + ) + +# ============================= (end edit) =================================== + +set(mock_name "${project_name}_mock") +set(real_name "${project_name}_real") + +create_real_library(${real_name} + "${real_source_files}" + "${real_include_directories}" + "" + ) + +list(APPEND utest_link_list + lib${real_name}.a + ) + +list(APPEND utest_dep_list + ${real_name} + ) + +set(utest_name "${project_name}_utest") +set(utest_source "${project_name}_utest.c") +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) diff --git a/libraries/standard/http/utest/config.h b/libraries/standard/http/utest/config.h new file mode 100644 index 0000000000..39f58af1f3 --- /dev/null +++ b/libraries/standard/http/utest/config.h @@ -0,0 +1,27 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG + +#define USE_AWS_IOT_CSDK_LOGGING 1 + +#ifdef USE_AWS_IOT_CSDK_LOGGING + +/* Include file for POSIX reference implementation. */ + #include "iot_logging.h" + +/* Define the IotLog logging interface to enable logging. + * This demo maps the macro to the reference POSIX implementation for logging. + * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the + * log, as metadata in each log message. */ + #define IotLog( messageLevel, pFormat, ... ) \ + IotLog_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) + +#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ + +#endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c new file mode 100644 index 0000000000..0252b92be8 --- /dev/null +++ b/libraries/standard/http/utest/http_utest.c @@ -0,0 +1,37 @@ +#include + +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "http_client.h" + +/* Private includes for internal macros. */ +#include "private/http_client_internal.h" +#include "private/http_client_parse.h" + +/* ============================ UNITY FIXTURES ============================== */ +void setUp( void ) +{ +} + +/* called before each testcase */ +void tearDown( void ) +{ +} + +/* called at the beginning of the whole suite */ +void suiteSetUp() +{ +} + +/* called at the end of the whole suite */ +int suiteTearDown( int numFailures ) +{ +} + +/* ====================== Testing HTTPClient_AddHeader ====================== */ +void test_Http_AddHeader_invalid_params( void ) +{ + TEST_ASSERT_EQUAL( HTTPClient_AddHeader( NULL, NULL, 0, NULL, 0 ), + HTTP_INVALID_PARAMETER ); +} diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt new file mode 100644 index 0000000000..e04c7be76f --- /dev/null +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -0,0 +1,22 @@ +# Include filepaths for source and include. +include(filePaths.cmake) + +# MQTT library target. +add_library( mqtt + ${MQTT_SOURCES} ) + +# MQTT public include path. +target_include_directories( mqtt PUBLIC ${MQTT_INCLUDE_PUBLIC_DIRS} ) + +# MQTT private include path. +target_include_directories( mqtt PRIVATE ${MQTT_INCLUDE_PRIVATE_DIRS} ) + +# Organization of MQTT in IDE projects. +set_target_properties( mqtt PROPERTIES FOLDER libraries/standard ) +source_group( include FILES include/mqtt.h include/mqtt_lightweight.h include/mqtt_state.h ) +source_group( src FILES ${MQTT_SOURCES} ) +source_group( src\\private FILES src/private/mqtt_internal.h ) + +if(BUILD_TESTS) + add_subdirectory(utest) +endif() diff --git a/libraries/standard/mqtt/filePaths.cmake b/libraries/standard/mqtt/filePaths.cmake new file mode 100644 index 0000000000..6410afb387 --- /dev/null +++ b/libraries/standard/mqtt/filePaths.cmake @@ -0,0 +1,20 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# MQTT library source files. +set( MQTT_SOURCES + "${MODULES_DIR}/standard/mqtt/src/mqtt.c" + "${MODULES_DIR}/standard/mqtt/src/mqtt_lightweight.c" ) + +# MQTT library Include directories. +set( MQTT_INCLUDE_PUBLIC_DIRS + "${MODULES_DIR}/standard/mqtt/include" + "${MODULES_DIR}/standard/utilities/include" ) + +# MQTT test include directories. +set( MQTT_TEST_INCLUDE_PRIVATE_DIRS + "${MODULES_DIR}/standard/mqtt/src" ) diff --git a/libraries/standard/mqtt/utest/CMakeLists.txt b/libraries/standard/mqtt/utest/CMakeLists.txt new file mode 100644 index 0000000000..36ca717560 --- /dev/null +++ b/libraries/standard/mqtt/utest/CMakeLists.txt @@ -0,0 +1,83 @@ +include("../filePaths.cmake") +project ("mqtt unit test") +cmake_minimum_required (VERSION 3.13) + +# ==================== Define your project name (edit) ======================== +set(project_name "mqtt") + +# ===================== Create your mock here (edit) ======================== + +# list the files to mock here +list(APPEND mock_list + "${MODULES_DIR}/standard/mqtt/include/mqtt_lightweight.h" + ) +# list the directories your mocks need +list(APPEND mock_include_list + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + "${ROOT_DIR}/platform/include" + ) +#list the definitions of your mocks to control what to be included +list(APPEND mock_define_list + "" + ) + +# ================= Create the library under test here (edit) ================== + +# list the files you would like to test here +list(APPEND real_source_files + ${MQTT_SOURCES} + ) +# list the directories the module under test includes +list(APPEND real_include_directories + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + ${MQTT_TEST_INCLUDE_PRIVATE_DIRS} + "${ROOT_DIR}/platform/include" + ) + +# ===================== Create UnitTest Code here (edit) ===================== + +# list the directories your test needs to include +list(APPEND test_include_directories + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + ${MQTT_TEST_INCLUDE_PRIVATE_DIRS} + "${ROOT_DIR}/platform/include" + ) + +# ============================= (end edit) =================================== + +set(mock_name "${project_name}_mock") +set(real_name "${project_name}_real") + +create_mock_list(${mock_name} + "${mock_list}" + "${ROOT_DIR}/tools/cmock/project.yml" + "${mock_include_list}" + "${mock_define_list}" + ) + +create_real_library(${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" + ) + +list(APPEND utest_link_list + -l${mock_name} + lib${real_name}.a + ) + +list(APPEND utest_dep_list + ${real_name} + ) + +set(utest_name "${project_name}_utest") +set(utest_source "${project_name}_utest.c") +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) diff --git a/libraries/standard/mqtt/utest/config.h b/libraries/standard/mqtt/utest/config.h new file mode 100644 index 0000000000..d11838db55 --- /dev/null +++ b/libraries/standard/mqtt/utest/config.h @@ -0,0 +1,63 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG + +#define USE_AWS_IOT_CSDK_LOGGING 1 + +#ifdef USE_AWS_IOT_CSDK_LOGGING + +/* Include file for POSIX reference implementation. */ + #include "iot_logging.h" + +/* Define the IotLog logging interface to enable logging. + * This demo maps the macro to the reference POSIX implementation for logging. + * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the + * log, as metadata in each log message. */ + #define IotLog( messageLevel, pFormat, ... ) \ + IotLog_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) + +#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ + + +/* Set network context to socket (int). */ +typedef int MQTTNetworkContext_t; + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT 10U + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient" + + +#endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c new file mode 100644 index 0000000000..167ad3b294 --- /dev/null +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -0,0 +1,37 @@ +#include + +#include "unity.h" + +#include "mock_mqtt_lightweight.h" + +/* Include paths for public enums, structures, and macros. */ +#include "mqtt.h" + +/* ============================ UNITY FIXTURES ============================ */ +void setUp( void ) +{ +} + +/* called before each testcase */ +void tearDown( void ) +{ +} + +/* called at the beginning of the whole suite */ +void suiteSetUp() +{ +} + +/* called at the end of the whole suite */ +int suiteTearDown( int numFailures ) +{ +} + +/* ============================ Testing MQTT_Connect ====================== */ +void test_Mqtt_connect_packet_size_gt_max( void ) +{ + /* This mocked method will be called inside MQTT_Connect(...). */ + MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + + TEST_ASSERT_EQUAL( MQTT_Connect( NULL, NULL, NULL, NULL ), MQTTBadParameter ); +} diff --git a/tools/cmake/filePaths.cmake b/tools/cmake/filePaths.cmake new file mode 100644 index 0000000000..e1cc98f9c3 --- /dev/null +++ b/tools/cmake/filePaths.cmake @@ -0,0 +1,6 @@ +# Set some global path variables. +get_filename_component(__root_dir "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +set(ROOT_DIR ${__root_dir} CACHE INTERNAL "C SDK source root.") +set(DEMOS_DIR "${ROOT_DIR}/demos" CACHE INTERNAL "C SDK demos root.") +set(MODULES_DIR "${ROOT_DIR}/libraries" CACHE INTERNAL "C SDK modules root.") +set(3RDPARTY_DIR "${MODULES_DIR}/3rdparty" CACHE INTERNAL "3rdparty libraries root.") diff --git a/tools/cmock/coverage.cmake b/tools/cmock/coverage.cmake new file mode 100644 index 0000000000..bc5cca4478 --- /dev/null +++ b/tools/cmock/coverage.cmake @@ -0,0 +1,70 @@ +# Taken from amazon-freertos repository +cmake_minimum_required(VERSION 3.13) +set(BINARY_DIR ${CMAKE_BINARY_DIR}) +# reset coverage counters +execute_process( + COMMAND lcov --directory ${CMAKE_BINARY_DIR} + --base-directory ${CMAKE_BINARY_DIR} + --zerocounters + + COMMAND mkdir -p ${CMAKE_BINARY_DIR}/coverage + ) +# make the initial/baseline capture a zeroed out files +execute_process( COMMAND lcov --directory ${CMAKE_BINARY_DIR} + --base-directory ${CMAKE_BINARY_DIR} + --initial + --capture + --rc lcov_branch_coverage=1 + --rc genhtml_branch_coverage=1 + --output-file=${CMAKE_BINARY_DIR}/base_coverage.info + ) +file(GLOB files "${CMAKE_BINARY_DIR}/bin/tests/*") + +set(REPORT_FILE ${CMAKE_BINARY_DIR}/utest_report.txt) +file(WRITE ${REPORT_FILE} "") +# execute all files in bin directory, gathering the output to show it in CI +foreach(testname ${files}) + get_filename_component(test + ${testname} + NAME_WLE + ) + message("Running ${testname}") + execute_process(COMMAND ${testname} OUTPUT_FILE ${CMAKE_BINARY_DIR}/${test}_out.txt) + + file(READ ${CMAKE_BINARY_DIR}/${test}_out.txt CONTENTS) + file(APPEND ${REPORT_FILE} "${CONTENTS}") +endforeach() + +# generate Junit style xml output +execute_process(COMMAND ruby + ${CMAKE_SOURCE_DIR}/../libraries/3rdparty/CMock/vendor/unity/auto/parse_output.rb + -xml ${REPORT_FILE} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + +# capture data after running the tests +execute_process( + COMMAND lcov --capture + --rc lcov_branch_coverage=1 + --rc genhtml_branch_coverage=1 + --base-directory ${CMAKE_BINARY_DIR} + --directory ${CMAKE_BINARY_DIR} + --output-file ${CMAKE_BINARY_DIR}/second_coverage.info + ) + +# combile baseline results (zeros) with the one after running the tests +execute_process( + COMMAND lcov --base-directory ${CMAKE_BINARY_DIR} + --directory ${CMAKE_BINARY_DIR} + --add-tracefile ${CMAKE_BINARY_DIR}/base_coverage.info + --add-tracefile ${CMAKE_BINARY_DIR}/second_coverage.info + --output-file ${CMAKE_BINARY_DIR}/coverage.info + --no-external + --rc lcov_branch_coverage=1 + ) +execute_process( + COMMAND genhtml --rc lcov_branch_coverage=1 + --branch-coverage + --output-directory ${CMAKE_BINARY_DIR}/coverage + ${CMAKE_BINARY_DIR}/coverage.info + ) diff --git a/tools/cmock/create_test.cmake b/tools/cmock/create_test.cmake new file mode 100644 index 0000000000..108d0e5911 --- /dev/null +++ b/tools/cmock/create_test.cmake @@ -0,0 +1,170 @@ +# Taken from amazon-freertos repository + +#function to create the test executable +function(create_test test_name + test_src + link_list + dep_list + include_list) + set(mocks_dir "${CMAKE_CURRENT_BINARY_DIR}/mocks") + include (CTest) + get_filename_component(test_src_absolute ${test_src} ABSOLUTE) + add_custom_command(OUTPUT ${test_name}_runner.c + COMMAND ruby + ${CMAKE_SOURCE_DIR}/libraries/3rdparty/unity/auto/generate_test_runner.rb + ${CMAKE_SOURCE_DIR}/tools/cmock/project.yml + ${test_src_absolute} + ${test_name}_runner.c + DEPENDS ${test_src} + ) + add_executable(${test_name} ${test_src} ${test_name}_runner.c) + target_link_libraries(${test_name} cmock) + set_target_properties(${test_name} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests" + INSTALL_RPATH_USE_LINK_PATH TRUE + LINK_FLAGS " \ + -Wl,-rpath,${CMAKE_BINARY_DIR}/lib \ + -Wl,-rpath,${CMAKE_CURRENT_BINARY_DIR}/lib" + ) + target_include_directories(${test_name} PUBLIC + ${mocks_dir} + ${include_list} + ) + target_link_libraries(${test_name} + "${ROOT_DIR}/platform/posix/iot_clock_posix.o" + "${ROOT_DIR}/platform/posix/iot_logging.o" + ) + + target_link_directories(${test_name} PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ) + + # link all libraries sent through parameters + foreach(link IN LISTS link_list) + target_link_libraries(${test_name} ${link}) + endforeach() + + # add dependency to all the dep_list parameter + foreach(dependency IN LISTS dep_list) + add_dependencies(${test_name} ${dependency}) + endforeach() + target_link_libraries(${test_name} -lgcov) + target_link_directories(${test_name} PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/lib + ) + add_test(NAME ${test_name} + COMMAND ${CMAKE_BINARY_DIR}/bin/tests/${test_name} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endfunction() + +# Run the C preprocessor on target files. +# Takes a CMAKE list of arguments to pass to the C compiler +function(preprocess_mock_list mock_name file_list compiler_args) + set_property(GLOBAL PROPERTY ${mock_name}_processed TRUE) + foreach (target_file IN LISTS file_list) + # Has to be TARGET ALL so the file is pre-processed before CMOCK + # is executed on the file. + add_custom_command(OUTPUT ${target_file}.backup + COMMAND scp ${target_file} ${target_file}.backup + VERBATIM COMMAND ${CMAKE_C_COMPILER} -E ${compiler_args} ${target_file} > ${target_file}.out + ) + add_custom_target(pre_${mock_name} + COMMAND mv ${target_file}.out ${target_file} + DEPENDS ${target_file}.backup + ) + endforeach() + + # Clean up temporary files that were created. + # First we test to see if the backup file still exists. If it does we revert + # the change made to the original file. + foreach (target_file IN LISTS file_list) + add_custom_command(TARGET ${mock_name} + POST_BUILD + COMMAND test ! -e ${target_file}.backup || mv ${target_file}.backup ${target_file} + ) + endforeach() +endfunction() + +# Generates a mock library based on a module's header file +# places the generated source file in the build directory +# @param mock_name: name of the target name +# @param mock_list list of header files to mock +# @param cmock_config configuration file of the cmock framework +# @param mock_include_list include list for the target +# @param mock_define_list special definitions to control compilation +function(create_mock_list mock_name + mock_list + cmock_config + mock_include_list + mock_define_list) + set(mocks_dir "${CMAKE_CURRENT_BINARY_DIR}/mocks") + add_library(${mock_name} SHARED) + foreach (mock_file IN LISTS mock_list) + get_filename_component(mock_file_abs + ${mock_file} + ABSOLUTE + ) + get_filename_component(mock_file_name + ${mock_file} + NAME_WLE + ) + get_filename_component(mock_file_dir + ${mock_file} + DIRECTORY + ) + add_custom_command ( + OUTPUT ${mocks_dir}/mock_${mock_file_name}.c + COMMAND ruby + ${CMAKE_SOURCE_DIR}/libraries/3rdparty/CMock/lib/cmock.rb + -o${cmock_config} ${mock_file_abs} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + target_sources(${mock_name} PUBLIC + ${mocks_dir}/mock_${mock_file_name}.c + ) + + target_include_directories(${mock_name} PUBLIC + ${mock_file_dir} + ) + endforeach() + target_include_directories(${mock_name} PUBLIC + ${mocks_dir} + ${mock_include_list} + ) + set_target_properties(${mock_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + ) + target_compile_definitions(${mock_name} PUBLIC + ${mock_define_list} + ) +endfunction() + + +function(create_real_library target + src_file + real_include_list + mock_name) + add_library(${target} STATIC + ${src_file} + ) + target_include_directories(${target} PUBLIC + ${real_include_list} + ) + set_target_properties(${target} PROPERTIES + COMPILE_FLAGS "-Wextra -Wpedantic \ + -fprofile-arcs -ftest-coverage -fprofile-generate \ + -Wno-unused-but-set-variable" + LINK_FLAGS "-fprofile-arcs -ftest-coverage \ + -fprofile-generate " + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib + ) + if(NOT(mock_name STREQUAL "")) + add_dependencies(${target} ${mock_name}) + target_link_libraries(${target} + -l${mock_name} + -lgcov + ) + endif() +endfunction() diff --git a/tools/cmock/project.yml b/tools/cmock/project.yml new file mode 100644 index 0000000000..6e5afe65f9 --- /dev/null +++ b/tools/cmock/project.yml @@ -0,0 +1,35 @@ +# Taken from amazon-freertos repository +:cmock: + :mock_prefix: mock_ + :when_no_prototypes: :warn + :enforce_strict_ordering: TRUE + :plugins: + - :ignore + - :ignore_arg + - :expect_any_args + - :array + - :callback + - :return_thru_ptr + :callback_include_count: true # include a count arg when calling the callback + :callback_after_arg_check: false # check arguments before calling the callback + :treat_as: + uint8: HEX8 + uint16: HEX16 + uint32: UINT32 + int8: INT8 + bool: UINT8 + :includes: # This will add these includes to each mock. + - + - + - + :treat_externs: :exclude # Now the extern-ed functions will be mocked. + :weak: __attribute__((weak)) + :verbosity: 3 + :attributes: + - PRIVILEGED_FUNCTION + - 'int fcntl(int s, int cmd, ...);' + :strippables: + - PRIVILEGED_FUNCTION + - portDONT_DISCARD + - '(?:fcntl\s*\(+.*?\)+)' # this function is causing some trouble with code coverage as the annotations are calling the mocked one, so we won't mock it + :treat_externs: :include From daa4cda41e616df157ea8656e9208ae4d9c5db30 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 12 May 2020 17:13:39 -0700 Subject: [PATCH 496/844] Remove Iot_ prefix from logging and clock (#932) * Remove Iot_ prefix from logging and clock * Remove Iot prefix from new log call-sites in http introduced from rebasing with development --- libraries/standard/http/src/http_client.c | 241 ++++++----- .../http/src/private/http_client_internal.h | 18 +- libraries/standard/http/test/Makefile | 2 +- libraries/standard/mqtt/src/mqtt.c | 136 +++---- .../standard/mqtt/src/mqtt_lightweight.c | 378 +++++++++--------- .../standard/mqtt/src/private/mqtt_internal.h | 18 +- ...{iot_logging_levels.h => logging_levels.h} | 0 .../{iot_logging_setup.h => logging_setup.h} | 124 +++--- platform/include/{iot_clock.h => clock.h} | 0 platform/include/{iot_logging.h => logging.h} | 8 +- .../{iot_clock_posix.c => clock_posix.c} | 4 +- platform/posix/{iot_logging.c => logging.c} | 12 +- 12 files changed, 470 insertions(+), 471 deletions(-) rename libraries/standard/utilities/include/{iot_logging_levels.h => logging_levels.h} (100%) rename libraries/standard/utilities/include/{iot_logging_setup.h => logging_setup.h} (55%) rename platform/include/{iot_clock.h => clock.h} (100%) rename platform/include/{iot_logging.h => logging.h} (92%) rename platform/posix/{iot_clock_posix.c => clock_posix.c} (98%) rename platform/posix/{iot_logging.c => logging.c} (97%) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index f46ac7553a..5e88c46418 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -345,11 +345,11 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, } else { - IotLogErrorWithArgs( "Unable to add header in buffer: " - "Buffer has insufficient memory: " - "RequiredBytes=%d, RemainingBufferSize=%d", - toAddLen, - ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ); + LogErrorWithArgs( "Unable to add header in buffer: " + "Buffer has insufficient memory: " + "RequiredBytes=%d, RemainingBufferSize=%d", + toAddLen, + ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ); returnStatus = HTTP_INSUFFICIENT_MEMORY; } @@ -429,37 +429,37 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques /* Check for NULL parameters. */ if( pRequestHeaders == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( "Parameter check failed: pRequestHeaders is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pRequestInfo == NULL ) ) { - IotLogError( "Parameter check failed: pRequestInfo is NULL." ); + LogError( "Parameter check failed: pRequestInfo is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pRequestInfo->method == NULL ) ) { - IotLogError( "Parameter check failed: pRequestInfo->method is NULL." ); + LogError( "Parameter check failed: pRequestInfo->method is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestInfo->pHost == NULL ) { - IotLogError( "Parameter check failed: pRequestInfo->pHost is NULL." ); + LogError( "Parameter check failed: pRequestInfo->pHost is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestInfo->methodLen == 0 ) { - IotLogError( "Parameter check failed: pRequestInfo->methodLen must be greater than 0." ); + LogError( "Parameter check failed: pRequestInfo->methodLen must be greater than 0." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestInfo->hostLen == 0 ) { - IotLogError( "Parameter check failed: pRequestInfo->hostLen must be greater than 0." ); + LogError( "Parameter check failed: pRequestInfo->hostLen must be greater than 0." ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -538,32 +538,32 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Check for NULL parameters. */ if( pRequestHeaders == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( "Parameter check failed: pRequestHeaders is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pField == NULL ) ) { - IotLogError( "Parameter check failed: pField is NULL." ); + LogError( "Parameter check failed: pField is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pValue == NULL ) ) { - IotLogError( "Parameter check failed: pValue is NULL." ); + LogError( "Parameter check failed: pValue is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( fieldLen == 0u ) { - IotLogError( "Parameter check failed: fieldLen must be greater than 0." ); + LogError( "Parameter check failed: fieldLen must be greater than 0." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( valueLen == 0u ) { - IotLogError( "Parameter check failed: valueLen must be greater than 0." ); + LogError( "Parameter check failed: valueLen must be greater than 0." ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -592,36 +592,36 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, if( pRequestHeaders == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( "Parameter check failed: pRequestHeaders is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( rangeEnd < HTTP_RANGE_REQUEST_END_OF_FILE ) { - IotLogErrorWithArgs( "Parameter check failed: rangeEnd is invalid: " - "rangeEnd should be >=-1: RangeEnd=%d", rangeEnd ); + LogErrorWithArgs( "Parameter check failed: rangeEnd is invalid: " + "rangeEnd should be >=-1: RangeEnd=%d", rangeEnd ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( rangeStartOrlastNbytes < 0 ) && ( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) ) { - IotLogErrorWithArgs( "Parameter check failed: Invalid range values: " - "rangeEnd should be -1 when rangeStart < 0: " - "RangeStart=%d, RangeEnd=%d", - rangeStartOrlastNbytes, rangeEnd ); + LogErrorWithArgs( "Parameter check failed: Invalid range values: " + "rangeEnd should be -1 when rangeStart < 0: " + "RangeStart=%d, RangeEnd=%d", + rangeStartOrlastNbytes, rangeEnd ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) && ( rangeStartOrlastNbytes > rangeEnd ) ) { - IotLogErrorWithArgs( "Parameter check failed: Invalid range values: " - "rangeStart should be < rangeEnd when both are >= 0: " - "RangeStart=%d, RangeEnd=%d", - rangeStartOrlastNbytes, rangeEnd ); + LogErrorWithArgs( "Parameter check failed: Invalid range values: " + "rangeStart should be < rangeEnd when both are >= 0: " + "RangeStart=%d, RangeEnd=%d", + rangeStartOrlastNbytes, rangeEnd ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -699,25 +699,25 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport if( transportStatus < 0 ) { - IotLogErrorWithArgs( "Failed to send HTTP headers: Transport send()" - " returned error: TransportStatus=%d", - transportStatus ); + LogErrorWithArgs( "Failed to send HTTP headers: Transport send()" + " returned error: TransportStatus=%d", + transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } else if( ( size_t ) transportStatus != pRequestHeaders->headersLen ) { - IotLogErrorWithArgs( "Failed to send HTTP headers: Transport layer " - "did not send the required bytes: RequiredBytes=%d" - ", SentBytes=%d.", - pRequestHeaders->headersLen, - transportStatus ); + LogErrorWithArgs( "Failed to send HTTP headers: Transport layer " + "did not send the required bytes: RequiredBytes=%d" + ", SentBytes=%d.", + pRequestHeaders->headersLen, + transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } else { - IotLogDebugWithArgs( "Sent HTTP headers over the transport: BytesSent " - "=%d.", - transportStatus ); + LogDebugWithArgs( "Sent HTTP headers over the transport: BytesSent " + "=%d.", + transportStatus ); } return returnStatus; @@ -742,24 +742,24 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, if( transportStatus < 0 ) { - IotLogErrorWithArgs( "Failed to send HTTP body: Transport send() " - " returned error: TransportStatus=%d", - transportStatus ); + LogErrorWithArgs( "Failed to send HTTP body: Transport send() " + " returned error: TransportStatus=%d", + transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } else if( ( size_t ) transportStatus != reqBodyBufLen ) { - IotLogErrorWithArgs( "Failed to send HTTP body: Transport send() " - "did not send the required bytes: RequiredBytes=%d" - ", Sent bytes=%d.", - reqBodyBufLen, - transportStatus ); + LogErrorWithArgs( "Failed to send HTTP body: Transport send() " + "did not send the required bytes: RequiredBytes=%d" + ", Sent bytes=%d.", + reqBodyBufLen, + transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } else { - IotLogDebugWithArgs( "Sent HTTP body over the transport: BytesSent=%d.", - transportStatus ); + LogDebugWithArgs( "Sent HTTP body over the transport: BytesSent=%d.", + transportStatus ); } return returnStatus; @@ -786,35 +786,35 @@ HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { - IotLogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " - "returned error: TransportStatus=%d.", - transportStatus ); + LogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " + "returned error: TransportStatus=%d.", + transportStatus ); returnStatus = HTTP_NETWORK_ERROR; } else if( ( size_t ) transportStatus > bufferLen ) { /* There is a bug in the transport recv if more bytes are reported * to have been read than the bytes asked for. */ - IotLogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " - " read more bytes than requested: BytesRead=%d, " - "RequestedBytes=%d", - transportStatus, - bufferLen ); + LogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " + " read more bytes than requested: BytesRead=%d, " + "RequestedBytes=%d", + transportStatus, + bufferLen ); returnStatus = HTTP_NETWORK_ERROR; } else if( transportStatus > 0 ) { /* Some or all of the specified data was received. */ *pBytesReceived = ( size_t ) ( transportStatus ); - IotLogDebugWithArgs( "Received data from the transport: BytesReceived=%d.", - transportStatus ); + LogDebugWithArgs( "Received data from the transport: BytesReceived=%d.", + transportStatus ); } else { /* When a zero is returned from the transport recv it will not be * invoked again. */ - IotLogDebug( "Received zero bytes from trasnport recv(). Receiving " - "transport data is complete." ); + LogDebug( "Received zero bytes from trasnport recv(). Receiving " + "transport data is complete." ); } return returnStatus; @@ -835,27 +835,27 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, /* If no parsing occurred, that means network data was never received. */ if( parsingState == HTTP_PARSING_NONE ) { - IotLogErrorWithArgs( "Response not received: Zero returned from " - "transport recv: totalReceived=%d", - totalReceived ); + LogErrorWithArgs( "Response not received: Zero returned from " + "transport recv: totalReceived=%d", + totalReceived ); returnStatus = HTTP_NO_RESPONSE; } else if( parsingState == HTTP_PARSING_INCOMPLETE ) { if( totalReceived == responseBufferLen ) { - IotLogErrorWithArgs( "Cannot receive complete response from tansport" - " interface: Response buffer has insufficient " - "space: responseBufferLen=%d", - responseBufferLen ); + LogErrorWithArgs( "Cannot receive complete response from tansport" + " interface: Response buffer has insufficient " + "space: responseBufferLen=%d", + responseBufferLen ); returnStatus = HTTP_INSUFFICIENT_MEMORY; } else { - IotLogErrorWithArgs( "Received partial response from transport ", - "recv(): ResponseSize=%d, TotalBufferSize=%d", - totalReceived, - responseBufferLen - totalReceived ); + LogErrorWithArgs( "Received partial response from transport ", + "recv(): ResponseSize=%d, TotalBufferSize=%d", + totalReceived, + responseBufferLen - totalReceived ); returnStatus = HTTP_PARTIAL_RESPONSE; } } @@ -943,32 +943,32 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, if( pTransport == NULL ) { - IotLogError( "Parameter check failed: pTransport interface is NULL." ); + LogError( "Parameter check failed: pTransport interface is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pTransport->send == NULL ) { - IotLogError( "Parameter check failed: pTransport->send is NULL." ); + LogError( "Parameter check failed: pTransport->send is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pTransport->recv == NULL ) { - IotLogError( "Parameter check failed: pTransport->recv is NULL." ); + LogError( "Parameter check failed: pTransport->recv is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( "Parameter check failed: pRequestHeaders is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - IotLogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pResponse != NULL ) && ( pResponse->pBuffer == NULL ) ) { - IotLogError( "Parameter check failed: pResponse->pBuffer is NULL." ); + LogError( "Parameter check failed: pResponse->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -994,7 +994,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, } else { - IotLogDebug( "A request body was not sent: pRequestBodyBuf is NULL." ); + LogDebug( "A request body was not sent: pRequestBodyBuf is NULL." ); } } @@ -1009,7 +1009,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, } else { - IotLogDebug( "Response ignored: pResponse is NULL." ); + LogDebug( "Response ignored: pResponse is NULL." ); } } @@ -1044,9 +1044,9 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /* Check whether the parsed header matches the header we are looking for. */ if( ( fieldLen == pContext->fieldLen ) && ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) { - IotLogDebugWithArgs( "Found header field in response: " - "HeaderName=%.*s, HeaderLocation=0x%d", - fieldLen, pContext->pField ); + LogDebugWithArgs( "Found header field in response: " + "HeaderName=%.*s, HeaderLocation=0x%d", + fieldLen, pContext->pField ); /* Set the flag to indicate that header has been found in response. */ pContext->fieldFound = 1u; @@ -1094,9 +1094,9 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, if( pContext->fieldFound == 1u ) { - IotLogDebugWithArgs( "Found header value in response: " - "RequestedField=%.*s, ValueLocation=0x%d", - pContext->fieldLen, pContext->pField, pVaLueLoc ); + LogDebugWithArgs( "Found header value in response: " + "RequestedField=%.*s, ValueLocation=0x%d", + pContext->fieldLen, pContext->pField, pVaLueLoc ); /* Populate the output parameters with the location of the header value in the response buffer. */ *pContext->pValueLoc = ( const uint8_t * ) pVaLueLoc; @@ -1134,10 +1134,10 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) /* If we have reached here, all headers in the response have been parsed but the requested * header has not been found in the response buffer. */ - IotLogDebugWithArgs( "Reached end of header parsing: Header not found in response: " - "RequestedHeader=%.*s", - ( ( findHeaderContext_t * ) pHttpParser->data )->fieldLen, - ( ( findHeaderContext_t * ) pHttpParser->data )->pField ); + LogDebugWithArgs( "Reached end of header parsing: Header not found in response: " + "RequestedHeader=%.*s", + ( ( findHeaderContext_t * ) pHttpParser->data )->fieldLen, + ( ( findHeaderContext_t * ) pHttpParser->data )->pField ); /* No further parsing is required; thus, indicate the parser to stop parsing. */ return HTTP_PARSER_STOP_PARSING; @@ -1188,8 +1188,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, ( const char * ) pBuffer, bufferLen ); - IotLogDebugWithArgs( "Parsed response for header search: NumBytesParsed=%d", - numOfBytesParsed ); + LogDebugWithArgs( "Parsed response for header search: NumBytesParsed=%d", + numOfBytesParsed ); if( context.fieldFound == 0u ) { @@ -1197,8 +1197,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, assert( context.valueFound == 0u ); /* Header is not present in buffer. */ - IotLogWarnWithArgs( "Header not found in response buffer: " - "RequestedHeader=%.*s", fieldLen, pField ); + LogWarnWithArgs( "Header not found in response buffer: " + "RequestedHeader=%.*s", fieldLen, pField ); returnStatus = HTTP_HEADER_NOT_FOUND; } @@ -1206,11 +1206,11 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, { /* The response buffer is invalid as only the header field was found * in the ": \r\n" format of an HTTP header. */ - IotLogErrorWithArgs( "Unable to find header value in response: " - "Response data is invalid: " - "RequestedHeader=%.*s, ParserError=%s", - fieldLen, pField, - http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + LogErrorWithArgs( "Unable to find header value in response: " + "Response data is invalid: " + "RequestedHeader=%.*s, ParserError=%s", + fieldLen, pField, + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; } else @@ -1220,10 +1220,10 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /* Header is found. */ assert( ( context.fieldFound == 1u ) && ( context.valueFound == 1u ) ); - IotLogDebugWithArgs( "Found requested header in response: " - "HeaderName=%.*s, HeaderValue=%.*s", - fieldLen, pField, - *pValueLen, *pValueLoc ); + LogDebugWithArgs( "Found requested header in response: " + "HeaderName=%.*s, HeaderValue=%.*s", + fieldLen, pField, + *pValueLen, *pValueLoc ); } /* If the header field-value pair is found in response, then the return value of "on_header_value" @@ -1231,8 +1231,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, if( ( returnStatus == HTTP_SUCCESS ) && ( ( parser.http_errno != HPE_CB_header_value ) ) ) { - IotLogErrorWithArgs( "Header found in response but http-parser returned error: ParserError=%s", - http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + LogErrorWithArgs( "Header found in response but http-parser returned error: ParserError=%s", + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); returnStatus = HTTP_INTERNAL_ERROR; } @@ -1241,8 +1241,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, else if( ( returnStatus == HTTP_HEADER_NOT_FOUND ) && ( ( parser.http_errno != HPE_OK ) ) ) { - IotLogErrorWithArgs( "Header not found in response: http-parser returned error: ParserError=%s", - http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + LogErrorWithArgs( "Header not found in response: http-parser returned error: ParserError=%s", + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; } else @@ -1265,39 +1265,39 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, if( pResponse == NULL ) { - IotLogError( "Parameter check failed: pResponse is NULL." ); + LogError( "Parameter check failed: pResponse is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pResponse->pBuffer == NULL ) { - IotLogError( "Parameter check failed: pResponse->pBuffer is NULL." ); + LogError( "Parameter check failed: pResponse->pBuffer is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pResponse->bufferLen == 0u ) { - IotLogError( "Parameter check failed: pResponse->bufferLen is 0: " - "Buffer len should be > 0." ); + LogError( "Parameter check failed: pResponse->bufferLen is 0: " + "Buffer len should be > 0." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pHeaderName == NULL ) { - IotLogError( "Parameter check failed: Input header name is NULL." ); + LogError( "Parameter check failed: Input header name is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( headerNameLen == 0u ) { - IotLogError( "Parameter check failed: Input header name length is 0: " - "headerNameLen should be > 0." ); + LogError( "Parameter check failed: Input header name length is 0: " + "headerNameLen should be > 0." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pHeaderValueLoc == NULL ) { - IotLogError( "Parameter check failed: Output parameter for header value location is NULL." ); + LogError( "Parameter check failed: Output parameter for header value location is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pHeaderValueLen == NULL ) { - IotLogError( "Parameter check failed: Output parameter for header value length is NULL." ); + LogError( "Parameter check failed: Output parameter for header value length is NULL." ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -1379,9 +1379,8 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) break; default: - IotLogWarnWithArgs( "Invalid status code received for string conversion: " - "StatusCode=%d", status ); - break; + LogWarnWithArgs( "Invalid status code received for string conversion: " + "StatusCode=%d", status ); } return str; diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index ef6b800ede..ae88347d6a 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -17,17 +17,17 @@ #endif #endif #define LIBRARY_LOG_NAME ( "HTTP" ) - #include "iot_logging_setup.h" + #include "logging_setup.h" #else /* ifdef USE_AWS_IOT_CSDK_LOGGING */ /* Otherwise please define logging macros in config.h. */ - #define IotLogError( message ) - #define IotLogErrorWithArgs( format, ... ) - #define IotLogWarn( message ) - #define IotLogWarnWithArgs( format, ... ) - #define IotLogInfo( message ) - #define IotLogInfoWithArgs( format, ... ) - #define IotLogDebug( message ) - #define IotLogDebugWithArgs( format, ... ) + #define LogError( message ) + #define LogErrorWithArgs( format, ... ) + #define LogWarn( message ) + #define LogWarnWithArgs( format, ... ) + #define LogInfo( message ) + #define LogInfoWithArgs( format, ... ) + #define LogDebug( message ) + #define LogDebugWithArgs( format, ... ) #endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ /** diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 1454779110..83fdd9e8eb 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -48,7 +48,7 @@ FUNCTIONS = addHeader \ include Mock4thewin.mk # additional dependencies for all tests -$(TESTS): $(CSDK_ROOT)/platform/posix/iot_clock_posix.o $(CSDK_ROOT)/platform/posix/iot_logging.o +$(TESTS): $(CSDK_ROOT)/platform/posix/clock_posix.o $(CSDK_ROOT)/platform/posix/logging.o # additional dependency for a specific test HTTPClient_AddHeader.c: addHeader.c diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 8d1e58e813..a6e8072326 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -88,15 +88,15 @@ static int32_t sendPacket( MQTTContext_t * pContext, bytesRemaining -= ( size_t ) bytesSent; totalBytesSent += bytesSent; pIndex += bytesSent; - IotLogDebugWithArgs( "Bytes sent=%d, bytes remaining=%ul," - "total bytes sent=%d.", - bytesSent, - bytesRemaining, - totalBytesSent ); + LogDebugWithArgs( "Bytes sent=%d, bytes remaining=%ul," + "total bytes sent=%d.", + bytesSent, + bytesRemaining, + totalBytesSent ); } else { - IotLogError( "Transport send failed." ); + LogError( "Transport send failed." ); totalBytesSent = -1; break; } @@ -106,8 +106,8 @@ static int32_t sendPacket( MQTTContext_t * pContext, if( totalBytesSent > -1 ) { pContext->lastPacketTime = sendTime; - IotLogDebugWithArgs( "Successfully sent packet at time %u.", - sendTime ); + LogDebugWithArgs( "Successfully sent packet at time %u.", + sendTime ); } return totalBytesSent; @@ -125,20 +125,20 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co /* Validate all the parameters. */ if( ( pContext == NULL ) || ( pSubscriptionList == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pSubscriptionList=%p.", - pContext, - pSubscriptionList ); + LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pSubscriptionList=%p.", + pContext, + pSubscriptionList ); status = MQTTBadParameter; } else if( subscriptionCount == 0UL ) { - IotLogError( "Subscription count is 0." ); + LogError( "Subscription count is 0." ); status = MQTTBadParameter; } else if( packetId == 0U ) { - IotLogError( "Packet Id for subscription packet is 0." ); + LogError( "Packet Id for subscription packet is 0." ); status = MQTTBadParameter; } else @@ -181,10 +181,10 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( ( pContext == NULL ) || ( pConnectInfo == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pConnectInfo=%p.", - pContext, - pConnectInfo ); + LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pConnectInfo=%p.", + pContext, + pConnectInfo ); status = MQTTBadParameter; } @@ -195,9 +195,9 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, pWillInfo, &remainingLength, &packetSize ); - IotLogDebugWithArgs( "CONNECT packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebugWithArgs( "CONNECT packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); } if( status == MQTTSuccess ) @@ -216,13 +216,13 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - IotLogError( "Transport send failed for CONNECT packet." ); + LogError( "Transport send failed for CONNECT packet." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of CONNECT packet.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of CONNECT packet.", + bytesSent ); } } @@ -239,22 +239,22 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, /* Check if received packet type is CONNACK and deserialize it. */ if( incomingPacket.type == MQTT_PACKET_TYPE_CONNACK ) { - IotLogInfo( "Received MQTT CONNACK from broker." ); + LogInfo( "Received MQTT CONNACK from broker." ); /* Deserialize CONNACK. */ status = MQTT_DeserializeAck( &incomingPacket, NULL, pSessionPresent ); } else { - IotLogErrorWithArgs( "Unexpected packet type %u received from network.", - incomingPacket.type ); + LogErrorWithArgs( "Unexpected packet type %u received from network.", + incomingPacket.type ); status = MQTTBadResponse; } } if( status == MQTTSuccess ) { - IotLogInfo( "MQTT connection established with the broker." ); + LogInfo( "MQTT connection established with the broker." ); pContext->connectStatus = MQTTConnected; } @@ -284,9 +284,9 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, subscriptionCount, &remainingLength, &packetSize ); - IotLogDebugWithArgs( "SUBSCRIBE packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebugWithArgs( "SUBSCRIBE packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); } if( status == MQTTSuccess ) @@ -308,13 +308,13 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - IotLogError( "Transport send failed for SUBSCRIBE packet." ); + LogError( "Transport send failed for SUBSCRIBE packet." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of SUBSCRIBE packet.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of SUBSCRIBE packet.", + bytesSent ); } } @@ -334,16 +334,16 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, /* Validate arguments. */ if( ( pContext == NULL ) || ( pPublishInfo == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pPublishInfo=%p.", - pContext, - pPublishInfo ); + LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pPublishInfo=%p.", + pContext, + pPublishInfo ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) { - IotLogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", - pPublishInfo->qos ); + LogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ); status = MQTTBadParameter; } else @@ -357,9 +357,9 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, status = MQTT_GetPublishPacketSize( pPublishInfo, &remainingLength, &packetSize ); - IotLogDebugWithArgs( "PUBLISH packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebugWithArgs( "PUBLISH packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); } if( status == MQTTSuccess ) @@ -369,8 +369,8 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, remainingLength, &( pContext->networkBuffer ), &headerSize ); - IotLogDebugWithArgs( "Serialized PUBLISH header size is %lu.", - headerSize ); + LogDebugWithArgs( "Serialized PUBLISH header size is %lu.", + headerSize ); } if( status == MQTTSuccess ) @@ -382,13 +382,13 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - IotLogError( "Transport send failed for PUBLISH header." ); + LogError( "Transport send failed for PUBLISH header." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of PUBLISH header.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of PUBLISH header.", + bytesSent ); /* Send Payload. */ bytesSent = sendPacket( pContext, @@ -397,13 +397,13 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - IotLogError( "Transport send failed for PUBLISH payload." ); + LogError( "Transport send failed for PUBLISH payload." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of PUBLISH payload.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of PUBLISH payload.", + bytesSent ); } } } @@ -423,7 +423,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) if( pContext == NULL ) { - IotLogError( "pContext is NULL." ); + LogError( "pContext is NULL." ); status = MQTTBadParameter; } @@ -442,13 +442,13 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) if( bytesSent < 0 ) { - IotLogError( "Transport send failed for PINGREQ packet." ); + LogError( "Transport send failed for PINGREQ packet." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of PINGREQ packet.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of PINGREQ packet.", + bytesSent ); } } @@ -478,9 +478,9 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, subscriptionCount, &remainingLength, &packetSize ); - IotLogDebugWithArgs( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebugWithArgs( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ); } if( status == MQTTSuccess ) @@ -502,13 +502,13 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - IotLogError( "Transport send failed for UNSUBSCRIBE packet." ); + LogError( "Transport send failed for UNSUBSCRIBE packet." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of UNSUBSCRIBE packet.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of UNSUBSCRIBE packet.", + bytesSent ); } } @@ -526,7 +526,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) /* Validate arguments. */ if( pContext == NULL ) { - IotLogError( "pContext cannot be NULL." ); + LogError( "pContext cannot be NULL." ); status = MQTTBadParameter; } @@ -534,8 +534,8 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) { /* Get MQTT DISCONNECT packet size. */ status = MQTT_GetDisconnectPacketSize( &packetSize ); - IotLogDebugWithArgs( "MQTT DISCONNECT packet size is %lu.", - packetSize ); + LogDebugWithArgs( "MQTT DISCONNECT packet size is %lu.", + packetSize ); } if( status == MQTTSuccess ) @@ -552,19 +552,19 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) if( bytesSent < 0 ) { - IotLogError( "Transport send failed for DISCONNECT packet." ); + LogError( "Transport send failed for DISCONNECT packet." ); status = MQTTSendFailed; } else { - IotLogDebugWithArgs( "Sent %d bytes of DISCONNECT packet.", - bytesSent ); + LogDebugWithArgs( "Sent %d bytes of DISCONNECT packet.", + bytesSent ); } } if( status == MQTTSuccess ) { - IotLogInfo( "Disconnected from the broker." ); + LogInfo( "Disconnected from the broker." ); pContext->connectStatus = MQTTNotConnected; } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 0e03ed145b..ffac5765c2 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -272,9 +272,9 @@ static size_t remainingLengthEncodedSize( size_t length ) encodedSize = 4U; } - IotLogDebugWithArgs( "Encoded size for length =%ul is %ul.", - length, - encodedSize ); + LogDebugWithArgs( "Encoded size for length =%ul is %ul.", + length, + encodedSize ); return encodedSize; } @@ -405,12 +405,12 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, /* Ensure that the given payload fits within the calculated limit. */ if( pPublishInfo->payloadLength > payloadLimit ) { - IotLogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " - "%lu so as not to exceed the maximum " - "remaining length of MQTT 3.1.1 packet( %lu ).", - pPublishInfo->payloadLength, - payloadLimit, - MQTT_MAX_REMAINING_LENGTH ); + LogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + pPublishInfo->payloadLength, + payloadLimit, + MQTT_MAX_REMAINING_LENGTH ); status = false; } else @@ -426,12 +426,12 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, /* Check that the given payload fits within the size allowed by MQTT spec. */ if( pPublishInfo->payloadLength > payloadLimit ) { - IotLogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " - "%lu so as not to exceed the maximum " - "remaining length of MQTT 3.1.1 packet( %lu ).", - pPublishInfo->payloadLength, - payloadLimit, - MQTT_MAX_REMAINING_LENGTH ); + LogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + pPublishInfo->payloadLength, + payloadLimit, + MQTT_MAX_REMAINING_LENGTH ); status = false; } else @@ -445,9 +445,9 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, } } - IotLogDebugWithArgs( "PUBLISH packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ); + LogDebugWithArgs( "PUBLISH packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ); return status; } @@ -477,12 +477,12 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, if( pPublishInfo->qos == MQTTQoS1 ) { - IotLogDebug( "Adding QoS as QoS1 in PUBLISH flags." ); + LogDebug( "Adding QoS as QoS1 in PUBLISH flags." ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); } else if( pPublishInfo->qos == MQTTQoS2 ) { - IotLogDebug( "Adding QoS as QoS2 in PUBLISH flags." ); + LogDebug( "Adding QoS as QoS2 in PUBLISH flags." ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); } else @@ -492,13 +492,13 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, if( pPublishInfo->retain ) { - IotLogDebug( "Adding retain bit in PUBLISH flags." ); + LogDebug( "Adding retain bit in PUBLISH flags." ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); } if( pPublishInfo->dup ) { - IotLogDebug( "Adding dup bit in PUBLISH flags." ); + LogDebug( "Adding dup bit in PUBLISH flags." ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ); } @@ -516,7 +516,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, /* A packet identifier is required for QoS 1 and 2 messages. */ if( pPublishInfo->qos > MQTTQoS0 ) { - IotLogDebug( "Adding packet Id in PUBLISH packet." ); + LogDebug( "Adding packet Id in PUBLISH packet." ); /* Place the packet identifier into the PUBLISH packet. */ *pIndex = UINT16_HIGH_BYTE( packetIdentifier ); *( pIndex + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); @@ -530,8 +530,8 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, if( ( pPublishInfo->payloadLength > 0U ) && ( serializePayload ) ) { - IotLogDebugWithArgs( "Copying PUBLISH payload of length =%lu to buffer", - pPublishInfo->payloadLength ); + LogDebugWithArgs( "Copying PUBLISH payload of length =%lu to buffer", + pPublishInfo->payloadLength ); /* This memcpy intentionally copies bytes from a void * buffer into * a uint8_t * buffer. */ @@ -648,8 +648,8 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, /* Check that the "Remaining length" is greater than the minimum. */ if( remainingLength < qos0Minimum ) { - IotLogDebugWithArgs( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum ); + LogDebugWithArgs( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum ); status = MQTTBadResponse; } @@ -661,8 +661,8 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, * packet identifier. */ if( remainingLength < ( qos0Minimum + 2U ) ) { - IotLogDebugWithArgs( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum + 2U ); + LogDebugWithArgs( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum + 2U ); status = MQTTBadResponse; } @@ -686,7 +686,7 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) ) { - IotLogDebug( "Bad QoS: 3." ); + LogDebug( "Bad QoS: 3." ); status = MQTTBadResponse; } @@ -708,21 +708,21 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, if( status == MQTTSuccess ) { - IotLogDebugWithArgs( "QoS is %d.", pPublishInfo->qos ); + LogDebugWithArgs( "QoS is %d.", pPublishInfo->qos ); /* Parse the Retain bit. */ pPublishInfo->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - IotLogDebugWithArgs( "Retain bit is %d.", pPublishInfo->retain ); + LogDebugWithArgs( "Retain bit is %d.", pPublishInfo->retain ); /* Parse the DUP bit. */ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) ) { - IotLogDebug( "DUP is 1." ); + LogDebug( "DUP is 1." ); } else { - IotLogDebug( "DUP is 0." ); + LogDebug( "DUP is 0." ); } } @@ -748,7 +748,7 @@ static void logConnackResponse( uint8_t responseCode ) "Connection refused: bad user name or password.", /* 4 */ "Connection refused: not authorized." /* 5 */ }; - IotLogErrorWithArgs( "%s", pConnackResponses[ responseCode ] ); + LogErrorWithArgs( "%s", pConnackResponses[ responseCode ] ); #endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ } @@ -767,8 +767,8 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, * "Remaining length" of 2. */ if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) { - IotLogErrorWithArgs( "CONNACK does not have remaining length of %d.", - MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + LogErrorWithArgs( "CONNACK does not have remaining length of %d.", + MQTT_PACKET_CONNACK_REMAINING_LENGTH ); status = MQTTBadResponse; } @@ -777,7 +777,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, * in CONNACK must be 0. */ else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) { - IotLogError( "Reserved bits in CONNACK incorrect." ); + LogError( "Reserved bits in CONNACK incorrect." ); status = MQTTBadResponse; } @@ -788,7 +788,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) { - IotLogWarn( "CONNACK session present bit set." ); + LogWarn( "CONNACK session present bit set." ); *pSessionPresent = true; /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the @@ -800,7 +800,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, } else { - IotLogInfo( "CONNACK session present bit not set." ); + LogInfo( "CONNACK session present bit not set." ); } } @@ -809,8 +809,8 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ if( pRemainingData[ 1 ] > 5U ) { - IotLogErrorWithArgs( "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ); + LogErrorWithArgs( "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ); status = MQTTBadResponse; } @@ -871,10 +871,10 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * * set the output parameter.*/ if( packetSize > MQTT_MAX_REMAINING_LENGTH ) { - IotLogErrorWithArgs( "Subscription packet length of %lu exceeds" - "the MQTT 3.1.1 maximum packet length of %lu.", - packetSize, - MQTT_MAX_REMAINING_LENGTH ); + LogErrorWithArgs( "Subscription packet length of %lu exceeds" + "the MQTT 3.1.1 maximum packet length of %lu.", + packetSize, + MQTT_MAX_REMAINING_LENGTH ); status = MQTTBadParameter; } else @@ -890,9 +890,9 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * *pPacketSize = packetSize; } - IotLogDebugWithArgs( "Subscription packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ); + LogDebugWithArgs( "Subscription packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ); return status; } @@ -921,13 +921,13 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, case 0x01: case 0x02: - IotLogDebugWithArgs( "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); + LogDebugWithArgs( "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); break; case 0x80: - IotLogDebugWithArgs( "Topic filter %lu refused.", ( unsigned long ) i ); + LogDebugWithArgs( "Topic filter %lu refused.", ( unsigned long ) i ); /* Application should remove subscription from the list */ status = MQTTServerRefused; @@ -935,7 +935,7 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, break; default: - IotLogDebugWithArgs( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + LogDebugWithArgs( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); status = MQTTBadResponse; @@ -969,7 +969,7 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, * packet identifier and at least 1 return code. */ if( remainingLength < 3U ) { - IotLogDebug( "SUBACK cannot have a remaining length less than 3." ); + LogDebug( "SUBACK cannot have a remaining length less than 3." ); status = MQTTBadResponse; } else @@ -977,7 +977,7 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ *pPacketIdentifier = UINT16_DECODE( pVariableHeader ); - IotLogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); + LogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); status = readSubackStatus( remainingLength - sizeof( uint16_t ), pVariableHeader + sizeof( uint16_t ) ); @@ -1000,20 +1000,20 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_ /* Validate all the parameters. */ if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " - "pSubscriptionList=%p.", - pBuffer, - pSubscriptionList ); + LogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " + "pSubscriptionList=%p.", + pBuffer, + pSubscriptionList ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) { - IotLogError( "Subscription count is 0." ); + LogError( "Subscription count is 0." ); status = MQTTBadParameter; } else if( packetId == 0U ) { - IotLogError( "Packet Id for subscription packet is 0." ); + LogError( "Packet Id for subscription packet is 0." ); status = MQTTBadParameter; } @@ -1025,10 +1025,10 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_ else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength ) > pBuffer->size ) { - IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized packet of size of %lu.", - pBuffer->size, - packetSize ); + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized packet of size of %lu.", + pBuffer->size, + packetSize ); status = MQTTNoMemory; } else @@ -1083,10 +1083,10 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming { /* Parse the topic. */ pPublishInfo->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); - IotLogDebugWithArgs( "Topic name length %hu: %.*s", - pPublishInfo->topicNameLength, - pPublishInfo->topicNameLength, - pPublishInfo->pTopicName ); + LogDebugWithArgs( "Topic name length %hu: %.*s", + pPublishInfo->topicNameLength, + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ); /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet * identifier starts immediately after the topic name. */ @@ -1096,7 +1096,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming { *pPacketId = UINT16_DECODE( pPacketIdentifierHigh ); - IotLogDebugWithArgs( "Packet identifier %hu.", *pPacketId ); + LogDebugWithArgs( "Packet identifier %hu.", *pPacketId ); /* Packet identifier cannot be 0. */ if( *pPacketId == 0U ) @@ -1121,7 +1121,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming pPublishInfo->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } - IotLogDebugWithArgs( "Payload length %hu.", pPublishInfo->payloadLength ); + LogDebugWithArgs( "Payload length %hu.", pPublishInfo->payloadLength ); } return status; @@ -1140,8 +1140,8 @@ static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, /* Check that the "Remaining length" of the received ACK is 2. */ if( pAck->remainingLength != MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ) { - IotLogErrorWithArgs( "ACK does not have remaining length of %d.", - MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ); + LogErrorWithArgs( "ACK does not have remaining length of %d.", + MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ); status = MQTTBadResponse; } @@ -1150,7 +1150,7 @@ static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, /* Extract the packet identifier (third and fourth bytes) from ACK. */ *pPacketIdentifier = UINT16_DECODE( pAck->pRemainingData ); - IotLogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); + LogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); /* Packet identifier cannot be 0. */ if( *pPacketIdentifier == 0U ) @@ -1173,8 +1173,8 @@ static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * const pPingres /* Check the "Remaining length" (second byte) of the received PINGRESP is 0. */ if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { - IotLogErrorWithArgs( "PINGRESP does not have remaining length of %d.", - MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + LogErrorWithArgs( "PINGRESP does not have remaining length of %d.", + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); status = MQTTBadResponse; } @@ -1287,8 +1287,8 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); } - IotLogDebugWithArgs( "Length of serialized CONNECT packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + LogDebugWithArgs( "Length of serialized CONNECT packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); /* Ensure that the difference between the end and beginning of the buffer * is less than the buffer size. */ @@ -1312,11 +1312,11 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pConnectInfo, - pRemainingLength, - pPacketSize ); + LogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pConnectInfo, + pRemainingLength, + pPacketSize ); status = MQTTBadParameter; } @@ -1362,9 +1362,9 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect *pPacketSize = connectPacketSize; } - IotLogDebugWithArgs( "CONNECT packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ); + LogDebugWithArgs( "CONNECT packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ); } return status; @@ -1385,19 +1385,19 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo /* Validate arguments. */ if( ( pConnectInfo == NULL ) || ( pBuffer == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " - "pBuffer=%p.", - pConnectInfo, - pBuffer ); + LogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " + "pBuffer=%p.", + pConnectInfo, + pBuffer ); status = MQTTBadParameter; } /* Check that the full packet size fits within the given buffer. */ else if( connectPacketSize > pBuffer->size ) { - IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized CONNECT packet of size of %lu.", - pBuffer->size, - connectPacketSize ); + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized CONNECT packet of size of %lu.", + pBuffer->size, + connectPacketSize ); status = MQTTNoMemory; } else @@ -1424,16 +1424,16 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSub if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pSubscriptionList, - pRemainingLength, - pPacketSize ); + LogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pSubscriptionList, + pRemainingLength, + pPacketSize ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) { - IotLogError( " subscriptionCount is 0." ); + LogError( " subscriptionCount is 0." ); status = MQTTBadParameter; } else @@ -1447,9 +1447,9 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSub if( status == MQTTBadParameter ) { - IotLogErrorWithArgs( "SUBSCRIBE packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + LogErrorWithArgs( "SUBSCRIBE packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); } } @@ -1503,8 +1503,8 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri pIndex++; } - IotLogDebugWithArgs( "Length of serialized SUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + LogDebugWithArgs( "Length of serialized SUBSCRIBE packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); } return status; @@ -1523,16 +1523,16 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pSubscriptionList, - pRemainingLength, - pPacketSize ); + LogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pSubscriptionList, + pRemainingLength, + pPacketSize ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) { - IotLogError( "Subscription count is 0." ); + LogError( "Subscription count is 0." ); status = MQTTBadParameter; } else @@ -1546,9 +1546,9 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS if( status == MQTTBadParameter ) { - IotLogErrorWithArgs( "UNSUBSCRIBE packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + LogErrorWithArgs( "UNSUBSCRIBE packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); } } @@ -1599,8 +1599,8 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc pSubscriptionList[ i ].topicFilterLength ); } - IotLogDebugWithArgs( "Length of serialized UNSUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + LogDebugWithArgs( "Length of serialized UNSUBSCRIBE packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); } return status; @@ -1616,19 +1616,19 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pPublishInfo=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pPublishInfo, - pRemainingLength, - pPacketSize ); + LogErrorWithArgs( "Argument cannot be NULL: pPublishInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pPublishInfo, + pRemainingLength, + pPacketSize ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { - IotLogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " - "topicNameLength=%u.", - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + LogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); status = MQTTBadParameter; } else @@ -1637,9 +1637,9 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish * what is allowed in the MQTT standard, return an error. */ if( calculatePublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) { - IotLogErrorWithArgs( "PUBLISH packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + LogErrorWithArgs( "PUBLISH packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); status = MQTTBadParameter; } } @@ -1659,24 +1659,24 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " - "pPublishInfo=%p.", - pBuffer, - pPublishInfo ); + LogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " + "pPublishInfo=%p.", + pBuffer, + pPublishInfo ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { - IotLogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " - "topicNameLength=%u.", - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + LogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) { - IotLogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", - pPublishInfo->qos ); + LogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ); status = MQTTBadParameter; } @@ -1687,10 +1687,10 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength ) > pBuffer->size ) { - IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PUBLISH packet of size of %lu.", - pBuffer->size, - packetSize ); + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH packet of size of %lu.", + pBuffer->size, + packetSize ); status = MQTTNoMemory; } else @@ -1720,25 +1720,25 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pHeaderSize == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " - "pPublishInfo=%p, pHeaderSize=%p.", - pBuffer, - pPublishInfo, - pHeaderSize ); + LogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " + "pPublishInfo=%p, pHeaderSize=%p.", + pBuffer, + pPublishInfo, + pHeaderSize ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { - IotLogErrorWithArgs( "Invalid topic name for publish: pTopicName=%p, " - "topicNameLength=%u.", - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + LogErrorWithArgs( "Invalid topic name for publish: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) { - IotLogErrorWithArgs( "Packet Id is 0 for publish with QoS=%u.", - pPublishInfo->qos ); + LogErrorWithArgs( "Packet Id is 0 for publish with QoS=%u.", + pPublishInfo->qos ); status = MQTTBadParameter; } @@ -1749,10 +1749,10 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength - pPublishInfo->payloadLength ) > pBuffer->size ) { - IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PUBLISH header packet of size of %lu.", - pBuffer->size, - packetSize ); + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH header packet of size of %lu.", + pBuffer->size, + packetSize ); status = MQTTNoMemory; } else @@ -1781,13 +1781,13 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, if( pBuffer == NULL ) { - IotLogError( "Provided buffer is NULL." ); + LogError( "Provided buffer is NULL." ); status = MQTTBadParameter; } /* The buffer must be able to fit 4 bytes for the packet. */ else if( pBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) { - IotLogError( "Insufficient memory for packet." ); + LogError( "Insufficient memory for packet." ); status = MQTTNoMemory; } else @@ -1806,8 +1806,8 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, break; default: - IotLogErrorWithArgs( "Packet type is not a publish ACK: Packet type=%02x", - packetType ); + LogErrorWithArgs( "Packet type is not a publish ACK: Packet type=%02x", + packetType ); status = MQTTBadParameter; break; } @@ -1836,25 +1836,25 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) /* Validate arguments. */ if( pBuffer == NULL ) { - IotLogError( "pBuffer cannot be NULL." ); + LogError( "pBuffer cannot be NULL." ); status = MQTTBadParameter; } if( status == MQTTSuccess ) { status = MQTT_GetDisconnectPacketSize( &disconnectPacketSize ); - IotLogDebugWithArgs( "MQTT DISCONNECT packet size is %ul.", - disconnectPacketSize ); + LogDebugWithArgs( "MQTT DISCONNECT packet size is %ul.", + disconnectPacketSize ); } if( status == MQTTSuccess ) { if( pBuffer->size < disconnectPacketSize ) { - IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized DISCONNECT packet of size of %lu.", - pBuffer->size, - disconnectPacketSize ); + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized DISCONNECT packet of size of %lu.", + pBuffer->size, + disconnectPacketSize ); status = MQTTNoMemory; } } @@ -1876,15 +1876,15 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) if( pBuffer == NULL ) { - IotLogError( "pBuffer is NULL." ); + LogError( "pBuffer is NULL." ); status = MQTTBadParameter; } else if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) { - IotLogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PINGREQ packet of size of %lu.", - pBuffer->size, - MQTT_PACKET_PINGREQ_SIZE ); + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PINGREQ packet of size of %lu.", + pBuffer->size, + MQTT_PACKET_PINGREQ_SIZE ); status = MQTTNoMemory; } else @@ -1916,17 +1916,17 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " - "pPacketId=%p, pPublishInfo=%p", - pIncomingPacket, - pPacketId, - pPublishInfo ); + LogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " + "pPacketId=%p, pPublishInfo=%p", + pIncomingPacket, + pPacketId, + pPublishInfo ); status = MQTTBadParameter; } else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) { - IotLogErrorWithArgs( "Packet is not publish. Packet type: %hu.", - pIncomingPacket->type ); + LogErrorWithArgs( "Packet is not publish. Packet type: %hu.", + pIncomingPacket->type ); status = MQTTBadParameter; } else @@ -1947,16 +1947,16 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pSessionPresent == NULL ) ) { - IotLogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " - "pPacketId=%p, pSessionPresent=%p", - pIncomingPacket, - pPacketId, - pSessionPresent ); + LogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " + "pPacketId=%p, pSessionPresent=%p", + pIncomingPacket, + pPacketId, + pSessionPresent ); status = MQTTBadParameter; } else if( pIncomingPacket->pRemainingData == NULL ) { - IotLogError( "Remaining data of incoming packet is NULL." ); + LogError( "Remaining data of incoming packet is NULL." ); status = MQTTBadParameter; } else @@ -1986,7 +1986,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket /* Any other packet type is invalid. */ default: - IotLogErrorWithArgs( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ); + LogErrorWithArgs( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ); status = MQTTBadResponse; break; } @@ -2019,8 +2019,8 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu } else { - IotLogErrorWithArgs( "Incoming packet invalid: Packet type=%u", - pIncomingPacket->type ); + LogErrorWithArgs( "Incoming packet invalid: Packet type=%u", + pIncomingPacket->type ); status = MQTTBadResponse; } } diff --git a/libraries/standard/mqtt/src/private/mqtt_internal.h b/libraries/standard/mqtt/src/private/mqtt_internal.h index c557969f2a..d024f5d2b0 100644 --- a/libraries/standard/mqtt/src/private/mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/mqtt_internal.h @@ -15,17 +15,17 @@ #endif #endif #define LIBRARY_LOG_NAME ( "MQTT" ) - #include "iot_logging_setup.h" + #include "logging_setup.h" #else /* ifdef USE_AWS_IOT_CSDK_LOGGING */ /* Otherwise please define logging macros in config.h. */ - #define IotLogError( message ) - #define IotLogErrorWithArgs( format, ... ) - #define IotLogWarn( message ) - #define IotLogWarnWithArgs( format, ... ) - #define IotLogInfo( message ) - #define IotLogInfoWithArgs( format, ... ) - #define IotLogDebug( message ) - #define IotLogDebugWithArgs( format, ... ) + #define LogError( message ) + #define LogErrorWithArgs( format, ... ) + #define LogWarn( message ) + #define LogWarnWithArgs( format, ... ) + #define LogInfo( message ) + #define LogInfoWithArgs( format, ... ) + #define LogDebug( message ) + #define LogDebugWithArgs( format, ... ) #endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ #endif /* ifndef MQTT_INTERNAL_H_ */ diff --git a/libraries/standard/utilities/include/iot_logging_levels.h b/libraries/standard/utilities/include/logging_levels.h similarity index 100% rename from libraries/standard/utilities/include/iot_logging_levels.h rename to libraries/standard/utilities/include/logging_levels.h diff --git a/libraries/standard/utilities/include/iot_logging_setup.h b/libraries/standard/utilities/include/logging_setup.h similarity index 55% rename from libraries/standard/utilities/include/iot_logging_setup.h rename to libraries/standard/utilities/include/logging_setup.h index 20006663ed..9bd3e3103e 100644 --- a/libraries/standard/utilities/include/iot_logging_setup.h +++ b/libraries/standard/utilities/include/logging_setup.h @@ -22,7 +22,7 @@ /** * @file iot_logging_setup.h - * @brief Defines the common logging framework that calls #IotLog interface. + * @brief Defines the common logging framework that calls #Log interface. */ #ifndef IOT_LOGGING_SETUP_H_ @@ -32,15 +32,15 @@ #include "config.h" /* Include header for logging level macros. */ -#include "iot_logging_levels.h" +#include "logging_levels.h" /** - * @functionpage{IotLog,logging,log} + * @functionpage{Log,logging,log} */ /** - * @def IotLog( messageLevel, pFormat, ... ) + * @def Log( messageLevel, pFormat, ... ) * @brief The common logging interface for all libraries. * * This acts as a hook for supplying a logging implementation stack @@ -57,82 +57,82 @@ */ /** - * @def IotLogError( message ) + * @def LogError( message ) * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_ERROR. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_ERROR, "%s" , message ) + * Log( IOT_LOG_ERROR, "%s" , message ) * @endcode */ /** - * @def IotLogErrorWithArgs( pFormat, ... ) + * @def LogErrorWithArgs( pFormat, ... ) * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_ERROR. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_ERROR, pFormat, ... ) + * Log( IOT_LOG_ERROR, pFormat, ... ) * @endcode */ /** - * @def IotLogWarn( message ) + * @def LogWarn( message ) * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_WARN. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_WARN, "%s" , message ) + * Log( IOT_LOG_WARN, "%s" , message ) * @endcode */ /** - * @def IotLogWarnWithArgs( pFormat, ... ) + * @def LogWarnWithArgs( pFormat, ... ) * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_WARN. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_WARN, pFormat, ... ) + * Log( IOT_LOG_WARN, pFormat, ... ) * @endcode */ /** - * @def IotLogInfo( message ) + * @def LogInfo( message ) * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_INFO. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_INFO, "%s" , message ) + * Log( IOT_LOG_INFO, "%s" , message ) * @endcode */ /** - * @def IotLogInfoWithArgs( pFormat, ... ) + * @def LogInfoWithArgs( pFormat, ... ) * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_INFO. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_INFO, pFormat, ... ) + * Log( IOT_LOG_INFO, pFormat, ... ) * @endcode */ /** - * @def IotLogDebug( message ) + * @def LogDebug( message ) * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_DEBUG. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_DEBUG, "%s" , message ) + * Log( IOT_LOG_DEBUG, "%s" , message ) * @endcode */ /** - * @def IotLogDebugWithArgs( pFormat, ... ) + * @def LogDebugWithArgs( pFormat, ... ) * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_DEBUG. * * Equivalent to: * @code{c} - * IotLog( IOT_LOG_DEBUG, pFormat, ... ) + * Log( IOT_LOG_DEBUG, pFormat, ... ) * @endcode */ @@ -146,64 +146,64 @@ #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." #else #if LIBRARY_LOG_LEVEL != IOT_LOG_NONE - #if !defined( IotLog ) - #error "Please define the common logging interface macro, IotLog(messageLevel, pFormat, ...)." + #if !defined( Log ) + #error "Please define the common logging interface macro, Log(messageLevel, pFormat, ...)." #endif #endif #if LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG /* All log level messages will logged. */ - #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) - #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define IotLogWarn( message ) IotLog( IOT_LOG_WARN, "%s", message ) - #define IotLogWarnWithArgs( pFormat, ... ) IotLog( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) - #define IotLogInfo( message ) IotLog( IOT_LOG_INFO, "%s", message ) - #define IotLogInfoWithArgs( pFormat, ... ) IotLog( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) - #define IotLogDebug( message ) IotLog( IOT_LOG_DEBUG, "%s", message ) - #define IotLogDebugWithArgs( pFormat, ... ) IotLog( IOT_LOG_DEBUG, pFormat, __VA_ARGS__ ) + #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) Log( IOT_LOG_WARN, "%s", message ) + #define LogWarnWithArgs( pFormat, ... ) Log( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define LogInfo( message ) Log( IOT_LOG_INFO, "%s", message ) + #define LogInfoWithArgs( pFormat, ... ) Log( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) + #define LogDebug( message ) Log( IOT_LOG_DEBUG, "%s", message ) + #define LogDebugWithArgs( pFormat, ... ) Log( IOT_LOG_DEBUG, pFormat, __VA_ARGS__ ) #elif LIBRARY_LOG_LEVEL == IOT_LOG_INFO /* Only INFO, WARNING and ERROR messages will be logged. */ - #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) - #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define IotLogWarn( message ) IotLog( IOT_LOG_WARN, "%s", message ) - #define IotLogWarnWithArgs( pFormat, ... ) IotLog( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) - #define IotLogInfo( message ) IotLog( IOT_LOG_INFO, "%s", message ) - #define IotLogInfoWithArgs( pFormat, ... ) IotLog( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) - #define IotLogDebug( message ) - #define IotLogDebugWithArgs( pFormat, ... ) + #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) Log( IOT_LOG_WARN, "%s", message ) + #define LogWarnWithArgs( pFormat, ... ) Log( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define LogInfo( message ) Log( IOT_LOG_INFO, "%s", message ) + #define LogInfoWithArgs( pFormat, ... ) Log( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) + #define LogDebug( message ) + #define LogDebugWithArgs( pFormat, ... ) #elif LIBRARY_LOG_LEVEL == IOT_LOG_WARN /* Only WARNING and ERROR messages will be logged.*/ - #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) - #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define IotLogWarn( message ) IotLog( IOT_LOG_WARN, "%s", message ) - #define IotLogWarnWithArgs( pFormat, ... ) IotLog( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) - #define IotLogInfo( message ) - #define IotLogInfoWithArgs( pFormat, ... ) - #define IotLogDebug( message ) - #define IotLogDebugWithArgs( pFormat, ... ) + #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) Log( IOT_LOG_WARN, "%s", message ) + #define LogWarnWithArgs( pFormat, ... ) Log( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define LogInfo( message ) + #define LogInfoWithArgs( pFormat, ... ) + #define LogDebug( message ) + #define LogDebugWithArgs( pFormat, ... ) #elif LIBRARY_LOG_LEVEL == IOT_LOG_ERROR /* Only ERROR messages will be logged. */ - #define IotLogError( message ) IotLog( IOT_LOG_ERROR, "%s", message ) - #define IotLogErrorWithArgs( pFormat, ... ) IotLog( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define IotLogWarn( message ) - #define IotLogWarnWithArgs( pFormat, ... ) - #define IotLogInfo( message ) - #define IotLogInfoWithArgs( pFormat, ... ) - #define IotLogDebug( message ) - #define IotLogDebugWithArgs( pFormat, ... ) + #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) + #define LogWarnWithArgs( pFormat, ... ) + #define LogInfo( message ) + #define LogInfoWithArgs( pFormat, ... ) + #define LogDebug( message ) + #define LogDebugWithArgs( pFormat, ... ) #else /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ - #define IotLogError( message ) - #define IotLogErrorWithArgs( pFormat, ... ) - #define IotLogWarn( message ) - #define IotLogWarnWithArgs( pFormat, ... ) - #define IotLogInfo( message ) - #define IotLogInfoWithArgs( pFormat, ... ) - #define IotLogDebug( message ) - #define IotLogDebugWithArgs( pFormat, ... ) + #define LogError( message ) + #define LogErrorWithArgs( pFormat, ... ) + #define LogWarn( message ) + #define LogWarnWithArgs( pFormat, ... ) + #define LogInfo( message ) + #define LogInfoWithArgs( pFormat, ... ) + #define LogDebug( message ) + #define LogDebugWithArgs( pFormat, ... ) #endif /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ #endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) */ diff --git a/platform/include/iot_clock.h b/platform/include/clock.h similarity index 100% rename from platform/include/iot_clock.h rename to platform/include/clock.h diff --git a/platform/include/iot_logging.h b/platform/include/logging.h similarity index 92% rename from platform/include/iot_logging.h rename to platform/include/logging.h index b50f32e84f..7b1c283f74 100644 --- a/platform/include/iot_logging.h +++ b/platform/include/logging.h @@ -44,7 +44,7 @@ */ /** - * @functionpage{IotLog_Generic,logging,generic} + * @functionpage{Log_Generic,logging,generic} */ /** @@ -60,9 +60,9 @@ * @return No return value. On errors, it prints nothing. */ /* @[declare_logging_generic] */ -void IotLog_Generic( int32_t messageLevel, - const char * const pFormat, - ... ); +void Log_Generic( int32_t messageLevel, + const char * const pFormat, + ... ); /* @[declare_logging_generic] */ diff --git a/platform/posix/iot_clock_posix.c b/platform/posix/clock_posix.c similarity index 98% rename from platform/posix/iot_clock_posix.c rename to platform/posix/clock_posix.c index 578ce30c75..568c408e35 100644 --- a/platform/posix/iot_clock_posix.c +++ b/platform/posix/clock_posix.c @@ -38,7 +38,7 @@ #endif /* Platform clock include. */ -#include "iot_clock.h" +#include "clock.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM @@ -52,7 +52,7 @@ #endif #define LIBRARY_LOG_NAME ( "CLOCK" ) -#include "iot_logging_setup.h" +#include "logging_setup.h" /*-----------------------------------------------------------*/ diff --git a/platform/posix/iot_logging.c b/platform/posix/logging.c similarity index 97% rename from platform/posix/iot_logging.c rename to platform/posix/logging.c index fbc2634e47..d02e193a3d 100644 --- a/platform/posix/iot_logging.c +++ b/platform/posix/logging.c @@ -33,13 +33,13 @@ #include /* Platform clock include. */ -#include "iot_clock.h" +#include "clock.h" /* Include header for logging level macros. */ -#include "iot_logging_levels.h" +#include "logging_levels.h" /* Logging includes. */ -#include "iot_logging.h" +#include "logging.h" /*-----------------------------------------------------------*/ @@ -127,9 +127,9 @@ static bool _reallocLoggingBuffer( void ** pOldBuffer, /*-----------------------------------------------------------*/ -void IotLog_Generic( int32_t messageLevel, - const char * const pFormat, - ... ) +void Log_Generic( int32_t messageLevel, + const char * const pFormat, + ... ) { assert( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ); From 6877c00bf1e7db63921165ddbe438c2c23aa48c3 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Wed, 13 May 2020 12:04:25 -0700 Subject: [PATCH 497/844] MISRA fixes (#927) --- libraries/standard/mqtt/include/mqtt.h | 5 + libraries/standard/mqtt/include/mqtt_state.h | 2 +- libraries/standard/mqtt/src/mqtt.c | 16 +- .../standard/mqtt/src/mqtt_lightweight.c | 160 ++++++++---------- libraries/standard/mqtt/src/mqtt_state.c | 82 +++++++-- 5 files changed, 152 insertions(+), 113 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 09178f8229..60965561c3 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -19,6 +19,9 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifndef MQTT_H +#define MQTT_H + #include "config.h" #include "mqtt_lightweight.h" @@ -228,3 +231,5 @@ MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, uint32_t timeoutMs ); uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ); + +#endif /* ifndef MQTT_H */ diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index 0f1b619ae4..8f346af2d5 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -114,7 +114,7 @@ MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, * @param[in] searchState The state to search for. * @param[in,out] pCursor Index at which to start searching. */ -uint16_t MQTT_StateSelect( MQTTContext_t * pMqttContext, +uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, MQTTPublishState_t searchState, MQTTStateCursor_t * pCursor ); diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index a6e8072326..4744b4da9d 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -77,7 +77,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, bytesRemaining = bytesToSend; /* Loop until the entire packet is sent. */ - while( bytesRemaining > 0 ) + while( bytesRemaining > 0UL ) { bytesSent = pContext->transportInterface.send( pContext->transportInterface.networkContext, pIndex, @@ -156,7 +156,7 @@ void MQTT_Init( MQTTContext_t * const pContext, const MQTTApplicationCallbacks_t * const pCallbacks, const MQTTFixedBuffer_t * const pNetworkBuffer ) { - memset( pContext, 0x00, sizeof( MQTTContext_t ) ); + ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) ); pContext->connectStatus = MQTTNotConnected; pContext->transportInterface = *pTransportInterface; @@ -174,7 +174,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pWillInfo, bool * const pSessionPresent ) { - size_t remainingLength, packetSize; + size_t remainingLength = 0UL, packetSize = 0UL; int32_t bytesSent; MQTTStatus_t status = MQTTSuccess; MQTTPacketInfo_t incomingPacket = { .type = ( ( uint8_t ) 0 ) }; @@ -268,7 +268,7 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, size_t subscriptionCount, uint16_t packetId ) { - size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; + size_t remainingLength = 0UL, packetSize = 0UL; int32_t bytesSent = 0; /* Validate arguments. */ @@ -327,7 +327,7 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pPublishInfo, uint16_t packetId ) { - size_t remainingLength = 0, packetSize = 0, headerSize = 0; + size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; @@ -340,7 +340,7 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, pPublishInfo ); status = MQTTBadParameter; } - else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { LogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", pPublishInfo->qos ); @@ -462,7 +462,7 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, size_t subscriptionCount, uint16_t packetId ) { - size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; + size_t remainingLength = 0UL, packetSize = 0UL; int32_t bytesSent = 0; /* Validate arguments. */ @@ -587,7 +587,7 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) pContext->nextPacketId++; - if( pContext->nextPacketId == 0 ) + if( pContext->nextPacketId == 0U ) { pContext->nextPacketId = 1; } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index ffac5765c2..383ff28d51 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -222,11 +222,11 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, - size_t subscriptionCount, - uint16_t packetId, - size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); +static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ); /** * @brief Serialize an MQTT CONNECT packet in the given buffer. @@ -320,6 +320,10 @@ static uint8_t * encodeString( uint8_t * pDestination, { uint8_t * pBuffer = NULL; + /* Typecast const char * typed source buffer to const uint8_t *. + * This is to use same type buffers in memcpy. */ + const uint8_t * pSourceBuffer = ( const uint8_t * ) source; + assert( pDestination != NULL ); pBuffer = pDestination; @@ -333,7 +337,7 @@ static uint8_t * encodeString( uint8_t * pDestination, pBuffer++; /* Copy the string into pBuffer. */ - ( void ) memcpy( pBuffer, source, sourceLength ); + ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); /* Return the pointer to the end of the encoded string. */ pBuffer += sourceLength; @@ -343,37 +347,6 @@ static uint8_t * encodeString( uint8_t * pDestination, /*-----------------------------------------------------------*/ -static int32_t recvExact( MQTTTransportRecvFunc_t recvFunc, - MQTTNetworkContext_t networkContext, - void * pBuffer, - size_t bytesToRecv ) -{ - uint8_t * pIndex = pBuffer; - size_t bytesRemaining = bytesToRecv; - int32_t totalBytesRecvd = 0, bytesRecvd; - - while( bytesRemaining > 0 ) - { - bytesRecvd = recvFunc( networkContext, pIndex, bytesRemaining ); - - if( bytesRecvd > 0 ) - { - bytesRemaining -= ( size_t ) bytesRecvd; - totalBytesRecvd += ( int32_t ) bytesRecvd; - pIndex += bytesRecvd; - } - else - { - totalBytesRecvd = -1; - break; - } - } - - return totalBytesRecvd; -} - -/*-----------------------------------------------------------*/ - static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, size_t * pRemainingLength, size_t * pPacketSize ) @@ -460,7 +433,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, bool serializePayload ) { uint8_t * pIndex = NULL; - size_t minBufferSize = 0; + const uint8_t * pPayloadBuffer = NULL; /* The first byte of a PUBLISH packet contains the packet type and flags. */ uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH; @@ -533,10 +506,11 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, LogDebugWithArgs( "Copying PUBLISH payload of length =%lu to buffer", pPublishInfo->payloadLength ); - /* This memcpy intentionally copies bytes from a void * buffer into - * a uint8_t * buffer. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pIndex, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + /* Typecast const void * typed payload buffer to const uint8_t *. + * This is to use same type buffers in memcpy. */ + pPayloadBuffer = ( const uint8_t * ) pPublishInfo->pPayload; + + ( void ) memcpy( pIndex, pPayloadBuffer, pPublishInfo->payloadLength ); pIndex += pPublishInfo->payloadLength; } @@ -619,7 +593,7 @@ static bool incomingPacketValid( uint8_t packetType ) case ( MQTT_PACKET_TYPE_PUBREL & 0xF0U ): /* The second bit of a PUBREL must be set. */ - if( packetType & 0x02U ) + if( ( packetType & 0x02U ) > 0U ) { status = true; } @@ -628,6 +602,8 @@ static bool incomingPacketValid( uint8_t packetType ) /* Any other packet type is invalid. */ default: + LogWarnWithArgs( "Incoming packet invalid: Packet type=%u", + packetType ); break; } @@ -733,6 +709,9 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, static void logConnackResponse( uint8_t responseCode ) { + /* Avoid unused parameter warning when assert and logs are disabled. */ + ( void ) responseCode; + assert( responseCode <= 5 ); /* Declare the CONNACK response code strings. The fourth byte of CONNACK @@ -988,14 +967,19 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, /*-----------------------------------------------------------*/ -static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, - size_t subscriptionCount, - uint16_t packetId, - size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) +static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, + size_t subscriptionCount, + uint16_t packetId, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ) { MQTTStatus_t status = MQTTSuccess; - size_t packetSize = 0UL; + + /* The serialized packet size = First byte + * + length of encoded size of remaining length + * + remaining length. */ + size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength; /* Validate all the parameters. */ if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) ) @@ -1016,14 +1000,7 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTSubscribeInfo_ LogError( "Packet Id for subscription packet is 0." ); status = MQTTBadParameter; } - - /* Validate if the passed buffer can hold the serialized packet. - * The serialized packet size = First byte - * + length of encoded size of remaining length - * + remaining length. - */ - else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength ) > pBuffer->size ) + else if( packetSize > pBuffer->size ) { LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " "serialized packet of size of %lu.", @@ -1189,7 +1166,7 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo size_t remainingLength, const MQTTFixedBuffer_t * const pBuffer ) { - uint8_t connectFlags = 0; + uint8_t connectFlags = 0U; uint8_t * pIndex = NULL; assert( pConnectInfo != NULL ); @@ -1244,6 +1221,10 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo { UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); } + else + { + /* Empty else MISRA 15.7 */ + } if( pWillInfo->retain == true ) { @@ -1469,11 +1450,11 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri /* Validate all the parameters. */ MQTTStatus_t status = - validateSubscribeUnsubscribeParams( pSubscriptionList, - subscriptionCount, - packetId, - remainingLength, - pBuffer ); + validateSubscriptionSerializeParams( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + pBuffer ); if( status == MQTTSuccess ) { @@ -1568,11 +1549,11 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc uint8_t * pIndex = NULL; /* Validate all the parameters. */ - status = validateSubscribeUnsubscribeParams( pSubscriptionList, - subscriptionCount, - packetId, - remainingLength, - pBuffer ); + status = validateSubscriptionSerializeParams( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + pBuffer ); if( status == MQTTSuccess ) { @@ -1584,7 +1565,7 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc pIndex++; /* Encode the "Remaining length" starting from the second byte. */ - pIndex = encodeRemainingLength( pBuffer, remainingLength ); + pIndex = encodeRemainingLength( pIndex, remainingLength ); /* Place the packet identifier into the UNSUBSCRIBE packet. */ *pIndex = UINT16_HIGH_BYTE( packetId ); @@ -1655,7 +1636,12 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo const MQTTFixedBuffer_t * const pBuffer ) { MQTTStatus_t status = MQTTSuccess; - size_t packetSize = 0UL; + + /* Length of serialized packet = First byte + * + Length of encoded remaining length + * + Remaining length. */ + size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength; if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) ) { @@ -1673,19 +1659,13 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo pPublishInfo->topicNameLength ); status = MQTTBadParameter; } - else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { LogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", pPublishInfo->qos ); status = MQTTBadParameter; } - - /* Check if the serialized packet can fit in the buffer. - * Length of serialized packet = First byte + Length of encoded remaining length - * + Remaining length. - */ - else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength ) - > pBuffer->size ) + else if( packetSize > pBuffer->size ) { LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH packet of size of %lu.", @@ -1715,7 +1695,15 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli size_t * const pHeaderSize ) { MQTTStatus_t status = MQTTSuccess; - size_t packetSize = 0UL; + + /* Length of serialized packet = First byte + * + Length of encoded remaining length + * + Remaining length + * - Payload Length. + * Payload length will be subtracted after verifying pPublishInfo parameter. + */ + size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength; if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pHeaderSize == NULL ) ) @@ -1735,24 +1723,20 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli pPublishInfo->topicNameLength ); status = MQTTBadParameter; } - else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0 ) ) + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { LogErrorWithArgs( "Packet Id is 0 for publish with QoS=%u.", pPublishInfo->qos ); status = MQTTBadParameter; } - /* Check if the serialized packet can fit in the buffer. - * Length of serialized packet = First byte + Length of encoded remaining length - * + Remaining length - Payload Length. - */ - else if( ( packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength - pPublishInfo->payloadLength ) > pBuffer->size ) + + else if( ( packetSize - pPublishInfo->payloadLength ) > pBuffer->size ) { LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH header packet of size of %lu.", pBuffer->size, - packetSize ); + ( packetSize - pPublishInfo->payloadLength ) ); status = MQTTNoMemory; } else @@ -1765,7 +1749,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli false ); /* Header size is the same as calculated packet size. */ - *pHeaderSize = packetSize; + *pHeaderSize = ( packetSize - pPublishInfo->payloadLength ); } return status; diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index e8b4b525b5..cc9cfed2fc 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -22,7 +22,7 @@ #include #include "mqtt_state.h" -#define MQTT_PACKET_ID_INVALID ( uint16_t ) 0U +#define MQTT_PACKET_ID_INVALID ( uint16_t ) 0U /** * @brief Test if a transition to new state is possible, when dealing with PUBLISHes. @@ -120,16 +120,21 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, MQTTQoS_t qos ) { bool isValid = false; + switch( currentState ) { case MQTTStateNull: + /* Transitions from null occur when storing a new entry into the record. */ if( opType == MQTT_RECEIVE ) { isValid = ( newState == MQTTPubAckSend ) || ( newState == MQTTPubRecSend ); } + break; + case MQTTPublishSend: + /* Outgoing publish. All such publishes start in this state due to * the reserve operation. */ switch( qos ) @@ -137,21 +142,27 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, case MQTTQoS0: isValid = ( newState == MQTTPublishDone ); break; + case MQTTQoS1: isValid = ( newState == MQTTPubAckPending ); break; + case MQTTQoS2: isValid = ( newState == MQTTPubRecPending ); break; + default: /* No other QoS value. */ break; } + break; + default: /* For a PUBLISH, we should not start from any other state. */ break; } + return isValid; } @@ -159,44 +170,52 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, MQTTPublishState_t newState ) { bool isValid = false; + switch( currentState ) { case MQTTPubAckSend: - /* Incoming publish, QoS 1. */ + /* Incoming publish, QoS 1. */ case MQTTPubAckPending: /* Outgoing publish, QoS 1. */ isValid = ( newState == MQTTPublishDone ); break; + case MQTTPubRecSend: /* Incoming publish, QoS 2. */ isValid = ( newState == MQTTPubRelPending ); break; + case MQTTPubRelPending: /* Incoming publish, QoS 2. */ isValid = ( newState == MQTTPubCompSend ); break; + case MQTTPubCompSend: /* Incoming publish, QoS 2. */ isValid = ( newState == MQTTPublishDone ); break; + case MQTTPubRecPending: /* Outgoing publish, Qos 2. */ isValid = ( newState == MQTTPubRelSend ); break; + case MQTTPubRelSend: /* Outgoing publish, Qos 2. */ isValid = ( newState == MQTTPubCompPending ); break; + case MQTTPubCompPending: /* Outgoing publish, Qos 2. */ isValid = ( newState == MQTTPublishDone ); break; + case MQTTPublishDone: - /* Done state should transition to invalid since it will be removed from the record. */ + /* Done state should transition to invalid since it will be removed from the record. */ case MQTTPublishSend: - /* If an ack was sent/received we shouldn't have been in this state. */ + /* If an ack was sent/received we shouldn't have been in this state. */ case MQTTStateNull: - /* If an ack was sent/received the record should exist. */ + /* If an ack was sent/received the record should exist. */ default: /* Invalid. */ break; @@ -209,6 +228,7 @@ static bool isPublishOutgoing( MQTTPubAckType_t packetType, MQTTStateOperation_t opType ) { bool isOutgoing = false; + switch( packetType ) { case MQTTPuback: @@ -216,13 +236,16 @@ static bool isPublishOutgoing( MQTTPubAckType_t packetType, case MQTTPubcomp: isOutgoing = ( opType == MQTT_RECEIVE ); break; + case MQTTPubrel: isOutgoing = ( opType == MQTT_SEND ); break; + default: /* No other ack type. */ break; } + return isOutgoing; } @@ -233,7 +256,9 @@ static size_t findInRecord( const MQTTPubAckInfo_t * records, MQTTPublishState_t * pCurrentState ) { size_t index = 0; + *pCurrentState = MQTTStateNull; + if( packetId == MQTT_PACKET_ID_INVALID ) { index = recordCount; @@ -250,6 +275,7 @@ static size_t findInRecord( const MQTTPubAckInfo_t * records, } } } + return index; } @@ -267,7 +293,7 @@ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, assert( qos != MQTTQoS0 ); /* Start from end so first available index will be populated. */ - for( index = ( (int32_t ) recordCount - 1 ); index >= 0; index-- ) + for( index = ( ( int32_t ) recordCount - 1 ); index >= 0; index-- ) { if( records[ index ].packetId == MQTT_PACKET_ID_INVALID ) { @@ -283,7 +309,7 @@ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, else { /* Empty else clause. */ - } + } } if( availableIndex < recordCount ) @@ -337,7 +363,7 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, qos, MQTTPublishSend ); } - + return status; } @@ -345,21 +371,26 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ) { MQTTPublishState_t calculatedState = MQTTStateNull; + switch( qos ) { case MQTTQoS0: calculatedState = MQTTPublishDone; break; + case MQTTQoS1: - calculatedState = ( opType == MQTT_SEND )? MQTTPubAckPending : MQTTPubAckSend; + calculatedState = ( opType == MQTT_SEND ) ? MQTTPubAckPending : MQTTPubAckSend; break; + case MQTTQoS2: - calculatedState = ( opType == MQTT_SEND )? MQTTPubRecPending : MQTTPubRecSend; + calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRecPending : MQTTPubRecSend; break; + default: /* No other QoS values. */ break; } + return calculatedState; } @@ -393,6 +424,7 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, packetId, &foundQoS, ¤tState ); + if( foundQoS != qos ) { /* Entry should match with supplied QoS. */ @@ -408,6 +440,7 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, { newState = MQTT_CalculateStatePublish( opType, qos ); isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos ); + if( isTransitionValid ) { /* addRecord will check for collisions. */ @@ -440,7 +473,7 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, { newState = MQTTStateNull; } - + return newState; } @@ -458,28 +491,36 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, qosValid = ( qos == MQTTQoS1 ); calculatedState = MQTTPublishDone; break; + case MQTTPubrec: + /* Incoming publish: send PUBREC, PUBREL pending. * Outgoing publish: receive PUBREC, send PUBREL. */ - calculatedState = ( opType == MQTT_SEND )? MQTTPubRelPending : MQTTPubRelSend; + calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRelPending : MQTTPubRelSend; break; + case MQTTPubrel: + /* Incoming publish: receive PUBREL, send PUBCOMP. * Outgoing publish: send PUBREL, PUBCOMP pending. */ - calculatedState = ( opType == MQTT_SEND )? MQTTPubCompPending : MQTTPubCompSend; + calculatedState = ( opType == MQTT_SEND ) ? MQTTPubCompPending : MQTTPubCompSend; break; + case MQTTPubcomp: calculatedState = MQTTPublishDone; break; + default: /* No other ack type. */ break; } + /* Sanity check, make sure ack and QoS agree. */ if( !qosValid ) { calculatedState = MQTTStateNull; } + return calculatedState; } @@ -505,6 +546,7 @@ MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, { records = pMqttContext->incomingPublishRecords; } + recordIndex = findInRecord( records, MQTT_STATE_ARRAY_MAX_COUNT, packetId, @@ -516,6 +558,7 @@ MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, newState = MQTT_CalculateStateAck( packetType, opType, qos ); shouldDeleteRecord = ( newState == MQTTPublishDone ); isTransitionValid = validateTransitionAck( currentState, newState ); + if( isTransitionValid ) { updateRecord( records, @@ -527,17 +570,17 @@ MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, { newState = MQTTStateNull; } - } + return newState; } -uint16_t MQTT_StateSelect( MQTTContext_t * pMqttContext, +uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, MQTTPublishState_t searchState, MQTTStateCursor_t * pCursor ) { uint16_t packetId = MQTT_PACKET_ID_INVALID; - MQTTPubAckInfo_t * records = NULL; + const MQTTPubAckInfo_t * records = NULL; /* Classify state into incoming or outgoing so we don't search both. */ switch( searchState ) @@ -546,21 +589,27 @@ uint16_t MQTT_StateSelect( MQTTContext_t * pMqttContext, case MQTTPubRecSend: case MQTTPubRelPending: case MQTTPubCompSend: + if( pMqttContext != NULL ) { records = pMqttContext->incomingPublishRecords; } + break; + case MQTTPublishSend: case MQTTPubAckPending: case MQTTPubRecPending: case MQTTPubRelSend: case MQTTPubCompPending: + if( pMqttContext != NULL ) { records = pMqttContext->outgoingPublishRecords; } + break; + default: /* NULL or done aren't valid entries to search for. */ break; @@ -576,6 +625,7 @@ uint16_t MQTT_StateSelect( MQTTContext_t * pMqttContext, ( *pCursor )++; break; } + ( *pCursor )++; } } From 4aa37ceb0b49ac949f1b482924ec5af1402d3e46 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 13 May 2020 12:27:52 -0700 Subject: [PATCH 498/844] Minor re-arrangement of Cmake logic across CMakeLists.txt (#936) --- CMakeLists.txt | 2 +- libraries/3rdparty/CMakeLists.txt | 63 ++++++++++++++++++++++++++++ libraries/CMakeLists.txt | 70 ++----------------------------- 3 files changed, 68 insertions(+), 67 deletions(-) create mode 100644 libraries/3rdparty/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f10970f91..b3e5e39134 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ include("tools/cmake/filePaths.cmake") # Configure options to always show in CMake GUI. option( BUILD_TESTS "Set this to ON to build both demo and test executables. When OFF, only demo executables are built." - ON ) + OFF ) option( BUILD_CLONE_SUBMODULES "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." ON ) diff --git a/libraries/3rdparty/CMakeLists.txt b/libraries/3rdparty/CMakeLists.txt new file mode 100644 index 0000000000..5e39d5c23f --- /dev/null +++ b/libraries/3rdparty/CMakeLists.txt @@ -0,0 +1,63 @@ +# Configuration for CMock if testing is enabled. +if( ${BUILD_TESTS} ) + # Check if the CMock source directory exists. + if( NOT EXISTS ${3RDPARTY_DIR}/CMock/src ) + # Attempt to clone CMock. + if( ${BUILD_CLONE_SUBMODULES} ) + find_package( Git REQUIRED ) + + message( "Cloning submodule CMock." ) + execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive libraries/3rdparty/CMock + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE CMOCK_CLONE_RESULT ) + + if( NOT ${CMOCK_CLONE_RESULT} STREQUAL "0" ) + message( FATAL_ERROR "Failed to clone CMock submodule." ) + endif() + else() + message( FATAL_ERROR "The required submodule CMock does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) + endif() + endif() + + # Build Configuration for CMock and Unity libraries. + include_directories("${3RDPARTY_DIR}/CMock/vendor/unity/src/" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src" + "${3RDPARTY_DIR}/CMock/src" + ) + link_directories("${CMAKE_BINARY_DIR}/lib" + ) + + add_library(cmock STATIC + "${ROOT_DIR}/libraries/3rdparty/CMock/src/cmock.c" + ) + + set_target_properties(cmock PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + COMPILE_FLAGS "-Og" + ) + + add_library(unity STATIC + "${3RDPARTY_DIR}/CMock/vendor/unity/src/unity.c" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src/unity_fixture.c" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src/unity_memory.c" + ) + set_target_properties(unity PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + ) + + target_include_directories(cmock PUBLIC + ${ROOT_DIR}/libraries/3rdparty/CMock/src + ${ROOT_DIR}/libraries/3rdparty/CMock/vendor/unity/src/ + ${ROOT_DIR}/libraries/3rdparty/CMock/examples + ) + target_link_libraries(cmock unity) + + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake + DEPENDS cmock unity http_utest mqtt_utest + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endif() \ No newline at end of file diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 6dd4e44a1f..cf644737af 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,71 +1,9 @@ +include("${ROOT_DIR}/tools/cmock/create_test.cmake") -if( ${BUILD_TESTS} ) - # Check if the CMock source directory exists. - if( NOT EXISTS ${3RDPARTY_DIR}/CMock/src ) - # Attempt to clone CMock. - if( ${BUILD_CLONE_SUBMODULES} ) - find_package( Git REQUIRED ) - - message( "Cloning submodule CMock." ) - execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive libraries/3rdparty/CMock - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE CMOCK_CLONE_RESULT ) - - if( NOT ${CMOCK_CLONE_RESULT} STREQUAL "0" ) - message( FATAL_ERROR "Failed to clone CMock submodule." ) - endif() - else() - message( FATAL_ERROR "The required submodule CMock does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) - endif() - endif() - - include("${ROOT_DIR}/tools/cmock/create_test.cmake") - - include_directories("${3RDPARTY_DIR}/CMock/vendor/unity/src/" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src" - "${3RDPARTY_DIR}/CMock/src" - ) - link_directories("${CMAKE_BINARY_DIR}/lib" - ) - - add_library(cmock STATIC - "${ROOT_DIR}/libraries/3rdparty/CMock/src/cmock.c" - ) - - set_target_properties(cmock PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib - POSITION_INDEPENDENT_CODE ON - COMPILE_FLAGS "-Og" - ) - - add_library(unity STATIC - "${3RDPARTY_DIR}/CMock/vendor/unity/src/unity.c" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src/unity_fixture.c" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src/unity_memory.c" - ) - set_target_properties(unity PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib - POSITION_INDEPENDENT_CODE ON - ) - - target_include_directories(cmock PUBLIC - ${ROOT_DIR}/libraries/3rdparty/CMock/src - ${ROOT_DIR}/libraries/3rdparty/CMock/vendor/unity/src/ - ${ROOT_DIR}/libraries/3rdparty/CMock/examples - ) - target_link_libraries(cmock unity) - - add_custom_target(coverage - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake - DEPENDS cmock unity http_utest mqtt_utest - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) -endif() - -# Add standard modules +# Add all CMakeLists.txt in 3rdparty and standard folders. file(GLOB standard_modules "${MODULES_DIR}/standard/*") -foreach(module IN LISTS standard_modules) +file(GLOB 3rdparty_modules "${3RDPARTY_DIR}/*") +foreach(module IN LISTS standard_modules 3rdparty_modules) if(IS_DIRECTORY "${module}" AND EXISTS "${module}/CMakeLists.txt") add_subdirectory(${module}) endif() From 133d55578d4172ec24df25fa4143d5a89735786a Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Wed, 13 May 2020 13:29:06 -0700 Subject: [PATCH 499/844] Update publish with state machine updates (#935) --- libraries/standard/mqtt/src/mqtt.c | 136 +++++++++++++++++++++++------ 1 file changed, 110 insertions(+), 26 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 4744b4da9d..6a65ea4508 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -23,6 +23,7 @@ #include #include "mqtt.h" +#include "mqtt_state.h" #include "private/mqtt_internal.h" /*-----------------------------------------------------------*/ @@ -56,6 +57,20 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co size_t subscriptionCount, uint16_t packetId ); +/** + * @brief Send serialized publish packet using transport send. + * + * @brief param[in] pContext Initialized MQTT context. + * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @brief param[in] headerSize Header size of the PUBLISH packet. + * + * @return #MQTTSendFailed if transport write failed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, + const MQTTPublishInfo_t * const pPublishInfo, + size_t headerSize ); + /*-----------------------------------------------------------*/ static int32_t sendPacket( MQTTContext_t * pContext, @@ -151,6 +166,54 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co /*-----------------------------------------------------------*/ +static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, + const MQTTPublishInfo_t * const pPublishInfo, + size_t headerSize ) +{ + MQTTStatus_t status = MQTTSuccess; + int32_t bytesSent = 0; + + assert( pContext != NULL ); + assert( pPublishInfo != NULL ); + assert( headerSize > 0 ); + + /* Send header first. */ + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + headerSize ); + + if( bytesSent < 0 ) + { + LogError( "Transport send failed for PUBLISH header." ); + status = MQTTSendFailed; + } + else + { + LogDebugWithArgs( "Sent %d bytes of PUBLISH header.", + bytesSent ); + + /* Send Payload. */ + bytesSent = sendPacket( pContext, + pPublishInfo->pPayload, + pPublishInfo->payloadLength ); + + if( bytesSent < 0 ) + { + LogError( "Transport send failed for PUBLISH payload." ); + status = MQTTSendFailed; + } + else + { + LogDebugWithArgs( "Sent %d bytes of PUBLISH payload.", + bytesSent ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, @@ -330,6 +393,7 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; + MQTTPublishState_t publishStatus = MQTTStateNull; /* Validate arguments. */ if( ( pContext == NULL ) || ( pPublishInfo == NULL ) ) @@ -375,41 +439,61 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - /* Send header first. */ - bytesSent = sendPacket( pContext, - pContext->networkBuffer.pBuffer, - headerSize ); - - if( bytesSent < 0 ) + /* Reserve state for publish message. Only to be done for QoS1 or QoS2. */ + if( pPublishInfo->qos > MQTTQoS0 ) { - LogError( "Transport send failed for PUBLISH header." ); - status = MQTTSendFailed; + status = MQTT_ReserveState( pContext, + packetId, + pPublishInfo->qos ); } - else - { - LogDebugWithArgs( "Sent %d bytes of PUBLISH header.", - bytesSent ); + } - /* Send Payload. */ - bytesSent = sendPacket( pContext, - pPublishInfo->pPayload, - pPublishInfo->payloadLength ); + if( status == MQTTSuccess ) + { + /* Sends the serialized publish packet over network. */ + status = sendPublish( pContext, + pPublishInfo, + headerSize ); - if( bytesSent < 0 ) - { - LogError( "Transport send failed for PUBLISH payload." ); - status = MQTTSendFailed; - } - else + /* TODO. When a publish fails, the reserved state has to be cleaned + * up. This will have to be done once an API in state machine is + * available. */ + } + + if( status == MQTTSuccess ) + { + /* Update state machine after PUBLISH is sent. + * Only to be done for QoS1 or QoS2. */ + if( pPublishInfo->qos > MQTTQoS0 ) + { + /* TODO MQTT_UpdateStatePublish will be updated to return + * MQTTStatus_t instead of MQTTPublishState_t. Update the + * code when that change is made. */ + publishStatus = MQTT_UpdateStatePublish( pContext, + packetId, + MQTT_SEND, + pPublishInfo->qos ); + + if( publishStatus == MQTTStateNull ) { - LogDebugWithArgs( "Sent %d bytes of PUBLISH payload.", - bytesSent ); + LogErrorWithArgs( "Update state for publish failed with status =%u." + " However PUBLISH packet is sent to the broker." + " Any further handling of ACKs for the packet Id" + " will fail.", + publishStatus ); + + /* TODO. Need to remove this update once MQTT_UpdateStatePublish is + * refactored with return type of MQTTStatus_t. */ + status = MQTTBadParameter; } } } - /* TODO - Update the state machine with the packet ID. This will have to - * be done once the state machine changes are available.*/ + if( status != MQTTSuccess ) + { + LogErrorWithArgs( "MQTT PUBLISH failed with status=%u.", + status ); + } return status; } From 1b28e023824c0d1280be787b2563c0b9239178e5 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 13 May 2020 13:36:43 -0700 Subject: [PATCH 500/844] Rename Log() -> SdkLog() to avoid naming collision (#933) * Rename Log() -> SdkLog() to avoid naming collision * Remove IOT_ prefix from log level macros * Remove AWS_IOT from USE_AWS_IOT_CSD_LOGGING config --- .../http/src/private/http_client_internal.h | 16 +-- libraries/standard/http/test/config.h | 22 ++-- libraries/standard/http/utest/config.h | 24 ++-- .../standard/mqtt/src/mqtt_lightweight.c | 4 +- .../standard/mqtt/src/private/mqtt_internal.h | 10 +- libraries/standard/mqtt/utest/config.h | 26 ++-- .../utilities/include/logging_levels.h | 40 +++--- .../utilities/include/logging_setup.h | 120 +++++++++--------- platform/posix/clock_posix.c | 10 +- platform/posix/logging.c | 12 +- 10 files changed, 142 insertions(+), 142 deletions(-) diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index ae88347d6a..94be7c5e1a 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -6,19 +6,19 @@ /** * AWS IoT Embedded C SDK optional specific logging setup. */ -#ifdef USE_AWS_IOT_CSDK_LOGGING - #ifdef IOT_LOG_LEVEL_HTTP - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_HTTP +#ifdef USE_CSDK_LOGGING + #ifdef LOG_LEVEL_HTTP + #define LIBRARY_LOG_LEVEL LOG_LEVEL_HTTP #else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #ifdef LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL LOG_LEVEL_GLOBAL #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL LOG_NONE #endif #endif #define LIBRARY_LOG_NAME ( "HTTP" ) #include "logging_setup.h" -#else /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +#else /* ifdef USE_CSDK_LOGGING */ /* Otherwise please define logging macros in config.h. */ #define LogError( message ) #define LogErrorWithArgs( format, ... ) @@ -28,7 +28,7 @@ #define LogInfoWithArgs( format, ... ) #define LogDebug( message ) #define LogDebugWithArgs( format, ... ) -#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +#endif /* ifdef USE_CSDK_LOGGING */ /** * @brief The HTTP protocol version of this library is HTTP/1.1. diff --git a/libraries/standard/http/test/config.h b/libraries/standard/http/test/config.h index 1f158d0f56..8349549bed 100644 --- a/libraries/standard/http/test/config.h +++ b/libraries/standard/http/test/config.h @@ -1,25 +1,25 @@ #ifndef CONFIG_H #define CONFIG_H -#define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG +#define LOG_LEVEL_HTTP LOG_DEBUG -#ifdef USE_AWS_IOT_CSDK_LOGGING +#ifdef USE_CSDK_LOGGING /* Include file for POSIX reference implementation. */ - #include "iot_logging.h" + #include "logging.h" /* Define the IotLog logging interface to enable logging. * This demo maps the macro to the reference POSIX implementation for logging. * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the * log, as metadata in each log message. */ - #define IotLog( messageLevel, pFormat, ... ) \ - IotLog_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) + #define SdkLog( messageLevel, pFormat, ... ) \ + Log_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) -#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +#endif /* ifdef USE_CSDK_LOGGING */ #endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/http/utest/config.h b/libraries/standard/http/utest/config.h index 39f58af1f3..7f697f3258 100644 --- a/libraries/standard/http/utest/config.h +++ b/libraries/standard/http/utest/config.h @@ -1,27 +1,27 @@ #ifndef CONFIG_H #define CONFIG_H -#define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG +#define LOG_LEVEL_HTTP LOG_DEBUG -#define USE_AWS_IOT_CSDK_LOGGING 1 +#define USE_CSDK_LOGGING 1 -#ifdef USE_AWS_IOT_CSDK_LOGGING +#ifdef USE_CSDK_LOGGING /* Include file for POSIX reference implementation. */ - #include "iot_logging.h" + #include "logging.h" /* Define the IotLog logging interface to enable logging. * This demo maps the macro to the reference POSIX implementation for logging. * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the * log, as metadata in each log message. */ - #define IotLog( messageLevel, pFormat, ... ) \ - IotLog_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) + #define SdkLog( messageLevel, pFormat, ... ) \ + Log_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) -#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +#endif /* ifdef USE_CSDK_LOGGING */ #endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 383ff28d51..8ffe2c8996 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -717,7 +717,7 @@ static void logConnackResponse( uint8_t responseCode ) /* Declare the CONNACK response code strings. The fourth byte of CONNACK * indexes into this array for the corresponding response. This array * does not need to be allocated if logs are not enabled. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #if LIBRARY_LOG_LEVEL > LOG_NONE static const char * const pConnackResponses[ 6 ] = { "Connection accepted.", /* 0 */ @@ -728,7 +728,7 @@ static void logConnackResponse( uint8_t responseCode ) "Connection refused: not authorized." /* 5 */ }; LogErrorWithArgs( "%s", pConnackResponses[ responseCode ] ); - #endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + #endif /* if LIBRARY_LOG_LEVEL > LOG_NONE */ } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/private/mqtt_internal.h b/libraries/standard/mqtt/src/private/mqtt_internal.h index d024f5d2b0..8c0718463c 100644 --- a/libraries/standard/mqtt/src/private/mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/mqtt_internal.h @@ -5,13 +5,13 @@ * AWS IoT Embedded C SDK optional specific logging setup. */ #ifdef USE_AWS_IOT_CSDK_LOGGING - #ifdef IOT_LOG_LEVEL_MQTT - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT + #ifdef LOG_LEVEL_MQTT + #define LIBRARY_LOG_LEVEL LOG_LEVEL_MQTT #else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #ifdef LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL LOG_LEVEL_GLOBAL #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL LOG_NONE #endif #endif #define LIBRARY_LOG_NAME ( "MQTT" ) diff --git a/libraries/standard/mqtt/utest/config.h b/libraries/standard/mqtt/utest/config.h index d11838db55..f0a59cde90 100644 --- a/libraries/standard/mqtt/utest/config.h +++ b/libraries/standard/mqtt/utest/config.h @@ -1,28 +1,28 @@ #ifndef CONFIG_H #define CONFIG_H -#define IOT_LOG_LEVEL_HTTP IOT_LOG_DEBUG +#define LOG_LEVEL_HTTP LOG_DEBUG -#define USE_AWS_IOT_CSDK_LOGGING 1 +#define USE_CSDK_LOGGING 1 -#ifdef USE_AWS_IOT_CSDK_LOGGING +#ifdef USE_CSDK_LOGGING /* Include file for POSIX reference implementation. */ - #include "iot_logging.h" + #include "logging.h" /* Define the IotLog logging interface to enable logging. * This demo maps the macro to the reference POSIX implementation for logging. * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the * log, as metadata in each log message. */ - #define IotLog( messageLevel, pFormat, ... ) \ - IotLog_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) - -#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ + #define SdkLog( messageLevel, pFormat, ... ) \ + Log_Generic( messageLevel, \ + "[%s:%d] [%s] "pFormat, \ + __FILE__, \ + __LINE__, \ + LIBRARY_LOG_NAME, \ + __VA_ARGS__ ) + +#endif /* ifdef USE_CSDK_LOGGING */ /* Set network context to socket (int). */ diff --git a/libraries/standard/utilities/include/logging_levels.h b/libraries/standard/utilities/include/logging_levels.h index d3cd716340..d0f45318e8 100644 --- a/libraries/standard/utilities/include/logging_levels.h +++ b/libraries/standard/utilities/include/logging_levels.h @@ -39,25 +39,25 @@ * for that library. * * Currently, there are 4 log levels. In the order of lowest to highest, they are: - * - #IOT_LOG_NONE
- * @copybrief IOT_LOG_NONE - * - #IOT_LOG_ERROR
- * @copybrief IOT_LOG_ERROR - * - #IOT_LOG_WARN
- * @copybrief IOT_LOG_WARN - * - #IOT_LOG_INFO
- * @copybrief IOT_LOG_INFO - * - #IOT_LOG_DEBUG
- * @copybrief IOT_LOG_DEBUG + * - #LOG_NONE
+ * @copybrief LOG_NONE + * - #LOG_ERROR
+ * @copybrief LOG_ERROR + * - #LOG_WARN
+ * @copybrief LOG_WARN + * - #LOG_INFO
+ * @copybrief LOG_INFO + * - #LOG_DEBUG
+ * @copybrief LOG_DEBUG */ /** * @brief No log messages. * - * When @ref LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no + * When @ref LIBRARY_LOG_LEVEL is #LOG_NONE, logging is disabled and no * logging messages are printed. */ -#define IOT_LOG_NONE 0 +#define LOG_NONE 0 /** * @brief Represents erroneous application state or event. @@ -66,9 +66,9 @@ * which it cannot recover. * * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either - * of #IOT_LOG_ERROR, #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + * of #LOG_ERROR, #LOG_WARN, #LOG_INFO or #LOG_DEBUG. */ -#define IOT_LOG_ERROR 1 +#define LOG_ERROR 1 /** * @brief Message about an abnormal event. @@ -78,9 +78,9 @@ * execution after logging a warning. * * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either - * of #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. + * of #LOG_WARN, #LOG_INFO or #LOG_DEBUG. */ -#define IOT_LOG_WARN 2 +#define LOG_WARN 2 /** * @brief A helpful, informational message. @@ -89,9 +89,9 @@ * the progress of the program at a coarse-grained level. * * These messages are printed when @ref LIBRARY_LOG_LEVEL is defined as either - * of #IOT_LOG_INFO or #IOT_LOG_DEBUG. + * of #LOG_INFO or #LOG_DEBUG. */ -#define IOT_LOG_INFO 3 +#define LOG_INFO 3 /** * @brief Detailed and excessive debug information. @@ -102,8 +102,8 @@ * variables, buffers, or other specific information. * * These messages are only printed when @ref LIBRARY_LOG_LEVEL is defined as - * #IOT_LOG_DEBUG. + * #LOG_DEBUG. */ -#define IOT_LOG_DEBUG 4 +#define LOG_DEBUG 4 #endif /* ifndef IOT_LOGGING_LEVELS_H_ */ diff --git a/libraries/standard/utilities/include/logging_setup.h b/libraries/standard/utilities/include/logging_setup.h index 9bd3e3103e..4a7ffab39c 100644 --- a/libraries/standard/utilities/include/logging_setup.h +++ b/libraries/standard/utilities/include/logging_setup.h @@ -21,12 +21,12 @@ */ /** - * @file iot_logging_setup.h + * @file logging_setup.h * @brief Defines the common logging framework that calls #Log interface. */ -#ifndef IOT_LOGGING_SETUP_H_ -#define IOT_LOGGING_SETUP_H_ +#ifndef LOGGING_SETUP_H_ +#define LOGGING_SETUP_H_ /* The config header is always included first. */ #include "config.h" @@ -48,8 +48,8 @@ * This macro should be mapped to the platform's logging library. * * @param[in] messageLevel The integer code for the log level of the message. - * Must be one of #IOT_LOG_ERROR, #IOT_LOG_WARN, #IOT_LOG_INFO or #IOT_LOG_DEBUG. - * Must not be #IOT_LOG_NONE. + * Must be one of #LOG_ERROR, #LOG_WARN, #LOG_INFO or #LOG_DEBUG. + * Must not be #LOG_NONE. * @param[in] pFormat The format string for the log message. * @param[in] ... The variadic argument list for the format string. * @@ -58,136 +58,136 @@ /** * @def LogError( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_ERROR. + * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_ERROR. * * Equivalent to: * @code{c} - * Log( IOT_LOG_ERROR, "%s" , message ) + * SdkLog( LOG_ERROR, "%s" , message ) * @endcode */ /** * @def LogErrorWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_ERROR. + * @brief Abbreviated logging macro for messages with arguments at level #LOG_ERROR. * * Equivalent to: * @code{c} - * Log( IOT_LOG_ERROR, pFormat, ... ) + * SdkLog( LOG_ERROR, pFormat, ... ) * @endcode */ /** * @def LogWarn( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_WARN. + * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_WARN. * * Equivalent to: * @code{c} - * Log( IOT_LOG_WARN, "%s" , message ) + * SdkLog( LOG_WARN, "%s" , message ) * @endcode */ /** * @def LogWarnWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_WARN. + * @brief Abbreviated logging macro for messages with arguments at level #LOG_WARN. * * Equivalent to: * @code{c} - * Log( IOT_LOG_WARN, pFormat, ... ) + * SdkLog( LOG_WARN, pFormat, ... ) * @endcode */ /** * @def LogInfo( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_INFO. + * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_INFO. * * Equivalent to: * @code{c} - * Log( IOT_LOG_INFO, "%s" , message ) + * SdkLog( LOG_INFO, "%s" , message ) * @endcode */ /** * @def LogInfoWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_INFO. + * @brief Abbreviated logging macro for messages with arguments at level #LOG_INFO. * * Equivalent to: * @code{c} - * Log( IOT_LOG_INFO, pFormat, ... ) + * SdkLog( LOG_INFO, pFormat, ... ) * @endcode */ /** * @def LogDebug( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #IOT_LOG_DEBUG. + * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_DEBUG. * * Equivalent to: * @code{c} - * Log( IOT_LOG_DEBUG, "%s" , message ) + * SdkLog( LOG_DEBUG, "%s" , message ) * @endcode */ /** * @def LogDebugWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #IOT_LOG_DEBUG. + * @brief Abbreviated logging macro for messages with arguments at level #LOG_DEBUG. * * Equivalent to: * @code{c} - * Log( IOT_LOG_DEBUG, pFormat, ... ) + * SdkLog( LOG_DEBUG, pFormat, ... ) * @endcode */ /* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ -#if !defined( LIBRARY_LOG_LEVEL ) || \ - ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && \ - ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) - #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." +#if !defined( LIBRARY_LOG_LEVEL ) || \ + ( ( LIBRARY_LOG_LEVEL != LOG_NONE ) && \ + ( LIBRARY_LOG_LEVEL != LOG_ERROR ) && \ + ( LIBRARY_LOG_LEVEL != LOG_WARN ) && \ + ( LIBRARY_LOG_LEVEL != LOG_INFO ) && \ + ( LIBRARY_LOG_LEVEL != LOG_DEBUG ) ) + #error "Please define LIBRARY_LOG_LEVEL as either LOG_NONE, LOG_ERROR, LOG_WARN, LOG_INFO, or LOG_DEBUG." #else - #if LIBRARY_LOG_LEVEL != IOT_LOG_NONE - #if !defined( Log ) - #error "Please define the common logging interface macro, Log(messageLevel, pFormat, ...)." + #if LIBRARY_LOG_LEVEL != LOG_NONE + #if !defined( SdkLog ) + #error "Please define the common logging interface macro, sdkLog(messageLevel, pFormat, ...)." #endif #endif - #if LIBRARY_LOG_LEVEL == IOT_LOG_DEBUG + #if LIBRARY_LOG_LEVEL == LOG_DEBUG /* All log level messages will logged. */ - #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) Log( IOT_LOG_WARN, "%s", message ) - #define LogWarnWithArgs( pFormat, ... ) Log( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) - #define LogInfo( message ) Log( IOT_LOG_INFO, "%s", message ) - #define LogInfoWithArgs( pFormat, ... ) Log( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) - #define LogDebug( message ) Log( IOT_LOG_DEBUG, "%s", message ) - #define LogDebugWithArgs( pFormat, ... ) Log( IOT_LOG_DEBUG, pFormat, __VA_ARGS__ ) - - #elif LIBRARY_LOG_LEVEL == IOT_LOG_INFO + #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) SdkLog( LOG_WARN, "%s", message ) + #define LogWarnWithArgs( pFormat, ... ) SdkLog( LOG_WARN, pFormat, __VA_ARGS__ ) + #define LogInfo( message ) SdkLog( LOG_INFO, "%s", message ) + #define LogInfoWithArgs( pFormat, ... ) SdkLog( LOG_INFO, pFormat, __VA_ARGS__ ) + #define LogDebug( message ) SdkLog( LOG_DEBUG, "%s", message ) + #define LogDebugWithArgs( pFormat, ... ) SdkLog( LOG_DEBUG, pFormat, __VA_ARGS__ ) + + #elif LIBRARY_LOG_LEVEL == LOG_INFO /* Only INFO, WARNING and ERROR messages will be logged. */ - #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) Log( IOT_LOG_WARN, "%s", message ) - #define LogWarnWithArgs( pFormat, ... ) Log( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) - #define LogInfo( message ) Log( IOT_LOG_INFO, "%s", message ) - #define LogInfoWithArgs( pFormat, ... ) Log( IOT_LOG_INFO, pFormat, __VA_ARGS__ ) + #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) SdkLog( LOG_WARN, "%s", message ) + #define LogWarnWithArgs( pFormat, ... ) SdkLog( LOG_WARN, pFormat, __VA_ARGS__ ) + #define LogInfo( message ) SdkLog( LOG_INFO, "%s", message ) + #define LogInfoWithArgs( pFormat, ... ) SdkLog( LOG_INFO, pFormat, __VA_ARGS__ ) #define LogDebug( message ) #define LogDebugWithArgs( pFormat, ... ) - #elif LIBRARY_LOG_LEVEL == IOT_LOG_WARN + #elif LIBRARY_LOG_LEVEL == LOG_WARN /* Only WARNING and ERROR messages will be logged.*/ - #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) Log( IOT_LOG_WARN, "%s", message ) - #define LogWarnWithArgs( pFormat, ... ) Log( IOT_LOG_WARN, pFormat, __VA_ARGS__ ) + #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogWarn( message ) SdkLog( LOG_WARN, "%s", message ) + #define LogWarnWithArgs( pFormat, ... ) SdkLog( LOG_WARN, pFormat, __VA_ARGS__ ) #define LogInfo( message ) #define LogInfoWithArgs( pFormat, ... ) #define LogDebug( message ) #define LogDebugWithArgs( pFormat, ... ) - #elif LIBRARY_LOG_LEVEL == IOT_LOG_ERROR + #elif LIBRARY_LOG_LEVEL == LOG_ERROR /* Only ERROR messages will be logged. */ - #define LogError( message ) Log( IOT_LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) Log( IOT_LOG_ERROR, pFormat, __VA_ARGS__ ) + #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) + #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) #define LogWarn( message ) #define LogWarnWithArgs( pFormat, ... ) #define LogInfo( message ) @@ -195,7 +195,7 @@ #define LogDebug( message ) #define LogDebugWithArgs( pFormat, ... ) - #else /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ + #else /* if LIBRARY_LOG_LEVEL == LOG_ERROR */ #define LogError( message ) #define LogErrorWithArgs( pFormat, ... ) #define LogWarn( message ) @@ -204,7 +204,7 @@ #define LogInfoWithArgs( pFormat, ... ) #define LogDebug( message ) #define LogDebugWithArgs( pFormat, ... ) - #endif /* if LIBRARY_LOG_LEVEL == IOT_LOG_ERROR */ -#endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_WARN ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_INFO ) && ( LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) ) */ + #endif /* if LIBRARY_LOG_LEVEL == LOG_ERROR */ +#endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != LOG_NONE ) && ( LIBRARY_LOG_LEVEL != LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != LOG_WARN ) && ( LIBRARY_LOG_LEVEL != LOG_INFO ) && ( LIBRARY_LOG_LEVEL != LOG_DEBUG ) ) */ -#endif /* ifndef IOT_LOGGING_SETUP_H_ */ +#endif /* ifndef LOGGING_SETUP_H_ */ diff --git a/platform/posix/clock_posix.c b/platform/posix/clock_posix.c index 568c408e35..6c24445980 100644 --- a/platform/posix/clock_posix.c +++ b/platform/posix/clock_posix.c @@ -41,13 +41,13 @@ #include "clock.h" /* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#ifdef LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL LOG_LEVEL_PLATFORM #else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #ifdef LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL LOG_LEVEL_GLOBAL #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL LOG_NONE #endif #endif diff --git a/platform/posix/logging.c b/platform/posix/logging.c index d02e193a3d..08e0ace650 100644 --- a/platform/posix/logging.c +++ b/platform/posix/logging.c @@ -72,25 +72,25 @@ */ static const char * _log_level_strerror( int32_t messageLevel ) { - assert( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ); + assert( ( messageLevel >= LOG_NONE ) && ( messageLevel <= LOG_DEBUG ) ); const char * retVal = NULL; switch( messageLevel ) { - case IOT_LOG_ERROR: + case LOG_ERROR: retVal = "ERROR"; break; - case IOT_LOG_WARN: + case LOG_WARN: retVal = "WARN"; break; - case IOT_LOG_INFO: + case LOG_INFO: retVal = "INFO"; break; - case IOT_LOG_DEBUG: + case LOG_DEBUG: retVal = "DEBUG"; break; } @@ -131,7 +131,7 @@ void Log_Generic( int32_t messageLevel, const char * const pFormat, ... ) { - assert( ( messageLevel >= IOT_LOG_NONE ) && ( messageLevel <= IOT_LOG_DEBUG ) ); + assert( ( messageLevel >= LOG_NONE ) && ( messageLevel <= LOG_DEBUG ) ); int requiredMessageSize = 0; size_t bufferSize = 0, From a0565e05622a37b0d502c5ef5dbb6732fb27da7a Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 13 May 2020 15:45:32 -0700 Subject: [PATCH 501/844] =?UTF-8?q?Update=20logConnackResponse=20function?= =?UTF-8?q?=20to=20log=20CONNACK=20error=20response=20with=E2=80=A6=20(#93?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update logConnackResponse function to log CONNACK error response without dependency on LOG_NONE --- .../standard/mqtt/src/mqtt_lightweight.c | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 8ffe2c8996..9563817507 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -714,21 +714,37 @@ static void logConnackResponse( uint8_t responseCode ) assert( responseCode <= 5 ); - /* Declare the CONNACK response code strings. The fourth byte of CONNACK - * indexes into this array for the corresponding response. This array - * does not need to be allocated if logs are not enabled. */ - #if LIBRARY_LOG_LEVEL > LOG_NONE - static const char * const pConnackResponses[ 6 ] = - { - "Connection accepted.", /* 0 */ - "Connection refused: unacceptable protocol version.", /* 1 */ - "Connection refused: identifier rejected.", /* 2 */ - "Connection refused: server unavailable", /* 3 */ - "Connection refused: bad user name or password.", /* 4 */ - "Connection refused: not authorized." /* 5 */ - }; - LogErrorWithArgs( "%s", pConnackResponses[ responseCode ] ); - #endif /* if LIBRARY_LOG_LEVEL > LOG_NONE */ + /* Log an error based on the CONNACK response code. */ + switch( responseCode ) + { + case 0u: + LogInfo( "Connection accepted." ); + break; + + case 1u: + LogInfo( "Connection refused: unacceptable protocol version." ); + break; + + case 2u: + LogInfo( "Connection refused: identifier rejected." ); + break; + + case 3u: + LogInfo( "Connection refused: server unavailable" ); + break; + + case 4u: + LogInfo( "Connection refused: bad user name or password." ); + break; + + case 5u: + LogInfo( "Connection refused: not authorized." ); + break; + + default: + /* Empty default MISRA 16.4. */ + break; + } } /*-----------------------------------------------------------*/ From 0bd26993a8639192316505eab93a26ef1664d6df Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 14 May 2020 12:14:42 -0700 Subject: [PATCH 502/844] Fix issues with CMake setup for http and mqtt (#938) --- CMakeLists.txt | 1 + docs/utesting.md | 2 +- libraries/3rdparty/CMakeLists.txt | 91 ++++++------------- libraries/CMakeLists.txt | 47 +++++++++- libraries/standard/http/CMakeLists.txt | 20 +++- libraries/standard/http/filePaths.cmake | 22 ----- libraries/standard/http/httpFilePaths.cmake | 22 +++++ libraries/standard/http/utest/CMakeLists.txt | 24 +++-- libraries/standard/mqtt/CMakeLists.txt | 2 +- libraries/standard/mqtt/include/mqtt_state.h | 2 +- .../{filePaths.cmake => mqttFilePaths.cmake} | 9 +- libraries/standard/mqtt/utest/CMakeLists.txt | 6 +- tools/cmake/logging.cmake | 7 ++ tools/cmock/create_test.cmake | 8 +- 14 files changed, 150 insertions(+), 113 deletions(-) delete mode 100644 libraries/standard/http/filePaths.cmake create mode 100644 libraries/standard/http/httpFilePaths.cmake rename libraries/standard/mqtt/{filePaths.cmake => mqttFilePaths.cmake} (73%) create mode 100644 tools/cmake/logging.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b3e5e39134..dd8a47acf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ endif() # Import global configurations. include("tools/cmake/filePaths.cmake") +include("tools/cmake/logging.cmake") # Configure options to always show in CMake GUI. option( BUILD_TESTS diff --git a/docs/utesting.md b/docs/utesting.md index ffc87ecb0b..b6f91f6731 100644 --- a/docs/utesting.md +++ b/docs/utesting.md @@ -5,7 +5,7 @@ 1. Install lcov: `sudo apt install lcov` 1. Go to the root directory of this repository. 1. Create build directory: `mkdir build && cd build` -1. Run cmake while inside build directory: `cmake ..` +1. Run cmake while inside build directory: `cmake .. -DBUILD_TESTS=ON` 1. Run this command to build and run the tests: `make coverage` 1. Go to `build` and open `*_utest_out.txt` to view logs 1. Go to `build/coverage` and open `index.html` to view test coverage results. diff --git a/libraries/3rdparty/CMakeLists.txt b/libraries/3rdparty/CMakeLists.txt index 5e39d5c23f..fc9f55bfae 100644 --- a/libraries/3rdparty/CMakeLists.txt +++ b/libraries/3rdparty/CMakeLists.txt @@ -1,63 +1,28 @@ -# Configuration for CMock if testing is enabled. -if( ${BUILD_TESTS} ) - # Check if the CMock source directory exists. - if( NOT EXISTS ${3RDPARTY_DIR}/CMock/src ) - # Attempt to clone CMock. - if( ${BUILD_CLONE_SUBMODULES} ) - find_package( Git REQUIRED ) - - message( "Cloning submodule CMock." ) - execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive libraries/3rdparty/CMock - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - RESULT_VARIABLE CMOCK_CLONE_RESULT ) - - if( NOT ${CMOCK_CLONE_RESULT} STREQUAL "0" ) - message( FATAL_ERROR "Failed to clone CMock submodule." ) - endif() - else() - message( FATAL_ERROR "The required submodule CMock does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) - endif() - endif() - - # Build Configuration for CMock and Unity libraries. - include_directories("${3RDPARTY_DIR}/CMock/vendor/unity/src/" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src" - "${3RDPARTY_DIR}/CMock/src" - ) - link_directories("${CMAKE_BINARY_DIR}/lib" - ) - - add_library(cmock STATIC - "${ROOT_DIR}/libraries/3rdparty/CMock/src/cmock.c" - ) - - set_target_properties(cmock PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib - POSITION_INDEPENDENT_CODE ON - COMPILE_FLAGS "-Og" - ) - - add_library(unity STATIC - "${3RDPARTY_DIR}/CMock/vendor/unity/src/unity.c" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src/unity_fixture.c" - "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src/unity_memory.c" - ) - set_target_properties(unity PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib - POSITION_INDEPENDENT_CODE ON - ) - - target_include_directories(cmock PUBLIC - ${ROOT_DIR}/libraries/3rdparty/CMock/src - ${ROOT_DIR}/libraries/3rdparty/CMock/vendor/unity/src/ - ${ROOT_DIR}/libraries/3rdparty/CMock/examples - ) - target_link_libraries(cmock unity) - - add_custom_target(coverage - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake - DEPENDS cmock unity http_utest mqtt_utest - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) -endif() \ No newline at end of file +# Build Configuration for CMock and Unity libraries. +add_library(cmock STATIC + "${ROOT_DIR}/libraries/3rdparty/CMock/src/cmock.c" + ) + +set_target_properties(cmock PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + COMPILE_FLAGS "-Og" + ) + +add_library(unity STATIC + "${3RDPARTY_DIR}/CMock/vendor/unity/src/unity.c" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src/unity_fixture.c" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src/unity_memory.c" + ) +set_target_properties(unity PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + ) + +target_include_directories(cmock PUBLIC + ${ROOT_DIR}/libraries/3rdparty/CMock/src + ${ROOT_DIR}/libraries/3rdparty/CMock/vendor/unity/src/ + ${ROOT_DIR}/libraries/3rdparty/CMock/examples + ) + +target_link_libraries(cmock unity) diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index cf644737af..62b71152b4 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,9 +1,48 @@ -include("${ROOT_DIR}/tools/cmock/create_test.cmake") +# Configuration for CMock if testing is enabled. +if( ${BUILD_TESTS} ) + # Check if the CMock source directory exists. + if( NOT EXISTS ${3RDPARTY_DIR}/CMock/src ) + # Attempt to clone CMock. + if( ${BUILD_CLONE_SUBMODULES} ) + find_package( Git REQUIRED ) -# Add all CMakeLists.txt in 3rdparty and standard folders. + message( "Cloning submodule CMock." ) + execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive libraries/3rdparty/CMock + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE CMOCK_CLONE_RESULT ) + + if( NOT ${CMOCK_CLONE_RESULT} STREQUAL "0" ) + message( FATAL_ERROR "Failed to clone CMock submodule." ) + endif() + else() + message( FATAL_ERROR "The required submodule CMock does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) + endif() + endif() + + include("${ROOT_DIR}/tools/cmock/create_test.cmake") + + include_directories("${3RDPARTY_DIR}/CMock/vendor/unity/src/" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/fixture/src" + "${3RDPARTY_DIR}/CMock/vendor/unity/extras/memory/src" + "${3RDPARTY_DIR}/CMock/src" + ) + link_directories("${CMAKE_BINARY_DIR}/lib" + ) + + # Include the 3rdparty CMakeLists.txt for CMock build configuration. + add_subdirectory(${3RDPARTY_DIR}) + + # Add a target for running coverage on tests. + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake + DEPENDS cmock unity http_utest mqtt_utest + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endif() + +# Add all CMakeLists.txts in the standard folder. file(GLOB standard_modules "${MODULES_DIR}/standard/*") -file(GLOB 3rdparty_modules "${3RDPARTY_DIR}/*") -foreach(module IN LISTS standard_modules 3rdparty_modules) +foreach(module IN LISTS standard_modules ) if(IS_DIRECTORY "${module}" AND EXISTS "${module}/CMakeLists.txt") add_subdirectory(${module}) endif() diff --git a/libraries/standard/http/CMakeLists.txt b/libraries/standard/http/CMakeLists.txt index ca307d3bde..ccd10f1592 100644 --- a/libraries/standard/http/CMakeLists.txt +++ b/libraries/standard/http/CMakeLists.txt @@ -1,5 +1,23 @@ # Include filepaths for source and include. -include(filePaths.cmake) +include(httpFilePaths.cmake) + +# HTTP library target. +add_library( http + ${HTTP_SOURCES} ) + +# HTTP public include path. +target_include_directories( http PUBLIC ${HTTP_INCLUDE_PUBLIC_DIRS} + ${ROOT_DIR}/platform/include + ${CMAKE_CURRENT_LIST_DIR}/utest ) + +# HTTP private include path. +target_include_directories( http PRIVATE ${HTTP_INCLUDE_PRIVATE_DIRS} ) + +# Organization of HTTP in IDE projects. +set_target_properties( http PROPERTIES FOLDER libraries/standard ) +source_group( include FILES include/http_client.h ) +source_group( src FILES ${HTTP_SOURCES} ) +source_group( src\\private FILES src/private/http_internal.h src/private/http_client_parse.h ) if(BUILD_TESTS) add_subdirectory(utest) diff --git a/libraries/standard/http/filePaths.cmake b/libraries/standard/http/filePaths.cmake deleted file mode 100644 index cbe2fed879..0000000000 --- a/libraries/standard/http/filePaths.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# This file is to add source files and include directories -# into variables so that it can be reused from different repositories -# in their Cmake based build system by including this file. -# -# Files specific to the repository such as test runner, platform tests -# are not added to the variables. - -# HTTP library source files. -set( HTTP_SOURCES - "${MODULES_DIR}/standard/http/src/http_client.c" - "${MODULES_DIR}/standard/http/src/http_client_parse.c" ) - -# HTTP library Include directories. -set( HTTP_INCLUDE_PUBLIC_DIRS - "${MODULES_DIR}/standard/http/include" - "${MODULES_DIR}/standard/utilities/include" - ) - -# HTTP test include directories. -set( HTTP_TEST_INCLUDE_PRIVATE_DIRS - "${MODULES_DIR}/standard/http/src" - ) diff --git a/libraries/standard/http/httpFilePaths.cmake b/libraries/standard/http/httpFilePaths.cmake new file mode 100644 index 0000000000..446e000364 --- /dev/null +++ b/libraries/standard/http/httpFilePaths.cmake @@ -0,0 +1,22 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# HTTP library source files. +set( HTTP_SOURCES + ${MODULES_DIR}/standard/http/src/http_client.c + ${MODULES_DIR}/standard/http/src/http_client_parse.c ) + +# HTTP library Public Include directories. +set( HTTP_INCLUDE_PUBLIC_DIRS + ${MODULES_DIR}/standard/http/include + ${MODULES_DIR}/standard/utilities/include ) + +# HTTP library Private Include directories. +set( HTTP_INCLUDE_PRIVATE_DIRS + ${MODULES_DIR}/standard/http/src + ${MODULES_DIR}/standard/http/third_party ) + diff --git a/libraries/standard/http/utest/CMakeLists.txt b/libraries/standard/http/utest/CMakeLists.txt index 02dd4a18d0..f3a94363a4 100644 --- a/libraries/standard/http/utest/CMakeLists.txt +++ b/libraries/standard/http/utest/CMakeLists.txt @@ -1,4 +1,4 @@ -include("../filePaths.cmake") +include("../httpFilePaths.cmake") project ("http unit test") cmake_minimum_required (VERSION 3.13) @@ -9,11 +9,11 @@ set(project_name "http") # list the files to mock here list(APPEND mock_list - "" + ${MODULES_DIR}/standard/http/third_party/http_parser/http_parser.h ) # list the directories your mocks need list(APPEND mock_include_list - "" + ${MODULES_DIR}/standard/http/third_party/http_parser ) #list the definitions of your mocks to control what to be included list(APPEND mock_define_list @@ -25,13 +25,14 @@ list(APPEND mock_define_list # list the files you would like to test here list(APPEND real_source_files ${HTTP_SOURCES} + ${LOGGING_SOURCES} ) # list the directories the module under test includes list(APPEND real_include_directories . ${HTTP_INCLUDE_PUBLIC_DIRS} - ${HTTP_TEST_INCLUDE_PRIVATE_DIRS} - "${ROOT_DIR}/platform/include" + ${HTTP_INCLUDE_PRIVATE_DIRS} + ${LOGGING_INCLUDE_DIRS} ) # ===================== Create UnitTest Code here (edit) ===================== @@ -40,23 +41,32 @@ list(APPEND real_include_directories list(APPEND test_include_directories . ${HTTP_INCLUDE_PUBLIC_DIRS} + ${HTTP_INCLUDE_PRIVATE_DIRS} ${HTTP_TEST_INCLUDE_PRIVATE_DIRS} "${ROOT_DIR}/platform/include" ) # ============================= (end edit) =================================== -set(mock_name "${project_name}_mock") +set(mock_name "http_parser_mock") set(real_name "${project_name}_real") +create_mock_list(${mock_name} + "${mock_list}" + "${ROOT_DIR}/tools/cmock/project.yml" + "${mock_include_list}" + "${mock_define_list}" + ) + create_real_library(${real_name} "${real_source_files}" "${real_include_directories}" - "" + "${mock_name}" ) list(APPEND utest_link_list lib${real_name}.a + -l${mock_name} ) list(APPEND utest_dep_list diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index e04c7be76f..2e9985592b 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -1,5 +1,5 @@ # Include filepaths for source and include. -include(filePaths.cmake) +include(mqttFilePaths.cmake) # MQTT library target. add_library( mqtt diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index 8f346af2d5..044e807717 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -24,7 +24,7 @@ #include "mqtt.h" -#define MQTT_STATE_CURSOR_INITIALIZER ( size_t ) 0 +#define MQTT_STATE_CURSOR_INITIALIZER ( size_t ) 0 /** * @brief Value indicating either send or receive. diff --git a/libraries/standard/mqtt/filePaths.cmake b/libraries/standard/mqtt/mqttFilePaths.cmake similarity index 73% rename from libraries/standard/mqtt/filePaths.cmake rename to libraries/standard/mqtt/mqttFilePaths.cmake index 6410afb387..3d543dac1e 100644 --- a/libraries/standard/mqtt/filePaths.cmake +++ b/libraries/standard/mqtt/mqttFilePaths.cmake @@ -8,13 +8,14 @@ # MQTT library source files. set( MQTT_SOURCES "${MODULES_DIR}/standard/mqtt/src/mqtt.c" + "${MODULES_DIR}/standard/mqtt/src/mqtt_state.c" "${MODULES_DIR}/standard/mqtt/src/mqtt_lightweight.c" ) -# MQTT library Include directories. +# MQTT library Public Include directories. set( MQTT_INCLUDE_PUBLIC_DIRS "${MODULES_DIR}/standard/mqtt/include" "${MODULES_DIR}/standard/utilities/include" ) -# MQTT test include directories. -set( MQTT_TEST_INCLUDE_PRIVATE_DIRS - "${MODULES_DIR}/standard/mqtt/src" ) +# MQTT library Private Include directories. +set( MQTT_INCLUDE_PRIVATE_DIRS + "${MODULES_DIR}/standard/mqtt/src" ) \ No newline at end of file diff --git a/libraries/standard/mqtt/utest/CMakeLists.txt b/libraries/standard/mqtt/utest/CMakeLists.txt index 36ca717560..bd2f332f7f 100644 --- a/libraries/standard/mqtt/utest/CMakeLists.txt +++ b/libraries/standard/mqtt/utest/CMakeLists.txt @@ -1,4 +1,4 @@ -include("../filePaths.cmake") +include("../mqttFilePaths.cmake") project ("mqtt unit test") cmake_minimum_required (VERSION 3.13) @@ -32,7 +32,7 @@ list(APPEND real_source_files list(APPEND real_include_directories . ${MQTT_INCLUDE_PUBLIC_DIRS} - ${MQTT_TEST_INCLUDE_PRIVATE_DIRS} + ${MQTT_INCLUDE_PRIVATE_DIRS} "${ROOT_DIR}/platform/include" ) @@ -42,7 +42,7 @@ list(APPEND real_include_directories list(APPEND test_include_directories . ${MQTT_INCLUDE_PUBLIC_DIRS} - ${MQTT_TEST_INCLUDE_PRIVATE_DIRS} + ${MQTT_INCLUDE_PRIVATE_DIRS} "${ROOT_DIR}/platform/include" ) diff --git a/tools/cmake/logging.cmake b/tools/cmake/logging.cmake new file mode 100644 index 0000000000..6b1441098f --- /dev/null +++ b/tools/cmake/logging.cmake @@ -0,0 +1,7 @@ +# Configuration for logging. +set( LOGGING_INCLUDE_DIRS + ${ROOT_DIR}/platform/include ) + +set( LOGGING_SOURCES + ${ROOT_DIR}/platform/posix/logging.c + ${ROOT_DIR}/platform/posix/clock_posix.c ) \ No newline at end of file diff --git a/tools/cmock/create_test.cmake b/tools/cmock/create_test.cmake index 108d0e5911..30fee4abc8 100644 --- a/tools/cmock/create_test.cmake +++ b/tools/cmock/create_test.cmake @@ -11,14 +11,13 @@ function(create_test test_name get_filename_component(test_src_absolute ${test_src} ABSOLUTE) add_custom_command(OUTPUT ${test_name}_runner.c COMMAND ruby - ${CMAKE_SOURCE_DIR}/libraries/3rdparty/unity/auto/generate_test_runner.rb + ${CMAKE_SOURCE_DIR}/libraries/3rdparty/CMock/vendor/unity/auto/generate_test_runner.rb ${CMAKE_SOURCE_DIR}/tools/cmock/project.yml ${test_src_absolute} ${test_name}_runner.c DEPENDS ${test_src} ) add_executable(${test_name} ${test_src} ${test_name}_runner.c) - target_link_libraries(${test_name} cmock) set_target_properties(${test_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests" INSTALL_RPATH_USE_LINK_PATH TRUE @@ -30,10 +29,6 @@ function(create_test test_name ${mocks_dir} ${include_list} ) - target_link_libraries(${test_name} - "${ROOT_DIR}/platform/posix/iot_clock_posix.o" - "${ROOT_DIR}/platform/posix/iot_logging.o" - ) target_link_directories(${test_name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} @@ -139,6 +134,7 @@ function(create_mock_list mock_name target_compile_definitions(${mock_name} PUBLIC ${mock_define_list} ) + target_link_libraries(${mock_name} cmock unity) endfunction() From 10d5ea96c4b819f2674b871c1ccce3accf344c8e Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 14 May 2020 14:16:54 -0700 Subject: [PATCH 503/844] MQTT CONNACK receive in MQTT_Connect (#934) --- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 7 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 7 +- libraries/standard/mqtt/include/mqtt.h | 6 + libraries/standard/mqtt/src/mqtt.c | 168 +++++++++++++++--- libraries/standard/mqtt/utest/mqtt_utest.c | 2 +- 5 files changed, 165 insertions(+), 25 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 07e755450e..910fd844f7 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -69,6 +69,11 @@ */ #define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) +/** + * @brief Timeout for receiving CONNACK packet in milli seconds. + */ +#define CONNACK_RECV_TIMEOUT_MS (1000) + /*-----------------------------------------------------------*/ /** @@ -348,7 +353,7 @@ static int establishMqttSession( MQTTContext_t * pContext, SSL * pSslContext ) connectInfo.pPassword = NULL; connectInfo.passwordLength = 0; - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, NULL ); + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, NULL ); if( mqttStatus != MQTTSuccess ) { diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 9ffe0bf689..cf72f94239 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -62,6 +62,11 @@ */ #define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) +/** + * @brief Timeout for receiving CONNACK packet in milli seconds. + */ +#define CONNACK_RECV_TIMEOUT_MS (1000) + /*-----------------------------------------------------------*/ /** @@ -242,7 +247,7 @@ static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) connectInfo.pPassword = NULL; connectInfo.passwordLength = 0; - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, NULL ); + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, NULL ); if( mqttStatus != MQTTSuccess ) { diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 60965561c3..3d60a82807 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -132,6 +132,8 @@ void MQTT_Init( MQTTContext_t * const pContext, * @brief param[in] pContext Initialized MQTT context. * @brief param[in] pConnectInfo MQTT CONNECT packet parameters. * @brief param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @brief param[in] timeoutMs Timeout in milliseconds for receiving + * CONNACK packet. * @brief param[out] pSessionPresent Whether a previous session was present. * Only relevant if not establishing a clean session. * @@ -139,11 +141,15 @@ void MQTT_Init( MQTTContext_t * const pContext, * hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport send failed; + * #MQTTRecvFailed if transport receive failed for CONNACK; + * #MQTTNoDataAvailable if no data available to receive in transport until + * the #timeoutMs for CONNACK; * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, + uint32_t timeoutMs, bool * const pSessionPresent ); /** diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 6a65ea4508..092fe81e74 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -71,6 +71,37 @@ static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pPublishInfo, size_t headerSize ); +/** + * @brief Receives a CONNACK MQTT packet. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] timeoutMs Timeout for waiting for CONNACK packet. + * @param[out] pIncomingPacket List of MQTT subscription info. + * @param[out] pSessionPresent Whether a previous session was present. + * Only relevant if not establishing a clean session. + * + * @return #MQTTBadResponse if a bad response is received; + * #MQTTNoDataAvailable if no data available for transport recv; + * ##MQTTRecvFailed if transport recv failed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, + uint32_t timeoutMs, + MQTTPacketInfo_t * const pIncomingPacket, + bool * const pSessionPresent ); + +/** + * @brief Calculate the interval between two timestamps, including when the + * later value has overflowed. + * + * @param[in] later The later time stamp. + * @param[in] start The earlier time stamp. + * + * @return later - start. + */ +static uint32_t calculateElapsedTime( uint32_t later, + uint32_t start ); + /*-----------------------------------------------------------*/ static int32_t sendPacket( MQTTContext_t * pContext, @@ -214,6 +245,110 @@ static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ +static uint32_t calculateElapsedTime( uint32_t later, + uint32_t start ) +{ + return later - start; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, + MQTTPacketInfo_t incomingPacket, + uint32_t remainingTimeMs ) +{ + return MQTTSuccess; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, + uint32_t timeoutMs, + MQTTPacketInfo_t * const incomingPacket, + bool * const pSessionPresent ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTGetCurrentTimeFunc_t getTimeStamp = NULL; + uint32_t entryTimeMs = 0U, remainingTimeMs = 0U, timeTakenMs = 0U; + + assert( pContext != NULL ); + assert( incomingPacket != NULL ); + assert( pContext->callbacks.getTime != NULL ); + + getTimeStamp = pContext->callbacks.getTime; + /* Get the entry time for the function. */ + entryTimeMs = getTimeStamp(); + + do + { + /* Transport read for incoming CONNACK packet type and length. + * MQTT_GetIncomingPacketTypeAndLength is a blocking call and it is + * returned after a transport receive timeout, an error, or a successful + * receive of packet type and length. */ + status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, + pContext->transportInterface.networkContext, + incomingPacket ); + + /* Loop until there is data to read or if the timeout has not expired. */ + } while( ( status == MQTTNoDataAvailable ) && + ( calculateElapsedTime( getTimeStamp(), entryTimeMs ) < timeoutMs ) ); + + if( status == MQTTSuccess ) + { + /* Time taken in this function so far. */ + timeTakenMs = calculateElapsedTime( getTimeStamp(), entryTimeMs ); + + if( timeTakenMs < timeoutMs ) + { + /* Calculate remaining time for receiving the remainder of + * the packet. */ + remainingTimeMs = timeoutMs - timeTakenMs; + } + + /* Reading the remainder of the packet by transport recv. + * Attempt to read once even if the timeout has expired at this point. + * Invoking receivePacket with remainingTime as 0 would attempt to + * recv from network once.*/ + if( incomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) + { + status = receivePacket( pContext, + *incomingPacket, + remainingTimeMs ); + } + else + { + LogErrorWithArgs( "Incorrect packet type %X received while expecting" + " CONNACK(%X).", + incomingPacket->type, + MQTT_PACKET_TYPE_CONNACK ); + status = MQTTBadResponse; + } + } + + if( status == MQTTSuccess ) + { + /* Update the packet info pointer to the buffer read. */ + incomingPacket->pRemainingData = pContext->networkBuffer.pBuffer; + + /* Deserialize CONNACK. */ + status = MQTT_DeserializeAck( &incomingPacket, NULL, pSessionPresent ); + } + + if( status != MQTTSuccess ) + { + LogErrorWithArgs( "CONNACK recv failed with status = %u.", + status ); + } + else + { + LogInfo( "Received MQTT CONNACK successfully from broker." ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + void MQTT_Init( MQTTContext_t * const pContext, const MQTTTransportInterface_t * const pTransportInterface, const MQTTApplicationCallbacks_t * const pCallbacks, @@ -235,6 +370,7 @@ void MQTT_Init( MQTTContext_t * const pContext, MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, const MQTTConnectInfo_t * const pConnectInfo, const MQTTPublishInfo_t * const pWillInfo, + uint32_t timeoutMs, bool * const pSessionPresent ) { size_t remainingLength = 0UL, packetSize = 0UL; @@ -289,30 +425,13 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, } } + /* Read CONNACK from transport layer. */ if( status == MQTTSuccess ) { - /* Transport read for incoming connack packet. */ - status = MQTT_GetIncomingPacket( pContext->transportInterface.recv, - pContext->transportInterface.networkContext, - &incomingPacket ); - } - - if( status == MQTTSuccess ) - { - /* Check if received packet type is CONNACK and deserialize it. */ - if( incomingPacket.type == MQTT_PACKET_TYPE_CONNACK ) - { - LogInfo( "Received MQTT CONNACK from broker." ); - - /* Deserialize CONNACK. */ - status = MQTT_DeserializeAck( &incomingPacket, NULL, pSessionPresent ); - } - else - { - LogErrorWithArgs( "Unexpected packet type %u received from network.", - incomingPacket.type ); - status = MQTTBadResponse; - } + status = receiveConnack( pContext, + timeoutMs, + &incomingPacket, + pSessionPresent ); } if( status == MQTTSuccess ) @@ -320,6 +439,11 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, LogInfo( "MQTT connection established with the broker." ); pContext->connectStatus = MQTTConnected; } + else + { + LogErrorWithArgs( "MQTT connection failed with status = %u.", + status ); + } return status; } diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 167ad3b294..dd38e79fa2 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -33,5 +33,5 @@ void test_Mqtt_connect_packet_size_gt_max( void ) /* This mocked method will be called inside MQTT_Connect(...). */ MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); - TEST_ASSERT_EQUAL( MQTT_Connect( NULL, NULL, NULL, NULL ), MQTTBadParameter ); + TEST_ASSERT_EQUAL( MQTT_Connect( NULL, NULL, NULL, 0U, NULL ), MQTTBadParameter ); } From 10dfa5eb0f4afc73a36c4b6ee9ebbdc584034593 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 14 May 2020 14:26:44 -0700 Subject: [PATCH 504/844] Convert HTTPClient_InitializeRequestHeaders unit test to use Unity (#930) * Add happy path test for InitializeRequestHeaders * Achieve 100% coverage * Document unit tests * Remove use of static buffer * Declare functions at the beginning of function * Add http-parser dependency * Use static and global buffer instead of declaring inside each method * Remove printf statement * Change to MODULES_DIR * Remove http_parser from real sources and dirs * Use HTTP_METHOD_GET instead * Use LOGGING_SOURCES instead * Fix HTTP_METHOD_GET --- libraries/standard/http/utest/http_utest.c | 222 ++++++++++++++++++++- 1 file changed, 218 insertions(+), 4 deletions(-) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 0252b92be8..6171f83c06 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -9,6 +9,58 @@ #include "private/http_client_internal.h" #include "private/http_client_parse.h" +#define HTTP_METHOD_GET_LEN ( sizeof( HTTP_METHOD_GET ) - 1 ) +#define HTTP_TEST_REQUEST_PATH "/robots.txt" +#define HTTP_TEST_REQUEST_PATH_LEN ( sizeof( HTTP_TEST_REQUEST_PATH ) - 1 ) +#define HTTP_TEST_HOST_VALUE "amazon.com" +#define HTTP_TEST_HOST_VALUE_LEN ( sizeof( HTTP_TEST_HOST_VALUE ) - 1 ) +#define HTTP_TEST_REQUEST_LINE \ + ( HTTP_METHOD_GET " " \ + HTTP_TEST_REQUEST_PATH " " \ + HTTP_PROTOCOL_VERSION "\r\n" ) +#define HTTP_TEST_REQUEST_LINE_LEN ( sizeof( HTTP_TEST_REQUEST_LINE ) - 1 ) + +/* Used for format parameter in snprintf(...). */ +#define HTTP_TEST_HEADER_FORMAT \ + "%s %s %s\r\n" \ + "%s: %s\r\n" \ + "%s: %s\r\n" \ + "%s: %s\r\n\r\n" + +/* Length of the following template HTTP header. + * \r\n + * : \r\n + * : \r\n + * : \r\n + * \r\n + * This is used to initialize the expectedHeader string. Note the missing + * . This is added later on depending on the + * value of HTTP_REQUEST_KEEP_ALIVE_FLAG in pRequestInfo->flags. */ +#define HTTP_TEST_PREFIX_HEADER_LEN \ + ( HTTP_METHOD_GET_LEN + SPACE_CHARACTER_LEN + \ + HTTP_TEST_REQUEST_PATH_LEN + SPACE_CHARACTER_LEN + \ + HTTP_PROTOCOL_VERSION_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_USER_AGENT_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_USER_AGENT_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_HOST_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_TEST_HOST_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_CONNECTION_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN ) + +/* Add HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN to account for longest possible + * length of template header. */ +#define HTTP_TEST_MAX_INITIALIZED_HEADER_LEN \ + ( HTTP_TEST_PREFIX_HEADER_LEN + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ) + +/* Add 1 because snprintf(...) writes a null byte at the end. */ +#define HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN \ + ( HTTP_TEST_MAX_INITIALIZED_HEADER_LEN + 1 ) + +#define HTTP_TEST_BUFFER_LEN 1024 + +static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LEN ] = { 0 }; + /* ============================ UNITY FIXTURES ============================== */ void setUp( void ) { @@ -29,9 +81,171 @@ int suiteTearDown( int numFailures ) { } -/* ====================== Testing HTTPClient_AddHeader ====================== */ -void test_Http_AddHeader_invalid_params( void ) +/* ============== Testing HTTPClient_InitializeRequestHeaders =============== */ + +/** + * @brief Initialize pRequestInfo with test-defined macros. + * + * @param[in] pRequestInfo Initial request header configurations. + */ +static void setupRequestInfo( HTTPRequestInfo_t * pRequestInfo ) { - TEST_ASSERT_EQUAL( HTTPClient_AddHeader( NULL, NULL, 0, NULL, 0 ), - HTTP_INVALID_PARAMETER ); + pRequestInfo->method = HTTP_METHOD_GET; + pRequestInfo->methodLen = HTTP_METHOD_GET_LEN; + pRequestInfo->pPath = HTTP_TEST_REQUEST_PATH; + pRequestInfo->pathLen = HTTP_TEST_REQUEST_PATH_LEN; + pRequestInfo->pHost = HTTP_TEST_HOST_VALUE; + pRequestInfo->hostLen = HTTP_TEST_HOST_VALUE_LEN; + pRequestInfo->flags = 0; } + +/** + * @brief Initialize pRequestHeaders based on parameters. + * + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] bufferLen Size of the buffer. + */ +static void setupBuffer( HTTPRequestHeaders_t * pRequestHeaders, + size_t bufferLen ) +{ + pRequestHeaders->pBuffer = httpBuffer; + pRequestHeaders->bufferLen = bufferLen; +} + +/** + * @brief Test happy path with zero-initialized requestHeaders and requestInfo. + */ +void test_Http_InitializeRequestHeaders_happy_path() +{ + HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + HTTPRequestInfo_t requestInfo = { 0 }; + size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; + char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; + int numBytes = 0; + + setupRequestInfo( &requestInfo ); + setupBuffer( &requestHeaders, expectedHeaderLen ); + + /* Happy Path testing. */ + expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN + + HTTP_CONNECTION_CLOSE_VALUE_LEN; + numBytes = snprintf( expectedHeader, expectedHeaderLen + 1, + HTTP_TEST_HEADER_FORMAT, + HTTP_METHOD_GET, HTTP_TEST_REQUEST_PATH, + HTTP_PROTOCOL_VERSION, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ); + TEST_ASSERT_EQUAL( numBytes, expectedHeaderLen ); + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL_MEMORY( requestHeaders.pBuffer, expectedHeader, expectedHeaderLen ); + TEST_ASSERT_EQUAL( requestHeaders.headersLen, expectedHeaderLen ); + TEST_ASSERT_EQUAL( test_err, HTTP_SUCCESS ); +} + +/** + * @brief Test NULL parameters, following order of else-if blocks in the HTTP library. + */ +void test_Http_InitializeRequestHeaders_invalid_params() +{ + HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + HTTPRequestInfo_t requestInfo = { 0 }; + size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; + char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; + + /* Test NULL parameters, following order of else-if blocks. */ + test_err = HTTPClient_InitializeRequestHeaders( NULL, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + /* TEST requestInfo.pBuffer == NULL */ + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + requestHeaders.pBuffer = httpBuffer; + requestHeaders.bufferLen = HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN; + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, NULL ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + /* Test requestInfo members are NULL */ + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + requestInfo.method = HTTP_METHOD_GET; + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + requestInfo.pHost = HTTP_TEST_HOST_VALUE; + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + requestInfo.pPath = HTTP_TEST_REQUEST_PATH; + requestInfo.pathLen = HTTP_TEST_REQUEST_PATH_LEN; + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + requestInfo.methodLen = HTTP_METHOD_GET_LEN; + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + requestInfo.hostLen = HTTP_TEST_HOST_VALUE_LEN; +} + +/** + * @brief Test default path "/" if path == NULL. Also, check that the "Connection" + * header is set to "keep-alive" when HTTP_REQUEST_KEEP_ALIVE_FLAG in requestHeaders + * is activated. + */ +void test_Http_InitializeRequestHeaders_req_info() +{ + HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + HTTPRequestInfo_t requestInfo = { 0 }; + size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; + char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; + int numBytes = 0; + + setupRequestInfo( &requestInfo ); + setupBuffer( &requestHeaders, expectedHeaderLen ); + + requestInfo.pPath = 0; + requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN - + HTTP_TEST_REQUEST_PATH_LEN + + HTTP_EMPTY_PATH_LEN + + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN; + numBytes = snprintf( expectedHeader, expectedHeaderLen + 1, + HTTP_TEST_HEADER_FORMAT, + HTTP_METHOD_GET, HTTP_EMPTY_PATH, + HTTP_PROTOCOL_VERSION, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_KEEP_ALIVE_VALUE ); + TEST_ASSERT_EQUAL( numBytes, expectedHeaderLen ); + + requestHeaders.pBuffer = httpBuffer; + requestHeaders.bufferLen = expectedHeaderLen; + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL_MEMORY( requestHeaders.pBuffer, expectedHeader, expectedHeaderLen ); + TEST_ASSERT_EQUAL( requestHeaders.headersLen, expectedHeaderLen ); + TEST_ASSERT_EQUAL( test_err, HTTP_SUCCESS ); +} + +/** + * @brief Test HTTP_INSUFFICIENT_MEMORY from having requestHeaders.bufferLen less than + * what is required to fit HTTP_TEST_REQUEST_LINE. + */ +void test_Http_InitializeRequestHeaders_insufficient_memory() +{ + HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + HTTPRequestInfo_t requestInfo = { 0 }; + size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; + char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; + + setupRequestInfo( &requestInfo ); + setupBuffer( &requestHeaders, expectedHeaderLen ); + + requestHeaders.bufferLen = HTTP_TEST_REQUEST_LINE_LEN - 1; + + test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( test_err, HTTP_INSUFFICIENT_MEMORY ); + TEST_ASSERT_TRUE( strncmp( ( char * ) requestHeaders.pBuffer, + HTTP_TEST_REQUEST_LINE, + HTTP_TEST_REQUEST_LINE_LEN ) != 0 ); +} + +/* ========================================================================== */ From 334f2e9b2e82334d2a8cd57cd50040b63307afd6 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 14 May 2020 16:31:40 -0700 Subject: [PATCH 505/844] Implement MQTTProcessLoop (#901) * Add implementation of receive loop * Add keep alive to process loop * Add timer to packet reception * Add packet identifier to app callback * Add publish info to app callback --- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 5 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 5 +- libraries/standard/mqtt/include/mqtt.h | 32 +- .../standard/mqtt/include/mqtt_lightweight.h | 17 +- libraries/standard/mqtt/src/mqtt.c | 697 +++++++++++++++++- .../standard/mqtt/src/mqtt_lightweight.c | 11 +- 6 files changed, 715 insertions(+), 52 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 910fd844f7..c1b4bea39e 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -303,7 +303,10 @@ static uint32_t getTime( void ) * * Currently not implemented. */ -static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ) +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ) { } diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index cf72f94239..632aa788c5 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -197,7 +197,10 @@ static uint32_t getTime( void ) * * Currently not implemented. */ -static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo ) +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ) { } diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 3d60a82807..7e3fce0a77 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -44,7 +44,9 @@ typedef int32_t (* MQTTTransportSendFunc_t )( MQTTNetworkContext_t context, typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, - MQTTPacketInfo_t * pPacketInfo ); + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ); typedef enum MQTTConnectionStatus { @@ -109,6 +111,13 @@ struct MQTTContext MQTTConnectionStatus_t connectStatus; MQTTApplicationCallbacks_t callbacks; uint32_t lastPacketTime; + bool controlPacketSent; + + /* Keep alive members. */ + uint16_t keepAliveIntervalSec; + uint32_t pingReqSendTimeMs; + uint32_t pingRespTimeoutMs; + bool waitingForPingResp; }; /** @@ -233,8 +242,25 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, */ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); -MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, - uint32_t timeoutMs ); +/** + * @brief Loop to receive packets from the transport interface. + * + * @param[in] pContext Initialized and connected MQTT context. + * @param[in] timeoutMs Minimum time in milliseconds that the receive loop will + * run, unless an error occurs. + * + * @return #MQTTBadParameter if context is NULL; + * #MQTTRecvFailed if a network error occurs during reception; + * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; + * #MQTTBadResponse if an invalid packet is received; + * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before + * pContext->pingRespTimeoutMs milliseconds; + * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an + * invalid transition for the internal state machine; + * #MQTTSuccess on success. + */ +MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, + uint32_t timeoutMs ); uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ); diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index eaf96c94a5..7107b60d3f 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -57,6 +57,11 @@ */ #define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ +/** + * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. + */ +#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4UL ) + struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -103,7 +108,8 @@ typedef enum MQTTStatus MQTTServerRefused, /**< The server refused a CONNECT or SUBSCRIBE. */ MQTTNoDataAvailable, /**< No data available from the transport interface. */ MQTTIllegalState, /**< An illegal state in the state record. */ - MQTTStateCollision /**< A collision with an existing state record entry. */ + MQTTStateCollision, /**< A collision with an existing state record entry. */ + MQTTKeepAliveTimeout /**< Timeout while waiting for PINGRESP. */ } MQTTStatus_t; /** @@ -246,11 +252,6 @@ struct MQTTPacketInfo */ uint8_t type; - /** - * @brief Packet identifier of incoming MQTT packet. - */ - uint16_t packetIdentifier; - /** * @brief Remaining serialized data in the MQTT packet. */ @@ -512,7 +513,9 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket * where type, remaining length and packet identifier are stored. * * @return #MQTTSuccess on successful extraction of type and length, - * #MQTTBadResponse on failure and #MQTTNoDataAvailable if there is nothing to read. + * #MQTTRecvFailed on transport receive failure, + * #MQTTBadResponse if an invalid packet is read, and + * #MQTTNoDataAvailable if there is nothing to read. */ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, MQTTNetworkContext_t networkContext, diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 092fe81e74..8bdcccd7b5 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -41,6 +41,115 @@ static int32_t sendPacket( MQTTContext_t * pContext, const uint8_t * pBufferToSend, size_t bytesToSend ); +/** + * @brief Calculate the interval between two millisecond timestamps, including + * when the later value has overflowed. + * + * @note In C, the operands are promoted to signed integers in subtraction. + * Using this function avoids the need to cast the result of subtractions back + * to uint32_t. + * + * @param[in] later The later time stamp, in milliseconds. + * @param[in] start The earlier time stamp, in milliseconds. + * + * @return later - start. + */ +static uint32_t calculateElapsedTime( uint32_t later, + uint32_t start ); + +/** + * @brief Convert a byte indicating a publish ack type to an #MQTTPubAckType_t. + * + * @param[in] packetType First byte of fixed header. + * + * @return Type of ack. + */ +static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ); + +/** + * @brief Receive bytes into the network buffer, with a timeout. + * + * @param[in] pContext Initialized MQTT Context. + * @param[in] bytesToRecv Number of bytes to receive. + * @param[in] timeoutMs Time remaining to receive the packet. + * + * @return Number of bytes received, or negative number on network error. + */ +static int32_t recvExact( const MQTTContext_t * const pContext, + size_t bytesToRecv, + uint32_t timeoutMs ); + +/** + * @brief Discard a packet from the transport interface. + * + * @param[in] PContext MQTT Connection context. + * @param[in] remainingLength Remaining length of the packet to dump. + * @param[in] timeoutMs Time remaining to discard the packet. + * + * @return #MQTTRecvFailed or #MQTTNoDataAvailable. + */ +static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, + size_t remainingLength, + uint32_t timeoutMs ); + +/** + * @brief Receive a packet from the transport interface. + * + * @param[in] pContext MQTT Connection context. + * @param[in] incomingPacket packet struct with remaining length. + * @param[in] remainingTimeMs Time remaining to receive the packet. + * + * @return #MQTTSuccess or #MQTTRecvFailed. + */ +static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, + MQTTPacketInfo_t incomingPacket, + uint32_t remainingTimeMs ); + +/** + * @brief Send acks for received QoS 1/2 publishes. + * + * @param[in] pContext MQTT Connection context. + * @param[in] packetId packet ID of original PUBLISH. + * @param[in,out] pPublishState Current/updated publish state in record. + * + * @return #MQTTSuccess or #MQTTIllegalState. + */ +static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, + uint16_t packetId, + MQTTPublishState_t * pPublishState ); + +/** + * @brief Send a keep alive PINGREQ if the keep alive interval has elapsed. + * + * @param[in] pContext Initialized MQTT Context. + * + * @return #MQTTKeepAliveTimeout if a PINGRESP is not received in time, + * #MQTTSendFailed if the PINGREQ cannot be sent, or #MQTTSuccess. + */ +static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ); + +/** + * @brief Handle received MQTT PUBLISH packet. + * + * @param[in] pContext MQTT Connection context. + * @param[in] pIncomingPacket Incoming packet. + * + * @return MQTTSuccess, MQTTIllegalState or deserialization error. + */ +static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, + MQTTPacketInfo_t * pIncomingPacket ); + +/** + * @brief Handle received MQTT ack. + * + * @param[in] pContext MQTT Connection context. + * @param[in] pIncomingPacket Incoming packet. + * + * @return MQTTSuccess, MQTTIllegalState, or deserialization error. + */ +static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, + MQTTPacketInfo_t * pIncomingPacket ); + /** * @brief Validates parameters of #MQTT_Subscribe or #MQTT_Unsubscribe. * @@ -67,7 +176,7 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co * @return #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, +static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pPublishInfo, size_t headerSize ); @@ -90,18 +199,6 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, MQTTPacketInfo_t * const pIncomingPacket, bool * const pSessionPresent ); -/** - * @brief Calculate the interval between two timestamps, including when the - * later value has overflowed. - * - * @param[in] later The later time stamp. - * @param[in] start The earlier time stamp. - * - * @return later - start. - */ -static uint32_t calculateElapsedTime( uint32_t later, - uint32_t start ); - /*-----------------------------------------------------------*/ static int32_t sendPacket( MQTTContext_t * pContext, @@ -161,6 +258,467 @@ static int32_t sendPacket( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ +static uint32_t calculateElapsedTime( uint32_t later, + uint32_t start ) +{ + return later - start; +} + +/*-----------------------------------------------------------*/ + +static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ) +{ + MQTTPubAckType_t ackType = MQTTPuback; + + switch( packetType ) + { + case MQTT_PACKET_TYPE_PUBACK: + ackType = MQTTPuback; + break; + + case MQTT_PACKET_TYPE_PUBREC: + ackType = MQTTPubrec; + break; + + case MQTT_PACKET_TYPE_PUBREL: + ackType = MQTTPubrel; + break; + + case MQTT_PACKET_TYPE_PUBCOMP: + ackType = MQTTPubcomp; + break; + + default: + /* This function is only called after checking the type is one of + * the above four values. */ + assert( 0 ); + break; + } + + return ackType; +} + +/*-----------------------------------------------------------*/ + +static int32_t recvExact( const MQTTContext_t * const pContext, + size_t bytesToRecv, + uint32_t timeoutMs ) +{ + uint8_t * pIndex = NULL; + size_t bytesRemaining = bytesToRecv; + int32_t totalBytesRecvd = 0, bytesRecvd; + uint32_t entryTimeMs = 0U; + MQTTTransportRecvFunc_t recvFunc = NULL; + MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; + bool receiveError = false; + + assert( pContext != NULL ); + assert( bytesToRecv <= pContext->networkBuffer.size ); + pIndex = pContext->networkBuffer.pBuffer; + recvFunc = pContext->transportInterface.recv; + getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); + + while( ( bytesRemaining > 0U ) && ( receiveError == false ) ) + { + bytesRecvd = recvFunc( pContext->transportInterface.networkContext, + pIndex, + bytesRemaining ); + + if( bytesRecvd >= 0 ) + { + bytesRemaining -= ( size_t ) bytesRecvd; + totalBytesRecvd += ( int32_t ) bytesRecvd; + pIndex += bytesRecvd; + } + else + { + LogErrorWithArgs( "Network error while receiving packet: ReturnCode=%d", + bytesRecvd ); + totalBytesRecvd = bytesRecvd; + receiveError = true; + } + + if( ( bytesRemaining > 0U ) && + ( calculateElapsedTime( getTimeStampMs(), entryTimeMs ) > timeoutMs ) ) + { + LogError( "Time expired while receiving packet." ); + receiveError = true; + } + } + + return totalBytesRecvd; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, + size_t remainingLength, + uint32_t timeoutMs ) +{ + MQTTStatus_t status = MQTTRecvFailed; + int32_t bytesReceived = 0; + size_t bytesToReceive = 0U; + uint32_t totalBytesReceived = 0U, entryTimeMs, elapsedTimeMs; + uint32_t remainingTimeMs = timeoutMs; + MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; + bool receiveError = false; + + assert( pContext != NULL ); + bytesToReceive = pContext->networkBuffer.size; + getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); + + while( ( totalBytesReceived < remainingLength ) && ( receiveError == false ) ) + { + if( ( remainingLength - totalBytesReceived ) < bytesToReceive ) + { + bytesToReceive = remainingLength - totalBytesReceived; + } + + bytesReceived = recvExact( pContext, bytesToReceive, remainingTimeMs ); + + if( bytesReceived != ( int32_t ) bytesToReceive ) + { + LogErrorWithArgs( "Receive error while discarding packet." + "ReceivedBytes=%d, ExpectedBytes=%u.", + bytesReceived, + bytesToReceive ); + receiveError = true; + } + else + { + totalBytesReceived += ( uint32_t ) bytesReceived; + /* Update remaining time and check for timeout. */ + elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + + if( elapsedTimeMs < timeoutMs ) + { + remainingTimeMs = timeoutMs - elapsedTimeMs; + } + else + { + LogError( "Time expired while discarding packet." ); + receiveError = true; + } + } + } + + if( totalBytesReceived == remainingLength ) + { + LogErrorWithArgs( "Dumped packet. DumpedBytes=%d.", + totalBytesReceived ); + /* Packet dumped, so no data is available. */ + status = MQTTNoDataAvailable; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, + MQTTPacketInfo_t incomingPacket, + uint32_t remainingTimeMs ) +{ + MQTTStatus_t status = MQTTSuccess; + int32_t bytesReceived = 0; + size_t bytesToReceive = 0U; + + assert( pContext != NULL ); + + if( incomingPacket.remainingLength > pContext->networkBuffer.size ) + { + LogErrorWithArgs( "Incoming packet length %u exceeds network buffer size %u." + "Incoming packet will be dumped.", + incomingPacket.remainingLength, + pContext->networkBuffer ); + status = discardPacket( pContext, + incomingPacket.remainingLength, + remainingTimeMs ); + } + else + { + bytesToReceive = incomingPacket.remainingLength; + bytesReceived = recvExact( pContext, bytesToReceive, remainingTimeMs ); + + if( bytesReceived == ( int32_t ) bytesToReceive ) + { + /* Receive successful, bytesReceived == bytesToReceive. */ + LogInfoWithArgs( "Packet received. ReceivedBytes=%d.", + bytesReceived ); + } + else + { + LogErrorWithArgs( "Packet reception failed. ReceivedBytes=%d, " + "ExpectedBytes=%u.", + bytesReceived, + bytesToReceive ); + status = MQTTRecvFailed; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, + uint16_t packetId, + MQTTPublishState_t * pPublishState ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTPublishState_t newState = MQTTStateNull; + int32_t bytesSent = 0; + uint8_t packetTypeByte = 0U; + MQTTPubAckType_t packetType; + + assert( pContext != NULL ); + assert( pPublishState != NULL ); + + switch( *pPublishState ) + { + case MQTTPubAckSend: + packetTypeByte = MQTT_PACKET_TYPE_PUBACK; + packetType = MQTTPuback; + break; + + case MQTTPubRecSend: + packetTypeByte = MQTT_PACKET_TYPE_PUBREC; + packetType = MQTTPubrec; + break; + + case MQTTPubRelSend: + packetTypeByte = MQTT_PACKET_TYPE_PUBREL; + packetType = MQTTPubrel; + break; + + case MQTTPubCompSend: + packetTypeByte = MQTT_PACKET_TYPE_PUBCOMP; + packetType = MQTTPubcomp; + break; + + default: + /* Take no action for states that do not require sending an ack. */ + break; + } + + if( packetTypeByte != 0U ) + { + status = MQTT_SerializeAck( &( pContext->networkBuffer ), + packetTypeByte, + packetId ); + + if( status == MQTTSuccess ) + { + bytesSent = sendPacket( pContext, + pContext->networkBuffer.pBuffer, + MQTT_PUBLISH_ACK_PACKET_SIZE ); + } + + if( bytesSent == ( int32_t ) MQTT_PUBLISH_ACK_PACKET_SIZE ) + { + pContext->controlPacketSent = true; + newState = MQTT_UpdateStateAck( pContext, + packetId, + packetType, + MQTT_SEND ); + + if( newState == MQTTStateNull ) + { + LogErrorWithArgs( "Failed to update state of publish %u.", + packetId ); + status = MQTTIllegalState; + } + } + else + { + LogErrorWithArgs( "Failed to send ACK packet: PacketType=%02x, " + "SentBytes=%d, " + "PacketSize=%u", + packetTypeByte, + bytesSent, + MQTT_PUBLISH_ACK_PACKET_SIZE ); + status = MQTTSendFailed; + } + } + + if( ( status == MQTTSuccess ) && ( newState != MQTTStateNull ) ) + { + *pPublishState = newState; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ) +{ + MQTTStatus_t status = MQTTSuccess; + uint32_t now = 0U, keepAliveMs = 0U; + + assert( pContext != NULL ); + now = pContext->callbacks.getTime(); + keepAliveMs = 1000U * ( uint32_t ) pContext->keepAliveIntervalSec; + + /* If keep alive interval is 0, it is disabled. */ + if( ( keepAliveMs != 0U ) && + ( calculateElapsedTime( now, pContext->lastPacketTime ) > keepAliveMs ) ) + { + if( pContext->waitingForPingResp ) + { + /* Has time expired? */ + if( calculateElapsedTime( now, pContext->pingReqSendTimeMs ) > + pContext->pingRespTimeoutMs ) + { + status = MQTTKeepAliveTimeout; + } + } + else + { + status = MQTT_Ping( pContext ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, + MQTTPacketInfo_t * pIncomingPacket ) +{ + MQTTStatus_t status = MQTTBadParameter; + MQTTPublishState_t publishRecordState = MQTTStateNull; + uint16_t packetIdentifier; + MQTTPublishInfo_t publishInfo; + + assert( pContext != NULL ); + assert( pIncomingPacket != NULL ); + + status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo ); + LogInfoWithArgs( "De-serialized incoming PUBLISH packet: DeserializerResult=%d", status ); + + if( status == MQTTSuccess ) + { + publishRecordState = MQTT_UpdateStatePublish( pContext, + packetIdentifier, + MQTT_RECEIVE, + publishInfo.qos ); + LogInfoWithArgs( "State record updated. New state=%d.", + publishRecordState ); + + /* Send PUBACK or PUBREC if necessary. */ + status = sendPublishAcks( pContext, + packetIdentifier, + &publishRecordState ); + } + + if( status == MQTTSuccess ) + { + /* Provide publish info to application. */ + pContext->callbacks.appCallback( pContext, + pIncomingPacket, + packetIdentifier, + &publishInfo ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, + MQTTPacketInfo_t * pIncomingPacket ) +{ + MQTTStatus_t status = MQTTBadResponse; + MQTTPublishState_t publishRecordState = MQTTStateNull; + uint16_t packetIdentifier; + /* Need a dummy variable for MQTT_DeserializeAck(). */ + bool sessionPresent = false; + MQTTPubAckType_t ackType; + + assert( pContext != NULL ); + assert( pIncomingPacket != NULL ); + + switch( pIncomingPacket->type ) + { + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBREC: + case MQTT_PACKET_TYPE_PUBREL: + case MQTT_PACKET_TYPE_PUBCOMP: + ackType = getAckFromPacketType( pIncomingPacket->type ); + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); + LogInfoWithArgs( "Ack packet deserialized with result: %d.", status ); + + if( status == MQTTSuccess ) + { + publishRecordState = MQTT_UpdateStateAck( pContext, + packetIdentifier, + ackType, + MQTT_RECEIVE ); + LogInfoWithArgs( "State record updated. New state=%d.", + publishRecordState ); + + /* Send PUBREL or PUBCOMP if necessary. */ + status = sendPublishAcks( pContext, + packetIdentifier, + &publishRecordState ); + + if( status == MQTTSuccess ) + { + pContext->callbacks.appCallback( pContext, + pIncomingPacket, + packetIdentifier, + NULL ); + } + } + + break; + + case MQTT_PACKET_TYPE_PINGRESP: + pContext->waitingForPingResp = false; + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); + + if( status == MQTTSuccess ) + { + pContext->callbacks.appCallback( pContext, + pIncomingPacket, + packetIdentifier, + NULL ); + } + + break; + + case MQTT_PACKET_TYPE_SUBACK: + case MQTT_PACKET_TYPE_UNSUBACK: + /* Deserialize and give these to the app provided callback. */ + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); + + if( status == MQTTSuccess ) + { + pContext->callbacks.appCallback( pContext, + pIncomingPacket, + packetIdentifier, + NULL ); + } + + break; + + default: + /* Bad response from the server. */ + LogErrorWithArgs( "Unexpected packet type from server: PacketType=%02x.", + pIncomingPacket->type ); + status = MQTTBadResponse; + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ + static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * const pContext, const MQTTSubscribeInfo_t * const pSubscriptionList, size_t subscriptionCount, @@ -197,7 +755,7 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co /*-----------------------------------------------------------*/ -static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, +static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, const MQTTPublishInfo_t * const pPublishInfo, size_t headerSize ) { @@ -245,23 +803,6 @@ static MQTTStatus_t sendPublish( const MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static uint32_t calculateElapsedTime( uint32_t later, - uint32_t start ) -{ - return later - start; -} - -/*-----------------------------------------------------------*/ - -static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, - MQTTPacketInfo_t incomingPacket, - uint32_t remainingTimeMs ) -{ - return MQTTSuccess; -} - -/*-----------------------------------------------------------*/ - static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, uint32_t timeoutMs, MQTTPacketInfo_t * const incomingPacket, @@ -655,6 +1196,8 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) } else { + pContext->pingReqSendTimeMs = pContext->lastPacketTime; + pContext->waitingForPingResp = true; LogDebugWithArgs( "Sent %d bytes of PINGREQ packet.", bytesSent ); } @@ -781,10 +1324,96 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Process( MQTTContext_t * const pContext, - uint32_t timeoutMs ) +MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, + uint32_t timeoutMs ) { - return MQTTSuccess; + MQTTStatus_t status = MQTTBadParameter; + MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; + uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; + MQTTPacketInfo_t incomingPacket; + + if( pContext != NULL ) + { + getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); + status = MQTTSuccess; + } + else + { + LogError( "MQTT Context cannot be NULL." ); + } + + while( status == MQTTSuccess ) + { + status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, + pContext->transportInterface.networkContext, + &incomingPacket ); + + if( status == MQTTNoDataAvailable ) + { + /* Assign status so an error can be bubbled up to application, + * but reset it on success. */ + status = handleKeepAlive( pContext ); + + if( status == MQTTSuccess ) + { + /* Reset the status to indicate that we should not try to read + * a packet from the transport interface. */ + status = MQTTNoDataAvailable; + } + } + else if( status != MQTTSuccess ) + { + LogErrorWithArgs( "Receiving incoming packet length failed. Status=%d", + status ); + } + else + { + /* Receive packet. Remaining time is recalculated at the end of the loop. */ + status = receivePacket( pContext, incomingPacket, remainingTimeMs ); + } + + /* Handle received packet. If no data was read then this will not execute. */ + if( status == MQTTSuccess ) + { + incomingPacket.pRemainingData = pContext->networkBuffer.pBuffer; + + /* PUBLISH packets allow flags in the lower four bits. For other + * packet types, they are reserved. */ + if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + status = handleIncomingPublish( pContext, &incomingPacket ); + } + else + { + status = handleIncomingAck( pContext, &incomingPacket ); + } + } + + if( status == MQTTNoDataAvailable ) + { + /* No data available is not an error. Reset to MQTTSuccess so the + * return code will indicate success. */ + status = MQTTSuccess; + } + + if( status != MQTTSuccess ) + { + LogErrorWithArgs( "Exiting receive loop. Error status=%d", status ); + } + + /* Recalculate remaining time and check if loop should exit. */ + elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + + if( elapsedTimeMs > timeoutMs ) + { + break; + } + + remainingTimeMs = timeoutMs - elapsedTimeMs; + } + + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 9563817507..febe408281 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -74,11 +74,6 @@ #define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ #define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ -/** - * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. - */ -#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4U ) - /* * UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP always have a remaining length * of 2. @@ -2024,10 +2019,14 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu status = MQTTBadResponse; } } - else + else if( bytesReceived == 0 ) { status = MQTTNoDataAvailable; } + else + { + status = MQTTRecvFailed; + } return status; } From bd3191146a9cea8e802114ca3ff0fed6aa91bd36 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 14 May 2020 18:18:08 -0700 Subject: [PATCH 506/844] Fix build warning for incorrect type (#940) --- libraries/standard/mqtt/src/mqtt.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 8bdcccd7b5..57ee29bd2e 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -289,6 +289,7 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ) break; default: + /* This function is only called after checking the type is one of * the above four values. */ assert( 0 ); @@ -805,7 +806,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, uint32_t timeoutMs, - MQTTPacketInfo_t * const incomingPacket, + MQTTPacketInfo_t * const pIncomingPacket, bool * const pSessionPresent ) { MQTTStatus_t status = MQTTSuccess; @@ -813,7 +814,7 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, uint32_t entryTimeMs = 0U, remainingTimeMs = 0U, timeTakenMs = 0U; assert( pContext != NULL ); - assert( incomingPacket != NULL ); + assert( pIncomingPacket != NULL ); assert( pContext->callbacks.getTime != NULL ); getTimeStamp = pContext->callbacks.getTime; @@ -828,7 +829,7 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, * receive of packet type and length. */ status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, pContext->transportInterface.networkContext, - incomingPacket ); + pIncomingPacket ); /* Loop until there is data to read or if the timeout has not expired. */ } while( ( status == MQTTNoDataAvailable ) && @@ -850,17 +851,17 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, * Attempt to read once even if the timeout has expired at this point. * Invoking receivePacket with remainingTime as 0 would attempt to * recv from network once.*/ - if( incomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) + if( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) { status = receivePacket( pContext, - *incomingPacket, + *pIncomingPacket, remainingTimeMs ); } else { LogErrorWithArgs( "Incorrect packet type %X received while expecting" " CONNACK(%X).", - incomingPacket->type, + pIncomingPacket->type, MQTT_PACKET_TYPE_CONNACK ); status = MQTTBadResponse; } @@ -869,10 +870,10 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { /* Update the packet info pointer to the buffer read. */ - incomingPacket->pRemainingData = pContext->networkBuffer.pBuffer; + pIncomingPacket->pRemainingData = pContext->networkBuffer.pBuffer; /* Deserialize CONNACK. */ - status = MQTT_DeserializeAck( &incomingPacket, NULL, pSessionPresent ); + status = MQTT_DeserializeAck( pIncomingPacket, NULL, pSessionPresent ); } if( status != MQTTSuccess ) From d598e60db5bdf6981312c9a94394ce433e5c0dfb Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 18 May 2020 10:46:25 -0700 Subject: [PATCH 507/844] Add parameter validation to MQTT_Init (#943) --- libraries/standard/mqtt/include/mqtt.h | 11 ++++--- libraries/standard/mqtt/src/mqtt.c | 43 +++++++++++++++++++------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 7e3fce0a77..e1b86c20ae 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -129,11 +129,14 @@ struct MQTTContext * @brief param[in] pTransportInterface The transport interface to use with the context. * @brief param[in] pCallbacks Callbacks to use with the context. * @brief param[in] pNetworkBuffer Network buffer provided for the context. + * + * @return #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. */ -void MQTT_Init( MQTTContext_t * const pContext, - const MQTTTransportInterface_t * const pTransportInterface, - const MQTTApplicationCallbacks_t * const pCallbacks, - const MQTTFixedBuffer_t * const pNetworkBuffer ); +MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, + const MQTTTransportInterface_t * const pTransportInterface, + const MQTTApplicationCallbacks_t * const pCallbacks, + const MQTTFixedBuffer_t * const pNetworkBuffer ); /** * @brief Establish a MQTT session. diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 57ee29bd2e..d610784581 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -891,20 +891,41 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -void MQTT_Init( MQTTContext_t * const pContext, - const MQTTTransportInterface_t * const pTransportInterface, - const MQTTApplicationCallbacks_t * const pCallbacks, - const MQTTFixedBuffer_t * const pNetworkBuffer ) +MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, + const MQTTTransportInterface_t * const pTransportInterface, + const MQTTApplicationCallbacks_t * const pCallbacks, + const MQTTFixedBuffer_t * const pNetworkBuffer ) { - ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) ); + MQTTStatus_t status = MQTTSuccess; - pContext->connectStatus = MQTTNotConnected; - pContext->transportInterface = *pTransportInterface; - pContext->callbacks = *pCallbacks; - pContext->networkBuffer = *pNetworkBuffer; + /* Validate arguments. */ + if( ( pContext == NULL ) || ( pTransportInterface == NULL ) || + ( pCallbacks == NULL ) || ( pNetworkBuffer == NULL ) ) + { + LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " + "pTransportInterface=%p " + "pCallbacks=%p " + "pNetworkBuffer=%p.", + pContext, + pTransportInterface, + pCallbacks, + pNetworkBuffer ); + status = MQTTBadParameter; + } + else + { + ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) ); + + pContext->connectStatus = MQTTNotConnected; + pContext->transportInterface = *pTransportInterface; + pContext->callbacks = *pCallbacks; + pContext->networkBuffer = *pNetworkBuffer; - /* Zero is not a valid packet ID per MQTT spec. Start from 1. */ - pContext->nextPacketId = 1; + /* Zero is not a valid packet ID per MQTT spec. Start from 1. */ + pContext->nextPacketId = 1; + } + + return status; } /*-----------------------------------------------------------*/ From 9e267f46c7eec73a99612f0e1c2aeee3fbdff54b Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 18 May 2020 11:02:57 -0700 Subject: [PATCH 508/844] Http/migrate add range header tests to unity (#939) --- libraries/standard/http/utest/http_utest.c | 393 ++++++++++++++++++++- tools/cmock/create_test.cmake | 1 + 2 files changed, 392 insertions(+), 2 deletions(-) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 6171f83c06..1ca40678b2 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -1,4 +1,5 @@ #include +#include #include "unity.h" @@ -9,6 +10,26 @@ #include "private/http_client_internal.h" #include "private/http_client_parse.h" +/* Default size for request buffer. */ +#define HTTP_TEST_BUFFER_SIZE ( 100 ) + +/* Headers data with "\r\n\r\n" terminator to be pre-populated in buffer before + * call to AddRangeHeader(). */ +#define PREEXISTING_HEADER_DATA "POST / HTTP/1.1 \r\nAuthorization: None\r\n\r\n" +#define PREEXISTING_HEADER_DATA_LEN ( sizeof( PREEXISTING_HEADER_DATA ) - 1 ) + +/* Headers data without "\r\n\r\n" terminator to be pre-populated in buffer before + * call to AddRangeHeader(). */ +#define PREEXISTING_REQUEST_LINE "POST / HTTP/1.1 \r\n" +#define PREEXISTING_REQUEST_LINE_LEN ( sizeof( PREEXISTING_REQUEST_LINE ) - 1 ) + +/* Type to store expected headers data. */ +typedef struct _headers +{ + uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ]; + size_t dataLen; +} _headers_t; + #define HTTP_METHOD_GET_LEN ( sizeof( HTTP_METHOD_GET ) - 1 ) #define HTTP_TEST_REQUEST_PATH "/robots.txt" #define HTTP_TEST_REQUEST_PATH_LEN ( sizeof( HTTP_TEST_REQUEST_PATH ) - 1 ) @@ -59,8 +80,80 @@ #define HTTP_TEST_BUFFER_LEN 1024 +/* File-scoped Global variables */ +static HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; +static uint8_t testBuffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; +static HTTPRequestHeaders_t testHeaders = { 0 }; +static _headers_t expectedHeaders = { 0 }; +static int testRangeStart = 0; +static int testRangeEnd = 0; static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LEN ] = { 0 }; +/* ============================ Helper Functions ============================== */ + +/** + * @brief Fills the test input buffer and expectation buffers with pre-existing data + * before calling the API function under test. + */ +static void setupBuffersWithPreexistingHeader( HTTPRequestHeaders_t * testRequestHeaders, + uint8_t * testBuffer, + size_t bufferSize, + _headers_t * expectedHeaders, + const char * preexistingData ) +{ + size_t dataLen = strlen( preexistingData ); + + testRequestHeaders->pBuffer = testBuffer; + testRequestHeaders->bufferLen = bufferSize; + int numBytes = snprintf( ( char * ) testRequestHeaders->pBuffer, + bufferSize, + "%s", + preexistingData ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( bufferSize, ( size_t ) numBytes ); + testRequestHeaders->headersLen = dataLen; + + /* Fill the same data in the expected buffer as HTTPClient_AddRangeHeaders() + * is not expected to change it. */ + memcpy( expectedHeaders->buffer, testRequestHeaders->pBuffer, + testRequestHeaders->headersLen ); + expectedHeaders->dataLen = testRequestHeaders->headersLen; +} + +/** + * @brief Common utility for adding the expected range string for a AddRangeRequest test case + * in the expectation buffer. + */ +static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, + const char * expectedRange, + bool terminatorExists ) +{ + size_t expectedRangeLen = RANGE_REQUEST_HEADER_FIELD_LEN + + HTTP_HEADER_FIELD_SEPARATOR_LEN + + RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + + strlen( expectedRange ) + + 2 * HTTP_HEADER_LINE_SEPARATOR_LEN; + + int numBytes = + snprintf( ( char * ) expectedHeaders->buffer + + expectedHeaders->dataLen - + ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ), + sizeof( expectedHeaders->buffer ), + "%s%s%s%s\r\n\r\n", + RANGE_REQUEST_HEADER_FIELD, + HTTP_HEADER_FIELD_SEPARATOR, + RANGE_REQUEST_HEADER_VALUE_PREFIX, + expectedRange ); + + /* Make sure that the Range request was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders->buffer ), ( size_t ) numBytes ); + + expectedHeaders->dataLen += expectedRangeLen - + ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ); +} + /* ============================ UNITY FIXTURES ============================== */ void setUp( void ) { @@ -69,6 +162,10 @@ void setUp( void ) /* called before each testcase */ void tearDown( void ) { + retCode = HTTP_NOT_SUPPORTED; + memset( &testHeaders, 0, sizeof( testHeaders ) ); + memset( testBuffer, 0, sizeof( testBuffer ) ); + memset( &expectedHeaders, 0, sizeof( expectedHeaders ) ); } /* called at the beginning of the whole suite */ @@ -100,7 +197,6 @@ static void setupRequestInfo( HTTPRequestInfo_t * pRequestInfo ) } /** - * @brief Initialize pRequestHeaders based on parameters. * * @param[in] pRequestHeaders Request header buffer information. * @param[in] bufferLen Size of the buffer. @@ -248,4 +344,297 @@ void test_Http_InitializeRequestHeaders_insufficient_memory() HTTP_TEST_REQUEST_LINE_LEN ) != 0 ); } -/* ========================================================================== */ +/* ============== Testing HTTPClient_AddRangeHeader ================== */ + +/** + * @brief Testing with invalid parameter inputs. + */ +void test_Http_AddRangeHeader_Invalid_Params( void ) +{ + /* Request header parameter is NULL. */ + tearDown(); + retCode = HTTPClient_AddRangeHeader( NULL, + 0 /* rangeStart */, + 0 /* rageEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Underlying buffer is NULL in request headers. */ + tearDown(); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 0 /* rangeStart */, + 0 /* rageEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Request Header Size is zero. */ + tearDown(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + /* The input buffer size is zero!. */ + testHeaders.bufferLen = 0u; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 0 /* rangeStart */, + 10 /* rageEnd */ ); + TEST_ASSERT_EQUAL( retCode, HTTP_INSUFFICIENT_MEMORY ); + + /* Test incorrect combinations of rangeStart and rangeEnd. */ + + /* rangeStart > rangeEnd */ + tearDown(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 10 /* rangeStart */, + 5 /* rageEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* rangeStart is negative but rangeStart is non-End of File. */ + tearDown(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + -10 /* rangeStart */, + HTTP_RANGE_REQUEST_END_OF_FILE + 1 /* rageEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + tearDown(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + -50 /* rangeStart */, + -10 /* rageEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); +} + +/** + * @brief Test Insufficient memory failure when the buffer has one less byte than required. + */ +void test_Http_AddRangeHeader_Insufficient_Memory( void ) +{ + setupBuffersWithPreexistingHeader( &testHeaders, + testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + size_t preHeadersLen = testHeaders.headersLen; + testRangeStart = 5; + testRangeEnd = 10; + + /* Update the expected header with the complete the range request header + * to determine the total required size of the buffer. */ + addRangeToExpectedHeaders( &expectedHeaders, + "5-10" /*expected range*/, + true ); + + /* Change the input headers buffer size to be one byte short of the required + * size to add Range Request header. */ + testHeaders.bufferLen = expectedHeaders.dataLen - 1; + + /* As the call to the API function is expected to fail, we need to store a + * local copy of the input headers buffer to verify that the data has not changed + * after the API call returns. Thus, overwrite the expected headers buffer with the + * copy of the complete input headers buffer to use for verification later. */ + TEST_ASSERT_GREATER_OR_EQUAL( testHeaders.bufferLen, sizeof( expectedHeaders.buffer ) ); + memcpy( expectedHeaders.buffer, testHeaders.pBuffer, testHeaders.bufferLen ); + + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, retCode ); + /* Verify the headers input parameter is unaltered. */ + TEST_ASSERT_EQUAL( preHeadersLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen - 1, testHeaders.bufferLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); +} + +/** + * @brief Test addition of range header in a buffer not containing any header. + */ +void test_Http_AddRangeHeader_Without_Trailing_Terminator( void ) +{ + /* Headers buffer does not contain data with trailing "\r\n\r\n". */ + + /* Range specification of the form [rangeStart, rangeEnd]. */ + /* Test with 0 as the range values */ + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_REQUEST_LINE ); + testRangeStart = 0; + testRangeEnd = 0; + addRangeToExpectedHeaders( &expectedHeaders, + "0-0" /*expected range*/, + false ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); +} + +/** + * @brief Test for Range specification of the form [rangeStart, rangeEnd]. + */ +void test_Http_AddRangeHeader_RangeType_File_SubRange( void ) +{ + /* Headers buffer contains header data ending with "\r\n\r\n". */ + + /* Test with 0 as the range values */ + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 0; + testRangeEnd = 0; + addRangeToExpectedHeaders( &expectedHeaders, + "0-0" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); + + tearDown(); + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 10; + testRangeEnd = 100; + addRangeToExpectedHeaders( &expectedHeaders, + "10-100" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); +} + +/** + * @brief Test for adding request header for the [0, eof) range. + */ +void test_Http_AddRangeHeader_RangeType_Entire_File( void ) +{ + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 0; + testRangeEnd = HTTP_RANGE_REQUEST_END_OF_FILE; + addRangeToExpectedHeaders( &expectedHeaders, + "0-" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); +} + +/** + * @brief Test for Range specification of the form [rangeStart, eof). + */ +void test_Http_AddRangeHeader_RangeType_All_Bytes_From_RangeStart( void ) +{ + /* Range specification of the form [rangeStart,) + * i.e. for all bytes >= rangeStart. */ + tearDown(); + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = 100; + testRangeEnd = HTTP_RANGE_REQUEST_END_OF_FILE; + addRangeToExpectedHeaders( &expectedHeaders, + "100-" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); +} + +/** + * @brief Test for adding range request for the last N bytes. + */ +void test_Http_AddRangeHeader_RangeType_LastNBytes( void ) +{ + /* Range specification for the last N bytes. */ + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = -50; + testRangeEnd = HTTP_RANGE_REQUEST_END_OF_FILE; + addRangeToExpectedHeaders( &expectedHeaders, + "-50" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); +} + +/** + * @brief Test addition of range request header with large integers. + */ +void test_Http_AddRangeHeader_With_Max_INT32_Range_Values( void ) +{ + /* Test with LARGE range values. */ + setupBuffersWithPreexistingHeader( &testHeaders, testBuffer, + sizeof( testBuffer ), + &expectedHeaders, + PREEXISTING_HEADER_DATA ); + testRangeStart = INT32_MAX; + testRangeEnd = INT32_MAX; + addRangeToExpectedHeaders( &expectedHeaders, + "2147483647-2147483647" /*expected range*/, + true ); + retCode = HTTPClient_AddRangeHeader( &testHeaders, + testRangeStart, + testRangeEnd ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + /* Verify the the Range Request header data. */ + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, testHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + testHeaders.pBuffer, + testHeaders.bufferLen ); + /* Verify that the bufferLen data was not tampered with. */ + TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); +} diff --git a/tools/cmock/create_test.cmake b/tools/cmock/create_test.cmake index 30fee4abc8..11cea704ed 100644 --- a/tools/cmock/create_test.cmake +++ b/tools/cmock/create_test.cmake @@ -19,6 +19,7 @@ function(create_test test_name ) add_executable(${test_name} ${test_src} ${test_name}_runner.c) set_target_properties(${test_name} PROPERTIES + COMPILE_FLAG "-O0 -ggdb" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests" INSTALL_RPATH_USE_LINK_PATH TRUE LINK_FLAGS " \ From 38b88fb684dab95c87d3fe5ddbfbed8643af37bc Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Mon, 18 May 2020 14:34:29 -0700 Subject: [PATCH 509/844] Add MQTT_Init unit tests using Unity Framework (#941) * Coverage for MQTT_Init * Remove mock for mqtt_lightweight * Change to expected, actual order for TEST_ASSERT * Provide updated comments for unity fixtures * Add validation for context.nextPacketId * Add tests for null parameters * Add missing sc; * Remove warning for suiteTearDown * Do not return numFailures * Return correct error code * return numFailures * Add docs --- libraries/standard/mqtt/utest/mqtt_utest.c | 65 ++++++++++++++++++---- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index dd38e79fa2..bc854f2585 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2,36 +2,81 @@ #include "unity.h" -#include "mock_mqtt_lightweight.h" - /* Include paths for public enums, structures, and macros. */ #include "mqtt.h" +/** + * @brief A valid starting packet ID per MQTT spec. Start from 1. + */ +#define MQTT_NEXT_PACKET_ID_START ( 1 ) + /* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ void setUp( void ) { } -/* called before each testcase */ +/* Called after each test method. */ void tearDown( void ) { } -/* called at the beginning of the whole suite */ +/* Called at the beginning of the whole suite. */ void suiteSetUp() { } -/* called at the end of the whole suite */ +/* Called at the end of the whole suite. */ int suiteTearDown( int numFailures ) { + return numFailures; } -/* ============================ Testing MQTT_Connect ====================== */ -void test_Mqtt_connect_packet_size_gt_max( void ) +/* ============================ Testing MQTT_Init ========================= */ + +/** + * @brief Test that MQTT_Init is able to update the context object correctly. + */ +void test_MQTT_Init_Happy_path( void ) { - /* This mocked method will be called inside MQTT_Connect(...). */ - MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); + TEST_ASSERT_EQUAL( MQTT_NEXT_PACKET_ID_START, context.nextPacketId ); + /* These Unity assertions take pointers and compare their contents. */ + TEST_ASSERT_EQUAL_MEMORY( &transport, &context.transportInterface, sizeof( transport ) ); + TEST_ASSERT_EQUAL_MEMORY( &callbacks, &context.callbacks, sizeof( callbacks ) ); + TEST_ASSERT_EQUAL_MEMORY( &networkBuffer, &context.networkBuffer, sizeof( networkBuffer ) ); +} + +/** + * @brief Test that any NULL parameter causes MQTT_Init to return MQTTBadParameter. + */ +void test_MQTT_Init_Invalid_params( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + /* Check that MQTTBadParameter is returned if any NULL parameters are passed. */ + mqttStatus = MQTT_Init( NULL, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + mqttStatus = MQTT_Init( &context, NULL, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + mqttStatus = MQTT_Init( &context, &transport, NULL, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - TEST_ASSERT_EQUAL( MQTT_Connect( NULL, NULL, NULL, 0U, NULL ), MQTTBadParameter ); + mqttStatus = MQTT_Init( &context, &transport, &callbacks, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); } From b0ef91072ead7ac28b6d243e42ec8fbfed3e2465 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 19 May 2020 14:39:28 -0700 Subject: [PATCH 510/844] Http/migrate HTTPClient_ReadHeader unit tests to Unity (#944) --- libraries/standard/http/httpFilePaths.cmake | 4 + libraries/standard/http/utest/CMakeLists.txt | 2 +- libraries/standard/http/utest/http_utest.c | 397 ++++++++++++++++++- 3 files changed, 382 insertions(+), 21 deletions(-) diff --git a/libraries/standard/http/httpFilePaths.cmake b/libraries/standard/http/httpFilePaths.cmake index 446e000364..89cf02e709 100644 --- a/libraries/standard/http/httpFilePaths.cmake +++ b/libraries/standard/http/httpFilePaths.cmake @@ -20,3 +20,7 @@ set( HTTP_INCLUDE_PRIVATE_DIRS ${MODULES_DIR}/standard/http/src ${MODULES_DIR}/standard/http/third_party ) +# HTTP library Include directories for Tests. +set( HTTP_TEST_INCLUDE_DIRS + ${MODULES_DIR}/standard/http/third_party/http_parser ) + diff --git a/libraries/standard/http/utest/CMakeLists.txt b/libraries/standard/http/utest/CMakeLists.txt index f3a94363a4..c877563641 100644 --- a/libraries/standard/http/utest/CMakeLists.txt +++ b/libraries/standard/http/utest/CMakeLists.txt @@ -42,7 +42,7 @@ list(APPEND test_include_directories . ${HTTP_INCLUDE_PUBLIC_DIRS} ${HTTP_INCLUDE_PRIVATE_DIRS} - ${HTTP_TEST_INCLUDE_PRIVATE_DIRS} + ${HTTP_TEST_INCLUDE_DIRS} "${ROOT_DIR}/platform/include" ) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 1ca40678b2..7a32ab25b0 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -1,4 +1,3 @@ -#include #include #include "unity.h" @@ -10,6 +9,9 @@ #include "private/http_client_internal.h" #include "private/http_client_parse.h" +/* Include mock implementation of http-parser dependency. */ +#include "mock_http_parser.h" + /* Default size for request buffer. */ #define HTTP_TEST_BUFFER_SIZE ( 100 ) @@ -80,6 +82,16 @@ typedef struct _headers #define HTTP_TEST_BUFFER_LEN 1024 +/* Template HTTP response for testing HTTPClient_ReadHeader API. */ +static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" + "test-header0: test-value0\r\n" + "test-header1: test-value1\r\n" + "test-header2: test-value2\r\n" + "\r\n"; + +#define HEADER_IN_BUFFER "test-header1" +#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" + /* File-scoped Global variables */ static HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; static uint8_t testBuffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; @@ -88,9 +100,112 @@ static _headers_t expectedHeaders = { 0 }; static int testRangeStart = 0; static int testRangeEnd = 0; static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LEN ] = { 0 }; +static const uint8_t * pValueLoc = NULL; +static size_t valueLen = 0u; +static HTTPResponse_t testResponse = { 0 }; +static const size_t headerFieldInRespLoc = 44; +static const size_t headerFieldInRespLen = sizeof( "test-header1" ) - 1u; +static const size_t headerValInRespLoc = 58; +static const size_t headerValInRespLen = sizeof( "test-value1" ) - 1u; +static http_parser * pCapturedParser = NULL; +static http_parser_settings * pCapturedSettings = NULL; +static const char * pExpectedBuffer = NULL; +static size_t expectedBufferSize = 0u; +static uint8_t invokeHeaderFieldCallback = 0u; +static const char * pFieldLocToReturn = NULL; +static size_t fieldLenToReturn = 0u; +static uint8_t invokeHeaderValueCallback = 0u; +static const char * pValueLocToReturn = NULL; +static size_t valueLenToReturn = 0u; +static int expectedValCbRetVal = 0; +static uint8_t invokeHeaderCompleteCallback = 0u; +static unsigned int parserErrNo = 0; /* ============================ Helper Functions ============================== */ +/** + * @brief Callback that is passed to the mock of http_parse_init function + * to set test expectations on input arguments sent by the HTTP API function under + * test. + */ +void parserInitExpectationCb( http_parser * parser, + enum http_parser_type type, + int cmock_num_calls ) +{ + /* Disable unused parameter warning. */ + ( void ) cmock_num_calls; + + TEST_ASSERT_NOT_NULL( parser ); + pCapturedParser = parser; + + TEST_ASSERT_EQUAL( HTTP_RESPONSE, type ); +} + +/** + * @brief Callback that is passed to the mock of http_parse_settings_init function + * to set test expectations on input arguments sent by the HTTP API function under + * test. + */ +void parserSettingsInitExpectationCb( http_parser_settings * settings, + int cmock_num_calls ) +{ + /* Disable unused parameter warning. */ + ( void ) cmock_num_calls; + + TEST_ASSERT_NOT_NULL( settings ); + pCapturedSettings = settings; +} + +/** + * @brief Callback that is passed to the mock of http_parse_execute() function + * to set test expectations on input arguments, and inject behavior of invoking + * http-parser callbacks depending on test-case specific configuration of the + * function. + */ +size_t parserExecuteExpectationsCb( http_parser * parser, + const http_parser_settings * settings, + const char * data, + size_t len, + int cmock_num_calls ) +{ + /* Disable unused parameter warning. */ + ( void ) cmock_num_calls; + + TEST_ASSERT_NOT_NULL( settings ); + TEST_ASSERT_EQUAL( pCapturedParser, parser ); + TEST_ASSERT_NOT_NULL( parser ); + TEST_ASSERT_EQUAL( pCapturedSettings, settings ); + + TEST_ASSERT_EQUAL( expectedBufferSize, len ); + TEST_ASSERT_EQUAL( pExpectedBuffer, data ); + + if( invokeHeaderFieldCallback == 1u ) + { + TEST_ASSERT_EQUAL( HTTP_PARSER_CONTINUE_PARSING, + settings->on_header_field( parser, + pFieldLocToReturn, + fieldLenToReturn ) ); + } + + if( invokeHeaderValueCallback == 1u ) + { + TEST_ASSERT_EQUAL( expectedValCbRetVal, + settings->on_header_value( parser, + pValueLocToReturn, + valueLenToReturn ) ); + } + + if( invokeHeaderCompleteCallback == 1u ) + { + TEST_ASSERT_EQUAL( HTTP_PARSER_STOP_PARSING, + settings->on_headers_complete( parser ) ); + } + + /* Set the error value in the parser. */ + parser->http_errno = parserErrNo; + return len; +} + /** * @brief Fills the test input buffer and expectation buffers with pre-existing data * before calling the API function under test. @@ -157,6 +272,16 @@ static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, /* ============================ UNITY FIXTURES ============================== */ void setUp( void ) { + testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; + testResponse.bufferLen = strlen( pTestResponse ); + + /* Configure the http_parser mocks with their callbacks. */ + http_parser_init_AddCallback( parserInitExpectationCb ); + http_parser_settings_init_AddCallback( parserSettingsInitExpectationCb ); + http_parser_execute_AddCallback( parserExecuteExpectationsCb ); + + /* Ignore the calls to http_errno_description. */ + http_errno_description_IgnoreAndReturn( "Mocked HTTP Parser Status" ); } /* called before each testcase */ @@ -166,16 +291,23 @@ void tearDown( void ) memset( &testHeaders, 0, sizeof( testHeaders ) ); memset( testBuffer, 0, sizeof( testBuffer ) ); memset( &expectedHeaders, 0, sizeof( expectedHeaders ) ); -} - -/* called at the beginning of the whole suite */ -void suiteSetUp() -{ -} - -/* called at the end of the whole suite */ -int suiteTearDown( int numFailures ) -{ + memset( &testResponse, + 0, + sizeof( testResponse ) ); + pValueLoc = NULL; + valueLen = 0u; + pValueLoc = NULL; + valueLen = 0u; + pExpectedBuffer = &pTestResponse[ 0 ]; + expectedBufferSize = strlen( pTestResponse ); + invokeHeaderFieldCallback = 0u; + pFieldLocToReturn = NULL; + fieldLenToReturn = 0u; + invokeHeaderValueCallback = 0u; + pValueLocToReturn = NULL; + expectedValCbRetVal = 0; + valueLenToReturn = 0u; + invokeHeaderCompleteCallback = 0u; } /* ============== Testing HTTPClient_InitializeRequestHeaders =============== */ @@ -368,7 +500,7 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) /* Request Header Size is zero. */ tearDown(); testHeaders.pBuffer = &testBuffer[ 0 ]; - /* The input buffer size is zero!. */ + /* The input buffer size is zero! */ testHeaders.bufferLen = 0u; retCode = HTTPClient_AddRangeHeader( &testHeaders, 0 /* rangeStart */, @@ -418,7 +550,7 @@ void test_Http_AddRangeHeader_Insufficient_Memory( void ) * to determine the total required size of the buffer. */ addRangeToExpectedHeaders( &expectedHeaders, "5-10" /*expected range*/, - true ); + 1u ); /* Change the input headers buffer size to be one byte short of the required * size to add Range Request header. */ @@ -460,7 +592,7 @@ void test_Http_AddRangeHeader_Without_Trailing_Terminator( void ) testRangeEnd = 0; addRangeToExpectedHeaders( &expectedHeaders, "0-0" /*expected range*/, - false ); + 0u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -490,7 +622,7 @@ void test_Http_AddRangeHeader_RangeType_File_SubRange( void ) testRangeEnd = 0; addRangeToExpectedHeaders( &expectedHeaders, "0-0" /*expected range*/, - true ); + 1u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -512,7 +644,7 @@ void test_Http_AddRangeHeader_RangeType_File_SubRange( void ) testRangeEnd = 100; addRangeToExpectedHeaders( &expectedHeaders, "10-100" /*expected range*/, - true ); + 1u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -539,7 +671,7 @@ void test_Http_AddRangeHeader_RangeType_Entire_File( void ) testRangeEnd = HTTP_RANGE_REQUEST_END_OF_FILE; addRangeToExpectedHeaders( &expectedHeaders, "0-" /*expected range*/, - true ); + 1u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -569,7 +701,7 @@ void test_Http_AddRangeHeader_RangeType_All_Bytes_From_RangeStart( void ) testRangeEnd = HTTP_RANGE_REQUEST_END_OF_FILE; addRangeToExpectedHeaders( &expectedHeaders, "100-" /*expected range*/, - true ); + 1u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -597,7 +729,7 @@ void test_Http_AddRangeHeader_RangeType_LastNBytes( void ) testRangeEnd = HTTP_RANGE_REQUEST_END_OF_FILE; addRangeToExpectedHeaders( &expectedHeaders, "-50" /*expected range*/, - true ); + 1u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -625,7 +757,7 @@ void test_Http_AddRangeHeader_With_Max_INT32_Range_Values( void ) testRangeEnd = INT32_MAX; addRangeToExpectedHeaders( &expectedHeaders, "2147483647-2147483647" /*expected range*/, - true ); + 1u ); retCode = HTTPClient_AddRangeHeader( &testHeaders, testRangeStart, testRangeEnd ); @@ -638,3 +770,228 @@ void test_Http_AddRangeHeader_With_Max_INT32_Range_Values( void ) /* Verify that the bufferLen data was not tampered with. */ TEST_ASSERT_EQUAL( sizeof( testBuffer ), testHeaders.bufferLen ); } + +/* ============== Testing HTTPClient_ReadHeader ================== */ + +/** + * @brief Test with invalid parameter inputs. + */ +void test_Http_ReadHeader_Invalid_Params( void ) +{ + /* Response parameter is NULL. */ + retCode = HTTPClient_ReadHeader( NULL, + ( const uint8_t * ) "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Underlying buffer is NULL in the response parameter. */ + tearDown(); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Response buffer size is zero. */ + tearDown(); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) "Header", + strlen( "Header" ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Header field name is NULL. */ + tearDown(); + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + NULL, + strlen( "Header" ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Header field length is 0. */ + tearDown(); + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) "Header", + 0u, + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* Invalid output parameters. */ + tearDown(); + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) "Header", + strlen( "Header" ), + NULL, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + tearDown(); + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) "Header", + strlen( "Header" ), + &pValueLoc, + NULL ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); +} + +/** + * @brief Test when requested header is not present in response. + */ +void test_Http_ReadHeader_Header_Not_In_Response( void ) +{ + /* Add expectations for http_parser dependencies. */ + http_parser_init_ExpectAnyArgs(); + http_parser_settings_init_ExpectAnyArgs(); + + /* Configure the http_parser_execute mock. */ + invokeHeaderCompleteCallback = 0u; + parserErrNo = HPE_OK; + http_parser_execute_ExpectAnyArgsAndReturn( strlen( pTestResponse ) ); + + /* Call the function under test. */ + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) HEADER_NOT_IN_BUFFER, + strlen( HEADER_NOT_IN_BUFFER ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_HEADER_NOT_FOUND, retCode ); +} + +/** + * @brief Test with an invalid HTTP response containing only the field name the + * requested header. + */ +void test_Http_ReadHeader_Invalid_Response_Only_Header_Field_Found() +{ + /* Test when invalid response only contains the header field for the requested header. */ + const char * pResponseWithoutValue = "HTTP/1.1 200 OK\r\n" + "test-header0: test-value0\r\n" + "test-header1:"; + + /* Add expectations for http_parser init dependencies. */ + http_parser_init_ExpectAnyArgs(); + http_parser_settings_init_ExpectAnyArgs(); + + /* Configure the http_parser_execute mock. */ + pExpectedBuffer = pResponseWithoutValue; + expectedBufferSize = strlen( pResponseWithoutValue ); + invokeHeaderFieldCallback = 1u; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + http_parser_execute_ExpectAnyArgsAndReturn( strlen( pResponseWithoutValue ) ); + + /* Call the function under test. */ + testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutValue[ 0 ]; + testResponse.bufferLen = strlen( pResponseWithoutValue ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_RESPONSE, retCode ); +} + +/** + * @brief Test with an invalid HTTP response that does not contain terminating + * characters ("\r\n\r\n") that represent the end of headers in the response. + */ +void test_Http_ReadHeader_Invalid_Response_No_Headers_Complete_Ending() +{ + /* Test response that does not contain requested header, + * is invalid as it doesn't end with "\r\n\r\n". */ + const char * pResponseWithoutHeaders = "HTTP/1.1 200 OK\r\n" + "test-header0:test-value0"; + + tearDown(); + + /* Add expectations for http_parser init dependencies. */ + http_parser_init_ExpectAnyArgs(); + http_parser_settings_init_ExpectAnyArgs(); + + /* Configure the http_parser_execute mock. */ + pExpectedBuffer = &pResponseWithoutHeaders[ 0 ]; + expectedBufferSize = strlen( pResponseWithoutHeaders ); + parserErrNo = HPE_UNKNOWN; + http_parser_execute_ExpectAnyArgsAndReturn( strlen( pResponseWithoutHeaders ) ); + /* Call the function under test. */ + testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutHeaders[ 0 ]; + testResponse.bufferLen = strlen( pResponseWithoutHeaders ); + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) HEADER_NOT_IN_BUFFER, + strlen( HEADER_NOT_IN_BUFFER ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INVALID_RESPONSE, retCode ); +} + +/** + * @brief Test when the header is present in response but http_parser_execute() + * does not set the expected errno value (of "CB_header_value") + * due to an internal error. + */ +void test_Http_ReadHeader_With_HttpParser_Internal_Error() +{ + /* Add expectations for http_parser init dependencies. */ + http_parser_init_ExpectAnyArgs(); + http_parser_settings_init_ExpectAnyArgs(); + + /* Configure the http_parser_execute mock. */ + invokeHeaderFieldCallback = 1u; + invokeHeaderValueCallback = 1u; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + expectedValCbRetVal = HTTP_PARSER_STOP_PARSING; + parserErrNo = HPE_CB_chunk_complete; + http_parser_execute_ExpectAnyArgsAndReturn( strlen( pTestResponse ) ); + + /* Call the function under test. */ + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_INTERNAL_ERROR, retCode ); +} + +/** + * @brief Test when requested header is present in the HTTP response. + */ +void test_Http_ReadHeader_Happy_Path() +{ + /* Add expectations for http_parser init dependencies. */ + http_parser_init_ExpectAnyArgs(); + http_parser_settings_init_ExpectAnyArgs(); + + /* Configure the http_parser_execute mock. */ + expectedValCbRetVal = HTTP_PARSER_STOP_PARSING; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + invokeHeaderFieldCallback = 1u; + invokeHeaderValueCallback = 1u; + parserErrNo = HPE_CB_header_value; + http_parser_execute_ExpectAnyArgsAndReturn( strlen( pTestResponse ) ); + + /* Call the function under test. */ + retCode = HTTPClient_ReadHeader( &testResponse, + ( const uint8_t * ) HEADER_IN_BUFFER, + strlen( HEADER_IN_BUFFER ), + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + TEST_ASSERT_EQUAL( ( const uint8_t * ) &pTestResponse[ headerValInRespLoc ], pValueLoc ); + TEST_ASSERT_EQUAL( headerValInRespLen, valueLen ); +} From 3af7719c6fb6e6fbd276c0fc8f9b545293f41a97 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Tue, 19 May 2020 18:36:23 -0700 Subject: [PATCH 511/844] Fix issues in MQTT library. (#955) Below are the issues fixed in this commit 1. Remove the incorrect and duplicate pointer initialization in sendPacket function. 2. Remove NULL check for the packet Id pointer passed to MQTT_DeserializeAck when packet type is either CONNACK or PINGRESP. --- libraries/standard/mqtt/src/mqtt.c | 2 -- .../standard/mqtt/src/mqtt_lightweight.c | 25 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index d610784581..5ca4781109 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -213,8 +213,6 @@ static int32_t sendPacket( MQTTContext_t * pContext, assert( pContext != NULL ); assert( bytesToSend != 0 ); - /* Point to the start of the network buffer from the context. */ - pIndex = pContext->networkBuffer.pBuffer; /* Record the time of transmission. */ sendTime = pContext->callbacks.getTime(); bytesRemaining = bytesToSend; diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index febe408281..a771f01b4c 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1940,13 +1940,26 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket { MQTTStatus_t status = MQTTSuccess; - if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pSessionPresent == NULL ) ) + if( ( pIncomingPacket == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " - "pPacketId=%p, pSessionPresent=%p", - pIncomingPacket, - pPacketId, - pSessionPresent ); + LogError( "pIncomingPacket cannot be NULL." ); + status = MQTTBadParameter; + } + /* Pointer for packet identifier cannot be NULL for packets other than + * CONNACK and PINGRESP. */ + else if( ( pPacketId == NULL ) && + ( ( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) && + ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) ) + { + LogErrorWithArgs( "pPacketId cannot be NULL for packet type %02x.", + pIncomingPacket->type ); + status = MQTTBadParameter; + } + /* Pointer for session present cannot be NULL for CONNACK. */ + else if( ( pSessionPresent == NULL ) && + ( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) ) + { + LogError( "pSessionPresent cannot be NULL for CONNACK packet." ); status = MQTTBadParameter; } else if( pIncomingPacket->pRemainingData == NULL ) From 6dd1973732b056e8226fac45a978fef6a32a4c6a Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Wed, 20 May 2020 10:33:16 -0700 Subject: [PATCH 512/844] Update Minimum CMake Version check in Root (#952) Update cmake_minimum_required in root of repo. The previous setup allowed using an outdated version on demos which require a newer add_executable function signature. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd8a47acf2..83d5dfa343 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # Project information. -cmake_minimum_required( VERSION 3.5.0 ) +cmake_minimum_required( VERSION 3.13.0 ) project( AwsIotDeviceSdkEmbeddedC VERSION 4.0.2 LANGUAGES C ) From 66bf2247af83116c42b7ec126016a82c7c9bb7fd Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 20 May 2020 12:33:03 -0700 Subject: [PATCH 513/844] fix: MQTT library issues found during demo development. (#950) * fix: MQTT library issues found during demo development. 1. Make serialize ping request behavior same as disconnect. 2. Remove unnecessary function call from serialize disconnect. 3. Fix serialize unsubscribe. --- .../standard/mqtt/include/mqtt_lightweight.h | 13 +++-- libraries/standard/mqtt/src/mqtt.c | 19 +++++- .../standard/mqtt/src/mqtt_lightweight.c | 58 +++++++++++++------ 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 7107b60d3f..943694c613 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -52,10 +52,6 @@ #define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ #define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ -/* - * Constant relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. - */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ /** * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. @@ -462,6 +458,15 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); */ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); +/** + * @brief Get the size of an MQTT PINGREQ packet. + * + * @param[out] pPacketSize The size of the MQTT PINGREQ packet. + * + * @return #MQTTSuccess or #MQTTBadParameter if pPacketSize is NULL. + */ +MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); + /** * @brief Serialize an MQTT PINGREQ packet into the given buffer. * diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 5ca4781109..b7d1137f9c 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1189,6 +1189,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) { int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; + size_t packetSize; if( pContext == NULL ) { @@ -1196,6 +1197,22 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) status = MQTTBadParameter; } + if( status == MQTTSuccess ) + { + /* Get MQTT PINGREQ packet size. */ + status = MQTT_GetPingReqPacketSize( &packetSize ); + + if( status == MQTTSuccess ) + { + LogDebugWithArgs( "MQTT PINGREQ packet size is %lu.", + packetSize ); + } + else + { + LogError( "Failed to get the PINGREQ packet size." ) + } + } + if( status == MQTTSuccess ) { /* Serialize MQTT PINGREQ. */ @@ -1207,7 +1224,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) /* Send the serialized PINGREQ packet to transport layer. */ bytesSent = sendPacket( pContext, pContext->networkBuffer.pBuffer, - MQTT_PACKET_PINGREQ_SIZE ); + packetSize ); if( bytesSent < 0 ) { diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index a771f01b4c..5580082803 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -63,6 +63,11 @@ */ #define MQTT_DISCONNECT_PACKET_SIZE ( 2UL ) +/* + * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) + /** * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec. */ @@ -1534,7 +1539,7 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS subscriptionCount, pRemainingLength, pPacketSize, - MQTT_SUBSCRIBE ); + MQTT_UNSUBSCRIBE ); if( status == MQTTBadParameter ) { @@ -1826,7 +1831,6 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) { MQTTStatus_t status = MQTTSuccess; - size_t disconnectPacketSize; /* Validate arguments. */ if( pBuffer == NULL ) @@ -1837,19 +1841,12 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) if( status == MQTTSuccess ) { - status = MQTT_GetDisconnectPacketSize( &disconnectPacketSize ); - LogDebugWithArgs( "MQTT DISCONNECT packet size is %ul.", - disconnectPacketSize ); - } - - if( status == MQTTSuccess ) - { - if( pBuffer->size < disconnectPacketSize ) + if( pBuffer->size < MQTT_DISCONNECT_PACKET_SIZE ) { LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " "serialized DISCONNECT packet of size of %lu.", pBuffer->size, - disconnectPacketSize ); + MQTT_DISCONNECT_PACKET_SIZE ); status = MQTTNoMemory; } } @@ -1865,6 +1862,26 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) /*-----------------------------------------------------------*/ +MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pPacketSize == NULL ) + { + LogError( "pPacketSize is NULL." ); + status = MQTTBadParameter; + } + else + { + /* MQTT PINGREQ packets always have the same size. */ + *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1874,15 +1891,20 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) LogError( "pBuffer is NULL." ); status = MQTTBadParameter; } - else if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) + + if( status == MQTTSuccess ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PINGREQ packet of size of %lu.", - pBuffer->size, - MQTT_PACKET_PINGREQ_SIZE ); - status = MQTTNoMemory; + if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) + { + LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " + "serialized PINGREQ packet of size of %lu.", + pBuffer->size, + MQTT_PACKET_PINGREQ_SIZE ); + status = MQTTNoMemory; + } } - else + + if( status == MQTTSuccess ) { /* Ping request packets are always the same. */ pBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; From e38b0c9dafea0242bdd870c726f4ee0c2b171b11 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Wed, 20 May 2020 13:39:41 -0700 Subject: [PATCH 514/844] Update HTTPClient_InitializeRequestHeaders unit test for consistency (#948) * Follow (expected, actual) order for TEST_ASSERT_EQUAL * Use expectedHeaders struct for expected headers data * Remove double declaration * Use idiomatic way of snprintf * Address PR comments * Address PR comments * Address PR comments * Make test_Http_InitializeRequestHeaders_Invalid_params test more readable * Update case * Update doc for setupBuffer * Update case * Add missing underscore * Fix merge conflicts * Change retCode to HTTP_INTERNAL_ERROR --- libraries/standard/http/utest/http_utest.c | 161 ++++++++++++--------- 1 file changed, 89 insertions(+), 72 deletions(-) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 7a32ab25b0..d3f2703b41 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -93,13 +93,12 @@ static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" #define HEADER_NOT_IN_BUFFER "header-not-in-buffer" /* File-scoped Global variables */ -static HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; +static HTTPStatus_t retCode = HTTP_INTERNAL_ERROR; static uint8_t testBuffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; static HTTPRequestHeaders_t testHeaders = { 0 }; static _headers_t expectedHeaders = { 0 }; static int testRangeStart = 0; static int testRangeEnd = 0; -static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LEN ] = { 0 }; static const uint8_t * pValueLoc = NULL; static size_t valueLen = 0u; static HTTPResponse_t testResponse = { 0 }; @@ -254,7 +253,7 @@ static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, snprintf( ( char * ) expectedHeaders->buffer + expectedHeaders->dataLen - ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ), - sizeof( expectedHeaders->buffer ), + sizeof( expectedHeaders->buffer ) - expectedHeaders->dataLen, "%s%s%s%s\r\n\r\n", RANGE_REQUEST_HEADER_FIELD, HTTP_HEADER_FIELD_SEPARATOR, @@ -270,6 +269,8 @@ static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, } /* ============================ UNITY FIXTURES ============================== */ + +/* Called before each test method. */ void setUp( void ) { testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; @@ -284,7 +285,7 @@ void setUp( void ) http_errno_description_IgnoreAndReturn( "Mocked HTTP Parser Status" ); } -/* called before each testcase */ +/* Called after each test method. */ void tearDown( void ) { retCode = HTTP_NOT_SUPPORTED; @@ -310,6 +311,17 @@ void tearDown( void ) invokeHeaderCompleteCallback = 0u; } +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + /* ============== Testing HTTPClient_InitializeRequestHeaders =============== */ /** @@ -329,87 +341,91 @@ static void setupRequestInfo( HTTPRequestInfo_t * pRequestInfo ) } /** + * @brief Initialize pRequestHeaders with static buffer. * * @param[in] pRequestHeaders Request header buffer information. - * @param[in] bufferLen Size of the buffer. */ -static void setupBuffer( HTTPRequestHeaders_t * pRequestHeaders, - size_t bufferLen ) +static void setupBuffer( HTTPRequestHeaders_t * pRequestHeaders ) { - pRequestHeaders->pBuffer = httpBuffer; - pRequestHeaders->bufferLen = bufferLen; + pRequestHeaders->pBuffer = testBuffer; + pRequestHeaders->bufferLen = sizeof( testBuffer ); } /** * @brief Test happy path with zero-initialized requestHeaders and requestInfo. */ -void test_Http_InitializeRequestHeaders_happy_path() +void test_Http_InitializeRequestHeaders_Happy_Path() { - HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; - size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; - char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; int numBytes = 0; setupRequestInfo( &requestInfo ); - setupBuffer( &requestHeaders, expectedHeaderLen ); + expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN + + HTTP_CONNECTION_CLOSE_VALUE_LEN; + setupBuffer( &requestHeaders ); /* Happy Path testing. */ - expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN + - HTTP_CONNECTION_CLOSE_VALUE_LEN; - numBytes = snprintf( expectedHeader, expectedHeaderLen + 1, + numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), HTTP_TEST_HEADER_FORMAT, HTTP_METHOD_GET, HTTP_TEST_REQUEST_PATH, HTTP_PROTOCOL_VERSION, HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ); - TEST_ASSERT_EQUAL( numBytes, expectedHeaderLen ); - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL_MEMORY( requestHeaders.pBuffer, expectedHeader, expectedHeaderLen ); - TEST_ASSERT_EQUAL( requestHeaders.headersLen, expectedHeaderLen ); - TEST_ASSERT_EQUAL( test_err, HTTP_SUCCESS ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); + + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, httpStatus ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, requestHeaders.pBuffer, + expectedHeaders.dataLen ); } /** * @brief Test NULL parameters, following order of else-if blocks in the HTTP library. */ -void test_Http_InitializeRequestHeaders_invalid_params() +void test_Http_InitializeRequestHeaders_Invalid_Params() { - HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; - size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; - char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; /* Test NULL parameters, following order of else-if blocks. */ - test_err = HTTPClient_InitializeRequestHeaders( NULL, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + httpStatus = HTTPClient_InitializeRequestHeaders( NULL, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + /* TEST requestInfo.pBuffer == NULL */ - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); - requestHeaders.pBuffer = httpBuffer; + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + requestHeaders.pBuffer = testBuffer; requestHeaders.bufferLen = HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, NULL ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); - /* Test requestInfo members are NULL */ - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + + /* Test requestInfo == NULL. */ + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, NULL ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test requestInfo.method == NULL. */ + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); requestInfo.method = HTTP_METHOD_GET; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + + /* Test requestInfo.pHost == NULL. */ + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test requestInfo.methodLen == 0. */ requestInfo.pHost = HTTP_TEST_HOST_VALUE; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); - requestInfo.pPath = HTTP_TEST_REQUEST_PATH; - requestInfo.pathLen = HTTP_TEST_REQUEST_PATH_LEN; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test requestInfo.hostLen == 0. */ requestInfo.methodLen = HTTP_METHOD_GET_LEN; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INVALID_PARAMETER ); - requestInfo.hostLen = HTTP_TEST_HOST_VALUE_LEN; + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); } /** @@ -417,60 +433,61 @@ void test_Http_InitializeRequestHeaders_invalid_params() * header is set to "keep-alive" when HTTP_REQUEST_KEEP_ALIVE_FLAG in requestHeaders * is activated. */ -void test_Http_InitializeRequestHeaders_req_info() +void test_Http_InitializeRequestHeaders_ReqInfo() { - HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; - size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; - char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; int numBytes = 0; setupRequestInfo( &requestInfo ); - setupBuffer( &requestHeaders, expectedHeaderLen ); + expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN - + HTTP_TEST_REQUEST_PATH_LEN + + HTTP_EMPTY_PATH_LEN + + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN; + setupBuffer( &requestHeaders ); requestInfo.pPath = 0; requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; - expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN - - HTTP_TEST_REQUEST_PATH_LEN + - HTTP_EMPTY_PATH_LEN + - HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN; - numBytes = snprintf( expectedHeader, expectedHeaderLen + 1, + numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), HTTP_TEST_HEADER_FORMAT, HTTP_METHOD_GET, HTTP_EMPTY_PATH, HTTP_PROTOCOL_VERSION, HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, HTTP_CONNECTION_FIELD, HTTP_CONNECTION_KEEP_ALIVE_VALUE ); - TEST_ASSERT_EQUAL( numBytes, expectedHeaderLen ); - - requestHeaders.pBuffer = httpBuffer; - requestHeaders.bufferLen = expectedHeaderLen; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL_MEMORY( requestHeaders.pBuffer, expectedHeader, expectedHeaderLen ); - TEST_ASSERT_EQUAL( requestHeaders.headersLen, expectedHeaderLen ); - TEST_ASSERT_EQUAL( test_err, HTTP_SUCCESS ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); + + requestHeaders.pBuffer = testBuffer; + requestHeaders.bufferLen = expectedHeaders.dataLen; + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, httpStatus ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, requestHeaders.pBuffer, + expectedHeaders.dataLen ); } /** * @brief Test HTTP_INSUFFICIENT_MEMORY from having requestHeaders.bufferLen less than * what is required to fit HTTP_TEST_REQUEST_LINE. */ -void test_Http_InitializeRequestHeaders_insufficient_memory() +void test_Http_InitializeRequestHeaders_Insufficient_Memory() { - HTTPStatus_t test_err = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; - size_t expectedHeaderLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; - char expectedHeader[ HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN ] = { 0 }; + + expectedHeaders.dataLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; setupRequestInfo( &requestInfo ); - setupBuffer( &requestHeaders, expectedHeaderLen ); + setupBuffer( &requestHeaders ); requestHeaders.bufferLen = HTTP_TEST_REQUEST_LINE_LEN - 1; - test_err = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); - TEST_ASSERT_EQUAL( test_err, HTTP_INSUFFICIENT_MEMORY ); + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, httpStatus ); TEST_ASSERT_TRUE( strncmp( ( char * ) requestHeaders.pBuffer, HTTP_TEST_REQUEST_LINE, HTTP_TEST_REQUEST_LINE_LEN ) != 0 ); From 1621344535fde6cd5535d4fbfed40bf7f13b5246 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 20 May 2020 14:12:01 -0700 Subject: [PATCH 515/844] Logging/update to new infrastructure (#945) --- CMakeLists.txt | 4 +- demos/logging-stack/logging.cmake | 3 + .../logging-stack}/logging_levels.h | 8 +- demos/logging-stack/logging_stack.h | 95 +++++ demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 6 + demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 53 +++ .../{config.h => mqtt_config.h} | 34 +- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 31 +- demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 6 + demos/mqtt/mqtt_demo_plaintext/demo_config.h | 53 +++ .../{config.h => mqtt_config.h} | 34 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 27 +- libraries/standard/http/CMakeLists.txt | 2 +- libraries/standard/http/include/http_client.h | 2 +- libraries/standard/http/src/http_client.c | 240 +++++------ .../http/src/private/http_client_internal.h | 35 +- libraries/standard/http/test/Makefile | 3 - libraries/standard/http/test/http_config.h | 25 ++ libraries/standard/http/utest/CMakeLists.txt | 3 +- libraries/standard/http/utest/config.h | 27 -- libraries/standard/http/utest/http_config.h | 25 ++ libraries/standard/mqtt/CMakeLists.txt | 7 +- libraries/standard/mqtt/include/mqtt.h | 2 +- .../standard/mqtt/include/mqtt_lightweight.h | 6 +- libraries/standard/mqtt/src/mqtt.c | 265 ++++++------ .../standard/mqtt/src/mqtt_lightweight.c | 389 +++++++++--------- .../standard/mqtt/src/private/mqtt_internal.h | 35 +- libraries/standard/mqtt/utest/CMakeLists.txt | 6 +- .../mqtt/utest/{config.h => mqtt_config.h} | 39 +- .../utilities/include/logging_setup.h | 210 ---------- platform/include/clock.h | 6 +- platform/posix/clock_posix.c | 6 +- platform/posix/logging.c | 262 ------------ tools/cmake/logging.cmake | 7 - 34 files changed, 869 insertions(+), 1087 deletions(-) create mode 100644 demos/logging-stack/logging.cmake rename {libraries/standard/utilities/include => demos/logging-stack}/logging_levels.h (96%) create mode 100644 demos/logging-stack/logging_stack.h create mode 100644 demos/mqtt/mqtt_demo_basic_tls/demo_config.h rename demos/mqtt/mqtt_demo_basic_tls/{config.h => mqtt_config.h} (74%) create mode 100644 demos/mqtt/mqtt_demo_plaintext/demo_config.h rename demos/mqtt/mqtt_demo_plaintext/{config.h => mqtt_config.h} (68%) create mode 100644 libraries/standard/http/test/http_config.h delete mode 100644 libraries/standard/http/utest/config.h create mode 100644 libraries/standard/http/utest/http_config.h rename libraries/standard/mqtt/utest/{config.h => mqtt_config.h} (58%) delete mode 100644 libraries/standard/utilities/include/logging_setup.h delete mode 100644 platform/posix/logging.c delete mode 100644 tools/cmake/logging.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 83d5dfa343..a24cef7515 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,8 @@ if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) endif() # Import global configurations. -include("tools/cmake/filePaths.cmake") -include("tools/cmake/logging.cmake") +include( "tools/cmake/filePaths.cmake" ) +include( "demos/logging-stack/logging.cmake" ) # Configure options to always show in CMake GUI. option( BUILD_TESTS diff --git a/demos/logging-stack/logging.cmake b/demos/logging-stack/logging.cmake new file mode 100644 index 0000000000..21bceef97a --- /dev/null +++ b/demos/logging-stack/logging.cmake @@ -0,0 +1,3 @@ +# Configuration for logging. +set( LOGGING_INCLUDE_DIRS + ${CMAKE_CURRENT_LIST_DIR} ) \ No newline at end of file diff --git a/libraries/standard/utilities/include/logging_levels.h b/demos/logging-stack/logging_levels.h similarity index 96% rename from libraries/standard/utilities/include/logging_levels.h rename to demos/logging-stack/logging_levels.h index d0f45318e8..eed53dcfee 100644 --- a/libraries/standard/utilities/include/logging_levels.h +++ b/demos/logging-stack/logging_levels.h @@ -21,12 +21,12 @@ */ /** - * @file iot_logging_levels.h + * @file logging_levels.h * @brief Defines the logging level macros. */ -#ifndef IOT_LOGGING_LEVELS_H_ -#define IOT_LOGGING_LEVELS_H_ +#ifndef LOGGING_LEVELS_H_ +#define LOGGING_LEVELS_H_ /** * @constantspage{logging,logging library} @@ -106,4 +106,4 @@ */ #define LOG_DEBUG 4 -#endif /* ifndef IOT_LOGGING_LEVELS_H_ */ +#endif /* ifndef LOGGING_LEVELS_H_ */ diff --git a/demos/logging-stack/logging_stack.h b/demos/logging-stack/logging_stack.h new file mode 100644 index 0000000000..37d0c588d4 --- /dev/null +++ b/demos/logging-stack/logging_stack.h @@ -0,0 +1,95 @@ +/* + * Logging Stack V1.0.0 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file logging_stack.h + * @brief Reference implementation of Logging stack as a header-only library. + */ + +#ifndef LOGGING_STACK_H_ +#define LOGGING_STACK_H_ + +/* Include header for logging level macros. */ +#include "logging_levels.h" + +/* Standard Include. */ +#include +#include + +/* Metadata information to prepend to every log message. */ +#define LOG_METADATA_FORMAT "[%s:%d] " +#define LOG_METADATA_ARGS __FILE__, __LINE__ + +/* Common macro for all logging interface macros. */ +/* TODO - Replace printf with an implementation function. */ +#define SdkLog( string ) printf string + +/* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ +#if !defined( LIBRARY_LOG_LEVEL ) || \ + ( ( LIBRARY_LOG_LEVEL != LOG_NONE ) && \ + ( LIBRARY_LOG_LEVEL != LOG_ERROR ) && \ + ( LIBRARY_LOG_LEVEL != LOG_WARN ) && \ + ( LIBRARY_LOG_LEVEL != LOG_INFO ) && \ + ( LIBRARY_LOG_LEVEL != LOG_DEBUG ) ) + #error "Please define LIBRARY_LOG_LEVEL as either LOG_NONE, LOG_ERROR, LOG_WARN, LOG_INFO, or LOG_DEBUG." +#elif !defined( LIBRARY_LOG_NAME ) + #error "Please define LIBRARY_LOG_NAME for the library." +#else + #if LIBRARY_LOG_LEVEL == LOG_DEBUG + /* All log level messages will logged. */ + #define LogError( message ) SdkLog( ( "[ERROR] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogWarn( message ) SdkLog( ( "[WARN] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogInfo( message ) SdkLog( ( "[INFO] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogDebug( message ) SdkLog( ( "[DEBUG] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + + #elif LIBRARY_LOG_LEVEL == LOG_INFO + /* Only INFO, WARNING and ERROR messages will be logged. */ + #define LogError( message ) SdkLog( ( "[ERROR] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogWarn( message ) SdkLog( ( "[WARN] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogInfo( message ) SdkLog( ( "[INFO] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogDebug( message ) + + #elif LIBRARY_LOG_LEVEL == LOG_WARN + /* Only WARNING and ERROR messages will be logged.*/ + #define LogError( message ) SdkLog( ( "[ERROR] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogWarn( message ) SdkLog( ( "[WARN] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogInfo( message ) + #define LogDebug( message ) + + #elif LIBRARY_LOG_LEVEL == LOG_ERROR + /* Only ERROR messages will be logged. */ + #define LogError( message ) SdkLog( ( "[ERROR] [%s] "LOG_METADATA_FORMAT, LIBRARY_LOG_NAME, LOG_METADATA_ARGS ) ); SdkLog( message ); SdkLog( ( "\r\n" ) ) + #define LogWarn( message ) + #define LogInfo( message ) + #define LogDebug( message ) + + #else /* if LIBRARY_LOG_LEVEL == LOG_ERROR */ + + #define LogError( message ) + #define LogWarn( message ) + #define LogInfo( message ) + #define LogDebug( message ) + + #endif /* if LIBRARY_LOG_LEVEL == LOG_ERROR */ +#endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != LOG_NONE ) && ( LIBRARY_LOG_LEVEL != LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != LOG_WARN ) && ( LIBRARY_LOG_LEVEL != LOG_INFO ) && ( LIBRARY_LOG_LEVEL != LOG_DEBUG ) ) */ + +#endif /* ifndef LOGGING_STACK_H_ */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt index 15943688e5..d8babee36d 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -24,3 +24,9 @@ target_include_directories( PUBLIC ${CMAKE_CURRENT_LIST_DIR} ) + +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h new file mode 100644 index 0000000000..7beb40cac2 --- /dev/null +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H +#define DEMO_CONFIG_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#define LIBRARY_LOG_NAME "DEMO" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient" + +#endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/config.h b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h similarity index 74% rename from demos/mqtt/mqtt_demo_basic_tls/config.h rename to demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h index b7db274bd2..9ba12040d2 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h @@ -19,8 +19,29 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the MQTT library. */ +#define LIBRARY_LOG_NAME "MQTT" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ /* Set network context to OpenSSL SSL context. */ #include @@ -50,11 +71,4 @@ typedef SSL * MQTTNetworkContext_t; */ #define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 -/** - * @brief MQTT client identifier. - * - * No two clients may use the same client identifier simultaneously. - */ -#define CLIENT_IDENTIFIER "testclient" - -#endif /* ifndef CONFIG_H */ +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index c1b4bea39e..0c6dc4e227 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -32,27 +32,30 @@ /* MQTT API header. */ #include "mqtt.h" +/* Demo Config header. */ +#include "demo_config.h" + /** * @brief MQTT server host name. * * This demo uses the Mosquitto test server. This is a public MQTT server; do not * publish anything sensitive to this server. */ -#define SERVER "test.mosquitto.org" +#define SERVER "test.mosquitto.org" /** * @brief MQTT server port number. * * In general, port 8883 is for secured MQTT connections. */ -#define PORT 8883 +#define PORT 8883 /** * @brief Path of the file containing the server's root CA certificate. * * This certificate should be PEM-encoded. */ -#define SERVER_CERT "mosquitto.org.crt" +#define SERVER_CERT "mosquitto.org.crt" /** * @brief Size of the network buffer for MQTT packets. @@ -72,7 +75,7 @@ /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ -#define CONNACK_RECV_TIMEOUT_MS (1000) +#define CONNACK_RECV_TIMEOUT_MS ( 1000 ) /*-----------------------------------------------------------*/ @@ -84,7 +87,8 @@ * * @return A file descriptor representing the TCP socket; -1 on failure. */ -static int connectToServer( const char * pServer, uint16_t port ) +static int connectToServer( const char * pServer, + uint16_t port ) { int status, tcpSocket = -1; struct addrinfo * pListHead = NULL, * pIndex; @@ -190,7 +194,7 @@ static SSL * tlsSetup( int tcpSocket ) if( pRootCa != NULL ) { sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), - pRootCa ); + pRootCa ); } } @@ -263,7 +267,9 @@ static SSL * tlsSetup( int tcpSocket ) * * @return Number of bytes sent; negative value on error. */ -static int32_t transportSend( MQTTNetworkContext_t pSslContext, const void * pMessage, size_t bytesToSend ) +static int32_t transportSend( MQTTNetworkContext_t pSslContext, + const void * pMessage, + size_t bytesToSend ) { return ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); } @@ -279,7 +285,9 @@ static int32_t transportSend( MQTTNetworkContext_t pSslContext, const void * pMe * * @return Number of bytes received; negative value on error. */ -static int32_t transportRecv( MQTTNetworkContext_t pSslContext, void * pBuffer, size_t bytesToRecv ) +static int32_t transportRecv( MQTTNetworkContext_t pSslContext, + void * pBuffer, + size_t bytesToRecv ) { return ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); } @@ -308,7 +316,6 @@ static void eventCallback( MQTTContext_t * pContext, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { - } /*-----------------------------------------------------------*/ @@ -321,7 +328,8 @@ static void eventCallback( MQTTContext_t * pContext, * * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. */ -static int establishMqttSession( MQTTContext_t * pContext, SSL * pSslContext ) +static int establishMqttSession( MQTTContext_t * pContext, + SSL * pSslContext ) { int status = EXIT_SUCCESS; MQTTStatus_t mqttStatus; @@ -394,7 +402,8 @@ static int disconnectMqttSession( MQTTContext_t * pContext ) /** * @brief Entry point of demo. */ -int main( int argc, char ** argv ) +int main( int argc, + char ** argv ) { bool mqttSessionEstablished = false; int status; diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt index 32c415c2da..714ade273b 100644 --- a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -14,6 +14,12 @@ target_link_libraries( mqtt ) +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) + target_include_directories( mqtt PUBLIC diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h new file mode 100644 index 0000000000..7beb40cac2 --- /dev/null +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H +#define DEMO_CONFIG_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#define LIBRARY_LOG_NAME "DEMO" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient" + +#endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_plaintext/config.h b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h similarity index 68% rename from demos/mqtt/mqtt_demo_plaintext/config.h rename to demos/mqtt/mqtt_demo_plaintext/mqtt_config.h index 3b5a6dc882..6b71794892 100644 --- a/demos/mqtt/mqtt_demo_plaintext/config.h +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h @@ -19,8 +19,29 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the MQTT library. */ +#define LIBRARY_LOG_NAME "MQTT" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ /* Set network context to socket (int). */ typedef int MQTTNetworkContext_t; @@ -37,11 +58,4 @@ typedef int MQTTNetworkContext_t; */ #define MQTT_STATE_ARRAY_MAX_COUNT 10U -/** - * @brief MQTT client identifier. - * - * No two clients may use the same client identifier simultaneously. - */ -#define CLIENT_IDENTIFIER "testclient" - -#endif /* ifndef CONFIG_H */ +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 632aa788c5..19998f9066 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -32,20 +32,23 @@ /* MQTT API header. */ #include "mqtt.h" +/* Demo Config header. */ +#include "demo_config.h" + /** * @brief MQTT server host name. * * This demo uses the Mosquitto test server. This is a public MQTT server; do not * publish anything sensitive to this server. */ -#define SERVER "test.mosquitto.org" +#define SERVER "test.mosquitto.org" /** * @brief MQTT server port number. * * In general, port 1883 is for unsecured MQTT connections. */ -#define PORT 1883 +#define PORT 1883 /** * @brief Size of the network buffer for MQTT packets. @@ -65,7 +68,7 @@ /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ -#define CONNACK_RECV_TIMEOUT_MS (1000) +#define CONNACK_RECV_TIMEOUT_MS ( 1000 ) /*-----------------------------------------------------------*/ @@ -77,7 +80,8 @@ * * @return A file descriptor representing the TCP socket; -1 on failure. */ -static int connectToServer( const char * pServer, uint16_t port ) +static int connectToServer( const char * pServer, + uint16_t port ) { int status, tcpSocket = -1; struct addrinfo * pListHead = NULL, * pIndex; @@ -157,7 +161,9 @@ static int connectToServer( const char * pServer, uint16_t port ) * * @return Number of bytes sent; negative value on error. */ -static int32_t transportSend( MQTTNetworkContext_t tcpSocket, const void * pMessage, size_t bytesToSend ) +static int32_t transportSend( MQTTNetworkContext_t tcpSocket, + const void * pMessage, + size_t bytesToSend ) { return ( int32_t ) send( tcpSocket, pMessage, bytesToSend, 0 ); } @@ -173,7 +179,9 @@ static int32_t transportSend( MQTTNetworkContext_t tcpSocket, const void * pMess * * @return Number of bytes received; negative value on error. */ -static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ) +static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, + void * pBuffer, + size_t bytesToRecv ) { return ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); } @@ -202,7 +210,6 @@ static void eventCallback( MQTTContext_t * pContext, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { - } /*-----------------------------------------------------------*/ @@ -215,7 +222,8 @@ static void eventCallback( MQTTContext_t * pContext, * * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. */ -static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) +static int establishMqttSession( MQTTContext_t * pContext, + int tcpSocket ) { int status = EXIT_SUCCESS; MQTTStatus_t mqttStatus; @@ -288,7 +296,8 @@ static int disconnectMqttSession( MQTTContext_t * pContext ) /** * @brief Entry point of demo. */ -int main( int argc, char ** argv ) +int main( int argc, + char ** argv ) { bool mqttSessionEstablished = false; int status = EXIT_SUCCESS; diff --git a/libraries/standard/http/CMakeLists.txt b/libraries/standard/http/CMakeLists.txt index ccd10f1592..8e0830a138 100644 --- a/libraries/standard/http/CMakeLists.txt +++ b/libraries/standard/http/CMakeLists.txt @@ -7,7 +7,7 @@ add_library( http # HTTP public include path. target_include_directories( http PUBLIC ${HTTP_INCLUDE_PUBLIC_DIRS} - ${ROOT_DIR}/platform/include + ${LOGGING_INCLUDE_DIRS} ${CMAKE_CURRENT_LIST_DIR}/utest ) # HTTP private include path. diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 7aa8b01a62..67d4dc0583 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -3,7 +3,7 @@ #include #include -#include "config.h" +#include "http_config.h" /** * @brief Maximum size, in bytes, of headers allowed from the server. diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 5e88c46418..aecbef1424 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -345,11 +345,11 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, } else { - LogErrorWithArgs( "Unable to add header in buffer: " - "Buffer has insufficient memory: " - "RequiredBytes=%d, RemainingBufferSize=%d", - toAddLen, - ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ); + LogError( ( "Unable to add header in buffer: " + "Buffer has insufficient memory: " + "RequiredBytes=%u, RemainingBufferSize=%u", + toAddLen, + ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ) ); returnStatus = HTTP_INSUFFICIENT_MEMORY; } @@ -429,37 +429,37 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques /* Check for NULL parameters. */ if( pRequestHeaders == NULL ) { - LogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pRequestInfo == NULL ) ) { - LogError( "Parameter check failed: pRequestInfo is NULL." ); + LogError( ( "Parameter check failed: pRequestInfo is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pRequestInfo->method == NULL ) ) { - LogError( "Parameter check failed: pRequestInfo->method is NULL." ); + LogError( ( "Parameter check failed: pRequestInfo->method is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestInfo->pHost == NULL ) { - LogError( "Parameter check failed: pRequestInfo->pHost is NULL." ); + LogError( ( "Parameter check failed: pRequestInfo->pHost is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestInfo->methodLen == 0 ) { - LogError( "Parameter check failed: pRequestInfo->methodLen must be greater than 0." ); + LogError( ( "Parameter check failed: pRequestInfo->methodLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestInfo->hostLen == 0 ) { - LogError( "Parameter check failed: pRequestInfo->hostLen must be greater than 0." ); + LogError( ( "Parameter check failed: pRequestInfo->hostLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -538,32 +538,32 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Check for NULL parameters. */ if( pRequestHeaders == NULL ) { - LogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pField == NULL ) ) { - LogError( "Parameter check failed: pField is NULL." ); + LogError( ( "Parameter check failed: pField is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pValue == NULL ) ) { - LogError( "Parameter check failed: pValue is NULL." ); + LogError( ( "Parameter check failed: pValue is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( fieldLen == 0u ) { - LogError( "Parameter check failed: fieldLen must be greater than 0." ); + LogError( ( "Parameter check failed: fieldLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( valueLen == 0u ) { - LogError( "Parameter check failed: valueLen must be greater than 0." ); + LogError( ( "Parameter check failed: valueLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -592,36 +592,36 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, if( pRequestHeaders == NULL ) { - LogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( rangeEnd < HTTP_RANGE_REQUEST_END_OF_FILE ) { - LogErrorWithArgs( "Parameter check failed: rangeEnd is invalid: " - "rangeEnd should be >=-1: RangeEnd=%d", rangeEnd ); + LogError( ( "Parameter check failed: rangeEnd is invalid: " + "rangeEnd should be >=-1: RangeEnd=%d", rangeEnd ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( rangeStartOrlastNbytes < 0 ) && ( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) ) { - LogErrorWithArgs( "Parameter check failed: Invalid range values: " - "rangeEnd should be -1 when rangeStart < 0: " - "RangeStart=%d, RangeEnd=%d", - rangeStartOrlastNbytes, rangeEnd ); + LogError( ( "Parameter check failed: Invalid range values: " + "rangeEnd should be -1 when rangeStart < 0: " + "RangeStart=%d, RangeEnd=%d", + rangeStartOrlastNbytes, rangeEnd ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) && ( rangeStartOrlastNbytes > rangeEnd ) ) { - LogErrorWithArgs( "Parameter check failed: Invalid range values: " - "rangeStart should be < rangeEnd when both are >= 0: " - "RangeStart=%d, RangeEnd=%d", - rangeStartOrlastNbytes, rangeEnd ); + LogError( ( "Parameter check failed: Invalid range values: " + "rangeStart should be < rangeEnd when both are >= 0: " + "RangeStart=%d, RangeEnd=%d", + rangeStartOrlastNbytes, rangeEnd ) ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -699,25 +699,25 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport if( transportStatus < 0 ) { - LogErrorWithArgs( "Failed to send HTTP headers: Transport send()" - " returned error: TransportStatus=%d", - transportStatus ); + LogError( ( "Failed to send HTTP headers: Transport send()" + " returned error: TransportStatus=%d", + transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } else if( ( size_t ) transportStatus != pRequestHeaders->headersLen ) { - LogErrorWithArgs( "Failed to send HTTP headers: Transport layer " - "did not send the required bytes: RequiredBytes=%d" - ", SentBytes=%d.", - pRequestHeaders->headersLen, - transportStatus ); + LogError( ( "Failed to send HTTP headers: Transport layer " + "did not send the required bytes: RequiredBytes=%u" + ", SentBytes=%d.", + pRequestHeaders->headersLen, + transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } else { - LogDebugWithArgs( "Sent HTTP headers over the transport: BytesSent " - "=%d.", - transportStatus ); + LogDebug( ( "Sent HTTP headers over the transport: BytesSent " + "=%d.", + transportStatus ) ); } return returnStatus; @@ -742,24 +742,24 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, if( transportStatus < 0 ) { - LogErrorWithArgs( "Failed to send HTTP body: Transport send() " - " returned error: TransportStatus=%d", - transportStatus ); + LogError( ( "Failed to send HTTP body: Transport send() " + " returned error: TransportStatus=%d", + transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } else if( ( size_t ) transportStatus != reqBodyBufLen ) { - LogErrorWithArgs( "Failed to send HTTP body: Transport send() " - "did not send the required bytes: RequiredBytes=%d" - ", Sent bytes=%d.", - reqBodyBufLen, - transportStatus ); + LogError( ( "Failed to send HTTP body: Transport send() " + "did not send the required bytes: RequiredBytes=%u" + ", SentBytes=%d.", + reqBodyBufLen, + transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } else { - LogDebugWithArgs( "Sent HTTP body over the transport: BytesSent=%d.", - transportStatus ); + LogDebug( ( "Sent HTTP body over the transport: BytesSent=%d.", + transportStatus ) ); } return returnStatus; @@ -786,35 +786,35 @@ HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { - LogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " - "returned error: TransportStatus=%d.", - transportStatus ); + LogError( ( "Failed to receive HTTP response: Transport recv() " + "returned error: TransportStatus=%d.", + transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } else if( ( size_t ) transportStatus > bufferLen ) { /* There is a bug in the transport recv if more bytes are reported * to have been read than the bytes asked for. */ - LogErrorWithArgs( "Failed to receive HTTP response: Transport recv() " - " read more bytes than requested: BytesRead=%d, " - "RequestedBytes=%d", - transportStatus, - bufferLen ); + LogError( ( "Failed to receive HTTP response: Transport recv() " + " read more bytes than requested: BytesRead=%d, " + "RequestedBytes=%u", + transportStatus, + bufferLen ) ); returnStatus = HTTP_NETWORK_ERROR; } else if( transportStatus > 0 ) { /* Some or all of the specified data was received. */ *pBytesReceived = ( size_t ) ( transportStatus ); - LogDebugWithArgs( "Received data from the transport: BytesReceived=%d.", - transportStatus ); + LogDebug( ( "Received data from the transport: BytesReceived=%d.", + transportStatus ) ); } else { /* When a zero is returned from the transport recv it will not be * invoked again. */ - LogDebug( "Received zero bytes from trasnport recv(). Receiving " - "transport data is complete." ); + LogDebug( ( "Received zero bytes from trasnport recv(). Receiving " + "transport data is complete." ) ); } return returnStatus; @@ -835,27 +835,27 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, /* If no parsing occurred, that means network data was never received. */ if( parsingState == HTTP_PARSING_NONE ) { - LogErrorWithArgs( "Response not received: Zero returned from " - "transport recv: totalReceived=%d", - totalReceived ); + LogError( ( "Response not received: Zero returned from " + "transport recv: totalReceived=%u", + totalReceived ) ); returnStatus = HTTP_NO_RESPONSE; } else if( parsingState == HTTP_PARSING_INCOMPLETE ) { if( totalReceived == responseBufferLen ) { - LogErrorWithArgs( "Cannot receive complete response from tansport" - " interface: Response buffer has insufficient " - "space: responseBufferLen=%d", - responseBufferLen ); + LogError( ( "Cannot receive complete response from tansport" + " interface: Response buffer has insufficient " + "space: responseBufferLen=%u", + responseBufferLen ) ); returnStatus = HTTP_INSUFFICIENT_MEMORY; } else { - LogErrorWithArgs( "Received partial response from transport ", - "recv(): ResponseSize=%d, TotalBufferSize=%d", - totalReceived, - responseBufferLen - totalReceived ); + LogError( ( "Received partial response from transport ", + "recv(): ResponseSize=%d, TotalBufferSize=%d", + totalReceived, + responseBufferLen - totalReceived ) ); returnStatus = HTTP_PARTIAL_RESPONSE; } } @@ -943,32 +943,32 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, if( pTransport == NULL ) { - LogError( "Parameter check failed: pTransport interface is NULL." ); + LogError( ( "Parameter check failed: pTransport interface is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pTransport->send == NULL ) { - LogError( "Parameter check failed: pTransport->send is NULL." ); + LogError( ( "Parameter check failed: pTransport->send is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pTransport->recv == NULL ) { - LogError( "Parameter check failed: pTransport->recv is NULL." ); + LogError( ( "Parameter check failed: pTransport->recv is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders == NULL ) { - LogError( "Parameter check failed: pRequestHeaders is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pRequestHeaders->pBuffer == NULL ) { - LogError( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ); + LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( ( pResponse != NULL ) && ( pResponse->pBuffer == NULL ) ) { - LogError( "Parameter check failed: pResponse->pBuffer is NULL." ); + LogError( ( "Parameter check failed: pResponse->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -994,7 +994,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, } else { - LogDebug( "A request body was not sent: pRequestBodyBuf is NULL." ); + LogDebug( ( "A request body was not sent: pRequestBodyBuf is NULL." ) ); } } @@ -1009,7 +1009,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, } else { - LogDebug( "Response ignored: pResponse is NULL." ); + LogDebug( ( "Response ignored: pResponse is NULL." ) ); } } @@ -1044,9 +1044,9 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /* Check whether the parsed header matches the header we are looking for. */ if( ( fieldLen == pContext->fieldLen ) && ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) { - LogDebugWithArgs( "Found header field in response: " - "HeaderName=%.*s, HeaderLocation=0x%d", - fieldLen, pContext->pField ); + LogDebug( ( "Found header field in response: " + "HeaderName=%.*s, HeaderLocation=0x%x", + fieldLen, pContext->pField, pFieldLoc ) ); /* Set the flag to indicate that header has been found in response. */ pContext->fieldFound = 1u; @@ -1094,9 +1094,9 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, if( pContext->fieldFound == 1u ) { - LogDebugWithArgs( "Found header value in response: " - "RequestedField=%.*s, ValueLocation=0x%d", - pContext->fieldLen, pContext->pField, pVaLueLoc ); + LogDebug( ( "Found header value in response: " + "RequestedField=%.*s, ValueLocation=0x%x", + pContext->fieldLen, pContext->pField, pVaLueLoc ) ); /* Populate the output parameters with the location of the header value in the response buffer. */ *pContext->pValueLoc = ( const uint8_t * ) pVaLueLoc; @@ -1134,10 +1134,10 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) /* If we have reached here, all headers in the response have been parsed but the requested * header has not been found in the response buffer. */ - LogDebugWithArgs( "Reached end of header parsing: Header not found in response: " - "RequestedHeader=%.*s", - ( ( findHeaderContext_t * ) pHttpParser->data )->fieldLen, - ( ( findHeaderContext_t * ) pHttpParser->data )->pField ); + LogDebug( ( "Reached end of header parsing: Header not found in response: " + "RequestedHeader=%.*s", + ( ( findHeaderContext_t * ) pHttpParser->data )->fieldLen, + ( ( findHeaderContext_t * ) pHttpParser->data )->pField ) ); /* No further parsing is required; thus, indicate the parser to stop parsing. */ return HTTP_PARSER_STOP_PARSING; @@ -1188,8 +1188,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, ( const char * ) pBuffer, bufferLen ); - LogDebugWithArgs( "Parsed response for header search: NumBytesParsed=%d", - numOfBytesParsed ); + LogDebug( ( "Parsed response for header search: NumBytesParsed=%u", + numOfBytesParsed ) ); if( context.fieldFound == 0u ) { @@ -1197,8 +1197,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, assert( context.valueFound == 0u ); /* Header is not present in buffer. */ - LogWarnWithArgs( "Header not found in response buffer: " - "RequestedHeader=%.*s", fieldLen, pField ); + LogWarn( ( "Header not found in response buffer: " + "RequestedHeader=%.*s", fieldLen, pField ) ); returnStatus = HTTP_HEADER_NOT_FOUND; } @@ -1206,11 +1206,11 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, { /* The response buffer is invalid as only the header field was found * in the ": \r\n" format of an HTTP header. */ - LogErrorWithArgs( "Unable to find header value in response: " - "Response data is invalid: " - "RequestedHeader=%.*s, ParserError=%s", - fieldLen, pField, - http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + LogError( ( "Unable to find header value in response: " + "Response data is invalid: " + "RequestedHeader=%.*s, ParserError=%s", + fieldLen, pField, + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; } else @@ -1220,10 +1220,10 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /* Header is found. */ assert( ( context.fieldFound == 1u ) && ( context.valueFound == 1u ) ); - LogDebugWithArgs( "Found requested header in response: " - "HeaderName=%.*s, HeaderValue=%.*s", - fieldLen, pField, - *pValueLen, *pValueLoc ); + LogDebug( ( "Found requested header in response: " + "HeaderName=%.*s, HeaderValue=%.*s", + fieldLen, pField, + *pValueLen, *pValueLoc ) ); } /* If the header field-value pair is found in response, then the return value of "on_header_value" @@ -1231,8 +1231,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, if( ( returnStatus == HTTP_SUCCESS ) && ( ( parser.http_errno != HPE_CB_header_value ) ) ) { - LogErrorWithArgs( "Header found in response but http-parser returned error: ParserError=%s", - http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + LogError( ( "Header found in response but http-parser returned error: ParserError=%s", + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INTERNAL_ERROR; } @@ -1241,8 +1241,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, else if( ( returnStatus == HTTP_HEADER_NOT_FOUND ) && ( ( parser.http_errno != HPE_OK ) ) ) { - LogErrorWithArgs( "Header not found in response: http-parser returned error: ParserError=%s", - http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ); + LogError( ( "Header not found in response: http-parser returned error: ParserError=%s", + http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; } else @@ -1265,39 +1265,39 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, if( pResponse == NULL ) { - LogError( "Parameter check failed: pResponse is NULL." ); + LogError( ( "Parameter check failed: pResponse is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pResponse->pBuffer == NULL ) { - LogError( "Parameter check failed: pResponse->pBuffer is NULL." ); + LogError( ( "Parameter check failed: pResponse->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pResponse->bufferLen == 0u ) { - LogError( "Parameter check failed: pResponse->bufferLen is 0: " - "Buffer len should be > 0." ); + LogError( ( "Parameter check failed: pResponse->bufferLen is 0: " + "Buffer len should be > 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pHeaderName == NULL ) { - LogError( "Parameter check failed: Input header name is NULL." ); + LogError( ( "Parameter check failed: Input header name is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( headerNameLen == 0u ) { - LogError( "Parameter check failed: Input header name length is 0: " - "headerNameLen should be > 0." ); + LogError( ( "Parameter check failed: Input header name length is 0: " + "headerNameLen should be > 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pHeaderValueLoc == NULL ) { - LogError( "Parameter check failed: Output parameter for header value location is NULL." ); + LogError( ( "Parameter check failed: Output parameter for header value location is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else if( pHeaderValueLen == NULL ) { - LogError( "Parameter check failed: Output parameter for header value length is NULL." ); + LogError( ( "Parameter check failed: Output parameter for header value length is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } else @@ -1379,8 +1379,8 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) break; default: - LogWarnWithArgs( "Invalid status code received for string conversion: " - "StatusCode=%d", status ); + LogWarn( ( "Invalid status code received for string conversion: " + "StatusCode=%d", status ) ); } return str; diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 94be7c5e1a..1777c8f00c 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -1,34 +1,23 @@ #ifndef HTTP_CLIENT_INTERNAL_H_ #define HTTP_CLIENT_INTERNAL_H_ -#include "config.h" +#include "http_config.h" -/** - * AWS IoT Embedded C SDK optional specific logging setup. - */ -#ifdef USE_CSDK_LOGGING - #ifdef LOG_LEVEL_HTTP - #define LIBRARY_LOG_LEVEL LOG_LEVEL_HTTP - #else - #ifdef LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL LOG_NONE - #endif - #endif - #define LIBRARY_LOG_NAME ( "HTTP" ) - #include "logging_setup.h" -#else /* ifdef USE_CSDK_LOGGING */ -/* Otherwise please define logging macros in config.h. */ +#ifndef LogError #define LogError( message ) - #define LogErrorWithArgs( format, ... ) +#endif + +#ifndef LogWarn #define LogWarn( message ) - #define LogWarnWithArgs( format, ... ) +#endif + +#ifndef LogInfo #define LogInfo( message ) - #define LogInfoWithArgs( format, ... ) +#endif + +#ifndef LogDebug #define LogDebug( message ) - #define LogDebugWithArgs( format, ... ) -#endif /* ifdef USE_CSDK_LOGGING */ +#endif /** * @brief The HTTP protocol version of this library is HTTP/1.1. diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile index 83fdd9e8eb..f45f7a998f 100644 --- a/libraries/standard/http/test/Makefile +++ b/libraries/standard/http/test/Makefile @@ -47,9 +47,6 @@ FUNCTIONS = addHeader \ # Changes to the above variables should remain above this include. include Mock4thewin.mk -# additional dependencies for all tests -$(TESTS): $(CSDK_ROOT)/platform/posix/clock_posix.o $(CSDK_ROOT)/platform/posix/logging.o - # additional dependency for a specific test HTTPClient_AddHeader.c: addHeader.c HTTPClient_AddRangeHeader.c: addHeader.c convertInt32ToAscii.c diff --git a/libraries/standard/http/test/http_config.h b/libraries/standard/http/test/http_config.h new file mode 100644 index 0000000000..874207e8f8 --- /dev/null +++ b/libraries/standard/http/test/http_config.h @@ -0,0 +1,25 @@ +#ifndef HTTP_CONFIG_H__ +#define HTTP_CONFIG_H__ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the HTTP library. */ +#define LIBRARY_LOG_NAME "HTTP" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef HTTP_CONFIG_H__ */ diff --git a/libraries/standard/http/utest/CMakeLists.txt b/libraries/standard/http/utest/CMakeLists.txt index c877563641..65917fec78 100644 --- a/libraries/standard/http/utest/CMakeLists.txt +++ b/libraries/standard/http/utest/CMakeLists.txt @@ -25,7 +25,6 @@ list(APPEND mock_define_list # list the files you would like to test here list(APPEND real_source_files ${HTTP_SOURCES} - ${LOGGING_SOURCES} ) # list the directories the module under test includes list(APPEND real_include_directories @@ -43,7 +42,7 @@ list(APPEND test_include_directories ${HTTP_INCLUDE_PUBLIC_DIRS} ${HTTP_INCLUDE_PRIVATE_DIRS} ${HTTP_TEST_INCLUDE_DIRS} - "${ROOT_DIR}/platform/include" + ${LOGGING_INCLUDE_DIRS} ) # ============================= (end edit) =================================== diff --git a/libraries/standard/http/utest/config.h b/libraries/standard/http/utest/config.h deleted file mode 100644 index 7f697f3258..0000000000 --- a/libraries/standard/http/utest/config.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#define LOG_LEVEL_HTTP LOG_DEBUG - -#define USE_CSDK_LOGGING 1 - -#ifdef USE_CSDK_LOGGING - -/* Include file for POSIX reference implementation. */ - #include "logging.h" - -/* Define the IotLog logging interface to enable logging. - * This demo maps the macro to the reference POSIX implementation for logging. - * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the - * log, as metadata in each log message. */ - #define SdkLog( messageLevel, pFormat, ... ) \ - Log_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) - -#endif /* ifdef USE_CSDK_LOGGING */ - -#endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/http/utest/http_config.h b/libraries/standard/http/utest/http_config.h new file mode 100644 index 0000000000..874207e8f8 --- /dev/null +++ b/libraries/standard/http/utest/http_config.h @@ -0,0 +1,25 @@ +#ifndef HTTP_CONFIG_H__ +#define HTTP_CONFIG_H__ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the HTTP library. */ +#define LIBRARY_LOG_NAME "HTTP" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef HTTP_CONFIG_H__ */ diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index 2e9985592b..e968160148 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -1,5 +1,5 @@ # Include filepaths for source and include. -include(mqttFilePaths.cmake) +include( mqttFilePaths.cmake ) # MQTT library target. add_library( mqtt @@ -9,7 +9,8 @@ add_library( mqtt target_include_directories( mqtt PUBLIC ${MQTT_INCLUDE_PUBLIC_DIRS} ) # MQTT private include path. -target_include_directories( mqtt PRIVATE ${MQTT_INCLUDE_PRIVATE_DIRS} ) +target_include_directories( mqtt PRIVATE ${MQTT_INCLUDE_PRIVATE_DIRS} + ${LOGGING_INCLUDE_DIRS} ) # Organization of MQTT in IDE projects. set_target_properties( mqtt PROPERTIES FOLDER libraries/standard ) @@ -18,5 +19,5 @@ source_group( src FILES ${MQTT_SOURCES} ) source_group( src\\private FILES src/private/mqtt_internal.h ) if(BUILD_TESTS) - add_subdirectory(utest) + add_subdirectory( utest ) endif() diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index e1b86c20ae..98bc2fd233 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -22,7 +22,7 @@ #ifndef MQTT_H #define MQTT_H -#include "config.h" +#include "mqtt_config.h" #include "mqtt_lightweight.h" struct MQTTApplicationCallbacks; diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 943694c613..a0b8fbf415 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -34,7 +34,7 @@ #include #include -#include "config.h" +#include "mqtt_config.h" /* MQTT packet types. */ #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ @@ -56,7 +56,7 @@ /** * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. */ -#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4UL ) +#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4UL ) struct MQTTFixedBuffer; typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; @@ -519,7 +519,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket * * @return #MQTTSuccess on successful extraction of type and length, * #MQTTRecvFailed on transport receive failure, - * #MQTTBadResponse if an invalid packet is read, and + * #MQTTBadResponse if an invalid packet is read, and * #MQTTNoDataAvailable if there is nothing to read. */ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index b7d1137f9c..10b0adb823 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -229,15 +229,15 @@ static int32_t sendPacket( MQTTContext_t * pContext, bytesRemaining -= ( size_t ) bytesSent; totalBytesSent += bytesSent; pIndex += bytesSent; - LogDebugWithArgs( "Bytes sent=%d, bytes remaining=%ul," - "total bytes sent=%d.", - bytesSent, - bytesRemaining, - totalBytesSent ); + LogDebug( ( "Bytes sent=%d, bytes remaining=%ul," + "total bytes sent=%d.", + bytesSent, + bytesRemaining, + totalBytesSent ) ); } else { - LogError( "Transport send failed." ); + LogError( ( "Transport send failed." ) ); totalBytesSent = -1; break; } @@ -247,8 +247,8 @@ static int32_t sendPacket( MQTTContext_t * pContext, if( totalBytesSent > -1 ) { pContext->lastPacketTime = sendTime; - LogDebugWithArgs( "Successfully sent packet at time %u.", - sendTime ); + LogDebug( ( "Successfully sent packet at time %u.", + sendTime ) ); } return totalBytesSent; @@ -332,8 +332,8 @@ static int32_t recvExact( const MQTTContext_t * const pContext, } else { - LogErrorWithArgs( "Network error while receiving packet: ReturnCode=%d", - bytesRecvd ); + LogError( ( "Network error while receiving packet: ReturnCode=%d", + bytesRecvd ) ); totalBytesRecvd = bytesRecvd; receiveError = true; } @@ -341,7 +341,7 @@ static int32_t recvExact( const MQTTContext_t * const pContext, if( ( bytesRemaining > 0U ) && ( calculateElapsedTime( getTimeStampMs(), entryTimeMs ) > timeoutMs ) ) { - LogError( "Time expired while receiving packet." ); + LogError( ( "Time expired while receiving packet." ) ); receiveError = true; } } @@ -379,10 +379,10 @@ static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, if( bytesReceived != ( int32_t ) bytesToReceive ) { - LogErrorWithArgs( "Receive error while discarding packet." - "ReceivedBytes=%d, ExpectedBytes=%u.", - bytesReceived, - bytesToReceive ); + LogError( ( "Receive error while discarding packet." + "ReceivedBytes=%d, ExpectedBytes=%u.", + bytesReceived, + bytesToReceive ) ); receiveError = true; } else @@ -397,7 +397,7 @@ static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, } else { - LogError( "Time expired while discarding packet." ); + LogError( ( "Time expired while discarding packet." ) ); receiveError = true; } } @@ -405,8 +405,8 @@ static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, if( totalBytesReceived == remainingLength ) { - LogErrorWithArgs( "Dumped packet. DumpedBytes=%d.", - totalBytesReceived ); + LogError( ( "Dumped packet. DumpedBytes=%d.", + totalBytesReceived ) ); /* Packet dumped, so no data is available. */ status = MQTTNoDataAvailable; } @@ -428,10 +428,11 @@ static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, if( incomingPacket.remainingLength > pContext->networkBuffer.size ) { - LogErrorWithArgs( "Incoming packet length %u exceeds network buffer size %u." - "Incoming packet will be dumped.", - incomingPacket.remainingLength, - pContext->networkBuffer ); + LogError( ( "Incoming packet will be dumped: " + "Packet length exceeds network buffer size." + "PacketSize=%u, NetworkBufferSize=%u", + incomingPacket.remainingLength, + pContext->networkBuffer.size ) ); status = discardPacket( pContext, incomingPacket.remainingLength, remainingTimeMs ); @@ -444,15 +445,15 @@ static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, if( bytesReceived == ( int32_t ) bytesToReceive ) { /* Receive successful, bytesReceived == bytesToReceive. */ - LogInfoWithArgs( "Packet received. ReceivedBytes=%d.", - bytesReceived ); + LogInfo( ( "Packet received. ReceivedBytes=%d.", + bytesReceived ) ); } else { - LogErrorWithArgs( "Packet reception failed. ReceivedBytes=%d, " - "ExpectedBytes=%u.", - bytesReceived, - bytesToReceive ); + LogError( ( "Packet reception failed. ReceivedBytes=%d, " + "ExpectedBytes=%u.", + bytesReceived, + bytesToReceive ) ); status = MQTTRecvFailed; } } @@ -525,19 +526,19 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, if( newState == MQTTStateNull ) { - LogErrorWithArgs( "Failed to update state of publish %u.", - packetId ); + LogError( ( "Failed to update state of publish %u.", + packetId ) ); status = MQTTIllegalState; } } else { - LogErrorWithArgs( "Failed to send ACK packet: PacketType=%02x, " - "SentBytes=%d, " - "PacketSize=%u", - packetTypeByte, - bytesSent, - MQTT_PUBLISH_ACK_PACKET_SIZE ); + LogError( ( "Failed to send ACK packet: PacketType=%02x, " + "SentBytes=%d, " + "PacketSize=%u", + packetTypeByte, + bytesSent, + MQTT_PUBLISH_ACK_PACKET_SIZE ) ); status = MQTTSendFailed; } } @@ -597,7 +598,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, assert( pIncomingPacket != NULL ); status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo ); - LogInfoWithArgs( "De-serialized incoming PUBLISH packet: DeserializerResult=%d", status ); + LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%d", status ) ); if( status == MQTTSuccess ) { @@ -605,8 +606,8 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, packetIdentifier, MQTT_RECEIVE, publishInfo.qos ); - LogInfoWithArgs( "State record updated. New state=%d.", - publishRecordState ); + LogInfo( ( "State record updated. New state=%d.", + publishRecordState ) ); /* Send PUBACK or PUBREC if necessary. */ status = sendPublishAcks( pContext, @@ -649,7 +650,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, case MQTT_PACKET_TYPE_PUBCOMP: ackType = getAckFromPacketType( pIncomingPacket->type ); status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); - LogInfoWithArgs( "Ack packet deserialized with result: %d.", status ); + LogInfo( ( "Ack packet deserialized with result: %d.", status ) ); if( status == MQTTSuccess ) { @@ -657,8 +658,8 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, packetIdentifier, ackType, MQTT_RECEIVE ); - LogInfoWithArgs( "State record updated. New state=%d.", - publishRecordState ); + LogInfo( ( "State record updated. New state=%d.", + publishRecordState ) ); /* Send PUBREL or PUBCOMP if necessary. */ status = sendPublishAcks( pContext, @@ -707,8 +708,8 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, default: /* Bad response from the server. */ - LogErrorWithArgs( "Unexpected packet type from server: PacketType=%02x.", - pIncomingPacket->type ); + LogError( ( "Unexpected packet type from server: PacketType=%02x.", + pIncomingPacket->type ) ); status = MQTTBadResponse; break; } @@ -728,20 +729,20 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co /* Validate all the parameters. */ if( ( pContext == NULL ) || ( pSubscriptionList == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pSubscriptionList=%p.", - pContext, - pSubscriptionList ); + LogError( ( "Argument cannot be NULL: pContext=%p, " + "pSubscriptionList=%p.", + pContext, + pSubscriptionList ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0UL ) { - LogError( "Subscription count is 0." ); + LogError( ( "Subscription count is 0." ) ); status = MQTTBadParameter; } else if( packetId == 0U ) { - LogError( "Packet Id for subscription packet is 0." ); + LogError( ( "Packet Id for subscription packet is 0." ) ); status = MQTTBadParameter; } else @@ -772,13 +773,13 @@ static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - LogError( "Transport send failed for PUBLISH header." ); + LogError( ( "Transport send failed for PUBLISH header." ) ); status = MQTTSendFailed; } else { - LogDebugWithArgs( "Sent %d bytes of PUBLISH header.", - bytesSent ); + LogDebug( ( "Sent %d bytes of PUBLISH header.", + bytesSent ) ); /* Send Payload. */ bytesSent = sendPacket( pContext, @@ -787,13 +788,13 @@ static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - LogError( "Transport send failed for PUBLISH payload." ); + LogError( ( "Transport send failed for PUBLISH payload." ) ); status = MQTTSendFailed; } else { - LogDebugWithArgs( "Sent %d bytes of PUBLISH payload.", - bytesSent ); + LogDebug( ( "Sent %d bytes of PUBLISH payload.", + bytesSent ) ); } } @@ -857,10 +858,10 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, } else { - LogErrorWithArgs( "Incorrect packet type %X received while expecting" - " CONNACK(%X).", - pIncomingPacket->type, - MQTT_PACKET_TYPE_CONNACK ); + LogError( ( "Incorrect packet type %X received while expecting" + " CONNACK(%X).", + pIncomingPacket->type, + MQTT_PACKET_TYPE_CONNACK ) ); status = MQTTBadResponse; } } @@ -876,12 +877,12 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, if( status != MQTTSuccess ) { - LogErrorWithArgs( "CONNACK recv failed with status = %u.", - status ); + LogError( ( "CONNACK recv failed with status = %u.", + status ) ); } else { - LogInfo( "Received MQTT CONNACK successfully from broker." ); + LogInfo( ( "Received MQTT CONNACK successfully from broker." ) ); } return status; @@ -900,14 +901,14 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, if( ( pContext == NULL ) || ( pTransportInterface == NULL ) || ( pCallbacks == NULL ) || ( pNetworkBuffer == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pTransportInterface=%p " - "pCallbacks=%p " - "pNetworkBuffer=%p.", - pContext, - pTransportInterface, - pCallbacks, - pNetworkBuffer ); + LogError( ( "Argument cannot be NULL: pContext=%p, " + "pTransportInterface=%p " + "pCallbacks=%p " + "pNetworkBuffer=%p.", + pContext, + pTransportInterface, + pCallbacks, + pNetworkBuffer ) ); status = MQTTBadParameter; } else @@ -941,10 +942,10 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( ( pContext == NULL ) || ( pConnectInfo == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pConnectInfo=%p.", - pContext, - pConnectInfo ); + LogError( ( "Argument cannot be NULL: pContext=%p, " + "pConnectInfo=%p.", + pContext, + pConnectInfo ) ); status = MQTTBadParameter; } @@ -955,9 +956,9 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, pWillInfo, &remainingLength, &packetSize ); - LogDebugWithArgs( "CONNECT packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ) ); } if( status == MQTTSuccess ) @@ -976,13 +977,13 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - LogError( "Transport send failed for CONNECT packet." ); + LogError( ( "Transport send failed for CONNECT packet." ) ); status = MQTTSendFailed; } else { - LogDebugWithArgs( "Sent %d bytes of CONNECT packet.", - bytesSent ); + LogDebug( ( "Sent %d bytes of CONNECT packet.", + bytesSent ) ); } } @@ -997,13 +998,13 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - LogInfo( "MQTT connection established with the broker." ); + LogInfo( ( "MQTT connection established with the broker." ) ); pContext->connectStatus = MQTTConnected; } else { - LogErrorWithArgs( "MQTT connection failed with status = %u.", - status ); + LogError( ( "MQTT connection failed with status = %u.", + status ) ); } return status; @@ -1032,9 +1033,9 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, subscriptionCount, &remainingLength, &packetSize ); - LogDebugWithArgs( "SUBSCRIBE packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebug( ( "SUBSCRIBE packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ) ); } if( status == MQTTSuccess ) @@ -1056,13 +1057,13 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - LogError( "Transport send failed for SUBSCRIBE packet." ); + LogError( ( "Transport send failed for SUBSCRIBE packet." ) ); status = MQTTSendFailed; } else { - LogDebugWithArgs( "Sent %d bytes of SUBSCRIBE packet.", - bytesSent ); + LogDebug( ( "Sent %d bytes of SUBSCRIBE packet.", + bytesSent ) ); } } @@ -1083,16 +1084,16 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, /* Validate arguments. */ if( ( pContext == NULL ) || ( pPublishInfo == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pContext=%p, " - "pPublishInfo=%p.", - pContext, - pPublishInfo ); + LogError( ( "Argument cannot be NULL: pContext=%p, " + "pPublishInfo=%p.", + pContext, + pPublishInfo ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { - LogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", - pPublishInfo->qos ); + LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ) ); status = MQTTBadParameter; } else @@ -1106,9 +1107,9 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, status = MQTT_GetPublishPacketSize( pPublishInfo, &remainingLength, &packetSize ); - LogDebugWithArgs( "PUBLISH packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebug( ( "PUBLISH packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ) ); } if( status == MQTTSuccess ) @@ -1118,8 +1119,8 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, remainingLength, &( pContext->networkBuffer ), &headerSize ); - LogDebugWithArgs( "Serialized PUBLISH header size is %lu.", - headerSize ); + LogDebug( ( "Serialized PUBLISH header size is %lu.", + headerSize ) ); } if( status == MQTTSuccess ) @@ -1161,11 +1162,11 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, if( publishStatus == MQTTStateNull ) { - LogErrorWithArgs( "Update state for publish failed with status =%u." - " However PUBLISH packet is sent to the broker." - " Any further handling of ACKs for the packet Id" - " will fail.", - publishStatus ); + LogError( ( "Update state for publish failed with status =%u." + " However PUBLISH packet is sent to the broker." + " Any further handling of ACKs for the packet Id" + " will fail.", + publishStatus ) ); /* TODO. Need to remove this update once MQTT_UpdateStatePublish is * refactored with return type of MQTTStatus_t. */ @@ -1176,8 +1177,8 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, if( status != MQTTSuccess ) { - LogErrorWithArgs( "MQTT PUBLISH failed with status=%u.", - status ); + LogError( ( "MQTT PUBLISH failed with status=%u.", + status ) ); } return status; @@ -1193,7 +1194,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) if( pContext == NULL ) { - LogError( "pContext is NULL." ); + LogError( ( "pContext is NULL." ) ); status = MQTTBadParameter; } @@ -1204,12 +1205,12 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) if( status == MQTTSuccess ) { - LogDebugWithArgs( "MQTT PINGREQ packet size is %lu.", - packetSize ); + LogDebug( ( "MQTT PINGREQ packet size is %lu.", + packetSize ) ); } else { - LogError( "Failed to get the PINGREQ packet size." ) + LogError( ( "Failed to get the PINGREQ packet size." ) ); } } @@ -1228,15 +1229,15 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) if( bytesSent < 0 ) { - LogError( "Transport send failed for PINGREQ packet." ); + LogError( ( "Transport send failed for PINGREQ packet." ) ); status = MQTTSendFailed; } else { pContext->pingReqSendTimeMs = pContext->lastPacketTime; pContext->waitingForPingResp = true; - LogDebugWithArgs( "Sent %d bytes of PINGREQ packet.", - bytesSent ); + LogDebug( ( "Sent %d bytes of PINGREQ packet.", + bytesSent ) ); } } @@ -1266,9 +1267,9 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, subscriptionCount, &remainingLength, &packetSize ); - LogDebugWithArgs( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ); + LogDebug( ( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ) ); } if( status == MQTTSuccess ) @@ -1290,13 +1291,13 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, if( bytesSent < 0 ) { - LogError( "Transport send failed for UNSUBSCRIBE packet." ); + LogError( ( "Transport send failed for UNSUBSCRIBE packet." ) ); status = MQTTSendFailed; } else { - LogDebugWithArgs( "Sent %d bytes of UNSUBSCRIBE packet.", - bytesSent ); + LogDebug( ( "Sent %d bytes of UNSUBSCRIBE packet.", + bytesSent ) ); } } @@ -1314,7 +1315,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) /* Validate arguments. */ if( pContext == NULL ) { - LogError( "pContext cannot be NULL." ); + LogError( ( "pContext cannot be NULL." ) ); status = MQTTBadParameter; } @@ -1322,8 +1323,8 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) { /* Get MQTT DISCONNECT packet size. */ status = MQTT_GetDisconnectPacketSize( &packetSize ); - LogDebugWithArgs( "MQTT DISCONNECT packet size is %lu.", - packetSize ); + LogDebug( ( "MQTT DISCONNECT packet size is %lu.", + packetSize ) ); } if( status == MQTTSuccess ) @@ -1340,19 +1341,19 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) if( bytesSent < 0 ) { - LogError( "Transport send failed for DISCONNECT packet." ); + LogError( ( "Transport send failed for DISCONNECT packet." ) ); status = MQTTSendFailed; } else { - LogDebugWithArgs( "Sent %d bytes of DISCONNECT packet.", - bytesSent ); + LogDebug( ( "Sent %d bytes of DISCONNECT packet.", + bytesSent ) ); } } if( status == MQTTSuccess ) { - LogInfo( "Disconnected from the broker." ); + LogInfo( ( "Disconnected from the broker." ) ); pContext->connectStatus = MQTTNotConnected; } @@ -1377,7 +1378,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, } else { - LogError( "MQTT Context cannot be NULL." ); + LogError( ( "MQTT Context cannot be NULL." ) ); } while( status == MQTTSuccess ) @@ -1401,8 +1402,8 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, } else if( status != MQTTSuccess ) { - LogErrorWithArgs( "Receiving incoming packet length failed. Status=%d", - status ); + LogError( ( "Receiving incoming packet length failed. Status=%d", + status ) ); } else { @@ -1436,7 +1437,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, if( status != MQTTSuccess ) { - LogErrorWithArgs( "Exiting receive loop. Error status=%d", status ); + LogError( ( "Exiting receive loop. Error status=%d", status ) ); } /* Recalculate remaining time and check if loop should exit. */ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 5580082803..4d38ed58b8 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -272,9 +272,9 @@ static size_t remainingLengthEncodedSize( size_t length ) encodedSize = 4U; } - LogDebugWithArgs( "Encoded size for length =%ul is %ul.", - length, - encodedSize ); + LogDebug( ( "Encoded size for length =%ul is %ul.", + length, + encodedSize ) ); return encodedSize; } @@ -378,12 +378,12 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, /* Ensure that the given payload fits within the calculated limit. */ if( pPublishInfo->payloadLength > payloadLimit ) { - LogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " - "%lu so as not to exceed the maximum " - "remaining length of MQTT 3.1.1 packet( %lu ).", - pPublishInfo->payloadLength, - payloadLimit, - MQTT_MAX_REMAINING_LENGTH ); + LogError( ( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + pPublishInfo->payloadLength, + payloadLimit, + MQTT_MAX_REMAINING_LENGTH ) ); status = false; } else @@ -399,12 +399,12 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, /* Check that the given payload fits within the size allowed by MQTT spec. */ if( pPublishInfo->payloadLength > payloadLimit ) { - LogErrorWithArgs( "PUBLISH payload length of %lu cannot exceed " - "%lu so as not to exceed the maximum " - "remaining length of MQTT 3.1.1 packet( %lu ).", - pPublishInfo->payloadLength, - payloadLimit, - MQTT_MAX_REMAINING_LENGTH ); + LogError( ( "PUBLISH payload length of %lu cannot exceed " + "%lu so as not to exceed the maximum " + "remaining length of MQTT 3.1.1 packet( %lu ).", + pPublishInfo->payloadLength, + payloadLimit, + MQTT_MAX_REMAINING_LENGTH ) ); status = false; } else @@ -418,9 +418,9 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, } } - LogDebugWithArgs( "PUBLISH packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ); + LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ) ); return status; } @@ -450,12 +450,12 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, if( pPublishInfo->qos == MQTTQoS1 ) { - LogDebug( "Adding QoS as QoS1 in PUBLISH flags." ); + LogDebug( ( "Adding QoS as QoS1 in PUBLISH flags." ) ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); } else if( pPublishInfo->qos == MQTTQoS2 ) { - LogDebug( "Adding QoS as QoS2 in PUBLISH flags." ); + LogDebug( ( "Adding QoS as QoS2 in PUBLISH flags." ) ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); } else @@ -465,13 +465,13 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, if( pPublishInfo->retain ) { - LogDebug( "Adding retain bit in PUBLISH flags." ); + LogDebug( ( "Adding retain bit in PUBLISH flags." ) ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); } if( pPublishInfo->dup ) { - LogDebug( "Adding dup bit in PUBLISH flags." ); + LogDebug( ( "Adding dup bit in PUBLISH flags." ) ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ); } @@ -489,7 +489,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, /* A packet identifier is required for QoS 1 and 2 messages. */ if( pPublishInfo->qos > MQTTQoS0 ) { - LogDebug( "Adding packet Id in PUBLISH packet." ); + LogDebug( ( "Adding packet Id in PUBLISH packet." ) ); /* Place the packet identifier into the PUBLISH packet. */ *pIndex = UINT16_HIGH_BYTE( packetIdentifier ); *( pIndex + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); @@ -503,8 +503,8 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, if( ( pPublishInfo->payloadLength > 0U ) && ( serializePayload ) ) { - LogDebugWithArgs( "Copying PUBLISH payload of length =%lu to buffer", - pPublishInfo->payloadLength ); + LogDebug( ( "Copying PUBLISH payload of length =%lu to buffer", + pPublishInfo->payloadLength ) ); /* Typecast const void * typed payload buffer to const uint8_t *. * This is to use same type buffers in memcpy. */ @@ -602,8 +602,8 @@ static bool incomingPacketValid( uint8_t packetType ) /* Any other packet type is invalid. */ default: - LogWarnWithArgs( "Incoming packet invalid: Packet type=%u", - packetType ); + LogWarn( ( "Incoming packet invalid: Packet type=%u", + packetType ) ); break; } @@ -624,8 +624,8 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, /* Check that the "Remaining length" is greater than the minimum. */ if( remainingLength < qos0Minimum ) { - LogDebugWithArgs( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum ); + LogDebug( ( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum ) ); status = MQTTBadResponse; } @@ -637,8 +637,8 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, * packet identifier. */ if( remainingLength < ( qos0Minimum + 2U ) ) { - LogDebugWithArgs( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum + 2U ); + LogDebug( ( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", + qos0Minimum + 2U ) ); status = MQTTBadResponse; } @@ -662,7 +662,7 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) ) { - LogDebug( "Bad QoS: 3." ); + LogDebug( ( "Bad QoS: 3." ) ); status = MQTTBadResponse; } @@ -684,21 +684,21 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, if( status == MQTTSuccess ) { - LogDebugWithArgs( "QoS is %d.", pPublishInfo->qos ); + LogDebug( ( "QoS is %d.", pPublishInfo->qos ) ); /* Parse the Retain bit. */ pPublishInfo->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - LogDebugWithArgs( "Retain bit is %d.", pPublishInfo->retain ); + LogDebug( ( "Retain bit is %d.", pPublishInfo->retain ) ); /* Parse the DUP bit. */ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) ) { - LogDebug( "DUP is 1." ); + LogDebug( ( "DUP is 1." ) ); } else { - LogDebug( "DUP is 0." ); + LogDebug( ( "DUP is 0." ) ); } } @@ -718,27 +718,27 @@ static void logConnackResponse( uint8_t responseCode ) switch( responseCode ) { case 0u: - LogInfo( "Connection accepted." ); + LogInfo( ( "Connection accepted." ) ); break; case 1u: - LogInfo( "Connection refused: unacceptable protocol version." ); + LogInfo( ( "Connection refused: unacceptable protocol version." ) ); break; case 2u: - LogInfo( "Connection refused: identifier rejected." ); + LogInfo( ( "Connection refused: identifier rejected." ) ); break; case 3u: - LogInfo( "Connection refused: server unavailable" ); + LogInfo( ( "Connection refused: server unavailable" ) ); break; case 4u: - LogInfo( "Connection refused: bad user name or password." ); + LogInfo( ( "Connection refused: bad user name or password." ) ); break; case 5u: - LogInfo( "Connection refused: not authorized." ); + LogInfo( ( "Connection refused: not authorized." ) ); break; default: @@ -762,8 +762,8 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, * "Remaining length" of 2. */ if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) { - LogErrorWithArgs( "CONNACK does not have remaining length of %d.", - MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + LogError( ( "CONNACK does not have remaining length of %d.", + MQTT_PACKET_CONNACK_REMAINING_LENGTH ) ); status = MQTTBadResponse; } @@ -772,7 +772,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, * in CONNACK must be 0. */ else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U ) { - LogError( "Reserved bits in CONNACK incorrect." ); + LogError( ( "Reserved bits in CONNACK incorrect." ) ); status = MQTTBadResponse; } @@ -783,7 +783,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) { - LogWarn( "CONNACK session present bit set." ); + LogWarn( ( "CONNACK session present bit set." ) ); *pSessionPresent = true; /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the @@ -795,7 +795,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, } else { - LogInfo( "CONNACK session present bit not set." ); + LogInfo( ( "CONNACK session present bit not set." ) ); } } @@ -804,8 +804,8 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ if( pRemainingData[ 1 ] > 5U ) { - LogErrorWithArgs( "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ); + LogError( ( "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ) ); status = MQTTBadResponse; } @@ -866,10 +866,10 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * * set the output parameter.*/ if( packetSize > MQTT_MAX_REMAINING_LENGTH ) { - LogErrorWithArgs( "Subscription packet length of %lu exceeds" - "the MQTT 3.1.1 maximum packet length of %lu.", - packetSize, - MQTT_MAX_REMAINING_LENGTH ); + LogError( ( "Subscription packet length of %lu exceeds" + "the MQTT 3.1.1 maximum packet length of %lu.", + packetSize, + MQTT_MAX_REMAINING_LENGTH ) ); status = MQTTBadParameter; } else @@ -885,9 +885,9 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * *pPacketSize = packetSize; } - LogDebugWithArgs( "Subscription packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ); + LogDebug( ( "Subscription packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ) ); return status; } @@ -916,13 +916,13 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, case 0x01: case 0x02: - LogDebugWithArgs( "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); + LogDebug( ( "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ) ); break; case 0x80: - LogDebugWithArgs( "Topic filter %lu refused.", ( unsigned long ) i ); + LogDebug( ( "Topic filter %lu refused.", ( unsigned long ) i ) ); /* Application should remove subscription from the list */ status = MQTTServerRefused; @@ -930,7 +930,7 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, break; default: - LogDebugWithArgs( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + LogDebug( ( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ) ); status = MQTTBadResponse; @@ -964,7 +964,7 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, * packet identifier and at least 1 return code. */ if( remainingLength < 3U ) { - LogDebug( "SUBACK cannot have a remaining length less than 3." ); + LogDebug( ( "SUBACK cannot have a remaining length less than 3." ) ); status = MQTTBadResponse; } else @@ -972,7 +972,7 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ *pPacketIdentifier = UINT16_DECODE( pVariableHeader ); - LogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); + LogDebug( ( "Packet identifier %hu.", *pPacketIdentifier ) ); status = readSubackStatus( remainingLength - sizeof( uint16_t ), pVariableHeader + sizeof( uint16_t ) ); @@ -1000,28 +1000,28 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo /* Validate all the parameters. */ if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " - "pSubscriptionList=%p.", - pBuffer, - pSubscriptionList ); + LogError( ( "Argument cannot be NULL: pBuffer=%p, " + "pSubscriptionList=%p.", + pBuffer, + pSubscriptionList ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) { - LogError( "Subscription count is 0." ); + LogError( ( "Subscription count is 0." ) ); status = MQTTBadParameter; } else if( packetId == 0U ) { - LogError( "Packet Id for subscription packet is 0." ); + LogError( ( "Packet Id for subscription packet is 0." ) ); status = MQTTBadParameter; } else if( packetSize > pBuffer->size ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized packet of size of %lu.", - pBuffer->size, - packetSize ); + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized packet of size of %lu.", + pBuffer->size, + packetSize ) ); status = MQTTNoMemory; } else @@ -1076,10 +1076,10 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming { /* Parse the topic. */ pPublishInfo->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); - LogDebugWithArgs( "Topic name length %hu: %.*s", - pPublishInfo->topicNameLength, - pPublishInfo->topicNameLength, - pPublishInfo->pTopicName ); + LogDebug( ( "Topic name length %hu: %.*s", + pPublishInfo->topicNameLength, + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet * identifier starts immediately after the topic name. */ @@ -1089,7 +1089,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming { *pPacketId = UINT16_DECODE( pPacketIdentifierHigh ); - LogDebugWithArgs( "Packet identifier %hu.", *pPacketId ); + LogDebug( ( "Packet identifier %hu.", *pPacketId ) ); /* Packet identifier cannot be 0. */ if( *pPacketId == 0U ) @@ -1114,7 +1114,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming pPublishInfo->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } - LogDebugWithArgs( "Payload length %hu.", pPublishInfo->payloadLength ); + LogDebug( ( "Payload length %hu.", pPublishInfo->payloadLength ) ); } return status; @@ -1133,8 +1133,8 @@ static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, /* Check that the "Remaining length" of the received ACK is 2. */ if( pAck->remainingLength != MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ) { - LogErrorWithArgs( "ACK does not have remaining length of %d.", - MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ); + LogError( ( "ACK does not have remaining length of %d.", + MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ) ); status = MQTTBadResponse; } @@ -1143,7 +1143,7 @@ static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, /* Extract the packet identifier (third and fourth bytes) from ACK. */ *pPacketIdentifier = UINT16_DECODE( pAck->pRemainingData ); - LogDebugWithArgs( "Packet identifier %hu.", *pPacketIdentifier ); + LogDebug( ( "Packet identifier %hu.", *pPacketIdentifier ) ); /* Packet identifier cannot be 0. */ if( *pPacketIdentifier == 0U ) @@ -1166,8 +1166,8 @@ static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * const pPingres /* Check the "Remaining length" (second byte) of the received PINGRESP is 0. */ if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { - LogErrorWithArgs( "PINGRESP does not have remaining length of %d.", - MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + LogError( ( "PINGRESP does not have remaining length of %d.", + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) ); status = MQTTBadResponse; } @@ -1284,8 +1284,8 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength ); } - LogDebugWithArgs( "Length of serialized CONNECT packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + LogDebug( ( "Length of serialized CONNECT packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ) ); /* Ensure that the difference between the end and beginning of the buffer * is less than the buffer size. */ @@ -1309,11 +1309,11 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pConnectInfo, - pRemainingLength, - pPacketSize ); + LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pConnectInfo, + pRemainingLength, + pPacketSize ) ); status = MQTTBadParameter; } @@ -1359,9 +1359,9 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect *pPacketSize = connectPacketSize; } - LogDebugWithArgs( "CONNECT packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ); + LogDebug( ( "CONNECT packet remaining length=%lu and packet size=%lu.", + *pRemainingLength, + *pPacketSize ) ); } return status; @@ -1382,19 +1382,19 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo /* Validate arguments. */ if( ( pConnectInfo == NULL ) || ( pBuffer == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pConnectInfo=%p, " - "pBuffer=%p.", - pConnectInfo, - pBuffer ); + LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " + "pBuffer=%p.", + pConnectInfo, + pBuffer ) ); status = MQTTBadParameter; } /* Check that the full packet size fits within the given buffer. */ else if( connectPacketSize > pBuffer->size ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized CONNECT packet of size of %lu.", - pBuffer->size, - connectPacketSize ); + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized CONNECT packet of size of %lu.", + pBuffer->size, + connectPacketSize ) ); status = MQTTNoMemory; } else @@ -1421,16 +1421,16 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSub if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pSubscriptionList, - pRemainingLength, - pPacketSize ); + LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pSubscriptionList, + pRemainingLength, + pPacketSize ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) { - LogError( " subscriptionCount is 0." ); + LogError( ( " subscriptionCount is 0." ) ); status = MQTTBadParameter; } else @@ -1444,9 +1444,9 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSub if( status == MQTTBadParameter ) { - LogErrorWithArgs( "SUBSCRIBE packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + LogError( ( "SUBSCRIBE packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ) ); } } @@ -1500,8 +1500,8 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri pIndex++; } - LogDebugWithArgs( "Length of serialized SUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + LogDebug( ( "Length of serialized SUBSCRIBE packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ) ); } return status; @@ -1520,16 +1520,16 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pSubscriptionList=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pSubscriptionList, - pRemainingLength, - pPacketSize ); + LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pSubscriptionList, + pRemainingLength, + pPacketSize ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) { - LogError( "Subscription count is 0." ); + LogError( ( "Subscription count is 0." ) ); status = MQTTBadParameter; } else @@ -1543,9 +1543,9 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS if( status == MQTTBadParameter ) { - LogErrorWithArgs( "UNSUBSCRIBE packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + LogError( ( "UNSUBSCRIBE packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ) ); } } @@ -1596,8 +1596,8 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc pSubscriptionList[ i ].topicFilterLength ); } - LogDebugWithArgs( "Length of serialized UNSUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ); + LogDebug( ( "Length of serialized UNSUBSCRIBE packet is %lu.", + ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ) ); } return status; @@ -1613,19 +1613,19 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pPublishInfo=%p, " - "pRemainingLength=%p, pPacketSize=%p.", - pPublishInfo, - pRemainingLength, - pPacketSize ); + LogError( ( "Argument cannot be NULL: pPublishInfo=%p, " + "pRemainingLength=%p, pPacketSize=%p.", + pPublishInfo, + pRemainingLength, + pPacketSize ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { - LogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " - "topicNameLength=%u.", - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ) ); status = MQTTBadParameter; } else @@ -1634,9 +1634,9 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish * what is allowed in the MQTT standard, return an error. */ if( calculatePublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) { - LogErrorWithArgs( "PUBLISH packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + LogError( ( "PUBLISH packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ) ); status = MQTTBadParameter; } } @@ -1661,32 +1661,32 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " - "pPublishInfo=%p.", - pBuffer, - pPublishInfo ); + LogError( ( "Argument cannot be NULL: pBuffer=%p, " + "pPublishInfo=%p.", + pBuffer, + pPublishInfo ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { - LogErrorWithArgs( "Invalid topic name for PUBLISH: pTopicName=%p, " - "topicNameLength=%u.", - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { - LogErrorWithArgs( "Packet Id is 0 for PUBLISH with QoS=%u.", - pPublishInfo->qos ); + LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ) ); status = MQTTBadParameter; } else if( packetSize > pBuffer->size ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PUBLISH packet of size of %lu.", - pBuffer->size, - packetSize ); + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH packet of size of %lu.", + pBuffer->size, + packetSize ) ); status = MQTTNoMemory; } else @@ -1724,35 +1724,35 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pHeaderSize == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pBuffer=%p, " - "pPublishInfo=%p, pHeaderSize=%p.", - pBuffer, - pPublishInfo, - pHeaderSize ); + LogError( ( "Argument cannot be NULL: pBuffer=%p, " + "pPublishInfo=%p, pHeaderSize=%p.", + pBuffer, + pPublishInfo, + pHeaderSize ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { - LogErrorWithArgs( "Invalid topic name for publish: pTopicName=%p, " - "topicNameLength=%u.", - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); + LogError( ( "Invalid topic name for publish: pTopicName=%p, " + "topicNameLength=%u.", + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { - LogErrorWithArgs( "Packet Id is 0 for publish with QoS=%u.", - pPublishInfo->qos ); + LogError( ( "Packet Id is 0 for publish with QoS=%u.", + pPublishInfo->qos ) ); status = MQTTBadParameter; } else if( ( packetSize - pPublishInfo->payloadLength ) > pBuffer->size ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PUBLISH header packet of size of %lu.", - pBuffer->size, - ( packetSize - pPublishInfo->payloadLength ) ); + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized PUBLISH header packet of size of %lu.", + pBuffer->size, + ( packetSize - pPublishInfo->payloadLength ) ) ); status = MQTTNoMemory; } else @@ -1781,13 +1781,13 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, if( pBuffer == NULL ) { - LogError( "Provided buffer is NULL." ); + LogError( ( "Provided buffer is NULL." ) ); status = MQTTBadParameter; } /* The buffer must be able to fit 4 bytes for the packet. */ else if( pBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) { - LogError( "Insufficient memory for packet." ); + LogError( ( "Insufficient memory for packet." ) ); status = MQTTNoMemory; } else @@ -1806,8 +1806,8 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, break; default: - LogErrorWithArgs( "Packet type is not a publish ACK: Packet type=%02x", - packetType ); + LogError( ( "Packet type is not a publish ACK: Packet type=%02x", + packetType ) ); status = MQTTBadParameter; break; } @@ -1835,7 +1835,7 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) /* Validate arguments. */ if( pBuffer == NULL ) { - LogError( "pBuffer cannot be NULL." ); + LogError( ( "pBuffer cannot be NULL." ) ); status = MQTTBadParameter; } @@ -1843,10 +1843,10 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) { if( pBuffer->size < MQTT_DISCONNECT_PACKET_SIZE ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized DISCONNECT packet of size of %lu.", - pBuffer->size, - MQTT_DISCONNECT_PACKET_SIZE ); + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized DISCONNECT packet of size of %lu.", + pBuffer->size, + MQTT_DISCONNECT_PACKET_SIZE ) ); status = MQTTNoMemory; } } @@ -1868,7 +1868,7 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ) if( pPacketSize == NULL ) { - LogError( "pPacketSize is NULL." ); + LogError( ( "pPacketSize is NULL." ) ); status = MQTTBadParameter; } else @@ -1888,7 +1888,7 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) if( pBuffer == NULL ) { - LogError( "pBuffer is NULL." ); + LogError( ( "pBuffer is NULL." ) ); status = MQTTBadParameter; } @@ -1896,10 +1896,10 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) { if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) { - LogErrorWithArgs( "Buffer size of %lu is not sufficient to hold " - "serialized PINGREQ packet of size of %lu.", - pBuffer->size, - MQTT_PACKET_PINGREQ_SIZE ); + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized PINGREQ packet of size of %lu.", + pBuffer->size, + MQTT_PACKET_PINGREQ_SIZE ) ); status = MQTTNoMemory; } } @@ -1933,17 +1933,17 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) ) { - LogErrorWithArgs( "Argument cannot be NULL: pIncomingPacket=%p, " - "pPacketId=%p, pPublishInfo=%p", - pIncomingPacket, - pPacketId, - pPublishInfo ); + LogError( ( "Argument cannot be NULL: pIncomingPacket=%p, " + "pPacketId=%p, pPublishInfo=%p", + pIncomingPacket, + pPacketId, + pPublishInfo ) ); status = MQTTBadParameter; } else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) { - LogErrorWithArgs( "Packet is not publish. Packet type: %hu.", - pIncomingPacket->type ); + LogError( ( "Packet is not publish. Packet type: %hu.", + pIncomingPacket->type ) ); status = MQTTBadParameter; } else @@ -1964,29 +1964,30 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket if( ( pIncomingPacket == NULL ) ) { - LogError( "pIncomingPacket cannot be NULL." ); + LogError( ( "pIncomingPacket cannot be NULL." ) ); status = MQTTBadParameter; } + /* Pointer for packet identifier cannot be NULL for packets other than * CONNACK and PINGRESP. */ else if( ( pPacketId == NULL ) && ( ( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) && ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) ) { - LogErrorWithArgs( "pPacketId cannot be NULL for packet type %02x.", - pIncomingPacket->type ); + LogError( ( "pPacketId cannot be NULL for packet type %02x.", + pIncomingPacket->type ) ); status = MQTTBadParameter; } /* Pointer for session present cannot be NULL for CONNACK. */ else if( ( pSessionPresent == NULL ) && ( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) ) { - LogError( "pSessionPresent cannot be NULL for CONNACK packet." ); + LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) ); status = MQTTBadParameter; } else if( pIncomingPacket->pRemainingData == NULL ) { - LogError( "Remaining data of incoming packet is NULL." ); + LogError( ( "Remaining data of incoming packet is NULL." ) ); status = MQTTBadParameter; } else @@ -2016,7 +2017,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket /* Any other packet type is invalid. */ default: - LogErrorWithArgs( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ); + LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ) ); status = MQTTBadResponse; break; } @@ -2049,8 +2050,8 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu } else { - LogErrorWithArgs( "Incoming packet invalid: Packet type=%u", - pIncomingPacket->type ); + LogError( ( "Incoming packet invalid: Packet type=%u", + pIncomingPacket->type ) ); status = MQTTBadResponse; } } diff --git a/libraries/standard/mqtt/src/private/mqtt_internal.h b/libraries/standard/mqtt/src/private/mqtt_internal.h index 8c0718463c..55f6d0e8dc 100644 --- a/libraries/standard/mqtt/src/private/mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/mqtt_internal.h @@ -1,31 +1,22 @@ #ifndef MQTT_INTERNAL_H_ #define MQTT_INTERNAL_H_ -/** - * AWS IoT Embedded C SDK optional specific logging setup. - */ -#ifdef USE_AWS_IOT_CSDK_LOGGING - #ifdef LOG_LEVEL_MQTT - #define LIBRARY_LOG_LEVEL LOG_LEVEL_MQTT - #else - #ifdef LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL LOG_NONE - #endif - #endif - #define LIBRARY_LOG_NAME ( "MQTT" ) - #include "logging_setup.h" -#else /* ifdef USE_AWS_IOT_CSDK_LOGGING */ -/* Otherwise please define logging macros in config.h. */ +#include "mqtt_config.h" + +#ifndef LogError #define LogError( message ) - #define LogErrorWithArgs( format, ... ) +#endif + +#ifndef LogWarn #define LogWarn( message ) - #define LogWarnWithArgs( format, ... ) +#endif + +#ifndef LogInfo #define LogInfo( message ) - #define LogInfoWithArgs( format, ... ) +#endif + +#ifndef LogDebug #define LogDebug( message ) - #define LogDebugWithArgs( format, ... ) -#endif /* ifdef USE_AWS_IOT_CSDK_LOGGING */ +#endif #endif /* ifndef MQTT_INTERNAL_H_ */ diff --git a/libraries/standard/mqtt/utest/CMakeLists.txt b/libraries/standard/mqtt/utest/CMakeLists.txt index bd2f332f7f..66cdc4d829 100644 --- a/libraries/standard/mqtt/utest/CMakeLists.txt +++ b/libraries/standard/mqtt/utest/CMakeLists.txt @@ -15,7 +15,7 @@ list(APPEND mock_list list(APPEND mock_include_list . ${MQTT_INCLUDE_PUBLIC_DIRS} - "${ROOT_DIR}/platform/include" + ${LOGGING_INCLUDE_DIRS} ) #list the definitions of your mocks to control what to be included list(APPEND mock_define_list @@ -33,7 +33,7 @@ list(APPEND real_include_directories . ${MQTT_INCLUDE_PUBLIC_DIRS} ${MQTT_INCLUDE_PRIVATE_DIRS} - "${ROOT_DIR}/platform/include" + ${LOGGING_INCLUDE_DIRS} ) # ===================== Create UnitTest Code here (edit) ===================== @@ -43,7 +43,7 @@ list(APPEND test_include_directories . ${MQTT_INCLUDE_PUBLIC_DIRS} ${MQTT_INCLUDE_PRIVATE_DIRS} - "${ROOT_DIR}/platform/include" + ${LOGGING_INCLUDE_DIRS} ) # ============================= (end edit) =================================== diff --git a/libraries/standard/mqtt/utest/config.h b/libraries/standard/mqtt/utest/mqtt_config.h similarity index 58% rename from libraries/standard/mqtt/utest/config.h rename to libraries/standard/mqtt/utest/mqtt_config.h index f0a59cde90..d9cf12124d 100644 --- a/libraries/standard/mqtt/utest/config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -1,29 +1,26 @@ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ -#define LOG_LEVEL_HTTP LOG_DEBUG +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ -#define USE_CSDK_LOGGING 1 - -#ifdef USE_CSDK_LOGGING +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ -/* Include file for POSIX reference implementation. */ - #include "logging.h" +/* Include header that defines log levels. */ +#include "logging_levels.h" -/* Define the IotLog logging interface to enable logging. - * This demo maps the macro to the reference POSIX implementation for logging. - * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the - * log, as metadata in each log message. */ - #define SdkLog( messageLevel, pFormat, ... ) \ - Log_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) +/* Configure name and log level for the MQTT library. */ +#define LIBRARY_LOG_NAME "MQTT" +#define LIBRARY_LOG_LEVEL LOG_INFO -#endif /* ifdef USE_CSDK_LOGGING */ +#include "logging_stack.h" +/************ End of logging configuration ****************/ /* Set network context to socket (int). */ typedef int MQTTNetworkContext_t; @@ -60,4 +57,4 @@ typedef int MQTTNetworkContext_t; #define CLIENT_IDENTIFIER "testclient" -#endif /* ifndef CONFIG_H */ +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/utilities/include/logging_setup.h b/libraries/standard/utilities/include/logging_setup.h deleted file mode 100644 index 4a7ffab39c..0000000000 --- a/libraries/standard/utilities/include/logging_setup.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Common Logging Framework - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file logging_setup.h - * @brief Defines the common logging framework that calls #Log interface. - */ - -#ifndef LOGGING_SETUP_H_ -#define LOGGING_SETUP_H_ - -/* The config header is always included first. */ -#include "config.h" - -/* Include header for logging level macros. */ -#include "logging_levels.h" - - -/** - * @functionpage{Log,logging,log} - */ - -/** - * @def Log( messageLevel, pFormat, ... ) - * @brief The common logging interface for all libraries. - * - * This acts as a hook for supplying a logging implementation stack - * for all libraries that log through this macro interface. - * This macro should be mapped to the platform's logging library. - * - * @param[in] messageLevel The integer code for the log level of the message. - * Must be one of #LOG_ERROR, #LOG_WARN, #LOG_INFO or #LOG_DEBUG. - * Must not be #LOG_NONE. - * @param[in] pFormat The format string for the log message. - * @param[in] ... The variadic argument list for the format string. - * - * @return No return value. - */ - -/** - * @def LogError( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_ERROR. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_ERROR, "%s" , message ) - * @endcode - */ - -/** - * @def LogErrorWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #LOG_ERROR. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_ERROR, pFormat, ... ) - * @endcode - */ - -/** - * @def LogWarn( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_WARN. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_WARN, "%s" , message ) - * @endcode - */ - -/** - * @def LogWarnWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #LOG_WARN. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_WARN, pFormat, ... ) - * @endcode - */ - -/** - * @def LogInfo( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_INFO. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_INFO, "%s" , message ) - * @endcode - */ - -/** - * @def LogInfoWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #LOG_INFO. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_INFO, pFormat, ... ) - * @endcode - */ - -/** - * @def LogDebug( message ) - * @brief Abbreviated logging macro for stand-alone message strings at level #LOG_DEBUG. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_DEBUG, "%s" , message ) - * @endcode - */ - -/** - * @def LogDebugWithArgs( pFormat, ... ) - * @brief Abbreviated logging macro for messages with arguments at level #LOG_DEBUG. - * - * Equivalent to: - * @code{c} - * SdkLog( LOG_DEBUG, pFormat, ... ) - * @endcode - */ - -/* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ -#if !defined( LIBRARY_LOG_LEVEL ) || \ - ( ( LIBRARY_LOG_LEVEL != LOG_NONE ) && \ - ( LIBRARY_LOG_LEVEL != LOG_ERROR ) && \ - ( LIBRARY_LOG_LEVEL != LOG_WARN ) && \ - ( LIBRARY_LOG_LEVEL != LOG_INFO ) && \ - ( LIBRARY_LOG_LEVEL != LOG_DEBUG ) ) - #error "Please define LIBRARY_LOG_LEVEL as either LOG_NONE, LOG_ERROR, LOG_WARN, LOG_INFO, or LOG_DEBUG." -#else - #if LIBRARY_LOG_LEVEL != LOG_NONE - #if !defined( SdkLog ) - #error "Please define the common logging interface macro, sdkLog(messageLevel, pFormat, ...)." - #endif - #endif - - #if LIBRARY_LOG_LEVEL == LOG_DEBUG - /* All log level messages will logged. */ - #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) SdkLog( LOG_WARN, "%s", message ) - #define LogWarnWithArgs( pFormat, ... ) SdkLog( LOG_WARN, pFormat, __VA_ARGS__ ) - #define LogInfo( message ) SdkLog( LOG_INFO, "%s", message ) - #define LogInfoWithArgs( pFormat, ... ) SdkLog( LOG_INFO, pFormat, __VA_ARGS__ ) - #define LogDebug( message ) SdkLog( LOG_DEBUG, "%s", message ) - #define LogDebugWithArgs( pFormat, ... ) SdkLog( LOG_DEBUG, pFormat, __VA_ARGS__ ) - - #elif LIBRARY_LOG_LEVEL == LOG_INFO - /* Only INFO, WARNING and ERROR messages will be logged. */ - #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) SdkLog( LOG_WARN, "%s", message ) - #define LogWarnWithArgs( pFormat, ... ) SdkLog( LOG_WARN, pFormat, __VA_ARGS__ ) - #define LogInfo( message ) SdkLog( LOG_INFO, "%s", message ) - #define LogInfoWithArgs( pFormat, ... ) SdkLog( LOG_INFO, pFormat, __VA_ARGS__ ) - #define LogDebug( message ) - #define LogDebugWithArgs( pFormat, ... ) - - #elif LIBRARY_LOG_LEVEL == LOG_WARN - /* Only WARNING and ERROR messages will be logged.*/ - #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) SdkLog( LOG_WARN, "%s", message ) - #define LogWarnWithArgs( pFormat, ... ) SdkLog( LOG_WARN, pFormat, __VA_ARGS__ ) - #define LogInfo( message ) - #define LogInfoWithArgs( pFormat, ... ) - #define LogDebug( message ) - #define LogDebugWithArgs( pFormat, ... ) - - #elif LIBRARY_LOG_LEVEL == LOG_ERROR - /* Only ERROR messages will be logged. */ - #define LogError( message ) SdkLog( LOG_ERROR, "%s", message ) - #define LogErrorWithArgs( pFormat, ... ) SdkLog( LOG_ERROR, pFormat, __VA_ARGS__ ) - #define LogWarn( message ) - #define LogWarnWithArgs( pFormat, ... ) - #define LogInfo( message ) - #define LogInfoWithArgs( pFormat, ... ) - #define LogDebug( message ) - #define LogDebugWithArgs( pFormat, ... ) - - #else /* if LIBRARY_LOG_LEVEL == LOG_ERROR */ - #define LogError( message ) - #define LogErrorWithArgs( pFormat, ... ) - #define LogWarn( message ) - #define LogWarnWithArgs( pFormat, ... ) - #define LogInfo( message ) - #define LogInfoWithArgs( pFormat, ... ) - #define LogDebug( message ) - #define LogDebugWithArgs( pFormat, ... ) - #endif /* if LIBRARY_LOG_LEVEL == LOG_ERROR */ -#endif /* if !defined( LIBRARY_LOG_LEVEL ) || ( ( LIBRARY_LOG_LEVEL != LOG_NONE ) && ( LIBRARY_LOG_LEVEL != LOG_ERROR ) && ( LIBRARY_LOG_LEVEL != LOG_WARN ) && ( LIBRARY_LOG_LEVEL != LOG_INFO ) && ( LIBRARY_LOG_LEVEL != LOG_DEBUG ) ) */ - -#endif /* ifndef LOGGING_SETUP_H_ */ diff --git a/platform/include/clock.h b/platform/include/clock.h index 2a99809b6b..087dbe8018 100644 --- a/platform/include/clock.h +++ b/platform/include/clock.h @@ -70,8 +70,8 @@ * @endcode */ /* @[declare_platform_clock_gettimestring] */ -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ); +bool Clock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ); #endif /* ifndef IOT_CLOCK_H_ */ diff --git a/platform/posix/clock_posix.c b/platform/posix/clock_posix.c index 6c24445980..4be9d91114 100644 --- a/platform/posix/clock_posix.c +++ b/platform/posix/clock_posix.c @@ -66,9 +66,9 @@ /*-----------------------------------------------------------*/ -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ) +bool Clock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ) { bool status = true; const time_t unixTime = time( NULL ); diff --git a/platform/posix/logging.c b/platform/posix/logging.c deleted file mode 100644 index 08e0ace650..0000000000 --- a/platform/posix/logging.c +++ /dev/null @@ -1,262 +0,0 @@ -/* - * IoT Common V1.1.0 - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_logging.c - * @brief Implementation of the generic logging function. - */ - -/* Standard includes. */ -#include -#include -#include -#include -#include - -/* Platform clock include. */ -#include "clock.h" - -/* Include header for logging level macros. */ -#include "logging_levels.h" - -/* Logging includes. */ -#include "logging.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief A guess of the maximum length of a timestring. - * - * There's no way for this logging library to know the length of a timestring - * before it's generated. Therefore, the logging library will assume a maximum - * length of any timestring it may get. This value should be generous enough - * to accommodate the vast majority of timestrings. - * - * @see @ref platform_clock_function_gettimestring - */ -#define MAX_TIMESTRING_LENGTH ( 64 ) - -/** - * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate - * `[]` and a null-terminator. - */ -#define MAX_LOG_LEVEL_LENGTH ( 8 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Log level code to string conversion utility. - * - * @param[in] messageLevel The log level to convert to string. - * - * @return The constant string representation of the log level. - */ -static const char * _log_level_strerror( int32_t messageLevel ) -{ - assert( ( messageLevel >= LOG_NONE ) && ( messageLevel <= LOG_DEBUG ) ); - - const char * retVal = NULL; - - switch( messageLevel ) - { - case LOG_ERROR: - retVal = "ERROR"; - break; - - case LOG_WARN: - retVal = "WARN"; - break; - - case LOG_INFO: - retVal = "INFO"; - break; - - case LOG_DEBUG: - retVal = "DEBUG"; - break; - } - - return retVal; -} - -/*-----------------------------------------------------------*/ - -static bool _reallocLoggingBuffer( void ** pOldBuffer, - size_t newSize, - size_t oldSize ) -{ - bool status = false; - - /* Allocate a new, larger buffer. */ - void * pNewBuffer = malloc( newSize ); - - /* Ensure that memory allocation succeeded. */ - if( pNewBuffer != NULL ) - { - /* Copy the data from the old buffer to the new buffer. */ - ( void ) memcpy( pNewBuffer, *pOldBuffer, oldSize ); - - /* Free the old buffer and update the pointer. */ - free( *pOldBuffer ); - *pOldBuffer = pNewBuffer; - - status = true; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void Log_Generic( int32_t messageLevel, - const char * const pFormat, - ... ) -{ - assert( ( messageLevel >= LOG_NONE ) && ( messageLevel <= LOG_DEBUG ) ); - - int requiredMessageSize = 0; - size_t bufferSize = 0, - bufferPosition = 0, timestringLength = 0; - char * pLoggingBuffer = NULL; - va_list args; - - /* Add length of log level if requested. */ - bufferSize += MAX_LOG_LEVEL_LENGTH; - - /* Add length of timestring. */ - bufferSize += MAX_TIMESTRING_LENGTH; - - /* Add 64 as an initial (arbitrary) guess for the length of the message. */ - bufferSize += 64; - - /* Allocate memory for the logging buffer. */ - pLoggingBuffer = ( char * ) malloc( bufferSize ); - - if( pLoggingBuffer == NULL ) - { - return; - } - - /* Print the message log level. */ - - /* Add the log level string to the logging buffer. */ - requiredMessageSize = snprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - "[%s]", - _log_level_strerror( messageLevel ) ); - - /* Check for encoding errors. */ - if( requiredMessageSize <= 0 ) - { - free( pLoggingBuffer ); - - return; - } - - /* Update the buffer position. */ - bufferPosition += ( size_t ) requiredMessageSize; - - /* Print the timestring if requested. */ - /* Add the opening '[' enclosing the timestring. */ - pLoggingBuffer[ bufferPosition ] = '['; - bufferPosition++; - - /* Generate the timestring and add it to the buffer. */ - if( IotClock_GetTimestring( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - ×tringLength ) == true ) - { - /* If the timestring was successfully generated, add the closing "]". */ - bufferPosition += timestringLength; - pLoggingBuffer[ bufferPosition ] = ']'; - bufferPosition++; - } - else - { - /* Sufficient memory for a timestring should have been allocated. A timestring - * probably failed to generate due to a clock read error; remove the opening '[' - * from the logging buffer. */ - bufferPosition--; - pLoggingBuffer[ bufferPosition ] = '\0'; - } - - /* Add a padding space between the last closing ']' and the message, unless - * the logging buffer is empty. */ - if( bufferPosition > 0 ) - { - pLoggingBuffer[ bufferPosition ] = ' '; - bufferPosition++; - } - - va_start( args, pFormat ); - - /* Add the log message to the logging buffer. */ - requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - pFormat, - args ); - - va_end( args ); - - /* If the logging buffer was too small to fit the log message, reallocate - * a larger logging buffer. */ - if( ( size_t ) requiredMessageSize >= bufferSize - bufferPosition ) - { - if( _reallocLoggingBuffer( ( void ** ) &pLoggingBuffer, - ( size_t ) requiredMessageSize + bufferPosition + 1, - bufferSize ) == false ) - { - /* If buffer reallocation failed, return. */ - free( pLoggingBuffer ); - - return; - } - - /* Reallocation successful, update buffer size. */ - bufferSize = ( size_t ) requiredMessageSize + bufferPosition + 1; - - /* Add the log message to the buffer. Now that the buffer has been - * reallocated, this should succeed. */ - va_start( args, pFormat ); - requiredMessageSize = vsnprintf( pLoggingBuffer + bufferPosition, - bufferSize - bufferPosition, - pFormat, - args ); - va_end( args ); - } - - /* Check for encoding errors. */ - if( requiredMessageSize <= 0 ) - { - free( pLoggingBuffer ); - - return; - } - - /* Print the logging buffer to stdout. */ - puts( pLoggingBuffer ); - - /* Free the logging buffer. */ - free( pLoggingBuffer ); -} - -/*-----------------------------------------------------------*/ diff --git a/tools/cmake/logging.cmake b/tools/cmake/logging.cmake deleted file mode 100644 index 6b1441098f..0000000000 --- a/tools/cmake/logging.cmake +++ /dev/null @@ -1,7 +0,0 @@ -# Configuration for logging. -set( LOGGING_INCLUDE_DIRS - ${ROOT_DIR}/platform/include ) - -set( LOGGING_SOURCES - ${ROOT_DIR}/platform/posix/logging.c - ${ROOT_DIR}/platform/posix/clock_posix.c ) \ No newline at end of file From 05c29fe9c5534ad60b467d20ff7679774b66ee8b Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 20 May 2020 16:30:17 -0700 Subject: [PATCH 516/844] Fix some build warnings (#959) * Update mqtt demo makefiles --- demos/mqtt/mqtt_demo_basic_tls/Makefile | 3 ++- demos/mqtt/mqtt_demo_plaintext/Makefile | 3 ++- libraries/standard/mqtt/include/mqtt_lightweight.h | 4 ++-- libraries/standard/mqtt/src/mqtt.c | 2 +- libraries/standard/mqtt/utest/mqtt_utest.c | 2 -- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/Makefile b/demos/mqtt/mqtt_demo_basic_tls/Makefile index 76c820089a..c73af4ceba 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/Makefile +++ b/demos/mqtt/mqtt_demo_basic_tls/Makefile @@ -1,7 +1,8 @@ CC = gcc INCLUDE_DIRS = -I . \ - -I ../../../libraries/standard/mqtt/include + -I ../../../libraries/standard/mqtt/include \ + -I ../../logging-stack/ SRC_FILES = mqtt_demo_basic_tls.c \ $(shell find ../../../libraries/standard/mqtt/src -name '*.c') diff --git a/demos/mqtt/mqtt_demo_plaintext/Makefile b/demos/mqtt/mqtt_demo_plaintext/Makefile index ab3012a5c8..93dd2c8134 100644 --- a/demos/mqtt/mqtt_demo_plaintext/Makefile +++ b/demos/mqtt/mqtt_demo_plaintext/Makefile @@ -1,7 +1,8 @@ CC = gcc INCLUDE_DIRS = -I . \ - -I ../../../libraries/standard/mqtt/include + -I ../../../libraries/standard/mqtt/include \ + -I ../../logging-stack/ SRC_FILES = mqtt_demo_plaintext.c \ $(shell find ../../../libraries/standard/mqtt/src -name '*.c') diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index a0b8fbf415..823c33558f 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -23,9 +23,9 @@ #define MQTT_LIGHTWEIGHT_H /* bools are only defined in C99+ */ -#if __STDC_VERSION__ >= 199901L +#if defined( __cplusplus ) || __STDC_VERSION__ >= 199901L #include -#else +#elif !defined( bool ) #define bool signed char #define false 0 #define true 1 diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 10b0adb823..6ac7da7295 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1201,7 +1201,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) if( status == MQTTSuccess ) { /* Get MQTT PINGREQ packet size. */ - status = MQTT_GetPingReqPacketSize( &packetSize ); + status = MQTT_GetPingreqPacketSize( &packetSize ); if( status == MQTTSuccess ) { diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index bc854f2585..058f19633e 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -1,5 +1,3 @@ -#include - #include "unity.h" /* Include paths for public enums, structures, and macros. */ From 320e1f0d58608fa8297369b569d87c2ee8cf4693 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 21 May 2020 16:27:58 -0700 Subject: [PATCH 517/844] Add MQTT state engine unit tests (#951) * Add MQTT state engine unit tests * Update parameter validations in library state engine --- libraries/CMakeLists.txt | 2 +- libraries/standard/mqtt/src/mqtt_state.c | 17 +- libraries/standard/mqtt/utest/CMakeLists.txt | 17 + .../standard/mqtt/utest/mqtt_state_utest.c | 436 ++++++++++++++++++ 4 files changed, 458 insertions(+), 14 deletions(-) create mode 100644 libraries/standard/mqtt/utest/mqtt_state_utest.c diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 62b71152b4..ea156f64ea 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -35,7 +35,7 @@ if( ${BUILD_TESTS} ) # Add a target for running coverage on tests. add_custom_target(coverage COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake - DEPENDS cmock unity http_utest mqtt_utest + DEPENDS cmock unity http_utest mqtt_utest mqtt_state_utest WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endif() diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index cc9cfed2fc..bd4e14c926 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -139,10 +139,6 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, * the reserve operation. */ switch( qos ) { - case MQTTQoS0: - isValid = ( newState == MQTTPublishDone ); - break; - case MQTTQoS1: isValid = ( newState == MQTTPubAckPending ); break; @@ -152,7 +148,7 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, break; default: - /* No other QoS value. */ + /* QoS 0 is checked before calling this function. */ break; } @@ -350,7 +346,7 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, { status = MQTTSuccess; } - else if( packetId == MQTT_PACKET_ID_INVALID ) + else if( ( packetId == MQTT_PACKET_ID_INVALID ) || ( pMqttContext == NULL ) ) { status = MQTTBadParameter; } @@ -425,7 +421,7 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, &foundQoS, ¤tState ); - if( foundQoS != qos ) + if( ( recordIndex == MQTT_STATE_ARRAY_MAX_COUNT ) || ( foundQoS != qos ) ) { /* Entry should match with supplied QoS. */ mqttStatus = MQTTBadParameter; @@ -453,14 +449,9 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, newState ); } /* Send operation. */ - else if( recordIndex < MQTT_STATE_ARRAY_MAX_COUNT ) - { - updateRecord( pMqttContext->outgoingPublishRecords, recordIndex, newState, false ); - } else { - /* Send operation with no record found. */ - mqttStatus = MQTTBadParameter; + updateRecord( pMqttContext->outgoingPublishRecords, recordIndex, newState, false ); } } else diff --git a/libraries/standard/mqtt/utest/CMakeLists.txt b/libraries/standard/mqtt/utest/CMakeLists.txt index 66cdc4d829..3ef50f743e 100644 --- a/libraries/standard/mqtt/utest/CMakeLists.txt +++ b/libraries/standard/mqtt/utest/CMakeLists.txt @@ -81,3 +81,20 @@ create_test(${utest_name} "${utest_dep_list}" "${test_include_directories}" ) + +# create targets for testing mqtt_lightweight.c +set(utest_name "${project_name}_state_utest") +set(utest_source "${project_name}_state_utest.c") + +set(utest_link_list "") +list(APPEND utest_link_list + -lunity + lib${real_name}.a + ) + +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c new file mode 100644 index 0000000000..650d4bc1b2 --- /dev/null +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -0,0 +1,436 @@ +#include "unity.h" + +#include "mqtt_state.h" + +#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) + +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) + +/* ============================ UNITY FIXTURES ============================ */ +void setUp( void ) +{ +} + +/* called before each testcase */ +void tearDown( void ) +{ +} + +/* called at the beginning of the whole suite */ +void suiteSetUp() +{ +} + +/* called at the end of the whole suite */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ========================================================================== */ + +static void resetPublishRecords( MQTTContext_t * pMqttContext ) +{ + int i = 0; + for( ; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ ) + { + pMqttContext->outgoingPublishRecords[ i ].packetId = MQTT_PACKET_ID_INVALID; + pMqttContext->outgoingPublishRecords[ i ].qos = MQTTQoS0; + pMqttContext->outgoingPublishRecords[ i ].publishState = MQTTStateNull; + pMqttContext->incomingPublishRecords[ i ].packetId = MQTT_PACKET_ID_INVALID; + pMqttContext->incomingPublishRecords[ i ].qos = MQTTQoS0; + pMqttContext->incomingPublishRecords[ i ].publishState = MQTTStateNull; + } +} + +static void addToRecord( MQTTPubAckInfo_t * records, + size_t index, + uint16_t packetId, + MQTTQoS_t qos, + MQTTPublishState_t state ) +{ + records[ index ].packetId = packetId; + records[ index ].qos = qos; + records[ index ].publishState = state; +} + +static void fillRecord( MQTTPubAckInfo_t * records, + uint16_t startingId, + MQTTQoS_t qos, + MQTTPublishState_t state ) +{ + int i; + for( i = 0; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ ) + { + records[ i ].packetId = startingId + i; + records[ i ].qos = qos; + records[ i ].publishState = state; + } +} + +/* ========================================================================== */ + +void test_MQTT_ReserveState( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTStatus_t status; + + /* QoS 0 returns success. */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ReserveState( NULL, MQTT_PACKET_ID_INVALID, MQTTQoS0 ) ); + + /* Test for bad parameters */ + status = MQTT_ReserveState( &mqttContext, MQTT_PACKET_ID_INVALID, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + status = MQTT_ReserveState( NULL, 1, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Test for collisions. */ + mqttContext.outgoingPublishRecords[ 1 ].packetId = 1; + mqttContext.outgoingPublishRecords[ 1 ].qos = MQTTQoS1; + mqttContext.outgoingPublishRecords[ 1 ].publishState = MQTTPublishSend; + + status = MQTT_ReserveState( &mqttContext, 1, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTStateCollision, status ); + + /* Test for no memory. */ + fillRecord( mqttContext.outgoingPublishRecords, 2, MQTTQoS1, MQTTPublishSend ); + status = MQTT_ReserveState( &mqttContext, 1, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTNoMemory, status ); + + /* Success. */ + resetPublishRecords( &mqttContext ); + status = MQTT_ReserveState( &mqttContext, 1, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* Reserve uses first available entry. */ + TEST_ASSERT_EQUAL( 1, mqttContext.outgoingPublishRecords[ 0 ].packetId ); + TEST_ASSERT_EQUAL( MQTTQoS1, mqttContext.outgoingPublishRecords[ 0 ].qos ); + TEST_ASSERT_EQUAL( MQTTPublishSend, mqttContext.outgoingPublishRecords[ 0 ].publishState ); +} + +/* ========================================================================== */ + +void test_MQTT_CalculateStatePublish( void ) +{ + /* QoS 0. */ + TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStatePublish( MQTT_SEND, MQTTQoS0 ) ); + TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStatePublish( MQTT_RECEIVE, MQTTQoS0 ) ); + + /* QoS 1. */ + TEST_ASSERT_EQUAL( MQTTPubAckPending, MQTT_CalculateStatePublish( MQTT_SEND, MQTTQoS1 ) ); + TEST_ASSERT_EQUAL( MQTTPubAckSend, MQTT_CalculateStatePublish( MQTT_RECEIVE, MQTTQoS1 ) ); + + /* QoS 2. */ + TEST_ASSERT_EQUAL( MQTTPubRecPending, MQTT_CalculateStatePublish( MQTT_SEND, MQTTQoS2 ) ); + TEST_ASSERT_EQUAL( MQTTPubRecSend, MQTT_CalculateStatePublish( MQTT_RECEIVE, MQTTQoS2 ) ); + + /* Invalid QoS. */ + TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStatePublish( MQTT_SEND, 3 ) ); +} + +/* ========================================================================== */ + +void test_MQTT_UpdateStatePublish( void ) +{ + MQTTContext_t mqttContext = { 0 }; + const uint16_t PACKET_ID = 1; + MQTTStateOperation_t operation = MQTT_SEND; + MQTTQoS_t qos = MQTTQoS0; + MQTTPublishState_t state; + + /* QoS 0. */ + state = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos ); + TEST_ASSERT_EQUAL( MQTTPublishDone, state ); + + /* Invalid parameters. */ + /* Invalid ID. */ + qos = MQTTQoS1; + state = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + /* NULL context. */ + state = MQTT_UpdateStatePublish( NULL, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + /* No record found. */ + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + /* QoS mismatch. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Invalid transition. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Invalid QoS. */ + operation = MQTT_SEND; + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, 3, MQTTPublishSend ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3 ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + operation = MQTT_RECEIVE; + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3 ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Invalid current state. */ + operation = MQTT_SEND; + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, qos, MQTTStateNull ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Collision. */ + operation = MQTT_RECEIVE; + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* No memory. */ + operation = MQTT_RECEIVE; + fillRecord( mqttContext.incomingPublishRecords, 2, MQTTQoS1, MQTTPublishSend ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + resetPublishRecords( &mqttContext ); + + /* QoS 1. */ + qos = MQTTQoS1; + /* Send. */ + operation = MQTT_SEND; + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPublishSend ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTPubAckPending, state ); + TEST_ASSERT_EQUAL( MQTTPubAckPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + /* Receive. */ + operation = MQTT_RECEIVE; + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTPubAckSend, state ); + TEST_ASSERT_EQUAL( MQTTPubAckSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + + resetPublishRecords( &mqttContext ); + + /* QoS 2. */ + qos = MQTTQoS2; + /* Send. */ + operation = MQTT_SEND; + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend ); + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTPubRecPending, state ); + TEST_ASSERT_EQUAL( MQTTPubRecPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + /* Receive. */ + operation = MQTT_RECEIVE; + state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + TEST_ASSERT_EQUAL( MQTTPubRecSend, state ); + TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + +} + +/* ========================================================================== */ + +void test_MQTT_CalculateStateAck( void ) +{ + MQTTPubAckType_t ack; + MQTTQoS_t qos; + MQTTStateOperation_t opType; + + /* Invalid qos. */ + qos = MQTTQoS0; + ack = MQTTPuback; + opType = MQTT_SEND; + TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) ); + qos = MQTTQoS2; + TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) ); + qos = MQTTQoS0; + ack = MQTTPubrec; + TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) ); + qos = MQTTQoS1; + TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) ); + + /* Invalid ack type. */ + ack = MQTTPubcomp + 1; + TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) ); + + /* PUBACK */ + ack = MQTTPuback; + qos = MQTTQoS1; + opType = MQTT_SEND; + TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) ); + opType = MQTT_RECEIVE; + TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) ); + + /* QoS 2 tests. */ + qos = MQTTQoS2; + + /* PUBREC */ + ack = MQTTPubrec; + /* Send */ + opType = MQTT_SEND; + TEST_ASSERT_EQUAL( MQTTPubRelPending, MQTT_CalculateStateAck( ack, opType, qos ) ); + /* Receive */ + opType = MQTT_RECEIVE; + TEST_ASSERT_EQUAL( MQTTPubRelSend, MQTT_CalculateStateAck( ack, opType, qos ) ); + + /* PUBREL */ + ack = MQTTPubrel; + /* Send */ + opType = MQTT_SEND; + TEST_ASSERT_EQUAL( MQTTPubCompPending, MQTT_CalculateStateAck( ack, opType, qos ) ); + /* Receive */ + opType = MQTT_RECEIVE; + TEST_ASSERT_EQUAL( MQTTPubCompSend, MQTT_CalculateStateAck( ack, opType, qos ) ); + + /* PUBCOMP */ + ack = MQTTPubcomp; + TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) ); + opType = MQTT_SEND; + TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) ); +} + +/* ========================================================================== */ + +void test_MQTT_UpdateStateAck( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTPubAckType_t ack = MQTTPuback; + MQTTStateOperation_t operation = MQTT_RECEIVE; + MQTTPublishState_t state = MQTTStateNull; + + const uint16_t PACKET_ID = 1; + + /* No matching record found. */ + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + /* Invalid packet ID. */ + state = MQTT_UpdateStateAck( &mqttContext, 0, ack, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Invalid transition. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); + ack = MQTTPubcomp; + state = MQTT_UpdateStateAck( &mqttContext, 1, ack, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Invalid ack type. */ + state = MQTT_UpdateStateAck( &mqttContext, 1, MQTTPubcomp + 1, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + /* Invalid current state. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishDone ); + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend ); + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTStateNull ); + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + + resetPublishRecords( &mqttContext ); + + /* QoS 1, outgoing publish. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending ); + operation = MQTT_RECEIVE; + ack = MQTTPuback; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPublishDone, state ); + /* Test for deletion. */ + TEST_ASSERT_EQUAL( MQTTStateNull, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 0 ].packetId ); + TEST_ASSERT_EQUAL( MQTTQoS0, mqttContext.outgoingPublishRecords[ 0 ].qos ); + /* Incoming publish. */ + operation = MQTT_SEND; + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend ); + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPublishDone, state ); + + resetPublishRecords( &mqttContext ); + + /* QoS 2, PUBREL. */ + /* Outgoing. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelSend ); + operation = MQTT_SEND; + ack = MQTTPubrel; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPubCompPending, state ); + /* Incoming . */ + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending ); + operation = MQTT_RECEIVE; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPubCompSend, state ); + /* Test for update. */ + TEST_ASSERT_EQUAL( MQTTPubCompSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + + /* QoS 2, PUBREC. */ + /* Outgoing. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); + operation = MQTT_RECEIVE; + ack = MQTTPubrec; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); + /* Incoming. */ + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecSend ); + operation = MQTT_SEND; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPubRelPending, state ); + + /* QoS 2, PUBCOMP. */ + /* Outgoing. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompPending ); + operation = MQTT_RECEIVE; + ack = MQTTPubcomp; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPublishDone, state ); + /* Incoming. */ + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompSend ); + operation = MQTT_SEND; + state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + TEST_ASSERT_EQUAL( MQTTPublishDone, state ); +} + +/* ========================================================================== */ + +void test_MQTT_StateSelect( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTStateCursor_t outgoingCursor = MQTT_STATE_CURSOR_INITIALIZER; + MQTTStateCursor_t incomingCursor = MQTT_STATE_CURSOR_INITIALIZER; + MQTTPublishState_t search = MQTTPublishSend; + uint16_t packetId; + const uint16_t PACKET_ID = 1; + const uint16_t PACKET_ID2 = 2; + const size_t index = MQTT_STATE_ARRAY_MAX_COUNT / 2; + const size_t secondIndex = index + 2; + + /* Invalid parameters. */ + packetId = MQTT_StateSelect( NULL, MQTTPublishSend, &outgoingCursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + packetId = MQTT_StateSelect( NULL, MQTTPubAckSend, &incomingCursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + packetId = MQTT_StateSelect( &mqttContext, search, NULL ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + packetId = MQTT_StateSelect( &mqttContext, MQTTStateNull, &outgoingCursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + + /* Incoming. */ + search = MQTTPubAckSend; + addToRecord( mqttContext.incomingPublishRecords, index, PACKET_ID, MQTTQoS1, search ); + packetId = MQTT_StateSelect( &mqttContext, search, &incomingCursor ); + TEST_ASSERT_EQUAL( PACKET_ID, packetId ); + TEST_ASSERT_EQUAL( index + 1, incomingCursor ); + + /* Outgoing. */ + search = MQTTPublishSend; + addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID, MQTTQoS1, search ); + packetId = MQTT_StateSelect( &mqttContext, search, &outgoingCursor ); + TEST_ASSERT_EQUAL( PACKET_ID, packetId ); + TEST_ASSERT_EQUAL( index + 1, outgoingCursor ); + + /* Test if second one can be found. */ + addToRecord( mqttContext.outgoingPublishRecords, secondIndex, PACKET_ID2, MQTTQoS2, search ); + packetId = MQTT_StateSelect( &mqttContext, search, &outgoingCursor ); + TEST_ASSERT_EQUAL( PACKET_ID2, packetId ); + TEST_ASSERT_EQUAL( secondIndex + 1, outgoingCursor ); + + /* Test if end of loop reached. */ + packetId = MQTT_StateSelect( &mqttContext, search, &outgoingCursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, outgoingCursor ); +} From 7496cb0bd9f80fe028c3d4795bc97554a1cd8434 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 21 May 2020 16:52:57 -0700 Subject: [PATCH 518/844] Convert HTTPClient_AddHeader unit test to use Unity (#931) * Add happy path test and invalid params test * Fix merge conflicts --- libraries/standard/http/utest/http_utest.c | 252 ++++++++++++++++++++- 1 file changed, 248 insertions(+), 4 deletions(-) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index d3f2703b41..9e04bf9ee3 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -80,7 +80,42 @@ typedef struct _headers #define HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN \ ( HTTP_TEST_MAX_INITIALIZED_HEADER_LEN + 1 ) -#define HTTP_TEST_BUFFER_LEN 1024 +/* Template HTTP header fields and values. */ +#define HTTP_TEST_HEADER_FIELD "Authorization" +#define HTTP_TEST_HEADER_FIELD_LEN ( sizeof( HTTP_TEST_HEADER_FIELD ) - 1 ) +#define HTTP_TEST_HEADER_VALUE "None" +#define HTTP_TEST_HEADER_VALUE_LEN ( sizeof( HTTP_TEST_HEADER_VALUE ) - 1 ) +/* Template for first line of HTTP header. */ +#define HTTP_TEST_HEADER_REQUEST_LINE "GET / HTTP/1.1 \r\n" +#define HTTP_TEST_HEADER_REQUEST_LINE_LEN ( sizeof( HTTP_TEST_HEADER_REQUEST_LINE ) - 1 ) +#define HTTP_REQUEST_HEADERS_INITIALIZER { 0 } +/* Template for snprintf(...) strings. */ +#define HTTP_TEST_SINGLE_HEADER_FORMAT "%s%s: %s\r\n\r\n" +#define HTTP_TEST_DOUBLE_HEADER_FORMAT "%s%s: %s\r\n%s: %s\r\n\r\n" + +/* Length of the following template HTTP header. + * \r\n + * : \r\n + * \r\n + * This is used to initialize the expectedHeader string. */ +#define HTTP_TEST_SINGLE_HEADER_LEN \ + ( HTTP_TEST_HEADER_REQUEST_LINE_LEN + \ + HTTP_TEST_HEADER_FIELD_LEN + \ + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_TEST_HEADER_VALUE_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN ) + +/* The longest possible header used for these unit tests. */ +#define HTTP_TEST_DOUBLE_HEADER_LEN \ + ( HTTP_TEST_SINGLE_HEADER_LEN + \ + HTTP_TEST_HEADER_FIELD_LEN + \ + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ + HTTP_TEST_HEADER_VALUE_LEN + \ + HTTP_HEADER_LINE_SEPARATOR_LEN ) + +#define HTTP_TEST_DOUBLE_HEADER_BUFFER_LEN \ + ( HTTP_TEST_DOUBLE_HEADER_LEN + 1 ) /* Template HTTP response for testing HTTPClient_ReadHeader API. */ static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" @@ -271,7 +306,7 @@ static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, /* ============================ UNITY FIXTURES ============================== */ /* Called before each test method. */ -void setUp( void ) +void setUp() { testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; testResponse.bufferLen = strlen( pTestResponse ); @@ -286,9 +321,9 @@ void setUp( void ) } /* Called after each test method. */ -void tearDown( void ) +void tearDown() { - retCode = HTTP_NOT_SUPPORTED; + retCode = HTTP_INTERNAL_ERROR; memset( &testHeaders, 0, sizeof( testHeaders ) ); memset( testBuffer, 0, sizeof( testBuffer ) ); memset( &expectedHeaders, 0, sizeof( expectedHeaders ) ); @@ -493,6 +528,213 @@ void test_Http_InitializeRequestHeaders_Insufficient_Memory() HTTP_TEST_REQUEST_LINE_LEN ) != 0 ); } +/* ===================== Testing HTTPClient_AddHeader ======================= */ + +/** + * @brief Prefill the user buffer with HTTP_TEST_HEADER_REQUEST_LINE and call + * HTTPClient_AddHeader using HTTP_TEST_HEADER_FIELD and HTTP_TEST_HEADER_VALUE. + */ +void test_Http_AddHeader_Happy_Path() +{ + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + int numBytes = 0; + + setupBuffer( &requestHeaders ); + + /* Add 1 because snprintf(...) writes a null byte at the end. */ + numBytes = snprintf( expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); + expectedHeaders.dataLen = HTTP_TEST_SINGLE_HEADER_LEN; + + /* Set parameters for requestHeaders. */ + numBytes = snprintf( ( char * ) requestHeaders.pBuffer, + HTTP_TEST_HEADER_REQUEST_LINE_LEN + 1, + HTTP_TEST_HEADER_REQUEST_LINE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( requestHeaders.bufferLen, ( size_t ) numBytes ); + /* We correctly set headersLen after writing request line to requestHeaders.pBuffer. */ + requestHeaders.headersLen = HTTP_TEST_HEADER_REQUEST_LINE_LEN; + + /* Run the method to test. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + requestHeaders.pBuffer, expectedHeaders.dataLen ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, httpStatus ); +} + +/** + * @brief Test invalid parameters, following order of else-if blocks in the HTTP library. + */ +void test_Http_AddHeader_Invalid_Parameters() +{ + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + + /* Test a NULL request headers interface. */ + httpStatus = HTTPClient_AddHeader( NULL, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test a NULL pBuffer member of request headers. */ + requestHeaders.pBuffer = NULL; + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test NULL header field. */ + requestHeaders.pBuffer = testBuffer; + requestHeaders.bufferLen = HTTP_TEST_DOUBLE_HEADER_LEN; + httpStatus = HTTPClient_AddHeader( &requestHeaders, + NULL, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test NULL header value. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + NULL, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test that fieldLen > 0. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, 0, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test that valueLen > 0. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, 0 ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); +} + +/** + * @brief Test adding extra header with sufficient memory. + */ +void test_Http_AddHeader_Extra_Header_Sufficient_Memory() +{ + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + int numBytes = 0; + + setupBuffer( &requestHeaders ); + + /* Add 1 because snprintf(...) writes a null byte at the end. */ + numBytes = snprintf( expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + HTTP_TEST_DOUBLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); + expectedHeaders.dataLen = HTTP_TEST_DOUBLE_HEADER_LEN; + + /* Prefill the buffer with a request line and header. */ + numBytes = snprintf( ( char * ) requestHeaders.pBuffer, + HTTP_TEST_SINGLE_HEADER_LEN + 1, + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); + TEST_ASSERT_EQUAL( HTTP_TEST_SINGLE_HEADER_LEN, numBytes ); + requestHeaders.headersLen = HTTP_TEST_SINGLE_HEADER_LEN; + requestHeaders.bufferLen = expectedHeaders.dataLen; + + /* Run the method to test. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + requestHeaders.pBuffer, expectedHeaders.dataLen ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, httpStatus ); +} + +/** + * @brief Test adding extra header with insufficient memory. + */ +void test_Http_AddHeader_Extra_Header_Insufficient_Memory() +{ + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + int numBytes = 0; + + setupBuffer( &requestHeaders ); + + /* Add 1 because snprintf(...) writes a null byte at the end. */ + numBytes = snprintf( expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); + expectedHeaders.dataLen = HTTP_TEST_SINGLE_HEADER_LEN; + + /* Prefill the buffer with a request line and header. */ + numBytes = snprintf( ( char * ) requestHeaders.pBuffer, + HTTP_TEST_SINGLE_HEADER_LEN + 1, + HTTP_TEST_SINGLE_HEADER_FORMAT, + HTTP_TEST_HEADER_REQUEST_LINE, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( requestHeaders.bufferLen, ( size_t ) numBytes ); + requestHeaders.headersLen = HTTP_TEST_SINGLE_HEADER_LEN; + requestHeaders.bufferLen = requestHeaders.headersLen; + + /* Run the method to test. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, + requestHeaders.pBuffer, expectedHeaders.dataLen ); + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, httpStatus ); +} + +/** + * @brief Test HTTP_INSUFFICIENT_MEMORY error from having buffer size less than + * what is required to fit a single HTTP header. + */ +void test_Http_AddHeader_Single_Header_Insufficient_Memory() +{ + HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPRequestHeaders_t requestHeaders = { 0 }; + int numBytes = 0; + + setupBuffer( &requestHeaders ); + + /* Add 1 because snprintf(...) writes a null byte at the end. */ + numBytes = snprintf( ( char * ) testBuffer, + HTTP_TEST_HEADER_REQUEST_LINE_LEN + 1, + HTTP_TEST_HEADER_REQUEST_LINE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( testBuffer ), ( size_t ) numBytes ); + requestHeaders.headersLen = HTTP_TEST_HEADER_REQUEST_LINE_LEN; + requestHeaders.pBuffer = testBuffer; + requestHeaders.bufferLen = HTTP_TEST_SINGLE_HEADER_LEN - 1; + + /* Run the method to test. */ + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, httpStatus ); +} + /* ============== Testing HTTPClient_AddRangeHeader ================== */ /** @@ -1012,3 +1254,5 @@ void test_Http_ReadHeader_Happy_Path() TEST_ASSERT_EQUAL( ( const uint8_t * ) &pTestResponse[ headerValInRespLoc ], pValueLoc ); TEST_ASSERT_EQUAL( headerValInRespLen, valueLen ); } + +/* ========================================================================== */ From 290cc285d40d684c241100317de9c96ee1490c89 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 22 May 2020 02:46:45 -0700 Subject: [PATCH 519/844] Add MQTT_SerializeConnect unit tests using Unity (#942) * Add mqtt_lightweight_utest.c * Achieve 100% coverage on MQTT_SerializeConnect * Remove accidental changes * Move comment * Change TEST_ASSERT to correct order (expected, actual) * Add checks for serialized network packet * Update docs * Remove warning about suiteTearDown * Add docs for private methods * Address PR comments * Explicitly declare combinations of willInfo and connectInfo being used for the test * Rename to pDestination * Remove tools/cmake/filePaths.cmake * Merge mqtt/utest/CMakeLists.txt * Add assert for pSource --- CMakeLists.txt | 9 +- libraries/CMakeLists.txt | 2 +- libraries/standard/mqtt/utest/CMakeLists.txt | 20 +- libraries/standard/mqtt/utest/mqtt_config.h | 2 +- .../mqtt/utest/mqtt_lightweight_utest.c | 504 ++++++++++++++++++ tools/cmake/filePaths.cmake | 6 - tools/cmock/create_test.cmake | 2 +- 7 files changed, 532 insertions(+), 13 deletions(-) create mode 100644 libraries/standard/mqtt/utest/mqtt_lightweight_utest.c delete mode 100644 tools/cmake/filePaths.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a24cef7515..95521a20bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,13 @@ if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) message( FATAL_ERROR "In-source build is not allowed. Please build in a separate directory, such as ${PROJECT_SOURCE_DIR}/build." ) endif() -# Import global configurations. -include( "tools/cmake/filePaths.cmake" ) +# Set global path variables. +get_filename_component(__root_dir "${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE) +set(ROOT_DIR ${__root_dir} CACHE INTERNAL "C SDK source root.") +set(DEMOS_DIR "${ROOT_DIR}/demos" CACHE INTERNAL "C SDK demos root.") +set(MODULES_DIR "${ROOT_DIR}/libraries" CACHE INTERNAL "C SDK modules root.") +set(3RDPARTY_DIR "${MODULES_DIR}/3rdparty" CACHE INTERNAL "3rdparty libraries root.") + include( "demos/logging-stack/logging.cmake" ) # Configure options to always show in CMake GUI. diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index ea156f64ea..001c88e823 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -35,7 +35,7 @@ if( ${BUILD_TESTS} ) # Add a target for running coverage on tests. add_custom_target(coverage COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake - DEPENDS cmock unity http_utest mqtt_utest mqtt_state_utest + DEPENDS cmock unity http_utest mqtt_utest mqtt_state_utest mqtt_lightweight_utest WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endif() diff --git a/libraries/standard/mqtt/utest/CMakeLists.txt b/libraries/standard/mqtt/utest/CMakeLists.txt index 3ef50f743e..4253f76f0e 100644 --- a/libraries/standard/mqtt/utest/CMakeLists.txt +++ b/libraries/standard/mqtt/utest/CMakeLists.txt @@ -82,13 +82,29 @@ create_test(${utest_name} "${test_include_directories}" ) -# create targets for testing mqtt_lightweight.c +# need to redefine because the tests below don't use any mocks +set(utest_link_list "") +list(APPEND utest_link_list + lib${real_name}.a + ) + +# mqtt_state_utest set(utest_name "${project_name}_state_utest") set(utest_source "${project_name}_state_utest.c") +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) + +# mqtt_lightweight_utest +set(utest_name "${project_name}_lightweight_utest") +set(utest_source "${project_name}_lightweight_utest.c") + set(utest_link_list "") list(APPEND utest_link_list - -lunity lib${real_name}.a ) diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index d9cf12124d..dfc9facbe0 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -54,7 +54,7 @@ typedef int MQTTNetworkContext_t; * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define MQTT_CLIENT_IDENTIFIER "testclient" #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c new file mode 100644 index 0000000000..530fc072b2 --- /dev/null +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -0,0 +1,504 @@ +#include + +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "mqtt_lightweight.h" + +/** + * @brief MQTT protocol version 3.1.1. + */ +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) + +/** + * @brief Test-defined macro for MQTT username. + */ +#define MQTT_TEST_USERNAME "username" +#define MQTT_TEST_USERNAME_LEN ( sizeof( MQTT_TEST_USERNAME ) - 1 ) + +/** + * @brief Test-defined macro for MQTT password. + */ +#define MQTT_TEST_PASSWORD "password" +#define MQTT_TEST_PASSWORD_LEN ( sizeof( MQTT_TEST_PASSWORD ) - 1 ) + +/** + * @brief Test-defined macro for MQTT topic. + */ +#define MQTT_TEST_TOPIC "topic" +#define MQTT_TEST_TOPIC_LEN ( sizeof( MQTT_TEST_TOPIC ) - 1 ) + +/** + * @brief Length of the client identifier. + */ +#define MQTT_CLIENT_IDENTIFIER_LEN ( sizeof( MQTT_CLIENT_IDENTIFIER ) - 1 ) + +/** + * @brief Payload for will info. + */ +#define MQTT_SAMPLE_PAYLOAD "payload" +#define MQTT_SAMPLE_PAYLOAD_LEN ( sizeof( MQTT_SAMPLE_PAYLOAD ) - 1 ) + +/* MQTT CONNECT flags. */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ + +/** + * @brief Set a bit in an 8-bit unsigned integer. + */ +#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) ) + +/** + * @brief Macro for checking if a bit is set in a 1-byte unsigned int. + * + * @param[in] x The unsigned int to check. + * @param[in] position Which bit to check. + */ +#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) ) + +/** + * @brief Get the high byte of a 16-bit unsigned integer. + */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) ) + +/** + * @brief Get the low byte of a 16-bit unsigned integer. + */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) ) + +/** + * @brief Maximum number of bytes in the Remaining Length field is four according + * to MQTT 3.1.1 spec. + */ +#define MQTT_REMAINING_BUFFER_MAX_LENGTH ( 4 ) + +/** + * @brief Length of the MQTT network buffer. + */ +#define MQTT_TEST_BUFFER_LENGTH ( 1024 ) + +static uint8_t remainingLengthBuffer[ MQTT_REMAINING_BUFFER_MAX_LENGTH ] = { 0 }; + +static uint8_t encodedStringBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; + +static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; + +/* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ +void setUp( void ) +{ +} + +/* Called after each test method. */ +void tearDown( void ) +{ +} + +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} + +/* ===================== Testing MQTT_SerializeConnect ===================== */ + +/** + * @brief Initialize pNetworkBuffer using static buffer. + * + * @param[in] pNetworkBuffer Network buffer provided for the context. + */ +static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer ) +{ + pNetworkBuffer->pBuffer = mqttBuffer; + pNetworkBuffer->size = MQTT_TEST_BUFFER_LENGTH; +} + +/** + * @brief Initialize pConnectInfo using test-defined macros. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + */ +static void setupConnectInfo( MQTTConnectInfo_t * const pConnectInfo ) +{ + pConnectInfo->cleanSession = true; + pConnectInfo->pClientIdentifier = MQTT_CLIENT_IDENTIFIER; + pConnectInfo->clientIdentifierLength = MQTT_CLIENT_IDENTIFIER_LEN; + pConnectInfo->keepAliveSeconds = 0; + pConnectInfo->pUserName = MQTT_TEST_USERNAME; + pConnectInfo->userNameLength = MQTT_TEST_USERNAME_LEN; + pConnectInfo->pPassword = MQTT_TEST_PASSWORD; + pConnectInfo->passwordLength = MQTT_TEST_PASSWORD_LEN; +} + +/** + * @brief Initialize pWillInfo using test-defined macros. + * + * @param[in] pWillInfo Last Will and Testament. + */ +static void setupWillInfo( MQTTPublishInfo_t * const pWillInfo ) +{ + pWillInfo->pPayload = MQTT_SAMPLE_PAYLOAD; + pWillInfo->payloadLength = MQTT_SAMPLE_PAYLOAD_LEN; + pWillInfo->pTopicName = MQTT_CLIENT_IDENTIFIER; + pWillInfo->topicNameLength = MQTT_CLIENT_IDENTIFIER_LEN; + pWillInfo->dup = true; + pWillInfo->qos = MQTTQoS0; + pWillInfo->retain = true; +} + +/** + * @brief Encode remaining length into pDestination for packet serialization + * using MQTT v3.1.1 spec. + * + * @param[in] pDestination Buffer to write encoded remaining length. + * @param[in] length Actual remaining length. + */ +static size_t encodeRemainingLength( uint8_t * pDestination, + size_t length ) +{ + uint8_t lengthByte; + uint8_t * pLengthEnd = NULL; + size_t remainingLength = length; + + TEST_ASSERT_NOT_NULL( pDestination ); + + pLengthEnd = pDestination; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + lengthByte = ( uint8_t ) ( remainingLength % 128U ); + remainingLength = remainingLength / 128U; + + /* Set the high bit of this byte, indicating that there's more data. */ + if( remainingLength > 0U ) + { + UINT8_SET_BIT( lengthByte, 7 ); + } + + /* Output a single encoded byte. */ + *pLengthEnd = lengthByte; + pLengthEnd++; + } while( remainingLength > 0U ); + + return ( size_t ) ( pLengthEnd - pDestination ); +} + +/** + * @brief Encode UTF-8 string and its length into pDestination for + * packet serialization. + * + * @param[in] pDestination Buffer to write encoded string. + * @param[in] source String to encode. + * @param[in] sourceLength Length of the string to encode. + */ +static size_t encodeString( uint8_t * pDestination, + const char * source, + uint16_t sourceLength ) +{ + uint8_t * pBuffer = NULL; + + /* Typecast const char * typed source buffer to const uint8_t *. + * This is to use same type buffers in memcpy. */ + const uint8_t * pSourceBuffer = ( const uint8_t * ) source; + + TEST_ASSERT_NOT_NULL( pSourceBuffer ); + TEST_ASSERT_NOT_NULL( pDestination ); + + pBuffer = pDestination; + + /* The first byte of a UTF-8 string is the high byte of the string length. */ + *pBuffer = UINT16_HIGH_BYTE( sourceLength ); + pBuffer++; + + /* The second byte of a UTF-8 string is the low byte of the string length. */ + *pBuffer = UINT16_LOW_BYTE( sourceLength ); + pBuffer++; + + /* Copy the string into pBuffer. */ + ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); + + /* Return the pointer to the end of the encoded string. */ + pBuffer += sourceLength; + + return ( size_t ) ( pBuffer - pDestination ); +} + +/** + * @brief Check the serialization of an MQTT CONNECT packet in the given buffer, + * following the same order in serializeConnectPacket. + * + * @param[in] pConnectInfo MQTT CONNECT packet parameters. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. + * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. + * @param[in] pBuffer Buffer to check packet serialization. + * + */ +static void verifySerializedConnectPacket( const MQTTConnectInfo_t * const pConnectInfo, + const MQTTPublishInfo_t * const pWillInfo, + size_t remainingLength, + const MQTTFixedBuffer_t * const pBuffer ) +{ + uint8_t connectFlags = 0U; + uint8_t encodedRemainingLength = 0U; + uint8_t encodedStringLength = 0U; + uint8_t * pIndex = NULL; + + pIndex = pBuffer->pBuffer; + /* The first byte in the CONNECT packet is the control packet type. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_TYPE_CONNECT, *pIndex ); + pIndex++; + + /* The remaining length of the CONNECT packet is encoded starting from the + * second byte. The remaining length does not include the length of the fixed + * header or the encoding of the remaining length. */ + encodedRemainingLength = encodeRemainingLength( remainingLengthBuffer, remainingLength ); + TEST_ASSERT_EQUAL_MEMORY( remainingLengthBuffer, pIndex, encodedRemainingLength ); + pIndex += encodedRemainingLength; + + /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable + * header. This string is 4 bytes long. */ + encodedStringLength = encodeString( encodedStringBuffer, "MQTT", 4 ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + + /* The MQTT protocol version is the second field of the variable header. */ + TEST_ASSERT_EQUAL( MQTT_VERSION_3_1_1, *pIndex ); + pIndex++; + + /* Set the clean session flag if needed. */ + if( pConnectInfo->cleanSession == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); + } + + /* Set the flags for username and password if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); + } + + if( pConnectInfo->pPassword != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); + } + + /* Set will flag if a Last Will and Testament is provided. */ + if( pWillInfo != NULL ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); + + /* Flags only need to be changed for Will QoS 1 or 2. */ + if( pWillInfo->qos == MQTTQoS1 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); + } + else if( pWillInfo->qos == MQTTQoS2 ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); + } + else + { + /* Empty else MISRA 15.7 */ + } + + if( pWillInfo->retain == true ) + { + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); + } + } + + TEST_ASSERT_EQUAL( connectFlags, *pIndex ); + pIndex++; + + /* Verify the 2 bytes of the keep alive interval into the CONNECT packet. */ + TEST_ASSERT_EQUAL( UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ), + *pIndex ); + pIndex++; + TEST_ASSERT_EQUAL( UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ), + *pIndex ); + pIndex++; + + /* Verify the client identifier into the CONNECT packet. */ + encodedStringLength = encodeString( encodedStringBuffer, + pConnectInfo->pClientIdentifier, + pConnectInfo->clientIdentifierLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + + /* Verify the will topic name and message into the CONNECT packet if provided. */ + if( pWillInfo != NULL ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pTopicName, + pWillInfo->topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + encodedStringLength = encodeString( encodedStringBuffer, + pWillInfo->pPayload, + ( uint16_t ) pWillInfo->payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + /* Verify the user name if provided. */ + if( pConnectInfo->pUserName != NULL ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pConnectInfo->pUserName, + pConnectInfo->userNameLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } + + /* Verify the password if provided. */ + if( pConnectInfo->pPassword != NULL ) + { + encodedStringLength = encodeString( encodedStringBuffer, + pConnectInfo->pPassword, + pConnectInfo->passwordLength ); + TEST_ASSERT_EQUAL_MEMORY( encodedStringBuffer, pIndex, encodedStringLength ); + pIndex += encodedStringLength; + } +} + +/** + * @brief Call Mqtt_SerializeConnect using NULL parameters and insufficient buffer + * size until we receive all possible MQTTBadParameter and MQTTNoMemory errors. + */ +void test_MQTT_SerializeConnect_invalid_params() +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + size_t remainingLength = 0, packetSize = 0; + MQTTFixedBuffer_t networkBuffer; + MQTTConnectInfo_t connectInfo; + + /* Test NULL pConnectInfo. */ + mqttStatus = MQTT_SerializeConnect( NULL, NULL, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Test NULL pBuffer. */ + mqttStatus = MQTT_SerializeConnect( &connectInfo, NULL, + remainingLength, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Test connectPacketSize > pBuffer->size. */ + /* Get MQTT connect packet size and remaining length. */ + mqttStatus = MQTT_GetConnectPacketSize( &connectInfo, + NULL, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + networkBuffer.pBuffer = mqttBuffer; + networkBuffer.size = remainingLength - 1; + mqttStatus = MQTT_SerializeConnect( &connectInfo, NULL, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTNoMemory, mqttStatus ); +} + +/** + * @brief This method calls MQTT_SerializeConnect successfully using different parameters + * until we have full coverage on the private method, serializeConnectPacket(...). + */ +void test_MQTT_SerializeConnect_happy_paths() +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + size_t remainingLength = 0, packetSize = 0; + MQTTFixedBuffer_t networkBuffer; + MQTTConnectInfo_t connectInfo; + MQTTPublishInfo_t willInfo; + + /* Fill structs to pass into methods to be tested. */ + setupNetworkBuffer( &networkBuffer ); + setupConnectInfo( &connectInfo ); + setupWillInfo( &willInfo ); + + /* Get MQTT connect packet size and remaining length. */ + mqttStatus = MQTT_GetConnectPacketSize( &connectInfo, + &willInfo, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTT_SerializeConnect( &connectInfo, &willInfo, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, &willInfo, + remainingLength, &networkBuffer ); + + + /* Repeat with MQTTQoS1. */ + willInfo.qos = MQTTQoS1; + mqttStatus = MQTT_GetConnectPacketSize( &connectInfo, + &willInfo, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTT_SerializeConnect( &connectInfo, &willInfo, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, &willInfo, + remainingLength, &networkBuffer ); + + + /* Re-initialize objects for branch coverage. */ + willInfo.pPayload = MQTT_SAMPLE_PAYLOAD; + willInfo.payloadLength = MQTT_SAMPLE_PAYLOAD_LEN; + willInfo.pTopicName = MQTT_CLIENT_IDENTIFIER; + willInfo.topicNameLength = MQTT_CLIENT_IDENTIFIER_LEN; + willInfo.dup = true; + willInfo.qos = MQTTQoS2; + willInfo.retain = false; + connectInfo.cleanSession = false; + connectInfo.pClientIdentifier = MQTT_CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = MQTT_CLIENT_IDENTIFIER_LEN; + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0; + + mqttStatus = MQTT_GetConnectPacketSize( &connectInfo, + NULL, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTT_SerializeConnect( &connectInfo, &willInfo, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, &willInfo, + remainingLength, &networkBuffer ); + + + /* Repeat with NULL pWillInfo. */ + mqttStatus = MQTT_GetConnectPacketSize( &connectInfo, + NULL, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Make sure buffer has enough space. */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, networkBuffer.size ); + mqttStatus = MQTT_SerializeConnect( &connectInfo, NULL, + remainingLength, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + verifySerializedConnectPacket( &connectInfo, NULL, + remainingLength, &networkBuffer ); +} + +/* ========================================================================== */ diff --git a/tools/cmake/filePaths.cmake b/tools/cmake/filePaths.cmake deleted file mode 100644 index e1cc98f9c3..0000000000 --- a/tools/cmake/filePaths.cmake +++ /dev/null @@ -1,6 +0,0 @@ -# Set some global path variables. -get_filename_component(__root_dir "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) -set(ROOT_DIR ${__root_dir} CACHE INTERNAL "C SDK source root.") -set(DEMOS_DIR "${ROOT_DIR}/demos" CACHE INTERNAL "C SDK demos root.") -set(MODULES_DIR "${ROOT_DIR}/libraries" CACHE INTERNAL "C SDK modules root.") -set(3RDPARTY_DIR "${MODULES_DIR}/3rdparty" CACHE INTERNAL "3rdparty libraries root.") diff --git a/tools/cmock/create_test.cmake b/tools/cmock/create_test.cmake index 11cea704ed..21b02deec4 100644 --- a/tools/cmock/create_test.cmake +++ b/tools/cmock/create_test.cmake @@ -44,7 +44,7 @@ function(create_test test_name foreach(dependency IN LISTS dep_list) add_dependencies(${test_name} ${dependency}) endforeach() - target_link_libraries(${test_name} -lgcov) + target_link_libraries(${test_name} -lgcov -lunity) target_link_directories(${test_name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/lib ) From c228b33ceab827b2109e513b73a2977738638e35 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 22 May 2020 10:16:08 -0700 Subject: [PATCH 520/844] Add MQTT_SerializeDisconnect unit tests using Unity (#949) * Achieve full coverage on MQTT_SerializeDisconnect * Add docs * Remove accidental update of mqtt_utest.c * Add setupNetworkBuffer * Add checks for serialized buffer * Remove redefinition of setupNetworkBuffer * Remove accidental update of mqtt_utest.c * Update case for consistency * Remove warnings in mqtt_state_utest.c --- .../mqtt/utest/mqtt_lightweight_utest.c | 89 +++++++++++++++---- .../standard/mqtt/utest/mqtt_state_utest.c | 5 +- libraries/standard/mqtt/utest/mqtt_utest.c | 8 +- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 530fc072b2..8c9514134d 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -8,45 +8,50 @@ /** * @brief MQTT protocol version 3.1.1. */ -#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) /** * @brief Test-defined macro for MQTT username. */ -#define MQTT_TEST_USERNAME "username" -#define MQTT_TEST_USERNAME_LEN ( sizeof( MQTT_TEST_USERNAME ) - 1 ) +#define MQTT_TEST_USERNAME "username" +#define MQTT_TEST_USERNAME_LEN ( sizeof( MQTT_TEST_USERNAME ) - 1 ) /** * @brief Test-defined macro for MQTT password. */ -#define MQTT_TEST_PASSWORD "password" -#define MQTT_TEST_PASSWORD_LEN ( sizeof( MQTT_TEST_PASSWORD ) - 1 ) +#define MQTT_TEST_PASSWORD "password" +#define MQTT_TEST_PASSWORD_LEN ( sizeof( MQTT_TEST_PASSWORD ) - 1 ) /** * @brief Test-defined macro for MQTT topic. */ -#define MQTT_TEST_TOPIC "topic" -#define MQTT_TEST_TOPIC_LEN ( sizeof( MQTT_TEST_TOPIC ) - 1 ) +#define MQTT_TEST_TOPIC "topic" +#define MQTT_TEST_TOPIC_LEN ( sizeof( MQTT_TEST_TOPIC ) - 1 ) /** * @brief Length of the client identifier. */ -#define MQTT_CLIENT_IDENTIFIER_LEN ( sizeof( MQTT_CLIENT_IDENTIFIER ) - 1 ) +#define MQTT_CLIENT_IDENTIFIER_LEN ( sizeof( MQTT_CLIENT_IDENTIFIER ) - 1 ) /** * @brief Payload for will info. */ -#define MQTT_SAMPLE_PAYLOAD "payload" -#define MQTT_SAMPLE_PAYLOAD_LEN ( sizeof( MQTT_SAMPLE_PAYLOAD ) - 1 ) +#define MQTT_SAMPLE_PAYLOAD "payload" +#define MQTT_SAMPLE_PAYLOAD_LEN ( sizeof( MQTT_SAMPLE_PAYLOAD ) - 1 ) /* MQTT CONNECT flags. */ -#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ -#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ -#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */ -#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ -#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ -#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ -#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */ + +/** + * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec. + */ +#define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 ) /** * @brief Set a bit in an 8-bit unsigned integer. @@ -377,7 +382,7 @@ static void verifySerializedConnectPacket( const MQTTConnectInfo_t * const pConn * @brief Call Mqtt_SerializeConnect using NULL parameters and insufficient buffer * size until we receive all possible MQTTBadParameter and MQTTNoMemory errors. */ -void test_MQTT_SerializeConnect_invalid_params() +void test_MQTT_SerializeConnect_Invalid_Params() { MQTTStatus_t mqttStatus = MQTTSuccess; size_t remainingLength = 0, packetSize = 0; @@ -412,7 +417,7 @@ void test_MQTT_SerializeConnect_invalid_params() * @brief This method calls MQTT_SerializeConnect successfully using different parameters * until we have full coverage on the private method, serializeConnectPacket(...). */ -void test_MQTT_SerializeConnect_happy_paths() +void test_MQTT_SerializeConnect_Happy_Paths() { MQTTStatus_t mqttStatus = MQTTSuccess; size_t remainingLength = 0, packetSize = 0; @@ -501,4 +506,50 @@ void test_MQTT_SerializeConnect_happy_paths() remainingLength, &networkBuffer ); } +/* ================== Testing MQTT_SerializeDisconnect ===================== */ + +/** + * @brief Call Mqtt_SerializeDisconnect using NULL pBuffer and insufficient buffer + * size in order to receive MQTTBadParameter and MQTTNoMemory errors. + */ +void test_MQTT_SerializeDisconnect_Invalid_Params() +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + size_t packetSize = 0; + MQTTFixedBuffer_t networkBuffer; + + /* Test NULL pBuffer. */ + mqttStatus = MQTT_SerializeDisconnect( NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Test disconnectPacketSize > pBuffer->size. */ + /* Get MQTT disconnect packet size and remaining length. */ + mqttStatus = MQTT_GetDisconnectPacketSize( &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + networkBuffer.pBuffer = mqttBuffer; + networkBuffer.size = packetSize - 1; + mqttStatus = MQTT_SerializeDisconnect( &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTNoMemory, mqttStatus ); +} + +/** + * @brief This method calls MQTT_SerializeDisconnect successfully in order to + * get full coverage on the method. + */ +void test_MQTT_SerializeDisconnect_Happy_Path() +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + size_t packetSize = 0; + MQTTFixedBuffer_t networkBuffer; + + /* Fill structs to pass into methods to be tested. */ + setupNetworkBuffer( &networkBuffer ); + + /* Make sure buffer has enough space. */ + mqttStatus = MQTT_SerializeDisconnect( &networkBuffer ); + TEST_ASSERT_EQUAL( MQTT_PACKET_TYPE_DISCONNECT, networkBuffer.pBuffer[ 0 ] ); + TEST_ASSERT_EQUAL( MQTT_DISCONNECT_REMAINING_LENGTH, networkBuffer.pBuffer[ 1 ] ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); +} + /* ========================================================================== */ diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c index 650d4bc1b2..d397f1272b 100644 --- a/libraries/standard/mqtt/utest/mqtt_state_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -4,8 +4,6 @@ #define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) -#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) - /* ============================ UNITY FIXTURES ============================ */ void setUp( void ) { @@ -32,6 +30,7 @@ int suiteTearDown( int numFailures ) static void resetPublishRecords( MQTTContext_t * pMqttContext ) { int i = 0; + for( ; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ ) { pMqttContext->outgoingPublishRecords[ i ].packetId = MQTT_PACKET_ID_INVALID; @@ -60,6 +59,7 @@ static void fillRecord( MQTTPubAckInfo_t * records, MQTTPublishState_t state ) { int i; + for( i = 0; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ ) { records[ i ].packetId = startingId + i; @@ -220,7 +220,6 @@ void test_MQTT_UpdateStatePublish( void ) state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); TEST_ASSERT_EQUAL( MQTTPubRecSend, state ); TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); - } /* ========================================================================== */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 058f19633e..fd798d2fe6 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -11,12 +11,12 @@ /* ============================ UNITY FIXTURES ============================ */ /* Called before each test method. */ -void setUp( void ) +void setUp() { } /* Called after each test method. */ -void tearDown( void ) +void tearDown() { } @@ -36,7 +36,7 @@ int suiteTearDown( int numFailures ) /** * @brief Test that MQTT_Init is able to update the context object correctly. */ -void test_MQTT_Init_Happy_path( void ) +void test_MQTT_Init_Happy_Path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; @@ -57,7 +57,7 @@ void test_MQTT_Init_Happy_path( void ) /** * @brief Test that any NULL parameter causes MQTT_Init to return MQTTBadParameter. */ -void test_MQTT_Init_Invalid_params( void ) +void test_MQTT_Init_Invalid_Params( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; From 58e14f48e130df129ff4f259e75480ed854b9567 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 22 May 2020 10:42:44 -0700 Subject: [PATCH 521/844] MQTT plain text demo (#954) --- demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 11 +- demos/mqtt/mqtt_demo_plaintext/demo_config.h | 4 +- demos/mqtt/mqtt_demo_plaintext/mqtt_config.h | 4 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 794 ++++++++++++++++-- 4 files changed, 725 insertions(+), 88 deletions(-) diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt index 714ade273b..1f0645f6b2 100644 --- a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -8,6 +8,12 @@ target_sources( "${DEMO_NAME}.c" ) +target_include_directories( + mqtt + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + target_link_libraries( ${DEMO_NAME} PRIVATE @@ -20,8 +26,3 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -target_include_directories( - mqtt - PUBLIC - ${CMAKE_CURRENT_LIST_DIR} -) diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index 7beb40cac2..8e660e5b05 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -36,8 +36,8 @@ #include "logging_levels.h" /* Logging configuration for the Demo. */ -#define LIBRARY_LOG_NAME "DEMO" -#define LIBRARY_LOG_LEVEL LOG_INFO +#define LIBRARY_LOG_NAME "DEMO" +#define LIBRARY_LOG_LEVEL LOG_INFO #include "logging_stack.h" diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h index 6b71794892..de5c2dfd6a 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h @@ -36,8 +36,8 @@ #include "logging_levels.h" /* Configure name and log level for the MQTT library. */ -#define LIBRARY_LOG_NAME "MQTT" -#define LIBRARY_LOG_LEVEL LOG_INFO +#define LIBRARY_LOG_NAME "MQTT" +#define LIBRARY_LOG_LEVEL LOG_INFO #include "logging_stack.h" diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 19998f9066..14577e0b51 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -19,11 +19,27 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* + * Demo for showing the use of MQTT APIs to establish an MQTT session, + * subscribe to a topic, publish to a topic, receive incoming publishes, + * unsubscribe from a topic and disconnect the MQTT session. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TCP connection established using POSIX sockets. + * The example is single threaded and uses statically allocated memory; + * it uses QOS0 and therefore does not implement any retransmission + * mechanism for Publish messages. + */ + /* Standard includes. */ +#include #include +#include /* POSIX socket includes. */ +#include #include +#include #include #include @@ -41,19 +57,24 @@ * This demo uses the Mosquitto test server. This is a public MQTT server; do not * publish anything sensitive to this server. */ -#define SERVER "test.mosquitto.org" +#define BROKER_ENDPOINT "test.mosquitto.org" + +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) /** * @brief MQTT server port number. * * In general, port 1883 is for unsecured MQTT connections. */ -#define PORT 1883 +#define BROKER_PORT ( 1883 ) /** * @brief Size of the network buffer for MQTT packets. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define NETWORK_BUFFER_SIZE ( 1024U ) /* Check that client identifier is defined. */ #ifndef CLIENT_IDENTIFIER @@ -63,43 +84,251 @@ /** * @brief Length of client identifier. */ -#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ -#define CONNACK_RECV_TIMEOUT_MS ( 1000 ) +#define CONNACK_RECV_TIMEOUT_MS ( 1000 ) + +/** + * @brief The topic to subscribe and publish to in the example. + * + * The topic name starts with the client identifier to ensure that each demo + * interacts with a unique topic name. + */ +#define MQTT_EXAMPLE_TOPIC CLIENT_IDENTIFIER "/example/topic" + +/** + * @brief Length of client MQTT topic. + */ +#define MQTT_EXAMPLE_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_TOPIC ) - 1 ) ) + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE "Hello World!" + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_MESSAGE ) - 1 ) ) + +/** + * @brief Timeout for MQTT_ProcessLoop function in milliseconds. + * + * Please note + */ +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 500U ) + +/** + * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to + * broker. + */ +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) + +/** + * @brief Delay between MQTT publishes in seconds. + */ +#define DELAY_BETWEEN_PUBLISHES_SECONDS ( 1U ) + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) + +/*-----------------------------------------------------------*/ + +/* @brief errno to check transport error. */ +extern int errno; /*-----------------------------------------------------------*/ /** - * @brief Establish a TCP connection to the given server. + * @brief globalEntryTime entry time into the application to use as a reference + * timestamp in #getTimeMs function. #getTimeMs will always return the difference + * of current time with the globalEntryTime. This will reduce the chances of + * overflow for 32 bit unsigned integer used for holding the timestamp. + * + */ +static uint32_t globalEntryTimeMs = 0U; + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker; + * it is used to match received Subscribe ACK to the transmitted subscribe. + */ +static uint16_t globalSubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; + * it is used to match received Unsubscribe response to the transmitted unsubscribe + * request. + */ +static uint16_t globalUnsubscribePacketIdentifier = 0U; + +/*-----------------------------------------------------------*/ + +/** + * @brief Creates a TCP connection to the MQTT broker as specified by + * BROKER_ENDPOINT and BROKER_PORT defined at the top of this file. * * @param[in] pServer Host name of server. * @param[in] port Server port. + * @param[out] pTcpSocket Pointer to TCP socket file descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ); + +/** + * @brief The transport send function provided to the MQTT context. + * + * @param[in] tcpSocket TCP socket. + * @param[in] pMessage Data to send. + * @param[in] bytesToSend Length of data to send. + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. + */ +static int32_t transportSend( MQTTNetworkContext_t tcpSocket, + const void * pMessage, + size_t bytesToSend ); + +/** + * @brief The transport receive function provided to the MQTT context. + * + * @param[in] tcpSocket TCP socket. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToSend Size of pBuffer. + * + * @return Number of bytes received; negative value on error. + */ +static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief The timer query function provided to the MQTT context. + * + * This function returns the elapsed time with reference to #globalEntryTimeMs. + * + * @return Time in milliseconds. + */ +static uint32_t getTimeMs( void ); + +/** + * @brief The function to handle the incoming publishes. + * + * @param[in] pPublishInfo Pointer to publish info of the incoming publish. + * @param[in] packetIdentifier Packet identifier of the incoming publish. + */ +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ); + +/** + * @brief The application callback function for getting the incoming publish + * and incoming acks reported from MQTT library. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] packetIdentifier Packet identifier of the incoming packet. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket.. + * + * @param[in] pContext MQTT context pointer. + * @param[in] tcpSocket TCP socket. + * + * @return EXIT_SUCCESS if an MQTT session is established; + * EXIT_FAILURE otherwise. + */ +static int establishMqttSession( MQTTContext_t * pContext, + int tcpSocket ); + +/** + * @brief Close an MQTT session by sending MQTT DISCONNECT. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if DISCONNECT was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int disconnectMqttSession( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT SUBSCRIBE to subscribe to #MQTT_EXAMPLE_TOPIC + * defined at the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int subscribeToTopic( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT UNSUBSCRIBE to unsubscribe from + * #MQTT_EXAMPLE_TOPIC defined at the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at + * the top of the file. + * + * @param[in] pContext MQTT context pointer. * - * @return A file descriptor representing the TCP socket; -1 on failure. + * @return EXIT_SUCCESS if PUBLISH was successfully sent; + * EXIT_FAILURE otherwise. */ +static int publishToTopic( MQTTContext_t * pContext ); + +/*-----------------------------------------------------------*/ + static int connectToServer( const char * pServer, - uint16_t port ) + uint16_t port, + int * pTcpSocket ) { - int status, tcpSocket = -1; - struct addrinfo * pListHead = NULL, * pIndex; + int status = EXIT_SUCCESS; + struct addrinfo hints, * pIndex, * pListHead = NULL; struct sockaddr * pServerInfo; uint16_t netPort = htons( port ); socklen_t serverInfoLength; + struct timeval transportTimeout; + + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0, sizeof( hints ) ); + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, NULL, &pListHead ); + status = getaddrinfo( pServer, NULL, &hints, &pListHead ); if( status != -1 ) { /* Attempt to connect to one of the retrieved DNS records. */ for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) { - tcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - if( tcpSocket == -1 ) + if( *pTcpSocket == -1 ) { continue; } @@ -119,11 +348,11 @@ static int connectToServer( const char * pServer, serverInfoLength = sizeof( struct sockaddr_in6 ); } - status = connect( tcpSocket, pServerInfo, serverInfoLength ); + status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); if( status == -1 ) { - close( tcpSocket ); + close( *pTcpSocket ); } else { @@ -134,11 +363,53 @@ static int connectToServer( const char * pServer, if( pIndex == NULL ) { /* Fail if no connection could be established. */ - status = -1; + status = EXIT_FAILURE; + LogError( ( "Located but could not connect to MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); } else { - status = tcpSocket; + status = EXIT_SUCCESS; + LogInfo( ( "TCP connection established with %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + } + } + else + { + LogError( ( "Could not locate MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + status = EXIT_FAILURE; + } + + /* Set the socket option for send and receive timeouts. */ + if( status == EXIT_SUCCESS ) + { + transportTimeout.tv_sec = 0; + transportTimeout.tv_usec = ( TRANSPORT_SEND_RECV_TIMEOUT_MS * 1000 ); + + /* Set the receive timeout. */ + if( setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_RCVTIMEO, + ( char * ) &transportTimeout, + sizeof( transportTimeout ) ) < 0 ) + { + LogError( ( "Setting socket receive timeout failed." ) ); + status = EXIT_FAILURE; + } + + /* Set the send timeout. */ + if( setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_SNDTIMEO, + ( char * ) &transportTimeout, + sizeof( transportTimeout ) ) < 0 ) + { + LogError( ( "Setting socket send timeout failed." ) ); + status = EXIT_FAILURE; } } @@ -152,117 +423,238 @@ static int connectToServer( const char * pServer, /*-----------------------------------------------------------*/ -/** - * @brief The transport send function provided to the MQTT context. - * - * @param[in] tcpSocket TCP socket. - * @param[in] pMessage Data to send. - * @param[in] bytesToSend Length of data to send. - * - * @return Number of bytes sent; negative value on error. - */ static int32_t transportSend( MQTTNetworkContext_t tcpSocket, const void * pMessage, size_t bytesToSend ) { - return ( int32_t ) send( tcpSocket, pMessage, bytesToSend, 0 ); + int32_t bytesSend = 0; + + bytesSend = send( ( int ) tcpSocket, pMessage, bytesToSend, 0 ); + + if( bytesSend < 0 ) + { + /* Check if it was time out */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate that send was timed out. */ + bytesSend = 0; + } + } + + return bytesSend; } /*-----------------------------------------------------------*/ -/** - * @brief The transport receive function provided to the MQTT context. - * - * @param[in] tcpSocket TCP socket. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToSend Size of pBuffer. - * - * @return Number of bytes received; negative value on error. - */ static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ) { - return ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); + int32_t bytesReceived = 0; + + bytesReceived = ( int32_t ) recv( ( int ) tcpSocket, pBuffer, bytesToRecv, 0 ); + + if( bytesReceived == 0 ) + { + /* Server closed the connection, treat it as an error. */ + bytesReceived = -1; + } + else if( bytesReceived < 0 ) + { + /* Check if it was time out */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate nothing to receive */ + bytesReceived = 0; + } + } + else + { + /* EMPTY else */ + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +static uint32_t getTimeMs( void ) +{ + uint32_t timeMs; + struct timespec timeSpec; + + /* Get the MONOTONIC time. */ + clock_gettime( CLOCK_MONOTONIC, &timeSpec ); + + /* Calculate the milliseconds from timespec. */ + timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) + + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); + + /* Reduce globalEntryTime from obtained time so as to always return the + * elapsed time in the application. */ + timeMs = ( uint32_t ) ( timeMs - globalEntryTimeMs ); + + return timeMs; } /*-----------------------------------------------------------*/ -/** - * @brief The timer query function provided to the MQTT context. - * - * Currently not implemented. - */ -static uint32_t getTime( void ) +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ) { - return 0; + assert( pPublishInfo != NULL ); + + /* Process incoming Publish. */ + LogInfo( ( "Incoming QOS : %d.", pPublishInfo->qos ) ); + + /* Verify the received publish is for the we have subscribed to. */ + if( ( pPublishInfo->topicNameLength == MQTT_EXAMPLE_TOPIC_LENGTH ) && + ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ) ) ) + { + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + LogInfo( ( "Incoming Publish Message : %.*s.", + ( int ) pPublishInfo->payloadLength, + ( const char * ) pPublishInfo->pPayload ) ); + } + else + { + LogInfo( ( "Incoming Publish Topic Name: %.*s does not match subscribed topic.", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + } } /*-----------------------------------------------------------*/ -/** - * @brief MQTT event callback. - * - * Currently not implemented. - */ static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { + assert( pContext != NULL ); + assert( pPacketInfo != NULL ); + + /* Handle incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + assert( pPublishInfo != NULL ); + /* Handle incoming publish. */ + handleIncomingPublish( pPublishInfo, packetIdentifier ); + } + else + { + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + LogInfo( ( "Subscribed to the topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( globalSubscribePacketIdentifier == packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + LogInfo( ( "Unsubscribed from the topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( globalUnsubscribePacketIdentifier == packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + LogInfo( ( "PIGRESP received." ) ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).", + pPacketInfo->type ) ); + } + } } /*-----------------------------------------------------------*/ -/** - * @brief Establish an MQTT session over a TCP connection by sending MQTT CONNECT. - * - * @param[in] pContext MQTT context. - * @param[in] tcpSocket TCP socket. - * - * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. - */ static int establishMqttSession( MQTTContext_t * pContext, int tcpSocket ) { int status = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; + char sessionPresent; MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + assert( pContext != NULL ); + assert( tcpSocket >= 0 ); + /* The network buffer must remain valid for the lifetime of the MQTT context. */ static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; - /* Initialize MQTT context. */ - transport.networkContext = tcpSocket; + /* Fill in TransportInterface send and receive function pointers. + * For this demo, TCP sockets are used to send and receive data + * from network. Network context is socket file descriptor.*/ + transport.networkContext = ( MQTTNetworkContext_t ) tcpSocket; transport.send = transportSend; transport.recv = transportRecv; + /* Fill the values for network buffer. */ networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; + /* Application callbacks for receiving incoming publishes and incoming acks + * from MQTT library. */ callbacks.appCallback = eventCallback; - callbacks.getTime = getTime; + /* Application callback for getting the time for MQTT library. This time + * function will be used to calculate intervals in MQTT library.*/ + callbacks.getTime = getTimeMs; + + /* Initialize MQTT library. */ MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); /* Establish MQTT session with a CONNECT packet. */ + + /* Start with a clean session i.e. direct the MQTT broker to discard any + * previous session data. Also, establishing a connection with clean session + * will ensure that the broker does not store any data when this client + * gets disconnected. */ connectInfo.cleanSession = true; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - connectInfo.keepAliveSeconds = 0; + + /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this demo. */ connectInfo.pUserName = NULL; connectInfo.userNameLength = 0; connectInfo.pPassword = NULL; connectInfo.passwordLength = 0; - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, NULL ); + /* Send MQTT CONNECT packet to broker. */ + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, &sessionPresent ); if( mqttStatus != MQTTSuccess ) { status = EXIT_FAILURE; + LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + } + else + { + LogInfo( ( "MQTT connection successfully established with broker." ) ); } return status; @@ -270,23 +662,150 @@ static int establishMqttSession( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ -/** - * @brief Close an MQTT session by sending MQTT DISCONNECT. - * - * @param[in] pContext MQTT context. - * - * @return EXIT_SUCCESS if DISCONNECT was successfully sent; EXIT_FAILURE otherwise. - */ static int disconnectMqttSession( MQTTContext_t * pContext ) { int status = EXIT_SUCCESS; + assert( pContext != NULL ); + + /* Send DISCONNECT. */ MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); if( mqttStatus != MQTTSuccess ) { + LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int subscribeToTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to only one topic and uses QOS0. */ + pSubscriptionList[ 0 ].qos = MQTTQoS0; + pSubscriptionList[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; + + /* Generate packet identifier for the SUBSCRIBE packet. */ + globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send SUBSCRIBE packet. */ + mqttStatus = MQTT_Subscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalSubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "SUBSCRIBE sent for topic %.*s to broker.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to and unsubscribes from only one topic + * and uses QOS0. */ + pSubscriptionList[ 0 ].qos = MQTTQoS0; + pSubscriptionList[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; + + /* Generate packet identifier for the UNSUBSCRIBE packet. */ + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send UNSUBSCRIBE packet. */ + mqttStatus = MQTT_Unsubscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalUnsubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); status = EXIT_FAILURE; } + else + { + LogInfo( ( "UNSUBSCRIBE sent for topic %.*s to broker.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int publishToTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttSuccess; + MQTTPublishInfo_t publishInfo; + + assert( pContext != NULL ); + + /* Some fields not used by this demo so start with everything at 0. */ + ( void ) memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + + /* This example publishes to only one topic and uses QOS0. */ + publishInfo.qos = MQTTQoS0; + publishInfo.pTopicName = MQTT_EXAMPLE_TOPIC; + publishInfo.topicNameLength = MQTT_EXAMPLE_TOPIC_LENGTH; + publishInfo.pPayload = MQTT_EXAMPLE_MESSAGE; + publishInfo.payloadLength = MQTT_EXAMPLE_MESSAGE_LENGTH; + + /* Send PUBLISH packet. Packet Id is not used for a QoS0 publish. + * Hence 0 is passed as packet id. */ + mqttSuccess = MQTT_Publish( pContext, + &publishInfo, + 0U ); + + if( status != MQTTSuccess ) + { + LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", + mqttSuccess ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "PUBLISH send for topic %.*s to broker.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } return status; } @@ -295,38 +814,149 @@ static int disconnectMqttSession( MQTTContext_t * pContext ) /** * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TCP connection established using POSIX sockets. + * The example is single threaded and uses statically allocated memory; + * it uses QOS0 and therefore does not implement any retransmission + * mechanism for Publish messages. + * */ int main( int argc, char ** argv ) { + int status = EXIT_SUCCESS, tcpSocket; bool mqttSessionEstablished = false; - int status = EXIT_SUCCESS; MQTTContext_t context; + MQTTStatus_t mqttStatus; + uint32_t publishCount = 0; + const uint32_t maxPublishCount = 5U; - /* Establish TCP connection. */ - int tcpSocket = connectToServer( SERVER, PORT ); + ( void ) argc; + ( void ) argv; - if( tcpSocket == -1 ) - { - status = EXIT_FAILURE; - } + /* Get the entry time to application. */ + globalEntryTimeMs = getTimeMs(); + + /* Establish a TCP connection with the MQTT broker. This example connects + * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at + * the top of this file. */ + LogInfo( ( "Creating a TCP connection to %.*s:%d.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT, + BROKER_PORT ) ); + status = connectToServer( BROKER_ENDPOINT, BROKER_PORT, &tcpSocket ); /* Establish MQTT session on top of TCP connection. */ - if( status = EXIT_SUCCESS ) + if( status == EXIT_SUCCESS ) { + /* Sends an MQTT Connect packet over the already connected TCP socket + * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); status = establishMqttSession( &context, tcpSocket ); if( status == EXIT_SUCCESS ) { + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be send at the end + * of the demo even if there are intermediate failures. */ mqttSessionEstablished = true; } } - /* Disconnect MQTT session if established. */ + if( status == EXIT_SUCCESS ) + { + /* The client is now connected to the broker. Subscribe to the topic + * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a + * subscribe packet. This client will then publish to the same topic it + * subscribed to, so it will expect all the messages it sends to the broker + * to be sent back to it from the broker. This demo uses QOS0 in Subscribe, + * therefore, the Publish messages received from the broker will have QOS0. */ + status = subscribeToTopic( &context ); + } + + if( status == EXIT_SUCCESS ) + { + /* Process incoming packet from the broker. Acknowledgment for subscription + * ( SUBACK ) will be received here. However after sending the subscribe, the + * client may receive a publish before it receives a subscribe ack. Since this + * demo is subscribing to the topic to which no one is publishing, probability + * of receiving publish message before subscribe ack is zero; but application + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * receive packet from network. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + } + + if( status == EXIT_SUCCESS ) + { + /* Publish messages with QOS0, receive incoming messages and + * send keep alive messages. */ + for( publishCount = 0; publishCount < maxPublishCount; publishCount++ ) + { + LogInfo( ( "Publish to the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = publishToTopic( &context ); + + /* Calling MQTT_ProcessLoop to process incoming publish echo, since + * application subscribed to the same topic the broker will send + * publish message back to the application. This function also + * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS + * has expired since the last MQTT packet sent and receive + * ping responses. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + LogInfo( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + + /* Leave connection idle for some time. */ + sleep( DELAY_BETWEEN_PUBLISHES_SECONDS ); + } + } + + if( status == EXIT_SUCCESS ) + { + /* Unsubscribe from the topic. */ + LogInfo( ( "Unsubscribe from the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = unsubscribeFromTopic( &context ); + } + + if( status == EXIT_SUCCESS ) + { + /* Process Incoming UNSUBACK packet from the broker. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + } + + /* Send an MQTT Disconnect packet over the already connected TCP socket. + * There is no corresponding response for the disconnect packet. After sending + * disconnect, client must close the network connection. */ if( mqttSessionEstablished == true ) { + LogInfo( ( "Disconnecting the MQTT connection with %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + if( status == EXIT_FAILURE ) { + /* Returned status is not used to update the local status as there + * were failures in demo execution. */ ( void ) disconnectMqttSession( &context ); } else @@ -335,13 +965,19 @@ int main( int argc, } } - /* Close TCP connection if established. */ + /* Close the network connection. */ if( tcpSocket != -1 ) { shutdown( tcpSocket, SHUT_RDWR ); close( tcpSocket ); } + /* Log the success message. */ + if( status == EXIT_SUCCESS ) + { + LogInfo( ( "Demo completed successfully." ) ); + } + return status; } From 934a346eefa956320e0892e3a77fce62896e9dd8 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 26 May 2020 15:12:45 -0700 Subject: [PATCH 522/844] feat: Add MQTT Light Weight API demo. (#962) * feat: Add MQTT Light Weight API demo. The demo is pretty much same as the one available on FreeRTOS.Org. 1. It is updated to use new API. 2. It use POSIX calls. 3. Keep-alive mechanism is updated. --- .../mqtt/mqtt_demo_lightweight/CMakeLists.txt | 23 + .../mqtt/mqtt_demo_lightweight/demo_config.h | 53 + .../mqtt/mqtt_demo_lightweight/mqtt_config.h | 49 + .../mqtt_demo_lightweight.c | 1040 +++++++++++++++++ 4 files changed, 1165 insertions(+) create mode 100644 demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt create mode 100644 demos/mqtt/mqtt_demo_lightweight/demo_config.h create mode 100644 demos/mqtt/mqtt_demo_lightweight/mqtt_config.h create mode 100644 demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c diff --git a/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt b/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt new file mode 100644 index 0000000000..9e9c64080f --- /dev/null +++ b/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt @@ -0,0 +1,23 @@ +set( DEMO_NAME "mqtt_demo_lightweight" ) +# Demo target. +add_executable(${DEMO_NAME}) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" + ${LOGGING_SOURCES} +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + mqtt +) + +target_include_directories( + mqtt + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + ${LOGGING_INCLUDE_DIRS} +) diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h new file mode 100644 index 0000000000..5f58e27233 --- /dev/null +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H_ +#define DEMO_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#define LIBRARY_LOG_NAME "DEMO" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient" + +#endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h new file mode 100644 index 0000000000..fb9fdf47d5 --- /dev/null +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the MQTT library. */ +#define LIBRARY_LOG_NAME "MQTT" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Set network context to socket (int). */ +typedef int MQTTNetworkContext_t; + +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c new file mode 100644 index 0000000000..f02a4ae994 --- /dev/null +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -0,0 +1,1040 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Demo for showing use of the MQTT light weight serializer / deserializer + * API to build simple MQTT client. + * The Light weight serializer API lets user to serialize and + * deserialize MQTT messages into a user provided buffer. + * This API allows use of a statically allocated buffer. + * + * The example shown below uses this API to create MQTT messages and + * send them over the connection established using POSIX sockets. + * The example is single threaded and uses statically allocated memory; + * it uses QOS0 and therefore does not implement any retransmission + * mechanism for Publish messages. + * + */ + +/* Include demo_config.h first for logging and other configuration */ +#include "demo_config.h" + +/* Standard includes. */ +#include + +/* POSIX socket includes. */ +#include +#include +#include +#include +#include + +#include +#include + +/* MQTT LightWeight API header. */ +#include "mqtt_lightweight.h" + +/** + * @brief MQTT server host name. + * + * This demo uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + */ +#define MQTT_BROKER_ENDPOINT "test.mosquitto.org" + +/** + * @brief MQTT server port number. + * + * In general, port 1883 is for unsecured MQTT connections. + */ +#define MQTT_BROKER_PORT 1883 + +/** + * @brief Size of the network buffer for MQTT packets. + */ +#define NETWORK_BUFFER_SIZE ( 1024U ) + +/* Check that client identifier is defined. */ +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif + +/** + * @brief The topic to subscribe and publish to in the example. + * + * The topic name starts with the client identifier to ensure that each demo + * interacts with a unique topic name. + */ +#define MQTT_EXAMPLE_TOPIC CLIENT_IDENTIFIER "/example/topic" + +/** + * @brief Dimensions a file scope buffer currently used to send and receive MQTT data + * from a socket. + */ +#define SHARED_BUFFER_SIZE 500U + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE "Hello Light Weight MQTT World!" + +/** + * @brief Keep alive period in seconds for MQTT connection. + */ +#define MQTT_KEEP_ALIVE_PERIOD_SECONDS 5U + +/** + * @brief Socket layer transportTimeout in milliseconds. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS 200U + +/** + * @brief Number of time network receive will be attempted + * if it fails due to transportTimeout. + */ +#define MQTT_MAX_RECV_ATTEMPTS 10U + +/** + * @brief Delay between two demo iterations. + */ +#define MQTT_DEMO_ITERATION_DELAY_SECONDS 5U + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a TCP connection to the given server. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * @param[out] pSocket pointer to the socket descriptor if connect + * is successful, this call will return the socket descriptor. + * + * @return EXIT_SUCCESS or EXIT_FAILURE. + */ +static int connectToServer( const char * pServer, + uint16_t port, + int * pSocket ); + +/** + * @brief Establish an MQTT session over a TCP connection by sending MQTT CONNECT. + * + * @param[in] tcpSocket TCP socket. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used for serialzing CONNECT packet and deserializing CONN-ACK. + * + * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. + */ +static int createMQTTConnectionWithBroker( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Subscribes to the topic as specified in MQTT_EXAMPLE_TOPIC at the top of + * this file. + * + * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which + * an MQTT connection has been established. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used for serialzing SUBSCRIBE packet. + * + */ +static void mqttSubscribeToTopic( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Publishes a message MQTT_EXAMPLE_MESSAGE on MQTT_EXAMPLE_TOPIC topic. + * + * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which + * an MQTT connection has been established. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used for serialzing PUBLISH packet. + * + */ +static void mqttPublishToTopic( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Unsubscribes from the previously subscribed topic as specified + * in MQTT_EXAMPLE_TOPIC. + * + * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which + * an MQTT connection has been established. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used for serialzing UNSUBSCRIBE packet. + * + */ +static void mqttUnsubscribeFromTopic( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Disconnect From the MQTT broker. + * + * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which + * an MQTT connection has been established. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used for serialzing DISCONNECT packet. + */ +static void mqttDisconnect( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Send Ping Request to the MQTT broker. + * + * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which + * an MQTT connection has been established. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used for serialzing PING request packet. + */ +static void mqttKeepAlive( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Receive and validate MQTT packet from the broker, determine the type + * of the packet and process the packet based on the type. + * + * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which + * an MQTT connection has been established. + * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. + * The buffer is used to deserialize incoming MQTT packet. + * + */ +static void mqttProcessIncomingPacket( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Process a response or ack to an MQTT request (PING, SUBSCRIBE + * or UNSUBSCRIBE). This function processes PING_RESP, SUB_ACK and UNSUB_ACK. + * + * @param[in] pIncomingPacket is a pointer to structure containing deserialized + * MQTT response. + * @param[in] packetId is packet identifier from the incoming MQTT packet, + * if it was received. + * + * @note Not all responses contain packet identifier. + */ +static void mqttProcessResponse( MQTTPacketInfo_t * pIncomingPacket, + uint16_t packetId ); + +/** + * @brief Process incoming Publish message. + * + * @param[in] pPubInfo is a pointer to structure containing deserialized + * Publish message. + * + * @param[in] packetId is packet identifier from the incoming publish if it was received. + * valid for only for QOS1 and QOS2. + */ +static void mqttProcessIncomingPublish( MQTTPublishInfo_t * pPubInfo, + uint16_t packetId ); + +/** + * @brief Generate and return monotonically increasing packet identifier. + * + * @return The next PacketId. + * + * @note This function is not thread safe. + */ +static uint16_t getNextPacketIdentifier(); + +/** + * @brief Calculate the interval between two timestamps, including + * when the later value has overflowed. + * + * @note In C, the operands are promoted to signed integers in subtraction. + * Using this function avoids the need to cast the result of subtractions back + * to uint32_t. + * + * @param[in] later The later time stamp, in milliseconds. + * @param[in] start The earlier time stamp, in milliseconds. + * + * @return later - start. + */ +static uint32_t calculateElapsedTime( uint32_t later, + uint32_t start ); + +/*-----------------------------------------------------------*/ + +/* @brief errno to check transport error. */ +extern int errno; + +/* @brief Static buffer used to hold MQTT messages being sent and received. */ +static uint8_t mqttSharedBuffer[ SHARED_BUFFER_SIZE ]; + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker; + * it is used to match received Subscribe ACK to the transmitted SUBSCRIBE request. + */ +static uint16_t subscribePacketIdentifier; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; + * it is used to match received Unsubscribe response to the transmitted unsubscribe + * request. + */ +static uint16_t unsubscribePacketIdentifier; + +/*-----------------------------------------------------------*/ + +static uint16_t getNextPacketIdentifier() +{ + static uint16_t packetId = 0; + + packetId++; + + /* Since 0 is invalid packet identifier value, + * take care of it when it rolls over */ + if( packetId == 0 ) + { + packetId = 1; + } + + return packetId; +} + +/*-----------------------------------------------------------*/ + +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ) +{ + int status = EXIT_SUCCESS; + struct addrinfo * pListHead = NULL, * pIndex, hint; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + struct timeval transportTimeout; + + /* Set up hint structure, so that only TCP address structures are returned. */ + memset( ( void * ) &hint, 0x00, sizeof( hint ) ); + hint.ai_socktype = SOCK_STREAM; + /* Perform a DNS lookup on the given host name. */ + status = getaddrinfo( pServer, NULL, &hint, &pListHead ); + + if( status != -1 ) + { + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( *pTcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + } + + status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); + + if( status == -1 ) + { + close( *pTcpSocket ); + } + else + { + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + LogError( ( "Failed to establish TCP connection to the broker %s.\n", pServer ) ); + status = EXIT_FAILURE; + } + else + { + /* Set send and receive timeouts */ + transportTimeout.tv_sec = 0; + transportTimeout.tv_usec = ( TRANSPORT_SEND_RECV_TIMEOUT_MS * 1000 ); + + if( setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_RCVTIMEO, + ( char * ) &transportTimeout, + sizeof( transportTimeout ) ) < 0 ) + { + LogError( ( "Setting socket receive transportTimeout failed \n" ) ); + status = EXIT_FAILURE; + } + + if( setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_SNDTIMEO, + ( char * ) &transportTimeout, + sizeof( transportTimeout ) ) < 0 ) + { + LogError( ( "Setting socket send transportTimeout failed.\n" ) ); + status = EXIT_FAILURE; + } + } + } + else + { + LogError( ( "DNS lookup failed for broker %s.\n", pServer ) ); + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The transport receive wrapper function supplied to the MQTT library for + * receiving type and length of an incoming MQTT packet. + * + * @param[in] tcpSocket TCP socket. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToRecv Size of pBuffer. + * + * @return Number of bytes received or zero to indicate transportTimeout; negative value on error. + */ +static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + + bytesReceived = ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); + + if( bytesReceived == 0 ) + { + /* Server closed the connection, treat it as an error */ + bytesReceived = -1; + } + else if( bytesReceived < 0 ) + { + /* Check if it was time out */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate nothing to receive */ + bytesReceived = 0; + } + } + else + { + /* EMPTY else */ + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +static int createMQTTConnectionWithBroker( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTConnectInfo_t mqttConnectInfo; + size_t remainingLength; + size_t packetSize; + MQTTStatus_t result; + MQTTPacketInfo_t incomingPacket; + int status; + unsigned short packetId = 0; + bool sessionPresent = false; + uint8_t receiveAttempts = 0; + + /*** + * For readability, error handling in this function is restricted to the use of + * asserts(). + ***/ + + + /* Many fields not used in this demo so start with everything at 0. */ + memset( ( void * ) &mqttConnectInfo, 0x00, sizeof( mqttConnectInfo ) ); + + /* Start with a clean session i.e. direct the MQTT broker to discard any + * previous session data. Also, establishing a connection with clean session + * will ensure that the broker does not store any data when this client + * gets disconnected. */ + mqttConnectInfo.cleanSession = true; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + mqttConnectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + mqttConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( CLIENT_IDENTIFIER ); + + /* Set MQTT keep-alive period. It is the responsibility of the application to ensure + * that the interval between Control Packets being sent does not exceed the Keep Alive value. + * In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet. */ + mqttConnectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_PERIOD_SECONDS; + + /* Get size requirement for the connect packet */ + result = MQTT_GetConnectPacketSize( &mqttConnectInfo, NULL, &remainingLength, &packetSize ); + + /* Make sure the packet size is less than static buffer size. */ + assert( result == MQTTSuccess ); + assert( packetSize < pFixedBuffer->size ); + + /* Serialize MQTT connect packet into the provided buffer. */ + result = MQTT_SerializeConnect( &mqttConnectInfo, NULL, remainingLength, pFixedBuffer ); + assert( result == MQTTSuccess ); + + /* Send the serialized connect packet to the MQTT broker */ + status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + assert( status == ( int ) packetSize ); + + /* Reset all fields of the incoming packet structure. */ + memset( ( void * ) &incomingPacket, 0x00, sizeof( MQTTPacketInfo_t ) ); + + /* Wait for connection acknowledgment. We cannot assume received data is the + * connection acknowledgment. Therefore this function reads type and remaining + * length of the received packet, before processing entire packet - although in + * this case to keep the example simple error checks are just performed by + * asserts. + */ + do + { + /* Since tcpSocket has timeout, retry until the data is available */ + result = MQTT_GetIncomingPacketTypeAndLength( transportRecv, tcpSocket, &incomingPacket ); + receiveAttempts++; + } while ( ( result == MQTTNoDataAvailable ) && ( receiveAttempts < MQTT_MAX_RECV_ATTEMPTS ) ); + + assert( result == MQTTSuccess ); + assert( incomingPacket.type == MQTT_PACKET_TYPE_CONNACK ); + assert( incomingPacket.remainingLength <= pFixedBuffer->size ); + + /* Now receive the remaining packet into statically allocated buffer. */ + status = recv( tcpSocket, ( void * ) pFixedBuffer->pBuffer, incomingPacket.remainingLength, 0 ); + assert( status == ( int ) incomingPacket.remainingLength ); + + incomingPacket.pRemainingData = pFixedBuffer->pBuffer; + + /* Deserialize the received packet to make sure the content of the CONNACK + * is valid. Note that the packetId is not present in the connection ack. */ + result = MQTT_DeserializeAck( &incomingPacket, &packetId, &sessionPresent ); + + if( result != MQTTSuccess ) + { + LogError( ( "Connection with MQTT broker failed.\r\n" ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "Successfully connected with the MQTT broker\r\n" ) ); + status = EXIT_SUCCESS; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void mqttSubscribeToTopic( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t result; + MQTTSubscribeInfo_t mqttSubscription[ 1 ]; + size_t remainingLength; + size_t packetSize; + int status; + MQTTFixedBuffer_t fixedBuffer; + + /*** + * For readability, error handling in this function is restricted to the use of + * asserts(). + ***/ + + /* Some fields not used by this demo so start with everything as 0. */ + memset( ( void * ) &mqttSubscription, 0x00, sizeof( mqttSubscription ) ); + + /* Subscribe to the MQTT_EXAMPLE_TOPIC topic filter. This example subscribes to + * only one topic and uses QOS0. */ + mqttSubscription[ 0 ].qos = MQTTQoS0; + mqttSubscription[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + mqttSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( MQTT_EXAMPLE_TOPIC ); + + result = MQTT_GetSubscribePacketSize( mqttSubscription, + sizeof( mqttSubscription ) / sizeof( MQTTSubscribeInfo_t ), + &remainingLength, &packetSize ); + + /* Make sure the packet size is less than static buffer size. */ + assert( result == MQTTSuccess ); + assert( packetSize < pFixedBuffer->size ); + subscribePacketIdentifier = getNextPacketIdentifier(); + + /* Serialize subscribe into statically allocated buffer. */ + result = MQTT_SerializeSubscribe( mqttSubscription, + sizeof( mqttSubscription ) / sizeof( MQTTSubscribeInfo_t ), + subscribePacketIdentifier, + remainingLength, + pFixedBuffer ); + + assert( result == MQTTSuccess ); + + /* Send Subscribe request to the broker. */ + status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + assert( status == ( int ) packetSize ); +} +/*-----------------------------------------------------------*/ + +static void mqttPublishToTopic( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t result; + MQTTPublishInfo_t mqttPublishInfo; + size_t remainingLength; + size_t packetSize = 0; + size_t headerSize = 0; + uint8_t * pPacketIdentifierHigh; + int status; + + /*** + * For readability, error handling in this function is restricted to the use of + * asserts(). + ***/ + + /* Some fields not used by this demo so start with everything as 0. */ + memset( ( void * ) &mqttPublishInfo, 0x00, sizeof( mqttPublishInfo ) ); + + /* This demo uses QOS0 */ + mqttPublishInfo.qos = MQTTQoS0; + mqttPublishInfo.retain = false; + mqttPublishInfo.pTopicName = MQTT_EXAMPLE_TOPIC; + mqttPublishInfo.topicNameLength = ( uint16_t ) strlen( MQTT_EXAMPLE_TOPIC ); + mqttPublishInfo.pPayload = MQTT_EXAMPLE_MESSAGE; + mqttPublishInfo.payloadLength = strlen( MQTT_EXAMPLE_MESSAGE ); + + /* Find out length of Publish packet size. */ + result = MQTT_GetPublishPacketSize( &mqttPublishInfo, &remainingLength, &packetSize ); + assert( result == MQTTSuccess ); + + /* Make sure the packet size is less than static buffer size. */ + assert( packetSize < pFixedBuffer->size ); + + /* Serialize MQTT Publish packet header. The publish message payload will + * be sent directly in order to avoid copying it into the buffer. + * QOS0 does not make use of packet identifier, therefore value of 0 is used */ + result = MQTT_SerializePublishHeader( &mqttPublishInfo, + 0, + remainingLength, + pFixedBuffer, + &headerSize ); + LogDebug( ( "Serialized PUBLISH header size is %lu.", + headerSize ) ); + assert( result == MQTTSuccess ); + /* Send Publish header to the broker. */ + status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, headerSize, 0 ); + assert( status == ( int ) headerSize ); + /* Send Publish payload to the broker */ + status = send( tcpSocket, ( void * ) mqttPublishInfo.pPayload, mqttPublishInfo.payloadLength, 0 ); + assert( status == ( int ) mqttPublishInfo.payloadLength ); +} +/*-----------------------------------------------------------*/ + +static void mqttUnsubscribeFromTopic( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t result; + MQTTSubscribeInfo_t mqttSubscription[ 1 ]; + size_t remainingLength; + size_t packetSize; + int status; + + /* Some fields not used by this demo so start with everything at 0. */ + memset( ( void * ) &mqttSubscription, 0x00, sizeof( mqttSubscription ) ); + + /* Unsubscribe to the MQTT_EXAMPLE_TOPIC topic filter. */ + mqttSubscription[ 0 ].qos = MQTTQoS0; + mqttSubscription[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + mqttSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( MQTT_EXAMPLE_TOPIC ); + + result = MQTT_GetUnsubscribePacketSize( mqttSubscription, + sizeof( mqttSubscription ) / sizeof( MQTTSubscribeInfo_t ), + &remainingLength, + &packetSize ); + assert( result == MQTTSuccess ); + /* Make sure the packet size is less than static buffer size */ + assert( packetSize < pFixedBuffer->size ); + + /* Get next unique packet identifier */ + unsubscribePacketIdentifier = getNextPacketIdentifier(); + + result = MQTT_SerializeUnsubscribe( mqttSubscription, + sizeof( mqttSubscription ) / sizeof( MQTTSubscribeInfo_t ), + unsubscribePacketIdentifier, + remainingLength, + pFixedBuffer ); + assert( result == MQTTSuccess ); + + /* Send Unsubscribe request to the broker. */ + status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + assert( status == ( int ) packetSize ); +} +/*-----------------------------------------------------------*/ + +static void mqttKeepAlive( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t result; + int status; + size_t packetSize = 0; + + /* Calculate PING request size. */ + status = MQTT_GetPingreqPacketSize( &packetSize ); + + /* Make sure the buffer can accommodate ping request. */ + assert( packetSize <= pFixedBuffer->size ); + + result = MQTT_SerializePingreq( pFixedBuffer ); + assert( result == MQTTSuccess ); + + /* Send Ping Request to the broker. */ + status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + assert( status == ( int ) packetSize ); +} + +/*-----------------------------------------------------------*/ + +static void mqttDisconnect( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t result; + int32_t status; + size_t packetSize = 0; + + status = MQTT_GetDisconnectPacketSize( &packetSize ); + + assert( packetSize <= pFixedBuffer->size ); + + result = MQTT_SerializeDisconnect( pFixedBuffer ); + assert( result == MQTTSuccess ); + + /* Send disconnect packet to the broker */ + status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + assert( status == ( int ) packetSize ); +} + +/*-----------------------------------------------------------*/ + +static void mqttProcessResponse( MQTTPacketInfo_t * pIncomingPacket, + uint16_t packetId ) +{ + switch( pIncomingPacket->type & 0xf0 ) + { + case MQTT_PACKET_TYPE_SUBACK: + LogInfo( ( "Subscribed to the topic %s.\r\n", MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( subscribePacketIdentifier == packetId ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + LogInfo( ( "Unsubscribed from the topic %s.\r\n", MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( unsubscribePacketIdentifier == packetId ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + LogInfo( ( "Ping Response successfully received.\r\n" ) ); + break; + + /* Any other packet type is invalid. */ + default: + LogWarn( ( "mqttProcessResponse() called with unknown packet type:(%u).", + ( unsigned ) pIncomingPacket->type ) ); + } +} + +/*-----------------------------------------------------------*/ + +static void mqttProcessIncomingPublish( MQTTPublishInfo_t * pPubInfo, + uint16_t packetId ) +{ + assert( pPubInfo != NULL ); + + /* Since this example does not make use of QOS1 or QOS2, + * packet identifier is not required. */ + ( void ) packetId; + + LogInfo( ( "Incoming QOS : %d\n", pPubInfo->qos ) ); + + /* Verify the received publish is for the topic we have subscribed to. */ + if( ( pPubInfo->topicNameLength == strlen( MQTT_EXAMPLE_TOPIC ) ) && + ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, pPubInfo->pTopicName, pPubInfo->topicNameLength ) ) ) + { + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n", + pPubInfo->topicNameLength, + pPubInfo->pTopicName ) ); + LogInfo( ( "Incoming Publish Message : %.*s\n", + pPubInfo->payloadLength, + pPubInfo->pPayload ) ); + } + else + { + LogError( ( "Incoming Publish Topic Name: %.*s does not match subscribed topic. \n", + pPubInfo->topicNameLength, + pPubInfo->pTopicName ) ); + } +} + +/*-----------------------------------------------------------*/ + +static void mqttProcessIncomingPacket( int tcpSocket, + MQTTFixedBuffer_t * pFixedBuffer ) +{ + MQTTStatus_t result; + MQTTPacketInfo_t incomingPacket; + MQTTPublishInfo_t publishInfo; + uint16_t packetId = 0; + int status; + bool sessionPresent = false; + uint16_t receiveAttempts = 0; + + /*** + * For readability, error handling in this function is restricted to the use of + * asserts(). + ***/ + + memset( ( void * ) &incomingPacket, 0x00, sizeof( MQTTPacketInfo_t ) ); + + /* Determine incoming packet type and remaining length. */ + do + { + /* Retry till data is available */ + result = MQTT_GetIncomingPacketTypeAndLength( transportRecv, tcpSocket, &incomingPacket ); + receiveAttempts++; + } while ( ( result == MQTTNoDataAvailable ) && ( receiveAttempts < MQTT_MAX_RECV_ATTEMPTS ) ); + + assert( result == MQTTSuccess ); + assert( incomingPacket.remainingLength <= pFixedBuffer->size ); + + /* Current implementation expects an incoming Publish and three different + * responses ( SUBACK, PINGRESP and UNSUBACK ). */ + + /* Receive the remaining bytes. */ + status = recv( tcpSocket, ( void * ) pFixedBuffer->pBuffer, incomingPacket.remainingLength, 0 ); + assert( status == ( int ) incomingPacket.remainingLength ); + + incomingPacket.pRemainingData = pFixedBuffer->pBuffer; + + if( ( incomingPacket.type & 0xf0 ) == MQTT_PACKET_TYPE_PUBLISH ) + { + result = MQTT_DeserializePublish( &incomingPacket, &packetId, &publishInfo ); + assert( result == MQTTSuccess ); + + /* Process incoming Publish message. */ + mqttProcessIncomingPublish( &publishInfo, packetId ); + } + else + { + /* If the received packet is not a Publish message, then it is an ACK for one + * of the messages we sent out, verify that the ACK packet is a valid MQTT + * packet. Since CONNACK is already processed, session present parameter is + * to NULL */ + result = MQTT_DeserializeAck( &incomingPacket, &packetId, &sessionPresent ); + assert( result == MQTTSuccess ); + + /* Process the response. */ + mqttProcessResponse( &incomingPacket, packetId ); + } +} + +/*-----------------------------------------------------------*/ + +static uint32_t calculateElapsedTime( uint32_t later, + uint32_t start ) +{ + return later - start; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + */ +int main( int argc, + char ** argv ) +{ + int status = EXIT_SUCCESS; + MQTTFixedBuffer_t fixedBuffer; + uint16_t loopCount = 0; + const uint16_t maxLoopCount = 5U; + uint16_t demoIterations = 0; + const uint16_t maxDemoIterations = 10U; + time_t lastControlPacketSentTimeStamp = 0; + struct timespec currentTimeStamp; + uint32_t timeDiff = 0; + bool controlPacketSent = false; + bool publishPacketSent = false; + int tcpSocket = -1; + + /*** + * Set Fixed size buffer structure that is required by API to serialize + * and deserialize data. pBuffer is pointing to a fixed sized mqttSharedBuffer. + * The application may allocate dynamic memory as well. + ***/ + fixedBuffer.pBuffer = mqttSharedBuffer; + fixedBuffer.size = SHARED_BUFFER_SIZE; + + for( demoIterations = 0; demoIterations < maxDemoIterations; demoIterations++ ) + { + /* Establish a TCP connection with the MQTT broker. This example connects to + * the MQTT broker as specified in MQTT_BROKER_ENDPOINT and + * MQTT_BROKER_PORT at the top of this file. */ + LogInfo( ( "Establishing TCP connection to the broker %s.\r\n", MQTT_BROKER_ENDPOINT ) ); + status = connectToServer( MQTT_BROKER_ENDPOINT, MQTT_BROKER_PORT, &tcpSocket ); + + if( status == EXIT_SUCCESS ) + { + /* Sends an MQTT Connect packet over the already connected TCP socket + * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ + LogInfo( ( "Establishing MQTT connection to the broker %s.\r\n", MQTT_BROKER_ENDPOINT ) ); + status = createMQTTConnectionWithBroker( tcpSocket, &fixedBuffer ); + assert( status == EXIT_SUCCESS ); + + /**************************** Subscribe. ******************************/ + + /* The client is now connected to the broker. Subscribe to the topic + * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a + * subscribe packet then waiting for a subscribe acknowledgment (SUBACK). + * This client will then publish to the same topic it subscribed to, so it + * will expect all the messages it sends to the broker to be sent back to it + * from the broker. This demo uses QOS0 in subscribe, therefore, the Publish + * messages received from the broker will have QOS0. */ + /* Subscribe and SUBACK */ + LogInfo( ( "Attempt to subscribe to the MQTT topic %s\r\n", MQTT_EXAMPLE_TOPIC ) ); + mqttSubscribeToTopic( tcpSocket, &fixedBuffer ); + + /* Since subscribe is a control packet, record the last control packet sent + * timestamp. This timestamp will be used to determine if it is necessary to + * send a PINGREQ packet. */ + status = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); + assert( status == 0 ); + lastControlPacketSentTimeStamp = currentTimeStamp.tv_sec; + + /* Process incoming packet from the broker. After sending the subscribe, the + * client may receive a publish before it receives a subscribe ack. Therefore, + * call generic incoming packet processing function. Since this demo is + * subscribing to the topic to which no one is publishing, probability of + * receiving Publish message before subscribe ack is zero; but application + * must be ready to receive any packet. This demo uses the generic packet + * processing function everywhere to highlight this fact. */ + mqttProcessIncomingPacket( tcpSocket, &fixedBuffer ); + + /********************* Publish and Keep Alive Loop. ********************/ + /* Publish messages with QOS0, send and process Keep alive messages. */ + for( loopCount = 0; loopCount < maxLoopCount; loopCount++ ) + { + /* Get the current time stamp */ + status = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); + + /* Publish to the topic every other time to trigger sending of PINGREQ */ + if( publishPacketSent == false ) + { + LogInfo( ( "Publish to the MQTT topic %s\r\n", MQTT_EXAMPLE_TOPIC ) ); + mqttPublishToTopic( tcpSocket, &fixedBuffer ); + + /* Set control packet sent flag to true so that the lastControlPacketSent + * timestamp will be updated. */ + controlPacketSent = true; + publishPacketSent = true; + } + else + { + /* Check if the keep-alive period has elapsed, since the last control packet was sent. + * If the period has elapsed, send out MQTT PINGREQ to the broker. */ + timeDiff = calculateElapsedTime( currentTimeStamp.tv_sec, lastControlPacketSentTimeStamp ); + LogInfo( ( "Time Since last control packet %u \r\n", timeDiff ) ); + + if( timeDiff >= MQTT_KEEP_ALIVE_PERIOD_SECONDS ) + { + /* Send PINGREQ to the broker */ + LogInfo( ( "Sending PINGREQ to the broker\n " ) ); + mqttKeepAlive( tcpSocket, &fixedBuffer ); + controlPacketSent = true; + } + + /* Since PUBLISH packet is not sent for this iteration, set publishPacketSent to false + * so the next iteration will send PUBLISH .*/ + publishPacketSent = false; + } + + if( controlPacketSent == true ) + { + /* Reset the last control packet sent timestamp */ + status = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); + assert( status == 0 ); + lastControlPacketSentTimeStamp = currentTimeStamp.tv_sec; + controlPacketSent = false; + + /* Since the application is subscribed publishing messages to the same topic, + * the broker will send the same message back to the application. + * Process incoming PUBLISH echo or PINGRESP. */ + mqttProcessIncomingPacket( tcpSocket, &fixedBuffer ); + } + + /* Sleep until keep alive time period, so that for the next iteration this + * loop will send out a PINGREQ if PUBLISH was not sent for this iteration. + * The broker will wait till 1.5 times keep-alive period before it disconnects + * the client. */ + ( void ) sleep( MQTT_KEEP_ALIVE_PERIOD_SECONDS ); + } + + /* Unsubscribe from the previously subscribed topic */ + LogInfo( ( "Unsubscribe from the MQTT topic %s.\r\n", MQTT_EXAMPLE_TOPIC ) ); + mqttUnsubscribeFromTopic( tcpSocket, &fixedBuffer ); + /* Process Incoming unsubscribe ack from the broker. */ + mqttProcessIncomingPacket( tcpSocket, &fixedBuffer ); + + /* Send an MQTT Disconnect packet over the already connected TCP socket. + * There is no corresponding response for the disconnect packet. After sending + * disconnect, client must close the network connection. */ + LogInfo( ( "Disconnecting the MQTT connection with %s.\r\n", MQTT_EXAMPLE_TOPIC ) ); + mqttDisconnect( tcpSocket, &fixedBuffer ); + + /* Close the TCP connection. */ + ( void ) shutdown( tcpSocket, SHUT_RDWR ); + ( void ) close( tcpSocket ); + } + + if( demoIterations < ( maxDemoIterations - 1U ) ) + { + /* Wait for some time between two iterations to ensure that we do not + * bombard the public test mosquitto broker. */ + LogInfo( ( "Short delay before starting the next iteration.... \r\n\r\n" ) ); + ( void ) sleep( MQTT_DEMO_ITERATION_DELAY_SECONDS ); + } + } + + LogInfo( ( "Demo completed successfully.\r\n" ) ); + return status; +} + +/*-----------------------------------------------------------*/ From 8a2c4690388b1171f37ecb663d98755de608d966 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Wed, 27 May 2020 10:56:08 -0700 Subject: [PATCH 523/844] Ci/coverity integration (#967) * Add misra config Add the misra configration for coverity. This is taken from the V4_Beta branch and will act as a living reference to what is checked. * Explicitly link DL and PThreads libraries The build node in CI is unable to find PTHREADS and DL libraries. * Move thread related options to proper file Move the thread related options from the root CMakeLists.txt file to the most specific one (the TLS demo) * Add explanatory comment to tls demo. Add an explanatory comment to the TLS demo. Ordinarily the demo does not use threading or dynamic loading, but OpenSSL requires them in CI and the CI platform requires that they are explicitly linked. --- demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 7 +++ tools/coverity/misra.config | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 tools/coverity/misra.config diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt index d8babee36d..c3f4988904 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -5,6 +5,9 @@ add_executable(${DEMO_NAME}) set(OPENSSL_USE_STATIC_LIBS TRUE) find_package(OpenSSL REQUIRED) +set( CMAKE_THREAD_PREFER_PTHREAD ON ) +find_package(Threads REQUIRED) + target_sources( ${DEMO_NAME} PRIVATE @@ -17,6 +20,10 @@ target_link_libraries( mqtt OpenSSL::Crypto OpenSSL::SSL + # SSL uses Threads and on some platforms require explicit linking. + Threads::Threads + # SSL uses Dynamic Loading and on some platforms requires explicit linking. + ${CMAKE_DL_LIBS} ) target_include_directories( diff --git a/tools/coverity/misra.config b/tools/coverity/misra.config new file mode 100644 index 0000000000..2ef1337996 --- /dev/null +++ b/tools/coverity/misra.config @@ -0,0 +1,46 @@ +// MISRA C-2012 Rules + +{ + version : "2.0", + standard : "c2012", + title: "Coverity MISRA Configuration", + deviations : [ + // Disable the following rules. + { + deviation: "Directive 4.5", + reason: "Allow names that MISRA considers ambiguous (such as enum IOT_MQTT_CONNECT and function IotMqtt_Connect)." + }, + { + deviation: "Directive 4.8", + reason: "Allow inclusion of unused types. Header files for a specific port, which are needed by all files, may define types that are not used by a specific file." + }, + { + deviation: "Directive 4.9", + reason: "Allow inclusion of function like macros. Logging is done using function like macros." + }, + { + deviation: "Rule 2.4", + reason: "Allow unused tags. Some compilers warn if types are not tagged." + }, + { + deviation: "Rule 2.5", + reason: "Allow unused macros. Library headers may define macros intended for the application's use, but not used by a specific file." + }, + { + deviation: "Rule 3.1", + reason: "Allow nested comments. Documentation blocks contain comments for example code." + }, + { + deviation: "Rule 11.5", + reason: "Allow casts from void *. Contexts are passed as void * and must be cast to the correct data type before use." + }, + { + deviation: "Rule 21.1", + reason: "Allow use of all names." + }, + { + deviation: "Rule 21.2", + reason: "Allow use of all names." + } + ] +} From f1e8ff2abd1e7f20b782ce8f7d1fdb877dbb979e Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 28 May 2020 11:08:27 -0700 Subject: [PATCH 524/844] Import lightweight unit tests (#957) * Import existing lightweight unit tests * Add overflow checking for serialized buffers --- libraries/CMakeLists.txt | 2 +- .../standard/mqtt/src/mqtt_lightweight.c | 21 +- libraries/standard/mqtt/utest/mqtt_config.h | 6 +- .../mqtt/utest/mqtt_lightweight_utest.c | 1306 ++++++++++++++++- 4 files changed, 1326 insertions(+), 9 deletions(-) diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 001c88e823..a68967daa3 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -35,7 +35,7 @@ if( ${BUILD_TESTS} ) # Add a target for running coverage on tests. add_custom_target(coverage COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake - DEPENDS cmock unity http_utest mqtt_utest mqtt_state_utest mqtt_lightweight_utest + DEPENDS cmock unity http_utest mqtt_utest mqtt_lightweight_utest mqtt_state_utest WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endif() diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 4d38ed58b8..007fbabe06 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -337,7 +337,10 @@ static uint8_t * encodeString( uint8_t * pDestination, pBuffer++; /* Copy the string into pBuffer. */ - ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); + if( pSourceBuffer != NULL ) + { + ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength ); + } /* Return the pointer to the end of the encoded string. */ pBuffer += sourceLength; @@ -847,7 +850,7 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * packetSize += sizeof( uint16_t ); /* Sum the lengths of all subscription topic filters; add 1 byte for each - * subscription's QoS if type is IOT_MQTT_SUBSCRIBE. */ + * subscription's QoS if type is MQTT_SUBSCRIBE. */ for( i = 0; i < subscriptionCount; i++ ) { /* Add the length of the topic filter. MQTT strings are prepended @@ -1267,6 +1270,7 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo pIndex = encodeString( pIndex, pWillInfo->pTopicName, pWillInfo->topicNameLength ); + pIndex = encodeString( pIndex, pWillInfo->pPayload, ( uint16_t ) pWillInfo->payloadLength ); @@ -1316,8 +1320,12 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect pPacketSize ) ); status = MQTTBadParameter; } - - if( status == MQTTSuccess ) + else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + { + LogError( ( "Mqtt_GetConnectPacketSize() client identifier must be set." ) ); + status = MQTTBadParameter; + } + else { /* Add the length of the client identifier. */ connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); @@ -1397,6 +1405,11 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo connectPacketSize ) ); status = MQTTNoMemory; } + else if ( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) ) + { + LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) ); + status = MQTTBadParameter; + } else { serializeConnectPacket( pConnectInfo, diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index dfc9facbe0..eca3ace873 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -16,14 +16,14 @@ /* Configure name and log level for the MQTT library. */ #define LIBRARY_LOG_NAME "MQTT" -#define LIBRARY_LOG_LEVEL LOG_INFO +#define LIBRARY_LOG_LEVEL LOG_NONE #include "logging_stack.h" /************ End of logging configuration ****************/ -/* Set network context to socket (int). */ -typedef int MQTTNetworkContext_t; +/* Set network context to double pointer to buffer (uint8_t**). */ +typedef uint8_t ** MQTTNetworkContext_t; /** * @brief The maximum number of MQTT PUBLISH messages that may be pending diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 8c9514134d..72b2755a30 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -5,6 +5,25 @@ /* Include paths for public enums, structures, and macros. */ #include "mqtt_lightweight.h" +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ +#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( 2U ) +#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( 2U ) +/* + * Client identifier and length to use for the MQTT API tests. + */ +#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ +/* + * Will topic name and length to use for the MQTT API tests. + */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ + + /** * @brief MQTT protocol version 3.1.1. */ @@ -82,6 +101,16 @@ */ #define MQTT_REMAINING_BUFFER_MAX_LENGTH ( 4 ) +/** + * @brief Length of buffer padding to use in under/overflow checks. + */ +#define BUFFER_PADDING_LENGTH ( 4 ) + +/** + * @brief Byte to use for buffer padding in under/overflow checks. + */ +#define BUFFER_PADDING_BYTE ( 0xA5 ) + /** * @brief Length of the MQTT network buffer. */ @@ -116,7 +145,76 @@ int suiteTearDown( int numFailures ) return numFailures; } -/* ===================== Testing MQTT_SerializeConnect ===================== */ +/* ========================================================================== */ + +/** + * @brief Mock successful transport receive by reading data from a buffer. + */ +static int32_t mockReceive( MQTTNetworkContext_t context, + void * pBuffer, + size_t bytesToRecv ) +{ + uint8_t * returnBuffer = ( uint8_t * ) pBuffer; + uint8_t * mockNetwork; + size_t bytesRead = 0; + + /* Treat network context as pointer to buffer for mocking */ + mockNetwork = ( *( uint8_t ** ) context ); + + while( bytesRead++ < bytesToRecv ) + { + /* Read single byte and advance buffer. */ + *returnBuffer++ = *mockNetwork++; + } + /* Move stream by bytes read. */ + ( *( uint8_t ** ) context ) = mockNetwork; + + return bytesToRecv; +} + +/** + * @brief Mock transport receive with no data available. + */ +static int32_t mockReceiveNoData( MQTTNetworkContext_t context, + void * pBuffer, + size_t bytesToRecv ) +{ + return 0; +} + +/** + * @brief Mock transport receive failure. + */ +static int32_t mockReceiveFailure( MQTTNetworkContext_t context, + void * pBuffer, + size_t bytesToRecv ) +{ + return -1; +} + +/** + * @brief Mock transport receive that succeeds once, then fails. + */ +static int32_t mockReceiveSucceedThenFail( MQTTNetworkContext_t context, + void *pBuffer, + size_t bytesToRecv ) +{ + int32_t retVal = 0; + static int counter = 0; + + if( counter++ ) + { + retVal = mockReceiveFailure( context, pBuffer, bytesToRecv ); + counter = 0; + } + else + { + retVal = mockReceive( context, pBuffer, bytesToRecv ); + } + return retVal; +} + +/* ========================================================================== */ /** * @brief Initialize pNetworkBuffer using static buffer. @@ -240,6 +338,1212 @@ static size_t encodeString( uint8_t * pDestination, return ( size_t ) ( pBuffer - pDestination ); } +/** + * @brief Pad beginning and end of buffer with non-zero bytes to be used in + * checking for under/overflow after serialization. + * + * @param[in] pBuffer Buffer to pad. + * @param[in] bufferLength Total length of buffer. + */ +static void padAndResetBuffer( uint8_t * pBuffer, size_t bufferLength ) +{ + int i = 0; + + for( i = 0; i < BUFFER_PADDING_LENGTH; i++ ) + { + pBuffer[ i ] = BUFFER_PADDING_BYTE; + pBuffer[ bufferLength - 1 - i ] = BUFFER_PADDING_BYTE; + } + + /* Zero out rest of buffer. */ + memset( ( void * ) &pBuffer[ BUFFER_PADDING_LENGTH ], 0x0, bufferLength - 2 * BUFFER_PADDING_LENGTH ); +} + +/** + * @brief Test buffer for under/overflow. + * + * @param[in] pBuffer Buffer to check. + * @param[in] bufferLength Total length of buffer. + */ +static void checkBufferOverflow( uint8_t * pBuffer, size_t bufferLength ) +{ + /* Check beginning of buffer. */ + TEST_ASSERT_EACH_EQUAL_UINT8( BUFFER_PADDING_BYTE, + pBuffer, + BUFFER_PADDING_LENGTH ); + /* Check end. */ + TEST_ASSERT_EACH_EQUAL_UINT8( BUFFER_PADDING_BYTE, + pBuffer + bufferLength - BUFFER_PADDING_LENGTH, + BUFFER_PADDING_LENGTH ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_GetConnectPacketSize works as intended. + */ +void test_MQTT_GetConnectPacketSize( void ) +{ + MQTTConnectInfo_t connectInfo; + size_t remainingLength = 0; + size_t packetSize = 0; + MQTTStatus_t status = MQTTSuccess; + MQTTPublishInfo_t willInfo = { 0 }; + + /* Call MQTT_GetConnectPacketSize() with various combinations of + * incorrect paramters */ + + status = MQTT_GetConnectPacketSize( NULL, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, NULL, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify empty connect info fails. */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify empty client identifier fails. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = 0; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + connectInfo.pClientIdentifier = NULL; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Connect packet too large. */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = UINT16_MAX; + connectInfo.pPassword = ""; + connectInfo.passwordLength = UINT16_MAX; + connectInfo.pUserName = ""; + connectInfo.userNameLength = UINT16_MAX; + willInfo.pTopicName = TEST_TOPIC_NAME; + willInfo.topicNameLength = UINT16_MAX; + willInfo.payloadLength = UINT16_MAX + 2; + status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Verify good case */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = "TEST"; + connectInfo.clientIdentifierLength = 4; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure remaining size returned is 16. */ + TEST_ASSERT_EQUAL_INT( 16, remainingLength ); + /* Make sure packet size is 18. */ + TEST_ASSERT_EQUAL_INT( 18, packetSize ); + + /* With will. These parameters will cause the packet to be + * 4 + 2 + 8 + 2 = 16 bytes larger. */ + memset( ( void * ) &willInfo, 0x0, sizeof( willInfo ) ); + willInfo.pTopicName = "test"; + willInfo.topicNameLength = 4; + willInfo.pPayload = "testload"; + willInfo.payloadLength = 8; + status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure remaining size returned is 32 = 16 + 16. */ + TEST_ASSERT_EQUAL_INT( 32, remainingLength ); + /* Make sure packet size is 34 = 18 + 16. */ + TEST_ASSERT_EQUAL_INT( 34, packetSize ); + + /* With username and password. This will add 4 + 2 + 4 + 2 = 12 bytes. */ + connectInfo.cleanSession = true; + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 4; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* Make sure remaining size returned is 28 = 16 + 12. */ + TEST_ASSERT_EQUAL_INT( 28, remainingLength ); + /* Make sure packet size is 30 = 18 + 12. */ + TEST_ASSERT_EQUAL_INT( 30, packetSize ); +} + +/** + * @brief Tests that MQTT_SerializeConnect works as intended. + */ +void test_MQTT_SerializeConnect( void ) +{ + MQTTConnectInfo_t connectInfo; + MQTTPublishInfo_t willInfo; + size_t remainingLength = 0; + uint8_t buffer[ 70 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + + /* Verify bad parameter errors. */ + status = MQTT_SerializeConnect( NULL, &willInfo, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + status = MQTT_SerializeConnect( &connectInfo, NULL, 120, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /* Good case succeeds */ + /* Calculate packet size. */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.pClientIdentifier = "TEST"; + connectInfo.clientIdentifierLength = 4; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Make sure test succeeds. */ + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeConnect( &connectInfo, NULL, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Encode user name. Also try clean session. */ + connectInfo.cleanSession = true; + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 4; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeConnect( &connectInfo, NULL, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Serialize connect with LWT. */ + /* Test for NULL topic name. */ + ( void ) memset( &willInfo, 0x00, sizeof( MQTTPublishInfo_t ) ); + willInfo.retain = true; + willInfo.qos = MQTTQoS1; + willInfo.pPayload = "test"; + willInfo.payloadLength = ( uint16_t ) strlen( willInfo.pPayload ); + status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Success. */ + ( void ) memset( &willInfo, 0x00, sizeof( MQTTPublishInfo_t ) ); + willInfo.retain = true; + willInfo.qos = MQTTQoS1; + willInfo.pTopicName = "test"; + willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); + willInfo.pPayload = "test"; + willInfo.payloadLength = ( uint16_t ) strlen( willInfo.pPayload ); + status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Again with QoS 2 and 0. */ + + willInfo.qos = MQTTQoS2; + status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + willInfo.qos = MQTTQoS0; + willInfo.retain = false; + /* NULL payload is acceptable. */ + willInfo.pPayload = NULL; + willInfo.payloadLength = 0; + status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_GetSubscribePacketSize works as intended. + */ +void test_MQTT_GetSubscribePacketSize( void ) +{ + MQTTSubscribeInfo_t subscriptionList; + size_t subscriptionCount = 0; + size_t remainingLength = 0; + size_t packetSize = 0; + MQTTStatus_t status = MQTTSuccess; + MQTTSubscribeInfo_t fourThousandSubscriptions[ 4096 ] = { 0 }; + int i; + + /* Verify parameters. */ + + status = MQTT_GetSubscribePacketSize( NULL, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_GetSubscribePacketSize( &subscriptionList, + subscriptionCount, + NULL, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_GetSubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + + /* Verify empty subscription list fails. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionCount = 0; + status = MQTT_GetSubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify packet size cannot exceed limit. Note the max remaining length of + * an MQTT packet is 2^28-1 = 268435455, or 256MiB. Since the only way to increase + * the subscribe packet size is with the topic filters of the subscriptions + * (the lengths of which are only 2 bytes), we need at least + * 2^28 / 2^16 = 2^12 = 4096 of them. */ + for( i = 0; i < 4096; i++ ) + { + fourThousandSubscriptions[ i ].topicFilterLength = UINT16_MAX; + } + subscriptionCount = sizeof( fourThousandSubscriptions ) / sizeof ( fourThousandSubscriptions[ 0 ] ); + status = MQTT_GetSubscribePacketSize( fourThousandSubscriptions, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify good case. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = MQTTQoS0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( MQTTSubscribeInfo_t ); + status = MQTT_GetSubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_GREATER_THAN( remainingLength, packetSize ); +} + +/** + * @brief Tests that MQTT_GetUnsubscribePacketSize works as intended. + */ +void test_MQTT_GetUnsubscribePacketSize( void ) +{ + MQTTSubscribeInfo_t subscriptionList; + size_t subscriptionCount = 0; + size_t remainingLength = 0; + size_t packetSize = 0; + MQTTStatus_t status = MQTTSuccess; + + /* Verify parameters. */ + + status = MQTT_GetUnsubscribePacketSize( NULL, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_GetUnsubscribePacketSize( &subscriptionList, + subscriptionCount, + NULL, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_GetUnsubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + + /* Verify empty subscription list fails. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionCount = 0; + status = MQTT_GetUnsubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Verify good case. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = MQTTQoS0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( MQTTSubscribeInfo_t ); + status = MQTT_GetUnsubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_GREATER_THAN( remainingLength, packetSize ); +} + +/** + * @brief Tests that MQTT_SerializeSubscribe works as intended. + */ +void test_MQTT_SerializeSubscribe( void ) +{ + MQTTSubscribeInfo_t subscriptionList; + size_t subscriptionCount = 1; + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t buffer[ 25 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + + const uint16_t PACKET_ID = 1; + + /* Verify bad parameters fail. */ + status = MQTT_SerializeSubscribe( NULL, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializeSubscribe( &subscriptionList, + subscriptionCount, + 0, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializeSubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Get correct values of packet size and remaining length. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = MQTTQoS0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( MQTTSubscribeInfo_t ); + status = MQTT_GetSubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + /* Make sure subscription count of zero fails. */ + status = MQTT_SerializeSubscribe( &subscriptionList, + 0, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Test if buffer is too small. */ + fixedBuffer.size = 1; + status = MQTT_SerializeSubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + fixedBuffer.size = bufferSize; + + /* Make sure success is returned for good case. */ + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeSubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/** + * @brief Tests that MQTT_SerializeUnsubscribe works as intended. + */ +void test_MQTT_SerializeUnsubscribe( void ) +{ + MQTTSubscribeInfo_t subscriptionList; + size_t subscriptionCount = 1; + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t buffer[ 25 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + + const uint16_t PACKET_ID = 1; + + status = MQTT_SerializeUnsubscribe( NULL, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + 0, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Get correct values of packetsize and remaining length. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = MQTTQoS0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( MQTTSubscribeInfo_t ); + status = MQTT_GetUnsubscribePacketSize( &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + /* Make sure subscription count of zero fails. */ + status = MQTT_SerializeUnsubscribe( &subscriptionList, + 0, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Test if buffer is too small. */ + fixedBuffer.size = 1; + status = MQTT_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + fixedBuffer.size = bufferSize; + + /* Make sure success it returned for good case. */ + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_GetPublishPacketSize works as intended. + */ +void test_MQTT_GetPublishPacketSize( void ) +{ + MQTTPublishInfo_t publishInfo; + size_t remainingLength = 0; + size_t packetSize; + MQTTStatus_t status = MQTTSuccess; + + /* Verify bad paramameters fail. */ + status = MQTT_GetPublishPacketSize( NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTT_GetPublishPacketSize( &publishInfo, NULL, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Empty topic must fail. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = NULL; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = 0; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Packet too large. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + publishInfo.payloadLength = MQTT_MAX_REMAINING_LENGTH - publishInfo.topicNameLength - sizeof( uint16_t ) - 1; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Good case succeeds. */ + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + + /* Again with QoS 2. */ + publishInfo.qos = MQTTQoS2; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); +} + +/** + * @brief Tests that MQTT_SerializePublish works as intended. + */ +void test_MQTT_SerializePublish( void ) +{ + MQTTPublishInfo_t publishInfo; + size_t remainingLength = 98; + uint16_t packetIdentifier; + uint8_t * pPacketIdentifierHigh; + uint8_t buffer[ 200 + 2 * BUFFER_PADDING_LENGTH ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + + const uint16_t PACKET_ID = 1; + const char * longTopic = "/test/topic/name/longer/than/one/hundred/twenty/eight/characters" \ + "/test/topic/name/longer/than/one/hundred/twenty/eight/characters"; + + /* Verify bad parameters fail. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + + status = MQTT_SerializePublish( NULL, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* 0 packet ID for QoS > 0. */ + publishInfo.qos = MQTTQoS1; + status = MQTT_SerializePublish( &publishInfo, + 0, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Empty topic fails. */ + publishInfo.pTopicName = NULL; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = 0; + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Remaining length larger than buffer size. */ + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + fixedBuffer.size = 5; + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + 10, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /* Good case succeeds */ + publishInfo.qos = MQTTQoS0; + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + fixedBuffer.size = bufferSize; + /* Calculate exact packet size and remaining length. */ + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Again with QoS2, dup, and retain. Also encode remaining length > 2 bytes. */ + publishInfo.qos = MQTTQoS2; + publishInfo.retain = true; + publishInfo.dup = true; + publishInfo.pTopicName = longTopic; + publishInfo.topicNameLength = strlen( longTopic ); + /* Calculate exact packet size and remaining length. */ + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Make sure buffer has enough space */ + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_SerializeDisconnect works as intended. + */ +void test_MQTT_SerializeDisconnect( void ) +{ + uint8_t buffer[ 10 + 2 * BUFFER_PADDING_LENGTH ]; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ] }; + MQTTStatus_t status = MQTTSuccess; + + /* Buffer size less than disconnect request fails. */ + fixedBuffer.size = 1; + status = MQTT_SerializeDisconnect( &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /* NULL buffer fails. */ + status = MQTT_SerializeDisconnect( NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Good case succeeds. */ + fixedBuffer.size = 2; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeDisconnect( &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/** + * @brief Tests that MQTT_SerializePingreq works as intended. + */ +void test_MQTT_SerializePingreq( void ) +{ + uint8_t buffer[ 10 + 2 * BUFFER_PADDING_LENGTH ]; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ] }; + MQTTStatus_t status = MQTTSuccess; + + /* Buffer size less than ping request fails. */ + fixedBuffer.size = 1; + status = MQTT_SerializePingreq( &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /* NULL buffer fails. */ + status = MQTT_SerializePingreq( NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Good case succeeds. */ + fixedBuffer.size = 2; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePingreq( &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_DeserializeAck works as intended with a CONNACK. + */ +void test_MQTT_DeserializeAck_connack( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + uint16_t packetIdentifier; + bool sessionPresent; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 10 ]; + + /* Verify parameters */ + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK; + status = MQTT_DeserializeAck( NULL, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Packet ID can be NULL for CONNACK, don't need to check that. */ + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + + /* Bad packet type. */ + mqttPacketInfo.type = 0x01; + mqttPacketInfo.pRemainingData = buffer; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK; + mqttPacketInfo.remainingLength = MQTT_PACKET_CONNACK_REMAINING_LENGTH - 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Incorrect reserved bits. */ + mqttPacketInfo.remainingLength = MQTT_PACKET_CONNACK_REMAINING_LENGTH; + buffer[ 0 ] = 0xf; + buffer[ 1 ] = 0; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Session present but nonzero return code. */ + buffer[ 0 ] = MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK; + buffer[ 1 ] = 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Invalid response code. */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 6; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Valid packet with rejected code. */ + buffer[ 1 ] = 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, NULL, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + /* Valid packet with success code. */ + buffer[ 0 ] = 1; + buffer[ 1 ] = 0; + status = MQTT_DeserializeAck( &mqttPacketInfo, NULL, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +/** + * @brief Tests that MQTT_DeserializeAck works as intended with a SUBACK. + */ +void test_MQTT_DeserializeAck_suback( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + uint16_t packetIdentifier; + bool sessionPresent; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = 2; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Set packet identifier. */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 1; + + /* Bad response code. */ + mqttPacketInfo.remainingLength = 3; + buffer[ 2 ] = 5; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Process a valid SUBACK with server refused response code. */ + mqttPacketInfo.remainingLength = 3; + buffer[ 2 ] = 0x80; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTServerRefused, status ); + + /* Process a valid SUBACK with various server acceptance codes. */ + mqttPacketInfo.remainingLength = 5; + buffer[ 2 ] = 0x00; + buffer[ 3 ] = 0x01; + buffer[ 4 ] = 0x02; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +/** + * @brief Tests that MQTT_DeserializeAck works as intended with an UNSUBACK. + */ +void test_MQTT_DeserializeAck_unsuback( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + uint16_t packetIdentifier; + bool sessionPresent; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_UNSUBACK; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = MQTT_PACKET_UNSUBACK_REMAINING_LENGTH - 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Packet identifier 0 is not valid (per spec). */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 0; + mqttPacketInfo.remainingLength = MQTT_PACKET_UNSUBACK_REMAINING_LENGTH; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Process a valid UNSUBACK. */ + buffer[ 1 ] = 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +/** + * @brief Tests that MQTT_DeserializeAck works as intended with a PINGRESP. + */ +void test_MQTT_DeserializeAck_pingresp( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + uint16_t packetIdentifier; + bool sessionPresent; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 10 ] = { 0 }; + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PINGRESP; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH + 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Process a valid PINGRESP. */ + mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH; + status = MQTT_DeserializeAck( &mqttPacketInfo, NULL, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +/** + * @brief Tests that MQTT_DeserializeAck works as intended with a PUBACK, + * PUBREC, PUBREL, and PUBCOMP. + */ +void test_MQTT_DeserializeAck_puback( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + uint16_t packetIdentifier; + bool sessionPresent; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 10 ] = { 0 }; + + /* Verify parameters */ + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBACK; + status = MQTT_DeserializeAck( NULL, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_DeserializeAck( &mqttPacketInfo, NULL, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* mqttPacketInfo.pRemainingData not set. */ + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBACK; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH - 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Packet identifier 0 is not valid (per spec). */ + buffer[ 0 ] = 0; + buffer[ 1 ] = 0; + mqttPacketInfo.remainingLength = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Process a valid PUBACK. */ + buffer[ 1 ] = 1; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 1, packetIdentifier ); + + /* PUBREC. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBREC; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 1, packetIdentifier ); + + /* PUBREL. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBREL; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 1, packetIdentifier ); + + /* PUBCOMP. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBCOMP; + status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 1, packetIdentifier ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_DeserializePublish works as intended. + */ +void test_MQTT_DeserializePublish( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + MQTTPublishInfo_t publishInfo; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 100 ]; + size_t bufferSize = sizeof( buffer ); + MQTTFixedBuffer_t fixedBuffer = { 0 }; + size_t packetSize = bufferSize; + + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t * pPacketIdentifierHigh; + fixedBuffer.pBuffer = buffer; + fixedBuffer.size = bufferSize; + + const uint16_t PACKET_ID = 1; + + /* Verify parameters. */ + status = MQTT_DeserializePublish( NULL, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_DeserializePublish( &mqttPacketInfo, NULL, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + + /* Bad Packet Type. */ + mqttPacketInfo.type = 0x01; + mqttPacketInfo.pRemainingData = buffer; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Incorrect flags. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH | 0xf; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* QoS 0 bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH; + mqttPacketInfo.remainingLength = 0; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* QoS 1 bad remaining length. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH | 0x2; + mqttPacketInfo.remainingLength = 0; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* QoS 1 invalid packet identifier. */ + mqttPacketInfo.remainingLength = 5; + buffer[ 0 ] = 0; + buffer[ 1 ] = 1; + buffer[ 2 ] = ( uint8_t )'a'; + buffer[ 3 ] = 0; + buffer[ 4 ] = 0; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Create a PUBLISH packet to test. */ + memset( &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); + publishInfo.pPayload = "Hello World"; + publishInfo.payloadLength = ( uint16_t ) strlen( publishInfo.pPayload ); + + /* Test serialization and deserialization of a QoS 0 PUBLISH. */ + publishInfo.qos = MQTTQoS0; + + /* Generate QoS 0 packet. */ + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + status = MQTT_SerializePublish( &publishInfo, + 0, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /* Deserialize QoS 0 packet. */ + mqttPacketInfo.type = buffer[ 0 ]; + /* We don't need to go through the trouble of calling MQTT_GetIncomingPacketTypeAndLength. + * We know the remaining length is < 128. */ + mqttPacketInfo.remainingLength = ( size_t ) buffer[ 1 ]; + mqttPacketInfo.pRemainingData = &buffer[ 2 ]; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + + /* Test serialization and deserialization of a QoS 1 PUBLISH. */ + publishInfo.qos = MQTTQoS1; + + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + mqttPacketInfo.type = buffer[ 0 ]; + mqttPacketInfo.remainingLength = ( size_t ) buffer[ 1 ]; + mqttPacketInfo.pRemainingData = &buffer[ 2 ]; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /* QoS 2 PUBLISH. */ + publishInfo.qos = MQTTQoS2; + /* Remaining length and packet size should be same as before. */ + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + mqttPacketInfo.type = buffer[ 0 ]; + mqttPacketInfo.remainingLength = ( size_t ) buffer[ 1 ]; + mqttPacketInfo.pRemainingData = &buffer[ 2 ]; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_GetIncomingPacketTypeAndLength works as intended. + */ +void test_MQTT_GetIncomingPacketTypeAndLength( void ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTPacketInfo_t mqttPacket; + uint8_t buffer[ 10 ]; + uint8_t * bufPtr = buffer; + + /* Dummy network context - pointer to pointer to a buffer. */ + MQTTNetworkContext_t networkContext = ( MQTTNetworkContext_t ) &bufPtr; + + buffer[ 0 ] = 0x20; /* CONN ACK */ + buffer[ 1 ] = 0x02; /* Remaining length. */ + + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 0x20, mqttPacket.type ); + TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); + + /* Remaining length of 128 needs 2 bytes. */ + bufPtr = buffer; + buffer[ 0 ] = MQTT_PACKET_TYPE_PUBLISH; + buffer[ 1 ] = 0x80; + buffer[ 2 ] = 0x01; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTT_PACKET_TYPE_PUBLISH, mqttPacket.type ); + TEST_ASSERT_EQUAL_INT( 128, mqttPacket.remainingLength ); + + /* Test with incorrect packet type. */ + bufPtr = buffer; + buffer[ 0 ] = 0x10; /* INVALID */ + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + + /* Test with invalid remaining length. */ + bufPtr = buffer; + buffer[ 0 ] = 0x20; /* CONN ACK */ + + /* To generate invalid remaining length response, + * four bytes need to have MSB (or continuation bit, 0x80) set */ + buffer[ 1 ] = 0xFF; + buffer[ 2 ] = 0xFF; + buffer[ 3 ] = 0xFF; + buffer[ 4 ] = 0xFF; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + + /* Check with an encoding that does not conform to the MQTT spec. */ + bufPtr = buffer; + buffer[ 1 ] = 0x80; + buffer[ 2 ] = 0x80; + buffer[ 3 ] = 0x80; + buffer[ 4 ] = 0x00; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + + /* Check when network receive fails. */ + memset( buffer, 0x00, 10 ); + bufPtr = buffer; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveFailure, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTRecvFailed, status ); + + /* Test if no data is available. */ + bufPtr = buffer; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveNoData, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTNoDataAvailable, status ); + + /* Branch coverage for PUBREL. */ + bufPtr = buffer; + buffer[ 0 ] = MQTT_PACKET_TYPE_PUBREL & 0xF0U; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); + + /* Receive type then fail. */ + bufPtr = buffer; + buffer[ 0 ] = MQTT_PACKET_TYPE_PUBREL; + status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveSucceedThenFail, networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); +} + +/* ===================== Testing MQTT_SerializeConnect ===================== */ + /** * @brief Check the serialization of an MQTT CONNECT packet in the given buffer, * following the same order in serializeConnectPacket. From 82b1d2249f1ac2c2c1870f2b46483b546f0fa71e Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 28 May 2020 12:16:32 -0700 Subject: [PATCH 525/844] Add MQTT connect and publish unit tests (#964) * Add unit tests for MQTT_Connect, MQTT_Publish, MQTT_Disconnect, MQTT_GetPacketId * Update MQTT_GetPacketId for NULL checks * Update MQTT_Connect for parameter checks --- libraries/standard/mqtt/src/mqtt.c | 20 +- libraries/standard/mqtt/utest/CMakeLists.txt | 1 + libraries/standard/mqtt/utest/mqtt_utest.c | 583 +++++++++++++++++++ 3 files changed, 596 insertions(+), 8 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 6ac7da7295..c07f5c4c79 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -940,12 +940,13 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, MQTTStatus_t status = MQTTSuccess; MQTTPacketInfo_t incomingPacket = { .type = ( ( uint8_t ) 0 ) }; - if( ( pContext == NULL ) || ( pConnectInfo == NULL ) ) + if( ( pContext == NULL ) || ( pConnectInfo == NULL ) || ( pSessionPresent == NULL ) ) { LogError( ( "Argument cannot be NULL: pContext=%p, " - "pConnectInfo=%p.", + "pConnectInfo=%p, pSessionPresent=%p.", pContext, - pConnectInfo ) ); + pConnectInfo, + pSessionPresent ) ); status = MQTTBadParameter; } @@ -1458,13 +1459,16 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) { - uint16_t packetId = pContext->nextPacketId; + uint16_t packetId = 0U; - pContext->nextPacketId++; - - if( pContext->nextPacketId == 0U ) + if( pContext != NULL ) { - pContext->nextPacketId = 1; + packetId = pContext->nextPacketId++; + + if( pContext->nextPacketId == 0U ) + { + pContext->nextPacketId++; + } } return packetId; diff --git a/libraries/standard/mqtt/utest/CMakeLists.txt b/libraries/standard/mqtt/utest/CMakeLists.txt index 4253f76f0e..15be6d2438 100644 --- a/libraries/standard/mqtt/utest/CMakeLists.txt +++ b/libraries/standard/mqtt/utest/CMakeLists.txt @@ -10,6 +10,7 @@ set(project_name "mqtt") # list the files to mock here list(APPEND mock_list "${MODULES_DIR}/standard/mqtt/include/mqtt_lightweight.h" + "${MODULES_DIR}/standard/mqtt/include/mqtt_state.h" ) # list the directories your mocks need list(APPEND mock_include_list diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index fd798d2fe6..db7b2e9145 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -1,18 +1,41 @@ +#include +#include + #include "unity.h" /* Include paths for public enums, structures, and macros. */ #include "mqtt.h" +#include "mock_mqtt_lightweight.h" +#include "mock_mqtt_state.h" + /** * @brief A valid starting packet ID per MQTT spec. Start from 1. */ #define MQTT_NEXT_PACKET_ID_START ( 1 ) +/** + * @brief Length of the MQTT network buffer. + */ +#define MQTT_TEST_BUFFER_LENGTH ( 1024 ) + +/** + * @brief Time at the beginning of each test. Note that this is not updated with + * a real clock. Instead, we simply increment this variable. + */ +static uint32_t globalEntryTime = 0; + +/** + * @brief A static buffer used by the MQTT library for storing packet data. + */ +static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; + /* ============================ UNITY FIXTURES ============================ */ /* Called before each test method. */ void setUp() { + memset( ( void * ) mqttBuffer, 0x0, sizeof( mqttBuffer ) ); } /* Called after each test method. */ @@ -31,6 +54,150 @@ int suiteTearDown( int numFailures ) return numFailures; } +/* ========================================================================== */ + +/** + * @brief Mock successful transport send, and write data into buffer for + * verification. + */ +static int32_t mockSend( MQTTNetworkContext_t context, + const void * pMessage, + size_t bytesToSend ) +{ + const uint8_t * buffer = ( const uint8_t * ) pMessage; + /* Treat network context as pointer to buffer for mocking. */ + uint8_t * mockNetwork = ( *( uint8_t ** ) context ); + size_t bytesSent = 0; + + while( bytesSent++ < bytesToSend ) + { + /* Write single byte and advance buffer. */ + *mockNetwork++ = *buffer++; + } + /* Move stream by bytes sent. */ + ( *( uint8_t ** ) context ) = mockNetwork; + + return bytesToSend; +} + +/** + * @brief Initialize pNetworkBuffer using static buffer. + * + * @param[in] pNetworkBuffer Network buffer provided for the context. + */ +static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer ) +{ + pNetworkBuffer->pBuffer = mqttBuffer; + pNetworkBuffer->size = MQTT_TEST_BUFFER_LENGTH; +} + +/** + * @brief Mocked MQTT event callback. + */ +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ) +{ + ( void ) pContext; + ( void ) pPacketInfo; + ( void ) packetIdentifier; + ( void ) pPublishInfo; +} + +/** + * @brief A mocked timer query function that increments on every call. This + * guarantees that only a single iteration runs in the ProcessLoop for ease + * of testing. + */ +static uint32_t getTime( void ) +{ + return globalEntryTime++; +} + +/** + * @brief Mocked successful transport send. + */ +static int32_t transportSendSuccess( MQTTNetworkContext_t pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + return bytesToWrite; +} + +/** + * @brief Mocked failed transport send. + */ +static int32_t transportSendFailure( MQTTNetworkContext_t pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + ( void ) bytesToWrite; + return -1; +} + +/** + * @brief Mocked successful transport read. + */ +static int32_t transportRecvSuccess( MQTTNetworkContext_t pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + ( void ) pBuffer; + return bytesToRead; +} + +/** + * @brief Mocked failed transport read. + */ +static int32_t transportRecvFailure( MQTTNetworkContext_t pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + ( void ) pBuffer; + ( void ) bytesToRead; + return -1; +} + +/** + * @brief Mocked failed transport read. + */ +static int32_t transportRecvOneByte( MQTTNetworkContext_t pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + ( void ) pBuffer; + return 1; +} + +/** + * @brief Initialize the transport interface with the mocked functions for + * send and receive. + */ +static void setupTransportInterface( MQTTTransportInterface_t * pTransport ) +{ + pTransport->networkContext = 0; + pTransport->send = transportSendSuccess; + pTransport->recv = transportRecvSuccess; +} + +/** + * @brief Initialize our event and time callback with the mocked functions + * defined for the purposes this test. + */ +static void setupCallbacks( MQTTApplicationCallbacks_t * pCallbacks ) +{ + pCallbacks->appCallback = eventCallback; + pCallbacks->getTime = getTime; +} + /* ============================ Testing MQTT_Init ========================= */ /** @@ -78,3 +245,419 @@ void test_MQTT_Init_Invalid_Params( void ) mqttStatus = MQTT_Init( &context, &transport, &callbacks, NULL ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); } + +/* ========================================================================== */ + +/** + * @brief Test MQTT_Connect, except for receiving the CONNACK. + */ +void test_MQTT_Connect_sendConnect( void ) +{ + MQTTContext_t mqttContext; + MQTTConnectInfo_t connectInfo; + MQTTPublishInfo_t willInfo; + uint32_t timeout = 2; + bool sessionPresent; + MQTTStatus_t status; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket; + size_t remainingLength, packetSize; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + /* Check parameters */ + status = MQTT_Connect( NULL, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_Connect( &mqttContext, NULL, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Empty connect info fails. */ + MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + connectInfo.pClientIdentifier = MQTT_CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = sizeof( MQTT_CLIENT_IDENTIFIER ) - 1; + + MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializeConnect_ExpectAnyArgsAndReturn( MQTTNoMemory ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + + /* Transport send failed when sending CONNECT. */ + /* Choose 10 bytes variable header + 1 byte payload for the remaining + * length of the CONNECT. The packet size needs to be nonzero for this test + * as that is the amount of bytes used in the call to send the packet. */ + packetSize = 13; + remainingLength = 11; + mqttContext.transportInterface.send = transportSendFailure; + MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize(); + MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength(); + MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize ); + MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); + + /* Send the CONNECT successfully. This provides branch coverage for sendPacket. */ + mqttContext.transportInterface.send = transportSendSuccess; + MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize ); + MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); +} + +/** + * @brief Test CONNACK reception in MQTT_Connect. + */ +void test_MQTT_Connect_receiveConnack( void ) +{ + MQTTContext_t mqttContext; + MQTTConnectInfo_t connectInfo; + uint32_t timeout = 0; + bool sessionPresent; + MQTTStatus_t status; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + transport.recv = transportRecvFailure; + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + /* Everything before receiving the CONNACK should succeed. */ + MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + + /* Nothing received from transport interface. Set timeout to 2 for branch coverage. */ + timeout = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status ); + + /* Did not receive a CONNACK. */ + incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP; + incomingPacket.remainingLength = 0; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Transport receive failure when receiving rest of packet. */ + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + timeout = 2; + mqttContext.transportInterface.recv = transportRecvFailure; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + /* Bad response when deserializing CONNACK. */ + mqttContext.transportInterface.recv = transportRecvSuccess; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTBadResponse ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + +/** + * @brief Test error cases for MQTT_Connect when a timeout occurs or the packet + * needs to be discarded in MQTT_Connect. + */ +void test_MQTT_Connect_partial_receive() +{ + MQTTContext_t mqttContext; + MQTTConnectInfo_t connectInfo; + uint32_t timeout = 0; + bool sessionPresent; + MQTTStatus_t status; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + transport.recv = transportRecvOneByte; + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + /* Everything before receiving the CONNACK should succeed. */ + MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + + /* Not enough time to receive entire packet, for branch coverage. */ + timeout = 1; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + timeout = 10; + + /* Not enough space for packet, discard it. */ + mqttContext.networkBuffer.size = 2; + incomingPacket.remainingLength = 3; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status ); + + /* Timeout while discarding packet. */ + incomingPacket.remainingLength = 20; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + /* Receive failure while discarding packet. */ + mqttContext.transportInterface.recv = transportRecvFailure; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); +} + +/** + * @brief Test success case for MQTT_Connect(). + */ +void test_MQTT_Connect_happy_path() +{ + MQTTContext_t mqttContext; + MQTTConnectInfo_t connectInfo; + MQTTPublishInfo_t willInfo; + uint32_t timeout = 2; + bool sessionPresent; + MQTTStatus_t status; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket; + size_t remainingLength, packetSize; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + + /* Success. */ + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_IgnoreAndReturn( MQTTSuccess ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + + /* With non-NULL Will. */ + mqttContext.connectStatus = MQTTNotConnected; + willInfo.pTopicName = "test"; + willInfo.topicNameLength = 4; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); +} + +/* ========================================================================== */ + +/** + * @brief Test that MQTT_Publish works as intended. + */ +void test_MQTT_Publish( void ) +{ + MQTTContext_t mqttContext; + MQTTPublishInfo_t publishInfo; + uint16_t packetId; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTStatus_t status; + size_t headerSize; + + const uint16_t PACKET_ID = 1; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + transport.send = transportSendFailure; + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + memset( ( void * ) &publishInfo, 0x0, sizeof( publishInfo ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + /* Verify parameters. */ + status = MQTT_Publish( NULL, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + status = MQTT_Publish( &mqttContext, NULL, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + publishInfo.qos = MQTTQoS1; + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Bad Parameter when getting packet size. */ + publishInfo.qos = MQTTQoS0; + MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Always return success from now on. */ + MQTT_GetPublishPacketSize_IgnoreAndReturn( MQTTSuccess ); + + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTNoMemory ); + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + + /* The transport interface will fail. */ + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* We need sendPacket to be called with at least 1 byte to send, so that + * it can return failure. This argument is the output of serializing the + * publish header. */ + headerSize = 1; + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); + + /* We can ignore this now since MQTT_Publish initializes the header size to + * 0, so its initial send returns success (since 0 bytes are sent). */ + MQTT_SerializePublishHeader_IgnoreAndReturn( MQTTSuccess ); + publishInfo.pPayload = "Test"; + publishInfo.payloadLength = 4; + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); + + mqttContext.transportInterface.send = transportSendSuccess; + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /* Now for non zero QoS, which uses state engine. */ + publishInfo.qos = MQTTQoS2; + MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTStateNull ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + publishInfo.qos = MQTTQoS1; + MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTPublishSend ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); +} + +/* ========================================================================== */ + +/** + * @brief Test that MQTT_Disconnect works as intended. + */ +void test_MQTT_Disconnect( void ) +{ + MQTTContext_t mqttContext; + MQTTStatus_t status; + uint8_t buffer[ 10 ]; + uint8_t * bufPtr = buffer; + MQTTNetworkContext_t networkContext = ( MQTTNetworkContext_t ) &bufPtr; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + size_t disconnectSize = 2; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + transport.networkContext = networkContext; + transport.recv = transportRecvSuccess; + transport.send = transportSendFailure; + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + mqttContext.connectStatus = MQTTConnected; + + /* Verify parameters. */ + status = MQTT_Disconnect( NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Send failure. */ + MQTT_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetDisconnectPacketSize_ReturnThruPtr_pPacketSize( &disconnectSize ); + MQTT_SerializeDisconnect_ExpectAnyArgsAndReturn( MQTTSuccess ); + status = MQTT_Disconnect( &mqttContext ); + TEST_ASSERT_EQUAL( MQTTSendFailed, status ); + + /* Successful send. */ + mqttContext.transportInterface.send = mockSend; + MQTT_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetDisconnectPacketSize_ReturnThruPtr_pPacketSize( &disconnectSize ); + MQTT_SerializeDisconnect_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Write a disconnect packet into the buffer. */ + mqttBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + status = MQTT_Disconnect( &mqttContext ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTNotConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_MEMORY( mqttBuffer, buffer, 2 ); +} + +/* ========================================================================== */ + +/** + * @brief Test that MQTT_GetPacketId works as intended. + */ +void test_MQTT_GetPacketId( void ) +{ + MQTTContext_t mqttContext; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + uint16_t packetId; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + /* Verify parameters. */ + packetId = MQTT_GetPacketId( NULL ); + TEST_ASSERT_EQUAL_INT( 0, packetId ); + + packetId = MQTT_GetPacketId( &mqttContext ); + TEST_ASSERT_EQUAL_INT( 1, packetId ); + TEST_ASSERT_EQUAL_INT( 2, mqttContext.nextPacketId ); + + mqttContext.nextPacketId = UINT16_MAX; + packetId = MQTT_GetPacketId( &mqttContext ); + TEST_ASSERT_EQUAL_INT( UINT16_MAX, packetId ); + TEST_ASSERT_EQUAL_INT( 1, mqttContext.nextPacketId ); +} From f6e9874024ab815d4d1e96341bcc53ec2f2c5e65 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 28 May 2020 13:38:13 -0700 Subject: [PATCH 526/844] Add CMock unit tests for MQTT_ProcessLoop (#963) * Add setup for testing handleIncomingPublish * Add coverage for handleIncomingPublish * Add callbacks for pubrec, pubrel, puback * Coverage for Acks * Add coverage for SUBACK and PINGRESP * Add coverage for all methods except handleKeepAlive * Add changes to mqtt_utest.c * Create expectProcessLoopCalls private method to make tests look much cleaner * Decouple context and expectProcessLoopCalls * Full coverage * Add @brief for all methods and variables * Update expectProcessLoopCalls from adding MQTT_GetPingreqPacketSize * Update comments * Address PR comments * Change parameter names in expectProcessLoopCalls * Update comments * Set remaining length when modifying packet type * Change case of method names * Address PR comments * Address PR comments * Add timer overflow test * Address PR comments * Add param docs * Rename macro to context instead of interface * Remove modifyIncomingPacket * Add divider --- libraries/standard/mqtt/utest/mqtt_utest.c | 615 ++++++++++++++++++++- 1 file changed, 607 insertions(+), 8 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index db7b2e9145..edfa0a593d 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -12,12 +12,83 @@ /** * @brief A valid starting packet ID per MQTT spec. Start from 1. */ -#define MQTT_NEXT_PACKET_ID_START ( 1 ) +#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) + +/** + * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) + +/** + * @brief A packet type not handled by MQTT_ProcessLoop. + */ +#define MQTT_PACKET_TYPE_INVALID ( 0U ) + +/** + * @brief Number of milliseconds in a second. + */ +#define MQTT_ONE_SECOND_TO_MS ( 1000U ) /** * @brief Length of the MQTT network buffer. */ -#define MQTT_TEST_BUFFER_LENGTH ( 1024 ) +#define MQTT_TEST_BUFFER_LENGTH ( 128 ) + +/** + * @brief Sample keep-alive interval that should be greater than 0. + */ +#define MQTT_SAMPLE_KEEPALIVE_INTERVAL_S ( 1U ) + +/** + * @brief Length of time spent for single test case with + * multiple iterations spent in the process loop for coverage. + */ +#define MQTT_SAMPLE_TIMEOUT_MS ( 1U ) + +/** + * @brief Zero timeout in the process loop implies one iteration. + */ +#define MQTT_NO_TIMEOUT_MS ( 0U ) + +/** + * @brief Sample length of remaining serialized data. + */ +#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) + +/** + * @brief Subtract this value from max value of global entry time + * for the timer overflow test. + */ +#define MQTT_OVERFLOW_OFFSET ( 3 ) + +/** + * @brief Subtract this value from max value of global entry time + * for the timer overflow test. + */ +#define MQTT_TIMER_CALLS_PER_ITERATION ( 3 ) + +/** + * @brief Timeout for the timer overflow test. + */ +#define MQTT_TIMER_OVERFLOW_TIMEOUT_MS ( 10 ) + +/** + * @brief A sample network context that we set to NULL. + */ +#define MQTT_SAMPLE_NETWORK_CONTEXT ( 0 ) + +/** + * @brief The packet type to be received by the process loop. + * IMPORTANT: Make sure this is set before calling expectProcessLoopCalls(...). + */ +static uint8_t currentPacketType = MQTT_PACKET_TYPE_INVALID; + +/** + * @brief The return value of modifyIncomingPacket(...) CMock callback that + * replaces a call to MQTT_GetIncomingPacketTypeAndLength. + * IMPORTANT: Make sure this is set before calling expectProcessLoopCalls(...). + */ +static MQTTStatus_t modifyIncomingPacketStatus = MQTTSuccess; /** * @brief Time at the beginning of each test. Note that this is not updated with @@ -36,6 +107,7 @@ static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; void setUp() { memset( ( void * ) mqttBuffer, 0x0, sizeof( mqttBuffer ) ); + globalEntryTime = 0; } /* Called after each test method. */ @@ -74,6 +146,7 @@ static int32_t mockSend( MQTTNetworkContext_t context, /* Write single byte and advance buffer. */ *mockNetwork++ = *buffer++; } + /* Move stream by bytes sent. */ ( *( uint8_t ** ) context ) = mockNetwork; @@ -93,13 +166,18 @@ static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer ) /** * @brief Mocked MQTT event callback. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] packetIdentifier Packet identifier of the incoming packet. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. */ static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { - ( void ) pContext; ( void ) pPacketInfo; ( void ) packetIdentifier; ( void ) pPublishInfo; @@ -117,12 +195,19 @@ static uint32_t getTime( void ) /** * @brief Mocked successful transport send. + * + * @param[in] tcpSocket TCP socket. + * @param[in] pMessage Data to send. + * @param[in] bytesToWrite Length of data to send. + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. */ static int32_t transportSendSuccess( MQTTNetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { - ( void ) pContext; + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pContext ); ( void ) pBuffer; return bytesToWrite; } @@ -142,12 +227,18 @@ static int32_t transportSendFailure( MQTTNetworkContext_t pContext, /** * @brief Mocked successful transport read. + * + * @param[in] tcpSocket TCP socket. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToRead Size of pBuffer. + * + * @return Number of bytes received; negative value on error. */ static int32_t transportRecvSuccess( MQTTNetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { - ( void ) pContext; + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pContext ); ( void ) pBuffer; return bytesToRead; } @@ -180,10 +271,12 @@ static int32_t transportRecvOneByte( MQTTNetworkContext_t pContext, /** * @brief Initialize the transport interface with the mocked functions for * send and receive. + * + * @brief param[in] pTransport The transport interface to use with the context. */ static void setupTransportInterface( MQTTTransportInterface_t * pTransport ) { - pTransport->networkContext = 0; + pTransport->networkContext = MQTT_SAMPLE_NETWORK_CONTEXT; pTransport->send = transportSendSuccess; pTransport->recv = transportRecvSuccess; } @@ -191,6 +284,8 @@ static void setupTransportInterface( MQTTTransportInterface_t * pTransport ) /** * @brief Initialize our event and time callback with the mocked functions * defined for the purposes this test. + * + * @brief param[in] pCallbacks Callbacks to use with the context. */ static void setupCallbacks( MQTTApplicationCallbacks_t * pCallbacks ) { @@ -198,7 +293,135 @@ static void setupCallbacks( MQTTApplicationCallbacks_t * pCallbacks ) pCallbacks->getTime = getTime; } -/* ============================ Testing MQTT_Init ========================= */ +/** + * @brief This helper function is used to expect any calls from the process loop + * to mocked functions belonging to an external header file. Its parameters + * are used to provide return values for these mocked functions. + */ +static void expectProcessLoopCalls( MQTTContext_t * const pContext, + MQTTStatus_t deserializeStatus, + MQTTPublishState_t stateAfterDeserialize, + MQTTStatus_t serializeStatus, + MQTTPublishState_t stateAfterSerialize, + MQTTStatus_t processLoopStatus, + bool incomingPublish ) +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + MQTTPacketInfo_t incomingPacket = { 0 }; + size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; + bool expectMoreCalls = true; + + /* Modify incoming packet depending on type to be tested. */ + incomingPacket.type = currentPacketType; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( modifyIncomingPacketStatus ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + + /* More calls are expected only with the following packet types. */ + if( ( currentPacketType != MQTT_PACKET_TYPE_PUBLISH ) && + ( currentPacketType != MQTT_PACKET_TYPE_PUBACK ) && + ( currentPacketType != MQTT_PACKET_TYPE_PUBREC ) && + ( currentPacketType != MQTT_PACKET_TYPE_PUBREL ) && + ( currentPacketType != MQTT_PACKET_TYPE_PUBCOMP ) && + ( currentPacketType != MQTT_PACKET_TYPE_PINGRESP ) && + ( currentPacketType != MQTT_PACKET_TYPE_SUBACK ) && + ( currentPacketType != MQTT_PACKET_TYPE_UNSUBACK ) ) + { + expectMoreCalls = false; + } + + /* When no data is available, the process loop tries to send a PINGREQ. */ + if( modifyIncomingPacketStatus == MQTTNoDataAvailable ) + { + if( ( pContext->waitingForPingResp == false ) && + ( pContext->keepAliveIntervalSec != 0U ) ) + { + MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Replace pointer parameter being passed to the method. */ + MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize ); + MQTT_SerializePingreq_ExpectAnyArgsAndReturn( serializeStatus ); + } + + expectMoreCalls = false; + } + + /* Deserialize based on the packet type (PUB or ACK) being received. */ + if( expectMoreCalls ) + { + if( incomingPublish ) + { + MQTT_DeserializePublish_ExpectAnyArgsAndReturn( deserializeStatus ); + } + else + { + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( deserializeStatus ); + } + + if( ( deserializeStatus != MQTTSuccess ) || + ( currentPacketType == MQTT_PACKET_TYPE_PINGRESP ) || + ( currentPacketType == MQTT_PACKET_TYPE_SUBACK ) || + ( currentPacketType == MQTT_PACKET_TYPE_UNSUBACK ) ) + { + expectMoreCalls = false; + } + } + + /* Update state based on the packet type (PUB or ACK) being received. */ + if( expectMoreCalls ) + { + if( incomingPublish ) + { + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( stateAfterDeserialize ); + } + else + { + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( stateAfterDeserialize ); + } + + if( stateAfterDeserialize == MQTTPublishDone ) + { + expectMoreCalls = false; + } + } + + /* Serialize the packet to be sent in response to the received packet. */ + if( expectMoreCalls ) + { + MQTT_SerializeAck_ExpectAnyArgsAndReturn( serializeStatus ); + + if( serializeStatus != MQTTSuccess ) + { + expectMoreCalls = false; + } + } + + /* Update the state based on the sent packet. */ + if( expectMoreCalls ) + { + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( stateAfterSerialize ); + } + + /* Expect the above calls when running MQTT_ProcessLoop. */ + mqttStatus = MQTT_ProcessLoop( pContext, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( processLoopStatus, mqttStatus ); + + /* Any final assertions to end the test. */ + if( mqttStatus == MQTTSuccess ) + { + if( currentPacketType == MQTT_PACKET_TYPE_PUBLISH ) + { + TEST_ASSERT_TRUE( pContext->controlPacketSent ); + } + + if( currentPacketType == MQTT_PACKET_TYPE_PINGRESP ) + { + TEST_ASSERT_FALSE( pContext->waitingForPingResp ); + } + } +} + +/* ========================================================================== */ /** * @brief Test that MQTT_Init is able to update the context object correctly. @@ -214,7 +437,7 @@ void test_MQTT_Init_Happy_Path( void ) mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); - TEST_ASSERT_EQUAL( MQTT_NEXT_PACKET_ID_START, context.nextPacketId ); + TEST_ASSERT_EQUAL( MQTT_FIRST_VALID_PACKET_ID, context.nextPacketId ); /* These Unity assertions take pointers and compare their contents. */ TEST_ASSERT_EQUAL_MEMORY( &transport, &context.transportInterface, sizeof( transport ) ); TEST_ASSERT_EQUAL_MEMORY( &callbacks, &context.callbacks, sizeof( callbacks ) ); @@ -297,6 +520,7 @@ void test_MQTT_Connect_sendConnect( void ) MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); /* Transport send failed when sending CONNECT. */ + /* Choose 10 bytes variable header + 1 byte payload for the remaining * length of the CONNECT. The packet size needs to be nonzero for this test * as that is the amount of bytes used in the call to send the packet. */ @@ -543,6 +767,7 @@ void test_MQTT_Publish( void ) /* The transport interface will fail. */ MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* We need sendPacket to be called with at least 1 byte to send, so that * it can return failure. This argument is the output of serializing the * publish header. */ @@ -661,3 +886,377 @@ void test_MQTT_GetPacketId( void ) TEST_ASSERT_EQUAL_INT( UINT16_MAX, packetId ); TEST_ASSERT_EQUAL_INT( 1, mqttContext.nextPacketId ); } + +/* ========================================================================== */ + +/** + * @brief Test that NULL pContext causes MQTT_ProcessLoop to return MQTTBadParameter. + */ +void test_MQTT_ProcessLoop_Invalid_Params( void ) +{ + MQTTStatus_t mqttStatus = MQTT_ProcessLoop( NULL, MQTT_NO_TIMEOUT_MS ); + + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); +} + +/** + * @brief This test case covers all calls to the private method, + * handleIncomingPublish(...), + * that result in the process loop returning successfully. + */ +void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + modifyIncomingPacketStatus = MQTTSuccess; + + /* Assume QoS = 1 so that a PUBACK will be sent after receiving PUBLISH. + * That is, expectProcessLoopCalls will take on the following parameters: + * incomingPublish=true and stateAfterDeserialize=MQTTPubAckSend. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubAckSend, + MQTTSuccess, MQTTPublishDone, + MQTTSuccess, true ); + + /* Assume QoS = 2 so that a PUBREC will be sent after receiving PUBLISH. + * That is, expectProcessLoopCalls will take on the following parameters: + * incomingPublish=true and stateAfterDeserialize=MQTTPubRecSend. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRecSend, + MQTTSuccess, MQTTPubRelPending, + MQTTSuccess, true ); +} + +/** + * @brief This test case covers all calls to the private method, + * handleIncomingPublish(...), + * that result in the process loop returning an error. + */ +void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + modifyIncomingPacketStatus = MQTTSuccess; + + /* Verify that an error is propagated when deserialization fails by returning + * MQTTBadResponse. Any parameters beyond that are actually irrelevant + * because they are only used as return values for non-expected calls. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, true ); +} + +/** + * @brief This test case covers all calls to the private method, + * handleIncomingAck(...), + * that result in the process loop returning successfully. + */ +void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + modifyIncomingPacketStatus = MQTTSuccess; + + /* Mock the receiving of a PUBACK packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_PUBACK; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, MQTTPublishDone, + MQTTSuccess, false ); + + /* Mock the receiving of a PUBREC packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_PUBREC; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRelSend, + MQTTSuccess, MQTTPubCompPending, + MQTTSuccess, false ); + + /* Mock the receiving of a PUBREL packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_PUBREL; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubCompSend, + MQTTSuccess, MQTTPublishDone, + MQTTSuccess, false ); + + /* Mock the receiving of a PUBCOMP packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_PUBCOMP; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, MQTTPublishDone, + MQTTSuccess, false ); + + /* Mock the receiving of a PINGRESP packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_PINGRESP; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); + + /* Mock the receiving of a SUBACK packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_SUBACK; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); + + /* Mock the receiving of an UNSUBACK packet type and expect the appropriate + * calls made from the process loop. */ + currentPacketType = MQTT_PACKET_TYPE_UNSUBACK; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); +} + +/** + * @brief This test case covers all calls to the private method, + * handleIncomingAck(...), + * that result in the process loop returning an error. + */ +void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + modifyIncomingPacketStatus = MQTTSuccess; + + /* Verify that MQTTBadResponse is propagated when deserialization fails upon + * receiving an unknown packet type. */ + currentPacketType = MQTT_PACKET_TYPE_INVALID; + expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false ); + + /* Verify that MQTTSendFailed is propagated when receiving a PUBREC + * then failing when serializing a PUBREL to send in response. */ + currentPacketType = MQTT_PACKET_TYPE_PUBREC; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRelSend, + MQTTNoMemory, MQTTStateNull, + MQTTSendFailed, false ); + + /* Verify that MQTTBadResponse is propagated when deserialization fails upon + * receiving a PUBACK. */ + currentPacketType = MQTT_PACKET_TYPE_PUBACK; + expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false ); + + /* Verify that MQTTBadResponse is propagated when deserialization fails upon + * receiving a PINGRESP. */ + currentPacketType = MQTT_PACKET_TYPE_PINGRESP; + expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false ); + + /* Verify that MQTTBadResponse is propagated when deserialization fails upon + * receiving a SUBACK. */ + currentPacketType = MQTT_PACKET_TYPE_SUBACK; + expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false ); + + /* Verify that MQTTIllegalState is returned if MQTT_UpdateStateAck(...) + * provides an unknown state such as MQTTStateNull to sendPublishAcks(...). */ + currentPacketType = MQTT_PACKET_TYPE_PUBREC; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRelSend, + MQTTSuccess, MQTTStateNull, + MQTTIllegalState, false ); +} + +/** + * @brief This test case covers all calls to the private method, + * handleKeepAlive(...), + * that result in the process loop returning successfully. + */ +void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + modifyIncomingPacketStatus = MQTTNoDataAvailable; + globalEntryTime = MQTT_ONE_SECOND_TO_MS; + + /* Coverage for the branch path where keep alive interval is 0. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + context.waitingForPingResp = false; + context.keepAliveIntervalSec = 0; + expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); + + /* Coverage for the branch path where keep alive interval is greater than 0, + * and the interval has expired. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + context.waitingForPingResp = true; + context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; + context.lastPacketTime = getTime(); + expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); + + /* Coverage for the branch path where PINGRESP timeout interval hasn't expired. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + context.waitingForPingResp = true; + context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; + context.lastPacketTime = 0; + context.pingReqSendTimeMs = MQTT_ONE_SECOND_TO_MS; + context.pingRespTimeoutMs = MQTT_ONE_SECOND_TO_MS; + expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); + + /* Coverage for the branch path where a PINGRESP hasn't been sent out yet. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + context.waitingForPingResp = false; + context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; + context.lastPacketTime = 0; + expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTSuccess, false ); +} + +/** + * @brief This test case covers all calls to the private method, + * handleKeepAlive(...), + * that result in the process loop returning an error. + */ +void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + modifyIncomingPacketStatus = MQTTNoDataAvailable; + globalEntryTime = MQTT_ONE_SECOND_TO_MS; + + /* Coverage for the branch path where PING timeout interval hasn't expired. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + context.lastPacketTime = 0; + context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; + context.waitingForPingResp = true; + expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, + MQTTSuccess, MQTTStateNull, + MQTTKeepAliveTimeout, false ); +} + +/** + * @brief This test mocks a failing transport receive and runs multiple + * iterations of the process loop, resulting in returning MQTTRecvFailed. + */ +void test_MQTT_ProcessLoop_Receive_Failed( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + mqttStatus = MQTT_ProcessLoop( &context, MQTT_SAMPLE_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus ); +} + +/** + * @brief Set the initial entry time close to the maximum value, causing + * an overflow. This test then checks that the process loop still runs for the + * expected number of iterations in spite of this. + */ +void test_MQTT_ProcessLoop_Timer_Overflow( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket = { 0 }; + uint8_t i = 0; + uint8_t numIterations = ( MQTT_TIMER_OVERFLOW_TIMEOUT_MS / MQTT_TIMER_CALLS_PER_ITERATION ) + 1; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + networkBuffer.size = 1000; + incomingPacket.type = MQTT_PACKET_TYPE_PUBLISH; + incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + + globalEntryTime = UINT32_MAX - MQTT_OVERFLOW_OFFSET; + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + /* Verify that we run the expected number of iterations despite overflowing. */ + for( ; i < numIterations; i++ ) + { + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + /* Assume QoS = 1 so that a PUBACK will be sent after receiving PUBLISH. */ + MQTT_DeserializePublish_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTPubAckSend ); + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTPublishDone ); + } + + mqttStatus = MQTT_ProcessLoop( &context, MQTT_TIMER_OVERFLOW_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); +} From 0b849eeeae8cc4e2e295b9fd7371b57ce085a215 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 28 May 2020 15:05:46 -0700 Subject: [PATCH 527/844] Increase coverage of lightweight unit tests (#968) * Add tests for MQTT_SerializePublishHeader * Add tests for MQTT_SerializeAck and packet sizes * Verify buffers for serializer unit tests --- .../standard/mqtt/src/mqtt_lightweight.c | 63 ++-- .../mqtt/utest/mqtt_lightweight_utest.c | 300 +++++++++++++++++- 2 files changed, 329 insertions(+), 34 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 007fbabe06..eafa2dc4b7 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -712,42 +712,24 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, static void logConnackResponse( uint8_t responseCode ) { + const char * const pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; + /* Avoid unused parameter warning when assert and logs are disabled. */ ( void ) responseCode; + ( void ) pConnackResponses; assert( responseCode <= 5 ); /* Log an error based on the CONNACK response code. */ - switch( responseCode ) - { - case 0u: - LogInfo( ( "Connection accepted." ) ); - break; - - case 1u: - LogInfo( ( "Connection refused: unacceptable protocol version." ) ); - break; - - case 2u: - LogInfo( ( "Connection refused: identifier rejected." ) ); - break; - - case 3u: - LogInfo( ( "Connection refused: server unavailable" ) ); - break; - - case 4u: - LogInfo( ( "Connection refused: bad user name or password." ) ); - break; - - case 5u: - LogInfo( ( "Connection refused: not authorized." ) ); - break; - - default: - /* Empty default MISRA 16.4. */ - break; - } + LogError( ( "%s", pConnackResponses[ responseCode ] ) ); } /*-----------------------------------------------------------*/ @@ -1803,6 +1785,11 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, LogError( ( "Insufficient memory for packet." ) ); status = MQTTNoMemory; } + else if( packetId == 0U ) + { + LogError( ( "Packet ID cannot be 0." ) ); + status = MQTTBadParameter; + } else { switch( packetType ) @@ -1833,10 +1820,20 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) { - /* MQTT DISCONNECT packets always have the same size. */ - *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE; + MQTTStatus_t status = MQTTSuccess; - return MQTTSuccess; + if( pPacketSize == NULL ) + { + LogError( ( "pPacketSize is NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* MQTT DISCONNECT packets always have the same size. */ + *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE; + } + + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 72b2755a30..7bd3a9224e 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -723,6 +723,8 @@ void test_MQTT_SerializeSubscribe( void ) size_t packetSize = bufferSize; MQTTStatus_t status = MQTTSuccess; MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + uint8_t expectedPacket[ 100 ]; + uint8_t * pIterator = expectedPacket; const uint16_t PACKET_ID = 1; @@ -789,6 +791,18 @@ void test_MQTT_SerializeSubscribe( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + /* MQTT SUBSCRIBE packet format: + * 0x82 (1 byte) + * Remaining length (1-4 bytes) + * Packet ID (2 bytes) + * Topic filters (series of 2 byte lengths followed by filter, then QoS) (variable) */ + *pIterator++ = MQTT_PACKET_TYPE_SUBSCRIBE; + pIterator += encodeRemainingLength( pIterator, remainingLength ); + *pIterator++ = UINT16_HIGH_BYTE( PACKET_ID ); + *pIterator++ = UINT16_LOW_BYTE( PACKET_ID ); + pIterator += encodeString( pIterator, subscriptionList.pTopicFilter, subscriptionList.topicFilterLength ); + *pIterator++ = subscriptionList.qos; + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); } /** @@ -805,6 +819,8 @@ void test_MQTT_SerializeUnsubscribe( void ) size_t packetSize = bufferSize; MQTTStatus_t status = MQTTSuccess; MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + uint8_t expectedPacket[ 100 ]; + uint8_t * pIterator = expectedPacket; const uint16_t PACKET_ID = 1; @@ -870,6 +886,17 @@ void test_MQTT_SerializeUnsubscribe( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + /* MQTT UNSUBSCRIBE packet format: + * 0xA2 (1 byte) + * Remaining length (1-4 bytes) + * Packet ID (2 bytes) + * Topic filters (series of 2 byte lengths followed by filter) (variable) */ + *pIterator++ = MQTT_PACKET_TYPE_UNSUBSCRIBE; + pIterator += encodeRemainingLength( pIterator, remainingLength ); + *pIterator++ = UINT16_HIGH_BYTE( PACKET_ID ); + *pIterator++ = UINT16_LOW_BYTE( PACKET_ID ); + pIterator += encodeString( pIterator, subscriptionList.pTopicFilter, subscriptionList.topicFilterLength ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); } /* ========================================================================== */ @@ -946,6 +973,8 @@ void test_MQTT_SerializePublish( void ) size_t packetSize = bufferSize; MQTTStatus_t status = MQTTSuccess; MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + uint8_t expectedPacket[ 200 ]; + uint8_t * pIterator; const uint16_t PACKET_ID = 1; const char * longTopic = "/test/topic/name/longer/than/one/hundred/twenty/eight/characters" \ @@ -1021,13 +1050,27 @@ void test_MQTT_SerializePublish( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + /* MQTT PUBLISH packet format: + * 0x30 | publish flags (dup, qos, retain) (1 byte) + * Remaining length (1-4 bytes) + * Topic name length (2 bytes) + * Topic name (variable) + * Packet ID (if QoS > 0) (1 byte) + * Payload (>= 0 bytes) */ + expectedPacket[ 0 ] = MQTT_PACKET_TYPE_PUBLISH; + expectedPacket[ 1 ] = remainingLength; + ( void ) encodeString( &expectedPacket[ 2 ], publishInfo.pTopicName, publishInfo.topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); /* Again with QoS2, dup, and retain. Also encode remaining length > 2 bytes. */ publishInfo.qos = MQTTQoS2; publishInfo.retain = true; publishInfo.dup = true; publishInfo.pTopicName = longTopic; - publishInfo.topicNameLength = strlen( longTopic ); + publishInfo.topicNameLength = strlen( longTopic ); + publishInfo.pPayload = MQTT_SAMPLE_PAYLOAD; + publishInfo.payloadLength = MQTT_SAMPLE_PAYLOAD_LEN; + memset( ( void * ) buffer, 0x00, bufferSize ); /* Calculate exact packet size and remaining length. */ status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); @@ -1040,10 +1083,38 @@ void test_MQTT_SerializePublish( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + memset( ( void * ) expectedPacket, 0x00, sizeof( expectedPacket ) ); + pIterator = expectedPacket; + /* Dup = 0x8, QoS2 = 0x4, Retain = 0x1. 8 + 4 + 1 = 0xD. */ + *pIterator++ = MQTT_PACKET_TYPE_PUBLISH | 0xD; + pIterator += encodeRemainingLength( pIterator, remainingLength ); + pIterator += encodeString( pIterator, publishInfo.pTopicName, publishInfo.topicNameLength ); + *pIterator++ = UINT16_HIGH_BYTE( PACKET_ID ); + *pIterator++ = UINT16_LOW_BYTE( PACKET_ID ); + ( void ) memcpy( pIterator, publishInfo.pPayload, publishInfo.payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); } /* ========================================================================== */ +/** + * @brief Tests that MQTT_GetDisconnectPacketSize works as intended. + */ +void test_MQTT_GetDisconnectPacketSize( void ) +{ + MQTTStatus_t status; + size_t packetSize; + + /* Verify parameters. */ + status = MQTT_GetDisconnectPacketSize( NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Good case succeeds. A DISCONNECT is 2 bytes. */ + status = MQTT_GetDisconnectPacketSize( &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 2, packetSize ); +} + /** * @brief Tests that MQTT_SerializeDisconnect works as intended. */ @@ -1051,6 +1122,7 @@ void test_MQTT_SerializeDisconnect( void ) { uint8_t buffer[ 10 + 2 * BUFFER_PADDING_LENGTH ]; MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ] }; + uint8_t expectedPacket[ 2 ] = { MQTT_PACKET_TYPE_DISCONNECT, 0 }; MQTTStatus_t status = MQTTSuccess; /* Buffer size less than disconnect request fails. */ @@ -1068,6 +1140,25 @@ void test_MQTT_SerializeDisconnect( void ) status = MQTT_SerializeDisconnect( &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], 2 ); +} + +/** + * @brief Tests that MQTT_GetPingreqPacketSize works as intended. + */ +void test_MQTT_GetPingreqPacketSize( void ) +{ + MQTTStatus_t status; + size_t packetSize; + + /* Verify parameters. */ + status = MQTT_GetPingreqPacketSize( NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Good case succeeds. A PINGREQ is 2 bytes. */ + status = MQTT_GetPingreqPacketSize( &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( 2, packetSize ); } /** @@ -1077,6 +1168,7 @@ void test_MQTT_SerializePingreq( void ) { uint8_t buffer[ 10 + 2 * BUFFER_PADDING_LENGTH ]; MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ] }; + uint8_t expectedPacket[ 2 ] = { MQTT_PACKET_TYPE_PINGREQ, 0 }; MQTTStatus_t status = MQTTSuccess; /* Buffer size less than ping request fails. */ @@ -1094,6 +1186,7 @@ void test_MQTT_SerializePingreq( void ) status = MQTT_SerializePingreq( &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], 2 ); } /* ========================================================================== */ @@ -1542,6 +1635,211 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) TEST_ASSERT_EQUAL( MQTTBadResponse, status ); } +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_SerializePublishHeader works as intended. + */ +void test_MQTT_SerializePublishHeader( void ) +{ + MQTTPublishInfo_t publishInfo; + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t * pPacketIdentifierHigh; + uint8_t buffer[ 200 + 2 * BUFFER_PADDING_LENGTH ]; + uint8_t expectedPacket[ 200 ]; + uint8_t * pIterator; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + size_t packetSize = bufferSize; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + size_t headerSize = 0; + + const uint16_t PACKET_ID = 1; + + /* Verify bad parameters fail. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = MQTT_SerializePublishHeader( NULL, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + NULL, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Empty topic fails. */ + publishInfo.pTopicName = NULL; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = 0; + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + + /* 0 packet ID for QoS > 0. */ + publishInfo.qos = MQTTQoS1; + status = MQTT_SerializePublishHeader( &publishInfo, + 0, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Buffer too small. */ + fixedBuffer.size = 1; + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + fixedBuffer.size = bufferSize; + + /* Success case. */ + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* MQTT PUBLISH packet format: + * 0x30 | publish flags (dup, qos, retain) (1 byte) + * Remaining length (1-4 bytes) + * Topic name length (2 bytes) + * Topic name (variable) + * Packet ID (if QoS > 0) (1 byte) + * Payload (>= 0 bytes) */ + memset( ( void * ) expectedPacket, 0x00, sizeof( expectedPacket ) ); + pIterator = expectedPacket; + *pIterator++ = MQTT_PACKET_TYPE_PUBLISH | ( publishInfo.qos << 1 ); + pIterator += encodeRemainingLength( pIterator, remainingLength ); + pIterator += encodeString( pIterator, publishInfo.pTopicName, publishInfo.topicNameLength ); + *pIterator++ = UINT16_HIGH_BYTE( PACKET_ID ); + *pIterator++ = UINT16_LOW_BYTE( PACKET_ID ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + publishInfo.qos = MQTTQoS0; + publishInfo.pPayload = "test"; + publishInfo.payloadLength = 4; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublishHeader( &publishInfo, + 0, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + memset( ( void * ) expectedPacket, 0x00, sizeof( expectedPacket ) ); + pIterator = expectedPacket; + *pIterator++ = MQTT_PACKET_TYPE_PUBLISH; + pIterator += encodeRemainingLength( pIterator, remainingLength ); + pIterator += encodeString( pIterator, publishInfo.pTopicName, publishInfo.topicNameLength ); + /* Payload should not be serialized. */ + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + +/* ========================================================================== */ + +/** + * @brief Tests that MQTT_SerializeAck works as intended. + */ +void test_MQTT_SerializeAck( void ) +{ + uint8_t buffer[ 10 + 2 * BUFFER_PADDING_LENGTH ]; + uint8_t expectedPacket[ MQTT_PUBLISH_ACK_PACKET_SIZE ]; + size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; + MQTTStatus_t status = MQTTSuccess; + MQTTFixedBuffer_t fixedBuffer = { .pBuffer = &buffer[ BUFFER_PADDING_LENGTH ], .size = bufferSize }; + uint8_t packetType = MQTT_PACKET_TYPE_PUBACK; + + const uint16_t PACKET_ID = 1; + expectedPacket[ 0 ] = packetType; + expectedPacket[ 1 ] = 2U; + expectedPacket[ 2 ] = UINT16_HIGH_BYTE( PACKET_ID ); + expectedPacket[ 3 ] = UINT16_LOW_BYTE( PACKET_ID ); + + /* Verify parameters. */ + status = MQTT_SerializeAck( NULL, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + status = MQTT_SerializeAck( &fixedBuffer, packetType, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Not a PUBACK, PUBREC, PUBREL, or PUBCOMP. */ + status = MQTT_SerializeAck( &fixedBuffer, MQTT_PACKET_TYPE_CONNACK, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* An ack is 4 bytes. */ + fixedBuffer.size = 3; + status = MQTT_SerializeAck( &fixedBuffer, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); + fixedBuffer.size = bufferSize; + + /* Good case succeeds. */ + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeAck( &fixedBuffer, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], MQTT_PUBLISH_ACK_PACKET_SIZE ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* QoS 2 acks. */ + packetType = MQTT_PACKET_TYPE_PUBREC; + expectedPacket[ 0 ] = packetType; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeAck( &fixedBuffer, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], MQTT_PUBLISH_ACK_PACKET_SIZE ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + packetType = MQTT_PACKET_TYPE_PUBREL; + expectedPacket[ 0 ] = packetType; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeAck( &fixedBuffer, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], MQTT_PUBLISH_ACK_PACKET_SIZE ); + checkBufferOverflow( buffer, sizeof( buffer ) ); + + packetType = MQTT_PACKET_TYPE_PUBCOMP; + expectedPacket[ 0 ] = packetType; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeAck( &fixedBuffer, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], MQTT_PUBLISH_ACK_PACKET_SIZE ); + checkBufferOverflow( buffer, sizeof( buffer ) ); +} + /* ===================== Testing MQTT_SerializeConnect ===================== */ /** From 073558f0862e7a6b05c86fec7e892c77b4a409ee Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 28 May 2020 16:00:12 -0700 Subject: [PATCH 528/844] Fix warnings when logs are enabled in MQTT (#974) --- libraries/standard/mqtt/src/mqtt.c | 8 ++++---- libraries/standard/mqtt/src/mqtt_lightweight.c | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index c07f5c4c79..9ac2e8d533 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -380,7 +380,7 @@ static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, if( bytesReceived != ( int32_t ) bytesToReceive ) { LogError( ( "Receive error while discarding packet." - "ReceivedBytes=%d, ExpectedBytes=%u.", + "ReceivedBytes=%d, ExpectedBytes=%lu.", bytesReceived, bytesToReceive ) ); receiveError = true; @@ -430,7 +430,7 @@ static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, { LogError( ( "Incoming packet will be dumped: " "Packet length exceeds network buffer size." - "PacketSize=%u, NetworkBufferSize=%u", + "PacketSize=%lu, NetworkBufferSize=%lu", incomingPacket.remainingLength, pContext->networkBuffer.size ) ); status = discardPacket( pContext, @@ -451,7 +451,7 @@ static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, else { LogError( ( "Packet reception failed. ReceivedBytes=%d, " - "ExpectedBytes=%u.", + "ExpectedBytes=%lu.", bytesReceived, bytesToReceive ) ); status = MQTTRecvFailed; @@ -535,7 +535,7 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, { LogError( ( "Failed to send ACK packet: PacketType=%02x, " "SentBytes=%d, " - "PacketSize=%u", + "PacketSize=%lu", packetTypeByte, bytesSent, MQTT_PUBLISH_ACK_PACKET_SIZE ) ); diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index eafa2dc4b7..d4ac635943 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1387,7 +1387,7 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo connectPacketSize ) ); status = MQTTNoMemory; } - else if ( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) ) + else if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) ) { LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) ); status = MQTTBadParameter; @@ -1907,7 +1907,7 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) { LogError( ( "Buffer size of %lu is not sufficient to hold " - "serialized PINGREQ packet of size of %lu.", + "serialized PINGREQ packet of size of %u.", pBuffer->size, MQTT_PACKET_PINGREQ_SIZE ) ); status = MQTTNoMemory; @@ -2027,7 +2027,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket /* Any other packet type is invalid. */ default: - LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pIncomingPacket->type ) ); + LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%02x).", pIncomingPacket->type ) ); status = MQTTBadResponse; break; } From 87c175b9003d142d7b1083bdca8203ea17666245 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 28 May 2020 16:23:19 -0700 Subject: [PATCH 529/844] HTTPClient_InitializeRequestHeaders - Do not automatically write "Connection: close" (#975) * Remove default writing of Connection header and reflect tests * Update doc for HTTPClient_InitializeRequestHeaders to reflect change * Remove extra value --- libraries/standard/http/include/http_client.h | 5 +-- libraries/standard/http/src/http_client.c | 11 +---- libraries/standard/http/utest/http_utest.c | 40 +++++++++---------- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 67d4dc0583..3fdd3eb8c7 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -443,10 +443,9 @@ typedef struct HTTPResponse * <#HTTPRequestInfo_t.method> <#HTTPRequestInfo_t.pPath> * User-Agent: * Host: <#HTTPRequestInfo_t.pHost> - * Connection: close * - * Note that Connection header value can be changed to keep-alive by setting - * the HTTP_REQUEST_KEEP_ALIVE_FLAG in #HTTPRequestInfo_t.flags. + * Note that "Connection" header can be added and set to "keep-alive" by + * activating the HTTP_REQUEST_KEEP_ALIVE_FLAG in #HTTPRequestInfo_t.flags. * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pRequestInfo Initial request header configurations. diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index aecbef1424..7e05d9f9d2 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -502,7 +502,7 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques if( returnStatus == HTTP_SUCCESS ) { - if( HTTP_REQUEST_KEEP_ALIVE_FLAG & pRequestInfo->flags ) + if( ( HTTP_REQUEST_KEEP_ALIVE_FLAG & pRequestInfo->flags ) != 0u ) { /* Write "Connection: keep-alive". */ returnStatus = addHeader( pRequestHeaders, @@ -511,15 +511,6 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques ( const uint8_t * ) HTTP_CONNECTION_KEEP_ALIVE_VALUE, HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ); } - else - { - /* Write "Connection: close". */ - returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) HTTP_CONNECTION_FIELD, - HTTP_CONNECTION_FIELD_LEN, - ( const uint8_t * ) HTTP_CONNECTION_CLOSE_VALUE, - HTTP_CONNECTION_CLOSE_VALUE_LEN ); - } } return returnStatus; diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 9e04bf9ee3..fc2d2c1fb3 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -47,18 +47,20 @@ typedef struct _headers #define HTTP_TEST_HEADER_FORMAT \ "%s %s %s\r\n" \ "%s: %s\r\n" \ - "%s: %s\r\n" \ + "%s: %s\r\n\r\n" + +#define HTTP_TEST_EXTRA_HEADER_FORMAT \ + "%s %s %s\r\n" \ + "%s: %s\r\n" \ + "%s: %s\r\n" \ "%s: %s\r\n\r\n" /* Length of the following template HTTP header. * \r\n * : \r\n * : \r\n - * : \r\n * \r\n - * This is used to initialize the expectedHeader string. Note the missing - * . This is added later on depending on the - * value of HTTP_REQUEST_KEEP_ALIVE_FLAG in pRequestInfo->flags. */ + * This is used to initialize the expectedHeader string. */ #define HTTP_TEST_PREFIX_HEADER_LEN \ ( HTTP_METHOD_GET_LEN + SPACE_CHARACTER_LEN + \ HTTP_TEST_REQUEST_PATH_LEN + SPACE_CHARACTER_LEN + \ @@ -67,18 +69,11 @@ typedef struct _headers HTTP_USER_AGENT_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ HTTP_HOST_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ HTTP_TEST_HOST_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ - HTTP_CONNECTION_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ - HTTP_HEADER_LINE_SEPARATOR_LEN + \ HTTP_HEADER_LINE_SEPARATOR_LEN ) -/* Add HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN to account for longest possible - * length of template header. */ -#define HTTP_TEST_MAX_INITIALIZED_HEADER_LEN \ - ( HTTP_TEST_PREFIX_HEADER_LEN + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ) - /* Add 1 because snprintf(...) writes a null byte at the end. */ #define HTTP_TEST_INITIALIZED_HEADER_BUFFER_LEN \ - ( HTTP_TEST_MAX_INITIALIZED_HEADER_LEN + 1 ) + ( HTTP_TEST_PREFIX_HEADER_LEN + 1 ) /* Template HTTP header fields and values. */ #define HTTP_TEST_HEADER_FIELD "Authorization" @@ -397,8 +392,7 @@ void test_Http_InitializeRequestHeaders_Happy_Path() int numBytes = 0; setupRequestInfo( &requestInfo ); - expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN + - HTTP_CONNECTION_CLOSE_VALUE_LEN; + expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN; setupBuffer( &requestHeaders ); /* Happy Path testing. */ @@ -407,8 +401,7 @@ void test_Http_InitializeRequestHeaders_Happy_Path() HTTP_METHOD_GET, HTTP_TEST_REQUEST_PATH, HTTP_PROTOCOL_VERSION, HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, - HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, - HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ); + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE ); /* Make sure that the entire pre-existing data was printed to the buffer. */ TEST_ASSERT_GREATER_THAN( 0, numBytes ); TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); @@ -474,18 +467,23 @@ void test_Http_InitializeRequestHeaders_ReqInfo() HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; int numBytes = 0; + size_t connectionKeepAliveHeaderLen = HTTP_CONNECTION_FIELD_LEN + + HTTP_HEADER_FIELD_SEPARATOR_LEN + + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN + + HTTP_HEADER_LINE_SEPARATOR_LEN; - setupRequestInfo( &requestInfo ); expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN - HTTP_TEST_REQUEST_PATH_LEN + HTTP_EMPTY_PATH_LEN + - HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN; + connectionKeepAliveHeaderLen; + + setupRequestInfo( &requestInfo ); setupBuffer( &requestHeaders ); requestInfo.pPath = 0; requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), - HTTP_TEST_HEADER_FORMAT, + HTTP_TEST_EXTRA_HEADER_FORMAT, HTTP_METHOD_GET, HTTP_EMPTY_PATH, HTTP_PROTOCOL_VERSION, HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, @@ -514,7 +512,7 @@ void test_Http_InitializeRequestHeaders_Insufficient_Memory() HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; - expectedHeaders.dataLen = HTTP_TEST_MAX_INITIALIZED_HEADER_LEN; + expectedHeaders.dataLen = HTTP_TEST_PREFIX_HEADER_LEN; setupRequestInfo( &requestInfo ); setupBuffer( &requestHeaders ); From 374c4086159c27ea979e63f3f6a6e4d162939aba Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 28 May 2020 17:00:09 -0700 Subject: [PATCH 530/844] Add MQTT ping and subscribe unit tests (#965) * Add setupSubscriptionInfo * Add simple test for subscribe * Add passing happy path test for MQTT_Subscribe * Achieve full coverage for MQTT_Subscribe and MQTT_Unsubscribe and its private functions * Add coverage for MQTT_Ping * Update comments * Move private method to top * Use MQTT_GetUnsubscribePacketSize for Unsubscribe tests * Make calls one line * Fix test_MQTT_SerializeConnect_Invalid_Params by setting connectInfo --- .../mqtt/utest/mqtt_lightweight_utest.c | 43 ++- libraries/standard/mqtt/utest/mqtt_utest.c | 315 ++++++++++++++++++ 2 files changed, 344 insertions(+), 14 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 7bd3a9224e..4c61c896fd 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -8,20 +8,22 @@ #define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) #define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ #define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */ -#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ -#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ #define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( 2U ) #define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( 2U ) + /* * Client identifier and length to use for the MQTT API tests. */ -#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ -#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ +#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ + /* * Will topic name and length to use for the MQTT API tests. */ -#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ -#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ /** @@ -166,6 +168,7 @@ static int32_t mockReceive( MQTTNetworkContext_t context, /* Read single byte and advance buffer. */ *returnBuffer++ = *mockNetwork++; } + /* Move stream by bytes read. */ ( *( uint8_t ** ) context ) = mockNetwork; @@ -196,12 +199,12 @@ static int32_t mockReceiveFailure( MQTTNetworkContext_t context, * @brief Mock transport receive that succeeds once, then fails. */ static int32_t mockReceiveSucceedThenFail( MQTTNetworkContext_t context, - void *pBuffer, + void * pBuffer, size_t bytesToRecv ) { int32_t retVal = 0; static int counter = 0; - + if( counter++ ) { retVal = mockReceiveFailure( context, pBuffer, bytesToRecv ); @@ -211,6 +214,7 @@ static int32_t mockReceiveSucceedThenFail( MQTTNetworkContext_t context, { retVal = mockReceive( context, pBuffer, bytesToRecv ); } + return retVal; } @@ -345,7 +349,8 @@ static size_t encodeString( uint8_t * pDestination, * @param[in] pBuffer Buffer to pad. * @param[in] bufferLength Total length of buffer. */ -static void padAndResetBuffer( uint8_t * pBuffer, size_t bufferLength ) +static void padAndResetBuffer( uint8_t * pBuffer, + size_t bufferLength ) { int i = 0; @@ -365,7 +370,8 @@ static void padAndResetBuffer( uint8_t * pBuffer, size_t bufferLength ) * @param[in] pBuffer Buffer to check. * @param[in] bufferLength Total length of buffer. */ -static void checkBufferOverflow( uint8_t * pBuffer, size_t bufferLength ) +static void checkBufferOverflow( uint8_t * pBuffer, + size_t bufferLength ) { /* Check beginning of buffer. */ TEST_ASSERT_EACH_EQUAL_UINT8( BUFFER_PADDING_BYTE, @@ -633,7 +639,8 @@ void test_MQTT_GetSubscribePacketSize( void ) { fourThousandSubscriptions[ i ].topicFilterLength = UINT16_MAX; } - subscriptionCount = sizeof( fourThousandSubscriptions ) / sizeof ( fourThousandSubscriptions[ 0 ] ); + + subscriptionCount = sizeof( fourThousandSubscriptions ) / sizeof( fourThousandSubscriptions[ 0 ] ); status = MQTT_GetSubscribePacketSize( fourThousandSubscriptions, subscriptionCount, &remainingLength, @@ -791,6 +798,7 @@ void test_MQTT_SerializeSubscribe( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + /* MQTT SUBSCRIBE packet format: * 0x82 (1 byte) * Remaining length (1-4 bytes) @@ -886,6 +894,7 @@ void test_MQTT_SerializeUnsubscribe( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + /* MQTT UNSUBSCRIBE packet format: * 0xA2 (1 byte) * Remaining length (1-4 bytes) @@ -1050,6 +1059,7 @@ void test_MQTT_SerializePublish( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + /* MQTT PUBLISH packet format: * 0x30 | publish flags (dup, qos, retain) (1 byte) * Remaining length (1-4 bytes) @@ -1436,6 +1446,7 @@ void test_MQTT_DeserializePublish( void ) size_t remainingLength = 0; uint16_t packetIdentifier; uint8_t * pPacketIdentifierHigh; + fixedBuffer.pBuffer = buffer; fixedBuffer.size = bufferSize; @@ -1478,7 +1489,7 @@ void test_MQTT_DeserializePublish( void ) mqttPacketInfo.remainingLength = 5; buffer[ 0 ] = 0; buffer[ 1 ] = 1; - buffer[ 2 ] = ( uint8_t )'a'; + buffer[ 2 ] = ( uint8_t ) 'a'; buffer[ 3 ] = 0; buffer[ 4 ] = 0; status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); @@ -1507,6 +1518,7 @@ void test_MQTT_DeserializePublish( void ) /* Deserialize QoS 0 packet. */ mqttPacketInfo.type = buffer[ 0 ]; + /* We don't need to go through the trouble of calling MQTT_GetIncomingPacketTypeAndLength. * We know the remaining length is < 128. */ mqttPacketInfo.remainingLength = ( size_t ) buffer[ 1 ]; @@ -1731,6 +1743,7 @@ void test_MQTT_SerializePublishHeader( void ) &fixedBuffer, &headerSize ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* MQTT PUBLISH packet format: * 0x30 | publish flags (dup, qos, retain) (1 byte) * Remaining length (1-4 bytes) @@ -1785,6 +1798,7 @@ void test_MQTT_SerializeAck( void ) uint8_t packetType = MQTT_PACKET_TYPE_PUBACK; const uint16_t PACKET_ID = 1; + expectedPacket[ 0 ] = packetType; expectedPacket[ 1 ] = 2U; expectedPacket[ 2 ] = UINT16_HIGH_BYTE( PACKET_ID ); @@ -1987,7 +2001,7 @@ static void verifySerializedConnectPacket( const MQTTConnectInfo_t * const pConn void test_MQTT_SerializeConnect_Invalid_Params() { MQTTStatus_t mqttStatus = MQTTSuccess; - size_t remainingLength = 0, packetSize = 0; + size_t remainingLength = 0UL, packetSize = 0UL; MQTTFixedBuffer_t networkBuffer; MQTTConnectInfo_t connectInfo; @@ -2003,13 +2017,14 @@ void test_MQTT_SerializeConnect_Invalid_Params() /* Test connectPacketSize > pBuffer->size. */ /* Get MQTT connect packet size and remaining length. */ + setupConnectInfo( &connectInfo ); mqttStatus = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); networkBuffer.pBuffer = mqttBuffer; - networkBuffer.size = remainingLength - 1; + networkBuffer.size = packetSize - 1; mqttStatus = MQTT_SerializeConnect( &connectInfo, NULL, remainingLength, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTNoMemory, mqttStatus ); diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index edfa0a593d..6adb989278 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -77,6 +77,16 @@ */ #define MQTT_SAMPLE_NETWORK_CONTEXT ( 0 ) +/** + * @brief Sample topic filter to subscribe to. + */ +#define MQTT_SAMPLE_TOPIC_FILTER "iot" + +/** + * @brief Length of sample topic filter. + */ +#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH ( sizeof( MQTT_SAMPLE_TOPIC_FILTER ) - 1 ) + /** * @brief The packet type to be received by the process loop. * IMPORTANT: Make sure this is set before calling expectProcessLoopCalls(...). @@ -293,6 +303,18 @@ static void setupCallbacks( MQTTApplicationCallbacks_t * pCallbacks ) pCallbacks->getTime = getTime; } +/** + * @brief Initialize pSubscribeInfo using test-defined macros. + * + * @param[in] pSubscribeInfo Pointer to MQTT subscription info. + */ +static void setupSubscriptionInfo( MQTTSubscribeInfo_t * pSubscribeInfo ) +{ + pSubscribeInfo->qos = MQTTQoS1; + pSubscribeInfo->pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER; + pSubscribeInfo->topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH; +} + /** * @brief This helper function is used to expect any calls from the process loop * to mocked functions belonging to an external header file. Its parameters @@ -1260,3 +1282,296 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) mqttStatus = MQTT_ProcessLoop( &context, MQTT_TIMER_OVERFLOW_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); } + +/* ========================================================================== */ + +/** + * @brief This test case verifies that MQTT_Subscribe returns MQTTBadParameter + * with an invalid parameter. This test case also gives us coverage over + * the private method, validateSubscribeUnsubscribeParams(...). + */ +void test_MQTT_Subscribe_invalid_params( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTSubscribeInfo_t subscribeInfo; + + /* Call subscribe with a NULL context. */ + mqttStatus = MQTT_Subscribe( NULL, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Call subscribe with a NULL subscription list. */ + mqttStatus = MQTT_Subscribe( &context, NULL, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Call subscribe with 0 subscriptions. */ + mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 0, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Packet ID cannot be 0 per MQTT 3.1.1 spec. */ + mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, 0 ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); +} + +/** + * @brief This test case verifies that MQTT_Subscribe returns successfully + * when valid parameters are passed and all bytes are sent. + */ +void test_MQTT_Subscribe_happy_path( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket = { 0 }; + MQTTSubscribeInfo_t subscribeInfo; + size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + setupSubscriptionInfo( &subscribeInfo ); + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTSuccess is returned with the following mocks. */ + MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize ); + MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + MQTT_SerializeSubscribe_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Expect the above calls when running MQTT_Subscribe. */ + mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); +} + +/** + * @brief This test case verifies that MQTT_Subscribe returns MQTTSendFailed + * if transport interface send returns an error. + */ +void test_MQTT_Subscribe_error_paths( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTSubscribeInfo_t subscribeInfo; + size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; + + /* Verify that an error is propagated when transport interface returns an error. */ + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + setupSubscriptionInfo( &subscribeInfo ); + + transport.send = transportSendFailure; + transport.recv = transportRecvFailure; + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTSendFailed is propagated when transport interface returns an error. */ + MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize ); + MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + MQTT_SerializeSubscribe_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Expect the above calls when running MQTT_Subscribe. */ + mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus ); +} + +/* ========================================================================== */ + +/** + * @brief This test case verifies that MQTT_Unsubscribe returns MQTTBadParameter + * with an invalid parameter. This test case also gives us coverage over + * the private method, validateSubscribeUnsubscribeParams(...). + */ +void test_MQTT_Unsubscribe_invalid_params( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTSubscribeInfo_t subscribeInfo; + + /* Call subscribe with a NULL context. */ + mqttStatus = MQTT_Unsubscribe( NULL, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Call subscribe with a NULL subscription list. */ + mqttStatus = MQTT_Unsubscribe( &context, NULL, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Call subscribe with 0 subscriptions. */ + mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 0, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Packet ID cannot be 0 per MQTT 3.1.1 spec. */ + mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, 0 ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); +} + +/** + * @brief This test case verifies that MQTT_Unsubscribe returns successfully + * when valid parameters are passed and all bytes are sent. + */ +void test_MQTT_Unsubscribe_happy_path( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTSubscribeInfo_t subscribeInfo; + size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + setupSubscriptionInfo( &subscribeInfo ); + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTSuccess is returned with the following mocks. */ + MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize ); + MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + MQTT_SerializeUnsubscribe_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Expect the above calls when running MQTT_Unsubscribe. */ + mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); +} + +/** + * @brief This test case verifies that MQTT_Unsubscribe returns MQTTSendFailed + * if transport interface send returns an error. + */ +void test_MQTT_Unsubscribe_error_path( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTSubscribeInfo_t subscribeInfo; + size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; + size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; + + /* Verify that an error is propagated when transport interface returns an error. */ + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + setupSubscriptionInfo( &subscribeInfo ); + + transport.send = transportSendFailure; + transport.recv = transportRecvFailure; + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTSendFailed is propagated when transport interface returns an error. */ + MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize ); + MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + MQTT_SerializeUnsubscribe_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Expect the above calls when running MQTT_Unsubscribe. */ + mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID ); + TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus ); +} + +/* ========================================================================== */ + +/** + * @brief This test case verifies that MQTT_Ping returns MQTTBadParameter + * with context parameter is NULL. + */ +void test_MQTT_Ping_invalid_params( void ) +{ + MQTTStatus_t mqttStatus; + + /* Call ping with a NULL context. */ + mqttStatus = MQTT_Ping( NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); +} + +/** + * @brief This test case verifies that MQTT_Ping returns successfully + * when valid parameters are passed and all bytes are sent. + */ +void test_MQTT_Ping_happy_path( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTSuccess is returned. */ + MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize ); + MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Expect the above calls when running MQTT_Ping. */ + mqttStatus = MQTT_Ping( &context ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + TEST_ASSERT_EQUAL( context.lastPacketTime, context.pingReqSendTimeMs ); + TEST_ASSERT_TRUE( context.waitingForPingResp ); +} + +/** + * @brief This test case verifies that MQTT_Ping returns MQTTSendFailed + * if transport interface send returns an error. + */ +void test_MQTT_Ping_error_path( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + + transport.send = transportSendFailure; + transport.recv = transportRecvFailure; + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTSendFailed is propagated when transport interface returns an error. */ + MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize ); + MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Expect the above calls when running MQTT_Ping. */ + mqttStatus = MQTT_Ping( &context ); + TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus ); + + + /* Initialize context. */ + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + /* Verify MQTTBadParameter is propagated when getting PINGREQ packet size fails. */ + MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); + MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize ); + /* Expect the above calls when running MQTT_Ping. */ + mqttStatus = MQTT_Ping( &context ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); +} + +/* ========================================================================== */ From 5c943be7d703d60c11b25db13455fb4d93e323e2 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Fri, 29 May 2020 11:37:29 -0700 Subject: [PATCH 531/844] HTTPClient_Send() Auto-Write Content-Length + Some MISRA Complete documentation in the header file. MISRA fixes on existing code. Automatically writing the Content-Length header. --- libraries/standard/http/include/http_client.h | 240 ++++-- libraries/standard/http/src/http_client.c | 748 ++++++++++++------ .../http/src/private/http_client_internal.h | 81 +- libraries/standard/http/utest/http_utest.c | 33 +- 4 files changed, 758 insertions(+), 344 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 3fdd3eb8c7..dcbfd740f9 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -8,9 +8,10 @@ /** * @brief Maximum size, in bytes, of headers allowed from the server. * - * If the total size in bytes of the headers sent from this server exceeds this - * configuration, then the status code #HTTP_SECURITY_ALERT_HEADERS is - * returned from #HTTPClient_Send. + * If the total size in bytes of the headers received from the server exceeds + * this configuration, then the status code + * #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED is returned from + * #HTTPClient_Send. */ #ifndef HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES #define HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES 2048U @@ -30,35 +31,72 @@ /** * @brief Supported HTTP request methods. */ -#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ -#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ -#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ -#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ +#define HTTP_METHOD_GET "GET" /**< HTTP Method GET. */ +#define HTTP_METHOD_PUT "PUT" /**< HTTP Method PUT. */ +#define HTTP_METHOD_POST "POST" /**< HTTP Method POST. */ +#define HTTP_METHOD_HEAD "HEAD" /**< HTTP Method HEAD. */ /** - * Flags for #HTTPRequestInfo_t.flags. - * These flags control what headers are written or not to the - * #HttpRequestHeaders_t.pBuffer. + * @brief The maximum Content-Length header field and value that could be + * written to the request header buffer. */ +#define HTTP_MAX_CONTENT_LENGTH_HEADER_LENGTH sizeof( "Content-Length: 4294967295" ) - 1u /** - * @brief Set this flag to indicate the request is for a persistent connection. + * @section http_send_flags + * @brief Values for #HTTPClient_Send flags parameter. + * These flags control some behavior of sending the request or receiving the + * response. * - * Setting this will cause a "Connection: Keep-Alive" to be written to the - * request. + * - #HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG
+ * @copybrief HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG + * + * Flags should be bitwise-ORed with each other to change the behavior of + * #HTTPClient_Send. */ -#define HTTP_REQUEST_KEEP_ALIVE_FLAG 0x1U /** * @brief Set this flag to disable automatically writing the Content-Length - * header. + * header to send to the server. + * + * This flag is valid only for #HTTPClient_Send.flags. */ -#define HTTP_REQUEST_DISABLE_CONTENT_LENGTH_FLAG 0x2U +#define HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG 0x1U /** - * Flags for #HTTPResponse_t.flags. - * These flags are populated in #HTTPResponse_t.flags by the #HTTPClient_Send() + * @section http_request_flags + * @brief Flags for #HTTPRequestInfo_t.flags. + * These flags control what headers are written or not to the + * #HttpRequestHeaders_t.pBuffer by #HTTPClient_InitializeRequestHeaders. + * + * - #HTTP_REQUEST_KEEP_ALIVE_FLAG
+ * @copybrief HTTP_REQUEST_KEEP_ALIVE_FLAG + * + * Flags should be bitwise-ORed with each other to change the behavior of + * #HTTPClient_InitializeRequestHeaders. + */ + +/** + * @brief Set this flag to indicate that the request is for a persistent + * connection. + * + * Setting this will cause a "Connection: Keep-Alive" to be written to the + * request headers. + * + * This flag is valid only for #HTTPRequestInfo.flags. + */ +#define HTTP_REQUEST_KEEP_ALIVE_FLAG 0x1U + +/** + * @section http_response_flags + * @brief Flags for #HTTPResponse_t.flags. + * These flags are populated in #HTTPResponse_t.flags by the #HTTPClient_Send * function. + * + * - #HTTP_RESPONSE_CONNECTION_CLOSE_FLAG
+ * @copybrief HTTP_RESPONSE_CONNECTION_CLOSE_FLAG + * - #HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG + * @copybrief HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG */ /** @@ -66,11 +104,15 @@ * * If a "Connection: close" header is present the application should always * close the connection. + * + * This flag is valid only for #HTTPResponse_t.flags. */ #define HTTP_RESPONSE_CONNECTION_CLOSE_FLAG 0x1U /** * @brief This will be set to true if header "Connection: Keep-Alive" is found. + * + * This flag is valid only for #HTTPResponse_t.flags. */ #define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U @@ -87,7 +129,6 @@ */ #define HTTP_RANGE_REQUEST_END_OF_FILE -1 - /** * @brief The HTTPNetworkContext is an incomplete type. The application must * define HTTPNetworkContext to the type of their network context. This context @@ -109,9 +150,9 @@ typedef struct HTTPNetworkContext HTTPNetworkContext_t; * * @return The number of bytes written or a negative network error code. */ -typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, - const void * pBuffer, - size_t bytesToWrite ); +typedef int32_t ( * HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ); /** * @brief Transport interface for reading data on the network. @@ -132,9 +173,9 @@ typedef int32_t (* HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, * * @return The number of bytes read or a negative error code. */ -typedef int32_t (* HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, - void * pBuffer, - size_t bytesToRead ); +typedef int32_t ( * HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ); /** * @brief The HTTP Client library transport layer interface. @@ -215,18 +256,75 @@ typedef enum HTTPStatus HTTP_INSUFFICIENT_MEMORY, /** - * @brief Represents all errors not related to user-input or transport I/O, but - * errors internal to the implementation of the HTTP client library. + * @brief The server sent more headers than the configured + * #HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES. * * Functions that may return this value: * - #HTTPClient_Send - * - #HTTPClient_ReadHeader */ - HTTP_INTERNAL_ERROR, HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED, - HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER, - HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, - /* TODO: Add return codes as implementation continues. */ + + /** + * @brief A response contained the "Connection: close" header, but there + * was more data at the end of the complete message. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA, + + /** + * @brief The server sent a chunk header containing an invalid character. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER, + + /** + * @brief The server sent a response with an invalid character in the + * HTTP protocol version. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION, + + /** + * @brief The server sent a response with an invalid character in the + * HTTP status-code or the HTTP status code is out of range. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE, + + /** + * @brief An invalid character was found in the HTTP response message. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, + + /** + * @brief The response contains either an invalid character in the + * Content-Length header or a Content-Length header when it was not expected + * to be present. + * + * Functions that may return this value: + * - #HTTPClient_Send + */ + HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH, + + /** + * @brief An error occurred in the third-party parsing library. + * + * Functions that may return this value: + * - #HTTPClient_Send + * - #HTTPClient_ReadHeader + */ + HTTP_PARSER_INTERNAL_ERROR, /** * @brief The requested header field was not found in the response buffer. @@ -237,15 +335,13 @@ typedef enum HTTPStatus HTTP_HEADER_NOT_FOUND, /** - * @brief The HTTP response, provided for parsing, is either corrupt or incomplete. + * @brief The HTTP response, provided for parsing, is either corrupt or + * incomplete. * * Functions that may return this value: * - #HTTPClient_ReadHeader */ - HTTP_INVALID_RESPONSE, - - /* Temporary error code while implementation is in progress. */ - HTTP_NOT_SUPPORTED, + HTTP_INVALID_RESPONSE } HTTPStatus_t; /** @@ -253,7 +349,10 @@ typedef enum HTTPStatus * * The memory for the header data buffer is supplied by the user. Information in * the buffer will be filled by calling #HTTPClient_InitializeRequestHeaders and - * #HTTPClient_AddHeader. + * #HTTPClient_AddHeader. This buffer may be automatically filled with the + * Content-Length header in #HTTPClient_Send, please see + * HTTP_MAX_CONTENT_LENGTH_HEADER_LENGTH for the maximum amount of space needed + * to accommodate the Content-Length header. */ typedef struct HTTPRequestHeaders { @@ -319,7 +418,7 @@ typedef struct HTTPRequestInfo * @brief Callback to intercept headers during the first parse through of the * response as it is received from the network. */ -typedef struct httpResponseParsingCallback +typedef struct HTTPClient_ResponseHeaderParsingCallback { /** * @brief Invoked when both a header field and its associated header value are found. @@ -339,7 +438,7 @@ typedef struct httpResponseParsingCallback /* Private context for the application. */ void * pContext; -} HTTPClient_HeaderParsingCallback_t; +} HTTPClient_ResponseHeaderParsingCallback_t; /** * @brief Represents an HTTP response. @@ -351,7 +450,7 @@ typedef struct HTTPResponse * * This buffer is supplied by the application. * - * This buffer is owned by the library during #HTTPClient_Send and + * This buffer is owned by the library during #HTTPClient_Send and * #HTTPClient_ReadHeader. This buffer should not be modifed until after * these functions return. * @@ -369,14 +468,14 @@ typedef struct HTTPResponse * parse through of the response as is it receive from the network. * Set to NULL to disable. */ - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback; + HTTPClient_ResponseHeaderParsingCallback_t * pHeaderParsingCallback; /** * @brief The starting location of the response headers in pBuffer. * * This is updated by #HTTPClient_Send. */ - uint8_t * pHeaders; + const uint8_t * pHeaders; /** * @brief Byte length of the response headers in pBuffer. @@ -390,7 +489,7 @@ typedef struct HTTPResponse * * This is updated by #HTTPClient_Send. */ - uint8_t * pBody; + const uint8_t * pBody; /** * @brief Byte length of the body in pBuffer. @@ -538,15 +637,29 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, int32_t rangeEnd ); /** - * @brief Send the request headers in #HTTPRequestHeaders_t and request body in - * parameter pRequestBodyBuf over the transport. The response is received in - * #HTTPResponse_t. - * - * The application should close the connection with the server if any - * HTTP_SECURITY_ALERT_X errors are returned. - * TODO: List all the security alerts possible after parsing development. - * - * TODO: Expand documentation. + * @brief Send the request headers in #HTTPRequestHeaders_t.pBuffer and request + * body in @p pRequestBodyBuf over the transport. The response is received in + * #HTTPResponse_t.pBuffer. + * + * If #HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG is not set in parameter @p flags, + * then the Content-Length to be sent to the server is automatically written to + * @p pRequestHeaders. The Content-Length will not be written when there is + * no request body. If there is not enough room in the buffer to write the + * Content-Length then #HTTP_INSUFFICIENT_MEMORY is returned. Please see + * #HTTP_MAX_CONTENT_LENGTH_HEADER_LENGTH for the maximum Content-Length header + * field and value that could be written to the buffer. + * + * The application should close the connection with the server if any of the + * following errors are returned: + * - #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED + * - #HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH + * + * The @p pResponse returned is valid only if this function returns HTTP_SUCCESS. * * @param[in] pTransport Transport interface, see #HTTPTransportInterface_t for * more information. @@ -557,6 +670,8 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * @param[in] reqBodyBufLen The length of the request entity in bytes. * @param[in] pResponse The response message and some notable response * parameters will be returned here on success. + * @param[in] pFlags Flags which modify the behavior of this function. Please + * see @ref http_send_flags. * * @return One of the following: * - #HTTP_SUCCESS (If successful.) @@ -564,14 +679,24 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * - #HTTP_NETWORK_ERROR (Errors in sending or receiving over the transport interface.) * - #HTTP_PARTIAL_RESPONSE (Part of an HTTP response was received in a partially filled response buffer.) * - #HTTP_NO_RESPONSE (No data was received from the transport interface.) - * - #HTTP_INSUFFICIENT_MEMORY (The response received could not fit into the response buffer.) - * TODO: Add more errors for parsing implementation. + * - #HTTP_INSUFFICIENT_MEMORY (The response received could not fit into the response buffer + * or extra headers could not be sent in the request.) + * - #HTTP_PARSER_INTERNAL_ERROR (Internal parsing error.) + * Security alerts are listed below, please see #HTTPStatus_t for more information: + * - #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED + * - #HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH */ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, - const HTTPRequestHeaders_t * pRequestHeaders, + HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, - HTTPResponse_t * pResponse ); + HTTPResponse_t * pResponse, + uint32_t flags ); /** * @brief Read a header from a buffer containing a complete HTTP response. @@ -595,6 +720,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, * - #HTTP_INVALID_PARAMETER (If any provided parameters or their members are invalid.) * - #HTTP_HEADER_NOT_FOUND (Header is not found in the passed response buffer.) * - #HTTP_INVALID_RESPONSE (Provided response is not a valid HTTP response for parsing.) + * - #HTTP_PARSER_INTERNAL_ERROR(If an error in the response parser.) */ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, const uint8_t * pHeaderName, diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 7e05d9f9d2..2d523f87f0 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -3,27 +3,23 @@ #include "http_client.h" #include "private/http_client_internal.h" -#include "private/http_client_parse.h" -#include "http_parser/http_parser.h" /*-----------------------------------------------------------*/ /** - * @brief An aggregator that represents the user-provided parameters to the - * #HTTPClient_ReadHeader API function. This will be used as context parameter - * for the parsing callbacks used by the API function. + * @brief Send HTTP bytes over the transport send interface. + * + * @param[in] pTransport Transport interface. + * @param[in] pDataHTTP request data to send. + * @param[in] dataLen HTTP request data length. + * + * @return #HTTP_SUCCESS if successful. If there was a network error or less + * bytes than what were specified were sent, then #HTTP_NETWORK_ERROR is + * returned. */ -typedef struct findHeaderContext -{ - const uint8_t * pField; - size_t fieldLen; - const uint8_t ** pValueLoc; - size_t * pValueLen; - uint8_t fieldFound : 1; - uint8_t valueFound : 1; -} findHeaderContext_t; - -/*-----------------------------------------------------------*/ +static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, + const uint8_t * pData, + size_t dataLen ); /** * @brief Send the HTTP headers over the transport send interface. @@ -31,31 +27,51 @@ typedef struct findHeaderContext * @param[in] pTransport Transport interface. * @param[in] pRequestHeaders Request headers to send, it includes the buffer * and length. + * @param[in] reqBodyLen The length of the request body to be sent. This is + * used to generated a Content-Length header. + * @param[in] flags Application provided flags to #HTTPClient_Send. * * @return #HTTP_SUCCESS if successful. If there was a network error or less - * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. + * bytes than what were specified were sent, then #HTTP_NETWORK_ERROR is + * returned. */ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, - const HTTPRequestHeaders_t * pRequestHeaders ); + HTTPRequestHeaders_t * pRequestHeaders, + size_t reqBodyLen, + uint32_t flags ); + +/** + * @brief Adds the Content-Length header field and value to the + * @p pRequestHeaders. + * + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] contentLength The Content-Length header value to write. + * + * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the + * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. + */ +static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeaders, + size_t contentLength ); /** * @brief Send the HTTP body over the transport send interface. * * @param[in] pTransport Transport interface. * @param[in] pRequestBodyBuf Request body buffer. - * @param[in] reqBodyLen Length of the request body buffer. + * @param[in] reqBodyBufLen Length of the request body buffer. * * @return #HTTP_SUCCESS if successful. If there was a network error or less - * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is returned. + * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is + * returned. */ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen ); /** - * @brief Write header based on parameters. This method also adds a trailing "\r\n". - * If a trailing "\r\n" already exists in the HTTP header, this method backtracks - * in order to write over it and updates the length accordingly. + * @brief Write header based on parameters. This method also adds a trailing + * "\r\n". If a trailing "\r\n" already exists in the HTTP header, this method + * backtracks in order to write over it and updates the length accordingly. * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pField The ISO 8859-1 encoded header field name to write. @@ -73,21 +89,21 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, size_t valueLen ); /** - * @brief Receive HTTP response from the transport recv interface. + * @brief Receive HTTP response from the transport receive interface. * * @param[in] pTransport Transport interface. - * @param[in] pResponse Response buffer. + * @param[in] pBuffer Response buffer. * @param[in] bufferLen Length of the response buffer. - * @param[out] Bytes received from the transport interface. + * @param[out] pBytesReceived Bytes received from the transport interface. * * @return Returns #HTTP_SUCCESS if successful. If there was a network error or * more bytes than what was specified were read, then #HTTP_NETWORK_ERROR is * returned. */ -HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, - uint8_t * pBuffer, - size_t bufferLen, - size_t * pBytesReceived ); +HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ); /** * @brief Get the status of the HTTP response given the parsing state and how @@ -113,28 +129,27 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, * * @param[in] pTransport Transport interface. * @param[in] pResponse Response message to receive data from the network. + * @param[in] pRequestHeaders Request headers for the corresponding HTTP request. * - * @return Returns #HTTP_SUCCESS if successful. If there was an issue with receiving - * the response over the network interface, then #HTTP_NETWORK_ERROR is returned, - * please see #receiveHttpResponse. If there was an issue with parsing, then the - * parsing error that occurred will be returned, please see - * #HTTPClient_InitializeParsingContext and HTTPClient_ParseResponse. Please - * see #getFinalResponseStatus for the status returned when there were no - * network or parsing errors. + * @return Returns #HTTP_SUCCESS if successful. Please see #receiveHttpData, + * #parseHttpResponse, and #getFinalResponseStatus for other statuses returned. */ static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, - HTTPResponse_t * pResponse ); + HTTPResponse_t * pResponse, + const HTTPRequestHeaders_t * pRequestHeaders ); /** - * @brief Converts an integer value to its ASCII representation in the passed buffer. + * @brief Converts an integer value to its ASCII representation in the passed + * buffer. * * @param[in] value The value to convert to ASCII. - * @param[out] pBuffer The buffer to store the ASCII representation of the integer. + * @param[out] pBuffer The buffer to store the ASCII representation of the + * integer. * * @return Returns the number of bytes written to @p pBuffer. */ static uint8_t convertInt32ToAscii( int32_t value, - uint8_t * pBuffer, + char * pBuffer, size_t bufferLength ); /** @@ -163,20 +178,20 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, * @param[in] bufferLen The length of the response buffer to parse. * @param[in] pField The header field to search for. * @param[in] fieldLen The length of pField. - * @param[out] pValue The location of the the header value found in pBuffer. + * @param[out] pValueLoc The location of the the header value found in pBuffer. * @param[out] pValueLen The length of pValue. * * @return One of the following: * - #HTTP_SUCCESS when header is found in the response. * - #HTTP_HEADER_NOT_FOUND if requested header is not found in response. * - #HTTP_INVALID_RESPONSE if passed response is invalid for parsing. - * - #HTTP_INTERNAL_ERROR for any parsing errors. + * - #HTTP_PARSER_INTERNAL_ERROR for any parsing errors. */ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, size_t bufferLen, const uint8_t * pField, size_t fieldLen, - const uint8_t ** pValue, + const uint8_t ** pValueLoc, size_t * pValueLen ); /** @@ -185,11 +200,13 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, * header field matched the header being searched for, and sets a flag to * represent reception of the header accordingly. * - * @param[in] pHttpParser The parser object to which this callback is registered. - * @param[in] pFieldLoc The location of the parsed header field in the response buffer. + * @param[in] pHttpParser Parsing object containing state and callback context. + * @param[in] pFieldLoc The location of the parsed header field in the response + * buffer. * @param[in] fieldLen The length of the header field. * - * @return Returns #HTTP_PARSER_CONTINUE_PARSING to indicate continuation with parsing. + * @return Returns #HTTP_PARSER_CONTINUE_PARSING to indicate continuation with + * parsing. */ static int findHeaderFieldParserCallback( http_parser * pHttpParser, const char * pFieldLoc, @@ -197,16 +214,17 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /** * @brief The "on_header_value" callback for the HTTP parser used by the - * #findHeaderInResponse function. The callback sets the user-provided output parameters - * for header value if the requested header's field was found in the + * #findHeaderInResponse function. The callback sets the user-provided output + * parameters for header value if the requested header's field was found in the * @ref findHeaderFieldParserCallback function. * - * @param[in] pHttpParser The parser object to which this callback is registered. - * @param[in] pVaLueLoc The location of the parsed header value in the response buffer. + * @param[in] pHttpParser Parsing object containing state and callback context. + * @param[in] pVaLueLoc The location of the parsed header value in the response + * buffer. * @param[in] valueLen The length of the header value. * - * @return Returns #HTTP_PARSER_STOP_PARSING if the header field/value pair are found; otherwise, - * #HTTP_PARSING_CONTINUE_PARSING. + * @return Returns #HTTP_PARSER_STOP_PARSING, if the header field/value pair are + * found, otherwise #HTTP_PARSING_CONTINUE_PARSING is returned. */ static int findHeaderValueParserCallback( http_parser * pHttpParser, const char * pVaLueLoc, @@ -220,25 +238,30 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, * the response. This callback is used to signal the parser to halt execution * if the requested header is not found. * - * @param[in] pHttpParser The parser object to which this callback is registered. + * @param[in] pHttpParser Parsing object containing state and callback context. * - * @return Returns #HTTP_PARSER_STOP_PARSING for the parser to halt further execution, - * as all headers have been parsed in the response. + * @return Returns #HTTP_PARSER_STOP_PARSING for the parser to halt further + * execution, as all headers have been parsed in the response. */ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ); /*-----------------------------------------------------------*/ static uint8_t convertInt32ToAscii( int32_t value, - uint8_t * pBuffer, + char * pBuffer, size_t bufferLength ) { - /* As input value may be altered and MISRA C 2012 rule 17.8 prevents modification - * of parameter, a local copy of the parameter is stored. */ - uint32_t absoluteValue = 0u; + /* As input value may be altered and MISRA C 2012 rule 17.8 prevents + * modification of parameter, a local copy of the parameter is stored. + * absoluteValue stores the positive version of the input value. Its type + * remains the same type as the input value to avoid unnecessary casting on + * a privately used variable. This variable's size will always be less + * than INT32_MAX. */ + int32_t absoluteValue = value; uint8_t numOfDigits = 0u; uint8_t index = 0u; uint8_t isNegative = 0u; + char temp = '\0'; assert( pBuffer != NULL ); assert( bufferLength >= MAX_INT32_NO_OF_DECIMAL_DIGITS ); @@ -249,32 +272,27 @@ static uint8_t convertInt32ToAscii( int32_t value, { isNegative = 1u; - *pBuffer = ( uint8_t ) '-'; + *pBuffer = '-'; /* Convert the value to its absolute representation. */ - absoluteValue = ( uint32_t ) ( value * -1 ); - } - else - { - /* As the input integer value is positive, store is as it-is. */ - absoluteValue = ( uint32_t ) value; + absoluteValue = value * -1; } /* Write the absolute integer value in reverse ASCII representation. */ do { - pBuffer[ isNegative + numOfDigits ] = ( uint8_t ) ( absoluteValue % 10u ) + ( uint8_t ) '0'; + pBuffer[ isNegative + numOfDigits ] = ( char ) ( ( absoluteValue % 10 ) + '0' ); numOfDigits++; - absoluteValue /= 10u; - } while( absoluteValue != 0u ); + absoluteValue /= 10; + } while( absoluteValue != 0 ); /* Reverse the digits in the buffer to store the correct ASCII representation * of the value. */ for( index = 0u; index < ( numOfDigits / 2u ); index++ ) { - pBuffer[ isNegative + index ] ^= pBuffer[ isNegative + numOfDigits - index - 1u ]; - pBuffer[ isNegative + numOfDigits - index - 1u ] ^= pBuffer[ isNegative + index ]; - pBuffer[ isNegative + index ] ^= pBuffer[ isNegative + numOfDigits - index - 1u ]; + temp = pBuffer[ isNegative + index ]; + pBuffer[ isNegative + index ] = pBuffer[ isNegative + numOfDigits - index - 1u ]; + pBuffer[ isNegative + numOfDigits - index - 1u ] = temp; } return( isNegative + numOfDigits ); @@ -316,29 +334,40 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, HTTP_HEADER_LINE_SEPARATOR_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN; - /* If we have enough room for the new header line, then write it to the header buffer. */ + /* If we have enough room for the new header line, then write it to the + * header buffer. */ if( ( backtrackHeaderLen + toAddLen ) <= pRequestHeaders->bufferLen ) { /* Write ": \r\n" to the headers buffer. */ /* Copy the header name into the buffer. */ - memcpy( pBufferCur, pField, fieldLen ); + ( void ) memcpy( pBufferCur, pField, fieldLen ); pBufferCur += fieldLen; /* Copy the field separator, ": ", into the buffer. */ - memcpy( pBufferCur, - ( const uint8_t * ) HTTP_HEADER_FIELD_SEPARATOR, - HTTP_HEADER_FIELD_SEPARATOR_LEN ); + + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, + HTTP_HEADER_FIELD_SEPARATOR, + HTTP_HEADER_FIELD_SEPARATOR_LEN ); pBufferCur += HTTP_HEADER_FIELD_SEPARATOR_LEN; /* Copy the header value into the buffer. */ - memcpy( pBufferCur, pValue, valueLen ); + ( void ) memcpy( pBufferCur, pValue, valueLen ); pBufferCur += valueLen; /* Copy the header end indicator, "\r\n\r\n" into the buffer. */ - memcpy( pBufferCur, - ( const uint8_t * ) HTTP_HEADER_END_INDICATOR, - HTTP_HEADER_END_INDICATOR_LEN ); + + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, + HTTP_HEADER_END_INDICATOR, + HTTP_HEADER_END_INDICATOR_LEN ); /* Update the headers length value. */ pRequestHeaders->headersLen = backtrackHeaderLen + toAddLen; @@ -347,9 +376,9 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, { LogError( ( "Unable to add header in buffer: " "Buffer has insufficient memory: " - "RequiredBytes=%u, RemainingBufferSize=%u", - toAddLen, - ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ) ); + "RequiredBytes=%lu, RemainingBufferSize=%lu", + ( unsigned long ) toAddLen, + ( unsigned long ) ( pRequestHeaders->bufferLen - pRequestHeaders->headersLen ) ) ); returnStatus = HTTP_INSUFFICIENT_MEMORY; } @@ -377,7 +406,7 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, assert( pMethod != NULL ); assert( methodLen != 0u ); - toAddLen += ( pPath == NULL || pathLen == 0 ) ? HTTP_EMPTY_PATH_LEN : pathLen; + toAddLen += ( ( pPath == NULL ) || ( pathLen == 0u ) ) ? HTTP_EMPTY_PATH_LEN : pathLen; if( ( toAddLen + pRequestHeaders->headersLen ) > pRequestHeaders->bufferLen ) { @@ -387,32 +416,69 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, if( returnStatus == HTTP_SUCCESS ) { /* Write " HTTP/1.1\r\n" to start the HTTP header. */ - memcpy( pBufferCur, pMethod, methodLen ); + + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, pMethod, methodLen ); pBufferCur += methodLen; - memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); + + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); pBufferCur += SPACE_CHARACTER_LEN; /* Use "/" as default value if is NULL. */ - if( ( pPath == NULL ) || ( pathLen == 0 ) ) + if( ( pPath == NULL ) || ( pathLen == 0u ) ) { - memcpy( pBufferCur, HTTP_EMPTY_PATH, HTTP_EMPTY_PATH_LEN ); + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, + HTTP_EMPTY_PATH, + HTTP_EMPTY_PATH_LEN ); pBufferCur += HTTP_EMPTY_PATH_LEN; } else { - memcpy( pBufferCur, pPath, pathLen ); + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, pPath, pathLen ); pBufferCur += pathLen; } - memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, + SPACE_CHARACTER, + SPACE_CHARACTER_LEN ); pBufferCur += SPACE_CHARACTER_LEN; - memcpy( pBufferCur, - HTTP_PROTOCOL_VERSION, HTTP_PROTOCOL_VERSION_LEN ); + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, + HTTP_PROTOCOL_VERSION, + HTTP_PROTOCOL_VERSION_LEN ); pBufferCur += HTTP_PROTOCOL_VERSION_LEN; - memcpy( pBufferCur, - HTTP_HEADER_LINE_SEPARATOR, HTTP_HEADER_LINE_SEPARATOR_LEN ); + + /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + ( void ) memcpy( pBufferCur, + HTTP_HEADER_LINE_SEPARATOR, + HTTP_HEADER_LINE_SEPARATOR_LEN ); pRequestHeaders->headersLen = toAddLen; } @@ -452,12 +518,12 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques LogError( ( "Parameter check failed: pRequestInfo->pHost is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( pRequestInfo->methodLen == 0 ) + else if( pRequestInfo->methodLen == 0u ) { LogError( ( "Parameter check failed: pRequestInfo->methodLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( pRequestInfo->hostLen == 0 ) + else if( pRequestInfo->hostLen == 0u ) { LogError( ( "Parameter check failed: pRequestInfo->hostLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; @@ -470,7 +536,7 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques if( returnStatus == HTTP_SUCCESS ) { /* Reset application-provided parameters. */ - pRequestHeaders->headersLen = 0; + pRequestHeaders->headersLen = 0u; /* Write " HTTP/1.1\r\n" to start the HTTP header. */ returnStatus = writeRequestLine( pRequestHeaders, @@ -578,7 +644,10 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, int32_t rangeEnd ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - uint8_t rangeValueBuffer[ MAX_RANGE_REQUEST_VALUE_LEN ] = { 0 }; + + /* This buffer uses a char type instead of the general purpose uint8_t because + * the range value expected to be written is within the ASCII character set. */ + char rangeValueBuffer[ HTTP_MAX_RANGE_REQUEST_VALUE_LEN ] = { '\0' }; size_t rangeValueLength = 0u; if( pRequestHeaders == NULL ) @@ -620,10 +689,10 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Generate the value data for the Range Request header.*/ /* Write the range value prefix in the buffer. */ - memcpy( rangeValueBuffer, - RANGE_REQUEST_HEADER_VALUE_PREFIX, - RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ); - rangeValueLength += RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN; + ( void ) memcpy( rangeValueBuffer, + HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX, + HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ); + rangeValueLength += HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN; /* Write the range start value in the buffer. */ rangeValueLength += convertInt32ToAscii( rangeStartOrlastNbytes, @@ -636,9 +705,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, if( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) { /* Write the "-" character to the buffer.*/ - memcpy( rangeValueBuffer + rangeValueLength, - DASH_CHARACTER, - DASH_CHARACTER_LEN ); + ( void ) memcpy( rangeValueBuffer + rangeValueLength, + DASH_CHARACTER, + DASH_CHARACTER_LEN ); rangeValueLength += DASH_CHARACTER_LEN; /* Write the rangeEnd value of the request range to the buffer .*/ @@ -650,9 +719,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, else if( rangeStartOrlastNbytes >= 0 ) { /* Write the "-" character to the buffer.*/ - memcpy( rangeValueBuffer + rangeValueLength, - DASH_CHARACTER, - DASH_CHARACTER_LEN ); + ( void ) memcpy( rangeValueBuffer + rangeValueLength, + DASH_CHARACTER, + DASH_CHARACTER_LEN ); rangeValueLength += DASH_CHARACTER_LEN; } else @@ -662,9 +731,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Add the Range Request header field and value to the buffer. */ returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) RANGE_REQUEST_HEADER_FIELD, - RANGE_REQUEST_HEADER_FIELD_LEN, - rangeValueBuffer, + ( const uint8_t * ) HTTP_RANGE_REQUEST_HEADER_FIELD, + HTTP_RANGE_REQUEST_HEADER_FIELD_LEN, + ( const uint8_t * ) rangeValueBuffer, rangeValueLength ); } @@ -673,40 +742,40 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ -static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, - const HTTPRequestHeaders_t * pRequestHeaders ) +static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, + const uint8_t * pData, + size_t dataLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; int32_t transportStatus = 0; assert( pTransport != NULL ); assert( pTransport->send != NULL ); - assert( pRequestHeaders != NULL ); + assert( pData != NULL ); - /* Send the HTTP headers over the network. */ transportStatus = pTransport->send( pTransport->pContext, - pRequestHeaders->pBuffer, - pRequestHeaders->headersLen ); + pData, + dataLen ); if( transportStatus < 0 ) { - LogError( ( "Failed to send HTTP headers: Transport send()" + LogError( ( "Failed to send HTTP data: Transport send()" " returned error: TransportStatus=%d", transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } - else if( ( size_t ) transportStatus != pRequestHeaders->headersLen ) + else if( ( size_t ) transportStatus != dataLen ) { - LogError( ( "Failed to send HTTP headers: Transport layer " - "did not send the required bytes: RequiredBytes=%u" + LogError( ( "Failed to send HTTP data: Transport layer " + "did not send the required bytes: RequiredBytes=%lu" ", SentBytes=%d.", - pRequestHeaders->headersLen, + ( unsigned long ) dataLen, transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } else { - LogDebug( ( "Sent HTTP headers over the transport: BytesSent " + LogDebug( ( "Sent HTTP data over the transport: BytesSent " "=%d.", transportStatus ) ); } @@ -716,41 +785,69 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport /*-----------------------------------------------------------*/ -static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, - const uint8_t * pRequestBodyBuf, - size_t reqBodyBufLen ) +static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeaders, + size_t contentLength ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - int32_t transportStatus = 0; + char pContentLengthValue[ MAX_INT32_NO_OF_DECIMAL_DIGITS ] = { '\0' }; + uint8_t contentLengthValueNumBytes = 0; + size_t headerLength = 0; - assert( pTransport != NULL ); - assert( pTransport->send != NULL ); - assert( pRequestBodyBuf != NULL ); + assert( pRequestHeaders != NULL ); + assert( contentLength > 0 ); - transportStatus = pTransport->send( pTransport->pContext, - pRequestBodyBuf, - reqBodyBufLen ); + contentLengthValueNumBytes = convertInt32ToAscii( ( int32_t ) contentLength, + pContentLengthValue, + sizeof( pContentLengthValue ) ); - if( transportStatus < 0 ) + returnStatus = addHeader( pRequestHeaders, + ( const uint8_t * ) HTTP_CONTENT_LENGTH_FIELD, + HTTP_CONTENT_LENGTH_FIELD_LEN, + ( const uint8_t * ) pContentLengthValue, + contentLengthValueNumBytes ); + + if( returnStatus != HTTP_SUCCESS ) { - LogError( ( "Failed to send HTTP body: Transport send() " - " returned error: TransportStatus=%d", - transportStatus ) ); - returnStatus = HTTP_NETWORK_ERROR; + LogError( ( "Failed to write Content-Length header to the request " + "header buffer: ContentLengthValue: %lu", + ( unsigned long ) contentLength ) ); } - else if( ( size_t ) transportStatus != reqBodyBufLen ) + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, + HTTPRequestHeaders_t * pRequestHeaders, + size_t reqBodyLen, + uint32_t flags ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + size_t numBytesToSend = 0u; + uint8_t shouldSendContentLength = 0u; + + assert( pTransport != NULL ); + assert( pTransport->send != NULL ); + assert( pRequestHeaders != NULL ); + + /* Send the content length header if the flag to disable is not set and the + * body length is greater than zero. */ + shouldSendContentLength = ( ( ( flags & HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG ) == 0u ) && + ( reqBodyLen > 0u ) ) ? 1u : 0u; + + if( shouldSendContentLength == 1u ) { - LogError( ( "Failed to send HTTP body: Transport send() " - "did not send the required bytes: RequiredBytes=%u" - ", SentBytes=%d.", - reqBodyBufLen, - transportStatus ) ); - returnStatus = HTTP_NETWORK_ERROR; + returnStatus = addContentLengthHeader( pRequestHeaders, reqBodyLen ); } - else + + if( returnStatus == HTTP_SUCCESS ) { - LogDebug( ( "Sent HTTP body over the transport: BytesSent=%d.", - transportStatus ) ); + LogDebug( ( "Sending HTTP request headers: HeaderBytes=%d", + numBytesToSend ) ); + + /* Send the HTTP headers over the network. */ + returnStatus = sendHttpData( pTransport, pRequestHeaders->pBuffer, pRequestHeaders->headersLen ); } return returnStatus; @@ -758,26 +855,47 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, - uint8_t * pBuffer, - size_t bufferLen, - size_t * pBytesReceived ) +static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, + const uint8_t * pRequestBodyBuf, + size_t reqBodyBufLen ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + assert( pTransport != NULL ); + assert( pTransport->send != NULL ); + assert( pRequestBodyBuf != NULL ); + + /* Send the request body. */ + LogDebug( ( "Sending the HTTP request body: BodyBytes=%d", + reqBodyBufLen ) ); + returnStatus = sendHttpData( pTransport, pRequestBodyBuf, reqBodyBufLen ); + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; + int32_t transportStatus = 0; assert( pTransport != NULL ); assert( pTransport->recv != NULL ); assert( pBuffer != NULL ); assert( pBytesReceived != NULL ); - int32_t transportStatus = pTransport->recv( pTransport->pContext, - pBuffer, - bufferLen ); + transportStatus = pTransport->recv( pTransport->pContext, + pBuffer, + bufferLen ); /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { - LogError( ( "Failed to receive HTTP response: Transport recv() " + LogError( ( "Failed to receive HTTP data: Transport recv() " "returned error: TransportStatus=%d.", transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; @@ -786,11 +904,11 @@ HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, { /* There is a bug in the transport recv if more bytes are reported * to have been read than the bytes asked for. */ - LogError( ( "Failed to receive HTTP response: Transport recv() " + LogError( ( "Failed to receive HTTP data: Transport recv() " " read more bytes than requested: BytesRead=%d, " - "RequestedBytes=%u", + "RequestedBytes=%lu", transportStatus, - bufferLen ) ); + ( unsigned long ) bufferLen ) ); returnStatus = HTTP_NETWORK_ERROR; } else if( transportStatus > 0 ) @@ -804,7 +922,8 @@ HTTPStatus_t receiveHttpResponse( const HTTPTransportInterface_t * pTransport, { /* When a zero is returned from the transport recv it will not be * invoked again. */ - LogDebug( ( "Received zero bytes from trasnport recv(). Receiving " + *pBytesReceived = 0; + LogDebug( ( "Received zero bytes from transport recv(). Receiving " "transport data is complete." ) ); } @@ -827,8 +946,8 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, if( parsingState == HTTP_PARSING_NONE ) { LogError( ( "Response not received: Zero returned from " - "transport recv: totalReceived=%u", - totalReceived ) ); + "transport recv: totalReceived=%lu", + ( unsigned long ) totalReceived ) ); returnStatus = HTTP_NO_RESPONSE; } else if( parsingState == HTTP_PARSING_INCOMPLETE ) @@ -837,16 +956,16 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, { LogError( ( "Cannot receive complete response from tansport" " interface: Response buffer has insufficient " - "space: responseBufferLen=%u", - responseBufferLen ) ); + "space: responseBufferLen=%lu", + ( unsigned long ) responseBufferLen ) ); returnStatus = HTTP_INSUFFICIENT_MEMORY; } else { - LogError( ( "Received partial response from transport ", - "recv(): ResponseSize=%d, TotalBufferSize=%d", - totalReceived, - responseBufferLen - totalReceived ) ); + LogError( ( "Received partial response from transport " + "receive(): ResponseSize=%lu, TotalBufferSize=%lu", + ( unsigned long ) totalReceived, + ( unsigned long ) ( responseBufferLen - totalReceived ) ) ); returnStatus = HTTP_PARTIAL_RESPONSE; } } @@ -858,51 +977,64 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, return returnStatus; } +/*-----------------------------------------------------------*/ + static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, - HTTPResponse_t * pResponse ) + HTTPResponse_t * pResponse, + const HTTPRequestHeaders_t * pRequestHeaders ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; size_t totalReceived = 0u; size_t currentReceived = 0u; HTTPParsingContext_t parsingContext = { 0 }; - uint8_t shouldRecv = 0u; + uint8_t shouldRecv = 1u; + uint8_t isHeadResponse = 0u; assert( pTransport != NULL ); assert( pTransport->recv != NULL ); assert( pResponse != NULL ); + assert( pRequestHeaders != NULL ); + assert( pRequestHeaders->headersLen >= HTTP_MINIMUM_REQUEST_LINE_LENGTH ); - /* Initialize the parsing context. */ - returnStatus = HTTPClient_InitializeParsingContext( &parsingContext, - pResponse->pHeaderParsingCallback ); - - if( returnStatus == HTTP_SUCCESS ) + /* The parsing context needs to know if the response is for a HEAD request. + * The third-party parser requires parsing is manually indicated to stop + * in the httpParserOnHeadersCompleteCallback() for a HEAD response, + * otherwise the parser will not indicate the message was complete. */ + if( strncmp( ( const char * ) ( pRequestHeaders->pBuffer ), + HTTP_METHOD_HEAD, + sizeof( HTTP_METHOD_HEAD ) - 1u ) == 0 ) { - shouldRecv = 1u; + isHeadResponse = 1u; } + /* Initialize the parsing context for parsing the response received from the + * network. */ + initializeParsingContextForFirstResponse( &parsingContext ); + while( shouldRecv == 1u ) { /* Receive the HTTP response data into the pResponse->pBuffer. */ - returnStatus = receiveHttpResponse( pTransport, - pResponse->pBuffer + totalReceived, - pResponse->bufferLen - totalReceived, - ¤tReceived ); + returnStatus = receiveHttpData( pTransport, + pResponse->pBuffer + totalReceived, + pResponse->bufferLen - totalReceived, + ¤tReceived ); if( returnStatus == HTTP_SUCCESS ) { - if( currentReceived > 0u ) - { - totalReceived += currentReceived; - /* Data is received into the buffer and must be parsed. */ - returnStatus = HTTPClient_ParseResponse( &parsingContext, - pResponse->pBuffer + totalReceived, - currentReceived ); - } + /* Data is received into the buffer and must be parsed. Parsing is + * invoked even with a length of zero. A length of zero indicates to + * the parser that there is no more data from the server (EOF). */ + returnStatus = parseHttpResponse( &parsingContext, + pResponse, + currentReceived, + isHeadResponse ); + totalReceived += currentReceived; } - /* While there are no errors in the transport recv or parsing, we received - * data over the transport, the response message is not finished, and - * there is room in the response buffer. */ + /* Reading should continue if there are no errors in the transport recv + * or parsing, non-zero data was received from the network, + * the parser indicated the response message is not finished, and there + * is room in the response buffer. */ shouldRecv = ( ( returnStatus == HTTP_SUCCESS ) && ( currentReceived > 0u ) && ( parsingContext.state != HTTP_PARSING_COMPLETE ) && @@ -911,9 +1043,9 @@ static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t if( returnStatus == HTTP_SUCCESS ) { - /* For no network or parsing errors, the final status of the response - * message is derived from the state of the parsing and how much data - * is in the buffer. */ + /* If there are errors in receiving from the network or during parsing, + * the final status of the response message is derived from the state of + * the parsing and how much data is in the buffer. */ returnStatus = getFinalResponseStatus( parsingContext.state, totalReceived, pResponse->bufferLen ); @@ -925,10 +1057,11 @@ static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, - const HTTPRequestHeaders_t * pRequestHeaders, + HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, - HTTPResponse_t * pResponse ) + HTTPResponse_t * pResponse, + uint32_t flags ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -957,11 +1090,26 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( pRequestHeaders->headersLen < HTTP_MINIMUM_REQUEST_LINE_LENGTH ) + { + LogError( ( "Parameter check failed: pRequestHeaders->headersLen " + "does not meet minimum the required length. " + "MinimumRequiredLength=%u, HeadersLength =%lu", + HTTP_MINIMUM_REQUEST_LINE_LENGTH, + ( unsigned long ) ( pRequestHeaders->headersLen ) ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else if( ( pResponse != NULL ) && ( pResponse->pBuffer == NULL ) ) { LogError( ( "Parameter check failed: pResponse->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( ( pRequestBodyBuf == NULL ) && ( reqBodyBufLen > 0u ) ) + { + LogError( ( "Parameter check failed: pRequestBodyBuf is NULL, but " + "reqBodyBufLen is greater than zero." ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else { /* Empty else for MISRA 15.7 compliance. */ @@ -971,7 +1119,9 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, if( returnStatus == HTTP_SUCCESS ) { returnStatus = sendHttpHeaders( pTransport, - pRequestHeaders ); + pRequestHeaders, + reqBodyBufLen, + flags ); } /* Send the body, which is at another location in memory. */ @@ -996,7 +1146,8 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, if( pResponse != NULL ) { returnStatus = receiveAndParseHttpResponse( pTransport, - pResponse ); + pResponse, + pRequestHeaders ); } else { @@ -1009,17 +1160,22 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -/* The coverity violation is for using a non-const type for the "pHttpParser" parser - * instead of a const-type as the pointed to object is not modified by the function. - * We suppress this violations this violation as this function follows the function - * function signature of the "on_header_field" callback specified by the http-parser - * library. */ +/* The MISRA rule 8.13 violation is for using a non-const type for the + * "pHttpParser" parser instead of a const-type, because the pointed to object + * is not modified by the function. This violations is suppressed because this + * function follows the function signature of the "on_header_field" callback + * specified by the http-parser library. + * /* The MISRA directive 4.6 violation is for using primitive type int, instead of + * a typedef which denotes the signedness and size of the type. This violation + * is suppressed because this callback follows the function signature for the + * http_data_cb type in http-parser. */ /* coverity[misra_c_2012_rule_8_13_violation] */ +/* coverity[misra_c_2012_directive_4_6_violation] */ static int findHeaderFieldParserCallback( http_parser * pHttpParser, const char * pFieldLoc, size_t fieldLen ) { - findHeaderContext_t * pContext = ( findHeaderContext_t * ) pHttpParser->data; + findHeaderContext_t * pContext = NULL; assert( pHttpParser != NULL ); assert( pFieldLoc != NULL ); @@ -1032,8 +1188,21 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, assert( pContext->fieldFound == 0u ); assert( pContext->valueFound == 0u ); + /* The MISRA rule 11.5 violation flags casting a void pointer to another + * type. This rule is suppressed here because the http-parser library + * requires that private context into callbacks must be set to a void + * pointer variable. */ + /* coverity[misra_c_2012_rule_11_5_violation] */ + pContext = ( findHeaderContext_t * ) pHttpParser->data; + /* Check whether the parsed header matches the header we are looking for. */ - if( ( fieldLen == pContext->fieldLen ) && ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) + + /* MISRA rule 21.15 flags the memcmp of two different types, const uint8_t* + * and const char*. These types are the same size, so this comparision is + * acceptable. */ + /* coverity[misra_c_2012_rule_21_15_violation] */ + if( ( fieldLen == pContext->fieldLen ) && + ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) { LogDebug( ( "Found header field in response: " "HeaderName=%.*s, HeaderLocation=0x%x", @@ -1047,6 +1216,10 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /* Empty else for MISRA 15.7 compliance. */ } + /* The MISRA directive 4.6 violation is for using primitive type int, instead of + * a typedef which denotes the signedness and size of the type. This violation + * is suppressed because http-parser requires this callback to return an integer. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ return HTTP_PARSER_CONTINUE_PARSING; } @@ -1057,19 +1230,24 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, * We suppress this violations this violation as this function follows the function * function signature of the "on_header_value" callback specified by the http-parser * library. */ + +/* The MISRA directive 4.6 violation is for using primitive type int, instead of + * a typedef which denotes the signedness and size of the type. This violation + * is suppressed because this callback follows the function signature for the + * http_data_cb type in http-parser. */ /* coverity[misra_c_2012_rule_8_13_violation] */ +/* coverity[misra_c_2012_directive_4_6_violation] */ static int findHeaderValueParserCallback( http_parser * pHttpParser, const char * pVaLueLoc, size_t valueLen ) { - findHeaderContext_t * pContext = ( findHeaderContext_t * ) pHttpParser->data; - /* The coverity violation is for using "int" instead of a type that specifies size * and signedness information. We suppress this violation as this variable represents * the return value type of this callback function, whose return type is defined by * http-parser. */ /* coverity[misra_c_2012_directive_4_6_violation] */ int retCode = HTTP_PARSER_CONTINUE_PARSING; + findHeaderContext_t * pContext = NULL; assert( pHttpParser != NULL ); assert( pVaLueLoc != NULL ); @@ -1080,6 +1258,13 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, assert( pContext->pValueLoc != NULL ); assert( pContext->pValueLen != NULL ); + /* The MISRA rule 11.5 violation flags casting a void pointer to another + * type. This rule is suppressed here because the http-parser library + * requires that private context into callbacks must be set to a void + * pointer variable. */ + /* coverity[misra_c_2012_rule_11_5_violation] */ + pContext = ( findHeaderContext_t * ) pHttpParser->data; + /* The header value found flag should not be set. */ assert( pContext->valueFound == 0u ); @@ -1098,6 +1283,11 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, /* As we have found the value associated with the header, we don't need * to parse the response any further. */ + + /* The MISRA directive 4.6 violation is for using primitive type int, instead of + * a typedef which denotes the signedness and size of the type. This violation + * is suppressed because http-parser requires this callback to return an integer. */ + /* coverity[misra_c_2012_directive_4_6_violation] */ retCode = HTTP_PARSER_STOP_PARSING; } else @@ -1115,20 +1305,35 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, * We suppress this violations this violation as this function follows the function * function signature of the "on_headers_complete" callback specified by the http-parser * library. */ + +/* The MISRA directive 4.6 violation is for using primitive type int, instead of + * a typedef which denotes the signedness and size of the type. This violation + * is suppressed because this callback follows the function signature for the + * http_cb type in http-parser. */ /* coverity[misra_c_2012_rule_8_13_violation] */ +/* coverity[misra_c_2012_directive_4_6_violation] */ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) { + findHeaderContext_t * pContext = NULL; + /* Disable unused parameter warning. */ ( void ) pHttpParser; assert( pHttpParser != NULL ); + /* The MISRA rule 11.5 violation flags casting a void pointer to another + * type. This rule is suppressed here because the http-parser library + * requires that private context into callbacks must be set to a void + * pointer variable. */ + /* coverity[misra_c_2012_rule_11_5_violation] */ + pContext = ( findHeaderContext_t * ) pHttpParser->data; + /* If we have reached here, all headers in the response have been parsed but the requested * header has not been found in the response buffer. */ LogDebug( ( "Reached end of header parsing: Header not found in response: " "RequestedHeader=%.*s", - ( ( findHeaderContext_t * ) pHttpParser->data )->fieldLen, - ( ( findHeaderContext_t * ) pHttpParser->data )->pField ) ); + pContext->fieldLen, + pContext->pField ) ); /* No further parsing is required; thus, indicate the parser to stop parsing. */ return HTTP_PARSER_STOP_PARSING; @@ -1146,18 +1351,17 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, HTTPStatus_t returnStatus = HTTP_SUCCESS; http_parser parser = { 0 }; http_parser_settings parserSettings = { 0 }; - findHeaderContext_t context = - { - .pField = pField, - .fieldLen = fieldLen, - .pValueLoc = pValueLoc, - .pValueLen = pValueLen, - .fieldFound = 0u, - .valueFound = 0u - }; + findHeaderContext_t context = { 0 }; size_t numOfBytesParsed = 0u; - /* Disable unused variable warning. */ + context.pField = pField; + context.fieldLen = fieldLen; + context.pValueLoc = pValueLoc; + context.pValueLen = pValueLen; + context.fieldFound = 0u; + context.valueFound = 0u; + + /* Disable unused variable warning. This variable is used only in logging. */ ( void ) numOfBytesParsed; http_parser_init( &parser, HTTP_RESPONSE ); @@ -1189,7 +1393,9 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /* Header is not present in buffer. */ LogWarn( ( "Header not found in response buffer: " - "RequestedHeader=%.*s", fieldLen, pField ) ); + "RequestedHeader=%.*s", + ( int ) fieldLen, + pField ) ); returnStatus = HTTP_HEADER_NOT_FOUND; } @@ -1197,42 +1403,73 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, { /* The response buffer is invalid as only the header field was found * in the ": \r\n" format of an HTTP header. */ + + /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This + * violation is suppressed because the http-parser library directly sets + * and maps http_parser.http_errno to the enum http_errno. */ + /* coverity[misra_c_2012_rule_10_5_violation] */ LogError( ( "Unable to find header value in response: " "Response data is invalid: " "RequestedHeader=%.*s, ParserError=%s", - fieldLen, pField, + ( int ) fieldLen, + pField, http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; } else { - /* Empty else (when assert and logging is disabled) for MISRA 15.7 compliance. */ + /* Empty else (when assert and logging is disabled) for MISRA 15.7 + * compliance. */ /* Header is found. */ assert( ( context.fieldFound == 1u ) && ( context.valueFound == 1u ) ); LogDebug( ( "Found requested header in response: " "HeaderName=%.*s, HeaderValue=%.*s", - fieldLen, pField, - *pValueLen, *pValueLoc ) ); + ( int ) fieldLen, + pField, + ( int ) ( *pValueLen ), + *pValueLoc ) ); } - /* If the header field-value pair is found in response, then the return value of "on_header_value" - * callback (related to the header value) should cause the http_parser.http_errno to be "CB_header_value". */ + /* If the header field-value pair is found in response, then the return + * value of "on_header_value" callback (related to the header value) should + * cause the http_parser.http_errno to be "CB_header_value". */ + + /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This + * violation is suppressed because the http-parser library directly sets + * and maps http_parser.http_errno to the enum http_errno. */ + /* coverity[misra_c_2012_rule_10_5_violation] */ if( ( returnStatus == HTTP_SUCCESS ) && - ( ( parser.http_errno != HPE_CB_header_value ) ) ) - { - LogError( ( "Header found in response but http-parser returned error: ParserError=%s", + ( parser.http_errno != HPE_CB_header_value ) ) + { + /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This + * violation is suppressed because the http-parser library directly sets + * and maps http_parser.http_errno to the enum http_errno. */ + /* coverity[misra_c_2012_rule_10_5_violation] */ + LogError( ( "Header found in response but http-parser returned error: " + "ParserError=%s", http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); - returnStatus = HTTP_INTERNAL_ERROR; + returnStatus = HTTP_PARSER_INTERNAL_ERROR; } - /* If header was not found, then the "on_header_complete" callback is expected to be called which should - * cause the http_parser.http_errno to be "OK" */ + /* If header was not found, then the "on_header_complete" callback is + * expected to be called which should cause the http_parser.http_errno to be + * "OK" */ + + /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This + * violation is suppressed because the http-parser library directly sets + * and maps http_parser.http_errno to the enum http_errno. */ + /* coverity[misra_c_2012_rule_10_5_violation] */ else if( ( returnStatus == HTTP_HEADER_NOT_FOUND ) && - ( ( parser.http_errno != HPE_OK ) ) ) - { - LogError( ( "Header not found in response: http-parser returned error: ParserError=%s", + ( parser.http_errno != HPE_OK ) ) + { + /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This + * violation is suppressed because the http-parser library directly sets + * and maps http_parser.http_errno to the enum http_errno. */ + /* coverity[misra_c_2012_rule_10_5_violation] */ + LogError( ( "Header not found in response: http-parser returned error: " + "ParserError=%s", http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; } @@ -1341,20 +1578,36 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) str = "HTTP_INSUFFICIENT_MEMORY"; break; - case HTTP_INTERNAL_ERROR: - str = "HTTP_INTERNAL_ERROR"; - break; - case HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED: str = "HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED"; break; - case HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER: - str = "HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER"; + case HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA: + str = "HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA"; + break; + + case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER: + str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER"; + break; + + case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION: + str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION"; + break; + + case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE: + str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE"; + break; + + case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER: + str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER"; + break; + + case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH: + str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH"; break; - case HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH: - str = "HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH"; + case HTTP_PARSER_INTERNAL_ERROR: + str = "HTTP_PARSER_INTERNAL_ERROR"; break; case HTTP_HEADER_NOT_FOUND: @@ -1365,13 +1618,10 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) str = "HTTP_INVALID_RESPONSE"; break; - case HTTP_NOT_SUPPORTED: - str = "HTTP_NOT_SUPPORTED"; - break; - default: LogWarn( ( "Invalid status code received for string conversion: " "StatusCode=%d", status ) ); + break; } return str; diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 1777c8f00c..c0c3177e08 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -2,6 +2,7 @@ #define HTTP_CLIENT_INTERNAL_H_ #include "http_config.h" +#include "http_parser.h" #ifndef LogError #define LogError( message ) @@ -50,13 +51,14 @@ #define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1u ) /** - * @brief Constants for header fields added automatically during the request initialization. + * @brief Constants for header fields added automatically during the request + * initialization. */ #define HTTP_USER_AGENT_FIELD "User-Agent" #define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1u ) #define HTTP_HOST_FIELD "Host" -#define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1 ) -#define HTTP_USER_AGENT_VALUE_LEN ( sizeof( HTTP_USER_AGENT_VALUE ) - 1 ) +#define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1u ) +#define HTTP_USER_AGENT_VALUE_LEN ( sizeof( HTTP_USER_AGENT_VALUE ) - 1u ) /** * @brief Constants for header fields added based on flags. @@ -78,31 +80,68 @@ /** * @brief Constants relating to Range Requests. */ -#define RANGE_REQUEST_HEADER_FIELD "Range" -#define RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( RANGE_REQUEST_HEADER_FIELD ) - 1u ) -#define RANGE_REQUEST_HEADER_VALUE_PREFIX "bytes=" -#define RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ( sizeof( RANGE_REQUEST_HEADER_VALUE_PREFIX ) - 1u ) - -/* Maximum value of a 32 bit signed integer is 2,147,483,647. Used for calculating buffer space for - * ASCII representation of range values. */ -#define MAX_INT32_NO_OF_DECIMAL_DIGITS 10u - -/* Maximum buffer space for storing a Range Request Value. - * Largest size is of the form "bytes=-<" */ -#define MAX_RANGE_REQUEST_VALUE_LEN \ - ( RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DECIMAL_DIGITS + \ +#define HTTP_RANGE_REQUEST_HEADER_FIELD "Range" +#define HTTP_RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( HTTP_RANGE_REQUEST_HEADER_FIELD ) - 1u ) +#define HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX "bytes=" +#define HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ( sizeof( HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX ) - 1u ) + +/** + * @brief Maximum value of a 32 bit signed integer is 2,147,483,647. + * + * Used for calculating buffer space for ASCII representation of range values. + */ +#define MAX_INT32_NO_OF_DECIMAL_DIGITS 10u + +/** + * @brief Maximum buffer space for storing a Range Request Value. + * + * The largest Range Request value is of the form: + * "bytes=-" + */ +#define HTTP_MAX_RANGE_REQUEST_VALUE_LEN \ + ( HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + MAX_INT32_NO_OF_DECIMAL_DIGITS + \ 1u /* Dash character '-' */ + MAX_INT32_NO_OF_DECIMAL_DIGITS ) /** - * @brief Return value for http_parser registered callback to signal halting further execution. + * @brief Return value for the http-parser registered callback to signal halting + * further execution. */ -#define HTTP_PARSER_STOP_PARSING 1 +#define HTTP_PARSER_STOP_PARSING 1 /** - * @brief Return value for http_parser registered callback to signal further continuation of - * HTTP response parsing. + * @brief Return value for http_parser registered callback to signal + * continuation of HTTP response parsing. */ -#define HTTP_PARSER_CONTINUE_PARSING 0 +#define HTTP_PARSER_CONTINUE_PARSING 0 +/** + * @brief The minimum request-line in the headers has a possible one character + * custom method and a single forward / or asterisk * for the path: + * + * <1 character custom method> <1 character / or *> HTTP/1.x\r\n\r\n + * + * Therefore the minimum length is 16. If this minimum request-line is not + * satisfied, then the request headers to send are invalid. + * + * Note that custom methods are allowed per: + * https://tools.ietf.org/html/rfc2616#section-5.1.1. + */ +#define HTTP_MINIMUM_REQUEST_LINE_LENGTH 16u + + +/** + * @brief An aggregator that represents the user-provided parameters to the + * #HTTPClient_ReadHeader API function. This will be used as context parameter + * for the parsing callbacks used by the API function. + */ +typedef struct findHeaderContext +{ + const uint8_t * pField; /**< The field that is being searched for. */ + size_t fieldLen; /**< The length of pField. */ + const uint8_t ** pValueLoc; /**< The location of the value found in the buffer. */ + size_t * pValueLen; /**< the length of the value found. */ + uint8_t fieldFound; /**< Indicates that the header field was found during parsing. */ + uint8_t valueFound; /**< Indicates that the header value was found during parsing. */ +} findHeaderContext_t; #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index fc2d2c1fb3..36426192dc 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -7,7 +7,6 @@ /* Private includes for internal macros. */ #include "private/http_client_internal.h" -#include "private/http_client_parse.h" /* Include mock implementation of http-parser dependency. */ #include "mock_http_parser.h" @@ -123,7 +122,7 @@ static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" #define HEADER_NOT_IN_BUFFER "header-not-in-buffer" /* File-scoped Global variables */ -static HTTPStatus_t retCode = HTTP_INTERNAL_ERROR; +static HTTPStatus_t retCode = HTTP_SUCCESS; static uint8_t testBuffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; static HTTPRequestHeaders_t testHeaders = { 0 }; static _headers_t expectedHeaders = { 0 }; @@ -273,9 +272,9 @@ static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, const char * expectedRange, bool terminatorExists ) { - size_t expectedRangeLen = RANGE_REQUEST_HEADER_FIELD_LEN + + size_t expectedRangeLen = HTTP_RANGE_REQUEST_HEADER_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + - RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + + HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN + strlen( expectedRange ) + 2 * HTTP_HEADER_LINE_SEPARATOR_LEN; @@ -285,9 +284,9 @@ static void addRangeToExpectedHeaders( _headers_t * expectedHeaders, ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ), sizeof( expectedHeaders->buffer ) - expectedHeaders->dataLen, "%s%s%s%s\r\n\r\n", - RANGE_REQUEST_HEADER_FIELD, + HTTP_RANGE_REQUEST_HEADER_FIELD, HTTP_HEADER_FIELD_SEPARATOR, - RANGE_REQUEST_HEADER_VALUE_PREFIX, + HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX, expectedRange ); /* Make sure that the Range request was printed to the buffer. */ @@ -318,7 +317,7 @@ void setUp() /* Called after each test method. */ void tearDown() { - retCode = HTTP_INTERNAL_ERROR; + retCode = HTTP_SUCCESS; memset( &testHeaders, 0, sizeof( testHeaders ) ); memset( testBuffer, 0, sizeof( testBuffer ) ); memset( &expectedHeaders, 0, sizeof( expectedHeaders ) ); @@ -386,7 +385,7 @@ static void setupBuffer( HTTPRequestHeaders_t * pRequestHeaders ) */ void test_Http_InitializeRequestHeaders_Happy_Path() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; int numBytes = 0; @@ -418,7 +417,7 @@ void test_Http_InitializeRequestHeaders_Happy_Path() */ void test_Http_InitializeRequestHeaders_Invalid_Params() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; @@ -463,7 +462,7 @@ void test_Http_InitializeRequestHeaders_Invalid_Params() */ void test_Http_InitializeRequestHeaders_ReqInfo() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; int numBytes = 0; @@ -508,7 +507,7 @@ void test_Http_InitializeRequestHeaders_ReqInfo() */ void test_Http_InitializeRequestHeaders_Insufficient_Memory() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; HTTPRequestInfo_t requestInfo = { 0 }; @@ -534,7 +533,7 @@ void test_Http_InitializeRequestHeaders_Insufficient_Memory() */ void test_Http_AddHeader_Happy_Path() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; int numBytes = 0; @@ -575,7 +574,7 @@ void test_Http_AddHeader_Happy_Path() */ void test_Http_AddHeader_Invalid_Parameters() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; /* Test a NULL request headers interface. */ @@ -623,7 +622,7 @@ void test_Http_AddHeader_Invalid_Parameters() */ void test_Http_AddHeader_Extra_Header_Sufficient_Memory() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; int numBytes = 0; @@ -665,7 +664,7 @@ void test_Http_AddHeader_Extra_Header_Sufficient_Memory() */ void test_Http_AddHeader_Extra_Header_Insufficient_Memory() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; int numBytes = 0; @@ -709,7 +708,7 @@ void test_Http_AddHeader_Extra_Header_Insufficient_Memory() */ void test_Http_AddHeader_Single_Header_Insufficient_Memory() { - HTTPStatus_t httpStatus = HTTP_INTERNAL_ERROR; + HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; int numBytes = 0; @@ -1219,7 +1218,7 @@ void test_Http_ReadHeader_With_HttpParser_Internal_Error() strlen( HEADER_IN_BUFFER ), &pValueLoc, &valueLen ); - TEST_ASSERT_EQUAL( HTTP_INTERNAL_ERROR, retCode ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); } /** From 1b8c0e3d3ba4610264aac9a4b455c1c71e0ac00a Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 2 Jun 2020 17:37:37 -0700 Subject: [PATCH 532/844] HTTPClient_Send() part 2: Parsing + Unit tests (#977) * Implement parsing the response in HTTPClient_Send(). * HTTPClient_Send unit tests. * Delete unmaintained unit tests to help with PR. * Update the cmake configs for a better format. * Address PR comments. * Add missing space to sendHttpData docstring for pData * Create library for http_parser and link to http * Autoclone http_parser upon building with cmake if directory doesn't exist * Remove MISRA suppressions relating to http-parser * Fix type (endoding to encoding) Co-authored-by: Oscar Abrina --- libraries/CMakeLists.txt | 2 +- libraries/standard/http/CMakeLists.txt | 8 +- libraries/standard/http/httpFilePaths.cmake | 5 +- libraries/standard/http/src/http_client.c | 971 +++++++++-- .../standard/http/src/http_client_parse.c | 29 - .../http/src/private/http_client_internal.h | 80 + .../http/src/private/http_client_parse.h | 71 - libraries/standard/http/test/Makefile | 68 - libraries/standard/http/test/README.md | 17 - libraries/standard/http/test/common.h | 13 - libraries/standard/http/test/config.h | 25 - libraries/standard/http/test/http_config.h | 25 - .../http/test/test-HTTPClient_AddHeader.c | 253 --- .../test/test-HTTPClient_AddRangeHeader.c | 336 ---- ...test-HTTPClient_InitializeRequestHeaders.c | 220 --- .../http/test/test-HTTPClient_ReadHeader.c | 322 ---- .../standard/http/test/test-HTTPClient_Send.c | 537 ------ .../standard/http/third_party/CMakeLists.txt | 26 + libraries/standard/http/utest/CMakeLists.txt | 9 + .../standard/http/utest/http_send_utest.c | 1509 +++++++++++++++++ libraries/standard/http/utest/http_utest.c | 2 +- 21 files changed, 2459 insertions(+), 2069 deletions(-) delete mode 100644 libraries/standard/http/src/http_client_parse.c delete mode 100644 libraries/standard/http/src/private/http_client_parse.h delete mode 100644 libraries/standard/http/test/Makefile delete mode 100644 libraries/standard/http/test/README.md delete mode 100644 libraries/standard/http/test/common.h delete mode 100644 libraries/standard/http/test/config.h delete mode 100644 libraries/standard/http/test/http_config.h delete mode 100644 libraries/standard/http/test/test-HTTPClient_AddHeader.c delete mode 100644 libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c delete mode 100644 libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c delete mode 100644 libraries/standard/http/test/test-HTTPClient_ReadHeader.c delete mode 100644 libraries/standard/http/test/test-HTTPClient_Send.c create mode 100644 libraries/standard/http/third_party/CMakeLists.txt create mode 100644 libraries/standard/http/utest/http_send_utest.c diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index a68967daa3..1b4604b292 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -35,7 +35,7 @@ if( ${BUILD_TESTS} ) # Add a target for running coverage on tests. add_custom_target(coverage COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/tools/cmock/coverage.cmake - DEPENDS cmock unity http_utest mqtt_utest mqtt_lightweight_utest mqtt_state_utest + DEPENDS cmock unity http_utest mqtt_utest mqtt_lightweight_utest mqtt_state_utest http_send_utest WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) endif() diff --git a/libraries/standard/http/CMakeLists.txt b/libraries/standard/http/CMakeLists.txt index 8e0830a138..40e80536fa 100644 --- a/libraries/standard/http/CMakeLists.txt +++ b/libraries/standard/http/CMakeLists.txt @@ -1,6 +1,9 @@ # Include filepaths for source and include. include(httpFilePaths.cmake) +# Add http_parser submodule to HTTP client library. +add_subdirectory( third_party ) + # HTTP library target. add_library( http ${HTTP_SOURCES} ) @@ -13,11 +16,14 @@ target_include_directories( http PUBLIC ${HTTP_INCLUDE_PUBLIC_DIRS} # HTTP private include path. target_include_directories( http PRIVATE ${HTTP_INCLUDE_PRIVATE_DIRS} ) +# Link http_parser to http target. +target_link_libraries( http PRIVATE http_parser ) + # Organization of HTTP in IDE projects. set_target_properties( http PROPERTIES FOLDER libraries/standard ) source_group( include FILES include/http_client.h ) source_group( src FILES ${HTTP_SOURCES} ) -source_group( src\\private FILES src/private/http_internal.h src/private/http_client_parse.h ) +source_group( src\\private FILES src/private/http_internal.h ) if(BUILD_TESTS) add_subdirectory(utest) diff --git a/libraries/standard/http/httpFilePaths.cmake b/libraries/standard/http/httpFilePaths.cmake index 89cf02e709..804a77752d 100644 --- a/libraries/standard/http/httpFilePaths.cmake +++ b/libraries/standard/http/httpFilePaths.cmake @@ -7,8 +7,7 @@ # HTTP library source files. set( HTTP_SOURCES - ${MODULES_DIR}/standard/http/src/http_client.c - ${MODULES_DIR}/standard/http/src/http_client_parse.c ) + ${MODULES_DIR}/standard/http/src/http_client.c ) # HTTP library Public Include directories. set( HTTP_INCLUDE_PUBLIC_DIRS @@ -18,7 +17,7 @@ set( HTTP_INCLUDE_PUBLIC_DIRS # HTTP library Private Include directories. set( HTTP_INCLUDE_PRIVATE_DIRS ${MODULES_DIR}/standard/http/src - ${MODULES_DIR}/standard/http/third_party ) + ${MODULES_DIR}/standard/http/third_party/http_parser ) # HTTP library Include directories for Tests. set( HTTP_TEST_INCLUDE_DIRS diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 2d523f87f0..578bb4af95 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -10,7 +10,7 @@ * @brief Send HTTP bytes over the transport send interface. * * @param[in] pTransport Transport interface. - * @param[in] pDataHTTP request data to send. + * @param[in] pData HTTP request data to send. * @param[in] dataLen HTTP request data length. * * @return #HTTP_SUCCESS if successful. If there was a network error or less @@ -245,6 +245,816 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, */ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ); + +/** + * @brief Initialize the parsing context for parsing a response fresh from the + * server. + * + * @param[in] pParsingContext The parsing context to initialize. + */ +static void initializeParsingContextForFirstResponse( HTTPParsingContext_t * pParsingContext ); + +/** + * @brief Parses the response buffer in @p pResponse. + * + * This function may be invoked multiple times for different parts of the the + * HTTP response. The state of what was last parsed in the response is kept in + * @p pParsingContext. + * + * @param[in,out] pParsingState The response parsing state. + * @param[in,out] pResponse The response information to be updated. + * @param[in] parseLen The next length to parse in pResponse->pBuffer. + * @param[in] isHeaderResponse If the response is to a HEAD request this is set + * to 1, otherwise this is set to 0. + * + * @return One of the following: + * - #HTTP_SUCCESS + * - #HTTP_INVALID_PARAMETER + * - Please see #processHttpParserError for parsing errors returned. + */ +static HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, + HTTPResponse_t * pResponse, + size_t parseLen, + uint8_t isHeadResponse ); + +/** + * @brief Callback invoked during http_parser_execute() to indicate the start of + * the HTTP response message. + * + * This callback is invoked when an "H" in the "HTTP/1.1" that starts a response + * is found. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * + * @return #HTTP_PARSER_CONTINUE_PARSING to continue parsing. + */ +static int httpParserOnMessageBeginCallback( http_parser * pHttpParser ); + +/** + * @brief Callback invoked during http_parser_execute() when the HTTP response + * status-code and its associated reason-phrase are found. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * @param[in] pLoc Location of the HTTP response reason-phrase string in the + * response message buffer. + * @param[in] length Length of the HTTP response status code string. + * + * @return #HTTP_PARSER_CONTINUE_PARSING to continue parsing. + */ +static int httpParserOnStatusCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ); + +/** + * @brief Callback invoked during http_parser_execute() when an HTTP response + * header field is found. + * + * If only part of the header field was found, then parsing of the next part of + * the response message will invoke this callback in succession. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * @param[in] pLoc Location of the header field string in the response + * message buffer. + * @param[in] length Length of the header field. + * + * @return #HTTP_PARSER_CONTINUE_PARSING to continue parsing. + */ +static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ); + +/** + * @brief Callback invoked during http_parser_execute() when an HTTP response + * header value is found. + * + * This header value corresponds to the header field that was found in the + * immediately preceeding httpParserOnHeaderFieldCallback(). + * + * If only part of the header value was found, then parsing of the next part of + * the response message will invoke this callback in succession. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * @param[in] pLoc Location of the header value in the response message buffer. + * @param[in] length Length of the header value. + * + * @return #HTTP_PARSER_CONTINUE_PARSING to continue parsing. + */ +static int httpParserOnHeaderValueCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ); + +/** + * @brief Callback invoked during http_parser_execute() when the end of the + * headers are found. + * + * The end of the headers is signaled in a HTTP response message by another + * "\r\n" after the final header line. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * + * @return #HTTP_PARSER_CONTINUE_PARSING to continue parsing. + * #HTTP_PARSER_STOP_PARSING is returned if the response is for a HEAD request. + */ +static int httpParserOnHeadersCompleteCallback( http_parser * pHttpParser ); + +/** + * @brief Callback invoked during http_parser_execute() when the HTTP response + * body is found. + * + * If only part of the response body was found, then parsing of the next part of + * the response message will invoke this callback in succession. + * + * This callback will be also invoked in succession if the response body is of + * type "Transfer-Encoding: chunked". This callback will be invoked after each + * chunk header. + * + * The follow is an example of a Transfer-Encoding chunked response: + * + * HTTP/1.1 200 OK\r\n + * Content-Type: text/plain\r\n + * Transfer-Encoding: chunked\r\n + * \r\n + * d\r\n + * Hello World! \r\n + * 7\r\n + * I am a \r\n + * a\r\n + * developer.\r\n + * 0\r\n + * \r\n + * + * The first invocation of this callback will contain @p pLoc = "Hello World!" + * and @p length = 13. + * The second invocation of this callback will contain @p pLoc = "I am a " and + * @p length = 7. + * The third invocation of this callback will contain @p pLoc = "developer." and + * @p length = 10. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * @param[in] pLoc - Pointer to the body string in the response message buffer. + * @param[in] length - The length of the body found. + * + * @return Zero to continue parsing. All other return values will stop parsing + * and http_parser_execute() will return with status HPE_CB_body. + */ +static int httpParserOnBodyCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ); + +/** + * @brief Callback invoked during http_parser_execute() to indicate the the + * completion of an HTTP response message. + * + * When there is no response body, the end of the response message is when the + * headers end. This is indicated by another "\r\n" after the final header line. + * + * When there is response body, the end of the response message is when the + * full "Content-Length" value is parsed following the end of the headers. If + * there is no Content-Length header, then http_parser_execute() expects a + * zero length-ed parsing data to indicate the end of the response. + * + * For a "Transfer-Encoding: chunked" type of response message, the complete + * response message is signalled by a terminating chunk header with length zero. + * + * See https://github.com/nodejs/http-parser for more information. + * + * @param[in] pHttpParser Parsing object containing state and callback context. + * + * @return Zero to continue parsing. All other return values will stop parsing + * and http_parser_execute() will return with status HPE_CB_message_complete. + */ +static int httpParserOnMessageCompleteCallback( http_parser * pHttpParser ); + +/** + * @brief When a complete header is found the HTTP response header count + * increases and the application is notified. + * + * This function is invoked only in callbacks that could follow + * #httpParserOnHeaderValueCallback. These callbacks are + * #httpParserOnHeaderFieldCallback and #httpParserOnHeadersCompleteCallback. + * A header field and value is not is not known to be complete until + * #httpParserOnHeaderValueCallback is not called in succession. + * + * @param[in] pParsingContext Parsing state containing information to notify + * the application of a complete header. + */ +static void processCompleteHeader( HTTPParsingContext_t * pParsingContext ); + +/** + * @brief When parsing is complete an error could be indicated in + * pHttpParser->http_errno. This function translates that error into a library + * specific error code. + * + * @param[in] pHttpParser Third-party HTTP parsing context. + * + * @return One of the following: + * - #HTTP_SUCCESS + * - #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED + * - #HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER + * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH + * - #HTTP_PARSER_INTERNAL_ERROR + */ +static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ); + +/*-----------------------------------------------------------*/ + +static void processCompleteHeader( HTTPParsingContext_t * pParsingContext ) +{ + HTTPResponse_t * pResponse = NULL; + + assert( pParsingContext != NULL ); + assert( pParsingContext->pResponse != NULL ); + + pResponse = pParsingContext->pResponse; + + /* A header is complete when both the last header field and value have been + * filled in. */ + if( ( pParsingContext->pLastHeaderField != NULL ) && + ( pParsingContext->pLastHeaderValue != NULL ) ) + { + /* Increase the header count. */ + pResponse->headerCount++; + + LogDebug( ( "Response parsing: Found complete header: " + "HeaderField=%.*s, HeaderValue=%.*s", + pParsingContext->lastHeaderFieldLen, + pParsingContext->pLastHeaderField, + pParsingContext->lastHeaderValueLen, + pParsingContext->pLastHeaderValue ) ); + + /* If the application registered a callback, then it must be notified. */ + if( pResponse->pHeaderParsingCallback != NULL ) + { + pResponse->pHeaderParsingCallback->onHeaderCallback( + pResponse->pHeaderParsingCallback->pContext, + ( const uint8_t * ) pParsingContext->pLastHeaderField, + pParsingContext->lastHeaderFieldLen, + ( const uint8_t * ) pParsingContext->pLastHeaderValue, + pParsingContext->lastHeaderValueLen, + pResponse->statusCode ); + } + + /* Prepare the next header field and value for the first invocation of + * httpParserOnHeaderFieldCallback() and + * httpParserOnHeaderValueCallback(). */ + pParsingContext->pLastHeaderField = NULL; + pParsingContext->lastHeaderFieldLen = 0u; + pParsingContext->pLastHeaderValue = NULL; + pParsingContext->lastHeaderValueLen = 0u; + } +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnMessageBeginCallback( http_parser * pHttpParser ) +{ + HTTPParsingContext_t * pParsingContext = NULL; + HTTPResponse_t * pResponse = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + pResponse = pParsingContext->pResponse; + + assert( pResponse != NULL ); + + /* Parsing has initiated. */ + pParsingContext->state = HTTP_PARSING_INCOMPLETE; + + LogDebug( ( "Response parsing: Found the start of the response message." ) ); + + return HTTP_PARSER_CONTINUE_PARSING; +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnStatusCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ) +{ + HTTPParsingContext_t * pParsingContext = NULL; + HTTPResponse_t * pResponse = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + assert( pLoc != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + pResponse = pParsingContext->pResponse; + + assert( pResponse != NULL ); + + /* Set the location of what to parse next. */ + pParsingContext->pBufferCur = pLoc + length; + + /* Initialize the first header field and value to be passed to the user + * callback. */ + pParsingContext->pLastHeaderField = NULL; + pParsingContext->lastHeaderFieldLen = 0u; + pParsingContext->pLastHeaderValue = NULL; + pParsingContext->lastHeaderValueLen = 0u; + + /* httpParserOnStatusCallback() is reached because http_parser_execute() has + * successfully read the HTTP response status code. */ + pResponse->statusCode = ( uint16_t ) ( pHttpParser->status_code ); + + LogDebug( ( "Response parsing: Found the Reason-Phrase: " + "StatusCode=%d, ReasonPhrase=%.*s", + pResponse->statusCode, + length, + pLoc ) ); + + return HTTP_PARSER_CONTINUE_PARSING; +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ) +{ + HTTPParsingContext_t * pParsingContext = NULL; + HTTPResponse_t * pResponse = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + assert( pLoc != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + pResponse = pParsingContext->pResponse; + + assert( pResponse != NULL ); + + /* If this is the first time httpParserOnHeaderFieldCallback() has been + * invoked on a response, then the headers location in the response was set + * to NULL in the preceding httpParseOnStatusFieldCallback(). */ + if( pResponse->pHeaders == NULL ) + { + pResponse->pHeaders = ( const uint8_t * ) pLoc; + } + + /* Set the location of what to parse next. */ + pParsingContext->pBufferCur = pLoc + length; + + /* The httpParserOnHeaderFieldCallback() always follows the + * httpParserOnHeaderValueCallback() if there is another header field. When + * httpParserOnHeaderValueCallback() is not called in succession, then a + * complete header has been found. */ + processCompleteHeader( pParsingContext ); + + /* If httpParserOnHeaderFieldCallback() is invoked in succession, then the + * last time http_parser_execute() was called only part of the header field + * was parsed. The indication of successive invocations is a non-NULL + * pParsingContext->pLastHeaderField. */ + if( pParsingContext->pLastHeaderField == NULL ) + { + pParsingContext->pLastHeaderField = pLoc; + pParsingContext->lastHeaderFieldLen = length; + } + else + { + pParsingContext->lastHeaderFieldLen += length; + } + + LogDebug( ( "Response parsing: Found a header field: " + "HeaderField=%.*s", + length, + pLoc ) ); + + return HTTP_PARSER_CONTINUE_PARSING; +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnHeaderValueCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ) +{ + HTTPParsingContext_t * pParsingContext = NULL; + HTTPResponse_t * pResponse = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + assert( pLoc != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + pResponse = pParsingContext->pResponse; + + assert( pResponse != NULL ); + + /* Set the location of what to parse next. */ + pParsingContext->pBufferCur = pLoc + length; + + /* If httpParserOnHeaderValueCallback() is invoked in succession, then the + * last time http_parser_execute() was called only part of the header field + * was parsed. The indication of successive invocations is a non-NULL + * pParsingContext->pLastHeaderField. */ + if( pParsingContext->pLastHeaderValue == NULL ) + { + pParsingContext->pLastHeaderValue = pLoc; + pParsingContext->lastHeaderValueLen = length; + } + else + { + pParsingContext->lastHeaderValueLen += length; + } + + /* Given that httpParserOnHeaderFieldCallback() is ALWAYS invoked before + * httpParserOnHeaderValueCallback() is invoked, then the last header field + * should never be NULL. This would indicate a bug in the http-parser + * library. */ + assert( pParsingContext->pLastHeaderField != NULL ); + + LogDebug( ( "Response parsing: Found a header value: " + "HeaderValue=%.*s", + length, + pLoc ) ); + + return HTTP_PARSER_CONTINUE_PARSING; +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnHeadersCompleteCallback( http_parser * pHttpParser ) +{ + int shouldContinueParse = HTTP_PARSER_CONTINUE_PARSING; + HTTPParsingContext_t * pParsingContext = NULL; + HTTPResponse_t * pResponse = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + pResponse = pParsingContext->pResponse; + + assert( pResponse != NULL ); + + /* Set the location of what to parse next. */ + pParsingContext->pBufferCur += HTTP_HEADER_END_INDICATOR_LEN; + + /* If headers existed, then pResponse->pHeaders was set during the first + * call to httpParserOnHeaderFieldCallback(). */ + if( pResponse->pHeaders != NULL ) + { + /* The start of the headers ALWAYS come before the the end of the headers. */ + assert( ( const char * ) ( pResponse->pHeaders ) < pParsingContext->pBufferCur ); + + /* MISRA Rule 10.8 flags the following line for casting from a signed + * pointer difference to a size_t. This rule is suppressed because in + * in the previous statement it is asserted that the pointer difference + * will never be negative. */ + /* coverity[misra_c_2012_rule_10_8_violation] */ + pResponse->headersLen = ( size_t ) ( pParsingContext->pBufferCur - ( const char * ) ( pResponse->pHeaders ) ); + } + else + { + pResponse->headersLen = 0u; + } + + /* If the Content-Length header was found, then pHttpParser->content_length + * will not be equal to the maximum 64 bit integer. */ + if( pHttpParser->content_length != ( ( uint64_t ) -1 ) ) + { + pResponse->contentLength = ( size_t ) ( pHttpParser->content_length ); + } + else + { + pResponse->contentLength = 0u; + } + + /* If the Connection: close header was found this flag will be set. */ + if( ( pHttpParser->flags & ( unsigned int ) ( F_CONNECTION_CLOSE ) ) != 0u ) + { + pResponse->flags |= HTTP_RESPONSE_CONNECTION_CLOSE_FLAG; + } + + /* If the Connection: keep-alive header was found this flag will be set. */ + if( ( pHttpParser->flags & ( unsigned int ) ( F_CONNECTION_KEEP_ALIVE ) ) != 0u ) + { + pResponse->flags |= HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG; + } + + /* http_parser_execute() requires that callback implementations must + * indicate that parsing stops on headers complete, if response is to a HEAD + * request. A HEAD response will contain Content-Length, but no body. If + * the parser is not stopped here, then it will try to keep parsing past the + * end of the headers up to the Content-Length found. */ + if( pParsingContext->isHeadResponse == 1u ) + { + shouldContinueParse = HTTP_PARSER_STOP_PARSING; + } + + /* If headers are present in the response, then + * httpParserOnHeadersCompleteCallback() always follows + * the httpParserOnHeaderValueCallback(). When + * httpParserOnHeaderValueCallback() is not called in succession, then a + * complete header has been found. */ + processCompleteHeader( pParsingContext ); + + LogDebug( ( "Response parsing: Found the end of the headers." ) ); + + return shouldContinueParse; +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnBodyCallback( http_parser * pHttpParser, + const char * pLoc, + size_t length ) +{ + int shouldContinueParse = HTTP_PARSER_CONTINUE_PARSING; + HTTPParsingContext_t * pParsingContext = NULL; + HTTPResponse_t * pResponse = NULL; + uint8_t * pNextWriteLoc = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + assert( pLoc != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + pResponse = pParsingContext->pResponse; + + assert( pResponse != NULL ); + assert( pResponse->pBuffer != NULL ); + assert( pLoc > ( const char * ) ( pResponse->pBuffer ) ); + assert( pLoc < ( const char * ) ( pResponse->pBuffer + pResponse->bufferLen ) ); + + /* If this is the first time httpParserOnBodyCallback() has been invoked, + * then the start of the response body is NULL. */ + if( pResponse->pBody == NULL ) + { + pResponse->pBody = ( const uint8_t * ) ( pParsingContext->pBufferCur ); + pResponse->bodyLen = 0u; + } + + /* The next location to write. */ + + /* MISRA Rule 11.8 flags casting away the const qualifier in the pointer + * type. This rule is suppressed because when the body is of transfer + * encoding chunked, the body must be copied over the chunk headers that + * preceed it. This is done to have a contigous response body. This does + * affect future parsing as the changed segment will always be before the + * next place to parse. */ + /* coverity[misra_c_2012_rule_11_8_violation] */ + pNextWriteLoc = ( uint8_t * ) ( pResponse->pBody + pResponse->bodyLen ); + + /* If the response is of type Transfer-Encoding: chunked, then actual body + * will follow the the chunked header. This body data is in a later location + * and must be moved up in the buffer. When pLoc is greater than the current + * end of the body, that signals the parser found a chunk header. */ + if( pLoc > ( const char * ) pNextWriteLoc ) + { + ( void ) memcpy( pNextWriteLoc, ( const uint8_t * ) pLoc, length ); + } + + /* Increase the length of the body found. */ + pResponse->bodyLen += length; + + /* Set the next location of parsing. */ + pParsingContext->pBufferCur = pLoc + length; + + LogDebug( ( "Response parsing: Found the response body. " + "BodyLength=%d", + length, + pLoc ) ); + + return shouldContinueParse; +} + +/*-----------------------------------------------------------*/ + +static int httpParserOnMessageCompleteCallback( http_parser * pHttpParser ) +{ + HTTPParsingContext_t * pParsingContext = NULL; + + assert( pHttpParser != NULL ); + assert( pHttpParser->data != NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + + /* The response message is complete. */ + pParsingContext->state = HTTP_PARSING_COMPLETE; + + LogDebug( ( "Response parsing: Response message complete." ) ); + + return HTTP_PARSER_CONTINUE_PARSING; +} + +/*-----------------------------------------------------------*/ + +static void initializeParsingContextForFirstResponse( HTTPParsingContext_t * pParsingContext ) +{ + assert( pParsingContext != NULL ); + + /* Initialize the third-party HTTP parser to parse responses. */ + http_parser_init( &( pParsingContext->httpParser ), HTTP_RESPONSE ); + + http_parser_set_max_header_size( HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES ); + + /* No response has been parsed yet. */ + pParsingContext->state = HTTP_PARSING_NONE; + + /* No response to update is associated with this parsing context yet. */ + pParsingContext->pResponse = NULL; +} + +/*-----------------------------------------------------------*/ + +static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + assert( pHttpParser != NULL ); + + switch( ( enum http_errno ) ( pHttpParser->http_errno ) ) + { + case HPE_OK: + /* There were no errors. */ + break; + + case HPE_INVALID_EOF_STATE: + + /* In this case the parser was passed a length of zero, which indicates + * an EOF from the server, in the middle of parsing the response. + * This case is already handled by checking HTTPParsingContext_t.state. */ + break; + + case HPE_HEADER_OVERFLOW: + LogError( ( "Response parsing error: Header byte limit " + "exceeded: HeaderByteLimit=%d.", + HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES ) ); + returnStatus = HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED; + break; + + case HPE_CLOSED_CONNECTION: + LogError( ( "Response parsing error: Data received past complete " + "response with \"Connection: close\" header present." ) ); + returnStatus = HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA; + break; + + case HPE_INVALID_CHUNK_SIZE: + + /* http_parser_execute() does not give feedback on the exact failing + * character and location. */ + LogError( ( "Response parsing error: Invalid character found in " + "chunk header." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER; + break; + + case HPE_INVALID_VERSION: + + /* http_parser_execute() does not give feedback on the exact failing + * character and location. */ + LogError( ( "Response parsing error: Invalid character found in " + "HTTP protocol version." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION; + break; + + case HPE_INVALID_STATUS: + + /* There could be an invalid character or the status code number + * could be out of range. This feedback is not given back by the + * http-parser library. */ + LogError( ( "Response parsing error: Invalid Status code." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE; + break; + + case HPE_STRICT: + case HPE_INVALID_CONSTANT: + LogError( ( "Response parsing error: Invalid character found in " + "Status-Line or header delimitters." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER; + break; + + case HPE_LF_EXPECTED: + LogError( ( "Response parsing error: Expected line-feed in header " + "not found." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER; + break; + + case HPE_INVALID_HEADER_TOKEN: + + /* http_parser_execute() does not give feedback on the exact failing + * character and location. */ + LogError( ( "Response parsing error: Invalid character found in " + "headers." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER; + break; + + case HPE_INVALID_CONTENT_LENGTH: + + /* http_parser_execute() does not give feedback on the exact failing + * character and location. */ + LogError( ( "Response parsing error: Invalid character found in " + "content-length headers." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH; + break; + + case HPE_UNEXPECTED_CONTENT_LENGTH: + LogError( ( "Response parsing error: A Content-Length header was " + "found when it shouldn't have been." ) ); + returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH; + break; + + /* All other error cases cannot be triggered and indicate an error in the + * third-party parsing library if found. */ + default: + LogError( ( "Error in third-party http-parser library." ) ); + returnStatus = HTTP_PARSER_INTERNAL_ERROR; + break; + } + + /* Errors with CB_ prepending are manual returns of non-zero in the + * response parsing callbacked. */ + LogDebug( ( "http-parser errno description: %s", + http_errno_description( HTTP_PARSER_ERRNO( pHttpParser ) ) ) ); + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, + HTTPResponse_t * pResponse, + size_t parseLen, + uint8_t isHeadResponse ) +{ + HTTPStatus_t returnStatus; + http_parser_settings parserSettings = { 0 }; + size_t bytesParsed = 0u; + + assert( pParsingContext != NULL ); + assert( pResponse != NULL ); + assert( isHeadResponse <= 1 ); + + /* If this is the first time this parsing context is used, then set the + * response input. */ + if( pParsingContext->pResponse == NULL ) + { + pParsingContext->pResponse = pResponse; + pParsingContext->pBufferCur = ( const char * ) pResponse->pBuffer; + /* Set if this response is for a HEAD request. */ + pParsingContext->isHeadResponse = isHeadResponse; + + /* Initialize the status-code returned in the response. */ + pResponse->statusCode = 0u; + /* Initialize the start of the response body and length. */ + pResponse->pBody = NULL; + pResponse->bodyLen = 0u; + + /* Initialize the start of the headers, its length, and the count for + * the parsing that follows the status. */ + pResponse->pHeaders = NULL; + pResponse->headersLen = 0u; + pResponse->headerCount = 0u; + /* Initialize the response flags. */ + pResponse->flags = 0u; + } + else + { + /* This function is currently private to the HTTP Client library. It is + * therefore a development bug to have this function invoked in + * succession without the same response. */ + assert( pParsingContext->pResponse == pResponse ); + } + + /* Initialize the callbacks that http_parser_execute will invoke. */ + http_parser_settings_init( &parserSettings ); + parserSettings.on_message_begin = httpParserOnMessageBeginCallback; + parserSettings.on_status = httpParserOnStatusCallback; + parserSettings.on_header_field = httpParserOnHeaderFieldCallback; + parserSettings.on_header_value = httpParserOnHeaderValueCallback; + parserSettings.on_headers_complete = httpParserOnHeadersCompleteCallback; + parserSettings.on_body = httpParserOnBodyCallback; + parserSettings.on_message_complete = httpParserOnMessageCompleteCallback; + + /* Setting this allows the parsing context and response to be carried to + * each of the callbacks that http_parser_execute() will invoke. */ + pParsingContext->httpParser.data = pParsingContext; + + /* This will begin the parsing. Each of the callbacks set in + * parserSettings will be invoked as parts of the HTTP response are + * reached. */ + bytesParsed = http_parser_execute( &( pParsingContext->httpParser ), + &parserSettings, + pParsingContext->pBufferCur, + parseLen ); + + LogDebug( ( "Parsed HTTP Response buffer: BytesParsed=%d, ", + "ExpectedBytesParsed=%d", + bytesParsed, + parseLen ) ); + + returnStatus = processHttpParserError( &( pParsingContext->httpParser ) ); + + return returnStatus; +} + /*-----------------------------------------------------------*/ static uint8_t convertInt32ToAscii( int32_t value, @@ -345,12 +1155,7 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, pBufferCur += fieldLen; /* Copy the field separator, ": ", into the buffer. */ - - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, + ( void ) memcpy( ( char * ) pBufferCur, HTTP_HEADER_FIELD_SEPARATOR, HTTP_HEADER_FIELD_SEPARATOR_LEN ); pBufferCur += HTTP_HEADER_FIELD_SEPARATOR_LEN; @@ -360,12 +1165,7 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, pBufferCur += valueLen; /* Copy the header end indicator, "\r\n\r\n" into the buffer. */ - - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, + ( void ) memcpy( ( char * ) pBufferCur, HTTP_HEADER_END_INDICATOR, HTTP_HEADER_END_INDICATOR_LEN ); @@ -416,67 +1216,38 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, if( returnStatus == HTTP_SUCCESS ) { /* Write " HTTP/1.1\r\n" to start the HTTP header. */ - - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, pMethod, methodLen ); + ( void ) memcpy( ( char * ) pBufferCur, pMethod, methodLen ); pBufferCur += methodLen; - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); + ( void ) memcpy( ( char * ) pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); pBufferCur += SPACE_CHARACTER_LEN; /* Use "/" as default value if is NULL. */ if( ( pPath == NULL ) || ( pathLen == 0u ) ) { - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, + ( void ) memcpy( ( char * ) pBufferCur, HTTP_EMPTY_PATH, HTTP_EMPTY_PATH_LEN ); pBufferCur += HTTP_EMPTY_PATH_LEN; } else { - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, pPath, pathLen ); + ( void ) memcpy( ( char * ) pBufferCur, pPath, pathLen ); pBufferCur += pathLen; } - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, + ( void ) memcpy( ( char * ) pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); pBufferCur += SPACE_CHARACTER_LEN; - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, + ( void ) memcpy( ( char * ) pBufferCur, HTTP_PROTOCOL_VERSION, HTTP_PROTOCOL_VERSION_LEN ); pBufferCur += HTTP_PROTOCOL_VERSION_LEN; - /* MISRA rule 21.15 flags the memcpy of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ - ( void ) memcpy( pBufferCur, + ( void ) memcpy( ( char * ) pBufferCur, HTTP_HEADER_LINE_SEPARATOR, HTTP_HEADER_LINE_SEPARATOR_LEN ); pRequestHeaders->headersLen = toAddLen; @@ -790,11 +1561,10 @@ static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeade { HTTPStatus_t returnStatus = HTTP_SUCCESS; char pContentLengthValue[ MAX_INT32_NO_OF_DECIMAL_DIGITS ] = { '\0' }; - uint8_t contentLengthValueNumBytes = 0; - size_t headerLength = 0; + uint8_t contentLengthValueNumBytes = 0u; assert( pRequestHeaders != NULL ); - assert( contentLength > 0 ); + assert( contentLength > 0u ); contentLengthValueNumBytes = convertInt32ToAscii( ( int32_t ) contentLength, pContentLengthValue, @@ -824,7 +1594,6 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport uint32_t flags ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - size_t numBytesToSend = 0u; uint8_t shouldSendContentLength = 0u; assert( pTransport != NULL ); @@ -844,7 +1613,7 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport if( returnStatus == HTTP_SUCCESS ) { LogDebug( ( "Sending HTTP request headers: HeaderBytes=%d", - numBytesToSend ) ); + pRequestHeaders->headersLen ) ); /* Send the HTTP headers over the network. */ returnStatus = sendHttpData( pTransport, pRequestHeaders->pBuffer, pRequestHeaders->headersLen ); @@ -1160,17 +1929,6 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -/* The MISRA rule 8.13 violation is for using a non-const type for the - * "pHttpParser" parser instead of a const-type, because the pointed to object - * is not modified by the function. This violations is suppressed because this - * function follows the function signature of the "on_header_field" callback - * specified by the http-parser library. - * /* The MISRA directive 4.6 violation is for using primitive type int, instead of - * a typedef which denotes the signedness and size of the type. This violation - * is suppressed because this callback follows the function signature for the - * http_data_cb type in http-parser. */ -/* coverity[misra_c_2012_rule_8_13_violation] */ -/* coverity[misra_c_2012_directive_4_6_violation] */ static int findHeaderFieldParserCallback( http_parser * pHttpParser, const char * pFieldLoc, size_t fieldLen ) @@ -1188,21 +1946,11 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, assert( pContext->fieldFound == 0u ); assert( pContext->valueFound == 0u ); - /* The MISRA rule 11.5 violation flags casting a void pointer to another - * type. This rule is suppressed here because the http-parser library - * requires that private context into callbacks must be set to a void - * pointer variable. */ - /* coverity[misra_c_2012_rule_11_5_violation] */ pContext = ( findHeaderContext_t * ) pHttpParser->data; /* Check whether the parsed header matches the header we are looking for. */ - - /* MISRA rule 21.15 flags the memcmp of two different types, const uint8_t* - * and const char*. These types are the same size, so this comparision is - * acceptable. */ - /* coverity[misra_c_2012_rule_21_15_violation] */ if( ( fieldLen == pContext->fieldLen ) && - ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) + ( memcmp( pContext->pField, ( const uint8_t * ) pFieldLoc, fieldLen ) == 0 ) ) { LogDebug( ( "Found header field in response: " "HeaderName=%.*s, HeaderLocation=0x%x", @@ -1216,36 +1964,15 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /* Empty else for MISRA 15.7 compliance. */ } - /* The MISRA directive 4.6 violation is for using primitive type int, instead of - * a typedef which denotes the signedness and size of the type. This violation - * is suppressed because http-parser requires this callback to return an integer. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ return HTTP_PARSER_CONTINUE_PARSING; } /*-----------------------------------------------------------*/ -/* The coverity violation is for using a non-const type for the "pHttpParser" parser - * instead of a const-type as the pointed to object is not modified by the function. - * We suppress this violations this violation as this function follows the function - * function signature of the "on_header_value" callback specified by the http-parser - * library. */ - -/* The MISRA directive 4.6 violation is for using primitive type int, instead of - * a typedef which denotes the signedness and size of the type. This violation - * is suppressed because this callback follows the function signature for the - * http_data_cb type in http-parser. */ -/* coverity[misra_c_2012_rule_8_13_violation] */ -/* coverity[misra_c_2012_directive_4_6_violation] */ static int findHeaderValueParserCallback( http_parser * pHttpParser, const char * pVaLueLoc, size_t valueLen ) { - /* The coverity violation is for using "int" instead of a type that specifies size - * and signedness information. We suppress this violation as this variable represents - * the return value type of this callback function, whose return type is defined by - * http-parser. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ int retCode = HTTP_PARSER_CONTINUE_PARSING; findHeaderContext_t * pContext = NULL; @@ -1258,11 +1985,6 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, assert( pContext->pValueLoc != NULL ); assert( pContext->pValueLen != NULL ); - /* The MISRA rule 11.5 violation flags casting a void pointer to another - * type. This rule is suppressed here because the http-parser library - * requires that private context into callbacks must be set to a void - * pointer variable. */ - /* coverity[misra_c_2012_rule_11_5_violation] */ pContext = ( findHeaderContext_t * ) pHttpParser->data; /* The header value found flag should not be set. */ @@ -1283,11 +2005,6 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, /* As we have found the value associated with the header, we don't need * to parse the response any further. */ - - /* The MISRA directive 4.6 violation is for using primitive type int, instead of - * a typedef which denotes the signedness and size of the type. This violation - * is suppressed because http-parser requires this callback to return an integer. */ - /* coverity[misra_c_2012_directive_4_6_violation] */ retCode = HTTP_PARSER_STOP_PARSING; } else @@ -1300,18 +2017,6 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, /*-----------------------------------------------------------*/ -/* The coverity violation is for using a non-const type for the "pHttpParser" parser - * instead of a const-type as the pointed to object is not modified by the function. - * We suppress this violations this violation as this function follows the function - * function signature of the "on_headers_complete" callback specified by the http-parser - * library. */ - -/* The MISRA directive 4.6 violation is for using primitive type int, instead of - * a typedef which denotes the signedness and size of the type. This violation - * is suppressed because this callback follows the function signature for the - * http_cb type in http-parser. */ -/* coverity[misra_c_2012_rule_8_13_violation] */ -/* coverity[misra_c_2012_directive_4_6_violation] */ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) { findHeaderContext_t * pContext = NULL; @@ -1321,11 +2026,6 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) assert( pHttpParser != NULL ); - /* The MISRA rule 11.5 violation flags casting a void pointer to another - * type. This rule is suppressed here because the http-parser library - * requires that private context into callbacks must be set to a void - * pointer variable. */ - /* coverity[misra_c_2012_rule_11_5_violation] */ pContext = ( findHeaderContext_t * ) pHttpParser->data; /* If we have reached here, all headers in the response have been parsed but the requested @@ -1403,11 +2103,6 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, { /* The response buffer is invalid as only the header field was found * in the ": \r\n" format of an HTTP header. */ - - /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This - * violation is suppressed because the http-parser library directly sets - * and maps http_parser.http_errno to the enum http_errno. */ - /* coverity[misra_c_2012_rule_10_5_violation] */ LogError( ( "Unable to find header value in response: " "Response data is invalid: " "RequestedHeader=%.*s, ParserError=%s", @@ -1435,18 +2130,9 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /* If the header field-value pair is found in response, then the return * value of "on_header_value" callback (related to the header value) should * cause the http_parser.http_errno to be "CB_header_value". */ - - /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This - * violation is suppressed because the http-parser library directly sets - * and maps http_parser.http_errno to the enum http_errno. */ - /* coverity[misra_c_2012_rule_10_5_violation] */ if( ( returnStatus == HTTP_SUCCESS ) && ( parser.http_errno != HPE_CB_header_value ) ) { - /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This - * violation is suppressed because the http-parser library directly sets - * and maps http_parser.http_errno to the enum http_errno. */ - /* coverity[misra_c_2012_rule_10_5_violation] */ LogError( ( "Header found in response but http-parser returned error: " "ParserError=%s", http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); @@ -1456,18 +2142,9 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /* If header was not found, then the "on_header_complete" callback is * expected to be called which should cause the http_parser.http_errno to be * "OK" */ - - /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This - * violation is suppressed because the http-parser library directly sets - * and maps http_parser.http_errno to the enum http_errno. */ - /* coverity[misra_c_2012_rule_10_5_violation] */ else if( ( returnStatus == HTTP_HEADER_NOT_FOUND ) && ( parser.http_errno != HPE_OK ) ) { - /* MISRA rule 10.5 notes casting an unsigned integer to an enum type. This - * violation is suppressed because the http-parser library directly sets - * and maps http_parser.http_errno to the enum http_errno. */ - /* coverity[misra_c_2012_rule_10_5_violation] */ LogError( ( "Header not found in response: http-parser returned error: " "ParserError=%s", http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); diff --git a/libraries/standard/http/src/http_client_parse.c b/libraries/standard/http/src/http_client_parse.c deleted file mode 100644 index 65825f6798..0000000000 --- a/libraries/standard/http/src/http_client_parse.c +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include - -#include "private/http_client_parse.h" -#include "private/http_client_internal.h" -#include "http_parser/http_parser.h" - -HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ) -{ - /* Disable unused parameter warnings. */ - ( void ) pParsingContext; - ( void ) pHeaderParsingCallback; - /* This function is to be implenmented. */ - return HTTP_SUCCESS; -} - -HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen ) -{ - /* Disable unused parameter warnings. */ - ( void ) pBuffer; - ( void ) bufferLen; - - /* This function is to be implemented. For now we return success. */ - pParsingContext->state = HTTP_PARSING_COMPLETE; - return HTTP_SUCCESS; -} diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index c0c3177e08..20e13fd4b6 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -128,6 +128,16 @@ */ #define HTTP_MINIMUM_REQUEST_LINE_LENGTH 16u +/** + * @brief The state of the response message parsed after function + * #parseHttpResponse returns. + */ +typedef enum HTTPParsingState_t +{ + HTTP_PARSING_NONE = 0, /**< The parser has not started reading any response. */ + HTTP_PARSING_INCOMPLETE, /**< The parser found a partial reponse. */ + HTTP_PARSING_COMPLETE /**< The parser found the entire response. */ +} HTTPParsingState_t; /** * @brief An aggregator that represents the user-provided parameters to the @@ -144,4 +154,74 @@ typedef struct findHeaderContext uint8_t valueFound; /**< Indicates that the header value was found during parsing. */ } findHeaderContext_t; +/** + * @brief The HTTP response parsing context for a response fresh from the + * server. This context is passed into the http-parser registered callbacks. + * The registered callbacks are private functions of the form + * httpParserXXXXCallbacks(). + * + * The transitions of the httpParserXXXXCallback() functions are shown belown. + * The XXXX is replaced by the strings in the state boxes: + * + * +---------------------+ + * |onMessageBegin | + * +--------+------------+ + * | + * | + * | + * v + * +--------+------------+ + * |onStatus | + * +--------+------------+ + * | + * | + * | + * v + * +--------+------------+ + * |onHeaderField +<---+ + * +--------+------------+ | + * | | + * | |(More headers) + * | | + * v | + * +--------+------------+ | + * |onHeaderValue +----^ + * +--------+------------+ + * | + * | + * | + * v + * +--------+------------+ + * |onHeadersComplete | + * +---------------------+ + * | + * | + * | + * v + * +--------+------------+ + * |onBody +<---+ + * +--------+--------+---+ | + * | | |(Transfer-encoding chunked body) + * | | | + * | +--------+ + * | + * v + * +--------+------------+ + * |onMessageComplete | + * +---------------------+ + */ +typedef struct HTTPParsingContext +{ + http_parser httpParser; /**< Third-party http-parser context. */ + HTTPParsingState_t state; /**< The current state of the HTTP response parsed. */ + HTTPResponse_t * pResponse; /**< HTTP response associated with this parsing context. */ + uint8_t isHeadResponse; /**< HTTP response is for a HEAD request. */ + + const char * pBufferCur; /**< The current location of the parser in the response buffer. */ + const char * pLastHeaderField; /**< Holds the last part of the header field parsed. */ + size_t lastHeaderFieldLen; /**< The length of the last header field parsed. */ + const char * pLastHeaderValue; /**< Holds the last part of the header value parsed. */ + size_t lastHeaderValueLen; /**< The length of the last value field parsed. */ +} HTTPParsingContext_t; + #endif /* ifndef HTTP_CLIENT_INTERNAL_H_ */ diff --git a/libraries/standard/http/src/private/http_client_parse.h b/libraries/standard/http/src/private/http_client_parse.h deleted file mode 100644 index 4e45991386..0000000000 --- a/libraries/standard/http/src/private/http_client_parse.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef HTTP_CLIENT_PARSE_H_ -#define HTTP_CLIENT_PARSE_H_ - -#include "http_client.h" - -/** - * @brief The state of the response message parsed after - * #HTTPClient_ParseResponse returns. - */ -typedef enum HTTPParsingState_t -{ - HTTP_PARSING_NONE = 0, /**< The parser has not started reading any response. */ - HTTP_PARSING_INCOMPLETE, /**< The parser found a partial reponse. */ - HTTP_PARSING_COMPLETE /**< The parser found the entire response. */ -} HTTPParsingState_t; - -/** - * @brief The HTTP response parsing context. - */ -typedef struct HTTPParsingContext -{ - /* http-parser dependencies will be added in the next incremental change. */ - /* Below will be un-commented when parsing is implemented. */ - #if 0 - http_parser httpParser; /**< Third-party http-parser context. */ - #endif - HTTPParsingState_t state; /**< The current state of the HTTP response parsed. */ - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback; /**< Callback to invoke with each header found. */ -} HTTPParsingContext_t; - -/** - * @brief Initialize the HTTP Client response parsing context. - * - * @param[in,out] pParsingContext The parsing context to initialize. - * @param[in] pHeaderParsingCallback Callback that will be invoked for each - * header and value pair found. - * - * @return One of the following: - * - #HTTP_SUCCESS - * - #HTTP_INVALID_PARAMETER - * TODO: Other return values will be added during implementation of the parsing. - */ -HTTPStatus_t HTTPClient_InitializeParsingContext( HTTPParsingContext_t * pParsingContext, - HTTPClient_HeaderParsingCallback_t * pHeaderParsingCallback ); - -/** - * Parse the input HTTP response buffer. - * - * This function may be invoked multiple times for different parts of the the - * HTTP response. The state of what was last parsed in the response is kept in - * #HTTPParsingContext_t. - * - * The application should close the connection with the server if any - * HTTP_SECURITY_ALERT_X errors are returned. - * TODO: List all the security alerts possible after parsing development. - * - * @param[in,out] pParsingState The state of the response parsing. - * @param[in] pBuffer The buffer containing response message to parse. - * @param[in] bufferLen The length in the buffer to parse. - * - * @return One of the following: - * - #HTTP_SUCCESS - * - #HTTP_INVALID_PARAMETER - * - #HTTP_SECURITY_ALERT_PARSER_INVALID_CHARACTER - * TODO: Other return values are to be added during implementation of parsing. - */ -HTTPStatus_t HTTPClient_ParseResponse( HTTPParsingContext_t * pParsingContext, - const uint8_t * pBuffer, - size_t bufferLen ); - -#endif /* ifndef HTTP_CLIENT_PARSE_H_ */ diff --git a/libraries/standard/http/test/Makefile b/libraries/standard/http/test/Makefile deleted file mode 100644 index f45f7a998f..0000000000 --- a/libraries/standard/http/test/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -CSDK_ROOT = ../../../.. - -# the path to be indexed (default: ..) -# Directory indexing includes subdirectories. -SOURCE = ../src - -# Show extra warnings. -CFLAGS += -Wextra -O0 -ggdb - -# arguments given to the compiler (default: see the included file) -#CFLAGS = std=c99 -Wall -fprofile-arcs -ftest-coverage -O0 -ggdb - -# include directories given to the compiler (default: none) -INCLUDE = . \ - ../include \ - ../src \ - $(CSDK_ROOT)/platform/include \ - ../../utilities/include \ - ../third_party - -# the default goal (default: help) -# Set to build or test as desired. -.DEFAULT_GOAL := test - -# names of unit test programs -# Values based on test source file names will be dynamically added. -#TESTS = - -# functions to dump into separate source files -# Values based on test source file names will be dynamically added. -FUNCTIONS = addHeader \ - writeRequestLine \ - convertInt32ToAscii \ - sendHttpHeaders \ - sendHttpBody \ - receiveHttpResponse \ - receiveAndParseHttpResponse \ - HTTPClient_InitializeParsingContext \ - HTTPClient_ParseResponse \ - getFinalResponseStatus \ - HTTPClient_strerror \ - findHeaderFieldParserCallback \ - findHeaderValueParserCallback \ - findHeaderOnHeaderCompleteCallback \ - findHeaderInResponse - -# Changes to the above variables should remain above this include. -include Mock4thewin.mk - -# additional dependency for a specific test -HTTPClient_AddHeader.c: addHeader.c -HTTPClient_AddRangeHeader.c: addHeader.c convertInt32ToAscii.c -HTTPClient_Send.c: sendHttpHeaders.c \ - sendHttpBody.c \ - receiveHttpResponse.c \ - receiveAndParseHttpResponse.c \ - HTTPClient_InitializeParsingContext.c \ - HTTPClient_ParseResponse.c \ - getFinalResponseStatus.c -HTTPClient_InitializeRequestHeaders.c: writeRequestLine.c addHeader.c -HTTPClient_ReadHeader.c: HTTPClient_strerror.c \ - findHeaderFieldParserCallback.c \ - findHeaderValueParserCallback.c \ - findHeaderOnHeaderCompleteCallback.c \ - findHeaderInResponse.c - -# additional header dependencies for all tests -COMMON = common.h diff --git a/libraries/standard/http/test/README.md b/libraries/standard/http/test/README.md deleted file mode 100644 index acb36b246a..0000000000 --- a/libraries/standard/http/test/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Instructions for running the tests: - -1. In your GNU compatible environment like WSL, mingw, Linux OS, MAC OS, etc., open a terminal. -1. To get all the required installation scripts, clone the LATEST from this repo: https://github.com/dan4thewin/mock4thewin. -1. Go to the repo directory: `cd \{mock4thewin repo}` -1. Run this command to install: `sudo ./install /usr/local` -1. To get another required tool, clone this repo https://github.com/dan4thewin/ctags-xref and follow the instructions in the root README.md. -1. Go back to the HTTP Client test folder: `cd {CSDK_ROOT}/libraries/standard/http/test` -1. Run this command to build and run the tests: `make test` -1. This framework automatically generates test-\[function_name\] function_name.c files for ease in mocking. Also auto-generated are *gcov* related files for coverage information. - -# Writing Unit Tests -1. Make a file under {CSDK_ROOT}/libraries/standard/http/test named "test-\[function_name\].c" - For example {CSDK_ROOT}/libraries/standard/http/test/test-HTTPClient_AddHeader.c -1. See https://gist.github.com/dan4thewin/6f708bf635f6cb647d9f7bc7f55e4706#the-complete-unit-test for an example unit tests and how to test assert. -1. Any shared mocked external functions should go into "common.h". -1. Build, run, and get coverage with: `make test` diff --git a/libraries/standard/http/test/common.h b/libraries/standard/http/test/common.h deleted file mode 100644 index 503aaa17ce..0000000000 --- a/libraries/standard/http/test/common.h +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "tap.h" - -/* Include paths for public enums, structures, and macros. */ -#include "http_client.h" - -/* Private includes for internal macros. */ -#include "private/http_client_internal.h" -#include "private/http_client_parse.h" - -static int _assertFailureCount; -#define assertReset() do { _assertFailureCount = 0; } while( 0 ) -#define assert( x ) do { if( !( x ) ) { _assertFailureCount++; } } while( 0 ) diff --git a/libraries/standard/http/test/config.h b/libraries/standard/http/test/config.h deleted file mode 100644 index 8349549bed..0000000000 --- a/libraries/standard/http/test/config.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#define LOG_LEVEL_HTTP LOG_DEBUG - -#ifdef USE_CSDK_LOGGING - -/* Include file for POSIX reference implementation. */ - #include "logging.h" - -/* Define the IotLog logging interface to enable logging. - * This demo maps the macro to the reference POSIX implementation for logging. - * Note: @ref LIBRARY_LOG_NAME adds the name of the library, that produces the - * log, as metadata in each log message. */ - #define SdkLog( messageLevel, pFormat, ... ) \ - Log_Generic( messageLevel, \ - "[%s:%d] [%s] "pFormat, \ - __FILE__, \ - __LINE__, \ - LIBRARY_LOG_NAME, \ - __VA_ARGS__ ) - -#endif /* ifdef USE_CSDK_LOGGING */ - -#endif /* ifndef CONFIG_H */ diff --git a/libraries/standard/http/test/http_config.h b/libraries/standard/http/test/http_config.h deleted file mode 100644 index 874207e8f8..0000000000 --- a/libraries/standard/http/test/http_config.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef HTTP_CONFIG_H__ -#define HTTP_CONFIG_H__ - -/**************************************************/ -/******* DO NOT CHANGE the following order ********/ -/**************************************************/ - -/* Logging related header files are required to be included in the following order: - * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". - */ - -/* Include header that defines log levels. */ -#include "logging_levels.h" - -/* Configure name and log level for the HTTP library. */ -#define LIBRARY_LOG_NAME "HTTP" -#define LIBRARY_LOG_LEVEL LOG_INFO - -#include "logging_stack.h" - -/************ End of logging configuration ****************/ - -#endif /* ifndef HTTP_CONFIG_H__ */ diff --git a/libraries/standard/http/test/test-HTTPClient_AddHeader.c b/libraries/standard/http/test/test-HTTPClient_AddHeader.c deleted file mode 100644 index f58dbdb651..0000000000 --- a/libraries/standard/http/test/test-HTTPClient_AddHeader.c +++ /dev/null @@ -1,253 +0,0 @@ -#include - -#include "common.h" - -/* Functions are pulled out into their own C files to be tested as a unit. */ -#include "addHeader.c" -#include "HTTPClient_AddHeader.c" - -/* Template HTTP header fields and values. */ -#define HTTP_TEST_HEADER_FIELD "Authorization" -#define HTTP_TEST_HEADER_FIELD_LEN ( sizeof( HTTP_TEST_HEADER_FIELD ) - 1 ) -#define HTTP_TEST_HEADER_VALUE "None" -#define HTTP_TEST_HEADER_VALUE_LEN ( sizeof( HTTP_TEST_HEADER_VALUE ) - 1 ) -/* Template for first line of HTTP header. */ -#define HTTP_TEST_HEADER_REQUEST_LINE "GET / HTTP/1.1 \r\n" -#define HTTP_TEST_HEADER_REQUEST_LINE_LEN ( sizeof( HTTP_TEST_HEADER_REQUEST_LINE ) - 1 ) -#define HTTP_REQUEST_HEADERS_INITIALIZER { 0 } -/* Template for snprintf(...) strings. */ -#define HTTP_TEST_SINGLE_HEADER_FORMAT "%s%s: %s\r\n\r\n" -#define HTTP_TEST_DOUBLE_HEADER_FORMAT "%s%s: %s\r\n%s: %s\r\n\r\n" - -/* Length of the following template HTTP header. - * \r\n - * : \r\n - * \r\n - * This is used to initialize the expectedHeader string. */ -#define HTTP_TEST_TEMPLATE_HEADER_LEN \ - ( HTTP_TEST_HEADER_REQUEST_LINE_LEN + \ - HTTP_TEST_HEADER_FIELD_LEN + \ - HTTP_HEADER_FIELD_SEPARATOR_LEN + \ - HTTP_TEST_HEADER_VALUE_LEN + \ - HTTP_HEADER_LINE_SEPARATOR_LEN + \ - HTTP_HEADER_LINE_SEPARATOR_LEN ) - -/* The longest possible header used for these unit tests. */ -#define HTTP_TEST_MAX_HEADER_LEN \ - ( HTTP_TEST_TEMPLATE_HEADER_LEN + \ - HTTP_TEST_HEADER_FIELD_LEN + \ - HTTP_HEADER_FIELD_SEPARATOR_LEN + \ - HTTP_TEST_HEADER_VALUE_LEN + \ - HTTP_HEADER_LINE_SEPARATOR_LEN ) - -/* Add 1 because snprintf(...) writes a null byte at the end. */ -#define HTTP_TEST_BUFFER_SIZE ( HTTP_TEST_MAX_HEADER_LEN + 1 ) - -int main() -{ - HTTPRequestHeaders_t reqHeaders = HTTP_REQUEST_HEADERS_INITIALIZER; - HTTPRequestHeaders_t reqHeadersDflt = HTTP_REQUEST_HEADERS_INITIALIZER; - HTTPStatus_t test_err = HTTP_NOT_SUPPORTED; - uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; - char expectedHeader[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; - size_t expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; - struct Header - { - uint8_t field[ HTTP_TEST_HEADER_FIELD_LEN ]; - size_t fieldLen; - uint8_t value[ HTTP_TEST_HEADER_VALUE_LEN ]; - size_t valueLen; - } - header; - -/* Write template header field and value to a struct to pass as - * parameters to HTTPClient_AddHeader() method. */ -#define fillHeaderStructTemplate() \ - do { \ - memcpy( header.field, \ - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN ); \ - header.fieldLen = HTTP_TEST_HEADER_FIELD_LEN; \ - memcpy( header.value, \ - HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); \ - header.valueLen = HTTP_TEST_HEADER_VALUE_LEN; \ - } \ - while( 0 ) - -#define reset() \ - do { \ - test_err = HTTP_NOT_SUPPORTED; \ - reqHeaders = reqHeadersDflt; \ - memset( buffer, 0, HTTP_TEST_BUFFER_SIZE ); \ - memset( expectedHeader, 0, HTTP_TEST_TEMPLATE_HEADER_LEN ); \ - memset( header.field, 0, HTTP_TEST_BUFFER_SIZE ); \ - header.fieldLen = 0; \ - memset( header.value, 0, HTTP_TEST_BUFFER_SIZE ); \ - header.valueLen = 0; \ - fillHeaderStructTemplate(); \ - reqHeaders.pBuffer = buffer; \ - } \ - while( 0 ) - - plan( 23 ); - - /* Happy Path testing. Prefill the user buffer with HTTP_TEST_HEADER_REQUEST_LINE - * and call HTTPClient_AddHeader using the field and value in the header struct. */ - reset(); - /* Add 1 because snprintf(...) writes a null byte at the end. */ - ok( snprintf( expectedHeader, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, - HTTP_TEST_SINGLE_HEADER_FORMAT, - HTTP_TEST_HEADER_REQUEST_LINE, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == HTTP_TEST_TEMPLATE_HEADER_LEN ); - expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; - /* Set parameters for reqHeaders. */ - ok( snprintf( ( char * ) reqHeaders.pBuffer, - HTTP_TEST_HEADER_REQUEST_LINE_LEN + 1, - HTTP_TEST_HEADER_REQUEST_LINE ) - == HTTP_TEST_HEADER_REQUEST_LINE_LEN ); - reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; - reqHeaders.headersLen = HTTP_TEST_HEADER_REQUEST_LINE_LEN; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - header.value, header.valueLen ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - expectedHeader, expectedHeaderLen ) == 0 ); - ok( reqHeaders.headersLen == expectedHeaderLen ); - ok( test_err == HTTP_SUCCESS ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test adding extra header with insufficient memory. */ - ok( snprintf( expectedHeader, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, - HTTP_TEST_SINGLE_HEADER_FORMAT, - HTTP_TEST_HEADER_REQUEST_LINE, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == HTTP_TEST_TEMPLATE_HEADER_LEN ); - expectedHeaderLen = HTTP_TEST_TEMPLATE_HEADER_LEN; - /* Prefill the buffer with a request line and header. */ - ok( snprintf( ( char * ) reqHeaders.pBuffer, HTTP_TEST_TEMPLATE_HEADER_LEN + 1, - HTTP_TEST_SINGLE_HEADER_FORMAT, - HTTP_TEST_HEADER_REQUEST_LINE, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == HTTP_TEST_TEMPLATE_HEADER_LEN ); - reqHeaders.headersLen = HTTP_TEST_TEMPLATE_HEADER_LEN; - reqHeaders.bufferLen = reqHeaders.headersLen; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - header.value, header.valueLen ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - expectedHeader, expectedHeaderLen ) == 0 ); - ok( reqHeaders.headersLen == expectedHeaderLen ); - ok( test_err == HTTP_INSUFFICIENT_MEMORY ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test adding extra header with sufficient memory. */ - expectedHeaderLen = HTTP_TEST_MAX_HEADER_LEN; - ok( snprintf( expectedHeader, expectedHeaderLen + 1, - HTTP_TEST_DOUBLE_HEADER_FORMAT, - HTTP_TEST_HEADER_REQUEST_LINE, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == ( int ) expectedHeaderLen ); - /* Prefill the buffer with a request line and header. */ - ok( snprintf( ( char * ) reqHeaders.pBuffer, - HTTP_TEST_TEMPLATE_HEADER_LEN + 1, - HTTP_TEST_SINGLE_HEADER_FORMAT, - HTTP_TEST_HEADER_REQUEST_LINE, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ) - == HTTP_TEST_TEMPLATE_HEADER_LEN ); - reqHeaders.headersLen = HTTP_TEST_TEMPLATE_HEADER_LEN; - reqHeaders.bufferLen = expectedHeaderLen; - - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - header.value, header.valueLen ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - expectedHeader, expectedHeaderLen ) == 0 ); - ok( reqHeaders.headersLen == expectedHeaderLen ); - ok( test_err == HTTP_SUCCESS ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a NULL request headers interface. */ - test_err = HTTPClient_AddHeader( NULL, - header.field, header.fieldLen, - header.value, header.valueLen ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a NULL pBuffer member of request headers. */ - reqHeaders.pBuffer = NULL; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - header.value, header.valueLen ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test NULL header field. */ - reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; - test_err = HTTPClient_AddHeader( &reqHeaders, - NULL, header.fieldLen, - header.value, header.valueLen ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test NULL header value. */ - reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - NULL, header.valueLen ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test that fieldLen > 0. */ - reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, 0, - header.value, header.valueLen ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test that valueLen > 0. */ - reqHeaders.bufferLen = HTTP_TEST_MAX_HEADER_LEN; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - header.value, 0 ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test HTTP_INSUFFICIENT_MEMORY error from having buffer size less than - * what is required to fit HTTP headers. */ - ok( snprintf( ( char * ) buffer, - HTTP_TEST_HEADER_REQUEST_LINE_LEN + 1, - HTTP_TEST_HEADER_REQUEST_LINE ) - == HTTP_TEST_HEADER_REQUEST_LINE_LEN ); - reqHeaders.headersLen = HTTP_TEST_HEADER_REQUEST_LINE_LEN; - reqHeaders.pBuffer = buffer; - reqHeaders.bufferLen = HTTP_TEST_TEMPLATE_HEADER_LEN - 1; - test_err = HTTPClient_AddHeader( &reqHeaders, - header.field, header.fieldLen, - header.value, header.valueLen ); - ok( test_err == HTTP_INSUFFICIENT_MEMORY ); - reset(); - - /* -----------------------------------------------------------------------*/ - - return grade(); -} diff --git a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c b/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c deleted file mode 100644 index d9cfeb5198..0000000000 --- a/libraries/standard/http/test/test-HTTPClient_AddRangeHeader.c +++ /dev/null @@ -1,336 +0,0 @@ -#include -#include - -#include "common.h" - -/* Functions are pulled out into their own C files to be tested as a unit. */ -#include "addHeader.c" -#include "convertInt32ToAscii.c" -#include "HTTPClient_AddRangeHeader.c" - -/* Default size for request buffer. */ -#define HTTP_TEST_BUFFER_SIZE ( 100 ) - -/* Headers data with "\r\n\r\n" terminator to be pre-populated in buffer before - * call to AddRangeHeader(). */ -#define PREEXISTING_HEADER_DATA "POST / HTTP/1.1 \r\nAuthorization: None\r\n\r\n" -#define PREEXISTING_HEADER_DATA_LEN ( sizeof( PREEXISTING_HEADER_DATA ) - 1 ) - -/* Headers data without "\r\n\r\n" terminator to be pre-populated in buffer before - * call to AddRangeHeader(). */ -#define PREEXISTING_REQUEST_LINE "POST / HTTP/1.1 \r\n" -#define PREEXISTING_REQUEST_LINE_LEN ( sizeof( PREEXISTING_REQUEST_LINE ) - 1 ) - - -/* Range Request data that is common for all range specification types. */ -#define RANGE_REQUEST_HEADER_DATA_PREFIX "Range: bytes=" -#define RANGE_REQUEST_HEADER_DATA_PREFIX_LEN ( sizeof( RANGE_REQUEST_HEADER_DATA_PREFIX ) - 1 ) - -/* The range end value for representing end of file byte. */ -#define RANGE_REQUEST_END_OF_FILE -1 - -/* Type to store expected headers data. */ -typedef struct _headers -{ - uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ]; - size_t dataLen; -} _headers_t; - -#define setupBuffersWithPreexistingHeader( requestHeaders, \ - testBuffer, \ - expectedHeaders, \ - preexistingData ) \ - do { \ - size_t dataLen = strlen( preexistingData ); \ - requestHeaders.pBuffer = testBuffer; \ - requestHeaders.bufferLen = sizeof( testBuffer ); \ - /* We add 1 bytes as snprintf() writes a null byte at the end. */ \ - int numBytes = snprintf( ( char * ) requestHeaders.pBuffer, \ - dataLen + 1, \ - "%s", \ - preexistingData ); \ - ok( numBytes == ( int ) dataLen ); \ - requestHeaders.headersLen = dataLen; \ - /* Fill the same data in the expected buffer as HTTPClient_AddRangeHeaders() - * is not expected to change it. */ \ - ok( memcpy( expectedHeaders.buffer, requestHeaders.pBuffer, \ - requestHeaders.headersLen ) \ - == expectedHeaders.buffer ); \ - expectedHeaders.dataLen = requestHeaders.headersLen; \ - } while( 0 ) - - -#define addRangeToExpectedHeaders( expectedHeaders, expectedRange, terminatorExists ) \ - do { \ - size_t rangeRequestLen = RANGE_REQUEST_HEADER_DATA_PREFIX_LEN + \ - strlen( expectedRange ) + \ - 2 * HTTP_HEADER_LINE_SEPARATOR_LEN; \ - int numBytes = \ - snprintf( ( char * ) expectedHeaders.buffer + \ - expectedHeaders.dataLen - \ - ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ), \ - /* We add 1 bytes as snprintf() writes a null byte at the end. */ \ - rangeRequestLen + 1, \ - "%s%s\r\n\r\n", \ - RANGE_REQUEST_HEADER_DATA_PREFIX, \ - expectedRange ); \ - ok( ( size_t ) numBytes == rangeRequestLen ); \ - expectedHeaders.dataLen += rangeRequestLen - \ - ( terminatorExists ? HTTP_HEADER_LINE_SEPARATOR_LEN : 0 ); \ - } while( 0 ) - - -int main() -{ - HTTPRequestHeaders_t testHeaders = { 0 }; - HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; - uint8_t testBuffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; - _headers_t expectedHeaders = { 0 }; - int testRangeStart = 0; - int testRangeEnd = 0; - -#define reset() \ - do { \ - retCode = HTTP_NOT_SUPPORTED; \ - memset( &testHeaders, 0, sizeof( testHeaders ) ); \ - memset( testBuffer, 0, sizeof( testBuffer ) ); \ - memset( &expectedHeaders, 0, sizeof( expectedHeaders ) ); \ - } while( 0 ) - - plan( 62 ); - - /*************************** Test happy path. *****************************/ - - /* Case 1: Headers buffer contains header data ending with "\r\n\r\n". */ - - /* Range specification of the form [rangeStart, rangeEnd]. */ - /* Test with 0 as the range values */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - testRangeStart = 0; - testRangeEnd = 0; - addRangeToExpectedHeaders( expectedHeaders, - "0-0" /*expected range*/, - true ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - /* Test for [0, eof) range */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - testRangeStart = 0; - testRangeEnd = RANGE_REQUEST_END_OF_FILE; - addRangeToExpectedHeaders( expectedHeaders, - "0-" /*expected range*/, - true ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - testRangeStart = 10; - testRangeEnd = 100; - addRangeToExpectedHeaders( expectedHeaders, - "10-100" /*expected range*/, - true ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - /* Range specification of the form [rangeStart,) - * i.e. for all bytes >= rangeStart. */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - testRangeStart = 100; - testRangeEnd = RANGE_REQUEST_END_OF_FILE; - addRangeToExpectedHeaders( expectedHeaders, - "100-" /*expected range*/, - true ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - /* Range specification for the last N bytes. */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - testRangeStart = -50; - testRangeEnd = RANGE_REQUEST_END_OF_FILE; - addRangeToExpectedHeaders( expectedHeaders, - "-50" /*expected range*/, - true ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - /* Test with LARGE range values. */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - testRangeStart = INT32_MAX; - testRangeEnd = INT32_MAX; - addRangeToExpectedHeaders( expectedHeaders, - "2147483647-2147483647" /*expected range*/, - true ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - /* Case 2: Headers buffer does not contain data with trailing "\r\n\r\n". */ - - /* Range specification of the form [rangeStart, rangeEnd]. */ - /* Test with 0 as the range values */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_REQUEST_LINE ); - testRangeStart = 0; - testRangeEnd = 0; - addRangeToExpectedHeaders( expectedHeaders, - "0-0" /*expected range*/, - false ); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_SUCCESS ); - /* Verify the the Range Request header data. */ - ok( testHeaders.headersLen == expectedHeaders.dataLen ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, expectedHeaders.dataLen ) - == 0 ); - /* Verify that the bufferLen data was not tampered with. */ - ok( testHeaders.bufferLen == sizeof( testBuffer ) ); - - /*************************** Test Failure Cases *****************************/ - - /* Request header parameter is NULL. */ - reset(); - retCode = HTTPClient_AddRangeHeader( NULL, - 0 /* rangeStart */, - 0 /* rageEnd */ ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Underlying buffer is NULL in request headers. */ - reset(); - retCode = HTTPClient_AddRangeHeader( &testHeaders, - 0 /* rangeStart */, - 0 /* rageEnd */ ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Request Header Size is zero. */ - reset(); - testHeaders.pBuffer = &testBuffer[ 0 ]; - retCode = HTTPClient_AddRangeHeader( &testHeaders, - 0 /* rangeStart */, - 0 /* rageEnd */ ); - ok( retCode == HTTP_INSUFFICIENT_MEMORY ); - - /* Test incorrect combinations of rangeStart and rangeEnd. */ - - /* rangeStart > rangeEnd */ - reset(); - testHeaders.pBuffer = &testBuffer[ 0 ]; - retCode = HTTPClient_AddRangeHeader( &testHeaders, - 10 /* rangeStart */, - 5 /* rageEnd */ ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* rangeStart is negative but rangeStart is non-End of File. */ - reset(); - testHeaders.pBuffer = &testBuffer[ 0 ]; - retCode = HTTPClient_AddRangeHeader( &testHeaders, - -10 /* rangeStart */, - RANGE_REQUEST_END_OF_FILE + 1 /* rageEnd */ ); - ok( retCode == HTTP_INVALID_PARAMETER ); - reset(); - testHeaders.pBuffer = &testBuffer[ 0 ]; - retCode = HTTPClient_AddRangeHeader( &testHeaders, - -50 /* rangeStart */, - -10 /* rageEnd */ ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Test Insufficient memory failure when the buffer has one less byte than required. */ - reset(); - setupBuffersWithPreexistingHeader( testHeaders, testBuffer, - expectedHeaders, - PREEXISTING_HEADER_DATA ); - size_t preHeadersLen = testHeaders.headersLen; - testRangeStart = 5; - testRangeEnd = 10; - addRangeToExpectedHeaders( expectedHeaders, - "5-10" /*expected range*/, - true ); - - /* Update headers buffer size to be one byte short of required size to add - * Range Request header. */ - testHeaders.bufferLen = expectedHeaders.dataLen - 1; - - /* Re-write the expected headers buffer to store a copy of the test headers - * to use for verification later. */ - memcpy( expectedHeaders.buffer, testHeaders.pBuffer, testHeaders.bufferLen ); - - retCode = HTTPClient_AddRangeHeader( &testHeaders, - testRangeStart, - testRangeEnd ); - ok( retCode == HTTP_INSUFFICIENT_MEMORY ); - /* Verify the headers input parameter is unaltered. */ - ok( testHeaders.headersLen == preHeadersLen ); - ok( testHeaders.bufferLen == expectedHeaders.dataLen - 1 ); - ok( memcmp( testHeaders.pBuffer, expectedHeaders.buffer, testHeaders.bufferLen ) - == 0 ); - - return grade(); -} diff --git a/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c b/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c deleted file mode 100644 index 398dc7776f..0000000000 --- a/libraries/standard/http/test/test-HTTPClient_InitializeRequestHeaders.c +++ /dev/null @@ -1,220 +0,0 @@ -#include - -#include "common.h" - -/* Functions are pulled out into their own C files to be tested as a unit. */ -#include "writeRequestLine.c" -#include "addHeader.c" -#include "HTTPClient_InitializeRequestHeaders.c" - -#define HTTP_TEST_REQUEST_METHOD "GET" -#define HTTP_TEST_REQUEST_METHOD_LEN ( sizeof( HTTP_TEST_REQUEST_METHOD ) - 1 ) -#define HTTP_TEST_REQUEST_PATH "/robots.txt" -#define HTTP_TEST_REQUEST_PATH_LEN ( sizeof( HTTP_TEST_REQUEST_PATH ) - 1 ) -#define HTTP_TEST_HOST_VALUE "amazon.com" -#define HTTP_TEST_HOST_VALUE_LEN ( sizeof( HTTP_TEST_HOST_VALUE ) - 1 ) -#define HTTP_TEST_REQUEST_LINE \ - ( HTTP_TEST_REQUEST_METHOD " " \ - HTTP_TEST_REQUEST_PATH " " \ - HTTP_PROTOCOL_VERSION "\r\n" ) -#define HTTP_TEST_REQUEST_LINE_LEN ( sizeof( HTTP_TEST_REQUEST_LINE ) - 1 ) - -/* Used for format parameter in snprintf(...). */ -#define HTTP_TEST_HEADER_FORMAT \ - "%s %s %s\r\n" \ - "%s: %s\r\n" \ - "%s: %s\r\n" \ - "%s: %s\r\n\r\n" - -#define HTTP_REQUEST_HEADERS_INITIALIZER { 0 } -#define HTTP_REQUEST_INFO_INITIALIZER { 0 } - -/* Length of the following template HTTP header. - * \r\n - * : \r\n - * : \r\n - * : \r\n - * \r\n - * This is used to initialize the expectedHeader string. Note the missing - * . This is added later on depending on the - * value of HTTP_REQUEST_KEEP_ALIVE_FLAG in reqInfo->flags. */ -#define HTTP_TEST_PREFIX_HEADER_LEN \ - ( HTTP_TEST_REQUEST_METHOD_LEN + SPACE_CHARACTER_LEN + \ - HTTP_TEST_REQUEST_PATH_LEN + SPACE_CHARACTER_LEN + \ - HTTP_PROTOCOL_VERSION_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ - HTTP_USER_AGENT_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ - HTTP_USER_AGENT_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ - HTTP_HOST_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ - HTTP_TEST_HOST_VALUE_LEN + HTTP_HEADER_LINE_SEPARATOR_LEN + \ - HTTP_CONNECTION_FIELD_LEN + HTTP_HEADER_FIELD_SEPARATOR_LEN + \ - HTTP_HEADER_LINE_SEPARATOR_LEN + \ - HTTP_HEADER_LINE_SEPARATOR_LEN ) - -/* Add HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN to account for longest possible - * length of template header. */ -#define HTTP_TEST_MAX_HEADER_LEN \ - ( HTTP_TEST_PREFIX_HEADER_LEN + HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ) - -/* Add 1 because snprintf(...) writes a null byte at the end. */ -#define HTTP_TEST_BUFFER_SIZE ( HTTP_TEST_MAX_HEADER_LEN + 1 ) - - -int main() -{ - HTTPRequestHeaders_t reqHeaders = HTTP_REQUEST_HEADERS_INITIALIZER; - HTTPRequestHeaders_t reqHeadersDflt = HTTP_REQUEST_HEADERS_INITIALIZER; - HTTPRequestInfo_t reqInfo = HTTP_REQUEST_INFO_INITIALIZER; - HTTPRequestInfo_t reqInfoDflt = HTTP_REQUEST_INFO_INITIALIZER; - HTTPStatus_t test_err = HTTP_NOT_SUPPORTED; - uint8_t buffer[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; - char expectedHeader[ HTTP_TEST_BUFFER_SIZE ] = { 0 }; - size_t expectedHeaderLen = HTTP_TEST_MAX_HEADER_LEN; - -/* Write template reqInfo to pass as parameter to - * HTTPClient_InitializeRequestHeaders() method. */ -#define fillReqInfoTemplate() \ - do { \ - reqInfo.method = HTTP_TEST_REQUEST_METHOD; \ - reqInfo.methodLen = HTTP_TEST_REQUEST_METHOD_LEN; \ - reqInfo.pPath = HTTP_TEST_REQUEST_PATH; \ - reqInfo.pathLen = HTTP_TEST_REQUEST_PATH_LEN; \ - reqInfo.pHost = HTTP_TEST_HOST_VALUE; \ - reqInfo.hostLen = HTTP_TEST_HOST_VALUE_LEN; \ - reqInfo.flags = 0; \ - } \ - while( 0 ) - -#define reset() \ - do { \ - test_err = HTTP_NOT_SUPPORTED; \ - reqHeaders = reqHeadersDflt; \ - reqInfo = reqInfoDflt; \ - memset( buffer, 0, HTTP_TEST_BUFFER_SIZE ); \ - memset( expectedHeader, 0, HTTP_TEST_MAX_HEADER_LEN + 1 ); \ - } \ - while( 0 ) - - plan( 22 ); - - /* Happy Path testing. */ - reset(); - expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN + \ - HTTP_CONNECTION_CLOSE_VALUE_LEN; - ok( snprintf( expectedHeader, expectedHeaderLen + 1, - HTTP_TEST_HEADER_FORMAT, - HTTP_TEST_REQUEST_METHOD, HTTP_TEST_REQUEST_PATH, HTTP_PROTOCOL_VERSION, - HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, - HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, - HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ) - == ( int ) expectedHeaderLen ); - /* Set parameters for reqHeaders. */ - reqHeaders.pBuffer = buffer; - reqHeaders.bufferLen = expectedHeaderLen; - /* Set parameters for reqInfo. */ - fillReqInfoTemplate(); - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - printf( "%s\n", reqHeaders.pBuffer ); - printf( "%s\n", expectedHeader ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - expectedHeader, expectedHeaderLen ) == 0 ); - ok( reqHeaders.headersLen == expectedHeaderLen ); - ok( test_err == HTTP_SUCCESS ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test NULL parameters, following order of else-if blocks. */ - test_err = HTTPClient_InitializeRequestHeaders( NULL, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - /* reqInfo.pBuffer should be NULL after reset(). */ - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reqHeaders.pBuffer = buffer; - reqHeaders.bufferLen = HTTP_TEST_BUFFER_SIZE; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, NULL ); - ok( test_err == HTTP_INVALID_PARAMETER ); - /* reqInfo members should be NULL after reset(). */ - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reqInfo.method = HTTP_TEST_REQUEST_METHOD; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reqInfo.pHost = HTTP_TEST_HOST_VALUE; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reqInfo.pPath = HTTP_TEST_REQUEST_PATH; - reqInfo.pathLen = HTTP_TEST_REQUEST_PATH_LEN; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reqInfo.methodLen = HTTP_TEST_REQUEST_METHOD_LEN; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INVALID_PARAMETER ); - reqInfo.hostLen = HTTP_TEST_HOST_VALUE_LEN; - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test HTTP_REQUEST_KEEP_ALIVE_FLAG. */ - fillReqInfoTemplate(); - reqInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; - expectedHeaderLen = HTTP_TEST_PREFIX_HEADER_LEN + \ - HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN; - ok( snprintf( expectedHeader, expectedHeaderLen + 1, - HTTP_TEST_HEADER_FORMAT, - HTTP_TEST_REQUEST_METHOD, HTTP_TEST_REQUEST_PATH, - HTTP_PROTOCOL_VERSION, - HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, - HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, - HTTP_CONNECTION_FIELD, HTTP_CONNECTION_KEEP_ALIVE_VALUE ) - == ( int ) expectedHeaderLen ); - reqHeaders.pBuffer = buffer; - reqHeaders.bufferLen = expectedHeaderLen; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - expectedHeader, expectedHeaderLen ) == 0 ); - ok( reqHeaders.headersLen == expectedHeaderLen ); - ok( test_err == HTTP_SUCCESS ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test default path "/" if path == NULL. */ - fillReqInfoTemplate(); - reqInfo.pPath = NULL; - expectedHeaderLen = ( HTTP_TEST_PREFIX_HEADER_LEN - HTTP_TEST_REQUEST_PATH_LEN ) + \ - HTTP_EMPTY_PATH_LEN + \ - HTTP_CONNECTION_CLOSE_VALUE_LEN; - ok( snprintf( expectedHeader, expectedHeaderLen + 1, - HTTP_TEST_HEADER_FORMAT, - HTTP_TEST_REQUEST_METHOD, HTTP_EMPTY_PATH, HTTP_PROTOCOL_VERSION, - HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, - HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, - HTTP_CONNECTION_FIELD, HTTP_CONNECTION_CLOSE_VALUE ) - == ( int ) expectedHeaderLen ); - reqHeaders.pBuffer = buffer; - reqHeaders.bufferLen = expectedHeaderLen; - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - expectedHeader, expectedHeaderLen ) == 0 ); - ok( reqHeaders.headersLen == expectedHeaderLen ); - ok( test_err == HTTP_SUCCESS ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test HTTP_INSUFFICIENT_MEMORY from writing request line. */ - /* Set parameters for reqHeaders. */ - reqHeaders.pBuffer = buffer; - reqHeaders.bufferLen = HTTP_TEST_REQUEST_LINE_LEN - 1; - /* Set parameters for reqInfo. */ - fillReqInfoTemplate(); - test_err = HTTPClient_InitializeRequestHeaders( &reqHeaders, &reqInfo ); - ok( test_err == HTTP_INSUFFICIENT_MEMORY ); - ok( strncmp( ( char * ) reqHeaders.pBuffer, - HTTP_TEST_REQUEST_LINE, HTTP_TEST_REQUEST_LINE_LEN ) != 0 ); - reset(); - - /* -----------------------------------------------------------------------*/ - - return grade(); -} diff --git a/libraries/standard/http/test/test-HTTPClient_ReadHeader.c b/libraries/standard/http/test/test-HTTPClient_ReadHeader.c deleted file mode 100644 index 9c20449da8..0000000000 --- a/libraries/standard/http/test/test-HTTPClient_ReadHeader.c +++ /dev/null @@ -1,322 +0,0 @@ -#include -#include - -#include "common.h" -#include "http_parser/http_parser.h" - -/* Mirror of the context type used to pass to http_parser. */ -typedef struct findHeaderContext -{ - const uint8_t * pField; - size_t fieldLen; - const uint8_t ** pValueLoc; - size_t * pValueLen; - uint8_t fieldFound : 1; - uint8_t valueFound : 1; -} findHeaderContext_t; - -/* Functions are pulled out into their own C files to be tested as a unit. */ -#include "HTTPClient_strerror.c" -#include "findHeaderFieldParserCallback.c" -#include "findHeaderValueParserCallback.c" -#include "findHeaderOnHeaderCompleteCallback.c" -#include "findHeaderInResponse.c" -#include "HTTPClient_ReadHeader.c" - -/* Template HTTP request for a PUT request. */ -static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" - "test-header0: test-value0\r\n" - "test-header1: test-value1\r\n" - "test-header2: test-value2\r\n" - "\r\n"; -static const size_t headerFieldInRespLoc = 44; -static const size_t headerFieldInRespLen = sizeof( "test-header1" ) - 1u; -static const size_t headerValInRespLoc = 58; -static const size_t headerValInRespLen = sizeof( "test-value1" ) - 1u; - -#define HEADER_IN_BUFFER "test-header1" -#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" - -/* -------------------- Mock'd implementations of http_parser function dependencies. ------------------ */ - -void http_parser_init( http_parser * parser, - enum http_parser_type type ) -{ - ok( parser != NULL ); - ok( type == HTTP_RESPONSE ); -} - -void http_parser_settings_init( http_parser_settings * settings ) -{ - ok( settings != NULL ); -} -/* Variables for controlling behavior of the http_parser_execute() mock. */ -static const char * pExpectedBuffer = NULL; -static size_t expectedBufferSize = 0u; -bool invokeHeaderFieldCallback = false; -const char * pFieldLocToReturn = NULL; -size_t fieldLenToReturn = 0u; -bool invokeHeaderValueCallback = false; -const char * pValueLocToReturn = NULL; -static int expectedValCbRetVal = 0; -size_t valueLenToReturn = 0u; -bool invokeHeaderCompleteCallback = false; -unsigned int parserErrNo = 0; -size_t http_parser_execute( http_parser * parser, - const http_parser_settings * settings, - const char * data, - size_t len ) -{ - ok( parser != NULL ); - ok( settings != NULL ); - ok( len == expectedBufferSize ); - ok( data == pExpectedBuffer ); - - ok( settings->on_header_field != NULL ); - ok( settings->on_header_value != NULL ); - ok( settings->on_headers_complete != NULL ); - - if( invokeHeaderFieldCallback == true ) - { - ok( HTTP_PARSER_CONTINUE_PARSING == - settings->on_header_field( parser, - pFieldLocToReturn, - fieldLenToReturn ) ); - } - - if( invokeHeaderValueCallback == true ) - { - ok( expectedValCbRetVal == - settings->on_header_value( parser, - pValueLocToReturn, - valueLenToReturn ) ); - } - - if( invokeHeaderCompleteCallback == true ) - { - ok( HTTP_PARSER_STOP_PARSING == - settings->on_headers_complete( parser ) ); - } - - /* Set the error value in the parser. */ - parser->http_errno = parserErrNo; - - return len; -} - -const char * http_errno_description( enum http_errno err ) -{ - ( void ) err; - return "test-error"; -} - -/* -------------------- End of http_parser function mocks. ------------------ */ - -int main() -{ - HTTPResponse_t testResponse = { 0 }; - HTTPStatus_t retCode = HTTP_NOT_SUPPORTED; - const uint8_t * pValueLoc = NULL; - size_t valueLen = 0u; - -#define reset() \ - do { \ - retCode = HTTP_NOT_SUPPORTED; \ - memset( &testResponse, 0, sizeof( testResponse ) ); \ - pValueLoc = NULL; \ - valueLen = 0u; \ - pExpectedBuffer = &pTestResponse[ 0 ]; \ - expectedBufferSize = strlen( pTestResponse ); \ - invokeHeaderFieldCallback = false; \ - pFieldLocToReturn = NULL; \ - fieldLenToReturn = 0u; \ - invokeHeaderValueCallback = false; \ - pValueLocToReturn = NULL; \ - expectedValCbRetVal = 0; \ - valueLenToReturn = 0u; \ - invokeHeaderCompleteCallback = false; \ - } while( 0 ) - - plan( 69 ); - - /*************************** Test Failure Cases *****************************/ - - /* Response parameter is NULL. */ - reset(); - retCode = HTTPClient_ReadHeader( NULL, - ( const uint8_t * ) "Header", - strlen( "Header" ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Underlying buffer is NULL in the response parameter. */ - reset(); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Response buffer size is zero. */ - reset(); - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Header field name is NULL. */ - reset(); - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - NULL, - strlen( "Header" ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Header field length is 0. */ - reset(); - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - 0u, - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Invalid output parameters. */ - reset(); - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), - NULL, - &valueLen ); - ok( retCode == HTTP_INVALID_PARAMETER ); - reset(); - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), - &pValueLoc, - NULL ); - ok( retCode == HTTP_INVALID_PARAMETER ); - - /* Test when the header is present in response but http_parser_execute() - * does not set the expected errno value (of "CB_header_value") - * due to an internal error. */ - reset(); - /* Configure the http_parser_execute mock. */ - invokeHeaderFieldCallback = true; - invokeHeaderValueCallback = true; - pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; - fieldLenToReturn = headerFieldInRespLen; - pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; - valueLenToReturn = headerValInRespLen; - expectedValCbRetVal = HTTP_PARSER_STOP_PARSING; - parserErrNo = HPE_CB_chunk_complete; - /* Call the function under test. */ - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INTERNAL_ERROR ); - - /* Test when requested header is not present in response. */ - - reset(); - /* Configure the http_parser_execute mock. */ - invokeHeaderCompleteCallback = false; - parserErrNo = HPE_OK; - /* Call the function under test. */ - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_NOT_IN_BUFFER, - strlen( HEADER_NOT_IN_BUFFER ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_HEADER_NOT_FOUND ); - - /* Test with invalid HTTP responses. */ - - /* Test when invalid response only contains the header field for the requested header. */ - const char * pResponseWithoutValue = "HTTP/1.1 200 OK\r\n" - "test-header0: test-value0\r\n" - "test-header1:"; - reset(); - /* Configure the http_parser_execute mock. */ - pExpectedBuffer = pResponseWithoutValue; - expectedBufferSize = strlen( pResponseWithoutValue ); - invokeHeaderFieldCallback = true; - pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; - fieldLenToReturn = headerFieldInRespLen; - /* Call the function under test. */ - testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutValue[ 0 ]; - testResponse.bufferLen = strlen( pResponseWithoutValue ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_RESPONSE ); - - /* Test when the invalid response does not contain the requested header. - * (Response is invalid as it doesn't end with "\r\n\r\n".) */ - reset(); - const char * pResponseWithoutHeaders = "HTTP/1.1 200 OK\r\n" - "test-header0:test-value0"; - /* Configure the http_parser_execute mock. */ - pExpectedBuffer = &pResponseWithoutHeaders[ 0 ]; - expectedBufferSize = strlen( pResponseWithoutHeaders ); - parserErrNo = HPE_UNKNOWN; - /* Call the function under test. */ - testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutHeaders[ 0 ]; - testResponse.bufferLen = strlen( pResponseWithoutHeaders ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_NOT_IN_BUFFER, - strlen( HEADER_NOT_IN_BUFFER ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_INVALID_RESPONSE ); - - /*************************** Test Happy-Path Cases *****************************/ - - /* Test when requested header in present response. */ - - reset(); - /* Configure the http_parser_execute mock. */ - expectedValCbRetVal = HTTP_PARSER_STOP_PARSING; - pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; - fieldLenToReturn = headerFieldInRespLen; - pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; - valueLenToReturn = headerValInRespLen; - invokeHeaderFieldCallback = true; - invokeHeaderValueCallback = true; - parserErrNo = HPE_CB_header_value; - /* Call the function under test. */ - testResponse.pBuffer = ( uint8_t * ) &pTestResponse[ 0 ]; - testResponse.bufferLen = strlen( pTestResponse ); - retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), - &pValueLoc, - &valueLen ); - ok( retCode == HTTP_SUCCESS ); - ok( pValueLoc == ( const uint8_t * ) &pTestResponse[ headerValInRespLoc ] ); - ok( valueLen == headerValInRespLen ); - - return grade(); -} diff --git a/libraries/standard/http/test/test-HTTPClient_Send.c b/libraries/standard/http/test/test-HTTPClient_Send.c deleted file mode 100644 index 8b3b4df6a8..0000000000 --- a/libraries/standard/http/test/test-HTTPClient_Send.c +++ /dev/null @@ -1,537 +0,0 @@ -#include -#include -#include "common.h" - -/* THESE TESTS WILL MOVE TO THE UNITY FRAMEWORK. - * ok()'s will be replaced with proper unity macros. */ - - -/* Functions are pulled out into their own C files to be tested as a unit. */ -#include "sendHttpHeaders.c" -#include "sendHttpBody.c" -#include "receiveHttpResponse.c" -#include "HTTPClient_InitializeParsingContext.c" -#include "HTTPClient_ParseResponse.c" -#include "getFinalResponseStatus.c" -#include "receiveAndParseHttpResponse.c" -#include "HTTPClient_Send.c" - -/* HTTP OK Status-Line. */ -#define HTTP_STATUS_LINE_OK "HTTP/1.1 200 OK\r\n" -#define HTTP_STATUS_CODE_OK 200 - -/* Template HTTP successful response with no body. */ -#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY \ - "HTTP/1.1 200 OK\r\n" \ - "Content-Length: 43\r\n" \ - "Date: Sun, 14 Jul 2019 06:07:52 GMT\r\n" \ - "ETag: \"3356-5233\"\r\n" \ - "Vary: *\r\n" \ - "P3P: CP=\"This is not a P3P policy\"\r\n" \ - "xserver: www1021\r\n\r\n" -#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1U -#define HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT 6 - - -/* Template HTTP request for a HEAD request. */ -#define HTTP_TEST_REQUEST_HEAD_HEADERS \ - "HEAD /somedir/somepage.html HTTP/1.1\r\n" \ - "test-header0: test-value0\r\n" \ - "test-header1: test-value1\r\n" \ - "test-header2: test-value2\r\n" \ - "test-header3: test-value0\r\n" \ - "test-header4: test-value1\r\n" \ - "test-header5: test-value2\r\n" \ - "\r\n" -#define HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH sizeof( HTTP_TEST_REQUEST_HEAD_HEADERS ) - 1 - -/* Template HTTP request for a PUT request. */ -#define HTTP_TEST_REQUEST_PUT_HEADERS \ - "PUT /somedir/somepage.html HTTP/1.1\r\n" \ - "Content-Length: 26\r\n" \ - "test-header1: test-value1\r\n" \ - "test-header2: test-value2\r\n" \ - "test-header3: test-value0\r\n" \ - "test-header4: test-value1\r\n" \ - "test-header5: test-value2\r\n" \ - "\r\n" -#define HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH sizeof( HTTP_TEST_REQUEST_PUT_HEADERS ) - 1 -#define HTTP_TEST_REQUEST_PUT_BODY "abcdefghijklmnopqrstuvwxyz" -#define HTTP_TEST_REQUEST_PUT_BODY_LENGTH sizeof( HTTP_TEST_REQUEST_PUT_BODY ) - 1 - -/* Test buffer to share among the test. */ -#define HTTP_TEST_BUFFER_LENGTH 1024 -static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LENGTH ] = { 0 }; - -/* -----------------------------------------------------------------------*/ - -/* Mocked successful transport send. */ -static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, - const void * pBuffer, - size_t bytesToWrite ) -{ - ( void ) pContext; - ( void ) pBuffer; - return bytesToWrite; -} - -/* -----------------------------------------------------------------------*/ - -/* This section contains all the support needed to mock the two different - * transport interface send cases. The three transport send error cases are: - * 1. A negative value returned. - * 2. Less than bytesToWrite returned. */ - -/* Transport send is called separately for the headers and the body, this counts - * the number of calls so far. */ -uint8_t sendCurrentCall; -uint8_t sendErrorCall; -static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, - const void * pBuffer, - size_t bytesToWrite ) -{ - ( void ) pContext; - ( void ) pBuffer; - int32_t retVal = bytesToWrite; - - if( sendErrorCall == sendCurrentCall ) - { - retVal = -1; - } - - sendCurrentCall++; - return retVal; -} - -static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, - const void * pBuffer, - size_t bytesToWrite ) -{ - ( void ) pContext; - ( void ) pBuffer; - int32_t retVal = bytesToWrite; - - if( sendErrorCall == sendCurrentCall ) - { - retVal -= 1; - } - - sendCurrentCall++; - return retVal; -} - -#define transportSendErrorReset() \ - do { \ - sendCurrentCall = 0; \ - sendErrorCall = 0; \ - } while( 0 ); - -/* -----------------------------------------------------------------------*/ - -/* Mocked successful transport read. */ -static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, - void * pBuffer, - size_t bytesToRead ) -{ - ( void ) pContext; - - memcpy( pBuffer, - HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY, - sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1 ); - - return sizeof( HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY ) - 1; -} - -/* -----------------------------------------------------------------------*/ - -/* This section contains all the support needed to mock an incremental transport - * read. The mocked transport receive will read 128 bytes at time from the - * response. */ - -/* Default increment read bytes transportRecvIncremental. */ -#define INCREMENT_BYTES_DEFAULT 0x80U - -uint32_t bytesRead; /* Total bytes read over multple calls. */ -uint8_t * pNetworkData; /* The network data to read. */ -size_t networkDataLen; /* The network data to read's length. */ -size_t incrementReadBytes; /* How many bytes of network data to read each call. */ -size_t stopReadBytes; /* Stop reading at >= this many bytes. */ -static int32_t transportRecvIncremental( HTTPNetworkContext_t * pContext, - void * pBuffer, - size_t bytesToRead ) -{ - ( void ) pContext; - - if( bytesRead >= stopReadBytes ) - { - return 0; - } - - uint8_t * pCopyLoc = pNetworkData + bytesRead; - size_t messageLenLeft = networkDataLen - bytesRead; - size_t copyLen = incrementReadBytes; - - /* If the amount requested to read is smaller than the increment, then - * copy that amount. */ - if( bytesToRead < copyLen ) - { - copyLen = bytesToRead; - } - - /* If there is less message left than the amount to read, then copy the - * rest. */ - if( messageLenLeft < copyLen ) - { - copyLen = messageLenLeft; - } - - memcpy( pBuffer, pCopyLoc, copyLen ); - bytesRead += copyLen; - return copyLen; -} -#define transportRecvIncrementalReset() \ - do { \ - bytesRead = 0; \ - pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY; \ - networkDataLen = HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH; \ - incrementReadBytes = INCREMENT_BYTES_DEFAULT; \ - stopReadBytes = 0; \ - } while( 0 ); - - -/* -----------------------------------------------------------------------*/ - -/* Mocked network error returning transport recv. */ -static int32_t transportRecvNetworkError( HTTPNetworkContext_t * pContext, - void * pBuffer, - size_t bytesToRead ) -{ - ( void ) pContext; - ( void ) pBuffer; - ( void ) bytesToRead; - - return -1; -} - -/* -----------------------------------------------------------------------*/ - -/* Mocked transport recv function that returns more bytes than expected. */ -static int32_t transportRecvMoreThanBytesToRead( HTTPNetworkContext_t * pContext, - void * pBuffer, - size_t bytesToRead ) -{ - ( void ) pContext; - ( void ) pBuffer; - - return( bytesToRead + 1 ); -} - -/* -----------------------------------------------------------------------*/ - -int main() -{ - HTTPStatus_t returnStatus = HTTP_SUCCESS; - HTTPResponse_t response = { 0 }; - HTTPTransportInterface_t transportInterface = { 0 }; - HTTPRequestHeaders_t requestHeaders = { 0 }; - -/* Resets each test back to the original happy path state. */ -#define reset() \ - do { \ - transportInterface.recv = transportRecvSuccess; \ - transportInterface.send = transportSendSuccess; \ - transportInterface.pContext = NULL; \ - requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_HEAD_HEADERS ); \ - requestHeaders.bufferLen = HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH; \ - requestHeaders.headersLen = HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH; \ - memset( &response, 0, sizeof( HTTPResponse_t ) ); \ - response.pBuffer = httpBuffer; \ - response.bufferLen = sizeof( httpBuffer ); \ - transportRecvIncrementalReset(); \ - transportSendErrorReset(); \ - } while( 0 ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Happy Path testing. Test sending a request and successfully receiving a - * response that is within the bounds of the response buffer. */ - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_SUCCESS ); - /* Uncomment after parsing is implemented. */ - /* ok( response.body == NULL ); */ - /* ok( response.bodyLen == 0 ); */ - /* ok( response.headers == response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ) ); */ - /* ok( response.headersLen == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH ); */ - /* ok( response.statusCode == HTTP_STATUS_CODE_OK ); */ - /* ok( response.contentLength == 0 ); */ - /* ok( response.headerCount == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT ); */ - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Happy path testing. Test sending a non-null body buffer and received a full - * response. */ - requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); - requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, - HTTP_TEST_REQUEST_PUT_BODY_LENGTH, - &response ); - - ok( returnStatus == HTTP_SUCCESS ); - /* Uncomment after parsing is implemented. */ - /* ok( response.body == NULL ); */ - /* ok( response.bodyLen == 0 ); */ - /* ok( response.headers == response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ) ); */ - /* ok( response.headersLen == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH ); */ - /* ok( response.statusCode == HTTP_STATUS_CODE_OK ); */ - /* ok( response.contentLength == 0 ); */ - /* ok( response.headerCount == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT ); */ - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test receiving the response message with multiple calls to transport - * recv. */ - transportInterface.recv = transportRecvIncremental; - stopReadBytes = networkDataLen; /* Read the whole network data. */ - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_SUCCESS ); - /* Uncomment after parsing is implemented. */ - /* ok( response.body == NULL ); */ - /* ok( response.bodyLen == 0 ); */ - /* ok( response.headers == response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ) ); */ - /* ok( response.headersLen == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_LENGTH ); */ - /* ok( response.statusCode == HTTP_STATUS_CODE_OK ); */ - /* ok( response.contentLength == 0 ); */ - /* ok( response.headerCount == HTTP_TEST_RESPONSE_HEADER_LINES_NO_BODY_HEADER_COUNT ); */ - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test receiving with a NULL response. */ - requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); - requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, - HTTP_TEST_REQUEST_PUT_BODY_LENGTH, - NULL ); - - ok( returnStatus == HTTP_SUCCESS ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a network error returned from a transport send of the request - * headers. */ - sendErrorCall = 0; - transportInterface.send = transportSendNetworkError; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_NETWORK_ERROR ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a network error returned from a transport send of the request - * body. */ - transportInterface.send = transportSendNetworkError; - sendErrorCall = 1; - requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); - requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, - HTTP_TEST_REQUEST_PUT_BODY_LENGTH, - &response ); - - ok( returnStatus == HTTP_NETWORK_ERROR ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test less bytes than expected returned from a transport send of the - * request headers. */ - transportInterface.send = transportSendLessThanBytesToWrite; - sendErrorCall = 0U; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_NETWORK_ERROR ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test less bytes than expected returned from a transport send of the - * request body. */ - transportInterface.send = transportSendLessThanBytesToWrite; - sendErrorCall = 1; - requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); - requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, - HTTP_TEST_REQUEST_PUT_BODY_LENGTH, - &response ); - - ok( returnStatus == HTTP_NETWORK_ERROR ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a network error returned from a transport recv of the response. */ - transportInterface.recv = transportRecvNetworkError; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - ok( returnStatus = HTTP_NETWORK_ERROR ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test more bytes than expected returned from a transport recv of the - * response. */ - transportInterface.recv = transportRecvMoreThanBytesToRead; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - ok( returnStatus = HTTP_NETWORK_ERROR ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test transport recv returning zero when first invoked. */ - transportInterface.recv = transportRecvIncremental; - stopReadBytes = 0; /* Stop immediately. */ - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - ok( returnStatus = HTTP_NO_RESPONSE ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test transport recv returning zero in the middle of a response message. */ - /* This test will be invoked upon parsing implementation completion. */ - #if 0 - transportInterface.recv = transportRecvMoreThanBytesToRead; - stopReadBytes = incrementReadBytes; /* Stop after the first increment. */ - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - ok( returnStatus = HTTP_PARTIAL_RESPONSE ); - reset(); - #endif - - /* -----------------------------------------------------------------------*/ - - /* Test a NULL transport interface. */ - returnStatus = HTTPClient_Send( NULL, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a NULL transport interface send. */ - transportInterface.send = NULL; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a NULL transport interface recv. */ - transportInterface.recv = NULL; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test NULL request headers structure. */ - returnStatus = HTTPClient_Send( &transportInterface, - NULL, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test NULL request header buffer. */ - requestHeaders.pBuffer = NULL; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - /* Test a NULL response buffer. */ - response.pBuffer = NULL; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response ); - - ok( returnStatus == HTTP_INVALID_PARAMETER ); - reset(); - - /* -----------------------------------------------------------------------*/ - - return grade(); -} diff --git a/libraries/standard/http/third_party/CMakeLists.txt b/libraries/standard/http/third_party/CMakeLists.txt new file mode 100644 index 0000000000..36150d35ec --- /dev/null +++ b/libraries/standard/http/third_party/CMakeLists.txt @@ -0,0 +1,26 @@ +# Check if the http_parser source directory exists. +if( NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/http_parser ) + # Attempt to clone http_parser. + if( ${BUILD_CLONE_SUBMODULES} ) + find_package( Git REQUIRED ) + + message( "Cloning submodule http_parser." ) + execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive http_parser + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + RESULT_VARIABLE HTTP_PARSER_CLONE_RESULT ) + + if( NOT ${HTTP_PARSER_CLONE_RESULT} STREQUAL "0" ) + message( FATAL_ERROR "Failed to clone http_parser submodule." ) + endif() + else() + message( FATAL_ERROR "The required submodule http_parser does not exist. Either clone it manually, or set BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) + endif() +endif() + +# http_parser library target. +add_library( http_parser + ${CMAKE_CURRENT_LIST_DIR}/http_parser/http_parser.c ) + +# http_parser public include path. +target_include_directories( http_parser PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/http_parser ) diff --git a/libraries/standard/http/utest/CMakeLists.txt b/libraries/standard/http/utest/CMakeLists.txt index 65917fec78..68f12bf097 100644 --- a/libraries/standard/http/utest/CMakeLists.txt +++ b/libraries/standard/http/utest/CMakeLists.txt @@ -80,3 +80,12 @@ create_test(${utest_name} "${utest_dep_list}" "${test_include_directories}" ) + +set(utest_name "${project_name}_send_utest") +set(utest_source "${project_name}_send_utest.c") +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c new file mode 100644 index 0000000000..2934a7a010 --- /dev/null +++ b/libraries/standard/http/utest/http_send_utest.c @@ -0,0 +1,1509 @@ +#include +#include +#include + +#include "unity.h" + +/* Include paths for public enums, structures, and macros. */ +#include "http_client.h" +/* Private includes for internal macros. */ +#include "private/http_client_internal.h" + +#include "mock_http_parser.h" + +/* Template HTTP request for a HEAD request. */ +#define HTTP_TEST_REQUEST_HEAD_HEADERS \ + "HEAD /somedir/somepage.html HTTP/1.1\r\n" \ + "test-header0: test-value0\r\n" \ + "test-header1: test-value1\r\n" \ + "test-header2: test-value2\r\n" \ + "test-header3: test-value0\r\n" \ + "test-header4: test-value1\r\n" \ + "test-header5: test-value2\r\n" \ + "\r\n" +#define HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH ( sizeof( HTTP_TEST_REQUEST_HEAD_HEADERS ) - 1U ) + +/* Template HTTP request for a PUT request. */ +#define HTTP_TEST_REQUEST_PUT_HEADERS \ + "PUT /somedir/somepage.html HTTP/1.1\r\n" \ + "test-header1: test-value1\r\n" \ + "test-header2: test-value2\r\n" \ + "test-header3: test-value0\r\n" \ + "test-header4: test-value1\r\n" \ + "test-header5: test-value2\r\n" \ + "\r\n" +#define HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ( sizeof( HTTP_TEST_REQUEST_PUT_HEADERS ) - 1U ) +#define HTTP_TEST_REQUEST_PUT_BODY "abcdefghijklmnopqrstuvwxyz" +#define HTTP_TEST_REQUEST_PUT_BODY_LENGTH ( sizeof( HTTP_TEST_REQUEST_PUT_BODY ) - 1U ) +#define HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED "Content-Length: 26\r\n" HTTP_HEADER_LINE_SEPARATOR +#define HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED_LENGTH ( sizeof( HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED ) - 1U ) + +/* Template HTTP request for a GET request. */ +#define HTTP_TEST_REQUEST_GET_HEADERS \ + "GET /somedir/somepage.html HTTP/1.1\r\n" \ + "test-header1: test-value1\r\n" \ + "test-header2: test-value2\r\n" \ + "test-header3: test-value0\r\n" \ + "test-header4: test-value1\r\n" \ + "test-header5: test-value2\r\n" \ + "\r\n" +#define HTTP_TEST_REQUEST_GET_HEADERS_LENGTH ( sizeof( HTTP_TEST_REQUEST_GET_HEADERS ) - 1U ) + +/* HTTP OK Status-Line. */ +#define HTTP_STATUS_LINE_OK "HTTP/1.1 200 OK\r\n" +#define HTTP_STATUS_CODE_OK 200 + +/* Various header lines for test response templates. */ +#define HTTP_TEST_CONTENT_LENGTH_HEADER_LINE "Content-Length: 43\r\n" +#define HTTP_TEST_DATE_HEADER_LINE "Date: Sun, 14 Jul 2019 06:07:52 GMT\r\n" +#define HTTP_TEST_ETAG_HEADER_LINE "ETag: \"3356-5233\"\r\n" +#define HTTP_TEST_VARY_HEADER_LINE "Vary: *\r\n" +#define HTTP_TEST_P3P_HEADER_LINE "P3P: CP=\"This is not a P3P policy\"\r\n" +#define HTTP_TEST_XSERVER_HEADER_LINE "xserver: www1021\r\n" +#define HTTP_TEST_CONNECTION_CLOSE_HEADER_LINE "Connection: close\r\n" +#define HTTP_TEST_CONNECTION_KEEP_ALIVE_HEADER_LINE "Connection: keep-alive\r\n" +#define HTTP_TEST_TRANSFER_ENCODING_CHUNKED_HEADER_LINE "Transfer-Encoding: chunked\r\n" + +/* Partial header field and value for testing partial header field and value + * handling in parser callback. */ +#define HTTP_TEST_CONTENT_LENGTH_PARTIAL_HEADER_FIELD "Content-Len" +#define HTTP_TEST_CONTENT_LENGTH_PARTIAL_HEADER_VALUE "Content-Length: 4" + +/* Template HTTP HEAD response. */ +#define HTTP_TEST_RESPONSE_HEAD \ + HTTP_STATUS_LINE_OK \ + HTTP_TEST_CONTENT_LENGTH_HEADER_LINE \ + HTTP_TEST_CONNECTION_CLOSE_HEADER_LINE \ + HTTP_TEST_DATE_HEADER_LINE \ + HTTP_TEST_ETAG_HEADER_LINE \ + HTTP_TEST_VARY_HEADER_LINE \ + HTTP_TEST_P3P_HEADER_LINE \ + HTTP_TEST_XSERVER_HEADER_LINE HTTP_HEADER_LINE_SEPARATOR +#define HTTP_TEST_RESPONSE_HEAD_LENGTH ( sizeof( HTTP_TEST_RESPONSE_HEAD ) - 1U ) +#define HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT 7 +#define HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH 43 +#define HTTP_TEST_RESPONSE_HEAD_PARTIAL_HEADER_FIELD_LENGTH ( sizeof( HTTP_STATUS_LINE_OK ) + sizeof( HTTP_TEST_CONTENT_LENGTH_PARTIAL_HEADER_FIELD ) - 2U ) +#define HTTP_TEST_RESPONSE_HEAD_PARTIAL_HEADER_VALUE_LENGTH ( sizeof( HTTP_STATUS_LINE_OK ) + sizeof( HTTP_TEST_CONTENT_LENGTH_PARTIAL_HEADER_VALUE ) - 2U ) + +/* Template HTTP PUT response. This has no body. */ +#define HTTP_TEST_RESPONSE_PUT \ + HTTP_STATUS_LINE_OK \ + HTTP_TEST_CONNECTION_KEEP_ALIVE_HEADER_LINE \ + HTTP_TEST_DATE_HEADER_LINE \ + HTTP_TEST_ETAG_HEADER_LINE \ + HTTP_TEST_VARY_HEADER_LINE \ + HTTP_TEST_P3P_HEADER_LINE \ + HTTP_TEST_XSERVER_HEADER_LINE HTTP_HEADER_LINE_SEPARATOR +#define HTTP_TEST_RESPONSE_PUT_LENGTH ( sizeof( HTTP_TEST_RESPONSE_PUT ) - 1U ) +#define HTTP_TEST_RESPONSE_PUT_HEADER_COUNT 6 + +/* Template HTTP GET response. */ +#define HTTP_TEST_RESPONSE_GET \ + HTTP_TEST_RESPONSE_HEAD \ + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq" +#define HTTP_TEST_RESPONSE_GET_LENGTH ( sizeof( HTTP_TEST_RESPONSE_GET ) - 1U) +#define HTTP_TEST_RESPONSE_GET_HEADER_COUNT HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT +#define HTTP_TEST_RESPONSE_GET_HEADERS_LENGTH ( HTTP_TEST_RESPONSE_HEAD_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ) ) +#define HTTP_TEST_RESPONSE_GET_BODY_LENGTH HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH +#define HTTP_TEST_RESPONSE_GET_CONTENT_LENGTH HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH +#define HTTP_TEST_RESPONSE_GET_PARTIAL_BODY_LENGTH ( HTTP_TEST_RESPONSE_GET_LENGTH - 13U ) + +/* Template HTTP transfer-encoding chunked response. */ +#define HTTP_TEST_RESPONSE_CHUNKED \ + HTTP_STATUS_LINE_OK \ + HTTP_TEST_TRANSFER_ENCODING_CHUNKED_HEADER_LINE \ + HTTP_TEST_CONNECTION_KEEP_ALIVE_HEADER_LINE \ + HTTP_TEST_DATE_HEADER_LINE \ + HTTP_TEST_ETAG_HEADER_LINE \ + HTTP_TEST_VARY_HEADER_LINE \ + HTTP_TEST_P3P_HEADER_LINE \ + HTTP_TEST_XSERVER_HEADER_LINE HTTP_HEADER_LINE_SEPARATOR \ + "b\r\n" \ + "abcdefghijk\r\n" \ + "c\r\n" \ + "lmnopqrstuvw\r\n" \ + "3\r\n" \ + "xyz\r\n" \ + "0\r\n" \ + "\r\n" +#define HTTP_TEST_RESPONSE_CHUNKED_LENGTH ( sizeof( HTTP_TEST_RESPONSE_CHUNKED ) - 1U ) +#define HTTP_TEST_RESPONSE_CHUNKED_HEADER_COUNT 7 +#define HTTP_TEST_RESPONSE_CHUNKED_BODY_LENGTH 26 +#define HTTP_TEST_RESPONSE_CHUNKED_HEADERS_LENGTH \ + sizeof( HTTP_TEST_TRANSFER_ENCODING_CHUNKED_HEADER_LINE ) + \ + sizeof( HTTP_TEST_CONNECTION_KEEP_ALIVE_HEADER_LINE ) + \ + sizeof( HTTP_TEST_DATE_HEADER_LINE ) + \ + sizeof( HTTP_TEST_ETAG_HEADER_LINE ) + \ + sizeof( HTTP_TEST_VARY_HEADER_LINE ) + \ + sizeof( HTTP_TEST_P3P_HEADER_LINE ) + \ + sizeof( HTTP_TEST_XSERVER_HEADER_LINE ) + \ + HTTP_HEADER_LINE_SEPARATOR_LEN - 7U + +/* Template HTTP response with no headers. */ +#define HTTP_TEST_RESPONSE_NO_HEADERS \ + HTTP_STATUS_LINE_OK HTTP_HEADER_LINE_SEPARATOR +#define HTTP_TEST_RESPONSE_NO_HEADERS_LENGTH ( sizeof( HTTP_TEST_RESPONSE_NO_HEADERS ) - 1U ) + +/* Test buffer to share among the test. */ +#define HTTP_TEST_BUFFER_LENGTH 1024 +static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LENGTH ] = { 0 }; + +/* Tests are run sequentially. If a response has these variables, then they + * will be set during the onHeaderCallback(). */ +static uint8_t hasConnectionClose = 0; +static uint8_t hasConnectionKeepAlive = 0; +static size_t contentLength = 0; + +/* The count of times a test invoked the onHeaderCallback(). */ +static uint8_t headerCallbackCount = 0; + +/* The count of times a test invoked the transport send interface. */ +static uint8_t sendCurrentCall = 0; +/* Set this to 1 to enable checking that the Content-Length header generated is correct. */ +static uint8_t checkContentLength = 0; + +/* The test sets this variable to indicate at which call count of transport send + * to return an error from. */ +static uint8_t sendErrorCall = 0; + +/* The network data to receive. */ +static uint8_t * pNetworkData = NULL; +/* The length of the network data to receive. */ +static size_t networkDataLen = 0; + +/* The number of bytes to send in the first call to the transport receive + * interface. */ +static size_t firstPartBytes = 0; +/* The count of times a test invoked the transport receive interface. */ +static uint8_t recvCurrentCall = 0; + +/* The test sets this variable to indcate which call count count of transport + * receive to return an error from. */ +static uint8_t recvStopCall = 0; +/* The count of times a mocked http_parser_execute callback has been invoked. */ +static uint8_t httpParserExecuteCallCount; + +/* The error to set to the parsing context when the http_parser_execute_error + * callback is invoked. */ +static enum http_errno httpParsingErrno; + +/* Response shared among the tests. */ +static HTTPResponse_t response = { 0 }; +/* Transport interface shared among the tests. */ +static HTTPTransportInterface_t transportInterface = { 0 }; +/* Request headers shared among the tests. */ +static HTTPRequestHeaders_t requestHeaders = { 0 }; +/* Header parsing callback shared among the tests. */ +static HTTPClient_ResponseHeaderParsingCallback_t headerParsingCallback = { 0 }; + +/* Application callback for intercepting the headers during the parse of a new + * response from the mocked network interface. */ +static void onHeaderCallback( void * pContext, + const uint8_t * fieldLoc, + size_t fieldLen, + const uint8_t * valueLoc, + size_t valueLen, + uint16_t statusCode ) +{ + ( void ) pContext; + ( void ) statusCode; + + if( strncmp( ( const char * ) fieldLoc, "Connection", fieldLen ) == 0 ) + { + if( strncmp( ( const char * ) valueLoc, "keep-alive", valueLen ) == 0 ) + { + hasConnectionKeepAlive = 1; + } + else if( strncmp( ( const char * ) valueLoc, "close", valueLen ) == 0 ) + { + hasConnectionClose = 1; + } + } + else if( strncmp( ( const char * ) fieldLoc, "Content-Length", fieldLen ) == 0 ) + { + contentLength = strtoul( ( const char * ) valueLoc, NULL, 10 ); + } + + headerCallbackCount++; +} + +/* Successful application transport send interface. */ +static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + + if( checkContentLength == 1U ) + { + if( sendCurrentCall == 0U ) + { + size_t contentLengthAndHeaderEndLen = HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED_LENGTH; + char* pContentLengthStart = ( ( (char *)pBuffer ) + bytesToWrite ) - contentLengthAndHeaderEndLen; + TEST_ASSERT_GREATER_OR_EQUAL( contentLengthAndHeaderEndLen, bytesToWrite ); + TEST_ASSERT_EQUAL_MEMORY( HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED, + pContentLengthStart, + HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED_LENGTH ); + } + } + + sendCurrentCall++; + return bytesToWrite; +} + +/* Application transport send interface that returns a network error depending +* on the call count. Set sendErrorCall to 0 to return an error on the +* first call. Set sendErrorCall to 1 to return an error on the second call. */ +static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + int32_t retVal = bytesToWrite; + + if( sendErrorCall == sendCurrentCall ) + { + retVal = -1; + } + + sendCurrentCall++; + return retVal; +} + +/* Application transport send interface that returns less bytes than expected + * depending on the call count. Set sendErrorCall to 0 to return an error on the + * first call. Set sendErrorCall to 1 to return an error on the second call. */ +static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + int32_t retVal = bytesToWrite; + + if( sendErrorCall == sendCurrentCall ) + { + retVal -= 1; + } + + sendCurrentCall++; + return retVal; +} + +/* Application transport receive interface that sends the bytes specified in + * firstPartBytes on the first call, then sends the rest of the response in the + * second call. The response to send is set in pNetworkData and the current + * call count is kept track of in recvCurrentCall. This function will return + * zero (timeout condition) when recvStopCall matches recvCurrentCall. */ +static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + size_t bytesToCopy = 0; + + /* To test stopping in the middle of a response message, check that the + * flags are set. */ + if( recvStopCall == recvCurrentCall ) + { + return 0; + } + + /* If this is the first call, then copy the specific first bytes. */ + if( recvCurrentCall == 0 ) + { + bytesToCopy = firstPartBytes; + } + /* Otherwise copy the rest of the network data. */ + else + { + bytesToCopy = networkDataLen; + } + + if( bytesToCopy > bytesToRead ) + { + bytesToCopy = bytesToRead; + } + + memcpy( pBuffer, pNetworkData, bytesToCopy ); + pNetworkData += bytesToCopy; + networkDataLen -= bytesToCopy; + recvCurrentCall++; + return bytesToCopy; +} + +/* Application transport receive that return a network error. */ +static int32_t transportRecvNetworkError( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + ( void ) pBuffer; + ( void ) bytesToRead; + + return -1; +} + +/* Application transport receive that returns more bytes read than expected. */ +static int32_t transportRecvMoreThanBytesToRead( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRead ) +{ + ( void ) pContext; + ( void ) pBuffer; + + return( bytesToRead + 1 ); +} + +/* Mocked http_parser_execute callback that sets the internal http_errno. */ +static size_t http_parser_execute_error( http_parser * pParser, + const http_parser_settings * pSettings, + const char * pData, + size_t len, + int cmock_num_calls ) +{ + ( void ) pSettings; + ( void ) pData; + ( void ) len; + ( void ) cmock_num_calls; + + pParser->http_errno = httpParsingErrno; + return 0; +} + +/* Mock helper that parses the status line starting from pNext. */ +static void helper_parse_status_line( const char ** pNext, + http_parser * pParser, + const http_parser_settings * pSettings ) +{ + const char * pReasonPhraseStart = NULL; + size_t reasonPhraseStartLen = 0; + + /* For purposes of unit testing the response is well formed in the non-error + * cases, so the reason-phrase is always after HTTP/1.1 and the three digit + * status code. strstr() is used only for unit testing where test input are \ + * always string literals. strstr() should not be used in application code. */ + *pNext = strstr( *pNext, SPACE_CHARACTER ); /* Get the space before the status-code. */ + *pNext += SPACE_CHARACTER_LEN; + *pNext = strstr( *pNext, SPACE_CHARACTER ); /* Get the space before the reason-phrase. */ + *pNext += SPACE_CHARACTER_LEN; + pReasonPhraseStart = *pNext; + *pNext = strstr( *pNext, HTTP_HEADER_LINE_SEPARATOR ); + reasonPhraseStartLen = ( size_t ) ( *pNext - pReasonPhraseStart ); + pParser->status_code = 200; + pSettings->on_status( pParser, + pReasonPhraseStart, + reasonPhraseStartLen ); + + *pNext += HTTP_HEADER_LINE_SEPARATOR_LEN; +} + +/* Mock helper that parses all of the headers starting from pNext. */ +static void helper_parse_headers( const char ** pNext, + http_parser * pParser, + const http_parser_settings * pSettings ) +{ + const char * pHeaderFieldStart = NULL; + size_t headerFieldLen = 0; + const char * pHeaderValueStart = NULL; + size_t headerValueLen = 0; + + while( **pNext != '\r' ) + { + pHeaderFieldStart = *pNext; + *pNext = strstr( *pNext, HTTP_HEADER_FIELD_SEPARATOR ); + headerFieldLen = ( size_t ) ( *pNext - pHeaderFieldStart ); + pSettings->on_header_field( pParser, pHeaderFieldStart, headerFieldLen ); + + *pNext += HTTP_HEADER_FIELD_SEPARATOR_LEN; + + pHeaderValueStart = *pNext; + *pNext = strstr( *pNext, HTTP_HEADER_LINE_SEPARATOR ); + headerValueLen = ( size_t ) ( *pNext - pHeaderValueStart ); + pSettings->on_header_value( pParser, pHeaderValueStart, headerValueLen ); + + *pNext += HTTP_HEADER_LINE_SEPARATOR_LEN; + } +} + +/* Mock helper that parses the end of the headers starting from pNext. pNext + * will point to the start of the body after this is finished. */ +static void helper_parse_headers_finish( const char ** pNext, + http_parser * pParser, + const http_parser_settings * pSettings, + uint8_t * isHeadResponse ) +{ + uint8_t isHeadResponseReturned = 0; + + pParser->content_length = contentLength; + + if( hasConnectionClose ) + { + pParser->flags |= F_CONNECTION_CLOSE; + } + + if( hasConnectionKeepAlive ) + { + pParser->flags |= F_CONNECTION_KEEP_ALIVE; + } + + isHeadResponseReturned = pSettings->on_headers_complete( pParser ); + + if( isHeadResponse != NULL ) + { + *isHeadResponse = isHeadResponseReturned; + } + + *pNext += HTTP_HEADER_LINE_SEPARATOR_LEN; +} + +/* Mock helper that parses the response body starting from pNext. */ +static void helper_parse_body( const char ** pNext, + http_parser * pParser, + const http_parser_settings * pSettings, + uint8_t isHeadResponse, + const char * pData, + size_t len ) +{ + const char * pBody = NULL; + size_t bodyLen = 0; + + pBody = *pNext; + + if( isHeadResponse == 0 ) + { + bodyLen = ( size_t ) ( len - ( size_t ) ( pBody - pData ) ); + + if( bodyLen > 0 ) + { + pSettings->on_body( pParser, pBody, bodyLen ); + } + } +} + +/* Mocked http_parser_execute callback that expects a whole response to be in + * the given data to parse. */ +static size_t http_parser_execute_whole_response( http_parser * pParser, + const http_parser_settings * pSettings, + const char * pData, + size_t len, + int cmock_num_calls ) +{ + ( void ) cmock_num_calls; + const char * pNext = pData; + uint8_t isHeadResponse = 0; + + pSettings->on_message_begin( pParser ); + + helper_parse_status_line( &pNext, pParser, pSettings ); + helper_parse_headers( &pNext, pParser, pSettings ); + helper_parse_headers_finish( &pNext, pParser, pSettings, &isHeadResponse ); + helper_parse_body( &pNext, pParser, pSettings, isHeadResponse, pData, len ); + + pSettings->on_message_complete( pParser ); + + httpParserExecuteCallCount++; + return len; +} + +/* Mocked http_parser_execute callback that will be called the first time on the + * response message up to the middle of the first header field, then the second + * time on the response message from the middle of the first header field to the + * end. */ +static size_t http_parser_execute_partial_header_field( http_parser * pParser, + const http_parser_settings * pSettings, + const char * pData, + size_t len, + int cmock_num_calls ) +{ + ( void ) cmock_num_calls; + const char * pNext = pData; + uint8_t isHeadResponse = 0; + const char * pHeaderFieldStart = NULL; + size_t headerFieldLen = 0; + + if( httpParserExecuteCallCount == 0 ) + { + pSettings->on_message_begin( pParser ); + + helper_parse_status_line( &pNext, pParser, pSettings ); + + /* pNext now points to the start of the partial header field. */ + pHeaderFieldStart = pNext; + headerFieldLen = len - ( size_t ) ( pHeaderFieldStart - pData ); + pSettings->on_header_field( pParser, pHeaderFieldStart, headerFieldLen ); + } + else + { + /* For testing of invoking http_parser_execute() with a parsing length + * of zero, when data had been previously parsed. */ + if( len == 0 ) + { + pParser->http_errno = HPE_INVALID_EOF_STATE; + return 0; + } + + helper_parse_headers( &pNext, pParser, pSettings ); + helper_parse_headers_finish( &pNext, pParser, pSettings, &isHeadResponse ); + helper_parse_body( &pNext, pParser, pSettings, isHeadResponse, pData, len ); + pSettings->on_message_complete( pParser ); + } + + httpParserExecuteCallCount++; + return len; +} + +/* Mocked http_parser_execute callback that will be called the first time on the + * response message up to the middle of the first header value, then the second + * time on the response message from the middle of the first header value to the + * end. */ +static size_t http_parser_execute_partial_header_value( http_parser * pParser, + const http_parser_settings * pSettings, + const char * pData, + size_t len, + int cmock_num_calls ) +{ + ( void ) cmock_num_calls; + + const char * pNext = pData; + uint8_t isHeadResponse = 0; + const char * pHeaderFieldStart = NULL; + size_t headerFieldLen = 0; + const char * pHeaderValueStart = NULL; + size_t headerValueLen = 0; + + if( httpParserExecuteCallCount == 0 ) + { + pSettings->on_message_begin( pParser ); + + helper_parse_status_line( &pNext, pParser, pSettings ); + + /* Get the first header field. */ + pHeaderFieldStart = pNext; + pNext = strstr( pNext, HTTP_HEADER_FIELD_SEPARATOR ); + headerFieldLen = ( size_t ) ( pNext - pHeaderFieldStart ); + pSettings->on_header_field( pParser, pHeaderFieldStart, headerFieldLen ); + + pNext += HTTP_HEADER_FIELD_SEPARATOR_LEN; + + /* pNext now points to the start of the partial header value. */ + pHeaderValueStart = pNext; + headerValueLen = len - ( size_t ) ( pHeaderValueStart - pData ); + pSettings->on_header_value( pParser, pHeaderValueStart, headerValueLen ); + } + else + { + /* In this second call to http_parser_execute mock, pData now starts + * at the partial header value. */ + pHeaderValueStart = pNext; + pNext = strstr( pNext, HTTP_HEADER_LINE_SEPARATOR ); + headerValueLen = ( size_t ) ( pNext - pHeaderValueStart ); + pSettings->on_header_value( pParser, pHeaderValueStart, headerValueLen ); + + pNext += HTTP_HEADER_FIELD_SEPARATOR_LEN; + + helper_parse_headers( &pNext, pParser, pSettings ); + helper_parse_headers_finish( &pNext, pParser, pSettings, &isHeadResponse ); + helper_parse_body( &pNext, pParser, pSettings, isHeadResponse, pData, len ); + + pSettings->on_message_complete( pParser ); + } + + httpParserExecuteCallCount++; + return len; +} + +/* Mocked http_parser_execute callback that will be called the first time on the + * response message up to the middle of the body, then the second time on the + * response message from the middle of the body to the end. */ +static size_t http_parser_execute_partial_body( http_parser * pParser, + const http_parser_settings * pSettings, + const char * pData, + size_t len, + int cmock_num_calls ) +{ + ( void ) cmock_num_calls; + + const char * pNext = pData; + + if( httpParserExecuteCallCount == 0 ) + { + pSettings->on_message_begin( pParser ); + + helper_parse_status_line( &pNext, pParser, pSettings ); + helper_parse_headers( &pNext, pParser, pSettings ); + helper_parse_headers_finish( &pNext, pParser, pSettings, NULL ); + helper_parse_body( &pNext, pParser, pSettings, 0, pData, len ); + } + else + { + /* Parse the rest of the body. */ + helper_parse_body( &pNext, pParser, pSettings, 0, pData, len ); + + pSettings->on_message_complete( pParser ); + } + + httpParserExecuteCallCount++; + return len; +} + +/* Mocked http_parser_execute callback that will be on a response of type + * transfer-encoding chunked. */ +static size_t http_parser_execute_chunked_body( http_parser * pParser, + const http_parser_settings * pSettings, + const char * pData, + size_t len, + int cmock_num_calls ) +{ + ( void ) cmock_num_calls; + + const char * pNext = pData; + uint8_t isHeadResponse = 0; + const char * pBody = NULL; + size_t bodyLen = 0; + const char * pChunkHeader = NULL; + + pSettings->on_message_begin( pParser ); + + helper_parse_status_line( &pNext, pParser, pSettings ); + helper_parse_headers( &pNext, pParser, pSettings ); + helper_parse_headers_finish( &pNext, pParser, pSettings, &isHeadResponse ); + + /* pNext now points to the start of the first chunk header. Loop until the + * last chunk header is detected. A "\r\" follows the last chunk header + * (length 0 chunk header). */ + while( *pNext != '\r' ) + { + pChunkHeader = pNext; + bodyLen = ( size_t ) strtoul( pChunkHeader, NULL, 16 ); + + pNext = strstr( pNext, HTTP_HEADER_LINE_SEPARATOR ); + pNext += HTTP_HEADER_LINE_SEPARATOR_LEN; + + pBody = pNext; + + if( bodyLen > 0 ) + { + pSettings->on_body( pParser, pBody, bodyLen ); + pNext = strstr( pNext, HTTP_HEADER_LINE_SEPARATOR ); + pNext += HTTP_HEADER_LINE_SEPARATOR_LEN; + } + } + + pSettings->on_message_complete( pParser ); + + httpParserExecuteCallCount++; + return len; +} + +/* ============================ UNITY FIXTURES ============================== */ + +/* Called before each test case. */ +void setUp( void ) +{ + /* Setup global testing variables. */ + hasConnectionClose = 0; + hasConnectionKeepAlive = 0; + contentLength = ULLONG_MAX; + headerCallbackCount = 0; + sendCurrentCall = 0; + sendErrorCall = 0; + checkContentLength = 0; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_HEAD; + networkDataLen = HTTP_TEST_RESPONSE_HEAD_LENGTH; + firstPartBytes = networkDataLen; + recvCurrentCall = 0; + recvStopCall = UINT8_MAX; + httpParserExecuteCallCount = 0; + httpParsingErrno = HPE_OK; + transportInterface.recv = transportRecvSuccess; + transportInterface.send = transportSendSuccess; + transportInterface.pContext = NULL; + requestHeaders.pBuffer = httpBuffer; + requestHeaders.bufferLen = sizeof( httpBuffer ); + memcpy( requestHeaders.pBuffer, HTTP_TEST_REQUEST_HEAD_HEADERS, HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH; + memset( &response, 0, sizeof( HTTPResponse_t ) ); + headerParsingCallback.onHeaderCallback = onHeaderCallback; + headerParsingCallback.pContext = NULL; + response.pBuffer = httpBuffer; + response.bufferLen = sizeof( httpBuffer ); + response.pHeaderParsingCallback = &headerParsingCallback; + + /* Ignore third-party init functions that return void. */ + http_parser_init_Ignore(); + http_parser_settings_init_Ignore(); + http_parser_set_max_header_size_Ignore(); + http_errno_description_IgnoreAndReturn( "Dummy unit test print." ); +} + +/* ======================== Testing HTTPClient_Send ========================= */ + +/* Test successfully parsing a response to a HEAD request. The full response + * message is present in the response buffer on the first network read. */ +void test_HTTPClient_Send_HEAD_request_parse_whole_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_whole_response ); + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0U, response.bodyLen ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ), response.headersLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test successfully parsing a response to a PUT request. The full response + * message is present in the response buffer on the first network read. */ +void test_HTTPClient_Send_PUT_request_parse_whole_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_whole_response ); + + checkContentLength = 1; + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, + HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_PUT; + networkDataLen = HTTP_TEST_RESPONSE_PUT_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_PUT_LENGTH; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.headersLen ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0, response.bodyLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( 0, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test successfully parsing a response to a GET request. The full response + * message is present in the response buffer on the first network read. */ +void test_HTTPClient_Send_GET_request_parse_whole_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_whole_response ); + + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_GET_HEADERS, + HTTP_TEST_REQUEST_GET_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_GET_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_GET; + networkDataLen = HTTP_TEST_RESPONSE_GET_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_GET_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_HEADERS_LENGTH, response.headersLen ); + TEST_ASSERT_EQUAL( response.pHeaders + HTTP_TEST_RESPONSE_GET_HEADERS_LENGTH, response.pBody ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_BODY_LENGTH, response.bodyLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_CONTENT_LENGTH, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test successfully parsing a response where there are no headers. The full + * response message is present in the response buffer on the first network read. */ +void test_HTTPClient_Send_no_response_headers( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_whole_response ); + + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_NO_HEADERS; + networkDataLen = HTTP_TEST_RESPONSE_NO_HEADERS_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_NO_HEADERS_LENGTH; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0U, response.bodyLen ); + TEST_ASSERT_EQUAL( NULL, response.pHeaders ); + TEST_ASSERT_EQUAL( 0, response.headersLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( 0, response.contentLength ); + TEST_ASSERT_EQUAL( 0, response.headerCount ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test successfully parsing a response where up to the middle of a header field + * is received on the first network read, then the rest of the response on the + * second read. */ +void test_HTTPClient_Send_parse_partial_header_field( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_partial_header_field ); + + firstPartBytes = HTTP_TEST_RESPONSE_HEAD_PARTIAL_HEADER_FIELD_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0, response.bodyLen ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ), response.headersLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test successfully parsing a response where up to the middle of a header value + * is received on the first network read, then the rest of the response on the + * second read. */ +void test_HTTPClient_Send_parse_partial_header_value( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_partial_header_value ); + + firstPartBytes = HTTP_TEST_RESPONSE_HEAD_PARTIAL_HEADER_VALUE_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0, response.bodyLen ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ), response.headersLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test successfully parsing a response where up to the middle of the body + * is received on the first network read, then the rest of the response on the + * second read. */ +void test_HTTPClient_Send_parse_partial_body( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_partial_body ); + + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_GET_HEADERS, + HTTP_TEST_REQUEST_GET_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_GET_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_GET; + networkDataLen = HTTP_TEST_RESPONSE_GET_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_GET_PARTIAL_BODY_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_HEADERS_LENGTH, response.headersLen ); + TEST_ASSERT_EQUAL( response.pHeaders + HTTP_TEST_RESPONSE_GET_HEADERS_LENGTH, response.pBody ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_BODY_LENGTH, response.bodyLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_CONTENT_LENGTH, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test receiving a response where the body is of Transfer-Encoding chunked. */ +void test_HTTPClient_Send_parse_chunked_body( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_chunked_body ); + + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, + HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_CHUNKED; + networkDataLen = HTTP_TEST_RESPONSE_CHUNKED_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_CHUNKED_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_CHUNKED_HEADERS_LENGTH, response.headersLen ); + TEST_ASSERT_EQUAL( ( response.pHeaders + response.headersLen ), response.pBody ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_CHUNKED_BODY_LENGTH, response.bodyLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( 0, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_CHUNKED_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); +} + +/*-----------------------------------------------------------*/ + +/* Test a timeout is returned from the first network read. */ +void test_HTTPClient_Send_timeout_recv_immediate( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_ExpectAnyArgsAndReturn( 0 ); + + recvStopCall = 0; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_NO_RESPONSE, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a timeout is received from the second network read. In the first + * network read a partial response is received and parsed. */ +void test_HTTPClient_Send_timeout_partial_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_partial_header_field ); + http_errno_description_IgnoreAndReturn( "Dummy unit test print." ); + + firstPartBytes = HTTP_TEST_RESPONSE_HEAD_PARTIAL_HEADER_VALUE_LENGTH; + recvStopCall = 1; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_PARTIAL_RESPONSE, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test the buffer limit is reached on the network read, but the parser indicated + * the response is not complete. */ +void test_HTTPClient_Send_response_larger_than_buffer( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_execute_Stub( http_parser_execute_partial_body ); + http_errno_description_IgnoreAndReturn( "Dummy unit test print." ); + + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_GET_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_GET_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_GET_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_GET; + networkDataLen = HTTP_TEST_RESPONSE_GET_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_GET_PARTIAL_BODY_LENGTH; + response.bufferLen = HTTP_TEST_RESPONSE_GET_PARTIAL_BODY_LENGTH; + + /* For everage of no header parsing callback configured. */ + response.pHeaderParsingCallback = NULL; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test sending a request with a NULL response configured. */ +void test_HTTPClient_Send_null_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, + HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + NULL, + 0U ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a network error is returned when sending the request headers. */ +void test_HTTPClient_Send_network_error_request_headers( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + sendErrorCall = 0U; + transportInterface.send = transportSendNetworkError; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0U, + &response, + 0U ); + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a network error is returned when sending the request body. */ +void test_HTTPClient_Send_network_error_request_body( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + transportInterface.send = transportSendNetworkError; + + /* There is no Content-Length header written so the call to send an error on + * is call 1. */ + sendErrorCall = 1U; + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response, + HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG ); + + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test less bytes, of the request headers, are sent than expected. */ +void test_HTTPClient_Send_less_bytes_request_headers( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + transportInterface.send = transportSendLessThanBytesToWrite; + sendErrorCall = 0U; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test less bytes, of the request body, are sent that expected. */ +void test_HTTPClient_Send_less_bytes_request_body( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + transportInterface.send = transportSendLessThanBytesToWrite; + + /* There is no Content-Length header written so the call to send an error on + * is call 1. */ + sendErrorCall = 1U; + requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response, + HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG ); + + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test when a network error is returned when receiving the response. */ +void test_HTTPClient_Send_network_error_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_init_Ignore(); + + transportInterface.recv = transportRecvNetworkError; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test when more bytes are received than expected, when receiving a response + * from the network. */ +void test_HTTPClient_Send_too_many_bytes_response( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_init_Ignore(); + + transportInterface.recv = transportRecvMoreThanBytesToRead; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a NULL transport interface passed to the API. */ +void test_HTTPClient_Send_null_transport_interface( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + returnStatus = HTTPClient_Send( NULL, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a NULL transport send callback passed to the API. */ +void test_HTTPClient_Send_null_transport_send( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + transportInterface.send = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a NULL transport receive callback passed to the API. */ +void test_HTTPClient_Send_null_transport_recv( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + transportInterface.recv = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a NULL request headers structure passed to the API. */ +void test_HTTPClient_Send_null_request_headers( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + returnStatus = HTTPClient_Send( &transportInterface, + NULL, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a null request headers buffer passed to the API. */ +void test_HTTPClient_Send_null_request_header_buffer( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + requestHeaders.pBuffer = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a NULL response buffer passed to the API. */ +void test_HTTPClient_Send_null_response_buffer( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + response.pBuffer = NULL; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test the request headers not containing enough bytes for a valid status-line. + */ +void test_HTTPClient_Send_not_enough_request_headers( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + requestHeaders.headersLen = HTTP_MINIMUM_REQUEST_LINE_LENGTH - 1; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test a NULL request body but a non-zero requets body length. + */ +void test_HTTPClient_Send_null_request_body_nonzero_body_length( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 1, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test when the Content-Length header cannot fit into the header buffer. */ +void test_HTTPClient_Send_Content_Length_Header_Doesnt_Fit( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + requestHeaders.pBuffer = (uint8_t*)HTTP_TEST_REQUEST_PUT_HEADERS; + /* Set the length of the buffer to be the same length as the current + * amount of headers without the Content-Length. */ + requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + HTTP_TEST_REQUEST_PUT_BODY_LENGTH, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, returnStatus ); +} + +/*-----------------------------------------------------------*/ + +/* Test parsing errors are translated to the appropriate HTTP Client library + * errors. */ +void test_HTTPClient_Send_parsing_errors( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_init_Ignore(); + http_parser_settings_init_Ignore(); + http_parser_execute_Stub( http_parser_execute_error ); + http_errno_description_IgnoreAndReturn( "Dummy unit test print." ); + + httpParsingErrno = HPE_HEADER_OVERFLOW; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED, returnStatus ); + + httpParsingErrno = HPE_INVALID_CHUNK_SIZE; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER, returnStatus ); + + httpParsingErrno = HPE_CLOSED_CONNECTION; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA, returnStatus ); + + httpParsingErrno = HPE_INVALID_VERSION; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION, returnStatus ); + + httpParsingErrno = HPE_INVALID_STATUS; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE, returnStatus ); + + httpParsingErrno = HPE_STRICT; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + + httpParsingErrno = HPE_INVALID_CONSTANT; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + + httpParsingErrno = HPE_LF_EXPECTED; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + + httpParsingErrno = HPE_INVALID_HEADER_TOKEN; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + + httpParsingErrno = HPE_INVALID_CONTENT_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH, returnStatus ); + + httpParsingErrno = HPE_UNEXPECTED_CONTENT_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH, returnStatus ); + + httpParsingErrno = HPE_UNKNOWN; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_PARSER_INTERNAL_ERROR, returnStatus ); +} diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 36426192dc..1aa17c542f 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -1218,7 +1218,7 @@ void test_Http_ReadHeader_With_HttpParser_Internal_Error() strlen( HEADER_IN_BUFFER ), &pValueLoc, &valueLen ); - TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); + TEST_ASSERT_EQUAL( HTTP_PARSER_INTERNAL_ERROR, retCode ); } /** From a1dbca666e0b9a88a6a0a0deaaf9d3616d95bab3 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Thu, 4 Jun 2020 20:51:18 -0700 Subject: [PATCH 533/844] Add spell scripts from v4_beta (#972) --- tools/spell/ablexicon | 96 +++++++++++++++++ tools/spell/extract-comments | 41 +++++++ tools/spell/find-unknown-comment-words | 143 +++++++++++++++++++++++++ 3 files changed, 280 insertions(+) create mode 100755 tools/spell/ablexicon create mode 100755 tools/spell/extract-comments create mode 100755 tools/spell/find-unknown-comment-words diff --git a/tools/spell/ablexicon b/tools/spell/ablexicon new file mode 100755 index 0000000000..c79c018d91 --- /dev/null +++ b/tools/spell/ablexicon @@ -0,0 +1,96 @@ +#!/bin/bash +# +# ablexicon - Compare an input list of words against a dictionary and +# optional lexicon. If any words are in neither the dictionary nor the +# lexicon, log them to stdout. +# +set -e +set -f + +function usage () { + echo "Find occurrences of non-dictionary/lexicon words" + echo "" + echo "Usage:" + echo " ${0##*/} [options]" + echo "" + echo "Options:" + echo " -f, --file source text (defaults to /dev/fd/0)" + echo " -l, --lexicon lexicon file (one word per line)" + echo " -h, --help display this help" + exit 1 +} + +# +# Verify that required commands are present +# +REQUIRED=( "spell" "getopt" ) +for i in "${REQUIRED[@]}" +do + command -v $i"" >/dev/null + if [ $? -ne "0" ] + then + echo "'"$i"' must be installed, exiting...">&2 + exit 1 + fi +done + +GETOPT_OUT=`getopt -o hf:l: --long help,file:,lexicon: -n "${0##*/}" -- "$@"` +if [ $? != 0 ] +then + echo "Exiting..." >&2 + exit 1 +fi + +eval set -- "$GETOPT_OUT" + +INFILE=/dev/fd/0 +LEXICON=/dev/null +while true; do + case "$1" in + -h | --help ) usage $0 ;; + -f | --file ) INFILE="$2"; shift 2 ;; + -l | --lexicon ) LEXICON="$2"; shift 2 ;; + -- ) shift; break ;; + * ) break ;; + esac +done + +if [ ! -f $INFILE"" ] && [ $INFILE"" != /dev/fd/0 ] +then + echo "Invalid input file" + usage +fi +# +# Read the lexicon into an array +# +readarray -t lexicon < $LEXICON"" +lexicon_size="${#lexicon[@]}" + +# +# Search for all input words in the dictionary +# and sort the output +# +for word in `cat $INFILE"" | spell | sort -u` +do + # + # Search for each remaining word in the lexicon + # + found="false" + i="0" + while [[ "$i" -lt "$lexicon_size" ]] && [ "$found" == "false" ] + do + if [ "${lexicon[i]}" == "$word" ] + then + found="true" + fi + i=$((i+1)) + done + if [ $found"" == "false" ] + then + # + # The word is neither in the dictionary nor the lexicon, send + # it to stdout. + # + echo $word + fi +done diff --git a/tools/spell/extract-comments b/tools/spell/extract-comments new file mode 100755 index 0000000000..861e77e5d1 --- /dev/null +++ b/tools/spell/extract-comments @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Extract comments from C/C++ files +# +set -e +set -f + +function usage () { + echo "Extract comments from C/C++ files" + echo "" + echo "usage: "${0##*/}" file-list" + exit 1 +} + +if [ $# -lt 1 ] +then + usage $0 +fi + +if [ $1 = "-h" ] || [ $1 == "--help" ] +then + usage $0 +fi + +while test $# -gt 0 +do + if [ ! -f $1 ] + then + echo $0": '"$1"' is not a file." 2>/dev/null + exit 1 + fi +# +# Extract all words from C/C++ language comments; add line +# numbers to aid in searching. +# +# NOTE: This has some limitations. For example, it prints +# non-comment text at the beginning of a comment line. +# + nl -ba $1 | awk '/\/\// {print $0}; /\/\*/ {comment=1; if(comment) print $0}; /\*\// {comment=0}' + shift +done diff --git a/tools/spell/find-unknown-comment-words b/tools/spell/find-unknown-comment-words new file mode 100755 index 0000000000..028df0cc83 --- /dev/null +++ b/tools/spell/find-unknown-comment-words @@ -0,0 +1,143 @@ +#!/bin/bash +# +# Locate unknown words in C/C++ comments. Uses "extract-comments" +# and "ablexicon" scripts. +# +set -o nounset +set -o pipefail +set -o errexit +set -f + +BLUE="\e[1;34m" +GREEN="\e[1;32m" +DEFAULTFG="\e[39m" + +function usage () { + echo "Find unknown words in C/C++ comments" + echo "" + echo "Usage:" + echo " ${0##*/} [options]" + echo "" + echo "Options:" + echo " -d, --directory directory to scan (defaults to .)" + echo " -l, --lexicon lexicon file (one word per line, default 'lexicon.txt')" + echo " -t, --terse terse output only (enabled if no lexicon available)" + echo " -h, --help display this help" + exit 1 +} + +# +# Verify that required commands are present +# +REQUIRED=( "extract-comments" "ablexicon" "getopt" ) +for i in "${REQUIRED[@]}" +do + command -v $i"" >/dev/null + if [ $? -ne "0" ] + then + echo "Can't find '"$i"' , exiting...">&2 + exit 1 + fi +done + +GETOPT_OUT=`getopt -o htd:l: --long help,terse,directory:,lexicon: -n "${0##*/}" -- "$@"` +if [ $? != 0 ] +then + echo "Exiting..." >&2 + exit 1 +fi + +eval set -- "$GETOPT_OUT" + +DIRNAME=/dev/fd/0 +LEXICON= +STATUS= +TERSE= +while true; do + case "$1" in + -h | --help ) usage $0 ;; + -t | --terse ) TERSE=1; shift ;; + -d | --directory ) DIRNAME="$2"; shift 2 ;; + -l | --lexicon ) LEXICON="$2"; shift 2 ;; + -- ) shift; break ;; + * ) break ;; + esac +done + +if [ ! -d $DIRNAME"" ] +then + echo "Invalid directory: "$DIRNAME + usage +fi + +if [ $LEXICON"" = "" ] +then + if [ -f $DIRNAME/lexicon.txt ] + then + LEXICON=$DIRNAME/lexicon.txt + else + LEXICON=/dev/null + TERSE=1 + fi +fi + +TMPFILE=${0##*/}-$USER-$RANDOM +unknowns=( "not-used" ) # get around empty array with nounset +extract-comments `find $DIRNAME -name \*.[ch]` | + tr [:upper:] [:lower:] | + grep -o -E '[a-zA-Z]+' | + ablexicon -l $LEXICON > $TMPFILE +readarray -O 1 -t unknowns < $TMPFILE +rm -f $TMPFILE + +for word in "${unknowns[@]}" +do + if [ $word"" == "not-used" ] + then + continue + fi + + if [ $TERSE"" != "" ] + then + echo $word + continue + fi + + for file in `find $DIRNAME -name \*.[ch]` + do + # Disable errexit here, extract-comments can return non-zero + set +e + # + # A little inefficient here; we will grep twice, once to detect + # the unknown word and another to print it with color highlighting. + # If there's a way to preserve ANSI color output with the first + # search and reuse it within the if statement (I gave up trying + # to find one after a few minutes), that would be nice. + # + extract-comments $file | grep -iw $word > /dev/null + if [ $? == "0" ] + then + if [ $STATUS"" != "1" ] + then + echo -e $GREEN"############################################################################"$DEFAULTFG + echo -e $GREEN"#"$DEFAULTFG + echo -e $GREEN"# Unknown word(s) found. Please either correct the spelling or add them"$DEFAULTFG + echo -e $GREEN"# to the lexicon file '"$LEXICON"'".$DEFAULTFG + echo -e $GREEN"#"$DEFAULTFG + echo -e $GREEN"############################################################################"$DEFAULTFG + STATUS=1 # Return non-zero status if any unidentified words are found + fi + echo "" + echo -e $BLUE$file$DEFAULTFG + echo "" + extract-comments $file | grep --color=always -iw $word | GREP_COLORS="mt=01;32" grep --color=always -E -e '^[ \t]*[0-9]+' + fi + # Re-enable errexit + set -o errexit + done +done + +if [ $STATUS"" = "1" ] +then + exit 1 +fi From b878817c86d07c598964ad1f832379bbe396aa1d Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Mon, 8 Jun 2020 17:31:58 -0700 Subject: [PATCH 534/844] Add HTTP plaintext demo (#908) * Implement HTTP plaintext demo * Address PR comments * Change comments * Change comment again * More comments * Add ifdef guards around _sendHttpRequest * Add CMakeLists.txt file for demo * Make some changes for the macros * Remove Makefile * Add server to _sendHttpRequest * Update cmake for this demo * segfualt fix * Add demo config * fix demo with strlen instead of sizeof *facepalm* * Need to re-establish connection on every request * Fix warnings * Add response status * Use keep-alive connection instead of close * Address PR comments * Update comments * Use strncpy for requestBodyBuffer * Revert network context to original * Address PR comments * Remove warnings * Update uint8_t for ip address to char * Add logging messages * Update transport to transportInterface when passing arg * Remove accidental change to http_client.h * Update doc for transportSend/Recv * Update doc for transportSend/Recv * Update docstring for sendHttpRequest * Rename pTransport to pTransportInterface * Doc at the top of the .c file * Remove platform/CMakeLists.txt * Add demo config * Address all PR comments * Update docstring for response * Remove = {0} initializers * Update main to include argc argv params * Move macros in http_demo_plaintext.c to demo_config.h * Address PR comments * Updates based on server demo * Address PR comments * Add missing period * Remove (int32_t) cast from recv * Add missing extern int errno * Add local http server for running the demos * Update comment * Update step 5 of README.md * Address PR comments --- README.md | 5 +- demos/http/http_demo_plaintext/CMakeLists.txt | 27 + demos/http/http_demo_plaintext/demo_config.h | 90 +++ demos/http/http_demo_plaintext/http_config.h | 46 ++ .../http_demo_plaintext/http_demo_plaintext.c | 531 ++++++++++++++++++ libraries/standard/http/CMakeLists.txt | 3 +- tools/http-echo-server/http_server.py | 69 +++ 7 files changed, 768 insertions(+), 3 deletions(-) create mode 100644 demos/http/http_demo_plaintext/CMakeLists.txt create mode 100644 demos/http/http_demo_plaintext/demo_config.h create mode 100644 demos/http/http_demo_plaintext/http_config.h create mode 100644 demos/http/http_demo_plaintext/http_demo_plaintext.c create mode 100644 tools/http-echo-server/http_server.py diff --git a/README.md b/README.md index 4d2fd7653d..8c8cc0fbda 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,7 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. 1. Create build directory: `mkdir build && cd build` 1. Run *cmake* while inside build directory: `cmake ..` 1. Run this command to build the demos: `make` -1. Go to the `build/bin` directory to see executables. +1. Go to the `build/bin` directory and run any demo executables from there. +1. To run demos prefixed with `http_`: + 1. Install `Python 3` if it is not yet installed in your system: `sudo apt-get install python3` + 1. Run the following from the root directory of this repository: `python3 tools/http-echo-server/http_server.py &` diff --git a/demos/http/http_demo_plaintext/CMakeLists.txt b/demos/http/http_demo_plaintext/CMakeLists.txt new file mode 100644 index 0000000000..5c4e4af26a --- /dev/null +++ b/demos/http/http_demo_plaintext/CMakeLists.txt @@ -0,0 +1,27 @@ +set( DEMO_NAME "http_demo_plaintext" ) +# Demo target. +add_executable(${DEMO_NAME}) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + http +) + +target_include_directories( + http + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) diff --git a/demos/http/http_demo_plaintext/demo_config.h b/demos/http/http_demo_plaintext/demo_config.h new file mode 100644 index 0000000000..031d220ab5 --- /dev/null +++ b/demos/http/http_demo_plaintext/demo_config.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H_ +#define DEMO_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#define LIBRARY_LOG_NAME "DEMO" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief HTTP server host name. + * + * @note A local HTTP server is used for this demo. Instructions for setting + * this up can be found in the top-level README.md file. + */ +#define SERVER_HOST "localhost" + +/** + * @brief HTTP server port number. + * + * @note In general, port 80 is for plaintext HTTP connections. However, + * the default plaintext port from the local http server is used below. + */ +#define SERVER_PORT 8080 + +/** + * @brief Paths for different HTTP methods for specified host. + */ +#define GET_PATH "/get" +#define HEAD_PATH "/get" +#define PUT_PATH "/put" +#define POST_PATH "/post" + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 1000 ) + +/** + * @brief The length in bytes of the user buffer. + */ +#define USER_BUFFER_LENGTH ( 1024 ) + +/** + * @brief Request body to send for PUT and POST requests in this demo. + */ +#define REQUEST_BODY "Hello, world!" + +/** + * @brief Length of the request body. + */ +#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) + +#endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/http/http_demo_plaintext/http_config.h b/demos/http/http_demo_plaintext/http_config.h new file mode 100644 index 0000000000..6a9f558a09 --- /dev/null +++ b/demos/http/http_demo_plaintext/http_config.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef HTTP_CONFIG_H +#define HTTP_CONFIG_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the HTTP library. */ +#define LIBRARY_LOG_NAME "HTTP" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef HTTP_CONFIG_H */ diff --git a/demos/http/http_demo_plaintext/http_demo_plaintext.c b/demos/http/http_demo_plaintext/http_demo_plaintext.c new file mode 100644 index 0000000000..cd9a7335bb --- /dev/null +++ b/demos/http/http_demo_plaintext/http_demo_plaintext.c @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include + +/* POSIX socket includes. */ +#include +#include +#include +#include +#include + +#include +#include + +/* HTTP API header. */ +#include "http_client.h" + +/* Demo config header. */ +#include "demo_config.h" + +/** + * @brief Length of an IPv6 address when converted to hex digits. + */ +#define IPV6_ADDRESS_STRING_LEN ( 40 ) + +/** + * @brief Defined by transport layer to check send or receive error. + */ +extern int errno; + +/** + * @brief A string to store the resolved IP address from the host name. + */ +static char resolvedIpAddr[ IPV6_ADDRESS_STRING_LEN ]; + +/** + * @brief A buffer used in the demo for storing HTTP request headers and + * HTTP response headers and body. + * + * @note This demo shows how the same buffer can be re-used for storing the HTTP + * response after the HTTP request is sent out. However, the user can also + * decide to use separate buffers for storing the HTTP request and response. + */ +static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; + +/** + * @brief Definition of the HTTP network context. + * + * @note An integer is used to store the descriptor of the socket. + */ +struct HTTPNetworkContext +{ + int tcpSocket; +}; + +/** + * @brief Structure based on the definition of the HTTP network context. + */ +static HTTPNetworkContext_t socketContext; + +/** + * @brief The HTTP Client library transport layer interface. + */ +static HTTPTransportInterface_t transportInterface; + +/** + * @brief Represents header data that will be sent in an HTTP request. + */ +static HTTPRequestHeaders_t requestHeaders; + +/** + * @brief Configurations of the initial request headers that are passed to + * #HTTPClient_InitializeRequestHeaders. + */ +static HTTPRequestInfo_t requestInfo; + +/** + * @brief Represents a response returned from an HTTP server. + */ +static HTTPResponse_t response; + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs a DNS lookup on the given host name, then establishes a TCP + * connection to the server. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * @param[out] pTcpSocket Pointer to TCP socket file descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ); + +/** + * @brief The transport send function that defines the transport interface. + * + * This is passed as the #HTTPTransportInterface.send function and used to + * send data over the network. + * + * @param[in] pContext User defined context (TCP socket for this demo). + * @param[in] pBuffer Buffer containing the bytes to send over the network stack. + * @param[in] bytesToSend Number of bytes to write to the network. + * + * @return Number of bytes sent if successful; otherwise negative value on error. + */ +static int32_t transportSend( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToSend ); + +/** + * @brief The transport receive function that defines the transport interface. + * + * This is passed as the #HTTPTransportInterface.recv function used for reading + * data received from the network. + * + * @param[in] pContext User defined context (TCP socket for this demo). + * @param[out] pBuffer Buffer to read network data into. + * @param[in] bytesToRead Number of bytes requested from the network. + * + * @return Number of bytes received if successful; otherwise negative value on error. + */ +static int32_t transportRecv( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief Send an HTTP request based on a specified method and path, then + * print the response received from the server. + * + * @param[in] pTransportInterface The transport interface for making network calls. + * @param[in] pHost The host name of the server. + * @param[in] pMethod The HTTP request method. + * @param[in] pPath The Request-URI to the objects of interest. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, + const char * pHost, + const char * pMethod, + const char * pPath ); + +/*-----------------------------------------------------------*/ + +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ) +{ + int returnStatus = EXIT_SUCCESS; + struct addrinfo hints, * pIndex, * pListHead = NULL; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + struct timeval transportTimeout; + + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0, sizeof( hints ) ); + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* Perform a DNS lookup on the given host name. */ + returnStatus = getaddrinfo( pServer, NULL, &hints, &pListHead ); + + if( returnStatus != -1 ) + { + LogInfo( ( "Performing DNS lookup: Host=%s.", + SERVER_HOST ) ); + + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( *pTcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + inet_ntop( pServerInfo->sa_family, + &( ( struct sockaddr_in * ) pServerInfo )->sin_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + inet_ntop( pServerInfo->sa_family, + &( ( struct sockaddr_in6 * ) pServerInfo )->sin6_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + + LogInfo( ( "Attempting to connect to server: Host=%s, IP address=%s.", + SERVER_HOST, resolvedIpAddr ) ); + + returnStatus = connect( *pTcpSocket, pServerInfo, serverInfoLength ); + + if( returnStatus == -1 ) + { + LogError( ( "Failed to connect to server: Host=%s, IP address=%s.", + SERVER_HOST, resolvedIpAddr ) ); + close( *pTcpSocket ); + } + else + { + LogInfo( ( "Connected to IP address: %s.", + resolvedIpAddr ) ); + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + returnStatus = EXIT_FAILURE; + LogError( ( "Could not connect to any resolved IP address from %.*s.\n", + ( int ) strlen( pServer ), + pServer ) ); + } + else + { + returnStatus = EXIT_SUCCESS; + LogInfo( ( "Established TCP connection: Server=%.*s.\n", + ( int ) strlen( pServer ), + pServer ) ); + } + } + else + { + LogError( ( "Could not resolve host %.*s.\n", + ( int ) strlen( pServer ), + pServer ) ); + returnStatus = EXIT_FAILURE; + } + + /* Set the socket option for send and receive timeouts. */ + if( returnStatus == EXIT_SUCCESS ) + { + transportTimeout.tv_sec = 0; + transportTimeout.tv_usec = ( TRANSPORT_SEND_RECV_TIMEOUT_MS * 1000 ); + + /* Set the receive timeout. */ + if( setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_RCVTIMEO, + ( char * ) &transportTimeout, + sizeof( transportTimeout ) ) < 0 ) + { + LogError( ( "Setting socket receive timeout failed." ) ); + returnStatus = EXIT_FAILURE; + } + + /* Set the send timeout. */ + if( setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_SNDTIMEO, + ( char * ) &transportTimeout, + sizeof( transportTimeout ) ) < 0 ) + { + LogError( ( "Setting socket send timeout failed." ) ); + returnStatus = EXIT_FAILURE; + } + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int32_t transportSend( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToSend ) +{ + int32_t bytesSent = 0; + + bytesSent = send( pContext->tcpSocket, pBuffer, bytesToSend, 0 ); + + if( bytesSent < 0 ) + { + /* Check if it was time out */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate that send had timed out. */ + bytesSent = 0; + } + } + + return bytesSent; +} + +/*-----------------------------------------------------------*/ + +static int32_t transportRecv( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + + bytesReceived = recv( pContext->tcpSocket, pBuffer, bytesToRecv, 0 ); + + if( bytesReceived == 0 ) + { + /* Server closed the connection, treat it as an error. */ + bytesReceived = -1; + } + else if( bytesReceived < 0 ) + { + /* Check if it was time out. */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate nothing to receive. */ + bytesReceived = 0; + } + } + else + { + /* EMPTY else */ + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, + const char * pHost, + const char * pMethod, + const char * pPath ) +{ + int returnStatus = EXIT_SUCCESS; + HTTPStatus_t httpStatus = HTTP_SUCCESS; + + /* Initialize the request object. */ + requestInfo.pHost = pHost; + requestInfo.hostLen = strlen( pHost ); + requestInfo.method = pMethod; + requestInfo.methodLen = strlen( pMethod ); + requestInfo.pPath = pPath; + requestInfo.pathLen = strlen( pPath ); + + /* Set "Connection" HTTP header to "keep-alive" so that multiple requests + * can be sent over the same established TCP connection. */ + requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + + /* Set the buffer used for storing request headers. */ + requestHeaders.pBuffer = userBuffer; + requestHeaders.bufferLen = USER_BUFFER_LENGTH; + + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, + &requestInfo ); + + if( httpStatus == HTTP_SUCCESS ) + { + /* Initialize the response object. The same buffer used for storing + * request headers is reused here. */ + response.pBuffer = userBuffer; + response.bufferLen = USER_BUFFER_LENGTH; + + LogInfo( ( "Sending HTTP %s request to %s%s...", + pMethod, SERVER_HOST, pPath ) ); + LogInfo( ( "Request Headers:\n%.*s", + ( int32_t ) requestHeaders.headersLen, + ( char * ) requestHeaders.pBuffer ) ); + LogInfo( ( "Request Body:\n%.*s\n", + ( int32_t ) REQUEST_BODY_LENGTH, + REQUEST_BODY ) ); + /* Send the request and receive the response. */ + httpStatus = HTTPClient_Send( pTransportInterface, + &requestHeaders, + ( uint8_t * ) REQUEST_BODY, + REQUEST_BODY_LENGTH, + &response, + 0 ); + } + else + { + LogError( ( "Failed to initialize HTTP request headers: Error=%s.", + HTTPClient_strerror( httpStatus ) ) ); + } + + if( httpStatus == HTTP_SUCCESS ) + { + LogInfo( ( "Received HTTP response from %s%s...", + SERVER_HOST, pPath ) ); + LogInfo( ( "Response Headers:\n%.*s", + ( int32_t ) response.headersLen, + response.pHeaders ) ); + LogInfo( ( "Response Status:\n%u", + response.statusCode ) ); + LogInfo( ( "Response Body:\n%.*s\n", + ( int32_t ) response.bodyLen, + response.pBody ) ); + } + else + { + LogError( ( "Failed to send HTTP %s request to %s%s: Error=%s.", + pMethod, SERVER_HOST, pPath, HTTPClient_strerror( httpStatus ) ) ); + } + + if( httpStatus != HTTP_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * This example resolves a domain, then establishes a TCP connection with an + * HTTP server to demonstrate HTTP request/response communication without using + * an encrypted channel (i.e. without TLS). After which, HTTP Client library API + * is used to send a GET, HEAD, PUT, and POST request in that order. For each + * request, the HTTP response from the server (or an error code) is logged. + * + * @note This example is single-threaded and uses statically allocated memory. + * + */ +int main( int argc, + char ** argv ) +{ + int returnStatus = EXIT_SUCCESS; + + ( void ) argc; + ( void ) argv; + + /**************************** Connect. ******************************/ + + /* Establish TCP connection. */ + returnStatus = connectToServer( SERVER_HOST, SERVER_PORT, &socketContext.tcpSocket ); + + /* Define the transport interface. */ + if( returnStatus == EXIT_SUCCESS ) + { + transportInterface.recv = transportRecv; + transportInterface.send = transportSend; + transportInterface.pContext = &socketContext; + } + + /*********************** Send HTTPS request. ************************/ + + /* Send GET Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + SERVER_HOST, + HTTP_METHOD_GET, + GET_PATH ); + } + + /* Send HEAD Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + SERVER_HOST, + HTTP_METHOD_HEAD, + HEAD_PATH ); + } + + /* Send PUT Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + SERVER_HOST, + HTTP_METHOD_PUT, + PUT_PATH ); + } + + /* Send POST Request. */ + if( returnStatus != EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + SERVER_HOST, + HTTP_METHOD_POST, + POST_PATH ); + } + + /************************** Disconnect. *****************************/ + + if( socketContext.tcpSocket != -1 ) + { + ( void ) shutdown( socketContext.tcpSocket, SHUT_RDWR ); + ( void ) close( socketContext.tcpSocket ); + } + + return returnStatus; +} diff --git a/libraries/standard/http/CMakeLists.txt b/libraries/standard/http/CMakeLists.txt index 40e80536fa..39c041b70c 100644 --- a/libraries/standard/http/CMakeLists.txt +++ b/libraries/standard/http/CMakeLists.txt @@ -10,8 +10,7 @@ add_library( http # HTTP public include path. target_include_directories( http PUBLIC ${HTTP_INCLUDE_PUBLIC_DIRS} - ${LOGGING_INCLUDE_DIRS} - ${CMAKE_CURRENT_LIST_DIR}/utest ) + ${LOGGING_INCLUDE_DIRS} ) # HTTP private include path. target_include_directories( http PRIVATE ${HTTP_INCLUDE_PRIVATE_DIRS} ) diff --git a/tools/http-echo-server/http_server.py b/tools/http-echo-server/http_server.py new file mode 100644 index 0000000000..a45883db5e --- /dev/null +++ b/tools/http-echo-server/http_server.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +HTTP server that responds with information about the request +in JSON format +Usage: + ./server.py [] +""" + +from http.server import BaseHTTPRequestHandler, HTTPServer +import logging +import json + + +class ServerRequestHandler(BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' + + def _set_response(self): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.end_headers() + + def do_GET(self): + response_body_dict = { + 'Method': str(self.command), + 'Path': str(self.path), + 'Request Headers': str(self.headers), + } + + # Get the Content-Length header value from the request and use that + # to read the request body from the stream + content_len = int(self.headers.get('Content-Length') + ) if self.headers.get('Content-Length') else 0 + if content_len: + response_body_dict['Request Body'] = self.rfile.read( + content_len).decode('utf8') + + response_body_str = json.dumps(response_body_dict, indent=4) + + logging.info('Received request:\n{}'.format(response_body_str)) + + self._set_response() + if str(self.command) != "HEAD": + self.wfile.write(str.encode(response_body_str)) + + do_PUT = do_POST = do_HEAD = do_GET + + +def run(server_class=HTTPServer, + handler_class=ServerRequestHandler, + port=8080): + logging.basicConfig(level=logging.INFO) + server_address = ('', port) + httpd = server_class(server_address, handler_class) + logging.info('Starting http server...') + try: + httpd.serve_forever() + except (KeyboardInterrupt, SystemExit): + pass + logging.info('Stopping http server...') + httpd.server_close() + + +if __name__ == '__main__': + from sys import argv + + if len(argv) == 2: + run(port=int(argv[1])) + else: + run() From 717882f3882a02c664bf1fdf6696c3fb00cd8d79 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Mon, 8 Jun 2020 19:06:53 -0700 Subject: [PATCH 535/844] Fix: HTTP header context initialization and usage of transport send (#985) * Fix findHeaderContext* pContext incorrectly set to NULL * Remove assumption that transport sent can send all bytes in one invokation * Address PR comments * Remove redundant setting of header context * Fix log format * Fix wrong parameters being passed to transport send * Update test_HTTPClient_Send_less_bytes_request_body and test_HTTPClient_Send_less_bytes_request_headers tests to succeed * Pass correct params to HTTPClient_Send for test_HTTPClient_Send_less_bytes_request_body test * Updates to http_send_utest.c Delete extraneous comment about Content-Length. Change sendErrorCall to sendPartialCall so for correctness on the variable usage. * Return error if transportStatus > bytesRemaining * Update http_send_utest for coverage on transport send error * transportSendMoreThanBytesToRead -> transportSendMoreThanBytesToWrite * Fix log message for transportSTatus > bytesREmaining * Fix %ul to %lu * Update http_client.c Changed data to bytes * Update log messages Co-authored-by: SarenaAWS <6563840+sarenameas@users.noreply.github.com> --- libraries/standard/http/src/http_client.c | 81 +++++++----- .../standard/http/utest/http_send_utest.c | 124 ++++++++++++++---- 2 files changed, 149 insertions(+), 56 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 578bb4af95..eea62229c3 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -886,7 +886,7 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) case HPE_HEADER_OVERFLOW: LogError( ( "Response parsing error: Header byte limit " - "exceeded: HeaderByteLimit=%d.", + "exceeded: HeaderByteLimit=%d", HTTP_MAX_RESPONSE_HEADERS_SIZE_BYTES ) ); returnStatus = HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED; break; @@ -1518,36 +1518,57 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, size_t dataLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; + const uint8_t * pIndex = pData; int32_t transportStatus = 0; + size_t bytesRemaining = dataLen; assert( pTransport != NULL ); assert( pTransport->send != NULL ); assert( pData != NULL ); - transportStatus = pTransport->send( pTransport->pContext, - pData, - dataLen ); - - if( transportStatus < 0 ) - { - LogError( ( "Failed to send HTTP data: Transport send()" - " returned error: TransportStatus=%d", - transportStatus ) ); - returnStatus = HTTP_NETWORK_ERROR; - } - else if( ( size_t ) transportStatus != dataLen ) + /* Loop until all data is sent. */ + while( bytesRemaining > 0UL ) { - LogError( ( "Failed to send HTTP data: Transport layer " - "did not send the required bytes: RequiredBytes=%lu" - ", SentBytes=%d.", - ( unsigned long ) dataLen, - transportStatus ) ); - returnStatus = HTTP_NETWORK_ERROR; + transportStatus = pTransport->send( pTransport->pContext, + pIndex, + bytesRemaining ); + + /* A transport status of less than zero is an error. */ + if( transportStatus < 0 ) + { + LogError( ( "Failed to send HTTP data: Transport send()" + " returned error: TransportStatus=%d", + transportStatus ) ); + returnStatus = HTTP_NETWORK_ERROR; + break; + } + else if( ( size_t ) transportStatus > bytesRemaining ) + { + LogError( ( "Failed to send HTTP data: Transport send()" + " wrote more data than what was expected: " + "BytesSent=%d, BytesRemaining=%lu", + transportStatus, + bytesRemaining ) ); + returnStatus = HTTP_NETWORK_ERROR; + break; + } + else + { + bytesRemaining -= ( size_t ) transportStatus; + pIndex += transportStatus; + LogDebug( ( "Sent HTTP data over the transport: " + "BytesSent=%d, BytesRemaining=%lu, " + "TotalBytesSent=%d", + transportStatus, + bytesRemaining, + dataLen - bytesRemaining ) ); + } } - else + + if( returnStatus == HTTP_SUCCESS ) { - LogDebug( ( "Sent HTTP data over the transport: BytesSent " - "=%d.", + LogDebug( ( "Sent HTTP data over the transport: " + "BytesSent=%d", transportStatus ) ); } @@ -1665,7 +1686,7 @@ HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, if( transportStatus < 0 ) { LogError( ( "Failed to receive HTTP data: Transport recv() " - "returned error: TransportStatus=%d.", + "returned error: TransportStatus=%d", transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } @@ -1674,8 +1695,8 @@ HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, /* There is a bug in the transport recv if more bytes are reported * to have been read than the bytes asked for. */ LogError( ( "Failed to receive HTTP data: Transport recv() " - " read more bytes than requested: BytesRead=%d, " - "RequestedBytes=%lu", + " read more bytes than requested: BytesReceived=%d, " + "BytesRequested=%lu", transportStatus, ( unsigned long ) bufferLen ) ); returnStatus = HTTP_NETWORK_ERROR; @@ -1684,7 +1705,7 @@ HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, { /* Some or all of the specified data was received. */ *pBytesReceived = ( size_t ) ( transportStatus ); - LogDebug( ( "Received data from the transport: BytesReceived=%d.", + LogDebug( ( "Received data from the transport: BytesReceived=%d", transportStatus ) ); } else @@ -1939,6 +1960,8 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, assert( pFieldLoc != NULL ); assert( fieldLen > 0u ); + pContext = ( findHeaderContext_t * ) pHttpParser->data; + assert( pContext->pField != NULL ); assert( pContext->fieldLen > 0u ); @@ -1946,8 +1969,6 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, assert( pContext->fieldFound == 0u ); assert( pContext->valueFound == 0u ); - pContext = ( findHeaderContext_t * ) pHttpParser->data; - /* Check whether the parsed header matches the header we are looking for. */ if( ( fieldLen == pContext->fieldLen ) && ( memcmp( pContext->pField, ( const uint8_t * ) pFieldLoc, fieldLen ) == 0 ) ) @@ -1980,13 +2001,13 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, assert( pVaLueLoc != NULL ); assert( valueLen > 0u ); + pContext = ( findHeaderContext_t * ) pHttpParser->data; + assert( pContext->pField != NULL ); assert( pContext->fieldLen > 0u ); assert( pContext->pValueLoc != NULL ); assert( pContext->pValueLen != NULL ); - pContext = ( findHeaderContext_t * ) pHttpParser->data; - /* The header value found flag should not be set. */ assert( pContext->valueFound == 0u ); diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index 2934a7a010..bc4804ed66 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -101,7 +101,7 @@ #define HTTP_TEST_RESPONSE_GET \ HTTP_TEST_RESPONSE_HEAD \ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopq" -#define HTTP_TEST_RESPONSE_GET_LENGTH ( sizeof( HTTP_TEST_RESPONSE_GET ) - 1U) +#define HTTP_TEST_RESPONSE_GET_LENGTH ( sizeof( HTTP_TEST_RESPONSE_GET ) - 1U ) #define HTTP_TEST_RESPONSE_GET_HEADER_COUNT HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT #define HTTP_TEST_RESPONSE_GET_HEADERS_LENGTH ( HTTP_TEST_RESPONSE_HEAD_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1U ) ) #define HTTP_TEST_RESPONSE_GET_BODY_LENGTH HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH @@ -166,6 +166,10 @@ static uint8_t checkContentLength = 0; * to return an error from. */ static uint8_t sendErrorCall = 0; +/* The test sets this variable to indicate at which call count of transport send + * to send less bytes than indicated. */ +static uint8_t sendPartialCall = 0; + /* The network data to receive. */ static uint8_t * pNetworkData = NULL; /* The length of the network data to receive. */ @@ -239,7 +243,7 @@ static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, if( sendCurrentCall == 0U ) { size_t contentLengthAndHeaderEndLen = HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED_LENGTH; - char* pContentLengthStart = ( ( (char *)pBuffer ) + bytesToWrite ) - contentLengthAndHeaderEndLen; + char * pContentLengthStart = ( ( ( char * ) pBuffer ) + bytesToWrite ) - contentLengthAndHeaderEndLen; TEST_ASSERT_GREATER_OR_EQUAL( contentLengthAndHeaderEndLen, bytesToWrite ); TEST_ASSERT_EQUAL_MEMORY( HTTP_TEST_REQUEST_PUT_CONTENT_LENGTH_EXPECTED, pContentLengthStart, @@ -272,8 +276,9 @@ static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, } /* Application transport send interface that returns less bytes than expected - * depending on the call count. Set sendErrorCall to 0 to return an error on the - * first call. Set sendErrorCall to 1 to return an error on the second call. */ + * depending on the call count. Set sendPartialCall to 0 to return less bytes on + * the first call. Set sendPartialCall to 1 to return less bytes on the second + * call. */ static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, const void * pBuffer, size_t bytesToWrite ) @@ -282,7 +287,7 @@ static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContex ( void ) pBuffer; int32_t retVal = bytesToWrite; - if( sendErrorCall == sendCurrentCall ) + if( sendPartialCall == sendCurrentCall ) { retVal -= 1; } @@ -291,6 +296,18 @@ static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContex return retVal; } +/* Application transport send that writes more bytes than expected. */ +static int32_t transportSendMoreThanBytesToWrite( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToWrite ) +{ + ( void ) pContext; + ( void ) pBuffer; + + return( bytesToWrite + 1 ); +} + + /* Application transport receive interface that sends the bytes specified in * firstPartBytes on the first call, then sends the rest of the response in the * second call. The response to send is set in pNetworkData and the current @@ -709,6 +726,7 @@ void setUp( void ) headerCallbackCount = 0; sendCurrentCall = 0; sendErrorCall = 0; + sendPartialCall = 0; checkContentLength = 0; pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_HEAD; networkDataLen = HTTP_TEST_RESPONSE_HEAD_LENGTH; @@ -777,9 +795,9 @@ void test_HTTPClient_Send_PUT_request_parse_whole_response( void ) http_parser_execute_Stub( http_parser_execute_whole_response ); checkContentLength = 1; - memcpy( requestHeaders.pBuffer, - HTTP_TEST_REQUEST_PUT_HEADERS, - HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, + HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_PUT; networkDataLen = HTTP_TEST_RESPONSE_PUT_LENGTH; @@ -814,8 +832,8 @@ void test_HTTPClient_Send_GET_request_parse_whole_response( void ) http_parser_execute_Stub( http_parser_execute_whole_response ); - memcpy( requestHeaders.pBuffer, - HTTP_TEST_REQUEST_GET_HEADERS, + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_GET_HEADERS, HTTP_TEST_REQUEST_GET_HEADERS_LENGTH ); requestHeaders.headersLen = HTTP_TEST_REQUEST_GET_HEADERS_LENGTH; pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_GET; @@ -943,8 +961,8 @@ void test_HTTPClient_Send_parse_partial_body( void ) http_parser_execute_Stub( http_parser_execute_partial_body ); - memcpy( requestHeaders.pBuffer, - HTTP_TEST_REQUEST_GET_HEADERS, + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_GET_HEADERS, HTTP_TEST_REQUEST_GET_HEADERS_LENGTH ); requestHeaders.headersLen = HTTP_TEST_REQUEST_GET_HEADERS_LENGTH; pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_GET; @@ -977,8 +995,8 @@ void test_HTTPClient_Send_parse_chunked_body( void ) http_parser_execute_Stub( http_parser_execute_chunked_body ); - memcpy( requestHeaders.pBuffer, - HTTP_TEST_REQUEST_PUT_HEADERS, + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_CHUNKED; @@ -1083,8 +1101,8 @@ void test_HTTPClient_Send_null_response( void ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - memcpy( requestHeaders.pBuffer, - HTTP_TEST_REQUEST_PUT_HEADERS, + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; returnStatus = HTTPClient_Send( &transportInterface, @@ -1146,8 +1164,18 @@ void test_HTTPClient_Send_less_bytes_request_headers( void ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; + http_parser_execute_Stub( http_parser_execute_whole_response ); + transportInterface.send = transportSendLessThanBytesToWrite; - sendErrorCall = 0U; + sendPartialCall = 0U; + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, + HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); + requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_PUT; + networkDataLen = HTTP_TEST_RESPONSE_PUT_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_PUT_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, &requestHeaders, NULL, @@ -1155,7 +1183,16 @@ void test_HTTPClient_Send_less_bytes_request_headers( void ) &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.headersLen ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0, response.bodyLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( 0, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); } /*-----------------------------------------------------------*/ @@ -1165,14 +1202,19 @@ void test_HTTPClient_Send_less_bytes_request_body( void ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; + http_parser_execute_Stub( http_parser_execute_whole_response ); + transportInterface.send = transportSendLessThanBytesToWrite; - /* There is no Content-Length header written so the call to send an error on - * is call 1. */ - sendErrorCall = 1U; - requestHeaders.pBuffer = ( uint8_t * ) ( HTTP_TEST_REQUEST_PUT_HEADERS ); - requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + sendPartialCall = 1U; + memcpy( requestHeaders.pBuffer, + HTTP_TEST_REQUEST_PUT_HEADERS, + HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH ); requestHeaders.headersLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; + pNetworkData = ( uint8_t * ) HTTP_TEST_RESPONSE_PUT; + networkDataLen = HTTP_TEST_RESPONSE_PUT_LENGTH; + firstPartBytes = HTTP_TEST_RESPONSE_PUT_LENGTH; + returnStatus = HTTPClient_Send( &transportInterface, &requestHeaders, ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, @@ -1180,7 +1222,16 @@ void test_HTTPClient_Send_less_bytes_request_body( void ) &response, HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG ); - TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, returnStatus ); + TEST_ASSERT_EQUAL( response.pBuffer + ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.pHeaders ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_LENGTH - ( sizeof( HTTP_STATUS_LINE_OK ) - 1 ), response.headersLen ); + TEST_ASSERT_EQUAL( NULL, response.pBody ); + TEST_ASSERT_EQUAL( 0, response.bodyLen ); + TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); + TEST_ASSERT_EQUAL( 0, response.contentLength ); + TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_HEADER_COUNT, response.headerCount ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); } /*-----------------------------------------------------------*/ @@ -1206,7 +1257,7 @@ void test_HTTPClient_Send_network_error_response( void ) /* Test when more bytes are received than expected, when receiving a response * from the network. */ -void test_HTTPClient_Send_too_many_bytes_response( void ) +void test_HTTPClient_Send_recv_too_many_bytes( void ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -1224,6 +1275,26 @@ void test_HTTPClient_Send_too_many_bytes_response( void ) /*-----------------------------------------------------------*/ +/* Test when more bytes are sent than expected, when sending data + * over the socket. */ +void test_HTTPClient_Send_send_too_many_bytes( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + http_parser_init_Ignore(); + + transportInterface.send = transportSendMoreThanBytesToWrite; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); +} + +/*-----------------------------------------------------------*/ + /* Test a NULL transport interface passed to the API. */ void test_HTTPClient_Send_null_transport_interface( void ) { @@ -1370,7 +1441,8 @@ void test_HTTPClient_Send_Content_Length_Header_Doesnt_Fit( void ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - requestHeaders.pBuffer = (uint8_t*)HTTP_TEST_REQUEST_PUT_HEADERS; + requestHeaders.pBuffer = ( uint8_t * ) HTTP_TEST_REQUEST_PUT_HEADERS; + /* Set the length of the buffer to be the same length as the current * amount of headers without the Content-Length. */ requestHeaders.bufferLen = HTTP_TEST_REQUEST_PUT_HEADERS_LENGTH; From 8944c414d322283e21bb194b6ad0166420b83423 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 9 Jun 2020 22:54:45 -0700 Subject: [PATCH 536/844] Test: Add remaining coverage for HTTP Client Library (#987) * Add coverage for ReadHeader (no private methods) * Add coverage for ReadHeader callbacks * Add coverage for writeRequestLine * Update test_Http_ReadHeader_Header_Not_In_Response to have full branch coverage * Update comment for test_Http_ReadHeader_Header_Not_In_Response * Address PR comments * Add check for &pTestResponse[ otherHeaderFieldInRespLoc ] * Use macro _LEN instead of strlen * Change AddHeader and ReadHeader params to const char * from const uint8_t * --- libraries/standard/http/include/http_client.h | 30 +-- libraries/standard/http/src/http_client.c | 74 ++++---- .../http/src/private/http_client_internal.h | 12 +- libraries/standard/http/utest/http_config.h | 2 +- libraries/standard/http/utest/http_utest.c | 175 +++++++++++++----- 5 files changed, 188 insertions(+), 105 deletions(-) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index dcbfd740f9..f0d0ee7040 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -349,8 +349,8 @@ typedef enum HTTPStatus * * The memory for the header data buffer is supplied by the user. Information in * the buffer will be filled by calling #HTTPClient_InitializeRequestHeaders and - * #HTTPClient_AddHeader. This buffer may be automatically filled with the - * Content-Length header in #HTTPClient_Send, please see + * #HTTPClient_AddHeader. This buffer may be automatically filled with the + * Content-Length header in #HTTPClient_Send, please see * HTTP_MAX_CONTENT_LENGTH_HEADER_LENGTH for the maximum amount of space needed * to accommodate the Content-Length header. */ @@ -430,9 +430,9 @@ typedef struct HTTPClient_ResponseHeaderParsingCallback * @param[in] statusCode The HTTP response status-code. */ void ( * onHeaderCallback )( void * pContext, - const uint8_t * fieldLoc, + const char * fieldLoc, size_t fieldLen, - const uint8_t * valueLoc, + const char * valueLoc, size_t valueLen, uint16_t statusCode ); @@ -584,9 +584,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques * - #HTTP_INSUFFICIENT_MEMORY (If application-provided buffer is not large enough to hold headers.) */ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const uint8_t * pField, + const char * pField, size_t fieldLen, - const uint8_t * pValue, + const char * pValue, size_t valueLen ); /** @@ -643,7 +643,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * * If #HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG is not set in parameter @p flags, * then the Content-Length to be sent to the server is automatically written to - * @p pRequestHeaders. The Content-Length will not be written when there is + * @p pRequestHeaders. The Content-Length will not be written when there is * no request body. If there is not enough room in the buffer to write the * Content-Length then #HTTP_INSUFFICIENT_MEMORY is returned. Please see * #HTTP_MAX_CONTENT_LENGTH_HEADER_LENGTH for the maximum Content-Length header @@ -708,11 +708,11 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, * incomplete until #HTTPClient_Send returns. * * @param[in] pResponse The buffer containing the completed HTTP response. - * @param[in] pHeaderName The header field name to read. - * @param[in] headerNameLen The length of the header field name in bytes. - * @param[out] pHeaderValueLoc This will be populated with the location of the + * @param[in] pField The header field name to read. + * @param[in] fieldLen The length of the header field name in bytes. + * @param[out] pValueLoc This will be populated with the location of the * header value in the response buffer, #HTTPResponse_t.pBuffer. - * @param[out] pHeaderValueLen This will be populated with the length of the + * @param[out] pValueLen This will be populated with the length of the * header value in bytes. * * @return One of the following: @@ -723,10 +723,10 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, * - #HTTP_PARSER_INTERNAL_ERROR(If an error in the response parser.) */ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, - const uint8_t * pHeaderName, - size_t headerNameLen, - const uint8_t ** pHeaderValueLoc, - size_t * pHeaderValueLen ); + const char * pField, + size_t fieldLen, + const char ** pValueLoc, + size_t * pValueLen ); /** * @brief Error code to string conversion utility for HTTP Client library. diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index eea62229c3..4e76172c28 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -83,9 +83,9 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. */ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, - const uint8_t * pField, + const char * pField, size_t fieldLen, - const uint8_t * pValue, + const char * pValue, size_t valueLen ); /** @@ -189,9 +189,9 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, */ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, size_t bufferLen, - const uint8_t * pField, + const char * pField, size_t fieldLen, - const uint8_t ** pValueLoc, + const char ** pValueLoc, size_t * pValueLen ); /** @@ -595,7 +595,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, * to NULL in the preceding httpParseOnStatusFieldCallback(). */ if( pResponse->pHeaders == NULL ) { - pResponse->pHeaders = ( const uint8_t * ) pLoc; + pResponse->pHeaders = pLoc; } /* Set the location of what to parse next. */ @@ -1111,9 +1111,9 @@ static uint8_t convertInt32ToAscii( int32_t value, /*-----------------------------------------------------------*/ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, - const uint8_t * pField, + const char * pField, size_t fieldLen, - const uint8_t * pValue, + const char * pValue, size_t valueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -1321,9 +1321,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques { /* Write "User-Agent: ". */ returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) HTTP_USER_AGENT_FIELD, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_FIELD_LEN, - ( const uint8_t * ) HTTP_USER_AGENT_VALUE, + HTTP_USER_AGENT_VALUE, HTTP_USER_AGENT_VALUE_LEN ); } @@ -1331,9 +1331,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques { /* Write "Host: ". */ returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) HTTP_HOST_FIELD, + HTTP_HOST_FIELD, HTTP_HOST_FIELD_LEN, - ( const uint8_t * ) pRequestInfo->pHost, + pRequestInfo->pHost, pRequestInfo->hostLen ); } @@ -1343,9 +1343,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques { /* Write "Connection: keep-alive". */ returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) HTTP_CONNECTION_FIELD, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_FIELD_LEN, - ( const uint8_t * ) HTTP_CONNECTION_KEEP_ALIVE_VALUE, + HTTP_CONNECTION_KEEP_ALIVE_VALUE, HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ); } } @@ -1356,9 +1356,9 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, - const uint8_t * pField, + const char * pField, size_t fieldLen, - const uint8_t * pValue, + const char * pValue, size_t valueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -1502,9 +1502,9 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /* Add the Range Request header field and value to the buffer. */ returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) HTTP_RANGE_REQUEST_HEADER_FIELD, + HTTP_RANGE_REQUEST_HEADER_FIELD, HTTP_RANGE_REQUEST_HEADER_FIELD_LEN, - ( const uint8_t * ) rangeValueBuffer, + rangeValueBuffer, rangeValueLength ); } @@ -1592,9 +1592,9 @@ static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeade sizeof( pContentLengthValue ) ); returnStatus = addHeader( pRequestHeaders, - ( const uint8_t * ) HTTP_CONTENT_LENGTH_FIELD, + HTTP_CONTENT_LENGTH_FIELD, HTTP_CONTENT_LENGTH_FIELD_LEN, - ( const uint8_t * ) pContentLengthValue, + pContentLengthValue, contentLengthValueNumBytes ); if( returnStatus != HTTP_SUCCESS ) @@ -1657,7 +1657,7 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, /* Send the request body. */ LogDebug( ( "Sending the HTTP request body: BodyBytes=%d", - reqBodyBufLen ) ); + ( int32_t ) reqBodyBufLen ) ); returnStatus = sendHttpData( pTransport, pRequestBodyBuf, reqBodyBufLen ); return returnStatus; @@ -1971,7 +1971,7 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /* Check whether the parsed header matches the header we are looking for. */ if( ( fieldLen == pContext->fieldLen ) && - ( memcmp( pContext->pField, ( const uint8_t * ) pFieldLoc, fieldLen ) == 0 ) ) + ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) { LogDebug( ( "Found header field in response: " "HeaderName=%.*s, HeaderLocation=0x%x", @@ -2018,7 +2018,7 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, pContext->fieldLen, pContext->pField, pVaLueLoc ) ); /* Populate the output parameters with the location of the header value in the response buffer. */ - *pContext->pValueLoc = ( const uint8_t * ) pVaLueLoc; + *pContext->pValueLoc = pVaLueLoc; *pContext->pValueLen = valueLen; /* Set the header value found flag. */ @@ -2064,9 +2064,9 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, size_t bufferLen, - const uint8_t * pField, + const char * pField, size_t fieldLen, - const uint8_t ** pValueLoc, + const char ** pValueLoc, size_t * pValueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -2182,10 +2182,10 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /*-----------------------------------------------------------*/ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, - const uint8_t * pHeaderName, - size_t headerNameLen, - const uint8_t ** pHeaderValueLoc, - size_t * pHeaderValueLen ) + const char * pField, + size_t fieldLen, + const char ** pValueLoc, + size_t * pValueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -2205,23 +2205,23 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, "Buffer len should be > 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( pHeaderName == NULL ) + else if( pField == NULL ) { LogError( ( "Parameter check failed: Input header name is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( headerNameLen == 0u ) + else if( fieldLen == 0u ) { LogError( ( "Parameter check failed: Input header name length is 0: " - "headerNameLen should be > 0." ) ); + "fieldLen should be > 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( pHeaderValueLoc == NULL ) + else if( pValueLoc == NULL ) { LogError( ( "Parameter check failed: Output parameter for header value location is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( pHeaderValueLen == NULL ) + else if( pValueLen == NULL ) { LogError( ( "Parameter check failed: Output parameter for header value length is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; @@ -2235,10 +2235,10 @@ HTTPStatus_t HTTPClient_ReadHeader( const HTTPResponse_t * pResponse, { returnStatus = findHeaderInResponse( pResponse->pBuffer, pResponse->bufferLen, - pHeaderName, - headerNameLen, - pHeaderValueLoc, - pHeaderValueLen ); + pField, + fieldLen, + pValueLoc, + pValueLen ); } return returnStatus; diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 20e13fd4b6..6e901bc30b 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -146,12 +146,12 @@ typedef enum HTTPParsingState_t */ typedef struct findHeaderContext { - const uint8_t * pField; /**< The field that is being searched for. */ - size_t fieldLen; /**< The length of pField. */ - const uint8_t ** pValueLoc; /**< The location of the value found in the buffer. */ - size_t * pValueLen; /**< the length of the value found. */ - uint8_t fieldFound; /**< Indicates that the header field was found during parsing. */ - uint8_t valueFound; /**< Indicates that the header value was found during parsing. */ + const char * pField; /**< The field that is being searched for. */ + size_t fieldLen; /**< The length of pField. */ + const char ** pValueLoc; /**< The location of the value found in the buffer. */ + size_t * pValueLen; /**< the length of the value found. */ + uint8_t fieldFound; /**< Indicates that the header field was found during parsing. */ + uint8_t valueFound; /**< Indicates that the header value was found during parsing. */ } findHeaderContext_t; /** diff --git a/libraries/standard/http/utest/http_config.h b/libraries/standard/http/utest/http_config.h index 874207e8f8..57649a9850 100644 --- a/libraries/standard/http/utest/http_config.h +++ b/libraries/standard/http/utest/http_config.h @@ -16,7 +16,7 @@ /* Configure name and log level for the HTTP library. */ #define LIBRARY_LOG_NAME "HTTP" -#define LIBRARY_LOG_LEVEL LOG_INFO +#define LIBRARY_LOG_LEVEL LOG_DEBUG #include "logging_stack.h" diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 1aa17c542f..4ce50da047 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -116,10 +116,17 @@ static const char * pTestResponse = "HTTP/1.1 200 OK\r\n" "test-header0: test-value0\r\n" "test-header1: test-value1\r\n" "test-header2: test-value2\r\n" + "header_not_in_buffer: test-value3\r\n" "\r\n"; -#define HEADER_IN_BUFFER "test-header1" -#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" +#define HEADER_INVALID_PARAMS "Header" +#define HEADER_INVALID_PARAMS_LEN ( sizeof( HEADER_INVALID_PARAMS ) - 1 ) + +#define HEADER_IN_BUFFER "test-header1" +#define HEADER_IN_BUFFER_LEN ( sizeof( HEADER_IN_BUFFER ) - 1 ) + +#define HEADER_NOT_IN_BUFFER "header-not-in-buffer" +#define HEADER_NOT_IN_BUFFER_LEN ( sizeof( HEADER_NOT_IN_BUFFER ) - 1 ) /* File-scoped Global variables */ static HTTPStatus_t retCode = HTTP_SUCCESS; @@ -128,11 +135,13 @@ static HTTPRequestHeaders_t testHeaders = { 0 }; static _headers_t expectedHeaders = { 0 }; static int testRangeStart = 0; static int testRangeEnd = 0; -static const uint8_t * pValueLoc = NULL; +static const char * pValueLoc = NULL; static size_t valueLen = 0u; static HTTPResponse_t testResponse = { 0 }; static const size_t headerFieldInRespLoc = 44; static const size_t headerFieldInRespLen = sizeof( "test-header1" ) - 1u; +static const size_t otherHeaderFieldInRespLoc = 98; +static const size_t otherHeaderFieldInRespLen = sizeof( "header_not_in_buffer" ) - 1u; static const size_t headerValInRespLoc = 58; static const size_t headerValInRespLen = sizeof( "test-value1" ) - 1u; static http_parser * pCapturedParser = NULL; @@ -245,13 +254,15 @@ static void setupBuffersWithPreexistingHeader( HTTPRequestHeaders_t * testReques const char * preexistingData ) { size_t dataLen = strlen( preexistingData ); + int numBytes = 0; testRequestHeaders->pBuffer = testBuffer; testRequestHeaders->bufferLen = bufferSize; - int numBytes = snprintf( ( char * ) testRequestHeaders->pBuffer, - bufferSize, - "%s", - preexistingData ); + + numBytes = snprintf( ( char * ) testRequestHeaders->pBuffer, + bufferSize, + "%s", + preexistingData ); /* Make sure that the entire pre-existing data was printed to the buffer. */ TEST_ASSERT_GREATER_THAN( 0, numBytes ); TEST_ASSERT_LESS_THAN( bufferSize, ( size_t ) numBytes ); @@ -324,6 +335,8 @@ void tearDown() memset( &testResponse, 0, sizeof( testResponse ) ); + testResponse.pBuffer = testBuffer; + testResponse.bufferLen = strlen( pTestResponse ); pValueLoc = NULL; valueLen = 0u; pValueLoc = NULL; @@ -479,7 +492,30 @@ void test_Http_InitializeRequestHeaders_ReqInfo() setupRequestInfo( &requestInfo ); setupBuffer( &requestHeaders ); - requestInfo.pPath = 0; + requestInfo.pPath = NULL; + requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + HTTP_TEST_EXTRA_HEADER_FORMAT, + HTTP_METHOD_GET, HTTP_EMPTY_PATH, + HTTP_PROTOCOL_VERSION, + HTTP_USER_AGENT_FIELD, HTTP_USER_AGENT_VALUE, + HTTP_HOST_FIELD, HTTP_TEST_HOST_VALUE, + HTTP_CONNECTION_FIELD, HTTP_CONNECTION_KEEP_ALIVE_VALUE ); + /* Make sure that the entire pre-existing data was printed to the buffer. */ + TEST_ASSERT_GREATER_THAN( 0, numBytes ); + TEST_ASSERT_LESS_THAN( sizeof( expectedHeaders.buffer ), ( size_t ) numBytes ); + + requestHeaders.pBuffer = testBuffer; + requestHeaders.bufferLen = expectedHeaders.dataLen; + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, &requestInfo ); + TEST_ASSERT_EQUAL( HTTP_SUCCESS, httpStatus ); + TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); + TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, requestHeaders.pBuffer, + expectedHeaders.dataLen ); + + /* Repeat the test above but with length of path == 0 for coverage. */ + requestInfo.pPath = HTTP_EMPTY_PATH; + requestInfo.pathLen = 0; requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), HTTP_TEST_EXTRA_HEADER_FORMAT, @@ -540,7 +576,8 @@ void test_Http_AddHeader_Happy_Path() setupBuffer( &requestHeaders ); /* Add 1 because snprintf(...) writes a null byte at the end. */ - numBytes = snprintf( expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + numBytes = snprintf( ( char * ) expectedHeaders.buffer, + sizeof( expectedHeaders.buffer ), HTTP_TEST_SINGLE_HEADER_FORMAT, HTTP_TEST_HEADER_REQUEST_LINE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); @@ -572,7 +609,7 @@ void test_Http_AddHeader_Happy_Path() /** * @brief Test invalid parameters, following order of else-if blocks in the HTTP library. */ -void test_Http_AddHeader_Invalid_Parameters() +void test_Http_AddHeader_Invalid_Params() { HTTPStatus_t httpStatus = HTTP_SUCCESS; HTTPRequestHeaders_t requestHeaders = { 0 }; @@ -629,7 +666,8 @@ void test_Http_AddHeader_Extra_Header_Sufficient_Memory() setupBuffer( &requestHeaders ); /* Add 1 because snprintf(...) writes a null byte at the end. */ - numBytes = snprintf( expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + numBytes = snprintf( ( char * ) expectedHeaders.buffer, + sizeof( expectedHeaders.buffer ), HTTP_TEST_DOUBLE_HEADER_FORMAT, HTTP_TEST_HEADER_REQUEST_LINE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE, @@ -651,8 +689,10 @@ void test_Http_AddHeader_Extra_Header_Sufficient_Memory() /* Run the method to test. */ httpStatus = HTTPClient_AddHeader( &requestHeaders, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, - HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + HTTP_TEST_HEADER_FIELD, + HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, + HTTP_TEST_HEADER_VALUE_LEN ); TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, requestHeaders.pBuffer, expectedHeaders.dataLen ); @@ -671,7 +711,8 @@ void test_Http_AddHeader_Extra_Header_Insufficient_Memory() setupBuffer( &requestHeaders ); /* Add 1 because snprintf(...) writes a null byte at the end. */ - numBytes = snprintf( expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), + numBytes = snprintf( ( char * ) expectedHeaders.buffer, + sizeof( expectedHeaders.buffer ), HTTP_TEST_SINGLE_HEADER_FORMAT, HTTP_TEST_HEADER_REQUEST_LINE, HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_VALUE ); @@ -694,8 +735,10 @@ void test_Http_AddHeader_Extra_Header_Insufficient_Memory() /* Run the method to test. */ httpStatus = HTTPClient_AddHeader( &requestHeaders, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, - HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + HTTP_TEST_HEADER_FIELD, + HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, + HTTP_TEST_HEADER_VALUE_LEN ); TEST_ASSERT_EQUAL( expectedHeaders.dataLen, requestHeaders.headersLen ); TEST_ASSERT_EQUAL_MEMORY( expectedHeaders.buffer, requestHeaders.pBuffer, expectedHeaders.dataLen ); @@ -727,8 +770,10 @@ void test_Http_AddHeader_Single_Header_Insufficient_Memory() /* Run the method to test. */ httpStatus = HTTPClient_AddHeader( &requestHeaders, - HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, - HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + HTTP_TEST_HEADER_FIELD, + HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, + HTTP_TEST_HEADER_VALUE_LEN ); TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, httpStatus ); } @@ -1036,45 +1081,45 @@ void test_Http_ReadHeader_Invalid_Params( void ) { /* Response parameter is NULL. */ retCode = HTTPClient_ReadHeader( NULL, - ( const uint8_t * ) "Header", - strlen( "Header" ), + HEADER_INVALID_PARAMS, + HEADER_INVALID_PARAMS_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Underlying buffer is NULL in the response parameter. */ tearDown(); + testResponse.pBuffer = NULL; retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), + HEADER_INVALID_PARAMS, + HEADER_INVALID_PARAMS_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Response buffer size is zero. */ tearDown(); + testResponse.bufferLen = 0; retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), + HEADER_INVALID_PARAMS, + HEADER_INVALID_PARAMS_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Header field name is NULL. */ tearDown(); - testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, NULL, - strlen( "Header" ), + HEADER_INVALID_PARAMS_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Header field length is 0. */ tearDown(); - testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", + HEADER_INVALID_PARAMS, 0u, &pValueLoc, &valueLen ); @@ -1082,18 +1127,16 @@ void test_Http_ReadHeader_Invalid_Params( void ) /* Invalid output parameters. */ tearDown(); - testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), + HEADER_INVALID_PARAMS, + HEADER_INVALID_PARAMS_LEN, NULL, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); tearDown(); - testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) "Header", - strlen( "Header" ), + HEADER_INVALID_PARAMS, + HEADER_INVALID_PARAMS_LEN, &pValueLoc, NULL ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); @@ -1109,15 +1152,55 @@ void test_Http_ReadHeader_Header_Not_In_Response( void ) http_parser_settings_init_ExpectAnyArgs(); /* Configure the http_parser_execute mock. */ - invokeHeaderCompleteCallback = 0u; + invokeHeaderFieldCallback = 1u; + invokeHeaderValueCallback = 1u; + pFieldLocToReturn = &pTestResponse[ headerFieldInRespLoc ]; + fieldLenToReturn = headerFieldInRespLen; + pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + expectedValCbRetVal = HTTP_PARSER_CONTINUE_PARSING; + invokeHeaderCompleteCallback = 1u; + parserErrNo = HPE_OK; + http_parser_execute_ExpectAnyArgsAndReturn( strlen( pTestResponse ) ); + + /* Call the function under test. */ + testResponse.bufferLen = strlen( pTestResponse ); + retCode = HTTPClient_ReadHeader( &testResponse, + HEADER_NOT_IN_BUFFER, + HEADER_NOT_IN_BUFFER_LEN, + &pValueLoc, + &valueLen ); + TEST_ASSERT_EQUAL( HTTP_HEADER_NOT_FOUND, retCode ); + + /* Repeat the test above but with fieldLenToReturn == HEADER_NOT_IN_BUFFER_LEN. + * Doing this allows us to take the branch where the actual contents + * of the fields are compared rather than just the length. */ + setUp(); + /* Add expectations for http_parser dependencies. */ + http_parser_init_ExpectAnyArgs(); + http_parser_settings_init_ExpectAnyArgs(); + /* Ensure that the header field does NOT match what we're searching. */ + TEST_ASSERT_EQUAL( otherHeaderFieldInRespLen, HEADER_NOT_IN_BUFFER_LEN ); + TEST_ASSERT_TRUE( memcmp( &pTestResponse[ otherHeaderFieldInRespLoc ], + HEADER_NOT_IN_BUFFER, + HEADER_NOT_IN_BUFFER_LEN ) != 0 ); + /* Configure the http_parser_execute mock. */ + invokeHeaderFieldCallback = 1u; + invokeHeaderValueCallback = 1u; + pFieldLocToReturn = &pTestResponse[ otherHeaderFieldInRespLoc ]; + fieldLenToReturn = otherHeaderFieldInRespLen; + pValueLocToReturn = &pTestResponse[ headerValInRespLoc ]; + valueLenToReturn = headerValInRespLen; + expectedValCbRetVal = HTTP_PARSER_CONTINUE_PARSING; + invokeHeaderCompleteCallback = 1u; parserErrNo = HPE_OK; http_parser_execute_ExpectAnyArgsAndReturn( strlen( pTestResponse ) ); /* Call the function under test. */ testResponse.bufferLen = strlen( pTestResponse ); retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_NOT_IN_BUFFER, - strlen( HEADER_NOT_IN_BUFFER ), + HEADER_NOT_IN_BUFFER, + HEADER_NOT_IN_BUFFER_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_HEADER_NOT_FOUND, retCode ); @@ -1150,8 +1233,8 @@ void test_Http_ReadHeader_Invalid_Response_Only_Header_Field_Found() testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutValue[ 0 ]; testResponse.bufferLen = strlen( pResponseWithoutValue ); retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), + HEADER_IN_BUFFER, + HEADER_IN_BUFFER_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_RESPONSE, retCode ); @@ -1183,8 +1266,8 @@ void test_Http_ReadHeader_Invalid_Response_No_Headers_Complete_Ending() testResponse.pBuffer = ( uint8_t * ) &pResponseWithoutHeaders[ 0 ]; testResponse.bufferLen = strlen( pResponseWithoutHeaders ); retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_NOT_IN_BUFFER, - strlen( HEADER_NOT_IN_BUFFER ), + HEADER_NOT_IN_BUFFER, + HEADER_NOT_IN_BUFFER_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_INVALID_RESPONSE, retCode ); @@ -1214,8 +1297,8 @@ void test_Http_ReadHeader_With_HttpParser_Internal_Error() /* Call the function under test. */ retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), + HEADER_IN_BUFFER, + HEADER_IN_BUFFER_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_PARSER_INTERNAL_ERROR, retCode ); @@ -1243,12 +1326,12 @@ void test_Http_ReadHeader_Happy_Path() /* Call the function under test. */ retCode = HTTPClient_ReadHeader( &testResponse, - ( const uint8_t * ) HEADER_IN_BUFFER, - strlen( HEADER_IN_BUFFER ), + HEADER_IN_BUFFER, + HEADER_IN_BUFFER_LEN, &pValueLoc, &valueLen ); TEST_ASSERT_EQUAL( HTTP_SUCCESS, retCode ); - TEST_ASSERT_EQUAL( ( const uint8_t * ) &pTestResponse[ headerValInRespLoc ], pValueLoc ); + TEST_ASSERT_EQUAL( &pTestResponse[ headerValInRespLoc ], pValueLoc ); TEST_ASSERT_EQUAL( headerValInRespLen, valueLen ); } From ee75c35cc7f873544d96767dcd35796e2d6ae80f Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Wed, 10 Jun 2020 09:57:05 -0700 Subject: [PATCH 537/844] MQTT server auth demo (#971) --- demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 10 + demos/mqtt/mqtt_demo_basic_tls/Makefile | 17 - demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 35 + .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 1199 +++++++++++++++-- 4 files changed, 1111 insertions(+), 150 deletions(-) delete mode 100644 demos/mqtt/mqtt_demo_basic_tls/Makefile diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt index c3f4988904..3e8ad422ec 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -37,3 +37,13 @@ target_include_directories( PUBLIC ${LOGGING_INCLUDE_DIRS} ) + +#Copy the server certificate file to the binary directory. +add_custom_command( + TARGET + ${DEMO_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_LIST_DIR}/mosquitto.org.crt" + "$/certificates/mosquitto.org.crt" +) diff --git a/demos/mqtt/mqtt_demo_basic_tls/Makefile b/demos/mqtt/mqtt_demo_basic_tls/Makefile deleted file mode 100644 index c73af4ceba..0000000000 --- a/demos/mqtt/mqtt_demo_basic_tls/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -CC = gcc - -INCLUDE_DIRS = -I . \ - -I ../../../libraries/standard/mqtt/include \ - -I ../../logging-stack/ - -SRC_FILES = mqtt_demo_basic_tls.c \ - $(shell find ../../../libraries/standard/mqtt/src -name '*.c') - -FLAGS = -g -O0 -Wall -Wextra -Wpedantic -LINK_FLAGS = -lssl -lcrypto - -all: - $(CC) $(FLAGS) $(INCLUDE_DIRS) $(SRC_FILES) -o mqtt_demo_basic_tls $(LINK_FLAGS) - -clean: - rm mqtt_demo_basic_tls diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 7beb40cac2..2745410568 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -43,6 +43,41 @@ /************ End of logging configuration ****************/ +/** + * @brief MQTT server host name. + * + * This demo uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + * Mosquitto MQTT broker can run locally as an alternate option. Please refer to + * the instructions in https://mosquitto.org/ for running a Mosquitto broker + * locally. + */ +#define BROKER_ENDPOINT "test.mosquitto.org" + +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) + +/** + * @brief MQTT server port number. + * + * In general, port 8883 is for secured MQTT connections. + */ +#define BROKER_PORT ( 8883 ) + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate should be PEM-encoded. + */ +#define SERVER_CERT_PATH "certificates/mosquitto.org.crt" + +/** + * @brief Length of path to server certificate. + */ +#define SERVER_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( SERVER_CERT_PATH ) - 1 ) ) + /** * @brief MQTT client identifier. * diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 0c6dc4e227..51e6e98c71 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -19,11 +19,29 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* + * Demo for showing the use of MQTT APIs to establish an MQTT session, + * subscribe to a topic, publish to a topic, receive incoming publishes, + * unsubscribe from a topic and disconnect the MQTT session. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS2 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBREC + * are resend in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBREC is received. + */ + /* Standard includes. */ +#include #include +#include /* POSIX socket includes. */ #include +#include +#include #include #include @@ -36,77 +54,357 @@ #include "demo_config.h" /** - * @brief MQTT server host name. - * - * This demo uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. + * @brief Size of the network buffer for MQTT packets. + */ +#define NETWORK_BUFFER_SIZE ( 1024U ) + +/* Check that client identifier is defined. */ +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif + +/** + * @brief Length of client identifier. */ -#define SERVER "test.mosquitto.org" +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /** - * @brief MQTT server port number. + * @brief Timeout for receiving CONNACK packet in milli seconds. + */ +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief The topic to subscribe and publish to in the example. * - * In general, port 8883 is for secured MQTT connections. + * The topic name starts with the client identifier to ensure that each demo + * interacts with a unique topic name. + */ +#define MQTT_EXAMPLE_TOPIC CLIENT_IDENTIFIER "/example/topic" + +/** + * @brief Length of client MQTT topic. + */ +#define MQTT_EXAMPLE_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_TOPIC ) - 1 ) ) + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE "Hello World!" + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_MESSAGE ) - 1 ) ) + +/** + * @brief Maximum number of outgoing publishes maintained in the application + * until an ack is received from the broker. + */ +#define MAX_OUTGOING_PUBLISHES ( 5U ) + +/** + * @brief Invalid packet identifier for the MQTT packets. Zero is always an + * invalid packet identifier as per MQTT 3.1.1 spec. */ -#define PORT 8883 +#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) /** - * @brief Path of the file containing the server's root CA certificate. + * @brief Timeout for MQTT_ProcessLoop function in milliseconds. * - * This certificate should be PEM-encoded. */ -#define SERVER_CERT "mosquitto.org.crt" +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 500U ) /** - * @brief Size of the network buffer for MQTT packets. + * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to + * broker. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) -/* Check that client identifier is defined. */ -#ifndef CLIENT_IDENTIFIER - #error "Please define a unique CLIENT_IDENTIFIER." -#endif +/** + * @brief Delay between MQTT publishes in seconds. + */ +#define DELAY_BETWEEN_PUBLISHES_SECONDS ( 1U ) /** - * @brief Length of client identifier. + * @brief Transport timeout in milliseconds for transport send and receive. */ -#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) + +/*-----------------------------------------------------------*/ /** - * @brief Timeout for receiving CONNACK packet in milli seconds. + * @brief Structure to keep the MQTT publish packets until an ack is received + * for QoS2 publishes. + */ +typedef struct PublishPackets +{ + /** + * @brief MQTT client identifier. Must be unique per client. + */ + uint16_t packetId; + + /** + * @brief MQTT client identifier. Must be unique per client. + */ + MQTTPublishInfo_t pubInfo; +} PublishPackets_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief globalEntryTime entry time into the application to use as a reference + * timestamp in #getTimeMs function. #getTimeMs will always return the difference + * of current time with the globalEntryTime. This will reduce the chances of + * overflow for 32 bit unsigned integer used for holding the timestamp. + * + */ +static uint32_t globalEntryTimeMs = 0U; + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker; + * it is used to match received Subscribe ACK to the transmitted subscribe. + */ +static uint16_t globalSubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; + * it is used to match received Unsubscribe response to the transmitted unsubscribe + * request. + */ +static uint16_t globalUnsubscribePacketIdentifier = 0U; + +/** + * @brief Array to keep the outgoing publish messages. + * These stored outgoing publish messages are used to keep the messages until + * a successful ack is received. */ -#define CONNACK_RECV_TIMEOUT_MS ( 1000 ) +static PublishPackets_t outgoingPublishPackets[ MAX_OUTGOING_PUBLISHES ] = { 0 }; /*-----------------------------------------------------------*/ /** - * @brief Establish a TCP connection to the given server. + * @brief Creates a TCP connection to the MQTT broker as specified by + * BROKER_ENDPOINT and BROKER_PORT defined at the top of this file. * * @param[in] pServer Host name of server. * @param[in] port Server port. + * @param[out] pTcpSocket Pointer to TCP socket file descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ); + +/** + * @brief Set up a TLS connection over an existing TCP connection. + * + * @param[in] tcpSocket Existing TCP connection. + * @param[out] pSslContext Pointer to SSL connection context pointer. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int tlsSetup( int tcpSocket, + SSL ** pSslContext ); + +/** + * @brief The transport send function provided to the MQTT context. + * + * @param[in] pSslContext Pointer to SSL context. + * @param[in] pMessage Data to send. + * @param[in] bytesToSend Length of data to send. + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. + */ +static int32_t transportSend( MQTTNetworkContext_t pSslContext, + const void * pMessage, + size_t bytesToSend ); + +/** + * @brief The transport receive function provided to the MQTT context. + * + * @param[in] pSslContext Pointer to SSL context. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToRecv Length of data to be received. + * + * @return Number of bytes received; negative value on error; + * 0 for timeout. + */ +static int32_t transportRecv( MQTTNetworkContext_t pSslContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief The timer query function provided to the MQTT context. + * + * This function returns the elapsed time with reference to #globalEntryTimeMs. + * + * @return Time in milliseconds. + */ +static uint32_t getTimeMs( void ); + +/** + * @brief The function to handle the incoming publishes. + * + * @param[in] pPublishInfo Pointer to publish info of the incoming publish. + * @param[in] packetIdentifier Packet identifier of the incoming publish. + */ +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ); + +/** + * @brief The application callback function for getting the incoming publish + * and incoming acks reported from MQTT library. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] packetIdentifier Packet identifier of the incoming packet. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket.. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pSslContext SSL context. + * @param[in] createCleanSession Creates a new MQTT session if true. + * If false, tries to establish the existing session if there was session + * already present in broker. + * @param[out] pSessionPresent Session was already present in the broker or not. + * Session present response is obtained from the CONNACK from broker. + * + * @return EXIT_SUCCESS if an MQTT session is established; + * EXIT_FAILURE otherwise. + */ +static int establishMqttSession( MQTTContext_t * pContext, + SSL * pSslContext, + bool createCleanSession, + bool * pSessionPresent ); + +/** + * @brief Close an MQTT session by sending MQTT DISCONNECT. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if DISCONNECT was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int disconnectMqttSession( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT SUBSCRIBE to subscribe to #MQTT_EXAMPLE_TOPIC + * defined at the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int subscribeToTopic( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT UNSUBSCRIBE to unsubscribe from + * #MQTT_EXAMPLE_TOPIC defined at the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at + * the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if PUBLISH was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int publishToTopic( MQTTContext_t * pContext ); + +/** + * @brief Function to get the free index at which an outgoing publish + * can be stored. + * + * @param[out] pIndex The pointer to index at which an outgoing publish message + * can be stored. + * + * @return EXIT_FAILURE if no more publishes can be stored; + * EXIT_SUCCESS if an index to store the next outgoing publish is obtained. + */ +static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ); + +/** + * @brief Function to clean up an outgoing publish at given index from the + * #outgoingPublishPackets array. + * + * @param[in] index The index at which a publish message has to be cleaned up. + */ +static void cleanupOutgoingPublishAt( uint8_t index ); + +/** + * @brief Function to clean up all the outgoing publishes maintained in the + * array. + */ +static void cleanupOutgoingPublishes(); + +/** + * @brief Function to clean up the publish packet with the given packet id. + * + * @param[in] packetId Packet identifier of the packet to be cleaned up from + * the array. + */ +static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ); + +/** + * @brief Function to resend the publishes if a session is re-established with + * the broker. This function handles the resending of the QoS2 publish packets, + * which are maintained locally. * - * @return A file descriptor representing the TCP socket; -1 on failure. + * @param[in] pContext MQTT context pointer. */ +static int handlePublishResend( MQTTContext_t * pContext ); + +/*-----------------------------------------------------------*/ + static int connectToServer( const char * pServer, - uint16_t port ) + uint16_t port, + int * pTcpSocket ) { - int status, tcpSocket = -1; - struct addrinfo * pListHead = NULL, * pIndex; + int status = EXIT_SUCCESS; + struct addrinfo hints, * pIndex, * pListHead = NULL; struct sockaddr * pServerInfo; uint16_t netPort = htons( port ); socklen_t serverInfoLength; + struct timeval transportTimeout; + + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0x00, sizeof( hints ) ); + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, NULL, &pListHead ); + status = getaddrinfo( pServer, NULL, &hints, &pListHead ); if( status != -1 ) { /* Attempt to connect to one of the retrieved DNS records. */ for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) { - tcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - if( tcpSocket == -1 ) + if( *pTcpSocket == -1 ) { continue; } @@ -126,11 +424,11 @@ static int connectToServer( const char * pServer, serverInfoLength = sizeof( struct sockaddr_in6 ); } - status = connect( tcpSocket, pServerInfo, serverInfoLength ); + status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); if( status == -1 ) { - close( tcpSocket ); + close( *pTcpSocket ); } else { @@ -141,13 +439,26 @@ static int connectToServer( const char * pServer, if( pIndex == NULL ) { /* Fail if no connection could be established. */ - status = -1; + status = EXIT_FAILURE; + LogError( ( "Located but could not connect to MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); } else { - status = tcpSocket; + status = EXIT_SUCCESS; + LogInfo( ( "TCP connection established with %.*s.\n\n", + ( int ) strlen( pServer ), + pServer ) ); } } + else + { + LogError( ( "Could not locate MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + status = EXIT_FAILURE; + } if( pListHead != NULL ) { @@ -159,19 +470,14 @@ static int connectToServer( const char * pServer, /*-----------------------------------------------------------*/ -/** - * @brief Set up a TLS connection over an existing TCP connection. - * - * @param[in] tcpSocket Existing TCP connection. - * - * @return An SSL connection context; NULL on failure. - */ -static SSL * tlsSetup( int tcpSocket ) +static int tlsSetup( int tcpSocket, + SSL ** pSslContext ) { - int sslStatus = 0; + int status = EXIT_FAILURE, sslStatus = 0; FILE * pRootCaFile = NULL; X509 * pRootCa = NULL; - SSL * pSslContext = NULL; + + assert( tcpSocket >= 0 ); /* Setup for creating a TLS client. */ SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); @@ -184,59 +490,86 @@ static SSL * tlsSetup( int tcpSocket ) /* OpenSSL does not provide a single function for reading and loading certificates * from files into stores, so the file API must be called. */ - pRootCaFile = fopen( SERVER_CERT, "r" ); + pRootCaFile = fopen( SERVER_CERT_PATH, "r" ); if( pRootCaFile != NULL ) { pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); } + else + { + LogError( ( "Unable to find the certificate file in the path" + " provided by SERVER_CERT_PATH(%.*s).", + SERVER_CERT_PATH_LENGTH, + SERVER_CERT_PATH ) ); + } if( pRootCa != NULL ) { sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), pRootCa ); } + else + { + LogError( ( "Failed to parse the server certificate from" + " file %.*s. Please validate the certificate.", + SERVER_CERT_PATH_LENGTH, + SERVER_CERT_PATH ) ); + } } /* Set up the TLS connection. */ if( sslStatus == 1 ) { /* Create a new SSL context. */ - pSslContext = SSL_new( pSslSetup ); + *pSslContext = SSL_new( pSslSetup ); - if( pSslContext != NULL ) + if( *pSslContext != NULL ) { /* Enable SSL peer verification. */ - SSL_set_verify( pSslContext, SSL_VERIFY_PEER, NULL ); + SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); - sslStatus = SSL_set_fd( pSslContext, tcpSocket ); + sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); } else { + LogError( ( "Failed to create a new SSL context." ) ); sslStatus = 0; } /* Perform the TLS handshake. */ if( sslStatus == 1 ) { - sslStatus = SSL_connect( pSslContext ); + sslStatus = SSL_connect( *pSslContext ); + } + else + { + LogError( ( "Failed to set the socket fd to SSL context." ) ); } if( sslStatus == 1 ) { - if( SSL_get_verify_result( pSslContext ) != X509_V_OK ) + if( SSL_get_verify_result( *pSslContext ) != X509_V_OK ) { sslStatus = 0; } } + else + { + LogError( ( "Failed to perform TLS handshake." ) ); + } /* Clean up on error. */ if( sslStatus == 0 ) { - SSL_free( pSslContext ); - pSslContext = NULL; + SSL_free( *pSslContext ); + *pSslContext = NULL; } } + else + { + LogError( ( "Failed to add certificate to store." ) ); + } if( pRootCaFile != NULL ) { @@ -253,83 +586,363 @@ static SSL * tlsSetup( int tcpSocket ) SSL_CTX_free( pSslSetup ); } - return pSslContext; + /* Log failure or success and update the correct exit status to return. */ + if( sslStatus == 0 ) + { + LogError( ( "Failed to establish a TLS connection to %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "Established a TLS connection to %.*s.\n\n", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + status = EXIT_SUCCESS; + } + + return status; } /*-----------------------------------------------------------*/ -/** - * @brief The transport send function provided to the MQTT context. - * - * @param[in] pSslContext SSL context. - * @param[in] pMessage Data to send. - * @param[in] bytesToSend Length of data to send. - * - * @return Number of bytes sent; negative value on error. - */ static int32_t transportSend( MQTTNetworkContext_t pSslContext, const void * pMessage, size_t bytesToSend ) { - return ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); + int32_t bytesSent = 0; + int pollStatus = 0; + struct pollfd fileDescriptor = + { + .events = POLLOUT, + .revents = 0 + }; + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pSslContext ); + + /* Poll the file descriptor to check if SSL_Write can be done now. */ + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( pollStatus > 0 ) + { + bytesSent = ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); + } + else if( pollStatus == 0 ) + { + LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); + } + else + { + LogError( ( "Polling of the SSL socket for write buffer availability failed" + " with status %d.", + pollStatus ) ); + bytesSent = -1; + } + + return bytesSent; } /*-----------------------------------------------------------*/ -/** - * @brief The transport receive function provided to the MQTT context. - * - * @param[in] pSslContext SSL context. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToSend Size of pBuffer. - * - * @return Number of bytes received; negative value on error. - */ static int32_t transportRecv( MQTTNetworkContext_t pSslContext, void * pBuffer, size_t bytesToRecv ) { - return ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); + int32_t bytesReceived = 0; + int pollStatus = -1, bytesAvailableToRead = 0; + struct pollfd fileDescriptor = + { + .events = POLLIN | POLLPRI, + .revents = 0 + }; + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pSslContext ); + + /* Check if there are any pending data available for read. */ + bytesAvailableToRead = SSL_pending( pSslContext ); + + /* Poll only if there is no data available yet to read. */ + if( bytesAvailableToRead <= 0 ) + { + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + } + + /* SSL read of data. */ + if( ( pollStatus > 0 ) || ( bytesAvailableToRead > 0 ) ) + { + bytesReceived = ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); + } + /* Poll timed out. */ + else if( pollStatus == 0 ) + { + LogDebug( ( "Poll timed out and there is no data to read from the buffer." ) ); + } + else + { + LogError( ( "Poll returned with status = %d.", pollStatus ) ); + bytesReceived = -1; + } + + return bytesReceived; } /*-----------------------------------------------------------*/ -/** - * @brief The timer query function provided to the MQTT context. - * - * Currently not implemented. - */ -static uint32_t getTime( void ) +static uint32_t getTimeMs( void ) { - return 0; + uint32_t timeMs; + struct timespec timeSpec; + + /* Get the MONOTONIC time. */ + clock_gettime( CLOCK_MONOTONIC, &timeSpec ); + + /* Calculate the milliseconds from timespec. */ + timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) + + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); + + /* Reduce globalEntryTime from obtained time so as to always return the + * elapsed time in the application. */ + timeMs = ( uint32_t ) ( timeMs - globalEntryTimeMs ); + + return timeMs; +} + +/*-----------------------------------------------------------*/ + +static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) +{ + int status = EXIT_FAILURE; + + assert( outgoingPublishPackets != NULL ); + + for( *pIndex = 0; *pIndex < MAX_OUTGOING_PUBLISHES; ( *pIndex )++ ) + { + /* A free index is marked by invalid packet id. + * Check if the the index has a free slot. */ + if( outgoingPublishPackets[ *pIndex ].packetId == MQTT_PACKET_ID_INVALID ) + { + status = EXIT_SUCCESS; + break; + } + } + + return status; +} +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishAt( uint8_t index ) +{ + assert( outgoingPublishPackets != NULL ); + assert( index < MAX_OUTGOING_PUBLISHES ); + + /* Clear the outgoing publish packet. */ + ( void ) memset( &( outgoingPublishPackets[ index ] ), + 0x00, + sizeof( outgoingPublishPackets[ index ] ) ); +} + +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishes() +{ + uint8_t index = 0; + + assert( outgoingPublishPackets != NULL ); + + /* Clean up all the saved outgoing publishes. */ + for( ; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + cleanupOutgoingPublishAt( index ); + } +} + +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ) +{ + uint8_t index = 0; + + assert( outgoingPublishPackets != NULL ); + assert( packetId != MQTT_PACKET_ID_INVALID ); + + /* Clean up all the saved outgoing publishes. */ + for( ; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + if( outgoingPublishPackets[ index ].packetId == packetId ) + { + cleanupOutgoingPublishAt( index ); + LogInfo( ( "Cleaned up outgoing publish packet with packet id %u.\n\n", + packetId ) ); + break; + } + } +} + +/*-----------------------------------------------------------*/ + +static int handlePublishResend( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; + uint8_t index = 0U; + + assert( outgoingPublishPackets != NULL ); + + /* Resend all the QoS2 publishes still in the array. These are the + * publishes that hasn't received a PUBREC. When a PUBREC is + * received, the publish is removed from the array. */ + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + if( outgoingPublishPackets[ index ].packetId != MQTT_PACKET_ID_INVALID ) + { + outgoingPublishPackets[ index ].pubInfo.dup = true; + + LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", + outgoingPublishPackets[ index ].packetId ) ); + mqttStatus = MQTT_Publish( pContext, + &outgoingPublishPackets[ index ].pubInfo, + outgoingPublishPackets[ index ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending duplicate PUBLISH for packet id %u " + " failed with status %u.", + outgoingPublishPackets[ index ].packetId, + mqttStatus ) ); + status = EXIT_FAILURE; + break; + } + else + { + LogInfo( ( "Sent duplicate PUBLISH successfully for packet id %u.\n\n", + outgoingPublishPackets[ index ].packetId ) ); + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ) +{ + assert( pPublishInfo != NULL ); + + /* Process incoming Publish. */ + LogInfo( ( "Incoming QOS : %d.", pPublishInfo->qos ) ); + + /* Verify the received publish is for the we have subscribed to. */ + if( ( pPublishInfo->topicNameLength == MQTT_EXAMPLE_TOPIC_LENGTH ) && + ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ) ) ) + { + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + LogInfo( ( "Incoming Publish message Packet Id is %u.", + packetIdentifier ) ); + LogInfo( ( "Incoming Publish Message : %.*s.\n\n", + ( int ) pPublishInfo->payloadLength, + ( const char * ) pPublishInfo->pPayload ) ); + } + else + { + LogInfo( ( "Incoming Publish Topic Name: %.*s does not match subscribed topic.", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + } } /*-----------------------------------------------------------*/ -/** - * @brief MQTT event callback. - * - * Currently not implemented. - */ static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { + assert( pContext != NULL ); + assert( pPacketInfo != NULL ); + + /* Handle incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + assert( pPublishInfo != NULL ); + /* Handle incoming publish. */ + handleIncomingPublish( pPublishInfo, packetIdentifier ); + } + else + { + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + LogInfo( ( "Subscribed to the topic %.*s.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( globalSubscribePacketIdentifier == packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + LogInfo( ( "Unsubscribed from the topic %.*s.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( globalUnsubscribePacketIdentifier == packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogInfo( ( "PINGRESP received.\n\n" ) ); + break; + + case MQTT_PACKET_TYPE_PUBREC: + LogInfo( ( "PUBREC received for packet id %u.", + packetIdentifier ) ); + /* Cleanup publish packet when a PUBREC is received. */ + cleanupOutgoingPublishWithPacketID( packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PUBREL: + + /* Nothing to be done from application as library handles + * PUBREL. */ + LogInfo( ( "PUBREL received for packet id %u.\n\n", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PUBCOMP: + + /* Nothing to be done from application as library handles + * PUBCOMP. */ + LogInfo( ( "PUBCOMP received for packet id %u.\n\n", + packetIdentifier ) ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).", + pPacketInfo->type ) ); + } + } } /*-----------------------------------------------------------*/ -/** - * @brief Establish an MQTT session over a TCP+TLS connection by sending MQTT CONNECT. - * - * @param[in] pContext MQTT context. - * @param[in] pSslContext SSL context. - * - * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. - */ static int establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext ) + SSL * pSslContext, + bool createCleanSession, + bool * pSessionPresent ) { int status = EXIT_SUCCESS; MQTTStatus_t mqttStatus; @@ -338,37 +951,76 @@ static int establishMqttSession( MQTTContext_t * pContext, MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + assert( pContext != NULL ); + assert( pSslContext != NULL ); + /* The network buffer must remain valid for the lifetime of the MQTT context. */ static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; - /* Initialize MQTT context. */ + /* Fill in TransportInterface send and receive function pointers. + * For this demo, TCP sockets are used to send and receive data + * from network. Network context is socket file descriptor.*/ transport.networkContext = pSslContext; transport.send = transportSend; transport.recv = transportRecv; + /* Fill the values for network buffer. */ networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; + /* Application callbacks for receiving incoming publishes and incoming acks + * from MQTT library. */ callbacks.appCallback = eventCallback; - callbacks.getTime = getTime; - - MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); - /* Establish MQTT session with a CONNECT packet. */ - connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - connectInfo.keepAliveSeconds = 0; - connectInfo.pUserName = NULL; - connectInfo.userNameLength = 0; - connectInfo.pPassword = NULL; - connectInfo.passwordLength = 0; + /* Application callback for getting the time for MQTT library. This time + * function will be used to calculate intervals in MQTT library.*/ + callbacks.getTime = getTimeMs; - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, NULL ); + /* Initialize MQTT library. */ + mqttStatus = MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); if( mqttStatus != MQTTSuccess ) { status = EXIT_FAILURE; + LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); + } + else + { + /* Establish MQTT session with a CONNECT packet. */ + + /* If #createCleanSession is true, start with a clean session + * i.e. direct the MQTT broker to discard any previous session data. + * If #createCleanSession is false, directs the broker to attempt to + * reestablish a session which was already present. */ + connectInfo.cleanSession = createCleanSession; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this demo. */ + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0U; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + + /* Send MQTT CONNECT packet to broker. */ + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + } + else + { + LogInfo( ( "MQTT connection successfully established with broker.\n\n" ) ); + } } return status; @@ -376,23 +1028,164 @@ static int establishMqttSession( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ -/** - * @brief Close an MQTT session by sending MQTT DISCONNECT. - * - * @param[in] pContext MQTT context. - * - * @return EXIT_SUCCESS if DISCONNECT was successfully sent; EXIT_FAILURE otherwise. - */ static int disconnectMqttSession( MQTTContext_t * pContext ) { int status = EXIT_SUCCESS; + assert( pContext != NULL ); + + /* Send DISCONNECT. */ MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); if( mqttStatus != MQTTSuccess ) { + LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int subscribeToTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to only one topic and uses QOS2. */ + pSubscriptionList[ 0 ].qos = MQTTQoS2; + pSubscriptionList[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; + + /* Generate packet identifier for the SUBSCRIBE packet. */ + globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send SUBSCRIBE packet. */ + mqttStatus = MQTT_Subscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalSubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "SUBSCRIBE sent for topic %.*s to broker.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to and unsubscribes from only one topic + * and uses QOS2. */ + pSubscriptionList[ 0 ].qos = MQTTQoS2; + pSubscriptionList[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; + + /* Generate packet identifier for the UNSUBSCRIBE packet. */ + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send UNSUBSCRIBE packet. */ + mqttStatus = MQTT_Unsubscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalUnsubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); status = EXIT_FAILURE; } + else + { + LogInfo( ( "UNSUBSCRIBE sent for topic %.*s to broker.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int publishToTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttSuccess; + uint8_t publishIndex = MAX_OUTGOING_PUBLISHES; + + assert( pContext != NULL ); + + /* Get the next free index for the outgoing publish. All QoS2 outgoing + * publishes are stored until a PUBREC is received. These messages are + * stored for supporting a resend if a network connection is broken before + * receiving a PUBREC. */ + status = getNextFreeIndexForOutgoingPublishes( &publishIndex ); + + if( status == EXIT_FAILURE ) + { + LogError( ( "Unable to find a free spot for outgoing PUBLISH message.\n\n" ) ); + } + else + { + /* This example publishes to only one topic and uses QOS2. */ + outgoingPublishPackets[ publishIndex ].pubInfo.qos = MQTTQoS2; + outgoingPublishPackets[ publishIndex ].pubInfo.pTopicName = MQTT_EXAMPLE_TOPIC; + outgoingPublishPackets[ publishIndex ].pubInfo.topicNameLength = MQTT_EXAMPLE_TOPIC_LENGTH; + outgoingPublishPackets[ publishIndex ].pubInfo.pPayload = MQTT_EXAMPLE_MESSAGE; + outgoingPublishPackets[ publishIndex ].pubInfo.payloadLength = MQTT_EXAMPLE_MESSAGE_LENGTH; + + /* Get a new packet id. */ + outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pContext ); + + /* Send PUBLISH packet. */ + mqttSuccess = MQTT_Publish( pContext, + &outgoingPublishPackets[ publishIndex ].pubInfo, + outgoingPublishPackets[ publishIndex ].packetId ); + + if( status != MQTTSuccess ) + { + LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", + mqttSuccess ) ); + cleanupOutgoingPublishAt( publishIndex ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "PUBLISH send for topic %.*s to broker with packet id %u.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC, + outgoingPublishPackets[ publishIndex ].packetId ) ); + } + } return status; } @@ -401,56 +1194,190 @@ static int disconnectMqttSession( MQTTContext_t * pContext ) /** * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TLS connection established using openSSL. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS2 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBREC + * are resend in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBREC is received. */ int main( int argc, char ** argv ) { - bool mqttSessionEstablished = false; - int status; + int status = EXIT_SUCCESS, tcpSocket; + bool sessionPresent, mqttSessionEstablished = false; MQTTContext_t context; + MQTTStatus_t mqttStatus = MQTTSuccess; SSL * pSslContext = NULL; + uint32_t publishCount = 0; + const uint32_t maxPublishCount = 5U; - /* Establish TCP connection and MQTT session. */ - int tcpSocket = connectToServer( SERVER, PORT ); + ( void ) argc; + ( void ) argv; - if( tcpSocket == -1 ) + /* Get the entry time to application. */ + globalEntryTimeMs = getTimeMs(); + + /* Establish a TCP connection with the MQTT broker. This example connects + * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at + * the top of this file. */ + LogInfo( ( "Creating a TCP connection to %.*s:%d.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT, + BROKER_PORT ) ); + status = connectToServer( BROKER_ENDPOINT, BROKER_PORT, &tcpSocket ); + + /* Establish TLS connection on top of TCP connection. */ + if( status == EXIT_SUCCESS ) { - status = EXIT_FAILURE; + LogInfo( ( "Creating a TLS connection on top of the TCP connection." ) ); + status = tlsSetup( tcpSocket, &pSslContext ); } - else + + /* Establish MQTT session on top of TCP+TLS connection. */ + if( status == EXIT_SUCCESS ) { - status = EXIT_SUCCESS; + /* Sends an MQTT Connect packet over the already connected TCP socket + * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + status = establishMqttSession( &context, pSslContext, false, &sessionPresent ); + + if( status == EXIT_SUCCESS ) + { + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be send at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; + } } - /* Establish TLS connection on top of TCP connection. */ if( status == EXIT_SUCCESS ) { - pSslContext = tlsSetup( tcpSocket ); + /* Check if session is present and if there are any outgoing publishes + * that need to resend. This is only valid if the broker is + * re-establishing a session which was already present. */ + if( sessionPresent == true ) + { + LogInfo( ( "An MQTT session with broker is re-established. " + "Resending unacked publishes." ) ); - if( pSslContext == NULL ) + /* Handle all the resend of publish messages. */ + status = handlePublishResend( &context ); + } + else { - status = EXIT_FAILURE; + LogInfo( ( "A clean MQTT connection is established." + " Cleaning up all the stored outgoing publishes.\n\n" ) ); + + /* Clean up the outgoing publishes waiting for ack as this new + * connection doesn't re-establish an existing session. */ + cleanupOutgoingPublishes(); } } - /* Establish MQTT session on top of TCP+TLS connection. */ if( status == EXIT_SUCCESS ) { - status = establishMqttSession( &context, pSslContext ); + /* The client is now connected to the broker. Subscribe to the topic + * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a + * subscribe packet. This client will then publish to the same topic it + * subscribed to, so it will expect all the messages it sends to the broker + * to be sent back to it from the broker. This demo uses QOS2 in Subscribe, + * therefore, the Publish messages received from the broker will have QOS2. */ + LogInfo( ( "Subscribing to the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = subscribeToTopic( &context ); + } - if( status == EXIT_SUCCESS ) + if( status == EXIT_SUCCESS ) + { + /* Process incoming packet from the broker. Acknowledgment for subscription + * ( SUBACK ) will be received here. However after sending the subscribe, the + * client may receive a publish before it receives a subscribe ack. Since this + * demo is subscribing to the topic to which no one is publishing, probability + * of receiving publish message before subscribe ack is zero; but application + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * receive packet from network. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) { - mqttSessionEstablished = true; + status = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + } + + if( status == EXIT_SUCCESS ) + { + /* Publish messages with QOS2, receive incoming messages and + * send keep alive messages. */ + for( publishCount = 0; publishCount < maxPublishCount; publishCount++ ) + { + LogInfo( ( "Sending Publish to the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = publishToTopic( &context ); + + /* Calling MQTT_ProcessLoop to process incoming publish echo, since + * application subscribed to the same topic the broker will send + * publish message back to the application. This function also + * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS + * has expired since the last MQTT packet sent and receive + * ping responses. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + LogWarn( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + + LogInfo( ( "Delay before continuing to next iteration.\n\n" ) ); + + /* Leave connection idle for some time. */ + sleep( DELAY_BETWEEN_PUBLISHES_SECONDS ); } } - /* Insert demo code here. */ + if( status == EXIT_SUCCESS ) + { + /* Unsubscribe from the topic. */ + LogInfo( ( "Unsubscribing from the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = unsubscribeFromTopic( &context ); + } + + if( status == EXIT_SUCCESS ) + { + /* Process Incoming UNSUBACK packet from the broker. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); - /* Disconnect MQTT session if established. */ + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + } + + /* Send an MQTT Disconnect packet over the already connected TCP socket. + * There is no corresponding response for the disconnect packet. After sending + * disconnect, client must close the network connection. */ if( mqttSessionEstablished == true ) { if( status == EXIT_FAILURE ) { + /* Returned status is not used to update the local status as there + * were failures in demo execution. */ ( void ) disconnectMqttSession( &context ); } else @@ -479,6 +1406,12 @@ int main( int argc, close( tcpSocket ); } + /* Log the success message. */ + if( status == EXIT_SUCCESS ) + { + LogInfo( ( "Demo completed successfully." ) ); + } + return status; } From d613385677b2cabe6fb4adddd7fe2aaef9648573 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Wed, 10 Jun 2020 23:15:49 -0700 Subject: [PATCH 538/844] Fix remaining warnings in the http client code and tests. (#991) --- libraries/standard/http/src/http_client.c | 51 +++++++++---------- .../standard/http/utest/http_send_utest.c | 14 ++--- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 4e76172c28..320886b17d 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -481,9 +481,9 @@ static void processCompleteHeader( HTTPParsingContext_t * pParsingContext ) LogDebug( ( "Response parsing: Found complete header: " "HeaderField=%.*s, HeaderValue=%.*s", - pParsingContext->lastHeaderFieldLen, + ( int ) ( pParsingContext->lastHeaderFieldLen ), pParsingContext->pLastHeaderField, - pParsingContext->lastHeaderValueLen, + ( int ) ( pParsingContext->lastHeaderValueLen ), pParsingContext->pLastHeaderValue ) ); /* If the application registered a callback, then it must be notified. */ @@ -491,9 +491,9 @@ static void processCompleteHeader( HTTPParsingContext_t * pParsingContext ) { pResponse->pHeaderParsingCallback->onHeaderCallback( pResponse->pHeaderParsingCallback->pContext, - ( const uint8_t * ) pParsingContext->pLastHeaderField, + pParsingContext->pLastHeaderField, pParsingContext->lastHeaderFieldLen, - ( const uint8_t * ) pParsingContext->pLastHeaderValue, + pParsingContext->pLastHeaderValue, pParsingContext->lastHeaderValueLen, pResponse->statusCode ); } @@ -566,7 +566,7 @@ static int httpParserOnStatusCallback( http_parser * pHttpParser, LogDebug( ( "Response parsing: Found the Reason-Phrase: " "StatusCode=%d, ReasonPhrase=%.*s", pResponse->statusCode, - length, + ( int ) length, pLoc ) ); return HTTP_PARSER_CONTINUE_PARSING; @@ -595,7 +595,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, * to NULL in the preceding httpParseOnStatusFieldCallback(). */ if( pResponse->pHeaders == NULL ) { - pResponse->pHeaders = pLoc; + pResponse->pHeaders = ( const uint8_t * ) pLoc; } /* Set the location of what to parse next. */ @@ -623,7 +623,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, LogDebug( ( "Response parsing: Found a header field: " "HeaderField=%.*s", - length, + ( int ) length, pLoc ) ); return HTTP_PARSER_CONTINUE_PARSING; @@ -672,7 +672,7 @@ static int httpParserOnHeaderValueCallback( http_parser * pHttpParser, LogDebug( ( "Response parsing: Found a header value: " "HeaderValue=%.*s", - length, + ( int ) length, pLoc ) ); return HTTP_PARSER_CONTINUE_PARSING; @@ -818,10 +818,9 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, /* Set the next location of parsing. */ pParsingContext->pBufferCur = pLoc + length; - LogDebug( ( "Response parsing: Found the response body. " + LogDebug( ( "Response parsing: Found the response body: " "BodyLength=%d", - length, - pLoc ) ); + ( int ) length ) ); return shouldContinueParse; } @@ -1045,10 +1044,10 @@ HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, pParsingContext->pBufferCur, parseLen ); - LogDebug( ( "Parsed HTTP Response buffer: BytesParsed=%d, ", - "ExpectedBytesParsed=%d", - bytesParsed, - parseLen ) ); + LogDebug( ( "Parsed HTTP Response buffer: BytesParsed=%lu, " + "ExpectedBytesParsed=%lu", + ( unsigned long ) bytesParsed, + ( unsigned long ) parseLen ) ); returnStatus = processHttpParserError( &( pParsingContext->httpParser ) ); @@ -1558,10 +1557,10 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, pIndex += transportStatus; LogDebug( ( "Sent HTTP data over the transport: " "BytesSent=%d, BytesRemaining=%lu, " - "TotalBytesSent=%d", + "TotalBytesSent=%lu", transportStatus, bytesRemaining, - dataLen - bytesRemaining ) ); + ( unsigned long ) ( dataLen - bytesRemaining ) ) ); } } @@ -1633,8 +1632,8 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport if( returnStatus == HTTP_SUCCESS ) { - LogDebug( ( "Sending HTTP request headers: HeaderBytes=%d", - pRequestHeaders->headersLen ) ); + LogDebug( ( "Sending HTTP request headers: HeaderBytes=%lu", + ( unsigned long ) ( pRequestHeaders->headersLen ) ) ); /* Send the HTTP headers over the network. */ returnStatus = sendHttpData( pTransport, pRequestHeaders->pBuffer, pRequestHeaders->headersLen ); @@ -1974,8 +1973,8 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) { LogDebug( ( "Found header field in response: " - "HeaderName=%.*s, HeaderLocation=0x%x", - fieldLen, pContext->pField, pFieldLoc ) ); + "HeaderName=%.*s, HeaderLocation=0x%p", + ( int ) fieldLen, pContext->pField, pFieldLoc ) ); /* Set the flag to indicate that header has been found in response. */ pContext->fieldFound = 1u; @@ -2014,8 +2013,8 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, if( pContext->fieldFound == 1u ) { LogDebug( ( "Found header value in response: " - "RequestedField=%.*s, ValueLocation=0x%x", - pContext->fieldLen, pContext->pField, pVaLueLoc ) ); + "RequestedField=%.*s, ValueLocation=0x%p", + ( int ) ( pContext->fieldLen ), pContext->pField, pVaLueLoc ) ); /* Populate the output parameters with the location of the header value in the response buffer. */ *pContext->pValueLoc = pVaLueLoc; @@ -2053,7 +2052,7 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) * header has not been found in the response buffer. */ LogDebug( ( "Reached end of header parsing: Header not found in response: " "RequestedHeader=%.*s", - pContext->fieldLen, + ( int ) ( pContext->fieldLen ), pContext->pField ) ); /* No further parsing is required; thus, indicate the parser to stop parsing. */ @@ -2104,8 +2103,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, ( const char * ) pBuffer, bufferLen ); - LogDebug( ( "Parsed response for header search: NumBytesParsed=%u", - numOfBytesParsed ) ); + LogDebug( ( "Parsed response for header search: NumBytesParsed=%lu", + ( unsigned long ) numOfBytesParsed ) ); if( context.fieldFound == 0u ) { diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index bc4804ed66..0acf8edec4 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -203,29 +203,29 @@ static HTTPClient_ResponseHeaderParsingCallback_t headerParsingCallback = { 0 }; /* Application callback for intercepting the headers during the parse of a new * response from the mocked network interface. */ static void onHeaderCallback( void * pContext, - const uint8_t * fieldLoc, + const char * fieldLoc, size_t fieldLen, - const uint8_t * valueLoc, + const char * valueLoc, size_t valueLen, uint16_t statusCode ) { ( void ) pContext; ( void ) statusCode; - if( strncmp( ( const char * ) fieldLoc, "Connection", fieldLen ) == 0 ) + if( strncmp( fieldLoc, "Connection", fieldLen ) == 0 ) { - if( strncmp( ( const char * ) valueLoc, "keep-alive", valueLen ) == 0 ) + if( strncmp( valueLoc, "keep-alive", valueLen ) == 0 ) { hasConnectionKeepAlive = 1; } - else if( strncmp( ( const char * ) valueLoc, "close", valueLen ) == 0 ) + else if( strncmp( valueLoc, "close", valueLen ) == 0 ) { hasConnectionClose = 1; } } - else if( strncmp( ( const char * ) fieldLoc, "Content-Length", fieldLen ) == 0 ) + else if( strncmp( fieldLoc, "Content-Length", fieldLen ) == 0 ) { - contentLength = strtoul( ( const char * ) valueLoc, NULL, 10 ); + contentLength = strtoul( valueLoc, NULL, 10 ); } headerCallbackCount++; From 34da535b7e7c5302f816332ce8e7d97417e78771 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 11 Jun 2020 19:27:32 -0700 Subject: [PATCH 539/844] Add HTTP server auth demo (#973) --- demos/http/http_demo_basic_tls/CMakeLists.txt | 49 + .../certificates/amazon.crt | 20 + demos/http/http_demo_basic_tls/demo_config.h | 93 ++ demos/http/http_demo_basic_tls/http_config.h | 46 + .../http_demo_basic_tls/http_demo_basic_tls.c | 842 ++++++++++++++++++ 5 files changed, 1050 insertions(+) create mode 100644 demos/http/http_demo_basic_tls/CMakeLists.txt create mode 100644 demos/http/http_demo_basic_tls/certificates/amazon.crt create mode 100644 demos/http/http_demo_basic_tls/demo_config.h create mode 100644 demos/http/http_demo_basic_tls/http_config.h create mode 100644 demos/http/http_demo_basic_tls/http_demo_basic_tls.c diff --git a/demos/http/http_demo_basic_tls/CMakeLists.txt b/demos/http/http_demo_basic_tls/CMakeLists.txt new file mode 100644 index 0000000000..7e38915eff --- /dev/null +++ b/demos/http/http_demo_basic_tls/CMakeLists.txt @@ -0,0 +1,49 @@ +set( DEMO_NAME "http_demo_basic_tls" ) +# Demo target. +add_executable(${DEMO_NAME}) + +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL REQUIRED) + +set( CMAKE_THREAD_PREFER_PTHREAD ON ) +find_package(Threads REQUIRED) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + http + OpenSSL::Crypto + OpenSSL::SSL + # SSL uses Threads and on some platforms require explicit linking. + Threads::Threads + # SSL uses Dynamic Loading and on some platforms requires explicit linking. + ${CMAKE_DL_LIBS} +) + +target_include_directories( + http + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) + +#Copy the certificates and client key to the binary directory. +add_custom_command( + TARGET + ${DEMO_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/certificates" + "$/certificates" +) diff --git a/demos/http/http_demo_basic_tls/certificates/amazon.crt b/demos/http/http_demo_basic_tls/certificates/amazon.crt new file mode 100644 index 0000000000..a6f3e92af5 --- /dev/null +++ b/demos/http/http_demo_basic_tls/certificates/amazon.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- diff --git a/demos/http/http_demo_basic_tls/demo_config.h b/demos/http/http_demo_basic_tls/demo_config.h new file mode 100644 index 0000000000..f17aed0380 --- /dev/null +++ b/demos/http/http_demo_basic_tls/demo_config.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H_ +#define DEMO_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#define LIBRARY_LOG_NAME "DEMO" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief HTTP server host name. + * + * This demo uses httpbin.org: A simple HTTP Request & Response Service. + */ +#define SERVER_HOST "httpbin.org" + +/** + * @brief HTTP server port number. + * + * In general, port 443 is for TLS HTTP connections. + */ +#define SERVER_PORT 443 + +/** + * @brief Path of the file containing the server's root CA certificate for TLS authentication. + * + * @note This certificate should be PEM-encoded. + */ +#define SERVER_CERT_PATH "certificates/amazon.crt" + +/** + * @brief Paths for different HTTP methods for specified host. + * + * For httpbin.org, see http://httpbin.org/#/HTTP_Methods for details on + * supported REST API. + */ +#define GET_PATH "/get" +#define HEAD_PATH "/get" +#define PUT_PATH "/put" +#define POST_PATH "/post" + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 5000 ) + +/** + * @brief The length in bytes of the user buffer. + */ +#define USER_BUFFER_LENGTH ( 1024 ) + +/** + * @brief Request body to send for PUT and POST requests in this demo. + */ +#define REQUEST_BODY "Hello, world!" + +#endif /* ifndef DEMO_CONFIG_H_ */ diff --git a/demos/http/http_demo_basic_tls/http_config.h b/demos/http/http_demo_basic_tls/http_config.h new file mode 100644 index 0000000000..dbe1ab0116 --- /dev/null +++ b/demos/http/http_demo_basic_tls/http_config.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef HTTP_CONFIG_H_ +#define HTTP_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the HTTP library. */ +#define LIBRARY_LOG_NAME "HTTP" +#define LIBRARY_LOG_LEVEL LOG_INFO + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef HTTP_CONFIG_H_ */ diff --git a/demos/http/http_demo_basic_tls/http_demo_basic_tls.c b/demos/http/http_demo_basic_tls/http_demo_basic_tls.c new file mode 100644 index 0000000000..625d686ee1 --- /dev/null +++ b/demos/http/http_demo_basic_tls/http_demo_basic_tls.c @@ -0,0 +1,842 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include +#include + +/* POSIX socket includes. */ +#include +#include +#include +#include +#include + +/* Socket includes. */ +#include +#include + +/* OpenSSL includes. */ +#include +#include + +/* HTTP API header. */ +#include "http_client.h" + +/* Demo Config header. */ +#include "demo_config.h" + +/* Check that hostname of the server is defined. */ +#ifndef SERVER_HOST + #error "Please define a SERVER_HOST." +#endif + +/* Check that TLS port of the server is defined. */ +#ifndef SERVER_PORT + #error "Please define a SERVER_PORT." +#endif + +/* Check that a path for HTTP Method GET is defined. */ +#ifndef GET_PATH + #error "Please define a GET_PATH." +#endif + +/* Check that a path for HTTP Method HEAD is defined. */ +#ifndef HEAD_PATH + #error "Please define a HEAD_PATH." +#endif + +/* Check that a path for HTTP Method PUT is defined. */ +#ifndef PUT_PATH + #error "Please define a PUT_PATH." +#endif + +/* Check that a path for HTTP Method POST is defined. */ +#ifndef POST_PATH + #error "Please define a POST_PATH." +#endif + +/* Check that transport timeout for transport send and receive is defined. */ +#ifndef TRANSPORT_SEND_RECV_TIMEOUT_MS + #define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 1000 ) +#endif + +/* Check that size of the user buffer is defined. */ +#ifndef USER_BUFFER_LENGTH + #define USER_BUFFER_LENGTH ( 1024 ) +#endif + +/* Check that a request body to send for PUT and POST requests is defined. */ +#ifndef REQUEST_BODY + #error "Please define a REQUEST_BODY." +#endif + +/** + * @brief The length of the HTTP server host name. + */ +#define SERVER_HOST_LENGTH ( sizeof( SERVER_HOST ) - 1 ) + +/** + * @brief The length of the HTTP GET method. + */ +#define HTTP_METHOD_GET_LENGTH ( sizeof( HTTP_METHOD_GET ) - 1 ) + +/** + * @brief The length of the HTTP HEAD method. + */ +#define HTTP_METHOD_HEAD_LENGTH ( sizeof( HTTP_METHOD_HEAD ) - 1 ) + +/** + * @brief The length of the HTTP PUT method. + */ +#define HTTP_METHOD_PUT_LENGTH ( sizeof( HTTP_METHOD_PUT ) - 1 ) + +/** + * @brief The length of the HTTP POST method. + */ +#define HTTP_METHOD_POST_LENGTH ( sizeof( HTTP_METHOD_POST ) - 1 ) + +/** + * @brief The length of the HTTP GET path. + */ +#define GET_PATH_LENGTH ( sizeof( GET_PATH ) - 1 ) + +/** + * @brief The length of the HTTP HEAD path. + */ +#define HEAD_PATH_LENGTH ( sizeof( HEAD_PATH ) - 1 ) + +/** + * @brief The length of the HTTP PUT path. + */ +#define PUT_PATH_LENGTH ( sizeof( PUT_PATH ) - 1 ) + +/** + * @brief The length of the HTTP POST path. + */ +#define POST_PATH_LENGTH ( sizeof( POST_PATH ) - 1 ) + +/** + * @brief Length of path to server certificate. + */ +#define SERVER_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( SERVER_CERT_PATH ) - 1 ) ) + +/** + * @brief Length of the request body. + */ +#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) + +/** + * @brief Length of an IPv6 address when converted to hex digits. + */ +#define IPV6_ADDRESS_STRING_LENGTH ( 40 ) + +/** + * @brief A buffer used in the demo for storing HTTP request headers and + * HTTP response headers and body. + * + * @note This demo shows how the same buffer can be re-used for storing the HTTP + * response after the HTTP request is sent out. However, the user can also + * decide to use separate buffers for storing the HTTP request and response. + */ +static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; + +/** + * @brief Definition of the HTTP network context. + * + * @note For this TLS demo, the socket descriptor and SSL context is used. + */ +struct HTTPNetworkContext +{ + int tcpSocket; + SSL * pSslContext; +}; + +/** + * @brief Structure based on the definition of the HTTP network context. + */ +static HTTPNetworkContext_t networkContext; + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs a DNS lookup on the given host name, then establishes a TCP + * connection to the server. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * @param[out] pTcpSocket The output parameter to return the created socket descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int connectToServer( const char * pServer, + size_t serverLen, + uint16_t port, + int * pTcpSocket ); + +/** + * @brief Set up a TLS connection over an existing TCP connection. + * + * @param[in] tcpSocket Socket descriptor corresponding to the existing TCP connection. + * @param[out] pSslContext The output parameter to return the created SSL context. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int tlsSetup( int tcpSocket, + SSL ** pSslContext ); + +/** + * @brief The transport send function that defines the transport interface. + * + * This is passed as the #HTTPTransportInterface.send function and used to + * send data over the network. + * + * @param[in] pContext User defined context (TCP socket and SSL context for this demo). + * @param[in] pBuffer Buffer containing the bytes to send over the network stack. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return Number of bytes sent if successful; otherwise negative value on error. + */ +static int32_t transportSend( HTTPNetworkContext_t * pContext, + const void * pBuffer, + size_t bytesToSend ); + +/** + * @brief The transport receive function that defines the transport interface. + * + * This is passed as the #HTTPTransportInterface.recv function used for reading + * data received from the network. + * + * @param[in] pContext User defined context (TCP socket and SSL context for this demo). + * @param[out] pBuffer Buffer to read network data into. + * @param[in] bytesToRead Number of bytes requested from the network. + * + * @return Number of bytes received if successful; otherwise negative value on error. + */ +static int32_t transportRecv( HTTPNetworkContext_t * pContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief Send an HTTP request based on a specified method and path, then + * print the response received from the server. + * + * @param[in] pTransportInterface The transport interface for making network calls. + * @param[in] pMethod The HTTP request method. + * @param[in] methodLen The length of the HTTP request method. + * @param[in] pPath The Request-URI to the objects of interest. + * @param[in] pathLen The length of the Request-URI. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ); + +/*-----------------------------------------------------------*/ + +static int connectToServer( const char * pServer, + size_t serverLen, + uint16_t port, + int * pTcpSocket ) +{ + int returnStatus = EXIT_SUCCESS; + struct addrinfo hints, * pIndex, * pListHead = NULL; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + char resolvedIpAddr[ IPV6_ADDRESS_STRING_LENGTH ]; + + /* Initialize string to store the resolved IP address from the host name. */ + ( void ) memset( resolvedIpAddr, 0, IPV6_ADDRESS_STRING_LENGTH ); + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0, sizeof( hints ) ); + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* Perform a DNS lookup on the given host name. */ + returnStatus = getaddrinfo( pServer, NULL, &hints, &pListHead ); + + if( returnStatus != -1 ) + { + LogInfo( ( "Performing DNS lookup: Host=%.*s.", + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST ) ); + + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( *pTcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + inet_ntop( pServerInfo->sa_family, + &( ( struct sockaddr_in * ) pServerInfo )->sin_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + inet_ntop( pServerInfo->sa_family, + &( ( struct sockaddr_in6 * ) pServerInfo )->sin6_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + + LogInfo( ( "Attempting to connect to server: Host=%.*s, IP address=%s.", + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, resolvedIpAddr ) ); + + returnStatus = connect( *pTcpSocket, pServerInfo, serverInfoLength ); + + if( returnStatus == -1 ) + { + LogError( ( "Failed to connect to server: Host=%.*s, IP address=%s.", + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, resolvedIpAddr ) ); + close( *pTcpSocket ); + } + else + { + LogInfo( ( "Connected to IP address: %s.", + resolvedIpAddr ) ); + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + LogError( ( "Could not connect to any resolved IP address from %.*s.", + ( int32_t ) serverLen, + pServer ) ); + returnStatus = EXIT_FAILURE; + } + else + { + LogInfo( ( "Established TCP connection: Server=%.*s.\n", + ( int32_t ) serverLen, + pServer ) ); + returnStatus = EXIT_SUCCESS; + } + } + else + { + LogError( ( "Could not resolve host %.*s.\n", + ( int32_t ) serverLen, + pServer ) ); + returnStatus = EXIT_FAILURE; + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int tlsSetup( int tcpSocket, + SSL ** pSslContext ) +{ + int returnStatus = EXIT_SUCCESS; + long verifyPeerCertStatus = X509_V_OK; + int sslStatus = 0; + char * cwd = getcwd( NULL, 0 ); + FILE * pRootCaFile = NULL; + X509 * pRootCa = NULL; + + /* Setup for creating a TLS client. */ + SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); + + if( pSslSetup != NULL ) + { + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. + * The mask returned by SSL_CTX_set_mode does not need to be checked. */ + ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); + + /* Log the absolute directory based on first character of certificate path. */ + if( ( SERVER_CERT_PATH[ 0 ] == '/' ) || ( SERVER_CERT_PATH[ 0 ] == '\\' ) ) + { + LogInfo( ( "Attempting to open root CA certificate: Path=%.*s.", + ( int32_t ) SERVER_CERT_PATH_LENGTH, + SERVER_CERT_PATH ) ); + } + else + { + LogInfo( ( "Attempting to open root CA certificate: Path=%s/%.*s.", + cwd, + ( int32_t ) SERVER_CERT_PATH_LENGTH, + SERVER_CERT_PATH ) ); + } + + /* OpenSSL does not provide a single function for reading and loading + * certificates from files into stores, so the file API must be called. */ + pRootCaFile = fopen( SERVER_CERT_PATH, "r" ); + + if( pRootCaFile == NULL ) + { + LogError( ( "fopen failed to find the root CA certificate file: " + "SERVER_CERT_PATH=%.*s.", + ( int32_t ) SERVER_CERT_PATH_LENGTH, + SERVER_CERT_PATH ) ); + } + else + { + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + } + + if( pRootCa == NULL ) + { + LogError( ( "PEM_read_X509 failed to read the " + "root CA certificate filestream." ) ); + } + else + { + /* Add the server's root CA to the set of trusted certificates. */ + sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), + pRootCa ); + } + } + + /* Set up the TLS connection. */ + if( sslStatus == 1 ) + { + /* Create a new SSL context. */ + *pSslContext = SSL_new( pSslSetup ); + + if( *pSslContext == NULL ) + { + LogError( ( "SSL_new failed to create a new SSL context." ) ); + sslStatus = 0; + } + else + { + /* Enable SSL peer verification. */ + SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); + + sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); + + if( sslStatus != 1 ) + { + LogError( ( "SSL_set_fd failed to set the socket fd to SSL context." ) ); + } + } + + /* Perform the TLS handshake. */ + if( sslStatus == 1 ) + { + sslStatus = SSL_connect( *pSslContext ); + + if( sslStatus != 1 ) + { + LogError( ( "SSL_connect failed to perform TLS handshake." ) ); + } + } + + /* Verify X509 certificate from peer. */ + if( sslStatus == 1 ) + { + verifyPeerCertStatus = SSL_get_verify_result( *pSslContext ); + + if( verifyPeerCertStatus != X509_V_OK ) + { + LogError( ( "SSL_get_verify_result failed to verify X509 " + "certificate from peer." ) ); + sslStatus = 0; + } + } + + /* Clean up on error. */ + if( sslStatus == 0 ) + { + SSL_free( *pSslContext ); + *pSslContext = NULL; + } + } + else + { + LogError( ( "X509_STORE_add_cert failed to add certificate to store." ) ); + } + + if( cwd != NULL ) + { + free( cwd ); + } + + if( pRootCaFile != NULL ) + { + ( void ) fclose( pRootCaFile ); + } + + if( pRootCa != NULL ) + { + X509_free( pRootCa ); + } + + if( pSslSetup != NULL ) + { + SSL_CTX_free( pSslSetup ); + } + + /* Log failure or success and return the correct exit status. */ + if( sslStatus == 0 ) + { + LogError( ( "Failed to establish a TLS connection: Host=%.*s.", + ( int32_t ) SERVER_HOST_LENGTH, + SERVER_HOST ) ); + returnStatus = EXIT_FAILURE; + } + else + { + LogInfo( ( "Established a TLS connection: Host=%.*s.\n\n", + ( int32_t ) SERVER_HOST_LENGTH, + SERVER_HOST ) ); + returnStatus = EXIT_SUCCESS; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int32_t transportSend( HTTPNetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ) +{ + int32_t bytesSent = 0; + int pollStatus = 0; + struct pollfd fileDescriptor; + + /* Initialize the file descriptor. */ + fileDescriptor.events = POLLOUT; + fileDescriptor.revents = 0; + /* Set the file descriptor for poll. */ + fileDescriptor.fd = pNetworkContext->tcpSocket; + + /* Poll the file descriptor to check if SSL_Write can be done now. */ + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( pollStatus > 0 ) + { + bytesSent = ( int32_t ) SSL_write( pNetworkContext->pSslContext, pBuffer, bytesToSend ); + } + else if( pollStatus == 0 ) + { + LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); + } + else + { + LogError( ( "Polling of the SSL socket for write buffer availability failed:" + " status=%d", + pollStatus ) ); + bytesSent = -1; + } + + return bytesSent; +} + +/*-----------------------------------------------------------*/ + +static int32_t transportRecv( HTTPNetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + int pollStatus = -1, bytesAvailableToRead = 0; + struct pollfd fileDescriptor; + + /* Initialize the file descriptor. */ + fileDescriptor.events = POLLIN | POLLPRI; + fileDescriptor.revents = 0; + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pNetworkContext->pSslContext ); + + /* Check if there are any pending data available for read. */ + bytesAvailableToRead = SSL_pending( pNetworkContext->pSslContext ); + + /* Poll only if there is no data available yet to read. */ + if( bytesAvailableToRead <= 0 ) + { + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + } + + /* bytesAvailableToRead > 0 means that there was pending data to be read. + * pollStatus > 0 means that there was no pending data, but it became available + * during polling. If either holds true, read the available data. */ + if( ( bytesAvailableToRead > 0 ) || ( pollStatus > 0 ) ) + { + bytesReceived = ( int32_t ) SSL_read( pNetworkContext->pSslContext, pBuffer, bytesToRecv ); + } + /* Poll timed out. */ + else if( pollStatus == 0 ) + { + LogInfo( ( "Poll timed out and there is no data to read from the buffer." ) ); + } + else + { + LogError( ( "Poll returned with status = %d.", pollStatus ) ); + bytesReceived = -1; + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ) +{ + /* Return value of this method. */ + int returnStatus = EXIT_SUCCESS; + + /* Configurations of the initial request headers that are passed to + * #HTTPClient_InitializeRequestHeaders. */ + HTTPRequestInfo_t requestInfo; + /* Represents a response returned from an HTTP server. */ + HTTPResponse_t response; + /* Represents header data that will be sent in an HTTP request. */ + HTTPRequestHeaders_t requestHeaders; + + /* Return value of all methods from the HTTP Client library API. */ + HTTPStatus_t httpStatus = HTTP_SUCCESS; + + assert( pMethod != NULL ); + assert( methodLen > 0 ); + + /* Initialize all HTTP Client library API structs to 0. */ + ( void ) memset( &requestInfo, 0, sizeof( requestInfo ) ); + ( void ) memset( &response, 0, sizeof( response ) ); + ( void ) memset( &requestHeaders, 0, sizeof( requestHeaders ) ); + + /* Initialize the request object. */ + requestInfo.pHost = SERVER_HOST; + requestInfo.hostLen = SERVER_HOST_LENGTH; + requestInfo.method = pMethod; + requestInfo.methodLen = methodLen; + requestInfo.pPath = pPath; + requestInfo.pathLen = pathLen; + + /* Set "Connection" HTTP header to "keep-alive" so that multiple requests + * can be sent over the same established TCP connection. */ + requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + + /* Set the buffer used for storing request headers. */ + requestHeaders.pBuffer = userBuffer; + requestHeaders.bufferLen = USER_BUFFER_LENGTH; + + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, + &requestInfo ); + + if( httpStatus == HTTP_SUCCESS ) + { + /* Initialize the response object. The same buffer used for storing + * request headers is reused here. */ + response.pBuffer = userBuffer; + response.bufferLen = USER_BUFFER_LENGTH; + + LogInfo( ( "Sending HTTP %.*s request to %.*s%.*s...", + ( int32_t ) methodLen, pMethod, + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, + ( int32_t ) pathLen, pPath ) ); + LogDebug( ( "Request Headers:\n%.*s\n" + "Request Body:\n%.*s\n", + ( int32_t ) requestHeaders.headersLen, + ( char * ) requestHeaders.pBuffer, + ( int32_t ) REQUEST_BODY_LENGTH, REQUEST_BODY ) ); + /* Send the request and receive the response. */ + httpStatus = HTTPClient_Send( pTransportInterface, + &requestHeaders, + ( uint8_t * ) REQUEST_BODY, + REQUEST_BODY_LENGTH, + &response, + 0 ); + } + else + { + LogError( ( "Failed to initialize HTTP request headers: Error=%s.", + HTTPClient_strerror( httpStatus ) ) ); + } + + if( httpStatus == HTTP_SUCCESS ) + { + LogInfo( ( "Received HTTP response from %.*s%.*s...\n" + "Response Headers:\n%.*s\n" + "Response Status:\n%u\n" + "Response Body:\n%.*s\n", + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, + ( int32_t ) pathLen, pPath, + ( int32_t ) response.headersLen, response.pHeaders, + response.statusCode, + ( int32_t ) response.bodyLen, response.pBody ) ); + } + else + { + LogError( ( "Failed to send HTTP %.*s request to %.*s%.*s: Error=%s.", + ( int32_t ) methodLen, pMethod, + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, + ( int32_t ) pathLen, pPath, + HTTPClient_strerror( httpStatus ) ) ); + } + + if( httpStatus != HTTP_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * This example resolves a domain, establishes a TCP connection, validates the + * server's certificate using the root CA certificate defined in the config header, + * then finally performs a TLS handshake with the HTTP server so that all communication + * is encrypted. After which, HTTP Client library API is used to send a GET, HEAD, + * PUT, and POST request in that order. For each request, the HTTP response from the + * server (or an error code) is logged. + * + * @note This example is single-threaded and uses statically allocated memory. + * + */ +int main( int argc, + char ** argv ) +{ + /* Return value of main. */ + int returnStatus = EXIT_SUCCESS; + /* The HTTP Client library transport layer interface. */ + HTTPTransportInterface_t transportInterface; + + ( void ) argc; + ( void ) argv; + + /**************************** Connect. ******************************/ + + /* Establish TCP connection. */ + returnStatus = connectToServer( SERVER_HOST, SERVER_HOST_LENGTH, + SERVER_PORT, &networkContext.tcpSocket ); + + /* Establish TLS connection on top of TCP connection. */ + if( returnStatus == EXIT_SUCCESS ) + { + LogInfo( ( "Performing TLS handshake on top of the TCP connection." ) ); + returnStatus = tlsSetup( networkContext.tcpSocket, + &networkContext.pSslContext ); + } + + /* Define the transport interface. */ + if( returnStatus == EXIT_SUCCESS ) + { + ( void ) memset( &transportInterface, 0, sizeof( transportInterface ) ); + transportInterface.recv = transportRecv; + transportInterface.send = transportSend; + transportInterface.pContext = &networkContext; + } + + /*********************** Send HTTPS request. ************************/ + + /* Send GET Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + HTTP_METHOD_GET, + HTTP_METHOD_GET_LENGTH, + GET_PATH, + GET_PATH_LENGTH ); + } + + /* Send HEAD Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + HTTP_METHOD_HEAD, + HTTP_METHOD_HEAD_LENGTH, + HEAD_PATH, + HEAD_PATH_LENGTH ); + } + + /* Send PUT Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + HTTP_METHOD_PUT, + HTTP_METHOD_PUT_LENGTH, + PUT_PATH, + PUT_PATH_LENGTH ); + } + + /* Send POST Request. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + HTTP_METHOD_POST, + HTTP_METHOD_POST_LENGTH, + POST_PATH, + POST_PATH_LENGTH ); + } + + /************************** Disconnect. *****************************/ + + /* Close TLS session if established. */ + if( networkContext.pSslContext != NULL ) + { + /* SSL shutdown should be called twice: once to send "close notify" and + * once more to receive the peer's "close notify". */ + if( SSL_shutdown( networkContext.pSslContext ) == 0 ) + { + ( void ) SSL_shutdown( networkContext.pSslContext ); + } + + SSL_free( networkContext.pSslContext ); + } + + if( networkContext.tcpSocket != -1 ) + { + ( void ) shutdown( networkContext.tcpSocket, SHUT_RDWR ); + ( void ) close( networkContext.tcpSocket ); + } + + return returnStatus; +} From 8dacf510b9f7b90faf415e6d63d2f14960d40bed Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:49:32 -0700 Subject: [PATCH 540/844] MQTT State engine refactor and hygiene (#979) * Refactor state engine * Reduce complexity and add strerror for state engine * Fix MQTT build warnings and add status strerror * Fix publish unit test to not trigger assert --- libraries/standard/mqtt/include/mqtt.h | 16 ++ libraries/standard/mqtt/include/mqtt_state.h | 36 ++- libraries/standard/mqtt/src/mqtt.c | 219 ++++++++++++------ .../standard/mqtt/src/mqtt_lightweight.c | 12 +- libraries/standard/mqtt/src/mqtt_state.c | 149 +++++++++--- .../standard/mqtt/utest/mqtt_state_utest.c | 171 ++++++++++---- libraries/standard/mqtt/utest/mqtt_utest.c | 124 +++++++++- 7 files changed, 550 insertions(+), 177 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 98bc2fd233..4fc1494d83 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -265,6 +265,22 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, uint32_t timeoutMs ); +/** + * @brief Get a packet ID that is valid according to the MQTT 3.1.1 spec. + * + * @param[in] pContext Initialized MQTT context. + * + * @return A non-zero number. + */ uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ); +/** + * @brief Error code to string conversion for MQTT statuses. + * + * @param[in] status The status to convert to a string. + * + * @return The string representation of the status. + */ +const char * MQTT_Status_strerror( MQTTStatus_t status ); + #endif /* ifndef MQTT_H */ diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index 044e807717..00824c985f 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -32,7 +32,7 @@ typedef enum MQTTStateOperation { MQTT_SEND, - MQTT_RECEIVE, + MQTT_RECEIVE } MQTTStateOperation_t; /** @@ -71,13 +71,16 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, * @param[in] packetId ID of the PUBLISH packet. * @param[in] opType Send or Receive. * @param[in] qos 0, 1, or 2. + * @param[out] pNewState Updated state of the publish. * - * @return The new state of the publish. + * @return #MQTTBadParameter, #MQTTIllegalState, #MQTTStateCollision or + * #MQTTSuccess. */ -MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, - uint16_t packetId, - MQTTStateOperation_t opType, - MQTTQoS_t qos ); +MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTStateOperation_t opType, + MQTTQoS_t qos, + MQTTPublishState_t * pNewState ); /** * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP. @@ -99,13 +102,15 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, * @param[in] packetId ID of the ack packet. * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. * @param[in] opType Send or Receive. + * @param[out] pNewState Updated state of the publish. * - * @return The new state of the publish. + * @return #MQTTBadParameter, #MQTTIllegalState, or #MQTTSuccess. */ -MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, - uint16_t packetId, - MQTTPubAckType_t packetType, - MQTTStateOperation_t opType ); +MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTPubAckType_t packetType, + MQTTStateOperation_t opType, + MQTTPublishState_t * pNewState ); /** * @brief Get the packet ID and index of a publish in a specified state. @@ -118,4 +123,13 @@ uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, MQTTPublishState_t searchState, MQTTStateCursor_t * pCursor ); +/** + * @brief State to string conversion for state engine. + * + * @param[in] state The state to convert to a string. + * + * @return The string representation of the state. + */ +const char * MQTT_State_strerror( MQTTPublishState_t state ); + #endif /* ifndef MQTT_STATE_H */ diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 9ac2e8d533..e8c26454eb 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -105,18 +105,28 @@ static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, MQTTPacketInfo_t incomingPacket, uint32_t remainingTimeMs ); +/** + * @brief Get the correct ack type to send. + * + * @param[in] state Current state of publish. + * + * @return Packet Type byte of PUBACK, PUBREC, PUBREL, or PUBCOMP if one of + * those should be sent, else 0. + */ +static uint8_t getAckTypeToSend( MQTTPublishState_t state ); + /** * @brief Send acks for received QoS 1/2 publishes. * * @param[in] pContext MQTT Connection context. * @param[in] packetId packet ID of original PUBLISH. - * @param[in,out] pPublishState Current/updated publish state in record. + * @param[in] publishState Current publish state in record. * - * @return #MQTTSuccess or #MQTTIllegalState. + * @return #MQTTSuccess, #MQTTIllegalState or #MQTTSendFailed. */ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, uint16_t packetId, - MQTTPublishState_t * pPublishState ); + MQTTPublishState_t publishState ); /** * @brief Send a keep alive PINGREQ if the keep alive interval has elapsed. @@ -211,7 +221,6 @@ static int32_t sendPacket( MQTTContext_t * pContext, uint32_t sendTime = 0U; assert( pContext != NULL ); - assert( bytesToSend != 0 ); /* Record the time of transmission. */ sendTime = pContext->callbacks.getTime(); @@ -244,7 +253,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, } /* Update time of last transmission if the entire packet was successfully sent. */ - if( totalBytesSent > -1 ) + if( totalBytesSent > 0 ) { pContext->lastPacketTime = sendTime; LogDebug( ( "Successfully sent packet at time %u.", @@ -283,14 +292,11 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ) break; case MQTT_PACKET_TYPE_PUBCOMP: - ackType = MQTTPubcomp; - break; - default: - /* This function is only called after checking the type is one of - * the above four values. */ - assert( 0 ); + * the above four values, so packet type must be PUBCOMP here. */ + assert( packetType == MQTT_PACKET_TYPE_PUBCOMP ); + ackType = MQTTPubcomp; break; } @@ -463,39 +469,26 @@ static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, - uint16_t packetId, - MQTTPublishState_t * pPublishState ) +static uint8_t getAckTypeToSend( MQTTPublishState_t state ) { - MQTTStatus_t status = MQTTSuccess; - MQTTPublishState_t newState = MQTTStateNull; - int32_t bytesSent = 0; uint8_t packetTypeByte = 0U; - MQTTPubAckType_t packetType; - assert( pContext != NULL ); - assert( pPublishState != NULL ); - - switch( *pPublishState ) + switch( state ) { case MQTTPubAckSend: packetTypeByte = MQTT_PACKET_TYPE_PUBACK; - packetType = MQTTPuback; break; case MQTTPubRecSend: packetTypeByte = MQTT_PACKET_TYPE_PUBREC; - packetType = MQTTPubrec; break; case MQTTPubRelSend: packetTypeByte = MQTT_PACKET_TYPE_PUBREL; - packetType = MQTTPubrel; break; case MQTTPubCompSend: packetTypeByte = MQTT_PACKET_TYPE_PUBCOMP; - packetType = MQTTPubcomp; break; default: @@ -503,8 +496,29 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, break; } + return packetTypeByte; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, + uint16_t packetId, + MQTTPublishState_t publishState ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTPublishState_t newState = MQTTStateNull; + int32_t bytesSent = 0; + uint8_t packetTypeByte = 0U; + MQTTPubAckType_t packetType; + + assert( pContext != NULL ); + + packetTypeByte = getAckTypeToSend( publishState ); + if( packetTypeByte != 0U ) { + packetType = getAckFromPacketType( packetTypeByte ); + status = MQTT_SerializeAck( &( pContext->networkBuffer ), packetTypeByte, packetId ); @@ -519,16 +533,15 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, if( bytesSent == ( int32_t ) MQTT_PUBLISH_ACK_PACKET_SIZE ) { pContext->controlPacketSent = true; - newState = MQTT_UpdateStateAck( pContext, - packetId, - packetType, - MQTT_SEND ); + status = MQTT_UpdateStateAck( pContext, + packetId, + packetType, + MQTT_SEND, + &newState ); - if( newState == MQTTStateNull ) + if( status != MQTTSuccess ) { - LogError( ( "Failed to update state of publish %u.", - packetId ) ); - status = MQTTIllegalState; + LogError( ( "Failed to update state of publish %u.", packetId ) ); } } else @@ -543,11 +556,6 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, } } - if( ( status == MQTTSuccess ) && ( newState != MQTTStateNull ) ) - { - *pPublishState = newState; - } - return status; } @@ -602,17 +610,21 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - publishRecordState = MQTT_UpdateStatePublish( pContext, - packetIdentifier, - MQTT_RECEIVE, - publishInfo.qos ); + status = MQTT_UpdateStatePublish( pContext, + packetIdentifier, + MQTT_RECEIVE, + publishInfo.qos, + &publishRecordState ); LogInfo( ( "State record updated. New state=%d.", publishRecordState ) ); + } + if( status == MQTTSuccess ) + { /* Send PUBACK or PUBREC if necessary. */ status = sendPublishAcks( pContext, packetIdentifier, - &publishRecordState ); + publishRecordState ); } if( status == MQTTSuccess ) @@ -638,10 +650,13 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, /* Need a dummy variable for MQTT_DeserializeAck(). */ bool sessionPresent = false; MQTTPubAckType_t ackType; + MQTTEventCallback_t appCallback; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); + appCallback = pContext->callbacks.appCallback; + switch( pIncomingPacket->type ) { case MQTT_PACKET_TYPE_PUBACK: @@ -654,24 +669,25 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - publishRecordState = MQTT_UpdateStateAck( pContext, - packetIdentifier, - ackType, - MQTT_RECEIVE ); + status = MQTT_UpdateStateAck( pContext, + packetIdentifier, + ackType, + MQTT_RECEIVE, + &publishRecordState ); LogInfo( ( "State record updated. New state=%d.", publishRecordState ) ); + } + if( status == MQTTSuccess ) + { /* Send PUBREL or PUBCOMP if necessary. */ status = sendPublishAcks( pContext, packetIdentifier, - &publishRecordState ); + publishRecordState ); if( status == MQTTSuccess ) { - pContext->callbacks.appCallback( pContext, - pIncomingPacket, - packetIdentifier, - NULL ); + appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); } } @@ -683,10 +699,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - pContext->callbacks.appCallback( pContext, - pIncomingPacket, - packetIdentifier, - NULL ); + appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); } break; @@ -698,10 +711,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - pContext->callbacks.appCallback( pContext, - pIncomingPacket, - packetIdentifier, - NULL ); + appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); } break; @@ -938,7 +948,9 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, size_t remainingLength = 0UL, packetSize = 0UL; int32_t bytesSent; MQTTStatus_t status = MQTTSuccess; - MQTTPacketInfo_t incomingPacket = { .type = ( ( uint8_t ) 0 ) }; + MQTTPacketInfo_t incomingPacket = { 0 }; + + incomingPacket.type = ( uint8_t ) 0; if( ( pContext == NULL ) || ( pConnectInfo == NULL ) || ( pSessionPresent == NULL ) ) { @@ -1153,25 +1165,19 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, * Only to be done for QoS1 or QoS2. */ if( pPublishInfo->qos > MQTTQoS0 ) { - /* TODO MQTT_UpdateStatePublish will be updated to return - * MQTTStatus_t instead of MQTTPublishState_t. Update the - * code when that change is made. */ - publishStatus = MQTT_UpdateStatePublish( pContext, - packetId, - MQTT_SEND, - pPublishInfo->qos ); - - if( publishStatus == MQTTStateNull ) + status = MQTT_UpdateStatePublish( pContext, + packetId, + MQTT_SEND, + pPublishInfo->qos, + &publishStatus ); + + if( status != MQTTSuccess ) { LogError( ( "Update state for publish failed with status =%u." " However PUBLISH packet is sent to the broker." " Any further handling of ACKs for the packet Id" " will fail.", - publishStatus ) ); - - /* TODO. Need to remove this update once MQTT_UpdateStatePublish is - * refactored with return type of MQTTStatus_t. */ - status = MQTTBadParameter; + status ) ); } } } @@ -1376,6 +1382,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, getTimeStampMs = pContext->callbacks.getTime; entryTimeMs = getTimeStampMs(); status = MQTTSuccess; + pContext->controlPacketSent = false; } else { @@ -1475,3 +1482,63 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) } /*-----------------------------------------------------------*/ + +const char * MQTT_Status_strerror( MQTTStatus_t status ) +{ + const char * str = NULL; + + switch( status ) + { + case MQTTSuccess: + str = "MQTTSuccess"; + break; + + case MQTTBadParameter: + str = "MQTTBadParameter"; + break; + + case MQTTNoMemory: + str = "MQTTNoMemory"; + break; + + case MQTTSendFailed: + str = "MQTTSendFailed"; + break; + + case MQTTRecvFailed: + str = "MQTTRecvFailed"; + break; + + case MQTTBadResponse: + str = "MQTTBadResponse"; + break; + + case MQTTServerRefused: + str = "MQTTServerRefused"; + break; + + case MQTTNoDataAvailable: + str = "MQTTNoDataAvailable"; + break; + + case MQTTIllegalState: + str = "MQTTIllegalState"; + break; + + case MQTTStateCollision: + str = "MQTTStateCollision"; + break; + + case MQTTKeepAliveTimeout: + str = "MQTTKeepAliveTimeout"; + break; + + default: + str = "Invalid MQTT Status code"; + break; + } + + return str; +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index d4ac635943..b7752b2352 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -738,10 +738,11 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, bool * const pSessionPresent ) { MQTTStatus_t status = MQTTSuccess; + const uint8_t * pRemainingData = NULL; assert( pConnack != NULL ); assert( pSessionPresent != NULL ); - const uint8_t * pRemainingData = pConnack->pRemainingData; + pRemainingData = pConnack->pRemainingData; /* According to MQTT 3.1.1, the second byte of CONNACK must specify a * "Remaining length" of 2. */ @@ -938,12 +939,14 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, uint16_t * pPacketIdentifier ) { MQTTStatus_t status = MQTTSuccess; + size_t remainingLength; + const uint8_t * pVariableHeader = NULL; assert( pSuback != NULL ); assert( pPacketIdentifier != NULL ); - size_t remainingLength = pSuback->remainingLength; - const uint8_t * pVariableHeader = pSuback->pRemainingData; + remainingLength = pSuback->remainingLength; + pVariableHeader = pSuback->pRemainingData; /* A SUBACK must have a remaining length of at least 3 to accommodate the * packet identifier and at least 1 return code. */ @@ -1024,11 +1027,12 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming MQTTPublishInfo_t * const pPublishInfo ) { MQTTStatus_t status = MQTTSuccess; + const uint8_t * pVariableHeader, * pPacketIdentifierHigh; assert( pIncomingPacket != NULL ); assert( pPacketId != NULL ); assert( pPublishInfo != NULL ); - const uint8_t * pVariableHeader = pIncomingPacket->pRemainingData, * pPacketIdentifierHigh; + pVariableHeader = pIncomingPacket->pRemainingData; /* The flags are the lower 4 bits of the first byte in PUBLISH. */ status = processPublishFlags( ( pIncomingPacket->type & 0x0FU ), pPublishInfo ); diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index bd4e14c926..bb4db2399f 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -298,6 +298,10 @@ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, else if( records[ index ].packetId == packetId ) { /* Collision. */ + LogError( ( "Collision when adding PacketID=%u at index=%u", + packetId, + index ) ); + status = MQTTStateCollision; availableIndex = recordCount; break; @@ -390,10 +394,11 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, return calculatedState; } -MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, - uint16_t packetId, - MQTTStateOperation_t opType, - MQTTQoS_t qos ) +MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTStateOperation_t opType, + MQTTQoS_t qos, + MQTTPublishState_t * pNewState ) { MQTTPublishState_t newState = MQTTStateNull; MQTTPublishState_t currentState = MQTTStateNull; @@ -402,12 +407,20 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, MQTTQoS_t foundQoS = MQTTQoS0; bool isTransitionValid = false; - if( qos == MQTTQoS0 ) + if( ( pMqttContext == NULL ) || ( pNewState == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p", + pMqttContext, + pNewState ) ); + + mqttStatus = MQTTBadParameter; + } + else if( qos == MQTTQoS0 ) { /* QoS 0 publish. Do nothing. */ - newState = MQTTPublishDone; + *pNewState = MQTTPublishDone; } - else if( ( packetId == MQTT_PACKET_ID_INVALID ) || ( pMqttContext == NULL ) ) + else if( packetId == MQTT_PACKET_ID_INVALID ) { /* Publishes > QoS 0 need a valid packet ID. */ mqttStatus = MQTTBadParameter; @@ -456,16 +469,16 @@ MQTTPublishState_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, } else { - mqttStatus = MQTTBadParameter; + mqttStatus = MQTTIllegalState; + LogError( ( "Invalid transition from state %s to state %s.", + MQTT_State_strerror( currentState ), + MQTT_State_strerror( newState ) ) ); } - } - if( mqttStatus != MQTTSuccess ) - { - newState = MQTTStateNull; + *pNewState = newState; } - return newState; + return mqttStatus; } MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, @@ -515,10 +528,11 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, return calculatedState; } -MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, - uint16_t packetId, - MQTTPubAckType_t packetType, - MQTTStateOperation_t opType ) +MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, + uint16_t packetId, + MQTTPubAckType_t packetType, + MQTTStateOperation_t opType, + MQTTPublishState_t * pNewState ) { MQTTPublishState_t newState = MQTTStateNull; MQTTPublishState_t currentState = MQTTStateNull; @@ -528,21 +542,31 @@ MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, MQTTQoS_t qos = MQTTQoS0; size_t recordIndex = MQTT_STATE_ARRAY_MAX_COUNT; MQTTPubAckInfo_t * records = NULL; + MQTTStatus_t status = MQTTBadParameter; - if( isOutgoingPublish ) + if( ( pMqttContext == NULL ) || ( pNewState == NULL ) ) { - records = pMqttContext->outgoingPublishRecords; + LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p.", + pMqttContext, + pNewState ) ); } else { - records = pMqttContext->incomingPublishRecords; - } + if( isOutgoingPublish ) + { + records = pMqttContext->outgoingPublishRecords; + } + else + { + records = pMqttContext->incomingPublishRecords; + } - recordIndex = findInRecord( records, - MQTT_STATE_ARRAY_MAX_COUNT, - packetId, - &qos, - ¤tState ); + recordIndex = findInRecord( records, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + &qos, + ¤tState ); + } if( recordIndex < MQTT_STATE_ARRAY_MAX_COUNT ) { @@ -556,14 +580,24 @@ MQTTPublishState_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, recordIndex, newState, shouldDeleteRecord ); + + status = MQTTSuccess; + *pNewState = newState; } else { - newState = MQTTStateNull; + status = MQTTIllegalState; + LogError( ( "Invalid transition from state %s to state %s.", + MQTT_State_strerror( currentState ), + MQTT_State_strerror( newState ) ) ); } } + else + { + LogError( ( "No matching record found for publish %u.", packetId ) ); + } - return newState; + return status; } uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, @@ -623,3 +657,62 @@ uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, return packetId; } + +const char * MQTT_State_strerror( MQTTPublishState_t state ) +{ + const char * str = NULL; + + switch( state ) + { + case MQTTStateNull: + str = "MQTTStateNull"; + break; + + case MQTTPublishSend: + str = "MQTTPublishSend"; + break; + + case MQTTPubAckSend: + str = "MQTTPubAckSend"; + break; + + case MQTTPubRecSend: + str = "MQTTPubRecSend"; + break; + + case MQTTPubRelSend: + str = "MQTTPubRelSend"; + break; + + case MQTTPubCompSend: + str = "MQTTPubCompSend"; + break; + + case MQTTPubAckPending: + str = "MQTTPubAckPending"; + break; + + case MQTTPubRecPending: + str = "MQTTPubRecPending"; + break; + + case MQTTPubRelPending: + str = "MQTTPubRelPending"; + break; + + case MQTTPubCompPending: + str = "MQTTPubCompPending"; + break; + + case MQTTPublishDone: + str = "MQTTPublishDone"; + break; + + default: + /* Invalid state received. */ + str = "Invalid MQTT State"; + break; + } + + return str; +} diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c index d397f1272b..4c8af00224 100644 --- a/libraries/standard/mqtt/utest/mqtt_state_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -136,58 +136,63 @@ void test_MQTT_UpdateStatePublish( void ) MQTTStateOperation_t operation = MQTT_SEND; MQTTQoS_t qos = MQTTQoS0; MQTTPublishState_t state; + MQTTStatus_t status; /* QoS 0. */ - state = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos ); + status = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos, &state ); TEST_ASSERT_EQUAL( MQTTPublishDone, state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); /* Invalid parameters. */ /* Invalid ID. */ qos = MQTTQoS1; - state = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* NULL context. */ - state = MQTT_UpdateStatePublish( NULL, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( NULL, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + /* NULL new state. */ + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* No record found. */ - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* QoS mismatch. */ addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* Invalid transition. */ addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); /* Invalid QoS. */ operation = MQTT_SEND; addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, 3, MQTTPublishSend ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3 ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); operation = MQTT_RECEIVE; - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3 ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); /* Invalid current state. */ operation = MQTT_SEND; addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, qos, MQTTStateNull ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); /* Collision. */ operation = MQTT_RECEIVE; addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTStateCollision, status ); /* No memory. */ operation = MQTT_RECEIVE; fillRecord( mqttContext.incomingPublishRecords, 2, MQTTQoS1, MQTTPublishSend ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTNoMemory, status ); resetPublishRecords( &mqttContext ); @@ -196,12 +201,14 @@ void test_MQTT_UpdateStatePublish( void ) /* Send. */ operation = MQTT_SEND; addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPublishSend ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubAckPending, state ); TEST_ASSERT_EQUAL( MQTTPubAckPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); /* Receive. */ operation = MQTT_RECEIVE; - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubAckSend, state ); TEST_ASSERT_EQUAL( MQTTPubAckSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); @@ -212,12 +219,14 @@ void test_MQTT_UpdateStatePublish( void ) /* Send. */ operation = MQTT_SEND; addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend ); - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRecPending, state ); TEST_ASSERT_EQUAL( MQTTPubRecPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); /* Receive. */ operation = MQTT_RECEIVE; - state = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRecSend, state ); TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); } @@ -291,36 +300,42 @@ void test_MQTT_UpdateStateAck( void ) MQTTPubAckType_t ack = MQTTPuback; MQTTStateOperation_t operation = MQTT_RECEIVE; MQTTPublishState_t state = MQTTStateNull; + MQTTStatus_t status; const uint16_t PACKET_ID = 1; + /* NULL parameters. */ + status = MQTT_UpdateStateAck( NULL, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, NULL ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* No matching record found. */ - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* Invalid packet ID. */ - state = MQTT_UpdateStateAck( &mqttContext, 0, ack, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, 0, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* Invalid transition. */ addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); ack = MQTTPubcomp; - state = MQTT_UpdateStateAck( &mqttContext, 1, ack, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, 1, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); /* Invalid ack type. */ - state = MQTT_UpdateStateAck( &mqttContext, 1, MQTTPubcomp + 1, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, 1, MQTTPubcomp + 1, operation, &state ); + TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* Invalid current state. */ addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishDone ); - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend ); - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTStateNull ); - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); - TEST_ASSERT_EQUAL( MQTTStateNull, state ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); resetPublishRecords( &mqttContext ); @@ -328,7 +343,8 @@ void test_MQTT_UpdateStateAck( void ) addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending ); operation = MQTT_RECEIVE; ack = MQTTPuback; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPublishDone, state ); /* Test for deletion. */ TEST_ASSERT_EQUAL( MQTTStateNull, mqttContext.outgoingPublishRecords[ 0 ].publishState ); @@ -337,7 +353,8 @@ void test_MQTT_UpdateStateAck( void ) /* Incoming publish. */ operation = MQTT_SEND; addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend ); - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPublishDone, state ); resetPublishRecords( &mqttContext ); @@ -347,12 +364,14 @@ void test_MQTT_UpdateStateAck( void ) addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelSend ); operation = MQTT_SEND; ack = MQTTPubrel; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubCompPending, state ); /* Incoming . */ addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending ); operation = MQTT_RECEIVE; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubCompSend, state ); /* Test for update. */ TEST_ASSERT_EQUAL( MQTTPubCompSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); @@ -362,12 +381,14 @@ void test_MQTT_UpdateStateAck( void ) addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); operation = MQTT_RECEIVE; ack = MQTTPubrec; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); /* Incoming. */ addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecSend ); operation = MQTT_SEND; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRelPending, state ); /* QoS 2, PUBCOMP. */ @@ -375,12 +396,14 @@ void test_MQTT_UpdateStateAck( void ) addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompPending ); operation = MQTT_RECEIVE; ack = MQTTPubcomp; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPublishDone, state ); /* Incoming. */ addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompSend ); operation = MQTT_SEND; - state = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPublishDone, state ); } @@ -433,3 +456,57 @@ void test_MQTT_StateSelect( void ) TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, outgoingCursor ); } + +void test_MQTT_State_strerror( void ) +{ + MQTTPublishState_t state; + const char * str = NULL; + + state = MQTTStateNull; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTStateNull", str ); + + state = MQTTPublishSend; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPublishSend", str ); + + state = MQTTPubAckSend; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubAckSend", str ); + + state = MQTTPubRecSend; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubRecSend", str ); + + state = MQTTPubRelSend; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubRelSend", str ); + + state = MQTTPubCompSend; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubCompSend", str ); + + state = MQTTPubAckPending; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubAckPending", str ); + + state = MQTTPubRecPending; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubRecPending", str ); + + state = MQTTPubRelPending; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubRelPending", str ); + + state = MQTTPubCompPending; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPubCompPending", str ); + + state = MQTTPublishDone; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "MQTTPublishDone", str ); + + state = MQTTPublishDone + 1; + str = MQTT_State_strerror( state ); + TEST_ASSERT_EQUAL_STRING( "Invalid MQTT State", str ); +} diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 6adb989278..7001ee46cc 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -235,6 +235,28 @@ static int32_t transportSendFailure( MQTTNetworkContext_t pContext, return -1; } +/** + * @brief Mocked transport send that succeeds then fails. + */ +static int32_t transportSendSucceedThenFail( MQTTNetworkContext_t context, + const void * pMessage, + size_t bytesToSend ) +{ + int32_t retVal = bytesToSend; + static int counter = 0; + + ( void ) context; + ( void ) pMessage; + + if( counter++ ) + { + retVal = -1; + counter = 0; + } + + return retVal; +} + /** * @brief Mocked successful transport read. * @@ -267,7 +289,7 @@ static int32_t transportRecvFailure( MQTTNetworkContext_t pContext, } /** - * @brief Mocked failed transport read. + * @brief Mocked transport reading one byte at a time. */ static int32_t transportRecvOneByte( MQTTNetworkContext_t pContext, void * pBuffer, @@ -394,11 +416,15 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, { if( incomingPublish ) { - MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( stateAfterDeserialize ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( ( stateAfterDeserialize == MQTTStateNull )? + MQTTIllegalState : MQTTSuccess ); + MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &stateAfterDeserialize ); } else { - MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( stateAfterDeserialize ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( ( stateAfterDeserialize == MQTTStateNull )? + MQTTIllegalState : MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); } if( stateAfterDeserialize == MQTTPublishDone ) @@ -421,7 +447,9 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, /* Update the state based on the sent packet. */ if( expectMoreCalls ) { - MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( stateAfterSerialize ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( ( stateAfterSerialize == MQTTStateNull )? + MQTTIllegalState : MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterSerialize ); } /* Expect the above calls when running MQTT_ProcessLoop. */ @@ -753,6 +781,7 @@ void test_MQTT_Publish( void ) MQTTApplicationCallbacks_t callbacks; MQTTStatus_t status; size_t headerSize; + MQTTPublishState_t expectedState; const uint16_t PACKET_ID = 1; @@ -798,28 +827,38 @@ void test_MQTT_Publish( void ) status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); - /* We can ignore this now since MQTT_Publish initializes the header size to - * 0, so its initial send returns success (since 0 bytes are sent). */ - MQTT_SerializePublishHeader_IgnoreAndReturn( MQTTSuccess ); + /* We want to test the first call to sendPacket within sendPublish succeeding, + * and the second one failing. */ + mqttContext.transportInterface.send = transportSendSucceedThenFail; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); publishInfo.pPayload = "Test"; publishInfo.payloadLength = 4; status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); mqttContext.transportInterface.send = transportSendSuccess; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); /* Now for non zero QoS, which uses state engine. */ publishInfo.qos = MQTTQoS2; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess ); - MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTStateNull ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTBadParameter ); status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); publishInfo.qos = MQTTQoS1; + expectedState = MQTTPublishSend; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess ); - MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTPublishSend ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &expectedState ); status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); } @@ -1252,6 +1291,8 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket = { 0 }; + MQTTPublishState_t publishState = MQTTPubAckSend; + MQTTPublishState_t ackState = MQTTPublishDone; uint8_t i = 0; uint8_t numIterations = ( MQTT_TIMER_OVERFLOW_TIMEOUT_MS / MQTT_TIMER_CALLS_PER_ITERATION ) + 1; @@ -1274,9 +1315,11 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); /* Assume QoS = 1 so that a PUBACK will be sent after receiving PUBLISH. */ MQTT_DeserializePublish_ExpectAnyArgsAndReturn( MQTTSuccess ); - MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTPubAckSend ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &publishState ); MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); - MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTPublishDone ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &ackState ); } mqttStatus = MQTT_ProcessLoop( &context, MQTT_TIMER_OVERFLOW_TIMEOUT_MS ); @@ -1575,3 +1618,62 @@ void test_MQTT_Ping_error_path( void ) } /* ========================================================================== */ + +/** + * @brief Test MQTT_Status_strerror returns correct strings. + */ +void test_MQTT_Status_strerror( void ) +{ + MQTTStatus_t status; + const char * str = NULL; + + status = MQTTSuccess; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTSuccess", str ); + + status = MQTTBadParameter; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTBadParameter", str ); + + status = MQTTNoMemory; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTNoMemory", str ); + + status = MQTTSendFailed; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTSendFailed", str ); + + status = MQTTRecvFailed; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTRecvFailed", str ); + + status = MQTTBadResponse; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTBadResponse", str ); + + status = MQTTServerRefused; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTServerRefused", str ); + + status = MQTTNoDataAvailable; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTNoDataAvailable", str ); + + status = MQTTIllegalState; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTIllegalState", str ); + + status = MQTTStateCollision; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTStateCollision", str ); + + status = MQTTKeepAliveTimeout; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "MQTTKeepAliveTimeout", str ); + + status = MQTTKeepAliveTimeout + 1; + str = MQTT_Status_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "Invalid MQTT Status code", str ); +} + +/* ========================================================================== */ From f6b808542d3a680e63e9c252f1df1fb058eff100 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 12 Jun 2020 11:33:52 -0700 Subject: [PATCH 541/844] Fix/logging macro redefinition in demos (#989) * Use #ifndef around logging macro definitions in config files to resolve macro re-definition warning in demo builds * Complete changes to HTTP and MQTT demos for logging config * Add comments for config file includes in MQTT and HTTP library files * Minor doc update for logging configuration across all config files --- demos/http/http_demo_plaintext/demo_config.h | 16 +++++++++------ demos/http/http_demo_plaintext/http_config.h | 18 +++++++++++------ .../http_demo_plaintext/http_demo_plaintext.c | 6 +++--- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 18 ++++++++++------- demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h | 19 +++++++++++------- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 5 +++-- .../mqtt/mqtt_demo_lightweight/demo_config.h | 16 +++++++++------ .../mqtt/mqtt_demo_lightweight/mqtt_config.h | 20 +++++++++++-------- demos/mqtt/mqtt_demo_plaintext/demo_config.h | 16 +++++++++------ demos/mqtt/mqtt_demo_plaintext/mqtt_config.h | 19 +++++++++++------- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 6 +++--- libraries/standard/http/include/http_client.h | 1 + .../http/src/private/http_client_internal.h | 1 + libraries/standard/mqtt/include/mqtt.h | 1 + .../standard/mqtt/include/mqtt_lightweight.h | 1 + .../standard/mqtt/src/private/mqtt_internal.h | 1 + 16 files changed, 103 insertions(+), 61 deletions(-) diff --git a/demos/http/http_demo_plaintext/demo_config.h b/demos/http/http_demo_plaintext/demo_config.h index 031d220ab5..eeb16094c0 100644 --- a/demos/http/http_demo_plaintext/demo_config.h +++ b/demos/http/http_demo_plaintext/demo_config.h @@ -26,19 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. */ -/* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the Demo. */ -#define LIBRARY_LOG_NAME "DEMO" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" /************ End of logging configuration ****************/ diff --git a/demos/http/http_demo_plaintext/http_config.h b/demos/http/http_demo_plaintext/http_config.h index 6a9f558a09..1064f859e2 100644 --- a/demos/http/http_demo_plaintext/http_config.h +++ b/demos/http/http_demo_plaintext/http_config.h @@ -26,21 +26,27 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for HTTP. + * 3. Include the header file "logging_stack.h", if logging is enabled for HTTP. */ -/* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the HTTP library. */ -#define LIBRARY_LOG_NAME "HTTP" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "HTTP" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" + /************ End of logging configuration ****************/ #endif /* ifndef HTTP_CONFIG_H */ diff --git a/demos/http/http_demo_plaintext/http_demo_plaintext.c b/demos/http/http_demo_plaintext/http_demo_plaintext.c index cd9a7335bb..06897654b3 100644 --- a/demos/http/http_demo_plaintext/http_demo_plaintext.c +++ b/demos/http/http_demo_plaintext/http_demo_plaintext.c @@ -33,12 +33,12 @@ #include #include +/* Include Demo Config as the first non-system header. */ +#include "demo_config.h" + /* HTTP API header. */ #include "http_client.h" -/* Demo config header. */ -#include "demo_config.h" - /** * @brief Length of an IPv6 address when converted to hex digits. */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 2745410568..6ecd22a7a3 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -26,19 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. */ -/* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the Demo. */ -#define LIBRARY_LOG_NAME "DEMO" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" /************ End of logging configuration ****************/ @@ -83,6 +87,6 @@ * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER "testclient" #endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h index 9ba12040d2..cba5a52ea6 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h @@ -26,18 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. */ -/* Include header that defines log levels. */ #include "logging_levels.h" -/* Configure name and log level for the MQTT library. */ -#define LIBRARY_LOG_NAME "MQTT" -#define LIBRARY_LOG_LEVEL LOG_INFO +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 51e6e98c71..3c2b49638b 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -47,11 +47,12 @@ #include #include +/* Include Demo Config as the first non-system header. */ +#include "demo_config.h" + /* MQTT API header. */ #include "mqtt.h" -/* Demo Config header. */ -#include "demo_config.h" /** * @brief Size of the network buffer for MQTT packets. diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h index 5f58e27233..d17af869c3 100644 --- a/demos/mqtt/mqtt_demo_lightweight/demo_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -26,19 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. */ -/* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the Demo. */ -#define LIBRARY_LOG_NAME "DEMO" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" /************ End of logging configuration ****************/ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h index fb9fdf47d5..16f3c900e7 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h @@ -26,21 +26,25 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. */ -/* Include header that defines log levels. */ #include "logging_levels.h" -/* Configure name and log level for the MQTT library. */ -#define LIBRARY_LOG_NAME "MQTT" -#define LIBRARY_LOG_LEVEL LOG_INFO +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif -#include "logging_stack.h" +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif +#include "logging_stack.h" /************ End of logging configuration ****************/ /* Set network context to socket (int). */ diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index 8e660e5b05..356da71172 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -26,19 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. */ -/* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the Demo. */ -#define LIBRARY_LOG_NAME "DEMO" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" /************ End of logging configuration ****************/ diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h index de5c2dfd6a..4018efa21a 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h @@ -26,18 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Include logging header files and define logging macros in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. */ -/* Include header that defines log levels. */ #include "logging_levels.h" -/* Configure name and log level for the MQTT library. */ -#define LIBRARY_LOG_NAME "MQTT" -#define LIBRARY_LOG_LEVEL LOG_INFO +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 14577e0b51..ba6cea22f7 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -45,12 +45,12 @@ #include #include +/* Include Demo Config as the first non-system header. */ +#include "demo_config.h" + /* MQTT API header. */ #include "mqtt.h" -/* Demo Config header. */ -#include "demo_config.h" - /** * @brief MQTT server host name. * diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index f0d0ee7040..2fe223575e 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -3,6 +3,7 @@ #include #include +/* Include config file before other headers. */ #include "http_config.h" /** diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index 6e901bc30b..d2c65bcdf8 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -1,6 +1,7 @@ #ifndef HTTP_CLIENT_INTERNAL_H_ #define HTTP_CLIENT_INTERNAL_H_ +/* Include config file before other headers. */ #include "http_config.h" #include "http_parser.h" diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 4fc1494d83..1e4a5144ca 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -22,6 +22,7 @@ #ifndef MQTT_H #define MQTT_H +/* Include config file before other headers. */ #include "mqtt_config.h" #include "mqtt_lightweight.h" diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 823c33558f..c15ccb01ff 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -34,6 +34,7 @@ #include #include +/* Include config file before other headers. */ #include "mqtt_config.h" /* MQTT packet types. */ diff --git a/libraries/standard/mqtt/src/private/mqtt_internal.h b/libraries/standard/mqtt/src/private/mqtt_internal.h index 55f6d0e8dc..c624895737 100644 --- a/libraries/standard/mqtt/src/private/mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/mqtt_internal.h @@ -1,6 +1,7 @@ #ifndef MQTT_INTERNAL_H_ #define MQTT_INTERNAL_H_ +/* Include config file before other headers. */ #include "mqtt_config.h" #ifndef LogError From 6d5f8c6ceed940a78be753493e4ce602f5cdb6bc Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 15 Jun 2020 10:10:34 -0700 Subject: [PATCH 542/844] Fix buffer corruption for incoming publishes (#998) --- libraries/standard/mqtt/src/mqtt.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index e8c26454eb..06d1e3f6f1 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -293,6 +293,7 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ) case MQTT_PACKET_TYPE_PUBCOMP: default: + /* This function is only called after checking the type is one of * the above four values, so packet type must be PUBCOMP here. */ assert( packetType == MQTT_PACKET_TYPE_PUBCOMP ); @@ -621,19 +622,17 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { - /* Send PUBACK or PUBREC if necessary. */ - status = sendPublishAcks( pContext, - packetIdentifier, - publishRecordState ); - } - - if( status == MQTTSuccess ) - { - /* Provide publish info to application. */ + /* Invoke application callback to hand the buffer over to application + * before sending acks. */ pContext->callbacks.appCallback( pContext, pIncomingPacket, packetIdentifier, &publishInfo ); + + /* Send PUBACK or PUBREC if necessary. */ + status = sendPublishAcks( pContext, + packetIdentifier, + publishRecordState ); } return status; @@ -680,15 +679,14 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, if( status == MQTTSuccess ) { + /* Invoke application callback to hand the buffer over to application + * before sending acks. */ + appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); + /* Send PUBREL or PUBCOMP if necessary. */ status = sendPublishAcks( pContext, packetIdentifier, publishRecordState ); - - if( status == MQTTSuccess ) - { - appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); - } } break; From e254aa27ef1a2f164624a35166eeeaee9baf05b9 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Mon, 15 Jun 2020 14:58:42 -0700 Subject: [PATCH 543/844] Fix MQTT MISRA violations (#993) * Fix MQTT MISRA violations * Remove unnecessary const * Remove unused function * Fix string.h warning and add macro to disable logs * Use ternary for MISRA 10.5 * Remove TODO from logging header --- demos/logging-stack/logging_stack.h | 7 +- .../mqtt_demo_lightweight.c | 1 + .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 2 +- libraries/standard/mqtt/include/mqtt.h | 36 +++--- .../standard/mqtt/include/mqtt_lightweight.h | 78 ++++++----- libraries/standard/mqtt/src/mqtt.c | 100 +++++++-------- .../standard/mqtt/src/mqtt_lightweight.c | 121 ++++++++---------- libraries/standard/mqtt/src/mqtt_state.c | 40 +++--- 8 files changed, 188 insertions(+), 197 deletions(-) diff --git a/demos/logging-stack/logging_stack.h b/demos/logging-stack/logging_stack.h index 37d0c588d4..47e7459e0e 100644 --- a/demos/logging-stack/logging_stack.h +++ b/demos/logging-stack/logging_stack.h @@ -40,8 +40,11 @@ #define LOG_METADATA_ARGS __FILE__, __LINE__ /* Common macro for all logging interface macros. */ -/* TODO - Replace printf with an implementation function. */ -#define SdkLog( string ) printf string +#if !defined( DISABLE_LOGGING ) + #define SdkLog( string ) printf string +#else + #define SdkLog( string ) +#endif /* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ #if !defined( LIBRARY_LOG_LEVEL ) || \ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index f02a4ae994..e213c8810d 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -39,6 +39,7 @@ /* Standard includes. */ #include +#include /* POSIX socket includes. */ #include diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index ba6cea22f7..9cb0e38988 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -588,7 +588,7 @@ static int establishMqttSession( MQTTContext_t * pContext, int status = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; - char sessionPresent; + bool sessionPresent; MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 1e4a5144ca..d502b20303 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -134,10 +134,10 @@ struct MQTTContext * @return #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, - const MQTTTransportInterface_t * const pTransportInterface, - const MQTTApplicationCallbacks_t * const pCallbacks, - const MQTTFixedBuffer_t * const pNetworkBuffer ); +MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, + const MQTTTransportInterface_t * pTransportInterface, + const MQTTApplicationCallbacks_t * pCallbacks, + const MQTTFixedBuffer_t * pNetworkBuffer ); /** * @brief Establish a MQTT session. @@ -159,11 +159,11 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, * the #timeoutMs for CONNACK; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, - const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, +MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, + const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, uint32_t timeoutMs, - bool * const pSessionPresent ); + bool * pSessionPresent ); /** * @brief Sends MQTT SUBSCRIBE for the given list of topic filters to @@ -180,8 +180,8 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, - const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, + const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ); @@ -197,8 +197,8 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, - const MQTTPublishInfo_t * const pPublishInfo, +MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ); /** @@ -211,7 +211,7 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ); +MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); /** * @brief Sends MQTT UNSUBSCRIBE for the given list of topic filters to @@ -228,8 +228,8 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ); * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, - const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, + const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ); @@ -244,7 +244,7 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, * #MQTTSendFailed if transport send failed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); +MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); /** * @brief Loop to receive packets from the transport interface. @@ -263,7 +263,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ); * invalid transition for the internal state machine; * #MQTTSuccess on success. */ -MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, +MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t timeoutMs ); /** @@ -273,7 +273,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, * * @return A non-zero number. */ -uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ); +uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); /** * @brief Error code to string conversion for MQTT statuses. diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index c15ccb01ff..3c2b6ff6b4 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -22,18 +22,18 @@ #ifndef MQTT_LIGHTWEIGHT_H #define MQTT_LIGHTWEIGHT_H +#include +#include + /* bools are only defined in C99+ */ -#if defined( __cplusplus ) || __STDC_VERSION__ >= 199901L +#if defined( __cplusplus ) || ( defined( __STDC_VERSION__ ) && ( __STDC_VERSION__ >= 199901L ) ) #include #elif !defined( bool ) - #define bool signed char - #define false 0 - #define true 1 + #define bool int8_t + #define false ( int8_t ) 0 + #define true ( int8_t ) 1 #endif -#include -#include - /* Include config file before other headers. */ #include "mqtt_config.h" @@ -271,10 +271,10 @@ struct MQTTPacketInfo * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec; #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ); +MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); /** * @brief Serialize an MQTT CONNECT packet in the given buffer. @@ -288,10 +288,10 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, +MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * pBuffer ); /** * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet. @@ -304,7 +304,7 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec; #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ); @@ -322,11 +322,11 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSub * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * pBuffer ); /** * @brief Get packet size and Remaining Length of an MQTT UNSUBSCRIBE packet. @@ -339,7 +339,7 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec; #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ); @@ -357,11 +357,11 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * pBuffer ); /** * @brief Get the packet size and remaining length of an MQTT PUBLISH packet. @@ -373,9 +373,9 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec or if invalid parameters are passed; #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ); +MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); /** * @brief Serialize an MQTT PUBLISH packet in the given buffer. @@ -394,10 +394,10 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, +MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * pBuffer ); /** * @brief Serialize an MQTT PUBLISH packet header in the given buffer. @@ -418,11 +418,11 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, +MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer, - size_t * const pHeaderSize ); + const MQTTFixedBuffer_t * pBuffer, + size_t * pHeaderSize ); /** * @brief Serialize an MQTT PUBACK, PUBREC, PUBREL, or PUBCOMP into the given @@ -435,7 +435,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli * * @return #MQTTBadParameter, #MQTTNoMemory, or #MQTTSuccess. */ -MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pBuffer, uint8_t packetType, uint16_t packetId ); @@ -457,7 +457,7 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ); +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pBuffer ); /** * @brief Get the size of an MQTT PINGREQ packet. @@ -477,11 +477,7 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ); - -MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, - MQTTNetworkContext_t networkContext, - MQTTPacketInfo_t * const pIncomingPacket ); +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pBuffer ); /** * @brief Deserialize an MQTT PUBLISH packet. @@ -492,9 +488,9 @@ MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, * * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. */ -MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, - uint16_t * const pPacketId, - MQTTPublishInfo_t * const pPublishInfo ); +MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTPublishInfo_t * pPublishInfo ); /** * @brief Deserialize an MQTT CONNACK, SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL, @@ -507,9 +503,9 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa * * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. */ -MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, - uint16_t * const pPacketId, - bool * const pSessionPresent ); +MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + bool * pSessionPresent ); /** * @brief Extract MQTT packet type and length from incoming packet. diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 06d1e3f6f1..4f2074aeb0 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -75,7 +75,7 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ); * * @return Number of bytes received, or negative number on network error. */ -static int32_t recvExact( const MQTTContext_t * const pContext, +static int32_t recvExact( const MQTTContext_t * pContext, size_t bytesToRecv, uint32_t timeoutMs ); @@ -88,7 +88,7 @@ static int32_t recvExact( const MQTTContext_t * const pContext, * * @return #MQTTRecvFailed or #MQTTNoDataAvailable. */ -static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, +static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, size_t remainingLength, uint32_t timeoutMs ); @@ -101,7 +101,7 @@ static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, * * @return #MQTTSuccess or #MQTTRecvFailed. */ -static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, +static MQTTStatus_t receivePacket( const MQTTContext_t * pContext, MQTTPacketInfo_t incomingPacket, uint32_t remainingTimeMs ); @@ -124,7 +124,7 @@ static uint8_t getAckTypeToSend( MQTTPublishState_t state ); * * @return #MQTTSuccess, #MQTTIllegalState or #MQTTSendFailed. */ -static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, +static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext, uint16_t packetId, MQTTPublishState_t publishState ); @@ -136,7 +136,7 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, * @return #MQTTKeepAliveTimeout if a PINGRESP is not received in time, * #MQTTSendFailed if the PINGREQ cannot be sent, or #MQTTSuccess. */ -static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ); +static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ); /** * @brief Handle received MQTT PUBLISH packet. @@ -146,7 +146,7 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ); * * @return MQTTSuccess, MQTTIllegalState or deserialization error. */ -static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, +static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ); /** @@ -157,7 +157,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, * * @return MQTTSuccess, MQTTIllegalState, or deserialization error. */ -static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, +static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ); /** @@ -171,8 +171,8 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, * @return #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * const pContext, - const MQTTSubscribeInfo_t * const pSubscriptionList, +static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pContext, + const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ); @@ -186,8 +186,8 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co * @return #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, - const MQTTPublishInfo_t * const pPublishInfo, +static MQTTStatus_t sendPublish( MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, size_t headerSize ); /** @@ -204,10 +204,10 @@ static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, * ##MQTTRecvFailed if transport recv failed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, +static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, uint32_t timeoutMs, - MQTTPacketInfo_t * const pIncomingPacket, - bool * const pSessionPresent ); + MQTTPacketInfo_t * pIncomingPacket, + bool * pSessionPresent ); /*-----------------------------------------------------------*/ @@ -306,7 +306,7 @@ static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType ) /*-----------------------------------------------------------*/ -static int32_t recvExact( const MQTTContext_t * const pContext, +static int32_t recvExact( const MQTTContext_t * pContext, size_t bytesToRecv, uint32_t timeoutMs ) { @@ -358,7 +358,7 @@ static int32_t recvExact( const MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, +static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, size_t remainingLength, uint32_t timeoutMs ) { @@ -423,7 +423,7 @@ static MQTTStatus_t discardPacket( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t receivePacket( MQTTContext_t * const pContext, +static MQTTStatus_t receivePacket( const MQTTContext_t * pContext, MQTTPacketInfo_t incomingPacket, uint32_t remainingTimeMs ) { @@ -502,7 +502,7 @@ static uint8_t getAckTypeToSend( MQTTPublishState_t state ) /*-----------------------------------------------------------*/ -static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, +static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext, uint16_t packetId, MQTTPublishState_t publishState ) { @@ -562,7 +562,7 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ) +static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ) { MQTTStatus_t status = MQTTSuccess; uint32_t now = 0U, keepAliveMs = 0U; @@ -575,7 +575,7 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ) if( ( keepAliveMs != 0U ) && ( calculateElapsedTime( now, pContext->lastPacketTime ) > keepAliveMs ) ) { - if( pContext->waitingForPingResp ) + if( pContext->waitingForPingResp == true ) { /* Has time expired? */ if( calculateElapsedTime( now, pContext->pingReqSendTimeMs ) > @@ -595,12 +595,12 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * const pContext ) /*-----------------------------------------------------------*/ -static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, +static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTBadParameter; MQTTPublishState_t publishRecordState = MQTTStateNull; - uint16_t packetIdentifier; + uint16_t packetIdentifier = 0U; MQTTPublishInfo_t publishInfo; assert( pContext != NULL ); @@ -640,7 +640,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, +static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTBadResponse; @@ -727,8 +727,8 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * const pContext, - const MQTTSubscribeInfo_t * const pSubscriptionList, +static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pContext, + const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ) { @@ -763,8 +763,8 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * co /*-----------------------------------------------------------*/ -static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, - const MQTTPublishInfo_t * const pPublishInfo, +static MQTTStatus_t sendPublish( MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, size_t headerSize ) { MQTTStatus_t status = MQTTSuccess; @@ -811,10 +811,10 @@ static MQTTStatus_t sendPublish( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, +static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, uint32_t timeoutMs, - MQTTPacketInfo_t * const pIncomingPacket, - bool * const pSessionPresent ) + MQTTPacketInfo_t * pIncomingPacket, + bool * pSessionPresent ) { MQTTStatus_t status = MQTTSuccess; MQTTGetCurrentTimeFunc_t getTimeStamp = NULL; @@ -898,10 +898,10 @@ static MQTTStatus_t receiveConnack( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, - const MQTTTransportInterface_t * const pTransportInterface, - const MQTTApplicationCallbacks_t * const pCallbacks, - const MQTTFixedBuffer_t * const pNetworkBuffer ) +MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, + const MQTTTransportInterface_t * pTransportInterface, + const MQTTApplicationCallbacks_t * pCallbacks, + const MQTTFixedBuffer_t * pNetworkBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -937,11 +937,11 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, - const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, +MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, + const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, uint32_t timeoutMs, - bool * const pSessionPresent ) + bool * pSessionPresent ) { size_t remainingLength = 0UL, packetSize = 0UL; int32_t bytesSent; @@ -1023,8 +1023,8 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, - const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, + const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ) { @@ -1083,12 +1083,11 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, - const MQTTPublishInfo_t * const pPublishInfo, +MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ) { size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; - int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; MQTTPublishState_t publishStatus = MQTTStateNull; @@ -1191,7 +1190,7 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) +MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ) { int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; @@ -1251,8 +1250,8 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * const pContext ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, - const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, + const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ) { @@ -1311,7 +1310,7 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) +MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ) { size_t packetSize; int32_t bytesSent; @@ -1367,7 +1366,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * const pContext ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, +MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t timeoutMs ) { MQTTStatus_t status = MQTTBadParameter; @@ -1462,13 +1461,14 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * const pContext, /*-----------------------------------------------------------*/ -uint16_t MQTT_GetPacketId( MQTTContext_t * const pContext ) +uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) { uint16_t packetId = 0U; if( pContext != NULL ) { - packetId = pContext->nextPacketId++; + packetId = pContext->nextPacketId; + pContext->nextPacketId++; if( pContext->nextPacketId == 0U ) { diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index b7752b2352..d483e3554e 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -169,7 +169,7 @@ typedef enum MQTTSubscriptionType static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t packetIdentifier, - const MQTTFixedBuffer_t * const pFixedBuffer, + const MQTTFixedBuffer_t * pFixedBuffer, bool serializePayload ); /** @@ -222,11 +222,11 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, +static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * pBuffer ); /** * @brief Serialize an MQTT CONNECT packet in the given buffer. @@ -237,10 +237,10 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo * @param[out] pBuffer Buffer for packet serialization. * */ -static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, +static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ); + const MQTTFixedBuffer_t * pBuffer ); /*-----------------------------------------------------------*/ @@ -432,7 +432,7 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, size_t remainingLength, uint16_t packetIdentifier, - const MQTTFixedBuffer_t * const pFixedBuffer, + const MQTTFixedBuffer_t * pFixedBuffer, bool serializePayload ) { uint8_t * pIndex = NULL; @@ -466,13 +466,13 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, /* Empty else MISRA 15.7 */ } - if( pPublishInfo->retain ) + if( pPublishInfo->retain == true ) { LogDebug( ( "Adding retain bit in PUBLISH flags." ) ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); } - if( pPublishInfo->dup ) + if( pPublishInfo->dup == true ) { LogDebug( ( "Adding dup bit in PUBLISH flags." ) ); UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ); @@ -504,7 +504,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, * This will help reduce an unnecessary copy of the payload into the buffer. */ if( ( pPublishInfo->payloadLength > 0U ) && - ( serializePayload ) ) + ( serializePayload == true ) ) { LogDebug( ( "Copying PUBLISH payload of length =%lu to buffer", pPublishInfo->payloadLength ) ); @@ -653,7 +653,7 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, /*-----------------------------------------------------------*/ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, - MQTTPublishInfo_t * const pPublishInfo ) + MQTTPublishInfo_t * pPublishInfo ) { MQTTStatus_t status = MQTTSuccess; @@ -690,7 +690,7 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, LogDebug( ( "QoS is %d.", pPublishInfo->qos ) ); /* Parse the Retain bit. */ - pPublishInfo->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + pPublishInfo->retain = ( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ) ) ? true : false; LogDebug( ( "Retain bit is %d.", pPublishInfo->retain ) ); @@ -734,8 +734,8 @@ static void logConnackResponse( uint8_t responseCode ) /*-----------------------------------------------------------*/ -static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * const pConnack, - bool * const pSessionPresent ) +static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack, + bool * pSessionPresent ) { MQTTStatus_t status = MQTTSuccess; const uint8_t * pRemainingData = NULL; @@ -935,7 +935,7 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, /*-----------------------------------------------------------*/ -static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, +static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback, uint16_t * pPacketIdentifier ) { MQTTStatus_t status = MQTTSuccess; @@ -971,11 +971,11 @@ static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * const pSuback, /*-----------------------------------------------------------*/ -static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * const pSubscriptionList, +static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1022,9 +1022,9 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo /*-----------------------------------------------------------*/ -static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, - uint16_t * const pPacketId, - MQTTPublishInfo_t * const pPublishInfo ) +static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTPublishInfo_t * pPublishInfo ) { MQTTStatus_t status = MQTTSuccess; const uint8_t * pVariableHeader, * pPacketIdentifierHigh; @@ -1111,7 +1111,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * const pIncoming /*-----------------------------------------------------------*/ -static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, +static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck, uint16_t * pPacketIdentifier ) { MQTTStatus_t status = MQTTSuccess; @@ -1146,7 +1146,7 @@ static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * const pAck, /*-----------------------------------------------------------*/ -static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * const pPingresp ) +static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp ) { MQTTStatus_t status = MQTTSuccess; @@ -1166,10 +1166,10 @@ static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * const pPingres /*-----------------------------------------------------------*/ -static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, +static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * pBuffer ) { uint8_t connectFlags = 0U; uint8_t * pIndex = NULL; @@ -1284,10 +1284,10 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * const pConnectInfo /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ) +MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) { MQTTStatus_t status = MQTTSuccess; size_t remainingLength; @@ -1363,10 +1363,10 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * const pConnect /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo, - const MQTTPublishInfo_t * const pWillInfo, +MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, + const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1409,7 +1409,7 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * const pConnectInfo /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ) @@ -1454,11 +1454,11 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * const pSub /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * pBuffer ) { size_t i = 0; uint8_t * pIndex = NULL; @@ -1508,7 +1508,7 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * const pSubscri /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ) @@ -1553,11 +1553,11 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * const pS /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubscriptionList, +MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * pBuffer ) { MQTTStatus_t status = MQTTSuccess; size_t i = 0; @@ -1604,9 +1604,9 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * const pSubsc /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublishInfo, - size_t * const pRemainingLength, - size_t * const pPacketSize ) +MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) { MQTTStatus_t status = MQTTSuccess; @@ -1645,10 +1645,10 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * const pPublish /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo, +MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer ) + const MQTTFixedBuffer_t * pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1703,11 +1703,11 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * const pPublishInfo /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPublishInfo, +MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * const pBuffer, - size_t * const pHeaderSize ) + const MQTTFixedBuffer_t * pBuffer, + size_t * pHeaderSize ) { MQTTStatus_t status = MQTTSuccess; @@ -1772,7 +1772,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * const pPubli /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * const pBuffer, +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pBuffer, uint8_t packetType, uint16_t packetId ) { @@ -1842,7 +1842,7 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * const pBuffer ) +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1896,7 +1896,7 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1930,18 +1930,9 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * const pBuffer ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetIncomingPacket( MQTTTransportRecvFunc_t recvFunc, - MQTTNetworkContext_t networkContext, - MQTTPacketInfo_t * const pIncomingPacket ) -{ - return MQTTSuccess; -} - -/*-----------------------------------------------------------*/ - -MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPacket, - uint16_t * const pPacketId, - MQTTPublishInfo_t * const pPublishInfo ) +MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTPublishInfo_t * pPublishInfo ) { MQTTStatus_t status = MQTTSuccess; @@ -1970,9 +1961,9 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * const pIncomingPa /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * const pIncomingPacket, - uint16_t * const pPacketId, - bool * const pSessionPresent ) +MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + bool * pSessionPresent ) { MQTTStatus_t status = MQTTSuccess; @@ -2053,7 +2044,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu if( bytesReceived == 1 ) { /* Check validity. */ - if( incomingPacketValid( pIncomingPacket->type ) ) + if( incomingPacketValid( pIncomingPacket->type ) == true ) { pIncomingPacket->remainingLength = getRemainingLength( readFunc, networkContext ); diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index bb4db2399f..38f5c98ec8 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -128,7 +128,7 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, /* Transitions from null occur when storing a new entry into the record. */ if( opType == MQTT_RECEIVE ) { - isValid = ( newState == MQTTPubAckSend ) || ( newState == MQTTPubRecSend ); + isValid = ( ( newState == MQTTPubAckSend ) || ( newState == MQTTPubRecSend ) ) ? true : false; } break; @@ -140,11 +140,11 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, switch( qos ) { case MQTTQoS1: - isValid = ( newState == MQTTPubAckPending ); + isValid = ( newState == MQTTPubAckPending ) ? true : false; break; case MQTTQoS2: - isValid = ( newState == MQTTPubRecPending ); + isValid = ( newState == MQTTPubRecPending ) ? true : false; break; default: @@ -173,37 +173,37 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, /* Incoming publish, QoS 1. */ case MQTTPubAckPending: /* Outgoing publish, QoS 1. */ - isValid = ( newState == MQTTPublishDone ); + isValid = ( newState == MQTTPublishDone ) ? true : false; break; case MQTTPubRecSend: /* Incoming publish, QoS 2. */ - isValid = ( newState == MQTTPubRelPending ); + isValid = ( newState == MQTTPubRelPending ) ? true : false; break; case MQTTPubRelPending: /* Incoming publish, QoS 2. */ - isValid = ( newState == MQTTPubCompSend ); + isValid = ( newState == MQTTPubCompSend ) ? true : false; break; case MQTTPubCompSend: /* Incoming publish, QoS 2. */ - isValid = ( newState == MQTTPublishDone ); + isValid = ( newState == MQTTPublishDone ) ? true : false; break; case MQTTPubRecPending: /* Outgoing publish, Qos 2. */ - isValid = ( newState == MQTTPubRelSend ); + isValid = ( newState == MQTTPubRelSend ) ? true : false; break; case MQTTPubRelSend: /* Outgoing publish, Qos 2. */ - isValid = ( newState == MQTTPubCompPending ); + isValid = ( newState == MQTTPubCompPending ) ? true : false; break; case MQTTPubCompPending: /* Outgoing publish, Qos 2. */ - isValid = ( newState == MQTTPublishDone ); + isValid = ( newState == MQTTPublishDone ) ? true : false; break; case MQTTPublishDone: @@ -230,11 +230,11 @@ static bool isPublishOutgoing( MQTTPubAckType_t packetType, case MQTTPuback: case MQTTPubrec: case MQTTPubcomp: - isOutgoing = ( opType == MQTT_RECEIVE ); + isOutgoing = ( opType == MQTT_RECEIVE ) ? true : false; break; case MQTTPubrel: - isOutgoing = ( opType == MQTT_SEND ); + isOutgoing = ( opType == MQTT_SEND ) ? true : false; break; default: @@ -328,7 +328,7 @@ static void updateRecord( MQTTPubAckInfo_t * records, MQTTPublishState_t newState, bool shouldDelete ) { - if( shouldDelete ) + if( shouldDelete == true ) { records[ recordIndex ].packetId = MQTT_PACKET_ID_INVALID; records[ recordIndex ].qos = MQTTQoS0; @@ -450,7 +450,7 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, newState = MQTT_CalculateStatePublish( opType, qos ); isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos ); - if( isTransitionValid ) + if( isTransitionValid == true ) { /* addRecord will check for collisions. */ if( opType == MQTT_RECEIVE ) @@ -487,12 +487,12 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, { MQTTPublishState_t calculatedState = MQTTStateNull; /* There are more QoS2 cases than QoS1, so initialize to that. */ - bool qosValid = ( qos == MQTTQoS2 ); + bool qosValid = ( qos == MQTTQoS2 ) ? true : false; switch( packetType ) { case MQTTPuback: - qosValid = ( qos == MQTTQoS1 ); + qosValid = ( qos == MQTTQoS1 ) ? true : false; calculatedState = MQTTPublishDone; break; @@ -520,7 +520,7 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, } /* Sanity check, make sure ack and QoS agree. */ - if( !qosValid ) + if( qosValid == false ) { calculatedState = MQTTStateNull; } @@ -552,7 +552,7 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, } else { - if( isOutgoingPublish ) + if( isOutgoingPublish == true ) { records = pMqttContext->outgoingPublishRecords; } @@ -571,10 +571,10 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, if( recordIndex < MQTT_STATE_ARRAY_MAX_COUNT ) { newState = MQTT_CalculateStateAck( packetType, opType, qos ); - shouldDeleteRecord = ( newState == MQTTPublishDone ); + shouldDeleteRecord = ( newState == MQTTPublishDone ) ? true : false; isTransitionValid = validateTransitionAck( currentState, newState ); - if( isTransitionValid ) + if( isTransitionValid == true ) { updateRecord( records, recordIndex, From 4c102796231abb98a5aded1b4e583de602f41cc8 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 15 Jun 2020 21:48:54 -0700 Subject: [PATCH 544/844] Remaining MISRA for the HTTP Client Library (#996) * MISRA rules 8.8,8.7, 15.4, 18.3, 2.5, 21.15, 21.16, 5.2, 5.7, 5.4. --- .../http_demo_plaintext/http_demo_plaintext.c | 2 +- libraries/standard/http/include/http_client.h | 58 +++---- libraries/standard/http/src/http_client.c | 143 ++++++++++-------- .../http/src/private/http_client_internal.h | 91 ++++++----- .../standard/http/utest/http_send_utest.c | 58 +++---- libraries/standard/http/utest/http_utest.c | 6 +- 6 files changed, 197 insertions(+), 161 deletions(-) diff --git a/demos/http/http_demo_plaintext/http_demo_plaintext.c b/demos/http/http_demo_plaintext/http_demo_plaintext.c index 06897654b3..7af0ea2ea6 100644 --- a/demos/http/http_demo_plaintext/http_demo_plaintext.c +++ b/demos/http/http_demo_plaintext/http_demo_plaintext.c @@ -381,7 +381,7 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface /* Set "Connection" HTTP header to "keep-alive" so that multiple requests * can be sent over the same established TCP connection. */ - requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + requestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG; /* Set the buffer used for storing request headers. */ requestHeaders.pBuffer = userBuffer; diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 2fe223575e..5a18a25c2f 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -45,7 +45,7 @@ /** * @section http_send_flags - * @brief Values for #HTTPClient_Send flags parameter. + * @brief Values for #HTTPClient_Send sendFlags parameter. * These flags control some behavior of sending the request or receiving the * response. * @@ -60,13 +60,13 @@ * @brief Set this flag to disable automatically writing the Content-Length * header to send to the server. * - * This flag is valid only for #HTTPClient_Send.flags. + * This flag is valid only for #HTTPClient_Send.sendFlags. */ #define HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG 0x1U /** * @section http_request_flags - * @brief Flags for #HTTPRequestInfo_t.flags. + * @brief Flags for #HTTPRequestInfo_t.reqFlags. * These flags control what headers are written or not to the * #HttpRequestHeaders_t.pBuffer by #HTTPClient_InitializeRequestHeaders. * @@ -84,14 +84,14 @@ * Setting this will cause a "Connection: Keep-Alive" to be written to the * request headers. * - * This flag is valid only for #HTTPRequestInfo.flags. + * This flag is valid only for #HTTPRequestInfo.reqFlags. */ #define HTTP_REQUEST_KEEP_ALIVE_FLAG 0x1U /** * @section http_response_flags - * @brief Flags for #HTTPResponse_t.flags. - * These flags are populated in #HTTPResponse_t.flags by the #HTTPClient_Send + * @brief Flags for #HTTPResponse_t.respFlags. + * These flags are populated in #HTTPResponse_t.respFlags by the #HTTPClient_Send * function. * * - #HTTP_RESPONSE_CONNECTION_CLOSE_FLAG
@@ -106,14 +106,14 @@ * If a "Connection: close" header is present the application should always * close the connection. * - * This flag is valid only for #HTTPResponse_t.flags. + * This flag is valid only for #HTTPResponse_t.respFlags. */ #define HTTP_RESPONSE_CONNECTION_CLOSE_FLAG 0x1U /** * @brief This will be set to true if header "Connection: Keep-Alive" is found. * - * This flag is valid only for #HTTPResponse_t.flags. + * This flag is valid only for #HTTPResponse_t.respFlags. */ #define HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG 0x2U @@ -280,7 +280,7 @@ typedef enum HTTPStatus * Functions that may return this value: * - #HTTPClient_Send */ - HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER, + HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER, /** * @brief The server sent a response with an invalid character in the @@ -289,7 +289,7 @@ typedef enum HTTPStatus * Functions that may return this value: * - #HTTPClient_Send */ - HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION, + HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION, /** * @brief The server sent a response with an invalid character in the @@ -298,7 +298,7 @@ typedef enum HTTPStatus * Functions that may return this value: * - #HTTPClient_Send */ - HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE, + HTTP_SECURITY_ALERT_INVALID_STATUS_CODE, /** * @brief An invalid character was found in the HTTP response message. @@ -306,7 +306,7 @@ typedef enum HTTPStatus * Functions that may return this value: * - #HTTPClient_Send */ - HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, + HTTP_SECURITY_ALERT_INVALID_CHARACTER, /** * @brief The response contains either an invalid character in the @@ -316,7 +316,7 @@ typedef enum HTTPStatus * Functions that may return this value: * - #HTTPClient_Send */ - HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH, + HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, /** * @brief An error occurred in the third-party parsing library. @@ -410,7 +410,7 @@ typedef struct HTTPRequestInfo /** * @brief Flags to activate other request header configurations. */ - uint32_t flags; + uint32_t reqFlags; } HTTPRequestInfo_t; @@ -527,7 +527,7 @@ typedef struct HTTPResponse * * This is updated by #HTTPClient_Send. */ - uint32_t flags; + uint32_t respFlags; } HTTPResponse_t; /** @@ -545,7 +545,7 @@ typedef struct HTTPResponse * Host: <#HTTPRequestInfo_t.pHost> * * Note that "Connection" header can be added and set to "keep-alive" by - * activating the HTTP_REQUEST_KEEP_ALIVE_FLAG in #HTTPRequestInfo_t.flags. + * activating the HTTP_REQUEST_KEEP_ALIVE_FLAG in #HTTPRequestInfo_t.reqFlags. * * @param[in] pRequestHeaders Request header buffer information. * @param[in] pRequestInfo Initial request header configurations. @@ -642,7 +642,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * body in @p pRequestBodyBuf over the transport. The response is received in * #HTTPResponse_t.pBuffer. * - * If #HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG is not set in parameter @p flags, + * If #HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG is not set in parameter @p sendFlags, * then the Content-Length to be sent to the server is automatically written to * @p pRequestHeaders. The Content-Length will not be written when there is * no request body. If there is not enough room in the buffer to write the @@ -654,11 +654,11 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * following errors are returned: * - #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED * - #HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH + * - #HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER + * - #HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION + * - #HTTP_SECURITY_ALERT_INVALID_STATUS_CODE + * - #HTTP_SECURITY_ALERT_INVALID_CHARACTER + * - #HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH * * The @p pResponse returned is valid only if this function returns HTTP_SUCCESS. * @@ -671,7 +671,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * @param[in] reqBodyBufLen The length of the request entity in bytes. * @param[in] pResponse The response message and some notable response * parameters will be returned here on success. - * @param[in] pFlags Flags which modify the behavior of this function. Please + * @param[in] sendFlags Flags which modify the behavior of this function. Please * see @ref http_send_flags. * * @return One of the following: @@ -686,18 +686,18 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * Security alerts are listed below, please see #HTTPStatus_t for more information: * - #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED * - #HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH + * - #HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER + * - #HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION + * - #HTTP_SECURITY_ALERT_INVALID_STATUS_CODE + * - #HTTP_SECURITY_ALERT_INVALID_CHARACTER + * - #HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH */ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, HTTPResponse_t * pResponse, - uint32_t flags ); + uint32_t sendFlags ); /** * @brief Read a header from a buffer containing a complete HTTP response. diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 320886b17d..f88e571c6b 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -29,7 +29,7 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, * and length. * @param[in] reqBodyLen The length of the request body to be sent. This is * used to generated a Content-Length header. - * @param[in] flags Application provided flags to #HTTPClient_Send. + * @param[in] sendFlags Application provided flags to #HTTPClient_Send. * * @return #HTTP_SUCCESS if successful. If there was a network error or less * bytes than what were specified were sent, then #HTTP_NETWORK_ERROR is @@ -38,7 +38,7 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, size_t reqBodyLen, - uint32_t flags ); + uint32_t sendFlags ); /** * @brief Adds the Content-Length header field and value to the @@ -100,10 +100,10 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, * more bytes than what was specified were read, then #HTTP_NETWORK_ERROR is * returned. */ -HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, - uint8_t * pBuffer, - size_t bufferLen, - size_t * pBytesReceived ); +static HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ); /** * @brief Get the status of the HTTP response given the parsing state and how @@ -451,14 +451,14 @@ static void processCompleteHeader( HTTPParsingContext_t * pParsingContext ); * - #HTTP_SUCCESS * - #HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED * - #HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER - * - #HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH + * - #HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER + * - #HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION + * - #HTTP_SECURITY_ALERT_INVALID_STATUS_CODE + * - #HTTP_SECURITY_ALERT_INVALID_CHARACTER + * - #HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH * - #HTTP_PARSER_INTERNAL_ERROR */ -static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ); +static HTTPStatus_t processHttpParserError( const http_parser * pHttpParser ); /*-----------------------------------------------------------*/ @@ -730,13 +730,13 @@ static int httpParserOnHeadersCompleteCallback( http_parser * pHttpParser ) /* If the Connection: close header was found this flag will be set. */ if( ( pHttpParser->flags & ( unsigned int ) ( F_CONNECTION_CLOSE ) ) != 0u ) { - pResponse->flags |= HTTP_RESPONSE_CONNECTION_CLOSE_FLAG; + pResponse->respFlags |= HTTP_RESPONSE_CONNECTION_CLOSE_FLAG; } /* If the Connection: keep-alive header was found this flag will be set. */ if( ( pHttpParser->flags & ( unsigned int ) ( F_CONNECTION_KEEP_ALIVE ) ) != 0u ) { - pResponse->flags |= HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG; + pResponse->respFlags |= HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG; } /* http_parser_execute() requires that callback implementations must @@ -770,7 +770,7 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, int shouldContinueParse = HTTP_PARSER_CONTINUE_PARSING; HTTPParsingContext_t * pParsingContext = NULL; HTTPResponse_t * pResponse = NULL; - uint8_t * pNextWriteLoc = NULL; + char * pNextWriteLoc = NULL; assert( pHttpParser != NULL ); assert( pHttpParser->data != NULL ); @@ -801,15 +801,21 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, * affect future parsing as the changed segment will always be before the * next place to parse. */ /* coverity[misra_c_2012_rule_11_8_violation] */ - pNextWriteLoc = ( uint8_t * ) ( pResponse->pBody + pResponse->bodyLen ); + pNextWriteLoc = ( char * ) ( pResponse->pBody + pResponse->bodyLen ); /* If the response is of type Transfer-Encoding: chunked, then actual body * will follow the the chunked header. This body data is in a later location * and must be moved up in the buffer. When pLoc is greater than the current * end of the body, that signals the parser found a chunk header. */ - if( pLoc > ( const char * ) pNextWriteLoc ) + + /* MISRA Rule 18.3 flags pLoc and pNextWriteLoc as pointing to two different + * objects. This rule is suppressed because both pNextWriteLoc and pLoc + * point to a location in the response buffer. */ + /* coverity[pointer_parameter] */ + /* coverity[misra_c_2012_rule_18_3_violation] */ + if( pLoc > pNextWriteLoc ) { - ( void ) memcpy( pNextWriteLoc, ( const uint8_t * ) pLoc, length ); + ( void ) memcpy( pNextWriteLoc, pLoc, length ); } /* Increase the length of the body found. */ @@ -864,7 +870,7 @@ static void initializeParsingContextForFirstResponse( HTTPParsingContext_t * pPa /*-----------------------------------------------------------*/ -static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) +static HTTPStatus_t processHttpParserError( const http_parser * pHttpParser ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -902,7 +908,7 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) * character and location. */ LogError( ( "Response parsing error: Invalid character found in " "chunk header." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER; + returnStatus = HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER; break; case HPE_INVALID_VERSION: @@ -911,7 +917,7 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) * character and location. */ LogError( ( "Response parsing error: Invalid character found in " "HTTP protocol version." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION; + returnStatus = HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION; break; case HPE_INVALID_STATUS: @@ -920,20 +926,20 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) * could be out of range. This feedback is not given back by the * http-parser library. */ LogError( ( "Response parsing error: Invalid Status code." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE; + returnStatus = HTTP_SECURITY_ALERT_INVALID_STATUS_CODE; break; case HPE_STRICT: case HPE_INVALID_CONSTANT: LogError( ( "Response parsing error: Invalid character found in " "Status-Line or header delimitters." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER; + returnStatus = HTTP_SECURITY_ALERT_INVALID_CHARACTER; break; case HPE_LF_EXPECTED: LogError( ( "Response parsing error: Expected line-feed in header " "not found." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER; + returnStatus = HTTP_SECURITY_ALERT_INVALID_CHARACTER; break; case HPE_INVALID_HEADER_TOKEN: @@ -942,7 +948,7 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) * character and location. */ LogError( ( "Response parsing error: Invalid character found in " "headers." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER; + returnStatus = HTTP_SECURITY_ALERT_INVALID_CHARACTER; break; case HPE_INVALID_CONTENT_LENGTH: @@ -951,13 +957,13 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) * character and location. */ LogError( ( "Response parsing error: Invalid character found in " "content-length headers." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH; + returnStatus = HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH; break; case HPE_UNEXPECTED_CONTENT_LENGTH: LogError( ( "Response parsing error: A Content-Length header was " "found when it shouldn't have been." ) ); - returnStatus = HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH; + returnStatus = HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH; break; /* All other error cases cannot be triggered and indicate an error in the @@ -978,10 +984,10 @@ static HTTPStatus_t processHttpParserError( http_parser * pHttpParser ) /*-----------------------------------------------------------*/ -HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, - HTTPResponse_t * pResponse, - size_t parseLen, - uint8_t isHeadResponse ) +static HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, + HTTPResponse_t * pResponse, + size_t parseLen, + uint8_t isHeadResponse ) { HTTPStatus_t returnStatus; http_parser_settings parserSettings = { 0 }; @@ -1012,7 +1018,7 @@ HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, pResponse->headersLen = 0u; pResponse->headerCount = 0u; /* Initialize the response flags. */ - pResponse->flags = 0u; + pResponse->respFlags = 0u; } else { @@ -1116,7 +1122,7 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, size_t valueLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - uint8_t * pBufferCur = pRequestHeaders->pBuffer + pRequestHeaders->headersLen; + char * pBufferCur = ( char * ) ( pRequestHeaders->pBuffer + pRequestHeaders->headersLen ); size_t toAddLen = 0u; size_t backtrackHeaderLen = pRequestHeaders->headersLen; @@ -1154,7 +1160,7 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, pBufferCur += fieldLen; /* Copy the field separator, ": ", into the buffer. */ - ( void ) memcpy( ( char * ) pBufferCur, + ( void ) memcpy( pBufferCur, HTTP_HEADER_FIELD_SEPARATOR, HTTP_HEADER_FIELD_SEPARATOR_LEN ); pBufferCur += HTTP_HEADER_FIELD_SEPARATOR_LEN; @@ -1164,7 +1170,7 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, pBufferCur += valueLen; /* Copy the header end indicator, "\r\n\r\n" into the buffer. */ - ( void ) memcpy( ( char * ) pBufferCur, + ( void ) memcpy( pBufferCur, HTTP_HEADER_END_INDICATOR, HTTP_HEADER_END_INDICATOR_LEN ); @@ -1193,7 +1199,7 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, size_t pathLen ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; - uint8_t * pBufferCur = pRequestHeaders->pBuffer; + char * pBufferCur = ( char * ) ( pRequestHeaders->pBuffer ); size_t toAddLen = methodLen + \ SPACE_CHARACTER_LEN + \ SPACE_CHARACTER_LEN + \ @@ -1215,38 +1221,38 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, if( returnStatus == HTTP_SUCCESS ) { /* Write " HTTP/1.1\r\n" to start the HTTP header. */ - ( void ) memcpy( ( char * ) pBufferCur, pMethod, methodLen ); + ( void ) memcpy( pBufferCur, pMethod, methodLen ); pBufferCur += methodLen; - ( void ) memcpy( ( char * ) pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); + ( void ) memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); pBufferCur += SPACE_CHARACTER_LEN; /* Use "/" as default value if is NULL. */ if( ( pPath == NULL ) || ( pathLen == 0u ) ) { - ( void ) memcpy( ( char * ) pBufferCur, + ( void ) memcpy( pBufferCur, HTTP_EMPTY_PATH, HTTP_EMPTY_PATH_LEN ); pBufferCur += HTTP_EMPTY_PATH_LEN; } else { - ( void ) memcpy( ( char * ) pBufferCur, pPath, pathLen ); + ( void ) memcpy( pBufferCur, pPath, pathLen ); pBufferCur += pathLen; } - ( void ) memcpy( ( char * ) pBufferCur, + ( void ) memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); pBufferCur += SPACE_CHARACTER_LEN; - ( void ) memcpy( ( char * ) pBufferCur, + ( void ) memcpy( pBufferCur, HTTP_PROTOCOL_VERSION, HTTP_PROTOCOL_VERSION_LEN ); pBufferCur += HTTP_PROTOCOL_VERSION_LEN; - ( void ) memcpy( ( char * ) pBufferCur, + ( void ) memcpy( pBufferCur, HTTP_HEADER_LINE_SEPARATOR, HTTP_HEADER_LINE_SEPARATOR_LEN ); pRequestHeaders->headersLen = toAddLen; @@ -1338,7 +1344,7 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques if( returnStatus == HTTP_SUCCESS ) { - if( ( HTTP_REQUEST_KEEP_ALIVE_FLAG & pRequestInfo->flags ) != 0u ) + if( ( HTTP_REQUEST_KEEP_ALIVE_FLAG & pRequestInfo->reqFlags ) != 0u ) { /* Write "Connection: keep-alive". */ returnStatus = addHeader( pRequestHeaders, @@ -1526,6 +1532,13 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, assert( pData != NULL ); /* Loop until all data is sent. */ + + /* MISCRA C-2012 Rule 15.4 flags multiple break statements in the while loop + * below. There are two only error conditions when reading data from the + * network which both need the loop to terminate. Both of these conditions + * necessitate different error logs, so two different break statements are + * required. */ + /* coverity[misra_c_2012_rule_15_4_violation] */ while( bytesRemaining > 0UL ) { transportStatus = pTransport->send( pTransport->pContext, @@ -1611,7 +1624,7 @@ static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeade static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, size_t reqBodyLen, - uint32_t flags ) + uint32_t sendFlags ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; uint8_t shouldSendContentLength = 0u; @@ -1622,7 +1635,7 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport /* Send the content length header if the flag to disable is not set and the * body length is greater than zero. */ - shouldSendContentLength = ( ( ( flags & HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG ) == 0u ) && + shouldSendContentLength = ( ( ( sendFlags & HTTP_SEND_DISABLE_CONTENT_LENGTH_FLAG ) == 0u ) && ( reqBodyLen > 0u ) ) ? 1u : 0u; if( shouldSendContentLength == 1u ) @@ -1664,10 +1677,10 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, - uint8_t * pBuffer, - size_t bufferLen, - size_t * pBytesReceived ) +static HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, + uint8_t * pBuffer, + size_t bufferLen, + size_t * pBytesReceived ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; int32_t transportStatus = 0; @@ -1850,7 +1863,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, HTTPResponse_t * pResponse, - uint32_t flags ) + uint32_t sendFlags ) { HTTPStatus_t returnStatus = HTTP_SUCCESS; @@ -1910,7 +1923,7 @@ HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, returnStatus = sendHttpHeaders( pTransport, pRequestHeaders, reqBodyBufLen, - flags ); + sendFlags ); } /* Send the body, which is at another location in memory. */ @@ -1970,7 +1983,7 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /* Check whether the parsed header matches the header we are looking for. */ if( ( fieldLen == pContext->fieldLen ) && - ( memcmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) + ( strncmp( pContext->pField, pFieldLoc, fieldLen ) == 0 ) ) { LogDebug( ( "Found header field in response: " "HeaderName=%.*s, HeaderLocation=0x%p", @@ -2151,7 +2164,7 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, * value of "on_header_value" callback (related to the header value) should * cause the http_parser.http_errno to be "CB_header_value". */ if( ( returnStatus == HTTP_SUCCESS ) && - ( parser.http_errno != HPE_CB_header_value ) ) + ( parser.http_errno != ( unsigned int ) HPE_CB_header_value ) ) { LogError( ( "Header found in response but http-parser returned error: " "ParserError=%s", @@ -2163,7 +2176,7 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, * expected to be called which should cause the http_parser.http_errno to be * "OK" */ else if( ( returnStatus == HTTP_HEADER_NOT_FOUND ) && - ( parser.http_errno != HPE_OK ) ) + ( parser.http_errno != ( unsigned int ) ( HPE_OK ) ) ) { LogError( ( "Header not found in response: http-parser returned error: " "ParserError=%s", @@ -2283,24 +2296,24 @@ const char * HTTPClient_strerror( HTTPStatus_t status ) str = "HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA"; break; - case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER: - str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER"; + case HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER: + str = "HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER"; break; - case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION: - str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION"; + case HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION: + str = "HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION"; break; - case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE: - str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE"; + case HTTP_SECURITY_ALERT_INVALID_STATUS_CODE: + str = "HTTP_SECURITY_ALERT_INVALID_STATUS_CODE"; break; - case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER: - str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER"; + case HTTP_SECURITY_ALERT_INVALID_CHARACTER: + str = "HTTP_SECURITY_ALERT_INVALID_CHARACTER"; break; - case HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH: - str = "HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH"; + case HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH: + str = "HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH"; break; case HTTP_PARSER_INTERNAL_ERROR: diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index d2c65bcdf8..f6f5d5386b 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -24,66 +24,89 @@ /** * @brief The HTTP protocol version of this library is HTTP/1.1. */ -#define HTTP_PROTOCOL_VERSION "HTTP/1.1" -#define HTTP_PROTOCOL_VERSION_LEN ( sizeof( HTTP_PROTOCOL_VERSION ) - 1u ) +#define HTTP_PROTOCOL_VERSION "HTTP/1.1" +#define HTTP_PROTOCOL_VERSION_LEN ( sizeof( HTTP_PROTOCOL_VERSION ) - 1u ) /** * @brief Default value when pRequestInfo->pPath == NULL. */ -#define HTTP_EMPTY_PATH "/" -#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1u ) +#define HTTP_EMPTY_PATH "/" +#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1u ) /** * @brief Consants for HTTP header formatting */ -#define HTTP_HEADER_LINE_SEPARATOR "\r\n" -#define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1u ) -#define HTTP_HEADER_END_INDICATOR "\r\n\r\n" -#define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1u ) -#define HTTP_HEADER_FIELD_SEPARATOR ": " -#define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1u ) -#define COLON_CHARACTER ":" -#define COLON_CHARACTER_LEN ( sizeof( COLON_CHARACTER ) - 1u ) -#define SPACE_CHARACTER " " -#define SPACE_CHARACTER_LEN ( sizeof( SPACE_CHARACTER ) - 1u ) -#define EQUAL_CHARACTER "=" -#define EQUAL_CHARACTER_LEN ( sizeof( EQUAL_CHARACTER ) - 1u ) -#define DASH_CHARACTER "-" -#define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1u ) +#define HTTP_HEADER_LINE_SEPARATOR "\r\n" +#define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1u ) +#define HTTP_HEADER_END_INDICATOR "\r\n\r\n" +#define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1u ) +#define HTTP_HEADER_FIELD_SEPARATOR ": " +#define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1u ) +#define SPACE_CHARACTER " " +#define SPACE_CHARACTER_LEN ( sizeof( SPACE_CHARACTER ) - 1u ) +#define DASH_CHARACTER "-" +#define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1u ) /** * @brief Constants for header fields added automatically during the request * initialization. */ -#define HTTP_USER_AGENT_FIELD "User-Agent" -#define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1u ) -#define HTTP_HOST_FIELD "Host" -#define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1u ) -#define HTTP_USER_AGENT_VALUE_LEN ( sizeof( HTTP_USER_AGENT_VALUE ) - 1u ) +#define HTTP_USER_AGENT_FIELD "User-Agent" +#define HTTP_USER_AGENT_FIELD_LEN ( sizeof( HTTP_USER_AGENT_FIELD ) - 1u ) +#define HTTP_HOST_FIELD "Host" +#define HTTP_HOST_FIELD_LEN ( sizeof( HTTP_HOST_FIELD ) - 1u ) +#define HTTP_USER_AGENT_VALUE_LEN ( sizeof( HTTP_USER_AGENT_VALUE ) - 1u ) /** * @brief Constants for header fields added based on flags. */ -#define HTTP_CONNECTION_FIELD "Connection" -#define HTTP_CONNECTION_FIELD_LEN ( sizeof( HTTP_CONNECTION_FIELD ) - 1u ) -#define HTTP_CONTENT_LENGTH_FIELD "Content-Length" -#define HTTP_CONTENT_LENGTH_FIELD_LEN ( sizeof( HTTP_CONTENT_LENGTH_FIELD ) - 1u ) +#define HTTP_CONNECTION_FIELD "Connection" +#define HTTP_CONNECTION_FIELD_LEN ( sizeof( HTTP_CONNECTION_FIELD ) - 1u ) +#define HTTP_CONTENT_LENGTH_FIELD "Content-Length" +#define HTTP_CONTENT_LENGTH_FIELD_LEN ( sizeof( HTTP_CONTENT_LENGTH_FIELD ) - 1u ) /** * @brief Constants for header values added based on flags. */ -#define HTTP_CONNECTION_KEEP_ALIVE_VALUE "keep-alive" -#define HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ( sizeof( HTTP_CONNECTION_KEEP_ALIVE_VALUE ) - 1u ) -#define HTTP_CONNECTION_CLOSE_VALUE "close" -#define HTTP_CONNECTION_CLOSE_VALUE_LEN ( sizeof( HTTP_CONNECTION_CLOSE_VALUE ) - 1u ) +/* MISRA Rule 5.4 flags the following macro's name as ambiguous from the + * one postfixed with _LEN. This rule is suppressed for naming consistency with + * other HTTP header field and value string and length macros in this file.*/ +/* coverity[other_declaration] */ +#define HTTP_CONNECTION_KEEP_ALIVE_VALUE "keep-alive" + +/* MISRA Rule 5.4 flags the following macro's name as ambiguous from the one + * above it. This rule is suppressed for naming consistency with other HTTP + * header field and value string and length macros in this file.*/ +/* coverity[misra_c_2012_rule_5_4_violation] */ +#define HTTP_CONNECTION_KEEP_ALIVE_VALUE_LEN ( sizeof( HTTP_CONNECTION_KEEP_ALIVE_VALUE ) - 1u ) /** * @brief Constants relating to Range Requests. */ -#define HTTP_RANGE_REQUEST_HEADER_FIELD "Range" -#define HTTP_RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( HTTP_RANGE_REQUEST_HEADER_FIELD ) - 1u ) -#define HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX "bytes=" + +/* MISRA Rule 5.4 flags the following macro's name as ambiguous from the + * one postfixed with _LEN. This rule is suppressed for naming consistency with + * other HTTP header field and value string and length macros in this file.*/ +/* coverity[other_declaration] */ +#define HTTP_RANGE_REQUEST_HEADER_FIELD "Range" + +/* MISRA Rule 5.4 flags the following macro's name as ambiguous from the one + * above it. This rule is suppressed for naming consistency with other HTTP + * header field and value string and length macros in this file.*/ +/* coverity[misra_c_2012_rule_5_4_violation] */ +#define HTTP_RANGE_REQUEST_HEADER_FIELD_LEN ( sizeof( HTTP_RANGE_REQUEST_HEADER_FIELD ) - 1u ) + +/* MISRA Rule 5.4 flags the following macro's name as ambiguous from the + * one postfixed with _LEN. This rule is suppressed for naming consistency with + * other HTTP header field and value string and length macros in this file.*/ +/* coverity[other_declaration] */ +#define HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX "bytes=" + +/* MISRA Rule 5.4 flags the following macro's name as ambiguous from the one + * above it. This rule is suppressed for naming consistency with other HTTP + * header field and value string and length macros in this file.*/ +/* coverity[misra_c_2012_rule_5_4_violation] */ #define HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ( sizeof( HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX ) - 1u ) /** diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index 0acf8edec4..9795eb76b6 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -780,8 +780,8 @@ void test_HTTPClient_Send_HEAD_request_parse_whole_response( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -818,8 +818,8 @@ void test_HTTPClient_Send_PUT_request_parse_whole_response( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( 0, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -854,8 +854,8 @@ void test_HTTPClient_Send_GET_request_parse_whole_response( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_CONTENT_LENGTH, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -886,8 +886,8 @@ void test_HTTPClient_Send_no_response_headers( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( 0, response.contentLength ); TEST_ASSERT_EQUAL( 0, response.headerCount ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -916,8 +916,8 @@ void test_HTTPClient_Send_parse_partial_header_field( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -946,8 +946,8 @@ void test_HTTPClient_Send_parse_partial_header_value( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_CONTENT_LENGTH, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_HEAD_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -982,8 +982,8 @@ void test_HTTPClient_Send_parse_partial_body( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_CONTENT_LENGTH, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_GET_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -1017,8 +1017,8 @@ void test_HTTPClient_Send_parse_chunked_body( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( 0, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_CHUNKED_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -1191,8 +1191,8 @@ void test_HTTPClient_Send_less_bytes_request_headers( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( 0, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -1230,8 +1230,8 @@ void test_HTTPClient_Send_less_bytes_request_body( void ) TEST_ASSERT_EQUAL( HTTP_STATUS_CODE_OK, response.statusCode ); TEST_ASSERT_EQUAL( 0, response.contentLength ); TEST_ASSERT_EQUAL( HTTP_TEST_RESPONSE_PUT_HEADER_COUNT, response.headerCount ); - TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.flags ); - TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.flags ); + TEST_ASSERT_BITS_LOW( HTTP_RESPONSE_CONNECTION_CLOSE_FLAG, response.respFlags ); + TEST_ASSERT_BITS_HIGH( HTTP_RESPONSE_CONNECTION_KEEP_ALIVE_FLAG, response.respFlags ); } /*-----------------------------------------------------------*/ @@ -1487,7 +1487,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHUNK_HEADER, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER, returnStatus ); httpParsingErrno = HPE_CLOSED_CONNECTION; returnStatus = HTTPClient_Send( &transportInterface, @@ -1505,7 +1505,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_PROTOCOL_VERSION, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION, returnStatus ); httpParsingErrno = HPE_INVALID_STATUS; returnStatus = HTTPClient_Send( &transportInterface, @@ -1514,7 +1514,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_STATUS_CODE, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_STATUS_CODE, returnStatus ); httpParsingErrno = HPE_STRICT; returnStatus = HTTPClient_Send( &transportInterface, @@ -1523,7 +1523,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CHARACTER, returnStatus ); httpParsingErrno = HPE_INVALID_CONSTANT; returnStatus = HTTPClient_Send( &transportInterface, @@ -1532,7 +1532,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CHARACTER, returnStatus ); httpParsingErrno = HPE_LF_EXPECTED; returnStatus = HTTPClient_Send( &transportInterface, @@ -1541,7 +1541,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CHARACTER, returnStatus ); httpParsingErrno = HPE_INVALID_HEADER_TOKEN; returnStatus = HTTPClient_Send( &transportInterface, @@ -1550,7 +1550,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CHARACTER, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CHARACTER, returnStatus ); httpParsingErrno = HPE_INVALID_CONTENT_LENGTH; returnStatus = HTTPClient_Send( &transportInterface, @@ -1559,7 +1559,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, returnStatus ); httpParsingErrno = HPE_UNEXPECTED_CONTENT_LENGTH; returnStatus = HTTPClient_Send( &transportInterface, @@ -1568,7 +1568,7 @@ void test_HTTPClient_Send_parsing_errors( void ) 0, &response, 0 ); - TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_MALFORMED_RESPONSE_INVALID_CONTENT_LENGTH, returnStatus ); + TEST_ASSERT_EQUAL( HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH, returnStatus ); httpParsingErrno = HPE_UNKNOWN; returnStatus = HTTPClient_Send( &transportInterface, diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 4ce50da047..ea22598f48 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -379,7 +379,7 @@ static void setupRequestInfo( HTTPRequestInfo_t * pRequestInfo ) pRequestInfo->pathLen = HTTP_TEST_REQUEST_PATH_LEN; pRequestInfo->pHost = HTTP_TEST_HOST_VALUE; pRequestInfo->hostLen = HTTP_TEST_HOST_VALUE_LEN; - pRequestInfo->flags = 0; + pRequestInfo->reqFlags = 0; } /** @@ -493,7 +493,7 @@ void test_Http_InitializeRequestHeaders_ReqInfo() setupBuffer( &requestHeaders ); requestInfo.pPath = NULL; - requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + requestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG; numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), HTTP_TEST_EXTRA_HEADER_FORMAT, HTTP_METHOD_GET, HTTP_EMPTY_PATH, @@ -516,7 +516,7 @@ void test_Http_InitializeRequestHeaders_ReqInfo() /* Repeat the test above but with length of path == 0 for coverage. */ requestInfo.pPath = HTTP_EMPTY_PATH; requestInfo.pathLen = 0; - requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + requestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG; numBytes = snprintf( ( char * ) expectedHeaders.buffer, sizeof( expectedHeaders.buffer ), HTTP_TEST_EXTRA_HEADER_FORMAT, HTTP_METHOD_GET, HTTP_EMPTY_PATH, From ed2b4adeac1e46737d2e263be081695f773eadd5 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 16 Jun 2020 00:17:36 -0700 Subject: [PATCH 545/844] Update HTTPNetworkContext_t to NetworkContext_t (struct ptr type) (#992) * Rename HTTPNetworkContext_t into NetworkContext_t and rename into struct ptr type * Update NetworkContext_t for http server auth demo --- demos/http/http_demo_basic_tls/demo_config.h | 16 ++++++++---- demos/http/http_demo_basic_tls/http_config.h | 17 ++++++++----- .../http_demo_basic_tls/http_demo_basic_tls.c | 19 ++++++-------- .../http_demo_plaintext/http_demo_plaintext.c | 25 ++++++++++--------- libraries/standard/http/include/http_client.h | 18 ++++++------- .../standard/http/utest/http_send_utest.c | 14 +++++------ 6 files changed, 59 insertions(+), 50 deletions(-) diff --git a/demos/http/http_demo_basic_tls/demo_config.h b/demos/http/http_demo_basic_tls/demo_config.h index f17aed0380..024381c033 100644 --- a/demos/http/http_demo_basic_tls/demo_config.h +++ b/demos/http/http_demo_basic_tls/demo_config.h @@ -26,18 +26,24 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Logging config definition and header files inclusion are required in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. */ /* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the Demo. */ -#define LIBRARY_LOG_NAME "DEMO" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" diff --git a/demos/http/http_demo_basic_tls/http_config.h b/demos/http/http_demo_basic_tls/http_config.h index dbe1ab0116..cab58f59e2 100644 --- a/demos/http/http_demo_basic_tls/http_config.h +++ b/demos/http/http_demo_basic_tls/http_config.h @@ -26,18 +26,23 @@ /******* DO NOT CHANGE the following order ********/ /**************************************************/ -/* Logging related header files are required to be included in the following order: +/* Logging config definition and header files inclusion are required in the following order: * 1. Include the header file "logging_levels.h". - * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. - * 3. Include the header file "logging_stack.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for HTTP. + * 3. Include the header file "logging_stack.h", if logging is enabled for HTTP. */ -/* Include header that defines log levels. */ #include "logging_levels.h" /* Logging configuration for the HTTP library. */ -#define LIBRARY_LOG_NAME "HTTP" -#define LIBRARY_LOG_LEVEL LOG_INFO +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "HTTP" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif #include "logging_stack.h" diff --git a/demos/http/http_demo_basic_tls/http_demo_basic_tls.c b/demos/http/http_demo_basic_tls/http_demo_basic_tls.c index 625d686ee1..a343447a74 100644 --- a/demos/http/http_demo_basic_tls/http_demo_basic_tls.c +++ b/demos/http/http_demo_basic_tls/http_demo_basic_tls.c @@ -165,17 +165,12 @@ static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; * * @note For this TLS demo, the socket descriptor and SSL context is used. */ -struct HTTPNetworkContext +struct NetworkContext { int tcpSocket; SSL * pSslContext; }; -/** - * @brief Structure based on the definition of the HTTP network context. - */ -static HTTPNetworkContext_t networkContext; - /*-----------------------------------------------------------*/ /** @@ -216,7 +211,7 @@ static int tlsSetup( int tcpSocket, * * @return Number of bytes sent if successful; otherwise negative value on error. */ -static int32_t transportSend( HTTPNetworkContext_t * pContext, +static int32_t transportSend( NetworkContext_t pNetworkContext, const void * pBuffer, size_t bytesToSend ); @@ -232,7 +227,7 @@ static int32_t transportSend( HTTPNetworkContext_t * pContext, * * @return Number of bytes received if successful; otherwise negative value on error. */ -static int32_t transportRecv( HTTPNetworkContext_t * pContext, +static int32_t transportRecv( NetworkContext_t pNetworkContext, void * pBuffer, size_t bytesToRecv ); @@ -536,7 +531,7 @@ static int tlsSetup( int tcpSocket, /*-----------------------------------------------------------*/ -static int32_t transportSend( HTTPNetworkContext_t * pNetworkContext, +static int32_t transportSend( NetworkContext_t pNetworkContext, const void * pBuffer, size_t bytesToSend ) { @@ -574,7 +569,7 @@ static int32_t transportSend( HTTPNetworkContext_t * pNetworkContext, /*-----------------------------------------------------------*/ -static int32_t transportRecv( HTTPNetworkContext_t * pNetworkContext, +static int32_t transportRecv( NetworkContext_t pNetworkContext, void * pBuffer, size_t bytesToRecv ) { @@ -658,7 +653,7 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface /* Set "Connection" HTTP header to "keep-alive" so that multiple requests * can be sent over the same established TCP connection. */ - requestInfo.flags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + requestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG; /* Set the buffer used for storing request headers. */ requestHeaders.pBuffer = userBuffer; @@ -748,6 +743,8 @@ int main( int argc, int returnStatus = EXIT_SUCCESS; /* The HTTP Client library transport layer interface. */ HTTPTransportInterface_t transportInterface; + /* Structure based on the definition of the HTTP network context. */ + struct NetworkContext networkContext; ( void ) argc; ( void ) argv; diff --git a/demos/http/http_demo_plaintext/http_demo_plaintext.c b/demos/http/http_demo_plaintext/http_demo_plaintext.c index 7af0ea2ea6..a316eb1e8b 100644 --- a/demos/http/http_demo_plaintext/http_demo_plaintext.c +++ b/demos/http/http_demo_plaintext/http_demo_plaintext.c @@ -69,7 +69,7 @@ static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; * * @note An integer is used to store the descriptor of the socket. */ -struct HTTPNetworkContext +struct NetworkContext { int tcpSocket; }; @@ -77,7 +77,7 @@ struct HTTPNetworkContext /** * @brief Structure based on the definition of the HTTP network context. */ -static HTTPNetworkContext_t socketContext; +static struct NetworkContext socketContext; /** * @brief The HTTP Client library transport layer interface. @@ -128,7 +128,7 @@ static int connectToServer( const char * pServer, * * @return Number of bytes sent if successful; otherwise negative value on error. */ -static int32_t transportSend( HTTPNetworkContext_t * pContext, +static int32_t transportSend( NetworkContext_t pContext, const void * pBuffer, size_t bytesToSend ); @@ -144,7 +144,7 @@ static int32_t transportSend( HTTPNetworkContext_t * pContext, * * @return Number of bytes received if successful; otherwise negative value on error. */ -static int32_t transportRecv( HTTPNetworkContext_t * pContext, +static int32_t transportRecv( NetworkContext_t pContext, void * pBuffer, size_t bytesToRecv ); @@ -308,7 +308,7 @@ static int connectToServer( const char * pServer, /*-----------------------------------------------------------*/ -static int32_t transportSend( HTTPNetworkContext_t * pContext, +static int32_t transportSend( NetworkContext_t pContext, const void * pBuffer, size_t bytesToSend ) { @@ -331,7 +331,7 @@ static int32_t transportSend( HTTPNetworkContext_t * pContext, /*-----------------------------------------------------------*/ -static int32_t transportRecv( HTTPNetworkContext_t * pContext, +static int32_t transportRecv( NetworkContext_t pContext, void * pBuffer, size_t bytesToRecv ) { @@ -464,6 +464,7 @@ int main( int argc, char ** argv ) { int returnStatus = EXIT_SUCCESS; + NetworkContext_t pSocketContext = &socketContext; ( void ) argc; ( void ) argv; @@ -471,14 +472,14 @@ int main( int argc, /**************************** Connect. ******************************/ /* Establish TCP connection. */ - returnStatus = connectToServer( SERVER_HOST, SERVER_PORT, &socketContext.tcpSocket ); + returnStatus = connectToServer( SERVER_HOST, SERVER_PORT, &pSocketContext->tcpSocket ); /* Define the transport interface. */ if( returnStatus == EXIT_SUCCESS ) { transportInterface.recv = transportRecv; transportInterface.send = transportSend; - transportInterface.pContext = &socketContext; + transportInterface.pContext = pSocketContext; } /*********************** Send HTTPS request. ************************/ @@ -511,7 +512,7 @@ int main( int argc, } /* Send POST Request. */ - if( returnStatus != EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { returnStatus = sendHttpRequest( &transportInterface, SERVER_HOST, @@ -521,10 +522,10 @@ int main( int argc, /************************** Disconnect. *****************************/ - if( socketContext.tcpSocket != -1 ) + if( pSocketContext->tcpSocket != -1 ) { - ( void ) shutdown( socketContext.tcpSocket, SHUT_RDWR ); - ( void ) close( socketContext.tcpSocket ); + ( void ) shutdown( pSocketContext->tcpSocket, SHUT_RDWR ); + ( void ) close( pSocketContext->tcpSocket ); } return returnStatus; diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 5a18a25c2f..387c3c9152 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -131,12 +131,12 @@ #define HTTP_RANGE_REQUEST_END_OF_FILE -1 /** - * @brief The HTTPNetworkContext is an incomplete type. The application must - * define HTTPNetworkContext to the type of their network context. This context + * @brief The NetworkContext is an incomplete type. The application must + * define NetworkContext to the type of their network context. This context * is passed into the network interface functions. */ -struct HTTPNetworkContext; -typedef struct HTTPNetworkContext HTTPNetworkContext_t; +struct NetworkContext; +typedef struct NetworkContext * NetworkContext_t; /** * @brief Transport interface for sending data over the network. @@ -151,7 +151,7 @@ typedef struct HTTPNetworkContext HTTPNetworkContext_t; * * @return The number of bytes written or a negative network error code. */ -typedef int32_t ( * HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, +typedef int32_t ( * HTTPTransportSend_t )( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ); @@ -174,7 +174,7 @@ typedef int32_t ( * HTTPTransportSend_t )( HTTPNetworkContext_t * pContext, * * @return The number of bytes read or a negative error code. */ -typedef int32_t ( * HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, +typedef int32_t ( * HTTPTransportRecv_t )( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ); @@ -183,9 +183,9 @@ typedef int32_t ( * HTTPTransportRecv_t )( HTTPNetworkContext_t * pContext, */ typedef struct HTTPTransportInterface { - HTTPTransportRecv_t recv; /**< Transport receive interface */ - HTTPTransportSend_t send; /**< Transport interface send interface. */ - HTTPNetworkContext_t * pContext; /**< User defined transport interface context. */ + HTTPTransportRecv_t recv; /**< Transport receive interface */ + HTTPTransportSend_t send; /**< Transport interface send interface. */ + NetworkContext_t pContext; /**< User defined transport interface context. */ } HTTPTransportInterface_t; /** diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index 9795eb76b6..e393af6a91 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -232,7 +232,7 @@ static void onHeaderCallback( void * pContext, } /* Successful application transport send interface. */ -static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, +static int32_t transportSendSuccess( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { @@ -258,7 +258,7 @@ static int32_t transportSendSuccess( HTTPNetworkContext_t * pContext, /* Application transport send interface that returns a network error depending * on the call count. Set sendErrorCall to 0 to return an error on the * first call. Set sendErrorCall to 1 to return an error on the second call. */ -static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, +static int32_t transportSendNetworkError( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { @@ -279,7 +279,7 @@ static int32_t transportSendNetworkError( HTTPNetworkContext_t * pContext, * depending on the call count. Set sendPartialCall to 0 to return less bytes on * the first call. Set sendPartialCall to 1 to return less bytes on the second * call. */ -static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContext, +static int32_t transportSendLessThanBytesToWrite( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { @@ -297,7 +297,7 @@ static int32_t transportSendLessThanBytesToWrite( HTTPNetworkContext_t * pContex } /* Application transport send that writes more bytes than expected. */ -static int32_t transportSendMoreThanBytesToWrite( HTTPNetworkContext_t * pContext, +static int32_t transportSendMoreThanBytesToWrite( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { @@ -313,7 +313,7 @@ static int32_t transportSendMoreThanBytesToWrite( HTTPNetworkContext_t * pContex * second call. The response to send is set in pNetworkData and the current * call count is kept track of in recvCurrentCall. This function will return * zero (timeout condition) when recvStopCall matches recvCurrentCall. */ -static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, +static int32_t transportRecvSuccess( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { @@ -351,7 +351,7 @@ static int32_t transportRecvSuccess( HTTPNetworkContext_t * pContext, } /* Application transport receive that return a network error. */ -static int32_t transportRecvNetworkError( HTTPNetworkContext_t * pContext, +static int32_t transportRecvNetworkError( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { @@ -363,7 +363,7 @@ static int32_t transportRecvNetworkError( HTTPNetworkContext_t * pContext, } /* Application transport receive that returns more bytes read than expected. */ -static int32_t transportRecvMoreThanBytesToRead( HTTPNetworkContext_t * pContext, +static int32_t transportRecvMoreThanBytesToRead( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { From 21b921fd0cef55234018564b8dc2543ced337f67 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 16 Jun 2020 16:39:08 -0700 Subject: [PATCH 546/844] Update logConnackResponse to log success response at Info level (#1001) --- libraries/standard/mqtt/src/mqtt_lightweight.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index d483e3554e..5838ce64bd 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -728,8 +728,16 @@ static void logConnackResponse( uint8_t responseCode ) assert( responseCode <= 5 ); - /* Log an error based on the CONNACK response code. */ - LogError( ( "%s", pConnackResponses[ responseCode ] ) ); + if( responseCode == 0u ) + { + /* Log at Info level for a success CONNACK response. */ + LogInfo( ( "%s", pConnackResponses[ 0 ] ) ); + } + else + { + /* Log an error based on the CONNACK response code. */ + LogError( ( "%s", pConnackResponses[ responseCode ] ) ); + } } /*-----------------------------------------------------------*/ From 3b79bebb4df9ff87fac636cf05c42dda4ad2aa71 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 17 Jun 2020 16:09:37 -0700 Subject: [PATCH 547/844] Rename MQTTNetworkContext_t to NetworkContext_t (#999) --- demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h | 2 +- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 8 ++++---- demos/mqtt/mqtt_demo_lightweight/mqtt_config.h | 2 +- .../mqtt_demo_lightweight.c | 2 +- demos/mqtt/mqtt_demo_plaintext/mqtt_config.h | 2 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 10 +++++----- libraries/standard/mqtt/include/mqtt.h | 4 ++-- .../standard/mqtt/include/mqtt_lightweight.h | 4 ++-- libraries/standard/mqtt/src/mqtt_lightweight.c | 4 ++-- libraries/standard/mqtt/utest/mqtt_config.h | 2 +- .../standard/mqtt/utest/mqtt_lightweight_utest.c | 10 +++++----- libraries/standard/mqtt/utest/mqtt_utest.c | 16 ++++++++-------- 12 files changed, 33 insertions(+), 33 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h index cba5a52ea6..af8cd9937c 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h @@ -50,7 +50,7 @@ /* Set network context to OpenSSL SSL context. */ #include -typedef SSL * MQTTNetworkContext_t; +typedef SSL * NetworkContext_t; /** * @brief The maximum number of MQTT PUBLISH messages that may be pending diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 3c2b49638b..95498052f7 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -218,7 +218,7 @@ static int tlsSetup( int tcpSocket, * @return Number of bytes sent; negative value on error; * 0 for timeout or 0 bytes sent. */ -static int32_t transportSend( MQTTNetworkContext_t pSslContext, +static int32_t transportSend( NetworkContext_t pSslContext, const void * pMessage, size_t bytesToSend ); @@ -232,7 +232,7 @@ static int32_t transportSend( MQTTNetworkContext_t pSslContext, * @return Number of bytes received; negative value on error; * 0 for timeout. */ -static int32_t transportRecv( MQTTNetworkContext_t pSslContext, +static int32_t transportRecv( NetworkContext_t pSslContext, void * pBuffer, size_t bytesToRecv ); @@ -608,7 +608,7 @@ static int tlsSetup( int tcpSocket, /*-----------------------------------------------------------*/ -static int32_t transportSend( MQTTNetworkContext_t pSslContext, +static int32_t transportSend( NetworkContext_t pSslContext, const void * pMessage, size_t bytesToSend ) { @@ -647,7 +647,7 @@ static int32_t transportSend( MQTTNetworkContext_t pSslContext, /*-----------------------------------------------------------*/ -static int32_t transportRecv( MQTTNetworkContext_t pSslContext, +static int32_t transportRecv( NetworkContext_t pSslContext, void * pBuffer, size_t bytesToRecv ) { diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h index 16f3c900e7..50b5e6dbff 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h @@ -48,6 +48,6 @@ /************ End of logging configuration ****************/ /* Set network context to socket (int). */ -typedef int MQTTNetworkContext_t; +typedef int NetworkContext_t; #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index e213c8810d..68b3d44305 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -426,7 +426,7 @@ static int connectToServer( const char * pServer, * * @return Number of bytes received or zero to indicate transportTimeout; negative value on error. */ -static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, +static int32_t transportRecv( NetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ) { diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h index 4018efa21a..82280bd6f7 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h @@ -49,7 +49,7 @@ /************ End of logging configuration ****************/ /* Set network context to socket (int). */ -typedef int MQTTNetworkContext_t; +typedef int NetworkContext_t; /** * @brief The maximum number of MQTT PUBLISH messages that may be pending diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 9cb0e38988..c7a76e36bc 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -192,7 +192,7 @@ static int connectToServer( const char * pServer, * @return Number of bytes sent; negative value on error; * 0 for timeout or 0 bytes sent. */ -static int32_t transportSend( MQTTNetworkContext_t tcpSocket, +static int32_t transportSend( NetworkContext_t tcpSocket, const void * pMessage, size_t bytesToSend ); @@ -205,7 +205,7 @@ static int32_t transportSend( MQTTNetworkContext_t tcpSocket, * * @return Number of bytes received; negative value on error. */ -static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, +static int32_t transportRecv( NetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ); @@ -423,7 +423,7 @@ static int connectToServer( const char * pServer, /*-----------------------------------------------------------*/ -static int32_t transportSend( MQTTNetworkContext_t tcpSocket, +static int32_t transportSend( NetworkContext_t tcpSocket, const void * pMessage, size_t bytesToSend ) { @@ -446,7 +446,7 @@ static int32_t transportSend( MQTTNetworkContext_t tcpSocket, /*-----------------------------------------------------------*/ -static int32_t transportRecv( MQTTNetworkContext_t tcpSocket, +static int32_t transportRecv( NetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ) { @@ -602,7 +602,7 @@ static int establishMqttSession( MQTTContext_t * pContext, /* Fill in TransportInterface send and receive function pointers. * For this demo, TCP sockets are used to send and receive data * from network. Network context is socket file descriptor.*/ - transport.networkContext = ( MQTTNetworkContext_t ) tcpSocket; + transport.networkContext = ( NetworkContext_t ) tcpSocket; transport.send = transportSend; transport.recv = transportRecv; diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index d502b20303..aafed71d25 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -38,7 +38,7 @@ typedef struct MQTTContext MQTTContext_t; struct MQTTTransportInterface; typedef struct MQTTTransportInterface MQTTTransportInterface_t; -typedef int32_t (* MQTTTransportSendFunc_t )( MQTTNetworkContext_t context, +typedef int32_t (* MQTTTransportSendFunc_t )( NetworkContext_t context, const void * pMessage, size_t bytesToSend ); @@ -82,7 +82,7 @@ struct MQTTTransportInterface { MQTTTransportSendFunc_t send; MQTTTransportRecvFunc_t recv; - MQTTNetworkContext_t networkContext; + NetworkContext_t networkContext; }; struct MQTTApplicationCallbacks diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 3c2b6ff6b4..15ed663bf3 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -87,7 +87,7 @@ typedef struct MQTTPacketInfo MQTTPacketInfo_t; * * @return The number of bytes received; negative value on failure. */ -typedef int32_t (* MQTTTransportRecvFunc_t )( MQTTNetworkContext_t context, +typedef int32_t (* MQTTTransportRecvFunc_t )( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ); @@ -520,7 +520,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, * #MQTTNoDataAvailable if there is nothing to read. */ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, - MQTTNetworkContext_t networkContext, + NetworkContext_t networkContext, MQTTPacketInfo_t * pIncomingPacket ); #endif /* ifndef MQTT_LIGHTWEIGHT_H */ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 5838ce64bd..9096ebafbc 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -523,7 +523,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, } static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, - MQTTNetworkContext_t networkContext ) + NetworkContext_t networkContext ) { size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; uint8_t encodedByte = 0; @@ -2042,7 +2042,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, /*-----------------------------------------------------------*/ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, - MQTTNetworkContext_t networkContext, + NetworkContext_t networkContext, MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTSuccess; diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index eca3ace873..40df17e2a2 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -23,7 +23,7 @@ /************ End of logging configuration ****************/ /* Set network context to double pointer to buffer (uint8_t**). */ -typedef uint8_t ** MQTTNetworkContext_t; +typedef uint8_t ** NetworkContext_t; /** * @brief The maximum number of MQTT PUBLISH messages that may be pending diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 4c61c896fd..552430bf55 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -152,7 +152,7 @@ int suiteTearDown( int numFailures ) /** * @brief Mock successful transport receive by reading data from a buffer. */ -static int32_t mockReceive( MQTTNetworkContext_t context, +static int32_t mockReceive( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ) { @@ -178,7 +178,7 @@ static int32_t mockReceive( MQTTNetworkContext_t context, /** * @brief Mock transport receive with no data available. */ -static int32_t mockReceiveNoData( MQTTNetworkContext_t context, +static int32_t mockReceiveNoData( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ) { @@ -188,7 +188,7 @@ static int32_t mockReceiveNoData( MQTTNetworkContext_t context, /** * @brief Mock transport receive failure. */ -static int32_t mockReceiveFailure( MQTTNetworkContext_t context, +static int32_t mockReceiveFailure( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ) { @@ -198,7 +198,7 @@ static int32_t mockReceiveFailure( MQTTNetworkContext_t context, /** * @brief Mock transport receive that succeeds once, then fails. */ -static int32_t mockReceiveSucceedThenFail( MQTTNetworkContext_t context, +static int32_t mockReceiveSucceedThenFail( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ) { @@ -1575,7 +1575,7 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) uint8_t * bufPtr = buffer; /* Dummy network context - pointer to pointer to a buffer. */ - MQTTNetworkContext_t networkContext = ( MQTTNetworkContext_t ) &bufPtr; + NetworkContext_t networkContext = ( NetworkContext_t ) &bufPtr; buffer[ 0 ] = 0x20; /* CONN ACK */ buffer[ 1 ] = 0x02; /* Remaining length. */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 7001ee46cc..8b0c942f9f 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -142,7 +142,7 @@ int suiteTearDown( int numFailures ) * @brief Mock successful transport send, and write data into buffer for * verification. */ -static int32_t mockSend( MQTTNetworkContext_t context, +static int32_t mockSend( NetworkContext_t context, const void * pMessage, size_t bytesToSend ) { @@ -213,7 +213,7 @@ static uint32_t getTime( void ) * @return Number of bytes sent; negative value on error; * 0 for timeout or 0 bytes sent. */ -static int32_t transportSendSuccess( MQTTNetworkContext_t pContext, +static int32_t transportSendSuccess( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { @@ -225,7 +225,7 @@ static int32_t transportSendSuccess( MQTTNetworkContext_t pContext, /** * @brief Mocked failed transport send. */ -static int32_t transportSendFailure( MQTTNetworkContext_t pContext, +static int32_t transportSendFailure( NetworkContext_t pContext, const void * pBuffer, size_t bytesToWrite ) { @@ -238,7 +238,7 @@ static int32_t transportSendFailure( MQTTNetworkContext_t pContext, /** * @brief Mocked transport send that succeeds then fails. */ -static int32_t transportSendSucceedThenFail( MQTTNetworkContext_t context, +static int32_t transportSendSucceedThenFail( NetworkContext_t context, const void * pMessage, size_t bytesToSend ) { @@ -266,7 +266,7 @@ static int32_t transportSendSucceedThenFail( MQTTNetworkContext_t context, * * @return Number of bytes received; negative value on error. */ -static int32_t transportRecvSuccess( MQTTNetworkContext_t pContext, +static int32_t transportRecvSuccess( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { @@ -278,7 +278,7 @@ static int32_t transportRecvSuccess( MQTTNetworkContext_t pContext, /** * @brief Mocked failed transport read. */ -static int32_t transportRecvFailure( MQTTNetworkContext_t pContext, +static int32_t transportRecvFailure( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { @@ -291,7 +291,7 @@ static int32_t transportRecvFailure( MQTTNetworkContext_t pContext, /** * @brief Mocked transport reading one byte at a time. */ -static int32_t transportRecvOneByte( MQTTNetworkContext_t pContext, +static int32_t transportRecvOneByte( NetworkContext_t pContext, void * pBuffer, size_t bytesToRead ) { @@ -874,7 +874,7 @@ void test_MQTT_Disconnect( void ) MQTTStatus_t status; uint8_t buffer[ 10 ]; uint8_t * bufPtr = buffer; - MQTTNetworkContext_t networkContext = ( MQTTNetworkContext_t ) &bufPtr; + NetworkContext_t networkContext = ( NetworkContext_t ) &bufPtr; MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; From b4ecf234c056a249ce75785eb1f246d49a4feec7 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 18 Jun 2020 16:09:24 -0700 Subject: [PATCH 548/844] MQTT Integration tests for Pub/Sub with all QoS levels (#1000) * Complete transport utilities library * Get a building integration test for PubSub with Qos 0 * Updates to CMake for building integration test target * Working QoS1 test and new test for QoS0 * Fix flaky behavior of QoS1 test by increasing ProcessLoop timeout * Minor changes for logging configuration in test and tls_utils files * Updates to CMock tooling for building mqtt_system_utest target * Add a test case for Pub/Sub with QoS2 * Changes from review comments * Change Publish request to not set retain flag to prevent inter-test side-effects * Doc hygiene changes from more review comments * Doc fix in demos/transport/CMakeLists.txt * Remaining hygiene changes from review comments * Rename integration test file to mqtt_system_test.c * Update filename for @file tags in transport_utils files * Update header macros for transport header files * Remove MQTT build dependency from transport_utils. Rename CMake variables from utest to stest for integration tests * Add inclue path for logging headers to transport_utils * Use spaces instead of tabs --- CMakeLists.txt | 1 + .../mqtt_demo_basic_tls/mosquitto.org.crt | 38 +- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 2 +- demos/transport/CMakeLists.txt | 26 + demos/transport/transport_config.h | 50 ++ demos/transport/transport_utils.c | 416 ++++++++++ demos/transport/transport_utils.h | 114 +++ libraries/CMakeLists.txt | 2 +- libraries/standard/mqtt/CMakeLists.txt | 1 + .../mqtt/integration-test/CMakeLists.txt | 60 ++ .../mqtt/integration-test/mosquitto.org.crt | 24 + .../mqtt/integration-test/mqtt_config.h | 79 ++ .../mqtt/integration-test/mqtt_system_test.c | 718 ++++++++++++++++++ .../mqtt/integration-test/test_config.h | 95 +++ tools/cmock/create_test.cmake | 3 +- 15 files changed, 1610 insertions(+), 19 deletions(-) create mode 100644 demos/transport/CMakeLists.txt create mode 100644 demos/transport/transport_config.h create mode 100644 demos/transport/transport_utils.c create mode 100644 demos/transport/transport_utils.h create mode 100644 libraries/standard/mqtt/integration-test/CMakeLists.txt create mode 100644 libraries/standard/mqtt/integration-test/mosquitto.org.crt create mode 100644 libraries/standard/mqtt/integration-test/mqtt_config.h create mode 100644 libraries/standard/mqtt/integration-test/mqtt_system_test.c create mode 100644 libraries/standard/mqtt/integration-test/test_config.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 95521a20bf..2efdd765ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,4 +51,5 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) add_subdirectory( libraries ) # Build the demos. +add_subdirectory( demos/transport ) add_subdirectory( demos ) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt b/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt index b8535e8872..e76dbd8559 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt +++ b/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt @@ -1,18 +1,24 @@ -----BEGIN CERTIFICATE----- -MIIC8DCCAlmgAwIBAgIJAOD63PlXjJi8MA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD -VQQGEwJHQjEXMBUGA1UECAwOVW5pdGVkIEtpbmdkb20xDjAMBgNVBAcMBURlcmJ5 -MRIwEAYDVQQKDAlNb3NxdWl0dG8xCzAJBgNVBAsMAkNBMRYwFAYDVQQDDA1tb3Nx -dWl0dG8ub3JnMR8wHQYJKoZIhvcNAQkBFhByb2dlckBhdGNob28ub3JnMB4XDTEy -MDYyOTIyMTE1OVoXDTIyMDYyNzIyMTE1OVowgZAxCzAJBgNVBAYTAkdCMRcwFQYD -VQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwGA1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1v -c3F1aXR0bzELMAkGA1UECwwCQ0ExFjAUBgNVBAMMDW1vc3F1aXR0by5vcmcxHzAd -BgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hvby5vcmcwgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBAMYkLmX7SqOT/jJCZoQ1NWdCrr/pq47m3xxyXcI+FLEmwbE3R9vM -rE6sRbP2S89pfrCt7iuITXPKycpUcIU0mtcT1OqxGBV2lb6RaOT2gC5pxyGaFJ+h -A+GIbdYKO3JprPxSBoRponZJvDGEZuM3N7p3S/lRoi7G5wG5mvUmaE5RAgMBAAGj -UDBOMB0GA1UdDgQWBBTad2QneVztIPQzRRGj6ZHKqJTv5jAfBgNVHSMEGDAWgBTa -d2QneVztIPQzRRGj6ZHKqJTv5jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA -A4GBAAqw1rK4NlRUCUBLhEFUQasjP7xfFqlVbE2cRy0Rs4o3KS0JwzQVBwG85xge -REyPOFdGdhBY2P1FNRy0MDr6xr+D2ZOwxs63dG1nnAnWZg7qwoLgpZ4fESPD3PkA -1ZgKJc2zbSQ9fCPxt2W3mdVav66c6fsb7els2W2Iz7gERJSX +MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL +BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG +A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU +BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv +by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE +BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES +MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp +dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg +UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW +Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA +s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH +3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo +E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT +MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV +6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC +6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf ++pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK +sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 +LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE +m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= -----END CERTIFICATE----- diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 95498052f7..590c8dc5a3 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -169,7 +169,7 @@ static uint16_t globalSubscribePacketIdentifier = 0U; /** * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; - * it is used to match received Unsubscribe response to the transmitted unsubscribe + * it is used to match received Unsubscribe ACK to the transmitted unsubscribe * request. */ static uint16_t globalUnsubscribePacketIdentifier = 0U; diff --git a/demos/transport/CMakeLists.txt b/demos/transport/CMakeLists.txt new file mode 100644 index 0000000000..4eb10057cd --- /dev/null +++ b/demos/transport/CMakeLists.txt @@ -0,0 +1,26 @@ +# CMake library target for transport_utils. +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL REQUIRED) + +set( CMAKE_THREAD_PREFER_PTHREAD ON ) +find_package(Threads REQUIRED) + +add_library( transport_utils transport_utils.c ) + +target_link_libraries( + transport_utils + PRIVATE + OpenSSL::Crypto + OpenSSL::SSL + # SSL uses Threads and on some platforms require explicit linking. + Threads::Threads + # SSL uses Dynamic Loading and on some platforms requires explicit linking. + ${CMAKE_DL_LIBS} +) + +target_include_directories( + transport_utils + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + ${LOGGING_INCLUDE_DIRS} +) diff --git a/demos/transport/transport_config.h b/demos/transport/transport_config.h new file mode 100644 index 0000000000..0d777968bd --- /dev/null +++ b/demos/transport/transport_config.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef TRANSPORT_CONFIG_H_ +#define TRANSPORT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for TLS Utils library. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. + */ + +#include "logging_levels.h" + +/* Logging configuration for the Transport. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "TRANSPORT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_NONE +#endif +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef TRANSPORT_CONFIG_H_ */ diff --git a/demos/transport/transport_utils.c b/demos/transport/transport_utils.c new file mode 100644 index 0000000000..07311aa856 --- /dev/null +++ b/demos/transport/transport_utils.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file transport_utils.c + * @brief Implementation of wrapper utilities for using OpenSSL as the TLS stack + * on POSIX platform. + */ + +/* POSIX socket includes. */ +#include +#include +#include +#include + +#include +#include + +/* Standard includes. */ +#include +#include +#include + +/* Include config file before non-system headers. */ +#include "transport_config.h" + +/* Header file for the library. */ +#include "transport_utils.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) + +/*-----------------------------------------------------------*/ + +int tcpConnectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ) +{ + int status = EXIT_SUCCESS; + struct addrinfo hints, * pIndex, * pListHead = NULL; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0x00, sizeof( hints ) ); + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + LogInfo( ( "Looking up DNS for endpoint: %s", pServer ) ); + + /* Perform a DNS lookup on the given host name. */ + status = getaddrinfo( pServer, NULL, &hints, &pListHead ); + + LogInfo( ( "Resolved DNS for endpoint: %s", pServer ) ); + + if( status != -1 ) + { + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + LogInfo( ( "Attempting to create TCP client socket" ) ); + + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( *pTcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + } + + LogInfo( ( "Attempting to connect with socket: Fd=%d", *pTcpSocket ) ); + + status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); + + if( status == -1 ) + { + LogError( ( "Connection with socket failed: Fd=%d", *pTcpSocket ) ); + close( *pTcpSocket ); + } + else + { + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + status = EXIT_FAILURE; + LogError( ( "Located but could not connect to MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + } + else + { + status = EXIT_SUCCESS; + LogInfo( ( "TCP connection established with %.*s.\n\n", + ( int ) strlen( pServer ), + pServer ) ); + } + } + else + { + LogError( ( "Could not locate MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + status = EXIT_FAILURE; + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +int tlsSetup( int tcpSocket, + const char * pServerCertPath, + SSL ** pSslContext ) +{ + int status = EXIT_FAILURE, sslStatus = 0; + FILE * pRootCaFile = NULL; + X509 * pRootCa = NULL; + + assert( tcpSocket >= 0 ); + + /* Setup for creating a TLS client. */ + SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); + + if( pSslSetup != NULL ) + { + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. + * The mask returned by SSL_CTX_set_mode does not need to be checked. */ + ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); + + /* OpenSSL does not provide a single function for reading and loading certificates + * from files into stores, so the file API must be called. */ + pRootCaFile = fopen( pServerCertPath, "r" ); + + if( pRootCaFile != NULL ) + { + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + } + else + { + LogError( ( "Unable to find the certificate file in the path" + " provided by SERVER_CERT_PATH(%s).", + pServerCertPath ) ); + } + + if( pRootCa != NULL ) + { + sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), + pRootCa ); + } + else + { + LogError( ( "Failed to parse the server certificate from" + " file %s. Please validate the certificate.", + pServerCertPath ) ); + } + } + + /* Set up the TLS connection. */ + if( sslStatus == 1 ) + { + /* Create a new SSL context. */ + *pSslContext = SSL_new( pSslSetup ); + + if( *pSslContext != NULL ) + { + /* Enable SSL peer verification. */ + SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); + + sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); + } + else + { + LogError( ( "Failed to create a new SSL context." ) ); + sslStatus = 0; + } + + /* Perform the TLS handshake. */ + if( sslStatus == 1 ) + { + sslStatus = SSL_connect( *pSslContext ); + } + else + { + LogError( ( "Failed to set the socket fd to SSL context." ) ); + } + + if( sslStatus == 1 ) + { + if( SSL_get_verify_result( *pSslContext ) != X509_V_OK ) + { + sslStatus = 0; + } + } + else + { + LogError( ( "Failed to perform TLS handshake." ) ); + } + + /* Clean up on error. */ + if( sslStatus == 0 ) + { + SSL_free( *pSslContext ); + *pSslContext = NULL; + } + } + else + { + LogError( ( "Failed to add certificate to store." ) ); + } + + if( pRootCaFile != NULL ) + { + ( void ) fclose( pRootCaFile ); + } + + if( pRootCa != NULL ) + { + X509_free( pRootCa ); + } + + if( pSslSetup != NULL ) + { + SSL_CTX_free( pSslSetup ); + } + + /* Log failure or success and update the correct exit status to return. */ + if( sslStatus == 0 ) + { + LogError( ( "Failed to establish a TLS connection." ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "Established a TLS connection." ) ); + status = EXIT_SUCCESS; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void closeTlsSession( SSL * pSslContext ) +{ + /* Need to call SSL shutdown twice: once to send "close notify" and + * once more to receive the peer's "close notify". */ + if( SSL_shutdown( pSslContext ) == 0 ) + { + ( void ) SSL_shutdown( pSslContext ); + } + + SSL_free( pSslContext ); +} + +/*-----------------------------------------------------------*/ + +void closeTcpConnection( int tcpSocket ) +{ +/* Close TCP connection if established. */ + if( tcpSocket != -1 ) + { + shutdown( tcpSocket, SHUT_RDWR ); + close( tcpSocket ); + } +} + +/*-----------------------------------------------------------*/ + +int32_t transportSend( SSL * pSslContext, + const void * pMessage, + size_t bytesToSend ) +{ + int32_t bytesSent = 0; + int pollStatus = 0; + struct pollfd fileDescriptor = + { + .events = POLLOUT, + .revents = 0 + }; + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pSslContext ); + + /* Poll the file descriptor to check if SSL_Write can be done now. */ + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( pollStatus > 0 ) + { + bytesSent = ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); + LogInfo( ( "Bytes sent over SSL: %d", bytesSent ) ); + } + else if( pollStatus == 0 ) + { + LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); + } + else + { + LogError( ( "Polling of the SSL socket for write buffer availability failed" + " with status %d.", + pollStatus ) ); + bytesSent = -1; + } + + return bytesSent; +} + +/*-----------------------------------------------------------*/ + +int32_t transportRecv( SSL * pSslContext, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + int pollStatus = -1, bytesAvailableToRead = 0; + struct pollfd fileDescriptor = + { + .events = POLLIN | POLLPRI, + .revents = 0 + }; + + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pSslContext ); + + /* Check if there are any pending data available for read. */ + bytesAvailableToRead = SSL_pending( pSslContext ); + + /* Poll only if there is no data available yet to read. */ + if( bytesAvailableToRead <= 0 ) + { + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + } + + /* SSL read of data. */ + if( ( pollStatus > 0 ) || ( bytesAvailableToRead > 0 ) ) + { + bytesReceived = ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); + } + /* Poll timed out. */ + else if( pollStatus == 0 ) + { + LogDebug( ( "Poll timed out and there is no data to read from the buffer." ) ); + } + else + { + LogError( ( "Poll returned with status = %d.", pollStatus ) ); + bytesReceived = -1; + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +uint32_t getTimeMs( void ) +{ + uint32_t timeMs; + struct timespec timeSpec; + + /* Get the MONOTONIC time. */ + clock_gettime( CLOCK_MONOTONIC, &timeSpec ); + + /* Calculate the milliseconds from timespec. */ + timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) + + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); + + return timeMs; +} diff --git a/demos/transport/transport_utils.h b/demos/transport/transport_utils.h new file mode 100644 index 0000000000..4d88eb1943 --- /dev/null +++ b/demos/transport/transport_utils.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file transport_utils.h + * @brief Wrapper utilities for using OpenSSL based TLS stack for demos and + * integration tests. + */ + +#ifndef TRANSPORT_UTILS_H_ +#define TRANSPORT_UTILS_H_ + +/* OpenSSL include. */ +#include + +/** + * @brief Creates a TCP connection to the MQTT broker as specified by + * the @p pServer and @p port parameters, and populate the tcp socket + * descriptor output parameter, if connection is successful. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * @param[out] pTcpSocket The output parameter to return the created socket + * descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +int tcpConnectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ); + +/** + * @brief Closes a TCP connection with a server, if valid. + * + * @param[in] tcpSocket The socket for the TCP connection. + */ +void closeTcpConnection( int tcpSocket ); + +/** + * @brief Set up a TLS connection over an existing TCP connection. + * + * @param[in] tcpSocket Existing TCP connection. + * @param[in] pServerCertPath The absolute file path to the server certificate. + * @param[out] pSslContext The output parameter to return the created SSL context. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +int tlsSetup( int tcpSocket, + const char * pServerCertPath, + SSL ** pSslContext ); + +/** + * @brief Closes an established TLS session. + * + * @param[in] pSSlContext The SSL context associated with the TLS session. + */ +void closeTlsSession( SSL * pSslContext ); + +/** + * @brief The transport send function provided to the MQTT context. + * + * @param[in] pSslContext Pointer to SSL context. + * @param[in] pMessage Data to send. + * @param[in] bytesToSend Length of data to send. + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. + */ +int32_t transportSend( SSL * pSslContext, + const void * pMessage, + size_t bytesToSend ); + +/** + * @brief The transport receive function provided to the MQTT context. + * + * @param[in] pSslContext Pointer to SSL context. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToRecv Length of data to be received. + * + * @return Number of bytes received; negative value on error; + * 0 for timeout. + */ +int32_t transportRecv( SSL * pSslContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief The timer query function provided to the MQTT context. + * + * This function returns the elapsed time with reference to #globalEntryTimeMs. + * + * @return Time in milliseconds. + */ +uint32_t getTimeMs( void ); + +#endif /* ifndef TRANSPORT_UTILS_H_ */ diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 1b4604b292..43a32816ca 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -46,4 +46,4 @@ foreach(module IN LISTS standard_modules ) if(IS_DIRECTORY "${module}" AND EXISTS "${module}/CMakeLists.txt") add_subdirectory(${module}) endif() -endforeach() +endforeach() \ No newline at end of file diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index e968160148..90f5cf3ad0 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -20,4 +20,5 @@ source_group( src\\private FILES src/private/mqtt_internal.h ) if(BUILD_TESTS) add_subdirectory( utest ) + add_subdirectory( integration-test ) endif() diff --git a/libraries/standard/mqtt/integration-test/CMakeLists.txt b/libraries/standard/mqtt/integration-test/CMakeLists.txt new file mode 100644 index 0000000000..343d2cd407 --- /dev/null +++ b/libraries/standard/mqtt/integration-test/CMakeLists.txt @@ -0,0 +1,60 @@ +include("../mqttFilePaths.cmake") +project ("mqtt system test") +cmake_minimum_required (VERSION 3.13) + +# ==================== Define your project name (edit) ======================== +set(project_name "mqtt_system") + +# ================= Create the library under test here (edit) ================== + +# list the files you would like to test here +list(APPEND real_source_files + ${MQTT_SOURCES} + ) +# list the directories the module under test includes +list(APPEND real_include_directories + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + ${MQTT_INCLUDE_PRIVATE_DIRS} + ${LOGGING_INCLUDE_DIRS} + ) + +# ===================== Create UnitTest Code here (edit) ===================== + +# list the directories your test needs to include +list(APPEND test_include_directories + . + ${MQTT_INCLUDE_PUBLIC_DIRS} + ${MQTT_INCLUDE_PRIVATE_DIRS} + ${LOGGING_INCLUDE_DIRS} + # Header path for the TLS server auth utility. + ${ROOT_DIR}/demos/transport + ) + +# ============================= (end edit) =================================== +set(real_name "${project_name}_real") + +create_real_library(${real_name} + "${real_source_files}" + "${real_include_directories}" + # Empty mock name as create_real_library needs the 4th argument. + "" + ) + +list(APPEND stest_link_list + lib${real_name}.a + ) + +list(APPEND stest_dep_list + ${real_name} + transport_utils + ) + +set(stest_name "${project_name}_test") +set(stest_source "${project_name}_test.c") +create_test(${stest_name} + ${stest_source} + "${stest_link_list}" + "${stest_dep_list}" + "${test_include_directories}" + ) diff --git a/libraries/standard/mqtt/integration-test/mosquitto.org.crt b/libraries/standard/mqtt/integration-test/mosquitto.org.crt new file mode 100644 index 0000000000..e76dbd8559 --- /dev/null +++ b/libraries/standard/mqtt/integration-test/mosquitto.org.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL +BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG +A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU +BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv +by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE +BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES +MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp +dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg +UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW +Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA +s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH +3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo +E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT +MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV +6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC +6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf ++pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK +sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 +LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE +m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= +-----END CERTIFICATE----- diff --git a/libraries/standard/mqtt/integration-test/mqtt_config.h b/libraries/standard/mqtt/integration-test/mqtt_config.h new file mode 100644 index 0000000000..af8cd9937c --- /dev/null +++ b/libraries/standard/mqtt/integration-test/mqtt_config.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. + */ + +#include "logging_levels.h" + +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Set network context to OpenSSL SSL context. */ +#include +typedef SSL * NetworkContext_t; + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT 10U + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 + +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c new file mode 100644 index 0000000000..02f8d054dd --- /dev/null +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file mqtt_system_test.c + * @brief Integration tests for the MQTT library when communication with AWS IoT + * from a POSIX platform. + */ + +#include +#include +#include + +/* Include config file before other non-system includes. */ +#include "test_config.h" + +#include "unity.h" +/* Include paths for public enums, structures, and macros. */ +#include "mqtt.h" + +/* Include header for TLS utilities. */ +#include "transport_utils.h" + +#ifndef BROKER_ENDPOINT + #error "BROKER_ENDPOINT should be defined for the MQTT integration tests." +#endif + +#ifndef SERVER_ROOT_CA_CERT_PATH + #error "SERVER_ROOT_CA_CERT_PATH should be defined for the MQTT integration tests." +#endif + +#ifndef CLIENT_IDENTIFIER + #error "CLIENT_IDENTIFIER should be defined for the MQTT integration tests." +#endif + +/** + * @brief A valid starting packet ID per MQTT spec. Start from 1. + */ +#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) + +/** + * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) + +/** + * @brief A packet type not handled by MQTT_ProcessLoop. + */ +#define MQTT_PACKET_TYPE_INVALID ( 0U ) + +/** + * @brief Number of milliseconds in a second. + */ +#define MQTT_ONE_SECOND_TO_MS ( 1000U ) + +/** + * @brief Length of the MQTT network buffer. + */ +#define MQTT_TEST_BUFFER_LENGTH ( 128 ) + +/** + * @brief Sample length of remaining serialized data. + */ +#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) + +/** + * @brief Subtract this value from max value of global entry time + * for the timer overflow test. + */ +#define MQTT_OVERFLOW_OFFSET ( 3 ) + +/** + * @brief Sample topic filter to subscribe to. + */ +#define TEST_MQTT_TOPIC "/iot/integration/test" + +/** + * @brief Length of sample topic filter. + */ +#define TEST_MQTT_TOPIC_LENGTH ( sizeof( TEST_MQTT_TOPIC ) - 1 ) + +/** + * @brief Size of the network buffer for MQTT packets. + */ +#define NETWORK_BUFFER_SIZE ( 1024U ) + +/** + * @brief Client identifier for MQTT session in the tests. + */ +#define TEST_CLIENT_IDENTIFIER "MQTT-Test" + +/** + * @brief Length of the client identifier. + */ +#define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) + +/** + * @brief Timeout for receiving CONNACK packet in milli seconds. + */ +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to + * broker. + */ +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 30U ) + +/** + * @brief Timeout for MQTT_ProcessLoop() function in milliseconds. + * The timeout value is appropriately chosen for receiving an incoming + * PUBLISH message and ack responses for QoS 1 and QoS 2 communications + * with the broker. + */ +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 700U ) + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE "Hello World!" + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker; + * it is used to match received Subscribe ACK to the transmitted subscribe. + */ +static uint16_t globalSubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; + * it is used to match received Unsubscribe ACK to the transmitted unsubscribe + * request. + */ +static uint16_t globalUnsubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Publish request was sent to the broker; + * it is used to match acknowledgement responses to the transmitted publish + * request. + */ +static uint16_t globalPublishPacketIdentifier = 0U; + +/** + * @brief Variable to store TCP socket descriptor used for connecting to and + * disconnecting from the broker. + */ +static int tcpSocket; + +/** + * @brief Represents the OpenSSL context used for TLS session with the broker + * for tests. + */ +static SSL * pSslContext; + +/** + * @brief The context representing the MQTT connection with the broker for + * the test case. + */ +static MQTTContext_t context; + +/** + * @brief Flag that represents whether a persistent session should be + * established with the broker for the test. + */ +static bool persistentSession = false; + +/** + * @brief Flag to represent whether a SUBACK is received from the broker. + */ +static bool receivedSubAck = false; + +/** + * @brief Flag to represent whether an UNSUBACK is received from the broker. + */ +static bool receivedUnsubAck = false; + +/** + * @brief Flag to represent whether a PUBACK is received from the broker. + */ +static bool receivedPubAck = false; + +/** + * @brief Flag to represent whether a PUBREC is received from the broker. + */ +static bool receivedPubRec = false; + +/** + * @brief Flag to represent whether a PUBREL is received from the broker. + */ +static bool receivedPubRel = false; + +/** + * @brief Flag to represent whether a PUBCOMP is received from the broker. + */ +static bool receivedPubComp = false; + +/** + * @brief Represents incoming PUBLISH information. + */ +static MQTTPublishInfo_t incomingInfo; + +/** + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pSslContext SSL context. + * @param[in] createCleanSession Creates a new MQTT session if true. + * If false, tries to establish the existing session if there was session + * already present in broker. + * @param[out] pSessionPresent Session was already present in the broker or not. + * Session present response is obtained from the CONNACK from broker. + */ +static void establishMqttSession( MQTTContext_t * pContext, + SSL * pSslContext, + bool createCleanSession, + bool * pSessionPresent ); + +/** + * @brief The application callback function that is expected to be invoked by the + * MQTT library for incoming publish and incoming acks received over the network. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] packetIdentifier Packet identifier of the incoming packet. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ); + +/*-----------------------------------------------------------*/ + +static void establishMqttSession( MQTTContext_t * pContext, + SSL * pSslContext, + bool createCleanSession, + bool * pSessionPresent ) +{ + MQTTConnectInfo_t connectInfo; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + assert( pContext != NULL ); + assert( pSslContext != NULL ); + + /* The network buffer must remain valid for the lifetime of the MQTT context. */ + static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + + /* Setup the transport interface object for the library. */ + transport.networkContext = pSslContext; + transport.send = transportSend; + transport.recv = transportRecv; + + /* Fill the values for network buffer. */ + networkBuffer.pBuffer = buffer; + networkBuffer.size = NETWORK_BUFFER_SIZE; + + /* Application callbacks for receiving incoming publishes and incoming acks + * from MQTT library. */ + callbacks.appCallback = eventCallback; + + /* Application callback for getting the time for MQTT library. This time + * function will be used to calculate intervals in MQTT library.*/ + callbacks.getTime = getTimeMs; + + /* Initialize MQTT library. */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Init( pContext, + &transport, + &callbacks, + &networkBuffer ) ); + + /* Establish MQTT session with a CONNECT packet. */ + + connectInfo.cleanSession = createCleanSession; + + connectInfo.pClientIdentifier = TEST_CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = TEST_CLIENT_IDENTIFIER_LENGTH; + + /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this test. */ + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0U; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + + /* Send MQTT CONNECT packet to broker. */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Connect( pContext, + &connectInfo, + NULL, + CONNACK_RECV_TIMEOUT_MS, + pSessionPresent ) ); +} + +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pContext != NULL ); + assert( pPacketInfo != NULL ); + + /* Handle incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + assert( pPublishInfo != NULL ); + /* Handle incoming publish. */ + + /* Cache information about the incoming PUBLISH message to process + * in test case. */ + memcpy( &incomingInfo, pPublishInfo, sizeof( MQTTPublishInfo_t ) ); + incomingInfo.pTopicName = NULL; + incomingInfo.pPayload = NULL; + /* Allocate buffers and copy information of topic name and payload. */ + incomingInfo.pTopicName = malloc( pPublishInfo->topicNameLength ); + TEST_ASSERT_NOT_NULL( incomingInfo.pTopicName ); + memcpy( ( void * ) incomingInfo.pTopicName, pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); + incomingInfo.pPayload = malloc( pPublishInfo->payloadLength ); + TEST_ASSERT_NOT_NULL( incomingInfo.pPayload ); + memcpy( ( void * ) incomingInfo.pPayload, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + } + else + { + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + /* Set the flag to represent reception of SUBACK. */ + receivedSubAck = true; + + LogDebug( ( "Received SUBACK: PacketID=%u", + packetIdentifier ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalSubscribePacketIdentifier, packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + /* Set the flag to represent reception of UNSUBACK. */ + receivedUnsubAck = true; + + LogDebug( ( "Received UNSUBACK: PacketID=%u", + packetIdentifier ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalUnsubscribePacketIdentifier, packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PUBACK: + /* Set the flag to represent reception of PUBACK. */ + receivedPubAck = true; + + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); + + LogDebug( ( "Received PUBACK: PacketID=%u", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogDebug( ( "Received PINGRESP" ) ); + break; + + case MQTT_PACKET_TYPE_PUBREC: + /* Set the flag to represent reception of PUBREC. */ + receivedPubRec = true; + + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); + + LogDebug( ( "Received PUBREC: PacketID=%u", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PUBREL: + /* Set the flag to represent reception of PUBREL. */ + receivedPubRel = true; + + /* Nothing to be done from application as library handles + * PUBREL. */ + LogDebug( ( "Received PUBREL: PacketID=%u", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PUBCOMP: + /* Set the flag to represent reception of PUBACK. */ + receivedPubComp = true; + + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); + + /* Nothing to be done from application as library handles + * PUBCOMP. */ + LogDebug( ( "Unexpected PUBCOMP received: PacketID=%u", + packetIdentifier ) ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).", + pPacketInfo->type ) ); + } + } +} + +static MQTTStatus_t subscribeToTopic( MQTTContext_t * pContext, + const char * pTopic, + MQTTQoS_t qos ) +{ + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + pSubscriptionList[ 0 ].qos = qos; + pSubscriptionList[ 0 ].pTopicFilter = pTopic; + pSubscriptionList[ 0 ].topicFilterLength = strlen( pTopic ); + + /* Generate packet identifier for the SUBSCRIBE packet. */ + globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send SUBSCRIBE packet. */ + return MQTT_Subscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalSubscribePacketIdentifier ); +} + +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext, + const char * pTopic, + MQTTQoS_t qos ) +{ + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + pSubscriptionList[ 0 ].qos = qos; + pSubscriptionList[ 0 ].pTopicFilter = pTopic; + pSubscriptionList[ 0 ].topicFilterLength = strlen( pTopic ); + + /* Generate packet identifier for the UNSUBSCRIBE packet. */ + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send UNSUBSCRIBE packet. */ + return MQTT_Unsubscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalUnsubscribePacketIdentifier ); +} + + +static MQTTStatus_t publishToTopic( MQTTContext_t * pContext, + const char * pTopic, + MQTTQoS_t qos ) +{ + assert( pContext != NULL ); + MQTTPublishInfo_t publishInfo; + + /* Set the retain flag to false to avoid side-effects across test runs. */ + publishInfo.retain = false; + + publishInfo.qos = qos; + publishInfo.dup = false; + publishInfo.pTopicName = pTopic; + publishInfo.topicNameLength = strlen( pTopic ); + publishInfo.pPayload = MQTT_EXAMPLE_MESSAGE; + publishInfo.payloadLength = strlen( MQTT_EXAMPLE_MESSAGE ); + + /* Get a new packet id. */ + globalPublishPacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send PUBLISH packet. */ + return MQTT_Publish( pContext, + &publishInfo, + globalPublishPacketIdentifier ); +} + +/* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ +void setUp() +{ + /* Reset file-scoped global variables. */ + receivedSubAck = false; + receivedUnsubAck = false; + receivedPubAck = false; + receivedPubRec = false; + receivedPubRel = false; + receivedPubComp = false; + persistentSession = false; + memset( &incomingInfo, 0u, sizeof( MQTTPublishInfo_t ) ); + + /* Establish a TCP connection with the server endpoint. */ + TEST_ASSERT_EQUAL( EXIT_SUCCESS, + tcpConnectToServer( BROKER_ENDPOINT, BROKER_PORT, &tcpSocket ) ); + TEST_ASSERT_NOT_EQUAL( -1, tcpSocket ); + + /* Establish TLS connection on top of TCP connection. */ + TEST_ASSERT_EQUAL( EXIT_SUCCESS, tlsSetup( tcpSocket, SERVER_ROOT_CA_CERT_PATH, &pSslContext ) ); + TEST_ASSERT_NOT_NULL( pSslContext ); + + /* Establish MQTT session on top of the TCP+TLS connection. */ + establishMqttSession( &context, pSslContext, true, &persistentSession ); +} + +/* Called after each test method. */ +void tearDown() +{ + /* Free memory, if allocated during test case execution. */ + if( incomingInfo.pTopicName != NULL ) + { + free( ( void * ) incomingInfo.pTopicName ); + } + + if( incomingInfo.pPayload != NULL ) + { + free( ( void * ) incomingInfo.pPayload ); + } + + /* Terminate MQTT connection. */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Disconnect( &context ) ); + + /* Terminate TLS session. */ + ( void ) closeTlsSession( pSslContext ); + + /* Terminate TCP connection. */ + ( void ) closeTcpConnection( tcpSocket ); +} + +/* ========================== Test Cases ============================ */ + +/** + * @brief Tests Subscribe and Publish operations with the MQTT broken using QoS 0. + * The test subscribes to a topic, and then publishes to the same topic. The + * broker is expected to route the publish message back to the test. + */ +void test_MQTT_Subscribe_Publish_With_Qos_0( void ) +{ + /* Subscribe to a topic with Qos 0. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); + + /* We expect a SUBACK from the broker for the subscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Publish to the same topic, that we subscribed to, with Qos 0. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); + + /* Call the MQTT library for the expectation to read an incoming PUBLISH for + * the same message that we published (as we have subscribed to the same topic). */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + /* We do not expect a PUBACK from the broker for the QoS 0 PUBLISH. */ + TEST_ASSERT_FALSE( receivedPubAck ); + + /* Make sure that we have received the same message from the server, + * that was published (as we have subscribed to the same topic). */ + TEST_ASSERT_EQUAL( MQTTQoS0, incomingInfo.qos ); + TEST_ASSERT_EQUAL( TEST_MQTT_TOPIC_LENGTH, incomingInfo.topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( TEST_MQTT_TOPIC, + incomingInfo.pTopicName, + TEST_MQTT_TOPIC_LENGTH ); + TEST_ASSERT_EQUAL( strlen( MQTT_EXAMPLE_MESSAGE ), incomingInfo.payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( MQTT_EXAMPLE_MESSAGE, + incomingInfo.pPayload, + incomingInfo.payloadLength ); + + /* Un-subscribe from a topic with Qos 0. */ + TEST_ASSERT_EQUAL( MQTTSuccess, unsubscribeFromTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); + + /* We expect an UNSUBACK from the broker for the unsubscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedUnsubAck ); +} + +/** + * @brief Tests Subscribe and Publish operations with the MQTT broken using QoS 1. + * The test subscribes to a topic, and then publishes to the same topic. The + * broker is expected to route the publish message back to the test. + */ +void test_MQTT_Subscribe_Publish_With_Qos_1( void ) +{ + /* Subscribe to a topic with Qos 1. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); + + /* Expect a SUBACK from the broker for the subscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Publish to the same topic, that we subscribed to, with Qos 1. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); + + /* Make sure that the MQTT context state was updated after the PUBLISH request. */ + TEST_ASSERT_EQUAL( MQTTQoS1, context.outgoingPublishRecords[ 0 ].qos ); + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, context.outgoingPublishRecords[ 0 ].packetId ); + TEST_ASSERT_EQUAL( MQTTPubAckPending, context.outgoingPublishRecords[ 0 ].publishState ); + + /* Expect a PUBACK response for the PUBLISH and an incoming PUBLISH for the + * same message that we published (as we have subscribed to the same topic). */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + /* Make sure we have received PUBACK response. */ + TEST_ASSERT_TRUE( receivedPubAck ); + + /* Make sure that we have received the same message from the server, + * that was published (as we have subscribed to the same topic). */ + TEST_ASSERT_EQUAL( MQTTQoS1, incomingInfo.qos ); + TEST_ASSERT_EQUAL( TEST_MQTT_TOPIC_LENGTH, incomingInfo.topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( TEST_MQTT_TOPIC, + incomingInfo.pTopicName, + TEST_MQTT_TOPIC_LENGTH ); + TEST_ASSERT_EQUAL( strlen( MQTT_EXAMPLE_MESSAGE ), incomingInfo.payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( MQTT_EXAMPLE_MESSAGE, + incomingInfo.pPayload, + incomingInfo.payloadLength ); + + /* Un-subscribe from a topic with Qos 1. */ + TEST_ASSERT_EQUAL( MQTTSuccess, unsubscribeFromTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); + + /* Expect an UNSUBACK from the broker for the unsubscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedUnsubAck ); +} + +/** + * @brief Tests Subscribe and Publish operations with the MQTT broken using QoS 2. + * The test subscribes to a topic, and then publishes to the same topic. The + * broker is expected to route the publish message back to the test. + */ +void test_MQTT_Subscribe_Publish_With_Qos_2( void ) +{ + /* Subscribe to a topic with Qos 2. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + + /* Expect a SUBACK from the broker for the subscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Publish to the same topic, that we subscribed to, with Qos 2. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + + /* Make sure that the MQTT context state was updated after the PUBLISH request. */ + TEST_ASSERT_EQUAL( MQTTQoS2, context.outgoingPublishRecords[ 0 ].qos ); + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, context.outgoingPublishRecords[ 0 ].packetId ); + TEST_ASSERT_EQUAL( MQTTPubRecPending, context.outgoingPublishRecords[ 0 ].publishState ); + + /* We expect PUBREC and PUBCOMP responses for the PUBLISH request, and + * incoming PUBLISH with the same message that we published (as we are subscribed + * to the same topic). Also, we expect a PUBREL ack response from the server for + * the incoming PUBLISH (as we subscribed and publish with QoS 2). */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_FALSE( receivedPubAck ); + TEST_ASSERT_TRUE( receivedPubRec ); + TEST_ASSERT_TRUE( receivedPubComp ); + TEST_ASSERT_TRUE( receivedPubRel ); + + /* Make sure that we have received the same message from the server, + * that was published (as we have subscribed to the same topic). */ + TEST_ASSERT_EQUAL( MQTTQoS2, incomingInfo.qos ); + TEST_ASSERT_EQUAL( TEST_MQTT_TOPIC_LENGTH, incomingInfo.topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( TEST_MQTT_TOPIC, + incomingInfo.pTopicName, + TEST_MQTT_TOPIC_LENGTH ); + TEST_ASSERT_EQUAL( strlen( MQTT_EXAMPLE_MESSAGE ), incomingInfo.payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( MQTT_EXAMPLE_MESSAGE, + incomingInfo.pPayload, + incomingInfo.payloadLength ); + + /* Un-subscribe from a topic with Qos 2. */ + TEST_ASSERT_EQUAL( MQTTSuccess, unsubscribeFromTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + + /* Expect an UNSUBACK from the broker for the unsubscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedUnsubAck ); +} diff --git a/libraries/standard/mqtt/integration-test/test_config.h b/libraries/standard/mqtt/integration-test/test_config.h new file mode 100644 index 0000000000..d8a6e7e3ff --- /dev/null +++ b/libraries/standard/mqtt/integration-test/test_config.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef TEST_CONFIG_H_ +#define TEST_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. + */ + +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "TEST" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief MQTT server host name. + * + * This test uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + * Mosquitto MQTT broker can run locally as an alternate option. Please refer to + * the instructions in https://mosquitto.org/ for running a Mosquitto broker + * locally. + */ +#ifndef BROKER_ENDPOINT + #define BROKER_ENDPOINT "test.mosquitto.org" +#endif + +/** + * @brief Length of MQTT server host name. + */ +#ifndef BROKER_ENDPOINT_LENGTH + #define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) +#endif + +/** + * @brief MQTT server port number. + * + * In general, port 8883 is for secured MQTT connections. + */ +#define BROKER_PORT ( 8883 ) + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate should be PEM-encoded. + */ +#ifndef SERVER_ROOT_CA_CERT_PATH + #error "SERVER_ROOT_CA_CERT_PATH should be defined for MQTT system tests." +#endif + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER "testclient" +#endif + +#endif /* ifndef TEST_CONFIG_H_ */ diff --git a/tools/cmock/create_test.cmake b/tools/cmock/create_test.cmake index 21b02deec4..306782cc03 100644 --- a/tools/cmock/create_test.cmake +++ b/tools/cmock/create_test.cmake @@ -43,8 +43,9 @@ function(create_test test_name # add dependency to all the dep_list parameter foreach(dependency IN LISTS dep_list) add_dependencies(${test_name} ${dependency}) + target_link_libraries(${test_name} ${dependency}) endforeach() - target_link_libraries(${test_name} -lgcov -lunity) + target_link_libraries(${test_name} -lgcov unity) target_link_directories(${test_name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/lib ) From 95045e599842790ba8ae85d846ae181077187957 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 19 Jun 2020 10:40:45 -0700 Subject: [PATCH 549/844] Mqtt mutual auth demo (#986) * MQTT mutual auth demo --- .../mqtt/mqtt_demo_mutual_auth/CMakeLists.txt | 56 + .../certificates/.gitignore | 4 + .../mqtt/mqtt_demo_mutual_auth/demo_config.h | 127 ++ .../mqtt/mqtt_demo_mutual_auth/mqtt_config.h | 66 + .../mqtt_demo_mutual_auth.c | 1629 +++++++++++++++++ 5 files changed, 1882 insertions(+) create mode 100644 demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt create mode 100644 demos/mqtt/mqtt_demo_mutual_auth/certificates/.gitignore create mode 100644 demos/mqtt/mqtt_demo_mutual_auth/demo_config.h create mode 100644 demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h create mode 100644 demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c diff --git a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt new file mode 100644 index 0000000000..bd41bf94ff --- /dev/null +++ b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt @@ -0,0 +1,56 @@ +set( DEMO_NAME "mqtt_demo_mutual_auth" ) +# Demo target. +add_executable(${DEMO_NAME}) + +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL REQUIRED) + +set( CMAKE_THREAD_PREFER_PTHREAD ON ) +find_package(Threads REQUIRED) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + mqtt + OpenSSL::Crypto + OpenSSL::SSL + # SSL uses Threads and on some platforms require explicit linking. + Threads::Threads + # SSL uses Dynamic Loading and on some platforms requires explicit linking. + ${CMAKE_DL_LIBS} +) + +target_include_directories( + mqtt + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) + +# Download the Amazon Root CA certificate. +message( "Downloading the Amazon Root CA certificate..." ) +execute_process( + COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem + -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt +) + +# Copy the certificates and client key to the binary directory. +add_custom_command( + TARGET + ${DEMO_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/certificates" + "$/certificates" +) diff --git a/demos/mqtt/mqtt_demo_mutual_auth/certificates/.gitignore b/demos/mqtt/mqtt_demo_mutual_auth/certificates/.gitignore new file mode 100644 index 0000000000..5e7d2734cf --- /dev/null +++ b/demos/mqtt/mqtt_demo_mutual_auth/certificates/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h new file mode 100644 index 0000000000..cfa08d0a2b --- /dev/null +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H_ +#define DEMO_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + + +/** + * @brief Details of the MQTT broker to connect to. + * + * This is the Thing's Rest API Endpoint for AWS IoT. + * + * @note Your AWS IoT Core endpoint can be found in the AWS IoT console under + * Settings/Custom Endpoint, or using the describe-endpoint API. + * + * #define AWS_IOT_ENDPOINT "...insert here..." + */ + +/** + * @brief AWS IoT MQTT broker port number. + * + * In general, port 8883 is for secured MQTT connections. + * + * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol + * name. When using port 8883, ALPN is not required. + */ +#define AWS_MQTT_PORT ( 8883 ) + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate is used to identify the AWS IoT server and is publicly + * available. Refer to the AWS documentation available in the link below + * https://docs.aws.amazon.com/iot/latest/developerguide/server-authentication.html#server-authentication-certs + * + * Amazon's root CA certificate is automatically downloaded to the certificates + * directory from @ref https://www.amazontrust.com/repository/AmazonRootCA1.pem + * using the CMake build system. + * + * @note This certificate should be PEM-encoded. + * @note This path is relative from the demo binary created. Update + * ROOT_CA_CERT_PATH to the absolute path if this demo is executed from elsewhere. + */ +#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" + +/** + * @brief Path of the file containing the client certificate. + * + * Refer to the AWS documentation below for details regarding client + * authentication. + * https://docs.aws.amazon.com/iot/latest/developerguide/client-authentication.html + * + * @note This certificate should be PEM-encoded. + * + * #define CLIENT_CERT_PATH "...insert here..." + */ + +/** + * @brief Path of the file containing the client's private key. + * + * Refer to the AWS documentation below for details regarding client + * authentication. + * https://docs.aws.amazon.com/iot/latest/developerguide/client-authentication.html + * + * @note This private key should be PEM-encoded. + * + * #define CLIENT_PRIVATE_KEY_PATH "...insert here..." + */ + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "" + + +/** + * @brief Size of the network buffer for MQTT packets. + */ +#define NETWORK_BUFFER_SIZE ( 1024U ) + + +#endif /* ifndef DEMO_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h new file mode 100644 index 0000000000..156e6b1c44 --- /dev/null +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Set network context to OpenSSL SSL context. */ +#include +typedef SSL * MQTTNetworkContext_t; + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) + +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c new file mode 100644 index 0000000000..66ead86607 --- /dev/null +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -0,0 +1,1629 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Demo for showing the use of MQTT APIs to establish an MQTT session, + * subscribe to a topic, publish to a topic, receive incoming publishes, + * unsubscribe from a topic and disconnect the MQTT session. + * + * A mutually authenticated TLS connection is used to connect to the AWS IoT + * MQTT message broker in this example. Define ROOT_CA_CERT_PATH, + * CLIENT_CERT_PATH, and CLIENT_PRIVATE_KEY_PATH in demo_config.h to achieve + * mutual authentication. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS1 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBACK + * are resent in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBACK is received. + */ + +/* Standard includes. */ +#include +#include +#include + +/* POSIX includes. */ +#include +#include +#include +#include + +#include +#include + +/* Demo Config header. */ +#include "demo_config.h" + +/* MQTT API header. */ +#include "mqtt.h" + +/** + * These configuration settings are required to run the mutual auth demo. + * Throw compilation error if the below configs are not defined. + */ +#ifndef AWS_IOT_ENDPOINT + #error "Please define AWS IoT MQTT broker endpoint(AWS_IOT_ENDPOINT) in demo_config.h." +#endif +#ifndef ROOT_CA_CERT_PATH + #error "Please define path to root ca certificate of the AWS IoT MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." +#endif +#ifndef CLIENT_CERT_PATH + #error "Please define path to client certificate(CLIENT_CERT_PATH) in demo_config.h." +#endif +#ifndef CLIENT_PRIVATE_KEY_PATH + #error "Please define path to client private key(CLIENT_PRIVATE_KEY_PATH) in demo_config.h." +#endif + + +/** + * Provide default values for undefined configuration settings. + */ +#ifndef AWS_MQTT_PORT + #define AWS_MQTT_PORT ( 8883 ) +#endif + +#ifndef NETWORK_BUFFER_SIZE + #define NETWORK_BUFFER_SIZE ( 1024U ) +#endif + +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER "testclient" +#endif + +/** + * @brief Length of MQTT server host name. + */ +#define AWS_IOT_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_ENDPOINT ) - 1 ) ) + +/** + * @brief Length of client identifier. + */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) + +/** + * @brief ALPN protocol name for AWS IoT MQTT. + * + * This will be used if the AWS_MQTT_PORT is configured as 443 for AWS IoT MQTT broker. + * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint + * in the link below. + * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ + */ +#define ALPN_PROTOCOL_NAME "\x0ex-amzn-mqtt-ca" + +/** + * @brief Length of ALPN protocol name. + */ +#define ALPN_PROTOCOL_NAME_LENGTH ( ( uint16_t ) ( sizeof( ALPN_PROTOCOL_NAME ) - 1 ) ) + +/** + * @brief Timeout for receiving CONNACK packet in milli seconds. + */ +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief The topic to subscribe and publish to in the example. + * + * The topic name starts with the client identifier to ensure that each demo + * interacts with a unique topic name. + */ +#define MQTT_EXAMPLE_TOPIC CLIENT_IDENTIFIER "/example/topic" + +/** + * @brief Length of client MQTT topic. + */ +#define MQTT_EXAMPLE_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_TOPIC ) - 1 ) ) + +/** + * @brief The MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE "Hello World!" + +/** + * @brief The length of the MQTT message published in this example. + */ +#define MQTT_EXAMPLE_MESSAGE_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_MESSAGE ) - 1 ) ) + +/** + * @brief Maximum number of outgoing publishes maintained in the application + * until an ack is received from the broker. + */ +#define MAX_OUTGOING_PUBLISHES ( 5U ) + +/** + * @brief Invalid packet identifier for the MQTT packets. Zero is always an + * invalid packet identifier as per MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) + +/** + * @brief Timeout for MQTT_ProcessLoop function in milliseconds. + */ +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 500U ) + +/** + * @brief The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. + */ +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) + +/** + * @brief Delay between MQTT publishes in seconds. + */ +#define DELAY_BETWEEN_PUBLISHES_SECONDS ( 1U ) + +/** + * @brief Timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Structure to keep the MQTT publish packets until an ack is received + * for QoS1 publishes. + */ +typedef struct PublishPackets +{ + /** + * @brief Packet identifier of the publish packet. + */ + uint16_t packetId; + + /** + * @brief Publish info of the publish packet. + */ + MQTTPublishInfo_t pubInfo; +} PublishPackets_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief globalEntryTime Entry time into the application to use as a reference + * timestamp in #getTimeMs function. #getTimeMs will always return the difference + * of current time with the globalEntryTime. This will reduce the chances of + * overflow for 32 bit unsigned integer used for holding the timestamp. + */ +static uint32_t globalEntryTimeMs = 0U; + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker; + * it is used to match received Subscribe ACK to the transmitted subscribe. + */ +static uint16_t globalSubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; + * it is used to match received Unsubscribe ACK to the transmitted unsubscribe + * request. + */ +static uint16_t globalUnsubscribePacketIdentifier = 0U; + +/** + * @brief Array to keep the outgoing publish messages. + * These stored outgoing publish messages are kept until a successful ack + * is received. + */ +static PublishPackets_t outgoingPublishPackets[ MAX_OUTGOING_PUBLISHES ] = { 0 }; + +/*-----------------------------------------------------------*/ + +/** + * @brief Creates a TCP connection to the MQTT broker as specified by + * AWS_IOT_ENDPOINT and AWS_MQTT_PORT defined in the demo_config.h file. + * + * @param[in] pServer Host name of server. + * @param[in] port Server port. + * @param[out] pTcpSocket The output parameter to return the created socket + * descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ); + +/** + * @brief Reads credentials from the filesystem. + * + * Uses OpenSSL to import the root CA certificate, client certificate, and + * client certificate private key. + * + * @param[in] pSslContext Destination for the imported credentials. + * @param[in] pRootCaPath Path to the root CA certificate. + * @param[in] pClientCertPath Path to the client certificate. + * @param[in] pCertPrivateKeyPath Path to the client private key. + * + * @return 1 if all credentials were successfully read; 0 otherwise. + */ +static int readCredentials( SSL_CTX * pSslContext, + const char * pRootCaPath, + const char * pClientCertPath, + const char * pCertPrivateKeyPath ); + +/** + * @brief Set up a TLS connection over an existing TCP connection. + * + * @param[in] tcpSocket Socket descriptor corresponding to the existing TCP connection. + * @param[in] pAlpnProtos List of ALPN protocols available to be negotiated. + * @param[in] alpnProtosLen Length of the ALPN protocols list. + * @param[out] pSslContext The output parameter to return the created SSL context. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int tlsSetup( int tcpSocket, + const char * pAlpnProtos, + size_t alpnProtosLen, + SSL ** pSslContext ); + + +/** + * @brief The transport send function provided to the MQTT context. + * + * @param[in] pMQTTNetworkContext Pointer to SSL context. + * @param[in] pMessage Data to send. + * @param[in] bytesToSend Length of data to send. + * + * @return Number of bytes sent; negative value on error; + * 0 for timeout or 0 bytes sent. + */ +static int32_t transportSend( MQTTNetworkContext_t pMQTTNetworkContext, + const void * pMessage, + size_t bytesToSend ); + +/** + * @brief The transport receive function provided to the MQTT context. + * + * @param[in] pMQTTNetworkContext Pointer to SSL context. + * @param[out] pBuffer Buffer for receiving data. + * @param[in] bytesToRecv Length of data to be received. + * + * @return Number of bytes received; negative value on error; + * 0 for timeout. + */ +static int32_t transportRecv( MQTTNetworkContext_t pMQTTNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief The timer query function provided to the MQTT context. + * + * This function returns the elapsed time with reference to #globalEntryTimeMs. + * + * @return Time in milliseconds. + */ +static uint32_t getTimeMs( void ); + +/** + * @brief The function to handle the incoming publishes. + * + * @param[in] pPublishInfo Pointer to publish info of the incoming publish. + * @param[in] packetIdentifier Packet identifier of the incoming publish. + */ +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ); + +/** + * @brief The application callback function for getting the incoming publish + * and incoming acks reported from MQTT library. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] packetIdentifier Packet identifier of the incoming packet. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. + * + * @param[in] pContext MQTT context pointer. + * @param[in] pSslContext SSL context. + * @param[in] createCleanSession Creates a new MQTT session if true. + * If false, tries to establish the existing session if there was session + * already present in broker. + * @param[out] pSessionPresent Session was already present in the broker or not. + * Session present response is obtained from the CONNACK from broker. + * + * @return EXIT_SUCCESS if an MQTT session is established; + * EXIT_FAILURE otherwise. + */ +static int establishMqttSession( MQTTContext_t * pContext, + SSL * pSslContext, + bool createCleanSession, + bool * pSessionPresent ); + +/** + * @brief Close an MQTT session by sending MQTT DISCONNECT. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if DISCONNECT was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int disconnectMqttSession( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT SUBSCRIBE to subscribe to #MQTT_EXAMPLE_TOPIC + * defined at the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int subscribeToTopic( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT UNSUBSCRIBE to unsubscribe from + * #MQTT_EXAMPLE_TOPIC defined at the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ); + +/** + * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at + * the top of the file. + * + * @param[in] pContext MQTT context pointer. + * + * @return EXIT_SUCCESS if PUBLISH was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int publishToTopic( MQTTContext_t * pContext ); + +/** + * @brief Function to get the free index at which an outgoing publish + * can be stored. + * + * @param[out] pIndex The output parameter to return the index at which an + * outgoing publish message can be stored. + * + * @return EXIT_FAILURE if no more publishes can be stored; + * EXIT_SUCCESS if an index to store the next outgoing publish is obtained. + */ +static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ); + +/** + * @brief Function to clean up an outgoing publish at given index from the + * #outgoingPublishPackets array. + * + * @param[in] index The index at which a publish message has to be cleaned up. + */ +static void cleanupOutgoingPublishAt( uint8_t index ); + +/** + * @brief Function to clean up all the outgoing publishes maintained in the + * array. + */ +static void cleanupOutgoingPublishes(); + +/** + * @brief Function to clean up the publish packet with the given packet id. + * + * @param[in] packetId Packet identifier of the packet to be cleaned up from + * the array. + */ +static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ); + +/** + * @brief Function to resend the publishes if a session is re-established with + * the broker. This function handles the resending of the QoS1 publish packets, + * which are maintained locally. + * + * @param[in] pContext MQTT context pointer. + */ +static int handlePublishResend( MQTTContext_t * pContext ); + +/*-----------------------------------------------------------*/ + +static int connectToServer( const char * pServer, + uint16_t port, + int * pTcpSocket ) +{ + int status = EXIT_SUCCESS; + struct addrinfo hints, * pIndex, * pListHead = NULL; + struct sockaddr * pServerInfo; + uint16_t netPort = htons( port ); + socklen_t serverInfoLength; + struct timeval transportTimeout; + + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0x00, sizeof( hints ) ); + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* Perform a DNS lookup on the given host name. */ + status = getaddrinfo( pServer, NULL, &hints, &pListHead ); + + if( status != -1 ) + { + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); + + if( *pTcpSocket == -1 ) + { + continue; + } + + pServerInfo = pIndex->ai_addr; + + if( pServerInfo->sa_family == AF_INET ) + { + /* IPv4 */ + ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in ); + } + else + { + /* IPv6 */ + ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; + serverInfoLength = sizeof( struct sockaddr_in6 ); + } + + status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); + + if( status == -1 ) + { + close( *pTcpSocket ); + } + else + { + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + status = EXIT_FAILURE; + LogError( ( "Located but could not connect to MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + } + else + { + status = EXIT_SUCCESS; + LogInfo( ( "TCP connection established with %.*s.\n\n", + ( int ) strlen( pServer ), + pServer ) ); + } + } + else + { + LogError( ( "Could not locate MQTT broker %.*s.", + ( int ) strlen( pServer ), + pServer ) ); + status = EXIT_FAILURE; + } + + if( pListHead != NULL ) + { + freeaddrinfo( pListHead ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int readCredentials( SSL_CTX * pSslContext, + const char * pRootCaPath, + const char * pClientCertPath, + const char * pCertPrivateKeyPath ) +{ + int sslStatus = 0, fcloseStatus = 0; + X509 * pRootCa = NULL; + FILE * pRootCaFile = NULL; + + /* OpenSSL does not provide a single function for reading and loading certificates + * from files into stores, so the file API must be called. Start with the + * root certificate. */ + if( ( pRootCaPath != NULL ) && + ( strlen( pRootCaPath ) != 0 ) ) + { + LogDebug( ( "Opening root certificate %s.", pRootCaPath ) ); + pRootCaFile = fopen( pRootCaPath, "r" ); + + if( pRootCaFile == NULL ) + { + LogError( ( "Failed to open %s.", pRootCaPath ) ); + } + else + { + /* Read the root CA into an X509 object, then close its file handle. */ + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + + if( pRootCa == NULL ) + { + LogError( ( "Failed to parse root CA." ) ); + } + else + { + sslStatus = 1; + LogDebug( ( "Successfully parsed root CA." ) ); + } + + /* Close the pRootCaFile regardless of whether we succeeded to + * parse the certificate or not. */ + fcloseStatus = fclose( pRootCaFile ); + + if( fcloseStatus != 0 ) + { + LogWarn( ( "Failed to close file %s", pRootCaPath ) ); + } + } + + if( sslStatus == 1 ) + { + /* Add the root CA to certificate store. */ + sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), + pRootCa ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to add root CA to certificate store." ) ); + } + } + } + + if( ( sslStatus == 1 ) && + ( pClientCertPath != NULL ) && + ( strlen( pClientCertPath ) != 0 ) ) + { + /* Import the client certificate. */ + sslStatus = SSL_CTX_use_certificate_chain_file( pSslContext, + pClientCertPath ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to import client certificate at %s.", + pClientCertPath ) ); + } + } + + if( ( sslStatus == 1 ) && + ( pCertPrivateKeyPath != NULL ) && + ( strlen( pCertPrivateKeyPath ) != 0 ) ) + { + /* Import the client certificate private key. */ + sslStatus = SSL_CTX_use_PrivateKey_file( pSslContext, + pCertPrivateKeyPath, + SSL_FILETYPE_PEM ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to import client certificate private key at %s.", + pCertPrivateKeyPath ) ); + } + } + + /* Free the root CA object. */ + if( pRootCa != NULL ) + { + X509_free( pRootCa ); + } + + if( sslStatus == 1 ) + { + LogInfo( ( "Successfully imported server root CA, client certificate and " + "client private key." ) ); + } + + return sslStatus; +} + +/*-----------------------------------------------------------*/ + +static int tlsSetup( int tcpSocket, + const char * pAlpnProtos, + size_t alpnProtosLen, + SSL ** pSslContext ) +{ + int status = EXIT_FAILURE, sslStatus = 0, alpnStatus = -1, sslVerifyStatus = 0; + FILE * pRootCaFile = NULL; + X509 * pRootCa = NULL; + SSL_CTX * pSslSetup = NULL; + + assert( tcpSocket >= 0 ); + + /* Setup for creating a TLS client. */ + pSslSetup = SSL_CTX_new( TLS_client_method() ); + + if( pSslSetup != NULL ) + { + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. + * The mask returned by SSL_CTX_set_mode does not need to be checked. */ + ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); + + /* Setup authentication. */ + sslStatus = readCredentials( pSslSetup, + ROOT_CA_CERT_PATH, + CLIENT_CERT_PATH, + CLIENT_PRIVATE_KEY_PATH ); + + if( sslStatus != 1 ) + { + LogError( ( "Setting up credentials failed." ) ); + } + } + else + { + LogError( ( "Failed setting up a TLS client." ) ); + } + + /* Set up the TLS connection. */ + if( sslStatus == 1 ) + { + /* Create a new SSL context. */ + *pSslContext = SSL_new( pSslSetup ); + + if( *pSslContext != NULL ) + { + /* Enable SSL peer verification. */ + SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); + sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to set the socket fd to SSL context." ) ); + } + } + else + { + LogError( ( "Failed to create a new SSL context." ) ); + sslStatus = 0; + } + } + + if( ( sslStatus == 1 ) && + ( pAlpnProtos != NULL ) && ( alpnProtosLen > 0 ) ) + { + sslStatus = -1; + + if( AWS_MQTT_PORT != 443 ) + { + LogError( ( "AWS_MQTT_PORT must be set to 443 to use ALPN." ) ); + } + else + { + alpnStatus = SSL_set_alpn_protos( *pSslContext, + ( unsigned char * ) pAlpnProtos, + ( unsigned int ) alpnProtosLen ); + } + + if( alpnStatus != 0 ) + { + LogError( ( "SSL_set_alpn_protos failed to set ALPN protos. %s", + pAlpnProtos ) ); + } + else + { + sslStatus = 1; + } + } + + /* Perform the TLS handshake. */ + if( sslStatus == 1 ) + { + sslStatus = SSL_connect( *pSslContext ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to perform TLS handshake." ) ); + } + } + + /* Verify the peer certificate. */ + if( sslStatus == 1 ) + { + sslVerifyStatus = SSL_get_verify_result( *pSslContext ); + + if( sslVerifyStatus != X509_V_OK ) + { + sslStatus = 0; + LogError( ( "Verifying the peer certificate failed. " ) ); + } + } + + /* Clean up on error. */ + if( ( sslStatus == 0 ) && ( pSslSetup != NULL ) ) + { + SSL_free( *pSslContext ); + *pSslContext = NULL; + } + + if( pRootCaFile != NULL ) + { + ( void ) fclose( pRootCaFile ); + } + + if( pRootCa != NULL ) + { + X509_free( pRootCa ); + } + + if( pSslSetup != NULL ) + { + SSL_CTX_free( pSslSetup ); + } + + /* Log failure or success and update the correct exit status to return. */ + if( sslStatus != 1 ) + { + LogError( ( "Failed to establish a TLS connection to %.*s.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "Established a TLS connection to %.*s.\n\n", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT ) ); + status = EXIT_SUCCESS; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int32_t transportSend( MQTTNetworkContext_t pMQTTNetworkContext, + const void * pMessage, + size_t bytesToSend ) +{ + int32_t bytesSent = 0; + int pollStatus = 0; + struct pollfd fileDescriptor; + + fileDescriptor.events = POLLOUT; + fileDescriptor.revents = 0; + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pMQTTNetworkContext ); + + /* Poll the file descriptor to check if SSL_Write can be done now. */ + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( pollStatus > 0 ) + { + bytesSent = ( int32_t ) SSL_write( pMQTTNetworkContext, pMessage, bytesToSend ); + } + else if( pollStatus == 0 ) + { + LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); + } + else + { + LogError( ( "Polling of the SSL socket for write buffer availability failed" + " with status %d.", + pollStatus ) ); + bytesSent = -1; + } + + return bytesSent; +} + +/*-----------------------------------------------------------*/ + +static int32_t transportRecv( MQTTNetworkContext_t pMQTTNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + int pollStatus = -1, bytesAvailableToRead = 0; + struct pollfd fileDescriptor; + + fileDescriptor.events = POLLIN | POLLPRI; + fileDescriptor.revents = 0; + /* Set the file descriptor for poll. */ + fileDescriptor.fd = SSL_get_fd( pMQTTNetworkContext ); + + /* Check if there are any pending data available for read. */ + bytesAvailableToRead = SSL_pending( pMQTTNetworkContext ); + + /* Poll only if there is no data available yet to read. */ + if( bytesAvailableToRead <= 0 ) + { + pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); + } + + /* Read data if it was already pending or if it became available during + * polling. */ + if( ( bytesAvailableToRead > 0 ) || ( pollStatus > 0 ) ) + { + bytesReceived = ( int32_t ) SSL_read( pMQTTNetworkContext, pBuffer, bytesToRecv ); + } + /* Poll timed out. */ + else if( pollStatus == 0 ) + { + LogDebug( ( "Poll timed out and there is no data to read from the buffer." ) ); + } + else + { + LogError( ( "Poll returned with status = %d.", pollStatus ) ); + bytesReceived = -1; + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +static uint32_t getTimeMs( void ) +{ + uint32_t timeMs; + struct timespec timeSpec; + + /* Get the MONOTONIC time. */ + clock_gettime( CLOCK_MONOTONIC, &timeSpec ); + + /* Calculate the milliseconds from timespec. */ + timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) + + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); + + /* Reduce globalEntryTime from obtained time so as to always return the + * elapsed time in the application. */ + timeMs = ( uint32_t ) ( timeMs - globalEntryTimeMs ); + + return timeMs; +} + +/*-----------------------------------------------------------*/ + +static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) +{ + int status = EXIT_FAILURE; + int index = 0; + + assert( outgoingPublishPackets != NULL ); + assert( pIndex != NULL ); + + for( index = 0; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + /* A free index is marked by invalid packet id. + * Check if the the index has a free slot. */ + if( outgoingPublishPackets[ index ].packetId == MQTT_PACKET_ID_INVALID ) + { + status = EXIT_SUCCESS; + break; + } + } + + /* Copy the available index into the output param. */ + *pIndex = index; + + return status; +} +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishAt( uint8_t index ) +{ + assert( outgoingPublishPackets != NULL ); + assert( index < MAX_OUTGOING_PUBLISHES ); + + /* Clear the outgoing publish packet. */ + ( void ) memset( &( outgoingPublishPackets[ index ] ), + 0x00, + sizeof( outgoingPublishPackets[ index ] ) ); +} + +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishes() +{ + assert( outgoingPublishPackets != NULL ); + + /* Clean up all the outgoing publish packets. */ + ( void ) memset( outgoingPublishPackets, 0x00, sizeof( outgoingPublishPackets ) ); +} + +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ) +{ + uint8_t index = 0; + + assert( outgoingPublishPackets != NULL ); + assert( packetId != MQTT_PACKET_ID_INVALID ); + + /* Clean up all the saved outgoing publishes. */ + for( ; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + if( outgoingPublishPackets[ index ].packetId == packetId ) + { + cleanupOutgoingPublishAt( index ); + LogInfo( ( "Cleaned up outgoing publish packet with packet id %u.\n\n", + packetId ) ); + break; + } + } +} + +/*-----------------------------------------------------------*/ + +static int handlePublishResend( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; + uint8_t index = 0U; + + assert( outgoingPublishPackets != NULL ); + + /* Resend all the QoS1 publishes still in the array. These are the + * publishes that hasn't received a PUBACK. When a PUBACK is + * received, the publish is removed from the array. */ + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + if( outgoingPublishPackets[ index ].packetId != MQTT_PACKET_ID_INVALID ) + { + outgoingPublishPackets[ index ].pubInfo.dup = true; + + LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", + outgoingPublishPackets[ index ].packetId ) ); + mqttStatus = MQTT_Publish( pContext, + &outgoingPublishPackets[ index ].pubInfo, + outgoingPublishPackets[ index ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending duplicate PUBLISH for packet id %u " + " failed with status %u.", + outgoingPublishPackets[ index ].packetId, + mqttStatus ) ); + status = EXIT_FAILURE; + break; + } + else + { + LogInfo( ( "Sent duplicate PUBLISH successfully for packet id %u.\n\n", + outgoingPublishPackets[ index ].packetId ) ); + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ) +{ + assert( pPublishInfo != NULL ); + + /* Process incoming Publish. */ + LogInfo( ( "Incoming QOS : %d.", pPublishInfo->qos ) ); + + /* Verify the received publish is for the topic we have subscribed to. */ + if( ( pPublishInfo->topicNameLength == MQTT_EXAMPLE_TOPIC_LENGTH ) && + ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ) ) ) + { + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n" + "Incoming Publish message Packet Id is %u.\n" + "Incoming Publish Message : %.*s.\n\n", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName, + packetIdentifier, + ( int ) pPublishInfo->payloadLength, + ( const char * ) pPublishInfo->pPayload ) ); + } + else + { + LogInfo( ( "Incoming Publish Topic Name: %.*s does not match subscribed topic.", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + } +} + +/*-----------------------------------------------------------*/ + +static void eventCallback( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pContext != NULL ); + assert( pPacketInfo != NULL ); + + /* Handle incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + assert( pPublishInfo != NULL ); + /* Handle incoming publish. */ + handleIncomingPublish( pPublishInfo, packetIdentifier ); + } + else + { + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + LogInfo( ( "Subscribed to the topic %.*s.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( globalSubscribePacketIdentifier == packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + LogInfo( ( "Unsubscribed from the topic %.*s.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( globalUnsubscribePacketIdentifier == packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogInfo( ( "PINGRESP received.\n\n" ) ); + break; + + case MQTT_PACKET_TYPE_PUBACK: + LogInfo( ( "PUBACK received for packet id %u.", + packetIdentifier ) ); + /* Cleanup publish packet when a PUBACK is received. */ + cleanupOutgoingPublishWithPacketID( packetIdentifier ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).", + pPacketInfo->type ) ); + } + } +} + +/*-----------------------------------------------------------*/ + +static int establishMqttSession( MQTTContext_t * pContext, + SSL * pSslContext, + bool createCleanSession, + bool * pSessionPresent ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTConnectInfo_t connectInfo; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + + assert( pContext != NULL ); + assert( pSslContext != NULL ); + + /* The network buffer must remain valid for the lifetime of the MQTT context. */ + static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + + /* Fill in TransportInterface send and receive function pointers. + * For this demo, TCP sockets are used to send and receive data + * from network. Network context is SSL context.*/ + transport.networkContext = pSslContext; + transport.send = transportSend; + transport.recv = transportRecv; + + /* Fill the values for network buffer. */ + networkBuffer.pBuffer = buffer; + networkBuffer.size = NETWORK_BUFFER_SIZE; + + /* Application callbacks for receiving incoming publishes and incoming acks + * from MQTT library. */ + callbacks.appCallback = eventCallback; + + /* Application callback for getting the time for MQTT library. This time + * function will be used to calculate intervals in MQTT library.*/ + callbacks.getTime = getTimeMs; + + /* Initialize MQTT library. */ + mqttStatus = MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); + } + else + { + /* Establish MQTT session by sending a CONNECT packet. */ + + /* If #createCleanSession is true, start with a clean session + * i.e. direct the MQTT broker to discard any previous session data. + * If #createCleanSession is false, directs the broker to attempt to + * reestablish a session which was already present. */ + connectInfo.cleanSession = createCleanSession; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + /* The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this demo. */ + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0U; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + + /* Send MQTT CONNECT packet to broker. */ + mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + } + else + { + LogInfo( ( "MQTT connection successfully established with broker.\n\n" ) ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int disconnectMqttSession( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + + assert( pContext != NULL ); + + /* Send DISCONNECT. */ + MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int subscribeToTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to only one topic and uses QOS1. */ + pSubscriptionList[ 0 ].qos = MQTTQoS1; + pSubscriptionList[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; + + /* Generate packet identifier for the SUBSCRIBE packet. */ + globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send SUBSCRIBE packet. */ + mqttStatus = MQTT_Subscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalSubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "SUBSCRIBE sent for topic %.*s to broker.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to and unsubscribes from only one topic + * and uses QOS1. */ + pSubscriptionList[ 0 ].qos = MQTTQoS1; + pSubscriptionList[ 0 ].pTopicFilter = MQTT_EXAMPLE_TOPIC; + pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; + + /* Generate packet identifier for the UNSUBSCRIBE packet. */ + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + + /* Send UNSUBSCRIBE packet. */ + mqttStatus = MQTT_Unsubscribe( pContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalUnsubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "UNSUBSCRIBE sent for topic %.*s to broker.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static int publishToTopic( MQTTContext_t * pContext ) +{ + int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + uint8_t publishIndex = MAX_OUTGOING_PUBLISHES; + + assert( pContext != NULL ); + + /* Get the next free index for the outgoing publish. All QoS1 outgoing + * publishes are stored until a PUBACK is received. These messages are + * stored for supporting a resend if a network connection is broken before + * receiving a PUBACK. */ + status = getNextFreeIndexForOutgoingPublishes( &publishIndex ); + + if( status == EXIT_FAILURE ) + { + LogError( ( "Unable to find a free spot for outgoing PUBLISH message.\n\n" ) ); + } + else + { + /* This example publishes to only one topic and uses QOS1. */ + outgoingPublishPackets[ publishIndex ].pubInfo.qos = MQTTQoS1; + outgoingPublishPackets[ publishIndex ].pubInfo.pTopicName = MQTT_EXAMPLE_TOPIC; + outgoingPublishPackets[ publishIndex ].pubInfo.topicNameLength = MQTT_EXAMPLE_TOPIC_LENGTH; + outgoingPublishPackets[ publishIndex ].pubInfo.pPayload = MQTT_EXAMPLE_MESSAGE; + outgoingPublishPackets[ publishIndex ].pubInfo.payloadLength = MQTT_EXAMPLE_MESSAGE_LENGTH; + + /* Get a new packet id. */ + outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pContext ); + + /* Send PUBLISH packet. */ + mqttStatus = MQTT_Publish( pContext, + &outgoingPublishPackets[ publishIndex ].pubInfo, + outgoingPublishPackets[ publishIndex ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", + mqttStatus ) ); + cleanupOutgoingPublishAt( publishIndex ); + status = EXIT_FAILURE; + } + else + { + LogInfo( ( "PUBLISH send for topic %.*s to broker with packet id %u.\n\n", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC, + outgoingPublishPackets[ publishIndex ].packetId ) ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TLS connection established using openSSL. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS1 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBACK + * are resent in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBACK is received. + */ +int main( int argc, + char ** argv ) +{ + int status = EXIT_SUCCESS, tcpSocket; + bool sessionPresent, mqttSessionEstablished = false; + MQTTContext_t context; + MQTTStatus_t mqttStatus = MQTTSuccess; + SSL * pSslContext = NULL; + uint32_t publishCount = 0; + const uint32_t maxPublishCount = 5U; + + ( void ) argc; + ( void ) argv; + + /* Get the entry time to application. */ + globalEntryTimeMs = getTimeMs(); + + /* Establish a TCP connection with the MQTT broker. This example connects + * to the MQTT broker as specified in AWS_IOT_ENDPOINT and AWS_MQTT_PORT in + * the demo_config.h file. */ + LogInfo( ( "Creating a TCP connection to %.*s:%d.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT, + AWS_MQTT_PORT ) ); + status = connectToServer( AWS_IOT_ENDPOINT, AWS_MQTT_PORT, &tcpSocket ); + + /* Establish TLS connection on top of TCP connection. */ + if( status == EXIT_SUCCESS ) + { + LogInfo( ( "Performing TLS handshake on top of the TCP connection." ) ); + + if( AWS_MQTT_PORT == 443 ) + { + /* Pass the ALPN protocol name depending on the port being used. + * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint + * in the link below. + * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ + */ + status = tlsSetup( tcpSocket, + ALPN_PROTOCOL_NAME, + ALPN_PROTOCOL_NAME_LENGTH, + &pSslContext ); + } + else if( AWS_MQTT_PORT == 8883 ) + { + status = tlsSetup( tcpSocket, + NULL, + 0, + &pSslContext ); + } + else + { + LogError( ( "AWS IoT Core does not support MQTT through port %d", + AWS_MQTT_PORT ) ); + status = EXIT_FAILURE; + } + } + + /* Establish MQTT session on top of TCP+TLS connection. */ + if( status == EXIT_SUCCESS ) + { + /* Sends an MQTT Connect packet over the already connected TCP socket + * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT ) ); + status = establishMqttSession( &context, pSslContext, false, &sessionPresent ); + + if( status == EXIT_SUCCESS ) + { + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be sent at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; + } + } + + if( status == EXIT_SUCCESS ) + { + /* Check if session is present and if there are any outgoing publishes + * that need to resend. This is only valid if the broker is + * re-establishing a session which was already present. */ + if( sessionPresent == true ) + { + LogInfo( ( "An MQTT session with broker is re-established. " + "Resending unacked publishes." ) ); + + /* Handle all the resend of publish messages. */ + status = handlePublishResend( &context ); + } + else + { + LogInfo( ( "A clean MQTT connection is established." + " Cleaning up all the stored outgoing publishes.\n\n" ) ); + + /* Clean up the outgoing publishes waiting for ack as this new + * connection doesn't re-establish an existing session. */ + cleanupOutgoingPublishes(); + } + } + + if( status == EXIT_SUCCESS ) + { + /* The client is now connected to the broker. Subscribe to the topic + * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a + * subscribe packet. This client will then publish to the same topic it + * subscribed to, so it will expect all the messages it sends to the broker + * to be sent back to it from the broker. This demo uses QOS1 in Subscribe, + * therefore, the Publish messages received from the broker will have QOS1. */ + LogInfo( ( "Subscribing to the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = subscribeToTopic( &context ); + } + + if( status == EXIT_SUCCESS ) + { + /* Process incoming packet from the broker. Acknowledgment for subscription + * ( SUBACK ) will be received here. However after sending the subscribe, the + * client may receive a publish before it receives a subscribe ack. Since this + * demo is subscribing to the topic to which no one is publishing, probability + * of receiving publish message before subscribe ack is zero; but application + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * receive packet from network. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + } + + if( status == EXIT_SUCCESS ) + { + /* Publish messages with QOS1, receive incoming messages and + * send keep alive messages. */ + for( publishCount = 0; publishCount < maxPublishCount; publishCount++ ) + { + LogInfo( ( "Sending Publish to the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = publishToTopic( &context ); + + /* Calling MQTT_ProcessLoop to process incoming publish echo, since + * application subscribed to the same topic the broker will send + * publish message back to the application. This function also + * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS + * has expired since the last MQTT packet sent and receive + * ping responses. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + LogWarn( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + + LogInfo( ( "Delay before continuing to next iteration.\n\n" ) ); + + /* Leave connection idle for some time. */ + sleep( DELAY_BETWEEN_PUBLISHES_SECONDS ); + } + } + + if( status == EXIT_SUCCESS ) + { + /* Unsubscribe from the topic. */ + LogInfo( ( "Unsubscribing from the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + status = unsubscribeFromTopic( &context ); + } + + if( status == EXIT_SUCCESS ) + { + /* Process Incoming UNSUBACK packet from the broker. */ + mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + status = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + } + + /* Send an MQTT Disconnect packet over the already connected TCP socket. + * There is no corresponding response for the disconnect packet. After sending + * disconnect, client must close the network connection. */ + if( mqttSessionEstablished == true ) + { + if( status == EXIT_FAILURE ) + { + /* Returned status is not used to update the local status as there + * were failures in demo execution. */ + ( void ) disconnectMqttSession( &context ); + } + else + { + status = disconnectMqttSession( &context ); + } + } + + /* Close TLS session if established. */ + if( pSslContext != NULL ) + { + /* SSL shutdown should be called twice: once to send "close notify" and + * once more to receive the peer's "close notify". */ + if( SSL_shutdown( pSslContext ) == 0 ) + { + ( void ) SSL_shutdown( pSslContext ); + } + + SSL_free( pSslContext ); + } + + /* Close TCP connection if established. */ + if( tcpSocket != -1 ) + { + shutdown( tcpSocket, SHUT_RDWR ); + close( tcpSocket ); + } + + /* Log the success message. */ + if( status == EXIT_SUCCESS ) + { + LogInfo( ( "Demo completed successfully." ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ From 50b09c193cfa1e7a3bce8dbd68a7a0e79cd06499 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 22 Jun 2020 13:32:51 -0700 Subject: [PATCH 550/844] Use NetWorkContext_t in mutual auth demo (#1008) --- .../mqtt/mqtt_demo_mutual_auth/mqtt_config.h | 2 +- .../mqtt_demo_mutual_auth.c | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h index 156e6b1c44..969a386029 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h @@ -49,7 +49,7 @@ /* Set network context to OpenSSL SSL context. */ #include -typedef SSL * MQTTNetworkContext_t; +typedef SSL * NetworkContext_t; /** * @brief The maximum number of MQTT PUBLISH messages that may be pending diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 66ead86607..184d9d9d20 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -285,28 +285,28 @@ static int tlsSetup( int tcpSocket, /** * @brief The transport send function provided to the MQTT context. * - * @param[in] pMQTTNetworkContext Pointer to SSL context. + * @param[in] pNetworkContext Pointer to SSL context. * @param[in] pMessage Data to send. * @param[in] bytesToSend Length of data to send. * * @return Number of bytes sent; negative value on error; * 0 for timeout or 0 bytes sent. */ -static int32_t transportSend( MQTTNetworkContext_t pMQTTNetworkContext, +static int32_t transportSend( NetworkContext_t pNetworkContext, const void * pMessage, size_t bytesToSend ); /** * @brief The transport receive function provided to the MQTT context. * - * @param[in] pMQTTNetworkContext Pointer to SSL context. + * @param[in] pNetworkContext Pointer to SSL context. * @param[out] pBuffer Buffer for receiving data. * @param[in] bytesToRecv Length of data to be received. * * @return Number of bytes received; negative value on error; * 0 for timeout. */ -static int32_t transportRecv( MQTTNetworkContext_t pMQTTNetworkContext, +static int32_t transportRecv( NetworkContext_t pNetworkContext, void * pBuffer, size_t bytesToRecv ); @@ -807,7 +807,7 @@ static int tlsSetup( int tcpSocket, /*-----------------------------------------------------------*/ -static int32_t transportSend( MQTTNetworkContext_t pMQTTNetworkContext, +static int32_t transportSend( NetworkContext_t pNetworkContext, const void * pMessage, size_t bytesToSend ) { @@ -818,14 +818,14 @@ static int32_t transportSend( MQTTNetworkContext_t pMQTTNetworkContext, fileDescriptor.events = POLLOUT; fileDescriptor.revents = 0; /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pMQTTNetworkContext ); + fileDescriptor.fd = SSL_get_fd( pNetworkContext ); /* Poll the file descriptor to check if SSL_Write can be done now. */ pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); if( pollStatus > 0 ) { - bytesSent = ( int32_t ) SSL_write( pMQTTNetworkContext, pMessage, bytesToSend ); + bytesSent = ( int32_t ) SSL_write( pNetworkContext, pMessage, bytesToSend ); } else if( pollStatus == 0 ) { @@ -844,7 +844,7 @@ static int32_t transportSend( MQTTNetworkContext_t pMQTTNetworkContext, /*-----------------------------------------------------------*/ -static int32_t transportRecv( MQTTNetworkContext_t pMQTTNetworkContext, +static int32_t transportRecv( NetworkContext_t pNetworkContext, void * pBuffer, size_t bytesToRecv ) { @@ -855,10 +855,10 @@ static int32_t transportRecv( MQTTNetworkContext_t pMQTTNetworkContext, fileDescriptor.events = POLLIN | POLLPRI; fileDescriptor.revents = 0; /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pMQTTNetworkContext ); + fileDescriptor.fd = SSL_get_fd( pNetworkContext ); /* Check if there are any pending data available for read. */ - bytesAvailableToRead = SSL_pending( pMQTTNetworkContext ); + bytesAvailableToRead = SSL_pending( pNetworkContext ); /* Poll only if there is no data available yet to read. */ if( bytesAvailableToRead <= 0 ) @@ -870,7 +870,7 @@ static int32_t transportRecv( MQTTNetworkContext_t pMQTTNetworkContext, * polling. */ if( ( bytesAvailableToRead > 0 ) || ( pollStatus > 0 ) ) { - bytesReceived = ( int32_t ) SSL_read( pMQTTNetworkContext, pBuffer, bytesToRecv ); + bytesReceived = ( int32_t ) SSL_read( pNetworkContext, pBuffer, bytesToRecv ); } /* Poll timed out. */ else if( pollStatus == 0 ) From 5ae7acf671529df176a227c6f5676e7320ac01f4 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Mon, 22 Jun 2020 18:32:20 -0700 Subject: [PATCH 551/844] Verify minimum version required for OpenSSL and update README (#1003) * Update minimum version required for OpenSSL and update README * Add or later to fatal error message * Match on 0.9 or 1.0 instead * Update mutual auth demo as well --- README.md | 4 ++-- demos/http/http_demo_basic_tls/CMakeLists.txt | 5 +++++ demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 5 +++++ demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt | 5 +++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8c8cc0fbda..c305c497d6 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ This branch currently hosts development of the next iteration of the AWS IoT Emb This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. ### Prerequisites -- CMake 3.5.0 or later and a C90 compiler. +- CMake 3.13.0 or later and a C90 compiler. - A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. - - On Linux systems, installation of OpenSSL development libraries and header files, *version 1.0.2g or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + - On Linux systems, installation of OpenSSL development libraries and header files, *version 1.1.0 or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. ### Build Steps 1. Go to the root directory of this repository. diff --git a/demos/http/http_demo_basic_tls/CMakeLists.txt b/demos/http/http_demo_basic_tls/CMakeLists.txt index 7e38915eff..874eebd610 100644 --- a/demos/http/http_demo_basic_tls/CMakeLists.txt +++ b/demos/http/http_demo_basic_tls/CMakeLists.txt @@ -5,6 +5,11 @@ add_executable(${DEMO_NAME}) set(OPENSSL_USE_STATIC_LIBS TRUE) find_package(OpenSSL REQUIRED) +# Verify the minimum OpenSSL version required +if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) + message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) +endif() + set( CMAKE_THREAD_PREFER_PTHREAD ON ) find_package(Threads REQUIRED) diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt index 3e8ad422ec..ae8e7ad85b 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -5,6 +5,11 @@ add_executable(${DEMO_NAME}) set(OPENSSL_USE_STATIC_LIBS TRUE) find_package(OpenSSL REQUIRED) +# Verify the minimum OpenSSL version required +if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) + message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) +endif() + set( CMAKE_THREAD_PREFER_PTHREAD ON ) find_package(Threads REQUIRED) diff --git a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt index bd41bf94ff..4f38d25670 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt @@ -5,6 +5,11 @@ add_executable(${DEMO_NAME}) set(OPENSSL_USE_STATIC_LIBS TRUE) find_package(OpenSSL REQUIRED) +# Verify the minimum OpenSSL version required +if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) + message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) +endif() + set( CMAKE_THREAD_PREFER_PTHREAD ON ) find_package(Threads REQUIRED) From 22c5b3459fbd79e4240135e73576e28ce89af415 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 25 Jun 2020 10:25:48 -0700 Subject: [PATCH 552/844] Small checks for MQTT_GetPacketId and MQTT_GetIncomingPacketTypeAndLength (#1014) * MQTT library updates for intentional overflow check and null parameter check. --- .../standard/mqtt/include/mqtt_lightweight.h | 5 ++-- libraries/standard/mqtt/src/mqtt.c | 7 +++-- .../standard/mqtt/src/mqtt_lightweight.c | 26 +++++++++++++++---- .../mqtt/utest/mqtt_lightweight_utest.c | 5 ++++ 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 15ed663bf3..e52b0790fe 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -508,13 +508,14 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, bool * pSessionPresent ); /** - * @brief Extract MQTT packet type and length from incoming packet. + * @brief Extract the MQTT packet type and length from incoming packet. * * @param[in] readFunc Transport layer read function pointer. - * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. + * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. This is * where type, remaining length and packet identifier are stored. * * @return #MQTTSuccess on successful extraction of type and length, + * #MQTTBadParameter if @p pIncomingPacket is invalid, * #MQTTRecvFailed on transport receive failure, * #MQTTBadResponse if an invalid packet is read, and * #MQTTNoDataAvailable if there is nothing to read. diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 4f2074aeb0..d386c074c7 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1468,9 +1468,12 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) if( pContext != NULL ) { packetId = pContext->nextPacketId; - pContext->nextPacketId++; - if( pContext->nextPacketId == 0U ) + if( pContext->nextPacketId == UINT16_MAX ) + { + pContext->nextPacketId = 1; + } + else { pContext->nextPacketId++; } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 9096ebafbc..8070e11792 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -235,7 +235,6 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. * @param[out] pBuffer Buffer for packet serialization. - * */ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, @@ -2046,8 +2045,18 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTSuccess; - /* Read a single byte. */ - int32_t bytesReceived = readFunc( networkContext, &( pIncomingPacket->type ), 1U ); + int32_t bytesReceived = 0; + + if( pIncomingPacket == NULL ) + { + LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* Read a single byte. */ + bytesReceived = readFunc( networkContext, &( pIncomingPacket->type ), 1U ); + } if( bytesReceived == 1 ) { @@ -2068,14 +2077,21 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu status = MQTTBadResponse; } } - else if( bytesReceived == 0 ) + else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) ) { status = MQTTNoDataAvailable; } - else + + /* If the input packet was valid, then any other number of bytes received is + * a failure. */ + else if( status != MQTTBadParameter ) { status = MQTTRecvFailed; } + else + { + /* Empty else MISRA 15.7 */ + } return status; } diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 552430bf55..2483de5bc3 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -1577,6 +1577,11 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) /* Dummy network context - pointer to pointer to a buffer. */ NetworkContext_t networkContext = ( NetworkContext_t ) &bufPtr; + /* Test a NULL pIncomingPacket parameter. */ + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Test a typical happy path case for a CONN ACK packet. */ buffer[ 0 ] = 0x20; /* CONN ACK */ buffer[ 1 ] = 0x02; /* Remaining length. */ From e59a0fb3fe05886c537f64d3dd243bea2f8d5415 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 25 Jun 2020 11:28:39 -0700 Subject: [PATCH 553/844] Add MQTT Receive Loop without keep alive (#990) * Add MQTT Receive Loop without keep alive * Add unit test for receive loop * Enforce presence of getTime function --- libraries/standard/mqtt/include/mqtt.h | 29 +- libraries/standard/mqtt/src/mqtt.c | 303 +++++++++++++++------ libraries/standard/mqtt/utest/mqtt_utest.c | 134 ++++++++- 3 files changed, 379 insertions(+), 87 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index aafed71d25..f4c215ca8c 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -125,6 +125,11 @@ struct MQTTContext * @brief Initialize an MQTT context. * * This function must be called on an MQTT context before any other function. + * + * @note The getTime callback function must be defined. If there is no time + * implementation, it is the responsibility of the application to provide a + * dummy function to always return 0, and provide 0 timeouts for functions. This + * will ensure all time based functions will run for a single iteration. * * @brief param[in] pContext The context to initialize. * @brief param[in] pTransportInterface The transport interface to use with the context. @@ -247,7 +252,8 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); /** - * @brief Loop to receive packets from the transport interface. + * @brief Loop to receive packets from the transport interface. Handles keep + * alive. * * @param[in] pContext Initialized and connected MQTT context. * @param[in] timeoutMs Minimum time in milliseconds that the receive loop will @@ -266,6 +272,27 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t timeoutMs ); +/** + * @brief Loop to receive packets from the transport interface. Does not handle + * keep alive. + * + * @note Passing a timeout value of 0 will run the loop for a single iteration. + * + * @param[in] pContext Initialized and connected MQTT context. + * @param[in] timeoutMs Minimum time in milliseconds that the receive loop will + * run, unless an error occurs. + * + * @return #MQTTBadParameter if context is NULL; + * #MQTTRecvFailed if a network error occurs during reception; + * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; + * #MQTTBadResponse if an invalid packet is received; + * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an + * invalid transition for the internal state machine; + * #MQTTSuccess on success. + */ +MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, + uint32_t timeoutMs ); + /** * @brief Get a packet ID that is valid according to the MQTT 3.1.1 spec. * diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index d386c074c7..e04f814ca7 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -154,11 +154,34 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, * * @param[in] pContext MQTT Connection context. * @param[in] pIncomingPacket Incoming packet. + * @param[in] manageKeepAlive Flag indicating if PINGRESPs should not be given + * to the application * * @return MQTTSuccess, MQTTIllegalState, or deserialization error. */ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, - MQTTPacketInfo_t * pIncomingPacket ); + MQTTPacketInfo_t * pIncomingPacket, + bool manageKeepAlive ); + +/** + * @brief Run a single iteration of the receive loop. + * + * @param[in] pContext MQTT Connection context. + * @param[in] remainingTimeMs Remaining time for the loop in milliseconds. + * @param[in] manageKeepAlive Flag indicating if keep alive should be handled. + * + * @return #MQTTRecvFailed if a network error occurs during reception; + * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; + * #MQTTBadResponse if an invalid packet is received; + * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before + * pContext->pingRespTimeoutMs milliseconds; + * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an + * invalid transition for the internal state machine; + * #MQTTSuccess on success. + */ +static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext, + uint32_t remainingTimeMs, + bool manageKeepAlive ); /** * @brief Validates parameters of #MQTT_Subscribe or #MQTT_Unsubscribe. @@ -221,10 +244,12 @@ static int32_t sendPacket( MQTTContext_t * pContext, uint32_t sendTime = 0U; assert( pContext != NULL ); + assert( pContext->callbacks.getTime != NULL ); + + bytesRemaining = bytesToSend; /* Record the time of transmission. */ sendTime = pContext->callbacks.getTime(); - bytesRemaining = bytesToSend; /* Loop until the entire packet is sent. */ while( bytesRemaining > 0UL ) @@ -252,7 +277,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, } } - /* Update time of last transmission if the entire packet was successfully sent. */ + /* Update time of last transmission if the entire packet is successfully sent. */ if( totalBytesSent > 0 ) { pContext->lastPacketTime = sendTime; @@ -313,16 +338,18 @@ static int32_t recvExact( const MQTTContext_t * pContext, uint8_t * pIndex = NULL; size_t bytesRemaining = bytesToRecv; int32_t totalBytesRecvd = 0, bytesRecvd; - uint32_t entryTimeMs = 0U; + uint32_t entryTimeMs = 0U, elapsedTimeMs = 0U; MQTTTransportRecvFunc_t recvFunc = NULL; MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; bool receiveError = false; assert( pContext != NULL ); assert( bytesToRecv <= pContext->networkBuffer.size ); + assert( pContext->callbacks.getTime != NULL ); pIndex = pContext->networkBuffer.pBuffer; recvFunc = pContext->transportInterface.recv; getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); while( ( bytesRemaining > 0U ) && ( receiveError == false ) ) @@ -345,8 +372,9 @@ static int32_t recvExact( const MQTTContext_t * pContext, receiveError = true; } - if( ( bytesRemaining > 0U ) && - ( calculateElapsedTime( getTimeStampMs(), entryTimeMs ) > timeoutMs ) ) + elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + + if( ( bytesRemaining > 0U ) && ( elapsedTimeMs >= timeoutMs ) ) { LogError( ( "Time expired while receiving packet." ) ); receiveError = true; @@ -365,14 +393,16 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, MQTTStatus_t status = MQTTRecvFailed; int32_t bytesReceived = 0; size_t bytesToReceive = 0U; - uint32_t totalBytesReceived = 0U, entryTimeMs, elapsedTimeMs; + uint32_t totalBytesReceived = 0U, entryTimeMs = 0U, elapsedTimeMs = 0U; uint32_t remainingTimeMs = timeoutMs; MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; bool receiveError = false; assert( pContext != NULL ); + assert( pContext->callbacks.getTime != NULL ); bytesToReceive = pContext->networkBuffer.size; getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); while( ( totalBytesReceived < remainingLength ) && ( receiveError == false ) ) @@ -395,9 +425,10 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, else { totalBytesReceived += ( uint32_t ) bytesReceived; - /* Update remaining time and check for timeout. */ + elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + /* Update remaining time and check for timeout. */ if( elapsedTimeMs < timeoutMs ) { remainingTimeMs = timeoutMs - elapsedTimeMs; @@ -641,15 +672,23 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, - MQTTPacketInfo_t * pIncomingPacket ) + MQTTPacketInfo_t * pIncomingPacket, + bool manageKeepAlive ) { MQTTStatus_t status = MQTTBadResponse; MQTTPublishState_t publishRecordState = MQTTStateNull; uint16_t packetIdentifier; /* Need a dummy variable for MQTT_DeserializeAck(). */ bool sessionPresent = false; + + /* We should always invoke the app callback unless we receive a PINGRESP + * and are managing keep alive, or if we receive an unknown packet. We + * initialize this to false since the callback must be invoked before + * sending any PUBREL or PUBCOMP. However, for other cases, we invoke it + * at the end to reduce the complexity of this function. */ + bool invokeAppCallback = false; MQTTPubAckType_t ackType; - MQTTEventCallback_t appCallback; + MQTTEventCallback_t appCallback = NULL; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); @@ -692,12 +731,12 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, break; case MQTT_PACKET_TYPE_PINGRESP: - pContext->waitingForPingResp = false; status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); + invokeAppCallback = ( manageKeepAlive ) ? false : true; - if( status == MQTTSuccess ) + if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) ) { - appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); + pContext->waitingForPingResp = false; } break; @@ -706,12 +745,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_UNSUBACK: /* Deserialize and give these to the app provided callback. */ status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); - - if( status == MQTTSuccess ) - { - appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); - } - + invokeAppCallback = true; break; default: @@ -722,6 +756,81 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, break; } + if( ( status == MQTTSuccess ) && ( invokeAppCallback == true ) ) + { + appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext, + uint32_t remainingTimeMs, + bool manageKeepAlive ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTPacketInfo_t incomingPacket; + + assert( pContext != NULL ); + + status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, + pContext->transportInterface.networkContext, + &incomingPacket ); + + if( status == MQTTNoDataAvailable ) + { + if( manageKeepAlive == true ) + { + /* Assign status so an error can be bubbled up to application, + * but reset it on success. */ + status = handleKeepAlive( pContext ); + } + + if( status == MQTTSuccess ) + { + /* Reset the status to indicate that we should not try to read + * a packet from the transport interface. */ + status = MQTTNoDataAvailable; + } + } + else if( status != MQTTSuccess ) + { + LogError( ( "Receiving incoming packet length failed. Status=%s", + MQTT_Status_strerror( status ) ) ); + } + else + { + /* Receive packet. Remaining time is recalculated before calling this + * function. */ + status = receivePacket( pContext, incomingPacket, remainingTimeMs ); + } + + /* Handle received packet. If no data was read then this will not execute. */ + if( status == MQTTSuccess ) + { + incomingPacket.pRemainingData = pContext->networkBuffer.pBuffer; + + /* PUBLISH packets allow flags in the lower four bits. For other + * packet types, they are reserved. */ + if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + status = handleIncomingPublish( pContext, &incomingPacket ); + } + else + { + status = handleIncomingAck( pContext, &incomingPacket, manageKeepAlive ); + } + } + + if( status == MQTTNoDataAvailable ) + { + /* No data available is not an error. Reset to MQTTSuccess so the + * return code will indicate success. */ + status = MQTTSuccess; + } + return status; } @@ -818,7 +927,11 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, { MQTTStatus_t status = MQTTSuccess; MQTTGetCurrentTimeFunc_t getTimeStamp = NULL; - uint32_t entryTimeMs = 0U, remainingTimeMs = 0U, timeTakenMs = 0U; + uint32_t entryTimeMs = 0U, remainingTimeMs = 0U; + /* Initialize time taken to the timeout in case a time function is not provided. */ + uint32_t timeTakenMs = timeoutMs; + /* Initialize loop exit condition to true in case a time function is not provided. */ + bool timeExpired = true; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); @@ -838,9 +951,10 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, pContext->transportInterface.networkContext, pIncomingPacket ); + timeExpired = ( calculateElapsedTime( getTimeStamp(), entryTimeMs ) >= timeoutMs ) ? true : false; + /* Loop until there is data to read or if the timeout has not expired. */ - } while( ( status == MQTTNoDataAvailable ) && - ( calculateElapsedTime( getTimeStamp(), entryTimeMs ) < timeoutMs ) ); + } while( ( status == MQTTNoDataAvailable ) && ( timeExpired != true ) ); if( status == MQTTSuccess ) { @@ -885,8 +999,8 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, if( status != MQTTSuccess ) { - LogError( ( "CONNACK recv failed with status = %u.", - status ) ); + LogError( ( "CONNACK recv failed with status = %s.", + MQTT_Status_strerror( status ) ) ); } else { @@ -910,8 +1024,8 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, ( pCallbacks == NULL ) || ( pNetworkBuffer == NULL ) ) { LogError( ( "Argument cannot be NULL: pContext=%p, " - "pTransportInterface=%p " - "pCallbacks=%p " + "pTransportInterface=%p, " + "pCallbacks=%p, " "pNetworkBuffer=%p.", pContext, pTransportInterface, @@ -919,6 +1033,16 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, pNetworkBuffer ) ); status = MQTTBadParameter; } + else if( ( pCallbacks->getTime == NULL ) || ( pCallbacks->appCallback == NULL ) || + ( pTransportInterface->recv == NULL ) || ( pTransportInterface->send == NULL ) ) + { + LogError( ( "Functions cannot be NULL: getTime=%p, appCallback=%p, recv=%p, send=%p.", + pCallbacks->getTime, + pCallbacks->appCallback, + pTransportInterface->recv, + pTransportInterface->send ) ); + status = MQTTBadParameter; + } else { ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) ); @@ -1014,8 +1138,8 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, } else { - LogError( ( "MQTT connection failed with status = %u.", - status ) ); + LogError( ( "MQTT connection failed with status = %s.", + MQTT_Status_strerror( status ) ) ); } return status; @@ -1170,19 +1294,19 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, if( status != MQTTSuccess ) { - LogError( ( "Update state for publish failed with status =%u." + LogError( ( "Update state for publish failed with status =%s." " However PUBLISH packet is sent to the broker." " Any further handling of ACKs for the packet Id" " will fail.", - status ) ); + MQTT_Status_strerror( status ) ) ); } } } if( status != MQTTSuccess ) { - LogError( ( "MQTT PUBLISH failed with status=%u.", - status ) ); + LogError( ( "MQTT PUBLISH failed with status=%s.", + MQTT_Status_strerror( status ) ) ); } return status; @@ -1374,86 +1498,99 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; MQTTPacketInfo_t incomingPacket; - if( pContext != NULL ) + if( ( pContext != NULL ) && ( pContext->callbacks.getTime != NULL ) ) { getTimeStampMs = pContext->callbacks.getTime; entryTimeMs = getTimeStampMs(); status = MQTTSuccess; pContext->controlPacketSent = false; } - else + else if( pContext == NULL ) { LogError( ( "MQTT Context cannot be NULL." ) ); } + else + { + LogError( ( "MQTT Context must set callbacks.getTime." ) ); + } while( status == MQTTSuccess ) { - status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, - pContext->transportInterface.networkContext, - &incomingPacket ); + status = receiveSingleIteration( pContext, remainingTimeMs, true ); - if( status == MQTTNoDataAvailable ) - { - /* Assign status so an error can be bubbled up to application, - * but reset it on success. */ - status = handleKeepAlive( pContext ); - - if( status == MQTTSuccess ) - { - /* Reset the status to indicate that we should not try to read - * a packet from the transport interface. */ - status = MQTTNoDataAvailable; - } - } - else if( status != MQTTSuccess ) + /* We don't need to break here since the status is already checked in + * the loop condition, and we do not want multiple breaks in a loop. */ + if( status != MQTTSuccess ) { - LogError( ( "Receiving incoming packet length failed. Status=%d", - status ) ); + LogError( ( "Exiting process loop. Error status=%s", + MQTT_Status_strerror( status ) ) ); } else { - /* Receive packet. Remaining time is recalculated at the end of the loop. */ - status = receivePacket( pContext, incomingPacket, remainingTimeMs ); - } - - /* Handle received packet. If no data was read then this will not execute. */ - if( status == MQTTSuccess ) - { - incomingPacket.pRemainingData = pContext->networkBuffer.pBuffer; + /* Recalculate remaining time and check if loop should exit. This is + * done at the end so the loop will run at least a single iteration. */ + elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); - /* PUBLISH packets allow flags in the lower four bits. For other - * packet types, they are reserved. */ - if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) - { - status = handleIncomingPublish( pContext, &incomingPacket ); - } - else + if( elapsedTimeMs > timeoutMs ) { - status = handleIncomingAck( pContext, &incomingPacket ); + break; } - } - if( status == MQTTNoDataAvailable ) - { - /* No data available is not an error. Reset to MQTTSuccess so the - * return code will indicate success. */ - status = MQTTSuccess; + remainingTimeMs = timeoutMs - elapsedTimeMs; } + } + return status; +} + +/*-----------------------------------------------------------*/ + +MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, + uint32_t timeoutMs ) +{ + MQTTStatus_t status = MQTTBadParameter; + MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; + uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; + + if( ( pContext != NULL ) && ( pContext->callbacks.getTime != NULL ) ) + { + getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); + status = MQTTSuccess; + } + else if( pContext == NULL ) + { + LogError( ( "MQTT Context cannot be NULL." ) ); + } + else + { + LogError( ( "MQTT Context must set callbacks.getTime." ) ); + } + + while( status == MQTTSuccess ) + { + status = receiveSingleIteration( pContext, remainingTimeMs, false ); + + /* We don't need to break here since the status is already checked in + * the loop condition, and we do not want multiple breaks in a loop. */ if( status != MQTTSuccess ) { - LogError( ( "Exiting receive loop. Error status=%d", status ) ); + LogError( ( "Exiting receive loop. Error status=%s", + MQTT_Status_strerror( status ) ) ); } + else + { + /* Recalculate remaining time and check if loop should exit. This is + * done at the end so the loop will run at least a single iteration. */ + elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); - /* Recalculate remaining time and check if loop should exit. */ - elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + if( elapsedTimeMs >= timeoutMs ) + { + break; + } - if( elapsedTimeMs > timeoutMs ) - { - break; + remainingTimeMs = timeoutMs - elapsedTimeMs; } - - remainingTimeMs = timeoutMs - elapsedTimeMs; } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 8b0c942f9f..a255020e18 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -65,7 +65,7 @@ * @brief Subtract this value from max value of global entry time * for the timer overflow test. */ -#define MQTT_TIMER_CALLS_PER_ITERATION ( 3 ) +#define MQTT_TIMER_CALLS_PER_ITERATION ( 4 ) /** * @brief Timeout for the timer overflow test. @@ -203,6 +203,15 @@ static uint32_t getTime( void ) return globalEntryTime++; } +/** + * @brief A mocked timer function that could be used on a device with no system + * time. + */ +static uint32_t getTimeDummy( void ) +{ + return 0; +} + /** * @brief Mocked successful transport send. * @@ -484,6 +493,8 @@ void test_MQTT_Init_Happy_Path( void ) MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + setupCallbacks( &callbacks ); + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); @@ -505,6 +516,9 @@ void test_MQTT_Init_Invalid_Params( void ) MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + setupCallbacks( &callbacks ); + setupTransportInterface( &transport ); + /* Check that MQTTBadParameter is returned if any NULL parameters are passed. */ mqttStatus = MQTT_Init( NULL, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); @@ -517,6 +531,26 @@ void test_MQTT_Init_Invalid_Params( void ) mqttStatus = MQTT_Init( &context, &transport, &callbacks, NULL ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Test if NULL is passed for any callbacks. */ + callbacks.getTime = NULL; + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + callbacks.appCallback = NULL; + callbacks.getTime = getTime; + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + callbacks.appCallback = eventCallback; + transport.recv = NULL; + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + transport.recv = transportRecvSuccess; + transport.send = NULL; + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); } /* ========================================================================== */ @@ -590,6 +624,8 @@ void test_MQTT_Connect_sendConnect( void ) MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize ); MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + /* We know the send was successful if MQTT_GetIncomingPacketTypeAndLength() + * is called. */ MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); @@ -686,7 +722,8 @@ void test_MQTT_Connect_partial_receive() incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; incomingPacket.remainingLength = 2; - /* Not enough time to receive entire packet, for branch coverage. */ + /* Not enough time to receive entire packet, for branch coverage. This is due + * to the fact the mocked receive function reads only one byte at a time. */ timeout = 1; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); @@ -703,7 +740,18 @@ void test_MQTT_Connect_partial_receive() status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status ); + /* Discard packet, no timeout provided. This should fail since multiple + * iterations of the discard loop are required to discard the packet, but only + * one will run. */ + mqttContext.transportInterface.recv = transportRecvSuccess; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + /* Timeout while discarding packet. */ + /* (Mocked) read only one byte at a time to ensure timeout will occur. */ + mqttContext.transportInterface.recv = transportRecvOneByte; incomingPacket.remainingLength = 20; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); @@ -712,9 +760,11 @@ void test_MQTT_Connect_partial_receive() /* Receive failure while discarding packet. */ mqttContext.transportInterface.recv = transportRecvFailure; + /* Test with dummy get time function to make sure there are no infinite loops. */ + mqttContext.callbacks.getTime = getTimeDummy; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); - status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, MQTT_NO_TIMEOUT_MS, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); } @@ -955,9 +1005,23 @@ void test_MQTT_GetPacketId( void ) */ void test_MQTT_ProcessLoop_Invalid_Params( void ) { + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks = { 0 }; MQTTStatus_t mqttStatus = MQTT_ProcessLoop( NULL, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + /* Get time function cannot be NULL. */ + context.callbacks.getTime = NULL; + mqttStatus = MQTT_ProcessLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); } /** @@ -1295,6 +1359,7 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) MQTTPublishState_t ackState = MQTTPublishDone; uint8_t i = 0; uint8_t numIterations = ( MQTT_TIMER_OVERFLOW_TIMEOUT_MS / MQTT_TIMER_CALLS_PER_ITERATION ) + 1; + uint32_t expectedFinalTime; setupTransportInterface( &transport ); setupCallbacks( &callbacks ); @@ -1304,6 +1369,7 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; globalEntryTime = UINT32_MAX - MQTT_OVERFLOW_OFFSET; + expectedFinalTime = MQTT_TIMER_CALLS_PER_ITERATION * numIterations - MQTT_OVERFLOW_OFFSET; mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); @@ -1324,6 +1390,68 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) mqttStatus = MQTT_ProcessLoop( &context, MQTT_TIMER_OVERFLOW_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + TEST_ASSERT_EQUAL( expectedFinalTime, globalEntryTime ); +} + +/* ========================================================================== */ + +/** + * @brief Test that MQTT_ReceiveLoop() works as intended. Since the only difference + * between this and the process loop is keep alive, we only need to test the + * differences for coverage. + */ +void test_MQTT_ReceiveLoop( void ) +{ + MQTTStatus_t mqttStatus; + MQTTContext_t context; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks = { 0 }; + MQTTPacketInfo_t incomingPacket = { 0 }; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + + mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + + /* NULL Context. */ + mqttStatus = MQTT_ReceiveLoop( NULL, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + context.callbacks.getTime = NULL; + mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + context.callbacks.getTime = getTime; + + /* Error case, for branch coverage. */ + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); + mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus ); + + /* Keep Alive should not trigger.*/ + context.keepAliveIntervalSec = 1; + MQTT_GetIncomingPacketTypeAndLength_IgnoreAndReturn( MQTTNoDataAvailable ); + mqttStatus = MQTT_ReceiveLoop( &context, 2000 ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + TEST_ASSERT_FALSE( context.controlPacketSent ); + + /* Test with a dummy getTime to ensure there's no infinite loops. */ + context.callbacks.getTime = getTimeDummy; + mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); + context.callbacks.getTime = getTime; + + MQTT_GetIncomingPacketTypeAndLength_StopIgnore(); + + /* Receive a PINGRESP. */ + incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP; + incomingPacket.remainingLength = 0U; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); } /* ========================================================================== */ From 550550fa3fe0f97fd9bb482166bd57fe4f949d53 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 25 Jun 2020 13:19:06 -0700 Subject: [PATCH 554/844] CBMC Proofs for MQTT_Init, MQTT_GetPacketId, and MQTT_GetIncomingPacketTypeAndLength (#1005) + CBMC proof infrastructure. --- .gitignore | 8 +++- .gitmodules | 3 ++ docs/{utesting.md => utest.md} | 0 .../standard/mqtt/cbmc/include/README.md | 1 + .../mqtt/cbmc/include/mqtt_cbmc_state.h | 28 +++++++++++ .../cbmc/include/network_interface_stubs.h | 32 +++++++++++++ ...T_GetIncomingPacketTypeAndLength_harness.c | 46 +++++++++++++++++++ .../Makefile | 14 ++++++ .../README.md | 10 ++++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 +++ .../MQTT_GetPacketId_harness.c | 39 ++++++++++++++++ .../cbmc/proofs/MQTT_GetPacketId/Makefile | 14 ++++++ .../cbmc/proofs/MQTT_GetPacketId/README.md | 10 ++++ .../proofs/MQTT_GetPacketId/cbmc-batch.yaml | 2 + .../proofs/MQTT_GetPacketId/cbmc-viewer.json | 7 +++ .../cbmc/proofs/MQTT_Init/MQTT_Init_harness.c | 40 ++++++++++++++++ .../mqtt/cbmc/proofs/MQTT_Init/Makefile | 13 ++++++ .../mqtt/cbmc/proofs/MQTT_Init/README.md | 10 ++++ .../cbmc/proofs/MQTT_Init/cbmc-batch.yaml | 2 + .../cbmc/proofs/MQTT_Init/cbmc-viewer.json | 7 +++ .../mqtt/cbmc/proofs/Makefile-project-defines | 29 ++++++++++++ .../mqtt/cbmc/proofs/Makefile-project-targets | 1 + .../mqtt/cbmc/proofs/Makefile-project-testing | 1 + .../cbmc/proofs/Makefile-template-defines | 1 + .../standard/mqtt/cbmc/proofs/Makefile.common | 1 + libraries/standard/mqtt/cbmc/proofs/README.md | 1 + .../standard/mqtt/cbmc/proofs/prepare.py | 1 + .../standard/mqtt/cbmc/sources/README.md | 1 + .../mqtt/cbmc/sources/mqtt_cbmc_state.c | 30 ++++++++++++ libraries/standard/mqtt/cbmc/stubs/README.md | 1 + .../mqtt/cbmc/stubs/network_interface_stubs.c | 46 +++++++++++++++++++ tools/aws-templates-for-cbmc-proofs | 1 + 33 files changed, 408 insertions(+), 1 deletion(-) rename docs/{utesting.md => utest.md} (100%) create mode 120000 libraries/standard/mqtt/cbmc/include/README.md create mode 100644 libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h create mode 100644 libraries/standard/mqtt/cbmc/include/network_interface_stubs.h create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/MQTT_GetPacketId_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines create mode 120000 libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets create mode 120000 libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing create mode 100644 libraries/standard/mqtt/cbmc/proofs/Makefile-template-defines create mode 120000 libraries/standard/mqtt/cbmc/proofs/Makefile.common create mode 120000 libraries/standard/mqtt/cbmc/proofs/README.md create mode 120000 libraries/standard/mqtt/cbmc/proofs/prepare.py create mode 120000 libraries/standard/mqtt/cbmc/sources/README.md create mode 100644 libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c create mode 120000 libraries/standard/mqtt/cbmc/stubs/README.md create mode 100644 libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c create mode 160000 tools/aws-templates-for-cbmc-proofs diff --git a/.gitignore b/.gitignore index ffb79c4814..f907ea4b43 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,16 @@ doc/tag/* # Ignore CMake build directory. build/ -#Ignore build artifacts +# Ignore build artifacts *.o # Ignore code coverage artifacts *.gcda *.gcno *.gcov + +# Ignore CBMC proof artifacts +**/cbmc/proofs/**/gotos +**/cbmc/proofs/**/html +**/cbmc/proofs/**/logs +**/cbmc/proofs/**/TAGS-* diff --git a/.gitmodules b/.gitmodules index 75b7619c25..bbe58d7cb4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "libraries/3rdparty/CMock"] path = libraries/3rdparty/CMock url = https://github.com/ThrowTheSwitch/CMock +[submodule "tools/aws-templates-for-cbmc-proofs"] + path = tools/aws-templates-for-cbmc-proofs + url = https://github.com/awslabs/aws-templates-for-cbmc-proofs.git diff --git a/docs/utesting.md b/docs/utest.md similarity index 100% rename from docs/utesting.md rename to docs/utest.md diff --git a/libraries/standard/mqtt/cbmc/include/README.md b/libraries/standard/mqtt/cbmc/include/README.md new file mode 120000 index 0000000000..b655718b06 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/include/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/include/README.md \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h new file mode 100644 index 0000000000..09d2f00289 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @brief Proof model for malloc that can fail and return NULL. + * + * @param[in] size The size in bytes of memory to allocate. + * @return NULL or requested memory. + */ +void * mallocCanFail( size_t size ); diff --git a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h new file mode 100644 index 0000000000..e7428989b5 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @brief Application defined network interface receive function. + * + * @param[in] context Application defined network interface context. + * @param[out] pBuffer MQTT network receive buffer. + * @param[in] bytesToRecv MQTT requested bytes. + * @return Any value from INT32_MIN to INT32_MAX. + */ +int32_t NetworkInterfaceReceiveStub( NetworkContext_t context, + void * pBuffer, + size_t bytesToRecv ); diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c new file mode 100644 index 0000000000..88ace53c42 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_GetIncomingPacketTypeAndLength_harness.c + * @brief Implements the proof harness for MQTT_GetIncomingPacketTypeAndLength function. + */ +#include "mqtt.h" +#include "network_interface_stubs.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + /* NetworkContext_t is an application defined network interface context. It + * is passed through to the readFunc parameter of + * MQTT_GetIncomingPacketTypeAndLength(). */ + NetworkContext_t networkContext; + + __CPROVER_assume( networkContext != NULL ); + + /* MQTT_GetIncomingPacketTypeAndLength() will set only the remainingLength + * field in the input MQTTPacketInfo_t structure. */ + MQTTPacketInfo_t * pIncomingPacketInfo = mallocCanFail( sizeof( MQTTPacketInfo_t ) ); + + MQTT_GetIncomingPacketTypeAndLength( NetworkInterfaceReceiveStub, + networkContext, + pIncomingPacketInfo ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile new file mode 100644 index 0000000000..2a9425b049 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile @@ -0,0 +1,14 @@ +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_GetIncomingPacketTypeAndLength_harness + +DEFINES += +INCLUDES += $(PROOFDIR)/../../include +REMOVE_FUNCTION_BODY += +UNWINDSET += getRemainingLength.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(PROOFDIR)/../../stubs/network_interface_stubs.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md new file mode 100644 index 0000000000..4211034166 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md @@ -0,0 +1,10 @@ +MQTT_GetIncomingPacketTypeAndLength proof +============== + +This directory contains a memory safety proof for MQTT_GetIncomingPacketTypeAndLength. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-viewer.json new file mode 100644 index 0000000000..80ed87a59d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_GetIncomingPacketTypeAndLength", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/MQTT_GetPacketId_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/MQTT_GetPacketId_harness.c new file mode 100644 index 0000000000..25cdec94c0 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/MQTT_GetPacketId_harness.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_GetPacketId_harness.c + * @brief Implements the proof harness for MQTT_GetPacketId function. + */ + +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + /* The MQTTContext_t is allocated such that we can test a NULL input. + * MQTT_GetPacketId() touches only the nextPacketId field in MQTTContext_t. + * This nextPacketId is left unbounded to verify the function under harness. + */ + MQTTContext_t * pContext = mallocCanFail( sizeof( MQTTContext_t ) ); + + MQTT_GetPacketId( pContext ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile new file mode 100644 index 0000000000..274540f23c --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile @@ -0,0 +1,14 @@ +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_GetPacketId_harness + +DEFINES += +INCLUDES += $(PROOFDIR)/../../include + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md new file mode 100644 index 0000000000..036a70518a --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md @@ -0,0 +1,10 @@ +MQTT_GetPacketId proof +============== + +This directory contains a memory safety proof for MQTT_GetPacketId. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-viewer.json new file mode 100644 index 0000000000..fdd2429cd0 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_GetPacketId", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c new file mode 100644 index 0000000000..11f2b3c7a9 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Init_harness.c + * @brief Implements the proof harness for MQTT_Init function. + */ + +#include "mqtt.h" + +void harness() +{ + MQTTContext_t context; + MQTTTransportInterface_t transportInterface; + MQTTApplicationCallbacks_t callbacks; + MQTTFixedBuffer_t networkBuffer; + + MQTT_Init( nondet_bool() ? NULL : &context, + nondet_bool() ? NULL : &transportInterface, + nondet_bool() ? NULL : &callbacks, + nondet_bool() ? NULL : &networkBuffer ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile new file mode 100644 index 0000000000..5ca3da21f4 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile @@ -0,0 +1,13 @@ +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Init_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md new file mode 100644 index 0000000000..754f91b513 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md @@ -0,0 +1,10 @@ +MQTT_Init proof +============== + +This directory contains a memory safety proof for MQTT_Init. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-viewer.json new file mode 100644 index 0000000000..8c1d680c85 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Init", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines new file mode 100644 index 0000000000..c3c0088a1d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines @@ -0,0 +1,29 @@ +# -*- mode: makefile -*- +# The first line sets the emacs major mode to Makefile + +################################################################ +# Use this file to give project-specific definitions of the command +# line arguments to pass to CBMC tools like goto-cc to build the goto +# binaries and cbmc to do the property and coverage checking. +# +# Use this file to override most default definitions of variables in +# Makefile.common. +################################################################ + +# Flags to pass to goto-cc for compilation (typically those passed to gcc -c) +COMPILE_FLAGS += -fPIC +COMPILE_FLAGS += -std=gnu90 + +# Flags to pass to goto-cc for linking (typically those passed to gcc) +LINK_FLAGS = + +# Preprocessor include paths -I... +INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/cbmc/include +INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/include +INCLUDES += -I$(SRCDIR)/libraries/standard/utilities/include +INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/src +INCLUDES += -I$(SRCDIR)/demos/logging-stack +INCLUDES += -I$(SRCDIR)/demos/mqtt/mqtt_demo_plaintext + +# Preprocessor definitions -D... +DEFINES += -Dmqtt_EXPORTS diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets new file mode 120000 index 0000000000..63d2fb982d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-targets \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing new file mode 120000 index 0000000000..fb8969bbbb --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-testing \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-template-defines b/libraries/standard/mqtt/cbmc/proofs/Makefile-template-defines new file mode 100644 index 0000000000..f8c68bedf9 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-template-defines @@ -0,0 +1 @@ +SRCDIR ?= $(abspath $(PROOF_ROOT)/../../../../..) diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile.common b/libraries/standard/mqtt/cbmc/proofs/Makefile.common new file mode 120000 index 0000000000..71c3692661 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile.common @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile.common \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/README.md b/libraries/standard/mqtt/cbmc/proofs/README.md new file mode 120000 index 0000000000..090da1de2d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/README.md \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/prepare.py b/libraries/standard/mqtt/cbmc/proofs/prepare.py new file mode 120000 index 0000000000..220e722174 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/prepare.py @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/prepare.py \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/sources/README.md b/libraries/standard/mqtt/cbmc/sources/README.md new file mode 120000 index 0000000000..bfcee2de2e --- /dev/null +++ b/libraries/standard/mqtt/cbmc/sources/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/sources/README.md \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c new file mode 100644 index 0000000000..80589bb759 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include "mqtt_cbmc_state.h" + +void * mallocCanFail( size_t size ) +{ + __CPROVER_assert( size < CBMC_MAX_OBJECT_SIZE, "mallocCanFail size is too big" ); + return nondet_bool() ? NULL : malloc( size ); +} diff --git a/libraries/standard/mqtt/cbmc/stubs/README.md b/libraries/standard/mqtt/cbmc/stubs/README.md new file mode 120000 index 0000000000..107ab2308f --- /dev/null +++ b/libraries/standard/mqtt/cbmc/stubs/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/stubs/README.md \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c new file mode 100644 index 0000000000..58b63029a6 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "mqtt.h" +#include "network_interface_stubs.h" + +int32_t NetworkInterfaceReceiveStub( NetworkContext_t context, + void * pBuffer, + size_t bytesToRecv ) +{ + __CPROVER_assert( context != NULL, + "IotNetworkInterfaceReceive pConnection is not NULL." ); + + __CPROVER_assert( pBuffer != NULL, + "IotNetworkInterfaceReceive pBuffer is not NULL." ); + + __CPROVER_assert( __CPROVER_w_ok( pBuffer, bytesToRecv ), + "pBuffer is writable up to bytesToRecv." ); + + __CPROVER_havoc_object( pBuffer ); + + /* This is unbounded as the MQTT code should be able to safely handle any + * int32_t value returned from the application defined network receive + * implementation. */ + int32_t bytesOrError; + + return bytesOrError; +} diff --git a/tools/aws-templates-for-cbmc-proofs b/tools/aws-templates-for-cbmc-proofs new file mode 160000 index 0000000000..c5eaea00bb --- /dev/null +++ b/tools/aws-templates-for-cbmc-proofs @@ -0,0 +1 @@ +Subproject commit c5eaea00bb86d804db62d83588f8e3efb709d43b From 2d2a6921d474e7358b23e41ecd03c54b5a315edd Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 25 Jun 2020 15:25:04 -0700 Subject: [PATCH 555/844] Add CBMC proof for HTTPClient_AddHeader (#1010) * Add CBMC setup for HTTPClient_AddHeader * Full coverage for HTTPClient_AddHeader * Update Makefile.common and follow style conventions * Update test to use __CPROVER_r_ok * Remove assumption that headersLen <= buffersLen * Update AddHeader harness * Remove files that are now symlink'd by setup.py * Remove files that are now symlink'd by setup.py * Check that length of headers > length of buffers * Add copyright notice * Remove top comment * Add missing assumption for headersLen * Update unit tests due to changes to http_client.c * Fix position of comment * Update docstrings * Add templates as submodule * Address PR comments * Add missing ; * Refactor and add the symlinks * Add prepare.py * Fix Makefile-defines per PR comment * Add README.md files * Update submodule * Add missing newlines * Match .gitmodules with dev branch --- .../standard/http/cbmc/include/README.md | 1 + .../http/cbmc/include/http_cbmc_state.h | 54 +++++++++++++++++++ .../HTTPClient_AddHeader_harness.c | 53 ++++++++++++++++++ .../cbmc/proofs/HTTPClient_AddHeader/Makefile | 15 ++++++ .../proofs/HTTPClient_AddHeader/README.md | 10 ++++ .../HTTPClient_AddHeader/cbmc-batch.yaml | 2 + .../HTTPClient_AddHeader/cbmc-viewer.json | 7 +++ .../http/cbmc/proofs/Makefile-project-defines | 29 ++++++++++ .../http/cbmc/proofs/Makefile-project-targets | 1 + .../http/cbmc/proofs/Makefile-project-testing | 1 + .../cbmc/proofs/Makefile-template-defines | 1 + .../standard/http/cbmc/proofs/Makefile.common | 1 + libraries/standard/http/cbmc/proofs/README.md | 1 + .../standard/http/cbmc/proofs/prepare.py | 1 + .../standard/http/cbmc/sources/README.md | 1 + .../http/cbmc/sources/http_cbmc_state.c | 38 +++++++++++++ libraries/standard/http/cbmc/stubs/README.md | 1 + libraries/standard/http/src/http_client.c | 5 ++ libraries/standard/http/utest/http_utest.c | 7 +++ 19 files changed, 229 insertions(+) create mode 120000 libraries/standard/http/cbmc/include/README.md create mode 100644 libraries/standard/http/cbmc/include/http_cbmc_state.h create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/Makefile-project-defines create mode 120000 libraries/standard/http/cbmc/proofs/Makefile-project-targets create mode 120000 libraries/standard/http/cbmc/proofs/Makefile-project-testing create mode 100644 libraries/standard/http/cbmc/proofs/Makefile-template-defines create mode 120000 libraries/standard/http/cbmc/proofs/Makefile.common create mode 120000 libraries/standard/http/cbmc/proofs/README.md create mode 120000 libraries/standard/http/cbmc/proofs/prepare.py create mode 120000 libraries/standard/http/cbmc/sources/README.md create mode 100644 libraries/standard/http/cbmc/sources/http_cbmc_state.c create mode 120000 libraries/standard/http/cbmc/stubs/README.md diff --git a/libraries/standard/http/cbmc/include/README.md b/libraries/standard/http/cbmc/include/README.md new file mode 120000 index 0000000000..b655718b06 --- /dev/null +++ b/libraries/standard/http/cbmc/include/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/include/README.md \ No newline at end of file diff --git a/libraries/standard/http/cbmc/include/http_cbmc_state.h b/libraries/standard/http/cbmc/include/http_cbmc_state.h new file mode 100644 index 0000000000..90034e9e07 --- /dev/null +++ b/libraries/standard/http/cbmc/include/http_cbmc_state.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef HTTP_CBMC_STATE_H_ +#define HTTP_CBMC_STATE_H_ + +#include "http_client.h" + +/** + * @brief Calls malloc based on given size or returns NULL for coverage. + * + * Implementation of safe malloc which returns NULL if the requested size is 0. + * The behavior of malloc(0) is platform dependent. + * It is possible for malloc(0) to return an address without allocating memory. + * + * @param[in] size Requested size to malloc. + */ +void * mallocCanFail( size_t size ); + +/** + * @brief Allocate a request headers object. + * + * @param[in] pRequestHeaders Request headers to allocate. + */ +HTTPRequestHeaders_t * allocateHttpRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders ); + +/** + * @brief Validates if a request headers object is feasible. + * + * @param[in] pRequestHeaders Request headers to validate. + * + * @return 1 if request headers is feasible; 0 otherwise. + */ +int isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ); + +#endif /* ifndef HTTP_CBMC_STATE_H_ */ diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c new file mode 100644 index 0000000000..2edc0e9ecb --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_AddHeader_harness.c + * @brief Implements the proof harness for HTTPClient_AddHeader function. + */ + +#include "http_client.h" + +#include "http_cbmc_state.h" + +void harness() +{ + HTTPRequestHeaders_t * pRequestHeaders = NULL; + char * pField = NULL; + char * pValue = NULL; + size_t fieldLen; + size_t valueLen; + + /* Initialize and make assumptions for request headers. */ + pRequestHeaders = allocateHttpRequestHeaders( pRequestHeaders ); + __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); + + /* Initialize and make assumptions for header field. */ + __CPROVER_assume( fieldLen < CBMC_MAX_OBJECT_SIZE ); + pField = mallocCanFail( fieldLen ); + + /* Initialize and make assumptions for header value. */ + __CPROVER_assume( valueLen < CBMC_MAX_OBJECT_SIZE ); + pValue = mallocCanFail( valueLen ); + + HTTPClient_AddHeader( pRequestHeaders, + pField, fieldLen, pValue, valueLen ); +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile new file mode 100644 index 0000000000..7f4395cc8a --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile @@ -0,0 +1,15 @@ +HARNESS_ENTRY=harness +HARNESS_FILE=HTTPClient_AddHeader_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += strncmp.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md new file mode 100644 index 0000000000..97bdc073b6 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md @@ -0,0 +1,10 @@ +HTTPClient_AddHeader proof +============== + +This directory contains a memory safety proof for HTTPClient_AddHeader. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-viewer.json new file mode 100644 index 0000000000..1202154ebd --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "HTTPClient_AddHeader", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-defines b/libraries/standard/http/cbmc/proofs/Makefile-project-defines new file mode 100644 index 0000000000..74c2771669 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-defines @@ -0,0 +1,29 @@ +# -*- mode: makefile -*- +# The first line sets the emacs major mode to Makefile + +################################################################ +# Use this file to give project-specific definitions of the command +# line arguments to pass to CBMC tools like goto-cc to build the goto +# binaries and cbmc to do the property and coverage checking. +# +# Use this file to override most default definitions of variables in +# Makefile.common. +################################################################ + +# Flags to pass to goto-cc for compilation (typically those passed to gcc -c) +COMPILE_FLAGS += -fPIC +COMPILE_FLAGS += -std=gnu90 + +# Flags to pass to goto-cc for linking (typically those passed to gcc) +# LINK_FLAGS = + +# Preprocessor include paths -I... +INCLUDES += -I$(SRCDIR)/libraries/standard/http/cbmc/include +INCLUDES += -I$(SRCDIR)/libraries/standard/http/include +INCLUDES += -I$(SRCDIR)/libraries/standard/http/src +INCLUDES += -I$(SRCDIR)/libraries/standard/http/third_party/http_parser +INCLUDES += -I$(SRCDIR)/demos/logging-stack +INCLUDES += -I$(SRCDIR)/demos/http/http_demo_plaintext + +# Preprocessor definitions -D... +DEFINES += -Dhttp_EXPORTS diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-targets b/libraries/standard/http/cbmc/proofs/Makefile-project-targets new file mode 120000 index 0000000000..63d2fb982d --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-targets @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-targets \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-testing b/libraries/standard/http/cbmc/proofs/Makefile-project-testing new file mode 120000 index 0000000000..fb8969bbbb --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-testing @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-testing \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/Makefile-template-defines b/libraries/standard/http/cbmc/proofs/Makefile-template-defines new file mode 100644 index 0000000000..f8c68bedf9 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile-template-defines @@ -0,0 +1 @@ +SRCDIR ?= $(abspath $(PROOF_ROOT)/../../../../..) diff --git a/libraries/standard/http/cbmc/proofs/Makefile.common b/libraries/standard/http/cbmc/proofs/Makefile.common new file mode 120000 index 0000000000..71c3692661 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile.common @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile.common \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/README.md b/libraries/standard/http/cbmc/proofs/README.md new file mode 120000 index 0000000000..090da1de2d --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/README.md \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/prepare.py b/libraries/standard/http/cbmc/proofs/prepare.py new file mode 120000 index 0000000000..220e722174 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/prepare.py @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/prepare.py \ No newline at end of file diff --git a/libraries/standard/http/cbmc/sources/README.md b/libraries/standard/http/cbmc/sources/README.md new file mode 120000 index 0000000000..bfcee2de2e --- /dev/null +++ b/libraries/standard/http/cbmc/sources/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/sources/README.md \ No newline at end of file diff --git a/libraries/standard/http/cbmc/sources/http_cbmc_state.c b/libraries/standard/http/cbmc/sources/http_cbmc_state.c new file mode 100644 index 0000000000..c6c59c6152 --- /dev/null +++ b/libraries/standard/http/cbmc/sources/http_cbmc_state.c @@ -0,0 +1,38 @@ +#include + +#include "http_cbmc_state.h" + +void * mallocCanFail( size_t size ) +{ + __CPROVER_assert( size < CBMC_MAX_OBJECT_SIZE, "mallocCanFail size is too big" ); + return nondet_bool() ? NULL : malloc( size ); +} + +HTTPRequestHeaders_t * allocateHttpRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders ) +{ + if( pRequestHeaders == NULL ) + { + pRequestHeaders = mallocCanFail( sizeof( HTTPRequestHeaders_t ) ); + } + + if( pRequestHeaders != NULL ) + { + __CPROVER_assume( pRequestHeaders->bufferLen < CBMC_MAX_OBJECT_SIZE ); + pRequestHeaders->pBuffer = mallocCanFail( pRequestHeaders->bufferLen ); + } + + return pRequestHeaders; +} + +int isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ) +{ + int isValid = 1; + + if( pRequestHeaders ) + { + isValid = pRequestHeaders->bufferLen < CBMC_MAX_OBJECT_SIZE && + pRequestHeaders->headersLen < CBMC_MAX_OBJECT_SIZE; + } + + return isValid; +} diff --git a/libraries/standard/http/cbmc/stubs/README.md b/libraries/standard/http/cbmc/stubs/README.md new file mode 120000 index 0000000000..107ab2308f --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/README.md @@ -0,0 +1 @@ +../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/stubs/README.md \ No newline at end of file diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index f88e571c6b..741156f363 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1399,6 +1399,11 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, LogError( ( "Parameter check failed: valueLen must be greater than 0." ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( pRequestHeaders->headersLen > pRequestHeaders->bufferLen ) + { + LogError( ( "Parameter check failed: pRequestHeaders->headersLen > pRequestHeaders->bufferLen." ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else { /* Empty else MISRA 15.7 */ diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index ea22598f48..3ae02b830c 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -652,6 +652,13 @@ void test_Http_AddHeader_Invalid_Params() HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, HTTP_TEST_HEADER_VALUE, 0 ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); + + /* Test that requestHeaders.headersLen <= requestHeaders.bufferLen. */ + requestHeaders.headersLen = requestHeaders.bufferLen + 1; + httpStatus = HTTPClient_AddHeader( &requestHeaders, + HTTP_TEST_HEADER_FIELD, HTTP_TEST_HEADER_FIELD_LEN, + HTTP_TEST_HEADER_VALUE, HTTP_TEST_HEADER_VALUE_LEN ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, httpStatus ); } /** From 6c387eaeaa83d0377368e83ead8f2b370e2f6e49 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 25 Jun 2020 17:35:58 -0700 Subject: [PATCH 556/844] Add transport interface with POSIX implementations (#1002) * Add tcp connect/disconnect and plaintext transport send/recv * Get transport code to work with http plaintext demo * Finish draft for plaintext transport * Add filepaths for http plaintext demo * Add updated plaintext demo * Remove network_common.h and move setTimeout to plaintext transport * Remove wrong declaration in plaintext transport header file * Address feedback * Refactor tcp_posix * Remove all warnings from transport implementations * Rename send/recvTimeout to tcpSend/RecvTimeout * Update http basic tls demos to work with new transport interface * Revert changes to libraries and demos, which are to be changed in succeeding PRs * Update TCP_Disconnect if socket parameter is invalid * pContext -> pNetworkContext * Use INET6_ADDRSTRLEN macro from POSIX instead of defining one * Decouple resolveHostName and attemptConnection * Use double ptr for pListHead in resolveHostName * Wrap resolveHostName around returnStatus check * Fix capitalization of openssl_posix.h include * Fix switched parameters for LogDebug * Make some minor formatting changes * Remove premature X509_free * Revert to using setsockopt for setting the timeout in TCP_connect as a parameter * Use both usec and sec fields for timeval structure to prevent EDOM error * Refactor transport code for complexity <= 8 * Log more errors depending on errno in tcp_posix.h * Create common sockets utility for establishing connection to server * Fix some warnings and errors from testing on a demo * Update docstrings * Address PR comments * Address remaining PR comments * Remove unnecessary includes and add asserts to private methods * Fix docstring * Update docstring for transport interface * Remove more unneeded includes * bytesToRead -> bytesToRecv * Update based on Gaurav's changes * Apply more changes from Gaurav * Fix some warnings from includes * Update docstrings for openssl_posix * Remove accidental commit of http_Demo_plaintext * Undo accidental git rm --- CMakeLists.txt | 4 + platform/CMakeLists.txt | 2 + platform/include/transport_interface.h | 72 ++ platform/posix/transport/CMakeLists.txt | 27 + .../posix/transport/include/openssl_posix.h | 196 ++++++ .../posix/transport/include/plaintext_posix.h | 122 ++++ .../posix/transport/include/sockets_posix.h | 103 +++ platform/posix/transport/src/openssl_posix.c | 639 ++++++++++++++++++ .../posix/transport/src/plaintext_posix.c | 168 +++++ platform/posix/transport/src/sockets_posix.c | 432 ++++++++++++ .../posix/transport/transportFilePaths.cmake | 23 + 11 files changed, 1788 insertions(+) create mode 100644 platform/CMakeLists.txt create mode 100644 platform/include/transport_interface.h create mode 100644 platform/posix/transport/CMakeLists.txt create mode 100644 platform/posix/transport/include/openssl_posix.h create mode 100644 platform/posix/transport/include/plaintext_posix.h create mode 100644 platform/posix/transport/include/sockets_posix.h create mode 100644 platform/posix/transport/src/openssl_posix.c create mode 100644 platform/posix/transport/src/plaintext_posix.c create mode 100644 platform/posix/transport/src/sockets_posix.c create mode 100644 platform/posix/transport/transportFilePaths.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 2efdd765ed..094d8d9845 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ endif() get_filename_component(__root_dir "${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE) set(ROOT_DIR ${__root_dir} CACHE INTERNAL "C SDK source root.") set(DEMOS_DIR "${ROOT_DIR}/demos" CACHE INTERNAL "C SDK demos root.") +set(PLATFORM_DIR "${ROOT_DIR}/platform" CACHE INTERNAL "C SDK platform root.") set(MODULES_DIR "${ROOT_DIR}/libraries" CACHE INTERNAL "C SDK modules root.") set(3RDPARTY_DIR "${MODULES_DIR}/3rdparty" CACHE INTERNAL "3rdparty libraries root.") @@ -50,6 +51,9 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # Add libraries. add_subdirectory( libraries ) +# Add platform. +add_subdirectory( platform ) + # Build the demos. add_subdirectory( demos/transport ) add_subdirectory( demos ) diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt new file mode 100644 index 0000000000..f8b4b5fbe2 --- /dev/null +++ b/platform/CMakeLists.txt @@ -0,0 +1,2 @@ +# Add the transport target +add_subdirectory( ${PLATFORM_DIR}/posix/transport ) diff --git a/platform/include/transport_interface.h b/platform/include/transport_interface.h new file mode 100644 index 0000000000..3980221402 --- /dev/null +++ b/platform/include/transport_interface.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef TRANSPORT_INTERFACE_H_ +#define TRANSPORT_INTERFACE_H_ + +#include +#include + +/** + * @brief The NetworkContext is an incomplete type. An implementation of this + * interface must define NetworkContext as per the requirements. This context + * is passed into the network interface functions. + */ +struct NetworkContext; +typedef struct NetworkContext NetworkContext_t; + +/** + * @brief Transport interface for receiving data on the network. + * + * @param[in] pNetworkContext Implementation-defined network context. + * @param[in] pBuffer Buffer to receive the data into. + * @param[in] bytesToRecv Number of bytes requested from the network. + * + * @return The number of bytes received or a negative error code. + */ +typedef int32_t ( * TransportRecv_t )( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief Transport interface for sending data over the network. + * + * @param[in] pNetworkContext Implementation-defined network context. + * @param[in] pBuffer Buffer containing the bytes to send over the network stack. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return The number of bytes sent or a negative error code. + */ +typedef int32_t ( * TransportSend_t )( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ); + +/** + * @brief The transport layer interface. + */ +typedef struct TransportInterface +{ + TransportRecv_t recv; /**< Transport receive interface. */ + TransportSend_t send; /**< Transport send interface. */ + NetworkContext_t * pNetworkContext; /**< Implementation-defined network context. */ +} TransportInterface_t; + +#endif /* ifndef TRANSPORT_INTERFACE_H_ */ diff --git a/platform/posix/transport/CMakeLists.txt b/platform/posix/transport/CMakeLists.txt new file mode 100644 index 0000000000..0f224c762b --- /dev/null +++ b/platform/posix/transport/CMakeLists.txt @@ -0,0 +1,27 @@ +# Include filepaths for source and include. +include(transportFilePaths.cmake) + +# Create target for sockets utility. +add_library( sockets_posix + ${SOCKETS_SOURCES} ) + +target_include_directories( sockets_posix + PUBLIC + ${COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS} + ${LOGGING_INCLUDE_DIRS} ) + +# Create target for plaintext transport. +add_library( plaintext_posix + ${PLAINTEXT_TRANSPORT_SOURCES} ) + +target_link_libraries( plaintext_posix + PUBLIC + sockets_posix ) + +# Create target for POSIX implementation of OpenSSL. +add_library( openssl_posix + ${OPENSSL_TRANSPORT_SOURCES} ) + +target_link_libraries( openssl_posix + PUBLIC + sockets_posix ) diff --git a/platform/posix/transport/include/openssl_posix.h b/platform/posix/transport/include/openssl_posix.h new file mode 100644 index 0000000000..dda1ec8740 --- /dev/null +++ b/platform/posix/transport/include/openssl_posix.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef OPENSSL_POSIX_H_ +#define OPENSSL_POSIX_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the transport interface implemenation which uses + * OpenSSL and Sockets. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "Transport_OpenSSL_Sockets" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_DEBUG +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Transport includes. */ +#include "transport_interface.h" + +/* Socket includes. */ +#include "sockets_posix.h" + +/** + * @brief Definition of the network context for the transport interface + * implemenation that uses OpenSSL and POSIX sockets. + * + * @note For this transport implementation, the socket descriptor and + * SSL context is used. + */ +struct NetworkContext +{ + int socketDescriptor; + SSL * pSsl; +}; + +/** + * @brief OpenSSL Connect / Disconnect return status. + */ +typedef enum OpensslStatus +{ + OPENSSL_SUCCESS = 0, /**< Function successfully completed. */ + OPENSSL_INVALID_PARAMETER, /**< At least one parameter was invalid. */ + OPENSSL_INSUFFICIENT_MEMORY, /**< Insufficient memory required to establish connection. */ + OPENSSL_INVALID_CREDENTIALS, /**< Provided credentials were invalid. */ + OPENSSL_HANDSHAKE_FAILED, /**< Performing TLS handshake with server failed. */ + OPENSSL_API_ERROR, /**< A call to a system API resulted in an internal error. */ + OPENSSL_DNS_FAILURE, /**< Resolving hostname of the server failed. */ + OPENSSL_CONNECT_FAILURE /**< Initial connection to the server failed. */ +} OpensslStatus_t; + +/** + * @brief Contains the credentials to establish a TLS connection. + */ +typedef struct OpensslCredentials +{ + /** + * @brief An array of ALPN protocols. Set to NULL to disable ALPN. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char * pAlpnProtos; + + /** + * @brief Length of the ALPN protocols array. + */ + size_t alpnProtosLen; + + /** + * @brief Set a host name to enable SNI. Set to NULL to disable SNI. + * + * @note This string must be NULL-terminated because the OpenSSL API requires it to be. + */ + const char * sniHostName; + + /** + * @brief Set the value for the TLS max fragment length (TLS MFLN) + * + * OpenSSL allows this value to be in the range of: + * [512, 16384 (SSL3_RT_MAX_PLAIN_LENGTH)] + * + * @note By setting this to 0, OpenSSL uses the default value, + * which is 16384 (SSL3_RT_MAX_PLAIN_LENGTH). + */ + size_t maxFragmentLength; + + /** + * @brief Filepaths to certificates and private key that are used when + * performing the TLS handshake. + * + * @note These strings must be NULL-terminated because the OpenSSL API requires them to be. + */ + const char * pRootCaPath; /**< @brief Filepath string to the trusted server root CA. */ + const char * pClientCertPath; /**< @brief Filepath string to the client certificate. */ + const char * pPrivateKeyPath; /**< @brief Filepath string to the client certificate's private key. */ +} OpensslCredentials_t; + +/** + * @brief Sets up a TLS session on top of a TCP connection using the OpenSSL API. + * + * @param[out] pNetworkContext The output parameter to return the created network context. + * @param[in] pServerInfo Server connection info. + * @param[in] pOpensslCredentials Credentials for the TLS connection. + * @param[in] sendTimeoutMs Timeout for transport send. + * @param[in] recvTimeoutMs Timeout for transport recv. + * + * @note A timeout of 0 means infinite timeout. + * + * @return #OPENSSL_SUCCESS on success; + * #OPENSSL_INVALID_PARAMETER, #OPENSSL_INVALID_CREDENTIALS, + * #OPENSSL_INVALID_CREDENTIALS, #OPENSSL_SYSTEM_ERROR on failure. + */ +OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, + const ServerInfo_t * pServerInfo, + const OpensslCredentials_t * pOpensslCredentials, + uint32_t sendTimeoutMs, + uint32_t recvTimeoutMs ); + +/** + * @brief Closes a TLS session on top of a TCP connection using the OpenSSL API. + * + * @param[out] pNetworkContext The output parameter to end the TLS session and + * clean the created network context. + * + * @return #OPENSSL_SUCCESS on success; #OPENSSL_INVALID_PARAMETER on failure. + */ +OpensslStatus_t Openssl_Disconnect( NetworkContext_t * pNetworkContext ); + +/** + * @brief Receives data over an established TLS session using the OpenSSL API. + * + * This can be used as #TransportInterface.recv function for receiving data + * from the network. + * + * @param[in] pNetworkContext The network context created using Openssl_Connect API. + * @param[out] pBuffer Buffer to receive network data into. + * @param[in] bytesToRecv Number of bytes requested from the network. + * + * @return Number of bytes received if successful; negative value on error. + */ +int32_t Openssl_Recv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief Sends data over an established TLS session using the OpenSSL API. + * + * This can be used as the #TransportInterface.send function to send data + * over the network. + * + * @param[in] pNetworkContext The network context created using Openssl_Connect API. + * @param[in] pBuffer Buffer containing the bytes to send over the network stack. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return Number of bytes sent if successful; negative value on error. + */ +int32_t Openssl_Send( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ); + +#endif /* ifndef OPENSSL_POSIX_H_ */ diff --git a/platform/posix/transport/include/plaintext_posix.h b/platform/posix/transport/include/plaintext_posix.h new file mode 100644 index 0000000000..9b9be748ef --- /dev/null +++ b/platform/posix/transport/include/plaintext_posix.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PLAINTEXT_POSIX_H_ +#define PLAINTEXT_POSIX_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the transport interface implemenation which uses + * Sockets. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "Transport_Plaintext_Sockets" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_DEBUG +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Transport includes. */ +#include "transport_interface.h" +#include "sockets_posix.h" + +/** + * @brief Definition of the network context. + */ +struct NetworkContext +{ + int socketDescriptor; +}; + +/** + * @brief Establish TCP connection to server. + * + * @param[out] pNetworkContext The output parameter to return the created network context. + * @param[in] pServerInfo Server connection info. + * @param[in] sendTimeout Timeout for socket send. + * @param[in] recvTimeout Timeout for socket recv. + * + * @note A timeout of 0 means infinite timeout. + * + * @return #SOCKETS_SUCCESS if successful; + * #SOCKETS_INVALID_PARAMETER, #SOCKETS_DNS_FAILURE, #SOCKETS_CONNECT_FAILURE on error. + */ +SocketStatus_t Plaintext_Connect( NetworkContext_t * pNetworkContext, + const ServerInfo_t * pServerInfo, + uint32_t sendTimeoutMs, + uint32_t recvTimeoutMs ); + +/** + * @brief Close TCP connection to server. + * + * @param[in] pNetworkContext The network context to close the connection. + * + * @return #SOCKETS_SUCCESS if successful; #SOCKETS_INVALID_PARAMETER on error. + */ +SocketStatus_t Plaintext_Disconnect( const NetworkContext_t * pNetworkContext ); + +/** + * @brief Receives data over an established TCP connection. + * + * This can be used as #TransportInterface.recv function to receive data over + * the network. + * + * @param[in] pNetworkContext The network context created using Plaintext_Connect API. + * @param[out] pBuffer Buffer to receive network data into. + * @param[in] bytesToRecv Number of bytes requested from the network. + * + * @return Number of bytes received if successful; negative value on error. + */ +int32_t Plaintext_Recv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief Sends data over an established TCP connection. + * + * This can be used as the #TransportInterface.send function to send data + * over the network. + * + * @param[in] pNetworkContext The network context created using Plaintext_Connect API. + * @param[in] pBuffer Buffer containing the bytes to send over the network. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return Number of bytes sent if successful; negative value on error. + */ +int32_t Plaintext_Send( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ); + +#endif /* ifndef PLAINTEXT_POSIX_H_ */ diff --git a/platform/posix/transport/include/sockets_posix.h b/platform/posix/transport/include/sockets_posix.h new file mode 100644 index 0000000000..a5368f966f --- /dev/null +++ b/platform/posix/transport/include/sockets_posix.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SOCKETS_POSIX_H_ +#define SOCKETS_POSIX_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Sockets. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "Sockets" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_DEBUG +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Transport interface include. */ +#include "transport_interface.h" + +/** + * @brief TCP Connect / Disconnect return status. + */ +typedef enum SocketStatus +{ + SOCKETS_SUCCESS = 0, /**< Function successfully completed. */ + SOCKETS_INVALID_PARAMETER, /**< At least one parameter was invalid. */ + SOCKETS_INSUFFICIENT_MEMORY, /**< Insufficient memory required to establish connection. */ + SOCKETS_API_ERROR, /**< A call to a system API resulted in an internal error. */ + SOCKETS_DNS_FAILURE, /**< Resolving hostname of server failed. */ + SOCKETS_CONNECT_FAILURE /**< Initial connection to the server failed. */ +} SocketStatus_t; + +/** + * @brief Information on the remote server for connection setup. + */ +typedef struct ServerInfo +{ + const char * pHostName; /**< @brief Server host name. */ + size_t hostNameLength; /**< @brief Length of the server host name. */ + uint16_t port; /**< @brief Server port in host-order. */ +} ServerInfo_t; + +/** + * @brief Establish a connection to server. + * + * @param[out] pTcpSocket The output parameter to return the created socket descriptor. + * @param[in] pServerInfo Server connection info. + * @param[in] sendTimeoutMs Timeout for transport send. + * @param[in] recvTimeoutMs Timeout for transport recv. + * + * @note A timeout of 0 means infinite timeout. + * + * @return #SOCKETS_SUCCESS if successful; + * #SOCKETS_INVALID_PARAMETER, #SOCKETS_DNS_FAILURE, #SOCKETS_CONNECT_FAILURE on error. + */ +SocketStatus_t Sockets_Connect( int * pTcpSocket, + const ServerInfo_t * pServerInfo, + uint32_t sendTimeoutMs, + uint32_t recvTimeoutMs ); + +/** + * @brief End connection to server. + * + * @param[in] tcpSocket The socket descriptor. + * + * @return #SOCKETS_SUCCESS if successful; #SOCKETS_INVALID_PARAMETER on error. + */ +SocketStatus_t Sockets_Disconnect( int tcpSocket ); + +#endif /* ifndef SOCKETS_POSIX_H_ */ diff --git a/platform/posix/transport/src/openssl_posix.c b/platform/posix/transport/src/openssl_posix.c new file mode 100644 index 0000000000..da5302c0cc --- /dev/null +++ b/platform/posix/transport/src/openssl_posix.c @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include + +/* POSIX socket include. */ +#include + +/* OpenSSL include. */ +#include + +/* Transport interface include. */ +#include "transport_interface.h" + +#include "openssl_posix.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Label of root CA when calling @ref logPath. + */ +#define ROOT_CA_LABEL "Root CA certificate" + +/** + * @brief Label of client certificate when calling @ref logPath. + */ +#define CLIENT_CERT_LABEL "client's certificate" + +/** + * @brief Label of client key when calling @ref logPath. + */ +#define CLIENT_KEY_LABEL "client's key" + +/*-----------------------------------------------------------*/ + +/** + * @brief Log the absolute path given a relative or absolute path. + * + * @param[in] path Relative or absolute path. + * @param[in] fileLabel NULL-terminated string describing the file label to log. + */ +#if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) + static void logPath( const char * path, + const char * fileLabel ); +#endif /* #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) */ + +/** + * @brief Add X509 certificate to the trusted list of root certificates. + * + * OpenSSL does not provide a single function for reading and loading certificates + * from files into stores, so the file API must be called. Start with the + * root certificate. + * + * @param[out] pSslContext SSL context to which the trusted server root CA is to be added. + * @param[in] pRootCaPath Filepath string to the trusted server root CA. + * + * @return 1 on success; -1, 0 on failure; + */ +static int setRootCa( SSL_CTX * pSslContext, + const char * pRootCaPath ); + +/** + * @brief Set X509 certificate as client certificate for the server to authenticate. + * + * @param[out] pSslContext SSL context to which the client certificate is to be set. + * @param[in] pClientCertPath Filepath string to the client certificate. + * + * @return 1 on success; 0 failure; + */ +static int setClientCertificate( SSL_CTX * pSslContext, + const char * pClientCertPath ); + +/** + * @brief Set private key for the client's certificate. + * + * @param[out] pSslContext SSL context to which the private key is to be added. + * @param[in] pPrivateKeyPath Filepath string to the client private key. + * + * @return 1 on success; 0 on failure; + */ +static int setPrivateKey( SSL_CTX * pSslContext, + const char * pPrivateKeyPath ); + +/** + * @brief Passes TLS credentials to the OpenSSL library. + * + * Provides the root CA certificate, client certificate, and private key to the + * OpenSSL library. If the client certificate or private key is not NULL, mutual + * authentication is used when performing the TLS handshake. + * + * @param[out] pSslContext SSL context to which the credentials are to be imported. + * @param[in] pOpensslCredentials TLS credentials to be imported. + * + * @return 1 on success; -1, 0 on failure; + */ +static int setCredentials( SSL_CTX * pSslContext, + const OpensslCredentials_t * pOpensslCredentials ); + +/** + * @brief Set optional configurations for the TLS connection. + * + * This function is used to set SNI, MFLN, and ALPN protocols. + * + * @param[in] pSsl SSL context to which the optional configurations are to be set. + * @param[in] pOpensslCredentials TLS credentials containing configurations. + */ +static void setOptionalConfigurations( SSL * pSsl, + const OpensslCredentials_t * pOpensslCredentials ); + +/*-----------------------------------------------------------*/ + +#if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) + static void logPath( const char * path, + const char * fileType ) + { + char * cwd = NULL; + + assert( path != NULL ); + assert( fileType != NULL ); + + /* Log the absolute directory based on first character of path. */ + if( ( path[ 0 ] == '/' ) || ( path[ 0 ] == '\\' ) ) + { + LogDebug( ( "Attempting to open %s: Path=%s.", + fileType, + path ) ); + } + else + { + cwd = getcwd( NULL, 0 ); + LogDebug( ( "Attempting to open %s: Path=%s/%s.", + fileType, + cwd, + path ) ); + } + + /* Free cwd because getcwd calls malloc. */ + if( cwd != NULL ) + { + free( cwd ); + } + } +#endif /* #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) */ +/*-----------------------------------------------------------*/ + +static int setRootCa( SSL_CTX * pSslContext, + const char * pRootCaPath ) +{ + int sslStatus = 1; + FILE * pRootCaFile = NULL; + X509 * pRootCa = NULL; + + assert( pSslContext != NULL ); + assert( pRootCaPath != NULL ); + + #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) + logPath( pRootCaPath, ROOT_CA_LABEL ); + #endif + + pRootCaFile = fopen( pRootCaPath, "r" ); + + if( pRootCaFile == NULL ) + { + LogError( ( "fopen failed to find the root CA certificate file: " + "ROOT_CA_PATH=%s.", + pRootCaPath ) ); + sslStatus = -1; + } + + if( sslStatus == 1 ) + { + /* Read the root CA into an X509 object. */ + pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); + + if( pRootCa == NULL ) + { + LogError( ( "PEM_read_X509 failed to parse root CA." ) ); + sslStatus = -1; + } + } + + if( sslStatus == 1 ) + { + /* Add the certificate to the context. */ + sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), + pRootCa ); + + if( sslStatus != 1 ) + { + LogError( ( "X509_STORE_add_cert failed to add root CA to certificate store." ) ); + sslStatus = -1; + } + } + + /* Close the file if it was successfully opened. */ + if( pRootCaFile != NULL ) + { + if( fclose( pRootCaFile ) != 0 ) + { + LogWarn( ( "fclose failed to close file %s", + pRootCaPath ) ); + } + } + + /* Log the success message if we successfully imported the root CA. */ + if( sslStatus == 1 ) + { + LogDebug( ( "Successfully imported root CA." ) ); + } + + return sslStatus; +} +/*-----------------------------------------------------------*/ + +static int setClientCertificate( SSL_CTX * pSslContext, + const char * pClientCertPath ) +{ + int sslStatus = -1; + + assert( pSslContext != NULL ); + assert( pClientCertPath != NULL ); + + #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) + logPath( pClientCertPath, CLIENT_CERT_LABEL ); + #endif + + /* Import the client certificate. */ + sslStatus = SSL_CTX_use_certificate_chain_file( pSslContext, + pClientCertPath ); + + if( sslStatus != 1 ) + { + LogError( ( "SSL_CTX_use_certificate_chain_file failed to import " + "client certificate at %s.", + pClientCertPath ) ); + } + else + { + LogDebug( ( "Successfully imported client certificate." ) ); + } + + return sslStatus; +} +/*-----------------------------------------------------------*/ + +static int setPrivateKey( SSL_CTX * pSslContext, + const char * pPrivateKeyPath ) +{ + int sslStatus = -1; + + assert( pSslContext != NULL ); + assert( pPrivateKeyPath != NULL ); + + #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) + logPath( pPrivateKeyPath, CLIENT_KEY_LABEL ); + #endif + + /* Import the client certificate private key. */ + sslStatus = SSL_CTX_use_PrivateKey_file( pSslContext, + pPrivateKeyPath, + SSL_FILETYPE_PEM ); + + if( sslStatus != 1 ) + { + LogError( ( "SSL_CTX_use_PrivateKey_file failed to import client " + "certificate private key at %s.", + pPrivateKeyPath ) ); + } + else + { + LogDebug( ( "Successfully imported client certificate private key." ) ); + } + + return sslStatus; +} +/*-----------------------------------------------------------*/ + +static int setCredentials( SSL_CTX * pSslContext, + const OpensslCredentials_t * pOpensslCredentials ) +{ + int sslStatus = 0; + + assert( pSslContext != NULL ); + assert( pOpensslCredentials != NULL ); + + if( pOpensslCredentials->pRootCaPath != NULL ) + { + sslStatus = setRootCa( pSslContext, + pOpensslCredentials->pRootCaPath ); + } + + if( ( sslStatus == 1 ) && + ( pOpensslCredentials->pClientCertPath != NULL ) ) + { + sslStatus = setClientCertificate( pSslContext, + pOpensslCredentials->pClientCertPath ); + } + + if( ( sslStatus == 1 ) && + ( pOpensslCredentials->pPrivateKeyPath != NULL ) ) + { + sslStatus = setPrivateKey( pSslContext, + pOpensslCredentials->pPrivateKeyPath ); + } + + return sslStatus; +} +/*-----------------------------------------------------------*/ + +static void setOptionalConfigurations( SSL * pSsl, + const OpensslCredentials_t * pOpensslCredentials ) +{ + int sslStatus = -1; + + assert( pSsl != NULL ); + assert( pOpensslCredentials != NULL ); + + /* Set TLS ALPN if requested. */ + if( ( pOpensslCredentials->pAlpnProtos != NULL ) && + ( pOpensslCredentials->alpnProtosLen > 0 ) ) + { + LogDebug( ( "Setting ALPN protos." ) ); + sslStatus = SSL_set_alpn_protos( pSsl, + ( unsigned char * ) pOpensslCredentials->pAlpnProtos, + ( unsigned int ) pOpensslCredentials->alpnProtosLen ); + + if( sslStatus != 0 ) + { + LogError( ( "SSL_set_alpn_protos failed to set ALPN protos. %s", + pOpensslCredentials->pAlpnProtos ) ); + } + } + + /* Set TLS MFLN if requested. */ + if( pOpensslCredentials->maxFragmentLength > 0 ) + { + LogDebug( ( "Setting max send fragment length %lu.", + ( unsigned long ) pOpensslCredentials->maxFragmentLength ) ); + + /* Set the maximum send fragment length. */ + sslStatus = SSL_set_max_send_fragment( pSsl, + ( long ) pOpensslCredentials->maxFragmentLength ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to set max send fragment length %lu.", + ( unsigned long ) pOpensslCredentials->maxFragmentLength ) ); + } + else + { + /* Change the size of the read buffer to match the + * maximum fragment length + some extra bytes for overhead. */ + SSL_set_default_read_buffer_len( pSsl, + pOpensslCredentials->maxFragmentLength + + SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); + } + } + + /* Enable SNI if requested. */ + if( pOpensslCredentials->sniHostName != NULL ) + { + LogDebug( ( "Setting server name %s for SNI.", + pOpensslCredentials->sniHostName ) ); + + sslStatus = SSL_set_tlsext_host_name( pSsl, + pOpensslCredentials->sniHostName ); + + if( sslStatus != 1 ) + { + LogError( ( "Failed to set server name %s for SNI.", + pOpensslCredentials->sniHostName ) ); + } + } +} +/*-----------------------------------------------------------*/ + +OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, + const ServerInfo_t * pServerInfo, + const OpensslCredentials_t * pOpensslCredentials, + uint32_t sendTimeoutMs, + uint32_t recvTimeoutMs ) +{ + SocketStatus_t socketStatus = SOCKETS_SUCCESS; + OpensslStatus_t returnStatus = OPENSSL_SUCCESS; + long verifyPeerCertStatus = X509_V_OK; + int sslStatus = 0; + SSL_CTX * pSslContext = NULL; + + /* Validate parameters. */ + if( pNetworkContext == NULL ) + { + LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); + returnStatus = OPENSSL_INVALID_PARAMETER; + } + else if( pOpensslCredentials == NULL ) + { + LogError( ( "Parameter check failed: pOpensslCredentials is NULL." ) ); + returnStatus = OPENSSL_INVALID_PARAMETER; + } + else + { + /* Empty else. */ + } + + /* Establish the TCP connection. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + socketStatus = Sockets_Connect( &pNetworkContext->socketDescriptor, + pServerInfo, + sendTimeoutMs, + recvTimeoutMs ); + + if( socketStatus == SOCKETS_INVALID_PARAMETER ) + { + returnStatus = OPENSSL_INVALID_PARAMETER; + } + else if( socketStatus == SOCKETS_DNS_FAILURE ) + { + returnStatus = OPENSSL_DNS_FAILURE; + } + else if( socketStatus == SOCKETS_CONNECT_FAILURE ) + { + returnStatus = OPENSSL_CONNECT_FAILURE; + } + else + { + /* Empty else. */ + } + } + +/* Create SSL context. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + pSslContext = SSL_CTX_new( TLS_client_method() ); + + if( pSslContext == NULL ) + { + LogError( ( "Creation of a new SSL_CTX object failed." ) ); + returnStatus = OPENSSL_API_ERROR; + } + } + + /* Setup credentials. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. + * The mask returned by SSL_CTX_set_mode does not need to be checked. */ + ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); + + sslStatus = setCredentials( pSslContext, + pOpensslCredentials ); + + if( sslStatus != 1 ) + { + LogError( ( "Setting up credentials failed." ) ); + returnStatus = OPENSSL_INVALID_CREDENTIALS; + } + } + + /* Create a new SSL session. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + pNetworkContext->pSsl = SSL_new( pSslContext ); + + if( pNetworkContext->pSsl == NULL ) + { + LogError( ( "SSL_new failed to create a new SSL context." ) ); + returnStatus = OPENSSL_API_ERROR; + } + } + + /* Setup the socket to use for communication. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + /* Enable SSL peer verification. */ + SSL_set_verify( pNetworkContext->pSsl, SSL_VERIFY_PEER, NULL ); + + sslStatus = SSL_set_fd( pNetworkContext->pSsl, pNetworkContext->socketDescriptor ); + + if( sslStatus != 1 ) + { + LogError( ( "SSL_set_fd failed to set the socket fd to SSL context." ) ); + returnStatus = OPENSSL_API_ERROR; + } + } + + /* Perform the TLS handshake. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + setOptionalConfigurations( pNetworkContext->pSsl, pOpensslCredentials ); + + sslStatus = SSL_connect( pNetworkContext->pSsl ); + + if( sslStatus != 1 ) + { + LogError( ( "SSL_connect failed to perform TLS handshake." ) ); + returnStatus = OPENSSL_HANDSHAKE_FAILED; + } + } + + /* Verify X509 certificate from peer. */ + if( returnStatus == OPENSSL_SUCCESS ) + { + verifyPeerCertStatus = SSL_get_verify_result( pNetworkContext->pSsl ); + + if( verifyPeerCertStatus != X509_V_OK ) + { + LogError( ( "SSL_get_verify_result failed to verify X509 " + "certificate from peer." ) ); + returnStatus = OPENSSL_HANDSHAKE_FAILED; + } + } + + /* Free the SSL context. */ + if( pSslContext != NULL ) + { + SSL_CTX_free( pSslContext ); + } + + /* Clean up on error. */ + if( ( returnStatus != OPENSSL_SUCCESS ) && ( sslStatus == 0 ) ) + { + SSL_free( pNetworkContext->pSsl ); + pNetworkContext->pSsl = NULL; + } + + /* Log failure or success depending on status. */ + if( returnStatus != OPENSSL_SUCCESS ) + { + LogError( ( "Failed to establish a TLS connection." ) ); + } + else + { + LogDebug( ( "Established a TLS connection." ) ); + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +OpensslStatus_t Openssl_Disconnect( NetworkContext_t * pNetworkContext ) +{ + OpensslStatus_t returnStatus = OPENSSL_SUCCESS; + SocketStatus_t socketStatus = SOCKETS_SUCCESS; + + if( pNetworkContext == NULL ) + { + LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); + returnStatus = OPENSSL_INVALID_PARAMETER; + } + else if( pNetworkContext->pSsl != NULL ) + { + /* SSL shutdown should be called twice: once to send "close notify" and + * once more to receive the peer's "close notify". */ + if( SSL_shutdown( pNetworkContext->pSsl ) == 0 ) + { + ( void ) SSL_shutdown( pNetworkContext->pSsl ); + } + + SSL_free( pNetworkContext->pSsl ); + } + else + { + /* Empty else. */ + } + + /* Tear down the socket connection. */ + socketStatus = Sockets_Disconnect( pNetworkContext->socketDescriptor ); + + if( socketStatus == SOCKETS_INVALID_PARAMETER ) + { + returnStatus = OPENSSL_INVALID_PARAMETER; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +int32_t Openssl_Recv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + + /* SSL read of data. */ + bytesReceived = ( int32_t ) SSL_read( pNetworkContext->pSsl, + pBuffer, + bytesToRecv ); + + if( bytesReceived <= 0 ) + { + LogError( ( "SSL_read of OpenSSL failed to receive data: " + " status=%d.", bytesReceived ) ); + } + + return bytesReceived; +} +/*-----------------------------------------------------------*/ + +int32_t Openssl_Send( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ) +{ + int32_t bytesSent = 0; + + /* SSL write of data. */ + bytesSent = ( int32_t ) SSL_write( pNetworkContext->pSsl, + pBuffer, + bytesToSend ); + + if( bytesSent <= 0 ) + { + LogError( ( "SSL_write of OpenSSL failed to send data: " + " status=%d.", bytesSent ) ); + } + + return bytesSent; +} +/*-----------------------------------------------------------*/ diff --git a/platform/posix/transport/src/plaintext_posix.c b/platform/posix/transport/src/plaintext_posix.c new file mode 100644 index 0000000000..a3dc2e8c38 --- /dev/null +++ b/platform/posix/transport/src/plaintext_posix.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* POSIX socket includes. */ +#include +#include + +#include "plaintext_posix.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Defined by transport layer to check send or receive error. + */ +extern int errno; + +/*-----------------------------------------------------------*/ + +/** + * @brief Log possible error from send/recv. + */ +static void logTransportError( void ); + +/*-----------------------------------------------------------*/ + +static void logTransportError( void ) +{ + switch( errno ) + { + case EBADF: + LogError( ( "The socket argument is not a valid file descriptor." ) ); + break; + + case ECONNRESET: + LogError( ( "A connection was forcibly closed by a peer." ) ); + break; + + case EDESTADDRREQ: + LogError( ( "The socket is not connection-mode and no peer address is set." ) ); + break; + + case EINTR: + LogError( ( "A signal interrupted send/recv." ) ); + break; + + case EINVAL: + LogError( ( "The MSG_OOB flag is set and no out-of-band data is available." ) ); + break; + + case ENOTCONN: + LogError( ( "A send/receive is attempted on a connection-mode socket that is not connected." ) ); + break; + + case ENOTSOCK: + LogError( ( "The socket argument does not refer to a socket." ) ); + break; + + case EOPNOTSUPP: + LogError( ( "The specified flags are not supported for this socket type or protocol." ) ); + break; + + case ETIMEDOUT: + LogError( ( "The connection timed out during connection establishment, or due to a transmission timeout on active connection." ) ); + break; + + case EMSGSIZE: + LogError( ( "The message is too large to be sent all at once, as the socket requires." ) ); + break; + + case EPIPE: + LogError( ( "The socket is shut down for writing, or the socket is connection-mode and is no longer connected. In the latter case, and if the socket is of type SOCK_STREAM or SOCK_SEQPACKET and the MSG_NOSIGNAL flag is not set, the SIGPIPE signal is generated to the calling thread." ) ); + break; + } +} +/*-----------------------------------------------------------*/ + +SocketStatus_t Plaintext_Connect( NetworkContext_t * pNetworkContext, + const ServerInfo_t * pServerInfo, + uint32_t sendTimeoutMs, + uint32_t recvTimeoutMs ) +{ + return Sockets_Connect( &pNetworkContext->socketDescriptor, + pServerInfo, + sendTimeoutMs, + recvTimeoutMs ); +} +/*-----------------------------------------------------------*/ + +SocketStatus_t Plaintext_Disconnect( const NetworkContext_t * pNetworkContext ) +{ + return Sockets_Disconnect( pNetworkContext->socketDescriptor ); +} +/*-----------------------------------------------------------*/ + +int32_t Plaintext_Recv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + int32_t bytesReceived = 0; + + bytesReceived = recv( pNetworkContext->socketDescriptor, pBuffer, bytesToRecv, 0 ); + + if( bytesReceived == 0 ) + { + /* Server closed the connection, treat it as an error. */ + bytesReceived = -1; + } + else if( bytesReceived < 0 ) + { + logTransportError(); + + /* Check if it was time out. */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate nothing to receive. */ + bytesReceived = 0; + } + } + else + { + /* Empty else. */ + } + + return bytesReceived; +} +/*-----------------------------------------------------------*/ + +int32_t Plaintext_Send( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ) +{ + int32_t bytesSent = 0; + + bytesSent = send( pNetworkContext->socketDescriptor, pBuffer, bytesToSend, 0 ); + + if( bytesSent < 0 ) + { + logTransportError(); + + /* Check if it was time out */ + if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) + { + /* Set return value to 0 to indicate that send had timed out. */ + bytesSent = 0; + } + } + + return bytesSent; +} +/*-----------------------------------------------------------*/ diff --git a/platform/posix/transport/src/sockets_posix.c b/platform/posix/transport/src/sockets_posix.c new file mode 100644 index 0000000000..eb475f818c --- /dev/null +++ b/platform/posix/transport/src/sockets_posix.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include + +/* POSIX sockets includes. */ +#include +#include +#include +#include +#include +#include + +#include "sockets_posix.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Number of DNS records to attempt a connection. + * + * @note Negative value implies an attempt to connect to all DNS records + * until successful. + */ +#define NUM_DNS_RECORDS_TO_TRY ( -1 ) + +/** + * @brief Number of milliseconds in one second. + */ +#define ONE_SEC_TO_MS ( 1000 ) + +/** + * @brief Number of microseconds in one millisecond. + */ +#define ONE_MS_TO_US ( 1000 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Defined by transport layer to check send or receive error. + */ +extern int errno; + +/*-----------------------------------------------------------*/ + +/** + * @brief Resolve a host name. + * + * @param[in] pHostName Server host name. + * @param[in] hostNameLength Length associated with host name. + * @param[out] pListHead The output parameter to return the list containing + * resolved DNS records. + * + * @return #SOCKETS_SUCCESS if successful; #SOCKETS_DNS_FAILURE, #SOCKETS_CONNECT_FAILURE on error. + */ +static SocketStatus_t resolveHostName( const char * pHostName, + size_t hostNameLength, + struct addrinfo ** pListHead ); + +/** + * @brief Traverse list of DNS records until a connection is established. + * + * @param[in] pListHead List containing resolved DNS records. + * @param[in] pHostName Server host name. + * @param[in] hostNameLength Length associated with host name. + * @param[in] port Server port in host-order. + * @param[out] pTcpSocket The output parameter to return the created socket. + * @param[in] maxAttempts Number of DNS records to attempt connection. + * + * @note If maxAttempts is negative, attempt to connect to all DNS records + * until successful. + * + * @return #SOCKETS_SUCCESS if successful; #SOCKETS_CONNECT_FAILURE on error. + */ +static SocketStatus_t attemptConnection( struct addrinfo * pListHead, + const char * pHostName, + size_t hostNameLength, + uint16_t port, + int * pTcpSocket, + int32_t maxAttempts ); + +/** + * @brief Log possible error using errno and return appropriate status. + * + * @return #SOCKETS_API_ERROR, #SOCKETS_INSUFFICIENT_MEMORY, #SOCKETS_INVALID_PARAMETER on error. + */ +static SocketStatus_t retreiveError( void ); + +/*-----------------------------------------------------------*/ + +static SocketStatus_t resolveHostName( const char * pHostName, + size_t hostNameLength, + struct addrinfo ** pListHead ) +{ + SocketStatus_t returnStatus = SOCKETS_SUCCESS; + int dnsStatus = -1; + struct addrinfo hints; + + assert( pHostName != NULL ); + assert( hostNameLength > 0 ); + + /* Add hints to retrieve only TCP sockets in getaddrinfo. */ + ( void ) memset( &hints, 0, sizeof( hints ) ); + + /* Address family of either IPv4 or IPv6. */ + hints.ai_family = AF_UNSPEC; + /* TCP Socket. */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* Perform a DNS lookup on the given host name. */ + dnsStatus = getaddrinfo( pHostName, NULL, &hints, pListHead ); + + if( dnsStatus == -1 ) + { + LogError( ( "Could not resolve host %.*s.\n", + ( int32_t ) hostNameLength, + pHostName ) ); + returnStatus = SOCKETS_DNS_FAILURE; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static SocketStatus_t attemptConnection( struct addrinfo * pListHead, + const char * pHostName, + size_t hostNameLength, + uint16_t port, + int * pTcpSocket, + int32_t maxAttempts ) +{ + SocketStatus_t returnStatus = SOCKETS_SUCCESS; + struct addrinfo * pIndex = NULL; + struct sockaddr * pAddrInfo; + socklen_t addrInfoLength; + uint16_t netPort = 0; + int curAttempts = 0, connectStatus = 0; + char resolvedIpAddr[ INET6_ADDRSTRLEN ]; + + assert( pListHead != NULL ); + assert( pHostName != NULL ); + assert( hostNameLength > 0 ); + assert( pTcpSocket != NULL ); + + netPort = htons( port ); + + LogDebug( ( "Performing DNS lookup: Host=%.*s.", + ( int32_t ) hostNameLength, + pHostName ) ); + + /* Attempt to connect to one of the retrieved DNS records. */ + for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + { + *pTcpSocket = socket( pIndex->ai_family, + pIndex->ai_socktype, + pIndex->ai_protocol ); + + if( *pTcpSocket == -1 ) + { + continue; + } + + pAddrInfo = pIndex->ai_addr; + + if( pAddrInfo->sa_family == AF_INET ) + { + /* Store IPv4 in string to log. */ + ( ( struct sockaddr_in * ) pAddrInfo )->sin_port = netPort; + addrInfoLength = sizeof( struct sockaddr_in ); + ( void ) inet_ntop( pAddrInfo->sa_family, + &( ( struct sockaddr_in * ) pAddrInfo )->sin_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + else + { + /* Store IPv6 in string to log. */ + ( ( struct sockaddr_in6 * ) pAddrInfo )->sin6_port = netPort; + addrInfoLength = sizeof( struct sockaddr_in6 ); + ( void ) inet_ntop( pAddrInfo->sa_family, + &( ( struct sockaddr_in6 * ) pAddrInfo )->sin6_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + + LogDebug( ( "Attempting to connect to server: Host=%.*s, IP address=%s.", + ( int32_t ) hostNameLength, + pHostName, + resolvedIpAddr ) ); + + connectStatus = connect( *pTcpSocket, pAddrInfo, addrInfoLength ); + + if( connectStatus == -1 ) + { + LogWarn( ( "Failed to connect to server: Host=%.*s, IP address=%s.", + ( int32_t ) hostNameLength, + pHostName, + resolvedIpAddr ) ); + close( *pTcpSocket ); + } + else + { + LogDebug( ( "Connected to IP address: %s.", + resolvedIpAddr ) ); + break; + } + + curAttempts += 1; + + if( ( maxAttempts >= 0 ) && ( curAttempts >= maxAttempts ) ) + { + /* Fail if no connection could be established. */ + LogError( ( "Could not connect to any resolved IP address from %.*s " + "after %d attempts.", + ( int32_t ) hostNameLength, + pHostName, + curAttempts ) ); + returnStatus = SOCKETS_CONNECT_FAILURE; + break; + } + } + + if( pIndex == NULL ) + { + /* Fail if no connection could be established. */ + LogError( ( "Could not connect to any resolved IP address from %.*s.", + ( int32_t ) hostNameLength, + pHostName ) ); + returnStatus = SOCKETS_CONNECT_FAILURE; + } + else + { + LogDebug( ( "Established TCP connection: Server=%.*s.\n", + ( int32_t ) hostNameLength, + pHostName ) ); + returnStatus = SOCKETS_SUCCESS; + } + + freeaddrinfo( pListHead ); + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static SocketStatus_t retreiveError( void ) +{ + SocketStatus_t returnStatus = SOCKETS_API_ERROR; + + switch( errno ) + { + case EBADF: + LogError( ( "The socket argument is not a valid file descriptor." ) ); + break; + + case EDOM: + LogError( ( "The send and receive timeout values are too big to fit " + "into the timeout fields in the socket structure." ) ); + break; + + case EINVAL: + LogError( ( "The specified option is invalid at the specified " + "socket level or the socket has been shut down." ) ); + break; + + case EISCONN: + LogError( ( "The socket is already connected, and a specified option " + "cannot be set while the socket is connected." ) ); + break; + + case ENOPROTOOPT: + LogError( ( "The option is not supported by the protocol." ) ); + break; + + case ENOTSOCK: + LogError( ( "The socket argument does not refer to a socket." ) ); + break; + + case ENOMEM: + LogError( ( "There was insufficient memory available for the " + "operation to complete." ) ); + break; + + case ENOBUFS: + LogError( ( "Insufficient resources are available in the system to " + "complete the call." ) ); + break; + } + + if( ( errno == ENOMEM ) || ( errno == ENOBUFS ) ) + { + returnStatus = SOCKETS_INSUFFICIENT_MEMORY; + } + else if( ( errno == ENOTSOCK ) || ( errno == EDOM ) || ( errno == EBADF ) ) + { + returnStatus = SOCKETS_INVALID_PARAMETER; + } + else + { + /* Empty else. */ + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +SocketStatus_t Sockets_Connect( int * pTcpSocket, + const ServerInfo_t * pServerInfo, + uint32_t sendTimeoutMs, + uint32_t recvTimeoutMs ) +{ + SocketStatus_t returnStatus = SOCKETS_SUCCESS; + struct addrinfo * pListHead = NULL; + struct timeval transportTimeout; + int setTimeoutStatus = -1; + + if( pServerInfo->pHostName == NULL ) + { + LogError( ( "Parameter check failed: pHostName is NULL." ) ); + returnStatus = SOCKETS_INVALID_PARAMETER; + } + else if( pTcpSocket == NULL ) + { + LogError( ( "Parameter check failed: pTcpSocket is NULL." ) ); + returnStatus = SOCKETS_INVALID_PARAMETER; + } + else if( pServerInfo->hostNameLength == 0 ) + { + LogError( ( "Parameter check failed: hostNameLength must be greater than 0." ) ); + returnStatus = SOCKETS_INVALID_PARAMETER; + } + else + { + /* Empty else. */ + } + + if( returnStatus == SOCKETS_SUCCESS ) + { + returnStatus = resolveHostName( pServerInfo->pHostName, + pServerInfo->hostNameLength, + &pListHead ); + } + + if( returnStatus == SOCKETS_SUCCESS ) + { + returnStatus = attemptConnection( pListHead, + pServerInfo->pHostName, + pServerInfo->hostNameLength, + pServerInfo->port, + pTcpSocket, + NUM_DNS_RECORDS_TO_TRY ); + } + + /* Set the send timeout. */ + if( returnStatus == SOCKETS_SUCCESS ) + { + transportTimeout.tv_sec = ( sendTimeoutMs / ONE_SEC_TO_MS ); + transportTimeout.tv_usec = ( ONE_MS_TO_US * ( sendTimeoutMs % ONE_SEC_TO_MS ) ); + + setTimeoutStatus = setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_SNDTIMEO, + &transportTimeout, + sizeof( transportTimeout ) ); + + if( setTimeoutStatus < 0 ) + { + LogError( ( "Setting socket send timeout failed." ) ); + returnStatus = retreiveError(); + } + } + + /* Set the receive timeout. */ + if( returnStatus == SOCKETS_SUCCESS ) + { + transportTimeout.tv_sec = ( recvTimeoutMs / ONE_SEC_TO_MS ); + transportTimeout.tv_usec = ( ONE_MS_TO_US * ( recvTimeoutMs % ONE_SEC_TO_MS ) ); + + setTimeoutStatus = setsockopt( *pTcpSocket, + SOL_SOCKET, + SO_RCVTIMEO, + &transportTimeout, + sizeof( transportTimeout ) ); + + if( setTimeoutStatus < 0 ) + { + LogError( ( "Setting socket receive timeout failed." ) ); + returnStatus = retreiveError(); + } + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +SocketStatus_t Sockets_Disconnect( int tcpSocket ) +{ + SocketStatus_t returnStatus = SOCKETS_SUCCESS; + + if( tcpSocket > 0 ) + { + ( void ) shutdown( tcpSocket, SHUT_RDWR ); + ( void ) close( tcpSocket ); + } + else + { + LogError( ( "Parameter check failed: tcpSocket was negative." ) ); + returnStatus = SOCKETS_INVALID_PARAMETER; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ diff --git a/platform/posix/transport/transportFilePaths.cmake b/platform/posix/transport/transportFilePaths.cmake new file mode 100644 index 0000000000..ed4d1fc75d --- /dev/null +++ b/platform/posix/transport/transportFilePaths.cmake @@ -0,0 +1,23 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# Sockets utility source files. +set( SOCKETS_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/sockets_posix.c ) + +# Plaintext transport source files. +set( PLAINTEXT_TRANSPORT_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/plaintext_posix.c ) + +# OpenSSL transport source files. +set( OPENSSL_TRANSPORT_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/openssl_posix.c ) + +# Transport Public Include directories. +set( COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS + ${CMAKE_CURRENT_LIST_DIR}/include + ${PLATFORM_DIR}/include ) From fe1ce6a5e4bec9cdadbfb5d83a0b87e818e9b246 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 26 Jun 2020 13:00:25 -0700 Subject: [PATCH 557/844] Add CBMC proof for HTTPClient_InitializeRequestHeaders (#1012) * Full coverage for InitRequestHeaders * Update based on PR comments * Make changes from PR comments * Change ret value for validate methods from int -> bool * Remove SPDX-License-Identifier for harness.c * Add midding #include --- .../http/cbmc/include/http_cbmc_state.h | 24 ++++++++-- .../cbmc/proofs/HTTPClient_AddHeader/Makefile | 3 ++ ...PClient_InitializeRequestHeaders_harness.c | 45 +++++++++++++++++++ .../Makefile | 18 ++++++++ .../README.md | 10 +++++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 +++ .../http/cbmc/sources/http_cbmc_state.c | 41 ++++++++++++++++- 8 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-viewer.json diff --git a/libraries/standard/http/cbmc/include/http_cbmc_state.h b/libraries/standard/http/cbmc/include/http_cbmc_state.h index 90034e9e07..f07377001d 100644 --- a/libraries/standard/http/cbmc/include/http_cbmc_state.h +++ b/libraries/standard/http/cbmc/include/http_cbmc_state.h @@ -22,6 +22,8 @@ #ifndef HTTP_CBMC_STATE_H_ #define HTTP_CBMC_STATE_H_ +#include + #include "http_client.h" /** @@ -38,17 +40,31 @@ void * mallocCanFail( size_t size ); /** * @brief Allocate a request headers object. * - * @param[in] pRequestHeaders Request headers to allocate. + * @param[in] pRequestHeaders Request headers object to allocate. */ HTTPRequestHeaders_t * allocateHttpRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders ); /** * @brief Validates if a request headers object is feasible. * - * @param[in] pRequestHeaders Request headers to validate. + * @param[in] pRequestHeaders Request headers object to validate. + * + * @return true if request headers is feasible; false otherwise. + */ +bool isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ); + +/** + * @brief Allocate a request info object. + */ +HTTPRequestInfo_t * allocateHttpRequestInfo(); + +/** + * @brief Validates if a request info object is feasible. + * + * @param[in] pRequestInfo Request info object to validate. * - * @return 1 if request headers is feasible; 0 otherwise. + * @return true if request headers is feasible; 0 otherwise. */ -int isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ); +bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ); #endif /* ifndef HTTP_CBMC_STATE_H_ */ diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile index 7f4395cc8a..078a462da0 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile @@ -1,3 +1,6 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_AddHeader_harness diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c new file mode 100644 index 0000000000..7999598787 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_InitializeRequestHeaders_harness.c + * @brief Implements the proof harness for HTTPClient_InitializeRequestHeaders function. + */ + +#include "http_client.h" + +#include "http_cbmc_state.h" + +void harness() +{ + HTTPRequestHeaders_t * pRequestHeaders = NULL; + HTTPRequestInfo_t * pRequestInfo = NULL; + + /* Initialize and make assumptions for request headers object. */ + pRequestHeaders = allocateHttpRequestHeaders( pRequestHeaders ); + __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); + + /* Initialize and make assumptions for request info object. */ + pRequestInfo = allocateHttpRequestInfo( pRequestInfo ); + __CPROVER_assume( isValidHttpRequestInfo( pRequestInfo ) ); + + HTTPClient_InitializeRequestHeaders( pRequestHeaders, pRequestInfo ); +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile new file mode 100644 index 0000000000..18f33c75ac --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=HTTPClient_InitializeRequestHeaders_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += strncmp.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md new file mode 100644 index 0000000000..621bcad4b4 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md @@ -0,0 +1,10 @@ +HTTPClient_InitializeRequestHeaders proof +============== + +This directory contains a memory safety proof for HTTPClient_InitializeRequestHeaders. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-viewer.json new file mode 100644 index 0000000000..11132842bf --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "HTTPClient_InitializeRequestHeaders", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/sources/http_cbmc_state.c b/libraries/standard/http/cbmc/sources/http_cbmc_state.c index c6c59c6152..93b52bfe1c 100644 --- a/libraries/standard/http/cbmc/sources/http_cbmc_state.c +++ b/libraries/standard/http/cbmc/sources/http_cbmc_state.c @@ -24,9 +24,9 @@ HTTPRequestHeaders_t * allocateHttpRequestHeaders( HTTPRequestHeaders_t * pReque return pRequestHeaders; } -int isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ) +bool isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ) { - int isValid = 1; + bool isValid = true; if( pRequestHeaders ) { @@ -36,3 +36,40 @@ int isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ) return isValid; } + +HTTPRequestInfo_t * allocateHttpRequestInfo( HTTPRequestInfo_t * pRequestInfo ) +{ + if( pRequestInfo == NULL ) + { + pRequestInfo = mallocCanFail( sizeof( HTTPRequestInfo_t ) ); + } + + if( pRequestInfo != NULL ) + { + __CPROVER_assume( pRequestInfo->methodLen < CBMC_MAX_OBJECT_SIZE ); + pRequestInfo->method = mallocCanFail( pRequestInfo->methodLen ); + + __CPROVER_assume( pRequestInfo->hostLen < CBMC_MAX_OBJECT_SIZE ); + pRequestInfo->pHost = mallocCanFail( pRequestInfo->hostLen ); + + __CPROVER_assume( pRequestInfo->pathLen < CBMC_MAX_OBJECT_SIZE ); + pRequestInfo->pPath = mallocCanFail( pRequestInfo->pathLen ); + } + + return pRequestInfo; +} + +bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ) +{ + bool isValid = true; + + if( pRequestInfo ) + { + isValid = ( pRequestInfo->reqFlags < CBMC_MAX_OBJECT_SIZE ) && + ( pRequestInfo->methodLen < CBMC_MAX_OBJECT_SIZE ) && + ( pRequestInfo->hostLen < CBMC_MAX_OBJECT_SIZE ) && + ( pRequestInfo->pathLen < CBMC_MAX_OBJECT_SIZE ); + } + + return isValid; +} From 5897287fac5db89642acf148858f6cf4edfc1fee Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Mon, 29 Jun 2020 14:25:15 -0700 Subject: [PATCH 558/844] Initialize transport interface for MQTT init happy path test (#1023) --- libraries/standard/mqtt/utest/mqtt_utest.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index a255020e18..c28cfd1698 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -494,6 +494,7 @@ void test_MQTT_Init_Happy_Path( void ) MQTTApplicationCallbacks_t callbacks; setupCallbacks( &callbacks ); + setupTransportInterface( &transport ); mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); From ef09174ea04170c964577944c48b52488bc54bcd Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 29 Jun 2020 14:28:33 -0700 Subject: [PATCH 559/844] Add strerror unit test to HTTP for 100% branch, function and line coverage. (#1024) --- libraries/standard/http/utest/http_utest.c | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 3ae02b830c..05ba2fe69a 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -1342,4 +1342,81 @@ void test_Http_ReadHeader_Happy_Path() TEST_ASSERT_EQUAL( headerValInRespLen, valueLen ); } +/** + * @brief Test HTTPClient_strerror returns correct strings. + */ +void test_HTTPClient_strerror( void ) +{ + HTTPStatus_t status; + const char * str = NULL; + + status = HTTP_SUCCESS; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SUCCESS", str ); + + status = HTTP_INVALID_PARAMETER; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_INVALID_PARAMETER", str ); + + status = HTTP_NETWORK_ERROR; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_NETWORK_ERROR", str ); + + status = HTTP_PARTIAL_RESPONSE; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_PARTIAL_RESPONSE", str ); + + status = HTTP_NO_RESPONSE; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_NO_RESPONSE", str ); + + status = HTTP_INSUFFICIENT_MEMORY; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_INSUFFICIENT_MEMORY", str ); + + status = HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_RESPONSE_HEADERS_SIZE_LIMIT_EXCEEDED", str ); + + status = HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_EXTRANEOUS_RESPONSE_DATA", str ); + + status = HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_INVALID_CHUNK_HEADER", str ); + + status = HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_INVALID_PROTOCOL_VERSION", str ); + + status = HTTP_SECURITY_ALERT_INVALID_STATUS_CODE; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_INVALID_STATUS_CODE", str ); + + status = HTTP_SECURITY_ALERT_INVALID_CHARACTER; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_INVALID_CHARACTER", str ); + + status = HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH", str ); + + status = HTTP_PARSER_INTERNAL_ERROR; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_PARSER_INTERNAL_ERROR", str ); + + status = HTTP_HEADER_NOT_FOUND; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_HEADER_NOT_FOUND", str ); + + status = HTTP_INVALID_RESPONSE; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( "HTTP_INVALID_RESPONSE", str ); + + status = HTTP_INVALID_RESPONSE + 1; + str = HTTPClient_strerror( status ); + TEST_ASSERT_EQUAL_STRING( NULL, str ); +} + /* ========================================================================== */ From 02c62ee23e313cfd21a96b4d68899441bec16e1c Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Mon, 29 Jun 2020 14:36:59 -0700 Subject: [PATCH 560/844] Bring down complexity of sendHttpData to 8 (#1016) * Bring down complexity of sendHttpData to 8 * Fix typo in http_client.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> --- libraries/standard/http/src/http_client.c | 24 ++++++----------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 741156f363..a7d563647e 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -797,7 +797,7 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, /* MISRA Rule 11.8 flags casting away the const qualifier in the pointer * type. This rule is suppressed because when the body is of transfer * encoding chunked, the body must be copied over the chunk headers that - * preceed it. This is done to have a contigous response body. This does + * precede it. This is done to have a contiguous response body. This does * affect future parsing as the changed segment will always be before the * next place to parse. */ /* coverity[misra_c_2012_rule_11_8_violation] */ @@ -1537,14 +1537,7 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, assert( pData != NULL ); /* Loop until all data is sent. */ - - /* MISCRA C-2012 Rule 15.4 flags multiple break statements in the while loop - * below. There are two only error conditions when reading data from the - * network which both need the loop to terminate. Both of these conditions - * necessitate different error logs, so two different break statements are - * required. */ - /* coverity[misra_c_2012_rule_15_4_violation] */ - while( bytesRemaining > 0UL ) + while( ( bytesRemaining > 0UL ) && ( returnStatus != HTTP_NETWORK_ERROR ) ) { transportStatus = pTransport->send( pTransport->pContext, pIndex, @@ -1557,25 +1550,21 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, " returned error: TransportStatus=%d", transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; - break; } else if( ( size_t ) transportStatus > bytesRemaining ) { - LogError( ( "Failed to send HTTP data: Transport send()" - " wrote more data than what was expected: " - "BytesSent=%d, BytesRemaining=%lu", + LogError( ( "Failed to send HTTP data: Transport send() wrote more data " + "than what was expected: BytesSent=%d, BytesRemaining=%lu", transportStatus, bytesRemaining ) ); returnStatus = HTTP_NETWORK_ERROR; - break; } else { bytesRemaining -= ( size_t ) transportStatus; pIndex += transportStatus; LogDebug( ( "Sent HTTP data over the transport: " - "BytesSent=%d, BytesRemaining=%lu, " - "TotalBytesSent=%lu", + "BytesSent=%d, BytesRemaining=%lu, TotalBytesSent=%lu", transportStatus, bytesRemaining, ( unsigned long ) ( dataLen - bytesRemaining ) ) ); @@ -1584,8 +1573,7 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, if( returnStatus == HTTP_SUCCESS ) { - LogDebug( ( "Sent HTTP data over the transport: " - "BytesSent=%d", + LogDebug( ( "Sent HTTP data over the transport: BytesSent=%d", transportStatus ) ); } From a50d02c54d52843444cf2b6228fc3effd1dc56ca Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Mon, 29 Jun 2020 14:53:54 -0700 Subject: [PATCH 561/844] Add unit test script for CI (#1018) * Add unit test script for CI --- tools/ci/ci_utest.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100755 tools/ci/ci_utest.sh diff --git a/tools/ci/ci_utest.sh b/tools/ci/ci_utest.sh new file mode 100755 index 0000000000..38a8244997 --- /dev/null +++ b/tools/ci/ci_utest.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +rm -fr build/ +mkdir build + +cmake -S . -B build/ -DBUILD_TESTS=1 -G "Unix Makefiles" + +# Gather all the make targets and select unit tests. They have utest in the name. +make help -C build/ | grep utest | tr -d '. ' | xargs make -C build/ + +STATUS= +for t in build/bin/tests/* +do + echo "= $( basename $t ) =" + ./$t + + if [ $? -ne "0" ] ; then + STATUS=1 + fi + +done + +if [ "$STATUS" = "1" ] ; then + exit 1 +fi From 6d4e47f3e8f93a2e7e3f74b79276aa034161d09d Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 29 Jun 2020 17:33:28 -0700 Subject: [PATCH 562/844] MQTT - Handle resends (#1007) Handling the resend and receive of duplicate packets when an MQTT session is reestablished. These are the cases that is handled in this PR, When a session is reestablished, Outgoing publishes: Resend PUBLISHes for which and ack is not received(PUBACK and PUBREC). Resend PUBRELs for PUBLISHes for which PUBCOMP was not received. Incoming publishes: Handle duplicate QoS1 and QoS2 PUBLISHes Handle duplicate PUBRELs. This change also handles the message ordering requirement in MQTT3.1.1 spec while re sending publishes and PUBRELs. --- libraries/standard/mqtt/include/mqtt.h | 9 +- libraries/standard/mqtt/include/mqtt_state.h | 29 +- libraries/standard/mqtt/src/mqtt.c | 406 +++++++--- .../standard/mqtt/src/mqtt_lightweight.c | 11 +- libraries/standard/mqtt/src/mqtt_state.c | 696 ++++++++++++++---- .../mqtt/utest/mqtt_lightweight_utest.c | 4 +- .../standard/mqtt/utest/mqtt_state_utest.c | 412 +++++++++-- libraries/standard/mqtt/utest/mqtt_utest.c | 357 +++++++-- 8 files changed, 1564 insertions(+), 360 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index f4c215ca8c..0cde26d596 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -26,6 +26,13 @@ #include "mqtt_config.h" #include "mqtt_lightweight.h" +/** + * @brief Invalid packet identifier. + * + * Zero is an invalid packet identifier as per MQTT v3.1.1 spec. + */ +#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) + struct MQTTApplicationCallbacks; typedef struct MQTTApplicationCallbacks MQTTApplicationCallbacks_t; @@ -125,7 +132,7 @@ struct MQTTContext * @brief Initialize an MQTT context. * * This function must be called on an MQTT context before any other function. - * + * * @note The getTime callback function must be defined. If there is no time * implementation, it is the responsibility of the application to provide a * dummy function to always return 0, and provide 0 timeouts for functions. This diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index 00824c985f..75a54de7d0 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -113,15 +113,34 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, MQTTPublishState_t * pNewState ); /** - * @brief Get the packet ID and index of a publish in a specified state. + * @brief Get the packet ID of next pending PUBREL ack to be resent. + * + * This function will need to be called to get the packet for which a PUBREL + * need to be sent when a session is reestablished. Calling this function + * repeatedly until packet id is 0 will give all the packets for which + * a PUBREL need to be resent in the correct order. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in,out] pCursor Index at which to start searching. + * @param[out] pState State indicating that PUBREL packet need to be sent. + */ +uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, + MQTTStateCursor_t * pCursor, + MQTTPublishState_t * pState ); + +/** + * @brief Get the packet ID of next pending publish to be resent. + * + * This function will need to be called to get the packet for which a publish + * need to be sent when a session is reestablished. Calling this function + * repeatedly until packet id is 0 will give all the packets for which + * a publish need to be resent in the correct order. * * @param[in] pMqttContext Initialized MQTT context. - * @param[in] searchState The state to search for. * @param[in,out] pCursor Index at which to start searching. */ -uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, - MQTTPublishState_t searchState, - MQTTStateCursor_t * pCursor ); +uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, + MQTTStateCursor_t * pCursor ); /** * @brief State to string conversion for state engine. diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index e04f814ca7..0c9f39f6a1 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -149,6 +149,17 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ); static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket ); +/** + * @brief Handle received MQTT publish acks. + * + * @param[in] pContext MQTT Connection context. + * @param[in] pIncomingPacket Incoming packet. + * + * @return MQTTSuccess, MQTTIllegalState, or deserialization error. + */ +static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, + MQTTPacketInfo_t * pIncomingPacket ); + /** * @brief Handle received MQTT ack. * @@ -232,6 +243,47 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket, bool * pSessionPresent ); +/** + * @brief Resends pending acks for a re-established MQTT session. + * + * @param[in] pContext Initialized MQTT context. + * + * @return #MQTTSendFailed if transport send failed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t resendPendingAcks( MQTTContext_t * pContext ); + +/** + * @brief Serializes a PUBLISH message. + * + * @brief param[in] pContext Initialized MQTT context. + * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @brief param[in] packetId Packet Id of the publish packet. + * @brief param[out] pHeaderSize Size of the serialized PUBLISH header. + * + * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t serializePublish( const MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, + uint16_t packetId, + size_t * const pHeaderSize ); + +/** + * @brief Function to validate #MQTT_Publish parameters. + * + * @brief param[in] pContext Initialized MQTT context. + * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @brief param[in] packetId Packet Id for the MQTT PUBLISH packet. + * + * @return #MQTTBadParameter if invalid parameters are passed; + * #MQTTSuccess otherwise. + */ +static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, + uint16_t packetId ); + /*-----------------------------------------------------------*/ static int32_t sendPacket( MQTTContext_t * pContext, @@ -580,7 +632,7 @@ static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext, { LogError( ( "Failed to send ACK packet: PacketType=%02x, " "SentBytes=%d, " - "PacketSize=%lu", + "PacketSize=%lu.", packetTypeByte, bytesSent, MQTT_PUBLISH_ACK_PACKET_SIZE ) ); @@ -633,6 +685,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTTPublishState_t publishRecordState = MQTTStateNull; uint16_t packetIdentifier = 0U; MQTTPublishInfo_t publishInfo; + bool duplicatePublish = false; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); @@ -647,18 +700,73 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTT_RECEIVE, publishInfo.qos, &publishRecordState ); - LogInfo( ( "State record updated. New state=%d.", - publishRecordState ) ); + + if( status == MQTTSuccess ) + { + LogInfo( ( "State record updated. New state=%s.", + MQTT_State_strerror( publishRecordState ) ) ); + } + + /* Different cases in which an incoming publish with duplicate flag is + * handled are as listed below. + * 1. No collision - This is the first instance of the incoming publish + * packet received or an earlier received packet state is lost. This + * will be handled as a new incoming publish for both QoS1 and QoS2 + * publishes. + * 2. Collision - The incoming packet was received before and a state + * record is present in the state engine. For QoS1 and QoS2 publishes + * this case can happen at 2 different cases and handling is + * different. + * a. QoS1 - If a PUBACK is not successfully sent for the incoming + * publish due to a connection issue, it can result in broker + * sending out a duplicate publish with dup flag set, when a + * session is reestablished. It can result in a collision in + * state engine. This will be handled by processing the incoming + * publish as a new publish ignoring the + * #MQTTStateCollision status from the state engine. The publish + * data is not passed to the application. + * b. QoS2 - If a PUBREC is not successfully sent for the incoming + * publish or the PUBREC sent is not successfully received by the + * broker due to a connection issue, it can result in broker + * sending out a duplicate publish with dup flag set, when a + * session is reestablished. It can result in a collision in + * state engine. This will be handled by ignoring the + * #MQTTStateCollision status from the state engine. The publish + * data is not passed to the application. */ + else if( ( status == MQTTStateCollision ) && ( publishInfo.dup == true ) ) + { + status = MQTTSuccess; + duplicatePublish = true; + + /* Calculate the state for the ack packet that needs to be sent out + * for the duplicate incoming publish. */ + publishRecordState = MQTT_CalculateStatePublish( MQTT_RECEIVE, + publishInfo.qos ); + LogDebug( ( "Incoming publish packet with packet id %u already exists.", + packetIdentifier ) ); + } + else + { + LogError( ( "Error in updating publish state for incoming publish with packet id %u." + " Error is %s", + packetIdentifier, + MQTT_Status_strerror( status ) ) ); + } } if( status == MQTTSuccess ) { /* Invoke application callback to hand the buffer over to application - * before sending acks. */ - pContext->callbacks.appCallback( pContext, - pIncomingPacket, - packetIdentifier, - &publishInfo ); + * before sending acks. + * Application callback will be invoked for all publishes, except for + * duplicate incoming publishes. */ + if( duplicatePublish == false ) + { + pContext->callbacks.appCallback( pContext, + pIncomingPacket, + packetIdentifier, + &publishInfo ); + } /* Send PUBACK or PUBREC if necessary. */ status = sendPublishAcks( pContext, @@ -671,12 +779,70 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ +static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, + MQTTPacketInfo_t * pIncomingPacket ) +{ + MQTTStatus_t status = MQTTBadResponse; + MQTTPublishState_t publishRecordState = MQTTStateNull; + uint16_t packetIdentifier; + MQTTPubAckType_t ackType; + MQTTEventCallback_t appCallback; + + assert( pContext != NULL ); + assert( pIncomingPacket != NULL ); + assert( pContext->callbacks.appCallback != NULL ); + + appCallback = pContext->callbacks.appCallback; + + ackType = getAckFromPacketType( pIncomingPacket->type ); + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); + LogInfo( ( "Ack packet deserialized with result: %s.", + MQTT_Status_strerror( status ) ) ); + + if( status == MQTTSuccess ) + { + status = MQTT_UpdateStateAck( pContext, + packetIdentifier, + ackType, + MQTT_RECEIVE, + &publishRecordState ); + + if( status == MQTTSuccess ) + { + LogInfo( ( "State record updated. New state=%s.", + MQTT_State_strerror( publishRecordState ) ) ); + } + else + { + LogError( ( "Updating the state engine for packet id %u" + " failed with error %s.", + packetIdentifier, + MQTT_Status_strerror( status ) ) ); + } + } + + if( status == MQTTSuccess ) + { + /* Invoke application callback to hand the buffer over to application + * before sending acks. */ + appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); + + /* Send PUBREL or PUBCOMP if necessary. */ + status = sendPublishAcks( pContext, + packetIdentifier, + publishRecordState ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, MQTTPacketInfo_t * pIncomingPacket, bool manageKeepAlive ) { MQTTStatus_t status = MQTTBadResponse; - MQTTPublishState_t publishRecordState = MQTTStateNull; uint16_t packetIdentifier; /* Need a dummy variable for MQTT_DeserializeAck(). */ bool sessionPresent = false; @@ -687,7 +853,6 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, * sending any PUBREL or PUBCOMP. However, for other cases, we invoke it * at the end to reduce the complexity of this function. */ bool invokeAppCallback = false; - MQTTPubAckType_t ackType; MQTTEventCallback_t appCallback = NULL; assert( pContext != NULL ); @@ -701,32 +866,9 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_PUBREC: case MQTT_PACKET_TYPE_PUBREL: case MQTT_PACKET_TYPE_PUBCOMP: - ackType = getAckFromPacketType( pIncomingPacket->type ); - status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); - LogInfo( ( "Ack packet deserialized with result: %d.", status ) ); - - if( status == MQTTSuccess ) - { - status = MQTT_UpdateStateAck( pContext, - packetIdentifier, - ackType, - MQTT_RECEIVE, - &publishRecordState ); - LogInfo( ( "State record updated. New state=%d.", - publishRecordState ) ); - } - - if( status == MQTTSuccess ) - { - /* Invoke application callback to hand the buffer over to application - * before sending acks. */ - appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); - /* Send PUBREL or PUBCOMP if necessary. */ - status = sendPublishAcks( pContext, - packetIdentifier, - publishRecordState ); - } + /* Handle all the publish acks. */ + status = handlePublishAcks( pContext, pIncomingPacket ); break; @@ -1012,6 +1154,99 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, /*-----------------------------------------------------------*/ +static MQTTStatus_t resendPendingAcks( MQTTContext_t * pContext ) +{ + MQTTStatus_t status = MQTTSuccess; + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + uint16_t packetId = MQTT_PACKET_ID_INVALID; + MQTTPublishState_t state = MQTTStateNull; + + assert( pContext != NULL ); + + /* Get the next packet Id for which a PUBREL need to be resent. */ + packetId = MQTT_PubrelToResend( pContext, &cursor, &state ); + + /* Resend all the PUBREL acks after session is reestablished. */ + while( ( packetId != MQTT_PACKET_ID_INVALID ) && + ( status == MQTTSuccess ) ) + { + status = sendPublishAcks( pContext, packetId, state ); + + packetId = MQTT_PubrelToResend( pContext, &cursor, &state ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t serializePublish( const MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, + uint16_t packetId, + size_t * const pHeaderSize ) +{ + MQTTStatus_t status = MQTTSuccess; + size_t remainingLength = 0UL, packetSize = 0UL; + + assert( pContext != NULL ); + assert( pPublishInfo != NULL ); + assert( pHeaderSize != NULL ); + + /* Get the remaining length and packet size.*/ + status = MQTT_GetPublishPacketSize( pPublishInfo, + &remainingLength, + &packetSize ); + LogDebug( ( "PUBLISH packet size is %lu and remaining length is %lu.", + packetSize, + remainingLength ) ); + + if( status == MQTTSuccess ) + { + status = MQTT_SerializePublishHeader( pPublishInfo, + packetId, + remainingLength, + &( pContext->networkBuffer ), + pHeaderSize ); + LogDebug( ( "Serialized PUBLISH header size is %lu.", + *pHeaderSize ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, + const MQTTPublishInfo_t * pPublishInfo, + uint16_t packetId ) +{ + MQTTStatus_t status = MQTTSuccess; + + /* Validate arguments. */ + if( ( pContext == NULL ) || ( pPublishInfo == NULL ) ) + { + LogError( ( "Argument cannot be NULL: pContext=%p, " + "pPublishInfo=%p.", + pContext, + pPublishInfo ) ); + status = MQTTBadParameter; + } + else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) + { + LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.", + pPublishInfo->qos ) ); + status = MQTTBadParameter; + } + else + { + /* Empty else MISRA 15.7 */ + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, const MQTTTransportInterface_t * pTransportInterface, const MQTTApplicationCallbacks_t * pCallbacks, @@ -1131,6 +1366,12 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, pSessionPresent ); } + /* Resend all the PUBREL when reestablishing a session. */ + if( ( status == MQTTSuccess ) && ( *pSessionPresent == true ) ) + { + status = resendPendingAcks( pContext ); + } + if( status == MQTTSuccess ) { LogInfo( ( "MQTT connection established with the broker." ) ); @@ -1211,60 +1452,34 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ) { - size_t remainingLength = 0UL, packetSize = 0UL, headerSize = 0UL; - MQTTStatus_t status = MQTTSuccess; + size_t headerSize = 0UL; MQTTPublishState_t publishStatus = MQTTStateNull; /* Validate arguments. */ - if( ( pContext == NULL ) || ( pPublishInfo == NULL ) ) - { - LogError( ( "Argument cannot be NULL: pContext=%p, " - "pPublishInfo=%p.", - pContext, - pPublishInfo ) ); - status = MQTTBadParameter; - } - else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) - { - LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.", - pPublishInfo->qos ) ); - status = MQTTBadParameter; - } - else - { - /* Empty else MISRA 15.7 */ - } - - if( status == MQTTSuccess ) - { - /* Get the remaining length and packet size.*/ - status = MQTT_GetPublishPacketSize( pPublishInfo, - &remainingLength, - &packetSize ); - LogDebug( ( "PUBLISH packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ) ); - } + MQTTStatus_t status = validatePublishParams( pContext, pPublishInfo, packetId ); if( status == MQTTSuccess ) { - status = MQTT_SerializePublishHeader( pPublishInfo, - packetId, - remainingLength, - &( pContext->networkBuffer ), - &headerSize ); - LogDebug( ( "Serialized PUBLISH header size is %lu.", - headerSize ) ); + /* Serialize PUBLISH packet. */ + status = serializePublish( pContext, + pPublishInfo, + packetId, + &headerSize ); } - if( status == MQTTSuccess ) + if( ( status == MQTTSuccess ) && ( pPublishInfo->qos > MQTTQoS0 ) ) { /* Reserve state for publish message. Only to be done for QoS1 or QoS2. */ - if( pPublishInfo->qos > MQTTQoS0 ) + status = MQTT_ReserveState( pContext, + packetId, + pPublishInfo->qos ); + + /* State already exists for a duplicate packet. + * If a state doesn't exist, it will be handled as a new publish in + * state engine. */ + if( ( status == MQTTStateCollision ) && ( pPublishInfo->dup == true ) ) { - status = MQTT_ReserveState( pContext, - packetId, - pPublishInfo->qos ); + status = MQTTSuccess; } } @@ -1274,38 +1489,31 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, status = sendPublish( pContext, pPublishInfo, headerSize ); - - /* TODO. When a publish fails, the reserved state has to be cleaned - * up. This will have to be done once an API in state machine is - * available. */ } - if( status == MQTTSuccess ) + if( ( status == MQTTSuccess ) && ( pPublishInfo->qos > MQTTQoS0 ) ) { /* Update state machine after PUBLISH is sent. * Only to be done for QoS1 or QoS2. */ - if( pPublishInfo->qos > MQTTQoS0 ) - { - status = MQTT_UpdateStatePublish( pContext, - packetId, - MQTT_SEND, - pPublishInfo->qos, - &publishStatus ); + status = MQTT_UpdateStatePublish( pContext, + packetId, + MQTT_SEND, + pPublishInfo->qos, + &publishStatus ); - if( status != MQTTSuccess ) - { - LogError( ( "Update state for publish failed with status =%s." - " However PUBLISH packet is sent to the broker." - " Any further handling of ACKs for the packet Id" - " will fail.", - MQTT_Status_strerror( status ) ) ); - } + if( status != MQTTSuccess ) + { + LogError( ( "Update state for publish failed with status %s." + " However PUBLISH packet was sent to the broker." + " Any further handling of ACKs for the packet Id" + " will fail.", + MQTT_Status_strerror( status ) ) ); } } if( status != MQTTSuccess ) { - LogError( ( "MQTT PUBLISH failed with status=%s.", + LogError( ( "MQTT PUBLISH failed with status %s.", MQTT_Status_strerror( status ) ) ); } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 8070e11792..302dd6f1b3 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -694,14 +694,9 @@ static MQTTStatus_t processPublishFlags( uint8_t publishFlags, LogDebug( ( "Retain bit is %d.", pPublishInfo->retain ) ); /* Parse the DUP bit. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) ) - { - LogDebug( ( "DUP is 1." ) ); - } - else - { - LogDebug( ( "DUP is 0." ) ); - } + pPublishInfo->dup = ( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) ) ? true : false; + + LogDebug( ( "DUP bit is %d.", pPublishInfo->dup ) ); } return status; diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index 38f5c98ec8..8afd93050a 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -20,9 +20,35 @@ */ #include +#include #include "mqtt_state.h" -#define MQTT_PACKET_ID_INVALID ( uint16_t ) 0U +/*-----------------------------------------------------------*/ + +/** + * @brief Create a 16-bit bitmap with bit set at specified position. + * + * @param[in] position The position at which the bit need to be set. + */ +#define UINT16_BITMAP_BIT_SET_AT( position ) ( ( uint16_t ) 0x01U << ( ( uint16_t ) position ) ) + +/** + * @brief Set a bit in an 16-bit unsigned integer. + * + * @param[in] x The 16-bit unsigned integer to set a bit. + * @param[in] position The position at which the bit need to be set. + */ +#define UINT16_SET_BIT( x, position ) ( ( x ) = ( uint16_t ) ( ( x ) | ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) ) + +/** + * @brief Macro for checking if a bit is set in a 16-bit unsigned integer. + * + * @param[in] x The unsigned 16-bit integer to check. + * @param[in] position Which bit to check. + */ +#define UINT16_CHECK_BIT( x, position ) ( ( ( x ) & ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) == ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) + +/*-----------------------------------------------------------*/ /** * @brief Test if a transition to new state is possible, when dealing with PUBLISHes. @@ -83,6 +109,19 @@ static size_t findInRecord( const MQTTPubAckInfo_t * records, MQTTQoS_t * pQos, MQTTPublishState_t * pCurrentState ); +/** + * @brief Compact records. + * + * Records are arranged in the relative order to maintain message ordering. + * This will lead to fragmentation and this function will help in defragmenting + * the records array. + * + * @param[in] records State record array. + * @param[in] recordCount Length of record array. + */ +static void compactRecords( MQTTPubAckInfo_t * records, + size_t recordCount ); + /** * @brief Store a new entry in the state record. * @@ -113,6 +152,59 @@ static void updateRecord( MQTTPubAckInfo_t * records, MQTTPublishState_t newState, bool shouldDelete ); +/** + * @brief Get the packet ID and index of an outgoing publish in specified + * states. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] searchStates The states to search for in 2-byte bit map. + * @param[in,out] pCursor Index at which to start searching. + */ +static uint16_t stateSelect( const MQTTContext_t * pMqttContext, + uint16_t searchStates, + MQTTStateCursor_t * pCursor ); + +/** + * @brief Update the state records for an ACK after state transition + * validations. + * + * @param[in] records State records pointer. + * @param[in] recordIndex Index at which the record is stored. + * @param[in] packetId Packet id of the packet. + * @param[in] currentState Current state of the publish record. + * @param[in] newState New state of the publish. + * + * @return #MQTTIllegalState, or #MQTTSuccess. + */ +static MQTTStatus_t updateStateAck( MQTTPubAckInfo_t * records, + size_t recordIndex, + uint16_t packetId, + MQTTPublishState_t currentState, + MQTTPublishState_t newState ); + +/** + * @brief Update the state record for a PUBLISH packet after validating + * the state transitions. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] recordIndex Index in state records at which publish record exists. + * @param[in] packetId ID of the PUBLISH packet. + * @param[in] opType Send or Receive. + * @param[in] qos 0, 1, or 2. + * @param[in] currentState Current state of the publish record. + * @param[in] newState New state of the publish record. + * + * @return #MQTTIllegalState, #MQTTStateCollision or #MQTTSuccess. + */ +static MQTTStatus_t updateStatePublish( MQTTContext_t * pMqttContext, + size_t recordIndex, + uint16_t packetId, + MQTTStateOperation_t opType, + MQTTQoS_t qos, + MQTTPublishState_t currentState, + MQTTPublishState_t newState ); + +/*-----------------------------------------------------------*/ static bool validateTransitionPublish( MQTTPublishState_t currentState, MQTTPublishState_t newState, @@ -154,6 +246,24 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, break; + /* Below cases are for validating the resends of publish when a session is + * reestablished. */ + case MQTTPubAckPending: + + /* When a session is reestablished, outgoing QoS1 publishes in state + * #MQTTPubAckPending can be resent. The state remains the same. */ + isValid = ( newState == MQTTPubAckPending ) ? true : false; + + break; + + case MQTTPubRecPending: + + /* When a session is reestablished, outgoing QoS2 publishes in state + * #MQTTPubRecPending can be resent. The state remains the same. */ + isValid = ( newState == MQTTPubRecPending ) ? true : false; + + break; + default: /* For a PUBLISH, we should not start from any other state. */ break; @@ -162,6 +272,8 @@ static bool validateTransitionPublish( MQTTPublishState_t currentState, return isValid; } +/*-----------------------------------------------------------*/ + static bool validateTransitionAck( MQTTPublishState_t currentState, MQTTPublishState_t newState ) { @@ -182,13 +294,46 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, break; case MQTTPubRelPending: - /* Incoming publish, QoS 2. */ - isValid = ( newState == MQTTPubCompSend ) ? true : false; + + /* Incoming publish, QoS 2. + * There are 2 valid transitions possible. + * 1. MQTTPubRelPending -> MQTTPubCompSend : A PUBREL ack is received + * when publish record state is MQTTPubRelPending. This is the + * normal state transition without any connection interuptions. + * 2. MQTTPubRelPending -> MQTTPubRelPending : Receiving a duplicate + * QoS2 publish can result in a transition to the same state. + * This can happen in the below state transition. + * 1. Incoming publish received. + * 2. PUBREC ack sent and state is now MQTTPubRelPending. + * 3. TCP connection failure and broker didn't receive the PUBREC. + * 4. Reestablished MQTT session. + * 5. MQTT broker resent the un-acked publish. + * 6. Publish is received when publish record state is in + * MQTTPubRelPending. + * 7. Sending out a PUBREC will result in this transition + * to the same state. */ + isValid = ( ( newState == MQTTPubCompSend ) || + ( newState == MQTTPubRelPending ) ) ? true : false; break; case MQTTPubCompSend: - /* Incoming publish, QoS 2. */ - isValid = ( newState == MQTTPublishDone ) ? true : false; + + /* Incoming publish, QoS 2. + * There are 2 valid transitions possible. + * 1. MQTTPubCompSend -> MQTTPublishDone : A PUBCOMP ack is sent + * after receiving a PUBREL from broker. This is the + * normal state transition without any connection interuptions. + * 2. MQTTPubCompSend -> MQTTPubCompSend : Receiving a duplicate PUBREL + * can result in a transition to the same state. + * This can happen in the below state transition. + * 1. A TCP connection failure happened before sending a PUBCOMP + * for an incoming PUBREL. + * 2. Reestablished an MQTT session. + * 3. MQTT broker resent the un-acked PUBREL. + * 4. Receiving the PUBREL again will result in this transition + * to the same state. */ + isValid = ( ( newState == MQTTPublishDone ) || + ( newState == MQTTPubCompSend ) ) ? true : false; break; case MQTTPubRecPending: @@ -202,8 +347,24 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, break; case MQTTPubCompPending: - /* Outgoing publish, Qos 2. */ - isValid = ( newState == MQTTPublishDone ) ? true : false; + + /* Outgoing publish, Qos 2. + * There are 2 valid transitions possible. + * 1. MQTTPubCompPending -> MQTTPublishDone : A PUBCOMP is received. + * This marks the complete state transistion for the publish packet. + * This is the normal state transition without any connection + * interuptions. + * 2. MQTTPubCompPending -> MQTTPubCompPending : Resending a PUBREL for + * packets in state #MQTTPubCompPending can result in this + * transition to the same state. + * This can happen in the below state transition. + * 1. A TCP connection failure happened before receiving a PUBCOMP + * for an outgoing PUBREL. + * 2. An MQTT session is reestablished. + * 3. Resending the un-acked PUBREL results in this transition + * to the same state. */ + isValid = ( ( newState == MQTTPublishDone ) || + ( newState == MQTTPubCompPending ) ) ? true : false; break; case MQTTPublishDone: @@ -220,6 +381,8 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, return isValid; } +/*-----------------------------------------------------------*/ + static bool isPublishOutgoing( MQTTPubAckType_t packetType, MQTTStateOperation_t opType ) { @@ -245,6 +408,8 @@ static bool isPublishOutgoing( MQTTPubAckType_t packetType, return isOutgoing; } +/*-----------------------------------------------------------*/ + static size_t findInRecord( const MQTTPubAckInfo_t * records, size_t recordCount, uint16_t packetId, @@ -275,6 +440,48 @@ static size_t findInRecord( const MQTTPubAckInfo_t * records, return index; } +/*-----------------------------------------------------------*/ + +static void compactRecords( MQTTPubAckInfo_t * records, + size_t recordCount ) +{ + size_t index = 0; + size_t emptyIndex = MQTT_STATE_ARRAY_MAX_COUNT; + + assert( records != NULL ); + + /* Find the empty spots and fill those with non empty values. */ + for( ; index < recordCount; index++ ) + { + /* Find the first empty spot. */ + if( records[ index ].packetId == MQTT_PACKET_ID_INVALID ) + { + if( emptyIndex == MQTT_STATE_ARRAY_MAX_COUNT ) + { + emptyIndex = index; + } + } + else + { + if( emptyIndex != MQTT_STATE_ARRAY_MAX_COUNT ) + { + /* Copy over the contents at non empty index to empty index. */ + records[ emptyIndex ].packetId = records[ index ].packetId; + records[ emptyIndex ].qos = records[ index ].qos; + records[ emptyIndex ].publishState = records[ index ].publishState; + + /* Mark the record at current non empty index as invalid. */ + records[ index ].packetId = MQTT_PACKET_ID_INVALID; + + /* Advance the emptyIndex. */ + emptyIndex++; + } + } + } +} + +/*-----------------------------------------------------------*/ + static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, size_t recordCount, uint16_t packetId, @@ -284,31 +491,48 @@ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, MQTTStatus_t status = MQTTNoMemory; int32_t index = 0; size_t availableIndex = recordCount; + bool validEntryFound = false; assert( packetId != MQTT_PACKET_ID_INVALID ); assert( qos != MQTTQoS0 ); - /* Start from end so first available index will be populated. */ + /* Check if we have to compact the records. This is known by checking if + * the last spot in the array is filled. */ + if( records[ recordCount - 1U ].packetId != MQTT_PACKET_ID_INVALID ) + { + compactRecords( records, recordCount ); + } + + /* Start from end so first available index will be populated. + * Available index is always found after the last element in the records. + * This is to make sure the relative order of the records in order to meet + * the message ordering requirement of MQTT spec 3.1.1. */ for( index = ( ( int32_t ) recordCount - 1 ); index >= 0; index-- ) { + /* Available index is only found after packet at the highest index. */ if( records[ index ].packetId == MQTT_PACKET_ID_INVALID ) { - availableIndex = ( size_t ) index; - } - else if( records[ index ].packetId == packetId ) - { - /* Collision. */ - LogError( ( "Collision when adding PacketID=%u at index=%u", - packetId, - index ) ); - - status = MQTTStateCollision; - availableIndex = recordCount; - break; + if( validEntryFound == false ) + { + availableIndex = ( size_t ) index; + } } else { - /* Empty else clause. */ + /* A non-empty spot found in the records. */ + validEntryFound = true; + + if( records[ index ].packetId == packetId ) + { + /* Collision. */ + LogError( ( "Collision when adding PacketID=%u at index=%u", + packetId, + index ) ); + + status = MQTTStateCollision; + availableIndex = recordCount; + break; + } } } @@ -323,16 +547,19 @@ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, return status; } +/*-----------------------------------------------------------*/ + static void updateRecord( MQTTPubAckInfo_t * records, size_t recordIndex, MQTTPublishState_t newState, bool shouldDelete ) { + assert( records != NULL ); + if( shouldDelete == true ) { + /* Mark the record as invalid. */ records[ recordIndex ].packetId = MQTT_PACKET_ID_INVALID; - records[ recordIndex ].qos = MQTTQoS0; - records[ recordIndex ].publishState = MQTTStateNull; } else { @@ -340,6 +567,218 @@ static void updateRecord( MQTTPubAckInfo_t * records, } } +/*-----------------------------------------------------------*/ + +static uint16_t stateSelect( const MQTTContext_t * pMqttContext, + uint16_t searchStates, + MQTTStateCursor_t * pCursor ) +{ + uint16_t packetId = MQTT_PACKET_ID_INVALID; + uint16_t outgoingStates = 0U; + const MQTTPubAckInfo_t * records = NULL; + bool stateCheck = false; + + assert( pMqttContext != NULL ); + assert( searchStates != 0U ); + assert( pCursor != NULL ); + + /* Create a bit map with all the outgoing publish states. */ + UINT16_SET_BIT( outgoingStates, MQTTPublishSend ); + UINT16_SET_BIT( outgoingStates, MQTTPubAckPending ); + UINT16_SET_BIT( outgoingStates, MQTTPubRecPending ); + UINT16_SET_BIT( outgoingStates, MQTTPubRelSend ); + UINT16_SET_BIT( outgoingStates, MQTTPubCompPending ); + + /* Only outgoing publish records need to be searched. */ + assert( ( outgoingStates & searchStates ) > 0U ); + assert( ( ~outgoingStates & searchStates ) == 0 ); + + records = pMqttContext->outgoingPublishRecords; + + while( *pCursor < MQTT_STATE_ARRAY_MAX_COUNT ) + { + /* Check if any of the search states are present. */ + stateCheck = UINT16_CHECK_BIT( searchStates, records[ *pCursor ].publishState ) ? true : false; + + if( stateCheck == true ) + { + packetId = records[ *pCursor ].packetId; + ( *pCursor )++; + break; + } + + ( *pCursor )++; + } + + return packetId; +} + +/*-----------------------------------------------------------*/ + +MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, + MQTTStateOperation_t opType, + MQTTQoS_t qos ) +{ + MQTTPublishState_t calculatedState = MQTTStateNull; + /* There are more QoS2 cases than QoS1, so initialize to that. */ + bool qosValid = ( qos == MQTTQoS2 ) ? true : false; + + switch( packetType ) + { + case MQTTPuback: + qosValid = ( qos == MQTTQoS1 ) ? true : false; + calculatedState = MQTTPublishDone; + break; + + case MQTTPubrec: + + /* Incoming publish: send PUBREC, PUBREL pending. + * Outgoing publish: receive PUBREC, send PUBREL. */ + calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRelPending : MQTTPubRelSend; + break; + + case MQTTPubrel: + + /* Incoming publish: receive PUBREL, send PUBCOMP. + * Outgoing publish: send PUBREL, PUBCOMP pending. */ + calculatedState = ( opType == MQTT_SEND ) ? MQTTPubCompPending : MQTTPubCompSend; + break; + + case MQTTPubcomp: + calculatedState = MQTTPublishDone; + break; + + default: + /* No other ack type. */ + break; + } + + /* Sanity check, make sure ack and QoS agree. */ + if( qosValid == false ) + { + calculatedState = MQTTStateNull; + } + + return calculatedState; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t updateStateAck( MQTTPubAckInfo_t * records, + size_t recordIndex, + uint16_t packetId, + MQTTPublishState_t currentState, + MQTTPublishState_t newState ) +{ + MQTTStatus_t status = MQTTIllegalState; + bool shouldDeleteRecord = false; + bool isTransitionValid = false; + + assert( records != NULL ); + + /* Record to be deleted if the state transition is completed or if a PUBREC + * is received for an outgoing QoS2 publish. When a PUBREC is received, + * record is deleted and added back to the end of the records to maintain + * ordering for PUBRELs. */ + shouldDeleteRecord = ( ( newState == MQTTPublishDone ) || ( newState == MQTTPubRelSend ) ) ? true : false; + isTransitionValid = validateTransitionAck( currentState, newState ); + + if( isTransitionValid == true ) + { + status = MQTTSuccess; + + /* Update record for acks. When sending or receiving acks for packets that + * are resent during a session reestablishment, the new state and + * current state can be the same. No update of record required in that case. */ + if( currentState != newState ) + { + updateRecord( records, + recordIndex, + newState, + shouldDeleteRecord ); + + /* For QoS2 messages, in order to preserve the message ordering, when + * a PUBREC is received for an outgoing publish, the record should be + * moved to the last. This move will help preserve the order in which + * a PUBREL needs to be resent in case of a session reestablishment. */ + if( newState == MQTTPubRelSend ) + { + status = addRecord( records, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + MQTTQoS2, + MQTTPubRelSend ); + } + } + } + else + { + /* Invalid state transition. */ + LogError( ( "Invalid transition from state %s to state %s.", + MQTT_State_strerror( currentState ), + MQTT_State_strerror( newState ) ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static MQTTStatus_t updateStatePublish( MQTTContext_t * pMqttContext, + size_t recordIndex, + uint16_t packetId, + MQTTStateOperation_t opType, + MQTTQoS_t qos, + MQTTPublishState_t currentState, + MQTTPublishState_t newState ) +{ + MQTTStatus_t status = MQTTSuccess; + bool isTransitionValid = false; + + assert( pMqttContext != NULL ); + assert( packetId != MQTT_PACKET_ID_INVALID ); + assert( qos != MQTTQoS0 ); + + /* This will always succeed for an incoming publish. This is due to the fact + * that the passed in currentState must be MQTTStateNull, since + * #MQTT_UpdateStatePublish does not perform a lookup for receives. */ + isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos ); + + if( isTransitionValid == true ) + { + /* addRecord will check for collisions. */ + if( opType == MQTT_RECEIVE ) + { + status = addRecord( pMqttContext->incomingPublishRecords, + MQTT_STATE_ARRAY_MAX_COUNT, + packetId, + qos, + newState ); + } + /* Send operation. */ + else + { + /* Skip updating record when publish is resend and no state + * update is required. */ + if( currentState != newState ) + { + updateRecord( pMqttContext->outgoingPublishRecords, recordIndex, newState, false ); + } + } + } + else + { + status = MQTTIllegalState; + LogError( ( "Invalid transition from state %s to state %s.", + MQTT_State_strerror( currentState ), + MQTT_State_strerror( newState ) ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos ) @@ -367,6 +806,8 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, return status; } +/*-----------------------------------------------------------*/ + MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ) { @@ -394,6 +835,8 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, return calculatedState; } +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, @@ -405,7 +848,6 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, MQTTStatus_t mqttStatus = MQTTSuccess; size_t recordIndex = MQTT_STATE_ARRAY_MAX_COUNT; MQTTQoS_t foundQoS = MQTTQoS0; - bool isTransitionValid = false; if( ( pMqttContext == NULL ) || ( pNewState == NULL ) ) { @@ -448,85 +890,26 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, if( ( qos != MQTTQoS0 ) && ( mqttStatus == MQTTSuccess ) ) { newState = MQTT_CalculateStatePublish( opType, qos ); - isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos ); - - if( isTransitionValid == true ) - { - /* addRecord will check for collisions. */ - if( opType == MQTT_RECEIVE ) - { - mqttStatus = addRecord( pMqttContext->incomingPublishRecords, - MQTT_STATE_ARRAY_MAX_COUNT, - packetId, - qos, - newState ); - } - /* Send operation. */ - else - { - updateRecord( pMqttContext->outgoingPublishRecords, recordIndex, newState, false ); - } - } - else + /* Validate state transition and update state records. */ + mqttStatus = updateStatePublish( pMqttContext, + recordIndex, + packetId, + opType, + qos, + currentState, + newState ); + + /* Update output parameter on success. */ + if( mqttStatus == MQTTSuccess ) { - mqttStatus = MQTTIllegalState; - LogError( ( "Invalid transition from state %s to state %s.", - MQTT_State_strerror( currentState ), - MQTT_State_strerror( newState ) ) ); + *pNewState = newState; } - - *pNewState = newState; } return mqttStatus; } -MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, - MQTTStateOperation_t opType, - MQTTQoS_t qos ) -{ - MQTTPublishState_t calculatedState = MQTTStateNull; - /* There are more QoS2 cases than QoS1, so initialize to that. */ - bool qosValid = ( qos == MQTTQoS2 ) ? true : false; - - switch( packetType ) - { - case MQTTPuback: - qosValid = ( qos == MQTTQoS1 ) ? true : false; - calculatedState = MQTTPublishDone; - break; - - case MQTTPubrec: - - /* Incoming publish: send PUBREC, PUBREL pending. - * Outgoing publish: receive PUBREC, send PUBREL. */ - calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRelPending : MQTTPubRelSend; - break; - - case MQTTPubrel: - - /* Incoming publish: receive PUBREL, send PUBCOMP. - * Outgoing publish: send PUBREL, PUBCOMP pending. */ - calculatedState = ( opType == MQTT_SEND ) ? MQTTPubCompPending : MQTTPubCompSend; - break; - - case MQTTPubcomp: - calculatedState = MQTTPublishDone; - break; - - default: - /* No other ack type. */ - break; - } - - /* Sanity check, make sure ack and QoS agree. */ - if( qosValid == false ) - { - calculatedState = MQTTStateNull; - } - - return calculatedState; -} +/*-----------------------------------------------------------*/ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, @@ -537,8 +920,6 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, MQTTPublishState_t newState = MQTTStateNull; MQTTPublishState_t currentState = MQTTStateNull; bool isOutgoingPublish = isPublishOutgoing( packetType, opType ); - bool shouldDeleteRecord = false; - bool isTransitionValid = false; MQTTQoS_t qos = MQTTQoS0; size_t recordIndex = MQTT_STATE_ARRAY_MAX_COUNT; MQTTPubAckInfo_t * records = NULL; @@ -571,25 +952,14 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, if( recordIndex < MQTT_STATE_ARRAY_MAX_COUNT ) { newState = MQTT_CalculateStateAck( packetType, opType, qos ); - shouldDeleteRecord = ( newState == MQTTPublishDone ) ? true : false; - isTransitionValid = validateTransitionAck( currentState, newState ); - if( isTransitionValid == true ) - { - updateRecord( records, - recordIndex, - newState, - shouldDeleteRecord ); + /* Validate state transition and update state record. */ + status = updateStateAck( records, recordIndex, packetId, currentState, newState ); - status = MQTTSuccess; - *pNewState = newState; - } - else + /* Update the output parameter. */ + if( status == MQTTSuccess ) { - status = MQTTIllegalState; - LogError( ( "Invalid transition from state %s to state %s.", - MQTT_State_strerror( currentState ), - MQTT_State_strerror( newState ) ) ); + *pNewState = newState; } } else @@ -600,64 +970,74 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, return status; } -uint16_t MQTT_StateSelect( const MQTTContext_t * pMqttContext, - MQTTPublishState_t searchState, - MQTTStateCursor_t * pCursor ) +/*-----------------------------------------------------------*/ + +uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, + MQTTStateCursor_t * pCursor, + MQTTPublishState_t * pState ) { uint16_t packetId = MQTT_PACKET_ID_INVALID; - const MQTTPubAckInfo_t * records = NULL; + uint16_t searchStates = 0U; - /* Classify state into incoming or outgoing so we don't search both. */ - switch( searchState ) + /* Validate arguments. */ + if( ( pMqttContext == NULL ) || ( pCursor == NULL ) || ( pState == NULL ) ) { - case MQTTPubAckSend: - case MQTTPubRecSend: - case MQTTPubRelPending: - case MQTTPubCompSend: - - if( pMqttContext != NULL ) - { - records = pMqttContext->incomingPublishRecords; - } - - break; + LogError( ( "Arguments cannot be NULL pMqttContext =%p, pCursor=%p" + " pState=%p.", + pMqttContext, + pCursor, + pState ) ); + } + else + { + /* PUBREL for packets in state #MQTTPubCompPending and #MQTTPubRelSend + * would need to be resent when a session is reestablished.*/ + UINT16_SET_BIT( searchStates, MQTTPubCompPending ); + UINT16_SET_BIT( searchStates, MQTTPubRelSend ); + packetId = stateSelect( pMqttContext, searchStates, pCursor ); + + /* The state needs to be in #MQTTPubRelSend for sending PUBREL. */ + if( packetId != MQTT_PACKET_ID_INVALID ) + { + *pState = MQTTPubRelSend; + } + } - case MQTTPublishSend: - case MQTTPubAckPending: - case MQTTPubRecPending: - case MQTTPubRelSend: - case MQTTPubCompPending: + return packetId; +} - if( pMqttContext != NULL ) - { - records = pMqttContext->outgoingPublishRecords; - } +/*-----------------------------------------------------------*/ - break; +uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, + MQTTStateCursor_t * pCursor ) +{ + uint16_t packetId = MQTT_PACKET_ID_INVALID; + uint16_t searchStates = 0U; - default: - /* NULL or done aren't valid entries to search for. */ - break; + /* Validate arguments. */ + if( ( pMqttContext == NULL ) || ( pCursor == NULL ) ) + { + LogError( ( "Arguments cannot be NULL pMqttContext =%p, pCursor=%p", + pMqttContext, + pCursor ) ); } - - if( ( records != NULL ) && ( pCursor != NULL ) ) + else { - while( *pCursor < MQTT_STATE_ARRAY_MAX_COUNT ) - { - if( records[ *pCursor ].publishState == searchState ) - { - packetId = records[ *pCursor ].packetId; - ( *pCursor )++; - break; - } - - ( *pCursor )++; - } + /* Packets in state #MQTTPublishSend, #MQTTPubAckPending and + * #MQTTPubRecPending would need to be resent when a session is + * reestablished. */ + UINT16_SET_BIT( searchStates, MQTTPublishSend ); + UINT16_SET_BIT( searchStates, MQTTPubAckPending ); + UINT16_SET_BIT( searchStates, MQTTPubRecPending ); + + packetId = stateSelect( pMqttContext, searchStates, pCursor ); } return packetId; } +/*-----------------------------------------------------------*/ + const char * MQTT_State_strerror( MQTTPublishState_t state ) { const char * str = NULL; @@ -716,3 +1096,5 @@ const char * MQTT_State_strerror( MQTTPublishState_t state ) return str; } + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 2483de5bc3..695b725ba9 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -1530,7 +1530,8 @@ void test_MQTT_DeserializePublish( void ) /* Test serialization and deserialization of a QoS 1 PUBLISH. */ publishInfo.qos = MQTTQoS1; - + /* Mark the publish as duplicate. */ + publishInfo.dup = true; status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); @@ -1546,6 +1547,7 @@ void test_MQTT_DeserializePublish( void ) mqttPacketInfo.pRemainingData = &buffer[ 2 ]; status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_TRUE( publishInfo.dup ); /* QoS 2 PUBLISH. */ publishInfo.qos = MQTTQoS2; diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c index 4c8af00224..3df940e82e 100644 --- a/libraries/standard/mqtt/utest/mqtt_state_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -1,3 +1,4 @@ +#include #include "unity.h" #include "mqtt_state.h" @@ -68,12 +69,27 @@ static void fillRecord( MQTTPubAckInfo_t * records, } } +static void validateRecordAt( MQTTPubAckInfo_t * records, + size_t index, + uint16_t packetId, + MQTTQoS_t qos, + MQTTPublishState_t state ) +{ + TEST_ASSERT_EQUAL( packetId, records[ index ].packetId ); + TEST_ASSERT_EQUAL( qos, records[ index ].qos ); + TEST_ASSERT_EQUAL( state, records[ index ].publishState ); +} + /* ========================================================================== */ void test_MQTT_ReserveState( void ) { MQTTContext_t mqttContext = { 0 }; MQTTStatus_t status; + const uint16_t PACKET_ID = 1; + const uint16_t PACKET_ID2 = 2; + const uint16_t PACKET_ID3 = 3; + const size_t index = MQTT_STATE_ARRAY_MAX_COUNT / 2; /* QoS 0 returns success. */ TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ReserveState( NULL, MQTT_PACKET_ID_INVALID, MQTTQoS0 ) ); @@ -81,30 +97,165 @@ void test_MQTT_ReserveState( void ) /* Test for bad parameters */ status = MQTT_ReserveState( &mqttContext, MQTT_PACKET_ID_INVALID, MQTTQoS1 ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); - status = MQTT_ReserveState( NULL, 1, MQTTQoS1 ); + status = MQTT_ReserveState( NULL, PACKET_ID, MQTTQoS1 ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* Test for collisions. */ - mqttContext.outgoingPublishRecords[ 1 ].packetId = 1; + mqttContext.outgoingPublishRecords[ 1 ].packetId = PACKET_ID; mqttContext.outgoingPublishRecords[ 1 ].qos = MQTTQoS1; mqttContext.outgoingPublishRecords[ 1 ].publishState = MQTTPublishSend; - status = MQTT_ReserveState( &mqttContext, 1, MQTTQoS1 ); + status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 ); TEST_ASSERT_EQUAL( MQTTStateCollision, status ); /* Test for no memory. */ fillRecord( mqttContext.outgoingPublishRecords, 2, MQTTQoS1, MQTTPublishSend ); - status = MQTT_ReserveState( &mqttContext, 1, MQTTQoS1 ); + status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 ); TEST_ASSERT_EQUAL( MQTTNoMemory, status ); /* Success. */ resetPublishRecords( &mqttContext ); - status = MQTT_ReserveState( &mqttContext, 1, MQTTQoS1 ); + status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); /* Reserve uses first available entry. */ - TEST_ASSERT_EQUAL( 1, mqttContext.outgoingPublishRecords[ 0 ].packetId ); + TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 0 ].packetId ); TEST_ASSERT_EQUAL( MQTTQoS1, mqttContext.outgoingPublishRecords[ 0 ].qos ); TEST_ASSERT_EQUAL( MQTTPublishSend, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + + /* Success. + * Add record after the highest non empty index. + * Already an entry exists at index 0. Adding 1 more entry at index 5. + * The new index used should be 6. */ + addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID2, MQTTQoS2, MQTTPubRelSend ); + status = MQTT_ReserveState( &mqttContext, PACKET_ID3, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( PACKET_ID3, mqttContext.outgoingPublishRecords[ index + 1 ].packetId ); + TEST_ASSERT_EQUAL( MQTTQoS1, mqttContext.outgoingPublishRecords[ index + 1 ].qos ); + TEST_ASSERT_EQUAL( MQTTPublishSend, mqttContext.outgoingPublishRecords[ index + 1 ].publishState ); +} + +/* ========================================================================== */ + +void test_MQTT_ReserveState_compactRecords( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTStatus_t status; + const uint16_t PACKET_ID = 1; + const uint16_t PACKET_ID2 = 2; + + /* Consider the state of the array with 2 states. 1 indicates a non empty + * spot and 0 an empty spot. Size of the array is 10. + * Pre condition - 0 0 0 0 0 0 0 0 0 1. + * Add an element will try to compact the array and the resulting state + * should be - 1 1 0 0 0 0 0 0 0 0. */ + addToRecord( mqttContext.outgoingPublishRecords, MQTT_STATE_ARRAY_MAX_COUNT - 1, PACKET_ID, MQTTQoS1, MQTTPubRelSend ); + status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* The existing record should be at index 0. */ + validateRecordAt( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubRelSend ); + /* New record should be added to index 1. */ + validateRecordAt( mqttContext.outgoingPublishRecords, 1, PACKET_ID2, MQTTQoS1, MQTTPublishSend ); + + /* One free spot. + * Pre condition - 1 1 1 0 1 1 1 1 1 1. + * Add an element will try to compact the array and the resulting state + * should be - 1 1 1 1 1 1 1 1 1 1. */ + fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend ); + /* Invalid record at index 3. */ + mqttContext.outgoingPublishRecords[ 3 ].packetId = MQTT_PACKET_ID_INVALID; + status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* The new record should be added to the end. */ + validateRecordAt( mqttContext.outgoingPublishRecords, + MQTT_STATE_ARRAY_MAX_COUNT - 1, + PACKET_ID, + MQTTQoS1, + MQTTPublishSend ); + /* Any new add should result in no memory error. */ + status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTNoMemory, status ); + + /* Alternate free spots. + * Pre condition - 1 0 1 0 1 0 1 0 1 0. + * Add an element will skip to compact the array and the resulting state + * should be - 1 0 1 0 1 0 1 0 1 1. */ + fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend ); + /* Invalidate record at alternate indexes starting from 1. */ + mqttContext.outgoingPublishRecords[ 1 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 3 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 5 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 7 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 9 ].packetId = MQTT_PACKET_ID_INVALID; + status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + /* The new record should be added to the end. */ + validateRecordAt( mqttContext.outgoingPublishRecords, + MQTT_STATE_ARRAY_MAX_COUNT - 1, + PACKET_ID, + MQTTQoS1, + MQTTPublishSend ); + + /* Array is in state 1 0 1 0 1 0 1 0 1 1. + * Adding one more element should result in array in state + * 1 1 1 1 1 1 1 0 0 0. */ + status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + validateRecordAt( mqttContext.outgoingPublishRecords, 6, PACKET_ID2, MQTTQoS1, MQTTPublishSend ); + /* Remaining records should be invalid. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 7 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 8 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 9 ].packetId ); + + /* Free spots only in the beginning. + * Pre condition - 0 0 0 0 0 1 1 1 1 1. + * Add an element will compact the array and the resulting state + * should be - 1 1 1 1 1 1 0 0 0 0. */ + fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend ); + /* Clear record from 0 to 4. */ + ( void ) memset( &mqttContext.outgoingPublishRecords[ 0 ], 0x00, 5 * sizeof( MQTTPubAckInfo_t ) ); + + /* Adding one element should result in array in state + * 1 1 1 1 1 1 0 0 0 0. */ + status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + validateRecordAt( mqttContext.outgoingPublishRecords, 5, PACKET_ID2, MQTTQoS1, MQTTPublishSend ); + /* Remaining records should be cleared. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 6 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 7 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 8 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 9 ].packetId ); + + /* Fragmented array. + * Pre condition - 1 0 0 1 1 1 1 0 0 1. + * Add an element will compact the array and the resulting state + * should be - 1 1 1 1 1 1 1 0 0 0. */ + fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend ); + /* Clear record at index 1,2,7 and 8. */ + mqttContext.outgoingPublishRecords[ 1 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 2 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 7 ].packetId = MQTT_PACKET_ID_INVALID; + mqttContext.outgoingPublishRecords[ 8 ].packetId = MQTT_PACKET_ID_INVALID; + status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + validateRecordAt( mqttContext.outgoingPublishRecords, 6, PACKET_ID2, MQTTQoS1, MQTTPublishSend ); + /* Remaining records should be cleared. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 7 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 8 ].packetId ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 9 ].packetId ); + + /* Fragmented array. + * Pre condition - 1 0 0 0 0 0 0 0 0 1. + * Add an element will compact the array and the resulting state + * should be - 1 1 1 0 0 0 0 0 0 0. */ + resetPublishRecords( &mqttContext ); + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); + addToRecord( mqttContext.outgoingPublishRecords, 9, PACKET_ID2 + 1, MQTTQoS2, MQTTPubCompPending ); + status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + validateRecordAt( mqttContext.outgoingPublishRecords, 2, PACKET_ID2, MQTTQoS1, MQTTPublishSend ); + /* Validate existing records. */ + validateRecordAt( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); + validateRecordAt( mqttContext.outgoingPublishRecords, 1, PACKET_ID2 + 1, MQTTQoS2, MQTTPubCompPending ); } /* ========================================================================== */ @@ -162,8 +313,8 @@ void test_MQTT_UpdateStatePublish( void ) status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); - /* Invalid transition. */ - addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending ); + /* Invalid state transition. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubRelPending ); status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); TEST_ASSERT_EQUAL( MQTTIllegalState, status ); @@ -205,12 +356,22 @@ void test_MQTT_UpdateStatePublish( void ) TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubAckPending, state ); TEST_ASSERT_EQUAL( MQTTPubAckPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + /* Resend when record already exists. */ + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTPubAckPending, state ); + TEST_ASSERT_EQUAL( MQTTPubAckPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); /* Receive. */ operation = MQTT_RECEIVE; status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubAckSend, state ); TEST_ASSERT_EQUAL( MQTTPubAckSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + /* Receive duplicate incoming publish. */ + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTStateCollision, status ); + TEST_ASSERT_EQUAL( MQTTPubAckSend, state ); + TEST_ASSERT_EQUAL( MQTTPubAckSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); resetPublishRecords( &mqttContext ); @@ -223,12 +384,29 @@ void test_MQTT_UpdateStatePublish( void ) TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRecPending, state ); TEST_ASSERT_EQUAL( MQTTPubRecPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + /* Resend when record already exists. */ + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTPubRecPending, state ); + TEST_ASSERT_EQUAL( MQTTPubRecPending, mqttContext.outgoingPublishRecords[ 0 ].publishState ); /* Receive. */ operation = MQTT_RECEIVE; status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRecSend, state ); TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + /* Receive incoming publish when the packet record is in state #MQTTPubRecSend. */ + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTStateCollision, status ); + TEST_ASSERT_EQUAL( MQTTPubRecSend, state ); + TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + /* Receive incoming publish when the packet record is in state #MQTTPubRelPending. */ + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending ); + status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state ); + TEST_ASSERT_EQUAL( MQTTStateCollision, status ); + /* The returned state will always be #MQTTPubRecSend as a PUBREC need to be sent. */ + TEST_ASSERT_EQUAL( MQTTPubRecSend, state ); + TEST_ASSERT_EQUAL( MQTTPubRelPending, mqttContext.incomingPublishRecords[ 0 ].publishState ); } /* ========================================================================== */ @@ -316,7 +494,24 @@ void test_MQTT_UpdateStateAck( void ) status = MQTT_UpdateStateAck( &mqttContext, 0, ack, operation, &state ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); - /* Invalid transition. */ + /* Invalid transitions. */ + /* Invalid transition from #MQTTPubRelPending. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending ); + ack = MQTTPubrel; + operation = MQTT_SEND; + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); + /* Invalid transition from #MQTTPubCompSend. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompSend ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); + /* Invalid transition from #MQTTPubCompPending. */ + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompPending ); + ack = MQTTPubrec; + operation = MQTT_RECEIVE; + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTIllegalState, status ); + /* Invalid transition from #MQTTPubRecPending. */ addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); ack = MQTTPubcomp; status = MQTT_UpdateStateAck( &mqttContext, 1, ack, operation, &state ); @@ -339,18 +534,17 @@ void test_MQTT_UpdateStateAck( void ) resetPublishRecords( &mqttContext ); - /* QoS 1, outgoing publish. */ + /* QoS 1, receive PUBACK for outgoing publish. */ addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending ); operation = MQTT_RECEIVE; ack = MQTTPuback; status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPublishDone, state ); + /* Test for deletion. */ - TEST_ASSERT_EQUAL( MQTTStateNull, mqttContext.outgoingPublishRecords[ 0 ].publishState ); TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 0 ].packetId ); - TEST_ASSERT_EQUAL( MQTTQoS0, mqttContext.outgoingPublishRecords[ 0 ].qos ); - /* Incoming publish. */ + /* Send PUBACK for incoming publish. */ operation = MQTT_SEND; addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend ); status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); @@ -367,7 +561,13 @@ void test_MQTT_UpdateStateAck( void ) status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubCompPending, state ); - /* Incoming . */ + /* Outgoing. Resend PUBREL when record in state #MQTTPubCompPending. */ + operation = MQTT_SEND; + ack = MQTTPubrel; + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTPubCompPending, state ); + /* Incoming. */ addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending ); operation = MQTT_RECEIVE; status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); @@ -375,6 +575,10 @@ void test_MQTT_UpdateStateAck( void ) TEST_ASSERT_EQUAL( MQTTPubCompSend, state ); /* Test for update. */ TEST_ASSERT_EQUAL( MQTTPubCompSend, mqttContext.incomingPublishRecords[ 0 ].publishState ); + /* Incoming. Duplicate PUBREL is received when record is in state #MQTTPubRelPending. */ + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTPubCompSend, state ); /* QoS 2, PUBREC. */ /* Outgoing. */ @@ -384,12 +588,42 @@ void test_MQTT_UpdateStateAck( void ) status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); + + /* Receiving a PUBREC will move the record to the end. + * In this case, only one record exists, no moving is required. */ + TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 0 ].packetId ); + TEST_ASSERT_EQUAL( MQTTQoS2, mqttContext.outgoingPublishRecords[ 0 ].qos ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, mqttContext.outgoingPublishRecords[ 0 ].publishState ); + + /* Outgoing. + * Test if the record moves to the end of the records when PUBREC is + * received. */ + resetPublishRecords( &mqttContext ); + addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending ); + addToRecord( mqttContext.outgoingPublishRecords, 1, PACKET_ID + 1, MQTTQoS2, MQTTPubRelSend ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); + + /* Receiving a PUBREC will move the record to the end. + * In this case, the record wil be moved to index 2. */ + TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 2 ].packetId ); + TEST_ASSERT_EQUAL( MQTTQoS2, mqttContext.outgoingPublishRecords[ 2 ].qos ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, mqttContext.outgoingPublishRecords[ 2 ].publishState ); + /* Record at the current index will be marked as onvalid. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 0 ].packetId ); + /* Incoming. */ addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecSend ); operation = MQTT_SEND; status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); TEST_ASSERT_EQUAL( MQTTPubRelPending, state ); + /* Incoming. Duplicate publish received and record is in state #MQTTPubRelPending. */ + addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending ); + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_EQUAL( MQTTPubRelPending, state ); /* QoS 2, PUBCOMP. */ /* Outgoing. */ @@ -409,54 +643,148 @@ void test_MQTT_UpdateStateAck( void ) /* ========================================================================== */ -void test_MQTT_StateSelect( void ) +void test_MQTT_AckToResend( void ) { MQTTContext_t mqttContext = { 0 }; - MQTTStateCursor_t outgoingCursor = MQTT_STATE_CURSOR_INITIALIZER; - MQTTStateCursor_t incomingCursor = MQTT_STATE_CURSOR_INITIALIZER; - MQTTPublishState_t search = MQTTPublishSend; + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + MQTTPublishState_t state = MQTTStateNull; uint16_t packetId; const uint16_t PACKET_ID = 1; const uint16_t PACKET_ID2 = 2; - const size_t index = MQTT_STATE_ARRAY_MAX_COUNT / 2; - const size_t secondIndex = index + 2; + const uint16_t PACKET_ID3 = 3; + const uint16_t PACKET_ID4 = 4; + const size_t index = 0; + const size_t index2 = 1; + const size_t index3 = MQTT_STATE_ARRAY_MAX_COUNT / 2; + const size_t index4 = index3 + 2; /* Invalid parameters. */ - packetId = MQTT_StateSelect( NULL, MQTTPublishSend, &outgoingCursor ); + packetId = MQTT_PubrelToResend( NULL, &cursor, &state ); TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); - packetId = MQTT_StateSelect( NULL, MQTTPubAckSend, &incomingCursor ); + packetId = MQTT_PubrelToResend( &mqttContext, NULL, &state ); TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); - packetId = MQTT_StateSelect( &mqttContext, search, NULL ); + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, NULL ); TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); - packetId = MQTT_StateSelect( &mqttContext, MQTTStateNull, &outgoingCursor ); + + /* No packet exists. */ + cursor = MQTT_STATE_CURSOR_INITIALIZER; + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor ); + + /* No packet exists in state #MQTTPubCompPending or #MQTTPubCompPending states. */ + cursor = MQTT_STATE_CURSOR_INITIALIZER; + addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID3, MQTTQoS2, MQTTPubRelPending ); + addToRecord( mqttContext.outgoingPublishRecords, index2, PACKET_ID4, MQTTQoS2, MQTTPubCompSend ); + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor ); - /* Incoming. */ - search = MQTTPubAckSend; - addToRecord( mqttContext.incomingPublishRecords, index, PACKET_ID, MQTTQoS1, search ); - packetId = MQTT_StateSelect( &mqttContext, search, &incomingCursor ); + /* Add a record in #MQTTPubCompPending state. */ + cursor = MQTT_STATE_CURSOR_INITIALIZER; + addToRecord( mqttContext.outgoingPublishRecords, index3, PACKET_ID, MQTTQoS2, MQTTPubCompPending ); + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); TEST_ASSERT_EQUAL( PACKET_ID, packetId ); - TEST_ASSERT_EQUAL( index + 1, incomingCursor ); + TEST_ASSERT_EQUAL( index3 + 1, cursor ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); - /* Outgoing. */ - search = MQTTPublishSend; - addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID, MQTTQoS1, search ); - packetId = MQTT_StateSelect( &mqttContext, search, &outgoingCursor ); + /* Add another record in #MQTTPubCompPending state. */ + addToRecord( mqttContext.outgoingPublishRecords, index4, PACKET_ID2, MQTTQoS2, MQTTPubCompPending ); + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); + TEST_ASSERT_EQUAL( PACKET_ID2, packetId ); + TEST_ASSERT_EQUAL( index4 + 1, cursor ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); + + /* Add another record in #MQTTPubRelSend state. */ + addToRecord( mqttContext.outgoingPublishRecords, index4 + 1, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend ); + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); + TEST_ASSERT_EQUAL( PACKET_ID2 + 1, packetId ); + TEST_ASSERT_EQUAL( index4 + 2, cursor ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); + + /* Only one record in #MQTTPubRelSend state. */ + resetPublishRecords( &mqttContext ); + cursor = MQTT_STATE_CURSOR_INITIALIZER; + addToRecord( mqttContext.outgoingPublishRecords, index3, PACKET_ID, MQTTQoS2, MQTTPubRelSend ); + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); TEST_ASSERT_EQUAL( PACKET_ID, packetId ); - TEST_ASSERT_EQUAL( index + 1, outgoingCursor ); + TEST_ASSERT_EQUAL( index3 + 1, cursor ); + TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); + + /* Further search should be return no valid packets. */ + state = MQTTStateNull; + packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + TEST_ASSERT_EQUAL( MQTTStateNull, state ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor ); +} + +void test_MQTT_PublishToResend( void ) +{ + MQTTContext_t mqttContext = { 0 }; + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + MQTTPublishState_t state = MQTTStateNull; + uint16_t packetId; + const uint16_t PACKET_ID = 1; + const uint16_t PACKET_ID2 = 2; + const uint16_t PACKET_ID3 = 3; + const uint16_t PACKET_ID4 = 4; + const size_t index = 0; + const size_t index2 = 1; + const size_t index3 = MQTT_STATE_ARRAY_MAX_COUNT / 2; + const size_t index4 = index3 + 2; - /* Test if second one can be found. */ - addToRecord( mqttContext.outgoingPublishRecords, secondIndex, PACKET_ID2, MQTTQoS2, search ); - packetId = MQTT_StateSelect( &mqttContext, search, &outgoingCursor ); + + /* Invalid parameters. */ + packetId = MQTT_PublishToResend( NULL, &cursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + packetId = MQTT_PublishToResend( &mqttContext, NULL ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + + /* No packet exists. */ + cursor = MQTT_STATE_CURSOR_INITIALIZER; + packetId = MQTT_PublishToResend( &mqttContext, &cursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor ); + + /* No packet exists in state #MQTTPublishSend, #MQTTPubAckPending and + * #MQTTPubRecPending states. */ + cursor = MQTT_STATE_CURSOR_INITIALIZER; + addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID3, MQTTQoS2, MQTTPubCompPending ); + addToRecord( mqttContext.outgoingPublishRecords, index2, PACKET_ID4, MQTTQoS2, MQTTPubRelSend ); + packetId = MQTT_PublishToResend( &mqttContext, &cursor ); + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor ); + + /* Add a record in #MQTTPublishSend state. */ + cursor = MQTT_STATE_CURSOR_INITIALIZER; + addToRecord( mqttContext.outgoingPublishRecords, index3, PACKET_ID, MQTTQoS2, MQTTPublishSend ); + packetId = MQTT_PublishToResend( &mqttContext, &cursor ); + TEST_ASSERT_EQUAL( PACKET_ID, packetId ); + TEST_ASSERT_EQUAL( index3 + 1, cursor ); + + /* Add another record in #MQTTPubAckPending state. */ + addToRecord( mqttContext.outgoingPublishRecords, index4, PACKET_ID2, MQTTQoS1, MQTTPubAckPending ); + packetId = MQTT_PublishToResend( &mqttContext, &cursor ); TEST_ASSERT_EQUAL( PACKET_ID2, packetId ); - TEST_ASSERT_EQUAL( secondIndex + 1, outgoingCursor ); + TEST_ASSERT_EQUAL( index4 + 1, cursor ); - /* Test if end of loop reached. */ - packetId = MQTT_StateSelect( &mqttContext, search, &outgoingCursor ); + /* Add another record in #MQTTPubRecPending state. */ + addToRecord( mqttContext.outgoingPublishRecords, index4 + 1, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRecPending ); + packetId = MQTT_PublishToResend( &mqttContext, &cursor ); + TEST_ASSERT_EQUAL( PACKET_ID2 + 1, packetId ); + TEST_ASSERT_EQUAL( index4 + 2, cursor ); + + /* Further search should find no packets. */ + packetId = MQTT_PublishToResend( &mqttContext, &cursor ); TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId ); - TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, outgoingCursor ); + TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor ); } +/* ========================================================================== */ + void test_MQTT_State_strerror( void ) { MQTTPublishState_t state; @@ -510,3 +838,5 @@ void test_MQTT_State_strerror( void ) str = MQTT_State_strerror( state ); TEST_ASSERT_EQUAL_STRING( "Invalid MQTT State", str ); } + +/* ========================================================================== */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index c28cfd1698..788f5b6170 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -111,6 +111,12 @@ static uint32_t globalEntryTime = 0; */ static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 }; +/** + * @brief A flag to indicate whether event callback is called from + * MQTT_ProcessLoop. + */ +static bool isEventCallbackInvoked = false; + /* ============================ UNITY FIXTURES ============================ */ /* Called before each test method. */ @@ -191,6 +197,9 @@ static void eventCallback( MQTTContext_t * pContext, ( void ) pPacketInfo; ( void ) packetIdentifier; ( void ) pPublishInfo; + + /* Update the global state to indicate that event callback is invoked. */ + isEventCallbackInvoked = true; } /** @@ -354,10 +363,12 @@ static void setupSubscriptionInfo( MQTTSubscribeInfo_t * pSubscribeInfo ) static void expectProcessLoopCalls( MQTTContext_t * const pContext, MQTTStatus_t deserializeStatus, MQTTPublishState_t stateAfterDeserialize, + MQTTStatus_t updateStateStatus, MQTTStatus_t serializeStatus, MQTTPublishState_t stateAfterSerialize, MQTTStatus_t processLoopStatus, - bool incomingPublish ) + bool incomingPublish, + MQTTPublishInfo_t * pPubInfo ) { MQTTStatus_t mqttStatus = MQTTSuccess; MQTTPacketInfo_t incomingPacket = { 0 }; @@ -405,6 +416,11 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, if( incomingPublish ) { MQTT_DeserializePublish_ExpectAnyArgsAndReturn( deserializeStatus ); + + if( pPubInfo != NULL ) + { + MQTT_DeserializePublish_ReturnThruPtr_pPublishInfo( pPubInfo ); + } } else { @@ -425,14 +441,12 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, { if( incomingPublish ) { - MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( ( stateAfterDeserialize == MQTTStateNull )? - MQTTIllegalState : MQTTSuccess ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( updateStateStatus ); MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &stateAfterDeserialize ); } else { - MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( ( stateAfterDeserialize == MQTTStateNull )? - MQTTIllegalState : MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( updateStateStatus ); MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize ); } @@ -440,6 +454,28 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, { expectMoreCalls = false; } + else + { + switch( updateStateStatus ) + { + case MQTTSuccess: + expectMoreCalls = true; + break; + + case MQTTStateCollision: + expectMoreCalls = pPubInfo->dup; + + if( pPubInfo->dup == true ) + { + MQTT_CalculateStatePublish_ExpectAnyArgsAndReturn( stateAfterDeserialize ); + } + + break; + + default: + expectMoreCalls = false; + } + } } /* Serialize the packet to be sent in response to the received packet. */ @@ -456,7 +492,7 @@ static void expectProcessLoopCalls( MQTTContext_t * const pContext, /* Update the state based on the sent packet. */ if( expectMoreCalls ) { - MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( ( stateAfterSerialize == MQTTStateNull )? + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( ( stateAfterSerialize == MQTTStateNull ) ? MQTTIllegalState : MQTTSuccess ); MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterSerialize ); } @@ -625,6 +661,7 @@ void test_MQTT_Connect_sendConnect( void ) MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize ); MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength ); + /* We know the send was successful if MQTT_GetIncomingPacketTypeAndLength() * is called. */ MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); @@ -769,6 +806,131 @@ void test_MQTT_Connect_partial_receive() TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); } +/** + * @brief Test resend of pending acks in MQTT_Connect. + */ +void test_MQTT_Connect_resendPendingAcks( void ) +{ + MQTTContext_t mqttContext; + MQTTConnectInfo_t connectInfo; + uint32_t timeout = 2; + bool sessionPresent, sessionPresentResult; + MQTTStatus_t status; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket; + uint16_t packetIdentifier = 1; + MQTTPublishState_t pubRelState = MQTTPubRelSend; + + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + + /* Test 1. No packets to resend reestablishing a session. */ + /* successful receive CONNACK packet. */ + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + /* Return with a session present flag. */ + sessionPresent = true; + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent ); + /* No packets to resend. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresentResult ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_TRUE( sessionPresentResult ); + + /* Test 2. One packet found in ack pending state, but Sending ack failed. */ + sessionPresentResult = false; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent ); + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier ); + MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState ); + /* Serialize Ack failure. */ + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTBadParameter ); + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresentResult ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_TRUE( sessionPresentResult ); + + /* Test 3. One packet found in ack pending state, Sent + * PUBREL successfully. */ + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent ); + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier ); + MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState ); + /* Serialize Ack successful. */ + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Query for any remaining packets pending to ack. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_ID_INVALID ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + + /* Test 4. Three packets found in ack pending state. Sent PUBREL successfully + * for first and failed for second and no attempt for third. */ + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent ); + /* First packet. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier ); + MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState ); + /* Serialize Ack successful. */ + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Second packet. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 1 ); + MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState ); + /* Serialize Ack failed. */ + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTBadParameter ); + /* Query for any remaining packets pending to ack. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 2 ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + + /* Test 5. Two packets found in ack pending state. Sent PUBREL successfully + * for first and failed for second. */ + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent ); + /* First packet. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier ); + MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState ); + /* Serialize Ack successful. */ + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Second packet. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 1 ); + MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState ); + /* Serialize Ack successful. */ + MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + /* Query for any remaining packets pending to ack. */ + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_ID_INVALID ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); +} + /** * @brief Test success case for MQTT_Connect(). */ @@ -912,6 +1074,34 @@ void test_MQTT_Publish( void ) MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &expectedState ); status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /* Duplicate publish. dup flag is not marked by application. */ + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); + MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTStateCollision ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTStateCollision, status ); + + /* Duplicate publish. dup flag is marked by application. */ + publishInfo.dup = true; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); + MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTStateCollision ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &expectedState ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + /* Duplicate publish. dup flag is marked by application. + * State record is not present. */ + publishInfo.dup = true; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); + MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &expectedState ); + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); } /* ========================================================================== */ @@ -1037,9 +1227,11 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Paths( void ) MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + MQTTPublishInfo_t pubInfo; setupTransportInterface( &transport ); setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); @@ -1051,16 +1243,55 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Paths( void ) * incomingPublish=true and stateAfterDeserialize=MQTTPubAckSend. */ currentPacketType = MQTT_PACKET_TYPE_PUBLISH; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubAckSend, - MQTTSuccess, MQTTPublishDone, - MQTTSuccess, true ); + MQTTSuccess, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, true, NULL ); /* Assume QoS = 2 so that a PUBREC will be sent after receiving PUBLISH. * That is, expectProcessLoopCalls will take on the following parameters: * incomingPublish=true and stateAfterDeserialize=MQTTPubRecSend. */ currentPacketType = MQTT_PACKET_TYPE_PUBLISH; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRecSend, - MQTTSuccess, MQTTPubRelPending, - MQTTSuccess, true ); + MQTTSuccess, MQTTSuccess, MQTTPubRelPending, + MQTTSuccess, true, NULL ); + + /* Duplicate QoS1 publish received. + * expectProcessLoopCalls will take on the following parameters: + * incomingPublish=true, stateAfterDeserialize=MQTTPubAckSend, + * updateStateStatus=MQTTStateCollision and pPubInfo is passed with + * dup flag set. The event callback should not be invoked. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + pubInfo.dup = true; + pubInfo.qos = MQTTQoS1; + isEventCallbackInvoked = false; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubAckSend, + MQTTStateCollision, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, true, &pubInfo ); + TEST_ASSERT_FALSE( isEventCallbackInvoked ); + + /* Duplicate QoS2 publish received. + * expectProcessLoopCalls will take on the following parameters: + * incomingPublish=true, stateAfterDeserialize=MQTTPubRecSend, + * updateStateStatus=MQTTStateCollision and pPubInfo is passed with + * dup flag set. The event callback should not be invoked. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + pubInfo.qos = MQTTQoS2; + isEventCallbackInvoked = false; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubAckSend, + MQTTStateCollision, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, true, &pubInfo ); + TEST_ASSERT_FALSE( isEventCallbackInvoked ); + + /* Duplicate QoS2 publish received with no collision. + * expectProcessLoopCalls will take on the following parameters: + * incomingPublish=true, stateAfterDeserialize=MQTTPubRecSend, + * updateStateStatus=MQTTStateCollision and pPubInfo is passed with + * dup flag set. The event callback should be invoked. */ + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + isEventCallbackInvoked = false; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubAckSend, + MQTTSuccess, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, true, &pubInfo ); + TEST_ASSERT_TRUE( isEventCallbackInvoked ); } /** @@ -1075,9 +1306,11 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void ) MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + MQTTPublishInfo_t publishInfo = { 0 }; setupTransportInterface( &transport ); setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); @@ -1089,8 +1322,29 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void ) * because they are only used as return values for non-expected calls. */ currentPacketType = MQTT_PACKET_TYPE_PUBLISH; expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, true ); + MQTTIllegalState, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, true, NULL ); + + /* A publish is received when already a state record exists, but dup + * flag is not set. */ + publishInfo.dup = false; + publishInfo.qos = MQTTQoS0; + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + isEventCallbackInvoked = false; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRecSend, + MQTTStateCollision, MQTTSuccess, MQTTPublishDone, + MQTTStateCollision, true, &publishInfo ); + TEST_ASSERT_FALSE( isEventCallbackInvoked ); + + /* A publish is received and dup flag is set, but state update failed. */ + publishInfo.dup = true; + publishInfo.qos = MQTTQoS2; + currentPacketType = MQTT_PACKET_TYPE_PUBLISH; + isEventCallbackInvoked = false; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubAckSend, + MQTTIllegalState, MQTTSuccess, MQTTPublishDone, + MQTTIllegalState, true, &publishInfo ); + TEST_ASSERT_FALSE( isEventCallbackInvoked ); } /** @@ -1108,6 +1362,7 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) setupTransportInterface( &transport ); setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); @@ -1118,50 +1373,56 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_PUBACK; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPublishDone, - MQTTSuccess, MQTTPublishDone, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, false, NULL ); /* Mock the receiving of a PUBREC packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_PUBREC; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRelSend, - MQTTSuccess, MQTTPubCompPending, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTPubCompPending, + MQTTSuccess, false, NULL ); /* Mock the receiving of a PUBREL packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_PUBREL; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubCompSend, - MQTTSuccess, MQTTPublishDone, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, false, NULL ); + + /* Duplicate PUBREL, but no record exists. */ + currentPacketType = MQTT_PACKET_TYPE_PUBREL; + expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, + MQTTBadParameter, MQTTSuccess, MQTTPublishDone, + MQTTBadParameter, false, NULL ); /* Mock the receiving of a PUBCOMP packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_PUBCOMP; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPublishDone, - MQTTSuccess, MQTTPublishDone, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTPublishDone, + MQTTSuccess, false, NULL ); /* Mock the receiving of a PINGRESP packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_PINGRESP; expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); /* Mock the receiving of a SUBACK packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_SUBACK; expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); /* Mock the receiving of an UNSUBACK packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_UNSUBACK; expectProcessLoopCalls( &context, MQTTSuccess, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTSuccess, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); } /** @@ -1189,43 +1450,43 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths( void ) * receiving an unknown packet type. */ currentPacketType = MQTT_PACKET_TYPE_INVALID; expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, false ); + MQTTIllegalState, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false, NULL ); /* Verify that MQTTSendFailed is propagated when receiving a PUBREC * then failing when serializing a PUBREL to send in response. */ currentPacketType = MQTT_PACKET_TYPE_PUBREC; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRelSend, - MQTTNoMemory, MQTTStateNull, - MQTTSendFailed, false ); + MQTTSuccess, MQTTNoMemory, MQTTStateNull, + MQTTSendFailed, false, NULL ); /* Verify that MQTTBadResponse is propagated when deserialization fails upon * receiving a PUBACK. */ currentPacketType = MQTT_PACKET_TYPE_PUBACK; expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, false ); + MQTTIllegalState, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false, NULL ); /* Verify that MQTTBadResponse is propagated when deserialization fails upon * receiving a PINGRESP. */ currentPacketType = MQTT_PACKET_TYPE_PINGRESP; expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, false ); + MQTTIllegalState, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false, NULL ); /* Verify that MQTTBadResponse is propagated when deserialization fails upon * receiving a SUBACK. */ currentPacketType = MQTT_PACKET_TYPE_SUBACK; expectProcessLoopCalls( &context, MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, MQTTStateNull, - MQTTBadResponse, false ); + MQTTIllegalState, MQTTBadResponse, MQTTStateNull, + MQTTBadResponse, false, NULL ); /* Verify that MQTTIllegalState is returned if MQTT_UpdateStateAck(...) * provides an unknown state such as MQTTStateNull to sendPublishAcks(...). */ currentPacketType = MQTT_PACKET_TYPE_PUBREC; expectProcessLoopCalls( &context, MQTTSuccess, MQTTPubRelSend, - MQTTSuccess, MQTTStateNull, - MQTTIllegalState, false ); + MQTTSuccess, MQTTSuccess, MQTTStateNull, + MQTTIllegalState, false, NULL ); } /** @@ -1253,8 +1514,8 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) context.waitingForPingResp = false; context.keepAliveIntervalSec = 0; expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTIllegalState, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); /* Coverage for the branch path where keep alive interval is greater than 0, * and the interval has expired. */ @@ -1264,8 +1525,8 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; context.lastPacketTime = getTime(); expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTIllegalState, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); /* Coverage for the branch path where PINGRESP timeout interval hasn't expired. */ mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); @@ -1276,8 +1537,8 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) context.pingReqSendTimeMs = MQTT_ONE_SECOND_TO_MS; context.pingRespTimeoutMs = MQTT_ONE_SECOND_TO_MS; expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTIllegalState, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); /* Coverage for the branch path where a PINGRESP hasn't been sent out yet. */ mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); @@ -1286,8 +1547,8 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; context.lastPacketTime = 0; expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTSuccess, false ); + MQTTIllegalState, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); } /** @@ -1316,8 +1577,8 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths( void ) context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; context.waitingForPingResp = true; expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, - MQTTSuccess, MQTTStateNull, - MQTTKeepAliveTimeout, false ); + MQTTIllegalState, MQTTSuccess, MQTTStateNull, + MQTTKeepAliveTimeout, false, NULL ); } /** From ed459b0190b13ab8b68477ad38abb090536208e0 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Tue, 30 Jun 2020 12:01:33 -0700 Subject: [PATCH 563/844] Add intermediate script for complexity (#1026) * Add intermediate script for complexity --- tools/ci/complexity.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 tools/ci/complexity.sh diff --git a/tools/ci/complexity.sh b/tools/ci/complexity.sh new file mode 100755 index 0000000000..5dfd647aca --- /dev/null +++ b/tools/ci/complexity.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +function usage { + cat < /dev/null +if [ $? -ne 0 ] ; then + echo "complexity not found" + exit 1 +fi + +# Run complexity. The `--threshold` option prints functions with a cylcomatic +# complexity greater than or equal to the value. The `--horrid-threshold` makes +# the program exit with a non-zero exit code if a function is strictly greater +# than the value. +complexity --scores --threshold=9 --horrid-threshold=8 $@ + +# When no functions are scored (i.e. below the threshold) complexity has an +# exit code of 5. The "horrid-threshold" value will cause the program to exit +# with a different code. Other non-zero values indicate a failure of some type. +if [ $? -eq 5 ] ; then + exit 0 +else + exit $? +fi From ff26deedc14080cd81dffd36ab3c36bb786b1a0a Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Tue, 30 Jun 2020 12:23:25 -0700 Subject: [PATCH 564/844] MISRA violation fixes (#1027) --- libraries/standard/mqtt/src/mqtt.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 0c9f39f6a1..3b405d38eb 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -874,7 +874,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_PINGRESP: status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); - invokeAppCallback = ( manageKeepAlive ) ? false : true; + invokeAppCallback = ( manageKeepAlive == true ) ? false : true; if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) ) { @@ -1704,7 +1704,6 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, MQTTStatus_t status = MQTTBadParameter; MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; - MQTTPacketInfo_t incomingPacket; if( ( pContext != NULL ) && ( pContext->callbacks.getTime != NULL ) ) { @@ -1814,7 +1813,7 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) { packetId = pContext->nextPacketId; - if( pContext->nextPacketId == UINT16_MAX ) + if( pContext->nextPacketId == ( uint16_t ) UINT16_MAX ) { pContext->nextPacketId = 1; } From a4ad8baf79034df4c58c5a259af0a31179323cec Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Tue, 30 Jun 2020 12:35:46 -0700 Subject: [PATCH 565/844] Handle session present error case (#1025) Handle session present error case Handle an error case when a session present flag is received from broker and a clean session is requested. --- libraries/standard/mqtt/src/mqtt.c | 16 ++++++++ libraries/standard/mqtt/utest/mqtt_utest.c | 45 +++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 3b405d38eb..590e65e48a 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -229,6 +229,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, * * @param[in] pContext Initialized MQTT context. * @param[in] timeoutMs Timeout for waiting for CONNACK packet. + * @param[in] cleanSession Clean session flag set by application. * @param[out] pIncomingPacket List of MQTT subscription info. * @param[out] pSessionPresent Whether a previous session was present. * Only relevant if not establishing a clean session. @@ -240,6 +241,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, */ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, uint32_t timeoutMs, + bool cleanSession, MQTTPacketInfo_t * pIncomingPacket, bool * pSessionPresent ); @@ -1064,6 +1066,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, uint32_t timeoutMs, + bool cleanSession, MQTTPacketInfo_t * pIncomingPacket, bool * pSessionPresent ) { @@ -1139,6 +1142,18 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, status = MQTT_DeserializeAck( pIncomingPacket, NULL, pSessionPresent ); } + /* If a clean session is requested, a session present should not be set by + * broker. */ + if( status == MQTTSuccess ) + { + if( ( cleanSession == true ) && ( *pSessionPresent == true ) ) + { + LogError( ( "Unexpected session present flag in CONNACK response from broker." + " CONNECT request with clean session was made with broker." ) ); + status = MQTTBadResponse; + } + } + if( status != MQTTSuccess ) { LogError( ( "CONNACK recv failed with status = %s.", @@ -1362,6 +1377,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, { status = receiveConnack( pContext, timeoutMs, + pConnectInfo->cleanSession, &incomingPacket, pSessionPresent ); } diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 788f5b6170..c2409ebccb 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -677,7 +677,7 @@ void test_MQTT_Connect_receiveConnack( void ) MQTTContext_t mqttContext; MQTTConnectInfo_t connectInfo; uint32_t timeout = 0; - bool sessionPresent; + bool sessionPresent, sessionPresentExpected; MQTTStatus_t status; MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; @@ -728,6 +728,18 @@ void test_MQTT_Connect_receiveConnack( void ) MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTBadResponse ); status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Test case when broker sends session present flag in response to a + * clean session connection request. */ + mqttContext.transportInterface.recv = transportRecvSuccess; + connectInfo.cleanSession = true; + sessionPresentExpected = true; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresentExpected ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); } /** @@ -828,6 +840,7 @@ void test_MQTT_Connect_resendPendingAcks( void ) setupNetworkBuffer( &networkBuffer ); memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + memset( ( void * ) &connectInfo, 0x00, sizeof( connectInfo ) ); MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); @@ -940,7 +953,7 @@ void test_MQTT_Connect_happy_path() MQTTConnectInfo_t connectInfo; MQTTPublishInfo_t willInfo; uint32_t timeout = 2; - bool sessionPresent; + bool sessionPresent, sessionPresentExpected; MQTTStatus_t status; MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; @@ -977,6 +990,34 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + + /* Request to establish a clean session. */ + mqttContext.connectStatus = MQTTNotConnected; + connectInfo.cleanSession = true; + sessionPresentExpected = false; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresentExpected ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_FALSE( sessionPresent ); + + /* Request to establish a session if present and session present is received + * from broker. */ + mqttContext.connectStatus = MQTTNotConnected; + connectInfo.cleanSession = false; + sessionPresentExpected = true; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresentExpected ); + MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_TRUE( sessionPresent ); } /* ========================================================================== */ From 10174c4de252f03b5012003044a3c12c9a3c4350 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Tue, 30 Jun 2020 15:53:00 -0700 Subject: [PATCH 566/844] MQTT_Connect with retries (#994) Update the logic in MQTT_Connect to work with retries if a timeout of 0ms is passed --- libraries/standard/mqtt/include/mqtt.h | 46 +++++++++-- libraries/standard/mqtt/src/mqtt.c | 62 +++++++++++---- libraries/standard/mqtt/utest/mqtt_config.h | 9 +++ libraries/standard/mqtt/utest/mqtt_utest.c | 85 +++++++++++++++++++++ 4 files changed, 181 insertions(+), 21 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 0cde26d596..7dfef12857 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -152,14 +152,28 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, const MQTTFixedBuffer_t * pNetworkBuffer ); /** - * @brief Establish a MQTT session. + * @brief Establish an MQTT session. * - * @brief param[in] pContext Initialized MQTT context. - * @brief param[in] pConnectInfo MQTT CONNECT packet parameters. - * @brief param[in] pWillInfo Last Will and Testament. Pass NULL if not used. - * @brief param[in] timeoutMs Timeout in milliseconds for receiving - * CONNACK packet. - * @brief param[out] pSessionPresent Whether a previous session was present. + * This function will send MQTT CONNECT packet and receive a CONNACK packet. The + * send and receive from the network is done through the transport interface. + * + * The maximum time this function waits for a CONNACK is decided in one of the + * following ways: + * 1. If #timeoutMs is greater than 0: + * #getTime is used to ensure that the function does not wait more than #timeoutMs + * for CONNACK. + * 2. If #timeoutMs is 0: + * The network receive for CONNACK is retried up to the number of times configured + * by #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] pConnectInfo MQTT CONNECT packet information. + * @param[in] pWillInfo Last Will and Testament. Pass NULL if Last Will and + * Testament is not used. + * @param[in] timeoutMs Maximum time in milliseconds to wait for a CONNACK packet. + * A zero timeout makes use of the retries for receiving CONNACK as configured with + * #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT . + * @param[out] pSessionPresent Whether a previous session was present. * Only relevant if not establishing a clean session. * * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to @@ -170,6 +184,24 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * #MQTTNoDataAvailable if no data available to receive in transport until * the #timeoutMs for CONNACK; * #MQTTSuccess otherwise. + * + * @note This API may spend more time than provided in the timeoutMS parameters in + * certain conditions as listed below: + * + * 1. Timeouts are incorrectly configured - If the timeoutMS is less than the + * transport receive timeout and if a CONNACK packet is not received within + * the transport receive timeout, the API will spend the transport receive + * timeout (which is more time than the timeoutMs). It is the case of incorrect + * timeout configuration as the timeoutMs parameter passed to this API must be + * greater than the transport receive timeout. Please refer to the transport + * interface documentation for more details about timeout configurations. + * + * 2. Partial CONNACK packet is received right before the expiry of the timeout - It + * is possible that first two bytes of CONNACK packet (packet type and remaining + * length) are received right before the expiry of the timeoutMS. In that case, + * the API makes one more network receive call in an attempt to receive the remaining + * 2 bytes. In the worst case, it can happen that the remaining 2 bytes are never + * received and this API will end up spending timeoutMs + transport receive timeout. */ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, const MQTTConnectInfo_t * pConnectInfo, diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 590e65e48a..9efe1b6185 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -26,6 +26,21 @@ #include "mqtt_state.h" #include "private/mqtt_internal.h" + +/** + * @brief The number of retries for receiving CONNACK. + * + * The MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT will be used only when the + * timeoutMs parameter of #MQTT_Connect() is passed as 0 . The transport + * receive for CONNACK will be retried MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT + * times before timing out. A value of 0 for this config will cause the + * transport receive for CONNACK to be invoked only once. + */ +#ifndef MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT + /* Default value for the CONNACK receive retries. */ + #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U ) +#endif + /*-----------------------------------------------------------*/ /** @@ -1072,17 +1087,16 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, { MQTTStatus_t status = MQTTSuccess; MQTTGetCurrentTimeFunc_t getTimeStamp = NULL; - uint32_t entryTimeMs = 0U, remainingTimeMs = 0U; - /* Initialize time taken to the timeout in case a time function is not provided. */ - uint32_t timeTakenMs = timeoutMs; - /* Initialize loop exit condition to true in case a time function is not provided. */ - bool timeExpired = true; + uint32_t entryTimeMs = 0U, remainingTimeMs = 0U, timeTakenMs = 0U; + bool breakFromLoop = false; + uint16_t loopCount = 0U; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); assert( pContext->callbacks.getTime != NULL ); getTimeStamp = pContext->callbacks.getTime; + /* Get the entry time for the function. */ entryTimeMs = getTimeStamp(); @@ -1096,10 +1110,27 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, pContext->transportInterface.networkContext, pIncomingPacket ); - timeExpired = ( calculateElapsedTime( getTimeStamp(), entryTimeMs ) >= timeoutMs ) ? true : false; + /* The loop times out based on 2 conditions. + * 1. If timeoutMs is greater than 0: + * Loop times out based on the timeout calculated by getTime() + * function. + * 2. If timeoutMs is 0: + * Loop times out based on the maximum number of retries config + * MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. This config will control + * maximum the number of retry attempts to read the CONNACK packet. + * A value of 0 for the config will try once to read CONNACK. */ + if( timeoutMs > 0U ) + { + breakFromLoop = ( calculateElapsedTime( getTimeStamp(), entryTimeMs ) >= timeoutMs ) ? true : false; + } + else + { + breakFromLoop = ( loopCount >= MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ) ? true : false; + loopCount++; + } - /* Loop until there is data to read or if the timeout has not expired. */ - } while( ( status == MQTTNoDataAvailable ) && ( timeExpired != true ) ); + /* Loop until there is data to read or if we have exceeded the timeout/retries. */ + } while( ( status == MQTTNoDataAvailable ) && ( breakFromLoop == false ) ); if( status == MQTTSuccess ) { @@ -1114,9 +1145,12 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, } /* Reading the remainder of the packet by transport recv. - * Attempt to read once even if the timeout has expired at this point. + * Attempt to read once even if the timeout has expired. * Invoking receivePacket with remainingTime as 0 would attempt to - * recv from network once.*/ + * recv from network once. If using retries, the remainder of the + * CONNACK packet is tried to be read only once. Reading once would be + * good as the packet type and remaining length was already read. Hence, + * the probability of the remaining 2 bytes available to read is very high. */ if( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) { status = receivePacket( pContext, @@ -1154,14 +1188,14 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, } } - if( status != MQTTSuccess ) + if( status == MQTTSuccess ) { - LogError( ( "CONNACK recv failed with status = %s.", - MQTT_Status_strerror( status ) ) ); + LogInfo( ( "Received MQTT CONNACK successfully from broker." ) ); } else { - LogInfo( ( "Received MQTT CONNACK successfully from broker." ) ); + LogError( ( "CONNACK recv failed with status = %s.", + MQTT_Status_strerror( status ) ) ); } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index 40df17e2a2..dc22c4ffa9 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -56,5 +56,14 @@ typedef uint8_t ** NetworkContext_t; */ #define MQTT_CLIENT_IDENTIFIER "testclient" +/** + * @brief Retry count for reading CONNACK from network. + * + * #MQTT_Connect() can be using retries. If timeout passed as 0 to MQTT_Connect(), + * retries are used to attempt to read from network. The maximum retry count is + * specified by this config. + */ +#define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) + #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index c2409ebccb..eb4b154de3 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -742,6 +742,67 @@ void test_MQTT_Connect_receiveConnack( void ) TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); } +/** + * @brief Test CONNACK reception in MQTT_Connect. + */ +void test_MQTT_Connect_receiveConnack_retries( void ) +{ + MQTTContext_t mqttContext; + MQTTConnectInfo_t connectInfo; + bool sessionPresent; + MQTTStatus_t status; + MQTTTransportInterface_t transport; + MQTTFixedBuffer_t networkBuffer; + MQTTApplicationCallbacks_t callbacks; + MQTTPacketInfo_t incomingPacket; + + /* Same set of tests with retries. MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is 2*/ + setupTransportInterface( &transport ); + setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); + transport.recv = transportRecvFailure; + + memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); + MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + + /* Everything before receiving the CONNACK should succeed. */ + MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); + MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + + /* Test with retries. MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is 2. + * Nothing received from transport interface. */ + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + /* 2 retries. */ + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status ); + + /* Did not receive a CONNACK. */ + incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP; + incomingPacket.remainingLength = 0; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); + + /* Transport receive failure when receiving rest of packet. */ + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status ); + + /* Bad response when deserializing CONNACK. */ + mqttContext.transportInterface.recv = transportRecvSuccess; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTBadResponse ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); +} + /** * @brief Test error cases for MQTT_Connect when a timeout occurs or the packet * needs to be discarded in MQTT_Connect. @@ -1018,6 +1079,30 @@ void test_MQTT_Connect_happy_path() TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); TEST_ASSERT_TRUE( sessionPresent ); + + /* CONNACK receive with timeoutMs=0. Retry logic will be used. */ + sessionPresent = false; + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_IgnoreAndReturn( MQTTSuccess ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + + /* CONNACK receive with timeoutMs=0. Retry logic will be used. + * #MQTTNoDataAvailable for first #MQTT_GetIncomingPacketTypeAndLength + * and success in the second time. */ + incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; + incomingPacket.remainingLength = 2; + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); + MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); + MQTT_DeserializeAck_IgnoreAndReturn( MQTTSuccess ); + status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); } /* ========================================================================== */ From 61a7dfab9b03a15010cdc37d6754d6bab425f0e7 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 1 Jul 2020 12:26:39 -0700 Subject: [PATCH 567/844] Reconnect Logic (#995) feature: Reconnect Logic Sample reconnect logic code for POSIX platform. --- demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 3 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 207 ++++++++++++++---- .../mqtt/integration-test/mqtt_config.h | 2 +- platform/include/reconnect.h | 78 +++++++ platform/posix/transport/CMakeLists.txt | 10 + .../posix/transport/src/reconnect_posix.c | 97 ++++++++ .../posix/transport/transportFilePaths.cmake | 4 + 7 files changed, 351 insertions(+), 50 deletions(-) create mode 100644 platform/include/reconnect.h create mode 100644 platform/posix/transport/src/reconnect_posix.c diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt index 1f0645f6b2..13b4ea4ef0 100644 --- a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -17,7 +17,8 @@ target_include_directories( target_link_libraries( ${DEMO_NAME} PRIVATE - mqtt + mqtt + reconnect_posix ) target_include_directories( diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index c7a76e36bc..a80a4d8de5 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -51,6 +51,10 @@ /* MQTT API header. */ #include "mqtt.h" + +/* Reconnect parameters. */ +#include "reconnect.h" + /** * @brief MQTT server host name. * @@ -132,6 +136,16 @@ */ #define DELAY_BETWEEN_PUBLISHES_SECONDS ( 1U ) +/** + * @brief Number of PUBLISH messages sent per iteration. + */ +#define MQTT_PUBLISH_COUNT_PER_LOOP ( 5U ) + +/** + * @brief Delay in seconds between two iterations of subscribePublishLoop() + */ +#define MQTT_SUBPUB_LOOP_DELAY_SECONDS ( 5U ) + /** * @brief Transport timeout in milliseconds for transport send and receive. */ @@ -182,6 +196,19 @@ static int connectToServer( const char * pServer, uint16_t port, int * pTcpSocket ); +/** + * @brief connect to MQTT broker with reconnection retries. + * If connection fails, retry is attempted after a timeout. + * Timeout value will exponentially increased till maximum + * timeout value is reached or the number of attemps are exhausted. + * + * @param[out] pTcpSocket Pointer to TCP socket file descriptor. Upon + * successful connect, the pointer will point at connected socket descriptor. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on successful connection. + */ +static int connectToServerWithBackoffRetries( int * pTcpSocket ); + /** * @brief The transport send function provided to the MQTT context. * @@ -209,6 +236,19 @@ static int32_t transportRecv( NetworkContext_t tcpSocket, void * pBuffer, size_t bytesToRecv ); +/** + * @brief A function that connects to MQTT broker, + * subscribes a topic, Publishes to the same + * topic MQTT_PUBLISH_COUNT_PER_LOOP number of times, and verifies if it + * receives the Publish message back. + * + * @param[in] tcpSocket TCP socket that is already connected to the broker. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int subscribePublishLoop( int tcpSocket ); + + /** * @brief The timer query function provided to the MQTT context. * @@ -423,6 +463,48 @@ static int connectToServer( const char * pServer, /*-----------------------------------------------------------*/ +static int connectToServerWithBackoffRetries( int * pTcpSocket ) +{ + int status = EXIT_SUCCESS; + bool backoffSuccess = true; + TransportReconnectParams_t reconnectParams; + + /* Initialize reconnect attempts and interval */ + Transport_ReconnectParamsReset( &reconnectParams ); + + /* Attempt to connect to MQTT broker. If connection fails, retry after + * a timeout. Timeout value will exponentially increase till maximum + * attemps are reached. + */ + do + { + /* Establish a TCP connection with the MQTT broker. This example connects + * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at + * the top of this file. */ + LogInfo( ( "Creating a TCP connection to %.*s:%d.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT, + BROKER_PORT ) ); + status = connectToServer( BROKER_ENDPOINT, BROKER_PORT, pTcpSocket ); + + if( status == EXIT_FAILURE ) + { + LogWarn( ( "Connection to the broker failed, sleeping %d seconds before the next attempt.", + ( reconnectParams.reconnectTimeoutSec > MAX_RECONNECT_TIMEOUT_SECONDS ) ? MAX_RECONNECT_TIMEOUT_SECONDS : reconnectParams.reconnectTimeoutSec ) ); + backoffSuccess = Transport_ReconnectBackoffAndSleep( &reconnectParams ); + } + + if( backoffSuccess == false ) + { + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + } + } while ( ( status == EXIT_FAILURE ) && ( backoffSuccess == true ) ); + + return status; +} + +/*-----------------------------------------------------------*/ + static int32_t transportSend( NetworkContext_t tcpSocket, const void * pMessage, size_t bytesToSend ) @@ -812,58 +894,33 @@ static int publishToTopic( MQTTContext_t * pContext ) /*-----------------------------------------------------------*/ -/** - * @brief Entry point of demo. - * - * The example shown below uses MQTT APIs to send and receive MQTT packets - * over the TCP connection established using POSIX sockets. - * The example is single threaded and uses statically allocated memory; - * it uses QOS0 and therefore does not implement any retransmission - * mechanism for Publish messages. - * - */ -int main( int argc, - char ** argv ) +static int subscribePublishLoop( int tcpSocket ) { - int status = EXIT_SUCCESS, tcpSocket; + int status = EXIT_SUCCESS; bool mqttSessionEstablished = false; MQTTContext_t context; MQTTStatus_t mqttStatus; uint32_t publishCount = 0; - const uint32_t maxPublishCount = 5U; - - ( void ) argc; - ( void ) argv; + const uint32_t maxPublishCount = MQTT_PUBLISH_COUNT_PER_LOOP; /* Get the entry time to application. */ globalEntryTimeMs = getTimeMs(); - /* Establish a TCP connection with the MQTT broker. This example connects - * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at - * the top of this file. */ - LogInfo( ( "Creating a TCP connection to %.*s:%d.", + /* Establish MQTT session on top of TCP connection. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", BROKER_ENDPOINT_LENGTH, - BROKER_ENDPOINT, - BROKER_PORT ) ); - status = connectToServer( BROKER_ENDPOINT, BROKER_PORT, &tcpSocket ); + BROKER_ENDPOINT ) ); + + /* Sends an MQTT Connect packet over the already connected TCP socket + * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ + status = establishMqttSession( &context, tcpSocket ); - /* Establish MQTT session on top of TCP connection. */ if( status == EXIT_SUCCESS ) { - /* Sends an MQTT Connect packet over the already connected TCP socket - * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ - LogInfo( ( "Creating an MQTT connection to %.*s.", - BROKER_ENDPOINT_LENGTH, - BROKER_ENDPOINT ) ); - status = establishMqttSession( &context, tcpSocket ); - - if( status == EXIT_SUCCESS ) - { - /* Keep a flag for indicating if MQTT session is established. This - * flag will mark that an MQTT DISCONNECT has to be send at the end - * of the demo even if there are intermediate failures. */ - mqttSessionEstablished = true; - } + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be send at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; } if( status == EXIT_SUCCESS ) @@ -965,19 +1022,73 @@ int main( int argc, } } - /* Close the network connection. */ - if( tcpSocket != -1 ) - { - shutdown( tcpSocket, SHUT_RDWR ); - close( tcpSocket ); - } + return status; +} +/*-----------------------------------------------------------*/ - /* Log the success message. */ - if( status == EXIT_SUCCESS ) +/** + * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TCP connection established using POSIX sockets. + * The example is single threaded and uses statically allocated memory; + * it uses QOS0 and therefore does not implement any retransmission + * mechanism for Publish messages. This example runs forever, if connection to + * the broker goes down, the code tries to reconnect to the broker with exponential + * backoff mechanism. + * + */ +int main( int argc, + char ** argv ) +{ + int status = EXIT_SUCCESS; + int tcpSocket = -1; + bool mqttSessionEstablished = false; + + ( void ) argc; + ( void ) argv; + + for( ; ; ) { - LogInfo( ( "Demo completed successfully." ) ); + /* Attempt to connect to the MQTT broker. If connection fails, retry after + * a timeout. Timeout value will be exponentially increased till the maximum + * attemps are reached or maximum timout value is reached. The function + * returns EXIT_FAILURE if the TCP connection cannot be established to + * broker after configured number of attemps. */ + status = connectToServerWithBackoffRetries( &tcpSocket ); + + if( status == EXIT_FAILURE ) + { + /* Log error to indicate connection failure after all + * reconnect attempts are over */ + LogError( ( "Failed to connect to MQTT broker %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + } + else + { + /* If TCP connection is successful, execute Subscribe Publish loop */ + status = subscribePublishLoop( tcpSocket ); + } + + if( status == EXIT_SUCCESS ) + { + /* Log message indicating an iteration completed successfully */ + LogInfo( ( "Demo completed successfully." ) ); + } + + /* Close the network connection. */ + if( tcpSocket != -1 ) + { + shutdown( tcpSocket, SHUT_RDWR ); + close( tcpSocket ); + } + + LogInfo( ( "Short delay before starting the next iteration....\n" ) ); + sleep( MQTT_SUBPUB_LOOP_DELAY_SECONDS ); } + return status; } diff --git a/libraries/standard/mqtt/integration-test/mqtt_config.h b/libraries/standard/mqtt/integration-test/mqtt_config.h index af8cd9937c..efaf4cf878 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_config.h +++ b/libraries/standard/mqtt/integration-test/mqtt_config.h @@ -68,7 +68,7 @@ typedef SSL * NetworkContext_t; * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. * - * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before * they can be completed. While they are awaiting the acknowledgement, the * client must maintain information about their state. The value of this * macro sets the limit on how many simultaneous PUBLISH states an MQTT diff --git a/platform/include/reconnect.h b/platform/include/reconnect.h new file mode 100644 index 0000000000..fbc77490d7 --- /dev/null +++ b/platform/include/reconnect.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file reconnect_config.h + * @brief Declaration of the exponential backoff reconnect logic utility functions + * and constants. + */ + + +/* bools are only defined in C99+ */ +#if defined( __cplusplus ) || __STDC_VERSION__ >= 199901L + #include +#elif !defined( bool ) + #define bool signed char + #define false 0 + #define true 1 +#endif + +/* @brief Max number of connect attempts, set this value to 0 if the client + * must try connecting to the server forever */ +#define MAX_RECONNECT_ATTEMPS 4U + +/* @brief Initial fixed timeout value in seconds between two successive + * connects. A random jitter value is added to every timeout value */ +#define INITIAL_RECONNECT_TIMEOUT_SECONDS 1U +/* @brief Max timout value in seconds */ +#define MAX_RECONNECT_TIMEOUT_SECONDS 128U +/* @brief Max jitter value in seconds */ +#define MAX_JITTER_VALUE_SECONDS 5U + + +/* @brief Transport reconnect parameter */ +typedef struct TransportReconnectParams +{ + uint32_t reconnectTimeoutSec; + uint32_t attemptsDone; +} TransportReconnectParams_t; + + +/** + * @brief Reset reconnection timeout value and number of attempts. + * This function must be called by the application before a new connection + * with the server is attempted. + * + * @param[in, out] reconnectParam structure containing attempts done and timeout + * value. + */ +void Transport_ReconnectParamsReset( TransportReconnectParams_t * reconnectParams ); + +/** + * @brief Simple platfrom specific exponential backoff function. The application + * must use this function between connection failures to add exponential delay. + * This function will block the calling task for the current timeout value. + * + * @param[in, out] reconnectParam structure containing reconnection parameters. + * + * @return true after successful sleep, false when all attempts are exhausted. + */ +bool Transport_ReconnectBackoffAndSleep( TransportReconnectParams_t * reconnectParams ); diff --git a/platform/posix/transport/CMakeLists.txt b/platform/posix/transport/CMakeLists.txt index 0f224c762b..a82147f5f5 100644 --- a/platform/posix/transport/CMakeLists.txt +++ b/platform/posix/transport/CMakeLists.txt @@ -25,3 +25,13 @@ add_library( openssl_posix target_link_libraries( openssl_posix PUBLIC sockets_posix ) + +# Create target for POSIX implementation of reconnect logic. +add_library( reconnect_posix + ${RECONNECT_SOURCES} ) + +target_include_directories( reconnect_posix + PUBLIC + ${COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS} + ${LOGGING_INCLUDE_DIRS} ) + \ No newline at end of file diff --git a/platform/posix/transport/src/reconnect_posix.c b/platform/posix/transport/src/reconnect_posix.c new file mode 100644 index 0000000000..86704e87e7 --- /dev/null +++ b/platform/posix/transport/src/reconnect_posix.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file reconnect_posix.c + * @brief Implementation of the backoff logic when connection fails to the server fails. + */ + +/* Standard includes. */ +#include +#include +#include +#include "reconnect.h" + +/*-----------------------------------------------------------*/ + +bool Transport_ReconnectBackoffAndSleep( TransportReconnectParams_t * pReconnectParams ) +{ + bool status = false; + uint32_t jitter = 0; + + /* If MAX_RECONNECT_ATTEMPTS is set to 0, try forever */ + if( ( pReconnectParams->attemptsDone < MAX_RECONNECT_ATTEMPS ) || + ( 0 == MAX_RECONNECT_ATTEMPS ) ) + { + /* Wait for timer to expire for the next reconnect */ + ( void ) sleep( pReconnectParams->reconnectTimeoutSec ); + + /* Calculate the next timeout value only if timeout value has not + * exceeded MAX_RECONNECT_TIMEOUT_SECONDS */ + if( pReconnectParams->reconnectTimeoutSec < MAX_RECONNECT_TIMEOUT_SECONDS ) + { + /* Calculate jitter value picking a random number + * between 0 and MAX_JITTER_VALUE_SECONDS. */ + jitter = ( rand() % MAX_JITTER_VALUE_SECONDS ); + /* Double the timeout value for the next iteration */ + pReconnectParams->reconnectTimeoutSec += pReconnectParams->reconnectTimeoutSec; + pReconnectParams->reconnectTimeoutSec += jitter; + } + + pReconnectParams->attemptsDone++; + status = true; + } + else + { + /* When max reconnect attempts are exhausted, let application know by returning + * false. Application may choose to restart the connection process after calling + * Transport_ReconnectParamsReset() */ + status = false; + Transport_ReconnectParamsReset( pReconnectParams ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void Transport_ReconnectParamsReset( TransportReconnectParams_t * pReconnectParams ) +{ + uint32_t jitter = 0; + struct timespec tp; + + /* Reset attempts done to zero so that the next connect cycle can start */ + pReconnectParams->attemptsDone = 0; + + /* Get current time to seed pseudo random number generator */ + ( void ) clock_gettime( CLOCK_REALTIME, &tp ); + + /* Seed pseudo ramdom number generator with nano seconds */ + srand( tp.tv_nsec ); + + /* Calculate jitter value using picking a random number. */ + jitter = ( rand() % MAX_JITTER_VALUE_SECONDS ); + + /* Reset the timout value to the initial time out value plus jitter */ + pReconnectParams->reconnectTimeoutSec = INITIAL_RECONNECT_TIMEOUT_SECONDS + jitter; +} + +/*-----------------------------------------------------------*/ diff --git a/platform/posix/transport/transportFilePaths.cmake b/platform/posix/transport/transportFilePaths.cmake index ed4d1fc75d..7d77dced55 100644 --- a/platform/posix/transport/transportFilePaths.cmake +++ b/platform/posix/transport/transportFilePaths.cmake @@ -17,6 +17,10 @@ set( PLAINTEXT_TRANSPORT_SOURCES set( OPENSSL_TRANSPORT_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/openssl_posix.c ) +# Reconnect logic source files. +set( RECONNECT_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/src/reconnect_posix.c ) + # Transport Public Include directories. set( COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS ${CMAKE_CURRENT_LIST_DIR}/include From 98ed2ebbcca4f997f2e09428e4268ede412c45f4 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Mon, 6 Jul 2020 13:31:29 -0700 Subject: [PATCH 568/844] Update all HTTP demos and tests to use common transport interface (#1028) * Add demo for mutual auth * Use correct type for private key * Update mutual auth demo based on changes to server auth demo * Create demo config header and transfer certificates there * Fix miscopied macros in demo config header * update docstring for main * Add missing newline * Use files to store keys and print paths * Push certs and mock key * Address PR comments * Change demo to publish messages to AWS IoT Core * Address PR comments * Update mutual auth demo based on latest changes to server auth demo * Update docstrings * Address PR comments * Add support for ALPN * Download root CA cert using curl through CMake * Add link to describe endpoint in docstring * Add missing param docstrings * Remove sigv4 comments * Comment IOT_CORE_ENDPOINT in demo header * Update demos to use common transport interface * Add missing macros in plaintext demo * Test that everything is working as expected * Remove mutual auth demo * Add platform/include dir for CBMC * Update http_client.c to correct version * Change pTransport->pContext -> pTransport->pNetworkContext * Address PR comments * Include stdint.h to reconnect.h * Add include guards * Add missing period --- demos/http/http_demo_basic_tls/CMakeLists.txt | 28 +- .../certificates/amazon.crt | 20 - demos/http/http_demo_basic_tls/demo_config.h | 6 +- .../http_demo_basic_tls/http_demo_basic_tls.c | 562 ++---------------- demos/http/http_demo_plaintext/CMakeLists.txt | 3 +- demos/http/http_demo_plaintext/demo_config.h | 15 +- .../http_demo_plaintext/http_demo_plaintext.c | 499 ++++++---------- .../http/cbmc/proofs/Makefile-project-defines | 1 + libraries/standard/http/httpFilePaths.cmake | 3 +- libraries/standard/http/include/http_client.h | 64 +- libraries/standard/http/src/http_client.c | 26 +- .../standard/http/utest/http_send_utest.c | 39 +- platform/include/reconnect.h | 6 + platform/posix/transport/CMakeLists.txt | 23 +- .../posix/transport/include/openssl_posix.h | 3 + platform/posix/transport/src/openssl_posix.c | 3 - 16 files changed, 326 insertions(+), 975 deletions(-) delete mode 100644 demos/http/http_demo_basic_tls/certificates/amazon.crt diff --git a/demos/http/http_demo_basic_tls/CMakeLists.txt b/demos/http/http_demo_basic_tls/CMakeLists.txt index 874eebd610..50894b82db 100644 --- a/demos/http/http_demo_basic_tls/CMakeLists.txt +++ b/demos/http/http_demo_basic_tls/CMakeLists.txt @@ -2,17 +2,6 @@ set( DEMO_NAME "http_demo_basic_tls" ) # Demo target. add_executable(${DEMO_NAME}) -set(OPENSSL_USE_STATIC_LIBS TRUE) -find_package(OpenSSL REQUIRED) - -# Verify the minimum OpenSSL version required -if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) - message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) -endif() - -set( CMAKE_THREAD_PREFER_PTHREAD ON ) -find_package(Threads REQUIRED) - target_sources( ${DEMO_NAME} PRIVATE @@ -23,12 +12,7 @@ target_link_libraries( ${DEMO_NAME} PRIVATE http - OpenSSL::Crypto - OpenSSL::SSL - # SSL uses Threads and on some platforms require explicit linking. - Threads::Threads - # SSL uses Dynamic Loading and on some platforms requires explicit linking. - ${CMAKE_DL_LIBS} + openssl_posix ) target_include_directories( @@ -43,7 +27,15 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -#Copy the certificates and client key to the binary directory. +# Download the Amazon Root CA certificate. +message( "Downloading the Amazon Root CA certificate..." ) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) +execute_process( + COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem + -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt +) + +# Copy the certificates and client key to the binary directory. add_custom_command( TARGET ${DEMO_NAME} diff --git a/demos/http/http_demo_basic_tls/certificates/amazon.crt b/demos/http/http_demo_basic_tls/certificates/amazon.crt deleted file mode 100644 index a6f3e92af5..0000000000 --- a/demos/http/http_demo_basic_tls/certificates/amazon.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj -ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM -9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw -IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 -VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L -93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm -jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA -A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI -U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs -N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv -o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU -5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy -rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE----- diff --git a/demos/http/http_demo_basic_tls/demo_config.h b/demos/http/http_demo_basic_tls/demo_config.h index 024381c033..6cb8b4e05a 100644 --- a/demos/http/http_demo_basic_tls/demo_config.h +++ b/demos/http/http_demo_basic_tls/demo_config.h @@ -52,7 +52,7 @@ /** * @brief HTTP server host name. * - * This demo uses httpbin.org: A simple HTTP Request & Response Service. + * @note This demo uses httpbin.org: A simple HTTP Request & Response Service. */ #define SERVER_HOST "httpbin.org" @@ -68,7 +68,7 @@ * * @note This certificate should be PEM-encoded. */ -#define SERVER_CERT_PATH "certificates/amazon.crt" +#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" /** * @brief Paths for different HTTP methods for specified host. @@ -89,7 +89,7 @@ /** * @brief The length in bytes of the user buffer. */ -#define USER_BUFFER_LENGTH ( 1024 ) +#define USER_BUFFER_LENGTH ( 2048 ) /** * @brief Request body to send for PUT and POST requests in this demo. diff --git a/demos/http/http_demo_basic_tls/http_demo_basic_tls.c b/demos/http/http_demo_basic_tls/http_demo_basic_tls.c index a343447a74..c9d0be65eb 100644 --- a/demos/http/http_demo_basic_tls/http_demo_basic_tls.c +++ b/demos/http/http_demo_basic_tls/http_demo_basic_tls.c @@ -24,26 +24,14 @@ #include #include -/* POSIX socket includes. */ -#include -#include -#include -#include -#include - -/* Socket includes. */ -#include -#include - -/* OpenSSL includes. */ -#include -#include +/* Include Demo Config as the first non-system header. */ +#include "demo_config.h" /* HTTP API header. */ #include "http_client.h" -/* Demo Config header. */ -#include "demo_config.h" +/* OpenSSL transport header. */ +#include "openssl_posix.h" /* Check that hostname of the server is defined. */ #ifndef SERVER_HOST @@ -55,6 +43,11 @@ #error "Please define a SERVER_PORT." #endif +/* Check that a path for Root CA Certificate is defined. */ +#ifndef ROOT_CA_CERT_PATH + #error "Please define a ROOT_CA_CERT_PATH." +#endif + /* Check that a path for HTTP Method GET is defined. */ #ifndef GET_PATH #error "Please define a GET_PATH." @@ -82,7 +75,7 @@ /* Check that size of the user buffer is defined. */ #ifndef USER_BUFFER_LENGTH - #define USER_BUFFER_LENGTH ( 1024 ) + #define USER_BUFFER_LENGTH ( 2048 ) #endif /* Check that a request body to send for PUT and POST requests is defined. */ @@ -93,62 +86,52 @@ /** * @brief The length of the HTTP server host name. */ -#define SERVER_HOST_LENGTH ( sizeof( SERVER_HOST ) - 1 ) +#define SERVER_HOST_LENGTH ( sizeof( SERVER_HOST ) - 1 ) /** * @brief The length of the HTTP GET method. */ -#define HTTP_METHOD_GET_LENGTH ( sizeof( HTTP_METHOD_GET ) - 1 ) +#define HTTP_METHOD_GET_LENGTH ( sizeof( HTTP_METHOD_GET ) - 1 ) /** * @brief The length of the HTTP HEAD method. */ -#define HTTP_METHOD_HEAD_LENGTH ( sizeof( HTTP_METHOD_HEAD ) - 1 ) +#define HTTP_METHOD_HEAD_LENGTH ( sizeof( HTTP_METHOD_HEAD ) - 1 ) /** * @brief The length of the HTTP PUT method. */ -#define HTTP_METHOD_PUT_LENGTH ( sizeof( HTTP_METHOD_PUT ) - 1 ) +#define HTTP_METHOD_PUT_LENGTH ( sizeof( HTTP_METHOD_PUT ) - 1 ) /** * @brief The length of the HTTP POST method. */ -#define HTTP_METHOD_POST_LENGTH ( sizeof( HTTP_METHOD_POST ) - 1 ) +#define HTTP_METHOD_POST_LENGTH ( sizeof( HTTP_METHOD_POST ) - 1 ) /** * @brief The length of the HTTP GET path. */ -#define GET_PATH_LENGTH ( sizeof( GET_PATH ) - 1 ) +#define GET_PATH_LENGTH ( sizeof( GET_PATH ) - 1 ) /** * @brief The length of the HTTP HEAD path. */ -#define HEAD_PATH_LENGTH ( sizeof( HEAD_PATH ) - 1 ) +#define HEAD_PATH_LENGTH ( sizeof( HEAD_PATH ) - 1 ) /** * @brief The length of the HTTP PUT path. */ -#define PUT_PATH_LENGTH ( sizeof( PUT_PATH ) - 1 ) +#define PUT_PATH_LENGTH ( sizeof( PUT_PATH ) - 1 ) /** * @brief The length of the HTTP POST path. */ -#define POST_PATH_LENGTH ( sizeof( POST_PATH ) - 1 ) - -/** - * @brief Length of path to server certificate. - */ -#define SERVER_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( SERVER_CERT_PATH ) - 1 ) ) +#define POST_PATH_LENGTH ( sizeof( POST_PATH ) - 1 ) /** * @brief Length of the request body. */ -#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) - -/** - * @brief Length of an IPv6 address when converted to hex digits. - */ -#define IPV6_ADDRESS_STRING_LENGTH ( 40 ) +#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) /** * @brief A buffer used in the demo for storing HTTP request headers and @@ -160,77 +143,8 @@ */ static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; -/** - * @brief Definition of the HTTP network context. - * - * @note For this TLS demo, the socket descriptor and SSL context is used. - */ -struct NetworkContext -{ - int tcpSocket; - SSL * pSslContext; -}; - /*-----------------------------------------------------------*/ -/** - * @brief Performs a DNS lookup on the given host name, then establishes a TCP - * connection to the server. - * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pTcpSocket The output parameter to return the created socket descriptor. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. - */ -static int connectToServer( const char * pServer, - size_t serverLen, - uint16_t port, - int * pTcpSocket ); - -/** - * @brief Set up a TLS connection over an existing TCP connection. - * - * @param[in] tcpSocket Socket descriptor corresponding to the existing TCP connection. - * @param[out] pSslContext The output parameter to return the created SSL context. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. - */ -static int tlsSetup( int tcpSocket, - SSL ** pSslContext ); - -/** - * @brief The transport send function that defines the transport interface. - * - * This is passed as the #HTTPTransportInterface.send function and used to - * send data over the network. - * - * @param[in] pContext User defined context (TCP socket and SSL context for this demo). - * @param[in] pBuffer Buffer containing the bytes to send over the network stack. - * @param[in] bytesToSend Number of bytes to send over the network. - * - * @return Number of bytes sent if successful; otherwise negative value on error. - */ -static int32_t transportSend( NetworkContext_t pNetworkContext, - const void * pBuffer, - size_t bytesToSend ); - -/** - * @brief The transport receive function that defines the transport interface. - * - * This is passed as the #HTTPTransportInterface.recv function used for reading - * data received from the network. - * - * @param[in] pContext User defined context (TCP socket and SSL context for this demo). - * @param[out] pBuffer Buffer to read network data into. - * @param[in] bytesToRead Number of bytes requested from the network. - * - * @return Number of bytes received if successful; otherwise negative value on error. - */ -static int32_t transportRecv( NetworkContext_t pNetworkContext, - void * pBuffer, - size_t bytesToRecv ); - /** * @brief Send an HTTP request based on a specified method and path, then * print the response received from the server. @@ -243,7 +157,7 @@ static int32_t transportRecv( NetworkContext_t pNetworkContext, * * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. */ -static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, +static int sendHttpRequest( const TransportInterface_t * pTransportInterface, const char * pMethod, size_t methodLen, const char * pPath, @@ -251,371 +165,7 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface /*-----------------------------------------------------------*/ -static int connectToServer( const char * pServer, - size_t serverLen, - uint16_t port, - int * pTcpSocket ) -{ - int returnStatus = EXIT_SUCCESS; - struct addrinfo hints, * pIndex, * pListHead = NULL; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - char resolvedIpAddr[ IPV6_ADDRESS_STRING_LENGTH ]; - - /* Initialize string to store the resolved IP address from the host name. */ - ( void ) memset( resolvedIpAddr, 0, IPV6_ADDRESS_STRING_LENGTH ); - /* Add hints to retrieve only TCP sockets in getaddrinfo. */ - ( void ) memset( &hints, 0, sizeof( hints ) ); - /* Address family of either IPv4 or IPv6. */ - hints.ai_family = AF_UNSPEC; - /* TCP Socket. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - /* Perform a DNS lookup on the given host name. */ - returnStatus = getaddrinfo( pServer, NULL, &hints, &pListHead ); - - if( returnStatus != -1 ) - { - LogInfo( ( "Performing DNS lookup: Host=%.*s.", - ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST ) ); - - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) - { - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - inet_ntop( pServerInfo->sa_family, - &( ( struct sockaddr_in * ) pServerInfo )->sin_addr, - resolvedIpAddr, - sizeof( resolvedIpAddr ) ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - inet_ntop( pServerInfo->sa_family, - &( ( struct sockaddr_in6 * ) pServerInfo )->sin6_addr, - resolvedIpAddr, - sizeof( resolvedIpAddr ) ); - } - - LogInfo( ( "Attempting to connect to server: Host=%.*s, IP address=%s.", - ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, resolvedIpAddr ) ); - - returnStatus = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( returnStatus == -1 ) - { - LogError( ( "Failed to connect to server: Host=%.*s, IP address=%s.", - ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, resolvedIpAddr ) ); - close( *pTcpSocket ); - } - else - { - LogInfo( ( "Connected to IP address: %s.", - resolvedIpAddr ) ); - break; - } - } - - if( pIndex == NULL ) - { - /* Fail if no connection could be established. */ - LogError( ( "Could not connect to any resolved IP address from %.*s.", - ( int32_t ) serverLen, - pServer ) ); - returnStatus = EXIT_FAILURE; - } - else - { - LogInfo( ( "Established TCP connection: Server=%.*s.\n", - ( int32_t ) serverLen, - pServer ) ); - returnStatus = EXIT_SUCCESS; - } - } - else - { - LogError( ( "Could not resolve host %.*s.\n", - ( int32_t ) serverLen, - pServer ) ); - returnStatus = EXIT_FAILURE; - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return returnStatus; -} - -/*-----------------------------------------------------------*/ - -static int tlsSetup( int tcpSocket, - SSL ** pSslContext ) -{ - int returnStatus = EXIT_SUCCESS; - long verifyPeerCertStatus = X509_V_OK; - int sslStatus = 0; - char * cwd = getcwd( NULL, 0 ); - FILE * pRootCaFile = NULL; - X509 * pRootCa = NULL; - - /* Setup for creating a TLS client. */ - SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); - - if( pSslSetup != NULL ) - { - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. - * The mask returned by SSL_CTX_set_mode does not need to be checked. */ - ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); - - /* Log the absolute directory based on first character of certificate path. */ - if( ( SERVER_CERT_PATH[ 0 ] == '/' ) || ( SERVER_CERT_PATH[ 0 ] == '\\' ) ) - { - LogInfo( ( "Attempting to open root CA certificate: Path=%.*s.", - ( int32_t ) SERVER_CERT_PATH_LENGTH, - SERVER_CERT_PATH ) ); - } - else - { - LogInfo( ( "Attempting to open root CA certificate: Path=%s/%.*s.", - cwd, - ( int32_t ) SERVER_CERT_PATH_LENGTH, - SERVER_CERT_PATH ) ); - } - - /* OpenSSL does not provide a single function for reading and loading - * certificates from files into stores, so the file API must be called. */ - pRootCaFile = fopen( SERVER_CERT_PATH, "r" ); - - if( pRootCaFile == NULL ) - { - LogError( ( "fopen failed to find the root CA certificate file: " - "SERVER_CERT_PATH=%.*s.", - ( int32_t ) SERVER_CERT_PATH_LENGTH, - SERVER_CERT_PATH ) ); - } - else - { - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - } - - if( pRootCa == NULL ) - { - LogError( ( "PEM_read_X509 failed to read the " - "root CA certificate filestream." ) ); - } - else - { - /* Add the server's root CA to the set of trusted certificates. */ - sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), - pRootCa ); - } - } - - /* Set up the TLS connection. */ - if( sslStatus == 1 ) - { - /* Create a new SSL context. */ - *pSslContext = SSL_new( pSslSetup ); - - if( *pSslContext == NULL ) - { - LogError( ( "SSL_new failed to create a new SSL context." ) ); - sslStatus = 0; - } - else - { - /* Enable SSL peer verification. */ - SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); - - sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); - - if( sslStatus != 1 ) - { - LogError( ( "SSL_set_fd failed to set the socket fd to SSL context." ) ); - } - } - - /* Perform the TLS handshake. */ - if( sslStatus == 1 ) - { - sslStatus = SSL_connect( *pSslContext ); - - if( sslStatus != 1 ) - { - LogError( ( "SSL_connect failed to perform TLS handshake." ) ); - } - } - - /* Verify X509 certificate from peer. */ - if( sslStatus == 1 ) - { - verifyPeerCertStatus = SSL_get_verify_result( *pSslContext ); - - if( verifyPeerCertStatus != X509_V_OK ) - { - LogError( ( "SSL_get_verify_result failed to verify X509 " - "certificate from peer." ) ); - sslStatus = 0; - } - } - - /* Clean up on error. */ - if( sslStatus == 0 ) - { - SSL_free( *pSslContext ); - *pSslContext = NULL; - } - } - else - { - LogError( ( "X509_STORE_add_cert failed to add certificate to store." ) ); - } - - if( cwd != NULL ) - { - free( cwd ); - } - - if( pRootCaFile != NULL ) - { - ( void ) fclose( pRootCaFile ); - } - - if( pRootCa != NULL ) - { - X509_free( pRootCa ); - } - - if( pSslSetup != NULL ) - { - SSL_CTX_free( pSslSetup ); - } - - /* Log failure or success and return the correct exit status. */ - if( sslStatus == 0 ) - { - LogError( ( "Failed to establish a TLS connection: Host=%.*s.", - ( int32_t ) SERVER_HOST_LENGTH, - SERVER_HOST ) ); - returnStatus = EXIT_FAILURE; - } - else - { - LogInfo( ( "Established a TLS connection: Host=%.*s.\n\n", - ( int32_t ) SERVER_HOST_LENGTH, - SERVER_HOST ) ); - returnStatus = EXIT_SUCCESS; - } - - return returnStatus; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportSend( NetworkContext_t pNetworkContext, - const void * pBuffer, - size_t bytesToSend ) -{ - int32_t bytesSent = 0; - int pollStatus = 0; - struct pollfd fileDescriptor; - - /* Initialize the file descriptor. */ - fileDescriptor.events = POLLOUT; - fileDescriptor.revents = 0; - /* Set the file descriptor for poll. */ - fileDescriptor.fd = pNetworkContext->tcpSocket; - - /* Poll the file descriptor to check if SSL_Write can be done now. */ - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - - if( pollStatus > 0 ) - { - bytesSent = ( int32_t ) SSL_write( pNetworkContext->pSslContext, pBuffer, bytesToSend ); - } - else if( pollStatus == 0 ) - { - LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); - } - else - { - LogError( ( "Polling of the SSL socket for write buffer availability failed:" - " status=%d", - pollStatus ) ); - bytesSent = -1; - } - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportRecv( NetworkContext_t pNetworkContext, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - int pollStatus = -1, bytesAvailableToRead = 0; - struct pollfd fileDescriptor; - - /* Initialize the file descriptor. */ - fileDescriptor.events = POLLIN | POLLPRI; - fileDescriptor.revents = 0; - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pNetworkContext->pSslContext ); - - /* Check if there are any pending data available for read. */ - bytesAvailableToRead = SSL_pending( pNetworkContext->pSslContext ); - - /* Poll only if there is no data available yet to read. */ - if( bytesAvailableToRead <= 0 ) - { - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - } - - /* bytesAvailableToRead > 0 means that there was pending data to be read. - * pollStatus > 0 means that there was no pending data, but it became available - * during polling. If either holds true, read the available data. */ - if( ( bytesAvailableToRead > 0 ) || ( pollStatus > 0 ) ) - { - bytesReceived = ( int32_t ) SSL_read( pNetworkContext->pSslContext, pBuffer, bytesToRecv ); - } - /* Poll timed out. */ - else if( pollStatus == 0 ) - { - LogInfo( ( "Poll timed out and there is no data to read from the buffer." ) ); - } - else - { - LogError( ( "Poll returned with status = %d.", pollStatus ) ); - bytesReceived = -1; - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, +static int sendHttpRequest( const TransportInterface_t * pTransportInterface, const char * pMethod, size_t methodLen, const char * pPath, @@ -678,6 +228,7 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface ( int32_t ) requestHeaders.headersLen, ( char * ) requestHeaders.pBuffer, ( int32_t ) REQUEST_BODY_LENGTH, REQUEST_BODY ) ); + /* Send the request and receive the response. */ httpStatus = HTTPClient_Send( pTransportInterface, &requestHeaders, @@ -741,35 +292,55 @@ int main( int argc, { /* Return value of main. */ int returnStatus = EXIT_SUCCESS; - /* The HTTP Client library transport layer interface. */ - HTTPTransportInterface_t transportInterface; - /* Structure based on the definition of the HTTP network context. */ - struct NetworkContext networkContext; + /* The transport layer interface used by the HTTP Client library. */ + TransportInterface_t transportInterface; + /* The network context for the transport layer interface. */ + NetworkContext_t networkContext; + /* Credentials to establish the TLS connection. */ + OpensslCredentials_t opensslCredentials; + /* Status returned by OpenSSL transport implementation. */ + OpensslStatus_t opensslStatus; + /* Information about the server to send the HTTP requests. */ + ServerInfo_t serverInfo; ( void ) argc; ( void ) argv; - /**************************** Connect. ******************************/ + /* Initialize TLS credentials. */ + ( void ) memset( &opensslCredentials, 0, sizeof( opensslCredentials ) ); + opensslCredentials.pRootCaPath = ROOT_CA_CERT_PATH; + + /* Initialize server information. */ + serverInfo.pHostName = SERVER_HOST; + serverInfo.hostNameLength = SERVER_HOST_LENGTH; + serverInfo.port = SERVER_PORT; - /* Establish TCP connection. */ - returnStatus = connectToServer( SERVER_HOST, SERVER_HOST_LENGTH, - SERVER_PORT, &networkContext.tcpSocket ); + /**************************** Connect. ******************************/ - /* Establish TLS connection on top of TCP connection. */ + /* Establish TLS connection on top of TCP connection using OpenSSL. */ if( returnStatus == EXIT_SUCCESS ) { LogInfo( ( "Performing TLS handshake on top of the TCP connection." ) ); - returnStatus = tlsSetup( networkContext.tcpSocket, - &networkContext.pSslContext ); + + opensslStatus = Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( opensslStatus != OPENSSL_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } } /* Define the transport interface. */ if( returnStatus == EXIT_SUCCESS ) { ( void ) memset( &transportInterface, 0, sizeof( transportInterface ) ); - transportInterface.recv = transportRecv; - transportInterface.send = transportSend; - transportInterface.pContext = &networkContext; + transportInterface.recv = Openssl_Recv; + transportInterface.send = Openssl_Send; + transportInterface.pNetworkContext = &networkContext; } /*********************** Send HTTPS request. ************************/ @@ -816,23 +387,12 @@ int main( int argc, /************************** Disconnect. *****************************/ - /* Close TLS session if established. */ - if( networkContext.pSslContext != NULL ) - { - /* SSL shutdown should be called twice: once to send "close notify" and - * once more to receive the peer's "close notify". */ - if( SSL_shutdown( networkContext.pSslContext ) == 0 ) - { - ( void ) SSL_shutdown( networkContext.pSslContext ); - } + /* Close TLS session. */ + opensslStatus = Openssl_Disconnect( &networkContext ); - SSL_free( networkContext.pSslContext ); - } - - if( networkContext.tcpSocket != -1 ) + if( opensslStatus != OPENSSL_SUCCESS ) { - ( void ) shutdown( networkContext.tcpSocket, SHUT_RDWR ); - ( void ) close( networkContext.tcpSocket ); + returnStatus = EXIT_FAILURE; } return returnStatus; diff --git a/demos/http/http_demo_plaintext/CMakeLists.txt b/demos/http/http_demo_plaintext/CMakeLists.txt index 5c4e4af26a..c99a3302a7 100644 --- a/demos/http/http_demo_plaintext/CMakeLists.txt +++ b/demos/http/http_demo_plaintext/CMakeLists.txt @@ -11,7 +11,8 @@ target_sources( target_link_libraries( ${DEMO_NAME} PRIVATE - http + http + plaintext_posix ) target_include_directories( diff --git a/demos/http/http_demo_plaintext/demo_config.h b/demos/http/http_demo_plaintext/demo_config.h index eeb16094c0..8c0aa252f2 100644 --- a/demos/http/http_demo_plaintext/demo_config.h +++ b/demos/http/http_demo_plaintext/demo_config.h @@ -50,18 +50,16 @@ /** * @brief HTTP server host name. * - * @note A local HTTP server is used for this demo. Instructions for setting - * this up can be found in the top-level README.md file. + * @note This demo uses httpbin.org: A simple HTTP Request & Response Service. */ -#define SERVER_HOST "localhost" +#define SERVER_HOST "httpbin.org" /** * @brief HTTP server port number. * - * @note In general, port 80 is for plaintext HTTP connections. However, - * the default plaintext port from the local http server is used below. + * @note In general, port 80 is for plaintext HTTP connections. */ -#define SERVER_PORT 8080 +#define SERVER_PORT 80 /** * @brief Paths for different HTTP methods for specified host. @@ -86,9 +84,4 @@ */ #define REQUEST_BODY "Hello, world!" -/** - * @brief Length of the request body. - */ -#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) - #endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/http/http_demo_plaintext/http_demo_plaintext.c b/demos/http/http_demo_plaintext/http_demo_plaintext.c index a316eb1e8b..8fb247405e 100644 --- a/demos/http/http_demo_plaintext/http_demo_plaintext.c +++ b/demos/http/http_demo_plaintext/http_demo_plaintext.c @@ -20,364 +20,176 @@ */ /* Standard includes. */ +#include #include #include -/* POSIX socket includes. */ -#include -#include -#include -#include -#include - -#include -#include - /* Include Demo Config as the first non-system header. */ #include "demo_config.h" /* HTTP API header. */ #include "http_client.h" -/** - * @brief Length of an IPv6 address when converted to hex digits. - */ -#define IPV6_ADDRESS_STRING_LEN ( 40 ) +/* Plaintext sockets transport header. */ +#include "plaintext_posix.h" + +/* Check that hostname of the server is defined. */ +#ifndef SERVER_HOST + #error "Please define a SERVER_HOST." +#endif + +/* Check that TLS port of the server is defined. */ +#ifndef SERVER_PORT + #error "Please define a SERVER_PORT." +#endif + +/* Check that a path for HTTP Method GET is defined. */ +#ifndef GET_PATH + #error "Please define a GET_PATH." +#endif + +/* Check that a path for HTTP Method HEAD is defined. */ +#ifndef HEAD_PATH + #error "Please define a HEAD_PATH." +#endif + +/* Check that a path for HTTP Method PUT is defined. */ +#ifndef PUT_PATH + #error "Please define a PUT_PATH." +#endif + +/* Check that a path for HTTP Method POST is defined. */ +#ifndef POST_PATH + #error "Please define a POST_PATH." +#endif + +/* Check that transport timeout for transport send and receive is defined. */ +#ifndef TRANSPORT_SEND_RECV_TIMEOUT_MS + #define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 1000 ) +#endif + +/* Check that size of the user buffer is defined. */ +#ifndef USER_BUFFER_LENGTH + #define USER_BUFFER_LENGTH ( 1024 ) +#endif /** - * @brief Defined by transport layer to check send or receive error. + * @brief The length of the HTTP server host name. */ -extern int errno; +#define SERVER_HOST_LENGTH ( sizeof( SERVER_HOST ) - 1 ) /** - * @brief A string to store the resolved IP address from the host name. + * @brief The length of the HTTP GET method. */ -static char resolvedIpAddr[ IPV6_ADDRESS_STRING_LEN ]; +#define HTTP_METHOD_GET_LENGTH ( sizeof( HTTP_METHOD_GET ) - 1 ) /** - * @brief A buffer used in the demo for storing HTTP request headers and - * HTTP response headers and body. - * - * @note This demo shows how the same buffer can be re-used for storing the HTTP - * response after the HTTP request is sent out. However, the user can also - * decide to use separate buffers for storing the HTTP request and response. + * @brief The length of the HTTP HEAD method. */ -static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; +#define HTTP_METHOD_HEAD_LENGTH ( sizeof( HTTP_METHOD_HEAD ) - 1 ) /** - * @brief Definition of the HTTP network context. - * - * @note An integer is used to store the descriptor of the socket. + * @brief The length of the HTTP PUT method. */ -struct NetworkContext -{ - int tcpSocket; -}; +#define HTTP_METHOD_PUT_LENGTH ( sizeof( HTTP_METHOD_PUT ) - 1 ) /** - * @brief Structure based on the definition of the HTTP network context. + * @brief The length of the HTTP POST method. */ -static struct NetworkContext socketContext; +#define HTTP_METHOD_POST_LENGTH ( sizeof( HTTP_METHOD_POST ) - 1 ) /** - * @brief The HTTP Client library transport layer interface. + * @brief The length of the HTTP GET path. */ -static HTTPTransportInterface_t transportInterface; +#define GET_PATH_LENGTH ( sizeof( GET_PATH ) - 1 ) /** - * @brief Represents header data that will be sent in an HTTP request. + * @brief The length of the HTTP HEAD path. */ -static HTTPRequestHeaders_t requestHeaders; +#define HEAD_PATH_LENGTH ( sizeof( HEAD_PATH ) - 1 ) /** - * @brief Configurations of the initial request headers that are passed to - * #HTTPClient_InitializeRequestHeaders. + * @brief The length of the HTTP PUT path. */ -static HTTPRequestInfo_t requestInfo; +#define PUT_PATH_LENGTH ( sizeof( PUT_PATH ) - 1 ) /** - * @brief Represents a response returned from an HTTP server. + * @brief The length of the HTTP POST path. */ -static HTTPResponse_t response; - -/*-----------------------------------------------------------*/ +#define POST_PATH_LENGTH ( sizeof( POST_PATH ) - 1 ) /** - * @brief Performs a DNS lookup on the given host name, then establishes a TCP - * connection to the server. - * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pTcpSocket Pointer to TCP socket file descriptor. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + * @brief Length of the request body. */ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ); +#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) /** - * @brief The transport send function that defines the transport interface. - * - * This is passed as the #HTTPTransportInterface.send function and used to - * send data over the network. - * - * @param[in] pContext User defined context (TCP socket for this demo). - * @param[in] pBuffer Buffer containing the bytes to send over the network stack. - * @param[in] bytesToSend Number of bytes to write to the network. + * @brief A buffer used in the demo for storing HTTP request headers and + * HTTP response headers and body. * - * @return Number of bytes sent if successful; otherwise negative value on error. + * @note This demo shows how the same buffer can be re-used for storing the HTTP + * response after the HTTP request is sent out. However, the user can also + * decide to use separate buffers for storing the HTTP request and response. */ -static int32_t transportSend( NetworkContext_t pContext, - const void * pBuffer, - size_t bytesToSend ); +static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; -/** - * @brief The transport receive function that defines the transport interface. - * - * This is passed as the #HTTPTransportInterface.recv function used for reading - * data received from the network. - * - * @param[in] pContext User defined context (TCP socket for this demo). - * @param[out] pBuffer Buffer to read network data into. - * @param[in] bytesToRead Number of bytes requested from the network. - * - * @return Number of bytes received if successful; otherwise negative value on error. - */ -static int32_t transportRecv( NetworkContext_t pContext, - void * pBuffer, - size_t bytesToRecv ); +/*-----------------------------------------------------------*/ /** * @brief Send an HTTP request based on a specified method and path, then * print the response received from the server. * * @param[in] pTransportInterface The transport interface for making network calls. - * @param[in] pHost The host name of the server. * @param[in] pMethod The HTTP request method. + * @param[in] methodLen The length of the HTTP request method. * @param[in] pPath The Request-URI to the objects of interest. + * @param[in] pathLen The length of the Request-URI. * * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. */ -static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, - const char * pHost, +static int sendHttpRequest( const TransportInterface_t * pTransportInterface, const char * pMethod, - const char * pPath ); + size_t methodLen, + const char * pPath, + size_t pathLen ); /*-----------------------------------------------------------*/ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ) +static int sendHttpRequest( const TransportInterface_t * pTransportInterface, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ) { + /* Return value of this method. */ int returnStatus = EXIT_SUCCESS; - struct addrinfo hints, * pIndex, * pListHead = NULL; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - struct timeval transportTimeout; - - /* Add hints to retrieve only TCP sockets in getaddrinfo. */ - ( void ) memset( &hints, 0, sizeof( hints ) ); - /* Address family of either IPv4 or IPv6. */ - hints.ai_family = AF_UNSPEC; - /* TCP Socket. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - /* Perform a DNS lookup on the given host name. */ - returnStatus = getaddrinfo( pServer, NULL, &hints, &pListHead ); - - if( returnStatus != -1 ) - { - LogInfo( ( "Performing DNS lookup: Host=%s.", - SERVER_HOST ) ); - - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) - { - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - inet_ntop( pServerInfo->sa_family, - &( ( struct sockaddr_in * ) pServerInfo )->sin_addr, - resolvedIpAddr, - sizeof( resolvedIpAddr ) ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - inet_ntop( pServerInfo->sa_family, - &( ( struct sockaddr_in6 * ) pServerInfo )->sin6_addr, - resolvedIpAddr, - sizeof( resolvedIpAddr ) ); - } - - LogInfo( ( "Attempting to connect to server: Host=%s, IP address=%s.", - SERVER_HOST, resolvedIpAddr ) ); - - returnStatus = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( returnStatus == -1 ) - { - LogError( ( "Failed to connect to server: Host=%s, IP address=%s.", - SERVER_HOST, resolvedIpAddr ) ); - close( *pTcpSocket ); - } - else - { - LogInfo( ( "Connected to IP address: %s.", - resolvedIpAddr ) ); - break; - } - } - - if( pIndex == NULL ) - { - /* Fail if no connection could be established. */ - returnStatus = EXIT_FAILURE; - LogError( ( "Could not connect to any resolved IP address from %.*s.\n", - ( int ) strlen( pServer ), - pServer ) ); - } - else - { - returnStatus = EXIT_SUCCESS; - LogInfo( ( "Established TCP connection: Server=%.*s.\n", - ( int ) strlen( pServer ), - pServer ) ); - } - } - else - { - LogError( ( "Could not resolve host %.*s.\n", - ( int ) strlen( pServer ), - pServer ) ); - returnStatus = EXIT_FAILURE; - } - /* Set the socket option for send and receive timeouts. */ - if( returnStatus == EXIT_SUCCESS ) - { - transportTimeout.tv_sec = 0; - transportTimeout.tv_usec = ( TRANSPORT_SEND_RECV_TIMEOUT_MS * 1000 ); - - /* Set the receive timeout. */ - if( setsockopt( *pTcpSocket, - SOL_SOCKET, - SO_RCVTIMEO, - ( char * ) &transportTimeout, - sizeof( transportTimeout ) ) < 0 ) - { - LogError( ( "Setting socket receive timeout failed." ) ); - returnStatus = EXIT_FAILURE; - } + /* Configurations of the initial request headers that are passed to + * #HTTPClient_InitializeRequestHeaders. */ + HTTPRequestInfo_t requestInfo; + /* Represents a response returned from an HTTP server. */ + HTTPResponse_t response; + /* Represents header data that will be sent in an HTTP request. */ + HTTPRequestHeaders_t requestHeaders; - /* Set the send timeout. */ - if( setsockopt( *pTcpSocket, - SOL_SOCKET, - SO_SNDTIMEO, - ( char * ) &transportTimeout, - sizeof( transportTimeout ) ) < 0 ) - { - LogError( ( "Setting socket send timeout failed." ) ); - returnStatus = EXIT_FAILURE; - } - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return returnStatus; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportSend( NetworkContext_t pContext, - const void * pBuffer, - size_t bytesToSend ) -{ - int32_t bytesSent = 0; - - bytesSent = send( pContext->tcpSocket, pBuffer, bytesToSend, 0 ); - - if( bytesSent < 0 ) - { - /* Check if it was time out */ - if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) - { - /* Set return value to 0 to indicate that send had timed out. */ - bytesSent = 0; - } - } - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportRecv( NetworkContext_t pContext, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - - bytesReceived = recv( pContext->tcpSocket, pBuffer, bytesToRecv, 0 ); - - if( bytesReceived == 0 ) - { - /* Server closed the connection, treat it as an error. */ - bytesReceived = -1; - } - else if( bytesReceived < 0 ) - { - /* Check if it was time out. */ - if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) - { - /* Set return value to 0 to indicate nothing to receive. */ - bytesReceived = 0; - } - } - else - { - /* EMPTY else */ - } - - return bytesReceived; -} + /* Return value of all methods from the HTTP Client library API. */ + HTTPStatus_t httpStatus = HTTP_SUCCESS; -/*-----------------------------------------------------------*/ + assert( pMethod != NULL ); + assert( methodLen > 0 ); -static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface, - const char * pHost, - const char * pMethod, - const char * pPath ) -{ - int returnStatus = EXIT_SUCCESS; - HTTPStatus_t httpStatus = HTTP_SUCCESS; + /* Initialize all HTTP Client library API structs to 0. */ + ( void ) memset( &requestInfo, 0, sizeof( requestInfo ) ); + ( void ) memset( &response, 0, sizeof( response ) ); + ( void ) memset( &requestHeaders, 0, sizeof( requestHeaders ) ); /* Initialize the request object. */ - requestInfo.pHost = pHost; - requestInfo.hostLen = strlen( pHost ); + requestInfo.pHost = SERVER_HOST; + requestInfo.hostLen = SERVER_HOST_LENGTH; requestInfo.method = pMethod; - requestInfo.methodLen = strlen( pMethod ); + requestInfo.methodLen = methodLen; requestInfo.pPath = pPath; - requestInfo.pathLen = strlen( pPath ); + requestInfo.pathLen = pathLen; /* Set "Connection" HTTP header to "keep-alive" so that multiple requests * can be sent over the same established TCP connection. */ @@ -397,14 +209,16 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface response.pBuffer = userBuffer; response.bufferLen = USER_BUFFER_LENGTH; - LogInfo( ( "Sending HTTP %s request to %s%s...", - pMethod, SERVER_HOST, pPath ) ); - LogInfo( ( "Request Headers:\n%.*s", - ( int32_t ) requestHeaders.headersLen, - ( char * ) requestHeaders.pBuffer ) ); - LogInfo( ( "Request Body:\n%.*s\n", - ( int32_t ) REQUEST_BODY_LENGTH, - REQUEST_BODY ) ); + LogInfo( ( "Sending HTTP %.*s request to %.*s%.*s...", + ( int32_t ) methodLen, pMethod, + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, + ( int32_t ) pathLen, pPath ) ); + LogDebug( ( "Request Headers:\n%.*s\n" + "Request Body:\n%.*s\n", + ( int32_t ) requestHeaders.headersLen, + ( char * ) requestHeaders.pBuffer, + ( int32_t ) REQUEST_BODY_LENGTH, REQUEST_BODY ) ); + /* Send the request and receive the response. */ httpStatus = HTTPClient_Send( pTransportInterface, &requestHeaders, @@ -421,21 +235,23 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface if( httpStatus == HTTP_SUCCESS ) { - LogInfo( ( "Received HTTP response from %s%s...", - SERVER_HOST, pPath ) ); - LogInfo( ( "Response Headers:\n%.*s", - ( int32_t ) response.headersLen, - response.pHeaders ) ); - LogInfo( ( "Response Status:\n%u", - response.statusCode ) ); - LogInfo( ( "Response Body:\n%.*s\n", - ( int32_t ) response.bodyLen, - response.pBody ) ); + LogInfo( ( "Received HTTP response from %.*s%.*s...\n" + "Response Headers:\n%.*s\n" + "Response Status:\n%u\n" + "Response Body:\n%.*s\n", + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, + ( int32_t ) pathLen, pPath, + ( int32_t ) response.headersLen, response.pHeaders, + response.statusCode, + ( int32_t ) response.bodyLen, response.pBody ) ); } else { - LogError( ( "Failed to send HTTP %s request to %s%s: Error=%s.", - pMethod, SERVER_HOST, pPath, HTTPClient_strerror( httpStatus ) ) ); + LogError( ( "Failed to send HTTP %.*s request to %.*s%.*s: Error=%s.", + ( int32_t ) methodLen, pMethod, + ( int32_t ) SERVER_HOST_LENGTH, SERVER_HOST, + ( int32_t ) pathLen, pPath, + HTTPClient_strerror( httpStatus ) ) ); } if( httpStatus != HTTP_SUCCESS ) @@ -463,23 +279,50 @@ static int sendHttpRequest( const HTTPTransportInterface_t * pTransportInterface int main( int argc, char ** argv ) { + /* Return value of main. */ int returnStatus = EXIT_SUCCESS; - NetworkContext_t pSocketContext = &socketContext; + /* The transport layer interface used by the HTTP Client library. */ + TransportInterface_t transportInterface; + /* The network context for the transport layer interface. */ + NetworkContext_t networkContext; + /* Status returned by plaintext sockets transport implementation. */ + SocketStatus_t socketStatus; + /* Information about the server to send the HTTP requests. */ + ServerInfo_t serverInfo; ( void ) argc; ( void ) argv; + /* Initialize server information. */ + serverInfo.pHostName = SERVER_HOST; + serverInfo.hostNameLength = SERVER_HOST_LENGTH; + serverInfo.port = SERVER_PORT; + /**************************** Connect. ******************************/ /* Establish TCP connection. */ - returnStatus = connectToServer( SERVER_HOST, SERVER_PORT, &pSocketContext->tcpSocket ); + if( returnStatus == EXIT_SUCCESS ) + { + LogInfo( ( "Performing TLS handshake on top of the TCP connection." ) ); + + socketStatus = Plaintext_Connect( &networkContext, + &serverInfo, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( socketStatus != SOCKETS_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + } /* Define the transport interface. */ if( returnStatus == EXIT_SUCCESS ) { - transportInterface.recv = transportRecv; - transportInterface.send = transportSend; - transportInterface.pContext = pSocketContext; + ( void ) memset( &transportInterface, 0, sizeof( transportInterface ) ); + transportInterface.recv = Plaintext_Recv; + transportInterface.send = Plaintext_Send; + transportInterface.pNetworkContext = &networkContext; } /*********************** Send HTTPS request. ************************/ @@ -488,44 +331,50 @@ int main( int argc, if( returnStatus == EXIT_SUCCESS ) { returnStatus = sendHttpRequest( &transportInterface, - SERVER_HOST, HTTP_METHOD_GET, - GET_PATH ); + HTTP_METHOD_GET_LENGTH, + GET_PATH, + GET_PATH_LENGTH ); } /* Send HEAD Request. */ if( returnStatus == EXIT_SUCCESS ) { returnStatus = sendHttpRequest( &transportInterface, - SERVER_HOST, HTTP_METHOD_HEAD, - HEAD_PATH ); + HTTP_METHOD_HEAD_LENGTH, + HEAD_PATH, + HEAD_PATH_LENGTH ); } /* Send PUT Request. */ if( returnStatus == EXIT_SUCCESS ) { returnStatus = sendHttpRequest( &transportInterface, - SERVER_HOST, HTTP_METHOD_PUT, - PUT_PATH ); + HTTP_METHOD_PUT_LENGTH, + PUT_PATH, + PUT_PATH_LENGTH ); } /* Send POST Request. */ if( returnStatus == EXIT_SUCCESS ) { returnStatus = sendHttpRequest( &transportInterface, - SERVER_HOST, HTTP_METHOD_POST, - POST_PATH ); + HTTP_METHOD_POST_LENGTH, + POST_PATH, + POST_PATH_LENGTH ); } /************************** Disconnect. *****************************/ - if( pSocketContext->tcpSocket != -1 ) + /* Close TCP connection. */ + socketStatus = Plaintext_Disconnect( &networkContext ); + + if( socketStatus != SOCKETS_SUCCESS ) { - ( void ) shutdown( pSocketContext->tcpSocket, SHUT_RDWR ); - ( void ) close( pSocketContext->tcpSocket ); + returnStatus = EXIT_FAILURE; } return returnStatus; diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-defines b/libraries/standard/http/cbmc/proofs/Makefile-project-defines index 74c2771669..a1b0b53bce 100644 --- a/libraries/standard/http/cbmc/proofs/Makefile-project-defines +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-defines @@ -24,6 +24,7 @@ INCLUDES += -I$(SRCDIR)/libraries/standard/http/src INCLUDES += -I$(SRCDIR)/libraries/standard/http/third_party/http_parser INCLUDES += -I$(SRCDIR)/demos/logging-stack INCLUDES += -I$(SRCDIR)/demos/http/http_demo_plaintext +INCLUDES += -I$(SRCDIR)/platform/include # Preprocessor definitions -D... DEFINES += -Dhttp_EXPORTS diff --git a/libraries/standard/http/httpFilePaths.cmake b/libraries/standard/http/httpFilePaths.cmake index 804a77752d..854b4a44b0 100644 --- a/libraries/standard/http/httpFilePaths.cmake +++ b/libraries/standard/http/httpFilePaths.cmake @@ -12,7 +12,7 @@ set( HTTP_SOURCES # HTTP library Public Include directories. set( HTTP_INCLUDE_PUBLIC_DIRS ${MODULES_DIR}/standard/http/include - ${MODULES_DIR}/standard/utilities/include ) + ${PLATFORM_DIR}/include ) # HTTP library Private Include directories. set( HTTP_INCLUDE_PRIVATE_DIRS @@ -22,4 +22,3 @@ set( HTTP_INCLUDE_PRIVATE_DIRS # HTTP library Include directories for Tests. set( HTTP_TEST_INCLUDE_DIRS ${MODULES_DIR}/standard/http/third_party/http_parser ) - diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 387c3c9152..51afcbd65d 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -5,6 +5,8 @@ #include /* Include config file before other headers. */ #include "http_config.h" +/* Transport interface include. */ +#include "transport_interface.h" /** * @brief Maximum size, in bytes, of headers allowed from the server. @@ -130,64 +132,6 @@ */ #define HTTP_RANGE_REQUEST_END_OF_FILE -1 -/** - * @brief The NetworkContext is an incomplete type. The application must - * define NetworkContext to the type of their network context. This context - * is passed into the network interface functions. - */ -struct NetworkContext; -typedef struct NetworkContext * NetworkContext_t; - -/** - * @brief Transport interface for sending data over the network. - * - * If the number of bytes written returned is not equal to @p bytesToWrite, then - * #HTTPClient_Send will return #HTTP_NETWORK_ERROR. If a negative value is - * returned then this #HTTPClient_Send will also return #HTTP_NETWORK_ERROR. - * - * @param[in] context User defined context. - * @param[in] pBuffer Buffer to write to the network stack. - * @param[in] bytesToWrite Number of bytes to write to the network. - * - * @return The number of bytes written or a negative network error code. - */ -typedef int32_t ( * HTTPTransportSend_t )( NetworkContext_t pContext, - const void * pBuffer, - size_t bytesToWrite ); - -/** - * @brief Transport interface for reading data on the network. - * - * This function will read up to @p bytesToRead amount of data from the network. - * - * If this function returns a value less than zero, then #HTTPClient_Send will - * return #HTTP_NETWORK_ERROR. - * - * If this function returns less than the bytesToRead and greater than zero, - * then this function will be invoked again if the data in @p pBuffer contains a - * partial HTTP response message and there is room left in the @p pBuffer. - * Repeated invocations will stop if this function returns zero. - * - * @param[in] context User defined context. - * @param[in] pBuffer Buffer to read network data into. - * @param[in] bytesToRead Number of bytes requested from the network. - * - * @return The number of bytes read or a negative error code. - */ -typedef int32_t ( * HTTPTransportRecv_t )( NetworkContext_t pContext, - void * pBuffer, - size_t bytesToRead ); - -/** - * @brief The HTTP Client library transport layer interface. - */ -typedef struct HTTPTransportInterface -{ - HTTPTransportRecv_t recv; /**< Transport receive interface */ - HTTPTransportSend_t send; /**< Transport interface send interface. */ - NetworkContext_t pContext; /**< User defined transport interface context. */ -} HTTPTransportInterface_t; - /** * @brief The HTTP Client library return status. */ @@ -662,7 +606,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * * The @p pResponse returned is valid only if this function returns HTTP_SUCCESS. * - * @param[in] pTransport Transport interface, see #HTTPTransportInterface_t for + * @param[in] pTransport Transport interface, see #TransportInterface_t for * more information. * @param[in] pRequestHeaders Request configuration containing the buffer of * headers to send. @@ -692,7 +636,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, * - #HTTP_SECURITY_ALERT_INVALID_CHARACTER * - #HTTP_SECURITY_ALERT_INVALID_CONTENT_LENGTH */ -HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, +HTTPStatus_t HTTPClient_Send( const TransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index a7d563647e..29c52e0846 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -17,7 +17,7 @@ * bytes than what were specified were sent, then #HTTP_NETWORK_ERROR is * returned. */ -static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport, const uint8_t * pData, size_t dataLen ); @@ -35,7 +35,7 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, * bytes than what were specified were sent, then #HTTP_NETWORK_ERROR is * returned. */ -static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t sendHttpHeaders( const TransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, size_t reqBodyLen, uint32_t sendFlags ); @@ -64,7 +64,7 @@ static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeade * bytes than what was specified were sent, then #HTTP_NETWORK_ERROR is * returned. */ -static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t sendHttpBody( const TransportInterface_t * pTransport, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen ); @@ -100,7 +100,7 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, * more bytes than what was specified were read, then #HTTP_NETWORK_ERROR is * returned. */ -static HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t receiveHttpData( const TransportInterface_t * pTransport, uint8_t * pBuffer, size_t bufferLen, size_t * pBytesReceived ); @@ -134,7 +134,7 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, * @return Returns #HTTP_SUCCESS if successful. Please see #receiveHttpData, * #parseHttpResponse, and #getFinalResponseStatus for other statuses returned. */ -static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t receiveAndParseHttpResponse( const TransportInterface_t * pTransport, HTTPResponse_t * pResponse, const HTTPRequestHeaders_t * pRequestHeaders ); @@ -1523,7 +1523,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ -static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport, const uint8_t * pData, size_t dataLen ) { @@ -1539,7 +1539,7 @@ static HTTPStatus_t sendHttpData( const HTTPTransportInterface_t * pTransport, /* Loop until all data is sent. */ while( ( bytesRemaining > 0UL ) && ( returnStatus != HTTP_NETWORK_ERROR ) ) { - transportStatus = pTransport->send( pTransport->pContext, + transportStatus = pTransport->send( pTransport->pNetworkContext, pIndex, bytesRemaining ); @@ -1614,7 +1614,7 @@ static HTTPStatus_t addContentLengthHeader( HTTPRequestHeaders_t * pRequestHeade /*-----------------------------------------------------------*/ -static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t sendHttpHeaders( const TransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, size_t reqBodyLen, uint32_t sendFlags ) @@ -1650,7 +1650,7 @@ static HTTPStatus_t sendHttpHeaders( const HTTPTransportInterface_t * pTransport /*-----------------------------------------------------------*/ -static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t sendHttpBody( const TransportInterface_t * pTransport, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen ) { @@ -1670,7 +1670,7 @@ static HTTPStatus_t sendHttpBody( const HTTPTransportInterface_t * pTransport, /*-----------------------------------------------------------*/ -static HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t receiveHttpData( const TransportInterface_t * pTransport, uint8_t * pBuffer, size_t bufferLen, size_t * pBytesReceived ) @@ -1683,7 +1683,7 @@ static HTTPStatus_t receiveHttpData( const HTTPTransportInterface_t * pTransport assert( pBuffer != NULL ); assert( pBytesReceived != NULL ); - transportStatus = pTransport->recv( pTransport->pContext, + transportStatus = pTransport->recv( pTransport->pNetworkContext, pBuffer, bufferLen ); @@ -1774,7 +1774,7 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, /*-----------------------------------------------------------*/ -static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t * pTransport, +static HTTPStatus_t receiveAndParseHttpResponse( const TransportInterface_t * pTransport, HTTPResponse_t * pResponse, const HTTPRequestHeaders_t * pRequestHeaders ) { @@ -1851,7 +1851,7 @@ static HTTPStatus_t receiveAndParseHttpResponse( const HTTPTransportInterface_t /*-----------------------------------------------------------*/ -HTTPStatus_t HTTPClient_Send( const HTTPTransportInterface_t * pTransport, +HTTPStatus_t HTTPClient_Send( const TransportInterface_t * pTransport, HTTPRequestHeaders_t * pRequestHeaders, const uint8_t * pRequestBodyBuf, size_t reqBodyBufLen, diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index e393af6a91..ffe7397d24 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -146,6 +146,13 @@ /* Test buffer to share among the test. */ #define HTTP_TEST_BUFFER_LENGTH 1024 + +/* Mock a NetworkContext structure for the test. */ +struct NetworkContext +{ + int mocked; +}; + static uint8_t httpBuffer[ HTTP_TEST_BUFFER_LENGTH ] = { 0 }; /* Tests are run sequentially. If a response has these variables, then they @@ -194,7 +201,7 @@ static enum http_errno httpParsingErrno; /* Response shared among the tests. */ static HTTPResponse_t response = { 0 }; /* Transport interface shared among the tests. */ -static HTTPTransportInterface_t transportInterface = { 0 }; +static TransportInterface_t transportInterface = { 0 }; /* Request headers shared among the tests. */ static HTTPRequestHeaders_t requestHeaders = { 0 }; /* Header parsing callback shared among the tests. */ @@ -232,11 +239,11 @@ static void onHeaderCallback( void * pContext, } /* Successful application transport send interface. */ -static int32_t transportSendSuccess( NetworkContext_t pContext, +static int32_t transportSendSuccess( NetworkContext_t * pNetworkContext, const void * pBuffer, size_t bytesToWrite ) { - ( void ) pContext; + ( void ) pNetworkContext; if( checkContentLength == 1U ) { @@ -258,11 +265,11 @@ static int32_t transportSendSuccess( NetworkContext_t pContext, /* Application transport send interface that returns a network error depending * on the call count. Set sendErrorCall to 0 to return an error on the * first call. Set sendErrorCall to 1 to return an error on the second call. */ -static int32_t transportSendNetworkError( NetworkContext_t pContext, +static int32_t transportSendNetworkError( NetworkContext_t * pNetworkContext, const void * pBuffer, size_t bytesToWrite ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; int32_t retVal = bytesToWrite; @@ -279,11 +286,11 @@ static int32_t transportSendNetworkError( NetworkContext_t pContext, * depending on the call count. Set sendPartialCall to 0 to return less bytes on * the first call. Set sendPartialCall to 1 to return less bytes on the second * call. */ -static int32_t transportSendLessThanBytesToWrite( NetworkContext_t pContext, +static int32_t transportSendLessThanBytesToWrite( NetworkContext_t * pNetworkContext, const void * pBuffer, size_t bytesToWrite ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; int32_t retVal = bytesToWrite; @@ -297,11 +304,11 @@ static int32_t transportSendLessThanBytesToWrite( NetworkContext_t pContext, } /* Application transport send that writes more bytes than expected. */ -static int32_t transportSendMoreThanBytesToWrite( NetworkContext_t pContext, +static int32_t transportSendMoreThanBytesToWrite( NetworkContext_t * pNetworkContext, const void * pBuffer, size_t bytesToWrite ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; return( bytesToWrite + 1 ); @@ -313,11 +320,11 @@ static int32_t transportSendMoreThanBytesToWrite( NetworkContext_t pContext, * second call. The response to send is set in pNetworkData and the current * call count is kept track of in recvCurrentCall. This function will return * zero (timeout condition) when recvStopCall matches recvCurrentCall. */ -static int32_t transportRecvSuccess( NetworkContext_t pContext, +static int32_t transportRecvSuccess( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRead ) { - ( void ) pContext; + ( void ) pNetworkContext; size_t bytesToCopy = 0; /* To test stopping in the middle of a response message, check that the @@ -351,11 +358,11 @@ static int32_t transportRecvSuccess( NetworkContext_t pContext, } /* Application transport receive that return a network error. */ -static int32_t transportRecvNetworkError( NetworkContext_t pContext, +static int32_t transportRecvNetworkError( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRead ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; ( void ) bytesToRead; @@ -363,11 +370,11 @@ static int32_t transportRecvNetworkError( NetworkContext_t pContext, } /* Application transport receive that returns more bytes read than expected. */ -static int32_t transportRecvMoreThanBytesToRead( NetworkContext_t pContext, +static int32_t transportRecvMoreThanBytesToRead( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRead ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; return( bytesToRead + 1 ); @@ -737,7 +744,7 @@ void setUp( void ) httpParsingErrno = HPE_OK; transportInterface.recv = transportRecvSuccess; transportInterface.send = transportSendSuccess; - transportInterface.pContext = NULL; + transportInterface.pNetworkContext = NULL; requestHeaders.pBuffer = httpBuffer; requestHeaders.bufferLen = sizeof( httpBuffer ); memcpy( requestHeaders.pBuffer, HTTP_TEST_REQUEST_HEAD_HEADERS, HTTP_TEST_REQUEST_HEAD_HEADERS_LENGTH ); diff --git a/platform/include/reconnect.h b/platform/include/reconnect.h index fbc77490d7..f7f9938e9d 100644 --- a/platform/include/reconnect.h +++ b/platform/include/reconnect.h @@ -25,6 +25,10 @@ * and constants. */ +#ifndef RECONNECT_H_ +#define RECONNECT_H_ + +#include /* bools are only defined in C99+ */ #if defined( __cplusplus ) || __STDC_VERSION__ >= 199901L @@ -76,3 +80,5 @@ void Transport_ReconnectParamsReset( TransportReconnectParams_t * reconnectParam * @return true after successful sleep, false when all attempts are exhausted. */ bool Transport_ReconnectBackoffAndSleep( TransportReconnectParams_t * reconnectParams ); + +#endif /* ifndef RECONNECT_H_ */ diff --git a/platform/posix/transport/CMakeLists.txt b/platform/posix/transport/CMakeLists.txt index a82147f5f5..f035bc0608 100644 --- a/platform/posix/transport/CMakeLists.txt +++ b/platform/posix/transport/CMakeLists.txt @@ -22,9 +22,29 @@ target_link_libraries( plaintext_posix add_library( openssl_posix ${OPENSSL_TRANSPORT_SOURCES} ) +set(OPENSSL_USE_STATIC_LIBS TRUE) +find_package(OpenSSL REQUIRED) + +# Verify the minimum OpenSSL version required +if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) + message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) +endif() + +set( CMAKE_THREAD_PREFER_PTHREAD ON ) +find_package(Threads REQUIRED) + target_link_libraries( openssl_posix PUBLIC - sockets_posix ) + sockets_posix + PRIVATE + OpenSSL::Crypto + OpenSSL::SSL + # SSL uses Threads and on some platforms require + # explicit linking. + Threads::Threads + # SSL uses Dynamic Loading and on some platforms + # requires explicit linking. + ${CMAKE_DL_LIBS} ) # Create target for POSIX implementation of reconnect logic. add_library( reconnect_posix @@ -34,4 +54,3 @@ target_include_directories( reconnect_posix PUBLIC ${COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS} ${LOGGING_INCLUDE_DIRS} ) - \ No newline at end of file diff --git a/platform/posix/transport/include/openssl_posix.h b/platform/posix/transport/include/openssl_posix.h index dda1ec8740..c8f664ed7a 100644 --- a/platform/posix/transport/include/openssl_posix.h +++ b/platform/posix/transport/include/openssl_posix.h @@ -48,6 +48,9 @@ /************ End of logging configuration ****************/ +/* OpenSSL include. */ +#include + /* Transport includes. */ #include "transport_interface.h" diff --git a/platform/posix/transport/src/openssl_posix.c b/platform/posix/transport/src/openssl_posix.c index da5302c0cc..e1e4db9388 100644 --- a/platform/posix/transport/src/openssl_posix.c +++ b/platform/posix/transport/src/openssl_posix.c @@ -26,9 +26,6 @@ /* POSIX socket include. */ #include -/* OpenSSL include. */ -#include - /* Transport interface include. */ #include "transport_interface.h" From f2870929abf08ae4a231c35bde87937ea2dd7dbe Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 6 Jul 2020 16:46:11 -0700 Subject: [PATCH 569/844] Fix for PINGRESP Deserializing (#1038) Allow deserializing of PINGRESP with NULL pointer to remaining data. --- libraries/standard/mqtt/src/mqtt_lightweight.c | 6 +++++- libraries/standard/mqtt/utest/mqtt_lightweight_utest.c | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 302dd6f1b3..2d1d2a4207 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1992,7 +1992,11 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) ); status = MQTTBadParameter; } - else if( pIncomingPacket->pRemainingData == NULL ) + + /* Pointer for remaining data cannot be NULL for packets other + * than PINGRESP. */ + else if( ( pIncomingPacket->pRemainingData == NULL ) && + ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) { LogError( ( "Remaining data of incoming packet is NULL." ) ); status = MQTTBadParameter; diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 695b725ba9..747de6a3e1 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -1351,17 +1351,17 @@ void test_MQTT_DeserializeAck_pingresp( void ) uint16_t packetIdentifier; bool sessionPresent; MQTTStatus_t status = MQTTSuccess; - uint8_t buffer[ 10 ] = { 0 }; /* Bad remaining length. */ + ( void ) memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); mqttPacketInfo.type = MQTT_PACKET_TYPE_PINGRESP; - mqttPacketInfo.pRemainingData = buffer; mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH + 1; status = MQTT_DeserializeAck( &mqttPacketInfo, &packetIdentifier, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status ); /* Process a valid PINGRESP. */ mqttPacketInfo.remainingLength = MQTT_PACKET_PINGRESP_REMAINING_LENGTH; + mqttPacketInfo.pRemainingData = NULL; status = MQTT_DeserializeAck( &mqttPacketInfo, NULL, NULL ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); } @@ -1583,7 +1583,7 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); - /* Test a typical happy path case for a CONN ACK packet. */ + /* Test a typical happy path case for a CONN ACK packet. */ buffer[ 0 ] = 0x20; /* CONN ACK */ buffer[ 1 ] = 0x02; /* Remaining length. */ From e00212ecdec3e48a95842299435e5de72440c065 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 6 Jul 2020 16:53:32 -0700 Subject: [PATCH 570/844] Add overflow and null parameters checks to MQTT_GetConnectPacketSize and others (#1035) * Add parameter check for will message payload length in MQTT_GetConnectPacketSize() * Add documentation about the necessity of MQTT_GetConnectPacketSize() * Add missing private static function documentation for consistency * Add documentation about starting over the packet ID at 1. * Add more parameter checks to MQTT_SerializeConnect() * Add unit tests for changes. * Disable logging in the HTTP Client unit tests. --- libraries/standard/http/utest/http_config.h | 2 +- .../standard/mqtt/include/mqtt_lightweight.h | 25 ++- libraries/standard/mqtt/src/mqtt.c | 2 + .../standard/mqtt/src/mqtt_lightweight.c | 163 ++++++++++++++---- .../mqtt/utest/mqtt_lightweight_utest.c | 24 ++- 5 files changed, 170 insertions(+), 46 deletions(-) diff --git a/libraries/standard/http/utest/http_config.h b/libraries/standard/http/utest/http_config.h index 57649a9850..cd0f7b84c6 100644 --- a/libraries/standard/http/utest/http_config.h +++ b/libraries/standard/http/utest/http_config.h @@ -16,7 +16,7 @@ /* Configure name and log level for the HTTP library. */ #define LIBRARY_LOG_NAME "HTTP" -#define LIBRARY_LOG_LEVEL LOG_DEBUG +#define LIBRARY_LOG_LEVEL LOG_NONE #include "logging_stack.h" diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index e52b0790fe..f1799febc3 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -263,6 +263,14 @@ struct MQTTPacketInfo /** * @brief Get the size and Remaining Length of an MQTT CONNECT packet. * + * This function must be called before #MQTT_SerializeConnect in order to verify + * the size of the MQTT CONNECT packet that is generated from + * #MQTTConnectInfo_t and optional #MQTTPublishInfo_t. The parameters + * @p pConnectInfo , @p pWillInfo , and @p pRemainingLength are valid only if + * this function returns #MQTTSuccess. + * @p pPacketSize returned is used to verify the size of a #MQTTFixedBuffer_t + * that will hold the MQTT CONNECT packet. + * * @param[in] pConnectInfo MQTT CONNECT packet parameters. * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. * @param[out] pRemainingLength The Remaining Length of the MQTT CONNECT packet. @@ -277,12 +285,19 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, size_t * pPacketSize ); /** - * @brief Serialize an MQTT CONNECT packet in the given buffer. + * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pBuffer. + * + * #MQTT_GetConnectPacketSize should be called with @p pConnectInfo and + * @p pWillInfo before invoking this routine. + * The @p remainingLength was calculated from the parameters in @p pConnectInfo + * and @p pWillInfo using function #MQTT_GetConnectPacketSize. @p pConnectInfo, + * @p pWillInfo , and @p remainingLength are valid only if + * #MQTT_GetConnectPacketSize returned #MQTTSuccess. * * @param[in] pConnectInfo MQTT CONNECT packet parameters. * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. - * @param[out] pBuffer Buffer for packet serialization. + * @param[out] pFixedBuffer Buffer for packet serialization. * * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; @@ -291,7 +306,7 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ); + const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet. @@ -510,6 +525,10 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, /** * @brief Extract the MQTT packet type and length from incoming packet. * + * This function must be called for every incoming packet to retrieve the + * #MQTTPacketInfo_t.type and #MQTTPacketInfo_t.remainingLength. A + * #MQTTPacketInfo_t is not valid until this routine has been invoked. + * * @param[in] readFunc Transport layer read function pointer. * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. This is * where type, remaining length and packet identifier are stored. diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 9efe1b6185..50db50da5d 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1863,6 +1863,8 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) { packetId = pContext->nextPacketId; + /* A packet ID of zero is not a valid packet ID. When the max ID + * is reached the next one should start at 1. */ if( pContext->nextPacketId == ( uint16_t ) UINT16_MAX ) { pContext->nextPacketId = 1; diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 2d1d2a4207..cc285aa657 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -35,11 +35,6 @@ */ #define MQTT_PACKET_CONNECT_HEADER_SIZE ( 10UL ) -/** - * @brief Maximum size of an MQTT CONNECT packet, per MQTT spec. - */ -#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) - /* MQTT CONNECT flags. */ #define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ #define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ @@ -241,6 +236,49 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, size_t remainingLength, const MQTTFixedBuffer_t * pBuffer ); +/** + * Prints the appropriate message for the CONNACK response code if logs are + * enabled. + * + * @param[in] responseCode MQTT standard CONNACK response code. + */ +static void logConnackResponse( uint8_t responseCode ); + +/** + * Encodes the remaining length of the packet using the variable length encoding + * scheme provided in the MQTT v3.1.1 specification. + * + * @param[out] pDestination The destination buffer to store the encoded remaining + * length. + * @param[in] length The remaining length to encode. + * + * @return The location of the byte following the encoded value. + */ +static uint8_t * encodeRemainingLength( uint8_t * pDestination, + size_t length ); + +/** + * Retrieve the size of the remaining length if it were to be encoded. + * + * @param[in] length The remaining length to be encoded. + * + * @return The size of the remaining length if it were to be encoded. + */ +static size_t remainingLengthEncodedSize( size_t length ); + +/** + * Encode a string whose size is at maximum 16 bits in length. + * + * @param[out] pDestination Destination buffer for the encoding. + * @param[in] pSource The source string to encode. + * @param[in] sourceLength The length of the source string to encode. + * + * @return A pointer to the end of the encoded string. + */ +static uint8_t * encodeString( uint8_t * pDestination, + const char * pSource, + uint16_t sourceLength ); + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -314,14 +352,14 @@ static uint8_t * encodeRemainingLength( uint8_t * pDestination, /*-----------------------------------------------------------*/ static uint8_t * encodeString( uint8_t * pDestination, - const char * source, + const char * pSource, uint16_t sourceLength ) { uint8_t * pBuffer = NULL; /* Typecast const char * typed source buffer to const uint8_t *. * This is to use same type buffers in memcpy. */ - const uint8_t * pSourceBuffer = ( const uint8_t * ) source; + const uint8_t * pSourceBuffer = ( const uint8_t * ) pSource; assert( pDestination != NULL ); @@ -1034,6 +1072,8 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket assert( pIncomingPacket != NULL ); assert( pPacketId != NULL ); assert( pPublishInfo != NULL ); + assert( pIncomingPacket->pRemainingData != NULL ); + pVariableHeader = pIncomingPacket->pRemainingData; /* The flags are the lower 4 bits of the first byte in PUBLISH. */ status = processPublishFlags( ( pIncomingPacket->type & 0x0FU ), pPublishInfo ); @@ -1178,6 +1218,7 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, assert( pConnectInfo != NULL ); assert( pBuffer != NULL ); + assert( pBuffer->pBuffer != NULL ); pIndex = pBuffer->pBuffer; /* The first byte in the CONNECT packet is the control packet type. */ @@ -1313,6 +1354,19 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, LogError( ( "Mqtt_GetConnectPacketSize() client identifier must be set." ) ); status = MQTTBadParameter; } + else if( ( pWillInfo != NULL ) && ( pWillInfo->payloadLength > ( size_t ) UINT16_MAX ) ) + { + /* The MQTTPublishInfo_t is reused for the will message. The payload + * length for any other message could be larger than 65,535, but + * the will message length is required to be represented in 2 bytes. + * By bounding the payloadLength of the will message, the CONNECT + * packet will never be larger than 327699 bytes. */ + LogError( ( "The Will Message length must not exceed %d. " + "pWillInfo->payloadLength=%lu", + UINT16_MAX, + pWillInfo->payloadLength ) ); + status = MQTTBadParameter; + } else { /* Add the length of the client identifier. */ @@ -1344,16 +1398,24 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */ connectPacketSize += 1U + remainingLengthEncodedSize( connectPacketSize ); - /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ - if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) - { - status = MQTTBadParameter; - } - else - { - *pRemainingLength = remainingLength; - *pPacketSize = connectPacketSize; - } + /* The connectPacketSize calculated from this function's parameters is + * guaranteed to be less than the maximum MQTT CONNECT packet size, which + * is 327700. If the maximum client identifier length, the maximum will + * message topic length, the maximum will topic payload length, the + * maximum username length, and the maximum password length are all present + * in the MQTT CONNECT packet, the total size will be calculated to be + * 327699: + * (variable length header)10 + + * (maximum client identifier length) 65535 + (encoded length) 2 + + * (maximum will message topic name length) 65535 + (encoded length)2 + + * (maximum will message payload length) 65535 + 2 + + * (maximum username length) 65535 + (encoded length) 2 + + * (maximum password length) 65535 + (encoded length) 2 + + * (packet type field length) 1 + + * (CONNECT packet encoded length) 3 = 327699 */ + + *pRemainingLength = remainingLength; + *pPacketSize = connectPacketSize; LogDebug( ( "CONNECT packet remaining length=%lu and packet size=%lu.", *pRemainingLength, @@ -1368,30 +1430,25 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ) + const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; - - /* Calculate CONNECT packet size. */ - size_t connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; + size_t connectPacketSize = 0; /* Validate arguments. */ - if( ( pConnectInfo == NULL ) || ( pBuffer == NULL ) ) + if( ( pConnectInfo == NULL ) || ( pFixedBuffer == NULL ) ) { LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " - "pBuffer=%p.", + "pFixedBuffer=%p.", pConnectInfo, - pBuffer ) ); + pFixedBuffer ) ); status = MQTTBadParameter; } - /* Check that the full packet size fits within the given buffer. */ - else if( connectPacketSize > pBuffer->size ) + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) { - LogError( ( "Buffer size of %lu is not sufficient to hold " - "serialized CONNECT packet of size of %lu.", - pBuffer->size, - connectPacketSize ) ); - status = MQTTNoMemory; + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; } else if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) ) { @@ -1400,10 +1457,27 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, } else { - serializeConnectPacket( pConnectInfo, - pWillInfo, - remainingLength, - pBuffer ); + /* Calculate CONNECT packet size. Overflow in in this addition is not checked + * because it is part of the API contract to call Mqtt_GetConnectPacketSize() + * before this function. */ + connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U; + + /* Check that the full packet size fits within the given buffer. */ + if( connectPacketSize > pFixedBuffer->size ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized CONNECT packet of size of %lu.", + pFixedBuffer->size, + connectPacketSize ) ); + status = MQTTNoMemory; + } + else + { + serializeConnectPacket( pConnectInfo, + pWillInfo, + remainingLength, + pFixedBuffer ); + } } return status; @@ -1953,6 +2027,12 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, pIncomingPacket->type ) ); status = MQTTBadParameter; } + else if( pIncomingPacket->pRemainingData == NULL ) + { + LogError( ( "Argument cannot be NULL: " + "pIncomingPacket->pRemainingData is NULL." ) ); + status = MQTTBadParameter; + } else { status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); @@ -1969,7 +2049,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, { MQTTStatus_t status = MQTTSuccess; - if( ( pIncomingPacket == NULL ) ) + if( pIncomingPacket == NULL ) { LogError( ( "pIncomingPacket cannot be NULL." ) ); status = MQTTBadParameter; @@ -2054,7 +2134,9 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu else { /* Read a single byte. */ - bytesReceived = readFunc( networkContext, &( pIncomingPacket->type ), 1U ); + bytesReceived = readFunc( networkContext, + &( pIncomingPacket->type ), + 1U ); } if( bytesReceived == 1 ) @@ -2062,7 +2144,8 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu /* Check validity. */ if( incomingPacketValid( pIncomingPacket->type ) == true ) { - pIncomingPacket->remainingLength = getRemainingLength( readFunc, networkContext ); + pIncomingPacket->remainingLength = getRemainingLength( readFunc, + networkContext ); if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { @@ -2078,6 +2161,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu } else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) ) { + LogError( ( "No data was received from the transport." ) ); status = MQTTNoDataAvailable; } @@ -2085,6 +2169,9 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu * a failure. */ else if( status != MQTTBadParameter ) { + LogError( ( "A single byte was not read from the transport: " + "transportStatus=%d", + bytesReceived ) ); status = MQTTRecvFailed; } else diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 747de6a3e1..c7870d492f 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -424,7 +424,7 @@ void test_MQTT_GetConnectPacketSize( void ) status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); - /* Connect packet too large. */ + /* Test a will message payload length that is too large. */ memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; connectInfo.clientIdentifierLength = UINT16_MAX; @@ -434,6 +434,7 @@ void test_MQTT_GetConnectPacketSize( void ) connectInfo.userNameLength = UINT16_MAX; willInfo.pTopicName = TEST_TOPIC_NAME; willInfo.topicNameLength = UINT16_MAX; + /* A valid will message payload is less than the maximum 16 bit integer. */ willInfo.payloadLength = UINT16_MAX + 2; status = MQTT_GetConnectPacketSize( &connectInfo, &willInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); @@ -502,11 +503,22 @@ void test_MQTT_SerializeConnect( void ) status = MQTT_SerializeConnect( &connectInfo, NULL, 120, &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status ); - /* Good case succeeds */ - /* Calculate packet size. */ + /* Create a good connection info. */ memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); connectInfo.pClientIdentifier = "TEST"; connectInfo.clientIdentifierLength = 4; + + /* Inject a invalid fixed buffer test with a good connectInfo. */ + memset( ( void * ) &fixedBuffer, 0x0, sizeof( fixedBuffer ) ); + status = MQTT_SerializeConnect( &connectInfo, NULL, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Good case succeeds. */ + /* Set the fixedBuffer properly for the rest of the succeeding test. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + fixedBuffer.size = bufferSize; + + /* Calculate a good packet size. */ status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); /* Make sure buffer has enough space */ @@ -1452,6 +1464,8 @@ void test_MQTT_DeserializePublish( void ) const uint16_t PACKET_ID = 1; + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + /* Verify parameters. */ status = MQTT_DeserializePublish( NULL, &packetIdentifier, &publishInfo ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); @@ -1460,7 +1474,9 @@ void test_MQTT_DeserializePublish( void ) status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); - memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + mqttPacketInfo.type = MQTT_PACKET_TYPE_PUBLISH; + status = MQTT_DeserializePublish( &mqttPacketInfo, &packetIdentifier, &publishInfo ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); /* Bad Packet Type. */ mqttPacketInfo.type = 0x01; From 3b9b493c7b4cae2ebe6e28554c1376fa2dd9da38 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 7 Jul 2020 13:13:24 -0700 Subject: [PATCH 571/844] Add HTTP mutual auth demo (#978) * Add demo for mutual auth * Use correct type for private key * Update mutual auth demo based on changes to server auth demo * Create demo config header and transfer certificates there * Fix miscopied macros in demo config header * update docstring for main * Add missing newline * Use files to store keys and print paths * Push certs and mock key * Address PR comments * Change demo to publish messages to AWS IoT Core * Address PR comments * Update mutual auth demo based on latest changes to server auth demo * Update docstrings * Address PR comments * Add support for ALPN * Download root CA cert using curl through CMake * Add link to describe endpoint in docstring * Add missing param docstrings * Remove sigv4 comments * Comment IOT_CORE_ENDPOINT in demo header * Update demos to use common transport interface * Add missing macros in plaintext demo * Test that everything is working as expected * Revert changes not related to mutual auth demo * Remove accidental changes to library * Checkout mqtt.c and mqtt_utest.c in development branch * Address PR comments --- .../http/http_demo_mutual_tls/CMakeLists.txt | 46 +++ demos/http/http_demo_mutual_tls/demo_config.h | 137 +++++++ demos/http/http_demo_mutual_tls/http_config.h | 52 +++ .../http_demo_mutual_tls.c | 351 ++++++++++++++++++ 4 files changed, 586 insertions(+) create mode 100644 demos/http/http_demo_mutual_tls/CMakeLists.txt create mode 100644 demos/http/http_demo_mutual_tls/demo_config.h create mode 100644 demos/http/http_demo_mutual_tls/http_config.h create mode 100644 demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c diff --git a/demos/http/http_demo_mutual_tls/CMakeLists.txt b/demos/http/http_demo_mutual_tls/CMakeLists.txt new file mode 100644 index 0000000000..51fb6cf905 --- /dev/null +++ b/demos/http/http_demo_mutual_tls/CMakeLists.txt @@ -0,0 +1,46 @@ +set( DEMO_NAME "http_demo_mutual_tls" ) +# Demo target. +add_executable(${DEMO_NAME}) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + http + openssl_posix +) + +target_include_directories( + http + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) + +# Download the Amazon Root CA certificate +message( "Downloading the Amazon Root CA certificate..." ) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) +execute_process( + COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem + -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt +) + +# Copy the certificates and client key to the binary directory. +add_custom_command( + TARGET + ${DEMO_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/certificates" + "$/certificates" +) diff --git a/demos/http/http_demo_mutual_tls/demo_config.h b/demos/http/http_demo_mutual_tls/demo_config.h new file mode 100644 index 0000000000..f41b6c928a --- /dev/null +++ b/demos/http/http_demo_mutual_tls/demo_config.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H_ +#define DEMO_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging config definition and header files inclusion are required in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief Your AWS IoT Core endpoint. + * + * @note Your AWS IoT Core endpoint can be found in the AWS IoT console under + * Settings/Custom Endpoint, or using the describe-endpoint API. + * + * #define IOT_CORE_ENDPOINT "your-aws-iot-core-endpoint" + */ + +/** + * @brief AWS IoT Core server port number for HTTPS connections. + * + * For this demo, an X.509 certificate is used to verify the client. + * + * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol + * name being x-amzn-http-ca. When using port 8443, ALPN is not required. + */ +#define IOT_CORE_PORT 443 + +/** + * @brief Path of the file containing Amazon's root CA certificate for TLS + * authentication to AWS IoT Core. + * + * Amazon's root CA certificate is automatically downloaded to the certificates + * directory from @ref https://www.amazontrust.com/repository/AmazonRootCA1.pem + * using the CMake build system. + * + * @note This certificate should be PEM-encoded. + */ +#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" + +/** + * @brief ALPN protocol name to be sent as part of the ClientHello message. + * + * @note When using ALPN, port 443 must be used to connect to AWS IoT Core. + */ +#define IOT_CORE_ALPN_PROTOCOL_NAME "\x0ex-amzn-http-ca" + +/** + * @brief Path of the file containing the client's certificate for TLS + * authentication to AWS IoT Core. + * + * @note This certificate should be PEM-encoded and must have an associated + * policy from AWS IoT core for the demo to function correctly. + * + * #define CLIENT_CERT_PATH "certificates/client.crt" + */ + +/** + * @brief Path of the file containing the client's private key for + * TLS client authentication. + * + * @note This key should be PEM-encoded and must have an associated + * policy from AWS IoT core for the demo to function correctly. + * + * #define CLIENT_PRIVATE_KEY_PATH "certificates/client.key" + */ + +/** + * @brief This endpoint can be used to publish a message to a topic named topic + * on AWS IoT Core. + * + * Each client certificate has an associated policy document that must be + * configured to support the path below for this demo to work correctly. + * + * @note QoS=1 implies the message is delivered to all subscribers of the topic + * at least once. + */ +#define POST_PATH "/topics/topic?qos=1" + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 5000 ) + +/** + * @brief The length in bytes of the user buffer. + */ +#define USER_BUFFER_LENGTH ( 2048 ) + +/** + * @brief Request body to send for PUT and POST requests in this demo. + */ +#define REQUEST_BODY "{ \"message\": \"Hello, world\" }" + +#endif /* ifndef DEMO_CONFIG_H_ */ diff --git a/demos/http/http_demo_mutual_tls/http_config.h b/demos/http/http_demo_mutual_tls/http_config.h new file mode 100644 index 0000000000..d8feed90b3 --- /dev/null +++ b/demos/http/http_demo_mutual_tls/http_config.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef HTTP_CONFIG_H +#define HTTP_CONFIG_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging config definition and header files inclusion are required in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for HTTP. + * 3. Include the header file "logging_stack.h", if logging is enabled for HTTP. + */ + +#include "logging_levels.h" + +/* Logging configuration for the HTTP library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "HTTP" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + + +/************ End of logging configuration ****************/ + +#endif /* ifndef HTTP_CONFIG_H */ diff --git a/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c b/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c new file mode 100644 index 0000000000..73a55e55b7 --- /dev/null +++ b/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Standard includes. */ +#include +#include +#include + +/* HTTP API header. */ +#include "http_client.h" + +/* Demo Config header. */ +#include "demo_config.h" + +/* OpenSSL transport header. */ +#include "openssl_posix.h" + +/* Check that AWS IoT Core endpoint is defined. */ +#ifndef IOT_CORE_ENDPOINT + #error "IOT_CORE_ENDPOINT must be defined to your AWS IoT Core endpoint." +#endif + +/* Check that TLS port used for AWS IoT Core is defined. */ +#ifndef IOT_CORE_PORT + #error "Please define a IOT_CORE_PORT." +#endif + +/* Check that a path for HTTP Method POST is defined. */ +#ifndef POST_PATH + #error "Please define a POST_PATH." +#endif + +/* Check that a path for Root CA Certificate is defined. */ +#ifndef ROOT_CA_CERT_PATH + #error "Please define a ROOT_CA_CERT_PATH." +#endif + +/* Check that a path for Client Certificate is defined. */ +#ifndef CLIENT_CERT_PATH + #error "Please define a CLIENT_CERT_PATH." +#endif + +/* Check that a path for Client's Private Key is defined. */ +#ifndef CLIENT_PRIVATE_KEY_PATH + #error "Please define a CLIENT_PRIVATE_KEY_PATH." +#endif + +/* Check that a request body to send for the POST request is defined. */ +#ifndef REQUEST_BODY + #error "Please define a REQUEST_BODY." +#endif + +/* Check that transport timeout for transport send and receive is defined. */ +#ifndef TRANSPORT_SEND_RECV_TIMEOUT_MS + #define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 1000 ) +#endif + +/* Check that size of the user buffer is defined. */ +#ifndef USER_BUFFER_LENGTH + #define USER_BUFFER_LENGTH ( 2048 ) +#endif + +/** + * @brief ALPN protocol name to be sent as part of the ClientHello message. + * + * @note When using ALPN, port 443 must be used to connect to AWS IoT Core. + */ +#ifndef IOT_CORE_ALPN_PROTOCOL_NAME + #define IOT_CORE_ALPN_PROTOCOL_NAME "\x0ex-amzn-http-ca" +#endif + +/** + * @brief The length of the HTTP server host name. + */ +#define IOT_CORE_ENDPOINT_LENGTH ( sizeof( IOT_CORE_ENDPOINT ) - 1 ) + +/** + * @brief The length of the ALPN protocol name. + */ +#define IOT_CORE_ALPN_PROTOCOL_NAME_LENGTH ( sizeof( IOT_CORE_ALPN_PROTOCOL_NAME ) - 1 ) + +/** + * @brief The length of the HTTP POST method. + */ +#define HTTP_METHOD_POST_LENGTH ( sizeof( HTTP_METHOD_POST ) - 1 ) + +/** + * @brief The length of the HTTP POST path. + */ +#define POST_PATH_LENGTH ( sizeof( POST_PATH ) - 1 ) + +/** + * @brief Length of the request body. + */ +#define REQUEST_BODY_LENGTH ( sizeof( REQUEST_BODY ) - 1 ) + +/** + * @brief A buffer used in the demo for storing HTTP request headers and + * HTTP response headers and body. + * + * @note This demo shows how the same buffer can be re-used for storing the HTTP + * response after the HTTP request is sent out. However, the user can also + * decide to use separate buffers for storing the HTTP request and response. + */ +static uint8_t userBuffer[ USER_BUFFER_LENGTH ]; + +/*-----------------------------------------------------------*/ + +/** + * @brief Send an HTTP request based on a specified method and path, then + * print the response received from the server. + * + * @param[in] pTransportInterface The transport interface for making network calls. + * @param[in] pMethod The HTTP request method. + * @param[in] methodLen The length of the HTTP request method. + * @param[in] pPath The Request-URI to the objects of interest. + * @param[in] pathLen The length of the Request-URI. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int sendHttpRequest( const TransportInterface_t * pTransportInterface, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ); + +/*-----------------------------------------------------------*/ + +static int sendHttpRequest( const TransportInterface_t * pTransportInterface, + const char * pMethod, + size_t methodLen, + const char * pPath, + size_t pathLen ) +{ + /* Return value of this method. */ + int returnStatus = EXIT_SUCCESS; + + /* Configurations of the initial request headers that are passed to + * #HTTPClient_InitializeRequestHeaders. */ + HTTPRequestInfo_t requestInfo; + /* Represents a response returned from an HTTP server. */ + HTTPResponse_t response; + /* Represents header data that will be sent in an HTTP request. */ + HTTPRequestHeaders_t requestHeaders; + + /* Return value of all methods from the HTTP Client library API. */ + HTTPStatus_t httpStatus = HTTP_SUCCESS; + + assert( pMethod != NULL ); + assert( methodLen > 0 ); + + /* Initialize all HTTP Client library API structs to 0. */ + ( void ) memset( &requestInfo, 0, sizeof( requestInfo ) ); + ( void ) memset( &response, 0, sizeof( response ) ); + ( void ) memset( &requestHeaders, 0, sizeof( requestHeaders ) ); + + /* Initialize the request object. */ + requestInfo.pHost = IOT_CORE_ENDPOINT; + requestInfo.hostLen = IOT_CORE_ENDPOINT_LENGTH; + requestInfo.method = pMethod; + requestInfo.methodLen = methodLen; + requestInfo.pPath = pPath; + requestInfo.pathLen = pathLen; + + /* Set "Connection" HTTP header to "keep-alive" so that multiple requests + * can be sent over the same established TCP connection. */ + requestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG; + + /* Set the buffer used for storing request headers. */ + requestHeaders.pBuffer = userBuffer; + requestHeaders.bufferLen = USER_BUFFER_LENGTH; + + httpStatus = HTTPClient_InitializeRequestHeaders( &requestHeaders, + &requestInfo ); + + if( httpStatus == HTTP_SUCCESS ) + { + /* Initialize the response object. The same buffer used for storing + * request headers is reused here. */ + response.pBuffer = userBuffer; + response.bufferLen = USER_BUFFER_LENGTH; + + LogInfo( ( "Sending HTTP %.*s request to %.*s%.*s...", + ( int32_t ) methodLen, pMethod, + ( int32_t ) IOT_CORE_ENDPOINT_LENGTH, IOT_CORE_ENDPOINT, + ( int32_t ) pathLen, pPath ) ); + LogDebug( ( "Request Headers:\n%.*s\n" + "Request Body:\n%.*s\n", + ( int32_t ) requestHeaders.headersLen, + ( char * ) requestHeaders.pBuffer, + ( int32_t ) REQUEST_BODY_LENGTH, REQUEST_BODY ) ); + + /* Send the request and receive the response. */ + httpStatus = HTTPClient_Send( pTransportInterface, + &requestHeaders, + ( uint8_t * ) REQUEST_BODY, + REQUEST_BODY_LENGTH, + &response, + 0 ); + } + else + { + LogError( ( "Failed to initialize HTTP request headers: Error=%s.", + HTTPClient_strerror( httpStatus ) ) ); + } + + if( httpStatus == HTTP_SUCCESS ) + { + LogInfo( ( "Received HTTP response from %.*s%.*s...\n" + "Response Headers:\n%.*s\n" + "Response Status:\n%u\n" + "Response Body:\n%.*s\n", + ( int32_t ) IOT_CORE_ENDPOINT_LENGTH, IOT_CORE_ENDPOINT, + ( int32_t ) pathLen, pPath, + ( int32_t ) response.headersLen, response.pHeaders, + response.statusCode, + ( int32_t ) response.bodyLen, response.pBody ) ); + } + else + { + LogError( ( "Failed to send HTTP %.*s request to %.*s%.*s: Error=%s.", + ( int32_t ) methodLen, pMethod, + ( int32_t ) IOT_CORE_ENDPOINT_LENGTH, IOT_CORE_ENDPOINT, + ( int32_t ) pathLen, pPath, + HTTPClient_strerror( httpStatus ) ) ); + } + + if( httpStatus != HTTP_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * This example resolves the AWS IoT Core endpoint, establishes a TCP connection, + * performs a mutually authenticated TLS handshake occurs such that all further + * communication is encrypted. After which, HTTP Client Library API is used to + * make a POST request to AWS IoT Core in order to publish a message to a topic + * named topic with QoS=1 so that all clients subscribed to the topic receive + * the message at least once. Any possible errors are also logged. + * + * @note This example is single-threaded and uses statically allocated memory. + * + */ +int main( int argc, + char ** argv ) +{ + /* Return value of main. */ + int returnStatus = EXIT_SUCCESS; + /* The transport layer interface used by the HTTP Client library. */ + TransportInterface_t transportInterface; + /* The network context for the transport layer interface. */ + NetworkContext_t networkContext; + /* Credentials to establish the TLS connection. */ + OpensslCredentials_t opensslCredentials; + /* Status returned by OpenSSL transport implementation. */ + OpensslStatus_t opensslStatus; + /* Information about the server to send the HTTP request. */ + ServerInfo_t serverInfo; + + ( void ) argc; + ( void ) argv; + + /* Initialize TLS credentials. */ + ( void ) memset( &opensslCredentials, 0, sizeof( opensslCredentials ) ); + opensslCredentials.pClientCertPath = CLIENT_CERT_PATH; + opensslCredentials.pPrivateKeyPath = CLIENT_PRIVATE_KEY_PATH; + opensslCredentials.pRootCaPath = ROOT_CA_CERT_PATH; + opensslCredentials.pAlpnProtos = IOT_CORE_ALPN_PROTOCOL_NAME; + opensslCredentials.alpnProtosLen = IOT_CORE_ALPN_PROTOCOL_NAME_LENGTH; + + /* Initialize server information. */ + serverInfo.pHostName = IOT_CORE_ENDPOINT; + serverInfo.hostNameLength = IOT_CORE_ENDPOINT_LENGTH; + serverInfo.port = IOT_CORE_PORT; + + /**************************** Connect. ******************************/ + + /* Establish TLS connection on top of TCP connection using OpenSSL. */ + if( returnStatus == EXIT_SUCCESS ) + { + LogInfo( ( "Performing TLS handshake on top of the TCP connection." ) ); + + opensslStatus = Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( opensslStatus != OPENSSL_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + } + + /* Define the transport interface. */ + if( returnStatus == EXIT_SUCCESS ) + { + ( void ) memset( &transportInterface, 0, sizeof( transportInterface ) ); + transportInterface.recv = Openssl_Recv; + transportInterface.send = Openssl_Send; + transportInterface.pNetworkContext = &networkContext; + } + + /*********************** Send HTTPS request. ************************/ + + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = sendHttpRequest( &transportInterface, + HTTP_METHOD_POST, + HTTP_METHOD_POST_LENGTH, + POST_PATH, + POST_PATH_LENGTH ); + } + + /************************** Disconnect. *****************************/ + + /* Close TLS session. */ + opensslStatus = Openssl_Disconnect( &networkContext ); + + if( opensslStatus != OPENSSL_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + + return returnStatus; +} From 4dc5afce8d72cc4c72249cf8d9164ab93ed252e6 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 7 Jul 2020 13:27:11 -0700 Subject: [PATCH 572/844] Add CBMC proof for HTTPClient_AddRangeHeader and HTTPClient_strerror (#1019) * Add CBMC proof for AddRangeHeader * Remove usage of nondet_int * Add missing newliens * Finish proof for _strerror * Update unit tests to include new checks * Add missing newlines * Add missing newline * Refactor HTTPClient_AddRangeHeader for complexity * Add assert for pRequestHeaders * Address PR comments * Remove " --- .../HTTPClient_AddRangeHeader_harness.c | 42 +++++ .../proofs/HTTPClient_AddRangeHeader/Makefile | 19 +++ .../HTTPClient_AddRangeHeader/README.md | 10 ++ .../HTTPClient_AddRangeHeader/cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 + .../HTTPClient_strerror_harness.c | 34 ++++ .../cbmc/proofs/HTTPClient_strerror/Makefile | 17 ++ .../cbmc/proofs/HTTPClient_strerror/README.md | 10 ++ .../HTTPClient_strerror/cbmc-batch.yaml | 2 + .../HTTPClient_strerror/cbmc-viewer.json | 7 + libraries/standard/http/src/http_client.c | 160 +++++++++++------- .../http/src/private/http_client_internal.h | 8 +- .../standard/http/utest/http_send_utest.c | 8 +- libraries/standard/http/utest/http_utest.c | 20 ++- 14 files changed, 273 insertions(+), 73 deletions(-) create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_strerror/HTTPClient_strerror_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-viewer.json diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c new file mode 100644 index 0000000000..4fdc7c1c68 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_AddRangeHeader_harness.c + * @brief Implements the proof harness for HTTPClient_AddRangeHeader function. + */ + +#include "http_client.h" + +#include "http_cbmc_state.h" + +void harness() +{ + HTTPRequestHeaders_t * pRequestHeaders = NULL; + int32_t rangeStartOrlastNbytes; + int32_t rangeEnd; + + /* Initialize and make assumptions for request headers. */ + pRequestHeaders = allocateHttpRequestHeaders( pRequestHeaders ); + __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); + + HTTPClient_AddRangeHeader( pRequestHeaders, rangeStartOrlastNbytes, rangeEnd ); +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile new file mode 100644 index 0000000000..006991c17e --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile @@ -0,0 +1,19 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=HTTPClient_AddRangeHeader_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +# Maximum value of a 32 bit signed integer is 2,147,483,647, which is 10 digits. +UNWINDSET += strncmp.0:5 convertInt32ToAscii.0:11 convertInt32ToAscii.1:11 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md new file mode 100644 index 0000000000..f1d5eee498 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md @@ -0,0 +1,10 @@ +HTTPClient_AddRangeHeader proof +============== + +This directory contains a memory safety proof for HTTPClient_AddRangeHeader. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-viewer.json new file mode 100644 index 0000000000..de4963db90 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "HTTPClient_AddRangeHeader", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/HTTPClient_strerror_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/HTTPClient_strerror_harness.c new file mode 100644 index 0000000000..febc72f3ea --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/HTTPClient_strerror_harness.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_strerror_harness.c + * @brief Implements the proof harness for HTTPClient_strerror function. + */ + +#include "http_client.h" + +void harness() +{ + HTTPStatus_t status; + + HTTPClient_strerror( status ); +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile new file mode 100644 index 0000000000..db6c267882 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=HTTPClient_strerror_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md new file mode 100644 index 0000000000..2ec9645fdf --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md @@ -0,0 +1,10 @@ +HTTPClient_strerror proof +============== + +This directory contains a memory safety proof for HTTPClient_strerror. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-viewer.json new file mode 100644 index 0000000000..68e49339d1 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "HTTPClient_strerror", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 29c52e0846..16a9619557 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -88,6 +88,24 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, const char * pValue, size_t valueLen ); +/** + * @brief Add the byte range request header to the request headers store in + * #HTTPRequestHeaders_t.pBuffer once all the parameters are validated. + * + * @param[in] pRequestHeaders Request header buffer information. + * @param[in] rangeStartOrlastNbytes Represents either the starting byte + * for a range OR the last N number of bytes in the requested file. + * @param[in] rangeEnd The ending range for the requested file. For end of file + * byte in Range Specifications 2. and 3., #HTTP_RANGE_REQUEST_END_OF_FILE + * should be passed. + * + * @return #HTTP_SUCCESS if successful. If there was insufficient memory in the + * application buffer, then #HTTP_INSUFFICIENT_MEMORY is returned. + */ +static HTTPStatus_t addRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, + int32_t rangeStartOrlastNbytes, + int32_t rangeEnd ); + /** * @brief Receive HTTP response from the transport receive interface. * @@ -1192,6 +1210,71 @@ static HTTPStatus_t addHeader( HTTPRequestHeaders_t * pRequestHeaders, /*-----------------------------------------------------------*/ +static HTTPStatus_t addRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, + int32_t rangeStartOrlastNbytes, + int32_t rangeEnd ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + char rangeValueBuffer[ HTTP_MAX_RANGE_REQUEST_VALUE_LEN ]; + size_t rangeValueLength = 0u; + + assert( pRequestHeaders != NULL ); + + /* This buffer uses a char type instead of the general purpose uint8_t because + * the range value expected to be written is within the ASCII character set. */ + ( void ) memset( rangeValueBuffer, '\0', HTTP_MAX_RANGE_REQUEST_VALUE_LEN ); + + /* Generate the value data for the Range Request header.*/ + + /* Write the range value prefix in the buffer. */ + ( void ) memcpy( rangeValueBuffer, + HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX, + HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ); + rangeValueLength += HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN; + + /* Write the range start value in the buffer. */ + rangeValueLength += convertInt32ToAscii( rangeStartOrlastNbytes, + rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength ); + + /* Add remaining value data depending on the range specification type. */ + + /* Add rangeEnd value if request is for [rangeStart, rangeEnd] byte range */ + if( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) + { + /* Write the "-" character to the buffer.*/ + *( rangeValueBuffer + rangeValueLength ) = DASH_CHARACTER; + rangeValueLength += DASH_CHARACTER_LEN; + + /* Write the rangeEnd value of the request range to the buffer .*/ + rangeValueLength += convertInt32ToAscii( rangeEnd, + rangeValueBuffer + rangeValueLength, + sizeof( rangeValueBuffer ) - rangeValueLength ); + } + /* Case when request is for bytes in the range [rangeStart, EoF). */ + else if( rangeStartOrlastNbytes >= 0 ) + { + /* Write the "-" character to the buffer.*/ + *( rangeValueBuffer + rangeValueLength ) = DASH_CHARACTER; + rangeValueLength += DASH_CHARACTER_LEN; + } + else + { + /* Empty else MISRA 15.7 */ + } + + /* Add the Range Request header field and value to the buffer. */ + returnStatus = addHeader( pRequestHeaders, + HTTP_RANGE_REQUEST_HEADER_FIELD, + HTTP_RANGE_REQUEST_HEADER_FIELD_LEN, + rangeValueBuffer, + rangeValueLength ); + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, const char * pMethod, size_t methodLen, @@ -1224,8 +1307,7 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, ( void ) memcpy( pBufferCur, pMethod, methodLen ); pBufferCur += methodLen; - ( void ) memcpy( pBufferCur, SPACE_CHARACTER, SPACE_CHARACTER_LEN ); - + *pBufferCur = SPACE_CHARACTER; pBufferCur += SPACE_CHARACTER_LEN; /* Use "/" as default value if is NULL. */ @@ -1242,9 +1324,7 @@ static HTTPStatus_t writeRequestLine( HTTPRequestHeaders_t * pRequestHeaders, pBufferCur += pathLen; } - ( void ) memcpy( pBufferCur, - SPACE_CHARACTER, - SPACE_CHARACTER_LEN ); + *pBufferCur = SPACE_CHARACTER; pBufferCur += SPACE_CHARACTER_LEN; ( void ) memcpy( pBufferCur, @@ -1426,11 +1506,6 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, { HTTPStatus_t returnStatus = HTTP_SUCCESS; - /* This buffer uses a char type instead of the general purpose uint8_t because - * the range value expected to be written is within the ASCII character set. */ - char rangeValueBuffer[ HTTP_MAX_RANGE_REQUEST_VALUE_LEN ] = { '\0' }; - size_t rangeValueLength = 0u; - if( pRequestHeaders == NULL ) { LogError( ( "Parameter check failed: pRequestHeaders is NULL." ) ); @@ -1441,6 +1516,11 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( pRequestHeaders->headersLen > pRequestHeaders->bufferLen ) + { + LogError( ( "Parameter check failed: pRequestHeaders->headersLen > pRequestHeaders->bufferLen." ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else if( rangeEnd < HTTP_RANGE_REQUEST_END_OF_FILE ) { LogError( ( "Parameter check failed: rangeEnd is invalid: " @@ -1465,57 +1545,19 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, rangeStartOrlastNbytes, rangeEnd ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( rangeStartOrlastNbytes == INT32_MIN ) + { + LogError( ( "Parameter check failed: Arithmetic overflow detected: " + "rangeStart should be > -2147483648 (INT32_MIN): ", + "RangeStart=%d", + rangeStartOrlastNbytes ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else { - /* Generate the value data for the Range Request header.*/ - - /* Write the range value prefix in the buffer. */ - ( void ) memcpy( rangeValueBuffer, - HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX, - HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN ); - rangeValueLength += HTTP_RANGE_REQUEST_HEADER_VALUE_PREFIX_LEN; - - /* Write the range start value in the buffer. */ - rangeValueLength += convertInt32ToAscii( rangeStartOrlastNbytes, - rangeValueBuffer + rangeValueLength, - sizeof( rangeValueBuffer ) - rangeValueLength ); - - /* Add remaining value data depending on the range specification type. */ - - /* Add rangeEnd value if request is for [rangeStart, rangeEnd] byte range */ - if( rangeEnd != HTTP_RANGE_REQUEST_END_OF_FILE ) - { - /* Write the "-" character to the buffer.*/ - ( void ) memcpy( rangeValueBuffer + rangeValueLength, - DASH_CHARACTER, - DASH_CHARACTER_LEN ); - rangeValueLength += DASH_CHARACTER_LEN; - - /* Write the rangeEnd value of the request range to the buffer .*/ - rangeValueLength += convertInt32ToAscii( rangeEnd, - rangeValueBuffer + rangeValueLength, - sizeof( rangeValueBuffer ) - rangeValueLength ); - } - /* Case when request is for bytes in the range [rangeStart, EoF). */ - else if( rangeStartOrlastNbytes >= 0 ) - { - /* Write the "-" character to the buffer.*/ - ( void ) memcpy( rangeValueBuffer + rangeValueLength, - DASH_CHARACTER, - DASH_CHARACTER_LEN ); - rangeValueLength += DASH_CHARACTER_LEN; - } - else - { - /* Empty else MISRA 15.7 */ - } - - /* Add the Range Request header field and value to the buffer. */ - returnStatus = addHeader( pRequestHeaders, - HTTP_RANGE_REQUEST_HEADER_FIELD, - HTTP_RANGE_REQUEST_HEADER_FIELD_LEN, - rangeValueBuffer, - rangeValueLength ); + returnStatus = addRangeHeader( pRequestHeaders, + rangeStartOrlastNbytes, + rangeEnd ); } return returnStatus; diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index f6f5d5386b..df8fd48a79 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -42,10 +42,10 @@ #define HTTP_HEADER_END_INDICATOR_LEN ( sizeof( HTTP_HEADER_END_INDICATOR ) - 1u ) #define HTTP_HEADER_FIELD_SEPARATOR ": " #define HTTP_HEADER_FIELD_SEPARATOR_LEN ( sizeof( HTTP_HEADER_FIELD_SEPARATOR ) - 1u ) -#define SPACE_CHARACTER " " -#define SPACE_CHARACTER_LEN ( sizeof( SPACE_CHARACTER ) - 1u ) -#define DASH_CHARACTER "-" -#define DASH_CHARACTER_LEN ( sizeof( DASH_CHARACTER ) - 1u ) +#define SPACE_CHARACTER ' ' +#define SPACE_CHARACTER_LEN ( 1u ) +#define DASH_CHARACTER '-' +#define DASH_CHARACTER_LEN ( 1u ) /** * @brief Constants for header fields added automatically during the request diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index ffe7397d24..ca541e1b97 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -406,11 +406,11 @@ static void helper_parse_status_line( const char ** pNext, /* For purposes of unit testing the response is well formed in the non-error * cases, so the reason-phrase is always after HTTP/1.1 and the three digit - * status code. strstr() is used only for unit testing where test input are \ - * always string literals. strstr() should not be used in application code. */ - *pNext = strstr( *pNext, SPACE_CHARACTER ); /* Get the space before the status-code. */ + * status code. strchr() is used only for unit testing where test input are + * always string literals. strchr() should not be used in application code. */ + *pNext = strchr( *pNext, SPACE_CHARACTER ); /* Get the space before the status-code. */ *pNext += SPACE_CHARACTER_LEN; - *pNext = strstr( *pNext, SPACE_CHARACTER ); /* Get the space before the reason-phrase. */ + *pNext = strchr( *pNext, SPACE_CHARACTER ); /* Get the space before the reason-phrase. */ *pNext += SPACE_CHARACTER_LEN; pReasonPhraseStart = *pNext; *pNext = strstr( *pNext, HTTP_HEADER_LINE_SEPARATOR ); diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 05ba2fe69a..2c2703ba58 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -795,14 +795,14 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) tearDown(); retCode = HTTPClient_AddRangeHeader( NULL, 0 /* rangeStart */, - 0 /* rageEnd */ ); + 0 /* rangeEnd */ ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Underlying buffer is NULL in request headers. */ tearDown(); retCode = HTTPClient_AddRangeHeader( &testHeaders, 0 /* rangeStart */, - 0 /* rageEnd */ ); + 0 /* rangeEnd */ ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Request Header Size is zero. */ @@ -812,7 +812,7 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) testHeaders.bufferLen = 0u; retCode = HTTPClient_AddRangeHeader( &testHeaders, 0 /* rangeStart */, - 10 /* rageEnd */ ); + 10 /* rangeEnd */ ); TEST_ASSERT_EQUAL( retCode, HTTP_INSUFFICIENT_MEMORY ); /* Test incorrect combinations of rangeStart and rangeEnd. */ @@ -822,7 +822,15 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) testHeaders.pBuffer = &testBuffer[ 0 ]; retCode = HTTPClient_AddRangeHeader( &testHeaders, 10 /* rangeStart */, - 5 /* rageEnd */ ); + 5 /* rangeEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); + + /* rangeStart == INT32_MIN */ + tearDown(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + INT32_MIN /* rangeStart */, + 5 /* rangeEnd */ ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* rangeStart is negative but rangeStart is non-End of File. */ @@ -830,13 +838,13 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) testHeaders.pBuffer = &testBuffer[ 0 ]; retCode = HTTPClient_AddRangeHeader( &testHeaders, -10 /* rangeStart */, - HTTP_RANGE_REQUEST_END_OF_FILE + 1 /* rageEnd */ ); + HTTP_RANGE_REQUEST_END_OF_FILE + 1 /* rangeEnd */ ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); tearDown(); testHeaders.pBuffer = &testBuffer[ 0 ]; retCode = HTTPClient_AddRangeHeader( &testHeaders, -50 /* rangeStart */, - -10 /* rageEnd */ ); + -10 /* rangeEnd */ ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); } From 796af268d6225c69ab9ab87fc52998b4ff5bc6e8 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 7 Jul 2020 19:25:58 -0700 Subject: [PATCH 573/844] Add CBMC proof for HTTPClient_ReadHeader (#1022) * Add ReadHeader harness * Add allocate and verify functions for response * Add docstrings for new http cbmc state functions * Change logs to use unsigned format specifier to resolve cbmc errors * Address PR comments * Change 0 -> false * Create http_config.h in includes directory * Create http_config.h in includes directory * Remove assertion that pResponse->pHeaderParsingCallback == NULL --- .../http/cbmc/include/http_cbmc_state.h | 46 +++++++++++---- .../standard/http/cbmc/include/http_config.h | 51 +++++++++++++++++ .../HTTPClient_ReadHeader_harness.c | 56 +++++++++++++++++++ .../proofs/HTTPClient_ReadHeader/Makefile | 19 +++++++ .../proofs/HTTPClient_ReadHeader/README.md | 10 ++++ .../HTTPClient_ReadHeader/cbmc-batch.yaml | 2 + .../HTTPClient_ReadHeader/cbmc-viewer.json | 7 +++ .../http/cbmc/proofs/Makefile-project-defines | 1 - .../http/cbmc/sources/http_cbmc_state.c | 31 ++++++++++ libraries/standard/http/src/http_client.c | 18 +++--- 10 files changed, 221 insertions(+), 20 deletions(-) create mode 100644 libraries/standard/http/cbmc/include/http_config.h create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-viewer.json diff --git a/libraries/standard/http/cbmc/include/http_cbmc_state.h b/libraries/standard/http/cbmc/include/http_cbmc_state.h index f07377001d..3b141ae672 100644 --- a/libraries/standard/http/cbmc/include/http_cbmc_state.h +++ b/libraries/standard/http/cbmc/include/http_cbmc_state.h @@ -34,37 +34,63 @@ * It is possible for malloc(0) to return an address without allocating memory. * * @param[in] size Requested size to malloc. + * + * @return Requested memory or NULL. */ void * mallocCanFail( size_t size ); /** - * @brief Allocate a request headers object. + * @brief Allocates an #HTTPRequestHeaders_t object. + * + * @param[in] pRequestHeaders #HTTPRequestHeaders_t object to allocate. * - * @param[in] pRequestHeaders Request headers object to allocate. + * @return NULL or pointer to allocated #HTTPRequestHeaders_t object. */ HTTPRequestHeaders_t * allocateHttpRequestHeaders( HTTPRequestHeaders_t * pRequestHeaders ); /** - * @brief Validates if a request headers object is feasible. + * @brief Validates if a #HTTPRequestHeaders_t object is feasible. * - * @param[in] pRequestHeaders Request headers object to validate. + * @param[in] pRequestHeaders #HTTPRequestHeaders_t object to validate. * - * @return true if request headers is feasible; false otherwise. + * @return True if #pRequestHeaders is feasible; false otherwise. */ bool isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ); /** - * @brief Allocate a request info object. + * @brief Allocates a #HTTPRequestInfo_t object. + * + * @param[in] pRequestInfo #HTTPRequestInfo_t object to allocate. + * + * @return NULL or pointer to allocated #HTTPRequestInfo_t object. */ -HTTPRequestInfo_t * allocateHttpRequestInfo(); +HTTPRequestInfo_t * allocateHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ); /** - * @brief Validates if a request info object is feasible. + * @brief Validates if a #HTTPRequestInfo_t object is feasible. * - * @param[in] pRequestInfo Request info object to validate. + * @param[in] pRequestInfo #HTTPRequestInfo_t object to validate. * - * @return true if request headers is feasible; 0 otherwise. + * @return True if #pRequestInfo is feasible; false otherwise. */ bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ); +/** + * @brief Allocates a #HTTPResponse_t object with unconstrained values. + * + * @param[in] pResponse #HTTPResponse_t object to allocate. + * + * @return NULL or pointer to allocated #HTTPResponse_t object. + */ +HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ); + +/** + * @brief Validates if a #HTTPResponse_t object is feasible. + * + * @param[in] pResponse #HTTPResponse_t object to validate. + * + * @return True if #HTTPResponse_t is feasible; false otherwise. + */ +bool isValidHttpResponse( const HTTPResponse_t * pResponse ); + #endif /* ifndef HTTP_CBMC_STATE_H_ */ diff --git a/libraries/standard/http/cbmc/include/http_config.h b/libraries/standard/http/cbmc/include/http_config.h new file mode 100644 index 0000000000..05f36ba881 --- /dev/null +++ b/libraries/standard/http/cbmc/include/http_config.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef HTTP_CONFIG_H +#define HTTP_CONFIG_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for HTTP. + * 3. Include the header file "logging_stack.h", if logging is enabled for HTTP. + */ + +#include "logging_levels.h" + +/* Logging configuration for the HTTP library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "HTTP" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_NONE +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef HTTP_CONFIG_H */ diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c new file mode 100644 index 0000000000..b7d6e6c32b --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_ReadHeader_harness.c + * @brief Implements the proof harness for HTTPClient_ReadHeader function. + */ + +#include "http_client.h" + +#include "http_cbmc_state.h" + +void harness() +{ + HTTPResponse_t * pResponse = NULL; + char * pField = NULL; + char * pValue = NULL; + size_t fieldLen; + size_t valueLen; + + /* Initialize and make assumptions for header field. */ + __CPROVER_assume( fieldLen < CBMC_MAX_OBJECT_SIZE ); + pField = mallocCanFail( fieldLen ); + + /* Initialize and make assumptions for header value. */ + __CPROVER_assume( valueLen < CBMC_MAX_OBJECT_SIZE ); + pValue = mallocCanFail( valueLen ); + + /* Initialize and make assumptions for response object. */ + pResponse = allocateHttpResponse( pResponse ); + __CPROVER_assume( isValidHttpResponse( pResponse ) ); + + HTTPClient_ReadHeader( pResponse, + pField, + fieldLen, + pValue, + valueLen ); +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile new file mode 100644 index 0000000000..6cddbd3e49 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile @@ -0,0 +1,19 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=HTTPClient_ReadHeader_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md new file mode 100644 index 0000000000..e2b1dc1a14 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md @@ -0,0 +1,10 @@ +HTTPClient_ReadHeader proof +============== + +This directory contains a memory safety proof for HTTPClient_ReadHeader. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-viewer.json new file mode 100644 index 0000000000..1999588c80 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "HTTPClient_ReadHeader", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-defines b/libraries/standard/http/cbmc/proofs/Makefile-project-defines index a1b0b53bce..c957ee85b9 100644 --- a/libraries/standard/http/cbmc/proofs/Makefile-project-defines +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-defines @@ -23,7 +23,6 @@ INCLUDES += -I$(SRCDIR)/libraries/standard/http/include INCLUDES += -I$(SRCDIR)/libraries/standard/http/src INCLUDES += -I$(SRCDIR)/libraries/standard/http/third_party/http_parser INCLUDES += -I$(SRCDIR)/demos/logging-stack -INCLUDES += -I$(SRCDIR)/demos/http/http_demo_plaintext INCLUDES += -I$(SRCDIR)/platform/include # Preprocessor definitions -D... diff --git a/libraries/standard/http/cbmc/sources/http_cbmc_state.c b/libraries/standard/http/cbmc/sources/http_cbmc_state.c index 93b52bfe1c..010cd26688 100644 --- a/libraries/standard/http/cbmc/sources/http_cbmc_state.c +++ b/libraries/standard/http/cbmc/sources/http_cbmc_state.c @@ -73,3 +73,34 @@ bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ) return isValid; } + +HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ) +{ + if( pResponse == NULL ) + { + pResponse = mallocCanFail( sizeof( HTTPResponse_t ) ); + } + + if( pResponse != NULL ) + { + __CPROVER_assume( pResponse->bufferLen < CBMC_MAX_OBJECT_SIZE ); + pResponse->pBuffer = mallocCanFail( pResponse->bufferLen ); + __CPROVER_assume( pResponse->bodyLen < CBMC_MAX_OBJECT_SIZE ); + pResponse->pBody = mallocCanFail( pResponse->bodyLen ); + } + + return pResponse; +} + +bool isValidHttpResponse( const HTTPResponse_t * pResponse ) +{ + bool isValid = true; + + if( pResponse ) + { + isValid = pResponse->bufferLen < CBMC_MAX_OBJECT_SIZE && + pResponse->bodyLen < CBMC_MAX_OBJECT_SIZE; + } + + return isValid; +} diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 16a9619557..55896c165b 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -2099,8 +2099,8 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) /* If we have reached here, all headers in the response have been parsed but the requested * header has not been found in the response buffer. */ LogDebug( ( "Reached end of header parsing: Header not found in response: " - "RequestedHeader=%.*s", - ( int ) ( pContext->fieldLen ), + "RequestedHeader=%u*s", + ( pContext->fieldLen ), pContext->pField ) ); /* No further parsing is required; thus, indicate the parser to stop parsing. */ @@ -2161,8 +2161,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, /* Header is not present in buffer. */ LogWarn( ( "Header not found in response buffer: " - "RequestedHeader=%.*s", - ( int ) fieldLen, + "RequestedHeader=%u*s", + fieldLen, pField ) ); returnStatus = HTTP_HEADER_NOT_FOUND; @@ -2173,8 +2173,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, * in the ": \r\n" format of an HTTP header. */ LogError( ( "Unable to find header value in response: " "Response data is invalid: " - "RequestedHeader=%.*s, ParserError=%s", - ( int ) fieldLen, + "RequestedHeader=%u*s, ParserError=%s", + fieldLen, pField, http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; @@ -2188,10 +2188,10 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, assert( ( context.fieldFound == 1u ) && ( context.valueFound == 1u ) ); LogDebug( ( "Found requested header in response: " - "HeaderName=%.*s, HeaderValue=%.*s", - ( int ) fieldLen, + "HeaderName=%u*s, HeaderValue=%u*s", + fieldLen, pField, - ( int ) ( *pValueLen ), + *pValueLen, *pValueLoc ) ); } From 79840ae7823700d21f03cf57afaa953e64f88c0a Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Wed, 8 Jul 2020 21:17:21 -0700 Subject: [PATCH 574/844] CBMC Proofs for MQTT_SerializeConnect and MQTT_DeserializePublish (#1039) --- .gitignore | 1 + .../mqtt/cbmc/include/mqtt_cbmc_state.h | 82 +++++++++++ .../standard/mqtt/cbmc/include/mqtt_config.h | 44 ++++++ .../cbmc/include/network_interface_stubs.h | 4 + .../MQTT_DeserializeAck_harness.c | 41 ++++++ .../cbmc/proofs/MQTT_DeserializeAck/Makefile | 17 +++ .../cbmc/proofs/MQTT_DeserializeAck/README.md | 10 ++ .../MQTT_DeserializeAck/cbmc-batch.yaml | 2 + .../MQTT_DeserializeAck/cbmc-viewer.json | 7 + .../MQTT_DeserializePublish_harness.c | 45 +++++++ .../proofs/MQTT_DeserializePublish/Makefile | 17 +++ .../proofs/MQTT_DeserializePublish/README.md | 10 ++ .../MQTT_DeserializePublish/cbmc-batch.yaml | 2 + .../MQTT_DeserializePublish/cbmc-viewer.json | 7 + ...T_GetIncomingPacketTypeAndLength_harness.c | 9 +- .../Makefile | 2 +- .../cbmc/proofs/MQTT_GetPacketId/Makefile | 2 +- .../cbmc/proofs/MQTT_Init/MQTT_Init_harness.c | 22 +-- .../mqtt/cbmc/proofs/MQTT_Init/Makefile | 1 + .../MQTT_SerializeConnect_harness.c | 62 +++++++++ .../proofs/MQTT_SerializeConnect/Makefile | 17 +++ .../proofs/MQTT_SerializeConnect/README.md | 10 ++ .../MQTT_SerializeConnect/cbmc-batch.yaml | 2 + .../MQTT_SerializeConnect/cbmc-viewer.json | 7 + .../mqtt/cbmc/proofs/Makefile-project-defines | 2 - .../mqtt/cbmc/sources/mqtt_cbmc_state.c | 127 +++++++++++++++++- .../mqtt/cbmc/stubs/network_interface_stubs.c | 3 - tools/aws-templates-for-cbmc-proofs | 2 +- 28 files changed, 536 insertions(+), 21 deletions(-) create mode 100644 libraries/standard/mqtt/cbmc/include/mqtt_config.h create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-viewer.json diff --git a/.gitignore b/.gitignore index f907ea4b43..490153eb5c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ build/ **/cbmc/proofs/**/html **/cbmc/proofs/**/logs **/cbmc/proofs/**/TAGS-* +**/cbmc/proofs/**/report diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h index 09d2f00289..ed11579cf9 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h @@ -18,11 +18,93 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifndef MQTT_CBMC_STATE_H_ +#define MQTT_CBMC_STATE_H_ + +#include + +/* For MQTT Client library types. */ +#include "mqtt.h" /** * @brief Proof model for malloc that can fail and return NULL. * * @param[in] size The size in bytes of memory to allocate. + * * @return NULL or requested memory. */ void * mallocCanFail( size_t size ); + +/** + * @brief Allocate a #MQTTPacketInfo_t object. + * + * @param[in] pPacketInfo #MQTTPacketInfo_t object information. + * + * @return NULL or allocated #MQTTPacketInfo_t memory. + */ +MQTTPacketInfo_t * allocateMqttPacketInfo( MQTTPacketInfo_t * pPacketInfo ); + +/** + * @brief Validate a #MQTTPacketInfo_t object. + * + * @param[in] pPacketInfo #MQTTPacketInfo_t object to validate. + * + * @return True if the #MQTTPacketInfo_t object is valid, false otherwise. + */ +bool isValidMqttPacketInfo( const MQTTPacketInfo_t * pPacketInfo ); + +/** + * @brief Allocate a #MQTTPublishInfo_t object. + * + * @param[in] pPublishInfo #MQTTPublishInfo_t object information. + * + * @return NULL or allocated #MQTTPublishInfo_t memory. + */ +MQTTPublishInfo_t * allocateMqttPublishInfo( MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Validate a #MQTTPublishInfo_t object. + * + * @param[in] pPublishInfo #MQTTPublishInfo_t object to validate. + * + * @return True if the #MQTTPublishInfo_t object is valid, false otherwise. + */ +bool isValidMqttPublishInfo( const MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Allocate a #MQTTConnectInfo_t object. + * + * @param[in] pConnectInfo #MQTTConnectInfo_t object information. + * + * @return NULL or allocated #MQTTConnectInfo_t memory. + */ +MQTTConnectInfo_t * allocateMqttConnectInfo( MQTTConnectInfo_t * pConnectInfo ); + +/** + * @brief Validate a #MQTTConnectInfo_t object. + * + * @param[in] pConnectInfo #MQTTConnectInfo_t object to validate. + * + * @return True if the #MQTTConnectInfo_t object is valid, false otherwise. + */ +bool isValidMqttConnectInfo( const MQTTConnectInfo_t * pConnectInfo ); + +/** + * @brief Allocate a #MQTTFixedBuffer_t object. + * + * @param[in] pBuffer #MQTTFixedBuffer_t object information. + * + * @return NULL or allocated #MQTTFixedBuffer_t memory. + */ +MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pBuffer ); + +/** + * @brief Validate a #MQTTFixedBuffer_t object. + * + * @param[in] pBuffer #MQTTFixedBuffer_t object to validate. + * + * @return True if the #MQTTFixedBuffer_t object is valid, false otherwise. + */ +bool isValidMqttFixedBuffer( const MQTTFixedBuffer_t * pBuffer ); + +#endif /* ifndef MQTT_CBMC_STATE_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_config.h b/libraries/standard/mqtt/cbmc/include/mqtt_config.h new file mode 100644 index 0000000000..050479dc32 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/include/mqtt_config.h @@ -0,0 +1,44 @@ +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the MQTT library. */ +#define LIBRARY_LOG_NAME "MQTT" +#define LIBRARY_LOG_LEVEL LOG_NONE + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Set network context to a socket (int). This is a stub and passed through to + * the application defined transport send and receive. */ +typedef int NetworkContext_t; + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + * + * @note This definition must exist in order to compile. 10U is a typical value + * used in the MQTT demos. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) + +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h index e7428989b5..be1cbf36f3 100644 --- a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h +++ b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h @@ -18,6 +18,8 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifndef NETWORK_INTERFACE_STUBS_H_ +#define NETWORK_INTERfACE_STUBS_H_ /** * @brief Application defined network interface receive function. @@ -30,3 +32,5 @@ int32_t NetworkInterfaceReceiveStub( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ); + +#endif diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c new file mode 100644 index 0000000000..e13fdb3cba --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_DeserializeAck_harness.c + * @brief Implements the proof harness for MQTT_DeserializeAck function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTPacketInfo_t * pIncomingPacket = NULL; + uint16_t * pPacketId; + bool * pSessionPresent; + + pIncomingPacket = allocateMqttPacketInfo( pIncomingPacket ); + __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); + + MQTT_DeserializeAck( pIncomingPacket, + pPacketId, + pSessionPresent ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile new file mode 100644 index 0000000000..0a2dda633d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_DeserializeAck_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md new file mode 100644 index 0000000000..9bf14d42cd --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md @@ -0,0 +1,10 @@ +MQTT_DeserializeAck proof +============== + +This directory contains a memory safety proof for MQTT_DeserializeAck. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-viewer.json new file mode 100644 index 0000000000..2b5117a612 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_DeserializeAck", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c new file mode 100644 index 0000000000..341ce34864 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_DeserializePublish_harness.c + * @brief Implements the proof harness for MQTT_DeserializePublish function. + */ + +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTPacketInfo_t * pIncomingPacket = NULL; + MQTTPublishInfo_t * pPublishInfo = NULL; + uint16_t packetId; + + pIncomingPacket = allocateMqttPacketInfo( pIncomingPacket ); + __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); + + pPublishInfo = allocateMqttPublishInfo( pPublishInfo ); + __CPROVER_assume( isValidMqttPublishInfo( pPublishInfo ) ); + + /* This function grabs the topic name, the topic name length, the + * the payload, and the payload length. */ + MQTT_DeserializePublish( pIncomingPacket, packetId, pPublishInfo ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile new file mode 100644 index 0000000000..f2494070d8 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_DeserializePublish_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md new file mode 100644 index 0000000000..624d15ac3c --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md @@ -0,0 +1,10 @@ +MQTT_DeserializePublish proof +============== + +This directory contains a memory safety proof for MQTT_DeserializePublish. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-viewer.json new file mode 100644 index 0000000000..0c9f5752fb --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_DeserializePublish", + "proof-root": "libraries/standard/mqtt/cbmc" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c index 88ace53c42..5067669267 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c @@ -34,13 +34,14 @@ void harness() * MQTT_GetIncomingPacketTypeAndLength(). */ NetworkContext_t networkContext; - __CPROVER_assume( networkContext != NULL ); - /* MQTT_GetIncomingPacketTypeAndLength() will set only the remainingLength * field in the input MQTTPacketInfo_t structure. */ - MQTTPacketInfo_t * pIncomingPacketInfo = mallocCanFail( sizeof( MQTTPacketInfo_t ) ); + MQTTPacketInfo_t * pIncomingPacket = NULL; + + pIncomingPacket = allocateMqttPacketInfo( pIncomingPacket ); + __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); MQTT_GetIncomingPacketTypeAndLength( NetworkInterfaceReceiveStub, networkContext, - pIncomingPacketInfo ); + pIncomingPacket ); } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile index 2a9425b049..0d1e176b22 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile @@ -2,7 +2,7 @@ HARNESS_ENTRY=harness HARNESS_FILE=MQTT_GetIncomingPacketTypeAndLength_harness DEFINES += -INCLUDES += $(PROOFDIR)/../../include +INCLUDES += REMOVE_FUNCTION_BODY += UNWINDSET += getRemainingLength.0:5 diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile index 274540f23c..ad741b3dc0 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile @@ -2,7 +2,7 @@ HARNESS_ENTRY=harness HARNESS_FILE=MQTT_GetPacketId_harness DEFINES += -INCLUDES += $(PROOFDIR)/../../include +INCLUDES += REMOVE_FUNCTION_BODY += UNWINDSET += diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c index 11f2b3c7a9..c424d1a60a 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c @@ -25,16 +25,22 @@ */ #include "mqtt.h" +#include "mqtt_cbmc_state.h" void harness() { - MQTTContext_t context; - MQTTTransportInterface_t transportInterface; - MQTTApplicationCallbacks_t callbacks; - MQTTFixedBuffer_t networkBuffer; + MQTTContext_t * pContext = NULL; + MQTTTransportInterface_t * pTransportInterface = NULL; + MQTTApplicationCallbacks_t * pCallbacks = NULL; + MQTTFixedBuffer_t * pNetworkBuffer = NULL; - MQTT_Init( nondet_bool() ? NULL : &context, - nondet_bool() ? NULL : &transportInterface, - nondet_bool() ? NULL : &callbacks, - nondet_bool() ? NULL : &networkBuffer ); + pContext = mallocCanFail( sizeof( MQTTContext_t ) ); + pTransportInterface = mallocCanFail( sizeof( MQTTContext_t ) ); + pCallbacks = mallocCanFail( sizeof( MQTTApplicationCallbacks_t ) ); + pNetworkBuffer = mallocCanFail( sizeof( MQTTFixedBuffer_t ) ); + + MQTT_Init( pContext, + pTransportInterface, + pCallbacks, + pNetworkBuffer ); } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile index 5ca3da21f4..60fe4da049 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile @@ -8,6 +8,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c new file mode 100644 index 0000000000..a34b3ea36b --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializeConnect_harness.c + * @brief Implements the proof harness for MQTT_SerializeConnect function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTConnectInfo_t * pConnectInfo = NULL; + MQTTPublishInfo_t * pWillInfo = NULL; + size_t remainingLength = 0; + MQTTFixedBuffer_t * pBuffer = NULL; + size_t packetSize = 0; + MQTTStatus_t status = MQTTSuccess; + + pConnectInfo = allocateMqttConnectInfo( pConnectInfo ); + __CPROVER_assume( isValidMqttConnectInfo( pConnectInfo ) ); + + pWillInfo = allocateMqttPublishInfo( pWillInfo ); + __CPROVER_assume( isValidMqttPublishInfo( pWillInfo ) ); + + pBuffer = allocateMqttFixedBuffer( pBuffer ); + __CPROVER_assume( isValidMqttFixedBuffer( pBuffer ) ); + + /* Before calling MQTT_SerializeConnect() it is up to the application to make + * sure that the information in MQTTConnectInfo_t and MQTTPublishInfo_t can + * fit into the MQTTFixedBuffer_t. It is a violation of the API to call + * MQTT_SerializeConnect without first calling MQTT_GetConnectPacketSize(). */ + if( pConnectInfo != NULL ) + { + status = MQTT_GetConnectPacketSize( pConnectInfo, pWillInfo, &remainingLength, &packetSize ); + } + + if( status == MQTTSuccess ) + { + /* For coverage, it is expected that a NULL pConnectInfo will reach this + * function. */ + MQTT_SerializeConnect( pConnectInfo, pWillInfo, remainingLength, pBuffer ); + } +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile new file mode 100644 index 0000000000..931f40fa78 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializeConnect_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += encodeRemainingLength.0:3 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md new file mode 100644 index 0000000000..7536a0ea31 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md @@ -0,0 +1,10 @@ +MQTT_SerializeConnect proof +============== + +This directory contains a memory safety proof for MQTT_SerializeConnect. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-viewer.json new file mode 100644 index 0000000000..57f66a9dcf --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializeConnect", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines index c3c0088a1d..e79676922a 100644 --- a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines @@ -20,10 +20,8 @@ LINK_FLAGS = # Preprocessor include paths -I... INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/cbmc/include INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/include -INCLUDES += -I$(SRCDIR)/libraries/standard/utilities/include INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/src INCLUDES += -I$(SRCDIR)/demos/logging-stack -INCLUDES += -I$(SRCDIR)/demos/mqtt/mqtt_demo_plaintext # Preprocessor definitions -D... DEFINES += -Dmqtt_EXPORTS diff --git a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c index 80589bb759..37e0f6c7b0 100644 --- a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +++ b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c @@ -18,7 +18,6 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include #include #include #include "mqtt_cbmc_state.h" @@ -28,3 +27,129 @@ void * mallocCanFail( size_t size ) __CPROVER_assert( size < CBMC_MAX_OBJECT_SIZE, "mallocCanFail size is too big" ); return nondet_bool() ? NULL : malloc( size ); } + +MQTTPacketInfo_t * allocateMqttPacketInfo( MQTTPacketInfo_t * pPacketInfo ) +{ + if( pPacketInfo == NULL ) + { + pPacketInfo = mallocCanFail( sizeof( MQTTPacketInfo_t ) ); + } + + if( pPacketInfo != NULL ) + { + __CPROVER_assume( pPacketInfo->remainingLength < CBMC_MAX_OBJECT_SIZE ); + pPacketInfo->pRemainingData = mallocCanFail( pPacketInfo->remainingLength ); + } + + return pPacketInfo; +} + +bool isValidMqttPacketInfo( const MQTTPacketInfo_t * pPacketInfo ) +{ + bool isValid = true; + + if( pPacketInfo != NULL ) + { + isValid = pPacketInfo->remainingLength < CBMC_MAX_OBJECT_SIZE; + } + + return isValid; +} + +MQTTPublishInfo_t * allocateMqttPublishInfo( MQTTPublishInfo_t * pPublishInfo ) +{ + if( pPublishInfo == NULL ) + { + pPublishInfo = mallocCanFail( sizeof( MQTTPublishInfo_t ) ); + } + + if( pPublishInfo != NULL ) + { + __CPROVER_assume( pPublishInfo->topicNameLength < CBMC_MAX_OBJECT_SIZE ); + pPublishInfo->pTopicName = mallocCanFail( pPublishInfo->topicNameLength ); + __CPROVER_assume( pPublishInfo->payloadLength < CBMC_MAX_OBJECT_SIZE ); + pPublishInfo->pPayload = mallocCanFail( pPublishInfo->payloadLength ); + } + + return pPublishInfo; +} + +bool isValidMqttPublishInfo( const MQTTPublishInfo_t * pPublishInfo ) +{ + bool isValid = true; + + if( pPublishInfo != NULL ) + { + bool validQos = ( ( pPublishInfo->qos >= MQTTQoS0 ) && + ( pPublishInfo->qos <= MQTTQoS2 ) ); + + bool validTopicNameLength = pPublishInfo->topicNameLength < CBMC_MAX_OBJECT_SIZE; + bool validPayloadLength = pPublishInfo->payloadLength < CBMC_MAX_OBJECT_SIZE; + + isValid = validQos && validTopicNameLength && validPayloadLength; + } + + return isValid; +} + +MQTTConnectInfo_t * allocateMqttConnectInfo( MQTTConnectInfo_t * pConnectInfo ) +{ + if( pConnectInfo == NULL ) + { + pConnectInfo = mallocCanFail( sizeof( MQTTConnectInfo_t ) ); + } + + if( pConnectInfo != NULL ) + { + __CPROVER_assume( pConnectInfo->clientIdentifierLength < CBMC_MAX_OBJECT_SIZE ); + pConnectInfo->pClientIdentifier = mallocCanFail( pConnectInfo->clientIdentifierLength ); + __CPROVER_assume( pConnectInfo->userNameLength < CBMC_MAX_OBJECT_SIZE ); + pConnectInfo->pUserName = mallocCanFail( pConnectInfo->userNameLength ); + __CPROVER_assume( pConnectInfo->passwordLength < CBMC_MAX_OBJECT_SIZE ); + pConnectInfo->pPassword = mallocCanFail( pConnectInfo->passwordLength ); + } + + return pConnectInfo; +} + +bool isValidMqttConnectInfo( const MQTTConnectInfo_t * pConnectInfo ) +{ + bool isValid = true; + + if( pConnectInfo != NULL ) + { + isValid = isValid && ( pConnectInfo->clientIdentifierLength < CBMC_MAX_OBJECT_SIZE ); + isValid = isValid && ( pConnectInfo->userNameLength < CBMC_MAX_OBJECT_SIZE ); + isValid = isValid && ( pConnectInfo->passwordLength < CBMC_MAX_OBJECT_SIZE ); + } + + return isValid; +} + +MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pBuffer ) +{ + if( pBuffer == NULL ) + { + pBuffer = mallocCanFail( sizeof( MQTTFixedBuffer_t ) ); + } + + if( pBuffer != NULL ) + { + __CPROVER_assume( pBuffer->size < CBMC_MAX_OBJECT_SIZE ); + pBuffer->pBuffer = mallocCanFail( pBuffer->size ); + } + + return pBuffer; +} + +bool isValidMqttFixedBuffer( const MQTTFixedBuffer_t * pBuffer ) +{ + bool isValid = true; + + if( pBuffer != NULL ) + { + isValid = pBuffer->size < CBMC_MAX_OBJECT_SIZE; + } + + return isValid; +} diff --git a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c index 58b63029a6..ae07809ae1 100644 --- a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +++ b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c @@ -26,9 +26,6 @@ int32_t NetworkInterfaceReceiveStub( NetworkContext_t context, void * pBuffer, size_t bytesToRecv ) { - __CPROVER_assert( context != NULL, - "IotNetworkInterfaceReceive pConnection is not NULL." ); - __CPROVER_assert( pBuffer != NULL, "IotNetworkInterfaceReceive pBuffer is not NULL." ); diff --git a/tools/aws-templates-for-cbmc-proofs b/tools/aws-templates-for-cbmc-proofs index c5eaea00bb..64879cfd7c 160000 --- a/tools/aws-templates-for-cbmc-proofs +++ b/tools/aws-templates-for-cbmc-proofs @@ -1 +1 @@ -Subproject commit c5eaea00bb86d804db62d83588f8e3efb709d43b +Subproject commit 64879cfd7c568e0caf1af0d0cc9ba6445c6431a9 From 7b09baa4c8d0172538bae3819cb016edf9347050 Mon Sep 17 00:00:00 2001 From: Dan Good <49254594+dan4thewin@users.noreply.github.com> Date: Thu, 9 Jul 2020 22:02:26 -0400 Subject: [PATCH 575/844] JSON validator (#1029) Add strict JSON document validator with key-value pair search --- libraries/standard/json/include/json.h | 112 +++ libraries/standard/json/src/json.c | 1173 ++++++++++++++++++++++++ 2 files changed, 1285 insertions(+) create mode 100644 libraries/standard/json/include/json.h create mode 100644 libraries/standard/json/src/json.c diff --git a/libraries/standard/json/include/json.h b/libraries/standard/json/include/json.h new file mode 100644 index 0000000000..a16824982b --- /dev/null +++ b/libraries/standard/json/include/json.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef JSON_H_ +#define JSON_H_ + +#include + +typedef enum JSONStatus +{ + JSONPartial = 0, + JSONSuccess, + JSONIllegalDocument, + JSONMaxDepthExceeded, + JSONNotFound +} JSONStatus_t; + +/** + * @brief Parse a buffer to determine if it contains a valid JSON document. + * + * @param[in] buf The buffer to parse. + * @param[in] max The size of the buffer. + * + * @return #JSONSuccess if the buffer contents are valid JSON; + * #JSONIllegalDocument if the buffer contents are NOT valid JSON; + * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold; + * #JSONPartial if the buffer contents are potentially valid but incomplete. + * + * @note The maximum nesting depth may be specified by defining the macro + * JSON_MAX_DEPTH. The default is 32 of sizeof(char). + * + * @note By default, a valid JSON document may contain a single element + * (e.g., string, boolean, number). To require that a valid document + * contain an object or array, define JSON_VALIDATE_COLLECTIONS_ONLY. + */ +JSONStatus_t JSON_Validate( const char * buf, + size_t max ); + +/** + * @brief Find a key in a JSON object and output a pointer to its value. + * + * @param[in] buf The buffer to search. + * @param[in] max size of the buffer. + * @param[in] queryKey The key to search for. + * @param[in] queryKeyLength Length of the key. + * @param[in] separator A character between a key and a sub-key in queryKey. + * @param[out] outValue A pointer to receive the address of the value found. + * @param[out] outValueLength A pointer to receive the length of the value found. + * + * The JSON document must contain an object (e.g., '{"key":"value"}'). Any + * value may also be an object and so forth to a maximum depth. A search + * may descend through nested objects when the queryKey contains matching + * key strings joined by a separator. + * + * For example, if buf contains '{"foo":"abc","bar":{"foo","xyz"}}', then a + * search for 'foo' would output 'abc', 'bar' would output '{"foo","xyz"}', + * and a search for 'bar.foo' would output 'xyz' (given separator is + * specified as '.'). + * + * On success, the pointer to the value points to a location in buf. No null + * termination is done for the value. For valid JSON it is safe to place + * a null character at the end of the value, so long as the character + * replaced is put back before running another search. + * + * result = JSON_Search(buf, bufLength, key, keyLength, '.', + * value, valueLength); + * if( result == JSONSuccess ) + * { + * char save = value[valueLength]; + * value[valueLength] = '\0'; + * printf("Found: %s -> %s\n", key, value); + * value[valueLength] = save; + * } + * + * @return #JSONSuccess if the queryKey is found and the value output; + * #JSONIllegalDocument if the buffer contents are NOT valid JSON; + * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold; + * #JSONNotFound if the queryKey is NOT found. + * + * @note The maximum nesting depth may be specified by defining the macro + * JSON_MAX_DEPTH. The default is 32 of sizeof(char). + * + * @note JSON_Search() performs validation, but stops upon finding a matching + * key and its value. To validate the entire JSON document, use JSON_Validate(). + */ +JSONStatus_t JSON_Search( char * buf, + size_t max, + char * queryKey, + size_t queryKeyLength, + char separator, + char ** outValue, + size_t * outValueLength ); + +#endif /* ifndef JSON_H_ */ diff --git a/libraries/standard/json/src/json.c b/libraries/standard/json/src/json.c new file mode 100644 index 0000000000..63840f583c --- /dev/null +++ b/libraries/standard/json/src/json.c @@ -0,0 +1,1173 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include "json.h" + +typedef enum bool_ +{ + true = 1, false = 0 +} bool_; + +/** + * @brief Advance buffer index beyond whitespace. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + */ +static void skipSpace( const char * buf, + size_t * start, + size_t max ) +{ + size_t i = *start; + + for( ; i < max; i++ ) + { + if( isspace( buf[ i ] ) == 0U ) + { + break; + } + } + + *start = i; +} + +/** + * @brief Count the leading 1s in a byte. + * + * The high-order 1 bits of the first byte in a UTF-8 encoding + * indicate the number of additional bytes to follow. + * + * @return the count + */ +static size_t countHighBits( uint8_t c ) +{ + uint8_t n = c; + size_t i = 0U; + + while( ( n & 0x80U ) != 0U ) + { + i++; + n <<= 1; + } + + return i; +} + +/** + * @brief Is the value a legal Unicode code point and encoded with + * the fewest bytes? + * + * The last Unicode code point is 0x10FFFF. + * + * Unicode 3.1 disallows UTF-8 interpretation of non-shortest form sequences. + * 1 byte encodes 0 through 7 bits + * 2 bytes encode 8 through 5+6 = 11 bits + * 3 bytes encode 12 through 4+6+6 = 16 bits + * 4 bytes encode 17 through 3+6+6+6 = 21 bits + * + * Unicode 3.2 disallows UTF-8 code point values in the surrogate range, + * [U+D800 to U+DFFF]. + * + * @note Disallow ASCII, as this is called only for multibyte sequences. + */ +static bool_ shortestUTF8( size_t length, + uint32_t value ) +{ + bool_ ret = false; + uint32_t min, max; + + switch( length ) + { + case 2: + min = ( uint32_t ) 1 << 7U; + max = ( ( uint32_t ) 1 << 11U ) - 1U; + break; + + case 3: + min = ( uint32_t ) 1 << 11U; + max = ( ( uint32_t ) 1 << 16U ) - 1U; + break; + + case 4: + min = ( uint32_t ) 1 << 16U; + max = 0x10FFFFU; + break; + + /* force a false outcome */ + default: + min = 1U; + max = 0U; + break; + } + + if( ( value >= min ) && ( value <= max ) && + ( ( value < 0xD800U ) || ( value > 0xDFFFU ) ) ) + { + ret = true; + } + + return ret; +} + +/** + * @brief Advance buffer index beyond a UTF-8 code point. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a valid code point was present; + * false otherwise. + * + * 00–7F Single-byte character + * 80–BF Trailing byte + * C0–DF Leading byte of two-byte character + * E0–EF Leading byte of three-byte character + * F0–F7 Leading byte of four-byte character + * F8–FB Illegal (formerly leading byte of five-byte character) + * FC–FD Illegal (formerly leading byte of six-byte character) + * FE–FF Illegal + * + * The octet values C0, C1, and F5 to FF are illegal, since C0 and C1 + * would introduce a non-shortest sequence, and F5 or above would + * introduce a value greater than the last code point, 0x10FFFF. + */ +static bool_ skipUTF8MultiByte( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + size_t i = *start, bitCount, j; + uint32_t value = 0; + uint8_t c = ( uint8_t ) buf[ i ]; + + if( ( c > 0xC1U ) && ( c < 0xF5U ) ) + { + bitCount = countHighBits( c ); + value = ( ( uint32_t ) c ) & ( ( ( uint32_t ) 1 << ( 7U - bitCount ) ) - 1U ); + + /* The bit count is 1 greater than the number of bytes, + * e.g., when j is 2, we skip one more byte. */ + for( j = bitCount - 1U; j > 0U; j-- ) + { + i++; + + if( i >= max ) + { + break; + } + + /* Additional bytes must match 10xxxxxx. */ + c = ( uint8_t ) buf[ i ]; + + if( ( c & 0xC0U ) != 0x80U ) + { + break; + } + + value = ( value << 6U ) | ( c & 0x3FU ); + } + + if( ( j == 0U ) && ( shortestUTF8( bitCount, value ) == true ) ) + { + *start = i + 1U; + ret = true; + } + } + + return ret; +} + +/** + * @brief Advance buffer index beyond an ASCII or UTF-8 code point. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a valid code point was present; + * false otherwise. + */ +static bool_ skipUTF8( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + + if( *start < max ) + { + /* an ASCII byte */ + if( ( ( ( uint8_t ) buf[ *start ] ) & 0x80U ) == 0U ) + { + *start += 1U; + ret = true; + } + else + { + ret = skipUTF8MultiByte( buf, start, max ); + } + } + + return ret; +} + +/** + * @brief Convert a hexadecimal character to an integer. + * + * @param[in] c The character to convert. + * + * @return the integer value upon success or UINT8_MAX on failure. + */ +static uint8_t hexToInt( char c ) +{ + uint8_t n = UINT8_MAX; + + if( isxdigit( c ) != 0U ) + { + if( c >= 'a' ) + { + n = 10U + ( ( uint8_t ) c - ( uint8_t ) 'a' ); + } + else if( c >= 'A' ) + { + n = 10U + ( ( uint8_t ) c - ( uint8_t ) 'A' ); + } + else + { + n = ( uint8_t ) c - ( uint8_t ) '0'; + } + } + + return n; +} + +/** + * @brief Advance buffer index beyond a \u Unicode escape sequence. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * @param[in] requireLowSurrogate true when a low surrogate is required. + * + * Surrogate pairs are two escape sequences that together denote + * a code point outside the Basic Multilingual Plane. They must + * occur as a pair with the first "high" value in [U+D800, U+DBFF], + * and the second "low" value in [U+DC00, U+DFFF]. + * + * @return true if a valid escape sequence was present; + * false otherwise. + * + * @note For the sake of security, \u0000 is disallowed. + */ +#define isHighSurrogate( x ) ( ( ( x ) >= 0xD800U ) && ( ( x ) <= 0xDBFFU ) ) +#define isLowSurrogate( x ) ( ( ( x ) >= 0xDC00U ) && ( ( x ) <= 0xDFFFU ) ) +static bool_ skipHexEscape( const char * buf, + size_t * start, + size_t max, + bool_ requireLowSurrogate ) +{ + bool_ ret = false; + size_t i = *start, end = *start + 6U; + uint16_t value = 0U; + + if( ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) ) + { + for( i += 2U; i < end; i++ ) + { + uint8_t n = hexToInt( buf[ i ] ); + + if( n == UINT8_MAX ) + { + break; + } + + value = ( value << 4U ) | n; + } + + if( ( i == end ) && ( value > 0U ) ) + { + if( requireLowSurrogate == true ) + { + if( isLowSurrogate( value ) ) + { + ret = true; + } + } + else if( isHighSurrogate( value ) ) + { + /* low surrogate must follow */ + ret = skipHexEscape( buf, &i, max, true ); + } + else if( isLowSurrogate( value ) ) + { + /* premature low surrogate */ + } + else + { + ret = true; + } + } + } + + if( ret == true ) + { + *start = i; + } + + return ret; +} + +/** + * @brief Advance buffer index beyond an escape sequence. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a valid escape sequence was present; + * false otherwise. + * + * @note For the sake of security, \NUL is disallowed. + */ +static bool_ skipEscape( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + size_t i = *start; + + if( ( ( i + 1U ) < max ) && ( buf[ i ] == '\\' ) ) + { + char c = buf[ i + 1U ]; + + switch( c ) + { + case '\0': + break; + + case 'u': + ret = skipHexEscape( buf, &i, max, false ); + break; + + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + i += 2U; + ret = true; + break; + + default: + + /* a control character: (NUL,SPACE) */ + if( ( ( uint8_t ) c ) < 0x20U ) + { + i += 2U; + ret = true; + } + + break; + } + } + + *start = i; + return ret; +} + +/** + * @brief Advance buffer index beyond a double-quoted string. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a valid string was present; + * false otherwise. + */ +static bool_ skipString( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + size_t i = *start; + + if( ( i < max ) && ( buf[ i ] == '"' ) ) + { + i++; + + while( i < max ) + { + if( buf[ i ] == '"' ) + { + ret = true; + i++; + break; + } + + if( buf[ i ] == '\\' ) + { + if( skipEscape( buf, &i, max ) != true ) + { + break; + } + } + /* An unescaped control character is not allowed. */ + else if( ( ( uint8_t ) buf[ i ] ) < 0x20U ) + { + break; + } + else if( skipUTF8( buf, &i, max ) != true ) + { + break; + } + else + { + /* MISRA 15.7 */ + } + } + } + + *start = i; + return ret; +} + +/** + * @brief Compare the leading n bytes of two character sequences. + * + * @param[in] a first character sequence + * @param[in] b second character sequence + * @param[in] n number of bytes + * + * @return true if the sequences are the same; + * false otherwise + */ +static bool_ strnEq( const char * a, + const char * b, + size_t n ) +{ + size_t i; + + for( i = 0U; i < n; i++ ) + { + if( a[ i ] != b[ i ] ) + { + break; + } + } + + return ( i == n ) ? true : false; +} + +/** + * @brief Advance buffer index beyond a literal. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if the literal was present; + * false otherwise. + */ +static bool_ skipLiteral( const char * buf, + size_t * start, + size_t max, + const char * literal, + size_t length ) +{ + bool_ ret = false; + + if( ( *start < max ) && ( length <= ( max - *start ) ) ) + { + ret = strnEq( &buf[ *start ], literal, length ); + } + + if( ret == true ) + { + *start += length; + } + + return ret; +} + +/** + * @brief Advance buffer index beyond a JSON literal. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a valid literal was present; + * false otherwise. + */ +static bool_ skipAnyLiteral( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + +#define skipLit_( x ) \ + ( skipLiteral( buf, start, max, x, ( sizeof( x ) - 1U ) ) == true ) + + if( skipLit_( "true" ) || skipLit_( "false" ) || skipLit_( "null" ) ) + { + ret = true; + } + + return ret; +} + +/** + * @brief Advance buffer index beyond one or more digits. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a digit was present; + * false otherwise. + */ +static bool_ skipDigits( const char * buf, + size_t * start, + size_t max ) +{ + size_t i = *start, save = *start; + + for( ; i < max; i++ ) + { + if( isdigit( buf[ i ] ) == 0U ) + { + break; + } + } + + *start = i; + return ( i > save ) ? true : false; +} + +/** + * @brief Advance buffer index beyond the decimal portion of a number. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + */ +static void skipDecimals( const char * buf, + size_t * start, + size_t max ) +{ + size_t i = *start; + + if( ( i < max ) && ( buf[ i ] == '.' ) ) + { + i++; + + if( skipDigits( buf, &i, max ) == true ) + { + *start = i; + } + } +} + +/** + * @brief Advance buffer index beyond the exponent portion of a number. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + */ +static void skipExponent( const char * buf, + size_t * start, + size_t max ) +{ + size_t i = *start; + + if( ( i < max ) && ( ( buf[ i ] == 'e' ) || ( buf[ i ] == 'E' ) ) ) + { + i++; + + if( ( i < max ) && ( ( buf[ i ] == '-' ) || ( buf[ i ] == '+' ) ) ) + { + i++; + } + + if( skipDigits( buf, &i, max ) == true ) + { + *start = i; + } + } +} + +/** + * @brief Advance buffer index beyond a number. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a valid number was present; + * false otherwise. + */ +static bool_ skipNumber( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + size_t i = *start; + + if( ( i < max ) && ( buf[ i ] == '-' ) ) + { + i++; + } + + if( i < max ) + { + /* JSON disallows superfluous leading zeroes, so an + * initial zero must either be alone, or followed by + * a decimal or exponent. + * + * Should there be a digit after the zero, that digit + * will not be skipped by this function, and later parsing + * will judge this an illegal document. */ + if( buf[ i ] == '0' ) + { + ret = true; + i++; + } + else + { + ret = skipDigits( buf, &i, max ); + } + } + + if( ret == true ) + { + skipDecimals( buf, &i, max ); + skipExponent( buf, &i, max ); + } + + *start = i; + return ret; +} + +/** + * @brief Advance buffer index beyond a scalar value. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a scalar value was present; + * false otherwise. + */ +static bool_ skipAnyScalar( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + + if( ( skipString( buf, start, max ) == true ) || + ( skipAnyLiteral( buf, start, max ) == true ) || + ( skipNumber( buf, start, max ) == true ) ) + { + ret = true; + } + + return ret; +} + +/** + * @brief Advance buffer index beyond a comma separator + * and surrounding whitespace. + * + * JSON uses a comma to separate values in an array and key-value + * pairs in an object. JSON does not permit a trailing comma. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return true if a non-terminal comma was present; + * false otherwise. + */ +static bool_ skipSpaceAndComma( const char * buf, + size_t * start, + size_t max ) +{ + bool_ ret = false; + size_t i; + + skipSpace( buf, start, max ); + i = *start; + + if( ( i < max ) && ( buf[ i ] == ',' ) ) + { + i++; + skipSpace( buf, &i, max ); + + if( ( i < max ) && ( buf[ i ] != '}' ) && ( buf[ i ] != ']' ) ) + { + ret = true; + *start = i; + } + } + + return ret; +} + +/** + * @brief Advance buffer index beyond the scalar values of an array. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @note Stops advance if a value is an object or array. + */ +static void skipArrayScalars( const char * buf, + size_t * start, + size_t max ) +{ + size_t i = *start; + + while( i < max ) + { + if( skipAnyScalar( buf, &i, max ) != true ) + { + break; + } + + if( skipSpaceAndComma( buf, &i, max ) != true ) + { + break; + } + } + + *start = i; +} + +/** + * @brief Advance buffer index beyond the scalar key-value pairs + * of an object. + * + * In JSON, objects consist of comma-separated key-value pairs. + * A key is always a string (a scalar) while a value may be a + * scalar, an object, or an array. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @note Stops advance if a value is an object or array. + */ +static void skipObjectScalars( const char * buf, + size_t * start, + size_t max ) +{ + size_t i = *start; + + while( i < max ) + { + if( skipString( buf, &i, max ) != true ) + { + break; + } + + skipSpace( buf, &i, max ); + + if( ( i < max ) && ( buf[ i ] != ':' ) ) + { + break; + } + + i++; + skipSpace( buf, &i, max ); + + if( skipAnyScalar( buf, &i, max ) != true ) + { + break; + } + + if( skipSpaceAndComma( buf, &i, max ) != true ) + { + break; + } + } + + *start = i; +} + +/** + * @brief Advance buffer index beyond one or more scalars. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + */ +static void skipScalars( const char * buf, + size_t * start, + size_t max, + char mode ) +{ + assert( ( mode == '[' ) || ( mode == '{' ) ); + + skipSpace( buf, start, max ); + + if( mode == '[' ) + { + skipArrayScalars( buf, start, max ); + } + else + { + skipObjectScalars( buf, start, max ); + } +} + +/** + * @brief Advance buffer index beyond a collection and handle nesting. + * + * A stack is used to continue parsing the prior collection type + * when a nested collection is finished. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * + * @return #JSONSuccess if the buffer contents are a valid JSON collection; + * #JSONIllegalDocument if the buffer contents are NOT valid JSON; + * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold; + * #JSONPartial if the buffer contents are potentially valid but incomplete. + */ +#ifndef JSON_MAX_DEPTH + #define JSON_MAX_DEPTH 32 +#endif +static JSONStatus_t skipCollection( const char * buf, + size_t * start, + size_t max ) +{ + JSONStatus_t ret = JSONPartial; + char c, stack[ JSON_MAX_DEPTH ]; + int16_t depth = -1; + size_t i = *start; + + while( i < max ) + { + c = buf[ i ]; + i++; + + switch( c ) + { + case '{': + case '[': + depth++; + + if( depth == JSON_MAX_DEPTH ) + { + ret = JSONMaxDepthExceeded; + break; + } + + stack[ depth ] = c; + break; + + case '}': + case ']': + + if( depth > 0 ) + { + depth--; + ( void ) skipSpaceAndComma( buf, &i, max ); + break; + } + + ret = ( depth == 0 ) ? JSONSuccess : JSONIllegalDocument; + break; + + default: + ret = JSONIllegalDocument; + break; + } + + if( ret != JSONPartial ) + { + break; + } + + skipScalars( buf, &i, max, stack[ depth ] ); + } + + *start = i; + return ret; +} + +/** + * See json.h for docs. + * + * Verify that the entire buffer contains exactly one scalar + * or collection within optional whitespace. + */ +JSONStatus_t JSON_Validate( const char * buf, + size_t max ) +{ + JSONStatus_t ret; + size_t i = 0U; + + assert( ( buf != NULL ) && ( max > 0U ) ); + + skipSpace( buf, &i, max ); + #ifndef JSON_VALIDATE_COLLECTIONS_ONLY + if( skipAnyScalar( buf, &i, max ) == true ) + { + ret = JSONSuccess; + } + else + #endif + { + ret = skipCollection( buf, &i, max ); + } + + if( ( ret == JSONSuccess ) && ( i < max ) ) + { + skipSpace( buf, &i, max ); + + if( i != max ) + { + ret = JSONIllegalDocument; + } + } + + return ret; +} + +/** + * @brief Output indexes for the next key-value pair of an object. + * + * Also advances the buffer index beyond the key-value pair. + * The value may be a scalar or a collection. + * + * @param[in] buf The buffer to parse. + * @param[in,out] start The index at which to begin. + * @param[in] max The size of the buffer. + * @param[out] key A pointer to receive the index of the key. + * @param[out] keyLength A pointer to receive the length of the key. + * @param[out] value A pointer to receive the index of the value. + * @param[out] valueLength A pointer to receive the length of the value. + * + * @return true if a key-value pair was present; + * false otherwise. + */ +static bool_ nextKeyValuePair( const char * buf, + size_t * start, + size_t max, + size_t * key, + size_t * keyLength, + size_t * value, + size_t * valueLength ) +{ + bool_ ret = true; + size_t i = *start, keyStart = *start, valueStart; + + if( skipString( buf, &i, max ) == true ) + { + *key = keyStart + 1U; + *keyLength = i - keyStart - 2U; + } + else + { + ret = false; + } + + if( ret == true ) + { + skipSpace( buf, &i, max ); + + if( ( i < max ) && ( buf[ i ] == ':' ) ) + { + i++; + skipSpace( buf, &i, max ); + valueStart = i; + } + else + { + ret = false; + } + } + + if( ret == true ) + { + if( ( skipAnyScalar( buf, &i, max ) == true ) || + ( skipCollection( buf, &i, max ) == JSONSuccess ) ) + { + *value = valueStart; + *valueLength = i - valueStart; + } + else + { + ret = false; + } + } + + *start = i; + return ret; +} + +/** + * @brief Find a key in a JSON object and output a pointer to its value. + * + * @param[in] buf The buffer to search. + * @param[in] max size of the buffer. + * @param[in] queryKey The key to search for. + * @param[in] queryKeyLength Length of the key. + * @param[out] outValue A pointer to receive the address of the value found. + * @param[out] outValueLength A pointer to receive the length of the value found. + * + * Iterate over the key-value pairs of an object, looking for a matching key. + * + * @return #JSONSuccess if the queryKey is found and the value output; + * #JSONIllegalDocument if the buffer contents are NOT valid JSON; + * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold; + * #JSONNotFound if the queryKey is NOT found. + * + * @note Parsing stops upon finding a match. + */ +static JSONStatus_t search( char * buf, + size_t max, + const char * queryKey, + size_t queryKeyLength, + char ** outValue, + size_t * outValueLength ) +{ + JSONStatus_t ret = JSONPartial; + size_t i = 0U, key, keyLength, value, valueLength; + + skipSpace( buf, &i, max ); + + if( ( i < max ) && ( buf[ i ] == '{' ) ) + { + i++; + skipSpace( buf, &i, max ); + + while( i < max ) + { + if( nextKeyValuePair( buf, &i, max, &key, &keyLength, + &value, &valueLength ) != true ) + { + ret = JSONIllegalDocument; + break; + } + + if( ( queryKeyLength == keyLength ) && + ( strnEq( queryKey, &buf[ key ], keyLength ) == true ) ) + { + ret = JSONSuccess; + break; + } + + if( skipSpaceAndComma( buf, &i, max ) != true ) + { + break; + } + } + } + + if( ret == JSONSuccess ) + { + /* String values and collections include their surrounding + * demarcation. If the value is a string, strip the quotes. */ + if( buf[ value ] == '"' ) + { + value++; + valueLength -= 2U; + } + + *outValue = &buf[ value ]; + *outValueLength = valueLength; + } + else if( ret == JSONPartial ) + { + ret = ( buf[ i ] == '}' ) ? JSONNotFound : JSONIllegalDocument; + } + else + { + /* MISRA 15.7 */ + } + + return ret; +} + +/** + * See json.h for docs. + * + * Handle a nested search by iterating over the parts of the queryKey. + */ +JSONStatus_t JSON_Search( char * buf, + size_t max, + char * queryKey, + size_t queryKeyLength, + char separator, + char ** outValue, + size_t * outValueLength ) +{ + JSONStatus_t ret = JSONPartial; + size_t i = 0U, start = 0U, keyLength = 0U; + char * p = buf; + size_t tmp = max; + + assert( ( buf != NULL ) && ( max > 0U ) ); + assert( ( queryKey != NULL ) && ( queryKeyLength != 0U ) ); + assert( ( outValue != NULL ) && ( outValueLength != NULL ) ); + assert( *queryKey != '\0' ); + + while( i < queryKeyLength ) + { + start = i; + + while( ( i < queryKeyLength ) && ( queryKey[ i ] != separator ) ) + { + i++; + } + + keyLength = i - start; + i++; + ret = search( p, tmp, &queryKey[ start ], keyLength, &p, &tmp ); + + if( ret != JSONSuccess ) + { + break; + } + } + + if( ret == JSONSuccess ) + { + *outValue = p; + *outValueLength = tmp; + } + + return ret; +} From b677d0e47d7a96b7df440a1d4cb7237c7f62bb5f Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 10 Jul 2020 11:11:44 -0700 Subject: [PATCH 576/844] Update Reconnect logic to match Full Jitter model (#1043) * Fix bug in reconnect logic by checking backoff value to be under max value before calling sleep() * hygiene: rename reconnect files to contain "transport_" prefix * Update reconnect logic based on review comment * Remove logic for capping max backoff value * Revert "Remove logic for capping max backoff value" This reverts commit aa24a56a5b2e42438547e58961f385238141e895. * Update backoff logic to use Full Jitter model * Fix issue in previous update for Full Jitter implementation * Fix issues in retry logic implementation to match Full Jitter exponential backoff model * Further improve implementation to account for corner case of attempt count overflow * Simplify max jitter calculation by avoiding bit shift operation * Account for MAX_RECONNECT_BACKOFF_SECONDS in max jitter value * More simplification * Remove empty macro artifact * style: avoid nested calls for better readability --- demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 2 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 7 ++- .../{reconnect.h => transport_reconnect.h} | 30 ++++++----- platform/posix/transport/CMakeLists.txt | 4 +- ...ct_posix.c => transport_reconnect_posix.c} | 54 ++++++++++--------- .../posix/transport/transportFilePaths.cmake | 2 +- 6 files changed, 54 insertions(+), 45 deletions(-) rename platform/include/{reconnect.h => transport_reconnect.h} (78%) rename platform/posix/transport/src/{reconnect_posix.c => transport_reconnect_posix.c} (65%) diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt index 13b4ea4ef0..5229a670da 100644 --- a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries( ${DEMO_NAME} PRIVATE mqtt - reconnect_posix + transport_reconnect_posix ) target_include_directories( diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index a80a4d8de5..158840f506 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -53,7 +53,7 @@ /* Reconnect parameters. */ -#include "reconnect.h" +#include "transport_reconnect.h" /** * @brief MQTT server host name. @@ -489,7 +489,7 @@ static int connectToServerWithBackoffRetries( int * pTcpSocket ) if( status == EXIT_FAILURE ) { - LogWarn( ( "Connection to the broker failed, sleeping %d seconds before the next attempt.", + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter.", ( reconnectParams.reconnectTimeoutSec > MAX_RECONNECT_TIMEOUT_SECONDS ) ? MAX_RECONNECT_TIMEOUT_SECONDS : reconnectParams.reconnectTimeoutSec ) ); backoffSuccess = Transport_ReconnectBackoffAndSleep( &reconnectParams ); } @@ -498,7 +498,7 @@ static int connectToServerWithBackoffRetries( int * pTcpSocket ) { LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); } - } while ( ( status == EXIT_FAILURE ) && ( backoffSuccess == true ) ); + } while( ( status == EXIT_FAILURE ) && ( backoffSuccess == true ) ); return status; } @@ -1088,7 +1088,6 @@ int main( int argc, sleep( MQTT_SUBPUB_LOOP_DELAY_SECONDS ); } - return status; } diff --git a/platform/include/reconnect.h b/platform/include/transport_reconnect.h similarity index 78% rename from platform/include/reconnect.h rename to platform/include/transport_reconnect.h index f7f9938e9d..e83f651f2b 100644 --- a/platform/include/reconnect.h +++ b/platform/include/transport_reconnect.h @@ -20,14 +20,15 @@ */ /** - * @file reconnect_config.h + * @file transport_reconnect.h * @brief Declaration of the exponential backoff reconnect logic utility functions * and constants. */ -#ifndef RECONNECT_H_ -#define RECONNECT_H_ +#ifndef TRANSPORT_RECONNECT_H_ +#define TRANSPORT_RECONNECT_H_ +/* Standard include. */ #include /* bools are only defined in C99+ */ @@ -41,22 +42,25 @@ /* @brief Max number of connect attempts, set this value to 0 if the client * must try connecting to the server forever */ -#define MAX_RECONNECT_ATTEMPS 4U +#define MAX_RECONNECT_ATTEMPTS 4U -/* @brief Initial fixed timeout value in seconds between two successive - * connects. A random jitter value is added to every timeout value */ -#define INITIAL_RECONNECT_TIMEOUT_SECONDS 1U -/* @brief Max timout value in seconds */ -#define MAX_RECONNECT_TIMEOUT_SECONDS 128U +/* @brief Initial fixed backoff value in seconds between two successive + * connects. A random jitter value is added to every backoff value */ +#define INITIAL_RECONNECT_BACKOFF_SECONDS 1U +/* @brief Max backoff value in seconds */ +#define MAX_RECONNECT_BACKOFF_SECONDS 128U /* @brief Max jitter value in seconds */ #define MAX_JITTER_VALUE_SECONDS 5U - -/* @brief Transport reconnect parameter */ +/* @brief Represents parameters required for reconnect logic. */ typedef struct TransportReconnectParams { - uint32_t reconnectTimeoutSec; + /* @brief The cumulative count of backoff delay cycles completed + * for reconnection. */ uint32_t attemptsDone; + + /** @brief The max jitter value for backoff time in reconnection attempt. */ + uint32_t nextJitterMax; } TransportReconnectParams_t; @@ -81,4 +85,4 @@ void Transport_ReconnectParamsReset( TransportReconnectParams_t * reconnectParam */ bool Transport_ReconnectBackoffAndSleep( TransportReconnectParams_t * reconnectParams ); -#endif /* ifndef RECONNECT_H_ */ +#endif /* ifndef TRANSPORT_RECONNECT_H_ */ diff --git a/platform/posix/transport/CMakeLists.txt b/platform/posix/transport/CMakeLists.txt index f035bc0608..554897a824 100644 --- a/platform/posix/transport/CMakeLists.txt +++ b/platform/posix/transport/CMakeLists.txt @@ -47,10 +47,10 @@ target_link_libraries( openssl_posix ${CMAKE_DL_LIBS} ) # Create target for POSIX implementation of reconnect logic. -add_library( reconnect_posix +add_library( transport_reconnect_posix ${RECONNECT_SOURCES} ) -target_include_directories( reconnect_posix +target_include_directories( transport_reconnect_posix PUBLIC ${COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS} ${LOGGING_INCLUDE_DIRS} ) diff --git a/platform/posix/transport/src/reconnect_posix.c b/platform/posix/transport/src/transport_reconnect_posix.c similarity index 65% rename from platform/posix/transport/src/reconnect_posix.c rename to platform/posix/transport/src/transport_reconnect_posix.c index 86704e87e7..f41dcee36b 100644 --- a/platform/posix/transport/src/reconnect_posix.c +++ b/platform/posix/transport/src/transport_reconnect_posix.c @@ -20,7 +20,7 @@ */ /** - * @file reconnect_posix.c + * @file transport_reconnect_posix.c * @brief Implementation of the backoff logic when connection fails to the server fails. */ @@ -28,42 +28,48 @@ #include #include #include -#include "reconnect.h" +#include + +#include "transport_reconnect.h" /*-----------------------------------------------------------*/ bool Transport_ReconnectBackoffAndSleep( TransportReconnectParams_t * pReconnectParams ) { bool status = false; - uint32_t jitter = 0; + int backOffDelay = 0; /* If MAX_RECONNECT_ATTEMPTS is set to 0, try forever */ - if( ( pReconnectParams->attemptsDone < MAX_RECONNECT_ATTEMPS ) || - ( 0 == MAX_RECONNECT_ATTEMPS ) ) + if( ( pReconnectParams->attemptsDone < MAX_RECONNECT_ATTEMPTS ) || + ( 0 == MAX_RECONNECT_ATTEMPTS ) ) { - /* Wait for timer to expire for the next reconnect */ - ( void ) sleep( pReconnectParams->reconnectTimeoutSec ); + /* Choose a random value for back-off time between 0 and the max jitter value. */ + backOffDelay = rand() % pReconnectParams->nextJitterMax; + + /* Wait for backoff time to expire for the next reconnect. */ + ( void ) sleep( backOffDelay ); + + /* Increment backoff counts. */ + pReconnectParams->attemptsDone++; - /* Calculate the next timeout value only if timeout value has not - * exceeded MAX_RECONNECT_TIMEOUT_SECONDS */ - if( pReconnectParams->reconnectTimeoutSec < MAX_RECONNECT_TIMEOUT_SECONDS ) + /* Double the max jitter value for the next reconnect attempt, only + * if the new value will be less than the max backoff time value. */ + if( pReconnectParams->nextJitterMax < ( MAX_RECONNECT_BACKOFF_SECONDS / 2U ) ) + { + pReconnectParams->nextJitterMax += pReconnectParams->nextJitterMax; + } + else { - /* Calculate jitter value picking a random number - * between 0 and MAX_JITTER_VALUE_SECONDS. */ - jitter = ( rand() % MAX_JITTER_VALUE_SECONDS ); - /* Double the timeout value for the next iteration */ - pReconnectParams->reconnectTimeoutSec += pReconnectParams->reconnectTimeoutSec; - pReconnectParams->reconnectTimeoutSec += jitter; + pReconnectParams->nextJitterMax = MAX_RECONNECT_BACKOFF_SECONDS; } - pReconnectParams->attemptsDone++; status = true; } else { /* When max reconnect attempts are exhausted, let application know by returning - * false. Application may choose to restart the connection process after calling - * Transport_ReconnectParamsReset() */ + * false. Application may choose to restart the connection process after calling + * Transport_ReconnectParamsReset(). */ status = false; Transport_ReconnectParamsReset( pReconnectParams ); } @@ -78,20 +84,20 @@ void Transport_ReconnectParamsReset( TransportReconnectParams_t * pReconnectPara uint32_t jitter = 0; struct timespec tp; - /* Reset attempts done to zero so that the next connect cycle can start */ + /* Reset attempts done to zero so that the next connect cycle can start. */ pReconnectParams->attemptsDone = 0; - /* Get current time to seed pseudo random number generator */ + /* Get current time to seed pseudo random number generator. */ ( void ) clock_gettime( CLOCK_REALTIME, &tp ); - /* Seed pseudo ramdom number generator with nano seconds */ + /* Seed pseudo ramdom number generator with nano seconds. */ srand( tp.tv_nsec ); /* Calculate jitter value using picking a random number. */ jitter = ( rand() % MAX_JITTER_VALUE_SECONDS ); - /* Reset the timout value to the initial time out value plus jitter */ - pReconnectParams->reconnectTimeoutSec = INITIAL_RECONNECT_TIMEOUT_SECONDS + jitter; + /* Reset the backoff value to the initial time out value plus jitter. */ + pReconnectParams->nextJitterMax = INITIAL_RECONNECT_BACKOFF_SECONDS + jitter; } /*-----------------------------------------------------------*/ diff --git a/platform/posix/transport/transportFilePaths.cmake b/platform/posix/transport/transportFilePaths.cmake index 7d77dced55..9deb7a6fa8 100644 --- a/platform/posix/transport/transportFilePaths.cmake +++ b/platform/posix/transport/transportFilePaths.cmake @@ -19,7 +19,7 @@ set( OPENSSL_TRANSPORT_SOURCES # Reconnect logic source files. set( RECONNECT_SOURCES - ${CMAKE_CURRENT_LIST_DIR}/src/reconnect_posix.c ) + ${CMAKE_CURRENT_LIST_DIR}/src/transport_reconnect_posix.c ) # Transport Public Include directories. set( COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS From 7d50e64658b34259188a6626633b6f6628778a30 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 10 Jul 2020 12:19:24 -0700 Subject: [PATCH 577/844] Add reconnection logic to all MQTT demos and update to use common transport interface (#1036) * Get all unit tests to pass * Update integration tests to common transport interface * Update OpenSSL send and recv to poll * Update OpenSSL send and recv * Update plaintext demo to use common transport interface and removal of warnings * Get mutual auth demo to compile but not returning successfully * Remove usage of poll in OpenSSL transport send/recv * Update based on latest changes to OpenSSL transport recv * Make certs directory if not exist * Add reconnection logic to mqtt basic tls demo * Add reconnection logic to lightweight demo and update transport interface * Make all demos consistent with each other * Update CBMC proofs to follow suit * Remove unnecessary includes * Add missing time.h include to mqtt_demo_plaintext * Remove unnecessary cast for network context buffer Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Address PR comments * Refactor Clock_GetTimeMs into clock_posix.c * Fix accidental replace * Remove accidentally pushed certificate * Remove time.h include from mqtt plaintext demo * Update demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Update demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Update demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Update demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Address PR comments * Address PR comments * Fix error in fixing merge conflict * Fix redefinition of NetworkContext_t in mqtt_config.h * Fix PUBREC comment in demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Fix PUBREC comment in demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Address PR comments * Address PR comments * Remove test assert for packet type != MQTT_PACKET_TYPE_PINGRESP * Add back handling of MQTT_PACKET_TYPE_PINGRESP * Add MQTT_PACKET_TYPE_PINGRESP log for mqtt system test * Address PR comments * Update library name of transport_posix * Address PR comments and add reconnect logic changes * Update comment in demos * Remove accidentally pushed cert * Update demos * Add missing closing parenthesis Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> --- CMakeLists.txt | 1 - demos/CMakeLists.txt | 2 +- demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 32 +- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 12 +- .../mqtt_demo_basic_tls/mosquitto.org.crt | 24 - demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h | 4 - .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 920 ++++++--------- .../mqtt/mqtt_demo_lightweight/CMakeLists.txt | 6 +- .../mqtt/mqtt_demo_lightweight/demo_config.h | 22 +- .../mqtt/mqtt_demo_lightweight/mqtt_config.h | 4 +- .../mqtt_demo_lightweight.c | 441 +++---- .../mqtt/mqtt_demo_mutual_auth/CMakeLists.txt | 21 +- .../mqtt/mqtt_demo_mutual_auth/demo_config.h | 12 +- .../mqtt/mqtt_demo_mutual_auth/mqtt_config.h | 4 - .../mqtt_demo_mutual_auth.c | 1015 +++++------------ demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 2 + demos/mqtt/mqtt_demo_plaintext/demo_config.h | 15 + demos/mqtt/mqtt_demo_plaintext/mqtt_config.h | 3 - .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 701 ++++-------- demos/transport/CMakeLists.txt | 26 - demos/transport/transport_config.h | 50 - demos/transport/transport_utils.c | 416 ------- demos/transport/transport_utils.h | 114 -- .../standard/mqtt/cbmc/include/mqtt_config.h | 8 +- .../cbmc/include/network_interface_stubs.h | 10 +- ...T_GetIncomingPacketTypeAndLength_harness.c | 2 +- .../cbmc/proofs/MQTT_Init/MQTT_Init_harness.c | 2 +- .../mqtt/cbmc/proofs/Makefile-project-defines | 1 + .../mqtt/cbmc/stubs/network_interface_stubs.c | 2 +- libraries/standard/mqtt/include/mqtt.h | 20 +- .../standard/mqtt/include/mqtt_lightweight.h | 24 +- .../mqtt/integration-test/CMakeLists.txt | 23 +- .../mqtt/integration-test/mosquitto.org.crt | 24 - .../mqtt/integration-test/mqtt_config.h | 4 - .../mqtt/integration-test/mqtt_system_test.c | 93 +- .../mqtt/integration-test/test_config.h | 2 +- libraries/standard/mqtt/mqttFilePaths.cmake | 2 +- libraries/standard/mqtt/src/mqtt.c | 13 +- .../standard/mqtt/src/mqtt_lightweight.c | 14 +- libraries/standard/mqtt/utest/mqtt_config.h | 14 +- .../mqtt/utest/mqtt_lightweight_utest.c | 39 +- libraries/standard/mqtt/utest/mqtt_utest.c | 93 +- platform/CMakeLists.txt | 4 +- platform/include/clock.h | 51 +- platform/posix/CMakeLists.txt | 10 + platform/posix/clock_posix.c | 73 +- .../posix/transport/include/openssl_posix.h | 2 +- platform/posix/transport/src/openssl_posix.c | 17 +- 48 files changed, 1299 insertions(+), 3095 deletions(-) delete mode 100644 demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt delete mode 100644 demos/transport/CMakeLists.txt delete mode 100644 demos/transport/transport_config.h delete mode 100644 demos/transport/transport_utils.c delete mode 100644 demos/transport/transport_utils.h delete mode 100644 libraries/standard/mqtt/integration-test/mosquitto.org.crt create mode 100644 platform/posix/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 094d8d9845..9777e74fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,5 +55,4 @@ add_subdirectory( libraries ) add_subdirectory( platform ) # Build the demos. -add_subdirectory( demos/transport ) add_subdirectory( demos ) diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 1675769aac..e49f56cde6 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -8,7 +8,7 @@ if( NOT DEFINED PLATFORM_NAME ) endif() # Include each subdirectory that has a CMakeLists.txt file in it -file(GLOB demo_dirs "${DEMOS_DIR}/*/*") +file(GLOB demo_dirs "${DEMOS_DIR}/mqtt/*") foreach(demo_dir IN LISTS demo_dirs) if(IS_DIRECTORY "${demo_dir}" AND EXISTS "${demo_dir}/CMakeLists.txt") get_filename_component( DEMO_EXE_NAME ${demo_dir} NAME ) diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt index ae8e7ad85b..b01a9688c5 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -2,17 +2,6 @@ set( DEMO_NAME "mqtt_demo_basic_tls" ) # Demo target. add_executable(${DEMO_NAME}) -set(OPENSSL_USE_STATIC_LIBS TRUE) -find_package(OpenSSL REQUIRED) - -# Verify the minimum OpenSSL version required -if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) - message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) -endif() - -set( CMAKE_THREAD_PREFER_PTHREAD ON ) -find_package(Threads REQUIRED) - target_sources( ${DEMO_NAME} PRIVATE @@ -23,12 +12,9 @@ target_link_libraries( ${DEMO_NAME} PRIVATE mqtt - OpenSSL::Crypto - OpenSSL::SSL - # SSL uses Threads and on some platforms require explicit linking. - Threads::Threads - # SSL uses Dynamic Loading and on some platforms requires explicit linking. - ${CMAKE_DL_LIBS} + clock_posix + openssl_posix + transport_reconnect_posix ) target_include_directories( @@ -43,12 +29,20 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -#Copy the server certificate file to the binary directory. +# Download the Mosquitto Root CA certificate. +message( "Downloading the Mosquitto Root CA certificate..." ) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) +execute_process( + COMMAND curl --url https://test.mosquitto.org/ssl/mosquitto.org.crt + -o ${CMAKE_CURRENT_LIST_DIR}/certificates/mosquitto.org.crt +) + +# Copy the server certificate file to the binary directory. add_custom_command( TARGET ${DEMO_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${CMAKE_CURRENT_LIST_DIR}/mosquitto.org.crt" + "${CMAKE_CURRENT_LIST_DIR}/certificates/mosquitto.org.crt" "$/certificates/mosquitto.org.crt" ) diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 6ecd22a7a3..2d7ff9dcdd 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -56,37 +56,37 @@ * the instructions in https://mosquitto.org/ for running a Mosquitto broker * locally. */ -#define BROKER_ENDPOINT "test.mosquitto.org" +#define BROKER_ENDPOINT "test.mosquitto.org" /** * @brief Length of MQTT server host name. */ -#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) /** * @brief MQTT server port number. * * In general, port 8883 is for secured MQTT connections. */ -#define BROKER_PORT ( 8883 ) +#define BROKER_PORT ( 8883 ) /** * @brief Path of the file containing the server's root CA certificate. * * This certificate should be PEM-encoded. */ -#define SERVER_CERT_PATH "certificates/mosquitto.org.crt" +#define ROOT_CA_CERT_PATH "certificates/mosquitto.org.crt" /** * @brief Length of path to server certificate. */ -#define SERVER_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( SERVER_CERT_PATH ) - 1 ) ) +#define ROOT_CA_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( ROOT_CA_CERT_PATH ) - 1 ) ) /** * @brief MQTT client identifier. * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER "testclient" #endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt b/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt deleted file mode 100644 index e76dbd8559..0000000000 --- a/demos/mqtt/mqtt_demo_basic_tls/mosquitto.org.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL -BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG -A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU -BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv -by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE -BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES -MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp -dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg -UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW -Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA -s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH -3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo -E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT -MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV -6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC -6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf -+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK -sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 -LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE -m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= ------END CERTIFICATE----- diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h index af8cd9937c..9dcb69293d 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h @@ -48,10 +48,6 @@ /************ End of logging configuration ****************/ -/* Set network context to OpenSSL SSL context. */ -#include -typedef SSL * NetworkContext_t; - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 590c8dc5a3..fc2422c026 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -38,32 +38,46 @@ #include #include -/* POSIX socket includes. */ -#include -#include -#include +/* POSIX includes. */ #include -#include -#include - /* Include Demo Config as the first non-system header. */ #include "demo_config.h" /* MQTT API header. */ #include "mqtt.h" +/* OpenSSL sockets transport implementation. */ +#include "openssl_posix.h" + +/* Reconnect parameters. */ +#include "transport_reconnect.h" + +/* Clock for timer. */ +#include "clock.h" /** - * @brief Size of the network buffer for MQTT packets. + * These configuration settings are required to run the basic TLS demo. + * Throw compilation error if the below configs are not defined. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) - -/* Check that client identifier is defined. */ +#ifndef ROOT_CA_CERT_PATH + #error "Please define path to Root CA certificate of the MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." +#endif #ifndef CLIENT_IDENTIFIER #error "Please define a unique CLIENT_IDENTIFIER." #endif +/** + * Provide default values for undefined configuration settings. + */ +#ifndef BROKER_PORT + #define BROKER_PORT ( 8883 ) +#endif + +#ifndef NETWORK_BUFFER_SIZE + #define NETWORK_BUFFER_SIZE ( 1024U ) +#endif + /** * @brief Length of client identifier. */ @@ -93,7 +107,7 @@ #define MQTT_EXAMPLE_MESSAGE "Hello World!" /** - * @brief The MQTT message published in this example. + * @brief The length of the MQTT message published in this example. */ #define MQTT_EXAMPLE_MESSAGE_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_MESSAGE ) - 1 ) ) @@ -111,13 +125,17 @@ /** * @brief Timeout for MQTT_ProcessLoop function in milliseconds. - * */ #define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 500U ) /** - * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to - * broker. + * @brief The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. */ #define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) @@ -126,10 +144,20 @@ */ #define DELAY_BETWEEN_PUBLISHES_SECONDS ( 1U ) +/** + * @brief Number of PUBLISH messages sent per iteration. + */ +#define MQTT_PUBLISH_COUNT_PER_LOOP ( 5U ) + +/** + * @brief Delay in seconds between two iterations of subscribePublishLoop(). + */ +#define MQTT_SUBPUB_LOOP_DELAY_SECONDS ( 5U ) + /** * @brief Transport timeout in milliseconds for transport send and receive. */ -#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200 ) /*-----------------------------------------------------------*/ @@ -140,27 +168,18 @@ typedef struct PublishPackets { /** - * @brief MQTT client identifier. Must be unique per client. + * @brief Packet identifier of the publish packet. */ uint16_t packetId; /** - * @brief MQTT client identifier. Must be unique per client. + * @brief Publish info of the publish packet. */ MQTTPublishInfo_t pubInfo; } PublishPackets_t; /*-----------------------------------------------------------*/ -/** - * @brief globalEntryTime entry time into the application to use as a reference - * timestamp in #getTimeMs function. #getTimeMs will always return the difference - * of current time with the globalEntryTime. This will reduce the chances of - * overflow for 32 bit unsigned integer used for holding the timestamp. - * - */ -static uint32_t globalEntryTimeMs = 0U; - /** * @brief Packet Identifier generated when Subscribe request was sent to the broker; * it is used to match received Subscribe ACK to the transmitted subscribe. @@ -176,74 +195,42 @@ static uint16_t globalUnsubscribePacketIdentifier = 0U; /** * @brief Array to keep the outgoing publish messages. - * These stored outgoing publish messages are used to keep the messages until - * a successful ack is received. + * These stored outgoing publish messages are kept until a successful ack + * is received. */ static PublishPackets_t outgoingPublishPackets[ MAX_OUTGOING_PUBLISHES ] = { 0 }; -/*-----------------------------------------------------------*/ - /** - * @brief Creates a TCP connection to the MQTT broker as specified by - * BROKER_ENDPOINT and BROKER_PORT defined at the top of this file. - * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pTcpSocket Pointer to TCP socket file descriptor. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + * @brief The network buffer must remain valid for the lifetime of the MQTT context. */ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ); +static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; -/** - * @brief Set up a TLS connection over an existing TCP connection. - * - * @param[in] tcpSocket Existing TCP connection. - * @param[out] pSslContext Pointer to SSL connection context pointer. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. - */ -static int tlsSetup( int tcpSocket, - SSL ** pSslContext ); +/*-----------------------------------------------------------*/ /** - * @brief The transport send function provided to the MQTT context. - * - * @param[in] pSslContext Pointer to SSL context. - * @param[in] pMessage Data to send. - * @param[in] bytesToSend Length of data to send. + * @brief Connect to MQTT broker with reconnection retries. * - * @return Number of bytes sent; negative value on error; - * 0 for timeout or 0 bytes sent. - */ -static int32_t transportSend( NetworkContext_t pSslContext, - const void * pMessage, - size_t bytesToSend ); - -/** - * @brief The transport receive function provided to the MQTT context. + * If connection fails, retry is attempted after a timeout. + * Timeout value will exponentially increased till maximum + * timeout value is reached or the number of attemps are exhausted. * - * @param[in] pSslContext Pointer to SSL context. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToRecv Length of data to be received. + * @param[out] pNetworkContext The output parameter to return the created network context. * - * @return Number of bytes received; negative value on error; - * 0 for timeout. + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on successful connection. */ -static int32_t transportRecv( NetworkContext_t pSslContext, - void * pBuffer, - size_t bytesToRecv ); +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ); /** - * @brief The timer query function provided to the MQTT context. + * @brief A function that connects to MQTT broker, + * subscribes to a topic, publishes to the same topic + * MQTT_PUBLISH_COUNT_PER_LOOP number of times, and verifies if it + * receives the Publish message back. * - * This function returns the elapsed time with reference to #globalEntryTimeMs. + * @param[in] pNetworkContext Pointer to the network context created using Openssl_Connect. * - * @return Time in milliseconds. + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. */ -static uint32_t getTimeMs( void ); +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ); /** * @brief The function to handle the incoming publishes. @@ -258,22 +245,22 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, * @brief The application callback function for getting the incoming publish * and incoming acks reported from MQTT library. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. * @param[in] packetIdentifier Packet identifier of the incoming packet. * @param[in] pPublishInfo Deserialized publish info pointer for the incoming * packet. */ -static void eventCallback( MQTTContext_t * pContext, +static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ); /** - * @brief Sends an MQTT CONNECT packet over the already connected TCP socket.. + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. * - * @param[in] pContext MQTT context pointer. - * @param[in] pSslContext SSL context. + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pNetworkContext Pointer to the network context created using Openssl_Connect. * @param[in] createCleanSession Creates a new MQTT session if true. * If false, tries to establish the existing session if there was session * already present in broker. @@ -283,60 +270,60 @@ static void eventCallback( MQTTContext_t * pContext, * @return EXIT_SUCCESS if an MQTT session is established; * EXIT_FAILURE otherwise. */ -static int establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext, +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext, bool createCleanSession, bool * pSessionPresent ); /** * @brief Close an MQTT session by sending MQTT DISCONNECT. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if DISCONNECT was successfully sent; * EXIT_FAILURE otherwise. */ -static int disconnectMqttSession( MQTTContext_t * pContext ); +static int disconnectMqttSession( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT SUBSCRIBE to subscribe to #MQTT_EXAMPLE_TOPIC * defined at the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static int subscribeToTopic( MQTTContext_t * pContext ); +static int subscribeToTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT UNSUBSCRIBE to unsubscribe from * #MQTT_EXAMPLE_TOPIC defined at the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ); +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at * the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if PUBLISH was successfully sent; * EXIT_FAILURE otherwise. */ -static int publishToTopic( MQTTContext_t * pContext ); +static int publishToTopic( MQTTContext_t * pMqttContext ); /** * @brief Function to get the free index at which an outgoing publish * can be stored. * - * @param[out] pIndex The pointer to index at which an outgoing publish message - * can be stored. + * @param[out] pIndex The output parameter to return the index at which an + * outgoing publish message can be stored. * * @return EXIT_FAILURE if no more publishes can be stored; * EXIT_SUCCESS if an index to store the next outgoing publish is obtained. @@ -355,7 +342,7 @@ static void cleanupOutgoingPublishAt( uint8_t index ); * @brief Function to clean up all the outgoing publishes maintained in the * array. */ -static void cleanupOutgoingPublishes(); +static void cleanupOutgoingPublishes( void ); /** * @brief Function to clean up the publish packet with the given packet id. @@ -370,367 +357,93 @@ static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ); * the broker. This function handles the resending of the QoS2 publish packets, * which are maintained locally. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. */ -static int handlePublishResend( MQTTContext_t * pContext ); +static int handlePublishResend( MQTTContext_t * pMqttContext ); /*-----------------------------------------------------------*/ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ) +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS; - struct addrinfo hints, * pIndex, * pListHead = NULL; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - struct timeval transportTimeout; - - /* Add hints to retrieve only TCP sockets in getaddrinfo. */ - ( void ) memset( &hints, 0x00, sizeof( hints ) ); - /* Address family of either IPv4 or IPv6. */ - hints.ai_family = AF_UNSPEC; - /* TCP Socket. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, &hints, &pListHead ); - - if( status != -1 ) - { - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) - { - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - } - - status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( status == -1 ) - { - close( *pTcpSocket ); - } - else - { - break; - } - } - - if( pIndex == NULL ) - { - /* Fail if no connection could be established. */ - status = EXIT_FAILURE; - LogError( ( "Located but could not connect to MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - } - else - { - status = EXIT_SUCCESS; - LogInfo( ( "TCP connection established with %.*s.\n\n", - ( int ) strlen( pServer ), - pServer ) ); - } - } - else - { - LogError( ( "Could not locate MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - status = EXIT_FAILURE; - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static int tlsSetup( int tcpSocket, - SSL ** pSslContext ) -{ - int status = EXIT_FAILURE, sslStatus = 0; - FILE * pRootCaFile = NULL; - X509 * pRootCa = NULL; - - assert( tcpSocket >= 0 ); - - /* Setup for creating a TLS client. */ - SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); - - if( pSslSetup != NULL ) - { - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. - * The mask returned by SSL_CTX_set_mode does not need to be checked. */ - ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); - - /* OpenSSL does not provide a single function for reading and loading certificates - * from files into stores, so the file API must be called. */ - pRootCaFile = fopen( SERVER_CERT_PATH, "r" ); - - if( pRootCaFile != NULL ) - { - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - } - else - { - LogError( ( "Unable to find the certificate file in the path" - " provided by SERVER_CERT_PATH(%.*s).", - SERVER_CERT_PATH_LENGTH, - SERVER_CERT_PATH ) ); - } - - if( pRootCa != NULL ) - { - sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), - pRootCa ); - } - else - { - LogError( ( "Failed to parse the server certificate from" - " file %.*s. Please validate the certificate.", - SERVER_CERT_PATH_LENGTH, - SERVER_CERT_PATH ) ); - } - } - - /* Set up the TLS connection. */ - if( sslStatus == 1 ) + int returnStatus = EXIT_SUCCESS; + bool retriesArePending = true; + OpensslStatus_t opensslStatus = OPENSSL_SUCCESS; + TransportReconnectParams_t reconnectParams; + ServerInfo_t serverInfo; + OpensslCredentials_t opensslCredentials; + + /* Initialize information to connect to the MQTT broker. */ + serverInfo.pHostName = BROKER_ENDPOINT; + serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; + serverInfo.port = BROKER_PORT; + + /* Initialize credentials for establishing TLS session. */ + memset( &opensslCredentials, 0, sizeof( OpensslCredentials_t ) ); + opensslCredentials.pRootCaPath = ROOT_CA_CERT_PATH; + + /* Initialize reconnect attempts and interval */ + Transport_ReconnectParamsReset( &reconnectParams ); + + /* Attempt to connect to MQTT broker. If connection fails, retry after + * a timeout. Timeout value will exponentially increase till maximum + * attemps are reached. + */ + do { - /* Create a new SSL context. */ - *pSslContext = SSL_new( pSslSetup ); - - if( *pSslContext != NULL ) - { - /* Enable SSL peer verification. */ - SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); - - sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); - } - else - { - LogError( ( "Failed to create a new SSL context." ) ); - sslStatus = 0; - } - - /* Perform the TLS handshake. */ - if( sslStatus == 1 ) - { - sslStatus = SSL_connect( *pSslContext ); - } - else - { - LogError( ( "Failed to set the socket fd to SSL context." ) ); - } - - if( sslStatus == 1 ) - { - if( SSL_get_verify_result( *pSslContext ) != X509_V_OK ) - { - sslStatus = 0; - } - } - else + /* Establish a TLS session with the MQTT broker. This example connects + * to the MQTT broker as specified in AWS_IOT_ENDPOINT and AWS_MQTT_PORT at + * the top of this file. */ + LogInfo( ( "Establishing a TLS session to %.*s:%d.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT, + BROKER_PORT ) ); + opensslStatus = Openssl_Connect( pNetworkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( opensslStatus != OPENSSL_SUCCESS ) { - LogError( ( "Failed to perform TLS handshake." ) ); + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter." ) ); + retriesArePending = Transport_ReconnectBackoffAndSleep( &reconnectParams ); } - /* Clean up on error. */ - if( sslStatus == 0 ) + if( retriesArePending == false ) { - SSL_free( *pSslContext ); - *pSslContext = NULL; + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + returnStatus = EXIT_FAILURE; } - } - else - { - LogError( ( "Failed to add certificate to store." ) ); - } - - if( pRootCaFile != NULL ) - { - ( void ) fclose( pRootCaFile ); - } - - if( pRootCa != NULL ) - { - X509_free( pRootCa ); - } - - if( pSslSetup != NULL ) - { - SSL_CTX_free( pSslSetup ); - } - - /* Log failure or success and update the correct exit status to return. */ - if( sslStatus == 0 ) - { - LogError( ( "Failed to establish a TLS connection to %.*s.", - BROKER_ENDPOINT_LENGTH, - BROKER_ENDPOINT ) ); - status = EXIT_FAILURE; - } - else - { - LogInfo( ( "Established a TLS connection to %.*s.\n\n", - BROKER_ENDPOINT_LENGTH, - BROKER_ENDPOINT ) ); - status = EXIT_SUCCESS; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportSend( NetworkContext_t pSslContext, - const void * pMessage, - size_t bytesToSend ) -{ - int32_t bytesSent = 0; - int pollStatus = 0; - struct pollfd fileDescriptor = - { - .events = POLLOUT, - .revents = 0 - }; - - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pSslContext ); + } while( ( opensslStatus != OPENSSL_SUCCESS ) && ( retriesArePending == true ) ); - /* Poll the file descriptor to check if SSL_Write can be done now. */ - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - - if( pollStatus > 0 ) - { - bytesSent = ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); - } - else if( pollStatus == 0 ) - { - LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); - } - else - { - LogError( ( "Polling of the SSL socket for write buffer availability failed" - " with status %d.", - pollStatus ) ); - bytesSent = -1; - } - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportRecv( NetworkContext_t pSslContext, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - int pollStatus = -1, bytesAvailableToRead = 0; - struct pollfd fileDescriptor = - { - .events = POLLIN | POLLPRI, - .revents = 0 - }; - - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pSslContext ); - - /* Check if there are any pending data available for read. */ - bytesAvailableToRead = SSL_pending( pSslContext ); - - /* Poll only if there is no data available yet to read. */ - if( bytesAvailableToRead <= 0 ) - { - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - } - - /* SSL read of data. */ - if( ( pollStatus > 0 ) || ( bytesAvailableToRead > 0 ) ) - { - bytesReceived = ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); - } - /* Poll timed out. */ - else if( pollStatus == 0 ) - { - LogDebug( ( "Poll timed out and there is no data to read from the buffer." ) ); - } - else - { - LogError( ( "Poll returned with status = %d.", pollStatus ) ); - bytesReceived = -1; - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -static uint32_t getTimeMs( void ) -{ - uint32_t timeMs; - struct timespec timeSpec; - - /* Get the MONOTONIC time. */ - clock_gettime( CLOCK_MONOTONIC, &timeSpec ); - - /* Calculate the milliseconds from timespec. */ - timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) - + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); - - /* Reduce globalEntryTime from obtained time so as to always return the - * elapsed time in the application. */ - timeMs = ( uint32_t ) ( timeMs - globalEntryTimeMs ); - - return timeMs; + return returnStatus; } /*-----------------------------------------------------------*/ static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) { - int status = EXIT_FAILURE; + int returnStatus = EXIT_FAILURE; + uint8_t index = 0; assert( outgoingPublishPackets != NULL ); + assert( pIndex != NULL ); - for( *pIndex = 0; *pIndex < MAX_OUTGOING_PUBLISHES; ( *pIndex )++ ) + for( index = 0; index < MAX_OUTGOING_PUBLISHES; index++ ) { /* A free index is marked by invalid packet id. * Check if the the index has a free slot. */ - if( outgoingPublishPackets[ *pIndex ].packetId == MQTT_PACKET_ID_INVALID ) + if( outgoingPublishPackets[ index ].packetId == MQTT_PACKET_ID_INVALID ) { - status = EXIT_SUCCESS; + returnStatus = EXIT_SUCCESS; break; } } - return status; + /* Copy the available index into the output param. */ + *pIndex = index; + + return returnStatus; } /*-----------------------------------------------------------*/ @@ -747,17 +460,12 @@ static void cleanupOutgoingPublishAt( uint8_t index ) /*-----------------------------------------------------------*/ -static void cleanupOutgoingPublishes() +static void cleanupOutgoingPublishes( void ) { - uint8_t index = 0; - assert( outgoingPublishPackets != NULL ); - /* Clean up all the saved outgoing publishes. */ - for( ; index < MAX_OUTGOING_PUBLISHES; index++ ) - { - cleanupOutgoingPublishAt( index ); - } + /* Clean up all the outgoing publish packets. */ + ( void ) memset( outgoingPublishPackets, 0x00, sizeof( outgoingPublishPackets ) ); } /*-----------------------------------------------------------*/ @@ -784,9 +492,9 @@ static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ) /*-----------------------------------------------------------*/ -static int handlePublishResend( MQTTContext_t * pContext ) +static int handlePublishResend( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus = MQTTSuccess; uint8_t index = 0U; @@ -803,7 +511,7 @@ static int handlePublishResend( MQTTContext_t * pContext ) LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", outgoingPublishPackets[ index ].packetId ) ); - mqttStatus = MQTT_Publish( pContext, + mqttStatus = MQTT_Publish( pMqttContext, &outgoingPublishPackets[ index ].pubInfo, outgoingPublishPackets[ index ].packetId ); @@ -813,7 +521,7 @@ static int handlePublishResend( MQTTContext_t * pContext ) " failed with status %u.", outgoingPublishPackets[ index ].packetId, mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; break; } else @@ -824,7 +532,7 @@ static int handlePublishResend( MQTTContext_t * pContext ) } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ @@ -834,21 +542,23 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, { assert( pPublishInfo != NULL ); + ( void ) packetIdentifier; + /* Process incoming Publish. */ LogInfo( ( "Incoming QOS : %d.", pPublishInfo->qos ) ); - /* Verify the received publish is for the we have subscribed to. */ + /* Verify the received publish is for the topic we have subscribed to. */ if( ( pPublishInfo->topicNameLength == MQTT_EXAMPLE_TOPIC_LENGTH ) && ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, pPublishInfo->pTopicName, pPublishInfo->topicNameLength ) ) ) { - LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.", + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n" + "Incoming Publish message Packet Id is %u.\n" + "Incoming Publish Message : %.*s.\n\n", pPublishInfo->topicNameLength, - pPublishInfo->pTopicName ) ); - LogInfo( ( "Incoming Publish message Packet Id is %u.", - packetIdentifier ) ); - LogInfo( ( "Incoming Publish Message : %.*s.\n\n", + pPublishInfo->pTopicName, + packetIdentifier, ( int ) pPublishInfo->payloadLength, ( const char * ) pPublishInfo->pPayload ) ); } @@ -862,12 +572,12 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ -static void eventCallback( MQTTContext_t * pContext, +static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { - assert( pContext != NULL ); + assert( pMqttContext != NULL ); assert( pPacketInfo != NULL ); /* Handle incoming publish. The lower 4 bits of the publish packet @@ -904,11 +614,12 @@ static void eventCallback( MQTTContext_t * pContext, /* Nothing to be done from application as library handles * PINGRESP. */ - LogInfo( ( "PINGRESP received.\n\n" ) ); + LogWarn( ( "PINGRESP should not be handled by the application " + "callback when using MQTT_ProcessLoop.\n\n" ) ); break; case MQTT_PACKET_TYPE_PUBREC: - LogInfo( ( "PUBREC received for packet id %u.", + LogInfo( ( "PUBREC received for packet id %u.\n\n", packetIdentifier ) ); /* Cleanup publish packet when a PUBREC is received. */ cleanupOutgoingPublishWithPacketID( packetIdentifier ); @@ -932,7 +643,7 @@ static void eventCallback( MQTTContext_t * pContext, /* Any other packet type is invalid. */ default: - LogError( ( "Unknown packet type received:(%02x).", + LogError( ( "Unknown packet type received:(%02x).\n\n", pPacketInfo->type ) ); } } @@ -940,30 +651,27 @@ static void eventCallback( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ -static int establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext, +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext, bool createCleanSession, bool * pSessionPresent ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; - MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + TransportInterface_t transport; - assert( pContext != NULL ); - assert( pSslContext != NULL ); - - /* The network buffer must remain valid for the lifetime of the MQTT context. */ - static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + assert( pMqttContext != NULL ); + assert( pNetworkContext != NULL ); /* Fill in TransportInterface send and receive function pointers. * For this demo, TCP sockets are used to send and receive data - * from network. Network context is socket file descriptor.*/ - transport.networkContext = pSslContext; - transport.send = transportSend; - transport.recv = transportRecv; + * from network. Network context is SSL context for OpenSSL.*/ + transport.pNetworkContext = pNetworkContext; + transport.send = Openssl_Send; + transport.recv = Openssl_Recv; /* Fill the values for network buffer. */ networkBuffer.pBuffer = buffer; @@ -975,19 +683,19 @@ static int establishMqttSession( MQTTContext_t * pContext, /* Application callback for getting the time for MQTT library. This time * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = getTimeMs; + callbacks.getTime = Clock_GetTimeMs; /* Initialize MQTT library. */ - mqttStatus = MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( pMqttContext, &transport, &callbacks, &networkBuffer ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); } else { - /* Establish MQTT session with a CONNECT packet. */ + /* Establish MQTT session by sending a CONNECT packet. */ /* If #createCleanSession is true, start with a clean session * i.e. direct the MQTT broker to discard any previous session data. @@ -1001,7 +709,12 @@ static int establishMqttSession( MQTTContext_t * pContext, connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ + /* The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. */ connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; /* Username and password for authentication. Not used in this demo. */ @@ -1011,11 +724,11 @@ static int establishMqttSession( MQTTContext_t * pContext, connectInfo.passwordLength = 0U; /* Send MQTT CONNECT packet to broker. */ - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); + mqttStatus = MQTT_Connect( pMqttContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); } else @@ -1024,39 +737,40 @@ static int establishMqttSession( MQTTContext_t * pContext, } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int disconnectMqttSession( MQTTContext_t * pContext ) +static int disconnectMqttSession( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; + int returnStatus = EXIT_SUCCESS; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Send DISCONNECT. */ - MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); + mqttStatus = MQTT_Disconnect( pMqttContext ); if( mqttStatus != MQTTSuccess ) { LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int subscribeToTopic( MQTTContext_t * pContext ) +static int subscribeToTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Start with everything at 0. */ ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); @@ -1067,10 +781,10 @@ static int subscribeToTopic( MQTTContext_t * pContext ) pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; /* Generate packet identifier for the SUBSCRIBE packet. */ - globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + globalSubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); /* Send SUBSCRIBE packet. */ - mqttStatus = MQTT_Subscribe( pContext, + mqttStatus = MQTT_Subscribe( pMqttContext, pSubscriptionList, sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), globalSubscribePacketIdentifier ); @@ -1079,7 +793,7 @@ static int subscribeToTopic( MQTTContext_t * pContext ) { LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { @@ -1088,18 +802,18 @@ static int subscribeToTopic( MQTTContext_t * pContext ) MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Start with everything at 0. */ ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); @@ -1111,10 +825,10 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; /* Generate packet identifier for the UNSUBSCRIBE packet. */ - globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); /* Send UNSUBSCRIBE packet. */ - mqttStatus = MQTT_Unsubscribe( pContext, + mqttStatus = MQTT_Unsubscribe( pMqttContext, pSubscriptionList, sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), globalUnsubscribePacketIdentifier ); @@ -1123,7 +837,7 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) { LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { @@ -1132,26 +846,26 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int publishToTopic( MQTTContext_t * pContext ) +static int publishToTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; - MQTTStatus_t mqttSuccess; + int returnStatus = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; uint8_t publishIndex = MAX_OUTGOING_PUBLISHES; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Get the next free index for the outgoing publish. All QoS2 outgoing * publishes are stored until a PUBREC is received. These messages are * stored for supporting a resend if a network connection is broken before * receiving a PUBREC. */ - status = getNextFreeIndexForOutgoingPublishes( &publishIndex ); + returnStatus = getNextFreeIndexForOutgoingPublishes( &publishIndex ); - if( status == EXIT_FAILURE ) + if( returnStatus == EXIT_FAILURE ) { LogError( ( "Unable to find a free spot for outgoing PUBLISH message.\n\n" ) ); } @@ -1165,101 +879,61 @@ static int publishToTopic( MQTTContext_t * pContext ) outgoingPublishPackets[ publishIndex ].pubInfo.payloadLength = MQTT_EXAMPLE_MESSAGE_LENGTH; /* Get a new packet id. */ - outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pContext ); + outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pMqttContext ); /* Send PUBLISH packet. */ - mqttSuccess = MQTT_Publish( pContext, - &outgoingPublishPackets[ publishIndex ].pubInfo, - outgoingPublishPackets[ publishIndex ].packetId ); + mqttStatus = MQTT_Publish( pMqttContext, + &outgoingPublishPackets[ publishIndex ].pubInfo, + outgoingPublishPackets[ publishIndex ].packetId ); - if( status != MQTTSuccess ) + if( mqttStatus != MQTTSuccess ) { LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", - mqttSuccess ) ); + mqttStatus ) ); cleanupOutgoingPublishAt( publishIndex ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { - LogInfo( ( "PUBLISH send for topic %.*s to broker with packet id %u.\n\n", + LogInfo( ( "PUBLISH sent for topic %.*s to broker with packet ID %u.\n\n", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC, outgoingPublishPackets[ publishIndex ].packetId ) ); } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -/** - * @brief Entry point of demo. - * - * The example shown below uses MQTT APIs to send and receive MQTT packets - * over the TLS connection established using openSSL. - * - * The example is single threaded and uses statically allocated memory; - * it uses QOS2 and therefore implements a retransmission mechanism - * for Publish messages. Retransmission of publish messages are attempted - * when a MQTT connection is established with a session that was already - * present. All the outgoing publish messages waiting to receive PUBREC - * are resend in this demo. In order to support retransmission all the outgoing - * publishes are stored until a PUBREC is received. - */ -int main( int argc, - char ** argv ) +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS, tcpSocket; - bool sessionPresent, mqttSessionEstablished = false; - MQTTContext_t context; + int returnStatus = EXIT_SUCCESS; + bool mqttSessionEstablished = false, sessionPresent; + MQTTContext_t mqttContext; MQTTStatus_t mqttStatus = MQTTSuccess; - SSL * pSslContext = NULL; uint32_t publishCount = 0; - const uint32_t maxPublishCount = 5U; - - ( void ) argc; - ( void ) argv; + const uint32_t maxPublishCount = MQTT_PUBLISH_COUNT_PER_LOOP; - /* Get the entry time to application. */ - globalEntryTimeMs = getTimeMs(); - - /* Establish a TCP connection with the MQTT broker. This example connects - * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at - * the top of this file. */ - LogInfo( ( "Creating a TCP connection to %.*s:%d.", + /* Establish MQTT session on top of TCP+TLS connection. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", BROKER_ENDPOINT_LENGTH, - BROKER_ENDPOINT, - BROKER_PORT ) ); - status = connectToServer( BROKER_ENDPOINT, BROKER_PORT, &tcpSocket ); + BROKER_ENDPOINT ) ); - /* Establish TLS connection on top of TCP connection. */ - if( status == EXIT_SUCCESS ) - { - LogInfo( ( "Creating a TLS connection on top of the TCP connection." ) ); - status = tlsSetup( tcpSocket, &pSslContext ); - } + /* Sends an MQTT Connect packet using the established TLS session, + * then waits for connection acknowledgment (CONNACK) packet. */ + returnStatus = establishMqttSession( &mqttContext, pNetworkContext, false, &sessionPresent ); - /* Establish MQTT session on top of TCP+TLS connection. */ - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { - /* Sends an MQTT Connect packet over the already connected TCP socket - * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ - LogInfo( ( "Creating an MQTT connection to %.*s.", - BROKER_ENDPOINT_LENGTH, - BROKER_ENDPOINT ) ); - status = establishMqttSession( &context, pSslContext, false, &sessionPresent ); - - if( status == EXIT_SUCCESS ) - { - /* Keep a flag for indicating if MQTT session is established. This - * flag will mark that an MQTT DISCONNECT has to be send at the end - * of the demo even if there are intermediate failures. */ - mqttSessionEstablished = true; - } + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be sent at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Check if session is present and if there are any outgoing publishes * that need to resend. This is only valid if the broker is @@ -1270,7 +944,7 @@ int main( int argc, "Resending unacked publishes." ) ); /* Handle all the resend of publish messages. */ - status = handlePublishResend( &context ); + returnStatus = handlePublishResend( &mqttContext ); } else { @@ -1283,7 +957,7 @@ int main( int argc, } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* The client is now connected to the broker. Subscribe to the topic * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a @@ -1294,29 +968,29 @@ int main( int argc, LogInfo( ( "Subscribing to the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = subscribeToTopic( &context ); + returnStatus = subscribeToTopic( &mqttContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Process incoming packet from the broker. Acknowledgment for subscription * ( SUBACK ) will be received here. However after sending the subscribe, the * client may receive a publish before it receives a subscribe ack. Since this * demo is subscribing to the topic to which no one is publishing, probability * of receiving publish message before subscribe ack is zero; but application - * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to * receive packet from network. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT_ProcessLoop returned with status = %u.", mqttStatus ) ); } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Publish messages with QOS2, receive incoming messages and * send keep alive messages. */ @@ -1325,7 +999,7 @@ int main( int argc, LogInfo( ( "Sending Publish to the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = publishToTopic( &context ); + returnStatus = publishToTopic( &mqttContext ); /* Calling MQTT_ProcessLoop to process incoming publish echo, since * application subscribed to the same topic the broker will send @@ -1333,7 +1007,7 @@ int main( int argc, * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS * has expired since the last MQTT packet sent and receive * ping responses. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { @@ -1348,23 +1022,23 @@ int main( int argc, } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Unsubscribe from the topic. */ LogInfo( ( "Unsubscribing from the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = unsubscribeFromTopic( &context ); + returnStatus = unsubscribeFromTopic( &mqttContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Process Incoming UNSUBACK packet from the broker. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT_ProcessLoop returned with status = %u.", mqttStatus ) ); } @@ -1375,45 +1049,87 @@ int main( int argc, * disconnect, client must close the network connection. */ if( mqttSessionEstablished == true ) { - if( status == EXIT_FAILURE ) + LogInfo( ( "Disconnecting the MQTT connection with %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + + if( returnStatus == EXIT_FAILURE ) { /* Returned status is not used to update the local status as there * were failures in demo execution. */ - ( void ) disconnectMqttSession( &context ); + ( void ) disconnectMqttSession( &mqttContext ); } else { - status = disconnectMqttSession( &context ); + returnStatus = disconnectMqttSession( &mqttContext ); } } - /* Close TLS session if established. */ - if( pSslContext != NULL ) + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TLS connection established using OpenSSL. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS2 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBREC + * are resent in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBREC is received. + */ +int main( int argc, + char ** argv ) +{ + int returnStatus = EXIT_SUCCESS; + NetworkContext_t networkContext; + + ( void ) argc; + ( void ) argv; + + for( ; ; ) { - /* SSL shutdown should be called twice: once to send "close notify" and - * once more to receive the peer's "close notify". */ - if( SSL_shutdown( pSslContext ) == 0 ) + /* Attempt to connect to the MQTT broker. If connection fails, retry after + * a timeout. Timeout value will be exponentially increased till the maximum + * attemps are reached or maximum timout value is reached. The function + * returns EXIT_FAILURE if the TCP connection cannot be established to + * broker after configured number of attemps. */ + returnStatus = connectToServerWithBackoffRetries( &networkContext ); + + if( returnStatus == EXIT_FAILURE ) + { + /* Log error to indicate connection failure after all + * reconnect attempts are over. */ + LogError( ( "Failed to connect to MQTT broker %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + } + else { - ( void ) SSL_shutdown( pSslContext ); + /* If TLS session is established, execute Subscribe/Publish loop. */ + returnStatus = subscribePublishLoop( &networkContext ); } - SSL_free( pSslContext ); - } + if( returnStatus == EXIT_SUCCESS ) + { + /* Log message indicating an iteration completed successfully. */ + LogInfo( ( "Demo completed successfully." ) ); + } - /* Close TCP connection if established. */ - if( tcpSocket != -1 ) - { - shutdown( tcpSocket, SHUT_RDWR ); - close( tcpSocket ); - } + /* End TLS session, then close TCP connection. */ + ( void ) Openssl_Disconnect( &networkContext ); - /* Log the success message. */ - if( status == EXIT_SUCCESS ) - { - LogInfo( ( "Demo completed successfully." ) ); + LogInfo( ( "Short delay before starting the next iteration....\n" ) ); + sleep( MQTT_SUBPUB_LOOP_DELAY_SECONDS ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt b/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt index 9e9c64080f..928c71ff3c 100644 --- a/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt @@ -6,13 +6,15 @@ target_sources( ${DEMO_NAME} PRIVATE "${DEMO_NAME}.c" - ${LOGGING_SOURCES} ) target_link_libraries( ${DEMO_NAME} PRIVATE - mqtt + mqtt + clock_posix + plaintext_posix + transport_reconnect_posix ) target_include_directories( diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h index d17af869c3..da90a63df9 100644 --- a/demos/mqtt/mqtt_demo_lightweight/demo_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -47,11 +47,31 @@ /************ End of logging configuration ****************/ +/** + * @brief MQTT server host name. + * + * This demo uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + */ +#define BROKER_ENDPOINT "test.mosquitto.org" + +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) + +/** + * @brief MQTT server port number. + * + * In general, port 1883 is for unsecured MQTT connections. + */ +#define BROKER_PORT 1883 + /** * @brief MQTT client identifier. * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER "testclient" #endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h index 50b5e6dbff..fef50d6e03 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_config.h @@ -45,9 +45,7 @@ #endif #include "logging_stack.h" -/************ End of logging configuration ****************/ -/* Set network context to socket (int). */ -typedef int NetworkContext_t; +/************ End of logging configuration ****************/ #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index 68b3d44305..c5b5dfbce0 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -38,41 +38,22 @@ #include "demo_config.h" /* Standard includes. */ +#include #include #include -/* POSIX socket includes. */ -#include -#include -#include -#include +/* POSIX includes. */ #include - -#include -#include +#include /* MQTT LightWeight API header. */ #include "mqtt_lightweight.h" -/** - * @brief MQTT server host name. - * - * This demo uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. - */ -#define MQTT_BROKER_ENDPOINT "test.mosquitto.org" +/* Plaintext transport implementation. */ +#include "plaintext_posix.h" -/** - * @brief MQTT server port number. - * - * In general, port 1883 is for unsecured MQTT connections. - */ -#define MQTT_BROKER_PORT 1883 - -/** - * @brief Size of the network buffer for MQTT packets. - */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +/* Reconnect parameters. */ +#include "transport_reconnect.h" /* Check that client identifier is defined. */ #ifndef CLIENT_IDENTIFIER @@ -85,139 +66,138 @@ * The topic name starts with the client identifier to ensure that each demo * interacts with a unique topic name. */ -#define MQTT_EXAMPLE_TOPIC CLIENT_IDENTIFIER "/example/topic" +#define MQTT_EXAMPLE_TOPIC CLIENT_IDENTIFIER "/example/topic" + +/** + * @brief Length of client MQTT topic. + */ +#define MQTT_EXAMPLE_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_TOPIC ) - 1 ) ) /** - * @brief Dimensions a file scope buffer currently used to send and receive MQTT data - * from a socket. + * @brief Size of the network buffer for MQTT packets. */ -#define SHARED_BUFFER_SIZE 500U +#ifndef NETWORK_BUFFER_SIZE + #define NETWORK_BUFFER_SIZE ( 1024U ) +#endif /** * @brief The MQTT message published in this example. */ -#define MQTT_EXAMPLE_MESSAGE "Hello Light Weight MQTT World!" +#define MQTT_EXAMPLE_MESSAGE "Hello World!" /** * @brief Keep alive period in seconds for MQTT connection. */ -#define MQTT_KEEP_ALIVE_PERIOD_SECONDS 5U +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 5U ) /** * @brief Socket layer transportTimeout in milliseconds. */ -#define TRANSPORT_SEND_RECV_TIMEOUT_MS 200U +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200U ) /** * @brief Number of time network receive will be attempted * if it fails due to transportTimeout. */ -#define MQTT_MAX_RECV_ATTEMPTS 10U +#define MQTT_MAX_RECV_ATTEMPTS ( 10U ) /** * @brief Delay between two demo iterations. */ -#define MQTT_DEMO_ITERATION_DELAY_SECONDS 5U +#define MQTT_DEMO_ITERATION_DELAY_SECONDS ( 5U ) /*-----------------------------------------------------------*/ /** - * @brief Establish a TCP connection to the given server. + * @brief Connect to MQTT broker with reconnection retries. + * + * If connection fails, retry is attempted after a timeout. + * Timeout value will exponentially increase until until maximum reconnection + * backoff time is reached or the number of attempts are exhausted. * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pSocket pointer to the socket descriptor if connect - * is successful, this call will return the socket descriptor. + * @param[out] pNetworkContext The output parameter to return the created network context. * - * @return EXIT_SUCCESS or EXIT_FAILURE. + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on successful connection. */ -static int connectToServer( const char * pServer, - uint16_t port, - int * pSocket ); +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ); /** * @brief Establish an MQTT session over a TCP connection by sending MQTT CONNECT. * - * @param[in] tcpSocket TCP socket. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used for serialzing CONNECT packet and deserializing CONN-ACK. * * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. */ -static int createMQTTConnectionWithBroker( int tcpSocket, +static int createMQTTConnectionWithBroker( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Subscribes to the topic as specified in MQTT_EXAMPLE_TOPIC at the top of * this file. * - * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which - * an MQTT connection has been established. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used for serialzing SUBSCRIBE packet. * */ -static void mqttSubscribeToTopic( int tcpSocket, +static void mqttSubscribeToTopic( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Publishes a message MQTT_EXAMPLE_MESSAGE on MQTT_EXAMPLE_TOPIC topic. * - * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which - * an MQTT connection has been established. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used for serialzing PUBLISH packet. * */ -static void mqttPublishToTopic( int tcpSocket, +static void mqttPublishToTopic( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Unsubscribes from the previously subscribed topic as specified * in MQTT_EXAMPLE_TOPIC. * - * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which - * an MQTT connection has been established. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used for serialzing UNSUBSCRIBE packet. * */ -static void mqttUnsubscribeFromTopic( int tcpSocket, +static void mqttUnsubscribeFromTopic( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Disconnect From the MQTT broker. * - * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which - * an MQTT connection has been established. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used for serialzing DISCONNECT packet. */ -static void mqttDisconnect( int tcpSocket, +static void mqttDisconnect( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Send Ping Request to the MQTT broker. * - * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which - * an MQTT connection has been established. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used for serialzing PING request packet. */ -static void mqttKeepAlive( int tcpSocket, +static void mqttKeepAlive( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Receive and validate MQTT packet from the broker, determine the type * of the packet and process the packet based on the type. * - * @param[in] tcpSocket is a TCP socket that is connected to an MQTT broker to which - * an MQTT connection has been established. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. * The buffer is used to deserialize incoming MQTT packet. * */ -static void mqttProcessIncomingPacket( int tcpSocket, +static void mqttProcessIncomingPacket( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); /** @@ -253,7 +233,7 @@ static void mqttProcessIncomingPublish( MQTTPublishInfo_t * pPubInfo, * * @note This function is not thread safe. */ -static uint16_t getNextPacketIdentifier(); +static uint16_t getNextPacketIdentifier( void ); /** * @brief Calculate the interval between two timestamps, including @@ -273,11 +253,10 @@ static uint32_t calculateElapsedTime( uint32_t later, /*-----------------------------------------------------------*/ -/* @brief errno to check transport error. */ -extern int errno; - -/* @brief Static buffer used to hold MQTT messages being sent and received. */ -static uint8_t mqttSharedBuffer[ SHARED_BUFFER_SIZE ]; +/** + * @brief Static buffer used to hold MQTT messages being sent and received. + */ +static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; /** * @brief Packet Identifier generated when Subscribe request was sent to the broker; @@ -294,7 +273,7 @@ static uint16_t unsubscribePacketIdentifier; /*-----------------------------------------------------------*/ -static uint16_t getNextPacketIdentifier() +static uint16_t getNextPacketIdentifier( void ) { static uint16_t packetId = 0; @@ -312,161 +291,67 @@ static uint16_t getNextPacketIdentifier() /*-----------------------------------------------------------*/ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ) +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS; - struct addrinfo * pListHead = NULL, * pIndex, hint; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - struct timeval transportTimeout; - - /* Set up hint structure, so that only TCP address structures are returned. */ - memset( ( void * ) &hint, 0x00, sizeof( hint ) ); - hint.ai_socktype = SOCK_STREAM; - /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, &hint, &pListHead ); - - if( status != -1 ) + int returnStatus = EXIT_SUCCESS; + bool retriesArePending = true; + SocketStatus_t socketStatus = SOCKETS_SUCCESS; + TransportReconnectParams_t reconnectParams; + ServerInfo_t serverInfo; + + /* Initialize information to connect to the MQTT broker. */ + serverInfo.pHostName = BROKER_ENDPOINT; + serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; + serverInfo.port = BROKER_PORT; + + /* Initialize reconnect attempts and interval */ + Transport_ReconnectParamsReset( &reconnectParams ); + + /* Attempt to connect to MQTT broker. If connection fails, retry after + * a timeout. Timeout value will exponentially increase till maximum + * attemps are reached. + */ + do { - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) + /* Establish a TCP connection with the MQTT broker. This example connects + * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT + * at the demo config header. */ + LogInfo( ( "Creating a TCP connection to %.*s:%d.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT, + BROKER_PORT ) ); + socketStatus = Plaintext_Connect( pNetworkContext, + &serverInfo, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( socketStatus != SOCKETS_SUCCESS ) { - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - } - - status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( status == -1 ) - { - close( *pTcpSocket ); - } - else - { - break; - } + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter." ) ); + retriesArePending = Transport_ReconnectBackoffAndSleep( &reconnectParams ); } - if( pIndex == NULL ) + if( retriesArePending == false ) { - /* Fail if no connection could be established. */ - LogError( ( "Failed to establish TCP connection to the broker %s.\n", pServer ) ); - status = EXIT_FAILURE; + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + returnStatus = EXIT_FAILURE; } - else - { - /* Set send and receive timeouts */ - transportTimeout.tv_sec = 0; - transportTimeout.tv_usec = ( TRANSPORT_SEND_RECV_TIMEOUT_MS * 1000 ); - - if( setsockopt( *pTcpSocket, - SOL_SOCKET, - SO_RCVTIMEO, - ( char * ) &transportTimeout, - sizeof( transportTimeout ) ) < 0 ) - { - LogError( ( "Setting socket receive transportTimeout failed \n" ) ); - status = EXIT_FAILURE; - } - - if( setsockopt( *pTcpSocket, - SOL_SOCKET, - SO_SNDTIMEO, - ( char * ) &transportTimeout, - sizeof( transportTimeout ) ) < 0 ) - { - LogError( ( "Setting socket send transportTimeout failed.\n" ) ); - status = EXIT_FAILURE; - } - } - } - else - { - LogError( ( "DNS lookup failed for broker %s.\n", pServer ) ); - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief The transport receive wrapper function supplied to the MQTT library for - * receiving type and length of an incoming MQTT packet. - * - * @param[in] tcpSocket TCP socket. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToRecv Size of pBuffer. - * - * @return Number of bytes received or zero to indicate transportTimeout; negative value on error. - */ -static int32_t transportRecv( NetworkContext_t tcpSocket, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - - bytesReceived = ( int32_t ) recv( tcpSocket, pBuffer, bytesToRecv, 0 ); - - if( bytesReceived == 0 ) - { - /* Server closed the connection, treat it as an error */ - bytesReceived = -1; - } - else if( bytesReceived < 0 ) - { - /* Check if it was time out */ - if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) - { - /* Set return value to 0 to indicate nothing to receive */ - bytesReceived = 0; - } - } - else - { - /* EMPTY else */ - } + } while( ( socketStatus != SOCKETS_SUCCESS ) && ( retriesArePending == true ) ); - return bytesReceived; + return returnStatus; } /*-----------------------------------------------------------*/ -static int createMQTTConnectionWithBroker( int tcpSocket, +static int createMQTTConnectionWithBroker( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { + int returnStatus = EXIT_SUCCESS; MQTTConnectInfo_t mqttConnectInfo; size_t remainingLength; size_t packetSize; MQTTStatus_t result; MQTTPacketInfo_t incomingPacket; - int status; unsigned short packetId = 0; bool sessionPresent = false; uint8_t receiveAttempts = 0; @@ -495,7 +380,7 @@ static int createMQTTConnectionWithBroker( int tcpSocket, /* Set MQTT keep-alive period. It is the responsibility of the application to ensure * that the interval between Control Packets being sent does not exceed the Keep Alive value. * In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet. */ - mqttConnectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_PERIOD_SECONDS; + mqttConnectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; /* Get size requirement for the connect packet */ result = MQTT_GetConnectPacketSize( &mqttConnectInfo, NULL, &remainingLength, &packetSize ); @@ -509,8 +394,8 @@ static int createMQTTConnectionWithBroker( int tcpSocket, assert( result == MQTTSuccess ); /* Send the serialized connect packet to the MQTT broker */ - status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); - assert( status == ( int ) packetSize ); + returnStatus = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, packetSize ); + assert( returnStatus == ( int ) packetSize ); /* Reset all fields of the incoming packet structure. */ memset( ( void * ) &incomingPacket, 0x00, sizeof( MQTTPacketInfo_t ) ); @@ -523,18 +408,18 @@ static int createMQTTConnectionWithBroker( int tcpSocket, */ do { - /* Since tcpSocket has timeout, retry until the data is available */ - result = MQTT_GetIncomingPacketTypeAndLength( transportRecv, tcpSocket, &incomingPacket ); + /* Since TCP socket has timeout, retry until the data is available */ + result = MQTT_GetIncomingPacketTypeAndLength( Plaintext_Recv, pNetworkContext, &incomingPacket ); receiveAttempts++; - } while ( ( result == MQTTNoDataAvailable ) && ( receiveAttempts < MQTT_MAX_RECV_ATTEMPTS ) ); + } while( ( result == MQTTNoDataAvailable ) && ( receiveAttempts < MQTT_MAX_RECV_ATTEMPTS ) ); assert( result == MQTTSuccess ); assert( incomingPacket.type == MQTT_PACKET_TYPE_CONNACK ); assert( incomingPacket.remainingLength <= pFixedBuffer->size ); /* Now receive the remaining packet into statically allocated buffer. */ - status = recv( tcpSocket, ( void * ) pFixedBuffer->pBuffer, incomingPacket.remainingLength, 0 ); - assert( status == ( int ) incomingPacket.remainingLength ); + returnStatus = Plaintext_Recv( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, incomingPacket.remainingLength ); + assert( returnStatus == ( int ) incomingPacket.remainingLength ); incomingPacket.pRemainingData = pFixedBuffer->pBuffer; @@ -545,20 +430,20 @@ static int createMQTTConnectionWithBroker( int tcpSocket, if( result != MQTTSuccess ) { LogError( ( "Connection with MQTT broker failed.\r\n" ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { LogInfo( ( "Successfully connected with the MQTT broker\r\n" ) ); - status = EXIT_SUCCESS; + returnStatus = EXIT_SUCCESS; } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static void mqttSubscribeToTopic( int tcpSocket, +static void mqttSubscribeToTopic( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t result; @@ -566,7 +451,6 @@ static void mqttSubscribeToTopic( int tcpSocket, size_t remainingLength; size_t packetSize; int status; - MQTTFixedBuffer_t fixedBuffer; /*** * For readability, error handling in this function is restricted to the use of @@ -601,12 +485,12 @@ static void mqttSubscribeToTopic( int tcpSocket, assert( result == MQTTSuccess ); /* Send Subscribe request to the broker. */ - status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + status = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, packetSize ); assert( status == ( int ) packetSize ); } /*-----------------------------------------------------------*/ -static void mqttPublishToTopic( int tcpSocket, +static void mqttPublishToTopic( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t result; @@ -614,7 +498,6 @@ static void mqttPublishToTopic( int tcpSocket, size_t remainingLength; size_t packetSize = 0; size_t headerSize = 0; - uint8_t * pPacketIdentifierHigh; int status; /*** @@ -652,15 +535,15 @@ static void mqttPublishToTopic( int tcpSocket, headerSize ) ); assert( result == MQTTSuccess ); /* Send Publish header to the broker. */ - status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, headerSize, 0 ); + status = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, headerSize ); assert( status == ( int ) headerSize ); /* Send Publish payload to the broker */ - status = send( tcpSocket, ( void * ) mqttPublishInfo.pPayload, mqttPublishInfo.payloadLength, 0 ); + status = Plaintext_Send( pNetworkContext, ( void * ) mqttPublishInfo.pPayload, mqttPublishInfo.payloadLength ); assert( status == ( int ) mqttPublishInfo.payloadLength ); } /*-----------------------------------------------------------*/ -static void mqttUnsubscribeFromTopic( int tcpSocket, +static void mqttUnsubscribeFromTopic( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t result; @@ -696,12 +579,12 @@ static void mqttUnsubscribeFromTopic( int tcpSocket, assert( result == MQTTSuccess ); /* Send Unsubscribe request to the broker. */ - status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + status = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, packetSize ); assert( status == ( int ) packetSize ); } /*-----------------------------------------------------------*/ -static void mqttKeepAlive( int tcpSocket, +static void mqttKeepAlive( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t result; @@ -718,13 +601,13 @@ static void mqttKeepAlive( int tcpSocket, assert( result == MQTTSuccess ); /* Send Ping Request to the broker. */ - status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + status = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, packetSize ); assert( status == ( int ) packetSize ); } /*-----------------------------------------------------------*/ -static void mqttDisconnect( int tcpSocket, +static void mqttDisconnect( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t result; @@ -739,7 +622,7 @@ static void mqttDisconnect( int tcpSocket, assert( result == MQTTSuccess ); /* Send disconnect packet to the broker */ - status = send( tcpSocket, ( void * ) pFixedBuffer->pBuffer, packetSize, 0 ); + status = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, packetSize ); assert( status == ( int ) packetSize ); } @@ -776,26 +659,28 @@ static void mqttProcessResponse( MQTTPacketInfo_t * pIncomingPacket, /*-----------------------------------------------------------*/ static void mqttProcessIncomingPublish( MQTTPublishInfo_t * pPubInfo, - uint16_t packetId ) + uint16_t packetIdentifier ) { assert( pPubInfo != NULL ); /* Since this example does not make use of QOS1 or QOS2, * packet identifier is not required. */ - ( void ) packetId; + ( void ) packetIdentifier; LogInfo( ( "Incoming QOS : %d\n", pPubInfo->qos ) ); /* Verify the received publish is for the topic we have subscribed to. */ - if( ( pPubInfo->topicNameLength == strlen( MQTT_EXAMPLE_TOPIC ) ) && + if( ( pPubInfo->topicNameLength == MQTT_EXAMPLE_TOPIC_LENGTH ) && ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, pPubInfo->pTopicName, pPubInfo->topicNameLength ) ) ) { - LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n", + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n" + "Incoming Publish message Packet ID is %u.\n" + "Incoming Publish Message : %.*s.\n\n", pPubInfo->topicNameLength, - pPubInfo->pTopicName ) ); - LogInfo( ( "Incoming Publish Message : %.*s\n", - pPubInfo->payloadLength, - pPubInfo->pPayload ) ); + pPubInfo->pTopicName, + packetIdentifier, + ( int ) pPubInfo->payloadLength, + ( const char * ) pPubInfo->pPayload ) ); } else { @@ -807,7 +692,7 @@ static void mqttProcessIncomingPublish( MQTTPublishInfo_t * pPubInfo, /*-----------------------------------------------------------*/ -static void mqttProcessIncomingPacket( int tcpSocket, +static void mqttProcessIncomingPacket( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t result; @@ -829,9 +714,9 @@ static void mqttProcessIncomingPacket( int tcpSocket, do { /* Retry till data is available */ - result = MQTT_GetIncomingPacketTypeAndLength( transportRecv, tcpSocket, &incomingPacket ); + result = MQTT_GetIncomingPacketTypeAndLength( Plaintext_Recv, pNetworkContext, &incomingPacket ); receiveAttempts++; - } while ( ( result == MQTTNoDataAvailable ) && ( receiveAttempts < MQTT_MAX_RECV_ATTEMPTS ) ); + } while( ( result == MQTTNoDataAvailable ) && ( receiveAttempts < MQTT_MAX_RECV_ATTEMPTS ) ); assert( result == MQTTSuccess ); assert( incomingPacket.remainingLength <= pFixedBuffer->size ); @@ -840,7 +725,7 @@ static void mqttProcessIncomingPacket( int tcpSocket, * responses ( SUBACK, PINGRESP and UNSUBACK ). */ /* Receive the remaining bytes. */ - status = recv( tcpSocket, ( void * ) pFixedBuffer->pBuffer, incomingPacket.remainingLength, 0 ); + status = Plaintext_Recv( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, incomingPacket.remainingLength ); assert( status == ( int ) incomingPacket.remainingLength ); incomingPacket.pRemainingData = pFixedBuffer->pBuffer; @@ -883,7 +768,7 @@ static uint32_t calculateElapsedTime( uint32_t later, int main( int argc, char ** argv ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTFixedBuffer_t fixedBuffer; uint16_t loopCount = 0; const uint16_t maxLoopCount = 5U; @@ -894,31 +779,34 @@ int main( int argc, uint32_t timeDiff = 0; bool controlPacketSent = false; bool publishPacketSent = false; - int tcpSocket = -1; + NetworkContext_t networkContext; + + ( void ) argc; + ( void ) argv; /*** * Set Fixed size buffer structure that is required by API to serialize - * and deserialize data. pBuffer is pointing to a fixed sized mqttSharedBuffer. + * and deserialize data. pBuffer is pointing to a fixed sized buffer. * The application may allocate dynamic memory as well. ***/ - fixedBuffer.pBuffer = mqttSharedBuffer; - fixedBuffer.size = SHARED_BUFFER_SIZE; + fixedBuffer.pBuffer = buffer; + fixedBuffer.size = NETWORK_BUFFER_SIZE; for( demoIterations = 0; demoIterations < maxDemoIterations; demoIterations++ ) { /* Establish a TCP connection with the MQTT broker. This example connects to - * the MQTT broker as specified in MQTT_BROKER_ENDPOINT and - * MQTT_BROKER_PORT at the top of this file. */ - LogInfo( ( "Establishing TCP connection to the broker %s.\r\n", MQTT_BROKER_ENDPOINT ) ); - status = connectToServer( MQTT_BROKER_ENDPOINT, MQTT_BROKER_PORT, &tcpSocket ); + * the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT + * at the demo config header. */ + LogInfo( ( "Establishing TCP connection to the broker %s.\r\n", BROKER_ENDPOINT ) ); + returnStatus = connectToServerWithBackoffRetries( &networkContext ); - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Sends an MQTT Connect packet over the already connected TCP socket - * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ - LogInfo( ( "Establishing MQTT connection to the broker %s.\r\n", MQTT_BROKER_ENDPOINT ) ); - status = createMQTTConnectionWithBroker( tcpSocket, &fixedBuffer ); - assert( status == EXIT_SUCCESS ); + * and waits for connection acknowledgment (CONNACK) packet. */ + LogInfo( ( "Establishing MQTT connection to the broker %s.\r\n", BROKER_ENDPOINT ) ); + returnStatus = createMQTTConnectionWithBroker( &networkContext, &fixedBuffer ); + assert( returnStatus == EXIT_SUCCESS ); /**************************** Subscribe. ******************************/ @@ -931,13 +819,13 @@ int main( int argc, * messages received from the broker will have QOS0. */ /* Subscribe and SUBACK */ LogInfo( ( "Attempt to subscribe to the MQTT topic %s\r\n", MQTT_EXAMPLE_TOPIC ) ); - mqttSubscribeToTopic( tcpSocket, &fixedBuffer ); + mqttSubscribeToTopic( &networkContext, &fixedBuffer ); /* Since subscribe is a control packet, record the last control packet sent * timestamp. This timestamp will be used to determine if it is necessary to * send a PINGREQ packet. */ - status = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); - assert( status == 0 ); + returnStatus = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); + assert( returnStatus == 0 ); lastControlPacketSentTimeStamp = currentTimeStamp.tv_sec; /* Process incoming packet from the broker. After sending the subscribe, the @@ -947,20 +835,20 @@ int main( int argc, * receiving Publish message before subscribe ack is zero; but application * must be ready to receive any packet. This demo uses the generic packet * processing function everywhere to highlight this fact. */ - mqttProcessIncomingPacket( tcpSocket, &fixedBuffer ); + mqttProcessIncomingPacket( &networkContext, &fixedBuffer ); /********************* Publish and Keep Alive Loop. ********************/ /* Publish messages with QOS0, send and process Keep alive messages. */ for( loopCount = 0; loopCount < maxLoopCount; loopCount++ ) { /* Get the current time stamp */ - status = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); + returnStatus = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); /* Publish to the topic every other time to trigger sending of PINGREQ */ if( publishPacketSent == false ) { LogInfo( ( "Publish to the MQTT topic %s\r\n", MQTT_EXAMPLE_TOPIC ) ); - mqttPublishToTopic( tcpSocket, &fixedBuffer ); + mqttPublishToTopic( &networkContext, &fixedBuffer ); /* Set control packet sent flag to true so that the lastControlPacketSent * timestamp will be updated. */ @@ -972,13 +860,13 @@ int main( int argc, /* Check if the keep-alive period has elapsed, since the last control packet was sent. * If the period has elapsed, send out MQTT PINGREQ to the broker. */ timeDiff = calculateElapsedTime( currentTimeStamp.tv_sec, lastControlPacketSentTimeStamp ); - LogInfo( ( "Time Since last control packet %u \r\n", timeDiff ) ); + LogInfo( ( "Time since last control packet %u \r\n", timeDiff ) ); - if( timeDiff >= MQTT_KEEP_ALIVE_PERIOD_SECONDS ) + if( timeDiff >= MQTT_KEEP_ALIVE_INTERVAL_SECONDS ) { /* Send PINGREQ to the broker */ LogInfo( ( "Sending PINGREQ to the broker\n " ) ); - mqttKeepAlive( tcpSocket, &fixedBuffer ); + mqttKeepAlive( &networkContext, &fixedBuffer ); controlPacketSent = true; } @@ -990,39 +878,38 @@ int main( int argc, if( controlPacketSent == true ) { /* Reset the last control packet sent timestamp */ - status = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); - assert( status == 0 ); + returnStatus = clock_gettime( CLOCK_MONOTONIC, ¤tTimeStamp ); + assert( returnStatus == 0 ); lastControlPacketSentTimeStamp = currentTimeStamp.tv_sec; controlPacketSent = false; /* Since the application is subscribed publishing messages to the same topic, * the broker will send the same message back to the application. * Process incoming PUBLISH echo or PINGRESP. */ - mqttProcessIncomingPacket( tcpSocket, &fixedBuffer ); + mqttProcessIncomingPacket( &networkContext, &fixedBuffer ); } /* Sleep until keep alive time period, so that for the next iteration this * loop will send out a PINGREQ if PUBLISH was not sent for this iteration. * The broker will wait till 1.5 times keep-alive period before it disconnects * the client. */ - ( void ) sleep( MQTT_KEEP_ALIVE_PERIOD_SECONDS ); + ( void ) sleep( MQTT_KEEP_ALIVE_INTERVAL_SECONDS ); } /* Unsubscribe from the previously subscribed topic */ LogInfo( ( "Unsubscribe from the MQTT topic %s.\r\n", MQTT_EXAMPLE_TOPIC ) ); - mqttUnsubscribeFromTopic( tcpSocket, &fixedBuffer ); + mqttUnsubscribeFromTopic( &networkContext, &fixedBuffer ); /* Process Incoming unsubscribe ack from the broker. */ - mqttProcessIncomingPacket( tcpSocket, &fixedBuffer ); + mqttProcessIncomingPacket( &networkContext, &fixedBuffer ); /* Send an MQTT Disconnect packet over the already connected TCP socket. * There is no corresponding response for the disconnect packet. After sending * disconnect, client must close the network connection. */ LogInfo( ( "Disconnecting the MQTT connection with %s.\r\n", MQTT_EXAMPLE_TOPIC ) ); - mqttDisconnect( tcpSocket, &fixedBuffer ); + mqttDisconnect( &networkContext, &fixedBuffer ); - /* Close the TCP connection. */ - ( void ) shutdown( tcpSocket, SHUT_RDWR ); - ( void ) close( tcpSocket ); + /* Close the TCP connection. */ + ( void ) Plaintext_Disconnect( &networkContext ); } if( demoIterations < ( maxDemoIterations - 1U ) ) @@ -1035,7 +922,7 @@ int main( int argc, } LogInfo( ( "Demo completed successfully.\r\n" ) ); - return status; + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt index 4f38d25670..85570cfc4b 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt @@ -2,17 +2,6 @@ set( DEMO_NAME "mqtt_demo_mutual_auth" ) # Demo target. add_executable(${DEMO_NAME}) -set(OPENSSL_USE_STATIC_LIBS TRUE) -find_package(OpenSSL REQUIRED) - -# Verify the minimum OpenSSL version required -if( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) - message( FATAL_ERROR "OpenSSL 1.1.0 or later required: OpenSSL ${OPENSSL_VERSION} found." ) -endif() - -set( CMAKE_THREAD_PREFER_PTHREAD ON ) -find_package(Threads REQUIRED) - target_sources( ${DEMO_NAME} PRIVATE @@ -23,12 +12,9 @@ target_link_libraries( ${DEMO_NAME} PRIVATE mqtt - OpenSSL::Crypto - OpenSSL::SSL - # SSL uses Threads and on some platforms require explicit linking. - Threads::Threads - # SSL uses Dynamic Loading and on some platforms requires explicit linking. - ${CMAKE_DL_LIBS} + clock_posix + openssl_posix + transport_reconnect_posix ) target_include_directories( @@ -45,6 +31,7 @@ target_include_directories( # Download the Amazon Root CA certificate. message( "Downloading the Amazon Root CA certificate..." ) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) execute_process( COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index cfa08d0a2b..b7495b7a15 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -58,6 +58,7 @@ * * #define AWS_IOT_ENDPOINT "...insert here..." */ +#define AWS_IOT_ENDPOINT "a36385mjytouy4-ats.iot.us-west-2.amazonaws.com" /** * @brief AWS IoT MQTT broker port number. @@ -67,7 +68,7 @@ * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol * name. When using port 8883, ALPN is not required. */ -#define AWS_MQTT_PORT ( 8883 ) +#define AWS_MQTT_PORT ( 8883 ) /** * @brief Path of the file containing the server's root CA certificate. @@ -84,7 +85,7 @@ * @note This path is relative from the demo binary created. Update * ROOT_CA_CERT_PATH to the absolute path if this demo is executed from elsewhere. */ -#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" /** * @brief Path of the file containing the client certificate. @@ -97,6 +98,7 @@ * * #define CLIENT_CERT_PATH "...insert here..." */ +#define CLIENT_CERT_PATH "certificates/client.crt" /** * @brief Path of the file containing the client's private key. @@ -109,19 +111,19 @@ * * #define CLIENT_PRIVATE_KEY_PATH "...insert here..." */ +#define CLIENT_PRIVATE_KEY_PATH "certificates/client.key" /** * @brief MQTT client identifier. * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "" - +#define CLIENT_IDENTIFIER "testclient" /** * @brief Size of the network buffer for MQTT packets. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define NETWORK_BUFFER_SIZE ( 1024U ) #endif /* ifndef DEMO_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h index 969a386029..1a6211a7ca 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h @@ -47,10 +47,6 @@ /************ End of logging configuration ****************/ -/* Set network context to OpenSSL SSL context. */ -#include -typedef SSL * NetworkContext_t; - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 184d9d9d20..8a73894abc 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -44,20 +44,23 @@ #include /* POSIX includes. */ -#include -#include -#include #include -#include -#include - -/* Demo Config header. */ +/* Include Demo Config as the first non-system header. */ #include "demo_config.h" /* MQTT API header. */ #include "mqtt.h" +/* OpenSSL sockets transport implementation. */ +#include "openssl_posix.h" + +/* Reconnect parameters. */ +#include "transport_reconnect.h" + +/* Clock for timer. */ +#include "clock.h" + /** * These configuration settings are required to run the mutual auth demo. * Throw compilation error if the below configs are not defined. @@ -66,7 +69,7 @@ #error "Please define AWS IoT MQTT broker endpoint(AWS_IOT_ENDPOINT) in demo_config.h." #endif #ifndef ROOT_CA_CERT_PATH - #error "Please define path to root ca certificate of the AWS IoT MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." + #error "Please define path to Root CA certificate of the MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." #endif #ifndef CLIENT_CERT_PATH #error "Please define path to client certificate(CLIENT_CERT_PATH) in demo_config.h." @@ -74,7 +77,9 @@ #ifndef CLIENT_PRIVATE_KEY_PATH #error "Please define path to client private key(CLIENT_PRIVATE_KEY_PATH) in demo_config.h." #endif - +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif /** * Provide default values for undefined configuration settings. @@ -87,10 +92,6 @@ #define NETWORK_BUFFER_SIZE ( 1024U ) #endif -#ifndef CLIENT_IDENTIFIER - #define CLIENT_IDENTIFIER "testclient" -#endif - /** * @brief Length of MQTT server host name. */ @@ -178,9 +179,19 @@ #define DELAY_BETWEEN_PUBLISHES_SECONDS ( 1U ) /** - * @brief Timeout in milliseconds for transport send and receive. + * @brief Number of PUBLISH messages sent per iteration. */ -#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) +#define MQTT_PUBLISH_COUNT_PER_LOOP ( 5U ) + +/** + * @brief Delay in seconds between two iterations of subscribePublishLoop(). + */ +#define MQTT_SUBPUB_LOOP_DELAY_SECONDS ( 5U ) + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 100 ) /*-----------------------------------------------------------*/ @@ -203,14 +214,6 @@ typedef struct PublishPackets /*-----------------------------------------------------------*/ -/** - * @brief globalEntryTime Entry time into the application to use as a reference - * timestamp in #getTimeMs function. #getTimeMs will always return the difference - * of current time with the globalEntryTime. This will reduce the chances of - * overflow for 32 bit unsigned integer used for holding the timestamp. - */ -static uint32_t globalEntryTimeMs = 0U; - /** * @brief Packet Identifier generated when Subscribe request was sent to the broker; * it is used to match received Subscribe ACK to the transmitted subscribe. @@ -231,93 +234,37 @@ static uint16_t globalUnsubscribePacketIdentifier = 0U; */ static PublishPackets_t outgoingPublishPackets[ MAX_OUTGOING_PUBLISHES ] = { 0 }; -/*-----------------------------------------------------------*/ - /** - * @brief Creates a TCP connection to the MQTT broker as specified by - * AWS_IOT_ENDPOINT and AWS_MQTT_PORT defined in the demo_config.h file. - * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pTcpSocket The output parameter to return the created socket - * descriptor. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + * @brief The network buffer must remain valid for the lifetime of the MQTT context. */ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ); +static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; -/** - * @brief Reads credentials from the filesystem. - * - * Uses OpenSSL to import the root CA certificate, client certificate, and - * client certificate private key. - * - * @param[in] pSslContext Destination for the imported credentials. - * @param[in] pRootCaPath Path to the root CA certificate. - * @param[in] pClientCertPath Path to the client certificate. - * @param[in] pCertPrivateKeyPath Path to the client private key. - * - * @return 1 if all credentials were successfully read; 0 otherwise. - */ -static int readCredentials( SSL_CTX * pSslContext, - const char * pRootCaPath, - const char * pClientCertPath, - const char * pCertPrivateKeyPath ); +/*-----------------------------------------------------------*/ /** - * @brief Set up a TLS connection over an existing TCP connection. + * @brief Connect to MQTT broker with reconnection retries. * - * @param[in] tcpSocket Socket descriptor corresponding to the existing TCP connection. - * @param[in] pAlpnProtos List of ALPN protocols available to be negotiated. - * @param[in] alpnProtosLen Length of the ALPN protocols list. - * @param[out] pSslContext The output parameter to return the created SSL context. + * If connection fails, retry is attempted after a timeout. + * Timeout value will exponentially increase until maximum + * timeout value is reached or the number of attempts are exhausted. * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. - */ -static int tlsSetup( int tcpSocket, - const char * pAlpnProtos, - size_t alpnProtosLen, - SSL ** pSslContext ); - - -/** - * @brief The transport send function provided to the MQTT context. + * @param[out] pNetworkContext The output parameter to return the created network context. * - * @param[in] pNetworkContext Pointer to SSL context. - * @param[in] pMessage Data to send. - * @param[in] bytesToSend Length of data to send. - * - * @return Number of bytes sent; negative value on error; - * 0 for timeout or 0 bytes sent. + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on successful connection. */ -static int32_t transportSend( NetworkContext_t pNetworkContext, - const void * pMessage, - size_t bytesToSend ); +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ); /** - * @brief The transport receive function provided to the MQTT context. - * - * @param[in] pNetworkContext Pointer to SSL context. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToRecv Length of data to be received. + * @brief A function that connects to MQTT broker, + * subscribes a topic, publishes to the same + * topic MQTT_PUBLISH_COUNT_PER_LOOP number of times, and verifies if it + * receives the Publish message back. * - * @return Number of bytes received; negative value on error; - * 0 for timeout. - */ -static int32_t transportRecv( NetworkContext_t pNetworkContext, - void * pBuffer, - size_t bytesToRecv ); - -/** - * @brief The timer query function provided to the MQTT context. + * @param[in] pNetworkContext Pointer to the network context created using Openssl_Connect. * - * This function returns the elapsed time with reference to #globalEntryTimeMs. - * - * @return Time in milliseconds. + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. */ -static uint32_t getTimeMs( void ); +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ); /** * @brief The function to handle the incoming publishes. @@ -332,13 +279,13 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, * @brief The application callback function for getting the incoming publish * and incoming acks reported from MQTT library. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. * @param[in] packetIdentifier Packet identifier of the incoming packet. * @param[in] pPublishInfo Deserialized publish info pointer for the incoming * packet. */ -static void eventCallback( MQTTContext_t * pContext, +static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ); @@ -346,8 +293,8 @@ static void eventCallback( MQTTContext_t * pContext, /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. * - * @param[in] pContext MQTT context pointer. - * @param[in] pSslContext SSL context. + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pNetworkContext Pointer to the network context created using Openssl_Connect. * @param[in] createCleanSession Creates a new MQTT session if true. * If false, tries to establish the existing session if there was session * already present in broker. @@ -357,53 +304,53 @@ static void eventCallback( MQTTContext_t * pContext, * @return EXIT_SUCCESS if an MQTT session is established; * EXIT_FAILURE otherwise. */ -static int establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext, +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext, bool createCleanSession, bool * pSessionPresent ); /** * @brief Close an MQTT session by sending MQTT DISCONNECT. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if DISCONNECT was successfully sent; * EXIT_FAILURE otherwise. */ -static int disconnectMqttSession( MQTTContext_t * pContext ); +static int disconnectMqttSession( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT SUBSCRIBE to subscribe to #MQTT_EXAMPLE_TOPIC * defined at the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static int subscribeToTopic( MQTTContext_t * pContext ); +static int subscribeToTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT UNSUBSCRIBE to unsubscribe from * #MQTT_EXAMPLE_TOPIC defined at the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ); +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at * the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if PUBLISH was successfully sent; * EXIT_FAILURE otherwise. */ -static int publishToTopic( MQTTContext_t * pContext ); +static int publishToTopic( MQTTContext_t * pMqttContext ); /** * @brief Function to get the free index at which an outgoing publish @@ -429,7 +376,7 @@ static void cleanupOutgoingPublishAt( uint8_t index ); * @brief Function to clean up all the outgoing publishes maintained in the * array. */ -static void cleanupOutgoingPublishes(); +static void cleanupOutgoingPublishes( void ); /** * @brief Function to clean up the publish packet with the given packet id. @@ -444,475 +391,87 @@ static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ); * the broker. This function handles the resending of the QoS1 publish packets, * which are maintained locally. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. */ -static int handlePublishResend( MQTTContext_t * pContext ); +static int handlePublishResend( MQTTContext_t * pMqttContext ); /*-----------------------------------------------------------*/ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ) +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS; - struct addrinfo hints, * pIndex, * pListHead = NULL; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - struct timeval transportTimeout; - - /* Add hints to retrieve only TCP sockets in getaddrinfo. */ - ( void ) memset( &hints, 0x00, sizeof( hints ) ); - /* Address family of either IPv4 or IPv6. */ - hints.ai_family = AF_UNSPEC; - /* TCP Socket. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, &hints, &pListHead ); - - if( status != -1 ) + int returnStatus = EXIT_SUCCESS; + bool retriesArePending = true; + OpensslStatus_t opensslStatus = OPENSSL_SUCCESS; + TransportReconnectParams_t reconnectParams; + ServerInfo_t serverInfo; + OpensslCredentials_t opensslCredentials; + + /* Initialize information to connect to the MQTT broker. */ + serverInfo.pHostName = AWS_IOT_ENDPOINT; + serverInfo.hostNameLength = AWS_IOT_ENDPOINT_LENGTH; + serverInfo.port = AWS_MQTT_PORT; + + /* Initialize credentials for establishing TLS session. */ + memset( &opensslCredentials, 0, sizeof( OpensslCredentials_t ) ); + opensslCredentials.pRootCaPath = ROOT_CA_CERT_PATH; + opensslCredentials.pClientCertPath = CLIENT_CERT_PATH; + opensslCredentials.pPrivateKeyPath = CLIENT_PRIVATE_KEY_PATH; + + if( AWS_MQTT_PORT == 443 ) { - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) - { - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - } - - status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( status == -1 ) - { - close( *pTcpSocket ); - } - else - { - break; - } - } - - if( pIndex == NULL ) - { - /* Fail if no connection could be established. */ - status = EXIT_FAILURE; - LogError( ( "Located but could not connect to MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - } - else - { - status = EXIT_SUCCESS; - LogInfo( ( "TCP connection established with %.*s.\n\n", - ( int ) strlen( pServer ), - pServer ) ); - } + /* Pass the ALPN protocol name depending on the port being used. + * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint + * in the link below. + * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ + */ + opensslCredentials.pAlpnProtos = ALPN_PROTOCOL_NAME; + opensslCredentials.alpnProtosLen = ALPN_PROTOCOL_NAME_LENGTH; } - else - { - LogError( ( "Could not locate MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - status = EXIT_FAILURE; - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -static int readCredentials( SSL_CTX * pSslContext, - const char * pRootCaPath, - const char * pClientCertPath, - const char * pCertPrivateKeyPath ) -{ - int sslStatus = 0, fcloseStatus = 0; - X509 * pRootCa = NULL; - FILE * pRootCaFile = NULL; - - /* OpenSSL does not provide a single function for reading and loading certificates - * from files into stores, so the file API must be called. Start with the - * root certificate. */ - if( ( pRootCaPath != NULL ) && - ( strlen( pRootCaPath ) != 0 ) ) - { - LogDebug( ( "Opening root certificate %s.", pRootCaPath ) ); - pRootCaFile = fopen( pRootCaPath, "r" ); - - if( pRootCaFile == NULL ) - { - LogError( ( "Failed to open %s.", pRootCaPath ) ); - } - else - { - /* Read the root CA into an X509 object, then close its file handle. */ - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - - if( pRootCa == NULL ) - { - LogError( ( "Failed to parse root CA." ) ); - } - else - { - sslStatus = 1; - LogDebug( ( "Successfully parsed root CA." ) ); - } - - /* Close the pRootCaFile regardless of whether we succeeded to - * parse the certificate or not. */ - fcloseStatus = fclose( pRootCaFile ); - - if( fcloseStatus != 0 ) - { - LogWarn( ( "Failed to close file %s", pRootCaPath ) ); - } - } - - if( sslStatus == 1 ) - { - /* Add the root CA to certificate store. */ - sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslContext ), - pRootCa ); - - if( sslStatus != 1 ) - { - LogError( ( "Failed to add root CA to certificate store." ) ); - } - } - } - - if( ( sslStatus == 1 ) && - ( pClientCertPath != NULL ) && - ( strlen( pClientCertPath ) != 0 ) ) - { - /* Import the client certificate. */ - sslStatus = SSL_CTX_use_certificate_chain_file( pSslContext, - pClientCertPath ); - - if( sslStatus != 1 ) - { - LogError( ( "Failed to import client certificate at %s.", - pClientCertPath ) ); - } - } - - if( ( sslStatus == 1 ) && - ( pCertPrivateKeyPath != NULL ) && - ( strlen( pCertPrivateKeyPath ) != 0 ) ) - { - /* Import the client certificate private key. */ - sslStatus = SSL_CTX_use_PrivateKey_file( pSslContext, - pCertPrivateKeyPath, - SSL_FILETYPE_PEM ); - - if( sslStatus != 1 ) - { - LogError( ( "Failed to import client certificate private key at %s.", - pCertPrivateKeyPath ) ); - } - } - - /* Free the root CA object. */ - if( pRootCa != NULL ) - { - X509_free( pRootCa ); - } - - if( sslStatus == 1 ) - { - LogInfo( ( "Successfully imported server root CA, client certificate and " - "client private key." ) ); - } - - return sslStatus; -} - -/*-----------------------------------------------------------*/ - -static int tlsSetup( int tcpSocket, - const char * pAlpnProtos, - size_t alpnProtosLen, - SSL ** pSslContext ) -{ - int status = EXIT_FAILURE, sslStatus = 0, alpnStatus = -1, sslVerifyStatus = 0; - FILE * pRootCaFile = NULL; - X509 * pRootCa = NULL; - SSL_CTX * pSslSetup = NULL; - - assert( tcpSocket >= 0 ); - - /* Setup for creating a TLS client. */ - pSslSetup = SSL_CTX_new( TLS_client_method() ); - - if( pSslSetup != NULL ) - { - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. - * The mask returned by SSL_CTX_set_mode does not need to be checked. */ - ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); - - /* Setup authentication. */ - sslStatus = readCredentials( pSslSetup, - ROOT_CA_CERT_PATH, - CLIENT_CERT_PATH, - CLIENT_PRIVATE_KEY_PATH ); - - if( sslStatus != 1 ) - { - LogError( ( "Setting up credentials failed." ) ); - } - } - else - { - LogError( ( "Failed setting up a TLS client." ) ); - } - - /* Set up the TLS connection. */ - if( sslStatus == 1 ) - { - /* Create a new SSL context. */ - *pSslContext = SSL_new( pSslSetup ); - - if( *pSslContext != NULL ) - { - /* Enable SSL peer verification. */ - SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); - sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); - - if( sslStatus != 1 ) - { - LogError( ( "Failed to set the socket fd to SSL context." ) ); - } - } - else - { - LogError( ( "Failed to create a new SSL context." ) ); - sslStatus = 0; - } - } - - if( ( sslStatus == 1 ) && - ( pAlpnProtos != NULL ) && ( alpnProtosLen > 0 ) ) - { - sslStatus = -1; - - if( AWS_MQTT_PORT != 443 ) - { - LogError( ( "AWS_MQTT_PORT must be set to 443 to use ALPN." ) ); - } - else - { - alpnStatus = SSL_set_alpn_protos( *pSslContext, - ( unsigned char * ) pAlpnProtos, - ( unsigned int ) alpnProtosLen ); - } - if( alpnStatus != 0 ) - { - LogError( ( "SSL_set_alpn_protos failed to set ALPN protos. %s", - pAlpnProtos ) ); - } - else - { - sslStatus = 1; - } - } + /* Initialize reconnect attempts and interval */ + Transport_ReconnectParamsReset( &reconnectParams ); - /* Perform the TLS handshake. */ - if( sslStatus == 1 ) + /* Attempt to connect to MQTT broker. If connection fails, retry after + * a timeout. Timeout value will exponentially increase until maximum + * attempts are reached. + */ + do { - sslStatus = SSL_connect( *pSslContext ); - - if( sslStatus != 1 ) + /* Establish a TLS session with the MQTT broker. This example connects + * to the MQTT broker as specified in AWS_IOT_ENDPOINT and AWS_MQTT_PORT + * at the demo config header. */ + LogInfo( ( "Establishing a TLS session to %.*s:%d.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT, + AWS_MQTT_PORT ) ); + opensslStatus = Openssl_Connect( pNetworkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( opensslStatus != OPENSSL_SUCCESS ) { - LogError( ( "Failed to perform TLS handshake." ) ); + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter." ) ); + retriesArePending = Transport_ReconnectBackoffAndSleep( &reconnectParams ); } - } - - /* Verify the peer certificate. */ - if( sslStatus == 1 ) - { - sslVerifyStatus = SSL_get_verify_result( *pSslContext ); - if( sslVerifyStatus != X509_V_OK ) + if( retriesArePending == false ) { - sslStatus = 0; - LogError( ( "Verifying the peer certificate failed. " ) ); + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + returnStatus = EXIT_FAILURE; } - } - - /* Clean up on error. */ - if( ( sslStatus == 0 ) && ( pSslSetup != NULL ) ) - { - SSL_free( *pSslContext ); - *pSslContext = NULL; - } - - if( pRootCaFile != NULL ) - { - ( void ) fclose( pRootCaFile ); - } - - if( pRootCa != NULL ) - { - X509_free( pRootCa ); - } - - if( pSslSetup != NULL ) - { - SSL_CTX_free( pSslSetup ); - } - - /* Log failure or success and update the correct exit status to return. */ - if( sslStatus != 1 ) - { - LogError( ( "Failed to establish a TLS connection to %.*s.", - AWS_IOT_ENDPOINT_LENGTH, - AWS_IOT_ENDPOINT ) ); - status = EXIT_FAILURE; - } - else - { - LogInfo( ( "Established a TLS connection to %.*s.\n\n", - AWS_IOT_ENDPOINT_LENGTH, - AWS_IOT_ENDPOINT ) ); - status = EXIT_SUCCESS; - } + } while( ( opensslStatus != OPENSSL_SUCCESS ) && ( retriesArePending == true ) ); - return status; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportSend( NetworkContext_t pNetworkContext, - const void * pMessage, - size_t bytesToSend ) -{ - int32_t bytesSent = 0; - int pollStatus = 0; - struct pollfd fileDescriptor; - - fileDescriptor.events = POLLOUT; - fileDescriptor.revents = 0; - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pNetworkContext ); - - /* Poll the file descriptor to check if SSL_Write can be done now. */ - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - - if( pollStatus > 0 ) - { - bytesSent = ( int32_t ) SSL_write( pNetworkContext, pMessage, bytesToSend ); - } - else if( pollStatus == 0 ) - { - LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); - } - else - { - LogError( ( "Polling of the SSL socket for write buffer availability failed" - " with status %d.", - pollStatus ) ); - bytesSent = -1; - } - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportRecv( NetworkContext_t pNetworkContext, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - int pollStatus = -1, bytesAvailableToRead = 0; - struct pollfd fileDescriptor; - - fileDescriptor.events = POLLIN | POLLPRI; - fileDescriptor.revents = 0; - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pNetworkContext ); - - /* Check if there are any pending data available for read. */ - bytesAvailableToRead = SSL_pending( pNetworkContext ); - - /* Poll only if there is no data available yet to read. */ - if( bytesAvailableToRead <= 0 ) - { - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - } - - /* Read data if it was already pending or if it became available during - * polling. */ - if( ( bytesAvailableToRead > 0 ) || ( pollStatus > 0 ) ) - { - bytesReceived = ( int32_t ) SSL_read( pNetworkContext, pBuffer, bytesToRecv ); - } - /* Poll timed out. */ - else if( pollStatus == 0 ) - { - LogDebug( ( "Poll timed out and there is no data to read from the buffer." ) ); - } - else - { - LogError( ( "Poll returned with status = %d.", pollStatus ) ); - bytesReceived = -1; - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -static uint32_t getTimeMs( void ) -{ - uint32_t timeMs; - struct timespec timeSpec; - - /* Get the MONOTONIC time. */ - clock_gettime( CLOCK_MONOTONIC, &timeSpec ); - - /* Calculate the milliseconds from timespec. */ - timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) - + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); - - /* Reduce globalEntryTime from obtained time so as to always return the - * elapsed time in the application. */ - timeMs = ( uint32_t ) ( timeMs - globalEntryTimeMs ); - - return timeMs; + return returnStatus; } /*-----------------------------------------------------------*/ static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) { - int status = EXIT_FAILURE; - int index = 0; + int returnStatus = EXIT_FAILURE; + uint8_t index = 0; assert( outgoingPublishPackets != NULL ); assert( pIndex != NULL ); @@ -923,7 +482,7 @@ static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) * Check if the the index has a free slot. */ if( outgoingPublishPackets[ index ].packetId == MQTT_PACKET_ID_INVALID ) { - status = EXIT_SUCCESS; + returnStatus = EXIT_SUCCESS; break; } } @@ -931,7 +490,7 @@ static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) /* Copy the available index into the output param. */ *pIndex = index; - return status; + return returnStatus; } /*-----------------------------------------------------------*/ @@ -948,7 +507,7 @@ static void cleanupOutgoingPublishAt( uint8_t index ) /*-----------------------------------------------------------*/ -static void cleanupOutgoingPublishes() +static void cleanupOutgoingPublishes( void ) { assert( outgoingPublishPackets != NULL ); @@ -980,9 +539,9 @@ static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ) /*-----------------------------------------------------------*/ -static int handlePublishResend( MQTTContext_t * pContext ) +static int handlePublishResend( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus = MQTTSuccess; uint8_t index = 0U; @@ -999,7 +558,7 @@ static int handlePublishResend( MQTTContext_t * pContext ) LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", outgoingPublishPackets[ index ].packetId ) ); - mqttStatus = MQTT_Publish( pContext, + mqttStatus = MQTT_Publish( pMqttContext, &outgoingPublishPackets[ index ].pubInfo, outgoingPublishPackets[ index ].packetId ); @@ -1009,7 +568,7 @@ static int handlePublishResend( MQTTContext_t * pContext ) " failed with status %u.", outgoingPublishPackets[ index ].packetId, mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; break; } else @@ -1020,7 +579,7 @@ static int handlePublishResend( MQTTContext_t * pContext ) } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ @@ -1058,12 +617,12 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ -static void eventCallback( MQTTContext_t * pContext, +static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { - assert( pContext != NULL ); + assert( pMqttContext != NULL ); assert( pPacketInfo != NULL ); /* Handle incoming publish. The lower 4 bits of the publish packet @@ -1100,11 +659,12 @@ static void eventCallback( MQTTContext_t * pContext, /* Nothing to be done from application as library handles * PINGRESP. */ - LogInfo( ( "PINGRESP received.\n\n" ) ); + LogWarn( ( "PINGRESP should not be handled by the application " + "callback when using MQTT_ProcessLoop.\n\n" ) ); break; case MQTT_PACKET_TYPE_PUBACK: - LogInfo( ( "PUBACK received for packet id %u.", + LogInfo( ( "PUBACK received for packet id %u.\n\n", packetIdentifier ) ); /* Cleanup publish packet when a PUBACK is received. */ cleanupOutgoingPublishWithPacketID( packetIdentifier ); @@ -1112,7 +672,7 @@ static void eventCallback( MQTTContext_t * pContext, /* Any other packet type is invalid. */ default: - LogError( ( "Unknown packet type received:(%02x).", + LogError( ( "Unknown packet type received:(%02x).\n\n", pPacketInfo->type ) ); } } @@ -1120,30 +680,27 @@ static void eventCallback( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ -static int establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext, +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext, bool createCleanSession, bool * pSessionPresent ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; - MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + TransportInterface_t transport; - assert( pContext != NULL ); - assert( pSslContext != NULL ); - - /* The network buffer must remain valid for the lifetime of the MQTT context. */ - static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + assert( pMqttContext != NULL ); + assert( pNetworkContext != NULL ); /* Fill in TransportInterface send and receive function pointers. * For this demo, TCP sockets are used to send and receive data - * from network. Network context is SSL context.*/ - transport.networkContext = pSslContext; - transport.send = transportSend; - transport.recv = transportRecv; + * from network. Network context is SSL context for OpenSSL.*/ + transport.pNetworkContext = pNetworkContext; + transport.send = Openssl_Send; + transport.recv = Openssl_Recv; /* Fill the values for network buffer. */ networkBuffer.pBuffer = buffer; @@ -1155,14 +712,14 @@ static int establishMqttSession( MQTTContext_t * pContext, /* Application callback for getting the time for MQTT library. This time * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = getTimeMs; + callbacks.getTime = Clock_GetTimeMs; /* Initialize MQTT library. */ - mqttStatus = MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( pMqttContext, &transport, &callbacks, &networkBuffer ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); } else @@ -1196,11 +753,11 @@ static int establishMqttSession( MQTTContext_t * pContext, connectInfo.passwordLength = 0U; /* Send MQTT CONNECT packet to broker. */ - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); + mqttStatus = MQTT_Connect( pMqttContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); } else @@ -1209,39 +766,40 @@ static int establishMqttSession( MQTTContext_t * pContext, } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int disconnectMqttSession( MQTTContext_t * pContext ) +static int disconnectMqttSession( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; + int returnStatus = EXIT_SUCCESS; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Send DISCONNECT. */ - MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); + mqttStatus = MQTT_Disconnect( pMqttContext ); if( mqttStatus != MQTTSuccess ) { LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int subscribeToTopic( MQTTContext_t * pContext ) +static int subscribeToTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Start with everything at 0. */ ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); @@ -1252,10 +810,10 @@ static int subscribeToTopic( MQTTContext_t * pContext ) pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; /* Generate packet identifier for the SUBSCRIBE packet. */ - globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + globalSubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); /* Send SUBSCRIBE packet. */ - mqttStatus = MQTT_Subscribe( pContext, + mqttStatus = MQTT_Subscribe( pMqttContext, pSubscriptionList, sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), globalSubscribePacketIdentifier ); @@ -1264,7 +822,7 @@ static int subscribeToTopic( MQTTContext_t * pContext ) { LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { @@ -1273,18 +831,18 @@ static int subscribeToTopic( MQTTContext_t * pContext ) MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Start with everything at 0. */ ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); @@ -1296,10 +854,10 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; /* Generate packet identifier for the UNSUBSCRIBE packet. */ - globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); /* Send UNSUBSCRIBE packet. */ - mqttStatus = MQTT_Unsubscribe( pContext, + mqttStatus = MQTT_Unsubscribe( pMqttContext, pSubscriptionList, sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), globalUnsubscribePacketIdentifier ); @@ -1308,7 +866,7 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) { LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { @@ -1317,26 +875,26 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int publishToTopic( MQTTContext_t * pContext ) +static int publishToTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; - MQTTStatus_t mqttStatus; + int returnStatus = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; uint8_t publishIndex = MAX_OUTGOING_PUBLISHES; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Get the next free index for the outgoing publish. All QoS1 outgoing * publishes are stored until a PUBACK is received. These messages are * stored for supporting a resend if a network connection is broken before * receiving a PUBACK. */ - status = getNextFreeIndexForOutgoingPublishes( &publishIndex ); + returnStatus = getNextFreeIndexForOutgoingPublishes( &publishIndex ); - if( status == EXIT_FAILURE ) + if( returnStatus == EXIT_FAILURE ) { LogError( ( "Unable to find a free spot for outgoing PUBLISH message.\n\n" ) ); } @@ -1350,10 +908,10 @@ static int publishToTopic( MQTTContext_t * pContext ) outgoingPublishPackets[ publishIndex ].pubInfo.payloadLength = MQTT_EXAMPLE_MESSAGE_LENGTH; /* Get a new packet id. */ - outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pContext ); + outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pMqttContext ); /* Send PUBLISH packet. */ - mqttStatus = MQTT_Publish( pContext, + mqttStatus = MQTT_Publish( pMqttContext, &outgoingPublishPackets[ publishIndex ].pubInfo, outgoingPublishPackets[ publishIndex ].packetId ); @@ -1362,114 +920,49 @@ static int publishToTopic( MQTTContext_t * pContext ) LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", mqttStatus ) ); cleanupOutgoingPublishAt( publishIndex ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { - LogInfo( ( "PUBLISH send for topic %.*s to broker with packet id %u.\n\n", + LogInfo( ( "PUBLISH sent for topic %.*s to broker with packet ID %u.\n\n", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC, outgoingPublishPackets[ publishIndex ].packetId ) ); } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -/** - * @brief Entry point of demo. - * - * The example shown below uses MQTT APIs to send and receive MQTT packets - * over the TLS connection established using openSSL. - * - * The example is single threaded and uses statically allocated memory; - * it uses QOS1 and therefore implements a retransmission mechanism - * for Publish messages. Retransmission of publish messages are attempted - * when a MQTT connection is established with a session that was already - * present. All the outgoing publish messages waiting to receive PUBACK - * are resent in this demo. In order to support retransmission all the outgoing - * publishes are stored until a PUBACK is received. - */ -int main( int argc, - char ** argv ) +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS, tcpSocket; - bool sessionPresent, mqttSessionEstablished = false; - MQTTContext_t context; + int returnStatus = EXIT_SUCCESS; + bool mqttSessionEstablished = false, sessionPresent; + MQTTContext_t mqttContext; MQTTStatus_t mqttStatus = MQTTSuccess; - SSL * pSslContext = NULL; uint32_t publishCount = 0; - const uint32_t maxPublishCount = 5U; - - ( void ) argc; - ( void ) argv; - - /* Get the entry time to application. */ - globalEntryTimeMs = getTimeMs(); + const uint32_t maxPublishCount = MQTT_PUBLISH_COUNT_PER_LOOP; - /* Establish a TCP connection with the MQTT broker. This example connects - * to the MQTT broker as specified in AWS_IOT_ENDPOINT and AWS_MQTT_PORT in - * the demo_config.h file. */ - LogInfo( ( "Creating a TCP connection to %.*s:%d.", + /* Establish MQTT session on top of TCP+TLS connection. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", AWS_IOT_ENDPOINT_LENGTH, - AWS_IOT_ENDPOINT, - AWS_MQTT_PORT ) ); - status = connectToServer( AWS_IOT_ENDPOINT, AWS_MQTT_PORT, &tcpSocket ); + AWS_IOT_ENDPOINT ) ); - /* Establish TLS connection on top of TCP connection. */ - if( status == EXIT_SUCCESS ) - { - LogInfo( ( "Performing TLS handshake on top of the TCP connection." ) ); + /* Sends an MQTT Connect packet using the established TLS session, + * then waits for connection acknowledgment (CONNACK) packet. */ + returnStatus = establishMqttSession( &mqttContext, pNetworkContext, false, &sessionPresent ); - if( AWS_MQTT_PORT == 443 ) - { - /* Pass the ALPN protocol name depending on the port being used. - * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint - * in the link below. - * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ - */ - status = tlsSetup( tcpSocket, - ALPN_PROTOCOL_NAME, - ALPN_PROTOCOL_NAME_LENGTH, - &pSslContext ); - } - else if( AWS_MQTT_PORT == 8883 ) - { - status = tlsSetup( tcpSocket, - NULL, - 0, - &pSslContext ); - } - else - { - LogError( ( "AWS IoT Core does not support MQTT through port %d", - AWS_MQTT_PORT ) ); - status = EXIT_FAILURE; - } - } - - /* Establish MQTT session on top of TCP+TLS connection. */ - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { - /* Sends an MQTT Connect packet over the already connected TCP socket - * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ - LogInfo( ( "Creating an MQTT connection to %.*s.", - AWS_IOT_ENDPOINT_LENGTH, - AWS_IOT_ENDPOINT ) ); - status = establishMqttSession( &context, pSslContext, false, &sessionPresent ); - - if( status == EXIT_SUCCESS ) - { - /* Keep a flag for indicating if MQTT session is established. This - * flag will mark that an MQTT DISCONNECT has to be sent at the end - * of the demo even if there are intermediate failures. */ - mqttSessionEstablished = true; - } + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be sent at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Check if session is present and if there are any outgoing publishes * that need to resend. This is only valid if the broker is @@ -1480,7 +973,7 @@ int main( int argc, "Resending unacked publishes." ) ); /* Handle all the resend of publish messages. */ - status = handlePublishResend( &context ); + returnStatus = handlePublishResend( &mqttContext ); } else { @@ -1493,7 +986,7 @@ int main( int argc, } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* The client is now connected to the broker. Subscribe to the topic * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a @@ -1504,29 +997,29 @@ int main( int argc, LogInfo( ( "Subscribing to the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = subscribeToTopic( &context ); + returnStatus = subscribeToTopic( &mqttContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Process incoming packet from the broker. Acknowledgment for subscription * ( SUBACK ) will be received here. However after sending the subscribe, the * client may receive a publish before it receives a subscribe ack. Since this * demo is subscribing to the topic to which no one is publishing, probability * of receiving publish message before subscribe ack is zero; but application - * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to * receive packet from network. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT_ProcessLoop returned with status = %u.", mqttStatus ) ); } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Publish messages with QOS1, receive incoming messages and * send keep alive messages. */ @@ -1535,7 +1028,7 @@ int main( int argc, LogInfo( ( "Sending Publish to the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = publishToTopic( &context ); + returnStatus = publishToTopic( &mqttContext ); /* Calling MQTT_ProcessLoop to process incoming publish echo, since * application subscribed to the same topic the broker will send @@ -1543,7 +1036,7 @@ int main( int argc, * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS * has expired since the last MQTT packet sent and receive * ping responses. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { @@ -1558,23 +1051,23 @@ int main( int argc, } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Unsubscribe from the topic. */ LogInfo( ( "Unsubscribing from the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = unsubscribeFromTopic( &context ); + returnStatus = unsubscribeFromTopic( &mqttContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Process Incoming UNSUBACK packet from the broker. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT_ProcessLoop returned with status = %u.", mqttStatus ) ); } @@ -1585,45 +1078,87 @@ int main( int argc, * disconnect, client must close the network connection. */ if( mqttSessionEstablished == true ) { - if( status == EXIT_FAILURE ) + LogInfo( ( "Disconnecting the MQTT connection with %.*s.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT ) ); + + if( returnStatus == EXIT_FAILURE ) { /* Returned status is not used to update the local status as there * were failures in demo execution. */ - ( void ) disconnectMqttSession( &context ); + ( void ) disconnectMqttSession( &mqttContext ); } else { - status = disconnectMqttSession( &context ); + returnStatus = disconnectMqttSession( &mqttContext ); } } - /* Close TLS session if established. */ - if( pSslContext != NULL ) + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TLS connection established using OpenSSL. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS1 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBACK + * are resent in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBACK is received. + */ +int main( int argc, + char ** argv ) +{ + int returnStatus = EXIT_SUCCESS; + NetworkContext_t networkContext; + + ( void ) argc; + ( void ) argv; + + for( ; ; ) { - /* SSL shutdown should be called twice: once to send "close notify" and - * once more to receive the peer's "close notify". */ - if( SSL_shutdown( pSslContext ) == 0 ) + /* Attempt to connect to the MQTT broker. If connection fails, retry after + * a timeout. Timeout value will be exponentially increased till the maximum + * attemps are reached or maximum timout value is reached. The function + * returns EXIT_FAILURE if the TCP connection cannot be established to + * broker after configured number of attemps. */ + returnStatus = connectToServerWithBackoffRetries( &networkContext ); + + if( returnStatus == EXIT_FAILURE ) { - ( void ) SSL_shutdown( pSslContext ); + /* Log error to indicate connection failure after all + * reconnect attempts are over. */ + LogError( ( "Failed to connect to MQTT broker %.*s.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT ) ); + } + else + { + /* If TLS session is established, execute Subscribe/Publish loop. */ + returnStatus = subscribePublishLoop( &networkContext ); } - SSL_free( pSslContext ); - } + if( returnStatus == EXIT_SUCCESS ) + { + /* Log message indicating an iteration completed successfully. */ + LogInfo( ( "Demo completed successfully." ) ); + } - /* Close TCP connection if established. */ - if( tcpSocket != -1 ) - { - shutdown( tcpSocket, SHUT_RDWR ); - close( tcpSocket ); - } + /* End TLS session, then close TCP connection. */ + ( void ) Openssl_Disconnect( &networkContext ); - /* Log the success message. */ - if( status == EXIT_SUCCESS ) - { - LogInfo( ( "Demo completed successfully." ) ); + LogInfo( ( "Short delay before starting the next iteration....\n" ) ); + sleep( MQTT_SUBPUB_LOOP_DELAY_SECONDS ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt index 5229a670da..afacd3d923 100644 --- a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -18,6 +18,8 @@ target_link_libraries( ${DEMO_NAME} PRIVATE mqtt + clock_posix + plaintext_posix transport_reconnect_posix ) diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index 356da71172..8fbe99cc26 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -47,6 +47,21 @@ /************ End of logging configuration ****************/ +/** + * @brief MQTT server host name. + * + * This demo uses the Mosquitto test server. This is a public MQTT server; do not + * publish anything sensitive to this server. + */ +#define BROKER_ENDPOINT "test.mosquitto.org" + +/** + * @brief MQTT server port number. + * + * In general, port 1883 is for unsecured MQTT connections. + */ +#define BROKER_PORT ( 1883 ) + /** * @brief MQTT client identifier. * diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h index 82280bd6f7..b2ca1158f4 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h @@ -48,9 +48,6 @@ /************ End of logging configuration ****************/ -/* Set network context to socket (int). */ -typedef int NetworkContext_t; - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 158840f506..96d2d3369f 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -36,53 +36,41 @@ #include #include -/* POSIX socket includes. */ -#include -#include -#include +/* POSIX includes. */ #include -#include -#include - /* Include Demo Config as the first non-system header. */ #include "demo_config.h" /* MQTT API header. */ #include "mqtt.h" +/* Plaintext sockets transport implementation. */ +#include "plaintext_posix.h" /* Reconnect parameters. */ #include "transport_reconnect.h" -/** - * @brief MQTT server host name. - * - * This demo uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. - */ -#define BROKER_ENDPOINT "test.mosquitto.org" +/* Clock for timer. */ +#include "clock.h" /** - * @brief Length of MQTT server host name. + * These configuration settings are required to run the plaintext demo. + * Throw compilation error if the below configs are not defined. */ -#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) - -/** - * @brief MQTT server port number. - * - * In general, port 1883 is for unsecured MQTT connections. - */ -#define BROKER_PORT ( 1883 ) +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif /** - * @brief Size of the network buffer for MQTT packets. + * Provide default values for undefined configuration settings. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#ifndef BROKER_PORT + #define BROKER_PORT ( 1883 ) +#endif -/* Check that client identifier is defined. */ -#ifndef CLIENT_IDENTIFIER - #error "Please define a unique CLIENT_IDENTIFIER." +#ifndef NETWORK_BUFFER_SIZE + #define NETWORK_BUFFER_SIZE ( 1024U ) #endif /** @@ -90,10 +78,15 @@ */ #define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) + /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ -#define CONNACK_RECV_TIMEOUT_MS ( 1000 ) +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) /** * @brief The topic to subscribe and publish to in the example. @@ -114,20 +107,23 @@ #define MQTT_EXAMPLE_MESSAGE "Hello World!" /** - * @brief The MQTT message published in this example. + * @brief The length of the MQTT message published in this example. */ #define MQTT_EXAMPLE_MESSAGE_LENGTH ( ( uint16_t ) ( sizeof( MQTT_EXAMPLE_MESSAGE ) - 1 ) ) /** * @brief Timeout for MQTT_ProcessLoop function in milliseconds. - * - * Please note */ #define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 500U ) /** - * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to - * broker. + * @brief The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. */ #define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) @@ -142,7 +138,7 @@ #define MQTT_PUBLISH_COUNT_PER_LOOP ( 5U ) /** - * @brief Delay in seconds between two iterations of subscribePublishLoop() + * @brief Delay in seconds between two iterations of subscribePublishLoop(). */ #define MQTT_SUBPUB_LOOP_DELAY_SECONDS ( 5U ) @@ -153,20 +149,6 @@ /*-----------------------------------------------------------*/ -/* @brief errno to check transport error. */ -extern int errno; - -/*-----------------------------------------------------------*/ - -/** - * @brief globalEntryTime entry time into the application to use as a reference - * timestamp in #getTimeMs function. #getTimeMs will always return the difference - * of current time with the globalEntryTime. This will reduce the chances of - * overflow for 32 bit unsigned integer used for holding the timestamp. - * - */ -static uint32_t globalEntryTimeMs = 0U; - /** * @brief Packet Identifier generated when Subscribe request was sent to the broker; * it is used to match received Subscribe ACK to the transmitted subscribe. @@ -175,88 +157,42 @@ static uint16_t globalSubscribePacketIdentifier = 0U; /** * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; - * it is used to match received Unsubscribe response to the transmitted unsubscribe + * it is used to match received Unsubscribe ACK to the transmitted unsubscribe * request. */ static uint16_t globalUnsubscribePacketIdentifier = 0U; -/*-----------------------------------------------------------*/ - /** - * @brief Creates a TCP connection to the MQTT broker as specified by - * BROKER_ENDPOINT and BROKER_PORT defined at the top of this file. - * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pTcpSocket Pointer to TCP socket file descriptor. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + * @brief The network buffer must remain valid for the lifetime of the MQTT context. */ -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ); +static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + +/*-----------------------------------------------------------*/ /** - * @brief connect to MQTT broker with reconnection retries. + * @brief Connect to MQTT broker with reconnection retries. + * * If connection fails, retry is attempted after a timeout. * Timeout value will exponentially increased till maximum * timeout value is reached or the number of attemps are exhausted. * - * @param[out] pTcpSocket Pointer to TCP socket file descriptor. Upon - * successful connect, the pointer will point at connected socket descriptor. + * @param[out] pNetworkContext The output parameter to return the created network context. * * @return EXIT_FAILURE on failure; EXIT_SUCCESS on successful connection. */ -static int connectToServerWithBackoffRetries( int * pTcpSocket ); - -/** - * @brief The transport send function provided to the MQTT context. - * - * @param[in] tcpSocket TCP socket. - * @param[in] pMessage Data to send. - * @param[in] bytesToSend Length of data to send. - * - * @return Number of bytes sent; negative value on error; - * 0 for timeout or 0 bytes sent. - */ -static int32_t transportSend( NetworkContext_t tcpSocket, - const void * pMessage, - size_t bytesToSend ); - -/** - * @brief The transport receive function provided to the MQTT context. - * - * @param[in] tcpSocket TCP socket. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToSend Size of pBuffer. - * - * @return Number of bytes received; negative value on error. - */ -static int32_t transportRecv( NetworkContext_t tcpSocket, - void * pBuffer, - size_t bytesToRecv ); +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ); /** * @brief A function that connects to MQTT broker, - * subscribes a topic, Publishes to the same + * subscribes a topic, publishes to the same * topic MQTT_PUBLISH_COUNT_PER_LOOP number of times, and verifies if it * receives the Publish message back. * - * @param[in] tcpSocket TCP socket that is already connected to the broker. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. */ -static int subscribePublishLoop( int tcpSocket ); - - -/** - * @brief The timer query function provided to the MQTT context. - * - * This function returns the elapsed time with reference to #globalEntryTimeMs. - * - * @return Time in milliseconds. - */ -static uint32_t getTimeMs( void ); +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ); /** * @brief The function to handle the incoming publishes. @@ -271,203 +207,86 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, * @brief The application callback function for getting the incoming publish * and incoming acks reported from MQTT library. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. * @param[in] packetIdentifier Packet identifier of the incoming packet. * @param[in] pPublishInfo Deserialized publish info pointer for the incoming * packet. */ -static void eventCallback( MQTTContext_t * pContext, +static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ); /** - * @brief Sends an MQTT CONNECT packet over the already connected TCP socket.. + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. * - * @param[in] pContext MQTT context pointer. - * @param[in] tcpSocket TCP socket. + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * * @return EXIT_SUCCESS if an MQTT session is established; * EXIT_FAILURE otherwise. */ -static int establishMqttSession( MQTTContext_t * pContext, - int tcpSocket ); +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext ); /** * @brief Close an MQTT session by sending MQTT DISCONNECT. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if DISCONNECT was successfully sent; * EXIT_FAILURE otherwise. */ -static int disconnectMqttSession( MQTTContext_t * pContext ); +static int disconnectMqttSession( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT SUBSCRIBE to subscribe to #MQTT_EXAMPLE_TOPIC * defined at the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static int subscribeToTopic( MQTTContext_t * pContext ); +static int subscribeToTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT UNSUBSCRIBE to unsubscribe from * #MQTT_EXAMPLE_TOPIC defined at the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ); +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at * the top of the file. * - * @param[in] pContext MQTT context pointer. + * @param[in] pMqttContext MQTT context pointer. * * @return EXIT_SUCCESS if PUBLISH was successfully sent; * EXIT_FAILURE otherwise. */ -static int publishToTopic( MQTTContext_t * pContext ); - -/*-----------------------------------------------------------*/ - -static int connectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ) -{ - int status = EXIT_SUCCESS; - struct addrinfo hints, * pIndex, * pListHead = NULL; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - struct timeval transportTimeout; - - /* Add hints to retrieve only TCP sockets in getaddrinfo. */ - ( void ) memset( &hints, 0, sizeof( hints ) ); - /* Address family of either IPv4 or IPv6. */ - hints.ai_family = AF_UNSPEC; - /* TCP Socket. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, &hints, &pListHead ); - - if( status != -1 ) - { - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) - { - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - } - - status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( status == -1 ) - { - close( *pTcpSocket ); - } - else - { - break; - } - } - - if( pIndex == NULL ) - { - /* Fail if no connection could be established. */ - status = EXIT_FAILURE; - LogError( ( "Located but could not connect to MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - } - else - { - status = EXIT_SUCCESS; - LogInfo( ( "TCP connection established with %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - } - } - else - { - LogError( ( "Could not locate MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - status = EXIT_FAILURE; - } - - /* Set the socket option for send and receive timeouts. */ - if( status == EXIT_SUCCESS ) - { - transportTimeout.tv_sec = 0; - transportTimeout.tv_usec = ( TRANSPORT_SEND_RECV_TIMEOUT_MS * 1000 ); - - /* Set the receive timeout. */ - if( setsockopt( *pTcpSocket, - SOL_SOCKET, - SO_RCVTIMEO, - ( char * ) &transportTimeout, - sizeof( transportTimeout ) ) < 0 ) - { - LogError( ( "Setting socket receive timeout failed." ) ); - status = EXIT_FAILURE; - } - - /* Set the send timeout. */ - if( setsockopt( *pTcpSocket, - SOL_SOCKET, - SO_SNDTIMEO, - ( char * ) &transportTimeout, - sizeof( transportTimeout ) ) < 0 ) - { - LogError( ( "Setting socket send timeout failed." ) ); - status = EXIT_FAILURE; - } - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return status; -} +static int publishToTopic( MQTTContext_t * pMqttContext ); /*-----------------------------------------------------------*/ -static int connectToServerWithBackoffRetries( int * pTcpSocket ) +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS; - bool backoffSuccess = true; + int returnStatus = EXIT_SUCCESS; + bool retriesArePending = true; + SocketStatus_t socketStatus = SOCKETS_SUCCESS; TransportReconnectParams_t reconnectParams; + ServerInfo_t serverInfo; + + /* Initialize information to connect to the MQTT broker. */ + serverInfo.pHostName = BROKER_ENDPOINT; + serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; + serverInfo.port = BROKER_PORT; /* Initialize reconnect attempts and interval */ Transport_ReconnectParamsReset( &reconnectParams ); @@ -479,104 +298,31 @@ static int connectToServerWithBackoffRetries( int * pTcpSocket ) do { /* Establish a TCP connection with the MQTT broker. This example connects - * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at - * the top of this file. */ + * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT + * at the demo config header. */ LogInfo( ( "Creating a TCP connection to %.*s:%d.", BROKER_ENDPOINT_LENGTH, BROKER_ENDPOINT, BROKER_PORT ) ); - status = connectToServer( BROKER_ENDPOINT, BROKER_PORT, pTcpSocket ); + socketStatus = Plaintext_Connect( pNetworkContext, + &serverInfo, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); - if( status == EXIT_FAILURE ) + if( socketStatus != SOCKETS_SUCCESS ) { - LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter.", - ( reconnectParams.reconnectTimeoutSec > MAX_RECONNECT_TIMEOUT_SECONDS ) ? MAX_RECONNECT_TIMEOUT_SECONDS : reconnectParams.reconnectTimeoutSec ) ); - backoffSuccess = Transport_ReconnectBackoffAndSleep( &reconnectParams ); + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter." ) ); + retriesArePending = Transport_ReconnectBackoffAndSleep( &reconnectParams ); } - if( backoffSuccess == false ) + if( retriesArePending == false ) { LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + returnStatus = EXIT_FAILURE; } - } while( ( status == EXIT_FAILURE ) && ( backoffSuccess == true ) ); - - return status; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportSend( NetworkContext_t tcpSocket, - const void * pMessage, - size_t bytesToSend ) -{ - int32_t bytesSend = 0; - - bytesSend = send( ( int ) tcpSocket, pMessage, bytesToSend, 0 ); - - if( bytesSend < 0 ) - { - /* Check if it was time out */ - if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) - { - /* Set return value to 0 to indicate that send was timed out. */ - bytesSend = 0; - } - } + } while( ( socketStatus != SOCKETS_SUCCESS ) && ( retriesArePending == true ) ); - return bytesSend; -} - -/*-----------------------------------------------------------*/ - -static int32_t transportRecv( NetworkContext_t tcpSocket, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - - bytesReceived = ( int32_t ) recv( ( int ) tcpSocket, pBuffer, bytesToRecv, 0 ); - - if( bytesReceived == 0 ) - { - /* Server closed the connection, treat it as an error. */ - bytesReceived = -1; - } - else if( bytesReceived < 0 ) - { - /* Check if it was time out */ - if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) ) - { - /* Set return value to 0 to indicate nothing to receive */ - bytesReceived = 0; - } - } - else - { - /* EMPTY else */ - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -static uint32_t getTimeMs( void ) -{ - uint32_t timeMs; - struct timespec timeSpec; - - /* Get the MONOTONIC time. */ - clock_gettime( CLOCK_MONOTONIC, &timeSpec ); - - /* Calculate the milliseconds from timespec. */ - timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) - + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); - - /* Reduce globalEntryTime from obtained time so as to always return the - * elapsed time in the application. */ - timeMs = ( uint32_t ) ( timeMs - globalEntryTimeMs ); - - return timeMs; + return returnStatus; } /*-----------------------------------------------------------*/ @@ -586,19 +332,23 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, { assert( pPublishInfo != NULL ); + ( void ) packetIdentifier; + /* Process incoming Publish. */ LogInfo( ( "Incoming QOS : %d.", pPublishInfo->qos ) ); - /* Verify the received publish is for the we have subscribed to. */ + /* Verify the received publish is for the topic we have subscribed to. */ if( ( pPublishInfo->topicNameLength == MQTT_EXAMPLE_TOPIC_LENGTH ) && ( 0 == strncmp( MQTT_EXAMPLE_TOPIC, pPublishInfo->pTopicName, pPublishInfo->topicNameLength ) ) ) { - LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.", + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n" + "Incoming Publish message Packet Id is %u.\n" + "Incoming Publish Message : %.*s.\n\n", pPublishInfo->topicNameLength, - pPublishInfo->pTopicName ) ); - LogInfo( ( "Incoming Publish Message : %.*s.", + pPublishInfo->pTopicName, + packetIdentifier, ( int ) pPublishInfo->payloadLength, ( const char * ) pPublishInfo->pPayload ) ); } @@ -612,12 +362,12 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, /*-----------------------------------------------------------*/ -static void eventCallback( MQTTContext_t * pContext, +static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { - assert( pContext != NULL ); + assert( pMqttContext != NULL ); assert( pPacketInfo != NULL ); /* Handle incoming publish. The lower 4 bits of the publish packet @@ -635,7 +385,7 @@ static void eventCallback( MQTTContext_t * pContext, switch( pPacketInfo->type ) { case MQTT_PACKET_TYPE_SUBACK: - LogInfo( ( "Subscribed to the topic %.*s.", + LogInfo( ( "Subscribed to the topic %.*s.\n\n", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); /* Make sure ACK packet identifier matches with Request packet identifier. */ @@ -643,7 +393,7 @@ static void eventCallback( MQTTContext_t * pContext, break; case MQTT_PACKET_TYPE_UNSUBACK: - LogInfo( ( "Unsubscribed from the topic %.*s.", + LogInfo( ( "Unsubscribed from the topic %.*s.\n\n", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); /* Make sure ACK packet identifier matches with Request packet identifier. */ @@ -651,12 +401,16 @@ static void eventCallback( MQTTContext_t * pContext, break; case MQTT_PACKET_TYPE_PINGRESP: - LogInfo( ( "PIGRESP received." ) ); + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogWarn( ( "PINGRESP should not be handled by the application " + "callback when using MQTT_ProcessLoop.\n\n" ) ); break; /* Any other packet type is invalid. */ default: - LogError( ( "Unknown packet type received:(%02x).", + LogError( ( "Unknown packet type received:(%02x).\n\n", pPacketInfo->type ) ); } } @@ -664,29 +418,26 @@ static void eventCallback( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ -static int establishMqttSession( MQTTContext_t * pContext, - int tcpSocket ) +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; bool sessionPresent; - MQTTTransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + TransportInterface_t transport; - assert( pContext != NULL ); - assert( tcpSocket >= 0 ); - - /* The network buffer must remain valid for the lifetime of the MQTT context. */ - static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + assert( pMqttContext != NULL ); + assert( pNetworkContext != NULL ); /* Fill in TransportInterface send and receive function pointers. * For this demo, TCP sockets are used to send and receive data * from network. Network context is socket file descriptor.*/ - transport.networkContext = ( NetworkContext_t ) tcpSocket; - transport.send = transportSend; - transport.recv = transportRecv; + transport.pNetworkContext = pNetworkContext; + transport.send = Plaintext_Send; + transport.recv = Plaintext_Recv; /* Fill the values for network buffer. */ networkBuffer.pBuffer = buffer; @@ -698,80 +449,94 @@ static int establishMqttSession( MQTTContext_t * pContext, /* Application callback for getting the time for MQTT library. This time * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = getTimeMs; + callbacks.getTime = Clock_GetTimeMs; /* Initialize MQTT library. */ - MQTT_Init( pContext, &transport, &callbacks, &networkBuffer ); - - /* Establish MQTT session with a CONNECT packet. */ - - /* Start with a clean session i.e. direct the MQTT broker to discard any - * previous session data. Also, establishing a connection with clean session - * will ensure that the broker does not store any data when this client - * gets disconnected. */ - connectInfo.cleanSession = true; - - /* The client identifier is used to uniquely identify this MQTT client to - * the MQTT broker. In a production device the identifier can be something - * unique, such as a device serial number. */ - connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; - - /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ - connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; - - /* Username and password for authentication. Not used in this demo. */ - connectInfo.pUserName = NULL; - connectInfo.userNameLength = 0; - connectInfo.pPassword = NULL; - connectInfo.passwordLength = 0; - - /* Send MQTT CONNECT packet to broker. */ - mqttStatus = MQTT_Connect( pContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, &sessionPresent ); + mqttStatus = MQTT_Init( pMqttContext, &transport, &callbacks, &networkBuffer ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; - LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + returnStatus = EXIT_FAILURE; + LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); } else { - LogInfo( ( "MQTT connection successfully established with broker." ) ); + /* Establish MQTT session by sending a CONNECT packet. */ + + /* Start with a clean session i.e. direct the MQTT broker to discard any + * previous session data. Also, establishing a connection with clean session + * will ensure that the broker does not store any data when this client + * gets disconnected. */ + connectInfo.cleanSession = true; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + /* The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this demo. */ + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0U; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + + /* Send MQTT CONNECT packet to broker. */ + mqttStatus = MQTT_Connect( pMqttContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, &sessionPresent ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + } + else + { + LogInfo( ( "MQTT connection successfully established with broker.\n\n" ) ); + } } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int disconnectMqttSession( MQTTContext_t * pContext ) +static int disconnectMqttSession( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + MQTTStatus_t mqttStatus = MQTTSuccess; + int returnStatus = EXIT_SUCCESS; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Send DISCONNECT. */ - MQTTStatus_t mqttStatus = MQTT_Disconnect( pContext ); + mqttStatus = MQTT_Disconnect( pMqttContext ); if( mqttStatus != MQTTSuccess ) { LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int subscribeToTopic( MQTTContext_t * pContext ) +static int subscribeToTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Start with everything at 0. */ ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); @@ -782,10 +547,10 @@ static int subscribeToTopic( MQTTContext_t * pContext ) pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; /* Generate packet identifier for the SUBSCRIBE packet. */ - globalSubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + globalSubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); /* Send SUBSCRIBE packet. */ - mqttStatus = MQTT_Subscribe( pContext, + mqttStatus = MQTT_Subscribe( pMqttContext, pSubscriptionList, sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), globalSubscribePacketIdentifier ); @@ -794,27 +559,27 @@ static int subscribeToTopic( MQTTContext_t * pContext ) { LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { - LogInfo( ( "SUBSCRIBE sent for topic %.*s to broker.", + LogInfo( ( "SUBSCRIBE sent for topic %.*s to broker.\n\n", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) +static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Start with everything at 0. */ ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); @@ -826,10 +591,10 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) pSubscriptionList[ 0 ].topicFilterLength = MQTT_EXAMPLE_TOPIC_LENGTH; /* Generate packet identifier for the UNSUBSCRIBE packet. */ - globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pContext ); + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); /* Send UNSUBSCRIBE packet. */ - mqttStatus = MQTT_Unsubscribe( pContext, + mqttStatus = MQTT_Unsubscribe( pMqttContext, pSubscriptionList, sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), globalUnsubscribePacketIdentifier ); @@ -838,27 +603,27 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext ) { LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", mqttStatus ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { - LogInfo( ( "UNSUBSCRIBE sent for topic %.*s to broker.", + LogInfo( ( "UNSUBSCRIBE sent for topic %.*s to broker.\n\n", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int publishToTopic( MQTTContext_t * pContext ) +static int publishToTopic( MQTTContext_t * pMqttContext ) { - int status = EXIT_SUCCESS; - MQTTStatus_t mqttSuccess; + int returnStatus = EXIT_SUCCESS; + MQTTStatus_t mqttSuccess = MQTTSuccess; MQTTPublishInfo_t publishInfo; - assert( pContext != NULL ); + assert( pMqttContext != NULL ); /* Some fields not used by this demo so start with everything at 0. */ ( void ) memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); @@ -872,15 +637,15 @@ static int publishToTopic( MQTTContext_t * pContext ) /* Send PUBLISH packet. Packet Id is not used for a QoS0 publish. * Hence 0 is passed as packet id. */ - mqttSuccess = MQTT_Publish( pContext, + mqttSuccess = MQTT_Publish( pMqttContext, &publishInfo, 0U ); - if( status != MQTTSuccess ) + if( returnStatus != MQTTSuccess ) { LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", mqttSuccess ) ); - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; } else { @@ -889,23 +654,20 @@ static int publishToTopic( MQTTContext_t * pContext ) MQTT_EXAMPLE_TOPIC ) ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ -static int subscribePublishLoop( int tcpSocket ) +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) { - int status = EXIT_SUCCESS; + int returnStatus = EXIT_SUCCESS; bool mqttSessionEstablished = false; - MQTTContext_t context; + MQTTContext_t mqttContext; MQTTStatus_t mqttStatus; uint32_t publishCount = 0; const uint32_t maxPublishCount = MQTT_PUBLISH_COUNT_PER_LOOP; - /* Get the entry time to application. */ - globalEntryTimeMs = getTimeMs(); - /* Establish MQTT session on top of TCP connection. */ LogInfo( ( "Creating an MQTT connection to %.*s.", BROKER_ENDPOINT_LENGTH, @@ -913,17 +675,17 @@ static int subscribePublishLoop( int tcpSocket ) /* Sends an MQTT Connect packet over the already connected TCP socket * tcpSocket, and waits for connection acknowledgment (CONNACK) packet. */ - status = establishMqttSession( &context, tcpSocket ); + returnStatus = establishMqttSession( &mqttContext, pNetworkContext ); - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Keep a flag for indicating if MQTT session is established. This - * flag will mark that an MQTT DISCONNECT has to be send at the end + * flag will mark that an MQTT DISCONNECT has to be sent at the end * of the demo even if there are intermediate failures. */ mqttSessionEstablished = true; } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* The client is now connected to the broker. Subscribe to the topic * as specified in MQTT_EXAMPLE_TOPIC at the top of this file by sending a @@ -931,38 +693,41 @@ static int subscribePublishLoop( int tcpSocket ) * subscribed to, so it will expect all the messages it sends to the broker * to be sent back to it from the broker. This demo uses QOS0 in Subscribe, * therefore, the Publish messages received from the broker will have QOS0. */ - status = subscribeToTopic( &context ); + LogInfo( ( "Subscribing to the MQTT topic %.*s.", + MQTT_EXAMPLE_TOPIC_LENGTH, + MQTT_EXAMPLE_TOPIC ) ); + returnStatus = subscribeToTopic( &mqttContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Process incoming packet from the broker. Acknowledgment for subscription * ( SUBACK ) will be received here. However after sending the subscribe, the * client may receive a publish before it receives a subscribe ack. Since this * demo is subscribing to the topic to which no one is publishing, probability * of receiving publish message before subscribe ack is zero; but application - * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to * receive packet from network. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT_ProcessLoop returned with status = %u.", mqttStatus ) ); } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Publish messages with QOS0, receive incoming messages and * send keep alive messages. */ for( publishCount = 0; publishCount < maxPublishCount; publishCount++ ) { - LogInfo( ( "Publish to the MQTT topic %.*s.", + LogInfo( ( "Sending Publish to the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = publishToTopic( &context ); + returnStatus = publishToTopic( &mqttContext ); /* Calling MQTT_ProcessLoop to process incoming publish echo, since * application subscribed to the same topic the broker will send @@ -970,32 +735,38 @@ static int subscribePublishLoop( int tcpSocket ) * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS * has expired since the last MQTT packet sent and receive * ping responses. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); - LogInfo( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + LogWarn( ( "MQTT_ProcessLoop returned with status = %u.", + mqttStatus ) ); + } + + LogInfo( ( "Delay before continuing to next iteration.\n\n" ) ); /* Leave connection idle for some time. */ sleep( DELAY_BETWEEN_PUBLISHES_SECONDS ); } } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Unsubscribe from the topic. */ - LogInfo( ( "Unsubscribe from the MQTT topic %.*s.", + LogInfo( ( "Unsubscribing from the MQTT topic %.*s.", MQTT_EXAMPLE_TOPIC_LENGTH, MQTT_EXAMPLE_TOPIC ) ); - status = unsubscribeFromTopic( &context ); + returnStatus = unsubscribeFromTopic( &mqttContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { /* Process Incoming UNSUBACK packet from the broker. */ - mqttStatus = MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &mqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); if( mqttStatus != MQTTSuccess ) { - status = EXIT_FAILURE; + returnStatus = EXIT_FAILURE; LogError( ( "MQTT_ProcessLoop returned with status = %u.", mqttStatus ) ); } @@ -1010,20 +781,21 @@ static int subscribePublishLoop( int tcpSocket ) BROKER_ENDPOINT_LENGTH, BROKER_ENDPOINT ) ); - if( status == EXIT_FAILURE ) + if( returnStatus == EXIT_FAILURE ) { /* Returned status is not used to update the local status as there * were failures in demo execution. */ - ( void ) disconnectMqttSession( &context ); + ( void ) disconnectMqttSession( &mqttContext ); } else { - status = disconnectMqttSession( &context ); + returnStatus = disconnectMqttSession( &mqttContext ); } } - return status; + return returnStatus; } + /*-----------------------------------------------------------*/ /** @@ -1041,9 +813,8 @@ static int subscribePublishLoop( int tcpSocket ) int main( int argc, char ** argv ) { - int status = EXIT_SUCCESS; - int tcpSocket = -1; - bool mqttSessionEstablished = false; + int returnStatus = EXIT_SUCCESS; + NetworkContext_t networkContext; ( void ) argc; ( void ) argv; @@ -1055,40 +826,36 @@ int main( int argc, * attemps are reached or maximum timout value is reached. The function * returns EXIT_FAILURE if the TCP connection cannot be established to * broker after configured number of attemps. */ - status = connectToServerWithBackoffRetries( &tcpSocket ); + returnStatus = connectToServerWithBackoffRetries( &networkContext ); - if( status == EXIT_FAILURE ) + if( returnStatus == EXIT_FAILURE ) { /* Log error to indicate connection failure after all - * reconnect attempts are over */ + * reconnect attempts are over. */ LogError( ( "Failed to connect to MQTT broker %.*s.", BROKER_ENDPOINT_LENGTH, BROKER_ENDPOINT ) ); } else { - /* If TCP connection is successful, execute Subscribe Publish loop */ - status = subscribePublishLoop( tcpSocket ); + /* If TCP connection is successful, execute Subscribe/Publish loop. */ + returnStatus = subscribePublishLoop( &networkContext ); } - if( status == EXIT_SUCCESS ) + if( returnStatus == EXIT_SUCCESS ) { - /* Log message indicating an iteration completed successfully */ + /* Log message indicating an iteration completed successfully. */ LogInfo( ( "Demo completed successfully." ) ); } - /* Close the network connection. */ - if( tcpSocket != -1 ) - { - shutdown( tcpSocket, SHUT_RDWR ); - close( tcpSocket ); - } + /* Close the TCP connection. */ + ( void ) Plaintext_Disconnect( &networkContext ); LogInfo( ( "Short delay before starting the next iteration....\n" ) ); sleep( MQTT_SUBPUB_LOOP_DELAY_SECONDS ); } - return status; + return returnStatus; } /*-----------------------------------------------------------*/ diff --git a/demos/transport/CMakeLists.txt b/demos/transport/CMakeLists.txt deleted file mode 100644 index 4eb10057cd..0000000000 --- a/demos/transport/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# CMake library target for transport_utils. -set(OPENSSL_USE_STATIC_LIBS TRUE) -find_package(OpenSSL REQUIRED) - -set( CMAKE_THREAD_PREFER_PTHREAD ON ) -find_package(Threads REQUIRED) - -add_library( transport_utils transport_utils.c ) - -target_link_libraries( - transport_utils - PRIVATE - OpenSSL::Crypto - OpenSSL::SSL - # SSL uses Threads and on some platforms require explicit linking. - Threads::Threads - # SSL uses Dynamic Loading and on some platforms requires explicit linking. - ${CMAKE_DL_LIBS} -) - -target_include_directories( - transport_utils - PUBLIC - ${CMAKE_CURRENT_LIST_DIR} - ${LOGGING_INCLUDE_DIRS} -) diff --git a/demos/transport/transport_config.h b/demos/transport/transport_config.h deleted file mode 100644 index 0d777968bd..0000000000 --- a/demos/transport/transport_config.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef TRANSPORT_CONFIG_H_ -#define TRANSPORT_CONFIG_H_ - -/**************************************************/ -/******* DO NOT CHANGE the following order ********/ -/**************************************************/ - -/* Include logging header files and define logging macros in the following order: - * 1. Include the header file "logging_levels.h". - * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on - * the logging configuration for TLS Utils library. - * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. - */ - -#include "logging_levels.h" - -/* Logging configuration for the Transport. */ -#ifndef LIBRARY_LOG_NAME - #define LIBRARY_LOG_NAME "TRANSPORT" -#endif - -#ifndef LIBRARY_LOG_LEVEL - #define LIBRARY_LOG_LEVEL LOG_NONE -#endif -#include "logging_stack.h" - -/************ End of logging configuration ****************/ - -#endif /* ifndef TRANSPORT_CONFIG_H_ */ diff --git a/demos/transport/transport_utils.c b/demos/transport/transport_utils.c deleted file mode 100644 index 07311aa856..0000000000 --- a/demos/transport/transport_utils.c +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file transport_utils.c - * @brief Implementation of wrapper utilities for using OpenSSL as the TLS stack - * on POSIX platform. - */ - -/* POSIX socket includes. */ -#include -#include -#include -#include - -#include -#include - -/* Standard includes. */ -#include -#include -#include - -/* Include config file before non-system headers. */ -#include "transport_config.h" - -/* Header file for the library. */ -#include "transport_utils.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief Transport timeout in milliseconds for transport send and receive. - */ -#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 20 ) - -/*-----------------------------------------------------------*/ - -int tcpConnectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ) -{ - int status = EXIT_SUCCESS; - struct addrinfo hints, * pIndex, * pListHead = NULL; - struct sockaddr * pServerInfo; - uint16_t netPort = htons( port ); - socklen_t serverInfoLength; - - /* Add hints to retrieve only TCP sockets in getaddrinfo. */ - ( void ) memset( &hints, 0x00, sizeof( hints ) ); - /* Address family of either IPv4 or IPv6. */ - hints.ai_family = AF_UNSPEC; - /* TCP Socket. */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - LogInfo( ( "Looking up DNS for endpoint: %s", pServer ) ); - - /* Perform a DNS lookup on the given host name. */ - status = getaddrinfo( pServer, NULL, &hints, &pListHead ); - - LogInfo( ( "Resolved DNS for endpoint: %s", pServer ) ); - - if( status != -1 ) - { - /* Attempt to connect to one of the retrieved DNS records. */ - for( pIndex = pListHead; pIndex != NULL; pIndex = pIndex->ai_next ) - { - LogInfo( ( "Attempting to create TCP client socket" ) ); - - *pTcpSocket = socket( pIndex->ai_family, pIndex->ai_socktype, pIndex->ai_protocol ); - - if( *pTcpSocket == -1 ) - { - continue; - } - - pServerInfo = pIndex->ai_addr; - - if( pServerInfo->sa_family == AF_INET ) - { - /* IPv4 */ - ( ( struct sockaddr_in * ) pServerInfo )->sin_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in ); - } - else - { - /* IPv6 */ - ( ( struct sockaddr_in6 * ) pServerInfo )->sin6_port = netPort; - serverInfoLength = sizeof( struct sockaddr_in6 ); - } - - LogInfo( ( "Attempting to connect with socket: Fd=%d", *pTcpSocket ) ); - - status = connect( *pTcpSocket, pServerInfo, serverInfoLength ); - - if( status == -1 ) - { - LogError( ( "Connection with socket failed: Fd=%d", *pTcpSocket ) ); - close( *pTcpSocket ); - } - else - { - break; - } - } - - if( pIndex == NULL ) - { - /* Fail if no connection could be established. */ - status = EXIT_FAILURE; - LogError( ( "Located but could not connect to MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - } - else - { - status = EXIT_SUCCESS; - LogInfo( ( "TCP connection established with %.*s.\n\n", - ( int ) strlen( pServer ), - pServer ) ); - } - } - else - { - LogError( ( "Could not locate MQTT broker %.*s.", - ( int ) strlen( pServer ), - pServer ) ); - status = EXIT_FAILURE; - } - - if( pListHead != NULL ) - { - freeaddrinfo( pListHead ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -int tlsSetup( int tcpSocket, - const char * pServerCertPath, - SSL ** pSslContext ) -{ - int status = EXIT_FAILURE, sslStatus = 0; - FILE * pRootCaFile = NULL; - X509 * pRootCa = NULL; - - assert( tcpSocket >= 0 ); - - /* Setup for creating a TLS client. */ - SSL_CTX * pSslSetup = SSL_CTX_new( TLS_client_method() ); - - if( pSslSetup != NULL ) - { - /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. - * The mask returned by SSL_CTX_set_mode does not need to be checked. */ - ( void ) SSL_CTX_set_mode( pSslSetup, SSL_MODE_AUTO_RETRY ); - - /* OpenSSL does not provide a single function for reading and loading certificates - * from files into stores, so the file API must be called. */ - pRootCaFile = fopen( pServerCertPath, "r" ); - - if( pRootCaFile != NULL ) - { - pRootCa = PEM_read_X509( pRootCaFile, NULL, NULL, NULL ); - } - else - { - LogError( ( "Unable to find the certificate file in the path" - " provided by SERVER_CERT_PATH(%s).", - pServerCertPath ) ); - } - - if( pRootCa != NULL ) - { - sslStatus = X509_STORE_add_cert( SSL_CTX_get_cert_store( pSslSetup ), - pRootCa ); - } - else - { - LogError( ( "Failed to parse the server certificate from" - " file %s. Please validate the certificate.", - pServerCertPath ) ); - } - } - - /* Set up the TLS connection. */ - if( sslStatus == 1 ) - { - /* Create a new SSL context. */ - *pSslContext = SSL_new( pSslSetup ); - - if( *pSslContext != NULL ) - { - /* Enable SSL peer verification. */ - SSL_set_verify( *pSslContext, SSL_VERIFY_PEER, NULL ); - - sslStatus = SSL_set_fd( *pSslContext, tcpSocket ); - } - else - { - LogError( ( "Failed to create a new SSL context." ) ); - sslStatus = 0; - } - - /* Perform the TLS handshake. */ - if( sslStatus == 1 ) - { - sslStatus = SSL_connect( *pSslContext ); - } - else - { - LogError( ( "Failed to set the socket fd to SSL context." ) ); - } - - if( sslStatus == 1 ) - { - if( SSL_get_verify_result( *pSslContext ) != X509_V_OK ) - { - sslStatus = 0; - } - } - else - { - LogError( ( "Failed to perform TLS handshake." ) ); - } - - /* Clean up on error. */ - if( sslStatus == 0 ) - { - SSL_free( *pSslContext ); - *pSslContext = NULL; - } - } - else - { - LogError( ( "Failed to add certificate to store." ) ); - } - - if( pRootCaFile != NULL ) - { - ( void ) fclose( pRootCaFile ); - } - - if( pRootCa != NULL ) - { - X509_free( pRootCa ); - } - - if( pSslSetup != NULL ) - { - SSL_CTX_free( pSslSetup ); - } - - /* Log failure or success and update the correct exit status to return. */ - if( sslStatus == 0 ) - { - LogError( ( "Failed to establish a TLS connection." ) ); - status = EXIT_FAILURE; - } - else - { - LogInfo( ( "Established a TLS connection." ) ); - status = EXIT_SUCCESS; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void closeTlsSession( SSL * pSslContext ) -{ - /* Need to call SSL shutdown twice: once to send "close notify" and - * once more to receive the peer's "close notify". */ - if( SSL_shutdown( pSslContext ) == 0 ) - { - ( void ) SSL_shutdown( pSslContext ); - } - - SSL_free( pSslContext ); -} - -/*-----------------------------------------------------------*/ - -void closeTcpConnection( int tcpSocket ) -{ -/* Close TCP connection if established. */ - if( tcpSocket != -1 ) - { - shutdown( tcpSocket, SHUT_RDWR ); - close( tcpSocket ); - } -} - -/*-----------------------------------------------------------*/ - -int32_t transportSend( SSL * pSslContext, - const void * pMessage, - size_t bytesToSend ) -{ - int32_t bytesSent = 0; - int pollStatus = 0; - struct pollfd fileDescriptor = - { - .events = POLLOUT, - .revents = 0 - }; - - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pSslContext ); - - /* Poll the file descriptor to check if SSL_Write can be done now. */ - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - - if( pollStatus > 0 ) - { - bytesSent = ( int32_t ) SSL_write( pSslContext, pMessage, bytesToSend ); - LogInfo( ( "Bytes sent over SSL: %d", bytesSent ) ); - } - else if( pollStatus == 0 ) - { - LogDebug( ( "Timed out while polling SSL socket for write buffer availability." ) ); - } - else - { - LogError( ( "Polling of the SSL socket for write buffer availability failed" - " with status %d.", - pollStatus ) ); - bytesSent = -1; - } - - return bytesSent; -} - -/*-----------------------------------------------------------*/ - -int32_t transportRecv( SSL * pSslContext, - void * pBuffer, - size_t bytesToRecv ) -{ - int32_t bytesReceived = 0; - int pollStatus = -1, bytesAvailableToRead = 0; - struct pollfd fileDescriptor = - { - .events = POLLIN | POLLPRI, - .revents = 0 - }; - - /* Set the file descriptor for poll. */ - fileDescriptor.fd = SSL_get_fd( pSslContext ); - - /* Check if there are any pending data available for read. */ - bytesAvailableToRead = SSL_pending( pSslContext ); - - /* Poll only if there is no data available yet to read. */ - if( bytesAvailableToRead <= 0 ) - { - pollStatus = poll( &fileDescriptor, 1, TRANSPORT_SEND_RECV_TIMEOUT_MS ); - } - - /* SSL read of data. */ - if( ( pollStatus > 0 ) || ( bytesAvailableToRead > 0 ) ) - { - bytesReceived = ( int32_t ) SSL_read( pSslContext, pBuffer, bytesToRecv ); - } - /* Poll timed out. */ - else if( pollStatus == 0 ) - { - LogDebug( ( "Poll timed out and there is no data to read from the buffer." ) ); - } - else - { - LogError( ( "Poll returned with status = %d.", pollStatus ) ); - bytesReceived = -1; - } - - return bytesReceived; -} - -/*-----------------------------------------------------------*/ - -uint32_t getTimeMs( void ) -{ - uint32_t timeMs; - struct timespec timeSpec; - - /* Get the MONOTONIC time. */ - clock_gettime( CLOCK_MONOTONIC, &timeSpec ); - - /* Calculate the milliseconds from timespec. */ - timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) - + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); - - return timeMs; -} diff --git a/demos/transport/transport_utils.h b/demos/transport/transport_utils.h deleted file mode 100644 index 4d88eb1943..0000000000 --- a/demos/transport/transport_utils.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file transport_utils.h - * @brief Wrapper utilities for using OpenSSL based TLS stack for demos and - * integration tests. - */ - -#ifndef TRANSPORT_UTILS_H_ -#define TRANSPORT_UTILS_H_ - -/* OpenSSL include. */ -#include - -/** - * @brief Creates a TCP connection to the MQTT broker as specified by - * the @p pServer and @p port parameters, and populate the tcp socket - * descriptor output parameter, if connection is successful. - * - * @param[in] pServer Host name of server. - * @param[in] port Server port. - * @param[out] pTcpSocket The output parameter to return the created socket - * descriptor. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. - */ -int tcpConnectToServer( const char * pServer, - uint16_t port, - int * pTcpSocket ); - -/** - * @brief Closes a TCP connection with a server, if valid. - * - * @param[in] tcpSocket The socket for the TCP connection. - */ -void closeTcpConnection( int tcpSocket ); - -/** - * @brief Set up a TLS connection over an existing TCP connection. - * - * @param[in] tcpSocket Existing TCP connection. - * @param[in] pServerCertPath The absolute file path to the server certificate. - * @param[out] pSslContext The output parameter to return the created SSL context. - * - * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. - */ -int tlsSetup( int tcpSocket, - const char * pServerCertPath, - SSL ** pSslContext ); - -/** - * @brief Closes an established TLS session. - * - * @param[in] pSSlContext The SSL context associated with the TLS session. - */ -void closeTlsSession( SSL * pSslContext ); - -/** - * @brief The transport send function provided to the MQTT context. - * - * @param[in] pSslContext Pointer to SSL context. - * @param[in] pMessage Data to send. - * @param[in] bytesToSend Length of data to send. - * - * @return Number of bytes sent; negative value on error; - * 0 for timeout or 0 bytes sent. - */ -int32_t transportSend( SSL * pSslContext, - const void * pMessage, - size_t bytesToSend ); - -/** - * @brief The transport receive function provided to the MQTT context. - * - * @param[in] pSslContext Pointer to SSL context. - * @param[out] pBuffer Buffer for receiving data. - * @param[in] bytesToRecv Length of data to be received. - * - * @return Number of bytes received; negative value on error; - * 0 for timeout. - */ -int32_t transportRecv( SSL * pSslContext, - void * pBuffer, - size_t bytesToRecv ); - -/** - * @brief The timer query function provided to the MQTT context. - * - * This function returns the elapsed time with reference to #globalEntryTimeMs. - * - * @return Time in milliseconds. - */ -uint32_t getTimeMs( void ); - -#endif /* ifndef TRANSPORT_UTILS_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_config.h b/libraries/standard/mqtt/cbmc/include/mqtt_config.h index 050479dc32..e805a164fc 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_config.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_config.h @@ -22,20 +22,16 @@ /************ End of logging configuration ****************/ -/* Set network context to a socket (int). This is a stub and passed through to - * the application defined transport send and receive. */ -typedef int NetworkContext_t; - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending - * acknowledgement at any time. + * acknowledgement at any time. * * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before * they can be completed. While they are awaiting the acknowledgement, the * client must maintain information about their state. The value of this * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. - * + * * @note This definition must exist in order to compile. 10U is a typical value * used in the MQTT demos. */ diff --git a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h index be1cbf36f3..949f048989 100644 --- a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h +++ b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h @@ -21,15 +21,21 @@ #ifndef NETWORK_INTERFACE_STUBS_H_ #define NETWORK_INTERfACE_STUBS_H_ +/* Mock a network context for the CBMC proofs. */ +struct NetworkContext +{ + int NetworkContext; +}; + /** * @brief Application defined network interface receive function. * - * @param[in] context Application defined network interface context. + * @param[in] pNetworkContext Application defined network interface context. * @param[out] pBuffer MQTT network receive buffer. * @param[in] bytesToRecv MQTT requested bytes. * @return Any value from INT32_MIN to INT32_MAX. */ -int32_t NetworkInterfaceReceiveStub( NetworkContext_t context, +int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ); diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c index 5067669267..75664ba4f4 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c @@ -42,6 +42,6 @@ void harness() __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); MQTT_GetIncomingPacketTypeAndLength( NetworkInterfaceReceiveStub, - networkContext, + &networkContext, pIncomingPacket ); } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c index c424d1a60a..94bad14628 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c @@ -30,7 +30,7 @@ void harness() { MQTTContext_t * pContext = NULL; - MQTTTransportInterface_t * pTransportInterface = NULL; + TransportInterface_t * pTransportInterface = NULL; MQTTApplicationCallbacks_t * pCallbacks = NULL; MQTTFixedBuffer_t * pNetworkBuffer = NULL; diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines index e79676922a..b18113e559 100644 --- a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines @@ -22,6 +22,7 @@ INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/cbmc/include INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/include INCLUDES += -I$(SRCDIR)/libraries/standard/mqtt/src INCLUDES += -I$(SRCDIR)/demos/logging-stack +INCLUDES += -I$(SRCDIR)/platform/include # Preprocessor definitions -D... DEFINES += -Dmqtt_EXPORTS diff --git a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c index ae07809ae1..30e6188695 100644 --- a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +++ b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c @@ -22,7 +22,7 @@ #include "mqtt.h" #include "network_interface_stubs.h" -int32_t NetworkInterfaceReceiveStub( NetworkContext_t context, +int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 7dfef12857..8db419707c 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -26,6 +26,8 @@ #include "mqtt_config.h" #include "mqtt_lightweight.h" +#include "transport_interface.h" + /** * @brief Invalid packet identifier. * @@ -42,13 +44,6 @@ typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; struct MQTTContext; typedef struct MQTTContext MQTTContext_t; -struct MQTTTransportInterface; -typedef struct MQTTTransportInterface MQTTTransportInterface_t; - -typedef int32_t (* MQTTTransportSendFunc_t )( NetworkContext_t context, - const void * pMessage, - size_t bytesToSend ); - typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, @@ -85,13 +80,6 @@ typedef enum MQTTPubAckType MQTTPubcomp } MQTTPubAckType_t; -struct MQTTTransportInterface -{ - MQTTTransportSendFunc_t send; - MQTTTransportRecvFunc_t recv; - NetworkContext_t networkContext; -}; - struct MQTTApplicationCallbacks { MQTTGetCurrentTimeFunc_t getTime; @@ -112,7 +100,7 @@ struct MQTTContext MQTTPubAckInfo_t incomingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT ]; size_t incomingPublishCount; - MQTTTransportInterface_t transportInterface; + TransportInterface_t transportInterface; MQTTFixedBuffer_t networkBuffer; uint16_t nextPacketId; @@ -147,7 +135,7 @@ struct MQTTContext * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, - const MQTTTransportInterface_t * pTransportInterface, + const TransportInterface_t * pTransportInterface, const MQTTApplicationCallbacks_t * pCallbacks, const MQTTFixedBuffer_t * pNetworkBuffer ); diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index f1799febc3..9e5dd39647 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -37,6 +37,8 @@ /* Include config file before other headers. */ #include "mqtt_config.h" +#include "transport_interface.h" + /* MQTT packet types. */ #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ #define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ @@ -74,23 +76,6 @@ typedef struct MqttPublishInfo MQTTPublishInfo_t; struct MQTTPacketInfo; typedef struct MQTTPacketInfo MQTTPacketInfo_t; -/** - * @brief Signature of the transport interface receive function. - * - * A function with this signature must be provided to the MQTT library to read - * data off the network. - * - * @param[in] context The network context provided with this function. - * @param[out] pBuffer Buffer to receive network data. - * @param[in] bytesToRecv Bytes to receive from the network. pBuffer must be at - * least this size. - * - * @return The number of bytes received; negative value on failure. - */ -typedef int32_t (* MQTTTransportRecvFunc_t )( NetworkContext_t context, - void * pBuffer, - size_t bytesToRecv ); - /** * @brief Return codes from MQTT functions. */ @@ -530,6 +515,7 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, * #MQTTPacketInfo_t is not valid until this routine has been invoked. * * @param[in] readFunc Transport layer read function pointer. + * @param[in] pNetworkContext The network context pointer provided by the application. * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. This is * where type, remaining length and packet identifier are stored. * @@ -539,8 +525,8 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, * #MQTTBadResponse if an invalid packet is read, and * #MQTTNoDataAvailable if there is nothing to read. */ -MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, - NetworkContext_t networkContext, +MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, + NetworkContext_t * pNetworkContext, MQTTPacketInfo_t * pIncomingPacket ); #endif /* ifndef MQTT_LIGHTWEIGHT_H */ diff --git a/libraries/standard/mqtt/integration-test/CMakeLists.txt b/libraries/standard/mqtt/integration-test/CMakeLists.txt index 343d2cd407..73614eb3b2 100644 --- a/libraries/standard/mqtt/integration-test/CMakeLists.txt +++ b/libraries/standard/mqtt/integration-test/CMakeLists.txt @@ -27,8 +27,6 @@ list(APPEND test_include_directories ${MQTT_INCLUDE_PUBLIC_DIRS} ${MQTT_INCLUDE_PRIVATE_DIRS} ${LOGGING_INCLUDE_DIRS} - # Header path for the TLS server auth utility. - ${ROOT_DIR}/demos/transport ) # ============================= (end edit) =================================== @@ -47,7 +45,8 @@ list(APPEND stest_link_list list(APPEND stest_dep_list ${real_name} - transport_utils + clock_posix + openssl_posix ) set(stest_name "${project_name}_test") @@ -58,3 +57,21 @@ create_test(${stest_name} "${stest_dep_list}" "${test_include_directories}" ) + +# Download the Mosquitto Root CA certificate. +message( "Downloading the Mosquitto Root CA certificate..." ) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) +execute_process( + COMMAND curl --url https://test.mosquitto.org/ssl/mosquitto.org.crt + -o ${CMAKE_CURRENT_LIST_DIR}/certificates/mosquitto.org.crt +) + +# Copy the certificates and client key to the binary directory. +add_custom_command( + TARGET + ${stest_name} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_LIST_DIR}/certificates" + "$/certificates" +) diff --git a/libraries/standard/mqtt/integration-test/mosquitto.org.crt b/libraries/standard/mqtt/integration-test/mosquitto.org.crt deleted file mode 100644 index e76dbd8559..0000000000 --- a/libraries/standard/mqtt/integration-test/mosquitto.org.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL -BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG -A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU -BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv -by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE -BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES -MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp -dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg -UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW -Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA -s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH -3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo -E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT -MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV -6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC -6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf -+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK -sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 -LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE -m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= ------END CERTIFICATE----- diff --git a/libraries/standard/mqtt/integration-test/mqtt_config.h b/libraries/standard/mqtt/integration-test/mqtt_config.h index efaf4cf878..1d8c997003 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_config.h +++ b/libraries/standard/mqtt/integration-test/mqtt_config.h @@ -48,10 +48,6 @@ /************ End of logging configuration ****************/ -/* Set network context to OpenSSL SSL context. */ -#include -typedef SSL * NetworkContext_t; - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index 02f8d054dd..b2b595f57c 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -36,8 +36,11 @@ /* Include paths for public enums, structures, and macros. */ #include "mqtt.h" -/* Include header for TLS utilities. */ -#include "transport_utils.h" +/* Include OpenSSL implementation of transport interface. */ +#include "openssl_posix.h" + +/* Include clock for timer. */ +#include "clock.h" #ifndef BROKER_ENDPOINT #error "BROKER_ENDPOINT should be defined for the MQTT integration tests." @@ -112,6 +115,11 @@ */ #define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200U ) + /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ @@ -157,16 +165,20 @@ static uint16_t globalUnsubscribePacketIdentifier = 0U; static uint16_t globalPublishPacketIdentifier = 0U; /** - * @brief Variable to store TCP socket descriptor used for connecting to and - * disconnecting from the broker. + * @brief Represents the OpenSSL context used for TLS session with the broker + * for tests. */ -static int tcpSocket; +static NetworkContext_t networkContext; /** - * @brief Represents the OpenSSL context used for TLS session with the broker - * for tests. + * @brief Represents the hostname and port of the broker. + */ +static ServerInfo_t serverInfo; + +/** + * @brief TLS credentials needed to connect to the broker. */ -static SSL * pSslContext; +static OpensslCredentials_t opensslCredentials; /** * @brief The context representing the MQTT connection with the broker for @@ -219,7 +231,7 @@ static MQTTPublishInfo_t incomingInfo; * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. * * @param[in] pContext MQTT context pointer. - * @param[in] pSslContext SSL context. + * @param[in] pNetworkContext Network context for OpenSSL transport implementation. * @param[in] createCleanSession Creates a new MQTT session if true. * If false, tries to establish the existing session if there was session * already present in broker. @@ -227,7 +239,7 @@ static MQTTPublishInfo_t incomingInfo; * Session present response is obtained from the CONNACK from broker. */ static void establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext, + NetworkContext_t * pNetworkContext, bool createCleanSession, bool * pSessionPresent ); @@ -249,25 +261,25 @@ static void eventCallback( MQTTContext_t * pContext, /*-----------------------------------------------------------*/ static void establishMqttSession( MQTTContext_t * pContext, - SSL * pSslContext, + NetworkContext_t * pNetworkContext, bool createCleanSession, bool * pSessionPresent ) { MQTTConnectInfo_t connectInfo; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; assert( pContext != NULL ); - assert( pSslContext != NULL ); + assert( pNetworkContext != NULL ); /* The network buffer must remain valid for the lifetime of the MQTT context. */ static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; /* Setup the transport interface object for the library. */ - transport.networkContext = pSslContext; - transport.send = transportSend; - transport.recv = transportRecv; + transport.pNetworkContext = pNetworkContext; + transport.send = Openssl_Send; + transport.recv = Openssl_Recv; /* Fill the values for network buffer. */ networkBuffer.pBuffer = buffer; @@ -279,7 +291,7 @@ static void establishMqttSession( MQTTContext_t * pContext, /* Application callback for getting the time for MQTT library. This time * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = getTimeMs; + callbacks.getTime = Clock_GetTimeMs; /* Initialize MQTT library. */ TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Init( pContext, @@ -355,6 +367,13 @@ static void eventCallback( MQTTContext_t * pContext, TEST_ASSERT_EQUAL( globalSubscribePacketIdentifier, packetIdentifier ); break; + case MQTT_PACKET_TYPE_PINGRESP: + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogDebug( ( "Received PINGRESP" ) ); + break; + case MQTT_PACKET_TYPE_UNSUBACK: /* Set the flag to represent reception of UNSUBACK. */ receivedUnsubAck = true; @@ -376,13 +395,6 @@ static void eventCallback( MQTTContext_t * pContext, packetIdentifier ) ); break; - case MQTT_PACKET_TYPE_PINGRESP: - - /* Nothing to be done from application as library handles - * PINGRESP. */ - LogDebug( ( "Received PINGRESP" ) ); - break; - case MQTT_PACKET_TYPE_PUBREC: /* Set the flag to represent reception of PUBREC. */ receivedPubRec = true; @@ -516,18 +528,24 @@ void setUp() receivedPubComp = false; persistentSession = false; memset( &incomingInfo, 0u, sizeof( MQTTPublishInfo_t ) ); - - /* Establish a TCP connection with the server endpoint. */ - TEST_ASSERT_EQUAL( EXIT_SUCCESS, - tcpConnectToServer( BROKER_ENDPOINT, BROKER_PORT, &tcpSocket ) ); - TEST_ASSERT_NOT_EQUAL( -1, tcpSocket ); - - /* Establish TLS connection on top of TCP connection. */ - TEST_ASSERT_EQUAL( EXIT_SUCCESS, tlsSetup( tcpSocket, SERVER_ROOT_CA_CERT_PATH, &pSslContext ) ); - TEST_ASSERT_NOT_NULL( pSslContext ); + memset( &opensslCredentials, 0u, sizeof( OpensslCredentials_t ) ); + opensslCredentials.pRootCaPath = SERVER_ROOT_CA_CERT_PATH; + serverInfo.pHostName = BROKER_ENDPOINT; + serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; + serverInfo.port = BROKER_PORT; + + /* Establish a TCP connection with the server endpoint, then + * establish TLS session on top of TCP connection. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); /* Establish MQTT session on top of the TCP+TLS connection. */ - establishMqttSession( &context, pSslContext, true, &persistentSession ); + establishMqttSession( &context, &networkContext, true, &persistentSession ); } /* Called after each test method. */ @@ -547,11 +565,8 @@ void tearDown() /* Terminate MQTT connection. */ TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Disconnect( &context ) ); - /* Terminate TLS session. */ - ( void ) closeTlsSession( pSslContext ); - - /* Terminate TCP connection. */ - ( void ) closeTcpConnection( tcpSocket ); + /* Terminate TLS session and TCP connection. */ + ( void ) Openssl_Disconnect( &networkContext ); } /* ========================== Test Cases ============================ */ diff --git a/libraries/standard/mqtt/integration-test/test_config.h b/libraries/standard/mqtt/integration-test/test_config.h index d8a6e7e3ff..589c3ed19c 100644 --- a/libraries/standard/mqtt/integration-test/test_config.h +++ b/libraries/standard/mqtt/integration-test/test_config.h @@ -80,7 +80,7 @@ * This certificate should be PEM-encoded. */ #ifndef SERVER_ROOT_CA_CERT_PATH - #error "SERVER_ROOT_CA_CERT_PATH should be defined for MQTT system tests." + #define SERVER_ROOT_CA_CERT_PATH "certificates/mosquitto.org.crt" #endif /** diff --git a/libraries/standard/mqtt/mqttFilePaths.cmake b/libraries/standard/mqtt/mqttFilePaths.cmake index 3d543dac1e..c0c0a338b0 100644 --- a/libraries/standard/mqtt/mqttFilePaths.cmake +++ b/libraries/standard/mqtt/mqttFilePaths.cmake @@ -14,7 +14,7 @@ set( MQTT_SOURCES # MQTT library Public Include directories. set( MQTT_INCLUDE_PUBLIC_DIRS "${MODULES_DIR}/standard/mqtt/include" - "${MODULES_DIR}/standard/utilities/include" ) + "${PLATFORM_DIR}/include" ) # MQTT library Private Include directories. set( MQTT_INCLUDE_PRIVATE_DIRS diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 50db50da5d..ea5247b6e6 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -323,7 +323,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, /* Loop until the entire packet is sent. */ while( bytesRemaining > 0UL ) { - bytesSent = pContext->transportInterface.send( pContext->transportInterface.networkContext, + bytesSent = pContext->transportInterface.send( pContext->transportInterface.pNetworkContext, pIndex, bytesRemaining ); @@ -408,13 +408,14 @@ static int32_t recvExact( const MQTTContext_t * pContext, size_t bytesRemaining = bytesToRecv; int32_t totalBytesRecvd = 0, bytesRecvd; uint32_t entryTimeMs = 0U, elapsedTimeMs = 0U; - MQTTTransportRecvFunc_t recvFunc = NULL; + TransportRecv_t recvFunc = NULL; MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; bool receiveError = false; assert( pContext != NULL ); assert( bytesToRecv <= pContext->networkBuffer.size ); assert( pContext->callbacks.getTime != NULL ); + pIndex = pContext->networkBuffer.pBuffer; recvFunc = pContext->transportInterface.recv; getTimeStampMs = pContext->callbacks.getTime; @@ -423,7 +424,7 @@ static int32_t recvExact( const MQTTContext_t * pContext, while( ( bytesRemaining > 0U ) && ( receiveError == false ) ) { - bytesRecvd = recvFunc( pContext->transportInterface.networkContext, + bytesRecvd = recvFunc( pContext->transportInterface.pNetworkContext, pIndex, bytesRemaining ); @@ -935,7 +936,7 @@ static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext, assert( pContext != NULL ); status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, - pContext->transportInterface.networkContext, + pContext->transportInterface.pNetworkContext, &incomingPacket ); if( status == MQTTNoDataAvailable ) @@ -1107,7 +1108,7 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, * returned after a transport receive timeout, an error, or a successful * receive of packet type and length. */ status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, - pContext->transportInterface.networkContext, + pContext->transportInterface.pNetworkContext, pIncomingPacket ); /* The loop times out based on 2 conditions. @@ -1297,7 +1298,7 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, /*-----------------------------------------------------------*/ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, - const MQTTTransportInterface_t * pTransportInterface, + const TransportInterface_t * pTransportInterface, const MQTTApplicationCallbacks_t * pCallbacks, const MQTTFixedBuffer_t * pNetworkBuffer ) { diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index cc285aa657..fae42e79c3 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -559,8 +559,8 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size ); } -static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, - NetworkContext_t networkContext ) +static size_t getRemainingLength( TransportRecv_t recvFunc, + NetworkContext_t * pNetworkContext ) { size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; uint8_t encodedByte = 0; @@ -575,7 +575,7 @@ static size_t getRemainingLength( MQTTTransportRecvFunc_t recvFunc, } else { - bytesReceived = recvFunc( networkContext, &encodedByte, 1U ); + bytesReceived = recvFunc( pNetworkContext, &encodedByte, 1U ); if( bytesReceived == 1 ) { @@ -2119,8 +2119,8 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFunc, - NetworkContext_t networkContext, +MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, + NetworkContext_t * pNetworkContext, MQTTPacketInfo_t * pIncomingPacket ) { MQTTStatus_t status = MQTTSuccess; @@ -2134,7 +2134,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu else { /* Read a single byte. */ - bytesReceived = readFunc( networkContext, + bytesReceived = readFunc( pNetworkContext, &( pIncomingPacket->type ), 1U ); } @@ -2145,7 +2145,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( MQTTTransportRecvFunc_t readFu if( incomingPacketValid( pIncomingPacket->type ) == true ) { pIncomingPacket->remainingLength = getRemainingLength( readFunc, - networkContext ); + pNetworkContext ); if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index dc22c4ffa9..a1dfd3eb62 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -22,9 +22,6 @@ /************ End of logging configuration ****************/ -/* Set network context to double pointer to buffer (uint8_t**). */ -typedef uint8_t ** NetworkContext_t; - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. @@ -35,7 +32,7 @@ typedef uint8_t ** NetworkContext_t; * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. */ -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 +#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 /** * @brief The maximum number of MQTT PUBLISH messages that may be pending @@ -47,14 +44,14 @@ typedef uint8_t ** NetworkContext_t; * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. */ -#define MQTT_STATE_ARRAY_MAX_COUNT 10U +#define MQTT_STATE_ARRAY_MAX_COUNT 10U /** * @brief MQTT client identifier. * * No two clients may use the same client identifier simultaneously. */ -#define MQTT_CLIENT_IDENTIFIER "testclient" +#define MQTT_CLIENT_IDENTIFIER "testclient" /** * @brief Retry count for reading CONNACK from network. @@ -65,5 +62,10 @@ typedef uint8_t ** NetworkContext_t; */ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) +/* Set network context to double pointer to buffer (uint8_t**). */ +struct NetworkContext +{ + uint8_t ** buffer; +}; #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index c7870d492f..65770fb4aa 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -152,7 +152,7 @@ int suiteTearDown( int numFailures ) /** * @brief Mock successful transport receive by reading data from a buffer. */ -static int32_t mockReceive( NetworkContext_t context, +static int32_t mockReceive( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { @@ -161,7 +161,7 @@ static int32_t mockReceive( NetworkContext_t context, size_t bytesRead = 0; /* Treat network context as pointer to buffer for mocking */ - mockNetwork = ( *( uint8_t ** ) context ); + mockNetwork = *( pNetworkContext->buffer ); while( bytesRead++ < bytesToRecv ) { @@ -170,7 +170,7 @@ static int32_t mockReceive( NetworkContext_t context, } /* Move stream by bytes read. */ - ( *( uint8_t ** ) context ) = mockNetwork; + *( pNetworkContext->buffer ) = mockNetwork; return bytesToRecv; } @@ -178,7 +178,7 @@ static int32_t mockReceive( NetworkContext_t context, /** * @brief Mock transport receive with no data available. */ -static int32_t mockReceiveNoData( NetworkContext_t context, +static int32_t mockReceiveNoData( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { @@ -188,7 +188,7 @@ static int32_t mockReceiveNoData( NetworkContext_t context, /** * @brief Mock transport receive failure. */ -static int32_t mockReceiveFailure( NetworkContext_t context, +static int32_t mockReceiveFailure( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { @@ -198,7 +198,7 @@ static int32_t mockReceiveFailure( NetworkContext_t context, /** * @brief Mock transport receive that succeeds once, then fails. */ -static int32_t mockReceiveSucceedThenFail( NetworkContext_t context, +static int32_t mockReceiveSucceedThenFail( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { @@ -207,12 +207,12 @@ static int32_t mockReceiveSucceedThenFail( NetworkContext_t context, if( counter++ ) { - retVal = mockReceiveFailure( context, pBuffer, bytesToRecv ); + retVal = mockReceiveFailure( pNetworkContext, pBuffer, bytesToRecv ); counter = 0; } else { - retVal = mockReceive( context, pBuffer, bytesToRecv ); + retVal = mockReceive( pNetworkContext, pBuffer, bytesToRecv ); } return retVal; @@ -1589,21 +1589,22 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) { MQTTStatus_t status = MQTTSuccess; MQTTPacketInfo_t mqttPacket; + NetworkContext_t networkContext; uint8_t buffer[ 10 ]; uint8_t * bufPtr = buffer; /* Dummy network context - pointer to pointer to a buffer. */ - NetworkContext_t networkContext = ( NetworkContext_t ) &bufPtr; + networkContext.buffer = &bufPtr; /* Test a NULL pIncomingPacket parameter. */ - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, NULL ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); /* Test a typical happy path case for a CONN ACK packet. */ buffer[ 0 ] = 0x20; /* CONN ACK */ buffer[ 1 ] = 0x02; /* Remaining length. */ - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( 0x20, mqttPacket.type ); TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); @@ -1613,7 +1614,7 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) buffer[ 0 ] = MQTT_PACKET_TYPE_PUBLISH; buffer[ 1 ] = 0x80; buffer[ 2 ] = 0x01; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTT_PACKET_TYPE_PUBLISH, mqttPacket.type ); TEST_ASSERT_EQUAL_INT( 128, mqttPacket.remainingLength ); @@ -1621,7 +1622,7 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) /* Test with incorrect packet type. */ bufPtr = buffer; buffer[ 0 ] = 0x10; /* INVALID */ - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTBadResponse, status ); /* Test with invalid remaining length. */ @@ -1634,7 +1635,7 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) buffer[ 2 ] = 0xFF; buffer[ 3 ] = 0xFF; buffer[ 4 ] = 0xFF; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTBadResponse, status ); /* Check with an encoding that does not conform to the MQTT spec. */ @@ -1643,30 +1644,30 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) buffer[ 2 ] = 0x80; buffer[ 3 ] = 0x80; buffer[ 4 ] = 0x00; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTBadResponse, status ); /* Check when network receive fails. */ memset( buffer, 0x00, 10 ); bufPtr = buffer; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveFailure, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveFailure, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTRecvFailed, status ); /* Test if no data is available. */ bufPtr = buffer; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveNoData, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveNoData, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTNoDataAvailable, status ); /* Branch coverage for PUBREL. */ bufPtr = buffer; buffer[ 0 ] = MQTT_PACKET_TYPE_PUBREL & 0xF0U; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTBadResponse, status ); /* Receive type then fail. */ bufPtr = buffer; buffer[ 0 ] = MQTT_PACKET_TYPE_PUBREL; - status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveSucceedThenFail, networkContext, &mqttPacket ); + status = MQTT_GetIncomingPacketTypeAndLength( mockReceiveSucceedThenFail, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL( MQTTBadResponse, status ); } diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index eb4b154de3..fc9f5a5162 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -148,13 +148,13 @@ int suiteTearDown( int numFailures ) * @brief Mock successful transport send, and write data into buffer for * verification. */ -static int32_t mockSend( NetworkContext_t context, +static int32_t mockSend( NetworkContext_t * pNetworkContext, const void * pMessage, size_t bytesToSend ) { const uint8_t * buffer = ( const uint8_t * ) pMessage; /* Treat network context as pointer to buffer for mocking. */ - uint8_t * mockNetwork = ( *( uint8_t ** ) context ); + uint8_t * mockNetwork = *( pNetworkContext->buffer ); size_t bytesSent = 0; while( bytesSent++ < bytesToSend ) @@ -164,7 +164,7 @@ static int32_t mockSend( NetworkContext_t context, } /* Move stream by bytes sent. */ - ( *( uint8_t ** ) context ) = mockNetwork; + *( pNetworkContext->buffer ) = mockNetwork; return bytesToSend; } @@ -231,11 +231,11 @@ static uint32_t getTimeDummy( void ) * @return Number of bytes sent; negative value on error; * 0 for timeout or 0 bytes sent. */ -static int32_t transportSendSuccess( NetworkContext_t pContext, +static int32_t transportSendSuccess( NetworkContext_t * pNetworkContext, const void * pBuffer, size_t bytesToWrite ) { - TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pContext ); + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext ); ( void ) pBuffer; return bytesToWrite; } @@ -243,11 +243,11 @@ static int32_t transportSendSuccess( NetworkContext_t pContext, /** * @brief Mocked failed transport send. */ -static int32_t transportSendFailure( NetworkContext_t pContext, +static int32_t transportSendFailure( NetworkContext_t * pNetworkContext, const void * pBuffer, size_t bytesToWrite ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; ( void ) bytesToWrite; return -1; @@ -256,14 +256,14 @@ static int32_t transportSendFailure( NetworkContext_t pContext, /** * @brief Mocked transport send that succeeds then fails. */ -static int32_t transportSendSucceedThenFail( NetworkContext_t context, +static int32_t transportSendSucceedThenFail( NetworkContext_t * pNetworkContext, const void * pMessage, size_t bytesToSend ) { int32_t retVal = bytesToSend; static int counter = 0; - ( void ) context; + ( void ) pNetworkContext; ( void ) pMessage; if( counter++ ) @@ -284,11 +284,11 @@ static int32_t transportSendSucceedThenFail( NetworkContext_t context, * * @return Number of bytes received; negative value on error. */ -static int32_t transportRecvSuccess( NetworkContext_t pContext, +static int32_t transportRecvSuccess( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRead ) { - TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pContext ); + TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext ); ( void ) pBuffer; return bytesToRead; } @@ -296,11 +296,11 @@ static int32_t transportRecvSuccess( NetworkContext_t pContext, /** * @brief Mocked failed transport read. */ -static int32_t transportRecvFailure( NetworkContext_t pContext, +static int32_t transportRecvFailure( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRead ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; ( void ) bytesToRead; return -1; @@ -309,11 +309,11 @@ static int32_t transportRecvFailure( NetworkContext_t pContext, /** * @brief Mocked transport reading one byte at a time. */ -static int32_t transportRecvOneByte( NetworkContext_t pContext, +static int32_t transportRecvOneByte( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRead ) { - ( void ) pContext; + ( void ) pNetworkContext; ( void ) pBuffer; return 1; } @@ -324,9 +324,9 @@ static int32_t transportRecvOneByte( NetworkContext_t pContext, * * @brief param[in] pTransport The transport interface to use with the context. */ -static void setupTransportInterface( MQTTTransportInterface_t * pTransport ) +static void setupTransportInterface( TransportInterface_t * pTransport ) { - pTransport->networkContext = MQTT_SAMPLE_NETWORK_CONTEXT; + pTransport->pNetworkContext = MQTT_SAMPLE_NETWORK_CONTEXT; pTransport->send = transportSendSuccess; pTransport->recv = transportRecvSuccess; } @@ -525,7 +525,7 @@ void test_MQTT_Init_Happy_Path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -549,7 +549,7 @@ void test_MQTT_Init_Invalid_Params( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -603,7 +603,7 @@ void test_MQTT_Connect_sendConnect( void ) uint32_t timeout = 2; bool sessionPresent; MQTTStatus_t status; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; @@ -679,7 +679,7 @@ void test_MQTT_Connect_receiveConnack( void ) uint32_t timeout = 0; bool sessionPresent, sessionPresentExpected; MQTTStatus_t status; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; @@ -751,7 +751,7 @@ void test_MQTT_Connect_receiveConnack_retries( void ) MQTTConnectInfo_t connectInfo; bool sessionPresent; MQTTStatus_t status; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; @@ -814,7 +814,7 @@ void test_MQTT_Connect_partial_receive() uint32_t timeout = 0; bool sessionPresent; MQTTStatus_t status; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; @@ -889,7 +889,7 @@ void test_MQTT_Connect_resendPendingAcks( void ) uint32_t timeout = 2; bool sessionPresent, sessionPresentResult; MQTTStatus_t status; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; @@ -1016,7 +1016,7 @@ void test_MQTT_Connect_happy_path() uint32_t timeout = 2; bool sessionPresent, sessionPresentExpected; MQTTStatus_t status; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; @@ -1115,7 +1115,7 @@ void test_MQTT_Publish( void ) MQTTContext_t mqttContext; MQTTPublishInfo_t publishInfo; uint16_t packetId; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTStatus_t status; @@ -1241,8 +1241,8 @@ void test_MQTT_Disconnect( void ) MQTTStatus_t status; uint8_t buffer[ 10 ]; uint8_t * bufPtr = buffer; - NetworkContext_t networkContext = ( NetworkContext_t ) &bufPtr; - MQTTTransportInterface_t transport; + NetworkContext_t networkContext; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; size_t disconnectSize = 2; @@ -1250,7 +1250,8 @@ void test_MQTT_Disconnect( void ) setupTransportInterface( &transport ); setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); - transport.networkContext = networkContext; + networkContext.buffer = &bufPtr; + transport.pNetworkContext = &networkContext; transport.recv = transportRecvSuccess; transport.send = transportSendFailure; @@ -1290,7 +1291,7 @@ void test_MQTT_Disconnect( void ) void test_MQTT_GetPacketId( void ) { MQTTContext_t mqttContext; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; uint16_t packetId; @@ -1323,7 +1324,7 @@ void test_MQTT_GetPacketId( void ) void test_MQTT_ProcessLoop_Invalid_Params( void ) { MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks = { 0 }; MQTTStatus_t mqttStatus = MQTT_ProcessLoop( NULL, MQTT_NO_TIMEOUT_MS ); @@ -1350,7 +1351,7 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPublishInfo_t pubInfo; @@ -1429,7 +1430,7 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPublishInfo_t publishInfo = { 0 }; @@ -1482,7 +1483,7 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -1560,7 +1561,7 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -1624,7 +1625,7 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -1686,7 +1687,7 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -1715,7 +1716,7 @@ void test_MQTT_ProcessLoop_Receive_Failed( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -1739,7 +1740,7 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket = { 0 }; @@ -1792,7 +1793,7 @@ void test_MQTT_ReceiveLoop( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks = { 0 }; MQTTPacketInfo_t incomingPacket = { 0 }; @@ -1880,7 +1881,7 @@ void test_MQTT_Subscribe_happy_path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket = { 0 }; @@ -1914,7 +1915,7 @@ void test_MQTT_Subscribe_error_paths( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; @@ -1981,7 +1982,7 @@ void test_MQTT_Unsubscribe_happy_path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; @@ -2014,7 +2015,7 @@ void test_MQTT_Unsubscribe_error_path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; @@ -2066,7 +2067,7 @@ void test_MQTT_Ping_happy_path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; @@ -2098,7 +2099,7 @@ void test_MQTT_Ping_error_path( void ) { MQTTStatus_t mqttStatus; MQTTContext_t context; - MQTTTransportInterface_t transport; + TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index f8b4b5fbe2..010f089666 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -1,2 +1,2 @@ -# Add the transport target -add_subdirectory( ${PLATFORM_DIR}/posix/transport ) +# Add the posix targets +add_subdirectory( ${PLATFORM_DIR}/posix ) diff --git a/platform/include/clock.h b/platform/include/clock.h index 087dbe8018..77f1a16077 100644 --- a/platform/include/clock.h +++ b/platform/include/clock.h @@ -1,5 +1,4 @@ /* - * IoT Platform V1.1.0 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -21,57 +20,23 @@ */ /** - * @file iot_clock.h + * @file clock.h * @brief Time-related functions used by libraries in this SDK. */ -#ifndef IOT_CLOCK_H_ -#define IOT_CLOCK_H_ +#ifndef CLOCK_H_ +#define CLOCK_H_ /* Standard includes. */ -#include -#include #include /** - * @functionspage{platform_clock,platform clock component,Clock} - * - @functionname{platform_clock_function_gettimestring} - */ - -/** - * @functionpage{IotClock_GetTimestring,platform_clock,gettimestring} - */ - -/** - * @brief Generates a human-readable timestring, such as "01 Jan 2018 12:00". - * - * This function uses the system clock to generate a human-readable timestring. - * This timestring is printed by the [logging functions](@ref logging_functions). - * - * @param[out] pBuffer A buffer to store the timestring in. - * @param[in] bufferSize The size of `pBuffer`. - * @param[out] pTimestringLength The actual length of the timestring stored in - * `pBuffer`. - * - * @return `true` if a timestring was successfully generated; `false` otherwise. - * - * @warning The implementation of this function must not call any [logging functions] - * (@ref logging_functions). + * @brief The timer query function. * - * Example - * @code{c} - * char timestring[ 32 ]; - * size_t timestringLength = 0; + * This function returns the elapsed time. * - * if( IotClock_GetTimestring( timestring, 32, ×tringLength ) == true ) - * { - * printf( "Timestring: %.*s", timestringLength, timestring ); - * } - * @endcode + * @return Time in milliseconds. */ -/* @[declare_platform_clock_gettimestring] */ -bool Clock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ); +uint32_t Clock_GetTimeMs( void ); -#endif /* ifndef IOT_CLOCK_H_ */ +#endif /* ifndef CLOCK_H_ */ diff --git a/platform/posix/CMakeLists.txt b/platform/posix/CMakeLists.txt new file mode 100644 index 0000000000..c9edd045f2 --- /dev/null +++ b/platform/posix/CMakeLists.txt @@ -0,0 +1,10 @@ +# Create target for POSIX implementation of reconnect logic. +add_library( clock_posix + "clock_posix.c" ) + +target_include_directories( clock_posix + PRIVATE + ${PLATFORM_DIR}/include ) + +# Add the transport targets +add_subdirectory( ${CMAKE_CURRENT_LIST_DIR}/transport ) diff --git a/platform/posix/clock_posix.c b/platform/posix/clock_posix.c index 4be9d91114..8a5c6253e7 100644 --- a/platform/posix/clock_posix.c +++ b/platform/posix/clock_posix.c @@ -20,16 +20,10 @@ */ /** - * @file iot_clock_posix.c - * @brief Implementation of the functions in iot_clock.h for POSIX systems. + * @file clock_posix.c + * @brief Implementation of the functions in clock.h for POSIX systems. */ -/* The config header is always included first. */ -#include "config.h" - -/* Standard includes. */ -#include - /* POSIX include. Allow the default POSIX header to be overridden. */ #ifdef POSIX_TIME_HEADER #include POSIX_TIME_HEADER @@ -40,64 +34,19 @@ /* Platform clock include. */ #include "clock.h" -/* Configure logs for the functions in this file. */ -#ifdef LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL LOG_LEVEL_PLATFORM -#else - #ifdef LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "CLOCK" ) -#include "logging_setup.h" - /*-----------------------------------------------------------*/ -/** - * @brief The format of timestrings printed in logs. - * - * For more information on timestring formats, see [this link.] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) - */ -#define TIMESTRING_FORMAT ( "%F %R:%S" ) - -/*-----------------------------------------------------------*/ - -bool Clock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ) +uint32_t Clock_GetTimeMs( void ) { - bool status = true; - const time_t unixTime = time( NULL ); - struct tm localTime = { 0 }; - size_t timestringLength = 0; - - /* localtime_r is the thread-safe variant of localtime. Its return value - * should be the pointer to the localTime struct. */ - if( localtime_r( &unixTime, &localTime ) != &localTime ) - { - status = false; - } + uint32_t timeMs; + struct timespec timeSpec; - if( status == true ) - { - /* Convert the localTime struct to a string. */ - timestringLength = strftime( pBuffer, bufferSize, TIMESTRING_FORMAT, &localTime ); + /* Get the MONOTONIC time. */ + clock_gettime( CLOCK_MONOTONIC, &timeSpec ); - /* Check for error from strftime. */ - if( timestringLength == 0 ) - { - status = false; - } - else - { - /* Set the output parameter. */ - *pTimestringLength = timestringLength; - } - } + /* Calculate the milliseconds from timespec. */ + timeMs = ( uint32_t ) ( timeSpec.tv_sec * 1000 ) + + ( uint32_t ) ( timeSpec.tv_nsec / ( 1000 * 1000 ) ); - return status; + return timeMs; } diff --git a/platform/posix/transport/include/openssl_posix.h b/platform/posix/transport/include/openssl_posix.h index c8f664ed7a..e8b9fdbad4 100644 --- a/platform/posix/transport/include/openssl_posix.h +++ b/platform/posix/transport/include/openssl_posix.h @@ -54,7 +54,7 @@ /* Transport includes. */ #include "transport_interface.h" -/* Socket includes. */ +/* Socket include. */ #include "sockets_posix.h" /** diff --git a/platform/posix/transport/src/openssl_posix.c b/platform/posix/transport/src/openssl_posix.c index e1e4db9388..f44b7c5c52 100644 --- a/platform/posix/transport/src/openssl_posix.c +++ b/platform/posix/transport/src/openssl_posix.c @@ -445,7 +445,7 @@ OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, } } -/* Create SSL context. */ + /* Create SSL context. */ if( returnStatus == OPENSSL_SUCCESS ) { pSslContext = SSL_CTX_new( TLS_client_method() ); @@ -598,6 +598,7 @@ int32_t Openssl_Recv( NetworkContext_t * pNetworkContext, size_t bytesToRecv ) { int32_t bytesReceived = 0; + int sslError = 0; /* SSL read of data. */ bytesReceived = ( int32_t ) SSL_read( pNetworkContext->pSsl, @@ -606,8 +607,18 @@ int32_t Openssl_Recv( NetworkContext_t * pNetworkContext, if( bytesReceived <= 0 ) { - LogError( ( "SSL_read of OpenSSL failed to receive data: " - " status=%d.", bytesReceived ) ); + sslError = SSL_get_error( pNetworkContext->pSsl, bytesReceived ); + + if( sslError == SSL_ERROR_WANT_READ ) + { + /* There is no data to receive at this time. */ + bytesReceived = 0; + } + else + { + LogError( ( "SSL_read of OpenSSL failed to receive data: " + "status=%d.", bytesReceived ) ); + } } return bytesReceived; From a5b75a071602a2e4694f0731d8515511c4484a8d Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Fri, 10 Jul 2020 14:01:33 -0700 Subject: [PATCH 578/844] Add parameter checks and documentation to MQTT Lightweight files (#1044) * Add unit tests for the parameter checks. --- .../cbmc/include/network_interface_stubs.h | 2 +- .../standard/mqtt/include/mqtt_lightweight.h | 135 ++++++++++---- .../standard/mqtt/src/mqtt_lightweight.c | 169 +++++++++++++----- .../mqtt/utest/mqtt_lightweight_utest.c | 145 ++++++++++++++- 4 files changed, 356 insertions(+), 95 deletions(-) diff --git a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h index 949f048989..609fd38a11 100644 --- a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h +++ b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h @@ -39,4 +39,4 @@ int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ); -#endif +#endif /* ifndef NETWORK_INTERFACE_STUBS_H_ */ diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 9e5dd39647..ec9bf4e340 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -248,13 +248,14 @@ struct MQTTPacketInfo /** * @brief Get the size and Remaining Length of an MQTT CONNECT packet. * - * This function must be called before #MQTT_SerializeConnect in order to verify - * the size of the MQTT CONNECT packet that is generated from - * #MQTTConnectInfo_t and optional #MQTTPublishInfo_t. The parameters - * @p pConnectInfo , @p pWillInfo , and @p pRemainingLength are valid only if - * this function returns #MQTTSuccess. - * @p pPacketSize returned is used to verify the size of a #MQTTFixedBuffer_t - * that will hold the MQTT CONNECT packet. + * This function must be called before #MQTT_SerializeConnect in order to get + * the size of the MQTT CONNECT packet that is generated from #MQTTConnectInfo_t + * and optional #MQTTPublishInfo_t. The size of the #MQTTFixedBuffer_t supplied + * to #MQTT_SerializeConnect must be at least @p pPacketSize. The provided + * @p pConnectInfo and @p pWillInfo are valid for serialization with + * #MQTT_SerializeConnect only if this function returns #MQTTSuccess. The + * remaining length returned in @p pRemainingLength and the packet size returned + * in @p pPacketSize are valid only if this function returns #MQTTSuccess. * * @param[in] pConnectInfo MQTT CONNECT packet parameters. * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. @@ -270,21 +271,20 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, size_t * pPacketSize ); /** - * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pBuffer. + * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pFixedBuffer. * * #MQTT_GetConnectPacketSize should be called with @p pConnectInfo and - * @p pWillInfo before invoking this routine. - * The @p remainingLength was calculated from the parameters in @p pConnectInfo - * and @p pWillInfo using function #MQTT_GetConnectPacketSize. @p pConnectInfo, - * @p pWillInfo , and @p remainingLength are valid only if - * #MQTT_GetConnectPacketSize returned #MQTTSuccess. + * @p pWillInfo before invoking this function to get the size of the required + * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be + * the same as returned by #MQTT_GetConnectPacketSize. The #MQTTFixedBuffer_t + * must be at least as large as the size returned by #MQTT_GetConnectPacketSize. * * @param[in] pConnectInfo MQTT CONNECT packet parameters. * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. * @param[out] pFixedBuffer Buffer for packet serialization. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ @@ -296,6 +296,15 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, /** * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet. * + * This function must be called before #MQTT_SerializeSubscribe in order to get + * the size of the MQTT SUBSCRIBE packet that is generated from the list of + * #MQTTSubscribeInfo_t. The size of the #MQTTFixedBuffer_t supplied + * to #MQTT_SerializeSubscribe must be at least @p pPacketSize. The provided + * @p pSubscriptionList is valid for serialization with #MQTT_SerializeSubscribe + * only if this function returns #MQTTSuccess. The remaining length returned in + * @p pRemainingLength and the packet size returned in @p pPacketSize are valid + * only if this function returns #MQTTSuccess. + * * @param[in] pSubscriptionList List of MQTT subscription info. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE packet. @@ -312,13 +321,19 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscript /** * @brief Serialize an MQTT SUBSCRIBE packet in the given buffer. * + * #MQTT_GetSubscribePacketSize should be called with @p pSubscriptionList + * before invoking this function to get the size of the required + * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be + * the same as returned by #MQTT_GetSubscribePacketSize. The #MQTTFixedBuffer_t + * must be at least as large as the size returned by #MQTT_GetSubscribePacketSize. + * * @param[in] pSubscriptionList List of MQTT subscription info. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] packetId packet ID generated by #MQTT_GetPacketId. * @param[in] remainingLength Remaining Length provided by #MQTT_GetSubscribePacketSize. - * @param[out] pBuffer Buffer for packet serialization. + * @param[out] pFixedBuffer Buffer for packet serialization. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ @@ -326,11 +341,20 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ); + const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Get packet size and Remaining Length of an MQTT UNSUBSCRIBE packet. * + * This function must be called before #MQTT_SerializeUnsubscribe in order to + * get the size of the MQTT UNSUBSCRIBE packet that is generated from the list + * of #MQTTSubscribeInfo_t. The size of the #MQTTFixedBuffer_t supplied + * to #MQTT_SerializeUnsubscribe must be at least @p pPacketSize. The provided + * @p pSubscriptionList is valid for serialization with #MQTT_SerializeUnsubscribe + * only if this function returns #MQTTSuccess. The remaining length returned in + * @p pRemainingLength and the packet size returned in @p pPacketSize are valid + * only if this function returns #MQTTSuccess. + * * @param[in] pSubscriptionList List of MQTT subscription info. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[out] pRemainingLength The Remaining Length of the MQTT UNSUBSCRIBE packet. @@ -347,13 +371,19 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscri /** * @brief Serialize an MQTT UNSUBSCRIBE packet in the given buffer. * + * #MQTT_GetUnsubscribePacketSize should be called with @p pSubscriptionList + * before invoking this function to get the size of the required + * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be + * the same as returned by #MQTT_GetUnsubscribePacketSize. The #MQTTFixedBuffer_t + * must be at least as large as the size returned by #MQTT_GetUnsubscribePacketSize. + * * @param[in] pSubscriptionList List of MQTT subscription info. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] packetId packet ID generated by #MQTT_GetPacketId. * @param[in] remainingLength Remaining Length provided by #MQTT_GetUnsubscribePacketSize. - * @param[out] pBuffer Buffer for packet serialization. + * @param[out] pFixedBuffer Buffer for packet serialization. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ @@ -361,11 +391,20 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ); + const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Get the packet size and remaining length of an MQTT PUBLISH packet. * + * This function must be called before #MQTT_SerializePublish in order to get + * the size of the MQTT PUBLISH packet that is generated from #MQTTPublishInfo_t. + * The size of the #MQTTFixedBuffer_t supplied to #MQTT_SerializePublish must be + * at least @p pPacketSize. The provided @p pPublishInfo is valid for + * serialization with #MQTT_SerializePublish only if this function returns + * #MQTTSuccess. The remaining length returned in @p pRemainingLength and the + * packet size returned in @p pPacketSize are valid only if this function + * returns #MQTTSuccess. + * * @param[in] pPublishInfo MQTT PUBLISH packet parameters. * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet. * @param[out] pPacketSize The total size of the MQTT PUBLISH packet. @@ -385,57 +424,69 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, * consider using #MQTT_SerializePublishHeader, which will serialize * only the PUBLISH header into the buffer. * + * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before + * invoking this function to get the size of the required #MQTTFixedBuffer_t and + * @p remainingLength. The @p remainingLength must be the same as returned by + * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large + * as the size returned by #MQTT_GetPublishPacketSize. + * * @param[in] pPublishInfo MQTT PUBLISH packet parameters. * @param[in] packetId packet ID generated by #MQTT_GetPacketId. - * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. - * @param[out] pBuffer Buffer for packet serialization. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize. + * @param[out] pFixedBuffer Buffer for packet serialization. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ); + const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Serialize an MQTT PUBLISH packet header in the given buffer. * - * This function serializes PUBLISH header in to the given buffer. Payload + * This function serializes PUBLISH header in to the given buffer. The payload * for PUBLISH will not be copied over to the buffer. This will help reduce - * the memory needed for the buffer and avoid an unwanted copy operataion of - * PUBLISH payload into the buffer. If payload also would need to be part of + * the memory needed for the buffer and avoid an unwanted copy operation of the + * PUBLISH payload into the buffer. If the payload also would need to be part of * the serialized buffer, consider using #MQTT_SerializePublish. * + * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before + * invoking this function to get the size of the required #MQTTFixedBuffer_t and + * @p remainingLength. The @p remainingLength must be the same as returned by + * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large + * as the size returned by #MQTT_GetPublishPacketSize. + * * @param[in] pPublishInfo MQTT PUBLISH packet parameters. * @param[in] packetId packet ID generated by #MQTT_GetPacketId. - * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize. - * @param[out] pBuffer Buffer for packet serialization. + * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize. + * @param[out] pFixedBuffer Buffer for packet serialization. * @param[out] pHeaderSize Size of the serialized MQTT PUBLISH header. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer, + const MQTTFixedBuffer_t * pFixedBuffer, size_t * pHeaderSize ); /** * @brief Serialize an MQTT PUBACK, PUBREC, PUBREL, or PUBCOMP into the given * buffer. * - * @param[out] pBuffer Buffer for packet serialization. + * @param[out] pFixedBuffer Buffer for packet serialization. * @param[in] packetType Byte of the corresponding packet fixed header per the * MQTT spec. * @param[in] packetId Packet ID of the publish. * * @return #MQTTBadParameter, #MQTTNoMemory, or #MQTTSuccess. */ -MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pBuffer, +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, uint8_t packetType, uint16_t packetId ); @@ -451,13 +502,16 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); /** * @brief Serialize an MQTT DISCONNECT packet into the given buffer. * - * @param[out] pBuffer Buffer for packet serialization. + * The input #MQTTFixedBuffer_t.size must be at least as large as the size + * returned by #MQTT_GetDisconnectPacketSize. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @param[out] pFixedBuffer Buffer for packet serialization. + * + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pBuffer ); +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Get the size of an MQTT PINGREQ packet. @@ -471,13 +525,16 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); /** * @brief Serialize an MQTT PINGREQ packet into the given buffer. * - * @param[out] pBuffer Buffer for packet serialization. + * The input #MQTTFixedBuffer_t.size must be at least as large as the size + * returned by #MQTT_GetPingreqPacketSize. + * + * @param[out] pFixedBuffer Buffer for packet serialization. * - * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; + * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. */ -MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pBuffer ); +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Deserialize an MQTT PUBLISH packet. diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index fae42e79c3..f02b72614f 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -83,7 +83,7 @@ /** * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT - * packet is this value. + * packet is this value, 256 MB. */ #define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) @@ -279,6 +279,18 @@ static uint8_t * encodeString( uint8_t * pDestination, const char * pSource, uint16_t sourceLength ); +/** + * Retrieves and decodes the Remaining Length from the network interface by + * reading a single byte at a time. + * + * @param[in] recvFunc Network interface receive function. + * @param[in] pNetworkContext Network interface context to the receive function. + * + * @return The Remaining Length of the incoming packet. + */ +static size_t getRemainingLength( TransportRecv_t recvFunc, + NetworkContext_t * pNetworkContext ); + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -480,10 +492,11 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, assert( pPublishInfo != NULL ); assert( pFixedBuffer != NULL ); + assert( pFixedBuffer->pBuffer != NULL ); /* Packet Id should be non zero for QoS1 and QoS2. */ - assert( pPublishInfo->qos == MQTTQoS0 || packetIdentifier != 0U ); + assert( ( pPublishInfo->qos == MQTTQoS0 ) || ( packetIdentifier != 0U ) ); /* Duplicate flag should be set only for Qos1 or Qos2. */ - assert( ( !pPublishInfo->dup ) || ( pPublishInfo->qos > MQTTQoS0 ) ); + assert( !( pPublishInfo->dup ) || ( pPublishInfo->qos != MQTTQoS0 ) ); /* Get the start address of the buffer. */ pIndex = pFixedBuffer->pBuffer; @@ -1015,7 +1028,7 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ) + const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1026,14 +1039,20 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo + remainingLength; /* Validate all the parameters. */ - if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) ) + if( ( pFixedBuffer == NULL ) || ( pSubscriptionList == NULL ) ) { - LogError( ( "Argument cannot be NULL: pBuffer=%p, " + LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, " "pSubscriptionList=%p.", - pBuffer, + pFixedBuffer, pSubscriptionList ) ); status = MQTTBadParameter; } + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; + } else if( subscriptionCount == 0U ) { LogError( ( "Subscription count is 0." ) ); @@ -1044,11 +1063,11 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo LogError( ( "Packet Id for subscription packet is 0." ) ); status = MQTTBadParameter; } - else if( packetSize > pBuffer->size ) + else if( packetSize > pFixedBuffer->size ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized packet of size of %lu.", - pBuffer->size, + pFixedBuffer->size, packetSize ) ); status = MQTTNoMemory; } @@ -1505,7 +1524,7 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscript } else if( subscriptionCount == 0U ) { - LogError( ( " subscriptionCount is 0." ) ); + LogError( ( "subscriptionCount is 0." ) ); status = MQTTBadParameter; } else @@ -1724,7 +1743,7 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ) + const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; @@ -1734,14 +1753,31 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength; - if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) ) + if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) ) { - LogError( ( "Argument cannot be NULL: pBuffer=%p, " + LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, " "pPublishInfo=%p.", - pBuffer, + pFixedBuffer, pPublishInfo ) ); status = MQTTBadParameter; } + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; + } + + /* For serializing a publish, if there exists a payload, then the buffer + * cannot be NULL. */ + else if( ( pPublishInfo->payloadLength > 0 ) && ( pPublishInfo->pPayload == NULL ) ) + { + LogError( ( "A nonzero payload length requires a non-NULL payload: ", + "payloadLength=%u, pPayload=%p.", + pPublishInfo->payloadLength, + pPublishInfo->pPayload ) ); + status = MQTTBadParameter; + } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, " @@ -1752,15 +1788,20 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) { - LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.", + LogError( ( "Packet ID is 0 for PUBLISH with QoS=%u.", pPublishInfo->qos ) ); status = MQTTBadParameter; } - else if( packetSize > pBuffer->size ) + else if( ( pPublishInfo->dup ) && ( pPublishInfo->qos == MQTTQoS0 ) ) + { + LogError( ( "Duplicate flag is set for PUBLISH with Qos 0," ) ); + status = MQTTBadParameter; + } + else if( packetSize > pFixedBuffer->size ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH packet of size of %lu.", - pBuffer->size, + pFixedBuffer->size, packetSize ) ); status = MQTTNoMemory; } @@ -1770,7 +1811,7 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, serializePublishCommon( pPublishInfo, remainingLength, packetId, - pBuffer, + pFixedBuffer, true ); } @@ -1782,7 +1823,7 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer, + const MQTTFixedBuffer_t * pFixedBuffer, size_t * pHeaderSize ) { MQTTStatus_t status = MQTTSuccess; @@ -1796,16 +1837,22 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + remainingLength; - if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || + if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pHeaderSize == NULL ) ) { - LogError( ( "Argument cannot be NULL: pBuffer=%p, " + LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, " "pPublishInfo=%p, pHeaderSize=%p.", - pBuffer, + pFixedBuffer, pPublishInfo, pHeaderSize ) ); status = MQTTBadParameter; } + /* A buffer must be configured for serialization. */ + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) ); + status = MQTTBadParameter; + } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) { LogError( ( "Invalid topic name for publish: pTopicName=%p, " @@ -1820,13 +1867,16 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo pPublishInfo->qos ) ); status = MQTTBadParameter; } - - - else if( ( packetSize - pPublishInfo->payloadLength ) > pBuffer->size ) + else if( ( pPublishInfo->dup ) && ( pPublishInfo->qos == MQTTQoS0 ) ) + { + LogError( ( "Duplicate flag is set for PUBLISH with Qos 0," ) ); + status = MQTTBadParameter; + } + else if( ( packetSize - pPublishInfo->payloadLength ) > pFixedBuffer->size ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH header packet of size of %lu.", - pBuffer->size, + pFixedBuffer->size, ( packetSize - pPublishInfo->payloadLength ) ) ); status = MQTTNoMemory; } @@ -1836,7 +1886,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo serializePublishCommon( pPublishInfo, remainingLength, packetId, - pBuffer, + pFixedBuffer, false ); /* Header size is the same as calculated packet size. */ @@ -1848,19 +1898,24 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pBuffer, +MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, uint8_t packetType, uint16_t packetId ) { MQTTStatus_t status = MQTTSuccess; - if( pBuffer == NULL ) + if( pFixedBuffer == NULL ) { LogError( ( "Provided buffer is NULL." ) ); status = MQTTBadParameter; } + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } /* The buffer must be able to fit 4 bytes for the packet. */ - else if( pBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) + else if( pFixedBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE ) { LogError( ( "Insufficient memory for packet." ) ); status = MQTTNoMemory; @@ -1879,10 +1934,10 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pBuffer, case MQTT_PACKET_TYPE_PUBREC: case MQTT_PACKET_TYPE_PUBREL: case MQTT_PACKET_TYPE_PUBCOMP: - pBuffer->pBuffer[ 0 ] = packetType; - pBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; - pBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId ); - pBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId ); + pFixedBuffer->pBuffer[ 0 ] = packetType; + pFixedBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH; + pFixedBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId ); + pFixedBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId ); break; default: @@ -1918,24 +1973,33 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pBuffer ) +MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; /* Validate arguments. */ - if( pBuffer == NULL ) + if( pFixedBuffer == NULL ) + { + LogError( ( "pFixedBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } + else if( pFixedBuffer->pBuffer == NULL ) { - LogError( ( "pBuffer cannot be NULL." ) ); + LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); status = MQTTBadParameter; } + else + { + /* Empty else MISRA 15.7 */ + } if( status == MQTTSuccess ) { - if( pBuffer->size < MQTT_DISCONNECT_PACKET_SIZE ) + if( pFixedBuffer->size < MQTT_DISCONNECT_PACKET_SIZE ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized DISCONNECT packet of size of %lu.", - pBuffer->size, + pFixedBuffer->size, MQTT_DISCONNECT_PACKET_SIZE ) ); status = MQTTNoMemory; } @@ -1943,8 +2007,8 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pBuffer ) if( status == MQTTSuccess ) { - pBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; - pBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH; + pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT; + pFixedBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH; } return status; @@ -1972,23 +2036,32 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ) /*-----------------------------------------------------------*/ -MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pBuffer ) +MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; - if( pBuffer == NULL ) + if( pFixedBuffer == NULL ) { - LogError( ( "pBuffer is NULL." ) ); + LogError( ( "pFixedBuffer is NULL." ) ); status = MQTTBadParameter; } + else if( pFixedBuffer->pBuffer == NULL ) + { + LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) ); + status = MQTTBadParameter; + } + else + { + /* Empty else MISRA 15.7 */ + } if( status == MQTTSuccess ) { - if( pBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) + if( pFixedBuffer->size < MQTT_PACKET_PINGREQ_SIZE ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PINGREQ packet of size of %u.", - pBuffer->size, + pFixedBuffer->size, MQTT_PACKET_PINGREQ_SIZE ) ); status = MQTTNoMemory; } @@ -1997,8 +2070,8 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pBuffer ) if( status == MQTTSuccess ) { /* Ping request packets are always the same. */ - pBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; - pBuffer->pBuffer[ 1 ] = 0x00; + pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ; + pFixedBuffer->pBuffer[ 1 ] = 0x00; } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 65770fb4aa..2d55c321cf 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -769,6 +769,18 @@ void test_MQTT_SerializeSubscribe( void ) NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify a NULL buffer in the fixed buffer struct fails */ + fixedBuffer.pBuffer = NULL; + status = MQTT_SerializeSubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the fixed buffer. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + /* Get correct values of packet size and remaining length. */ memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); subscriptionList.qos = MQTTQoS0; @@ -865,6 +877,18 @@ void test_MQTT_SerializeUnsubscribe( void ) NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify a NULL buffer in the fixed buffer struct fails */ + fixedBuffer.pBuffer = NULL; + status = MQTT_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the fixed buffer. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + /* Get correct values of packetsize and remaining length. */ memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); subscriptionList.qos = MQTTQoS0; @@ -1018,7 +1042,30 @@ void test_MQTT_SerializePublish( void ) NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); - /* 0 packet ID for QoS > 0. */ + /* Verify a NULL buffer in the fixed buffer struct fails */ + fixedBuffer.pBuffer = NULL; + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the fixed buffer. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + + /* Verify that a non-zero payload length and a NULL payload fails. */ + publishInfo.payloadLength = 1; + publishInfo.pPayload = NULL; + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the payload length to valid for tests. */ + publishInfo.payloadLength = 0; + + /* Verify that 0 packet ID for QoS > 0 fails. */ publishInfo.qos = MQTTQoS1; status = MQTT_SerializePublish( &publishInfo, 0, @@ -1026,6 +1073,19 @@ void test_MQTT_SerializePublish( void ) &fixedBuffer ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify that a duplicate flag for Qos 0 fails. */ + publishInfo.qos = MQTTQoS0; + publishInfo.dup = true; + status = MQTT_SerializePublish( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the previous flags for other tests. */ + publishInfo.qos = MQTTQoS1; + publishInfo.dup = false; + /* Empty topic fails. */ publishInfo.pTopicName = NULL; publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; @@ -1107,7 +1167,7 @@ void test_MQTT_SerializePublish( void ) checkBufferOverflow( buffer, sizeof( buffer ) ); memset( ( void * ) expectedPacket, 0x00, sizeof( expectedPacket ) ); pIterator = expectedPacket; - /* Dup = 0x8, QoS2 = 0x4, Retain = 0x1. 8 + 4 + 1 = 0xD. */ + /* Set the flags as follows: Dup = 0x8, QoS2 = 0x4, Retain = 0x1. 8 | 4 | 1 = 0xD. */ *pIterator++ = MQTT_PACKET_TYPE_PUBLISH | 0xD; pIterator += encodeRemainingLength( pIterator, remainingLength ); pIterator += encodeString( pIterator, publishInfo.pTopicName, publishInfo.topicNameLength ); @@ -1202,6 +1262,14 @@ void test_MQTT_SerializePingreq( void ) status = MQTT_SerializePingreq( NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify a NULL buffer in the fixed buffer struct fails */ + fixedBuffer.pBuffer = NULL; + status = MQTT_SerializePingreq( &fixedBuffer ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the fixed buffer. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + /* Good case succeeds. */ fixedBuffer.size = 2; padAndResetBuffer( buffer, sizeof( buffer ) ); @@ -1718,6 +1786,17 @@ void test_MQTT_SerializePublishHeader( void ) NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify a NULL buffer in the fixed buffer struct fails */ + fixedBuffer.pBuffer = NULL; + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Restore the fixed buffer. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + /* Empty topic fails. */ publishInfo.pTopicName = NULL; publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; @@ -1747,6 +1826,20 @@ void test_MQTT_SerializePublishHeader( void ) &headerSize ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify that a duplicate flag for Qos 0 fails. */ + publishInfo.qos = MQTTQoS0; + publishInfo.dup = true; + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the previous flags for other tests. */ + publishInfo.qos = MQTTQoS1; + publishInfo.dup = false; + /* Buffer too small. */ fixedBuffer.size = 1; status = MQTT_SerializePublishHeader( &publishInfo, @@ -1805,6 +1898,31 @@ void test_MQTT_SerializePublishHeader( void ) /* Payload should not be serialized. */ TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); checkBufferOverflow( buffer, sizeof( buffer ) ); + + + /* Again with QoS2 and dup. */ + publishInfo.qos = MQTTQoS2; + publishInfo.dup = true; + status = MQTT_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializePublishHeader( &publishInfo, + PACKET_ID, + remainingLength, + &fixedBuffer, + &headerSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + + memset( ( void * ) expectedPacket, 0x00, sizeof( expectedPacket ) ); + pIterator = expectedPacket; + /* Set the flags as follows: Dup = 0x8, QoS2 = 0x4, 8 | 4 = 0xC. */ + *pIterator++ = MQTT_PACKET_TYPE_PUBLISH | 0xC; + pIterator += encodeRemainingLength( pIterator, remainingLength ); + pIterator += encodeString( pIterator, publishInfo.pTopicName, publishInfo.topicNameLength ); + *pIterator++ = UINT16_HIGH_BYTE( PACKET_ID ); + *pIterator++ = UINT16_LOW_BYTE( PACKET_ID ); + TEST_ASSERT_EQUAL_MEMORY( expectedPacket, &buffer[ BUFFER_PADDING_LENGTH ], packetSize ); + checkBufferOverflow( buffer, sizeof( buffer ) ); } /* ========================================================================== */ @@ -1828,13 +1946,21 @@ void test_MQTT_SerializeAck( void ) expectedPacket[ 2 ] = UINT16_HIGH_BYTE( PACKET_ID ); expectedPacket[ 3 ] = UINT16_LOW_BYTE( PACKET_ID ); - /* Verify parameters. */ + /* Verify invalid parameter failures. */ status = MQTT_SerializeAck( NULL, packetType, PACKET_ID ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); status = MQTT_SerializeAck( &fixedBuffer, packetType, 0 ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify a NULL buffer in the fixed buffer struct fails */ + fixedBuffer.pBuffer = NULL; + status = MQTT_SerializeAck( &fixedBuffer, packetType, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Restore the fixed buffer. */ + fixedBuffer.pBuffer = &buffer[ BUFFER_PADDING_LENGTH ]; + /* Not a PUBACK, PUBREC, PUBREL, or PUBCOMP. */ status = MQTT_SerializeAck( &fixedBuffer, MQTT_PACKET_TYPE_CONNACK, PACKET_ID ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); @@ -2150,8 +2276,8 @@ void test_MQTT_SerializeConnect_Happy_Paths() /* ================== Testing MQTT_SerializeDisconnect ===================== */ /** - * @brief Call Mqtt_SerializeDisconnect using NULL pBuffer and insufficient buffer - * size in order to receive MQTTBadParameter and MQTTNoMemory errors. + * @brief Call Mqtt_SerializeDisconnect using a NULL pBuffer and an insufficient + * buffer size in order to receive MQTTBadParameter and MQTTNoMemory errors. */ void test_MQTT_SerializeDisconnect_Invalid_Params() { @@ -2159,11 +2285,16 @@ void test_MQTT_SerializeDisconnect_Invalid_Params() size_t packetSize = 0; MQTTFixedBuffer_t networkBuffer; - /* Test NULL pBuffer. */ + /* Test NULL pFixedBuffer. */ mqttStatus = MQTT_SerializeDisconnect( NULL ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - /* Test disconnectPacketSize > pBuffer->size. */ + /* Test a NULL pFixedBuffer->pBuffer. */ + networkBuffer.pBuffer = NULL; + mqttStatus = MQTT_SerializeDisconnect( &networkBuffer ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + + /* Test disconnectPacketSize > pFixedBuffer->size. */ /* Get MQTT disconnect packet size and remaining length. */ mqttStatus = MQTT_GetDisconnectPacketSize( &packetSize ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); From b1b9595023d64883b05b9794c6dc52f8e957dbe5 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 10 Jul 2020 17:12:37 -0700 Subject: [PATCH 579/844] Update README to include instructions for configuring AWS IoT credentials and setting up local echo servers (#1045) * Update README with instructions for setting up local server and configuring credentials from AWS IoT * Remove accidentally pushed certificates * Add optional to README --- README.md | 48 +++++++++++++++++-- demos/http/http_demo_mutual_tls/demo_config.h | 4 +- .../http_demo_mutual_tls.c | 35 +++++--------- 3 files changed, 58 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c305c497d6..eabd8ffe9b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# AWS IoT Device SDK C v4.0.0 +# AWS IoT Device SDK C v4.0.2 ## Development branch -This branch currently hosts development of the next iteration of the AWS IoT Embedded C SDK version 4. It is currently a work in progress and should not be used to create any products. We will update this README when that status changes. +This branch currently hosts development of AWS IoT Embedded C SDK version 4 beta 2. It is currently a work in progress and should not be used to create any products at the moment. We will update this README when that status changes. ## Building and Running Demos @@ -12,12 +12,50 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. - A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. - On Linux systems, installation of OpenSSL development libraries and header files, *version 1.1.0 or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. +### AWS IoT Account Setup +It is required to setup an AWS account and access the AWS IoT Console for running demos and tests. Follow the links to: +- [Setup an AWS account](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html). +- [Sign-in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) after setting up the AWS account. + +*Note: If using the Provisioning library, a fleet provisioning template, a provisioning claim, IoT policies and IAM policies need to be setup for the AWS account. Complete the steps to setup your device and AWS IoT account outlined [here](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#use-claim).* + +### Configuring the mutual auth demos +- You can pass the following configuration settings as command line options in order to run the mutual auth demos: `cmake .. -DAWS_IOT_ENDPOINT="aws-iot-endpoint" -DCLIENT_CERT_PATH="certificate-path" -DCLIENT_PRIVATE_KEY_PATH="private-key-path"` +- In order to set these settings manually, edit `demo_config.h` in `demos/mqtt/mqtt_demo_mutual_auth/` and `demos/http/http_demo_mutual_auth/` to `#define` the following: + - Set `AWS_IOT_ENDPOINT` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. + - Set `ROOT_CA_CERT_PATH` to the path of the root CA certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + - Set `CLIENT_CERT_PATH` to the path of the client certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + - Set `CLIENT_PRIVATE_KEY_PATH` to the path of the private key downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + ### Build Steps 1. Go to the root directory of this repository. 1. Create build directory: `mkdir build && cd build` 1. Run *cmake* while inside build directory: `cmake ..` 1. Run this command to build the demos: `make` 1. Go to the `build/bin` directory and run any demo executables from there. -1. To run demos prefixed with `http_`: - 1. Install `Python 3` if it is not yet installed in your system: `sudo apt-get install python3` - 1. Run the following from the root directory of this repository: `python3 tools/http-echo-server/http_server.py &` + +### Optional: Installing Mosquitto to run MQTT demos locally +1. [Download and install Mosquitto](https://mosquitto.org/download/) +1. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set `localhost`. +1. [Follow these instructions](https://dzone.com/articles/secure-communication-with-tls-and-the-mosquitto-broker) to setup TLS authentication for your local Mosquitto server. +1. Set `ROOT_CA_CERT_PATH` to the server certificate used when setting up TLS authentication for your local Mosquitto server. + +### Optional: Installing httpbin to run HTTP demos locally +1. Install `Docker`: +```shell +curl -fsSL https://get.docker.com -o get-docker.sh +sh get-docker.sh +``` +1. Run `httpbin` through port 80: +```shell +docker pull kennethreitz/httpbin +docker run -p 80:80 kennethreitz/httpbin +``` +1. `SERVER_HOST` defined in `demos/http/http_demo_plaintext/demo_config.h` can now be set to `localhost`. +1. To run `http_demo_basic_tls`, [download ngrok](https://ngrok.com/download) in order to create an HTTPS tunnel to the `httpbin` server currently hosted on port 80: +```shell +./ngrok http 80 # May have to use ./ngrok.exe depending on OS or filename of the executable +``` +1. `ngrok` will provide an https link that can be substituted in `demos/http/http_demo_basic_tls/demo_config.h` and has a format of `https://ABCDEFG12345.ngrok.io`. +1. Set `SERVER_HOST` in `demos/http/http_demo_basic_tls/demo_config.h` to the https link provided by `ngrok`. +1. You must also download the Root CA certificate provided by `ngrok` and set `ROOT_CA_CERT_PATH` in `demo_config.h` to the file path of the downloaded certificate. diff --git a/demos/http/http_demo_mutual_tls/demo_config.h b/demos/http/http_demo_mutual_tls/demo_config.h index f41b6c928a..1428a6bffb 100644 --- a/demos/http/http_demo_mutual_tls/demo_config.h +++ b/demos/http/http_demo_mutual_tls/demo_config.h @@ -55,7 +55,7 @@ * @note Your AWS IoT Core endpoint can be found in the AWS IoT console under * Settings/Custom Endpoint, or using the describe-endpoint API. * - * #define IOT_CORE_ENDPOINT "your-aws-iot-core-endpoint" + * #define AWS_IOT_ENDPOINT "your-aws-iot-core-endpoint" */ /** @@ -66,7 +66,7 @@ * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol * name being x-amzn-http-ca. When using port 8443, ALPN is not required. */ -#define IOT_CORE_PORT 443 +#define AWS_IOT_PORT 443 /** * @brief Path of the file containing Amazon's root CA certificate for TLS diff --git a/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c b/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c index 73a55e55b7..3f1dfb3f70 100644 --- a/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c +++ b/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c @@ -34,13 +34,13 @@ #include "openssl_posix.h" /* Check that AWS IoT Core endpoint is defined. */ -#ifndef IOT_CORE_ENDPOINT - #error "IOT_CORE_ENDPOINT must be defined to your AWS IoT Core endpoint." +#ifndef AWS_IOT_ENDPOINT + #error "AWS_IOT_ENDPOINT must be defined to your AWS IoT Core endpoint." #endif /* Check that TLS port used for AWS IoT Core is defined. */ -#ifndef IOT_CORE_PORT - #error "Please define a IOT_CORE_PORT." +#ifndef AWS_IOT_PORT + #error "Please define a AWS_IOT_PORT." #endif /* Check that a path for HTTP Method POST is defined. */ @@ -78,19 +78,10 @@ #define USER_BUFFER_LENGTH ( 2048 ) #endif -/** - * @brief ALPN protocol name to be sent as part of the ClientHello message. - * - * @note When using ALPN, port 443 must be used to connect to AWS IoT Core. - */ -#ifndef IOT_CORE_ALPN_PROTOCOL_NAME - #define IOT_CORE_ALPN_PROTOCOL_NAME "\x0ex-amzn-http-ca" -#endif - /** * @brief The length of the HTTP server host name. */ -#define IOT_CORE_ENDPOINT_LENGTH ( sizeof( IOT_CORE_ENDPOINT ) - 1 ) +#define AWS_IOT_ENDPOINT_LENGTH ( sizeof( AWS_IOT_ENDPOINT ) - 1 ) /** * @brief The length of the ALPN protocol name. @@ -173,8 +164,8 @@ static int sendHttpRequest( const TransportInterface_t * pTransportInterface, ( void ) memset( &requestHeaders, 0, sizeof( requestHeaders ) ); /* Initialize the request object. */ - requestInfo.pHost = IOT_CORE_ENDPOINT; - requestInfo.hostLen = IOT_CORE_ENDPOINT_LENGTH; + requestInfo.pHost = AWS_IOT_ENDPOINT; + requestInfo.hostLen = AWS_IOT_ENDPOINT_LENGTH; requestInfo.method = pMethod; requestInfo.methodLen = methodLen; requestInfo.pPath = pPath; @@ -200,7 +191,7 @@ static int sendHttpRequest( const TransportInterface_t * pTransportInterface, LogInfo( ( "Sending HTTP %.*s request to %.*s%.*s...", ( int32_t ) methodLen, pMethod, - ( int32_t ) IOT_CORE_ENDPOINT_LENGTH, IOT_CORE_ENDPOINT, + ( int32_t ) AWS_IOT_ENDPOINT_LENGTH, AWS_IOT_ENDPOINT, ( int32_t ) pathLen, pPath ) ); LogDebug( ( "Request Headers:\n%.*s\n" "Request Body:\n%.*s\n", @@ -228,7 +219,7 @@ static int sendHttpRequest( const TransportInterface_t * pTransportInterface, "Response Headers:\n%.*s\n" "Response Status:\n%u\n" "Response Body:\n%.*s\n", - ( int32_t ) IOT_CORE_ENDPOINT_LENGTH, IOT_CORE_ENDPOINT, + ( int32_t ) AWS_IOT_ENDPOINT_LENGTH, AWS_IOT_ENDPOINT, ( int32_t ) pathLen, pPath, ( int32_t ) response.headersLen, response.pHeaders, response.statusCode, @@ -238,7 +229,7 @@ static int sendHttpRequest( const TransportInterface_t * pTransportInterface, { LogError( ( "Failed to send HTTP %.*s request to %.*s%.*s: Error=%s.", ( int32_t ) methodLen, pMethod, - ( int32_t ) IOT_CORE_ENDPOINT_LENGTH, IOT_CORE_ENDPOINT, + ( int32_t ) AWS_IOT_ENDPOINT_LENGTH, AWS_IOT_ENDPOINT, ( int32_t ) pathLen, pPath, HTTPClient_strerror( httpStatus ) ) ); } @@ -294,9 +285,9 @@ int main( int argc, opensslCredentials.alpnProtosLen = IOT_CORE_ALPN_PROTOCOL_NAME_LENGTH; /* Initialize server information. */ - serverInfo.pHostName = IOT_CORE_ENDPOINT; - serverInfo.hostNameLength = IOT_CORE_ENDPOINT_LENGTH; - serverInfo.port = IOT_CORE_PORT; + serverInfo.pHostName = AWS_IOT_ENDPOINT; + serverInfo.hostNameLength = AWS_IOT_ENDPOINT_LENGTH; + serverInfo.port = AWS_IOT_PORT; /**************************** Connect. ******************************/ From d98cd7ad8d2f72c72b44ca3c57d7ad8ac0678fc9 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Sun, 12 Jul 2020 13:19:22 -0700 Subject: [PATCH 580/844] Change returnStatus of unsubscribeFromTopic to int rather than MQTTStatus to fix potential build error (#1049) --- demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 4 ++-- demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c | 4 ++-- demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index fc2422c026..a53c1a2a85 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -305,7 +305,7 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ); * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ); +static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at @@ -807,7 +807,7 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ) /*-----------------------------------------------------------*/ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ) +static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) { int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 8a73894abc..f569d95f9d 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -339,7 +339,7 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ); * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ); +static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at @@ -836,7 +836,7 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ) /*-----------------------------------------------------------*/ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ) +static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) { int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 96d2d3369f..3bc4fb188c 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -260,7 +260,7 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ); * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; * EXIT_FAILURE otherwise. */ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ); +static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ); /** * @brief Sends an MQTT PUBLISH to #MQTT_EXAMPLE_TOPIC defined at @@ -573,7 +573,7 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ) /*-----------------------------------------------------------*/ -static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pMqttContext ) +static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) { int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; From 9d9b21e83762986c5dfbfe1a97b05b5ea7bb3c89 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 13 Jul 2020 12:59:58 -0700 Subject: [PATCH 581/844] Rename http_demo_mutual_tls to http_demo_mutual_auth. (#1050) --- .../CMakeLists.txt | 2 +- .../demo_config.h | 0 .../http_config.h | 0 .../http_demo_mutual_tls.c | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename demos/http/{http_demo_mutual_tls => http_demo_mutual_auth}/CMakeLists.txt (96%) rename demos/http/{http_demo_mutual_tls => http_demo_mutual_auth}/demo_config.h (100%) rename demos/http/{http_demo_mutual_tls => http_demo_mutual_auth}/http_config.h (100%) rename demos/http/{http_demo_mutual_tls => http_demo_mutual_auth}/http_demo_mutual_tls.c (100%) diff --git a/demos/http/http_demo_mutual_tls/CMakeLists.txt b/demos/http/http_demo_mutual_auth/CMakeLists.txt similarity index 96% rename from demos/http/http_demo_mutual_tls/CMakeLists.txt rename to demos/http/http_demo_mutual_auth/CMakeLists.txt index 51fb6cf905..669bfc6970 100644 --- a/demos/http/http_demo_mutual_tls/CMakeLists.txt +++ b/demos/http/http_demo_mutual_auth/CMakeLists.txt @@ -1,4 +1,4 @@ -set( DEMO_NAME "http_demo_mutual_tls" ) +set( DEMO_NAME "http_demo_mutual_auth" ) # Demo target. add_executable(${DEMO_NAME}) diff --git a/demos/http/http_demo_mutual_tls/demo_config.h b/demos/http/http_demo_mutual_auth/demo_config.h similarity index 100% rename from demos/http/http_demo_mutual_tls/demo_config.h rename to demos/http/http_demo_mutual_auth/demo_config.h diff --git a/demos/http/http_demo_mutual_tls/http_config.h b/demos/http/http_demo_mutual_auth/http_config.h similarity index 100% rename from demos/http/http_demo_mutual_tls/http_config.h rename to demos/http/http_demo_mutual_auth/http_config.h diff --git a/demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c b/demos/http/http_demo_mutual_auth/http_demo_mutual_tls.c similarity index 100% rename from demos/http/http_demo_mutual_tls/http_demo_mutual_tls.c rename to demos/http/http_demo_mutual_auth/http_demo_mutual_tls.c From af15abc2b66adba0768bc80d1dcf44fbc83e3533 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 14 Jul 2020 09:54:50 -0700 Subject: [PATCH 582/844] Fix warnings in http_client.c and mqtt_lightweight.c (#1053) Deleted commas in the middle of log strings. Fixed incorrect string modifiers. --- libraries/standard/http/src/http_client.c | 21 +++++++++---------- .../standard/mqtt/src/mqtt_lightweight.c | 6 +++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 55896c165b..a00fcae9a3 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1548,7 +1548,7 @@ HTTPStatus_t HTTPClient_AddRangeHeader( HTTPRequestHeaders_t * pRequestHeaders, else if( rangeStartOrlastNbytes == INT32_MIN ) { LogError( ( "Parameter check failed: Arithmetic overflow detected: " - "rangeStart should be > -2147483648 (INT32_MIN): ", + "rangeStart should be > -2147483648 (INT32_MIN): " "RangeStart=%d", rangeStartOrlastNbytes ) ); returnStatus = HTTP_INVALID_PARAMETER; @@ -2099,8 +2099,8 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) /* If we have reached here, all headers in the response have been parsed but the requested * header has not been found in the response buffer. */ LogDebug( ( "Reached end of header parsing: Header not found in response: " - "RequestedHeader=%u*s", - ( pContext->fieldLen ), + "RequestedHeader=%.*s", + ( int ) ( pContext->fieldLen ), pContext->pField ) ); /* No further parsing is required; thus, indicate the parser to stop parsing. */ @@ -2160,9 +2160,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, assert( context.valueFound == 0u ); /* Header is not present in buffer. */ - LogWarn( ( "Header not found in response buffer: " - "RequestedHeader=%u*s", - fieldLen, + LogWarn( ( "Header not found in response buffer: RequestedHeader=%.*s", + ( int ) fieldLen, pField ) ); returnStatus = HTTP_HEADER_NOT_FOUND; @@ -2173,8 +2172,8 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, * in the ": \r\n" format of an HTTP header. */ LogError( ( "Unable to find header value in response: " "Response data is invalid: " - "RequestedHeader=%u*s, ParserError=%s", - fieldLen, + "RequestedHeader=%.*s, ParserError=%s", + ( int ) fieldLen, pField, http_errno_description( HTTP_PARSER_ERRNO( &( parser ) ) ) ) ); returnStatus = HTTP_INVALID_RESPONSE; @@ -2188,10 +2187,10 @@ static HTTPStatus_t findHeaderInResponse( const uint8_t * pBuffer, assert( ( context.fieldFound == 1u ) && ( context.valueFound == 1u ) ); LogDebug( ( "Found requested header in response: " - "HeaderName=%u*s, HeaderValue=%u*s", - fieldLen, + "HeaderName=%.*s, HeaderValue=%.*s", + ( int ) fieldLen, pField, - *pValueLen, + ( int ) ( *pValueLen ), *pValueLoc ) ); } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index f02b72614f..e0b2c1a0fa 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1772,9 +1772,9 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, * cannot be NULL. */ else if( ( pPublishInfo->payloadLength > 0 ) && ( pPublishInfo->pPayload == NULL ) ) { - LogError( ( "A nonzero payload length requires a non-NULL payload: ", - "payloadLength=%u, pPayload=%p.", - pPublishInfo->payloadLength, + LogError( ( "A nonzero payload length requires a non-NULL payload: " + "payloadLength=%lu, pPayload=%p.", + ( unsigned long ) pPublishInfo->payloadLength, pPublishInfo->pPayload ) ); status = MQTTBadParameter; } From 629e090f1d96ff23b1646fbb3b4f44583ebc45c8 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 14 Jul 2020 11:47:09 -0700 Subject: [PATCH 583/844] Add lexicon.txt to demos, libraries, and platform. (#1054) Add lexicon.txt to demos, platform, libraries/standard/http, libraries/standard/json, and libraries/standard/mqtt. Fix spelling mistakes where they were found. Update script to ignore the third_party folder. Add README.md on how to create a new lexicon.txt. --- demos/lexicon.txt | 100 ++++++++ demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h | 2 +- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 8 +- .../mqtt_demo_lightweight.c | 14 +- .../mqtt_demo_mutual_auth.c | 4 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 8 +- libraries/standard/http/include/http_client.h | 6 +- libraries/standard/http/lexicon.txt | 143 +++++++++++ libraries/standard/http/src/http_client.c | 10 +- .../http/src/private/http_client_internal.h | 4 +- .../standard/http/utest/http_send_utest.c | 6 +- libraries/standard/json/lexicon.txt | 45 ++++ .../standard/mqtt/include/mqtt_lightweight.h | 2 +- libraries/standard/mqtt/lexicon.txt | 229 ++++++++++++++++++ libraries/standard/mqtt/src/mqtt_state.c | 8 +- libraries/standard/mqtt/utest/mqtt_config.h | 2 +- .../mqtt/utest/mqtt_lightweight_utest.c | 2 +- .../standard/mqtt/utest/mqtt_state_utest.c | 4 +- platform/include/transport_reconnect.h | 4 +- platform/lexicon.txt | 71 ++++++ .../posix/transport/include/openssl_posix.h | 4 +- .../posix/transport/include/plaintext_posix.h | 2 +- .../transport/src/transport_reconnect_posix.c | 2 +- tools/spell/README.md | 20 ++ tools/spell/extract-comments | 2 +- tools/spell/find-unknown-comment-words | 4 + 26 files changed, 659 insertions(+), 47 deletions(-) create mode 100644 demos/lexicon.txt create mode 100644 libraries/standard/http/lexicon.txt create mode 100644 libraries/standard/json/lexicon.txt create mode 100644 libraries/standard/mqtt/lexicon.txt create mode 100644 platform/lexicon.txt create mode 100644 tools/spell/README.md diff --git a/demos/lexicon.txt b/demos/lexicon.txt new file mode 100644 index 0000000000..6abf836eef --- /dev/null +++ b/demos/lexicon.txt @@ -0,0 +1,100 @@ +ack +acks +alpn +amazonrootca +amazontrust +amzn +api +apis +auth +aws +backoff +br +ca +cert +certs +clienthello +cmake +com +config +configs +connack +constantspage +copybrief +createcleansession +crt +deserialize +deserialized +deserializer +deserializing +developerguide +doesn +dup +endif +hasn +html +http +httpbin +httpclient +https +ifndef +inc +initializerequestheaders +iot +lastcontrolpacketsent +metadata +methodlen +milli +mosquitto +mqtt +noninfringement +openssl +org +outgoingpublishpackets +packetid +packetidentifier +param +pathlen +pbuffer +pem +pfixedbuffer +pincomingpacket +pindex +pingreq +pingresp +plaintext +pmethod +pmqttcontext +pnetworkcontext +posix +ppacketinfo +ppath +ppubinfo +ppublishinfo +processloop +psessionpresent +ptransportinterface +puback +pubcomp +publishpacketsent +pubrec +pubrel +qos +resending +resp +sdk +ssl +structs +suback +sublicense +subscribepublishloop +tcp +tcpsocket +tls +transportinterface +transporttimeout +uint +unsub +unsuback +uri +www diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h index 9dcb69293d..1d8c997003 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h @@ -64,7 +64,7 @@ * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. * - * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before * they can be completed. While they are awaiting the acknowledgement, the * client must maintain information about their state. The value of this * macro sets the limit on how many simultaneous PUBLISH states an MQTT diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index a53c1a2a85..8d1aed1fe0 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -212,7 +212,7 @@ static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; * * If connection fails, retry is attempted after a timeout. * Timeout value will exponentially increased till maximum - * timeout value is reached or the number of attemps are exhausted. + * timeout value is reached or the number of attempts are exhausted. * * @param[out] pNetworkContext The output parameter to return the created network context. * @@ -386,7 +386,7 @@ static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext /* Attempt to connect to MQTT broker. If connection fails, retry after * a timeout. Timeout value will exponentially increase till maximum - * attemps are reached. + * attempts are reached. */ do { @@ -1097,9 +1097,9 @@ int main( int argc, { /* Attempt to connect to the MQTT broker. If connection fails, retry after * a timeout. Timeout value will be exponentially increased till the maximum - * attemps are reached or maximum timout value is reached. The function + * attempts are reached or maximum timeout value is reached. The function * returns EXIT_FAILURE if the TCP connection cannot be established to - * broker after configured number of attemps. */ + * broker after configured number of attempts. */ returnStatus = connectToServerWithBackoffRetries( &networkContext ); if( returnStatus == EXIT_FAILURE ) diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index c5b5dfbce0..d4f9d76bd0 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -126,7 +126,7 @@ static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext * * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. - * The buffer is used for serialzing CONNECT packet and deserializing CONN-ACK. + * The buffer is used for serializing CONNECT packet and deserializing CONN-ACK. * * @return EXIT_SUCCESS if an MQTT session is established; EXIT_FAILURE otherwise. */ @@ -139,7 +139,7 @@ static int createMQTTConnectionWithBroker( NetworkContext_t * pNetworkContext, * * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. - * The buffer is used for serialzing SUBSCRIBE packet. + * The buffer is used for serializing SUBSCRIBE packet. * */ static void mqttSubscribeToTopic( NetworkContext_t * pNetworkContext, @@ -150,7 +150,7 @@ static void mqttSubscribeToTopic( NetworkContext_t * pNetworkContext, * * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. - * The buffer is used for serialzing PUBLISH packet. + * The buffer is used for serializing PUBLISH packet. * */ static void mqttPublishToTopic( NetworkContext_t * pNetworkContext, @@ -162,7 +162,7 @@ static void mqttPublishToTopic( NetworkContext_t * pNetworkContext, * * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. - * The buffer is used for serialzing UNSUBSCRIBE packet. + * The buffer is used for serializing UNSUBSCRIBE packet. * */ static void mqttUnsubscribeFromTopic( NetworkContext_t * pNetworkContext, @@ -173,7 +173,7 @@ static void mqttUnsubscribeFromTopic( NetworkContext_t * pNetworkContext, * * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. - * The buffer is used for serialzing DISCONNECT packet. + * The buffer is used for serializing DISCONNECT packet. */ static void mqttDisconnect( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); @@ -183,7 +183,7 @@ static void mqttDisconnect( NetworkContext_t * pNetworkContext, * * @param[in] pNetworkContext Pointer to the network context created using Plaintext_Connect. * @param[in] pFixedBuffer Pointer to a structure containing fixed buffer and its length. - * The buffer is used for serialzing PING request packet. + * The buffer is used for serializing PING request packet. */ static void mqttKeepAlive( NetworkContext_t * pNetworkContext, MQTTFixedBuffer_t * pFixedBuffer ); @@ -309,7 +309,7 @@ static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext /* Attempt to connect to MQTT broker. If connection fails, retry after * a timeout. Timeout value will exponentially increase till maximum - * attemps are reached. + * attempts are reached. */ do { diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index f569d95f9d..76895d8ab1 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -1126,9 +1126,9 @@ int main( int argc, { /* Attempt to connect to the MQTT broker. If connection fails, retry after * a timeout. Timeout value will be exponentially increased till the maximum - * attemps are reached or maximum timout value is reached. The function + * attempts are reached or maximum timeout value is reached. The function * returns EXIT_FAILURE if the TCP connection cannot be established to - * broker after configured number of attemps. */ + * broker after configured number of attempts. */ returnStatus = connectToServerWithBackoffRetries( &networkContext ); if( returnStatus == EXIT_FAILURE ) diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 3bc4fb188c..3e79223938 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -174,7 +174,7 @@ static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; * * If connection fails, retry is attempted after a timeout. * Timeout value will exponentially increased till maximum - * timeout value is reached or the number of attemps are exhausted. + * timeout value is reached or the number of attempts are exhausted. * * @param[out] pNetworkContext The output parameter to return the created network context. * @@ -293,7 +293,7 @@ static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext /* Attempt to connect to MQTT broker. If connection fails, retry after * a timeout. Timeout value will exponentially increase till maximum - * attemps are reached. + * attempts are reached. */ do { @@ -823,9 +823,9 @@ int main( int argc, { /* Attempt to connect to the MQTT broker. If connection fails, retry after * a timeout. Timeout value will be exponentially increased till the maximum - * attemps are reached or maximum timout value is reached. The function + * attempts are reached or maximum timeout value is reached. The function * returns EXIT_FAILURE if the TCP connection cannot be established to - * broker after configured number of attemps. */ + * broker after configured number of attempts. */ returnStatus = connectToServerWithBackoffRetries( &networkContext ); if( returnStatus == EXIT_FAILURE ) diff --git a/libraries/standard/http/include/http_client.h b/libraries/standard/http/include/http_client.h index 51afcbd65d..e89544bc68 100644 --- a/libraries/standard/http/include/http_client.h +++ b/libraries/standard/http/include/http_client.h @@ -23,7 +23,7 @@ /** * @brief The HTTP header "User-Agent" value. * - * The following headerline is automatically written to + * The following header line is automatically written to * #HTTPRequestHeaders_t.pBuffer: * "User-Agent: my-platform-name\r\n" */ @@ -308,7 +308,7 @@ typedef struct HTTPRequestHeaders * * This buffer is owned by the library during #HTTPClient_AddHeader, * #HTTPClient_AddRangeHeader, #HTTPClient_InitializeRequestHeaders, and - * #HTTPClient_Send. This buffer should not be modifed until + * #HTTPClient_Send. This buffer should not be modified until * after these functions return. * * For optimization this buffer may be re-used with the response. The user @@ -396,7 +396,7 @@ typedef struct HTTPResponse * This buffer is supplied by the application. * * This buffer is owned by the library during #HTTPClient_Send and - * #HTTPClient_ReadHeader. This buffer should not be modifed until after + * #HTTPClient_ReadHeader. This buffer should not be modified until after * these functions return. * * For optimization this buffer may be used with the request headers. The diff --git a/libraries/standard/http/lexicon.txt b/libraries/standard/http/lexicon.txt new file mode 100644 index 0000000000..351eba5508 --- /dev/null +++ b/libraries/standard/http/lexicon.txt @@ -0,0 +1,143 @@ +absolutevalue +addrangeheader +addrangerequest +aggregator +api +ascii +br +bufferlen +cbmc +chunked +com +config +const +contentlength +copybrief +coverity +datalen +doesn +endif +enums +eof +errno +expectedheader +fieldfound +fieldlen +fieldlentoreturn +fieldloc +findheaderfieldparsercallback +findheaderinresponse +firstpartbytes +getfinalresponsestatus +github +headerslen +hostlen +html +http +httpparseonstatusfieldcallback +httpparser +httpparseronbodycallback +httpparseronheaderfieldcallback +httpparseronheaderscompletecallback +httpparseronheadervaluecallback +httpparseronstatuscallback +httpparserxxxxcallback +httpparserxxxxcallbacks +httprequestinfo +https +ietf +ifndef +inc +init +isheaderresponse +isheadresponse +iso +lastheaderfieldlen +lastheadervaluelen +latin +malloc +methodlen +misra +networkcontext +nodejs +noninfringement +ok +onbody +onheadercallback +onheaderfield +onheaderscomplete +onheadervalue +onmessagebegin +onmessagecomplete +onstatus +ored +org +param +parsehttpresponse +parselen +parsersettings +parsingstate +pathlen +pbuffer +pbuffercur +pbytesreceived +pcontext +pdata +pfield +pfieldloc +pheaders +phost +phttpparser +plastheaderfield +plastheadervalue +ploc +pmethod +pname +pnetworkdata +pnext +pnextwriteloc +pparsingcontext +pparsingstate +ppath +pre +prefill +prequestbodybuf +prequestheaders +prequestinfo +presponse +processhttpparsererror +ptransport +pvalue +pvaluelen +pvalueloc +rangeend +rangestart +rangestartorlastnbytes +receivehttpdata +recv +recvcurrentcall +recvstopcall +reponse +reqbodybuflen +reqbodylen +reqflags +requestheaders +requestinfo +respflags +responsebufferlen +senderrorcall +sendflags +sendpartialcall +snprintf +src +statuscode +strchr +sublicense +totalreceived +txt +uri +utest +valuefound +valuelen +valueloc +xxxx diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index a00fcae9a3..ff6092f45e 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -346,7 +346,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, * header value is found. * * This header value corresponds to the header field that was found in the - * immediately preceeding httpParserOnHeaderFieldCallback(). + * immediately preceding httpParserOnHeaderFieldCallback(). * * If only part of the header value was found, then parsing of the next part of * the response message will invoke this callback in succession. @@ -432,7 +432,7 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, * zero length-ed parsing data to indicate the end of the response. * * For a "Transfer-Encoding: chunked" type of response message, the complete - * response message is signalled by a terminating chunk header with length zero. + * response message is signaled by a terminating chunk header with length zero. * * See https://github.com/nodejs/http-parser for more information. * @@ -950,7 +950,7 @@ static HTTPStatus_t processHttpParserError( const http_parser * pHttpParser ) case HPE_STRICT: case HPE_INVALID_CONSTANT: LogError( ( "Response parsing error: Invalid character found in " - "Status-Line or header delimitters." ) ); + "Status-Line or header delimiters." ) ); returnStatus = HTTP_SECURITY_ALERT_INVALID_CHARACTER; break; @@ -993,7 +993,7 @@ static HTTPStatus_t processHttpParserError( const http_parser * pHttpParser ) } /* Errors with CB_ prepending are manual returns of non-zero in the - * response parsing callbacked. */ + * response parsing callback. */ LogDebug( ( "http-parser errno description: %s", http_errno_description( HTTP_PARSER_ERRNO( pHttpParser ) ) ) ); @@ -1791,7 +1791,7 @@ static HTTPStatus_t getFinalResponseStatus( HTTPParsingState_t parsingState, { if( totalReceived == responseBufferLen ) { - LogError( ( "Cannot receive complete response from tansport" + LogError( ( "Cannot receive complete response from transport" " interface: Response buffer has insufficient " "space: responseBufferLen=%lu", ( unsigned long ) responseBufferLen ) ); diff --git a/libraries/standard/http/src/private/http_client_internal.h b/libraries/standard/http/src/private/http_client_internal.h index df8fd48a79..dd60a55692 100644 --- a/libraries/standard/http/src/private/http_client_internal.h +++ b/libraries/standard/http/src/private/http_client_internal.h @@ -34,7 +34,7 @@ #define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1u ) /** - * @brief Consants for HTTP header formatting + * @brief Constants for HTTP header formatting */ #define HTTP_HEADER_LINE_SEPARATOR "\r\n" #define HTTP_HEADER_LINE_SEPARATOR_LEN ( sizeof( HTTP_HEADER_LINE_SEPARATOR ) - 1u ) @@ -184,7 +184,7 @@ typedef struct findHeaderContext * The registered callbacks are private functions of the form * httpParserXXXXCallbacks(). * - * The transitions of the httpParserXXXXCallback() functions are shown belown. + * The transitions of the httpParserXXXXCallback() functions are shown below. * The XXXX is replaced by the strings in the state boxes: * * +---------------------+ diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index ca541e1b97..e10dbbbcb6 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -188,7 +188,7 @@ static size_t firstPartBytes = 0; /* The count of times a test invoked the transport receive interface. */ static uint8_t recvCurrentCall = 0; -/* The test sets this variable to indcate which call count count of transport +/* The test sets this variable to indicate which call count count of transport * receive to return an error from. */ static uint8_t recvStopCall = 0; /* The count of times a mocked http_parser_execute callback has been invoked. */ @@ -1089,7 +1089,7 @@ void test_HTTPClient_Send_response_larger_than_buffer( void ) firstPartBytes = HTTP_TEST_RESPONSE_GET_PARTIAL_BODY_LENGTH; response.bufferLen = HTTP_TEST_RESPONSE_GET_PARTIAL_BODY_LENGTH; - /* For everage of no header parsing callback configured. */ + /* For coverage of no header parsing callback configured. */ response.pHeaderParsingCallback = NULL; returnStatus = HTTPClient_Send( &transportInterface, @@ -1426,7 +1426,7 @@ void test_HTTPClient_Send_not_enough_request_headers( void ) /*-----------------------------------------------------------*/ -/* Test a NULL request body but a non-zero requets body length. +/* Test a NULL request body but a non-zero requests body length. */ void test_HTTPClient_Send_null_request_body_nonzero_body_length( void ) { diff --git a/libraries/standard/json/lexicon.txt b/libraries/standard/json/lexicon.txt new file mode 100644 index 0000000000..4f8bb94420 --- /dev/null +++ b/libraries/standard/json/lexicon.txt @@ -0,0 +1,45 @@ +abc +ascii +bf +buf +buflength +com +dbff +df +dfff +ef +endif +fb +fc +fd +fe +ff +foo +ifndef +inc +json +jsonillegaldocument +jsonmaxdepthexceeded +jsonnotfound +jsonpartial +jsonsuccess +keylength +misra +multibyte +noninfringement +nul +outvalue +outvaluelength +param +printf +querykey +querykeylength +requirelowsurrogate +sizeof +src +sublicense +unescaped +unicode +utf +valuelength +xyz diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index ec9bf4e340..44ee296ddd 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -25,7 +25,7 @@ #include #include -/* bools are only defined in C99+ */ +/* bool is defined in only C99+. */ #if defined( __cplusplus ) || ( defined( __STDC_VERSION__ ) && ( __STDC_VERSION__ >= 199901L ) ) #include #elif !defined( bool ) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt new file mode 100644 index 0000000000..270a9506e0 --- /dev/null +++ b/libraries/standard/mqtt/lexicon.txt @@ -0,0 +1,229 @@ +ack +acked +acks +addrecord +api +app +aws +bufferlength +bytesreceived +bytestoread +bytestoreceive +bytestorecv +bytestosend +bytestowrite +ca +calculatestatepublish +cbmc +cleansession +cmock +com +config +connack +connectinfo +connectpacketsize +const +createcleansession +currentstate +defragmenting +deserialization +deserialize +deserializeack +deserialized +deserializepublish +deserializing +didn +disconnectpacketsize +doesn +dup +emptyindex +endif +enums +expectprocessloopcalls +fixedbuffer +getconnectpacketsize +getdisconnectpacketsize +getincomingpackettypeandlength +getpacketid +getpingreqpacketsize +getpublishpacketsize +getsubscribepacketsize +gettime +getunsubscribepacketsize +handleincomingack +handleincomingpublish +handlekeepalive +hasn +headersize +https +ifndef +inc +incomingpacket +incomingpublish +init +int +iot +isn +lwt +malloc +managekeepalive +memcpy +milli +min +misra +modifyincomingpacket +mosquitto +mqtt +mqttbadparameter +mqttbadresponse +mqttconnectinfo +mqttcontext +mqttfixedbuffer +mqttillegalstate +mqttkeepalivetimeout +mqttnodataavailable +mqttnomemory +mqttpacketinfo +mqttpubackpending +mqttpubacksend +mqttpubacktype +mqttpubcomppending +mqttpubcompsend +mqttpublishdone +mqttpublishinfo +mqttpublishsend +mqttpubrecpending +mqttpubrecsend +mqttpubrelpending +mqttpubrelsend +mqttqos +mqttrecvfailed +mqttsendfailed +mqttserverrefused +mqttstatecollision +mqttstatenull +mqttsubscribeinfo +mqttsuccess +msb +networkbuffer +networkcontext +newstate +nextpacketid +noninfringement +openssl +optype +org +packetid +packetidentifier +packetsize +packettype +param +paramters +payloadlength +pbuffer +pbuffertosend +pcallbacks +pconnectinfo +pcontext +pcurrentstate +pcursor +pdestination +pem +pfixedbuffer +pheadersize +pincomingpacket +pingreq +pingresp +pingresps +pingresptimeoutms +pmessage +pmqttcontext +pnetworkbuffer +pnetworkcontext +pnewstate +posix +ppacketid +ppacketinfo +ppacketsize +ppubinfo +ppublishinfo +pqos +pre +premainingdata +premaininglength +processloop +psessionpresent +psource +pstate +psubscribeinfo +psubscriptionlist +ptr +ptransport +ptransportinterface +puback +pubcomp +publishstate +pubrec +pubrel +pubrels +pwillinfo +qos +readfunc +receiveloop +receivepacket +recordcount +recordindex +recv +recvfunc +reestablishment +remaininglength +remainingtime +remainingtimems +resending +responsecode +searchstates +sendpacket +sendpublish +sendpublishacks +serializeack +serializeconnect +serializeconnectpacket +serializedisconnect +serializepayload +serializepingreq +serializepublish +serializepublishheader +serializesubscribe +serializeunsubscribe +shoulddelete +shouldn +sizeof +sourcelength +src +stateafterdeserialize +strerror +struct +structs +suback +sublicense +subscriptioncount +subscriptiontype +tcp +tcpsocket +testcase +timeoutms +tls +uint +un +unsuback +updatestateack +updatestatepublish +updatestatestatus +utest +utf +validatesubscribeunsubscribeparams +xa +xb +xc +xd +xe diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index 8afd93050a..42fbda686c 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -299,7 +299,7 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, * There are 2 valid transitions possible. * 1. MQTTPubRelPending -> MQTTPubCompSend : A PUBREL ack is received * when publish record state is MQTTPubRelPending. This is the - * normal state transition without any connection interuptions. + * normal state transition without any connection interruptions. * 2. MQTTPubRelPending -> MQTTPubRelPending : Receiving a duplicate * QoS2 publish can result in a transition to the same state. * This can happen in the below state transition. @@ -322,7 +322,7 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, * There are 2 valid transitions possible. * 1. MQTTPubCompSend -> MQTTPublishDone : A PUBCOMP ack is sent * after receiving a PUBREL from broker. This is the - * normal state transition without any connection interuptions. + * normal state transition without any connection interruptions. * 2. MQTTPubCompSend -> MQTTPubCompSend : Receiving a duplicate PUBREL * can result in a transition to the same state. * This can happen in the below state transition. @@ -351,9 +351,9 @@ static bool validateTransitionAck( MQTTPublishState_t currentState, /* Outgoing publish, Qos 2. * There are 2 valid transitions possible. * 1. MQTTPubCompPending -> MQTTPublishDone : A PUBCOMP is received. - * This marks the complete state transistion for the publish packet. + * This marks the complete state transition for the publish packet. * This is the normal state transition without any connection - * interuptions. + * interruptions. * 2. MQTTPubCompPending -> MQTTPubCompPending : Resending a PUBREL for * packets in state #MQTTPubCompPending can result in this * transition to the same state. diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index a1dfd3eb62..c1eb585bda 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -26,7 +26,7 @@ * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. * - * QoS 1 and 2 MQTT PUBLISHes require acknowlegement from the server before + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before * they can be completed. While they are awaiting the acknowledgement, the * client must maintain information about their state. The value of this * macro sets the limit on how many simultaneous PUBLISH states an MQTT diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 2d55c321cf..b4826726a3 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -956,7 +956,7 @@ void test_MQTT_GetPublishPacketSize( void ) size_t packetSize; MQTTStatus_t status = MQTTSuccess; - /* Verify bad paramameters fail. */ + /* Verify bad parameters fail. */ status = MQTT_GetPublishPacketSize( NULL, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c index 3df940e82e..7a125e96a8 100644 --- a/libraries/standard/mqtt/utest/mqtt_state_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -606,11 +606,11 @@ void test_MQTT_UpdateStateAck( void ) TEST_ASSERT_EQUAL( MQTTPubRelSend, state ); /* Receiving a PUBREC will move the record to the end. - * In this case, the record wil be moved to index 2. */ + * In this case, the record will be moved to index 2. */ TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 2 ].packetId ); TEST_ASSERT_EQUAL( MQTTQoS2, mqttContext.outgoingPublishRecords[ 2 ].qos ); TEST_ASSERT_EQUAL( MQTTPubRelSend, mqttContext.outgoingPublishRecords[ 2 ].publishState ); - /* Record at the current index will be marked as onvalid. */ + /* Record at the current index will be marked as invalid. */ TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 0 ].packetId ); /* Incoming. */ diff --git a/platform/include/transport_reconnect.h b/platform/include/transport_reconnect.h index e83f651f2b..b9069fd73a 100644 --- a/platform/include/transport_reconnect.h +++ b/platform/include/transport_reconnect.h @@ -31,7 +31,7 @@ /* Standard include. */ #include -/* bools are only defined in C99+ */ +/* bool is defined in only C99+. */ #if defined( __cplusplus ) || __STDC_VERSION__ >= 199901L #include #elif !defined( bool ) @@ -75,7 +75,7 @@ typedef struct TransportReconnectParams void Transport_ReconnectParamsReset( TransportReconnectParams_t * reconnectParams ); /** - * @brief Simple platfrom specific exponential backoff function. The application + * @brief Simple platform specific exponential backoff function. The application * must use this function between connection failures to add exponential delay. * This function will block the calling task for the current timeout value. * diff --git a/platform/lexicon.txt b/platform/lexicon.txt new file mode 100644 index 0000000000..b5cb5ef241 --- /dev/null +++ b/platform/lexicon.txt @@ -0,0 +1,71 @@ +alpn +api +aws +backoff +bool +bytestorecv +bytestosend +ca +com +const +cwd +dns +endif +errno +filelabel +filepath +filepaths +functionname +functionpage +functionspage +getaddrinfo +getcwd +hostnamelength +https +ifndef +implemenation +inc +iot +logpath +malloc +maxattempts +messagelevel +mfln +mqtt +nano +networkcontext +noninfringement +openssl +param +pbuffer +pclientcertpath +pformat +phostname +platfrom +plisthead +pnetworkcontext +popensslcredentials +posix +pprivatekeypath +prootcapath +pserverinfo +pssl +psslcontext +ptcpsocket +ramdom +reconnectparam +recv +recvtimeout +recvtimeoutms +sdk +sendtimeout +sendtimeoutms +sni +src +ssl +sublicense +tcp +tcpsocket +timespec +tls +transportinterface diff --git a/platform/posix/transport/include/openssl_posix.h b/platform/posix/transport/include/openssl_posix.h index e8b9fdbad4..8d4bf0f913 100644 --- a/platform/posix/transport/include/openssl_posix.h +++ b/platform/posix/transport/include/openssl_posix.h @@ -35,7 +35,7 @@ /* Include header that defines log levels. */ #include "logging_levels.h" -/* Logging configuration for the transport interface implemenation which uses +/* Logging configuration for the transport interface implementation which uses * OpenSSL and Sockets. */ #ifndef LIBRARY_LOG_NAME #define LIBRARY_LOG_NAME "Transport_OpenSSL_Sockets" @@ -59,7 +59,7 @@ /** * @brief Definition of the network context for the transport interface - * implemenation that uses OpenSSL and POSIX sockets. + * implementation that uses OpenSSL and POSIX sockets. * * @note For this transport implementation, the socket descriptor and * SSL context is used. diff --git a/platform/posix/transport/include/plaintext_posix.h b/platform/posix/transport/include/plaintext_posix.h index 9b9be748ef..ea3b17bcab 100644 --- a/platform/posix/transport/include/plaintext_posix.h +++ b/platform/posix/transport/include/plaintext_posix.h @@ -35,7 +35,7 @@ /* Include header that defines log levels. */ #include "logging_levels.h" -/* Logging configuration for the transport interface implemenation which uses +/* Logging configuration for the transport interface implementation which uses * Sockets. */ #ifndef LIBRARY_LOG_NAME #define LIBRARY_LOG_NAME "Transport_Plaintext_Sockets" diff --git a/platform/posix/transport/src/transport_reconnect_posix.c b/platform/posix/transport/src/transport_reconnect_posix.c index f41dcee36b..3066c0a3cb 100644 --- a/platform/posix/transport/src/transport_reconnect_posix.c +++ b/platform/posix/transport/src/transport_reconnect_posix.c @@ -90,7 +90,7 @@ void Transport_ReconnectParamsReset( TransportReconnectParams_t * pReconnectPara /* Get current time to seed pseudo random number generator. */ ( void ) clock_gettime( CLOCK_REALTIME, &tp ); - /* Seed pseudo ramdom number generator with nano seconds. */ + /* Seed pseudo random number generator with nano seconds. */ srand( tp.tv_nsec ); /* Calculate jitter value using picking a random number. */ diff --git a/tools/spell/README.md b/tools/spell/README.md new file mode 100644 index 0000000000..cbc77fff10 --- /dev/null +++ b/tools/spell/README.md @@ -0,0 +1,20 @@ +# How to create a lexicon.txt for a new library. + +1. In your GNU environment install programs: *spell* and *getopt* + For Linux here are the commands to install these programs: + ```shell + apt-get install spell + apt-get install getopt + ``` + +1. Add **tools/spell/ablexicon**, **tools/spell/extract-comments**, and **tools/spell/find-unknown-comment-words** to your system's PATH. + ```shell + export PATH=/tools/spell/ablexicon:tools/spell/extract-comments:/tools/spell/find-unknown-comment-words:$PATH + ``` + +1. Ensure there does not exist a file called "lexicon.txt" in your library's directory. Run the following command to create a lexicon.txt for your library: + ```shell + find-unknown-comment-words -d /libraries// > /libraries///lexicon.txt + ``` + +1. Check the contents of */libraries///lexicon.txt* for any misspelled words. Fix them in your library's source code and delete them from the lexicon.txt. \ No newline at end of file diff --git a/tools/spell/extract-comments b/tools/spell/extract-comments index 861e77e5d1..f52cb303de 100755 --- a/tools/spell/extract-comments +++ b/tools/spell/extract-comments @@ -36,6 +36,6 @@ do # NOTE: This has some limitations. For example, it prints # non-comment text at the beginning of a comment line. # - nl -ba $1 | awk '/\/\// {print $0}; /\/\*/ {comment=1; if(comment) print $0}; /\*\// {comment=0}' + nl -ba $1 | awk '/\/\// {print $0}; /\/\*/ {comment=1}; {if(comment) print $0}; /\*\// {comment=0}' shift done diff --git a/tools/spell/find-unknown-comment-words b/tools/spell/find-unknown-comment-words index 028df0cc83..c86c215486 100755 --- a/tools/spell/find-unknown-comment-words +++ b/tools/spell/find-unknown-comment-words @@ -105,6 +105,10 @@ do for file in `find $DIRNAME -name \*.[ch]` do + if [[ $file == *"third_party"* ]] + then + continue + fi # Disable errexit here, extract-comments can return non-zero set +e # From 9b52c4bdb4deec6605550e6a7796801e5f029699 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 14 Jul 2020 15:03:11 -0700 Subject: [PATCH 584/844] Conditionally build mutual auth targets if AWS IoT Credentials are not defined (#1051) * Add fix for http_parser autoclone * Selectively add targets based on macros defined * Update check_symbol_exists to work with demo config header * Fix by unsetting symbol exists flag in the INTERNAL CACHE * Add missing rename of file * Remove http_demo_mutual_tls.c * Update README and allow credentials to be passed as CMake flags --- README.md | 101 ++++++++++++++---- demos/CMakeLists.txt | 9 +- .../http/http_demo_mutual_auth/CMakeLists.txt | 21 ++++ ...o_mutual_tls.c => http_demo_mutual_auth.c} | 0 .../mqtt/mqtt_demo_mutual_auth/CMakeLists.txt | 21 ++++ .../standard/http/third_party/CMakeLists.txt | 2 +- 6 files changed, 132 insertions(+), 22 deletions(-) rename demos/http/http_demo_mutual_auth/{http_demo_mutual_tls.c => http_demo_mutual_auth.c} (100%) diff --git a/README.md b/README.md index eabd8ffe9b..ff30d3f42a 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,122 @@ + # AWS IoT Device SDK C v4.0.2 + + ## Development branch -This branch currently hosts development of AWS IoT Embedded C SDK version 4 beta 2. It is currently a work in progress and should not be used to create any products at the moment. We will update this README when that status changes. + +This branch currently hosts development of AWS IoT Embedded C SDK version 4 beta 2. It is currently a work in progress and should not be used to create any products at the moment. We will update this README when that status changes. + + ## Building and Running Demos + + This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. + + ### Prerequisites + - CMake 3.13.0 or later and a C90 compiler. + - A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. - - On Linux systems, installation of OpenSSL development libraries and header files, *version 1.1.0 or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + +- On Linux systems, installation of OpenSSL development libraries and header files, *version 1.1.0 or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + + ### AWS IoT Account Setup -It is required to setup an AWS account and access the AWS IoT Console for running demos and tests. Follow the links to: -- [Setup an AWS account](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html). -- [Sign-in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) after setting up the AWS account. + +It is required to setup an AWS account and access the AWS IoT Console for running demos and tests. Follow the links to: + +- [Setup an AWS account](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html). + +- [Sign-in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) after setting up the AWS account. + + *Note: If using the Provisioning library, a fleet provisioning template, a provisioning claim, IoT policies and IAM policies need to be setup for the AWS account. Complete the steps to setup your device and AWS IoT account outlined [here](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#use-claim).* + + ### Configuring the mutual auth demos + - You can pass the following configuration settings as command line options in order to run the mutual auth demos: `cmake .. -DAWS_IOT_ENDPOINT="aws-iot-endpoint" -DCLIENT_CERT_PATH="certificate-path" -DCLIENT_PRIVATE_KEY_PATH="private-key-path"` + - In order to set these settings manually, edit `demo_config.h` in `demos/mqtt/mqtt_demo_mutual_auth/` and `demos/http/http_demo_mutual_auth/` to `#define` the following: - - Set `AWS_IOT_ENDPOINT` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. - - Set `ROOT_CA_CERT_PATH` to the path of the root CA certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). - - Set `CLIENT_CERT_PATH` to the path of the client certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). - - Set `CLIENT_PRIVATE_KEY_PATH` to the path of the private key downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + + - Set `AWS_IOT_ENDPOINT` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. + + - Set `ROOT_CA_CERT_PATH` to the path of the root CA certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + + - Set `CLIENT_CERT_PATH` to the path of the client certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + + - Set `CLIENT_PRIVATE_KEY_PATH` to the path of the private key downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + + ### Build Steps + 1. Go to the root directory of this repository. + 1. Create build directory: `mkdir build && cd build` + 1. Run *cmake* while inside build directory: `cmake ..` + 1. Run this command to build the demos: `make` + 1. Go to the `build/bin` directory and run any demo executables from there. + + ### Optional: Installing Mosquitto to run MQTT demos locally -1. [Download and install Mosquitto](https://mosquitto.org/download/) -1. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set `localhost`. -1. [Follow these instructions](https://dzone.com/articles/secure-communication-with-tls-and-the-mosquitto-broker) to setup TLS authentication for your local Mosquitto server. + +1. [Download and install Mosquitto](https://mosquitto.org/download/) + +1. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set to `localhost`. + +1. [Follow these instructions](https://dzone.com/articles/secure-communication-with-tls-and-the-mosquitto-broker) to setup TLS authentication for your local Mosquitto server. + 1. Set `ROOT_CA_CERT_PATH` to the server certificate used when setting up TLS authentication for your local Mosquitto server. -### Optional: Installing httpbin to run HTTP demos locally -1. Install `Docker`: + + +### Optional: Installing httpbin to run HTTP demos locally + +1. Install Docker: + ```shell + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh + ``` -1. Run `httpbin` through port 80: + +2. Run httpbin through port 80: + ```shell + docker pull kennethreitz/httpbin + docker run -p 80:80 kennethreitz/httpbin + ``` -1. `SERVER_HOST` defined in `demos/http/http_demo_plaintext/demo_config.h` can now be set to `localhost`. -1. To run `http_demo_basic_tls`, [download ngrok](https://ngrok.com/download) in order to create an HTTPS tunnel to the `httpbin` server currently hosted on port 80: + +3. `SERVER_HOST` defined in `demos/http/http_demo_plaintext/demo_config.h` can now be set to `localhost`. + +4. To run `http_demo_basic_tls`, [download ngrok](https://ngrok.com/download) in order to create an HTTPS tunnel to the httpbin server currently hosted on port 80: + ```shell + ./ngrok http 80 # May have to use ./ngrok.exe depending on OS or filename of the executable + ``` -1. `ngrok` will provide an https link that can be substituted in `demos/http/http_demo_basic_tls/demo_config.h` and has a format of `https://ABCDEFG12345.ngrok.io`. -1. Set `SERVER_HOST` in `demos/http/http_demo_basic_tls/demo_config.h` to the https link provided by `ngrok`. -1. You must also download the Root CA certificate provided by `ngrok` and set `ROOT_CA_CERT_PATH` in `demo_config.h` to the file path of the downloaded certificate. + +5. `ngrok` will provide an https link that can be substituted in `demos/http/http_demo_basic_tls/demo_config.h` and has a format of `https://ABCDEFG12345.ngrok.io`. + +6. Set `SERVER_HOST` in `demos/http/http_demo_basic_tls/demo_config.h` to the https link provided by ngrok. + +7. You must also download the Root CA certificate provided by ngrok and set `ROOT_CA_CERT_PATH` in `demo_config.h` to the file path of the downloaded certificate. diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index e49f56cde6..041f108349 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -8,10 +8,17 @@ if( NOT DEFINED PLATFORM_NAME ) endif() # Include each subdirectory that has a CMakeLists.txt file in it -file(GLOB demo_dirs "${DEMOS_DIR}/mqtt/*") +file(GLOB demo_dirs "${DEMOS_DIR}/*/*") foreach(demo_dir IN LISTS demo_dirs) if(IS_DIRECTORY "${demo_dir}" AND EXISTS "${demo_dir}/CMakeLists.txt") get_filename_component( DEMO_EXE_NAME ${demo_dir} NAME ) add_subdirectory(${demo_dir}) endif() endforeach() + +# Replace macro definition if defined +if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) + set_target_properties(http_demo_mutual_auth mqtt_demo_mutual_auth + PROPERTIES COMPILE_DEFINITIONS + "AWS_IOT_ENDPOINT;CLIENT_CERT_PATH;CLIENT_PRIVATE_KEY_PATH") +endif() diff --git a/demos/http/http_demo_mutual_auth/CMakeLists.txt b/demos/http/http_demo_mutual_auth/CMakeLists.txt index 669bfc6970..047b6ceb23 100644 --- a/demos/http/http_demo_mutual_auth/CMakeLists.txt +++ b/demos/http/http_demo_mutual_auth/CMakeLists.txt @@ -1,4 +1,25 @@ +include(CheckSymbolExists) + set( DEMO_NAME "http_demo_mutual_auth" ) + +# Check if all required macros needed to run this demo are defined +list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_LIST_DIR};${LOGGING_INCLUDE_DIRS}") +if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) + set(CMAKE_REQUIRED_DEFINITIONS -DAWS_IOT_ENDPOINT -DCLIENT_CERT_PATH -DCLIENT_PRIVATE_KEY_PATH) +endif() +unset(HAVE_AWS_ENDPOINT CACHE) +unset(HAVE_CLIENT_CERT CACHE) +unset(HAVE_PRIVATE_KEY CACHE) +set(FILES_TO_CHECK "demo_config.h") +check_symbol_exists(AWS_IOT_ENDPOINT ${FILES_TO_CHECK} HAVE_AWS_ENDPOINT) +check_symbol_exists(CLIENT_CERT_PATH ${FILES_TO_CHECK} HAVE_CLIENT_CERT) +check_symbol_exists(CLIENT_PRIVATE_KEY_PATH ${FILES_TO_CHECK} HAVE_PRIVATE_KEY) +if(NOT(HAVE_AWS_ENDPOINT AND HAVE_CLIENT_CERT AND HAVE_PRIVATE_KEY)) + message("To run ${DEMO_NAME}, define AWS_IOT_ENDPOINT, CLIENT_CERT_PATH, CLIENT_PRIVATE_KEY_PATH in demos/${DEMO_NAME}/demo_config.h.") + message("After doing this, rebuild using cmake ..") + return() +endif() + # Demo target. add_executable(${DEMO_NAME}) diff --git a/demos/http/http_demo_mutual_auth/http_demo_mutual_tls.c b/demos/http/http_demo_mutual_auth/http_demo_mutual_auth.c similarity index 100% rename from demos/http/http_demo_mutual_auth/http_demo_mutual_tls.c rename to demos/http/http_demo_mutual_auth/http_demo_mutual_auth.c diff --git a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt index 85570cfc4b..6311996816 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt @@ -1,4 +1,25 @@ +include(CheckSymbolExists) + set( DEMO_NAME "mqtt_demo_mutual_auth" ) + +# Check if all required macros needed to run this demo are defined +list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_LIST_DIR};${LOGGING_INCLUDE_DIRS}") +if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) + set(CMAKE_REQUIRED_DEFINITIONS -DAWS_IOT_ENDPOINT -DCLIENT_CERT_PATH -DCLIENT_PRIVATE_KEY_PATH) +endif() +unset(HAVE_AWS_ENDPOINT CACHE) +unset(HAVE_CLIENT_CERT CACHE) +unset(HAVE_PRIVATE_KEY CACHE) +set(FILES_TO_CHECK "demo_config.h") +check_symbol_exists(AWS_IOT_ENDPOINT ${FILES_TO_CHECK} HAVE_AWS_ENDPOINT) +check_symbol_exists(CLIENT_CERT_PATH ${FILES_TO_CHECK} HAVE_CLIENT_CERT) +check_symbol_exists(CLIENT_PRIVATE_KEY_PATH ${FILES_TO_CHECK} HAVE_PRIVATE_KEY) +if(NOT(HAVE_AWS_ENDPOINT AND HAVE_CLIENT_CERT AND HAVE_PRIVATE_KEY)) + message("To run ${DEMO_NAME}, define AWS_IOT_ENDPOINT, CLIENT_CERT_PATH, CLIENT_PRIVATE_KEY_PATH in demos/${DEMO_NAME}/demo_config.h") + message("After doing this, rebuild using cmake ..") + return() +endif() + # Demo target. add_executable(${DEMO_NAME}) diff --git a/libraries/standard/http/third_party/CMakeLists.txt b/libraries/standard/http/third_party/CMakeLists.txt index 36150d35ec..852f19e73b 100644 --- a/libraries/standard/http/third_party/CMakeLists.txt +++ b/libraries/standard/http/third_party/CMakeLists.txt @@ -1,5 +1,5 @@ # Check if the http_parser source directory exists. -if( NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/http_parser ) +if( NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/http_parser/http_parser.c ) # Attempt to clone http_parser. if( ${BUILD_CLONE_SUBMODULES} ) find_package( Git REQUIRED ) From 41a8d8c979899251ec04d1680c1278fa2f45d812 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 14 Jul 2020 17:56:14 -0700 Subject: [PATCH 585/844] Initialize variables and add macro for magic num (#1056) --- libraries/standard/mqtt/src/mqtt.c | 25 +++++++++++-------- .../standard/mqtt/src/mqtt_lightweight.c | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index ea5247b6e6..c87446bcf8 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -41,6 +41,11 @@ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U ) #endif +/** + * @brief A return code indicating an error from the transport interface. + */ +#define TRANSPORT_ERROR ( -1 ) + /*-----------------------------------------------------------*/ /** @@ -340,8 +345,8 @@ static int32_t sendPacket( MQTTContext_t * pContext, } else { - LogError( ( "Transport send failed." ) ); - totalBytesSent = -1; + LogError( ( "Transport send failed. Error code=%d.", bytesSent ) ); + totalBytesSent = TRANSPORT_ERROR; break; } } @@ -861,9 +866,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, bool manageKeepAlive ) { MQTTStatus_t status = MQTTBadResponse; - uint16_t packetIdentifier; - /* Need a dummy variable for MQTT_DeserializeAck(). */ - bool sessionPresent = false; + uint16_t packetIdentifier = MQTT_PACKET_ID_INVALID; /* We should always invoke the app callback unless we receive a PINGRESP * and are managing keep alive, or if we receive an unknown packet. We @@ -885,13 +888,13 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_PUBREL: case MQTT_PACKET_TYPE_PUBCOMP: - /* Handle all the publish acks. */ + /* Handle all the publish acks. The app callback is invoked here. */ status = handlePublishAcks( pContext, pIncomingPacket ); break; case MQTT_PACKET_TYPE_PINGRESP: - status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); invokeAppCallback = ( manageKeepAlive == true ) ? false : true; if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) ) @@ -904,7 +907,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_SUBACK: case MQTT_PACKET_TYPE_UNSUBACK: /* Deserialize and give these to the app provided callback. */ - status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, &sessionPresent ); + status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); invokeAppCallback = true; break; @@ -1577,7 +1580,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ) { int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; - size_t packetSize; + size_t packetSize = 0U; if( pContext == NULL ) { @@ -1695,8 +1698,8 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ) { - size_t packetSize; - int32_t bytesSent; + size_t packetSize = 0U; + int32_t bytesSent = 0; MQTTStatus_t status = MQTTSuccess; /* Validate arguments. */ diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index e0b2c1a0fa..f8386de890 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1086,7 +1086,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket MQTTPublishInfo_t * pPublishInfo ) { MQTTStatus_t status = MQTTSuccess; - const uint8_t * pVariableHeader, * pPacketIdentifierHigh; + const uint8_t * pVariableHeader, * pPacketIdentifierHigh = NULL; assert( pIncomingPacket != NULL ); assert( pPacketId != NULL ); From 970b42956a5ad5bdd3aa020f5e9a48ed589d31ec Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Wed, 15 Jul 2020 14:42:47 -0700 Subject: [PATCH 586/844] MQTT CBMC Proofs (#1052) CBMC proofs for: MQTT_SerializeAck() MQTT_SerializeDisconnect() MQTT_SerializePingreq() MQTT_SerializePublish() MQTT_SerializePublishHeader MQTT_SerializeSubscribe() MQTT_SerializeUnsubscribe() --- .../HTTPClient_AddHeader_harness.c | 8 +- .../cbmc/proofs/HTTPClient_AddHeader/Makefile | 2 +- .../proofs/HTTPClient_AddHeader/README.md | 4 +- .../HTTPClient_AddRangeHeader_harness.c | 4 +- .../proofs/HTTPClient_AddRangeHeader/Makefile | 2 +- .../HTTPClient_AddRangeHeader/README.md | 4 +- ...PClient_InitializeRequestHeaders_harness.c | 8 +- .../Makefile | 2 +- .../README.md | 4 +- .../HTTPClient_ReadHeader_harness.c | 8 +- .../proofs/HTTPClient_ReadHeader/Makefile | 2 +- .../proofs/HTTPClient_ReadHeader/README.md | 4 +- .../cbmc/proofs/HTTPClient_strerror/README.md | 4 +- .../mqtt/cbmc/include/mqtt_cbmc_state.h | 41 +++++++++- .../MQTT_DeserializeAck_harness.c | 4 +- .../cbmc/proofs/MQTT_DeserializeAck/Makefile | 2 +- .../cbmc/proofs/MQTT_DeserializeAck/README.md | 4 +- .../MQTT_DeserializePublish_harness.c | 8 +- .../proofs/MQTT_DeserializePublish/Makefile | 2 +- .../proofs/MQTT_DeserializePublish/README.md | 4 +- ...T_GetIncomingPacketTypeAndLength_harness.c | 4 +- .../Makefile | 4 +- .../README.md | 4 +- .../cbmc/proofs/MQTT_GetPacketId/Makefile | 2 +- .../cbmc/proofs/MQTT_GetPacketId/README.md | 4 +- .../cbmc/proofs/MQTT_Init/MQTT_Init_harness.c | 8 +- .../mqtt/cbmc/proofs/MQTT_Init/Makefile | 2 +- .../mqtt/cbmc/proofs/MQTT_Init/README.md | 4 +- .../MQTT_SerializeAck_harness.c | 39 ++++++++++ .../cbmc/proofs/MQTT_SerializeAck/Makefile | 17 ++++ .../cbmc/proofs/MQTT_SerializeAck/README.md | 10 +++ .../proofs/MQTT_SerializeAck/cbmc-batch.yaml | 2 + .../proofs/MQTT_SerializeAck/cbmc-viewer.json | 7 ++ .../MQTT_SerializeConnect_harness.c | 36 +++++---- .../proofs/MQTT_SerializeConnect/Makefile | 2 +- .../proofs/MQTT_SerializeConnect/README.md | 4 +- .../MQTT_SerializeDisconnect_harness.c | 37 +++++++++ .../proofs/MQTT_SerializeDisconnect/Makefile | 17 ++++ .../proofs/MQTT_SerializeDisconnect/README.md | 10 +++ .../MQTT_SerializeDisconnect/cbmc-batch.yaml | 2 + .../MQTT_SerializeDisconnect/cbmc-viewer.json | 7 ++ .../MQTT_SerializePingreq_harness.c | 37 +++++++++ .../proofs/MQTT_SerializePingreq/Makefile | 17 ++++ .../proofs/MQTT_SerializePingreq/README.md | 10 +++ .../MQTT_SerializePingreq/cbmc-batch.yaml | 2 + .../MQTT_SerializePingreq/cbmc-viewer.json | 7 ++ .../MQTT_SerializePublish_harness.c | 67 ++++++++++++++++ .../proofs/MQTT_SerializePublish/Makefile | 17 ++++ .../proofs/MQTT_SerializePublish/README.md | 10 +++ .../MQTT_SerializePublish/cbmc-batch.yaml | 2 + .../MQTT_SerializePublish/cbmc-viewer.json | 7 ++ .../MQTT_SerializePublishHeader_harness.c | 75 ++++++++++++++++++ .../MQTT_SerializePublishHeader/Makefile | 17 ++++ .../MQTT_SerializePublishHeader/README.md | 10 +++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../MQTT_SerializeSubscribe_harness.c | 74 ++++++++++++++++++ .../proofs/MQTT_SerializeSubscribe/Makefile | 25 ++++++ .../proofs/MQTT_SerializeSubscribe/README.md | 10 +++ .../MQTT_SerializeSubscribe/cbmc-batch.yaml | 2 + .../MQTT_SerializeSubscribe/cbmc-viewer.json | 7 ++ .../MQTT_SerializeUnsubscribe_harness.c | 77 +++++++++++++++++++ .../proofs/MQTT_SerializeUnsubscribe/Makefile | 25 ++++++ .../MQTT_SerializeUnsubscribe/README.md | 10 +++ .../MQTT_SerializeUnsubscribe/cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../mqtt/cbmc/sources/mqtt_cbmc_state.c | 66 +++++++++++++--- 67 files changed, 850 insertions(+), 85 deletions(-) create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/MQTT_SerializeAck_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/MQTT_SerializeDisconnect_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/MQTT_SerializePingreq_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-viewer.json diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c index 2edc0e9ecb..aa8a0459ab 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/HTTPClient_AddHeader_harness.c @@ -30,14 +30,14 @@ void harness() { - HTTPRequestHeaders_t * pRequestHeaders = NULL; - char * pField = NULL; - char * pValue = NULL; + HTTPRequestHeaders_t * pRequestHeaders; + char * pField; + char * pValue; size_t fieldLen; size_t valueLen; /* Initialize and make assumptions for request headers. */ - pRequestHeaders = allocateHttpRequestHeaders( pRequestHeaders ); + pRequestHeaders = allocateHttpRequestHeaders( NULL ); __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); /* Initialize and make assumptions for header field. */ diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile index 078a462da0..1b07958ca2 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile @@ -11,7 +11,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md index 97bdc073b6..8bff8204c4 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for HTTPClient_AddHeader. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c index 4fdc7c1c68..641a72f578 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/HTTPClient_AddRangeHeader_harness.c @@ -30,12 +30,12 @@ void harness() { - HTTPRequestHeaders_t * pRequestHeaders = NULL; + HTTPRequestHeaders_t * pRequestHeaders; int32_t rangeStartOrlastNbytes; int32_t rangeEnd; /* Initialize and make assumptions for request headers. */ - pRequestHeaders = allocateHttpRequestHeaders( pRequestHeaders ); + pRequestHeaders = allocateHttpRequestHeaders( NULL ); __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); HTTPClient_AddRangeHeader( pRequestHeaders, rangeStartOrlastNbytes, rangeEnd ); diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile index 006991c17e..da4f80c38a 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile @@ -12,7 +12,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += strncmp.0:5 convertInt32ToAscii.0:11 convertInt32ToAscii.1:11 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md index f1d5eee498..ad71b3a1a1 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for HTTPClient_AddRangeHeader. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c index 7999598787..7eef9f233c 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/HTTPClient_InitializeRequestHeaders_harness.c @@ -30,15 +30,15 @@ void harness() { - HTTPRequestHeaders_t * pRequestHeaders = NULL; - HTTPRequestInfo_t * pRequestInfo = NULL; + HTTPRequestHeaders_t * pRequestHeaders; + HTTPRequestInfo_t * pRequestInfo; /* Initialize and make assumptions for request headers object. */ - pRequestHeaders = allocateHttpRequestHeaders( pRequestHeaders ); + pRequestHeaders = allocateHttpRequestHeaders( NULL ); __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); /* Initialize and make assumptions for request info object. */ - pRequestInfo = allocateHttpRequestInfo( pRequestInfo ); + pRequestInfo = allocateHttpRequestInfo( NULL ); __CPROVER_assume( isValidHttpRequestInfo( pRequestInfo ) ); HTTPClient_InitializeRequestHeaders( pRequestHeaders, pRequestInfo ); diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile index 18f33c75ac..e2f55becf8 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile @@ -11,7 +11,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md index 621bcad4b4..bdca0c5aac 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for HTTPClient_InitializeRequestHe To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c index b7d6e6c32b..a5a58717ba 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c @@ -30,9 +30,9 @@ void harness() { - HTTPResponse_t * pResponse = NULL; - char * pField = NULL; - char * pValue = NULL; + HTTPResponse_t * pResponse; + char * pField; + char * pValue; size_t fieldLen; size_t valueLen; @@ -45,7 +45,7 @@ void harness() pValue = mallocCanFail( valueLen ); /* Initialize and make assumptions for response object. */ - pResponse = allocateHttpResponse( pResponse ); + pResponse = allocateHttpResponse( NULL ); __CPROVER_assume( isValidHttpResponse( pResponse ) ); HTTPClient_ReadHeader( pResponse, diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile index 6cddbd3e49..d8df39e3e2 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile @@ -11,7 +11,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md index e2b1dc1a14..cbf595ff6a 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for HTTPClient_ReadHeader. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md index 2ec9645fdf..7a57565516 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for HTTPClient_strerror. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h index ed11579cf9..2f9d44f6fc 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h @@ -50,6 +50,9 @@ MQTTPacketInfo_t * allocateMqttPacketInfo( MQTTPacketInfo_t * pPacketInfo ); * @param[in] pPacketInfo #MQTTPacketInfo_t object to validate. * * @return True if the #MQTTPacketInfo_t object is valid, false otherwise. + * + * @note A NULL object is a valid object. This is for coverage of the NULL + * parameter checks in the function under proof. */ bool isValidMqttPacketInfo( const MQTTPacketInfo_t * pPacketInfo ); @@ -68,6 +71,9 @@ MQTTPublishInfo_t * allocateMqttPublishInfo( MQTTPublishInfo_t * pPublishInfo ); * @param[in] pPublishInfo #MQTTPublishInfo_t object to validate. * * @return True if the #MQTTPublishInfo_t object is valid, false otherwise. + * + * @note A NULL object is a valid object. This is for coverage of the NULL + * parameter checks in the function under proof. */ bool isValidMqttPublishInfo( const MQTTPublishInfo_t * pPublishInfo ); @@ -86,6 +92,9 @@ MQTTConnectInfo_t * allocateMqttConnectInfo( MQTTConnectInfo_t * pConnectInfo ); * @param[in] pConnectInfo #MQTTConnectInfo_t object to validate. * * @return True if the #MQTTConnectInfo_t object is valid, false otherwise. + * + * @note A NULL object is a valid object. This is for coverage of the NULL + * parameter checks in the function under proof. */ bool isValidMqttConnectInfo( const MQTTConnectInfo_t * pConnectInfo ); @@ -96,7 +105,7 @@ bool isValidMqttConnectInfo( const MQTTConnectInfo_t * pConnectInfo ); * * @return NULL or allocated #MQTTFixedBuffer_t memory. */ -MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pBuffer ); +MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Validate a #MQTTFixedBuffer_t object. @@ -104,7 +113,35 @@ MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pBuffer ); * @param[in] pBuffer #MQTTFixedBuffer_t object to validate. * * @return True if the #MQTTFixedBuffer_t object is valid, false otherwise. + * + * @note A NULL object is a valid object. This is for coverage of the NULL + * parameter checks in the function under proof. + */ +bool isValidMqttFixedBuffer( const MQTTFixedBuffer_t * pFixedBuffer ); + +/** + * @brief Allocate an array of #MQTTSubscribeInfo_t objects. + * + * @param[in] pSubscriptionList #MQTTSubscribeInfo_t object information. + * @param[in] subscriptionCount The amount of #MQTTSubscribeInfo_t objects to allocate. + * + * @return NULL or allocated #MQTTSubscribeInfo_t array. + */ +MQTTSubscribeInfo_t * allocateMqttSubscriptionList( MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount ); + +/** + * @brief Validate an array of #MQTTSubscribeInfo_t objects. + * + * @param[in] pSubscriptionList #MQTTSubscribeInfo_t object information. + * @param[in] subscriptionCount The length of #MQTTSubscribeInfo_t objects in the pSubscriptionList. + * + * @return True if the #MQTTSubscribeInfo_t is valid. + * + * @note A NULL object is a valid object. This is for coverage of the NULL + * parameter checks in the function under proof. */ -bool isValidMqttFixedBuffer( const MQTTFixedBuffer_t * pBuffer ); +bool isValidMqttSubscriptionList( MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount ); #endif /* ifndef MQTT_CBMC_STATE_H_ */ diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c index e13fdb3cba..e904268690 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c @@ -28,11 +28,11 @@ void harness() { - MQTTPacketInfo_t * pIncomingPacket = NULL; + MQTTPacketInfo_t * pIncomingPacket; uint16_t * pPacketId; bool * pSessionPresent; - pIncomingPacket = allocateMqttPacketInfo( pIncomingPacket ); + pIncomingPacket = allocateMqttPacketInfo( NULL ); __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); MQTT_DeserializeAck( pIncomingPacket, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile index 0a2dda633d..95bca32b98 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile @@ -11,7 +11,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md index 9bf14d42cd..0446710de6 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for MQTT_DeserializeAck. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c index 341ce34864..bee97511de 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c @@ -29,14 +29,14 @@ void harness() { - MQTTPacketInfo_t * pIncomingPacket = NULL; - MQTTPublishInfo_t * pPublishInfo = NULL; + MQTTPacketInfo_t * pIncomingPacket; + MQTTPublishInfo_t * pPublishInfo; uint16_t packetId; - pIncomingPacket = allocateMqttPacketInfo( pIncomingPacket ); + pIncomingPacket = allocateMqttPacketInfo( NULL ); __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); - pPublishInfo = allocateMqttPublishInfo( pPublishInfo ); + pPublishInfo = allocateMqttPublishInfo( NULL ); __CPROVER_assume( isValidMqttPublishInfo( pPublishInfo ) ); /* This function grabs the topic name, the topic name length, the diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile index f2494070d8..f6b921e4f1 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile @@ -11,7 +11,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md index 624d15ac3c..f89623093f 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for MQTT_DeserializePublish. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c index 75664ba4f4..138fba492a 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/MQTT_GetIncomingPacketTypeAndLength_harness.c @@ -36,9 +36,9 @@ void harness() /* MQTT_GetIncomingPacketTypeAndLength() will set only the remainingLength * field in the input MQTTPacketInfo_t structure. */ - MQTTPacketInfo_t * pIncomingPacket = NULL; + MQTTPacketInfo_t * pIncomingPacket; - pIncomingPacket = allocateMqttPacketInfo( pIncomingPacket ); + pIncomingPacket = allocateMqttPacketInfo( NULL ); __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); MQTT_GetIncomingPacketTypeAndLength( NetworkInterfaceReceiveStub, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile index 0d1e176b22..b0b25adecd 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile @@ -7,8 +7,8 @@ REMOVE_FUNCTION_BODY += UNWINDSET += getRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c -PROOF_SOURCES += $(PROOFDIR)/../../stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md index 4211034166..209750fbd6 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for MQTT_GetIncomingPacketTypeAndL To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile index ad741b3dc0..c2f2b1ec5b 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile @@ -8,7 +8,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md index 036a70518a..4fb4964343 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for MQTT_GetPacketId. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c index 94bad14628..dd7faccdb8 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c @@ -29,10 +29,10 @@ void harness() { - MQTTContext_t * pContext = NULL; - TransportInterface_t * pTransportInterface = NULL; - MQTTApplicationCallbacks_t * pCallbacks = NULL; - MQTTFixedBuffer_t * pNetworkBuffer = NULL; + MQTTContext_t * pContext; + TransportInterface_t * pTransportInterface; + MQTTApplicationCallbacks_t * pCallbacks; + MQTTFixedBuffer_t * pNetworkBuffer; pContext = mallocCanFail( sizeof( MQTTContext_t ) ); pTransportInterface = mallocCanFail( sizeof( MQTTContext_t ) ); diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile index 60fe4da049..b987e303fb 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile @@ -8,7 +8,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md index 754f91b513..f0f4cf314c 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for MQTT_Init. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/MQTT_SerializeAck_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/MQTT_SerializeAck_harness.c new file mode 100644 index 0000000000..54bf9e979d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/MQTT_SerializeAck_harness.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializeAck_harness.c + * @brief Implements the proof harness for MQTT_SerializeAck function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTFixedBuffer_t * pFixedBuffer; + uint8_t packetType; + uint16_t packetId; + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_ASSUME( isValidMqttFixedBuffer( pFixedBuffer ) ); + + MQTT_SerializeAck( pFixedBuffer, packetType, packetId ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile new file mode 100644 index 0000000000..09385c39d9 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializeAck_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/README.md new file mode 100644 index 0000000000..028c8f2ad5 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/README.md @@ -0,0 +1,10 @@ +MQTT_SerializeAck proof +============== + +This directory contains a memory safety proof for MQTT_SerializeAck. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-viewer.json new file mode 100644 index 0000000000..a69c3ae7c8 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializeAck", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c index a34b3ea36b..568382314e 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c @@ -28,35 +28,43 @@ void harness() { - MQTTConnectInfo_t * pConnectInfo = NULL; - MQTTPublishInfo_t * pWillInfo = NULL; - size_t remainingLength = 0; - MQTTFixedBuffer_t * pBuffer = NULL; - size_t packetSize = 0; + MQTTConnectInfo_t * pConnectInfo; + MQTTPublishInfo_t * pWillInfo; + size_t remainingLength; + MQTTFixedBuffer_t * pFixedBuffer; + size_t packetSize; MQTTStatus_t status = MQTTSuccess; - pConnectInfo = allocateMqttConnectInfo( pConnectInfo ); + pConnectInfo = allocateMqttConnectInfo( NULL ); __CPROVER_assume( isValidMqttConnectInfo( pConnectInfo ) ); - pWillInfo = allocateMqttPublishInfo( pWillInfo ); + pWillInfo = allocateMqttPublishInfo( NULL ); __CPROVER_assume( isValidMqttPublishInfo( pWillInfo ) ); - pBuffer = allocateMqttFixedBuffer( pBuffer ); - __CPROVER_assume( isValidMqttFixedBuffer( pBuffer ) ); + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_assume( isValidMqttFixedBuffer( pFixedBuffer ) ); /* Before calling MQTT_SerializeConnect() it is up to the application to make - * sure that the information in MQTTConnectInfo_t and MQTTPublishInfo_t can - * fit into the MQTTFixedBuffer_t. It is a violation of the API to call - * MQTT_SerializeConnect without first calling MQTT_GetConnectPacketSize(). */ + * sure that the information in MQTTConnectInfo_t and MQTTPublishInfo_t can + * fit into the MQTTFixedBuffer_t. It is a violation of the API to call + * MQTT_SerializeConnect() without first calling MQTT_GetConnectPacketSize(). */ if( pConnectInfo != NULL ) { - status = MQTT_GetConnectPacketSize( pConnectInfo, pWillInfo, &remainingLength, &packetSize ); + /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of their + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * to recalculate the packetSize. */ + status = MQTT_GetConnectPacketSize( pConnectInfo, + pWillInfo, + &remainingLength, + &packetSize ); } if( status == MQTTSuccess ) { /* For coverage, it is expected that a NULL pConnectInfo will reach this * function. */ - MQTT_SerializeConnect( pConnectInfo, pWillInfo, remainingLength, pBuffer ); + MQTT_SerializeConnect( pConnectInfo, pWillInfo, remainingLength, pFixedBuffer ); } } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile index 931f40fa78..6ffa033935 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile @@ -11,7 +11,7 @@ REMOVE_FUNCTION_BODY += UNWINDSET += encodeRemainingLength.0:3 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md index 7536a0ea31..f2be34287a 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/README.md @@ -5,6 +5,6 @@ This directory contains a memory safety proof for MQTT_SerializeConnect. To run the proof. * Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer - to your path -* Run "make" + to your path. +* Run "make". * Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/MQTT_SerializeDisconnect_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/MQTT_SerializeDisconnect_harness.c new file mode 100644 index 0000000000..595b9b4716 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/MQTT_SerializeDisconnect_harness.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializeDisconnect_harness.c + * @brief Implements the proof harness for MQTT_SerializeDisconnect function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTFixedBuffer_t * pFixedBuffer; + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_ASSUME( isValidMqttFixedBuffer( pFixedBuffer ) ); + + MQTT_SerializeDisconnect( pFixedBuffer ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile new file mode 100644 index 0000000000..6e3e8dc6cf --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializeDisconnect_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/README.md new file mode 100644 index 0000000000..3de682147e --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/README.md @@ -0,0 +1,10 @@ +MQTT_SerializeDisconnect proof +============== + +This directory contains a memory safety proof for MQTT_SerializeDisconnect. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-viewer.json new file mode 100644 index 0000000000..858f3ae88b --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializeDisconnect", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/MQTT_SerializePingreq_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/MQTT_SerializePingreq_harness.c new file mode 100644 index 0000000000..38bc28853e --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/MQTT_SerializePingreq_harness.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializePingreq_harness.c + * @brief Implements the proof harness for MQTT_SerializePingreq function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTFixedBuffer_t * pFixedBuffer; + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_ASSUME( isValidMqttFixedBuffer( pFixedBuffer ) ); + + MQTT_SerializePingreq( pFixedBuffer ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile new file mode 100644 index 0000000000..7546038354 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializePingreq_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/README.md new file mode 100644 index 0000000000..5723a640bb --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/README.md @@ -0,0 +1,10 @@ +MQTT_SerializePingreq proof +============== + +This directory contains a memory safety proof for MQTT_SerializePingreq. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-viewer.json new file mode 100644 index 0000000000..4e487fbe4f --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializePingreq", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c new file mode 100644 index 0000000000..0c892a005c --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializePublish_harness.c + * @brief Implements the proof harness for MQTT_SerializePublish function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTPublishInfo_t * pPublishInfo; + uint16_t packetId; + size_t remainingLength; + size_t packetSize; + const MQTTFixedBuffer_t * pFixedBuffer; + MQTTStatus_t status = MQTTSuccess; + + pPublishInfo = allocateMqttPublishInfo( NULL ); + __CPROVER_assume( isValidMqttPublishInfo( pPublishInfo ) ); + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_assume( isValidMqttFixedBuffer( pFixedBuffer ) ); + + /* Before calling MQTT_SerializePublish() it is up to the application to + * make sure that the information in MQTTPublishInfo_t can fit into the + * MQTTFixedBuffer_t. It is a violation of the API to call + * MQTT_SerializePublish() without first calling MQTT_GetPublishPacketSize(). */ + if( pPublishInfo != NULL ) + { + /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of their + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * to recalculate the packetSize. */ + status = MQTT_GetPublishPacketSize( pPublishInfo, &remainingLength, &packetSize ); + } + + if( status == MQTTSuccess ) + { + /* For coverage it is expected that a NULL pPublishInfo could + * reach this function. */ + MQTT_SerializePublish( pPublishInfo, + packetId, + remainingLength, + pFixedBuffer ); + } +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile new file mode 100644 index 0000000000..f628a733e4 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializePublish_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += encodeRemainingLength.0:4 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/README.md new file mode 100644 index 0000000000..019e933140 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/README.md @@ -0,0 +1,10 @@ +MQTT_SerializePublish proof +============== + +This directory contains a memory safety proof for MQTT_SerializePublish. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-viewer.json new file mode 100644 index 0000000000..f6464b3232 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializePublish", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c new file mode 100644 index 0000000000..32f10912ac --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializePublishHeader_harness.c + * @brief Implements the proof harness for MQTT_SerializePublishHeader function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTPublishInfo_t * pPublishInfo; + uint16_t packetId; + size_t remainingLength; + size_t packetSize; + MQTTFixedBuffer_t * pFixedBuffer; + size_t * pHeaderSize; + MQTTStatus_t status = MQTTSuccess; + + pPublishInfo = allocateMqttPublishInfo( NULL ); + __CPROVER_assume( isValidMqttPublishInfo( pPublishInfo ) ); + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_assume( isValidMqttFixedBuffer( pFixedBuffer ) ); + + /* Allocate space for a returned header size to get coverage of a possibly + * NULL input. */ + pHeaderSize = mallocCanFail( sizeof( size_t ) ); + + /* Before calling MQTT_SerializePublishHeader() it is up to the application + * to verify that the information in MQTTPublishInfo_t can fit into the + * MQTTFixedBuffer_t. It is a violation of the API to call + * MQTT_SerializePublishHeader() without first calling MQTT_GetPublishPacketSize(). */ + if( pPublishInfo != NULL ) + { + /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of their + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * to recalculate the packetSize. */ + status = MQTT_GetPublishPacketSize( pPublishInfo, + &remainingLength, + &packetSize ); + } + + if( status == MQTTSuccess ) + { + /* For coverage it is expected that a NULL pPublishInfo could + * reach this function. */ + MQTT_SerializePublishHeader( pPublishInfo, + packetId, + remainingLength, + pFixedBuffer, + pHeaderSize ); + } +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile new file mode 100644 index 0000000000..1c666d2de6 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile @@ -0,0 +1,17 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializePublishHeader_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += encodeRemainingLength.0:4 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/README.md new file mode 100644 index 0000000000..543ecb9390 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/README.md @@ -0,0 +1,10 @@ +MQTT_SerializePublishHeader proof +============== + +This directory contains a memory safety proof for MQTT_SerializePublishHeader. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-viewer.json new file mode 100644 index 0000000000..1ddfd34edd --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializePublishHeader", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c new file mode 100644 index 0000000000..c9871bbe3b --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializeSubscribe_harness.c + * @brief Implements the proof harness for MQTT_SerializeSubscribe function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTSubscribeInfo_t * pSubscriptionList; + size_t subscriptionCount; + size_t remainingLength; + uint16_t packetId; + size_t packetSize; + MQTTFixedBuffer_t * pFixedBuffer; + MQTTStatus_t status = MQTTSuccess; + + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + + pSubscriptionList = allocateMqttSubscriptionList( NULL, subscriptionCount ); + __CPROVER_assume( isValidMqttSubscriptionList( pSubscriptionList, subscriptionCount ) ); + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_assume( isValidMqttFixedBuffer( pFixedBuffer ) ); + + /* Before calling MQTT_SerializeSubscribe() it is up to the application to + * make sure that the information in the list of MQTTSubscribeInfo_t can fit + * into the MQTTFixedBuffer_t. It is a violation of the API to call + * MQTT_SerializeSubscribe() without first calling MQTT_GetSubscribePacketSize(). */ + if( pSubscriptionList != NULL ) + { + /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of their + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * to recalculate the packetSize. */ + status = MQTT_GetSubscribePacketSize( pSubscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + } + + if( status == MQTTSuccess ) + { + /* For coverage it is expected that a NULL pSubscriptionList could + * reach this function. */ + MQTT_SerializeSubscribe( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + pFixedBuffer ); + } +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile new file mode 100644 index 0000000000..2ace429421 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile @@ -0,0 +1,25 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializeSubscribe_harness + +# Bind the subscription count. Please see the default value in mqtt_cbmc_state.c +# for more information on this bound. This is set to 2 currently to have the proof +# run quickly. +SUBSCRIPTION_COUNT_MAX=2 +DEFINES += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += allocateMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += isValidMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_calculateSubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += MQTT_SerializeSubscribe.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:4 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/README.md new file mode 100644 index 0000000000..5ea3360346 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/README.md @@ -0,0 +1,10 @@ +MQTT_SerializeSubscribe proof +============== + +This directory contains a memory safety proof for MQTT_SerializeSubscribe. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-viewer.json new file mode 100644 index 0000000000..5b0c51c469 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializeSubscribe", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c new file mode 100644 index 0000000000..63f7fecdd0 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_SerializeUnsubscribe_harness.c + * @brief Implements the proof harness for MQTT_SerializeUnsubscribe function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTSubscribeInfo_t * pSubscriptionList; + size_t subscriptionCount; + size_t remainingLength; + uint16_t packetId; + + /* This variable is not used but is needed for MQTT_GetUnsubscribePacketSize() + * to verify the pSubscriptionList. */ + size_t packetSize; + MQTTFixedBuffer_t * pFixedBuffer; + MQTTStatus_t status = MQTTSuccess; + + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + + pSubscriptionList = allocateMqttSubscriptionList( NULL, subscriptionCount ); + __CPROVER_assume( isValidMqttSubscriptionList( pSubscriptionList, subscriptionCount ) ); + + pFixedBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_assume( isValidMqttFixedBuffer( pFixedBuffer ) ); + + /* Before calling MQTT_SerializeUnsubscribe() it is up to the application to + * make sure that the information in the list of MQTTSubscribeInfo_t can fit + * into the MQTTFixedBuffer_t. It is a violation of the API to call + * MQTT_SerializeUnsubscribe() without first calling MQTT_GetUnsubscribePacketSize(). */ + if( pSubscriptionList != NULL ) + { + /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of their + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * to recalculate the packetSize. */ + status = MQTT_GetUnsubscribePacketSize( pSubscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + } + + if( status == MQTTSuccess ) + { + /* For coverage it is expected that a NULL pSubscriptionList could + * reach this function. */ + MQTT_SerializeUnsubscribe( pSubscriptionList, + subscriptionCount, + packetId, + remainingLength, + pFixedBuffer ); + } +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile new file mode 100644 index 0000000000..2017873c40 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile @@ -0,0 +1,25 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_SerializeUnsubscribe_harness + +# Bind the subscription count. Please see the default value in mqtt_cbmc_state.c +# for more information on this bound. This is set to 2 currently to have the proof +# run quickly. +SUBSCRIPTION_COUNT_MAX=2 +DEFINES += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += allocateMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += isValidMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_calculateSubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += MQTT_SerializeUnsubscribe.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:4 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/README.md new file mode 100644 index 0000000000..30b6af435b --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/README.md @@ -0,0 +1,10 @@ +MQTT_SerializeUnsubscribe proof +============== + +This directory contains a memory safety proof for MQTT_SerializeUnsubscribe. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-viewer.json new file mode 100644 index 0000000000..166638dc86 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_SerializeUnsubscribe", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c index 37e0f6c7b0..d794b9fafc 100644 --- a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +++ b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c @@ -22,6 +22,15 @@ #include #include "mqtt_cbmc_state.h" +/* A default bound on the subscription count. Iterating over possibly SIZE_MAX + * number of subscriptions does not add any value to the proofs. An application + * can allocate memory for as many subscriptions as their system can handle. + * The proofs verify that the code can handle the maximum topicFilterLength in + * each subscription. */ +#ifndef SUBSCRIPTION_COUNT_MAX + #define SUBSCRIPTION_COUNT_MAX 1U +#endif + void * mallocCanFail( size_t size ) { __CPROVER_assert( size < CBMC_MAX_OBJECT_SIZE, "mallocCanFail size is too big" ); @@ -126,29 +135,66 @@ bool isValidMqttConnectInfo( const MQTTConnectInfo_t * pConnectInfo ) return isValid; } -MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pBuffer ) +MQTTFixedBuffer_t * allocateMqttFixedBuffer( MQTTFixedBuffer_t * pFixedBuffer ) +{ + if( pFixedBuffer == NULL ) + { + pFixedBuffer = mallocCanFail( sizeof( MQTTFixedBuffer_t ) ); + } + + if( pFixedBuffer != NULL ) + { + __CPROVER_assume( pFixedBuffer->size < CBMC_MAX_OBJECT_SIZE ); + pFixedBuffer->pBuffer = mallocCanFail( pFixedBuffer->size ); + } + + return pFixedBuffer; +} + +bool isValidMqttFixedBuffer( const MQTTFixedBuffer_t * pFixedBuffer ) +{ + bool isValid = true; + + if( pFixedBuffer != NULL ) + { + isValid = pFixedBuffer->size < CBMC_MAX_OBJECT_SIZE; + } + + return isValid; +} + +MQTTSubscribeInfo_t * allocateMqttSubscriptionList( MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount ) { - if( pBuffer == NULL ) + if( pSubscriptionList == NULL ) { - pBuffer = mallocCanFail( sizeof( MQTTFixedBuffer_t ) ); + __CPROVER_assume( sizeof( MQTTSubscribeInfo_t ) * subscriptionCount < CBMC_MAX_OBJECT_SIZE ); + pSubscriptionList = mallocCanFail( sizeof( MQTTSubscribeInfo_t ) * subscriptionCount ); } - if( pBuffer != NULL ) + if( pSubscriptionList != NULL ) { - __CPROVER_assume( pBuffer->size < CBMC_MAX_OBJECT_SIZE ); - pBuffer->pBuffer = mallocCanFail( pBuffer->size ); + for( int i = 0; i < subscriptionCount; i++ ) + { + __CPROVER_assume( pSubscriptionList[ i ].topicFilterLength < CBMC_MAX_OBJECT_SIZE ); + pSubscriptionList[ i ].pTopicFilter = mallocCanFail( pSubscriptionList[ i ].topicFilterLength ); + } } - return pBuffer; + return pSubscriptionList; } -bool isValidMqttFixedBuffer( const MQTTFixedBuffer_t * pBuffer ) +bool isValidMqttSubscriptionList( MQTTSubscribeInfo_t * pSubscriptionList, + size_t subscriptionCount ) { bool isValid = true; - if( pBuffer != NULL ) + if( pSubscriptionList != NULL ) { - isValid = pBuffer->size < CBMC_MAX_OBJECT_SIZE; + for( int i = 0; i < subscriptionCount; i++ ) + { + isValid = isValid && ( pSubscriptionList[ i ].topicFilterLength < CBMC_MAX_OBJECT_SIZE ); + } } return isValid; From fcac6b8f2b5b8d499b810185d61bb7ddbcb1cd9c Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 15 Jul 2020 16:58:25 -0700 Subject: [PATCH 587/844] Set keep alive interval and update unit tests (#1057) * Set keep alive interval and update unit tests * Change error log when no packet is available --- libraries/standard/mqtt/src/mqtt.c | 1 + .../standard/mqtt/src/mqtt_lightweight.c | 2 +- .../mqtt/utest/mqtt_lightweight_utest.c | 19 +++++++++++--- libraries/standard/mqtt/utest/mqtt_utest.c | 26 +++++++++++++++++-- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index c87446bcf8..1eeebf1207 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1430,6 +1430,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, { LogInfo( ( "MQTT connection established with the broker." ) ); pContext->connectStatus = MQTTConnected; + pContext->keepAliveIntervalSec = pConnectInfo->keepAliveSeconds; } else { diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index f8386de890..e3b9ba6f16 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -2234,7 +2234,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, } else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) ) { - LogError( ( "No data was received from the transport." ) ); + LogDebug( ( "No data was received from the transport." ) ); status = MQTTNoDataAvailable; } diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index b4826726a3..0d2f973338 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -1677,16 +1677,29 @@ void test_MQTT_GetIncomingPacketTypeAndLength( void ) TEST_ASSERT_EQUAL_INT( 0x20, mqttPacket.type ); TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); - /* Remaining length of 128 needs 2 bytes. */ + /* Remaining length of 128. MQTT uses 7 bits for data and 1 continuation + * bit in each byte. Since 128 is 8 bits, it needs 2 bytes. */ bufPtr = buffer; buffer[ 0 ] = MQTT_PACKET_TYPE_PUBLISH; - buffer[ 1 ] = 0x80; - buffer[ 2 ] = 0x01; + buffer[ 1 ] = 0x80; /* LSB: CB=1, value=0x00 */ + buffer[ 2 ] = 0x01; /* MSB: CB=0, value=0x01 */ status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTT_PACKET_TYPE_PUBLISH, mqttPacket.type ); TEST_ASSERT_EQUAL_INT( 128, mqttPacket.remainingLength ); + /* Remaining length of 16384. MQTT uses 7 bits for data and 1 continuation + * bit in each byte. Since 16384 is 15 bits, it needs 3 bytes. */ + bufPtr = buffer; + buffer[ 0 ] = MQTT_PACKET_TYPE_PUBLISH; + buffer[ 1 ] = 0x80; /* LSB : CB=1, value=0x00 */ + buffer[ 2 ] = 0x80; /* Byte 1: CB=1, value=0x00 */ + buffer[ 3 ] = 0x01; /* MSB : CB=0, value=0x01 */ + status = MQTT_GetIncomingPacketTypeAndLength( mockReceive, &networkContext, &mqttPacket ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_INT( MQTT_PACKET_TYPE_PUBLISH, mqttPacket.type ); + TEST_ASSERT_EQUAL_INT( 16384, mqttPacket.remainingLength ); + /* Test with incorrect packet type. */ bufPtr = buffer; buffer[ 0 ] = 0x10; /* INVALID */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index fc9f5a5162..53395cba7f 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -906,6 +906,7 @@ void test_MQTT_Connect_resendPendingAcks( void ) MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); + connectInfo.keepAliveSeconds = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; /* Test 1. No packets to resend reestablishing a session. */ /* successful receive CONNACK packet. */ @@ -922,10 +923,13 @@ void test_MQTT_Connect_resendPendingAcks( void ) status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresentResult ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); TEST_ASSERT_TRUE( sessionPresentResult ); /* Test 2. One packet found in ack pending state, but Sending ack failed. */ sessionPresentResult = false; + mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -937,7 +941,7 @@ void test_MQTT_Connect_resendPendingAcks( void ) MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID ); status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresentResult ); TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); - TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( MQTTNotConnected, mqttContext.connectStatus ); TEST_ASSERT_TRUE( sessionPresentResult ); /* Test 3. One packet found in ack pending state, Sent @@ -956,9 +960,12 @@ void test_MQTT_Connect_resendPendingAcks( void ) status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); /* Test 4. Three packets found in ack pending state. Sent PUBREL successfully * for first and failed for second and no attempt for third. */ + mqttContext.keepAliveIntervalSec = 0; + mqttContext.connectStatus = MQTTNotConnected; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -978,7 +985,7 @@ void test_MQTT_Connect_resendPendingAcks( void ) MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 2 ); status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status ); - TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( MQTTNotConnected, mqttContext.connectStatus ); /* Test 5. Two packets found in ack pending state. Sent PUBREL successfully * for first and failed for second. */ @@ -1003,6 +1010,7 @@ void test_MQTT_Connect_resendPendingAcks( void ) status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); } /** @@ -1028,6 +1036,7 @@ void test_MQTT_Connect_happy_path() memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + connectInfo.keepAliveSeconds = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); @@ -1041,9 +1050,11 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); /* With non-NULL Will. */ mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; willInfo.pTopicName = "test"; willInfo.topicNameLength = 4; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -1051,9 +1062,11 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); /* Request to establish a clean session. */ mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; connectInfo.cleanSession = true; sessionPresentExpected = false; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -1063,11 +1076,13 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); TEST_ASSERT_FALSE( sessionPresent ); /* Request to establish a session if present and session present is received * from broker. */ mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; connectInfo.cleanSession = false; sessionPresentExpected = true; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -1078,9 +1093,12 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); TEST_ASSERT_TRUE( sessionPresent ); /* CONNACK receive with timeoutMs=0. Retry logic will be used. */ + mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; sessionPresent = false; incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; incomingPacket.remainingLength = 2; @@ -1090,10 +1108,13 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); /* CONNACK receive with timeoutMs=0. Retry logic will be used. * #MQTTNoDataAvailable for first #MQTT_GetIncomingPacketTypeAndLength * and success in the second time. */ + mqttContext.connectStatus = MQTTNotConnected; + mqttContext.keepAliveIntervalSec = 0; incomingPacket.type = MQTT_PACKET_TYPE_CONNACK; incomingPacket.remainingLength = 2; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable ); @@ -1103,6 +1124,7 @@ void test_MQTT_Connect_happy_path() status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); + TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); } /* ========================================================================== */ From 694d9e96ea2995aca1e4e5ba0b661b9f122a49e8 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Wed, 15 Jul 2020 17:42:50 -0700 Subject: [PATCH 588/844] Revert accidentally committed demo config header (#1058) --- demos/mqtt/mqtt_demo_mutual_auth/demo_config.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index b7495b7a15..fc6db44fe6 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -58,7 +58,6 @@ * * #define AWS_IOT_ENDPOINT "...insert here..." */ -#define AWS_IOT_ENDPOINT "a36385mjytouy4-ats.iot.us-west-2.amazonaws.com" /** * @brief AWS IoT MQTT broker port number. @@ -68,7 +67,7 @@ * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol * name. When using port 8883, ALPN is not required. */ -#define AWS_MQTT_PORT ( 8883 ) +#define AWS_MQTT_PORT ( 8883 ) /** * @brief Path of the file containing the server's root CA certificate. @@ -85,7 +84,7 @@ * @note This path is relative from the demo binary created. Update * ROOT_CA_CERT_PATH to the absolute path if this demo is executed from elsewhere. */ -#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" /** * @brief Path of the file containing the client certificate. @@ -98,7 +97,6 @@ * * #define CLIENT_CERT_PATH "...insert here..." */ -#define CLIENT_CERT_PATH "certificates/client.crt" /** * @brief Path of the file containing the client's private key. @@ -111,19 +109,18 @@ * * #define CLIENT_PRIVATE_KEY_PATH "...insert here..." */ -#define CLIENT_PRIVATE_KEY_PATH "certificates/client.key" /** * @brief MQTT client identifier. * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER "testclient" /** * @brief Size of the network buffer for MQTT packets. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define NETWORK_BUFFER_SIZE ( 1024U ) #endif /* ifndef DEMO_CONFIG_H_ */ From 507ec1b336b9422d25cf582a961f3d0ed688a1af Mon Sep 17 00:00:00 2001 From: Navin Soni <51832113+navinns@users.noreply.github.com> Date: Fri, 17 Jul 2020 13:04:43 -0700 Subject: [PATCH 589/844] Add tool to build Demo (#1059) --- tools/ci/ci_demos.py | 246 +++++++++++++++++++++++++++++++++++++++ tools/ci/demo_config.yml | 22 ++++ 2 files changed, 268 insertions(+) create mode 100644 tools/ci/ci_demos.py create mode 100644 tools/ci/demo_config.yml diff --git a/tools/ci/ci_demos.py b/tools/ci/ci_demos.py new file mode 100644 index 0000000000..6a78157776 --- /dev/null +++ b/tools/ci/ci_demos.py @@ -0,0 +1,246 @@ +import argparse +import re +import shutil +import subprocess +import sys +from pathlib import Path + +import junitparser as junit +import yaml + + +def cli_config_build(args): + _file = Path(args.config_file) + _config = _read_config(_file) + _build_flags = _config.get("_default", {}).get("build_flags", []) + + _del_dir(args.build_path) + _config_build(args.src, _build_flags, args.build_path) + + +def cli_get_targets(args): + _targets = _get_targets(args.build_path) + print(" ".join(_targets)) + + +def cli_build_targets(args): + _file = Path(args.config_file) + _config = _read_config(_file) + result = _build_targets(args.targets, _config, args.build_path) + + _junit_filename = _config.get("_default", {}).get("name", None) + if _junit_filename: + _junit_filename = f"build_{_junit_filename}.xml" + + _log_save_result(result, _junit_filename) + _check_status(result) + + +def cli_build(args): + _file = Path(args.config_file) + _config = _read_config(_file) + _build_flags = _config.get("_default", {}).get("build_flags", []) + + _config_build(args.src, _build_flags, args.build_path) + _targets = _get_targets(args.build_path) + + allowed = "|".join(_config.get("_default", {}).get("allow", [])) + _targets = [_target for _target in _targets if re.match(allowed, _target)] + + result = _build_targets(_targets, _config, args.build_path) + + _junit_filename = _config.get("_default", {}).get("name", None) + if _junit_filename: + _junit_filename = f"build_{_junit_filename}.xml" + + _log_save_result(result, _junit_filename) + _check_status(result) + + +def cli_invoke(args_list): + _cli_invoke_next(args_list) + + +def get_parser(): + def new_argument(arg, **kwargs): + arguments[arg] = kwargs + + def add_argument(cmd, arg, **kwargs): + cmd.add_argument(arg, **{**arguments.get(arg, {}), **kwargs}) + + def add_arguments(cmd, *args): + for arg in args: + cmd.add_argument(arg, **arguments[arg]) + + arguments = {} + new_argument("--src", required=True, help="Path to C-SDK") + new_argument("--config-file", default=".", help="Path to config file") + new_argument("--build-path", required=True, help="Path to build location") + new_argument( + "--build-args", default="", help="Arguments required for build configuration" + ) + new_argument("--targets", nargs="+", required=True, help="Targets to build") + + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + + cmd_config_build = subparsers.add_parser("configure-build") + add_arguments( + cmd_config_build, "--config-file", "--src", "--build-path", "--build-args" + ) + cmd_config_build.set_defaults(func="config-build") + + cmd_get_targets = subparsers.add_parser("get-targets") + add_argument(cmd_get_targets, "--build-path") + cmd_get_targets.set_defaults(func="get-targets") + + cmd_build_targets = subparsers.add_parser("build-targets") + add_arguments(cmd_build_targets, "--config-file", "--targets", "--build-path") + cmd_build_targets.set_defaults(func="build-targets") + + cmd_build = subparsers.add_parser("build") + add_arguments(cmd_build, "--config-file", "--src", "--build-path", "--build-args") + cmd_build.set_defaults(func="build") + return parser + + +def main(): + parser = get_parser() + if len(sys.argv) <= 1: + parser.print_help() + sys.exit(1) + + args_list = parser.parse_args() + cli_invoke(args_list) + + +# ----------------------------------------------------------------------------------- +# Private Functions +# ----------------------------------------------------------------------------------- +def _cli_invoke_next(args_list, prefix="cli"): + next_cmd = args_list.func + args = args_list + func = globals()[prefix + "_" + next_cmd.replace("-", "_")] + func_args = [args] if vars(args) else [] + [args_list] if args_list else [] + return func(*func_args) + + +def _config_build(_src, _build_flags, _build_path): + _build_flags = " ".join(_build_flags) + _run_cmd(f'cmake -S {_src} -B {_build_path} {_build_flags} -G "Unix Makefiles"') + + +def _build_target(_target, _c_flags, build_path): + print("\n----------------------------------------------------------------") + print(f"Building target: {_target}") + print("----------------------------------------------------------------") + _c_flags = " ".join(_c_flags) + cmd = f"make -C {build_path} {_c_flags} {_target}" + print(_run_cmd(cmd)) + + +def _get_targets(build_path): + _targets = _run_cmd(f"make help -C {build_path} | tr -d '. '") + _targets = [_t.strip() for _t in _targets.split()] + return _targets + + +def _build_targets(_targets, _config, build_path): + result = {} + + for _target in _targets: + _target_result = result.setdefault(_target, {}) + _target_build_result = _target_result.setdefault("Build", {}) + try: + _c_flags = _config.get("_default", {}).get("c_flags", []) + _config.get( + _target, {} + ).get("c_flags", []) + _build_target(_target, _c_flags, build_path) + _target_build_result["status"] = "PASS" + except subprocess.CalledProcessError as err: + print(err.stdout) + _target_build_result["status"] = "FAIL" + _target_build_result["details"] = "Build Failure" + return result + + +def _del_dir(dir_name): + try: + print(f"Deleting dir: {dir_name}") + shutil.rmtree(dir_name) + except OSError as err: + print("Error: %s - %s." % (err.filename, err.strerror)) + + +def _read_config(_path): + try: + _config = yaml.load(_path.read_text()) + return _config + except yaml.YAMLError: + print(f"Error: Unable to load file {_path.name}") + sys.exit(1) + + +def _run_cmd(cmd): + print(f"Executing command: {cmd}") + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + encoding="utf-8", + check=True, + ) + return result.stdout + + +def _check_status(result): + status = any([_k for _k, _v in result.items() if _v.get("status") == "FAIL"]) + if status: + sys.exit(1) + + +def _log_save_result(result, filename): + print("\n----------------------------------------------------------------") + print("Build Result") + print("----------------------------------------------------------------") + print(yaml.dump(result)) + + if filename: + _write_junit(_to_junit(result, "linux.cmake"), filename) + + +def _to_junit(result, platform=""): + """ + Convert result to junit format. + """ + report = junit.JUnitXml() + if not result: + return report + + platform += platform and "." + + for target, target_result in result.items(): + suite = junit.TestSuite(platform + target) + for case_name, case_result in target_result.items(): + case = junit.TestCase(case_name) + if case_result["status"] == "FAIL": + case.result = junit.Failure(case_result.get("details", "")) + if case_result["status"] == "IGNORE": + case.result = junit.Skipped(case_result.get("details", "")) + suite.add_testcase(case) + report.add_testsuite(suite) + report.update_statistics() + + return report + + +def _write_junit(junit_report, file_name): + try: + junit_report.write(file_name, pretty=True) + except Exception as err: + print(f"[ERROR] Unable to write junit_file: {str(err)}") + + +if __name__ == "__main__": + main() diff --git a/tools/ci/demo_config.yml b/tools/ci/demo_config.yml new file mode 100644 index 0000000000..ea1c2a3f11 --- /dev/null +++ b/tools/ci/demo_config.yml @@ -0,0 +1,22 @@ +# Note: Edit this file for local testing, Contact CI for testing in CI Infra + +# _default section consists of config common to all targets +_default: + name: demos + build_flags: + # Provide list of flags required for configuring build using cmake + - "" + allow: + # Provide list of allowed targets [Accepts regex] + - ".*_demo_.*" + c_flags: + # Provide list of c_flags common for all targets + - "-Wall -Wextra -Werror" + + +#: + #c_flags: + # Provide list of c_flags specific to target + #- + #- + #- From 0b90fe152e53e4509cd97ab75cc2c03d45dbe922 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Fri, 17 Jul 2020 15:44:52 -0700 Subject: [PATCH 590/844] Fix straggling lexicon words in mqtt and fixed spelling of retrieve. (#1060) --- libraries/standard/mqtt/lexicon.txt | 4 ++++ platform/posix/transport/src/sockets_posix.c | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 270a9506e0..6032e47d58 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -5,6 +5,7 @@ addrecord api app aws +bool bufferlength bytesreceived bytestoread @@ -14,6 +15,7 @@ bytestosend bytestowrite ca calculatestatepublish +cb cbmc cleansession cmock @@ -64,6 +66,7 @@ init int iot isn +lsb lwt malloc managekeepalive @@ -213,6 +216,7 @@ tcpsocket testcase timeoutms tls +topicFilterLength uint un unsuback diff --git a/platform/posix/transport/src/sockets_posix.c b/platform/posix/transport/src/sockets_posix.c index eb475f818c..b91b3d6e35 100644 --- a/platform/posix/transport/src/sockets_posix.c +++ b/platform/posix/transport/src/sockets_posix.c @@ -103,7 +103,7 @@ static SocketStatus_t attemptConnection( struct addrinfo * pListHead, * * @return #SOCKETS_API_ERROR, #SOCKETS_INSUFFICIENT_MEMORY, #SOCKETS_INVALID_PARAMETER on error. */ -static SocketStatus_t retreiveError( void ); +static SocketStatus_t retrieveError( void ); /*-----------------------------------------------------------*/ @@ -262,7 +262,7 @@ static SocketStatus_t attemptConnection( struct addrinfo * pListHead, } /*-----------------------------------------------------------*/ -static SocketStatus_t retreiveError( void ) +static SocketStatus_t retrieveError( void ) { SocketStatus_t returnStatus = SOCKETS_API_ERROR; @@ -385,7 +385,7 @@ SocketStatus_t Sockets_Connect( int * pTcpSocket, if( setTimeoutStatus < 0 ) { LogError( ( "Setting socket send timeout failed." ) ); - returnStatus = retreiveError(); + returnStatus = retrieveError(); } } @@ -404,7 +404,7 @@ SocketStatus_t Sockets_Connect( int * pTcpSocket, if( setTimeoutStatus < 0 ) { LogError( ( "Setting socket receive timeout failed." ) ); - returnStatus = retreiveError(); + returnStatus = retrieveError(); } } From af2d64cfec23ae100b779fcfd9b1ab837dbbe2f3 Mon Sep 17 00:00:00 2001 From: Navin Soni <51832113+navinns@users.noreply.github.com> Date: Tue, 21 Jul 2020 13:08:23 -0700 Subject: [PATCH 591/844] Add code for unittest (#1062) * Add code for unittest * Fix lexicon.txt Fix topicfilterlength in lexicon.txt in MQTT to be all lowercase. * update run targets * Fix check status Co-authored-by: SarenaAWS <6563840+sarenameas@users.noreply.github.com> --- libraries/standard/mqtt/lexicon.txt | 2 +- tools/ci/{ci_demos.py => ci_agent.py} | 113 +++++++++++++++++++++----- tools/ci/demo_config.yml | 1 + tools/ci/unittest_config.yml | 23 ++++++ 4 files changed, 118 insertions(+), 21 deletions(-) rename tools/ci/{ci_demos.py => ci_agent.py} (63%) create mode 100644 tools/ci/unittest_config.yml diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 6032e47d58..b3ccee2500 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -216,7 +216,7 @@ tcpsocket testcase timeoutms tls -topicFilterLength +topicfilterlength uint un unsuback diff --git a/tools/ci/ci_demos.py b/tools/ci/ci_agent.py similarity index 63% rename from tools/ci/ci_demos.py rename to tools/ci/ci_agent.py index 6a78157776..64ddbe5503 100644 --- a/tools/ci/ci_demos.py +++ b/tools/ci/ci_agent.py @@ -12,7 +12,8 @@ def cli_config_build(args): _file = Path(args.config_file) _config = _read_config(_file) - _build_flags = _config.get("_default", {}).get("build_flags", []) + _default_config = _config.get("_default", {}) + _build_flags = _default_config.get("build_flags", []) _del_dir(args.build_path) _config_build(args.src, _build_flags, args.build_path) @@ -26,35 +27,71 @@ def cli_get_targets(args): def cli_build_targets(args): _file = Path(args.config_file) _config = _read_config(_file) + _default_config = _config.get("_default", {}) + _junit_filename = _default_config.get("name", None) result = _build_targets(args.targets, _config, args.build_path) - _junit_filename = _config.get("_default", {}).get("name", None) - if _junit_filename: - _junit_filename = f"build_{_junit_filename}.xml" - - _log_save_result(result, _junit_filename) + _log_save_result(result, f"build_{_junit_filename}.xml") _check_status(result) +def cli_run_targets(args): + _file = Path(args.config_file) + _config = _read_config(_file) + _default_config = _config.get("_default", {}) + _junit_filename = _default_config.get("name", None) + + run_result = _run_targets( + args.targets, f"{args.build_path}/{_default_config.get('output_loc', '')}" + ) + _log_save_result(run_result, f"run_{_junit_filename}.xml") + _check_status(run_result) + + def cli_build(args): _file = Path(args.config_file) _config = _read_config(_file) - _build_flags = _config.get("_default", {}).get("build_flags", []) + _default_config = _config.get("_default", {}) + _junit_filename = _default_config.get("name", None) + _build_flags = _default_config.get("build_flags", []) + _del_dir(args.build_path) _config_build(args.src, _build_flags, args.build_path) _targets = _get_targets(args.build_path) - allowed = "|".join(_config.get("_default", {}).get("allow", [])) + allowed = "|".join(_default_config.get("allow", [])) _targets = [_target for _target in _targets if re.match(allowed, _target)] - result = _build_targets(_targets, _config, args.build_path) + build_result = _build_targets(_targets, _config, args.build_path) - _junit_filename = _config.get("_default", {}).get("name", None) - if _junit_filename: - _junit_filename = f"build_{_junit_filename}.xml" + _log_save_result(build_result, f"build_{_junit_filename}.xml") + _check_status(build_result) - _log_save_result(result, _junit_filename) - _check_status(result) + +def cli_run(args): + _file = Path(args.config_file) + _config = _read_config(_file) + _default_config = _config.get("_default", {}) + _junit_filename = _default_config.get("name", None) + _build_flags = _default_config.get("build_flags", []) + + _del_dir(args.build_path) + _config_build(args.src, _build_flags, args.build_path) + _targets = _get_targets(args.build_path) + + allowed = "|".join(_default_config.get("allow", [])) + _targets = [_target for _target in _targets if re.match(allowed, _target)] + + build_result = _build_targets(_targets, _config, args.build_path) + + _log_save_result(build_result, f"build_{_junit_filename}.xml") + + run_result = _run_targets( + build_result.keys(), + f"{args.build_path}/{_default_config.get('output_loc', '')}", + ) + _log_save_result(run_result, f"run_{_junit_filename}.xml") + _check_status(run_result) def cli_invoke(args_list): @@ -98,9 +135,17 @@ def add_arguments(cmd, *args): add_arguments(cmd_build_targets, "--config-file", "--targets", "--build-path") cmd_build_targets.set_defaults(func="build-targets") + cmd_run_targets = subparsers.add_parser("run-targets") + add_arguments(cmd_run_targets, "--config-file", "--targets", "--build-path") + cmd_run_targets.set_defaults(func="run-targets") + cmd_build = subparsers.add_parser("build") add_arguments(cmd_build, "--config-file", "--src", "--build-path", "--build-args") cmd_build.set_defaults(func="build") + + cmd_run = subparsers.add_parser("run") + add_arguments(cmd_run, "--config-file", "--src", "--build-path", "--build-args") + cmd_run.set_defaults(func="run") return parser @@ -136,7 +181,7 @@ def _build_target(_target, _c_flags, build_path): print("----------------------------------------------------------------") _c_flags = " ".join(_c_flags) cmd = f"make -C {build_path} {_c_flags} {_target}" - print(_run_cmd(cmd)) + return _run_cmd(cmd) def _get_targets(build_path): @@ -147,23 +192,43 @@ def _get_targets(build_path): def _build_targets(_targets, _config, build_path): result = {} + _default_config = _config.get("_default", {}) for _target in _targets: _target_result = result.setdefault(_target, {}) _target_build_result = _target_result.setdefault("Build", {}) try: - _c_flags = _config.get("_default", {}).get("c_flags", []) + _config.get( + _c_flags = _default_config.get("c_flags", []) + _config.get( _target, {} ).get("c_flags", []) - _build_target(_target, _c_flags, build_path) + out = _build_target(_target, _c_flags, build_path) + print(out) _target_build_result["status"] = "PASS" - except subprocess.CalledProcessError as err: + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: print(err.stdout) _target_build_result["status"] = "FAIL" _target_build_result["details"] = "Build Failure" return result +def _run_targets(_targets, _path): + result = {} + for _target in _targets: + _target_result = result.setdefault(_target, {}) + _target_run_result = _target_result.setdefault("Run", {}) + print("\n----------------------------------------------------------------") + print(f"Running Target: {_target}") + print("----------------------------------------------------------------") + try: + print(_run_cmd(f"{_path}/{_target}")) + _target_run_result["status"] = "PASS" + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + print(err.stdout) + _target_run_result["status"] = "FAIL" + _target_run_result["details"] = "Run Failure" + return result + + def _del_dir(dir_name): try: print(f"Deleting dir: {dir_name}") @@ -190,19 +255,27 @@ def _run_cmd(cmd): shell=True, encoding="utf-8", check=True, + timeout=10, ) return result.stdout def _check_status(result): - status = any([_k for _k, _v in result.items() if _v.get("status") == "FAIL"]) + status = any( + [ + _k + for _k, _v in result.items() + for _k1, _v1 in _v.items() + if not _v1.get("status") == "PASS" + ] + ) if status: sys.exit(1) def _log_save_result(result, filename): print("\n----------------------------------------------------------------") - print("Build Result") + print(f"{filename.split('_')[0].title()} Result") print("----------------------------------------------------------------") print(yaml.dump(result)) diff --git a/tools/ci/demo_config.yml b/tools/ci/demo_config.yml index ea1c2a3f11..fbc3a0160a 100644 --- a/tools/ci/demo_config.yml +++ b/tools/ci/demo_config.yml @@ -12,6 +12,7 @@ _default: c_flags: # Provide list of c_flags common for all targets - "-Wall -Wextra -Werror" + output_loc: "bin" #: diff --git a/tools/ci/unittest_config.yml b/tools/ci/unittest_config.yml new file mode 100644 index 0000000000..a8bae1eb64 --- /dev/null +++ b/tools/ci/unittest_config.yml @@ -0,0 +1,23 @@ +# Note: Edit this file for local testing, Contact CI for testing in CI Infra + +# _default section consists of config common to all targets +_default: + name: unittest + build_flags: + # Provide list of flags required for configuring build using cmake + - "-DBUILD_TESTS=1" + allow: + # Provide list of allowed targets [Accepts regex] + - ".*utest.*" + c_flags: + # Provide list of c_flags common for all targets + - "-Wall -Wextra -Werror" + output_loc: "bin/tests" + + +#: + #c_flags: + # Provide list of c_flags specific to target + #- + #- + #- From 3535f2229c8f3e9ce9dd523f32a7ac521ab8786c Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Tue, 21 Jul 2020 17:42:03 -0400 Subject: [PATCH 592/844] Repair loop names mangled by the new cbmc. (#1041) Co-authored-by: Mark R Tuttle Co-authored-by: SarenaAWS <6563840+sarenameas@users.noreply.github.com> --- .../http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile | 4 +++- .../cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile index da4f80c38a..bbaa8fd164 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile @@ -9,7 +9,9 @@ INCLUDES += REMOVE_FUNCTION_BODY += # Maximum value of a 32 bit signed integer is 2,147,483,647, which is 10 digits. -UNWINDSET += strncmp.0:5 convertInt32ToAscii.0:11 convertInt32ToAscii.1:11 +UNWINDSET += strncmp.0:5 +UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.0:11 +UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.1:11 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile index b0b25adecd..664c913b1b 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile @@ -4,7 +4,7 @@ HARNESS_FILE=MQTT_GetIncomingPacketTypeAndLength_harness DEFINES += INCLUDES += REMOVE_FUNCTION_BODY += -UNWINDSET += getRemainingLength.0:5 +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_getRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c From febf2ab349d36edc3befa5fb0bfb995e1818a8df Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 22 Jul 2020 09:14:08 -0700 Subject: [PATCH 593/844] Fix/http build warnings (#1064) * Suppress unused variable warnings in http * Suppress unused variable warnings in http --- libraries/standard/http/src/http_client.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index ff6092f45e..576ae65d09 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -533,6 +533,9 @@ static int httpParserOnMessageBeginCallback( http_parser * pHttpParser ) HTTPParsingContext_t * pParsingContext = NULL; HTTPResponse_t * pResponse = NULL; + /* Disable unused variable warning. */ + ( void ) pResponse; + assert( pHttpParser != NULL ); assert( pHttpParser->data != NULL ); @@ -656,6 +659,9 @@ static int httpParserOnHeaderValueCallback( http_parser * pHttpParser, HTTPParsingContext_t * pParsingContext = NULL; HTTPResponse_t * pResponse = NULL; + /* Disable unused variable warning. */ + ( void ) pResponse; + assert( pHttpParser != NULL ); assert( pHttpParser->data != NULL ); assert( pLoc != NULL ); @@ -1011,6 +1017,9 @@ static HTTPStatus_t parseHttpResponse( HTTPParsingContext_t * pParsingContext, http_parser_settings parserSettings = { 0 }; size_t bytesParsed = 0u; + /* Disable unused variable warning. */ + ( void ) bytesParsed; + assert( pParsingContext != NULL ); assert( pResponse != NULL ); assert( isHeadResponse <= 1 ); @@ -2091,6 +2100,8 @@ static int findHeaderOnHeaderCompleteCallback( http_parser * pHttpParser ) /* Disable unused parameter warning. */ ( void ) pHttpParser; + /* Disable unused variable warning. */ + ( void ) pContext; assert( pHttpParser != NULL ); From 4211fa9b1d42eb37f58e9633cf8051e8d51ae2a1 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Wed, 22 Jul 2020 11:51:03 -0700 Subject: [PATCH 594/844] Add system tests for LWT and Keep Alive (#1013) * Add system tests for LWT and Keep Alive * Add sleep function to clock.h --- .../mqtt/integration-test/mqtt_system_test.c | 135 ++++++++++++++++-- libraries/standard/mqtt/src/mqtt.c | 2 +- .../standard/mqtt/src/mqtt_lightweight.c | 4 +- platform/include/clock.h | 7 + platform/lexicon.txt | 1 + platform/posix/clock_posix.c | 19 +++ 6 files changed, 156 insertions(+), 12 deletions(-) diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index b2b595f57c..202d3bedf4 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -100,6 +100,16 @@ */ #define TEST_MQTT_TOPIC_LENGTH ( sizeof( TEST_MQTT_TOPIC ) - 1 ) +/** + * @brief Sample topic filter to subscribe to. + */ +#define TEST_MQTT_LWT_TOPIC "/iot/integration/test/lwt" + +/** + * @brief Length of sample topic filter. + */ +#define TEST_MQTT_LWT_TOPIC_LENGTH ( sizeof( TEST_MQTT_LWT_TOPIC ) - 1 ) + /** * @brief Size of the network buffer for MQTT packets. */ @@ -115,6 +125,16 @@ */ #define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) +/** + * @brief Client identifier for use in LWT tests. + */ +#define TEST_CLIENT_IDENTIFIER_LWT "MQTT-Test-LWT" + +/** + * @brief Length of LWT client identifier. + */ +#define TEST_CLIENT_IDENTIFIER_LWT_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER_LWT ) - 1u ) + /** * @brief Transport timeout in milliseconds for transport send and receive. */ @@ -129,7 +149,7 @@ * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to * broker. */ -#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 30U ) +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 5U ) /** * @brief Timeout for MQTT_ProcessLoop() function in milliseconds. @@ -187,11 +207,16 @@ static OpensslCredentials_t opensslCredentials; static MQTTContext_t context; /** - * @brief Flag that represents whether a persistent session should be - * established with the broker for the test. + * @brief Flag that represents whether a persistent session was resumed + * with the broker for the test. */ static bool persistentSession = false; +/** + * @brief Flag to indicate if LWT is being used when establishing a connection. + */ +static bool useLWTClientIdentifier = false; + /** * @brief Flag to represent whether a SUBACK is received from the broker. */ @@ -269,6 +294,7 @@ static void establishMqttSession( MQTTContext_t * pContext, TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; + MQTTPublishInfo_t lwtInfo; assert( pContext != NULL ); assert( pNetworkContext != NULL ); @@ -303,8 +329,16 @@ static void establishMqttSession( MQTTContext_t * pContext, connectInfo.cleanSession = createCleanSession; - connectInfo.pClientIdentifier = TEST_CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = TEST_CLIENT_IDENTIFIER_LENGTH; + if( useLWTClientIdentifier ) + { + connectInfo.pClientIdentifier = TEST_CLIENT_IDENTIFIER_LWT; + connectInfo.clientIdentifierLength = TEST_CLIENT_IDENTIFIER_LWT_LENGTH; + } + else + { + connectInfo.pClientIdentifier = TEST_CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = TEST_CLIENT_IDENTIFIER_LENGTH; + } /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; @@ -315,10 +349,19 @@ static void establishMqttSession( MQTTContext_t * pContext, connectInfo.pPassword = NULL; connectInfo.passwordLength = 0U; + /* LWT Info. */ + lwtInfo.pTopicName = TEST_MQTT_LWT_TOPIC; + lwtInfo.topicNameLength = TEST_MQTT_LWT_TOPIC_LENGTH; + lwtInfo.pPayload = MQTT_EXAMPLE_MESSAGE; + lwtInfo.payloadLength = strlen( MQTT_EXAMPLE_MESSAGE ); + lwtInfo.qos = MQTTQoS0; + lwtInfo.dup = false; + lwtInfo.retain = false; + /* Send MQTT CONNECT packet to broker. */ TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Connect( pContext, &connectInfo, - NULL, + &lwtInfo, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ) ); } @@ -425,7 +468,7 @@ static void eventCallback( MQTTContext_t * pContext, /* Nothing to be done from application as library handles * PUBCOMP. */ - LogDebug( ( "Unexpected PUBCOMP received: PacketID=%u", + LogDebug( ( "Received PUBCOMP: PacketID=%u", packetIdentifier ) ); break; @@ -527,6 +570,7 @@ void setUp() receivedPubRel = false; receivedPubComp = false; persistentSession = false; + useLWTClientIdentifier = false; memset( &incomingInfo, 0u, sizeof( MQTTPublishInfo_t ) ); memset( &opensslCredentials, 0u, sizeof( OpensslCredentials_t ) ); opensslCredentials.pRootCaPath = SERVER_ROOT_CA_CERT_PATH; @@ -702,9 +746,11 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) /* We expect PUBREC and PUBCOMP responses for the PUBLISH request, and * incoming PUBLISH with the same message that we published (as we are subscribed * to the same topic). Also, we expect a PUBREL ack response from the server for - * the incoming PUBLISH (as we subscribed and publish with QoS 2). */ + * the incoming PUBLISH (as we subscribed and publish with QoS 2). Since it takes + * longer to complete a QoS 2 publish, we run the process loop longer to allow it + * ample time. */ TEST_ASSERT_EQUAL( MQTTSuccess, - MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_FALSE( receivedPubAck ); TEST_ASSERT_TRUE( receivedPubRec ); TEST_ASSERT_TRUE( receivedPubComp ); @@ -731,3 +777,74 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_TRUE( receivedUnsubAck ); } + +void test_MQTT_Connect_LWT( void ) +{ + int secondTcpSocket; + NetworkContext_t secondNetworkContext = { 0 }; + bool sessionPresent; + MQTTContext_t secondContext; + + /* Establish a second TCP connection with the server endpoint, then + * a TLS session. The server info and credentials can be reused. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &secondNetworkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, secondNetworkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( secondNetworkContext.pSsl ); + + /* Establish MQTT session on top of the TCP+TLS connection. */ + useLWTClientIdentifier = true; + establishMqttSession( &secondContext, &secondNetworkContext, true, &sessionPresent ); + + /* Subscribe to LWT Topic. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_LWT_TOPIC, MQTTQoS0 ) ); + + /* Abruptly terminate TCP connection. */ + ( void ) Openssl_Disconnect( &secondNetworkContext ); + + /* Run the process loop to receive the LWT. Allow some more time for the + * server to realize the connection is closed. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Test if we have received the LWT. */ + TEST_ASSERT_EQUAL( MQTTQoS0, incomingInfo.qos ); + TEST_ASSERT_EQUAL( TEST_MQTT_LWT_TOPIC_LENGTH, incomingInfo.topicNameLength ); + TEST_ASSERT_EQUAL_MEMORY( TEST_MQTT_LWT_TOPIC, + incomingInfo.pTopicName, + TEST_MQTT_LWT_TOPIC_LENGTH ); + TEST_ASSERT_EQUAL( strlen( MQTT_EXAMPLE_MESSAGE ), incomingInfo.payloadLength ); + TEST_ASSERT_EQUAL_MEMORY( MQTT_EXAMPLE_MESSAGE, + incomingInfo.pPayload, + incomingInfo.payloadLength ); + + /* Un-subscribe from a topic with Qos 0. */ + TEST_ASSERT_EQUAL( MQTTSuccess, unsubscribeFromTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); + + /* We expect an UNSUBACK from the broker for the unsubscribe operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedUnsubAck ); +} + +void test_MQTT_ProcessLoop_KeepAlive( void ) +{ + uint32_t connectPacketTime = context.lastPacketTime; + uint32_t elapsedTime = 0; + TEST_ASSERT_EQUAL( 0, context.pingReqSendTimeMs ); + + /* Sleep until control packet needs to be sent. */ + Clock_SleepMs( MQTT_KEEP_ALIVE_INTERVAL_SECONDS * 1000 ); + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + TEST_ASSERT_NOT_EQUAL( 0, context.pingReqSendTimeMs ); + TEST_ASSERT_NOT_EQUAL( connectPacketTime, context.lastPacketTime ); + /* Test that the ping was sent within 1.5 times the keep alive interval. */ + elapsedTime = context.lastPacketTime - connectPacketTime; + TEST_ASSERT_LESS_OR_EQUAL( MQTT_KEEP_ALIVE_INTERVAL_SECONDS * 1500, elapsedTime ); +} diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 1eeebf1207..de53ade3b8 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -337,7 +337,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, bytesRemaining -= ( size_t ) bytesSent; totalBytesSent += bytesSent; pIndex += bytesSent; - LogDebug( ( "Bytes sent=%d, bytes remaining=%ul," + LogDebug( ( "Bytes sent=%d, bytes remaining=%lu," "total bytes sent=%d.", bytesSent, bytesRemaining, diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index e3b9ba6f16..a11f98f580 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -321,7 +321,7 @@ static size_t remainingLengthEncodedSize( size_t length ) encodedSize = 4U; } - LogDebug( ( "Encoded size for length =%ul is %ul.", + LogDebug( ( "Encoded size for length %lu is %lu bytes.", length, encodedSize ) ); @@ -2096,7 +2096,7 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, } else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) { - LogError( ( "Packet is not publish. Packet type: %hu.", + LogError( ( "Packet is not publish. Packet type: %02x.", pIncomingPacket->type ) ); status = MQTTBadParameter; } diff --git a/platform/include/clock.h b/platform/include/clock.h index 77f1a16077..5d88e30539 100644 --- a/platform/include/clock.h +++ b/platform/include/clock.h @@ -39,4 +39,11 @@ */ uint32_t Clock_GetTimeMs( void ); +/** + * @brief Millisecond sleep function. + * + * @param[in] sleepTimeMs milliseconds to sleep. + */ +void Clock_SleepMs( uint32_t sleepTimeMs ); + #endif /* ifndef CLOCK_H_ */ diff --git a/platform/lexicon.txt b/platform/lexicon.txt index b5cb5ef241..6003b490fe 100644 --- a/platform/lexicon.txt +++ b/platform/lexicon.txt @@ -60,6 +60,7 @@ recvtimeoutms sdk sendtimeout sendtimeoutms +sleeptimems sni src ssl diff --git a/platform/posix/clock_posix.c b/platform/posix/clock_posix.c index 8a5c6253e7..fadc341350 100644 --- a/platform/posix/clock_posix.c +++ b/platform/posix/clock_posix.c @@ -34,6 +34,12 @@ /* Platform clock include. */ #include "clock.h" +/* + * Time conversion constants. + */ +#define NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ +#define MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ + /*-----------------------------------------------------------*/ uint32_t Clock_GetTimeMs( void ) @@ -50,3 +56,16 @@ uint32_t Clock_GetTimeMs( void ) return timeMs; } + +/*-----------------------------------------------------------*/ + +void Clock_SleepMs( uint32_t sleepTimeMs ) +{ + /* Convert parameter to timespec. */ + struct timespec sleepTime = { 0 }; + sleepTime.tv_sec = sleepTimeMs / MILLISECONDS_PER_SECOND; + sleepTime.tv_nsec = ( sleepTimeMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND; + + /* High resolution sleep. */ + ( void ) nanosleep( &sleepTime, NULL ); +} From 05daa81c33281fbb062de6cc89dc9f54f22cbc65 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 22 Jul 2020 21:42:19 -0700 Subject: [PATCH 595/844] Fix failing MQTT unit test in Release builds (#1069) * Fix missing network buffer initialization logic causing test case failure --- libraries/standard/mqtt/utest/mqtt_utest.c | 36 ++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 53395cba7f..1e26238f40 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -12,80 +12,80 @@ /** * @brief A valid starting packet ID per MQTT spec. Start from 1. */ -#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) +#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) /** * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /** * @brief A packet type not handled by MQTT_ProcessLoop. */ -#define MQTT_PACKET_TYPE_INVALID ( 0U ) +#define MQTT_PACKET_TYPE_INVALID ( 0U ) /** * @brief Number of milliseconds in a second. */ -#define MQTT_ONE_SECOND_TO_MS ( 1000U ) +#define MQTT_ONE_SECOND_TO_MS ( 1000U ) /** * @brief Length of the MQTT network buffer. */ -#define MQTT_TEST_BUFFER_LENGTH ( 128 ) +#define MQTT_TEST_BUFFER_LENGTH ( 128 ) /** * @brief Sample keep-alive interval that should be greater than 0. */ -#define MQTT_SAMPLE_KEEPALIVE_INTERVAL_S ( 1U ) +#define MQTT_SAMPLE_KEEPALIVE_INTERVAL_S ( 1U ) /** * @brief Length of time spent for single test case with * multiple iterations spent in the process loop for coverage. */ -#define MQTT_SAMPLE_TIMEOUT_MS ( 1U ) +#define MQTT_SAMPLE_PROCESS_LOOP_TIMEOUT_MS ( 1U ) /** * @brief Zero timeout in the process loop implies one iteration. */ -#define MQTT_NO_TIMEOUT_MS ( 0U ) +#define MQTT_NO_TIMEOUT_MS ( 0U ) /** * @brief Sample length of remaining serialized data. */ -#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) +#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) /** * @brief Subtract this value from max value of global entry time * for the timer overflow test. */ -#define MQTT_OVERFLOW_OFFSET ( 3 ) +#define MQTT_OVERFLOW_OFFSET ( 3 ) /** * @brief Subtract this value from max value of global entry time * for the timer overflow test. */ -#define MQTT_TIMER_CALLS_PER_ITERATION ( 4 ) +#define MQTT_TIMER_CALLS_PER_ITERATION ( 4 ) /** * @brief Timeout for the timer overflow test. */ -#define MQTT_TIMER_OVERFLOW_TIMEOUT_MS ( 10 ) +#define MQTT_TIMER_OVERFLOW_TIMEOUT_MS ( 10 ) /** * @brief A sample network context that we set to NULL. */ -#define MQTT_SAMPLE_NETWORK_CONTEXT ( 0 ) +#define MQTT_SAMPLE_NETWORK_CONTEXT ( NULL ) /** * @brief Sample topic filter to subscribe to. */ -#define MQTT_SAMPLE_TOPIC_FILTER "iot" +#define MQTT_SAMPLE_TOPIC_FILTER "iot" /** * @brief Length of sample topic filter. */ -#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH ( sizeof( MQTT_SAMPLE_TOPIC_FILTER ) - 1 ) +#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH ( sizeof( MQTT_SAMPLE_TOPIC_FILTER ) - 1 ) /** * @brief The packet type to be received by the process loop. @@ -123,6 +123,8 @@ static bool isEventCallbackInvoked = false; void setUp() { memset( ( void * ) mqttBuffer, 0x0, sizeof( mqttBuffer ) ); + MQTT_State_strerror_IgnoreAndReturn( "DUMMY_MQTT_STATE" ); + globalEntryTime = 0; } @@ -531,6 +533,7 @@ void test_MQTT_Init_Happy_Path( void ) setupCallbacks( &callbacks ); setupTransportInterface( &transport ); + setupNetworkBuffer( &networkBuffer ); mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); @@ -1589,6 +1592,7 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths( void ) setupTransportInterface( &transport ); setupCallbacks( &callbacks ); + setupNetworkBuffer( &networkBuffer ); mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); @@ -1749,7 +1753,7 @@ void test_MQTT_ProcessLoop_Receive_Failed( void ) TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); - mqttStatus = MQTT_ProcessLoop( &context, MQTT_SAMPLE_TIMEOUT_MS ); + mqttStatus = MQTT_ProcessLoop( &context, MQTT_SAMPLE_PROCESS_LOOP_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus ); } From 0044768357e6ad2b22a8a559e61cf22d87912377 Mon Sep 17 00:00:00 2001 From: Navin Soni <51832113+navinns@users.noreply.github.com> Date: Thu, 23 Jul 2020 16:58:01 -0700 Subject: [PATCH 596/844] Change targets to run individually and add c_flags support (#1072) * Change targets to run individually * Add c flags support * Change pattern to allow --- tools/ci/ci_agent.py | 263 +++++++++++++++++++++-------------- tools/ci/demo_config.yml | 22 ++- tools/ci/unittest_config.yml | 3 - 3 files changed, 174 insertions(+), 114 deletions(-) diff --git a/tools/ci/ci_agent.py b/tools/ci/ci_agent.py index 64ddbe5503..077c90ed83 100644 --- a/tools/ci/ci_agent.py +++ b/tools/ci/ci_agent.py @@ -9,89 +9,83 @@ import yaml +def log(message): + print(message) + + def cli_config_build(args): _file = Path(args.config_file) - _config = _read_config(_file) - _default_config = _config.get("_default", {}) - _build_flags = _default_config.get("build_flags", []) + config = _read_config(_file) + build_flags = args.build_flags or _get_flags(config, "build_flags") + c_flags = args.c_flags or _get_flags(config, "c_flags") - _del_dir(args.build_path) - _config_build(args.src, _build_flags, args.build_path) + _config_build(args.src, args.build_path, build_flags, c_flags) def cli_get_targets(args): - _targets = _get_targets(args.build_path) - print(" ".join(_targets)) + allow = args.allow or [""] + targets = _get_targets(args.build_path, allow) + log(" ".join(targets)) def cli_build_targets(args): _file = Path(args.config_file) - _config = _read_config(_file) - _default_config = _config.get("_default", {}) - _junit_filename = _default_config.get("name", None) - result = _build_targets(args.targets, _config, args.build_path) + config = _read_config(_file) + default_config = config.get("_default", {}) + junit_filename = default_config.get("name", None) + result = _build_targets(args.targets, args.src, args.build_path, config) - _log_save_result(result, f"build_{_junit_filename}.xml") + _log_save_result(result, f"build_{junit_filename}.xml") _check_status(result) def cli_run_targets(args): _file = Path(args.config_file) - _config = _read_config(_file) - _default_config = _config.get("_default", {}) - _junit_filename = _default_config.get("name", None) + config = _read_config(_file) + default_config = config.get("_default", {}) + junit_filename = default_config.get("name", None) - run_result = _run_targets( - args.targets, f"{args.build_path}/{_default_config.get('output_loc', '')}" - ) - _log_save_result(run_result, f"run_{_junit_filename}.xml") - _check_status(run_result) + result = _run_targets(args.targets, args.src, args.build_path, config) + _log_save_result(result, f"run_{junit_filename}.xml") + _check_status(result) def cli_build(args): _file = Path(args.config_file) - _config = _read_config(_file) - _default_config = _config.get("_default", {}) - _junit_filename = _default_config.get("name", None) - _build_flags = _default_config.get("build_flags", []) + config = _read_config(_file) + default_config = config.get("_default", {}) + junit_filename = default_config.get("name", None) - _del_dir(args.build_path) - _config_build(args.src, _build_flags, args.build_path) - _targets = _get_targets(args.build_path) + build_flags = _get_flags(config, "build_flags") + c_flags = _get_flags(config, "c_flags") + _config_build(args.src, args.build_path, build_flags, c_flags) - allowed = "|".join(_default_config.get("allow", [])) - _targets = [_target for _target in _targets if re.match(allowed, _target)] + allowed = args.allow or default_config.get("allow", [""]) + targets = args.targets or _get_targets(args.build_path, allowed) - build_result = _build_targets(_targets, _config, args.build_path) + build_result = _build_targets(targets, args.src, args.build_path, config) - _log_save_result(build_result, f"build_{_junit_filename}.xml") + _log_save_result(build_result, f"build_{junit_filename}.xml") _check_status(build_result) def cli_run(args): _file = Path(args.config_file) - _config = _read_config(_file) - _default_config = _config.get("_default", {}) - _junit_filename = _default_config.get("name", None) - _build_flags = _default_config.get("build_flags", []) - - _del_dir(args.build_path) - _config_build(args.src, _build_flags, args.build_path) - _targets = _get_targets(args.build_path) + config = _read_config(_file) + default_config = config.get("_default", {}) + junit_filename = default_config.get("name", None) - allowed = "|".join(_default_config.get("allow", [])) - _targets = [_target for _target in _targets if re.match(allowed, _target)] + build_flags = _get_flags(config, "build_flags") + c_flags = _get_flags(config, "c_flags") + _config_build(args.src, args.build_path, build_flags, c_flags) - build_result = _build_targets(_targets, _config, args.build_path) + allowed = args.allow or default_config.get("allow", [""]) + targets = args.targets or _get_targets(args.build_path, allowed) - _log_save_result(build_result, f"build_{_junit_filename}.xml") + result = _run_targets(targets, args.src, args.build_path, config) - run_result = _run_targets( - build_result.keys(), - f"{args.build_path}/{_default_config.get('output_loc', '')}", - ) - _log_save_result(run_result, f"run_{_junit_filename}.xml") - _check_status(run_result) + _log_save_result(result, f"run_{junit_filename}.xml") + _check_status(result) def cli_invoke(args_list): @@ -113,38 +107,57 @@ def add_arguments(cmd, *args): new_argument("--src", required=True, help="Path to C-SDK") new_argument("--config-file", default=".", help="Path to config file") new_argument("--build-path", required=True, help="Path to build location") + new_argument("--targets", nargs="+", required=True, help="Targets to build") new_argument( - "--build-args", default="", help="Arguments required for build configuration" + "--build-flags", nargs="+", help="Optional flags required to configure build", ) - new_argument("--targets", nargs="+", required=True, help="Targets to build") + new_argument( + "--c-flags", nargs="+", help="Optional c_flags required to configure build", + ) + new_argument("--allow", nargs="+", help="Pattern for target selection") parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() cmd_config_build = subparsers.add_parser("configure-build") add_arguments( - cmd_config_build, "--config-file", "--src", "--build-path", "--build-args" + cmd_config_build, + "--config-file", + "--src", + "--build-path", + "--build-flags", + "--c-flags", ) cmd_config_build.set_defaults(func="config-build") cmd_get_targets = subparsers.add_parser("get-targets") - add_argument(cmd_get_targets, "--build-path") + add_arguments(cmd_get_targets, "--build-path", "--allow") cmd_get_targets.set_defaults(func="get-targets") cmd_build_targets = subparsers.add_parser("build-targets") - add_arguments(cmd_build_targets, "--config-file", "--targets", "--build-path") + add_arguments( + cmd_build_targets, "--src", "--config-file", "--targets", "--build-path" + ) cmd_build_targets.set_defaults(func="build-targets") cmd_run_targets = subparsers.add_parser("run-targets") - add_arguments(cmd_run_targets, "--config-file", "--targets", "--build-path") + add_arguments( + cmd_run_targets, "--src", "--config-file", "--targets", "--build-path" + ) cmd_run_targets.set_defaults(func="run-targets") cmd_build = subparsers.add_parser("build") - add_arguments(cmd_build, "--config-file", "--src", "--build-path", "--build-args") + add_arguments( + cmd_build, "--config-file", "--src", "--build-path", "--build-flags", "--allow" + ) + add_argument(cmd_build, "--targets", required=False) cmd_build.set_defaults(func="build") cmd_run = subparsers.add_parser("run") - add_arguments(cmd_run, "--config-file", "--src", "--build-path", "--build-args") + add_arguments( + cmd_run, "--config-file", "--src", "--build-path", "--build-flags", "--allow" + ) + add_argument(cmd_run, "--targets", required=False) cmd_run.set_defaults(func="run") return parser @@ -170,71 +183,107 @@ def _cli_invoke_next(args_list, prefix="cli"): return func(*func_args) -def _config_build(_src, _build_flags, _build_path): - _build_flags = " ".join(_build_flags) - _run_cmd(f'cmake -S {_src} -B {_build_path} {_build_flags} -G "Unix Makefiles"') +def _config_build(src, build_path, build_flags, c_flags): + build_flags = " ".join(build_flags) + c_flags = " ".join(c_flags).replace("'", '"').replace('"', '\\"') + c_flags = f"-DCMAKE_C_FLAGS='{c_flags}'" + _del_dir(build_path) + _run_cmd( + f'cmake -S {src} -B {build_path} {build_flags} {c_flags} -G "Unix Makefiles"' + ) -def _build_target(_target, _c_flags, build_path): - print("\n----------------------------------------------------------------") - print(f"Building target: {_target}") - print("----------------------------------------------------------------") - _c_flags = " ".join(_c_flags) - cmd = f"make -C {build_path} {_c_flags} {_target}" - return _run_cmd(cmd) +def _get_flags(config, flag_type, target="all"): + targets = config.keys() if target == "all" else ["_default", target] + + flags = [] + for _target in targets: + flags += config.get(_target, {}).get(flag_type, []) + return flags + + +def _get_targets(build_path, allow): + targets = _run_cmd(f"make help -C {build_path} | tr -d '. '") + targets = [t.strip() for t in targets.split()] + allow = "|".join(allow) + targets = [target for target in targets if re.search(allow, target)] + return targets + +def _build_target(target, src, build_path, build_flags, c_flags): + log("\n----------------------------------------------------------------") + log(f"Building target: {target}") + log("----------------------------------------------------------------") -def _get_targets(build_path): - _targets = _run_cmd(f"make help -C {build_path} | tr -d '. '") - _targets = [_t.strip() for _t in _targets.split()] - return _targets + _config_build(src, build_path, build_flags, c_flags) + + cmd = f"make -C {build_path} {target}" + return _run_cmd(cmd) -def _build_targets(_targets, _config, build_path): +def _build_targets(targets, src, build_path, config): result = {} - _default_config = _config.get("_default", {}) - for _target in _targets: - _target_result = result.setdefault(_target, {}) - _target_build_result = _target_result.setdefault("Build", {}) + for target in targets: + target_result = result.setdefault(target, {}) + target_build_result = target_result.setdefault("Build", {}) try: - _c_flags = _default_config.get("c_flags", []) + _config.get( - _target, {} - ).get("c_flags", []) - out = _build_target(_target, _c_flags, build_path) - print(out) - _target_build_result["status"] = "PASS" + build_flags = _get_flags(config, "build_flags", target) + c_flags = _get_flags(config, "c_flags", target) + out = _build_target(target, src, build_path, build_flags, c_flags) + log(out) + target_build_result["status"] = "PASS" except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: - print(err.stdout) - _target_build_result["status"] = "FAIL" - _target_build_result["details"] = "Build Failure" + log(err.stdout) + target_build_result["status"] = "FAIL" + target_build_result["details"] = "Build Failure" return result -def _run_targets(_targets, _path): +def _run_target(target, run_path): + log("\n----------------------------------------------------------------") + log(f"Running Target: {target}") + log("----------------------------------------------------------------") + return _run_cmd(f"cd {run_path} && ./{target}") + + +def _run_targets(targets, src, build_path, config): result = {} - for _target in _targets: - _target_result = result.setdefault(_target, {}) - _target_run_result = _target_result.setdefault("Run", {}) - print("\n----------------------------------------------------------------") - print(f"Running Target: {_target}") - print("----------------------------------------------------------------") + default_config = config.get("_default", {}) + for target in targets: + target_result = result.setdefault(target, {}) + + target_build_result = target_result.setdefault("Build", {}) + try: + build_flags = _get_flags(config, "build_flags", target) + c_flags = _get_flags(config, "c_flags", target) + out = _build_target(target, src, build_path, build_flags, c_flags) + log(out) + target_build_result["status"] = "PASS" + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + log(err.stdout) + target_build_result["status"] = "FAIL" + target_build_result["details"] = "Build Failure" + + target_run_result = target_result.setdefault("Run", {}) + run_path = f'{build_path}/{default_config.get("output_loc", "")}' try: - print(_run_cmd(f"{_path}/{_target}")) - _target_run_result["status"] = "PASS" + out = _run_target(target, run_path) + log(out) + target_run_result["status"] = "PASS" except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: - print(err.stdout) - _target_run_result["status"] = "FAIL" - _target_run_result["details"] = "Run Failure" + log(err.stdout) + target_run_result["status"] = "FAIL" + target_run_result["details"] = "Run Failure" return result def _del_dir(dir_name): try: - print(f"Deleting dir: {dir_name}") + log(f"Deleting dir: {dir_name}") shutil.rmtree(dir_name) except OSError as err: - print("Error: %s - %s." % (err.filename, err.strerror)) + log("Error: %s - %s." % (err.filename, err.strerror)) def _read_config(_path): @@ -242,12 +291,12 @@ def _read_config(_path): _config = yaml.load(_path.read_text()) return _config except yaml.YAMLError: - print(f"Error: Unable to load file {_path.name}") + log(f"Error: Unable to load file {_path.name}") sys.exit(1) def _run_cmd(cmd): - print(f"Executing command: {cmd}") + log(f"Executing command: {cmd}") result = subprocess.run( cmd, stdout=subprocess.PIPE, @@ -255,7 +304,7 @@ def _run_cmd(cmd): shell=True, encoding="utf-8", check=True, - timeout=10, + timeout=180, ) return result.stdout @@ -274,10 +323,10 @@ def _check_status(result): def _log_save_result(result, filename): - print("\n----------------------------------------------------------------") - print(f"{filename.split('_')[0].title()} Result") - print("----------------------------------------------------------------") - print(yaml.dump(result)) + log("\n----------------------------------------------------------------") + log(f"{filename.split('_')[0].title()} Result") + log("----------------------------------------------------------------") + log(yaml.dump(result)) if filename: _write_junit(_to_junit(result, "linux.cmake"), filename) @@ -312,7 +361,7 @@ def _write_junit(junit_report, file_name): try: junit_report.write(file_name, pretty=True) except Exception as err: - print(f"[ERROR] Unable to write junit_file: {str(err)}") + log(f"[ERROR] Unable to write junit_file: {str(err)}") if __name__ == "__main__": diff --git a/tools/ci/demo_config.yml b/tools/ci/demo_config.yml index fbc3a0160a..37cba9aa4f 100644 --- a/tools/ci/demo_config.yml +++ b/tools/ci/demo_config.yml @@ -6,12 +6,14 @@ _default: build_flags: # Provide list of flags required for configuring build using cmake - "" + c_flags: + # Provide list of c_flags common to all target + - -Wall + - -Wextra + - -Werror allow: # Provide list of allowed targets [Accepts regex] - - ".*_demo_.*" - c_flags: - # Provide list of c_flags common for all targets - - "-Wall -Wextra -Werror" + - "_demo_" output_loc: "bin" @@ -21,3 +23,15 @@ _default: #- #- #- + +mqtt_demo_mutual_auth: + c_flags: + - -DAWS_IOT_ENDPOINT='aws-iot-endpoint' + - -DCLIENT_CERT_PATH="certificate-path" + - -DCLIENT_PRIVATE_KEY_PATH="private-key-path" + +http_demo_mutual_auth: + c_flags: + - -DAWS_IOT_ENDPOINT="aws-iot-endpoint" + - -DCLIENT_CERT_PATH="certificate-path" + - -DCLIENT_PRIVATE_KEY_PATH="private-key-path" diff --git a/tools/ci/unittest_config.yml b/tools/ci/unittest_config.yml index a8bae1eb64..181cabf690 100644 --- a/tools/ci/unittest_config.yml +++ b/tools/ci/unittest_config.yml @@ -9,9 +9,6 @@ _default: allow: # Provide list of allowed targets [Accepts regex] - ".*utest.*" - c_flags: - # Provide list of c_flags common for all targets - - "-Wall -Wextra -Werror" output_loc: "bin/tests" From 771025cfc4cb1a5a65376ce1e749e23f33bce6d7 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 23 Jul 2020 17:28:32 -0700 Subject: [PATCH 597/844] Update mqtt.c send and recv functions to assert on more bytes than expected. (#1073) Updated missed pFixedBuffer variable name changes. Add periods in logs where missing. Add docs to the user callbacks. Add unit tests where coverage is missing. Address missing MISRA flags. --- libraries/standard/http/src/http_client.c | 27 ++-- .../standard/http/utest/http_send_utest.c | 63 ---------- .../mqtt/cbmc/stubs/network_interface_stubs.c | 1 + libraries/standard/mqtt/include/mqtt.h | 15 +++ libraries/standard/mqtt/lexicon.txt | 2 + libraries/standard/mqtt/src/mqtt.c | 118 ++++++++++-------- .../standard/mqtt/src/mqtt_lightweight.c | 52 ++++---- libraries/standard/mqtt/src/mqtt_state.c | 2 +- libraries/standard/mqtt/utest/mqtt_utest.c | 28 +++-- 9 files changed, 142 insertions(+), 166 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 576ae65d09..498fdc99f2 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1594,6 +1594,10 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport, pIndex, bytesRemaining ); + /* It is a bug in the application's transport send implementation if + * more bytes than expected are sent. */ + assert( transportStatus <= (int32_t)bytesRemaining ); + /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { @@ -1602,14 +1606,6 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport, transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } - else if( ( size_t ) transportStatus > bytesRemaining ) - { - LogError( ( "Failed to send HTTP data: Transport send() wrote more data " - "than what was expected: BytesSent=%d, BytesRemaining=%lu", - transportStatus, - bytesRemaining ) ); - returnStatus = HTTP_NETWORK_ERROR; - } else { bytesRemaining -= ( size_t ) transportStatus; @@ -1738,6 +1734,10 @@ static HTTPStatus_t receiveHttpData( const TransportInterface_t * pTransport, pBuffer, bufferLen ); + /* It is a bug in the application's transport receive implementation if + * more bytes than expected are received. */ + assert( transportStatus <= (int32_t)bufferLen ); + /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { @@ -1746,17 +1746,6 @@ static HTTPStatus_t receiveHttpData( const TransportInterface_t * pTransport, transportStatus ) ); returnStatus = HTTP_NETWORK_ERROR; } - else if( ( size_t ) transportStatus > bufferLen ) - { - /* There is a bug in the transport recv if more bytes are reported - * to have been read than the bytes asked for. */ - LogError( ( "Failed to receive HTTP data: Transport recv() " - " read more bytes than requested: BytesReceived=%d, " - "BytesRequested=%lu", - transportStatus, - ( unsigned long ) bufferLen ) ); - returnStatus = HTTP_NETWORK_ERROR; - } else if( transportStatus > 0 ) { /* Some or all of the specified data was received. */ diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index e10dbbbcb6..482804565a 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -303,18 +303,6 @@ static int32_t transportSendLessThanBytesToWrite( NetworkContext_t * pNetworkCon return retVal; } -/* Application transport send that writes more bytes than expected. */ -static int32_t transportSendMoreThanBytesToWrite( NetworkContext_t * pNetworkContext, - const void * pBuffer, - size_t bytesToWrite ) -{ - ( void ) pNetworkContext; - ( void ) pBuffer; - - return( bytesToWrite + 1 ); -} - - /* Application transport receive interface that sends the bytes specified in * firstPartBytes on the first call, then sends the rest of the response in the * second call. The response to send is set in pNetworkData and the current @@ -369,17 +357,6 @@ static int32_t transportRecvNetworkError( NetworkContext_t * pNetworkContext, return -1; } -/* Application transport receive that returns more bytes read than expected. */ -static int32_t transportRecvMoreThanBytesToRead( NetworkContext_t * pNetworkContext, - void * pBuffer, - size_t bytesToRead ) -{ - ( void ) pNetworkContext; - ( void ) pBuffer; - - return( bytesToRead + 1 ); -} - /* Mocked http_parser_execute callback that sets the internal http_errno. */ static size_t http_parser_execute_error( http_parser * pParser, const http_parser_settings * pSettings, @@ -1262,46 +1239,6 @@ void test_HTTPClient_Send_network_error_response( void ) /*-----------------------------------------------------------*/ -/* Test when more bytes are received than expected, when receiving a response - * from the network. */ -void test_HTTPClient_Send_recv_too_many_bytes( void ) -{ - HTTPStatus_t returnStatus = HTTP_SUCCESS; - - http_parser_init_Ignore(); - - transportInterface.recv = transportRecvMoreThanBytesToRead; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response, - 0 ); - TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); -} - -/*-----------------------------------------------------------*/ - -/* Test when more bytes are sent than expected, when sending data - * over the socket. */ -void test_HTTPClient_Send_send_too_many_bytes( void ) -{ - HTTPStatus_t returnStatus = HTTP_SUCCESS; - - http_parser_init_Ignore(); - - transportInterface.send = transportSendMoreThanBytesToWrite; - returnStatus = HTTPClient_Send( &transportInterface, - &requestHeaders, - NULL, - 0, - &response, - 0 ); - TEST_ASSERT_EQUAL( HTTP_NETWORK_ERROR, returnStatus ); -} - -/*-----------------------------------------------------------*/ - /* Test a NULL transport interface passed to the API. */ void test_HTTPClient_Send_null_transport_interface( void ) { diff --git a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c index 30e6188695..9114025e6a 100644 --- a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +++ b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c @@ -38,6 +38,7 @@ int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, * int32_t value returned from the application defined network receive * implementation. */ int32_t bytesOrError; + __CPROVER_assume( bytesOrError <= bytesToRecv ); return bytesOrError; } diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 8db419707c..e92cdd3e82 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -44,8 +44,23 @@ typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; struct MQTTContext; typedef struct MQTTContext MQTTContext_t; +/** + * @brief Application provided callback to retrieve the current time in + * milliseconds. + * + * @return The current time in milliseconds. + */ typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); +/** + * @brief Application callback for receiving incoming publishes and incoming + * acks. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] pPacketInfo Information on the type of incoming MQTT packet. + * @param[in] packetIdentifier Packet identifier of incoming PUBLISH packet. + * @param[in] pPublishInfo Incoming PUBLISH packet parameters. + */ typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index b3ccee2500..e342e39439 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -7,6 +7,7 @@ app aws bool bufferlength +bytesorerror bytesreceived bytestoread bytestoreceive @@ -110,6 +111,7 @@ mqttsuccess msb networkbuffer networkcontext +networkinterfacesendstub newstate nextpacketid noninfringement diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index de53ade3b8..5677f04755 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -41,11 +41,6 @@ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U ) #endif -/** - * @brief A return code indicating an error from the transport interface. - */ -#define TRANSPORT_ERROR ( -1 ) - /*-----------------------------------------------------------*/ /** @@ -55,7 +50,7 @@ * @brief param[in] pBufferToSend Buffer to be sent to network. * @brief param[in] bytesToSend Number of bytes to be sent. * - * @return Total number of bytes sent; -1 if there is an error. + * @return Total number of bytes sent, or negative number on network error. */ static int32_t sendPacket( MQTTContext_t * pContext, const uint8_t * pBufferToSend, @@ -316,6 +311,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, size_t bytesRemaining = bytesToSend; int32_t totalBytesSent = 0, bytesSent; uint32_t sendTime = 0U; + bool sendError = false; assert( pContext != NULL ); assert( pContext->callbacks.getTime != NULL ); @@ -326,33 +322,37 @@ static int32_t sendPacket( MQTTContext_t * pContext, sendTime = pContext->callbacks.getTime(); /* Loop until the entire packet is sent. */ - while( bytesRemaining > 0UL ) + while( ( bytesRemaining > 0UL ) && ( sendError == false ) ) { bytesSent = pContext->transportInterface.send( pContext->transportInterface.pNetworkContext, pIndex, bytesRemaining ); - if( bytesSent > 0 ) + /* It is a bug in the application's transport send implementation if + * more bytes than expected are sent. */ + assert( bytesSent <= ( int32_t ) bytesRemaining ); + + if( bytesSent <= 0 ) + { + LogError( ( "Transport send failed. Error code=%d.", bytesSent ) ); + totalBytesSent = bytesSent; + sendError = true; + } + else { bytesRemaining -= ( size_t ) bytesSent; totalBytesSent += bytesSent; pIndex += bytesSent; - LogDebug( ( "Bytes sent=%d, bytes remaining=%lu," - "total bytes sent=%d.", + LogDebug( ( "BytesSent=%d, BytesRemaining=%lu," + " TotalBytesSent=%d.", bytesSent, bytesRemaining, totalBytesSent ) ); } - else - { - LogError( ( "Transport send failed. Error code=%d.", bytesSent ) ); - totalBytesSent = TRANSPORT_ERROR; - break; - } } /* Update time of last transmission if the entire packet is successfully sent. */ - if( totalBytesSent > 0 ) + if( bytesRemaining == 0U ) { pContext->lastPacketTime = sendTime; LogDebug( ( "Successfully sent packet at time %u.", @@ -433,19 +433,28 @@ static int32_t recvExact( const MQTTContext_t * pContext, pIndex, bytesRemaining ); - if( bytesRecvd >= 0 ) - { - bytesRemaining -= ( size_t ) bytesRecvd; - totalBytesRecvd += ( int32_t ) bytesRecvd; - pIndex += bytesRecvd; - } - else + /* It is a bug in the application's transport receive implementation if + * more bytes than expected are received. */ + assert( bytesRecvd <= ( int32_t ) bytesRemaining ); + + if( bytesRecvd < 0 ) { - LogError( ( "Network error while receiving packet: ReturnCode=%d", + LogError( ( "Network error while receiving packet: ReturnCode=%d.", bytesRecvd ) ); totalBytesRecvd = bytesRecvd; receiveError = true; } + else + { + bytesRemaining -= ( size_t ) bytesRecvd; + totalBytesRecvd += ( int32_t ) bytesRecvd; + pIndex += bytesRecvd; + LogDebug( ( "BytesReceived=%d, BytesRemaining=%lu, " + "TotalBytesReceived=%d.", + bytesRecvd, + bytesRemaining, + totalBytesRecvd ) ); + } elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); @@ -538,12 +547,13 @@ static MQTTStatus_t receivePacket( const MQTTContext_t * pContext, size_t bytesToReceive = 0U; assert( pContext != NULL ); + assert( pContext->networkBuffer.pBuffer != NULL ); if( incomingPacket.remainingLength > pContext->networkBuffer.size ) { LogError( ( "Incoming packet will be dumped: " "Packet length exceeds network buffer size." - "PacketSize=%lu, NetworkBufferSize=%lu", + "PacketSize=%lu, NetworkBufferSize=%lu.", incomingPacket.remainingLength, pContext->networkBuffer.size ) ); status = discardPacket( pContext, @@ -714,7 +724,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, assert( pIncomingPacket != NULL ); status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo ); - LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%d", status ) ); + LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%d.", status ) ); if( status == MQTTSuccess ) { @@ -937,6 +947,7 @@ static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext, MQTTPacketInfo_t incomingPacket; assert( pContext != NULL ); + assert( pContext->networkBuffer.pBuffer != NULL ); status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv, pContext->transportInterface.pNetworkContext, @@ -1051,7 +1062,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, headerSize ); - if( bytesSent < 0 ) + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != headerSize ) ) { LogError( ( "Transport send failed for PUBLISH header." ) ); status = MQTTSendFailed; @@ -1066,7 +1077,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, pPublishInfo->pPayload, pPublishInfo->payloadLength ); - if( bytesSent < 0 ) + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != pPublishInfo->payloadLength ) ) { LogError( ( "Transport send failed for PUBLISH payload." ) ); status = MQTTSendFailed; @@ -1398,7 +1409,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, packetSize ); - if( bytesSent < 0 ) + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) { LogError( ( "Transport send failed for CONNECT packet." ) ); status = MQTTSendFailed; @@ -1486,7 +1497,7 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, packetSize ); - if( bytesSent < 0 ) + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) { LogError( ( "Transport send failed for SUBSCRIBE packet." ) ); status = MQTTSendFailed; @@ -1618,7 +1629,8 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ) pContext->networkBuffer.pBuffer, packetSize ); - if( bytesSent < 0 ) + /* It is an error to not send the entire PINGREQ packet. */ + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) { LogError( ( "Transport send failed for PINGREQ packet." ) ); status = MQTTSendFailed; @@ -1680,7 +1692,7 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, packetSize ); - if( bytesSent < 0 ) + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) { LogError( ( "Transport send failed for UNSUBSCRIBE packet." ) ); status = MQTTSendFailed; @@ -1730,7 +1742,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ) pContext->networkBuffer.pBuffer, packetSize ); - if( bytesSent < 0 ) + if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) { LogError( ( "Transport send failed for DISCONNECT packet." ) ); status = MQTTSendFailed; @@ -1760,20 +1772,24 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; - if( ( pContext != NULL ) && ( pContext->callbacks.getTime != NULL ) ) + if( pContext == NULL ) { - getTimeStampMs = pContext->callbacks.getTime; - entryTimeMs = getTimeStampMs(); - status = MQTTSuccess; - pContext->controlPacketSent = false; + LogError( ( "MQTT Context cannot be NULL." ) ); } - else if( pContext == NULL ) + else if( pContext->callbacks.getTime == NULL ) { - LogError( ( "MQTT Context cannot be NULL." ) ); + LogError( ( "MQTT Context must set callbacks.getTime." ) ); + } + else if( pContext->networkBuffer.pBuffer == NULL ) + { + LogError( ( "The MQTT context's networkBuffer must not be NULL." ) ); } else { - LogError( ( "MQTT Context must set callbacks.getTime." ) ); + getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); + pContext->controlPacketSent = false; + status = MQTTSuccess; } while( status == MQTTSuccess ) @@ -1814,19 +1830,23 @@ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; - if( ( pContext != NULL ) && ( pContext->callbacks.getTime != NULL ) ) + if( pContext == NULL ) { - getTimeStampMs = pContext->callbacks.getTime; - entryTimeMs = getTimeStampMs(); - status = MQTTSuccess; + LogError( ( "MQTT Context cannot be NULL." ) ); } - else if( pContext == NULL ) + else if( pContext->callbacks.getTime == NULL ) { - LogError( ( "MQTT Context cannot be NULL." ) ); + LogError( ( "MQTT Context must set callbacks.getTime." ) ); + } + else if( pContext->networkBuffer.pBuffer == NULL ) + { + LogError( ( "The MQTT context's networkBuffer must not be NULL." ) ); } else { - LogError( ( "MQTT Context must set callbacks.getTime." ) ); + getTimeStampMs = pContext->callbacks.getTime; + entryTimeMs = getTimeStampMs(); + status = MQTTSuccess; } while( status == MQTTSuccess ) diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index a11f98f580..7ea35e3e4c 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -211,7 +211,7 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] packetId Packet identifier. * @param[in] remainingLength Remaining length of the packet. - * @param[in] pBuffer Buffer for packet serialization. + * @param[in] pFixedBuffer Buffer for packet serialization. * * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; @@ -221,7 +221,7 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ); + const MQTTFixedBuffer_t * pFixedBuffer ); /** * @brief Serialize an MQTT CONNECT packet in the given buffer. @@ -229,12 +229,12 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo * @param[in] pConnectInfo MQTT CONNECT packet parameters. * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used. * @param[in] remainingLength Remaining Length of MQTT CONNECT packet. - * @param[out] pBuffer Buffer for packet serialization. + * @param[out] pFixedBuffer Buffer for packet serialization. */ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ); + const MQTTFixedBuffer_t * pFixedBuffer ); /** * Prints the appropriate message for the CONNACK response code if logs are @@ -496,7 +496,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, /* Packet Id should be non zero for QoS1 and QoS2. */ assert( ( pPublishInfo->qos == MQTTQoS0 ) || ( packetIdentifier != 0U ) ); /* Duplicate flag should be set only for Qos1 or Qos2. */ - assert( !( pPublishInfo->dup ) || ( pPublishInfo->qos != MQTTQoS0 ) ); + assert( ( pPublishInfo->dup != true ) || ( pPublishInfo->qos != MQTTQoS0 ) ); /* Get the start address of the buffer. */ pIndex = pFixedBuffer->pBuffer; @@ -655,7 +655,7 @@ static bool incomingPacketValid( uint8_t packetType ) /* Any other packet type is invalid. */ default: - LogWarn( ( "Incoming packet invalid: Packet type=%u", + LogWarn( ( "Incoming packet invalid: Packet type=%u.", packetType ) ); break; } @@ -1230,16 +1230,16 @@ static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp ) static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ) + const MQTTFixedBuffer_t * pFixedBuffer ) { uint8_t connectFlags = 0U; uint8_t * pIndex = NULL; assert( pConnectInfo != NULL ); - assert( pBuffer != NULL ); - assert( pBuffer->pBuffer != NULL ); + assert( pFixedBuffer != NULL ); + assert( pFixedBuffer->pBuffer != NULL ); - pIndex = pBuffer->pBuffer; + pIndex = pFixedBuffer->pBuffer; /* The first byte in the CONNECT packet is the control packet type. */ *pIndex = MQTT_PACKET_TYPE_CONNECT; pIndex++; @@ -1337,11 +1337,11 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, } LogDebug( ( "Length of serialized CONNECT packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ) ); + ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); /* Ensure that the difference between the end and beginning of the buffer * is less than the buffer size. */ - assert( ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) <= pBuffer->size ); + assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size ); } /*-----------------------------------------------------------*/ @@ -1381,7 +1381,7 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, * By bounding the payloadLength of the will message, the CONNECT * packet will never be larger than 327699 bytes. */ LogError( ( "The Will Message length must not exceed %d. " - "pWillInfo->payloadLength=%lu", + "pWillInfo->payloadLength=%lu.", UINT16_MAX, pWillInfo->payloadLength ) ); status = MQTTBadParameter; @@ -1553,7 +1553,7 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ) + const MQTTFixedBuffer_t * pFixedBuffer ) { size_t i = 0; uint8_t * pIndex = NULL; @@ -1564,11 +1564,11 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL subscriptionCount, packetId, remainingLength, - pBuffer ); + pFixedBuffer ); if( status == MQTTSuccess ) { - pIndex = pBuffer->pBuffer; + pIndex = pFixedBuffer->pBuffer; /* The first byte in SUBSCRIBE is the packet type. */ *pIndex = MQTT_PACKET_TYPE_SUBSCRIBE; @@ -1595,7 +1595,7 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL } LogDebug( ( "Length of serialized SUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ) ); + ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); } return status; @@ -1652,7 +1652,7 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio size_t subscriptionCount, uint16_t packetId, size_t remainingLength, - const MQTTFixedBuffer_t * pBuffer ) + const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; size_t i = 0; @@ -1663,12 +1663,12 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio subscriptionCount, packetId, remainingLength, - pBuffer ); + pFixedBuffer ); if( status == MQTTSuccess ) { /* Get the start of the buffer to the iterator variable. */ - pIndex = pBuffer->pBuffer; + pIndex = pFixedBuffer->pBuffer; /* The first byte in UNSUBSCRIBE is the packet type. */ *pIndex = MQTT_PACKET_TYPE_UNSUBSCRIBE; @@ -1691,7 +1691,7 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio } LogDebug( ( "Length of serialized UNSUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pBuffer->pBuffer ) ) ) ); + ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); } return status; @@ -1770,7 +1770,7 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, /* For serializing a publish, if there exists a payload, then the buffer * cannot be NULL. */ - else if( ( pPublishInfo->payloadLength > 0 ) && ( pPublishInfo->pPayload == NULL ) ) + else if( ( pPublishInfo->payloadLength > 0U ) && ( pPublishInfo->pPayload == NULL ) ) { LogError( ( "A nonzero payload length requires a non-NULL payload: " "payloadLength=%lu, pPayload=%p.", @@ -1792,7 +1792,7 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, pPublishInfo->qos ) ); status = MQTTBadParameter; } - else if( ( pPublishInfo->dup ) && ( pPublishInfo->qos == MQTTQoS0 ) ) + else if( ( pPublishInfo->dup == true ) && ( pPublishInfo->qos == MQTTQoS0 ) ) { LogError( ( "Duplicate flag is set for PUBLISH with Qos 0," ) ); status = MQTTBadParameter; @@ -1867,7 +1867,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo pPublishInfo->qos ) ); status = MQTTBadParameter; } - else if( ( pPublishInfo->dup ) && ( pPublishInfo->qos == MQTTQoS0 ) ) + else if( ( pPublishInfo->dup == true ) && ( pPublishInfo->qos == MQTTQoS0 ) ) { LogError( ( "Duplicate flag is set for PUBLISH with Qos 0," ) ); status = MQTTBadParameter; @@ -2227,7 +2227,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, } else { - LogError( ( "Incoming packet invalid: Packet type=%u", + LogError( ( "Incoming packet invalid: Packet type=%u.", pIncomingPacket->type ) ); status = MQTTBadResponse; } @@ -2243,7 +2243,7 @@ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, else if( status != MQTTBadParameter ) { LogError( ( "A single byte was not read from the transport: " - "transportStatus=%d", + "transportStatus=%d.", bytesReceived ) ); status = MQTTRecvFailed; } diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index 42fbda686c..37b23e0944 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -525,7 +525,7 @@ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, if( records[ index ].packetId == packetId ) { /* Collision. */ - LogError( ( "Collision when adding PacketID=%u at index=%u", + LogError( ( "Collision when adding PacketID=%u at index=%u.", packetId, index ) ); diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 1e26238f40..1a03e50e98 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -196,6 +196,7 @@ static void eventCallback( MQTTContext_t * pContext, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ) { + ( void ) pContext; ( void ) pPacketInfo; ( void ) packetIdentifier; ( void ) pPublishInfo; @@ -317,6 +318,7 @@ static int32_t transportRecvOneByte( NetworkContext_t * pNetworkContext, { ( void ) pNetworkContext; ( void ) pBuffer; + ( void ) bytesToRead; return 1; } @@ -602,14 +604,12 @@ void test_MQTT_Connect_sendConnect( void ) { MQTTContext_t mqttContext; MQTTConnectInfo_t connectInfo; - MQTTPublishInfo_t willInfo; uint32_t timeout = 2; bool sessionPresent; MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; - MQTTPacketInfo_t incomingPacket; size_t remainingLength, packetSize; setupTransportInterface( &transport ); @@ -1031,7 +1031,6 @@ void test_MQTT_Connect_happy_path() MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; - size_t remainingLength, packetSize; setupTransportInterface( &transport ); setupCallbacks( &callbacks ); @@ -1139,7 +1138,6 @@ void test_MQTT_Publish( void ) { MQTTContext_t mqttContext; MQTTPublishInfo_t publishInfo; - uint16_t packetId; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; @@ -1288,7 +1286,7 @@ void test_MQTT_Disconnect( void ) status = MQTT_Disconnect( NULL ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); - /* Send failure. */ + /* Send failure with network error. */ MQTT_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetDisconnectPacketSize_ReturnThruPtr_pPacketSize( &disconnectSize ); MQTT_SerializeDisconnect_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -1365,6 +1363,13 @@ void test_MQTT_ProcessLoop_Invalid_Params( void ) context.callbacks.getTime = NULL; mqttStatus = MQTT_ProcessLoop( &context, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + /* Restore the time function for the next test. */ + context.callbacks.getTime = getTime; + + /* The fixed network buffer cannot be NULL. */ + context.networkBuffer.pBuffer = NULL; + mqttStatus = MQTT_ProcessLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); } /** @@ -1830,15 +1835,22 @@ void test_MQTT_ReceiveLoop( void ) mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); - /* NULL Context. */ + /* Verify that a NULL Context returns an error. */ mqttStatus = MQTT_ReceiveLoop( NULL, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + /* Verify that a NULL time function returns an error. */ context.callbacks.getTime = NULL; mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); context.callbacks.getTime = getTime; + /* Verify that a null fixed network buffer returns an error. */ + context.networkBuffer.pBuffer = NULL; + mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); + TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); + setupNetworkBuffer( &( context.networkBuffer ) ); + /* Error case, for branch coverage. */ MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); @@ -1910,7 +1922,6 @@ void test_MQTT_Subscribe_happy_path( void ) TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTApplicationCallbacks_t callbacks; - MQTTPacketInfo_t incomingPacket = { 0 }; MQTTSubscribeInfo_t subscribeInfo; size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; @@ -2134,6 +2145,8 @@ void test_MQTT_Ping_error_path( void ) setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); + /* Test a network error is returned from sending the PING packet over the + * transport send . */ transport.send = transportSendFailure; transport.recv = transportRecvFailure; @@ -2148,7 +2161,6 @@ void test_MQTT_Ping_error_path( void ) mqttStatus = MQTT_Ping( &context ); TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus ); - /* Initialize context. */ mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); From a3ae9a296258e9298730aa2b597894bf4b42b422 Mon Sep 17 00:00:00 2001 From: Navin Soni <51832113+navinns@users.noreply.github.com> Date: Thu, 23 Jul 2020 18:19:01 -0700 Subject: [PATCH 598/844] Add cflags and enable release build (#1075) Co-authored-by: Archit Aggarwal --- tools/ci/demo_config.yml | 4 ++-- tools/ci/unittest_config.yml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/ci/demo_config.yml b/tools/ci/demo_config.yml index 37cba9aa4f..407b2721a2 100644 --- a/tools/ci/demo_config.yml +++ b/tools/ci/demo_config.yml @@ -5,12 +5,12 @@ _default: name: demos build_flags: # Provide list of flags required for configuring build using cmake - - "" + - "-DCMAKE_BUILD_TYPE=Release" c_flags: # Provide list of c_flags common to all target - -Wall - -Wextra - - -Werror + #- -Werror allow: # Provide list of allowed targets [Accepts regex] - "_demo_" diff --git a/tools/ci/unittest_config.yml b/tools/ci/unittest_config.yml index 181cabf690..1ea6bfbf0e 100644 --- a/tools/ci/unittest_config.yml +++ b/tools/ci/unittest_config.yml @@ -6,6 +6,12 @@ _default: build_flags: # Provide list of flags required for configuring build using cmake - "-DBUILD_TESTS=1" + - "-DCMAKE_BUILD_TYPE=Release" + c_flags: + # Provide list of c_flags common to all target + - -Wall + - -Wextra + #- -Werror allow: # Provide list of allowed targets [Accepts regex] - ".*utest.*" From 93e6d871d1a2ef9b72a0260d7d9181b88ebce16f Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 24 Jul 2020 12:01:55 -0700 Subject: [PATCH 599/844] Exclude mutual auth demo from default targets if no credentials provided (#1074) * Exclude mutual auth demo from default targets if no credentials provided * Fix log message for check_aws_credentials Co-authored-by: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> --- demos/CMakeLists.txt | 35 +++++++++++++++---- .../http/http_demo_mutual_auth/CMakeLists.txt | 21 ++--------- .../mqtt/mqtt_demo_mutual_auth/CMakeLists.txt | 21 ++--------- 3 files changed, 34 insertions(+), 43 deletions(-) diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 041f108349..433295cd9a 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -7,6 +7,34 @@ if( NOT DEFINED PLATFORM_NAME ) endif() endif() +# This function will add a demo target to the list of default targets +# if all AWS Credentials are defined. +function(check_aws_credentials demo_name) + if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) + target_compile_definitions( + ${demo_name} PRIVATE + AWS_IOT_ENDPOINT="${AWS_IOT_ENDPOINT}" + CLIENT_CERT_PATH="${CLIENT_CERT_PATH}" + CLIENT_PRIVATE_KEY_PATH="${CLIENT_PRIVATE_KEY_PATH}" + ) + set(CMAKE_REQUIRED_DEFINITIONS -DAWS_IOT_ENDPOINT -DCLIENT_CERT_PATH -DCLIENT_PRIVATE_KEY_PATH) + endif() + + set(FILES_TO_CHECK "demo_config.h") + list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_LIST_DIR};${LOGGING_INCLUDE_DIRS}") + unset(HAVE_AWS_ENDPOINT CACHE) + unset(HAVE_CLIENT_CERT CACHE) + unset(HAVE_PRIVATE_KEY CACHE) + check_symbol_exists(AWS_IOT_ENDPOINT ${FILES_TO_CHECK} HAVE_AWS_ENDPOINT) + check_symbol_exists(CLIENT_CERT_PATH ${FILES_TO_CHECK} HAVE_CLIENT_CERT) + check_symbol_exists(CLIENT_PRIVATE_KEY_PATH ${FILES_TO_CHECK} HAVE_PRIVATE_KEY) + + if(NOT(HAVE_AWS_ENDPOINT AND HAVE_CLIENT_CERT AND HAVE_PRIVATE_KEY)) + message("To run ${demo_name}, define AWS_IOT_ENDPOINT, CLIENT_CERT_PATH, CLIENT_PRIVATE_KEY_PATH in ${demo_name}/demo_config.h.") + set_target_properties(${demo_name} PROPERTIES EXCLUDE_FROM_ALL true) + endif() +endfunction() + # Include each subdirectory that has a CMakeLists.txt file in it file(GLOB demo_dirs "${DEMOS_DIR}/*/*") foreach(demo_dir IN LISTS demo_dirs) @@ -15,10 +43,3 @@ foreach(demo_dir IN LISTS demo_dirs) add_subdirectory(${demo_dir}) endif() endforeach() - -# Replace macro definition if defined -if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) - set_target_properties(http_demo_mutual_auth mqtt_demo_mutual_auth - PROPERTIES COMPILE_DEFINITIONS - "AWS_IOT_ENDPOINT;CLIENT_CERT_PATH;CLIENT_PRIVATE_KEY_PATH") -endif() diff --git a/demos/http/http_demo_mutual_auth/CMakeLists.txt b/demos/http/http_demo_mutual_auth/CMakeLists.txt index 047b6ceb23..c6657010d5 100644 --- a/demos/http/http_demo_mutual_auth/CMakeLists.txt +++ b/demos/http/http_demo_mutual_auth/CMakeLists.txt @@ -2,27 +2,12 @@ include(CheckSymbolExists) set( DEMO_NAME "http_demo_mutual_auth" ) -# Check if all required macros needed to run this demo are defined -list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_LIST_DIR};${LOGGING_INCLUDE_DIRS}") -if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) - set(CMAKE_REQUIRED_DEFINITIONS -DAWS_IOT_ENDPOINT -DCLIENT_CERT_PATH -DCLIENT_PRIVATE_KEY_PATH) -endif() -unset(HAVE_AWS_ENDPOINT CACHE) -unset(HAVE_CLIENT_CERT CACHE) -unset(HAVE_PRIVATE_KEY CACHE) -set(FILES_TO_CHECK "demo_config.h") -check_symbol_exists(AWS_IOT_ENDPOINT ${FILES_TO_CHECK} HAVE_AWS_ENDPOINT) -check_symbol_exists(CLIENT_CERT_PATH ${FILES_TO_CHECK} HAVE_CLIENT_CERT) -check_symbol_exists(CLIENT_PRIVATE_KEY_PATH ${FILES_TO_CHECK} HAVE_PRIVATE_KEY) -if(NOT(HAVE_AWS_ENDPOINT AND HAVE_CLIENT_CERT AND HAVE_PRIVATE_KEY)) - message("To run ${DEMO_NAME}, define AWS_IOT_ENDPOINT, CLIENT_CERT_PATH, CLIENT_PRIVATE_KEY_PATH in demos/${DEMO_NAME}/demo_config.h.") - message("After doing this, rebuild using cmake ..") - return() -endif() - # Demo target. add_executable(${DEMO_NAME}) +# Add to default target if all required macros needed to run this demo are defined +check_aws_credentials(${DEMO_NAME}) + target_sources( ${DEMO_NAME} PRIVATE diff --git a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt index 6311996816..d32617b59b 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt @@ -2,27 +2,12 @@ include(CheckSymbolExists) set( DEMO_NAME "mqtt_demo_mutual_auth" ) -# Check if all required macros needed to run this demo are defined -list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_LIST_DIR};${LOGGING_INCLUDE_DIRS}") -if(AWS_IOT_ENDPOINT AND CLIENT_CERT_PATH AND CLIENT_PRIVATE_KEY_PATH) - set(CMAKE_REQUIRED_DEFINITIONS -DAWS_IOT_ENDPOINT -DCLIENT_CERT_PATH -DCLIENT_PRIVATE_KEY_PATH) -endif() -unset(HAVE_AWS_ENDPOINT CACHE) -unset(HAVE_CLIENT_CERT CACHE) -unset(HAVE_PRIVATE_KEY CACHE) -set(FILES_TO_CHECK "demo_config.h") -check_symbol_exists(AWS_IOT_ENDPOINT ${FILES_TO_CHECK} HAVE_AWS_ENDPOINT) -check_symbol_exists(CLIENT_CERT_PATH ${FILES_TO_CHECK} HAVE_CLIENT_CERT) -check_symbol_exists(CLIENT_PRIVATE_KEY_PATH ${FILES_TO_CHECK} HAVE_PRIVATE_KEY) -if(NOT(HAVE_AWS_ENDPOINT AND HAVE_CLIENT_CERT AND HAVE_PRIVATE_KEY)) - message("To run ${DEMO_NAME}, define AWS_IOT_ENDPOINT, CLIENT_CERT_PATH, CLIENT_PRIVATE_KEY_PATH in demos/${DEMO_NAME}/demo_config.h") - message("After doing this, rebuild using cmake ..") - return() -endif() - # Demo target. add_executable(${DEMO_NAME}) +# Add to default target if all required macros needed to run this demo are defined +check_aws_credentials(${DEMO_NAME}) + target_sources( ${DEMO_NAME} PRIVATE From 32865d3fb09a9f40f7fe85d49ced5df4d719be96 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 27 Jul 2020 22:02:42 -0700 Subject: [PATCH 600/844] mqtt.c updates in sendPacket(), asserting on developer bugs, and payload length of zero. (#1082) * Move assert on bug in the transport rx/tx to after checking for a negative returned. This is done to avoid overflow on the signed to unsigned conversion. * Delete check for the sendPacket return not equal to the total amount to send. sendPacket() returns only the full amount to send OR a negative return code. * Check for a payload length of zero for a publish. When the payload length is zero, then the payload buffer is allowed to be NULL. Added unit tests for this case. --- libraries/standard/http/lexicon.txt | 2 + libraries/standard/http/src/http_client.c | 20 +++--- libraries/standard/mqtt/include/mqtt.h | 2 +- libraries/standard/mqtt/lexicon.txt | 3 + libraries/standard/mqtt/src/mqtt.c | 79 +++++++++++++++------- libraries/standard/mqtt/utest/mqtt_utest.c | 16 +++++ 6 files changed, 87 insertions(+), 35 deletions(-) diff --git a/libraries/standard/http/lexicon.txt b/libraries/standard/http/lexicon.txt index 351eba5508..74bdd4828c 100644 --- a/libraries/standard/http/lexicon.txt +++ b/libraries/standard/http/lexicon.txt @@ -6,6 +6,7 @@ api ascii br bufferlen +bytesremaining cbmc chunked com @@ -134,6 +135,7 @@ statuscode strchr sublicense totalreceived +transportstatus txt uri utest diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 498fdc99f2..9beb021374 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1594,10 +1594,6 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport, pIndex, bytesRemaining ); - /* It is a bug in the application's transport send implementation if - * more bytes than expected are sent. */ - assert( transportStatus <= (int32_t)bytesRemaining ); - /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { @@ -1608,6 +1604,12 @@ static HTTPStatus_t sendHttpData( const TransportInterface_t * pTransport, } else { + /* It is a bug in the application's transport send implementation if + * more bytes than expected are sent. To avoid a possible overflow + * in converting bytesRemaining from unsigned to signed, this assert + * must exist after the check for transportStatus being negative. */ + assert( ( size_t ) transportStatus <= bytesRemaining ); + bytesRemaining -= ( size_t ) transportStatus; pIndex += transportStatus; LogDebug( ( "Sent HTTP data over the transport: " @@ -1734,10 +1736,6 @@ static HTTPStatus_t receiveHttpData( const TransportInterface_t * pTransport, pBuffer, bufferLen ); - /* It is a bug in the application's transport receive implementation if - * more bytes than expected are received. */ - assert( transportStatus <= (int32_t)bufferLen ); - /* A transport status of less than zero is an error. */ if( transportStatus < 0 ) { @@ -1748,6 +1746,12 @@ static HTTPStatus_t receiveHttpData( const TransportInterface_t * pTransport, } else if( transportStatus > 0 ) { + /* It is a bug in the application's transport receive implementation if + * more bytes than expected are received. To avoid a possible overflow + * in converting bytesRemaining from unsigned to signed, this assert + * must exist after the check for transportStatus being negative. */ + assert( ( size_t ) transportStatus <= bufferLen ); + /* Some or all of the specified data was received. */ *pBytesReceived = ( size_t ) ( transportStatus ); LogDebug( ( "Received data from the transport: BytesReceived=%d", diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index e92cdd3e82..254c4e9657 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -175,7 +175,7 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * Testament is not used. * @param[in] timeoutMs Maximum time in milliseconds to wait for a CONNACK packet. * A zero timeout makes use of the retries for receiving CONNACK as configured with - * #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT . + * #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. * @param[out] pSessionPresent Whether a previous session was present. * Only relevant if not establishing a clean session. * diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index e342e39439..dc1e04fc3b 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -9,6 +9,9 @@ bool bufferlength bytesorerror bytesreceived +bytesrecvd +bytesremaining +bytessent bytestoread bytestoreceive bytestorecv diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 5677f04755..b48bf7bfaa 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -315,6 +315,8 @@ static int32_t sendPacket( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pContext->callbacks.getTime != NULL ); + assert( pContext->transportInterface.send != NULL ); + assert( pIndex != NULL ); bytesRemaining = bytesToSend; @@ -328,11 +330,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, pIndex, bytesRemaining ); - /* It is a bug in the application's transport send implementation if - * more bytes than expected are sent. */ - assert( bytesSent <= ( int32_t ) bytesRemaining ); - - if( bytesSent <= 0 ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed. Error code=%d.", bytesSent ) ); totalBytesSent = bytesSent; @@ -340,6 +338,12 @@ static int32_t sendPacket( MQTTContext_t * pContext, } else { + /* It is a bug in the application's transport send implementation if + * more bytes than expected are sent. To avoid a possible overflow + * in converting bytesRemaining from unsigned to signed, this assert + * must exist after the check for bytesSent being negative. */ + assert( ( size_t ) bytesSent <= bytesRemaining ); + bytesRemaining -= ( size_t ) bytesSent; totalBytesSent += bytesSent; pIndex += bytesSent; @@ -352,7 +356,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, } /* Update time of last transmission if the entire packet is successfully sent. */ - if( bytesRemaining == 0U ) + if( totalBytesSent > 0 ) { pContext->lastPacketTime = sendTime; LogDebug( ( "Successfully sent packet at time %u.", @@ -420,6 +424,8 @@ static int32_t recvExact( const MQTTContext_t * pContext, assert( pContext != NULL ); assert( bytesToRecv <= pContext->networkBuffer.size ); assert( pContext->callbacks.getTime != NULL ); + assert( pContext->transportInterface.recv != NULL ); + assert( pContext->networkBuffer.pBuffer != NULL ); pIndex = pContext->networkBuffer.pBuffer; recvFunc = pContext->transportInterface.recv; @@ -433,10 +439,6 @@ static int32_t recvExact( const MQTTContext_t * pContext, pIndex, bytesRemaining ); - /* It is a bug in the application's transport receive implementation if - * more bytes than expected are received. */ - assert( bytesRecvd <= ( int32_t ) bytesRemaining ); - if( bytesRecvd < 0 ) { LogError( ( "Network error while receiving packet: ReturnCode=%d.", @@ -446,6 +448,13 @@ static int32_t recvExact( const MQTTContext_t * pContext, } else { + /* It is a bug in the application's transport receive implementation + * if more bytes than expected are received. To avoid a possible + * overflow in converting bytesRemaining from unsigned to signed, + * this assert must exist after the check for bytesRecvd being + * negative. */ + assert( ( size_t ) bytesRecvd <= bytesRemaining ); + bytesRemaining -= ( size_t ) bytesRecvd; totalBytesRecvd += ( int32_t ) bytesRecvd; pIndex += bytesRecvd; @@ -1056,13 +1065,15 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pPublishInfo != NULL ); assert( headerSize > 0 ); + assert( pContext->networkBuffer.pBuffer != NULL ); + assert( !( pPublishInfo->payloadLength > 0 ) || ( pPublishInfo->pPayload != NULL ) ); /* Send header first. */ bytesSent = sendPacket( pContext, pContext->networkBuffer.pBuffer, headerSize ); - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != headerSize ) ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed for PUBLISH header." ) ); status = MQTTSendFailed; @@ -1072,20 +1083,28 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, LogDebug( ( "Sent %d bytes of PUBLISH header.", bytesSent ) ); - /* Send Payload. */ - bytesSent = sendPacket( pContext, - pPublishInfo->pPayload, - pPublishInfo->payloadLength ); - - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != pPublishInfo->payloadLength ) ) + /* Send Payload if there is one to send. It is valid for a PUBLISH + * Packet to contain a zero length payload.*/ + if( pPublishInfo->payloadLength > 0U ) { - LogError( ( "Transport send failed for PUBLISH payload." ) ); - status = MQTTSendFailed; + bytesSent = sendPacket( pContext, + pPublishInfo->pPayload, + pPublishInfo->payloadLength ); + + if( bytesSent < 0 ) + { + LogError( ( "Transport send failed for PUBLISH payload." ) ); + status = MQTTSendFailed; + } + else + { + LogDebug( ( "Sent %d bytes of PUBLISH payload.", + bytesSent ) ); + } } else { - LogDebug( ( "Sent %d bytes of PUBLISH payload.", - bytesSent ) ); + LogDebug( "PUBLISH payload was not sent. Payload length was zero." ); } } @@ -1301,6 +1320,14 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, pPublishInfo->qos ) ); status = MQTTBadParameter; } + else if( ( pPublishInfo->payloadLength > 0U ) && ( pPublishInfo->pPayload == NULL ) ) + { + LogError( ( "A nonzero payload length requires a non-NULL payload: " + "payloadLength=%lu, pPayload=%p.", + ( unsigned long ) pPublishInfo->payloadLength, + pPublishInfo->pPayload ) ); + status = MQTTBadParameter; + } else { /* Empty else MISRA 15.7 */ @@ -1409,7 +1436,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, packetSize ); - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed for CONNECT packet." ) ); status = MQTTSendFailed; @@ -1497,7 +1524,7 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, packetSize ); - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed for SUBSCRIBE packet." ) ); status = MQTTSendFailed; @@ -1630,7 +1657,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ) packetSize ); /* It is an error to not send the entire PINGREQ packet. */ - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed for PINGREQ packet." ) ); status = MQTTSendFailed; @@ -1692,7 +1719,7 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, pContext->networkBuffer.pBuffer, packetSize ); - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed for UNSUBSCRIBE packet." ) ); status = MQTTSendFailed; @@ -1742,7 +1769,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ) pContext->networkBuffer.pBuffer, packetSize ); - if( ( bytesSent < 0 ) || ( ( size_t ) bytesSent != packetSize ) ) + if( bytesSent < 0 ) { LogError( ( "Transport send failed for DISCONNECT packet." ) ); status = MQTTSendFailed; diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 1a03e50e98..e98cb4f551 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -1164,6 +1164,11 @@ void test_MQTT_Publish( void ) publishInfo.qos = MQTTQoS1; status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + publishInfo.payloadLength = 1; + publishInfo.pPayload = NULL; + status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + memset( ( void * ) &publishInfo, 0x0, sizeof( publishInfo ) ); /* Bad Parameter when getting packet size. */ publishInfo.qos = MQTTQoS0; @@ -1205,6 +1210,17 @@ void test_MQTT_Publish( void ) status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Test that sending a publish without a payload succeeds. */ + publishInfo.pPayload = NULL; + publishInfo.payloadLength = 0; + MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); + MQTT_SerializePublishHeader_ReturnThruPtr_pHeaderSize( &headerSize ); + status = MQTT_Publish( &mqttContext, &publishInfo, 0 ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + /* Restore the test payload and length. */ + publishInfo.pPayload = "Test"; + publishInfo.payloadLength = 4; + /* Now for non zero QoS, which uses state engine. */ publishInfo.qos = MQTTQoS2; MQTT_SerializePublishHeader_ExpectAnyArgsAndReturn( MQTTSuccess ); From 2383328cfb7187bb160a52d4457ccd081ba45a6e Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 28 Jul 2020 09:57:46 -0700 Subject: [PATCH 601/844] MQTT Hygiene: Remove "callback" label from getTime function (#1071) Remove MQTTApplicationCallbacks_t struct from MQTT and add getTime and appCallback as separate members of MQTTContext_t struct --- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 15 +- .../mqtt_demo_mutual_auth.c | 15 +- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 15 +- .../cbmc/proofs/MQTT_Init/MQTT_Init_harness.c | 7 +- libraries/standard/mqtt/include/mqtt.h | 16 +- .../mqtt/integration-test/mqtt_system_test.c | 56 +++--- libraries/standard/mqtt/lexicon.txt | 4 +- libraries/standard/mqtt/src/mqtt.c | 84 +++++---- libraries/standard/mqtt/utest/mqtt_utest.c | 168 +++++------------- tools/spell/README.md | 9 +- 10 files changed, 147 insertions(+), 242 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 8d1aed1fe0..a895e23b2b 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -660,7 +660,6 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; TransportInterface_t transport; assert( pMqttContext != NULL ); @@ -677,16 +676,12 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; - /* Application callbacks for receiving incoming publishes and incoming acks - * from MQTT library. */ - callbacks.appCallback = eventCallback; - - /* Application callback for getting the time for MQTT library. This time - * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = Clock_GetTimeMs; - /* Initialize MQTT library. */ - mqttStatus = MQTT_Init( pMqttContext, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( pMqttContext, + &transport, + Clock_GetTimeMs, + eventCallback, + &networkBuffer ); if( mqttStatus != MQTTSuccess ) { diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 76895d8ab1..d827148cd6 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -689,7 +689,6 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, MQTTStatus_t mqttStatus; MQTTConnectInfo_t connectInfo; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; TransportInterface_t transport; assert( pMqttContext != NULL ); @@ -706,16 +705,12 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; - /* Application callbacks for receiving incoming publishes and incoming acks - * from MQTT library. */ - callbacks.appCallback = eventCallback; - - /* Application callback for getting the time for MQTT library. This time - * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = Clock_GetTimeMs; - /* Initialize MQTT library. */ - mqttStatus = MQTT_Init( pMqttContext, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( pMqttContext, + &transport, + Clock_GetTimeMs, + eventCallback, + &networkBuffer ); if( mqttStatus != MQTTSuccess ) { diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 3e79223938..5dadfd9ae8 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -426,7 +426,6 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, MQTTConnectInfo_t connectInfo; bool sessionPresent; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; TransportInterface_t transport; assert( pMqttContext != NULL ); @@ -443,16 +442,12 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; - /* Application callbacks for receiving incoming publishes and incoming acks - * from MQTT library. */ - callbacks.appCallback = eventCallback; - - /* Application callback for getting the time for MQTT library. This time - * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = Clock_GetTimeMs; - /* Initialize MQTT library. */ - mqttStatus = MQTT_Init( pMqttContext, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( pMqttContext, + &transport, + Clock_GetTimeMs, + eventCallback, + &networkBuffer ); if( mqttStatus != MQTTSuccess ) { diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c index dd7faccdb8..bdd598b969 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c @@ -31,16 +31,17 @@ void harness() { MQTTContext_t * pContext; TransportInterface_t * pTransportInterface; - MQTTApplicationCallbacks_t * pCallbacks; + MQTTGetCurrentTimeFunc_t getTimeFunction; + MQTTEventCallback_t userCallback; MQTTFixedBuffer_t * pNetworkBuffer; pContext = mallocCanFail( sizeof( MQTTContext_t ) ); pTransportInterface = mallocCanFail( sizeof( MQTTContext_t ) ); - pCallbacks = mallocCanFail( sizeof( MQTTApplicationCallbacks_t ) ); pNetworkBuffer = mallocCanFail( sizeof( MQTTFixedBuffer_t ) ); MQTT_Init( pContext, pTransportInterface, - pCallbacks, + getTimeFunction, + userCallback, pNetworkBuffer ); } diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 254c4e9657..dff0f17608 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -95,12 +95,6 @@ typedef enum MQTTPubAckType MQTTPubcomp } MQTTPubAckType_t; -struct MQTTApplicationCallbacks -{ - MQTTGetCurrentTimeFunc_t getTime; - MQTTEventCallback_t appCallback; -}; - struct MQTTPubAckInfo { uint16_t packetId; @@ -120,7 +114,8 @@ struct MQTTContext uint16_t nextPacketId; MQTTConnectionStatus_t connectStatus; - MQTTApplicationCallbacks_t callbacks; + MQTTGetCurrentTimeFunc_t getTime; + MQTTEventCallback_t appCallback; uint32_t lastPacketTime; bool controlPacketSent; @@ -143,7 +138,9 @@ struct MQTTContext * * @brief param[in] pContext The context to initialize. * @brief param[in] pTransportInterface The transport interface to use with the context. - * @brief param[in] pCallbacks Callbacks to use with the context. + * @brief param[in] getTimeFunction The time utility function to use with the context. + * @brief param[in] userCallback The user callback to use with the context to notify about + * incoming packet events. * @brief param[in] pNetworkBuffer Network buffer provided for the context. * * @return #MQTTBadParameter if invalid parameters are passed; @@ -151,7 +148,8 @@ struct MQTTContext */ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, const TransportInterface_t * pTransportInterface, - const MQTTApplicationCallbacks_t * pCallbacks, + MQTTGetCurrentTimeFunc_t getTimeFunction, + MQTTEventCallback_t userCallback, const MQTTFixedBuffer_t * pNetworkBuffer ); /** diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index 202d3bedf4..e909db0600 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -57,99 +57,99 @@ /** * @brief A valid starting packet ID per MQTT spec. Start from 1. */ -#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) +#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) /** * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /** * @brief A packet type not handled by MQTT_ProcessLoop. */ -#define MQTT_PACKET_TYPE_INVALID ( 0U ) +#define MQTT_PACKET_TYPE_INVALID ( 0U ) /** * @brief Number of milliseconds in a second. */ -#define MQTT_ONE_SECOND_TO_MS ( 1000U ) +#define MQTT_ONE_SECOND_TO_MS ( 1000U ) /** * @brief Length of the MQTT network buffer. */ -#define MQTT_TEST_BUFFER_LENGTH ( 128 ) +#define MQTT_TEST_BUFFER_LENGTH ( 128 ) /** * @brief Sample length of remaining serialized data. */ -#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) +#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) /** * @brief Subtract this value from max value of global entry time * for the timer overflow test. */ -#define MQTT_OVERFLOW_OFFSET ( 3 ) +#define MQTT_OVERFLOW_OFFSET ( 3 ) /** * @brief Sample topic filter to subscribe to. */ -#define TEST_MQTT_TOPIC "/iot/integration/test" +#define TEST_MQTT_TOPIC "/iot/integration/test" /** * @brief Length of sample topic filter. */ -#define TEST_MQTT_TOPIC_LENGTH ( sizeof( TEST_MQTT_TOPIC ) - 1 ) +#define TEST_MQTT_TOPIC_LENGTH ( sizeof( TEST_MQTT_TOPIC ) - 1 ) /** * @brief Sample topic filter to subscribe to. */ -#define TEST_MQTT_LWT_TOPIC "/iot/integration/test/lwt" +#define TEST_MQTT_LWT_TOPIC "/iot/integration/test/lwt" /** * @brief Length of sample topic filter. */ -#define TEST_MQTT_LWT_TOPIC_LENGTH ( sizeof( TEST_MQTT_LWT_TOPIC ) - 1 ) +#define TEST_MQTT_LWT_TOPIC_LENGTH ( sizeof( TEST_MQTT_LWT_TOPIC ) - 1 ) /** * @brief Size of the network buffer for MQTT packets. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define NETWORK_BUFFER_SIZE ( 1024U ) /** * @brief Client identifier for MQTT session in the tests. */ -#define TEST_CLIENT_IDENTIFIER "MQTT-Test" +#define TEST_CLIENT_IDENTIFIER "MQTT-Test" /** * @brief Length of the client identifier. */ -#define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) +#define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) /** * @brief Client identifier for use in LWT tests. */ -#define TEST_CLIENT_IDENTIFIER_LWT "MQTT-Test-LWT" +#define TEST_CLIENT_IDENTIFIER_LWT "MQTT-Test-LWT" /** * @brief Length of LWT client identifier. */ -#define TEST_CLIENT_IDENTIFIER_LWT_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER_LWT ) - 1u ) +#define TEST_CLIENT_IDENTIFIER_LWT_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER_LWT ) - 1u ) /** * @brief Transport timeout in milliseconds for transport send and receive. */ -#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200U ) +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200U ) /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ -#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) /** * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to * broker. */ -#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 5U ) +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 5U ) /** * @brief Timeout for MQTT_ProcessLoop() function in milliseconds. @@ -157,12 +157,12 @@ * PUBLISH message and ack responses for QoS 1 and QoS 2 communications * with the broker. */ -#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 700U ) +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 700U ) /** * @brief The MQTT message published in this example. */ -#define MQTT_EXAMPLE_MESSAGE "Hello World!" +#define MQTT_EXAMPLE_MESSAGE "Hello World!" /** * @brief Packet Identifier generated when Subscribe request was sent to the broker; @@ -293,7 +293,6 @@ static void establishMqttSession( MQTTContext_t * pContext, MQTTConnectInfo_t connectInfo; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPublishInfo_t lwtInfo; assert( pContext != NULL ); @@ -311,18 +310,11 @@ static void establishMqttSession( MQTTContext_t * pContext, networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; - /* Application callbacks for receiving incoming publishes and incoming acks - * from MQTT library. */ - callbacks.appCallback = eventCallback; - - /* Application callback for getting the time for MQTT library. This time - * function will be used to calculate intervals in MQTT library.*/ - callbacks.getTime = Clock_GetTimeMs; - /* Initialize MQTT library. */ TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Init( pContext, &transport, - &callbacks, + Clock_GetTimeMs, + eventCallback, &networkBuffer ) ); /* Establish MQTT session with a CONNECT packet. */ @@ -780,7 +772,6 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) void test_MQTT_Connect_LWT( void ) { - int secondTcpSocket; NetworkContext_t secondNetworkContext = { 0 }; bool sessionPresent; MQTTContext_t secondContext; @@ -836,6 +827,7 @@ void test_MQTT_ProcessLoop_KeepAlive( void ) { uint32_t connectPacketTime = context.lastPacketTime; uint32_t elapsedTime = 0; + TEST_ASSERT_EQUAL( 0, context.pingReqSendTimeMs ); /* Sleep until control packet needs to be sent. */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index dc1e04fc3b..a1a3b7ab3c 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -55,6 +55,7 @@ getpingreqpacketsize getpublishpacketsize getsubscribepacketsize gettime +gettimefunction getunsubscribepacketsize handleincomingack handleincomingpublish @@ -228,6 +229,7 @@ unsuback updatestateack updatestatepublish updatestatestatus +usercallback utest utf validatesubscribeunsubscribeparams @@ -235,4 +237,4 @@ xa xb xc xd -xe +xe \ No newline at end of file diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index b48bf7bfaa..0d961a4579 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -314,14 +314,14 @@ static int32_t sendPacket( MQTTContext_t * pContext, bool sendError = false; assert( pContext != NULL ); - assert( pContext->callbacks.getTime != NULL ); + assert( pContext->getTime != NULL ); assert( pContext->transportInterface.send != NULL ); assert( pIndex != NULL ); bytesRemaining = bytesToSend; /* Record the time of transmission. */ - sendTime = pContext->callbacks.getTime(); + sendTime = pContext->getTime(); /* Loop until the entire packet is sent. */ while( ( bytesRemaining > 0UL ) && ( sendError == false ) ) @@ -423,13 +423,13 @@ static int32_t recvExact( const MQTTContext_t * pContext, assert( pContext != NULL ); assert( bytesToRecv <= pContext->networkBuffer.size ); - assert( pContext->callbacks.getTime != NULL ); + assert( pContext->getTime != NULL ); assert( pContext->transportInterface.recv != NULL ); assert( pContext->networkBuffer.pBuffer != NULL ); pIndex = pContext->networkBuffer.pBuffer; recvFunc = pContext->transportInterface.recv; - getTimeStampMs = pContext->callbacks.getTime; + getTimeStampMs = pContext->getTime; entryTimeMs = getTimeStampMs(); @@ -492,9 +492,9 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, bool receiveError = false; assert( pContext != NULL ); - assert( pContext->callbacks.getTime != NULL ); + assert( pContext->getTime != NULL ); bytesToReceive = pContext->networkBuffer.size; - getTimeStampMs = pContext->callbacks.getTime; + getTimeStampMs = pContext->getTime; entryTimeMs = getTimeStampMs(); @@ -693,7 +693,7 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ) uint32_t now = 0U, keepAliveMs = 0U; assert( pContext != NULL ); - now = pContext->callbacks.getTime(); + now = pContext->getTime(); keepAliveMs = 1000U * ( uint32_t ) pContext->keepAliveIntervalSec; /* If keep alive interval is 0, it is disabled. */ @@ -804,10 +804,10 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, * duplicate incoming publishes. */ if( duplicatePublish == false ) { - pContext->callbacks.appCallback( pContext, - pIncomingPacket, - packetIdentifier, - &publishInfo ); + pContext->appCallback( pContext, + pIncomingPacket, + packetIdentifier, + &publishInfo ); } /* Send PUBACK or PUBREC if necessary. */ @@ -832,9 +832,9 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pIncomingPacket != NULL ); - assert( pContext->callbacks.appCallback != NULL ); + assert( pContext->appCallback != NULL ); - appCallback = pContext->callbacks.appCallback; + appCallback = pContext->appCallback; ackType = getAckFromPacketType( pIncomingPacket->type ); status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); @@ -898,7 +898,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pIncomingPacket != NULL ); - appCallback = pContext->callbacks.appCallback; + appCallback = pContext->appCallback; switch( pIncomingPacket->type ) { @@ -1127,9 +1127,9 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, assert( pContext != NULL ); assert( pIncomingPacket != NULL ); - assert( pContext->callbacks.getTime != NULL ); + assert( pContext->getTime != NULL ); - getTimeStamp = pContext->callbacks.getTime; + getTimeStamp = pContext->getTime; /* Get the entry time for the function. */ entryTimeMs = getTimeStamp(); @@ -1340,31 +1340,31 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, const TransportInterface_t * pTransportInterface, - const MQTTApplicationCallbacks_t * pCallbacks, + MQTTGetCurrentTimeFunc_t getTimeFunction, + MQTTEventCallback_t userCallback, const MQTTFixedBuffer_t * pNetworkBuffer ) { MQTTStatus_t status = MQTTSuccess; /* Validate arguments. */ if( ( pContext == NULL ) || ( pTransportInterface == NULL ) || - ( pCallbacks == NULL ) || ( pNetworkBuffer == NULL ) ) + ( pNetworkBuffer == NULL ) ) { LogError( ( "Argument cannot be NULL: pContext=%p, " "pTransportInterface=%p, " - "pCallbacks=%p, " - "pNetworkBuffer=%p.", + "pNetworkBuffer=%p", pContext, pTransportInterface, - pCallbacks, pNetworkBuffer ) ); status = MQTTBadParameter; } - else if( ( pCallbacks->getTime == NULL ) || ( pCallbacks->appCallback == NULL ) || + else if( ( getTimeFunction == NULL ) || ( userCallback == NULL ) || ( pTransportInterface->recv == NULL ) || ( pTransportInterface->send == NULL ) ) { - LogError( ( "Functions cannot be NULL: getTime=%p, appCallback=%p, recv=%p, send=%p.", - pCallbacks->getTime, - pCallbacks->appCallback, + LogError( ( "Function pointers cannot be NULL: getTimeFunction=%p, userCallback=%p, " + "transportRecv=%p, transportRecvSend=%p", + getTimeFunction, + userCallback, pTransportInterface->recv, pTransportInterface->send ) ); status = MQTTBadParameter; @@ -1375,7 +1375,8 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, pContext->connectStatus = MQTTNotConnected; pContext->transportInterface = *pTransportInterface; - pContext->callbacks = *pCallbacks; + pContext->getTime = getTimeFunction; + pContext->appCallback = userCallback; pContext->networkBuffer = *pNetworkBuffer; /* Zero is not a valid packet ID per MQTT spec. Start from 1. */ @@ -1796,25 +1797,23 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t timeoutMs ) { MQTTStatus_t status = MQTTBadParameter; - MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; if( pContext == NULL ) { - LogError( ( "MQTT Context cannot be NULL." ) ); + LogError( ( "Invalid input parameter: MQTT Context cannot be NULL." ) ); } - else if( pContext->callbacks.getTime == NULL ) + else if( pContext->getTime == NULL ) { - LogError( ( "MQTT Context must set callbacks.getTime." ) ); + LogError( ( "Invalid input parameter: MQTT Context must have valid getTime." ) ); } else if( pContext->networkBuffer.pBuffer == NULL ) { - LogError( ( "The MQTT context's networkBuffer must not be NULL." ) ); + LogError( ( "Invalid input parameter: The MQTT context's networkBuffer must not be NULL." ) ); } else { - getTimeStampMs = pContext->callbacks.getTime; - entryTimeMs = getTimeStampMs(); + entryTimeMs = pContext->getTime(); pContext->controlPacketSent = false; status = MQTTSuccess; } @@ -1827,14 +1826,15 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * the loop condition, and we do not want multiple breaks in a loop. */ if( status != MQTTSuccess ) { - LogError( ( "Exiting process loop. Error status=%s", + LogError( ( "Exiting process loop due to failure: ErrorStatus=%s", MQTT_Status_strerror( status ) ) ); } else { /* Recalculate remaining time and check if loop should exit. This is * done at the end so the loop will run at least a single iteration. */ - elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + elapsedTimeMs = calculateElapsedTime( pContext->getTime(), + entryTimeMs ); if( elapsedTimeMs > timeoutMs ) { @@ -1854,25 +1854,23 @@ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, uint32_t timeoutMs ) { MQTTStatus_t status = MQTTBadParameter; - MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL; uint32_t entryTimeMs = 0U, remainingTimeMs = timeoutMs, elapsedTimeMs = 0U; if( pContext == NULL ) { - LogError( ( "MQTT Context cannot be NULL." ) ); + LogError( ( "Invalid input parameter: MQTT Context cannot be NULL." ) ); } - else if( pContext->callbacks.getTime == NULL ) + else if( pContext->getTime == NULL ) { - LogError( ( "MQTT Context must set callbacks.getTime." ) ); + LogError( ( "Invalid input parameter: MQTT Context must have a valid getTime function." ) ); } else if( pContext->networkBuffer.pBuffer == NULL ) { - LogError( ( "The MQTT context's networkBuffer must not be NULL." ) ); + LogError( ( "Invalid input parameter: MQTT context's networkBuffer must not be NULL." ) ); } else { - getTimeStampMs = pContext->callbacks.getTime; - entryTimeMs = getTimeStampMs(); + entryTimeMs = pContext->getTime(); status = MQTTSuccess; } @@ -1891,7 +1889,7 @@ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, { /* Recalculate remaining time and check if loop should exit. This is * done at the end so the loop will run at least a single iteration. */ - elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs ); + elapsedTimeMs = calculateElapsedTime( pContext->getTime(), entryTimeMs ); if( elapsedTimeMs >= timeoutMs ) { diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index e98cb4f551..a62b69181d 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -335,18 +335,6 @@ static void setupTransportInterface( TransportInterface_t * pTransport ) pTransport->recv = transportRecvSuccess; } -/** - * @brief Initialize our event and time callback with the mocked functions - * defined for the purposes this test. - * - * @brief param[in] pCallbacks Callbacks to use with the context. - */ -static void setupCallbacks( MQTTApplicationCallbacks_t * pCallbacks ) -{ - pCallbacks->appCallback = eventCallback; - pCallbacks->getTime = getTime; -} - /** * @brief Initialize pSubscribeInfo using test-defined macros. * @@ -531,19 +519,18 @@ void test_MQTT_Init_Happy_Path( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; - setupCallbacks( &callbacks ); setupTransportInterface( &transport ); setupNetworkBuffer( &networkBuffer ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); TEST_ASSERT_EQUAL( MQTT_FIRST_VALID_PACKET_ID, context.nextPacketId ); + TEST_ASSERT_EQUAL_PTR( getTime, context.getTime ); + TEST_ASSERT_EQUAL_PTR( eventCallback, context.appCallback ); /* These Unity assertions take pointers and compare their contents. */ TEST_ASSERT_EQUAL_MEMORY( &transport, &context.transportInterface, sizeof( transport ) ); - TEST_ASSERT_EQUAL_MEMORY( &callbacks, &context.callbacks, sizeof( callbacks ) ); TEST_ASSERT_EQUAL_MEMORY( &networkBuffer, &context.networkBuffer, sizeof( networkBuffer ) ); } @@ -556,42 +543,33 @@ void test_MQTT_Init_Invalid_Params( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; - setupCallbacks( &callbacks ); setupTransportInterface( &transport ); /* Check that MQTTBadParameter is returned if any NULL parameters are passed. */ - mqttStatus = MQTT_Init( NULL, &transport, &callbacks, &networkBuffer ); - TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - - mqttStatus = MQTT_Init( &context, NULL, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( NULL, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - mqttStatus = MQTT_Init( &context, &transport, NULL, &networkBuffer ); + mqttStatus = MQTT_Init( &context, NULL, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, NULL ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, NULL ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - /* Test if NULL is passed for any callbacks. */ - callbacks.getTime = NULL; - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + /* Test if NULL is passed for any of the function pointers. */ + mqttStatus = MQTT_Init( &context, &transport, NULL, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - callbacks.appCallback = NULL; - callbacks.getTime = getTime; - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, NULL, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - callbacks.appCallback = eventCallback; transport.recv = NULL; - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); transport.recv = transportRecvSuccess; transport.send = NULL; - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); } @@ -609,15 +587,13 @@ void test_MQTT_Connect_sendConnect( void ) MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; size_t remainingLength, packetSize; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); /* Check parameters */ status = MQTT_Connect( NULL, &connectInfo, NULL, timeout, &sessionPresent ); @@ -684,16 +660,14 @@ void test_MQTT_Connect_receiveConnack( void ) MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); transport.recv = transportRecvFailure; memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); /* Everything before receiving the CONNACK should succeed. */ MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); @@ -756,17 +730,15 @@ void test_MQTT_Connect_receiveConnack_retries( void ) MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; /* Same set of tests with retries. MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is 2*/ setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); transport.recv = transportRecvFailure; memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); /* Everything before receiving the CONNACK should succeed. */ MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); @@ -819,16 +791,14 @@ void test_MQTT_Connect_partial_receive() MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); transport.recv = transportRecvOneByte; memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); /* Everything before receiving the CONNACK should succeed. */ MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); @@ -875,7 +845,7 @@ void test_MQTT_Connect_partial_receive() /* Receive failure while discarding packet. */ mqttContext.transportInterface.recv = transportRecvFailure; /* Test with dummy get time function to make sure there are no infinite loops. */ - mqttContext.callbacks.getTime = getTimeDummy; + mqttContext.getTime = getTimeDummy; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); status = MQTT_Connect( &mqttContext, &connectInfo, NULL, MQTT_NO_TIMEOUT_MS, &sessionPresent ); @@ -894,18 +864,16 @@ void test_MQTT_Connect_resendPendingAcks( void ) MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; uint16_t packetIdentifier = 1; MQTTPublishState_t pubRelState = MQTTPubRelSend; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); memset( ( void * ) &connectInfo, 0x00, sizeof( connectInfo ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess ); @@ -1029,15 +997,13 @@ void test_MQTT_Connect_happy_path() MQTTStatus_t status; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); connectInfo.keepAliveSeconds = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess ); @@ -1140,7 +1106,6 @@ void test_MQTT_Publish( void ) MQTTPublishInfo_t publishInfo; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTStatus_t status; size_t headerSize; MQTTPublishState_t expectedState; @@ -1148,13 +1113,12 @@ void test_MQTT_Publish( void ) const uint16_t PACKET_ID = 1; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); transport.send = transportSendFailure; memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); memset( ( void * ) &publishInfo, 0x0, sizeof( publishInfo ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); /* Verify parameters. */ status = MQTT_Publish( NULL, &publishInfo, PACKET_ID ); @@ -1283,11 +1247,9 @@ void test_MQTT_Disconnect( void ) NetworkContext_t networkContext; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; size_t disconnectSize = 2; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); networkContext.buffer = &bufPtr; transport.pNetworkContext = &networkContext; @@ -1295,7 +1257,7 @@ void test_MQTT_Disconnect( void ) transport.send = transportSendFailure; memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); mqttContext.connectStatus = MQTTConnected; /* Verify parameters. */ @@ -1332,14 +1294,12 @@ void test_MQTT_GetPacketId( void ) MQTTContext_t mqttContext; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; uint16_t packetId; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); memset( ( void * ) &mqttContext, 0x0, sizeof( mqttContext ) ); - MQTT_Init( &mqttContext, &transport, &callbacks, &networkBuffer ); + MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer ); /* Verify parameters. */ packetId = MQTT_GetPacketId( NULL ); @@ -1365,22 +1325,20 @@ void test_MQTT_ProcessLoop_Invalid_Params( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks = { 0 }; MQTTStatus_t mqttStatus = MQTT_ProcessLoop( NULL, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Get time function cannot be NULL. */ - context.callbacks.getTime = NULL; + context.getTime = NULL; mqttStatus = MQTT_ProcessLoop( &context, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); /* Restore the time function for the next test. */ - context.callbacks.getTime = getTime; + context.getTime = getTime; /* The fixed network buffer cannot be NULL. */ context.networkBuffer.pBuffer = NULL; @@ -1399,14 +1357,12 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPublishInfo_t pubInfo; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); modifyIncomingPacketStatus = MQTTSuccess; @@ -1478,14 +1434,12 @@ void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPublishInfo_t publishInfo = { 0 }; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); modifyIncomingPacketStatus = MQTTSuccess; @@ -1531,13 +1485,11 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); modifyIncomingPacketStatus = MQTTSuccess; @@ -1609,13 +1561,11 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); modifyIncomingPacketStatus = MQTTSuccess; @@ -1674,16 +1624,15 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; + setupNetworkBuffer( &networkBuffer ); setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); modifyIncomingPacketStatus = MQTTNoDataAvailable; globalEntryTime = MQTT_ONE_SECOND_TO_MS; /* Coverage for the branch path where keep alive interval is 0. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); context.waitingForPingResp = false; context.keepAliveIntervalSec = 0; @@ -1693,7 +1642,7 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) /* Coverage for the branch path where keep alive interval is greater than 0, * and the interval has expired. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); context.waitingForPingResp = true; context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; @@ -1703,7 +1652,7 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) MQTTSuccess, false, NULL ); /* Coverage for the branch path where PINGRESP timeout interval hasn't expired. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); context.waitingForPingResp = true; context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; @@ -1715,7 +1664,7 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) MQTTSuccess, false, NULL ); /* Coverage for the branch path where a PINGRESP hasn't been sent out yet. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); context.waitingForPingResp = false; context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; @@ -1736,16 +1685,14 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); modifyIncomingPacketStatus = MQTTNoDataAvailable; globalEntryTime = MQTT_ONE_SECOND_TO_MS; /* Coverage for the branch path where PING timeout interval hasn't expired. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); context.lastPacketTime = 0; context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; @@ -1765,12 +1712,11 @@ void test_MQTT_ProcessLoop_Receive_Failed( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; + setupNetworkBuffer( &networkBuffer ); setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed ); @@ -1789,7 +1735,6 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTPacketInfo_t incomingPacket = { 0 }; MQTTPublishState_t publishState = MQTTPubAckSend; MQTTPublishState_t ackState = MQTTPublishDone; @@ -1798,7 +1743,6 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) uint32_t expectedFinalTime; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); networkBuffer.size = 1000; incomingPacket.type = MQTT_PACKET_TYPE_PUBLISH; @@ -1807,7 +1751,7 @@ void test_MQTT_ProcessLoop_Timer_Overflow( void ) globalEntryTime = UINT32_MAX - MQTT_OVERFLOW_OFFSET; expectedFinalTime = MQTT_TIMER_CALLS_PER_ITERATION * numIterations - MQTT_OVERFLOW_OFFSET; - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify that we run the expected number of iterations despite overflowing. */ @@ -1842,13 +1786,11 @@ void test_MQTT_ReceiveLoop( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks = { 0 }; MQTTPacketInfo_t incomingPacket = { 0 }; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify that a NULL Context returns an error. */ @@ -1856,10 +1798,10 @@ void test_MQTT_ReceiveLoop( void ) TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); /* Verify that a NULL time function returns an error. */ - context.callbacks.getTime = NULL; + context.getTime = NULL; mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus ); - context.callbacks.getTime = getTime; + context.getTime = getTime; /* Verify that a null fixed network buffer returns an error. */ context.networkBuffer.pBuffer = NULL; @@ -1880,10 +1822,10 @@ void test_MQTT_ReceiveLoop( void ) TEST_ASSERT_FALSE( context.controlPacketSent ); /* Test with a dummy getTime to ensure there's no infinite loops. */ - context.callbacks.getTime = getTimeDummy; + context.getTime = getTimeDummy; mqttStatus = MQTT_ReceiveLoop( &context, MQTT_NO_TIMEOUT_MS ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); - context.callbacks.getTime = getTime; + context.getTime = getTime; MQTT_GetIncomingPacketTypeAndLength_StopIgnore(); @@ -1937,18 +1879,16 @@ void test_MQTT_Subscribe_happy_path( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); setupSubscriptionInfo( &subscribeInfo ); /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTSuccess is returned with the following mocks. */ MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -1970,14 +1910,12 @@ void test_MQTT_Subscribe_error_paths( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; /* Verify that an error is propagated when transport interface returns an error. */ setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); setupSubscriptionInfo( &subscribeInfo ); @@ -1985,7 +1923,7 @@ void test_MQTT_Subscribe_error_paths( void ) transport.recv = transportRecvFailure; /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTSendFailed is propagated when transport interface returns an error. */ MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -2037,18 +1975,16 @@ void test_MQTT_Unsubscribe_happy_path( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); setupSubscriptionInfo( &subscribeInfo ); /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTSuccess is returned with the following mocks. */ MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -2070,14 +2006,12 @@ void test_MQTT_Unsubscribe_error_path( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; MQTTSubscribeInfo_t subscribeInfo; size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH; size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH; /* Verify that an error is propagated when transport interface returns an error. */ setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); setupSubscriptionInfo( &subscribeInfo ); @@ -2085,7 +2019,7 @@ void test_MQTT_Unsubscribe_error_path( void ) transport.recv = transportRecvFailure; /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTSendFailed is propagated when transport interface returns an error. */ MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -2122,15 +2056,13 @@ void test_MQTT_Ping_happy_path( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTSuccess is returned. */ MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -2154,11 +2086,9 @@ void test_MQTT_Ping_error_path( void ) MQTTContext_t context; TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; - MQTTApplicationCallbacks_t callbacks; size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE; setupTransportInterface( &transport ); - setupCallbacks( &callbacks ); setupNetworkBuffer( &networkBuffer ); /* Test a network error is returned from sending the PING packet over the @@ -2167,7 +2097,7 @@ void test_MQTT_Ping_error_path( void ) transport.recv = transportRecvFailure; /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTSendFailed is propagated when transport interface returns an error. */ MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -2178,7 +2108,7 @@ void test_MQTT_Ping_error_path( void ) TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus ); /* Initialize context. */ - mqttStatus = MQTT_Init( &context, &transport, &callbacks, &networkBuffer ); + mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); /* Verify MQTTBadParameter is propagated when getting PINGREQ packet size fails. */ MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter ); diff --git a/tools/spell/README.md b/tools/spell/README.md index cbc77fff10..5f5fbec50b 100644 --- a/tools/spell/README.md +++ b/tools/spell/README.md @@ -1,15 +1,14 @@ # How to create a lexicon.txt for a new library. -1. In your GNU environment install programs: *spell* and *getopt* - For Linux here are the commands to install these programs: +1. In your GNU environment, install the *spell* and *getopt* programs. Use the following commands in Debian distributions, to install the packages (*getopt* is part of the `util-linux` package): ```shell apt-get install spell - apt-get install getopt + apt-get install util-linux ``` -1. Add **tools/spell/ablexicon**, **tools/spell/extract-comments**, and **tools/spell/find-unknown-comment-words** to your system's PATH. +1. Add the folder containing the **tools/spell/ablexicon**, **tools/spell/extract-comments**, and **tools/spell/find-unknown-comment-words** scripts to your system's PATH. ```shell - export PATH=/tools/spell/ablexicon:tools/spell/extract-comments:/tools/spell/find-unknown-comment-words:$PATH + export PATH=/tools/spell:$PATH ``` 1. Ensure there does not exist a file called "lexicon.txt" in your library's directory. Run the following command to create a lexicon.txt for your library: From f4eb9693c843bf181e4c007cbccd7acc88d475c9 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Tue, 28 Jul 2020 11:02:38 -0700 Subject: [PATCH 602/844] Add CBMC proof for HTTPClient_Send, speed up proof for HTTPClient_InitializeRequestHeaders (#1080) * Add CBMC proof for AddRangeHeader * Add harness for HTTPClient_Send (not working) * Assigning struct member to function pointer in stubs breaks the build * Full coverage but so many weird errors * Revert to original way of subtracting bytesRemaining in sendHttpData for CI check * Revert http_client.c * Revert cbmc state * Test removal of sendHttpHeaders * So frustrating... * Try this * Try returning bytesToSend/Recv * My goodness... * This should pass * Loop should terminate * Change bytesRemaining to size_t * Return HTTP_SUCCESS always * will this work? * Last for the night * Last for the night again * Get it working * Working * Mangle loop names and simplify harness for HTTPClient_Send proof. * Update proof to allocate network context * Update HTTP aws-templates-for-cbmc-proofs submodule to current master * Improve CBMC HTTP Send coverage by removing unreachable functions. * Add CBMC HTTP memcpy stub. * Improve CBMC HTTP InitializeRequestHeaders performance with memcpy stub. * Stub out http_parser_execute for HTTPClient_Send * Add extra parameter checks for HTTPClient_Send * Fix any accidental changes from rebase * Address PR comments * Move assert for receiving more bytes than expected in send and recv * Update stubs * Update lexicon.txt for http * Address PR comments * Update aws-templates submodule * Order of lines in transport interface stubs * Use an assert instead of assume for len in http_parser_execute stub * Update Makefile for AddHeader to use memcpy stub * Remove function bodies for callbacks * Address PR comments * Add blank line before @return * Fix merge conflict in http_client.c * Update lexicon for http * Change pContext -> pNetworkContext Co-authored-by: Mark R. Tuttle --- .../http/cbmc/include/http_cbmc_state.h | 25 +++++- .../cbmc/include/transport_interface_stubs.h | 59 ++++++++++++++ .../cbmc/proofs/HTTPClient_AddHeader/Makefile | 19 ++++- .../Makefile | 19 ++++- .../HTTPClient_Send/HTTPClient_Send_harness.c | 80 +++++++++++++++++++ .../http/cbmc/proofs/HTTPClient_Send/Makefile | 36 +++++++++ .../cbmc/proofs/HTTPClient_Send/README.md | 10 +++ .../proofs/HTTPClient_Send/cbmc-batch.yaml | 2 + .../proofs/HTTPClient_Send/cbmc-viewer.json | 7 ++ .../http/cbmc/sources/http_cbmc_state.c | 22 ++++- .../HTTPClient_Send_http_parser_execute.c | 79 ++++++++++++++++++ libraries/standard/http/cbmc/stubs/memcpy.c | 54 +++++++++++++ .../cbmc/stubs/transport_interface_stubs.c | 66 +++++++++++++++ libraries/standard/http/lexicon.txt | 8 ++ libraries/standard/http/src/http_client.c | 30 ++++++- tools/aws-templates-for-cbmc-proofs | 2 +- 16 files changed, 504 insertions(+), 14 deletions(-) create mode 100644 libraries/standard/http/cbmc/include/transport_interface_stubs.h create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_Send/README.md create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c create mode 100644 libraries/standard/http/cbmc/stubs/memcpy.c create mode 100644 libraries/standard/http/cbmc/stubs/transport_interface_stubs.c diff --git a/libraries/standard/http/cbmc/include/http_cbmc_state.h b/libraries/standard/http/cbmc/include/http_cbmc_state.h index 3b141ae672..19162ff176 100644 --- a/libraries/standard/http/cbmc/include/http_cbmc_state.h +++ b/libraries/standard/http/cbmc/include/http_cbmc_state.h @@ -23,8 +23,21 @@ #define HTTP_CBMC_STATE_H_ #include +#include #include "http_client.h" +#include "private/http_client_internal.h" +#include "http_parser.h" + +struct NetworkContext +{ + int filler; +}; + +/** + * @brief Attains coverage when a variable needs to possibly contain two values. + */ +bool nondet_bool(); /** * @brief Calls malloc based on given size or returns NULL for coverage. @@ -64,7 +77,7 @@ bool isValidHttpRequestHeaders( const HTTPRequestHeaders_t * pRequestHeaders ); * * @return NULL or pointer to allocated #HTTPRequestInfo_t object. */ -HTTPRequestInfo_t * allocateHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ); +HTTPRequestInfo_t * allocateHttpRequestInfo( HTTPRequestInfo_t * pRequestInfo ); /** * @brief Validates if a #HTTPRequestInfo_t object is feasible. @@ -93,4 +106,14 @@ HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ); */ bool isValidHttpResponse( const HTTPResponse_t * pResponse ); +/** + * @brief Allocate a transport interface for CBMC. + * + * @param[in] pTransport Transport interface. + * + * @return An allocated TransportInterface_t object to use as a parameter + * for the function under test. + */ +TransportInterface_t * allocateTransportInterface( TransportInterface_t * pTransport ); + #endif /* ifndef HTTP_CBMC_STATE_H_ */ diff --git a/libraries/standard/http/cbmc/include/transport_interface_stubs.h b/libraries/standard/http/cbmc/include/transport_interface_stubs.h new file mode 100644 index 0000000000..cbafe8b4b2 --- /dev/null +++ b/libraries/standard/http/cbmc/include/transport_interface_stubs.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef TRANSPORT_INTERFACE_STUBS_H_ +#define TRANSPORT_INTERFACE_STUBS_H_ + +#include + +#include "http_client.h" + +#ifndef MAX_TRIES + #define MAX_TRIES 5 +#endif + +/** + * @brief Transport interface stub for mocking data sent over the network. + * + * @param[in] pNetworkContext Implementation-defined network context. + * @param[in] pBuffer Buffer containing the bytes to send over the network stack. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return The number of bytes sent or a negative error code. + */ +int32_t TransportInterfaceSendStub( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToSend ); + +/** + * @brief Transport interface for mocking data received over the network. + * + * @param[in] pNetworkContext Implementation-defined network context. + * @param[in] pBuffer Buffer to receive the data into. + * @param[in] bytesToRecv Number of bytes requested from the network. + * + * @return The number of bytes received or a negative error code. + */ +int32_t TransportInterfaceReceiveStub( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + +#endif /* ifndef TRANSPORT_INTERFACE_STUBS_H_ */ diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile index 1b07958ca2..cef44282ca 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile @@ -7,11 +7,26 @@ HARNESS_FILE=HTTPClient_AddHeader_harness DEFINES += INCLUDES += -REMOVE_FUNCTION_BODY += +# We remove these function bodies so that they aren't categorized as possible +# function pointer targets, leading to faster execution time. +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderFieldParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderValueParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnBodyCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderFieldCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderValueCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnStatusCallback + +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeadersCompleteCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback + +REMOVE_FUNCTION_BODY += memcpy + UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(PROOFDIR)/../../stubs/memcpy.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile index e2f55becf8..e87c758f2a 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile @@ -7,11 +7,26 @@ HARNESS_FILE=HTTPClient_InitializeRequestHeaders_harness DEFINES += INCLUDES += -REMOVE_FUNCTION_BODY += +# We remove these function bodies so that they aren't categorized as possible +# function pointer targets, leading to faster execution time. +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderFieldParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderValueParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnBodyCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderFieldCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderValueCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnStatusCallback + +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeadersCompleteCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback + +REMOVE_FUNCTION_BODY += memcpy + UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(PROOFDIR)/../../stubs/memcpy.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c new file mode 100644 index 0000000000..16e6fcbadc --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_Send_harness.c + * @brief Implements the proof harness for HTTPClient_Send function. + */ + +#include "http_client.h" +#include "http_cbmc_state.h" +#include "transport_interface_stubs.h" + +void harness() +{ + HTTPRequestHeaders_t * pRequestHeaders = NULL; + HTTPResponse_t * pResponse = NULL; + + /* Ideally, this should be allocated in the heap but doing so makes CBMC + * run out of memory. */ + TransportInterface_t transportInterface; + uint8_t * pRequestBodyBuf = NULL; + size_t reqBodyBufLen; + uint32_t sendFlags; + + /* Initialize and make assumptions for request headers. */ + pRequestHeaders = allocateHttpRequestHeaders( NULL ); + __CPROVER_assume( isValidHttpRequestHeaders( pRequestHeaders ) ); + + /* Initialize and make assumptions for buffer to receive request body. */ + __CPROVER_assume( reqBodyBufLen < CBMC_MAX_OBJECT_SIZE ); + pRequestBodyBuf = mallocCanFail( reqBodyBufLen ); + + /* Initialize and make assumptions for response object. */ + pResponse = allocateHttpResponse( NULL ); + __CPROVER_assume( isValidHttpResponse( pResponse ) ); + + /* Initialize transport interface. */ + ( void ) allocateTransportInterface( &transportInterface ); + + if( nondet_bool() ) + { + /* Ideally, we want to set the function pointers below with __CPROVER_assume() + * but doing so makes CBMC run out of memory. */ + transportInterface.send = nondet_bool() ? NULL : TransportInterfaceSendStub; + transportInterface.recv = nondet_bool() ? NULL : TransportInterfaceReceiveStub; + HTTPClient_Send( &transportInterface, + pRequestHeaders, + pRequestBodyBuf, + reqBodyBufLen, + pResponse, + sendFlags ); + } + else + { + HTTPClient_Send( NULL, + pRequestHeaders, + pRequestBodyBuf, + reqBodyBufLen, + pResponse, + sendFlags ); + } +} diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile new file mode 100644 index 0000000000..8cc6c1f1e9 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile @@ -0,0 +1,36 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=HTTPClient_Send_harness + +DEFINES += +INCLUDES += + +# We remove these function bodies so that they aren't categorized as possible +# function pointer targets, leading to faster execution time. +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderFieldParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderValueParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnBodyCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderFieldCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderValueCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnStatusCallback + +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeadersCompleteCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback + +UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.0:11 +UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.1:11 +UNWINDSET += __CPROVER_file_local_http_client_c_receiveAndParseHttpResponse.0:10 +UNWINDSET += __CPROVER_file_local_http_client_c_sendHttpData.0:10 +UNWINDSET += strncmp.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c +PROOF_SOURCES += $(PROOFDIR)/../../stubs/HTTPClient_Send_http_parser_execute.c +PROOF_SOURCES += $(PROOFDIR)/../../stubs/transport_interface_stubs.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/README.md b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/README.md new file mode 100644 index 0000000000..862dffd55b --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/README.md @@ -0,0 +1,10 @@ +HTTPClient_Send proof +============== + +This directory contains a memory safety proof for HTTPClient_Send. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path +* Run "make" +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-viewer.json new file mode 100644 index 0000000000..47d8ce63a4 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "HTTPClient_Send", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/sources/http_cbmc_state.c b/libraries/standard/http/cbmc/sources/http_cbmc_state.c index 010cd26688..4acbc9fd5d 100644 --- a/libraries/standard/http/cbmc/sources/http_cbmc_state.c +++ b/libraries/standard/http/cbmc/sources/http_cbmc_state.c @@ -65,8 +65,7 @@ bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ) if( pRequestInfo ) { - isValid = ( pRequestInfo->reqFlags < CBMC_MAX_OBJECT_SIZE ) && - ( pRequestInfo->methodLen < CBMC_MAX_OBJECT_SIZE ) && + isValid = ( pRequestInfo->methodLen < CBMC_MAX_OBJECT_SIZE ) && ( pRequestInfo->hostLen < CBMC_MAX_OBJECT_SIZE ) && ( pRequestInfo->pathLen < CBMC_MAX_OBJECT_SIZE ); } @@ -98,9 +97,24 @@ bool isValidHttpResponse( const HTTPResponse_t * pResponse ) if( pResponse ) { - isValid = pResponse->bufferLen < CBMC_MAX_OBJECT_SIZE && - pResponse->bodyLen < CBMC_MAX_OBJECT_SIZE; + isValid = ( pResponse->bufferLen < CBMC_MAX_OBJECT_SIZE ) && + ( pResponse->bodyLen < CBMC_MAX_OBJECT_SIZE ); } return isValid; } + +TransportInterface_t * allocateTransportInterface( TransportInterface_t * pTransport ) +{ + if( pTransport == NULL ) + { + pTransport = mallocCanFail( sizeof( TransportInterface_t ) ); + } + + if( pTransport != NULL ) + { + pTransport->pNetworkContext = mallocCanFail( sizeof( NetworkContext_t ) ); + } + + return pTransport; +} diff --git a/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c b/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c new file mode 100644 index 0000000000..aca35ae02d --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_Send_http_parser_execute.c + * @brief Creates a stub for http_parser_execute for coverage of HTTPClient_Send. + */ + +#include +#include + +#include "http_client.h" +#include "private/http_client_internal.h" +#include "http_parser.h" + +/** + * @brief Attains coverage when a variable needs to possibly contain two values. + */ +bool nondet_bool(); + +size_t http_parser_execute( http_parser * parser, + const http_parser_settings * settings, + const char * data, + size_t len ) +{ + size_t fieldLength; + size_t valueLength; + HTTPParsingContext_t * pParsingContext; + + __CPROVER_assert( parser != NULL, + "http_parser_execute parser is NULL" ); + __CPROVER_assert( settings != NULL, + "http_parser_execute settings is NULL" ); + __CPROVER_assert( data != NULL, + "http_parser_execute data is NULL" ); + __CPROVER_assert( len < CBMC_MAX_OBJECT_SIZE, + "http_parser_execute len >= CBMC_MAX_OBJECT_SIZE" ); + + __CPROVER_assume( fieldLength <= len ); + __CPROVER_assume( valueLength <= len ); + + pParsingContext = ( HTTPParsingContext_t * ) ( parser->data ); + /* Choose whether the parser found the header */ + pParsingContext->pLastHeaderField = nondet_bool() ? NULL : malloc( fieldLength ); + pParsingContext->state = HTTP_PARSING_COMPLETE; + + if( pParsingContext->pLastHeaderField ) + { + pParsingContext->lastHeaderFieldLen = fieldLength; + pParsingContext->pLastHeaderValue = malloc( valueLength ); + pParsingContext->lastHeaderValueLen = valueLength; + } + else + { + pParsingContext->lastHeaderFieldLen = 0u; + pParsingContext->pLastHeaderValue = NULL; + pParsingContext->lastHeaderValueLen = 0u; + } + + return pParsingContext->lastHeaderValueLen; +} diff --git a/libraries/standard/http/cbmc/stubs/memcpy.c b/libraries/standard/http/cbmc/stubs/memcpy.c new file mode 100644 index 0000000000..6eeaf10704 --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/memcpy.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file memcpy.c + * @brief Creates a stub for memcpy so that the proof for + * HTTPClient_InitializeRequestHeaders runs much faster. + */ + +#include + +/* This is a clang macro not available on linux */ +#ifndef __has_builtin + #define __has_builtin( x ) 0 +#endif + +#if __has_builtin( __builtin___memcpy_chk ) + void * __builtin___memcpy_chk( void * dest, + const void * src, + size_t n, + size_t m ) + { + __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + return dest; + } +#else + void * memcpy( void * dest, + const void * src, + size_t n ) + { + __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + return dest; + } +#endif /* if __has_builtin( __builtin___memcpy_chk ) */ diff --git a/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c b/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c new file mode 100644 index 0000000000..262a2cfdd3 --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c @@ -0,0 +1,66 @@ +#include "transport_interface_stubs.h" + +int32_t TransportInterfaceSendStub( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToSend ) +{ + /* The number of tries to send the message before this invocation */ + static int32_t tries; + /* The number of bytes considered sent after this invocation */ + int32_t ret; + + __CPROVER_assert( pBuffer != NULL, + "TransportInterfaceSendStub pBuffer is NULL" ); + + /**************************************************************** + * The send method sends some portion of the message and returns the + * total number of bytes in the prefix sent so far. The send method + * is used in a loop of the form + * + * while ( send( conn, msg, len ) < len ) { ... } + * + * We need to bound the number of loop iterations, so we need to + * bound the number of times it takes for send to finish sending the + * message. We use a static variable 'tries' to count the number of + * times send has tried to send the message, and we force send to + * finish the message after MAX_TRIES tries. + ****************************************************************/ + + __CPROVER_assume( ret <= ( int32_t ) bytesToSend ); + + tries++; + + if( tries >= MAX_TRIES ) + { + tries = 0; + + if( bytesToSend <= INT32_MAX ) + { + ret = bytesToSend; + } + else + { + ret = INT32_MAX; + } + } + + return ret; +} + +int32_t TransportInterfaceReceiveStub( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + /* The number of bytes considered received after this invocation */ + int32_t ret; + + __CPROVER_assert( pBuffer != NULL, + "TransportInterfaceReceiveStub pBuffer is NULL" ); + + if( bytesToRecv <= INT32_MAX ) + { + __CPROVER_assume( ret <= ( int32_t ) bytesToRecv ); + } + + return ret; +} diff --git a/libraries/standard/http/lexicon.txt b/libraries/standard/http/lexicon.txt index 74bdd4828c..90ce4c6247 100644 --- a/libraries/standard/http/lexicon.txt +++ b/libraries/standard/http/lexicon.txt @@ -7,6 +7,8 @@ ascii br bufferlen bytesremaining +bytestorecv +bytestosend cbmc chunked com @@ -56,9 +58,13 @@ iso lastheaderfieldlen lastheadervaluelen latin +len +linux malloc methodlen +memcpy misra +msg networkcontext nodejs noninfringement @@ -94,6 +100,7 @@ plastheadervalue ploc pmethod pname +pnetworkcontext pnetworkdata pnext pnextwriteloc @@ -114,6 +121,7 @@ pvalueloc rangeend rangestart rangestartorlastnbytes +readheadervalue receivehttpdata recv recvcurrentcall diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 9beb021374..4ecddd4b4c 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1687,11 +1687,19 @@ static HTTPStatus_t sendHttpHeaders( const TransportInterface_t * pTransport, if( returnStatus == HTTP_SUCCESS ) { - LogDebug( ( "Sending HTTP request headers: HeaderBytes=%lu", - ( unsigned long ) ( pRequestHeaders->headersLen ) ) ); - /* Send the HTTP headers over the network. */ - returnStatus = sendHttpData( pTransport, pRequestHeaders->pBuffer, pRequestHeaders->headersLen ); + if( pRequestHeaders->headersLen > INT32_MAX ) + { + LogError( ( "Parameter check failed: pRequestHeaders->headersLen > " + "2147483647 (INT32_MAX)." ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } + else + { + LogDebug( ( "Sending HTTP request headers: HeaderBytes=%lu", + ( unsigned long ) ( pRequestHeaders->headersLen ) ) ); + returnStatus = sendHttpData( pTransport, pRequestHeaders->pBuffer, pRequestHeaders->headersLen ); + } } return returnStatus; @@ -1938,6 +1946,12 @@ HTTPStatus_t HTTPClient_Send( const TransportInterface_t * pTransport, ( unsigned long ) ( pRequestHeaders->headersLen ) ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( pRequestHeaders->headersLen > pRequestHeaders->bufferLen ) + { + LogError( ( "Parameter check failed: pRequestHeaders->headersLen > " + "pRequestHeaders->bufferLen." ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else if( ( pResponse != NULL ) && ( pResponse->pBuffer == NULL ) ) { LogError( ( "Parameter check failed: pResponse->pBuffer is NULL." ) ); @@ -1949,6 +1963,14 @@ HTTPStatus_t HTTPClient_Send( const TransportInterface_t * pTransport, "reqBodyBufLen is greater than zero." ) ); returnStatus = HTTP_INVALID_PARAMETER; } + else if( reqBodyBufLen > INT32_MAX ) + { + /* This check is needed because convertInt32ToAscii() is used on the + * reqBodyBufLen to create a Content-Length header value string. */ + LogError( ( "Parameter check failed: reqBodyBufLen > " + "2147483647 (INT32_MAX)." ) ); + returnStatus = HTTP_INVALID_PARAMETER; + } else { /* Empty else for MISRA 15.7 compliance. */ diff --git a/tools/aws-templates-for-cbmc-proofs b/tools/aws-templates-for-cbmc-proofs index 64879cfd7c..ddb267ac14 160000 --- a/tools/aws-templates-for-cbmc-proofs +++ b/tools/aws-templates-for-cbmc-proofs @@ -1 +1 @@ -Subproject commit 64879cfd7c568e0caf1af0d0cc9ba6445c6431a9 +Subproject commit ddb267ac14e011da917bd96c1a802b7bb43b9796 From 53e128d56a0d9029141559cfc8ed2f000b16c01c Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 28 Jul 2020 14:56:21 -0700 Subject: [PATCH 603/844] Integ tests/mqtt restore session (#1078) Add integration tests for MQTT session restoration when 1) Unacked PUBREL packets are resent by the client 2) Duplicate PUBREL packets are sent by broker --- .../mqtt/integration-test/mqtt_system_test.c | 416 +++++++++++++----- 1 file changed, 313 insertions(+), 103 deletions(-) diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index e909db0600..444f1bd17c 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -252,6 +252,12 @@ static bool receivedPubComp = false; */ static MQTTPublishInfo_t incomingInfo; +/** + * @brief Disconnect when receiving this packet type. Used for session + * restoration tests. + */ +static uint8_t disconnectOnPacketType = 0U; + /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. * @@ -268,6 +274,14 @@ static void establishMqttSession( MQTTContext_t * pContext, bool createCleanSession, bool * pSessionPresent ); +/** + * @brief Handler for incoming acknowledgement packets from the broker. + * @param[in] pPacketInfo Info for the incoming acknowledgement packet. + * @param[in] packetIdentifier The ID of the incoming packet. + */ +static void handleAckEvents( MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier ); + /** * @brief The application callback function that is expected to be invoked by the * MQTT library for incoming publish and incoming acks received over the network. @@ -310,12 +324,16 @@ static void establishMqttSession( MQTTContext_t * pContext, networkBuffer.pBuffer = buffer; networkBuffer.size = NETWORK_BUFFER_SIZE; - /* Initialize MQTT library. */ - TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Init( pContext, - &transport, - Clock_GetTimeMs, - eventCallback, - &networkBuffer ) ); + /* Clear the state of the MQTT context when creating a clean session. */ + if( createCleanSession == true ) + { + /* Initialize MQTT library. */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Init( pContext, + &transport, + Clock_GetTimeMs, + eventCallback, + &networkBuffer ) ); + } /* Establish MQTT session with a CONNECT packet. */ @@ -358,6 +376,91 @@ static void establishMqttSession( MQTTContext_t * pContext, pSessionPresent ) ); } +static void handleAckEvents( MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier ) +{ + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + /* Set the flag to represent reception of SUBACK. */ + receivedSubAck = true; + + LogDebug( ( "Received SUBACK: PacketID=%u", + packetIdentifier ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalSubscribePacketIdentifier, packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogDebug( ( "Received PINGRESP" ) ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + /* Set the flag to represent reception of UNSUBACK. */ + receivedUnsubAck = true; + + LogDebug( ( "Received UNSUBACK: PacketID=%u", + packetIdentifier ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalUnsubscribePacketIdentifier, packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PUBACK: + /* Set the flag to represent reception of PUBACK. */ + receivedPubAck = true; + + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); + + LogDebug( ( "Received PUBACK: PacketID=%u", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PUBREC: + /* Set the flag to represent reception of PUBREC. */ + receivedPubRec = true; + + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); + + LogDebug( ( "Received PUBREC: PacketID=%u", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PUBREL: + /* Set the flag to represent reception of PUBREL. */ + receivedPubRel = true; + + /* Nothing to be done from application as library handles + * PUBREL. */ + LogDebug( ( "Received PUBREL: PacketID=%u", + packetIdentifier ) ); + break; + + case MQTT_PACKET_TYPE_PUBCOMP: + /* Set the flag to represent reception of PUBACK. */ + receivedPubComp = true; + + /* Make sure ACK packet identifier matches with Request packet identifier. */ + TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); + + /* Nothing to be done from application as library handles + * PUBCOMP. */ + LogDebug( ( "Received PUBCOMP: PacketID=%u", + packetIdentifier ) ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).", + pPacketInfo->type ) ); + } +} + static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, uint16_t packetIdentifier, @@ -366,108 +469,42 @@ static void eventCallback( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pPacketInfo != NULL ); - /* Handle incoming publish. The lower 4 bits of the publish packet - * type is used for the dup, QoS, and retain flags. Hence masking - * out the lower bits to check if the packet is publish. */ - if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + if( pPacketInfo->type == disconnectOnPacketType ) { - assert( pPublishInfo != NULL ); - /* Handle incoming publish. */ - - /* Cache information about the incoming PUBLISH message to process - * in test case. */ - memcpy( &incomingInfo, pPublishInfo, sizeof( MQTTPublishInfo_t ) ); - incomingInfo.pTopicName = NULL; - incomingInfo.pPayload = NULL; - /* Allocate buffers and copy information of topic name and payload. */ - incomingInfo.pTopicName = malloc( pPublishInfo->topicNameLength ); - TEST_ASSERT_NOT_NULL( incomingInfo.pTopicName ); - memcpy( ( void * ) incomingInfo.pTopicName, pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); - incomingInfo.pPayload = malloc( pPublishInfo->payloadLength ); - TEST_ASSERT_NOT_NULL( incomingInfo.pPayload ); - memcpy( ( void * ) incomingInfo.pPayload, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + /* Terminate MQTT connection with server for session restoration test. */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Disconnect( &context ) ); + + /* Terminate TLS session and TCP connection to test session restoration + * across network connection. */ + ( void ) Openssl_Disconnect( &networkContext ); } else { - /* Handle other packets. */ - switch( pPacketInfo->type ) + /* Handle incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) { - case MQTT_PACKET_TYPE_SUBACK: - /* Set the flag to represent reception of SUBACK. */ - receivedSubAck = true; - - LogDebug( ( "Received SUBACK: PacketID=%u", - packetIdentifier ) ); - /* Make sure ACK packet identifier matches with Request packet identifier. */ - TEST_ASSERT_EQUAL( globalSubscribePacketIdentifier, packetIdentifier ); - break; - - case MQTT_PACKET_TYPE_PINGRESP: - - /* Nothing to be done from application as library handles - * PINGRESP. */ - LogDebug( ( "Received PINGRESP" ) ); - break; - - case MQTT_PACKET_TYPE_UNSUBACK: - /* Set the flag to represent reception of UNSUBACK. */ - receivedUnsubAck = true; - - LogDebug( ( "Received UNSUBACK: PacketID=%u", - packetIdentifier ) ); - /* Make sure ACK packet identifier matches with Request packet identifier. */ - TEST_ASSERT_EQUAL( globalUnsubscribePacketIdentifier, packetIdentifier ); - break; - - case MQTT_PACKET_TYPE_PUBACK: - /* Set the flag to represent reception of PUBACK. */ - receivedPubAck = true; - - /* Make sure ACK packet identifier matches with Request packet identifier. */ - TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); - - LogDebug( ( "Received PUBACK: PacketID=%u", - packetIdentifier ) ); - break; - - case MQTT_PACKET_TYPE_PUBREC: - /* Set the flag to represent reception of PUBREC. */ - receivedPubRec = true; - - /* Make sure ACK packet identifier matches with Request packet identifier. */ - TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); - - LogDebug( ( "Received PUBREC: PacketID=%u", - packetIdentifier ) ); - break; - - case MQTT_PACKET_TYPE_PUBREL: - /* Set the flag to represent reception of PUBREL. */ - receivedPubRel = true; - - /* Nothing to be done from application as library handles - * PUBREL. */ - LogDebug( ( "Received PUBREL: PacketID=%u", - packetIdentifier ) ); - break; - - case MQTT_PACKET_TYPE_PUBCOMP: - /* Set the flag to represent reception of PUBACK. */ - receivedPubComp = true; - - /* Make sure ACK packet identifier matches with Request packet identifier. */ - TEST_ASSERT_EQUAL( globalPublishPacketIdentifier, packetIdentifier ); - - /* Nothing to be done from application as library handles - * PUBCOMP. */ - LogDebug( ( "Received PUBCOMP: PacketID=%u", - packetIdentifier ) ); - break; - - /* Any other packet type is invalid. */ - default: - LogError( ( "Unknown packet type received:(%02x).", - pPacketInfo->type ) ); + assert( pPublishInfo != NULL ); + /* Handle incoming publish. */ + + /* Cache information about the incoming PUBLISH message to process + * in test case. */ + memcpy( &incomingInfo, pPublishInfo, sizeof( MQTTPublishInfo_t ) ); + incomingInfo.pTopicName = NULL; + incomingInfo.pPayload = NULL; + /* Allocate buffers and copy information of topic name and payload. */ + incomingInfo.pTopicName = malloc( pPublishInfo->topicNameLength ); + TEST_ASSERT_NOT_NULL( incomingInfo.pTopicName ); + memcpy( ( void * ) incomingInfo.pTopicName, pPublishInfo->pTopicName, pPublishInfo->topicNameLength ); + incomingInfo.pPayload = malloc( pPublishInfo->payloadLength ); + TEST_ASSERT_NOT_NULL( incomingInfo.pPayload ); + memcpy( ( void * ) incomingInfo.pPayload, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + } + else + { + handleAckEvents( pPacketInfo, + packetIdentifier ); } } } @@ -563,6 +600,7 @@ void setUp() receivedPubComp = false; persistentSession = false; useLWTClientIdentifier = false; + disconnectOnPacketType = 0U; memset( &incomingInfo, 0u, sizeof( MQTTPublishInfo_t ) ); memset( &opensslCredentials, 0u, sizeof( OpensslCredentials_t ) ); opensslCredentials.pRootCaPath = SERVER_ROOT_CA_CERT_PATH; @@ -619,6 +657,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_0( void ) &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); /* We expect a SUBACK from the broker for the subscribe operation. */ + TEST_ASSERT_FALSE( receivedSubAck ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_TRUE( receivedSubAck ); @@ -629,6 +668,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_0( void ) /* Call the MQTT library for the expectation to read an incoming PUBLISH for * the same message that we published (as we have subscribed to the same topic). */ + TEST_ASSERT_FALSE( receivedPubAck ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); /* We do not expect a PUBACK from the broker for the QoS 0 PUBLISH. */ @@ -668,6 +708,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_1( void ) &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); /* Expect a SUBACK from the broker for the subscribe operation. */ + TEST_ASSERT_FALSE( receivedSubAck ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_TRUE( receivedSubAck ); @@ -683,6 +724,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_1( void ) /* Expect a PUBACK response for the PUBLISH and an incoming PUBLISH for the * same message that we published (as we have subscribed to the same topic). */ + TEST_ASSERT_FALSE( receivedPubAck ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); /* Make sure we have received PUBACK response. */ @@ -722,6 +764,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); /* Expect a SUBACK from the broker for the subscribe operation. */ + TEST_ASSERT_FALSE( receivedSubAck ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_TRUE( receivedSubAck ); @@ -741,6 +784,10 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) * the incoming PUBLISH (as we subscribed and publish with QoS 2). Since it takes * longer to complete a QoS 2 publish, we run the process loop longer to allow it * ample time. */ + TEST_ASSERT_FALSE( receivedPubAck ); + TEST_ASSERT_FALSE( receivedPubRec ); + TEST_ASSERT_FALSE( receivedPubComp ); + TEST_ASSERT_FALSE( receivedPubRel ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_FALSE( receivedPubAck ); @@ -770,6 +817,10 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) TEST_ASSERT_TRUE( receivedUnsubAck ); } +/** + * @brief Verifies that the MQTT library supports the "Last Will and Testament" feature when + * establishing a connection with a broker. + */ void test_MQTT_Connect_LWT( void ) { NetworkContext_t secondNetworkContext = { 0 }; @@ -818,11 +869,16 @@ void test_MQTT_Connect_LWT( void ) &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); /* We expect an UNSUBACK from the broker for the unsubscribe operation. */ + TEST_ASSERT_FALSE( receivedUnsubAck ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); TEST_ASSERT_TRUE( receivedUnsubAck ); } +/** + * @brief Verifies that the MQTT library sends a Ping Request packet if the connection is + * idle for more than the keep-alive period. + */ void test_MQTT_ProcessLoop_KeepAlive( void ) { uint32_t connectPacketTime = context.lastPacketTime; @@ -840,3 +896,157 @@ void test_MQTT_ProcessLoop_KeepAlive( void ) elapsedTime = context.lastPacketTime - connectPacketTime; TEST_ASSERT_LESS_OR_EQUAL( MQTT_KEEP_ALIVE_INTERVAL_SECONDS * 1500, elapsedTime ); } + +/** + * @brief Verifies the behavior of the MQTT library in a restored session connection with the broker + * for a PUBLISH operation that was incomplete in the previous connection. + * Tests that the library resends PUBREL packets to the broker in a restored session for an incomplete + * PUBLISH operation in a previous connection. + */ +void test_MQTT_Restore_Session_Resend_PubRel( void ) +{ + /* Terminate TLS session and TCP network connection to discard the current MQTT session + * that was created as a "clean session". */ + ( void ) Openssl_Disconnect( &networkContext ); + + /* Establish a new MQTT connection over TLS with the broker with the "clean session" flag set to 0 + * to start a persistent session with the broker. */ + + /* Create the TLS+TCP connection with the broker. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Establish a new MQTT connection for a persistent session with the broker. */ + establishMqttSession( &context, &networkContext, false, &persistentSession ); + TEST_ASSERT_FALSE( persistentSession ); + + /* Publish to a topic with Qos 2. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + + /* Disconnect on receiving PUBREC so that we are not able to complete the QoS 2 PUBLISH in the current connection. */ + TEST_ASSERT_FALSE( receivedPubComp ); + disconnectOnPacketType = MQTT_PACKET_TYPE_PUBREC; + TEST_ASSERT_EQUAL( MQTTSendFailed, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_FALSE( receivedPubComp ); + + /* Verify that the connection with the broker has been disconnected. */ + TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); + + /* We will re-establish an MQTT over TLS connection with the broker to restore the persistent session. */ + + /* Create a new TLS+TCP network connection with the server. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Re-establish the persistent session with the broker by connecting with "clean session" flag set to 0. */ + establishMqttSession( &context, &networkContext, false, &persistentSession ); + + /* Verify that the session was resumed. */ + TEST_ASSERT_TRUE( persistentSession ); + + /* Resume the incomplete QoS 2 PUBLISH in previous MQTT connection. */ + TEST_ASSERT_FALSE( receivedPubComp ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Test that the MQTT library has completed the QoS 2 publish by sending the PUBREL flag. */ + TEST_ASSERT_TRUE( receivedPubComp ); +} + +/** + * @brief Verifies the behavior of the MQTT library on receiving a duplicate + * PUBREL packet from the broker in a restored session connection. + * Tests that the library sends a PUBCOMP packet in response to the broker for the + * incoming QoS 2 PUBLISH operation that was incomplete in a previous connection + * of the same session. + */ +void test_MQTT_Restore_Session_Complete_Incoming_Publish( void ) +{ + /* Terminate TLS session and TCP connection network connection to discard current MQTT session + * that was created as a "clean session". */ + ( void ) Openssl_Disconnect( &networkContext ); + + /* Establish a new MQTT connection over TLS with the broker with the "clean session" flag set to 0 + * to start a persistent session with the broker. */ + + /* Create the TLS+TCP connection with the broker. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Establish a new MQTT connection for starting a persistent session with the broker + * by setting the "clean session" flag to 0. */ + establishMqttSession( &context, &networkContext, false, &persistentSession ); + TEST_ASSERT_FALSE( persistentSession ); + + /* Subscribe to a topic from which we will be receiving an incomplete incoming + * QoS 2 PUBLISH transaction in this connection. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + TEST_ASSERT_FALSE( receivedSubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Publish to the same topic with Qos 2 (so that the broker can re-publish it back to us). */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + + /* Disconnect on receiving PUBREL so that we are not able to complete in the incoming QoS2 + * PUBLISH in the current connection. */ + disconnectOnPacketType = MQTT_PACKET_TYPE_PUBREL; + TEST_ASSERT_EQUAL( MQTTSendFailed, + MQTT_ProcessLoop( &context, 3 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Verify that the connection with the broker has been disconnected. */ + TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); + + /* We will re-establish an MQTT over TLS connection with the broker to restore the persistent session. */ + + /* Create a new TLS+TCP network connection with the server. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Re-establish the persistent session with the broker by connecting with "clean session" flag set to 0. */ + establishMqttSession( &context, &networkContext, false, &persistentSession ); + + /* Verify that the session was resumed. */ + TEST_ASSERT_TRUE( persistentSession ); + + /* Clear the global variable for not disconnecting on PUBREL + * that we receive from the broker on the session restoration. */ + disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; + + /* Resume the incomplete incoming QoS 2 PUBLISH transaction from the previous MQTT connection. */ + TEST_ASSERT_FALSE( receivedPubRel ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that the broker resent the PUBREL packet on session restoration. */ + TEST_ASSERT_TRUE( receivedPubRel ); + + /* Make sure that the library sent a PUBCOMP packet in response to the PUBREL packet + * from the server to complete the incoming PUBLISH QoS2 transaction. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); +} From 156af10bae575ec500280e401b3d03d8759291f1 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 28 Jul 2020 15:37:49 -0700 Subject: [PATCH 604/844] CBMC Proofs for MQTT_ProcessLoop and MQTT_Ping (#1061) - Add licenses where missing. - Add helpful comments where needed else where. - Fix MQTT_DeserializeAck proof. --- .../cbmc/proofs/HTTPClient_AddHeader/Makefile | 22 ++++- .../proofs/HTTPClient_AddRangeHeader/Makefile | 22 ++++- .../Makefile | 22 ++++- .../proofs/HTTPClient_ReadHeader/Makefile | 22 ++++- .../cbmc/proofs/HTTPClient_strerror/Makefile | 22 ++++- .../mqtt/cbmc/include/event_callback_stub.h | 46 ++++++++++ .../mqtt/cbmc/include/get_time_stub.h | 37 ++++++++ .../mqtt/cbmc/include/mqtt_cbmc_state.h | 29 +++++- .../standard/mqtt/cbmc/include/mqtt_config.h | 6 ++ .../cbmc/include/network_interface_stubs.h | 28 ++++-- .../MQTT_DeserializeAck_harness.c | 4 + .../cbmc/proofs/MQTT_DeserializeAck/Makefile | 30 ++++++- .../MQTT_DeserializePublish_harness.c | 6 +- .../proofs/MQTT_DeserializePublish/Makefile | 22 ++++- .../Makefile | 25 ++++++ .../cbmc/proofs/MQTT_GetPacketId/Makefile | 21 +++++ .../cbmc/proofs/MQTT_Init/MQTT_Init_harness.c | 4 +- .../mqtt/cbmc/proofs/MQTT_Init/Makefile | 21 +++++ .../cbmc/proofs/MQTT_Ping/MQTT_Ping_harness.c | 37 ++++++++ .../mqtt/cbmc/proofs/MQTT_Ping/Makefile | 45 ++++++++++ .../mqtt/cbmc/proofs/MQTT_Ping/README.md | 10 +++ .../cbmc/proofs/MQTT_Ping/cbmc-batch.yaml | 2 + .../cbmc/proofs/MQTT_Ping/cbmc-viewer.json | 7 ++ .../MQTT_ProcessLoop_harness.c | 46 ++++++++++ .../cbmc/proofs/MQTT_ProcessLoop/Makefile | 74 +++++++++++++++ .../cbmc/proofs/MQTT_ProcessLoop/README.md | 10 +++ .../proofs/MQTT_ProcessLoop/cbmc-batch.yaml | 2 + .../proofs/MQTT_ProcessLoop/cbmc-viewer.json | 7 ++ .../cbmc/proofs/MQTT_SerializeAck/Makefile | 22 ++++- .../MQTT_SerializeConnect_harness.c | 6 +- .../proofs/MQTT_SerializeConnect/Makefile | 22 ++++- .../proofs/MQTT_SerializeDisconnect/Makefile | 22 ++++- .../proofs/MQTT_SerializePingreq/Makefile | 22 ++++- .../MQTT_SerializePublish_harness.c | 6 +- .../proofs/MQTT_SerializePublish/Makefile | 22 ++++- .../MQTT_SerializePublishHeader_harness.c | 6 +- .../MQTT_SerializePublishHeader/Makefile | 22 ++++- .../MQTT_SerializeSubscribe_harness.c | 6 +- .../proofs/MQTT_SerializeSubscribe/Makefile | 28 ++++-- .../MQTT_SerializeUnsubscribe_harness.c | 6 +- .../proofs/MQTT_SerializeUnsubscribe/Makefile | 28 ++++-- .../mqtt/cbmc/sources/mqtt_cbmc_state.c | 90 +++++++++++++++++-- .../mqtt/cbmc/stubs/event_callback_stub.c | 34 +++++++ .../standard/mqtt/cbmc/stubs/get_time_stub.c | 37 ++++++++ .../mqtt/cbmc/stubs/network_interface_stubs.c | 54 +++++++++-- libraries/standard/mqtt/include/mqtt.h | 7 +- libraries/standard/mqtt/lexicon.txt | 2 + 47 files changed, 987 insertions(+), 84 deletions(-) create mode 100644 libraries/standard/mqtt/cbmc/include/event_callback_stub.h create mode 100644 libraries/standard/mqtt/cbmc/include/get_time_stub.h create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/MQTT_Ping_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c create mode 100644 libraries/standard/mqtt/cbmc/stubs/get_time_stub.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile index cef44282ca..6e583db184 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_AddHeader_harness diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile index bbaa8fd164..a831edfc64 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddRangeHeader/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_AddRangeHeader_harness diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile index e87c758f2a..b9dea2cd3d 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_InitializeRequestHeaders_harness diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile index d8df39e3e2..8245a64d57 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_ReadHeader_harness diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile index db6c267882..9a040c0bd5 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_strerror/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_strerror_harness diff --git a/libraries/standard/mqtt/cbmc/include/event_callback_stub.h b/libraries/standard/mqtt/cbmc/include/event_callback_stub.h new file mode 100644 index 0000000000..9db2f2c3f9 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/include/event_callback_stub.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file event_callback_stub.h + * @brief Stub definition for the application defined MQTT library incoming + * event callback. + */ +#ifndef EVENT_CALLBACK_STUB_H_ +#define EVENT_CALLBACK_STUB_H_ + +/* mqtt.h must precede including this header. */ + +/** + * @brief User defined callback for receiving incoming publishes and incoming + * acks. + * + * @param[in] pContext Initialized MQTT context. + * @param[in] pPacketInfo Information on the type of incoming MQTT packet. + * @param[in] packetIdentifier Packet identifier of incoming PUBLISH packet. + * @param[in] pPublishInfo Incoming PUBLISH packet parameters. + */ +void EventCallbackStub( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ); + +#endif /* ifndef EVENT_CALLBACK_STUB_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/get_time_stub.h b/libraries/standard/mqtt/cbmc/include/get_time_stub.h new file mode 100644 index 0000000000..e96fa99ea3 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/include/get_time_stub.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file get_time_stub.h + * @brief Stub definition for the application defined callback to retrieve the + * current time in milliseconds. + */ +#ifndef GET_TIME_STUB_H_ +#define GET_TIME_STUB_H_ + +/** + * Application defined callback to retrieve the current time in milliseconds. + * + * @return The current time in milliseconds. + */ +uint32_t GetCurrentTimeStub( void ); + +#endif /* ifndef GET_TIME_STUB_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h index 2f9d44f6fc..568c1621ac 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_cbmc_state.h @@ -18,13 +18,17 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +/** + * @file mqtt_cbmc_state.h + * @brief Allocation and assumption utilities for the MQTT library CBMC proofs. + */ #ifndef MQTT_CBMC_STATE_H_ #define MQTT_CBMC_STATE_H_ #include -/* For MQTT Client library types. */ -#include "mqtt.h" +/* mqtt.h must precede including this header. */ /** * @brief Proof model for malloc that can fail and return NULL. @@ -144,4 +148,25 @@ MQTTSubscribeInfo_t * allocateMqttSubscriptionList( MQTTSubscribeInfo_t * pSubsc bool isValidMqttSubscriptionList( MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount ); +/** + * @brief Allocate a #MQTTContext_t object. + * + * @param[in] pBuffer #MQTTContext_t object information. + * + * @return NULL or allocated #MQTTContext_t memory. + */ +MQTTContext_t * allocateMqttContext( MQTTContext_t * pContext ); + +/** + * @brief Validate a #MQTTContext_t object. + * + * @param[in] pBuffer #MQTTContext_t object to validate. + * + * @return True if the #MQTTContext_t object is valid, false otherwise. + * + * @note A NULL object is a valid object. This is for coverage of the NULL + * parameter checks in the function under proof. + */ +bool isValidMqttContext( const MQTTContext_t * pContext ); + #endif /* ifndef MQTT_CBMC_STATE_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_config.h b/libraries/standard/mqtt/cbmc/include/mqtt_config.h index e805a164fc..94ca72df58 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_config.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_config.h @@ -20,6 +20,12 @@ #include "logging_stack.h" +/* Mock a network context for the CBMC proofs. */ +struct NetworkContext +{ + int NetworkContext; +}; + /************ End of logging configuration ****************/ /** diff --git a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h index 609fd38a11..3699bc15a4 100644 --- a/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h +++ b/libraries/standard/mqtt/cbmc/include/network_interface_stubs.h @@ -18,14 +18,16 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +/** + * @file network_interface_stubs.h + * @brief Stub definitions for the application defined transport interface send + * and receive callback. + */ #ifndef NETWORK_INTERFACE_STUBS_H_ -#define NETWORK_INTERfACE_STUBS_H_ +#define NETWORK_INTERFACE_STUBS_H_ -/* Mock a network context for the CBMC proofs. */ -struct NetworkContext -{ - int NetworkContext; -}; +/* transport_interface.h must precede including this header. */ /** * @brief Application defined network interface receive function. @@ -33,10 +35,24 @@ struct NetworkContext * @param[in] pNetworkContext Application defined network interface context. * @param[out] pBuffer MQTT network receive buffer. * @param[in] bytesToRecv MQTT requested bytes. + * * @return Any value from INT32_MIN to INT32_MAX. */ int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ); +/** + * @brief Application defined network interface send function. + * + * @param[in] pNetworkContext Application defined network interface context. + * @param[out] pBuffer MQTT network send buffer. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return Any value from INT32_MIN to INT32_MAX. + */ +int32_t NetworkInterfaceSendStub( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ); + #endif /* ifndef NETWORK_INTERFACE_STUBS_H_ */ diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c index e904268690..53fa29671a 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/MQTT_DeserializeAck_harness.c @@ -35,6 +35,10 @@ void harness() pIncomingPacket = allocateMqttPacketInfo( NULL ); __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); + /* These are allocated for coverage of a NULL input. */ + pPacketId = mallocCanFail( sizeof( uint16_t ) ); + pSessionPresent = mallocCanFail( sizeof( bool ) ); + MQTT_DeserializeAck( pIncomingPacket, pPacketId, pSessionPresent ); diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile index 95bca32b98..7984d21ba3 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializeAck/Makefile @@ -1,14 +1,36 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_DeserializeAck_harness -DEFINES += +# The maximum remaining length is bounded for MQTT_DeserializeAck() in order to +# place a limit on the number of iterations in deserializing a SUBACK. Please +# see REMAINING_LENGTH_MAX in libraries\standard\mqtt\cbmc\sources\mqtt_cbmc_state.c. +REMAINING_LENGTH_MAX=5 +DEFINES += -DREMAINING_LENGTH_MAX=$(REMAINING_LENGTH_MAX) INCLUDES += REMOVE_FUNCTION_BODY += -UNWINDSET += +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_readSubackStatus.0:$(REMAINING_LENGTH_MAX) PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c index bee97511de..c4b034e8dc 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/MQTT_DeserializePublish_harness.c @@ -31,7 +31,7 @@ void harness() { MQTTPacketInfo_t * pIncomingPacket; MQTTPublishInfo_t * pPublishInfo; - uint16_t packetId; + uint16_t * pPacketId; pIncomingPacket = allocateMqttPacketInfo( NULL ); __CPROVER_assume( isValidMqttPacketInfo( pIncomingPacket ) ); @@ -39,7 +39,9 @@ void harness() pPublishInfo = allocateMqttPublishInfo( NULL ); __CPROVER_assume( isValidMqttPublishInfo( pPublishInfo ) ); + pPacketId = mallocCanFail( sizeof( uint16_t ) ); + /* This function grabs the topic name, the topic name length, the * the payload, and the payload length. */ - MQTT_DeserializePublish( pIncomingPacket, packetId, pPublishInfo ); + MQTT_DeserializePublish( pIncomingPacket, pPacketId, pPublishInfo ); } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile index f6b921e4f1..07a4db8adb 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_DeserializePublish/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_DeserializePublish_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile index 664c913b1b..1a65462614 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetIncomingPacketTypeAndLength/Makefile @@ -1,9 +1,34 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + HARNESS_ENTRY=harness HARNESS_FILE=MQTT_GetIncomingPacketTypeAndLength_harness DEFINES += INCLUDES += REMOVE_FUNCTION_BODY += + +# The getRemainingLength loop is unwound 5 times because getRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_getRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile index c2f2b1ec5b..38cfc6004d 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetPacketId/Makefile @@ -1,3 +1,24 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + HARNESS_ENTRY=harness HARNESS_FILE=MQTT_GetPacketId_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c index bdd598b969..75729fe510 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/MQTT_Init_harness.c @@ -36,7 +36,9 @@ void harness() MQTTFixedBuffer_t * pNetworkBuffer; pContext = mallocCanFail( sizeof( MQTTContext_t ) ); - pTransportInterface = mallocCanFail( sizeof( MQTTContext_t ) ); + pTransportInterface = mallocCanFail( sizeof( TransportInterface_t ) ); + getTimeFunction = mallocCanFail( sizeof( MQTTGetCurrentTimeFunc_t ) ); + userCallback = mallocCanFail( sizeof( MQTTEventCallback_t ) ); pNetworkBuffer = mallocCanFail( sizeof( MQTTFixedBuffer_t ) ); MQTT_Init( pContext, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile index b987e303fb..8b21e51fac 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Init/Makefile @@ -1,3 +1,24 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + HARNESS_ENTRY=harness HARNESS_FILE=MQTT_Init_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/MQTT_Ping_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/MQTT_Ping_harness.c new file mode 100644 index 0000000000..559da9a1c8 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/MQTT_Ping_harness.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Ping_harness.c + * @brief Implements the proof harness for MQTT_Ping function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + MQTT_Ping( pContext ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile new file mode 100644 index 0000000000..9f52ac3b93 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile @@ -0,0 +1,45 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Ping_harness + +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on the MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +INCLUDES += + +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/README.md new file mode 100644 index 0000000000..231e2c67b8 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/README.md @@ -0,0 +1,10 @@ +MQTT_Ping proof +============== + +This directory contains a memory safety proof for MQTT_Ping. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-viewer.json new file mode 100644 index 0000000000..7ee974b28d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Ping", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c new file mode 100644 index 0000000000..7cf9082dcb --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_ProcessLoop_harness.c + * @brief Implements the proof harness for MQTT_ProcessLoop function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" +#include "network_interface_stubs.h" +#include "get_time_stub.h" +#include "event_callback_stub.h" + +void harness() +{ + MQTTContext_t * pContext; + uint32_t timeoutMs; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + __CPROVER_assume( timeoutMs < MQTT_PROCESS_LOOP_TIMEOUT ); + + /* The MQTT_PROCESS_LOOP_TIMEOUT is used here to control the number of loops + * when receiving on the network. The default is used here because memory + * safety can be proven in only a few iterations. */ + MQTT_ProcessLoop( pContext, timeoutMs ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile new file mode 100644 index 0000000000..850e01a36b --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile @@ -0,0 +1,74 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_ProcessLoop_harness + +# Bound on the timeout in MQTT_ProcessLoop. This timeout is bounded because +# memory saftey can be proven in a only a few iteration of the MQTT operations. +# Each iteration will try to receive a single packet in its entirey. With a time +# out of 2 we can get coverage of the entire function. Another iteration will +# performed unnecessarily duplicating of the proof. +MQTT_PROCESS_LOOP_TIMEOUT=3 +# Please see libraries/mqtt/cbmc/stubs/network_interface_subs.c for more +# information on the MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +# Please see libraries/standard/mqtt/cbmc/include/mqtt_config.h for more +# information. +MQTT_STATE_ARRAY_MAX_COUNT=11 +DEFINES += -DMQTT_PROCESS_LOOP_TIMEOUT=$(MQTT_PROCESS_LOOP_TIMEOUT) +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +INCLUDES += + +# These functions have their memory saftey proven in other harnesses. +REMOVE_FUNCTION_BODY += MQTT_Ping +REMOVE_FUNCTION_BODY += MQTT_DeserializeAck +REMOVE_FUNCTION_BODY += MQTT_SerializeAck + +UNWINDSET += MQTT_ProcessLoop.0:$(MQTT_PROCESS_LOOP_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_discardPacket.0:$(MQTT_PROCESS_LOOP_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_recvExact.0:$(MQTT_PROCESS_LOOP_TIMEOUT) +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) +# The getRemainingLength loop is unwound 5 times because getRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_getRemainingLength.0:5 +# These loops will run for the maximum number of publishes pending +# acknowledgements plus one. This value is set in +# libraries/standard/mqtt/cbmc/include/mqtt_config.h. +UNWINDSET += __CPROVER_file_local_mqtt_state_c_addRecord.0:$(MQTT_STATE_ARRAY_MAX_COUNT) +UNWINDSET += __CPROVER_file_local_mqtt_state_c_findInRecord.0:$(MQTT_STATE_ARRAY_MAX_COUNT) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/README.md new file mode 100644 index 0000000000..a3aaf3f22a --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/README.md @@ -0,0 +1,10 @@ +MQTT_ProcessLoop proof +============== + +This directory contains a memory safety proof for MQTT_ProcessLoop. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-viewer.json new file mode 100644 index 0000000000..ba8c554a4d --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_ProcessLoop", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile index 09385c39d9..861bd7c948 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeAck/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializeAck_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c index 568382314e..648d2222fe 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/MQTT_SerializeConnect_harness.c @@ -51,9 +51,9 @@ void harness() if( pConnectInfo != NULL ) { /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() - * must not be NULL. packetSize returned is not used in this proof, but - * is used normally by the application to verify the size of their - * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of its + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength * to recalculate the packetSize. */ status = MQTT_GetConnectPacketSize( pConnectInfo, pWillInfo, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile index 6ffa033935..51b51bf2b6 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializeConnect_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile index 6e3e8dc6cf..ac0d7e3e2f 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeDisconnect/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializeDisconnect_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile index 7546038354..6b48b75058 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePingreq/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializePingreq_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c index 0c892a005c..529e052505 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/MQTT_SerializePublish_harness.c @@ -48,9 +48,9 @@ void harness() if( pPublishInfo != NULL ) { /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() - * must not be NULL. packetSize returned is not used in this proof, but - * is used normally by the application to verify the size of their - * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of its + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength * to recalculate the packetSize. */ status = MQTT_GetPublishPacketSize( pPublishInfo, &remainingLength, &packetSize ); } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile index f628a733e4..7abf57ccc4 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializePublish_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c index 32f10912ac..3993cb32af 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/MQTT_SerializePublishHeader_harness.c @@ -53,9 +53,9 @@ void harness() if( pPublishInfo != NULL ) { /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() - * must not be NULL. packetSize returned is not used in this proof, but - * is used normally by the application to verify the size of their - * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of its + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength * to recalculate the packetSize. */ status = MQTT_GetPublishPacketSize( pPublishInfo, &remainingLength, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile index 1c666d2de6..86a51e237d 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializePublishHeader_harness diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c index c9871bbe3b..2d67dacde5 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c @@ -51,9 +51,9 @@ void harness() if( pSubscriptionList != NULL ) { /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() - * must not be NULL. packetSize returned is not used in this proof, but - * is used normally by the application to verify the size of their - * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of its + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength * to recalculate the packetSize. */ status = MQTT_GetSubscribePacketSize( pSubscriptionList, subscriptionCount, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile index 2ace429421..5d9930cd3f 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile @@ -1,12 +1,30 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializeSubscribe_harness -# Bind the subscription count. Please see the default value in mqtt_cbmc_state.c -# for more information on this bound. This is set to 2 currently to have the proof -# run quickly. +# Bound on the the subscription count. Please see the default value in +# mqtt_cbmc_state.c for more information on this bound. This is set to 2 +# currently to have the proof run quickly. SUBSCRIPTION_COUNT_MAX=2 DEFINES += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) INCLUDES += diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c index 63f7fecdd0..d539e19e0e 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c @@ -54,9 +54,9 @@ void harness() if( pSubscriptionList != NULL ) { /* The output parameter pPacketSize of the function MQTT_GetConnectPacketSize() - * must not be NULL. packetSize returned is not used in this proof, but - * is used normally by the application to verify the size of their - * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength + * must not be NULL. packetSize returned is not used in this proof, but + * is used normally by the application to verify the size of its + * MQTTFixedBuffer_t. MQTT_SerializeConnect() will use the remainingLength * to recalculate the packetSize. */ status = MQTT_GetUnsubscribePacketSize( pSubscriptionList, subscriptionCount, diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile index 2017873c40..273d079e09 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile @@ -1,12 +1,30 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=MQTT_SerializeUnsubscribe_harness -# Bind the subscription count. Please see the default value in mqtt_cbmc_state.c -# for more information on this bound. This is set to 2 currently to have the proof -# run quickly. +# Bound on the the subscription count. Please see the default value in +# mqtt_cbmc_state.c for more information on this bound. This is set to 2 +# currently to have the proof run quickly. SUBSCRIPTION_COUNT_MAX=2 DEFINES += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) INCLUDES += diff --git a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c index d794b9fafc..e9ab84a1c2 100644 --- a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +++ b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c @@ -20,7 +20,11 @@ */ #include #include +#include "mqtt.h" #include "mqtt_cbmc_state.h" +#include "network_interface_stubs.h" +#include "get_time_stub.h" +#include "event_callback_stub.h" /* A default bound on the subscription count. Iterating over possibly SIZE_MAX * number of subscriptions does not add any value to the proofs. An application @@ -31,6 +35,15 @@ #define SUBSCRIPTION_COUNT_MAX 1U #endif +/* A default bound on the remainingLength in an incoming packet. This bound + * is used for the MQTT_DeserializeAck() proof to limit the number of iterations + * on a SUBACK packet's payload bytes. We do not need to iterate an unbounded + * remaining length amount of bytes to verify memory safety in the dereferencing + * the SUBACK payload's bytes. */ +#ifndef REMAINING_LENGTH_MAX + #define REMAINING_LENGTH_MAX CBMC_MAX_OBJECT_SIZE +#endif + void * mallocCanFail( size_t size ) { __CPROVER_assert( size < CBMC_MAX_OBJECT_SIZE, "mallocCanFail size is too big" ); @@ -46,7 +59,9 @@ MQTTPacketInfo_t * allocateMqttPacketInfo( MQTTPacketInfo_t * pPacketInfo ) if( pPacketInfo != NULL ) { - __CPROVER_assume( pPacketInfo->remainingLength < CBMC_MAX_OBJECT_SIZE ); + __CPROVER_assert( REMAINING_LENGTH_MAX <= CBMC_MAX_OBJECT_SIZE, + "REMAINING_LENGTH_MAX size is too big" ); + __CPROVER_assume( pPacketInfo->remainingLength < REMAINING_LENGTH_MAX ); pPacketInfo->pRemainingData = mallocCanFail( pPacketInfo->remainingLength ); } @@ -59,7 +74,9 @@ bool isValidMqttPacketInfo( const MQTTPacketInfo_t * pPacketInfo ) if( pPacketInfo != NULL ) { - isValid = pPacketInfo->remainingLength < CBMC_MAX_OBJECT_SIZE; + __CPROVER_assert( REMAINING_LENGTH_MAX <= CBMC_MAX_OBJECT_SIZE, + "REMAINING_LENGTH_MAX size is too big" ); + isValid = pPacketInfo->remainingLength < REMAINING_LENGTH_MAX; } return isValid; @@ -89,13 +106,8 @@ bool isValidMqttPublishInfo( const MQTTPublishInfo_t * pPublishInfo ) if( pPublishInfo != NULL ) { - bool validQos = ( ( pPublishInfo->qos >= MQTTQoS0 ) && - ( pPublishInfo->qos <= MQTTQoS2 ) ); - - bool validTopicNameLength = pPublishInfo->topicNameLength < CBMC_MAX_OBJECT_SIZE; - bool validPayloadLength = pPublishInfo->payloadLength < CBMC_MAX_OBJECT_SIZE; - - isValid = validQos && validTopicNameLength && validPayloadLength; + isValid = isValid && ( pPublishInfo->topicNameLength < CBMC_MAX_OBJECT_SIZE ); + isValid = isValid && ( pPublishInfo->payloadLength < CBMC_MAX_OBJECT_SIZE ); } return isValid; @@ -199,3 +211,63 @@ bool isValidMqttSubscriptionList( MQTTSubscribeInfo_t * pSubscriptionList, return isValid; } + +MQTTContext_t * allocateMqttContext( MQTTContext_t * pContext ) +{ + TransportInterface_t * pTransportInterface; + MQTTFixedBuffer_t * pNetworkBuffer; + MQTTStatus_t status = MQTTSuccess; + + if( pContext == NULL ) + { + pContext = mallocCanFail( sizeof( MQTTContext_t ) ); + } + + pTransportInterface = mallocCanFail( sizeof( TransportInterface_t ) ); + + if( pTransportInterface != NULL ) + { + /* The possibility that recv and send callbacks are NULL is tested in the + * MQTT_Init proof. MQTT_Init is required to be called before any other + * function in mqtt.h. */ + pTransportInterface->recv = NetworkInterfaceReceiveStub; + pTransportInterface->send = NetworkInterfaceSendStub; + } + + pNetworkBuffer = allocateMqttFixedBuffer( NULL ); + __CPROVER_assume( isValidMqttFixedBuffer( pNetworkBuffer ) ); + + /* It is part of the API contract to call MQTT_Init() with the MQTTContext_t + * before any other function in mqtt.h. */ + if( pContext != NULL ) + { + status = MQTT_Init( pContext, + pTransportInterface, + GetCurrentTimeStub, + EventCallbackStub, + pNetworkBuffer ); + } + + /* If the MQTTContext_t initialization failed, then set the context to NULL + * so that function under harness will return immediately upon a NULL + * parameter check. */ + if( status != MQTTSuccess ) + { + pContext = NULL; + } + + return pContext; +} + +bool isValidMqttContext( const MQTTContext_t * pContext ) +{ + bool isValid = true; + + if( pContext != NULL ) + { + isValid = isValid && pContext->networkBuffer.size < CBMC_MAX_OBJECT_SIZE; + isValid = isValid && isValidMqttFixedBuffer( &( pContext->networkBuffer ) ); + } + + return isValid; +} diff --git a/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c b/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c new file mode 100644 index 0000000000..db005f29a9 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "mqtt.h" +#include "event_callback_stub.h" + +void EventCallbackStub( MQTTContext_t * pContext, + MQTTPacketInfo_t * pPacketInfo, + uint16_t packetIdentifier, + MQTTPublishInfo_t * pPublishInfo ) +{ + __CPROVER_assert( pContext != NULL, + "EventCallbackStub pContext is not NULL" ); + __CPROVER_assert( pPacketInfo != NULL, + "EventCallbackStub pPacketInfo is not NULL" ); + /* pPublishInfo will be NULL for an incoming ACK event. */ +} diff --git a/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c b/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c new file mode 100644 index 0000000000..6468371c80 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "mqtt.h" +#include "get_time_stub.h" + +uint32_t GetCurrentTimeStub( void ) +{ + /* There are loops in the MQTT library that rely on the timestamp being + * reasonable in order to complete. Returning an unbounded timestamp does + * not add value to the proofs as the MQTT library uses the timestamp for + * only arithmetic operations. In C arithmetic operations on unsigned + * integers are guaranteed to reliably wrap around with no adverse side + * effects. If the time returned was unbounded, the loops could be unwound + * a large number of times making the proof execution very long. */ + static uint32_t globalEntryTime = 0; + + return ++globalEntryTime; +} diff --git a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c index 9114025e6a..18dd12f3ee 100644 --- a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +++ b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c @@ -22,23 +22,65 @@ #include "mqtt.h" #include "network_interface_stubs.h" +/* An exclusive bound on the times that the NetworkInterfaceSendStub will be + * invoked before returning a loop terminating value. This is usually defined + * in the Makefile of the harnessed function. */ +#ifndef MAX_NETWORK_SEND_TRIES + #define MAX_NETWORK_SEND_TRIES 3 +#endif + int32_t NetworkInterfaceReceiveStub( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { __CPROVER_assert( pBuffer != NULL, - "IotNetworkInterfaceReceive pBuffer is not NULL." ); + "NetworkInterfaceReceiveStub pBuffer is not NULL." ); __CPROVER_assert( __CPROVER_w_ok( pBuffer, bytesToRecv ), - "pBuffer is writable up to bytesToRecv." ); + "NetworkInterfaceReceiveStub pBuffer is writable up to bytesToRecv." ); __CPROVER_havoc_object( pBuffer ); - /* This is unbounded as the MQTT code should be able to safely handle any - * int32_t value returned from the application defined network receive - * implementation. */ int32_t bytesOrError; - __CPROVER_assume( bytesOrError <= bytesToRecv ); + + /* It is a bug for the application defined transport send function to return + * more than bytesToRecv. */ + __CPROVER_assume( bytesOrError <= ( int32_t ) bytesToRecv ); + + return bytesOrError; +} + +int32_t NetworkInterfaceSendStub( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ) +{ + __CPROVER_assert( pBuffer != NULL, + "NetworkInterfaceSendStub pBuffer is not NULL." ); + + /* The number of tries to send the message before this invocation. */ + static size_t tries = 1; + + int32_t bytesOrError; + + /* It is a bug for the application defined transport send function to return + * more than bytesToSend. */ + __CPROVER_assume( bytesOrError <= ( int32_t ) bytesToSend ); + + /* If the maximum tries are reached, then return a timeout. In the MQTT library + * this stub is wrapped in a loop that will does not end until the bytesOrError + * returned is negative. This means we could loop possibly INT32_MAX + * iterations. Looping for INT32_MAX times adds no value to the proof. + * What matters is that the MQTT library can handle all the possible values + * that could be returned. */ + if( tries < MAX_NETWORK_SEND_TRIES ) + { + tries++; + } + else + { + tries = 1; + bytesOrError = bytesToSend; + } return bytesOrError; } diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index dff0f17608..2e5c15c0d5 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -35,14 +35,11 @@ */ #define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) -struct MQTTApplicationCallbacks; -typedef struct MQTTApplicationCallbacks MQTTApplicationCallbacks_t; - struct MQTTPubAckInfo; -typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; +typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; struct MQTTContext; -typedef struct MQTTContext MQTTContext_t; +typedef struct MQTTContext MQTTContext_t; /** * @brief Application provided callback to retrieve the current time in diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index a1a3b7ab3c..f94a099954 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -45,9 +45,11 @@ dup emptyindex endif enums +eventcallbackstub expectprocessloopcalls fixedbuffer getconnectpacketsize +getcurrenttimestub getdisconnectpacketsize getincomingpackettypeandlength getpacketid From c2f340509175a59af65b2523e7db4a3dcf3de493 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 29 Jul 2020 10:02:02 -0700 Subject: [PATCH 605/844] Integ tests/more cases of mqtt restore session (#1083) Add integration tests for: 1) Resending unacked PUBLISH packets (Qos 1 and QoS 2) 2) Receiving duplicate PUBLISH packets (QoS 1 and QoS 2) from the broker in restored session --- .../mqtt/integration-test/mqtt_system_test.c | 422 ++++++++++++++---- 1 file changed, 342 insertions(+), 80 deletions(-) diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index 444f1bd17c..e74e4ef235 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -35,6 +35,7 @@ #include "unity.h" /* Include paths for public enums, structures, and macros. */ #include "mqtt.h" +#include "mqtt_state.h" /* Include OpenSSL implementation of transport interface. */ #include "openssl_posix.h" @@ -256,7 +257,7 @@ static MQTTPublishInfo_t incomingInfo; * @brief Disconnect when receiving this packet type. Used for session * restoration tests. */ -static uint8_t disconnectOnPacketType = 0U; +static uint8_t disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. @@ -297,6 +298,38 @@ static void eventCallback( MQTTContext_t * pContext, uint16_t packetIdentifier, MQTTPublishInfo_t * pPublishInfo ); +/** + * @brief Implementation of TransportSend_t interface that terminates the TLS + * and TCP connection with the broker and returns failure. + * + * @param[in] pNetworkContext The context associated with the network connection + * to be disconnected. + * @param[in] pBuffer This parameter is ignored. + * @param[in] bytesToRecv This parameter is ignored. + * + * @return -1 to represent failure. + */ +static int32_t failedRecv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + + +/** + * @brief Helper function to start a new persistent session. + * It terminates the existing "clean session", and creates a new connection + * with the "clean session" flag set to 0 to create a persistent session + * with the broker. + */ +static void startPersistentSession(); + +/** + * @brief Helper function to resume connection in persistent session + * with the broker. + * It resumes the session with the broker by establishing a new connection + * with the "clean session" flag set to 0. + */ +static void resumePersistentSession(); + /*-----------------------------------------------------------*/ static void establishMqttSession( MQTTContext_t * pContext, @@ -469,7 +502,8 @@ static void eventCallback( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pPacketInfo != NULL ); - if( pPacketInfo->type == disconnectOnPacketType ) + if( ( pPacketInfo->type == disconnectOnPacketType ) || + ( ( pPacketInfo->type & 0xF0U ) == disconnectOnPacketType ) ) { /* Terminate MQTT connection with server for session restoration test. */ TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_Disconnect( &context ) ); @@ -562,7 +596,9 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext, static MQTTStatus_t publishToTopic( MQTTContext_t * pContext, const char * pTopic, - MQTTQoS_t qos ) + bool isDuplicate, + MQTTQoS_t qos, + uint16_t packetId ) { assert( pContext != NULL ); MQTTPublishInfo_t publishInfo; @@ -571,19 +607,74 @@ static MQTTStatus_t publishToTopic( MQTTContext_t * pContext, publishInfo.retain = false; publishInfo.qos = qos; - publishInfo.dup = false; + publishInfo.dup = isDuplicate; publishInfo.pTopicName = pTopic; publishInfo.topicNameLength = strlen( pTopic ); publishInfo.pPayload = MQTT_EXAMPLE_MESSAGE; publishInfo.payloadLength = strlen( MQTT_EXAMPLE_MESSAGE ); /* Get a new packet id. */ - globalPublishPacketIdentifier = MQTT_GetPacketId( pContext ); + globalPublishPacketIdentifier = packetId; /* Send PUBLISH packet. */ return MQTT_Publish( pContext, &publishInfo, - globalPublishPacketIdentifier ); + packetId ); +} + +static int32_t failedRecv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + ( void ) pBuffer; + ( void ) bytesToRecv; + + /* Terminate the TLS+TCP connection with the broker for the test. */ + ( void ) Openssl_Disconnect( pNetworkContext ); + + return -1; +} + +static void startPersistentSession() +{ + /* Terminate TLS session and TCP network connection to discard the current MQTT session + * that was created as a "clean session". */ + ( void ) Openssl_Disconnect( &networkContext ); + + /* Establish a new MQTT connection over TLS with the broker with the "clean session" flag set to 0 + * to start a persistent session with the broker. */ + + /* Create the TLS+TCP connection with the broker. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Establish a new MQTT connection for a persistent session with the broker. */ + establishMqttSession( &context, &networkContext, false, &persistentSession ); + TEST_ASSERT_FALSE( persistentSession ); +} + +static void resumePersistentSession() +{ + /* Create a new TLS+TCP network connection with the server. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Re-establish the persistent session with the broker by connecting with "clean session" flag set to 0. */ + TEST_ASSERT_FALSE( persistentSession ); + establishMqttSession( &context, &networkContext, false, &persistentSession ); + + /* Verify that the session was resumed. */ + TEST_ASSERT_TRUE( persistentSession ); } /* ============================ UNITY FIXTURES ============================ */ @@ -600,7 +691,7 @@ void setUp() receivedPubComp = false; persistentSession = false; useLWTClientIdentifier = false; - disconnectOnPacketType = 0U; + disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; memset( &incomingInfo, 0u, sizeof( MQTTPublishInfo_t ) ); memset( &opensslCredentials, 0u, sizeof( OpensslCredentials_t ) ); opensslCredentials.pRootCaPath = SERVER_ROOT_CA_CERT_PATH; @@ -664,7 +755,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_0( void ) /* Publish to the same topic, that we subscribed to, with Qos 0. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, MQTTQoS0 ) ); + &context, TEST_MQTT_TOPIC, false, MQTTQoS0, MQTT_GetPacketId( &context ) ) ); /* Call the MQTT library for the expectation to read an incoming PUBLISH for * the same message that we published (as we have subscribed to the same topic). */ @@ -715,7 +806,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_1( void ) /* Publish to the same topic, that we subscribed to, with Qos 1. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); + &context, TEST_MQTT_TOPIC, false, MQTTQoS1, MQTT_GetPacketId( &context ) ) ); /* Make sure that the MQTT context state was updated after the PUBLISH request. */ TEST_ASSERT_EQUAL( MQTTQoS1, context.outgoingPublishRecords[ 0 ].qos ); @@ -771,7 +862,7 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) /* Publish to the same topic, that we subscribed to, with Qos 2. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); /* Make sure that the MQTT context state was updated after the PUBLISH request. */ TEST_ASSERT_EQUAL( MQTTQoS2, context.outgoingPublishRecords[ 0 ].qos ); @@ -905,29 +996,12 @@ void test_MQTT_ProcessLoop_KeepAlive( void ) */ void test_MQTT_Restore_Session_Resend_PubRel( void ) { - /* Terminate TLS session and TCP network connection to discard the current MQTT session - * that was created as a "clean session". */ - ( void ) Openssl_Disconnect( &networkContext ); - - /* Establish a new MQTT connection over TLS with the broker with the "clean session" flag set to 0 - * to start a persistent session with the broker. */ - - /* Create the TLS+TCP connection with the broker. */ - TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, - &serverInfo, - &opensslCredentials, - TRANSPORT_SEND_RECV_TIMEOUT_MS, - TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); - TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); - TEST_ASSERT_NOT_NULL( networkContext.pSsl ); - - /* Establish a new MQTT connection for a persistent session with the broker. */ - establishMqttSession( &context, &networkContext, false, &persistentSession ); - TEST_ASSERT_FALSE( persistentSession ); + /* Start a persistent session with the broker. */ + startPersistentSession(); /* Publish to a topic with Qos 2. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); /* Disconnect on receiving PUBREC so that we are not able to complete the QoS 2 PUBLISH in the current connection. */ TEST_ASSERT_FALSE( receivedPubComp ); @@ -939,25 +1013,10 @@ void test_MQTT_Restore_Session_Resend_PubRel( void ) /* Verify that the connection with the broker has been disconnected. */ TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); - /* We will re-establish an MQTT over TLS connection with the broker to restore the persistent session. */ - - /* Create a new TLS+TCP network connection with the server. */ - TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, - &serverInfo, - &opensslCredentials, - TRANSPORT_SEND_RECV_TIMEOUT_MS, - TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); - TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); - TEST_ASSERT_NOT_NULL( networkContext.pSsl ); - - /* Re-establish the persistent session with the broker by connecting with "clean session" flag set to 0. */ - establishMqttSession( &context, &networkContext, false, &persistentSession ); - - /* Verify that the session was resumed. */ - TEST_ASSERT_TRUE( persistentSession ); - + /* We will re-establish an MQTT over TLS connection with the broker to restore + * the persistent session. */ + resumePersistentSession(); /* Resume the incomplete QoS 2 PUBLISH in previous MQTT connection. */ - TEST_ASSERT_FALSE( receivedPubComp ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); @@ -974,26 +1033,8 @@ void test_MQTT_Restore_Session_Resend_PubRel( void ) */ void test_MQTT_Restore_Session_Complete_Incoming_Publish( void ) { - /* Terminate TLS session and TCP connection network connection to discard current MQTT session - * that was created as a "clean session". */ - ( void ) Openssl_Disconnect( &networkContext ); - - /* Establish a new MQTT connection over TLS with the broker with the "clean session" flag set to 0 - * to start a persistent session with the broker. */ - - /* Create the TLS+TCP connection with the broker. */ - TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, - &serverInfo, - &opensslCredentials, - TRANSPORT_SEND_RECV_TIMEOUT_MS, - TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); - TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); - TEST_ASSERT_NOT_NULL( networkContext.pSsl ); - - /* Establish a new MQTT connection for starting a persistent session with the broker - * by setting the "clean session" flag to 0. */ - establishMqttSession( &context, &networkContext, false, &persistentSession ); - TEST_ASSERT_FALSE( persistentSession ); + /* Start a persistent session with the broker. */ + startPersistentSession(); /* Subscribe to a topic from which we will be receiving an incomplete incoming * QoS 2 PUBLISH transaction in this connection. */ @@ -1006,7 +1047,7 @@ void test_MQTT_Restore_Session_Complete_Incoming_Publish( void ) /* Publish to the same topic with Qos 2 (so that the broker can re-publish it back to us). */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); /* Disconnect on receiving PUBREL so that we are not able to complete in the incoming QoS2 * PUBLISH in the current connection. */ @@ -1017,9 +1058,57 @@ void test_MQTT_Restore_Session_Complete_Incoming_Publish( void ) /* Verify that the connection with the broker has been disconnected. */ TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); - /* We will re-establish an MQTT over TLS connection with the broker to restore the persistent session. */ + /* We will re-establish an MQTT over TLS connection with the broker to restore + * the persistent session. */ + resumePersistentSession(); - /* Create a new TLS+TCP network connection with the server. */ + /* Clear the global variable for not disconnecting on PUBREL + * that we receive from the broker on the session restoration. */ + disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; + + /* Resume the incomplete incoming QoS 2 PUBLISH transaction from the previous MQTT connection. */ + TEST_ASSERT_FALSE( receivedPubRel ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that the broker resent the PUBREL packet on session restoration. */ + TEST_ASSERT_TRUE( receivedPubRel ); + + /* Make sure that the library sent a PUBCOMP packet in response to the PUBREL packet + * from the server to complete the incoming PUBLISH QoS2 transaction. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); +} + +/** + * @brief Verifies that the MQTT library supports resending a PUBLISH QoS 1 packet which is + * un-acknowledged in its first attempt. + * Tests that the library is able to support resending the PUBLISH packet with the DUP flag. + */ +void test_MQTT_Resend_Unacked_Publish_QoS1( void ) +{ + /* Initiate the PUBLISH operation at QoS 1. The library should add an + * outgoing PUBLISH record in the context. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, false, MQTTQoS1, MQTT_GetPacketId( &context ) ) ); + + /* Setup the MQTT connection to terminate to simulate incomplete PUBLISH operation. */ + context.transportInterface.recv = failedRecv; + + /* Attempt to complete the PUBLISH operation at QoS1 which should fail due + * to terminated network connection. + * The abrupt network disconnection should cause the PUBLISH packet to be left + * in an un-acknowledged state in the MQTT context. */ + TEST_ASSERT_EQUAL( MQTTRecvFailed, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Verify that the library has stored the PUBLISH as an incomplete operation. */ + TEST_ASSERT_NOT_EQUAL( MQTT_PACKET_ID_INVALID, context.outgoingPublishRecords[ 0 ].packetId ); + + + /* Reset the transport receive function in the context. */ + context.transportInterface.recv = Openssl_Recv; + + /* Re-establish a TLS+TCP network connection with the server. */ TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, &serverInfo, &opensslCredentials, @@ -1028,25 +1117,198 @@ void test_MQTT_Restore_Session_Complete_Incoming_Publish( void ) TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); TEST_ASSERT_NOT_NULL( networkContext.pSsl ); - /* Re-establish the persistent session with the broker by connecting with "clean session" flag set to 0. */ + /* Re-establish a connection with the broker to resend the PUBLISH packet. */ establishMqttSession( &context, &networkContext, false, &persistentSession ); - /* Verify that the session was resumed. */ - TEST_ASSERT_TRUE( persistentSession ); + /* Obtain the packet ID of the PUBLISH packet that didn't complete in the previous connection. */ + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + uint16_t publishPackedId = MQTT_PublishToResend( &context, &cursor ); + TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 0 ].packetId, publishPackedId ); - /* Clear the global variable for not disconnecting on PUBREL - * that we receive from the broker on the session restoration. */ + /* Resend the PUBLISH packet that didn't complete in the previous connection. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, true, MQTTQoS1, publishPackedId ) ); + + /* Complete the QoS 1 PUBLISH resend operation. */ + TEST_ASSERT_FALSE( receivedPubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that the PUBLISH resend was complete. */ + TEST_ASSERT_TRUE( receivedPubAck ); + + /* Make sure that the library has removed the record for the outgoing PUBLISH packet. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.outgoingPublishRecords[ 0 ].packetId ); +} + +/** + * @brief Verifies that the MQTT library supports resending a PUBLISH QoS 2 packet which is + * un-acknowledged in its first attempt. + * Tests that the library is able to support resending the PUBLISH packet with the DUP flag. + */ +void test_MQTT_Resend_Unacked_Publish_QoS2( void ) +{ + /* Initiate the PUBLISH operation at QoS 2. The library should add an + * outgoing PUBLISH record in the context. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + + /* Setup the MQTT connection to terminate to simulate incomplete PUBLISH operation. */ + context.transportInterface.recv = failedRecv; + + /* Attempt to complete the PUBLISH operation at QoS 2 which should fail due + * to terminated network connection. + * The abrupt network disconnection should cause the PUBLISH packet to be left + * in an un-acknowledged state in the MQTT context. */ + TEST_ASSERT_EQUAL( MQTTRecvFailed, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Verify that the library has stored the PUBLISH as an incomplete operation. */ + TEST_ASSERT_NOT_EQUAL( MQTT_PACKET_ID_INVALID, context.outgoingPublishRecords[ 0 ].packetId ); + + /* Reset the transport receive function in the context. */ + context.transportInterface.recv = Openssl_Recv; + + /* Re-establish a TLS+TCP network connection with the server. */ + TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ) ); + TEST_ASSERT_NOT_EQUAL( -1, networkContext.socketDescriptor ); + TEST_ASSERT_NOT_NULL( networkContext.pSsl ); + + /* Re-establish a connection with the broker to resend the PUBLISH packet. */ + establishMqttSession( &context, &networkContext, false, &persistentSession ); + + /* Obtain the packet ID of the PUBLISH packet that didn't complete in the previous connection. */ + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + uint16_t publishPackedId = MQTT_PublishToResend( &context, &cursor ); + TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 0 ].packetId, publishPackedId ); + + /* Resend the PUBLISH packet that didn't complete in the previous connection. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, true, MQTTQoS2, publishPackedId ) ); + + /* Complete the QoS 2 PUBLISH resend operation. */ + TEST_ASSERT_FALSE( receivedPubRec ); + TEST_ASSERT_FALSE( receivedPubComp ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that the QoS 2 PUBLISH re-transmission was complete. */ + TEST_ASSERT_TRUE( receivedPubRec ); + TEST_ASSERT_TRUE( receivedPubComp ); + + /* Make sure that the library has removed the record for the outgoing PUBLISH packet. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.outgoingPublishRecords[ 0 ].packetId ); +} + +/** + * @brief Verifies the behavior of the MQTT library on receiving a duplicate + * QoS 1 PUBLISH packet from the broker in a restored session connection. + * Tests that the library responds with a PUBACK to the duplicate incoming QoS 1 PUBLISH + * packet that was un-acknowledged in a previous connection of the same session. + */ +void test_MQTT_MQTT_Restore_Session_Duplicate_Incoming_Publish_Qos1( void ) +{ + /* Start a persistent session with the broker. */ + startPersistentSession(); + + /* Subscribe to a topic from which we will be receiving an incomplete incoming + * QoS 2 PUBLISH transaction in this connection. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); + TEST_ASSERT_FALSE( receivedSubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Publish to the same topic with Qos 1 (so that the broker can re-publish it back to us). */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, false, MQTTQoS1, MQTT_GetPacketId( &context ) ) ); + + /* Disconnect on receiving the incoming PUBLISH packet from the broker so that + * an acknowledgement cannot be sent to the broker. */ + disconnectOnPacketType = MQTT_PACKET_TYPE_PUBLISH; + TEST_ASSERT_EQUAL( MQTTSendFailed, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that a record was created for the incoming PUBLISH packet. */ + TEST_ASSERT_NOT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); + + /* Verify that the connection with the broker has been disconnected. */ + TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); + + /* We will re-establish an MQTT over TLS connection with the broker to restore + * the persistent session. */ + resumePersistentSession(); + + /* Clear the global variable for not disconnecting on the duplicate PUBLISH + * packet that we receive from the broker on the session restoration. */ disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; - /* Resume the incomplete incoming QoS 2 PUBLISH transaction from the previous MQTT connection. */ + /* Process the duplicate incoming QoS 1 PUBLISH that will be sent by the broker + * to re-attempt the PUBLISH operation. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that the library cleared the record for the incoming QoS 1 PUBLISH packet. */ + TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); +} + +/** + * @brief Verifies the behavior of the MQTT library on receiving a duplicate + * QoS 2 PUBLISH packet from the broker in a restored session connection. + * Tests that the library responds with the ack packets for the incoming duplicate + * QoS 2 PUBLISH packet that was un-acknowledged in a previous connection of the same session. + */ +void test_MQTT_Restore_Session_Duplicate_Incoming_Publish_Qos2( void ) +{ + /* Start a persistent session with the broker. */ + startPersistentSession(); + + /* Subscribe to a topic from which we will be receiving an incomplete incoming + * QoS 2 PUBLISH transaction in this connection. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS2 ) ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Publish to the same topic with Qos 2 (so that the broker can re-publish it back to us). */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( + &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + + /* Disconnect on receiving the incoming PUBLISH packet from the broker so that + * an acknowledgement cannot be sent to the broker. */ + disconnectOnPacketType = MQTT_PACKET_TYPE_PUBLISH; + TEST_ASSERT_EQUAL( MQTTSendFailed, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + + /* Make sure that a record was created for the incoming PUBLISH packet. */ + TEST_ASSERT_NOT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); + + /* Verify that the connection with the broker has been disconnected. */ + TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus ); + + /* We will re-establish an MQTT over TLS connection with the broker to restore + * the persistent session. */ + resumePersistentSession(); + + /* Clear the global variable for not disconnecting on the duplicate PUBLISH + * packet that we receive from the broker on the session restoration. */ + disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; + + /* Process the duplicate incoming QoS 2 PUBLISH that will be sent by the broker + * to re-attempt the PUBLISH operation. */ TEST_ASSERT_FALSE( receivedPubRel ); TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); - /* Make sure that the broker resent the PUBREL packet on session restoration. */ + /* Make sure that the incoming QoS 2 transaction was completed. */ TEST_ASSERT_TRUE( receivedPubRel ); - /* Make sure that the library sent a PUBCOMP packet in response to the PUBREL packet - * from the server to complete the incoming PUBLISH QoS2 transaction. */ + /* Make sure that the library cleared the record for the incoming QoS 2 PUBLISH packet. */ TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); } From 6584f2f75f665c70ce2e90e9636248ef6756eca3 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 31 Jul 2020 11:46:18 -0700 Subject: [PATCH 606/844] Integ tests/mqtt retain flag (#1086) Add integration test for verifying that the MQTT library supports the "retained" PUBLISH message feature --- .../mqtt/integration-test/mqtt_system_test.c | 164 ++++++++++++++++-- libraries/standard/mqtt/lexicon.txt | 2 + 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index e74e4ef235..af3de7a44b 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -96,6 +96,11 @@ */ #define TEST_MQTT_TOPIC "/iot/integration/test" +/** + * @brief Sample topic filter 2 to use in tests. + */ +#define TEST_MQTT_TOPIC_2 "/iot/integration/test2" + /** * @brief Length of sample topic filter. */ @@ -248,6 +253,12 @@ static bool receivedPubRel = false; */ static bool receivedPubComp = false; +/** + * @brief Flag to represent whether an incoming PUBLISH packet is received + * with the "retain" flag set. + */ +static bool receivedRetainedMessage = false; + /** * @brief Represents incoming PUBLISH information. */ @@ -534,6 +545,10 @@ static void eventCallback( MQTTContext_t * pContext, incomingInfo.pPayload = malloc( pPublishInfo->payloadLength ); TEST_ASSERT_NOT_NULL( incomingInfo.pPayload ); memcpy( ( void * ) incomingInfo.pPayload, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + + /* Update the global variable if the incoming PUBLISH packet + * represents a retained message. */ + receivedRetainedMessage = pPublishInfo->retain; } else { @@ -593,9 +608,9 @@ static MQTTStatus_t unsubscribeFromTopic( MQTTContext_t * pContext, globalUnsubscribePacketIdentifier ); } - static MQTTStatus_t publishToTopic( MQTTContext_t * pContext, const char * pTopic, + bool setRetainFlag, bool isDuplicate, MQTTQoS_t qos, uint16_t packetId ) @@ -603,8 +618,7 @@ static MQTTStatus_t publishToTopic( MQTTContext_t * pContext, assert( pContext != NULL ); MQTTPublishInfo_t publishInfo; - /* Set the retain flag to false to avoid side-effects across test runs. */ - publishInfo.retain = false; + publishInfo.retain = setRetainFlag; publishInfo.qos = qos; publishInfo.dup = isDuplicate; @@ -689,6 +703,7 @@ void setUp() receivedPubRec = false; receivedPubRel = false; receivedPubComp = false; + receivedRetainedMessage = false; persistentSession = false; useLWTClientIdentifier = false; disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; @@ -755,7 +770,12 @@ void test_MQTT_Subscribe_Publish_With_Qos_0( void ) /* Publish to the same topic, that we subscribed to, with Qos 0. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS0, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS0, + MQTT_GetPacketId( &context ) ) ); /* Call the MQTT library for the expectation to read an incoming PUBLISH for * the same message that we published (as we have subscribed to the same topic). */ @@ -806,7 +826,12 @@ void test_MQTT_Subscribe_Publish_With_Qos_1( void ) /* Publish to the same topic, that we subscribed to, with Qos 1. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS1, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS1, + MQTT_GetPacketId( &context ) ) ); /* Make sure that the MQTT context state was updated after the PUBLISH request. */ TEST_ASSERT_EQUAL( MQTTQoS1, context.outgoingPublishRecords[ 0 ].qos ); @@ -862,7 +887,12 @@ void test_MQTT_Subscribe_Publish_With_Qos_2( void ) /* Publish to the same topic, that we subscribed to, with Qos 2. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS2, + MQTT_GetPacketId( &context ) ) ); /* Make sure that the MQTT context state was updated after the PUBLISH request. */ TEST_ASSERT_EQUAL( MQTTQoS2, context.outgoingPublishRecords[ 0 ].qos ); @@ -1001,7 +1031,12 @@ void test_MQTT_Restore_Session_Resend_PubRel( void ) /* Publish to a topic with Qos 2. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS2, + MQTT_GetPacketId( &context ) ) ); /* Disconnect on receiving PUBREC so that we are not able to complete the QoS 2 PUBLISH in the current connection. */ TEST_ASSERT_FALSE( receivedPubComp ); @@ -1047,7 +1082,12 @@ void test_MQTT_Restore_Session_Complete_Incoming_Publish( void ) /* Publish to the same topic with Qos 2 (so that the broker can re-publish it back to us). */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS2, + MQTT_GetPacketId( &context ) ) ); /* Disconnect on receiving PUBREL so that we are not able to complete in the incoming QoS2 * PUBLISH in the current connection. */ @@ -1089,7 +1129,12 @@ void test_MQTT_Resend_Unacked_Publish_QoS1( void ) /* Initiate the PUBLISH operation at QoS 1. The library should add an * outgoing PUBLISH record in the context. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS1, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS1, + MQTT_GetPacketId( &context ) ) ); /* Setup the MQTT connection to terminate to simulate incomplete PUBLISH operation. */ context.transportInterface.recv = failedRecv; @@ -1127,7 +1172,12 @@ void test_MQTT_Resend_Unacked_Publish_QoS1( void ) /* Resend the PUBLISH packet that didn't complete in the previous connection. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, true, MQTTQoS1, publishPackedId ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + true, /* isDuplicate */ + MQTTQoS1, + publishPackedId ) ); /* Complete the QoS 1 PUBLISH resend operation. */ TEST_ASSERT_FALSE( receivedPubAck ); @@ -1151,7 +1201,12 @@ void test_MQTT_Resend_Unacked_Publish_QoS2( void ) /* Initiate the PUBLISH operation at QoS 2. The library should add an * outgoing PUBLISH record in the context. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS2, + MQTT_GetPacketId( &context ) ) ); /* Setup the MQTT connection to terminate to simulate incomplete PUBLISH operation. */ context.transportInterface.recv = failedRecv; @@ -1188,7 +1243,12 @@ void test_MQTT_Resend_Unacked_Publish_QoS2( void ) /* Resend the PUBLISH packet that didn't complete in the previous connection. */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, true, MQTTQoS2, publishPackedId ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + true, /* isDuplicate */ + MQTTQoS2, + publishPackedId ) ); /* Complete the QoS 2 PUBLISH resend operation. */ TEST_ASSERT_FALSE( receivedPubRec ); @@ -1226,7 +1286,12 @@ void test_MQTT_MQTT_Restore_Session_Duplicate_Incoming_Publish_Qos1( void ) /* Publish to the same topic with Qos 1 (so that the broker can re-publish it back to us). */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS1, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS1, + MQTT_GetPacketId( &context ) ) ); /* Disconnect on receiving the incoming PUBLISH packet from the broker so that * an acknowledgement cannot be sent to the broker. */ @@ -1278,7 +1343,12 @@ void test_MQTT_Restore_Session_Duplicate_Incoming_Publish_Qos2( void ) /* Publish to the same topic with Qos 2 (so that the broker can re-publish it back to us). */ TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( - &context, TEST_MQTT_TOPIC, false, MQTTQoS2, MQTT_GetPacketId( &context ) ) ); + &context, + TEST_MQTT_TOPIC, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS2, + MQTT_GetPacketId( &context ) ) ); /* Disconnect on receiving the incoming PUBLISH packet from the broker so that * an acknowledgement cannot be sent to the broker. */ @@ -1312,3 +1382,69 @@ void test_MQTT_Restore_Session_Duplicate_Incoming_Publish_Qos2( void ) /* Make sure that the library cleared the record for the incoming QoS 2 PUBLISH packet. */ TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, context.incomingPublishRecords[ 0 ].packetId ); } + +/** + * @brief Verifies that the library supports notifying the broker to retain a PUBLISH message + * for a topic using the retain flag. + */ +void test_MQTT_Publish_With_Retain_Flag( void ) +{ + /* Publish to a topic with the "retain" flag set. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( &context, + TEST_MQTT_TOPIC, + true, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS1, + MQTT_GetPacketId( &context ) ) ); + /* Complete the QoS 1 PUBLISH operation. */ + TEST_ASSERT_FALSE( receivedPubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedPubAck ); + + /* Subscribe to the same topic that we published the message to. + * The broker should send the "retained" message with the "retain" flag set. */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC, MQTTQoS1 ) ); + TEST_ASSERT_FALSE( receivedSubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Make sure that the library invoked the event callback with the incoming PUBLISH from + * the broker containing the "retained" flag set. */ + TEST_ASSERT_TRUE( receivedRetainedMessage ); + + /* Reset the global variables for the remainder of the test. */ + receivedPubAck = false; + receivedSubAck = false; + receivedUnsubAck = false; + receivedRetainedMessage = false; + + /* Publish to another topic with the "retain" flag set to 0. */ + TEST_ASSERT_EQUAL( MQTTSuccess, publishToTopic( &context, + TEST_MQTT_TOPIC_2, + false, /* setRetainFlag */ + false, /* isDuplicate */ + MQTTQoS1, + MQTT_GetPacketId( &context ) ) ); + + /* Complete the QoS 1 PUBLISH operation. */ + TEST_ASSERT_FALSE( receivedPubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedPubAck ); + + /* Again, subscribe to the same topic that we just published to. + * We don't expect the broker to send the message to us (as we + * PUBLISHed without a retain flag set). */ + TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( + &context, TEST_MQTT_TOPIC_2, MQTTQoS1 ) ); + TEST_ASSERT_FALSE( receivedSubAck ); + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, 2 * MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + + /* Make sure that the library did not receive an incoming PUBLISH from the broker. */ + TEST_ASSERT_FALSE( receivedRetainedMessage ); +} diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index f94a099954..8f7be1438c 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -72,6 +72,7 @@ incomingpublish init int iot +isduplicate isn lsb lwt @@ -206,6 +207,7 @@ serializepublish serializepublishheader serializesubscribe serializeunsubscribe +setretainflag shoulddelete shouldn sizeof From ec365a67ae2f3154ab7c4b6ae4cb8554ecd24c4b Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Sat, 1 Aug 2020 21:17:39 -0700 Subject: [PATCH 607/844] CBMC Proofs for MQTT and Some HTTPClient_Send proof updates (#1079) * Add MQTT proofs: MQTT_Connect MQTT_Disconnect Proof MQTT_Publish MQTT_Subscribe harness MQTT_Unsubscribe MQTT_ReceiveLoop proof * Add some clarification comments. --- .gitignore | 7 -- libraries/standard/http/cbmc/.gitignore | 15 ++++ .../cbmc/proofs/HTTPClient_AddHeader/Makefile | 4 +- .../Makefile | 5 +- .../HTTPClient_Send/HTTPClient_Send_harness.c | 34 +++----- .../http/cbmc/proofs/HTTPClient_Send/Makefile | 34 ++++++-- .../http/cbmc/proofs/Makefile-project-targets | 8 +- .../http/cbmc/proofs/Makefile-project-testing | 9 +- libraries/standard/mqtt/cbmc/.gitignore | 15 ++++ .../standard/mqtt/cbmc/include/mqtt_config.h | 13 ++- .../MQTT_Connect/MQTT_Connect_harness.c | 54 ++++++++++++ .../mqtt/cbmc/proofs/MQTT_Connect/Makefile | 83 +++++++++++++++++++ .../mqtt/cbmc/proofs/MQTT_Connect/README.md | 10 +++ .../cbmc/proofs/MQTT_Connect/cbmc-batch.yaml | 2 + .../cbmc/proofs/MQTT_Connect/cbmc-viewer.json | 7 ++ .../MQTT_Disconnect/MQTT_Disconnect_harness.c | 37 +++++++++ .../mqtt/cbmc/proofs/MQTT_Disconnect/Makefile | 48 +++++++++++ .../cbmc/proofs/MQTT_Disconnect/README.md | 10 +++ .../proofs/MQTT_Disconnect/cbmc-batch.yaml | 2 + .../proofs/MQTT_Disconnect/cbmc-viewer.json | 7 ++ .../mqtt/cbmc/proofs/MQTT_Ping/Makefile | 2 +- .../MQTT_ProcessLoop_harness.c | 12 ++- .../cbmc/proofs/MQTT_ProcessLoop/Makefile | 14 ++-- .../MQTT_Publish/MQTT_Publish_harness.c | 42 ++++++++++ .../mqtt/cbmc/proofs/MQTT_Publish/Makefile | 60 ++++++++++++++ .../mqtt/cbmc/proofs/MQTT_Publish/README.md | 10 +++ .../cbmc/proofs/MQTT_Publish/cbmc-batch.yaml | 2 + .../cbmc/proofs/MQTT_Publish/cbmc-viewer.json | 7 ++ .../MQTT_ReceiveLoop_harness.c | 44 ++++++++++ .../cbmc/proofs/MQTT_ReceiveLoop/Makefile | 55 ++++++++++++ .../cbmc/proofs/MQTT_ReceiveLoop/README.md | 10 +++ .../proofs/MQTT_ReceiveLoop/cbmc-batch.yaml | 2 + .../proofs/MQTT_ReceiveLoop/cbmc-viewer.json | 7 ++ .../proofs/MQTT_SerializeConnect/Makefile | 12 ++- .../proofs/MQTT_SerializePublish/Makefile | 5 +- .../MQTT_SerializePublishHeader/Makefile | 5 +- .../MQTT_SerializeSubscribe_harness.c | 2 + .../proofs/MQTT_SerializeSubscribe/Makefile | 5 +- .../MQTT_SerializeUnsubscribe_harness.c | 2 + .../proofs/MQTT_SerializeUnsubscribe/Makefile | 5 +- .../MQTT_Subscribe/MQTT_Subscribe_harness.c | 47 +++++++++++ .../mqtt/cbmc/proofs/MQTT_Subscribe/Makefile | 62 ++++++++++++++ .../mqtt/cbmc/proofs/MQTT_Subscribe/README.md | 10 +++ .../proofs/MQTT_Subscribe/cbmc-batch.yaml | 2 + .../proofs/MQTT_Subscribe/cbmc-viewer.json | 7 ++ .../MQTT_Unsubscribe_harness.c | 47 +++++++++++ .../cbmc/proofs/MQTT_Unsubscribe/Makefile | 62 ++++++++++++++ .../cbmc/proofs/MQTT_Unsubscribe/README.md | 10 +++ .../proofs/MQTT_Unsubscribe/cbmc-batch.yaml | 2 + .../proofs/MQTT_Unsubscribe/cbmc-viewer.json | 7 ++ .../mqtt/cbmc/proofs/Makefile-project-targets | 8 +- .../mqtt/cbmc/proofs/Makefile-project-testing | 9 +- .../mqtt/cbmc/sources/mqtt_cbmc_state.c | 22 ++--- libraries/standard/mqtt/cbmc/stubs/memcpy.c | 54 ++++++++++++ .../mqtt/cbmc/stubs/network_interface_stubs.c | 2 +- libraries/standard/mqtt/lexicon.txt | 1 + libraries/standard/mqtt/src/mqtt.c | 5 ++ tools/aws-templates-for-cbmc-proofs | 2 +- 58 files changed, 988 insertions(+), 78 deletions(-) create mode 100644 libraries/standard/http/cbmc/.gitignore mode change 120000 => 100644 libraries/standard/http/cbmc/proofs/Makefile-project-targets mode change 120000 => 100644 libraries/standard/http/cbmc/proofs/Makefile-project-testing create mode 100644 libraries/standard/mqtt/cbmc/.gitignore create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/MQTT_Connect_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/MQTT_Disconnect_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/MQTT_Publish_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/MQTT_ReceiveLoop_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/MQTT_Subscribe_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-viewer.json create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/MQTT_Unsubscribe_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-viewer.json mode change 120000 => 100644 libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets mode change 120000 => 100644 libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing create mode 100644 libraries/standard/mqtt/cbmc/stubs/memcpy.c diff --git a/.gitignore b/.gitignore index 490153eb5c..c1cb0f6609 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,3 @@ build/ *.gcda *.gcno *.gcov - -# Ignore CBMC proof artifacts -**/cbmc/proofs/**/gotos -**/cbmc/proofs/**/html -**/cbmc/proofs/**/logs -**/cbmc/proofs/**/TAGS-* -**/cbmc/proofs/**/report diff --git a/libraries/standard/http/cbmc/.gitignore b/libraries/standard/http/cbmc/.gitignore new file mode 100644 index 0000000000..cb6ef5cd6f --- /dev/null +++ b/libraries/standard/http/cbmc/.gitignore @@ -0,0 +1,15 @@ +# Emitted when running CBMC proofs +proofs/**/logs +proofs/**/gotos +proofs/**/report +proofs/**/html + +# Emitted by CBMC Viewer +TAGS-* + +# Emitted by Arpa +arpa_cmake/ +Makefile.arpa + +# These files should be overwritten whenever prepare.py runs +cbmc-batch.yaml diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile index 6e583db184..0a7df437c7 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_AddHeader/Makefile @@ -43,8 +43,8 @@ REMOVE_FUNCTION_BODY += memcpy UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c -PROOF_SOURCES += $(PROOFDIR)/../../stubs/memcpy.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/memcpy.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile index b9dea2cd3d..9b21bc5995 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_InitializeRequestHeaders/Makefile @@ -33,7 +33,6 @@ REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnBodyCallb REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderFieldCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderValueCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnStatusCallback - REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeadersCompleteCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback @@ -43,8 +42,8 @@ REMOVE_FUNCTION_BODY += memcpy UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c -PROOF_SOURCES += $(PROOFDIR)/../../stubs/memcpy.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/memcpy.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c index 16e6fcbadc..27733e513e 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c @@ -35,8 +35,8 @@ void harness() /* Ideally, this should be allocated in the heap but doing so makes CBMC * run out of memory. */ - TransportInterface_t transportInterface; - uint8_t * pRequestBodyBuf = NULL; + TransportInterface_t * pTransportInterface; + uint8_t * pRequestBodyBuf; size_t reqBodyBufLen; uint32_t sendFlags; @@ -53,28 +53,20 @@ void harness() __CPROVER_assume( isValidHttpResponse( pResponse ) ); /* Initialize transport interface. */ - ( void ) allocateTransportInterface( &transportInterface ); + pTransportInterface = allocateTransportInterface( NULL ); - if( nondet_bool() ) + if( pTransportInterface != NULL ) { /* Ideally, we want to set the function pointers below with __CPROVER_assume() * but doing so makes CBMC run out of memory. */ - transportInterface.send = nondet_bool() ? NULL : TransportInterfaceSendStub; - transportInterface.recv = nondet_bool() ? NULL : TransportInterfaceReceiveStub; - HTTPClient_Send( &transportInterface, - pRequestHeaders, - pRequestBodyBuf, - reqBodyBufLen, - pResponse, - sendFlags ); - } - else - { - HTTPClient_Send( NULL, - pRequestHeaders, - pRequestBodyBuf, - reqBodyBufLen, - pResponse, - sendFlags ); + pTransportInterface->send = nondet_bool() ? NULL : TransportInterfaceSendStub; + pTransportInterface->recv = nondet_bool() ? NULL : TransportInterfaceReceiveStub; } + + HTTPClient_Send( pTransportInterface, + pRequestHeaders, + pRequestBodyBuf, + reqBodyBufLen, + pResponse, + sendFlags ); } diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile index 8cc6c1f1e9..609f52ca82 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile @@ -1,5 +1,23 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# HARNESS_ENTRY=harness HARNESS_FILE=HTTPClient_Send_harness @@ -11,15 +29,19 @@ INCLUDES += # function pointer targets, leading to faster execution time. REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderFieldParserCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderValueParserCallback +# The memory safety of the http-parser callbacks are proven separately. The +# proofs for the functions below must have assumptions that are satisfied by the +# calling context in HTTPClient_Send. REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnBodyCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderFieldCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeaderValueCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnStatusCallback - REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeadersCompleteCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback +# There is a total of 10 digits in INT32_MAX. These loops are unwound once more +# than the total possible iterations in the int32_t to ASCII converation. UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.0:11 UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.1:11 UNWINDSET += __CPROVER_file_local_http_client_c_receiveAndParseHttpResponse.0:10 @@ -27,9 +49,9 @@ UNWINDSET += __CPROVER_file_local_http_client_c_sendHttpData.0:10 UNWINDSET += strncmp.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c -PROOF_SOURCES += $(PROOFDIR)/../../sources/http_cbmc_state.c -PROOF_SOURCES += $(PROOFDIR)/../../stubs/HTTPClient_Send_http_parser_execute.c -PROOF_SOURCES += $(PROOFDIR)/../../stubs/transport_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-targets b/libraries/standard/http/cbmc/proofs/Makefile-project-targets deleted file mode 120000 index 63d2fb982d..0000000000 --- a/libraries/standard/http/cbmc/proofs/Makefile-project-targets +++ /dev/null @@ -1 +0,0 @@ -../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-targets \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-targets b/libraries/standard/http/cbmc/proofs/Makefile-project-targets new file mode 100644 index 0000000000..bf681eed15 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-targets @@ -0,0 +1,7 @@ +# -*- mode: makefile -*- +# The first line sets the emacs major mode to Makefile + +################################################################ +# Use this file to give project-specific targets, including targets +# that may depend on targets defined in Makefile.common. +################################################################ diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-testing b/libraries/standard/http/cbmc/proofs/Makefile-project-testing deleted file mode 120000 index fb8969bbbb..0000000000 --- a/libraries/standard/http/cbmc/proofs/Makefile-project-testing +++ /dev/null @@ -1 +0,0 @@ -../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-testing \ No newline at end of file diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-testing b/libraries/standard/http/cbmc/proofs/Makefile-project-testing new file mode 100644 index 0000000000..03b9a05263 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-testing @@ -0,0 +1,8 @@ +# -*- mode: makefile -*- +# The first line sets the emacs major mode to Makefile + +################################################################ +# Use this file to define project-specific targets and definitions for +# unit testing or continuous integration that may depend on targets +# defined in Makefile.common +################################################################ diff --git a/libraries/standard/mqtt/cbmc/.gitignore b/libraries/standard/mqtt/cbmc/.gitignore new file mode 100644 index 0000000000..cb6ef5cd6f --- /dev/null +++ b/libraries/standard/mqtt/cbmc/.gitignore @@ -0,0 +1,15 @@ +# Emitted when running CBMC proofs +proofs/**/logs +proofs/**/gotos +proofs/**/report +proofs/**/html + +# Emitted by CBMC Viewer +TAGS-* + +# Emitted by Arpa +arpa_cmake/ +Makefile.arpa + +# These files should be overwritten whenever prepare.py runs +cbmc-batch.yaml diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_config.h b/libraries/standard/mqtt/cbmc/include/mqtt_config.h index 94ca72df58..7eef16572d 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_config.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_config.h @@ -41,6 +41,17 @@ struct NetworkContext * @note This definition must exist in order to compile. 10U is a typical value * used in the MQTT demos. */ -#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) + +/** + * @brief Retry count for reading CONNACK from network. + * + * The MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT will be used only when the + * timeoutMs parameter of #MQTT_Connect() is passed as 0 . The transport + * receive for CONNACK will be retried MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT + * times before timing out. A value of 0 for this config will cause the + * transport receive for CONNACK to be invoked only once. + */ +#define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/MQTT_Connect_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/MQTT_Connect_harness.c new file mode 100644 index 0000000000..199ebcc2b3 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/MQTT_Connect_harness.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Connect_harness.c + * @brief Implements the proof harness for MQTT_Connect function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + MQTTConnectInfo_t * pConnectInfo; + MQTTPublishInfo_t * pWillInfo; + uint32_t timeoutMs; + bool * pSessionPresent; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + pConnectInfo = allocateMqttConnectInfo( NULL ); + __CPROVER_assume( isValidMqttConnectInfo( pConnectInfo ) ); + + pWillInfo = allocateMqttPublishInfo( NULL ); + __CPROVER_assume( isValidMqttPublishInfo( pWillInfo ) ); + + pSessionPresent = mallocCanFail( sizeof( bool ) ); + + /* The MQTT_RECEIVE_TIMEOUT is used here to control the number of loops + * when receiving on the network. The default is used here because memory + * safety can be proven in only a few iterations. */ + __CPROVER_assume( timeoutMs < MQTT_RECEIVE_TIMEOUT ); + + MQTT_Connect( pContext, pConnectInfo, pWillInfo, timeoutMs, pSessionPresent ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/Makefile new file mode 100644 index 0000000000..82e30bc1cf --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/Makefile @@ -0,0 +1,83 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Connect_harness + +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +# Bound on the timeout in receiveConnack. This timeout is bounded because +# memory saftey can be proven in a only a few iteration of the MQTT operations. +# Each iteration will try to receive a single packet in its entirety. With a +# time out of 3 we can get coverage of the entire function. Another iteration +# performed will unnecessarily duplicate the proof. +MQTT_RECEIVE_TIMEOUT=3 +# Please see libraries/standard/mqtt/cbmc/include/mqtt_config.h for more +# information on these defines. +MQTT_STATE_ARRAY_MAX_COUNT=11 +MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT=3 +DEFINES += -DMQTT_RECEIVE_TIMEOUT=$(MQTT_RECEIVE_TIMEOUT) +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +INCLUDES += + +# These functions do not coincide with the call graph of MQTT_Connect, but are +# found by CBMC during processing in logs/MQTT_Connect_harness3.txt. We remove +# the function bodies to improve coverage accuracy. +REMOVE_FUNCTION_BODY += MQTT_ProcessLoop +REMOVE_FUNCTION_BODY += MQTT_ReceiveLoop +REMOVE_FUNCTION_BODY += __CPROVER_file_local_mqtt_c_handleIncomingPublish +REMOVE_FUNCTION_BODY += __CPROVER_file_local_mqtt_c_handleKeepAlive + +# The loops below are unwound once more than the timeout. The loops below use +# the user passed in timeout to break the loop. +UNWINDSET += __CPROVER_file_local_mqtt_c_recvExact.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_discardPacket.0:$(MQTT_RECEIVE_TIMEOUT) +# If the user passed in timeout is zero, then the loop will run until the +# MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is reached. +UNWINDSET += __CPROVER_file_local_mqtt_c_receiveConnack.0:$(MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT) +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) +# The loops are unwound 5 times because these functions divides a size_t +# variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_getRemainingLength.0:5 +# This loop will run for the maximum number of publishes pending +# acknowledgements plus one. This value is set in +# libraries/standard/mqtt/cbmc/include/mqtt_config.h. +UNWINDSET += __CPROVER_file_local_mqtt_state_c_stateSelect.0:$(MQTT_STATE_ARRAY_MAX_COUNT) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/memcpy.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/README.md new file mode 100644 index 0000000000..69a96824fd --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/README.md @@ -0,0 +1,10 @@ +MQTT_Connect proof +============== + +This directory contains a memory safety proof for MQTT_Connect. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-viewer.json new file mode 100644 index 0000000000..2ca9f7f9b6 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Connect/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Connect", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/MQTT_Disconnect_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/MQTT_Disconnect_harness.c new file mode 100644 index 0000000000..6f59a53fb2 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/MQTT_Disconnect_harness.c @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Disconnect_harness.c + * @brief Implements the proof harness for MQTT_Disconnect function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + MQTT_Disconnect( pContext ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/Makefile new file mode 100644 index 0000000000..b105017c77 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/Makefile @@ -0,0 +1,48 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Disconnect_harness + +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +INCLUDES += + +REMOVE_FUNCTION_BODY += +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/README.md new file mode 100644 index 0000000000..28250cb078 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/README.md @@ -0,0 +1,10 @@ +MQTT_Disconnect proof +============== + +This directory contains a memory safety proof for MQTT_Disconnect. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-viewer.json new file mode 100644 index 0000000000..a0f8337091 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Disconnect/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Disconnect", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile index 9f52ac3b93..50711f78b3 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Ping/Makefile @@ -23,7 +23,7 @@ HARNESS_ENTRY=harness HARNESS_FILE=MQTT_Ping_harness # Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for -# more information on the MAX_NETWORK_SEND_TRIES. +# more information on MAX_NETWORK_SEND_TRIES. MAX_NETWORK_SEND_TRIES=3 DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) INCLUDES += diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c index 7cf9082dcb..a9de5f983f 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/MQTT_ProcessLoop_harness.c @@ -25,9 +25,6 @@ */ #include "mqtt.h" #include "mqtt_cbmc_state.h" -#include "network_interface_stubs.h" -#include "get_time_stub.h" -#include "event_callback_stub.h" void harness() { @@ -37,10 +34,11 @@ void harness() pContext = allocateMqttContext( NULL ); __CPROVER_assume( isValidMqttContext( pContext ) ); - __CPROVER_assume( timeoutMs < MQTT_PROCESS_LOOP_TIMEOUT ); - - /* The MQTT_PROCESS_LOOP_TIMEOUT is used here to control the number of loops + /* The MQTT_RECEIVE_TIMEOUT is used here to control the number of loops * when receiving on the network. The default is used here because memory - * safety can be proven in only a few iterations. */ + * safety can be proven in only a few iterations. Please see this proof's + * Makefile for more information. */ + __CPROVER_assume( timeoutMs < MQTT_RECEIVE_TIMEOUT ); + MQTT_ProcessLoop( pContext, timeoutMs ); } diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile index 850e01a36b..72bde2f215 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ProcessLoop/Makefile @@ -27,14 +27,14 @@ HARNESS_FILE=MQTT_ProcessLoop_harness # Each iteration will try to receive a single packet in its entirey. With a time # out of 2 we can get coverage of the entire function. Another iteration will # performed unnecessarily duplicating of the proof. -MQTT_PROCESS_LOOP_TIMEOUT=3 -# Please see libraries/mqtt/cbmc/stubs/network_interface_subs.c for more -# information on the MAX_NETWORK_SEND_TRIES. +MQTT_RECEIVE_TIMEOUT=3 +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. MAX_NETWORK_SEND_TRIES=3 # Please see libraries/standard/mqtt/cbmc/include/mqtt_config.h for more # information. MQTT_STATE_ARRAY_MAX_COUNT=11 -DEFINES += -DMQTT_PROCESS_LOOP_TIMEOUT=$(MQTT_PROCESS_LOOP_TIMEOUT) +DEFINES += -DMQTT_RECEIVE_TIMEOUT=$(MQTT_RECEIVE_TIMEOUT) DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) INCLUDES += @@ -43,9 +43,9 @@ REMOVE_FUNCTION_BODY += MQTT_Ping REMOVE_FUNCTION_BODY += MQTT_DeserializeAck REMOVE_FUNCTION_BODY += MQTT_SerializeAck -UNWINDSET += MQTT_ProcessLoop.0:$(MQTT_PROCESS_LOOP_TIMEOUT) -UNWINDSET += __CPROVER_file_local_mqtt_c_discardPacket.0:$(MQTT_PROCESS_LOOP_TIMEOUT) -UNWINDSET += __CPROVER_file_local_mqtt_c_recvExact.0:$(MQTT_PROCESS_LOOP_TIMEOUT) +UNWINDSET += MQTT_ProcessLoop.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_discardPacket.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_recvExact.0:$(MQTT_RECEIVE_TIMEOUT) # Unlike recvExact, sendPacket is not bounded by the timeout. The loop in # sendPacket will continue until all the bytes are sent or a network error # occurs. Please see NetworkInterfaceReceiveStub in diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/MQTT_Publish_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/MQTT_Publish_harness.c new file mode 100644 index 0000000000..eae41cd5ee --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/MQTT_Publish_harness.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Publish_harness.c + * @brief Implements the proof harness for MQTT_Publish function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + MQTTPublishInfo_t * pPublishInfo; + uint16_t packetId; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + pPublishInfo = allocateMqttPublishInfo( NULL ); + __CPROVER_assume( isValidMqttPublishInfo( pPublishInfo ) ); + + MQTT_Publish( pContext, pPublishInfo, packetId ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/Makefile new file mode 100644 index 0000000000..a7ec6b7e76 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/Makefile @@ -0,0 +1,60 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Publish_harness + +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +# Please see libraries/standard/mqtt/cbmc/include/mqtt_config.h for more +# information. +MQTT_STATE_ARRAY_MAX_COUNT=11 +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +INCLUDES += + +REMOVE_FUNCTION_BODY += +REMOVE_FUNCTION_BODY += +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) +# These loops will run for the maximum number of publishes pending acknowledgement. +# This is set in libraries/standard/mqtt/cbmc/include/mqtt_config.h. +UNWINDSET += __CPROVER_file_local_mqtt_state_c_addRecord.0:$(MQTT_STATE_ARRAY_MAX_COUNT) +UNWINDSET += __CPROVER_file_local_mqtt_state_c_findInRecord.0:$(MQTT_STATE_ARRAY_MAX_COUNT) +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/README.md new file mode 100644 index 0000000000..bf95bd5e1b --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/README.md @@ -0,0 +1,10 @@ +MQTT_Publish proof +============== + +This directory contains a memory safety proof for MQTT_Publish. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-viewer.json new file mode 100644 index 0000000000..36e6107265 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Publish/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Publish", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/MQTT_ReceiveLoop_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/MQTT_ReceiveLoop_harness.c new file mode 100644 index 0000000000..ed74110fa0 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/MQTT_ReceiveLoop_harness.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_ReceiveLoop_harness.c + * @brief Implements the proof harness for MQTT_ReceiveLoop function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + uint32_t timeoutMs; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + /* The MQTT_RECEIVE_TIMEOUT is used here to control the number of loops + * when receiving on the network. The default is used here because memory + * safety can be proven in only a few iterations. Please see this proof's + * Makefile for more information. */ + __CPROVER_assume( timeoutMs < MQTT_RECEIVE_TIMEOUT ); + + MQTT_ReceiveLoop( pContext, timeoutMs ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/Makefile new file mode 100644 index 0000000000..93606c9472 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/Makefile @@ -0,0 +1,55 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_ReceiveLoop_harness + +# Bound on the timeout in MQTT_ProcessLoop. This timeout is bounded because +# memory saftey can be proven in a only a few iteration of the MQTT operations. +# Each iteration will try to receive a single packet in its entirety. With a time +# out of 2 we can get coverage of the entire function. Another iteration will +# performed unnecessarily duplicating of the proof. +MQTT_RECEIVE_TIMEOUT=3 +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +# Please see libraries/standard/mqtt/cbmc/include/mqtt_config.h for more +# information. +MQTT_STATE_ARRAY_MAX_COUNT=11 +DEFINES += -DMQTT_RECEIVE_TIMEOUT=$(MQTT_RECEIVE_TIMEOUT) +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +INCLUDES += + +# These functions have their memory saftey proven in other harnesses. +REMOVE_FUNCTION_BODY += MQTT_DeserializeAck +REMOVE_FUNCTION_BODY += MQTT_SerializeAck + +# The loops below are unwound once more than the exclusive timeout bound. +UNWINDSET += MQTT_ReceiveLoop.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_discardPacket.0:$(MQTT_RECEIVE_TIMEOUT) +UNWINDSET += __CPROVER_file_local_mqtt_c_recvExact.0:$(MQTT_RECEIVE_TIMEOUT) +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) +# The getRemainingLength loop is unwound 5 times because getRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_getRemainingLength.0:5 +# These loops will run for the maximum number of publishes pending acknowledgement. +# This is set in libraries/standard/mqtt/cbmc/include/mqtt_config.h. +UNWINDSET += __CPROVER_file_local_mqtt_state_c_addRecord.0:$(MQTT_STATE_ARRAY_MAX_COUNT) +UNWINDSET += __CPROVER_file_local_mqtt_state_c_findInRecord.0:$(MQTT_STATE_ARRAY_MAX_COUNT) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/README.md new file mode 100644 index 0000000000..afffdfe206 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/README.md @@ -0,0 +1,10 @@ +MQTT_ReceiveLoop proof +============== + +This directory contains a memory safety proof for MQTT_ReceiveLoop. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-viewer.json new file mode 100644 index 0000000000..05e9d9b5b6 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_ReceiveLoop/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_ReceiveLoop", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile index 51b51bf2b6..a12eaf208d 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeConnect/Makefile @@ -25,11 +25,19 @@ HARNESS_FILE=MQTT_SerializeConnect_harness DEFINES += INCLUDES += -REMOVE_FUNCTION_BODY += -UNWINDSET += encodeRemainingLength.0:3 +# This function does not coincide with the call graph of MQTT_Serialize, but are +# found by CBMC during processing in logs/MQTT_Connect_harness3.txt. We remove +# the function body to improve coverage accuracy. +REMOVE_FUNCTION_BODY += MQTT_GetIncomingPacketTypeAndLength + +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/memcpy.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile index 7abf57ccc4..4253601dc9 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublish/Makefile @@ -26,7 +26,10 @@ DEFINES += INCLUDES += REMOVE_FUNCTION_BODY += -UNWINDSET += encodeRemainingLength.0:4 +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile index 86a51e237d..10ea76cc82 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializePublishHeader/Makefile @@ -26,7 +26,10 @@ DEFINES += INCLUDES += REMOVE_FUNCTION_BODY += -UNWINDSET += encodeRemainingLength.0:4 +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c index 2d67dacde5..9ec6da0949 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/MQTT_SerializeSubscribe_harness.c @@ -36,6 +36,8 @@ void harness() MQTTFixedBuffer_t * pFixedBuffer; MQTTStatus_t status = MQTTSuccess; + /* Please see the default bound description on SUBSCRIPTION_COUNT_MAX in + * mqtt_cbmc_state.c for more information. */ __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); pSubscriptionList = allocateMqttSubscriptionList( NULL, subscriptionCount ); diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile index 5d9930cd3f..eb81c7aa6e 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeSubscribe/Makefile @@ -34,7 +34,10 @@ UNWINDSET += allocateMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) UNWINDSET += isValidMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_calculateSubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) UNWINDSET += MQTT_SerializeSubscribe.0:$(SUBSCRIPTION_COUNT_MAX) -UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:4 +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c index d539e19e0e..f31eb28a36 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/MQTT_SerializeUnsubscribe_harness.c @@ -39,6 +39,8 @@ void harness() MQTTFixedBuffer_t * pFixedBuffer; MQTTStatus_t status = MQTTSuccess; + /* Please see the default bound description on SUBSCRIPTION_COUNT_MAX in + * mqtt_cbmc_state.c for more information. */ __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); pSubscriptionList = allocateMqttSubscriptionList( NULL, subscriptionCount ); diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile index 273d079e09..40538e73c0 100644 --- a/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_SerializeUnsubscribe/Makefile @@ -34,7 +34,10 @@ UNWINDSET += allocateMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) UNWINDSET += isValidMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_calculateSubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) UNWINDSET += MQTT_SerializeUnsubscribe.0:$(SUBSCRIPTION_COUNT_MAX) -UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:4 +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/MQTT_Subscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/MQTT_Subscribe_harness.c new file mode 100644 index 0000000000..3745dd7a5e --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/MQTT_Subscribe_harness.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Subscribe_harness.c + * @brief Implements the proof harness for MQTT_Subscribe function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + MQTTSubscribeInfo_t * pSubscriptionList; + size_t subscriptionCount; + uint16_t packetId; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + /* Please see the default bound description on SUBSCRIPTION_COUNT_MAX in + * mqtt_cbmc_state.c for more information. */ + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + + pSubscriptionList = allocateMqttSubscriptionList( NULL, subscriptionCount ); + __CPROVER_assume( isValidMqttSubscriptionList( pSubscriptionList, subscriptionCount ) ); + + MQTT_Subscribe( pContext, pSubscriptionList, subscriptionCount, packetId ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/Makefile new file mode 100644 index 0000000000..fe9d39f7ad --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/Makefile @@ -0,0 +1,62 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Subscribe_harness + +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +# Bound on the the subscription count. Please see the default value in +# mqtt_cbmc_state.c for more information on this bound. This is set to 2 +# currently to have the proof run quickly. +SUBSCRIPTION_COUNT_MAX=2 + +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +DEFINES += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) +INCLUDES += + +REMOVE_FUNCTION_BODY += +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) +UNWINDSET += allocateMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += isValidMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_calculateSubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += MQTT_SerializeSubscribe.0:$(SUBSCRIPTION_COUNT_MAX) +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/README.md new file mode 100644 index 0000000000..377e39c33a --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/README.md @@ -0,0 +1,10 @@ +MQTT_Subscribe proof +============== + +This directory contains a memory safety proof for MQTT_Subscribe. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-viewer.json new file mode 100644 index 0000000000..1223d9d373 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Subscribe/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Subscribe", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/MQTT_Unsubscribe_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/MQTT_Unsubscribe_harness.c new file mode 100644 index 0000000000..3f17faa3a5 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/MQTT_Unsubscribe_harness.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_Unsubscribe_harness.c + * @brief Implements the proof harness for MQTT_Unsubscribe function. + */ +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTContext_t * pContext; + MQTTSubscribeInfo_t * pSubscriptionList; + size_t subscriptionCount; + uint16_t packetId; + + pContext = allocateMqttContext( NULL ); + __CPROVER_assume( isValidMqttContext( pContext ) ); + + /* Please see the default bound description on SUBSCRIPTION_COUNT_MAX in + * mqtt_cbmc_state.c for more information. */ + __CPROVER_assume( subscriptionCount < SUBSCRIPTION_COUNT_MAX ); + + pSubscriptionList = allocateMqttSubscriptionList( NULL, subscriptionCount ); + __CPROVER_assume( isValidMqttSubscriptionList( pSubscriptionList, subscriptionCount ) ); + + MQTT_Unsubscribe( pContext, pSubscriptionList, subscriptionCount, packetId ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/Makefile new file mode 100644 index 0000000000..892ebb0ae1 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/Makefile @@ -0,0 +1,62 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_Unsubscribe_harness + +# Please see libraries/standard/mqtt/cbmc/stubs/network_interface_subs.c for +# more information on MAX_NETWORK_SEND_TRIES. +MAX_NETWORK_SEND_TRIES=3 +# Bound on the the subscription count. Please see the default value in +# mqtt_cbmc_state.c for more information on this bound. This is set to 2 +# currently to have the proof run quickly. +SUBSCRIPTION_COUNT_MAX=2 + +DEFINES += -DMAX_NETWORK_SEND_TRIES=$(MAX_NETWORK_SEND_TRIES) +DEFINES += -DSUBSCRIPTION_COUNT_MAX=$(SUBSCRIPTION_COUNT_MAX) +INCLUDES += + +REMOVE_FUNCTION_BODY += +# Unlike recvExact, sendPacket is not bounded by the timeout. The loop in +# sendPacket will continue until all the bytes are sent or a network error +# occurs. Please see NetworkInterfaceReceiveStub in +# libraries\standard\mqtt\cbmc\stubs\network_interface_stubs.c for more +# information. +UNWINDSET += __CPROVER_file_local_mqtt_c_sendPacket.0:$(MAX_NETWORK_SEND_TRIES) +UNWINDSET += allocateMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += isValidMqttSubscriptionList.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_calculateSubscriptionPacketSize.0:$(SUBSCRIPTION_COUNT_MAX) +UNWINDSET += MQTT_SerializeUnsubscribe.0:$(SUBSCRIPTION_COUNT_MAX) +# The encodeRemainingLength loop is unwound 5 times because encodeRemainingLength() +# divides a size_t variable by 128 until it reaches zero to stop the loop. +# log128(SIZE_MAX) = 4.571... +UNWINDSET += __CPROVER_file_local_mqtt_lightweight_c_encodeRemainingLength.0:5 + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/get_time_stub.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_lightweight.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt_state.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/README.md new file mode 100644 index 0000000000..8251781274 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/README.md @@ -0,0 +1,10 @@ +MQTT_Unsubscribe proof +============== + +This directory contains a memory safety proof for MQTT_Unsubscribe. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-viewer.json new file mode 100644 index 0000000000..7d278ab892 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_Unsubscribe/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_Unsubscribe", + "proof-root": "libraries/standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets deleted file mode 120000 index 63d2fb982d..0000000000 --- a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets +++ /dev/null @@ -1 +0,0 @@ -../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-targets \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets new file mode 100644 index 0000000000..bf681eed15 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-targets @@ -0,0 +1,7 @@ +# -*- mode: makefile -*- +# The first line sets the emacs major mode to Makefile + +################################################################ +# Use this file to give project-specific targets, including targets +# that may depend on targets defined in Makefile.common. +################################################################ diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing deleted file mode 120000 index fb8969bbbb..0000000000 --- a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing +++ /dev/null @@ -1 +0,0 @@ -../../../../../tools/aws-templates-for-cbmc-proofs/template-for-repository/proofs/Makefile-project-testing \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing new file mode 100644 index 0000000000..03b9a05263 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-testing @@ -0,0 +1,8 @@ +# -*- mode: makefile -*- +# The first line sets the emacs major mode to Makefile + +################################################################ +# Use this file to define project-specific targets and definitions for +# unit testing or continuous integration that may depend on targets +# defined in Makefile.common +################################################################ diff --git a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c index e9ab84a1c2..c5240c933c 100644 --- a/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +++ b/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c @@ -26,20 +26,20 @@ #include "get_time_stub.h" #include "event_callback_stub.h" -/* A default bound on the subscription count. Iterating over possibly SIZE_MAX - * number of subscriptions does not add any value to the proofs. An application - * can allocate memory for as many subscriptions as their system can handle. - * The proofs verify that the code can handle the maximum topicFilterLength in - * each subscription. */ +/* An exclusive default bound on the subscription count. Iterating over possibly + * SIZE_MAX number of subscriptions does not add any value to the proofs. An + * application can allocate memory for as many subscriptions as their system can + * handle. The proofs verify that the code can handle the maximum + * topicFilterLength in each subscription. */ #ifndef SUBSCRIPTION_COUNT_MAX - #define SUBSCRIPTION_COUNT_MAX 1U + #define SUBSCRIPTION_COUNT_MAX 2U #endif -/* A default bound on the remainingLength in an incoming packet. This bound - * is used for the MQTT_DeserializeAck() proof to limit the number of iterations - * on a SUBACK packet's payload bytes. We do not need to iterate an unbounded - * remaining length amount of bytes to verify memory safety in the dereferencing - * the SUBACK payload's bytes. */ +/* An exclusive default bound on the remainingLength in an incoming packet. This + * bound is used for the MQTT_DeserializeAck() proof to limit the number of + * iterations on a SUBACK packet's payload bytes. We do not need to iterate an + * unbounded remaining length amount of bytes to verify memory safety in the + * dereferencing the SUBACK payload's bytes. */ #ifndef REMAINING_LENGTH_MAX #define REMAINING_LENGTH_MAX CBMC_MAX_OBJECT_SIZE #endif diff --git a/libraries/standard/mqtt/cbmc/stubs/memcpy.c b/libraries/standard/mqtt/cbmc/stubs/memcpy.c new file mode 100644 index 0000000000..e30481cf93 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/stubs/memcpy.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file memcpy.c + * @brief A stub for memcpy so that the proofs for functions that call memcpy + * run much faster. + */ + +#include + +/* This is a clang macro not available on linux */ +#ifndef __has_builtin + #define __has_builtin( x ) 0 +#endif + +#if __has_builtin( __builtin___memcpy_chk ) + void * __builtin___memcpy_chk( void * dest, + const void * src, + size_t n, + size_t m ) + { + __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + return dest; + } +#else + void * memcpy( void * dest, + const void * src, + size_t n ) + { + __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + return dest; + } +#endif /* if __has_builtin( __builtin___memcpy_chk ) */ diff --git a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c index 18dd12f3ee..6a0c1da1a0 100644 --- a/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c +++ b/libraries/standard/mqtt/cbmc/stubs/network_interface_stubs.c @@ -72,7 +72,7 @@ int32_t NetworkInterfaceSendStub( NetworkContext_t * pNetworkContext, * iterations. Looping for INT32_MAX times adds no value to the proof. * What matters is that the MQTT library can handle all the possible values * that could be returned. */ - if( tries < MAX_NETWORK_SEND_TRIES ) + if( tries < ( MAX_NETWORK_SEND_TRIES - 1 ) ) { tries++; } diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 8f7be1438c..a31454f99e 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -74,6 +74,7 @@ int iot isduplicate isn +linux lsb lwt malloc diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 0d961a4579..f257828199 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -493,6 +493,7 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, assert( pContext != NULL ); assert( pContext->getTime != NULL ); + bytesToReceive = pContext->networkBuffer.size; getTimeStampMs = pContext->getTime; @@ -693,6 +694,8 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ) uint32_t now = 0U, keepAliveMs = 0U; assert( pContext != NULL ); + assert( pContext->getTime != NULL ); + now = pContext->getTime(); keepAliveMs = 1000U * ( uint32_t ) pContext->keepAliveIntervalSec; @@ -731,6 +734,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pIncomingPacket != NULL ); + assert( pContext->appCallback != NULL ); status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo ); LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%d.", status ) ); @@ -897,6 +901,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, assert( pContext != NULL ); assert( pIncomingPacket != NULL ); + assert( pContext->appCallback != NULL ); appCallback = pContext->appCallback; diff --git a/tools/aws-templates-for-cbmc-proofs b/tools/aws-templates-for-cbmc-proofs index ddb267ac14..c21a9830c1 160000 --- a/tools/aws-templates-for-cbmc-proofs +++ b/tools/aws-templates-for-cbmc-proofs @@ -1 +1 @@ -Subproject commit ddb267ac14e011da917bd96c1a802b7bb43b9796 +Subproject commit c21a9830c13ebbeaa59acf77c2add725d23b20c4 From b6572237b8d00b63d6c7c02dd58c0cbe34cd1eba Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 3 Aug 2020 19:13:34 -0700 Subject: [PATCH 608/844] Update README to provide Docker instructions for Mosquitto broker (#1090) Update REAMDE to provide Docker instructions for running Mosquitto broker --- README.md | 78 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index ff30d3f42a..f4d94ba971 100644 --- a/README.md +++ b/README.md @@ -69,33 +69,75 @@ It is required to setup an AWS account and access the AWS IoT Console for runnin 1. Go to the `build/bin` directory and run any demo executables from there. - +### Alternative option of Docker containers for running demos locally. -### Optional: Installing Mosquitto to run MQTT demos locally +Install Docker: -1. [Download and install Mosquitto](https://mosquitto.org/download/) +```shell -1. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set to `localhost`. +curl -fsSL https://get.docker.com -o get-docker.sh -1. [Follow these instructions](https://dzone.com/articles/secure-communication-with-tls-and-the-mosquitto-broker) to setup TLS authentication for your local Mosquitto server. +sh get-docker.sh -1. Set `ROOT_CA_CERT_PATH` to the server certificate used when setting up TLS authentication for your local Mosquitto server. +``` - +#### Installing Mosquitto to run MQTT demos locally -### Optional: Installing httpbin to run HTTP demos locally +The following instructions have been tested on an Ubuntu 18.04 environment with Docker and OpenSSL installed. -1. Install Docker: +1. Download the official Docker image for Mosquitto. ```shell -curl -fsSL https://get.docker.com -o get-docker.sh +docker pull eclipse-mosquitto:latest -sh get-docker.sh +``` +2. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set to `localhost`. + +3. For TLS communication with Mosquitto broker, server and CA credentials need to be created. Use OpenSSL commands to generate the credentials for the Mosquitto server. + +Generate CA key and certificate. Provide the Subject field information as appropriate. +```shell +openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -keyout ca.key -out ca.crt +``` + +Generate server key and certificate and sign with the CA cert. +```shell + +openssl req -nodes -sha256 -new -keyout server.key -out server.csr + +openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 ``` -2. Run httpbin through port 80: +4. Create a mosquitto.conf file to use port 8883 (for TLS communication) and providing path to the generated credentials. + +``` +port 8883 + +cafile /mosquitto/config/ca.crt +certfile /mosquitto/config/server.crt +keyfile /mosquitto/config/server.key + +# Use this option for TLS mutual authentication (where client will provide CA signed certificate) +#require_certificate true +tls_version tlsv1.2 +#use_identity_as_username true + +``` + +5. Run the docker container from the local directory containing the generated credential and mosquitto.conf files. + +```shell +docker run -it -p 8883:8883 -v $(pwd):/mosquitto/config/ --name mosquitto-basic-tls eclipse-mosquitto:latest +``` + +6. Set `ROOT_CA_CERT_PATH` to the absolute path of the CA certificate created in step 3. for the local Mosquitto server. + + +#### Installing httpbin to run HTTP demos locally + +1. Run httpbin through port 80: ```shell @@ -105,18 +147,16 @@ docker run -p 80:80 kennethreitz/httpbin ``` -3. `SERVER_HOST` defined in `demos/http/http_demo_plaintext/demo_config.h` can now be set to `localhost`. +2. `SERVER_HOST` defined in `demos/http/http_demo_plaintext/demo_config.h` can now be set to `localhost`. -4. To run `http_demo_basic_tls`, [download ngrok](https://ngrok.com/download) in order to create an HTTPS tunnel to the httpbin server currently hosted on port 80: +3. To run `http_demo_basic_tls`, [download ngrok](https://ngrok.com/download) in order to create an HTTPS tunnel to the httpbin server currently hosted on port 80: ```shell - ./ngrok http 80 # May have to use ./ngrok.exe depending on OS or filename of the executable - ``` -5. `ngrok` will provide an https link that can be substituted in `demos/http/http_demo_basic_tls/demo_config.h` and has a format of `https://ABCDEFG12345.ngrok.io`. +4. `ngrok` will provide an https link that can be substituted in `demos/http/http_demo_basic_tls/demo_config.h` and has a format of `https://ABCDEFG12345.ngrok.io`. -6. Set `SERVER_HOST` in `demos/http/http_demo_basic_tls/demo_config.h` to the https link provided by ngrok. +5. Set `SERVER_HOST` in `demos/http/http_demo_basic_tls/demo_config.h` to the https link provided by ngrok. -7. You must also download the Root CA certificate provided by ngrok and set `ROOT_CA_CERT_PATH` in `demo_config.h` to the file path of the downloaded certificate. +6. You must also download the Root CA certificate provided by ngrok and set `ROOT_CA_CERT_PATH` in `demo_config.h` to the file path of the downloaded certificate. From 075f931c2cc2315ccebdca43b33da07694ecab09 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 4 Aug 2020 11:14:39 -0700 Subject: [PATCH 609/844] Fix logging call in MQTT, Make hygiene improvement in logging stack (#1095) * Fix issues with logging across files * Fix format specifier causing warning --- demos/logging-stack/logging_stack.h | 2 +- libraries/standard/http/utest/http_config.h | 9 +++++++-- libraries/standard/mqtt/src/mqtt.c | 2 +- libraries/standard/mqtt/src/mqtt_lightweight.c | 2 +- libraries/standard/mqtt/utest/mqtt_config.h | 9 +++++++-- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/demos/logging-stack/logging_stack.h b/demos/logging-stack/logging_stack.h index 47e7459e0e..6424623325 100644 --- a/demos/logging-stack/logging_stack.h +++ b/demos/logging-stack/logging_stack.h @@ -37,7 +37,7 @@ /* Metadata information to prepend to every log message. */ #define LOG_METADATA_FORMAT "[%s:%d] " -#define LOG_METADATA_ARGS __FILE__, __LINE__ +#define LOG_METADATA_ARGS __FUNCTION__, __LINE__ /* Common macro for all logging interface macros. */ #if !defined( DISABLE_LOGGING ) diff --git a/libraries/standard/http/utest/http_config.h b/libraries/standard/http/utest/http_config.h index cd0f7b84c6..fea6979ba2 100644 --- a/libraries/standard/http/utest/http_config.h +++ b/libraries/standard/http/utest/http_config.h @@ -15,8 +15,13 @@ #include "logging_levels.h" /* Configure name and log level for the HTTP library. */ -#define LIBRARY_LOG_NAME "HTTP" -#define LIBRARY_LOG_LEVEL LOG_NONE +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "HTTP" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_NONE +#endif #include "logging_stack.h" diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index f257828199..0bd629caa7 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1109,7 +1109,7 @@ static MQTTStatus_t sendPublish( MQTTContext_t * pContext, } else { - LogDebug( "PUBLISH payload was not sent. Payload length was zero." ); + LogDebug( ( "PUBLISH payload was not sent. Payload length was zero." ) ); } } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 7ea35e3e4c..6d3725226b 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1164,7 +1164,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket pPublishInfo->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } - LogDebug( ( "Payload length %hu.", pPublishInfo->payloadLength ) ); + LogDebug( ( "Payload length %lu.", pPublishInfo->payloadLength ) ); } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index c1eb585bda..f4e28cefc2 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -15,8 +15,13 @@ #include "logging_levels.h" /* Configure name and log level for the MQTT library. */ -#define LIBRARY_LOG_NAME "MQTT" -#define LIBRARY_LOG_LEVEL LOG_NONE +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_NONE +#endif #include "logging_stack.h" From b5f1ed669b1e4e76e5482b49c9505e2fdd1e2011 Mon Sep 17 00:00:00 2001 From: Navin Soni <51832113+navinns@users.noreply.github.com> Date: Tue, 4 Aug 2020 18:17:51 -0700 Subject: [PATCH 610/844] Adds Code Coverage (#1076) * Adds Code Coverage * Adds code to create coverage report * Adds option to upload report to codecov.io * Use requests --- tools/ci/ci_agent.py | 129 +++++++++++++++++++++++++++++++++-- tools/ci/unittest_config.yml | 1 + 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/tools/ci/ci_agent.py b/tools/ci/ci_agent.py index 077c90ed83..b492a5fcc8 100644 --- a/tools/ci/ci_agent.py +++ b/tools/ci/ci_agent.py @@ -1,4 +1,6 @@ +from shlex import quote import argparse +import os import re import shutil import subprocess @@ -6,6 +8,7 @@ from pathlib import Path import junitparser as junit +import requests import yaml @@ -88,6 +91,36 @@ def cli_run(args): _check_status(result) +def cli_code_coverage(args): + """ + Generates Code Coverage report. + + Create code coverage report using gcov. + + Parameters: + src: location of csdk src + build_path: location of build dir + config_file: Specifies configuration for setting cmake build. + build_flags: [Optional] Array of build flags to setup cmake build. + c_flags: [Optional] Array of c flags used by compiler + codecov_token: [Optional] codecov token required to upload code to codecov.io + """ + + _file = Path(args.config_file) + config = _read_config(_file) + default_config = config.get("_default", {}) + + build_flags = args.build_flags or _get_flags(config, "build_flags") + c_flags = args.c_flags or _get_flags(config, "c_flags") + codecov_token = args.codecov_token or default_config.get("codecov_token", None) + + result = _build_code_coverage( + args.src, args.build_path, build_flags, c_flags, codecov_token + ) + _log_save_result(result, f"build_coverage.xml") + _check_status(result) + + def cli_invoke(args_list): _cli_invoke_next(args_list) @@ -115,6 +148,9 @@ def add_arguments(cmd, *args): "--c-flags", nargs="+", help="Optional c_flags required to configure build", ) new_argument("--allow", nargs="+", help="Pattern for target selection") + new_argument( + "--codecov-token", help="Optional token to upload coverage report to codecov.io" + ) parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() @@ -159,6 +195,19 @@ def add_arguments(cmd, *args): ) add_argument(cmd_run, "--targets", required=False) cmd_run.set_defaults(func="run") + + cmd_code_coverage = subparsers.add_parser("code-coverage") + add_arguments( + cmd_code_coverage, + "--config-file", + "--src", + "--build-path", + "--build-flags", + "--c-flags", + "--codecov-token", + ) + cmd_code_coverage.set_defaults(func="code-coverage") + return parser @@ -189,7 +238,7 @@ def _config_build(src, build_path, build_flags, c_flags): c_flags = f"-DCMAKE_C_FLAGS='{c_flags}'" _del_dir(build_path) _run_cmd( - f'cmake -S {src} -B {build_path} {build_flags} {c_flags} -G "Unix Makefiles"' + f'cmake -S {quote(src)} -B {quote(build_path)} {build_flags} {c_flags} -G "Unix Makefiles"' ) @@ -203,7 +252,7 @@ def _get_flags(config, flag_type, target="all"): def _get_targets(build_path, allow): - targets = _run_cmd(f"make help -C {build_path} | tr -d '. '") + targets = _run_cmd(f"make help -C {quote(build_path)} | tr -d '. '") targets = [t.strip() for t in targets.split()] allow = "|".join(allow) targets = [target for target in targets if re.search(allow, target)] @@ -217,7 +266,7 @@ def _build_target(target, src, build_path, build_flags, c_flags): _config_build(src, build_path, build_flags, c_flags) - cmd = f"make -C {build_path} {target}" + cmd = f"make -C {quote(build_path)} {quote(target)}" return _run_cmd(cmd) @@ -244,7 +293,7 @@ def _run_target(target, run_path): log("\n----------------------------------------------------------------") log(f"Running Target: {target}") log("----------------------------------------------------------------") - return _run_cmd(f"cd {run_path} && ./{target}") + return _run_cmd(f"cd {quote(run_path)} && './{target}'") def _run_targets(targets, src, build_path, config): @@ -278,6 +327,78 @@ def _run_targets(targets, src, build_path, config): return result +def _build_code_coverage(src, build_path, build_flags, c_flags, codecov_token): + """ + Private code coverage function. + + Parameters: + src: location of csdk src + build_path: location of build dir + build_flags: Array of build flags to setup cmake build. + c_flags: Array of c flags used by compiler + codecov_token: codecov token required to upload code to codecov.io + """ + result = {} + try: + target_result = result.setdefault("coverage", {}) + target_build_result = target_result.setdefault("CodeCoverage", {}) + out = _build_target("coverage", src, build_path, build_flags, c_flags) + log(out) + out = _run_cmd(f"gcovr -r . -x -o {quote(build_path)}/cobertura.xml") + log(out) + + commit_id = os.environ.get("ghprbActualCommit") or "" + + if codecov_token and commit_id: + log("\n----------------------------------------------------------------") + log("Upload Code Coverage report to codecov.io") + log("----------------------------------------------------------------") + + params = { + "token": codecov_token, + "commit": commit_id, + "branch": "", + "build": "", + "build_url": "", + "name": "", + "tag": "", + "slug": "", + "service": "", + "flags": "", + "pr": "", + "job": "", + "cmd_args": "", + } + headers = {"content-type": "application/x-www-form-urlencoded"} + with open(f"{build_path}/cobertura.xml", "rb") as payload: + try: + requests.post( + "https://codecov.io/upload/v2", + data=payload, + verify=True, + headers=headers, + params=params, + ) + except requests.exceptions.RequestException as err: + log(f"Error calling codecov.io: {err}") + else: + if not commit_id: + log( + "Please provide commit id, if you want to upload coverage report to codecov.io" + ) + if not codecov_token: + log( + "Provide '--codecov-token' in commandline, \ + if you want to upload coverage report to codecov.io" + ) + target_build_result["status"] = "PASS" + except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err: + log(err.stdout) + target_build_result["status"] = "FAIL" + target_build_result["details"] = "Code Coverage Failure" + return result + + def _del_dir(dir_name): try: log(f"Deleting dir: {dir_name}") diff --git a/tools/ci/unittest_config.yml b/tools/ci/unittest_config.yml index 1ea6bfbf0e..a95c204bf3 100644 --- a/tools/ci/unittest_config.yml +++ b/tools/ci/unittest_config.yml @@ -16,6 +16,7 @@ _default: # Provide list of allowed targets [Accepts regex] - ".*utest.*" output_loc: "bin/tests" + codecov_token: "" #: From 4a292f545d18fcccd19323901a5a4f8b6f371901 Mon Sep 17 00:00:00 2001 From: DanielYEHsieh Date: Fri, 7 Aug 2020 10:36:23 +0800 Subject: [PATCH 611/844] Shadow initial commit (#1081) * Shadow LTS initial commit: - added shadow library src folder - added utest for shadow to test shadow library APIs and MACROS Co-authored-by: DanielYEHsieh Co-authored-by: Hsieh --- libraries/CMakeLists.txt | 8 +- libraries/aws/shadow/CMakeLists.txt | 19 + libraries/aws/shadow/include/shadow.h | 455 ++++++++++++++++ libraries/aws/shadow/shadowFilePaths.cmake | 17 + libraries/aws/shadow/src/shadow.c | 592 +++++++++++++++++++++ libraries/aws/shadow/utest/CMakeLists.txt | 61 +++ libraries/aws/shadow/utest/shadow_config.h | 25 + libraries/aws/shadow/utest/shadow_utest.c | 519 ++++++++++++++++++ 8 files changed, 1693 insertions(+), 3 deletions(-) create mode 100644 libraries/aws/shadow/CMakeLists.txt create mode 100644 libraries/aws/shadow/include/shadow.h create mode 100644 libraries/aws/shadow/shadowFilePaths.cmake create mode 100644 libraries/aws/shadow/src/shadow.c create mode 100644 libraries/aws/shadow/utest/CMakeLists.txt create mode 100644 libraries/aws/shadow/utest/shadow_config.h create mode 100644 libraries/aws/shadow/utest/shadow_utest.c diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 43a32816ca..e2d7b1a619 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -40,10 +40,12 @@ if( ${BUILD_TESTS} ) ) endif() -# Add all CMakeLists.txts in the standard folder. +# Add all modules. file(GLOB standard_modules "${MODULES_DIR}/standard/*") -foreach(module IN LISTS standard_modules ) +file(GLOB aws_modules "${MODULES_DIR}/aws/*") +foreach(module IN LISTS standard_modules aws_modules) if(IS_DIRECTORY "${module}" AND EXISTS "${module}/CMakeLists.txt") add_subdirectory(${module}) endif() -endforeach() \ No newline at end of file +endforeach() + diff --git a/libraries/aws/shadow/CMakeLists.txt b/libraries/aws/shadow/CMakeLists.txt new file mode 100644 index 0000000000..997cfb2498 --- /dev/null +++ b/libraries/aws/shadow/CMakeLists.txt @@ -0,0 +1,19 @@ +# Include filepaths for source and include. +include( shadowFilePaths.cmake ) + +# SHADOW library target. +add_library( shadow + ${SHADOW_SOURCES} ) + +# SHADOW public include path. +target_include_directories( shadow PUBLIC ${SHADOW_INCLUDE_PUBLIC_DIRS} + ${LOGGING_INCLUDE_DIRS} ) + +# Organization of SHADOW in IDE projects. +set_target_properties( shadow PROPERTIES FOLDER libraries/aws ) +source_group( include FILES include/shadow.h) +source_group( src FILES ${SHADOW_SOURCES} ) + +if(BUILD_TESTS) + add_subdirectory( utest ) +endif() diff --git a/libraries/aws/shadow/include/shadow.h b/libraries/aws/shadow/include/shadow.h new file mode 100644 index 0000000000..d7eb630d0f --- /dev/null +++ b/libraries/aws/shadow/include/shadow.h @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file shadow.h + * @brief User-facing Shadow functions, and parameter structs. + */ + +#ifndef _SHADOW_H_ +#define _SHADOW_H_ + +/* Standard includes. */ +#include +/* Include config file before other headers. */ +#include "shadow_config.h" + +/*--------------------------- Shadow types ---------------------------*/ + +/** + * @brief Each of these values describes the type of a shadow message. + * https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html + */ +typedef enum ShadowMessageType +{ + ShadowMessageTypeGetAccepted = 0, + ShadowMessageTypeGetRejected, + ShadowMessageTypeDeleteAccepted, + ShadowMessageTypeDeleteRejected, + ShadowMessageTypeUpdateAccepted, + ShadowMessageTypeUpdateRejected, + ShadowMessageTypeUpdateDocuments, + ShadowMessageTypeUpdateDelta, + ShadowMessageTypeMaxNum +} ShadowMessageType_t; + +/** + * @brief Each of these values describes the type of a shadow topic string. + * + * These are used for topicType parameter of Shadow_GetTopicString() to tell it + * what topic string to assemble. + */ +typedef enum ShadowTopicStringType +{ + ShadowTopicStringTypeGet = 0, + ShadowTopicStringTypeGetAccepted, + ShadowTopicStringTypeGetRejected, + ShadowTopicStringTypeDelete, + ShadowTopicStringTypeDeleteAccepted, + ShadowTopicStringTypeDeleteRejected, + ShadowTopicStringTypeUpdate, + ShadowTopicStringTypeUpdateAccepted, + ShadowTopicStringTypeUpdateRejected, + ShadowTopicStringTypeUpdateDocuments, + ShadowTopicStringTypeUpdateDelta, + ShadowTopicStringTypeMaxNum +} ShadowTopicStringType_t; + +/** + * @brief Return codes from Shadow functions. + */ +typedef enum ShadowStatus +{ + SHADOW_SUCCESS = 0, /**< @brief Shadow function success. */ + SHADOW_FAIL, /**< @brief Shadow function encountered error. */ + SHADOW_BAD_PARAMETER, /**< @brief Input parameter is invalid. */ + SHADOW_BUFFER_TOO_SMALL, /**< @brief The provided buffer is too small. */ + SHADOW_THINGNAME_PARSE_FAILED, /**< @brief Could not parse the thing name. */ + SHADOW_SHADOW_MESSAGE_TYPE_PARSE_FAILED /**< @brief Could not parse the shadow type. */ +} ShadowStatus_t; + +/*------------------------ Shadow library functions -------------------------*/ + +/** + * @brief The common prefix of all Shadow MQTT topics + * from here https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html. + */ +#define SHADOW_PREFIX "$aws/things/" + +/** + * @brief The length of #SHADOW_PREFIX. + */ +#define SHADOW_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_PREFIX ) - 1U ) ) + +/** + * @brief The string representing a Shadow "DELETE" operation in a Shadow MQTT topic. + */ +#define SHADOW_OP_DELETE "/shadow/delete" + +/** + * @brief The length of #SHADOW_OP_DELETE. + */ +#define SHADOW_OP_DELETE_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_OP_DELETE ) - 1U ) ) + +/** + * @brief The string representing a Shadow "GET" operation in a Shadow MQTT topic. + */ +#define SHADOW_OP_GET "/shadow/get" + +/** + * @brief The length of #SHADOW_OP_GET. + */ +#define SHADOW_OP_GET_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_OP_GET ) - 1U ) ) + +/** + * @brief The string representing a Shadow "UPDATE" operation in a Shadow MQTT topic. + */ +#define SHADOW_OP_UPDATE "/shadow/update" + +/** + * @brief The length of #SHADOW_OP_UPDATE. + */ +#define SHADOW_OP_UPDATE_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_OP_UPDATE ) - 1U ) ) + +/** + * @brief The suffix for a Shadow operation "accepted" topic. + */ +#define SHADOW_SUFFIX_ACCEPTED "/accepted" + +/** + * @brief The length of #SHADOW_SUFFIX_ACCEPTED. + */ +#define SHADOW_SUFFIX_ACCEPTED_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_SUFFIX_ACCEPTED ) - 1U ) ) + +/** + * @brief The suffix for a Shadow operation "rejected" topic. + */ +#define SHADOW_SUFFIX_REJECTED "/rejected" + +/** + * @brief The length of #SHADOW_SUFFIX_REJECTED. + */ +#define SHADOW_SUFFIX_REJECTED_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_SUFFIX_REJECTED ) - 1U ) ) + +/** + * @brief The suffix for a Shadow "delta" topic. + */ +#define SHADOW_SUFFIX_DELTA "/delta" + +/** + * @brief The length of #SHADOW_SUFFIX_DELTA. + */ +#define SHADOW_SUFFIX_DELTA_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_SUFFIX_DELTA ) - 1U ) ) + +/** + * @brief The suffix for a Shadow "documents" topic. + */ +#define SHADOW_SUFFIX_DOCUMENTS "/documents" + +/** + * @brief The length of #SHADOW_SUFFIX_DOCUMENTS. + */ +#define SHADOW_SUFFIX_DOCUMENTS_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_SUFFIX_DOCUMENTS ) - 1U ) ) + +/** + * @brief The suffix for a "null" suffix. + */ +#define SHADOW_SUFFIX_NULL + +/** + * @brief The length of null suffix. + */ +#define SHADOW_SUFFIX_NULL_LENGTH ( 0U ) + +/** + * @brief The maximum length of Thing Name. + */ +#define SHADOW_THINGNAME_LENGTH_MAX ( 128U ) + +/** + * @brief Compute shadow topic length. + * + * The format of shadow topic strings is defined at https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html + * + * A shadow topic string takes one of the two forms: + * $aws/things//shadow/ + * $aws/things//shadow// + * + * The , and segments correspond to the three input + * parameters of this macro. The part can be null. + * + * When thingName is known to be "myThing" at compile time, invoke the macro like this: + * (In this case, the length is a constant at compile time.) + * + * SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_DELTA_LENGTH, 7 ) + * + * When thingName is only known at run time and held in a variable myThingName, invoke + * the macro like this: + * + * SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_DELTA_LENGTH, + * strlen( ( const char * ) myThingName ) ) + * + * @param[operationLen] Can be one of: + * - SHADOW_OP_UPDATE_LENGTH + * - SHADOW_OP_DELETE_LENGTH + * - SHADOW_OP_GET_LENGTH + * @param[suffixLen] Can be one of: + * - SHADOW_SUFFIX_NULL_LENGTH + * - SHADOW_SUFFIX_ACCEPTED_LENGTH + * - SHADOW_SUFFIX_REJECTED_LENGTH + * - SHADOW_SUFFIX_DELTA_LENGTH + * - SHADOW_SUFFIX_DOCUMENTS_LENGTH + * @param[thingNameLength] Length of the thingName excluding the ending NULL. + * + * @return Length of the shadow topic in bytes. + */ +#define SHADOW_TOPIC_LENGTH( operationLength, suffixLength, thingNameLength ) \ + ( operationLength + suffixLength + thingNameLength + SHADOW_PREFIX_LENGTH ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/update". + */ +#define SHADOW_TOPIC_LENGTH_UPDATE( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_NULL_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/update/accepted". + */ +#define SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_ACCEPTED_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/update/rejected". + */ +#define SHADOW_TOPIC_LENGTH_UPDATE_REJECTED( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_REJECTED_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/update/documents". + */ +#define SHADOW_TOPIC_LENGTH_UPDATE_DOCUMENTS( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_DOCUMENTS_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/update/delta". + */ +#define SHADOW_TOPIC_LENGTH_UPDATE_DELTA( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_DELTA_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/get". + */ +#define SHADOW_TOPIC_LENGTH_GET( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_GET_LENGTH, SHADOW_SUFFIX_NULL_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/get/accepted". + */ +#define SHADOW_TOPIC_LENGTH_GET_ACCEPTED( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_GET_LENGTH, SHADOW_SUFFIX_ACCEPTED_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/get/rejected". + */ +#define SHADOW_TOPIC_LENGTH_GET_REJECTED( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_GET_LENGTH, SHADOW_SUFFIX_REJECTED_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/delete". + */ +#define SHADOW_TOPIC_LENGTH_DELETE( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_DELETE_LENGTH, SHADOW_SUFFIX_NULL_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/delete/accepted". + */ +#define SHADOW_TOPIC_LENGTH_DELETE_ACCEPTED( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_DELETE_LENGTH, SHADOW_SUFFIX_ACCEPTED_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of shadow topic "$aws/things//shadow/delete/rejected". + */ +#define SHADOW_TOPIC_LENGTH_DELETE_REJECTED( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_DELETE_LENGTH, SHADOW_SUFFIX_REJECTED_LENGTH, thingNameLength ) + +/** + * @brief Compute the length of the longest shadow topic. + */ +#define SHADOW_TOPIC_LENGTH_MAX( thingNameLength ) \ + SHADOW_TOPIC_LENGTH( SHADOW_OP_UPDATE_LENGTH, SHADOW_SUFFIX_DOCUMENTS_LENGTH, thingNameLength ) + +/** + * @brief Assemble constant shadow topic strings when Thing Name is known at compile time. + * + * When thingName is known to be "myThing" at compile time, invoke the macro like this: + * + * SHADOW_TOPIC_STRING( SHADOW_OP_UPDATE, SHADOW_SUFFIX_DELTA, "myThing" ) + * + * When thingName is only known at run time, do not use this macro. Use the + * Shadow_GetTopicString() function instead. + * + * @param[operation] Can be one of: + * - SHADOW_OP_UPDATE + * - SHADOW_OP_DELETE + * - SHADOW_OP_GET + * @param[suffix] Can be one of: + * - SHADOW_SUFFIX_NULL + * - SHADOW_SUFFIX_ACCEPTED + * - SHADOW_SUFFIX_REJECTED + * - SHADOW_SUFFIX_DELTA + * - SHADOW_SUFFIX_DOCUMENTS + * + * @param[thingName] Thing Name. + * + * @return Topic string. + */ +#define SHADOW_TOPIC_STRING( thingName, operation, suffix ) \ + ( SHADOW_PREFIX thingName operation suffix ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/update". + */ +#define SHADOW_TOPIC_STRING_UPDATE( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_UPDATE, SHADOW_SUFFIX_NULL ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/update/accepted". + */ +#define SHADOW_TOPIC_STRING_UPDATE_ACCEPTED( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_UPDATE, SHADOW_SUFFIX_ACCEPTED ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/update/rejected". + */ +#define SHADOW_TOPIC_STRING_UPDATE_REJECTED( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_UPDATE, SHADOW_SUFFIX_REJECTED ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/update/documents". + */ +#define SHADOW_TOPIC_STRING_UPDATE_DOCUMENTS( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_UPDATE, SHADOW_SUFFIX_DOCUMENTS ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/update/delta". + */ +#define SHADOW_TOPIC_STRING_UPDATE_DELTA( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_UPDATE, SHADOW_SUFFIX_DELTA ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/get". + */ +#define SHADOW_TOPIC_STRING_GET( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_GET, SHADOW_SUFFIX_NULL ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/get/accepted". + */ +#define SHADOW_TOPIC_STRING_GET_ACCEPTED( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_GET, SHADOW_SUFFIX_ACCEPTED ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/get/rejected". + */ +#define SHADOW_TOPIC_STRING_GET_REJECTED( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_GET, SHADOW_SUFFIX_REJECTED ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/delete". + */ +#define SHADOW_TOPIC_STRING_DELETE( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_DELETE, SHADOW_SUFFIX_NULL ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/delete/accepted". + */ +#define SHADOW_TOPIC_STRING_DELETE_ACCEPTED( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_DELETE, SHADOW_SUFFIX_ACCEPTED ) + +/** + * @brief Assemble shadow topic string "$aws/things//shadow/delete/rejected". + */ +#define SHADOW_TOPIC_STRING_DELETE_REJECTED( thingName ) \ + SHADOW_TOPIC_STRING( thingName, SHADOW_OP_DELETE, SHADOW_SUFFIX_REJECTED ) + +/** + * @brief Assemble shadow topic strings when Thing Name is only known at run time. + * + * @param[in] topicType Indicates what topic will be written into the buffer pointed to by pTopicBuffer. + * can be one of: + * - ShadowTopicStringTypeGet + * - ShadowTopicStringTypeGetAccepted + * - ShadowTopicStringTypeGetRejected + * - ShadowTopicStringTypeDelete + * - ShadowTopicStringTypeDeleteAccepted + * - ShadowTopicStringTypeDeleteRejected + * - ShadowTopicStringTypeUpdate + * - ShadowTopicStringTypeUpdateAccepted + * - ShadowTopicStringTypeUpdateRejected + * - ShadowTopicStringTypeUpdateDocuments + * - ShadowTopicStringTypeUpdateDelta + * @param[in] pThingName Thing Name string. No need to be null terminated. Must not be NULL. + * @param[in] thingNameLength Length of Thing Name string pointed to by pThingName. Must not be zero. + * @param[out] pTopicBuffer Pointer to buffer for returning the topic string. + * Caller is responsible for supplying memory pointed to by pTopicBuffer. + * This function does not fill in the terminating null character. The app + * can supply a buffer that does not have space for holding the null character. + * @param[in] bufferSize Length of pTopicBuffer. This function will return error if + * bufferSize is less than the length of the assembled topic string. + * @param[out] pOutLength Pointer to caller-supplied memory for returning the length of the topic string. + * @return One of the following: + * - SHADOW_SUCCESS if successful. + * - An error code if failed to assemble. + */ +ShadowStatus_t Shadow_GetTopicString( ShadowTopicStringType_t topicType, + const char * pThingName, + uint8_t thingNameLength, + char * pTopicBuffer, + uint16_t bufferSize, + uint16_t * pOutLength ); + +/** + * @brief Given the topic string of an incoming message, determine whether it is + * related to a device shadow; if it is, return information about the type of + * device shadow message, and a pointer to the Thing Name inside of the topic string. + * + * @note When this function returns, the pointer pThingName points at the first character + * of the segment inside of the topic string. + * Caller is responsible for keeping the memory holding the topic strings around. + * + * @param[in] pTopic Pointer to the MQTT topic string. Does not have to be null-terminated. + * @param[in] topicLength Length of the MQTT topic string. + * @param[out] pMessageType Pointer to call-supplied memory for returning the type of the shadow message. + * @param[out] pThingName Points to the 1st character of Thing Name inside of the topic string. + * @param[out] pThingNameLength Pointer to caller-supplied memory for returning the length of the Thing Name. + * @return One of the following: + * - SHADOW_SUCCESS if the message is related to a device shadow; + * - An error code if the message is not related to a device shadow, + * if any input parameter is invalid, or if the function fails to + * parse the topic string. + */ +ShadowStatus_t Shadow_MatchTopic( const char * pTopic, + uint16_t topicLength, + ShadowMessageType_t * pMessageType, + const char ** pThingName, + uint16_t * pThingNameLength ); + +#endif /* ifndef _SHADOW_H_ */ diff --git a/libraries/aws/shadow/shadowFilePaths.cmake b/libraries/aws/shadow/shadowFilePaths.cmake new file mode 100644 index 0000000000..7f856720fa --- /dev/null +++ b/libraries/aws/shadow/shadowFilePaths.cmake @@ -0,0 +1,17 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# SHADOW library source files. +set( SHADOW_SOURCES + ${MODULES_DIR}/aws/shadow/src/shadow.c ) + +# SHADOW library Public Include directories. +# Temporary adding utest into PUBLIC_DIRS to make shadow_config.h visible +# Will remove it after shadow demo is merged +set( SHADOW_INCLUDE_PUBLIC_DIRS + ${MODULES_DIR}/aws/shadow/include + ${MODULES_DIR}/aws/shadow/utest ) diff --git a/libraries/aws/shadow/src/shadow.c b/libraries/aws/shadow/src/shadow.c new file mode 100644 index 0000000000..3861459137 --- /dev/null +++ b/libraries/aws/shadow/src/shadow.c @@ -0,0 +1,592 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file shadow.c + * @brief Implements the user-facing functions of the Shadow library. + */ + +/* Standard includes. */ +#include + +/* Shadow includes. */ +#include "shadow.h" + +/** + * @brief The string representing "/shadow/update/accepted". + */ +#define SHADOW_OP_UPDATE_ACCEPTED SHADOW_OP_UPDATE SHADOW_SUFFIX_ACCEPTED + +/** + * @brief The string representing "/shadow/update/rejected". + */ +#define SHADOW_OP_UPDATE_REJECTED SHADOW_OP_UPDATE SHADOW_SUFFIX_REJECTED + +/** + * @brief The string representing "/shadow/update/delta". + */ +#define SHADOW_OP_UPDATE_DELTA SHADOW_OP_UPDATE SHADOW_SUFFIX_DELTA + +/** + * @brief The string representing "/shadow/update/document". + */ +#define SHADOW_OP_UPDATE_DOCUMENTS SHADOW_OP_UPDATE SHADOW_SUFFIX_DOCUMENTS + +/** + * @brief The string representing "/shadow/delete/accepted". + */ +#define SHADOW_OP_DELETE_ACCEPTED SHADOW_OP_DELETE SHADOW_SUFFIX_ACCEPTED + +/** + * @brief The string representing "/shadow/delete/accepted". + */ +#define SHADOW_OP_DELETE_REJECTED SHADOW_OP_DELETE SHADOW_SUFFIX_REJECTED + +/** + * @brief The string representing "/shadow/get/accepted". + */ +#define SHADOW_OP_GET_ACCEPTED SHADOW_OP_GET SHADOW_SUFFIX_ACCEPTED + +/** + * @brief The string representing "/shadow/get/accepted". + */ +#define SHADOW_OP_GET_REJECTED SHADOW_OP_GET SHADOW_SUFFIX_REJECTED + +/** + * @brief The length of "/shadow/update/accepted". + */ +#define SHADOW_OP_UPDATE_ACCEPTED_LENGTH ( SHADOW_OP_UPDATE_LENGTH + SHADOW_SUFFIX_ACCEPTED_LENGTH ) + +/** + * @brief The length of "/shadow/update/rejected". + */ +#define SHADOW_OP_UPDATE_REJECTED_LENGTH ( SHADOW_OP_UPDATE_LENGTH + SHADOW_SUFFIX_REJECTED_LENGTH ) + +/** + * @brief The length of "/shadow/update/document". + */ +#define SHADOW_OP_UPDATE_DOCUMENTS_LENGTH ( SHADOW_OP_UPDATE_LENGTH + SHADOW_SUFFIX_DOCUMENTS_LENGTH ) + +/** + * @brief The length of "/shadow/update/rejected". + */ +#define SHADOW_OP_UPDATE_DELTA_LENGTH ( SHADOW_OP_UPDATE_LENGTH + SHADOW_SUFFIX_DELTA_LENGTH ) + +/** + * @brief The length of "/shadow/get/accepted". + */ +#define SHADOW_OP_GET_ACCEPTED_LENGTH ( SHADOW_OP_GET_LENGTH + SHADOW_SUFFIX_ACCEPTED_LENGTH ) + +/** + * @brief The length of "/shadow/get/rejected". + */ +#define SHADOW_OP_GET_REJECTED_LENGTH ( SHADOW_OP_GET_LENGTH + SHADOW_SUFFIX_REJECTED_LENGTH ) + +/** + * @brief The length of "/shadow/get/accepted". + */ +#define SHADOW_OP_DELETE_ACCEPTED_LENGTH ( SHADOW_OP_DELETE_LENGTH + SHADOW_SUFFIX_ACCEPTED_LENGTH ) + +/** + * @brief The length of "/shadow/delete/rejected". + */ +#define SHADOW_OP_DELETE_REJECTED_LENGTH ( SHADOW_OP_DELETE_LENGTH + SHADOW_SUFFIX_REJECTED_LENGTH ) + +/** + * @brief Determine if the string contains the substring. + * + * @param[in] pString Pointer to the string. + * @param[in] stringLength Length of pString. + * @param[in] pSubString Pointer to the substring. + * @param[in] subStringLength Length of pSubString. + * + * @return Return SHADOW_SUCCESS if it contains; + * return SHADOW_FAIL if not. + */ +static ShadowStatus_t containsSubString( const char * pString, + uint16_t stringLength, + const char * pSubString, + uint16_t subStringLength ); +/** + * @brief Check if the Thing Name is valid. + * + * @param[in] pString Pointer to the starting of thing name. + * @param[in] stringLength Length of pString. + * @param[out] pThingNameLength Pointer to caller-supplied memory for returning the length of the Thing Name. + * + * @return Return SHADOW_SUCCESS if it is valid; + * return SHADOW_THINGNAME_PARSE_FAILED if it is not. + */ +static ShadowStatus_t validateThingName( const char * pString, + uint16_t stringLength, + uint16_t * pThingNameLength ); + +/** + * @brief Extract the Shadow message type from a string. + * + * @param[in] pString Pointer to the string. + * @param[in] stringLength Length of pString. + * @param[out] pMessageType Pointer to call-supplied memory for returning the type of the shadow message. + * + * @return Return SHADOW_SUCCESS if successfully extracted; + * return SHADOW_SHADOW_MESSAGE_TYPE_PARSE_FAILED if failed. + */ +static ShadowStatus_t extractShadowMessageType( const char * pString, + uint16_t stringLength, + ShadowMessageType_t * pMessageType ); + +/** + * @brief Get the shadow operation string for a given shadow topic type. + * + * @param[in] topicType The given shadow topic type. + * + * @return The shadow operation string for the given shadow type. + */ +static const char * getShadowOperationString( ShadowTopicStringType_t topicType ); + +/** + * @brief Get the shadow operation string length for a given shadow topic type. + * + * @param[in] topicType The given shadow topic type. + * + * @return The shadow operation string length for the given shadow type. + */ +static uint16_t getShadowOperationLength( ShadowTopicStringType_t topicType ); + +/*-----------------------------------------------------------*/ + +static ShadowStatus_t containsSubString( const char * pString, + uint16_t stringLength, + const char * pSubString, + uint16_t subStringLength ) +{ + ShadowStatus_t returnStatus = SHADOW_FAIL; + + /* The string must be at least as long as the substring to contain it + * completely. */ + if( stringLength >= subStringLength ) + { + /* We are only checking up to subStringLength characters in the original + * string. The string may be longer and contain additional characters. */ + if( strncmp( pString, pSubString, ( size_t ) subStringLength ) == 0 ) + { + returnStatus = SHADOW_SUCCESS; + } + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static ShadowStatus_t validateThingName( const char * pString, + uint16_t stringLength, + uint16_t * pThingNameLength ) +{ + uint16_t index = 0U; + ShadowStatus_t returnStatus = SHADOW_THINGNAME_PARSE_FAILED; + + for( ; index < stringLength; index++ ) + { + if( pString[ index ] == ( char ) '/' ) + { + break; + } + } + + /* Zero length thing name is not valid, + * $"$aws/things// + * $"$aws/things/" + * will extract the same thing name result. + * Only empty thing name string like: + * "$aws/things/" or "$aws/things" will fail. + */ + if( index > 0U ) + { + * pThingNameLength = index; + returnStatus = SHADOW_SUCCESS; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static ShadowStatus_t extractShadowMessageType( const char * pString, + uint16_t stringLength, + ShadowMessageType_t * pMessageType ) +{ + uint32_t index = 0U; + ShadowStatus_t returnStatus = SHADOW_FAIL; + + /* Lookup table for Shadow message string. */ + static const char * const pMessageStrings[ ShadowMessageTypeMaxNum ] = + { + SHADOW_OP_GET_ACCEPTED, + SHADOW_OP_GET_REJECTED, + SHADOW_OP_DELETE_ACCEPTED, + SHADOW_OP_DELETE_REJECTED, + SHADOW_OP_UPDATE_ACCEPTED, + SHADOW_OP_UPDATE_REJECTED, + SHADOW_OP_UPDATE_DOCUMENTS, + SHADOW_OP_UPDATE_DELTA + }; + + /* Lookup table for Shadow message string length. */ + static const uint16_t pMessageStringsLength[ ShadowMessageTypeMaxNum ] = + { + SHADOW_OP_GET_ACCEPTED_LENGTH, + SHADOW_OP_GET_REJECTED_LENGTH, + SHADOW_OP_DELETE_ACCEPTED_LENGTH, + SHADOW_OP_DELETE_REJECTED_LENGTH, + SHADOW_OP_UPDATE_ACCEPTED_LENGTH, + SHADOW_OP_UPDATE_REJECTED_LENGTH, + SHADOW_OP_UPDATE_DOCUMENTS_LENGTH, + SHADOW_OP_UPDATE_DELTA_LENGTH + }; + + /* Lookup table for Shadow message types. */ + static const ShadowMessageType_t pMessageTypes[ ShadowMessageTypeMaxNum ] = + { + ShadowMessageTypeGetAccepted , + ShadowMessageTypeGetRejected, + ShadowMessageTypeDeleteAccepted, + ShadowMessageTypeDeleteRejected, + ShadowMessageTypeUpdateAccepted, + ShadowMessageTypeUpdateRejected, + ShadowMessageTypeUpdateDocuments, + ShadowMessageTypeUpdateDelta + }; + + for( ; index < ( uint32_t ) ( sizeof( pMessageStrings ) / sizeof( pMessageStrings[0] ) ); index++ ) + { + returnStatus = containsSubString( pString, + stringLength, + pMessageStrings[ index ], + pMessageStringsLength[ index ] ); + + /* If the operation string matches, there must not be any other extra + * character remaining in the string. */ + if( returnStatus == SHADOW_SUCCESS ) + { + if( stringLength != pMessageStringsLength[ index ] ) + { + returnStatus = SHADOW_FAIL; + } + else + { + * pMessageType = pMessageTypes[ index ]; + break; + } + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static const char * getShadowOperationString( ShadowTopicStringType_t topicType ) +{ + const char *shadowOperationString = NULL; + + switch( topicType ) + { + case ShadowTopicStringTypeGet: + shadowOperationString = SHADOW_OP_GET; + break; + + case ShadowTopicStringTypeGetAccepted: + shadowOperationString = SHADOW_OP_GET_ACCEPTED; + break; + + case ShadowTopicStringTypeGetRejected: + shadowOperationString = SHADOW_OP_GET_REJECTED; + break; + + case ShadowTopicStringTypeDelete: + shadowOperationString = SHADOW_OP_DELETE; + break; + + case ShadowTopicStringTypeDeleteAccepted: + shadowOperationString = SHADOW_OP_DELETE_ACCEPTED; + break; + + case ShadowTopicStringTypeDeleteRejected: + shadowOperationString = SHADOW_OP_DELETE_REJECTED; + break; + + case ShadowTopicStringTypeUpdate: + shadowOperationString = SHADOW_OP_UPDATE; + break; + + case ShadowTopicStringTypeUpdateAccepted: + shadowOperationString = SHADOW_OP_UPDATE_ACCEPTED; + break; + + case ShadowTopicStringTypeUpdateRejected: + shadowOperationString = SHADOW_OP_UPDATE_REJECTED; + break; + + case ShadowTopicStringTypeUpdateDocuments: + shadowOperationString = SHADOW_OP_UPDATE_DOCUMENTS; + break; + + case ShadowTopicStringTypeUpdateDelta: + shadowOperationString = SHADOW_OP_UPDATE_DELTA; + break; + + default: + LogError( ( "Unexpected topicType: %u", topicType ) ); + shadowOperationString = NULL; + break; + } + + return shadowOperationString; +} + +/*-----------------------------------------------------------*/ + +static uint16_t getShadowOperationLength( ShadowTopicStringType_t topicType ) +{ + uint16_t shadowOperationLength = 0U; + + switch( topicType ) + { + case ShadowTopicStringTypeGet: + shadowOperationLength = SHADOW_OP_GET_LENGTH; + break; + + case ShadowTopicStringTypeGetAccepted: + shadowOperationLength = SHADOW_OP_GET_ACCEPTED_LENGTH; + break; + + case ShadowTopicStringTypeGetRejected: + shadowOperationLength = SHADOW_OP_GET_REJECTED_LENGTH; + break; + + case ShadowTopicStringTypeDelete: + shadowOperationLength = SHADOW_OP_DELETE_LENGTH; + break; + + case ShadowTopicStringTypeDeleteAccepted: + shadowOperationLength = SHADOW_OP_DELETE_ACCEPTED_LENGTH; + break; + + case ShadowTopicStringTypeDeleteRejected: + shadowOperationLength = SHADOW_OP_DELETE_REJECTED_LENGTH; + break; + + case ShadowTopicStringTypeUpdate: + shadowOperationLength = SHADOW_OP_UPDATE_LENGTH; + break; + + case ShadowTopicStringTypeUpdateAccepted: + shadowOperationLength = SHADOW_OP_UPDATE_ACCEPTED_LENGTH; + break; + + case ShadowTopicStringTypeUpdateRejected: + shadowOperationLength = SHADOW_OP_UPDATE_REJECTED_LENGTH; + break; + + case ShadowTopicStringTypeUpdateDocuments: + shadowOperationLength = SHADOW_OP_UPDATE_DOCUMENTS_LENGTH; + break; + + case ShadowTopicStringTypeUpdateDelta: + shadowOperationLength = SHADOW_OP_UPDATE_DELTA_LENGTH; + break; + + default: + LogError( ( "Unexpected topicType: %u", topicType ) ); + break; + + } + + return shadowOperationLength; +} + +/*-----------------------------------------------------------*/ + +/*-----------------------------------------------------------*/ + +ShadowStatus_t Shadow_MatchTopic( const char * pTopic, + uint16_t topicLength, + ShadowMessageType_t * pMessageType, + const char ** pThingName, + uint16_t * pThingNameLength ) +{ + uint16_t consumedTopicLength = 0U; + ShadowStatus_t shadowStatus = SHADOW_SUCCESS; + + if( ( pTopic == NULL ) || + ( topicLength == 0U ) || + ( pMessageType == NULL ) || + ( pThingName == NULL ) || + ( pThingNameLength == NULL ) ) + { + shadowStatus = SHADOW_BAD_PARAMETER; + LogError( ( "Invalid input parameters pTopic: %p, topicLength: %u, pMessageType: %p, pThingName: %p, pThingNameLength: %p", + pTopic, + topicLength, + pMessageType, + pThingName, + pThingNameLength ) ); + } + + /* A shadow topic string takes one of the two forms: + * $aws/things//shadow/ + * $aws/things//shadow// + * + * We need to match the following things: + * 1. Prefix ($aws/things). + * 2. Thing Name. + * 3. Shadow operation and suffix. + */ + if( shadowStatus == SHADOW_SUCCESS ) + { + /* First match the prefix. */ + shadowStatus = containsSubString( & ( pTopic[ consumedTopicLength ] ), + topicLength - consumedTopicLength, + SHADOW_PREFIX, + SHADOW_PREFIX_LENGTH ); + if( shadowStatus == SHADOW_SUCCESS ) + { + consumedTopicLength += SHADOW_PREFIX_LENGTH; + + /* If no more topic string is left to parse, fail. */ + if( consumedTopicLength >= topicLength ) + { + shadowStatus = SHADOW_THINGNAME_PARSE_FAILED; + LogDebug( ( "Not related to Shadow, thing name is not in pTopic %s, failed to parse thing name", pTopic ) ); + } + } + else + { + LogDebug( ( "Not related to Shadow, failed to parse shadow topic prefix in pTopic %s", pTopic ) ); + } + } + + if( shadowStatus == SHADOW_SUCCESS ) + { + /* Extract thing name. */ + shadowStatus = validateThingName( & ( pTopic[ consumedTopicLength ] ), + topicLength - consumedTopicLength, + pThingNameLength ); + if( shadowStatus == SHADOW_SUCCESS ) + { + /* Update the out parameter if we successfully extracted the thing name. */ + * pThingName = & ( pTopic[ consumedTopicLength ] ); + + consumedTopicLength += * pThingNameLength; + + /* If no more topic string is left to parse, fail. */ + if( consumedTopicLength >= topicLength ) + { + shadowStatus = SHADOW_SHADOW_MESSAGE_TYPE_PARSE_FAILED; + LogDebug( ( "Not related to Shadow, shadow message type is not in pTopic %s, failed to parse shadow message type", pTopic ) ); + } + } + else + { + LogDebug( ( "Not related to Shadow, failed to parse thing name in pTopic %s", pTopic ) ); + } + } + + if( shadowStatus == SHADOW_SUCCESS ) + { + /* Extract shadow message type. */ + shadowStatus = extractShadowMessageType( & ( pTopic[ consumedTopicLength ] ), + topicLength - consumedTopicLength, + pMessageType ); + if( shadowStatus != SHADOW_SUCCESS ) + { + LogDebug( ( "Not related to Shadow, failed to match shadow message type in pTopic %s", pTopic ) ); + } + } + + return shadowStatus; +} +/*-----------------------------------------------------------*/ + +ShadowStatus_t Shadow_GetTopicString( ShadowTopicStringType_t topicType, + const char * pThingName, + uint8_t thingNameLength, + char * pTopicBuffer, + uint16_t bufferSize, + uint16_t * pOutLength ) +{ + uint16_t offset = 0U, generatedTopicStringLength = 0U, operationStringLength = 0U; + ShadowStatus_t shadowStatus = SHADOW_SUCCESS; + const char * pOperationString = NULL; + + if( ( pTopicBuffer == NULL ) || + ( pThingName == NULL ) || + ( thingNameLength == 0U ) || + ( topicType >= ShadowTopicStringTypeMaxNum ) || + ( pOutLength == NULL ) ) + { + shadowStatus = SHADOW_BAD_PARAMETER; + LogError( ( "Invalid input parameters pTopicBuffer: %p, pThingName: %p, thingNameLength: %u, topicType: %u, pOutLength: %p", + pTopicBuffer, + pThingName, + thingNameLength, + topicType, + pOutLength ) ); + } + else + { + generatedTopicStringLength = SHADOW_PREFIX_LENGTH + /* Prefix ("$aws/things/"). */ + thingNameLength + /* Thing name. */ + getShadowOperationLength( topicType ); /* Shadow operation. */ + + if( bufferSize < generatedTopicStringLength ) + { + shadowStatus = SHADOW_BUFFER_TOO_SMALL; + LogError( ( "Input bufferSize too small, bufferSize %d, required ", bufferSize, generatedTopicStringLength) ); + } + else + { + /* Copy the Shadow topic prefix into the topic buffer. */ + ( void ) memcpy( ( void * ) pTopicBuffer, + ( const void * ) SHADOW_PREFIX, + ( size_t ) SHADOW_PREFIX_LENGTH ); + offset = ( uint16_t ) ( offset + SHADOW_PREFIX_LENGTH ); + + /* Copy the Thing Name into the topic buffer. */ + ( void ) memcpy( ( void * ) & ( pTopicBuffer[ offset ] ), + ( const void * ) pThingName, + ( size_t ) thingNameLength ); + offset = ( uint16_t ) ( offset + thingNameLength ); + + pOperationString = getShadowOperationString( topicType ); + operationStringLength = getShadowOperationLength( topicType ); + /* Copy the Shadow operation string into the topic buffer. */ + ( void ) memcpy( ( void * ) & ( pTopicBuffer[ offset ] ), + ( const void * ) pOperationString, + ( size_t ) operationStringLength ); + + /* Return the generated topic string length to the caller. */ + * pOutLength = generatedTopicStringLength; + } + } + + return shadowStatus; +} +/*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/utest/CMakeLists.txt b/libraries/aws/shadow/utest/CMakeLists.txt new file mode 100644 index 0000000000..2fc5100192 --- /dev/null +++ b/libraries/aws/shadow/utest/CMakeLists.txt @@ -0,0 +1,61 @@ +include("../shadowFilePaths.cmake") +project ("shadow unit test") +cmake_minimum_required (VERSION 3.13) + +# ==================== Define your project name (edit) ======================== +set(project_name "shadow") + +# ===================== Create your mock here (edit) ======================== +# ================= Create the library under test here (edit) ================== + +# list the files you would like to test here +list(APPEND real_source_files + ${SHADOW_SOURCES} + ) +# list the directories the module under test includes +list(APPEND real_include_directories + . + ${SHADOW_INCLUDE_PUBLIC_DIRS} + ${SHADOW_INCLUDE_PRIVATE_DIRS} + ${LOGGING_INCLUDE_DIRS} + ) + +# ===================== Create UnitTest Code here (edit) ===================== + +# list the directories your test needs to include +list(APPEND test_include_directories + . + ${SHADOW_INCLUDE_PUBLIC_DIRS} + ${SHADOW_INCLUDE_PRIVATE_DIRS} + ${LOGGING_INCLUDE_DIRS} + ) + +# ============================= (end edit) =================================== + +set(real_name "${project_name}_real") + +create_real_library(${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" + ) + +list(APPEND utest_link_list + lib${real_name}.a + ) + +list(APPEND utest_dep_list + ${real_name} + ) + +set(utest_name "${project_name}_utest") +set(utest_source "${project_name}_utest.c") +create_test(${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" + ) + + + diff --git a/libraries/aws/shadow/utest/shadow_config.h b/libraries/aws/shadow/utest/shadow_config.h new file mode 100644 index 0000000000..1df772438a --- /dev/null +++ b/libraries/aws/shadow/utest/shadow_config.h @@ -0,0 +1,25 @@ +#ifndef SHADOW_CONFIG_H__ +#define SHADOW_CONFIG_H__ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Configure name and log level for the Shadow library. */ +#define LIBRARY_LOG_NAME "SHADOW" +#define LIBRARY_LOG_LEVEL LOG_NONE + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef SHADOW_CONFIG_H__ */ diff --git a/libraries/aws/shadow/utest/shadow_utest.c b/libraries/aws/shadow/utest/shadow_utest.c new file mode 100644 index 0000000000..41ec4645b5 --- /dev/null +++ b/libraries/aws/shadow/utest/shadow_utest.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/** + * @file shadow_utest.c + * @brief Tests for the user-facing API functions (declared in shadow.h). + */ + +/* Standard includes. */ +#include +#include + +/* Test framework includes. */ +#include "unity.h" + +/* Shadow include. */ +#include "shadow.h" + + +/*-----------------------------------------------------------*/ + +/** + * @brief The Thing Name shared among all the tests. + */ +#define TEST_THING_NAME "TestThingName" + +/** + * @brief The length of #TEST_THING_NAME. + */ +#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) + +/** + * @brief The shadow topic string "update" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_UPDATE "$aws/things/TestThingName/shadow/update" + +/** + * @brief The shadow topic string "update/accepted" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_UPDATE_ACCEPTED "$aws/things/TestThingName/shadow/update/accepted" + +/** + * @brief The shadow topic string "update/rejected" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_UPDATE_REJECTED "$aws/things/TestThingName/shadow/update/rejected" + +/** + * @brief The shadow topic string "update/documents" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_UPDATE_DOCUMENTS "$aws/things/TestThingName/shadow/update/documents" + +/** + * @brief The shadow topic string "update/delta" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_UPDATE_DELTA "$aws/things/TestThingName/shadow/update/delta" + +/** + * @brief The shadow topic string "get" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_GET "$aws/things/TestThingName/shadow/get" + +/** + * @brief The shadow topic string "get/accepted" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_GET_ACCEPTED "$aws/things/TestThingName/shadow/get/accepted" + +/** + * @brief The shadow topic string "get/rejected" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_GET_REJECTED "$aws/things/TestThingName/shadow/get/rejected" + +/** + * @brief The shadow topic string "delete" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_DELETE "$aws/things/TestThingName/shadow/delete" + +/** + * @brief The shadow topic string "delete/accepted" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_DELETE_ACCEPTED "$aws/things/TestThingName/shadow/delete/accepted" + +/** + * @brief The shadow topic string "delete/rejected" shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_STRING_DELETE_REJECTED "$aws/things/TestThingName/shadow/delete/rejected" + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_UPDATE shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_UPDATE ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_UPDATE ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_UPDATE_ACCEPTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_UPDATE_ACCEPTED ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_UPDATE_REJECTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_UPDATE_REJECTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_UPDATE_REJECTED ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_UPDATE_DOCUMENTS shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_UPDATE_DOCUMENTS ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_UPDATE_DOCUMENTS ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_UPDATE_DELTA shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_UPDATE_DELTA ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_UPDATE_DELTA ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_GET shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_GET ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_GET ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_GET_ACCEPTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_GET_ACCEPTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_GET_ACCEPTED ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_GET_REJECTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_GET_REJECTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_GET_REJECTED ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_DELETE shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_DELETE ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_DELETE ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_DELETE_ACCEPTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_DELETE_ACCEPTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_DELETE_ACCEPTED ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_DELETE_REJECTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_DELETE_REJECTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_DELETE_REJECTED ) - 1U ) + +/** + * @brief A topic string with an empty thing name. + */ +#define TEST_SHADOW_TOPIC_STRING_EMPTY_THINGNAME "$aws/things/" + +/** + * @brief A topic string with an empty shadow message type. + */ +#define TEST_SHADOW_TOPIC_STRING_EMPTY_SHADOW_MESSAGE_TYPE "$aws/things/TestThingName" + +/** + * @brief A topic string that is not related to Shadow. + */ +#define TEST_SHADOW_TOPIC_STRING_INVALID_SHADOW_RESPONSE "$aws/things/TestThingName/shadow/invalid/invalid" + +/** + * @brief A topic string that is not related to Shadow. + */ +#define TEST_SHADOW_TOPIC_STRING_INVALID_GET_REJECTED "$aws/things/TestThingName/shadow/get/rejected/gibberish" + +/** + * @brief A topic string that is not related to Shadow. + */ +#define TEST_SHADOW_TOPIC_STRING_INVALID_PREFIX "$aws/jobs/TestThingName/shadow/get/rejected" + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_EMPTY_THINGNAME shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_EMPTY_THINGNAME ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_EMPTY_THINGNAME ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_EMPTY_SHADOW_MESSAGE_TYPE shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_EMPTY_SHADOW_MESSAGE_TYPE ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_EMPTY_SHADOW_MESSAGE_TYPE ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_INVALID_SHADOW_RESPONSE shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_INVALID_SHADOW_RESPONSE ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_INVALID_SHADOW_RESPONSE ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_INVALID_GET_REJECTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_INVALID_GET_REJECTED ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_INVALID_GET_REJECTED ) - 1U ) + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_INVALID_PREFIX shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_LENGTH_INVALID_PREFIX ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_STRING_INVALID_PREFIX ) - 1U ) + +/** + * @brief The init value for a topic buffer. + */ +#define TEST_SHADOW_TOPIC_BUFFER_INITIALZIE "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789abcdefghijklmno" + +/** + * @brief The init value for a topic buffer. + */ +#define TEST_SHADOW_TOPIC_BUFFER_MODIFIED "$aws/things/TestThingName/shadow/get/acceptedklmno" + +/** + * @brief The length of #TEST_SHADOW_TOPIC_STRING_DELETE_REJECTED shared among all test cases. + */ +#define TEST_SHADOW_TOPIC_BUFFER_LENGTH ( ( uint16_t ) sizeof( TEST_SHADOW_TOPIC_BUFFER_INITIALZIE ) - 1U ) + +/*-----------------------------------------------------------*/ + +/* ============================ UNITY FIXTURES ============================ */ + +/* Called before each test method. */ +void setUp() +{ +} + +/* Called after each test method. */ +void tearDown() +{ +} + +/* Called at the beginning of the whole suite. */ +void suiteSetUp() +{ +} + +/* Called at the end of the whole suite. */ +int suiteTearDown( int numFailures ) +{ + return numFailures; +} +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the macros generates the expected strings. + */ +void test_Shadow_MacrosString( void ) +{ + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_UPDATE, SHADOW_TOPIC_STRING_UPDATE( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_UPDATE_ACCEPTED, SHADOW_TOPIC_STRING_UPDATE_ACCEPTED( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_UPDATE_REJECTED, SHADOW_TOPIC_STRING_UPDATE_REJECTED( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_UPDATE_DOCUMENTS, SHADOW_TOPIC_STRING_UPDATE_DOCUMENTS( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_UPDATE_DELTA, SHADOW_TOPIC_STRING_UPDATE_DELTA( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_GET, SHADOW_TOPIC_STRING_GET( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_GET_ACCEPTED, SHADOW_TOPIC_STRING_GET_ACCEPTED( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_GET_REJECTED, SHADOW_TOPIC_STRING_GET_REJECTED( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_DELETE, SHADOW_TOPIC_STRING_DELETE( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_DELETE_ACCEPTED, SHADOW_TOPIC_STRING_DELETE_ACCEPTED( TEST_THING_NAME ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_STRING_DELETE_REJECTED, SHADOW_TOPIC_STRING_DELETE_REJECTED( TEST_THING_NAME ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the macros generates the expected strings length. + */ +void test_Shadow_MacrosLength( void ) +{ + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_UPDATE, SHADOW_TOPIC_LENGTH_UPDATE( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED, SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_UPDATE_REJECTED, SHADOW_TOPIC_LENGTH_UPDATE_REJECTED( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_UPDATE_DOCUMENTS, SHADOW_TOPIC_LENGTH_UPDATE_DOCUMENTS( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_UPDATE_DELTA, SHADOW_TOPIC_LENGTH_UPDATE_DELTA( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_GET, SHADOW_TOPIC_LENGTH_GET( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_GET_ACCEPTED, SHADOW_TOPIC_LENGTH_GET_ACCEPTED( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_GET_REJECTED, SHADOW_TOPIC_LENGTH_GET_REJECTED( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_DELETE, SHADOW_TOPIC_LENGTH_DELETE( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_DELETE_ACCEPTED, SHADOW_TOPIC_LENGTH_DELETE_ACCEPTED( TEST_THING_NAME_LENGTH ) ); + TEST_ASSERT_EQUAL( TEST_SHADOW_TOPIC_LENGTH_DELETE_REJECTED, SHADOW_TOPIC_LENGTH_DELETE_REJECTED( TEST_THING_NAME_LENGTH ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Shadow_GetTopicString() with valid parameters. + */ +void test_Shadow_GetTopicString_Happy_Path( void ) +{ + ShadowStatus_t shadowStatus = SHADOW_SUCCESS; + uint16_t outLength = 0; + ShadowTopicStringType_t topicType = ShadowTopicStringTypeGetAccepted; + char topicBuffer[ TEST_SHADOW_TOPIC_BUFFER_LENGTH ] = TEST_SHADOW_TOPIC_BUFFER_INITIALZIE; + uint16_t bufferSize = TEST_SHADOW_TOPIC_BUFFER_LENGTH; + char topicBufferGetAccepted[ SHADOW_TOPIC_LENGTH_GET_ACCEPTED( TEST_THING_NAME_LENGTH ) ] = { 0 }; + uint16_t bufferSizeGetAccepted = TEST_SHADOW_TOPIC_LENGTH_GET_ACCEPTED; + + /* Call Shadow_GetTopicString() with valid parameters but bufferSize > topic string length + * and verify result. */ + shadowStatus = Shadow_GetTopicString( topicType, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + & ( topicBuffer[ 0 ] ), + bufferSize, + & outLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_SUCCESS, shadowStatus ); + TEST_ASSERT_EQUAL_INT( TEST_SHADOW_TOPIC_LENGTH_GET_ACCEPTED, outLength ); + TEST_ASSERT_LESS_THAN( bufferSize, outLength ); + TEST_ASSERT_EQUAL_STRING_LEN( TEST_SHADOW_TOPIC_BUFFER_MODIFIED, + topicBuffer, + bufferSize ); + + /* Call Shadow_GetTopicString() with valid parameters and verify result. */ + shadowStatus = Shadow_GetTopicString( topicType, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + & ( topicBufferGetAccepted[ 0 ] ), + bufferSizeGetAccepted, + & outLength ); + TEST_ASSERT_EQUAL_INT( TEST_SHADOW_TOPIC_LENGTH_GET_ACCEPTED, outLength ); + TEST_ASSERT_EQUAL_STRING_LEN( TEST_SHADOW_TOPIC_STRING_GET_ACCEPTED, + topicBufferGetAccepted, + bufferSizeGetAccepted ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Shadow_GetTopicString() with invalid parameters. + */ +void test_Shadow_GetTopicString_Invalid_Parameters( void ) +{ + ShadowStatus_t shadowStatus = SHADOW_SUCCESS; + uint16_t outLength = 0; + ShadowTopicStringType_t topicType = ShadowTopicStringTypeGetAccepted; + char topicBuffer[ TEST_SHADOW_TOPIC_BUFFER_LENGTH ] = TEST_SHADOW_TOPIC_BUFFER_INITIALZIE; + uint16_t bufferSize = TEST_SHADOW_TOPIC_BUFFER_LENGTH; + + /* Call Shadow_GetTopicString() with various combinations of + * incorrect parameters. */ + shadowStatus = Shadow_GetTopicString( 0, + "", + 0, + & ( topicBuffer[ 0 ] ), + bufferSize, + & outLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_GetTopicString( topicType, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + NULL, + 0, + & outLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_GetTopicString( ShadowTopicStringTypeMaxNum + 1, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + & ( topicBuffer[ 0 ] ), + bufferSize, + & outLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_GetTopicString( topicType, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + & ( topicBuffer[ 0 ] ), + bufferSize, + NULL ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_GetTopicString( topicType, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + & ( topicBuffer[ 0 ] ), + 0, + & outLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BUFFER_TOO_SMALL, shadowStatus ); + + shadowStatus = Shadow_GetTopicString( topicType, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + & ( topicBuffer[ 0 ] ), + ( bufferSize / 2 ), + & outLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BUFFER_TOO_SMALL, shadowStatus ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Shadow_MatchTopic() with valid parameters. + */ +void test_Shadow_MatchTopic_Happy_Path( void ) +{ + ShadowStatus_t shadowStatus = SHADOW_SUCCESS; + ShadowMessageType_t messageType = ShadowMessageTypeMaxNum; + const char * pThingName = NULL; + uint16_t thingNameLength = 0; + const char topicBuffer[ TEST_SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED ] = TEST_SHADOW_TOPIC_STRING_UPDATE_ACCEPTED; + uint16_t bufferSize = TEST_SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED; + + /* Call Shadow_MatchTopic() with valid parameters and verify result. */ + shadowStatus = Shadow_MatchTopic( & ( topicBuffer[ 0 ] ), + bufferSize, + & messageType, + & pThingName, + & thingNameLength ); + + TEST_ASSERT_EQUAL_INT( SHADOW_SUCCESS, shadowStatus ); + TEST_ASSERT_EQUAL_INT( TEST_THING_NAME_LENGTH, thingNameLength ); + TEST_ASSERT_EQUAL_INT( ShadowMessageTypeUpdateAccepted, messageType ); + TEST_ASSERT_EQUAL_STRING_LEN( TEST_THING_NAME, pThingName, TEST_THING_NAME_LENGTH ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Shadow_MatchTopic() with various + * invalid parameters. + */ +void test_Shadow_MatchTopic_Invalid_Parameters( void ) +{ + ShadowStatus_t shadowStatus = SHADOW_SUCCESS; + ShadowMessageType_t messageType = ShadowMessageTypeMaxNum; + const char * pThingName = NULL; + uint16_t thingNameLength = 0; + const char topicBuffer[ TEST_SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED ] = TEST_SHADOW_TOPIC_STRING_UPDATE_ACCEPTED; + uint16_t bufferSize = TEST_SHADOW_TOPIC_LENGTH_UPDATE_ACCEPTED; + + /* Call Shadow_MatchTopic() with various combinations of + * incorrect parameters. */ + shadowStatus = Shadow_MatchTopic( NULL, + bufferSize, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( & ( topicBuffer[ 0 ] ), + 0, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( & ( topicBuffer[ 0 ] ), + bufferSize, + NULL, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( & ( topicBuffer[ 0 ] ), + bufferSize, + & messageType, + & pThingName, + NULL ); + TEST_ASSERT_EQUAL_INT( SHADOW_BAD_PARAMETER, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( TEST_SHADOW_TOPIC_STRING_INVALID_PREFIX, + TEST_SHADOW_TOPIC_LENGTH_INVALID_PREFIX, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_FAIL, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( TEST_SHADOW_TOPIC_STRING_EMPTY_THINGNAME, + TEST_SHADOW_TOPIC_LENGTH_EMPTY_THINGNAME, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_THINGNAME_PARSE_FAILED, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( TEST_SHADOW_TOPIC_STRING_EMPTY_SHADOW_MESSAGE_TYPE, + TEST_SHADOW_TOPIC_LENGTH_EMPTY_SHADOW_MESSAGE_TYPE, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_SHADOW_MESSAGE_TYPE_PARSE_FAILED, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( TEST_SHADOW_TOPIC_STRING_INVALID_SHADOW_RESPONSE, + TEST_SHADOW_TOPIC_LENGTH_INVALID_SHADOW_RESPONSE, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_FAIL, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( TEST_SHADOW_TOPIC_STRING_INVALID_GET_REJECTED, + TEST_SHADOW_TOPIC_LENGTH_INVALID_GET_REJECTED, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_FAIL, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( & ( topicBuffer[ 0 ] ), + bufferSize / 2, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_SHADOW_MESSAGE_TYPE_PARSE_FAILED, shadowStatus ); + + shadowStatus = Shadow_MatchTopic( & ( topicBuffer[ 0 ] ), + bufferSize * 2, + & messageType, + & pThingName, + & thingNameLength ); + TEST_ASSERT_EQUAL_INT( SHADOW_FAIL, shadowStatus ); +} + +/*-----------------------------------------------------------*/ From edf5af8c566239f8185367809da6ed835c8c8e20 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 6 Aug 2020 23:30:40 -0700 Subject: [PATCH 612/844] Use MQTT_PublishToResend() in the basic_tls and mutual_auth demos (#1101) MQTT_PublishToResend() is now used in the mqtt_basic_tls_demo and the mqtt_mutual_auth_demo because it preserves the ordering of MQTT publish messages. These demos use a Qos > 0. Updated the README.md in tools/spell for clarity on how to use the scripts. --- demos/lexicon.txt | 2 + .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 80 +++++++++++++------ .../mqtt_demo_mutual_auth.c | 80 +++++++++++++------ tools/spell/README.md | 15 +++- 4 files changed, 125 insertions(+), 52 deletions(-) diff --git a/demos/lexicon.txt b/demos/lexicon.txt index 6abf836eef..b2c4fdb548 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -31,6 +31,7 @@ developerguide doesn dup endif +hashmap hasn html http @@ -53,6 +54,7 @@ org outgoingpublishpackets packetid packetidentifier +packetidtoresend param pathlen pbuffer diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index a895e23b2b..395f260914 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -44,8 +44,9 @@ /* Include Demo Config as the first non-system header. */ #include "demo_config.h" -/* MQTT API header. */ +/* MQTT API headers. */ #include "mqtt.h" +#include "mqtt_state.h" /* OpenSSL sockets transport implementation. */ #include "openssl_posix.h" @@ -497,39 +498,68 @@ static int handlePublishResend( MQTTContext_t * pMqttContext ) int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus = MQTTSuccess; uint8_t index = 0U; + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + uint16_t packetIdToResend = MQTT_PACKET_ID_INVALID; + bool foundPacketId = false; + assert( pMqttContext != NULL ); assert( outgoingPublishPackets != NULL ); - /* Resend all the QoS2 publishes still in the array. These are the - * publishes that hasn't received a PUBREC. When a PUBREC is - * received, the publish is removed from the array. */ - for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) + /* MQTT_PublishToResend() provides a packet ID of the next PUBLISH packet + * that should be resent. In accordance with the MQTT v3.1.1 spec, + * MQTT_PublishToResend() preserves the ordering of when the original + * PUBLISH packets were sent. The outgoingPublishPackets array is searched + * through for the associated packet ID. If the application requires + * increased efficiency in the look up of the packet ID, then a hashmap of + * packetId key and PublishPacket_t values may be used instead. */ + packetIdToResend = MQTT_PublishToResend( pMqttContext, &cursor ); + + while( packetIdToResend != MQTT_PACKET_ID_INVALID ) { - if( outgoingPublishPackets[ index ].packetId != MQTT_PACKET_ID_INVALID ) + foundPacketId = false; + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) { - outgoingPublishPackets[ index ].pubInfo.dup = true; - - LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", - outgoingPublishPackets[ index ].packetId ) ); - mqttStatus = MQTT_Publish( pMqttContext, - &outgoingPublishPackets[ index ].pubInfo, - outgoingPublishPackets[ index ].packetId ); - - if( mqttStatus != MQTTSuccess ) + if( outgoingPublishPackets[ index ].packetId == packetIdToResend ) { - LogError( ( "Sending duplicate PUBLISH for packet id %u " - " failed with status %u.", - outgoingPublishPackets[ index ].packetId, - mqttStatus ) ); - returnStatus = EXIT_FAILURE; - break; - } - else - { - LogInfo( ( "Sent duplicate PUBLISH successfully for packet id %u.\n\n", + foundPacketId = true; + outgoingPublishPackets[ index ].pubInfo.dup = true; + + LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", outgoingPublishPackets[ index ].packetId ) ); + mqttStatus = MQTT_Publish( pMqttContext, + &outgoingPublishPackets[ index ].pubInfo, + outgoingPublishPackets[ index ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending duplicate PUBLISH for packet id %u " + " failed with status %u.", + outgoingPublishPackets[ index ].packetId, + mqttStatus ) ); + returnStatus = EXIT_FAILURE; + break; + } + else + { + LogInfo( ( "Sent duplicate PUBLISH successfully for packet id %u.\n\n", + outgoingPublishPackets[ index ].packetId ) ); + } } } + + if( foundPacketId == false ) + { + LogError( ( "Packet id %u requires resend, but was not found in " + "outgoingPublishPackets.", + packetIdToResend ) ); + returnStatus = EXIT_FAILURE; + break; + } + else + { + /* Get the next packetID to be resent. */ + packetIdToResend = MQTT_PublishToResend( pMqttContext, &cursor ); + } } return returnStatus; diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index d827148cd6..9892f17cbf 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -49,8 +49,9 @@ /* Include Demo Config as the first non-system header. */ #include "demo_config.h" -/* MQTT API header. */ +/* MQTT API headers. */ #include "mqtt.h" +#include "mqtt_state.h" /* OpenSSL sockets transport implementation. */ #include "openssl_posix.h" @@ -544,39 +545,68 @@ static int handlePublishResend( MQTTContext_t * pMqttContext ) int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus = MQTTSuccess; uint8_t index = 0U; + MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + uint16_t packetIdToResend = MQTT_PACKET_ID_INVALID; + bool foundPacketId = false; + assert( pMqttContext != NULL ); assert( outgoingPublishPackets != NULL ); - /* Resend all the QoS1 publishes still in the array. These are the - * publishes that hasn't received a PUBACK. When a PUBACK is - * received, the publish is removed from the array. */ - for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) + /* MQTT_PublishToResend() provides a packet ID of the next PUBLISH packet + * that should be resent. In accordance with the MQTT v3.1.1 spec, + * MQTT_PublishToResend() preserves the ordering of when the original + * PUBLISH packets were sent. The outgoingPublishPackets array is searched + * through for the associated packet ID. If the application requires + * increased efficiency in the look up of the packet ID, then a hashmap of + * packetId key and PublishPacket_t values may be used instead. */ + packetIdToResend = MQTT_PublishToResend( pMqttContext, &cursor ); + + while( packetIdToResend != MQTT_PACKET_ID_INVALID ) { - if( outgoingPublishPackets[ index ].packetId != MQTT_PACKET_ID_INVALID ) + foundPacketId = false; + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) { - outgoingPublishPackets[ index ].pubInfo.dup = true; - - LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", - outgoingPublishPackets[ index ].packetId ) ); - mqttStatus = MQTT_Publish( pMqttContext, - &outgoingPublishPackets[ index ].pubInfo, - outgoingPublishPackets[ index ].packetId ); - - if( mqttStatus != MQTTSuccess ) + if( outgoingPublishPackets[ index ].packetId == packetIdToResend ) { - LogError( ( "Sending duplicate PUBLISH for packet id %u " - " failed with status %u.", - outgoingPublishPackets[ index ].packetId, - mqttStatus ) ); - returnStatus = EXIT_FAILURE; - break; - } - else - { - LogInfo( ( "Sent duplicate PUBLISH successfully for packet id %u.\n\n", + foundPacketId = true; + outgoingPublishPackets[ index ].pubInfo.dup = true; + + LogInfo( ( "Sending duplicate PUBLISH with packet id %u.", outgoingPublishPackets[ index ].packetId ) ); + mqttStatus = MQTT_Publish( pMqttContext, + &outgoingPublishPackets[ index ].pubInfo, + outgoingPublishPackets[ index ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending duplicate PUBLISH for packet id %u " + " failed with status %u.", + outgoingPublishPackets[ index ].packetId, + mqttStatus ) ); + returnStatus = EXIT_FAILURE; + break; + } + else + { + LogInfo( ( "Sent duplicate PUBLISH successfully for packet id %u.\n\n", + outgoingPublishPackets[ index ].packetId ) ); + } } } + + if( foundPacketId == false ) + { + LogError( ( "Packet id %u requires resend, but was not found in " + "outgoingPublishPackets.", + packetIdToResend ) ); + returnStatus = EXIT_FAILURE; + break; + } + else + { + /* Get the next packetID to be resent. */ + packetIdToResend = MQTT_PublishToResend( pMqttContext, &cursor ); + } } return returnStatus; diff --git a/tools/spell/README.md b/tools/spell/README.md index 5f5fbec50b..66379e28f5 100644 --- a/tools/spell/README.md +++ b/tools/spell/README.md @@ -1,4 +1,4 @@ -# How to create a lexicon.txt for a new library. +# Pre-requisites to running the spell check scripts 1. In your GNU environment, install the *spell* and *getopt* programs. Use the following commands in Debian distributions, to install the packages (*getopt* is part of the `util-linux` package): ```shell @@ -11,9 +11,20 @@ export PATH=/tools/spell:$PATH ``` +# How to create a lexicon.txt for a new library. + 1. Ensure there does not exist a file called "lexicon.txt" in your library's directory. Run the following command to create a lexicon.txt for your library: ```shell find-unknown-comment-words -d /libraries// > /libraries///lexicon.txt ``` -1. Check the contents of */libraries///lexicon.txt* for any misspelled words. Fix them in your library's source code and delete them from the lexicon.txt. \ No newline at end of file +1. Check the contents of */libraries///lexicon.txt* for any misspelled words. Fix them in your library's source code and delete them from the lexicon.txt. + +# How to run for changes to an existing library. + +1. If there exists a lexicon.txt in the library's directory, run the following command: + ```shell + find-unknown-comment-words -d /libraries// + ``` + +1. Add any non-dictionary correctly spelled words to */libraries///lexicon.txt*. Fix any misspelled words in your code comment change. \ No newline at end of file From 47cc16870399c6cfe2abf70457d6ee7f63230d1b Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 7 Aug 2020 10:33:37 -0700 Subject: [PATCH 613/844] Improve format specifiers in logs (#1097) * Remove C99 %hhu format specifier * Add unsigned long to %lu format specifiers * Log stringified status codes instead of integers --- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 37 +++++----- .../mqtt_demo_lightweight.c | 2 +- .../mqtt_demo_mutual_auth.c | 37 +++++----- .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 41 ++++++----- libraries/standard/mqtt/src/mqtt.c | 37 +++++----- .../standard/mqtt/src/mqtt_lightweight.c | 69 +++++++++---------- 6 files changed, 112 insertions(+), 111 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 395f260914..314a2e0d8d 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -533,9 +533,9 @@ static int handlePublishResend( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { LogError( ( "Sending duplicate PUBLISH for packet id %u " - " failed with status %u.", + " failed with status %s.", outgoingPublishPackets[ index ].packetId, - mqttStatus ) ); + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; break; } @@ -716,7 +716,7 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); + LogError( ( "MQTT init failed: Status=%s.", MQTT_Status_strerror( mqttStatus ) ) ); } else { @@ -754,7 +754,8 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + LogError( ( "Connection with MQTT broker failed with status %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } else { @@ -779,8 +780,8 @@ static int disconnectMqttSession( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", - mqttStatus ) ); + LogError( ( "Sending MQTT DISCONNECT failed with status=%s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } @@ -816,8 +817,8 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -860,8 +861,8 @@ static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -913,8 +914,8 @@ static int publishToTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send PUBLISH packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); cleanupOutgoingPublishAt( publishIndex ); returnStatus = EXIT_FAILURE; } @@ -1010,8 +1011,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } } @@ -1036,8 +1037,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { - LogWarn( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogWarn( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } LogInfo( ( "Delay before continuing to next iteration.\n\n" ) ); @@ -1064,8 +1065,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } } diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index d4f9d76bd0..cffd236aaa 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -532,7 +532,7 @@ static void mqttPublishToTopic( NetworkContext_t * pNetworkContext, pFixedBuffer, &headerSize ); LogDebug( ( "Serialized PUBLISH header size is %lu.", - headerSize ) ); + ( unsigned long ) headerSize ) ); assert( result == MQTTSuccess ); /* Send Publish header to the broker. */ status = Plaintext_Send( pNetworkContext, ( void * ) pFixedBuffer->pBuffer, headerSize ); diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 9892f17cbf..827a1fddf7 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -580,9 +580,9 @@ static int handlePublishResend( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { LogError( ( "Sending duplicate PUBLISH for packet id %u " - " failed with status %u.", + " failed with status %s.", outgoingPublishPackets[ index ].packetId, - mqttStatus ) ); + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; break; } @@ -745,7 +745,7 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); + LogError( ( "MQTT init failed: Status=%s.", MQTT_Status_strerror( mqttStatus ) ) ); } else { @@ -783,7 +783,8 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + LogError( ( "Connection with MQTT broker failed with status %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } else { @@ -808,8 +809,8 @@ static int disconnectMqttSession( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", - mqttStatus ) ); + LogError( ( "Sending MQTT DISCONNECT failed with status=%s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } @@ -845,8 +846,8 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -889,8 +890,8 @@ static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -942,8 +943,8 @@ static int publishToTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send PUBLISH packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); cleanupOutgoingPublishAt( publishIndex ); returnStatus = EXIT_FAILURE; } @@ -1039,8 +1040,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } } @@ -1065,8 +1066,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { - LogWarn( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogWarn( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } LogInfo( ( "Delay before continuing to next iteration.\n\n" ) ); @@ -1093,8 +1094,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } } diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 5dadfd9ae8..1e8ea6ed4f 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -452,7 +452,7 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT init failed with status %u.", mqttStatus ) ); + LogError( ( "MQTT init failed: Status=%s.", MQTT_Status_strerror( mqttStatus ) ) ); } else { @@ -490,7 +490,8 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + LogError( ( "Connection with MQTT broker failed with status %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } else { @@ -515,8 +516,8 @@ static int disconnectMqttSession( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", - mqttStatus ) ); + LogError( ( "Sending MQTT DISCONNECT failed with status=%s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } @@ -552,8 +553,8 @@ static int subscribeToTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -596,8 +597,8 @@ static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %u.", - mqttStatus ) ); + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -615,7 +616,7 @@ static int unsubscribeFromTopic( MQTTContext_t * pMqttContext ) static int publishToTopic( MQTTContext_t * pMqttContext ) { int returnStatus = EXIT_SUCCESS; - MQTTStatus_t mqttSuccess = MQTTSuccess; + MQTTStatus_t mqttStatus = MQTTSuccess; MQTTPublishInfo_t publishInfo; assert( pMqttContext != NULL ); @@ -632,14 +633,12 @@ static int publishToTopic( MQTTContext_t * pMqttContext ) /* Send PUBLISH packet. Packet Id is not used for a QoS0 publish. * Hence 0 is passed as packet id. */ - mqttSuccess = MQTT_Publish( pMqttContext, - &publishInfo, - 0U ); + mqttStatus = MQTT_Publish( pMqttContext, &publishInfo, 0U ); - if( returnStatus != MQTTSuccess ) + if( mqttStatus != MQTTSuccess ) { - LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", - mqttSuccess ) ); + LogError( ( "Failed to send PUBLISH packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); returnStatus = EXIT_FAILURE; } else @@ -708,8 +707,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } } @@ -734,8 +733,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { - LogWarn( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogWarn( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } LogInfo( ( "Delay before continuing to next iteration.\n\n" ) ); @@ -762,8 +761,8 @@ static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) if( mqttStatus != MQTTSuccess ) { returnStatus = EXIT_FAILURE; - LogError( ( "MQTT_ProcessLoop returned with status = %u.", - mqttStatus ) ); + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); } } diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 0bd629caa7..b9e349c8e5 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -350,7 +350,7 @@ static int32_t sendPacket( MQTTContext_t * pContext, LogDebug( ( "BytesSent=%d, BytesRemaining=%lu," " TotalBytesSent=%d.", bytesSent, - bytesRemaining, + ( unsigned long ) bytesRemaining, totalBytesSent ) ); } } @@ -461,7 +461,7 @@ static int32_t recvExact( const MQTTContext_t * pContext, LogDebug( ( "BytesReceived=%d, BytesRemaining=%lu, " "TotalBytesReceived=%d.", bytesRecvd, - bytesRemaining, + ( unsigned long ) bytesRemaining, totalBytesRecvd ) ); } @@ -513,7 +513,7 @@ static MQTTStatus_t discardPacket( const MQTTContext_t * pContext, LogError( ( "Receive error while discarding packet." "ReceivedBytes=%d, ExpectedBytes=%lu.", bytesReceived, - bytesToReceive ) ); + ( unsigned long ) bytesToReceive ) ); receiveError = true; } else @@ -564,8 +564,8 @@ static MQTTStatus_t receivePacket( const MQTTContext_t * pContext, LogError( ( "Incoming packet will be dumped: " "Packet length exceeds network buffer size." "PacketSize=%lu, NetworkBufferSize=%lu.", - incomingPacket.remainingLength, - pContext->networkBuffer.size ) ); + ( unsigned long ) incomingPacket.remainingLength, + ( unsigned long ) pContext->networkBuffer.size ) ); status = discardPacket( pContext, incomingPacket.remainingLength, remainingTimeMs ); @@ -586,7 +586,7 @@ static MQTTStatus_t receivePacket( const MQTTContext_t * pContext, LogError( ( "Packet reception failed. ReceivedBytes=%d, " "ExpectedBytes=%lu.", bytesReceived, - bytesToReceive ) ); + ( unsigned long ) bytesToReceive ) ); status = MQTTRecvFailed; } } @@ -737,7 +737,8 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, assert( pContext->appCallback != NULL ); status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo ); - LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%d.", status ) ); + LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%s.", + MQTT_Status_strerror( status ) ) ); if( status == MQTTSuccess ) { @@ -1285,8 +1286,8 @@ static MQTTStatus_t serializePublish( const MQTTContext_t * pContext, &remainingLength, &packetSize ); LogDebug( ( "PUBLISH packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ) ); + ( unsigned long ) packetSize, + ( unsigned long ) remainingLength ) ); if( status == MQTTSuccess ) { @@ -1296,7 +1297,7 @@ static MQTTStatus_t serializePublish( const MQTTContext_t * pContext, &( pContext->networkBuffer ), pHeaderSize ); LogDebug( ( "Serialized PUBLISH header size is %lu.", - *pHeaderSize ) ); + ( unsigned long ) *pHeaderSize ) ); } return status; @@ -1424,8 +1425,8 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, &remainingLength, &packetSize ); LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ) ); + ( unsigned long ) packetSize, + ( unsigned long ) remainingLength ) ); } if( status == MQTTSuccess ) @@ -1509,8 +1510,8 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, &remainingLength, &packetSize ); LogDebug( ( "SUBSCRIBE packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ) ); + ( unsigned long ) packetSize, + ( unsigned long ) remainingLength ) ); } if( status == MQTTSuccess ) @@ -1641,7 +1642,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ) if( status == MQTTSuccess ) { LogDebug( ( "MQTT PINGREQ packet size is %lu.", - packetSize ) ); + ( unsigned long ) packetSize ) ); } else { @@ -1704,8 +1705,8 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, &remainingLength, &packetSize ); LogDebug( ( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.", - packetSize, - remainingLength ) ); + ( unsigned long ) packetSize, + ( unsigned long ) remainingLength ) ); } if( status == MQTTSuccess ) @@ -1760,7 +1761,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ) /* Get MQTT DISCONNECT packet size. */ status = MQTT_GetDisconnectPacketSize( &packetSize ); LogDebug( ( "MQTT DISCONNECT packet size is %lu.", - packetSize ) ); + ( unsigned long ) packetSize ) ); } if( status == MQTTSuccess ) diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 6d3725226b..f8f7c2a4ce 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -322,8 +322,8 @@ static size_t remainingLengthEncodedSize( size_t length ) } LogDebug( ( "Encoded size for length %lu is %lu bytes.", - length, - encodedSize ) ); + ( unsigned long ) length, + ( unsigned long ) encodedSize ) ); return encodedSize; } @@ -433,8 +433,8 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, LogError( ( "PUBLISH payload length of %lu cannot exceed " "%lu so as not to exceed the maximum " "remaining length of MQTT 3.1.1 packet( %lu ).", - pPublishInfo->payloadLength, - payloadLimit, + ( unsigned long ) pPublishInfo->payloadLength, + ( unsigned long ) payloadLimit, MQTT_MAX_REMAINING_LENGTH ) ); status = false; } @@ -454,8 +454,8 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, LogError( ( "PUBLISH payload length of %lu cannot exceed " "%lu so as not to exceed the maximum " "remaining length of MQTT 3.1.1 packet( %lu ).", - pPublishInfo->payloadLength, - payloadLimit, + ( unsigned long ) pPublishInfo->payloadLength, + ( unsigned long ) payloadLimit, MQTT_MAX_REMAINING_LENGTH ) ); status = false; } @@ -471,8 +471,8 @@ static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, } LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ) ); + ( unsigned long ) *pRemainingLength, + ( unsigned long ) *pPacketSize ) ); return status; } @@ -557,7 +557,7 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, ( serializePayload == true ) ) { LogDebug( ( "Copying PUBLISH payload of length =%lu to buffer", - pPublishInfo->payloadLength ) ); + ( unsigned long ) pPublishInfo->payloadLength ) ); /* Typecast const void * typed payload buffer to const uint8_t *. * This is to use same type buffers in memcpy. */ @@ -678,7 +678,7 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, if( remainingLength < qos0Minimum ) { LogDebug( ( "QoS 0 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum ) ); + ( unsigned long ) qos0Minimum ) ); status = MQTTBadResponse; } @@ -691,7 +691,7 @@ static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, if( remainingLength < ( qos0Minimum + 2U ) ) { LogDebug( ( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.", - qos0Minimum + 2U ) ); + ( unsigned long ) ( qos0Minimum + 2U ) ) ); status = MQTTBadResponse; } @@ -843,8 +843,7 @@ static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack, /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ if( pRemainingData[ 1 ] > 5U ) { - LogError( ( "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ) ); + LogError( ( "CONNACK response %u is invalid.", pRemainingData[ 1 ] ) ); status = MQTTBadResponse; } @@ -907,7 +906,7 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * { LogError( ( "Subscription packet length of %lu exceeds" "the MQTT 3.1.1 maximum packet length of %lu.", - packetSize, + ( unsigned long ) packetSize, MQTT_MAX_REMAINING_LENGTH ) ); status = MQTTBadParameter; } @@ -925,8 +924,8 @@ static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * } LogDebug( ( "Subscription packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ) ); + ( unsigned long ) *pRemainingLength, + ( unsigned long ) *pPacketSize ) ); return status; } @@ -955,7 +954,7 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, case 0x01: case 0x02: - LogDebug( ( "Topic filter %lu accepted, max QoS %hhu.", + LogDebug( ( "Topic filter %lu accepted, max QoS %u.", ( unsigned long ) i, subscriptionStatus ) ); break; @@ -969,7 +968,7 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, break; default: - LogDebug( ( "Bad SUBSCRIBE status %hhu.", subscriptionStatus ) ); + LogDebug( ( "Bad SUBSCRIBE status %u.", subscriptionStatus ) ); status = MQTTBadResponse; @@ -1067,8 +1066,8 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized packet of size of %lu.", - pFixedBuffer->size, - packetSize ) ); + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) packetSize ) ); status = MQTTNoMemory; } else @@ -1164,7 +1163,7 @@ static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket pPublishInfo->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } - LogDebug( ( "Payload length %lu.", pPublishInfo->payloadLength ) ); + LogDebug( ( "Payload length %lu.", ( unsigned long ) pPublishInfo->payloadLength ) ); } return status; @@ -1337,7 +1336,7 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, } LogDebug( ( "Length of serialized CONNECT packet is %lu.", - ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); + ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); /* Ensure that the difference between the end and beginning of the buffer * is less than the buffer size. */ @@ -1383,7 +1382,7 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, LogError( ( "The Will Message length must not exceed %d. " "pWillInfo->payloadLength=%lu.", UINT16_MAX, - pWillInfo->payloadLength ) ); + ( unsigned long ) pWillInfo->payloadLength ) ); status = MQTTBadParameter; } else @@ -1437,8 +1436,8 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, *pPacketSize = connectPacketSize; LogDebug( ( "CONNECT packet remaining length=%lu and packet size=%lu.", - *pRemainingLength, - *pPacketSize ) ); + ( unsigned long ) *pRemainingLength, + ( unsigned long ) *pPacketSize ) ); } return status; @@ -1486,8 +1485,8 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized CONNECT packet of size of %lu.", - pFixedBuffer->size, - connectPacketSize ) ); + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) connectPacketSize ) ); status = MQTTNoMemory; } else @@ -1595,7 +1594,7 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL } LogDebug( ( "Length of serialized SUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); + ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); } return status; @@ -1691,7 +1690,7 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio } LogDebug( ( "Length of serialized UNSUBSCRIBE packet is %lu.", - ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); + ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) ); } return status; @@ -1801,8 +1800,8 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH packet of size of %lu.", - pFixedBuffer->size, - packetSize ) ); + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) packetSize ) ); status = MQTTNoMemory; } else @@ -1876,8 +1875,8 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH header packet of size of %lu.", - pFixedBuffer->size, - ( packetSize - pPublishInfo->payloadLength ) ) ); + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) ( packetSize - pPublishInfo->payloadLength ) ) ); status = MQTTNoMemory; } else @@ -1999,7 +1998,7 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized DISCONNECT packet of size of %lu.", - pFixedBuffer->size, + ( unsigned long ) pFixedBuffer->size, MQTT_DISCONNECT_PACKET_SIZE ) ); status = MQTTNoMemory; } @@ -2061,7 +2060,7 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PINGREQ packet of size of %u.", - pFixedBuffer->size, + ( unsigned long ) pFixedBuffer->size, MQTT_PACKET_PINGREQ_SIZE ) ); status = MQTTNoMemory; } From b760d7d6ddd2b50deab31b110138b1436b56217f Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 7 Aug 2020 10:53:27 -0700 Subject: [PATCH 614/844] Return suback when server refuses subscription (#1089) * Return suback when server refuses subscription * Add new deserialized struct parameter to callback * Continue process loop if server refused --- demos/lexicon.txt | 1 + .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 19 +++++------ .../mqtt_demo_mutual_auth.c | 19 +++++------ .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 19 +++++------ .../mqtt/cbmc/include/event_callback_stub.h | 6 ++-- .../mqtt/cbmc/stubs/event_callback_stub.c | 6 ++-- libraries/standard/mqtt/include/mqtt.h | 24 ++++++++++---- .../mqtt/integration-test/mqtt_system_test.c | 19 +++++------ libraries/standard/mqtt/lexicon.txt | 3 +- libraries/standard/mqtt/src/mqtt.c | 32 +++++++++++++++---- .../standard/mqtt/src/mqtt_lightweight.c | 2 +- libraries/standard/mqtt/utest/mqtt_utest.c | 19 +++++++---- 12 files changed, 104 insertions(+), 65 deletions(-) diff --git a/demos/lexicon.txt b/demos/lexicon.txt index b2c4fdb548..f68fd30d6c 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -58,6 +58,7 @@ packetidtoresend param pathlen pbuffer +pdeserializedinfo pem pfixedbuffer pincomingpacket diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 314a2e0d8d..2a8b7b353e 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -248,14 +248,11 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, * * @param[in] pMqttContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. - * @param[in] packetIdentifier Packet identifier of the incoming packet. - * @param[in] pPublishInfo Deserialized publish info pointer for the incoming - * packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. */ static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ); + MQTTDeserializedInfo_t * pDeserializedInfo ); /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. @@ -604,20 +601,24 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ) + MQTTDeserializedInfo_t * pDeserializedInfo ) { + uint16_t packetIdentifier; + assert( pMqttContext != NULL ); assert( pPacketInfo != NULL ); + assert( pDeserializedInfo != NULL ); + + packetIdentifier = pDeserializedInfo->packetIdentifier; /* Handle incoming publish. The lower 4 bits of the publish packet * type is used for the dup, QoS, and retain flags. Hence masking * out the lower bits to check if the packet is publish. */ if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) { - assert( pPublishInfo != NULL ); + assert( pDeserializedInfo->pPublishInfo != NULL ); /* Handle incoming publish. */ - handleIncomingPublish( pPublishInfo, packetIdentifier ); + handleIncomingPublish( pDeserializedInfo->pPublishInfo, packetIdentifier ); } else { diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 827a1fddf7..a89e691e24 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -282,14 +282,11 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, * * @param[in] pMqttContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. - * @param[in] packetIdentifier Packet identifier of the incoming packet. - * @param[in] pPublishInfo Deserialized publish info pointer for the incoming - * packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. */ static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ); + MQTTDeserializedInfo_t * pDeserializedInfo ); /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. @@ -649,20 +646,24 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ) + MQTTDeserializedInfo_t * pDeserializedInfo ) { + uint16_t packetIdentifier; + assert( pMqttContext != NULL ); assert( pPacketInfo != NULL ); + assert( pDeserializedInfo != NULL ); + + packetIdentifier = pDeserializedInfo->packetIdentifier; /* Handle incoming publish. The lower 4 bits of the publish packet * type is used for the dup, QoS, and retain flags. Hence masking * out the lower bits to check if the packet is publish. */ if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) { - assert( pPublishInfo != NULL ); + assert( pDeserializedInfo->pPublishInfo != NULL ); /* Handle incoming publish. */ - handleIncomingPublish( pPublishInfo, packetIdentifier ); + handleIncomingPublish( pDeserializedInfo->pPublishInfo, packetIdentifier ); } else { diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 1e8ea6ed4f..aa6a512ed6 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -209,14 +209,11 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, * * @param[in] pMqttContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. - * @param[in] packetIdentifier Packet identifier of the incoming packet. - * @param[in] pPublishInfo Deserialized publish info pointer for the incoming - * packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. */ static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ); + MQTTDeserializedInfo_t * pDeserializedInfo ); /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. @@ -364,20 +361,24 @@ static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo, static void eventCallback( MQTTContext_t * pMqttContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ) + MQTTDeserializedInfo_t * pDeserializedInfo ) { + uint16_t packetIdentifier; + assert( pMqttContext != NULL ); assert( pPacketInfo != NULL ); + assert( pDeserializedInfo != NULL ); + + packetIdentifier = pDeserializedInfo->packetIdentifier; /* Handle incoming publish. The lower 4 bits of the publish packet * type is used for the dup, QoS, and retain flags. Hence masking * out the lower bits to check if the packet is publish. */ if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) { - assert( pPublishInfo != NULL ); + assert( pDeserializedInfo->pPublishInfo != NULL ); /* Handle incoming publish. */ - handleIncomingPublish( pPublishInfo, packetIdentifier ); + handleIncomingPublish( pDeserializedInfo->pPublishInfo, packetIdentifier ); } else { diff --git a/libraries/standard/mqtt/cbmc/include/event_callback_stub.h b/libraries/standard/mqtt/cbmc/include/event_callback_stub.h index 9db2f2c3f9..39857bb716 100644 --- a/libraries/standard/mqtt/cbmc/include/event_callback_stub.h +++ b/libraries/standard/mqtt/cbmc/include/event_callback_stub.h @@ -35,12 +35,10 @@ * * @param[in] pContext Initialized MQTT context. * @param[in] pPacketInfo Information on the type of incoming MQTT packet. - * @param[in] packetIdentifier Packet identifier of incoming PUBLISH packet. - * @param[in] pPublishInfo Incoming PUBLISH packet parameters. + * @param[in] pDeserializedInfo Deserialized information from incoming packet. */ void EventCallbackStub( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ); + MQTTDeserializedInfo_t * pDeserializedInfo ); #endif /* ifndef EVENT_CALLBACK_STUB_H_ */ diff --git a/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c b/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c index db005f29a9..d645572d9b 100644 --- a/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c +++ b/libraries/standard/mqtt/cbmc/stubs/event_callback_stub.c @@ -23,12 +23,12 @@ void EventCallbackStub( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ) + MQTTDeserializedInfo_t * pDeserializedInfo ) { __CPROVER_assert( pContext != NULL, "EventCallbackStub pContext is not NULL" ); __CPROVER_assert( pPacketInfo != NULL, "EventCallbackStub pPacketInfo is not NULL" ); - /* pPublishInfo will be NULL for an incoming ACK event. */ + __CPROVER_assert( pDeserializedInfo != NULL, + "EventCallbackStub pDeserializedInfo is not NULL" ); } diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 2e5c15c0d5..84a5cc5fee 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -36,10 +36,13 @@ #define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) struct MQTTPubAckInfo; -typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; +typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; struct MQTTContext; -typedef struct MQTTContext MQTTContext_t; +typedef struct MQTTContext MQTTContext_t; + +struct MQTTDeserializedInfo; +typedef struct MQTTDeserializedInfo MQTTDeserializedInfo_t; /** * @brief Application provided callback to retrieve the current time in @@ -53,15 +56,17 @@ typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); * @brief Application callback for receiving incoming publishes and incoming * acks. * + * @note This callback will be called only if packets are deserialized with a + * result of #MQTTSuccess or #MQTTServerRefused. The latter can be obtained + * when deserializing a SUBACK, indicating a broker's rejection of a subscribe. + * * @param[in] pContext Initialized MQTT context. * @param[in] pPacketInfo Information on the type of incoming MQTT packet. - * @param[in] packetIdentifier Packet identifier of incoming PUBLISH packet. - * @param[in] pPublishInfo Incoming PUBLISH packet parameters. + * @param[in] pDeserializedInfo Deserialized information from incoming packet. */ typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ); + MQTTDeserializedInfo_t * pDeserializedInfo ); typedef enum MQTTConnectionStatus { @@ -123,6 +128,13 @@ struct MQTTContext bool waitingForPingResp; }; +struct MQTTDeserializedInfo +{ + uint16_t packetIdentifier; + MQTTPublishInfo_t * pPublishInfo; + MQTTStatus_t deserializationResult; +}; + /** * @brief Initialize an MQTT context. * diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index af3de7a44b..bce1846225 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -300,14 +300,11 @@ static void handleAckEvents( MQTTPacketInfo_t * pPacketInfo, * * @param[in] pContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. - * @param[in] packetIdentifier Packet identifier of the incoming packet. - * @param[in] pPublishInfo Deserialized publish info pointer for the incoming - * packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. */ static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ); + MQTTDeserializedInfo_t * pDeserializedInfo ); /** * @brief Implementation of TransportSend_t interface that terminates the TLS @@ -507,11 +504,16 @@ static void handleAckEvents( MQTTPacketInfo_t * pPacketInfo, static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ) + MQTTDeserializedInfo_t * pDeserializedInfo ) { + MQTTPublishInfo_t * pPublishInfo = NULL; + assert( pContext != NULL ); assert( pPacketInfo != NULL ); + assert( pDeserializedInfo != NULL ); + + TEST_ASSERT_EQUAL( MQTTSuccess, pDeserializedInfo->deserializationResult ); + pPublishInfo = pDeserializedInfo->pPublishInfo; if( ( pPacketInfo->type == disconnectOnPacketType ) || ( ( pPacketInfo->type & 0xF0U ) == disconnectOnPacketType ) ) @@ -552,8 +554,7 @@ static void eventCallback( MQTTContext_t * pContext, } else { - handleAckEvents( pPacketInfo, - packetIdentifier ); + handleAckEvents( pPacketInfo, pDeserializedInfo->packetIdentifier ); } } } diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index a31454f99e..7c237863eb 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -140,6 +140,7 @@ pconnectinfo pcontext pcurrentstate pcursor +pdeserializedinfo pdestination pem pfixedbuffer @@ -242,4 +243,4 @@ xa xb xc xd -xe \ No newline at end of file +xe diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index b9e349c8e5..8a932e1689 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -730,6 +730,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, MQTTPublishState_t publishRecordState = MQTTStateNull; uint16_t packetIdentifier = 0U; MQTTPublishInfo_t publishInfo; + MQTTDeserializedInfo_t deserializedInfo; bool duplicatePublish = false; assert( pContext != NULL ); @@ -803,6 +804,11 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, if( status == MQTTSuccess ) { + /* Set fields of deserialized struct. */ + deserializedInfo.packetIdentifier = packetIdentifier; + deserializedInfo.pPublishInfo = &publishInfo; + deserializedInfo.deserializationResult = status; + /* Invoke application callback to hand the buffer over to application * before sending acks. * Application callback will be invoked for all publishes, except for @@ -811,8 +817,7 @@ static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext, { pContext->appCallback( pContext, pIncomingPacket, - packetIdentifier, - &publishInfo ); + &deserializedInfo ); } /* Send PUBACK or PUBREC if necessary. */ @@ -834,6 +839,7 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, uint16_t packetIdentifier; MQTTPubAckType_t ackType; MQTTEventCallback_t appCallback; + MQTTDeserializedInfo_t deserializedInfo; assert( pContext != NULL ); assert( pIncomingPacket != NULL ); @@ -870,9 +876,14 @@ static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext, if( status == MQTTSuccess ) { + /* Set fields of deserialized struct. */ + deserializedInfo.packetIdentifier = packetIdentifier; + deserializedInfo.deserializationResult = status; + deserializedInfo.pPublishInfo = NULL; + /* Invoke application callback to hand the buffer over to application * before sending acks. */ - appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); + appCallback( pContext, pIncomingPacket, &deserializedInfo ); /* Send PUBREL or PUBCOMP if necessary. */ status = sendPublishAcks( pContext, @@ -891,6 +902,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, { MQTTStatus_t status = MQTTBadResponse; uint16_t packetIdentifier = MQTT_PACKET_ID_INVALID; + MQTTDeserializedInfo_t deserializedInfo; /* We should always invoke the app callback unless we receive a PINGRESP * and are managing keep alive, or if we receive an unknown packet. We @@ -920,7 +932,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_PINGRESP: status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); - invokeAppCallback = ( manageKeepAlive == true ) ? false : true; + invokeAppCallback = ( ( status == MQTTSuccess ) && ( manageKeepAlive == false ) ) ? true : false; if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) ) { @@ -933,7 +945,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, case MQTT_PACKET_TYPE_UNSUBACK: /* Deserialize and give these to the app provided callback. */ status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL ); - invokeAppCallback = true; + invokeAppCallback = ( ( status == MQTTSuccess ) || ( status == MQTTServerRefused ) ) ? true : false; break; default: @@ -944,9 +956,15 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, break; } - if( ( status == MQTTSuccess ) && ( invokeAppCallback == true ) ) + if( invokeAppCallback == true ) { - appCallback( pContext, pIncomingPacket, packetIdentifier, NULL ); + /* Set fields of deserialized struct. */ + deserializedInfo.packetIdentifier = packetIdentifier; + deserializedInfo.deserializationResult = status; + deserializedInfo.pPublishInfo = NULL; + appCallback( pContext, pIncomingPacket, &deserializedInfo ); + /* In case a SUBACK indicated refusal, reset the status to continue the loop. */ + status = MQTTSuccess; } return status; diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index f8f7c2a4ce..9c40073268 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -960,7 +960,7 @@ static MQTTStatus_t readSubackStatus( size_t statusCount, case 0x80: - LogDebug( ( "Topic filter %lu refused.", ( unsigned long ) i ) ); + LogWarn( ( "Topic filter %lu refused.", ( unsigned long ) i ) ); /* Application should remove subscription from the list */ status = MQTTServerRefused; diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index a62b69181d..bd7e020c6f 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -187,19 +187,15 @@ static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer ) * * @param[in] pContext MQTT context pointer. * @param[in] pPacketInfo Packet Info pointer for the incoming packet. - * @param[in] packetIdentifier Packet identifier of the incoming packet. - * @param[in] pPublishInfo Deserialized publish info pointer for the incoming - * packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. */ static void eventCallback( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, - uint16_t packetIdentifier, - MQTTPublishInfo_t * pPublishInfo ) + MQTTDeserializedInfo_t * pDeserializedInfo ) { ( void ) pContext; ( void ) pPacketInfo; - ( void ) packetIdentifier; - ( void ) pPublishInfo; + ( void ) pDeserializedInfo; /* Update the global state to indicate that event callback is invoked. */ isEventCallbackInvoked = true; @@ -1542,6 +1538,15 @@ void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void ) MQTTSuccess, MQTTSuccess, MQTTStateNull, MQTTSuccess, false, NULL ); + /* Verify that process loop is still successful when SUBACK indicates a + * server refusal. */ + currentPacketType = MQTT_PACKET_TYPE_SUBACK; + isEventCallbackInvoked = false; + expectProcessLoopCalls( &context, MQTTServerRefused, MQTTStateNull, + MQTTSuccess, MQTTSuccess, MQTTStateNull, + MQTTSuccess, false, NULL ); + TEST_ASSERT_TRUE( isEventCallbackInvoked ); + /* Mock the receiving of an UNSUBACK packet type and expect the appropriate * calls made from the process loop. */ currentPacketType = MQTT_PACKET_TYPE_UNSUBACK; From aa425798300ac4554e0f2d1656ed1b2fe06f0f07 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 7 Aug 2020 11:39:55 -0700 Subject: [PATCH 615/844] Minor hygiene updates for logging (#1096) * Log stripped out filename as metadata instead of function name for ISO C adherence * Make ERROR_LOG as default logging verbosity for MQTT unit tests --- demos/logging-stack/logging_stack.h | 7 ++++++- libraries/standard/mqtt/utest/mqtt_config.h | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/demos/logging-stack/logging_stack.h b/demos/logging-stack/logging_stack.h index 6424623325..9e11be1f45 100644 --- a/demos/logging-stack/logging_stack.h +++ b/demos/logging-stack/logging_stack.h @@ -34,10 +34,15 @@ /* Standard Include. */ #include #include +#include + +/* Macro to extract only the file name from file path to use for metadata in + * log messages. */ +#define FILENAME ( strrchr( __FILE__, '/' ) ? strrchr( __FILE__, '/' ) + 1 : __FILE__ ) /* Metadata information to prepend to every log message. */ #define LOG_METADATA_FORMAT "[%s:%d] " -#define LOG_METADATA_ARGS __FUNCTION__, __LINE__ +#define LOG_METADATA_ARGS FILENAME, __LINE__ /* Common macro for all logging interface macros. */ #if !defined( DISABLE_LOGGING ) diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index f4e28cefc2..382bdfda9b 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -20,7 +20,7 @@ #endif #ifndef LIBRARY_LOG_LEVEL - #define LIBRARY_LOG_LEVEL LOG_NONE + #define LIBRARY_LOG_LEVEL LOG_ERROR #endif #include "logging_stack.h" From 9dc4ae1a06e1b6bce43a88c75e9b9918a3378e5d Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 7 Aug 2020 17:00:16 -0700 Subject: [PATCH 616/844] Fix Segfault issue in demos from incorrect hostname OR from incorrect initialization of SSL object (#1100) Fix 2 different corner cases of SegFault in demos: 1) Using incorrect hostname for which DNS resolution fails 2) Running the demo against an MQTT server that is down (thereby, not accepting TCP connections) --- demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 2 +- .../mqtt_demo_lightweight/mqtt_demo_lightweight.c | 2 +- .../mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c | 2 +- demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c | 2 +- platform/posix/transport/src/openssl_posix.c | 11 +++++++++-- platform/posix/transport/src/sockets_posix.c | 7 ++++--- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 2a8b7b353e..351f54d2d1 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -1115,7 +1115,7 @@ int main( int argc, char ** argv ) { int returnStatus = EXIT_SUCCESS; - NetworkContext_t networkContext; + NetworkContext_t networkContext = { 0 }; ( void ) argc; ( void ) argv; diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index cffd236aaa..fb6cfb7f0e 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -779,7 +779,7 @@ int main( int argc, uint32_t timeDiff = 0; bool controlPacketSent = false; bool publishPacketSent = false; - NetworkContext_t networkContext; + NetworkContext_t networkContext = { 0 }; ( void ) argc; ( void ) argv; diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index a89e691e24..3554aa9bed 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -1144,7 +1144,7 @@ int main( int argc, char ** argv ) { int returnStatus = EXIT_SUCCESS; - NetworkContext_t networkContext; + NetworkContext_t networkContext = { 0 }; ( void ) argc; ( void ) argv; diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index aa6a512ed6..04864c1e6c 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -809,7 +809,7 @@ int main( int argc, char ** argv ) { int returnStatus = EXIT_SUCCESS; - NetworkContext_t networkContext; + NetworkContext_t networkContext = { 0 }; ( void ) argc; ( void ) argv; diff --git a/platform/posix/transport/src/openssl_posix.c b/platform/posix/transport/src/openssl_posix.c index f44b7c5c52..b230337431 100644 --- a/platform/posix/transport/src/openssl_posix.c +++ b/platform/posix/transport/src/openssl_posix.c @@ -30,6 +30,7 @@ #include "transport_interface.h" #include "openssl_posix.h" +#include /*-----------------------------------------------------------*/ @@ -401,6 +402,7 @@ OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, OpensslStatus_t returnStatus = OPENSSL_SUCCESS; long verifyPeerCertStatus = X509_V_OK; int sslStatus = 0; + uint8_t sslObjectCreated = 0; SSL_CTX * pSslContext = NULL; /* Validate parameters. */ @@ -484,6 +486,10 @@ OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, LogError( ( "SSL_new failed to create a new SSL context." ) ); returnStatus = OPENSSL_API_ERROR; } + else + { + sslObjectCreated = 1u; + } } /* Setup the socket to use for communication. */ @@ -535,7 +541,7 @@ OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, } /* Clean up on error. */ - if( ( returnStatus != OPENSSL_SUCCESS ) && ( sslStatus == 0 ) ) + if( ( returnStatus != OPENSSL_SUCCESS ) && ( sslObjectCreated == 1u ) ) { SSL_free( pNetworkContext->pSsl ); pNetworkContext->pSsl = NULL; @@ -575,6 +581,7 @@ OpensslStatus_t Openssl_Disconnect( NetworkContext_t * pNetworkContext ) } SSL_free( pNetworkContext->pSsl ); + pNetworkContext->pSsl = NULL; } else { @@ -617,7 +624,7 @@ int32_t Openssl_Recv( NetworkContext_t * pNetworkContext, else { LogError( ( "SSL_read of OpenSSL failed to receive data: " - "status=%d.", bytesReceived ) ); + "status=%s.", ERR_reason_error_string( sslError ) ) ); } } diff --git a/platform/posix/transport/src/sockets_posix.c b/platform/posix/transport/src/sockets_posix.c index b91b3d6e35..018c4b26de 100644 --- a/platform/posix/transport/src/sockets_posix.c +++ b/platform/posix/transport/src/sockets_posix.c @@ -130,11 +130,12 @@ static SocketStatus_t resolveHostName( const char * pHostName, /* Perform a DNS lookup on the given host name. */ dnsStatus = getaddrinfo( pHostName, NULL, &hints, pListHead ); - if( dnsStatus == -1 ) + if( dnsStatus != 0 ) { - LogError( ( "Could not resolve host %.*s.\n", + LogError( ( "Failed to resolve DNS: Hostname=%.*s, ErrorCode=%d.\n", ( int32_t ) hostNameLength, - pHostName ) ); + pHostName, + dnsStatus ) ); returnStatus = SOCKETS_DNS_FAILURE; } From 5099702fd986bf72fc45869ef3736c8d72ab87f0 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Sat, 8 Aug 2020 21:27:00 -0700 Subject: [PATCH 617/844] Add CBMC proofs for http parser callbacks (#1093) * Create harness for httpParserOnMessageBeginCallback * Try a different approach * Add CBMC proofs for all callbacks (still need to resolve same object violation issue) * Change pLoc to be within bounds of response buffer * Push callback stubs * Update to fix memcpy src/dst overlap error * Update all CBMC proofs to use mangled names of static method under test (Problem with httpParserOnHeadersCompleteCallback) * Update httpParserOnHeadersCompleteCallback harness with right assumptions but assumptions not propagating * Update httpParserOnHeadersCompleteCallback to run correctly using an imperative approach * Update httpParserOnBodyCallback to use memmove and stub it out for the proof * Add stub for memmove * Document additional functions for http cbmc state * Update transport interface send stubs to remove execution paths that are never reached * Add full coverage for findHeaderInResponse * Update http_parser_execute field and value to point to buffer param * Complete headerFieldParserCallback proof * Update lexicon.txt for http * Add dest to http lexicon.txt * Add docstrings to allocators for HTTPClient_ReadHeader * Commit remaining proofs for http parser callbacks * Add missing newlines to files * Revert Makefile.common to original * Update cbmc templates to latest commit * Remove unnecessary enclosing parenthesis around UINT64_MAX * Address PR comments * Add missing newlines * Change offsets to be < rather than <= * Address PR comments * Remove unused vars --- .../http/cbmc/include/callback_stubs.h | 46 +++++++ .../http/cbmc/include/http_cbmc_state.h | 67 +++++++++- .../proofs/HTTPClient_ReadHeader/Makefile | 12 +- .../HTTPClient_Send/HTTPClient_Send_harness.c | 7 +- .../http/cbmc/proofs/HTTPClient_Send/Makefile | 5 + .../findHeaderFieldParserCallback/Makefile | 22 ++++ .../findHeaderFieldParserCallback/README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../findHeaderFieldParserCallback_harness.c | 61 +++++++++ .../Makefile | 18 +++ .../README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ ...ndHeaderOnHeaderCompleteCallback_harness.c | 39 ++++++ .../findHeaderValueParserCallback/Makefile | 18 +++ .../findHeaderValueParserCallback/README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../findHeaderValueParserCallback_harness.c | 63 ++++++++++ .../proofs/httpParserOnBodyCallback/Makefile | 20 +++ .../proofs/httpParserOnBodyCallback/README.md | 10 ++ .../httpParserOnBodyCallback/cbmc-batch.yaml | 2 + .../httpParserOnBodyCallback/cbmc-viewer.json | 7 ++ .../httpParserOnBodyCallback_harness.c | 51 ++++++++ .../httpParserOnHeaderFieldCallback/Makefile | 19 +++ .../httpParserOnHeaderFieldCallback/README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../httpParserOnHeaderFieldCallback_harness.c | 57 +++++++++ .../httpParserOnHeaderValueCallback/Makefile | 18 +++ .../httpParserOnHeaderValueCallback/README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../httpParserOnHeaderValueCallback_harness.c | 50 ++++++++ .../Makefile | 19 +++ .../README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ ...pParserOnHeadersCompleteCallback_harness.c | 56 +++++++++ .../httpParserOnMessageBeginCallback/Makefile | 18 +++ .../README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ ...httpParserOnMessageBeginCallback_harness.c | 38 ++++++ .../Makefile | 18 +++ .../README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ ...pParserOnMessageCompleteCallback_harness.c | 38 ++++++ .../httpParserOnStatusCallback/Makefile | 18 +++ .../httpParserOnStatusCallback/README.md | 10 ++ .../cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 ++ .../httpParserOnStatusCallback_harness.c | 49 ++++++++ .../http/cbmc/sources/http_cbmc_state.c | 118 +++++++++++++++++- ...TTPClient_ReadHeader_http_parser_execute.c | 89 +++++++++++++ .../HTTPClient_Send_http_parser_execute.c | 3 +- .../standard/http/cbmc/stubs/callback_stubs.c | 42 +++++++ libraries/standard/http/cbmc/stubs/memmove.c | 54 ++++++++ .../cbmc/stubs/transport_interface_stubs.c | 10 +- libraries/standard/http/lexicon.txt | 10 ++ libraries/standard/http/src/http_client.c | 22 ++-- 63 files changed, 1331 insertions(+), 34 deletions(-) create mode 100644 libraries/standard/http/cbmc/include/callback_stubs.h create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/findHeaderFieldParserCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/findHeaderOnHeaderCompleteCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/findHeaderValueParserCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/httpParserOnBodyCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/httpParserOnHeaderFieldCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/httpParserOnHeaderValueCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/httpParserOnHeadersCompleteCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/httpParserOnMessageBeginCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/httpParserOnMessageCompleteCallback_harness.c create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/Makefile create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/README.md create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-batch.yaml create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-viewer.json create mode 100644 libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/httpParserOnStatusCallback_harness.c create mode 100644 libraries/standard/http/cbmc/stubs/HTTPClient_ReadHeader_http_parser_execute.c create mode 100644 libraries/standard/http/cbmc/stubs/callback_stubs.c create mode 100644 libraries/standard/http/cbmc/stubs/memmove.c diff --git a/libraries/standard/http/cbmc/include/callback_stubs.h b/libraries/standard/http/cbmc/include/callback_stubs.h new file mode 100644 index 0000000000..27955f5157 --- /dev/null +++ b/libraries/standard/http/cbmc/include/callback_stubs.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef CALLBACK_STUBS_H_ +#define CALLBACK_STUBS_H_ + +#include +#include + + +/** + * @brief Invoked when both a header field and its associated header value are found. + * + * @param[in] pContext User context. + * @param[in] fieldLoc Location of the header field name in the response buffer. + * @param[in] fieldLen Length in bytes of the field name. + * @param[in] valueLoc Location of the header value in the response buffer. + * @param[in] valueLen Length in bytes of the value. + * @param[in] statusCode The HTTP response status-code. + */ +void onHeaderCallbackStub( void * pContext, + const char * fieldLoc, + size_t fieldLen, + const char * valueLoc, + size_t valueLen, + uint16_t statusCode ); + +#endif /* ifndef CALLBACK_STUBS_H_ */ diff --git a/libraries/standard/http/cbmc/include/http_cbmc_state.h b/libraries/standard/http/cbmc/include/http_cbmc_state.h index 19162ff176..3fd56c5d94 100644 --- a/libraries/standard/http/cbmc/include/http_cbmc_state.h +++ b/libraries/standard/http/cbmc/include/http_cbmc_state.h @@ -28,6 +28,7 @@ #include "http_client.h" #include "private/http_client_internal.h" #include "http_parser.h" +#include "transport_interface_stubs.h" struct NetworkContext { @@ -89,7 +90,7 @@ HTTPRequestInfo_t * allocateHttpRequestInfo( HTTPRequestInfo_t * pRequestInfo ); bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ); /** - * @brief Allocates a #HTTPResponse_t object with unconstrained values. + * @brief Allocates a #HTTPResponse_t object. * * @param[in] pResponse #HTTPResponse_t object to allocate. * @@ -107,13 +108,69 @@ HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ); bool isValidHttpResponse( const HTTPResponse_t * pResponse ); /** - * @brief Allocate a transport interface for CBMC. + * @brief Allocates a #TransportInterface_t object. * - * @param[in] pTransport Transport interface. + * @param[in] pTransport #TransportInterface_t object to allocate. * - * @return An allocated TransportInterface_t object to use as a parameter - * for the function under test. + * @return NULL or pointer to allocated #TransportInterface_t object. */ TransportInterface_t * allocateTransportInterface( TransportInterface_t * pTransport ); +/** + * @brief Validates if a #TransportInterface_t object is feasible. + * + * @param[in] pTransportInterface #TransportInterface_t object to validate. + * + * @return True if #pTransportInterface is feasible; false otherwise. + */ +bool isValidTransportInterface( TransportInterface_t * pTransportInterface ); + +/** + * @brief Allocate an #http_parser object that is valid in the context of the + * HTTPClient_Send() function. + * + * @param[in] pHttpParser #http_parser object to allocate. + * + * @return NULL or pointer to allocated #http_parser object. + */ +http_parser * allocateHttpSendParser( http_parser * pHttpParser ); + +/** + * @brief Allocate an #HTTPParsingContext_t object. + * + * @param[in] pHttpParsingContext #HTTPParsingContext_t object to allocate. + * + * @return NULL or pointer to allocated #HTTPParsingContext_t object. + */ +HTTPParsingContext_t * allocateHttpSendParsingContext( HTTPParsingContext_t * pHttpParsingContext ); + +/** + * @brief Validates if a #HTTPParsingContext_t object is feasible. + * + * @param[in] pHttpParsingContext #HTTPParsingContext_t object to validate. + * + * @return True if #pHttpParsingContext is feasible; false otherwise. + */ +bool isValidHttpSendParsingContext( const HTTPParsingContext_t * pHttpParsingContext ); + +/** + * @brief Allocate an #http_parser object that is valid in the context of the + * HTTPClient_ReadHeader() function. + * + * @param[in] pHttpParser #http_parser object to allocate. + * + * @return NULL or pointer to allocated #http_parser object. + */ +http_parser * allocateHttpReadHeaderParser( http_parser * pHttpParser ); + +/** + * @brief Allocate an #findHeaderContext_t object. + * + * @param[in] pFindHeaderContext #findHeaderContext_t object to allocate. + * + * @return NULL or pointer to allocated #findHeaderContext_t object. + */ +findHeaderContext_t * allocateFindHeaderContext( findHeaderContext_t * pFindHeaderContext ); + + #endif /* ifndef HTTP_CBMC_STATE_H_ */ diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile index 8245a64d57..a9073ccb8e 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/Makefile @@ -25,11 +25,21 @@ HARNESS_FILE=HTTPClient_ReadHeader_harness DEFINES += INCLUDES += -REMOVE_FUNCTION_BODY += +# The memory safety of the http-parser callbacks are proven separately. +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderFieldParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderValueParserCallback +REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_findHeaderOnHeaderCompleteCallback + +# Remove any unused functions for http parser +REMOVE_FUNCTION_BODY += http_parser_init +REMOVE_FUNCTION_BODY += http_parser_set_max_header_size +REMOVE_FUNCTION_BODY += http_parser_settings_init + UNWINDSET += PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/HTTPClient_ReadHeader_http_parser_execute.c PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c index 27733e513e..1f71f3d8d7 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/HTTPClient_Send_harness.c @@ -30,11 +30,8 @@ void harness() { - HTTPRequestHeaders_t * pRequestHeaders = NULL; - HTTPResponse_t * pResponse = NULL; - - /* Ideally, this should be allocated in the heap but doing so makes CBMC - * run out of memory. */ + HTTPRequestHeaders_t * pRequestHeaders; + HTTPResponse_t * pResponse; TransportInterface_t * pTransportInterface; uint8_t * pRequestBodyBuf; size_t reqBodyBufLen; diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile index 609f52ca82..848b48ea30 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_Send/Makefile @@ -40,6 +40,11 @@ REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnHeadersCo REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback REMOVE_FUNCTION_BODY += __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback +# Remove any unused functions for http parser +REMOVE_FUNCTION_BODY += http_parser_init +REMOVE_FUNCTION_BODY += http_parser_set_max_header_size +REMOVE_FUNCTION_BODY += http_parser_settings_init + # There is a total of 10 digits in INT32_MAX. These loops are unwound once more # than the total possible iterations in the int32_t to ASCII converation. UNWINDSET += __CPROVER_file_local_http_client_c_convertInt32ToAscii.0:11 diff --git a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile new file mode 100644 index 0000000000..0bb0632675 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile @@ -0,0 +1,22 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=findHeaderFieldParserCallback_harness + +# The header field length is bounded, so strncmp can be unwounded to an expected +# amount that won't make the proof run too long. +MAX_HEADER_FIELD_LENGTH=10 + +DEFINES += -DMAX_HEADER_FIELD_LENGTH=$(MAX_HEADER_FIELD_LENGTH) +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += strncmp.0:$(MAX_HEADER_FIELD_LENGTH) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/README.md b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/README.md new file mode 100644 index 0000000000..6f94aaad66 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/README.md @@ -0,0 +1,10 @@ +findHeaderFieldParserCallback proof +============== + +This directory contains a memory safety proof for findHeaderFieldParserCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-viewer.json new file mode 100644 index 0000000000..0d204bcd58 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "findHeaderFieldParserCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/findHeaderFieldParserCallback_harness.c b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/findHeaderFieldParserCallback_harness.c new file mode 100644 index 0000000000..b4c6545263 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/findHeaderFieldParserCallback_harness.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file findHeaderFieldParserCallback_harness.c + * @brief Implements the proof harness for findHeaderFieldParserCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" +#include "http_client.h" + + +void harness() +{ + http_parser * pHttpParser; + HTTPResponse_t * pResponse; + findHeaderContext_t * pFindHeaderContext; + size_t fieldLen, fieldContextLen, fieldOffset, valueLen, valueOffset; + char * pFieldLoc; + + pHttpParser = allocateHttpReadHeaderParser( NULL ); + pFindHeaderContext = allocateFindHeaderContext( NULL ); + pResponse = allocateHttpResponse( NULL ); + __CPROVER_assume( isValidHttpResponse( pResponse ) && + pResponse != NULL && + pResponse->pBuffer != NULL && + pResponse->bufferLen > 0 ); + + __CPROVER_assume( 0 < fieldLen && fieldLen <= MAX_HEADER_FIELD_LENGTH && fieldLen <= pResponse->bufferLen ); + __CPROVER_assume( fieldOffset < fieldLen ); + pFieldLoc = pResponse->pBuffer + fieldOffset; + __CPROVER_assume( pFieldLoc + fieldLen < pResponse->pBuffer + pResponse->bufferLen ); + + __CPROVER_assume( 0 < fieldContextLen && fieldContextLen < CBMC_MAX_OBJECT_SIZE ); + pFindHeaderContext->pField = ( char * ) malloc( fieldContextLen ); + pFindHeaderContext->fieldLen = fieldContextLen; + pFindHeaderContext->fieldFound = 0; + pFindHeaderContext->valueFound = 0; + pHttpParser->data = ( void * ) pFindHeaderContext; + + __CPROVER_file_local_http_client_c_findHeaderFieldParserCallback( pHttpParser, pFieldLoc, fieldLen ); +} diff --git a/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/Makefile b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/Makefile new file mode 100644 index 0000000000..9830f4e745 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=findHeaderOnHeaderCompleteCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/README.md b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/README.md new file mode 100644 index 0000000000..c24c00ec69 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/README.md @@ -0,0 +1,10 @@ +findHeaderOnHeaderCompleteCallback proof +============== + +This directory contains a memory safety proof for findHeaderOnHeaderCompleteCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-viewer.json new file mode 100644 index 0000000000..e635199b1b --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "findHeaderOnHeaderCompleteCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/findHeaderOnHeaderCompleteCallback_harness.c b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/findHeaderOnHeaderCompleteCallback_harness.c new file mode 100644 index 0000000000..270ec9f38c --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderOnHeaderCompleteCallback/findHeaderOnHeaderCompleteCallback_harness.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file findHeaderOnHeaderCompleteCallback_harness.c + * @brief Implements the proof harness for findHeaderOnHeaderCompleteCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" +#include "http_client.h" + + +void harness() +{ + http_parser * pHttpParser; + + pHttpParser = allocateHttpReadHeaderParser( NULL ); + + __CPROVER_file_local_http_client_c_findHeaderOnHeaderCompleteCallback( pHttpParser ); +} diff --git a/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/Makefile b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/Makefile new file mode 100644 index 0000000000..a3494ca539 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=findHeaderValueParserCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/README.md b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/README.md new file mode 100644 index 0000000000..e7896b20b7 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/README.md @@ -0,0 +1,10 @@ +findHeaderValueParserCallback proof +============== + +This directory contains a memory safety proof for findHeaderValueParserCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-viewer.json new file mode 100644 index 0000000000..e613f86c75 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "findHeaderValueParserCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/findHeaderValueParserCallback_harness.c b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/findHeaderValueParserCallback_harness.c new file mode 100644 index 0000000000..e826799dc0 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/findHeaderValueParserCallback/findHeaderValueParserCallback_harness.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file findHeaderValueParserCallback_harness.c + * @brief Implements the proof harness for findHeaderValueParserCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" +#include "http_client.h" + + +void harness() +{ + http_parser * pHttpParser; + HTTPResponse_t * pResponse; + findHeaderContext_t * pFindHeaderContext; + size_t fieldLen, fieldOffset, valueLen, valueOffset; + char * pValueLoc; + + pHttpParser = allocateHttpReadHeaderParser( NULL ); + pResponse = allocateHttpResponse( NULL ); + pFindHeaderContext = allocateFindHeaderContext( NULL ); + __CPROVER_assume( isValidHttpResponse( pResponse ) && + pResponse != NULL && + pResponse->pBuffer != NULL && + pResponse->bufferLen > 0 ); + + __CPROVER_assume( 0 < valueLen && valueLen <= pResponse->bufferLen ); + __CPROVER_assume( valueOffset < valueLen ); + pValueLoc = pResponse->pBuffer + valueOffset; + + __CPROVER_assume( 0 < fieldLen && fieldLen <= pResponse->bufferLen ); + __CPROVER_assume( fieldOffset < fieldLen ); + pFindHeaderContext->pField = pResponse->pBuffer + fieldOffset; + pFindHeaderContext->fieldLen = fieldLen; + pFindHeaderContext->pValueLen = &valueLen; + pFindHeaderContext->pValueLoc = &pValueLoc; + pFindHeaderContext->fieldFound = nondet_bool() ? 0 : 1; + pFindHeaderContext->valueFound = 0; + pHttpParser->data = ( void * ) pFindHeaderContext; + + __CPROVER_file_local_http_client_c_findHeaderValueParserCallback( pHttpParser, pValueLoc, valueLen ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/Makefile new file mode 100644 index 0000000000..4e6cd86f8f --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/Makefile @@ -0,0 +1,20 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnBodyCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += memmove + +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/memmove.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/README.md new file mode 100644 index 0000000000..6b3d8e3afc --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnBodyCallback proof +============== + +This directory contains a memory safety proof for httpParserOnBodyCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-viewer.json new file mode 100644 index 0000000000..126d307d78 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnBodyCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/httpParserOnBodyCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/httpParserOnBodyCallback_harness.c new file mode 100644 index 0000000000..d02efdaa9d --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnBodyCallback/httpParserOnBodyCallback_harness.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnBodyCallback_harness.c + * @brief Implements the proof harness for httpParserOnBodyCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" + + +void harness() +{ + http_parser * pHttpParser; + HTTPParsingContext_t * pParsingContext; + HTTPResponse_t * pResponse; + size_t length; + char * pLoc; + + pHttpParser = allocateHttpSendParser( NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) pHttpParser->data; + + pResponse = pParsingContext->pResponse; + __CPROVER_assume( length < pResponse->bufferLen ); + pLoc = pResponse->pBuffer + length; + + __CPROVER_assume( pLoc + length < + ( pResponse->pBuffer + pResponse->bufferLen ) ); + + __CPROVER_file_local_http_client_c_httpParserOnBodyCallback( pHttpParser, pLoc, length ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/Makefile new file mode 100644 index 0000000000..da962cad0d --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/Makefile @@ -0,0 +1,19 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnHeaderFieldCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/callback_stubs.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/README.md new file mode 100644 index 0000000000..b0bfc40937 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnHeaderFieldCallback proof +============== + +This directory contains a memory safety proof for httpParserOnHeaderFieldCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-viewer.json new file mode 100644 index 0000000000..9e107cc546 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnHeaderFieldCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/httpParserOnHeaderFieldCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/httpParserOnHeaderFieldCallback_harness.c new file mode 100644 index 0000000000..0a04ec9e6d --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderFieldCallback/httpParserOnHeaderFieldCallback_harness.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnHeaderFieldCallback_harness.c + * @brief Implements the proof harness for httpParserOnHeaderFieldCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" +#include "callback_stubs.h" + + +void harness() +{ + http_parser * pHttpParser; + HTTPParsingContext_t * pParsingContext; + HTTPResponse_t * pResponse; + HTTPClient_ResponseHeaderParsingCallback_t headerParserCallback; + size_t length, locOffset; + char * pLoc; + + pHttpParser = allocateHttpSendParser( NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + headerParserCallback.onHeaderCallback = onHeaderCallbackStub; + + pResponse = pParsingContext->pResponse; + pResponse->pHeaderParsingCallback = &headerParserCallback; + + __CPROVER_assume( length <= pResponse->bufferLen ); + __CPROVER_assume( locOffset < length ); + pLoc = pResponse->pBuffer + locOffset; + + /* This assumption suppresses an overflow error when incrementing pResponse->headerCount. */ + __CPROVER_assume( pResponse->headerCount < SIZE_MAX ); + + __CPROVER_file_local_http_client_c_httpParserOnHeaderFieldCallback( pHttpParser, pLoc, length ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/Makefile new file mode 100644 index 0000000000..855515a867 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnHeaderValueCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/README.md new file mode 100644 index 0000000000..8c395341e3 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnHeaderValueCallback proof +============== + +This directory contains a memory safety proof for httpParserOnHeaderValueCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-viewer.json new file mode 100644 index 0000000000..3173839fe2 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnHeaderValueCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/httpParserOnHeaderValueCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/httpParserOnHeaderValueCallback_harness.c new file mode 100644 index 0000000000..b73bd0021d --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeaderValueCallback/httpParserOnHeaderValueCallback_harness.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnHeaderValueCallback_harness.c + * @brief Implements the proof harness for httpParserOnHeaderValueCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" + + +void harness() +{ + http_parser * pHttpParser; + HTTPParsingContext_t * pParsingContext; + HTTPResponse_t * pResponse; + size_t length, locOffset; + char * pLoc; + + pHttpParser = allocateHttpSendParser( NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) pHttpParser->data; + __CPROVER_assume( pParsingContext->pLastHeaderField != NULL ); + + pResponse = pParsingContext->pResponse; + __CPROVER_assume( length <= pResponse->bufferLen ); + __CPROVER_assume( locOffset < length ); + pLoc = pResponse->pBuffer + locOffset; + + __CPROVER_file_local_http_client_c_httpParserOnHeaderValueCallback( pHttpParser, pLoc, length ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/Makefile new file mode 100644 index 0000000000..8eeb5d9639 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/Makefile @@ -0,0 +1,19 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnHeadersCompleteCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/stubs/callback_stubs.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/README.md new file mode 100644 index 0000000000..396c2e041a --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnHeadersCompleteCallback proof +============== + +This directory contains a memory safety proof for httpParserOnHeadersCompleteCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-viewer.json new file mode 100644 index 0000000000..bab1f234f9 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnHeadersCompleteCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/httpParserOnHeadersCompleteCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/httpParserOnHeadersCompleteCallback_harness.c new file mode 100644 index 0000000000..868f803a7e --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnHeadersCompleteCallback/httpParserOnHeadersCompleteCallback_harness.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnHeadersCompleteCallback_harness.c + * @brief Implements the proof harness for httpParserOnHeadersCompleteCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" +#include "callback_stubs.h" + + +void harness() +{ + http_parser * pHttpParser; + HTTPParsingContext_t * pParsingContext; + HTTPResponse_t * pResponse; + HTTPClient_ResponseHeaderParsingCallback_t headerParserCallback; + size_t bufferOffset; + + pHttpParser = allocateHttpSendParser( NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) ( pHttpParser->data ); + headerParserCallback.onHeaderCallback = onHeaderCallbackStub; + + pResponse = pParsingContext->pResponse; + pResponse->pHeaderParsingCallback = &headerParserCallback; + + __CPROVER_assume( pResponse->headersLen <= bufferOffset && + bufferOffset < pResponse->bufferLen ); + pParsingContext->pBufferCur = pResponse->pBuffer + bufferOffset; + + /* This assumption suppresses an overflow error when incrementing pResponse->headerCount. */ + __CPROVER_assume( pResponse->headerCount < SIZE_MAX ); + + __CPROVER_file_local_http_client_c_httpParserOnHeadersCompleteCallback( pHttpParser ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/Makefile new file mode 100644 index 0000000000..4cf671f5de --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnMessageBeginCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/README.md new file mode 100644 index 0000000000..17e1db66dc --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnMessageBeginCallback proof +============== + +This directory contains a memory safety proof for httpParserOnMessageBeginCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-viewer.json new file mode 100644 index 0000000000..01930fe29c --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnMessageBeginCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/httpParserOnMessageBeginCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/httpParserOnMessageBeginCallback_harness.c new file mode 100644 index 0000000000..270121e445 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageBeginCallback/httpParserOnMessageBeginCallback_harness.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnMessageBeginCallback_harness.c + * @brief Implements the proof harness for httpParserOnMessageBeginCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" + + +void harness() +{ + http_parser * pHttpParser; + + pHttpParser = allocateHttpSendParser( NULL ); + + __CPROVER_file_local_http_client_c_httpParserOnMessageBeginCallback( pHttpParser ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/Makefile new file mode 100644 index 0000000000..eab2e3a877 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnMessageCompleteCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/README.md new file mode 100644 index 0000000000..358343a5ee --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnMessageCompleteCallback proof +============== + +This directory contains a memory safety proof for httpParserOnMessageCompleteCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-viewer.json new file mode 100644 index 0000000000..1b44b6143b --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnMessageCompleteCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/httpParserOnMessageCompleteCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/httpParserOnMessageCompleteCallback_harness.c new file mode 100644 index 0000000000..0aacac458a --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnMessageCompleteCallback/httpParserOnMessageCompleteCallback_harness.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnMessageCompleteCallback_harness.c + * @brief Implements the proof harness for httpParserOnMessageCompleteCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" + + +void harness() +{ + http_parser * pHttpParser; + + pHttpParser = allocateHttpSendParser( NULL ); + + __CPROVER_file_local_http_client_c_httpParserOnMessageCompleteCallback( pHttpParser ); +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/Makefile b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/Makefile new file mode 100644 index 0000000000..8ec43665e6 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/Makefile @@ -0,0 +1,18 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +HARNESS_ENTRY=harness +HARNESS_FILE=httpParserOnStatusCallback_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/http/cbmc/sources/http_cbmc_state.c + +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/http/src/http_client.c + +include ../Makefile.common diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/README.md b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/README.md new file mode 100644 index 0000000000..bf3fe9dcf8 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/README.md @@ -0,0 +1,10 @@ +httpParserOnStatusCallback proof +============== + +This directory contains a memory safety proof for httpParserOnStatusCallback. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-batch.yaml b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-viewer.json b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-viewer.json new file mode 100644 index 0000000000..3c30c02e47 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "httpParserOnStatusCallback", + "proof-root": "libraries/standard/http/cbmc/proofs" +} diff --git a/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/httpParserOnStatusCallback_harness.c b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/httpParserOnStatusCallback_harness.c new file mode 100644 index 0000000000..b6f3961c79 --- /dev/null +++ b/libraries/standard/http/cbmc/proofs/httpParserOnStatusCallback/httpParserOnStatusCallback_harness.c @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file httpParserOnStatusCallback_harness.c + * @brief Implements the proof harness for httpParserOnStatusCallback function. + */ + +#include "http_cbmc_state.h" +#include "http_parser.h" +#include "http_client.h" + +void harness() +{ + http_parser * pHttpParser; + HTTPParsingContext_t * pParsingContext; + HTTPResponse_t * pResponse; + size_t length, locOffset; + char * pLoc; + + pHttpParser = allocateHttpSendParser( NULL ); + + pParsingContext = ( HTTPParsingContext_t * ) pHttpParser->data; + + pResponse = pParsingContext->pResponse; + __CPROVER_assume( length <= pResponse->bufferLen ); + __CPROVER_assume( locOffset < length ); + pLoc = pResponse->pBuffer + locOffset; + + __CPROVER_file_local_http_client_c_httpParserOnStatusCallback( pHttpParser, pLoc, length ); +} diff --git a/libraries/standard/http/cbmc/sources/http_cbmc_state.c b/libraries/standard/http/cbmc/sources/http_cbmc_state.c index 4acbc9fd5d..a7ae6565e8 100644 --- a/libraries/standard/http/cbmc/sources/http_cbmc_state.c +++ b/libraries/standard/http/cbmc/sources/http_cbmc_state.c @@ -75,6 +75,8 @@ bool isValidHttpRequestInfo( const HTTPRequestInfo_t * pRequestInfo ) HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ) { + size_t headerOffset, bodyOffset; + if( pResponse == NULL ) { pResponse = mallocCanFail( sizeof( HTTPResponse_t ) ); @@ -83,9 +85,27 @@ HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ) if( pResponse != NULL ) { __CPROVER_assume( pResponse->bufferLen < CBMC_MAX_OBJECT_SIZE ); + __CPROVER_assume( pResponse->bodyLen <= pResponse->bufferLen ); + __CPROVER_assume( pResponse->headersLen <= pResponse->bufferLen ); + pResponse->pBuffer = mallocCanFail( pResponse->bufferLen ); - __CPROVER_assume( pResponse->bodyLen < CBMC_MAX_OBJECT_SIZE ); - pResponse->pBody = mallocCanFail( pResponse->bodyLen ); + + __CPROVER_assume( headerOffset < pResponse->headersLen ); + pResponse->pHeaders = nondet_bool() ? NULL : + pResponse->pBuffer + headerOffset; + + if( pResponse->bufferLen == 0 ) + { + bodyOffset = 0; + } + else + { + __CPROVER_assume( pResponse->headersLen < bodyOffset && + bodyOffset < pResponse->bufferLen ); + } + + pResponse->pBody = nondet_bool() ? NULL : + pResponse->pBuffer + bodyOffset; } return pResponse; @@ -98,7 +118,9 @@ bool isValidHttpResponse( const HTTPResponse_t * pResponse ) if( pResponse ) { isValid = ( pResponse->bufferLen < CBMC_MAX_OBJECT_SIZE ) && - ( pResponse->bodyLen < CBMC_MAX_OBJECT_SIZE ); + ( pResponse->bodyLen < pResponse->bufferLen ) && + ( pResponse->headersLen < pResponse->bufferLen ); + isValid = isValid || pResponse->pBody == NULL; } return isValid; @@ -118,3 +140,93 @@ TransportInterface_t * allocateTransportInterface( TransportInterface_t * pTrans return pTransport; } + +bool isValidTransportInterface( TransportInterface_t * pTransportInterface ) +{ + bool isValid = true; + + if( pTransportInterface ) + { + isValid = isValid && ( pTransportInterface->send == TransportInterfaceSendStub || + pTransportInterface->send == NULL ); + isValid = isValid && ( pTransportInterface->recv == TransportInterfaceReceiveStub || + pTransportInterface->recv == NULL ); + } +} + +http_parser * allocateHttpSendParser( http_parser * pHttpParser ) +{ + HTTPParsingContext_t * pHttpParsingContext; + + if( pHttpParser == NULL ) + { + pHttpParser = malloc( sizeof( http_parser ) ); + } + + pHttpParsingContext = allocateHttpSendParsingContext( NULL ); + __CPROVER_assume( isValidHttpSendParsingContext( pHttpParsingContext ) ); + pHttpParser->data = ( void * ) pHttpParsingContext; + + return pHttpParser; +} + +HTTPParsingContext_t * allocateHttpSendParsingContext( HTTPParsingContext_t * pHttpParsingContext ) +{ + HTTPResponse_t * pResponse; + size_t bufferOffset; + + if( pHttpParsingContext == NULL ) + { + pHttpParsingContext = malloc( sizeof( HTTPParsingContext_t ) ); + } + + if( pHttpParsingContext != NULL ) + { + pResponse = allocateHttpResponse( NULL ); + __CPROVER_assume( isValidHttpResponse( pResponse ) && + pResponse != NULL && + pResponse->pBuffer != NULL && + pResponse->bufferLen > 0 ); + pHttpParsingContext->pResponse = pResponse; + + __CPROVER_assume( bufferOffset < pResponse->bufferLen ); + pHttpParsingContext->pBufferCur = pResponse->pBuffer + bufferOffset; + } + + return pHttpParsingContext; +} + +bool isValidHttpSendParsingContext( const HTTPParsingContext_t * pHttpParsingContext ) +{ + bool isValid = true; + + isValid = isValid && ( pHttpParsingContext->lastHeaderFieldLen ) <= ( SIZE_MAX - CBMC_MAX_OBJECT_SIZE ); + isValid = isValid && ( pHttpParsingContext->lastHeaderValueLen ) <= ( SIZE_MAX - CBMC_MAX_OBJECT_SIZE ); + + return isValid; +} + +http_parser * allocateHttpReadHeaderParser( http_parser * pHttpParser ) +{ + HTTPParsingContext_t * pFindHeaderContext; + + if( pHttpParser == NULL ) + { + pHttpParser = malloc( sizeof( http_parser ) ); + } + + pFindHeaderContext = allocateFindHeaderContext( NULL ); + pHttpParser->data = ( void * ) pFindHeaderContext; + + return pHttpParser; +} + +findHeaderContext_t * allocateFindHeaderContext( findHeaderContext_t * pFindHeaderContext ) +{ + if( pFindHeaderContext == NULL ) + { + pFindHeaderContext = malloc( sizeof( findHeaderContext_t ) ); + } + + return pFindHeaderContext; +} diff --git a/libraries/standard/http/cbmc/stubs/HTTPClient_ReadHeader_http_parser_execute.c b/libraries/standard/http/cbmc/stubs/HTTPClient_ReadHeader_http_parser_execute.c new file mode 100644 index 0000000000..5d5c3d5dc2 --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/HTTPClient_ReadHeader_http_parser_execute.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file HTTPClient_Send_http_parser_execute.c + * @brief Creates a stub for http_parser_execute for coverage of HTTPClient_Send. + */ + +#include +#include + +#include "http_client.h" +#include "private/http_client_internal.h" +#include "http_parser.h" + +/** + * @brief Attains coverage when a variable needs to possibly contain two values. + */ +bool nondet_bool(); + +size_t http_parser_execute( http_parser * parser, + const http_parser_settings * settings, + const char * data, + size_t len ) +{ + char * pValue; + size_t fieldLength, fieldOffset, valueLength, valueOffset; + unsigned int http_errno; + findHeaderContext_t * pParsingContext; + + __CPROVER_assert( parser != NULL, + "http_parser_execute parser is NULL" ); + __CPROVER_assert( settings != NULL, + "http_parser_execute settings is NULL" ); + __CPROVER_assert( data != NULL, + "http_parser_execute data is NULL" ); + __CPROVER_assert( len < CBMC_MAX_OBJECT_SIZE, + "http_parser_execute len >= CBMC_MAX_OBJECT_SIZE" ); + + parser->http_errno = http_errno; + + __CPROVER_assume( fieldLength <= len ); + __CPROVER_assume( fieldOffset < fieldLength ); + __CPROVER_assume( valueLength <= len ); + __CPROVER_assume( valueOffset < valueLength ); + + pParsingContext = ( findHeaderContext_t * ) ( parser->data ); + pParsingContext->pField = data + fieldOffset; + pParsingContext->fieldLen = fieldLength; + pParsingContext->pValueLoc = NULL; + pParsingContext->pValueLen = 0; + pParsingContext->fieldFound = nondet_bool() ? 0 : 1; + + if( pParsingContext->fieldFound ) + { + pParsingContext->valueFound = nondet_bool() ? 0 : 1; + } + else + { + pParsingContext->valueFound = 0; + } + + if( pParsingContext->valueFound ) + { + pValue = data + valueOffset; + pParsingContext->pValueLen = &valueLength; + pParsingContext->pValueLoc = &pValue; + } + + return pParsingContext->fieldFound ? valueLength : 0; +} diff --git a/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c b/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c index aca35ae02d..7ba7bf14e9 100644 --- a/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c +++ b/libraries/standard/http/cbmc/stubs/HTTPClient_Send_http_parser_execute.c @@ -41,8 +41,7 @@ size_t http_parser_execute( http_parser * parser, const char * data, size_t len ) { - size_t fieldLength; - size_t valueLength; + size_t fieldLength, valueLength; HTTPParsingContext_t * pParsingContext; __CPROVER_assert( parser != NULL, diff --git a/libraries/standard/http/cbmc/stubs/callback_stubs.c b/libraries/standard/http/cbmc/stubs/callback_stubs.c new file mode 100644 index 0000000000..ae83db0d17 --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/callback_stubs.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file callback_stubs.c + * @brief Creates a stub for onHeaderCallback. + */ + +#include "callback_stubs.h" + +void onHeaderCallbackStub( void * pContext, + const char * fieldLoc, + size_t fieldLen, + const char * valueLoc, + size_t valueLen, + uint16_t statusCode ) +{ + ( void ) pContext; + __CPROVER_assert( fieldLoc != NULL, "onHeaderCallbackStub fieldLoc is NULL" ); + ( void ) fieldLen; + __CPROVER_assert( valueLoc != NULL, "onHeaderCallbackStub valueLoc is NULL" ); + ( void ) valueLen; + ( void ) statusCode; +} diff --git a/libraries/standard/http/cbmc/stubs/memmove.c b/libraries/standard/http/cbmc/stubs/memmove.c new file mode 100644 index 0000000000..523bedefe9 --- /dev/null +++ b/libraries/standard/http/cbmc/stubs/memmove.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file memmove.c + * @brief Creates a stub for memmove so that the proof for + * httpParserOnBodyCallback is not stuck in the Post-Processing Step. + */ + +#include + +/* This is a clang macro not available on linux */ +#ifndef __has_builtin + #define __has_builtin( x ) 0 +#endif + +#if __has_builtin( __builtin___memmove_chk ) + void * __builtin___memmove_chk( void * dest, + const void * src, + size_t n, + size_t m ) + { + __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + return dest; + } +#else + void * memmove( void * dest, + const void * src, + size_t n ) + { + __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + return dest; + } +#endif /* if __has_builtin( __builtin___memmove_chk ) */ diff --git a/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c b/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c index 262a2cfdd3..b6be43059c 100644 --- a/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c +++ b/libraries/standard/http/cbmc/stubs/transport_interface_stubs.c @@ -33,15 +33,7 @@ int32_t TransportInterfaceSendStub( NetworkContext_t * pNetworkContext, if( tries >= MAX_TRIES ) { tries = 0; - - if( bytesToSend <= INT32_MAX ) - { - ret = bytesToSend; - } - else - { - ret = INT32_MAX; - } + ret = bytesToSend; } return ret; diff --git a/libraries/standard/http/lexicon.txt b/libraries/standard/http/lexicon.txt index 90ce4c6247..1eac462de4 100644 --- a/libraries/standard/http/lexicon.txt +++ b/libraries/standard/http/lexicon.txt @@ -18,6 +18,7 @@ contentlength copybrief coverity datalen +dest doesn endif enums @@ -30,9 +31,12 @@ fieldlentoreturn fieldloc findheaderfieldparsercallback findheaderinresponse +findheaderonheadercompletecallback +findheadervalueparsercallback firstpartbytes getfinalresponsestatus github +headercount headerslen hostlen html @@ -43,6 +47,8 @@ httpparseronbodycallback httpparseronheaderfieldcallback httpparseronheaderscompletecallback httpparseronheadervaluecallback +httpparseronmessagebegincallback +httpparseronmessagecompletecallback httpparseronstatuscallback httpparserxxxxcallback httpparserxxxxcallbacks @@ -63,6 +69,7 @@ linux malloc methodlen memcpy +memmove misra msg networkcontext @@ -92,9 +99,11 @@ pcontext pdata pfield pfieldloc +pfindheadercontext pheaders phost phttpparser +phttpparsingcontext plastheaderfield plastheadervalue ploc @@ -115,6 +124,7 @@ prequestinfo presponse processhttpparsererror ptransport +ptransportinterface pvalue pvaluelen pvalueloc diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index 4ecddd4b4c..f173b405cc 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -237,7 +237,7 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, * @ref findHeaderFieldParserCallback function. * * @param[in] pHttpParser Parsing object containing state and callback context. - * @param[in] pVaLueLoc The location of the parsed header value in the response + * @param[in] pValueLoc The location of the parsed header value in the response * buffer. * @param[in] valueLen The length of the header value. * @@ -245,7 +245,7 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, * found, otherwise #HTTP_PARSING_CONTINUE_PARSING is returned. */ static int findHeaderValueParserCallback( http_parser * pHttpParser, - const char * pVaLueLoc, + const char * pValueLoc, size_t valueLen ); /** @@ -494,6 +494,7 @@ static void processCompleteHeader( HTTPParsingContext_t * pParsingContext ) if( ( pParsingContext->pLastHeaderField != NULL ) && ( pParsingContext->pLastHeaderValue != NULL ) ) { + assert( pResponse->headerCount < SIZE_MAX ); /* Increase the header count. */ pResponse->headerCount++; @@ -639,6 +640,7 @@ static int httpParserOnHeaderFieldCallback( http_parser * pHttpParser, } else { + assert( pParsingContext->lastHeaderFieldLen <= SIZE_MAX - length ); pParsingContext->lastHeaderFieldLen += length; } @@ -742,7 +744,7 @@ static int httpParserOnHeadersCompleteCallback( http_parser * pHttpParser ) /* If the Content-Length header was found, then pHttpParser->content_length * will not be equal to the maximum 64 bit integer. */ - if( pHttpParser->content_length != ( ( uint64_t ) -1 ) ) + if( pHttpParser->content_length != UINT64_MAX ) { pResponse->contentLength = ( size_t ) ( pHttpParser->content_length ); } @@ -805,7 +807,7 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, assert( pResponse != NULL ); assert( pResponse->pBuffer != NULL ); - assert( pLoc > ( const char * ) ( pResponse->pBuffer ) ); + assert( pLoc >= ( const char * ) ( pResponse->pBuffer ) ); assert( pLoc < ( const char * ) ( pResponse->pBuffer + pResponse->bufferLen ) ); /* If this is the first time httpParserOnBodyCallback() has been invoked, @@ -839,7 +841,9 @@ static int httpParserOnBodyCallback( http_parser * pHttpParser, /* coverity[misra_c_2012_rule_18_3_violation] */ if( pLoc > pNextWriteLoc ) { - ( void ) memcpy( pNextWriteLoc, pLoc, length ); + /* memmove is used instead of memcpy because memcpy has undefined behavior + * when source and destination locations in memory overlap. */ + ( void ) memmove( pNextWriteLoc, pLoc, length ); } /* Increase the length of the body found. */ @@ -2062,14 +2066,14 @@ static int findHeaderFieldParserCallback( http_parser * pHttpParser, /*-----------------------------------------------------------*/ static int findHeaderValueParserCallback( http_parser * pHttpParser, - const char * pVaLueLoc, + const char * pValueLoc, size_t valueLen ) { int retCode = HTTP_PARSER_CONTINUE_PARSING; findHeaderContext_t * pContext = NULL; assert( pHttpParser != NULL ); - assert( pVaLueLoc != NULL ); + assert( pValueLoc != NULL ); assert( valueLen > 0u ); pContext = ( findHeaderContext_t * ) pHttpParser->data; @@ -2086,10 +2090,10 @@ static int findHeaderValueParserCallback( http_parser * pHttpParser, { LogDebug( ( "Found header value in response: " "RequestedField=%.*s, ValueLocation=0x%p", - ( int ) ( pContext->fieldLen ), pContext->pField, pVaLueLoc ) ); + ( int ) ( pContext->fieldLen ), pContext->pField, pValueLoc ) ); /* Populate the output parameters with the location of the header value in the response buffer. */ - *pContext->pValueLoc = pVaLueLoc; + *pContext->pValueLoc = pValueLoc; *pContext->pValueLen = valueLen; /* Set the header value found flag. */ From 9bb787af90e97689e210efb9b95f45b073a07df8 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 10 Aug 2020 14:36:05 -0700 Subject: [PATCH 618/844] [PR from CLI tool] Update OpenSSL transport implementation to fix integration test SegFault (#1106) * Update SSL transport implementation to avoid SegFault issues * Updates to error messages based on review feedback * Refactor logging logic for OpenSSL_Recv function to be consistent with OpenSSL_Send * Hygiene update in OpenSSL_Recv and OpenSSL_Send functions to NULL check network context parameter * Hygiene change in OpenSSL_Recv for better readability --- platform/posix/transport/src/openssl_posix.c | 73 ++++++++++++++------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/platform/posix/transport/src/openssl_posix.c b/platform/posix/transport/src/openssl_posix.c index b230337431..fb64c6dc55 100644 --- a/platform/posix/transport/src/openssl_posix.c +++ b/platform/posix/transport/src/openssl_posix.c @@ -581,7 +581,6 @@ OpensslStatus_t Openssl_Disconnect( NetworkContext_t * pNetworkContext ) } SSL_free( pNetworkContext->pSsl ); - pNetworkContext->pSsl = NULL; } else { @@ -607,26 +606,39 @@ int32_t Openssl_Recv( NetworkContext_t * pNetworkContext, int32_t bytesReceived = 0; int sslError = 0; - /* SSL read of data. */ - bytesReceived = ( int32_t ) SSL_read( pNetworkContext->pSsl, - pBuffer, - bytesToRecv ); - - if( bytesReceived <= 0 ) + if( pNetworkContext == NULL ) + { + LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); + } + else if( pNetworkContext->pSsl != NULL ) { - sslError = SSL_get_error( pNetworkContext->pSsl, bytesReceived ); + /* SSL read of data. */ + bytesReceived = ( int32_t ) SSL_read( pNetworkContext->pSsl, + pBuffer, + bytesToRecv ); - if( sslError == SSL_ERROR_WANT_READ ) - { - /* There is no data to receive at this time. */ - bytesReceived = 0; - } - else + /* Handle error return status if transport read did not succeed. */ + if( bytesReceived <= 0 ) { - LogError( ( "SSL_read of OpenSSL failed to receive data: " - "status=%s.", ERR_reason_error_string( sslError ) ) ); + sslError = SSL_get_error( pNetworkContext->pSsl, bytesReceived ); + + if( sslError == SSL_ERROR_WANT_READ ) + { + /* There is no data to receive at this time. */ + bytesReceived = 0; + } + else + { + LogError( ( "Failed to receive data over network: SSL_read failed: " + "ErrorStatus=%s.", ERR_reason_error_string( sslError ) ) ); + } } } + else + { + LogError( ( "Failed to receive data over network: " + "SSL object in network context is NULL." ) ); + } return bytesReceived; } @@ -637,16 +649,31 @@ int32_t Openssl_Send( NetworkContext_t * pNetworkContext, size_t bytesToSend ) { int32_t bytesSent = 0; + int32_t sslError = 0; + + if( pNetworkContext == NULL ) + { + LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); + } + else if( pNetworkContext->pSsl != NULL ) + { + /* SSL write of data. */ + bytesSent = ( int32_t ) SSL_write( pNetworkContext->pSsl, + pBuffer, + bytesToSend ); - /* SSL write of data. */ - bytesSent = ( int32_t ) SSL_write( pNetworkContext->pSsl, - pBuffer, - bytesToSend ); + if( bytesSent <= 0 ) + { + sslError = SSL_get_error( pNetworkContext->pSsl, bytesSent ); - if( bytesSent <= 0 ) + LogError( ( "Failed to send data over network: SSL_write of OpenSSL failed: " + "ErrorStatus=%s.", ERR_reason_error_string( sslError ) ) ); + } + } + else { - LogError( ( "SSL_write of OpenSSL failed to send data: " - " status=%d.", bytesSent ) ); + LogError( ( "Failed to send data over network: " + "SSL object in network context is NULL." ) ); } return bytesSent; From cf0b2e7991571b1714fb00b611cf823ddfdc6b8d Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 11 Aug 2020 11:46:24 -0700 Subject: [PATCH 619/844] Add doxygen comments, remove unused fields (#1112) * Add doxygen comments to structs and enums in mqtt.h * Remove unused publish count parameters in MQTTContext_t --- libraries/standard/mqtt/include/mqtt.h | 113 ++++++++++++++++++------- libraries/standard/mqtt/lexicon.txt | 13 +++ 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 84a5cc5fee..8d1069656e 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -68,71 +68,126 @@ typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, MQTTPacketInfo_t * pPacketInfo, MQTTDeserializedInfo_t * pDeserializedInfo ); +/** + * @brief Values indicating if an MQTT connection exists. + */ typedef enum MQTTConnectionStatus { - MQTTNotConnected, - MQTTConnected + MQTTNotConnected, /**< @brief MQTT Connection is inactive. */ + MQTTConnected /**< @brief MQTT Connection is active. */ } MQTTConnectionStatus_t; +/** + * @brief The state of QoS 1 or QoS 2 MQTT publishes, used in the state engine. + */ typedef enum MQTTPublishState { - MQTTStateNull = 0, - MQTTPublishSend, - MQTTPubAckSend, - MQTTPubRecSend, - MQTTPubRelSend, - MQTTPubCompSend, - MQTTPubAckPending, - MQTTPubRelPending, - MQTTPubRecPending, - MQTTPubCompPending, - MQTTPublishDone + MQTTStateNull = 0, /**< @brief An empty state with no corresponding PUBLISH. */ + MQTTPublishSend, /**< @brief The library will send an outgoing PUBLISH packet. */ + MQTTPubAckSend, /**< @brief The library will send a PUBACK for a received PUBLISH. */ + MQTTPubRecSend, /**< @brief The library will send a PUBREC for a received PUBLISH. */ + MQTTPubRelSend, /**< @brief The library will send a PUBREL for a received PUBREC. */ + MQTTPubCompSend, /**< @brief The library will send a PUBCOMP for a received PUBREL. */ + MQTTPubAckPending, /**< @brief The library is awaiting a PUBACK for an outgoing PUBLISH. */ + MQTTPubRecPending, /**< @brief The library is awaiting a PUBREC for an outgoing PUBLISH. */ + MQTTPubRelPending, /**< @brief The library is awaiting a PUBREL for an incoming PUBLISH. */ + MQTTPubCompPending, /**< @brief The library is awaiting a PUBCOMP for an outgoing PUBLISH. */ + MQTTPublishDone /**< @brief The PUBLISH has been completed. */ } MQTTPublishState_t; +/** + * @brief Packet types used in acknowledging QoS 1 or QoS 2 publishes. + */ typedef enum MQTTPubAckType { - MQTTPuback, - MQTTPubrec, - MQTTPubrel, - MQTTPubcomp + MQTTPuback, /**< @brief PUBACKs are sent in response to a QoS 1 PUBLISH. */ + MQTTPubrec, /**< @brief PUBRECs are sent in response to a QoS 2 PUBLISH. */ + MQTTPubrel, /**< @brief PUBRELs are sent in response to a PUBREC. */ + MQTTPubcomp /**< @brief PUBCOMPs are sent in response to a PUBREL. */ } MQTTPubAckType_t; +/** + * @brief An element of the state engine records for QoS 1/2 publishes. + */ struct MQTTPubAckInfo { - uint16_t packetId; - MQTTQoS_t qos; - MQTTPublishState_t publishState; + uint16_t packetId; /**< @brief The packet ID of the original PUBLISH. */ + MQTTQoS_t qos; /**< @brief The QoS of the original PUBLISH. */ + MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */ }; +/** + * @brief A struct representing an MQTT connection. + */ struct MQTTContext { + /** + * @brief State engine records for outgoing publishes. + */ MQTTPubAckInfo_t outgoingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT ]; - size_t outgoingPublishCount; + + /** + * @brief State engine records for incoming publishes. + */ MQTTPubAckInfo_t incomingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT ]; - size_t incomingPublishCount; + /** + * @brief The transport interface used by the MQTT connection. + */ TransportInterface_t transportInterface; + + /** + * @brief The buffer used in sending and receiving packets from the network. + */ MQTTFixedBuffer_t networkBuffer; + /** + * @brief The next available ID for outgoing MQTT packets. + */ uint16_t nextPacketId; + + /** + * @brief Whether the context currently has a connection to the broker. + */ MQTTConnectionStatus_t connectStatus; + + /** + * @brief Function used to get millisecond timestamps. + */ MQTTGetCurrentTimeFunc_t getTime; + + /** + * @brief Callback function used to give deserialized MQTT packets to the application. + */ MQTTEventCallback_t appCallback; + + /** + * @brief Timestamp of the last packet sent by the library. + */ uint32_t lastPacketTime; + + /** + * @brief Whether the library sent a packet during a call of #MQTT_ProcessLoop or + * #MQTT_ReceiveLoop. + */ bool controlPacketSent; /* Keep alive members. */ - uint16_t keepAliveIntervalSec; - uint32_t pingReqSendTimeMs; - uint32_t pingRespTimeoutMs; - bool waitingForPingResp; + uint16_t keepAliveIntervalSec; /**< @brief Keep Alive interval. */ + uint32_t pingReqSendTimeMs; /**< @brief Timestamp of the last sent PINGREQ. */ + uint32_t pingRespTimeoutMs; /**< @brief Timeout for waiting for a PINGRESP. */ + bool waitingForPingResp; /**< @brief If the library is currently awaiting a PINGRESP. */ }; +/** + * @brief Struct to hold deserialized packet information for an #MQTTEventCallback_t + * callback. + */ struct MQTTDeserializedInfo { - uint16_t packetIdentifier; - MQTTPublishInfo_t * pPublishInfo; - MQTTStatus_t deserializationResult; + uint16_t packetIdentifier; /**< @brief Packet ID of deserialized packet. */ + MQTTPublishInfo_t * pPublishInfo; /**< @brief Pointer to deserialized publish info. */ + MQTTStatus_t deserializationResult; /**< @brief Return code of deserialization. */ }; /** diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 7c237863eb..0a17f47e80 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -33,6 +33,7 @@ createcleansession currentstate defragmenting deserialization +deserializationresult deserialize deserializeack deserialized @@ -74,6 +75,7 @@ int iot isduplicate isn +keepaliveintervalsec linux lsb lwt @@ -88,6 +90,7 @@ mosquitto mqtt mqttbadparameter mqttbadresponse +mqttconnected mqttconnectinfo mqttcontext mqttfixedbuffer @@ -95,17 +98,22 @@ mqttillegalstate mqttkeepalivetimeout mqttnodataavailable mqttnomemory +mqttnotconnected mqttpacketinfo +mqttpuback mqttpubackpending mqttpubacksend mqttpubacktype +mqttpubcomp mqttpubcomppending mqttpubcompsend mqttpublishdone mqttpublishinfo mqttpublishsend +mqttpubrec mqttpubrecpending mqttpubrecsend +mqttpubrel mqttpubrelpending mqttpubrelsend mqttqos @@ -147,6 +155,7 @@ pfixedbuffer pheadersize pincomingpacket pingreq +pingreqsendtimems pingresp pingresps pingresptimeoutms @@ -175,9 +184,12 @@ ptr ptransport ptransportinterface puback +pubacks pubcomp +pubcomps publishstate pubrec +pubrecs pubrel pubrels pwillinfo @@ -239,6 +251,7 @@ usercallback utest utf validatesubscribeunsubscribeparams +waitingforpingresp xa xb xc From 9658ac619e3cfda36fa81e6e0d232df0b351b47a Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 11 Aug 2020 11:51:24 -0700 Subject: [PATCH 620/844] Add subscription manager for MQTT (#1094) --- demos/lexicon.txt | 10 + .../mqtt/subscription-manager/CMakeLists.txt | 17 + .../mqtt_subscription_manager.c | 491 ++++++++++++++++++ .../mqtt_subscription_manager.h | 144 +++++ 4 files changed, 662 insertions(+) create mode 100644 demos/mqtt/subscription-manager/CMakeLists.txt create mode 100644 demos/mqtt/subscription-manager/mqtt_subscription_manager.c create mode 100644 demos/mqtt/subscription-manager/mqtt_subscription_manager.h diff --git a/demos/lexicon.txt b/demos/lexicon.txt index f68fd30d6c..f681c9de6e 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -31,6 +31,7 @@ developerguide doesn dup endif +filterindex hashmap hasn html @@ -48,6 +49,7 @@ methodlen milli mosquitto mqtt +nameindex noninfringement openssl org @@ -58,6 +60,7 @@ packetidtoresend param pathlen pbuffer +pcontext pdeserializedinfo pem pfixedbuffer @@ -66,8 +69,10 @@ pindex pingreq pingresp plaintext +pmatch pmethod pmqttcontext +pnameindex pnetworkcontext posix ppacketinfo @@ -76,6 +81,8 @@ ppubinfo ppublishinfo processloop psessionpresent +ptopicfilter +ptopicname ptransportinterface puback pubcomp @@ -91,9 +98,12 @@ structs suback sublicense subscribepublishloop +sys tcp tcpsocket tls +topicfilterlength +topicnamelength transportinterface transporttimeout uint diff --git a/demos/mqtt/subscription-manager/CMakeLists.txt b/demos/mqtt/subscription-manager/CMakeLists.txt new file mode 100644 index 0000000000..d7914fbd43 --- /dev/null +++ b/demos/mqtt/subscription-manager/CMakeLists.txt @@ -0,0 +1,17 @@ +set( LIBRARY_NAME "mqtt_subscription_manager" ) +# Library target. +add_library( ${LIBRARY_NAME} + "${LIBRARY_NAME}.c" ) + +target_link_libraries( + ${LIBRARY_NAME} + PRIVATE + mqtt +) + +target_include_directories( + ${LIBRARY_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} +) + diff --git a/demos/mqtt/subscription-manager/mqtt_subscription_manager.c b/demos/mqtt/subscription-manager/mqtt_subscription_manager.c new file mode 100644 index 0000000000..d9b321230a --- /dev/null +++ b/demos/mqtt/subscription-manager/mqtt_subscription_manager.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file mqtt_subscription_manager.c + * @brief Implementation of the API of a subscription manager for handling subscription callbacks + * to topic filters in MQTT operations. + */ + +/* Standard includes. */ +#include +#include + +/* Include header for the subscription manager. */ +#include "mqtt_subscription_manager.h" + +/** + * @brief Represents a registered record of the topic filter and its associated callback + * in the subscription manager registry. + */ +typedef struct SubscriptionManagerRecord +{ + const char * pTopicFilter; + uint16_t topicFilterLength; + SubscriptionManagerCallback_t callback; +} SubscriptionManagerRecord_t; + +/** + * @brief The default value for the maximum size of the callback registry in the + * subscription manager. + */ +#ifndef MAX_SUBSCRIPTION_CALLBACK_RECORDS + #define MAX_SUBSCRIPTION_CALLBACK_RECORDS 5 +#endif + +/** + * @brief The registry to store records of topic filters and their subscription callbacks. + */ +static SubscriptionManagerRecord_t callbackRecordList[ MAX_SUBSCRIPTION_CALLBACK_RECORDS ] = { 0 }; + +/** + * @brief Handle special corner cases for wildcards at the end of topic + * filters, as documented by the MQTT protocol spec. + * + * It concludes a match between the topic name and topic filter for the + * following special cases: + * - When the topic filter ends with '+' character, but the topic name only + * ends with '/'. + * - When the topic filter ends with "/#" characters, but the topic name + * ends at the parent level. + * + * @param[in] pTopicFilter The topic filter containing the wildcard. + * @param[in] topicFilterLength Length of the topic filter being examined. + * @param[in] topicNameLength Length of the topic name being examined. + * @param[in] nameIndex Index of the topic name being examined. + * @param[in] filterIndex Index of the topic filter being examined. + * + * @return Returns whether the topic filter and the topic name match. + */ +static bool matchWildcardsSpecialCases( const char * pTopicFilter, + uint16_t topicFilterLength, + uint16_t topicNameLength, + uint16_t nameIndex, + uint16_t filterIndex ); + +/** + * @brief Attempt to match topic name with a topic filter starting with a wildcard. + * + * If the topic filter starts with a '+' (single-level) wildcard, the function + * advances the @a pNameIndex by a level in the topic name. + * If the topic filter starts with a '#' (multi-level) wildcard, the function + * concludes that both the topic name and topic filter match. + * + * @param[in] pTopicFilter The topic filter containing the wildcard. + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of the topic name. + * @param[in] filterIndex Index of the wildcard in the topic filter. + * @param[in,out] pNameIndex Index of character in topic name. This variable is + * advanced for `+` wildcards. + * @param[out] pMatch Whether the topic filter and topic name match. + * + * @return `true` if the caller of this function should exit; `false` if the caller + * should continue parsing the topics. + */ +static bool matchWildcards( const char * pTopicFilter, + const char * pTopicName, + uint16_t topicNameLength, + uint16_t filterIndex, + uint16_t * pNameIndex, + bool * pMatch ); + +/** + * @brief Match a topic name and topic filter allowing the use of wildcards. + * + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of the topic name. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of topic filter. + * + * @return `true` if the topic name and topic filter match; `false` otherwise. + */ +static bool matchTopicFilter( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength ); + +/** + * @brief Matches a topic name (from a incoming PUBLISH) with a topic filter. + * + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of the topic name. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of topic filter. + * + * @return `true` if the topic name and topic filter match; `false` + * otherwise. + */ +static bool matchTopic( const char * pTopicName, + const uint16_t topicNameLength, + const char * pTopicFilter, + const uint16_t topicFilterLength ); + +/*-----------------------------------------------------------*/ + +static bool matchWildcardsSpecialCases( const char * pTopicFilter, + uint16_t topicFilterLength, + uint16_t topicNameLength, + uint16_t nameIndex, + uint16_t filterIndex ) +{ + bool matchFound = false; + + /* Determine if the last character is reached for the topic name, and the + * third to last character is reached for the topic filter. */ + if( ( nameIndex == ( topicNameLength - 1U ) ) && + ( filterIndex == ( topicFilterLength - 3U ) ) ) + { + /* Determine if the topic filter contains "/#" as the last 2 characters. + * The '#' wildcard represents the parent and any number of child levels + * in the topic name. For example, the filter "sport/#" matches "sport" + * as well as "sport/tennis" topics. */ + matchFound = ( ( pTopicFilter[ filterIndex + 1U ] == '/' ) && + ( pTopicFilter[ filterIndex + 2U ] == '#' ) ) ? true : false; + } + else + { + /* Determine if the last character is reached for the topic name and, + * the second to last character for the topic filter. */ + if( ( nameIndex == ( topicNameLength - 1U ) ) && + ( filterIndex == ( topicFilterLength - 2U ) ) ) + { + /* Determine if the topic filter contains "+" as the last character. + * This covers the special case of topic matching when the topic name + * ends with '/' but the topic filter ends with "/+". Thus, for example, + * topic filter "sport/+" matches the "sport/" but not "sport". */ + matchFound = ( pTopicFilter[ filterIndex + 1U ] == '+' ) ? true : false; + } + } + + return matchFound; +} + +/*-----------------------------------------------------------*/ + +static bool matchWildcards( const char * pTopicFilter, + const char * pTopicName, + uint16_t topicNameLength, + uint16_t filterIndex, + uint16_t * pNameIndex, + bool * pMatch ) +{ + bool shouldStopMatching = false; + + /* Check for wildcards. */ + if( pTopicFilter[ filterIndex ] == '+' ) + { + /* Move topic name index to the end of the current level. + * This is identified by '/'. */ + while( ( *pNameIndex < topicNameLength ) && ( pTopicName[ *pNameIndex ] != '/' ) ) + { + ( *pNameIndex )++; + } + + /* Decrement the topic name index for 2 different cases: + * - If the break condition is ( *pNameIndex < topicNameLength ), then + * we have reached the end of the topic name. + * - If the break condition is ( pTopicName[ *pNameIndex ] != '/' ), + * we move back the index on the '/' character to be at the last + * position in the current topic level. */ + ( *pNameIndex )--; + } + else if( pTopicFilter[ filterIndex ] == '#' ) + { + /* Subsequent characters don't need to be checked for the + * multi-level wildcard. */ + *pMatch = true; + shouldStopMatching = true; + } + else + { + /* Any character mismatch other than '+' or '#' means the topic + * name does not match the topic filter. */ + *pMatch = false; + shouldStopMatching = true; + } + + return shouldStopMatching; +} + +/*-----------------------------------------------------------*/ + +static bool matchTopicFilter( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + bool matchFound = false, shouldStopMatching = false; + uint16_t nameIndex = 0, filterIndex = 0; + + assert( pTopicName != NULL ); + assert( topicNameLength != 0 ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + + while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) + { + /* Check if the character in the topic name matches the corresponding + * character in the topic filter string. */ + if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) + { + /* Handle special corner cases regarding wildcards at the end of + * topic filters, as documented by the MQTT protocol spec. */ + matchFound = matchWildcardsSpecialCases( pTopicFilter, + topicFilterLength, + topicNameLength, + nameIndex, + filterIndex ); + } + else + { + /* Check for matching wildcards. */ + shouldStopMatching = matchWildcards( pTopicFilter, + pTopicName, + topicNameLength, + filterIndex, + &nameIndex, + &matchFound ); + } + + if( ( matchFound == true ) || ( shouldStopMatching == true ) ) + { + break; + } + + /* Increment indexes. */ + nameIndex++; + filterIndex++; + } + + if( matchFound == false ) + { + /* If the end of both strings has been reached, they match. This represents the + * case when the topic filter contains the '+' wildcard at a non-starting position. + * For example, when matching either of "sport/+/player" OR "sport/hockey/+" topic + * filters with "sport/hockey/player" topic name. */ + matchFound = ( ( nameIndex == topicNameLength ) && + ( filterIndex == topicFilterLength ) ) ? true : false; + } + + return matchFound; +} + +/*-----------------------------------------------------------*/ + +static bool matchTopic( const char * pTopicName, + const uint16_t topicNameLength, + const char * pTopicFilter, + const uint16_t topicFilterLength ) +{ + assert( pTopicName != NULL ); + assert( topicNameLength != 0 ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + + bool status = false; + bool topicFilterStartsWithWildcard = false; + + /* Check for an exact match if the incoming topic name and the registered + * topic filter length match. */ + if( topicNameLength == topicFilterLength ) + { + status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ) ? true : false; + } + + if( status == false ) + { + /* If an exact match was not found, match against wildcard characters in + * topic filter.*/ + + /* Determine if topic filter starts with a wildcard. */ + topicFilterStartsWithWildcard = ( ( pTopicFilter[ 0 ] == '+' ) || + ( pTopicFilter[ 0 ] == '#' ) ) ? true : false; + + /* Note: According to the MQTT 3.1.1 specification, incoming PUBLISH topic names + * starting the "$" character cannot be matched against topic filter starting with + * a wildcard, i.e. for example, "$SYS/sport" cannot be matched with "#" or + * "+/sport" topic filters. */ + if( !( ( pTopicName[ 0 ] == '$' ) && ( topicFilterStartsWithWildcard == true ) ) ) + { + status = matchTopicFilter( pTopicName, topicNameLength, pTopicFilter, topicFilterLength ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/* Design - + * * Common handler can contain logic of processing acks and retries. + * * Only forward PUBLISH message to message dispatcher/handler as individual callbacks + * only want to process incoming PUBLISH message. + */ +void SubscriptionManager_DispatchHandler( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pPublishInfo != NULL ); + assert( pContext != NULL ); + + size_t listIndex = 0u; + + /* Iterate through record list to find matching topics, and invoke their callbacks. */ + for( listIndex = 0; listIndex < MAX_SUBSCRIPTION_CALLBACK_RECORDS; listIndex++ ) + { + if( ( callbackRecordList[ listIndex ].pTopicFilter != NULL ) && + ( matchTopic( pPublishInfo->pTopicName, + pPublishInfo->topicNameLength, + callbackRecordList[ listIndex ].pTopicFilter, + callbackRecordList[ listIndex ].topicFilterLength ) == true ) ) + { + LogInfo( ( "Invoking subscription callback of matching topic filter: " + "TopicFilter=%.*s, TopicName=%.*s", + callbackRecordList[ listIndex ].topicFilterLength, + callbackRecordList[ listIndex ].pTopicFilter, + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + + /* Invoke the callback associated with the record as the topics match. */ + callbackRecordList[ listIndex ].callback( pContext, pPublishInfo ); + } + } +} + +/*-----------------------------------------------------------*/ + +SubscriptionManagerStatus_t SubscriptionManager_RegisterCallback( const char * pTopicFilter, + uint16_t topicFilterLength, + SubscriptionManagerCallback_t callback ) +{ + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + assert( callback != NULL ); + + SubscriptionManagerStatus_t returnStatus = SUBSCRIPTION_MANAGER_INVALID; + size_t availableIndex = MAX_SUBSCRIPTION_CALLBACK_RECORDS; + bool recordExists = false; + size_t index = 0u; + + /* Search for the first available spot in the list to store the record, and also check if + * a record for the topic filter already exists. */ + while( ( recordExists == false ) && ( index < MAX_SUBSCRIPTION_CALLBACK_RECORDS ) ) + { + /* Check if the index represents an empty spot in the registry. If we had already + * found an empty spot in the list, we will not update it. */ + if( ( availableIndex == MAX_SUBSCRIPTION_CALLBACK_RECORDS ) && + ( callbackRecordList[ index ].pTopicFilter == NULL ) ) + { + availableIndex = index; + } + + /* Check if the current record's topic filter in the registry matches the topic filter + * we are trying to register. */ + else if( ( callbackRecordList[ index ].topicFilterLength == topicFilterLength ) && + ( strncmp( pTopicFilter, callbackRecordList[ index ].pTopicFilter, topicFilterLength ) + == 0 ) ) + { + recordExists = true; + } + + index++; + } + + if( recordExists == true ) + { + /* The record for the topic filter already exists. */ + LogError( ( "Failed to register callback: Record for topic filter already exists: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + + returnStatus = SUBSCRIPTION_MANAGER_RECORD_EXISTS; + } + else if( availableIndex == MAX_SUBSCRIPTION_CALLBACK_RECORDS ) + { + /* The registry is full. */ + LogError( ( "Unable to register callback: Registry list is full: TopicFilter=%.*s, MaxRegistrySize=%u", + topicFilterLength, + pTopicFilter, + MAX_SUBSCRIPTION_CALLBACK_RECORDS ) ); + + returnStatus = SUBSCRIPTION_MANAGER_REGISTRY_FULL; + } + else + { + callbackRecordList[ availableIndex ].pTopicFilter = pTopicFilter; + callbackRecordList[ availableIndex ].topicFilterLength = topicFilterLength; + callbackRecordList[ availableIndex ].callback = callback; + + returnStatus = SUBSCRIPTION_MANAGER_SUCCESS; + + LogDebug( ( "Added callback to registry: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +void SubscriptionManager_RemoveCallback( const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + + size_t index; + SubscriptionManagerRecord_t * pRecord = NULL; + + /* Iterate through the records list to find the matching record. */ + for( index = 0; index < MAX_SUBSCRIPTION_CALLBACK_RECORDS; index++ ) + { + pRecord = &callbackRecordList[ index ]; + + /* Only match the non-empty records. */ + if( pRecord->pTopicFilter != NULL ) + { + if( ( topicFilterLength == pRecord->topicFilterLength ) && + ( strncmp( pTopicFilter, pRecord->pTopicFilter, topicFilterLength ) == 0 ) ) + { + break; + } + } + } + + /* Delete the record by clearing the found entry in the records list. */ + if( index < MAX_SUBSCRIPTION_CALLBACK_RECORDS ) + { + pRecord->pTopicFilter = NULL; + pRecord->topicFilterLength = 0u; + pRecord->callback = NULL; + + LogDebug( ( "Deleted callback record for topic filter: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + } + else + { + LogWarn( ( "Attempted to remove callback for un-registered topic filter: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + } +} +/*-----------------------------------------------------------*/ diff --git a/demos/mqtt/subscription-manager/mqtt_subscription_manager.h b/demos/mqtt/subscription-manager/mqtt_subscription_manager.h new file mode 100644 index 0000000000..a9664e95b0 --- /dev/null +++ b/demos/mqtt/subscription-manager/mqtt_subscription_manager.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file mqtt_subscription_manager.h + * @brief The API of a subscription manager for handling subscription callbacks + * to topic filters in MQTT operations. + */ + +#ifndef MQTT_SUBSCRIPTION_MANAGER_H_ +#define MQTT_SUBSCRIPTION_MANAGER_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Subscription Manager module. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "Subscription Manager" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_DEBUG +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* Include MQTT library. */ +#include "mqtt.h" + +/* Enumeration type for return status value from Subscription Manager API. */ +typedef enum SubscriptionManagerStatus +{ + SUBSCRIPTION_MANAGER_INVALID = 0, + + /** + * @brief Success return value from Subscription Manager API. + */ + SUBSCRIPTION_MANAGER_SUCCESS = 1, + + /** + * @brief Failure return value due to registry being full. + */ + SUBSCRIPTION_MANAGER_REGISTRY_FULL = 2, + + /** + * @brief Failure return value due to an already existing record in the + * registry for a new callback registration's requested topic filter. + */ + SUBSCRIPTION_MANAGER_RECORD_EXISTS = 3 +} SubscriptionManagerStatus_t; + + +/** + * @brief Callback type to be registered for a topic filter with the subscription manager. + * + * For incoming PUBLISH messages received on topics that match the registered topic filter, + * the callback would be invoked by the subscription manager. + * + * @param[in] pContext The context associated with the MQTT connection. + * @param[in] pPublishInfo The incoming PUBLISH message information. + */ +typedef void (* SubscriptionManagerCallback_t )( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Dispatches the incoming PUBLISH message to the callbacks that have their + * registered topic filters matching the incoming PUBLISH topic name. The dispatch + * handler will invoke all these callbacks with matching topic filters. + * + * @param[in] pContext The context associated with the MQTT connection. + * @param[in] pPublishInfo The incoming PUBLISH message information. + */ +void SubscriptionManager_DispatchHandler( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Utility to register a callback for a topic filter in the subscription manager. + * + * The callback will be invoked when an incoming PUBLISH message is received on + * a topic that matches the topic filter, @a pTopicFilter. The subscription manager + * accepts wildcard topic filters. + * + * @param[in] pTopicFilter The topic filter to register the callback for. + * @param[in] topicFilterLength The length of the topic filter string. + * @param[in] callback The callback to be registered for the topic filter. + * + * @note The subscription manager does not allow more than one callback to be registered + * for the same topic filter. + * @note The passed topic filter, @a pTopicFilter, is saved in the registry. + * The application must not free or alter the content of the topic filter memory + * until the callback for the topic filter is removed from the subscription manager. + * + * @return Returns one of the following: + * - #SUBSCRIPTION_MANAGER_SUCCESS if registration of the callback is successful. + * - #SUBSCRIPTION_MANAGER_REGISTRY_FULL if the registration failed due to registry + * being already full. + * - #SUBSCRIPTION_MANAGER_RECORD_EXISTS, if a registered callback already exists for + * the requested topic filter in the subscription manager. + */ +SubscriptionManagerStatus_t SubscriptionManager_RegisterCallback( const char * pTopicFilter, + uint16_t topicFilterLength, + SubscriptionManagerCallback_t pCallback ); + +/** + * @brief Utility to remove the callback registered for a topic filter from the + * subscription manager. + * + * @param[in] pTopicFilter The topic filter to remove from the subscription manager. + * @param[in] topicFilterLength The length of the topic filter string. + */ +void SubscriptionManager_RemoveCallback( const char * pTopicFilter, + uint16_t topicFilterLength ); + + +#endif /* ifndef MQTT_SUBSCRIPTION_MANAGER_H_ */ From c23c0f8fd5df52aa9509c94fcf985618d4a5e323 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 11 Aug 2020 13:21:48 -0700 Subject: [PATCH 621/844] Add metrics to the MQTT TLS Mutual Auth demo. (#1110) The MQTT metrics report to AWS IoT the SDK/OS version, SDK/OS version and the Hardware Platform. This allows AWS IoT to improve security and provide better technical support. --- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 1 + .../mqtt/mqtt_demo_mutual_auth/demo_config.h | 21 ++++++++++-- .../mqtt_demo_mutual_auth.c | 34 +++++++++++++++++-- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 351f54d2d1..af35cbfea3 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -514,6 +514,7 @@ static int handlePublishResend( MQTTContext_t * pMqttContext ) while( packetIdToResend != MQTT_PACKET_ID_INVALID ) { foundPacketId = false; + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) { if( outgoingPublishPackets[ index ].packetId == packetIdToResend ) diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index fc6db44fe6..7ab3a16205 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -115,12 +115,29 @@ * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#define CLIENT_IDENTIFIER "testclient" /** * @brief Size of the network buffer for MQTT packets. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define NETWORK_BUFFER_SIZE ( 1024U ) + +/** + * @brief The name of the operating system or SDK that the application is + * running a part of. + */ +#define SDK_NAME "aws-iot-device-sdk-embedded-C" + +/** + * @brief The version of the operating system or SDK that the application is + * running a part of. + */ +#define SDK_VERSION "4.0.0" + +/** + * @brief The name of the hardware platform the application is running on. + */ +#define HARDWARE_PLATFORM_NAME "Posix" #endif /* ifndef DEMO_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 3554aa9bed..2f1db3a97e 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -93,6 +93,18 @@ #define NETWORK_BUFFER_SIZE ( 1024U ) #endif +#ifndef SDK_NAME + #define SDK_NAME "aws-iot-device-sdk-embedded-C" +#endif + +#ifndef SDK_VERSION + #define SDK_VERSION "4.0.0" +#endif + +#ifndef HARDWARE_PLATFORM_NAME + #define HARDWARE_PLATFORM_NAME "Posix" +#endif + /** * @brief Length of MQTT server host name. */ @@ -194,6 +206,16 @@ */ #define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 100 ) +/** + * @brief The MQTT metrics string expected by AWS IoT. + */ +#define METRICS_STRING "?SDK=" SDK_NAME "&Version=" SDK_VERSION "&Platform=" HARDWARE_PLATFORM_NAME + +/** + * @brief The length of the MQTT metrics string expected by AWS IoT. + */ +#define METRICS_STRING_LENGTH ( ( uint16_t ) ( sizeof( METRICS_STRING ) - 1 ) ) + /*-----------------------------------------------------------*/ /** @@ -561,6 +583,7 @@ static int handlePublishResend( MQTTContext_t * pMqttContext ) while( packetIdToResend != MQTT_PACKET_ID_INVALID ) { foundPacketId = false; + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) { if( outgoingPublishPackets[ index ].packetId == packetIdToResend ) @@ -772,9 +795,14 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, * PINGREQ Packet. */ connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; - /* Username and password for authentication. Not used in this demo. */ - connectInfo.pUserName = NULL; - connectInfo.userNameLength = 0U; + /* The username field is populated with voluntary metrics to AWS IoT. + * The metrics collected by AWS IoT are the current operating system or + * SDK and its version. These metrics help AWS IoT improve security and + * provide better technical support. */ + connectInfo.pUserName = METRICS_STRING; + connectInfo.userNameLength = METRICS_STRING_LENGTH; + + /* Password for authentication is not used in this demo. */ connectInfo.pPassword = NULL; connectInfo.passwordLength = 0U; From 54090fb8c46e6272d0e7689aab05d81624bb7a85 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 11 Aug 2020 14:34:42 -0700 Subject: [PATCH 622/844] Remove unused enum value from subscription manager (#1114) --- demos/mqtt/subscription-manager/mqtt_subscription_manager.c | 2 +- demos/mqtt/subscription-manager/mqtt_subscription_manager.h | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/demos/mqtt/subscription-manager/mqtt_subscription_manager.c b/demos/mqtt/subscription-manager/mqtt_subscription_manager.c index d9b321230a..b367910199 100644 --- a/demos/mqtt/subscription-manager/mqtt_subscription_manager.c +++ b/demos/mqtt/subscription-manager/mqtt_subscription_manager.c @@ -379,7 +379,7 @@ SubscriptionManagerStatus_t SubscriptionManager_RegisterCallback( const char * p assert( topicFilterLength != 0 ); assert( callback != NULL ); - SubscriptionManagerStatus_t returnStatus = SUBSCRIPTION_MANAGER_INVALID; + SubscriptionManagerStatus_t returnStatus; size_t availableIndex = MAX_SUBSCRIPTION_CALLBACK_RECORDS; bool recordExists = false; size_t index = 0u; diff --git a/demos/mqtt/subscription-manager/mqtt_subscription_manager.h b/demos/mqtt/subscription-manager/mqtt_subscription_manager.h index a9664e95b0..09b0c14b5c 100644 --- a/demos/mqtt/subscription-manager/mqtt_subscription_manager.h +++ b/demos/mqtt/subscription-manager/mqtt_subscription_manager.h @@ -59,8 +59,6 @@ /* Enumeration type for return status value from Subscription Manager API. */ typedef enum SubscriptionManagerStatus { - SUBSCRIPTION_MANAGER_INVALID = 0, - /** * @brief Success return value from Subscription Manager API. */ From 234de33254a8b3c6b23d96afc03e7932975cc86c Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 12 Aug 2020 14:27:06 -0700 Subject: [PATCH 623/844] Address unused parameter and unused variable warnings (#1117) --- .../mqtt/utest/mqtt_lightweight_utest.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 0d2f973338..6f00cbeaa1 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -182,6 +182,11 @@ static int32_t mockReceiveNoData( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { + /* Suppress unused parameter warning. */ + ( void ) pNetworkContext; + ( void ) pBuffer; + ( void ) bytesToRecv; + return 0; } @@ -192,6 +197,11 @@ static int32_t mockReceiveFailure( NetworkContext_t * pNetworkContext, void * pBuffer, size_t bytesToRecv ) { + /* Suppress unused parameter warning. */ + ( void ) pNetworkContext; + ( void ) pBuffer; + ( void ) bytesToRecv; + return -1; } @@ -736,7 +746,6 @@ void test_MQTT_SerializeSubscribe( void ) MQTTSubscribeInfo_t subscriptionList; size_t subscriptionCount = 1; size_t remainingLength = 0; - uint16_t packetIdentifier; uint8_t buffer[ 25 + 2 * BUFFER_PADDING_LENGTH ]; size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; size_t packetSize = bufferSize; @@ -845,7 +854,6 @@ void test_MQTT_SerializeUnsubscribe( void ) MQTTSubscribeInfo_t subscriptionList; size_t subscriptionCount = 1; size_t remainingLength = 0; - uint16_t packetIdentifier; uint8_t buffer[ 25 + 2 * BUFFER_PADDING_LENGTH ]; size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; size_t packetSize = bufferSize; @@ -1011,8 +1019,6 @@ void test_MQTT_SerializePublish( void ) { MQTTPublishInfo_t publishInfo; size_t remainingLength = 98; - uint16_t packetIdentifier; - uint8_t * pPacketIdentifierHigh; uint8_t buffer[ 200 + 2 * BUFFER_PADDING_LENGTH ]; size_t bufferSize = sizeof( buffer ) - 2 * BUFFER_PADDING_LENGTH; size_t packetSize = bufferSize; @@ -1525,7 +1531,6 @@ void test_MQTT_DeserializePublish( void ) size_t remainingLength = 0; uint16_t packetIdentifier; - uint8_t * pPacketIdentifierHigh; fixedBuffer.pBuffer = buffer; fixedBuffer.size = bufferSize; @@ -1761,8 +1766,6 @@ void test_MQTT_SerializePublishHeader( void ) { MQTTPublishInfo_t publishInfo; size_t remainingLength = 0; - uint16_t packetIdentifier; - uint8_t * pPacketIdentifierHigh; uint8_t buffer[ 200 + 2 * BUFFER_PADDING_LENGTH ]; uint8_t expectedPacket[ 200 ]; uint8_t * pIterator; @@ -2324,7 +2327,6 @@ void test_MQTT_SerializeDisconnect_Invalid_Params() void test_MQTT_SerializeDisconnect_Happy_Path() { MQTTStatus_t mqttStatus = MQTTSuccess; - size_t packetSize = 0; MQTTFixedBuffer_t networkBuffer; /* Fill structs to pass into methods to be tested. */ From a5894f869bf58f1e9068bf86b8c8acd75a229ed4 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 12 Aug 2020 14:31:42 -0700 Subject: [PATCH 624/844] Fix build warnings from logging pointers in MQTT library (#1109) --- libraries/standard/mqtt/src/mqtt.c | 45 ++++++++++------- .../standard/mqtt/src/mqtt_lightweight.c | 48 +++++++++---------- libraries/standard/mqtt/src/mqtt_state.c | 20 ++++---- 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 8a932e1689..c0abf0c03d 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1055,8 +1055,8 @@ static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pC { LogError( ( "Argument cannot be NULL: pContext=%p, " "pSubscriptionList=%p.", - pContext, - pSubscriptionList ) ); + ( void * ) pContext, + ( void * ) pSubscriptionList ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0UL ) @@ -1334,8 +1334,8 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, { LogError( ( "Argument cannot be NULL: pContext=%p, " "pPublishInfo=%p.", - pContext, - pPublishInfo ) ); + ( void * ) pContext, + ( void * ) pPublishInfo ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) ) @@ -1377,20 +1377,29 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, LogError( ( "Argument cannot be NULL: pContext=%p, " "pTransportInterface=%p, " "pNetworkBuffer=%p", - pContext, - pTransportInterface, - pNetworkBuffer ) ); + ( void * ) pContext, + ( void * ) pTransportInterface, + ( void * ) pNetworkBuffer ) ); status = MQTTBadParameter; } - else if( ( getTimeFunction == NULL ) || ( userCallback == NULL ) || - ( pTransportInterface->recv == NULL ) || ( pTransportInterface->send == NULL ) ) + else if( getTimeFunction == NULL ) { - LogError( ( "Function pointers cannot be NULL: getTimeFunction=%p, userCallback=%p, " - "transportRecv=%p, transportRecvSend=%p", - getTimeFunction, - userCallback, - pTransportInterface->recv, - pTransportInterface->send ) ); + LogError( ( "Invalid parameter: getTimeFunction is NULL" ) ); + status = MQTTBadParameter; + } + else if( userCallback == NULL ) + { + LogError( ( "Invalid parameter: userCallback is NULL" ) ); + status = MQTTBadParameter; + } + else if( pTransportInterface->recv == NULL ) + { + LogError( ( "Invalid parameter: pTransportInterface->recv is NULL" ) ); + status = MQTTBadParameter; + } + else if( pTransportInterface->send == NULL ) + { + LogError( ( "Invalid parameter: pTransportInterface->send is NULL" ) ); status = MQTTBadParameter; } else @@ -1429,9 +1438,9 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, { LogError( ( "Argument cannot be NULL: pContext=%p, " "pConnectInfo=%p, pSessionPresent=%p.", - pContext, - pConnectInfo, - pSessionPresent ) ); + ( void * ) pContext, + ( void * ) pConnectInfo, + ( void * ) pSessionPresent ) ); status = MQTTBadParameter; } diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 9c40073268..5e93cf1085 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1042,8 +1042,8 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo { LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, " "pSubscriptionList=%p.", - pFixedBuffer, - pSubscriptionList ) ); + ( void * ) pFixedBuffer, + ( void * ) pSubscriptionList ) ); status = MQTTBadParameter; } /* A buffer must be configured for serialization. */ @@ -1362,9 +1362,9 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, { LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " "pRemainingLength=%p, pPacketSize=%p.", - pConnectInfo, - pRemainingLength, - pPacketSize ) ); + ( void * ) pConnectInfo, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); status = MQTTBadParameter; } else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) ) @@ -1458,8 +1458,8 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, { LogError( ( "Argument cannot be NULL: pConnectInfo=%p, " "pFixedBuffer=%p.", - pConnectInfo, - pFixedBuffer ) ); + ( void * ) pConnectInfo, + ( void * ) pFixedBuffer ) ); status = MQTTBadParameter; } /* A buffer must be configured for serialization. */ @@ -1516,9 +1516,9 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscript { LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, " "pRemainingLength=%p, pPacketSize=%p.", - pSubscriptionList, - pRemainingLength, - pPacketSize ) ); + ( void * ) pSubscriptionList, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) @@ -1615,9 +1615,9 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscri { LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, " "pRemainingLength=%p, pPacketSize=%p.", - pSubscriptionList, - pRemainingLength, - pPacketSize ) ); + ( void * ) pSubscriptionList, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); status = MQTTBadParameter; } else if( subscriptionCount == 0U ) @@ -1708,9 +1708,9 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, { LogError( ( "Argument cannot be NULL: pPublishInfo=%p, " "pRemainingLength=%p, pPacketSize=%p.", - pPublishInfo, - pRemainingLength, - pPacketSize ) ); + ( void * ) pPublishInfo, + ( void * ) pRemainingLength, + ( void * ) pPacketSize ) ); status = MQTTBadParameter; } else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) ) @@ -1756,8 +1756,8 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, { LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, " "pPublishInfo=%p.", - pFixedBuffer, - pPublishInfo ) ); + ( void * ) pFixedBuffer, + ( void * ) pPublishInfo ) ); status = MQTTBadParameter; } /* A buffer must be configured for serialization. */ @@ -1841,9 +1841,9 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo { LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, " "pPublishInfo=%p, pHeaderSize=%p.", - pFixedBuffer, - pPublishInfo, - pHeaderSize ) ); + ( void * ) pFixedBuffer, + ( void * ) pPublishInfo, + ( void * ) pHeaderSize ) ); status = MQTTBadParameter; } /* A buffer must be configured for serialization. */ @@ -2088,9 +2088,9 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, { LogError( ( "Argument cannot be NULL: pIncomingPacket=%p, " "pPacketId=%p, pPublishInfo=%p", - pIncomingPacket, - pPacketId, - pPublishInfo ) ); + ( void * ) pIncomingPacket, + ( void * ) pPacketId, + ( void * ) pPublishInfo ) ); status = MQTTBadParameter; } else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH ) diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index 37b23e0944..862bef440a 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -852,8 +852,8 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, if( ( pMqttContext == NULL ) || ( pNewState == NULL ) ) { LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p", - pMqttContext, - pNewState ) ); + ( void * ) pMqttContext, + ( void * ) pNewState ) ); mqttStatus = MQTTBadParameter; } @@ -928,8 +928,8 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, if( ( pMqttContext == NULL ) || ( pNewState == NULL ) ) { LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p.", - pMqttContext, - pNewState ) ); + ( void * ) pMqttContext, + ( void * ) pNewState ) ); } else { @@ -982,11 +982,11 @@ uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, /* Validate arguments. */ if( ( pMqttContext == NULL ) || ( pCursor == NULL ) || ( pState == NULL ) ) { - LogError( ( "Arguments cannot be NULL pMqttContext =%p, pCursor=%p" + LogError( ( "Arguments cannot be NULL pMqttContext=%p, pCursor=%p" " pState=%p.", - pMqttContext, - pCursor, - pState ) ); + ( void * ) pMqttContext, + ( void * ) pCursor, + ( void * ) pState ) ); } else { @@ -1018,8 +1018,8 @@ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, if( ( pMqttContext == NULL ) || ( pCursor == NULL ) ) { LogError( ( "Arguments cannot be NULL pMqttContext =%p, pCursor=%p", - pMqttContext, - pCursor ) ); + ( void * ) pMqttContext, + ( void * ) pCursor ) ); } else { From f6024952c32bbfd9af7bbd5f3f8e4510be8b88fc Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 12 Aug 2020 18:34:55 -0700 Subject: [PATCH 625/844] [PR from CLI tool] Add new MQTT API for obtaining SUBACK codes (#1108) * Add new API in lightweight MQTT for extracting payload from SUBACK packet * Add parameter names of new API to lexicon.txt * Minor updates * Update readSubackStatus in lightweight file to use named constants * Address hygiene comments from review * Move new API for SUBACK decoding from lightweight to managed layer * Make minor changes suggested in review comments Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Rename API for customer usability and update doc * Remove new QoS and SUBACK failure macros from lightweight API; Add a new enum for SUBACK status in managed API * Fix CI build issues * Adding another word to lexicon.txt for spell check * Address MISRA violation of type promotion in subtraction logic Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> --- libraries/standard/mqtt/include/mqtt.h | 40 ++++++++++++++ libraries/standard/mqtt/lexicon.txt | 7 ++- libraries/standard/mqtt/src/mqtt.c | 58 ++++++++++++++++++++ libraries/standard/mqtt/utest/mqtt_utest.c | 64 ++++++++++++++++++++++ 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 8d1069656e..5c3fa2a219 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -116,6 +116,17 @@ struct MQTTPubAckInfo MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */ }; +/** + * @brief The status codes in the SUBACK response to a subscription request. + */ +typedef enum MQTTSubAckStatus +{ + MQTTSubAckSuccessQos0 = 0x00, /**< @brief Success with a maximum delivery at QoS 0 . */ + MQTTSubAckSuccessQos1 = 0x01, /**< @brief Success with a maximum delivery at QoS 1. */ + MQTTSubAckSuccessQos2 = 0x02, /**< @brief Success with a maximum delivery at QoS 2. */ + MQTTSubAckFailure = 0x80 /**< @brief Failure. */ +} MQTTSubAckStatus_t; + /** * @brief A struct representing an MQTT connection. */ @@ -406,6 +417,35 @@ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, */ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); +/** + * @brief Parses the payload of an MQTT SUBACK packet that contains status codes + * corresponding to topic filter subscription requests from the original + * subscribe packet. + * + * Each return code in the SUBACK packet corresponds to a topic filter in the + * SUBSCRIBE Packet being acknowledged. + * The status codes can be one of the following: + * - 0x00 - Success - Maximum QoS 0 + * - 0x01 - Success - Maximum QoS 1 + * - 0x02 - Success - Maximum QoS 2 + * - 0x80 - Failure + * Refer to @ref MQTTSubAckStatus for the status codes. + * + * @param[in] pSubackPacket The SUBACK packet whose payload is to be parsed. + * @param[out] pPayloadStart This is populated with the starting address + * of the payload (or return codes for topic filters) in the SUBACK packet. + * @param[out] pPayloadSize This is populated with the size of the payload + * in the SUBACK packet. It represents the number of topic filters whose + * SUBACK status is present in the packet. + * + * @return Returns one of the following: + * - #MQTTBadParameter if the input SUBACK packet is invalid. + * - #MQTTSuccess if parsing the payload was successful. + */ +MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, + uint8_t ** pPayloadStart, + uint16_t * pPayloadSize ); + /** * @brief Error code to string conversion for MQTT statuses. * diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 0a17f47e80..9a32e1e130 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -122,6 +122,8 @@ mqttsendfailed mqttserverrefused mqttstatecollision mqttstatenull +mqttsubackfailure +mqttsubackstatus mqttsubscribeinfo mqttsuccess msb @@ -168,6 +170,8 @@ posix ppacketid ppacketinfo ppacketsize +ppayloadsize +ppayloadstart ppubinfo ppublishinfo pqos @@ -178,6 +182,7 @@ processloop psessionpresent psource pstate +psubackpacket psubscribeinfo psubscriptionlist ptr @@ -256,4 +261,4 @@ xa xb xc xd -xe +xe \ No newline at end of file diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index c0abf0c03d..23a8fc4009 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1963,6 +1963,64 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) /*-----------------------------------------------------------*/ +MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, + uint8_t ** pPayloadStart, + uint16_t * pPayloadSize ) +{ + MQTTStatus_t status = MQTTSuccess; + + if( pSubackPacket == NULL ) + { + LogError( ( "Invalid parameter: pSubackPacket is NULL." ) ); + status = MQTTBadParameter; + } + else if( pPayloadStart == NULL ) + { + LogError( ( "Invalid parameter: pPayloadStart is NULL." ) ); + status = MQTTBadParameter; + } + else if( pPayloadSize == NULL ) + { + LogError( ( "Invalid parameter: pPayloadSize is NULL." ) ); + status = MQTTBadParameter; + } + else if( pSubackPacket->type != MQTT_PACKET_TYPE_SUBACK ) + { + LogError( ( "Invalid parameter: Input packet is not a SUBACK packet: " + "ExpectedType=%02x, InputType=%02x", + MQTT_PACKET_TYPE_SUBACK, pSubackPacket->type ) ); + status = MQTTBadParameter; + } + else if( pSubackPacket->pRemainingData == NULL ) + { + LogError( ( "Invalid parameter: pSubackPacket->pRemainingData is NULL" ) ); + status = MQTTBadParameter; + } + + /* A SUBACK must have a remaining length of at least 3 to accommodate the + * packet identifier and at least 1 return code. */ + else if( pSubackPacket->remainingLength < 3U ) + { + LogError( ( "Invalid parameter: Packet remaining length is invalid: " + "Should be greater than 2 for SUBACK packet: InputRemainingLength=%u", + pSubackPacket->remainingLength ) ); + status = MQTTBadParameter; + } + else + { + /* According to the MQTT 3.1.1 protocol specification, the "Remaining Length" field is a + * length of the variable header (2 bytes) plus the length of the payload. + * Therefore, we add 2 positions for the starting address of the payload, and + * subtract 2 bytes from the remaining length for the length of the payload.*/ + *pPayloadStart = pSubackPacket->pRemainingData + ( ( uint16_t ) sizeof( uint16_t ) ); + *pPayloadSize = ( uint16_t ) ( pSubackPacket->remainingLength - sizeof( uint16_t ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + const char * MQTT_Status_strerror( MQTTStatus_t status ) { const char * str = NULL; diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index bd7e020c6f..004a007c5b 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2125,6 +2125,70 @@ void test_MQTT_Ping_error_path( void ) /* ========================================================================== */ +/** + * @brief Tests that MQTT_GetSubAckPayload works as expected in parsing the + * payload information of a SUBACK packet. + */ +void test_MQTT_GetSubAckPayload( void ) +{ + MQTTPacketInfo_t mqttPacketInfo; + uint16_t payloadSize; + uint8_t * pPayloadStart; + MQTTStatus_t status = MQTTSuccess; + uint8_t buffer[ 10 ] = { 0 }; + + buffer[ 0 ] = 0; + buffer[ 1 ] = 1; + buffer[ 2 ] = 0x00; + buffer[ 3 ] = 0x01; + buffer[ 4 ] = 0x02; + buffer[ 5 ] = 0x80; + + /* Process a valid SUBACK packet containing whole range of server response codes. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK; + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = 6; + status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize ); + TEST_ASSERT_EQUAL_INT( MQTTSuccess, status ); + TEST_ASSERT_EQUAL_PTR( &buffer[ 2 ], pPayloadStart ); + TEST_ASSERT_EQUAL_INT( MQTTSubAckSuccessQos0, pPayloadStart[ 0 ] ); + TEST_ASSERT_EQUAL_INT( MQTTSubAckSuccessQos1, pPayloadStart[ 1 ] ); + TEST_ASSERT_EQUAL_INT( MQTTSubAckSuccessQos2, pPayloadStart[ 2 ] ); + TEST_ASSERT_EQUAL_INT( MQTTSubAckFailure, pPayloadStart[ 3 ] ); + TEST_ASSERT_EQUAL_INT( 4, payloadSize ); + + /* Packet is NULL. */ + status = MQTT_GetSubAckStatusCodes( NULL, &pPayloadStart, &payloadSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Output parameter, pPayloadStart, is NULL. */ + status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, NULL, &payloadSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Output parameter, pPayloadSize, is NULL. */ + status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, NULL ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Remaining Data is NULL. */ + mqttPacketInfo.pRemainingData = NULL; + status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* non-SUBACK packet type. */ + mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK; + mqttPacketInfo.pRemainingData = buffer; + status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + + /* Invalid remaining length value in packet. */ + mqttPacketInfo.remainingLength = 0; + mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK; + status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); +} + +/* ========================================================================== */ + /** * @brief Test MQTT_Status_strerror returns correct strings. */ From b7d2b8f553de45e62cd9b6a172f1c3657fa64247 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 13 Aug 2020 14:13:10 -0700 Subject: [PATCH 626/844] [PR from CLI tool] Allow connectivity configs in MQTT demo to be CLI overridden (#1124) * Add #ifndefs around config macros in demos to allow them to be overridden * Add #error statements for BROKER_ENDPOINT in demo source files --- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 12 ++++++++---- demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 5 ++++- demos/mqtt/mqtt_demo_lightweight/demo_config.h | 4 +++- .../mqtt_demo_lightweight/mqtt_demo_lightweight.c | 5 +++++ demos/mqtt/mqtt_demo_plaintext/demo_config.h | 4 +++- demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c | 5 ++++- 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 2d7ff9dcdd..501fa99567 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -56,26 +56,30 @@ * the instructions in https://mosquitto.org/ for running a Mosquitto broker * locally. */ -#define BROKER_ENDPOINT "test.mosquitto.org" +#ifndef BROKER_ENDPOINT + #define BROKER_ENDPOINT "test.mosquitto.org" +#endif /** * @brief Length of MQTT server host name. */ -#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) /** * @brief MQTT server port number. * * In general, port 8883 is for secured MQTT connections. */ -#define BROKER_PORT ( 8883 ) +#define BROKER_PORT ( 8883 ) /** * @brief Path of the file containing the server's root CA certificate. * * This certificate should be PEM-encoded. */ -#define ROOT_CA_CERT_PATH "certificates/mosquitto.org.crt" +#ifndef ROOT_CA_CERT_PATH + #define ROOT_CA_CERT_PATH "certificates/mosquitto.org.crt" +#endif /** * @brief Length of path to server certificate. diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index af35cbfea3..21da0a2b18 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -61,8 +61,11 @@ * These configuration settings are required to run the basic TLS demo. * Throw compilation error if the below configs are not defined. */ +#ifndef BROKER_ENDPOINT + #error "Please define an MQTT broker endpoint, BROKER_ENDPOINT, in demo_config.h." +#endif #ifndef ROOT_CA_CERT_PATH - #error "Please define path to Root CA certificate of the MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." + #error "Please define path to Root CA certificate of the MQTT broker, ROOT_CA_CERT_PATH, in demo_config.h." #endif #ifndef CLIENT_IDENTIFIER #error "Please define a unique CLIENT_IDENTIFIER." diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h index da90a63df9..a122f4ac5d 100644 --- a/demos/mqtt/mqtt_demo_lightweight/demo_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -53,7 +53,9 @@ * This demo uses the Mosquitto test server. This is a public MQTT server; do not * publish anything sensitive to this server. */ -#define BROKER_ENDPOINT "test.mosquitto.org" +#ifndef BROKER_ENDPOINT + #define BROKER_ENDPOINT "test.mosquitto.org" +#endif /** * @brief Length of MQTT server host name. diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index fb6cfb7f0e..03f6516c13 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -55,6 +55,11 @@ /* Reconnect parameters. */ #include "transport_reconnect.h" +/* Check that the broker endpoint is defined. */ +#ifndef BROKER_ENDPOINT + #error "Please define an MQTT broker endpoint, BROKER_ENDPOINT, in demo_config.h." +#endif + /* Check that client identifier is defined. */ #ifndef CLIENT_IDENTIFIER #error "Please define a unique CLIENT_IDENTIFIER." diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index 8fbe99cc26..3854c616dc 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -53,7 +53,9 @@ * This demo uses the Mosquitto test server. This is a public MQTT server; do not * publish anything sensitive to this server. */ -#define BROKER_ENDPOINT "test.mosquitto.org" +#ifndef BROKER_ENDPOINT + #define BROKER_ENDPOINT "test.mosquitto.org" +#endif /** * @brief MQTT server port number. diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index 04864c1e6c..d238e0d580 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -58,8 +58,11 @@ * These configuration settings are required to run the plaintext demo. * Throw compilation error if the below configs are not defined. */ +#ifndef BROKER_ENDPOINT + #error "Please define an MQTT broker endpoint, BROKER_ENDPOINT, in demo_config.h." +#endif #ifndef CLIENT_IDENTIFIER - #error "Please define a unique CLIENT_IDENTIFIER." + #error "Please define a unique CLIENT_IDENTIFIER in demo_config.h." #endif /** From 57d775b43b4dcfb88c9da0957593e9fbbf7eb086 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Thu, 13 Aug 2020 16:08:32 -0700 Subject: [PATCH 627/844] Add macro for ping response timeout (#1113) * Add macro to initialize ping response timeout * Remove pingRespTimeoutMs field from MQTT context * Add macro to demo configs * Remove unused MQTT_MAX_QUEUED_PUBLISH_MESSAGES macro --- demos/lexicon.txt | 1 + demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h | 15 ++++++------- .../mqtt/mqtt_demo_mutual_auth/mqtt_config.h | 9 ++++++++ demos/mqtt/mqtt_demo_plaintext/mqtt_config.h | 9 ++++++++ .../standard/mqtt/cbmc/include/mqtt_config.h | 9 ++++++++ libraries/standard/mqtt/include/mqtt.h | 3 +-- .../mqtt/integration-test/mqtt_config.h | 15 ++++++------- libraries/standard/mqtt/lexicon.txt | 1 - libraries/standard/mqtt/src/mqtt.c | 16 ++++++++++++-- libraries/standard/mqtt/utest/mqtt_config.h | 21 ++++++++----------- libraries/standard/mqtt/utest/mqtt_utest.c | 6 +++--- 11 files changed, 67 insertions(+), 38 deletions(-) diff --git a/demos/lexicon.txt b/demos/lexicon.txt index f681c9de6e..dfe08831f5 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -49,6 +49,7 @@ methodlen milli mosquitto mqtt +mqttkeepalivetimeout nameindex noninfringement openssl diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h index 1d8c997003..9034d6173d 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_config.h @@ -58,18 +58,15 @@ * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. */ -#define MQTT_STATE_ARRAY_MAX_COUNT 10U +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) /** - * @brief The maximum number of MQTT PUBLISH messages that may be pending - * acknowledgement at any time. + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. * - * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before - * they can be completed. While they are awaiting the acknowledgement, the - * client must maintain information about their state. The value of this - * macro sets the limit on how many simultaneous PUBLISH states an MQTT - * context maintains. + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. */ -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 +#define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h index 1a6211a7ca..73b94c1a26 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_config.h @@ -59,4 +59,13 @@ */ #define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) + #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h index b2ca1158f4..73e7a95dd1 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_config.h @@ -60,4 +60,13 @@ */ #define MQTT_STATE_ARRAY_MAX_COUNT 10U +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#define MQTT_PINGRESP_TIMEOUT_MS 500U + #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/cbmc/include/mqtt_config.h b/libraries/standard/mqtt/cbmc/include/mqtt_config.h index 7eef16572d..067d9b931b 100644 --- a/libraries/standard/mqtt/cbmc/include/mqtt_config.h +++ b/libraries/standard/mqtt/cbmc/include/mqtt_config.h @@ -54,4 +54,13 @@ struct NetworkContext */ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) + #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 5c3fa2a219..043c4e89c8 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -186,7 +186,6 @@ struct MQTTContext /* Keep alive members. */ uint16_t keepAliveIntervalSec; /**< @brief Keep Alive interval. */ uint32_t pingReqSendTimeMs; /**< @brief Timestamp of the last sent PINGREQ. */ - uint32_t pingRespTimeoutMs; /**< @brief Timeout for waiting for a PINGRESP. */ bool waitingForPingResp; /**< @brief If the library is currently awaiting a PINGRESP. */ }; @@ -379,7 +378,7 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; * #MQTTBadResponse if an invalid packet is received; * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before - * pContext->pingRespTimeoutMs milliseconds; + * #MQTT_PINGRESP_TIMEOUT_MS milliseconds; * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an * invalid transition for the internal state machine; * #MQTTSuccess on success. diff --git a/libraries/standard/mqtt/integration-test/mqtt_config.h b/libraries/standard/mqtt/integration-test/mqtt_config.h index 1d8c997003..9034d6173d 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_config.h +++ b/libraries/standard/mqtt/integration-test/mqtt_config.h @@ -58,18 +58,15 @@ * macro sets the limit on how many simultaneous PUBLISH states an MQTT * context maintains. */ -#define MQTT_STATE_ARRAY_MAX_COUNT 10U +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) /** - * @brief The maximum number of MQTT PUBLISH messages that may be pending - * acknowledgement at any time. + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. * - * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before - * they can be completed. While they are awaiting the acknowledgement, the - * client must maintain information about their state. The value of this - * macro sets the limit on how many simultaneous PUBLISH states an MQTT - * context maintains. + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. */ -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 +#define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) #endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 9a32e1e130..7f48b9aa42 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -160,7 +160,6 @@ pingreq pingreqsendtimems pingresp pingresps -pingresptimeoutms pmessage pmqttcontext pnetworkbuffer diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 23a8fc4009..a465fd6875 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -41,6 +41,18 @@ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U ) #endif +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#ifndef MQTT_PINGRESP_TIMEOUT_MS + /* Wait 0.5 seconds by default for a ping response. */ + #define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) +#endif + /*-----------------------------------------------------------*/ /** @@ -200,7 +212,7 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ; * #MQTTBadResponse if an invalid packet is received; * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before - * pContext->pingRespTimeoutMs milliseconds; + * #MQTT_PINGRESP_TIMEOUT_MS milliseconds; * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an * invalid transition for the internal state machine; * #MQTTSuccess on success. @@ -707,7 +719,7 @@ static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext ) { /* Has time expired? */ if( calculateElapsedTime( now, pContext->pingReqSendTimeMs ) > - pContext->pingRespTimeoutMs ) + MQTT_PINGRESP_TIMEOUT_MS ) { status = MQTTKeepAliveTimeout; } diff --git a/libraries/standard/mqtt/utest/mqtt_config.h b/libraries/standard/mqtt/utest/mqtt_config.h index 382bdfda9b..137297506b 100644 --- a/libraries/standard/mqtt/utest/mqtt_config.h +++ b/libraries/standard/mqtt/utest/mqtt_config.h @@ -27,18 +27,6 @@ /************ End of logging configuration ****************/ -/** - * @brief The maximum number of MQTT PUBLISH messages that may be pending - * acknowledgement at any time. - * - * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before - * they can be completed. While they are awaiting the acknowledgement, the - * client must maintain information about their state. The value of this - * macro sets the limit on how many simultaneous PUBLISH states an MQTT - * context maintains. - */ -#define MQTT_MAX_QUEUED_PUBLISH_MESSAGES 10 - /** * @brief The maximum number of MQTT PUBLISH messages that may be pending * acknowledgement at any time. @@ -67,6 +55,15 @@ */ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 2U ) +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#define MQTT_PINGRESP_TIMEOUT_MS ( 1000U ) + /* Set network context to double pointer to buffer (uint8_t**). */ struct NetworkContext { diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 004a007c5b..69b49e59f7 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -1663,7 +1663,6 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths( void ) context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; context.lastPacketTime = 0; context.pingReqSendTimeMs = MQTT_ONE_SECOND_TO_MS; - context.pingRespTimeoutMs = MQTT_ONE_SECOND_TO_MS; expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, MQTTIllegalState, MQTTSuccess, MQTTStateNull, MQTTSuccess, false, NULL ); @@ -1696,11 +1695,12 @@ void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths( void ) modifyIncomingPacketStatus = MQTTNoDataAvailable; globalEntryTime = MQTT_ONE_SECOND_TO_MS; - /* Coverage for the branch path where PING timeout interval hasn't expired. */ + /* Coverage for the branch path where PINGRESP timeout interval has expired. */ mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus ); - context.lastPacketTime = 0; context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S; + context.lastPacketTime = 0; + context.pingReqSendTimeMs = 0; context.waitingForPingResp = true; expectProcessLoopCalls( &context, MQTTStateNull, MQTTStateNull, MQTTIllegalState, MQTTSuccess, MQTTStateNull, From 988edba897abb85a1ec48e8d908f724869a9f333 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Thu, 13 Aug 2020 16:59:29 -0700 Subject: [PATCH 628/844] Add remaining unit test coverage from CBMC changes to HTTP library (#1116) * Remove extra wrapping parenthesis * Add remaining coverage for HTTPClient_Send and its private methods * Add remaining coverage for HTTPClient_AddRangeHeader --- libraries/standard/http/src/http_client.c | 8 +-- .../standard/http/utest/http_send_utest.c | 61 +++++++++++++++++++ libraries/standard/http/utest/http_utest.c | 14 ++++- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/libraries/standard/http/src/http_client.c b/libraries/standard/http/src/http_client.c index f173b405cc..499bad44e9 100644 --- a/libraries/standard/http/src/http_client.c +++ b/libraries/standard/http/src/http_client.c @@ -1372,12 +1372,12 @@ HTTPStatus_t HTTPClient_InitializeRequestHeaders( HTTPRequestHeaders_t * pReques LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( ( pRequestInfo == NULL ) ) + else if( pRequestInfo == NULL ) { LogError( ( "Parameter check failed: pRequestInfo is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( ( pRequestInfo->method == NULL ) ) + else if( pRequestInfo->method == NULL ) { LogError( ( "Parameter check failed: pRequestInfo->method is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; @@ -1472,12 +1472,12 @@ HTTPStatus_t HTTPClient_AddHeader( HTTPRequestHeaders_t * pRequestHeaders, LogError( ( "Parameter check failed: pRequestHeaders->pBuffer is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( ( pField == NULL ) ) + else if( pField == NULL ) { LogError( ( "Parameter check failed: pField is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; } - else if( ( pValue == NULL ) ) + else if( pValue == NULL ) { LogError( ( "Parameter check failed: pValue is NULL." ) ); returnStatus = HTTP_INVALID_PARAMETER; diff --git a/libraries/standard/http/utest/http_send_utest.c b/libraries/standard/http/utest/http_send_utest.c index 482804565a..73e42e9a43 100644 --- a/libraries/standard/http/utest/http_send_utest.c +++ b/libraries/standard/http/utest/http_send_utest.c @@ -1327,6 +1327,24 @@ void test_HTTPClient_Send_null_request_header_buffer( void ) /*-----------------------------------------------------------*/ +/* Test when the length of request headers is greater than length of buffer. */ +void test_HTTPClient_Send_request_headers_gt_buffer( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + requestHeaders.headersLen = requestHeaders.bufferLen + 1; + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + /* Test a NULL response buffer passed to the API. */ void test_HTTPClient_Send_null_response_buffer( void ) { @@ -1345,6 +1363,27 @@ void test_HTTPClient_Send_null_response_buffer( void ) /*-----------------------------------------------------------*/ +/* Test when reqBodyBufLen is greater than the max value of a 32-bit integer. */ +void test_HTTPClient_Send_request_body_buffer_length_gt_max( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + size_t reqBodyBufLen = INT32_MAX; + + /* Increment separately to prevent an overflow warning. */ + reqBodyBufLen++; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + ( uint8_t * ) HTTP_TEST_REQUEST_PUT_BODY, + reqBodyBufLen, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + /* Test the request headers not containing enough bytes for a valid status-line. */ void test_HTTPClient_Send_not_enough_request_headers( void ) @@ -1363,6 +1402,28 @@ void test_HTTPClient_Send_not_enough_request_headers( void ) /*-----------------------------------------------------------*/ +/* Test when length of headers is greater than the max value of a 32-bit integer. */ +void test_HTTPClient_Send_headers_length_gt_max( void ) +{ + HTTPStatus_t returnStatus = HTTP_SUCCESS; + + requestHeaders.headersLen = INT32_MAX; + /* Increment separately to prevent an overflow warning. */ + requestHeaders.headersLen++; + requestHeaders.bufferLen = requestHeaders.headersLen; + + returnStatus = HTTPClient_Send( &transportInterface, + &requestHeaders, + NULL, + 0, + &response, + 0 ); + + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, returnStatus ); +} + +/*-----------------------------------------------------------*/ + /* Test a NULL request body but a non-zero requests body length. */ void test_HTTPClient_Send_null_request_body_nonzero_body_length( void ) diff --git a/libraries/standard/http/utest/http_utest.c b/libraries/standard/http/utest/http_utest.c index 2c2703ba58..0d3060d563 100644 --- a/libraries/standard/http/utest/http_utest.c +++ b/libraries/standard/http/utest/http_utest.c @@ -813,7 +813,17 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) retCode = HTTPClient_AddRangeHeader( &testHeaders, 0 /* rangeStart */, 10 /* rangeEnd */ ); - TEST_ASSERT_EQUAL( retCode, HTTP_INSUFFICIENT_MEMORY ); + TEST_ASSERT_EQUAL( HTTP_INSUFFICIENT_MEMORY, retCode ); + + /* Length of headers > length of buffer.*/ + tearDown(); + testHeaders.pBuffer = &testBuffer[ 0 ]; + /* The input buffer size is zero! */ + testHeaders.headersLen = testHeaders.bufferLen + 1; + retCode = HTTPClient_AddRangeHeader( &testHeaders, + 0 /* rangeStart */, + 10 /* rangeEnd */ ); + TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* Test incorrect combinations of rangeStart and rangeEnd. */ @@ -830,7 +840,7 @@ void test_Http_AddRangeHeader_Invalid_Params( void ) testHeaders.pBuffer = &testBuffer[ 0 ]; retCode = HTTPClient_AddRangeHeader( &testHeaders, INT32_MIN /* rangeStart */, - 5 /* rangeEnd */ ); + HTTP_RANGE_REQUEST_END_OF_FILE /* rangeEnd */ ); TEST_ASSERT_EQUAL( HTTP_INVALID_PARAMETER, retCode ); /* rangeStart is negative but rangeStart is non-End of File. */ From e9fc2eaa4b97d14a29a6887c7a652af4cdec75f8 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Fri, 14 Aug 2020 08:05:02 -0700 Subject: [PATCH 629/844] Add cbmc pointer primitive check flag and mqtt_lightweight.c small refactor (#1120) * Update proof templates, add proof flag --pointer-primitive-check * Fix overflow in MQTT_SerializeSubscribe, MQTT_SerializeUnsubscribe, MQTT_SerializePublish, and MQTT_SerializePublishHeader. Co-authored-by: Mark R. Tuttle --- .../http/cbmc/proofs/Makefile-project-defines | 4 + .../mqtt/cbmc/proofs/Makefile-project-defines | 4 + libraries/standard/mqtt/cbmc/stubs/memcpy.c | 6 +- libraries/standard/mqtt/lexicon.txt | 1 + .../standard/mqtt/src/mqtt_lightweight.c | 80 +++++++++++-------- .../mqtt/utest/mqtt_lightweight_utest.c | 16 ++++ tools/aws-templates-for-cbmc-proofs | 2 +- 7 files changed, 75 insertions(+), 38 deletions(-) diff --git a/libraries/standard/http/cbmc/proofs/Makefile-project-defines b/libraries/standard/http/cbmc/proofs/Makefile-project-defines index c957ee85b9..ad52781fec 100644 --- a/libraries/standard/http/cbmc/proofs/Makefile-project-defines +++ b/libraries/standard/http/cbmc/proofs/Makefile-project-defines @@ -27,3 +27,7 @@ INCLUDES += -I$(SRCDIR)/platform/include # Preprocessor definitions -D... DEFINES += -Dhttp_EXPORTS + +# Ensure that all assumptions are sound by checking that "all pointers +# in pointer primitives are valid or null" +CHECKFLAGS += --pointer-primitive-check diff --git a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines index b18113e559..82e9b5b4ae 100644 --- a/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines +++ b/libraries/standard/mqtt/cbmc/proofs/Makefile-project-defines @@ -26,3 +26,7 @@ INCLUDES += -I$(SRCDIR)/platform/include # Preprocessor definitions -D... DEFINES += -Dmqtt_EXPORTS + +# Ensure that all assumptions are sound by checking that "all pointers +# in pointer primitives are valid or null" +CHECKFLAGS += --pointer-primitive-check diff --git a/libraries/standard/mqtt/cbmc/stubs/memcpy.c b/libraries/standard/mqtt/cbmc/stubs/memcpy.c index e30481cf93..cf8adc3e8e 100644 --- a/libraries/standard/mqtt/cbmc/stubs/memcpy.c +++ b/libraries/standard/mqtt/cbmc/stubs/memcpy.c @@ -47,8 +47,10 @@ const void * src, size_t n ) { - __CPROVER_assert( __CPROVER_w_ok( dest, n ), "write" ); - __CPROVER_assert( __CPROVER_r_ok( src, n ), "read" ); + /* Per ANSI C specification, memcpy must be able to handle a copy length + * of zero. */ + __CPROVER_assert( ( n == 0 ) || __CPROVER_w_ok( dest, n ), "write" ); + __CPROVER_assert( ( n == 0 ) || __CPROVER_r_ok( src, n ), "read" ); return dest; } #endif /* if __has_builtin( __builtin___memcpy_chk ) */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 7f48b9aa42..9f19f90a70 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -2,6 +2,7 @@ ack acked acks addrecord +ansi api app aws diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 5e93cf1085..368fb03d0d 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -1030,12 +1030,7 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; - - /* The serialized packet size = First byte - * + length of encoded size of remaining length - * + remaining length. */ - size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength; + size_t packetSize = 0; /* Validate all the parameters. */ if( ( pFixedBuffer == NULL ) || ( pSubscriptionList == NULL ) ) @@ -1062,17 +1057,22 @@ static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo LogError( ( "Packet Id for subscription packet is 0." ) ); status = MQTTBadParameter; } - else if( packetSize > pFixedBuffer->size ) - { - LogError( ( "Buffer size of %lu is not sufficient to hold " - "serialized packet of size of %lu.", - ( unsigned long ) pFixedBuffer->size, - ( unsigned long ) packetSize ) ); - status = MQTTNoMemory; - } else { - /* Empty else MISRA 15.7 */ + /* The serialized packet size = First byte + * + length of encoded size of remaining length + * + remaining length. */ + packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength; + + if( packetSize > pFixedBuffer->size ) + { + LogError( ( "Buffer size of %lu is not sufficient to hold " + "serialized packet of size of %lu.", + ( unsigned long ) pFixedBuffer->size, + ( unsigned long ) packetSize ) ); + status = MQTTNoMemory; + } } return status; @@ -1745,12 +1745,7 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, const MQTTFixedBuffer_t * pFixedBuffer ) { MQTTStatus_t status = MQTTSuccess; - - /* Length of serialized packet = First byte - * + Length of encoded remaining length - * + Remaining length. */ - size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength; + size_t packetSize = 0; if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) ) { @@ -1796,7 +1791,16 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, LogError( ( "Duplicate flag is set for PUBLISH with Qos 0," ) ); status = MQTTBadParameter; } - else if( packetSize > pFixedBuffer->size ) + else + { + /* Length of serialized packet = First byte + * + Length of encoded remaining length + * + Remaining length. */ + packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength; + } + + if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH packet of size of %lu.", @@ -1804,7 +1808,8 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, ( unsigned long ) packetSize ) ); status = MQTTNoMemory; } - else + + if( status == MQTTSuccess ) { /* Serialize publish with header and payload. */ serializePublishCommon( pPublishInfo, @@ -1826,15 +1831,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo size_t * pHeaderSize ) { MQTTStatus_t status = MQTTSuccess; - - /* Length of serialized packet = First byte - * + Length of encoded remaining length - * + Remaining length - * - Payload Length. - * Payload length will be subtracted after verifying pPublishInfo parameter. - */ - size_t packetSize = 1U + remainingLengthEncodedSize( remainingLength ) - + remainingLength; + size_t packetSize = 0; if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pHeaderSize == NULL ) ) @@ -1871,7 +1868,19 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo LogError( ( "Duplicate flag is set for PUBLISH with Qos 0," ) ); status = MQTTBadParameter; } - else if( ( packetSize - pPublishInfo->payloadLength ) > pFixedBuffer->size ) + else + { + /* Length of serialized packet = First byte + * + Length of encoded remaining length + * + Remaining length + * - Payload Length. + */ + packetSize = 1U + remainingLengthEncodedSize( remainingLength ) + + remainingLength + - pPublishInfo->payloadLength; + } + + if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) ) { LogError( ( "Buffer size of %lu is not sufficient to hold " "serialized PUBLISH header packet of size of %lu.", @@ -1879,7 +1888,8 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo ( unsigned long ) ( packetSize - pPublishInfo->payloadLength ) ) ); status = MQTTNoMemory; } - else + + if( status == MQTTSuccess ) { /* Serialize publish without copying the payload. */ serializePublishCommon( pPublishInfo, @@ -1889,7 +1899,7 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo false ); /* Header size is the same as calculated packet size. */ - *pHeaderSize = ( packetSize - pPublishInfo->payloadLength ); + *pHeaderSize = packetSize; } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index 6f00cbeaa1..ab231abf9b 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -605,6 +605,22 @@ void test_MQTT_SerializeConnect( void ) status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer ); TEST_ASSERT_EQUAL( MQTTSuccess, status ); checkBufferOverflow( buffer, sizeof( buffer ) ); + + /* Success right on the buffer boundary. */ + connectInfo.pUserName = "USER"; + connectInfo.userNameLength = 4; + /* Throwing in a possible valid zero length password. */ + connectInfo.pPassword = "PASS"; + connectInfo.passwordLength = 0; + status = MQTT_GetConnectPacketSize( &connectInfo, NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + TEST_ASSERT_GREATER_OR_EQUAL( packetSize, bufferSize ); + /* Set the fixed buffer to exactly the size of the packet. */ + fixedBuffer.size = packetSize; + padAndResetBuffer( buffer, sizeof( buffer ) ); + status = MQTT_SerializeConnect( &connectInfo, NULL, remainingLength, &fixedBuffer ); + TEST_ASSERT_EQUAL( MQTTSuccess, status ); + checkBufferOverflow( buffer, sizeof( buffer ) ); } /* ========================================================================== */ diff --git a/tools/aws-templates-for-cbmc-proofs b/tools/aws-templates-for-cbmc-proofs index c21a9830c1..1234c4e8ae 160000 --- a/tools/aws-templates-for-cbmc-proofs +++ b/tools/aws-templates-for-cbmc-proofs @@ -1 +1 @@ -Subproject commit c21a9830c13ebbeaa59acf77c2add725d23b20c4 +Subproject commit 1234c4e8aeabc6f156f3c12aa7290571d77d014f From 1e0fb3777733db22e87cdb8c9a6cdcf591976fd4 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 14 Aug 2020 09:39:39 -0700 Subject: [PATCH 630/844] Download certs into demos/certificates with an option to disable downloading (#1126) * Add option to turn off downloading of certificates and create demos/certificates directory * Remove Mosquitto certificate from list of certs to download * Address PR comments * Add CMake command line options to use for configuring demos * Update README.md to contain extra flag for ROOT_CA_CERT_PATH * Set prefix to PWD if any path flags are relative --- CMakeLists.txt | 3 ++ README.md | 7 ++-- demos/CMakeLists.txt | 32 ++++++++++++++++- .../certificates/.gitignore | 0 demos/http/http_demo_basic_tls/CMakeLists.txt | 29 +++++++-------- demos/http/http_demo_basic_tls/demo_config.h | 10 ++++-- .../http/http_demo_mutual_auth/CMakeLists.txt | 23 ++++-------- .../http/http_demo_mutual_auth/demo_config.h | 6 ++-- demos/http/http_demo_plaintext/CMakeLists.txt | 7 ++++ demos/http/http_demo_plaintext/demo_config.h | 4 ++- demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt | 35 ++++++++++--------- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 4 ++- .../mqtt/mqtt_demo_lightweight/CMakeLists.txt | 13 +++++++ .../mqtt/mqtt_demo_lightweight/demo_config.h | 4 ++- .../mqtt/mqtt_demo_mutual_auth/CMakeLists.txt | 35 ++++++++++--------- .../mqtt/mqtt_demo_mutual_auth/demo_config.h | 6 ++-- demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt | 12 +++++++ demos/mqtt/mqtt_demo_plaintext/demo_config.h | 6 ++-- .../mqtt/integration-test/CMakeLists.txt | 8 ----- 19 files changed, 153 insertions(+), 91 deletions(-) rename demos/{mqtt/mqtt_demo_mutual_auth => }/certificates/.gitignore (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9777e74fe9..b467eb88fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,9 @@ option( BUILD_TESTS option( BUILD_CLONE_SUBMODULES "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." ON ) +option( DOWNLOAD_CERTS + "Set this to ON to automatically download certificates needed to run the demo. When OFF, certificates must be manually downloaded." + ON ) # Unity test framework does not export the correct symbols for DLLs. set( ALLOW_SHARED_LIBRARIES ON ) diff --git a/README.md b/README.md index f4d94ba971..ff0a98a245 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,12 @@ It is required to setup an AWS account and access the AWS IoT Console for runnin ### Configuring the mutual auth demos -- You can pass the following configuration settings as command line options in order to run the mutual auth demos: `cmake .. -DAWS_IOT_ENDPOINT="aws-iot-endpoint" -DCLIENT_CERT_PATH="certificate-path" -DCLIENT_PRIVATE_KEY_PATH="private-key-path"` +- You can pass the following configuration settings as command line options in order to run the mutual auth demos: +```bash +cmake .. -DAWS_IOT_ENDPOINT="aws-iot-endpoint" -DROOT_CA_CERT_PATH="root-ca-path" -DCLIENT_CERT_PATH="certificate-path" -DCLIENT_PRIVATE_KEY_PATH="private-key-path" +``` -- In order to set these settings manually, edit `demo_config.h` in `demos/mqtt/mqtt_demo_mutual_auth/` and `demos/http/http_demo_mutual_auth/` to `#define` the following: +- In order to set these configurations manually, edit `demo_config.h` in `demos/mqtt/mqtt_demo_mutual_auth/` and `demos/http/http_demo_mutual_auth/` to `#define` the following: - Set `AWS_IOT_ENDPOINT` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 433295cd9a..44a9315e30 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -35,11 +35,41 @@ function(check_aws_credentials demo_name) endif() endfunction() +if(DOWNLOAD_CERTS) + # Download the Amazon Root CA certificate. + message( "Downloading the Amazon Root CA certificate..." ) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) + execute_process( + COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem + -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt + ) +endif() + +# Copy the certificates and client keys to the build directory. +file(COPY "${CMAKE_CURRENT_LIST_DIR}/certificates" + DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +if(BUILD_TESTS) + file(COPY "${CMAKE_CURRENT_LIST_DIR}/certificates" + DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/tests") +endif() + +# Set prefix to PWD if any path flags are relative +if(DEFINED ENV{PWD}) + if(NOT ROOT_CA_CERT_PATH MATCHES "/$") + set(ROOT_CA_CERT_PATH "$ENV{PWD}/${ROOT_CA_CERT_PATH}") + endif() + if(NOT CLIENT_CERT_PATH MATCHES "/$") + set(CLIENT_CERT_PATH "$ENV{PWD}/${CLIENT_CERT_PATH}") + endif() + if(NOT CLIENT_PRIVATE_KEY_PATH MATCHES "/$") + set(CLIENT_PRIVATE_KEY_PATH "$ENV{PWD}/${CLIENT_PRIVATE_KEY_PATH}") + endif() +endif() + # Include each subdirectory that has a CMakeLists.txt file in it file(GLOB demo_dirs "${DEMOS_DIR}/*/*") foreach(demo_dir IN LISTS demo_dirs) if(IS_DIRECTORY "${demo_dir}" AND EXISTS "${demo_dir}/CMakeLists.txt") - get_filename_component( DEMO_EXE_NAME ${demo_dir} NAME ) add_subdirectory(${demo_dir}) endif() endforeach() diff --git a/demos/mqtt/mqtt_demo_mutual_auth/certificates/.gitignore b/demos/certificates/.gitignore similarity index 100% rename from demos/mqtt/mqtt_demo_mutual_auth/certificates/.gitignore rename to demos/certificates/.gitignore diff --git a/demos/http/http_demo_basic_tls/CMakeLists.txt b/demos/http/http_demo_basic_tls/CMakeLists.txt index 50894b82db..3576bb5df0 100644 --- a/demos/http/http_demo_basic_tls/CMakeLists.txt +++ b/demos/http/http_demo_basic_tls/CMakeLists.txt @@ -27,20 +27,15 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -# Download the Amazon Root CA certificate. -message( "Downloading the Amazon Root CA certificate..." ) -file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) -execute_process( - COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem - -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt -) - -# Copy the certificates and client key to the binary directory. -add_custom_command( - TARGET - ${DEMO_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_LIST_DIR}/certificates" - "$/certificates" -) +if(ROOT_CA_CERT_PATH) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + ROOT_CA_CERT_PATH="${ROOT_CA_CERT_PATH}" + ) +endif() +if(SERVER_HOST) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + SERVER_HOST="${SERVER_HOST}" + ) +endif() diff --git a/demos/http/http_demo_basic_tls/demo_config.h b/demos/http/http_demo_basic_tls/demo_config.h index 6cb8b4e05a..4d13897165 100644 --- a/demos/http/http_demo_basic_tls/demo_config.h +++ b/demos/http/http_demo_basic_tls/demo_config.h @@ -54,21 +54,25 @@ * * @note This demo uses httpbin.org: A simple HTTP Request & Response Service. */ -#define SERVER_HOST "httpbin.org" +#ifndef SERVER_HOST + #define SERVER_HOST "httpbin.org" +#endif /** * @brief HTTP server port number. * * In general, port 443 is for TLS HTTP connections. */ -#define SERVER_PORT 443 +#define SERVER_PORT 443 /** * @brief Path of the file containing the server's root CA certificate for TLS authentication. * * @note This certificate should be PEM-encoded. */ -#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#ifndef ROOT_CA_CERT_PATH + #define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#endif /** * @brief Paths for different HTTP methods for specified host. diff --git a/demos/http/http_demo_mutual_auth/CMakeLists.txt b/demos/http/http_demo_mutual_auth/CMakeLists.txt index c6657010d5..8eae7211e5 100644 --- a/demos/http/http_demo_mutual_auth/CMakeLists.txt +++ b/demos/http/http_demo_mutual_auth/CMakeLists.txt @@ -33,20 +33,9 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -# Download the Amazon Root CA certificate -message( "Downloading the Amazon Root CA certificate..." ) -file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) -execute_process( - COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem - -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt -) - -# Copy the certificates and client key to the binary directory. -add_custom_command( - TARGET - ${DEMO_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_LIST_DIR}/certificates" - "$/certificates" -) +if(ROOT_CA_CERT_PATH) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + ROOT_CA_CERT_PATH="${ROOT_CA_CERT_PATH}" + ) +endif() diff --git a/demos/http/http_demo_mutual_auth/demo_config.h b/demos/http/http_demo_mutual_auth/demo_config.h index 1428a6bffb..3830ab9cf3 100644 --- a/demos/http/http_demo_mutual_auth/demo_config.h +++ b/demos/http/http_demo_mutual_auth/demo_config.h @@ -66,7 +66,7 @@ * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol * name being x-amzn-http-ca. When using port 8443, ALPN is not required. */ -#define AWS_IOT_PORT 443 +#define AWS_IOT_PORT 443 /** * @brief Path of the file containing Amazon's root CA certificate for TLS @@ -78,7 +78,9 @@ * * @note This certificate should be PEM-encoded. */ -#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#ifndef ROOT_CA_CERT_PATH + #define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#endif /** * @brief ALPN protocol name to be sent as part of the ClientHello message. diff --git a/demos/http/http_demo_plaintext/CMakeLists.txt b/demos/http/http_demo_plaintext/CMakeLists.txt index c99a3302a7..90f68eab7d 100644 --- a/demos/http/http_demo_plaintext/CMakeLists.txt +++ b/demos/http/http_demo_plaintext/CMakeLists.txt @@ -26,3 +26,10 @@ target_include_directories( PUBLIC ${LOGGING_INCLUDE_DIRS} ) + +if(SERVER_HOST) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + SERVER_HOST="${SERVER_HOST}" + ) +endif() diff --git a/demos/http/http_demo_plaintext/demo_config.h b/demos/http/http_demo_plaintext/demo_config.h index 8c0aa252f2..4532068176 100644 --- a/demos/http/http_demo_plaintext/demo_config.h +++ b/demos/http/http_demo_plaintext/demo_config.h @@ -52,7 +52,9 @@ * * @note This demo uses httpbin.org: A simple HTTP Request & Response Service. */ -#define SERVER_HOST "httpbin.org" +#ifndef SERVER_HOST + #define SERVER_HOST "httpbin.org" +#endif /** * @brief HTTP server port number. diff --git a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt index b01a9688c5..cf247d90fc 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_basic_tls/CMakeLists.txt @@ -29,20 +29,21 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -# Download the Mosquitto Root CA certificate. -message( "Downloading the Mosquitto Root CA certificate..." ) -file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) -execute_process( - COMMAND curl --url https://test.mosquitto.org/ssl/mosquitto.org.crt - -o ${CMAKE_CURRENT_LIST_DIR}/certificates/mosquitto.org.crt -) - -# Copy the server certificate file to the binary directory. -add_custom_command( - TARGET - ${DEMO_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${CMAKE_CURRENT_LIST_DIR}/certificates/mosquitto.org.crt" - "$/certificates/mosquitto.org.crt" -) +if(ROOT_CA_CERT_PATH) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + ROOT_CA_CERT_PATH="${ROOT_CA_CERT_PATH}" + ) +endif() +if(BROKER_ENDPOINT) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + BROKER_ENDPOINT="${BROKER_ENDPOINT}" + ) +endif() +if(CLIENT_IDENTIFIER) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + CLIENT_IDENTIFIER="${CLIENT_IDENTIFIER}" + ) +endif() diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 501fa99567..76345b6819 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -91,6 +91,8 @@ * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER "testclient" +#endif #endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt b/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt index 928c71ff3c..bb9bfd23b1 100644 --- a/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_lightweight/CMakeLists.txt @@ -23,3 +23,16 @@ target_include_directories( ${CMAKE_CURRENT_LIST_DIR} ${LOGGING_INCLUDE_DIRS} ) + +if(BROKER_ENDPOINT) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + BROKER_ENDPOINT="${BROKER_ENDPOINT}" + ) +endif() +if(CLIENT_IDENTIFIER) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + CLIENT_IDENTIFIER="${CLIENT_IDENTIFIER}" + ) +endif() diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h index a122f4ac5d..189f45bff4 100644 --- a/demos/mqtt/mqtt_demo_lightweight/demo_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -74,6 +74,8 @@ * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER "testclient" +#endif #endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt index d32617b59b..6042937dac 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_mutual_auth/CMakeLists.txt @@ -35,20 +35,21 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) -# Download the Amazon Root CA certificate. -message( "Downloading the Amazon Root CA certificate..." ) -file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) -execute_process( - COMMAND curl --url https://www.amazontrust.com/repository/AmazonRootCA1.pem - -o ${CMAKE_CURRENT_LIST_DIR}/certificates/AmazonRootCA1.crt -) - -# Copy the certificates and client key to the binary directory. -add_custom_command( - TARGET - ${DEMO_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_LIST_DIR}/certificates" - "$/certificates" -) +if(ROOT_CA_CERT_PATH) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + ROOT_CA_CERT_PATH="${ROOT_CA_CERT_PATH}" + ) +endif() +if(BROKER_ENDPOINT) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + BROKER_ENDPOINT="${BROKER_ENDPOINT}" + ) +endif() +if(CLIENT_IDENTIFIER) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + CLIENT_IDENTIFIER="${CLIENT_IDENTIFIER}" + ) +endif() diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index 7ab3a16205..5f6cdc645d 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -67,7 +67,7 @@ * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol * name. When using port 8883, ALPN is not required. */ -#define AWS_MQTT_PORT ( 8883 ) +#define AWS_MQTT_PORT ( 8883 ) /** * @brief Path of the file containing the server's root CA certificate. @@ -84,7 +84,9 @@ * @note This path is relative from the demo binary created. Update * ROOT_CA_CERT_PATH to the absolute path if this demo is executed from elsewhere. */ -#define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#ifndef ROOT_CA_CERT_PATH + #define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#endif /** * @brief Path of the file containing the client certificate. diff --git a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt index afacd3d923..ec178258c5 100644 --- a/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt +++ b/demos/mqtt/mqtt_demo_plaintext/CMakeLists.txt @@ -29,3 +29,15 @@ target_include_directories( ${LOGGING_INCLUDE_DIRS} ) +if(BROKER_ENDPOINT) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + BROKER_ENDPOINT="${BROKER_ENDPOINT}" + ) +endif() +if(CLIENT_IDENTIFIER) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + CLIENT_IDENTIFIER="${CLIENT_IDENTIFIER}" + ) +endif() diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index 3854c616dc..ee4e72ed94 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -62,13 +62,15 @@ * * In general, port 1883 is for unsecured MQTT connections. */ -#define BROKER_PORT ( 1883 ) +#define BROKER_PORT ( 1883 ) /** * @brief MQTT client identifier. * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER "testclient" +#endif #endif /* ifndef DEMO_CONFIG_H */ diff --git a/libraries/standard/mqtt/integration-test/CMakeLists.txt b/libraries/standard/mqtt/integration-test/CMakeLists.txt index 73614eb3b2..fe1eb8371e 100644 --- a/libraries/standard/mqtt/integration-test/CMakeLists.txt +++ b/libraries/standard/mqtt/integration-test/CMakeLists.txt @@ -58,14 +58,6 @@ create_test(${stest_name} "${test_include_directories}" ) -# Download the Mosquitto Root CA certificate. -message( "Downloading the Mosquitto Root CA certificate..." ) -file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/certificates) -execute_process( - COMMAND curl --url https://test.mosquitto.org/ssl/mosquitto.org.crt - -o ${CMAKE_CURRENT_LIST_DIR}/certificates/mosquitto.org.crt -) - # Copy the certificates and client key to the binary directory. add_custom_command( TARGET From b9d22d831117aa1d16d98d2a90b6bbdc4f04a2bd Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 14 Aug 2020 10:20:49 -0700 Subject: [PATCH 631/844] Check that cert path flags are set before prepending an absolute path (#1127) --- demos/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 44a9315e30..8456675761 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -55,13 +55,13 @@ endif() # Set prefix to PWD if any path flags are relative if(DEFINED ENV{PWD}) - if(NOT ROOT_CA_CERT_PATH MATCHES "/$") + if(ROOT_CA_CERT_PATH AND NOT ROOT_CA_CERT_PATH MATCHES "/$") set(ROOT_CA_CERT_PATH "$ENV{PWD}/${ROOT_CA_CERT_PATH}") endif() - if(NOT CLIENT_CERT_PATH MATCHES "/$") + if(CLIENT_CERT_PATH AND NOT CLIENT_CERT_PATH MATCHES "/$") set(CLIENT_CERT_PATH "$ENV{PWD}/${CLIENT_CERT_PATH}") endif() - if(NOT CLIENT_PRIVATE_KEY_PATH MATCHES "/$") + if(CLIENT_PRIVATE_KEY_PATH AND NOT CLIENT_PRIVATE_KEY_PATH MATCHES "/$") set(CLIENT_PRIVATE_KEY_PATH "$ENV{PWD}/${CLIENT_PRIVATE_KEY_PATH}") endif() endif() From 357fd26dd8134955c572db03fb4f887a47044cf1 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 14 Aug 2020 10:44:45 -0700 Subject: [PATCH 632/844] Add doxygen code examples for mqtt.h (#1123) * Add code examples for mqtt.h --- libraries/standard/mqtt/include/mqtt.h | 240 +++++++++++++++++++++++++ libraries/standard/mqtt/lexicon.txt | 41 ++++- 2 files changed, 279 insertions(+), 2 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 043c4e89c8..585a43069f 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -219,6 +219,48 @@ struct MQTTDeserializedInfo * * @return #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Function for obtaining a timestamp. + * uint32_t getTimeStampMs(); + * // Callback function for receiving packets. + * void eventCallback( + * MQTTContext_t * pContext, + * MQTTPacketInfo_t * pPacketInfo, + * MQTTDeserializedInfo_t * pDeserialized + * ); + * // Network send. + * int32_t networkSend( NetworkContext_t * pContext, const void * pBuffer, size_t bytes ); + * // Network receive. + * int32_t networkRecv( NetworkContext_t * pContext, void * pBuffer, size_t bytes ); + * + * MQTTContext_t mqttContext; + * TransportInterface_t transport; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ 1024 ]; + * + * // Clear context. + * memset( ( void * ) &mqttContext, 0x00, sizeof( MQTTContext_t ) ); + * + * // Set transport interface members. + * transport.pNetworkInterface = &someNetworkInterface; + * transport.send = networkSend; + * transport.recv = networkRecv; + * + * // Set buffer members. + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = 1024; + * + * status = MQTT_Init( &mqttContext, &transport, getTimeStampMs, eventCallback, &fixedBuffer ); + * + * if( status == MQTTSuccess ) + * { + * // Do something with mqttContext. The transport and fixedBuffer structs were + * // copied into the context, so the original structs do not need to stay in scope. + * } + * @endcode */ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, const TransportInterface_t * pTransportInterface, @@ -277,6 +319,52 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * the API makes one more network receive call in an attempt to receive the remaining * 2 bytes. In the worst case, it can happen that the remaining 2 bytes are never * received and this API will end up spending timeoutMs + transport receive timeout. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTConnectInfo_t connectInfo = { 0 }; + * MQTTPublishInfo_t willInfo = { 0 }; + * bool sessionPresent; + * // This is assumed to have been initialized before calling this function. + * MQTTContext_t * pContext; + * + * // True for creating a new session with broker, false if we want to resume an old one. + * connectInfo.cleanSession = true; + * // Client ID must be unique to broker. This field is required. + * connectInfo.pClientIdentifier = "someClientID"; + * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier ); + * + * // The following fields are optional. + * // Value for keep alive. + * connectInfo.keepAliveSeconds = 60; + * // Optional username and password. + * connectInfo.pUserName = "someUserName"; + * connectInfo.userNameLength = strlen( connectInfo.pUserName ); + * connectInfo.pPassword = "somePassword"; + * connectInfo.passwordLength = strlen( connectInfo.pPassword ); + * + * // The last will and testament is optional, it will be published by the broker + * // should this client disconnect without sending a DISCONNECT packet. + * willInfo.qos = MQTTQoS0; + * willInfo.pTopicName = "/lwt/topic/name"; + * willInfo.topicNameLength = strlen( willInfo.pTopicName ); + * willInfo.pPayload = "LWT Message"; + * willInfo.payloadLength = strlen( "LWT Message" ); + * + * // Send the connect packet. Use 100 ms as the timeout to wait for the CONNACK packet. + * status = MQTT_Connect( pContext, &connectInfo, &willInfo, 100, &sessionPresent ); + * + * if( status == MQTTSuccess ) + * { + * // Since we requested a clean session, this must be false + * assert( sessionPresent == false ); + * + * // Do something with the connection. + * } + * @endcode */ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, const MQTTConnectInfo_t * pConnectInfo, @@ -298,6 +386,40 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 }; + * uint16_t packetId; + * // This context is assumed to be initialized and connected. + * MQTTContext_t * pContext; + * // This is assumed to be a list of filters we want to subscribe to. + * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ]; + * + * // Set each subscription. + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * subscriptionList[ i ].qos = MQTTQoS0; + * // Each subscription needs a topic filter + * subscriptionList[ i ].pTopicFilter = filters[ i ]; + * subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] ); + * } + * + * // Obtain a new packet id for the subscription. + * packetId = MQTT_GetPacketId( pContext ); + * + * status = MQTT_Subscribe( pContext, &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId ); + * + * if( status == MQTTSuccess ) + * { + * // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the SUBACK. + * // If the broker accepts the subscription we can now receive publishes + * // on the requested topics. + * } + * @endcode */ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, const MQTTSubscribeInfo_t * pSubscriptionList, @@ -315,6 +437,35 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPublishInfo_t publishInfo; + * uint16_t packetId; + * // This context is assumed to be initialized and connected. + * MQTTContext_t * pContext; + * + * // QoS of publish. + * publishInfo.qos = MQTTQoS1; + * publishInfo.pTopicName = "/some/topic/name"; + * publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); + * publishInfo.pPayload = "Hello World!"; + * publishInfo.payloadLength = strlen( "Hello World!" ); + * + * // Packet ID is needed for QoS > 0. + * packetId = MQTT_GetPacketId( pContext ); + * + * status = MQTT_Publish( pContext, &publishInfo, packetId ); + * + * if( status == MQTTSuccess ) + * { + * // Since the QoS is > 0, we will need to call MQTT_ReceiveLoop() + * // or MQTT_ProcessLoop() to process the publish acknowledgments. + * } + * @endcode */ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, @@ -346,6 +497,39 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTSubscribeInfo_t unsubscribeList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 }; + * uint16_t packetId; + * // This context is assumed to be initialized and connected. + * MQTTContext_t * pContext; + * // This is assumed to be a list of filters we want to unsubscribe from. + * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ]; + * + * // Set information for each unsubscribe request. + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * unsubscribeList[ i ].pTopicFilter = filters[ i ]; + * unsubscribeList[ i ].topicFilterLength = strlen( filters[ i ] ); + * + * // The QoS field of MQTT_SubscribeInfo_t is unused for unsubscribing. + * } + * + * // Obtain a new packet id for the unsubscribe request. + * packetId = MQTT_GetPacketId( pContext ); + * + * status = MQTT_Subscribe( pContext, &unsubscribeList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId ); + * + * if( status == MQTTSuccess ) + * { + * // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the UNSUBACK. + * // After this the broker should no longer send publishes for these topics. + * } + * @endcode */ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, const MQTTSubscribeInfo_t * pSubscriptionList, @@ -382,6 +566,30 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an * invalid transition for the internal state machine; * #MQTTSuccess on success. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * uint32_t timeoutMs = 100; + * // This context is assumed to be initialized and connected. + * MQTTContext_t * pContext; + * + * while( true ) + * { + * status = MQTT_ProcessLoop( pContext, timeoutMs ); + * + * if( status != MQTTSuccess ) + * { + * // Determine the error. It's possible we might need to disconnect TCP. + * } + * else + * { + * // Other application functions. + * } + * } + * @endcode */ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t timeoutMs ); @@ -403,6 +611,38 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an * invalid transition for the internal state machine; * #MQTTSuccess on success. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * uint32_t timeoutMs = 100; + * uint32_t keepAliveMs = 60 * 1000; + * // This context is assumed to be initialized and connected. + * MQTTContext_t * pContext; + * + * while( true ) + * { + * status = MQTT_ReceiveLoop( pContext, timeoutMs ); + * + * if( status != MQTTSuccess ) + * { + * // Determine the error. It's possible we might need to disconnect TCP. + * } + * else + * { + * // Since this function does not send pings, the application may need + * // to in order to comply with keep alive. + * if( ( pContext->getTime() - pContext->lastPacketTime ) > keepAliveMs ) + * { + * status = MQTT_Ping( pContext ); + * } + * + * // Other application functions. + * } + * } + * @endcode */ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, uint32_t timeoutMs ); diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 9f19f90a70..55a3038400 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -23,6 +23,7 @@ calculatestatepublish cb cbmc cleansession +clientidentifierlength cmock com config @@ -45,8 +46,10 @@ disconnectpacketsize doesn dup emptyindex +endcode endif enums +eventcallback eventcallbackstub expectprocessloopcalls fixedbuffer @@ -60,6 +63,7 @@ getpublishpacketsize getsubscribepacketsize gettime gettimefunction +gettimestampms getunsubscribepacketsize handleincomingack handleincomingpublish @@ -77,12 +81,16 @@ iot isduplicate isn keepaliveintervalsec +keepalivems +keepaliveseconds +lastpackettime linux lsb lwt malloc managekeepalive memcpy +memset milli min misra @@ -94,6 +102,8 @@ mqttbadresponse mqttconnected mqttconnectinfo mqttcontext +mqttdeserializedinfo +mqtteventcallback mqttfixedbuffer mqttillegalstate mqttkeepalivetimeout @@ -111,6 +121,7 @@ mqttpubcompsend mqttpublishdone mqttpublishinfo mqttpublishsend +mqttpublishstate mqttpubrec mqttpubrecpending mqttpubrecsend @@ -123,6 +134,7 @@ mqttsendfailed mqttserverrefused mqttstatecollision mqttstatenull +mqttstatus mqttsubackfailure mqttsubackstatus mqttsubscribeinfo @@ -131,6 +143,8 @@ msb networkbuffer networkcontext networkinterfacesendstub +networkrecv +networksend newstate nextpacketid noninfringement @@ -143,14 +157,16 @@ packetsize packettype param paramters +passwordlength payloadlength pbuffer pbuffertosend -pcallbacks +pclientidentifier pconnectinfo pcontext pcurrentstate pcursor +pdeserialized pdeserializedinfo pdestination pem @@ -165,11 +181,14 @@ pmessage pmqttcontext pnetworkbuffer pnetworkcontext +pnetworkinterface pnewstate posix ppacketid ppacketinfo ppacketsize +ppassword +ppayload ppayloadsize ppayloadstart ppubinfo @@ -185,6 +204,8 @@ pstate psubackpacket psubscribeinfo psubscriptionlist +ptopicfilter +ptopicname ptr ptransport ptransportinterface @@ -192,11 +213,13 @@ puback pubacks pubcomp pubcomps +publishinfo publishstate pubrec pubrecs pubrel pubrels +pusername pwillinfo qos readfunc @@ -226,19 +249,27 @@ serializepublish serializepublishheader serializesubscribe serializeunsubscribe +sessionpresent setretainflag shoulddelete shouldn sizeof +someclientid +somenetworkinterface +somepassword +someusername sourcelength src stateafterdeserialize strerror +strlen struct structs suback sublicense +subscribeinfo subscriptioncount +subscriptionlist subscriptiontype tcp tcpsocket @@ -246,19 +277,25 @@ testcase timeoutms tls topicfilterlength +topicnamelength +transportinterface +transportsend uint un unsuback +unsubscribelist updatestateack updatestatepublish updatestatestatus usercallback +usernamelength utest utf validatesubscribeunsubscribeparams waitingforpingresp +willinfo xa xb xc xd -xe \ No newline at end of file +xe From 792032cfa9a50c0bc38b15703eb2ef869aed1da8 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 14 Aug 2020 13:45:20 -0700 Subject: [PATCH 633/844] Utilize IS_ABSOLUTE CMake function instead of REGEX match for relative paths (#1129) * Utilize IS_ABSOLUTE CMake function instead of REGEX match * Address PR comments --- demos/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 8456675761..b11619e37b 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -53,15 +53,16 @@ if(BUILD_TESTS) DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/tests") endif() -# Set prefix to PWD if any path flags are relative +# Set prefix to PWD if any path flags are relative. +# PWD is set to the path where you run the cmake command. if(DEFINED ENV{PWD}) - if(ROOT_CA_CERT_PATH AND NOT ROOT_CA_CERT_PATH MATCHES "/$") + if(ROOT_CA_CERT_PATH AND NOT IS_ABSOLUTE ${ROOT_CA_CERT_PATH}) set(ROOT_CA_CERT_PATH "$ENV{PWD}/${ROOT_CA_CERT_PATH}") endif() - if(CLIENT_CERT_PATH AND NOT CLIENT_CERT_PATH MATCHES "/$") + if(CLIENT_CERT_PATH AND NOT IS_ABSOLUTE ${CLIENT_CERT_PATH}) set(CLIENT_CERT_PATH "$ENV{PWD}/${CLIENT_CERT_PATH}") endif() - if(CLIENT_PRIVATE_KEY_PATH AND NOT CLIENT_PRIVATE_KEY_PATH MATCHES "/$") + if(CLIENT_PRIVATE_KEY_PATH AND NOT IS_ABSOLUTE ${CLIENT_PRIVATE_KEY_PATH}) set(CLIENT_PRIVATE_KEY_PATH "$ENV{PWD}/${CLIENT_PRIVATE_KEY_PATH}") endif() endif() From def655c24fa31ea8e44d82febc85b702311f4009 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 18 Aug 2020 11:21:58 -0700 Subject: [PATCH 634/844] Hygiene changes in MQTT demos and tests (#1128) * Upgrade MQTT integration test to use mutual auth, and update its build setup to use CMake cache variables for credentials * Fix build warning from logging * Remove references to test.mosquitto.org from demos --- demos/lexicon.txt | 4 +- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 25 +++++------- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 5 +++ .../mqtt/mqtt_demo_lightweight/demo_config.h | 17 ++++----- .../mqtt_demo_lightweight.c | 5 +++ .../mqtt/mqtt_demo_mutual_auth/demo_config.h | 2 - demos/mqtt/mqtt_demo_plaintext/demo_config.h | 10 +++-- .../mqtt/integration-test/CMakeLists.txt | 34 ++++++++++++----- .../mqtt/integration-test/mqtt_system_test.c | 10 +++-- .../mqtt/integration-test/test_config.h | 38 +++++++------------ libraries/standard/mqtt/lexicon.txt | 4 +- libraries/standard/mqtt/src/mqtt.c | 4 +- 12 files changed, 88 insertions(+), 70 deletions(-) diff --git a/demos/lexicon.txt b/demos/lexicon.txt index dfe08831f5..bae5ded5d5 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -44,6 +44,7 @@ inc initializerequestheaders iot lastcontrolpacketsent +md metadata methodlen milli @@ -91,6 +92,7 @@ publishpacketsent pubrec pubrel qos +readme resending resp sdk @@ -111,4 +113,4 @@ uint unsub unsuback uri -www +www \ No newline at end of file diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 76345b6819..8cb6960055 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -50,36 +50,31 @@ /** * @brief MQTT server host name. * - * This demo uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. - * Mosquitto MQTT broker can run locally as an alternate option. Please refer to - * the instructions in https://mosquitto.org/ for running a Mosquitto broker - * locally. + * This demo can be run using the open-source Mosquitto broker tool. + * A Mosquitto MQTT broker can be setup locally for running this demo against + * it. Please refer to the instructions in https://mosquitto.org/ for running + * a Mosquitto broker locally. + * Alternatively,instructions to run Mosquitto server on Docker container can + * be viewed in the README.md of the root directory. */ #ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "test.mosquitto.org" + #define BROKER_ENDPOINT "localhost" #endif -/** - * @brief Length of MQTT server host name. - */ -#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) - /** * @brief MQTT server port number. * * In general, port 8883 is for secured MQTT connections. */ -#define BROKER_PORT ( 8883 ) +#define BROKER_PORT ( 8883 ) /** * @brief Path of the file containing the server's root CA certificate. * * This certificate should be PEM-encoded. + * + * #define ROOT_CA_CERT_PATH ".....insert here...." */ -#ifndef ROOT_CA_CERT_PATH - #define ROOT_CA_CERT_PATH "certificates/mosquitto.org.crt" -#endif /** * @brief Length of path to server certificate. diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 21da0a2b18..d1c285854a 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -82,6 +82,11 @@ #define NETWORK_BUFFER_SIZE ( 1024U ) #endif +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) + /** * @brief Length of client identifier. */ diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h index 189f45bff4..348ae63c02 100644 --- a/demos/mqtt/mqtt_demo_lightweight/demo_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -50,24 +50,23 @@ /** * @brief MQTT server host name. * - * This demo uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. + * This demo can be run using the open-source Mosquitto broker tool. + * A Mosquitto MQTT broker can be setup locally for running this demo against + * it. Please refer to the instructions in https://mosquitto.org/ for running + * a Mosquitto broker locally. + * Alternatively,instructions to run Mosquitto server on Docker container can + * be viewed in the README.md of the root directory. */ #ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "test.mosquitto.org" + #define BROKER_ENDPOINT "localhost" #endif -/** - * @brief Length of MQTT server host name. - */ -#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) - /** * @brief MQTT server port number. * * In general, port 1883 is for unsecured MQTT connections. */ -#define BROKER_PORT 1883 +#define BROKER_PORT 1883 /** * @brief MQTT client identifier. diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index 03f6516c13..4507c36197 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -65,6 +65,11 @@ #error "Please define a unique CLIENT_IDENTIFIER." #endif +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) + /** * @brief The topic to subscribe and publish to in the example. * diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index 5f6cdc645d..f43b6718c7 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -51,8 +51,6 @@ /** * @brief Details of the MQTT broker to connect to. * - * This is the Thing's Rest API Endpoint for AWS IoT. - * * @note Your AWS IoT Core endpoint can be found in the AWS IoT console under * Settings/Custom Endpoint, or using the describe-endpoint API. * diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index ee4e72ed94..b0ec0c88c5 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -50,11 +50,15 @@ /** * @brief MQTT server host name. * - * This demo uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. + * This demo can be run using the open-source Mosquitto broker tool. + * A Mosquitto MQTT broker can be setup locally for running this demo against + * it. Please refer to the instructions in https://mosquitto.org/ for running + * a Mosquitto broker locally. + * Alternatively,instructions to run Mosquitto server on Docker container can + * be viewed in the README.md of the root directory. */ #ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "test.mosquitto.org" + #define BROKER_ENDPOINT "localhost" #endif /** diff --git a/libraries/standard/mqtt/integration-test/CMakeLists.txt b/libraries/standard/mqtt/integration-test/CMakeLists.txt index fe1eb8371e..9c3a7175c6 100644 --- a/libraries/standard/mqtt/integration-test/CMakeLists.txt +++ b/libraries/standard/mqtt/integration-test/CMakeLists.txt @@ -58,12 +58,28 @@ create_test(${stest_name} "${test_include_directories}" ) -# Copy the certificates and client key to the binary directory. -add_custom_command( - TARGET - ${stest_name} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_LIST_DIR}/certificates" - "$/certificates" -) +# Set preprocessor defines for test if configured in build. +if(BROKER_ENDPOINT) + target_compile_definitions( + ${stest_name} PRIVATE + BROKER_ENDPOINT="${BROKER_ENDPOINT}" + ) +endif() +if(ROOT_CA_CERT_PATH) + target_compile_definitions( + ${stest_name} PRIVATE + SERVER_ROOT_CA_CERT_PATH="${ROOT_CA_CERT_PATH}" + ) +endif() +if(CLIENT_CERT_PATH) + target_compile_definitions( + ${stest_name} PRIVATE + CLIENT_CERT_PATH="${CLIENT_CERT_PATH}" + ) +endif() +if(CLIENT_PRIVATE_KEY_PATH) + target_compile_definitions( + ${stest_name} PRIVATE + CLIENT_PRIVATE_KEY_PATH="${CLIENT_PRIVATE_KEY_PATH}" + ) +endif() \ No newline at end of file diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index bce1846225..276d1dcf31 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -51,9 +51,10 @@ #error "SERVER_ROOT_CA_CERT_PATH should be defined for the MQTT integration tests." #endif -#ifndef CLIENT_IDENTIFIER - #error "CLIENT_IDENTIFIER should be defined for the MQTT integration tests." -#endif +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) /** * @brief A valid starting packet ID per MQTT spec. Start from 1. @@ -711,6 +712,9 @@ void setUp() memset( &incomingInfo, 0u, sizeof( MQTTPublishInfo_t ) ); memset( &opensslCredentials, 0u, sizeof( OpensslCredentials_t ) ); opensslCredentials.pRootCaPath = SERVER_ROOT_CA_CERT_PATH; + opensslCredentials.pClientCertPath = CLIENT_CERT_PATH; + opensslCredentials.pPrivateKeyPath = CLIENT_PRIVATE_KEY_PATH; + serverInfo.pHostName = BROKER_ENDPOINT; serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; serverInfo.port = BROKER_PORT; diff --git a/libraries/standard/mqtt/integration-test/test_config.h b/libraries/standard/mqtt/integration-test/test_config.h index 589c3ed19c..8aad65be44 100644 --- a/libraries/standard/mqtt/integration-test/test_config.h +++ b/libraries/standard/mqtt/integration-test/test_config.h @@ -50,22 +50,8 @@ /** * @brief MQTT server host name. * - * This test uses the Mosquitto test server. This is a public MQTT server; do not - * publish anything sensitive to this server. - * Mosquitto MQTT broker can run locally as an alternate option. Please refer to - * the instructions in https://mosquitto.org/ for running a Mosquitto broker - * locally. + * #define BROKER_ENDPOINT "...insert here..." */ -#ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "test.mosquitto.org" -#endif - -/** - * @brief Length of MQTT server host name. - */ -#ifndef BROKER_ENDPOINT_LENGTH - #define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) -#endif /** * @brief MQTT server port number. @@ -75,21 +61,23 @@ #define BROKER_PORT ( 8883 ) /** - * @brief Path of the file containing the server's root CA certificate. + * @brief Path of the file containing the client certificate. * - * This certificate should be PEM-encoded. + * #define CLIENT_CERT_PATH "...insert here..." */ -#ifndef SERVER_ROOT_CA_CERT_PATH - #define SERVER_ROOT_CA_CERT_PATH "certificates/mosquitto.org.crt" -#endif /** - * @brief MQTT client identifier. + * @brief Path of the file containing the client's private key. * - * No two clients may use the same client identifier simultaneously. + * #define CLIENT_PRIVATE_KEY_PATH "...insert here..." + */ + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate should be PEM-encoded. + * + * * #define SERVER_ROOT_CA_CERT_PATH "...insert here..." */ -#ifndef CLIENT_IDENTIFIER - #define CLIENT_IDENTIFIER "testclient" -#endif #endif /* ifndef TEST_CONFIG_H_ */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 55a3038400..75de4be144 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -41,6 +41,7 @@ deserializeack deserialized deserializepublish deserializing +developerguide didn disconnectpacketsize doesn @@ -70,6 +71,7 @@ handleincomingpublish handlekeepalive hasn headersize +html https ifndef inc @@ -298,4 +300,4 @@ xa xb xc xd -xe +xe \ No newline at end of file diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index a465fd6875..af2da7ec27 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -2014,8 +2014,8 @@ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, else if( pSubackPacket->remainingLength < 3U ) { LogError( ( "Invalid parameter: Packet remaining length is invalid: " - "Should be greater than 2 for SUBACK packet: InputRemainingLength=%u", - pSubackPacket->remainingLength ) ); + "Should be greater than 2 for SUBACK packet: InputRemainingLength=%lu", + ( unsigned long ) pSubackPacket->remainingLength ) ); status = MQTTBadParameter; } else From efeb64e38bf1c8f84a42eea007dbd1dcc9cb4158 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 18 Aug 2020 12:24:01 -0700 Subject: [PATCH 635/844] Fix doxygen warnings in MQTT Library documentation (#1122) Ignore private functions in mqtt_state.h and .c --- libraries/standard/mqtt/include/mqtt.h | 83 ++++----- .../standard/mqtt/include/mqtt_lightweight.h | 45 ++--- libraries/standard/mqtt/include/mqtt_state.h | 42 ++++- libraries/standard/mqtt/lexicon.txt | 14 ++ libraries/standard/mqtt/src/mqtt.c | 6 +- .../standard/mqtt/src/mqtt_lightweight.c | 165 ++++++++++++++++-- libraries/standard/mqtt/src/mqtt_state.c | 80 ++++++++- .../standard/mqtt/src/private/mqtt_internal.h | 33 ++++ .../standard/mqtt/utest/mqtt_state_utest.c | 5 +- 9 files changed, 388 insertions(+), 85 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 585a43069f..61d6a58921 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -19,6 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file mqtt.h + * @brief User-facing functions of the MQTT 3.1.1 library. + */ #ifndef MQTT_H #define MQTT_H @@ -35,14 +39,10 @@ */ #define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) +/* Structures defined in this file. */ struct MQTTPubAckInfo; -typedef struct MQTTPubAckInfo MQTTPubAckInfo_t; - struct MQTTContext; -typedef struct MQTTContext MQTTContext_t; - struct MQTTDeserializedInfo; -typedef struct MQTTDeserializedInfo MQTTDeserializedInfo_t; /** * @brief Application provided callback to retrieve the current time in @@ -64,9 +64,9 @@ typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); * @param[in] pPacketInfo Information on the type of incoming MQTT packet. * @param[in] pDeserializedInfo Deserialized information from incoming packet. */ -typedef void (* MQTTEventCallback_t )( MQTTContext_t * pContext, - MQTTPacketInfo_t * pPacketInfo, - MQTTDeserializedInfo_t * pDeserializedInfo ); +typedef void (* MQTTEventCallback_t )( struct MQTTContext * pContext, + struct MQTTPacketInfo * pPacketInfo, + struct MQTTDeserializedInfo * pDeserializedInfo ); /** * @brief Values indicating if an MQTT connection exists. @@ -106,16 +106,6 @@ typedef enum MQTTPubAckType MQTTPubcomp /**< @brief PUBCOMPs are sent in response to a PUBREL. */ } MQTTPubAckType_t; -/** - * @brief An element of the state engine records for QoS 1/2 publishes. - */ -struct MQTTPubAckInfo -{ - uint16_t packetId; /**< @brief The packet ID of the original PUBLISH. */ - MQTTQoS_t qos; /**< @brief The QoS of the original PUBLISH. */ - MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */ -}; - /** * @brief The status codes in the SUBACK response to a subscription request. */ @@ -127,10 +117,20 @@ typedef enum MQTTSubAckStatus MQTTSubAckFailure = 0x80 /**< @brief Failure. */ } MQTTSubAckStatus_t; +/** + * @brief An element of the state engine records for QoS 1 or Qos 2 publishes. + */ +typedef struct MQTTPubAckInfo +{ + uint16_t packetId; /**< @brief The packet ID of the original PUBLISH. */ + MQTTQoS_t qos; /**< @brief The QoS of the original PUBLISH. */ + MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */ +} MQTTPubAckInfo_t; + /** * @brief A struct representing an MQTT connection. */ -struct MQTTContext +typedef struct MQTTContext { /** * @brief State engine records for outgoing publishes. @@ -187,34 +187,37 @@ struct MQTTContext uint16_t keepAliveIntervalSec; /**< @brief Keep Alive interval. */ uint32_t pingReqSendTimeMs; /**< @brief Timestamp of the last sent PINGREQ. */ bool waitingForPingResp; /**< @brief If the library is currently awaiting a PINGRESP. */ -}; +} MQTTContext_t; /** * @brief Struct to hold deserialized packet information for an #MQTTEventCallback_t * callback. */ -struct MQTTDeserializedInfo +typedef struct MQTTDeserializedInfo { uint16_t packetIdentifier; /**< @brief Packet ID of deserialized packet. */ MQTTPublishInfo_t * pPublishInfo; /**< @brief Pointer to deserialized publish info. */ MQTTStatus_t deserializationResult; /**< @brief Return code of deserialization. */ -}; +} MQTTDeserializedInfo_t; /** * @brief Initialize an MQTT context. * - * This function must be called on an MQTT context before any other function. + * This function must be called on a #MQTTContext_t before any other function. * - * @note The getTime callback function must be defined. If there is no time - * implementation, it is the responsibility of the application to provide a - * dummy function to always return 0, and provide 0 timeouts for functions. This - * will ensure all time based functions will run for a single iteration. + * @note The #MQTTGetCurrentTimeFunc_t callback function must be defined. If + * there is no time implementation, it is the responsibility of the application + * to provide a dummy function to always return 0, and provide 0 timeouts for + * functions. This will ensure all time based functions will run for a single + * iteration. * * @brief param[in] pContext The context to initialize. - * @brief param[in] pTransportInterface The transport interface to use with the context. - * @brief param[in] getTimeFunction The time utility function to use with the context. - * @brief param[in] userCallback The user callback to use with the context to notify about - * incoming packet events. + * @brief param[in] pTransportInterface The transport interface to use with the + * context. + * @brief param[in] getTimeFunction The time utility function to use with the + * context. + * @brief param[in] userCallback The user callback to use with the context to + * notify about incoming packet events. * @brief param[in] pNetworkBuffer Network buffer provided for the context. * * @return #MQTTBadParameter if invalid parameters are passed; @@ -276,12 +279,12 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * * The maximum time this function waits for a CONNACK is decided in one of the * following ways: - * 1. If #timeoutMs is greater than 0: - * #getTime is used to ensure that the function does not wait more than #timeoutMs - * for CONNACK. - * 2. If #timeoutMs is 0: - * The network receive for CONNACK is retried up to the number of times configured - * by #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. + * 1. If @p timeoutMs is greater than 0: + * #MQTTContext_t.getTime is used to ensure that the function does not wait + * more than @p timeoutMs for CONNACK. + * 2. If @p timeoutMs is 0: + * The network receive for CONNACK is retried up to the number of times + * configured by #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. * * @param[in] pContext Initialized MQTT context. * @param[in] pConnectInfo MQTT CONNECT packet information. @@ -299,7 +302,7 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * #MQTTSendFailed if transport send failed; * #MQTTRecvFailed if transport receive failed for CONNACK; * #MQTTNoDataAvailable if no data available to receive in transport until - * the #timeoutMs for CONNACK; + * the @p timeoutMs for CONNACK; * #MQTTSuccess otherwise. * * @note This API may spend more time than provided in the timeoutMS parameters in @@ -379,7 +382,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * @param[in] pContext Initialized MQTT context. * @param[in] pSubscriptionList List of MQTT subscription info. * @param[in] subscriptionCount The number of elements in pSubscriptionList. - * @param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] packetId Packet ID generated by #MQTT_GetPacketId. * * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to * hold the MQTT packet; @@ -668,7 +671,7 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); * - 0x01 - Success - Maximum QoS 1 * - 0x02 - Success - Maximum QoS 2 * - 0x80 - Failure - * Refer to @ref MQTTSubAckStatus for the status codes. + * Refer to #MQTTSubAckStatus_t for the status codes. * * @param[in] pSubackPacket The SUBACK packet whose payload is to be parsed. * @param[out] pPayloadStart This is populated with the starting address diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 44ee296ddd..3961541798 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -19,12 +19,23 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file mqtt_lightweight.h + * @brief User-facing functions for serializing and deserializing MQTT 3.1.1 + * packets. This header should be included for building a lightweight MQTT + * client bypassing the managed CSDK MQTT library API in mqtt.h. + */ #ifndef MQTT_LIGHTWEIGHT_H #define MQTT_LIGHTWEIGHT_H #include #include +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + */ + /* bool is defined in only C99+. */ #if defined( __cplusplus ) || ( defined( __STDC_VERSION__ ) && ( __STDC_VERSION__ >= 199901L ) ) #include @@ -33,6 +44,7 @@ #define false ( int8_t ) 0 #define true ( int8_t ) 1 #endif +/** @endcond */ /* Include config file before other headers. */ #include "mqtt_config.h" @@ -55,26 +67,17 @@ #define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ #define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ - /** * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. */ #define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4UL ) +/* Structures defined in this file. */ struct MQTTFixedBuffer; -typedef struct MQTTFixedBuffer MQTTFixedBuffer_t; - struct MQTTConnectInfo; -typedef struct MQTTConnectInfo MQTTConnectInfo_t; - struct MQTTSubscribeInfo; -typedef struct MQTTSubscribeInfo MQTTSubscribeInfo_t; - -struct MqttPublishInfo; -typedef struct MqttPublishInfo MQTTPublishInfo_t; - +struct MQTTPublishInfo; struct MQTTPacketInfo; -typedef struct MQTTPacketInfo MQTTPacketInfo_t; /** * @brief Return codes from MQTT functions. @@ -110,16 +113,16 @@ typedef enum MQTTQoS * These buffers are not copied and must remain in scope for the duration of the * MQTT operation. */ -struct MQTTFixedBuffer +typedef struct MQTTFixedBuffer { uint8_t * pBuffer; /**< @brief Pointer to buffer. */ size_t size; /**< @brief Size of buffer. */ -}; +} MQTTFixedBuffer_t; /** * @brief MQTT CONNECT packet parameters. */ -struct MQTTConnectInfo +typedef struct MQTTConnectInfo { /** * @brief Whether to establish a new, clean session or resume a previous session. @@ -160,12 +163,12 @@ struct MQTTConnectInfo * @brief Length of MQTT password. Set to 0 if not used. */ uint16_t passwordLength; -}; +} MQTTConnectInfo_t; /** * @brief MQTT SUBSCRIBE packet parameters. */ -struct MQTTSubscribeInfo +typedef struct MQTTSubscribeInfo { /** * @brief Quality of Service for subscription. @@ -181,12 +184,12 @@ struct MQTTSubscribeInfo * @brief Length of subscription topic filter. */ uint16_t topicFilterLength; -}; +} MQTTSubscribeInfo_t; /** * @brief MQTT PUBLISH packet parameters. */ -struct MqttPublishInfo +typedef struct MQTTPublishInfo { /** * @brief Quality of Service for message. @@ -222,12 +225,12 @@ struct MqttPublishInfo * @brief Message payload length. */ size_t payloadLength; -}; +} MQTTPublishInfo_t; /** * @brief MQTT incoming packet parameters. */ -struct MQTTPacketInfo +typedef struct MQTTPacketInfo { /** * @brief Type of incoming MQTT packet. @@ -243,7 +246,7 @@ struct MQTTPacketInfo * @brief Length of remaining serialized data. */ size_t remainingLength; -}; +} MQTTPacketInfo_t; /** * @brief Get the size and Remaining Length of an MQTT CONNECT packet. diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index 75a54de7d0..af5e261690 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -19,14 +19,25 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file mqtt_state.h + * @brief Function to keep state of MQTT PUBLISH packet deliveries. + */ #ifndef MQTT_STATE_H #define MQTT_STATE_H #include "mqtt.h" +/** + * @brief Initializer value for an #MQTTStateCursor_t, indicating a search + * should start at the beginning of a state record array + */ #define MQTT_STATE_CURSOR_INITIALIZER ( size_t ) 0 /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this enum is private. + * * @brief Value indicating either send or receive. */ typedef enum MQTTStateOperation @@ -34,6 +45,7 @@ typedef enum MQTTStateOperation MQTT_SEND, MQTT_RECEIVE } MQTTStateOperation_t; +/** @endcond */ /** * @brief Cursor for iterating through state records. @@ -41,7 +53,10 @@ typedef enum MQTTStateOperation typedef size_t MQTTStateCursor_t; /** - * @brief Reserve an entry for an outgoing QoS 1/2 publish. + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * + * @brief Reserve an entry for an outgoing QoS 1 or Qos 2 publish. * * @param[in] pMqttContext Initialized MQTT context. * @param[in] packetId The ID of the publish packet. @@ -52,8 +67,12 @@ typedef size_t MQTTStateCursor_t; MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos ); +/** @endcond */ /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * * @brief Calculate the new state for a publish from its qos and operation type. * * @param[in] opType Send or Receive. @@ -63,8 +82,12 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, */ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ); +/** @endcond */ /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * * @brief Update the state record for a PUBLISH packet. * * @param[in] pMqttContext Initialized MQTT context. @@ -81,8 +104,12 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, MQTTStateOperation_t opType, MQTTQoS_t qos, MQTTPublishState_t * pNewState ); +/** @endcond */ /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP. * * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. @@ -94,8 +121,12 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos ); +/** @endcond */ /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * * @brief Update the state record for an ACKed publish. * * @param[in] pMqttContext Initialized MQTT context. @@ -111,8 +142,12 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTPublishState_t * pNewState ); +/** @endcond */ /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * * @brief Get the packet ID of next pending PUBREL ack to be resent. * * This function will need to be called to get the packet for which a PUBREL @@ -127,6 +162,7 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState ); +/** @endcond */ /** * @brief Get the packet ID of next pending publish to be resent. @@ -143,6 +179,9 @@ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor ); /** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section, this function is private. + * * @brief State to string conversion for state engine. * * @param[in] state The state to convert to a string. @@ -150,5 +189,6 @@ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, * @return The string representation of the state. */ const char * MQTT_State_strerror( MQTTPublishState_t state ); +/** @endcond */ #endif /* ifndef MQTT_STATE_H */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 75de4be144..ea6adc9116 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -26,6 +26,7 @@ cleansession clientidentifierlength cmock com +cond config connack connectinfo @@ -33,6 +34,7 @@ connectpacketsize const createcleansession currentstate +csdk defragmenting deserialization deserializationresult @@ -45,10 +47,13 @@ developerguide didn disconnectpacketsize doesn +doxygen dup +endcond emptyindex endcode endif +enum enums eventcallback eventcallbackstub @@ -163,6 +168,8 @@ passwordlength payloadlength pbuffer pbuffertosend +pcallbacks +pconnack pclientidentifier pconnectinfo pcontext @@ -171,6 +178,8 @@ pcursor pdeserialized pdeserializedinfo pdestination +ppacketidentifier +ppingresp pem pfixedbuffer pheadersize @@ -203,6 +212,8 @@ processloop psessionpresent psource pstate +pstatusstart +psuback psubackpacket psubscribeinfo psubscriptionlist @@ -215,6 +226,7 @@ puback pubacks pubcomp pubcomps +publishflags publishinfo publishstate pubrec @@ -263,6 +275,8 @@ someusername sourcelength src stateafterdeserialize +stateful +statuscount strerror strlen struct diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index af2da7ec27..ed7bf265e1 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -19,6 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file mqtt.c + * @brief Implements the user-facing functions in mqtt.h. + */ #include #include @@ -109,7 +113,7 @@ static int32_t recvExact( const MQTTContext_t * pContext, /** * @brief Discard a packet from the transport interface. * - * @param[in] PContext MQTT Connection context. + * @param[in] pContext MQTT Connection context. * @param[in] remainingLength Remaining length of the packet to dump. * @param[in] timeoutMs Time remaining to discard the packet. * diff --git a/libraries/standard/mqtt/src/mqtt_lightweight.c b/libraries/standard/mqtt/src/mqtt_lightweight.c index 368fb03d0d..783de86918 100644 --- a/libraries/standard/mqtt/src/mqtt_lightweight.c +++ b/libraries/standard/mqtt/src/mqtt_lightweight.c @@ -19,6 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file mqtt_lightweight.c + * @brief Implements the user-facing functions in mqtt_lightweight.h. + */ #include #include @@ -58,7 +62,7 @@ */ #define MQTT_DISCONNECT_PACKET_SIZE ( 2UL ) -/* +/** * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. */ #define MQTT_PACKET_PINGREQ_SIZE ( 2U ) @@ -135,11 +139,13 @@ /*-----------------------------------------------------------*/ -/* MQTT Subscription packet types. */ +/** + * @brief MQTT Subscription packet types. + */ typedef enum MQTTSubscriptionType { - MQTT_SUBSCRIBE, - MQTT_UNSUBSCRIBE + MQTT_SUBSCRIBE, /**< @brief The type is a SUBSCRIBE packet. */ + MQTT_UNSUBSCRIBE /**< @brief The type is a UNSUBSCRIBE packet. */ } MQTTSubscriptionType_t; /*-----------------------------------------------------------*/ @@ -147,9 +153,9 @@ typedef enum MQTTSubscriptionType /** * @brief Serializes MQTT PUBLISH packet into the buffer provided. * - * This function serializes MQTT PUBLISH packet into #pFixedBuffer.pBuffer. + * This function serializes MQTT PUBLISH packet into #MQTTFixedBuffer_t.pBuffer. * Copy of the payload into the buffer is done as part of the serialization - * only if #serializePayload is true. + * only if @p serializePayload is true. * * @brief param[in] pPublishInfo Publish information. * @brief param[in] remainingLength Remaining length of the PUBLISH packet. @@ -237,16 +243,16 @@ static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo, const MQTTFixedBuffer_t * pFixedBuffer ); /** - * Prints the appropriate message for the CONNACK response code if logs are - * enabled. + * @brief Prints the appropriate message for the CONNACK response code if logs + * are enabled. * * @param[in] responseCode MQTT standard CONNACK response code. */ static void logConnackResponse( uint8_t responseCode ); /** - * Encodes the remaining length of the packet using the variable length encoding - * scheme provided in the MQTT v3.1.1 specification. + * @brief Encodes the remaining length of the packet using the variable length + * encoding scheme provided in the MQTT v3.1.1 specification. * * @param[out] pDestination The destination buffer to store the encoded remaining * length. @@ -258,7 +264,7 @@ static uint8_t * encodeRemainingLength( uint8_t * pDestination, size_t length ); /** - * Retrieve the size of the remaining length if it were to be encoded. + * @brief Retrieve the size of the remaining length if it were to be encoded. * * @param[in] length The remaining length to be encoded. * @@ -267,7 +273,7 @@ static uint8_t * encodeRemainingLength( uint8_t * pDestination, static size_t remainingLengthEncodedSize( size_t length ); /** - * Encode a string whose size is at maximum 16 bits in length. + * @brief Encode a string whose size is at maximum 16 bits in length. * * @param[out] pDestination Destination buffer for the encoding. * @param[in] pSource The source string to encode. @@ -280,8 +286,8 @@ static uint8_t * encodeString( uint8_t * pDestination, uint16_t sourceLength ); /** - * Retrieves and decodes the Remaining Length from the network interface by - * reading a single byte at a time. + * @brief Retrieves and decodes the Remaining Length from the network interface + * by reading a single byte at a time. * * @param[in] recvFunc Network interface receive function. * @param[in] pNetworkContext Network interface context to the receive function. @@ -291,6 +297,133 @@ static uint8_t * encodeString( uint8_t * pDestination, static size_t getRemainingLength( TransportRecv_t recvFunc, NetworkContext_t * pNetworkContext ); +/** + * @brief Check if an incoming packet type is valid. + * + * @param[in] packetType The packet type to check. + * + * @return `true` if the packet type is valid; `false` otherwise. + */ +static bool incomingPacketValid( uint8_t packetType ); + +/** + * @brief Check the remaining length of an incoming PUBLISH packet against some + * value for QoS 0, or for QoS 1 and 2. + * + * The remaining length for a QoS 1 and 2 packet will always be two greater than + * for a QoS 0. + * + * @param[in] remainingLength Remaining length of the PUBLISH packet. + * @param[in] qos The QoS of the PUBLISH. + * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH. + * + * @return #MQTTSuccess or #MQTTBadResponse. + */ +static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength, + MQTTQoS_t qos, + size_t qos0Minimum ); + +/** + * @brief Process the flags of an incoming PUBLISH packet. + * + * @param[in] publishFlags Flags of an incoming PUBLISH. + * @param[in, out] pPublishInfo Pointer to #MQTTPublishInfo_t struct where + * output will be written. + * + * @return #MQTTSuccess or #MQTTBadResponse. + */ +static MQTTStatus_t processPublishFlags( uint8_t publishFlags, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Deserialize a CONNACK packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t. + * + * @param[in] pConnack Pointer to an MQTT packet struct representing a + * CONNACK. + * @param[out] pSessionPresent Whether a previous session was present. + * + * @return #MQTTSuccess if CONNACK specifies that CONNECT was accepted; + * #MQTTServerRefused if CONNACK specifies that CONNECT was rejected; + * #MQTTBadResponse if the CONNACK packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack, + bool * pSessionPresent ); + +/** + * @brief Decode the status bytes of a SUBACK packet to a #MQTTStatus_t. + * + * @param[in] statusCount Number of status bytes in the SUBACK. + * @param[in] pStatusStart The first status byte in the SUBACK. + * + * @return #MQTTSuccess, #MQTTServerRefused, or #MQTTBadResponse. + */ +static MQTTStatus_t readSubackStatus( size_t statusCount, + const uint8_t * pStatusStart ); + +/** + * @brief Deserialize a SUBACK packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts + * the packet identifier. + * + * @param[in] pSuback Pointer to an MQTT packet struct representing a SUBACK. + * @param[out] pPacketIdentifier Packet ID of the SUBACK. + * + * @return #MQTTSuccess if SUBACK is valid; #MQTTBadResponse if SUBACK packet + * doesn't follow the MQTT spec. + */ +static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a PUBLISH packet received from the server. + * + * Converts the packet from a stream of bytes to an #MQTTPublishInfo_t and + * extracts the packet identifier. Also prints out debug log messages about the + * packet. + * + * @param[in] pIncomingPacket Pointer to an MQTT packet struct representing a + * PUBLISH. + * @param[out] pPacketId Packet identifier of the PUBLISH. + * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is + * written. + * + * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse + * if the PUBLISH packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket, + uint16_t * pPacketId, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Deserialize an UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts + * the packet identifier. + * + * @param[in] pAck Pointer to the MQTT packet structure representing the packet. + * @param[out] pPacketIdentifier Packet ID of the ack type packet. + * + * @return #MQTTSuccess if UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP is valid; + * #MQTTBadResponse if the packet doesn't follow the MQTT spec. + */ +static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck, + uint16_t * pPacketIdentifier ); + +/** + * @brief Deserialize a PINGRESP packet. + * + * Converts the packet from a stream of bytes to an #MQTTStatus_t. + * + * @param[in] pPingresp Pointer to an MQTT packet struct representing a PINGRESP. + * + * @return #MQTTSuccess if PINGRESP is valid; #MQTTBadResponse if the PINGRESP + * packet doesn't follow MQTT spec. + */ +static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp ); + /*-----------------------------------------------------------*/ static size_t remainingLengthEncodedSize( size_t length ) @@ -493,9 +626,9 @@ static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo, assert( pPublishInfo != NULL ); assert( pFixedBuffer != NULL ); assert( pFixedBuffer->pBuffer != NULL ); - /* Packet Id should be non zero for QoS1 and QoS2. */ + /* Packet Id should be non zero for Qos 1 and Qos 2. */ assert( ( pPublishInfo->qos == MQTTQoS0 ) || ( packetIdentifier != 0U ) ); - /* Duplicate flag should be set only for Qos1 or Qos2. */ + /* Duplicate flag should be set only for Qos 1 or Qos 2. */ assert( ( pPublishInfo->dup != true ) || ( pPublishInfo->qos != MQTTQoS0 ) ); /* Get the start address of the buffer. */ diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index 862bef440a..fad14e3b56 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -19,6 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file mqtt_state.c + * @brief Implements the functions in mqtt_state.h. + */ #include #include #include "mqtt_state.h" @@ -127,11 +131,11 @@ static void compactRecords( MQTTPubAckInfo_t * records, * * @param[in] records State record array. * @param[in] recordCount Length of record array. - * @param[in] packetId, packet ID of new entry. + * @param[in] packetId Packet ID of new entry. * @param[in] qos QoS of new entry. - * @param[in] publishState state of new entry. + * @param[in] publishState State of new entry. * - * @return MQTTSuccess, MQTTNoMemory, MQTTStateCollision. + * @return #MQTTSuccess, #MQTTNoMemory, or #MQTTStateCollision. */ static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records, size_t recordCount, @@ -159,6 +163,8 @@ static void updateRecord( MQTTPubAckInfo_t * records, * @param[in] pMqttContext Initialized MQTT context. * @param[in] searchStates The states to search for in 2-byte bit map. * @param[in,out] pCursor Index at which to start searching. + * + * @return Packet ID of the outgoing publish. */ static uint16_t stateSelect( const MQTTContext_t * pMqttContext, uint16_t searchStates, @@ -615,6 +621,15 @@ static uint16_t stateSelect( const MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ +/** + * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP. + * + * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. + * @param[in] opType Send or Receive. + * @param[in] qos 1 or 2. + * + * @return The calculated state. + */ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos ) @@ -779,6 +794,15 @@ static MQTTStatus_t updateStatePublish( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ +/** + * @brief Reserve an entry for an outgoing QoS 1 or Qos 2 publish. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] packetId The ID of the publish packet. + * @param[in] qos 1 or 2. + * + * @return MQTTSuccess, MQTTNoMemory, or MQTTStateCollision. + */ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos ) @@ -808,6 +832,14 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ +/** + * @brief Calculate the new state for a publish from its qos and operation type. + * + * @param[in] opType Send or Receive. + * @param[in] qos 0, 1, or 2. + * + * @return The calculated state. + */ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ) { @@ -837,6 +869,18 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, /*-----------------------------------------------------------*/ +/** + * @brief Update the state record for a PUBLISH packet. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] packetId ID of the PUBLISH packet. + * @param[in] opType Send or Receive. + * @param[in] qos 0, 1, or 2. + * @param[out] pNewState Updated state of the publish. + * + * @return #MQTTBadParameter, #MQTTIllegalState, #MQTTStateCollision or + * #MQTTSuccess. + */ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, @@ -911,6 +955,17 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ +/** + * @brief Update the state record for an ACKed publish. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in] packetId ID of the ack packet. + * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. + * @param[in] opType Send or Receive. + * @param[out] pNewState Updated state of the publish. + * + * @return #MQTTBadParameter, #MQTTIllegalState, or #MQTTSuccess. + */ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTPubAckType_t packetType, @@ -972,6 +1027,18 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ +/** + * @brief Get the packet ID of next pending PUBREL ack to be resent. + * + * This function will need to be called to get the packet for which a PUBREL + * need to be sent when a session is reestablished. Calling this function + * repeatedly until packet id is 0 will give all the packets for which + * a PUBREL need to be resent in the correct order. + * + * @param[in] pMqttContext Initialized MQTT context. + * @param[in,out] pCursor Index at which to start searching. + * @param[out] pState State indicating that PUBREL packet need to be sent. + */ uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState ) @@ -1038,6 +1105,13 @@ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ +/** + * @brief State to string conversion for state engine. + * + * @param[in] state The state to convert to a string. + * + * @return The string representation of the state. + */ const char * MQTT_State_strerror( MQTTPublishState_t state ) { const char * str = NULL; diff --git a/libraries/standard/mqtt/src/private/mqtt_internal.h b/libraries/standard/mqtt/src/private/mqtt_internal.h index c624895737..cc7a6f61bd 100644 --- a/libraries/standard/mqtt/src/private/mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/mqtt_internal.h @@ -1,9 +1,41 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file mqtt_internal.h + * @brief Internal header of the MQTT library. This header should not be + * included in typical application code. + */ #ifndef MQTT_INTERNAL_H_ #define MQTT_INTERNAL_H_ /* Include config file before other headers. */ #include "mqtt_config.h" +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Configure logs for MQTT functions. + */ #ifndef LogError #define LogError( message ) #endif @@ -19,5 +51,6 @@ #ifndef LogDebug #define LogDebug( message ) #endif +/** @endcond */ #endif /* ifndef MQTT_INTERNAL_H_ */ diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c index 7a125e96a8..882efc8548 100644 --- a/libraries/standard/mqtt/utest/mqtt_state_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -30,7 +30,7 @@ int suiteTearDown( int numFailures ) static void resetPublishRecords( MQTTContext_t * pMqttContext ) { - int i = 0; + uint32_t i = 0; for( ; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ ) { @@ -59,7 +59,7 @@ static void fillRecord( MQTTPubAckInfo_t * records, MQTTQoS_t qos, MQTTPublishState_t state ) { - int i; + uint32_t i; for( i = 0; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ ) { @@ -725,7 +725,6 @@ void test_MQTT_PublishToResend( void ) { MQTTContext_t mqttContext = { 0 }; MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; - MQTTPublishState_t state = MQTTStateNull; uint16_t packetId; const uint16_t PACKET_ID = 1; const uint16_t PACKET_ID2 = 2; From 264b483c2faf7d4cc0643b591f5904dd50636e6c Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 18 Aug 2020 14:10:28 -0700 Subject: [PATCH 636/844] Hygiene changes in demo configuration (#1136) --- CMakeLists.txt | 16 ++++++++++++++++ demos/CMakeLists.txt | 14 -------------- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 10 ++-------- demos/mqtt/mqtt_demo_lightweight/demo_config.h | 5 ++--- demos/mqtt/mqtt_demo_plaintext/demo_config.h | 5 ++--- 5 files changed, 22 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b467eb88fd..bd03490fbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,20 @@ set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) +# Set prefix to PWD if any path flags are relative. +# PWD is set to the path where you run the cmake command. +if(DEFINED ENV{PWD}) + if(ROOT_CA_CERT_PATH AND NOT IS_ABSOLUTE ${ROOT_CA_CERT_PATH}) + set(ROOT_CA_CERT_PATH "$ENV{PWD}/${ROOT_CA_CERT_PATH}") + endif() + if(CLIENT_CERT_PATH AND NOT IS_ABSOLUTE ${CLIENT_CERT_PATH}) + set(CLIENT_CERT_PATH "$ENV{PWD}/${CLIENT_CERT_PATH}") + endif() + if(CLIENT_PRIVATE_KEY_PATH AND NOT IS_ABSOLUTE ${CLIENT_PRIVATE_KEY_PATH}) + set(CLIENT_PRIVATE_KEY_PATH "$ENV{PWD}/${CLIENT_PRIVATE_KEY_PATH}") + endif() +endif() + # Add libraries. add_subdirectory( libraries ) @@ -59,3 +73,5 @@ add_subdirectory( platform ) # Build the demos. add_subdirectory( demos ) + + diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index b11619e37b..6d02a2ff68 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -53,20 +53,6 @@ if(BUILD_TESTS) DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/tests") endif() -# Set prefix to PWD if any path flags are relative. -# PWD is set to the path where you run the cmake command. -if(DEFINED ENV{PWD}) - if(ROOT_CA_CERT_PATH AND NOT IS_ABSOLUTE ${ROOT_CA_CERT_PATH}) - set(ROOT_CA_CERT_PATH "$ENV{PWD}/${ROOT_CA_CERT_PATH}") - endif() - if(CLIENT_CERT_PATH AND NOT IS_ABSOLUTE ${CLIENT_CERT_PATH}) - set(CLIENT_CERT_PATH "$ENV{PWD}/${CLIENT_CERT_PATH}") - endif() - if(CLIENT_PRIVATE_KEY_PATH AND NOT IS_ABSOLUTE ${CLIENT_PRIVATE_KEY_PATH}) - set(CLIENT_PRIVATE_KEY_PATH "$ENV{PWD}/${CLIENT_PRIVATE_KEY_PATH}") - endif() -endif() - # Include each subdirectory that has a CMakeLists.txt file in it file(GLOB demo_dirs "${DEMOS_DIR}/*/*") foreach(demo_dir IN LISTS demo_dirs) diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 8cb6960055..3369db9822 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -56,10 +56,9 @@ * a Mosquitto broker locally. * Alternatively,instructions to run Mosquitto server on Docker container can * be viewed in the README.md of the root directory. + * + * #define BROKER_ENDPOINT "...insert here..." */ -#ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "localhost" -#endif /** * @brief MQTT server port number. @@ -76,11 +75,6 @@ * #define ROOT_CA_CERT_PATH ".....insert here...." */ -/** - * @brief Length of path to server certificate. - */ -#define ROOT_CA_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( ROOT_CA_CERT_PATH ) - 1 ) ) - /** * @brief MQTT client identifier. * diff --git a/demos/mqtt/mqtt_demo_lightweight/demo_config.h b/demos/mqtt/mqtt_demo_lightweight/demo_config.h index 348ae63c02..7ecf3eb6d8 100644 --- a/demos/mqtt/mqtt_demo_lightweight/demo_config.h +++ b/demos/mqtt/mqtt_demo_lightweight/demo_config.h @@ -56,10 +56,9 @@ * a Mosquitto broker locally. * Alternatively,instructions to run Mosquitto server on Docker container can * be viewed in the README.md of the root directory. + * + * #define BROKER_ENDPOINT "...insert here..." */ -#ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "localhost" -#endif /** * @brief MQTT server port number. diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index b0ec0c88c5..8be951cec3 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -56,10 +56,9 @@ * a Mosquitto broker locally. * Alternatively,instructions to run Mosquitto server on Docker container can * be viewed in the README.md of the root directory. + * + * #define BROKER_ENDPOINT "...insert here..." */ -#ifndef BROKER_ENDPOINT - #define BROKER_ENDPOINT "localhost" -#endif /** * @brief MQTT server port number. From 707cbe3a7b6c127e32d9e1846cc5fba56c6f4695 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 18 Aug 2020 15:21:30 -0700 Subject: [PATCH 637/844] Fix MQTT_GetSubAckStatusCodes API, and add CBMC proof for it (#1132) * Add CBMC proof for MQTT_GetSubAckStatusCodes function * Fix parameter type in API signature found by CBMC proof --- .../MQTT_GetSubAckStatusCodes_harness.c | 47 +++++++++++++++ .../proofs/MQTT_GetSubAckStatusCodes/Makefile | 35 +++++++++++ .../MQTT_GetSubAckStatusCodes/README.md | 10 ++++ .../MQTT_GetSubAckStatusCodes/cbmc-batch.yaml | 2 + .../cbmc-viewer.json | 7 +++ libraries/standard/mqtt/include/mqtt.h | 60 +++++++++---------- libraries/standard/mqtt/src/mqtt.c | 4 +- libraries/standard/mqtt/utest/mqtt_utest.c | 6 +- 8 files changed, 136 insertions(+), 35 deletions(-) create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/MQTT_GetSubAckStatusCodes_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/README.md create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-viewer.json diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/MQTT_GetSubAckStatusCodes_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/MQTT_GetSubAckStatusCodes_harness.c new file mode 100644 index 0000000000..8ca650230f --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/MQTT_GetSubAckStatusCodes_harness.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_GetSubAckStatusCodes_harness.c + * @brief Implements the proof harness for MQTT_GetSubAckStatusCodes function. + */ + +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + MQTTPacketInfo_t * pSubackPacket; + uint8_t ** pPayloadStart; + size_t * pPayloadSize; + + pSubackPacket = allocateMqttPacketInfo( NULL ); + __CPROVER_assume( isValidMqttPacketInfo( pSubackPacket ) ); + + /* pPayloadStart and pPayloadSize are output parameters, and + * thus, don't carry any assumptions. */ + pPayloadStart = mallocCanFail( sizeof( uint8_t * ) ); + pPayloadSize = mallocCanFail( sizeof( size_t ) ); + + MQTT_GetSubAckStatusCodes( pSubackPacket, + pPayloadStart, + pPayloadSize ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/Makefile new file mode 100644 index 0000000000..3c8ad8d093 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/Makefile @@ -0,0 +1,35 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_GetSubAckStatusCodes_harness + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/README.md b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/README.md new file mode 100644 index 0000000000..57f2c3af66 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/README.md @@ -0,0 +1,10 @@ +MQTT_GetSubAckStatusCodes proof +============== + +This directory contains a memory safety proof for MQTT_GetSubAckStatusCodes. + +To run the proof. +* Add cbmc, goto-cc, goto-instrument, goto-analyzer, and cbmc-viewer + to your path. +* Run "make". +* Open html/index.html in a web browser. \ No newline at end of file diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-viewer.json new file mode 100644 index 0000000000..95db5bf1d9 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_GetSubAckStatusCodes/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_GetSubAckStatusCodes", + "proof-root": "../../../../.." +} diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index 61d6a58921..a3554ac5b0 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -325,7 +325,7 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * * Example * @code{c} - * + * * // Variables used in this example. * MQTTStatus_t status; * MQTTConnectInfo_t connectInfo = { 0 }; @@ -339,7 +339,7 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * // Client ID must be unique to broker. This field is required. * connectInfo.pClientIdentifier = "someClientID"; * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier ); - * + * * // The following fields are optional. * // Value for keep alive. * connectInfo.keepAliveSeconds = 60; @@ -348,7 +348,7 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * connectInfo.userNameLength = strlen( connectInfo.pUserName ); * connectInfo.pPassword = "somePassword"; * connectInfo.passwordLength = strlen( connectInfo.pPassword ); - * + * * // The last will and testament is optional, it will be published by the broker * // should this client disconnect without sending a DISCONNECT packet. * willInfo.qos = MQTTQoS0; @@ -359,12 +359,12 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * * // Send the connect packet. Use 100 ms as the timeout to wait for the CONNACK packet. * status = MQTT_Connect( pContext, &connectInfo, &willInfo, 100, &sessionPresent ); - * + * * if( status == MQTTSuccess ) * { * // Since we requested a clean session, this must be false * assert( sessionPresent == false ); - * + * * // Do something with the connection. * } * @endcode @@ -389,7 +389,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. - * + * * Example * @code{c} * @@ -401,7 +401,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * MQTTContext_t * pContext; * // This is assumed to be a list of filters we want to subscribe to. * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ]; - * + * * // Set each subscription. * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) * { @@ -410,12 +410,12 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * subscriptionList[ i ].pTopicFilter = filters[ i ]; * subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] ); * } - * + * * // Obtain a new packet id for the subscription. * packetId = MQTT_GetPacketId( pContext ); - * + * * status = MQTT_Subscribe( pContext, &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId ); - * + * * if( status == MQTTSuccess ) * { * // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the SUBACK. @@ -440,29 +440,29 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. - * + * * Example * @code{c} - * + * * // Variables used in this example. * MQTTStatus_t status; * MQTTPublishInfo_t publishInfo; * uint16_t packetId; * // This context is assumed to be initialized and connected. * MQTTContext_t * pContext; - * + * * // QoS of publish. * publishInfo.qos = MQTTQoS1; * publishInfo.pTopicName = "/some/topic/name"; * publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); * publishInfo.pPayload = "Hello World!"; * publishInfo.payloadLength = strlen( "Hello World!" ); - * + * * // Packet ID is needed for QoS > 0. * packetId = MQTT_GetPacketId( pContext ); - * + * * status = MQTT_Publish( pContext, &publishInfo, packetId ); - * + * * if( status == MQTTSuccess ) * { * // Since the QoS is > 0, we will need to call MQTT_ReceiveLoop() @@ -500,7 +500,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); * #MQTTBadParameter if invalid parameters are passed; * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. - * + * * Example * @code{c} * @@ -512,7 +512,7 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); * MQTTContext_t * pContext; * // This is assumed to be a list of filters we want to unsubscribe from. * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ]; - * + * * // Set information for each unsubscribe request. * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) * { @@ -521,12 +521,12 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); * * // The QoS field of MQTT_SubscribeInfo_t is unused for unsubscribing. * } - * + * * // Obtain a new packet id for the unsubscribe request. * packetId = MQTT_GetPacketId( pContext ); - * + * * status = MQTT_Subscribe( pContext, &unsubscribeList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId ); - * + * * if( status == MQTTSuccess ) * { * // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the UNSUBACK. @@ -569,20 +569,20 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an * invalid transition for the internal state machine; * #MQTTSuccess on success. - * + * * Example * @code{c} - * + * * // Variables used in this example. * MQTTStatus_t status; * uint32_t timeoutMs = 100; * // This context is assumed to be initialized and connected. * MQTTContext_t * pContext; - * + * * while( true ) * { * status = MQTT_ProcessLoop( pContext, timeoutMs ); - * + * * if( status != MQTTSuccess ) * { * // Determine the error. It's possible we might need to disconnect TCP. @@ -617,18 +617,18 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * * Example * @code{c} - * + * * // Variables used in this example. * MQTTStatus_t status; * uint32_t timeoutMs = 100; * uint32_t keepAliveMs = 60 * 1000; * // This context is assumed to be initialized and connected. * MQTTContext_t * pContext; - * + * * while( true ) * { * status = MQTT_ReceiveLoop( pContext, timeoutMs ); - * + * * if( status != MQTTSuccess ) * { * // Determine the error. It's possible we might need to disconnect TCP. @@ -641,7 +641,7 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * { * status = MQTT_Ping( pContext ); * } - * + * * // Other application functions. * } * } @@ -686,7 +686,7 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); */ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, uint8_t ** pPayloadStart, - uint16_t * pPayloadSize ); + size_t * pPayloadSize ); /** * @brief Error code to string conversion for MQTT statuses. diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index ed7bf265e1..024ff2441d 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1981,7 +1981,7 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, uint8_t ** pPayloadStart, - uint16_t * pPayloadSize ) + size_t * pPayloadSize ) { MQTTStatus_t status = MQTTSuccess; @@ -2029,7 +2029,7 @@ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, * Therefore, we add 2 positions for the starting address of the payload, and * subtract 2 bytes from the remaining length for the length of the payload.*/ *pPayloadStart = pSubackPacket->pRemainingData + ( ( uint16_t ) sizeof( uint16_t ) ); - *pPayloadSize = ( uint16_t ) ( pSubackPacket->remainingLength - sizeof( uint16_t ) ); + *pPayloadSize = pSubackPacket->remainingLength - sizeof( uint16_t ); } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 69b49e59f7..e02caae0ad 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2126,13 +2126,13 @@ void test_MQTT_Ping_error_path( void ) /* ========================================================================== */ /** - * @brief Tests that MQTT_GetSubAckPayload works as expected in parsing the + * @brief Tests that MQTT_GetSubAckStatusCodes works as expected in parsing the * payload information of a SUBACK packet. */ -void test_MQTT_GetSubAckPayload( void ) +void test_MQTT_GetSubAckStatusCodes( void ) { MQTTPacketInfo_t mqttPacketInfo; - uint16_t payloadSize; + size_t payloadSize; uint8_t * pPayloadStart; MQTTStatus_t status = MQTTSuccess; uint8_t buffer[ 10 ] = { 0 }; From a9aa988195eb96959acbddc078c6ece921aa3b86 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 18 Aug 2020 15:38:02 -0700 Subject: [PATCH 638/844] [PR from CLI tool] Add utility for MQTT topic filter and topic matching to library (#1119) * Add topic matching utility to MQTT API * Add thorough unit tests for topic matching utility * Fix failing ExactMatch unit test * Add topic matching parameter names to mqtt/lexicon.txt * Fixes caught in code review Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Remove problematic assert in matchWildcardsSpecialCases and make hygiene parameter re-ordering changes * Add another param name to mqtt/lexicon.txt * Update topic matching to support corner case of matching topic filter ending with "/#" with topic name ending with "/" * Remove duplicate word entry in mqtt/lexicon.txt Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Fix bugs in topic matching algorithm that allowed matching when wildcards are not preceding with '/' * Slight style change for improved readability to matchWildcards function * Re-add param names to mqtt/lexicon.txt that were removed in merge conflict resolution * Rename private function for better expressivity * Revert "Rename private function for better expressivity" This reverts commit fe22642a4d723342dbb4c37f2df28c7aea9df0ca. * Hygiene changes for better readability * Add more cases to unit tests * Hygiene changes for invoking matchEndWildcardsSpecialCases function * More hygiene refactors from review comments Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> * Separate Match and no-match test case sets for # willdcard * Remove extraneous param docstring for private function * Add note to MatchTopic API about assumption and behavior of function Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> --- libraries/standard/mqtt/include/mqtt.h | 27 ++ libraries/standard/mqtt/lexicon.txt | 6 + libraries/standard/mqtt/src/mqtt.c | 314 ++++++++++++++++ libraries/standard/mqtt/utest/mqtt_utest.c | 397 +++++++++++++++++++++ 4 files changed, 744 insertions(+) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index a3554ac5b0..eb5eb705fb 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -659,6 +659,33 @@ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, */ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); +/** + * @brief A utility function that determines whether the passed topic filter and + * topic name match according to the MQTT 3.1.1 protocol specification. + * + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of the topic name. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of topic filter. + * @param[out] pIsMatch This is filled with the whether there + * exists a match or not. + * + * @note The API assumes that the passed topic name is valid to meet the + * requirements of the MQTT 3.1.1 specification. Invalid topic names (for example, + * containing wildcard characters) should not be passed to the function. + * Also, the API checks validity of topic filter for wildcard characters ONLY if + * the passed topic name and topic filter do not have an exact string match. + * + * @return Returns one of the following: + * - #MQTTBadParameter, if any of the input parameters is invalid. + * - #MQTTSuccess, if the matching operation was performed. + */ +MQTTStatus_t MQTT_MatchTopic( const char * pTopicName, + const uint16_t topicNameLength, + const char * pTopicFilter, + const uint16_t topicFilterLength, + bool * pIsMatch ); + /** * @brief Parses the payload of an MQTT SUBACK packet that contains status codes * corresponding to topic filter subscription requests from the original diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index ea6adc9116..b9d5920a69 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -58,6 +58,7 @@ enums eventcallback eventcallbackstub expectprocessloopcalls +filterindex fixedbuffer getconnectpacketsize getcurrenttimestub @@ -147,6 +148,7 @@ mqttsubackstatus mqttsubscribeinfo mqttsuccess msb +nameindex networkbuffer networkcontext networkinterfacesendstub @@ -188,8 +190,11 @@ pingreq pingreqsendtimems pingresp pingresps +pismatch +pmatch pmessage pmqttcontext +pnameindex pnetworkbuffer pnetworkcontext pnetworkinterface @@ -287,6 +292,7 @@ subscribeinfo subscriptioncount subscriptionlist subscriptiontype +sys tcp tcpsocket testcase diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 024ff2441d..dcae57ecbb 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -317,6 +317,251 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ); +/** + * topic filter, this function handles the following 2 cases: + * - When the topic filter ends with "/+" or "/#" characters, but the topic + * name only ends with '/'. + * - When the topic filter ends with "/#" characters, but the topic name + * ends at the parent level. + * + * @note This function ASSUMES that the topic name been consumed in linear + * matching with the topic filer, but the topic filter has remaining characters + * to be matched. + * + * @param[in] pTopicFilter The topic filter containing the wildcard. + * @param[in] topicFilterLength Length of the topic filter being examined. + * @param[in] filterIndex Index of the topic filter being examined. + * + * @return Returns whether the topic filter and the topic name match. + */ +static bool matchEndWildcardsSpecialCases( const char * pTopicFilter, + uint16_t topicFilterLength, + uint16_t filterIndex ); + +/** + * @brief Attempt to match topic name with a topic filter starting with a wildcard. + * + * If the topic filter starts with a '+' (single-level) wildcard, the function + * advances the @a pNameIndex by a level in the topic name. + * If the topic filter starts with a '#' (multi-level) wildcard, the function + * concludes that both the topic name and topic filter match. + * + * @param[in] pTopicName The topic name to match. + * @param[in] topicNameLength Length of the topic name. + * @param[in] pTopicFilter The topic filter to match. + * @param[in] topicFilterLength Length of the topic filter. + * @param[in,out] pNameIndex Current index in topic name being examined.. It is + * advanced by one level for `+` wildcards. + * @param[in] filterIndex Current index in the topic filter being examined.. + * @param[out] pMatch Whether the topic filter and topic name match. + * + * @return `true` if the caller of this function should exit; `false` if the + * caller should continue parsing the topics. + */ +static bool matchWildcards( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength, + uint16_t * pNameIndex, + uint16_t filterIndex, + bool * pMatch ); + +/** + * @brief Match a topic name and topic filter allowing the use of wildcards. + * + * @param[in] pTopicName The topic name to check. + * @param[in] topicNameLength Length of the topic name. + * @param[in] pTopicFilter The topic filter to check. + * @param[in] topicFilterLength Length of topic filter. + * + * @return `true` if the topic name and topic filter match; `false` otherwise. + */ +static bool matchTopicFilter( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength ); + +/*-----------------------------------------------------------*/ + +static bool matchEndWildcardsSpecialCases( const char * pTopicFilter, + uint16_t topicFilterLength, + uint16_t filterIndex ) +{ + bool matchFound = false; + + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + + /* Check if the topic filter has 2 remaining characters and it ends in + * "/#". This check handles the case to match filter "sport/#" with topic + * "sport". The reason is that the '#' wildcard represents the parent and + * any number of child levels in the topic name.*/ + if( ( filterIndex == ( topicFilterLength - 3U ) ) && + ( pTopicFilter[ filterIndex + 1U ] == '/' ) && + ( pTopicFilter[ filterIndex + 2U ] == '#' ) ) + + { + matchFound = true; + } + + /* Check if the next character is "#" or "+" and the topic filter ends in + * "/#" or "/+". This check handles the cases to match: + * + * - Topic filter "sport/+" with topic "sport/". + * - Topic filter "sport/#" with topic "sport/". + */ + if( ( filterIndex == ( topicFilterLength - 2U ) ) && + ( pTopicFilter[ filterIndex ] == '/' ) ) + { + /* Check that the last character is a wildcard. */ + matchFound = ( ( pTopicFilter[ filterIndex + 1U ] == '+' ) || + ( pTopicFilter[ filterIndex + 1U ] == '#' ) ) ? true : false; + } + + return matchFound; +} + +/*-----------------------------------------------------------*/ + +static bool matchWildcards( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength, + uint16_t * pNameIndex, + uint16_t filterIndex, + bool * pMatch ) +{ + bool shouldStopMatching = false; + bool locationIsValidForWildcard; + + assert( pTopicName != NULL ); + assert( topicNameLength != 0 ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + assert( pNameIndex != NULL ); + assert( pMatch != NULL ); + + /* Wild card in a topic filter is only valid either at the starting position + * or when it is preceded by a '/'.*/ + locationIsValidForWildcard = ( ( filterIndex == 0u ) || + ( pTopicFilter[ filterIndex - 1U ] == '/' ) + ) ? true : false; + + if( locationIsValidForWildcard == true ) + { + if( pTopicFilter[ filterIndex ] == '+' ) + { + /* Move topic name index to the end of the current level. The end of the + * current level is identified by '/'. */ + while( ( *pNameIndex < topicNameLength ) && ( pTopicName[ *pNameIndex ] != '/' ) ) + { + ( *pNameIndex )++; + } + + /* Decrement the topic name index for 2 different cases: + * - If the break condition is ( *pNameIndex < topicNameLength ), then + * we have reached past the end of the topic name and we move back the + * the index on the last character. + * - If the break condition is ( pTopicName[ *pNameIndex ] != '/' ), we + * move back the index on the '/' character. */ + ( *pNameIndex )--; + } + + /* '#' matches everything remaining in the topic name. It must be the + * last character in a topic filter. */ + else if( ( pTopicFilter[ filterIndex ] == '#' ) && + ( filterIndex == ( topicFilterLength - 1U ) ) ) + { + /* Subsequent characters don't need to be checked for the + * multi-level wildcard. */ + *pMatch = true; + shouldStopMatching = true; + } + else + { + /* Any character mismatch other than '+' or '#' means the topic + * name does not match the topic filter. */ + *pMatch = false; + shouldStopMatching = true; + } + } + else + { + /* If the location is not valid for a wildcard, the topic name does not + * match the topic filter. */ + *pMatch = false; + shouldStopMatching = true; + } + + return shouldStopMatching; +} + +/*-----------------------------------------------------------*/ + +static bool matchTopicFilter( const char * pTopicName, + uint16_t topicNameLength, + const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + bool matchFound = false, shouldStopMatching = false; + uint16_t nameIndex = 0, filterIndex = 0; + + assert( pTopicName != NULL ); + assert( topicNameLength != 0 ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + + while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) + { + /* Check if the character in the topic name matches the corresponding + * character in the topic filter string. */ + if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) + { + /* If the topic name has been consumed but the topic filter has not + * been consumed, match for special cases when the topic filter ends + * with wildcard character. */ + if( nameIndex == ( topicNameLength - 1U ) ) + { + matchFound = matchEndWildcardsSpecialCases( pTopicFilter, + topicFilterLength, + filterIndex ); + } + } + else + { + /* Check for matching wildcards. */ + shouldStopMatching = matchWildcards( pTopicName, + topicNameLength, + pTopicFilter, + topicFilterLength, + &nameIndex, + filterIndex, + &matchFound ); + } + + if( ( matchFound == true ) || ( shouldStopMatching == true ) ) + { + break; + } + + /* Increment indexes. */ + nameIndex++; + filterIndex++; + } + + if( matchFound == false ) + { + /* If the end of both strings has been reached, they match. This represents the + * case when the topic filter contains the '+' wildcard at a non-starting position. + * For example, when matching either of "sport/+/player" OR "sport/hockey/+" topic + * filters with "sport/hockey/player" topic name. */ + matchFound = ( ( nameIndex == topicNameLength ) && + ( filterIndex == topicFilterLength ) ) ? true : false; + } + + return matchFound; +} + /*-----------------------------------------------------------*/ static int32_t sendPacket( MQTTContext_t * pContext, @@ -1979,6 +2224,75 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ) /*-----------------------------------------------------------*/ +MQTTStatus_t MQTT_MatchTopic( const char * pTopicName, + const uint16_t topicNameLength, + const char * pTopicFilter, + const uint16_t topicFilterLength, + bool * pIsMatch ) +{ + MQTTStatus_t status = MQTTSuccess; + bool topicFilterStartsWithWildcard = false; + bool matchStatus = false; + + if( ( pTopicName == NULL ) || ( topicNameLength == 0u ) ) + { + LogError( ( "Invalid paramater: Topic name should be non-NULL and its " + "length should be > 0: TopicName=%p, TopicNameLength=%u", + ( void * ) pTopicName, + topicNameLength ) ); + + status = MQTTBadParameter; + } + else if( ( pTopicFilter == NULL ) || ( topicFilterLength == 0u ) ) + { + LogError( ( "Invalid paramater: Topic filter should be non-NULL and " + "its length should be > 0: TopicName=%p, TopicFilterLength=%u", + ( void * ) pTopicFilter, + topicFilterLength ) ); + status = MQTTBadParameter; + } + else if( pIsMatch == NULL ) + { + LogError( ( "Invalid paramater: Output parameter, pIsMatch, is NULL" ) ); + status = MQTTBadParameter; + } + else + { + /* Check for an exact match if the incoming topic name and the registered + * topic filter length match. */ + if( topicNameLength == topicFilterLength ) + { + matchStatus = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ) ? true : false; + } + + if( matchStatus == false ) + { + /* If an exact match was not found, match against wildcard characters in + * topic filter.*/ + + /* Determine if topic filter starts with a wildcard. */ + topicFilterStartsWithWildcard = ( ( pTopicFilter[ 0 ] == '+' ) || + ( pTopicFilter[ 0 ] == '#' ) ) ? true : false; + + /* Note: According to the MQTT 3.1.1 specification, incoming PUBLISH topic names + * starting with "$" character cannot be matched against topic filter starting with + * a wildcard, i.e. for example, "$SYS/sport" cannot be matched with "#" or + * "+/sport" topic filters. */ + if( !( ( pTopicName[ 0 ] == '$' ) && ( topicFilterStartsWithWildcard == true ) ) ) + { + matchStatus = matchTopicFilter( pTopicName, topicNameLength, pTopicFilter, topicFilterLength ); + } + } + + /* Update the output parameter with the match result. */ + *pIsMatch = matchStatus; + } + + return status; +} + +/*-----------------------------------------------------------*/ + MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, uint8_t ** pPayloadStart, size_t * pPayloadSize ) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index e02caae0ad..c975c8372c 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2125,6 +2125,403 @@ void test_MQTT_Ping_error_path( void ) /* ========================================================================== */ +/** + * @brief Test MQTT_MatchTopic for invalid input parameters. + */ +void test_MQTT_MatchTopic_InvalidInput( void ) +{ + bool matchResult = false; + + /* NULL topic name. */ + TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( NULL, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid topic name length. */ + TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER, + 0u, + MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* NULL topic filter. */ + TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + NULL, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid topic filter length. */ + TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + MQTT_SAMPLE_TOPIC_FILTER, + 0u, + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid output parameter. */ + TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + NULL ) ); +} + +/** + * @brief Verifies that MQTT_MatchTopic is able to determine an exact match between the + * topic name and topic filter. + */ +void test_MQTT_MatchTopic_ExactMatch( void ) +{ + const char * pTopicFilter = NULL; + const char * pTopicName = NULL; + bool matchResult = false; + + /* Test for topic filter and topic name having exact match. */ + pTopicName = "/test/match"; + pTopicFilter = "/test/match"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test for exact match when topic name and filter start with '$' .*/ + pTopicName = "$///"; + pTopicFilter = "$///"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test for no match (with no wildcard in the topic filter). */ + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + MQTT_SAMPLE_TOPIC_FILTER, + MQTT_SAMPLE_TOPIC_FILTER_LENGTH, + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); +} + +/** + * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification of all + * cases of matching topic filters that contain the single-level '+' wildcard. + */ +void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) +{ + const char * pTopicName = NULL; + const char * pTopicFilter = NULL; + bool matchResult = false; + + /* Nominal case of topic filter ending with '+' .*/ + pTopicName = "/test/match/level1"; + pTopicFilter = "/test/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test for match with a topic name starting with '$' .*/ + pTopicName = "$test/match/level1"; + pTopicFilter = "$test/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test that match fails when topic name has more levels than topic filter. */ + pTopicName = "/test/match/level1/level2"; + pTopicFilter = "/test/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Test with '+' as the topic filter. */ + pTopicName = "test"; + pTopicFilter = "+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test that '+' in topic filter matches topic name containing consecutive + * level separators, "//" in the corresponding level. */ + pTopicName = "/test//level1"; + pTopicFilter = "/test/+/level1"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test with multiple placements of wildcard in topic filter. */ + pTopicName = "/test/match/level1"; + pTopicFilter = "/+/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + pTopicName = "/test/match/level1"; + pTopicFilter = "+/+/+/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + pTopicName = "/test///level1"; + pTopicFilter = "/test/+/+/level1"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Edge case where filter ending with '/+' matches topic ending with '/'. */ + pTopicName = "/test/match/"; + pTopicFilter = "/test/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Edge case where filter ending with '/+' should not match a topic ending with + * at parent level. */ + pTopicName = "/test/match"; + pTopicFilter = "/test/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Edge case where topic filter starts with '+' and topic name starts with '$'. */ + pTopicName = "$/test/match"; + pTopicFilter = "+/test/match"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Topic name matches all characters with topic filter, but topic filter is invalid. */ + pTopicName = "test/match/level"; + pTopicFilter = "test/match/level+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid topic filter where non-starting '+' is not placed after '/'.*/ + pTopicName = "test/match/level1"; + pTopicFilter = "test/match/level+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); +} + +/** + * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification of all + * cases of matching topic filters that contain the multi-level '#' wildcard. + */ +void test_MQTT_MatchTopic_Wildcard_MultiLevel_Match_Cases( void ) +{ + const char * pTopicName = NULL; + const char * pTopicFilter = NULL; + bool matchResult = false; + + /* Match topic filter ending with '#' with a single level .*/ + pTopicName = "/test/match/level1"; + pTopicFilter = "/test/match/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Match topic filter ending with '#' with multiple levels in topic name.*/ + pTopicName = "/test/match/level1/level2/level3"; + pTopicFilter = "/test/match/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + pTopicName = "/test/match/level1/level2/level3"; + pTopicFilter = "/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Match when topic filter is "#" */ + pTopicName = "test/match/level"; + pTopicFilter = "#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test for match with a topic name starting with '$' .*/ + pTopicName = "$test/match/level1"; + pTopicFilter = "$test/match/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Match '#' with topic name ending at the parent level. */ + pTopicName = "/test/match"; + pTopicFilter = "/test/match/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Edge case where filter ending with '/#' matches topic ending with '/'. */ + pTopicName = "/test/match/"; + pTopicFilter = "/test/match/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + /* Test for topic filters containing both '+' and '#' wildcard characters. */ + pTopicName = "/test/match"; + pTopicFilter = "+/test/match/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); + + pTopicName = "/test/match/level"; + pTopicFilter = "+/+/+/#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( true, matchResult ); +} + +/** + * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification for + * cases of where topic filter containing '#' wildcard do not match topic name. + */ +void test_MQTT_MatchTopic_Wildcard_MultiLevel_No_Match_Cases( void ) +{ + const char * pTopicName = NULL; + const char * pTopicFilter = NULL; + bool matchResult = false; + + /* Edge case where topic filter starts with '#' and topic name starts with '$'. */ + pTopicName = "$/test/match"; + pTopicFilter = "#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid topic filter where non-starting '#' is not placed after '/'.*/ + pTopicName = "test/match/level1"; + pTopicFilter = "test/match/level#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Topic name matches all characters with topic filter, but topic filter is invalid. */ + pTopicName = "test/match/level"; + pTopicFilter = "test/match/level?#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + pTopicName = "test/match/level"; + pTopicFilter = "test/match/level#"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid topic filters that contain '#' at a non-ending position .*/ + pTopicName = "test/match/level1/level2"; + pTopicFilter = "test/match/#/level2"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + pTopicName = "test/match/level2"; + pTopicFilter = "#/match/level2"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); +} + +/* ========================================================================== */ + /** * @brief Tests that MQTT_GetSubAckStatusCodes works as expected in parsing the * payload information of a SUBACK packet. From 71df018badd91f92b51f8923b820af9cc3567a23 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Tue, 18 Aug 2020 16:57:16 -0700 Subject: [PATCH 639/844] Add doxygen code examples for mqtt_lightweight.h (#1135) * Add doxygen code examples for mqtt_lightweight.h * Comment fixes in mqtt.h Co-authored-by: SarenaAWS <6563840+sarenameas@users.noreply.github.com> --- libraries/standard/mqtt/include/mqtt.h | 10 +- .../standard/mqtt/include/mqtt_lightweight.h | 541 +++++++++++++++++- libraries/standard/mqtt/lexicon.txt | 7 + 3 files changed, 552 insertions(+), 6 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index eb5eb705fb..b2be8ce4cd 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -232,7 +232,7 @@ typedef struct MQTTDeserializedInfo * void eventCallback( * MQTTContext_t * pContext, * MQTTPacketInfo_t * pPacketInfo, - * MQTTDeserializedInfo_t * pDeserialized + * MQTTDeserializedInfo_t * pDeserializedInfo * ); * // Network send. * int32_t networkSend( NetworkContext_t * pContext, const void * pBuffer, size_t bytes ); @@ -406,7 +406,7 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) * { * subscriptionList[ i ].qos = MQTTQoS0; - * // Each subscription needs a topic filter + * // Each subscription needs a topic filter. * subscriptionList[ i ].pTopicFilter = filters[ i ]; * subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] ); * } @@ -585,7 +585,8 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); * * if( status != MQTTSuccess ) * { - * // Determine the error. It's possible we might need to disconnect TCP. + * // Determine the error. It's possible we might need to disconnect + * // the underlying transport connection. * } * else * { @@ -631,7 +632,8 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * * if( status != MQTTSuccess ) * { - * // Determine the error. It's possible we might need to disconnect TCP. + * // Determine the error. It's possible we might need to disconnect + * // the underlying transport connection. * } * else * { diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 3961541798..048457c109 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -267,6 +267,33 @@ typedef struct MQTTPacketInfo * * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec; #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTConnectInfo_t connectInfo = { 0 }; + * MQTTPublishInfo_t willInfo = { 0 }; + * size_t remainingLength = 0, packetSize = 0; + * + * // Initialize the connection info, the details are out of scope for this example. + * initializeConnectInfo( &connectInfo ); + * + * // Initialize the optional will info, the details are out of scope for this example. + * initializeWillInfo( &willInfo ); + * + * // Get the size requirement for the connect packet. + * status = MQTT_GetConnectPacketSize( + * &connectInfo, &willInfo, &remainingLength, &packetSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the connect request. + * } + * @endcode */ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, @@ -290,6 +317,37 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTConnectInfo_t connectInfo = { 0 }; + * MQTTPublishInfo_t willInfo = { 0 }; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * size_t remainingLength = 0, packetSize = 0; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // Assume connectInfo and willInfo are initialized. Get the size requirement for + * // the connect packet. + * status = MQTT_GetConnectPacketSize( + * &connectInfo, &willInfo, &remainingLength, &packetSize + * ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the connect packet into the fixed buffer. + * status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer ); + * + * if( status == MQTTSuccess ) + * { + * // The connect packet can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, @@ -315,6 +373,37 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, * * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec; #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 }; + * size_t remainingLength = 0, packetSize = 0; + * // This is assumed to be a list of filters we want to subscribe to. + * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ]; + * + * // Set each subscription. + * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ ) + * { + * subscriptionList[ i ].qos = MQTTQoS0; + * // Each subscription needs a topic filter. + * subscriptionList[ i ].pTopicFilter = filters[ i ]; + * subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] ); + * } + * + * // Get the size requirement for the subscribe packet. + * status = MQTT_GetSubscribePacketSize( + * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the subscribe request. + * } + * @endcode */ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, @@ -339,6 +428,46 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscript * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 }; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * size_t remainingLength = 0, packetSize = 0; + * uint16_t packetId; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // Function to return a valid, unused packet identifier. The details are out of + * // scope for this example. + * packetId = getNewPacketId(); + * + * // Assume subscriptionList has been initialized. Get the subscribe packet size. + * status = MQTT_GetSubscribePacketSize( + * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize + * ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the subscribe packet into the fixed buffer. + * status = MQTT_SerializeSubscribe( + * &subscriptionList[ 0 ], + * NUMBER_OF_SUBSCRIPTIONS, + * packetId, + * remainingLength, + * &fixedBuffer + * ); + * + * if( status == MQTTSuccess ) + * { + * // The subscribe packet can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, @@ -365,6 +494,29 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL * * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec; #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 }; + * size_t remainingLength = 0, packetSize = 0; + * + * // Initialize the subscribe info. The details are out of scope for this example. + * initializeSubscribeInfo( &subscriptionList[ 0 ] ); + * + * // Get the size requirement for the unsubscribe packet. + * status = MQTT_GetUnsubscribePacketSize( + * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the unsubscribe request. + * } + * @endcode */ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, @@ -389,6 +541,46 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscri * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 }; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * size_t remainingLength = 0, packetSize = 0; + * uint16_t packetId; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // Function to return a valid, unused packet identifier. The details are out of + * // scope for this example. + * packetId = getNewPacketId(); + * + * // Assume subscriptionList has been initialized. Get the unsubscribe packet size. + * status = MQTT_GetUnsubscribePacketSize( + * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize + * ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the unsubscribe packet into the fixed buffer. + * status = MQTT_SerializeUnsubscribe( + * &subscriptionList[ 0 ], + * NUMBER_OF_SUBSCRIPTIONS, + * packetId, + * remainingLength, + * &fixedBuffer + * ); + * + * if( status == MQTTSuccess ) + * { + * // The unsubscribe packet can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, @@ -414,6 +606,33 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio * * @return #MQTTBadParameter if the packet would exceed the size allowed by the * MQTT spec or if invalid parameters are passed; #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPublishInfo_t publishInfo = { 0 }; + * size_t remainingLength = 0, packetSize = 0; + * + * // Initialize the publish info. + * publishInfo.qos = MQTTQoS0; + * publishInfo.pTopicName = "/some/topic/name"; + * publishInfo.topicNameLength = strlen( publishInfo.pTopicName ); + * publishInfo.pPayload = "Hello World!"; + * publishInfo.payloadLength = strlen( "Hello World!" ); + * + * // Get the size requirement for the publish packet. + * status = MQTT_GetPublishPacketSize( + * &publishInfo, &remainingLength, &packetSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The application should allocate or use a static #MQTTFixedBuffer_t + * // of size >= packetSize to serialize the publish. + * } + * @endcode */ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, size_t * pRemainingLength, @@ -441,6 +660,45 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPublishInfo_t publishInfo = { 0 }; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * size_t remainingLength = 0, packetSize = 0; + * uint16_t packetId; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // A packet identifier is unused for QoS 0 publishes. Otherwise, a valid, unused packet + * // identifier must be used. + * packetId = 0; + * + * // Assume publishInfo has been initialized. Get publish packet size. + * status = MQTT_GetPublishPacketSize( + * &publishInfo, &remainingLength, &packetSize + * ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the publish packet into the fixed buffer. + * status = MQTT_SerializePublish( + * &publishInfo, + * packetId, + * remainingLength, + * &fixedBuffer + * ); + * + * if( status == MQTTSuccess ) + * { + * // The publish packet can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, @@ -471,6 +729,54 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPublishInfo_t publishInfo = { 0 }; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * size_t remainingLength = 0, packetSize = 0, headerSize = 0; + * uint16_t packetId; + * int32_t bytesSent; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // A packet identifier is unused for QoS 0 publishes. Otherwise, a valid, unused packet + * // identifier must be used. + * packetId = 0; + * + * // Assume publishInfo has been initialized. Get the publish packet size. + * status = MQTT_GetPublishPacketSize( + * &publishInfo, &remainingLength, &packetSize + * ); + * assert( status == MQTTSuccess ); + * // The payload will not be serialized, so the the fixed buffer does not need to hold it. + * assert( ( packetSize - publishInfo.payloadLength ) <= BUFFER_SIZE ); + * + * // Serialize the publish packet header into the fixed buffer. + * status = MQTT_SerializePublishHeader( + * &publishInfo, + * packetId, + * remainingLength, + * &fixedBuffer, + * &headerSize + * ); + * + * if( status == MQTTSuccess ) + * { + * // The publish header and payload can now be sent to the broker. + * // mqttSocket here is a socket descriptor created and connected to the MQTT + * // broker outside of this function. + * bytesSent = send( mqttSocket, ( void * ) fixedBuffer.pBuffer, headerSize, 0 ); + * assert( bytesSent == headerSize ); + * bytesSent = send( mqttSocket, publishInfo.pPayload, publishInfo.payloadLength, 0 ); + * assert( bytesSent == publishInfo.payloadLength ); + * } + * @endcode */ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, @@ -488,6 +794,36 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo * @param[in] packetId Packet ID of the publish. * * @return #MQTTBadParameter, #MQTTNoMemory, or #MQTTSuccess. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * uint16_t packetId; + * uint8_t packetType; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * // The fixed buffer must be large enough to hold 4 bytes. + * assert( BUFFER_SIZE >= MQTT_PUBLISH_ACK_PACKET_SIZE ); + * + * // The packet ID must be the same as the original publish packet. + * packetId = publishPacketId; + * + * // The byte representing a packet of type ACK. This function accepts PUBACK, PUBREC, PUBREL, or PUBCOMP. + * packetType = MQTT_PACKET_TYPE_PUBACK; + * + * // Serialize the publish acknowledgment into the fixed buffer. + * status = MQTT_SerializeAck( &fixedBuffer, packetType, packetId ); + * + * if( status == MQTTSuccess ) + * { + * // The publish acknowledgment can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, uint8_t packetType, @@ -498,7 +834,24 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, * * @param[out] pPacketSize The size of the MQTT DISCONNECT packet. * - * @return Always returns #MQTTSuccess. + * @return #MQTTSuccess, or #MQTTBadParameter if @p pPacketSize is NULL. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * size_t packetSize = 0; + * + * // Get the size requirement for the disconnect packet. + * status = MQTT_GetDisconnectPacketSize( &packetSize ); + * assert( status == MQTTSuccess ); + * assert( packetSize == 2 ); + * + * // The application should allocate or use a static #MQTTFixedBuffer_t of + * // size >= 2 to serialize the disconnect packet. + * + * @endcode */ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); @@ -513,6 +866,31 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // Get the disconnect packet size. + * status = MQTT_GetDisconnectPacketSize( &packetSize ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the disconnect into the fixed buffer. + * status = MQTT_SerializeDisconnect( &fixedBuffer ); + * + * if( status == MQTTSuccess ) + * { + * // The disconnect packet can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ); @@ -522,6 +900,23 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ); * @param[out] pPacketSize The size of the MQTT PINGREQ packet. * * @return #MQTTSuccess or #MQTTBadParameter if pPacketSize is NULL. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * size_t packetSize = 0; + * + * // Get the size requirement for the ping request packet. + * status = MQTT_GetPingreqPacketSize( &packetSize ); + * assert( status == MQTTSuccess ); + * assert( packetSize == 2 ); + * + * // The application should allocate or use a static #MQTTFixedBuffer_t of + * // size >= 2 to serialize the ping request. + * + * @endcode */ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); @@ -536,6 +931,31 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTFixedBuffer_t fixedBuffer; + * uint8_t buffer[ BUFFER_SIZE ]; + * + * fixedBuffer.pBuffer = buffer; + * fixedBuffer.size = BUFFER_SIZE; + * + * // Get the ping request packet size. + * status = MQTT_GetPingreqPacketSize( &packetSize ); + * assert( status == MQTTSuccess ); + * assert( packetSize <= BUFFER_SIZE ); + * + * // Serialize the ping request into the fixed buffer. + * status = MQTT_SerializePingreq( &fixedBuffer ); + * + * if( status == MQTTSuccess ) + * { + * // The ping request can now be sent to the broker. + * } + * @endcode */ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); @@ -547,6 +967,54 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); * @param[out] pPublishInfo Struct containing information about the publish. * * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. + * + * Example + * @code{c} + * + * // TransportRecv_t function for reading from the network. + * int32_t socket_recv( + * NetworkContext_t * pNetworkContext, + * void * pBuffer, + * size_t bytesToRecv + * ); + * // Some context to be used with the above transport receive function. + * NetworkContext_t networkContext; + * + * // Other variables used in this example. + * MQTTStatus_t status; + * MQTTPacketInfo_t incomingPacket; + * MQTTPublishInfo_t publishInfo = { 0 }; + * uint16_t packetId; + * + * int32_t bytesRecvd; + * // A buffer to hold remaining data of the incoming packet. + * uint8_t buffer[ BUFFER_SIZE ]; + * + * // Populate all fields of the incoming packet. + * status = MQTT_GetIncomingPacketTypeAndLength( + * socket_recv, + * &networkContext, + * &incomingPacket + * ); + * assert( status == MQTTSuccess ); + * assert( incomingPacket.remainingLength <= BUFFER_SIZE ); + * bytesRecvd = socket_recv( + * &networkContext, + * ( void * ) buffer, + * incomingPacket.remainingLength + * ); + * incomingPacket.pRemainingData = buffer; + * + * // Deserialize the publish information if the incoming packet is a publish. + * if( ( incomingPacket.type & 0xF0 ) == MQTT_PACKET_TYPE_PUBLISH ) + * { + * status = MQTT_DeserializePublish( &incomingPacket, &packetId, &publishInfo ); + * if( status == MQTTSuccess ) + * { + * // The deserialized publish information can now be used from `publishInfo`. + * } + * } + * @endcode */ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, uint16_t * pPacketId, @@ -561,7 +1029,34 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, * in CONNACK or PINGRESP. * @param[out] pSessionPresent Boolean flag from a CONNACK indicating present session. * - * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess. + * @return #MQTTBadParameter, #MQTTBadResponse, #MQTTServerRefused, or #MQTTSuccess. + * + * Example + * @code{c} + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTPacketInfo_t incomingPacket; + * // Used for SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP. + * uint16_t packetId; + * // Used for CONNACK. + * bool sessionPresent; + * + * // Receive an incoming packet and populate all fields. The details are out of scope + * // for this example. + * receiveIncomingPacket( &incomingPacket ); + * + * // Deserialize ack information if the incoming packet is not a publish. + * if( ( incomingPacket.type & 0xF0 ) != MQTT_PACKET_TYPE_PUBLISH ) + * { + * status = MQTT_DeserializeAck( &incomingPacket, &packetId, &sessionPresent ); + * if( status == MQTTSuccess ) + * { + * // The packet ID or session present flag information is available. For + * // ping response packets, the only information is the status code. + * } + * } + * @endcode */ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, uint16_t * pPacketId, @@ -584,6 +1079,48 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, * #MQTTRecvFailed on transport receive failure, * #MQTTBadResponse if an invalid packet is read, and * #MQTTNoDataAvailable if there is nothing to read. + * + * Example + * @code{c} + * + * // TransportRecv_t function for reading from the network. + * int32_t socket_recv( + * NetworkContext_t * pNetworkContext, + * void * pBuffer, + * size_t bytesToRecv + * ); + * // Some context to be used with above transport receive function. + * NetworkContext_t networkContext; + * + * // Struct to hold the incoming packet information. + * MQTTPacketInfo_t incomingPacket; + * MQTTStatus_t status = MQTTSuccess; + * int32_t bytesRecvd; + * // Buffer to hold the remaining data of the incoming packet. + * uint8_t buffer[ BUFFER_SIZE ]; + * + * // Loop until data is available to be received. + * do{ + * status = MQTT_GetIncomingPacketTypeAndLength( + * socket_recv, + * &networkContext, + * &incomingPacket + * ); + * } while( status == MQTTNoDataAvailable ); + * + * assert( status == MQTTSuccess ); + * + * // Receive the rest of the incoming packet. + * assert( incomingPacket.remainingLength <= BUFFER_SIZE ); + * bytesRecvd = socket_recv( + * &networkContext, + * ( void * ) buffer, + * incomingPacket.remainingLength + * ); + * + * // Set the remaining data field. + * incomingPacket.pRemainingData = buffer; + * @endcode */ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, NetworkContext_t * pNetworkContext, diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index b9d5920a69..ac14073240 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -64,6 +64,7 @@ getconnectpacketsize getcurrenttimestub getdisconnectpacketsize getincomingpackettypeandlength +getnewpacketid getpacketid getpingreqpacketsize getpublishpacketsize @@ -84,6 +85,9 @@ inc incomingpacket incomingpublish init +initializeconnectinfo +initializesubscribeinfo +initializewillinfo int iot isduplicate @@ -140,6 +144,7 @@ mqttqos mqttrecvfailed mqttsendfailed mqttserverrefused +mqttsocket mqttstatecollision mqttstatenull mqttstatus @@ -233,6 +238,7 @@ pubcomp pubcomps publishflags publishinfo +publishpacketid publishstate pubrec pubrecs @@ -242,6 +248,7 @@ pusername pwillinfo qos readfunc +receiveincomingpacket receiveloop receivepacket recordcount From 5ae868b852447d869b56c11f982af84a24be4993 Mon Sep 17 00:00:00 2001 From: Dan Good <49254594+dan4thewin@users.noreply.github.com> Date: Tue, 18 Aug 2020 23:23:29 -0400 Subject: [PATCH 640/844] MISRA compliance changes (#1107) --- libraries/standard/json/include/json.h | 6 +++--- libraries/standard/json/lexicon.txt | 1 + libraries/standard/json/src/json.c | 27 ++++++++++++++++---------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/libraries/standard/json/include/json.h b/libraries/standard/json/include/json.h index a16824982b..2397cdf038 100644 --- a/libraries/standard/json/include/json.h +++ b/libraries/standard/json/include/json.h @@ -24,7 +24,7 @@ #include -typedef enum JSONStatus +typedef enum { JSONPartial = 0, JSONSuccess, @@ -70,8 +70,8 @@ JSONStatus_t JSON_Validate( const char * buf, * may descend through nested objects when the queryKey contains matching * key strings joined by a separator. * - * For example, if buf contains '{"foo":"abc","bar":{"foo","xyz"}}', then a - * search for 'foo' would output 'abc', 'bar' would output '{"foo","xyz"}', + * For example, if buf contains '{"foo":"abc","bar":{"foo":"xyz"}}', then a + * search for 'foo' would output 'abc', 'bar' would output '{"foo":"xyz"}', * and a search for 'bar.foo' would output 'xyz' (given separator is * specified as '.'). * diff --git a/libraries/standard/json/lexicon.txt b/libraries/standard/json/lexicon.txt index 4f8bb94420..29c512793c 100644 --- a/libraries/standard/json/lexicon.txt +++ b/libraries/standard/json/lexicon.txt @@ -4,6 +4,7 @@ bf buf buflength com +coverity dbff df dfff diff --git a/libraries/standard/json/src/json.c b/libraries/standard/json/src/json.c index 63840f583c..9ce33bb0de 100644 --- a/libraries/standard/json/src/json.c +++ b/libraries/standard/json/src/json.c @@ -25,7 +25,7 @@ #include #include "json.h" -typedef enum bool_ +typedef enum { true = 1, false = 0 } bool_; @@ -45,7 +45,7 @@ static void skipSpace( const char * buf, for( ; i < max; i++ ) { - if( isspace( buf[ i ] ) == 0U ) + if( isspace( ( uint8_t ) buf[ i ] ) == 0U ) { break; } @@ -241,23 +241,23 @@ static bool_ skipUTF8( const char * buf, * * @return the integer value upon success or UINT8_MAX on failure. */ -static uint8_t hexToInt( char c ) +static uint8_t hexToInt( uint8_t c ) { uint8_t n = UINT8_MAX; if( isxdigit( c ) != 0U ) { - if( c >= 'a' ) + if( c >= ( uint8_t ) 'a' ) { - n = 10U + ( ( uint8_t ) c - ( uint8_t ) 'a' ); + n = 10U + ( c - ( uint8_t ) 'a' ); } - else if( c >= 'A' ) + else if( c >= ( uint8_t ) 'A' ) { - n = 10U + ( ( uint8_t ) c - ( uint8_t ) 'A' ); + n = 10U + ( c - ( uint8_t ) 'A' ); } else { - n = ( uint8_t ) c - ( uint8_t ) '0'; + n = c - ( uint8_t ) '0'; } } @@ -284,6 +284,13 @@ static uint8_t hexToInt( char c ) */ #define isHighSurrogate( x ) ( ( ( x ) >= 0xD800U ) && ( ( x ) <= 0xDBFFU ) ) #define isLowSurrogate( x ) ( ( ( x ) >= 0xDC00U ) && ( ( x ) <= 0xDFFFU ) ) + +/* MISRA Rule 17.2 prohibits recursion due to the + * risk of exceeding available stack space. In this + * function, recursion is limited to exactly one level; + * the recursive call sets the final argument to true + * which satisfies the base case. */ +/* coverity[misra_c_2012_rule_17_2_violation] */ static bool_ skipHexEscape( const char * buf, size_t * start, size_t max, @@ -297,7 +304,7 @@ static bool_ skipHexEscape( const char * buf, { for( i += 2U; i < end; i++ ) { - uint8_t n = hexToInt( buf[ i ] ); + uint8_t n = hexToInt( ( uint8_t ) buf[ i ] ); if( n == UINT8_MAX ) { @@ -561,7 +568,7 @@ static bool_ skipDigits( const char * buf, for( ; i < max; i++ ) { - if( isdigit( buf[ i ] ) == 0U ) + if( isdigit( ( uint8_t ) buf[ i ] ) == 0U ) { break; } From 1aed50a023d381f73741b162a0214d6ea730eb4d Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 20 Aug 2020 13:06:20 -0700 Subject: [PATCH 641/844] Proof for MQTT_MatchTopic API (#1137) --- .../findHeaderFieldParserCallback/Makefile | 2 +- .../MQTT_MatchTopic/MQTT_MatchTopic_harness.c | 49 ++++++++ .../mqtt/cbmc/proofs/MQTT_MatchTopic/Makefile | 42 +++++++ .../proofs/MQTT_MatchTopic/cbmc-batch.yaml | 2 + .../proofs/MQTT_MatchTopic/cbmc-viewer.json | 7 ++ libraries/standard/mqtt/lexicon.txt | 11 +- libraries/standard/mqtt/src/mqtt.c | 106 ++++++++++++------ libraries/standard/mqtt/utest/mqtt_utest.c | 9 ++ 8 files changed, 187 insertions(+), 41 deletions(-) create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/MQTT_MatchTopic_harness.c create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/Makefile create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-batch.yaml create mode 100644 libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-viewer.json diff --git a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile index 0bb0632675..40ed213087 100644 --- a/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile +++ b/libraries/standard/http/cbmc/proofs/findHeaderFieldParserCallback/Makefile @@ -4,7 +4,7 @@ HARNESS_ENTRY=harness HARNESS_FILE=findHeaderFieldParserCallback_harness -# The header field length is bounded, so strncmp can be unwounded to an expected +# The header field length is bounded, so strncmp can be unwound to an expected # amount that won't make the proof run too long. MAX_HEADER_FIELD_LENGTH=10 diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/MQTT_MatchTopic_harness.c b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/MQTT_MatchTopic_harness.c new file mode 100644 index 0000000000..b8344a2949 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/MQTT_MatchTopic_harness.c @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file MQTT_MatchTopic_harness.c + * @brief Implements the proof harness for MQTT_MatchTopic function. + */ + +#include "mqtt.h" +#include "mqtt_cbmc_state.h" + +void harness() +{ + const char * pTopicName; + uint16_t nameLength; + const char * pTopicFilter; + uint16_t filterLength; + bool * pMatchResult; + + __CPROVER_assume( nameLength < MAX_TOPIC_NAME_FILTER_LENGTH ); + pTopicName = mallocCanFail( ( sizeof( char ) * nameLength ) ); + __CPROVER_assume( filterLength < MAX_TOPIC_NAME_FILTER_LENGTH ); + pTopicFilter = mallocCanFail( ( sizeof( char ) * filterLength ) ); + pMatchResult = mallocCanFail( sizeof( bool ) ); + + MQTT_MatchTopic( pTopicName, + nameLength, + pTopicFilter, + filterLength, + pMatchResult ); +} diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/Makefile b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/Makefile new file mode 100644 index 0000000000..9645901380 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/Makefile @@ -0,0 +1,42 @@ +# +# Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +HARNESS_ENTRY=harness +HARNESS_FILE=MQTT_MatchTopic_harness + +# The topic name/filter length are bounded, so that the loops in topic matching algorithmic +# functions called by MQTT_MatchTopic can be unwound to an expected +# amount that won't make the proof run too long. +MAX_TOPIC_NAME_FILTER_LENGTH=10 + +DEFINES += -DMAX_TOPIC_NAME_FILTER_LENGTH=$(MAX_TOPIC_NAME_FILTER_LENGTH) +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += __CPROVER_file_local_mqtt_c_matchTopicFilter.0:$(MAX_TOPIC_NAME_FILTER_LENGTH) +UNWINDSET += strncmp.0:$(MAX_TOPIC_NAME_FILTER_LENGTH) +UNWINDSET += __CPROVER_file_local_mqtt_c_matchWildcards.0:$(MAX_TOPIC_NAME_FILTER_LENGTH) + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROOF_SOURCES += $(SRCDIR)/libraries/standard/mqtt/cbmc/sources/mqtt_cbmc_state.c +PROJECT_SOURCES += $(SRCDIR)/libraries/standard/mqtt/src/mqtt.c + +include ../Makefile.common diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-batch.yaml b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-batch.yaml new file mode 100644 index 0000000000..7eeb12ad71 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-batch.yaml @@ -0,0 +1,2 @@ +# This file marks this directory as containing a CBMC proof. +# The contents of this file will be generated by continuous integration. diff --git a/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-viewer.json b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-viewer.json new file mode 100644 index 0000000000..0f39d31093 --- /dev/null +++ b/libraries/standard/mqtt/cbmc/proofs/MQTT_MatchTopic/cbmc-viewer.json @@ -0,0 +1,7 @@ +{ "expected-missing-functions": + [ + + ], + "proof-name": "MQTT_MatchTopic", + "proof-root": "standard/mqtt/cbmc/proofs" +} diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index ac14073240..edb7136a6d 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -33,8 +33,8 @@ connectinfo connectpacketsize const createcleansession -currentstate csdk +currentstate defragmenting deserialization deserializationresult @@ -49,9 +49,9 @@ disconnectpacketsize doesn doxygen dup -endcond emptyindex endcode +endcond endif enum enums @@ -176,8 +176,8 @@ payloadlength pbuffer pbuffertosend pcallbacks -pconnack pclientidentifier +pconnack pconnectinfo pcontext pcurrentstate @@ -185,9 +185,8 @@ pcursor pdeserialized pdeserializedinfo pdestination -ppacketidentifier -ppingresp pem +pfilterindex pfixedbuffer pheadersize pincomingpacket @@ -206,12 +205,14 @@ pnetworkinterface pnewstate posix ppacketid +ppacketidentifier ppacketinfo ppacketsize ppassword ppayload ppayloadsize ppayloadstart +ppingresp ppubinfo ppublishinfo pqos diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index dcae57ecbb..f467be2ae4 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -318,7 +318,11 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, uint16_t packetId ); /** - * topic filter, this function handles the following 2 cases: + * @brief Performs matching for special cases when the passed topic filter + * ends with a wildcard character. + * + * When the topic name has been consumed but there are remaining characters to + * to match in the topic filter, this function handles the following 2 cases: * - When the topic filter ends with "/+" or "/#" characters, but the topic * name only ends with '/'. * - When the topic filter ends with "/#" characters, but the topic name @@ -350,9 +354,10 @@ static bool matchEndWildcardsSpecialCases( const char * pTopicFilter, * @param[in] topicNameLength Length of the topic name. * @param[in] pTopicFilter The topic filter to match. * @param[in] topicFilterLength Length of the topic filter. - * @param[in,out] pNameIndex Current index in topic name being examined.. It is + * @param[in,out] pNameIndex Current index in the topic name being examined. It is * advanced by one level for `+` wildcards. - * @param[in] filterIndex Current index in the topic filter being examined.. + * @param[in, out] pFilterIndex Current index in the topic filter being examined. + * It is advanced to position of '/' level separator for '+' wildcard. * @param[out] pMatch Whether the topic filter and topic name match. * * @return `true` if the caller of this function should exit; `false` if the @@ -363,7 +368,7 @@ static bool matchWildcards( const char * pTopicName, const char * pTopicFilter, uint16_t topicFilterLength, uint16_t * pNameIndex, - uint16_t filterIndex, + uint16_t * pFilterIndex, bool * pMatch ); /** @@ -396,7 +401,8 @@ static bool matchEndWildcardsSpecialCases( const char * pTopicFilter, * "/#". This check handles the case to match filter "sport/#" with topic * "sport". The reason is that the '#' wildcard represents the parent and * any number of child levels in the topic name.*/ - if( ( filterIndex == ( topicFilterLength - 3U ) ) && + if( ( topicFilterLength >= 3U ) && + ( filterIndex == ( topicFilterLength - 3U ) ) && ( pTopicFilter[ filterIndex + 1U ] == '/' ) && ( pTopicFilter[ filterIndex + 2U ] == '#' ) ) @@ -428,7 +434,7 @@ static bool matchWildcards( const char * pTopicName, const char * pTopicFilter, uint16_t topicFilterLength, uint16_t * pNameIndex, - uint16_t filterIndex, + uint16_t * pFilterIndex, bool * pMatch ) { bool shouldStopMatching = false; @@ -439,56 +445,86 @@ static bool matchWildcards( const char * pTopicName, assert( pTopicFilter != NULL ); assert( topicFilterLength != 0 ); assert( pNameIndex != NULL ); + assert( pFilterIndex != NULL ); assert( pMatch != NULL ); /* Wild card in a topic filter is only valid either at the starting position * or when it is preceded by a '/'.*/ - locationIsValidForWildcard = ( ( filterIndex == 0u ) || - ( pTopicFilter[ filterIndex - 1U ] == '/' ) + locationIsValidForWildcard = ( ( *pFilterIndex == 0u ) || + ( pTopicFilter[ *pFilterIndex - 1U ] == '/' ) ) ? true : false; - if( locationIsValidForWildcard == true ) + if( ( pTopicFilter[ *pFilterIndex ] == '+' ) && ( locationIsValidForWildcard == true ) ) { - if( pTopicFilter[ filterIndex ] == '+' ) + bool nextLevelExistsInTopicName = false; + bool nextLevelExistsinTopicFilter = false; + + /* Move topic name index to the end of the current level. The end of the + * current level is identified by the last character before the next level + * separator '/'. */ + while( *pNameIndex < topicNameLength ) { - /* Move topic name index to the end of the current level. The end of the - * current level is identified by '/'. */ - while( ( *pNameIndex < topicNameLength ) && ( pTopicName[ *pNameIndex ] != '/' ) ) + /* Exit the loop if we hit the level separator. */ + if( pTopicName[ *pNameIndex ] == '/' ) { - ( *pNameIndex )++; + nextLevelExistsInTopicName = true; + break; } - /* Decrement the topic name index for 2 different cases: - * - If the break condition is ( *pNameIndex < topicNameLength ), then - * we have reached past the end of the topic name and we move back the - * the index on the last character. - * - If the break condition is ( pTopicName[ *pNameIndex ] != '/' ), we - * move back the index on the '/' character. */ - ( *pNameIndex )--; + ( *pNameIndex )++; } - /* '#' matches everything remaining in the topic name. It must be the - * last character in a topic filter. */ - else if( ( pTopicFilter[ filterIndex ] == '#' ) && - ( filterIndex == ( topicFilterLength - 1U ) ) ) + /* Determine if the topic filter contains a child level after the current level + * represented by the '+' wildcard. */ + if( ( *pFilterIndex < ( topicFilterLength - 1U ) ) && + ( pTopicFilter[ *pFilterIndex + 1U ] == '/' ) ) { - /* Subsequent characters don't need to be checked for the - * multi-level wildcard. */ - *pMatch = true; - shouldStopMatching = true; + nextLevelExistsinTopicFilter = true; } - else + + /* If the topic name contains a child level but the topic filter ends at + * the current level, then there does not exist a match. */ + if( ( nextLevelExistsInTopicName == true ) && + ( nextLevelExistsinTopicFilter == false ) ) { - /* Any character mismatch other than '+' or '#' means the topic - * name does not match the topic filter. */ *pMatch = false; shouldStopMatching = true; } + + /* If the topic name and topic filter have child levels, then advance the + * filter index to the level separator in the topic filter, so that match + * can be performed in the next level. + * Note: The name index already points to the level separator in the topic + * name. */ + else if( nextLevelExistsInTopicName == true ) + { + ( *pFilterIndex )++; + } + else + { + /* If we have reached here, the the loop terminated on the + * ( *pNameIndex < topicNameLength) condition, which means that have + * reached past the end of the topic name, and thus, we decrement the + * index to the last character in the topic name.*/ + ( *pNameIndex )--; + } + } + + /* '#' matches everything remaining in the topic name. It must be the + * last character in a topic filter. */ + else if( ( pTopicFilter[ *pFilterIndex ] == '#' ) && + ( *pFilterIndex == ( topicFilterLength - 1U ) ) && + ( locationIsValidForWildcard == true ) ) + { + /* Subsequent characters don't need to be checked for the + * multi-level wildcard. */ + *pMatch = true; + shouldStopMatching = true; } else { - /* If the location is not valid for a wildcard, the topic name does not - * match the topic filter. */ + /* Any character mismatch other than '+' or '#' means the topic + * name does not match the topic filter. */ *pMatch = false; shouldStopMatching = true; } @@ -535,7 +571,7 @@ static bool matchTopicFilter( const char * pTopicName, pTopicFilter, topicFilterLength, &nameIndex, - filterIndex, + &filterIndex, &matchFound ); } diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index c975c8372c..25aaa2d010 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2251,6 +2251,15 @@ void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) &matchResult ) ); TEST_ASSERT_EQUAL( false, matchResult ); + pTopicName = "/"; + pTopicFilter = "+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + /* Test with '+' as the topic filter. */ pTopicName = "test"; pTopicFilter = "+"; From a66e6642a9dd7d93b83f3ef7ec3cfa437a45b715 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 20 Aug 2020 15:53:17 -0700 Subject: [PATCH 642/844] [PR from CLI tool] Add MQTT demo for connection sharing with subscription manager (#1098) --- demos/lexicon.txt | 5 + .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 6 +- .../CMakeLists.txt | 60 + .../demo_config.h | 85 ++ .../mqtt_config.h | 73 + .../mqtt_demo_connection_sharing.c | 1307 +++++++++++++++++ .../subscription-manager/CMakeLists.txt | 0 .../mqtt_subscription_manager.c | 213 +++ .../mqtt_subscription_manager.h | 0 .../mqtt_subscription_manager.c | 491 ------- libraries/standard/mqtt/lexicon.txt | 2 +- libraries/standard/mqtt/src/mqtt.c | 6 +- 12 files changed, 1752 insertions(+), 496 deletions(-) create mode 100644 demos/mqtt/mqtt_demo_connection_sharing/CMakeLists.txt create mode 100644 demos/mqtt/mqtt_demo_connection_sharing/demo_config.h create mode 100644 demos/mqtt/mqtt_demo_connection_sharing/mqtt_config.h create mode 100644 demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c rename demos/mqtt/{ => mqtt_demo_connection_sharing}/subscription-manager/CMakeLists.txt (100%) create mode 100644 demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/mqtt_subscription_manager.c rename demos/mqtt/{ => mqtt_demo_connection_sharing}/subscription-manager/mqtt_subscription_manager.h (100%) delete mode 100644 demos/mqtt/subscription-manager/mqtt_subscription_manager.c diff --git a/demos/lexicon.txt b/demos/lexicon.txt index bae5ded5d5..912c4f4a15 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -53,6 +53,7 @@ mqtt mqttkeepalivetimeout nameindex noninfringement +numoftopicfilters openssl org outgoingpublishpackets @@ -72,6 +73,7 @@ pingreq pingresp plaintext pmatch +pmessage pmethod pmqttcontext pnameindex @@ -83,7 +85,9 @@ ppubinfo ppublishinfo processloop psessionpresent +ptopic ptopicfilter +ptopicfilters ptopicname ptransportinterface puback @@ -106,6 +110,7 @@ tcp tcpsocket tls topicfilterlength +topiclength topicnamelength transportinterface transporttimeout diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index d1c285854a..6fa2fb00e2 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -397,7 +397,7 @@ static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext do { /* Establish a TLS session with the MQTT broker. This example connects - * to the MQTT broker as specified in AWS_IOT_ENDPOINT and AWS_MQTT_PORT at + * to the MQTT broker as specified in BROKER_ENDPOINT and BROKER_PORT at * the top of this file. */ LogInfo( ( "Establishing a TLS session to %.*s:%d.", BROKER_ENDPOINT_LENGTH, @@ -442,6 +442,10 @@ static int getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) if( outgoingPublishPackets[ index ].packetId == MQTT_PACKET_ID_INVALID ) { returnStatus = EXIT_SUCCESS; + + /* Copy the available index into the output param. */ + *pIndex = index; + break; } } diff --git a/demos/mqtt/mqtt_demo_connection_sharing/CMakeLists.txt b/demos/mqtt/mqtt_demo_connection_sharing/CMakeLists.txt new file mode 100644 index 0000000000..b11afd33ab --- /dev/null +++ b/demos/mqtt/mqtt_demo_connection_sharing/CMakeLists.txt @@ -0,0 +1,60 @@ +include(CheckSymbolExists) + +add_subdirectory( ${CMAKE_CURRENT_LIST_DIR}/subscription-manager ) + +set( DEMO_NAME "mqtt_demo_connection_sharing" ) + +# Demo target. +add_executable(${DEMO_NAME}) + +# Add to default target if all required macros needed to run this demo are defined +check_aws_credentials(${DEMO_NAME}) + +target_sources( + ${DEMO_NAME} + PRIVATE + "${DEMO_NAME}.c" +) + +target_link_libraries( + ${DEMO_NAME} + PRIVATE + mqtt + mqtt_subscription_manager + clock_posix + openssl_posix + transport_reconnect_posix +) + +target_include_directories( + mqtt + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +target_include_directories( + ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} + ${CMAKE_CURRENT_LIST_DIR}/subscription-manager +) + +# Set preprocess defines for demo, if passed through CMake build. +if(ROOT_CA_CERT_PATH) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + ROOT_CA_CERT_PATH="${ROOT_CA_CERT_PATH}" + ) +endif() +if(BROKER_ENDPOINT) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + BROKER_ENDPOINT="${BROKER_ENDPOINT}" + ) +endif() +if(CLIENT_IDENTIFIER) + target_compile_definitions( + ${DEMO_NAME} PRIVATE + CLIENT_IDENTIFIER="${CLIENT_IDENTIFIER}" + ) +endif() \ No newline at end of file diff --git a/demos/mqtt/mqtt_demo_connection_sharing/demo_config.h b/demos/mqtt/mqtt_demo_connection_sharing/demo_config.h new file mode 100644 index 0000000000..1b8bdcc9b3 --- /dev/null +++ b/demos/mqtt/mqtt_demo_connection_sharing/demo_config.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H +#define DEMO_CONFIG_H + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for DEMO. + * 3. Include the header file "logging_stack.h", if logging is enabled for DEMO. + */ + +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "DEMO" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief MQTT server host name. + * + * This demo can be run using the open-source Mosquitto broker tool. + * A Mosquitto MQTT broker can be setup locally for running this demo against + * it. Please refer to the instructions in https://mosquitto.org/ for running + * a Mosquitto broker locally. + * Alternatively,instructions to run Mosquitto server on Docker container can + * be viewed in the README.md of the root directory. + * + * #define BROKER_ENDPOINT "...insert here..." + */ + +/** + * @brief MQTT server port number. + * + * In general, port 8883 is for secured MQTT connections. + */ +#define BROKER_PORT ( 8883 ) + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate should be PEM-encoded. + * + * #define ROOT_CA_CERT_PATH "....insert here...." + */ + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + */ +#define CLIENT_IDENTIFIER "testclient1" + +#endif /* ifndef DEMO_CONFIG_H */ diff --git a/demos/mqtt/mqtt_demo_connection_sharing/mqtt_config.h b/demos/mqtt/mqtt_demo_connection_sharing/mqtt_config.h new file mode 100644 index 0000000000..3d9945a032 --- /dev/null +++ b/demos/mqtt/mqtt_demo_connection_sharing/mqtt_config.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_CONFIG_H_ +#define MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros depending on + * the logging configuration for MQTT. + * 3. Include the header file "logging_stack.h", if logging is enabled for MQTT. + */ + +#include "logging_levels.h" + +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT 10U + +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) + + +#endif /* ifndef MQTT_CONFIG_H_ */ diff --git a/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c b/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c new file mode 100644 index 0000000000..5fd7d8e2bc --- /dev/null +++ b/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c @@ -0,0 +1,1307 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Demo for showing the use of MQTT APIs to establish an MQTT session, + * subscribe to a topic, publish to a topic, receive incoming publishes, + * unsubscribe from a topic and disconnect the MQTT session. + * + * The example is single threaded and uses statically allocated memory; + * it uses QOS1 and therefore implements a retransmission mechanism + * for Publish messages. Retransmission of publish messages are attempted + * when a MQTT connection is established with a session that was already + * present. All the outgoing publish messages waiting to receive PUBACK + * are resend in this demo. In order to support retransmission all the outgoing + * publishes are stored until a PUBACK is received. + */ + +/** + * @file mqtt_demo_connection_sharing.c + * @brief MQTT Demo application that showcases multiplexing of the same MQTT connection + * across multiple topic subscriptions using a subscription manager. + */ + +/* Standard includes. */ +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Include Demo Config as the first non-system header. */ +#include "demo_config.h" + +/* MQTT API header. */ +#include "mqtt.h" + +/* OpenSSL sockets transport implementation. */ +#include "openssl_posix.h" + +/* Reconnect parameters. */ +#include "transport_reconnect.h" + +/* Clock for timer. */ +#include "clock.h" + +/* Include subscription manager. */ +#include "mqtt_subscription_manager.h" + +/** + * These configuration settings are required to run the basic TLS demo. + * Throw compilation error if the below configs are not defined. + */ +#ifndef BROKER_ENDPOINT + #error "Please define an MQTT broker endpoint, BROKER_ENDPOINT, in demo_config.h." +#endif +#ifndef ROOT_CA_CERT_PATH + #error "Please define path to Root CA certificate of the MQTT broker, ROOT_CA_CERT_PATH, in demo_config.h." +#endif +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif + +/** + * @brief Length of MQTT server host name. + */ +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) + +/** + * @brief Length of path to server certificate. + */ +#define ROOT_CA_CERT_PATH_LENGTH ( ( uint16_t ) ( sizeof( ROOT_CA_CERT_PATH ) - 1 ) ) + +/** + * Provide default values for undefined configuration settings. + */ +#ifndef BROKER_PORT + #define BROKER_PORT ( 8883 ) +#endif + +#ifndef NETWORK_BUFFER_SIZE + #define NETWORK_BUFFER_SIZE ( 1024U ) +#endif + +/** + * @brief Length of client identifier. + */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) + +/** + * @brief Timeout for receiving CONNACK packet in milli seconds. + */ +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief The MQTT topic filter to subscribe to for temperature data in the demo. + */ +#define DEMO_TEMPERATURE_TOPIC_FILTER CLIENT_IDENTIFIER "/demo/temperature/+" + +/** + * @brief The length of the temperature topic filter. + */ +#define DEMO_TEMPERATURE_TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( DEMO_TEMPERATURE_TOPIC_FILTER ) - 1U ) ) + +/** + * @brief The MQTT topic for the high temperature data value that the demo will + * publish to. + */ +#define DEMO_TEMPERATURE_HIGH_TOPIC CLIENT_IDENTIFIER "/demo/temperature/high" + +/** + * @brief The length of the high temperature topic name. + */ +#define DEMO_TEMPERATURE_HIGH_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( DEMO_TEMPERATURE_HIGH_TOPIC ) - 1U ) ) + +/** + * @brief The MQTT topic for the high temperature data value that the demo will + * publish to. + */ +#define DEMO_TEMPERATURE_LOW_TOPIC CLIENT_IDENTIFIER "/demo/temperature/low" + +/** + * @brief The length of the high temperature topic name. + */ +#define DEMO_TEMPERATURE_LOW_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( DEMO_TEMPERATURE_LOW_TOPIC ) - 1U ) ) + +/** + * @brief The MQTT topic filter to subscribe and publish to for humidity data in the demo. + * + * The topic name starts with the client identifier to ensure that each demo + * interacts with a unique topic name. + */ +#define DEMO_HUMIDITY_TOPIC CLIENT_IDENTIFIER "/demo/humidity" + +/** + * @brief The length of the humidity topic. + */ +#define DEMO_HUMIDITY_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( DEMO_HUMIDITY_TOPIC ) - 1U ) ) + +/** + * @brief The MQTT topic filter to subscribe and publish to for precipitation data in the demo. + * + * The topic name starts with the client identifier to ensure that each demo + * interacts with a unique topic name. + */ +#define DEMO_PRECIPITATION_TOPIC CLIENT_IDENTIFIER "/demo/precipitation" + +/** + * @brief The length of the precipitation topic. + */ +#define DEMO_PRECIPITATION_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( DEMO_PRECIPITATION_TOPIC ) - 1U ) ) + +/** + * @brief The MQTT message for the #DEMO_TEMPERATURE_HIGH_TOPIC topic published + * in this example. + */ +#define DEMO_TEMPERATURE_HIGH_MESSAGE "Today's High is 80 degree F" + +/** + * @brief The MQTT message for the #DEMO_TEMPERATURE_LOW_TOPIC topic published + * in this example. + */ +#define DEMO_TEMPERATURE_LOW_MESSAGE "Today's Low 52 degree F" + +/** + * @brief The MQTT message for the #DEMO_HUMIDITY_TOPIC topic published in this example. + */ +#define DEMO_HUMIDITY_MESSAGE "Today's humidity at 58%" + +/** + * @brief The MQTT message for the #DEMO_PRECIPITATION_TOPIC_LENGTH topic published in this example. + */ +#define DEMO_PRECIPITATION_MESSAGE "Today's precipitation at 9%" + +/** + * @brief Timeout for MQTT_ProcessLoop function in milliseconds. + */ +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 500U ) + +/** + * @brief The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. + */ +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) + +/** + * @brief Delay in seconds between two iterations of subscribePublishLoop(). + */ +#define MQTT_SUBPUB_LOOP_DELAY_SECONDS ( 5U ) + +/** + * @brief Transport timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Structure to keep the MQTT publish packets until an ack is received + * for QoS1 publishes. + */ +typedef struct PublishPackets +{ + /** + * @brief Packet identifier of the publish packet. + */ + uint16_t packetId; + + /** + * @brief Publish info of the publish packet. + */ + MQTTPublishInfo_t pubInfo; +} PublishPackets_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker; + * it is used to match received Subscribe ACK to the transmitted subscribe. + */ +static uint16_t lastSubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker; + * it is used to match received Unsubscribe ACK to the transmitted unsubscribe + * request. + */ +static uint16_t lastUnsubscribePacketIdentifier = 0U; + +/** + * @brief The network buffer must remain valid for the lifetime of the MQTT context. + */ +static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + +/** + * @brief Flag to represent whether that the temperature callback has been invoked with + * incoming PUBLISH message for high temperature data. + */ +static bool globalReceivedHighTemperatureData = false; + +/** + * @brief Flag to represent whether that the temperature callback has been invoked with + * incoming PUBLISH message for low temperature data. + */ +static bool globalReceivedLowTemperatureData = false; + +/** + * @brief Flag to represent whether that the humidity topic callback has been invoked. + */ +static bool globalReceivedHumidityData = false; + +/** + * @brief Flag to represent whether that the precipitation topic callback has been invoked. + */ +static bool globalReceivedPrecipitationData = false; + +/*-----------------------------------------------------------*/ + +/** + * @brief Connect to MQTT broker with reconnection retries. + * + * If connection fails, retry is attempted after a back-off period. + * The back-off period will exponentially increase until either the maximum + * back-off period is reached or the number of attempts are exhausted. + * + * @param[out] pNetworkContext The output parameter to return the created network context. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on successful connection. + */ +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ); + +/** + * @brief A function that connects to the MQTT broker, + * subscribes to a topic, publishes to the same topic + * MQTT_PUBLISH_COUNT_PER_LOOP number of times, and verifies if it + * receives the Publish message back. + * + * @param[in] pNetworkContext Pointer to the network context created using Openssl_Connect. + * + * @return EXIT_FAILURE on failure; EXIT_SUCCESS on success. + */ +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ); + +/** + * @brief The function to handle the incoming publishes. + * + * @param[in] pPublishInfo Pointer to publish info of the incoming publish. + */ +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Callback that is registered for the temperature data topic filter, + * #DEMO_TEMPERATURE_TOPIC_FILTER, with the subscription manager. + * + * The callback determines the type of temperature data ("high" or "low") received + * on the incoming PUBLISH topic, prints the data, and sets the associated + * global flag for the temperature data type to reception of data. + * + * @param[in] pContext The MQTT context associated with the incoming PUBLISH. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void temperatureDataCallback( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Callback that is registered for the humidity data topic filter, + * #DEMO_HUMIDITY_TOPIC, with the subscription manager. + * + * The callback prints the received humidity data, and sets a global + * flag to indicate invocation of the callback. + * + * @param[in] pContext The MQTT context associated with the incoming PUBLISH. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void humidityDataCallback( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief Callback that is registered for the precipitation data topic filter, + * #DEMO_PRECIPITATION_TOPIC, with the subscription manager. + * + * The callback prints the received precipitation data, and sets a global + * flag to indicate invocation of the callback. + * + * @param[in] pContext The MQTT context associated with the incoming PUBLISH. + * @param[in] pPublishInfo Deserialized publish info pointer for the incoming + * packet. + */ +static void precipitationDataCallback( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ); + +/** + * @brief The callback function that is registered with the MQTT connection + * for dispatching incoming publish messages to their registered callbacks, + * and handling incoming acks reported from the MQTT library. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] packetIdentifier Packet identifier of the incoming packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. + * packet. + */ +static void commonEventHandler( MQTTContext_t * pMqttContext, + MQTTPacketInfo_t * pPacketInfo, + MQTTDeserializedInfo_t * pDeserializedInfo ); + +/** + * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pNetworkContext Pointer to the network context created using Openssl_Connect. + * @param[in] createCleanSession Creates a new MQTT session if true. + * If false, tries to establish the existing session if there was session + * already present in broker. + * @param[out] pSessionPresent Session was already present in the broker or not. + * Session present response is obtained from the CONNACK from broker. + * + * @return EXIT_SUCCESS if an MQTT session is established; + * EXIT_FAILURE otherwise. + */ +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext, + bool createCleanSession, + bool * pSessionPresent ); + +/** + * @brief Close an MQTT session by sending MQTT DISCONNECT. + * + * @param[in] pMqttContext MQTT context pointer. + * + * @return EXIT_SUCCESS if DISCONNECT was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int disconnectMqttSession( MQTTContext_t * pMqttContext ); + +/** + * @brief Subscribes to the passed topic filter by sending an MQTT SUBSCRIBE + * packet and waiting for a SUBACK acknowledgement response from the broker. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pTopicFilter The topic filter to subscribe to. + * @param[in] topicFilterLength The length of the topic filter. + * + * @return EXIT_SUCCESS if SUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int subscribeToTopic( MQTTContext_t * pMqttContext, + const char * pTopicFilter, + uint16_t topicFilterLength ); + +/** + * @brief Utility to subscribe to the passed topic filter and register + * a callback for it in the subscription manager. + * + * The registered callback will be invoked by the subscription manager + * when PUBLISH messages on topic(s) that match the registered topic filter + * are received from the broker. + * + * @param[in] pContext The MQTT context representing the MQTT connection. + * @param[in] pTopicFilter The topic filter to subscribe to and register a + * callback for in the subscription manager. + * @param[in] topicFilterLength The length of the topic filter, @p pTopicFilter. + * @param[in] callback The callback to register for the topic filter with the + * subscription manager. + * + * @return EXIT_SUCCESS if subscription and callback registration operations + * for the topic filter were successfully; EXIT_FAILURE otherwise. + */ +static int subscribeToAndRegisterTopicFilter( MQTTContext_t * pContext, + const char * pTopicFilter, + uint16_t topicFilterLength, + SubscriptionManagerCallback_t callback ); + +/** + * @brief Unsubscribe from the passed topic filters by sending + * a single MQTT UNSUBSCRIBE packet to the broker and waiting for + * an acknowledgement response with UNSUBACK packet. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pTopicFilters The list of topic filter to unsubscribe from. + * @param[in] numOfTopicFilters The number of topic filters in the list. + * + * @return EXIT_SUCCESS if UNSUBSCRIBE was successfully sent; + * EXIT_FAILURE otherwise. + */ +static int unsubscribeFromTopicFilters( MQTTContext_t * pMqttContext, + const MQTTSubscribeInfo_t * pTopicFilters, + size_t numOfTopicFilters ); + +/** + * @brief Sends an MQTT PUBLISH to the passed topic + * with the passed message and waits for acknowledgement response + * from the broker. Also, this function processes an incoming PUBLISH + * packet of the same message that the broker should send back to us. + * + * As this demo subscribes to topic filters that match the topics + * that it publishes to, the broker should send back to us the same + * message that we publish to it. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pTopic The topic on which to send a PUBLISH message. + * @param[in] topicLength The length of the topic. + * @param[in] pMessage The message payload to PUBLISH. + * + * @return EXIT_SUCCESS if PUBLISH was successfully sent and processing + * of incoming PUBLISH was successful; EXIT_FAILURE otherwise. + */ +static int publishToTopicAndProcessIncomingMessage( MQTTContext_t * pMqttContext, + const char * pTopic, + uint16_t topicLength, + const char * pMessage ); + +/*-----------------------------------------------------------*/ + +static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ) +{ + int returnStatus = EXIT_SUCCESS; + bool retriesArePending = true; + OpensslStatus_t opensslStatus = OPENSSL_SUCCESS; + TransportReconnectParams_t reconnectParams; + ServerInfo_t serverInfo; + OpensslCredentials_t opensslCredentials; + + /* Initialize information to connect to the MQTT broker. */ + serverInfo.pHostName = BROKER_ENDPOINT; + serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; + serverInfo.port = BROKER_PORT; + + /* Initialize credentials for establishing TLS session. */ + memset( &opensslCredentials, 0, sizeof( OpensslCredentials_t ) ); + opensslCredentials.pRootCaPath = ROOT_CA_CERT_PATH; + + /* Initialize reconnect attempts and interval. */ + Transport_ReconnectParamsReset( &reconnectParams ); + + /* Attempt to connect to MQTT broker. If connection fails, retry after + * a timeout. Timeout value will exponentially increase until maximum + * attempts are reached. + */ + do + { + /* Establish a TLS session with the MQTT broker. This example connects + * to the MQTT broker as specified in BROKER_ENDPOINT and AWS_MQTT_PORT at + * the top of this file. */ + LogInfo( ( "Establishing a TLS session to %.*s:%d.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT, + BROKER_PORT ) ); + opensslStatus = Openssl_Connect( pNetworkContext, + &serverInfo, + &opensslCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( opensslStatus != OPENSSL_SUCCESS ) + { + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter." ) ); + retriesArePending = Transport_ReconnectBackoffAndSleep( &reconnectParams ); + } + + if( retriesArePending == false ) + { + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + returnStatus = EXIT_FAILURE; + } + } while( ( opensslStatus != OPENSSL_SUCCESS ) && ( retriesArePending == true ) ); + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static void handleIncomingPublish( MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pPublishInfo != NULL ); + + /* Process incoming Publish. */ + LogInfo( ( "Incoming QOS : %d.", pPublishInfo->qos ) ); + + + LogInfo( ( "Incoming Publish Topic Name: %.*s matches subscribed topic.\n" + "Incoming Publish Message : %.*s.\n\n", + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName, + ( int ) pPublishInfo->payloadLength, + ( const char * ) pPublishInfo->pPayload ) ); +} + +/*-----------------------------------------------------------*/ + +static void temperatureDataCallback( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pPublishInfo != NULL ); + assert( pContext != NULL ); + + LogInfo( ( "Invoked temperature callback." ) ); + + /* Determine whether the incoming PUBLISH message is for "high" + * or "low" temperature data. */ + if( ( pPublishInfo->topicNameLength == DEMO_TEMPERATURE_HIGH_TOPIC_LENGTH ) && + ( strncmp( pPublishInfo->pTopicName, DEMO_TEMPERATURE_HIGH_TOPIC, + DEMO_TEMPERATURE_HIGH_TOPIC_LENGTH ) == 0 ) ) + { + /* Incoming PUBLISH message has been received on the High Temperature topic. + * Set the flag to indicate that the high temperature data has been received. */ + globalReceivedHighTemperatureData = true; + } + else if( ( pPublishInfo->topicNameLength == DEMO_TEMPERATURE_LOW_TOPIC_LENGTH ) && + ( strncmp( pPublishInfo->pTopicName, DEMO_TEMPERATURE_LOW_TOPIC, + DEMO_TEMPERATURE_LOW_TOPIC_LENGTH ) == 0 ) ) + { + /* Incoming PUBLISH message has been received on the Low Temperature topic. + * Set the flag to indicate that the low temperature data has been received. */ + globalReceivedLowTemperatureData = true; + } + else + { + LogError( ( "Callback failed to identify the temperature data type received." ) ); + } + + handleIncomingPublish( pPublishInfo ); +} + +static void humidityDataCallback( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pPublishInfo != NULL ); + assert( pContext != NULL ); + + LogInfo( ( "Invoked humidity callback." ) ); + + /* Set the global flag to indicate that the humidity data has been received. */ + globalReceivedHumidityData = true; + + handleIncomingPublish( pPublishInfo ); +} + +static void precipitationDataCallback( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ) +{ + assert( pPublishInfo != NULL ); + assert( pContext != NULL ); + + LogInfo( ( "Invoked precipitation callback." ) ); + + /* Set the global flag to indicate that the humidity data has been received. */ + globalReceivedPrecipitationData = true; + + handleIncomingPublish( pPublishInfo ); +} + +static void commonEventHandler( MQTTContext_t * pMqttContext, + MQTTPacketInfo_t * pPacketInfo, + MQTTDeserializedInfo_t * pDeserializedInfo ) +{ + assert( pMqttContext != NULL ); + assert( pPacketInfo != NULL ); + assert( pDeserializedInfo != NULL ); + assert( pDeserializedInfo->packetIdentifier != MQTT_PACKET_ID_INVALID ); + + /* Handle incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + assert( pDeserializedInfo->pPublishInfo != NULL ); + /* Handle incoming publish. */ + SubscriptionManager_DispatchHandler( pMqttContext, pDeserializedInfo->pPublishInfo ); + } + else + { + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + LogInfo( ( "Received SUBACK.\n\n" ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( lastSubscribePacketIdentifier == pDeserializedInfo->packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + LogInfo( ( "Received UNSUBACK.\n\n" ) ); + /* Make sure ACK packet identifier matches with Request packet identifier. */ + assert( lastUnsubscribePacketIdentifier == pDeserializedInfo->packetIdentifier ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + + /* Nothing to be done from application as library handles + * PINGRESP. */ + LogWarn( ( "PINGRESP should not be handled by the application " + "callback when using MQTT_ProcessLoop.\n\n" ) ); + break; + + case MQTT_PACKET_TYPE_PUBACK: + LogInfo( ( "PUBACK received for packet id %u.\n\n", + pDeserializedInfo->packetIdentifier ) ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).\n\n", + pPacketInfo->type ) ); + } + } +} + +/*-----------------------------------------------------------*/ + +static int establishMqttSession( MQTTContext_t * pMqttContext, + NetworkContext_t * pNetworkContext, + bool createCleanSession, + bool * pSessionPresent ) +{ + int returnStatus = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTConnectInfo_t connectInfo; + MQTTFixedBuffer_t networkBuffer; + TransportInterface_t transport; + + assert( pMqttContext != NULL ); + assert( pNetworkContext != NULL ); + + /* Fill in TransportInterface send and receive function pointers. + * For this demo, TCP sockets are used to send and receive data + * from network. Network context is SSL context for OpenSSL.*/ + transport.pNetworkContext = pNetworkContext; + transport.send = Openssl_Send; + transport.recv = Openssl_Recv; + + /* Fill the values for network buffer. */ + networkBuffer.pBuffer = buffer; + networkBuffer.size = NETWORK_BUFFER_SIZE; + + /* Initialize MQTT library. */ + mqttStatus = MQTT_Init( pMqttContext, + &transport, + Clock_GetTimeMs, + commonEventHandler, + &networkBuffer ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "MQTT init failed with status %s.", MQTT_Status_strerror( mqttStatus ) ) ); + } + else + { + /* Establish MQTT session by sending a CONNECT packet. */ + + /* If #createCleanSession is true, start with a clean session + * i.e. direct the MQTT broker to discard any previous session data. + * If #createCleanSession is false, directs the broker to attempt to + * reestablish a session which was already present. */ + connectInfo.cleanSession = createCleanSession; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + /* The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * It is the responsibility of the Client to ensure that the interval between + * Control Packets being sent does not exceed the this Keep Alive value. In the + * absence of sending any other Control Packets, the Client MUST send a + * PINGREQ Packet. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this demo. */ + connectInfo.pUserName = NULL; + connectInfo.userNameLength = 0U; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + + /* Send MQTT CONNECT packet to broker. */ + mqttStatus = MQTT_Connect( pMqttContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "Connection with MQTT broker failed with status %u.", mqttStatus ) ); + } + else + { + LogInfo( ( "MQTT connection successfully established with broker.\n\n" ) ); + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int disconnectMqttSession( MQTTContext_t * pMqttContext ) +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + int returnStatus = EXIT_SUCCESS; + + assert( pMqttContext != NULL ); + + /* Send DISCONNECT. */ + mqttStatus = MQTT_Disconnect( pMqttContext ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "MQTT disconnect failed: Error=%s", + MQTT_Status_strerror( mqttStatus ) ) ); + returnStatus = EXIT_FAILURE; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int subscribeToTopic( MQTTContext_t * pMqttContext, + const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + int returnStatus = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pMqttContext != NULL ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This demo subscribes and publishes to topics at Qos1, so the publish + * messages received from the broker should have QoS1 as well. */ + pSubscriptionList[ 0 ].qos = MQTTQoS1; + pSubscriptionList[ 0 ].pTopicFilter = pTopicFilter; + pSubscriptionList[ 0 ].topicFilterLength = topicFilterLength; + + /* Generate packet identifier for the SUBSCRIBE packet. */ + lastSubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); + + /* Send SUBSCRIBE packet. */ + mqttStatus = MQTT_Subscribe( pMqttContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + lastSubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %u.", + mqttStatus ) ); + returnStatus = EXIT_FAILURE; + } + else + { + LogInfo( ( "SUBSCRIBE sent for topic %.*s to broker.\n\n", + topicFilterLength, + pTopicFilter ) ); + + /* Wait for acknowledgement packet (SUBACK) from the broker in response to the + * subscribe request. */ + mqttStatus = MQTT_ProcessLoop( pMqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop failed: Status=%s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int subscribeToAndRegisterTopicFilter( MQTTContext_t * pContext, + const char * pTopicFilter, + uint16_t topicFilterLength, + SubscriptionManagerCallback_t callback ) +{ + int returnStatus = EXIT_SUCCESS; + SubscriptionManagerStatus_t managerStatus = 0u; + + /* Register the topic filter and its callback with subscription manager. + * On an incoming PUBLISH message whose topic name that matches the topic filter + * being registered, its callback will be invoked. */ + managerStatus = SubscriptionManager_RegisterCallback( pTopicFilter, + topicFilterLength, + callback ); + + if( managerStatus != SUBSCRIPTION_MANAGER_SUCCESS ) + { + returnStatus = EXIT_FAILURE; + } + else + { + LogInfo( ( "Subscribing to the MQTT topic %.*s.", + topicFilterLength, + pTopicFilter ) ); + + returnStatus = subscribeToTopic( pContext, + pTopicFilter, + topicFilterLength ); + } + + if( returnStatus != EXIT_SUCCESS ) + { + /* Remove the registered callback for the temperature topic filter as + * the subscription operation for the topic filter did not succeed. */ + ( void ) SubscriptionManager_RemoveCallback( pTopicFilter, + topicFilterLength ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int unsubscribeFromTopicFilters( MQTTContext_t * pMqttContext, + const MQTTSubscribeInfo_t * pTopicFilters, + size_t numOfTopicFilters ) +{ + int returnStatus = EXIT_SUCCESS; + MQTTStatus_t mqttStatus; + size_t index = 0U; + + /* Generate packet identifier for the UNSUBSCRIBE packet. */ + lastUnsubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); + + /* Send UNSUBSCRIBE packet. */ + mqttStatus = MQTT_Unsubscribe( pMqttContext, + pTopicFilters, + numOfTopicFilters, + lastUnsubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send UNSUBSCRIBE packet to broker. Error=%s.", + MQTT_Status_strerror( mqttStatus ) ) ); + returnStatus = EXIT_FAILURE; + } + else + { + LogInfo( ( "UNSUBSCRIBE sent for the following topic filter(s)" ) ); + + for( index = 0U; index < numOfTopicFilters; index++ ) + { + LogInfo( ( "%.*s", + pTopicFilters[ index ].topicFilterLength, + pTopicFilters[ index ].pTopicFilter ) ); + } + + /* Process Incoming UNSUBACK packet from the broker. */ + mqttStatus = MQTT_ProcessLoop( pMqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "MQTT_ProcessLoop failed: Status=%s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int publishToTopicAndProcessIncomingMessage( MQTTContext_t * pMqttContext, + const char * pTopic, + uint16_t topicLength, + const char * pMessage ) +{ + int returnStatus = EXIT_SUCCESS; + + MQTTStatus_t mqttStatus = MQTTSuccess; + MQTTPublishInfo_t publishInfo = { 0 }; + uint16_t pubPacketId = MQTT_PACKET_ID_INVALID; + + assert( pMqttContext != NULL ); + + if( returnStatus == EXIT_FAILURE ) + { + LogError( ( "Unable to find a free spot for outgoing PUBLISH message.\n\n" ) ); + } + else + { + /* This example publishes with QOS1. */ + publishInfo.qos = MQTTQoS1; + publishInfo.pTopicName = pTopic; + publishInfo.topicNameLength = topicLength; + publishInfo.pPayload = pMessage; + publishInfo.payloadLength = strlen( pMessage ); + + /* Get a new packet ID for the publish. */ + pubPacketId = MQTT_GetPacketId( pMqttContext ); + + /* Send PUBLISH packet. */ + mqttStatus = MQTT_Publish( pMqttContext, + &publishInfo, + pubPacketId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send PUBLISH packet to broker with error = %u.", + mqttStatus ) ); + returnStatus = EXIT_FAILURE; + } + else + { + LogInfo( ( "PUBLISH sent for topic %.*s to broker with packet ID %u.\n\n", + topicLength, + pTopic, + pubPacketId ) ); + + /* Calling MQTT_ProcessLoop to process incoming publish echo, since + * application subscribed to the same topic the broker will send + * publish message back to the application. This function also + * sends ping request to broker if MQTT_KEEP_ALIVE_INTERVAL_SECONDS + * has expired since the last MQTT packet sent and receive + * ping responses. */ + mqttStatus = MQTT_ProcessLoop( pMqttContext, MQTT_PROCESS_LOOP_TIMEOUT_MS ); + + if( mqttStatus != MQTTSuccess ) + { + LogWarn( ( "MQTT_ProcessLoop failed: Error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int subscribePublishLoop( NetworkContext_t * pNetworkContext ) +{ + int returnStatus = EXIT_SUCCESS; + bool mqttSessionEstablished = false, sessionPresent = false; + MQTTContext_t mqttContext; + MQTTSubscribeInfo_t pSubscriptionList[ 3 ]; + + /* Establish MQTT session on top of TCP+TLS connection. */ + LogInfo( ( "Creating an MQTT connection to %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + + /* Sends an MQTT Connect packet to establish a clean connection over the + * established TLS session, then waits for connection acknowledgment + * (CONNACK) packet. */ + returnStatus = establishMqttSession( &mqttContext, + pNetworkContext, + true, /* clean session */ + &sessionPresent ); + + if( returnStatus == EXIT_SUCCESS ) + { + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be sent at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; + + /* As we requested a clean session with the MQTT broker, the broker + * should have sent the acknowledgement packet (CONNACK) with the + * session present bit set to 0. */ + assert( sessionPresent == false ); + } + + /* Subscribe to a wildcard temperature topic filter so that we can receive incoming PUBLISH + * messages from multiple topics from the broker. */ + if( returnStatus == EXIT_SUCCESS ) + { + /* Register the subscription callback for the temperature topic filter, containing the + * '+' wildcard character, so that the incoming PUBLISH messages from the broker for + * both temperature topic types (for "high" and "low" topics) can be handled by the + * callback. */ + returnStatus = subscribeToAndRegisterTopicFilter( &mqttContext, + DEMO_TEMPERATURE_TOPIC_FILTER, + DEMO_TEMPERATURE_TOPIC_FILTER_LENGTH, + temperatureDataCallback ); + } + + /* PUBLISH to the "high" temperature topic, so that the broker can send the PUBLISH message + * back to us. The temperature callback should be invoked on receiving the message back + * from the broker, as we have registered the callback on a wildcard topic filter for + * temperature topics. */ + if( returnStatus == EXIT_SUCCESS ) + { + /* Publish messages to all service topics so that broker will forward it back to us, + * and cause registered callbacks to be invoked. */ + LogInfo( ( "Publishing to topic %s.", + DEMO_TEMPERATURE_HIGH_TOPIC ) ); + + returnStatus = publishToTopicAndProcessIncomingMessage( &mqttContext, + DEMO_TEMPERATURE_HIGH_TOPIC, + DEMO_TEMPERATURE_HIGH_TOPIC_LENGTH, + DEMO_TEMPERATURE_HIGH_MESSAGE ); + } + + /* The temperature callback should have been invoked for handling the incoming + * PUBLISH message on the "high" temperature topic. */ + if( returnStatus == EXIT_SUCCESS ) + { + if( globalReceivedHighTemperatureData != true ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "Registered callback was not invoked on sending a PUBLISH to " + "the high temperature topic: Topic=%s", DEMO_TEMPERATURE_HIGH_TOPIC ) ); + } + } + + /* PUBLISH to the "low" temperature topic, so that the broker can send the PUBLISH message + * back to us. The temperature callback should be invoked on receiving the message back + * from the broker, as we have registered the callback on a wildcard topic filter for + * temperature topics. */ + if( returnStatus == EXIT_SUCCESS ) + { + /* Publish messages to all service topics so that broker will forward it back to us, + * and cause registered callbacks to be invoked. */ + LogInfo( ( "Publishing to topic %s.", + DEMO_TEMPERATURE_LOW_TOPIC ) ); + + returnStatus = publishToTopicAndProcessIncomingMessage( &mqttContext, + DEMO_TEMPERATURE_LOW_TOPIC, + DEMO_TEMPERATURE_LOW_TOPIC_LENGTH, + DEMO_TEMPERATURE_LOW_MESSAGE ); + } + + /* The temperature callback should have been invoked for handling the incoming + * PUBLISH message on the "low" temperature topic. */ + if( returnStatus == EXIT_SUCCESS ) + { + if( globalReceivedLowTemperatureData != true ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "Registered callback was not invoked on sending a PUBLISH to " + "the low temperature topic: Topic=%s", DEMO_TEMPERATURE_LOW_TOPIC ) ); + } + } + + /* Subscribe to a humidity topic filter, this time without any wildcard characters, so that we can + * receive incoming PUBLISH message only on the same topic from the broker. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = subscribeToAndRegisterTopicFilter( &mqttContext, + DEMO_HUMIDITY_TOPIC, + DEMO_HUMIDITY_TOPIC_LENGTH, + humidityDataCallback ); + } + + /* PUBLISH to the humidity topic, so that the broker can send the PUBLISH message + * back to us. The humidity callback should be invoked on receiving the message back + * from the broker, as we have registered the callback for the humidity topic filter. */ + if( returnStatus == EXIT_SUCCESS ) + { + /* Publish messages to all service topics so that broker will forward it back to us, + * and cause registered callbacks to be invoked. */ + LogInfo( ( "Publish to topic %s.", + DEMO_HUMIDITY_TOPIC ) ); + + returnStatus = publishToTopicAndProcessIncomingMessage( &mqttContext, + DEMO_HUMIDITY_TOPIC, + DEMO_HUMIDITY_TOPIC_LENGTH, + DEMO_HUMIDITY_MESSAGE ); + } + + /* The humidity callback should have been invoked for handling the incoming + * PUBLISH message on the humidity topic. */ + if( returnStatus == EXIT_SUCCESS ) + { + if( globalReceivedHumidityData != true ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "Registered callback was not invoked on sending a PUBLISH to " + "the humidity topic: Topic=%s", DEMO_HUMIDITY_TOPIC ) ); + } + } + + /* Subscribe to the precipitation topic filter, this time also without any wildcard characters, + * so that we can receive incoming PUBLISH message only on the same topic from the broker. */ + if( returnStatus == EXIT_SUCCESS ) + { + returnStatus = subscribeToAndRegisterTopicFilter( &mqttContext, + DEMO_PRECIPITATION_TOPIC, + DEMO_PRECIPITATION_TOPIC_LENGTH, + precipitationDataCallback ); + } + + /* PUBLISH to the precipitation topic, so that the broker can send the PUBLISH message + * back to us. The precipitation callback should be invoked on receiving the message back + * from the broker, as we have registered the callback for the precipitation topic filter. */ + if( returnStatus == EXIT_SUCCESS ) + { + /* Publish messages to all service topics so that broker will forward it back to us, + * and cause registered callbacks to be invoked. */ + LogInfo( ( "Publish to topic %s.", + DEMO_PRECIPITATION_TOPIC ) ); + + returnStatus = publishToTopicAndProcessIncomingMessage( &mqttContext, + DEMO_PRECIPITATION_TOPIC, + DEMO_PRECIPITATION_TOPIC_LENGTH, + DEMO_PRECIPITATION_MESSAGE ); + } + + /* The precipitation callback should have been invoked for handling the incoming + * PUBLISH message on the precipitation topic. */ + if( returnStatus == EXIT_SUCCESS ) + { + if( globalReceivedPrecipitationData != true ) + { + returnStatus = EXIT_FAILURE; + LogError( ( "Registered callback was not invoked on sending a PUBLISH to " + "the precipitation topic: Topic=%s", DEMO_PRECIPITATION_TOPIC ) ); + } + } + + /* As we don't have any further PUBLISH/SUBSCRIBE operations in the loop iteration further + * we will perform clean-up operations of removing the subscription callbacks registrations, + * and unsubscribing from the temperature and humidity topic filters.*/ + + /* Remove all callbacks from the subscription manager. */ + if( returnStatus == EXIT_SUCCESS ) + { + /* Remove the callback for temperature topics. */ + SubscriptionManager_RemoveCallback( DEMO_TEMPERATURE_TOPIC_FILTER, + DEMO_TEMPERATURE_TOPIC_FILTER_LENGTH ); + + /* Remove the callback for the humidity topic filter. */ + SubscriptionManager_RemoveCallback( DEMO_HUMIDITY_TOPIC, + DEMO_HUMIDITY_TOPIC_LENGTH ); + + /* Remove the callback for the precipitation topic filter. */ + SubscriptionManager_RemoveCallback( DEMO_PRECIPITATION_TOPIC, + DEMO_PRECIPITATION_TOPIC_LENGTH ); + + /* Set the subscription list memory to zero . */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* Populate the array with topic filters to unsubscribe from. + * We will use a single UNSUBSCRIBE packet to unsubscribe from all + * topic filters. */ + pSubscriptionList[ 0 ].pTopicFilter = DEMO_TEMPERATURE_TOPIC_FILTER; + pSubscriptionList[ 0 ].topicFilterLength = DEMO_TEMPERATURE_TOPIC_FILTER_LENGTH; + pSubscriptionList[ 1 ].pTopicFilter = DEMO_HUMIDITY_TOPIC; + pSubscriptionList[ 1 ].topicFilterLength = DEMO_HUMIDITY_TOPIC_LENGTH; + pSubscriptionList[ 2 ].pTopicFilter = DEMO_PRECIPITATION_TOPIC; + pSubscriptionList[ 2 ].topicFilterLength = DEMO_PRECIPITATION_TOPIC_LENGTH; + + /* Unsubscribe from all topic filters of temperature, humidity and + * precipitation data. */ + returnStatus = unsubscribeFromTopicFilters( &mqttContext, + pSubscriptionList, + sizeof( pSubscriptionList ) + / sizeof( MQTTSubscribeInfo_t ) ); + } + + /* Send an MQTT Disconnect packet over the already connected TCP socket. + * There is no corresponding response for the disconnect packet. After sending + * disconnect, client must close the network connection. */ + if( mqttSessionEstablished == true ) + { + LogInfo( ( "Disconnecting the MQTT connection with %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + + if( returnStatus == EXIT_FAILURE ) + { + /* Returned status is not used to update the local status as there + * were failures in demo execution. */ + ( void ) disconnectMqttSession( &mqttContext ); + } + else + { + returnStatus = disconnectMqttSession( &mqttContext ); + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Entry point of demo. + * + * The example shown below uses MQTT APIs to send and receive MQTT packets + * over the TLS connection established using OpenSSL. + * + * The example is single threaded, uses statically allocated memory, and + * uses QOS1 for publishing messages to the broker. + */ +int main( int argc, + char ** argv ) +{ + int returnStatus = EXIT_SUCCESS; + NetworkContext_t networkContext; + + ( void ) argc; + ( void ) argv; + + for( ; ; ) + { + /* Attempt to connect to the MQTT broker. If connection fails, retry after + * a timeout. Timeout value will be exponentially increased till the maximum + * attempts are reached or maximum timeout value is reached. The function + * returns EXIT_FAILURE if the TCP connection cannot be established to + * broker after configured number of attempts. */ + returnStatus = connectToServerWithBackoffRetries( &networkContext ); + + if( returnStatus == EXIT_FAILURE ) + { + /* Log error to indicate connection failure after all + * reconnect attempts are over. */ + LogError( ( "Failed to connect to MQTT broker %.*s.", + BROKER_ENDPOINT_LENGTH, + BROKER_ENDPOINT ) ); + } + else + { + /* If TLS session is established, execute Subscribe/Publish loop. */ + returnStatus = subscribePublishLoop( &networkContext ); + } + + if( returnStatus == EXIT_SUCCESS ) + { + /* Log message indicating an iteration completed successfully. */ + LogInfo( ( "Demo completed successfully." ) ); + } + + /* End TLS session, then close TCP connection. */ + ( void ) Openssl_Disconnect( &networkContext ); + + LogInfo( ( "Short delay (of %u seconds) before starting the next iteration ....\n", + MQTT_SUBPUB_LOOP_DELAY_SECONDS ) ); + sleep( MQTT_SUBPUB_LOOP_DELAY_SECONDS ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/mqtt/subscription-manager/CMakeLists.txt b/demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/CMakeLists.txt similarity index 100% rename from demos/mqtt/subscription-manager/CMakeLists.txt rename to demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/CMakeLists.txt diff --git a/demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/mqtt_subscription_manager.c b/demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/mqtt_subscription_manager.c new file mode 100644 index 0000000000..2a0484dfba --- /dev/null +++ b/demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/mqtt_subscription_manager.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file mqtt_subscription_manager.c + * @brief Implementation of the API of a subscription manager for handling subscription callbacks + * to topic filters in MQTT operations. + */ + +/* Standard includes. */ +#include +#include + +/* Include header for the subscription manager. */ +#include "mqtt_subscription_manager.h" + +/** + * @brief Represents a registered record of the topic filter and its associated callback + * in the subscription manager registry. + */ +typedef struct SubscriptionManagerRecord +{ + const char * pTopicFilter; + uint16_t topicFilterLength; + SubscriptionManagerCallback_t callback; +} SubscriptionManagerRecord_t; + +/** + * @brief The default value for the maximum size of the callback registry in the + * subscription manager. + */ +#ifndef MAX_SUBSCRIPTION_CALLBACK_RECORDS + #define MAX_SUBSCRIPTION_CALLBACK_RECORDS 5 +#endif + +/** + * @brief The registry to store records of topic filters and their subscription callbacks. + */ +static SubscriptionManagerRecord_t callbackRecordList[ MAX_SUBSCRIPTION_CALLBACK_RECORDS ] = { 0 }; + +/*-----------------------------------------------------------*/ + +void SubscriptionManager_DispatchHandler( MQTTContext_t * pContext, + MQTTPublishInfo_t * pPublishInfo ) +{ + bool matchStatus = false; + size_t listIndex = 0u; + + assert( pPublishInfo != NULL ); + assert( pContext != NULL ); + + /* Iterate through record list to find matching topics, and invoke their callbacks. */ + for( listIndex = 0; listIndex < MAX_SUBSCRIPTION_CALLBACK_RECORDS; listIndex++ ) + { + if( ( callbackRecordList[ listIndex ].pTopicFilter != NULL ) && + ( MQTT_MatchTopic( pPublishInfo->pTopicName, + pPublishInfo->topicNameLength, + callbackRecordList[ listIndex ].pTopicFilter, + callbackRecordList[ listIndex ].topicFilterLength, + &matchStatus ) == MQTTSuccess ) && + ( matchStatus == true ) ) + { + LogInfo( ( "Invoking subscription callback of matching topic filter: " + "TopicFilter=%.*s, TopicName=%.*s", + callbackRecordList[ listIndex ].topicFilterLength, + callbackRecordList[ listIndex ].pTopicFilter, + pPublishInfo->topicNameLength, + pPublishInfo->pTopicName ) ); + + /* Invoke the callback associated with the record as the topics match. */ + callbackRecordList[ listIndex ].callback( pContext, pPublishInfo ); + } + } +} + +/*-----------------------------------------------------------*/ + +SubscriptionManagerStatus_t SubscriptionManager_RegisterCallback( const char * pTopicFilter, + uint16_t topicFilterLength, + SubscriptionManagerCallback_t callback ) +{ + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + assert( callback != NULL ); + + SubscriptionManagerStatus_t returnStatus; + size_t availableIndex = MAX_SUBSCRIPTION_CALLBACK_RECORDS; + bool recordExists = false; + size_t index = 0u; + + /* Search for the first available spot in the list to store the record, and also check if + * a record for the topic filter already exists. */ + while( ( recordExists == false ) && ( index < MAX_SUBSCRIPTION_CALLBACK_RECORDS ) ) + { + /* Check if the index represents an empty spot in the registry. If we had already + * found an empty spot in the list, we will not update it. */ + if( ( availableIndex == MAX_SUBSCRIPTION_CALLBACK_RECORDS ) && + ( callbackRecordList[ index ].pTopicFilter == NULL ) ) + { + availableIndex = index; + } + + /* Check if the current record's topic filter in the registry matches the topic filter + * we are trying to register. */ + else if( ( callbackRecordList[ index ].topicFilterLength == topicFilterLength ) && + ( strncmp( pTopicFilter, callbackRecordList[ index ].pTopicFilter, topicFilterLength ) + == 0 ) ) + { + recordExists = true; + } + + index++; + } + + if( recordExists == true ) + { + /* The record for the topic filter already exists. */ + LogError( ( "Failed to register callback: Record for topic filter already exists: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + + returnStatus = SUBSCRIPTION_MANAGER_RECORD_EXISTS; + } + else if( availableIndex == MAX_SUBSCRIPTION_CALLBACK_RECORDS ) + { + /* The registry is full. */ + LogError( ( "Unable to register callback: Registry list is full: TopicFilter=%.*s, MaxRegistrySize=%u", + topicFilterLength, + pTopicFilter, + MAX_SUBSCRIPTION_CALLBACK_RECORDS ) ); + + returnStatus = SUBSCRIPTION_MANAGER_REGISTRY_FULL; + } + else + { + callbackRecordList[ availableIndex ].pTopicFilter = pTopicFilter; + callbackRecordList[ availableIndex ].topicFilterLength = topicFilterLength; + callbackRecordList[ availableIndex ].callback = callback; + + returnStatus = SUBSCRIPTION_MANAGER_SUCCESS; + + LogDebug( ( "Added callback to registry: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +void SubscriptionManager_RemoveCallback( const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + assert( pTopicFilter != NULL ); + assert( topicFilterLength != 0 ); + + size_t index; + SubscriptionManagerRecord_t * pRecord = NULL; + + /* Iterate through the records list to find the matching record. */ + for( index = 0; index < MAX_SUBSCRIPTION_CALLBACK_RECORDS; index++ ) + { + pRecord = &callbackRecordList[ index ]; + + /* Only match the non-empty records. */ + if( pRecord->pTopicFilter != NULL ) + { + if( ( topicFilterLength == pRecord->topicFilterLength ) && + ( strncmp( pTopicFilter, pRecord->pTopicFilter, topicFilterLength ) == 0 ) ) + { + break; + } + } + } + + /* Delete the record by clearing the found entry in the records list. */ + if( index < MAX_SUBSCRIPTION_CALLBACK_RECORDS ) + { + pRecord->pTopicFilter = NULL; + pRecord->topicFilterLength = 0u; + pRecord->callback = NULL; + + LogDebug( ( "Deleted callback record for topic filter: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + } + else + { + LogWarn( ( "Attempted to remove callback for un-registered topic filter: TopicFilter=%.*s", + topicFilterLength, + pTopicFilter ) ); + } +} +/*-----------------------------------------------------------*/ diff --git a/demos/mqtt/subscription-manager/mqtt_subscription_manager.h b/demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/mqtt_subscription_manager.h similarity index 100% rename from demos/mqtt/subscription-manager/mqtt_subscription_manager.h rename to demos/mqtt/mqtt_demo_connection_sharing/subscription-manager/mqtt_subscription_manager.h diff --git a/demos/mqtt/subscription-manager/mqtt_subscription_manager.c b/demos/mqtt/subscription-manager/mqtt_subscription_manager.c deleted file mode 100644 index b367910199..0000000000 --- a/demos/mqtt/subscription-manager/mqtt_subscription_manager.c +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file mqtt_subscription_manager.c - * @brief Implementation of the API of a subscription manager for handling subscription callbacks - * to topic filters in MQTT operations. - */ - -/* Standard includes. */ -#include -#include - -/* Include header for the subscription manager. */ -#include "mqtt_subscription_manager.h" - -/** - * @brief Represents a registered record of the topic filter and its associated callback - * in the subscription manager registry. - */ -typedef struct SubscriptionManagerRecord -{ - const char * pTopicFilter; - uint16_t topicFilterLength; - SubscriptionManagerCallback_t callback; -} SubscriptionManagerRecord_t; - -/** - * @brief The default value for the maximum size of the callback registry in the - * subscription manager. - */ -#ifndef MAX_SUBSCRIPTION_CALLBACK_RECORDS - #define MAX_SUBSCRIPTION_CALLBACK_RECORDS 5 -#endif - -/** - * @brief The registry to store records of topic filters and their subscription callbacks. - */ -static SubscriptionManagerRecord_t callbackRecordList[ MAX_SUBSCRIPTION_CALLBACK_RECORDS ] = { 0 }; - -/** - * @brief Handle special corner cases for wildcards at the end of topic - * filters, as documented by the MQTT protocol spec. - * - * It concludes a match between the topic name and topic filter for the - * following special cases: - * - When the topic filter ends with '+' character, but the topic name only - * ends with '/'. - * - When the topic filter ends with "/#" characters, but the topic name - * ends at the parent level. - * - * @param[in] pTopicFilter The topic filter containing the wildcard. - * @param[in] topicFilterLength Length of the topic filter being examined. - * @param[in] topicNameLength Length of the topic name being examined. - * @param[in] nameIndex Index of the topic name being examined. - * @param[in] filterIndex Index of the topic filter being examined. - * - * @return Returns whether the topic filter and the topic name match. - */ -static bool matchWildcardsSpecialCases( const char * pTopicFilter, - uint16_t topicFilterLength, - uint16_t topicNameLength, - uint16_t nameIndex, - uint16_t filterIndex ); - -/** - * @brief Attempt to match topic name with a topic filter starting with a wildcard. - * - * If the topic filter starts with a '+' (single-level) wildcard, the function - * advances the @a pNameIndex by a level in the topic name. - * If the topic filter starts with a '#' (multi-level) wildcard, the function - * concludes that both the topic name and topic filter match. - * - * @param[in] pTopicFilter The topic filter containing the wildcard. - * @param[in] pTopicName The topic name to check. - * @param[in] topicNameLength Length of the topic name. - * @param[in] filterIndex Index of the wildcard in the topic filter. - * @param[in,out] pNameIndex Index of character in topic name. This variable is - * advanced for `+` wildcards. - * @param[out] pMatch Whether the topic filter and topic name match. - * - * @return `true` if the caller of this function should exit; `false` if the caller - * should continue parsing the topics. - */ -static bool matchWildcards( const char * pTopicFilter, - const char * pTopicName, - uint16_t topicNameLength, - uint16_t filterIndex, - uint16_t * pNameIndex, - bool * pMatch ); - -/** - * @brief Match a topic name and topic filter allowing the use of wildcards. - * - * @param[in] pTopicName The topic name to check. - * @param[in] topicNameLength Length of the topic name. - * @param[in] pTopicFilter The topic filter to check. - * @param[in] topicFilterLength Length of topic filter. - * - * @return `true` if the topic name and topic filter match; `false` otherwise. - */ -static bool matchTopicFilter( const char * pTopicName, - uint16_t topicNameLength, - const char * pTopicFilter, - uint16_t topicFilterLength ); - -/** - * @brief Matches a topic name (from a incoming PUBLISH) with a topic filter. - * - * @param[in] pTopicName The topic name to check. - * @param[in] topicNameLength Length of the topic name. - * @param[in] pTopicFilter The topic filter to check. - * @param[in] topicFilterLength Length of topic filter. - * - * @return `true` if the topic name and topic filter match; `false` - * otherwise. - */ -static bool matchTopic( const char * pTopicName, - const uint16_t topicNameLength, - const char * pTopicFilter, - const uint16_t topicFilterLength ); - -/*-----------------------------------------------------------*/ - -static bool matchWildcardsSpecialCases( const char * pTopicFilter, - uint16_t topicFilterLength, - uint16_t topicNameLength, - uint16_t nameIndex, - uint16_t filterIndex ) -{ - bool matchFound = false; - - /* Determine if the last character is reached for the topic name, and the - * third to last character is reached for the topic filter. */ - if( ( nameIndex == ( topicNameLength - 1U ) ) && - ( filterIndex == ( topicFilterLength - 3U ) ) ) - { - /* Determine if the topic filter contains "/#" as the last 2 characters. - * The '#' wildcard represents the parent and any number of child levels - * in the topic name. For example, the filter "sport/#" matches "sport" - * as well as "sport/tennis" topics. */ - matchFound = ( ( pTopicFilter[ filterIndex + 1U ] == '/' ) && - ( pTopicFilter[ filterIndex + 2U ] == '#' ) ) ? true : false; - } - else - { - /* Determine if the last character is reached for the topic name and, - * the second to last character for the topic filter. */ - if( ( nameIndex == ( topicNameLength - 1U ) ) && - ( filterIndex == ( topicFilterLength - 2U ) ) ) - { - /* Determine if the topic filter contains "+" as the last character. - * This covers the special case of topic matching when the topic name - * ends with '/' but the topic filter ends with "/+". Thus, for example, - * topic filter "sport/+" matches the "sport/" but not "sport". */ - matchFound = ( pTopicFilter[ filterIndex + 1U ] == '+' ) ? true : false; - } - } - - return matchFound; -} - -/*-----------------------------------------------------------*/ - -static bool matchWildcards( const char * pTopicFilter, - const char * pTopicName, - uint16_t topicNameLength, - uint16_t filterIndex, - uint16_t * pNameIndex, - bool * pMatch ) -{ - bool shouldStopMatching = false; - - /* Check for wildcards. */ - if( pTopicFilter[ filterIndex ] == '+' ) - { - /* Move topic name index to the end of the current level. - * This is identified by '/'. */ - while( ( *pNameIndex < topicNameLength ) && ( pTopicName[ *pNameIndex ] != '/' ) ) - { - ( *pNameIndex )++; - } - - /* Decrement the topic name index for 2 different cases: - * - If the break condition is ( *pNameIndex < topicNameLength ), then - * we have reached the end of the topic name. - * - If the break condition is ( pTopicName[ *pNameIndex ] != '/' ), - * we move back the index on the '/' character to be at the last - * position in the current topic level. */ - ( *pNameIndex )--; - } - else if( pTopicFilter[ filterIndex ] == '#' ) - { - /* Subsequent characters don't need to be checked for the - * multi-level wildcard. */ - *pMatch = true; - shouldStopMatching = true; - } - else - { - /* Any character mismatch other than '+' or '#' means the topic - * name does not match the topic filter. */ - *pMatch = false; - shouldStopMatching = true; - } - - return shouldStopMatching; -} - -/*-----------------------------------------------------------*/ - -static bool matchTopicFilter( const char * pTopicName, - uint16_t topicNameLength, - const char * pTopicFilter, - uint16_t topicFilterLength ) -{ - bool matchFound = false, shouldStopMatching = false; - uint16_t nameIndex = 0, filterIndex = 0; - - assert( pTopicName != NULL ); - assert( topicNameLength != 0 ); - assert( pTopicFilter != NULL ); - assert( topicFilterLength != 0 ); - - while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) - { - /* Check if the character in the topic name matches the corresponding - * character in the topic filter string. */ - if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] ) - { - /* Handle special corner cases regarding wildcards at the end of - * topic filters, as documented by the MQTT protocol spec. */ - matchFound = matchWildcardsSpecialCases( pTopicFilter, - topicFilterLength, - topicNameLength, - nameIndex, - filterIndex ); - } - else - { - /* Check for matching wildcards. */ - shouldStopMatching = matchWildcards( pTopicFilter, - pTopicName, - topicNameLength, - filterIndex, - &nameIndex, - &matchFound ); - } - - if( ( matchFound == true ) || ( shouldStopMatching == true ) ) - { - break; - } - - /* Increment indexes. */ - nameIndex++; - filterIndex++; - } - - if( matchFound == false ) - { - /* If the end of both strings has been reached, they match. This represents the - * case when the topic filter contains the '+' wildcard at a non-starting position. - * For example, when matching either of "sport/+/player" OR "sport/hockey/+" topic - * filters with "sport/hockey/player" topic name. */ - matchFound = ( ( nameIndex == topicNameLength ) && - ( filterIndex == topicFilterLength ) ) ? true : false; - } - - return matchFound; -} - -/*-----------------------------------------------------------*/ - -static bool matchTopic( const char * pTopicName, - const uint16_t topicNameLength, - const char * pTopicFilter, - const uint16_t topicFilterLength ) -{ - assert( pTopicName != NULL ); - assert( topicNameLength != 0 ); - assert( pTopicFilter != NULL ); - assert( topicFilterLength != 0 ); - - bool status = false; - bool topicFilterStartsWithWildcard = false; - - /* Check for an exact match if the incoming topic name and the registered - * topic filter length match. */ - if( topicNameLength == topicFilterLength ) - { - status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ) ? true : false; - } - - if( status == false ) - { - /* If an exact match was not found, match against wildcard characters in - * topic filter.*/ - - /* Determine if topic filter starts with a wildcard. */ - topicFilterStartsWithWildcard = ( ( pTopicFilter[ 0 ] == '+' ) || - ( pTopicFilter[ 0 ] == '#' ) ) ? true : false; - - /* Note: According to the MQTT 3.1.1 specification, incoming PUBLISH topic names - * starting the "$" character cannot be matched against topic filter starting with - * a wildcard, i.e. for example, "$SYS/sport" cannot be matched with "#" or - * "+/sport" topic filters. */ - if( !( ( pTopicName[ 0 ] == '$' ) && ( topicFilterStartsWithWildcard == true ) ) ) - { - status = matchTopicFilter( pTopicName, topicNameLength, pTopicFilter, topicFilterLength ); - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -/* Design - - * * Common handler can contain logic of processing acks and retries. - * * Only forward PUBLISH message to message dispatcher/handler as individual callbacks - * only want to process incoming PUBLISH message. - */ -void SubscriptionManager_DispatchHandler( MQTTContext_t * pContext, - MQTTPublishInfo_t * pPublishInfo ) -{ - assert( pPublishInfo != NULL ); - assert( pContext != NULL ); - - size_t listIndex = 0u; - - /* Iterate through record list to find matching topics, and invoke their callbacks. */ - for( listIndex = 0; listIndex < MAX_SUBSCRIPTION_CALLBACK_RECORDS; listIndex++ ) - { - if( ( callbackRecordList[ listIndex ].pTopicFilter != NULL ) && - ( matchTopic( pPublishInfo->pTopicName, - pPublishInfo->topicNameLength, - callbackRecordList[ listIndex ].pTopicFilter, - callbackRecordList[ listIndex ].topicFilterLength ) == true ) ) - { - LogInfo( ( "Invoking subscription callback of matching topic filter: " - "TopicFilter=%.*s, TopicName=%.*s", - callbackRecordList[ listIndex ].topicFilterLength, - callbackRecordList[ listIndex ].pTopicFilter, - pPublishInfo->topicNameLength, - pPublishInfo->pTopicName ) ); - - /* Invoke the callback associated with the record as the topics match. */ - callbackRecordList[ listIndex ].callback( pContext, pPublishInfo ); - } - } -} - -/*-----------------------------------------------------------*/ - -SubscriptionManagerStatus_t SubscriptionManager_RegisterCallback( const char * pTopicFilter, - uint16_t topicFilterLength, - SubscriptionManagerCallback_t callback ) -{ - assert( pTopicFilter != NULL ); - assert( topicFilterLength != 0 ); - assert( callback != NULL ); - - SubscriptionManagerStatus_t returnStatus; - size_t availableIndex = MAX_SUBSCRIPTION_CALLBACK_RECORDS; - bool recordExists = false; - size_t index = 0u; - - /* Search for the first available spot in the list to store the record, and also check if - * a record for the topic filter already exists. */ - while( ( recordExists == false ) && ( index < MAX_SUBSCRIPTION_CALLBACK_RECORDS ) ) - { - /* Check if the index represents an empty spot in the registry. If we had already - * found an empty spot in the list, we will not update it. */ - if( ( availableIndex == MAX_SUBSCRIPTION_CALLBACK_RECORDS ) && - ( callbackRecordList[ index ].pTopicFilter == NULL ) ) - { - availableIndex = index; - } - - /* Check if the current record's topic filter in the registry matches the topic filter - * we are trying to register. */ - else if( ( callbackRecordList[ index ].topicFilterLength == topicFilterLength ) && - ( strncmp( pTopicFilter, callbackRecordList[ index ].pTopicFilter, topicFilterLength ) - == 0 ) ) - { - recordExists = true; - } - - index++; - } - - if( recordExists == true ) - { - /* The record for the topic filter already exists. */ - LogError( ( "Failed to register callback: Record for topic filter already exists: TopicFilter=%.*s", - topicFilterLength, - pTopicFilter ) ); - - returnStatus = SUBSCRIPTION_MANAGER_RECORD_EXISTS; - } - else if( availableIndex == MAX_SUBSCRIPTION_CALLBACK_RECORDS ) - { - /* The registry is full. */ - LogError( ( "Unable to register callback: Registry list is full: TopicFilter=%.*s, MaxRegistrySize=%u", - topicFilterLength, - pTopicFilter, - MAX_SUBSCRIPTION_CALLBACK_RECORDS ) ); - - returnStatus = SUBSCRIPTION_MANAGER_REGISTRY_FULL; - } - else - { - callbackRecordList[ availableIndex ].pTopicFilter = pTopicFilter; - callbackRecordList[ availableIndex ].topicFilterLength = topicFilterLength; - callbackRecordList[ availableIndex ].callback = callback; - - returnStatus = SUBSCRIPTION_MANAGER_SUCCESS; - - LogDebug( ( "Added callback to registry: TopicFilter=%.*s", - topicFilterLength, - pTopicFilter ) ); - } - - return returnStatus; -} - -/*-----------------------------------------------------------*/ - -void SubscriptionManager_RemoveCallback( const char * pTopicFilter, - uint16_t topicFilterLength ) -{ - assert( pTopicFilter != NULL ); - assert( topicFilterLength != 0 ); - - size_t index; - SubscriptionManagerRecord_t * pRecord = NULL; - - /* Iterate through the records list to find the matching record. */ - for( index = 0; index < MAX_SUBSCRIPTION_CALLBACK_RECORDS; index++ ) - { - pRecord = &callbackRecordList[ index ]; - - /* Only match the non-empty records. */ - if( pRecord->pTopicFilter != NULL ) - { - if( ( topicFilterLength == pRecord->topicFilterLength ) && - ( strncmp( pTopicFilter, pRecord->pTopicFilter, topicFilterLength ) == 0 ) ) - { - break; - } - } - } - - /* Delete the record by clearing the found entry in the records list. */ - if( index < MAX_SUBSCRIPTION_CALLBACK_RECORDS ) - { - pRecord->pTopicFilter = NULL; - pRecord->topicFilterLength = 0u; - pRecord->callback = NULL; - - LogDebug( ( "Deleted callback record for topic filter: TopicFilter=%.*s", - topicFilterLength, - pTopicFilter ) ); - } - else - { - LogWarn( ( "Attempted to remove callback for un-registered topic filter: TopicFilter=%.*s", - topicFilterLength, - pTopicFilter ) ); - } -} -/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index edb7136a6d..ead44efd00 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -328,4 +328,4 @@ xa xb xc xd -xe \ No newline at end of file +xe diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index f467be2ae4..e3d8d55212 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -318,11 +318,11 @@ static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext, uint16_t packetId ); /** - * @brief Performs matching for special cases when the passed topic filter - * ends with a wildcard character. + * @brief Performs matching for special cases when a topic filter ends + * with a wildcard character. * * When the topic name has been consumed but there are remaining characters to - * to match in the topic filter, this function handles the following 2 cases: + * to match in topic filter, this function handles the following 2 cases: * - When the topic filter ends with "/+" or "/#" characters, but the topic * name only ends with '/'. * - When the topic filter ends with "/#" characters, but the topic name From fa7520c003bdbe8423b1bde4beff218105089d53 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 20 Aug 2020 17:25:04 -0700 Subject: [PATCH 643/844] Fix flaky behavior of LWT test (#1147) --- .../standard/mqtt/integration-test/mqtt_system_test.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index 276d1dcf31..e441628130 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -513,6 +513,9 @@ static void eventCallback( MQTTContext_t * pContext, assert( pPacketInfo != NULL ); assert( pDeserializedInfo != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pContext; + TEST_ASSERT_EQUAL( MQTTSuccess, pDeserializedInfo->deserializationResult ); pPublishInfo = pDeserializedInfo->pPublishInfo; @@ -971,6 +974,11 @@ void test_MQTT_Connect_LWT( void ) TEST_ASSERT_EQUAL( MQTTSuccess, subscribeToTopic( &context, TEST_MQTT_LWT_TOPIC, MQTTQoS0 ) ); + /* Wait for the SUBACK response from the broker for the subscribe request. */ + TEST_ASSERT_EQUAL( MQTTSuccess, + MQTT_ProcessLoop( &context, MQTT_PROCESS_LOOP_TIMEOUT_MS ) ); + TEST_ASSERT_TRUE( receivedSubAck ); + /* Abruptly terminate TCP connection. */ ( void ) Openssl_Disconnect( &secondNetworkContext ); From 854f55a981d300abdc02948070e4244f988590da Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Thu, 20 Aug 2020 18:04:37 -0700 Subject: [PATCH 644/844] Fix warnings in MQTT library and demo targets as CI enforces warnings as errors (#1143) * Add -Werror flag to MQTT library and demo targets * Fix unused param/variable warnings in demos from asserts disabled in Release builds * Fix warnings in connection sharing demo --- .../mqtt_demo_basic_tls/mqtt_demo_basic_tls.c | 3 +++ .../mqtt_demo_connection_sharing.c | 9 +++++++ .../mqtt_demo_lightweight.c | 26 +++++++++++++++++++ .../mqtt_demo_mutual_auth.c | 3 +++ .../mqtt_demo_plaintext/mqtt_demo_plaintext.c | 3 +++ 5 files changed, 44 insertions(+) diff --git a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c index 6fa2fb00e2..cf2e84c15f 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c +++ b/demos/mqtt/mqtt_demo_basic_tls/mqtt_demo_basic_tls.c @@ -622,6 +622,9 @@ static void eventCallback( MQTTContext_t * pMqttContext, assert( pPacketInfo != NULL ); assert( pDeserializedInfo != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pMqttContext; + packetIdentifier = pDeserializedInfo->packetIdentifier; /* Handle incoming publish. The lower 4 bits of the publish packet diff --git a/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c b/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c index 5fd7d8e2bc..30106acb03 100644 --- a/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c +++ b/demos/mqtt/mqtt_demo_connection_sharing/mqtt_demo_connection_sharing.c @@ -559,6 +559,9 @@ static void temperatureDataCallback( MQTTContext_t * pContext, assert( pPublishInfo != NULL ); assert( pContext != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pContext; + LogInfo( ( "Invoked temperature callback." ) ); /* Determine whether the incoming PUBLISH message is for "high" @@ -593,6 +596,9 @@ static void humidityDataCallback( MQTTContext_t * pContext, assert( pPublishInfo != NULL ); assert( pContext != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pContext; + LogInfo( ( "Invoked humidity callback." ) ); /* Set the global flag to indicate that the humidity data has been received. */ @@ -607,6 +613,9 @@ static void precipitationDataCallback( MQTTContext_t * pContext, assert( pPublishInfo != NULL ); assert( pContext != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pContext; + LogInfo( ( "Invoked precipitation callback." ) ); /* Set the global flag to indicate that the humidity data has been received. */ diff --git a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c index 4507c36197..0b01fb2eb5 100644 --- a/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c +++ b/demos/mqtt/mqtt_demo_lightweight/mqtt_demo_lightweight.c @@ -462,6 +462,10 @@ static void mqttSubscribeToTopic( NetworkContext_t * pNetworkContext, size_t packetSize; int status; + /* Suppress unused variable warnings when asserts are disabled in build. */ + ( void ) status; + ( void ) result; + /*** * For readability, error handling in this function is restricted to the use of * asserts(). @@ -510,6 +514,10 @@ static void mqttPublishToTopic( NetworkContext_t * pNetworkContext, size_t headerSize = 0; int status; + /* Suppress unused variable warnings when asserts are disabled in build. */ + ( void ) status; + ( void ) result; + /*** * For readability, error handling in this function is restricted to the use of * asserts(). @@ -562,6 +570,10 @@ static void mqttUnsubscribeFromTopic( NetworkContext_t * pNetworkContext, size_t packetSize; int status; + /* Suppress unused variable warnings when asserts are disabled in build. */ + ( void ) status; + ( void ) result; + /* Some fields not used by this demo so start with everything at 0. */ memset( ( void * ) &mqttSubscription, 0x00, sizeof( mqttSubscription ) ); @@ -601,6 +613,10 @@ static void mqttKeepAlive( NetworkContext_t * pNetworkContext, int status; size_t packetSize = 0; + /* Suppress unused variable warnings when asserts are disabled in build. */ + ( void ) status; + ( void ) result; + /* Calculate PING request size. */ status = MQTT_GetPingreqPacketSize( &packetSize ); @@ -624,6 +640,10 @@ static void mqttDisconnect( NetworkContext_t * pNetworkContext, int32_t status; size_t packetSize = 0; + /* Suppress unused variable warnings when asserts are disabled in build. */ + ( void ) status; + ( void ) result; + status = MQTT_GetDisconnectPacketSize( &packetSize ); assert( packetSize <= pFixedBuffer->size ); @@ -641,6 +661,9 @@ static void mqttDisconnect( NetworkContext_t * pNetworkContext, static void mqttProcessResponse( MQTTPacketInfo_t * pIncomingPacket, uint16_t packetId ) { + /* Suppress unused parameter warnings when asserts are disabled in build. */ + ( void ) packetId; + switch( pIncomingPacket->type & 0xf0 ) { case MQTT_PACKET_TYPE_SUBACK: @@ -713,6 +736,9 @@ static void mqttProcessIncomingPacket( NetworkContext_t * pNetworkContext, bool sessionPresent = false; uint16_t receiveAttempts = 0; + /* Suppress unused variable warning when asserts are disabled in build. */ + ( void ) status; + /*** * For readability, error handling in this function is restricted to the use of * asserts(). diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 2f1db3a97e..140bc0c373 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -677,6 +677,9 @@ static void eventCallback( MQTTContext_t * pMqttContext, assert( pPacketInfo != NULL ); assert( pDeserializedInfo != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pMqttContext; + packetIdentifier = pDeserializedInfo->packetIdentifier; /* Handle incoming publish. The lower 4 bits of the publish packet diff --git a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c index d238e0d580..791f08d25c 100644 --- a/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c +++ b/demos/mqtt/mqtt_demo_plaintext/mqtt_demo_plaintext.c @@ -372,6 +372,9 @@ static void eventCallback( MQTTContext_t * pMqttContext, assert( pPacketInfo != NULL ); assert( pDeserializedInfo != NULL ); + /* Suppress unused parameter warning when asserts are disabled in build. */ + ( void ) pMqttContext; + packetIdentifier = pDeserializedInfo->packetIdentifier; /* Handle incoming publish. The lower 4 bits of the publish packet From f23811cef6c07483c3efd72b2993caec6448e78c Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Thu, 20 Aug 2020 21:12:19 -0700 Subject: [PATCH 645/844] Add missing clientid check in the MQTT mutual auth demo for ci. (#1144) --- demos/mqtt/mqtt_demo_basic_tls/demo_config.h | 4 ++-- demos/mqtt/mqtt_demo_mutual_auth/demo_config.h | 4 +++- demos/mqtt/mqtt_demo_plaintext/demo_config.h | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h index 3369db9822..239b72eab2 100644 --- a/demos/mqtt/mqtt_demo_basic_tls/demo_config.h +++ b/demos/mqtt/mqtt_demo_basic_tls/demo_config.h @@ -54,8 +54,8 @@ * A Mosquitto MQTT broker can be setup locally for running this demo against * it. Please refer to the instructions in https://mosquitto.org/ for running * a Mosquitto broker locally. - * Alternatively,instructions to run Mosquitto server on Docker container can - * be viewed in the README.md of the root directory. + * Alternatively, instructions to run a Mosquitto broker on a Docker container + * can be viewed in the README.md of the root directory. * * #define BROKER_ENDPOINT "...insert here..." */ diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index f43b6718c7..a4ad3d118d 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -115,7 +115,9 @@ * * No two clients may use the same client identifier simultaneously. */ -#define CLIENT_IDENTIFIER "testclient" +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER "testclient" +#endif /** * @brief Size of the network buffer for MQTT packets. diff --git a/demos/mqtt/mqtt_demo_plaintext/demo_config.h b/demos/mqtt/mqtt_demo_plaintext/demo_config.h index 8be951cec3..994e3c4877 100644 --- a/demos/mqtt/mqtt_demo_plaintext/demo_config.h +++ b/demos/mqtt/mqtt_demo_plaintext/demo_config.h @@ -54,8 +54,8 @@ * A Mosquitto MQTT broker can be setup locally for running this demo against * it. Please refer to the instructions in https://mosquitto.org/ for running * a Mosquitto broker locally. - * Alternatively,instructions to run Mosquitto server on Docker container can - * be viewed in the README.md of the root directory. + * Alternatively, instructions to run a Mosquitto broker on a Docker container + * can be viewed in the README.md of the root directory. * * #define BROKER_ENDPOINT "...insert here..." */ From f4a3c7db816a98082936088744f24dc40d0972d7 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 21 Aug 2020 09:31:33 -0700 Subject: [PATCH 646/844] Reduce the complexity score for openssl_connect (#1141) * Reduce the complexity score for openssl_connect * Spell check fixes * Update platform/posix/transport/src/openssl_posix.c --- platform/lexicon.txt | 3 + platform/posix/transport/src/openssl_posix.c | 99 ++++++++++++-------- 2 files changed, 64 insertions(+), 38 deletions(-) diff --git a/platform/lexicon.txt b/platform/lexicon.txt index 6003b490fe..849dd96849 100644 --- a/platform/lexicon.txt +++ b/platform/lexicon.txt @@ -36,6 +36,7 @@ nano networkcontext noninfringement openssl +openssl_invalid_parameter param pbuffer pclientcertpath @@ -62,6 +63,8 @@ sendtimeout sendtimeoutms sleeptimems sni +socketstatus +sockets_invalid_parameter src ssl sublicense diff --git a/platform/posix/transport/src/openssl_posix.c b/platform/posix/transport/src/openssl_posix.c index fb64c6dc55..a7e87088b7 100644 --- a/platform/posix/transport/src/openssl_posix.c +++ b/platform/posix/transport/src/openssl_posix.c @@ -125,6 +125,16 @@ static int setCredentials( SSL_CTX * pSslContext, static void setOptionalConfigurations( SSL * pSsl, const OpensslCredentials_t * pOpensslCredentials ); +/** + * @brief Converts the sockets wrapper status to openssl status. + * + * @param[in] socketStatus Sockets wrapper status. + * + * @return #OPENSSL_SUCCESS, #OPENSSL_INVALID_PARAMETER, #OPENSSL_DNS_FAILURE, + * and #OPENSSL_CONNECT_FAILURE. + */ +static OpensslStatus_t convertToOpensslStatus( SocketStatus_t socketStatus ); + /*-----------------------------------------------------------*/ #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) @@ -161,6 +171,37 @@ static void setOptionalConfigurations( SSL * pSsl, #endif /* #if ( LIBRARY_LOG_LEVEL == LOG_DEBUG ) */ /*-----------------------------------------------------------*/ +static OpensslStatus_t convertToOpensslStatus( SocketStatus_t socketStatus ) +{ + OpensslStatus_t opensslStatus = OPENSSL_INVALID_PARAMETER; + + switch( socketStatus ) + { + case SOCKETS_SUCCESS: + opensslStatus = OPENSSL_SUCCESS; + break; + + case SOCKETS_INVALID_PARAMETER: + opensslStatus = OPENSSL_INVALID_PARAMETER; + break; + + case SOCKETS_DNS_FAILURE: + opensslStatus = OPENSSL_DNS_FAILURE; + break; + + case SOCKETS_CONNECT_FAILURE: + opensslStatus = OPENSSL_CONNECT_FAILURE; + break; + + default: + LogError( ( "Unexpected status received from socket wrapper: Socket status = %u", + socketStatus ) ); + } + + return opensslStatus; +} +/*-----------------------------------------------------------*/ + static int setRootCa( SSL_CTX * pSslContext, const char * pRootCaPath ) { @@ -429,22 +470,8 @@ OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, sendTimeoutMs, recvTimeoutMs ); - if( socketStatus == SOCKETS_INVALID_PARAMETER ) - { - returnStatus = OPENSSL_INVALID_PARAMETER; - } - else if( socketStatus == SOCKETS_DNS_FAILURE ) - { - returnStatus = OPENSSL_DNS_FAILURE; - } - else if( socketStatus == SOCKETS_CONNECT_FAILURE ) - { - returnStatus = OPENSSL_CONNECT_FAILURE; - } - else - { - /* Empty else. */ - } + /* Convert socket wrapper status to openssl status. */ + returnStatus = convertToOpensslStatus( socketStatus ); } /* Create SSL context. */ @@ -563,39 +590,35 @@ OpensslStatus_t Openssl_Connect( NetworkContext_t * pNetworkContext, OpensslStatus_t Openssl_Disconnect( NetworkContext_t * pNetworkContext ) { - OpensslStatus_t returnStatus = OPENSSL_SUCCESS; - SocketStatus_t socketStatus = SOCKETS_SUCCESS; + SocketStatus_t socketStatus = SOCKETS_INVALID_PARAMETER; if( pNetworkContext == NULL ) { + /* No need to update the status here. The socket status + * SOCKETS_INVALID_PARAMETER will be converted to openssl + * status OPENSSL_INVALID_PARAMETER before returning from this + * function. */ LogError( ( "Parameter check failed: pNetworkContext is NULL." ) ); - returnStatus = OPENSSL_INVALID_PARAMETER; - } - else if( pNetworkContext->pSsl != NULL ) - { - /* SSL shutdown should be called twice: once to send "close notify" and - * once more to receive the peer's "close notify". */ - if( SSL_shutdown( pNetworkContext->pSsl ) == 0 ) - { - ( void ) SSL_shutdown( pNetworkContext->pSsl ); - } - - SSL_free( pNetworkContext->pSsl ); } else { - /* Empty else. */ - } + if( pNetworkContext->pSsl != NULL ) + { + /* SSL shutdown should be called twice: once to send "close notify" and + * once more to receive the peer's "close notify". */ + if( SSL_shutdown( pNetworkContext->pSsl ) == 0 ) + { + ( void ) SSL_shutdown( pNetworkContext->pSsl ); + } - /* Tear down the socket connection. */ - socketStatus = Sockets_Disconnect( pNetworkContext->socketDescriptor ); + SSL_free( pNetworkContext->pSsl ); + } - if( socketStatus == SOCKETS_INVALID_PARAMETER ) - { - returnStatus = OPENSSL_INVALID_PARAMETER; + /* Tear down the socket connection, pNetworkContext != NULL here. */ + socketStatus = Sockets_Disconnect( pNetworkContext->socketDescriptor ); } - return returnStatus; + return convertToOpensslStatus( socketStatus ); } /*-----------------------------------------------------------*/ From e7c5e7c8df87a49a16bc01392a72c839adf98f45 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 21 Aug 2020 15:25:07 -0700 Subject: [PATCH 647/844] MQTT Mutual auth demo: Username password based client authentication (#1130) --- demos/lexicon.txt | 2 + .../mqtt/mqtt_demo_mutual_auth/demo_config.h | 34 ++++ .../mqtt_demo_mutual_auth.c | 151 ++++++++++++++---- 3 files changed, 157 insertions(+), 30 deletions(-) diff --git a/demos/lexicon.txt b/demos/lexicon.txt index 912c4f4a15..a6f552011e 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -39,6 +39,7 @@ http httpbin httpclient https +ifdef ifndef inc initializerequestheaders @@ -100,6 +101,7 @@ readme resending resp sdk +sni ssl structs suback diff --git a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h index a4ad3d118d..d634a69988 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h +++ b/demos/mqtt/mqtt_demo_mutual_auth/demo_config.h @@ -110,6 +110,40 @@ * #define CLIENT_PRIVATE_KEY_PATH "...insert here..." */ +/** + * @brief The username value for authenticating client to MQTT broker when + * username/password based client authentication is used. + * + * Refer to the AWS IoT documentation below for details regarding client + * authentication with a username and password. + * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html + * As mentioned in the link above, an authorizer setup needs to be done to use + * username/password based client authentication. + * + * @note AWS IoT message broker requires either a set of client certificate/private key + * or username/password to authenticate the client. If this config is defined, + * the username and password will be used instead of the client certificate and + * private key for client authentication. + * + * #define CLIENT_USERNAME "...insert here..." + */ + +/** + * @brief The password value for authenticating client to MQTT broker when + * username/password based client authentication is used. + * + * Refer to the AWS IoT documentation below for details regarding client + * authentication with a username and password. + * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html + * As mentioned in the link above, an authorizer setup needs to be done to use + * username/password based client authentication. + * + * @note AWS IoT message broker requires either a set of client certificate/private key + * or username/password to authenticate the client. + * + * #define CLIENT_PASSWORD "...insert here..." + */ + /** * @brief MQTT client identifier. * diff --git a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c index 140bc0c373..113d98d12b 100644 --- a/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c +++ b/demos/mqtt/mqtt_demo_mutual_auth/mqtt_demo_mutual_auth.c @@ -25,9 +25,20 @@ * unsubscribe from a topic and disconnect the MQTT session. * * A mutually authenticated TLS connection is used to connect to the AWS IoT - * MQTT message broker in this example. Define ROOT_CA_CERT_PATH, - * CLIENT_CERT_PATH, and CLIENT_PRIVATE_KEY_PATH in demo_config.h to achieve - * mutual authentication. + * MQTT message broker in this example. Define ROOT_CA_CERT_PATH for server + * authentication in the client. Client authentication can be achieved in either + * of the 2 different ways mentioned below. + * 1. Define CLIENT_CERT_PATH and CLIENT_PRIVATE_KEY_PATH in demo_config.h + * for client authentication to be done based on the client certificate + * and client private key. More details about this client authentication + * can be found in the link below. + * https://docs.aws.amazon.com/iot/latest/developerguide/client-authentication.html + * 2. Define CLIENT_USERNAME and CLIENT_PASSWORD in demo_config.h for client + * authentication to be done using a username and password. More details about + * this client authentication can be found in the link below. + * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html + * An authorizer setup needs to be done, as mentioned in the above link, to use + * username/password based client authentication. * * The example is single threaded and uses statically allocated memory; * it uses QOS1 and therefore implements a retransmission mechanism @@ -72,16 +83,34 @@ #ifndef ROOT_CA_CERT_PATH #error "Please define path to Root CA certificate of the MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." #endif -#ifndef CLIENT_CERT_PATH - #error "Please define path to client certificate(CLIENT_CERT_PATH) in demo_config.h." -#endif -#ifndef CLIENT_PRIVATE_KEY_PATH - #error "Please define path to client private key(CLIENT_PRIVATE_KEY_PATH) in demo_config.h." -#endif #ifndef CLIENT_IDENTIFIER - #error "Please define a unique CLIENT_IDENTIFIER." + #error "Please define a unique client identifier, CLIENT_IDENTIFIER, in demo_config.h." #endif +/* The AWS IoT message broker requires either a set of client certificate/private key + * or username/password to authenticate the client. */ +#ifndef CLIENT_USERNAME + #ifndef CLIENT_CERT_PATH + #error "Please define path to client certificate(CLIENT_CERT_PATH) in demo_config.h." + #endif + #ifndef CLIENT_PRIVATE_KEY_PATH + #error "Please define path to client private key(CLIENT_PRIVATE_KEY_PATH) in demo_config.h." + #endif +#else + +/* If a username is defined, a client password also would need to be defined for + * client authentication. */ + #ifndef CLIENT_PASSWORD + #error "Please define client password(CLIENT_PASSWORD) in demo_config.h for client authentication based on username/password." + #endif + +/* AWS IoT MQTT broker port needs to be 443 for client authentication based on + * username/password. */ + #if AWS_MQTT_PORT != 443 + #error "Broker port, AWS_MQTT_PORT, should be defined as 443 in demo_config.h for client authentication based on username/password." + #endif +#endif /* ifndef CLIENT_USERNAME */ + /** * Provide default values for undefined configuration settings. */ @@ -116,19 +145,30 @@ #define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /** - * @brief ALPN protocol name for AWS IoT MQTT. + * @brief ALPN (Application-Layer Protocol Negotiation) protocol name for AWS IoT MQTT. * * This will be used if the AWS_MQTT_PORT is configured as 443 for AWS IoT MQTT broker. * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint * in the link below. * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ */ -#define ALPN_PROTOCOL_NAME "\x0ex-amzn-mqtt-ca" +#define AWS_IOT_MQTT_ALPN "\x0ex-amzn-mqtt-ca" /** * @brief Length of ALPN protocol name. */ -#define ALPN_PROTOCOL_NAME_LENGTH ( ( uint16_t ) ( sizeof( ALPN_PROTOCOL_NAME ) - 1 ) ) +#define AWS_IOT_MQTT_ALPN_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_MQTT_ALPN ) - 1 ) ) + +/** + * @brief This is the ALPN (Application-Layer Protocol Negotiation) string + * required by AWS IoT for password-based authentication using TCP port 443. + */ +#define AWS_IOT_PASSWORD_ALPN "\x04mqtt" + +/** + * @brief Length of password ALPN. + */ +#define AWS_IOT_PASSWORD_ALPN_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_PASSWORD_ALPN ) - 1 ) ) /** * @brief Timeout for receiving CONNACK packet in milli seconds. @@ -216,6 +256,18 @@ */ #define METRICS_STRING_LENGTH ( ( uint16_t ) ( sizeof( METRICS_STRING ) - 1 ) ) + +#ifdef CLIENT_USERNAME + +/** + * @brief Append the username with the metrics string if #CLIENT_USERNAME is defined. + * + * This is to support both metrics reporting and username/password based client + * authentication by AWS IoT. + */ + #define CLIENT_USERNAME_WITH_METRICS CLIENT_USERNAME METRICS_STRING +#endif + /*-----------------------------------------------------------*/ /** @@ -434,18 +486,40 @@ static int connectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext /* Initialize credentials for establishing TLS session. */ memset( &opensslCredentials, 0, sizeof( OpensslCredentials_t ) ); opensslCredentials.pRootCaPath = ROOT_CA_CERT_PATH; - opensslCredentials.pClientCertPath = CLIENT_CERT_PATH; - opensslCredentials.pPrivateKeyPath = CLIENT_PRIVATE_KEY_PATH; + + /* If #CLIENT_USERNAME is defined, username/password is used for authenticating + * the client. */ + #ifndef CLIENT_USERNAME + opensslCredentials.pClientCertPath = CLIENT_CERT_PATH; + opensslCredentials.pPrivateKeyPath = CLIENT_PRIVATE_KEY_PATH; + #endif + + /* AWS IoT requires devices to send the Server Name Indication (SNI) + * extension to the Transport Layer Security (TLS) protocol and provide + * the complete endpoint address in the host_name field. Details about + * SNI for AWS IoT can be found in the link below. + * https://docs.aws.amazon.com/iot/latest/developerguide/transport-security.html */ + opensslCredentials.sniHostName = AWS_IOT_ENDPOINT; if( AWS_MQTT_PORT == 443 ) { /* Pass the ALPN protocol name depending on the port being used. - * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint - * in the link below. + * Please see more details about the ALPN protocol for the AWS IoT MQTT + * endpoint in the link below. * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ + * + * For username and password based authentication in AWS IoT, + * #AWS_IOT_PASSWORD_ALPN is used. More details can be found in the + * link below. + * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-auth-using.html */ - opensslCredentials.pAlpnProtos = ALPN_PROTOCOL_NAME; - opensslCredentials.alpnProtosLen = ALPN_PROTOCOL_NAME_LENGTH; + #ifdef CLIENT_USERNAME + opensslCredentials.pAlpnProtos = AWS_IOT_PASSWORD_ALPN; + opensslCredentials.alpnProtosLen = AWS_IOT_PASSWORD_ALPN_LENGTH; + #else + opensslCredentials.pAlpnProtos = AWS_IOT_MQTT_ALPN; + opensslCredentials.alpnProtosLen = AWS_IOT_MQTT_ALPN_LENGTH; + #endif } /* Initialize reconnect attempts and interval */ @@ -744,9 +818,9 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, { int returnStatus = EXIT_SUCCESS; MQTTStatus_t mqttStatus; - MQTTConnectInfo_t connectInfo; - MQTTFixedBuffer_t networkBuffer; - TransportInterface_t transport; + MQTTConnectInfo_t connectInfo = { 0 }; + MQTTFixedBuffer_t networkBuffer = { 0 }; + TransportInterface_t transport = { 0 }; assert( pMqttContext != NULL ); assert( pNetworkContext != NULL ); @@ -798,16 +872,33 @@ static int establishMqttSession( MQTTContext_t * pMqttContext, * PINGREQ Packet. */ connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; - /* The username field is populated with voluntary metrics to AWS IoT. + /* Use the username and password for authentication, if they are defined. + * Refer to the AWS IoT documentation below for details regarding client + * authentication with a username and password. + * https://docs.aws.amazon.com/iot/latest/developerguide/enhanced-custom-authentication.html + * An authorizer setup needs to be done, as mentioned in the above link, to use + * username/password based client authentication. + * + * The username field is populated with voluntary metrics to AWS IoT. * The metrics collected by AWS IoT are the current operating system or * SDK and its version. These metrics help AWS IoT improve security and - * provide better technical support. */ - connectInfo.pUserName = METRICS_STRING; - connectInfo.userNameLength = METRICS_STRING_LENGTH; - - /* Password for authentication is not used in this demo. */ - connectInfo.pPassword = NULL; - connectInfo.passwordLength = 0U; + * provide better technical support. + * + * If client authentication is based on username/password in AWS IoT, + * the metrics string is appended to the username to support both client + * authentication and metrics collection. */ + #ifdef CLIENT_USERNAME + connectInfo.pUserName = CLIENT_USERNAME_WITH_METRICS; + connectInfo.userNameLength = strlen( CLIENT_USERNAME_WITH_METRICS ); + connectInfo.pPassword = CLIENT_PASSWORD; + connectInfo.passwordLength = strlen( CLIENT_PASSWORD ); + #else + connectInfo.pUserName = METRICS_STRING; + connectInfo.userNameLength = METRICS_STRING_LENGTH; + /* Password for authentication is not used. */ + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + #endif /* ifdef CLIENT_USERNAME */ /* Send MQTT CONNECT packet to broker. */ mqttStatus = MQTT_Connect( pMqttContext, &connectInfo, NULL, CONNACK_RECV_TIMEOUT_MS, pSessionPresent ); From 4fc228de7c026b006790cfb30a65d76fba722eeb Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 21 Aug 2020 15:32:11 -0700 Subject: [PATCH 648/844] Update proof for HTTPClient_ReadHeader to pass double pointer (#1142) * Update HTTPClient_ReadHeader proof to contain double pointer * Update bodyOffset like headerOffset --- .../HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c | 4 ++-- libraries/standard/http/cbmc/sources/http_cbmc_state.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c index a5a58717ba..5e1304af57 100644 --- a/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c +++ b/libraries/standard/http/cbmc/proofs/HTTPClient_ReadHeader/HTTPClient_ReadHeader_harness.c @@ -32,7 +32,7 @@ void harness() { HTTPResponse_t * pResponse; char * pField; - char * pValue; + char ** pValue; size_t fieldLen; size_t valueLen; @@ -42,7 +42,7 @@ void harness() /* Initialize and make assumptions for header value. */ __CPROVER_assume( valueLen < CBMC_MAX_OBJECT_SIZE ); - pValue = mallocCanFail( valueLen ); + pValue = mallocCanFail( sizeof( char * ) ); /* Initialize and make assumptions for response object. */ pResponse = allocateHttpResponse( NULL ); diff --git a/libraries/standard/http/cbmc/sources/http_cbmc_state.c b/libraries/standard/http/cbmc/sources/http_cbmc_state.c index a7ae6565e8..489a63d87a 100644 --- a/libraries/standard/http/cbmc/sources/http_cbmc_state.c +++ b/libraries/standard/http/cbmc/sources/http_cbmc_state.c @@ -90,7 +90,7 @@ HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ) pResponse->pBuffer = mallocCanFail( pResponse->bufferLen ); - __CPROVER_assume( headerOffset < pResponse->headersLen ); + __CPROVER_assume( headerOffset <= pResponse->headersLen ); pResponse->pHeaders = nondet_bool() ? NULL : pResponse->pBuffer + headerOffset; @@ -101,7 +101,7 @@ HTTPResponse_t * allocateHttpResponse( HTTPResponse_t * pResponse ) else { __CPROVER_assume( pResponse->headersLen < bodyOffset && - bodyOffset < pResponse->bufferLen ); + bodyOffset <= pResponse->bufferLen ); } pResponse->pBody = nondet_bool() ? NULL : From c58ab3c4391745e1a415b801158997d2144c41e7 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 21 Aug 2020 16:10:20 -0700 Subject: [PATCH 649/844] Reduce GNU complexity score of posix sockets wrapper (#1138) --- platform/lexicon.txt | 2 + platform/posix/transport/src/sockets_posix.c | 137 +++++++++++-------- 2 files changed, 83 insertions(+), 56 deletions(-) diff --git a/platform/lexicon.txt b/platform/lexicon.txt index 849dd96849..9013f74109 100644 --- a/platform/lexicon.txt +++ b/platform/lexicon.txt @@ -24,6 +24,7 @@ hostnamelength https ifndef implemenation +ip inc iot logpath @@ -37,6 +38,7 @@ networkcontext noninfringement openssl openssl_invalid_parameter +paddrinfo param pbuffer pclientcertpath diff --git a/platform/posix/transport/src/sockets_posix.c b/platform/posix/transport/src/sockets_posix.c index 018c4b26de..75fa21fa96 100644 --- a/platform/posix/transport/src/sockets_posix.c +++ b/platform/posix/transport/src/sockets_posix.c @@ -98,6 +98,19 @@ static SocketStatus_t attemptConnection( struct addrinfo * pListHead, int * pTcpSocket, int32_t maxAttempts ); +/** + * @brief Connect to server using the provided address record. + * + * @param[in, out] pAddrInfo Address record of the server. + * @param[in] port Server port in host-order. + * @param[in] pTcpSocket Socket handle. + * + * @return #SOCKETS_SUCCESS if successful; #SOCKETS_CONNECT_FAILURE on error. + */ +static SocketStatus_t connectToAddress( struct sockaddr * pAddrInfo, + uint16_t port, + int tcpSocket ); + /** * @brief Log possible error using errno and return appropriate status. * @@ -143,6 +156,64 @@ static SocketStatus_t resolveHostName( const char * pHostName, } /*-----------------------------------------------------------*/ +static SocketStatus_t connectToAddress( struct sockaddr * pAddrInfo, + uint16_t port, + int tcpSocket ) +{ + SocketStatus_t returnStatus = SOCKETS_SUCCESS; + int connectStatus = 0; + char resolvedIpAddr[ INET6_ADDRSTRLEN ]; + socklen_t addrInfoLength; + uint16_t netPort = 0; + + assert( pAddrInfo != NULL ); + assert( pAddrInfo->sa_family == AF_INET || pAddrInfo->sa_family == AF_INET6 ); + assert( tcpSocket >= 0 ); + + + /* Convert port from host byte order to network byte order. */ + netPort = htons( port ); + + if( pAddrInfo->sa_family == AF_INET ) + { + /* Store IPv4 in string to log. */ + ( ( struct sockaddr_in * ) pAddrInfo )->sin_port = netPort; + addrInfoLength = sizeof( struct sockaddr_in ); + ( void ) inet_ntop( pAddrInfo->sa_family, + &( ( struct sockaddr_in * ) pAddrInfo )->sin_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + else + { + /* Store IPv6 in string to log. */ + ( ( struct sockaddr_in6 * ) pAddrInfo )->sin6_port = netPort; + addrInfoLength = sizeof( struct sockaddr_in6 ); + ( void ) inet_ntop( pAddrInfo->sa_family, + &( ( struct sockaddr_in6 * ) pAddrInfo )->sin6_addr, + resolvedIpAddr, + sizeof( resolvedIpAddr ) ); + } + + LogDebug( ( "Attempting to connect to server using the resolved IP address:" + " IP address=%s.", + resolvedIpAddr ) ); + + /* Attempt to connect. */ + connectStatus = connect( tcpSocket, pAddrInfo, addrInfoLength ); + + if( connectStatus == -1 ) + { + LogWarn( ( "Failed to connect to server using the resolved IP address: IP address=%s.", + resolvedIpAddr ) ); + close( tcpSocket ); + returnStatus = SOCKETS_CONNECT_FAILURE; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + static SocketStatus_t attemptConnection( struct addrinfo * pListHead, const char * pHostName, size_t hostNameLength, @@ -150,22 +221,16 @@ static SocketStatus_t attemptConnection( struct addrinfo * pListHead, int * pTcpSocket, int32_t maxAttempts ) { - SocketStatus_t returnStatus = SOCKETS_SUCCESS; + SocketStatus_t returnStatus = SOCKETS_CONNECT_FAILURE; struct addrinfo * pIndex = NULL; - struct sockaddr * pAddrInfo; - socklen_t addrInfoLength; - uint16_t netPort = 0; - int curAttempts = 0, connectStatus = 0; - char resolvedIpAddr[ INET6_ADDRSTRLEN ]; + int curAttempts = 0; assert( pListHead != NULL ); assert( pHostName != NULL ); assert( hostNameLength > 0 ); assert( pTcpSocket != NULL ); - netPort = htons( port ); - - LogDebug( ( "Performing DNS lookup: Host=%.*s.", + LogDebug( ( "Attempting to connect to: Host=%.*s.", ( int32_t ) hostNameLength, pHostName ) ); @@ -181,48 +246,12 @@ static SocketStatus_t attemptConnection( struct addrinfo * pListHead, continue; } - pAddrInfo = pIndex->ai_addr; - - if( pAddrInfo->sa_family == AF_INET ) - { - /* Store IPv4 in string to log. */ - ( ( struct sockaddr_in * ) pAddrInfo )->sin_port = netPort; - addrInfoLength = sizeof( struct sockaddr_in ); - ( void ) inet_ntop( pAddrInfo->sa_family, - &( ( struct sockaddr_in * ) pAddrInfo )->sin_addr, - resolvedIpAddr, - sizeof( resolvedIpAddr ) ); - } - else - { - /* Store IPv6 in string to log. */ - ( ( struct sockaddr_in6 * ) pAddrInfo )->sin6_port = netPort; - addrInfoLength = sizeof( struct sockaddr_in6 ); - ( void ) inet_ntop( pAddrInfo->sa_family, - &( ( struct sockaddr_in6 * ) pAddrInfo )->sin6_addr, - resolvedIpAddr, - sizeof( resolvedIpAddr ) ); - } - - LogDebug( ( "Attempting to connect to server: Host=%.*s, IP address=%s.", - ( int32_t ) hostNameLength, - pHostName, - resolvedIpAddr ) ); - - connectStatus = connect( *pTcpSocket, pAddrInfo, addrInfoLength ); + /* Attempt to connect to a resolved DNS address of the host. */ + returnStatus = connectToAddress( pIndex->ai_addr, port, *pTcpSocket ); - if( connectStatus == -1 ) - { - LogWarn( ( "Failed to connect to server: Host=%.*s, IP address=%s.", - ( int32_t ) hostNameLength, - pHostName, - resolvedIpAddr ) ); - close( *pTcpSocket ); - } - else + /* If connected to an IP address successfully, exit from the loop. */ + if( returnStatus == SOCKETS_SUCCESS ) { - LogDebug( ( "Connected to IP address: %s.", - resolvedIpAddr ) ); break; } @@ -236,25 +265,21 @@ static SocketStatus_t attemptConnection( struct addrinfo * pListHead, ( int32_t ) hostNameLength, pHostName, curAttempts ) ); - returnStatus = SOCKETS_CONNECT_FAILURE; break; } } - if( pIndex == NULL ) + if( returnStatus == SOCKETS_SUCCESS ) { - /* Fail if no connection could be established. */ - LogError( ( "Could not connect to any resolved IP address from %.*s.", + LogDebug( ( "Established TCP connection: Server=%.*s.\n", ( int32_t ) hostNameLength, pHostName ) ); - returnStatus = SOCKETS_CONNECT_FAILURE; } else { - LogDebug( ( "Established TCP connection: Server=%.*s.\n", + LogError( ( "Could not connect to any resolved IP address from %.*s.", ( int32_t ) hostNameLength, pHostName ) ); - returnStatus = SOCKETS_SUCCESS; } freeaddrinfo( pListHead ); From ce7bc523b67d61cca8113135a2086c73ca132dd2 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 21 Aug 2020 16:31:56 -0700 Subject: [PATCH 650/844] Change state status code and increase coverage (#1140) * Change a status code in MQTT_UpdateStateAck to MQTTBadParameter when a record is not found * Add some unit tests to increase code coverage --- libraries/standard/mqtt/include/mqtt_state.h | 5 ++- libraries/standard/mqtt/src/mqtt.c | 2 + libraries/standard/mqtt/src/mqtt_state.c | 39 ++++++++++++------- .../mqtt/utest/mqtt_lightweight_utest.c | 19 +++++++++ .../standard/mqtt/utest/mqtt_state_utest.c | 6 +-- libraries/standard/mqtt/utest/mqtt_utest.c | 10 +++++ 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index af5e261690..d2c4efb9cf 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -135,7 +135,10 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, * @param[in] opType Send or Receive. * @param[out] pNewState Updated state of the publish. * - * @return #MQTTBadParameter, #MQTTIllegalState, or #MQTTSuccess. + * @return #MQTTBadParameter if an invalid parameter is passed; + * #MQTTBadResponse if the packet from the network is not found in the records; + * #MQTTIllegalState if the requested update would result in an illegal transition; + * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index e3d8d55212..8cf2c8d259 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -1215,6 +1215,8 @@ static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext, appCallback = pContext->appCallback; + LogDebug( ( "Received packet of type %02x.", pIncomingPacket->type ) ); + switch( pIncomingPacket->type ) { case MQTT_PACKET_TYPE_PUBACK: diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index fad14e3b56..21a6117c80 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -424,22 +424,17 @@ static size_t findInRecord( const MQTTPubAckInfo_t * records, { size_t index = 0; + assert( packetId != MQTT_PACKET_ID_INVALID ); + *pCurrentState = MQTTStateNull; - if( packetId == MQTT_PACKET_ID_INVALID ) - { - index = recordCount; - } - else + for( index = 0; index < recordCount; index++ ) { - for( index = 0; index < recordCount; index++ ) + if( records[ index ].packetId == packetId ) { - if( records[ index ].packetId == packetId ) - { - *pQos = records[ index ].qos; - *pCurrentState = records[ index ].publishState; - break; - } + *pQos = records[ index ].qos; + *pCurrentState = records[ index ].publishState; + break; } } @@ -964,7 +959,10 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, * @param[in] opType Send or Receive. * @param[out] pNewState Updated state of the publish. * - * @return #MQTTBadParameter, #MQTTIllegalState, or #MQTTSuccess. + * @return #MQTTBadParameter if an invalid parameter is passed; + * #MQTTBadResponse if the packet from the network is not found in the records; + * #MQTTIllegalState if the requested update would result in an illegal transition; + * #MQTTSuccess otherwise. */ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, @@ -978,13 +976,24 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, MQTTQoS_t qos = MQTTQoS0; size_t recordIndex = MQTT_STATE_ARRAY_MAX_COUNT; MQTTPubAckInfo_t * records = NULL; - MQTTStatus_t status = MQTTBadParameter; + MQTTStatus_t status = MQTTBadResponse; if( ( pMqttContext == NULL ) || ( pNewState == NULL ) ) { LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p.", ( void * ) pMqttContext, ( void * ) pNewState ) ); + status = MQTTBadParameter; + } + else if( packetId == MQTT_PACKET_ID_INVALID ) + { + LogError( ( "Packet ID must be nonzero." ) ); + status = MQTTBadParameter; + } + else if( packetType > MQTTPubcomp ) + { + LogError( ( "Invalid packet type %u.", packetType ) ); + status = MQTTBadParameter; } else { @@ -1019,7 +1028,7 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, } else { - LogError( ( "No matching record found for publish %u.", packetId ) ); + LogError( ( "No matching record found for publish: PacketId=%u.", packetId ) ); } return status; diff --git a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c index ab231abf9b..30e00da5fd 100644 --- a/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_lightweight_utest.c @@ -709,6 +709,8 @@ void test_MQTT_GetUnsubscribePacketSize( void ) size_t remainingLength = 0; size_t packetSize = 0; MQTTStatus_t status = MQTTSuccess; + MQTTSubscribeInfo_t fourThousandSubscriptions[ 4096 ] = { 0 }; + int i; /* Verify parameters. */ @@ -740,6 +742,23 @@ void test_MQTT_GetUnsubscribePacketSize( void ) &packetSize ); TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify packet size cannot exceed limit. Note the max remaining length of + * an MQTT packet is 2^28-1 = 268435455, or 256MiB. Since the only way to increase + * the subscribe packet size is with the topic filters of the subscriptions + * (the lengths of which are only 2 bytes), we need at least + * 2^28 / 2^16 = 2^12 = 4096 of them. */ + for( i = 0; i < 4096; i++ ) + { + fourThousandSubscriptions[ i ].topicFilterLength = UINT16_MAX; + } + + subscriptionCount = sizeof( fourThousandSubscriptions ) / sizeof( fourThousandSubscriptions[ 0 ] ); + status = MQTT_GetUnsubscribePacketSize( fourThousandSubscriptions, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status ); + /* Verify good case. */ memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); subscriptionList.qos = MQTTQoS0; diff --git a/libraries/standard/mqtt/utest/mqtt_state_utest.c b/libraries/standard/mqtt/utest/mqtt_state_utest.c index 882efc8548..29ba0bcf71 100644 --- a/libraries/standard/mqtt/utest/mqtt_state_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_state_utest.c @@ -487,12 +487,12 @@ void test_MQTT_UpdateStateAck( void ) TEST_ASSERT_EQUAL( MQTTBadParameter, status ); status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, NULL ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); - /* No matching record found. */ - status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); - TEST_ASSERT_EQUAL( MQTTBadParameter, status ); /* Invalid packet ID. */ status = MQTT_UpdateStateAck( &mqttContext, 0, ack, operation, &state ); TEST_ASSERT_EQUAL( MQTTBadParameter, status ); + /* No matching record found. */ + status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state ); + TEST_ASSERT_EQUAL( MQTTBadResponse, status ); /* Invalid transitions. */ /* Invalid transition from #MQTTPubRelPending. */ diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 25aaa2d010..1b827f6a40 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2209,6 +2209,16 @@ void test_MQTT_MatchTopic_ExactMatch( void ) MQTT_SAMPLE_TOPIC_FILTER_LENGTH, &matchResult ) ); TEST_ASSERT_EQUAL( false, matchResult ); + + /* Test for match at end with no wildcards. */ + pTopicName = "/test/match/"; + pTopicFilter = "/test/match/a"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); } /** From 3c18f315cfd9ac912b59d66588deb4e462a3652e Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 24 Aug 2020 11:56:25 -0700 Subject: [PATCH 651/844] Add #error statements for CLIENT_CERT_PATH and CLIENT_PRIVATE_KEY_PATH macros (#1155) --- .../standard/mqtt/integration-test/mqtt_system_test.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/standard/mqtt/integration-test/mqtt_system_test.c b/libraries/standard/mqtt/integration-test/mqtt_system_test.c index e441628130..0a3c34cfaf 100644 --- a/libraries/standard/mqtt/integration-test/mqtt_system_test.c +++ b/libraries/standard/mqtt/integration-test/mqtt_system_test.c @@ -43,6 +43,7 @@ /* Include clock for timer. */ #include "clock.h" +/* Ensure that config macros, required for TLS connection, have been defined. */ #ifndef BROKER_ENDPOINT #error "BROKER_ENDPOINT should be defined for the MQTT integration tests." #endif @@ -51,6 +52,14 @@ #error "SERVER_ROOT_CA_CERT_PATH should be defined for the MQTT integration tests." #endif +#ifndef CLIENT_CERT_PATH + #error "CLIENT_CERT_PATH should be defined for the MQTT integration tests." +#endif + +#ifndef CLIENT_PRIVATE_KEY_PATH + #error "CLIENT_PRIVATE_KEY_PATH should be defined for the MQTT integration tests." +#endif + /** * @brief Length of MQTT server host name. */ From d444ac6c71724ac4f15b4ed91907b1f758602bb9 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Mon, 24 Aug 2020 12:57:34 -0700 Subject: [PATCH 652/844] Add code examples for new and state API functions (#1146) * Add code examples for MQTT_PublishToResend, MQTT_MatchTopic, and MQTT_GetSubAckStatusCodes Co-authored-by: Gary Wicker <14828980+gkwicker@users.noreply.github.com> --- libraries/standard/mqtt/include/mqtt.h | 78 ++++++++++++++++++++ libraries/standard/mqtt/include/mqtt_state.h | 55 ++++++++++++++ libraries/standard/mqtt/lexicon.txt | 8 ++ 3 files changed, 141 insertions(+) diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index b2be8ce4cd..a5d9eaa0a7 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -681,6 +681,27 @@ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); * @return Returns one of the following: * - #MQTTBadParameter, if any of the input parameters is invalid. * - #MQTTSuccess, if the matching operation was performed. + * + * Example + * @code{c} + * + * // Variables used in this example. + * const char * pTopic = "topic/match/1"; + * const char * pFilter = "topic/#"; + * MQTTStatus_t status = MQTTSuccess; + * bool match = false; + * + * status = MQTT_MatchTopic( pTopic, strlen( pTopic ), pFilter, strlen( pFilter ), &match ); + * // Our parameters were valid, so this will return success. + * assert( status == MQTTSuccess ); + * + * // For this specific example, we already know this value is true. This + * // check is placed here as an example for use with variable topic names. + * if( match ) + * { + * // Application can decide what to do with the matching topic name. + * } + * @endcode */ MQTTStatus_t MQTT_MatchTopic( const char * pTopicName, const uint16_t topicNameLength, @@ -712,6 +733,63 @@ MQTTStatus_t MQTT_MatchTopic( const char * pTopicName, * @return Returns one of the following: * - #MQTTBadParameter if the input SUBACK packet is invalid. * - #MQTTSuccess if parsing the payload was successful. + * + * Example + * @code{c} + * + * // Global variable used in this example. + * // This is assumed to be the subscription list in the original SUBSCRIBE packet. + * MQTTSubscribeInfo_t pSubscribes[ NUMBER_OF_SUBSCRIPTIONS ]; + * + * // MQTT_GetSubAckStatusCodes is intended to be used from the application + * // callback that is called by the library in MQTT_ProcessLoop or MQTT_ReceiveLoop. + * void eventCallback( + * MQTTContext_t * pContext, + * MQTTPacketInfo_t * pPacketInfo, + * MQTTDeserializedInfo_t * pDeserializedInfo + * ) + * { + * MQTTStatus_t status = MQTTSuccess; + * uint8_t * pCodes; + * size_t numCodes; + * + * if( pPacketInfo->type == MQTT_PACKET_TYPE_SUBACK ) + * { + * status = MQTT_GetSubAckStatusCodes( pPacketInfo, &pCodes, &numCodes ); + * + * // Since the pointers to the payload and payload size are not NULL, and + * // we use the packet info struct passed to the app callback (verified + * // to be valid by the library), this function must return success. + * assert( status == MQTTSuccess ); + * // The server must send a response code for each topic filter in the + * // original SUBSCRIBE packet. + * assert( numCodes == NUMBER_OF_SUBSCRIPTIONS ); + * + * for( int i = 0; i < numCodes; i++ ) + * { + * // The only failure code is 0x80 = MQTTSubAckFailure. + * if( pCodes[ i ] == MQTTSubAckFailure ) + * { + * // The subscription failed, we may want to retry the + * // subscription in pSubscribes[ i ] outside of this callback. + * } + * else + * { + * // The subscription was granted, but the maximum QoS may be + * // lower than what was requested. We can verify the granted QoS. + * if( pSubscribes[ i ].qos != pCodes[ i ] ) + * { + * LogWarn( ( + * "Requested QoS %u, but granted QoS %u for %s", + * pSubscribes[ i ].qos, pCodes[ i ], pSubscribes[ i ].pTopicFilter + * ) ); + * } + * } + * } + * } + * // Handle other packet types. + * } + * @endcode */ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, uint8_t ** pPayloadStart, diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index d2c4efb9cf..3225f7bdf9 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -177,6 +177,61 @@ uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, * * @param[in] pMqttContext Initialized MQTT context. * @param[in,out] pCursor Index at which to start searching. + * + * Example + * @code{c} + * + * // For this example assume this function returns an outgoing unacknowledged + * // QoS 1 or 2 publish from its packet identifier. + * MQTTPublishInfo_t * getPublish( uint16_t packetID ); + * + * // Variables used in this example. + * MQTTStatus_t status; + * MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; + * bool sessionPresent; + * uint16_t packetID; + * MQTTPublishInfo_t * pResendPublish = NULL; + * MQTTConnectInfo_t connectInfo = { 0 }; + * + * // This is assumed to have been initialized before the call to MQTT_Connect(). + * MQTTContext_t * pContext; + * + * // Set clean session to false to attempt session resumption. + * connectInfo.cleanSession = false; + * connectInfo.pClientIdentifier = "someClientID"; + * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier ); + * connectInfo.keepAliveSeconds = 60; + * // Optional connect parameters are not relevant to this example. + * + * // Create an MQTT connection. Use 100 milliseconds as a timeout. + * status = MQTT_Connect( pContext, &connectInfo, NULL, 100, &sessionPresent ); + * + * if( status == MQTTSuccess ) + * { + * if( sessionPresent ) + * { + * // Loop while packet ID is nonzero. + * while( ( packetID = MQTT_PublishToResend( pContext, &cursor ) ) != 0 ) + * { + * // Assume this function will succeed. + * pResendPublish = getPublish( packetID ); + * // Set DUP flag. + * pResendPublish->dup = true; + * status = MQTT_Publish( pContext, pResendPublish, packetID ); + * + * if( status != MQTTSuccess ) + * { + * // Application can decide how to handle a failure. + * } + * } + * } + * else + * { + * // The broker did not resume a session, so we can clean up the + * // list of outgoing publishes. + * } + * } + * @endcode */ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor ); diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index ead44efd00..2a9e8870a7 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -67,6 +67,7 @@ getincomingpackettypeandlength getnewpacketid getpacketid getpingreqpacketsize +getpublish getpublishpacketsize getsubscribepacketsize gettime @@ -97,6 +98,7 @@ keepalivems keepaliveseconds lastpackettime linux +logwarn lsb lwt malloc @@ -162,6 +164,7 @@ networksend newstate nextpacketid noninfringement +numcodes openssl optype org @@ -177,6 +180,7 @@ pbuffer pbuffertosend pcallbacks pclientidentifier +pcodes pconnack pconnectinfo pcontext @@ -186,6 +190,7 @@ pdeserialized pdeserializedinfo pdestination pem +pfilter pfilterindex pfixedbuffer pheadersize @@ -219,6 +224,7 @@ pqos pre premainingdata premaininglength +presendpublish processloop psessionpresent psource @@ -227,7 +233,9 @@ pstatusstart psuback psubackpacket psubscribeinfo +psubscribes psubscriptionlist +ptopic ptopicfilter ptopicname ptr From e148578462c74ede21525e8e809ade182c94b5e4 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Mon, 24 Aug 2020 13:33:45 -0700 Subject: [PATCH 653/844] Clear old records for clean sessions (#1148) * Clear state records when a CONNACK's session present flag is false Co-authored-by: Gary Wicker <14828980+gkwicker@users.noreply.github.com> --- libraries/standard/mqtt/src/mqtt.c | 47 +++++++++++++++------- libraries/standard/mqtt/utest/mqtt_utest.c | 10 +++++ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index 8cf2c8d259..f1e0e4dd4c 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -277,14 +277,17 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, bool * pSessionPresent ); /** - * @brief Resends pending acks for a re-established MQTT session. + * @brief Resends pending acks for a re-established MQTT session, or + * clears existing state records for a clean session. * * @param[in] pContext Initialized MQTT context. + * @param[in] sessionPresent Session present flag received from the MQTT broker. * - * @return #MQTTSendFailed if transport send failed; + * @return #MQTTSendFailed if transport send during resend failed; * #MQTTSuccess otherwise. */ -static MQTTStatus_t resendPendingAcks( MQTTContext_t * pContext ); +static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext, + bool sessionPresent ); /** * @brief Serializes a PUBLISH message. @@ -1560,7 +1563,8 @@ static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext, /*-----------------------------------------------------------*/ -static MQTTStatus_t resendPendingAcks( MQTTContext_t * pContext ) +static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext, + bool sessionPresent ) { MQTTStatus_t status = MQTTSuccess; MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER; @@ -1569,16 +1573,29 @@ static MQTTStatus_t resendPendingAcks( MQTTContext_t * pContext ) assert( pContext != NULL ); - /* Get the next packet Id for which a PUBREL need to be resent. */ - packetId = MQTT_PubrelToResend( pContext, &cursor, &state ); - - /* Resend all the PUBREL acks after session is reestablished. */ - while( ( packetId != MQTT_PACKET_ID_INVALID ) && - ( status == MQTTSuccess ) ) + if( sessionPresent == true ) { - status = sendPublishAcks( pContext, packetId, state ); - + /* Get the next packet ID for which a PUBREL need to be resent. */ packetId = MQTT_PubrelToResend( pContext, &cursor, &state ); + + /* Resend all the PUBREL acks after session is reestablished. */ + while( ( packetId != MQTT_PACKET_ID_INVALID ) && + ( status == MQTTSuccess ) ) + { + status = sendPublishAcks( pContext, packetId, state ); + + packetId = MQTT_PubrelToResend( pContext, &cursor, &state ); + } + } + else + { + /* Clear any existing records if a new session is established. */ + ( void ) memset( pContext->outgoingPublishRecords, + 0x00, + sizeof( pContext->outgoingPublishRecords ) ); + ( void ) memset( pContext->incomingPublishRecords, + 0x00, + sizeof( pContext->incomingPublishRecords ) ); } return status; @@ -1791,10 +1808,10 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, pSessionPresent ); } - /* Resend all the PUBREL when reestablishing a session. */ - if( ( status == MQTTSuccess ) && ( *pSessionPresent == true ) ) + if( status == MQTTSuccess ) { - status = resendPendingAcks( pContext ); + /* Resend PUBRELs when reestablishing a session, or clear records for new sessions. */ + status = handleSessionResumption( pContext, *pSessionPresent ); } if( status == MQTTSuccess ) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index 1b827f6a40..daf8d30b48 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -994,6 +994,7 @@ void test_MQTT_Connect_happy_path() TransportInterface_t transport; MQTTFixedBuffer_t networkBuffer; MQTTPacketInfo_t incomingPacket; + MQTTPubAckInfo_t cleanRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 }; setupTransportInterface( &transport ); setupNetworkBuffer( &networkBuffer ); @@ -1033,6 +1034,12 @@ void test_MQTT_Connect_happy_path() mqttContext.keepAliveIntervalSec = 0; connectInfo.cleanSession = true; sessionPresentExpected = false; + /* Populate some state records to make sure they are cleared since a clean session + * will be established. */ + mqttContext.outgoingPublishRecords[ 0 ].packetId = 1; + mqttContext.outgoingPublishRecords[ 0 ].qos = MQTTQoS2; + mqttContext.outgoingPublishRecords[ 0 ].publishState = MQTTPublishSend; + mqttContext.incomingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT - 1 ].packetId = 1; MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess ); MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket ); MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess ); @@ -1042,6 +1049,9 @@ void test_MQTT_Connect_happy_path() TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus ); TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec ); TEST_ASSERT_FALSE( sessionPresent ); + /* Test old records were cleared. */ + TEST_ASSERT_EQUAL_MEMORY( cleanRecords, mqttContext.outgoingPublishRecords, sizeof( cleanRecords ) ); + TEST_ASSERT_EQUAL_MEMORY( cleanRecords, mqttContext.incomingPublishRecords, sizeof( cleanRecords ) ); /* Request to establish a session if present and session present is received * from broker. */ From 0cbaf79592528e580f4198137ae85a87f278c40c Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 24 Aug 2020 15:02:00 -0700 Subject: [PATCH 654/844] Add 3 test cases for full branch coverage of MatchTopic (#1156) --- libraries/standard/mqtt/utest/mqtt_utest.c | 83 ++++++++++++++++------ 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/libraries/standard/mqtt/utest/mqtt_utest.c b/libraries/standard/mqtt/utest/mqtt_utest.c index daf8d30b48..aea8de65bd 100644 --- a/libraries/standard/mqtt/utest/mqtt_utest.c +++ b/libraries/standard/mqtt/utest/mqtt_utest.c @@ -2220,7 +2220,7 @@ void test_MQTT_MatchTopic_ExactMatch( void ) &matchResult ) ); TEST_ASSERT_EQUAL( false, matchResult ); - /* Test for match at end with no wildcards. */ + /* Edge case tests (for branch coverage) to match at end with no wildcards. */ pTopicName = "/test/match/"; pTopicFilter = "/test/match/a"; TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, @@ -2229,13 +2229,33 @@ void test_MQTT_MatchTopic_ExactMatch( void ) strlen( pTopicFilter ), &matchResult ) ); TEST_ASSERT_EQUAL( false, matchResult ); + + pTopicName = "a"; + pTopicFilter = "a/"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + /* Edge case test (for branch coverage) when topic name has more levels + * than topic filter. */ + pTopicName = "test/match"; + pTopicFilter = "test"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); } /** * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification of all * cases of matching topic filters that contain the single-level '+' wildcard. */ -void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) +void test_MQTT_MatchTopic_Wildcard_SingleLevel_Match_Cases( void ) { const char * pTopicName = NULL; const char * pTopicFilter = NULL; @@ -2261,25 +2281,6 @@ void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) &matchResult ) ); TEST_ASSERT_EQUAL( true, matchResult ); - /* Test that match fails when topic name has more levels than topic filter. */ - pTopicName = "/test/match/level1/level2"; - pTopicFilter = "/test/match/+"; - TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, - strlen( pTopicName ), - pTopicFilter, - strlen( pTopicFilter ), - &matchResult ) ); - TEST_ASSERT_EQUAL( false, matchResult ); - - pTopicName = "/"; - pTopicFilter = "+"; - TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, - strlen( pTopicName ), - pTopicFilter, - strlen( pTopicFilter ), - &matchResult ) ); - TEST_ASSERT_EQUAL( false, matchResult ); - /* Test with '+' as the topic filter. */ pTopicName = "test"; pTopicFilter = "+"; @@ -2329,6 +2330,25 @@ void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) &matchResult ) ); TEST_ASSERT_EQUAL( true, matchResult ); + /* Test that match fails when topic name has more levels than topic filter. */ + pTopicName = "/test/match/level1/level2"; + pTopicFilter = "/test/match/+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + + pTopicName = "/"; + pTopicFilter = "+"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); + /* Edge case where filter ending with '/+' matches topic ending with '/'. */ pTopicName = "/test/match/"; pTopicFilter = "/test/match/+"; @@ -2338,6 +2358,17 @@ void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) strlen( pTopicFilter ), &matchResult ) ); TEST_ASSERT_EQUAL( true, matchResult ); +} + +/** + * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification for + * cases of where topic filter containing '+' wildcard do not match topic name. + */ +void test_MQTT_MatchTopic_Wildcard_SingleLevel_No_Match_Cases( void ) +{ + const char * pTopicName = NULL; + const char * pTopicFilter = NULL; + bool matchResult = false; /* Edge case where filter ending with '/+' should not match a topic ending with * at parent level. */ @@ -2379,6 +2410,16 @@ void test_MQTT_MatchTopic_Wildcard_SingleLevel( void ) strlen( pTopicFilter ), &matchResult ) ); TEST_ASSERT_EQUAL( false, matchResult ); + + /* Invalid topic filter where intermediate '+' is not followed by '/'.*/ + pTopicName = "test/match/level"; + pTopicFilter = "test/+?level"; + TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName, + strlen( pTopicName ), + pTopicFilter, + strlen( pTopicFilter ), + &matchResult ) ); + TEST_ASSERT_EQUAL( false, matchResult ); } /** From 8ab8c1a952a6f23517dfa3724c8fe9f261a20b71 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Mon, 24 Aug 2020 15:53:59 -0700 Subject: [PATCH 655/844] Doxygen for MQTT API Only (#1149) * MQTT API doxygen documentation and supporting files. Co-authored-by: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> --- .gitignore | 3 +- libraries/standard/mqtt/README.md | 10 + .../standard/mqtt/doc/doxygen/config.doxyfile | 2556 +++++++++++++++++ .../standard/mqtt/doc/doxygen/layout.xml | 228 ++ libraries/standard/mqtt/doc/doxygen/pages.txt | 260 ++ libraries/standard/mqtt/doc/doxygen/style.css | 132 + libraries/standard/mqtt/include/mqtt.h | 102 +- .../standard/mqtt/include/mqtt_lightweight.h | 47 + libraries/standard/mqtt/include/mqtt_state.h | 72 +- libraries/standard/mqtt/lexicon.txt | 4 + libraries/standard/mqtt/src/mqtt.c | 27 - libraries/standard/mqtt/src/mqtt_state.c | 71 - 12 files changed, 3375 insertions(+), 137 deletions(-) create mode 100644 libraries/standard/mqtt/README.md create mode 100644 libraries/standard/mqtt/doc/doxygen/config.doxyfile create mode 100644 libraries/standard/mqtt/doc/doxygen/layout.xml create mode 100644 libraries/standard/mqtt/doc/doxygen/pages.txt create mode 100644 libraries/standard/mqtt/doc/doxygen/style.css diff --git a/.gitignore b/.gitignore index c1cb0f6609..0733a0a4fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Ignore documentation output. -doc/output/* -doc/tag/* +**/doc/**/output/* # Ignore CMake build directory. build/ diff --git a/libraries/standard/mqtt/README.md b/libraries/standard/mqtt/README.md new file mode 100644 index 0000000000..54c855fff8 --- /dev/null +++ b/libraries/standard/mqtt/README.md @@ -0,0 +1,10 @@ +# MQTT Client Library + +## Generating documentation + +The Doxygen references were created using Doxygen version 1.8.19. To generate the +Doxygen pages, please run the following command from the root of this repository: + +```shell +doxygen doc/doxygen/config.doxyfile +``` diff --git a/libraries/standard/mqtt/doc/doxygen/config.doxyfile b/libraries/standard/mqtt/doc/doxygen/config.doxyfile new file mode 100644 index 0000000000..4c6e793541 --- /dev/null +++ b/libraries/standard/mqtt/doc/doxygen/config.doxyfile @@ -0,0 +1,2556 @@ +# Doxyfile 1.8.19 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "MQTT" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = "LTS rc1" + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "MQTT 3.1.1 Client Library" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen/output/ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +# NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# (including Cygwin) and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = doc/doxygen/layout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ./doc/doxygen ./include ./src ./src/private + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.h \ + *.txt + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = include + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +# CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +# CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the "-p" option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +# CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = ./doc/doxygen/style.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +# HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +# FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /
here. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Code Size of MQTT LTS rc1(example generated with GCC for ARM Cortex-M)
FileWith -O1 OptimisationWith -Os Optimisation
mqtt.c3.3K2.9K
mqtt_state.c2.1K1.6K
mqtt_lightweight.c3.5K2.9K
Total estimates8.9K7.4K
+ */ + +/** +@page mqtt_design Design +MQTT Library Design +*/ + +/** +@page mqtt_config Configurations +@brief Configurations of the MQTT Library. + +@par configpagestyle + +Configuration settings are C pre-processor constants. They can be set with a \#define in the config file (mqtt_config.h) or by using a compiler option such as -D in gcc. + +@section MQTT_STATE_ARRAY_MAX_COUNT +
+@copydoc MQTT_STATE_ARRAY_MAX_COUNT + +@section MQTT_PINGRESP_TIMEOUT_MS +
+@copydoc MQTT_PINGRESP_TIMEOUT_MS + +@section MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT +
+@copydoc MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT +*/ + +/** +@page mqtt_functions Functions +@brief Primary functions of the MQTT library:

+@subpage mqtt_init_function
+@subpage mqtt_connect_function
+@subpage mqtt_subscribe_function
+@subpage mqtt_publish_function
+@subpage mqtt_ping_function
+@subpage mqtt_unsubscribe_function
+@subpage mqtt_disconnect_function
+@subpage mqtt_processloop_function
+@subpage mqtt_receiveloop_function
+@subpage mqtt_getpacketid_function
+@subpage mqtt_getsubackstatuscodes_function
+@subpage mqtt_status_strerror_function
+@subpage mqtt_publishtoresend_function

+ +Lightweight functions of the MQTT library:

+@subpage mqtt_getconnectpacketsize_function
+@subpage mqtt_serializeconnect_function
+@subpage mqtt_getsubscribepacketsize_function
+@subpage mqtt_serializesubscribe_function
+@subpage mqtt_getunsubscribepacketsize_function
+@subpage mqtt_serializeunsubscribe_function
+@subpage mqtt_getpublishpacketsize_function
+@subpage mqtt_serializepublish_function
+@subpage mqtt_serializepublishheader_function
+@subpage mqtt_serializeack_function
+@subpage mqtt_getdisconnectpacketsize_function
+@subpage mqtt_serializedisconnect_function
+@subpage mqtt_getpingreqpacketsize_function
+@subpage mqtt_serializepingreq_function
+@subpage mqtt_deserializepublish_function
+@subpage mqtt_deserializeack_function
+@subpage mqtt_getincomingpackettypeandlength_function
+ +@page mqtt_init_function MQTT_Init +@snippet mqtt.h declare_mqtt_init +@copydoc MQTT_Init + +@page mqtt_connect_function MQTT_Connect +@snippet mqtt.h declare_mqtt_connect +@copydoc MQTT_Connect + +@page mqtt_subscribe_function MQTT_Subscribe +@snippet mqtt.h declare_mqtt_subscribe +@copydoc MQTT_Subscribe + +@page mqtt_publish_function MQTT_Publish +@snippet mqtt.h declare_mqtt_publish +@copydoc MQTT_Publish + +@page mqtt_ping_function MQTT_Ping +@snippet mqtt.h declare_mqtt_ping +@copydoc MQTT_Ping + +@page mqtt_unsubscribe_function MQTT_Unsubscribe +@snippet mqtt.h declare_mqtt_unsubscribe +@copydoc MQTT_Unsubscribe + +@page mqtt_disconnect_function MQTT_Disconnect +@snippet mqtt.h declare_mqtt_disconnect +@copydoc MQTT_Disconnect + +@page mqtt_processloop_function MQTT_ProcessLoop +@snippet mqtt.h declare_mqtt_processloop +@copydoc MQTT_ProcessLoop + +@page mqtt_receiveloop_function MQTT_ReceiveLoop +@snippet mqtt.h declare_mqtt_receiveloop +@copydoc MQTT_ReceiveLoop + +@page mqtt_getpacketid_function MQTT_GetPacketId +@snippet mqtt.h declare_mqtt_getpacketid +@copydoc MQTT_GetPacketId + +@page mqtt_getsubackstatuscodes_function MQTT_GetSubAckStatusCodes +@snippet mqtt.h declare_mqtt_getsubackstatuscodes +@copydoc MQTT_GetSubAckStatusCodes + +@page mqtt_status_strerror_function MQTT_Status_strerror +@snippet mqtt.h declare_mqtt_status_strerror +@copydoc MQTT_Status_strerror + +@page mqtt_publishtoresend_function MQTT_PublishToResend +@snippet mqtt_state.h declare_mqtt_publishtoresend +@copydoc MQTT_PublishToResend + +@page mqtt_getconnectpacketsize_function MQTT_GetConnectPacketSize +@snippet mqtt_lightweight.h declare_mqtt_getconnectpacketsize +@copydoc MQTT_GetConnectPacketSize + +@page mqtt_serializeconnect_function MQTT_SerializeConnect +@snippet mqtt_lightweight.h declare_mqtt_serializeconnect +@copydoc MQTT_SerializeConnect + +@page mqtt_getsubscribepacketsize_function MQTT_GetSubscribePacketSize +@snippet mqtt_lightweight.h declare_mqtt_getsubscribepacketsize +@copydoc MQTT_GetSubscribePacketSize + +@page mqtt_serializesubscribe_function MQTT_SerializeSubscribe +@snippet mqtt_lightweight.h declare_mqtt_serializesubscribe +@copydoc MQTT_SerializeSubscribe + +@page mqtt_getunsubscribepacketsize_function MQTT_GetUnsubscribePacketSize +@snippet mqtt_lightweight.h declare_mqtt_getunsubscribepacketsize +@copydoc MQTT_GetUnsubscribePacketSize + +@page mqtt_serializeunsubscribe_function MQTT_SerializeUnsubscribe +@snippet mqtt_lightweight.h declare_mqtt_serializeunsubscribe +@copydoc MQTT_SerializeUnsubscribe + +@page mqtt_getpublishpacketsize_function MQTT_GetPublishPacketSize +@snippet mqtt_lightweight.h declare_mqtt_getpublishpacketsize +@copydoc MQTT_GetPublishPacketSize + +@page mqtt_serializepublish_function MQTT_SerializePublish +@snippet mqtt_lightweight.h declare_mqtt_serializepublish +@copydoc MQTT_SerializePublish + +@page mqtt_serializepublishheader_function MQTT_SerializePublishHeader +@snippet mqtt_lightweight.h declare_mqtt_serializepublishheader +@copydoc MQTT_SerializePublishHeader + +@page mqtt_serializeack_function MQTT_SerializeAck +@snippet mqtt_lightweight.h declare_mqtt_serializeack +@copydoc MQTT_SerializeAck + +@page mqtt_getdisconnectpacketsize_function MQTT_GetDisconnectPacketSize +@snippet mqtt_lightweight.h declare_mqtt_getdisconnectpacketsize +@copydoc MQTT_GetDisconnectPacketSize + +@page mqtt_serializedisconnect_function MQTT_SerializeDisconnect +@snippet mqtt_lightweight.h declare_mqtt_serializedisconnect +@copydoc MQTT_SerializeDisconnect + +@page mqtt_getpingreqpacketsize_function MQTT_GetPingreqPacketSize +@snippet mqtt_lightweight.h declare_mqtt_getpingreqpacketsize +@copydoc MQTT_GetPingreqPacketSize + +@page mqtt_serializepingreq_function MQTT_SerializePingreq +@snippet mqtt_lightweight.h declare_mqtt_serializepingreq +@copydoc MQTT_SerializePingreq + +@page mqtt_deserializepublish_function MQTT_DeserializePublish +@snippet mqtt_lightweight.h declare_mqtt_deserializepublish +@copydoc MQTT_DeserializePublish + +@page mqtt_deserializeack_function MQTT_DeserializeAck +@snippet mqtt_lightweight.h declare_mqtt_deserializeack +@copydoc MQTT_DeserializeAck + +@page mqtt_getincomingpackettypeandlength_function MQTT_GetIncomingPacketTypeAndLength +@snippet mqtt_lightweight.h declare_mqtt_getincomingpackettypeandlength +@copydoc MQTT_GetIncomingPacketTypeAndLength +*/ + +/** +@defgroup mqtt_enum_types Enumerated Types +@brief Enumerated types of the MQTT library +*/ + +/** +@defgroup mqtt_callbacks_types Callback Types +@brief Callback function pointer types of the MQTT library +*/ + +/** +@defgroup mqtt_struct_types Parameter Structures +@brief Structures passed as parameters to [MQTT library functions](@ref mqtt_functions) + +These structures are passed as parameters to library functions. Documentation for these structures will state the functions associated with each parameter structure and the purpose of each member. +*/ + +/** +@defgroup mqtt_basic_types Basic Types +@brief Primitive types of the MQTT library. +*/ + +/** +@defgroup mqtt_constants Constants +@brief Constants defined in the MQTT library +*/ \ No newline at end of file diff --git a/libraries/standard/mqtt/doc/doxygen/style.css b/libraries/standard/mqtt/doc/doxygen/style.css new file mode 100644 index 0000000000..99d7ab71fa --- /dev/null +++ b/libraries/standard/mqtt/doc/doxygen/style.css @@ -0,0 +1,132 @@ +/* + * Stylesheet for Doxygen HTML output. + * + * This file defines styles for custom elements in the header/footer and + * overrides some of the default Doxygen styles. + * + * Styles in this file do not affect the treeview sidebar. + */ + +/* Set the margins to place a small amount of whitespace on the left and right + * side of the page. */ +div.contents { + margin-left:4em; + margin-right:4em; +} + +/* Justify text in paragraphs. */ +p { + text-align: justify; +} + +/* Style of section headings. */ +h1 { + border-bottom: 1px solid #879ECB; + color: #354C7B; + font-size: 160%; + font-weight: normal; + padding-bottom: 4px; + padding-top: 8px; +} + +/* Style of subsection headings. */ +h2:not(.memtitle):not(.groupheader) { + font-size: 125%; + margin-bottom: 0px; + margin-top: 16px; + padding: 0px; +} + +/* Style of paragraphs immediately after subsection headings. */ +h2 + p { + margin: 0px; + padding: 0px; +} + +/* Style of subsection headings. */ +h3 { + font-size: 100%; + margin-bottom: 0px; + margin-left: 2em; + margin-right: 2em; +} + +/* Style of paragraphs immediately after subsubsection headings. */ +h3 + p { + margin-top: 0px; + margin-left: 2em; + margin-right: 2em; +} + +/* Style of the prefix "AWS IoT Device SDK C" that appears in the header. */ +#csdkprefix { + color: #757575; +} + +/* Style of the "Return to main page" link that appears in the header. */ +#returntomain { + padding: 0.5em; +} + +/* Style of the dividers on Configuration Settings pages. */ +div.configpagedivider { + margin-left: 0px !important; + margin-right: 0px !important; + margin-top: 20px !important; +} + +/* Style of configuration setting names. */ +dl.section.user ~ h1 { + border-bottom: none; + color: #000000; + font-family: monospace, fixed; + font-size: 16px; + margin-bottom: 0px; + margin-left: 2em; + margin-top: 1.5em; +} + +/* Style of paragraphs on a configuration settings page. */ +dl.section.user ~ * { + margin-bottom: 10px; + margin-left: 4em; + margin-right: 4em; + margin-top: 0px; +} + +/* Hide the configuration setting marker. */ +dl.section.user { + display: none; +} + +/* Overrides for code fragments and lines. */ +div.fragment { + background: #ffffff; + border: none; + padding: 5px; +} + +div.line { + color: #3a3a3a; +} + +/* Overrides for code syntax highlighting colors. */ +span.comment { + color: #008000; +} + +span.keyword, span.keywordtype, span.keywordflow { + color: #0000ff; +} + +span.preprocessor { + color: #50015a; +} + +span.stringliteral, span.charliteral { + color: #800c0c; +} + +a.code, a.code:visited, a.line, a.line:visited { + color: #496194; +} diff --git a/libraries/standard/mqtt/include/mqtt.h b/libraries/standard/mqtt/include/mqtt.h index a5d9eaa0a7..1051e0ede5 100644 --- a/libraries/standard/mqtt/include/mqtt.h +++ b/libraries/standard/mqtt/include/mqtt.h @@ -33,18 +33,70 @@ #include "transport_interface.h" /** + * @ingroup mqtt_constants * @brief Invalid packet identifier. * * Zero is an invalid packet identifier as per MQTT v3.1.1 spec. */ #define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) +/** + * @brief The maximum number of MQTT PUBLISH messages that may be pending + * acknowledgement at any time. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains. + * + * Possible values: Any positive 32 bit integer.
+ * Default value: `10` + */ +#ifndef MQTT_STATE_ARRAY_MAX_COUNT + /* Default value for the maximum acknowledgement pending PUBLISH messages. */ + #define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) +#endif + +/** + * @brief The number of retries for receiving CONNACK. + * + * The MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT will be used only when the + * timeoutMs parameter of #MQTT_Connect is passed as 0 . The transport + * receive for CONNACK will be retried MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT + * times before timing out. A value of 0 for this config will cause the + * transport receive for CONNACK to be invoked only once. + * + * Possible values: Any positive 16 bit integer.
+ * Default value: `5` + */ +#ifndef MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT + /* Default value for the CONNACK receive retries. */ + #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U ) +#endif + +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + * + * Possible values: Any positive integer up to SIZE_MAX.
+ * Default value: `500` + */ +#ifndef MQTT_PINGRESP_TIMEOUT_MS + /* Wait 0.5 seconds by default for a ping response. */ + #define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) +#endif + /* Structures defined in this file. */ struct MQTTPubAckInfo; struct MQTTContext; struct MQTTDeserializedInfo; /** + * @ingroup mqtt_callbacks_types * @brief Application provided callback to retrieve the current time in * milliseconds. * @@ -53,6 +105,7 @@ struct MQTTDeserializedInfo; typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); /** + * @ingroup mqtt_callbacks_types * @brief Application callback for receiving incoming publishes and incoming * acks. * @@ -69,6 +122,7 @@ typedef void (* MQTTEventCallback_t )( struct MQTTContext * pContext, struct MQTTDeserializedInfo * pDeserializedInfo ); /** + * @ingroup mqtt_enum_types * @brief Values indicating if an MQTT connection exists. */ typedef enum MQTTConnectionStatus @@ -78,6 +132,7 @@ typedef enum MQTTConnectionStatus } MQTTConnectionStatus_t; /** + * @ingroup mqtt_enum_types * @brief The state of QoS 1 or QoS 2 MQTT publishes, used in the state engine. */ typedef enum MQTTPublishState @@ -96,6 +151,7 @@ typedef enum MQTTPublishState } MQTTPublishState_t; /** + * @ingroup mqtt_enum_types * @brief Packet types used in acknowledging QoS 1 or QoS 2 publishes. */ typedef enum MQTTPubAckType @@ -107,6 +163,7 @@ typedef enum MQTTPubAckType } MQTTPubAckType_t; /** + * @ingroup mqtt_enum_types * @brief The status codes in the SUBACK response to a subscription request. */ typedef enum MQTTSubAckStatus @@ -118,6 +175,7 @@ typedef enum MQTTSubAckStatus } MQTTSubAckStatus_t; /** + * @ingroup mqtt_struct_types * @brief An element of the state engine records for QoS 1 or Qos 2 publishes. */ typedef struct MQTTPubAckInfo @@ -128,6 +186,7 @@ typedef struct MQTTPubAckInfo } MQTTPubAckInfo_t; /** + * @ingroup mqtt_struct_types * @brief A struct representing an MQTT connection. */ typedef struct MQTTContext @@ -190,6 +249,7 @@ typedef struct MQTTContext } MQTTContext_t; /** + * @ingroup mqtt_struct_types * @brief Struct to hold deserialized packet information for an #MQTTEventCallback_t * callback. */ @@ -211,14 +271,12 @@ typedef struct MQTTDeserializedInfo * functions. This will ensure all time based functions will run for a single * iteration. * - * @brief param[in] pContext The context to initialize. - * @brief param[in] pTransportInterface The transport interface to use with the - * context. - * @brief param[in] getTimeFunction The time utility function to use with the - * context. - * @brief param[in] userCallback The user callback to use with the context to + * @param[in] pContext The context to initialize. + * @param[in] pTransportInterface The transport interface to use with the context. + * @param[in] getTimeFunction The time utility function to use with the context. + * @param[in] userCallback The user callback to use with the context to * notify about incoming packet events. - * @brief param[in] pNetworkBuffer Network buffer provided for the context. + * @param[in] pNetworkBuffer Network buffer provided for the context. * * @return #MQTTBadParameter if invalid parameters are passed; * #MQTTSuccess otherwise. @@ -265,11 +323,13 @@ typedef struct MQTTDeserializedInfo * } * @endcode */ +/* @[declare_mqtt_init] */ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, const TransportInterface_t * pTransportInterface, MQTTGetCurrentTimeFunc_t getTimeFunction, MQTTEventCallback_t userCallback, const MQTTFixedBuffer_t * pNetworkBuffer ); +/* @[declare_mqtt_init] */ /** * @brief Establish an MQTT session. @@ -369,11 +429,13 @@ MQTTStatus_t MQTT_Init( MQTTContext_t * pContext, * } * @endcode */ +/* @[declare_mqtt_connect] */ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, uint32_t timeoutMs, bool * pSessionPresent ); +/* @[declare_mqtt_connect] */ /** * @brief Sends MQTT SUBSCRIBE for the given list of topic filters to @@ -424,17 +486,19 @@ MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext, * } * @endcode */ +/* @[declare_mqtt_subscribe] */ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ); +/* @[declare_mqtt_subscribe] */ /** * @brief Publishes a message to the given topic name. * - * @brief param[in] pContext Initialized MQTT context. - * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters. - * @brief param[in] packetId packet ID generated by #MQTT_GetPacketId. + * @param[in] pContext Initialized MQTT context. + * @param[in] pPublishInfo MQTT PUBLISH packet parameters. + * @param[in] packetId packet ID generated by #MQTT_GetPacketId. * * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet; * #MQTTBadParameter if invalid parameters are passed; @@ -470,9 +534,11 @@ MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext, * } * @endcode */ +/* @[declare_mqtt_publish] */ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId ); +/* @[declare_mqtt_publish] */ /** * @brief Sends an MQTT PINGREQ to broker. @@ -484,7 +550,9 @@ MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext, * #MQTTSendFailed if transport write failed; * #MQTTSuccess otherwise. */ +/* @[declare_mqtt_ping] */ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); +/* @[declare_mqtt_ping] */ /** * @brief Sends MQTT UNSUBSCRIBE for the given list of topic filters to @@ -534,10 +602,12 @@ MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext ); * } * @endcode */ +/* @[declare_mqtt_unsubscribe] */ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId ); +/* @[declare_mqtt_unsubscribe] */ /** * @brief Disconnect an MQTT session. @@ -550,7 +620,9 @@ MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext, * #MQTTSendFailed if transport send failed; * #MQTTSuccess otherwise. */ +/* @[declare_mqtt_disconnect] */ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); +/* @[declare_mqtt_disconnect] */ /** * @brief Loop to receive packets from the transport interface. Handles keep @@ -595,8 +667,10 @@ MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext ); * } * @endcode */ +/* @[declare_mqtt_processloop] */ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, uint32_t timeoutMs ); +/* @[declare_mqtt_processloop] */ /** * @brief Loop to receive packets from the transport interface. Does not handle @@ -649,8 +723,10 @@ MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext, * } * @endcode */ +/* @[declare_mqtt_receiveloop] */ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, uint32_t timeoutMs ); +/* @[declare_mqtt_receiveloop] */ /** * @brief Get a packet ID that is valid according to the MQTT 3.1.1 spec. @@ -659,7 +735,9 @@ MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext, * * @return A non-zero number. */ +/* @[declare_mqtt_getpacketid] */ uint16_t MQTT_GetPacketId( MQTTContext_t * pContext ); +/* @[declare_mqtt_getpacketid] */ /** * @brief A utility function that determines whether the passed topic filter and @@ -791,9 +869,11 @@ MQTTStatus_t MQTT_MatchTopic( const char * pTopicName, * } * @endcode */ +/* @[declare_mqtt_getsubackstatuscodes] */ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, uint8_t ** pPayloadStart, size_t * pPayloadSize ); +/* @[declare_mqtt_getsubackstatuscodes] */ /** * @brief Error code to string conversion for MQTT statuses. @@ -802,6 +882,8 @@ MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket, * * @return The string representation of the status. */ +/* @[declare_mqtt_status_strerror] */ const char * MQTT_Status_strerror( MQTTStatus_t status ); +/* @[declare_mqtt_status_strerror] */ #endif /* ifndef MQTT_H */ diff --git a/libraries/standard/mqtt/include/mqtt_lightweight.h b/libraries/standard/mqtt/include/mqtt_lightweight.h index 048457c109..c0a6ced6de 100644 --- a/libraries/standard/mqtt/include/mqtt_lightweight.h +++ b/libraries/standard/mqtt/include/mqtt_lightweight.h @@ -52,6 +52,10 @@ #include "transport_interface.h" /* MQTT packet types. */ +/** + * @addtogroup mqtt_constants + * @{ + */ #define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ #define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ #define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */ @@ -66,8 +70,10 @@ #define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xC0U ) /**< @brief PINGREQ (client-to-server). */ #define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */ #define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */ +/** @} */ /** + * @ingroup mqtt_constants * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec. */ #define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4UL ) @@ -80,6 +86,7 @@ struct MQTTPublishInfo; struct MQTTPacketInfo; /** + * @ingroup mqtt_enum_types * @brief Return codes from MQTT functions. */ typedef enum MQTTStatus @@ -98,6 +105,7 @@ typedef enum MQTTStatus } MQTTStatus_t; /** + * @ingroup mqtt_enum_types * @brief MQTT Quality of Service values. */ typedef enum MQTTQoS @@ -108,6 +116,7 @@ typedef enum MQTTQoS } MQTTQoS_t; /** + * @ingroup mqtt_struct_types * @brief Buffer passed to MQTT library. * * These buffers are not copied and must remain in scope for the duration of the @@ -120,6 +129,7 @@ typedef struct MQTTFixedBuffer } MQTTFixedBuffer_t; /** + * @ingroup mqtt_struct_types * @brief MQTT CONNECT packet parameters. */ typedef struct MQTTConnectInfo @@ -166,6 +176,7 @@ typedef struct MQTTConnectInfo } MQTTConnectInfo_t; /** + * @ingroup mqtt_struct_types * @brief MQTT SUBSCRIBE packet parameters. */ typedef struct MQTTSubscribeInfo @@ -187,6 +198,7 @@ typedef struct MQTTSubscribeInfo } MQTTSubscribeInfo_t; /** + * @ingroup mqtt_struct_types * @brief MQTT PUBLISH packet parameters. */ typedef struct MQTTPublishInfo @@ -228,6 +240,7 @@ typedef struct MQTTPublishInfo } MQTTPublishInfo_t; /** + * @ingroup mqtt_struct_types * @brief MQTT incoming packet parameters. */ typedef struct MQTTPacketInfo @@ -295,10 +308,12 @@ typedef struct MQTTPacketInfo * } * @endcode */ +/* @[declare_mqtt_getconnectpacketsize] */ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t * pRemainingLength, size_t * pPacketSize ); +/* @[declare_mqtt_getconnectpacketsize] */ /** * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pFixedBuffer. @@ -349,10 +364,12 @@ MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo, * } * @endcode */ +/* @[declare_mqtt_serializeconnect] */ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength, const MQTTFixedBuffer_t * pFixedBuffer ); +/* @[declare_mqtt_serializeconnect] */ /** * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet. @@ -405,10 +422,12 @@ MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo, * } * @endcode */ +/* @[declare_mqtt_getsubscribepacketsize] */ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ); +/* @[declare_mqtt_getsubscribepacketsize] */ /** * @brief Serialize an MQTT SUBSCRIBE packet in the given buffer. @@ -469,11 +488,13 @@ MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscript * } * @endcode */ +/* @[declare_mqtt_serializesubscribe] */ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, const MQTTFixedBuffer_t * pFixedBuffer ); +/* @[declare_mqtt_serializesubscribe] */ /** * @brief Get packet size and Remaining Length of an MQTT UNSUBSCRIBE packet. @@ -518,10 +539,12 @@ MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionL * } * @endcode */ +/* @[declare_mqtt_getunsubscribepacketsize] */ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, size_t * pRemainingLength, size_t * pPacketSize ); +/* @[declare_mqtt_getunsubscribepacketsize] */ /** * @brief Serialize an MQTT UNSUBSCRIBE packet in the given buffer. @@ -582,11 +605,13 @@ MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscri * } * @endcode */ +/* @[declare_mqtt_serializeunsubscribe] */ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList, size_t subscriptionCount, uint16_t packetId, size_t remainingLength, const MQTTFixedBuffer_t * pFixedBuffer ); +/* @[declare_mqtt_serializeunsubscribe] */ /** * @brief Get the packet size and remaining length of an MQTT PUBLISH packet. @@ -634,9 +659,11 @@ MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptio * } * @endcode */ +/* @[declare_mqtt_getpublishpacketsize] */ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, size_t * pRemainingLength, size_t * pPacketSize ); +/* @[declare_mqtt_getpublishpacketsize] */ /** * @brief Serialize an MQTT PUBLISH packet in the given buffer. @@ -700,10 +727,12 @@ MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo, * } * @endcode */ +/* @[declare_mqtt_serializepublish] */ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, const MQTTFixedBuffer_t * pFixedBuffer ); +/* @[declare_mqtt_serializepublish] */ /** * @brief Serialize an MQTT PUBLISH packet header in the given buffer. @@ -778,11 +807,13 @@ MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo, * } * @endcode */ +/* @[declare_mqtt_serializepublishheader] */ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo, uint16_t packetId, size_t remainingLength, const MQTTFixedBuffer_t * pFixedBuffer, size_t * pHeaderSize ); +/* @[declare_mqtt_serializepublishheader] */ /** * @brief Serialize an MQTT PUBACK, PUBREC, PUBREL, or PUBCOMP into the given @@ -825,9 +856,11 @@ MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo * } * @endcode */ +/* @[declare_mqtt_serializeack] */ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, uint8_t packetType, uint16_t packetId ); +/* @[declare_mqtt_serializeack] */ /** * @brief Get the size of an MQTT DISCONNECT packet. @@ -853,7 +886,9 @@ MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer, * * @endcode */ +/* @[declare_mqtt_getdisconnectpacketsize] */ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); +/* @[declare_mqtt_getdisconnectpacketsize] */ /** * @brief Serialize an MQTT DISCONNECT packet into the given buffer. @@ -892,7 +927,9 @@ MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize ); * } * @endcode */ +/* @[declare_mqtt_serializedisconnect] */ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ); +/* @[declare_mqtt_serializedisconnect] */ /** * @brief Get the size of an MQTT PINGREQ packet. @@ -918,7 +955,9 @@ MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer ); * * @endcode */ +/* @[declare_mqtt_getpingreqpacketsize] */ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); +/* @[declare_mqtt_getpingreqpacketsize] */ /** * @brief Serialize an MQTT PINGREQ packet into the given buffer. @@ -957,7 +996,9 @@ MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize ); * } * @endcode */ +/* @[declare_mqtt_serializepingreq] */ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); +/* @[declare_mqtt_serializepingreq] */ /** * @brief Deserialize an MQTT PUBLISH packet. @@ -1016,9 +1057,11 @@ MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer ); * } * @endcode */ +/* @[declare_mqtt_deserializepublish] */ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, uint16_t * pPacketId, MQTTPublishInfo_t * pPublishInfo ); +/* @[declare_mqtt_deserializepublish] */ /** * @brief Deserialize an MQTT CONNACK, SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL, @@ -1058,9 +1101,11 @@ MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket, * } * @endcode */ +/* @[declare_mqtt_deserializeack] */ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, uint16_t * pPacketId, bool * pSessionPresent ); +/* @[declare_mqtt_deserializeack] */ /** * @brief Extract the MQTT packet type and length from incoming packet. @@ -1122,8 +1167,10 @@ MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket, * incomingPacket.pRemainingData = buffer; * @endcode */ +/* @[declare_mqtt_getincomingpackettypeandlength] */ MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc, NetworkContext_t * pNetworkContext, MQTTPacketInfo_t * pIncomingPacket ); +/* @[declare_mqtt_getincomingpackettypeandlength] */ #endif /* ifndef MQTT_LIGHTWEIGHT_H */ diff --git a/libraries/standard/mqtt/include/mqtt_state.h b/libraries/standard/mqtt/include/mqtt_state.h index 3225f7bdf9..392aa05094 100644 --- a/libraries/standard/mqtt/include/mqtt_state.h +++ b/libraries/standard/mqtt/include/mqtt_state.h @@ -29,10 +29,17 @@ #include "mqtt.h" /** + * @ingroup mqtt_constants * @brief Initializer value for an #MQTTStateCursor_t, indicating a search * should start at the beginning of a state record array */ -#define MQTT_STATE_CURSOR_INITIALIZER ( size_t ) 0 +#define MQTT_STATE_CURSOR_INITIALIZER ( ( size_t ) 0 ) + +/** + * @ingroup mqtt_basic_types + * @brief Cursor for iterating through state records. + */ +typedef size_t MQTTStateCursor_t; /** * @cond DOXYGEN_IGNORE @@ -48,14 +55,7 @@ typedef enum MQTTStateOperation /** @endcond */ /** - * @brief Cursor for iterating through state records. - */ -typedef size_t MQTTStateCursor_t; - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos ); * @brief Reserve an entry for an outgoing QoS 1 or Qos 2 publish. * * @param[in] pMqttContext Initialized MQTT context. @@ -64,15 +64,17 @@ typedef size_t MQTTStateCursor_t; * * @return MQTTSuccess, MQTTNoMemory, or MQTTStateCollision. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos ); /** @endcond */ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ) * @brief Calculate the new state for a publish from its qos and operation type. * * @param[in] opType Send or Receive. @@ -80,14 +82,16 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, * * @return The calculated state. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ); /** @endcond */ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, MQTTQoS_t qos, MQTTPublishState_t * pNewState ); * @brief Update the state record for a PUBLISH packet. * * @param[in] pMqttContext Initialized MQTT context. @@ -99,6 +103,10 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, * @return #MQTTBadParameter, #MQTTIllegalState, #MQTTStateCollision or * #MQTTSuccess. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, @@ -107,9 +115,7 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, /** @endcond */ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos ); * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP. * * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. @@ -118,15 +124,17 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, * * @return The calculated state. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos ); /** @endcond */ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTPublishState_t * pNewState ); * @brief Update the state record for an ACKed publish. * * @param[in] pMqttContext Initialized MQTT context. @@ -140,6 +148,10 @@ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, * #MQTTIllegalState if the requested update would result in an illegal transition; * #MQTTSuccess otherwise. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTPubAckType_t packetType, @@ -148,9 +160,7 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, /** @endcond */ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState ); * @brief Get the packet ID of next pending PUBREL ack to be resent. * * This function will need to be called to get the packet for which a PUBREL @@ -162,6 +172,10 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, * @param[in,out] pCursor Index at which to start searching. * @param[out] pState State indicating that PUBREL packet need to be sent. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState ); @@ -233,19 +247,23 @@ uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, * } * @endcode */ +/* @[declare_mqtt_publishtoresend] */ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor ); +/* @[declare_mqtt_publishtoresend] */ /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section, this function is private. - * + * @fn const char * MQTT_State_strerror( MQTTPublishState_t state ); * @brief State to string conversion for state engine. * * @param[in] state The state to convert to a string. * * @return The string representation of the state. */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this definition, this function is private. + */ const char * MQTT_State_strerror( MQTTPublishState_t state ); /** @endcond */ diff --git a/libraries/standard/mqtt/lexicon.txt b/libraries/standard/mqtt/lexicon.txt index 2a9e8870a7..26dcd28152 100644 --- a/libraries/standard/mqtt/lexicon.txt +++ b/libraries/standard/mqtt/lexicon.txt @@ -2,11 +2,13 @@ ack acked acks addrecord +addtogroup ansi api app aws bool +br bufferlength bytesorerror bytesreceived @@ -60,6 +62,7 @@ eventcallbackstub expectprocessloopcalls filterindex fixedbuffer +fn getconnectpacketsize getcurrenttimestub getdisconnectpacketsize @@ -85,6 +88,7 @@ ifndef inc incomingpacket incomingpublish +ingroup init initializeconnectinfo initializesubscribeinfo diff --git a/libraries/standard/mqtt/src/mqtt.c b/libraries/standard/mqtt/src/mqtt.c index f1e0e4dd4c..0002e9df06 100644 --- a/libraries/standard/mqtt/src/mqtt.c +++ b/libraries/standard/mqtt/src/mqtt.c @@ -30,33 +30,6 @@ #include "mqtt_state.h" #include "private/mqtt_internal.h" - -/** - * @brief The number of retries for receiving CONNACK. - * - * The MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT will be used only when the - * timeoutMs parameter of #MQTT_Connect() is passed as 0 . The transport - * receive for CONNACK will be retried MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT - * times before timing out. A value of 0 for this config will cause the - * transport receive for CONNACK to be invoked only once. - */ -#ifndef MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT - /* Default value for the CONNACK receive retries. */ - #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U ) -#endif - -/** - * @brief Number of milliseconds to wait for a ping response to a ping - * request as part of the keep-alive mechanism. - * - * If a ping response is not received before this timeout, then - * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. - */ -#ifndef MQTT_PINGRESP_TIMEOUT_MS - /* Wait 0.5 seconds by default for a ping response. */ - #define MQTT_PINGRESP_TIMEOUT_MS ( 500U ) -#endif - /*-----------------------------------------------------------*/ /** diff --git a/libraries/standard/mqtt/src/mqtt_state.c b/libraries/standard/mqtt/src/mqtt_state.c index 21a6117c80..fb21757dad 100644 --- a/libraries/standard/mqtt/src/mqtt_state.c +++ b/libraries/standard/mqtt/src/mqtt_state.c @@ -616,15 +616,6 @@ static uint16_t stateSelect( const MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ -/** - * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP. - * - * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. - * @param[in] opType Send or Receive. - * @param[in] qos 1 or 2. - * - * @return The calculated state. - */ MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos ) @@ -789,15 +780,6 @@ static MQTTStatus_t updateStatePublish( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ -/** - * @brief Reserve an entry for an outgoing QoS 1 or Qos 2 publish. - * - * @param[in] pMqttContext Initialized MQTT context. - * @param[in] packetId The ID of the publish packet. - * @param[in] qos 1 or 2. - * - * @return MQTTSuccess, MQTTNoMemory, or MQTTStateCollision. - */ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos ) @@ -827,14 +809,6 @@ MQTTStatus_t MQTT_ReserveState( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ -/** - * @brief Calculate the new state for a publish from its qos and operation type. - * - * @param[in] opType Send or Receive. - * @param[in] qos 0, 1, or 2. - * - * @return The calculated state. - */ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos ) { @@ -864,18 +838,6 @@ MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, /*-----------------------------------------------------------*/ -/** - * @brief Update the state record for a PUBLISH packet. - * - * @param[in] pMqttContext Initialized MQTT context. - * @param[in] packetId ID of the PUBLISH packet. - * @param[in] opType Send or Receive. - * @param[in] qos 0, 1, or 2. - * @param[out] pNewState Updated state of the publish. - * - * @return #MQTTBadParameter, #MQTTIllegalState, #MQTTStateCollision or - * #MQTTSuccess. - */ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, @@ -950,20 +912,6 @@ MQTTStatus_t MQTT_UpdateStatePublish( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ -/** - * @brief Update the state record for an ACKed publish. - * - * @param[in] pMqttContext Initialized MQTT context. - * @param[in] packetId ID of the ack packet. - * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP. - * @param[in] opType Send or Receive. - * @param[out] pNewState Updated state of the publish. - * - * @return #MQTTBadParameter if an invalid parameter is passed; - * #MQTTBadResponse if the packet from the network is not found in the records; - * #MQTTIllegalState if the requested update would result in an illegal transition; - * #MQTTSuccess otherwise. - */ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, uint16_t packetId, MQTTPubAckType_t packetType, @@ -1036,18 +984,6 @@ MQTTStatus_t MQTT_UpdateStateAck( MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ -/** - * @brief Get the packet ID of next pending PUBREL ack to be resent. - * - * This function will need to be called to get the packet for which a PUBREL - * need to be sent when a session is reestablished. Calling this function - * repeatedly until packet id is 0 will give all the packets for which - * a PUBREL need to be resent in the correct order. - * - * @param[in] pMqttContext Initialized MQTT context. - * @param[in,out] pCursor Index at which to start searching. - * @param[out] pState State indicating that PUBREL packet need to be sent. - */ uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState ) @@ -1114,13 +1050,6 @@ uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext, /*-----------------------------------------------------------*/ -/** - * @brief State to string conversion for state engine. - * - * @param[in] state The state to convert to a string. - * - * @return The string representation of the state. - */ const char * MQTT_State_strerror( MQTTPublishState_t state ) { const char * str = NULL; From 5bd5638dddc7d9d3149e974b4130180c7435e6a6 Mon Sep 17 00:00:00 2001 From: SarenaAWS <6563840+sarenameas@users.noreply.github.com> Date: Tue, 25 Aug 2020 23:23:35 -0700 Subject: [PATCH 656/844] Doxygen templates and instructions for writing docs for a new library (#1150) * Add doxygen templates and instructions for writing docs for a new library. * Make some fixes to the MQTT library doxygen. --- doc/doxygen_guide.md | 121 + doc/doxygen_templates/config.doxyfile | 2565 +++++++++++++++++ doc/doxygen_templates/layout.xml | 228 ++ doc/doxygen_templates/pages.txt | 108 + doc/doxygen_templates/style.css | 132 + docs/utest.md => doc/utest_guide.md | 0 libraries/standard/mqtt/README.md | 2 +- .../standard/mqtt/doc/doxygen/config.doxyfile | 15 +- .../standard/mqtt/doc/doxygen/layout.xml | 2 +- libraries/standard/mqtt/doc/doxygen/pages.txt | 2 +- libraries/standard/mqtt/include/mqtt.h | 4 +- 11 files changed, 3172 insertions(+), 7 deletions(-) create mode 100644 doc/doxygen_guide.md create mode 100644 doc/doxygen_templates/config.doxyfile create mode 100644 doc/doxygen_templates/layout.xml create mode 100644 doc/doxygen_templates/pages.txt create mode 100644 doc/doxygen_templates/style.css rename docs/utest.md => doc/utest_guide.md (100%) diff --git a/doc/doxygen_guide.md b/doc/doxygen_guide.md new file mode 100644 index 0000000000..4bd48c2b2c --- /dev/null +++ b/doc/doxygen_guide.md @@ -0,0 +1,121 @@ +# Creating Doxygen documentation for a new library +1. All supporting files were created with Doxygen version 1.8.20. Please download +from https://sourceforge.net/projects/doxygen/files/. + +1. Your library API should have each function, data type, and constant documented +according to the Doxygen format using **@brief** and **@param**. Doxygen will output +warnings if you are missing this. Please see the MQTT library for example documentation. + An example function: + ```C + /** + * @brief Function description. + * + * @param[in] input An input parameter. + * @param[out] output An output parameter. + * + * @return List of values returned. + * + * Example + * @code{c} + * status = MyFunction(input, output); + * @endcode + */ + /* @[declare_mylibrary_myfunction] */ + size_t MyLibrary_MyFunction( size_t input, size_t * output ); + /* @[declare_mylibrary_myfunction] */ + ``` + +1. Your library must have a **@file** command and **@brief** description in each +file for Doxygen to know to parse the file as part of the library. Please see the +MQTT library for examples. + An example from mqtt.h: + ```C + /** + * @file mqtt.h + * @brief User-facing functions of the MQTT 3.1.1 library. + */ + ``` + +1. Please associate each data type and constant in your library's public API to +a group so that it will appear in the custom Doxygen pages. + For each comment block add the following commands to associate your data types and constants: + + | Data Type | Command | + | --- | --- | + | constant | **@ingroup** _constants | + | function pointer | **@ingroup** _callback_types | + | enum | **@ingroup** _enum_types | + | struct | **@ingroup** _struct_types | + + + Some examples of grouped data types and constants in mqtt.h: + ```C + /** + * @ingroup mqtt_constants + * @brief Invalid packet identifier. + * + * Zero is an invalid packet identifier as per MQTT v3.1.1 spec. + */ + #define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) + + /** + * @ingroup mqtt_callback_types + * @brief Application provided callback to retrieve the current time in + * milliseconds. + * + * @return The current time in milliseconds. + */ + typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void ); + + /** + * @ingroup mqtt_enum_types + * @brief Values indicating if an MQTT connection exists. + */ + typedef enum MQTTConnectionStatus + { + MQTTNotConnected, /**< @brief MQTT Connection is inactive. */ + MQTTConnected /**< @brief MQTT Connection is active. */ + } MQTTConnectionStatus_t; + + /** + * @ingroup mqtt_struct_types + * @brief An element of the state engine records for QoS 1 or Qos 2 publishes. + */ + typedef struct MQTTPubAckInfo + { + uint16_t packetId; /**< @brief The packet ID of the original PUBLISH. */ + MQTTQoS_t qos; /**< @brief The QoS of the original PUBLISH. */ + MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */ + } MQTTPubAckInfo_t; + ``` + +1. Please add **/\* \@\[declare_\\] \*/** around each API function +so that Doxygen can copy the function signature to custom function pages. + + ```C + /* @[declare_mylibrary_myfunction] */ + size_t MyLibrary_MyFunction( size_t input, size_t * output ); + /* @[declare_mylibrary_myfunction] */ + ``` + +1. Please copy the all of the template pages to your library's doc/doxygen folder. +For our CI to detect files correctly, please keep the folder name as *doc/doxygen* +and all of the files names the same. + + ```console + cp -R /doc/doxygen_templates /doc/doxygen + ``` + +1. Search for "FIXME"s in /doc/doxygen/config.doxygen and update with +your library information. + +1. Search for "FIXME"s in /doc/doxygen/pages.txt and update with your +library's information. + +1. Generate Doxygen with the current working directory as the root of your library's repo: + + ```console + doxygen doc/doxygen/config.doxygen + ``` + + Fix all warnings. \ No newline at end of file diff --git a/doc/doxygen_templates/config.doxyfile b/doc/doxygen_templates/config.doxyfile new file mode 100644 index 0000000000..c73ab010bc --- /dev/null +++ b/doc/doxygen_templates/config.doxyfile @@ -0,0 +1,2565 @@ +# Doxyfile 1.8.20 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "FIXME: Short Name of library" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = "FIXME: Version of library" + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "FIXME: Long name of library" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/doxygen/output/ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +# NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# (including Cygwin) and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = doc/doxygen/layout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ./doc/doxygen \ + + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.h \ + *.txt + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +# CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +# CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the "-p" option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +# CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = ./doc/doxygen/style.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +# HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +# FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

+The libraries in this SDK are not dependent on any operating systems. +However, the demos for the libraries in this SDK are built and tested on a +Linux platform. This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. +

+ +@section build_prerequisites Prerequisites +@brief Prerequisites needed to run the demos. + +@pre +- CMake 3.13.0 or later and a C90 compiler. + +@pre +- A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. + - Linux system with POSIX sockets and timer APIs. (CI tests on Ubuntu 18.04).
+ - On Linux systems, installation of OpenSSL development libraries and header files, version 1.1.0 or later, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + - Although not a part of the C90 standard, stdint.h is required for fixed-width integer types (e.g int32_t). + +@section aws_iot_setup AWS IoT Account Setup +@brief Setting up AWS IoT to run demos. + +It is required to setup an AWS account and access the AWS IoT Console for running demos and tests. Follow the links to: +- [Setup an AWS account](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html). +- [Sign-in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) after setting up the AWS account. + +*Note: If using the Provisioning library, a fleet provisioning template, a provisioning claim, IoT policies and IAM policies need to be setup for the AWS account. Complete the steps to setup your device and AWS IoT account outlined [here](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#use-claim).* + +@section configuring_mutual_auth_demo Configuring the Mutual Auth Demo +@brief Passing configuration settings to run the mutual auth demo. + +- You can pass the following configuration settings as command line options in order to run the mutual auth demos: +@code{sh} +cmake .. -DAWS_IOT_ENDPOINT="aws-iot-endpoint" -DROOT_CA_CERT_PATH="root-ca-path" -DCLIENT_CERT_PATH="certificate-path" -DCLIENT_PRIVATE_KEY_PATH="private-key-path" +@endcode + +- In order to set these configurations manually, edit `demo_config.h` in `demos/mqtt/mqtt_demo_mutual_auth/` to `#define` the following: + + - Set `AWS_IOT_ENDPOINT` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. + + - Set `ROOT_CA_CERT_PATH` to the path of the root CA certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + + - Set `CLIENT_CERT_PATH` to the path of the client certificate downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + + - Set `CLIENT_PRIVATE_KEY_PATH` to the path of the private key downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). + +@section building_demo_commmandline Build Steps +@brief How to build the demo applications on the command-line. + +- While in the root directory of AWS IoT Device SDK C, create a build directory, then change to that build directory. +@code{sh} +mkdir build && cd build +@endcode + +- Run cmake while inside build directory. +@code{sh} +cmake .. +@endcode + +- Run this command to build the demos. +@code{sh} +make +@endcode + +

All demo executables can now be found in the `build/bin` directory and +should be run while inside that directory.

+ +@subsection docker_containers_for_demos Installing Docker Containers for Demos +@brief Alternative option of using Docker containers for running demos locally. + +Install Docker: + +@code{sh} +curl -fsSL https://get.docker.com -o get-docker.sh + +sh get-docker.sh +@endcode + +#### Installing Mosquitto Broker to run MQTT demos locally + +The following instructions have been tested on an Ubuntu 18.04 environment with Docker and OpenSSL installed. +
    +
  1. Download the official Docker image for Mosquitto.
  2. + +@code{sh} +docker pull eclipse-mosquitto:latest +@endcode + +
  3. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set to `localhost`.
  4. + +
  5. For TLS communication with Mosquitto broker, server and CA credentials need to be created. Use OpenSSL commands to generate the credentials for the Mosquitto server.
  6. + +- Generate CA key and certificate. Provide the Subject field information as appropriate. +@code{sh} +openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -keyout ca.key -out ca.crt +@endcode + +- Generate server key and certificate and sign with the CA cert. +@code{sh} + +openssl req -nodes -sha256 -new -keyout server.key -out server.csr + +openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 +@endcode + +
  7. Create a mosquitto.conf file to use port 8883 (for TLS communication) and providing path to the generated credentials.
  8. + +@code +port 8883 + +cafile /mosquitto/config/ca.crt +certfile /mosquitto/config/server.crt +keyfile /mosquitto/config/server.key + +# Use this option for TLS mutual authentication (where client will provide CA signed certificate) +#require_certificate true +tls_version tlsv1.2 +#use_identity_as_username true + +@endcode + +
  9. Run the docker container from the local directory containing the generated credential and mosquitto.conf files.
  10. + +@code{sh} +docker run -it -p 8883:8883 -v $(pwd):/mosquitto/config/ --name mosquitto-basic-tls eclipse-mosquitto:latest +@endcode + +
  11. Set `ROOT_CA_CERT_PATH` to the absolute path of the CA certificate created in step 3. for the local Mosquitto server.
  12. +
+ +*/ From 3303a347f217cb6e30926f1480565d44ea1ec7c7 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 11 Sep 2020 10:07:15 -0700 Subject: [PATCH 716/844] Improve formatting of the master README (#1219) * Improve formatting of the README * Fix ordering of bash comments * Add prereq for stdint.h --- README.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c822530118..13e600297d 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,11 @@ The libraries in this SDK are not dependent on any operating systems. However, t - CMake 3.13.0 or later and a C90 compiler. - A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. + - Linux system with POSIX sockets and timer APIs. (CI tests on Ubuntu 18.04). + - On Linux systems, installation of OpenSSL development libraries and header files, *version 1.1.0 or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + - Although not a part of the C90 standard, `stdint.h` is required for fixed-width integer types (e.g int32_t). -- On Linux systems, installation of OpenSSL development libraries and header files, *version 1.1.0 or later*, are required. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. - ### AWS IoT Account Setup @@ -42,11 +43,11 @@ It is required to setup an AWS account and access the AWS IoT Console for runnin - [Sign-in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) after setting up the AWS account. - + *Note: If using the Provisioning library, a fleet provisioning template, a provisioning claim, IoT policies and IAM policies need to be setup for the AWS account. Complete the steps to setup your device and AWS IoT account outlined [here](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#use-claim).* - + ### Configuring the mutual auth demos @@ -65,7 +66,7 @@ cmake .. -DAWS_IOT_ENDPOINT="aws-iot-endpoint" -DROOT_CA_CERT_PATH="root-ca-path - Set `CLIENT_PRIVATE_KEY_PATH` to the path of the private key downloaded when setting up the device certificate (or Provisioning Claim for Fleet Provisioning) in [AWS IoT Account Setup](https://github.com/aws/aws-iot-device-sdk-embedded-C/tree/v4_beta#aws-iot-account-setup). - + ### Build Steps @@ -105,17 +106,15 @@ docker pull eclipse-mosquitto:latest 2. `BROKER_ENDPOINT` defined in `demos/mqtt/mqtt_demo_basic_tls/demo_config.h` can now be set to `localhost`. 3. For TLS communication with Mosquitto broker, server and CA credentials need to be created. Use OpenSSL commands to generate the credentials for the Mosquitto server. - -Generate CA key and certificate. Provide the Subject field information as appropriate. ```shell +# Generate CA key and certificate. Provide the Subject field information as appropriate. openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -keyout ca.key -out ca.crt ``` -Generate server key and certificate and sign with the CA cert. ```shell - +# Generate server key and certificate. openssl req -nodes -sha256 -new -keyout server.key -out server.csr - +# Sign with the CA cert. openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 ``` From 002c38caf847858c0f0c588d6f1fe19d097bf3a1 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 11 Sep 2020 10:15:48 -0700 Subject: [PATCH 717/844] Add randomization in client ID of integration tests to avoid collision (#1214) --- integration-test/lexicon.txt | 2 +- integration-test/mqtt/mqtt_system_test.c | 110 +++++++++++++++++------ platform/lexicon.txt | 1 - platform/posix/retry_utils_posix.c | 2 +- 4 files changed, 85 insertions(+), 30 deletions(-) diff --git a/integration-test/lexicon.txt b/integration-test/lexicon.txt index 457bf220b8..bef6d97594 100644 --- a/integration-test/lexicon.txt +++ b/integration-test/lexicon.txt @@ -63,4 +63,4 @@ tls transportsend un unsuback -www +www \ No newline at end of file diff --git a/integration-test/mqtt/mqtt_system_test.c b/integration-test/mqtt/mqtt_system_test.c index f29cd6856d..83a86b8a66 100644 --- a/integration-test/mqtt/mqtt_system_test.c +++ b/integration-test/mqtt/mqtt_system_test.c @@ -25,9 +25,12 @@ * from a POSIX platform. */ +/* Standard header includes. */ #include #include #include +#include +#include /* Include config file before other non-system includes. */ #include "test_config.h" @@ -63,109 +66,123 @@ /** * @brief Length of MQTT server host name. */ -#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) +#define BROKER_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( BROKER_ENDPOINT ) - 1 ) ) /** * @brief A valid starting packet ID per MQTT spec. Start from 1. */ -#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) +#define MQTT_FIRST_VALID_PACKET_ID ( 1 ) /** * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec. */ -#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) +#define MQTT_PACKET_PINGREQ_SIZE ( 2U ) /** * @brief A packet type not handled by MQTT_ProcessLoop. */ -#define MQTT_PACKET_TYPE_INVALID ( 0U ) +#define MQTT_PACKET_TYPE_INVALID ( 0U ) /** * @brief Number of milliseconds in a second. */ -#define MQTT_ONE_SECOND_TO_MS ( 1000U ) +#define MQTT_ONE_SECOND_TO_MS ( 1000U ) /** * @brief Length of the MQTT network buffer. */ -#define MQTT_TEST_BUFFER_LENGTH ( 128 ) +#define MQTT_TEST_BUFFER_LENGTH ( 128 ) /** * @brief Sample length of remaining serialized data. */ -#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) +#define MQTT_SAMPLE_REMAINING_LENGTH ( 64 ) /** * @brief Subtract this value from max value of global entry time * for the timer overflow test. */ -#define MQTT_OVERFLOW_OFFSET ( 3 ) +#define MQTT_OVERFLOW_OFFSET ( 3 ) /** * @brief Sample topic filter to subscribe to. */ -#define TEST_MQTT_TOPIC "/iot/integration/test" +#define TEST_MQTT_TOPIC "/iot/integration/test" /** * @brief Sample topic filter 2 to use in tests. */ -#define TEST_MQTT_TOPIC_2 "/iot/integration/test2" +#define TEST_MQTT_TOPIC_2 "/iot/integration/test2" /** * @brief Length of sample topic filter. */ -#define TEST_MQTT_TOPIC_LENGTH ( sizeof( TEST_MQTT_TOPIC ) - 1 ) +#define TEST_MQTT_TOPIC_LENGTH ( sizeof( TEST_MQTT_TOPIC ) - 1 ) /** * @brief Sample topic filter to subscribe to. */ -#define TEST_MQTT_LWT_TOPIC "/iot/integration/test/lwt" +#define TEST_MQTT_LWT_TOPIC "/iot/integration/test/lwt" /** * @brief Length of sample topic filter. */ -#define TEST_MQTT_LWT_TOPIC_LENGTH ( sizeof( TEST_MQTT_LWT_TOPIC ) - 1 ) +#define TEST_MQTT_LWT_TOPIC_LENGTH ( sizeof( TEST_MQTT_LWT_TOPIC ) - 1 ) /** * @brief Size of the network buffer for MQTT packets. */ -#define NETWORK_BUFFER_SIZE ( 1024U ) +#define NETWORK_BUFFER_SIZE ( 1024U ) /** * @brief Client identifier for MQTT session in the tests. */ -#define TEST_CLIENT_IDENTIFIER "MQTT-Test" +#define TEST_CLIENT_IDENTIFIER "MQTT-Test" /** * @brief Length of the client identifier. */ -#define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) +#define TEST_CLIENT_IDENTIFIER_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER ) - 1u ) /** * @brief Client identifier for use in LWT tests. */ -#define TEST_CLIENT_IDENTIFIER_LWT "MQTT-Test-LWT" +#define TEST_CLIENT_IDENTIFIER_LWT "MQTT-Test-LWT" /** * @brief Length of LWT client identifier. */ -#define TEST_CLIENT_IDENTIFIER_LWT_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER_LWT ) - 1u ) +#define TEST_CLIENT_IDENTIFIER_LWT_LENGTH ( sizeof( TEST_CLIENT_IDENTIFIER_LWT ) - 1u ) + +/** + * @brief The largest random number to use in client identifier. + * + * @note Random number is added to MQTT client identifier to avoid client + * identifier collisions while connecting to MQTT broker. + */ +#define MAX_RAND_NUMBER_FOR_CLIENT_ID ( 999u ) + +/** + * @brief Maximum number of random number digits in Client Identifier. + * @note The value is derived from the #MAX_RAND_NUM_IN_FOR_CLIENT_ID. + */ +#define MAX_RAND_NUMBER_DIGITS_FOR_CLIENT_ID ( 3u ) /** * @brief Transport timeout in milliseconds for transport send and receive. */ -#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200U ) +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 200U ) /** * @brief Timeout for receiving CONNACK packet in milli seconds. */ -#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) /** * @brief Time interval in seconds at which an MQTT PINGREQ need to be sent to * broker. */ -#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 5U ) +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 5U ) /** * @brief Timeout for MQTT_ProcessLoop() function in milliseconds. @@ -173,12 +190,12 @@ * PUBLISH message and ack responses for QoS 1 and QoS 2 communications * with the broker. */ -#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 700U ) +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 700U ) /** * @brief The MQTT message published in this example. */ -#define MQTT_EXAMPLE_MESSAGE "Hello World!" +#define MQTT_EXAMPLE_MESSAGE "Hello World!" /** * @brief Packet Identifier generated when Subscribe request was sent to the broker; @@ -280,6 +297,15 @@ static MQTTPublishInfo_t incomingInfo; */ static uint8_t disconnectOnPacketType = MQTT_PACKET_TYPE_INVALID; +/** + * @brief Random number for the client identifier of the MQTT connection(s) in + * the test. + * + * Random number is used to avoid client identifier collisions while connecting + * to MQTT broker. + */ +static int clientIdRandNumber; + /** * @brief Sends an MQTT CONNECT packet over the already connected TCP socket. * @@ -366,6 +392,12 @@ static void establishMqttSession( MQTTContext_t * pContext, /* The network buffer must remain valid for the lifetime of the MQTT context. */ static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + /* Buffer for storing client ID with random integer. + * Note: Size value is chosen to accommodate both LWT and non-LWT client ID + * strings along with NULL character.*/ + char clientIdBuffer[ TEST_CLIENT_IDENTIFIER_LWT_LENGTH + + MAX_RAND_NUMBER_DIGITS_FOR_CLIENT_ID + 1u ] = { 0 }; + /* Setup the transport interface object for the library. */ transport.pNetworkContext = pNetworkContext; transport.send = Openssl_Send; @@ -392,15 +424,28 @@ static void establishMqttSession( MQTTContext_t * pContext, if( useLWTClientIdentifier ) { - connectInfo.pClientIdentifier = TEST_CLIENT_IDENTIFIER_LWT; - connectInfo.clientIdentifierLength = TEST_CLIENT_IDENTIFIER_LWT_LENGTH; + /* Populate client identifier for connection with LWT topic with random number. */ + connectInfo.clientIdentifierLength = + snprintf( clientIdBuffer, sizeof( clientIdBuffer ), + "%d%s", + clientIdRandNumber, + TEST_CLIENT_IDENTIFIER_LWT ); + connectInfo.pClientIdentifier = clientIdBuffer; } else { - connectInfo.pClientIdentifier = TEST_CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = TEST_CLIENT_IDENTIFIER_LENGTH; + /* Populate client identifier with random number. */ + connectInfo.clientIdentifierLength = + snprintf( clientIdBuffer, + sizeof( clientIdBuffer ), + "%d%s", clientIdRandNumber, + TEST_CLIENT_IDENTIFIER ); + connectInfo.pClientIdentifier = clientIdBuffer; } + LogDebug( ( "Created randomized client ID for MQTT connection: ClientID={%.*s}", connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier ) ); + /* The interval at which an MQTT PINGREQ needs to be sent out to broker. */ connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; @@ -710,6 +755,8 @@ static void resumePersistentSession() /* Called before each test method. */ void setUp() { + struct timespec tp; + /* Reset file-scoped global variables. */ receivedSubAck = false; receivedUnsubAck = false; @@ -731,6 +778,15 @@ void setUp() serverInfo.hostNameLength = BROKER_ENDPOINT_LENGTH; serverInfo.port = BROKER_PORT; + /* Get current time to seed pseudo random number generator. */ + ( void ) clock_gettime( CLOCK_REALTIME, &tp ); + + /* Seed pseudo random number generator with nanoseconds. */ + srand( tp.tv_nsec ); + + /* Generate a random number to use in the client identifier. */ + clientIdRandNumber = ( rand() % ( MAX_RAND_NUMBER_FOR_CLIENT_ID + 1u ) ); + /* Establish a TCP connection with the server endpoint, then * establish TLS session on top of TCP connection. */ TEST_ASSERT_EQUAL( OPENSSL_SUCCESS, Openssl_Connect( &networkContext, diff --git a/platform/lexicon.txt b/platform/lexicon.txt index b883d2d748..9c81724095 100644 --- a/platform/lexicon.txt +++ b/platform/lexicon.txt @@ -70,7 +70,6 @@ mynetworkrecvimplementation mynetworksendimplementation mytcpsocketcontext mytlscontext -nano nanosleep networkcontext nextjittermax diff --git a/platform/posix/retry_utils_posix.c b/platform/posix/retry_utils_posix.c index 99aaa5738d..6dc558d5d2 100644 --- a/platform/posix/retry_utils_posix.c +++ b/platform/posix/retry_utils_posix.c @@ -90,7 +90,7 @@ void RetryUtils_ParamsReset( RetryUtilsParams_t * pRetryParams ) /* Get current time to seed pseudo random number generator. */ ( void ) clock_gettime( CLOCK_REALTIME, &tp ); - /* Seed pseudo random number generator with nano seconds. */ + /* Seed pseudo random number generator with nanoseconds. */ srand( tp.tv_nsec ); /* Calculate jitter value using picking a random number. */ From 55ab955e34bf86632df5186ac9d9871f2ff961c1 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed <54290492+muneebahmed10@users.noreply.github.com> Date: Fri, 11 Sep 2020 10:30:06 -0700 Subject: [PATCH 718/844] Add migration guide for MQTT (#1213) * Add migration guide for MQTT --- docs/doxygen/migration/migration.txt | 3 + docs/doxygen/migration/mqtt.txt | 127 +++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 docs/doxygen/migration/mqtt.txt diff --git a/docs/doxygen/migration/migration.txt b/docs/doxygen/migration/migration.txt index 732e3d2d2e..e0ddd46715 100644 --- a/docs/doxygen/migration/migration.txt +++ b/docs/doxygen/migration/migration.txt @@ -2,6 +2,9 @@ @page migration_guide Migration Guide @brief Migration guide for v3 library to the 202009.00 release. +- @subpage mqtt_migration
+ @copybrief mqtt_migration + - @subpage shadow_migration
@copybrief shadow_migration */ diff --git a/docs/doxygen/migration/mqtt.txt b/docs/doxygen/migration/mqtt.txt new file mode 100644 index 0000000000..6d372c7872 --- /dev/null +++ b/docs/doxygen/migration/mqtt.txt @@ -0,0 +1,127 @@ +/** +@page mqtt_migration MQTT +@brief How to migrate an MQTT application from v3 to 202009.00. + +The MQTT library has been refactored in 202009.00 to use only a single thread, and therefore no longer has any thread synchronization dependencies. New features such as persistent session support and QoS 2 publish delivery have also been added. + +The features of the MQTT libraries in v3 and 202009.00 are defined by the common MQTT 3.1.1 spec; therefore, the two versions are similar. + +@section migration_mqtt_removed Removed features +@brief The following features were present in v3, but removed in 202009.00: +- Auto-reconnect
+ Version 3 has an auto-reconnect feature triggered through a call to `aws_iot_mqtt_yield`. In version 202009.00, the auto-reconnect feature has been removed.
+ Workaround: When a version 202009.00 API call does not return @ref MQTTSuccess, the application should re-establish + the underlying transport connection and then call @ref MQTT_Connect. We recommend that the application employ an exponential backoff strategy when re-establishing connections as explained in + the [AWS blog for Exponential Back off and Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/). +- Thread support
+ Version 3 has an `_ENABLE_THREAD_SUPPORT_` macro for multi-threading use cases. Version 202009.00 does not spawn any threads and therefore, the macro `_ENABLE_THREAD_SUPPORT_` has been removed. + +@section migration_mqtt_datatypes Data Types +@brief The following table lists equivalent data types in v3 and 202009.00. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 202009.00Notes
enum QoS@ref MQTTQoS_tMQTT Quality of service.
AWS_IoT_Client@ref MQTTContext_tMQTT connection handle.
IoT_Publish_Message_Params@ref MQTTPublishInfo_tParameters of an MQTT publish.
IoT_MQTT_Will_Options@ref MQTTPublishInfo_tOptional Will parameter of an MQTT connect.
IoT_Client_Connect_Params@ref MQTTConnectInfo_tParameters of an MQTT connect.
IoT_Client_Init_ParamsNoneThe members of this struct in v3 handled setup of the network connection.
This is handled outside of the MQTT library in 202009.00.
pApplicationHandler_t@ref MQTTEventCallback_tFunction pointer of application callback to receive incoming data.
+ +@section migration_mqtt_functions Functions +@brief The following table lists equivalent API functions in v3 and 202009.00. These functions are the API functions declared in: +- In v3: aws_iot_mqtt_client_interface.h +- In 202009.00: [MQTT functions](@ref mqtt_functions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 3Version 202009.00Notes
aws_iot_mqtt_init@ref MQTT_Init
aws_iot_mqtt_freeNone202009.00 does not allocate any memory and therefore, has nothing to free.
aws_iot_mqtt_connect@ref MQTT_Connect
aws_iot_mqtt_publish@ref MQTT_Publish
aws_iot_mqtt_subscribe@ref MQTT_Subscribe
aws_iot_mqtt_resubscribeNoneFunction removed because auto-reconnect was removed.
aws_iot_mqtt_unsubscribe@ref MQTT_Unsubscribe
aws_iot_mqtt_disconnect@ref MQTT_Disconnect
aws_iot_mqtt_yield@ref MQTT_ProcessLoop@ref MQTT_ReceiveLoop is the equivalent function without any keep-alive mechanism.
aws_iot_mqtt_attempt_reconnectNoneFunction removed because auto-reconnect was removed.
+*/ From 3b931570561d52597265455c11c379bd084e7b3c Mon Sep 17 00:00:00 2001 From: dixit Date: Fri, 11 Sep 2020 11:13:05 -0700 Subject: [PATCH 719/844] feat: Add manifest file. --- manifest.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 manifest.yml diff --git a/manifest.yml b/manifest.yml new file mode 100644 index 0000000000..0103674e5f --- /dev/null +++ b/manifest.yml @@ -0,0 +1,26 @@ +name : "AWS_IoT_Device_SDK_for_Embedded_C" # Required project name. Maximum of 50 characters. No spaces allowed (replace with "_"). +version: "202009.00" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. +description: |- # Required project description. Maximum of 255 character. + "The AWS IoT Device SDK for Embedded C is a collection of C source files + that can be used in embedded applications to securely connect to the + AWS IoT platform and interact with AWS IoT services on AWS Cloud. \n + \n + See dependencies for included libraries." +dependencies: # Optional. An array of all dependencies. + - name: "coreMQTT" # Required. Maximum of 50 characters. No spaces allowed (replace with "_"). + version: "1.0.0" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. + repository: # Required if artifact does not exist in FreeRTOS Interactive registry. + type: "git" # Required. Only git is supported. Potential future support for zip, svn, or hg. + url: "https://github.com/FreeRTOS/coreMQTT" # Required. Maximum of 255 characters. + - name: "coreJSON" # Required. Maximum of 50 characters. No spaces allowed (replace with "_"). + version: "1.0.0" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. + repository: # Required if artifact does not exist in FreeRTOS Interactive registry. + type: "git" # Required. Only git is supported. Potential future support for zip, svn, or hg. + url: "https://github.com/FreeRTOS/coreJSON" # Required. Maximum of 255 characters. + - name: "Device-Shadow-for-AWS-IoT-embedded-sdk" # Required. Maximum of 50 characters. No spaces allowed (replace with "_"). + version: "1.0.0" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. + repository: # Required if artifact does not exist in FreeRTOS Interactive registry. + type: "git" # Required. Only git is supported. Potential future support for zip, svn, or hg. + url: "https://github.com/aws/Device-Shadow-for-AWS-IoT-embedded-sdk" # Required. Maximum of 255 characters. + +license: "MIT" # Required. SPDX license ID of the library. \ No newline at end of file From 8509fb622875f704bb62e5b107261db6bd40b6cf Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Fri, 11 Sep 2020 11:43:05 -0700 Subject: [PATCH 720/844] Add Rule 2.3 to MISRA configuration (#1235) --- tools/coverity/misra.config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/coverity/misra.config b/tools/coverity/misra.config index 2ef1337996..7e5788ec84 100644 --- a/tools/coverity/misra.config +++ b/tools/coverity/misra.config @@ -18,6 +18,10 @@ deviation: "Directive 4.9", reason: "Allow inclusion of function like macros. Logging is done using function like macros." }, + { + deviation: "Rule 2.3", + reason: "Allow unused types. Library headers may define types intended for the application's use, but not used within the library files." + }, { deviation: "Rule 2.4", reason: "Allow unused tags. Some compilers warn if types are not tagged." From 8d971d62ddba93ac0f43062de46b2ba86e5dc743 Mon Sep 17 00:00:00 2001 From: dixit Date: Fri, 11 Sep 2020 11:48:57 -0700 Subject: [PATCH 721/844] Add manifest file for CSDK --- manifest.yml | 50 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/manifest.yml b/manifest.yml index 0103674e5f..7d3201d8ac 100644 --- a/manifest.yml +++ b/manifest.yml @@ -1,26 +1,36 @@ -name : "AWS_IoT_Device_SDK_for_Embedded_C" # Required project name. Maximum of 50 characters. No spaces allowed (replace with "_"). -version: "202009.00" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. -description: |- # Required project description. Maximum of 255 character. +name : "AWS_IoT_Device_SDK_for_Embedded_C" +version: "202009.00" +description: |- "The AWS IoT Device SDK for Embedded C is a collection of C source files that can be used in embedded applications to securely connect to the AWS IoT platform and interact with AWS IoT services on AWS Cloud. \n \n See dependencies for included libraries." -dependencies: # Optional. An array of all dependencies. - - name: "coreMQTT" # Required. Maximum of 50 characters. No spaces allowed (replace with "_"). - version: "1.0.0" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. - repository: # Required if artifact does not exist in FreeRTOS Interactive registry. - type: "git" # Required. Only git is supported. Potential future support for zip, svn, or hg. - url: "https://github.com/FreeRTOS/coreMQTT" # Required. Maximum of 255 characters. - - name: "coreJSON" # Required. Maximum of 50 characters. No spaces allowed (replace with "_"). - version: "1.0.0" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. - repository: # Required if artifact does not exist in FreeRTOS Interactive registry. - type: "git" # Required. Only git is supported. Potential future support for zip, svn, or hg. - url: "https://github.com/FreeRTOS/coreJSON" # Required. Maximum of 255 characters. - - name: "Device-Shadow-for-AWS-IoT-embedded-sdk" # Required. Maximum of 50 characters. No spaces allowed (replace with "_"). - version: "1.0.0" # Required project version. Can support calendar or semantic versioning. Maximum of 20 characters. - repository: # Required if artifact does not exist in FreeRTOS Interactive registry. - type: "git" # Required. Only git is supported. Potential future support for zip, svn, or hg. - url: "https://github.com/aws/Device-Shadow-for-AWS-IoT-embedded-sdk" # Required. Maximum of 255 characters. +dependencies: + - name: "coreMQTT" + version: "1.0.0" + repository: + type: "git" + url: "https://github.com/FreeRTOS/coreMQTT" + - name: "coreHTTP" + version: "1.0.0" + repository: + type: "git" + url: "https://github.com/FreeRTOS/coreHTTP" + - name: "coreJSON" + version: "1.0.0" + repository: + type: "git" + url: "https://github.com/FreeRTOS/coreJSON" + - name: "Device-Shadow-for-AWS-IoT-embedded-sdk" + version: "1.0.0" + repository: + type: "git" + url: "https://github.com/aws/Device-Shadow-for-AWS-IoT-embedded-sdk" + - name: "Cmock" + version: "2.5.2" + repository: + type: "git" + url: "https://github.com/ThrowTheSwitch/CMock" -license: "MIT" # Required. SPDX license ID of the library. \ No newline at end of file +license: "MIT" \ No newline at end of file From c14b6067bdf15862c7d3d2b63b7745e07ca9d166 Mon Sep 17 00:00:00 2001 From: Oscar Michael Abrina Date: Fri, 11 Sep 2020 12:50:17 -0700 Subject: [PATCH 722/844] Add developer style guide to doxygen documentation (#1205) * Add developer style guide to doxygen docs * Fix doxygen warnings in style guide * Address PR comments * Fix some typos * Address PR comments * Use singular nouns for names and fix some wording * Update to use the dox extension for any documentation * Update build guide to use .dox extension as well Co-authored-by: SarenaAWS <6563840+sarenameas@users.noreply.github.com> --- docs/doxygen/{building.txt => building.dox} | 0 docs/doxygen/config.doxyfile | 7 +- docs/doxygen/developer.dox | 8 + docs/doxygen/developer/style.dox | 312 ++++++++++++++++++++ 4 files changed, 326 insertions(+), 1 deletion(-) rename docs/doxygen/{building.txt => building.dox} (100%) create mode 100644 docs/doxygen/developer.dox create mode 100644 docs/doxygen/developer/style.dox diff --git a/docs/doxygen/building.txt b/docs/doxygen/building.dox similarity index 100% rename from docs/doxygen/building.txt rename to docs/doxygen/building.dox diff --git a/docs/doxygen/config.doxyfile b/docs/doxygen/config.doxyfile index 56d946aeb4..a359e85795 100644 --- a/docs/doxygen/config.doxyfile +++ b/docs/doxygen/config.doxyfile @@ -270,6 +270,9 @@ TAB_SIZE = 4 # a double escape (\\{ and \\}) ALIASES = +# Aliases for tables on the Style Guide page. +ALIASES += formattable{1}="This table contains the formats for the names of the most common SDK \1s. It is not intended to be comprehensive. Bold text indicates a variable but required part of the \1's name.^^^^| Format | Applies to | Example |^^| ------ | ---------- | ------- |" +ALIASES += formattableentry{3}="| \1 | \2 | \3 |" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -846,6 +849,7 @@ WARN_LOGFILE = INPUT = ./docs/doxygen \ ./docs/doxygen/migration \ + ./docs/doxygen/developer \ ./platform/include # This tag can be used to specify the character encoding of the source files @@ -875,7 +879,8 @@ INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c \ *.h \ - *.txt + *.txt \ + *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. diff --git a/docs/doxygen/developer.dox b/docs/doxygen/developer.dox new file mode 100644 index 0000000000..bf06c14296 --- /dev/null +++ b/docs/doxygen/developer.dox @@ -0,0 +1,8 @@ +/** +@page guide_developer Developer's Guide +@brief Guide for maintaining and contributing code to this project. + +This guide contains the following pages. All pages assume the reader has intermediate familiarity with this SDK. +- @subpage guide_developer_styleguide
+ @copybrief guide_developer_styleguide +*/ diff --git a/docs/doxygen/developer/style.dox b/docs/doxygen/developer/style.dox new file mode 100644 index 0000000000..c331931da1 --- /dev/null +++ b/docs/doxygen/developer/style.dox @@ -0,0 +1,312 @@ +/** +@page guide_developer_styleguide Style Guide +@brief Guide for the coding style used in this SDK. + +The goal of this style guide is to encourage a readable and consistent coding style across the entire SDK. + +@section guide_developer_styleguide_codingstyle Coding Style +@brief The coding style used in this SDK. + +The coding style aims to produce code that is readable and easy to debug. An example is provided in @ref guide_developer_styleguide_codingstyle_example. + +@subsection guide_developer_styleguide_codingstyle_generalguidelines General guidelines +@brief General guidelines for library style. +- All libraries should only use [ANSI C](https://en.wikipedia.org/wiki/ANSI_C#C89) features and standard library functions. +- Libraries should log extensively, but exceptions are made for pure functions. +- Code should be well-commented. +- Only `/`* and *`/` should be used to start and end comments. +- All comments end with a period. +- Use of `goto` is discouraged. +- Only spaces should be used for indenting. A single indent is 4 spaces. No tab characters should be used. +- A parenthesis is usually followed by a space (see @ref guide_developer_styleguide_codingstyle_example). +- Lines of code should be less than 80 characters long, although longer lines are permitted. +- Local variables should be declared at the top of a block in the narrowest scope possible. Reducing complexity could be a valid case for breaking this guideline, so this should be done at the programmer's discretion. +- All global variables should be declared at the top of a file. +- Variables are always initialized. +- A separator is placed between different sections of a file. The current separator is: +@code{c} +/*-----------------------------------------------------------*/ +@endcode +- All files must include the config file at the top of the file before any other includes. +- `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. +- The [GNU complexity](https://www.gnu.org/software/complexity/manual/complexity.html) of every function should be less than 9. +- Deviations from [MISRA C:2012](https://en.wikipedia.org/wiki/MISRA_C) coding guidelines should be documented as comments in the code. + +@subsection guide_developer_styleguide_codingstyle_typeguidelines Type guidelines +@brief Guidelines for variable types. +- Although not a part of the C89 standard, only fixed-width integer types should be used. Exceptions are for `bool` and types required by third-party APIs. +- The default integer in the libraries should be 32 bits wide, i.e. `int32_t` or `uint32_t`. +- Sizes and lengths should be represented with `size_t`. +- Libraries may define `bool` macros for use with C89 compilers. + +@section guide_developer_styleguide_codingstyle_example Example File +@brief An example file that follows the coding style rules. + +See @ref guide_developer_styleguide_naming for how to name the functions, variables, and macros. + +@code{c} +/* + * License header pasted here. + */ + +/** + * @file example_file.c + * @brief An example of how source files are typically written in this SDK. + */ + +/* Included headers are at the top of the file. The config file include is always first. */ +#include "config.h" + +/* Standard includes are immediately after the config file. They are sorted alphabetically. + * They use angle brackets <> around the file name. */ + +/* Standard includes. */ +#include +#include +#include + +/* This file defines `bool` for compatibility with C89 compilers. */ +#if defined( __cplusplus ) || ( defined( __STDC_VERSION__ ) && ( __STDC_VERSION__ >= 199901L ) ) + #include +#elif !defined( bool ) && !defined( false ) && !defined( true ) + #define bool int8_t + #define false ( int8_t ) 0 + #define true ( int8_t ) 1 +#endif + +/* Library internal headers are included next. They use quotes "" around the file name. */ + +/* Library internal include. */ +#include "private/library_internal.h" + +/* Error handling include (include only when needed). */ +#include "private/error.h" + +/* Library application-facing headers are included last. They use quotes "" around the file name. */ + +/* Library include. */ +#include "library.h" + +/*-----------------------------------------------------------*/ + +/* Defined constants follow the included headers. */ + +/* When possible, parentheses () should be placed around constant values and a type + * should be specified. */ +#define LIBRARY_CONSTANT ( ( int32_t ) 10 ) + +#define LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ +{ + \ /* Function-like macros are surrounded by curly braces {}. */ + macro_body( ( argument ) ); + \ /* Parentheses surround macro arguments for MISRA 20.7 */ +} + +/*-----------------------------------------------------------*/ + +/* Library typedefs follow the defined constants. */ + +/* Forward declarations are used only when necessary. They are placed before all + * other typedefs. */ + +typedef int32_t type_t; + +/** + * @brief Structs are named along with the typedef. + */ +typedef struct structType +{ + int32_t member; + + /* As usage of unions violates MISRA rule 19.2, we must document our usage + * and justification. Unions are used here to reduce the size of this struct. */ + union /* Anonymous structs/unions are permitted only inside of other structs. */ + { + int8_t a[ 4 ]; + int32_t b; + }; +} structType_t; + +/*-----------------------------------------------------------*/ + +/* Declarations of static and extern functions follow the typedefs. */ + +static bool libraryStaticFunction( void * pArgument, + size_t argumentLength ); + +/* External function declarations should be used sparingly (using an internal + * header file to declare functions is preferred). */ +extern int32_t Library_ExternalFunction( void * pArgument ); + +/*-----------------------------------------------------------*/ + +/* Declarations of global variables follow the static and extern function + * declarations. Global variables are permitted, but should be avoided when + * possible. */ + +/* Global variables are always initialized. */ +static int globalVariable = 0; +static int pGlobalArray[ LIBRARY_CONSTANT ] = { 0 }; + +/*-----------------------------------------------------------*/ + +/* Implementations of static functions follow the global variable declarations. */ + +static bool libraryStaticFunction( void * pArgument, + size_t argumentLength ) +{ + int32_t * pLocalPointer = ( int32_t * ) pArgument; + bool status = true; + + /* All functions make generous use of the logging library. */ + LogInfo( "Performing calculation..." ); + + if( ( pArgument == NULL ) || ( argumentLength == 0 ) ) /* Note the parentheses and spacing in if statements */ + { + LogError( "Bad parameters." ); + + /* Local variable instead of a goto for error handling. */ + status = false; + } + else /* Note an else clause is used instead of goto for error handling */ + { + /* Local variables should be declared at the top of a block in the narrowest scope possible. */ + int32_t localVariable = 0; + size_t i; + + for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ + { + localVariable += Library_ExternalFunction( pArgument ); + + LogDebug( "Current value is %d.", ( int ) localVariable ); + } + + if( localVariable < 0 ) + { + LogWarn( "Failed to calculate positive value." ); + } + + LogInfo( "Calculation done." ); + } + + return status; +} + + +/* A separator is placed between all function implementations. */ +/*-----------------------------------------------------------*/ + +/* Implementations of application-facing functions are at the bottom of the file. */ + +bool Library_ApplicationFunction( void ) /* Functions with no arguments have void in their argument list. */ +{ + LIBRARY_FUNCTION_MACRO( globalArray ); + + return true; +} + +/* Separator and newline at end of file */ +/*-----------------------------------------------------------*/ + +@endcode + +@section guide_developer_styleguide_naming Naming +@brief Naming convention used in this SDK. + +The naming convention aims to differentiate this SDK's files, variables, and functions to avoid name collisions. In general: +- The first characters of all publicly visible names should identify the name as part of this SDK.
+ Example: For general-purpose libraries (such as MQTT), names start with _ (i.e. MQTT_ for the MQTT API). +- Words in names should be ordered with the most general word first and the most specific word last.
+ Example: `core_mqtt_serializer.c` identifies a file as part of the general MQTT library. `MQTT_Connect` identifies a public-facing function of the general MQTT library. +- Names should avoid using abbreviations. + +@subsection guide_developer_styleguide_naming_definedconstantsandenumvalues Defined constants and enum values +@brief Naming convention for constants set using preprocessor `#define` and enum values. + +@formattable{defined constant and enum value} +@formattableentry{LibraryDescription,Defined constants and enum values in application-facing library header files,`MQTTSuccess` (core_mqtt_lightweight.h)} +@formattableentry{LIBRARY_DESCRIPTION,Defined constants and enum values in demos and tests,`MQTT_EXAMPLE_TOPIC`} +@formattableentry{DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal names in AWS-specific libraries,`ROOT_CA_CERT_PATH`} + +@subsection guide_developer_styleguide_naming_files Files +@brief Naming convention for files. + +@formattable{file} +@formattableentry{library`_`description`.extension`,General library file,`core_mqtt_serializer.c`} +@formattableentry{library`_internal.h`,Internal library header,`core_mqtt_internal.h`} +@formattableentry{library`_demo_`description`.c`,Library demo source,`mqtt_demo_plaintext.c`} +@formattableentry{library_description`_utest.c`
library_description`_system_test.c`,Library test source,`core_mqtt_utest.c`
`mqtt_system_test.c`} + +File names contain only lowercase letters and underscores. All file names should be named according to their purpose. For example: +- `core_mqtt_state.c`: A file in the MQTT library that handles state of MQTT PUBLISH packet deliveries. +- `mqtt_demo_plaintext.c`: A file in the demos for the MQTT library that communicates over a plaintext channel. +- `core_mqtt_utest.c`: A file in the Tests for the MQTT library. Since the tests currently only run on POSIX systems, test file names do not use the `_posix` suffix. + +Library file names should use one or two words to describe the functions implemented in that file. For example: +- `core_mqtt_serializer.c`: Implements the MQTT library's packet serialization and deserialization functions. +- `clock_posix.c`: Implements the platform clock component for POSIX systems. + +Declarations of internal functions, structures, macros, etc. of a library should be placed in a header file with an `_internal` suffix. The `_internal` header file should go in the `source/include/private` directory. For example: +- `core_mqtt_internal.h`: Declares the MQTT library's internal functions, structures, macros, etc. + +

File names for demos should begin with `library_demo_`. +Unit tests must start with `library_` and end with `_utest`. +System tests must start with `library_` and end with `_system_test`. +The names should then specify the library being demoed or tested. +For example, the files names of the MQTT library's demos start with `mqtt_demo_`. +Additionally, test file names should describe what tests are implemented in the file, +such as `mqtt_demo_mutual_auth.c` for a file containing a demo that mutually +authenticates over TLS with an MQTT broker.

+ +@subsection guide_developer_styleguide_naming_functions Functions (and function-like macros) +@brief Naming convention of functions and function-like macros. + +@formattable{function} +@formattableentry{Library`_`Description,Externally-visible library function,`MQTT_Publish`} +@formattableentry{description,`static` function (never uses `Aws` prefix),`sendPublish`
`eventCallback`} +@formattableentry{`test_`Library`_`accessedFunction_Description,Test access function,`test_MQTT_ProcessLoop_Timer_Overflow`} + +Externally visible (i.e. not `static`) functions are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case). Function names should then specify their library name, followed by an underscore, followed by a brief description of what the function does. For example: +- `MQTT_SerializePublish`: This function is part of the public MQTT API. It serializes an MQTT PUBLISH packet into a buffer. + +Functions not visible outside their source file (i.e. `static` functions) have names that are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). These function names do not contain the library name or `Aws`. For example: +- `receiveSingleIteration`: A `static` function in `core_mqtt.c`. + +@subsection guide_developer_styleguide_naming_types Types +@brief Naming conventions of library `typedef` types. + +@formattable{type} +@formattableentry{LibraryDescription`_t`,General types in application-facing library header files,`MQTTStatus_t` (core_mqtt.h)} + +Types intended for use in applications are [UpperCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names end with `_t`. Parameter structures should indicate their associated function: for example, `MQTTPublishInfo_t` is passed as a parameter to `MQTT_Publish`. + +Types intended for internal library use defined in a header file are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case). The names must start with the library name and end with `_t`. Internal types defined in a library source file must end with `_t` and not include the library name. + +`struct` typedefs should always be named along with the `typedef`. The struct name should be identical to the `typedef` name, but without the `_t` suffix. For example: +@code{c} +typedef struct LibraryStruct +{ + int32_t member; +} LibraryStruct_t; + +typedef struct libraryInternalStruct +{ + int32_t member; +} libraryInternalStruct_t; +@endcode + +A `struct` may contain `union` members, but its usage must be documented as it violates MISRA rule 19.2. + +@subsection guide_developer_styleguide_naming_variables Variables +@brief Naming conventions of variables. + +@formattable{variable} +@formattableentry{variableDescription,General local variable,`startTime`} +@formattableentry{`p`VariableDescription,Variable pointers and arrays (including strings),`pSubscriptionList`} + +Local variable names are [lowerCamelCased](https://en.wikipedia.org/wiki/Camel_case) and consist only of a description of the variable. Names like `i` or `j` are acceptable for loop counters, but all other variables should have a descriptive name. + +Global variables that are `static` consist of only the description in [lowerCamelCase](https://en.wikipedia.org/wiki/Camel_case). Global variables that are not static consist of the library name and the description in [UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case). + +All pointers, arrays, and strings (both global and local) start with `p`. +*/ From 33b915d17d49dc8065048085ade30a5a65c6ccf7 Mon Sep 17 00:00:00 2001 From: Sukhmani Minhas <50919130+sukhmanm@users.noreply.github.com> Date: Fri, 11 Sep 2020 15:58:13 -0400 Subject: [PATCH 723/844] Add demo documentation and workflow diagrams (#1224) - Doxygen documentation for all demos under demos/mqtt. - Diagrams for all demos under demos/mqtt Co-authored-by: SarenaAWS <6563840+sarenameas@users.noreply.github.com> --- docs/doxygen/config.doxyfile | 7 +- docs/doxygen/demos/demos_main.txt | 16 + docs/doxygen/demos/demos_subpages.txt | 54 + docs/doxygen/demos/images/basic_tls.PNG | Bin 0 -> 420279 bytes docs/doxygen/demos/images/demo_diagrams.xml | 2055 +++++++++++++++++ docs/doxygen/demos/images/mutual_auth.PNG | Bin 0 -> 389302 bytes docs/doxygen/demos/images/plaintext.PNG | Bin 0 -> 367524 bytes docs/doxygen/demos/images/serializer.PNG | Bin 0 -> 438666 bytes .../demos/images/subscription_manager.PNG | Bin 0 -> 973418 bytes 9 files changed, 2131 insertions(+), 1 deletion(-) create mode 100644 docs/doxygen/demos/demos_main.txt create mode 100644 docs/doxygen/demos/demos_subpages.txt create mode 100644 docs/doxygen/demos/images/basic_tls.PNG create mode 100644 docs/doxygen/demos/images/demo_diagrams.xml create mode 100644 docs/doxygen/demos/images/mutual_auth.PNG create mode 100644 docs/doxygen/demos/images/plaintext.PNG create mode 100644 docs/doxygen/demos/images/serializer.PNG create mode 100644 docs/doxygen/demos/images/subscription_manager.PNG diff --git a/docs/doxygen/config.doxyfile b/docs/doxygen/config.doxyfile index a359e85795..98fc130127 100644 --- a/docs/doxygen/config.doxyfile +++ b/docs/doxygen/config.doxyfile @@ -848,6 +848,7 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = ./docs/doxygen \ + ./docs/doxygen/demos \ ./docs/doxygen/migration \ ./docs/doxygen/developer \ ./platform/include @@ -949,7 +950,11 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = ./docs/doxygen/demos/images/basic_tls.png \ + ./docs/doxygen/demos/images/mutual_auth.png \ + ./docs/doxygen/demos/images/plaintext.png \ + ./docs/doxygen/demos/images/serializer.png \ + ./docs/doxygen/demos/images/subscription_manager.png # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/docs/doxygen/demos/demos_main.txt b/docs/doxygen/demos/demos_main.txt new file mode 100644 index 0000000000..138a58a9b2 --- /dev/null +++ b/docs/doxygen/demos/demos_main.txt @@ -0,0 +1,16 @@ +/** +@page demos_main Demos +@brief Description of demos used to illustrate various functionalities of MQTT libraries. They demonstrate the use of MQTT APIs to establish an MQTT session, subscribe to a topic filter, publish to a topic, receive incoming publishes, unsubscribe from a topic and disconnect the MQTT session. + +The following demos are provided: +- @subpage mqtt_demo_basic_tls
+ @copybrief mqtt_demo_basic_tls +- @subpage mqtt_demo_mutual_auth
+ @copybrief mqtt_demo_mutual_auth +- @subpage mqtt_demo_plaintext
+ @copybrief mqtt_demo_plaintext +- @subpage mqtt_demo_serializer
+ @copybrief mqtt_demo_serializer +- @subpage mqtt_demo_subscription_manager
+ @copybrief mqtt_demo_subscription_manager +*/ \ No newline at end of file diff --git a/docs/doxygen/demos/demos_subpages.txt b/docs/doxygen/demos/demos_subpages.txt new file mode 100644 index 0000000000..1b3fef3e94 --- /dev/null +++ b/docs/doxygen/demos/demos_subpages.txt @@ -0,0 +1,54 @@ +/** +@page mqtt_demo_basic_tls Basic TLS Demo +@brief Demo of an MQTT application that establishes a TLS connection with server-only authentication, and uses QoS 2 level of communication with broker. + +This demo uses an OpenSSL-based transport interface implementation to establish a server-authenticated TLS connection, and demonstrates the subscribe-publish workflow of MQTT at QoS 2 level. After subscribing to a single topic filter, it publishes to the same topic and waits for receipt of that message back from the server at QoS 2 level. This cycle of publishing to the broker and receiving the same message back from the broker is repeated indefinitely. + +Messages in this demo are sent at QoS 2, which guarantees exactly one delivery according to the MQTT spec. See the demo workflow below: + +@image html basic_tls.png width=100% +*/ + +/** +@page mqtt_demo_mutual_auth Mutual Authentication Demo +@brief Demo of an MQTT application that establishes a TLS connection with both server and client authentication, and uses QoS 1 level of communication with broker. + +This demo uses an OpenSSL-based transport interface implementation to establish a server and client-authenticated TLS connection, and demonstrates the subscribe-publish workflow of MQTT at QoS 1 level. After subscribing to a single topic filter, it publishes to the same topic and waits for receipt of that message back from the server at QoS 1 level. This cycle of publishing to the broker and receiving the same message back from the broker is repeated indefinitely. + +Messages in this demo are sent at QoS 1, which guarantees at least one delivery according to the MQTT spec. See the demo workflow below: + +@image html mutual_auth.png width=100% +*/ + +/** +@page mqtt_demo_plaintext Plaintext Demo +@brief Demo of an MQTT application that establishes a plaintext (no encryption) TCP connection with the server, and uses QoS 0 level of communication with broker. + +This demo uses a POSIX socket-based transport interface implementation to establish a TCP connection, and demonstrates the subscribe-publish workflow of MQTT at Qos 0 level. After subscribing to a single topic filter, it publishes to the same topic and waits for receipt of that message to be returned from the server at QoS 0 level. This cycle of publishing to the broker and receiving the same message back from the broker is repeated indefinitely. + +Messages in this demo are sent at QoS 0, which guarantees at most one delivery according to the MQTT spec. See the demo workflow below: + +@image html plaintext.png width=100% +*/ + +/** +@page mqtt_demo_serializer Serializer Demo +@brief Demo of an MQTT application using only the MQTT serializer API to communicate at QoS 0 level with broker over a plaintext (no encryption) TCP connection. + +This demo uses POSIX sockets to establish a TCP connection, and demonstrates the subscribe-publish workflow of MQTT at QoS 0 level. After subscribing to a single topic filter, it publishes to the same topic and waits for receipt of that message to be returned from the server at QoS 0 level. It demonstrates use of the serializer API as a lightweight alternative to the standard MQTT API. This cycle of publishing to the broker and receiving the same message back from the broker is repeated indefinitely. + +Messages in this demo are sent at QoS 0, which guarantees at most one delivery according to the MQTT spec. See the demo workflow below: + +@image html serializer.png width=100% +*/ + +/** +@page mqtt_demo_subscription_manager Susbcription Manager Demo +@brief Demo of an MQTT application that subscribes to multiple topic filters using a subscription manager to manage multiple subscriptions, register different callbacks for each subscription, and handle wildcard topics. It establishes a TLS connection with server-only authentication, and communicates at QoS 1 level with the broker. + +This demo uses an OpenSSL-based transport interface implementation to establish a server-authenticated TLS connection, and demonstrates the subscribe-publish workflow of MQTT at QoS 1 level, using a subscription manager. Before subscribing to a topic filter, the application registers the filter's unique callback with the subscription manager. After subscription, it publishes to the same topic and waits for receipt of that message to be returned from the server at QoS 1 level. Once received, the subscription manager will invoke the callback registered for the topic filter that matches the topic of the incoming PUBLISH. This subscribe-publish process is repeated for 3 different topic filters, including one wildcard filter. This cycle of publishing to the broker and receiving the same message back from the broker is repeated indefinitely. + +Messages in this demo are sent at QoS 1, which guarantees at least one delivery according to the MQTT spec. See the demo workflow below: + +@image html subscription_manager.png width=100% +*/ \ No newline at end of file diff --git a/docs/doxygen/demos/images/basic_tls.PNG b/docs/doxygen/demos/images/basic_tls.PNG new file mode 100644 index 0000000000000000000000000000000000000000..85f802e0ecbc497940825e4cfaa5be9ed239dcd1 GIT binary patch literal 420279 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 +QH78CmqwEsfvq~T4 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 z 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�S_fdi;tc?6N-?-kROI4 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૪Z5`76}?pCP39O#eP`l8#4&^O|^J@VL5?&5yl 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


!o=?7NO#(a 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 S;HD7>#8A&fx9sAr$o~Mi4jqC3 literal 0 HcmV?d00001 diff --git a/docs/doxygen/demos/images/demo_diagrams.xml b/docs/doxygen/demos/images/demo_diagrams.xml new file mode 100644 index 0000000000..ed8f342e5c --- /dev/null +++ b/docs/doxygen/demos/images/demo_diagrams.xml @@ -0,0 +1,2055 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/doxygen/demos/images/mutual_auth.PNG b/docs/doxygen/demos/images/mutual_auth.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d1208fe554900de654178fcb17e0713b73ebe0a2 GIT binary patch literal 389302 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�dTJR1n7Bv^wecOHS_OD%5(Tl40$}7k-hq9?dwJ4p(F5b@&Mw2G)J@#S zrRPMY?h!U~fGKS%9Ce>U!88K8gqH`oi#A3#4dEf0&2=_~mOxFxx1+dyocH{`=5BQ< z@zO&%J&w(_$q%+Z8O^!y%v$4onoW1;g=P1+`9*`-yCln#`z&y&FYHD@$cdYfL!54D zDWz1yT$d$V)y;DnZG$h_g&yd_H_W~{oM5BjWMGQ4Q)V|zBfBKR8a>yh(130WJM#sg z`>O-QeD3G!jDp!)VPj=x3!dAxi^-bTI(%-R66aCN*Z~FiL$J$6U10B|-?jF-|Al#? z`}BZS%QS9fuOXZR=fE0kAiAZWntLLV%s2EDeEzK=V=|j5tGgg5ZhG}n$)?gd&z<{_ zZBOiF+gJ7Cy*#l6l3p+jkA`~k)4Qf~hS!~&I@C35a0+AMm;!6;8*v9#8#bKBP+rlW z8`Q48QRsbMjOd(`k5eR{B%e)3q>pX(64vgPIwzeKtY}E>2s^_T&Lj`n(y8>Zas<*f z_@tP%r~8U0cN=E_046X-k2bHZxWvz^;gjrOfmWYj>`JZVMjM&T?_E$q9fVl%z-YVr z(h)D4_zi7fmWlIY!k?L+DoX5I&|OBh3Bd=V#!Sq(0UrB;Cob*oOoY=-sdSi(O^lXk`=w08oNMwmD(P^M!P4Ytpo<$jV7VobB6J_UeL<*I>Obbi%hz|b~Hz=7=xqh1Wrge^=OE(IFBQB5Iq zj7%C%E>gp&y480s+a%=nS^dWbv+NCRui&qroZir8c>1ffff)C91-q}5J{>Tov+;_X z)>C_|hEWD`I0`OY>&#-Uq5KCfClV*?`<}53>J>yL=71eMX6} zOUhu~zeiDd!uMsuw~Q$a%W>uIZ1ua%ZE6fWm8Tm0?D~mPh7Nq{6J8>v0(>v**@U(9 zcMF=`sMPH+j5C|r9o4rid(Aa)<3ELZoNxc)Kg?b^N7eZtf`w+(fvHmGlxvMn?1S*5 zIZtvY*ZBgX7uG=(9J_Fs1xvh^CR&&SNPvocrNbh2v_G|Rqes6JJb2M{I({TDk_xDk zs<^^`cd^w3*hQlD@K+8f3v|Tdtow4)MZ)-5}zvL@ImDJ zeTO07B_MP^TsGwV+d}qe*f*kABXzPCXkK z?E5?+Q)QaRdf_qalv~A{S&q5Q&OAlGyx(%R@lu1Nsi$4}OBp|Dc{5@OdSO!f7<=76 z6I+P1Cd1AWo44=!Cbj$4U3^)}F_R0jnTzvC3i&F0n$)Fr=acaK`2$rMba?XCi9nK_ z+Z_C;NW2dx+{sflEF~HXjB{Q3LOO)grH+#|88=fFmSNqzDE~d0#I8E>!_6!wIi$_0 z^DGZ?1V}6!GxNUqsPCZ%+lh=KK9^l>9~#cR6Y6b+JHE>VaI2DsPCaA^-nf?Qv>b;A zL$ZNNeQJPQS-HA7IXJxCsZeviVz{RMP!uyi4n`l0gJf(6;mba_ELVrq%{X07Za?Z+ zUcP$SBhP8s9eC5bjcq`{uyVc+@5Sp4cw0OFLSg)RQzOWyd>vH#@$`j9OsTL^SBr*D z!5C?v_^48H#%ukowojEXQiRWp-&*cBR)0}lqL!nFf`US)``k!9-iK!38{?DcX2k8j z$i4HO!GPZ;O9{CREAufKgBd=;VcYdg)PBhB@OBoWY>+L}x@Px39lLg#WBS#?*$3?U z?)ERcjFlC&G%^Wvk6ff=1wNIZcQhhcCQ%#azVg-E2KlBwcB*vx`d(5qzyWTnI_ zd9xhV1O`VmL$Lb~uYazyp{>)oN^}-yJU@fH4uCynlIZmV{{ldxBYdqq>VU1q8}qLD zZn~V~;pfiEUMD>cRBxvPjrYDkyRTFc3-y!|IRHlYJQ4Ppcp$L+;qSzI%>eri`NhJ3 z@@soFnWP4@??(lueC_u1)aA=Qfu;(DZ_kkntvz1&~$2CfC_V#a8`Ps{l*DFGu zE+U0M{u-|_3F+d?Mp7_p$;Lh4BmyPQ@zhe;y-~Nuh}Y`s>O*DBnC+UymWANZ=MBeT zl|e-?j}yrr2a^$b04r3b-Ehc;?!X!~=O2w_X}W1UTM ztzegGuYsfbKp&*<&%^+o%8L{)<*iC#KT0a~Nq7099OmBK{QtG!YDeoGaSV^vV za?2mkM}1=(v@>*^A1ga6PlBuRsfmZLkEjC|xQQB9A<6Cc{u8(R=`4w!@2a@v8B4f0 z3zbB<40v>#pl>?+=1Hjqy+_Om+l%Uli*o!WC2eyp7SDFv1Y|ieBZGrERgwQPtT9dn zkKj0=QK(|pAc3gUN_m;~>4(w?$$ebM89>_@Pu9*SsfBdV(U?W{Wb(E<>0`Bi)-vsD zG6{d{TEx#ioi~Rs4Oms>d~S$U9^W6ZN%|X5vfo_?Ufu9hEi4>f4@1)6A|J8slQBTK zftL=!%2Ph>GR-h`i&J&(`p3@~yDt`oo$>>}dB@$m?Qx+)c~_#Aw{Yb5|Hx;0r%6S( ze>K}7YD|gXFq*W^6m|+2YeQ*weS7b*a^tQvtDAz6`k1PmL@H73zfsSxi$03p4st%; zOL963=sBC!727#Dcy;Gm`zJ6blu&yMKYaK|E#n!>v?OW6*vuFiS(@hR=-)Q&GNAz6 zk#H9%z-mg)zU!bc7I7`z@FPb7>t4X)_8E2D>Y9`4ApDu~&f>BL@BFL%xE)r?X`W3l z_t{;#bYt}n{G@`v7eHezBkc@Zc5OiCj`0M#(SJ+ZU9wSAWg1|Dn|=Lb;W~%c-wy(g z>qSpA@NsecVqyna{+~h^iQe4Y;^n`1M?*uyf4H3K(wS{;2L_MXGuQn$&C$kH%+oye zriCnCM;%*<>yfsqDtR1++NRbdP3WD3gajYe{{}tZDgRvKaNql{yQDg{ zW}WP?0E+ZcKORT{?svUAN9wU74n25U)Z_!WK%7PnC!~&G5&A*%<(V(p4ADZM7a|yK^#>nQ|N|Y#Ws&7t0iXm3sv;kT`fT1 zX1DqEtRudvj+y`Y(s~hR%B^>vY=I6|6E@EjA8-o!6wTd#{rpP4kK?_2MG1+C6~Vi~ z9%Oo_-n6;7Io2_haGlUzkX;L%ZGGcx2OiU0(3&QQYn>RRZm5Ue#57e}DC+nj0iWgCo+QTMMxwc^tF#7U zwL7v-cHIO{)W`CyI(Qg3V*gj|Xdt&f*fVvBKfS~x1Cqu19e@S@KFgV_+57Zx?j~ix z3IZuQyH$2{@A_g2n3mG$=;-&kJCEHd`_thjBDAz^ehi2meG8NnkF`|(D6eV?u{DIR z^_MyaGV{-r`;ec1Z%s(3bFz?6wxz(=t}M4vQvYSv@2`74xuNaB#?sqOTQv?Iyvqf? zq&XT}{MBal;H%hu;XsR8eDVBIg3nShUgNXA&(MU=pvSn=<;x?#PDbiYdT4{|k9 z5}}yhxPC|}CTyVCg&h%Vh;;`nBfGls3Q`ZL4Wvg_b);& z5J_R%iRK!`%>ks5%HuUfT?%if%S(!jhf0IyM%;3ii)X`sP2y|1x?Y?MkkP%6_;*&B znUA$hm-#S(agiq1Hrfb^hXBB5y00+)F>7w&Z3wNGTKhxffYR~%|4+vF+CBPSjV2HU zqoJj>zj6l1)9)XSoVTW=X5TDd>kCdtdl&^p0GSK9x5+!D*&!=aQyz-=>3d2Vjx`~K zZgZ(Q%@MFU3}EHe6;Etlc{5U%vzz^` z{}(!)h=tI5U;C7}lHuEa{#S?cpDF8hdH{l-n2<2mpzZ5vnGgv#Mr4n7l>*$O|3i03 zNmZ@MI{Ey89oE4eI9h&Umup+fajhMF{*90V;j7k8!SK&HO|`!t3-ZETWaL^jkZg}a zq3rUm7F@5QrpEj>8WMpt+3|Y%_|K{D(}8V%xsjI!Ptmk5J*ebyAmwsVlk6y@(22Dc zVN4RN5xv}JHB_ul`(K8b0(OuM9|e*jpwMY%cUEq01Fby+#Goy&o|KP5M@zJV#*lZh zs5vO0b=jNrF;studW%}g{li!G=V}AwSNA3F*wfJ>Qf@Z%e zAN5u24`iVF6f6Wh7KPVO3la8h>E4>TR8keo``41<{$Ay1GdIu|x^8Z z;<#tu*+BvlOgFOE2Dvj@MI z3iM8in>vYOc=i@Nbzj0B37^ZZ@6_oY%;7uP4r>O?>#SY0kJ3iQi>f4z^cz2+Q9&r02=*>3c#qRe-z=YQ8mE512|cT z=~sw@fWg-nb3OFc-WRnh9=`VVJNf?Q$g~(h7NjCl1FS~~Ha2%G%_$Y?2=vD=plgj* zk6jtY@~)So>XutSwCLuQi*FSjW}$l z49XtLefD+?+1`T!d^5l2v8-29ZMdh+8|zaY3%J#*8~QmHp7w zO7|5oG11qag$42#3q~7|&8e$eH{>yGTD1hh2wS!1VN<%_t?3`8Fw>lq_di%^m4Bw8 zUK($S4W`?!2qsw8D38@seg4L8y(*`6?#!yn>gay<);~R#xKO-(+k=xG{8iOun!s3* ze-9r=34A;)>w!Y4LxM5Xt-c-K=1QHw%(>C$1dD{Zn(bU~>+fFmqj6xKf1e4NRqJ&L z9e}h{1P7v7p?8(^ypkzBJ!L{gZKt7;mD|u-HBDA3eY*21{+HQk+O50(dGQ(Wgtx7E z<%j5S4!uezVK#yiay|HsH=~-SuAzYeIv+>z^Q(UZBY(^%*&-e9@o4Kj# z+?sIHIC${ArR zmBCuO`3VV01Aysq2n@EheF4o2?-h(kKoLLZ$t+prz%PYI_!>nI6__%43T7J564Q07 z%gtsfp1oz^GErteDEd{FSp?_^KOY<_GWa=mH*FQ?F(Ge-S%6+ZI?V1IJ2v!d*Y|t3 ztWNFQ;#6LI)IhK7FwpahW@&>u!awwF6=x|U9bb{+WyGV6w|^}m!#Ym)z5!nalw6ff!*}~qK_POC zTnkOOK(c)A=W=xA>N+xA`vd9%dF>_8s+>Ff6dk=*Gp(U(I}|Uu1Hd$@9S9kow>Eyj z*zZ&$0KS6vu8&sm32y4z=W~5Jhe&Oj4$^Y0&^36fW!Vw7^vtiNx!I2(Bk}a~q=~&d z^+IMMm$;aZACD{tVYrQmpXY2!2Jls8cS2GU6kt{bdHFIIk^)gJF=j1yi8~%@?84poxkE5K!dhtPE=fkcW`CEj6ClliE?)gG@mLyFUWb|#VPDE~Lg92ek* zHYTpUAx)6^P*}yYhZf&{im*8>T^9ikM9D|4Vg{&XI`6uWaFInZ#u^JnOZm7LHvT}` z@3-~*^Nm=HPOrl3^xyY`4A`i%HjnKog=l+Mz{#U40VLba1#CF*=bJ|WP7V7W7TV=l zqjtIygK~@dtm4f`U+5oA%XxL$KO-(fy^iAJ*m&&VibWp2lUMW6QE~gh8;~Q|82Rm* zPV!eA3uToI{N`4{N5>U_mHr1xn1~YX#^>hlzqw=Adz44i*Zd18T@Fj$Z8~zDiWNAa zF`j_^)94(gZxl!kcmg|*b?7m;~ng_n9yPl#h0gePHn4tMC-9WaDCq zGqA6zvaca1e4UPvCL2GX!$hIaNSAh-%wX;N9LBJ+TSiX9ZSP~712fEcJS~qOb4i(w ze@BRGnFR#}c6or;7z8~VNoD8Wxb`3jx~7QDnb8|XeU>hE6V$6bt4#~rUmbbZ@_cnZ@FJ$GfNw_~ zj3V*SK@+!M1n2{txIb}Co`@ zs7gichJ3)v{z*0XA7;*Abq-OpD{{tD9hD;}D8MJ=zq{wL1* zZfY;;GvyAQbna7Od*Ay+-XOsjp}70f&z@^i9p5RUuBjPFh^QyP?qRo&%GPot`lsUq z(`BURlo&*S7{l*(S0Aetu-}j7NIAYjEr>^dI$8JA_h`KLI~VXK#dvUrzKK8;Y#GyY z`F7aXTSzzf*pePJxkjTs_4dA{yo$j3`uazA&!LZd_Vxp=h=c4s(mvu^Z_@lwUi0Kt zYK)LPs2bIZYWGRv9jJuq$WI*L_HTTjv2%G~3#XuHd~oKeVyM**9+H~apbmiX{9=c` zPQion0;ei!)HmSfL+gUg@gqschsS3R?ci%rj&mS|G4{#7FrMR6fRR!~Iu&SrF!6{n z<&%S6FpIA63H?%3+I`W#C57K8P81XYJ#7Aa?zf-)E+>6B9FBgO3;~dck65QGJi?sV zAWU36Ht3HZmC8h~qK0z{10D(00h3+Hc2mX)j7XZAa#dyJ5Ul}{$N><|cf|bfkc!^{ ztDYr)L>pE;)DWlQ2^g-p-@A-$0gOn*@TjFig>kAxdN>$#mEUOall7!3WLCj|F1D;; zeFn%k9cot(H#d(;ARpws7>yKW1%1Dn$tfrKv{7-LJQmP;HHY|1OH^3?o3j65prnmW zyNixhv-P=hwtc=A&%vxC&OA+0{Wwy2-VXS#1PXC**oFCO&d32gx#r`lWemClW)Sdd zZmj0*uL_A)I;xsNca*=S@lPLjZ#UaKbii~x98E*-Bcke$*AvY9431~Hd0XO|9UfHiY_|;?|9Fj5$ktVqnU8W9 zF0sdY9==P;sQ}WdHQ+ec)dB>7r6%4F1z;vS0-P<>?JZv$Xf4xW_^Ls(&%{H9{c9~9 z-+|(L)IIB!{xO+qg_j%v!hmBn{*<#X1)Nt3P%sS$EhEt3F@@hfrvI2`8*utHJ(1x+ zj2xTdz{Sj=n+MScAubi_6mU)Y#fX?7mW3+>_v=0RfM0X;`P%uaP=3YK{B1Kgq-dUp zs$8t3=9ZZG8x!nXe*LVm^eS)3do&VmaGI`GVNIP_-^Q=D{&nIf`qX$NrZy#^G-uwtjf7agunO5Ur4|%coeOnsZ9cOV=ukY`78nccKg7SOd&L7Z zm>S7hRbi&gd?ZIR)U~njziGG)-8R?Tw2_^+LVzYuKc`6nA*+d$6T=adV92FGo><8L zADYON`90vNUJP6U&_pu>CGkMj@f~A8h{&hXuM?9vn)lr(TG*HV4>E80PG&!Cr^@~| z^n7T;vz>pO_OVW@k?VT}d@RB$@qxY*UI};lK^3X5ZU6|nJNk4Lc+-AQ8qK4iAw-=v zF>kgB$WsBR*x|s}_;pB96~Mj0cZcCN0Nc-7H(ffndfU~OkG-B!bN4HBnv zzZ^pDK3bl)ho@)N@uqHP`P&3Nz>N%r;;joaR^yTyiZqXt5kNubyZr8KzGc&mTZH!2=bh*8-tko$#sfR~8gm)*PBAYb*-%mb$>-?A=!q5ayp z@gKl-PK{T=WD5^&r_DBSfs6eN-=iGNnnESZ^Q^cx}|MUM)_9fs@ukZVvI#QfC zoszP)D?%z{Pn$}p7~5DwWE=ZF6v>IuW{K<}GiL0@j2S}N8L}I@7>qF(W-yljJ5F`J z|Lb@C{{P?4)pfc~U00u(_w&Bbb3gZeKhL|8~P!6+A`Un

?st8;;=skt?XX53d09!Hn~U z>zB8qVLe;}K)7}Ua;tM+_Ib0sK4Zh-*v7^?B;XQp>Mw2x-3*Muz5`-m4?EjoehSHe z!x3Od?tZ`tl@l2K4yF|M(7x$>*YTU)<$yjYMkptN0b=|4{O&5$E2Z`VEW()J{ju@iT4E}P&4qk7a2WO(k7PT3u=@JBhNH1@=S;_9OU%}(ZL&B25Oz9Mch^ydZ=Z7Qe+hMw`zju_2=Kp( z7`|ZIHL)3BPy;& z_eo;VY`$g9fRn@9oxUEt`xf%;$DKw={DcYc%gOo3ujl5lx5dky4iJCVB{hG>tevON z?c2APHdpDBSd#TuHF-atl7~T4SeD~PDd+618cPgXrgO@L2L;3$i1Yg?6RSV&e0y06t;-VY0;9)2g;Qt?v0q>M^fNf#>MkG&Zkae}RBK^^s znz6h)#KnT$id=sMUiXjY7hu~=#UL%E}PnCs-sb}n04>1temXj+t72Q!d!E& zC>z@w=%f*>dAm^+&^>TA@Xiv)eXV85M6zDq& z=XnwFx|Vb@(Yq=jJqNmru4+w8>oHwksZFROd{(yJr?pRiSdX^T9UvK5G#35zn^(VC zbjSF=Z%B>}hLeDf42JWJnZ%5yd(ConD+$;;;K978*HLo?3o&)nIB1}C9@YP=Boc>5 z7pV5XG2K16PmVkETxaJ&vtjEn3NI9XEvZ}w{L9O13&s@#UuF{7kyI0R@&owaC&`c4 z?Y|jxwV{W8tJzyXolDIFyUy>a<&D>|)QCgU&Lx?C7xb{`9cJSR(mZPXIJ1+p6?LU^ ze*MslnMFSXEs;e8Xkwk{Te?rf6x~sKct9uqu|gj>tv2svp9xT11}*suBa$_UI^&^g9b9ep)$(`4;NMEo^!LsvM`b$~O(}6*n+|_|DV?CKjH(MSA{&uzA6cj zDY@IBSc5#6ej@^(>73fQwqnmzaBpUB&*oGFO>A?GG*1lWC&)gE)Y|7fusFgy`OPZ3 z_mky;m|*$59>d9@gPm=9wn`4>g{GsotFQ-#PAW-|7TxI8$m0^4QSU9tAJ)BN>zomFre^))3$+pSbs_K_Ed- zaA+W$VDZ@{F<|UScjfS(4vzL_qFltf^(lQ)0Ib_QX#N&zW0~6GHSgkd*C&}I-Q%`4 zQTBEH!={&U1E*8<-i~c1-KBV2w0w0N{I&uFlT;2!qO>WlGtzWUhh4cxB}GLkd03|i znu9KC_+^23`Af2frn>OE7U-G9j=B#pU!#zOUG9+BI*v&JD^+Nkua}ocv+t2N=GHbR zY@x30KK!vtqvbO-c^M0>T|3S4@;2Hw?J9q5#Q-TCoVxLs{s}eBr%1_S5LPPWi4-nuD7q zm`y1xtxRO>^f)W5+SPhkr6+v15dS_RU#louNhom04>Mb<$4iT2{E|{r^FTcPGZXl& zCM$qnCGnzdRAK-ZF@%%~_vaxvR}Q%8qze1jgA#kDyg{>&edJaSfEm*DyoUO^QL-F} zCSGAxJJvACfs>~PcU!WDS7eAk5Y0SJ`a-XESG!D0QL4`ZyE#VWVwYQ?`ylRCmv4x0 zj?%1nPl9Z?NPl z(vPlbl#<&7ov~*-?K~rwWx6KB4!$gK*RH777P`(Ag7@jIFNKC#)AV)?gB%Mau_x zD|OTB&R*u#Ngy_~7Tp&6Fn!v<6CJll4Rtl3#L=M1r#;YNYeRqW=Lgt9;A10g$Xm?- zi#c5vGdAaXY=5KzTAUh|Tuv;x$ItQ26L_8TCj1Pi3v_2^neb+=#VAb^(>abwVVHrZF>^zqq3|L6E`!R-_hp3{Zg| zYS2kJ(w6N+3a0C`eXqQZSS4dqbok<0t)F}#*8HOCd02ju*#pZ{HCcwbR+sXko=u?i z;<8U%@?J7AYw~JA&fu+A^yG(pw?3%9PP*TPmF*kFF>~)L9nJ!e&1C)p6>TIoZ@XQE zLoTE3>A-$;lU-Fq*&_ee8%-%!%-{F;7@#EGqr-d2AWmM^5N?$u8@ff$!L2XJ@b`WW z4DD{4ihMi=i7U@nzGpgDdz@Jfz6V|63NyLfZp2mJOqmpJg=k%_m@%2aRtFKJ32Fo# z#4ldr3JgH-qxNinA24c6$~t4kTQ?pL;yj%R2)2K3a?+A9CJ1%xswIJ*vj}H}gZ6Hg zzSlm<7~e2EZ&8*c%eX42d#UfLmeK2FGeflgEQsqIeMv`RIeqgcs#oXnh*r%oosz<_ zKDm>XXZ}hK^Hxu*tW|{By)1s!!|G&pv3>#MseK>-efb1T*K8UN08g0Kvud}{8Lkf5 zQeSiLsaw`~n%d^FrP4Q-UX-pN1DECJusF6j5a^4>trxeGjm)}_I&>NW@HmhdfX11q zd)bCfEZ}>V%nhFU_SN6&!UlVY1sK3r;KDg-LV>o&n1Ra`%bTA6BIpait^MSX-fb}M zgNF_0*~%b;N&*@=T;Is(wuMOPkBBr|0td4y0bJ>l?KZNicfg9s1*BqQ;|neL{Umn_17VL{~Vlh zZni6sgHDFBSubOpM-Ebzh6I7dV&%UNEQ_iJrl2pkNmg3w9Bpo9Q4WL^B!qP&gWO*b za#PMA$@U)<;PA=4iTy-HZO?rg1HYu?CwE`*amA{ncXa)?uJZphvSsZ&{1^g-I=G8Z z_5!VQC?OVT{r$z>NKZ<$xHz7J_@fYB`^2$&AP8R1Bf_Ygi_(jee~#x`JP(P}QFxrS zPt@n3yP-&yXx^NL<49d@upR%+>_1~gePFQaqka}cM+M{6I-xT{3$#({wQ}kN26;lV zpMZJA*I0-jP;P1sHwlyd7?hUez8LEDaP%tVuN`j?ZwRG3XlY&K(X!G9vfuSk%(U`@Tr;Ja;a7 zlv;T;sD*3|noH($obmKcg^=Sg)%EUb+Q2r@Qaoy@7^oPfnEdn0?oind%N*l{?5qID z3A(BzQqo^t{VBTwfRf5TiTiDonm^0_QEwFFRLuxdOpea#dah1eN##G?;6kOf3w8|T zNf{U>f5PGA+gSIXh$MdmkqSKn2C}dW_C2fie*6K9I;K3_C9EN=BW&;=!Aj>k2v#%O zaS@okTrmrmCVgT1nlW<>WV*RWQ`)_^+)aX4BiqFngMX``<`(ex$EPQ$9<8nW0w`tXg{@p55JHr zn;745zbRJI9n}XyWR}mSA$!J;D~3>&c4BCSBn#$4OdRWZPC(}jCE&}HG3LpUYH0)7 zg7D-Cn|*~hBOy{;&4dOrusxk)mgXxbGetI-IiK;4-yJ1X9w8it=bq6JyrOmNb1L&~ zq06;!7Rk zefpP1$^axW)YY%&=>ZvRNRr}1DWk0XiccQ$U=iT0$=C8o(wG4&sJDu+t|=N}7g-~T zkVY#qmU?K9R|TrC^<%6Fd@AexjU&Vj67fd8<5NY~2;9z7CD4s;n( z^M0kJv|u9WZDQmz`~wa-^r0gTcno;1FDp#Ze{H%cDWf!t#Sm?S|CurDj;7|I@+%(1 z$>9R4Fw8qVJDlb;1q4H8yAV5X2)#P@eafFAQvo%5=D$?rj9dAxm9HJgV0uV)r(Um2*2$N~HySRT;(`2Z>{tqO`1 z>g9gAOHFETGwD!J^T7m~4_QIUWD8^Glgti3A55BB=g6^POVY&v*0pJ$6B_@QN5Ji* z_WibGkC5u=?c-JPHotTp=4hXftHs5C#xM1r^}5+$B2lk|Ryc1kk92M7N*Lr0;(N!$ z7Bne+7S5jkE_H5?lGpq#3F%~~jPTm9=uD_hTJ9_1$DfK$ENcuDs*1w3*YHc&UkAa1 zgMAPHpX!8BVc|R}j=yA~r^Kns z!~gZ(B(dpwJx|XP;5G$guA2z51qTtpFKtLwt^Dds{3t%LObXneERug>U)Z2x$-_KY zuKRB&ovw+Z+Q81;69fOAU-jL3vg7*Wm+OGr)~Io+pYrD&G*F z;2(55)A_x6vG$6mZ`G4gukK!Mr@H_SAoHTx@h2W2BLX}8R7@SE8R8;%@m5BP0f-nr zV(GEf8vvmjdj|Fj;1TZdC|<{kIyn00b9W>?Wq)xXe@X&sg>9h}^nxU@C6+;F^w4bS zdr+#NfKc4RJgF7A{CXkZL4=8Dx`qaf#D5J(kMhlY-qOH6_Uj6 zni&k=xeQ7ac2a0kX-MQWFJi}{Nf3+BT^K|YyoNJ6|Gm{#tG3XZDiZd$o(#aN>Yl#E zd`eG!$@lC;3_Q(DE)O+ow3ep-R(Au@T9Z(C)vL^G8z-gEHi!exg66&hlD|&b$6Wxc zJ!5nO@5gbre{AP+GNR$&D-A>wBs15w0H7pYZN-kP==CQyvJ+)b2KI*L-cG}c#)hr3 z_n+w(ZsNuWK4|V}qSqK!Zu;3pNin4!RD@oVd|50NclausU+Z;JpWDhE|=I4 z^`4DyzOeu8+b_zy)6dJe(5TyCkn4tUC{Kv{V!;3~4$iFGgImVY)Y6TjTFn5Hq(EI~3*G1h(*$>i@cnKtm_%&LWUs zjR1L%$~AcmO!Gr3lj6<5m?$6wuXzE0TEm-i6USN2j@AG$xz(bi`%#2sGNXVe#)l1M|HPdxt+m!vv* zEjF$L&t7;_%Kyd?4#<+D&&8X1AgGAF%{Tb%&YYn|xwd!~G$0}|H+IqQtyF@F(P7W; zlN`IAlO9`58aCvg?LN4tc|(6za;Lj-0n=oNoDoaiu8H4_>JIvluPsI#7j<6A;0`Ke zm~Dr_>C>rTa`B7SU*y0OYj&~9R=|M&&EzDda;;YK6L`V)bz|y z0vlzJ>e%8c&M9ERmCg49W7)4N5tAz=O#mF}cJO;?L#4Z52y0Q2duD8)Lut2bx}IC8Uc$sAZcRp^V45bfAEdfZ-K$ zT7@CxH@F?Oa80mG6rhKZygCBe#(Jiz@J^Pgr?38*p8o)6&3RH{=V+Pu`2h~{Xby;l z2A--vA|a_Z_gLqMaVcetyL~m2ewQrxbLpRKJJ#3+U1}rf^+qhV>h-vMfoyGp2TKQ7_ zQ`zWIr=r7e)IM1rm>=Ha)>p>0$qBad3W+c0RF<59M|X$4LNTCNduzQ6}sV zZG4Q)g&@&VHb2E9GDPlXOBrxr#AfqTFW+G0^3Bs^FZo#|NSx^@n}Da{gmPTTPj`CI zgw6=~e2C#EmfpWHexl0P@Jv9Qcq_zLN^#3W1P!DkZ?m6|e^SAAd3C1WczrJbIA{cw zymC>X`jeD4!hMl+Agquw5ule5TCyPLzpyOnY}7Te9J$>6r~OO)IQ~BlJG0T4Rru+| z@ZgQ*Kncbnb|RejC5K3*x3OuO=f7wgw+o>JbXA#J=~A)4w$F1E~2C>fSmq!cqt^xW1{s4PvFp6lb`nrOK?msIZ5qSMn$C#uq7 z!w&J3jNSk0)m37Eow%F6&~yNDmA6^YdKW#+PxJk;q?D%TVsgD5K`)D$ya_?m0fI&W z50>x?Q~<9@5Y`*+R(Rj&)(@6m=bVrgIjX#EH<^E1W%@WdI6sUvgHJ+1L&ytvgP#IA zjnQ>`$BwO~+t=0g{_PrP_XpSh82FL;P3X!Wp<{{iuu>49v2wGUAD01aFGk zke!%7eArbjGYQ7z=YW-w{c`TJp#ZA2@;3AN)I#Eh-Rq(UmV(O794?+Wh*$LPHi2%u z?UH(zr0i4(jML!j*d57Fr+j3&-?s=PQkli^UX?gfl4xKTcDRt>v7)DBwPeDMpC4zG z??T8w1E`l3HH>JGg!%afQ(>v!@DcYPM^!uovKaM!LY?WJBl`=VXVu2%v&+{Q5v8th z?m8p*wU3a}amXIdAmy_wCO(fpO0FAJZ1N>u@_du1^IalkD;%O?XoD0n?wa&^pN`Q> z@Me(Rnj$e}ZR@S9x#EjNg$}4->_|X=3GEcgi1lFQLoU5L-X^T@oV2`}Kp}Ewed!Hv zzf6cm8PN9D3W0;F7nBtPPOqfU=fF%|z&XRTiu(JFp+%3hnyuEkFC1v6v-(Llw|Ew} zBtQ1Bo9^lTyi^_hh};58FkrB>m@!ac9rc=_&Uz!hwUM>B(jApH<-QOdX17%gPbbQ( zBrMdml3^LF(@_nY4w=5|72#2#j2<=4lQC8YzKfE4)`T{Q7v4KP7){Zvs>66WZqPe| z?jCOtIRTcg_UEmo&`tf&S-2K~LTp1Vo@iort|NZVu5T7Jaz}49Y0CcN0@D z%LyNK42?H?_5S7i1GfEX*TQD8rbJ(d3sBhJ7A2PZW7$j0uSw=%l&wkQ(n|QPm}%!2 zG&TRjY+!8STQcgrQl?KFUwCLdE*7j!zk4iTaUWLr$u`=~*H7yHJ_IL$Bm41}yZdAtnt+-iBe zw}o$~gZW=nBn+4VOZ{2|IMF-3Z1WrhBYoRac_ajeS< zv_U_K{h=@&b<%@u0%r5~o$iph9w^*r5y(ezI-<*g+nJfKF>-HnVzuskoc|ifeZ*7j znRx0sX=P@x!$tJ%arbPfIB{#t+zc4DcCmP`V>$w$58y#Lj>GR$G#^xxUmKS?d<)nb zP-mn1!FO+ykR7u4A&}0dKstNK+rFAh$6?v-3V6s)?3*`l;?EmaA*z*7u*mA0p-%%X z&`u$iBkUGmAgLdUDNyI;mzurTm2ak-Hw;nqdx;BS+^;GW&0k~XK-&ud_)t$At=u^j z^?OIoH`_e-lZSUZn1_&Gcg~T>Etzp)df9NRQcsNH@-HPmh`KEkk4zHWYNbuf#m`5ER4R!~ps4~~Fr*JSELD#*RY~!oYLIFTO@?d?= zUjw|~*DWp|u75085^81u;X_$N%05|iX9N5GVtA>+8A=%1uhrtuBjyW9pLX^RpY}0d zceoRf>F1KT55UtCx9DkM>kI&W`xwI%K6Ot|Zr_yOMS3t6CuFMVUMP zd;7wkIrH^EyRCKZBYI9~>W0_I0$&HPfk$sbFIEIYS%!qOj9|_nLBb+N z2JNnGlxg@XxvE`+lsZCT{go75#h6jAqRX@^On~D)o@}4#s}aS9na1hggaFjp?l;5S zU#9-UD@W;_e^$LEdr8hvcSUU&)Jbv^AK%NJg!!ygOvhb$d0=q157-;>K%DV6>NtpvG- z=ZF6AWHHy>iFLoX8Dr*kxex5_(2dB(k-)&knxPlEr~89-r2pPs;U`64;E84j_kk&W zTeE10*WyQJA701QksNsbo7}*#+)DBWxeK=%cQf#Dz~CE-$=@3-Hp4GPBJ_q*J{H2o zQ#3WAOz+)E!%7FtVYx5k-SdCcbfy$9HDw~#aHbb^qmYH2IJUt}{qR=W!zy{#jy5Pq zjdCc8Yb5xoi9_&j4(YFCRPpjrOEF5%L%wOT9D0 zeTqSy{R6|i@Y)9bL@|GDVJ*94G&_j2^#ByS-BcTP=8I`1m7me(&IH9)ZPsoiEEm*a znxF*Cj%zLfTr-$jd9VJemX`Bk&7F-4XO!9$p$7xRm6x-}+P^x(k8_!BPy?#!>=&<% z=@I5eft=F8Qu9!D7EKM7KYFsx*536bO zqZ2N&wS4~j)`h~K>Rd!^7*Pw;cokG#0yAv$g!lcwTfLK*idzRFWqCos7~%9yw~+2T zfB7&Pu_l|GOm39O1Jh;o_4WPSU*x>OojBOYIeg7_YD#8?hieHQ!GFrxTy!1J_GN*a zU06`=SMHobwl$h+u`YM?**3Bd*gG35T#KzAttPyA<0~!Hm&)vt7@65qt=lV{xIMt= zRh>>vwl=+VN1Fi)ZttX0-VGUoJuD-I@?u5Y%hm64*?H#D5; z+p3%^WE}zpRzru3$R8%0;ap{>dM~JSF?-(EWk7w%Ddl!2lvCa&+5RXF1>cw-RPe3I z&XU^QZb+W_cvNxg#Bd1P8TDl7cm?9}{gtF!^R9>`XOx6~{&6pVt)`JKQ7dStpUG2? z!r*AgC*du#Zf;}<^3Wf?M_f|2tInEO0VEn7&rbNQ4+S+u=x zo;WOtvueJwXvOM1Mb`6fFqx9jg(P6-dXnn)0I#6@5e2fc!F>_6eP9L-hT};^Fu>^F zc!U=u97X?OA|s(@J$l21xPa!>?Wz-^B^BuDYhoyZlA?`PtZh}yAdnD(3V?d@wWa+p zL{a1v_|RahvVT^&Q?}SgEwo3eZ@+w7aTJGA5Q-RrimLNwX^4F-UptsER(d63$im)+qud z?o}en%oKRU3gZ7eWh{ghC6Y1sr)YZVp#;tO^pNYL6o3GxTW$pVs99*s!=0;$tu&$Y zR<(b4%iI(vUl;dC`;36RKC~zhWPb32#H&Lk*Jx+?1_wP~vT-JRP!H#;cc}7|Qck(y z@!IoorwvTaY$L@_F#q_!T^uJAGWcWb^x`OS(Se2{$;997k8RGsUjp%PGQ+PhMmuliKiJY zcA3z|yf%Db$Bql88rQ0&>B$w|tdZXUDqD`(9Rw7iQ^8quSnA0`H(MK+zW%GnGTN0ENBZU zR1a?YEr)o`e?BHZEU@J(>N~&RI}yPcGRNy-V48(>Xo-J3EttjE?z6q>ClZmZMSwZp z%>p!U2C(f4%C?!x9}GITxPiMS}9qOOwW2`lmUeIdz(PT_18+=Fj)l z%`E?=wNO>mz4}1m=HRiw;R7Y6aVy~q-HJP>Ry%hOXGa|AU&D8(ldD13z6nQ(b4Uuc z@lt5-+8-M(MkzU)n`9k!%Rc1>tL@0KrS{}&rmp`S1bNU&(H0DbI5asAPGA!BJ$(sC zsh-QN=`)Md8ACALo6W_!_t3JdqdqJ8^cRSxnu|jT&4r9?hrxp%8(kv#rO5CVWp9bM zSz2zG{>=hTQTMmTv1-d<*#m1ne!2aLFjkXX`9v+$`1jhX9{3gqxxeAH zh%g-|)4S<_4KKUCv|&`Tz?ixL2mouY(9EjOS~2|%dv-Sq}wqtz4JwJg~)Tj1BBqg-cZX(^S;`}9hYQn=J zi;9MsG7`;ds+OME$*-XkPxEDT&syuxE0_2n5)U<-TDaS`lNtI=aOY@^p|8k1ak{TE_md_w~`ZQH^?at zm8Ki7TFzFcc7X*{8K1rQFxlW(@ni@oYjN{r7xit-OaaAy^Q$N>#)>oA!zw9STtIYh zYt7Yd{_4w2^;;ojyz~XU`ig%h4Y0WHk5|08+;{JCZ>`@daG#r841I#sM=i!atc%n> zS%FaCTZoyN?ryj06b`O4;y->;AWZr2@0@k>&RQ?!l) zD*5_b9kNThRXoDtPqPxVO}_B@-7)EhQ&B&b(MbP{Ay4@KP(CfYfmBJ&(A&rQVlL`- zEag)=KCPfCp?YhT&8VFfYbieFTT{c7#n|;k?`LM5oFBhlmCnsicp}r0^Tyc9lh%)I zl0rIXRBE1xjda`f5va|OFNdH0C+PZHHj zRWL2xYU|}w3(J@;$`@|cJn{5k)_2Y2jZ7R|T+wazdUW50{fa9}D6E((g@P^*_HON| zV8s&Fy-I}9ThNt@uE^@3&j}Ng-;F9~1D$NRvkm7r`7sq6QVdfv4bZS(+zX0`92E|f%O(W^v-8bu=O?f7nSozUtcGC;sK**{zN6hzFf&1U)OIt+;<38QNOGIxgAWIy8W|I+bX+IzY z^q$T6#cSwO<#|;|Dc%R3tG!lPMsT=F*Tfa*Ujfoyc=`uIU}`NSl~pL&1h~!&2pI@- z1@3jGb|G|bw*Ob_G-a7cay9QmGYgK2S5BCfm`khiVPZVwU@AP+~!5NwVgqx%n|5VN8+fqQRMd1r;!+5E#s@lKmC1$SXu@oRz|c+u*}M z?Vv<`{|E(7a-iTbG?A*gwV^{$8oky+EpEO~m>;TD6RuS%?6e zBK$3){={$Q&Xvl7L07R_qN~3(**880yz0GVEeoD6-@L0GdZd2Z<(x?Fg(r?-tLba= z-ZY?md$)&IM6a36uySbA?e`4Pt}|T^`N4r9!F-S~JV+@IMsckH|}LS-^LZ zqXvvAuNl;}z!`r8%>gl1v)xB0a}EO=b_TjFv-GDFu;T?N$_Vkb=gdnMyhu38V^}1j ziDDY#kdF&k`@|%@+kX*NaLigO@Ulon7Z9!7suwG!V+CQZ9e_e7$lB?y_>Uo43EX$x z3h9pD_S)hafb9Xvw<$h77E@!^g`Hv{XsC)x>SKv>p!e5$&o7(w8_-UDXIb6 z3DBDmb^#|Z^K!2O@5SvULKo9u*b1I94sKxkvBa-bf%oUgSw&MYzl=Q`!fE|)x5Nu> zv6xagOt{xZ2fy@$kE~VrW?gp>H#YV#czx%_LRlsep5m87al90J)FpnBs99gwee3rP>ev_Pg$YBYRJ72 z;Q9qXoETV&tiOp9QTlq5Ppj=oV+*B5ZTC{f(UtKh`FL;A{FmL(m7GwoRemW95>04? zMo8^v!X4}oMM)*nedzh`oa8mPN47RlGiNg&NUW@!T<;cPB?`5|Oz9)#RZ`{J%?JGb z4ADXFzHHT8X9-pk7qJJrM{cp!x>wRF@7DFaYfsZL`-awqA24YU4*d2!7Z?zzYP zI>6V){e9t9kl4#0smi9;I>YTN2!wwIA^?+gQlsCB1%LDf6F44anC9@*Y^KOw>rAc&%ZHos@>eeFN8nYzlvv8X=G9(O62hgjYtpr z!aDW&As($9gt4VZVf5`sYzn)Vn^-Y2DW;WZeU1A$ZnYcmuZkBh(Za2IB)-76ctmna zDk}(mJR<0QLA-iZr|Mm5ITO!_@shp~KFA5J#}Z^CKG zq^jrb3VdcS|9!E3uzjWb;5w7wulFzHF_C!zeWeiCV(fA4!u6eq3yMRj*L=3PkBk?ankNxmj~sgRwQKM2$)CkF-LTNS+s#v}e^^*uRPpn~9sZKmSPZ)W3^VIG19?eI?Z{Y-MMv@g@dtwyC zeA#)0b_v{eW%u6=BRqYHbM5etReSeo3~vUaU(4v~lMm{0b7gK4LM|ULr_V_=75YCm zlnPu?a&5d0ouB6LnpCjGEps_T8c-wp2iF3ftH=ZE23x}hR6K7A8G+9;_K4y3s0^xO z4l1UIuImSsRU<@l-H2-Rf%lAhm`Fj77hy7Z3?{{T)JO`iB=%!g<66BbXsBBf_s0^` z^Ut&y8x8#_=n_*UZ|rh{!6#MUerwv$9<_Hp<0+ft9nNO!aBwHB_hbQuYCjC`D6J%P z0gar3YQG^hnp1GrZGVJ~5pb*TLkc=Y=dKbqR_fHeNMIINZ;XN2v^YGtau)PVOo7YA zKw79j34Z%AhABx4LI$#`P0-NtJK0VkCBZ{%Y{F>uj9m-$1{Gy?!ANC*B!(T3O*bs( zPVt*P4B_N0)sR=VcPf$JY9v>V?HpLS=f`XvlmqOW!EYN=y3u|Pi6Q3q+h)T$HW^&O z25R_zl%@?d$TT3vCw(~wUwJers93b2)Ru|tJPQ?@5y*9YIO)a&NsJ z=sfK96iZuK|4R^hzQwI@abOY57n`kM%@vi?DoBzC0G0kE`PufM)c4@GCpz~9o|v7K z78^b-;+~*&*e7e*q0yVtnLx;?kO?3OeaYFkG-*nqQY$yi^Y%4Qv~!+Nrv?+x*~#D2 zpOUC*Gw3{Rattk=%J-nIgHdGUN}p5UfD;_Q?nsfGTtHmsFBG*3}0_0b|qLUiwHObaTU8zeEzRnqHLaT zcjjOEVnwL_>ehXq z)DJi=j<`G7RgN0(-{g_+8?6b;sKPqCySdE{Z&HUH!AQz~$0-T{FKcSteg8?<@meD@ zvt$Rd$ps%olJU|p!Opz4!8`=L7MFF4Wean8j^Uve?vt%7>0!X4bEiVqT|Yy0m2tg^ zlz_l8<<)l$&iI9qkBC_d8!w%~x=b4$2Bi`?*3%!%4<_9fOZxtN`crQ1N#&mnLa8C!;}^!EItiB=LhUbH zg2l&(>RO)QjAGW_=q_tOD8l|ABAUB$D{@-R{M_Ff{N`V;f|kG6c1}P->BtL2+QcgA zb+~h-J?`QvOWAkET7HNbhh7z9Wg{#9yx1i#RFIYR{N1p*?abafwAeT1?O_Rc<&ui~ zQi;PnV4*;pTFo%gRnB)_A-1DU&G^0)7K@d6y87zaHOH|pd!^3v+Dj^%7oGk#2gv4s zeonHt!2`ry$fqm>{pl`FoM+~>tiC17BGgglWz2_9z~nD!P-$$V#AHOtWJYM*KzuM5 zF7M}=)e)Lm>oha5dd#BM!nr<{44J+>@A(E(N?&2E*M~%xk-RB9{hLfiX*^F)W#Ts$ z<*v#^@A9QJk!jXP8swe1=^UKQMUN_Zd`-=w&NjLPhf|b)7d)k;Z1H(VkL+>KWF0!W zrkxV7*f8q3>YR8zcyyILC-KQIyQQ%P_~dR}5@L7b|3oQ@zPi|}eS{Gxc+t-OZkMkW zJcFCQj-XThlS{PnR38D<>lxjo$EM{HFWaPZJHxOGv4^S_K@405_jsG&J`WD9CnaW( zrWuohz_ZU?S==`0B^Q*kwN$6p2Y60Q2#eQ`8858tQZHh%@e1>Whjy>C8}aq7%AxTJandTuxujzi3#wnHS>MYw6sjq zN>Y?vQ_+52O$V+MU(KKIBUP4Np`>wjy*O~Xth@Ds{rvAr)Oc=3pX8n~g@bNYzQ88( zVA>CKiaPZh*^q{ZW@0b4w*{xM)h?s2VG<@!lpPw+B^oPt0Y+`anePtk;FTcvK6%p} zDC#5a50A+yz7=c-2wf_v$HwXfi(6kHiuA| z`a0t#j``6da4r|?Op;Lb4K0U{_VMFUZ+djW!AYe>RLHMwUS3ng&bYx;XXDr3I{K-H z3cr4G5iz@!JrN~R2>lH#C z40as?vl6Dt8ejxW$0l>+ki#Bdz5*ko*rc~qaWlzvwIi>T>T5-V68QGrt@u`w&1S1- z+d07{#LbVARXR>z&ES|fH2Sfoj-r>TmSnHFpL+(uU`rmR z6<_Vf_sT!$8yKH4n>wxk3T|@O5KT)6Ui|Pu2o{M&Ebc#X^G{GIJ$bn}r$p>S5(ApR zRT!>w;y(s;%MH3D5$KLh=>`9Fim_if4tIhvgH)o9RNbP$I41K%dLM0huh?MD8TIwW zCS{BaXtvgXdLX~O=CrYP<*ifa6o;QO8OFe(49hVJu#1{&Z#%p>H#_(Uv^_6%KLfVv z*HM(#`lsWw&+rA^dZK2cD;WmrSa4~LU0`#(jI$kRw|sJUdphfryP)atv&O1Gn+CA< zk{ZNPW{uU59L3<8KB!B$mZWmeB5R);=~+TTZUMUCYGj&SW^Qm5I{;&N=E8G#teuDR z%25N($({E`^#YN1bClNcJ=jR@IQe@$vw^!AqMi)yngIPi2A-?$&g$-I{@K4Ub(MV@ z*dx|;v$adAtE=nXHW$2VW%&DV@2U-rJNP{`Hz%!K%Q=@mzcrIMGrQW8Qb;!UZ-n&i zgh#geCvB~XTqhM!DD8*2bY9;8-LBuO$cBM6e>&K;rB3b#!4b5iRtmE|Ki%at_H3`$ zx7%V^z>)p))8+}>FJ<8dC2Q>apB(l{T7@M)1X;3v_j1IuX#jf-JcqtltNT*gqzpVO z+-artT&cW1PFsvyn9%uE(FsUgqJgREg-%0kSD1cyaH6 z)4flg?i!T)`M^|%fBd3T;ED+Bq4uB2!)mvdE0q)zD=#Zo@=0;Hf^5X1&kd9Om%|3e zpA6k1V_NBPLj&Ji{WZ*Yf%`_laDhU1Q_t0p>b6krYe5cDxCH_vkV+g50%$P`K*mRD(kc0O+O4O^gN@^fl1wk*W13-WXb5tP} z28+zy;kVyYw`~p`6e^;QXI(DZ!2l;`ap&fgtSVbw+ z{%SpVG8f0Us5#np>VG)I)%DZUR(%8SFJ^GhY++@eHpw1&;et4(0ZGYoiyV6jX zYsGvp?~wqU@#aKBPYe8*wFlDLWI< zL~w1I06{X>dgQG0fScMme#l#i^UNBz7G%gZ_5|Qx-L)#5F_Sy`GY@^UKZR=3v9xgZ zs?d^BY&w1D!{gPykO4XSt|7zWgD6FQu=(f(y|LX_c(dscvJ>d?&&L-ioTm-LUPX$! z;bIsm&igb3tPo9fIvoYBfolJ3GAM9bE8)7u&s2Ja7s#PUA9HP;*8VVJe7C7!BLcM^ zui*7y%hlP)37I$}h{{Q8Xle3a4k}WkZ4y9-U{^W(E8DEuIhNxE+=D(2{joC)74_3| z?of}w8Rf231iiccac(}C-a?2_zsVzHtNZ+eLrB7NU^IqU{=}8v zes!8o`L0N+f~(YKOlo$%d-tvk;SR1CEgd6K71|~b#CHY)t2FnJ$+K9J|I1_pT1j4;Zw|fah9|aG zidQdAM?|sd=JyXm3Y7%zYX|=Z6QkdXx7^^5zx)2e*WJ7#v%i|Qt`NV*gS&-fTG|so zom!q7EO#O%s{kg3%30dKMPT;Kn|#;M)c1Q zHrOk9OufRK9$S<)aW*jZkHJ`beHF{gSKC<78G7)LNcC$?)O2QvnQx}V%d{8_1zxOhDm!~k#Qy>$u5xMev_IwE$U`1eN}A>Fgs?N-VD!L5q<73j zm71KpyZhkxu!&uqJjLzc+&-EAH2}8$j}0_8eX^Z{K`5_F9wbyn9dSz+miKrE7`-4R zx6fV`^mM)@rS5PyZ5ImYbvk^4fAEfaT5HX*YtMVDLO4+c1?|r#QY>!xiuI;FhwJxv zT>k5d+|5Nd{&UZ_m>AqH#@eRvoC4QwA&Rpg?-vwHo{w?i|`m4q6FF4D4T;xlr%c{3P@ z)Rmw>3f*~CM`yEPkwq?t-@ma63)~b`nq09O7*|!?%3uH!Rx6MTSv5GdvO2g$E9U_H z4uV#Y-H-K7RM}eV08}XobGkXwRHS0rZJ|mbRL-*gz5XjtyzHe**Y%%+)C+8jXl}7& zpY+Hh7i&>J$sKM zpzDpdz6CGWjp(l+GyF|LF^J-j;4V@X*L-(z$p*;$xI6S7Z-R4*;+ANx^`bFj(j zM_dXW0#d8z=(4F=d6UY`7;ueTkVj1hqq<1CjLZO~H;I)uQCT`y>AyTE_t^fVPaJT= z;t+JPtwrm*CD@N(LxnSEc(F5!{oaYhuKKUSYnAm60UZ!2bbjdnkoM*AP=DY5s&^}; z-et)$D(@D1vNNbuDk0gjlccexv5mpBp@?a*Wt)^OLiTNjvS%CF*AX&@44rm^E~ICdrny6(Zez>C1;LwdylDE$e#0AIHTbHTmG}v zLtld1*AfN@B+#|J)q4+{hd3OS^ZuYMB;UX9AsXK3fEc{peLt}_<-X#lpSNtUxuTGc zcQ5Z^RnAc8Rssf&I54{*E6X2UmS38rmW@r#`sjstyl{P2{!@nCywwjlPOd~1WLEe= z2ZcDA#-5-72o^%lN_)jlxF;A^m~!z)BW*{Za`!@%x~MV3)Fj* zaCBOKtE=|z>+ee=k2rvVbSzUglKO1#;i;ujG+La*QK%n?8zLwqH9gEdVPmUr`&Om;UAMf6XVb#*=*`I_U| z2QpC1V&4wrZf{Hhn)bb;rEK9sO1xnfUvc6Oc6lV*P9n=bP@T`voO*&bxAr17bm!9UxJu@YL)(^<3Rnfa)X)%qZLuxHT0i(nc;ZAp4CFsKthMHBuJ z?t)imV9Tb%=*WEcHP(=hBpXfbYZAWv3uM|k#;^X%U2Ix?kS|QO)*y;RB7xYY3}j`} zwYLL3l)v#89o$lBoj4+|6Q}sCAV2a0$d~k64Jw&>5L(BHW59#ai}6|~0OMW1$T*(j zBi9x);A%*)SaqOiLVT+SQ({FYpXPsdYvcNpG5W!Ndqt@^3Ed)G2QJWyc~VARzTQ#S z8-ASB_ddl2+@}JX5FpUO;M#Fxj}pl>aK~h8Fpmg9Lu)(6{{%$`w8*TSZXQ{4&2`*y z;0$Ot3x1}{yMA_j81dEr%W~h^bYUW~h}QLOhF`xwd$xR8MWLUin}KcXG5TD6KK812 z<^vP*(<@dyR3P;t!CmPnX<;yG9jB0>p9&2M@w zBWAn?QBm=3w4*a}_S)~b26d?W+R%ZumyFr6=pQ~!eoL3HyRN&a>Uk=jM_JuHVcgfv zePyY03}P6CIrmE=a!9e7k54uY@vK(d*gA_{`I7CNZrs^#hEc!e^do|V*srLb#Q&}F z8c}vWrA=`-8#^D|*?IY5%hSbz!qK03g%xWOMV}964GhwmT)Nyz>N$7BNSx#eB5kcuzwAW{C{*`U~35EiPqcdWBzdqUq zU~eOzj^6`P-X( zv?gX!x{lwd}zmN!GllW%=vTC;T=;R0bnR%TT&4TCAMf0}Ao@>my zfT-LikX|R87n{l2?LHuDl(LD)vcc6<1-yvo4p zV0i=TNfZmkMEI4_$c)MXkK=pVck=SdSlN<(a|Wx@=Z^Y4tipwp=EJFGo`xhY&o;K~ z$$^VmBOvtvWX;M?KOUe>ecGy{ZtsRWpfzs--bPBsYhy4U(Rm=igq{XO^-s8Xh1=D9 zzY4G+AD2je0p8%io^X-37XK!w&z}HCH0J_EpL6+#@hh|#9%}4KOzrvY1s*VGc{+?( zo{kLvf(Fl-L2i@BkZTyU+G#&aJ%5i9x(PQ?(6N1=+D{PEFgbZAy|FWRkhQ{D@G-FA z4rDia_5UU%+h42%R-+scXb&(#fdMP@#_Vn?2eKmcsBBGvt8rytN6+2R#ZXE2#f+9B z3Zg3%?KeAXNmpFp07)}QAMmLqrO-bk89;gVmdsEE6@GG%jWI(XSaVa?DXIO6QFObw zu=;m*_gSS~UV@B0ED^rTXVd`A2mJxSGoEVd7IaZkk<%z11g52Ic~kjJ9=jytFW5$r z;V&L`jt%#p_gL;oq!bOC)EHS=37H&Cad=o!y1Jg{b^5`>aJ#jyL*K6*`*lL#HOH$9 zN*5nJI~JsOaIfW0pKX?DWzXM#n3-&t@W~Yo%J4qny{bC9{Rr2C%iDwN6W=_lKVEvR z_z=gnJ&#}Ac(Y8`w@_^#Y=_@FH*pPBAv7sNF|+8juuwf7v9d@lPxf!}6N3NDkld78 zK3ew=aKLN}2hL6BXy&9Vvg>KS)JAIiUg)zOc#P;oIAklM6Z^F;Ozmh~i*doNcSqPb z$NSt{49>sP*{0K&mx=#n#-Z99#j}s3t`_9Ed~<57bF9qO@4S&kLKlZFC}xQ7s+%Pr z3a=X&OP-wn{`RyzoKQDF-z2FY$hs%bZqm7&fK19PA}Etx74D@jtbUi$?MEj>E`FT! z^&^*xpGSLoJ1pjoeEGS)3O6m26ynAn$h4@C&on73ZO<+L;-B_{DnL@o%>Thl=@abKV)(nqLCZ42X7n303q?8RJZ)c@XUWlS8%FhT?BemSeKp;izOdA=yImqA z`#M^E(b{b%|6eV(o@(r*0t+=SL_sK)X?2Oi_6%)l^+Ox9 zn&-rw1B~cK$i~`Q9|NaX&3)A`4(A=m3Onr|y`p_R@3heC6qgcYBF_aUgZS+1G-X$d zic^D&_dlKUu`3kvJ4}+u7?6sqQ^dSe#5qzY=gkeGC~~yRuH2J;O%MP2IzjNC%T~bKe>8@wF7eCzE!AwRx6P-B6e&U!^ zvxQ|$DwLQs1jnoPsY`8;@=+)#Fd!S3W=?>kRofz$u{|oGgEGxA?%sX(4Ad69%*?n! zHthJJm9KKJVfA%$^9uorQf~gr#!)=}MAC!#alv-N=mG71S-I4%uC9@WCuyfx{Y=)G zFG!!pO+MUQFWbX0JNCnxBF$1%X+6U1t-?v6=~UdQ@?+D|GheZj&;sN5y4LCBpY+Fe zDD5J`I=SIN@nbuyQ)lAPic-__i9L-Ojb1}5k2-Q|K8JU4WP?0Y@1fhNw>%IA5fmJq z&}+W1x#^#>W|O}MX1izh?>+DSwJ`hYf41k({@U8}f!J@=j4(Q8znPVy&^tv{^86R* z`kQYDY*UiojS^FGocj$7qR_n3C-{R^1MhMkN~nvY&YcXrwROgKmN%wv-@N=&>$x|` zwwj_ij3?%tN^`XGX{-LcI%8-Gtp)1vK!s~9o0lZ7i?tSkGM1g z3rHg3ny;HpL%2xgL$*iF=N*Sl-FlyP6LHu3r`L#BaFnt*oQSBgwxJ> z_;Lj2{XZ^JFPxGMTH)3R8uA+QgMs|@CMf6r825F3dQy-v6W1!XMmcwpY9oB&=ky+Y z5r2Q2=aB!p>>Y2n`}D^KQi%wIs3AsA=EPhCC(&`=zSO_?%gYr&WChpoFp(J-P_mVh{Fykgq{hUEKWk&CGb#`haOW&bp0M%lfS>h!6ZbDZ`zlX$qmnWm}bH zwK_FcQo9ywu1`gikcQ+2r;0>Lf+iN@Z8f)cZqBot2#+EGp`D#0bzQj2<%b&9?|H-g zo+jZloYG#tO~-}cTh>|!$UAhN=fg$i%E3X;eu<1X2PDiWZ|`XMr}An(VWp86>!MV|PzqwwBsc^=>AkDx4-h5iY>5qX4rw)Y9(e9A{e zG?_kNi_W)GkwyPa+T?pK zmI5m7X#mU)u2=b0w57 zK7j=>G+t?TA`D_E%Kk%}2xE8)VRU`QixxI!w7B~H?4FoQg@|X3e|tVG++Kg^ql!dT zU}$~OIL4c)7^VXz=!M964t3V^7D9$%-ln|tww&|(WySXV{08#)GlF=${OVb-C!7v= zZ|aalfRw*HzO!eZH`urG+1{G9@#*#{XJU$A-K9>%9}Uj1zoAD+`b-%FCo;HLukDwv zEVO6NXRdT^1;Dy7jhvt~$sxz7khbvxv(y4d+gsER0C*(!Yyc06ba&ZrWpd_?3meCy zJt1bMrn!mVZJ*D7p7b3khY_ozk}?B%surS0v9~=S8in>5ZKGmBH;Tl|MOEj2RgMIQ zid&wxl(H9>*V4WLBnU{f9>DIgnfn)2;w|s6I zuUiGI*=kyUNnb*67(!}8W5Vz0s-4gtd`K6+iq3};m87ya@dl7^%h(aIJmxM_s4EnO z)xlbxvQ!JagU@Kxexb|e4=;8CEjJCgM1cHDy6CJ+b|O;q~I77{#_ z_BAY>%3BJ}cf#q$$W6@3`v3AYsW9qX&b5bVQ7ILtRKF}Fj0A7{6VHR^#}8#0SR_=? zp6T_c_|@VRozTXd zHZsLJ4}&P4s(C=g_@X|s z=`Ar}T2Zce1<6qXxd5P4GNQq@g%(}rH~;Iq$z~XjH5@oDd+I+-`r=h-^?|aS0BZ=V zqUIahEdI@wo25RQl>@JZUXK|f1Q603aWc5K|Vtg z-@o%W1lM-y6UmU2df_l1T?XAy&bqPk60v!>Bk#9Vhh&lv+f!=*KJID6Xg-1@F4Wt6 zY_~jY^@_&pWo_w^FrQ9c>;>qj@WG#be&QJcjt2`jT{{(ipohiL{_aga=poRDCm?Vhb0^ z?WJ<$ynS?Mn%TXP^|zQBsnKaDuTf0s0Dk9*A(; z7uTr=zpcGIDwC_!yUC97UfRydzn)#l@&2wlF=E!DTc-Oi{@#uFx7pLZqhgR73Ij-l z14K@hWulmM4-CXLOLK5oNXxGH(FdbI`t^kDYp~&9_0$cNAi72=f!)^UlRr^D+7Zs= zDdaa>qE<|+!G5SyuA;NW*?$PzqK5~mU!a#fmcu@J;zb)9*Q12sk#)EBH5Ls#Y|P)l z1>bkDoku~b)mCjx)G7sQ48t3*D!OiCJMUM#_!*sgdS(euAfKI<%hoExjb*a%3#fi4 zpP04~CRI^=b6ol_pB|6Dg@$hZ%AqGb8Gw{5>=15mNE&;Lu(Yt;*Yr=DBn8yr130h_ zJ2vNYC|a`s$x&5a2SIKHfjkNXS25FeNFP~Qm}t*S{83e`_qJ{7yJnI_Va)6OLlD^V ziU+xUrGUJzxsWysJv_)8tV)!W;sr>(aA+aZGCHs*Oo!Yx#(CtD9iqtsY@z2Rowi|% zZJ-P>>yhOH@F4#AzS*=TmP`;;os#v$%R06V!&d93bNzEBeJz0G2i(N@jqqZS5C`qOS6@B_u)t^A07W8a__iM ziGxKSSp{_+>CcFibyfE_vAT0X z;{h1{BJ!e}VIK{TP8y7KdRxe!r(d3O9~kq!17mYC)dS-|avGA;8#fy(zkScvQQ}A` zhYrp);a~=dgST#{a7Csmqh?>MHwKzZ1^m)8fp8J^V2}@`P~sJ1i_NfwRyiQd56a|x znX3yWiZIF#^A#x)gm_@?dq!3pn^=UOb&lqVBo$^9^bhh`| z!3UJFPl%2MS?7f&Ckt>s?*M~+@B2y=Tf_XAS0B=mDxShG$9(87U(S&wFQG|W<#$#z zK*poZXFu>0oxSAj*Imi7KfrSy1x6Q`_F62V#ccDFg{c2|5|5M>>{fLEsQQk`uHqIh zYWhdp_br0uByyW2zE_>%(c?eR@z0(%yZE2!@P8oLzkX_cK3|P`+h>&^)L{@mDu@@?9ajx5BL5WH*jz9q7b=DuU;(ynQWzll;{rRdt|BFx0$CuF(Fezn_5;=;8w^NM~#_#cy8X(Ul(^Mz4yF{t*&2@K&;XY&D+|-V3YkP+IOwP%R(%=3a%1N+s&G zx99(m$@6G#ZvK>|A3Mt!&dM#^57rEG5QRw1AsHi!3L z^8xR6$e>667Uz6D4~WIFEu%K9VT;c={`z;TaFcWZv(_i;h3_We4ye3z{3k2FZ_z$l z1n;w1)d8$J{@Qkat5PlV|F%J`KSqPwxr{a*aDy+q#|zXPTNd7s22hQK4t>x*1 zr_kJIB6+#5MMs^vK$~`WQ3oQCUxg`-Jg1#33|$-)nJ!viV;E1Vz}k|Wn3s7h!d>@n z{L0w}fWz+UD9E~ zBM>xOdFOZ)fe`BxL{8-(GI$>thp?$!Du!I}y!OX%@^l?q2t%}xkz(%@9+wOtob1G# zsgV%RrgNB}^kkmj#~9U%3+z(ZFC$y>XcwpoZJidQuI|baH=jPs8>k9|`-=xiP-)Jj z3~J6fc43L1FdHZfJOfzcBgehJe+Xcw8tEV1Mxqwzy~KxT|bp_F&0*WS!54=HSJ z_yM1;y7taGY+?I+5d|eRwEg}O)`&0nSfr1K+4R@~HGNA(^m}8~;!Vgi%}0ce4uhO< zyj?k{6Noi5Sc^Gcxy+;wsb|QdnGIq;Hd&SBFMPCbY?~)ckv2cv?P&PMc=xa$S5>z0 z?m4|qNcPbPQck!rZc|=x90AV-+7(-%B!H-oK$e>d<7`M zD}G*`IR24ZaM_KcQ=@XL=^vnJeE9BuX>nweE9AsMv`u?P1YQe*)?_1e-m?5|nJH_) zI2q9Wl1=p@fEz6zN45-%V?Yj$|Mi3nYfrJ8PCJKQZq`6Nx5$FZqNhoC42HfUwS8ev zuU6e5jvt#+i$fN4C1=PP1R3^#_0Syj-jxqcG3PC>2MPImE0f~jePUI@Q(@QV+I7`c zV=-9s&mofwOGo>|pUm~pliuny1U}~+twU$;A#ITe0bDh3D+v?U*(YUS)aqQ&ur=0H7`o`b&mJ*4?j<&Wp1&^DJSJj1^%T5W>Ut19!gChm=y<)tn zf?Fc!(b`Se=1AE7Yt6VhzW+u+dAKwz#qVou0b(0CPN@G7+iCD1%5tgGCwb41z5k=m zJdZOe6i^vu34+j)JK9cc z-QO8M32Xt?=c03a8imiG=D&}WX8UE*JBf$hNLiXY)!}b>Z~of(A|SvQt!YzXU$B3z z;4`NYaCF=8G;N>yTs2ldMWK^8W=I^nQ|!4(hf<)8(s&9k&JYY@ zyVK8MREmnX7El88j~mbG^Brrz>R*o76r5jo!TyiS_UCc_FUz;{(`e?uT)j1}tFO5c zzF$7MTp19rLA{M?mP3G}tzEig8FaE8r)4Z%)VYWe%c6NlP!N%+tV->%1?wP>q`WQc zwI9Onmfa2_kr$IP9H>^SCtSl3%KA0d;|GE<(lnWS zD-7Rla`eJGAY~wng?-pK(M>GF9?B(;4;|6b}*ebO&S zByA~pcl&<2WkQUmwr=|sd{dX4f|)5Y{}(NmA`ua53?9_PH}A3|n{P zPR60uua?Uvs}N9k`-W@Q-_Y+Hh`Cf8cjaBFf&pA8~Vf*^il(|p^n;5YIhYb4n zurixgIwjpIDOJe-48 z)N)n0HVltsV|(h@EVF*K5rUPc820(oi++KC<1|iA3#7IWQ3y@M9XL$yQ*l;-8xfxR z^T{i_@+SD5IT*uvE)l`ZIMCb=DHbWn^f^KK_7ges36*5uoz(yiZU}dL`4sNmB7D@# zG+%MLE?iWP6mjm&Me<2|lCitl&K8>*OX=dFZuW!>*)=68mL+pjh-Cr57IWPKop@=Jb_>;eS+>U6)a7F`sR$++rbB&!oQk{>${$6y z9yMFQBikA}#pW%8oQwQ*V$PK-QGT3>TwipBEe3}T1*Hgj>T2666VGD2XA@z+eoa$1 zk8SzQ$ZzV}a$~dcNF{g-r^x>Ta&Ig)+rL<=8-`vBtnZhqibTItfkpYI)yT#|&uMVm zE6(o#fz4CxI-dVn-jdM&8~bsyFSh@}2>w@#j`j6_{B&1!Oay1DCS}#984PN&nQ3hC zdZu*tiB6*}s`%s<>f7w-KQ;#c&zh-?OsnjtlXBC_3TAE5FP8h7^(Xv18J6ti{vxrs z*yd{thEL0sueMY;2F<7CTH{zqQTR$KcEV$Q>E_^= zQD~F6_np9t{RNBRBeNrNk+OF}d_S(46%gMobmWQUCf}VdEr4meKBc5Ww4ICmvhQ5e zRgF_utmbNQWt12tZi>@|r>z3KxB9Xem+Ere^~ycl9eid6!ta$A5Zm1AVV@>`(&^3z z=0(wJ2$U)ISAvrWqHu9yp}HMS!hL(ab0!&H@xYSRijN)_2FGn9ZI1Z9`9(M{hu#b9 z;6g_}c|g2}oVC3K=TP`X_p}{Xvud#O=;FTY{tO3_nt*M=RP-Im$)2JNTYP~Ks#iYV zvgihkVXB3B`R+Efc8?L#0^CmvETWxRs2`>C8D0-e;uxntm&$xXuOEQS64u z-hF6gdO1D3*7H!cgqeYWXxWW+Ej1&j4j-p3Gxps#SDQZe75!CNbmXyBF|{q+Gj6cl zqO8L5T_5Bf#;ajrlvn1^>hx-5Sa)Wjr-w}i<*vrls0?q^xRQd>qm{p$s$xX zE*R^_;A_BNC=q}Da&q*+dT0%%v(7}3$2kCgON%>UX7xi$9ar6IROPY-!>Rm3S_b~4 zN|9cG<%&3@LZzUFqc7<tc=QAGz)irMNw`?oX6D(;=oA$hMm?-d$o~9|48 z%|vm-j@C+qb6?qmavw3`pHiH})6+2u@&jp^sY!eD_F*n8E{pj__S)x(j72PRJw{mh z)4dQj(>F`INIkl}BmulJ86Kw2DLsH(#6-1G{FZYfC_J5`c=b(4UW7$Y6Yg6?DD3J=%TYPP zuV*1x$J5z6r>61j%bcbB%^Crs+l-bWL$yA=No!>3&Uzc=%Eaw5hQO$o7(okbxq5(h z@&P3v7_Ve`+JAin9!9^tMoOO4;^L~Hk5vfG*(bZd3>cblN_t{?y@HyI7bloa$cRCW z+z1;qcyvKnHb~oMI=#DW@|kv#e#l5kiC^rtvyRHH_tqHFRf z8k1v{1uMhN8{u;kv09OmP5e7-mz@LfNjin+_a`K5*fnL3MONka; z4s!go*DOP9N5{6+HOO=I^Z^~Y( zH!BYh8X63Ul^Ke;)ub!P2rmpdUi`7LxWek}c(O}SmCz-Vv*wTkb+3raQG0TeeX?d9 zb_l*P7F%C+z;)@Pe}x{<9%HyKmX&=Fy-EoPWvXl%(F->KP4%7y^$ye=Iu=QkJaDLG z26|eq*~hW`jxfLUDOYzRg*wIP(oaY5)rjZm!&#ApXCDt)*A+@vFKLs!vd$8C8;JHp z9l16wwkY%5TS7*8+2Mw+Sl?&J|KWOV;m|G%W26HG)YmwZ~Wd;AlXH;(9?}0fZsesBUc5VtF zbT>@+2M%#{wtC$3fEaf4#w~(xo=?4EoAOxyVk+{I=vd3EaWZ0?N?rz z_GR;2aL}#0Egk1RjBsojG^aEK?sgU9e5f^j=wk@{0cYeqUOBL0Xw+(6r#lecr|cRb zvM#;I%dz}bWBKON)6A(5A#Ih&{bg3S)O>v>yiTl5gkqD%aw}m5b(qq^;oVYhx1icMmd_5$*UGDtzn}a`Sg}t4G5cSwgRsB@?X>ExPYtE zizm9aTqMqj=4*YUMZf4h*p-8lzvQzV*EiTZYIRE=)(^0B3Jxt&sL^?|m#J1ef8MV) zCw>P}k){|p-$2qn85oUD{7RKNeAi0tjG*x!AI7Zy@-F^d=}+SEST5i7*G(zZ;HLo6 zOT3aA#l)3j_#S#MmXz{hH#UQbyGtrtD9N^n?}Joi^br2YMKq4a8SiZDm3EQ-)FXn~ zXZ`-A_-Z+izgL-QUj#boAO=1`S6{=1ulpZS`xrdS7&fC`8OBX5r-#vbDtN|FEv0g9L;L`7<0Ot3DWM%2{u=7%cyj0WiEO)FfS31SX0Uo?jOyjII)Lj@uN(Y>O;3(irl zXX|wX*k^egX7W9fuvG+2d15d=a+(u*hJ{?K)Gb+#_ePA#n;Gx%NLd7#lr&vC9S%-` z+cS)lb|)-3@~Qjw%eqw`=q;TatxJ7zn0zdJpb5(OIn-X7ifKUZ)K!aa>oY{0PL_UK zbgfF0F5qyF>vR8fgoJjxWn7GBkAhr*Kx7-7ruE6-q(I`d`plU^xb{bOw01p8VZjMJs-Z@t9I32>4pwyQx?zuzkR`QFzd%qMFQTZ3wO=R8NZ zSZ~~(leX*5Ub@<4LJ=`BoZ^tG!>A07Aoe?DO?d%}o%nusZGGCbqL3Zwy}A4i#4s)& z!0Z%33?|0Y-)5Lo2kvI^EF8G{+F~MV{JmE&pI{l&xB49VU51PuM4ik$r*KLsI{Qc` z!$gqris(6|YI|O0Iv!y%yw2~~#ico_U#UPcIW#H)0Hl)Qj6l>ZCGp!l8st|BHbax|_jR7FILYunbQrhHc+;XdS)Q6jEPw<5d|8=F}^VG$3FPw-1~NNOEk z_8WaNY!<3zHkEI-VU-7ko(!y$AjJ?nI(ST7Wi-h%^<}05^ zd+xZbWQoS_)_QrrwR5=$M|1_|(Am3f)nLIOhwKk(IrcS5@DN>;NK;OY^@e)A`HUcS2)*G!I z!&&mmdtV zeh}afvAyQ`IfwFGy5>>2yFl!9jy%<@+s?7^W#e_ywaAxrBggUy(|wdrBBJO#=01$Z zv&0?D^6Qw+dWqmV!_j3PCN&}d;bQP-++w&%FWx?32^k~nWA4+EuMtvl+Ks@NeYZbY z5v3)`bMem|t-;Q?S>74) z#wDyd@j#bRA#HhDQAFH_lo$FT8hNBUw`tCnvI_hA9h}NH+>MXEwUz$L$)6&o>se@e z90?EYbE%WHX?cx6`}`#*x>AmUwQzk=@Isd2aXNIl`g`l9z)gKIb^7^4EQ9Gp<|Wh$O1 zjkf7+BuaCdsd{lzH}9N%-L-K}8eN37>)&)iIG68S{OmmO!3o&CG1(IdHk*+%)(?Q! z;C&cx z^A@rZ*=;?72kJN6SCNBoFH?OFw4@Vmv?G74;}e3@r)!fT$W%+0j&Q8reU~EJn6spM ztfuOpn=Ul62nZPC&9&+E^0p+1@s*L(fGhLSal7V*p!UOb<~fRC%ztl|z1w0ACx<}0 z4XE-+8^B+-*xT}dG3tLHvK9Zy64tiopp0WOP$ZL4$El7<@Vl=^(E8*&rXf-Q+;$?N z1kW;A-w=s;$k$zb^4L2Ns`YJ85Jh(mFdv)7i{aj*X9DeoR-O@`-nehM4p~{LYtQ2Z zT)UH;pz|qHj*EHBU*-na zt8uWgNiLR;+}7(V>j)aj;Zb!ZOco8+=ohP>M~T(5QyfRC@;LoA!n?CkjjmYHOvLIi z*0{A)Ho349=;{}{g<`Jg5Y#B&YUx3jyHiez2{j`HJqJCr^XtM*Sdvm+74R;gKx^FN z6S6`p9QaMX{Qo&<+2Hg4KCofOo$9pA_O7?|%n}eXf;-xs>lg!t8>QNj1yhM;Y@r}1 z75oavbDNK~$vG+lH{sjZ9{I?);U{2>9h{T-PD+ON4}`LqVdMG#Iuyc-b<{kH8_j*7c!?MuE`mKWS=aB8r&ukANh9XFF=-TtuK= zq3o^5&5umU?HRej)IIpoUA6=F{{Sm1o(0qDF0O<>O4x8+zCTt;GmU&kvep~Bx3hh1HC)^IgNv>jm%S5Qw*ip(Jv)ec zdjQcIxXb!^$t*|LwH{;N3rF6+-1q^Xg9?W~o;WQE-|T?(0yzA@FZ+lPABhK$STo3{ zO&GF56wb;nKRNSgVss zpdp7{`LTVnCs^zTVt_a1a{_ARHdM9TSDSlptZL$iN`)m@XzTm1XHw4zs7|hlEOx2! zd?+o*j1@crq$w%&o_Kld272jAk811+_Y#;^wM#*vxdkfO+_zgb;NL zMYb~5RN3*nNB9(;Q1`T2jBbwwvHoBN?l!lyv|(v6Ul4n<4 zqs-%3{D1-l3wG%~Be{jD9JD$Zn%L(D`WDz+kKLmw(9_fY9T{zpDGd6yy?7og55JW` zkxIZe^6{Z_JAaS~@JL5<>#P6Q^VHnOL)$;*jVV}0bV1i15XA0V#N6ti#J+8-{_PvH z9z82`knL+Nm&P?u(igNp)U~sa)@wMi#_xIq-L$TsI5S5;FDOa6U#Fp) zL)+0vEiR#$5(V@^_)$ue*^bjLwQ2~f-ijf>=RJXzqiT6hCO)&ZL#dkFP5ADGq590) zyWPdjL8=v5*>04I$>99?#2|3)@Lk3}nPrzyF5pe0pplV-zJ7$p(3m$0` zF2X0jb-Jpv&Yix9EVygr+?`I}gGXW35cok@Cy1HpTN4|+El|`Lwj7ga_eF!+W*!4_ zuz&9EF5bVeQ|}*Q8SHCVHMd!k1t%uG*Pf2oMr%&VPpGp=tfM} zn*dUtuA;Q6eMK*rn8_kO*~Hs_5ZQA5-|au|e`8^5VLk=B>wV1nkARK*)^`PwGIg`B zhh&$kF=xW@_l6PACRl?40z>e*qq)_=os*zwIOuKHx5&e%?+q`9=qO1{FFa8KlmVr5 zSF%*0>#g>A{4Y1SFrbYkr-WW@G?ah*pQ7oO@7=ViIWEo={uvxp4jq$_5}NQX)6vC6 zM?!*MesyoWM8#ZsK5;IHYI(tNSc>?sO37`pAKzqF;G9QSLpY|TuVmP#t!f)k6_IL7 z+H~5=QiI33lGD#^U4sn%pF8@JDA}A+YU99rETx!6afbU^h0;wtkh~rbU+!>tj!S)4 zhm&w!d|$DiBso$PuZDhx*d%_=*X3X3(nlLQY$zb@=0mn_G8D`H8V7YzS*bW!bh zb1hi82iK`Ko_4>CY}<>EGbqILq09?RnGRh!bPi+3kgRJ4I@`2}xP0tzOt*TX(&GEn)>XSG%Y>p*{iPu!W}Op5X-#Ezk<{56Vg@sO z9$VrpLqk{G<8VK!4tMz$lN1KKU~erUOS>v6EK$cU`wd_<)V-#tE=)vx?%N*npqQ?& znVCLevSk;V%3J^#<{Nu7!o<}rwsD?OvfLAb&wV4F{7 z5Rdp(PJ-{~qi3W;F}f=AueAS6=r+3#v63HL|A?taT8Q{^`>pX(1$}TGj-xJWO<)qS zehz9K&9sy8@)Kk=Tu*tDUwipXd(bresi!LTD5af@W6UW(^$_gJCJ${J00ZVrvdthn z5-ex*b2<-ts{=_K#M7jUCKOtq&vNrpw5G@|*5HL^9nJrpaFNdur1wh+y3aG(T(Y5} z+CVT^g-P!Gu7H{nn_%Gy7C3ln&yqm#R%cV08|r9Tm`O#eubnKb4LzCVwT%@M1~%v9 zrDr6Wp@-&uMPHomdzX)t2jwld*b?BwK<&g{pJ4xh#I22QV?0R%+Ts1tEiidBA>PN@ zn`Y|Z)R8(m6}mwp)e2Zuo>wBiY?0*ApmQhND;qjzvHyY+Wo3w%*&0cN_}nLC zDJAh~=d&& zZXQdS8{jU++OngeS+TdCP1#66*FY3jkzsOQ0th#tDMk((B zM-cZ~cW1aoMyhTD0&44RmEzY&%+LpYy0gs${}53fA9Fl<_^C5Oqf9jo3OK!0mt zWgKHwd;mi)8(Cb+J&h4Jm!iZSQ2+JH>77AOYD#eTB+<`6%&ByCZON;Wxf~4s)q!=# zj}GmPukmo3n+VDN(OK{SN-9toxT&a)x)uUCv#NvUxANtb^3kKMcHZfa@mcmV0PDkJ zc^bv(=gl}Np(2+G!{N%sCq{a*m`n=Q$o_C$s1{=y=S9en$nxV+owX*2ok7OmpDv=m zoWfr$YM_mA?c z2)CGO&-90O#Rl|uNmzP7gT4(*rUI_Vv+g`$PHx9wJoPH1v{WuUUsnSGsS6~CTz z?Xa?oWch-JW~w8KUWV=VvPUflPb}yrM9^0xUCNm&ZCdk|LjETG3Yj?FfrC(bqm?MO zD9hC+;xjUTib9s$)7-e=hH*rpY<-r^!Wxkx%*hdoox+MwvQS-jcI@p$A;#WjvChT) z)vb+8(Zas^Y(tw@mSXI9*HUR!f4l{3I!|z2Z32DE8zkAH=Ec6$c-M+G58dzM=Zovy z&?5|mfg#7SfDrCfxH`RLVbRrtxg52+(jeA0P-XdsUb*7FqN!%uvra`+2db50U%keOGTC!v8OjQ|z0nfoP9T>}Dbr2^Ki8c->oc7GXDmExxRztC zgP>Dwo;5tFU3z^!-B6zbipnK|qh{76XUBWF5XcW;!^MQ9 zmbdekdYR;(GHQzxl~liz6c5ja@04qpwFhIkz}NV9F-0q<6R6m1UpWd0)21N2Ru+z? zl5mz_HSn)hBK&?Nv!BK;Yfr=nBpW^QC-#z(sFF*jgbe4X|3}+*fHjq^Ycn<+MX|$3 z8!Ji`6_D=8C@NA^inK&UL_~;44Iv2@KtVu9=_Mjk1R>H}ih_XBf*>6d0Z9k}0wfSZ z+P{L%Idkqk=id9@vY&aLag$xvUgcZg`<3^jQrStJR_0ifxj1gJN|7xE^9%DHM8lYjCEe(Z`UZ5hiW@eL56X zp2+MyP7Zb!8#FM$f{;e^gq#{m8)EZ<%mSK6q~iM)lwU+(&jS2$7>j4yhVQO z+2jMAvB(G1)&7+vYu>#Ber7++8&dr3l z+bD2>RWdQx+INyta|}P(tIJk7BzfbglVxgR0D9~x{p&LdCO{t~2K-Pt=jpZ{v$j3% zrj89WQ~AQGqv%!P;ZCp`^(hl~OY92PP4fhGAckLhuVihCZ4A)bX&uVX6n7yx*oj{mv^>`1B?4lt3Dfgwbb<_A{ z_OP!$x@uPl=+(25g68}hsTNbi9RnZAk^a*?8n>id0+aN$1z($?E&cbWKEn15Joc_o6*8(Aw^5DX7u@znu!g#m^PH4Tbh%UJ>HLCZ159Oc)Oo8cz4_x=aCL2+ ziB6w|Q_@zKSi|X*Qoz&5hF1u2mVIX?=cnV82^DeF&H%EWfa1ksK|X_-ua(CAggbH2 z9^#>BYov*AF^g+?gXCD`;6(y@qP@u6ZuSmYhWNZq01C-Hnbf9=4gy~0A!c?x$6tze zX-nPNcza>$l8`f2a8}cRzmGoQiOruybjP;|E|}Gu+l=^0)sc6_jD4sefN4COP=@Sm zLhe3<<{Zoc#^Q?<#2x)5C?sw}qhd}HOhd;fvz}a-YHSzp0EFvd9E#Pbpj_1PdABp~OQrhrI*=YDp zw)u#*XIf1{OVY(-g?WsA6o0E}7Ci1JRKaxaQL(C#f1=OW6NA(-3K_V^9>2HRqiaqK zJO_qD@tt2}Oia8=dVE>hrhr!Sj<(MD{Or8l@&Ha#ZbPwgdk&e5#F^QJ49|@B;B^#O z{XJQ(5Ki?QwC6CX;a+V33&l$D>$B3t<{mW>>{0rp^2Iy`HRV~7Hz(kv#BJz7HJF(s zYt(=;$xci|A*CY>I$o)mUwBj+!~Le*;i0q$Hq;5PEcio4p2_w?m*QQ`&245-l~yZ3 ztmvatS-thBve3yUHXxM}MVL=x7fOfx%G{o|q$Em@LCxoP3Xeo_(y4-lioV;EK1SP2 zUz%lcPg1x6Q;hanJ#6Jhb!SfZo!S9%x|O|6N2@zzv`0DoSL}RP*KP0NrK-I(F=>5p z*##h0z3Lgg2?SwclPikCTi3|xzLm&^hcR+GN&2co3$&Qf`Fo`F9s(?Tq`gKoRAAj( zTHRsje4AVq<~bSj6jl3*Hf`slAk&1spRd|7>Nxv_gV(JIkMZ@tKI}h|T)ek1Lh~3W z^$qg+6^f&VIzOq3DIBpLtC@7JL>c9sibK91)(pNfTx~$KQ|B3|)WoPcH^-;W#OwCp zVprzH|(p zpQ5;Aae=fwhYj*9x@VZqn_!}`)S0QSIBHI+-&Jy+dbp#>a5U^AB2Bf`O0UTEW}KT< z;pqg3zimBYal!%frk9vc${8?GkPEW-;-u2LKKz|AFhYqebhFL8Q;6>~Rm0xQ@pDbX z`QvA$HmI~l82=p(9=`!G|3|#1ew29v6UZPjo~M0)Fd^^}gj{lj2_(>~KDufmqt6t6 zL)g)SBMA1p4B~jB{;ZByvtulS9d)yT=ou3hGka9L54p@0!0g(( z61^Z*R`aQ`U@ss35NO-7!9m@7Nw8SEz7w_igZ)9J^tpS!ZI&`f(a6Hvntm*begBsC z$2&fqvp6i|Nm+?pXxM0MJ3c%o^+DzELUtfNX8`_-t4Ikl6Q@Cx*wl3dcEHfg zrjGq;URV!T%}OASsfYEblXDpbYllqy9mix8mwcc7e&aieIKevXzerH=D*^^OX|iAE z-+<_@t?BDqoBs`JK;rGlqSd|t4Z%o=gUIaLQ@;I@sw)|H%vmNfT3EPE60^DoGIbZR zikk36R=D_zgEv76oJ5+;EHl3z1@LF(JhFzV=fv=U{BC%10kTyqaw{it*f>Z?gd zA4ZK|7@rnPctB-fCZ{l>5w1(+BB4~25mAPT3X)Dh@dj+`vLj+huER)9R(zTP*|qG5 z@$4hNX&J&3&ea2)eeitcp%AKzaOn%#eev7WoZWCLL4*ur^u~U}w+)bLgn@K7Go@#o z3rl{($(&M79D2C4V1~!K7EISNtB|DiC0r7*Bi^LF4(j(No0ZjOmz`i`#s=Fi&FX5j zAj%@HOJ}tk=Cx#AAowXL+~vj+QRllg9?>Jo$O(@=8AEen0L!9J=J%Q{(EaKzQP+D4 zOr{riyCpOUVE(Tr!Q!ii1IaMLBrq&o1%=ttwF8*4> z-+AnL1l1uIHCyjs@Ike~b!7L~YX3U}I%!qT_9cmo?Z;-2Jn0+KNL}V91^%SCDWR*y6 zn{=?(?E16A4|H+6kn_Z5!U1wU!;d$yauLy;{pIniZI__fIzcsZAS zBZrP_@VF$F-QdGX)Qttp?ZZH+zv_QJZtcfWs`Do|6W&R3e@H543~OVfnkp!YwOd2g zwvnXz)XhBXQM$M}u@N4X;cZ3Q8LHev3}%&Jl3hfx}`Y#}vjb0U$w0hYJ6Wi)E74+~rRyHan+ zk7r60xGptPuzAg44St_12)5vM*5YW*h8EKY zV?_^S+wiy(f+MzjR>Y+`+G>+K56#S`x`+7Hg?q$?8s&{kHdoQm86(bsIVrYhZHsCg z;n>@%#ic-f!lBJQ(Q$X-pI`kv+W81JwOD)8*|TBXOs?6@9&%pmcZ8K`30i=Vv3E8@ zFAm$yf)iXE9Q zGLE7|=G%`t#3$QuqEXg|(3)wm{pgoRXOD%2I)*G$slhB=?H!USAbpB5J7 zPmYvxmt*c9%07&py#94K0{6D_$#c996^*W*3m=Xkof8E%ThQ$j_y+_6YDx*}%&ov_ z`)UlUi0!N;kFa?lnyFpi@dl~y9pZ1lTJbE;?3jWrBUzN7{gG^N5u>4@+hTj|<;|J#x*b%De zr^d)753b!wjIn@SJ1XODp3`V)pe)qo{gZKU=!e@=`*^LXNdaE36;?DZ z1>yFFD>KhX(NXjuI@wNZZ5JKgNPDv!MKh?g9<*p7{nioTKpzZEoW`iYa}X8J`*(9|S> z(|vgsH;6(6rjo>1wSGSEWE>)HuCa+jZ;Bup=?(?h`^2!@Bvpv*4)k6iHYa8KL;Az zo9Wez|3l0TaZ>HI|1p3tmfm0d7C$%~;eTtZrRbyCK*W7!L7gD7k*V!Mi9xsIt*c=T0isiHfJ=M1?27MqH380f8s}PI;OI6|wJ>_Ou4Q=+0&=n;-(W&H8n`sn!)Tru zNSw)vqlbZ0=G~fLcL%q&G9unsE*S5(XRf!$&*o8rQ~8@chrH<2{X^73{Ynkuu0E%( zxWifrLF#72iCmm*>G<73nlW}`pJ^b=*e)u?$6hoOkybk~H-VYpDAtuFNDedA*0-47 zY+LO>Emk$4dPrf6ONt%kZ>Tw0CIU!XuiAN+si(J|WOhqSAN`8i-(6syj|s^$G6V!t zI_OnvNFzM`T8eNLeyr}P)uiiJNb_XUk?H@CrxClXN@xfqOY5|r>PfOna(QRCJ(LpMs6elyr6-gHdZhOo6XDQ$KDBRd z0X{U{MFXJES80R3tPmc5mdl>Pds`__juAq@-&1r=Sh ztG|u~+~2~0PhGXXH*PPlS3keL8F~VLl167x=#^UV#f%}+hEL)7id0;FyP--$v%jv{ zHLGDis|Ac+$#wt0z&ur(jUi*_J+86Z(&DF|gEZ-%GS|tk&6)|}*vpsw_8xv9j7BnC zu(G9D-=2^(vECev0XuaLoJh}o$oy2;8E{5fK?H+iWhaRY!F}Cx2J@M?e!d*AQlwA} z{d6bwktKfINXxo6JzIA1;4&fVPTn;o$BqU+)YRe6s6s8V*knqG>j7zcg1Qr#mN#?v z4*4XM6p!IEv2)rdHz%Seo=bx?xT9x#D%B(Kdx25n`*rLUe|)aCGXkpa^m$g^yKu$6Sc80=-^r=7B#btY*qmoe%63n8=<;y5%^T{w z(+HKJnwe!fl!w5{{iXXY5)(C+0OtVYGhIn=gFKN|V#0k?3Pq{^QivBW`O!%lBm|zt zs9kVTAIsd`Rh;|g9>yYvABCZzxT^4cMn)gq@thS~th!mkIW?U>!%)6v+eK-C*?}0J z>%pBhsyTNi3QUA^O~3uzVhXrr%pXb!qLqodDe-w&vE}KY1^|~iSwz!(^!yvEbuF*g zOH;~!9h&RHzB-9qEOxXPyXHJZ7h|m?(IelEUd`9yI==WU_L;AF_ek6ORQi|J4NdVL zHQx%-?~hCj@6lE6GEGk)Vf)56qKDTboF%F+RzH=!!Y!^xDos}nKGbr0rS(iX8~Cu# zWj1;zor;z>KF~Zf?CsQkrC&p#$BNZVRc01|g34Q}6YT%!xzpkawbvqY#a<5$ez&L@ zf%jcdHc6_8w48J;;{pTc;MkU~$+7II3*7qZlN?>h4Ki2W551w9{VAquLODh$p<+$9 z7B<+nwW^E4?H--8a~~UkwTZ5e=gXpu3gh+|62DGbZ+%h%Y;Cv6o!RL~I zzhXKA3495VliGgVR>FFsAnn%1MnW$PgPgPb%r{V>9n*BME+desf52YvJS({8Yy1o~ zU&ri$plUdty-Hfb}QzM$#{;zV6GRqZH$_KC9DVI_*0`W5`4TA}O zQpD=73j>{5CISyKBP|}JO4^-e6*34aUSxmZJ~=o=gpycUsHrGu?%JManiVs;N)zs* zKfhA+l(R%YLw|n=@e$062x&WXE|tOO9~yS`42;EVpbv}95FUO&fe6uRk9nP3!ynIt zbWI&Nt8NhZAf1($t(Ipe49Y}u4`*9ce?5X}%hpt>n#@xLPK;;!AFHNj>N&KdnrbL< z*k}eLr6Yo*r_1!uP2WQAmxHx9rMdL9x^|6}h|Ui)Sqo>mHPdHd#$2QW;;p>!9vGfj z!dzB}MbVs~HT&u1C-vIXwTwawUG!L#$N6Czh@P-S&i+JyLP8ywLd>N+7&~qt-qR!! zk6t-(^0cDvZg=`$8(3n0gHiu_}_L+n?iwut7!T;$5hemNATA~KIN@I@aVtBP%M!rP-pkA{p&ntXIZRutvshDN)(4xJvn zI``mBll;VcEiA%?tfCK0QD@9~)>$iA{ zzk=~ubOBy~Ud&K1E$_L*&et!G)f@@njC~%M9$zcer6O|OYwTQ9ISn~h2HFb3TA6S7 z=dHe|DW>Xu=GxxomE4KOW|x325w&1Mo^F(6H9J6p&I$Kc9 z?YL&O-y$`!6=p1J$%{o}kI5tmz}EY)2bVn%$qG_yqftl3i>p9!o{g#V6gbw@CJDo; z2S(;$NB$Gjj=|dEq81HjO!^Y}*+9a?CVc;+Mg@DHslX0k2rfA5E({z?xDaccSNsyc zSq=5|t{-EnjA+yTOethcbvun4%?u9A@f>xH(LW5!0){o=VW+&4tAn{qTVoOC|@SrBA-i>>wTl?9tBda)^?b5Di3Y@+8N%lLVK!Gc?% z&s`1irn=_ips92#kYq7%iyqUG1RQHh!8g4zx|&A(HsjYOLj;wz&`1zkPdp6^YMiAH zo*?e^?nt|2n?cZfctui^&liDESm1Eul@8pM%+XD~eL-JrzhPz-)yx_nDE?kd$KCoO%0nL}{QrI(bUkSFgNN=81Lse14AeppR-+S_fnh^>58 zi=HXdM8DoW8U2-?WrkM~`%vuQia#x#q%2+Uj`4pMn3_yTCussA#WTzxv+j-OEeN7i znXD)RS5!RQJL=BH3M?AiCzZDsHeUTbgS^x)V-S95I@T4!>i1)N>gT;fFxaukmyK@2 zBQeKAK)F}?OO`?H*V|L`L~~L~8B~9PB2-WF70PtrC=iUmkiJssTus{`cBXgIJ3>q8 z=GCUmN`i(S#~iBSjK)0C5Y%g~>q#e?(?aOY^ePg@rP7jF8E!Z)Q0PgOYjzhh>ZZ&C z!HA}^u?nK>hd1X0(1kg_&-F6yJ)~JyXae!&?V;}3eN7iFQC7+2zGJsXQc{`=`EJ9> z+|*l-7+=LUK#vL;1;<(D-OSiYfeZ-bh zL8zLfuxM|M{RHC&IXy^pmSZZz#Oq>T@OxT$U}u+*J)xP z`T0MkWb^a6^!87JlwXeho8aCUSh0oSV*AlMa|>{3otS(I8JWl`HrFIM4be%_^&t_) zQx(P{2kY)hNY{Gy#QZgL%$v&coOr+YRkUyTje9Uhw1Ei8R}%cHAbi<0d}vQ7 znkz?1<*Z9sD{W79)@NPBSON=K&=Ga&qsO$|9lQi@$Lj<98J- zvSG&4KL<)fZHmQK@N93DJ%#4i{z|kPJRS0tD%ZIjN1(gKDMo*61)EQIeDP~XW5GqZ*zEd&dz~+vA3>=?FxykJQr$JFdXH`n@fERrOOiyEd z3u2E-JxRlPPh6@^CE?&|`VgEpD~i|PUl`t5I4Nr-8aL~#!iYk;jptV?opwjhJpyE5;@Sjr2_@5A z3H-{D>hkzu14Cul_O_`^H%`Gi9kzaDe{(F-bxfNPir7n-JW$Av&!Z|xr=3x=h0VEmazkZ7T_L#X7> zR)s$0tP{a1XS3On75*VlU+M}u1{BK((m}f?P&po`+FlZHiNygMNyM=Rdz^qW2bsMj z;uvs67i$(nss*nkXE-km{E@8#%}5ZYAw{;TYGe{r*M9;MEO1c6Ae&m3PMOlyxFz*H zRIqTV;uGK#$!s4KXXaQoe%$*-2~g@tq_jU0EtRty8B~7*v|H?5?*6$B*O=A>T{cRu zyi`7A=zv<_h*PXNx_Tj*-BwFVmq&hinx=A%0Q}4jPsm+WgKjh`yKX(PJ6|p=r6Y%Q z%t$aHNg;V#2jqkt;M|l+70_#af0re*d`l<4%n!!+m}+^iiMM|yk&n^6h7F~CrDY+|f|I1{j%}uKY1(xcZS)?w^3Y4Jg_&IDJb!Pe% zm1(YtdZh2w5rsT)rDS*@(B>7#ti@EwFxU8mNOK9U-@e`St<{0l`-!xo9F^2Vq8NkX zT?Z4?q3QcA6Q9_Yvx7IXgb+p81K8I_=SSy@F66NvW$bb-oUQz{V~YGSeN;1y_K{`= z>+{GtTJV;=q_?i|Qb>$JC%=}Z20bs2@*#paEYjW+oFKwF!7&*@9)GQ%*D4F1g-YZ7 zW=J4U+ZT16LKUo&*oYb(W{^6n#|Rm%Xk zrsLA(#wWe|R6yv`qT@fh&}WV>%d>mHMXoA}-u4?8RdPM+%+~!>&nAD|4XI{hEMUp4 zicsi2c#YYU1rY^IjDmR9=;E#Rt{f0!#X2e?RnYJwWsQ@IBk!Z4wok~>`YXi}C}-k2 zCRy6Q1nEsL9;5eD1cibn-`WSWef+T!&J=ZL@rVZk6&)~@vfz(NuZZ>hD!c2T^Ti+& zt!s!gA&=Ol&nPJnQC?;>aijbjqIASZV?4R$7N%a>FmXe(qVMa7N{DetTWT$-N%PId zZvRWfLbT*wSW%pV$tB&@CsyL@u$T-b?7pMe!|$+N3K{SueK^!-&#W-T`On+vT9f%%(?$;M*Kjx|b;iH8K zZoC#}@Ag(`EWew_4rZcoG3~g^Phu=?K;Ay$9Q+eQ<~Bo5OAnR~ca8b%(F(r|XzV2m=CZwzj>PFc!0dY8vtx1A z;gFCnjp_3kUl1$|>B4@xd%1JS-OVp@&Z?QAp)W+8UAPYrW(OqM=RDodn9M1vui!J@9c z^WMAA_%=J+SY6_lq1ZXW=9ma}@qQVGOLZ!Gg+_bSXU1Ot4v#wa0AGM7FULsDglXzg z;R-GP1Rzpt%5a}#(r_OGGu-!D(r5!ovF_YPFI)7yR2=cqO+|N-B#};rC_$^HO(HfJ zzNd8N;%_CZLi#4n|sqo*~$3D_5>WF{8I z(io@-%KG`GofWT?daBK3)Xzx|`dxG^4?0iC7J;-LFv6Rk>%{Z0)w>Hqa(!*Ne~H5X zOG@Aa>X-ZJz;Z32Q&?xbG>hxHFC0AWj5}a-doz8J>fG7&|kJahFj` zw4p$N0j4Wm0?vUf7<6X%*uE3KfT8qz$}32Mx>xx$hzG2FKXyp!f!N_5SL4R_l}Uif zrR;!yQTRbTqQ8MGQIHB1eZzI|^yB*I-$A3o%1Fj3J0J#F z5LqtHi`gUC+EWG`CY9vXz1x;EOb)#}tGfkuj5+yJ%~RR!y54(NNW6XhywC?G?BLSx zrg+PPG#6RDgQu+TM6bVfQ~ae8vcHnloa}t|_)L}LgY=Lb{;8`iQ=n=0)eC)?_=zUJ zIymjtjP^9h^m*lBrr3{)Hn=`;1|*3sXSIS>S<_FAC=vMe;FUp(_9i2zb_0c^CDKgE zi`am?_MR_JnbP*!5s%?ylnxYW`ho--1z`6A89bdG^-((g(H}NDMSoHP$$#%l{64?* z1D$2TBKmv|bshz{DzkV{Tf>_4Kg^NcUaDd7x*+rHwUPG`*-xKUkPvTu41Ib;V+GHJ zOXFVjPqtx{Iu%mVTi=`M*9w&+LWPwe^=z!5eEfj&$`PC&($R-Ur^!Odw&>z z1kW$&vJ&l4M}Dow^)*;YSh z*amv1Lm*LzAE3_4mPlDT*4tnpM24>&E(`p_&uJB=O|#UvTW`)ux$OYgMhnp`KLxUN=~L>_pNU=yLJ9jgP$YdIfQL~ zqQrmntVApWyjE@Hq^6gf%)GE{j=va2ZNITB657qd4wu&?M_*o-SYQwJAT~s2^fpcv zy9(DHcLfZiAUjt#54N{~KL!%Xo&&S>a(|`LcFO!nEd4PQAGD{u8wqM4lLfQfG2gQs z@5wy^39H^f$`aP@%v7$IF$D>fHp_i-{ODxqWFYMx@JZ>4 zZ-2{x2G9H-B{$dSWqEvRLi;(|6*we;BueqI%+NnEXcSA17rwm#SnP$$!t}rXwtSVu zzvfc^{`k;}Y`ZDs_ODgTZdKoF@EWc`EW z_50)hKgsfcv`NfaZ%u26O?tjRHm$>>?+&~BuiTO^+NQMJ?DXG`%~&>DK1&y8cZ-kZ zZu9Opv0*W^V{UaxnbIMyzorslOZwq9q~QdZ9aD6}vnWjdxpm(&$UPR}Ic zPSKI!(=jpFg{f|$OU#Sa{mnmPe{`1nR~N(WRl=Ri;F z!K*$r&l9uoerrdcJMxfcf~waM&fEJP+V&__CmF2&hx5#j{j-DXz4>Qu zCUvLIn>STdQJY{p7EF$L+DCEV9X#XoveBv?6eDcoyJ{>gmw%~=Vs?f*IxAY@>O$hn zep=H}jF`6cwA)5Zz32^i46@a8EL#!nNB*|=UA|me04kpvIbE`&nHcN{%}~ip%dx;X zZC+#cME*BssY~@no2$IFNb|dJ56t2(=^=OA^cM=0#&xb4(|jo(Yexx@G-EnasTqL` z9M(06IhfAqzz9{CUELJlFL6PHOv)V7!C(OsXzTW3<00$60uBRo-_O0c<;_z<;^xB% zI;eGee209*+S{2Y5ZpcJ7L`nyp?O9rd)Ns?m@fsLtPh$DoIoRWm|1=!vyIog`irb4 z<+gv@cmA`2X>#CLsb~p<>(V{{NU^S#W4o$*y*KtvqNeoc{CSSN|Gsq1Jk}_8cMy?1 zLXranD!t>&+l_>Tb}Gw+as#_9_Rz8LwOGXt!xcko>ovJ#8cVxgaCX-nDRsrWKJXbi z(?YZD0iRA%s*l3_cl;C}g&-?2Lzrg8o2oU>Hf~{799?}%-qij*3Qan1|MCQ3TYuxs zJTl2jLE;7R&o_OT8Q+&9qWz373`$22qyaDFGqTSuavt~`*hi;>$f*>QCJ-J!I%Jy} zxbGfsZ5eN*(S+-^!0PS6<3@UG)-CW5F*hG+N_j$s?dSsG}5p}Po?MF-yUn|Tt1m3-|?4DqQ zo>|v!HOTZ#%p*aqB!H5smCz{(3Y2T z&w&z9e9M7CU7<;DmLDNeV5dL;dfw;ey77x5MhPAz^cC__Sg6 z3{+a0Y05w*Xm@Xs?>m2l|j+BZm%olY(pjd-s@ug0WO6k58=kgeB%?OOBCM= zO8G`7v*27`Hb>qzN^{Ng@O&tUT0!oNRFn|ci3PkH&+_BNq$N|Oh9yg{f4nNr{*u{~ z0hh1N8sXZS_MW}-)-D!9U5BP;L$n5i&q#e1=By5+;-}LVFv>PkKPkmcxK!;0sjKJu zXJ33N?P4$NQ+aRRR5ve7&vYDV{wZtM*q&4qAe(MTO00=p*2g4XoPE$cc9*4x;aw+B zIB=g#uu#6fAQB_l#PIihfh|hpxDwLR<$8Hg`YtI@IlUwV!Y*8hdQxpD%d~`75_ZvY z9pxFgd_otbOA)&9;Y1J^5>UTLJl44x)wMMhyQa0UjKr<{S{USlZ_M;uJHNMhs^)^R z860Xuih`JX6wa-E=X#psXnwFI<~E)mB_S{}EBpO6ie8U_NsqW5SPHTP0MOz)1B$h& zO;AGZ$fjM>lX_6tjqCfZ5tj=LzEry`XfEawVvkKII9NL??v02d3~shDI}YhLM#aMr z(`a<=Kt^Bl$w~-H4ZofB5;L2zl?BO~J3GBK)?`1-KLErT#8*V}p4*c+Vi7|4_4aUr zKJ}={lDddIHaH_8u@aPr6~DDTEH?z=*MlulVHW0T_TkS$YPP&bNXn@+%ga z{3Sx~cKN)6S$uzrcxT94)J5 z7%fZY8Z16bmNws0(YwFE%OxpXTRY2U5j*(s^Hvau^3OMK_aB>gM*g97mt7x4(quJ4 z`iie+%?~EXr#c2CC5JV+JbKINXvVfJBpvf{-jN$9V=e73&@+=rsr~OGMln2i`&z8& z_on{b`pbbo&28dQ`jh^r{ZPC*^l2M+P#4h2OGqrkg3!AiN7XjS5l&wDB~3JWNvCXW zvz6#0t)rCgTY`~6GXCIi29zRQmGLem8NK-l1;AZaUqUYJta6*BoE6&+ zBC`_gOa!r|?7IxWtI}P@xZug?e=}qgM7UHtQDpXNyOmKI!FP6yKBr1``wiY3h#Bhq zjf6?s|CplBz#zLNOECMyk}WxKs1=UA0isMS#hu8^Dh1jf{UyvLnG@-lZP9vf@s4;{ zYRf?Y_Dir{vv_&$}s_}&m^t=F)m5Ek7Xh^k&zl(dS-5vqqxW`e=gePx@$Tb;K3O-+E!@OXh5GQ+_xvi0M<#;&09msiHSH@B=f)$K6S|H=neQm!v>)G#X zvwiA2<{Wp-zRmU%6;=2GRN45emAwR1uP?nmlnlzkg#&RX`!^>a%Jd!VW0Ahv2}Z;?|#_3$sYmQmCBZ}#8pbGvy``@9s#SX5p!2l@Qr z=h2Qy2L$N1-u>n3G9YDiA86ht-9P>-T^A2bg>Vs_Z5p_V!N)p-i5$9XuS%2#zoW6U)&Sc^ZFsEL=Gxol z(Mh{*8f0g`3E-ULdds5xt-~h3OeN8-w5*R@E?F*C@obEQ@XtJciy9FI)*>x|@-l#Ax(=H1u7s{Nh3hS34I+i7Xf_bZ0n*r^BS z;UwiKk+$ZY$-pH7i6}S~ZQ;6Z{kv>Gx)s)v1*=hIZZA8Rl1x<$xIOId3n~)=d^9ys zK6$vTb99$t?9>TEGy5Its6(y1*48kL^q6NPmGJHBQA9VyW8{GkH@R&@`zpn)ITE{d zzomS_l)WIi$OZO+Q7`RpC25=##=$xq9ycwc{}PnH-b zmNhRg>?!Y0{bE5NP?Nd4L|5WESC2a-eDl1SUj~zBfpg3P4eK_2MKa5?kZ%W)%+z3A zHNR#D6WcMqHd~mpsU+kcW>)}%sX28xS5o$Xc%tgK|J4kS-12^Y*i7hjQdU00M{O)# z9dQ`8;qlsxWn`8pUSNnJ51`WaeA&U96xF(_tYGyjFGSO1Z^+$=$y>?l@UbH{UO2g2 zMfU9#@seSmS=DZ3Ty@wJDE~zs^ty^`w%tX)hXvXkH`~sgbU0G%wdTaC59iBVf``^V z!B)+sc!&`gB=EBfu`On_RugHMPu7sR!aKv{1(8+5d)6~?`UB!uK15kH%42)F?kqTn zl3=!_yJ+K{6#n^zBendX=?AK?O}IsCdal zh+V$Qk{TbGmO$m=@|HU98I+7JtHL@OV&Q{4jOb?LY~{lZslUX+E1UE4B`^5jmhbw8 z*9HVCNI*?Fv5pm;1)33qJsJqNNmcUsVz91xw$C@QPHa4t(Al{Bm@OIp@Y@Q*-ZuA` zV3aO2Lo{3aqjs1inp6)r-KVZ4@UDJFDl`^l_`cEg(#BJ_TnQ=zcq{?J=iV}A{gL}T zRNlykA7 z7QFAF_-^x!)2y6T)>fTx<+)Op?!H-a9$VIsrKH476Ku(tep!P`(*QuX(?iKHBov()0tUFsdi<{$wa3RQy1ScT~7^VMf5qsNq3<1Yn?-^nikK1G_YSA z*!G&ubBh`UW@I3)!KXg}cqMehofZZpesxV0Gd)3|CLLarr^7UlV-MDCY;!uOUMZM1 z-XKu49oUz^XWNTS*vD?FF{Q%Zz`uz*;Kft1 zvL?GpZ1Pz_#-h1zLM+Y_2@4H4TaV8pk{~+l!agqQNRGRS=_<pA z{6c{u0o)xi{5=SQ@?P`ukL5DbJJZAsHm1Mss0leXPh;xtf!W$2&Eow0%ixAEn?E94 zoK*5FC;;zL3$D5?004H1%z)so!~GXti{|@&`#Bk)@{v@wO_<5+Ta-E9b=ETB(Wik( z`@C>ZJM);5!sAULP|GeeVmbUlo!SLyxX}pt;N! zsHtf_lF&QBnErqgO8}<-!{d7Q=4G$rm6!FWjJ`dgF)FOJt%!~VzoLGlqh4KF+k&l{ zI1HxVL@W1T)r`546_}s5$SZq~{g?FAb)v26YWUJ6I{Hlcqv4J#-u{;rcYh>+-2k$s0g2tkI=)&HET_-YblH*FV-YKs4KK2?Dy@=9XIV=* z9jU>Y9rC&MTF#3vd5YqE16foEP>9=VrzYB~<8$+3CT885?Ow%uTr4?`cXJ$brVc$q zj_O?%ZNd%}H-CW`nN8a3PbWeRW2!0|hh)IsFd|zbkdaYkjLOHwY`y$8SB(G1!OJqN z{_b@9JA{%5-v96DSHD!vE_*L>{2a^W@&5l z3v?s^%WspS-tu2@kcLzGqi%HZM!0M*H3Pqb1JEqr$_4;xZ4;=mnPMs4b%Cwa5CABN zHEnr0il?Q_BO;xcg#G-W8n>c4wANpS)cM*!aGHj)Q=7_CJH_9xro zd~HYm@`c)MRVvc06u!{BL%qW24^AWjzHpxY!oh%wzgs~lP>=I=67M3R!TZ4E{z0w` z{K2ccK#u8AUJZG+Vc{lNiLz_QGPo1&_Bs9xP;uAUY1M!&LkEy5gVJeOF+kG;C8b}) zd$iKU%8@#bHF4`kt1`wp$##=Pim`6~e#i2VlZ1T$xxXx*_Ym~Ef3H6*W~~ke?leEg zbohA2kIHG`hlasp=U=IxVEO;3Q~Xy$`48V{%1`2}B>3os>D9-Zs>!0oz$&$0-^$=# z)~KYsmYj)%|E`vK9M?|2b}v3bYOMk7+riHr!m*!Y{r|Y|##MJyZcEcUw%^=}M}L@Y zt`0i$Q(4r3niX4C)Z7_byQtuk98C;mweDr1}q|e3|bKfal zMtozaGbnKwy3#stCWKzR{k<3m9=~aZKV5$E&z!Xov@r1F`?!EFPa|>)Ga`C7NBp~+{_(|sJCk5v`eFWG{!^%8)OU04_Y2y; z`?LSNI#_KPwEj3f9t$MfpFaUPU9@be`tuJ);+FpHSpM4OGri9_es4Dze|!EBh)s!g z77nd7lTR*MdD`EP&09y6XutTQ;&*83xUJ*$`!UCk6oczRPh8|m*GLU#2Vk|%Bu6aM7%guIx%rG z|MhF1x{qmI%}V7B7O*w0S{6<%$(&VtuE%AP$_NqiijRMCVOGg>55%k2+|v@b=l0Z4 zNoxR3aN$t59rr17LpbacneB#x8hbBRyn=TSg&-(* zZOrhl+`C{Ul9m|935u*f;DgLphsyW5^Ele8>jYQ3mM9nS!5nGO%Cz_H_7We{1QyZV zI5ftAdBGFq?{TPWa`B?)q>)V8Xt;SWrY5ARLaO_$D2?s|Nz-|`o9ww#9gl&7ThFyhjyQ#T;|almjNX>sNZuC{L5$@Aj> zY}bw%XYSAM6)m5gJa4nhcIJGS$RspfyvidV8x!iIq{UX;18M~r+X1mGw7$U)d=mm$ zeVj81KgF30i=FN}e`8CHyHY#tfUe3z{VJ-f3(8TZ_^JNN&;BH`d;9xx);w(xxvrg}{l|3Q(_gCH{~v2_0uJT- z{tc_Xr4(97Wviq_mKIBv8LdK635BsF$ueXa`ll+|2E&kqBKtBJL$VCU zjCE!(X6Cs^-|z4F{eSQC9?$=2}MgIq^=%&v*(cVp9xc9GxTf*j8iNvcQydj%9}&1KyYsh+mshBUYYe48q1O zvA#?A+h~OB76@mPT>54R>Opi<27c@wl~!o(}B07N1Hx-YCyA)CvO`V19e3 zAZYHDE$^VZX=@ma=-SW}K&1CycNxO8t_y*_9pNnT`%06_WW<42|2wsn?`|_h4ln3J zdTQp61~RLKp}X4VxunOwTJ)&hC;jyJ}OzlGoB#FJZU5xST8K#Ls6?W`fu%1FB z5|x|VBq-yHRYVLNQO6&C)p!AV*2N8qvAno$MG5>qR)46I9$dS6hq%i00}gL7&la3B zp9}|xsB8;-2XV1k+S$P&s-=zNI~Yx4oad_bZJ#PA@`r|2!+*UwHFrSl;{;EX2%S(y zS=bY2#Y0LenXwlY4rzXdv&BaR!Y$y7yVG}v{|;DUT$<{>m%>B?UB0}fkC zNr8@XKv?w5yjzX0zB}@;+P7E`?q^J50s|8oaSsq_Nx5zAgZ-k$JM5o#XV&YwE_}`| zdie8N{UL#Unf__}lEpxmy+o290jzxV0>W4B0=F&7?@92Q9{MG_4)@62r2^*`H|Vg< zbv%4^;XsGBG0%eV%&o1hPqV1)-d?I)TRvoj%$F<6w&${sPfj|hEq+~oIKJ3%0WxE6 z9^8@^Jmp(|--+qS;Pxu<(WERHim zvJP6*Lm~DiyA$z5AASN*jqs(+FtsU0ybZ{-n?8M^Zv8UP4(hxp&|0VE_?+Z5f06v$|FZYs z&gW9*Cf+oJmTdG0Ujz9}t^nO9e`Lq`jN*Oy?ZZx`+6w@p;J(}i#QB|VDNJ=@?Nlwc z!sDIzsSA2)JKLhA2>=Oa9jF_KWg)nT+R~|mS2tySylEeyZhY&&Jlpn?#q+A({$k%T z$KEAR^E+8nT6+}?)Rsl8@pmlAUpY@_4!ufNuZ!P(r&aNN)P7azO#;8SXu3f(n(6w6DEH98BY;836B5tG?`sb1pey*oZaXE>DLtd6^VV>CW;lqnSPxK; zcW59YxDH=l2(>NF2hiw;XeL zqG3LwHC5yjW(H7Qu0yp;Wm!2K_UcqZ?Aq1Ya2@9nn+@SPiW3S>5;ZIl9;w6X)S;G)UoDP>ok z@1)v|;bj^Yzv}sO$6pnXxlh-f)YiYl>w!akbsrb6RU8HgLV9=8Zw}QacOcQNM(L1? zF#*hN7oH;e*42IKujAZRQKgNflib~V`*+xT+-VJ%orz~4TFn6}G7oZnE9vEJs`6NE zEy{_p(gQ^gqR1vseCIpF2Dv(zEZGHa=e2R@?=W(+TZh;9k0V2#yp@$aZ``|HHOj>w3&HYupwfX6zwCb4 zymao!o)!g*3MRO*-;{OyCWTHK0OD1-)f!9Zwg$uiBk?q&B)p34F+{K4Q#t6{evvy+ z%GG=u4ZG#DvFZR`>pibXG$Agw#2LF)P*0@Vx3o@eF3KrD%hB5UU3k3i5)(M$Id0V> zG59MP&`h1$9Xs20OJ`mbFp9`r+l9*Xuj{@`>5ERk4j|tfkpkAD6Ke}-P z;3C++N2-Z`J-yR<_dAn1Ka#pKL=o6sw`gOttI49kVLp#T$NAVn&qY#>Cn+`5(eeq4 zOX&#}8w*Fz4BBUf3|YBJ@8TZHk|{`dce58{d~z{DT;M5pxsG;&lFS+`vOJf%tIZ43 zeuwpdc&ZQk%EDT+`Te87kAQP}!X%}$=Hkva-vh}qf&b{#>2gN1)Zw^_Y7OB(oJlZs z@MkH<^$G-j{q}=_c`)ipI;*Fn+Jfh-_(+K%NKKWx zx6{LT6!%KZ9RIQLTu8HCP3(_E<7gqBcc7CWq>3e=((%|k-LR;P$si_wiT{CwccsVs z)vNlOors7rF6ph=>KP05w$Z7ovSQmz=!y28T2Ea2K83EX;HcpaYQh1Wa{etz@*mz^ zQ(~@N?+e(LLFVmnkH1^+C?=6>eueYwFFAQ-*phyOJ$Ez>H_4$f$;6yQL`$sp<4+Ku zwF@jXkyf6UX1%XU!sZV5%@Ngfd4pBXBW^vb)~mg=W25yRA=!*{^%Ra0=v(n1)>`kO z-&#&eahbVwP?K6sNlV|`h=W7wKz^bc!9`ek+=RPS<u+0* ziwTg7B)nn1)ww>-seQEUm*mD9yijTNkS<6~h%ed*2&;IL z2kV2^UWh|{#KMoaWwZ0TeT;)r1!A-`&3X#t?y!!RcvkTiHIChR?ZEK49#RJ&%u+A5 z|B;DH58i6*$#67ejD81H(yzBmWLPg%Ep5Gj z_??Y_1Y)trU+Eibir_e;Ja5^lq;gN;0qiUl|KX|5lP-#oO_>!~s`9uRm1)dvQe=I| zMurBi44Id^_#??db7QQ~XTIfuHJ5LqEbkpXa;-kwAa1!KHNK6}g(<*SZigmOX{>2;g)-aQZ2{n_Zows zTIX^&ZfVv~!F8--su~9rC9w=Q(Pj9?t;n!OXLjx_tm|pJ}&S?gI{G+ZW zF~iLKb?t0p#UtA~djNg)<+*P9tObY%3QZ(haFpN^iN&f;C&#KRP9ftxeHE0Ig~KhN zL^J6h$o|*3(|@#S>9omRlb;5 zY)87aD|h`QBFN&A?CdOC8bj@y)oGc@i`^4$0c&1-aDyT-0<1+8Z!L~0hs>L8S#jmr z&aN8~mG`d&C+sE}|1lBJ@@Tm-Wp3SJMuekO*{F7DCukXMLTzcyM#oDq-lZ$LRu{>Q zu^L2W-?2giMguLY_qp%Gaa&sZw!@pp(s3Da5e+7&+RRMY;eGNCi3%GMrxgr(gT|uX zwrQ8lK4J9|@}!Hd3n(g3qu$bj-cHrH7a?QP)JX;>ovm$te7fjf4U64h3(k*aczqJ0 zbxdpIZSJUMi?=>I1lU({o?~udy}jCt=l?cIYANsET_0$(`Dj-$>J!bTPL2*Dk!Ih* z9WDH?KI8n3MiRBv0CDkDc6K^g|Dzs8Da*lal@qmeu9{oBM#FW_JSDhuM`{w9^$JG& zJEtz*HGQAI_El6i#s8ipG+mB!gd!P_LkKG?kPJjr)`|33ej4yfgaP*^B*dxw%9(3+B(fEy_NA&HaKBC8#oB@dj&cej)g|=Bz_gq zL}D1G=9s$2p2t>u43Qz3X0x84n`pbP4sx~G%KDZsV55(}E{{IqkO^@+t_YkZ`np^1 zBz5N@v#DM`&r+*}>frr~Zh^3SHk{wDe(i(EIFsJq`I2%2%=e>$WcP`wu>(C$Ux>Y|H{mRLUf7FP1VzDE9>cKb_X;aq&sENpr&E}L- z31)d8wa?k5F@o6(?n6Hhq@&q2RI-z@hZJg=%W?L2>W=gp_4R4h9wUQQv9;!qvx8IY zU$=l6-i|GBiy?tr9xvpZVK5X+sb%8>9m2WTb%rllmSk&iq57@^F0bdytCKJ=B7^r-WRUd9hlWB1v&-Y#l<`r1~Mk(|##%*@jQ4bKRHQfxg zYwD%efiY??GbCO*W`Nn&jYeGuiB;lQA3LG!ZRhgz9S_%mK6gdsajCIx#f80 zKnRK&9O$(Wx#N!mtnmzIra0#UU}nE|rxPoPkkJ&!<~WU3w@Qy(_WTvYIc}OoSFRfy zJ?NHp6&Ua7Ls8Mrr~m?!=2mu7k&+fj+f8e`>35O2;hhOz*f1PkkAT3|9uZ21tH~oa z1FO50J(l&lHv0OYmi!TxnMl)s-)sy9MytZytOHr7L+b$bj3H@G8mbZX(8^s*vf?Xh z#ae%*y}hA7ZVJm73v5rbHEv2fCFcF=MMbuotTV}b?gS$?gRUEP}~M&0~nihOB~oEBi!#6 zJV=AADQekG%ZWcwJ^5j%#7Gds=tT-!z-FF`_s`!`N8xZo&d$!mDxQwSr3jQqPfGp$ zn>`21rgZ9GKA4>LLd_qG;J4Q$1k%FjScg_6^%{HfEf=MW-vwJo@T|EP2ZYO(mN;%T zHckcf*~a-yz~JRrdCrIs1k}U{9O8xQH7Zvun2b-w(@rG^GrC(3=@TrE1!uEKdNQEA@b}NPTXyam;>DE?# zZd_Ao6Nfl(03t=2|B;L{B%5(9%c|H5%Ua`H1A7xbGJ)N)rq`yK)+6p-1Lb*Q$l-$h zxj zLs+XtzR61DRp#C|MSQw%qSepEaa|;TOLJG7!JNf7#71jJohzGFXH>>rMGztqM7Yec zNOJX;Vi|#*#NEoLmtj529M7&eE9O8!LUux_zy-K$c%ncn*rPEgQ}+Ep)w~W7MwY^l zbZyvYud?2iNvZm7+ISi*P9NnFGzRq%7-r3Sg@oTF-IUVb;%D?N**VaDV zwKg=piaZiV?bk;yM}SuoOcN3-&d3lEi>gk_+Qz+gz^tfo#-}=}4flBig>>2LgrxD0 zh>h{TyAMS4TY&N$m>#>nYu_WBjIAwMZG2ou=+FC?J8cMr<^U z`?Q>v8B!zdi!xJ6IZfov(PsgDyC2^zMlGthWB&@_xb>FNc?we+iw0;}sbvQs%(-pD0AUo+GXsIt&dwNXKM%}Izz=lY9r2rYUix6G z;fQ?7N~|l&{WF(M|E<<~k@3Nqb+vQdtjEU2oE)wLlZ7Nb_8kJsHA_Jz6H;PFL71;2 zo#PMb*XK%_ze!PMs_2?M@Zc_&JvNq_@2EX}rGudE-T_;!iW*m>bucN7LvnU`GT@|K zThmV>gZ)8NSJaR2f4)PD5u+r)*hJv0!`QBQ3;4ZpfCnS40Ye@y0r zUZ#^+GSfNGX!(G#c8%->f@)hJbISY;;Cz~vG73E6O71rgDZ#eHy+q8s+*tpflU~kc z)xGEpx}v=q08n6Yd09{#Ok@$rZc|uLH*kR@z&TUp2B@->AOMdE6z=&alR=ZH~fdoHYv8wZ9R`Gdmt?ivBnak z_ZhDSZ*t(DdubcWt2QkiC42!pcoxZ1ts|o))WBH?)z$U04$G%{r~-YOQ?K-Ca+}oO zenHrk#yY>7W?gZaxJtlm(Ay;Xz5}h1g5`U*P4gnZGrpIzw8i8>!$5yCEN8r&HgX}E zfj>8v=!{=qw?ldUp1kFBV@vn7Zvrdc|NotfBL`l6FpUKf)}=iCRCajd)0KmJ)i`jZ z_(&PDYkdyw*}!H=Jz}#Y9yLp8ecK^8OU|WJdk6vaDJBndoR(r8sUXsPt%aM0j`%Hn z|JxBQuGUMmbXOgqRj3n3NP%-1QOEih$fMQ&c;Ndt0Se_!fTG}OgYG$_l+jX@LfW3D ztvOdhpAY=9(|gePAv)x&$nnd|+A6m?jCqnnjF$QidnjF5{NNtmvXdy(iV9*sN()s|JHk*^8~SxD9*bu9 z`vSZE(n|m~a>I--c03#v!&0<{76S1=6wj_?9{#JwbYJU6K=dLjYXl;ZRH80qnZuqD!*6 zs>S@&bp>Me2&q}M?TsMMTgn8Af&A0{ALwwGPGB1Fe=IxDMUey?Yc39Mb z_#c?|?G@MfoSxF3XKX_uMUP(L5q#j>Xhm}};~Tj8j+mWyeJKg|P7=?{x!C$9v- z+>d8F5=-shAyWgf{5;nB(~esHUn3Q6e@Q_5HJL)8P?em@A`5(BKA}#b1nL=09gKqB zmMBgRfEm(HFcmkh4WV^NYo1|Vo1pg}828y$Mr46`HO{?DIplQXqH2#*J;vW(ruW3HmQ60WUWmerMZMI$i02ka;AvIJrnTddhDUYuVABJrN zJ$#KfwxG9q;eo;%KM&}vd~c|~jl4G>Rx2B*aic%5u*F2dx9weP{|M>WSrzjeg-|5% z^Llu=ZNAG3%h^1I5?Ec+`8TVpt_%l9>GVif?3)*avk=XSm4??ioTl%qO`>?VO-vC$ zXA%I6F9EPA$}e;b7dWQ55i8t%ZG7}FC(YFXw^6gDzLVfG#SAtWs(WzMQ=^q8SCNhwo*iGv9lA+X4DXAcg2eTW9wh*8hY@2k#4{`Bl`^66xRQMh8EwPM+u) zguZL@NRGMJjK%)gexTiWXvu7O#;Qln%DlQAK<1|UP2vnO+!Hs4XM7q8 z+OuzB3u5D7CDh{eN8K1{nrL4qcygaG>d&d=q<&dY~(KC%QUuwf3DL z*H>l{78Sc&(o$2&dpJDK*Jv57I6JW0z2O_Hfi*Q4k9b1JvY~jcIY=7^{B$P%#EqvY zvX0*AU4P+33Jw_U7^d9%B)vN*U?6eG^_vE@cL+a%AK#xmhao$Wb_%D zT0@|;SUMujyCkq!ESPJhB3jVd%~N=d&l0A-t6BiNgMTwAUrZ$}Zn=)Naf=8&s(2+c ziCbzj&g3kleSfARF>v~fX<~8Y+pyJImie9AnG1Ty5-hydRTN0dOs%X1J=A5kCtX4y zOLXtL?y*AT_`TOV9bbS0RS3!_&jOcep2vAnD46G~`me)GD@ zjbpL6`8q1&fm7I-$jncKJBQv=v2R@CdR#wwx%zrry~OIl;qX>aUjV4e`@y4uq&Cn^ zpI70D@pBUIZ&o;IXwTg!rM^?-4hy^%4as}!7Ze_M8vi)#xgDpn@2o$qvILmAv#yCx zZ9h6A4E%J?%{K@;%#@P^;4M z!s3R!mC2J_QR zi3~s;Ik?@?0)3UEdiq44rZd>tiX743Bh9j_8i+`1KUQ}JihRU6nF1|5*c_Z55hn~* zv`SV_ELXm&Sad!IN*lEdqOjyW7R=-dsWLwpj%RoBv)AonLl%Ne4$X1h zrdJt6(_RBjpf++q&O*7n_s1E(K9m%CGG8x3&W6*1DBtxKWcTwf6#hHL1FjQ z%c*T%lF|VnQxW#gAEW8cwXu(IF(L~rLV-WMHivL1I9Rgm&O9Wt{h}7fo=nqFi@KBE z?I{+kCkoK??I%JY1rt?Lh%+9=M-m_9N(L73ra-#z0U>I zqREl647kgZw{>=iNn17*4eLF37cDn{eW$nMTY0*fX;1m-jwX$tjvTA{KVz$(+u z0m1ag8D2K5E6ugwit<~*>HcHv#GH2^NLO(wXggzo~Se z+5%uNpTtvOdy&@^i5e~{!kV$0sXg`L%FlUV|J_$i~{Q!sQk}t_@7IhGec*k-4=qYP0Si#jJ0gM4+>-LKWD2SNcTf zGye>hn(2}qVK~1JaC=?YNZTAKpAgSHezxsxt(7@*2p3#n!5^{B8~&i!_Sm4txqK&h486HGhV8#| zDXrg-0PcrHh`P?;7A1f9NIS2AE@(SFs;Opqi?*my<;Md z8eH>pE(`cy{WSv~1-MkcO;!0A=(f}z%9 zA5{zY1(-EQ%W0eMO)Ji)9x8qaW-4oNH`I!>bwoYCi14QfnzR{S4 zVDqOVcMg$6vx2|9p3}M8yS@=+Y#o12`_|kNen6I$8NsmmT_sVR-+FFyD9_R&02gzo zeLlTPSnR~0>IeGvYKy}sk=t_OlY(_K5~7TiAX6dR?ie;+Tqfhjf)+B39=;Y!XbU*F zjLE~tVvRB-@@kv^g6tkKVK&xcDvGa2^s#;QG|PlxHLJ^3_3%PkVMEm9F!PvO?iea$ z;c2U`w>xYLsK;OR9frU(!6NFs+VHWpKbfIZM+hB zB9+f`LZP?oJk9yz6COCFZz&F7(!_s0zqzK|-=KK9!R~%7PFyJB?j2NakKnqdA@P`m z#3y25uZdieqO=^|_h%ppDldu3V`K_P3%zh@zYa~IY24EKeWk~%U25E|5uFmUaXG~t6Dv6QmpCl8ro2$~MgNlb@J#pqnHw8hUP#SXcP4sUUZF%>8HV<7(m*gvodzo9t)c50F_a|-$Sa~X%s!UXGGwy5je2CQF z?lj|#kn9_}OB~=gh*V^kLX4#kIDUrsqXxwUwy=q}jYjYO z7?%fkZAOE^WKZcr$6eBoC_x5TyeGzJ^gi=?i@mzJ(ucjmc^Jj>a1iwrEFRq*cl++z zO1VqOv9kFN_NA0xR;;N}5G%>{j8uO(;^n=YA}?b#DLS-($pFY$-LWJ6CF6+OnasII z1R~)g(x0BTI%WuZ+qH-+?!~|{VERXMBPV=9Z3t~4&Ui(AuJ^pLZ%2;(7c^Q5>F^?5 zeM4qfm!F0P_ggQLv>t9#mVkCjvKl%neQxl&W_ttp;bNSiy8>4e9R&@&2h`D)UMBO( zhXCxp@cu&cUk%?wF)23KK=a_%TZc4TuAAkI-nBEig3_VShMGY%hr{VweMUWES5#G1 z-40ciLDw4kD`(nl&*VBqKUhw){Z{0;Qq|9V-4J>z3A5I-Co zs<7J0b`Co%B(~xQJB-rp^gTeEG4xe5@5v6y=Q%Lk5ZYWl=;b|O;vflgWP3I;&gW8A z|E1QYW2U8(j}0zGU)D!mc`%Jp*t>Aa@V91Ee*?dq!13N$PY6|0EK2_AO~(B?$W`K! zeThH4W+h2dLG@qJlM*ndla9)3c+_N4LXNd=gT^W#zjEnr8s4Oto zGud%Uf4HLRH`ncx%rL!<$yo+<<~G7VU}L}?2MFLAqqmWok9#1a`Ap$1;VUA?S z^4Uj5q$MRy3QlN^?02Y+IYAP_K;!)mK6dK zNjv>&>6o5Km{erueQajci%_q{%+!Q&=|tq`8+P8M&}=e(CS07j{qoyGj{-oS4H@ej zK3&>i8$X-sW`!5G^WP0zRwdF!P?W^FC27S>*Z295F7bM)&aJY2AS}Hma*K1R z{ds3e7!AH|(gXP*CS-r00q@GNEA&#?sLdEB6Y4fhtKG)6s*MhGjDIfPNfhbjxrltw zr%nBmX7Q(fc8Fn$HPc{LdR-viW@Lx`kF`m|;wxTZ-wPO)@`*;cUMfo&E(%B6^(tRl>?Iie4)4xe7dh(}z%KQ_1|o-d zYHJCc75Q}G!Z+|-z4_|DYwxZIXIkOMTzsV(Rh-C-u9aT zP@I%UA=&i%()8~}rN87A6uby=bmHaK_upXVzcSWDYo-^EFj-fZuW!F?jit;ur4%|o zUpSl5e2Ssl3DGQr)&xo9McS38m4V9BB&oaG(EeHddd!?5F;a=JR+$l|mZ|70-JZj$ zeRn!l#@VAtcb-xatcbEdX{mV~Cb8d z2+-lvT?8P?Vjy(=zq$?ha0dYN(xg+27NTjxGC;xm_7lKpi;rA}x^bml#-TOq-69Fb zH4DdmrC@sVyLkP?No8L2jOnad<-z*JI(0*?&>Bl~;CgcKjRKc(Wy_^Xikp>U`|>5b zEW}9bNgHQ%Ywp@FsSFK>%2<%E5PpG09xgEg{Mx*3b@1}Ds(h9uAIqHj0MR{#Sn<6z^aXyw|9NHOhWo$dkK?G7&gA{97y zmJn8o6U8%d9Yd@p{3s;x0H5X+#g~28JdB0j@@1Uz^ZxNr%Xj!bbKB3Sej_z`#YcHn z?SNd$-^o+{O{ze|E#ZRREms$nYWuXo4c5=uisn3{Bv&>2!okn8t3yAkeo8cwe-%A^@%`-1`Q)P10;|ZM$1`xW{v3X@lQ)G|9|X$tHzvV`kZ^ z+7CSSbsDuATg}HFF0rS^`Wss|XraKzY4^)i=iOiZNtU~S2hxrzTuUG+>crR~O>-i} z8OkK4gaWCZywc#E@wlVfh~E{_)Od6A?e>x;m7JU;b$)xGKe_=TX&u$`aSTpehmn@H zyW5C6i>ZF?AaLvUlVt4t=caWi){%0M2$b2qEpGKvD8rGLwi@)^&X3bwNDj?hnXw>` zxtQ>lYGw%IV$(GCCy%o6g$p2x2>?=R)lDK#LEdM-aZR-g%Z;xjV^?t&JpC!HhmDo4#$*PS&GaSL1U zuH$p*C3Z-GM_U`qm0LS~8}FMQ8j{)WGVSZ6^2ld(Y=e0i`1QGQv{Nk)jv2!AB>T8m z;OQ`0tu@|Y?!~(}j7d}aOjA|v#qN6ETKtm?vb=4eRP?d&x7jt*lCk*uZ(CP7gUU*> zL*Fnm*T6PI>)Uy2*a-=*W7)F0)Ikp&LL2J+%P3ho2KxH*wsP=5rmCfh32PEX)H!zO zocP(Gs{lgE$M*>^`>S|KZ~w}!lYZ7qUqk#<3$)c&Id+|toet`rE59#@bSB`cTcM*) zK3ti{!dx>pq$w|l*u61}B*9wF)59p>4zVcUfiKG!IAM&1?2?W=Cyk}fOAKXme6v!@ zhlw(WQuJX1gOKMds%q4Vrt7@S555TiRNG|qf5kX}zYJ>=T{-Z_bYfd9>RffOMUAL$ z?G;aV%g{l|WebA-+ZU7851(-8#QD-u_L!4;TBM^nr@p1NWi#t4qfgy&)Xi(EO|U6T zP{;eV5q`y+H2{t&@2fNL*~h+ZKcA$wYcAvKZ?7wTc)H~|kC;lc5+*CGn3pU!#*uT` zWMSdlO~LT=7myJ3^w%9-cq8mFnvfKuqaby2RI{Nd>&%B^ah`h<2>%{aF)!s2k{U2{ zxE?Plu44NPZr3EpOJ@L>OD0acJ8UwJ8Q3m3HVC$Y(|Y#*jg%AGNG_;QJVkggu|LkagQyZ$}n zmiE|HR{AyJ0f%?Wg1qD>z8|KhrkEF#gF)dEK@<12Dj%1c3RP>A3I!7!^bO-SQ@1m! zf$g%!XDe3xDzmlsx*AMY;~HfhH%d7)DYKXJf^+adj#!eM)+3C4#Rz(@|0ocStar&-ZuRMpb7~MDJzLOJbir? z>RTX3sWqeV9=zGU@65dTY+JJza=b#!_$9AM1>f{R*?kVOC0ygXCEQH}F@wk(+S3|q zN1xdRTOMQSGEbLS%R-T#LN`g`&)8MYSnjLSfj`>dNwGie#x~y58$af|H=5NTR(7V@L<^I6Ppj`{mJkHrs1izrKO``BI+j&>*lgE;Sa#byDK6L zj0vjE=-eoB?sbw0k>y?d(;7oNC3MBdn_}Awo7KhUdN zZJ(Wep}Y*q5x0Dvri`> z9;qlezCG;scvIS>TWc$Sl^wX)V!d1>ELXm~wcP{!TsN-2W|ToCLRm~2b_!oTBm!K#{lv#@ zz*f-3rVI)cXJNC25#E@YxVj1yx_1BHNu+z7h6lQ6j6t$_wPnF!GY5c*K4}5*eL&kf zd(vvVH|E{-SLJ1T>D0p4qOH7=gD-&CjT_DbC8pSg#OTnou)g5h_(b7S4>6;X6t^_0 z0i?|3$mZ{L!isaK)fpVJzF2kl0)S)Eaz`sZlf4heZmey*5}!+31AF$xdk6XE2GWC2 zr=n~e<5d6DrqI1x=7t|>Lj0ObNSKTlb@1UD*Fih*pPRxaITll+tFwZ%O?G~^Ep@90 z#E_LF+A;8QOlB_GsRy!VhDqjpf2&7Q0Xp&Q_wE1TXkne6ZU+U|nF9*L3;8|Q2R=og zsvL8JExVo%TNQ3o-;GNE-`3uAF~|lBewWnp9cm|`6oEA%IW|lBRtidKe*uu_1dDm= z4Jls4LRD8eC$RqY1AF^y>S!I9kyMr#&wz51ycXWrqsqCNSa(Er{5K9=eiv=$`;|z< z)Vwq#QxYFrJMOCv>H(v@ORIdE5C4JP73LqWdCeL6Qjs$A@7BWu#A=XXlK^V`ga`Tqk{O#)S5u_@0U1AqLLp(9HB&v~Qjn#56cUBW zMk0BU6O}VRo{Z=nRQ9O|w$@2>A1wG{qZsPdScgqCtDCSk_V=%{G#0c`o4Lz`n|;8V z!>@!&LQF|CP*>@riI@AJ*f@(73co-waQ0-dRG=HQhZ*aS4vZ$ZD zp-m?=-%4i`s8QsT%_)|WfvFZ1Lj#;@ATZ=%hvfXwsIQZhHQBwrR_Bm&tFlbez2;YG zrSjpQ0fFXmfwXB;eJxKNTb4Boh($b6qvp%leT_ z3VhljU?h-pHXz6^7`mKKO`)z$zX`yXU@c;Mn5+KDS1H5rppkX#ONnf zu>u-F-_!m-MMwY3x>COTV#@8~raGtsbY^+Z?Hj0{yr>JnOtr)R zUo_7@SjhhV8GD|54O==G@wUxBy%MV3h5%r3_Xly`cyoE8>VAiJdrrHAi>?xoUQgiV z`2s-|o9*?nuxm-TDF$IEflne^PkWpHqR+K}D>L-B^r(j%OuUm8{J3iO4pHm1qx#QL zay5ol(qZ30H)<*E?nHOQ@(cDXhad7Reg>Ck4>&C#Pjg`w*a;7&RRBC7^@#bi0<-L@ zTxu4f;OPqDpw2IBp0I%`Xa!wr4@N6PvR5^ic|ZlHRzfpXn5-yw1CqQu55oZK`EQj#!{H z8!kQm5ONxJkv-Avp!B5sT<|$=GYzC@4*43vwjGi1MXs%A7%PHAT_=<0v(~egm0by` zrA?xre6Fdt4k-yt-E-cKq!v)K_nJyUN(V^c4%Nj1f*2IW74z(tYe7mo<(^!Df4OjZ zn#J{p6o@f1Orfw5CLc_c`?f-gXX#q>Vk+}sjE)1=(g5}xiB?|Zc=wy&)mF`ZjXMMhfx&({y!e|IY>&3m_mzGeIKq++I6+k7thU9s*MMOJe_=jd^M7nZzV1(Z??UtyI`?DR z@BNzC<}D2h3$v`2&$!z<_-*Me=9SKAeZzjavB^nfPOdwRW}Q6t8KVWDRcob6JMVW~4184`67c?>BtS2S$cg0o0f&^11I1n$M4@-E%rtR)h#0|#Ty=fZe4JYZ-(0^WZ` zM&AejB!x2<|y^POLoAVyq(xOb{erfp(_igcsiacno<5@vXhT znznO8wPAPsd;)}&DOP>6opm$#_G_0^e}w#tWhuUA?Er23x|x1%P%7}rbCM6`m94>W zb+~DjljNTXP(c<)`JR@no$}Cl(xPYHGuEGR2c?}|TsG}G3rckbp#+% z4k*`rCpn8xQ6l(L_rZ#s@RZrv;m>s+br>M$Df>mIoRck1w}wd;IUZ}B zs$m|(#*Qs`G@x@-bm#&uaf(G zdKseI%f`&g0Y~&KfVzx)gXcFp2PiS@iZ_-&MO*fp=Gb+e-HU-vOlMU5C5hDRZ$Nj| zwYPVBvDhqj75Ir4XbmQca)fot;}1D_jkr|5h&wMiDlb_Lda_ zZ5O~jg#2{z3E<#?`oXvv3M#m8-dA^XZ&y2uuzSA>L{gj_p7(%i|}{GOnjg&LIa*L`?A<5DHX8K9xB7M79KhHwe+%DhvOe{vi7Tb@&P09klNiW~isK zgE2Qq4mUJ-Xc!Y;MAWG#MF}4SwBPQV+YJQ#-gcc`!&th(2pYx{mQlI0TOW*>YKWLj z=d7t3F=aK~Ru^fE=4C{$(qHsTiFqpArB)D)hu64PC27)s_Plcw(?MU}$nU&GoG3hP zUg4sH^oPMG9-%wj#oCsa!ZNDxvu28&g(qC=VyIG|G=h) zJkD^P_3X~v*<3eeJ!!a{fBO#B+PdbYgB6%^FOGe#*#b)sfo80mB+=Gb?YGrlZvDoS zPzf1x`*#TmZN0R}ZiWr4Q36q^W2ahr%f?m^@bo{kmw2ShoY4GB#Q$W;pAoGAz20uQZ5VGL2n(|1oMAnBhk_Zrv9s&8$d zCP@PE!h0$vgH>1h5-UrHwX(9pDIg0)ax~SA-FXpFrUzq1)13>I&pWEYL#@fUSOW8~ zhA6^i>)?bJ>#LY%ua3p1v>vQd4x%2mVS4YFQ6pvFqHE}s>ro}`?lVrG%E6;t=I7}U zJ>Qf1d0Z)a>g4H$nURsaBMb7)kUyn&Z@ed9E2!r#PZg`#n0(n(%{b4UgnGH6VQ4 zNg;i|E_4dF zNeLG^kljYNB>lje7IvUd%1<2T+y|W^Df!Ui?)CKP(+zWfOaN+NGj+`u2nbO)x-N)p zNODLNR~E&+I81ewaWA?oE+zKf*+$8JHa}c%82ZMqe~zt8C!t5l+QcnYI}0X<#b?vw zmbaQd1K0Xf=`5W7=i&5*nMAVua%0!*e2H>S<%^#rYtzN==Vozlb~;Rk(OSXQI3NW& zVr{B9Ta?^qq`e*x445hKoCy2?zxL1lKE4z)I<|g}M`b@iU)-YH)0}0HBL=u7kGnAP z(n=cXL7WRsLKaHc4q_lvabGO0okYn+I1Es5%Z zpQbhtAOb(WTpwy;bD`$L@Y-1cTevm#oVN|7wWY4;`??YrsWhU5Q8(7!4P^T$xC=fk z$#Nf4`pXNDpOe$*HP_9x%9RHv&2(;g`g8BTeF)CVawhd-L0XO@oBHu_=zjaebpaf` zQzxGBR#{MEb6pJ{u_4s-B9`IN|327Gm)uD=$*A-?zSaR8i>GSD@6C}`cb!FiCWfzb! z`j1Jb1061D)P-|qr6X|7{bLu=_Z=2*T)-jQ-x0k2aY&XxwenP$nnMMrVE3!UF`Pmn zLRd5Rz#afqt7uPEw-U+r5$^X@hIrV&=3kB^Y!=J9v~DiXwm`!Rgxy#|LgV|qDmVBt zQP_!zoylL)YHnQ@HLbaOL2;zzl<@0xcy=RH04^h%cI>+_dpcUiJ60Tg6&_fLL9#Bd zYUD-l>DE;*S%?3cbq?KEBU=3Ym};|`>iYC=k2h?Xm-zhfcr%|G__wNPxF@34~*cs`L39_PQV^U0iu zb&yzFTf-fB9PoJjP2d2MvVEX5PRQwgFrL73x`}W-yHO>jrypfn%n+lVmRjJRWFNKh}-9BsOvM z1o)$m!>M+l0}zN{-1z!E2qj53pscPrJPcJh-f!XDK-*#E{9<1e=P_b9t0H293$&8- zD~at0Ust{!LX?sDCh)8{s|e#21Mk^|rlQ97J<85Ef@BsdnIlQj*5pIE(5&_1QZ@km zXs7k6+0nnT+NciO42BT_#2iEe`FIRw6on_SN2>uy;Kc_nmbwET`g}KCTt@u7jRK}*xru@wl13HMGJT^* zTS=vE$o4&r8-x?q4H`glgqMX_!){!p5}0_sN8%R{!$1CLXjdfp=KVk|PpJ!QJ6$3L z_<)bS`Z}Nw-g9IIK;UVfWl=e&bZEwGA%(HWmJVPTA^@O_xM7|bFqPh^L<8J{u^H}{ zF}ze<&JVc6GFG>t&~ZMwfTQ%r;6icZ-t}rVf=M~azkIG+1u|TxM2dE059vsvi0K6; z1IWME>Kd>Ycna?dh3 zbm6Pf53LJSd_IJrqpVGD6N{VGS@ zTyGS9uPNIL-Ui2clOBM}vA8&jY|VZ^wLckLHGT3O!zY$x73Vs!9`lEui005A!-V_? z_7@7fhl8vAG9?sNSa#MuA**@{?n&;0;8T@5?{K*_fo**01MM=|TK=O8%vVxqi$HSq zlkhpBu0}hyc-+~N`Y=&?IaFKfA@vV{VXXAln57N0)Xj$=I(5eEtSukQuo!)Ios^`1 zKlqo#D!ts@y?hx$CgO5psJ3UnI`qPXCYUdS$PU0m5zM@}>nWHA*6Q<>eU9Z;Jlqck ziZ$>kFNF72%p+UH#e05}{?NbO(ge5oO6o;IC4)jBG&7E64?Zeog@<=>-w6FUaScrD zhf?U%(5_sV;^SS9qshSMA|OV=Qiph5`vEH;(5)JO4zcw8B(9j6I0WXrEl@GB`tolF zQBAKK(~Q!I;QG4#xcJWa6O0)`W=vldJnh^Qg<}_bl(=4i?c^&H4^eF3?cuA5kTe>q zbf1&t>qfz6#?SwGB%T8i(5RLf>fStCmMq*llq}%58DARIeil3wWal5}FMNEiu5UcB z)*a>x&9($1>-BRvlQbPr0`aXDfZRiYLieKodspnFbwsD*7QctdTguQUd-XoyzA5Mc zcEw4e9KRoCQsfDg#^U0Y?T9Q^*S`($-FSKZTP%%7?sscrHg~>N{G(IC+saB&@vkw? z0|@;6DjiMxjrx$LMn+8JJA>(O7Vh*`r&ZY`^f|6S1%GnmrRY5isJsCr*xRhLx6h@u zJryysGM%3sRVSO!Tp6Y&O65O-wHSxEYNAIcphZ4(X1J;BbS5~4|JnxkS`~4L^bFrH z)sat!LpIrc<(KPy{Z~Z|&?SNvSd^jx+61cevsPAi%~+bz?sH4atAK$0$d1>2KY zoNoRGhAZ^ZNdK2S2JT6;A796}v#n>lvX=UixJaDM)y}^eE4@g5G zZTJ|>z)F>N%Fk_KnJ|9boz%y^qmr%yr~W;BU5`TN8S?5@JyjKuiTkWKz*mVrDX0Hw zRo)97(-j)w>d-n6$GqBU2dT#BQ<;6=f#)=U?-9{9n#c za5DoX^@}al4R!gJTGH=DC5)ibgsgS#Q9jNY1d%0w(PL&0M8eK@I*bgObwBDF>MXE} zCXP6jD7do-Nl7;@w3GG4>^n>?{$#VX#3dwxwFW>DNh8a(Gs~XRbZSCilC3UlR3!gTkc7y# zCE^`*^%1|L6J6*33n&7wSojk~KAvmz#~H;HOCxaI55Q3xWzwd6Ipu|#{HLMmi1G)OVU zy^x4Alk+QWOHz(|(UUDBpdb}_RVo5lV0X-~IfykaG4K3{CRayZpz(9$-2Cp!rFlZY zodbJz6evX!L@}rpkg38)f2VGoj!80!dAO2Mh+A-x-W4$_`*z&zdOu)Dwk?#bni=R11aj08%jfbvo7|7DNgjLq846AYY{QX;ddNQ)494aQ*}X50NdMge`)q zazlUXD${jv1_iU^k$Vcv%uQe^#^(EMgSZlD-dd#`io;~$EJP4Bx1Pci^tKFAskucr zE1ks$rTqGof%Dko!T=v>X1|vbY%HDS2T1rPLbE!f(A#ce8KTWBC&s;6-aT{Xi@F_JhwECX%kz^fdzaPoh@>wPD9eldSI z$MRnsJ`L?cnP=hgvof@rk-Pq}nqYdYO3TT{S}R$Hue`bFAeq80+E=TQo&}s#%s1j?r+_o)h=KnIE1DeICE+sr$e+D0}DI zSjp@=ZTURLL;T0DG$=9~ybhd$ri(jfO00yFvi7rzMr<#-ea5LqJq;(`L8?;c-VLr_ zJj?d#L)wMPP_?G)s!$vEiNJ&8YYV`$)T!ZTZ;2B_sPLIzAJ5gcFrq+Q=sc+)}Ni6==rTm z;YL?OS_)=V58qE=P@T9*P-J}R*mK6V6ivhEDnP4~*7EGSfS=a$8=akM0jp3xoRQ89EC=knws)qZ)AE&|nlZrc&(}q(O9;$M03?fC5xE1@BX6W7NQ3 zldOKFDfH(VDf=aLGrpx?HzXy=18LCU-X1=;IAhXJ#?!}L_p#NqCft^%@=4*eE-NLH zhqH8J=9|NYQZkd{-D&~xBQ~%`86OKpkfKZKkrR&1elFxw+4d*B7eM0K4P1m{LNAPvsvbZSbmoHZ=l2e7prz*N5#v z9Va`|LSVG4<$3Yg5GDR=c@b;6CFuhUzm#J3RRVRp&(7}Ir8JeTe@&fCA2xcc`UNJVJHcH$t4#xiy%@fHEKVO%GTQt52C^%h)S|C zf63k2psRV;>S`naztgijU$Z-Unzhr9=4% zlrQQ36_b%V^Rhd{!R{+Q*#upisu4ub@J%M!l5kd9TEY9Su^fz6GGvfm=k~m`ZL?_3 zKfdP-W!s@Iq$4*Yb#7xOy>~Bt)q3g7 z+sH>2GCBTPdLBIYVeIyuNoA)rZw}-&@se;Ntdl^IJaw1KTt-#L0qwIX=1JT+I^v5z zJl-RxMW82e{y)eSa*^Yh|-d8S?)=N6qfhi-hU6@D{esa4j_j)Q6UkZiu|7r87R4E zG$)BcM>wLF%Gd=KV)wP>7VarnEkz;$!j~?h_@$VCUB!8XZxsROadyv^_X7MIEFK4m zSP5B5^8L8=4CG6Fxk@{r-!h63&nLLSrFUHz&UwRX8v;E|62t5G2=D69d@eiJxzx=U zDCZ|@MGTqqU2^raFq8C3!wsQnF`b0e)KoJ&Z?Bc%R6;0iQ4yp91RZq2UH;%eyzOaV z5XAh%#`ACb-D}2wq^@3(3WDqBZEQQjFEjT!Qc6%^u{QOkJB@`$Bi*QyBv}$J5?A&T z7s{Pes-nf=#PNjY0kCz>?iaCd_qdS*@IcT#r54@@p9dL_vJ$C6Pcc>F-!l|nkQDUY zd-0i8@593KBwNnqT_eVJqF#!6=MQ=cg(qikh1H}JV~s-9-3!juMIw61K6B|(D{a@l z%q)Vo6*jhDcJ*JkT>=CM&uKImnhh9P_@Y?vgQEADh6AC>OC_%!PepAX zz#E@~H_EUxx_U<7B_V_fv1dfN#wq&+dNZdZIQfbV>&Ip&0qnH~n6-heZQI}482>Lm zX1Lv5cB9JxPBA_?xI#}oiVO1qHAeJGWP0wx*)z;$CR`4eVZ7^8C#S)YENx<*cY`v~ z)NEfndsksD-;zGpNN(G{57|zpH&K?ofzbcY*J}w-a=r2SFiJMlzI!f>l<1A(Wsv$k zkL4J{I^0Vof%=nBCK@#so?Dbw4ilMu{S$rpDO%W@A7a0whb#mgM@0Mk zDTDXCeqP{BxYvUtI*j48ns;G-JgG-jy$ciNZsSzkA+)YBn5LcW&X0kVh|jmyG#zCN z_IE}!p~{Y%dd5If2;C$&`Ljzx@gyW~X6(egIecvuy^Cy}nT;EX;n!|c*cTTMrB2sr9ke>_(KND(?H4ISJ~Po-nA zcz`=(Dk~CCeF*TmE)*X6od2)A>GTzFm`Pk#Ry!k=Jy#}bX!iN$zOEm2GMlP?PnkSC zZ@Fvh+EFmjvr_K@^sF6KYwBGf`yl|Qa__GlX>I1){tsq*JTu&;s@%pZ9a}f6sTw5gLRcBW!pPGBBS#RTS6UjC3b6@x&@}s^mFhc_--P%| zG%2X|nmgs(Ajz&(*8@p%{NBJG0@gJ}6Lod^gzJcHyS4Z1h)aoAzUdDVV`n?|)t;MF zU2wC@HQ@|8!M6mMT+AQKjJDZok%{z<#re6E0*g4*|88de%iv+n0{U9(rqab;@pO<@ zP2uhwV#X3;YTLEg!+z){UuVwc3;W%`#$lAA0s~Yxh!$Giu_a4s5B!YY_o_4n?=`QX zhu+QxCG|#2=fy*M5^W{87uE0P0}=Anh?><4q(DCvf+!_Sw&*#pUZ)q9Ml&A~LITz4 z6!Z9H5OiB~uRWi>Qc)v6`V}~2^DfR778G4KI$E%}L3M~uPH&wZ4GtRT&Qj$<|IXTW z+`twchbtbm`UC?!BD*-kk%(aWiD{}+L4^lB8b$Yj+i?zXqhwna*3Zzt$R+YHA@tkX%R18XcVByK?H+2vXMF{;K5xEwfdR?_k zfikGf0Xo<<$9S|Kl%4-nZ+5z)GT+nB5_IfJ?^u2u5A6lO_6p$Gt5Gq3@lWDn4xNA& z?(w3Kg@Jatr)p?Lih>6(OEF9N0o}ZdpsRSF1a$`WMXr78Es#8CH zT%#9ux)+Gj()`#)g?e0Zn97+0s~){VkF2#k+xy#5FS!fJ?EKd=5xK~gtHd73sP45> zfV$u!R6~&-Bk{bFTeXv4i{Q`RiHI6z=`iev_YRoGdS*kjX)e@wtWARJjBSqLz(Pi(lX_qnZ$r zfLxl^38K4H`3;er)VjF6;JK3?&-D<1qy;s?*p`CT5V$JA$h2<*J52W?`4{WBAn(KU z?>D^ql1~ptLGU3ccEIFSdk|How0sV9oVYb|CvboyAP@fK*9`Ey84Zse<&5BtisB6Q z2L@6M9JFa%A^-i&-&Gu z3Kin)x!QmmKJFvXEQ)i%O#t}14XnTxMDcdT$W2lTf8d>@{*Y6|^c_YkDVhSYC9$c={OHA-L*%kYlx&6bRXXknl zRqlY3+EOJriCQGN#i&QnN(u`0V+G3k1SBv5e-k_{;_!Gej4b~(=jRRfR;MPidnXrf z|HR}URQ|R>VE;WQh?H3+BCg-SR@gsHM52uiZuc7|xCj0vETHTB7r_E|C&jC z0JJ41$pgUU&liag>|zB@=2QCHB4xql*9Sfd`P{PhX;9(tTISt0Eo3wSSe#fUwi3@= z`#07+$Oo;DcjR!pJNbSHePlOx84+dHyL^DQr_B@-+5^K=)e6nIdjb8a@2i*^3-?vV z{w@c>scREgarYNY+cA$q(89)4hphE5lm%nK4>_TrU^G=2l;yK0y2U9M@Jp zTEG&ZsUUhuF|~;}CJ$@3@|M3ABU`_!uY>-)HU=E~ORv$}<2_`n^t7KNq#irGXwyUP zc;z0i{5Vu2*@a*q62a&k}1@%dIw5|KtUvE$W z?TrAD0O~d@#g`ldh$9GfttRy#FQ9>;r9C%^xgyme&CsHL2iy|D9v1_eX{BJ?3yqt= zl(3CE&ad9*P#6Su4C)P+x{FG3-|H|JE70f$p{jB z00F3Z)i^*pMjDU9H`wA06q(4?-hIk_L?DB8}J^<)(Yp8Z#KL^bxCtTTcqnSTM z_8o$XsuSn6=l0b@hMX=}zr5NR;qs@gup7uv@zf zNI=?0a|LtUhH!fQeD!@3L4Jd=I*;-%Plwhr~UznyZeo)?G9=^3Iw!v0VXDxzd6@E z0)u8paT}$sjxTOazxu!6=kG?Q{s}n%H8-Mk|AE0``IDxN9oBuyb*h1GD+p5nUx7!( z5JYj}ArM*L<|U#5MmfWFV_$KgcM=DMi{8gxRo;^e?9!wjTrzUqr+Y&moANL&_Xbvz zY6gMrA3E3e6cDO;Df&M`HE(Totuc{ZxZ^IsG;R(}w^AyK}_Sc8dI@N)=?Ez`jQ@R7|G#=s3=anOlbSOq>jgkOB&APPfLGMcs;RYhoeH%nCO+@7HC`YL z)SlVo>L4!G{{a&?tZ&`1R}&%b6Y2kTVOM={%yqW zSJ$d=xq4?&V>fWffbVXh@;fx)yxMvm*Ufm*yL)y>qW?(qznq8*q>5Z<4rKE9hn(!y zHNzb^V_+6L7_TNO@YFD6#^WrM#;GX^WF6w#5*9L{{jl00M;b7`R4&&Z5Q)-`Pd^n? z=;&*ro0PL$5=wm_(tv(-M9K7+U!jcSeCC3jTNXNIhY8K$iK!#W^(J%UNw5)R8L!SU z3s*I!6h9|=c7gvOsl|HL3ZPgqs?MB1nwO!?a)OySy$%3PH1MS)=21?B*afCYy=@vD zrV4iq6pNv*E&}rg!tf(AlkZe2CKWEOj&4PFCYa{9`l0Key^%bB!@=sSRF}|C=7bbw zIzhx``ixtB`5W1X^Vf+V@9q#Yx_#SRO<#;rFYmjUqkR-16gS@j2!mE^bnH1@4KL0I z$dA_uRzJ*6V&tR|^M5~&iexL)>f_O4voa(FnI+51ICOm*o@tjX&vJrGbp3CJ>=HUEK*Un@+TO4|R@NFqlPFH+dv&OVQc!8CV z_wn2RwaF#nvt`+3T+P)w7f>ctg)7EBZ0!o~e+XbCx3cg7BaC+&l_HaF@=oyQ%*xWg z1bJSDtp<56b@G59&kB+??aUYSp*F!T)Z9am1s_o}9tqk%+hvZbVfhA%9XV4F%y8a>AhaU_fcNsne%NwGb8&!jIWi<(Wh6;D5Y zmdD zzoK-7bO_i*M!+{{apWcltbN zOw>C2UX$ACRNnT+cytR@nHX?^Hz(zYXq?xw(l~Imoe=I1HHy*bnmP!QLC3wlQByL_AAqmA84WZ$P>BWUu~f7s}GjZQe8+asy!x!HgnA&lu2^)tJe zti@lMIa+`eIyCsw9=})Xi&X2(J#+S?AVz!Y2JB(+=h3pAU(goMp6hcZtdJw##ea_XzAmbhH$dsOoP*9Q1g_H$VNE7a^ujP!`-ZgtRYEn|HZY7F}cO7keb?oF7s{8sl=g z3?Jgi@jZIGEOzkY7Dq=vli>;Z{+!dWLazs#8Zt!%e&T&sE-q7cVT}&WOm>HEuEvxN z?ysA&mFu(T52QtDkFE2CFXyF*_4U3mv7Wz(S{lBn+cmcap~^dD9@8pizT%kUXF3-e zCe|J!|8owCC@mTOjyin|G{erWf24YNwai6tV!unp&+z6_HqnIXcEKN8ZWwh@*cQqM zpKJrSzwDHBt#EIwAv_ghROfPR!%Ir^!poY%48}tpm?+|(E|8@pO?1boM zLZqu}2(v|34%1^gkRz$~`g++NxoJI&m7lS_>GEPkAs_3xFLiOHU2MJT#PR@D!_U|* zD%MnJw?GLh&3<{w#TP_Z39z0ARXktn6j_)~?jBQa+wG;qZ*O22dZi1c--26k9a%&* zdD*}|hm<%3nkF}Ejp=&b)a`Z&ESafcMMxLuaUvnMf6`Tex$&~dN%5C+&C^g8D&ov= z6S;6k%*XXURM7t)wMcIOR)F79cpTlkWOl=wuO{fOUW?X+&h46KqLq%W1oqmeA0O-3 zu?-b}if`{*{2d*83G~E%0r6=ovSEfDDVa%rK}l;~ety21*gZ4#f^uT+;2YeUX!Y?F zry9QsoMbPsG-83J(N~>iv?jIB#^O`#4MHeWk|K29gM8}Av$vy^a<@)zzDG+g3;s(D z4Y-QId*#=T(XXuuc3ksElUJTNO*dZK;%nl-)S6sDvxYYgFa;OUW0M5}CkAh96)2H+ zS=s$=3)OLXAFk*6qZsAzgc@OZc87)oc7MMrY;#cRMm~{ui~EFpsn6O>%%3yd*e07P z+ZK&xuG@HeQTeD+UV@wA99QzUjVdpft4_P0Te^^OxJyND0(ONu)Oq;mnMVE~j%oga zhNOc4XHX(?JhaR8=I|Z9q4hsvCTb50Zd65tqQ|0Au*xBu9qBbA5my%LwC7=(RrO~J zzKdPCIzb(qy8!M1z+j=&iZ=wLMtWZhWSDbe?3;TuKl7c4M4*?z4jJN;L6fY!ExSpV zsh_0Sx_k$AUC9T?SdPzgzXtvb$M`E;VLh&E&x=hzF;YG@i9J>lUrV}09Ipsq-!C@x z5my;cKVxjt9ns4-edSUBj)`MN*m=9|x^kbL%i7UYe>T!#?ka)ZF-1QQX7U(~NQY=A zm3N^ijUZ(yG|#@P1O7GcP?~Ga#~|}zUqkwhHBI`C3sf_^oSKh?rC&}H;+D?f<~lDz zenvj{=a6OBPK_O^rRyH~@+6q)(~_In_PIy1);4?>3w<(_8_Z`YloOO3Bz5)e2uytG zf|O6qlg^FP>*8EjWN%p9!qSpxdvN2P#&NYSDsuPrpJ7})Awwqpl&(K+Mq_IfN&>yS zOv~G=RQB+avg@XxpF{aXHfr#vulg>$X=RmB@=@}Bt+RO@H@VmLz(wfVHu93&-O3p1 zWWzaZ9g5+i-S8zV3oq0L90QrRlkv-EJ%Lvf9&bGl0vq8eLSHHOX~NTXt^GK5^r2_h z_0j0EOQDW3k!gYVWNDjccO9AsAMyUKFJFby0>p1`!fm^B=}CCl=_lp0&ZWoRq2Ihj zhyG0DBce{ndi4*n_SY_~G_Za9FCRk%cD`|-s!DWnwherFbGSiZ7w}XQTb>wQXNNaX zlKrow7)At%JR5ksB6*Y|xg0aecs6jkQP6!y;#A2S*SU8`p8krwQ=5hyD8bfUrQaB+ z$?N$+u&oeH8ciA%P8v;HK!u$2pf+`4$3q?OJa41qwMkXnpFEs$MP$Z_do_3-f-okP zJ3yYhcC4YyrxBOumIUO-BjNS)e++0ooPs2OEIl6^6Bmc2DqZD}SS`~iOk+%1z(e#J z4!u+HO}5nI5^S#g4%RW5w0I@9Sy3V7zR-*n+4uA~YcL7QV1i_|tL13dr9L9eNnA=vv`_ zH-X@Yo7Fhm6+YCZv5$Lw{EH!=^&~voxCqjn)4~=(V2NR+I~Ed(Ux-UawgNh!$nGg< zS|)-rbPl>(&X2am)klo-*^@ron~I(NS~YhfCJ&ieyqmN4^QorLb-NCo(DVSlS|a+& z!S5dzu<&(i6=0McpwV8lY&wOv%$&Y;VsAh*rfXa?KGM;75eRw|vb=jkTpx5Yea4IO zz!Qu$-HGa7sy;5(VeJLC;SJ~t@8hd|HyS-IxE}O|zAN2mGH*EBwEdv{nLG1ib9o%2 zF#E3E-4&#H0b;%Y=fJ^yM-!i+(i|h3_vG87jIvhUlKIs zz}|#hiiuu47`)b%b3tR*wSn$3ljm{b3740?4@|Ig&#{k+beYK3FD&&?CoDMetJG#G zS98nvokOsDPS)L{1UZ{_C2=N(uF4MJB8xscM>%$Bv$p;)O!N+RJ+N;w_Vx;Ig!Pf`uK>-bT7HwtwoAC9Wgl2?Hve!d!4#qjKm#;(&2u{TVdxi*y#M~Uh>?3hV1`uR-Avpv>jc#t$ z-2J8oUH#^k4Aq;isOh3I`s+tQdGcRP=`1(zvz)7Wln)_%p69g=J~sYPTU<~4b~iU1 zS#_7Xv5EA+-mE2uH>OiEzA;~B{ULns-2Nj(r*m27hm6^A8*Zz99@VW3`RMAQM}+%y3xBW zJW?rc_8qBG|73>d(fv<`J+{Q@w_V`Owo=zyPT(j)0f%6AR{K}B{|wVnu6qdbA|M-T zzUk1%*|M)T3PFD3pOzDF)g@)Pr_0kB+J32cD`JQkT%%n%eb5;c+REoI0^$$p871l~ zDKS8xTk~3u`n~_6aRge~a*bdS$`6FDGdbYvdebK_Fi;)v%15QPI#$R1`M|7@*E&m+s6?j*a%{ndoZ#L&>L zz+33YSp2lr#PK8(5P{x!zt}GiE0u|o=yjc!V$bj{6>4%^t0KnjWcmwF=zn{+XV1_U zJM=*onbNG-B}Lt5F~`K8*tXUEqp`*J1`!~_wZ6zA|aj-J?nk8BINTGU&{9$*;9&I z@8>_9@LhOY(fP>fTV$@IOG-BNodY}Z?Xa;x!%T^eNVbT2d&kg-j(WR57y84ifL>Du z$1GY!E+3&&=W;Ywp^GXgqHf&jYLZ-|v7CD>4NO!qh%aK3?b*N7M4t8!tw=z1Zms9P zs+^6~)$?!P_-Dtj|E~RAv&LZjIOtvSZx(|)w_ifSHB$4CT_)}h(ARVrBx#sh?G@n3^$PI*1`{eL45S9L)#PyY}~sVWfy4GB;FfrmrB$zO@UuzPn;f zG_pR4gJttEw;-c@IPvB7_&x!1q2d$sxBZ8W5dBdB_a#i}aQe9>*`}2SnOT(5vtoxZ z8`v`GMfhj4pUj`0d#+Zoq0HYWD|{uz5_b~M4Wtj2y;RC=W^Ps1o0-}bTHP#cK{rlU zX9{)r<|Wa>hCe#5F}twQ%WGM_GdtzG<-s^tNeFGZ_@#?|DrFc%(I^^oIAM!wnsi4`Q2t z9aOT>*B`rhNyFuW$#scp^2_4a<0y@)%1UEm`;f7vK`8ou#b-poMf};O2#yH)Hcrnj zRMKGotJ0xNzRb_DMvp=TcfqRDnd_1lEucaq8X$kxpb(jo5OHDDw+HpeQ<#s@@#xyR z%%lYw?M4C(ol=twD%{q}=_P&*MV2D36@23G?t+ac-s@+~0$8iLKWJB8Qh2qzz>-Y( za_F%u@yVoI7H&>7u(NBX;tHv8=muCvv1|KqCLV=nE#DkalTR04%3pC7 ziJ`5)RLWFIq1#1Nks_AVl$l1$?g1Ct$0~DqeE^$FRrLD&bS*qG`}Na6ql-QL3!9Q? zX;9{>RAUd#X3jZ)SFfuFThBjpo*i|8OPm~|Wc6@F^|EfI{fE#h>95DmYlBvb{&#(z zor*N!I~W;4dZRXXM|c$4s^9|ii&d88jx>Ce*(cxZ8vFp}cn=ps*8HlA^EH0Mk8h!> z*}RNBc;>uzM6qX#4wU{}si6*?95QnJYZP*R*yX7PZ4o3@vQYWMTLq9f=St-3bqT0# z58fHIZsV~)@waUWp&N2esfmgMw;mjH9mG*GS-YI;#>(7*{X?`YnIS!Bt zgT9m6?-4dEZ;e~`lqQN_wlB~{3S^-(!-W1$>b;(t@AH~J<&~dwxlCZ+#VtSA{NhQ* z1(bn|4D|fl&|DQG+I;gUsESD-#Y5m&JIi2t9pCcx%QOQ$_Z~avSV6v-rK#bdy{lQi zJdwF(#S5%}HM z9mmmDml+uY?0hZ>&QtMsJH}Z(KpwaBiYrg%(`zo@*Y&c|$HqU|pqi$=%N8=Dd0*wLpR%*6ZM& z8*!9PaG=tW-T>a5qqZtfA56IsWf@@JThvx9MK2I9v^u_3#Og5S$9f$FLP~sdtk=vX zjHC#%WHDwqNmpOr=s%^u30oF;4?hU|OvO`z_KUtcC_=(5K%$io&Ani7C}7p@zKE9GsMC^g0l~iv99E zO+USbbJYff90Ln3=;7-%!x9F_J7=Z8Y1qlD|jPA5sph+Ksg0jz#o(&W{?M8ejrPRqtdI*dV0r9&wRV$od5_@WN zl~;@fPVbdU>-1nWr<;nsLXDIk4$ZNbT;3S%Q>Tt>59&`$guMDMLYQyS)t_=;s=-=p zODb7;k3H1mksCwwZxP`OXMncDoelJ+?TF-=Pqj-frt@zLR(_Z=JdUkc*R7L%_`RHK z+J*&_K%*F@$nAS8=?$DxD7r!h3V;dY#@THY_^l7?iR$OMb^lt(D~{8#ok3#0a*rT( zmZ8%JUp3nnJ;n^gtA+^{?xTj+S_<*0v>tzt}Nehd+a*bd!>_BS-i| zK$D5TGsR}lkL85ISDyds2#J9$Isk3S^r|DBnVMl7ldYz!XCc_A+h=T5xN1$l13bpR z8n*%+sUehcK!m~OGR;iCIWul8NCx%mLx6>u$1fi&CkUS)+To6-J= z(T=drtesCovfk)g2Be748*kZb7c73#jSwK3FL%?<;cz%YRMg%Jui9G)OXl|N(+{L~ zH{_J2d1^~r=v`biW_p)*6}3FlV&JPR1tpw_c}bsqnnk7@xaO)S{1)c0KxecjF z7_aIBFqaVgIKJW%qfjgmXM^A0DVCPf ziP(+;_D0oF8J1U7he6@#f6=$U&wcybhxcbncJl8BZnQn4DC%#8yTb6lgm(Xro;lFw zmK3;l3h08J;bh=kOGyX$63HtdwFJZw>2K88-j|^onz7GuK4IB+VQeIEz$8aszrX^D z^CMfYgmRVZwNHy5;taTVPQ@F@AxNdr3I#=Q90Lf*%KY>p`;N?{#Pck&PvKC%!tW+q z(2cGYg494LI+jMRlz|g{fJ{>G-cHTlKV#QCAWh{ASihBg;C_EsZd{thD0N!C>#n54n z0lvEhg2ZHiqsbs}U^og+6Q}ino%?ppU@22ZCezaz2#kO$E?~BrZ69``8!-|Wy zxdr2zJNd{^z^)1#(jY?6d3!Vk&}wy)^WgA?72mpWPxqt3|1=bPhvaqU9rgG-&GM)Ez+BElU7L@CS6ys}z=7-D8b*L{ek74}G!IXevZT zVy11lGk54Yww*h}L069}gM3}zjO7Qun_^Q*HzGhK2}#!Qmmt`!;p>XYu{|PH^X5 zpwzR--!}^h${1c0quI*JpQJm`7&Ih#hbFKAr1^lmIIq5&i^Zr{=P`G?FHZtdCz30n z3%rVIjSZ=+d?@?^I3GvMZis75JzDrk-c<9~x2i=!!#J_IpAQ=iQhUI7J{RC^SiLwr z1G(jSEr)atfbL3Ayg@^PaY@uMFJ$1~wr6h2dF@F%^Vp%8pI09(700y?U3Ny5DuK}? zDNW13MdJ>{ep5u64Vmq0OmACm@?%-E2e*|rG?fQvr2>3jw8Qy!IfXkf+m1rY;njK6Q7D^N-E zr~|$542XoDI;;UteTh@RIU5H3^*DXn14!oUH!jVCNA7TydkT*Ex)4a-q10_P{M_#l zjWi}F+g*ysEv&C#yfd;wlU1?)^5wUYueZ(}QQ^sZVz!2NdnO+LLJw;boy^I^VCyFQDXbVBPB$mjm<* z51X!D2>+tv=Ql3ydW344_bdp}5iXRhj&b)TO|h(OCwn&!Ek#IFiZv<689%H>KAug= z3vQjVUtawL-zebb&6ZO|4d2Kj+CJVikdsla z5{dPLBQ~R8gJt&B79D>P)Otz^(=EVQ5cmZ0{*)2$B2OZCU{p1k{Sqlw4L@?wC5t&T#KP*bY=dV4uRuzOS)l?a ze{yNyLWhsH8$CkG7X*E~X^Xc)kJ&6x>BQaIouW zL~(|j`T_zjXBH68Yzr&fmVRN|M2lQu>;QdgPj|~p+MuUsVE0Na2>x&BOqJJ|w3>Y! z#uU|{Nw8hz-F~FlUMlJKVY09YlNjIW`RbHRq)C*5mpK8OnRy0C@2sk}#s<$xYDw6~ z)zEGj(vlnVV5aJ}l?g6)MjM9~_IeLDmZn$rU*(MVMI>UBd_5_sAn@s*PP67rB9Cdu zzLr&*QlX%ParfTVzqP+Z>T7#Hd27XNB0DHnok=VFAmP|bl0h(J4;l#|5Tiq1KPM$W z-&yv63G`JdNo@o1JUn~$W2ruprZ-X&6OW=?UEli-q}@c}6*x5)o%=mgkUz~+3O*mz z4O_XZ-{;<&dM(V)dQ^+q+?k}M-5HiXno*DlLv(h;O6co`#p2Hwry_(O5Y1N<#j zUTuo*WUtI6vfZ}N-~1qb5H|gdI*HFBnk>%Z%9iX(ceWxDH@)uEYdfCL&T>x_gs&7i zT-+}+_Q5B`-7&wHe<(QJPt{P`?-C`^eSp$!Wm_F3)B2D~8`OBx$I0l`AuD!f?~Vzh zxQ*&(^&Qd_EGYQFVr^`RXioD~f=!KpA<$~A!+dF^!z5IvAaoW6EKozjt(Mv!-`ynr znsz9_;bcX;uA>QC(YEk8<2EOb;1u4N@yaJYM-HKvYU<-m`El!r!5V>QQY%$uSt?8K zG;kGbr#F==KP-Lgu#4Fxrqt#40SqAj_+)D{QL*mlwQ>QI#r@UMQ;Ewxi~A^%9h26M z467N?XrUdrNrMuJol#OV60viD=V(I?&}(Zm8Wv{t6-~E)Fm>&^y4D3W!G<7=rT zqIy3qmx1&5g$8aLh_3N^bx+WAek5!NK9+mL2-@=PIh8O>+VuMH=|Xmh*cZ-HbG1Pj z>TY=1;&G7awtLfe-kC$LlEutT!-wCqs<1@&I7un+@$1<;1{UkY)5~dXXsga^;>AAJM2`Yr*Jtt zM5LZY?k(`&I#)q*NHfpAsN9!r%XPyEXz2E@j`fqprINE9BP%UB6YQ6tzkEr91xW|Q zNUuSfF@S_f2qwW%W;WdYvIqAXg9kqzu zWygr5xDqwTMJRbalCNA?x2fIkkR{FO`(p83PU%n@L-o_#ib4{)&@+BM|7*Moo3g^& z((hL7hgmO_ZdkM(44W*;LAhR*_a0;)7>{>3lmT<-hfTs&LqKyt=a4;TS$ZjbuAV0_ zf@?HzPUCYX?p7jsxn5r0?iOm^uI`9i$+|r9=-Zia zdOouC+89JYrHuwuaqKU16o(9WZ>ZIT(;a~?nje3Y-$xGFx?*Q*)5UfpE)q{_Mfk;j zY9ZjxDXb2CW7A*8h>R3!!#=s1uWtk26v1YL&MPBsuLYrmYv- zdG>g~*T_)5BPH{KQPqJ>%_L*S$C+RtZ3g|z6N0JkWdJHEcR`b({*V+2!X-Cy@E`5%v;foMyJ+y3ppjJ zbyWt~=fv_CJUrQ#r1JH|`aTm+_D&6H)ZW|6ZLMm_h^=Gqy2-vBNvw|zm-x?$+YFl) zmbIhw03icex;44Kf%O*c?q(h!QxVOyJX~`?bli#hlF2kvylj)n#o<(2&)}^csXXzL z5GCqG+~#QoCt~(~Zo|mUaAKvWU$NvX>F65LiH}i4oj*g+ul7(KVJ9y(;Ar6+#U^1{ zy6V>qYW!`9Ho;RLHg!W3j)Nw8^A>)=HdtXHO7Du`Hd;A{H`f3|41J?Sd&g~w95OWA)AX!*tG8?-Wc1}D3mblrHincQpDV>0=nL~-^eUrt5!FP&>5 z_kwEIOUi}ynU`v4-fy}70%S}%c_W56ww(lq*PR{$>xqZlkUJNz&N1%hWj?kwYc&{6 zQ0q`!A2F>@q1aw=-lZ4SwtQn)9t%4;z4RhQ|2SLx8SmF`K^miezJ$P5&wDRS`FQp2MuzkIKYeyE>eOg)QX@J9NBC>o)>r|kmiWj(SWl##6jZw?SA)XJMmEY1|8_OG?W70ZE@{TtC@O*`=!2Gn#dL4+1 zjM<0rN;v23Wv3+=^>rkd!%fp{pu9ClZQSRD{VEGKS;5qy4@rX(7fMlwoT_Jf=mWd* zayM*=VV2ZYt!Ph&cHEwxDZxyieO`0r2MtgWr_AHNQUGP*yPv*_rOmqFpC>11ZY z)7(RPO1&288MwI3l3tj_%+fPDUjk7MoX;u6rYn#8iGPHDUzg;1g?%|Xnx)TBv8&1q zTU=S<_B$u5G5Vy;&PPAzCwf}9ksA~a3HU5>w=br8uOmfp)oi!Z5gkW)gpP7tNqTa; zo<~^9Q9mzyNoncfUK^ePz?}ZdUVcWJFjKCotb7?CAK%SL>n$h^rj6(-bsE5W<8%*I zg;c&Q?9QAjT!{x6LW_)Ig8ML+TuGfTO!ou!~#8Vfmyjz7zn}2`4 zW~Y~x9Dj0g!rqKUrTSkvR%wDz_kDA?sCLt*pZK4ou-8eF`%Y4vUK{>)QlIsrJiPnm znZb?gM(bmyf+MqhI#OsoM@XfXj+x=wRm9g?RX*gjA?=b6b47>w`%p6v0=mygr)8A% z_x9$VkSHJ}X2D9PGt6Y>gja}(MX8TqPj}>Xm@dbJ1O{E1luTi`9cN})-XMBfxeM-k z`o33rn;pcJ5q!6$ze_@-q)_JsnDuPsEKp1gK$eY{ci+5W6zt%h$+D6A7EJ1@uhm8j zJp-6yB)>qs!x3@A!dtB*r%Wrz$1!!-&dDh~{+W)dT!6nWMlz*G;W+b~W%=68rz<@% z^Az747@F1QWq5okT#ErvN%b!_Ia50zbn67fbr2~axr2~)j829Y(nZbWck*D7LB|I2GVTf;jiQpCPFBLLz`N% zwRlh38EW=Z2dH7 z4VC@JAw

    hn(+_8VdQ8<)%%yY58!y3JRm7hmP*ke7utY#uVKOdYIyO<8q1 z?|WTP4`W8n^>U>otP#Ec5+zoxuaacWEK1P!mVV;ct7i%9lzW{k%Z{t{ z^U`mSH#PRjp_E_#9qHbVU3H>~{s}iJ;{v#HE|N*^!cxd&CqAk%HgFy%d~_sEqOerU z%(E+k8mxk_QU1(A+_VnCY(u<{I(-q>xnM1ypTB1$=C{snrUJ=V80CsAnqICBH;Grn zPX2Gc&2m`Q4E(Khj8k8;Oi*R;2Dlgvdt@{5X)eWJ;r}|-f{xX9fKu81AzRB5jj&vC zM!Q^z233eB5#T@r23)aUs*=V{Rh&@;!Q1fxH`{lvuQ=?3O2j5lki_`=-1w`#Q=XW@ zuAMY`zx-SJ0`6PFLds8nSy(*qS?$Uevu8iq$*c~!bxxH)MmI7xyC%!qbrifg=mkTb zJUK@{Oz$Xanle}-U>BScO7E0e04178WNO8*6=Sc-ON$ncn6+{tpq^2fB>a-~$F{3Y zT(s#3#$s;MC~8xaQyF5)S+J>T&2F`uB(p*K=qB9D@FRR*jmmbqsGofKiEKYf9g?f$ zt*>O_!g1aVUNl9L^e+lc(c7Tt_uIgcy&grwF6Hw!!lcShyDMOwAJ3)2D(Ej=yrJKyH&AS*g z`DE|yFSa>Q^{AU7Ro!1dXxZOtSm>9zFDM`^N&49ES1zQ+M;6@iGxc3pX!mZQ=k&Q* zY_$k68_!k>T}&c0ND1rndv1v0#2m2KV#*ye+cmrwN!LI9KG@u?3c8Y~$y+SJ*WkH3 zXJ&cTtOn77O`-aCQj@pRJ7ViyMkx(Ge77$0V4f3I^hCC@fF8ZG0XYM^N_3GlcgC@5?nd ze5n*tPeHwDatgOsiaoEkeTp)tL{SvGtYT@>)|7`ud=kDC`3+^nsp57XldqlI_Rmri z3~YjLlcG>Ihktgs_7y9#`7nl8)q!jXy7y{2`z22`qgRCn&OAMKn?&JgZ1gRKq%*P# zqh`o1-#g-5sX`7FvmYqfLUb{l=B|`ZX1KCg=!SN3a=iT4`eqCVarBGTOB3{#cN8+i z4a2`z?YXf%^}4@~RUpQ%5fDyWVCyw@%}pT<{ad9T_H_-SvxNN#hI_@LHe985F?SUG zzI9cd#_N>1@8KY^kq~q#3;IpUZ9HpHRID;5DIiMyCEGB4pQ0b(a|1TXp~|4HV&Z32WZj7!>AkMAWMEI#-_d+%PjiZ(&9(@h9N)-_l%7Z~UU>gELvS;6b(IpcuV zA=*JeZ&5w%0C|Cd_9&+Q5hYt=qiml$jAYE?<+Fi!THCYw&*wCCty&eCRrPyjPNlFE z*o;A8ELicpHp1;;T5HLP-6n}tt(;6NO|V$x+ZTlhpXCGRlc%t|@PGfs<7M#A!^+?x zT_&cq?A)o$K=^sAP|p$XeJ`KGa_5wm=42J_}Xls@(^GeTH@cjTEBHL15=&mHK zj#e+0?9y-LgN>v$i94BOPW?d_ouMD4`#d-Dp<_$HW-x`oWMk%QU@A4Y#udC$Zp}KK z)p_OEDSFm^#3>kG-P*5eBqtn?1V@u*OH4yMR?zI+0qCQJk%Sd( z37u7soPk|FqrfnB`RZVQs?l6tPqE`wBU)RO`q?;R7bSgR4g#fA(%(PK!m`C{&ttg= zH~q6nT8Pd)(iUwKRLUfm%F<0*%V2v-`_S6TN#Otr2y07oU)_XMs@h2CiH+)}qe8%&AC;fJ?s=%Z5I+*^zdmsPgwC9_CNG5#n_Sjy zP#qaY=`Wp2t=~|!+nqS@4PECy16TAX{i@JS?Ds_?>(F|w(LgyEZ9t-}A3g9ZKYUy^ z=D#QN*R1opQ?ZI2N4lsyNi!GEdKA_sY&x}vn*`2(QdpaAs8P*3c-+J}cTy>*8blYgJnx0FJlu_P-I=c1+3MKqGK^ESIY0g>DoEV)VjX{0-S z@Cgt&HZH2sXkknWXyHcIx(%A6Qp=vQKCDuwwS}~eN-GSbqFNK>a6GAQvZU7)o*e1k zf_U+nv@S6}EEl{-CVW3N#ijqAZQ;yDy*=BJFF_R)a_vj-gnQd8-T5aC8aEqv;;}jj z#Fqc@!V0$n8C$J8zrC_^kO0K(R)z3tKjC^n=>sL@9I&RI%^x?3c7{!H(;gt6c9da3 z(uYpc=4L1laYX&Al($QeJ?bH}WEZ5~*Ar?KBzN~06sEr~5zaZ)kYn#U7YB?0)sq<` z?F8s}I+IL6I}t*OJfw7u?wB>2jaiJ9JaGT@`wD4tBny>%t;qw(kPI=96+X|%TfZtt z1g0+6+9?Km(RN)y!X4o8m;<(rs@*_z?2@^+^_w-DTv02I&1r{q)zlU-lge79~-FdVAdEeNv-L^`}iS{r&kYxjZ z@pzHbndGk9U{_|5W;4>@yl`@Cw7_fUx$pU~0qQ&UaC&5Pmo7)M$L5HYNB1EO>N(Hg zBlwM>XGOlLX1_}6Ue!CHDhC0~)Pws{FZ|;5?I-mek;JAFtS2RSK~0+fTtDXOgPX>F z%`0=oq%fuK7252t&5Z^hrILo``!c}`O>j?ZI789~j){cjkl~ata>d1!9frveQ2Esr zj&?oSjnesDwM#MBSe1{&SulC`C#`$TI@{@6T&Azgpep_ZW$L2-mc35dT=2oC_^^V!0MHwmbmebJ{hSJ_#@jNk|b-AUDS2&>Og&Epjia6ifXg}z{-qQxEd3Jh8geirI zz|EITtA16%YY0sxyZ)45=6-4Z1kUfZ;f29%zS7R3nTm7pj4q42BEYphGn6XsZ$QMFNi^d=w z1%M1gn)Kak#kKyXHchkb{SyA>H@#ByS8-w?lWln28dG4Z;T!BKP4%LPz$xsO+i`5! z{+w|Ysr|h^TtD$0nV4KG*vawT7~xloXsjoMPz17ExJrA4x9@#9@FC-M^0ZeQ@G4FH zKD5McYKo&{fjr)o7E;QQS#wkb$AL`^j7ipoWvnZqaQp(7OQ;JqEZk_wO;O%_(=Fh3Qix-*B z#@)Vs@zWoCx6isNyg%{BXXB@T1gL04I)A$nCiVBpCtV*l`2LLi`zvO1+yzft)$!se zmYGhuu%hN~27$ls%-0(kVLce6`4)fG5Gj^GoUGRv&AiM^WS=PUO!J=3Go^l_-^*-h zoox5{O`Kz*P=0ly#hYs#7T!AklcQ;Dy;sct@pBvfMs|vw9(s<$a1t-STg66N73U|f z@^{b8EQhRr`$Y{URjqX?aD9f|b=e-Sny$%3=b+H2&#;n=V6L<%t{Xq1vxC1_F2o;N z@HJM?pu<$n>SG=2Nsw;ZGrP`-RnY}Z8%6ACd&7+1g*85tII5&W=?02n*$Dx=ZJKWq zI;QW{=;yd^GnL}iU3u3Rt8P~DGFe4udW2(3l`wSQo(22Po$k~*CAZI4Ju31?K8Lai z4J1}4bH&o{ZKR_CKI)phT)7ZXP}_78nj$C7)|=pz%b#azGEpOlj(oX<`;)XWl*G9{ zq0;tAkn3mU{Oj7$?gnK4v`kkfr)~LAo6D9)_tFAGhCPqMt}k+^U$MBq@hx;zyp+K9 zqUo`lO<#*s}q%1vlDE+S5TZ`g9al2|peVR7@c`?d`7 zQ?ocDY(D!M=}6bXrY-2(^|cRRlH;fdY3~aUwbH{lI$=( zFl;q53SFjXZbd6K&`u<74~1IfUPGwh$Yv8T4=NGd=foJEX*odFpR= z=SA9{frJCiv7c%WrSwt=5?oH*ZkBI~jX5w{606b^QT2Ms%nLBC?5jg^(JgazBu@_K z6sLX|(CpbbT}&vfZF*dU(^&V~?up`cs1jZ2!))%gXxDNz?1Een}JUP=!_R~0z8ez|V;X~r~ZTmnveacEpeV%9n^lEf;LXy85QQ1>dNQt8|khPw`xo(kf>|#oKf-#CWmVL^yD}g%AJ4BcQ7?> zl+&-%kGq!_2J)zJ4V_{XxfrvWtC4m4$p@1PAc0OoK;rTPwJG(KwL@1CAjkE{`6G(J>Mj%V^(UDw~!;3^a}GE_FZ;a(+Q+|WLLq|L#OX-4UWQyWKP8@)yDdTdEyO^K|U zDRFIpUTuO;c2F4+ab}|O3hSySN$BTc7764W-t$)ke-OFVe@QWcWykk1Ln?sPOo`b1 z(3Vq3@?51ydFs`7V>!1xHb%d!)~$vtS_>NuxqcEnVf(vGwqmQa#nPh5)?Vw~_`wI| zI7Trw&SJw-<-5z?-kU++{1sB_JTFt0JQNvZ8I}C6NF(UYyxAdM#)vK7n*P{Aqq->U zob_augS$3o{+w$HD4JkQugI`Vsy9!65$pChk{N4-wMZmOOMEa!rA;{5y6`%ni)1)o zr@(EDs(jvGPxO7BP^>aa%k31v=d0v|)z{4+HS|FjfQY!xct&kR+tMi~&y5p`^PVmf z+SilU);^%~2^t4HG|LF%J&A!mIsjZxlQxaR}T!&gYjEvKg9s>FD}Y6empo_TZ@< zG=zB~d8`UA>V6gGz83#dh(7U^_(DegHW_n2Z@ss(9S?P1%aK)shxbV`B|qXCLc(9J z=7nUCr#U#YKC0K))Y5bJO1W@;o=0c%<`s>0XiZ%N9R=*+ixOSgc0+}DbaB{Y( zzioyN-^(=jCjpnuJm)q4?XbolSh`ks43xs7c*7ty ziPz$2jqsXfJ@=+4II8RLh$YT&(pye?OZ7Cht+scps!#4G9l>VWq7Y`Qa@Fm&&&s>P zZ{#?twsUV{1`an%S{wfII(2ZO+VpZ?9;OYWKYx#KOF=5+mYp@btQje+bkkg;rxww6%}L6=S1=OH-~foiOv?bCbfQxFmb#QRB%_ z`=hf~CZ5SZumP3Vxbvju)|nvkxrQ8jq_As*LWBBX&dR&F3ky3Lb?=~8rkAgD>(|F8 zN?^FMTPZzbH70i(*7s|^y{Sndi(#=G>1`Jp+uT+YN{iMGPrIbVS98gha0!}9s=lJH zC*XpZGV+TKm^yu|leN`VeL7&N`&S=|J7njg(NkZ%ilCc+cM2dGg38StvkA%(;J~H( z%QR}K+HL62K#J}9?OO6%u81GljD=|qX^mzPJLuA4^Cy<+0V27tt0yk&m5VTP8=2zJ zphB~BShzMBzqLugscBDziG>aJH!-oo!}`q93VL4c9y}ZJqsq9dWlIMq*Q9Po&WJDi zKCE`i-Wlolyb-r>s;t`M1;^K(uPKj|WEE1kzA<-EDPP`f?^Fav*D9!c5-D-g76D^3 zYP*SF?Y^?crDkQ?>7pZJoa#yFoH@bay$`XEa?IpIoK}5KSsj&H4nvnO^6`#LMmNZF z8oW2-M=*aPCkL(?Yrkl2A-f-)0&Ad#e^LGYSq<*nKeZ_XE;;E!?0LU&7lR*aQy!nX zl1DU5&{#it36kcE;$zN(4Q~KfW)t;&v2!t+o|~udN_O$(*4+Ha;HJd{PNg^d3VsF2 z=Pz(;e0WwSLD*D_7-Kxc-2=Vn@iY8F6Tf3n38wXnrTgP}!ORIjma<5ca3sNdHT4m_@|HOW(V0$z3#rDd9~{q1LYg6q?A zY3ff>&;o3$>Vqi}w8`0raRX=HyEiHBfL}9x9ESRZlXO2d2jv+#Abj+Se)G^GqZ4cj`7NC8b$z15)ZX&$55~c772>H?986vP=iJJh!=*^gjm>>EdUf7QwS~!_Y&qhb zXv^ut`>{bg?Pl^B(Q}4IgB7XiYxB=nQ)FK{7O2?gu1{)r_zU>CeIV<9lw-SfuEOco z%a@4?Z4Zue88BCOydO@(Fw+$tqHtcDb~!~Zd-6@O{o*s-4wufJ`eRNGgb`J*VSG6s zSJ27572J2E47$5qO;K|GQVCL$wa9pqy^~FWXs|?HS)Q|N?avCNj@35}%@`uLwl?AwxiVXU?CVmPQl|bWmJ#sB2FBQ6(YI)p9 zzsSNG^A-*r)w7;eXls&{{2=eJ^oK*Y7F=`CcHP%ZP|<8tK<}8RvHv_KIsa9SeDJQ1 zo;J(w@>|tpqVsyP0B-j%*>2-n@|2k!Lk(Wz!RWQL?mxC3LZx)PZTUA#`FfHRp6m1@ zKqDC*u@s|pkdXFHapu7y4|bRSYox5B1qF^A)bF%5)SB)H_hqE`(i`W8+%2kSx)v!z zV*Q*8gN$;M*KS?L+@D%7wzm_?_CxJ>_z6pSb4Qy=NA z2caTLA!BZrBI(Xv28Yg*eXjb=Z#&g-gX2g<;REf-+ZWHCYSBCHkow4^tR-4}P|FWy zeT9)@C&2i;Si-8bw>-2sc^fq!=rpw9g+T|2g#vq{uwnC4bffQRu-W8xH!3wFRc?p6 zwYh59shWjcVhiy(N&3hXPz-Z(a@%;~xQEN+$3}-SFWpXXQfUGliO1e5t7~g1NwunW zJ*G8}uNfFT+E=Qu$~RmHsvd}TS$I}pR2Sb5b9w!Wy^GkpKDnGW)3~y+x86><{P{V0 zHp2vuLD$_2U0Iw{*q9qAClME}7tSa7Ou&QMVpA2juWC=`Z4$e>U43RXs}-sjVaU+MAP zTVA_*J&8HwT2fUgXYD6?L+Gje1PpuHfyTU5UfC!QzZU$~h^D66JjQ+2`<(7nRYphfzmgoJb?!0M zLc@tD$fw}3Dq@==mkdIfbN12bWjPs-pV4!zf^*B?HvTfq1_mzRF5SnB1I*={ueRhp z|942ERz;hybgnsZEm?ESUGUm;?zOoPczTtbQ^v`)SGDg86xyx%~nvST@9Dm@>$um@;I&1IF~c> z;i=GQ#pz9>cP>uQ!SbZydMyN3+H}3R#==`Wb!UnwYFDE)PLrOA-ihD<@$gNQEV{Rn zpIehLEb^v-1jhD#NG?P{;Zkp1fsKTb(HA%Zq|FP&P0?J|vst+d(0YZVS-n;|y?7nagU+=1`d1xG z0-b$V{TLf|4&FF)H#uN9q z;6n29Wy7Cms}x$T_u?A@Lw*{I5LDVtN3E$L*l20jjAUYbYHU`+h3jJORd?Jjyj?dk zsy*MTrjwQT$IY|*Lf3D9UNwV&?e}>25KO{^;;zr{Kh?`d>Kc6fL=}an>OAnTQ5pRc z(Uk>>jjuSsLH~C7M|Op6{@PRsbfo|_mPg(BZRv7>x%fP*juiaqa~+{EfWedof$+f% zZ{TP%{lX+j$5;pwhTz|*|LWE#8Y^J*oP2up)+4P>*?g*lkd2Kg+skW&LGiSh`SGRv zXWLUgvi~qyv=>99J)T~_ZIGL5X*6?N*RE$GGIq*;(Y&A!E2+I^wQA>3cq?3HvNZWr zWn86Vhpvr`=fDCr29(17@4CJ2x1pnLC8aH4^{!ICdu$w&f4k^fwN~Nz3Vf?+(f*_G zfj1-RTHGqvM(pOTa_^+-Y}mY24zYrUT?xU3vc;u(M(6Xa9ZIpKoqhk;E!zv1KGAX& zp6N!%#qq~s0>-LsxE#hS0!@=P6YnU8sKiV+^ zS&O()g?QiH-=}I738^N(u&%aRqpt7v9{M`pjzsc2GHP&-*{iOTRqW_+aBm(};3lS6 znTEA<{?AO$eakE0l)WoM3K>nnj3;1x_*)CC%G{VXpE6JrOJcRk<4Rw6R#mb|xium9 zmUfR~w>T`dB&9Z*F@}@kg1q-9$T!>x#|dahny|1lTv*pfKV?Nu)p?kp`bVqmi-k;b zOtm*1#-nXg*2glP^ajGFSM56;^{aY&Tynsi{%bVvYbRrONL{?8I@sX52x;?=d+59z zZAi;6mb^;^$rMn>ISz@4n5(&!{(D)|*JTJl5p6?xqAuH4Qq7fKrdyF$L7al z^mOP>(F1VzX2;{k&(2}hu}D=D8P9BAjHx=&dOPl~Ro4hk=1$~`lLt&0wdG4vKPv)) zyGmn)1teb#A{}2%%iPyopf$zZSBqcdVYa>WrFbRBv)9i7lG*6xSGdZk>S6Fq63}hl z8=B5&Zi&i35&O2{W8y6JGd)vz1qH@xK4Z|0M3?a8!#m?;on>LrT_Gj7QNMu;HCNb$ zy~}dzHkE2TmC+?`+8-q=5c{n1iwal*GI+hK%3GlmGUPy*qxa;^ z9eP}a-Jl6XHWmj1wa*jXF2m@FYw*6Vs+~tdLL(EU`4yf8KV>o0`MY*9o`bInIVP3u zI{CW!TVGVj>tGMhB`J?sU2w9;?)&|DO*9Cjze$|$!!1Tz?C<386n?YGQ(-j@rj8Tm zYl&5^2j$Ch-bu}Vh4J0dxkU_)!te0=(%uVJHXo1r$VA@B3i**OXIEIFZaDy z5vHDb3-xDOeo>uaY>q+djs3fo@ESSRCAOgiC)BUZIz`G}){CDu!9@)XUni#G{;Cxv z{z|(uT<;_ta#`;6ZS{<%`~GH*4Y?<*942lTjl}fmzOJZR3%l?2>+;1gh(Ld=mTvWM zeT;?w$TJ}I@%XMZ$to+ktnU7*Ze0~89FpI3V$QUU@;vgPWmIai03UBADhytvYVrh0 zOKIZZzFEEQjc6L2cz1YjaLPcd&DD*Acts$y!H?Rbe3GHRDTJPC(rhZHv*@FAg>gUf`V&eL2s4!@iFtgC7w#<>xxKYR`AzdOBNJXLxrSwoG3 zB#mYB?x##8M75K(8o6c<3DrAzv)Q*&9kmm9k$yQ*(@rXBCcST^Q_Nymk|eSfmzOY6 z8~$Z$xH+9Ar=NlP)X+pPl#~j;j_qrn+=e=KV9@kjH!gKnx$d5E;YbW0^n(~{YCa(t zu{Lm}3yoBoYWl&CPEoy|P-&ZH)T-=cz<|u*IP#*I9&CNxt%rE-7W+R(HsMUy)3qpv z1#g^PxBGmEyjmAv{atvJT6fhNhoO-!zm~e4usxIjJJ6N6q85PiQK0x$kBGp#UU$cR z@lqh4E)>kK{P4_Z??s`Y-9!+6Z7@^RiZCg>Y9%v+nJ6_RR~1%z76`<7tzF&a%&D|g z|K(XwMceJE^t5O5kFCvyI5kfaOJUB>29JjKpsdyM?6&I<+pG2fcCz%_R~~ITJEjyh zg=_hnK#yQ{2{=<`Ww{H_$Ad-9MsiP805M5laTs+od}s9@`t#ca+iA34|=$iApQD>eT3xTe}keN@?kY z9nCm2%C*pO5=yYed{Y^2r&Mq;Eo8_cNb2gT7jd79TiMK#o~`8Tej!F2Or0f_YA+dU zi#JKRe)gC3Qrj3@NIWAkRBTik>qe*M;XnAx_Wip!H$P?$+1=FS^6RM&bkv^IBuJXy z?n@EVdnFO-@FK~}ew1)M|Mb2G5ic>>028fz*J>8;J6vCtn!Ft^2N#zU`CDpOHRJOl zrmeNY3!1^sAvjF4Z0_vsyfDWr=v4qVMzsr6*?!36-@VdQUY%T)H@p0#ZcbNCNCpVN zJR6q+1qK)$n2m-uQrO#fDprU*GG2(6+DXt<44Gus#XH;Uk?Z`2;c`(CC%?bqVqN2+SMfn`E68(q^y3QSUZQuSWey83$Wy3Zy`8xUAq zi$hU%{ZRIH=qBzS|E_sgB`K7;o1VUrB;zwaA6U+Fe*(=uIcteq7;*}5zAob!S<|!z zP~^*$1@=>yC%L799ytvB)^lHa)sCCMG_BX*{E6}aCEye=l|+klKYiZsUmMuOUpjN@ zueU?yjsLD@Na7A3%&Rx?v-NJ`qqHT|w@9~UXbChjy}INYX#)n2vhV3nQj~&{G{cJw zjrLqnI118)V8)IDrH49V3!`hrLLXM^1J~mH5@&mdEcX+sYu)F(;QOG%w0^Q9Gw5&q z<5ls^zLR~HNw59N^;ZWo8N|dH-BKH!xGL52YGCA<7lq%rgS7Ny%uLlY~ zj6G~gGt{3BU4)+R+^NnxS8)*xrpfz>$y(5bn7CY@a%=azis+;7-C%o z(UTC=!K2aotI4X zhrW9o?RavaB$l&?VwmsGHl+IFAf7tuwth$raKKei+Uuf1`xi(*H2F##MScEX5XT8yi0LIUQ(m ztr|}D_Vv`6T{BMEwg#xCs1)u)hLrZHEjbzk76Tv%^q`ulsp9dP!q6Oz^hF!u$=Z z7H$w2-(DBXO}8Tz4VOe@S2LNR{T%YUZ|8}E_uW=7n*0_ObW2@cIGgTc=bdX6HQY~j z_?-NpZY1I?`5+G$@^e_^PMa+F?&BYq8+S}O_hctZ=`^5VJzV-Etov-gZ7RV{@c-0w z<$+B9|9{b^^eIOqxvyM_$`Q*MO1b6E@JTV`o_nrRgrcIz#y*bRM}^ri{k83NJfF|k^Z9r_-j7$m=Ip$r&ctn-5j!CTS!>1Abc>I)un(o) zZcqCMpHl-#^+^)1VdVgXmn5s8csBQbC^+9#PjJ;w*ky1M?rg(h`5y+bM=tqxDg!SS z&t5ktJ^`nP8y%=5lyV$zb$sOA`y)ESp9SZX;Ook^E0s)sv20$O9m7m*dGAF%8+lz^ zR4eIxVbQyb`g#?2(qnZjig7u&l=TT40zhHnp$VUUJr3KZjGJayadBi#4Gd8=m*3>oz*!`inEPgm&cZLukxm`br7lV%+OKan@;EIE zQy$>GJ=%`L?a^NfDgOw_iOSZWBA3h;dC)rh(rd37r>pvsXK3y8a1D{Rq%B^RKnz8d zT1_-ZffXoXLcKq;C5pSRCeUNLT; z02NRr@5Q?CphBlQk>8_@G~#9L&h_P7OZakq5$C?EnQ9cCbK48J=L3vXeTb$?gCuix zpoR6<`su=qgJQHOg`M-nFdVZ!M&!ugd<*FHsAlvR?D!KRoX2Hi1#!2Lxuo*#*_FH6Cq58&co%&hvgh)MqTVn)7*(vfjO? zSkVsEyCL_iB{H6&6|)#y7cDz*_Q6;$il5I;B_wMk#mRe*1V+>Qr=&6;zrG?BSdpdVwUf$WjUt!_TnEPG2 z-Gyrhz_4Z)#}My;^x1uNx%K2rU}sqr=PAHVho4WJ+ddg$VB2oc^?gW=$8)E$Vr^zG zN2@*!Z)1*SXPK4Xt#@ejP|UvJaHknkuNyA9#y6T)>itjdz7A8C)P8v25Mn9+%htEc zY-s$mW9{|wGdb(S6&k~%wt#h8EXtT$RMG}TSZun}lzxs-TERDz0F zck|05g^z(c%|eC%)XQ$ny3trFHa|GBI#dAz`8qbJ74Y)bosX3H0C zZRd%pf`a0qFV38|j57E>^lSvwcs?|3skGld|1RK)j}`X&u#{aUacQ$|l5Fab(jMv^ zSq{fVy$H}wJ>>(;r)*7V0)Ih#6hU+zOd{oo%*Efn_Uf{m`ig!%Eu3p2tH(es<;`O) zfer)59{+Y)t%pR!>O_xv>utzfY2b?`=FT#o*riSvOEIn&8=SFXv(#g;_^0rIbsdaL z`zW-zaLx6?&AeZEt^;N2Iyx@Bg06_Jo|2Xl^v_1mYUJN0vdg7+Yzv{7Abkk3$nc;z z-O}{}wdjPl6!-i|4$;?-gEDAB40$yfJG1aim$WDFp@|YS{iKczAa?_j5S~ti9+6p} z=vtc=uf;R9He`k=vMf*7nn7$Fl_DUF_phw3#2IKpYFNbapOI~SLp67;F7?WvxiQdf z&OPm){S7Q!b1IArP!YO!meBqTicCfjb>bYE5agN7F zI0BK@`1KzB5r-`> z>{%gJ8(voP_wzJo-xqc3C*KG*k=oLck|H>h8Y{3S#Y?|@R;=l}R%XxPwFrC|!|EMn zta0iY-@pnY^KF`2V`hfa0Fmwaj&FZ;k#Z}YLSm$-~Gbrl5jW?GEc zyt(d!q_1^G2egHt0 zd|HBUBnX9U(|^L%)7lZrY8pNK*B(qBzn{}&Vq!-ED_)U@r7O1*nhxo5X*JCi>NkC_ z6xD;DR4D_rhJSi8^9zdY855TjwQU{FTWT>=4(4OAYVC5LY4N9H#enz>ItIf+n7)r3 zx3ttjf!Hs5*c@^DSJh&;5BPYU~ z;PA>*#}FvL!^-;~zHeV#3N91w4q}z@w>jmmh(cd|m6V*kIJ?a``1~~Ayns{w?N4*EnpZXIl2N?x7_%syZ*sz@WyTuToUlkqs(Q$FMR`h4uBd#? zXEKKyQG5gtt75M)MCs>PXk?^j?OhN3Q;DZso0P(i z$go~Lo6vuI)uR1>9PW0{4O5-t{FwRE5X5Co@McD|x+F!jvf>E7-zVDH0|pfvMf`v? zpBe^AVkWDpQL3*hIVD#IcNmXkjrB(#?Lim2s-Yz0$+u*Svwx^xMp@0@qjQi?{adw4 z)-kIbq3dpI14tzDa-qJ@BqkumKuuSXdle)X% zG~C($al}IR+eU%QSddyE`?LhxZaH8$z$i^KqJyGK!bqy@3nR9>G3r2Kt!M}}E%M_O zPKVhlK>PE{{5-@cATgoeVcTa`W1N3Gxdx4{1vxe!0_&c5a_8iWGi1tyM5{D}4hdSQ zN|@{~CttiMQ-JTmr<3G)|G!lrly|%HCBO3IDcIVpn1wd|bJLp64(HFF0Ng@##3uOR zeEQ&?L~y?yh%H-`5LIfDSzT5IOSuD_mllIge1rC8;HY;9$s&*#WN%*!S9r`f;@C70 zZ`tyC3dCB)UGoG7XE{^9X9YhtV0jDopWd9%@Nn2ya@+eS?r5x|eS@!p@P+r5?6RJL z*@izY(kCUxvvI+x>7N0XaqMG0EQFp;Iy=h^{F&8n3O%%2t_apKvFMT!{k_l{#m;tI z;#>f^U}tu;ioVGJG71Z;pSwj2V929b;?~?W!AmrE{Lh)*fU&=4(lLSlHf>+DAF7&W z1@Z#SLHFB5`hqz3>jTI_`p`L2^_Mss2*dzPBs8?E?|neIw`;~XL8`%G8QD(9W zBMQTp^bel%58aMpY<+z7L6C$D)p6N(Fbn7FsRzwaGttWqV7KQ`rdgkXnwNiEPiovb zfk7W;2f)pWo;v<`k=z~< zWIPcP6dWf=x(C7_qcv#?QSWewq&oL*DX&R;UP#+&R7tc69~dBzSZD;aB6%o>7)9y$%Et3*AuJHHjHaW6ZTJ!nihUBJ=Gb^p zx(y$6`^wPO$%c14As`E_%2GjncHeawVD6w;9-lktV+M*B zd&MmlJUJdvW=eKsDZA2OXa}wjRj>1|vbA~`5P+;fDgM@#KU6ZEy!!De~!@}3S8tA6Sxvp-`pZJl~T7h_YW;0_`;Kre86Xe%3XJAmZhI@9zph^#U zr~TQN5Ef8wOE-1dw=tnIbR|%HEoi-|0=yW Z6umPzWts*NVeSOz?kzKeO1*!c{vUq*f{XSkN)lIr@2lb0hpzmO2_S!WAJ35g=LxL5kmtaJi_9n7 zbN5s+@Uij;yx|W4yZeB>Zumc7QBszDAR{X)FL71k>a~iCxxnCae+Jum1O)o{gIO$e z^dx1Z2lUT^Rjw+l$jAuDD5%KFsmLfhE+XHtu+SeE>S)snW?M{ns=by{!vJ=8u`VcaosB*rCw_fOKCj#c(SQwheb>*Q!P; zxxM#ASHL!}`SDje6zCIwuc2bQ$sGDa}LkJ*WQ}_E0>Q)_Dm^i12Sn7PdctJrpwca7+@@_Hk{hv}R%6KRa8KHkun}?r9@478vTJ0!o)+@FBslvDFDy6jTYQ;o6LUSulUT|l0S1x2g_=Q6toxg zNw6`n#~<*G-vRKQOz;O6{OGnY;!kYG0D6TVmV5CJgj-AqQ2+!1Em9P@iBmjk2K1W> zFMmzn`)B&RzmoQXIcZ}IvG*_ZZvgavZZDJ7#Ydq|&=>#0Rr?pNsk{AG>>vDEH~>4c z2>pL^P)2ng=OX@HbuOMNvx(fU=xX9Y-!r%V&4>>No-+ACAMnYHUiRLmwejl=jJ2D} zEzL4rY~Z+FYIhdb=`vrF4vd@sDtZhU*FkElc5Q;^k2Tkc{r2;&FW3OXV7@kk87vv+ znSA{!;ncKKZf1|1-ez4F_w=gQzpT;zFKZlCYMC{c^+jJTZ5$!=9ZqxbRr=4uQkVisA+$1#|+7W)>1aGFkVlS{sLe(LBTgj^M3@NiCL*% zg1mg>r33edkZ2Tw(^|e0QHPmTSh$5hkaFtJ&^!MMJ?9q$G0Sh)0k-<{(xUcr4J94T zd7f6BOVGj}$rpYLt-pR(aQWmvD`I{hO+pg>Kl;lP^q?2FSt?`?OH++B=(qF4o{ssE za%F!^9xxGWFRR}(1oXvYv8UzJ<|j6c zKZ77l#Ay5)6wwNbRa*=HDOM}Y-P#5v{n;>6+C-UBe2j@VPyb0jBjIu=Vc9wC_j(`R zVDj-1rjQ8DY0zdn$x?~2*%3WWI==_JjRrO}&Sc><9nChiM!#Y)VKW4T`ZE^v?~Z;j z^~n}fAqWlhvM?=s@Eo2Y2EiQhWoVbwDx7^a0uyE1~n&v?bve0LB8o>8GfZxxQf`cw4k9~J=UUaMYq83WbLbC+rmbY8pu+ar*HffsUtUa$oi$!vI~a#Z8lNmr zQsw!?-@P!fP0Bv+H@(fDl}kCjKyhVf)Hu13P>p0T#0^%vNKXX;3gPo&%`*OUInKm2z$c zLaiI8yVfpHJ73H+i$qW+yNkyMRz(nN*iVTq3E=5rA}yFHx=#@SNw{a9_jwX zV&1GxN6HB!()_rAZHn^&jAP3V$iJ;sNca!S3%sz_Uur@(93cNm$2Kh((V)iG$mhp9 z0PO;u-U*BR_N-*#+-efMDeq>TWuOu!f`Xm7uRxjVi8$>xJ?w;~)N1<5hQ|6a7L;7S z+oRE6=kGUqc`C_?+%ZNTq~2bzaDDASAU2?hRuQeE{?q*6{h;JSUPa)NrF|Opj4>G3KqvO~4wzLq~# z+3U6Ht=Z{q0&K0(V^9$XTg$}QauuqH2ep0$&~sT8U}YQAb=8g|EE&18mP(-$A{f}* z=~gvj6aT=PHq%It#=`EQo-}Rib&~ec490roJc?qr+?7D;HI(@5`!@Am3FFhE+POS4 z23#I%c#*7@mKKeA2W@sF9CSn;Y(bnZBgBF8__(o(*uoiw)Pw2(%qkHk-=1KqWjAH& zw8zR+k&qX;UKSg|Wd84!)~zt^jdJUXmlf?^rj@o~=H*|@r(3+%I%~)o_DhO7t!pW` z04x+3Yvarm##4YWTAbQEF#MI$Ez5KpULK92Tdto?sRjRn`1KuA#|Z#BZb1hT_D9l% zE&$BI!8B*gqw>SXt+{}d2Y!%2)n6CHnEK||o$tdqxAYngwyZ3{$_^0o(!=ArfL#82 z{F^nfife)RE9ND$Aq+UX6ZVupE%3m3WvoJ`e@)|v`@OJVX9Dv7t_IXc z!}E)sNgY}@Tmb#DJ2sd9++v8Hp8wwxE)Q+K!4_K}V$QQ?F+4>!vx>|| zTz~r85?vR+pN`<*S)W|VV$vDd-l;$Pe42N&rG+R-dghmGF7D41bC#DkY-wY+V;&j# z&Mj`GA-0ZSUbN08YT344Z7tmur9_q4gg}Nrr*~FGco<#(KtvEPvid5e?cG(%!d9W> z-S)9u*PYtlGQnIeE*`ILL9C`*u16Jh4`W3%!E{}&y)Ea~{P=0Rdfg}te(9Abkb=A3 zzt?uRbaw*_$cs-p=VSK=_lJ9g!mgSZS==5CvB#7nT|E6hQZ-0k)ca)rMy;MQ=Js|m z-f#~pUJ>|ql0|p&MYoG*ZH`b1l5f820L#O3f3I1=^Dpr^VK+_z?0=@&mK?U{pPI?w zBZ?FnUOTXSJ8;{vKS#EIkGi*8D%0r3t%<0N&q7w%oUf0_D>=s22pCEsMpTPQAotA@r4=?JTm>pGq3QGd~0u}+w+=|f> zj)C4$#i&ev!kRy@pS)7JlGzm3Jhp)ZSTLBH(gi-@U2;ARSs_1rQvn6S0KQiN*MzUp z@SJ~IObb_@48SEd96|*YqgNk@!vNmm|G}FD59n$bOsaHH^^cSqoH^$8GGLUruF0t!T|_zu zh`f(EVq65kV}EHJ9<9M8+`VxtoXMvw&tsa6L2Opn7hoH>(|yu;iaBiJTv*>rwF_XH z7GIHiU&i;rs;CnAiV1@i)dqqTBBz1HLSX^CXQl|66~N$Xvue8+^BRI{xeKm%DUf^s z_vdBJxuE(^d|jh?Ep})!p1il{I^Qe#h}dH7H@)Nnh{uQG{>2nx`&hY_JI{XllPn~JUfW>H!=F{-TWzPKZN8^W?^YB^8n>IfGO&>5C7m+?KBIZ z$|5npGJWVdRwI_7JGG0O7;SbwQW^F0Y@;!2XG0yA))_Ij7A~S_lKC}5N0;z8o`6LR z(Nu7K$y8+X?sF~!l}jKWws}BOOvSQbVB&)h9~<#tNWQCO$Ak6n9|yNpI=+n{NdtV1 z1gUk>qVq1UbqI)ME(cCsaH!IGUTdZcL=D$@a9yo!eFw)QYo`sG>aZoA*pp6m2?Ec~ zCG1T%r=C}rmXKYq!7dknPYu$W+Sw%hJcdtH=jyXGRe8b2^78VIs&&|tXZrp>zG41R z#R9y3q5i3y@h!uy$fq6-q5`WsJn7vA#1*?29ibV%t6cV&(PL7voO*>L;@##Kc4)@-hQ8i ziA9dKg8^sLbujzlL9G&7_cqPJhDfpRc>aaQC34~y{SoT_EGW&s^O_-k2BgUhk}b;o z3{mm%zb)%SwknrEs8f$2fUYag;XqyBYR~nlKI-kH#0bW1>kZ$q2T)9Fup1E8WqcT5 zB`=KmXT3kWeYQwAcyyq1*%TCV&7zkyDNF%n$m7=;t`^+YMb1MP+ z-97BebxGM;Bqz-kfgmYpvy5Uckh(5$pZ}~R+&w00qNrVQv^hwx)D1y?DsVK~;EE2Q zb`Sc|wIsXI0myHj1m%vU#_s+{NvGvY=SQ~`eMb^~ixVYrSIsr{a)~YbSc0cA zx3dTNMKHI{zsCBa&tSYL=a$~@WaH+0*S?cnR6oL!h%M>$E_Pm(QQoHkItlEZ!*p(| zKOQ>sFlhUus{dfjA+kw-3b923i0eyAzo#JqTP`D9@|s5KaxDy%kKDNX*;jfXuDsCO z$Up(#tZA>|K#dclW`1lRn-5MQOJ_+^on|%^lx|7x(I68PRgC0mkhnB`%Roy}4n<<}q2BA-1JDnjh;hP%FZSY|!Jjf(QAH5USS z_t{+a0z>56Exs9IvE;A}>_ajxDjTHAmg36ra!pnj%$u{029fKagZ|xvl<7#a?rc3> zg$uSYd8twPGWSm6Hb||Am&Jk;178+CD!8{_ECEp2#=0#*#CsobCAWDURmu}?L$T84 z>Khqix`_qydS+^XL9HZK#ulaBhT{B<&U)M@$`U+tUfp=ih$y-hMAa>O;_e6oOGyG1 zGnq|?dT!0gsf_Q_Jyaf-PCZ^sNH&+}Nw$_tj6f>2sk~$sEJz!l#3Uw7ZfmBxMlzgX z45^J+a@3CuC%8(akzgh`Q84M2U4W%jkP0$)e^&Ro#5&Jqfu_%6vuYU$ zetF>ZuIwyuEI7OFE>tvl8q)<**!GoBIIY&3s4EEbr`knM7mBLpUpCsxWu1R%&94A> z`J?)&k_9C0yD=;!O)dVzFG~aC1(y?>Z6?x@35!x`Ps!(VtaKQvtF%y+kvA1o z4gJZ{Xr91r!kX~`HUnw&!SW~_<2CQ`v^Oj`pbA(pmgQG{CFH-;kk>uF0&JTKu&q!* zYF50F3RLa;YonqdzF_oK&a>XU9mH}Txq16EIENOyhdN?YzVul`dhdXt+L-b}Cc1ra zco14Y$Er-g1~62Q^rcFs1jPnSnwKoztVbVd6P5ZP}=9x^=?Vm{_PnBU9gkTa(FRkLfXz; z)U?OmO7@&cq3<{{?K6AKKYOnz2}E4%`TR?HIOPTv`Wi(F6-Z>2KNbgK3iCCU7LOm= zk3Cj1vS%)*JOKi}%gtM{KZk>Bp>Nl%yl^PM^1dEF`|6mNX%$L!*GPG3RjI^>lG(_J zI}KmRWlavedk}wuAKsyUdR2(l+-8^?COw7^L`(AAuM3zSz=K-u zr7NWhJPSHeA`UpWL4w`E;=_cYO8uu;-8^ZYy@c)s^I! z=nO8u6l&0;l2dlh_glx)#(TsYt%I|y%G{qPp8b?FZAHps??iQ0ov{yah9gI(~yA|2GUQb++(-ZiU6;$8$MTOG;+?hdabE1Oo-LD z^;{^Vv_{X{e)$p`V~{eq#&Dy5%1|mHneH85oG6)tC^-{Ewq2m?ZvN*I(NA8&x03e} zVO!Uv)4n=&OQl`ejL$AHEg=qCyU1$ym-hy|9cs4aa*W`|-`8(-;Q5&ge57FugJ2X=? zV%p|>km@H5hLwq+%`(h9Pi+Vu1$XY@@4{h;0_rgJ{f79j?PGz90EwbP)d~0c1JD+@ z2K3q&H_p;Q{&4&mf?^ojdPdo9me6BNA$4*jM8s{S?dFF>1;4*lq+)(AG=uMSr=7f5 zU9Uu_S`c-F;IWMeED7CO`9&!(tVRUL0R4ED9aIN>d(29+sZ;D#GYk~!s`$CxsLGxl zlc~`kUxM}ZXI#n+>cS-I`qVhwOX1wO1k5r}($yFXnLOZm^ty_Z>Kv6von$Ow8B278*Mtu6FR)7U~_Al!3;|2IzCGI+N@l(m}d; zXxO;;BtuqTO-WGX9Y_@l^A(y^MpmR>JV4@EDx5n+KL26-7}(mzFf!OEEaGrIM=3hf zMSA2(n@gBiesE0KP;#CuGW)Xm;&6!2a>r!GE;IW-aZYJD{A-+*Wn{{z>0OEGULKk7d-9>2ioJpR_sb+F>}VY{>|DkXV;4Sm`!m+*N3$>Zs&A+oSn=)=F(*co>jvng-5_RdTU-s6* ztX;h-o)i7aS^7IG!j0W9xAJ3u`xjVG71k7=@ZL`xgm51(%cf9KkR$2O;PtaAT<_LU zPhe0)I`5DZ6_I8Kj~XFZqrA@%?gw zN?rRdm(sW0fsFgah9o3+(#x-#%m5~>VNc=W`&ffnhK}N{(K(LXds6PI$-iQe0g^f1 zpAQH#_`@Sjoq<4wY7-dwZPg}Re&DX3=jHJV!}J%Sl8y>W)+Y)g=$I~*7Q(oU_3?!T zJZ+_8^ZlUAv1<{Yb*;&I#U@dM=Gk)n_QTu86RKEl;mW)Z28;B&l%97U1v64_t_(&> zeL3Ih8LFF45`em@L*!@NzQ3X^Be^TGo8LH}RvECy_o0T|;GU7o)NEnRsHXZtkmWlM z=diJs)b&%QS6#zDe(YM7bb`rSUqTV&a@5DL_vi_)=Js!~Y4%Ef9zWV@3w@c)4dZZH z5uV!_|7+*Rp3JfP>O@C#grI9HxF9k7cFA}h;Z{^3b;jVH6FZOrwzwEP+mf}~K)SDU z><(IeS1A#w$DK3mRuVb-n(YZSM{j@`JnKi{gt!#nsU+oo5NV8tG;e_|-XNouPvl4K zHE+4SDb%yLKW6*JOy19LD~-2*2KO}3EgLe@et~*tXMWk!=;B)PB!Y*V6qGbyK%yeM zBPAUbQz|d`?W+!&{kzlsX;Q6Apz+3Dl30Ue^yBEUBeRg|Y2D^eUfifYWoKk?Z&Xf` zR?Liyj$O^oZyCpzLmv-*Iodce^5z(7J=mC+qo(+_TXl8~p~*}S)XqqyLDEu!<1`=} zHk_J}4teAzCGiX^w^JvR4L(fLYSkMH(#Gy(0LAHab zk&iL}_c1eLO(|8<@BMhg1lioR8{Cc0!TCnr~gmD1X~C9>;BQ#Eq) zH$MUEyzev-ItS>o2B?GL>~b9H*UYt$NPnBPlc0-h^%stH_jSNR`;X>*SgCa_A3iEo z=W;K!B|5PAF7@tm)EtX)&B(Wa8-wfx8{(9Z!G=58X9w-{dqhkMJ`jXSJBd6J1?Pht zr>(I*?{=OtcMRlWpI@HFodr@1(m*osxL?$PW6=h~*O>~~sCSzn>T(dX;(hSrc!N#+ z_Qo03))P1u(q{CRsQuA{jW%&P#3UeD;jS-BLBndapsZIEqa~{!cZ_wMJ!jo(CDt|s z>|*v!!-TQjK}?;rlhWKfl{lUw5$D&Rs~#S_opdT)&Ro^nC6q3#TyN~<&=Jtkp|Zx| z!{hBx7w1jd1utptgMm;?I{p3_qP93$3(kk5wg}d<_fiUKBjuz0AZ&4cnk+0rfb4DkW zSxREb__Y%l<5BVj#oH^BD;!GB?i2A7$nJNcF!)DP`*$iMOjLv2+nfz)PqDbH+B-$B z{75V9ZEucQ_u36>?)9B-nD(1yuoCV#^%n-tVvybR(Eby7lc6E)R3ri?1~R`-XGO7! z=PPbgv)#9hu=pEjTzDth-;yjr+I+C(2ayNZ*|5W9qi(oQ20==Gw^5H^2%py2c_~-- zT!cuQ{W8qiIOO{eV=>okBa1qpelT@P#&<+8WNk>v^XUm@EMBC%!a|$7n)xqBvRtdT(5=|I&D{3mj|Z~{xD0h7Zp#1!&JC{ zyb|y!DsXo$l2+HW+rdtn$7lw0-wFsZSEh6XmXwo;rH0`JXjNDF==b#e11!(4O}nxI zEC@tl6}wfo)piSau-UEyt4|YV-k6G7X&yesCwO72-7Y5OuWtz>(%n~-xx1Q?)|kgB z^_75QD$c)G+GM@n5ERXl+V1txUNm83^5~?Uf#Fce&YDbJl;R6 zVK_y``hePCBPboJ{E8qV<4044Y!HYVp@dq;h5-}SALGiUSh2E9&_&ZOb@~tXuF#P$ z>s&-~^ibP|1`Y;-@j{{bvB)vc`6l$p6dw&Ikye(U24*E7j`4l-yjxX#Y*DFaMM37J+ zSU0bgw$!gMk#hhSKclY!iQ_$>&X)11=Fka`ug@>)e)@pMU2T~g92#y#5?Gz>)l?Wu z*)CZLb<3B+_~{pv=Rc&+C-SM!R7C!LN%`j!_R`^p`_h^&Zih8rF2KQNuf8G}Zg0fj z$t$n;i#~sp^5A^yO#J4%M5P6??uk;{CQr|k&*8yc%&J;*YU|n&hph=(**w(Xah8=Y z?mr@C&Bj3|n(4j^PR0l{qxX zO`Dl~;Stfgtcq*AyXvBC<{Or4QYfz*N!wCtP3|%e_jn!tlO8L3e?}){6PGf$?FvJX zLm}N2dGh_^USIW4_T^^0oO3l}zRpOMR*$)Q zQv|1p@`c_ijM{!0y=>V{hdZx~*Ue4i&AfsF@LPO&hXOD3L2=-;VRSf=I0%^2`7Tvr z;Bc?7{boitV=fdr8-ESQ^$vtY_GiJ6GMLue2cSDE3)M3^ZW+P->cP8zQ6_b>Q&(S* zc#6Nm2wV4-GqcZ)w|6quy7KmI-n_dZr{bJ4hLD@h?4|GB{M-V6k13C#DLw}p3G8A1 z4BOcVg0r0Pjo};@lDX!3bB7AiR(bP{cEsS%prLZBRx{`R;i16c?l58@7jIx-G_uM6 z@!>;g14HEck9R$8Fq*xWlM~GEo9ebO6>}^s0^H{youawZgkbj}p=O z%7G=egIR|l>C`~FDHs-xz|gg5kaQ;e zyp6p=PG7T^o4bl}BTe@91GcUz@>ZwOhK7@yQKUnZ`q&QiD(5T1L9|Ph_NOjI!}C!; zQx&r69EFuOS3C+dxe_#YE$Vph`F`%;DypG=u53)-UHGD%e@)(VHqO7}ViaPBE5v71 zh`QO$$c$m-GUz7Mr+1-UB-9ZuU$ZVqItY>^oyAksX*GVkL{;VW7j82ojn2W5td`lm z$%t02#`|MX@Z@%V#j+PKDF6#9-=1hySqun zrxm4xlB022ScYee#o7O(hM-za^=OR3mI28N`YLs_li8=j4Y)is7aMgD=33;`0}Ql2 zV(2eb=PcNhM8}r!%v~~xk?3PsRU0kcy%C_i&}@AN#eFMgB~06ulbqja+YC$G{V^w8 zX4447RkSd=G9EEp%&Jm8hzTHu6f9H?*2%ir)UV_^Z5;v zZ8xbXP}jnzsDaJ?r?sIF2f$BGQ*Oe;dx4A($T>iCx|0F7=ZjjpZyo;(X5c?fly}JP z?D{&D*yuAO@2Mte1KCdYt)RgPi@q#32Fo5nSpV}XXf=Q|ovK8f=tIqpW0Yy2UfPi7W) zRNEPsgR#OX{?icI5u)2uta#Ns!$7ci0L%iNU@WFQc5NVQ=>gY55R3$7oyJ=e8#dzdd@g=$b z1Z4!lFrq+z&Y6oqLUeh%FRPyqHR+ft3QFtPTDfLDidsG~k9zmmgIbj`^x|0;+F9c9 zktt1XTOsVM9DR{-D{E=J!msOvhAC=zQz`k0Kpm~JqJ)f*luF-|`z~5w z{c&KS+I?r_99Yd-r|SbUXdS(djaF(GX4@;%RG#Av6< zIu-3D8;Q(z@0O5IWVULY6IWT@wYT2?9EtkO`}d#7cf=4s8FSX-_SdiPz@ygb+3yYp z)E8bDe0ICXL=5##lt-G^OhZ>MC89r3ta*-5|M|IVP=sZ!0iATjINrdb7nLGZn1@)9 zliJP86G|3xJp!M5T}6s|$SHnqrp_Q>QxN*I*8WrfkKQ7Kl?^t-#!!4*yikFwOzBZ4 zbq>R1{bc>BHz42Fy~T|{bYqi-6pLr3qJTvWjtMUFq=+QK3#NxM1Vv2pEMAwKJ!3Ak zyxLuzPB`IW+=yfL+WY{%Y=G47K9)>fAc3V{bXD8KcXP5~wA?jH>0L+?xeFXo^6F^- z`+NS#zfF&t=hNZ0g0?1P!SemD zdr#~dP`}o;&U}-DyI%I~-nF9BX79AF+D3Hm4Ov{A4qld(AF1j6`=?uqC?w>VXu*Nr zkESXqQ4<-*F|b!82)h5P7X4s|Zqu6eKRe77DZt4v3sM}uqU2LH;uE84fYYAFo4aJ@ zIf>E|;)A(4=01}`e?quKe*qdKWRR{&1xKSR&H)+YG?AwJw@*i6ks8w{b-k5F>P?Hl zMv=g`h-rQZqHwOL{BrIWR&l9gmB;)|--b%MwoI93>2Gxt9p%oM;ksS@V+7MWJKRT~ zgjwi4o8=Eg)kE2L;zoD4DsQAu31jH5Gd6F&+;~~a%mhjnIJsOra&FxOZ$tSmCp{pk zG;_!3EPJ_mVn|tF;{>j_i_<8X1dH^FIiu%LkP-&&*5{+Oc9)W4t2Hw|IaH1nD2Sud zq-RqXxL1cu%Sz=~fOa$|l_HU?)8gTLJZFYKJDeyrC0r7|RQzKQF*6hH0QYNg$NSxT zŽ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ⅈdKg%pID1=jQvC=aHMjf36B1@kbq}?w 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≶dJjccqn?0EOQ>fpWwD)DRz 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

    )3SQbSnX&5LaD(^K$1|Y*6jEk#YP= z^tAN$yF<8Xxa8dOdhj*AjtVF+%-%dFlTiqz0+M~FxXn167O+g6*`%>8IB{&s`=U4C=oKoIQ z+9SOr<;?(jT_Slu$Z4HBsEGhD7{@CoFh7zO+D8FAXq4z459}aIU8nLR5jQ2acyBQ# zqL_B82ubCeFXw+Gpb%!Srcj(Yd3m{(;^!=pRbaR^TOJfS<66meA;w&7>SE#6tyRn% zoT@D(5*2k&Z4i{shO#nY#bX4Zg4y$N`erkt1fpiyXqAo;AU##rs-@s5v$|EnIz;~p z93LNVrh2z5l^m_W#9F}OBMtbm8^PB#$R_5-{gu&3?6jhnQ=poorT1+W%SM#NvId$l zSeRiZJ^j`KIkuxTaH#K`vV?zyPq)wBpxZSeb+1K}y8`o9C9!(ZQb&o3Hey7~^tzJE zbk3=Y2Qv6O_}{lVH4DRiEQ zGfFkBe@}|fqr4i>@%9sZA*j@X^>2_Ha5n)09U<-%9wLq>ogS@-?^7~9yVvz*;<~D0 zl((~AgG60TziGc7>2Uyku5mFvN{7!N%~lE+MJu`~=Mc;qT< zdCbUqT*}Bvd3x~qO>)i;lZWvktdI2XG;g7KuoP^+gmA!psPMfn|7+Lw6?ZwTl|ei% zrV;zic8K9^aF1_KFS3R63ZyM8tEa`Rv@Ejx>WdTWlT3Hn2R76sje@hL1Ow`ULDfD{ z8jaS>xJc4ih}W*PSPt@2sUh#PGQ!-INL-XVB#Al!V;pJ1;kM?3D&Ku+6yjr>LM1nH zw}g08q1(#2>9VyAiZ5nZ8rOWBNZMU^6;8TRJO>|R(9KNX)Yr2m;cBU93NScyRQ0Ke zfn{^h%@?sk3WfYMC9RkcLgqdc>7-^gbZzi*uWHTjjqpQ$>y�rgl!!Ax@O%R0bQeQ* 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

    @bsm2R9 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ⅇ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 z z=lWPLrANakWrc`g0DK3wNiyWHJACeD0h+_+R`4GK`{M_2AE|dr3kZlh9@y9#m<;}9 zxBwlp>{1s@{waj+bf^&v=aWGrMX3=RcpKFbawkR#w(uoTo8-1yuiHn#_JlyhX@kaQ z0*j;#Mu?uAShd6eWYEa8D^tHK!|&Amr$;|+K9HjCuk+W?U_JL6ki#far|P^f>gfcH zt3SSfz8dc$8RSXr#;EM{`Da%EH1~= zZCNEJH-gsYAK1zxYi*(!MBM}PV@T!TDguK>@=DRwB}-Ud9uACdUou0=KjiNue=YOI z2z-+Xc`F<&0+~845TI@Q1?O(I&xs9DvpMO*{Z-|GE{|R7_#^FI>ur~^b=TDnkJ9Ls z?u%LDg$x^nKebNkNziiYp)gCx2M+N8J(#=@YX|E*7N9*8sE-EzDbt6D+O{9HhQ^Ne z0d)}k|D-9>^#)7tR@OzvHy$&YY^3BHK#;w3{q*$a8YRrl7(4%%KNlpmnAwU>>xS(5 zMvZJ7mFflt=-U+Ki?MSUP&rXnL0yg|3c1KIlfmk8a_gL?Dq%v1W8flcrZuF#f zE~GuQQ#voL5l_C2P%b7#X5z$J<(wKRTBSNTeKmka>`{$tQ>HHgM)VX7Tk*4wgcb^3x!vDgAwV+N_@LqH zYn&?_{@F8>C-#>6o%m_>!)bpCE9C#S(h0`F(U@*2692IZ5ByqGTF~f^QXBS{Z4iFe z`!A(RJRO4d=FT~ZQ^k!S5OuQjZT*y>snewBM7hh^A!~!iQ#d|`W5vqPO3YYO&%xFt z%db~|byXW#G*E|kj-DgCCU?GWo#$!i<43hC(pv(HFe-g3QL2oQLdLnb;M%gL2`Nz;s%_d>__#gQmRrP}L9__xvpX>%pTpy1cAn z$765F{}SkP4WSODIH_#3$!*>^{+nu&ZUHl;Q~h-G%^1&2H(`kvt*=&$5au#9qlLy+ z$S!B->Um2qfh(7i+fMI?+?gm7WgztS1~5bpJD?HhRXw}05W$T8z~y?YSc0U*2mt3|}&Z`$e ziYp#ru88Q?HgxS;b$f^?#m}CMcZ{t>o`~nW$?;MBHjir?fBewU;LtA52BK2Q!27)< z~Pv0Yv-s;P z&6bnlQLF*?4s-+MZtgri_;(ijko8sXGX(<}NM2YqsOVr0_8o~W0rUP{&zcu+4tH>6 zZ~L74iHk}vKUBS|P!{p_mvg6i%vZ=&%O+JSnLp#J`B9c{pw$0-PUH#Xb)uYWliNg^ zykx6tI{kU~gjB$(uL^AV87U_%t0rR=Le}Y-$XnTbo>vkTU~UBTQjq?s0WFfeIAv^> z6%+vtxXX%c zfb?tcL9QO#vPtK@)X<5HT=+f^Fh4_4wqC?V(EVin1b`|jaI{hhYvWGoE$5_9d0VCm zMyBFGVx-Nfj&SYCbNv@k^RIaBWE;!5c&Kvz{n27F&R{W%YXOakWVCL!#Z|ytCQj7+ zoRN#=?MxP5vDc$k2TiS%45$=Wdgot$9YoKl>6ue&{=8^uj^hI^nb)q`4FQKGl1C9bo#!eNC+_2J`}-?-JEzjq3M8|V%#_GfQq zZ!;mszMEEfG;uWIzNV~1`KtKTyEuZNjJwmV5cdQzwTiUfq zbe`&FqYG58cvFV7dbi@ouf>wL_vc?o&IE*)736;yjt** z`M(9&@hmETx=N#&S{~ZgEYhC+DhcKK(*514TR<`RAqcvGV(fVESRLBTI`!0dvuo=S zvS^J~*}m+I!0h?Yomah2z;YEuk6xsf|F5nrGAsPbW_Ua$+By4Hd(X4b_fA2p_()%4 zRD)cQQ7nivdb+=(>#s%VXz*5?f!A8nGR~rq8}FZc{JxhjHm7T3AXz!l0P~%ay-r`0 zZii&FKady69FbbQB@f9NYW3uYpp*;d)YuO&8FXy7_vg>#O?*> zQO&yxE7T)Nl~m1InfU-9)L0RzWAWQ%(GWJaC~5$20P*$wkRjAC)>(uQc`H#$GR0@L z%1ADEaF~bXUKy-!-6vop2MX9!4r2EmnGCCIweF~3IoDZqZXhTZbuLM_we;Lu`Tymd z`d@rHuCKrGF)CtQ^J*n8Nkm+Kc2j2EIfwh-R-peO~DvALSb z{T+++t=2A!cie0t|$o}D*fgYVbA zZx<4>OQe05oWxU%<4og%?|yBz{#fyOas9ND4rSGcSqH0{%0?HX$F2sZxxr^{QfQ&Z z?+m}-O$Sqh^cwbAZ>xV0=yBQiVt+4z24I2K{Ia~6|A}aBGpm$4?Oi!nCO^m`ix(7t z^s3pN;gX6f4Yb&YiQHUmk{%~X8)TefK}K9|yP~pjPg{HHv>y^0D2c+?t@w=n{S}`C zE{9sX{qg&S;A87oVG7ryCsM6xqs5__o#V+#5eXF^Koksued~~^`71HQpdwlmipQ>i zyddPvuC1LO;!RgLWZQwGjdS{E#?F}9&u}2|sEw`?PI_@@TFk8^2Ig|`_n7b9mY|@( z4?=zCI>;9+7#lKR4l-r^{a7+@VMT@f60=6wQn%%l7d-<51LPxFEdHduPjCZOeCF2` zrqbShsUwVm)~FU~P5u^RKG0v0+Xxa*reDS^&RSaTKwgB-f3=;$$khlj(Qyp=3>VQx z{odWt?HV}F2Z`IDMQ^&BmrPFu7VR3D&VwX{?+W6sHD-E6g-J}WXpPKDB(G%{y82q+ z#0lC^EmH44HHozCIEqR6t-bgc$m=wff&^&kJrYR7Q*rw(7L_t`?}}2tS4}-#8bJm} znXr$%T_F)xTbE)b0~rrOyFhogY025kBvy{ZmDP_;AZMEmQtOT5Dw4HM|C}=&_m2-4ixJ+$G1Xmt`=ooQ7Xp|0mqc&Hu)I=7;*Fk4Yh0J zRf%46{X>f|%t}K6{|*S7S{BBh4k!ne%tR4t$D9$n4` zIpdDuYEI;F6xWOZNA2N;56a_bBGu&T2~N9)t3Y4>?EMsrlarq0DNnA*(>l9Uo_#V& z?wl?3)I%=BKL#V$t3e`^@tn1S8OtHT1Q0bR*Z?R8K<+KJhSo$IzulJ6O4Ba0g!jem z|DzHzltTjdf48J#gG5C%LNMbsm^1lDjK^cs5rM37-Fzt#@m@NojH6A{{jOUeR@^D? z?}ndolYjAHi7`j>2yj@IrlELvX{~yTXiTqvqj8#MQPt|BC8lddGWY9CB1I3W-|%^U zhMMBO@p36*rAl0;Ej4yyY7P=D>n;&;@vM2cByCWr6J!i9%NJjd?AOl;s;yyY^mrft zRsPcUf)Q`?`q2oz&83yJDXQZ9I#999_VwBc459@&O<6`lCP*90LVk!~&mfzK6$@cY zp*C^QYL|l_*hR^nV%E4+IYuB2me-zzb=K7n?MY&lagL3~CJi7y(Pb{J(v*TG5*7{6YEw*Pan z;}@Us`1k5h9Tr*-KK_7H-570jnpDmab3@*;!UFsA#ZqUNe>PS!e+rJDUvObBTVv!#8s3Ot{(OqA}Vt1b+S4Ho+J zn@MVhtQE@ngeM%X-29<__vQUI(~VD}n2~Cxr?ObNmK)Nza^WTWsW0Wn&iYmP<(o!) zepk{yiBtJZCS1SA+v5g&9Qz5r9`Qo&yUmfYh4U+@;e@$93(rB%!(dDc`(JklwC|`I zxVZJj@CH)}OJC-A`#Iv{2(3!}=7t(@SoMXailC(Q{g4q=JhzNSvKbI*lmH%~y;# zg7EjlhO~6@ggH;zkiaOEOnA{8gm2}kz?&{s85V8LS>G!m1YwAh_ozPj3 zSSeYz7`h+z^2Zt!q_&0#OLQ~)0HZ{_Z>jwN>s%0hU#H$2ohBjLid3q(vDn_4i+{P8 zclouq_%Y+@YMzF%koh-D479(WQqd9DUy=b+aL_Umfh0t`Q-Z~REsw}wE#91GjnLjo z^vD{NV)2<)sx`_gB`|veI=`eq#kUlAk_($pB6E17$0Jfw*z%GR3wmOLCAc9$&WRAp zHy7Z{gy_S-RX!f?h{>Pc5d&8ZVQjTu&e{rPM+>&;9X6H*IS(2XpHZ!X1*|efZT!>2 zk{c3?`&}!Y(KB(54+lJa$NRD!)7?uIBBv=qQhfeK@uqv5&WcoD)8C`=dZdg$L`T^j zur#zdj+pLkE*>n2rK>Ib1?&6ruH`O(Zq)eDrp1FDHYz)RuXu8 zu|~+7p@?H0S8V#+2|X$oBiWAXn+0u*Yf zp{4E={Z3+AlvTkg`$wO(&Aj;<^W}HG;C0<6@_(A)|t7{rK#JjJRUgXOSP^t+^ zFN3HIv~US_#YOV>Pp=qpkKDvs&-A-gJhdu9UdA5h3!Hj24F)+qzg<;o5R1uOINH#r zUE=JJrJp01l$(ylin|WrGNkLf3kHZsYu0qlSJs~eZl`;{f$a3N7l;NKbWZ54MwEte zxR&n7c&4FHa8+F81<2{lp=KupK_>{2Tz}GxMb`84s>{G?e=+Q5=ANneVz8cZMv1Fb&pe=b zWO(^@S(uGYdjLL8L3xHT_#y_ltw;hM%bUJT$d#8A`Ec67e-r&)wLxn038IgZXsp!! zP}=BN*Kd_}cPrR%nYRGq&V~m~UOm7V!K^I3oEuyHpmS}g#fLa~lsMTfWoC>>&&pB+ z1M?JF8}o8=F$%4=ki|WV5fVWKs1Qql%j{jg(ZnaUdUN1fr;oR#$5s)2gJUWUWqY^V zx=%QqV^Lar&yQhIc~tDN8V{R_(G)dAMDpI@#fSD+n$16ZMLG6)LSQ{Rdwf*QLRu=RJi8rYiwc$0Zk=DFPiGqayKF%1P-i7zj)e3Fv2_W@^_P-EdGXe3fLqi%O1Jg?^+k0;Ato;a-%nZ zE>3DkS$WRW`dqDt4>Z{0=i}$ZtEEqOKo|r*@n+nL;P{0|{R^mUD{;%q_nxvju}XOmk7Bo6Oe3+R-qmFDBvz))gTbE znNZl!K#3wsJtaigp1)u0t8KnP-qp8aE#^N32xM26S3z0O)_1B}*mZXDLg<*jE_hUa zF+Q2x^`8l#S_kg3Ff#3Uw@rlYmD%#G!J%tLDrLbUL5}tw*N(N@E`a4i)E5Y5x?%MK zTr~m~OT?bc@x!fC?wcDm{(GvPg2WoQ{i$}}#xMT9I~(6@r>5IOE)8@if$>Ayn+rvK zi&poke0Dp$T|i*L3b0)M8JsX*?(?Z`!tvqqucKDo%kgDAPF?ijuo;dW_giXs`Ew_2 zp`IxLVcstmBb}Gz8|24Ve_RYN^Cz)pRsRRSIOj6uKTKqxds*1mt=-fhaXb0!gj2Buh_f^>(QQvjx)z93Enm)A8t5jW!F;BT5x|qQ zfF|WGn6#zivLsqly>w)sMZNd4^lOi-96uRJop4MIs=xIRfqs86vf;+S+8p6!2JV{RLckXTKQ7^={;PZ9c4TAM@f)aBslH ze}&{aIZy++|J>ZWJr#)L!Pa^Wv^54$@iRnkcv?F?vlL6ITN-_-hhBC)y&#Tu-vE<* zRh*5EzvB{&NQjD1_6bhB?on;Z8 zLn6%?iORXnpcr=1Z7}}(Mem9xFXpZuoK@SNFtjfLF!UacL{2;RLEOIgmfrg{58Egk z0o6x$cRh-@_|Vl#E}ZKLqWFNd{ENACkb)!UCI^I|>)hgmmhF_ZhG(|G3M z7LS@aktp=B(a>at&yv0dxU=cX!BxcXwl>iTq@_QQm97mK5N?N?(-@vB<;?>FhK^A96g_YKXUtk+ z1d;%22XrxH+ztr3&~5-A{kcV^!M;aurL&Fh-20(Bb!-NS|l_APhVB@Ao)b zctTnRK@L_gHzCGV)EwtuN_qcE9PHv7Cgn{Qprmyg`!9nW&u{@JJ$|}ls#i+G5s+l? z8mM7^K=NwGGOt+&61@w&RQ|hwe5IEtuPPIs8r}Md`(2k$JFSiX;wx~k&k_EcPaa#4 z0BNE!$Q^Kg_GC<;=UqoCK{Ol&NV|Y5mgavq+Jwwp49uDXj3ylr#pbrZL{ScpIAq1? z+>LkWZo4qYOcX#X0r{5W%!beM5R5XOy(rEw=cYsHiM)mHcpJ{ysm z3txTkwGdhySHlMq>2odkhLz6A4qssLy8s!ySWu2%rm(VKSlMtjgtU1YK(<*WS6 z6OE;%nTg=E?;zi^1EFum%fp?;Yo6x74KcRg8m;j0O6I>V8ANcAqALE0n%^agYA@JR zABRJG(4jqI+J-`HI-P>8%-QcKN-L~uvF2tpoK4$MohQ{bNg3glc&U!N?6K>>v{e>W zTyhjHz$1y;z}go6Dh~HHXe~QH>;J^O7B){l@|)zN#r?a%eY85AaCU9b$};sd!s|nS zUx$9hEwsLIEH<4j38#{l>KZH(o?EDUwBgWPQRkdEbp~9FgC)qcwFrXo? z_e?hPS-vUQTqUGscG#->tN0tmgsfztVbe>sn1+o{GnOe5LG1&WJMwK(yvSLaWGj_t zQi@$eU!Sm!XU`ua8j%nYU2^>E{(e7~qQb%^g1^Xw zcu|_N6puo7`jRx#3zj6fH7|%|UvxwPl!%k5Y3%WmIl2h0gIv{wwUBKWw(=YF1TAm{ zEgCTgh8K4xX7^!(}fV7w|ZJF6raeX5Tr%sQ%ACV@#uz*7k-BCQ#T>mWjpHc?)9NiKgm zy>NtXN-&vUD{coI3W8`L`^)jPB;ynk{kMe$AKU(2Dh)r+fLh=hZEPw*S9DRl9N-^; z|5&66StBAfsop=L9hB|?D4zhsq(0G-`=qsd`~q=oEFK@=|DS~d%Mw9ATlrV101QbF zX&QPE{KAh|a@sL-Bm$;&bdkj)kkVV~L44%X(wjk?%8#71M=F zGro}ypGH5~bp|I6tk=5_#tfzU`mI=f4?8}i0?o2o$PP(@rNMh{k~a4lY&JEp_73*cpE!^U_>Hf#IFkWM~8Q1p(g}NFMX-T(IJiHV$1=DJo z56`^V8P_d#lbu5cLZD7eW#`Sc?QBk$*ty8szV_IM?oWubq)oFD;|Lq&J_C^(-q|QJ z-Fz_%uy59nPM~Q*K75{H^tJn^{Poiz+||7m0$?ue!=-PXD|XEmz*IxEr)IG0){zIg zMz#X&`f0NqS0qw4F66oMf>SNTj||%NjA*s|^ADqJBNWfcw7Tl}X3Su~JwM>{5lkpy zGK5?ro%>V82e@<;A+3D=fh#DAzOQ8mhVj((H0I-_olEJ17{+wohCos$-FEYLhs*4& zlRqKv_yk?+$;jBi2xer`mCV}AXZB`Dw#DtM8~a{RKmY?8vav~`<08{&PI_`jNoQ5X z2w=myqKkfBeKKlW|J@#hpFVdl5ZFuq82F!H6eAf6+*NRsoW+%2=^5FXokD3*SeK%l zQ?bU0fF5g0VDiN~asfqD14&MITF&k2b8TS{KEoM1jF)5t`&CL_b`LmfCGt{)9xod7 z`N;tb?+kWF(0&r^)Nr=1ib=`KIE(~TBlF@Fa@p1yupCka?A32NIcYKDJU!nzG` zfI7*-lcU7584D;17(5Ru{2=3=vF3k)*_l!mx6W`RGTi~v8emB&|B}MrfBsehXDTAn zk)egujV%Aj|9mz_m0t7f*J39yV9i&_@mAFf2pfe6xO<)VT_)#d@r9@Tl<(*5AXf|E z!7kzcA`R2WDIj3;cdxSq6AA!H4DQ^t*(YXR+%UM&;kjKJIYD@`#oWUI&*D8GBaWzt z>#_<`o%)6eGbqZ?=hj`5M3B?2p4BbVjZ|5Cxx}Q0tYz2Gb&h9}%9bj`zCwtu4`RO($t)j+5ec*p3X>Il$N5Xmhq$76^z%7vdC0-@)V zG4tYW4gXY-TQaHC99;ZAqdkqe{6DT+w)F>X$+z@W7w>|LkXyS)G}_!P??$J(A!M=s z>e}R}zoSo7&sOvqe_4n}0nD)&`zVr|^YY-#=P(UkZDz68YDOX#J6AK&Kr`2h7Shuy zLU!^k_f1z9F=pz!GI0^>=CSL)I)sQ8_)Stig%KkhYFDH>_hf`x_B61{?a3%dM8n`@ zG4DnQb$0i~u!X?&)#%6EucJ@vJh;yG&}0AoGv>~$!_;G?Cay2T7wrAkROr9* zHI<#Jnb(D>%E4;po*sTPBtDnq8H;)&F_pm3`D+L@;Qm18L_q1W6+UO(DI}W=-~OA4 z1DHz~0a59OQ!i7MV5v=)H49@8K5ANr3;X5SmlF-h85phec{0r_MjC$&Nz@fucZMD4 zuehs?0gv(4`#f{*&}*%miTsu~6SE;rCA%j5{2Hk*Mw&C&|x=ki}cQ%CH$v2`HPiROuUG z=B()Qa@z`&O?zXb>_jH+vZ;AkP}6AfrTe#wPi{wNU_aGdn%*o2;^O4gLt2Pu}kfYh3nAGyY+0FIRi;f9rUb!F>en_d_lG5x(Y_j); zW4>_C&)WK+tb-3xwmse%DJUCvn47A5=`s4NjZV9=L($z?I4YrexvIIXEoyKu8T=W0 z3|aT3IP22RptvoS27CD?_xkd_Z?;!sUD9OVUOjl}V9Vu7J7AwQn(v8s5)eH#x}RgB zC_#}HVT^Lu$a1Y>{N5OwcFlIw0fiAAVQOu7qBgrkU24LV-YdB1B1TFe>sY19^*^T$ z+5q-}M^$Mu!wk*JRb0$d%i=P+iJ+e7+=o>hB>wfCR4%B4Wru6wR(Mk4Z06+UN{=CL zP|u(IcInL9W0klIJ$B`l$LN_EP3W?(AsO3LD6sY<^k(9&n+MG?jQ8_nSJ5xLb2I!tN|1tViWJq~LvL%HxJnF@9;WIeIKSkNof=iYMezO3&SF_Uf% z&y57vJ%FA4Tc! zkr5a$Ofxz1&46qkRn2^9vlr?L$LQE&^ir~L)KGFKOxUmB-dj0)Bb`$k|5^20M5e-n zO#3*Op4}BCcJvOK;pp@Hk5QBY-Tvva4BukM&z!=afOg42xSF0J)L?Kn{E+Ya~YsUNWW{5-)x|58I@ zKXzzYW?^(@MH|+AUbs2cEh`p<(n280PpYqzkpTslDSk`gLsh0a)sEWp#TWSN za5y~HVs7@$ieJb*T@HnBfwza1S5e>b-B80oE^Nyo&33Y5IE{0KPeg;Q@bxhz*_w6rwK*_al;cVt%L;AXE+Qobc7J|Fj*!Me`6_y^We1;P< z?QaTfdH-g7@77#frqESfTTETa^s7DE9bf0RJ`pXmv|l*UJ4@!7-F0nm7U3(}IJl^_ zDnKe53UH04g<`i@<_E2TXG5U`yburs66m#R&{4lmN0x%mNfEHOY#nW-adJ){W z1v~fYym^>V=a7gqa)UcxI^d(5DXQDzoaaE1h91RDf{vk}#X`riIv%}*pMK_H)#yh5 zs&zS~Q8fGO#&Qi!*cu0s*gLJ1xiRv-pb>ir>E;#zvoBnF7B=5~nA5D!%wc=RHQ0cf zDO0D+0QQExu-&DFGac27X=Dibfx~fK#w+^AxNQEuU7*=ZAkg<{;e=k#!hvSaG6o8A zAZN9T^_mo$VgLY5$@?WFoQ#T&w$~~aEs(z#H~TLQdwvXnfv_Hw6W&M#NI`k={Yqb* zm&?yrKY(J8$}{XLsp-0n)Gxi&A)&CJe0Rba9$$d*1LL*L!iLN|na8yCIb#68eCbr( zjXjs9-Y-yh=c0ahM^Q8;6tr;gh2$v80rXf8)_8N=Vv|j*Za5d{zWl1Y&4s25F*)VY z=<}|2;H6@J{k)au zX?EXC*fuY^!hT5rx*}7UrsWFI3ApAz!1Lh#Ss2?})6OUgXLNnSlRTV3_il3h2HHz! z!{=n8!WUITWFs}}K@=tN_hDy&@LNH>Ws>g<$hG$IrLx#PF*ZHd%S!-wANqzGGrt1% z{`Uc$8D^f((aS#-Jp&VtagHa==f4tD5D7^Z?t{Fo2FBucBj`jZi_Wt{Vv4nkRq+%l zQ( z7#%yj-wYFUz2@geLa80n+gq*C5B;swmPU%*OzTs|S z$0hUTQh3@>;>FpQ0t5WQb&#`;$-;HPW%}MzpU}P1(u^0LuJ)F)Szo{zhy8u3EG#ds z0)vy5b-AV)`t#Qi$A8Gi7e1z%hzaV>13XIFm1>nH9A*Obnlc=+_G|^&O-v^Ml=&5Tok9O@;W|(+BX>A&B z*Zxv+cU`Cw5W1kl;4Ah&28V1y)CiY!mjwD|<#t_QK;~g;K7b8l8gNtXSArYjKH+e0 z4al|_jmFIU3}ATRQOW-jU{0VM5B4w3rHzlQX4xUt&9h2@FEPpl-K|G3uTU{_ zx*9Sv5E<`#Pz0c+re}!g!cWGt#*T7B``ZI*(PI8Ul>szF04LrJ-|{b9ySWDl$(e(* z;2#{U*B*lJIeoCy9`d(jtBC-#VA02D<4kq92P`qwAr6H`|1wXp z-C0an>vt>@%s&d`h@>4&?XOUd0`9pu&rBh@*V_vNydAXoEQPrIFPoe5EK`By%LP%T z<`@^r(}u*ue$60jx28tGa%zF5^Z;Ay-bCg~^*_5;bC1t#jvV6g0& z@}ciuH`!?QLmkFu{*FLF?5c$8hR=r%C~5&$$xv{F9v=HDO9!@Gkkm?YN8k`f=3y2` z$NDj;|B;Qia?GP0`Y6#?SWuh-4AZgyYMADAWg?TF;GLBQMZ{gjuXH$IkF312Cn-tR z%{l^4(pmg6y4TfD*w4b>6;@cfry9JvsDBIxOZAqHsP~_$v?0nzdvu%hCqCfqtTBpc zOdGau^wYf$7oD$o%oh*)x%$B~4OXzRHp^83eCmwN(hmTZWe9_%Z~rA{ldeE1)OI?3 zM*Ld6BrMV*H)3|u%sS46v_4z1(2q>YEFOC+BNPAMg(|>yMo$1R*e|W9t<-D z;rw%yi=?C|z#Pj|6~QvQN`8c8$mA}BXG64sAw8?KR0e9u05+rEpLh8}-7xouL*jLo zZ&Wn*mLA#^*}uYbXtWh8Uuc+*B|)CNEBZQd}{R~?B_l-5?Dgn3YyMF7X~&2($Q%z|<($wR!)ZRNCOucQ?S+Tx=q2`0dMsDJEr zZxaG@bGtmU1H@|HAVLWW-wJ+ZfGbgVW8=EY->0f(1?(G3)B}`@s&imDEC^shEXpM< zR@Gunj!suq4J^|QvkHPfbL|x?=(j3&U-!^{S#zv%6`Ic8#(mz8VGa&b4s;ly2{-p}-zy)W~tOu$f-oCiFHy)Na+ot4mhX_@S z`YFQjcy>sePoxu*ebdkTRyJ@!_EZXviGuk3PlF`$v6;3k3Ptr~<{nPUR+Uv}Rs?m- z`oGuRTRQsnOUr>vmt4UJcvv~LO}ki++sa}4afSq>MXeV0=`Av&m@|FHV)%p~XY=tK zqiMw40VYP2DfCgIB5lzf8f)5<^-j9+EQ?dqO&uq5LkGSsZ*PUrj+{4#f7_}s zA?l`V-BF>&4-9|Y1{(DX0x z`Bwf!s5MT)LL^Dn6;sc`^5)xU`Ke3!AI~?%LYy(c`-OmXhE9TV_mb5=fc2nFKp9qe zV9~6^Az@U3<&5H<8w~XN*Ec&}cv5y*XctF{@SL5R+F1AxI85G+jCHgk1eUiX%S4+@ zupAK(Z+FxBpvMjWYYbSsf9bv2lfP0gTUFgJK6d!fmgo8)nIqizpACEk z8z9{Kw6m|mH&0A{f*XFYplv((D&~_5$TOQun%A!wF<%plN`^*L2^SYv^Rx`yZh5+^ zRB_(}2_0_FQf^a7`*rKhhYpEsk-V4N4*i4nX985Zv&)Gl6X2%2s+J_V_Qb1T4<4dl zc`iI&e3mpB_r^YJ%EQeh_2fTE3bmzx%do4q4@y{|~yKl4-GnwC=lsaJd*QBiObNH{SysZb> z{^g8;X6$7=Cl?n!&iTKrf8LlF*qNo9)m4VF2X~^EWNpva*!o}JRHDwoETK?v+9eLH z2Pf0+LX#w+I2|@V$e{hd7C(~1tCt9m3LI}qzy#8~;9;ak-feFRp!mH@?*APAYc1!M zF72+7(>fpvX&5QciuI-0$5}Wu6~ny-vkIC`i!8!Aj> zXBJAzaF|n};8bYqdg$I`QupYl-_|L`+Ug9J+R}-wF zCaeZ+>K%2`&M)4A3?Fh2LRKPnvz(7CtvxV%GJ4E!XV7pJqB~|7K9kxrS{ROR ze8M4(erFdUgN0Ylz(*T<=HiI?ng%G9Astb1@z{k0FX>mSeJe}mu>R`WQ;V=+QLnO& zQyjvnDyD0Pc^$%c9PsNt_ohKEjQe*;-!1hnC_J-&?UxlK>v~hOR;!0;a*^Sis^eg* zjp?vWHrfwLXnwI8zla3(8Gr~YyG^R$U86J7k*O}alxmT=yW(*c!6_##H-CsE_%FCf z=j*U8K|Zh>eA-UtK6bY`q0DeP>6g4IxbF;iD{?rx|FGU2TU0(r_XBs2Ne+X{6FTri zjTe53GsoE#oK!zrP(`3h~%1m$cEX=(XU~K1I@Zq zA0!OZSZ|Q5v<(!j9BWKS@F}clzQVTByv_w%vkhJ}3!sRDvnvS5Q{{l(iRXHK(e!-F zjx)5SJ89$64sEX|Qjxb%;!p?O9V|;v_M|BoH`T|)TZ?mXihQbGM=9l}-wT)^=O5*| z8H;=5-(f%P9UxbnB-p}bdH+VXK#pwn?_8Nb)1(O3PkV}4d4|G@3u?~7Mh+H{;$4g* z-7hXW)v^VBC*@a7dY@9Pk!YtSwxLLz7FHHn&)D+#o7J&;CAAFRH!j%(%QQS*H^j=j zP!#~s0m*iP$ETeM3UCz6dYDT;RfVRX%A;A3)KsF>d_&x_^tQ>ypei}D;FC;w!2#-Y zYoxg$1C1>;E^}?fOU#SgK6DANekoQtCP!h&BE(H=S_WSD=?>2*2V@rr&ia;9dCHrE zm&6yrWl#@B)e*p*KN2SBBO{g12q+5qeGBgFw1({J>KPRC^`#dG^DN=dF%CCnX-+)l z3M^sx3kp%R16wWddwq5}nHqd2W$-c@I(N*ai`Q-zQ}O!Py**C=7kw_Qu#$l^JwK3> zOfRl-G5Qw1BQ&}U+F*Ab;rE4gtk2NuGc>U%Kc8H3z?+!@eJ@%Wdn^?;&z3Aq^duP=_ZINH}PgSDSv6@tm`XlQ|<3xiUr@hZp=qKJDIn6Kab|B340vTEV23Uq+h2= z{~E7i5#iR<{ZfHZMh%?D{j;TM2*PjkvQks?di#xvlv@?mYse(Rt&r%>v7IdM;(li$ zJUxrStG{`C?de7{SDIt^o;Vw;#U9^r{hBB6M?;MXhkA~W-%3=**Nlq#Px)7O_GAU6 z&8Ti8ILkLYY;yufpWR5%JP5Zzc6P2Lkg{VKNinh;A3|k3^AC9IK7cRuxuGdtJ1wR# zsYvJWgr49OBJEnN#@s91tsax5Q>+D~ZxIRl9gtQwrF6bVzkm)>H_k2RQX@4%!}N~f z4wjC|Vo2+V^%M~ug+e*(WI1x$@iP(|d){=7eK(!&c;cO&-Ic8=yL&EC%!iv!wrw;G ziT6rY_HMH8MlZtBbZ_uS%0#9x9{&NY>YgkvOPR>TW8Gm-WEmFIgYBu-&nWd=ZKUku zl8Qa27QE$tdvua2lI?rB0xL3#Z(>K}3TFRm^s9hfi_OLQ<2FsQUwCVk>{44YPz0_@ z&=tm~YK-G%rC=g7spT>cr1#ihyS^AXYFg`PuOG4BEO>~)e%SvnWrKi;{NYA8Ng=P4 zJ9fiXP1_KrIz9X4&-l{-xyl_4AAd#EpO$re%x1iNM}xLt)3e?d+*129ILQB{ozhT* znCO%jFN@mhmMemE?O|P*n-)3hdl=bznJ0MSv0ST%avwT924&Zixtg}Q@cLxWM8#U@ zGt}#8u_hjLziY){kW!lK9K0z(t!I?c(7tZa(umkTmQ1#ZxoZ}hKJS!L&-LA6QCm3& z9?dD+{w*2ub@`SlW#BF}ShSL}BDy3nmWY}(KPY14gYv=C6N$Sk+TfcRpGXT;8F&AA z7RtW6)mWLb>jBFH8-)ME*?Ryr)pc#dSh1rbBA}pxpdz9`q$esWAk9iIA}C-4q=p0n zs0bbK33pQ4firo-@+f}6!+4C(+(;9do;#cG+ajeht3nRt6cV(`>fUQ6)*;Pz_Plc zb}4#3NqZ-pu>>5I%jDtzOKNK(-DI|Kin zuuOrD;46psOL0DFZ0J>%-Ci$1ZFl(WRz^3m14>i!8|{=hNgRL-(KDG(u5*dD!oGR<99A1z)9YA6W+}BD+jOsh;-*mfYdcq(7;Ls_VIZKdZZ-3g2lIwClF( zA;eHVhs7E~ezZ`3HF~0#wpGYi2Jo>%k54~3hPmx##x?sd~B-20>~fcFyCZi50(1lm5Ds4XBEu*mwl zQkF9e1Fk=K7>sHiJ0_(k-!ZTrld=EXda=z{Eq9%RI|Bp#q_jk5{ln{Rg)Tey?&DfE zpK$>c!3G9WO+TCVe-SDfbByAsF<2Q7q6n`I{zgj{EfttPN-C?hvD7AmrSP9csK+g_>sn`(e8qfcHKqvn4Z>^o%Nr z1VS3owaH#kdU=Bn4(;!&2#E+`SJ9a9j7pJTeA;uM z&g0LlKJMG(RrS?PBdKh(LZwvZx7Kr3vPGLnf4XIJUAs~UN8%1_Y=BJ_YISy)4V`u1 zc9X4JlZ}z(EJR~jMmlw!{zO76h9}vOmK^q9g4f^>uyR82r?Wx`C;OU$IB&H()Z8C+ zWmNqm!!zy`xXXK8#9s-8VwA?G2Ne|+^Xlq`BqKY4qkVFLgH86_%{;bv749#Wb}hk$ z7zPKRW%0$3rga%;2zAi&AaIxcyQT;KhK^6;qXRza1kM=+^ak~02dzZ z9RE7Ewn~k(+*GK-oY~bU!g*nq^`yOM1B1qH@E$^;_W&Pt@oKLQ3xEbf58A&0mb+^u z_}AZ%&v8jBxc-N!nvJdlzR4Rqz_s~($vs`nCuA_p_jK|n0f%z$cbA2=dg95P>;&xAsc7DmAG)rciHIV zrM7|(xrnm9lD#zN`hD!J4Q-d>r}WL!&u*Yg!zAd8ngDF)+?kR-KWPuANCJCBE~27Y ztt-sewbhSk;f(fJJ$c-1 z+2~7s{{TxNg?c<~11J*KSqI{u+AS4F=GD~PN+NkjNI0NfpLggW%2LeSs3Z`ZOD--> zHc>{;1(sX|pmh*V!{lw`uA>PGh{*Dl3{Qr49|@^;>~3x->LksvsG0HTuj z?SdI#*d6m5rfpgY)jV9!EU?$1;k0RLpt5~MxAEQl#3d&_p}#A)a#W_aN|MO#XQP7a zTc4(3#%hB-{1x5v+McOXs*3salLdo z+~qvrgDj&c?`Gqj`B>8n&1jymPb1{IUVIAvl|&> zqP$9kl6&Gi>V|y>2MAtO)fQPqpT4#R)5~Yw#+aG?9sx~~<<3#xwg0<3< za6_(B^j$7|NfwZ7J%DNWd&QQCwxwED#uowZkNpaPP#u#TbtV=7v#E|Qn~5gLrL(i# zqSEhh&|>XcOm(W`BLyt0A-!$*R$qo8)Z{31LMYR{95M>XSF)7XdA8 z6CbwHd%t(YUR*uUZgk|0yE=uM&b=M~D>cC1n*pQLJ;@#_tUy=&uCBfxA#VHZIyY*k zDSeV``bWFE6MpvvviyaQ9h#uqkIq;e5?uaz+%0qM^ncDy7P_pQ*JfiPuV}*8!j87x zGr0iWd@nvD^;V_LW-3(e%agvauNmJ4%;){D-jiYJ`f4hM1UIA*#M~Q>D*Jm655Bb$ zoheP&Of+AqE0(MqDRET{)^;pG^=z%k_(R}He;ugL@Fl>PA8(%?VWhsRW|wz4F+$WS zotwJCYP(R_e((xnL@ni*NF5!~;WxPUIo;5^_{c~6AfwnbnWHj`orOnks zDUT90Lrdg6QtcD3f;?Qt{6P}XoNsRYn@uA1l7{F{x_Ni(H_wyZ-$WNPdJfLqjBm28 z8^ip#?119x-iw6Uu_>9>PUJ6$ZilaSgWSaT@nv;jD2Z)J6cNx`dc5^6|HdH^X|pOoLVCZg8o8_@r~^zpw<9e1>3JlLD~ zY#^*Myvnu8<^;6REp5ezUPUgkF)6dK@li%E>|%c2Ie4r1V`5SSF*jFU0c7P?Z9w=5 z{T$|9g8{I`bERU4Pbz3i8Irk!Sp;qcITr7<8U?fW3fl)WBV{U4^~~@sfsNXp=exnp z^%Fs6CS%%z+!m`=@ZtMV#?!{}6Awm_lshM9xHC})QdiGs1-(6L<@Hr|6Q)!by=ues zR6`Ltyf-V6d9G8Xop_MA%xS5%yI;MuBIOM z+;w*EHU!$YHZ_bmS%5R^j}j6(sC>pI6)@s)d;Y`U9KWJgTWEMC?~Q|!X9>>>)-w7Q z(aHNH+T2d(T9fo_a)(c~)ypo<4QOHNXZ=@kQ79FlN9 ztb2OD=u{icxeQ#wr)sG_O*S&yI_g4~t6Vv;5pT3*N0C1B4u6%qqR)rSDynAus&WUmR;WI&`}0E4c;c35Z)(TDqu;zVsJC{Kc?}cn z{{r4W^=-wX7G2ydGm3&m|Dm{P5%b@X3YC}$ph!D zPqiBj*(_7}-$*V1Ut>Iju?A=uGF(@BCD82-(LPJU2Rw{9j|=h5gWnrc85?V?+v~PP zJ_TE{0?#GeU{=6`I`2T)l^CUxGZIcCA?cdXD?^Lge$|Hpeu1%0E7WIx?fA zx}=~4Rg#r-!_3*u-{p=?wSykfknLVX%`Wa#*F|Ox;{8=@Ncx_KyKhZD%IN^kHD^+X zZ3>2|In;*?aoA=-tBk{=y`0$~x?8C=+V#qJcs}Kg56^vQ$j+vZ%KUPf=-|2eb?&lm z=v18lZ>*&0>pY|6R50%T=jwef(qi?Q}C2-9b{WN?I-n4h;NWaqS!NdVEU;3+aI`*y$E%mx#IH zkmlN@GFwGA7@_)8xj?fU96F|RUu^@OHT%@=V5MN*iR_F@9!oe7 z)s>QVKVt@=Tz<0QM{Zmb*~MS%R~orRE*qadU5mc};l8WA1FZj}mar6rlt%M<76Wnf=$op=WL3EW zEsVyknMD1Ll}!f0x3wcn8X5yC$2F6H%4Mpo5KMc#>HR_Xkwi&aZm}AKNkJs|= z8XCU)>qIo=@qiOh{a=9ynzXy?YYYcza|wTwgqS1XpK`cJ01ST_L&uSyEG*|d`t5~H z8*gpBF=%6>Tt9|%0Ix5ErsVTEQ{~>wx7v>cKVA|Nx?(jLqyHw_0;hQF) z_mi9&DKXW)DHnk3)ui6Iaqp3feYxnO3S1bxrmVc&KkVGRR3&x64~BYstVIETA?F&CfLLw zz>;ujo){-xsiB!3*=d>*-y@=@z8i4f0P{XZ1d^G9<$&h^92FohZ#KvlAgQh5Rc+E^ z#!U@?4@`g-dMmX_N4dt6FDVV$H&nLD^)#ELhj7y(r&_aYA4Pqt{y#A);A2&jQ87PH zIhGhM8H&avEKasw%eW9DY%*k)rgG&*^pUf~5Vm(88(|Ubht*vo3;Z%{h2L9lWJ9#^ zV1HS%kPn|de?#+U7pfWrF5&-Tj^kqIYF7?JPpg^ez>+Q~1A=R%Dvx{rgO6zmw9fR@ zEigBJ?{-(=1GuaHW;%lua_fBKk^j7I4awK9K;6k~Kp1`<*`SgT*_6Q!!Vn-|Rj6ziHpyY1a`YV@T`es@L$=$htTEJ#@ zRD`fvA`iC@x^z_LhgD5+)!X5f-%6AFMuQQ#STd{E(tB_&hm2_~x4NeWl z>pBAalFuRrHQ-ce_5R4?>SO)|pzR+E{w~}6w~V%{JR|_n3jydCS8pURP^DWD_>7)x{wpDG6YZLNhm_*8jOivzNL+gj=o_NT*oP zA=jm!vUgP+J?dGl3`@OFJlgZKU&gGRqBhN(0hatf-`NkV~K`3b3WwXDG#6Nclttmu2eF->Y z)vf4^_1MXxPy1S0w%xHee-f2fh0h2~0!_ISOGKu!%)pA;um(?)P2HkEv=KNnOsx8I zadBnRf3w0uGxK>VFnMKli!ShF4#6^_H|_x8)|?I6^%F3!03n^oX3yt&`g{~-x&Gx( zydq7~E?F7+;Roo% z(aZRcfbEb=L$XdP(ib2%Cb|Yr+CYnd<1vMfFRGL;oeEh{!*AS=l+8g-VoN%pGNqz@6qE}#75Y3Eve*Yd0~paSylLYg)(zP=!>{wX6bUx z(c0kCYso*=-7tSt3#|1%H2}u&4xlZ8qmc-Qgx>T6TDUnv1gk{uOmk5Z6S^}}eSQT} zQm4L7AS;@4WR$^p0TAu^d+C0jE8Ppsir3@~qu?gZ^J*gqP;3r@Esi& zcqwf2^2hfq^~U_?gGf5`LTZTx>PsKQ|C=sPwA` zq~hm-BjLS+r|E~nrLry~Oyi^O1+`b$||9VBW%d3%?g($*f9Y>wcb>DebI2{7{pkIkV>Spom+QOJVEnR!U}I~fbG*$n~T;qeFa ze*ghsn++@d6(ycO3?}Wl+(^DW@%Qb>Gs8jM?ZUYZH4uw|8>m_6pzOFI6V4>J0jL0oVmCdgyp(*O{Z_4dHDVU`pSOIQ zdJ!21nktwcJYboWagcWTAPAbT- zy{sV3EiW(cj=jA{_yGtv(_Yb}9%ktT?hL%XPu*JfWJ@{aX5)|MqKhB7G{Lvq)?cBy zGht@0ot>WqLwGph)}AVb5e)La}OJs*lffiZ&yGtV1{cC+;isK8T!!tO8BZ zup|LyicC%&{~{(vzG(GM!cNX9=BRU2iIFa@_vYXHGu!jfHtg|1O( z_+WzM!9g!_3J{k5=RM_=v`z!)Q}=wBX6Vn2{GKxte-_5eCM)`k z6`8Q3JAMD18G}puQ8J!;uME%&tHaSzM-B*p_P0&?@RhAOI)O_7Dvpb?%e0%ua7&g$ zd~=?Hojw8OJU{iJX$IS?<2PTn^>W`9O8>zUyZp6gy!BMsbftK4=>x0`tSo4KO0{F* zwkVLPnVC%_$cA6CavHsxi3AvN=Z$E4=>ye#vp6walBH9Eix~*`i~L_S-=uM>ItXN> z=am0HhTV^?+nC&_yC4odXF2$xbg5;xI41%ZoiLK#g{y@XBqi->V=x4J%@dOj-{gEa zshXadd4k8Kd>FU1_2>BA0bl|2+;6~WCn`45NiuJ9;Q(gOTm!5JCYXm{9w`Pv2?jvs zg@pXSBswq#8CTc$dJ$zE|COV{Mij4b-Hjzve9_awVNtiOtu3)9FXz$sP#x%#iAw3N zg!y0@Yp0W^WSNipPlq&VDHdk!k(@F6h|LaKI#b4?(Fm5`HoyaCw|3lDxz4-04gXIN z>@1p~<|+3VF#_#Fyl0nyY=H{ez;bGzZBHs0ng=!fpj{7wjA4_4Lzxa;q18=7QT;V@Q(oN_)OU&AQU(1(Vz<*G3{AVl7nB*5|h6>@MKdFPE@9Zsy8e%b6f zx3Qt2vy-j+5?{Rd&F$Y1_J;#Q_mqX%4USRU+NHE`8&!ap9lUVvjqgb#9ARLc-(Pv@ z++qTWotSti5$J4{%yg)mIa#}u=1|_6U5&(o;V_E^wfSITg&Mv?Mo##vI?yMg_gbr& zgeWl=Zjm=g$qaaB8)Aa>pQebsm*`Wla@t7tz3UKj$)x`p>|803yaY8A>V~ai$}1y| z5}dNsX>LzT?{LUKhCVt_Gw{5Tx6_4w`2lz+OSP3WafYcQ64 z|C;*rK2uH&0A#j^6vq>i%)g)n-KlW~eC4mB96!HK9<}IGH+U`fJCJz2Wz9xU=-?~B zsmfKM#JtA@@W0;@h3VGn@t3A-%+Y5Bt9ToEei^?udclPF+xz2jSIxgKvVZFVlK|r< zBKSA|e7uH==_A83HAqWC)G|(`;O1xqi;!?W0Ih$|aZyA|{^#wDG$IwoI3;o~08}47I zYsDo(+w&oTwbS!m;W928e)UI`=HgUe-eayQfDA#T+B?7=cQ|Xo`;mHZKHeqkJS7Vj zEg`M>u;$%arHjq6U%w10YWoumn@j9^gT7r+*}f;PPCW~RZ&7qBQQAl^dIci8|9pML z8}Dgh=@bmWXO+qqH%x(??|)bhx!TmBCxdmAM)@k_LioJy0_M$26K*H-x!9|HR^w&~ z(4#}d)4ekyojJp`%hn{{z0`~Fl#BC1kpIW;F`%I7# z*8VEHN4Zz6wn`Cl8i1to`{=_inNSzZiG%-&-n&l%O6HA!HrO&pL*PR(aBDLl8MxYz zg7xVFE)ecj&`FZHZ+Y~kS9ws&F$`_cBMRPUPNpdD@qiom9-WZ@vZBU_|K>uqku&{7 zaPUir*6P4pQ2XEKQ{?Dg?G8C`qrFkfyQwB$`GQ|x_4U@PwN$Qt|4821>t-KT# zxmgTaFQ^xs6wla5d}`=ZM0v#4)ghoAjGvlV8@=(}FirK5WmV|sW*COI7kb)%aY$OO zkq%CNzZSgN76IlN(PdXO0*ywdl2s>!dwMQ*A7=E-O;l^_c%?n2JeQX$I9DOe%n z*-=u;!5K65m;$3;BiM$?S%+su)rF~q_AeJE!cw7(gtvx-LjsoLEC^E8nw>K}2oco& z#+7>(0@4BNH*Bu2&#k?}Rg*c*wU04_uYAR-|5`7v^RQwJBy(Pm>uu0)gd=x$0;yCo zhRm&i+Fb`qkq5(!Qz?fGJRV~hM)&l4*BR!$>rjUVOYb4*Zusz>2AmF`^$9z*yC7ao zcz=UJ*4Sw?U7jbj$VZ_|(=>mL|HIbO@`1^$eOCRmQXQ7@pSl4wD-LuB^DOx;VoDAA zSx=22eiL1#Ffc2!1hETF10Bxza#tP)D={5&z6HxN`f@Ukiu-(9|CgapG(#2gOM$A{ z+iR)3_XJl>BtwmeXMruUl@d;pNc$-<`eCke=0^mNA6?hPY>#%Rbm*@c#xu02IKlgdFJTOCqOmR6NkKZv21>L*^ME zDwXZk45V`^Ta39y`E~MznXsgw^5Ky8{E=75i1laGM)MmdN2U{`H3uZ;_IGk$9YoP~ zCiDMr{tW3CU2J5Wug%8B-{;Hx6t{8e(Oe9hoXUjoTP2Oaqsa4lZw*k&H$xH{7YiUW zEW-h3U43gQ>eS_5xg0OTtqUls&FMBi$K-wkr>+?$)_Cpc^oQzY6^upwSg3%)nO?J|fq)L)DeBjn~z`>fXae5)Jmj%5CW_Bl|?r!n-kbNUG6ZInl_N!RKWJbyWds1j%d+(^0SlOXx9$_q-h>>YdD@rS#W?U{KG z7wk^0!yoPi{_UrKu7SMvQLdxiq2z*6SpykpIR%0w2wk}tnG6@F+!S6q!W=kWY*i=U|4j}G%m78oHJWB!`{9DP-g5t zC2C)NbIQ>}gJh(iF?k?7#7gUd(&-yUD7ypZO+oZG*sZ5J20r&jZ&d`i1|XdS$f~{r zkS%PWgsW}M+Xah3Mi|d~%e7b2hfbjyR#M-*6t0B$#KNjQ1}wH*jsc9-nPepECbS4v zLy1Yevgw-aAU-eIVf3W+3u1IVy#bGqhp*3sivbKmLIuTOz7doiAxG03c!?N>C5{EX ztIVrOx)19OD^40>beMUfP3RbfrA9~8)8NhoaYEAX(d)@5#^iMyH`5fI9?3|VVsZ}m z8i`vIZyNc8q=975d2TPJm?e(_4E9VXpZ(mAaBJ6_@xY?03yt$j)ZC@60YXCelDE(F zSI^)!N$UG7$oFNJeIF@u{{&7`AR)m(>C@W2knoX_1G7h}vDptv_J)74QMb&QYER^K zdM#CbshcEtdJ~bH6kU-?`v}?UQ8+l>FcPpK1$DcWrI{JN6Z0PzQt`*?L^6ZOBzbhE zrwFjREsE%XPNE@teGW<+&TqD7UX!eIV6A&cWO{m7w+=$b0vVWQpFw|LEJFVvooUbv z1*4v2n(gET!8=8v0pC!{k_cpv2KC*a5YZ>QlZ0jL84V*IZ^LMT4nMu4eO*@hBxu=A z{2TKbmtJrQJ?Tw5e58JD-QVI`U{K&1;-H5dRZ}c^pfG z0PY0+7(J%cd^Zp3zXnj?%RqL>B)v~ZxuAc~FfuhJtGlOwL*g%n!*#~P2FnUp>{RDN zS{dD+UXm{=C|K-EIT1^=A3p1#6_4>2y2dELc_t3qK{SF{xX5+LJuP&(dwse$`NhW& z9coO7HKxa(sLO7vat9GUWCdE?4CxPC`m&esfkc5+U%nADw;DmN&1(F)K0~Q+qAEVN zqhnm6)^Y4Li{%I-8Iw9UnM9WiueTDgU6n`GN5z#J(1VhqDVO}pD0PaXT?$zU`Oo(g zOxSG+XX2-ylGc6$RX5fm?$f!D!$4!f5RHt$iJfGgEG_0fk@j4e*rp8vlli zS5CkV_FA^7cAz7d<4JwmfG*k6&Yw^BBY&}@qXOo+uEd;jpP&3a+IFj)SD8fhJmO&m zrAO!`e@4=pT`^;pX_g=%$}IZwT#MQ_awcxlV>gQvIlXoSw;07jWM6fUCxWggvj>zN zPTno>t@t{CpS26IRuZfe!&m%(y+at(d_?WytuHB}+Ko_8lX~*_z?s4%ah}wSY4{Bv zrQPiGUgU6EV_}%jJNLS%*kH5UZ`MjCmX{Q50PUR)jpB?35>;syp8o~r6p+SIAgmUBjGH??YS%l`eO!X`(wPC}L+{rhQ#}!m<>&ZA zB28!8N=nw(l7m9DAx{8i9Eg5=5fd%Cn0powU*t}+R10(Sn|^+kpu-aN74<=Wo+t*TS((}~J1;P(M*cNk?K3M=M6U-D;huO*~ppeHRE#t_GmY%4|9Bx55Qi5J~ z)m=ZAa<>AKU4P1{SErv3!&8mV`sS7Us1cAdRD`d8v}-mwthOM{(dM&{fstfA&J0^e zIvhMF>&=UcEGpeMnxYoNW?f90ltNJ-s0~*vt=M#s_KZfZUe(qBi&CQ;abHnN(or6fnY{;UxurNh=7OBR8RaZZ=7+v-me4U}B@^6sze9U^PkvYl=_yk@cwp<5 zYoeLSGDIC36M^LJagwGmX-v184{cp=ouoWSk^jn;eE#PGp1ObKQ3~?}g&C!tg+if{ zw^1C9-uAvMF6GSu+quV7k?WAtUqe|lXVEL}2GPWZrMbh*m#!L+{ff*jbL8O&EqUcYRr8~EJH z!zFl;s$CZh!D@CwYPXj7$6Q}4#v?1dD%6+3sORTSJG>03G0mmhdLl;$UAok9k!(&rl?KjQ zM)&y4;bFYibfY@@r>@AcYcbfnX4|SC_s9q`mQ`Jox0xe!d>sa}8utni8m9->0e{4) z#KE+b&J8HmcR8;E(I{mC{zvhreIeYyKddXn!BK04RF)lw>*uWj^b$G}{4{WRtDVJY z{w-`1b;B%}{Pep;yiOXXGQvu#GZy-tvYE9cvsub@o1s>t3hrR!c|CTDz4j>OK8#8w z-LIntzI{tRx--I@@JxYIU>+ccu&dZDscy35+d{Q{Cg8on`aO7Z{T1lYuaO4}h1%02 zyU!=aI_3Xr+I>DY>s(_cw$aG%x=jMrWoUNV;w1zsgb?Np=Y=Sa1^B(>)O!I`QruEm z523uRK=As&om=MenlHDvy?1(?azu-2c_#B&0w^MD#SXJPBu=VDOG&=;Tk*kzeMH@btK;~S@9Ki2$V?#})8`5Q9x z%Uy*uH$m{+?Y<_LepeFYBkfT@5jIMF{qopvbkEcH);i&r+=zY}yx_4u*1K+pu6$ic zzR6DhkCk~-*e~1?a6c`b)%Et??X+!J8*7zRzpnNB3yG(A~ z`WscV-5(S@s8WUjM`5a+P`}k%~zs)A#+eai8}o9(3j!;9i`y$a4aItjle{b>u+}J zJjY|#b+0!&>DEowzddGsrI{v6pPWA6>A5>NgYo7g=S6esKqmrhPuw($9UoE4-WjO< zCV0fK;6}*kUa?H6`Ba6DBcLY{1Mkq^e~s~WNf!Hy(xt2KVeQ|r&hHuq!cxJv zfrzeRZS65T{TtZo_0uT*O^(X?Rw5pZ)e5Re}?_pK|NvO?2FIzb%?{%)f&9I zZYE-^UK6jrmt8h8JU);;FXd3LHp83mW`+1LZ{R6cUuCMUS*`4HKR{Vd@I<1?eXmzW z%9}fR_H@77Lv_=sc@|sG=|i0CeM#DSL?Uh4;!?G;0swMQteLr^J**zypIqWN&}6t8 zpTn5UrPP$5d^8uWNe0cGP-&L{BtN?r-0B@Pi9EJEB+sk=la7T3=9{G)U8vS0juvh! z=6af1P!fnbXHz#MXL;x|%I~}p@c5vb@qA(oX2q^WHzpdkf_^&{_%@uVQpc=+OLV!^ zPZ^LHpI1hGAajG@rKsY?x_vX3M$q$XudSD%W%l^Vtn>AtKMfD}s-xgddlh(rbdU(( zXDNbV~GAC2ixD<%gU()pvPseGxDc(ahDJt=oCwy~c&r!x+?fBxVPeX``EtmbCMU~b?34{@rEdJJ8m)O<9U}PT(qtPYqu$D{4lSF%tg^Tk{oCo1 z`;eTfOJ@R*wuq=YPg)+4Pf_>G1Z@*q)1XvqMM4h=#ih#oRJ^*Qa{BDKig2xKceZQV zDr9I3OU`{%8d96BURAVc|Kg&Qa3Du;x(!}U`>F%st&pg~23~rNsGt{>>1WUYzG`l^@ zvTIS4kTFw+r`NaB2`4j&n;KS%90IdyHBZD%ag>8AzhW^Ke)1BKNufFt#*}Y<%nID< zd&ZYnX@QfWqwPxSCJI$T#%v{2oZo}=C(uiCMoWz%MX?YPENkQ$(`1QNh)Q@=J#yYm zmx%`HJ9jw*-1r6VxbN2XOhP5#BWU|^E4k~v=&CA$T)RtW>M6YEF7yt!xb@=dF_mK6 zCZXP*ujq_1;aLHG4>wb8#|s1JYd1rdxt%EU4+Pcm^U6aUbPc8F!Xq8s-}pc;40wcd zer>(!8$DY1VCk|p*4uZ zkxZ7quOG%OV0Ml(k=+yupE$7ir*h<(?48t+o;&0AIB(L2V`l<2?N1J}M{A|K*gYQt zdv{)Wlhg=VeGRAohcokcnLQ5tLN3!!3>dFZM8=eV zFExXR?&_wo9zqx`9E8ip4VTLUJ7Jhp0yv^Ur_R69@S{Tj1_oi9!gy{b-@UV=ZlTG0 z6-LnH6FahDEmMY~&P}Vb)30T37yvm_o~gkrFZFDk@F~c5L5~)ba{(76DC8w7em5i} z+L*aoKb1DRXw6`=b~P+ge~X%33~BUTU5y8y%;JqG7_ToBK;G9gIK?`v3wl6soH+I8 z$4kC?WK*JpAnu&~AzQnSOAlA~uU!<5yBDL-Ni4FGRdwl7J2jZGYxnL))9s1*M+1ST zgCH@X?kkNatvjl(3zDx38Ejv3Dz94h3g>@^#WQ3uYdKjKRBUF#XM)Di3nnJe{MG0# zfWhT+lx3-^O84C)dhZ?TM8NdLwhUM)M!N=0{Ty7PS9NBVu9-cj|uB|#F=Vemnh1!^lI8)7EZopR;n_Eg46)#%J850^jRQ7u*-+Qn1N z(lILG@&}f%q_Oj)=u)vd9%Bow{i-0?F9HbA8cyR;$>sRf&!AJk-=Ek56tWo>VdKa{ zbwT4Vwv{~_zd8uC#{dm@{?Md+aElaIuF4k@dYXDf{ZsSf&42|v7=}~8gYC>oGDo>VBX)TKgZ9ePX zLd?=8N?N!$mibD$_=d^VmWyW9;b}yj#a-%HA6O9@blU1;aRHt)(y|HIAjhB$=W9{- z?Y%l-jRTl;OEwZsXpWym&etrwc!ha>**cpSY~i+|Z2K&eLY=I_#BSWGYheOdV{vM& zWwhrA>ZN@;K0@29h6#W3SXvuxiHFk)lPeU3C{)@rW~xH^v)OpkeRK`^%`9MT#3{^; zZ#yWAmt;Qx+ekBOCuJ43dB#Ix%YOI-N7bz%IHNLxd8wVtvGJy^Sqk>V=Q7GRj$?S9 zq{~ccFOwB1{>rs;W-;}1Qzta@LK8FmQVeIuiZj)soW3H&f zRy0K-g>2-IItc+vzSviQKz-ONV5Y{ylZ7kNzcES+;q?gnT1t=10-xXbyiGRm(JdMr zt&x0y9@ItqaOqK*?J{SZHx_07D>cx(EfADuvQtZ)w}}`WCC=4^)c^#oX${c!CDn8J z^Pj^uX5h*bu3*}vrA?idTbnKbP&OoM{xt;==mka{m<#Ir8Q}v4bQ)y5XY*2s2Wk&I zC^N|WrSXS=bc#<;cs!8_hy%sP=|wUw(i1HcPn zkUi72#*c-53&_Ms`i4x%2v*6VyrO2e?9NLz>qUG zerj>i{pN#~1H7m3PRhPfFe+`+`O2?}0S#oV`p7|F+C4;6CFKp#@6U9yI<6FpR}aBm zEsjwg86hb?+_?cycEYIafM9{_j+1^)3m>18xpN&k1*u0=zxEsNPA018U59mJa173* zQbyHzi=jGnd6-t{)cb|pzui{Wlws`1d}Q+QsUhHmvy=OTW{PiaPcj<(lVki&TE}$z zz9Uagsw*nb?9P(#^n7c&e{1Z=g>^~+G;>E&A!)#2eQqCzJEU(S0g93#Jp<0j5&5Bn ze33-?QJW6oyG34|uny?C#)d6%2oZbtETkuezlI znHgdRc%d;);Jv`&*6oU!o+KA_-neJ$arHl=@sX)F5| zuP#glWT;+cy?WXgX7(L~Kj39UI)9a&fiZ0^TrV{(&tjv)5ngyFa^EvA9T|X{<@$9; z21?^OI^>-$8v@##f$b<5p9mO(I?M*A-@0gXIEemGH1z`diQBU{!nxtnqHj%5WVThb z*cq-}OXMWyLt}v}AT1y>5BudcR>#a-AK8AXK-u(|xIbfvQIU8{Dd;E>hxr!)JuG}{(VxB_vl z4?rUMG$54X6r?ISXWGEFcGK^$pag(WjyQ*Xl|(&XEW8S?9jv4t6RyMe8rvyOWb zDa0H9-6nTgUdFSEHjHs6(74=}F_!qX*%*d6GT5;@P+tlFKIO8RRN8cc>fsMg?lPXE zA*Kk79d}f5OOoglD>~exVG%(R2=_Ae@ye8oy>hQR8{S!N=weIRL+l`8Fxv(L)qf@+ zL)Lma^D8sECjE*Mr72eBX0h8SN-B;ahhucd@A(v$YLb+bHXNRXhY-p>T?>}&?rBjM zKUu_VH$p=s|ZY^BmXr;u8jq5yP8 z{|U(g(o`Y>s=^fjd>7s*hWPU3tO|AJi%M0{yFJnxx7Hpx2Xv9Fem2*5TSTlcl!IM{|ww)?3MRv8xX zGrT%NXl%^1W~0Zj4BeiVasgAmWPaxC*&UXq#y~ULexOog~+F`G0zuSwS zru}f_;ZcS-U?6Y^U`I0EMq-|2dLIAa@zPCVc2QD$qLQQ)W$=oLVC(qT)Mmy2wt@Bk zVjFDn5n8D95xd<-N~Xj9nSqV1Zlr+MU)ODMk_czQT{i6Bji9BwF00$M*4=4ZcnQ zfrFp^-k)+|+(2{Xw}gsY?^ex+Q_tGkIv3MP$CMk^0Z8UuTU)y{TTX6!J8d+z;fiY(5y4=gM~bNErsaEc%sb;SfA`^44^Yy zb3T7N{wDxBqxlDf(w-8sP^>fte^k~kUwl+vm(Aj# zlMa|{%qt22Y5ICGZERFSyZWKebG} z1>K1km0q@<12t|e@zv|OP4I?50Ql8V-T+HX)tWh#3T#!pks9;&IDiB)E%3U`*Mkmq zkoj(}40>XKud*!QqNXa07z{AZ0@x9|1~6BEQPAFqUf&PmDT;IXjW6xlf&k(ZJiOs5 zDrU{9pC{Et@gHcnF*(G=zyQiwmO%afwSBllWjSa^L`mR4Gql5{y*AzTB#`&;3@gJw z^85aCkLuuPG(fYz*rBj2SwxH-*NX@MGoqipJ94p?4TGm14H@zB{!#n?u=Xb4P`CZt zxK`~dcZn<&k%Y2jUy8DYR{L@bF~(Ba8jR6KQpujdAY1lrkin=_vNITrWwH(igE2Fi zvAoyN{oMES`#tafeUIP&y$;7w!*{-OeXqIJ&v|~%GdT)~#kf^MyJaeWu9P~uE}wxG zw6aw}@Z*FKpnJjj)?$Nkt{e3KOX1-wBgErxi3cFJChpG=l&j<9;~TqgfsoC!u(WXe zazhJfF7&)|a&px3Q@*0jv2=hnJ#Dxfz^UD4x3sT@pLJW`vYO=UGUjGVd|@$hgF9=u z$hJ_=w$no5G3wF7r!d4|c?EGH|LJJRgTdhNM6b)e{SkSI^~M!9rhJY9**}M9Q%JwV z!6rWheLy_Nhym=nL*s{;Z2>w;_Ppknu-C!Z3k03%FM5(W0hyF z_@yJU-c#-H$!@%Y;Cm%(wx7T?Q4Qvl-S3e%%=%=>3Cjt64K=EWSe7&a_fI)X@F^Z*HW99!VfOQ!_sE8sqImUU9sha zyLoubrD24SnHb_@ZW{4XM!t{2DoH5QoA+lrsne-#|F!18b$`=F1uOx^!;w_ECiYpu zEnsn`bF4L{!ooh#qxP14tMwyRFC>`;&s5RJ0pACf={g>udtdvhpKV~PuPep|eaYji z3=O$Xf}G*eD>zjU_ScJs){djkaRR2d&b=KRH{)~hZ$ZKbV1sgqYjQ)7(bd07BpfDY zZaK972$k^}c_6wx1=3hN0!C#HPs}fMMBPhp$m?tiEWc^vb^FDOXrI)POa)Kp!)6}Q zo#~hZwhOZq%eF6ujEE{OhL9EaKnzeg!Lc*aQV&dIb)QF=c8`6en-@6t0$=}K#| zqgYNAuShCvZB*m;kHG-KX1)5MdK+S;p8X@ReuU>pqqB}}U+TMuy)hLHZVIdI3odJ; zwLjMzuZBljny&OJd#=c3ul7B+jf8s(DNpQMuO(xBW1PaMnp1B~=abhMtSbDE3he4O z==v{+1{I@`L|qVC2S2p02xcMr{g=z@-x?GWyQK|wj*Ty$vfa6F(&KLXr%Tvci42*H zyVcF=?wD&yGMB!GAJD8Ts?>WZ=6s&O%AmH@SKvXj5z`(PH#!mr-? zm87KP!>+G(vv4GP*0{=^H9+sR*y=;XDgz>Envl|Gz-NEd$25A~)!m&yCiVB1H@&;D zrtNp*M(PxaAFPzb(T+3&!$n~Wz>bhz{2Fz9Y=>zjBMnaaKyO&jE;TOA)PxB;^s zwArG0vyVyVECHlvUc7osC$4m?P8nfyLReC(v1w=HhZ}2T6B82+Ub&(l`?jUFXThey zsQU9S%0WsxELavYbe3D}fSWPWX*0K!%LS??Vz{4TY5(})Vw$lTn~=Y-MDG{UZJ}F0 z3JT+5Gw(I zQta68Ok39h$rd7k@MrKHOK8GAp$O^9O7nZFAUwc;hqF>aCEH~Q76)NuJxw49EjGsqVCk;kEV zw)6NY>lKFXU*zPGx^=|R0DHf}CMatg2iw*k48rE?0D(f=+9B1eP&W8$ZVm~CfW(s2 z43J)tCE3zXaQHR9wV`F!={w8up1y^$?b1dC(HXGOEtJ@a%ID~fJl==xu@n9b7Xzv? z#LYc&<*o%%_#zt6MbrozNsAYrC!OaE!ZvsBXDg7kr_B^*%z^)KMBd>enmtx81eK`g zmTWmB4IZEb=2xe(_?dGJKhAkTFxuBerlp8zCoV1^wQc7ZQ)k(oljDY-LDM`u*%i=S zIc`u_`v5PPmTb~{H+ z551NtS)m_Q6uf?26-D1u z>#3aJ%5^@5n{1n7dREg{h`mE&{k`~wfdN-uWrw%CJ1?{4y#6u#jyvJ17yi5jvU4hI zv9APFt}RkMThMuV=cpO%?w{`DV1N%g^xFd< z5VlPuw@9}nj8lJoxGw+L$;zjkmTVp9$Z}PGkeJn+tU=t&oYM`Q;#G=_(rAUpLV>JidmfKLU9DPYe~zl)}?F5#alWgLUA|M_yW zO*Dm;mV%wGb#--_-MZP7n_z1=MWg6@5V2-O9?C^rk;IzZ^>aMdD_6Lc`9l*-1_stF z3ST^*51pT%7v^f3 z%0juf+AZ1K5;fg!UAnlqnCIlrHCRABih{O6)oN*(A{NOT_X^V9&;&0)f3#}cDdc#! zD`i3EKFAwdE%hgIk+QEgwOzgWsnmpsDS$8#(@mVwAb2Z|Ae+D`Co!zp%5%f z(;e2OnMUz0YW@C`u$57ApaZ*RWf{!AIno%#j_8P(MT`-B7OYBeTEA4<{p z03?HH^yR-Oy8UyzpFVtDr=&U~;|?&%7F}Mhyr(K5VIYI66v|P7+;tomh?&rVi@B|4 zHN28FQX53SGw|^-UucptcRehdebw^R^o1ARtF%WhO;>3M%c1k(4JT~+dydn(uu~15 z<58pli;Et8t_6}vD=*K21RQLj=j5z03rQ|0`~F0Og?U9;{mIvdg;&XEJhdUZ|LQQl zcDeJOP>61lH#2_mXB{-Z_{UfK+K|#A48@E1$I(A5ZoSV^5S%3LI}+}RuUu>JYPs3! z&05VNmJz!SiK`jY0wl%7BM|D$cK}ubWdzEib2)%H{Wr(@cnofyF4t#;2h z4&}wU@HDSwpS^>v7H+}%1JO*pO4oun4bD}D~7xcPe6%GImi z>=4w-piWh;zoa|@Y;sW+eO8uKfo+Ad5(kpQ-oFR%=o=TJMwoOZy3?Kl&M$T0d9rn> ztz=#G5M=wnM8BARoLe#{;U!+OONk%QjG~NJKl0*n-KnvK{dbe=%MFjOcVg8rEwbSn z-}m*;tY)>fB>9$9mWvN^DGvtbmu8?#;-Cnhq(qjk$GOGnwUl1p04SUk(*L?*z`^~% zb>^B4$wIXi<^**H+EQR53T}br`(j(k7IwC`VyH0ES|_|9S=u)&Fv-XJg_{rAMJ>8# z`R5--68EhsRg~3REZA_m!mV>PE_&@{ZI$joM|nooH@b&}4?`IwTg>xPZKTy%)xY~_ zNbJN51+bmKf~CB^6Yx+R*~;c-i>H3)EddJV0iVOC2txDnTjt9ou7e zs3lh*cIm%UjCzVp^o}(tf3E*iqUvitPNo7miA#?bEu9&={c-oJf@h@#Hk9#zq#nQC z&@;Y~^~=gCyQec*8M(2Qtz$Qx^gNv|H|s@N6WZ>d!MGFd0yP^9Mc%HU!9s<1Jm%9& zTCa{U!Tv)cONXInZEelENM2sm?jF6g8k2skjfd$jc#L7}*aXzLN#uHLM*pdYEscq~e)8zdMB>!hoU9U|{DcmlWNu6niNb-ZOuhns_>S9LL`5`amA%d+iinpL*Ps_6J zcgX>0+@R#8>Oyb!kWT^oaNuo~rOqC;+Pd7S5$*W*$mUM%BvD?use%R(x8m&39fpg8 zllsm|YP9A~(Z|_(M^tX4He4y2nHWcr)ASrvBJvt8&hnT<*&)w`daVlE73PXIFTrW6 zUS^QPqC*)k!2eX_Qx_}b5%R1qqk>5f|}Tuq%gE(b|xQvFxwxfy6+EFMN4^A9I_DjM6Y~KGS_;n zPU=l2Fh44EPVXB0X7r9s(ra^Dz1F|9(n-FeI_Y#ZZgw9_mj3`CwAkh*JFcou!`u}6m=A-S_BFEl59os*qxtGy?i=aemO<*kFq20MbCNhBCKfs)I=%M zrn^dI8JyI~f>2WxyN49y&oRik8AfOO(JvTdZ=pVl zQ{42M7|^m9m;ElK+>uTwpR3m)z| z^_SbOSeMIwsp0*!Y~KB>0&CKy!75E5=O+6WdNvr^<{~_n7U;>VUyx@raaR>*R@cWm zjgdO+e##Lv7d@X|Nmnb#=o%FO%n0;G<92>UitdRPO_8b{Ii4Br=@!v%zSTPFypKe` zEPtq2f-En6`Y6Su13A{2o*{dq-~5S@*_@O6bNUZOaAA+CjitkhQdK^~tHI+c_p^bmW0(&>gq6tcHWBh7u`7k}nU zStFGezQqb4fj8Zqo)uMfSj(va=43ED_t88p3sUk6DHKq=O@`E^!3Znd*Cf$<19p33Uc5wG6?w)F0#(Ymq(2OA-#h~z<7H_#r6DVDV5uXQ zr1w$MyQ=iISNfN}EWbGj;)jo-0Yb&Cf9S!sOCpYGgG-r546$UV#rhtQH_mrXFuCq_ zDk%|2h#sExf%qiZ{KP1w77P(hEfvx0jG>9|mhLjidb8mxdwDob4t5+rKLTGxd%S<3 z9}7hrYLHqJ?k!Pi**lUW!cmJCs4&k!6Xnjn{l_|XG2ok9@Ap@LxKPk8=Tn+ZEiUT? zgveGPi2m$}XuH;FZTZj-p}f9dE#ccki?l;eU*LP~%nX`4K)~FL^6)u)D~jZYYF1r4 z=BfTHLDhI?l7QCW(V7R^ZBDaX=Byh0v*mK68=gP)3eYTEroTRW>+DL#a$UrSHap{z z+^v)Q5BRl;`$~8oF+wa{bmCx)7qCTIbCjE;+`Zh2i$MuNgk1){CW+f|Cd$5w8dh%#gU8H;t#n7 zsN_F8` zq48pz$uFO~x1wOkeOw_uPHnt3!=BEz;fY4R(QXx$mP#SY-{-bNN(Q12OQ<1cMhimk z*2I$K2chdUdB0n|nXlI`u)fu3EbZJ=b7@naHAR6fM5p-*O{*+*y!$~tS)Mm5^u_g# z1WR0BKG8s5zuDe5x403Z!m^u#ARpnjcX8hdPY_AM?b#t=dIve|K=R&K=1?+7VDVI} z8Q*UBZpJ*=vkF1X$vyBWG;q2(<>@RoFI+RFUGW)~xb*RvODLsU@5JrhGa)_srNJxp z8UrS)4MvPG_yA3HPeuAi)>R%SbB3-`xSNc1VYGFQ^*R=^br9<=ZkXuH*snpy7E54G zc%$XI^;WCa>Fj4X(I+=UI8C$6btEQu{9vuaTEEVbP%(s_b!f|$ltSI94S$s1he5Uc zuCv}V*Ul($-w!?OF>~z+?6mwBy>s6a;KH9n#hNdK54Vdrqfven5b7gbVpOxeOb4~j z2$$_9j>8R*j}b^{(VN0k3FaC+K2Of$u`lHxxF=Hy(}U!dHzd;eh~27H#w=&1xo`QR zMqOG=qmR43<%ZR1ll}R*n#IJ}q#}ChxFdKeg z-IYAPPtF~lrNe|8h?}v7Am{%q$*Sp#8}l^HpPm^@W^o3Bp0lc|L%1u)XQA9@q&qZu z);xdr6~l8rJcVvujIOo&cRs}a@QLit+)cgT=vHN$wX`}L_;MCbyrpb#w(s=V>{sWK z7h}f=3NI%;=kVpq1x+`-y+5-)8y%c3sX06t(H>g_^eNjs3fs)9)k7$*{$h5kO38sa z8h!aJ862Z)dsb{?&t%@#pgA6uLpk*~n%w()l;K?uSXZ~z6eL!pIBDE!_;XoOA)XwC zg0j!HnRm4nmlRzPKk3U^JxZjOOGyG*+8wjZNpT_BaHc|wI%UbKeYUJ{o1fIPei;ou zCYCP$ZSA+ZkXKf^E8CIBK!%^=?d@{6!U*aB;X28VC$ zRkt6~spvF1Cl0suor3yXMGasAF({ueqKRNRGO|tUhfoX#>-D*>&oK<-W=pLqh&CE3 zGmu7e-V^4}{P&e5yRvrfUv&j+jy8FL3QH#W0d8C|YLrg8+(#K7(7_bzun6+`U0^W?t=CMpQY%z$W`rC!RYVewpzWywQ?O-Z=NB8`^ zyu6ACt99M)9S(#p>;=7vK2E@>f?YnvM(a@l_xqNQ0BFrUd-q(!>2m8LC)*vK2q2w1 zwO@#i%`s<%u5E2=Fq)hSU(e)%O7IQ`)X4H4;z9;>lyvKJpTGnXE}KN5kg79M$r_XZ z>JUZ^ljO5iri(RQ?eqQ?cP<8n!M4&UuUgac-^qt=G#{oA5wOaYU+EV+x7Qy~t08{s zQ%o^~_svHeE_8WXvYacTg8f$HqRZEO=(NJ7jiQ9CG>k$4qH(UUy_LN@)4Y5lIec4VULOn z)%=FmMfB*K;wqD?n<7=I<9+ht1($2Q6{#QYlMF;H8P%e`ZqI#QfZ{a)5SEP3%=w14Hu+I=bMwduR5U}2#z*kG z!)-dJjS@Bbq8B(#PJ;KIm3)w2IQ!;kB2uo)WKlorBvNq(ERSZ~p+0x-RFt+7bgXL% z#p|lvle&K4OH)!lp(oLd=Sd=7(pphxm20taSeyD;kUSHdFwogUSe@`OU9_n9Y$VdZ z^bRU9JEttz@nZbg*(6(#TNh5NY_$lRhUKi@Q_(QRgfGFX(qbUzlC`XR{Cl>G+11tT z)YfV{Y1j-sTwE2^gYlSwCIYu>U+)yT2YKUt{yF1?<&WM33npmfF_Ywv9`|2Aqc&tc zyO$*2tFAEveS#ul+#9?Eiu!u4S|e)k(-@{k)S&;2ym^6wvdD;)4g&(oi3+Y+2bxDm z_Gz^KBZ}H;?Vn-SBLz|KJ<$?EH!w)5HON_70?}sgSJx5{dAx0n(_&9|9*ws<5&fTF z#8FDY!G_Ru*puICnXcdnbmSvZYVWZ6D7^j2XkfpB;k zL8&708Sty=q+M`)xP#<%xbtiEkna7qY5x{OKIHkH97#2A&z*O5!0v{FBOB=Tjl2dj zE5fB)IrIur7dFDpBS9V?v&Y|t;>(Y+4Zb|GB;CuNVan?;Z=E+}HoLn_n(%7t^2M`H z6&xs!2Sxe#7$928!Q2W=!}MnAg_c8B0%K|sbnW(Ms~SS^=IV+A{i|Ku7Va~%v*M}C z-<*;aNMtyaJ(<# zj~VsvRi(C0Uea$)ax4WBq-)nyWFix|k-lp{5;5V^w6-M8HLL<#)v3|>al8CoQG^|g9km0M_waag+m%o!2A9ed>ue7ZHU-6Q`m2MaWr?ruW}$-?G;Hl0gar2gfeA^|jTK#W(u#{xS`l&Fac-RXzAr6EO<2 zI!v<+9FmXWpnRRE34&OqsVf29fD~R*k_8-&5u%DwQZf3MF=V#T>}&T#EZ~8Hqy<(@%72?fe92W zs!K|4;`7YyUw>-@VWj-jvEMRdGA}M}fFWNypT;;fy}Mw4Z6t9681UO031UwGJCO!D ziP$&4eJBpP;a2*I82z)NKxB#Cq#@mm{8%8#>hRjzee6fXV$8R7V|D)#PW&Gm)+H&7 z69D}K*bWo$7>?-y6)YTo!=J;?O@Dv{|6aqC6Ju8? z=r*`^0aTOby;ulLd5qa^dC)Tfq<{&2@iS+DS~R$39#XPW-?OT_c8AtRe+KK4Q<^(+ zqn)+Vf1o1%CknxC1k3e9!s#h_RxiK5jyqZ3EOWsNUwl(__{?9P&bpVwcqF4?VnQCT zy(JYCObdQ774k2Dcb-sPxE4z~fD~O8Hhyz~?D5#(66x3)n)k1x7@b0>>hBkYkSk=%e?xKQ+{Ae^S^Nr!EnM3YKuN3_Oa%PVBNGxJ7 z%2YnXBC;@$@j)uC5A1MiU<9t*Q6hJWHR-4aCMK$SU_&W^jQYiz(+WR+MCh1#vIlBv zQpHcJWl~yr#JP>>@>2##Er8G7_Em~N3?w^loj$=5p_#tm$IQbX6wcSOd-Lr)2( zCChIv0_>Z;?-thcbi3iss@GUM$qu=E$Cy}IxgMTzbX+U>iq!78N#SU>jM;ZCuH>^$ z{4uphiTn7^f7M>k+k*eSod-90VFCzu-QC@y9r@^(MS) z)ZC3a(NY?Giga$=Xk!`u{l|l^H&MjAlZb4Z^AyynInged=ttf#9{lSDnj(Jb#-5hv zBwM;ajXa*{kYfA;&;);!g9zq2v$XzxJPyqauYZ0@klEk<2H>Vk=4wc}WDP{%wPHV- z3sCs`yJ#1Hi61zWUigO3x6D7i8Q0kO^FgJ?(#`2yQR$qUo8&31;sBoiAo!bR31qFv z=D_Tr1SexAVw-%w;SAA6I7GeIe&Z;sGGo7A?Af5c>JZyVa{uXy|MATK-yDhl3x4&V zwwtZ1*FhCugij>ZqJ!D9etLd>3nNJu%%wXJq-G&liNpB52z;D$2 zLvqc=#bRbBD!qt2eY>>$*6VYBd4~662>Q%C?E0wa5ny{oUqvKvEe@hMhf8Dq0v>Q60|R0F85V< zB+JJR0KUnJGZ3}$Y{P_%4>DEeyu=zv+>THAkZ)w{78;!HX_9?^kcOFMw2v5`3Z}G6sp6qTZ z)VGGi^*ywl{8;oYlL^0J0&IF)UY>dB)yBrgCx_V?Rlqs8guqY@WiNc7oS+2^k4Pgw z`nDwJi)WL*A5qXL+ix%Snu|B$;}6DAN41nyhr19M4QBx&Um1HJj4$jttB=>Gr>FC> z0|AYL?WzlM-}mN;Z{CpOt+LTO_+`!ON?=$4Eha~HW>4F@3&J1P7k}p=I|A+Dn_ylt z{H@o`yeOHP|F~-*cU-c>(|?T_y$s&wxmhuFE#7L2569 z<``QxYy-yVa(ZicOOJT``0uKJ75Q40Jmqvz)xOcs)~Devw-3}lX{}wDE5VL^MUnw~ z&@)~Du~{_Z#@uZ)voi`qkJ8TBRQDorD*x|Pkq1lX>|l&l3)z83MS3N&jm`5xd0QK1 z~!DB=Ax_sKXQjU1WQDgcq_R5-aAdpe~$b zEgS6BqaO9(r$I(t2_VninP^u3+-z%h-MOXv5-GpW#y)aUIT<@QtRoof2246~(6@~= zXkM~veizF`JSVl3+;Uv7jbH*v6|u8fN*zA+w?Tq@wQ&}@?eW$ zT-sSt;{&}qgdAIsn?)6uh-5*c@?@YIq*d=8(A`$JXpJ4(mAdB9bbO+NeTXOz-Wv1o zxNK@|-Sma7Vd~Mu5)`%jx^ zH-)wg7WRkHJLRiv_1;%bjg{tAXUGVfOK$D0zvCS%SJ6{v z=y7;5PrIt1|4JCP7u)8&4t;1EmF2fB_@PI_%kf^Fy1-fhOAC9Fk_NEu%r)dSbyjJz zYw|={OGhHHV=lqjHY>6IMuk?DL|I%Kc^SZ~s8Cl1NpGs0fZ+1xwISV?W9^u1`D@w| zY{)xji?!OEwv^r2ZsdL~NSpZczqbP*gya+PAv-$)z_Ss0iIAIyQFBPIuI(A3gK{0d z_Ag?ar?Z_<7xh#OOZq~UR320S?DG;itwBxYGlOc05M2*gy0A=S%_)R_)$LKcNyoTc zzDcws-U^8+@9KshGG+#1@{y`*{m5D*+(j(X8zU+?da0t0cUUx+jUniLm|pN(i9oy? zO{uqO+al2OwW7R)>oh>1a-o6qa9%YRw33g0cswdW*|5U0M=J{3%6Ls*dE-O;T4Fp) z)%JMX-ZKgHwyH4b>0BF7guzjAUWn?_*(7>;m1SNsa7|@C0b3~Psu`J1MCFhYDIFdM zm9Yj}m@Av>VM<6m{%3q73MD>rfa5{h_~#f=3_2z^7IvVg@lMQyH&PY)0dd+Mvxa|Q zIO1b+SQWH_grxaeh{|ex>rjZrnpM`657YoM9zb+%qc}4R&#}7HjT60A@?=-?50)(k zSvn<)sY#`NWmafE6X5!k<8qBwfg1z>TF;9Vy3VB}H3R&7GtklMW56)rAQ4>s!9kiQ<1f)>~f}Cv82jX5;f@r76zp z=wfx~_{MjNF|K(=P&a(9LxsJpCkalrFvodxTR%M()l*MFf7Q0p%O2YY_3k*TNX+k3f;4>jV#+s{i{#K>0iiK|pP zi=C*ld{31Aqsp;l$oauywon{9PVR{UK~i7QNC17d#&(srwx*f2vfDV1b$iNh5Ir(M zFS0DG>RS!Z)m1lM_yzP3+j-dfh<|?GYF^@?2VQTKNdxrr53!0p*r+nAa@ofP`K|Jg z1<%5=8Y-o&R=%aY$YL`6pl2$b!=|)Lnx+_XlEe8&_RQQQAt$@34igLY@2DpuwLMD0 zmVS3}8m#8?l^z2Yw3aob5f5B?GAL3Jb3CA1M}lt6BW-gI$O(smZoX0A5#`F=xSvSP z9z4m1Y%1Ap1FQFFE=mBIT_VcGiMkdS)gz{-BS{lm@~_VWm@Ywvtn4c0FAq`i9crk_ zG=>&>62J`Ni)M);dmwkMlZC+egAJ~`!RpRwv4}lu9V+%je(wE+@ZncOE0vraDH72@ zhkv8^?RJB^i&1WW2t>;(W#)HkJugq0&7V0sfBN+4iQ=kmSB9A3tb>b8`*8{M^CdRm z=8*jt;C7p*X<1+vyPM%{abR#xeV7fqu?(e#arl{z=_d9l#{A2eWztdKUbq-8EA0;1@ThO) zf4D;gDO42#ytA7;F#OxmN$oAz45{UXpse;1;^_${pG;xx;hh#lM) z;xY^st8_!AbYWAfMt}o3qr!4$KMR}Xg|98Qz8u&%wukyI=e=5O+Mq&5bt^XYN}JZ~ z<dG-em1#(%Q(qMoMOAhD)z&zIx7~hKC zJ<{g5MYH6rq}1k_RNlE7oJqU1*JsAW$vfE(pquE{4;>r3r6Cn(yRbj1y?ZQP0I66t z33jbVGmu3#0Sy`W+!geWJbGxJW193Y+!ayYnZTMs;=?@jw^>|n>^nQdoqhIipRM5+;ey=e-TF-})s%ut%h#Agv&`pcE_h7qsNE2`w#cKuflke?d!q zNTnlJ`~!8WJl?|wr?QgpfwG~2!L-C;4IX88Q;~M6UXN{0U*`A#tqcRVov*nE=%O;z zU-r$a7JNe!?w?UV+SEYA`6X~1dOOB+B5j=Lw_u0!n)$hTr{Xuy;z8K-qLfb}t#aa7 z4bq0FzSK}@!*|dpGX=!8&msj$UMpO_Ypo)S*APvQz5Si~ zRYW2Aq1AIs%hXl{N?|{M$;7s6)l{_RvJ4?#ZuZ>i>8sqI)B!_#7m2FR!n0Xs3|e^M zdaAr>8xC$)m@J@d*d}JZjxw65KO4*Lb=*^AFM%?mr%HSQDL$A9*rBp^#Pn2hp`M%N zH_#g@eN6P)J@J9Bt*?BR>|l5On>Pq{LQut%V9)+ZqROtjl4X(HpW4-kkLz zmw~_8!&PW0CXe-&JagtPAe`2~ZF?IG7&3BwAE;#iDYEx~x~Q_p2bpo7s7kf=Hm6Mug{(bkkzU~`=?W~`<~&dNov`H^Uvh(HSpQ(|Fttj z@Z=Rw5IB1K>L0Pr;LQW_9&q~1{*ffd$=qr<1kHW0Nr~mS47`0eqN5sV_X#;z9;TD@Agrv6$%`)gTA!Y(+f?65qL{}MH(#o?=oAW%H*WW zIoPg7JLyydg?UB4T$~&_D;x1q-~4W^NolCI5&&CJxaZrgX(j6{hXxI z6WFs6y4>2=K(%zYl&T^kdXON@@w+b7{O(bBycQ1sQ1?Iu|GpQ_s*8mcd8zZW1C_UI z23Kt=!Ik@w@rSmi3yYlL!Hx}#q_I(vlM$Jj zq7W#Q3MXBQ%M1KWRHEvj&Y3G|rI+RF;R4n(vdoRma5Aez^BOsYY6))@LBIyFdi2fK zF^$5ypZ+ERpp?mAcL3TT2zzf7u_h^9vlJ5Ee5=op`!ukH{e7iqDs>3o>qN z#S@kPH-WK#80KvHVym6L6U7b&X{NuVE{^5wAT{)SgB{yqral!vTk zgkjGJC9!U7U@Y*KX_iS7Aw;O;yRxa3*|{u14bwN0$06TA>XP$@%;|p!#i=F9+0t{f z9J;50_s{&QTZ?VR-G|RNA9W)0hyt+UI1B7*p6=WqAxlC@m1Z?Z{!UAY2Lay>w)Noe zQ6H3!{`sl_fBhGT{%qa70TjqR9Mc81Ee2yGi9j(SCAu*FHV0hbcYN<{wEopUe(-1j zGTz>{ZX%{}qkk$p{|gE+R${#}Hovvy6YyjL@wnjcT#g9)q|j~H;mlYqQHy?yJa*JB zXo$Qh%`aAOL}f_#9a%jTy`HePWs+3sooDnaUD8`JRbU;MWOp#RBR zM}#G?T zxXcB=c|~5uKVJYSWZOnh*!-k}lK`|9lKEE?X&ZsLsyIlk41^ z={Mdt>8=(lXA3Mqqk#qyF2lgF1T(f#fH+iBwUe?J%>oYps^Y|)A zwEI`VXl?G^&^v(Dc2moJQxEIgw?(|Yy+t1vz)_5y|Gta!%*(d!x3(1q_Expg_Y5OT zW~T00fF3UH6#k3ASh9c+YU=o?kRh^)`SJWi5hH)u7q_pX5D&6u2L0RjvF*BowP5$m z%FyV>-r_nD4GoZ*?+B7?{lbU1cBF9m|B471?`}x|E)%%OoAI)&?bMoYvs3%q-qnA3 zvNijim0j?ffR2zS7ZcIIStZp^Yvu)jz($WG?Y9mFLRa9gh5tucHcYJvsGnMH?b4emA z13=pGPNB+#Ir`5VE#5q_QkF=!dW<)D@q&2SL_c@G_0dkB*v#YCXw}an1k*0_hpC*U z##YGk+s1pOcyD?2d@jbe5h@OvJ!5ESsL{7|qDux=Pf6@Npmd?^n$iauLt__r7yazT zth{X4L!15^jNq(Wq+5(Jt$w$;X}tsmh663LQ6}x#kp!a#!MZOy_EPycIQALq)O$k4 zbL6vGzGPf%!JK~I+j4imi~a?bl}ZBGpY2}1Bv^}av9S$PLGiLB%KOs&X|j-6EylQ3 zWvn?Yn1JJO2mYL!z~mk=hR{PfLY` zO4Du`E6Kx$r`t$c@Cj)}RsUB%8Q;ksKc*d%muK!wfpn>sY1=|oTM7O*khS3nk8}W_ zlI(qMLx*}Nj7=wA7r{?-_j|W4llCja`w9gSGKDnW*$aEdN6){d3^9|CJc1lEd51zK zSk-4}OFkKyjfj@+)MXnu(HwdR^y%}cm+b*{Id`)CM}xa+AaDY%kKsX|XyoY=e&Yx6 z`_@o@ukNyEu$Hs>f*ydPj+Hr&uNL?wcS`SjQ>3JCeoZ0vF3cm4Wm$T@lK)-3U&tO~ zckH<3l%!L2zAcBkKYhk^DEn50?K?RWpNV64I48dOe7EnrZ~19sG^reB*TChFCQmA@ z&j}SH2>T^wPM$f_K~T2Pv|aqnl_6aJ@yJsB+C*VzNj(dr6rQ4jKSU0r1ycGe9BIyF z0{wps*7Cgn#;BL7k`tQVTS%!P+u~{i*HNxR$*aFQTpxXKdqYaw!P3ldb$W9H{hq5h zym;WQ%IJmITTmhP6c-fHmp@pF7!ww15afK=Bw3LT7=~Guo}^6KHdR?6%l;##RuMhZ z33Gkq{*vQ!jcGBo``f6y`kSmv5Wk)ZHA1WK&fy(P_$ioVMzRK->dM-9 zisCG#Qyd&=<<6h!pOi5tHHOogVt8zc$mFdjP3} z6IgkP^O>1Z!wY|UGdG@V@Qlz6zF7FebZyg|lg*2t8gmVS%}|>pwWEXBjqf;3VJ;Om z;IHbQrjFn7r*U`jL=R+n+VhS$)T+Ia+9wRGWUsSxkFJM?~3& zz)qoBC8&VfQRSZeCa7?QiltLH{n(Wr>iSYRk<5ePxy$@$>$ja|fntBor%4Fpz;%+% zB-7fWwo+E&I0m0lAbGqL*V&n5yrqbB`S0sTPy?MH zcp;?vO5~grQ8>WKn%(aTZ4q?`9E(`#?2lBKJ@k?2-&5;AeqAu$^qvNLMq)1opC)&K zP0}&h2jL65d?;0{p_yX-eOz6HfGez*)fdsuFl(EMNEJ#mRLc^5-_FKhimpQW0ZrrX zKV)5J8h1=1x`RLM;4tjjtbz?fi#eB1oZ><0+{a2#gxM+Y9R4z?U=L?6W#O&!iIv@? z5g)EMwZ=Ku23@_vGp;4hIaFx9La2_##T8bG7Vek4JbO9MGY|7hC_jQf&E-(5&Liyo z&-785qms+)9+GlsH@mWBbUMA-#1fiqu$1;j^=u(coD*v>XA#*`E@AOh$6)4R&-6IU zU+0&=Ad)L9fxZjblwVr-ak}xrZlsR?tGmg;gurEt}j1&(tAc{rY(WEh7V{I__k<|d91H=Mrihe1;*7}c|yLh zwOpE>QQiEZ9($!?=A_iQGZo)ABAmg65a<;fqU0QwHRm-RV|p*V?t<^!fY(I4Q)YO; zm@s60mC;sj#NTAPerj-Oo`gG8`bID8p2vFXqcd#@_(esFN2A&C}4TyY=7hnm<obsBSS8i=W(9Ie~=Yp7YqeiubeO4#!8{ zW;O<1zJh}h6{fKr1On3gvwEbzS3R@Ty+eW;V&?H|yw3b;v#G0Zp2`I8oKN`IR{pg2 zO^mziOuE}}5uNK#CucyB`-IXp@cfR9o!}TI+A4AhN1iByHPgCr%ji1x%{QMu$_-_^`R zrMjCQFSFOS9U~9I7e7Ct>Lm2Bx-QQg$G&_|YbTp+96>i4EuKU^Yz#QFC&jCJ z*LKOkuM+&%;~_+zT1DwnT=X`s<}41rZy$pvI^I`*42Pllh<*MMIuf)e?fidwjR;l> zRn6eM*lS5QSQwX(ba-)Js zp6?@qW5usN3i&+(WV;d_j^M0aO~$I@T%9cy0TPBu%JP!%0La?8jc3xdKgO8r+uQMt zX{N~jiupL|Zb+P~oqV#l6Yfh)Fj5=u3IO!mWAViNY6UfhkOCvd7A~8=#_-rz<)!Xz zjAchsjv=ZfF97`+s$uo}0Nnv=5f zI7F}++Apa-_p2H&_Jje>0L#~tuk7WaTN;rtt!S^ll7J6wOL%;vSJ+~zxDjtA?0k3yoGP%HC^{kYi?eu?^=pJucNdHlIg?7N-b3+1wp0rlxi zrS&Rj$^FrELavhGP_Fp#iuw7cxFv|c6V2N0@7YS;@pN9Vmp=X2Q7N-`Jscc=K2laj z8AzDsH%uRjqPh0nUM>Lg-hMYb)8#zon&q5~pce;lGD-sHJ+GRCJoYLK6wQZEVgt_f z$Ve34&fye5-aDtRCiGqq42?Ci?YgpnPp{qb^>Lw*&a5mC1gn24J<6!DexKAt_|b_g z^MdM&5#i|)1s_id<#=4CGR5SwhF0cvjMKZJHb171Uv$;~U&OsHYpr#ynV9h9LLna})?EiK&HP$r5PnA>b4%junM*S{@d+q&3}}=Kk^=|^^jVe zX6)I_AE-_%U1OBWWpP3>!WbNIgE&0@NPmD8o>`*{+0lw-B`NemTvl?UeZj_!IDY zv`YUM!oJ^ovrR&V7O~swFMU2Te+R*`C*GSp->sJ}YkTP-h3}HxHGxZ7or@3JV-o$6 zY3W+;`rJ&br5KH!_-jkUdEG75+aa+ToRhwO-tGQgE|-i#U^-d%Sf0m`BYLbun2lqW z6x+8)a$+57iu3MalI`6M73m#?U>NnTRabwC?sppELypAwt8oqD5aX1Cmbl=;ns53F zdiz@2^8b?8^ZhlH-p;pv5{&bo!LX#=vKKpox2jemSZ9SE^%VJ74ewMadV#ayvdOfj zQbQH8(^5`pI<R#>Xy7cA8e{+C3hkJqKc)g&!2oViNPn~Eoct676oTOX&E3`t zJgr%tXRX!kz^}bf@)~xnKZz-`bbN+3m!w@f?rqDPYirDP?m`1tC*31zr21P^@9IG^ zwaO-k$_%e!jt&yYqn1}+Y6yRAO)p}FAG4l(5NwgtCuWp1PyOMpP2QSIoE>=AJKpbe zh6uV2>YVH*#$OKKK~dH}6*;5v)V>LOUD7VEPB=8s6sR(m+2tAFw&M)rG<5vdzMjr# zdK~+e-kvDQ}+~}_^Fj; zlZl)7BQD8n_U%HihHz)_mdYA5;Th{0$WEdV4^@^`&+Yd)r>Ts!iDg|IHgeHYoLO<~ zo?ARz??n^-2Tpd*NXt;{rLYEgqwo`=N+1D7`=YUyJQb?LNR9S-9<$hc9Ns_)H3OB@ zhj+qEBSrI}^VYq~&9sqZcx5hpg4JZ7nPYJ!u9Dh2T2@k0vMfvr&zXaj!QJ5WyNJ8J z)u+;O-jM^MHjuI#x_#@E0kUr(95|oAt3jGsI}A|(-pZ~ zK0BN4+H_l5D^H#_Vpo=C@kr5ZHawea&w>f#CW_wUOVi1<&@;)r%dIHWsqf>BP=g<* z{!NIp&HHK_y(K#wE`N@j=Lr7MQVy=d0gFk&ey{n*eLB=4D}&{o25CKbLG%MX_p3#B zq@l8GXXDRw=I-Qd6?D02B2cY=b(|YsmiKeGv$2U4;~adH^r+K_pX>h5PlR_kKtXP2 zGxHD_?x`BD@R1;1sc~N!vu{5ZMug;&&}Jn8QVs1S1Uw6@+ooMAqy%SBKg`M|D}0_C z`%Lq&X3TbFSLtMW*pEgEA05FL+QBhwchXjC;&{2(;wQ#%Ez=ET4q*j%Hv5r`{vsBWKBo#Mfx_H2xo>-4 zuw~GSVC=+Pe^T{d35E(xIs8_R&rNhF=Wmn#tLz2CCaJ=E-h@muj+1bntQI=d`SVd4 zoJoUk1sb}Df#K=x@2|DHS~LzZLHXxuSPqo^g}j93ALna5ZYqL%Ti_hDldv=&z5iVm zum*DrM#FD|?49fDURB0?fY2`?|M68?;pOTu>Jo$kRr}BQ3i6p+`$CF$N-9*L!S3^f z)KaTSohszs=Tf7N<%F2u2itu9<OJeup}?+S5!(=(Cy2fpWx=PKN2JwG}#B^=dT=btnLY}o!O`a#f+k)Ib{ zyXF--e2ubXul)!8IQ*fSCjzVmuKmLf*))^*FD0&S=?887-f~lhx2EykfL{HKadTCU z)yqY_zLbrMu8?!MHt}vKLoz{V)TOgiYvKP|nyHX=T#FH}h@-0GjoY;>Dr+(?iyEr( zv+#HqdL&LG%S&1Bn>eJB~Pfs!qAGdF| z_Xg!~tZ^cCCC?}8F{)o($7A}e{~K;fvA3k@51ww;AXwa5OJWvhjN^k}J|`HtOl+8> zUY%ZwUYQuxQIhgJX2Wo{n{Eh%OMc0=B#`^g@HP2lulZHdVjR;(3oSGJ8B92B1pXAN zg+eb|b?G0D)Ap$>RxsaNgWRm=E_gH*#P^UY2fMbHU|GdjF&HL-*0rP;0Sa}AQYfLT zA$O!!pE_+H5Tmc1y!Mvw**81Lq($P$W9}Z~#ee3loI$+CueUl9 z`JS|HiFSxLS$YQgM4X6hteo_{j8p^JAQ?53Yy3{w>-$Te zGZw~qFsLN#Y060RV)XaLdtZEsifhF-7o#4uaDM@DLla*`n3dQLC0>KjDnH3C^d!e$ zDdOL(P98&c;C+@#OWXo{zc;8Ggy>GRo%1__HABp@XlSIi^jq>P*Y7Fn*1b>EM<4yB z3nzsiyVw_BqJTrSntIk_Y)hW!C6YdhVcSZ?hve>xZ<*J$#=v8=1o(oEE-BH&3x8XF zJSlj4!~5~L zvTvC>5^|C1Zq#MM%;9$04^xv@gYCa90=LD(b0?e1&p2WpNK{y2SHw{=_w!Ci)+5Ka zBZ~YOTXyn^c{Mu8@?Fl`)A~(o&sU_Pu=XNd7CAB7tJEo_Zok$Sl36pGMzmVQ>wVdS zzZqVQe?7k|-MiY^4?$aPXZO|SE~+`FZ~H_36k$f67q`{R?fZ9dclMgsF)dB<8>+u4 zogg%HY|i|%$)0KOX2P~PzkFiBNXtm%;;@mA`w_g`a{@#_%zI=%Q>+P+M>;CZ*pE8X zMzOHY)>}SMSKm1vE9)-({yv}StjZoA?b9-O{(!vbOgt3F*S1S%FAo9M83yZ^VV~#N z1)+mNe%rZB5xC#76p<`2t+A*hT(g=bH*mmUI(X6Qx_-78>h4t?z5vtJMvt7_$LN~r z*453IO1n!%!$YP5h*~IHL$S@sp4^dVs3n0FpVWIoRYz@tE?W>zTzv8!C3z>S+v|bM zoVmK(Ie6ZToq6>>9q7aL(}z+D&SIbA{MQ#AEk! zgL%CpEYTkkj7YQGY|oV#WT|Qu@>BOc8vlWfxIC`I!QPHhD|S|jMyffMAAKc*X9>ez z1{5Ej%PguU%Lcbp6@yAL2wIz~yKG}`+o8MUYoxbi9=^zV(&pIwb_^DO`I#b749;q{ zY%sI&J=Ns#isj8L$02NOGAZRcehICXdPaZVT^etxf?#feHI=yOCoutt(o>=kkaIZT z7lh+(OI>D@iCN=r?M*3;;b@=MU|QK9N5*TIUFyNkt)}{I_Xr|y`csEUA4tCRl5DlI zBQw+GDCBl*V=>2H=0eWMUJkbD^D2RxtBNSKm}c(f^QPN%!nh+j4d#g)Z_O3*s}?a8 zd?_al?XL)oSfs-F zQsLAov_E;+AZuMTF#P?gF2nFJO6xXHT~ukY&&`zZQzQHf`Jr`L9Bt-?GoEUp6xPFL z#{%E+qt92ubtHxamcqP4&__v6ZJd>-m7AI`I1{Ihnw&s8J`NQCG}6A3&dbj&nw@jp z{I1fO5uATLCkvC0K6fVk(I1&6&nJS?$KDkrPs`pbs>w?eywu{Hb#&~iElcGlY|cSj zb{s9#n(WEl;v7bGT}5Q_N|ww}j!qZzlCnH~V@ zMeOhA4*IIud3qo9T`&4?325UUl>gJiE#6&JulD0`D-y-ax2u6w&RiOGab8a0Bo*|= zhb1UbMWi}H+?tEj&;2nm$+v_ZYE7=nA)x=T_|SSXe0{`+R$*Jv`bKJ2ylD%4tn!AP zWuOMnnO1Lb8xCdaaUAKtU#pGY#NJw}^e!3l;$NYEM)~Bb-G*XWA@2+6!fhM&%$%o}R7Miy0r~i^2QN35}u!lWdwTZ}Ok%2uI)L3S_UCx7K#j z@at&MA(1^-uAULhV|i*1?|&sWAo*mp>{|Nfa_I1lDlYV-imSDW(DK#TnIL)pW_p3A zaZ8#0RWF5PLGThwbGmur8@UR+^gzY?v`wFbNt>JahfAU8+u71FN7GO=U*Q0^n}_oz z%nidBITS^~oN1Q}M{xJDrE4^f@SSSL)cV|3sp@X03+H@AS3XA{k!<4J!!W*crc2W~ zzP%Onx$2+{4GUZ8ureeD@_TjXmh9ZcFW1Yk33;C^SQpjIJ-+(XMIk!`R-3xdmKHoe zkGRytO*(_%qF>JVnPG`DS<$LFX;{+8{z9vL{E`~B zL5q8it@u=G#t(Dox3|iwgrKfsq^@F!oc>#roTWKg9A8-LPk`GQv-kMd{Lshg9Ah@c zCX>;)+XV9tiDDFi%LXn0x$)b7fw!YG}?%k`OjE4L>%KPHHpx zy)AD=M?4(cy`e(e{Zi=${RrKVF@{CoH8G*7JSliny5*9gY#Lvi%p^(;8OMVhm(7d{ zu$0f5O<2b5>DCPnQ4{L3VMjU-D;2i&<*|stc(p`06}rsTeu_KDUBSf?qLW`=j1rJVO~_+HM@C@xwqoP_k*M zM6c)g6^xh9y7_JX`C?&z($O{&;}Kly$FVMaM6%+?NZ&ADiP$xInae~4xP#hV%1U{Z zYWz$?R4Uf@-iAKcOjh&8J@!s#X~rfUIhjSEj_RMOy+1QDq=+DNcdxBVP-D*Z;7<|= z2iFZQ`EErnWlu(~%}waEZ1Jy1VTx~d6OB!>0Fqxl>M@i(py*)|w_4EHA7;Y97%0qB zBem$T)is~}?{9<3d*oynoK=f^Dm>YRfjo7bB0y@Xm16(b{_ytGaFX_X%Vs28n1&f9I% z{7o3yNlWLK4f>2~*?8RE0|SPab2vYJTItvaPfhT6zXE|??r^XoSE_AWnT+EvK3OXx zEkU}2^YkladP}(hQI&C$hQkUKX2~O^0d1F&F&c+f3t`AOw_YKN=!=jStAN!M{LDGr z+a>oh{JT+jb9vI4MQb|y{;#5HVZ+m!vsHg$B3g2eOjC|iHzSB2gD;FxAMA}-_2c0A zqRx*HES@YR6kNPwP%Tf1a9rl|d=`MfwCBXLFWsp0$#1igcKs;loEt_q*)~4vuO0D0 zU_JS6QZVY7%asNvmC1l8Q+#n-{BddlY@U>GNMsX~C0&oFJOpc@^osZWeX7GDzOzlu zuIS`4zD={=Y|7#BVRr7bDOqb({lWPO;Ogs$rvc>=8v^T0ZQ@?z8M^C%RuX>xB3W>M zbi~GFGf&YG`u(f}ZVxU8a`2pj__op2-+P~v;+9H14jQ#v&AsDj*+TTGCeTAgy?Hr&&tKO`%$yP623<^*gd4xSc2)ooqDWh&X z8Cu;p9XLdlk*;mYYBwu=e1ehkdP9ZGX_#RfG@^(-Bb#5nUH>&}``Ky6^ZiI-h>^sO<0qr(&xK z_~i2$;~JLZs!7NR-#@7)u$A(>le?>@J)4~~kH%vubdRcr9MO+p3JtZ>x4R1QFNfWa z1lWjF;B$}mfFf=KeVlG0O&v|*Xq!tOvec_$wi}nl8v3@*-BO_lH)n6syVozD7-YGdoADyi4;K0fuNdj?w!l;wYkvn43%si9`-^cUT?6WV6zwL zX9l~~KA^&LrF}CLN`SJUUq{Iq6%@Q#FZ->tsEq8$NFM8mw-RUgfmDtjK?W^8r zo=i>C0vh6bKT)-JH)!3?@zT6qnd9bU8=73CVq^o)%>PX5L)VJ4vOc83mGpBMvXNYGCMx zjZ)`607cTUy| zEBdGBaV@2i%n>_HaVOH|(rBDq+O1WygQH6lsQY~G&4?O8c&WV->~@Oq^qZKc1#8uX zV(*`uN!;KkAF*bOt#u5*dD?V z?}nv$YDY$MY&;J;Vvg~MUMmkVE8O-LhhgPegs@ZmsH#iItz_(XRo|RAh{G+M5y&fj zbWUI{c`S&p$^%#a`|9y9s$~c4X zuxEbGD(Xz_Z)jTa6Ot#R$@bLHQF2pNe)t-O)h)H%4^%NX1<2fY+u-2&Xxn+%q4T@e z&WEB683weewz9Q-2lJQT$O*TXiiLDf*Ebb4E1ZR;$}K02kn zy2t;v=$R1MN*G3-?>TL{bw%Hj;-k)X{)K4ZneM|g(;D=V*Z@Bx*ox2LdaLect8Rwc zAG_MLOe^zEuN|AxDf$$7F5RkFVJL<0Py!i=5$vvyw2rSWYxQ%Uy1Lgzy}fIs%iK^^ z;`9?j1NiJ^bFZZBO#)6zCd902oUjJcJJmpk=6#&%90pQ&L33vBnJ3jFC2Ok!n$7uV zgR=)0r{G6hPfU_h&sORR4g+J;JxkQIcR*eJF46%$^*% zCLaP$zbQ!Msd30r+##F*X~iZ#9)-IdvJyfwXXf9ja%V?9%)S`un=?0aaU+SjirGVS z46mi;`X7_&h-Bmxcjwnk7n+}FwzL!M*XBAe32yG^C0YBNFH48JG{8&!b_6?=&;o(~ zpw8iD)wum9!duF(EJ4oEM9*?rnN_!vxip%wmi(M38_hQ|>vJNFfmIUGLj3sV!{J`7 z;a_qg$Dr_hR_*5ps$-w6>DXDrlJyTXGWRWD;q&#GAqv&^VYa1G_^&7VYj8DSe^uyE z9^AiMZZ-mw81uAz{*j{JHf%X~&8oTLd#(~W8FAip?+CadyYobKB$||aQKfqNclg1B z>;!mMS3Esb3MKoDBd}$4}Ddq*+Sv8ra~P2_i%DrJA4$!0oQKCfIpPZLIZSNg{BBh{^nYba-_ zV>XnkQ6=}RFn9iuGOndfF^sW{L1G!0@@##_s;lNOv|(^%*V^i_#lrWV$Y=8Ya(alT zVYL~B-P!xXo^1<;epZ&6<;PNG*w}Ooe`#tB;A?4#lJD;5>f%X%ypB7S=H+6UuHb?y zocdq%w9fdI{h8W;cQD1Q6fZ!W2BOFX!aZH76CRds$qHz7t6SAT&^p+b+^HWFFqo`B zIoYtp@>?*q$oVezF)U2=H_nZg8$85&I3W>NyH<4jRS)DA$E61n1N%FJQ_D^e-j$BW zyDbO@c&(o*$dWz#SymI<3*9EgInh-88lqXsF?%avKHnn~ z`K(^+3`^1kJhK##DsQZQQOu`7i`7Tf-g`eFKG%B(7$0-Sl020m#JNtHm?}Q*AmO}A zJ#f%Di7+37++DePNb-Rgf4JLKxjw!Gq&xM)v#ZX~qCKgKq_3uq()G=DcocOju5-J_ z{pJ!DT>M$o1xKk`W#6?n*2`%B3I=W==-?9h>|?l=?b5o$`|!ugsLiX?@l|`m62lw$YVrCMKN$rPY82xZ1%J<=RT>mlBmXr6oB`XHIke6n0V{56lLZ+Ig zhj^F9-sJM0)y2rEW;QRYKu$X7R8r(5!Qj}!f01)N;8oVf~NYsr|R5Y&&ldFO|P7q zn~3kV1VCN#D}WKC^;6@#I6z~qpd0shG~RdIwDI2N!vemH?VPgs{oI?7BxyZK+dIJl z2Y5g$eHE9-imn99JWbXH`81$sY0G%y1V~l#_l`tV21qzB3suk)*~w!+7y!$;u{1<+ z69Kh z)hYKsGE1L(;-?M)+C_!zeCj8tTwnAQdwprxu_O2y=@qx%8A(*x8Q5;N$3h7hrk9t! zm1Sbd8RH+P^KRJ}rf1~l=1RyoT3g?ee9c~t!(B#RlsR~$h&wy0vb4Yidc0`OU%bvg zo!~8tGVQwLL2LtO2u-WK`8tlBw(Bt4<7aYUba~mEStfbuRm^X5_J^Hj=ps%*I@u94 zN9QqH6>_L{!ET*kDWC4QKt?&~GKm2KF}CnqfduL#MS1~ZtBgNFHZu{It+;f8Zz-;p zxwha+N)wN@)w9FmyZ9g|{YJwhu*dPYJ%aCV8Iyys8Lw~oXdz@ipVdwOP)lE))3vp= zMf?VCp5^^Of+72+qs(;Vqu!G~M?7LddS`YY1hs)ohGkJYY@2+)pUzr-BEg?W7_9a1 z0h(b}6z_J%J{pK1{A0Ns?HNLk%oWWh-&(WE9kUF$IsX?8%R38Vmg-<9AxmcO8z zL?bPvur^Hlub*bk-B6HZd{yewOxj8^y+&dcV$-er(+Rq}+3I>+8==6#0QX~YoYS~g zvl`=;2ATq{Z#DLtyuO9$f7h2~N~T-ScD#ki?Q=dm*EtWeQSd3SU2cT5GvJYY%Dj4A zE1Jcv^A`(@`NtDiclHR+PahZ`sFgW*8|-7|0rev6Y(1YL75dI5BLO|qo$a9Je>^Ps z^?%;=+@-jubAbTj1UFH{9^jy&BsM-&`%5xTx9AgtxOD)}0_fI+d~KVb_<%b1#U4-5-j% zCt|q?sE35;8j{CHi z_9>7SIO|$fvq{Dz#A(|c#EX+2lB2wDf^noOvAly4G4z;=lS9(ylHeG3-dEZ{M+&>> zlV2rz4oQj9-8ait=Kv59=x`&y^K1c{PoN~)YSmbOW3xcwPhndErFkpthc~e5d74X# zgt`KJMK5Dd_@3x(*ha@1#{ruA6qNg3XKgVs1$u;?wcE+A@ug~!6L)@@Orq;Q=fmg2&*@%#1Sk>(wFhl%PfnsUt7_&aw-YRD#r#iED)F8MOf0}@E;B-g zMq%`KwUa|Bit&1j`+c)JOr1RSCLe)dyl>VS`@^c8FprXoo&Q;r-;K>XQX9fw7c8A)D+7 zdFqJk3oOdp_1V%|SZpAV`$c%%kJ)%kUspff3nG^|>!3W1DU zy|Ic#eI6;KN_N{5IHVf-xyGtfC`Jl5xyFDJ?BU<|>b>_a2Tf1Ti4cQpE?)5vd|ih3 z7HQa8B!1h)=Fays;@8P56dd`8-0XSkWnamDV`l6jcG+Ye;`2>#Qco6AmNq%4VZ5t^ zI}T}n8Vi2QJgzX){ap|h?T3@QlV8O_25Y&l5XcOESNn1zxayE` z247BjsyMkGm961v9QB42PwvJI?ZccwWlERZV%k@^=W?d9H203G4=?b1kFAVg{eAX( zB%u}FkB3xIE0MY5sZE#EVwWIXhJ2P?&(#8E?!^-yTNw!MiqNkWZ064jW$7nmjtH^G zQbcLtdY38PSkRw0$UEhB+q~vc7KNiB1BieYc{+lvEGCC8Iki)eiYx1&r>C>jf%FVM zdp3AcWH)()h&CZFW`pxNnHE{WZrGXjR=^|c0Ys$j#KpF(r?S(w@Ys1Bj2?WR=6V4~ z#te<4T*C%rWusN z4?V!_odcss6VjV^$?N|sI&!JpwfO&{tUhZ$5zqahh~<3x!&i3Uc<%aK>VJ^^+Se9f zfb3VspIL7`R&*A0gJ^2T4J><~VPk8#N^O%S6h~F;Xk;A;N|r7*Z>wfJx~zla-=NaJ zv-E6iK5bk7uZ!Zf-Nzq$AD(zG4dni)itM}~_rNczK{(2Z3bZt;q_m{u;lvjWk00tw z?vK5PdsmH6Eozl>K0jmX@K(f&Rj?6o=8M5{J=|yd7@kv&}tJ z>Gtpf&ENk5W8*Zkv#k)xa7NLaq(Now)J%84s142^7ESU2X|b2FP|i zVQ1ct%p83{gUZJWpnS4`z7goC0RN7l?*$WdGj3@?o%TCQX~>n*%5`V_m|-vK==*^Y zk*dO3WI{Xbwg z(p{}wE2C~J3(V6Fn)Z^MedE>Gt`^Xvp5{fKwQy_c`qxx^wuM`>5ysY}>n?>`ev&!X zZQre}ncGkMi>!4EoWW~~t?v{X{j(;90tI};I~nUciksh_>f%I#)33iiZ8_sm#eBA$e<*oN$U_oTZt?DInG4Go zyW4{AK)Ku2x@BHl@Rne?({0|mSl{#uo#<%mc7)N}hqo=p8i1S~Y6Yt*F{YGo12zA| z##DB9{^HDzqWu&0AI%ZxZe#BOxMkr}H!E))rY)ga4ToFX5-`Le;=u*2R>@l~T!=G5 z&I6kL;l<~4`A_He^rFiCrm9l>`N7Z$EVB{6vIx^~}FZupyM8%WfZ zD1IYce95TPA=vtpf7L-CD*YA3(MIoPk3c`2T9&;ys15qhUHe^D2%6=f57FKN*_ z<*rNi)?MwxX>sVm4DYxzgMwKIX!lutN@n7z8?vpRv?(&TKWTkO7yHm^GfcM^`4&P6 z+UckL$yw{9XwoZzOO9Q?nq_&|+AtvD zez#<2;}+jg1`EStEWjL?S5dV!qz(1nw)qsY^!l-VY;})Q(r6&V?R$1F>khT$U?!%2 zt0=8d3^ahRsq1OFTm^3@?ah|#%aWrb zWUUEDo1q-b@mggfT9|qfE2T8UoOMnjm*Jth<^X_Xz(FFB#7#d75UXhyp91w0+j2bU z9|{72m@PaGk}Ceb6c9Yke3(G%87W`g64;97W)V*Q+Bf{*6m+CiC^ndltB=6r9onS-1_4qYhKb>xyTY1UN)%EX* zB#lFVoLp=dHjUXQ$_A)?WvT}#OxgBk08lf9!N!l?w44gqZQX2HlLKP;_EsJZM>ro%4& zein^=GIl7>Gzn_4awQqFUb*=TU%3|b5xMq5yYIAArE(Iw(j1S*r388BW6$TB7#?rA z#5Xs?;PXGBbZXD&C?gBO$`Ak8>J@HrfHL-182JcUZP&-s#SLVr+(Ck(_j0Q3|L-xz zOf;+qXnI)cL$4J5dXS4>Qv05InH_JG^Id5vvYJYZ0+ay)Z&`oRWfQXTd9s^qyG#NydSR0D+%V0k7TIiCvoyg+{Dv2>}exYXFYbo zB>R+ps{|iLKKCkD9gB@D~+&ORe%T#Zvo6-ZH|-EYQgR zy82SX@@Qc{5OZ3CzU5`l;2m(^EE6btT<)6aQOem*<^E4b3(Mq`fqqOH~ukVbOF3=F9^sPUCjr|uZ%CGyu z7!A>@=TYPp+3fb)o-g4Y;Nei?r!9Qqt@3{~J32Ze=UA?Q$v(MzM8f%0bYTH>LJJLZOf)ViPMYkAb?`EfbG%^1v|->Xw933AuH0m$Dvs_I&-bb4-(b= zo$R)38b~}Zh!dpiYfq3{R>N1$mMCGKiJb$Og$f86I;V5XnPU>J62w7c;76ad?PM++d{oJCTI_D5Lxt_x z-IV#EDm$5_EMCbtba`)_LPMW^*H0l={&HIT7D_poV>z|?QPiU)in&qwy6r)hr>Tx$ zy@Rsc9iDBy(irR%QZa|LI9XqTfH^uVqcLIjQWb|I*_0*9m%5ho?TYApd}>V!&n@tq zgUp<-s_{=feWSb8C4UOu3oM$~3B!MY*ui#lFUT(*E9wwm(ThGgDMhLFimGw(eB!OA z33(9tKVCIEe#Or1oAMatURWIi^y=1_Qt_%m*WZ)==%Eeb%jpht80S2f0l?1z`mc#F$Fx@~%Y4LZ-cozDHmo36cZ{mj7&WmeaP1FVxddQ#~t^#dr4 z^5vVF-}BcS*ja&2v!wf)#zJrRF+Ko#B_7dZMS7a zG&6Gq%tlp1O@4bGc3xB&`(x(~TRR$45a?fQ0U*|@?oN%I*VKSq&3W6~H2}}B&+!jq zV=Q0%&A~$WYGKXl-E1WBoiXu}-36WxLf)_vc7_M2(zaHY=(_lg}gM)OIw zk;-hZ^O{_JEFAC@0`n^qxBq*bmcn0-ODr0o1g##wtFqeA7b9?p&_}r(UH* zGk`e!66SzXp}#XT46XQzlgB3XTh)BT2~-Vd7c?>;q6N^i8y#NO!~p5;Hcc)cgSc{h zSV4hfyC3aHU1Ew=zJO#?u)u-Xqmpf6H>*KNDBLp3!Fss+qNn7<{az~$yUnT=H}Bg8 zWAtbgcpGv20Aci^**O6^5A_kZeIfnH_sGb|qVKsU4XO_{s%vg{b=40e6K&kC!01PE zmQ|g*8oG#hSc1Fv27l`#%csQo<27Y$qEE;Z6mQ65>Txf$I1hx*?}tBW73Gh_sA-6Q zOHuo5Fy2@ZV`Xntv>9R1RPfsp%GYwZ^y2LI0LN@PSQX#ny;JW7*YWUboHKO20>inj z=jYv+i}#-_S*wt%LR15~KJBR2{aN)`ipbFIn^Xw85RgLIYn%|G*A&*T>WA2e+J`fw zyRO8)JH&T6s(Y@BXzI*V73^Ll!*J)u$uPc4lSc5jYHNX#mMa+UTl0)T$o+N{lL z`Y{!EM)v>}h(qOz_iv=s*kEb;ddrx-&6S3NudR*=Cwn+^0WB>I zvASB@nXy<{NWUM?N|Ad4eZ50-{u32$qh!@kg74Z)tYhV8ktgJoK_vK=Yy*YqIY+$` zoIm?er2DkE{iTG?FTE+U+L-oP?nyKrP9*wImkbOQoamgO&)XSok0}3Z9uB=pIX7*G z>^c^u?M7Z)vWtrwY>bhF|67#-2+bIo%u$NY=hGp26`I6~i?GGR6V4i%Gse#T#8neh z5tYGrjuYokU{82{)>}&!+%1hNJI86y{s*0}dw49?)1kLPou*^o8?@bL;cNVXyV%&G zF+sXvVIy)Qo8r--%aw)3|F-lHxxCD5_6osF#qL>?XXO)k5D?xkWH8*%CXVz)^$??~NXs_lA_w5uZJQeJ%h{|GuUC|TVB{6Qc5Plh$=XW?aDrT1Ia}|0I38n86Z*)5>^6Sys=k8809*#r-P)$hXm$F| zlprsD4vtlhA(j)SiZaKk!|75=4C_Nc;B_D0o;)jg9hqbWiw*8MJ7zaWp1 z#^2jPFR+|J_cg|3P8{zDz0*H-}%#CVr8ulPd;%|Ur88{&n zcFT*?eIyQ8`aY1yA>t_tpesEK5gCoX=k(Lypfx{EzV(wOBXx)Q1so_hDG_*ieO#L; z*+C|9E7H9BO?21BBFFndvAB8{^j80SJFl1UVI!I-=HJ?HNB$G>8y@?}YH}h0Ka=l; zVKe!%w$`WNMhSM`K5@*y!QQk?iDC7%peA6Kkyk5wLF;7Vq+R~7vA5wCrIz9=B4Mtha^yYOun+YBP>5w$&nF<~e$g33rRt`HXYo#fi`8Plvk=rIN6UE@lArmG% zwzkn2uMiEOf)h+{i?}uJrbYu({w@gK*l{KJH&A28@!`}^{?=j!J|+?~C5a2pn)H=Yd`V9g@HGJlqCY>f zx>$CHpk6$TY|#^E$qS}L_dov>bKm7*DaF=HcAO$gd>8$^9CFm^W@r4@U>yvpgBB) zj_J3Yq|5HW5Wg7N!*$}^@blG73(Jhd;slB`M#E(CE%V0@kF?jOez&|E{S`gx*8298 z;8T^4OwnkuwI@3<&C7mMwAZW>k{!S?<<6!z~HU8?=&h<`OnE_IybO@#fJK1M6t z`8!+06)UcuZ?m;2lEzLn!@l5L_-P@hAIzVrIosnFur!>wNN(<_WPfR=eiLq07Q?9%gT|W z@3`#(RnA4PMZZjot?(i$^J0IEw4bxd!?d&P!f&*9$?S(7E!(-P|C*1@PfQxQ|UAB?y|J|{Ee(V?U4E^xmpY}vQ4)@xxa9VC*P7~JN`ty^^w73$z z?`|#ipr`$s9DKXnwJ96jmogeg&R4~_8)GV1p}0GS?N4MpNU4R+_V)JH;0;Cw3Hjvh zJ*64BmPu{Fe^m^BC~|W_yy#=LCcHQW^K?S&P4%E~*S;G5OS+IqthwRhV)H4=@2{l3 zJR7C6o#WKz+c^j1?jfoRE${d|(T=+_ZRqB+parZ>%Hu-@5Kl`2%Re`y931Cul^khGSJr-k2*#2Jjg6ky>(Mk zca4ONmj0;K96Rt}e)5OwqR(O?=;-b=hFgSsFKo3N3UAm9rIgp8sdtnSY9Wl;d&uM}!IA8Gc;|;I9Dud0Q~}-oNd{6wp4n zBXIiX5AepdnzB#WzSHe~-A`}1*MYS2GeH+YHk^>J44CM3filxQK0mF%mNC{YgVetC z>h$l$LJ$GLudQxX^Y1Obz9ib_`n-fse~eAPAMay#NkYa_9_Z)(pEzsvj$^Xdwj)0A z*g{W4Si$Y7XpPzndFN4(p@AZBbK5!RpHP0N{Um7bn7-lhU15K2Jq+8V2;=AEl5d)8-q1QDM|?#+iCBb5i@Zy*?E~pT_TX_ zupRyaI_tG-y|=&G6PAYYFOYj?mc9gq^{qw^qm`iojP2&%JCFq=4>HI+4K3pfkRzw? zPJBECG+lLjKNoL0E^V~7?ZJFZYmZ517xq2}J8lR(O$#krXPlaS2Q(D5Q$t;N(-NCD z!oSC_{7#1s%y#ZLq!{BH-5i>n1ex1w@6wI+eRE!(U5R2(7O=o3ZzygPSZ7K?+9v}B zgtJ0VVlDHkbVIAlU+u~}{V_t=^U6Bcj*HMB8_E*qVQpHYJ-Lc_FfUI*TSELs$V$XQ z%OiSwyR`U5){x?-(z7KeOU{<8%o^52N8k;bAI-$8hZ0)7vX8Duy28xMyc5mK&J=Gb z5igq9>Bbk1BzpQbmqf?j<0m4H;0%nPDaJIriVVrd54T*S?%V-0OAwpQ3e{uSId9E#JGK9ow)hy1d{*Oh%nrKf#~kVISLZW&BS|07y`{yCSU2;=H~YWT z9yC>^juf=^fp%w&EnrEP0kvD(C4$O1j@-0oe#*mxWvfOeF zV;z?wulRYr+i^l2mhxLE@eOf?3=Fju6d^9Q(D&VW^i}uVY5rzf7}>K6<9@(hqmWzd zsEBLS+9h~N5s7lI{Temd4q%;jl{lr}{C#=p@bQ1THZZiowH;F2-Bj4EEMBSMx)&D) zGB=FG?xlp)`~EK0CBW)no3HTDg&5<&RGt&V`?1nh->Mf(YyGt^`P$tN>^$E+kf~ef zl``lk$?|Dk8FfAH81+Va+3s@nMNy|}5)o6l^863_k=$+5q_4snVk#3BSZPfF=5}mc z?79c~Tx&hU=iEzVF?N;`(l?U%rEcU)i=dvhHt(8Z8L=q#xbxVx}8h}#3puDu*dme{d+udW_Ke;aw@@o?{~ChjOpG~o|xE#nBbUVRDiZ) zLgMh262PMiT9582l7gJ`95_`QLDEv#nqV(kd(mNg@gN`}rFL^X4PvR5GrE)tT9Lk_ zpi%UwI$xC>W_wmr&JsIwayPL|UNq7l)Io?6ZCd=177Xked5nJnu`}SqA?{%LDli{t z7Y1lv^XQDecns2aYh6N6I(9m7@aev>-eOFzv3u0~E6lmxF4F|L|(qI6`gOh0+3y3-?i+Ne8o|2y^zlk{`{G$bX!kbKW|V4wh#-2_jA1Q~uO zK|`0CKJy>ohlS*;89&_ZM1LmD=Xt24lqf&9I)}2b`@PGxr2$p>d>Wh_@`*is7cZOtuov1=GjR+&^NT&itePyD>&*U6@&3ZhHp zZ6N2{*o5FX;Z{^W0Ds&@;hnb8iw*^jHN~WwzKq({qGg)*ZP!c_Udd^Vzf`8@-Ib3} z2Zb}s*nK|^?yvMoT3@6;!VV$^9{uenY#a2gxBqf+FR?LKrVnU4z{bxB!qZ5^t#;CB zArT^EYL|sg6J0lXH2li%%Dh$6w(3N`&;92oiUreK5C?~veUcR)OGdhkw_}PXrunGv zvGyN%`Gf*d)cvp(L|~?F#%%G}b51sMR@)Vy1Ex@R-s7G+5o~Pz;aAY&1^4B%fV%i3 zO2^2a894=o|wFZ%4Bbd-uGTvj3=1X`Kp(Xx3d5tJ*()gZSF`59Bwx?3r7hXx$Ur#sA`*60b5pLCku!Qed?mtn3qS0q!1-Rf z;#)QCh?obk&3f4Wk;^5_EpU-Q>OeGA^={Q}8AVG6hCHs6AMUMYLb9(s z;SXW%#V?6JAE-XGAUL=KAx#v*d6ZxPZy1226f#g?_i8`psOd3a^*iv-TjmfQ$(8PIn`;=R(O@HP7RO{4~vepmFxu%JAPd;Vo(@+vyLoZfVqDuDx15ufo#z z*jt_d|FQSoaZROLyVwS17)3?}6iG(0QY|PofCW*Cs3;wRB27v}N=PWdii#2&MXHK` zbm=t-ihvS|5Q?-QVCWDaAt6AL`|dC^=bV{2-#2r=->Lbi)YAWEI zS^ZDp{0~JTywM4n{M=vwgI$n-6YE2m;S2Mf~Y_srA@S3WVtmUYypaP9PJh%zk z>W@9K^WLL(Eo)!v@E0r!>FIyYg;Iitsp0c3+l;d5R5 zk_|22R*5x++B36x{lG&xGMB&u=N>78Qc{9ew#zDuP z$}x@~l1@MWTLu-N0Po69E#K`i*}(td5$iLJ`k`^ytQ`ChC;hg+1}7z$G1U3_8p^w+UOiGff}2fG*(m;H2XWgnrMFe z(0X7ezxW(Y^hzJQt$*~tPT*e9Ss>r|Q(&yY@qd!R9c9$)djbR=F05;iw!PILuAz@w zF(-9btQLwe8y-W=;NLVneVWz@y$!wV*(YnEH029Z>`oe=lajpt{6OE9afP!p@#{6-4?#>n&iOeHlP`kZ!u}G}EeUu%5=m$-bHZnnNde>N zJnI$5ISX^DmuUfgM2{Z&5gn_5_sc%>;v#TD1!c-T zC=3b?0Rz}*HK0ed#DV@`b>c4;IBzK+p!h1y&J3jtsid8YiQRocyF{L^7Xmw?vHX{s zg4Q_n^RFyo@Z%rpzR(N)s{#%B{qIS{0VNQ5wC*aTNDuK5W$K^^nt18z{@#-*@Nn-& z9cB54DQDTz&>!1tfpj1w*vzLO6*S5HLa)`@8qV(hPLlHQ@a*&c58qAFvC1mvP&~BkC=q;zuMy~M>*_#R1sC*a ziX))yhF3og(LOYYc@O+o3vH=y&x+9b3xJo9l<7b6)8|qrx^4?F@5+SZ;3lk10k{SB zMR?hg9cawkdNjFw2>R*(ANaDaBb!Pe(86Dig}$WF7HDbtfisnSTZ1fs&xFH&%_)JZKgq{-TtWh6Pi*}bb@uHD-9ibue<8$FkheSmP8H@VkJ zQ>@Tpz1VvY-@M4cK9hp@od~go?$*Mk{z&P zQM^{EUAo)K4N=Y6)99_$9&@ z2!@N6(UX!M*M^cI&z?cz=!`$W@z!=7&f+2~(`!K_&p*x^q4 zNk}0OV%(8D7TC5+lAQD1dM?})onUXjioparjmrbMaL(4^>v;1{NWVVcLp>`mTkit< zA>OxQym$CDB;G!;wRYAa_L8-m#|G%z34wk6jWk}-Hf9RwGbscZ_^j?Ync>fS`g(@va(cc0fjJrn5F_(S#8v-g7p!Q;zQ@QA!P4Yh_^ zfGj=dOqay{_8;u^ldv4|iaQ{+m3~UY*POJz+s7=FE?z^s2pk|f2d`SYiq&3iI);hK zb3wp?`a+k!g$zS~eEyx%4^l?W^1ALOS^I$2|L`?HjIp$^lgMhR%B~0%gon!Gphxh= z+gd&u?S>jldq$-uoaf*USNOVVRVBRExb?7KME96_h)kB%HoAopZpZewc!_4Orb#Hp zu>4ueqIlx=*wn|2(AxJWn~wS1gY`B@?zCc>1M-R`YhJikxe=x>_kPyG&%IXnWk7ED zC?(ao-@kNYt)GIjlHfLdF$LM?t{WmBW%uAk5P8=yn5w%%KIUC*WcW!R57;}5k0UHA zNp`(JC9Jmp*6?6fyVw17USgO|nqLK? zdD4O!P^yP$ua;b>uI=rvC=v5-Ml4z^e5+M%QrdS{V%Q7Gc=sXqfk*lU?Fksf=mXSU zkR$2F-Y8FU#{#Wez6oZe6u14t2HzuXH49mnpiHKq51gMdt;uTWVfyr zmorC?%TOXyk>HWtR{j;{!l{L$m)?}Gg&T^GNJy?aj~e&i@(J@s;vz09ASTi5x$5zG zI|bD1`(L$=hb@iKj_-jD6z+=&Tetm@U?I93haR5|f-3N5CZs!jv>6&E&;Ob63M_e( zW@pw!XIfH{Y|sRXry*LGR6aG*qV=Bs?gLXVdJ7&&oQlbWu_fRpxwza0@u|pFV_%{~ zB4pM2;uT+odc(S$iXHJYf$nH8*l93`T-;01vZ}8=aMaqmyJ#@5`hvL=BZ7^D+}QrcdSuSn@OWDFa-fE&_V%gAr-8k}8u}?7 zkY>sIJlg}Yk*_QPw(zx3A&x8#bBZPP(xrJo?S0F;_w>vNCbM0kHJFnFB z!@K8UOOm9(p-rMg;2cqzko`AiX(Y4S|H>%MKCC%^S{{=%dtiKODnmszHN{|Wn8~O) z411{y*LTx2&|Wq9ROft3N(%AisR+!gn6($=FofxE2l20IO^8S_j}A)j(<0UoZp~$e zN^($u)9YiZo?_o2F-&KTCjsa;z?d&cDwJpxNhxtTm4+`qm|;yy5(Gx zTAq~SOSi98xzVx{-TS<^wyeb5H|{9_eH>r+aWO#lXfvyB)`2*3K&xi0 z;|to<0}H2u58*V4SIJ9Lfie$=v_1rXC}^wQ2_?4XjQJXG!>Njcnqu1;_3CvZ7lH8B zFrgxR!1ur-FrBgJI}Q}X7pz((vpQC|82w8(H!V3?z0im{vt>!ZhGt>Tj*U*%5u*;9 z2Pu1Wxf$5~sm;wiubVBSmMj<+I_4Ds`npote@?zyraAsimHr(naA$#u0T% zBn1V2C~&Hg?r>u5_+*N)U2sprYQube-0|#FZ`^r(CrgdngsW(p2@qWIMX6cFWD;sa5 zr#`fm&QA@U?I%@yAnXu+>(kjXF>v$U6j0$mlXD`_gt3=HiZEcq;b3SI;FDUL*eYVb zDu$OTNhg50$c9+WgALCqF>A|H)vNjw{uTJdzr>i+Xu-5Z+o;2yTkR|wr=isH-bs+B z0hRyXPc7CM5{guCQ@gDxXcD`Z`6Vy8)3jQ|m2?dE`Xe2EKd~IEo=Y@4m!}Xrz+Li8Xh^#8`xY&6oY_{^4*pTT%f&~kbrWN#@ z?Dv4U?q_go*u&$fx4k}I(K-eeIP%Ve2CR{K%^DkSG9mtZtKhSghk{Ht#CGhJsFD$c z@G?JE>3qVb-tAw11Y4j1ls(79-|CkH18Phi%%Q~WGw_W{DBOq712;R+9ht!x`-u`+ z%`B_|5)4k zkTqJjd~N{2N8&rPU5?;Dy^*(X3Bp<3OM1$*VdP6)mjdgbW|hxcAbx~SFA!Z38dO@z zoPMIN{0T;WRmg%wM#n}*KCWaznS#$CDB={`Ani(sZ@Z?~(K*q}to=U1pnmzhn(aU= z^P|7|N$kn) zylKsLQ);+XB!Z|Aj|EJ4VmZBG=w1+4rvrxwP-2nH$bN!LJOS1l-~cpZ7a;ntC?V;# zuWtt3pEwe;RA73#0R}jd%$C|kbTd&?Pjp6v7$(64{GXDjMP0?l!B-&=v>eQsxUVZH zP^IFQ*9dUqISKetc4pz2h71bOBEGzh0rWJyLfgO4+S|lm=uCgUsrl?EqVPikL!8A zPH3O!_K|S2f&Tj$*+u|`Z5X?KZ9UI^lt)X-h1eg5u^l#O573YT2kX4xTU{vl2@fzm z<3%L5Avq0HkUS$mQ_zwRSOU{ixyRmU|3tE!iIs^&M8mb`bwNx2I;O{5a9O_O3+@vd zS+cHa|b0P_I>kCatE1?{Sjk>!%gf&5J z6F@*4y4fp6$jVX-8ptE(V|`}q5>)F{Glm}8SB%slZZM)#K@up9TqcTq@fIkG`~g;z z9Ciq0O_Z^G_#29oiz3&!PEqxp2M2`v|H>T718SSjl7U@DMSWTMqFh5mTH3jVv_Jz= z`s8aB?>3JoUXkhH*!^qOSGcH~eMURKQl4kEj zQ=fNfc0u#y8k5gVixqXOQbU3DhSPOzUD^K%3(IQ?J7xjKP8FhZV%$!CmF&QP*JZ%`!Hu0 zmQuA7iMU0vS?3i(FPd5$4NZYdNuUUL$30NZ7vdY+@w!kt;S-r+X*t8pX%Xf#SB=V` zu=)#Ke4bU=BCDK&51vnAwi$4r=#=ju^dH)tO79EOADf!2i&`n9D+>UM_)!r5jUacw z_fYG8k6>@)=PvZM@B_xE%zOh5&V*~Y5T#Fi+NeDm$Hd{Ls{-%KAo6F6N1w2l@Ju{^ zdNL41|A(GnoEUNlG~OMz2hx)Wfvj^@meIz_&vn%F7yiso%~^7@%CEz}1U?AG!AXMs z`v>T|59u4i49Pg+0r(cHj+zAKWK&JEC4#ON_PZQfF?Fg|8J~^jc$60T^{$YyMT)q&B1Zw|L zmS{9Gxm5ko1p~{uRAw@2@n}0X@U`7cA~Mpc!XFE~3%a{e{nn|ErB4cRc#X+_B+v#g4_R z*-JSY_y&12E>)w z(5u)-+}TU~79QwwHFWE>lM5(Gp5?-zj=vH-DH$SNt@|UBAZcv_iZC|CoUbD1_dhOS z!l|(-Mw8=K-oh9cBC6?u&q$44=bP!eB;<1-juJgGU`O-nn|-(OA_vm@UyBAtWGpE` zGP?JllhHSBqLT2)jytI48HVt>`V~Sh{^F515Y#Dr!XmPG%}`QHYuqm5uHA>|g(ebD zq5w~zH8#k(!63_fIC-=U-!1`o&$OaGpWuJ7E2hK?O` zbAA5Iw*hRzZ8Zweh7j_-5eOWquLeJ5vOiUb)_KU?L@nfRXnCjHsRQ3%LZL1uQtBfwsm>5^KdQig>SfW^vA<%@PPW2P;NZd%=ve zvVk*+^WJC4?exc}qNJ#bZ#zfpOoHVI-ZP0QQCYaj^)#`(bHqgG8Ycj~dQs?|U)+a2 zaF=u9-qF_`i3!@A;zR>y0ymSHLNPJn(~MCDC=4Z*lFT1;7)>_`(f}oha^e=DkP~R& zj*|>!CIVHCHvnab{%Ys*HLN9;H-c}mbdHOsda*Pqn*47vqafuUY68J25g3I)d{Cih z&^|6WmL+cX1uYb;Ag?&M&PdpZE`Knk!6?g=4W{B33sd%*vNE(z8=I!GB3#d=7)$31 z7!*QpQ~%EFm0kyyF;t42u?YBYjPw2~=^7+c5C4U(^LuyLPyDP_U~WfgHHY8%lZnpU zic8OzH@4fN5<`$c5MU*6#A1$p)QW-Mxa@;!#Kn z$h9G+z-w-dc{D)ZtQ}pDYTT67xcp3jx0jN(E;i%2cS^(wc)8AH9i&HP8i^*c9oeU( z6M{9AGxFBUxF|rou;BQg5YuGbg5x7|c#dYl`3rrN7|Gz+h_$xL-%OX^d7l+Tl3^e1 zptmeE!~=WKLM301&gh7Qgehpu7&=b;H8~jQrGK|S1pUo#T_=Ymz?65+Y3->nH)w;} zI`!Yu7Ly}K54KPjn~WBT^VOK=5h+{!?SJX&fMQw7wM!ye^^nPwAVW7wXpdn%S3fBF ziR~5}ACU%pJ+y^I8eTR-IgQ}>_e{Fw-~apN@uIPCQb)M~F-^04?p@jMIutDI=m;b$ zfVPd`SiX+MbSkB#m5}P}NHQQe<^<&(NW%1{$u*(e|PtmfB#2PXGm`IKQ^^10MR3Xh&t!g zXPJL)ya(f2Ltba1jGd!9(q8mw9-hQ~Ape;*0QBa6b76&O6eGz6D!%kl%g*BDLxmHE zcFI59Wefs@;IJ;K2Mzpab~-Jm~)!v7!NAn;Uh{PE&||NU1( zkHi0C@$G+4f%ki5RTQ;!2ZGyOXc@$)$u3~OF0~9IMYgLr&KCS815@(r*xY}Y!Tm>L z=gXd-eh(qKZqB4j<3!PfYMIr2PD*i63IIkN=kfrGQULhF8DJRi?SWoqCbcyCAQTTThs%IgI!ejh;V>x0?}%xX6@9hsT(V!d;Y0fgAdapmPIfg+ta<@*jho$HZeDN>*HPXyDjBW+ z7M84#vP$TU`Eu$Qa`CQ2zurAF3Q)YdF)?y2P(cs1h30!AY~nqnB2qXF6KzHW0)*8+s{m9(Fb{n2H8L-1N3 zJaJ-XQZWndc$W(r7*6c{%I%VhjTzQK^mSnp?tOPg(jm;heu}PbW?Q>T=z;KZ*r^oJ z(_Ml%#oEtWfCxja)`#%L$REO?E*$@(LntKW%Vf><`ig$v<} zF*r!WYpmQVQ@-ptvP1nakVqAU$Sj#3Zm)8bk&&^?V5o9uV)Mg({O%`8^_rD!YwXRt z|6AJ!=%U>LBp?xg@~bvBgri6KL;wf+_e{`j`d9~1z;CxSKhaz{BJwJU%)*LiHSp&a z*_Rc+I#|p0D1SIytM}b150uX#IODt|m}C1@f$q+=7t;OH1T@!>rDkDI1e_f^=~6(+ zNQG2BA|S0I0UdhH=(ZapCj6YH%X8SDB*i}zQ6b9#2X!s@n?laxh(Sq;*&p8ZDrimc z>MPT&Q#ydRE^@v0Ga$UO=(`H(BZkBSZ=!)4Cbqm!fkp<9l0SgR-zfjGg7=#hh=@Tr zbw^RTfENw86j_w~g{wZ<`rq^r_CdRFul@ti5}KkNC`>NB}h$0Y|a-bcAmli`KV1dNllFQ2W2Hawh3dXfjssg z{=YE?C1Qr3FqtUeKHVT)-a>skvhPX(f(mj|sNWo)O8V=ILA9WFBu z-kX!$+SV2U;8*!R^;l>^mE?W;H?F0rDb2(b86@>5z;f;EeaCEpN%ME24}@yogH|B8 zXv=2yF5<@%ueRLlB-9H3lvU3kg_AmRV%Z`I5am12#eP4%5627Z{#j76g7_APf z(m&8Q6EO$rKyuC)44)qgcq^xnvr{_<1qkllR&DwpgMl1_)7)&)vxlrNtA0%W#l1cg zw%@WT(l`QGR^;`cGJe`pQF@RPr=IoKeCu~uuK2ey2jEG0K|QV(2z(mrj}BRFKOR0D z>WySx@zR-#F5VhVS8B#;tz5eP!5fNrq{^+})h?iOT>xfT!Jsed_cpDQ{Pn$g$@+$% zF!$RGMsRCR__z{*uQuS?CP02eZv&cP?H4dUY~rgt9L;PP7;gfW|81IXZ*xmrZb~xe z1Ee_(jxV>j|BP>MZ(f7((Icj=8QQjG{zJ7;E?)26#p~)ohT``n>x+wEy&FM$j5g$E zBzQPptN@gIsG)mWZS=#$3^a4110s`d65#ED*92r6zL!{v&i z17uGk%{q;cgn*BELuT2cqrd=r{YzZo804mYr+l)?GSQ*!HxF=uDR%gUi?3iA5ZGnh zw{*g)jThV6O|jnFyLXQG$S$BhgK|W8>!WqZt1cQ#5kVY7L-IGD%KFWgGT*fj7M>Td zUM|kQv>|6!oJ)iGxWwenGXw8pe`ANFQlTaE#rDM382Ud{gUmyR0_K98vm8}kavrL3 z6M^j=_w-?{N52jXC!BC&<_V^S#oo*un}9^N#OCh7FQI^x8W^(`Ob@^13Z{pPz^H9B zphf)^iL_foI3Azty1>szD+^r%0b)L}cSr0ko9{bx59=QPyrbuM+Iq!-wJWj;FMKe3 zEYV6t74izZh#rO2>4Z^v@L;W1ICuWIVxgic5_Q++igm_Mx~&EcN~b~|U5c()l>dTx^IUZAUn8W_<>LWwxo^ zwG)R;0G=<>kgD^Nv+*Aixu4bAsU#rM<(EPuTM2WY5UEB{ldD;Fh|r;ngGu-l6m_N{ z)BIetCfXC*FWPuYG7fhn2K~9>2=eWn5n4fZI{VyQfaQ;DB8#`kNDB^=P%_Fe2k76z zCi-nh@Kd@%cOMz?fW|u-_}C^3+g*DN=g`Z_?E&kfY1t!QXW29ztdBBShDZawY`A>% zUQuEyH9NZ?BS3#FXl$kx`=uWUCW7W#4bV~t!qKVGzc``;K#!Z+u1pbaBV{Ycj!fQ1 zwskB-1R|a*b$>rWz=}0grAK7sDVyX}45cIbIZGS{MOq0Pge5zoV9cFBC*YPyLkH?h zToLNaBdnjh2|}|t*;WP!1vW&8<$==~g43Nc13BF2Pd;?uSLn+Xv2*n z2nFc-gRHXhxM0;PZ=VRiSg*gOt|C%IYx}(aJ40l2FUvT}?#XG~ly@FW9?4B-QptT$ z%HQiI{geQr-9DJ$%^!+%$xh$>ngp@}=>@IE44F-nzyS`|#BpC?|9X0_!ihtP$)T#;JsF9pg;QNit%H^x^k6yUO0_e3$|duc_ydN@r&1GUGA9nCWCYZL zIO#|js%i07T6x35bCT=EP+wJp#oZQ3b+8q`99r_gj_OrjqA(OyT~xF5X7=z#M%^ZI zWc4g*F1*fBW3D{dm27a$SddTt`X9mzbW%-TP6q0gLf*Fy=0 zFD5Z$I=wbGQYVme4)9{UpH{fnVsnDIhLPq}R z`fbh%(*0h{GT799KJg_vb_JJ9F>O z)$lgn53(t|c446|%Cl}b+!iA>KN3L&yz@CDv0OYla5C1ldCv-iTkZR|2F{PJ(rHwx zy_8uj52uKvyUoSm2Q9}#S8V7d`A_uCk4ri?g}rs}%qyty#aML|DQQys4|dqZwx4ge z+=FuF?3c^QSjZJlNllnbthe6li$i1_}FCa$k>#ztu4W_JS{D+-Qc#bW>bUGu_g4yih1tGb`=R&_s#&@4(#i0^L64(IWOJ+lKJA!fcgQ1&XT%f@=TK95*unWyGf zbrpLU5mVITt*;&7W*MU@*HDE&9oAv<$mA@32Y60>cM(70zvha(Bk_CblcRbv?{rr` z!CAJ2v3(`&p4kTTYNYeXR@*}!cAt1;x$cKu*Km$~rHk1L`|$QfB4=g&*W!4ONm8wb zoL3zchehp^>MpwN&#}pM-&RBrxr$?>sZVXg%_rD8K{N3D&l)V{8|>)t$?%}e)Qi1? z(KW1%-COZ`;<~je1mmv){t#+>-(p($raxtDf@1=LEj~w+7rcga-lPM4Dz7()x z>@i5NghIejFOZKbosG%J`j~n4~5f750P?(RC+h07e$CP*%NLttiZ6%O%7gyS1^|K|Q_4n-FcDeFV`D9}&9;oMp#% zZGYc4MxEWyon6vv;irN zl9M5;i>H3uzZ&gJr{&F1`_5YxxX=S`*7D-9HTii#WwT=$EzYQU8;LzRZq`xCXZdD1 zW4J)K&RDLrD?#?&oTTigxPmgA6b3p^v?r8X`pVrIfu&uUTc?D5qHk}tXC+hWM!=53 zsU({KiZmuKaCoYGwtfCMYDDA--ZI$e@UtZo_`KHw4^4{7E+6Xhc}H$+ib}`H$i!pZ z-V%CAjThTBW278k%x^n!3mDfyNpGbOqdvq72 z3SSj}fn>(?2RskG*VA`?@04a^tS#(7u)-zIfW^|>CS0Mj)Z*wHMff=cZ1DZ%iN34D z8SpT*P@p%he--Ht<!3Y8Cp5Jwx@+uPlPJE_#Z z%X{uTgP(H8iEre$Yf>ZI!w0p#RR=3t7iA9Zk3M}rfz^a$EKM9R^5GBUDu<8y+r*1Q=It{s+Q1(59;Atz!&E)R{16p{hAgo1*{qV90EKk z`1M8&4F^rr!3uwKFn_4YP%%s*wNqI6l&}ouvYjEN=a^VSlz^T9JS#T#JFM;#U#D-+ zNikIJG!?%TE-^vOZr#9vVrcP|*awTTcSYC^7WDOm0Wy2@9A^uA^Ys~0AV)p`YgNmt z@d)cG^L6vH75^Ceg``PjH6V0$?1@|}{$796cvukrtv@bo@Fzr-EK><~^GnBZmrFr#EMJ(l1SzUr%fY=6SDF02J~qaAtsM9& zs`(NMM&6MMpN`IAvdDQcq|8GN>VO^kFvS(EM|Ju^t77z>50?Np+^vK_WZ4z%O4KZ? z)#z@C7wdgyWo?!+=#&`K5T+bso@w#8@VS;Xy-eL#hjmi>LUX=)a<47txPfpY#xKTq zM4Yp6A|R1833wDUik9VW`sf(MU`Zw-$MIr}rrQw>-n?wfjWyo+7)adtM1J3~ch`4d z$EYBra?X>a!Zop5=wS?RlR55fkqfB#fzKgYN54MCehj@PRDo?vS16DJmjUvLHzf0Ly@r|wH|fXeJA=(R!xKwI!XRp2ejsCAzHA- zlD_6}(W_s_Ecu@*BF>@F=&|wfjKCrH(~9|bf`@FbwyLK&dh+-NmUu=Ak0r%)?~B%e zaZYF-dXZ7C>gzk_G--j%ZCu`<_rC{?<_8x77B{$Z?=)CyUdp4|YRV@bTIA#WPf#8d z43zA2<;>ph^f4r7`h0v+Z7F?+l0Vv9Kuge^O|v1lRGf8%1^ZZ4r0`WRt-JrFiV&*g zLAcsiT_CQJ2hww$Wds*I)=>9Dw?-YX-oO!Z0!VPUT=mla8m3Ix5$`<}2I<0sL$?Zq zf0Z}e6>z4>Q`rSkiq?_sg`&%y?2ww4g7WRCgKJ<^rhOTYicj7Um5^YjZ2h!Jrq?0f zW3(~U(;!Pxp5m2n(lAt5RFtww-t4l+uBP^K&(pnl#1oSXO!w2SXmjnyE$@JEppy*t zxb~V2=xVffMDdCC-hBDFxjE7WHpx+pN1)ViK6uX5+q8X(&;A%ZiyUuJCC>LUDRj2* zH(~gG9m4ec;gsP7^f?XpsXS(dtli@VAU-BYr6QV=dwaouf&`Nk^;;ICmzHy>A$|@- z32|0PJiR#gU)&(5RGF0kx5)UnTSWAp>@Ccnwah;lo6xKxo2BM?%8D3x@o;a;0eP9c z%U$T>?l zm6J1g(_~9~*hNkbky||~#^W_u{xTWy`7YSGgw-xCx2`Iup?~f9^3-aN@4B!;Xd9Jc~S^KCwHZTQNF!NBlmzWhe9fZBfeRCUsdi$>oximDrv7 z7Cb~i&bYD+%zVj_dlOCOtcjV20-EH?#}mpK?IDK%&8v6;l5zK))#Y zCGOKu+z@>zzGVgP&a#U4To}+9GUX}}nNNJs zZBe<2r9)jukTo??g{X7|b5=W<3RJ1G(ZiweC`?4PS#Vmt2ph|q3#rp}P02M@sY<>FO{?Z-uT(tA+Eb}?L zK#Pjo14W#WpMQxszr`a{N966H+hfy9^QP7m^_-y7*acT|{wOtpc;qEU?G4&X16y19 z=w)g=){43kIHk>H@-I^j(#p%NA=cxHBQA}XH^i9Wj`5WxLsq?4j>DZ>;5OMr2G12E z0Uz{I@WLp&yCpT452sw<50giC^9z-b%j}^SxuDWsceU#Fa3Yqjlak1@pFZ};8v z;Ny%nu+~bS8?MqZw8qU2GUrKreKS0_7EOr^4SuF9Pz|e8d3&{So9m||`a^{U=Oacj zhLmlxt@WD~Iac3k4mxH~0%hBtAr<=t*= zStJ+~gK!(c@i$3sA@x(eL9TAK?aUDeJnfe^Sr=$rETU|l#KX+4N{n#}A_iuWf%$u?cSmip#V!ZJhW?XcpmD*#ucgz7Aa1B_swBGiIVTmOB-bN8c+Q4<&K>RUr|0J1%w zR{U9u=X-`e;KK}W>&(2`m6l3V+%=XRFm)PL#)O9Se)qSi5k3vze$b(N^Ov^sUuT?v zzY>sBbbB>}Y2RBuya1Y+3)A{K2VQ%Y%!qP+k`1HPEBJzR%3#Ln(qHl$gn=GLes&t+bR)6<%4ygh;&zQh$=@zu!43oltMRCAF% zRi{(VT0Ja?bw*7^sprtO?X885-d#j52RrFsDWzYUE{RM_6$JX`pBB8$MYBe_5D&Q8p6Q3$}18cutC2T1@%R)al;Zpad zUSQgnMbD%1A z6reSyP=pCZO8_A#YD&o7?%t4_?O|Iav;T+6s{_$BL5YDapE|uBGE!=3dXhxH57Z?u ztpUG_oq3J{tRp7Ag3H8(v|1=9R~JGpZYv1cY&R!8UQ;W6x|UNscpzI|_@g-W#9bL) ze@xSaB~Kn%wwJK9B$~&0y7&D&R9PxSCj#4l;L_v-wttSnAXu%3nH^Xx@tp(`YneZCHB z8#>|zt9nPz8k^HUDKk*J@@X#bo)k)KW}MF}J5n1wmwus>R>m#hLf>6rZFFYX&paK4 zMvi5jMt$nYI+lC!K!*-Y{7bFaPXjhX)sHx`Ysq$dV5vvvmS<;8L0z>^=D05G2hY4hh9ATYOAXf(X3+3R<43^E=Fs%`lzwcV9A^f@jn_9T8j6f{$4P86;9Z zyS$v3bu^{q2P$Z2uz(muPKQq1$Q*3=JRgFh;*+%Qyuv38HxDjN(gXF0U^ogKbC7ux zBIgk8mH^!0^N3G_NP3l5<*_-nE5RKkmCdYeF%=hc6OUp&8-;O!D(UGymHJ&AN zCdpDt{F_m@pScuC{{Gl-hjLP5u7F`vIS;xa_FFJiOSe3-HHvhoT%o)_4Ea%sJgljF z8YxNo%*z?;h6jU>x|_~%K00*H&gPozFW{WKW6ig8PPo7TyO(04iQSUa z3#_#uv3O$wc3mJQ?_!g2-;RtT6!5opwY_B9vU>>53t3SF_3|IZ&1&EF_dg^BdqH?j z%WoZ*c)X^O)0eH|;U4lu!>F8va`ymo_r_PXfj}t|ONaB%7U6Oc`(I;j^~v6&g*k{v z;m~stmgQff(ty}HK#VQbx8j5DCC~w$79#kPx?B)GbmRBjn6@g+Cg|)5zt{ow4e~y# zuk0E$Pk7V@kjWjfl2td&Lksu2`-{s-9?QcG#kjFow~`qzdv;f$>{#szM&Xn9CPi_q z>tbs=Q~DO;*_WflHEMa)MEw$-bMx_YX2^nlP;s3X6iybQI`G>c{8VI81P$nQZ#}{v zZ3=2ksNDwXIEWGWt2j>tx3snVPg^UN{PpDyGZxO)WsH}x=iJD3K0Omaf>1mz(;|1f z+d)hb)z^D0c;azj`YUH|gYxp(dmu+69?s=lFfqi4+yt*uK`*R-Hph*J53qOe0QEm6qn4`9@+oUPu696sM|APLcT= z9`&Z|m1!+klOZ_#+@l42jAtb_Y(hMd)eYY_kDz1G+Gx5SeaISBu2@LBi2bq34i*`t zu>EcLXY_$0#g&7C8fs2R`6%!4`w1x}9%zjLT|}k&$S`{ZOTMuK1{4=u@Pj3RmTs&* zqDY1VY0z)joK>oZTLkNhPQB|}gI`}a9EFE(H}VY;6Iwjiv|uun^{lMC+!TpKUV(gr z^Y8G!!@WB@{5n!SG?_}An(zj)ro$~M&%GJ#Xa3ka(bVR%aNnJF?QQhVC!dMi0-%|i zbOQHYL2%VjK~UA5)_jM6vCdM($uSpTIsf;2CQKmH=(^|5d3-{rxS#$*)qP^cJ$}}H zmT~OByyJIe-p#o5d)?~DofPkqJmNQDF8(5_x?=lQWtOI=YGkF>kHu7i`1pfCJFV}| zCQTk(e(3eVLoZw+bwCRnAvwGf52Nn)!PbW*SZ?^fNARG1)djW-cq z9ZPiy-Y7mmnj@42BlkiNA$w8b5@^q2njn{I5y$RA>pLH6d~IP&4*_Y@=lUyz)+%Yz z=j&U0v<^bz3qbzZjaSi{=i(I;H2uWq^J7r3s5C%w1>e6o3!=u06(33~2Dg4a>qYe# zrhX^HdYFQ^jxl9q9swtvb85eF+qLn@PRop-AWp{Eq8 z-2(;BvyhS4zohgp8V~6mQcWEi+MHJEdg98JD`PnmVD=3%oMGV9p_uphMi!4Q3Hr)s0TdtnjmelM}&%-vWh^@&*WYR`5|?1lijYrEDAcg*_=@ z_Z`z84lk>%y?|W6r1C}TWlQWT{JpSE<uj*P#S&^@$i!B*S-KNUv6cIV#V?b-CMz3}mST{nRQvo4%W@9L6atl$ zzeyxNY7;C=02G~>k znfQ1aj9cYe zZO~h;I27za5EHV0GWnlW_d@%2N!reL6ZKug&BEYb(Fi&En^A ztUFD~Vs^aiK4uSJHp^bdL2Ibcq4UjRd$w_6$-v+!lDHI+xf@fzn1!xTv`(2gM(5l6 zao4H_Z<8U--pV|U7__vMvMW;k<&MAn8}PoHhljDeS?0$sql7GUZ(JEXU`Jy5NM~Hj zRCeI@W-+LDpcc96(OE%GN!@?17OnA2c;I^I!uR8IAW~3{6dqe11aPPsOD)mbEEp ze`u$YoH46uZM(2=p~{cgcH99}a%*pI{KCS*Il&O|A*SlQXt>7i!g_UuIhL<;qG4=? zcGTg9xY*)5W1YnPzFM;n1Fz_sqfx+OvZ2d3LX2?sqF^ssFWV0~gZ{r3!)u$U|MQ*1 zK1XlgDTjM+0~M}gfTMTwqkWnmmcUk8l0Bb$BaqZ=fSISU>tH#$it zaT%W3M*v}3s#t`Ko2gblNhd4?r4b0YFWUuAPRJ;bf!ViqLW$=mW@Dz5Vxz8rBn1a) zOA3B#zaUV7`B;Ny&m|Ly)YjtiVjZ=nDn{D&WuXP3b&$NoonQSq)(158rd+f+R_kauqBBSk}`@Slxjm5Pd7=GK@ z-4P$8(@t>9^S|%O;{2 zv|Zi*oT&jMDu(KWJ2#kUAC8}WaxiZ+GSYJN(ax~^!#}1vQ<7ZC2=q78(#T{?Qj6Bo zNP&OD8qUVKlmCQT)P7!iL9HIWjtf!=;v^E|68s15O{C{qaOxvmXD-3F>R;oP4kw2x z1$t-$C^ND-STdcD3)y_|DTmD_`s>NY^8onX@SA$UFE zr+yC9f7lzcg566x+#A%?8!%Rvk<(?0s=LVyZwwD~6u7`5M4-Ua36M#e_#XAy87+Qp;}jBjc4Us*|b|lw2&IugiDv99g2-zDbSe5L7b}(iF znxHA2ynSgLNOVWFZT}cd%RollChJttLr1?oJx$$^m&hIP{%*}+{~hJvZQ}&kCi2Gn z7}|YDGA}Q0`P_o2aPY;*5gd84*y-ZDeDGXEr%x$5$R@HnuX?G(*sm{wJmv~rPx~v- zs))WO{vy;N68_XSQV%r=ZNVURAVj}FM4)6SYZy->f=#Xfz!eH3O64PmavHF?`qdsk zR99T9&U@-%+g+9!H9t@t29Oso9opm@nkKq0qSp!J9-SVSIF3MYt%J+a=e_?t2c2}E z9>@-aj7rt|Flw%IV-tB3b#B>pH)ir1wcMJ{Ol`Ztf~qDkO&bB5+PEHTD*#A z9MoR{ZL>E$^OaW$#Ch!@ivb~4S}dx3r!9oh#q)ZGUqkh@=yRylyu++fVrPd7aRGh^ zJ9is9c8^fu%Xu*!pm2Ii@@|0)rT`?N90Ao@3}ApM^FIAxA9Ifp96EB5mz5Lj(ArPd zS!%|=R<*@`dL&r)(NOO~Lz3g~ZR#C!HVtII5vo&l$kij_uU8qZlB}3m^65R);WWG$ zUa8gC!G*F7q-KR$E&JrAs#w7WF`)=H4k6R+ca-_Dc)o9YI3rSWSP6dm)_|AxbiZ8V zWdUsy@-{j&pu86vsl z75+Bp-qf64iZmiSfRY?FktR~FuK@>>LT(FRY4DF-`#%J5iq0nHlzFTktUfa(9#`uD zL3*5OfpA#JDs#%z{fdT-{{EDkqYD8#k*962SSy0Nu{PQV?^~3t2`}4QSd?j0+n{|Y zRuU-=$R;>SWASLmi|O`3HK>W7v(^r)Jh^JDi{$=J=S}JT4a131;e0s!V#K z0BLY?)VO?cmcROPv8#dboYHB1xs3?BCYqDicxf~)=SeZc-PX^CT=76UZnx|Km8CtC7(PcibzQ&Ur| z9UV~u^RmpBq<6^QSP~Ipz(^&ub^pj8GpJnRrTB5bgjm}<1ED4$3Ybk!ngE@_!_Jey zoen*j8Q4BipW>|2a8f?p`bnfRGTOMfpxz)J&e_=^fgOuJ{ zOR42g431u3gni6eBNXxuXy44X6W@Zi$)rKrx$;Hqcbg!Il7!uwl*WcwO1N=si>b$? zrH32JP&4t%Z~Z>jJ<`%~Y_;3NjWfltD&O0RaI)s*VK}1eGQ= zDncmIL|Pz;6$>Rcij+h|L3#~HNunY|X^|RgP>_V)0tq1`x$h3n98df1Ip==oclRIu z5O&Jm`z_D2p0(CP+>ZRrHcsI4eFa8Hy?mNe8CGdn_|6UA#uSiw=1Azt+fKYj;;d3z&hw(VE72!%PKJsbj6w=cpyg z5g4C2IyWm#&_dGdyqG&87R#oV91!iM&QFQ67O*C;-5w-CkREX@wAY^B3Rx8$hVd5! zrrYFD+i_c+*)g=MgC@Qf`lW$Tz3aTNy|f6J|E*0Hd@rYpDShL{jAPPoIjT6KCJtZ( zslq*63u>aG#q=#(QumGSBlQ*8_<~m-+aa_UlG2{Y`_N-{{f>@dLLQ0jGV=aqMn-#m z$JYQ8_TNEV015W^hxB2?b^GQ1r*hfav!U^ONoNEjHMSWsadU zY=0iHgkikm6i<-Ys&6dYg1o$6Sw>JItd@%22W#pH+Sb0?jC}KW!sLO>z>(+wQ(9na zWVin%eO!}%?VrI+-!pXoj$h)zjaZurC$rj$101^Bn?cV?N6xQdf9eJPC6dW^<+ADs zI!F4t0_|Badu`PzlQFj1LN*@|>Jqk-0=k$jHKZ#=&>Z+DEQAv*x+BMyorg7i8(QTe z7|C~foRSe87KmabO-D8`WMJb!kgR)J1|+Lh+Er;WdV6?NWlQ;U*_5#DI7^P>9%w>~C@hl@ zNU4H&@q_Mv@c?fJ#f+RI zvv9lnTrxYo7$N|z0l7@VYiLX9^aMd%{MMYbVwW~oz={c?fH*B&gH#dWK=rFDlf`uM zp{*SvhitLZ$VY5DR*9MAM+I|Ni0q8~(v^|$rE3iLrR(XQw6zqufwcgyRGOcd9K+=` zxSwnU^p2Df-+ts^LX$VYo)l=VH`rUa#?B)o2oSYS%V7zdBapA3Rl#{m%(yRIDc9*-po@55()mKfjeKwP(OUDRmfM-ZUc3Kma)B6 zyBwyDZ+~XQ=#?I_+>HdNUu&YKiJ%!U8zE|Kcd5675&YF`MIAY#N?zd|yBHR3UYe(C zE|?w4ztkVPa@;Ed4lmk*MJ~3s%A7amw6`;+G9SC;XW1#)dItA2%zecKx#H)Ed2?TR zqA#B&#hCeWQ-WW>^PHdNd%wCVX{5`W3=@#%vS3xaWC?>WG}=b5x>E}|xgha)Fk&a6 z9=Ls7%me**^i1I1Nz7odj|*ulHl*PlMvuf^!#Ja$!}3{)28tdt=O^B^-5tR*zK%t3 zC#rxKxDwzZB(R2!E7eQO6EZeY&S_nKnUQE|L7vR1C}X|E=4uuvwiDC|^!U9erj{mx zU)nnQ9U4NPwoM1Lzzk^|b6^gtMCC2i3zD$Kvkf`yg>zm({Y&9v(C7o5Dp@JR7o6IZ z@)kli?ULSp89V-kJV>YtM>A5woKdIh5H~R|78k*a`unC!k_-ZR$z+5tI!WnnRyIJ8 zO(-*Cd)T~KwZ12Kmu(l5HmQQPNji_j<|F58&`AUOYelZ{0Qo|_(U7@Cxz!66aAuL$ z&*RO}j6-}8V@dtV&P(s4Gk`zgkSTx`L@pe7LtKCOk@UtDLAO3gsY`d@EqvUG0FhH* z+ivz)m=pUDIaYs&n_JcwSN3D%m#_7P&1tqFxnKHSoLqi-Gbp;caIHeeAn6n!4MLC^ zI66hQw;8!{G&pTU+sWO|5@Xp%#pEuYo9|amvEVUvXb;MMRK^`PTscV?*(k?$S|cm3 z5SGg(Me{0b444ro*;tIL!&mo3Gj1`0%W_Ef5bVAz?Gv0R;<4 zj@8NfIMt*%W{`>GG=v*NdLAKKYq$crCe+ALa&jB7(^v(eJLRfYzgN*f4KVdTD|BZc zp4bVeOjkt|*o<<{UV6KKUD?~=Gg9Q|Pf($T>L%XHZ=}&iCOCMbI8Y)@|!QKz3 zsiCQ!^%tcTW~}b|89lkYeR}@YO2Ds>j;x~dW^1R zYWHkVn!?+w$re?eM5gJX&|3_D?mEM_Y4PEyh~pf>PnD6hDS!FqF-Jd%ao>N&_JvB6 z;86eR;<>r!+duH>6EMnNlb;*dJ9T3U^dn3$=lM{EFV0ZZm6VEs1kVIkA#d_cC(voSZPmBY^V?l)+tH&B`ntOl zVVYHe-rj(69}~k@4{fglh23*U-tf>gUKn_u?ARs~xi41_Y?ePUm*A`HO=VIIjD26u z1>3@&mhIg6j;Fg{A;Jp{l?FS06zRl4`3g8J-RHBe|7x&ubd=U1bxxWgaHdrlRLW$3 zLHNY4VqU>4TuBL}+i`x09U|!#gg5IO9Fd&Sg>h+f>ja;>eI5)-b$qtx(4^rO+@6FCckjePzIto9@e|J6gVKF zA$bNk%nFCQobJBi&M}OX^vf0wFo^CJYRutiC+9r-TaAE^@+fCOW=P7$7jOqE_r9Ac zb7awTWUmJmHc_2I&IH++u9=c66K_td3YVA@Yp*|q?zLFm2chpDOZE3Es$;xI$HyDu zw>qz83shBy4KA*eL|5qr{wfR31ioM38G9lVi$W}n-?oz^_s zcug<()A$b_{PCl9joBp*ZcQKeK){2@`NhZw1`!u93ZFoZP7tLllxmdXb>$IrQPK&k zL|S8o3m7=>ANW&wq#<44i*F^-1sTvA`9g#!k!uy~&ld6)zPxI*dH(D1fDsJ;>#SiQ zz}#8@bLt^fE>rsn$iO2hcZ9GqD4gL|sY9wO&;y!kH1iZ~>Kno^PM_kRM;gHkl=F#u z2m`jxPa%pi0H+V8Req^edlArtd^jz$dHwRs%7{T!N(&w)huqAXAERdIIENR?fs|#< zb_3B`2yXtTQ3bBMnZLPS+7avZOrgg4{aUgS(1w>3tV zz-jAzAD@a?2OJEN7pfK(bqIRM(N^4Xj3H42C$0tcuK?dSev(7Ece~$9-p>chT3C(x zKBj{HO0`qA0v;@bEQi(t=VtxC4BH$nEz`dz{S{0hr?_LS^hW(6mSs;_#)LitK2hLh z>-(qt0l>d;T4`NVsnG6YIA;8`?Cj{|q&X6ajHM)lc2^yrD9PW|D>BmSU;&n>qj?@~ z0(gf#qSV(+789If09_z~br50KuGPM4TQFo4{(1Wcyj#26LB3;6n$Dg9E~c&zOzz0NwbeD;uyds7G` zQ}~lKyzGI)FCragmhom(M_9fB72MWJhu1Zqc_GB` zW_)pv>7@-HGL8jxJcYk3b7sXs)_aQ^F8~r3EB9*i?fgc7STD2x0>_?`2==sl=K#_Z^-B^*$g#+q)Auy0yIs_m2a>}^Qvv0kz zc@hqxl0D(nGrI%Gku{bUvn@<*QN!^qw#2KJHabhyj3U|)ME8RMIPvVJC*t+jXZ~Z} zBA7?u`1eV`>IV`NTcfrFoMko~$n>E13b_OHXP`;nyFr^5H5SGSvaDn{JaD0ya$FR* z5cQ3Fu>5iVrX}9;i-diZpEWgl**&0BoS(EGQWdwXZz#8GOwbVC32@f6d&1(EtEtV4 zK?&qB8?wyeT*)Qc1v}ZceQMC%`r{noPk##|E&uW#xU+#j{Hq3Je>wHdZ(X218T8UB zvkz$~q+On(JWlao&>KC->8D0AOIbOk__CKN7MJV33C^IkqB7W)--TDwmA3-x8yrz; z7+axVnx3bc)XIw9#_WD@d-$Nsx7gx8`BKm&LeKhOidX+q@!WK}yz4ykl<^5L;taAU z`YfP~1v=EvK%kwWi1K#xEY{cW&R9P8Z~I*@|Msu!GXD$2Yp@3M zOC4Hk$z_)&@?`rwSW$yTtg5)%9+Npgj4TXdj*D+<0P?1ik{4SVQIbEHr9g>)Eii;?FTmf%2EY8Wk)`C3|O|DWE9-mr+8Ud z^E|xiw7mpK@IHF7C0JoluZ7&jx6R@6y$*i)wJ3%`q$8R--;OaEV1 zjsJHsDQIhWCU|_##)~vxRQ7@mny1-Sjt|t$ z2Qv=MLt5M*yG?JoP>^x8IvVzY(PlGNTG)uTEB7ws+i!mWx;g>L%9CZ0j@|skyCX4Y z6j(E$(pQ(^$<0vX4;CCUd~*pJ+kU_P@(qdtF1^?M?|ohbRKuBP>bk!Vz5+DqeMiNR ztVqh14G9#Sv2kTTs7MV3ct-Xt&3d6+PoI}7yVp~zx3h;PlQ#OMsHsRd8X-AN)(u{Rp~Sx7z-Qp1A)*B&)NQx9E46O0KdQ~?V03_l-ei8Gj^!5czKoJg9_AbRY2MAkaBp3*vfofBxto7Xez3)TG zibqg2`NVx=vW8pq^!1q?3lkJt7q~b6d9!HFtgR>O6=n#kP+cNW<{@|-BqM;o2i)hj zw+TmwkO~HoTAl$Tief#)QH$CG<d8Y((HE5X)SQ9^Dev_h*mJ}`Q z8uQ|=bDz{Szf08U3FX8{Q{`lX!_8QVwzJLLZ!r9oU&mv<)YU66W2u1ka7;QiNFm}V zE=OTd$8$7HD0HqHy@F4l8r9n3eqmN|DPo}@;A=xFl%VUvi}pCE?*B7{xh zPL8}d`)V5@2jGBE`H{8yz0^L^L<%VdfB3+8NsSjA0I|+yKIvmasEf)$wUwitabm55 zj{QRui=RNn(1FL0LW4@;rTX`!n-l_7U^xN0g$>88_cyzg01kR9-~^kOB8QOT%`pMb z$dVGj&k^&D%%QpcX5rr{n)t`m|108KE{#nj8F|yH6upe`$gpl=z^^L(_-q|n^pcdv z+Z*LTf^2G<*gVcY{e~Dg0}7x&`I!7teHX?a@hUE;TDRiLtq#R|J;)(b9YD?+Sn89? zA*+w2aT>I#5?d1uR}K%m%B6DLQHyUdOq-MBd;6x|E+SV_36FafZlC0SOA>+rZf$a3WJX((I zm~uzC52L)xbr-G_gkrVNztMMdWHs!j@={_ci78!kQR!PeYWz)GW{1R!|1}N`;F5sV zfigUMpfOB?lIR=b5M%Zbp|-vWFfhl1@b6E5?Oz<#DGIoOxhg1}JSxr=kW1I~ z1dyr31D+oY?B3Rc*?5`l-m{KM*4O|t5bz|wegXpC5f5jC}<M3eOJ?7r!W&%S)Q-}n}Ng;1laQCzSo+zZG)C@F4W@p+NwW9 zQjoQa@c5g6{a^)@mP0z1%(P;~Z!EH9>7mNIS?Q{|+mPNytxxcVH`3ALQ=czn?1;Fp z0b$;cRuc6+z_AR-0b-%`Nmz1G11Ag?^X|#Nh?(zi$kN;^rq%TyxbKtUBaHDSTr zyuL;{4rZIF9lIX@T~@wSdWjb~%??Pmb@RIM0RcovLy~}iKH*nDJv*N@PV2MH>}%c( zl#i$3kb{(-Jm0Q+@v(dcS->+cLW~;|lS&Qd(!Qk%66b|26Tm_XHK3C?_{aYWJ-Sn( zpVnH1{ww8rD!?dh{v}g`elCBpB$OGG!)s$rcyOCIcKYMp?MX4l{JSOR-}YW!OwV7k ztQZBxG|$3yjgg={ZzA8ONaa?hit{`%jP-88lOwRgzZayGKCi~s>pd2G!Jn2zWc*pEt z*EE-Wr(Hr=_KAtLf=XWABn@~wCgGD36N`RcF34Le3+Wu2lUOXJmc_M6#b3I@{sIZ0M!^ zzRk~%j(ol;*#u~&Dc;5>2o{DR5{>#n zvN)%RYRPT7c_6FWl#-&~^0+7H1=hOe=7@U6PL_{{J8=nFb7uUWdJ&}4=|Q!oVPfi2${N+Ek_^Ow3i z+8?%C#s;zTQb2Y%FWF`E%NGcn1MBApfaVA0dDl}~s*_Mp+AEY$$@`7x&w*sJbjUQy z99X)&AvQnZSy~w(5Z*6`c7lvoqyPgbY+LVw80gxJJ64cmh$oDUWCl?EZ;pOE={<(* z#*U8LVM~2rXN&9VPrbm7I~6ieV5vI44=fQ|Klyzr{|sJQv_%|OnSni--O}Nt6m`y_ z0%MBt)qmmEZjX8ntzM6Ts^Zg|x-T4vXTQ9nUONU1IerzV7z5t^>~84$i(IRP;ydB- zEt$P`M%Mqm6oi<`7RJ~S4e)ZvOO09hrAq)D5?SVCiKu6l2hh_&@h|$U=*v*i#!M&e z_XZsBBzkd#3 zoMzDRD#w{{wYt$~akn#F>Ag8_4GFQ(4giifN5GvO?Je4&+~R-7NHN<*0*SESH&u`$ z;rTTF`*Vb6|AXrRzyEvi8any!Fa7^^>iM&C^OI)!I7i!J$hvjTpzCRn4=p+9a`zLg zrx(T3A4jr!`-h93Go53-7 z!wW(L{Xh6>0KX%0mhrn$MiS}~80H~hnBxGjBRtMOPYkmC>Ye_kI!Vrq+RrV#I?&SE z3S@q>}heq1izTrw*LiIw#SmvK_EmC=M$`l74M4B4cyktP0#t1LJ$V1 z-J+XqO6AOD=x<-XRsRfq`_4>d^wj);#;`Ch5T@D>K$*fFMoM){);@afVbVN6UGpD45yC`g!n?Z|$ zxMtUQnPp=iOI*e<%*?lRd4B}3ytuabY338gG8o7Z+W$bUzhk0-pzR_ek!oz%n5hHf zfY4!ZeNquCW}+`M*cNTUtd^EbiEFS+L~wz`41A68?yiSe!~ikev*4(Sv6?S%K|pqf z_aigCyac;&x9REWb0wBoES7VF-zq-h(R6Ou`Q6ZCp8xXD7pVww&~T$VY2~|RLqU_z0=S`a z(xybsP2cP1%?Vp}Yk`ob-EJE&#W>{q=ZI-EULG;?se?+!=6x1E^%*}lI8bS0U}){G2U>ZzDpx*G7;|MPvMK220{P?y!@Xxk_eo9YHDFhDj29aJeqJAi~W8X8|{5pZrYN{#K@ct zaG*2(6SPxU%YwfDzv)^5UXie&uSf30whmO58)T2c;WP>!O_FAP@3f@}f_(qrwL|ay zA3*ldtDu{5nxPkb**vM#Tjs2OMcLm#zMmrmC!sO6HWOz&^B5{?K*!FL%gh3>^;dtZ zssB$~%JPf;(^LODi)9%OY3SWussM^=CDk@B;VOozfCmX3tUK6Km~>Tk??a0r`#5^^ zN#=A~>wvcOSIagzl|J8OtKu-i7~sjQWqP&*(S8?*rTE zKiAmsZ)MYO5fgEUP@Dy8DCUGrIV^H8J+A79HT8=x%Cp}Ou9Y=f(&53 z)Y6i)K_yPxd8VYXD%fJ)@bR2`6C;IWoLP^~`|0tX)+o-M7byZJVadL_SQGU(*ZS~x zCeW+`6B_x%O?#6cQ&xa|F{3P7bD~qv%pLW9Bvc$sntT(vuEhO5IpINl3+^D_hbQ7c zKbZT97MT1QJKUy6MJ_MNZ?rVs@7C9m>RN?Yd~$-a^+fJatnm7~VX<5Gt#Svdw0|B) zZJs7%NKi%>_2o~%h)fdGK8eVm5~Q@p3;NM(tXxrTt1MI-eAfbF`^-#_X=w~Fbh%d1 zDmc_mwmt|gc-T?YKJT&751wJc@KG9GF`Gtox=Fg#n`9BxK6Xa5u z+!k6p6&Zs~uRKurt8<5QSKrS;f#2v;^Zm z>{UY!ay**GbDlm;g2m0_b{M`Sagr!6Vfw+=K!YW+zAQT%3Qy9&;JIzW!ma-W+|IJn zQq_@%^A4@Rhsu%?Gm#led(_5xjHcuP|G1s+4uG@_tVQWbmp{~}Uza~ZZ?$`w5;`wB zbVk1{2()*AJxr6fTa2q)dWEX#77n8)i6J$lJ33cGmq!)L;`TsMOC8bShpkTmk+!1F zh`ja|BaB&?mt3hVJVm?tKH8dMxjZCkdx&O@dk0`58^L24Q4w4#%Lwur$(LP~u|fBW z|C}o3ZD;U{yu7@epv!%UP9U#&To)e$U?9ydlXEzi)Gr{EPJg;xUVh38$FlKG_M1}NNX3;Oa8cn zz8G!h?{^ zr;lEO;&sfmMXdFp#>i%d@*kDVr%t(1wf}7>=1<8W2-Nm!;c$287HYTYFhYj07P>m0 zSU5Yi`4O@pMdNB%O^lX~ckicuer=xsWS-abf*4M0dhQCvA5sRL_y=bzU||7^O?`qm zi+L~Z6rW-uTG5e_l3ezq)fCrC21lEWnQNne6mK*RtB!9W(Za)ww1d=0pL-#Bdy2)G zyYl8sBQVE@7N;6MD&=O4twX3lQVYXWPA#}dmaIqZ(hiTvPsxN|{|eMh8vPW`Pp--c zt$(f+OkX-=*yxKp?~eM&0lVuf+j^TdYp->q_lJvU(S{^{lP37P@9N#fHHsr6_ry^0 z@|vU&iEQ+Ut{i$XUfvY9RA~ViAZ!wvsna4q>|}6c=@mVY1gIUJ7qP(|YA?3s39O8) z(OWr&>qItJiZ#mHk+i-03p4;kiFH%a{mq+mMgVtBJ+NK@e8*(KlGamhdi$8#k*it*j+o8}HlyjfhD$89}(I4=7o;wd3#izYmyiy7CEV_%Q44@>J2b@3>G65W^mtoRYISj$ z+r&UJT`5^t-GFP1s#|_jKppV_w?jV6GhLOz~LRvC4Lw5I4@%D!YU#! z^tdeWi#A8_#Bl^Yz$F?9k7BXCHi#tXbxn_B=7M*P_7E!17N51Cru}u9zys`HoBn18 zlRC__6aO)#bxG8L?iw$Nnj#g|r!F35KB3d&cN;S8GF~$FP5|1|HAJ!6?@Y1nptCpB zjzKCW@aolc5~tFurHm9{QQU(I3ZBYm9mI z-X1-r-kjbQ{yDUT#kxV=1|x}JK@DQ+l>xcCy93U2aU#tp#jpK2aj+cCWB8c$FR~27 zCP^Jv!d!e0Gcyg2DdnQvO7lW^?fc@Wzh8Kse+Ez%mG97{9?$>W*k$_Hsbi)@qjZB4 z7SF(|eE6NY-apiE{>5Z-9BjS^07rGF$|}qTcW9w z5(Y|R3mdlk<={`@qe`$3+Yo^Aisl_F?Q_w7IB55p9{@Xvp3M*hcMe+a?=>?23bYJ5 z0v+DPp#vMa@;&Ow_7uXs*GzqOI@RV5T^oBCKc4Nf0mD2_$t}$Ddd=%hqV5t=2MPh2 z^DR1(XV_SIn?C*n067@IGPTu7FcMr;XI^88O@+dYRnI&#@s4NkQAEdE0gAVsn{O`U z7t4`WjSUZn>?14&#>X7Ey7e?I=9OnVRoc4#Lw!^!Olo2N&LdtKE(!^%_LAo3WmlC= z<-gqkEdh1KISty+_0DIxuUvZyh#^iLJ%~kOBkQY*C?UeXJcCxYjn<~~`>J`E^J|XE z5FFAxISe!*@Dt+VF7{}_xTJuh--UB9LbR%q&r8*EGi7G?o*2u}q=BG9-x`eyc2iGL^G|ip;el{C<3V8b zIgWQdpYEG~!bG+b(OEGZ9lzF|d?W^LWf0Ropj#85ut%WJ++Krn_OaEIY*f zePKV-V9z~V>E5ustPpv^$=>%ZGhmyh)ZFP>gWG zvs`>(!h4sDbVbY<*bc%1oEIGCI{PT5R$ndVpxW1dq>9>z^J` z_~I~byQW=+6@(k8qO}uj2l@Q|Mp)fD5-gndv};0fP1Hoz#yt{BE300;_HL#*3>q3K zU{|4eWzE&0Z?tWlA5J(ZG-(SmNrdgVQE7!CQZEPt@Pg>81}^&9^5oY>F_s3>&j*6! zdG*8EhUd^zjE6Od0nU1|(Ows)_5uC?SrkU#M0Q;f__J;AEUi=6z;MBNau+w(@N37? zLVQpY9cs~wy)uzV4sdiz&)`p4A&Y;G26k!cjcXd~l2}Ww6Lqn|;pDeGoa)cTaqv2h zy6#V*6>!%Pl^icGd4EHS(b&t8V6Ex-`!bNvM<=8 zVkUrkX+D$08^*GZm+j6EcPMf*%zu1m?Fo$_7e z(V@aU`8s>#NTUa}-+i6khkwsZuWC>;LVp$&z5Uq2UVFHFBc@#$=I;!?MrX%t#ZG&C zn|-4b<#X{!;H7&pMe5&S&>*IW-D&}Kj?J*U%<-d%ZE*! z1hk2FSHI3$kG!M862TTF9l1%h&S6!l&%}ql>>!-yJP>PVhh_J+B4OJ`7H-Ke`3)O6 zFD%x3+Tdr8|FGVAjhB&QG^=WDnk>a0<1&ZMAF|8HYjRjsTZyaAt;C+1*Vqc0;~m(< z9o7C>#LR^goPE$m$eg|DrY5X3csjo{4C_dS3JrGLV#gT;3n=RnJ=T8KtNgj=(+~TO zwm*Wgy?jGX>3Pf#UuXrpv9H{7=R{y@O|IfASl5T-QL=6FX#*7mTE^B*YRc-nE>g4bqr6dhd)kE)O1ZnpL;VYu1gqpt8?FHc#$f}fuOLc4ln?46BS zOWAaW5%geA^=G~8xN@|8-$U2?SvEwUndi4hOm!R|&rixmiX{pbAKOaWn}Efv-rBol z?C|`)Gh?1}eyVvrJ;v}B9 z-c!{!5Bwu-ZF;Lf(FsQ6`PH}E(>u2smV2q{JY4>KaPMB(@4OPFr>CbZkjp_lwLeyg zs`@r~Hf#SX@Y(3@4$?V~oWrF74WyEK| z5S(&5UA<%wX*)1G_{^K!4v;(yDG=ChU@`g7&LR&M=^WEHBFkuMz2<#@RT=BMrL~8l z1x_5*QHNZljQQ%kcJnRO>t~d7hABx!PK;*-G_@h`>rc%DS zG->AD)%kv>hs3MZ8Lci8k}ohzHA9@-A4~AL@+C3$bz`#Ue7<^*z}#Mqy6WlS%mhoZ zJ+oe-$-GFoD|KqZh}TjNi=)U+&ydRXD*G=!z0$`?32Xg{vUK|p6HGo>HZ&U3WO1(m zR0Rs&>nMa-glBt2DeE%Y%Sp%4y0kzb@na@o+z;9(>#V)xU97H;4qe2d3Ru^}@1ffB z%kAr^lBC{GuC>LzA=%KLsNGI>f6bt0X5?%waRlQ+X;3>$7z?BpS)A#D-3=8^7Bl#a zU7nRu&x$wK8S%~8%xv3hmYaJsEX_tB_RJ=qVi7)TIf4_+vkbYLxQM<$ov+jwi%`SD6g5Ow zwj~)viFn>c{j}iF&K~4_=aF`586FR3ZyQr7-BGG6S8Pj~Y*FaZj0KC|@8d4`kjZug z|F&*$pg{!Xz*z59lOa_4O?&QU%G+G)>YHSgb@s+Oxx1`+4IoevJ=S)YG2-RS-gX$9 zG}Eo`7V2fBy}xw=tPSH~M9Uv;KzPLN+g~}PAC+PG(4FvrHMbr>xY|Ts+xIT{x##Vw zT(XL(FS^I|Fx3GB6i2PID<(ya!+#ZzlYo`(b7Cb;naM8{VQSpt+nXd0 zR@mogc36))jC(bH$|}L;c<#zqDlJBYZE6IfT)soRggu!y*}734h2Bq6@v&U(cczeb z2xB+G(vHo~x5h_ZcQP&(G*ZQC;l}bbzZFNi)Q-Iwu?CfT-v_U`C9-*>mk@NT(mac83+mcK@x@ zj*CnNAitvd60b=Dt~C0rrmp;n)s|>Mu)L|Ln!T01r2qy7mMGtI3%^^8Y&4@|JC!;5 zKp7=6ovuSHEs02aEqeF59_RSxib@L_j{F z5g+Rd0~@G48vi68Lz>A748Kn@UcA|Vg5;#A>&p!d+kYh3DtB#%@PK3C<1jOzKAF_&`R z-o>Fr!fU10F4Akawuzr=_VfBlo4YGD5!uJo^&LYtG8KCf?VBS4jOL%Tclk9Qty%YJ zihHwvOVMN}R&0?!SP7HA61CF>_pXa#tQodufL~u-7GI-xahr=E{oN%QT+!=^kQ?N4 zlhQcSl(}?)+WSHMII5EjB_BO6&*pgf!0vrv*HfZjg%G1^hJCgO37W8~Z6d2oNbX4x<4b=KNwvyBFFJ z$?dUDV(PdifYn;1fo<~p=Zgco$645 z0DI8Q%?zOI#clKN*N@Dg1(+N;E@moSRg$-BnL&NcBRz8t)Q)x4)L>J>84X%4GAR94 z{2Jrx-D(U9jFaC%wHzAbLBED(JgXhXr`*N4T(A9AOEke}V3nhDmuTgq%UM?m%Xq4# z9S>$8VT<=ovpR*36T-QP<8@YB!@5B`L|_{Np9uxq3a}t8fYmTs_>l@fRF^LiNRNFg zW?F$vk6Ji<|Qe(4lGx{S$t-dElmVU7O-Hpl-Q8x;DByV>`p^rdR!{zv+Vmyy*n8GRSS>Q*ST&q=E?R5hYI~Y+{C7mfq-~r#zKW*$TQUj6C~+*;2~&q2xtQ zIa%nAB+QM+Z^5P%Y8r6AXH$fKFV4vvZ00jy^*|`*TAO^e^MW|&-&|Z23c9eGost4A z>EECp0H`kWLX!va!vRomeI@BoW)NE%WB|lZdS`ttf)fiX^;LWEMwLwm8lQ>*C$d25dlDvbR4n~vuXDEtp;TR8NhsGNZ^fQ9#X+VEz&0BGdh%BI;uDM34STCA=Sdd zlr9?wG=|LC9SLSap}x<;MVH%a*U!fTS!=ayzL-6vWwKH9c<)l2uRW^~EiYchOPTtB z4aQoo$;c(B_Bn;-9XVS0a06F1upe}UEk8VgaoLdH6*5ulrR!V|KK39Fk~-2<0aRi|o)H0$CgG~att%U3?wZ@< z^~4XZjvx1uN`NhGTXlg0sYw>!ZLK zHJ~~qDud?J|Lh{`BVQpqGgm^+sFTK57Es-o z3hkKjTb$?OBFVioTkF8H=Z+=RavkO*)b&|Fz&f8XVoIM6ta z2sI_0D1tchkWnITYlY+tjaO#Nb6U38c>~&vwcMczu9tu=3YROwRce*x%3kNs+?Rjl z7~F{Gw7uUjHBp_bjQZ{L?0zI?Td*-Y*I=1}l=!T*HRu2aE6rB|GT- zB|>FTdU|@(WyHVJOHN3rc#&~HButgK5WQ~4jU=&i1E#~To-6q@kbqfk@qUrC$n$C| znIZDBd)4zdy$uPZv?uHprMGXdisbHD$rPKZOWXB+e-*Id>rgB`Sq@)3SEHfgvtB#a zusf0~LwWFd$UAx~W>o5@+5pn+Sqho8acFT|KL1CV;x5$5-l_g z8^>^1dRx-Sr`9FNW&4M{GduipX_nzxusm86Diqg`Os#*SK*fC6-rFtA;sI-T*Cd{$7{}NjZgPa;3H>;1w zyG`}9U#zSKW}!b$jL{}x{svRPZTm6-!mfOzCnBAh-3mSXnkRB5Ioo;`ICV(!;KGtN zg%UGzyk|>oW$x`!Mk9a#fo7A)DD^ELZ@gKm4S-`pZY(d#JMzIGu;5I+($BkYrZI;I zO}Znlbw6+o+!xb$#94a1MoWNd3~19#ylkO1Xn(OQ1WDF#1kHlI`zo}1I$lp4$G|pE zx#*s?V1`KagJy?zVXvu^Au%2XIvv(ArgsCSDL032J9@vSHXf-#LYCu2L2DQWEfw{K zjfdw8?@i=Q_BqXIVFpi6YRi>H_T166@!vatv2W()E6AL;|FodqX! zU6zU4<`X%C@i3-xmp=#Uivc-!rQiR9U;dZqJDqlzIS@H^4>ht4wpFOmLfXlW-CdWx z@Qa?XMYJc1<0wJdUKK@q)E*%|6M5mx;ngBiaYsAX!-Aw!Edr;t&M4jvPZj$)i}V>c z%s={Big9o+OOdtv_hB)y$T#UW3F=#V>{dT&zqrk7{z=MkX+#L^$Q1R;RHvAOVu|Vv zjD4BvyGLS22A-@Cc0B&&lC}KJE9Y%&B>X(Pl3X;KsWnHUhRXN%GPgg$Sjaq%iHR}9 zcw8Qng>mT*4(geG)sUGCwRff-H>Pc&XsC*MrEM8mFgxPkFZJ%&HQh%82^$7<GPu*Omssr!1pigkCqIUW%fUN7*LZSo_Nsz^iZ2Su~qO3GTe z4;uH8IrG5it{da@bIpw0BodHyIzhWHv;E)Vrk$r&s|-pJb}J6wU#GnCrdZ#g_{{J} z>9~9EyKHkG%>tBd^|)8GLz)N>|*?$&q5>a=O+U8;bz5X`wV2Gs2d_o>Imy-E(R zHY;?(Z{Tjc0KW`3h|!Rxbai$N%-ihWg1MmJysD;RAfz1;zVnJ1uuGarh{tVP_OjhA z7r9PRKgxF8MQUZO>v)e}-G#7Rw#E;URhH=s9VgZVmb5VIUuQs;VT@S6IzV}kZ z_Rm!9#^%qs#3EBe!;Zy*c7DLx}Ku@`ShBuic$rs@BGR%!>@Pl~i1UoRyqvulQH~K6gB_AfFZ$ zEVzH6RxPB9a~M%xb9K|Fs8n`ONKMeO&+9f40tYpsCZa#@*ih{jf9k4(Vs8`@69`m; zsf3@%MK~#y+I=$#C*% zRCjT4h`|1IkG#eVkQ8o1{=L5Q%7VMwQ7UJuesoE42^(Y;_T#bfP=|$~Tz)}i zi2QDrHob;;EP7{*|LXa|%f0aA(Ny~!ojhgR+@acwK-nr2LB3KLe%W-~YmEbJ^O&UB zCc5##*UUWqVpH&R&I>@eR$#mR>K@{1%rm8?`K{F}Oe}I9uACFAyssAPk}&Q$q&7q~ zQZI?WqOdY$;TMw3rCRAu{E?{+G1F>4!k)3l5SH2o!ou^6(Mxx-6z@&{y7Ep@Cw;qa zu7upXBc4%}UK zQb_;3H=eK6eb92PnH%ymo@b1aXjhoepqq)G6%*C4;`!AP?nkPV9@ePyXPnRM61@5q z>*YH1aOFT}%{o@QqUpHlOvNjdv9h2ne_`rEY89b5z#u|HzCJ8qN%vROlV7$^(|XvS zMlE-2NHcnWpjN3T1as!VntZ;Dl)!uMu^T_Rgyux@RAQip8wCOom3Debl^o44*j{i? z4Luk+wEdtO`lI76_Jf#b3dF1VdA|qxjuy?#sVL_My!c#>jVfa}t`<>PnF!p3&IA$L zbouQUnyhxOY##lrV0OuYikDoi+inOaD>kJNbS_L>dKD{uqOLy&l%{W(OSdgC>-327H(*U{C5#+EiesWz<* zHWB zuH&hA9~&+7S-8-b=5(t?3Xu#pNYMCu@zU_{<^z{Hle{NnT>IvIN?a_*@Tc=K5l&HB z@BNlOD9B|)M(2hR8dXRGtUv0r{oxz7Ny9Fi71cKwhA4*1sD4mx(~B8OoSf30;X4o{ zs|_WFXeE&SdZ^c{rnAo+mNt9p^~f_^G4hXu4)H$>*Pz#nH=F9CtZdWEn+{Fu#lGtm04ZxFQ%#tc_oQ?2>7;dR8ek zjMx1~{f~W;37C+5)2yq__2>Vz@xosy!iSj)hg*K%92%r}m#eFVouGL(6Qi5T{VnOU zg9hVC_c9(-Kuju6MBho_9yhrUYbHGA?~Y+VutNQb)s@@`ej!77GLeQixSzm>*@P#~ zb5qmG6J(qRt`S&q;!EN;3?P6TRy$!Df7mCTSvA$NKT75`xW)H@pe)8YS3Y`&l3ucuXX7_lIYua}AdVjS7Z%~yevAk#EuHPmpz{&;atFPNyXp(Vd;^>yr;2c~)^(N^Z z*sycuxk9cDue1!eRuIb(x{~7>iTuek{>3 z>EMz-28|M5PB&i@lsbMBcslQ##2!PLVmKz(jRxaeI6-Y%Hpu)_;KA2lTF;7_$2&i} zd*$HcHx~wv-sHmO7g3^>M{9gB*X0?DCDzc@+EX_Mh&DnBSE0hCv!0E1J5Ks9N5646 zkGfFxhq#epszXo!E0?rz`E;27J?6s5v#yaW!X6y*cDF;WYIWFYz+Sml(~0uA2n#=S z99F*n2wayaMnsKqkUleE;)+2Dzsr|RLJ`DLZDjDsC!?JDuB$csQ$i)@7)OT_d{*GG z)*p?uD2h!`9bDZM6jCRKttYHMQ>_+KJ1?c#mwC421ee45N`Xv-NVhJxhg{2|tVMh% zmcEh;S?7@O5hh4cghZq&ek>7dW`t}!g7!^8w9*Mw7SbV>sg`;&xmNpbd4vIcA`QFo zVnmk=scBvD4I9!~sT`-C=x)?(-bz`i+*)kaPb;rIA=YpIWs`yDVGZABVZu!SIUC9d_5Hbu`JHo__$ya>{oyhpNjVTYL!gP7XCF0NwlXI zZWx<2@!vf9JY+hPFnmbid0L7Dfh;jxAgsyY4ux%@^Q*@H`Fq{9C8c`1_|%ue zf41Md8D<=NRsMoBDtLdeq?x2sqf&xL-q)!X2qa5PX{@p3O|`$Rn2CLHTXh_qb*65mO64HMv-rWa=cLd#k^>#*D@E*%U}Z9Mn5Duz@*~Zd-JQxYBr#TD}(6*Z_k^ z!e5HtWKB8nQ?7Fq1+QS+)WxSVPSn|#$`Z#GJPQw*Ts8G`w8m-T{sEq|ht?LYSPA5*7cbxODT|aH;AXnQ`Au{BELMDZHb~%}4Rg;yz?X3*= z(@!%dNY~ClTDQU%=gW!vf(s%F+#U_nK6Dozo$f62_`uEtE3E{K`s+SW;)mLoD zWi$XG-p3TfO^so<_vzMbJP2%a6e}v2k;+Z&Ea#3ONGf&72 z<@NsSP$Nv&J7tF2=siujv5L9~3Q5END72Hy$vWQkGBf2v4wLJ%WwFsUzop~|c;`=# z-&-ZU9*^wX@iyS&)?BRYOp!-_(XDQBmTIXPVh|XCAJ-bErf3kuVtKsNz7x!sm!}-h{Nnq@?g`R?`vB*5^o#yLG16m zC`*ZKC=0l))-ePRbd_;Z=C9-|n2D5!IQDlpJvlL-C)^g|C`vi2YldET6r+vjY$An+ zBI__6tOH|-0Zm*k|7gs92Ngw2qj zBsHhOIQh+&ly{<-6+REeBXp_ zk};FFF1sjUQ+64dsZrF@MeQddI4Qju%6YO}tUP_b!blZL$S$Ux?CONT#S z2rgvh6kX0B0?klm{TneeHL0_0)8z&~TCy<*TGQ<2f3Y-`|M)&Q8PRCJRH51ZjNjRS z5!a4>o3e2#R2LM%mlgBRU;_tVjHrt|2wC;2ki(W%YVshb4plTMDlr?B%bBDx=un|MJz9!PL+sNHsiUF@BAP%%sN{XZCnTnt4*)hy) zgGqy&2P*|JqAkG)CQ+Ls|ubc^!l!ua?W&6 zA;_+VU-Guu93#&)3v#{nf9B&evInir$0u2gwV)L6@#P8d@s$iC6ib3vfA{JoaBCIa z7pL{+EVmXSDezBh0(X|N6gazgW7fdoDY2aNJY|sba4+`= z1XMtH6~xPj@?xYjxH3PrwKCuxsQ}fWo2rvxXQ{P@IDjLFWdd#89fvBs#xBvX&VwWMk zyZe{70e!=P4nt%0n5yydM;or0mR)8i6=5}Jc#jtu-=ll}a-BEmOW)i3EXi^qJzZ2O zJ$t`Fc&LVwTCubteHk=F|Hm4Z5AFcny<%j_up(BIB{rNe?b{b4&3@o>td!H5smMzvQdk*&81zGld)GFHg4# z7=h|$Q5Tk$TXwb&hMXF>)I@rA`*Q9REN?SsYitjENf7oJWRPY_3blao%IJYCKe*{2 zz(5qOxqHrLqa-NzMT!9JCC)&V^W|ma(-css_@mv-wgpa!1ZSymNvlBEgS$ta&KJ|UX1A8(+;_%H8=oIUzP90P5#gp(;;FD^gspYH3K#X-V z1tYJw=3)7zc;U1s@!VW(dBnAiRkET{3^Ydr|GA@+`9GNg^{(Qgg zpHd@#d+RGLAO-}ALM+DWeJph9d=7(?pDh__@OPaLnYEc)cG}eCFxUp4qi-0j!=UVM zgcYs(_x`Db^0)KxF5`dl)%Qi~{qP4oIH`z{ioB~!<7h33I&V_Ozdj5_AJ(W-VT?6b zK_XR;L9e;}>7CbbgDX;>XKBHa^v}}sy8rs@c&~f&I*)E2XUG2b*0VDV9opw;GCqxR zO}rb!953nqInM{^%>1QL&39{JHC45GvZDl>W$AK8hTXc``-Yp__2ji!W7FH8?vA2( z@BP~soPB0oKcMoyWoEX`tnZ>+&U29NX9Kbx?<&;J={7aaqBnq#a6{uftKmS-6Laj! zDaY%oTiNU-=4(6zqafjYW;v{KVrg@Sv+rM*+|{dc#5}7ZGNXdD^%jmjx%21YA>-er z2|g{;-8y|!#tHqrMJHhF`87RaJZZ?ymG70hq_Qt8qs7@JgEb)A|A7#CTSIcC^FUu| zK~5@RM@>Sqlt{=+l^;Fxd-_Cz9Mh9Cg2JJvBR%vH!GH7isPpre{%7u9p+uQw%GNz^ z>xrtjn&vqTzst)EYw4N4R8s^ZIbQ)Khim@1ySrP>?;*`-2VajQQeHnZ?J6*E=417~ z<>50~e`?DuZoLc`%`1N0eUgQJp{;$2OPz%b<@rt*c#O+xQs7NqbK|LXUL&b5;K(-Z zG;e)%_MxL$QPx7JFGJ-{x%z<FeX4pl4dohZ05V;QmWEU>c| z_qq^_NtXe9BQdsc(GOjgKccIL4)k7xkqs!OxQTPO+Vso)nc$rZvASA9Y!O-VDBKMv-72@JYK3LV5FK5O1;n7 zr{cTdX+su=V^eMBSHR437`ewS*cD$kglDstfwPtN^C_ar^Il@th4O@6_t!qZCM~&D zURH3*F_>?*<|NOA{#r0j2qpBsX>xtxsoAP8M(GjrT(wXhu6&@*$5Kfbj5=cm5T!eR=% z=4c*E{6I#%ex4R;TZa=Ep_q;H=?77r#TMBaORR};*1(4Go7K7?j4UE3$c-BNyZpx{ zp=p~qLkUrm2 z0~PKwO}CaZ(Fjcx-j}m1=eDGqdZvt@b@Y!!h(^?8o#s=dBE%*Nr1E6oPWZ#)1R+YN z1S!0|n>B?@d>=lt;yMy?@Q$LyB-uF4A?q;~qPJGF$`lI^q-g&-h%>OQAFWKDM7#Ue z_!!CpAWH43X5JG{X#{Pr3ReQgHPvAjpd(BJ^wi-7R6WxKbScgm_fuHLzqpaW8b9TK zX>t^L%Vhn~IqO}f!e~<~#;2y1+}+oOFC!Iv3+?bhOAUH^pUJMC`dG|wWfT=L$Uczg zD(nz3^DOrgX=|oyj_TBSd~b^hng(}$jSF5rmP;^fNvEVr_;w^i=GEpk+GSrQ5m8;D z>_Z5NBR_`9w(c;bMIS_5PU)#C?d2%!nej_*4HKim6(kI(TJ8Kp1r`N}&;gH9c*VEV zFnPg2r>k#p7pS*uz#S&O_5n|+9!t$036r6>ve)qJ$Q(}M?@bdGA%B2ZObEU1NLUtx zjFvBY5tA;)2mPyG3ZKalwBdeXUE`9@*@tMDm}974okPv3r2Azc@HN=1=b(HG)ZL?C zwdY@nj6WxBpzN#eo+2(o(jrvC6p0d!AJdRaPZO$gRh$%TKnyJ$Tbutx8s3}Dn1h7O z`Hbu1f}?7~@^xm13H-wFSkb0s>Gmy?Vc?)FgVmSU%SpVenyf<%V~O4KCEF8JOo9*7 zTf?N;;#Bd{!F$ct(o+p4cU~h@N2xc}Os;Bz&@cSlEB8sx1XeeQ!aisq`=^pqT34St zQX@3Ey=E%>SeAcp?>p6p??5ueu%ya= z?;L;K)X40hQ65?yF*$qnn|jvDM*B5tH@K=3(D`;TaSN@r$8+TJ2v=+F62`2j1Tu|II+dHrO)9Qzh zMJbU}4_Axovl_>GKsOJ)5D$_lDyV=Q*}E3BE8+?lXF##D(@Q~ovk4?QdDpPDhC^}B zCX-zh$`Wpie~BowuV?;5c|vq6;XMgCUKsOrb7XJ-LoG{uv>YMiP6qPZhmpHyDGyjY z@2KXfdz;kw71R6DC_(};qxS~8EiGJM-X?(+bJ(Q+U4xV{o*UK@6huUYJx}PN<(ny4 z$YE!nt1R9L9Y=q_mMUj(77pFSfToa$19u$vlJjnqB|zEM((Uw!y1v?7d|^-ja;X@9 zf`t-gp109Re9T{xkJxZFLb=JPHm`p)ta*-Ev#-CE=Ny~L<*gKJ>AiTm(qKYGWvU4y zz*3hGRw`hlIZZ}`yX#J`WTM+-%#n2;|Mlb*Wh@Rs2x1`LC}}l*CA92E&Q%{{XbHj{ zMJc0Q?7fO8J&!RxVH+OeGQw|`%ffe6wo^5>KN%q*#%u?1e5Z@DL)auo_Bw_PRhRupA{T3_7lFVzdiDX z!nz#l3vTKX#+OhhnM^SidqBnM`<-J{3q1(YEeW=KyZQ(u-xOp#HMOpc}0 zM6{xw#|YfOO!c^Q*6l^+YcFUZS=EzRT6&r81=~uf@&0sxW!uIuf59CiI+dmOtvy#; zzW<@A|KV+W*6aa*KB4Ry%gZ5a-iw_bE^MGsz*uX=J5z$8b_5Xl;m`jt{2Y1sg zE{&%Iy3szp>$sPIZeH(8=pQ;o-A7xe*VF1SCd}Glg6-0t7aL=d+%F^d;EuGd66R}w zHI#Z%^UbJ+Ua~>MJKRp?=+ID&LOCK1!;R3X^F@IUq+FzS9^5cP-Sbk03fx~qn&}{zUPblQ z9H>H0N7pp6C&Lvq{h+Wn2L-co7zUf4!xODR>+3zfT&(_~1A?fC-yQvi9?!iA!4)5) z?}?}mKJhnqIYH3k-+0S!H`^At@I$w=gZ(qq+64-1eMw_tWaQ;>i=H^Z4DRZ!pQ;Wy>8jaO?@NNQI<<}PkUQgP}UZ z>bqgHR2LqYAVa8y1a-;qoHX{vmf)5-8WjA}Pd1g@@a;XU1wDq-cZ6vrnQNXUPbn0UAG$@3{Zi*W?!YP? zAwBx|f8^3~Fl{{<|HNSq^ZbD@AhB$6{3Crf8dF@%Eebngm^aT?l4`3DO-^mA?tzaq z2o7BC&EBk9+EOAHj4_~juGQylgE#`3w@{agEpu~Vq4OR(DaEsOEh7MxM(y8Ipkx{L z#Vq=(g)IB<1Lsbe2XF zt}Ix=qI4e7cYHeG$aoRfXEqTYve9F(WwXI7MumrD#vL6ZU%PMO0L)(h-Uu$U?}ZIgEBC5BX+z*`(D<|mepKCKx*%odQ-{L&J)=Bn zY~Yi70H!1NLhja7P=As6*viSBoP`g$+ot!^#KGNsuaJR3LB-5m=7ZHuG0c0)d1u>g z$mEqPawdUfPGT>|hkLpEUAvwDAh`~p6@5f^PxN2-3G^znBPVbK%RM7gLNL|ec+$KJ zAUw}M(S045wc)3DD!AYRdY+cGEVVRW$I`aGv7w+rxAGf)0(bIbN>M$Q-MQ*SVwZ=X z>RHmhcm2C-`+K7jnF}^w5iZ(yc#T>&3t0n`sCl~_pRgYp1A~nx6w|{UG*Bfa@`?@( z%JTiXDWnMRdB?R2`qsVm#|x$GSDrloyIJw-=#bFxxh$b++124fC3^oyx&?%I*&b87 zNHIV0z0%sS4%dJb@&t^*!2egf!j{RZ^QM_gz2F`X{0{l9&7g8Sp=P9Z(|)w6PdDx! zYdQ$2K7NCxt9#W~f4Xz^;WMW{0bbYRQ?}7@V{VjMj>ko7c(#`Y&Iedn$SIsrlgPCK z&2a}*EHC0()}y?ds{^hA^}Ei}eUIksIhi$#ZhLyA1Esmy>ZG*4ZARnjDloo(sL8+$ z=|nJl2YIt;*uLG92k6MEd(IE6<_t9}Nu6{Wv$h1Ak*WaWVVONX`de{( z`*^`P@Y zPYgr8Ws1kh-Su;( zMI(Epp9<``ob#WN&!zic`uhgHs8&tBztk9jhe{ zNG$Jt;Q0GnGvGKzjrZ704U!7@YSf|~V6xR@B+1(p<-|n1kmj}#`hBt7M_#sTk7$Lp zf?Epz@jUu zs?2Keg4)>F06M#iby0{kmNPogH2);LG~d_!l1|-s*^^yBj#$m3`)l|jxWF!SzXrZ*(YEtwNRPWr@h&j=f6 z=+wm#O6r|@Qiv%d0PqcXD3x6!fC9-LcFdL1=3C2C9pS1~{_Z9iB`Sy+K_;%s^v*{~_g~6X)JZAd(=_=4CJ>iMJ zblKcHRuluGFD zQ>B}29n{*7lj#(v^38f6bhi=Su>^29_GV>Am2jP4Q7E=M82{3!+lj#gpwc$fp{98b zf{$3EgH9+`+s^yFJm9+m@>@o~7ti>Dh8|}><-cI^u)X`rXFkq|cie2b9lwUz94znr z_#H5)$E1{W8&}>zih1$X&YymO+E_QdMK+q6aIx!jhW5Mtoe7-8T-UX_``fGc9LaSga_CW7SMw0N-`eDNQA?jO1B5%#S|IEvAeO;-=UJ^uY z*=Z<*El@nVqHl~;_~E}f3hzvOu|fwL;mh-sWYe@sN*3at=&zb)x{q{o1Yiw3UQFE; zMFs^JaD!K4E3;2ALgl7>jvR$$aT%Z71h*VPjpyKD3;z&vVXK(RRF8$i^=gwrF)PSe z9paf*flL*+&0Y$BRpz)rm?C+pqu>Ps^T01^k*qXA8f*PM@@F=pzO!FYL%nB)e(i z%PTAUNqE!uK2%-C=b03@d)l+K{t0(eH)HI1bjiJA>?GCr*DH2MEy8J79CwKeRJdP;kN}slOa)y zX+Xnh11umZAzo`Ix!k{6JMl8|e0=6&D75T1&S#cPck%1MvBi3F;e-5ymC5HjX*|pb zc4;mk4W!AVd2OBQMoN{YCDfm4zZ0DdkD?@qExt>;UGtr{Ztmav1MGgUT<~cSf?2IO z1Q8bj5-Q&dBgpTWz@`$+nhv5q6db$P=()B+$H;Cb`*cy~iu4}N_){0K*lz{!-_$gr zrhb<{Nev|)YDz^Fzt~pht^lk6F!RDlAu<I&Amytmy!0OXo^!3G| zqKO+R-{B%i&#R-A$7C*g5aZ;EF9yuaz$+-oxoB#C0oF0|MZMR&`7=4C&mDX2Sy)~> zZf<5OsiJ9~sc|e5P}Bhm@B?|mc_C{#RW<16dZRdNa$4kM)ec3F7yv!IKT-kWyqOfq z4P`VIE2JhVH|BVoip?%B{4m_r)7ugLW>GXEgiD}b<=_r?jCs{0A9YT#!b;0%(>8ZF z{Q)QkEh4#qNdOu-7A=m|+vo>aT*!WQkK3uW*mLbFWou8d_gL69Q!@4!Dc0V`WX^^` zHL zH!QcVjLKcvit5-iQji>SNt@9ock6$ab@~FxE&>Uuy~19C$6*P|_1emUDiLi4PWrF# zt5y8xrq<@)>$X-cU#1a&s*T6iCW|#N-o|K}5p>XJP>8cg z7-0)D3k;GTe*!5lg#&_`o!EkGh)Ix_0aX-J6&|~2VhbZ^-)y{zg^yKU#<2B%D|lOw zk7rJ;Z=`tpIpX#xAC~p;&_Bklzxwt@uwD%>q)-r1>@sS-HCE?S1|Y02?Q-`nU*0R$ zX%Qo%uO~6omlf*np}+MDxU8P$wO_hC)OqHPAMUGfd5ZV(fI#qL` zp8|AkO&9G9fokZ6bYJUV*eTcz&MVkSc|&sMwc!n^eh+%LTp8)3lRW6tg10^u)iMD) zsd*`l1qGMnya2KRqxUO#@eH_jfP%c&zij(gzF%35_uGYk{AQK=e(%k3ZMH7BU=eB0dUCtoZ&t}X`B2sVYk9JuN_%iX`#=$89DRWjs) z^~8aHS-VmrLaE!ybWsR23-l;?rT=$i?Z429CPuVQN~wk2wvPU=irO-VhEY%~+p+){ z4?NXU=yC`R)i;sv>yvbrscHsV4fZVxGG=e<>oqy^c}_QqAcW$D-#&z{St+KL^h1)*tqWNnVQj6iV8!h{e4;1 zs!vmAdaJ^xqJFVU$R{}ZUjco%yNhxIr!LL`Xv@M(-e3pcD;PO-X8CdF;@F<(VVx=L zB0RwUoEcbG8OCM-8RlldDK!hYkF*M`KSY3}P||Vq&VpJiU6L+(Jdu*k^B90*Wa+-Y zLh!)0{{UK_14+!Dpa0tP0d0>rbGMrsJ(*3{jimHs?zhOSmr-*bu0mR9V8rVd(z7+Z z^(tnpa>}2$;FGm<>Os+FR}b^NjduiCt4kw;OaDkTXG2IDmd zL#kZH>RNcVlXo2cZ)jV**Z&@|gFpVCBNO+F(v|)E=XLMGc!_3c*Xy@?ind;P!cOT0e;-*`$xL>lc4tLF8v>WdYy4z83^Bh zpK%xXZAD|WW?4n27Tw&woKeoYOS2#7lErrxkcGiV|3SM0+m$@Io5LVP#~oncDfee| zK(3=mR#fVxz`oZ$jGPoCFGYQc!^^JGPcF)~DEwdqVN`W!qs!|qx(S{dW#bLe(@@?{ z2uLl6#(ir_7#W|@K+RLvuZ#e75dNOznq?Lt1aCwWZIIF48Oj-&rU zwi=dtHcSMf#11}IVAHonMW~WUo=2eGtc(~;1U9U+l%&fGq^iCRsygs|YHlq*4KG#sw8(r4Bo@YAK1oqY?NBWD%+6##gy1C~$H+){)B%C_;iyy^eLWaau&;-n7rf81T&KN(|5_@9{oL%Paw zKoB?|5!;`21Pp>?i;s6!QXifNMM?aWTA(iz$l{`*`fw4XjwC63PjypIe3J(eHT4 zwCoSCMkSL|6ZONy3vHy7-iULBIh^DTb_rHjll|}~c=6_&gv#a?MiI~53IvRCDQ3{XwM|UC_4~DWPnr- zO2qRtiMSPiSvw8rwX`&e1fS2Kq-QZ{)j^TwE6aw$S|*t4WpnHPCRqV~93J5$EH@Pp zl+EbG;zBLdZQUBR6p6N4s7reib#_0~%*OHMZb+}l9EyZ?J)1zc)i7j~u^jC@I~rKw z(KD@GbNm#zlmTnbvhSH-q`!9Pm!~zidY^hZ8oh_#D;^04w7TI&)DU(Cq26?Tf0B~s)kots zkcqSpty{)H-ECn9L1wMLs)H@gK9u+V?)aj{3M_FI17WB_&e{ItL=XI0qq8%8KE}3O_&R0coIB>=9H89!f1dA_E~pI+TJ_0TZ%*Q1-NCQv_4d8 zW*+iKtLNYlLcylwSfoQhFL}!fCrW{>e!m)#g1;&CsPs`ONfAE@7Gq%zJ zR1%#Z-vs@;90PKvgU+F}PwuN5?*#+RTtZTi&h;B{1PR|@j*LKP){*WwCh`ZTX_027 zxBq#0eQJLO=09f`vbh3rsIwOmoiahxWGU2_CcaYo2|G0t zg8C+#8aCE6@ucg3W~1y~d7m6XhF2eyedt5bu6u0^Pw$dm^5Gl%6+5DTy=AD?nkb|N zMOl|b5b%dXAcsR}37pMIM-m}a??jy#Tk6A-%;qFM$f87l26IKzcnEW3{*Uv>=P9)l z&bQC3El{k+P7PTOd_g0q_gchiTTWba|BivsJVPQosE11}OhY-uUo-dgmooFMymSnk zc=0A{U^HI)-9YRZ!dqbjz(?n3MU}HTO`AdQh_&{d z-4EwECvpX^*gVRXc$8gjbZ8+fiL|4(40mUc4dv)mr4GZq>Y>XQ?&l4JM#?IuKcpXIe zRvQ{F18=mAsPKMllID4Rz$ip~u8U3k>KDe?G9DLmIVa3$7dvva^(}EWtamm{W`g?x z`vqDkcJ5Zx@#U7bn&AiqnFkj_BJpd#eab1zyE_=K14PWH7t!BBmO z8usymbR3yKS@r;$uRjIEJLD&tf+06`lQk#Cpo-QKNSbHdhCEROVNd0FeBVfdTsue$ zQMh)3$7rm7Ifdaj6J2?F1g#c3~dPZ$GV_ z?5Q0M&2Q;INYdLHr6`xPf*wP$d!`Pb+%r>&YB)YM8h@m$*VZXS?=gQ=0_nhVXr}}y zGjv<9UsIQIl01=`K-H`h!?!VR=E7O)M@?%s%Nd1zjl%TSO+REC!PZhi0%NImt>zA- zo3`SYT0V3pXFUumigVoG36D$&Rpf5PYR79Ix?I|{8HU+}wQhKF#!DDqpKV~z>kaV4 z?(SED)R}XNJn4+ILVD~gIUj4&Z=#yH1tMe4j>PsZ^3mA!VoT2Seb&@W(B-`gnz7k%SI$4d4eiT9bYjQnC2 zWKVG(`!z{v=*^p$6{`~0A67?oW6>$rZOdE6QXG7tQztATv}7bZU?;83eS$6I)E(>H z)?7I*y4K$Di!Fg4uF>e`7Ilk!h@mYa@22inm$w^nNHh@aOa#*`-LE6XDN9iQ&9wXQD1 zqUJ-TF0mX=&Nr_gdf>UdJJyhejAOb~z)wKLh6pY1HXq-g%96R1<-5E)rJ)G^rm!K^ zNCl{@w~^3OcvU|i>pe}&4Y7t%l`4^5s@-woQ=H_rw$-XLF$wUKW##FbbrN(O;0g}~ zE;p&SrX7p-R0ak_5|5)rK`E{wBZ8bok}DmzCSm~BYg^VdlZ1!+@Lx$GPbYfnMnFXJ z6E;>Tf372~VqN3m@kdhShX8b5F1jY6cfHlGO#?0y&s2L(c>Ccp0ek;0KxJJ)0l_@T+s#exG0c3bcH5({_Go7?DQVl=#+&p-#wNhz)tyRx{O(Q1Ojb&TFWY zd2kjp>GstN;uB#iwldhU<0FR2j2V9%tPv2KS}^!v!|Jf3k8d1P{b7uM_I}Zik4;Dk zCM5R9+?KV4?6Ffntr+N0v(WXV*e3(#yIvX#soCO5ddCcN{cR6|I_7ocqQH==0abmt zMPp-P=<2Gr&<7i>KjbC4)^u`OX^HuT)JTV*#rwI-$Rk(`6d_5S5wOWy>Aj`b<{Ofs}Z2254jE1Fi}NKP$xrnUS|WeTlxoEmXQ~Z5+GJv*j0uv$bwZ<5d>my_je6Xq>R2Qr${XE z5a+Q&ww!Y2=HM&Mt!caVwdkMPlD6{g`=QCO@IZja|EeHA{$5(4xx6sETJNXSDRDd? z+Ya81l0WjXD7Y*v>X$1rWNTl*d;B_ay3Z#_0tq>cr0q4vqzZ^EX@0seUnJSHFvodk zwul^GIQ#Sj4EgnxT3|DFk zfl+Dptiuo|HaXz*z{bNK=MF4$%GX2}(yaTuDZG3u>^H0oUa?U23}kETyp({*D{jhA zi#}wHTNX=)%xgx7=I+Qi8OnchA0zA!eVc>v8%gjJdj{4v&4Dz`ul+Hs&Z|`49;=_n zx`S(KgB<5WR-4oT%HpohD>c;<h-_0$QNO;iTa`SoxWOvA8e)DOjk(O=}sdicO6neOf8GiVZ z9x9Rg%P0JidwM49`XlJD@|Kxtnx*h7oceXM0Y{dvb?inGU3u$Xo7ijzMk0}T*JK!aW4F~|j-h1%{_&G=EndN!JE)J~GZ%5x#R(~{9X-JXiJ7h(_GoA32i4mHGVaft z5*4X-Hcs;RJ8jDX8Utt-X_t?Bq$V9|zD|en8=)huUm)79J;X5J=T0Pk#{iWj1L>N+ z7D16fV{uW+G|!8^m(xCda4IW_l0P*6TQjs^$R*0f&pe8suxIbeyFv7A{3rLH$^j?) zR!NFW1vBq!h6WTSSX6xQV=~3_ox;P}6}EI_i4TXUXg52S_+IxQ;hpp)J*NwAT;wt0Qf~h6i!Bp5pWP9lZix zQKyBw&vT;Od+HqxzH&yk!9v_XpZT9*aUVv)i!9o z*%i;HS9fjKS35ji!sl~G!HtOhT<10WrN#VV?mXd-+WESTy&keGjMdiFv%S z7x5DL;3iA3HCQ~>WVkX+%4=!3zTX^z{!RxLk)Mw{jrOEhK0tiw>7azXLHNMZg7ReE z?Icb{i0v!d41?5-Br5ezZ*1KqL*;e1)~7+*QMj2`K23oa(DYlZ^u;_mda)!$vh_wx znzX6a)@kdqiUUQ`A%bh!Q{byKP#Kxg)i)1WffDH4wDTU}OZ3@}Dxy z;D#+z?nQU_=^xCyyBvxkye_jr1?be-?6MqG{F1PUqYi$!mWbOhL{WM)L#2qnIi3@9 zxi@eHMXm0o){g?0(FZ!K`8*#HJTQHsUd*X1?wMs@3ZXZFyw z0lzh^_dOFoHVitWyHLio%$ z#;9yY+1juU%Pb{`RhE5-Uh!*JcMLyyBw|lnix}{VIK0&73!_f^t(%rC{81HOzX_ZW zc?$V!WGGQ9#3Kll$TU|vcr}G%&O7UWt!(#i3)8ku5dzQt{V)(EQ(kIKnw=^N+Lh2I zumu!Ng7zITjF}g$=)Kw`S<%)^&Rp@^sZ*5Zn-O!fvEhjHk@IW*#=%!G4!U8@{J(Y3 z#rh3VME8Fg5lDUso&V94Lm0@VUN%+GJ}t$~a&x8F)|4tO#7&2_dBSSxnAju0FR#|- zix=%Ob{Cnv5@l^XD>`jlA?nzLLw9_z113sQ_&2Am$M}0?kTSx&b2AtytD37x0dK-B+Lm#Rq7;X$ur~NlQwfLtV5d*>>XEZ4u30+EFq82d z|M|UpvQ+&dtepsC65_~2!WrgG)=eqD?)JterN}75$xo4CTiu@3!#<#yiBcR>V}c6R zgB%?4TyBz!UdLEP+-lW&G5t<-EDjk5%C$mw zt(BMN{4s`HYoo-Py|rU~TZoJiE9mTkZ%7MtYW-&8@VzUhSC}-^=xs$Y3Pz`uv@$u* zLz#!^VB1slnNF4K+fbxdxfm6GZ9)WSc#XBF=N9N0_Z&C}6XrjtISAsdW$0Sciq$SJ zBTUU|@Ku z)8>9wJ2n#e`Mu`E&cY(&N_P9WAMr^CS_zAs7GT+8ZIuic zpkqlrMQCHEP8mgn=XfWCx8)6d4pYfsM*J`4-UFPWK=*}Mjaan5kZg^ zP)1P^P!SLiA|OqgNDTpEXGR1pG-(kL5b3=W1tCfcgc5oL3;_b61OiD&@~;hZrkpwF zyytxH`~KgzuS-m_leO1gdzEKB&wbyIW@vQep4!(Afu4IULhvZl2cw?BV)AAwJenHG z_GN6Y=^)-k*UBi&IwVYD0(8Zo)?Is#4(veM{z6_a(x{ime8$kOvi#KRN+GMYM+F6b z_~9e75N=ZCa9x#Tm-;s3%ued!kf@a>o{^anJnCnX0N~RwGh) zBUKSDN1`WiHIHfz#Q2z%#K3p-KfYd-w1gpG>?j4jPAqlcBm8oec&2TPn7s)ApgZfAt7_klMZjU zwK&VShb9XE$DjP< z<77P}_RUDZV=@_8G1)j3_xOEPlIX-}6!8AvnbUDHECc~}Z2(Hp5&Y%W{zqWfGiS%2 zoy$71(G}LdrFi_NMr}ow97`g5Fkb!?YrIeW)coe-S*bC{mCH<>zV&Qr zDCW!-gcN!mlX1(>-2_Ebj#`8L$vTU(-X8)RGvF5>uu7q6*j(VuG754BTO4X&v=ndl zfQwwmE5&-0&!uJgxD=g$$$93SelIqR+KR4ts4*1^K(_mPi_wH7dTS0PXj()Ll$;0O z9=GAihKOwIczV?|-J6RUh^v|7*Te|{vehBXuAZ0+Sj0XTE2;IUq9ft-@=+>aCl|KO z1D{-X=llH?x#5f9_S^OC_U47tU(u$&uP`4844oZ_Iqu}A2e*FC&3R%2(31eZ-o>e6 zONA6E+20F?7u2w5??PZNSq0SOHK3U-|2{AF&5vDjVGB;^8kOE5aEuWM#OLfV`DZnHx=9(GW zfpj}oWMm0``o4|(o(1iWKoSnyc!2JwV=YmgT{x5wb6I`%{g{@V^twAt(1ObGjzIn9 z_vpw;{N2!baKBSpp-BO^*wlz>bpx!5+OI&M^j~&p6xbYb?JwCU`A)t+@TT>7=egJQ z(2$)pelWJjw!qC&H9b5$$u-ax|FP)Shbiyn;f_MjWVOZZf4)pgGE3hG?L4XO8Vupf zD+d<2GifmXGM?4bmS*@eZU~YY!Q5tlJaSYRq{!@r*i91g>D7x&w_I00G@=Azb=+y+ z{wo;t4>BfmO}rn2XwjAP`RpKm_)My~QqY|Z>#qAj_^}B^l>&#x@3Eu{z@OzPHtXFsMM7O2;Icg0@ z%ltYNlAQ+95N-Ebc`rz5b`ff_Fv+3F!^T9^4F5WGYl|Y$AdUq9_OI-%*uBE38OG^f z?R3$Q*M1Bg4IZ?Q1l$JY9aSELXS&XMLr2j1M}dIHIRav$2ViBlsgHkI z$S?{#P|Y^UZA}NG=u4X6pqC9R5~z}Ntf`VnU%5e<2Q^*C1d&!9!T94|0cB(np4JDopwT8h`y z@G*SZ-?qI8^x{=n5hqWEBba=Gc(6CPl|sMt2=1UB5yj_>PPlmlc?5h8wNBcmdHp5= zWVqSt2Jq5rS|`WHE8ye2(tq(~aQ?3hC8)5!s+%}H zmv-hbvk#j*lJ4hh0^_9ZCOc{TTs$f3T)b(np=7k5V;!4Lac0 zYpVeC3tM)#$JuI{jFDwG5X^!FDZ@-1_NcdkY^~5W!ST<-(`)D3{;B#%+eigzMOLeb z?btgzJBfUATrxObND{636@W>gqwNsX0s7lGKUv`HtyEm6rq5Mds@m6;xCN~en!#6; z&;JV%6L9`Nsa*E|1`ulL-!_y0{vJHT@c((pVV-~cNY%!pw*fTOsHq~xk+MHjEPZ!} z;|)pygb(1T0VA|gQ@>yy#O)`BIP|7Q%7i@ywaL=Ym0tnq0DzdziVQxO=zrP5V)r|U zh!(ZTBv{fG=T(HU2e_K<>Bc8CFxYxxN| z48RuOe1wO#Vw~M4nUpBhsf{>~fTow`sL9TZ2?)kKSUWneS=?B$5G1SW`jgKPns0K?o-4p3*N+v+{HjzPGE0IrL*nbIo-G#{XG&8!zGS=Vx2B>(iD zo8LGm%CT+&eh(K(3TbraJp`sP-Sjr*x78AK#aC(t*U>m-o7&*RCc^7DZx?B#^rb|A zFU&%8-DQDHR)x1t1V`_Z&%mGBI1JHI&E;l3d~`W|U1smbh-j+rFDwZ5Ews5gbTkYu z_2kONSq!hW)ShycKtYNliUh=)|4V4f^YFyGDExzS8#dhuGT&HBA_HihN)@Jho(u%c zCG_MLR)hM-eO925pb?h!HN7C3+N4C8+a(<9o<%al^VI7_``d3H{LN?ZJ?)2!4~9`)=+FEbm7&!cXoc;w+81 znNwNG!7g!#F;zdhdf@tT7VSdlDfGr}is(FP@RqK1 zU#e8BUg=bqe6!x(+Ib+`!hdbAzNStG)l_1qRXiL>n!)rZf?$CdYl&-`p_Ni4dhewO zJOodT&=?&4l{FrA0-~MFJ|s|pqEOgCvDYHN)L>0d1XC{+f8uC41ga`Ji z)ef0-Cr=)P0&)pLM9UL|XVj|-s{05^INl>kUqlEOfCQd%x1PQuV~cBwE1tQ8tuLv& zTG}A6uJ8UTMrz&lov$fPg-y7S-RDVpk;5UxW?xQ zBI05mi4LPS(*HylkReFt+ADJ;_h3X8FkCs(R;M|c?5|DP<#rnNjL-Xu6e@fph9ixU zu?E8Jd#hG7m-dJH`IuusT!@m|5IF9^ch@O{S@ry_LefW37IEsRGb&P75Bt$eF+e0F z54kGZb=0D%(oZ#9Uss(AQG`&y62Pne;>D!3>91Ho8^}m7U z`Yx+o<+VzeQd6x)%!eLMbfi;LKb%$_=^D&33;iLujCz|8o1f{zxa~Ji`BLM=wKCn- zQ#s~GNoB8jL0|nkX?z6}fQ9a`c21=)HNQSMhPKyYNSOKUoq(Ti@qjyep|yLZeQz+( z_FAX&a_!MO>5EK??s4_jk~!(R=bNG1Vl+y0hc0UtMHL=TtF|Z_S?$b8W-`eh|gfJ51?UORQWiSC55dhm(cBs6pk5uPn>Tdp*8d+bj19Ecfm>lgTQeI!SM zPVVuY1gQvA6H@u-ibEe=Jl?q;1R^<>fv6vtdnUP%-V1{IWh9n2Va$(>NX^QzMg4|h zT<3P!0*)JA#6(6c3J~fUfE*(K&8U-s)|JgxqJ%^I!a}|>O!C^CE8SngpS63K(%r$Q zXulB~r^VmA~ZfU#;Nn%*D+gLfx!m1ipyW#$eIb*((q4!L5Ak zttWFs$Jx~@`|q8g_slGh=xogwzBDtJ`Y_;QiEZLTZ49-4QxIKaolf=IaMbC4{Pe`B zxevhi;i5=5`|%is`aGs377^4&+A_y>M6fdAPA*>W(3R4yvMySY`NcN=v>&Z~vfAl0 zuKGzPh>^n6J}OgoGWJsc#0g zy$m%LB0JR}0)U<#1tZO+s4ut$Drcj=js?2L$XE9hqviVxZfE6Qr53cLJ80LPJtcZ4 z`6sjCcAtRuN%e|D5vGHb=%E%-t5Ch3%Bo83*)Zx;yf4kUOQs2^7*+QoLV8F6l~2j> zBe9|C;na`#{07CD&jFj>{`cjrhbg-MI+bm1nf1?Wl zHQ%WVI}R=*cK#+B0*Spss-}&Os}iDgtXjHs!1>7m{+A**v>=eN`HtNd zY!yUYw-yl7FKLP%g7R3pnU|-e6m&PPvIDrw2el3PykeIE>>h+50k9bYh%b^N!$KfT zDSWXK;$PC-s}L~S64BU5a(fOLn9&n<*s>pBG{1$B{Sy!;4xF58)YQ&ywqyT8L-;x$EU;j@jpzGR#yFGfI%2<`}h0%l;F^g@v z1>Ixy7u`@x^Xqf*+dCQoZE#A(XtAY#d&fI!sC^T-iML4yAKhGZ%^eELgce9)k&ChwgEouH1km@ps^kyBHrYx@nTSbcMNlwPu?H zi1&rLngF(jOhaZ}eWv2xn}FL=B>4yOQA><_8A6NQUu=$QN*hu}joix$yiQ$EwkImf zLsAKL7Uij^;q;}Uc!b~Q(;1bO8j{)*A72nxx&EfXV35eS69a*at@CJORL1X&VLsz4 zpl6xOx9mO)t_Ha{fj7kcv{}O3%DjI#N|T^zAyUt1NzrfhAA&miRsdH4|A9dO8Mwa= z0_N`3cfSshDX&%DL+6@S=2YP(K>`R)j{JG3nS@XweZCc-&b;o|@#iFY+JC64E6NDW z6R!p#x9IBeS04cQ_;2olxD=r8bHbCaY=h$(Y)N z8^HAHbUest;Z2K6_Zx#uvvi@R%0io9>ix`U}M% zn(CV&pNMpM7t))s2t=H)T;Xi&n^|K7+bRo8)775AjLsJia# zexit8vjGr*`3zv=ki>kA@V8Ua&~~t5sfGi1RaT5jB5{XNBck+5CAU?h0IQS+a(s%y zKR1GX!tY)Lv$6PRQJL8^^BDSDkb=EGzxnxT{hRKU5cj}=)85hX!swiHK#g`j*P>asssT{AN;slpDwX}o>kP4Lz3|CZWHFR@bIt+zm4&d>Wwx5e(-wY49B z&moU>XL=@0P*o9wSbxLJ`Bt5uTcPcq-yB|Q%hn)?oDAW+|Uw@tk? z-@U1$l&$JKome_%SKf+3C12M3ny2hFF~JhgKKrLNJ6oalYiTu2Itp`($sa91b1)EoISS_*OF`*NkTg{MbPVd%xo7upr)5``*p{ z0>;%yh+Vjc3g=L`+JK-7Sm76QLO!f!ELa&?|K152=zlU2UfHxaa>6_ib~@rxHeOrp z6_HByY-`@6nS;%3wWAT!+r*NtLG&XzYsS0v>z5%ngMGT;;9~t7husF!CIVyi!3~6h z0L%AKNB&iinST6Ng1u%HTMQ8F$qsc5Lqh+r(ElF|{TeQ|^DvqmtT+sWk!fJ2>OnUW zsNwYbOss~C)g#D1ZGLFr$lTU>e-;1X$Cw(;{xiirSjdZT$Gs^Un;;@*CRtqm{2>iO;n{G%U`| z9HxBHu&^LyR$cRMvL37rG;%Hqx8*+u?t)N;YBi=w+QxE7m>E#nc(KR>ii)csKX?-&V*gNI@+mn5Z+dJE} zgXcSX8O?$sD|n^e2uy2X0hZ!E2`v|Q8XzyTc$9yd@7B|q&I{q>rSV@85nozS&HLX5X ztD?#9J4+y0#R=?lqFX6BI%d;jg~kx$ZB>vIG1yc?BuHxs>j!-gdmIWYo_KVgoqTKY zfFY6ud@-5UaqZ7xP-%V7pg7DUh-wAzQmO9T&xTkUG3=-fWSU160zQ4YXj4RV4<)Co ziLewtt5I=0H$9%YIj$+Ykk7jp5q_?1z)8AZQONI_lEaimNs=iYD)l{mwT@GOZtz3( zmn94wa&bga9fQD*`z0Ts60B$@7IWDys0nGIzQ>aF(QSTFKGOuns2zDc)894?~esh%?Jzd-7(LcjQBP~Z)CyYf}&!uNEyEpuu{-59AA(_^9NtB5a zf!uReiJ`=MpYiq`BjwTm!{7o)vnJoG!LoH=_rSLO7Eus>W>b-P#2xio`sQ4L9X7M7 zJT9%5npALArS@w;iL})#d|{oSh>o5dBe}$Kd3Baugt;8@C~Q}Md`IgPHy{vjv;_j6 zt$StuFBc`_Z5hpl@&@DMXEWw;1>xVlm?zTz;TEBwReWtl(k-Wg_2{kMe~=;Jzfhb0 z-{Jjtw?8{@p$DGYxP1=_1!f6TCFj0{BK}mbb7O+q#P1}=S89+2)J6<^;_N)cE&?HJ zC+BTX1lp~Z3+R*$7FYDd!8~jhLMi^qkJ>KHnr~E31M+`99}v$Sz46RK_jb9akRJC3 zl*O!l(Q-_U66vPSO7cF%ICa^IKzMjAzekkh6Syu#aXxws=$)U>uB9cX0;Ae4!^v4@ z?}7djA1_LwgOLg%-_F*e?7FY|%IrjMS@c+^F2q3o(?b2PH`kwjD#byg4MZ@3Xm{J# zh*~+c2zvvB!On!$PQK0!*zs*?K>W)fYIto=>}+1T=FVCN*>!hB|Cwr);<4h|d9dgN zvze+MhWbXDhp~XHd}i#IU8QgEJWeBTI0kt>6Eo-GBTSPiS3`o6>z-&qVK0sA^S_D- z=Ut``c<^0gPuSoB!`WMRMdNtIxlL3EN(<~$=<-1cc@UKntW~$5caeX4=P#`xJAcgX zOLCX+8##K#QR(9|%SAVM#K6f})5Rl3rCR<5latpXMK2%};=93SLR8^eY?N~n-UFwy zm=o|LEOXrQ1uiEfs~|-FIP}`^hTyiI+q#$^uRRfnSo{C*FDY+MW4-4Mm@jVjNUi?nOaoTzQ1)d zz9a3wMYcS9iXqI!3a7#`ly_Ozd|S;O)1h@RFpDe_8s4m532`l{sliZi-h3UB36@96 zTS_YKd1qXh1utZCk=8GBk62WToeWM+n(iuGQ#UX(<90?>$iDsEBhZELG1=Ba2qc(H zR7Mff?2*eous1ufh@*Er(rU8%jge$#))7dh#{93jgU<93S0;0>B|;nGDBMtOx2!5Zgi=`=)$8_*eB@ob-#nZ9hnWw~7T zZ42wJ#*C!tdZGS#gRd8>{|hMwsW^T=2@_cXa*yHJ)sy>i@!Pa+SRhIqSB)p2)bV%f zopH||jqNyi;ZvjMj$)}Kd`r8N$(5k{z^Ajxcv}m4RXdEET_gB6T>`NG)+yY)=2IhK zv)1Jieu+N0s#63xwZ;foZ1K^@?uZmgf|KW}))E9!xQ;!z3Q7SOlmrk$FxU)4_MB>X z3LSXcx318hpShU;U^Yo$+SBYvFzpyuY6&r>y`+L&51RNK=sbG@Y-4^ZztHw7YrL_@ z*_clyesJxtIK1e7^;v|FvVFtM*VOj@D(tYjYPEkZ@d$fuu2cY|)uk!6Ve{ zIN<%}cOe*n@6poi2wdqPf(murMAhV^AH)EEz~#wbH4<8DC-;Q7DRndqZMeDZIMw48 zsOKFA2y|1vSCbD360WKkLM{YgR6^|jPYJ%0&Jbt?`T$ZlS$zdy1pj}`8tD#{JRe$# z_eT-04$wl7_}bsHU*!3@PFuyO>F*sLFrtvS{1M@o7EVfnAt_JP<{38^&7nJH(D$P{Ib>P+vADG(XZ! zLY<29>FoF)OGxw2)$PF@+&Kfr>%UZ=2VeT11Z(`oa|6oP>7V?E)@aO_aY*T3q^NRo za*A#Z*8)Lw&0tP(hF{k{*45MGxJX~u&w7{Rd`3Py6BEi=;!`L;a^c==^TNY&9zA7> zZUdUf+5PARjp!E`@s9_>pJ2yqZtBH{om2>_#L9wax0k?F5S<_-`ujK zFD)(20@|EUvZpOoQ-_2iz}2UD3h4KN+2PUCUAo7O&hh~x_O5_@ zt3CklNF*&U<^tLGfXhg7bWu=8g=-wW*ZBdx7q9($nv^tkWgrnF7Erp7>ZaivW;7oA z6ePBR#(Q3I=|Q)E6)H$I*0BxcuzD-A%LGhxNtR_yC~AMEc%Is6EP^rsB^ zOEeXooDe?rIjHe1>M8^^}&YTv*{t{vx#A1Ap;vRWTy0i1J7Y7D^ld84|%;P_1=&Rvm#uFz2YIS}GO(nIahWB*kuz+-w zXtR0biUe4gO%~h(m9=VCKs@V}Za6!o3am-l!_VURN^VzKPC%10TdQf64;oO+Ykw6NcE{1jEe3egu&JjhRR|iabH}>*z@8(S!kcIc7Z>&*}u3f`1ap_Ri5rg1;{G&aHy|jQec}g0}2>` z1Cpf9i~1E8t(p310hg_c>FlvALj5I+>{D zK94sX1vaiy2*;vZ{qFY$Mg4Pd-`aKl{;tF_W_XD9NFTdCi`@dR5Nj8?>)%=){|prIZZiQ^!R%f@ zQmvl`S%(pFSB1O4HHB^|eGmKuB@D(STH#a8!URM1{TFZ@dH06MUgy?MR3#+g43evX zY5l9ik6=nukbz$bv<|~>TYvN^)ZMk*hB`V5#cFPq4$t=LLHN%$*?}h;*S(6Wr*^>t zrv!VyfHDhgcsljfl!`pzN8TCR2Od&wR74tuY{&d$wEu@MiGml<)oy%!~aC{l|N$UGaP>Yl3Pv7+d1r6N&p~RRs$K^UkbYb~)fvYJ@er8?+V@m++o_UGI$J0$}q#>fdg2 zC)NzJVuU;&F}TzeDEqz!=A^g0tLrY`2N?O_O-NS259jAv| z>R5fSl7ZIWGFN2_*NH!La^`(aB^VG;9F2}=aL)a-2h`R3buAQCxDblu#!XS3K00*s4H&0vUODt+V5t8^w>G;P8dTYKz7(aKf!0CHe)3DL+fK^9!PZ(!_ZAqL?owvM ztUdv{5i~he3Q1O0LRjMV|1@FgA&;88%75*7Fe*&|6_GnNS2lbiAq<4%D1HM;rUTr% z!1JRAq)+T`!F>46d3b_g#4%~iego#PiF_n723nmyTSBoQ^r?2^_Uj`}2_liC0?+Bb zq7~-M8{i7K_UpuE&e_>8~q|L-DF zb!hKYm5o^1)rIlTpehbxJo+k7B%G2&`(xF8AzNA;Te)>bE73DlJ75)Ck{=o9BDx*l zwpDGv%c?6SCz-Cya#DCz5(RK{%@hlSLSBs}Yo6&Vg8<-$V&4LCz@RIu6H0GJ8X|UnT<8qi(#rD^Sl2E)+4Es6! z+Bd-IKZjcUd{7>A$9@gsvvizmC)Nzc9Wp9}U%dIl#v<9Z5xIZ0hfZ!oH_U-yXGdPf zJ9pn}UksMSUK%aD8940qHs|a5hBY;P3ms3T+jFp*hL*FyzG$6lbnF}*TE?6E2hYAf z4eIU6o{sY-Yh&D~)UIqv6-q9!x7X%`Jt0RdIx$M~LLL_c_&KXXG2!0)`^XSH#YXTY$nhTMH4Dz2!#z>aG(WP6;cQP<)S$VuCR8kFWU!aSi_ z%|YXOcb0eO1iY!1Q3pKjplxpR8bAGQg3tg1h-!8xzw{Bm!fb=I@#NZA5_t+gNV@-8OcKF7phTGTP>D`1JTLMWMy*n^mGZ1G81DQpt$618j$jJdi8|u`) zYdN;)eX#qI-0^`;{SxgG5( zw-yegeZ|g{2)dyhop*9|L|l?koOplOnI~v?)+$|7&OpuI7vZxIK+=9L!&&qJwtu2D z$Z6gH@KJuv7UF}MSl~ZNxl72kRCPNqO!h<@BXS=tS5?f~6FerVT{nf1|Hx7#{d)mj z(y>ol_w|tigz||ekrM3p565)H=WN4V)2}@9fg|$fq4!7ozcg)bp&e~4IsW>%a-LQ) zDoSxc5BwE<8<8EaS~}J8sr|v2@mQ;YQ9$cC*409aPw$t0GpvLhd0&732D$9=#^KVA z*`z1z?PtpFg}F#>2SGferl`&Kvu8XIeFe^~w+C_ZjA8 zw@Ws;33(EQ*^ROEA%w+D%0M@-&>^*Ma@w%^nH#0)Bejb>fWcSUH5Anz?|KjAbd z;{0KPKnPZe2RL-d*8hF?7}i!5ebPa^y{6mc=csLLVw-@1GOYGzF_#+}sgJYJb9bZQ z;62n2&Nx|zc$Q>~WGCO9{;nnS$uMF|OJ>oJ$1p1$86DE9c14ngS&hDz1Jb$YURg}Iva@BxK#-rYc5W-? zLm}~n;R$u@u>ha|bs8u@{ZSg2VX^6@%}*SQ+&!l1yZw9^=-^ z>XQy{(LlYXd5cSp=E_OI76HZA^%A{jOup?demp2=<%IZoePLJmg&sB9=IHNw!N5;D zu{|!P=&V&1KomOnO8dPjjsh}ET0fpPI@fMT1b~IVu3Rp= zZ#)v2cBy`Jb>bCVDCo;6#-1ns`F(;HVio1!i=28b?en<8SDWknOui~%G3P5dcx}+s z0W-)dB`#M$J2qmmlI?;ta^%+|-+;vA6yr?+i%rNR-poPmXAXpc;ubQdUT#y-Yncq3 z_KXLSuRb#~pZ^R!%)nx6SY5db5uKVIliK71GRGFkRlAka(!{j+-%ezEsHfmkm+zR6 z@)K)Y&GqswIC+^Is>N|vUX(=;IF(jjH|lO z!^i~|a0p8*JM(|uxVG4pBb8!HxpXkOWJmL*Dg~GxZDtL53Kl%2sm;;5k5aErBYSlF z9}_a==rtN#=3Nfsa9AiI6Pi7JOc1$rzb}!2lOR#TYO|ZM`rF4nE`ZcItD=wpY}98~ z>-aeFhH%TF3riTw2gBT5EV3|HU2Cxnkc3Y8I0d&>4n-j_y<%YXX^Bi6tVr8JprZqbj+1GB~+q@h!i=?GVL!eF9a4b=kRhgjtQhs+pTeFE8zsQXQ5f58n(-}++&bEy`D4s`h}4pT(f*Y zvO`A^b7_4(uQ!UFxxd@Wzg^HUX9dGQ#^<)xO(chK(1vj)=FH_$S-wN|TnTrnE{ZiT z6K(B%Tl1acD~~}7)&(m@&|B`<)~k5;!l_Rse#Z8R!Y09@)iS%Z4p!$COex9oOOmWu zuDG!|p9wnlV~mT7M|v7D%bZ|j4nObnDOxf>gUrtDpC>A4Ji6*&N)b-hZwSTUWL>2NI0Xdz<$C^ z7~a%qV`Ia$~aniN3r#y@hrk9GFXSDprIYs7LyoI6j z8O#z<-+E6Uirt1T_L0R*`=&zc7<$c8dKAMGMe*x=vQV3{Sh&MMj}kh zRbq~`wvq~#rO->&OLWa{bEP!1G0W#sf~teTde*XAtuS!Si8oc9Gie(06c;H{)9EFajy; zCyiN|-2P?z;C=HWaO)Dr7_wIw<4w48lv_O$sAnxP-YHgdbkM)UfOS;?!`Eg`7%hp( z?v*0A>zMxHK1M){KH)u!qZ)9jmTJm`60^y0t~Q_6y^sO}!~xn;w>A%q7s1D0sxbSz ztn$r5eS*IR&47epL#1Ul%TXSI`#8H*H3XH?3=If@4W1LrjWczHL(7jAG@_R54v8T> zPy5y_Xv@toDi&#sjKf;o*px!a1VOnbEQiWh!dhHrjZ3L+(dOs!<7#~RF{w!HD$V$n z$b%pSmevewq6@B}Rq0$7-*`J_g@!X>aLE6~gnm&u~U&7%qQWGu#Zfu+Uh;cMfQ1*Y(Sk zQU!dZUV=&ZXvsnfno|l}TpnrU1a?dE4prZZl?>VAK7gF}K%Q3}ede*~UGmwtc_n69z#*6ly4GRB9B0f0c}YR4ko=qN5yCP?|HW zKm_n2+W5~urN?vVFIuyO8w+#gQR8tzhU4nnp&!n^d;MqyxnUkW-ch$Lqksc=iw3Tu0VICbg{bm4$ORq16r=JV!R6LKvy&} zbTP16qLFXVE#ZX9nZpM*)nKY=IYp*3FR4j_c*+)ZxD)5&8Aq|gcTDrwj)q}?qhr!c|ZwZeY|o9{K@PEUAq^YqEn8%BZbxh7|!Pb zwaLzhUL&0*1jh|Qv^?54=NUrQPex+oGS_`LX8fO$t_nF=EnNZT>mM&d; zAD2ru%6!gnWyRP&yo=ROKfj}HOduHJa!mUEc&<#q)nkJR{=)uKel=DYVT(gt)%`;n zNw@lT4~L^?3??6rAVx3(A34K6RajM-RZmT}fA-!vqojunykz0Bb0$<+@+F2}?d{OP zPm_K300YMbd=xRDn+eu)NGZY^nlnSkT?XtTLaHM)WwUq1T*EhGLU(RcyW$V`=}wKI z1bG%+tU4&;)0bgjP3plPd{B+!CKid-a&a8Do*>SO4yX69ajmspaOE&gh)!53opAq_ zw!m9?G;zfl!6xZXT&W&H_8dyQIn*8^^S#0eO5~441mjw?$rZ_x9dXpR^DZKFjNTpC zmVsP^B*L1wEL9^v!ktv-D;}AUKO8()xbu{kEMVz#ziZKW*;d8=t|dd|6vH;oJUPot z-b7Ht&G%9h{F>9yy#iIanv*t8GFseEbvBtTqqyWnv4T7o<{?#O^1c$$2zkq6wm>?MikXY zFP&>zCX=JPkZ)F{JHq z!J=tJZ-h-D#k)=EThWVb~$M%WUrsvN<)O@%%0o_Kg(sD*|;#n!cO>D&#{BfdoQw6>|2b-|C` z=(2T6igNYqmv@$rVyW&AZucbUZgX3#fuBD$2NGzPgo1*_kML=qzhh++mChu2I?V6y zxRxj#=V+xhS$k^%2=8Y_wrR?hG}rLc8sJVhW>RhS=W=CO(?=&@VcGTZg{%Yd4+Pv- z+^qHIyH7F7_}MsL9slD(did)I#;B-Ruj4n}6?c!M|$nQs$+3d&2j%Udd`vdzEK+(MFhW)-5>s4J@X6zpHU! zj#&)+ly73Fax}tLkl}hgNe*%C$=!f%X}_^jdd9bkQn=*=FQP>?4YO3~{qDAHkCk{V zKIB>Na}GlJAl)v)LuTO3!+`Ivr|<8MH$HNd;2tBF({W8a38;X|XYZ)20*ga_k?Fv0 zv1>g*Ii%k7YyC%sV@sL~c~$QHDDLB7zGI&xss*gk?LU_d*&?#B)q9ntZb z=)maoTqh0ImK@~$u(j8>GH;|&E>*v=)Vp0^xL7Y~TSU@4V>SXw-i7e5WWoJUnf1EG zSk*f77gN~tDX@p~TLP%%JQ8jjp8Lu9o5#MC9t*T$i!@jP#}()!~xjBYO#z!-;0;4+Ul_lvqT^ z;b2>%qv=?*yG16M8HZdk@^@s*4R@ese(dnHHeLCCTrStp$faHyGmm-br^Io~T>iFE z|8d~CWGQ?gr+>Hz7Kp3A{Pybg;5HuTp`}L|BJdcs<&7qnBv3crb4p!FTh6*;`C+^5 zezTQhI&}pv3Me0&Yigo%E}`azRAu%BUn(rW-9};>k=$kds(W;e>0D%$K()<<4!C;* zr+a`?V{9b8+d66X>Q#1M^Xz+h)sU7DRbRgJ{h8WX4YL6PjPBfLp~Xz-G#{zHxpL2` zM5@RrxM_YzYa{bsS5=RMrX^Sw+g4~NY)+L8N3-GbU)ii;66M=5@_^&a>{sl}7X5Ut zM%BWnx^r4`IeyIj<+{jX`s|C~w(YU7pz-9>8yKfP2jFEoY@N@!UP`KT-`ZF>QogcM zLF<-ogge$-d-}n4gAn8{lp=j0jA+*ZR2hRMbHbrDcOCZE-uXDi*A{ zv}^iZwTAIlo%4buV%4|y9;Si!UG*8uM~`yu95E#5TgV;}bX-}ex-~&rw93Q_yzE`4 z)lXb`!jOp3n_F+^?v&)_ER(pK)H0FAec@M#%QYZmA1qNO^xcfmlCX%!hdkj2voo5n zF!1Lr!uXSoH!32d$I7#tM|s%b*}H60F`-O&CTw(u_4y@5_pvXbhMJsTkG9*MsCh(l z;^TqrYa_7{ih1Epg=vJ?-6{hmikriQH+fW!^?kj1>00pk9R9d>p-i>r@Z2NHoF$`N zzNK)at~gYQr0iQLW4WH95FxxzFA;UdA~U9+s7ZSjv}9nh@KHetUOhQk^+WYlT0*W& z@QryL<;Qr;Y>A~rgW41MwxyNsVIkquqZ>Y2o}psQ_sCEM`={IT3zBTa;4&kzr}KcrQm@<>e4SfSWO|{4beogt_fQwdsV?dkuTl+9w7#`osnVa)Wu}DBKwA65Io@MF9wxJ? zcO8$Bu19RBX}^^l*z8xcBZqa`t_Rse)O@TfXtmIReN!BeTA~~=!d)iABx3xqriS_q z+<>QEYb<4d;Lpc0U5oKaFVUS1OXw?OHME6$Uo=RS3?fo=o8ju5tXbz@_-A&OC?nYc!A83IyI9?5)kh7AXN@c# zr$3)oS_(1G*Gu)s#>oxA=srC*DBW#bms4D>gN;7p&=79uw!vu3rBI(18J{cqj5m6z z_Y^we(m@8?qo*=B_yO_TA#$>=&b&)wI1#V z?=5_yBt6+qCzuw6JD=tirpcMA;*7?~pbl*{u)JB?Nz|N}=l0Q_PxPGUW@rz-ydd@h zbgPM)ph~w63+LOGab$ixze$pz8J=3RsJhK{^NiikJR-0cCw>wb>fcEuYU&juL*Krf zw#F(eHn&DDIS>q~MOIZv>4hl%W?HZs=8%$nOU6$5mP&0vu)8DUlG&o7|4sKH#P%n` zKcq|%mcO_!S=BthK#hx5l0gmBT$N*F4c0s-vz?~MGUw87Hpyrlw+^{nftcB>_*2AS zftX;kog8D}y=HjviOH$LxQ%1&qIDtEZ^zmf)doZL%I~6frjJ%#i?JTOtx8w)=aY<$ ze!RQnu0emQdFP3+I2cU$?vDz6qZ{yLL4gLITkE4gUeuI~)f3|{XI14BMi=t+q+fQQ zDjTgy`(Wm3pVUu0v}tVagPkHUjBH5Jow`G)sZDz1uwSFd8K8_;bm#{A4lkFxyy zF8?Ys`K`?<{<*$WB4JBU^Yv1q0}LLIzNQY*#)h!IQME%Ujm7nO21=y#{HVpMZ)Z}C z7j2su1L*Nf>68$rdAdvt25FU5ACOG*s4m`f)TrVozLh=ST24=@PP4RWxnxCpb2%`1 zP5CHqanI#vHab@N$Wtz$BLwl@Sw8Qk=I;vVp43%6`^|i9K_68DlYX}kVg&~j+2Keh z5TiYMJkg|=3)ghtqCcrhz;=$sspjU_54J7FZ^~2d!aanGUO7eQjdGSKS~!WEO3w>K z+mxMt13^aWCLUKxPG9`RR*;z`?n~V?X6|WCuPd3zRpC?a_Na5%qsmFj+W4!_1~rkOTqKcbV9$w5;lCZ>*vauL#6uvrasa=5TZG#+-=TJ%$pLUrX9$ znOFL?SIl~|qqXhUoBYlY z@yElg1vFpORPu66(INPepxcim*WH8^JI33$HM*Vjq9zWr$tx<^#VgyN&AY0-wu3#A z^6Gqs67C(T`}!7HWq!JT5-tagMHw8}w`T73}L2RU;** ziYn#To1tM2#i_?HxNnD9RxC&q1P7Vt<|GFmPmj8&NYErLe|vAx&lMRe^{V1W;eEmy zd%q*}YdpF^ulw{ccWf)>TazD3HJIV!d#JH9D#6Z5bPOREwp~RWp_wq(fh|$@T`tp` zN6YtBzcNed*5nL$fxTUA{(zTsf?Dq7kICKzwoTLe3UCkC`GM&aB@DArB`iV)e-Oqg z)eDU-%et6=E$;eRmDC;PO|)fE*{-K!0D)UOSz95 zI?>IUo`|vV!mDcg^INA2RD8>2(3jYg&-4yx7b>XJn!VpCC=Pp2`*$Lr?NmH`g8 zN3|BB)$HDjy+B7uJ|jz#7I{do_P390V$L6*dnoqYZl~N1q>~h;`pUrs z#tX0|fTtBVVw$Oov?!GyG3 z3s;O?4<$!8TRw%Iq5Ad@nWiXFC1w%}70O2*&o(X)ajtokvH1O?$&U3dp1tt+aLPbj zVXLHS|N2Px+BS2!qm~n)*f>-uDKYkxy zrs5bzKpHba zZaAZukD`mnEqbtkvljOoC!uYQ(@aop_rV15N3NAtS3N0F#QhY@it+nz>RDwwT8(NJ zcGr2oQUQqDJ2LRnP-fQX*)9C3qc^yKGP!qO{ZDupJV@IEKEIuzV9gBUwam>Yi zp5?7@B|<**$(+@>4Q%1f1WmRRu{dCAIIZm?6)Wt>TNpU%z5Mg+g_BLQH;X>7X%9zh zIEh83{L`J!K>W)>t|dczIoNtOcmj^u-N2jna1Oiu5x(IGdLjRfS&Hrck^uM5_GVUz zkpu3e6!F*y*}{iDmS0O$aJ;nm7jrEsJ;%{#UDo-Z<#R17AGO<)uEj@c50Fl*T-v#T zA!r0gSff%Yo942GraLd$o(=BxAsy8AB2eSenBxQd_#8>g#6I93xEt>wv#C?afLcVb z#9+U7&{|n4+SCathqaALCv==fcb}9mItIIVG?*!;W`!1uTfP=-_O!T3QDD=zdt5ej_P!MmE`FM4ib}H~C~Yi=O7FcWqYhmKDM3I)nxXd&jue&V zfYPfp1w!uxPy$4xN+k4Phy+4_00BY?@a=%kdEfV(^ZU-3bH4w6&vngQn5XS$@3q%n z>t6R-``cSkz{)-y1@RlpjtLtjZR$awrp8yELB733#wI)gY^J8@v3egn|6UJO=WvQ-8s|6MsdJQ*r`%AU{T+?= zl!=Tmb;d_K@R(5Tt9-~pkl);xM|1k<=iMY0 z9*uVrX`cNruyMmB;kiz5gUY#TTr?rMP05+qt==(TMTjJeT=5N3aQ}U0TFf&a6YIq; zz2hh)6WM!xYku_jy=nQ)=LppMXhW5q%{BFhJkQ8MQM)E)L)@e|%R!=0T~HS(Hr9{3 zm&@aB#co>Z&bR9XeIN75D|4m-gEEwHenPwwaZXc&QD)571R|i#MITTzUo8Ns9&(R! zhv7}VKku?H>~`<+5pCKGrdhRZ0|h;8OT6-RS$oG7FDq^Z?V9}QH@qI&yWZC8EzDTN&(1@aRvkSpVY5+fysV`bMY9W_@XM4I2u&?5nQEn;{6b=9b$r%wJ8 zL<5;UM54F(*GR?OrR&=MvUPqvh(3)MmT#J4Pj#^;PiSbqdbT#9LVAj#!6cCa*>6^^ z%L$o3y29H~35pbkbrEA1p=lDAE#+L1e@3z>?S!1+4LsvFV2u zxpjyL>*i}nR6b-PyTnLN9NMfEZ@4`XFr7wjzueo%Dm*X)a3M^>;gRx~du^gh0*6=X zlr9dGa}*$5N13Bwu{fnU3nWhl`W#!}(#r6ecFh=yOC2to-7%%G^u76D9db|XO{;6 z&g^G*yKN(UjcHH-q_C($xK`A@cD34~P>FKP#yZcbd22y*?aNX}FHToU=mEf|3CZW} z8t>Ug1#Y*rsVMQo22}bsXS2dX|IAP0apZ8*JDG_*iTq7vZV1-`;S3ZL(bwe>#(VPl z3=AZ#-U?G#sN=utA}{lM43VeOeA+|IxL`2;=_9V%}YVO_pPJ!gdQUedX1KCcqr(Z z{ISK6igf9!XxC1>Q~Ac%o-KWKwrH@WLgX|FcC{yCB_=4RD$iV8$a>`+V~Y%BSOFHg z2s@>#(QZxBDic^UDWO_HG@G9sgZrwgQIxj+EXNg1b2ahT3lZ%%E(p<}=}DHwa% zNzup;?m5jXYetSR&#Bt{M(rX8o>`-|KgF6%Z1p-LGtTjfw=Z!!ZFiTTAiZqJ;>hzG z++GuLTGKCCb$5M{yomUV5XYlm%;GNVsvlf-;&yv5M0}-&sEJk*exQ<9R=7G;kF6xJ zJB_={{dqggQk>6_sms!thknKA(P98niGr`<=smZJt5}BUj=7MOu%)YF+D~FQdZKoFA>vo&YwxWx$wacg>x%9_<7l%M_V8Tx(nl<~Gk8s=Wj6 z-SJl+u``HsChe^Z&sQzBYT{gnUQFoTSP6&NMbV$0K5$X;rRkp;OK}+?xS&OZyUzA@ z=R$*ib?$~l+~rV-z)jTR=fdvYp6<@n&Ur5k03n84$=zU(@^`PO*vgZ~$l_ayA|)mp zH+sLj;{q3D&V}>6{ODa)hQzU7cO%)vY&qJQ^B83Z?LPJ=ugL;B%qLFX#_U#1jBCTQ z81wk`Wgfqb<{>`4~qn za#dGuVW<*aUMM1~V&9}5;5EAxFe2b4GCtfW+Xi)>Ekov19p{NQ|8;#MC|~MIFH$o; z#E|D4d;2aWx1({6!HVVTJpg|>`e9ylG}^;U4qc(q*83uda<@>dDqkN#lh;3xJ}T^T zpl4KmP16I)Vli`bx%<)7Hv>)k7pP>Qxs;!CdGy2}P(cBuYV>5wX!-I118uS6yIVtD zK9zl;Sg8cllRK`Xuq3i$om3|t5iWHW;S$jmxVyR%=Og5Lj62}_i4J*_?~z-k4v!d< zfpvE3mbqfVjCX9E=V;av*1WhjCZE)(5Xs|VQMk2^SB`=`d0Dr(f-*5>xRS;Yd51Kt z-Ww{>v!O2g{gYF;Qqx*hDf-)aY=5fAZ|(5|HRIlvAgK9lwS5`2IOi7E&$l`W9}{28 zqQ;Mrr@L7b%mRJZTb_5f7$j}waAZyd4yIYmU1a5-s-K|BO9V~B(F>uCzqawjPL|%N zn^X?BUh^4a4LV)Cwd#Zz3g3A$xo^;fRQH~5a7TCJ`UrQ>jvJn5m0J1WPG#h_N#L)_o6SM# zUP6-|6U0ol76ZSzyUjbmgd_%fS&DN{x23;!ys}(3)8SaRkPH{4^kv6%Gn#mfzRyJF z7Tz8VvRg^_L)0$C@U4p2H#%SvbWG~ad(L^_&obv$G+1ZITDi&+V>LNH8%peQFC}S* z^DAAknp!K;f0ud05_sH({%(%hC1N*C9GNY7gA_N;63cRZ z(Pg)}((Mgj)?(;ocB-*llUqvzPC+#T->9sVao(P$xoM&JF}Ek$TsTDv<1Z@B3%a?E*Bw)KY*X_Vd6G!7)^4k1~%3e=6 z$ls?dvY(oSX)`!v^f-*ChE}w!CoC{M^9#EA6{bI-jsN!X8KN(%+sjLRv@3{;S^Sa; zFxmEgFEu0ddJH+(!HcC44dI7fNBZIWcNs}k+=LzMy-=^5Opz44%86jJQy{YCwB`P+ zyn0J=JL%fHGlt*kU_2E`Y@g)^?^Ttetl6umEeUL<4_Z15zpuVo$& z#5FaQ(Rdl(*&@-a#M`d4{QGjB#mI^w8G>(Ra4GZX`7*KLIRr3tcSnU@*qw?7fu`=nIrZV>8+f$=mxa76BE+@$$r2=1ywJ6L;O+Z6p>x8Y$ zd)%MJ)(EVJ*W?7DBj(@(IH`&o9n;1I2A)RlE<}-fLV&{ye$2w4A$2UCWHQocuoU4f zCOT1&8R}I@eKN%y<1rGW!@|B>wB)9+mKBajH+Qlpj3--)Q(g_4ec`}Qzs&Mx>eO5% zz`aoKH{+-MxE7-fkW4X@vY-ho97=oqw)TJmA(kFekf5gSLHF zofeX&(|Cm&-!>KEWmz_Fx+JFlQ8V9_VTInXvh?c7=t_&zk-A1nUjt!6l?U!UF+>5v z^xQD;z{7b0v@q;!kduBw^)pC<@=JdsLnE7q@^4IK9K^;-pCs`vK3lrFXQ?se75sxd)7`*E6+lqZ5mZJEIl%7yYX!#DYqAXqv%E8ue3 zB6YP*nv@l4PyNi~C8HRg+Ks&UhDW{jDCqCFTB@c^Q?gFg+Yso9aGobH_MD<_nej`C z&ZtDV-TNKAq`&5AHq(*oz;iAUdSCfmCTJtE4PQT^jD_H4>2#jhR#{<#0z9f3rpFl_X{L1Y%jMffU~%yd2=p$VQbxKZEldz=DfBnlye}u#893tFgN8>?5O{ z{s2lyKB%3TD2HA0JcI{P&haLN@bO}0e!D2Wjt^Y*5T8j5xrY>-iyTw zi;lTYl`KPYr(cpwnil_f+*tkGT)J_!KmY{^h*&$-qs$NUh~|h^S!|fdtJ*Xmwaeto zk#sWRF`Z_^3m+6Q4efrzX~GJ8_THb&4!UL9Lz2D3g}MttWK$oMi;S?d4lVDg(ifVm z9KI>k>@3u6a%aujK~AS9RM3Y^jcB$XcJ1gb3-Y_f6|@|)41u*4U$cCNS#T5hI=2AX zczD`$?6dUUxceGImp}i|sGUQXpAbFI=2rJ~pqQJoChjAzL0KJS&1|Yq|A?p-eD`f- zh;-5o5QB@AZNpq9>DxV;6^jriI9oe2EeV0w{^_|-tio@y*X+D4gys5Fe16b)ms3Vd z#t&boZn0Tda@n-ah8bp}VqZ3F4ah^~Spjy)=eQ(bKbTsK6fQMY>>Ok z2(wCqjtZpH)t{(dtLL`2*z65kT*}WG2wA3FF>dT~5U-y#t8PN(B4c?{-X7TA;uk`t zFzJ51Ic-?}hl2)2jWc>+tWke1eme`vY0^xp-}bbi%)6zt*ISG)_rSCG1N;Txr zM-Olbb-U?ftDaNJCHS?Dki}I-1u&iMJh6|6t}_~7s6e`c&Sl9GkhWa_h|B*H^=D76 z$^5tVaD!ci6EJEhqZ1*TBF!LJV4l8iKXTJ=e-VUEJ-h4fA~5=!w!u}4lq*hECu?5p zeb77DsMQhBh;K$#^I6QuQ_1Wzt!}5m|0juG(g4Dg z-tzQ=53!zKR^GB>P}29Gx^)iF1VZ&c76=Uk5x0~`orZ+@$smfxy{DFM;Nj9-g|j3} zg>Tek)BYEOH*C|hUlz9!Pw|Hh$E7oqy3^Bo>>8wGHvEk!ohipU-=DidUeLA0bcEms zbZvF2?1UtFE4R|K3@4iGV6pu{Jn~fY7Z(_2URip6&`BA$>O?*vC+UCZdtDdTqdZ(L z1D(Ek)rZ_~Kt)ELwTMI4C)&Fh&igC1ogTMh;L3j|9*r{2f1R!8^o+Z3+jq9?WLwPO zq_I2x>e_dx`BNP8*rU!qn-9U2=s&8t1Z+~$7lT0(!M})VD8Bm&6riBl-wwuCM&z-Z z+F~N%0~%wOpBPm|I=p?nDqhoZ9B}A9lXpY=OMF>ke-NHQKb37$g1ZkJPTke}Q`grA&SzP@h^6hhe~P-*ko6mwP*+}9k^Ip|ksO1pgK2KSeK>No+_GAhz!1iX2UXe3YUVVeaT z1)`Z}d!tu4#oSK_CIx|3bR*Y#X@55Gj7&#FY8+yI_N7mJv#B;7g< zisSl_30JYtY9w~G`y$cplDpm=3|JVuIa38sP|-Lmd-i&88GzzrGX_2-0udZ2924ud zG*Z6k#=F(tY9o${>isl&EpXUvCQyN|k?%gEWwPGY_J5QT&^HE5J`)?rW)wuPI~0&( zGM3hH*eII}?Yz-KfH9uAxHne0VfcvYZpMm?fV>M9KEMG2KnEI+uIuKY7|cN|0HsSMcfMrQM@91(U`C3$NJVgyhLiTgaJrh0|14X_2hML7L_ z_r2(*GHM1~o{h=r{lOp5u+_;u0>~Pc$@Kblp}cTTeABLV-15iUq892@7=! zimQbJEnP@nsNq8(MdPec)c>i?14E z0n%HMOz#%_fCd`Mc&z0teyuFi{v7GG^QIV7%rH!n8K=KRM^~TE5w9YH_rh*y`E0cX z<+zPr2Krybe_@5J7b_B6pKz$2HorjT`Ca?_3kNRJ(T#TyPh~Lc?##ZtB$y%N00BTe zdh9#dqV+{MAEmrZ!83SazwEf_2igndQL#Q`5L21*TVUaRoHu$A)9L6j5_H*WT!d9~ z`k=e{KZ+gOqdOQ(sbb);1OPlhJGLOf>0n1I@cOj9ht+4B1sJM_op?zpcQMM6g&$Yd zr41WhFw5j#)9xc0=dAKWm|2HA8wLBaY2~ZvGFucMf|Toqmz3fq#*AUVVKxa$HJ#Fa zy7;x$y-p|$^BV@yHYTC(W@p~3#b5K1Qk z^s~`qfRL&Xtpq37U*X5Q%-15#H@RFUEf%Z}+yiwWf6piUf|}N0woHdB+DkfjmJ%}O zQFXAC3TFe3-TpKSXOc*0{TpdbJTs4?EEBi9WKy0hUM0|J-L{8lCuB)zI4`&*3JVc^ zIGUzbQiWEB_AR9+OAB`e1bITBl^<=SkD7DU`atOEGA}B+5q3kpc9uvW0vPNXmzOg$`(T3_@q!sC= z%RIDi!tw(nsdkD-oTeHi$2!eCHHLtYjddQBRNEV#ZVo^~THMnD{J`Q9@wX;f8O}9m z)c+FvXfN}>FH+XCjv*3=D|=zdZrx@nbvpmjcv(Zjp({}!LQXp}Q%v0Adcz%h6~vBq z9=HGe@+Q9L zPkG%|(imTS;OnuysdVQAZ_tXFx6=aoCMY$ipAw2PJL;B^@-+uesqf=@^0Pnw-G~1- z+EOs4VxxOZSDSM%r^}x*@y~rEwEk^el(Idn{dtmgLUVBcv6-;Faiaa>tKeQUX@5%X zmqZ7DsdoQW*W*sb|KQR)Cj{2ago>)s&)cdx2wG`#=k!byjByl4%b&$KG3X#U+GC9d5utk;(E>s;h=as6&Xb2r%C3XiMLX(S%43v|X|i`|Lf>QPQ7^i_ zk7O`z*G$O3?neDEN+68gSru)~GMHUw3kY;2>AngldR|5rwqM-szM3rtHj~c1_q2Z= zWI}pl9QbcjijcmIY6Elr5=?d454+kNzQGHRL!V=wHD^bgL$fRQH8ZInZyxv^SWKW?&z-4FSh=+kbl|D>%MU*$ zAF!nM8w7@byuwR-yuz?KX1bTTGd?g8y?-WiKAu>0&!}t{pzKdB(H6oVh2eu|v-~`N z-2FGFQq@>_qlF0bk!<3zS?@a6}9%J^w zKeaeJ@Xnbh5fe!#trY{VLpp1gFg3Y)baZ#P)8%%@7JI%=qafM!^ejxAJTCeW`X^80 z|Lq(9u87~6vKC1_k=L6xlGA0PC}X-W6v5SCcIel+h+xjPEBg4viMzCC|Kp_F@0@?t zWc$DVuj(P88o`Tcd8f286r>`3mX}Mvd=Nt9J*(E8u6FEcF}9#;dWC&#Y(70}Fd~>F zI8dM~MM@Z?g85}5P@pEz zJC>5D08zm}BCHZ^+iihBBqMI;NM)V@<$A##`mOz}_z#|n9ec&2&P+IuAj zhfpcz=EfvZ{pHz%?}2iZ1!HrEBoHfgdMU4cA}~7pi^V(2$PfcUVmc$`G*(X4?q}na zv}j?!omN8rrR(x^-Fp@h__&ojjQNeR?!!C{j_m35r}s=}@be5-*_#Nvq!={Qa62@? zsa{Oblp0w73)qDgoCNDllu`Z6w&-@TO4t&AB;ZJLGY`3w)w+}g8 zK1;k<8aK&pG|zD2J|a_Tjp>=!ghWw!k38W)@Pel1zCI4-q2+QM<(#iDA6m=d4Ld*T zknz6cweZ!6{+pz5g4ebfp<1b0HAd*mfqIFbeHTTexFHTVEX0RQt4XLBo)!HS#AsZK z$Q7qFI~cKmph7+8_S zx}Um^Z|0K`CT@ATRku=@#;(~bKMw!-VKlo(WWpUp=$@#K_mvKxxSdyZAwE>pW;OyF zrG5$lCunEfju(jbP+ke9M`Vsuw5I9wMY4SOfn-h9Z@u1SYP12XFc=^@x+wtwXZU(; zR*I5eAizKdZqU z+pYC<=Ll-~l*32uZzJVv(iMc$OiI`niUiH212Q?im;Yp&5fHcN{|O8`o1xiN(|b+1 z5g1k!Y12~8n+M?@{N%hO!#h_2SgnRUuyBAn*m&gK^s%@h)Ipbey{(0OTUTD$?y0aM zL&1JEE8xBuz-pI2Gh;Wxw&pku2r9Imr>Tphy&1b1oh=G+8M?qzQvT8VbbJxmjcMtL z3-)f4>TE}QC;T7X@WxZ!;!frtkWcwx$OcP;8FGko(2UdZ1cr?sMe@B z41OH!G0{74-}yvi3~c5y`#byulBJeT%0=7nTSQUEM5yE1Tj;XVwVQ0F9HhG`KJKGD zfeZ!aG7X|i-vS=cgdDnRE#(vC;P(Y!$UK-tNin$6YMZ;xC=3X>#o~fr*mb{!v%agw zQfZ8LeD=UehR*{K+ombG>9DG>nBSpqUXy#(!gn?AItQ~TVDbsQ7a&IZO()G%+srIq zD9n~ZN8r`m<=p%bbRL^8LUTm>kVqgqJVyKeN9?IC#cb*sy;Pp_UFxKAJ7Fmv;Ub%Nii#dJ#!x@p7 z5<4idw9(N47ZNAU2L~>3g@ulLYMFaItsQUe^0;!J+SR(vcMS8^hY=sn=~!R4!v)wg z_%;uQm0o)Z!E^*m2RuHKbJxg~JHIzEDb>W^Ja$mj-g~Hg#^XVJQ`jc%%{94_u0V25 ziOptLk_E`Qwo>NRizOT=q%bzjSE>y;gma4{c^@?2ds>UB)Os=7E7N%E&^dG`m%u`a zWLprsMt_*LN3WSKWp3)ToN734Vfw=GXm%2}A=R62uJi&@(2{>%*=$3hPsvX|3-`FH zw&57BKw*cY0$!GusroMoXV{B3#s{1Kr2b+n@IwQiOg6m7Xs345E%v`Lxo(ixZ1HNw zOL|wZ2oV4ZE9^{OfucOQ_zOP?8yNYe0hWG7mybl+F>DuEilKch7UFM{Nq*@l4oM^&@((&;66edB=+&2FJlq0z~d zuiF%oNoAuTm4Zh{ntj(#*fmPBj`{1Mj;oM;Y2jPv;hC}RX$6n(pG0T7P{2W{ z5U#)E)R4c>*kJ3N-hk;pTXI>&#E|Nhb;)5!-_{Hik1I~4cV91?J=*;r4d3zP+idbW z1`!Q~i)H1>bJfqHzn$vWzbtrRSJzDV=`>mvs<5%+_ zTw*CUq2}nIRp6R_nbZ!?gktT$2gPKWpzHGYjdKs<@7f>5oX`N#$f0ZSJN5yM)91sh z&n4b&t1bztoe&{aIMFhp5`ug(wjoL}9MND$U*!I})bhfj)#7%@0S^ztSoI6$m~^m= z#TJiRvM=O5XO7=-U6e_FO*8pu*3zC6S%49nP`;K5lH?(RLT4#6+1S|B_~|Km{JV>s z(Wml3==s4#UAd~aAARgkm=%5JkJXC==`XK8`N%niGZBfN(^YadH`?5Us=pBGylw94BD?wNVk}vp?LB{b-y=EO z-R*uUBH#3@Q;Ep{eiZaUZ@S=nMRyI^r4{C$UsBxNH%BffP#mx-#wl5Zj z6_OT)pSl8oAQmz+Sp8OMIezirwY77aV|T+%QTVF)SM#!8%B5j_oM!!w*Swb+z6ID9 zylocL_E=nq_@dGJS6oZTy5X7gV=h4HxUp=7`O3+3~UX_=n_??z!CwU6oVl8FSGvcY`mT`k1Ow z@~ie@G;STmy0b&+aIX+=aB`x|`SJMLBG$x2Rn|UTj3Yc!o)5mwq&_$s&J(Xy(v11G zSi+zFdmg8VDG@0+mmWYM!fMtWUkwM8Z(FR6BAqwQbrsXK5-#$ngmvAOe%One?7Y&n zSO^lvN_%rOH%uHg@*-Y*+-0vGElk-?ja|(Kv60{wqRKke_ z+4vs%6e4W`th--xqk4^~psh?=ms%_E-Oy3F$B6lNRgS$1EzC0GW5*M3XZOfoO#AFL za`YmgE)Gt-j-JOv*7jE1BII$?y(?1QW(bK|!|>Y&fZ6!Z#;PtPHKuoLy7kOV8K1oB z`8~T*3K!i#I$E>4u~3N)%ujzkA>@A*7w9EZ22wL$bngTidG=1V^PbpFKT=v`4JO`Tw5(wjuTiF|EEk>%sI+cFj^T?_ z0c+|I=LA=lI$X|RO;6L6qSZDLz-rDoaaqSvDbcaSCf!dM@X$N4fXhGW*$c;jT$ANG z*XCc2r^(Gu3fo<0rTZENJOhxu?I#T0`kSM*w1>`?u3s<1r=`Oa#O z{O%v6gV)1P3yJ;W)DeF%EF9G|F?2z(`f&4{VY3EwUM6#5mY{zG#}go#HY+#Dfk&Q> zwB%8nAR;4=nq#1(#SMpQMjJEPYw-T3ppI0-+Q{R%xd(u(d-S5NiV`jB3Soha);hRy zuPuJE`wh|QT2sd4^x}<^z9o$vrOrbxNp7|8T?7|zxG<4hT)J=`k)!sFy-ip~;NgK4 z>utG7X~H!g0jO%Tb^Evo;tV1zRPVO5=hV(@kukQsutXiWVB9|vbFrh#H@|zE#q*x2`-ms-_bx=3ow-_qiaB0kU zyb9#OA^PQ_CKHYeH!c^HVx>plusukyjKCrg%{x2#G@YIN%itFvL2)62z=6MA<%zjK z9@$;}8mGQ!O66LBse}it*{yNi0vf({D)~ziacF4U#~@%vQQp3Gn?)#$H1PfFsp#H@ zGYL^VRjh)rr%Jf6POxs9!i(_!);!V-k- zB$q2u3WBmRuBK-JaXbml3!O}%lmct{)U zRBhuO(BHcFeZVkOMtuKsfa^Li>e~9ebW2BpO{i>Z^&S_sYMb27o3TQD?L1Ekj6;ts zyh(~0k988wv3oU!YF#Sb%d02x^M`Z`<@>%PvuS*i52o&iEqZi9lybP>C;) zadd%XZL&#*x$D4vg1*)FF!EGoy9l~K*^oM^y0DIqGxI+GzzY{;$4r-5u54C*3BJ6j zrf#&eyiEyZ#rJ9^RCa%c7A5H%sM4VYpj5j#wkHy%JWx#!uj6EU9Zn#ZqOpGHGZ$~= z=vAGM_Tn+gn2z@0Q6my0N;yi>?_<0e6mG&-cUx|kh&noAJRsi z=Cjy1Ww?8-3%KJAh~?`Iq_dv&%gKFSeVOhod$RyURw6mL(s!Q4p&(nPYlZCw17eXX z_;=a;5C>p=I?n>3<71Umfw;%#ZdBOba;7$!}*@w}iDDRD}SE5>3Y+AxNN zhSbjty!}SU?FrXKW!$VRedo^y4}RI$e$wl!#bBk6gTiA4oysC@ft9d?%9LYfRAF!4 zl!6%%O+9Y-)}wfjwd)F0=d=yqzZ_E$6X7)5n4Wr12db4Licp0Ui7uCRK)RH_oO={n z#c2}AMV`-&b;&U)AMIJSOucQB9(27{(&13x2u1f8JJ2eR<-x9a{NW;`G8;%CaBG}^ z9*O+Ru}Q-5dOYUnYYr+OZ(zhvd35l!9pCvlF@bTlHCqvX<2Apg`2CgW!k5j5o;7Kn zpSZ~T7S{y1kLpluKZOyCCN_fU4pNo3L>JvUdg}pwn1R%6e)2i1HqtTX{PL=$qy13O zX(R`)$C@!PNP6m>^=sz}3gZYv5czbZ;}W%BQwDg$&f`u&gMED7OuoB2-^`c)*sAR@ zY#ta|^})){v~jt$o><-b#5G-Xmp!zzSf|%w!ka}cmiYQjDuU!FC>zeOe1l~6Y&IV^ zG`S?-en+W6(G0oX7ChFc6dk8L+a-Y-DbFcfBQmzDH*YWa#Twx5dN$WZ%S|rA-JU;P zr=>tblX~a#Qo<}_y5K!s@-DlBK?L z##{+A?Rd+IGYhM7gn z0V4c%4Z%fz%6WsqxEK*V+!ZYl2tVm*^rvNfS)3T& zJD#|Bu_+rE#Ep9b+yx*%-l-h%z5cE@5)(Ft&BO$GeLp-^H=OJC0q`7x2IE1a9)04W z_ssO%3)pd8N$7*eLVoAGmK^u`zi!H(NUpgp!Tb4T#*L%AfmuHPY{_#bCtQ%>auSk^ zaUFaCIo1uNPe&otq@=lc)c}7}M9*iixG~G;st(N;zN<5n#@wx0wy|h|6{#5?nPunt z{3m$8qZK~(@a!1H3$OtJrf$up4=^yna7oZa+QVt4Qb)GEt=UTs>)k1|iEA;gybJ0r z=)by19UK5D98Fir=RALhuHH%yw64YCsf&$?*{mvxXA2b#p;3tLorf@Wg21$D{K7DZv%XW$PhZ*28E`deou8D&WCsNrdoQky`aZ6iGag2d z3%%&H95N$(P#k%*zF0cSvhmSL%tdjL!=3#w{>^caiaXD>#>_evD;NI92gR42kD!b3 zty5*8K7kL-jI%|Wu8fIq&ccgF+}bw81Vxy-G}#*5oaQ|a(me*X`#K6sj)_bJnK#gr zIpUo>dg|1#@`*Bh%H1oV1cp2w|K{tBQ;jdBg$1#P^yj4FYqMDz@_>PVe$?fyl>;P8 z2A(snzc}gjacfBbW{`36?6-0D>|CFJaNqjj{{V@_dft7C`@S!U`tAwb&Z!TW8=Q7s zCS?~;HeFf}2b)#WpoqPkzqgv#LWbw!#W3$VZx)pJbSxYFyiho^ZoL-i_#u(2a^Xp# z)1r!Q++lAd5BqFaZw!igZa25(A(AVPIq82SCRY>=ar@hUOlONR-<=9R#$8f0UWrQt zAcZE`7i@nju zBMxkQ12K(4)^U(-Brnwc6iCP(*#Ex_HRYJ<1E6*fCS_xz@h|xe5eo|IvhSl=y2|@;gg>B>a z*Nu#|Jxzvjg6S%Ncqm);*20IYLNVGDV{pUmSKxQjLIZ#f^PD5T*&Ud3?+c4*$uWJ! zAK)Oi6Q}AoIDz4imUDdEa1fBV_DR!EUG3}uo+|mJ&=%M7-vHhAhUBNh5b93(65TBO zd8CpX*>TN|olRD!_>7k!*7D{}ui*m#%r4zxQP$DWBzxMr)UYX9t2Y$%Eqxx*R)`)B z)2@;%0foToTv__C$O7<}pX9$sq#^$TNChB$yf#-B*S@kNIO8toxs3>i;fRh60Y6;j z!aTPF*KY3-YgOw2a{_0i^#3ZjPzs_jxm6dE;qBFpGc-j#xg;t=u4sH@#MAIF#*6mOT4 zxo%R~{S6&Mv$&8cFyNOM4=}%!;EPC=OBNe@12cL# z3`euSPR1iM8aIoQg%8Yt679j)YHm$b(PG#jP!&yCSg`~9qaZ`3C8p-zi-1t%<*=!; zn?7@4O4Xw5@$>_>e~XTz+FJ#Dehb1DDPL;uMR}8Mtu%xXCMA{WXgf6b{2~f+qVopL zvk`^10HX2+xj)IF2ETZsBk|`D`0myG+~>2rs{~PCUK$Mq6tr~9+MscYYs~ylv~)0NW%qsN{s<^^>tRczACLYqY?-$N(`8D|3ub`i zj>V-|Rutb9almUJtW+`Dga;|YFcbY(Bzz31seD9a+)LMcw!r-UCBYJ!HI5s|4xLhs zp%r7I9Wz*(9Kpq09zmcffaUv2F>Gqz{IwZyKdMT({NuVzIf<0JXm@VW~b$=4vGC$UjIwpinm9>83%FO>Sr_}vZpbxaoy(%|i;uzUFN)x() z#>_0b9|L1gBh>6g{m~S-3!`hr0-=NRI;_3dCx@6yI0EE(9o04(M^u23mh>e@;e(0Y-7vqrW*b*>-!L2YHgMSlMQCexW^EsHS2FOn9#1B;{A5=9|5}rk;pH?%E>$?ZZB`Un;s0+df3UNIKibgr>VO^bZ1xO;MHU28TkqO2zdHn20%&xf|CeyamR3Vv4X7&Y zg?Z}LOFxv2tqNH4QvemJ)@wPjT+J=BdK*h>*2@1&iH!6n;?wL^Gpb@UB9tE_Quntg zrpqJ(AI^9j!pW~PEtDKb{pHF)e~dB<8S~)P>zj=6Id1!GPg~Y3A9kafiEoc1U4vcy zsbBalK91c~_X)yGx(n|KC*AycNzy^+im}4R#z@1iM&SOY;&gpdA$Zfw1&LMD%4RCQ zqCYp~}d*fFOS?n7?*-T{EOBbv$qDsT#+0lTd`%lM%{pyM`}z6nNf8xai|(-u~8-`(Rfq? z7E20xEs^;mze6{qFlFBtm|NQ7V)NMMD$bFE9}~^v zP2&I5p}!MKTdrv}1VCQI_+h`$acN~yusWy?n$)kuM4iyJmGMsVQ7pP37?CLtmtOl= zx(=%WRB5SjW>%U(j}C?a1MQvb&tZ?w{z)7AyIBHz+U;EMHAz2gWa;iQBn<@l?T0|ARb$`;N4Q8}RhCKf~MH_N^ok5)uIWkiOxhOs3K3 z`!ZKg*(fH13?|@0AHr}_ zq|1~mJ$2h`*{x?XmoPTDxdu6JujD*X+WldmB@eo)J`F6~!96>#j~leP-pKxbVCUDG zxKg;#Bio%SX7urNdb+-;JyX;DHkfxaH)^PEj&_T`HT#CQojrN^#0+*|W0C#C-iL6#IUdlIN%P0$gyS?Yh>wRBTcMSa>G-s$z85lSKaklW|!$=iX6)eqH|;nKLT?AVa)KyCFc?M60S3-HiO!AsQiK|G4VLo~xHe>KG>F>+SVf)kT&hpH7wOysEVb)A|_S*{a39u22GSrhxi% z|D)J`UMb*XEKSV<1tJYt#MLaMBQjb(dd@6Lk)YAf+LpQ+l7*kq&IdzIm?U`VKTKzP z=GJSPHBEa$=@Y=Jrk#9pov9ori%;7d;K|&KJgH2DpOopoi~6oiWOS=o&+b?C14?~m zKYbK;KbO=c0B7R#!Jq0T*!^}gE z!jjiI9%)(I20B3Pfx|a_Xr!USB*{ieYOB%x*i12aep+TRqZ+YlYvovg?*@^FcG*l_ zy?Cw8!L3kUTH|8h(z*ZPaJV=bbT5pdm}YW;V?_&f#@)eoyJa*2lqFNx>(pYV;+l=3 zeUp1;_>9o@rL@^|B#~{&w1YGbu`2&_(;TOIgN#bF&!kwPg_Z~HLBY{0En{s2{lS@n zrLRvv)A}kFA-$s|Kj70;{qfvB1DB4j4RoKpy8TjP5P#TEndW-2?Ks#Jd>%s6?$fy` z5obWNe-H430U?MgR#xr<8|Vh>tgHgr_5hy`<>u$XC0Y=3fQvT6rKys53X`;!PfIb9 zzh~N9%$+CB^5rNkCox#(YyRLeb`HkiF;l~WYl`7%(s32sYbT^{K&Ww$fKs4+&5L8I z{1Fyw#-mKLug**_jYf7f8Inev?(f-f?s*o_Rqg!x_B<#oR_DO@pFlnsTkj}T%a(jk z#EFIQRv~;3?wJdjczX%w0rvuOn@vT%P56Q~*cJ0;?3rm3)?>F>O=?VK^wquQk^&yo zB95Du?^#4rSCu)(!Gr>w3=gY^tomW@hcYAP2{3{_#Ao99~B;(W5g@)35L48-leV<|Ohg>3LZ@2xC@M^PHwB z51<5&{+uZ*vSn31OtqGMrbhv-fzgZOf@nC;J~&0kCria7px#%Hg8oydC~;%Mt~!MR z<@!6+!He7ks+s`*Ir%^M%F z!Vcz2Q;j$G$2K_qt!~LwT=9s=M6R%10FOyz!UgF-dEcRWi?1xiqg`3yC~&hDElZrw za>8EX%2!y^J^B0qyr9{+o z_Z~TR%^--aMg&0+L`3BGa?a=T{oeQQzW?)x$;w=>-oI$Fm{z`=7BM1?#e@L zy1NnM4+UnSkLxNN1pC;K5OKd~scLV3&5c?yFH_Cpk=3V!5ecAqUeGWa+lA73D8C@J z%PZ+cNB4^rk=^moYGdAAtx*RmhzM9!5WN?){@4L*%jn$K;2MYHWcKA3a=V7!X}`}D z%N>vUG^UfF&cI+=D<|M@X|KO?Fa`p7UHw}DF_ePK5b|PF$(t?3m8sZWL)*ZC>J3_; zj(jy#-+x!Q>UyFLYQ1p<$)vKR_G+o}K4-cYVa8T4;5h;IU9Hj{TUm7$CSR?%Oc|dJ zTm}o{(Q$@GmU7UN=Gp}wPsU-+V_TZkxnO&Y)cv6Nzz^2VH;8Pwz(J9-gz|~JG%tc_ z8b+D_zP#SAUgKPKTa(w;uo`cr%UST9!J(3!)^Jg@N^2w&q?-Y=Pzo`q6%sIlJp<@! z(A41Hb<-_}UuIE8NIe2;`=5FbJ51x}YjnrrP_^ z!?WRDfuWc4>thoMPS*tK+mFH*lFUj~OvWu21xycYCGn-#y2fN;juFLW$c*1w%>x<& zbMM3E-%s!Xko?MZhJ^Lq6>db3R5>nyOlUxy!cnX0c^f3GwDqNFT;^CE-{4C6q7f?` z&=Z$1ONu(RyfxLdDHpB)-k1YS43Cy#z|!F8Nj(PnDT}{=;xH?-P4KY^9_2gs>jz-H zb@1?v7;;e3a3I$5_EfvIDpXFum-cD1Jv~k=dGdSONd<%sCCkn|Wy%eZn5Eli#MrQZ zR-&)Kpv4!D-HAe-83s4DY%PasPlTz`iMrI|A?9FBRCKW?N_P10awgV3n0kQitI$0T z*xqm4VSfd8*~g)!OpY7y82S|$@{oWbwv8Io;8Qp+~`bn zufD&)>Qpgb&11w|Z;8YroQ|>Rmx7cEX=R=vJ|64g_R;mV*K1SY@QZMaKh%BLW_7O} zR}J^KGBrHu3%I?HV|cMIggkqi-sT3w=;&BJr*!Ek@ZLXH7+Nqn4wyrUBH(D<%+{L$ zxyF|ev%caCa)9Ohqr1ZMPF%$cQ~_8zsw@m&>N7HMS~<&gDJDYzMEG&Pt!NkljuS&W zE|hf|4p><2v?a&Ic}a-Nzo%qot+_I#-j-y4#aH~e5qu31q3BX0PvBeY4#tj{XoL(N zN1P^iJ+uAEZ4yIT2C zPrYi$R)k@_CkN=U?}n1wRa*OKelmq(!e{Ujulxbd_aEXu*kp-Y1HGq@-QMiV`6HHk zSF@74|MPF(5R2&uujjn?t2TwI{(1SY8sLa32BRmU{hY)xJ(58HUJi{W^_L0$PxS}3S8z537eZfof)5z=@sE2QxCq#=ZWtsmLH z)`J@ORsO#Zbuc&{fV&m{nMyjI|K4K!7d!Zo&9fHr|NN=c|GPas5ZgLay<-~H|KCqX zS$*`PEkX)0tjB26d4XdVYmnngYi4LGJA-w=ID>T%6T~pOKQN4lM2o9t`&Q8;c7V$( z-OtxZ2|A{iqV|l03xo!I=4>pOn+hr@ zRbDP1I=^z=gqs9*bB@~ox-zwUvwwYa9%I7*z(ntIB3iKh$KiOwLTg2JcF>Ajc9vV3 z+bfd|nhI#aw>YYo^y!pz$HH%|11S3&RX57g={V@R?>-Vf7MJ(|YR`S>&w_f^rMQ4w z1b_0tc$fMIsmG{GG&K}e2IBwZJROf#?}z+M7A^b6A|BB1?+Pkzb>GGrYy#wE-Av-E zy&a|L>pB#vH2##;abFY-;eNiZqr34S;8T^Xxb24vbQJ|@nPh35p&`E+ius`8Eo3J7 zz0Om_jLyIi$b35P8NkC+RNtyqKbVU_-YvXvB1f*?!SAnQD0S#MoyIGLw@rI|E6GJ- zc1QH83dEzv{5y|;NFoT4!CmFDeVtAA>a>i{*S~N42x^C)oJanP1rA&SvOASuL_4`p zzn{256Wlz_N5}KTUe<&$GyZt-g23SYAK>Oe9k5G5+_dh<)9|sAw|bBI3Pzc)ESbad}M0*nR$A; zoP_5%*vb32G49sS2%xeIsOOJzP~Fc^59025JXD5ud882jK=V(YXB9A21`e?gi>bBFt z{{B7vl-67i5X$$j8$`+Ef8fR>^;a)(1DdI_M?W`*B2(pCHq{VK9IO5$V#N!i!g@dk z8vv|3aF6y9_m~XkJ%uSw=mOGjd9CCwu{<8M_?(QB`(MkA^W7^q=zJ5AR3g({VW245 z5K`E8!ncWuO^v~cO0+ijxds%p#PaSwf)~Gh+ca^01c`GvDR}nJ>%p=MhH{51*^wcg-cNSfUM^K2S#)6batCh9|)Z zZ%&p$A4QMy1P?U0KPApv=&hfWkh>3zrRG`)l?xN5X7|{utjpz2l+a|Fwj9!lp)O_V z52B9VDiD%ig9l(AqC#sk!zO_};N%)WK zs;!_IU~vPo!n@94i6hSYsOyN)ZI0T$t~7AY0a<`)gBz`W6ALz3m9d4O_#f~MF{*ZN zVEe0rb5YOBTi7UK4qena?pqDCE$%sVFPtje^-3`mQbaQ-VvS`m$M)Xx6*QA?RJQib zWYvAST5BzMa98RDLRHO9vicTG?<`FBmEKFi{sLk%H{vdi6-7u_SI|%7b%>Cw=7EO6 z^z6w~@7fhrmVz@AUWbfL$W}?yzc;J`$pdoh*1b-_;02iF`^Kfw zt{os+!qBM45&a5hxVEd#PWr2u%^)#Z_)!7lc14nme5F$!n*!UzAHVqs@?Ux+<~$59E8H#M%e)S`f0g zl5}-0tM4u&VwT)p;#Uj-J1<8Ll?->x7u1%oZ@*&xd&wwhg#H2`OsCk+t&N-SW!SW` zGaEG(n}I~Rmd`Y4vBD?d;#)iB*fia5*p91+CH!5e_GhD>u~gv+4Sq-1HWKJe1Gg{r zmghM+N+2G{W`X<^8+l}PNds#+Cj@dXOApW50#!jLTpJ(7@WbbzX#{DbU{jbpn{VOh zqZ`7V*<3<@sw}B8ay8Nn0hxcdWVw%fyJg3_cR9*RBtCW6*-@mS-{E;`z&MCp3uJ$* zn8%1Uu^bc_aYa{Sf(dGjDx$c(CrdX#kz|aIx$j)<$WQe+p>xGlAm;k^D1Rk6>zI;X zviBkwIO;iqP7sk>vVf|dS(&8^*z0~PN%RaoY03>UFvI;$4y02lmaL0QWSviDJ-23I6_aj zr28&2(=!v;4s-p(B`x#jUiwyOc}JETQGv*lYD6=aKTCaxtFnlQUFX134d8;lj+JY> zxn4nmZlRc0cU=SQlnszp-t+-}{*>C}wp3 zb^mslU=L}PO_!s=W`Al=o(VBaNG&mJjURLId0A(`Rn}A4Ra;oQ1qT!rj$|QxawOD| zDzR^EZK~}zZOD5PS)#sN=}PN$*Fp%Guv}@9x>b1M@%sMn!sYCXtSQ{k?9~;oqbd{FP{C(9AxoWVwfUZ(_ z# zWc%%PukSvH5XfwdPp0z>xPnTSqsE5(T&1(C`i?L#^P?nYU=6;Jngmn+ct5e(OUB)w zmSi)u`ulxJ9}_$pbYyRl6?vAcHz{uP`ZY}Q=-IwpKEc2xMS!Kf8&U;3k<)IK8l0td zJa8sp;HoQL@PdJ>*ck8GX?Lw!xn7R?Q(=K^tNFYUf@(k2%`jR=2d z<7J5-W)B&srx(IyIj{5zA0^O6tC<)SR?lRM{ah--d?XV^;Syi%(Yx1)H6U`#ekcBj zs{y7D%^3jZH`#WfMW%d}W>oZj;KqAHwEf$rCx7+$G!ywozC#LSr{pD=#bg z2#s!;O@-Y}K8l)O=w+LHp;>2Fi<_%&MQ|Osjk&koPcUqyEV5T70%R2+uV1*uw&dRKKW>kvI|3BFH|bR zoG48A3MQa^_i#_^t($D+g1WMBqvAaEkyglLFNSC8C9?TK))7}0A zowC;2tmbpgyyC}`XZz9^WRHGXn(we;7Gsv8%N_NWF-SA%(}nqaC{aNo2-N{LkgqQe z8nVm!7GU|)FC6n*H;%H$W~XX<+-Tqzm_ysVqXOzy_6lN^CR@Feooh%bh$9}cnpHbs z7~UI3Q>&k8M)&j5J;sMn+4KH+5Liv_B~Y5XQ~dT+bj2vO9aHw#(MLc;45zM_hHMn4 z_xX8Pzh1sphbLI8AIO}hOVnGN0|OHyO!qi{{<|CKNN6M(xx>cRU4ASib-nJN3u

)UwD)YD;ZwI1G((veW72{pNLPAPOqXa^a_KVqd&DuMv%?9{9 z-Ym<{NByv2uXR)t;-3%5WOB7s@IQI@yrA#yH6BTvlL_+lRW1Kx31P7R(UyT)9h39+B@5mOU1hIGwK#vj)2*jct=v=1Zfk0u- zm)~>E`qW-s?-0_g@X_x9e*z&;QK1K2T!T|OAfC{Jeqj|Q*o3Xo69J zGSIJ_^q@lS=oo)VC=c@5pZ^V=|Cjs!-^8QzAfTn~Lj%?h2X+;}ND#jwUiDq>Us3+Q zPR`^n^@yd*?IYV;C>Unp+}> z4M#~o`)JjJ!~PDKa~S(QJ53pi*YJM`-%zykHRBWhU@%)|DCTg@pms)hWgz-Vkv9c6 zE5kmUJ&BHqE!Pl-P|d&^+=kE>0S2W4AGRA$I3kUB2(p%~q+8*%@VEFEMiT^7XnRCS%a7N8lg#}j=P`ieDBXoFy8o~2QHpbMYW;>hSr(R_1Dgo z!v|)+JfC(KK5Cum7j@V9j9bJal_@?VeoKP4=!wV6883{=U$i|O7Og`6;R)BrpxjT# zU)E(ZmANz?YEWN4cWq4L&z#|?edC<=vczI8npWSv>1fu>)nJ`~`T+7b9-gJ6GJ2`Y zT-9Ir^?#A-B$(zSQTz(%a<@VMY&9vPY z8&oWm8)Nwlm^{FjF>`#Y+`oG2clulL{u#yc#td>%LGzl_0%+{gJ0u_b^23n)fnTCL z!^>$#CCWTZDRv2ciExR=CpyfZlz8igr{(YmXo>;4lJFIUzPB@#xngMa(qq9hA)S5d zHlJW-YCiFgVE`cs zj1Ck>QP}qaV9-D4LC#YFVLHU02A3nRiwlXie}DBBP!jz9QJ&-Fh~;e-t7oM`$x_!* z!=%2f{(aT;;?dwfFa<6EiVEq2Cn~w@d3jG;*R1bc)76;OIZ-w#%C|W$uG_eT81Hv` z9WFZjEy94V=_vSWb^nCpa#7sLP#Zo#^Isar_oabm#>@XhC;0zCt9c}xwIlrZxWQzm zdNj75t`(kdVWV~Qbv-(N#=$fDLcdGrckB5*k7j%s(C93shL)#g+{Y-a0HYon$w6|i z&G)O;lHvEPYmH|d@QpRtmDB%krtBhF{5>YXjy%PL0)HQ<16PcG_h}4C`Kt&~DVb7j z>-pgO;FjI)+8|sQuU*7#Tl_(*`fR1eDqRF?=+7D4+wb>pA@yZLeJybh8=mE3)NW3+%ocsKPulXkJmvR&dLQ)LqS zmW}>>AkX~i=2z5?_@(2iETVT%`M6|3-U)~_de4YFp`+l3mvJWt#wzFXuoJ<#MJT6AwXY|j_qeg;UC)2zk{s>}w+4fEu>fTV`Jr;Q8t zZ+!DPWP_acj%*=vcW&JD?jJFM6qF~kn_W*?PLlHJ1}X7Oo_G9G2*g(8$jdLI-p`S^d(akP|M37t&irbua6g%7&Kr0 z_>vhI7}(R(L(2KmB99=1oZosfTQT1A=;y`$h)UJ#3A2~cF&dg4so*ERm*>EBhK7d8 z{5FdrM1a`p#v+-o)A=Wgin)F-$Ci&bX8PlIsOff8jY5a-HO3z?(hx(T?8`#dM_l3= zF6vWYPr;#M2TTSkIEVqrJ75Mb9IKH zGk?f+wG`pBUc?~;_Sf?AE^sbs0>n%%hj&O`kCN|wQ@B?g50k5w4xe(yOiDLNC^rPr z>$+?QE^I8$Y&@i7}ZRW!JLkiBii&TV4hL5EhWh*Enlu{b!;W*VP9$ z+8$8DMvJqywoxD7n#|WP-v4==e~$h$uD2eIh}*R3fe($w5s%;#K?larH| zSG_ccMR7541W+LOTc6tehzA<#+se745HxxEFiA*Rrg0r*4=*U+%PA$+D4!)T8^G)tc6Ux&oJ-tsUAe+ z0~zMF*p&+qdx75a29HJHLWOfjql^mEvnk(o@S&a%Lf&4@;f~M~^A&SI9D=C1w=v)B z+ZKqURr1pNbbocK>h(iP$`ldzo8KM^yI4|m3xWVRBm5}oqA@r-9c_#?uQ!QXSQ(9JbvQ}6^tqp-dR8=DrV6Qw1 zzZNK1TnyvPD<^n6x*VO%Vp)@Mo%EXs+W+X5TA580SlfHnc`J-U5>ni$AF@e?896GN z-5Ap_0?6T2ckiagn(P8e890D4zOGMDZLa5DrPiDKX$TNs*3C+CU#I@-3>GD{A_mu7 ziAK@B{u>+E0NsIfo06CJ_Nt*od8p!qmfdjA>_}hdY^GkE)y$dT_BL|wO*^Y_UG06& zRSKdBLuBiBJ>r^T>}4I!)O)}ok?u}49$4dM%#^b6Y-3o|mdzSGp~E|sDAki6UXT0s z)+oc<4A^zjwbo>er&-w@dmZt`hiJLNeCX@fuWfB@dU|>VS|urPxN`mc_wElsoRyN2 znw^ zH=6ehfZCG`IR;AHS4MuK_;UkS2^7O0fL$gm+W;I101l1t2f}pTTisO?=s*l0#M*p2 zKy-4y^UalJ0Hah=EH3NggL93^M?3k4$y`*p^FPy{+q&DWm-htp;1u~?X`{ABJhM|c zsz_zE1A)3fz)6EdoHzL^E>FX>pZ3UK6jSP+%s$=uxzHtkP5^f0RYin_wVxAyp~PgU*mW{ z(0lDo)oB?zAyi6-ldC%KhVQv0>%)N?uLzyQm_z=#_chf0)y-mh75l#~^~d2iMaq~W zrERL-2@$?r{fW-$bMvonIvG2Yz5XQ&CFbhAhSxz-Qt-W0h_320_E|q=lOUoinwXY< zJ?8&_c8iAmlpAJV`#fjX+m4j~n;HL?y7UDhIGu#cCqxjaj#w=H(oX;%Dt@nxjWTdx z20*NF6~7n^IDNqCU#cFUB%(R^yI~i|U-)kVPXMC*1H%7fHS|o1{)0aOy|6$02^fvG z>a%wNe_{Ga_?YRZU+v9jElk&z0UrOKiQ?}!r@8h!z->k7j)r_*|STV(%xy6NjUl8{_Ih~Wdq11lC&i_s!o*Um7KHr2hi zzQtT!-cPU2wR1fPD-7S-v+#_Un`Dfb$V1oH`VXVpm@0PxQ&?2O{`7&JB=9Cj?K1Bq zNkgN*gn)QS6nNb!g1%W)Vgh2{x<5ZMh1pZ5Yfk9=WSF@YO81>rr;dy68RiHg0p$wq zA}w{|yLiRyyraT?bg}fKps29ky+I;hL5pM7R-vJ;d60pO|ItiBR-&UPWa(qv1>LziM>h5y z6_Hr%?wol=(!e2s6xeU3kTj8D8fO}%UPNd%kTxM1pvZI9eLE)o3pt?kTKdBSW4TLv zT8eI&&%>So2lCQr)52=y9TrU(Bo#(|Dh@~Y)JJ?EfQCYDX|k7Ij}v@%7*U(b4V{-W zCs_Hp*Wk$PpYi$4=k-Q?&MXMFSct?l7E%}~032en$KSS?sre3Q0)JH06NpKPrvmyM z5^}8sxNx#B8#lF%Nw=KCu@`-AfVL9kRxBWU39^+p&(X$LVaOdpIZW2xQo+&9^RS%<0#|A9m+_?mx5HWY@WY94%+}+(fJ3E24a?Jjd2(xfhEo$JQ=>fu zE1s>w8_$X(HtHZb%UGh?oy+%8?Z9CJ^UrmD_!}=j6yoLBzVPYI-r|1Ti@PVC`B)CJ z_B&zz?!U+0Zju!FE)i_!ZjeCSo*aBT!_Ul518_Z=O{&L7ch1J&SNNl19mH-$I3GIw zY^dgp=gCm+m{yz*4hL2{9CMjy>^Sp#I$mT#D#*NtFb^B@#mPdQDNgVPH{m@kOnV^f zuWc3#)$-7-n%uLe*^7n&aACCGd3}ANWVX;bwOK}gAvFkRelOu6xb?aZ9r z(TP0fK|Ix5K7YgM*{Rpz29C`OC(rb!d_NP3YUtgyMAUoL6WDbOfndQm@NXp$Xlr31 zB9hUM7*JNyiBT{|z+DV=l&%u)r87`V*lTP9a#%WmC$Ekf;|AxC72T)S@Chc(1+>F5 zA8E5MW7n2eo@(;oPxIobL7oW2U7Kzg*^WfaT6zt-Ik1_Ff^V=(1OqwX=wjmt2h46} z)zSsf5P9EQX1n|QBA$nzy3nAsIsmmj`ZXa78g*-cwI zOF^iS9oy6kdeGy9*LR+4&mpk@hV?g7u?XT4Gn1Cq>o8UaBEGgF4PT9mJFv|ICy89) zBV%3TW|Kb+OK-D{aaYla`M>7Yvh5T&>Sx|FG~3NbLGgQ^I)9*jRs^c_!O=Kbt1Dgc zx|ITcOg@Ue>$K;hc+4UJ^Gmp@zN6azWehlZ=4Hl{$&j0`#2|4)902I!stC*JYMZF2 zC|g@wdT%EuKGLWA2NMR?B z#SpqLJGpWz${~09zdvUKnH~-r*h#xDqo<8zimG$IKk=^P`nW|(fjsg6&LV{|^$d&$ zyVoiuU{aE>(C7LOBuLasel8711?F5Q@LSfHu?rS=8}x0BL9T+0yv66j$NUnkbsog}_0odr*!OX$d;Ld5VsG)nvZhr{xCyzY3CEz} zrCuxB*42~{C@tes8M*2CIZmzPth=IQ53P>6z3L6}u?lKcqkGCaSZleA+SyvD;WiH6 z)R@LjQR3Bjb5enn0#EAqntR06*M9a72w}_=|574C;RSAU^A&#%Y#V-Z84)jJxnhV6l-{+Q?4-7ueg~8jNI;_L|D1d^= zt)|FHApP^Z)%Y(E6ZSl@A_wpQi=Bo8`_H5+Z-~0HS1M7hq7JDX*Q-4=3C+E#`9K^I z*=qlDma07ZOY7NxCn|ogu8GRmqhmhDf4EAUkmJhW!NGyElhY@ylDWKRnNHC}Xd`D^ z%~TH_{Mg)lVlhm;ixkSg1*k%so}BRv&L(<4ba8|F2&%@Gkbbuq5|tr8=wgN5eB~kb z5qNok`)gIlZ|3VQ;vyu96|WPH9j7atm8Ntz6dXxq`5j!md;}MCj3|k8!o1nBbB`^Y ze2$}-mE6d><|6W!H?bS$mCSn2mN*~Dl4dvr36itt{+7wfb8yZ*aj;pjO>U&%xO9pZ z9M^fWPdD4#)TVt6wN>sg*{Z+McV)OEdz}_7`u-HbNrtZ%f!ljHr}VzA^}4M?UV{ng zX1m4HVW6N598k63>-=|u19TzbSCNP(9_n?=y0CRShrEzU`s#n32>G{?JkR)yRL($C zxBv!!5H30$zdSGw;85Hj?44+PU3~we@MEeVNEFG?)_@kPB;bV0kly_t8K@ip+uRxx zSXv{R1YVx+%#}VW&z|cRI9?2Nz8i0|ZwFcrt@q{mF7o~t=KUMifUaim`I=6-X@4-e zopqVHjX_gCX~H> zt;v%>WeqYQo{s;Ei2dbzz_0%BJ&dTB{|5}@AG`hs|NBdeq!8D~N}jKc%I?j^?T}(+ zFc z96tE-7Nh?Dt@Db~bzE*);p8!Q4S9WjLFy$tY9l_+fR z^L#Z^0Lx(Ap%ir4c+f(0g&BaNCpP8+!&B=6BGrL(FLwSlTM-4BEz%X>;1U&mB-x{L z(fxr6$p5WBkrn2Ku3K}BrL%R8=HZ7j2Dy-jT3TA><|{2NEnvrGdoZ>%l-lNGe|01) zD+{{R3lIJT0FeEoX-P3T>2R4tRjYlX*=FNW3Z<)ozpV`5@LLSzrt#+olr zUxz!vE%0jV4cmjG^dDfqueQ{qgdZ=_+;bH%FfhQrT1oZgGou61LaC&o<(jQUFCGN4 zh-gbVMheShE5z~J&B5Ss{B0+oo(B}{9C0FHfkt?YpgayZO8R3Wz(rb6=w#6?`VB4; z)Jk%t7fAEes5+A`8Bg^*60|Olny+p+YUHWj(D`wBaaO2Vl*Ipkk@nSbRc%qXgh)x3 zl%moKA`KD-9ft;KQIrq_Ie;K3AuR|<9zsIE0F{(Z1*E$Kq(i!ucoR^rH@+9YcmKNg z0%z~N_FQw0ImVdlSkj1_ly0tQ7unCeAJ7Pz{&Xx4w;xn5A5lJJ%YcO2F84d7-$<^zn(z4G`|+BQErQ zM5aj~ONy6CJ zIQW$4&HSsL$Bj!rIxL+vdkf)-q?YrGZ7$v0bqZYpGFmz*@e_mb>Qt+-*1|EV>I8-a zaW_Is^r8gkWj_WvYaE3+v|}nNDqOSI>*@w%Yh|q}Hk0%&d!tg!!AY}c!&9g z)%Jywk^RgW^gQVx8YUd74*u~ZUedR0omVg_$`nK10KReCS)&q1sj|?xXkmMtX@9MC zG3eQt1ZOl^hVya*Zpc%7Y-cZw>&z{5Exp50`iNVp&aJLlws%L%z{q23ms0 zb>6J*zm}Q9pg5{^?J>!txRxwJ;VU*a+>4FTJSKr>S%Oykg|8t(SWc`{oYySR3kdi! z{v{Gp;fa2*qCI9f=8s)|?BQ{o?_Y}qrhVMgtT%{JICXV}cd*oQ`Reduu2YQVIIi5a zIbCcwOX-Dq{E`lOhH_E?-EgS_w-+tbcfwO5Q4dLbjW6(8CHaaRPdNXI&$%wn%pKNGAuK` zyojt1;U^7fqU?KZU(G2L(?{_yxiLR292qZky;&63!6*kN@ zJrPyzyGfR!Vl{j7_P0v!kefh2INP-8aieUnayjgTR#mAxrBHIu*|BhKkVvDS zKBryX=kO@lLs;cHr!Sn^IoN@Al#i#ZW>aRoTQwMMzT!1)NnBmE?TO+t!Vu5j7pi*R8I9F3)wZ{X1sv|SP~&@{6uds{wX!o6 zqzz=@ten_LIJ$!%*7w-hv2tlV1$!eocSj~Jt`O(d?~pvlX7u#+zi|?s;SvZTHl+w! ze}Uif?OT=4X>r#R8t6Q%bg^#C&o;xDvI7XcpLS&Evs$J@2lOY0*q65-Ys$|u)OFOS0rVbkvJ zF4R+J4(Yd<1|>6dbNWdVxSYrKVFooPymrtjza^+otko#+-RokUMWw(Vs1;5taoXtX z@9*#Hs~lq&QA1ZJwrKOM^(%fW=&;BT>CRhDW$1j>-U!d@DFLqMVtCDLMyhc+&p(F6*phKHHBI;Q#B8AUAljRaEBBRovKZ-n!{t*T zwd#-@F%{Kk?{=7!BV)Q!}aM#uf44J>7+pc6Co@bXdQi%+XP5@kAHY4m^o>B`5*LXLN*0EP$N5$s`MP+(zWS1EEZG8#h} zS`22EmGV0)bb^*Y(AoF+?sXU(5sh@K(70mJHY{M2q&!3EO2&-m&8_Jzi@=K1E+ct0 z+~ak4oL#-wSc>k4n_;(RhYQ?d{9YY?TLGcJ(Ap+}pF>%1Fo|Y>wczcG_ znPD?Bve1WO#qw${^~#gi)4?GNs;cl4CiqEoU7hgQ{Jep+McWO@y(=#o&d|}YGI{SP zYCy3FiAGfqz0eB3gSCU{&Fy1tnix*LE}#Lq4kmm8KX~juz7~C5kXKm_?`P~QW30x< zW5!b26~co5xW@h}>_b)7>)vCBPk?tP9jbXlL&K-TWBZgTld$k9am=J%f2t256CybL z^>9hwr@VO2)WH2Ac#kUMSQ39lXMrU-UJb6#aXDEdH?`*%l7s-1zN9jPPkT`BTIKKp zhcm3m{QTy3CcHa@aG(Pl+jpy1p_07Vk12)Q7=-GW;a=pjjvnM}jOP{kAR0d47!VNj z>b8fFcNWEnKL;zA4Ucb|LIM{wX50oq|H627N0G=2d|2fIV^Vz3}K=Ri$ODu@n%CdPf*i%v%>%y471 z^~a?=@|_spA+D3XG+jcG93BqKPo46dU8Aqq5ovGF@7_Ayo(3L<#$ZXCP{k9$s8v_B zS2VSLZ_M-GzkdL1&vMu=n9F_ubKfILsxIi{_{tNh4e#x4h(|H9tEZ#IP5&L}s%iZL z=$8LK0^I?1>5GS0LjMXv_+G4A_@+&Y+#MO(2U{yO7hfQ3JklX+yuI5P^RDE^>!y( z(h^)}=I1$!9nAc|y{Tl`l8A0RGC3)5v)mnsv25t2LhkPE?hya{a!Mdrmft`8lf! z#xB-EYclrtYs`U^SU16pP7iY#pSMNq{A0uZM8%JPq2fus^OSC!&b!+}yrSP-m+M$O zSS!wO5dKE_xzqeDU+n*`wveViO zRuLHsVk}AUt#v` zd8wI65;4FNPe99$L)cnu&>#LgP!Bh*sN;l$x#wv_mQwwHuOw7lp1z0Z&>(tPmorG6 zXP}THkGu|8xsLe$(|4C07HwyQlae~lCeE^Hb*l2b=9{UBiS=$nH%SQz zG_IYc&!>geNNqfNJ-(d2d!gVXrgYaUjW1PIm^Q;nzJrfuIRuaC(C5MQ!SVjci-yc9 z$F4_S(8e%930Mt6(?~r>j^(DCV|uksugLzb+up9Vl*m>Qrp>%M)W`i&bm(;)m-)GyTs{17@Q9xWYE$_WakP zF+`5@mJKWrz%^HyTSd0h7%;b&$5g~!&$|zD*hEF??O7C}79X)U4tzvX&()vU>`zlV zoe}PZLdz~7z7{dYS6}`Z)6q;@^wBELT|L#?sDizB-C2gsriKFLLkp!p=ONh*WIBkF zF*0NmD#tPS`^JXVhH;6Di^pZj^O7Rp-q@CHO3$}@_Z}iu=ABtP^R9YY{!LXx$NuRvQ&<#U>oGG#% z@`*-crd`%P^MMy{Y-R>;%QPprR0c=;GS;3dX5c9hS0MK&uW)%*zAyf6Nalt(D{l)` zi-@cac$NA2`OF@?2i%{{sjcT>AqEpZX^dR%Ii=U1)iMX#ojFgBX#al<~!R zhjA+>IJcD=ic6z1pd1*-g{hNAVroz3 zJX|mUH;}HbZb2k&vpSl}JK5XiCKqeYSHD;{>IQh&ipMNOItVP7)K6SlIk`(hk6$V! zG?^MQEn@oBdUWj*YiClTOE*XGKj3dX>apz0Ve6fP)-Tz`3WJC9I}F%nLEJAcTvn!U zpln%XL7;{|kot<-Jqz~sdGbV9P|(52314hG3z1?yxG`;kOH+a$Gm1BQNnakWeb&me<1`~&~+DGQ0+$~py?^~#tN2tQa*|N(q@lgujY&zibLUUeu;P>MqjUhb zhZk47YD}`@$r*(;#H<(`~{E?HZ&MBeA~pKYIZ?-a&vRj z(&`5C6aeimyz@#48bx3h69>h9e&^v#fnFGhp>Za*^hC2ffOB`gL;!h&KZ+N8b8|8^ zQaLR#qU(+Jxf2w1<;JYNlGHS;;otxMV%U9IXNN~dN5!|s!l1p56LDkAq|CeKY;QeiBZ#)VakCYU~+AW~E-tBySI0Vz={%$|C8km5~JwG<_ zmx=sX@``ODv?pK&DJm@dR9%gR6M6;wxHFxvl~95t2xy-OS@vzS#UrH4Ok%)TevOXw zxOD}`7{8yNpFhL~C|$6b2#3j1oY-%JB;^jmBm~v)3TzY5;17KMjLj}R`HF!d4UiY? z`nu+3q6xdk*)cF@r@?F=aI-sL^hUb@5;}m)&7F-QOG`^65_#ro$)h#s&&w=&xqIR- z-Ijtj#nEw6zJUoU|M#}kFR(LQe@v&rGxA)-m71K+)cZIxJZueWpxf@&Bsj(W{QMZn z(1osUmF=wSE{`dgh=s5C%L8liUHCL}MK5GT3QEf3YqHml)urG)fxAyX`#Xjw+QZq@ zmJ=Mf6o8@H`!Rdb$Ukx{JTg)%4G}jsW(>Z2*R63X!Nv8tenzH-C9w3GG{<{emOs^a zty!_UFg}h&8zOY<+PK?d1$Nel2x4V?WBko)_GR14^4GXj(=eN?k;rqCa)ql>9YutaNKy82fY{8qKUidF6pQU4qd!t!%#8{ z=aoi*%*;%{Gb8&$$yGX2j}!qdK1qkFQnuEmd)j5SmDZ~G!DJJ?mDK`pesHpZezAi} zNZAcwF)=8=*;@@1RG8vEu@Tu;%wzfHf(drci3i}7rYe-1!XN~~`HF|U95a?$ti-=Szuj*s8DdpA@= z(Zg63>w5I{7&%5no!~;TL}B|^HX|Hvt3YdfYtDH^>$txUbg&I|%Qpf0g_RT)xIQI-3icHKm`2c{dVf1!A#>5JYPtFUk( z^ov01mD$hfz)1*#9<7drJ3lv+qPWoOkN3CuxbM|Iy1-@=TJK|H;GfboSnBd~bJO7i z-7c_Xu$>HD)+kcy>gsUu@J4F=5){tqbA^8orGe}sOXHNDhnBQd$s@gNjRU&dZM_?@|^!5fE zH{09vl}=A>Ca5v|BSqJq{eg{_S_~|PajU0Qlkv}LS8CDWrpY1vQHOtK*bBGl=?;H) zjI_MnKJp2dz#RMFh{6a)TyWyMvJAPuYTbU2rrt#tnOlDPb>buePlx8uABG)s@@~hx zuT4`N)`tToCqwU8yuImZvG?1?S_#WIc;0R2f0Z>X66z{bUYa1yp8nmT!H?w{8mT)8 zPV6399jbM7yq4dsiU+YDd=Ow!;iOXa zv6C%6yZW?`l;(fT{ZQjU)2JvSF??N)f-gq7fy$2kP;E8K+LiCGJ;XgRN=W1)FsVO) z#F+o}OAR*zCWp~Nh$4m6Yv&U2z-jir5XBa*<8i5&oKg|KJMq-!J;E25F5}i!F~?d+g5(;lcx=+2IWa zZR%i69D{#%7sgtUa(;_}-2aA|D{&qV&y{LE7TW)Um_)>17oR+Q+z{O5M%A8M!VUr> z_b8&ms4bSh${*adv4?YeXk?^xlsa`(*!iP-8W9pay#HI=L-k+2^yl8&fT^e1^O?x;`scW0%&;>B%sWgMhbsU+ z6ORLnrYI*j1LF`fGBS|qUQrlH(p1aBd>56)K zsAjubtEdt)XaY}Ys0au@oG*s9s}qH?d>s?EWKHO#!J&Zgp*vg;__iPS!UmuNrhdTe z6{ky$-L2cV!5sPYw79{I8&M%4#MIQTiaDAXU%b!9|CHEUi9_Em_|M9qi zRZfUl0V~Rh@Mq6d*i-~U;^Q;n`OG3r4OLaIjf05ztv~C=`)i`d^n=fxI|pS@G8Sxe zDw!J8cdgisU319RED?yON~uDZ`ne~@-e~2XIEwE?&U5j|M-$N|Io_{?_Az5~b5>&F z72WgJrU=eFaWGhxmAUEY=%}cW*xlUS-Ws`dE(xmGt9mXcwbrl5qm>N{ z=!1=q>xy7w{~2+rSq3FgIG|*bXb++0g1`7)-*J^0@ne?gs zQ5^{lcurXgisRVWVX|)w%M8LV^RQ_3!66TIKo+G`t<8UR8mabsmHd6+W42lv0u+y= z>fXT|U;qHgo-EUyw9tJkG)*}9Mk`Zo-1Y|g;X1=|jA5fKSG1vVe_MH*n3#Cohme4V zceoMP0nb(V0>)v2;r8TM#zfn?X@(ZfwWX<})0b~G^n`$xQ#bF#1S9rc;WwE7`aQuf z`OC}85KM-KLr_vdXvT+MHEB0uJIWqQbaaPTeh^_ba*>CLiK+EoR2VNx(I@q_HyxpSS# zNORr%hRtsAwQvo(AW6)nvN?l7TiJ|l%&4WMB{0$u5E8!Jaa-%uLQXWi_yiUPNQB1P z+R;5@_0P}eG%qeL0@15tf4?9s)ZTz5A0%!zpw*P>)LBKk^MkX?0+bQCYt)Wcv zfEp9uO-MY=HEBL&lYHs+rx3JfT+h5(pBDf`x7DDGtp*@eT}>zMipsfg=FAyxZdDly zON8+A=g&3t^hA&o3N{7H%QiKY8>j{)pJPYsc$EK*OQ&}~4?rN|AmK?)mVo()WE`95 zR7yT9uaM(%O7q&DsU-ew1jIPmIN(AsCQ@vq`+{lQxh+N3+)0&J1<^!KL-T039R2+k zkY|!xH33u^+AtW1f#k+MT87_Y7}c;ZDud4U+Di7hmyn9%l`HN#oVzB4Z{EBC7dFt{ z0kq;eCy}aqMTPL{%!2vszxPbWJ3Tc3)nxOejFM8fW#-P%64dzLwf>!?D;mS;3j_>g zVOuYYCMCLMPB)6CL0{>=40#raPMq#D>+@e{)##B)E{DNt8lLTKY8bUZAQGo0Sus3eNP=7w;hN!C>H%WX6@h~^P7yQ zEtE;40mU1*Yp`i(bff(J{YlBlz@(%^@Mvu&7qohn`398z{6H_17a);dyW}@f{>o-h zd(alc$0+FZx38MHdgFIt990Q4v{#I7pLcWaV%ncj6wHVD-=_P2{8a%)UHeRIlB$bS zYW~N){<6$>3mmLR$>}Iphgfl+anzyhORz9f^qlHkOA5+Q{1bz1&NCe*ldGk&QqNiZ zHC#)&_=|O&@az8zaE*{YO<%fkh#@oVZ|wdP$^j?}Q`mjFbI;=^u5P6$OO)E$U*Rzp ztDtUavyKYmc=Qv-s>-{ z=8o-lo$eQ&!#m1rf8jYmNPm2G>bpN+*~sq#@;99L7a)bF$R_hp6FGe2_}efQvR; z^*cLv83B9|69`42-dLruLO_FW2imK90|iS!jPr1FuX4N!J|8n8NBdR?%7!J_zXjB_ zu(b$HuoPSs(8m7ypsP{sU`Sh~5LkX~xML1P2h6>?sHlL6B^Jcc6e|@My;gFqKIjYH zi5y-R7Jo2tgbaF34R3s&f8qq&T(LFKk{`}J!F|ksh-6|?7a`v)Hf0#o;?GWVUU9DqDxN!bFS*+ksQ$nBC%}e8> z_NC}nH~(s0-qq~)F2Vis*Q3;cf%Ww8fV!O!5_08k;=SvYt$IZ zOGbtSgJhq7UQl2lIscVN=Vf_s+^sM|4LP>kUf|(p(Kgf&5(Z#cob_1W(ebJz_Oeb(D`6o2l#=! z3sw6bjs(1ZpZ9LVNm2$<=1{Ie#vJobe@>!XWG1H@d%1+SqqrYj9>Gw%1>5qPZI zeK}?s85y=5dlRu%P+xJ8S z1ftf%?y8P;36~#cvhE8Pe959AF)jEu#km2tHuFhxQCAPwVY0RtWGBMGQT|dR zU0Yw@SL#w!R#vu@?;0qSG5QV~lezbwp3?suKsLAHPwd}c%_4-(Gnn)FSI>G%n+6P> zB$1Y*RxP$URzt264bkx6jNa_5tSwrXKaS1bUu=nPb+}++8FkyNala70Qbmb6MO16_kzvZNOPs}S~_b&zkJaSv_ zH38jF4CHH|9G&TQ@;C(q9@d7OfqAoi7iX?svBM?(=^Bm*2xt`zFk3)x1wGQVrI}gA z$ad&y3W_x_qk(5Gz9R*=Nh##5KEcZoU8wXGz-&-EHQ22fwtoC@U8k%dK3bhw>;J zzxxTi%bzfbM67|W6fkWWetLO6N(jlUUuwCF@_hHfMBlcEp1$%gd;Q+ zw{G16(zgz`R=z9C@M3+O{<3VP%+)|c7}Z&68xGO>tSAI+nY;(qkxK@2KZTxlsDE-QR96A=1uJ! zQx&_$x?X7{e_&xSuKU_Ae1nD-jp{Gl_~OHo#zz}ccDt;U5DhKm2z`^XHkPe^MLiTR zYhm)x)O_q#)tE}COA+$2s2akbTw-{-a>s>(OTS;3@!2Q-?5WV#FIy1)1J&5 znHi@QCn-(I7D)vubpI9{2WZ&T>IE9^d z>rYDP5C@F5DO+7l{jsj6mUVFq*MazU>0_GeKas`(?UK#gWcjsd?qT!Df)F}U@T17k${Bw@1w*u`)A|W&?v(xNNyAqNl>NQ{ z;SwPC;lqa~g!FxHw6lM#%?>$E#z8^e1QGz~x zI9{~AZjxCc>dO7@bd_^`ld#<+oH*dLnZ)FM`zTayxMFH3bixU!QQ~g9J@0SBvaf!;kJ#@c)Ikckc;SMAjErw!AV3DS z4JBg}6YS&1=Yc9nN%0bnj*4Qfryo?{UYG8EQ(yI%9UAgPsXr_30n+^3+eR!W5t5-- zU~N?4fwc+~!d=kd^xfU(oR)!eFvHiV^_pmkJ8xiE?C?xyVq#*jCuJmF8HhB8xpzCj z2C8eEoyuVybOp9;bp$3ol2DAy0A&FBjl6Zgzyg&`EB$1+OhBG^-`z|)LUq4vXG}hi zqdoJBEktn;d`Ojhune)4r@9ruzGM$ct?#bJAW}5}$J}y6c-ePn0v}^1n#eY+e}} z=ghr4gh(VkHT4##v$HWSAoPLr#c4V_!|0FJ>PD&AiH0*zhs0s289v+@~`^!d%yp`qU zRSx#L`uhFN4(Cm=@5Z;!W|iIFc0%Zo$srJMm^0H{9@y8w^7N(+g@A*h!IS{>=*$UA z`EhS=Z*>E}F;P)HK!Sp;psx<{LrosL_|7Vs9rM|<9>P+WmE`5g#sPz;=wlr_=6n6} zY;dObb018N%1PRgz@unEM=J+@VIW18%!LgtF{1K!4`LVA(a{0mo|1wBOc!NP%bRwc z$S^4(y3&8ERuA-6f{Cz3$ueKiBL|Vf^u@cMn;Q&jq{yazJwV8hrVL z(%Ni^@Vn+-MeV}lr;bgicXLh9reFOdf&{N(4inF{5eq~^r+!&p02!FM1U#PA9f;J~8mg0RVaCGGo`3uwYey2V?)hSYIKA_q3$(8{ zsGeUCnsZwU%6W+ig`R{zJI{3$Td`#GreJ#ZKcT@voSQ~U4SssVQASV&OfA~-^A?SF8sHsM@MsC+dq#= zPR_rS9Br8LcfCh@Lj?Uk(a~OO1150kK{ZVK*l{crEr_XTfW-kO##X?H(aEU zOvwQOAvX?4j1c-{x7}h@NBezB5PaNV5RqiQnpNYP?gHMRQm8k8KG>S92;oRJfD)lT z&%?t*=I}dmCDG6r-^xl{RcB&m_P6nwGt+{$CBWDB#z&h})>b}bmm|kMIW2h^s7s(4 zBQ9uvFseG9`XN7sPg`4?mx0Hmi4nhJFrDXAxArec(^5-!+5QbQZm)zswq5!%B3FuT zc9hgW%G^&4Y4xc~V3!Fy(JDKd+ilR^t&t=)DFFw+z>AVjLh+}NwlOS4WX>DQz%-n4 z<`7bKqXh<0+X$C2%@e}H&fyWA5|OcjZ+tk{7p9s%M;}9cm79c)?gHV98f~Z4jwO2077D?OTvrJHBtF46Fr+ior2anWa*5lvf}Y$iG_Rt&&kq)Ayy>Phfp z7(YsFqln=6*^EgEzQK4nngvK(nbwZV&`?tIgy+wh`t}93x3?60*Ohi&90%H|x6G}K zwIeqTdC%CGQ7B?c70T&*>UPKHhr~GT>FqYLNk%d6cjrw$+x5gtB?B%eyWc0pAO`?;U`_seJ3M2^81_+ zInP-;z9QSikcu`F4X9(V)jGs@dDXWcsr2u{mtUk@2FEqvM@CV2YOT`|ZTz=|vnmIW z+%d#RJ)gO_uSlEJ+nP_B1sJIR=r3gM!YHh5Ar2v-=EZMPITI2MF?{DmM7F^#pVSTb zhL+n{24)Ya8-Dlys?&IkHvplY3jzg+5EqxES!lnl?+fGbn?ylVT`gw!i$-@C9g^;W zdJpfnPH2kNmWJTOiDH{^|4bK zkUHA$ktTx*2_C92r)RAD0$e9!^!O>{sWRnZtsN+~zzx2Twgl!|r&H(>6 z{V#u3@cw|{T9F;0Vr}n(xLg(&Izej&Gp05Fywv5&%4;>bPi0>+W(`k`5J6?1KL1B6 z)j?>>eC*ZOXt4PGwiLh84!N;~o`kOLhtcO=qWX1~J~N;#9yPPdm)(UvUV~{BC>a6% z{_6`vL$LSZo#_l!-a>7ePR^7My;)w^shbOS-ZyM`V4QmVH)=hBMw^CUldO7);ITI(l&Zo8q9V+THrY#hg= z;v_fml5?CTjXQn1%Kxxm9F$8P8XPaJ+wg(@ZnR&+id>!85jC3sV#vVNk?kv8(;h=e z+?P|I-`)4*;lioQ*^F5uR?27oL8kxmIbf6YNxV{jq3Bv7KF2-MG1Ht&@Xs=Sz#V`? zg*-*^9*snMuB=m-EcT!FjeV-%p6Y}Aj9z!euhO&kxzTU_rFE8j3=_nH?SK zkK!UCq_QwEFo2%0YpmV3x!x~X(_HJe)Iov;WdNLOG?bKGzyXB5!>8g@Haak>07gnR zb=4Jsk&RsQ`fxV4y`Mucp|Jer8M(Jj<ukg;&kC_Gio5Rm`D$VY^WI}LOU z$QP2geLmmm`v*0WFuOh~_Zh^B>l(NE;f&^2{lX}$8pqRmLYf@qbE)X=wEqaMxdj6; zF#YiA!8Eewf)K%JF}#j|g5D zN;8Rflq#8&xO{?u0M3_4bCCB3r~8#99VYnF44A#`124??zTESdE~$iHE{=vVtXz=l zLDN3KWDxmcc>&X#IOeU+{VQfTvGi3Y}FF24e>xzfQ}zMvrW<0-HBF?)~zZ zXaZOMF2VJ0sVFO7xi*TVFLZ|4CC#C!LZMcQNX``~7aF`165XzSTr4coIdCpILy0JI zBG7cyU&t>X#M~4r(1IcQK~#^^VM@ezwzgh`lXGpy2^(eK{r2(qkYUNgII1&SRQ7@&De2d<*d3Wws8128sx&M5NCEJ_RX<$Q`k*MMWECLuE%u_A1g!IDj3Z``MkV#aR z?Y{#thOYle0||C+T~O}(Li1ANjPKlGJO0+FVH$j}KyRE2l9!b=>-{he6VPuoGe8Pa z6EICUf;aRYAN z{8y3b=@Swno5P+EqFrB+s%6j8O8P+zBcf`Q%|HpN3fMf2G=Tr@c*_t|y> z)$xD0vjw4o1h?Ifpwsi}!Ep*dfH#HX1f;W|D#~TqapXe4d<7q#UrfdU+OYzy#5Rsn zsRs~-@A4mprSV2HF#C2;!@(uVS;X+yF(QuK8qXslR57rQANT+KHpd4VxEGCOFsfo6 z79CA}>Qp98U?vMJW-_6PiAk92++a!UU>kEc(HedS!=XF^QVm#QGJc@KTCYWo3R@`s z;-6Twgr#h@K!~Bux8uie`Y~Mp-W+|Ng8*#+^G}~+hLj*|T?-2f=re$Zf$?kEC-1F> z#9WeT8~vgLADkulV2%Kv{TMn7uffPGt&mO1BY``>$nMinI5;gX`G^O`h?EB%K~D1@ zrFGMn+m*yic7M#0kdOfKW1*1QXO9Z6D@RVLwU|u9ArE${d?)p@;oRv%0EUf?t+r1( zWu2ZB58(R;i+$4niKL)Gze4YR2%8)uMh1|8#NL2}a_-~Ht2xhe z{a|X*0M-&Z+@ zhle^VL)v<=Xp5)v93;p)X#S1@u2iL#)G?lYI}Km{l0Moqa<4A2%l&T#geU&r%x#Hw zib`v)bfSoHBwo=Y;{N$N$7wR#w&jN!9XG-d!4?H;j}=9wvOco;<7r{73hjKece#f) zb0SaB&O-xhm04XvFOkQEfgJyFjZvia3`e@gZiyDQ zjR?(y{}#J&B` zPfpFrQ8x|rX~cDVxW67~xhHq6C|NuCSoZ(JT_+g3VVX$o<-_SMT9UFKYjOEs2eCa?mK?##(<-pto-f-aUkmlYGNBxSIiwDIFd&0Ek3y>q3F$*R`u7d6*+wv z8eAjTY9DFNmU%H_(PB*v_jLhhJL}hdsck4o{{6GWTPUw?-Mt{up?(7?JfM4vaPRKp zk$9mq5;qH8(emEvbIvB|SlKJtEW@F~tkU>;u%2ylr**x83U#O|W7@ytOF!ff@+rbBi&#Ai?#mci=g$dZNx! zm;CLZ!>4-VxPv@7bi&H=*PVzIRo_J5I3E0F1Mas@xbC}WLT89Io|r_QY`{HpQ6#pA z`?1iOVj4<4Xz?U0@tQd=MUh=IaNvJ<2LpM{fe)7ICc?LmJYN467mHW%qQ!juu?eq5 z^BdM?N9xgfehpqEXgc=zWULC)B&A3Qub7 zqtu?O+sYy}q((lTI{TTKsW!t&_c99S_S7_3`na#FT;AhAEXOhc~woH>8=b z8((h7kh7iesz!=RJQXT-bz zM9GC6zSN8^uiAC9?A^`EmsHGm6k-ZZW>%}(O%hO+bk@=HsnN=jyFN7Z9a z><8pcL@u*L-k#%f&t}J_*QytlxZAU_>D+T!)Yy7=4`N|}@Y(Ix(qE&+a_1)7W9H*K z-8sK)1-Jx#8qN6oRVG9?Ce5rkWAn!7L({fS-*-JfdhZ{X8TVfD+fxX1%eof7|4gn9 zg%8T_Ku8^LsGB1eir}^{cgnI=ihV?@Us>i_GB_=;6Zo&oP_iiOeW|QrRBDe*WrX!x zn$-T3!Ard#O64eXMPCcU%@O&m3r?xm;#EftdsZu7|1{(ygl`cXZUef6{_JN>k)LDz zJf~CS9e#A=MQ3@BU9eu%a8$yYkU7S)e6Ss=FZSlmbP8EraGcHcmVe3d<1-|~0Df1q zKi^Kw9CGiHfSGpN%7REjNIaKav-+s}mlOD$1W{rA4@5tQ4jC*~UgkNT760fnEZnY> zYhtT|2*vNHsX!5PrM-GA)ENVJg#*ujGxH!CaqfK)`f?nr`()d zdfrzBj)#tx?G;VRUlcXEuwh^-E(if;{$42BP~yJVdSYL9ygm-$2BaI3x(VZFYM00| zv&biY%%v_4(tX^`D52rB_oQ}g=6p}XpVpeZu(7oC!Y$YPVlh_?-a=7U@cR-@Jvv2Z zbMudcr|5PDkii*N^zkDlG+b%sMh}prv%WYui$wVM+REI5B?AnAYICF3rMX_@Yil-L z;x05@2`Nny3HoNhJbB@*8U?u;`1a2Fo+w-@rJDKFbSK^E%C#>9$enf7MWlN6B=$yL zn|FX`b6~Ma&PvhWLk7&oA+TL9W12lQ}gxh;X%!Ltb=#TXN549 z$yukoh#+OU*WEPTat5XO%p5WLiGZqfAjd5Es^^cpCRthn-El}5j_G-J4*Qt{pVi=I zuZgr?N#SP)iFyiiN>WniFC0eTJZG4@s-Jo1j?-j=Cb>Vk&M#QYQn61A0;Z*I&#ghLdo zd$8A7&?>%Fo#u*5W1lh78(2themL|sZh?hIvjN*jNW)LRK{hB^T0r?xL;McP;mcTH zp>LpBT}l|g=_GQJx#h!Kt&8c>!GZnXuw}co`=4R`Bio}S%n@Xxox`6 z;jM~-=UUha`>}ILKB_&GNj7I&LRLYc2iFPRJF6Spf+_Q$!j`|=F|IIV*C5V!UREH^<-_KcO~*p z%~;@haW$HDSz1|HH^#3@^05!MmY=?P6TY{V;PYw#b%~{30S@yjE{OXfTa-%n=(!WJ z3%wI169weRviege1m`Fl(uV&<1GOx{sNSq&iK)G0V_o&(?y%=Zx<=XV zT9)bFOuw#GiDT~vt!e}-(G>4n&98RdbA)P*ridnCC5^J5U)`Q~ETr-0+^%t6w22DC z?`cKJ&`h1UVW81eU~~6^zy(phDRtelT}9*dPl{2E)X1dMXFK2S{^<4#Pv0JlBE&() z2wZ&sU^K+Ce~{Fhi-l?_Tu2C|yc?K8pg&=%7OwPb4Vr}YGG*17|5^kG*A~n1xeAm0 zdelHVA#qWLJl@T9EXPb~$jz;6xqis;^@N0!q&M&9eW&Nsgw5Xf1J+G0OEs-C@fj#{ zJD+6z)(Pq0T?q1!Gp(=+!D8x?NTsX3w_%c}?^{!H()^?G=V=}JV4zA7dPz0poE?$( zW4?$t*WcEm+p4$DbgQ^h(Fw)%QV)A}b{_w}xqit@s0?KtUwKL40S<`*jk z6|wiZFTVPV%U_ZEn{pbFB#`P@4066gb-onWYBXA_fb!%MOG5+MgH9QXQ=v}f90d1GzHYutu+Gf&3ia9Rl zjJ@mTB6gvTEAkWSQ}Eun-N%WJy6=tJ*wurk#5%d={6>|@=4Tq}B=vaHoEGNRAc^EK zS(OS!TWE;97_qn(%|pu5JudK)HKC`k<$g^?v&*yxk!+ej(%kmHkK}6;DFl!c?#A-|uhPymsHrOoz>!i3P>7IX6|EEk zq*BEo5s6?@Rtqs?qDTN4Wf2JAum!bJ6hR>(4Je`n5)87X$R>-j?;o>J)acaG@kTVrFlU0)Kd~%y=3mqa_1w4+4ofG8-@}% zMGtJt?Sg!(683mpo@;ezin&YO>8fQBIm}-W999ixqa(3GQ}aBT7cfpWzGqcqZ2=k( z-x4e|IgS>+wWmY6@cXEXS@w0ZfmC`vqPFJ4>(`)`O@H6LniSIkyIdj>OmT|K{r3y~ z+@O)hQw@=z$-^m1lRh7qG}2ephM-36On=&$kdX6?AZUT+ePz%TJEZSEtvh1uL5HW4 z@mMyF0@B*vVClft>*}_E-4uPp6i>rc9Lj`VOzaMy`cI935DR@tOJUO4VB=o&rCAWG zYxH*|C^HC)ZaB*xFGzzJ(*u3VZ{`1gu;Cwq5^;i)@)Up0$o2==GIwA@UVL@Ja;6pju;Ro^A`q*F*wZ@C_el5$6FKp zU%(wv;}`TPf#@;sAlF{n8Reyy81ch*lj2PsTW=z)r^i#2ON2%IK0(#5vn zs(7_z6~y4gt{pPm%6NrAZ_C8#ianBNag(D!Tb~9y2+(^6C=82t?qnfbQ9x@cSBwKn zo?M=ZZ3$Axa0idWJk8&U+!>Qg&Dl8<~F3qU)31Qb;0{3gZu?mx7YI3cq z@5>8X0=z>%TJf9C5fd2-=4@&0hFH+S1WkgVL{gp#Q0N2Tl!#1wS0maXS|BZ!ma#GV zmM;e{BfJDHJ}}yok-GRjcriBt0889guow11VK=M?)8pA#yLx2&I~h*=&Z)e%m(h@mAGvW;_W=kC=o)* zo4d}!T;VgJVmG$ohWK8XY;d~Vme7k@%%WtMWozVwS*+x9LhM&bnMk>~rDw{}DS|XGCz__posm*S1IeK%+B6LouqgmDHPL0FUJel|sKSoW z$mWiU0#hrRxyD2|o|Z~0qCKDuIga!p@`1s=3poJUmEj;~!zmn; z6bk5e6xF(9{9C!a!9iw4V(Y adSew~^{1k4I8U-4`t4xnWLskG8T&VB3<>H0 literal 0 HcmV?d00001 diff --git a/doc/plantuml/mqtt_demo.pu b/doc/plantuml/mqtt_demo.pu index 8264b923e9..bfb9ee29d3 100644 --- a/doc/plantuml/mqtt_demo.pu +++ b/doc/plantuml/mqtt_demo.pu @@ -16,8 +16,8 @@ participant "MQTT Server" as server main -> server: Subscribe to topic filters server -> main: Subscription ACK -loop AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times - loop AWS_IOT_DEMO_MQTT_PUBLISH_BURST_SIZE times +loop IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times + loop IOT_DEMO_MQTT_PUBLISH_BURST_SIZE times main -> server: Publish "Hello world n!" server -> main: Publish ACK server -> server: Check for\nmatching subscriptions @@ -27,7 +27,7 @@ loop AWS_IOT_DEMO_MQTT_PUBLISH_BURST_COUNT times server -> callback: Publish ACK end - main -> main: Wait for\nAWS_IOT_DEMO_MQTT_BURST_SIZE\nmessages to be received + main -> main: Wait for\nIOT_DEMO_MQTT_BURST_SIZE\nmessages to be received end main -> server: Unsubscribe from topic filters diff --git a/doc/plantuml/shadow_demo.pu b/doc/plantuml/shadow_demo.pu new file mode 100644 index 0000000000..0b1e7d0d60 --- /dev/null +++ b/doc/plantuml/shadow_demo.pu @@ -0,0 +1,33 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica +autonumber + +box "Demo Device 1\n(part of demo application)" #LightBlue + participant "Main demo thread" as main +end box + +box "Demo Device 2\n(part of demo application)" #LightGreen + participant "Delta callback" as delta_callback +end box + +box "Shadow Service" #Orange + participant "Update topic" as update_topic + participant "Delta topic" as delta_topic +end box + +main -> delta_topic: SUBSCRIBE (set delta callback) +delta_topic -> delta_callback: SUBACK (delta callback set) + +loop AWS_IOT_DEMO_SHADOW_UPDATE_COUNT times + loop every AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS + main -> update_topic: New "desired" state + update_topic -> delta_topic: Generate delta\ndocument + delta_topic -> delta_callback: Send delta document + delta_callback -> delta_callback: Change state + delta_callback -> update_topic: Report state change + delta_callback -> main: Notify state change done + end +end + +@enduml From 727cc9ccf894d2ae2eccad475339a4d9c16bbefe Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 10 Apr 2019 23:08:17 -0700 Subject: [PATCH 089/844] Complete documentation for Shadow (#367) * Documentation for AwsIotShadow_SetUpdatedCallback * Documentation for AwsIotShadow_RemovePersistentSubscriptions * Document Shadow tests * Documentation for Shadow Delete and TimedDelete * Document Shadow Get and TimedGet * Documentation for Shadow Update and TimedUpdate --- README.md | 1 - demos/aws_iot_demo_shadow.c | 14 +- doc/config/mqtt | 8 +- doc/config/shadow | 5 +- doc/lib/mqtt.txt | 60 +-- doc/lib/shadow.txt | 31 ++ lib/include/aws_iot_shadow.h | 441 +++++++++++++++++- .../system/aws_iot_tests_shadow_system.c | 3 + tests/shadow/unit/aws_iot_tests_shadow_api.c | 4 + 9 files changed, 512 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 798a33c0d3..f4331e2255 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ This Beta library is a new design that inherits from both the AWS IoT Device SDK - MQTT persistent session support. Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: -- Documentation for Shadow library is incomplete. - Auto-reconnect for MQTT connections. - mbedTLS network stack. - Shadow JSON document generator. diff --git a/demos/aws_iot_demo_shadow.c b/demos/aws_iot_demo_shadow.c index 78a838697b..e5fab4b409 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/aws_iot_demo_shadow.c @@ -338,10 +338,12 @@ static void _shadowDeltaCallback( void * pCallbackContext, else { /* Send the Shadow update. Its result is not checked, as the Shadow updated - * callback will report if the Shadow was successfully updated. */ + * callback will report if the Shadow was successfully updated. Because the + * Shadow is constantly updated in this demo, the "Keep Subscriptions" flag + * is passed to this function. */ updateStatus = AwsIotShadow_Update( pCallbackParam->mqttConnection, &updateDocument, - 0, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, NULL, NULL ); @@ -750,10 +752,14 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, AWS_IOT_DEMO_SHADOW_UPDATE_COUNT, pUpdateDocument ); - /* Send the Shadow update. */ + /* Send the Shadow update. Because the Shadow is constantly updated in + * this demo, the "Keep Subscriptions" flag is passed to this function. + * Note that this flag only needs to be passed on the first call, but + * passing it for subsequent calls is fine. + */ updateStatus = AwsIotShadow_TimedUpdate( mqttConnection, &updateDocument, - 0, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, _TIMEOUT_MS ); /* Check the status of the Shadow update. */ diff --git a/doc/config/mqtt b/doc/config/mqtt index 62244d214e..a16e311eb5 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -16,17 +16,15 @@ PREDEFINED += "IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES=1" GENERATE_TAGFILE = doc/tag/mqtt.tag # Directories containing library source code. -INPUT = doc \ - doc/lib \ +INPUT = doc/lib \ lib/include \ lib/include/types \ lib/include/private \ lib/source/mqtt \ demos/ \ - tests/ \ tests/mqtt/unit \ - tests/mqtt/access \ - tests/mqtt/system + tests/mqtt/system \ + tests/mqtt/access # Library file names. FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt diff --git a/doc/config/shadow b/doc/config/shadow index a59ad74dcf..cf76705a93 100644 --- a/doc/config/shadow +++ b/doc/config/shadow @@ -17,7 +17,10 @@ INPUT = doc/lib/ \ lib/include \ lib/include/private \ lib/include/types \ - lib/source/shadow + lib/source/shadow \ + demos \ + tests/shadow/unit \ + tests/shadow/system # Library file names. FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index a30ebff222..ebd6b9a530 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -80,6 +80,36 @@ See @subpage mqtt_demo_config for configuration settings that change the behavio The main MQTT demo file is iot_demo_mqtt.c, which contains platform-independent code. See @ref building_demo for instructions on building the MQTT demo. */ +/** +@configpage{mqtt_demo,MQTT demo,Demo,demos} + +@section IOT_DEMO_MQTT_TOPIC_PREFIX +@brief The string prepended to topic filters in the demo. + +The string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the demo. + +@configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
+@configdefault `"iotmqttdemo"` + +@section IOT_DEMO_MQTT_PUBLISH_BURST_SIZE +@brief The number of messages published in each burst. + +Messages in a burst are rapidly published. After a complete burst is published, the demo waits for the messages to be received on a subscription topic filter. This value may be increased for higher throughput, at the expense of an increased chance of dropped messages. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. + +@configpossible Any positive integer.
+@configdefault `10` + +@section IOT_DEMO_MQTT_PUBLISH_BURST_COUNT +@brief The number of [publish bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) in this demo. + +Each burst will rapidly publish @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE messages. After publishing, the demo will wait for the published messages to be received on a subscription topic filter. + +This setting can be increased for a longer demo. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. + +@configpossible Any positive integer.
+@configdefault `10` +*/ + /** @page mqtt_tests Tests @brief Tests written for the MQTT library. @@ -185,36 +215,6 @@ Up to @ref IOT_TEST_MQTT_THREADS `*` @ref IOT_TEST_MQTT_PUBLISHES_PER_THREAD pub @configdefault When @ref IOT_STATIC_MEMORY_ONLY is `1`, this value defaults to @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. Otherwise, it defaults to `100`. */ -/** -@configpage{mqtt_demo,MQTT demo,Demo,demos} - -@section IOT_DEMO_MQTT_TOPIC_PREFIX -@brief The string prepended to topic filters in the demo. - -The string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the demo. - -@configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
-@configdefault `"iotmqttdemo"` - -@section IOT_DEMO_MQTT_PUBLISH_BURST_SIZE -@brief The number of messages published in each burst. - -Messages in a burst are rapidly published. After a complete burst is published, the demo waits for the messages to be received on a subscription topic filter. This value may be increased for higher throughput, at the expense of an increased chance of dropped messages. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. - -@configpossible Any positive integer.
-@configdefault `10` - -@section IOT_DEMO_MQTT_PUBLISH_BURST_COUNT -@brief The number of [publish bursts](@ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE) in this demo. - -Each burst will rapidly publish @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE messages. After publishing, the demo will wait for the published messages to be received on a subscription topic filter. - -This setting can be increased for a longer demo. The MQTT demo publishes a total of @ref IOT_DEMO_MQTT_PUBLISH_BURST_SIZE `*` @ref IOT_DEMO_MQTT_PUBLISH_BURST_COUNT messages. - -@configpossible Any positive integer.
-@configdefault `10` -*/ - /** @configpage{mqtt,MQTT library} diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index ef75ac5029..4179045f88 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -80,6 +80,37 @@ This value may be `0`, which causes a new Shadow update to be sent as soon as th @configdefault `3000` */ +/** +@page shadow_tests Tests +@brief Tests written for the Shadow library. + +The Shadow tests reside in the `tests/shadow` directory. They are divided into the following subdirectories: +- `system`: Shadow system tests. These tests require a network connection and AWS IoT credentials. The command line option `-n` may be passed to the test executable to disable these tests. +- `unit`: Shadow unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT test access files.](@ref mqtt_tests) + +See @subpage shadow_tests_config for configuration settings that change the behavior of the Shadow system tests. The Shadow unit tests require no special configuration. + +The current Shadow tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. See @ref building_tests for a guide on running the tests. +*/ + +/** +@configpage{shadow_tests,Shadow system tests,Test,tests} + +@section AWS_IOT_TEST_SHADOW_THING_NAME +@brief The Thing Name to use in the Shadow system tests. + +Thing Names are used to manage devices with AWS IoT. No default value is provided for Thing Names, so this constant must be defined. In addition to the Thing Name, AWS IoT credentials ([root CA certificate](@ref IOT_TEST_ROOT_CA), [client certificate](@ref IOT_TEST_CLIENT_CERT), and [client certificate private key](@ref IOT_TEST_PRIVATE_KEY)) must be provided to run the system tests. The [AWS IoT policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must also be properly configured. + +@configpossible A string representing an AWS IoT Thing Name. + +@section shadow_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S +@brief The keep-alive interval to use in the Shadow system tests. + +This constant is shared with the [MQTT tests](@ref mqtt_tests). + +@see @ref IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S on the MQTT tests config page. +*/ + /** @configpage{shadow,Shadow library} diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 3ca0d1a9cb..9988d334cf 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -93,9 +93,10 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeout ); /** * @brief One-time deinitialization function for the Shadow library. * - * This function frees resources taken in @ref shadow_function_init. It should - * be called to clean up the Shadow library. After this function returns, @ref - * shadow_function_init must be called again before calling any other Shadow + * This function frees resources taken in @ref shadow_function_init and deletes + * any [persistent subscriptions.](@ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS) + * It should be called to clean up the Shadow library. After this function returns, + * @ref shadow_function_init must be called again before calling any other Shadow * function. * * @warning No thread-safety guarantees are provided for this function. @@ -109,6 +110,64 @@ void AwsIotShadow_Cleanup( void ); /** * @brief Delete a Thing Shadow and receive an asynchronous notification when * the Delete completes. + * + * This function deletes any existing Shadow document for the given Thing Name. + * If the given Thing has no Shadow and this function is called, the result will + * be #AWS_IOT_SHADOW_NOT_FOUND. + * + * Deleting a Shadow involves sending an MQTT message to AWS IoT and waiting on + * a response. This message will always be sent at [MQTT QoS 0](@ref #IOT_MQTT_QOS_0). + * + * @param[in] mqttConnection The MQTT connection to use for Shadow delete. + * @param[in] pThingName The Thing Name associated with the Shadow to delete. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pDeleteOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Shadow delete + * completes. + * + * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully + * queuing a Shadow delete. + * @return If this function fails before queuing a Shadow delete, it will return one of: + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * @return Upon successful completion of the Shadow delete (either through an #AwsIotShadowCallbackInfo_t + * or #AwsIotShadow_Wait), the status will be #AWS_IOT_SHADOW_SUCCESS. + * @return Should the Shadow delete fail, the status will be one of: + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) + * + * @see @ref shadow_function_timeddelete for a blocking variant of this function. + * + * Example + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * // Shadow operation handle. + * AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + * + * // Queue a Shadow delete. + * AwsIotShadowError_t deleteResult = AwsIotShadow_Delete( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * 0, + * NULL, + * &deleteOperation ); + * + * // Shadow delete should return AWS_IOT_SHADOW_STATUS_PENDING upon success. + * if( deleteResult == AWS_IOT_SHADOW_STATUS_PENDING ) + * { + * // Wait for the Shadow delete to complete. + * deleteResult = AwsIotShadow_Wait( deleteOperation, 5000 ); + * + * // Delete result should be AWS_IOT_SHADOW_SUCCESS upon successfully + * // deleting an existing Shadow. + * } + * @endcode */ /* @[declare_shadow_delete] */ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, @@ -121,6 +180,27 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, /** * @brief Delete a Thing Shadow with a timeout. + * + * This function queues a Shadow delete, then waits for the result. Internally, this + * function is a call to @ref shadow_function_delete followed by @ref shadow_function_wait. + * See @ref shadow_function_delete for more information on the Shadow delete operation. + * + * @param[in] mqttConnection The MQTT connection to use for Shadow delete. + * @param[in] pThingName The Thing Name associated with the Shadow to delete. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. + * @param[in] timeoutMs If the Shadow service does not respond to the Shadow delete + * within this timeout, this function returns #AWS_IOT_SHADOW_TIMEOUT. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - #AWS_IOT_SHADOW_TIMEOUT + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) */ /* @[declare_shadow_timeddelete] */ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, @@ -133,6 +213,83 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection /** * @brief Retrieve a Thing Shadow and receive an asynchronous notification when * the Shadow document is received. + * + * This function retrieves the Thing Shadow document currently stored by the + * Shadow service. If a given Thing has no Shadow and this function is called, + * the result will be #AWS_IOT_SHADOW_NOT_FOUND. + * + * Shadow documents may be large, and their size is not known beforehand. + * Therefore, this function works best when memory is dynamically allocated. + * Because the Shadow document is retrieved in an MQTT PUBLISH packet, the MQTT + * library will allocate a buffer for the Shadow document using #IotMqtt_MallocMessage. + * + * The MQTT library may free the buffer for a retrieved Shadow document as soon + * as the [Shadow completion callback](@ref AwsIotShadowCallbackInfo_t) returns. + * Therefore, any data needed later must be copied from the Shadow document. + * Similarly, if the flag #AWS_IOT_SHADOW_FLAG_WAITABLE is given to this function + * (which indicates that the Shadow document will be needed after the Shadow + * operation completes), #AwsIotShadowDocumentInfo_t.mallocDocument must be + * provided to allocate a longer-lasting buffer. + * + * @note Because of the potentially large size of complete Shadow documents, it is more + * memory-efficient for most applications to use [delta callbacks] + * (@ref shadow_function_setdeltacallback) to retrieve Shadows from + * the Shadow service. + * + * @param[in] mqttConnection The MQTT connection to use for Shadow get. + * @param[in] pGetInfo Shadow document parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pGetOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Shadow get + * completes. + * + * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully + * queuing a Shadow get. + * @return If this function fails before queuing a Shadow get, it will return one of: + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * @return Upon successful completion of the Shadow get (either through an #AwsIotShadowCallbackInfo_t + * or #AwsIotShadow_Wait), the status will be #AWS_IOT_SHADOW_SUCCESS. + * @return Should the Shadow get fail, the status will be one of: + * - #AWS_IOT_SHADOW_NO_MEMORY (Memory could not be allocated for incoming document) + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) + * + * @see @ref shadow_function_timedget for a blocking variant of this function. + * + * Example + * @code{c} + * // Shadow get completion callback. The retrieved document will be in + * // pCallbackParam. Any data in the retrieved document needed after this + * // function returns must be copied. + * void _processRetrievedDocument( void * pCallbackContext, + * AwsIotShadowCallbackParam_t * pCallbackParam ); + * + * // Parameters and return value of Shadow get. + * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; + * AwsIotShadowDocumentInfo_t getInfo = { ... }; + * uint32_t timeout = 5000; // 5 seconds + * + * // Callback for get completion. + * AwsIotShadowCallbackInfo_t getCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + * getCallback.function = _processRetrievedDocument; + * + * // Shadow get operation. + * result = AwsIotShadow_Get( mqttConnection, + * &getInfo, + * 0, + * &getCallback, + * NULL ); + * + * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The function + * // _processRetrievedDocument will be invoked once the Shadow get completes. + * @endcode + * + * See @ref shadow_function_wait Example 2 for an example of using this + * function with #AWS_IOT_SHADOW_FLAG_WAITABLE and @ref shadow_function_wait. */ /* @[declare_shadow_get] */ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, @@ -144,6 +301,33 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, /** * @brief Retrieve a Thing Shadow with a timeout. + * + * This function queues a Shadow get, then waits for the result. Internally, this + * function is a call to @ref shadow_function_get followed by @ref shadow_function_wait. + * See @ref shadow_function_get for more information on the Shadow get operation. + * + * @param[in] mqttConnection The MQTT connection to use for Shadow get. + * @param[in] pGetInfo Shadow document parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. + * @param[in] timeoutMs If the Shadow service does not respond to the Shadow get + * within this timeout, this function returns #AWS_IOT_SHADOW_TIMEOUT. + * @param[out] pShadowDocument A pointer to a buffer containing the Shadow document + * retrieved by a Shadow get is placed here. The buffer was allocated with the function + * `pGetInfo->get.mallocDocument`. This output parameter is only valid if this function + * returns #AWS_IOT_SHADOW_SUCCESS. + * @param[out] pShadowDocumentLength The length of the Shadow document in + * `pShadowDocument` is placed here. This output parameter is only valid if this function + * returns #AWS_IOT_SHADOW_SUCCESS. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - #AWS_IOT_SHADOW_TIMEOUT + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) */ /* @[declare_shadow_timedget] */ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, @@ -157,6 +341,90 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, /** * @brief Send a Thing Shadow update and receive an asynchronous notification when * the Shadow Update completes. + * + * This function modifies the Thing Shadow document stored by the Shadow service. + * If a given Thing has no Shadow and this function is called, then a new Shadow + * is created. + * + * New JSON keys in the Shadow document will be appended. For example, if the Shadow service + * currently has a document containing key `example1` and this function sends a document + * only containing key `example2`, then the resulting document in the Shadow service + * will contain both `example1` and `example2`. + * + * Existing JSON keys in the Shadow document will be replaced. For example, if the Shadow + * service currently has a document containing `"example1": [0,1,2]` and this function sends + * a document containing key `"example1": [1,2,3]`, then the resulting document in the Shadow + * service will contain `"example1": [1,2,3]`. + * + * Successful Shadow updates will trigger the [Shadow updated callback] + * (@ref shadow_function_setupdatedcallback). If the resulting Shadow document contains + * different `desired` and `reported` keys, then the [Shadow delta callback] + * (@ref shadow_function_setdeltacallback) will be triggered as well. + * + * @attention All documents passed to this function must contain a `clientToken`. + * The [client token] + * (https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-document.html#client-token) + * is a string used to distinguish between Shadow updates. They are limited to 64 + * characters; attempting to use a client token longer than 64 characters will + * cause the Shadow update to fail. They must be unique at any given time, i.e. + * they may be reused as long as no two Shadow updates are using the same + * client token at the same time. + * + * @param[in] mqttConnection The MQTT connection to use for Shadow update. + * @param[in] pUpdateInfo Shadow document parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pUpdateOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Shadow update + * completes. + * + * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully + * queuing a Shadow update. + * @return If this function fails before queuing a Shadow update, it will return one of: + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * @return Upon successful completion of the Shadow update (either through an #AwsIotShadowCallbackInfo_t + * or #AwsIotShadow_Wait), the status will be #AWS_IOT_SHADOW_SUCCESS. + * @return Should the Shadow update fail, the status will be one of: + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) + * + * @see @ref shadow_function_timedupdate for a blocking variant of this function. + * + * Example + * @code{c} + * // Shadow update completion callback. + * void _updateComplete( void * pCallbackContext, + * AwsIotShadowCallbackParam_t * pCallbackParam ); + * + * // Parameters and return value of Shadow update. + * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; + * AwsIotShadowDocumentInfo_t updateInfo = { ... }; + * uint32_t timeout = 5000; // 5 seconds + * + * // Set Shadow document to send. + * updateInfo.update.pUpdateDocument = "{...}"; // Must contain clientToken + * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); + * + * // Callback for update completion. + * AwsIotShadowCallbackInfo_t updateCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + * updateCallback.function = _updateComplete; + * + * // Shadow update operation. + * result = AwsIotShadow_Update( mqttConnection, + * &updateInfo, + * 0, + * &updateCallback, + * NULL ); + * + * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The function + * // _updateComplete will be invoked once the Shadow update completes. + * @endcode + * + * See @ref shadow_function_wait Example 1 for an example of using this + * function with #AWS_IOT_SHADOW_FLAG_WAITABLE and @ref shadow_function_wait. */ /* @[declare_shadow_update] */ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, @@ -168,6 +436,26 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /** * @brief Send a Thing Shadow update with a timeout. + * + * This function queues a Shadow update, then waits for the result. Internally, this + * function is a call to @ref shadow_function_update followed by @ref shadow_function_wait. + * See @ref shadow_function_update for more information on the Shadow update operation. + * + * @param[in] mqttConnection The MQTT connection to use for Shadow update. + * @param[in] pUpdateInfo Shadow document parameters. + * @param[in] flags Flags which modify the behavior of this function. See @ref shadow_constants_flags. + * @param[in] timeoutMs If the Shadow service does not respond to the Shadow update + * within this timeout, this function returns #AWS_IOT_SHADOW_TIMEOUT. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_BAD_RESPONSE + * - #AWS_IOT_SHADOW_TIMEOUT + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) */ /* @[declare_shadow_timedupdate] */ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, @@ -213,7 +501,7 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_BAD_RESPONSE * - #AWS_IOT_SHADOW_TIMEOUT - * - A Shadow service rejected reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) + * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) * * Example 1 (Shadow Update) @@ -334,7 +622,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * @param[in] thingNameLength The length of `pThingName`. * @param[in] flags This parameter is for future-compatibility. Currently, flags * are not supported for this function and this parameter is ignored. - * @param[in] pDeltaCallback Callback function and to invoke for incoming delta + * @param[in] pDeltaCallback Callback function to invoke for incoming delta * documents. * * @return One of the following: @@ -352,8 +640,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * * Example * @code{c} - * #define _THING_NAME "Test_device" - * #define _THING_NAME_LENGTH ( sizeof( _THING_NAME ) - 1 ) + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) * * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; * AwsIotShadowCallbackInfo_t deltaCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; @@ -363,8 +651,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * * // Set the delta callback for the Thing "Test_device". * result = AwsIotShadow_SetDeltaCallback( mqttConnection, - * _THING_NAME, - * _THING_NAME_LENGTH, + * THING_NAME, + * THING_NAME_LENGTH, * 0, * &deltaCallback ); * @@ -374,8 +662,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * AwsIotShadowDocumentInfo_t updateInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; * * // Set the Thing Name for Shadow update. - * updateInfo.pThingName = _THING_NAME; - * updateInfo.thingNameLength = _THING_NAME_LENGTH; + * updateInfo.pThingName = THING_NAME; + * updateInfo.thingNameLength = THING_NAME_LENGTH; * * // Set the Shadow document to send. This document has different "reported" * // and "desired" states. It represents a scenario where a device is currently @@ -404,8 +692,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * // Once the delta callback is no longer needed, it may be removed by passing * // NULL as pDeltaCallback. * result = AwsIotShadow_SetDeltaCallback( mqttConnection, - * _THING_NAME, - * _THING_NAME_LENGTH, + * THING_NAME, + * THING_NAME_LENGTH, * 0, * NULL ); * @@ -424,6 +712,104 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConne /** * @brief Set a callback to be invoked when a Thing Shadow changes. + * + * The Shadow service publishes a state document to the `update/documents` topic + * whenever a Thing Shadow is successfully updated. This document reports the + * complete previous and current Shadow documents in `previous` and `current` + * sections, respectively. Therefore, the `update/documents` topic is useful + * for monitoring Shadow updates. + * + * An updated callback may be invoked whenever a document is published to + * `update/documents`. Each Thing may have a single updated callback set. This function + * modifies the updated callback for a specific Thing depending on the `pUpdatedCallback` + * parameter and the presence of any existing updated callback. + * - When no existing updated callback exists for a specific Thing, a new updated + * callback is added. + * - If there is an existing updated callback and `pUpdatedCallback` is not `NULL`, + * then the existing callback function and parameter are replaced with `pUpdatedCallback`. + * - If there is an existing updated callback and `pUpdatedCallback` is `NULL`, + * then the updated callback is removed. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription to `update/documents`. + * @param[in] pThingName The subscription to `update/documents` will be added for + * this Thing Name. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags This parameter is for future-compatibility. Currently, flags are + * not supported for this function and this parameter is ignored. + * @param[in] pUpdatedCallback Callback function to invoke for incoming updated documents. + * + * @return One of the following: + * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_BAD_PARAMETER + * - #AWS_IOT_SHADOW_NO_MEMORY + * - #AWS_IOT_SHADOW_MQTT_ERROR + * - #AWS_IOT_SHADOW_TIMEOUT + * + * @note Documents published to `update/documents` will be large, as they contain 2 + * complete Shadow state documents. If an updated callback is used, ensure that the + * device has sufficient memory for incoming documents. + * + * @see @ref shadow_function_setdeltacallback for the function to register callbacks + * for delta documents. + * + * Example + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * AwsIotShadowError_t result = AWS_IOT_SHADOW_STATUS_PENDING; + * AwsIotShadowCallbackInfo_t updatedCallback = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + * + * // _updatedCallbackFunction will be invoked when an updated document is received. + * updatedCallback.function = _updatedCallbackFunction; + * + * // Set the updated callback for the Thing "Test_device". + * result = AwsIotShadow_SetUpdatedCallback( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * 0, + * &updatedCallback ); + * + * // Check if the callback was successfully set. + * if( result == AWS_IOT_SHADOW_SUCCESS ) + * { + * AwsIotShadowDocumentInfo_t updateInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + * + * // Set the Thing Name for Shadow update. + * updateInfo.pThingName = THING_NAME; + * updateInfo.thingNameLength = THING_NAME_LENGTH; + * + * // Set the Shadow document to send. Any Shadow update will trigger the + * // updated callback. + * updateInfo.update.pUpdateDocument = + * "{" + * "\"state\": {" + * "\"reported\": { \"deviceOn\": false }" + * "}" + * "}"; + * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); + * + * // Send the Shadow document. A successful update will trigger the updated callback. + * result = AwsIotShadow_TimedUpdate( mqttConnection, + * &updateInfo, + * 0, + * 0, + * 5000 ); + * + * // After a successful Shadow update, the updated callback will be invoked. + * + * // Once the updated callback is no longer needed, it may be removed by + * // passing NULL as pUpdatedCallback. + * result = AwsIotShadow_SetUpdatedCallback( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * NULL ); + * + * // The return value from removing an updated callback should always be + * // success. + * assert( result == AWS_IOT_SHADOW_SUCCESS ); + * } + * @endcode */ /* @[declare_shadow_setupdatedcallback] */ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttConnection, @@ -436,7 +822,34 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon /** * @brief Remove persistent Thing Shadow operation topic subscriptions. * - * Not safe to call with any in-progress operation. Does not affect callbacks. + * Passing the flag @ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS to @ref shadow_function_delete, + * @ref shadow_function_get, or @ref shadow_function_update causes the Shadow operation topic + * subscriptions to be maintained for future calls to the same function. If a persistent + * subscription for a Shadow topic are no longer needed, this function may be used to + * remove it. + * + * @param[in] mqttConnection The MQTT connection associated with the persistent subscription. + * @param[in] pThingName The Thing Name associated with the persistent subscription. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags Flags that determine which subscriptions to remove. Valid values are + * the bitwise OR of the following individual flags: + * - @ref AWS_IOT_SHADOW_FLAG_REMOVE_DELETE_SUBSCRIPTIONS + * - @ref AWS_IOT_SHADOW_FLAG_REMOVE_GET_SUBSCRIPTIONS + * - @ref AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS + * + * @return On success: + * - #AWS_IOT_SHADOW_SUCCESS + * @return If an MQTT UNSUBSCRIBE packet cannot be sent, one of the following: + * - #AWS_IOT_SHADOW_NO_MEMORY + * - #AWS_IOT_SHADOW_MQTT_ERROR + * + * @note @ref shadow_function_cleanup removes all persistent subscriptions as well. + * + * @warning This function is not safe to call with any in-progress operations! + * It also does not affect delta and updated callbacks registered with @ref + * shadow_function_setdeltacallback and @ref shadow_function_setupdatedcallback, + * respectively. (See documentation for those functions on how to remove their + * callbacks). */ /* @[declare_shadow_removepersistentsubscriptions] */ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 96396a755f..7a03339461 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -678,6 +678,9 @@ TEST( Shadow_System, DeltaCallback ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the Shadow updated callback. + */ TEST( Shadow_System, UpdatedCallback ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 22d2d7d835..f9cfa0bc56 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -788,6 +788,10 @@ TEST( Shadow_Unit_API, GetMallocFail ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of @ref shadow_function_update when memory + * allocation fails at various points. + */ TEST( Shadow_Unit_API, UpdateMallocFail ) { int32_t i = 0, mqttErrorCount = 0; From 410d48c08bd049ff282d3b5a84c3c889f3cc9abb Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 11 Apr 2019 16:03:32 -0700 Subject: [PATCH 090/844] Use atomic in MQTT (#368) --- CMakeLists.txt | 15 -- lib/include/iot_atomic.h | 72 +++++++++ lib/include/private/iot_mqtt_internal.h | 15 -- lib/source/mqtt/CMakeLists.txt | 4 - lib/source/mqtt/iot_mqtt_api.c | 30 +++- lib/source/mqtt/iot_mqtt_serialize.c | 134 ++--------------- .../atomic/atomic_user_override_template.h | 140 ------------------ .../include/atomic/iot_atomic_gcc.h | 134 ++++------------- tests/common/unit/iot_tests_atomic.c | 76 +++++----- tests/iot_tests_config.h | 9 +- 10 files changed, 183 insertions(+), 446 deletions(-) create mode 100644 lib/include/iot_atomic.h delete mode 100644 platform/include/atomic/atomic_user_override_template.h rename lib/include/platform/atomic.h => platform/include/atomic/iot_atomic_gcc.h (61%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cd14096ee..4fe940e4e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,21 +25,6 @@ endif() set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) -# Atomic headers. -# If GCC -- -# for version <4.7.0, __sync_* shall be called. -# for version >= 4.7.0, __atomic_* shall be called. -# We only implemented atomic with __atomic_*. -if( CMAKE_C_COMPILER_ID STREQUAL GNU AND ( CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.7 OR CMAKE_C_COMPILER_VERSION VERSION_EQUAL 4.7 ) ) - add_definitions( -DCOMPILER_OPTION_GCC_ATOMIC_BUILTIN=1 ) -endif() - -# If Clang -- -# Clang supports GCC's __atomic_* built-in functions since 3.1 release. -if( CMAKE_C_COMPILER_ID STREQUAL Clang AND ( CMAKE_C_COMPILER_VERSION VERSION_GREATER 3.1 OR CMAKE_C_COMPILER_VERSION VERSION_EQUAL 3.1 ) ) - add_definitions( -DCOMPILER_OPTION_GCC_ATOMIC_BUILTIN=1 ) -endif() - # SDK include paths. include_directories( ${PROJECT_SOURCE_DIR}/lib/include ${PROJECT_SOURCE_DIR}/platform/include diff --git a/lib/include/iot_atomic.h b/lib/include/iot_atomic.h new file mode 100644 index 0000000000..41c91b9e37 --- /dev/null +++ b/lib/include/iot_atomic.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_atomic.h + * @brief Chooses the appropriate atomic operations header. + * + * This file first checks if the operating system is Amazon FreeRTOS. If so, the + * atomic header provided with Amazon FreeRTOS is used. + * + * Otherwise, this file checks the compiler and chooses an appropriate atomic + * header depending on the compiler. + * + * If no supported compilers are available, then a "generic" atomic implementation + * is used. The generic implementation is less efficient, but will work on all + * platforms. + */ + +#ifndef IOT_ATOMIC_H_ +#define IOT_ATOMIC_H_ + +#if defined( __free_rtos__ ) + /* Use the FreeRTOS atomic operation header on FreeRTOS. */ + #include "atomic.h" +#elif defined( __GNUC__ ) + /* Both clang and gcc define __GNUC__, but only clang defines __clang__ */ + #ifdef __clang__ + /* clang versions 3.1.0 and greater have built-in atomic support. */ + #define CLANG_VERSION ( __clang_major__ * 100 + __clang_minor__ ) + #if CLANG_VERSION > 301 + /* clang is compatible with gcc atomic extensions. */ + #include "atomic/iot_atomic_gcc.h" + #else + #define IOT_ATOMIC_GENERIC 1 + #endif + #else + /* GCC versions 4.7.0 and greater have built-in atomic support. */ + #define GCC_VERSION ( __GNUC__ * 100 + __GNUC_MINOR__ ) + #if GCC_VERSION >= 407 + #include "atomic/iot_atomic_gcc.h" + #else + #define IOT_ATOMIC_GENERIC 1 + #endif + #endif /* ifdef __clang__ */ +#else /* if defined( __free_rtos__ ) */ + #define IOT_ATOMIC_GENERIC 1 +#endif /* if defined( __free_rtos__ ) */ + +/* Include the generic atomic header if no supported compiler was found. */ +#if ( IOT_ATOMIC_GENERIC == 1 ) + #include "atomic/iot_atomic_generic.h" +#endif + +#endif /* ifndef IOT_ATOMIC_H_ */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 742ab12113..6a12e28c1c 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -462,21 +462,6 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, /*-------------------- MQTT packet serializer functions ---------------------*/ -/** - * @brief Initialize the MQTT packet serializer. Called by @ref mqtt_function_init - * when initializing the MQTT library. - * - * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_INIT_FAILED. - */ -IotMqttError_t _IotMqtt_InitSerialize( void ); - -/** - * @brief Free resources taken by #_IotMqtt_InitSerialize. - * - * No parameters, no return values. - */ -void _IotMqtt_CleanupSerialize( void ); - /** * @brief Get the MQTT packet type from a stream of bytes off the network. * diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index b7cedbe54c..b7646a52ad 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,7 +1,3 @@ -#Whether to use atomic or not. -#Default to "no". -add_definitions( -DIOT_ATOMIC_OPERATION=0 ) - # MQTT library source files. add_library( iotmqtt SHARED iot_mqtt_api.c diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 32644ac7f7..f4c29ef5b9 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -847,12 +847,25 @@ IotMqttError_t IotMqtt_Init( void ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Initialize MQTT serializer. */ - if( _IotMqtt_InitSerialize() != IOT_MQTT_SUCCESS ) + /* Call any additional serializer initialization function if serializer + * overrides are enabled. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_InitSerializeAdditional + if( _IotMqtt_InitSerializeAdditional() == false ) + { + status = IOT_MQTT_INIT_FAILED; + } + else + { + _EMPTY_ELSE_MARKER; + } + #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Log initialization status. */ + if( status != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to initialize MQTT library serializer. " ); - - status = IOT_MQTT_INIT_FAILED; } else { @@ -866,8 +879,13 @@ IotMqttError_t IotMqtt_Init( void ) void IotMqtt_Cleanup() { - /* Clean up MQTT serializer. */ - _IotMqtt_CleanupSerialize(); + /* Call any additional serializer cleanup initialization function if serializer + * overrides are enabled. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_CleanupSerializeAdditional + _IotMqtt_CleanupSerializeAdditional(); + #endif + #endif IotLogInfo( "MQTT library cleanup done." ); } diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index d5f3ebe673..d90236626f 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -42,8 +42,7 @@ #include "platform/iot_threads.h" /* Atomic operations. */ -#include "platform/atomic.h" - +#include "iot_atomic.h" /*-----------------------------------------------------------*/ @@ -283,50 +282,18 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, }; #endif -/** - * @brief Guards access to the packet identifier counter. - * - * Each packet should have a unique packet identifier. This mutex ensures that only - * one thread at a time may read the global packet identifer. The mutex is only needed - * when atomic operation is not supported. - */ -#if ( IOT_ATOMIC_OPERATION != 1 ) - static IotMutex_t _packetIdentifierMutex; -#endif /* IOT_ATOMIC_OPERATION */ - /*-----------------------------------------------------------*/ static uint16_t _nextPacketIdentifier( void ) { - #if ( IOT_ATOMIC_OPERATION == 1 ) - - /* MQTT protocol specifies 2 bytes for Packet Identifier. - * For atomic operation, operating 16-bit on 32-bit MCU is not faster than operating 32-bit directly. - * Here, using addition two bytes and casting, to achieve the same implementation as in the other branch. */ - static uint32_t nextPacketIdentifier = 1; - - return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); - #else /* ( IOT_ATOMIC_OPERATION != 1 ) */ - static uint16_t nextPacketIdentifier = 1; - uint16_t newPacketIdentifier = 0; - - /* Lock the packet identifier mutex so that only one thread may read and - * modify nextPacketIdentifier. */ - IotMutex_Lock( &( _packetIdentifierMutex ) ); - - /* Read the next packet identifier. */ - newPacketIdentifier = nextPacketIdentifier; - - /* The next packet identifier will be greater by 2. This prevents packet - * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet - * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ - nextPacketIdentifier = ( uint16_t ) ( nextPacketIdentifier + ( ( uint16_t ) 2 ) ); - - /* Unlock the packet identifier mutex. */ - IotMutex_Unlock( &( _packetIdentifierMutex ) ); - - return newPacketIdentifier; - #endif /* IOT_ATOMIC_OPERATION */ + /* MQTT specifies 2 bytes for the packet identifier; however, operating on + * 32-bit integers is generally faster. */ + static uint32_t nextPacketIdentifier = 1; + + /* The next packet identifier will be greater by 2. This prevents packet + * identifiers from ever being 0, which is not allowed by MQTT 3.1.1. Packet + * identifiers will follow the sequence 1,3,5...65535,1,3,5... */ + return ( uint16_t ) Atomic_Add_u32( &nextPacketIdentifier, 2 ); } /*-----------------------------------------------------------*/ @@ -610,89 +577,6 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_InitSerialize( void ) -{ - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - - /* Create the packet identifier mutex. - * This is only needed when atomic operation is not supported. */ - #if ( IOT_ATOMIC_OPERATION != 1 ) - bool packetMutexCreated = false; - - packetMutexCreated = IotMutex_Create( &( _packetIdentifierMutex ), false ); - - if( packetMutexCreated == false ) - { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); - } - else - { - _EMPTY_ELSE_MARKER; - } - #endif /* IOT_ATOMIC_OPERATION */ - - /* Call any additional serializer initialization function if serializer - * overrides are enabled. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef _IotMqtt_InitSerializeAdditional - if( _IotMqtt_InitSerializeAdditional() == false ) - { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); - } - else - { - _EMPTY_ELSE_MARKER; - } - #endif - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - - _IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Only needs to clean up when atomic operation is not supported. */ - #if ( IOT_ATOMIC_OPERATION != 1 ) - /* Clean up on error. */ - if( status != IOT_MQTT_SUCCESS ) - { - if( packetMutexCreated == true ) - { - IotMutex_Destroy( &( _packetIdentifierMutex ) ); - } - else - { - _EMPTY_ELSE_MARKER; - } - } - else - { - _EMPTY_ELSE_MARKER; - } - #endif /* IOT_ATOMIC_OPERATION */ - - _IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_CleanupSerialize( void ) -{ - #if ( IOT_ATOMIC_OPERATION != 1 ) - - /* Destroy the packet identifier mutex. - * Only needed when atomic operation is not supported.*/ - IotMutex_Destroy( &( _packetIdentifierMutex ) ); - #endif /* IOT_ATOMIC_OPERATION */ - - /* Call any additional serializer cleanup initialization function is serializer - * overrides are enabled. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef _IotMqtt_CleanupSerializeAdditional - _IotMqtt_CleanupSerializeAdditional(); - #endif - #endif -} - -/*-----------------------------------------------------------*/ - uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ) { diff --git a/platform/include/atomic/atomic_user_override_template.h b/platform/include/atomic/atomic_user_override_template.h deleted file mode 100644 index 02c6375d06..0000000000 --- a/platform/include/atomic/atomic_user_override_template.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file atomic_user_override.h - * @brief Provides a method for user to override atomic implementation. - */ - -#include -#include - -/** - * GCC flatten/always_inline equivalent. - * - * @todo Define this macro, refer to compiler specific docs. - * Please note that, Atomic_*() functions in atomic.h are calling - * into user defined functions in this header file. Please make - * sure atomic nested function calls are "flattened" and "inlined", - * from caller's perspective. - */ -#define FORCE_INLINE - -/** - * GCC always_inline equivalent. - * - * @todo Define this macro, refer to compiler specific docs. - * This, in combine with FORCE_INLINE, should guarantee the atomic - * functions are always inlined even without compiler optimization. - * The behavior can be checked with compiler objdump disassembly. - */ -#define FORCE_INLINE_NESTED - -/** - * GCC __asm__ __volatile__ equivalent. - * - * @todo Optionally define this macro, refer to compiler specific docs. - */ -#define COMPILER_ASM_VOLATILE - -/** - * Atomic user defined implementations. - * - * @todo Implement ALL below functions. - * - * @note Here, we are mearly providing extension points for user to - * supply their own atomic implementation. It's solely up to user - * to validate whether the implementation is correct. - * - * Though, two suggested approaches -- - * (1) (always works) Emulate atomic with critical section. - * (2) (recommended) ISA supported atomic instruction(s). - * - * Example -- Emulating atomic with critical section - * @code{.c} - * static FORCE_INLINE int32_t Atomic_Add_i32_User_Override(int32_t volatile * pAddend, int32_t lCount) - * { - * int32_t lCurrent; - * CriticalSectionType_t temp; - * - * // Platform specific definition of enter/exit critical section. - * // The return value from enter critical section function call - * // could be previous interrupt mask or priority. - * temp = ENTER_CRITICAL_SECTION( ); - * lCurrent = *pAddend; - * *pAddend += lCount; - * EXIT_CRITICAL_SECTION( temp ); - * - * return lCurrent; - * } - * @endcode - * - * Example -- ISA supported atomic instruction(s), with compiler support. - * @code{.c} - * // A compiler has atomic built-in extension. - * // Include atomic header in app code. - * - * static FORCE_INLINE int32_t Atomic_Add_i32_User_Override(int32_t volatile * pAddend, int32_t lCount) - * { - * // Assume atomic_fetch_add() does something similar to GCC __atomic_fetch_add() - * // with memory order __ATOMIC_SEQ_CST. - * return atomic_fetch_add(pAddend, lCount); - * } - * @endcode - * - * Example -- ISA supported atomic instructions(s), without compiler built-in function. - * @code{.c} - * static FORCE_INLINE int32_t Atomic_Add_i32_User_Override(int32_t volatile * pAddend, int32_t lCount) - * { - * // assembly, if you have to. - * __asm__ __volatile__(......); - * } - * @endcode - * - */ - -/*----------------------------- Swap && CAS ------------------------------*/ -static FORCE_INLINE_NESTED uint32_t Atomic_CompareAndSwap_u32_User_Override( uint32_t volatile * pDestination, - uint32_t ulExchange, - uint32_t ulComparand ); -static FORCE_INLINE_NESTED void * Atomic_SwapPointers_p32_User_Override( void * volatile * ppDestination, - void * pExchange ); -static FORCE_INLINE_NESTED uint32_t Atomic_CompareAndSwapPointers_p32_User_Override( void * volatile * ppDestination, - void * pExchange, - void * pComparand ); - -/*----------------------------- Arithmetic ------------------------------*/ -static FORCE_INLINE_NESTED uint32_t Atomic_Add_u32_User_Override( uint32_t volatile * pAddend, - uint32_t lCount ); -static FORCE_INLINE_NESTED uint32_t Atomic_Subtract_u32_User_Override( uint32_t volatile * pAddend, - uint32_t lCount ); -static FORCE_INLINE_NESTED uint32_t Atomic_Increment_u32_User_Override( uint32_t volatile * pAddend ); -static FORCE_INLINE_NESTED uint32_t Atomic_Decrement_u32_User_Override( uint32_t volatile * pAddend ); - -/*----------------------------- Bitwise Logical ------------------------------*/ -static FORCE_INLINE_NESTED uint32_t Atomic_OR_u32_User_Override( uint32_t volatile * pDestination, - uint32_t ulValue ); -static FORCE_INLINE_NESTED uint32_t Atomic_AND_u32_User_Override( uint32_t volatile * pDestination, - uint32_t ulValue ); -static FORCE_INLINE_NESTED uint32_t Atomic_NAND_u32_User_Override( uint32_t volatile * pDestination, - uint32_t ulValue ); -static FORCE_INLINE_NESTED uint32_t Atomic_XOR_u32_User_Override( uint32_t volatile * pDestination, - uint32_t ulValue ); diff --git a/lib/include/platform/atomic.h b/platform/include/atomic/iot_atomic_gcc.h similarity index 61% rename from lib/include/platform/atomic.h rename to platform/include/atomic/iot_atomic_gcc.h index 7986e4f851..f133cf7975 100644 --- a/lib/include/platform/atomic.h +++ b/platform/include/atomic/iot_atomic_gcc.h @@ -20,52 +20,25 @@ */ /** - * @file atomic.h - * @brief Atomic operations. + * @file iot_atomic_gcc.h + * @brief Atomic operations implemented on GCC extensions. + * + * Compatible with GCC and clang. */ -#ifndef ATOMIC_H -#define ATOMIC_H +#ifndef IOT_ATOMIC_GCC_H +#define IOT_ATOMIC_GCC_H /* Standard includes. */ +#include #include -/* Compiler specific implementation. */ -#if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - -/* Standard boolean for CAS "strong". */ - #include - -/* Default GCC compiler. - * If GCC is used && the target architecture supports atomic instructions, - * (e.g. ARM, Xtensa, ... ) - * there's no reason to not use GCC built-in atomics. - * Thus, no user extension is provided in this branch. - * - * If you believe atomic built-in is not compiled correctly by GCC for your target, - * please use the other branch and provide your own implementation for atomic. - * - * attribute flatten is not needed in this branch. - */ - #define FORCE_INLINE inline __attribute__( ( always_inline ) ) - -/* GCC asm volatile. - * This can be helpful when user wants to observe how atomic inline function is - * compiled. Simply add a tag (or no-op) before and after atomic inline function - * call, and observe objdump disassembly. Though, it is not suggested to call this - * macro in user application code. +/** + * @brief GCC function attribute to always inline a function. */ - #define COMPILER_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) - -#else /* if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) */ +#define FORCE_INLINE inline __attribute__( ( always_inline ) ) -/* Using none GCC compiler || user customization - * User needs to figure out compiler specific syntax for always-inline and flatten.*/ - #include "atomic/atomic_user_override_template.h" - -#endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ - -/*----------------------------- Swap && CAS ------------------------------*/ +/*----------------------- Swap and Compare-and-Swap -------------------------*/ /** * Atomic compare-and-swap @@ -86,14 +59,10 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes { uint32_t ulReturnValue = 0; - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - if( __atomic_compare_exchange( pDestination, &ulComparand, &ulExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) - { - ulReturnValue = 1; - } - #else - ulReturnValue = Atomic_CompareAndSwap_u32_User_Override( pDestination, ulExchange, ulComparand ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + if( __atomic_compare_exchange( pDestination, &ulComparand, &ulExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) + { + ulReturnValue = 1; + } return ulReturnValue; } @@ -111,14 +80,11 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDes static FORCE_INLINE void * Atomic_SwapPointers_p32( void * volatile * ppDestination, void * pExchange ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - void * pReturnValue; - __atomic_exchange( ppDestination, &pExchange, &pReturnValue, __ATOMIC_SEQ_CST ); + void * pReturnValue; + + __atomic_exchange( ppDestination, &pExchange, &pReturnValue, __ATOMIC_SEQ_CST ); - return pReturnValue; - #else - return Atomic_SwapPointers_p32_User_Override( ppDestination, pExchange ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return pReturnValue; } /** @@ -140,14 +106,10 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwapPointers_p32( void * volatile { uint32_t ulReturnValue = 0; - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - if( __atomic_compare_exchange( ppDestination, &pComparand, &pExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) - { - ulReturnValue = 1; - } - #else - ulReturnValue = Atomic_CompareAndSwapPointers_p32_User_Override( ppDestination, pExchange, pComparand ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + if( __atomic_compare_exchange( ppDestination, &pComparand, &pExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) + { + ulReturnValue = 1; + } return ulReturnValue; } @@ -168,11 +130,7 @@ static FORCE_INLINE uint32_t Atomic_CompareAndSwapPointers_p32( void * volatile static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAddend, uint32_t lCount ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_add( pAddend, lCount, __ATOMIC_SEQ_CST ); - #else - return Atomic_Add_u32_User_Override( pAddend, lCount ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_add( pAddend, lCount, __ATOMIC_SEQ_CST ); } /** @@ -188,11 +146,7 @@ static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAddend, static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pAddend, uint32_t lCount ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_sub( pAddend, lCount, __ATOMIC_SEQ_CST ); - #else - return Atomic_Subtract_u32_User_Override( pAddend, lCount ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_sub( pAddend, lCount, __ATOMIC_SEQ_CST ); } /** @@ -206,11 +160,7 @@ static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pAddend, */ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAddend ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_add( pAddend, 1, __ATOMIC_SEQ_CST ); - #else - return Atomic_Increment_u32_User_Override( pAddend ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_add( pAddend, 1, __ATOMIC_SEQ_CST ); } /** @@ -224,11 +174,7 @@ static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAddend ) */ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pAddend ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_sub( pAddend, 1, __ATOMIC_SEQ_CST ); - #else - return Atomic_Decrement_u32_User_Override( pAddend ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_sub( pAddend, 1, __ATOMIC_SEQ_CST ); } /*----------------------------- Bitwise Logical ------------------------------*/ @@ -246,11 +192,7 @@ static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pAddend ) static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pDestination, uint32_t ulValue ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_or( pDestination, ulValue, __ATOMIC_SEQ_CST ); - #else - return Atomic_OR_u32_User_Override( pDestination, ulValue ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_or( pDestination, ulValue, __ATOMIC_SEQ_CST ); } /** @@ -266,11 +208,7 @@ static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pDestination, static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pDestination, uint32_t ulValue ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_and( pDestination, ulValue, __ATOMIC_SEQ_CST ); - #else - return Atomic_AND_u32_User_Override( pDestination, ulValue ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_and( pDestination, ulValue, __ATOMIC_SEQ_CST ); } /** @@ -286,11 +224,7 @@ static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pDestination, static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pDestination, uint32_t ulValue ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_nand( pDestination, ulValue, __ATOMIC_SEQ_CST ); - #else - return Atomic_NAND_u32_User_Override( pDestination, ulValue ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_nand( pDestination, ulValue, __ATOMIC_SEQ_CST ); } /** @@ -305,11 +239,7 @@ static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pDestination, static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pDestination, uint32_t ulValue ) { - #if ( COMPILER_OPTION_GCC_ATOMIC_BUILTIN == 1 ) - return __atomic_fetch_xor( pDestination, ulValue, __ATOMIC_SEQ_CST ); - #else - return Atomic_XOR_u32_User_Override( pDestination, ulValue ); - #endif /* COMPILER_OPTION_GCC_ATOMIC_BUILTIN */ + return __atomic_fetch_xor( pDestination, ulValue, __ATOMIC_SEQ_CST ); } -#endif /* ATOMIC_H */ +#endif /* IOT_ATOMIC_GCC_H */ diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index 2f8fbea5d3..47785d986f 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -35,11 +35,11 @@ #include IOT_CONFIG_FILE #endif -/* NULL */ +/* Standard includes. */ #include -/* Platform layer includes. */ -#include "platform/atomic.h" +/* Atomic include. */ +#include "iot_atomic.h" /* Test framework includes. */ #include "unity.h" @@ -115,9 +115,9 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) ulCasComparator_32 = MAGIC_NUMBER_32BIT_1; ulCasNewValue_32 = MAGIC_NUMBER_32BIT_2; - COMPILER_ASM_VOLATILE( "atomic_cas_1: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_1: " ); ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); - COMPILER_ASM_VOLATILE( "atomic_cas_1_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_1_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == ulCasNewValue_32, "Atomic_CompareAndSwap_u32 -- did not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); @@ -125,9 +125,9 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) /* #2 -- CAS, comparator from the same mem location. */ ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; - COMPILER_ASM_VOLATILE( "atomic_cas_2: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_2: " ); ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, MAGIC_NUMBER_32BIT_2, ulCasDestination_32 ); - COMPILER_ASM_VOLATILE( "atomic_cas_2_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_2_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_2, "Atomic_CompareAndSwap_u32 -- did not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); @@ -137,9 +137,9 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapNewValue_32 = &ulCasNewValue_32; pReturnValue_32 = NULL; - COMPILER_ASM_VOLATILE( "atomic_xchg_32bit: " ); + IOT_TEST_ASM_VOLATILE( "atomic_xchg_32bit: " ); pReturnValue_32 = Atomic_SwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32 ); - COMPILER_ASM_VOLATILE( "atomic_xchg_32bit_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_xchg_32bit_end: " ); TEST_ASSERT_MESSAGE( pSwapDestination_32 == &ulCasNewValue_32, "Atomic_SwapPointers_p32 -- did not swap." ); TEST_ASSERT_MESSAGE( pReturnValue_32 == &ulCasDestination_32, "Atomic_SwapPointers_p32 -- expected to return previous value." ); @@ -149,9 +149,9 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapNewValue_8 = &uCasComparator_8; pReturnValue_8 = NULL; - COMPILER_ASM_VOLATILE( "atomic_xchg_8bit: nop" ); + IOT_TEST_ASM_VOLATILE( "atomic_xchg_8bit: nop" ); pReturnValue_8 = Atomic_SwapPointers_p32( ( void ** ) &pSwapDestination_8, pSwapNewValue_8 ); - COMPILER_ASM_VOLATILE( "atomic_xchg_8bit_end: nop" ); + IOT_TEST_ASM_VOLATILE( "atomic_xchg_8bit_end: nop" ); TEST_ASSERT_MESSAGE( pSwapDestination_8 == &uCasComparator_8, "Atomic_SwapPointers_p32 -- did not swap." ); TEST_ASSERT_MESSAGE( pReturnValue_8 == &uCasDestination_8, "Atomic_SwapPointers_p32 -- expected to return previous value." ); @@ -160,9 +160,9 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapDestination_32 = &ulCasDestination_32; pSwapNewValue_32 = &ulCasNewValue_32; - COMPILER_ASM_VOLATILE( "atomic_CAS_pointers: nop" ); + IOT_TEST_ASM_VOLATILE( "atomic_CAS_pointers: nop" ); ulReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); - COMPILER_ASM_VOLATILE( "atomic_CAS_pointers_end: nop" ); + IOT_TEST_ASM_VOLATILE( "atomic_CAS_pointers_end: nop" ); TEST_ASSERT_MESSAGE( ( intptr_t ) pSwapDestination_32 == ( intptr_t ) pSwapNewValue_32, "Atomic_CompareAndSwapPointers_p32 -- did not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwapPointers_p32 -- expected return value true." ); @@ -187,7 +187,7 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #0 -- Some examples for user -- * casting number in range 0x80000000-0xFFFFFFFF. - * COMPILER_ASM_VOLATILE is omitted, as this is the normal user caller routine. */ + * IOT_TEST_ASM_VOLATILE is omitted, as this is the normal user caller routine. */ uAddend_32 = ( uint32_t ) 0xFFFFFFFE; /* signed 0xFFFFFFFE is -2, 2's complement. */ uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); @@ -215,9 +215,9 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) uAddend_32 = 0; uDelta_32 = 1; - COMPILER_ASM_VOLATILE( "atomic_add_reg: " ); + IOT_TEST_ASM_VOLATILE( "atomic_add_reg: " ); uReturnValue_32 = Atomic_Add_u32( &uAddend_32, uDelta_32 ); - COMPILER_ASM_VOLATILE( "atomic_add_reg_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_add_reg_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); @@ -225,9 +225,9 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #2 -- add immediate */ uAddend_32 = 0; - COMPILER_ASM_VOLATILE( "atomic_add_imme: " ); + IOT_TEST_ASM_VOLATILE( "atomic_add_imme: " ); uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); - COMPILER_ASM_VOLATILE( "atomic_add_imme_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_add_imme_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add immediate number correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); @@ -236,18 +236,18 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) uAddend_8 = 1; uAddend_32 = ( uint32_t ) uAddend_8; - COMPILER_ASM_VOLATILE( "atomic_add_8bit: " ); + IOT_TEST_ASM_VOLATILE( "atomic_add_8bit: " ); uReturnValue_32 = Atomic_Add_u32( &uAddend_32, UINT8_MAX ); - COMPILER_ASM_VOLATILE( "atomic_add_8bit_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_add_8bit_end: " ); TEST_ASSERT_MESSAGE( ( uint8_t ) uReturnValue_32 == 1, "Atomic_Add_u32 -- did not roll over correctly." ); /* #4 -- sub, almost but not underflow */ uAddend_32 = 1; - COMPILER_ASM_VOLATILE( "atomic_sub_reg: " ); + IOT_TEST_ASM_VOLATILE( "atomic_sub_reg: " ); uReturnValue_32 = Atomic_Subtract_u32( &uAddend_32, 1 ); - COMPILER_ASM_VOLATILE( "atomic_sub_reg_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_sub_reg_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Subtract_u32 -- did not subtract correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Subtract_u32 -- expected return value 1." ); @@ -255,9 +255,9 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #5 -- inc, sanity check */ uAddend_32 = 0; - COMPILER_ASM_VOLATILE( "atomic_inc: " ); + IOT_TEST_ASM_VOLATILE( "atomic_inc: " ); uReturnValue_32 = Atomic_Increment_u32( &uAddend_32 ); - COMPILER_ASM_VOLATILE( "atomic_inc_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_inc_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Increment_u32 -- did not increment correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Increment_u32 -- expected return value 0." ); @@ -265,9 +265,9 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #6 -- dec, sanity check */ uAddend_32 = 1; - COMPILER_ASM_VOLATILE( "atomic_dec: " ); + IOT_TEST_ASM_VOLATILE( "atomic_dec: " ); uReturnValue_32 = Atomic_Decrement_u32( &uAddend_32 ); - COMPILER_ASM_VOLATILE( "atomic_dec_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_dec_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Decrement_u32 -- did not decrement correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Decrement_u32 -- expected return value 1." ); @@ -283,9 +283,9 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_1; ulOp2 = MAGIC_NUMBER_32BIT_2; - COMPILER_ASM_VOLATILE( "atomic_and: " ); + IOT_TEST_ASM_VOLATILE( "atomic_and: " ); ulReturnValue = Atomic_AND_u32( &ulOp1, ulOp2 ); - COMPILER_ASM_VOLATILE( "atomic_and_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_and_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0xA0A0A0A0, "Atomic_AND_u32 -- did not ANDed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_AND_u32 -- expected return value 0xA5A5A5A5." ); @@ -294,9 +294,9 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_2; ulOp2 = MAGIC_NUMBER_32BIT_3; - COMPILER_ASM_VOLATILE( "atomic_or: " ); + IOT_TEST_ASM_VOLATILE( "atomic_or: " ); ulReturnValue = Atomic_OR_u32( &ulOp1, ulOp2 ); - COMPILER_ASM_VOLATILE( "atomic_or_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_or_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0xF0F0F0FF, "Atomic_OR_u32 -- did not ORed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_2, "Atomic_AND_u32 -- expected return value 0xF0F0F0F0." ); @@ -305,9 +305,9 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_1; ulOp2 = MAGIC_NUMBER_32BIT_2; - COMPILER_ASM_VOLATILE( "atomic_nand: " ); + IOT_TEST_ASM_VOLATILE( "atomic_nand: " ); ulReturnValue = Atomic_NAND_u32( &ulOp1, ulOp2 ); - COMPILER_ASM_VOLATILE( "atomic_nand_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_nand_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0x5F5F5F5F, "Atomic_NAND_u32 -- did not NANDed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_NAND_u32 -- expected return value 0xA5A5A5A5." ); @@ -316,9 +316,9 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_1; ulOp2 = MAGIC_NUMBER_32BIT_2; - COMPILER_ASM_VOLATILE( "atomic_xor: " ); + IOT_TEST_ASM_VOLATILE( "atomic_xor: " ); ulReturnValue = Atomic_XOR_u32( &ulOp1, ulOp2 ); - COMPILER_ASM_VOLATILE( "atomic_XOR_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_XOR_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0x55555555, "Atomic_XOR_u32 -- did not XORed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_XOR_u32 -- expected return value 0xA5A5A5A5." ); @@ -340,9 +340,9 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) ulCasComparator_32 = MAGIC_NUMBER_32BIT_2; ulCasNewValue_32 = MAGIC_NUMBER_32BIT_3; - COMPILER_ASM_VOLATILE( "atomic_cas_neq: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_neq: " ); ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); - COMPILER_ASM_VOLATILE( "atomic_cas_neq_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_neq_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwap_u32 -- should not swap." ); @@ -352,9 +352,9 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) pCasComparator_32 = &ulCasComparator_32; pCasNewValue_32 = &ulCasNewValue_32; - COMPILER_ASM_VOLATILE( "atomic_cas_pointers_neq: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_pointers_neq: " ); ulReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); - COMPILER_ASM_VOLATILE( "atomic_cas_pointers_neq_end: " ); + IOT_TEST_ASM_VOLATILE( "atomic_cas_pointers_neq_end: " ); TEST_ASSERT_MESSAGE( ( intptr_t ) pCasDestination_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 651678b647..3249c3c6ee 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -174,10 +174,17 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; #define IotTestNetwork_Init IotNetworkOpenssl_Init #define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup +/* Macro for placing inline assembly in test code. */ +#define IOT_TEST_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) + +#ifndef __GNUC__ + #error "Unsupported compiler. Only gcc and clang are supported." +#endif + /* Configure code coverage testing if enabled. */ #if IOT_TEST_COVERAGE == 1 /* Define the empty else marker if test coverage is enabled. */ - #define _EMPTY_ELSE_MARKER asm volatile ( "nop" ) + #define _EMPTY_ELSE_MARKER IOT_TEST_ASM_VOLATILE( "nop" ) /* Define a custom logging puts function. This function allows coverage * testing of logging functions, but prevents excessive logs from being From 7bfeb44ffe534a36656b8967032b253fba655f2b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 12 Apr 2019 11:50:38 -0700 Subject: [PATCH 091/844] Submodule tinycbor and move unity to third_party (#369) --- .gitmodules | 6 + CMakeLists.txt | 26 +- lib/source/common/CMakeLists.txt | 1 - lib/source/defender/CMakeLists.txt | 2 +- lib/source/serializer/CMakeLists.txt | 3 +- .../{common => serializer}/iot_json_utils.c | 0 lib/source/shadow/CMakeLists.txt | 2 +- tests/iot_tests_config.h | 16 +- tests/unity/CMakeLists.txt | 5 - third_party/include/cbor.h | 486 ------ third_party/tinycbor/CMakeLists.txt | 16 +- third_party/tinycbor/LICENSE | 21 - third_party/tinycbor/assert_p.h | 29 - third_party/tinycbor/cborconstants_p.h | 52 - third_party/tinycbor/cborencoder.c | 626 -------- .../cborencoder_close_container_checked.c | 82 -- third_party/tinycbor/cborerrorstrings.c | 165 --- third_party/tinycbor/cborparser.c | 1300 ----------------- third_party/tinycbor/cborparser_dup_string.c | 113 -- third_party/tinycbor/cborpretty.c | 471 ------ third_party/tinycbor/compilersupport_p.h | 234 --- third_party/tinycbor/extract_number_p.h | 78 - third_party/tinycbor/math_support_p.h | 47 - third_party/tinycbor/tinycbor | 1 + third_party/unity/CMakeLists.txt | 5 + .../unity}/unity/fixture/unity_fixture.c | 0 .../unity}/unity/fixture/unity_fixture.h | 0 .../unity/fixture/unity_fixture_internals.h | 0 .../fixture/unity_fixture_malloc_overrides.h | 0 .../unity}/unity/fixture/unity_memory_mt.c | 0 {tests => third_party/unity}/unity/unity.c | 0 {tests => third_party/unity}/unity/unity.h | 0 .../unity}/unity/unity_internals.h | 0 third_party/unity/unity/unity_memory_mt.c | 89 ++ 34 files changed, 135 insertions(+), 3741 deletions(-) create mode 100644 .gitmodules rename lib/source/{common => serializer}/iot_json_utils.c (100%) delete mode 100644 tests/unity/CMakeLists.txt delete mode 100644 third_party/include/cbor.h delete mode 100644 third_party/tinycbor/LICENSE delete mode 100644 third_party/tinycbor/assert_p.h delete mode 100644 third_party/tinycbor/cborconstants_p.h delete mode 100644 third_party/tinycbor/cborencoder.c delete mode 100644 third_party/tinycbor/cborencoder_close_container_checked.c delete mode 100644 third_party/tinycbor/cborerrorstrings.c delete mode 100644 third_party/tinycbor/cborparser.c delete mode 100644 third_party/tinycbor/cborparser_dup_string.c delete mode 100644 third_party/tinycbor/cborpretty.c delete mode 100644 third_party/tinycbor/compilersupport_p.h delete mode 100644 third_party/tinycbor/extract_number_p.h delete mode 100644 third_party/tinycbor/math_support_p.h create mode 160000 third_party/tinycbor/tinycbor create mode 100644 third_party/unity/CMakeLists.txt rename {tests => third_party/unity}/unity/fixture/unity_fixture.c (100%) rename {tests => third_party/unity}/unity/fixture/unity_fixture.h (100%) rename {tests => third_party/unity}/unity/fixture/unity_fixture_internals.h (100%) rename {tests => third_party/unity}/unity/fixture/unity_fixture_malloc_overrides.h (100%) rename {tests => third_party/unity}/unity/fixture/unity_memory_mt.c (100%) rename {tests => third_party/unity}/unity/unity.c (100%) rename {tests => third_party/unity}/unity/unity.h (100%) rename {tests => third_party/unity}/unity/unity_internals.h (100%) create mode 100644 third_party/unity/unity/unity_memory_mt.c diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..0944d04412 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "third_party/unity/unity"] + path = third_party/unity/unity + url = https://github.com/ThrowTheSwitch/Unity.git +[submodule "third_party/tinycbor/tinycbor"] + path = third_party/tinycbor/tinycbor + url = https://github.com/intel/tinycbor.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fe940e4e3..c33a5204be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,28 +27,32 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # SDK include paths. include_directories( ${PROJECT_SOURCE_DIR}/lib/include - ${PROJECT_SOURCE_DIR}/platform/include - ${PROJECT_SOURCE_DIR}/third_party/include ) + ${PROJECT_SOURCE_DIR}/platform/include ) + +# TinyCBOR include path. +include_directories( ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) # Demo include path. include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) # Use either the demo or test configuration file. if( ${IOT_BUILD_TESTS} ) - # Tests include directories. + # Define the constant to enable test access. + add_definitions( -DIOT_BUILD_TESTS=1 ) + + # Test include paths. include_directories( ${PROJECT_SOURCE_DIR}/tests - tests/unity - tests/unity/fixture tests/mqtt/access ) - # Define the constant to enable test access. - add_definitions( -DIOT_BUILD_TESTS=1 ) + # Unity test framework include paths. + include_directories( third_party/unity/unity + third_party/unity/unity/fixture ) # Tests config file. add_definitions( -DIOT_CONFIG_FILE="iot_tests_config.h" ) # Build unity test framework. - add_subdirectory( tests/unity ) + add_subdirectory( third_party/unity ) else() add_definitions( -DIOT_CONFIG_FILE="iot_demo_config.h" ) endif() @@ -59,9 +63,6 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( platform/source/posix ) endif() -# Thrid party libraries (tinycbor). -add_subdirectory( third_party/tinycbor ) - # Common libraries (linear containers, logging, etc.) add_subdirectory( lib/source/common ) @@ -77,6 +78,9 @@ add_subdirectory( lib/source/serializer ) # Defender library. add_subdirectory( lib/source/defender ) +# TinyCBOR library (third-party). +add_subdirectory( third_party/tinycbor ) + # Demo executables. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( demos/posix ) diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index b41c562c9f..65a2abf0fb 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,7 +1,6 @@ # Common libraries source files. add_library( iotcommon SHARED iot_common.c - iot_json_utils.c iot_logging.c iot_taskpool.c static_memory/iot_static_memory_common.c diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt index f343807b06..0dfcd46bdc 100644 --- a/lib/source/defender/CMakeLists.txt +++ b/lib/source/defender/CMakeLists.txt @@ -13,4 +13,4 @@ target_link_libraries( awsiotdefender iotserializer iotmqtt iotplatform ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) target_link_libraries( awsiotdefender unity ) -endif() \ No newline at end of file +endif() diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index 0ad7d1d176..e2ef090cb6 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -1,5 +1,6 @@ # Serializer library source files. add_library( iotserializer SHARED + iot_json_utils.c cbor/iot_serializer_tinycbor_decoder.c cbor/iot_serializer_tinycbor_encoder.c ) @@ -12,4 +13,4 @@ target_link_libraries( iotserializer tinycbor ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotserializer unity ) -endif() \ No newline at end of file +endif() diff --git a/lib/source/common/iot_json_utils.c b/lib/source/serializer/iot_json_utils.c similarity index 100% rename from lib/source/common/iot_json_utils.c rename to lib/source/serializer/iot_json_utils.c diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index 10536b1dab..b3db46817d 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -9,7 +9,7 @@ add_library( awsiotshadow SHARED set_target_properties( awsiotshadow PROPERTIES VERSION ${PROJECT_VERSION} ) # Link required libraries. -target_link_libraries( awsiotshadow iotcommon iotplatform iotmqtt ) +target_link_libraries( awsiotshadow iotserializer iotplatform iotmqtt ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/tests/iot_tests_config.h b/tests/iot_tests_config.h index 3249c3c6ee..b88921b6e1 100644 --- a/tests/iot_tests_config.h +++ b/tests/iot_tests_config.h @@ -82,6 +82,14 @@ #define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) +/* Static memory resource settings for the tests. These values must be large + * enough to support the stress tests. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #define IOT_MQTT_CONNECTIONS ( 2 ) + #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) + #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) +#endif + /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ #define IotThreads_Malloc unity_malloc_mt @@ -94,14 +102,6 @@ #define IotTest_Malloc unity_malloc_mt #define IotTest_Free unity_free_mt -/* Static memory resource settings for the tests. These values must be large - * enough to support the stress tests. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #define IOT_MQTT_CONNECTIONS ( 2 ) - #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) - #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) -#endif - /* Memory allocation function configuration for libraries affected by * IOT_STATIC_MEMORY_ONLY. */ #if IOT_STATIC_MEMORY_ONLY == 0 diff --git a/tests/unity/CMakeLists.txt b/tests/unity/CMakeLists.txt deleted file mode 100644 index dec7e4cd5a..0000000000 --- a/tests/unity/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Unity test framework. -add_library( unity SHARED - unity.c - fixture/unity_fixture.c - fixture/unity_memory_mt.c ) diff --git a/third_party/include/cbor.h b/third_party/include/cbor.h deleted file mode 100644 index d56c354417..0000000000 --- a/third_party/include/cbor.h +++ /dev/null @@ -1,486 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#ifndef CBOR_H -#define CBOR_H - -#include -#include -#include -#include -#include -#include - - - -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - -#ifndef SIZE_MAX -/* Some systems fail to define SIZE_MAX in , even though C99 requires it... - * Conversion from signed to unsigned is defined in 6.3.1.3 (Signed and unsigned integers) p2, - * which says: "the value is converted by repeatedly adding or subtracting one more than the - * maximum value that can be represented in the new type until the value is in the range of the - * new type." - * So -1 gets converted to size_t by adding SIZE_MAX + 1, which results in SIZE_MAX. - */ -# define SIZE_MAX ((size_t)-1) -#endif - -#ifndef CBOR_API -# define CBOR_API -#endif -#ifndef CBOR_PRIVATE_API -# define CBOR_PRIVATE_API -#endif -#ifndef CBOR_INLINE_API -# if defined(__cplusplus) -# define CBOR_INLINE inline -# define CBOR_INLINE_API inline -# else -# define CBOR_INLINE_API static CBOR_INLINE -# if defined(_MSC_VER) -# define CBOR_INLINE __inline -# elif defined(__GNUC__) -# define CBOR_INLINE __inline__ -# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -# define CBOR_INLINE inline -# else -# define CBOR_INLINE -# endif -# endif -#endif - -typedef enum CborType { - CborIntegerType = 0x00, - CborByteStringType = 0x40, - CborTextStringType = 0x60, - CborArrayType = 0x80, - CborMapType = 0xa0, - CborTagType = 0xc0, - CborSimpleType = 0xe0, - CborBooleanType = 0xf5, - CborNullType = 0xf6, - CborUndefinedType = 0xf7, - CborHalfFloatType = 0xf9, - CborFloatType = 0xfa, - CborDoubleType = 0xfb, - - CborInvalidType = 0xff /* equivalent to the break byte, so it will never be used */ -} CborType; - -typedef uint64_t CborTag; -typedef enum CborKnownTags { - CborDateTimeStringTag = 0, /* RFC 3339 format: YYYY-MM-DD hh:mm:ss+zzzz */ - CborUnixTime_tTag = 1, - CborPositiveBignumTag = 2, - CborNegativeBignumTag = 3, - CborDecimalTag = 4, - CborBigfloatTag = 5, - CborExpectedBase64urlTag = 21, - CborExpectedBase64Tag = 22, - CborExpectedBase16Tag = 23, - CborUriTag = 32, - CborBase64urlTag = 33, - CborBase64Tag = 34, - CborRegularExpressionTag = 35, - CborMimeMessageTag = 36, /* RFC 2045-2047 */ - CborSignatureTag = 55799 -} CborKnownTags; - -/* Error API */ - -typedef enum CborError { - CborNoError = 0, - - /* errors in all modes */ - CborUnknownError, - CborErrorUnknownLength, /* request for length in array, map, or string with indeterminate length */ - CborErrorAdvancePastEOF, - CborErrorIO, - - /* parser errors streaming errors */ - CborErrorGarbageAtEnd = 256, - CborErrorUnexpectedEOF, - CborErrorUnexpectedBreak, - CborErrorUnknownType, /* can only heppen in major type 7 */ - CborErrorIllegalType, /* type not allowed here */ - CborErrorIllegalNumber, - CborErrorIllegalSimpleType, /* types of value less than 32 encoded in two bytes */ - - /* parser errors in strict mode parsing only */ - CborErrorUnknownSimpleType = 512, - CborErrorUnknownTag, - CborErrorInappropriateTagForType, - CborErrorDuplicateObjectKeys, - CborErrorInvalidUtf8TextString, - - /* encoder errors */ - CborErrorTooManyItems = 768, - CborErrorTooFewItems, - - /* internal implementation errors */ - CborErrorDataTooLarge = 1024, - CborErrorNestingTooDeep, - CborErrorUnsupportedType, - - /* errors in converting to JSON */ - CborErrorJsonObjectKeyIsAggregate, - CborErrorJsonObjectKeyNotString, - CborErrorJsonNotImplemented, - - CborErrorOutOfMemory = (int) (~0U / 2 + 1), - CborErrorInternalError = (int) ~0U -} CborError; - -CBOR_API const char *cbor_error_string(CborError error); - -/* Encoder API */ -struct CborEncoder -{ - union { - uint8_t *ptr; - ptrdiff_t bytes_needed; - } data; - const uint8_t *end; - size_t added; - int flags; -}; -typedef struct CborEncoder CborEncoder; - -static const size_t CborIndefiniteLength = SIZE_MAX; - -CBOR_API void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags); -CBOR_API CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value); -CBOR_API CborError cbor_encode_int(CborEncoder *encoder, int64_t value); -CBOR_API CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value); -CBOR_API CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value); -CBOR_API CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag); -CBOR_API CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length); -CBOR_INLINE_API CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) -{ return cbor_encode_text_string(encoder, string, strlen(string)); } -CBOR_API CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length); -CBOR_API CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value); - -CBOR_INLINE_API CborError cbor_encode_boolean(CborEncoder *encoder, bool value) -{ return cbor_encode_simple_value(encoder, (int)value - 1 + (CborBooleanType & 0x1f)); } -CBOR_INLINE_API CborError cbor_encode_null(CborEncoder *encoder) -{ return cbor_encode_simple_value(encoder, CborNullType & 0x1f); } -CBOR_INLINE_API CborError cbor_encode_undefined(CborEncoder *encoder) -{ return cbor_encode_simple_value(encoder, CborUndefinedType & 0x1f); } - -CBOR_INLINE_API CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) -{ return cbor_encode_floating_point(encoder, CborHalfFloatType, value); } -CBOR_INLINE_API CborError cbor_encode_float(CborEncoder *encoder, float value) -{ return cbor_encode_floating_point(encoder, CborFloatType, &value); } -CBOR_INLINE_API CborError cbor_encode_double(CborEncoder *encoder, double value) -{ return cbor_encode_floating_point(encoder, CborDoubleType, &value); } - -CBOR_API CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length); -CBOR_API CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length); -CBOR_API CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder); -CBOR_API CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder); - -CBOR_INLINE_API size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) -{ - return (size_t)(encoder->data.ptr - buffer); -} - -CBOR_INLINE_API size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) -{ - return encoder->end ? 0 : (size_t)encoder->data.bytes_needed; -} - -/* Parser API */ - -enum CborParserIteratorFlags -{ - CborIteratorFlag_IntegerValueTooLarge = 0x01, - CborIteratorFlag_NegativeInteger = 0x02, - CborIteratorFlag_UnknownLength = 0x04, - CborIteratorFlag_ContainerIsMap = 0x20 -}; - -struct CborParser -{ - const uint8_t *end; - int flags; -}; -typedef struct CborParser CborParser; - -struct CborValue -{ - const CborParser *parser; - const uint8_t *ptr; - uint32_t remaining; - uint16_t extra; - uint8_t type; - uint8_t flags; -}; -typedef struct CborValue CborValue; - -CBOR_API CborError cbor_parser_init(const uint8_t *buffer, size_t size, int flags, CborParser *parser, CborValue *it); - -CBOR_INLINE_API bool cbor_value_at_end(const CborValue *it) -{ return it->remaining == 0; } -CBOR_INLINE_API const uint8_t *cbor_value_get_next_byte(const CborValue *it) -{ return it->ptr; } -CBOR_API CborError cbor_value_advance_fixed(CborValue *it); -CBOR_API CborError cbor_value_advance(CborValue *it); -CBOR_INLINE_API bool cbor_value_is_container(const CborValue *it) -{ return it->type == CborArrayType || it->type == CborMapType; } -CBOR_API CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed); -CBOR_API CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed); - -CBOR_PRIVATE_API uint64_t _cbor_value_decode_int64_internal(const CborValue *value); -CBOR_INLINE_API uint64_t _cbor_value_extract_int64_helper(const CborValue *value) -{ - return value->flags & CborIteratorFlag_IntegerValueTooLarge ? - _cbor_value_decode_int64_internal(value) : value->extra; -} - -CBOR_INLINE_API bool cbor_value_is_valid(const CborValue *value) -{ return value && value->type != CborInvalidType; } -CBOR_INLINE_API CborType cbor_value_get_type(const CborValue *value) -{ return (CborType)value->type; } - -/* Null & undefined type */ -CBOR_INLINE_API bool cbor_value_is_null(const CborValue *value) -{ return value->type == CborNullType; } -CBOR_INLINE_API bool cbor_value_is_undefined(const CborValue *value) -{ return value->type == CborUndefinedType; } - -/* Booleans */ -CBOR_INLINE_API bool cbor_value_is_boolean(const CborValue *value) -{ return value->type == CborBooleanType; } -CBOR_INLINE_API CborError cbor_value_get_boolean(const CborValue *value, bool *result) -{ - assert(cbor_value_is_boolean(value)); - *result = !!value->extra; - return CborNoError; -} - -/* Simple types */ -CBOR_INLINE_API bool cbor_value_is_simple_type(const CborValue *value) -{ return value->type == CborSimpleType; } -CBOR_INLINE_API CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) -{ - assert(cbor_value_is_simple_type(value)); - *result = (uint8_t)value->extra; - return CborNoError; -} - -/* Integers */ -CBOR_INLINE_API bool cbor_value_is_integer(const CborValue *value) -{ return value->type == CborIntegerType; } -CBOR_INLINE_API bool cbor_value_is_unsigned_integer(const CborValue *value) -{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger) == 0; } -CBOR_INLINE_API bool cbor_value_is_negative_integer(const CborValue *value) -{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger); } - -CBOR_INLINE_API CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) -{ - assert(cbor_value_is_integer(value)); - *result = _cbor_value_extract_int64_helper(value); - return CborNoError; -} - -CBOR_INLINE_API CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) -{ - assert(cbor_value_is_unsigned_integer(value)); - *result = _cbor_value_extract_int64_helper(value); - return CborNoError; -} - -CBOR_INLINE_API CborError cbor_value_get_int64(const CborValue *value, int64_t *result) -{ - assert(cbor_value_is_integer(value)); - *result = (int64_t) _cbor_value_extract_int64_helper(value); - if (value->flags & CborIteratorFlag_NegativeInteger) - *result = -*result - 1; - return CborNoError; -} - -CBOR_INLINE_API CborError cbor_value_get_int(const CborValue *value, int *result) -{ - assert(cbor_value_is_integer(value)); - *result = (int) _cbor_value_extract_int64_helper(value); - if (value->flags & CborIteratorFlag_NegativeInteger) - *result = -*result - 1; - return CborNoError; -} - -CBOR_API CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result); -CBOR_API CborError cbor_value_get_int_checked(const CborValue *value, int *result); - -CBOR_INLINE_API bool cbor_value_is_length_known(const CborValue *value) -{ return (value->flags & CborIteratorFlag_UnknownLength) == 0; } - -/* Tags */ -CBOR_INLINE_API bool cbor_value_is_tag(const CborValue *value) -{ return value->type == CborTagType; } -CBOR_INLINE_API CborError cbor_value_get_tag(const CborValue *value, CborTag *result) -{ - assert(cbor_value_is_tag(value)); - *result = _cbor_value_extract_int64_helper(value); - return CborNoError; -} -CBOR_API CborError cbor_value_skip_tag(CborValue *it); - -/* Strings */ -CBOR_INLINE_API bool cbor_value_is_byte_string(const CborValue *value) -{ return value->type == CborByteStringType; } -CBOR_INLINE_API bool cbor_value_is_text_string(const CborValue *value) -{ return value->type == CborTextStringType; } - -CBOR_INLINE_API CborError cbor_value_get_string_length(const CborValue *value, size_t *length) -{ - assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); - if (!cbor_value_is_length_known(value)) - return CborErrorUnknownLength; - uint64_t v = _cbor_value_extract_int64_helper(value); - *length = (size_t)v; - if (*length != v) - return CborErrorDataTooLarge; - return CborNoError; -} - -CBOR_PRIVATE_API CborError _cbor_value_copy_string(const CborValue *value, void *buffer, - size_t *buflen, CborValue *next); -CBOR_PRIVATE_API CborError _cbor_value_dup_string(const CborValue *value, void **buffer, - size_t *buflen, CborValue *next); - -CBOR_API CborError cbor_value_calculate_string_length(const CborValue *value, size_t *length); - -CBOR_INLINE_API CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, - size_t *buflen, CborValue *next) -{ - assert(cbor_value_is_text_string(value)); - return _cbor_value_copy_string(value, buffer, buflen, next); -} -CBOR_INLINE_API CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, - size_t *buflen, CborValue *next) -{ - assert(cbor_value_is_byte_string(value)); - return _cbor_value_copy_string(value, buffer, buflen, next); -} - -CBOR_INLINE_API CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, - size_t *buflen, CborValue *next) -{ - assert(cbor_value_is_text_string(value)); - return _cbor_value_dup_string(value, (void **)buffer, buflen, next); -} -CBOR_INLINE_API CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, - size_t *buflen, CborValue *next) -{ - assert(cbor_value_is_byte_string(value)); - return _cbor_value_dup_string(value, (void **)buffer, buflen, next); -} - -/* ### TBD: partial reading API */ - -CBOR_API CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result); - -/* Maps and arrays */ -CBOR_INLINE_API bool cbor_value_is_array(const CborValue *value) -{ return value->type == CborArrayType; } -CBOR_INLINE_API bool cbor_value_is_map(const CborValue *value) -{ return value->type == CborMapType; } - -CBOR_INLINE_API CborError cbor_value_get_array_length(const CborValue *value, size_t *length) -{ - assert(cbor_value_is_array(value)); - if (!cbor_value_is_length_known(value)) - return CborErrorUnknownLength; - uint64_t v = _cbor_value_extract_int64_helper(value); - *length = (size_t)v; - if (*length != v) - return CborErrorDataTooLarge; - return CborNoError; -} - -CBOR_INLINE_API CborError cbor_value_get_map_length(const CborValue *value, size_t *length) -{ - assert(cbor_value_is_map(value)); - if (!cbor_value_is_length_known(value)) - return CborErrorUnknownLength; - uint64_t v = _cbor_value_extract_int64_helper(value); - *length = (size_t)v; - if (*length != v) - return CborErrorDataTooLarge; - return CborNoError; -} - -CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element); - -/* Floating point */ -CBOR_INLINE_API bool cbor_value_is_half_float(const CborValue *value) -{ return value->type == CborHalfFloatType; } -CBOR_API CborError cbor_value_get_half_float(const CborValue *value, void *result); - -CBOR_INLINE_API bool cbor_value_is_float(const CborValue *value) -{ return value->type == CborFloatType; } -CBOR_INLINE_API CborError cbor_value_get_float(const CborValue *value, float *result) -{ - assert(cbor_value_is_float(value)); - assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); - uint32_t data = (uint32_t)_cbor_value_decode_int64_internal(value); - memcpy(result, &data, sizeof(*result)); - return CborNoError; -} - -CBOR_INLINE_API bool cbor_value_is_double(const CborValue *value) -{ return value->type == CborDoubleType; } -CBOR_INLINE_API CborError cbor_value_get_double(const CborValue *value, double *result) -{ - assert(cbor_value_is_double(value)); - assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); - uint64_t data = _cbor_value_decode_int64_internal(value); - memcpy(result, &data, sizeof(*result)); - return CborNoError; -} - -/* The following API requires a hosted C implementation (uses FILE*) */ -#if !defined(__STDC_HOSTED__) || __STDC_HOSTED__-0 == 1 - -/* Human-readable (dump) API */ -CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value); -CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value) -{ - CborValue copy = *value; - return cbor_value_to_pretty_advance(out, ©); -} - -#endif /* __STDC_HOSTED__ check */ - -#ifdef __cplusplus -} -#endif - -#endif /* CBOR_H */ - diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 49c6e7ef26..0ff394b922 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -1,14 +1,12 @@ # Tinycbor library source files. add_library( tinycbor SHARED - cborencoder.c - cborencoder_close_container_checked.c - cborerrorstrings.c - cborparser.c - cborparser_dup_string.c - cborpretty.c ) - -# Library version. -set_target_properties( tinycbor PROPERTIES VERSION ${PROJECT_VERSION} ) + tinycbor/src/cborencoder.c + tinycbor/src/cborencoder_close_container_checked.c + tinycbor/src/cborerrorstrings.c + tinycbor/src/cborparser.c + tinycbor/src/cborparser_dup_string.c + tinycbor/src/cborpretty.c + tinycbor/src/cborpretty_stdio.c ) # Link math library. target_link_libraries( tinycbor m ) diff --git a/third_party/tinycbor/LICENSE b/third_party/tinycbor/LICENSE deleted file mode 100644 index 4aad977ceb..0000000000 --- a/third_party/tinycbor/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Intel Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/third_party/tinycbor/assert_p.h b/third_party/tinycbor/assert_p.h deleted file mode 100644 index 994be06441..0000000000 --- a/third_party/tinycbor/assert_p.h +++ /dev/null @@ -1,29 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#include -#ifdef NDEBUG -# undef assert -# define assert(cond) do { if (!(cond)) unreachable(); } while (0) -#endif diff --git a/third_party/tinycbor/cborconstants_p.h b/third_party/tinycbor/cborconstants_p.h deleted file mode 100644 index d412056d24..0000000000 --- a/third_party/tinycbor/cborconstants_p.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef CBORCONSTANTS_P_H -#define CBORCONSTANTS_P_H - -/* - * CBOR Major types - * Encoded in the high 3 bits of the descriptor byte - * See http://tools.ietf.org/html/rfc7049#section-2.1 - */ -typedef enum CborMajorTypes { - UnsignedIntegerType = 0U, - NegativeIntegerType = 1U, - ByteStringType = 2U, - TextStringType = 3U, - ArrayType = 4U, - MapType = 5U, /* a.k.a. object */ - TagType = 6U, - SimpleTypesType = 7U -} CborMajorTypes; - -/* - * CBOR simple and floating point types - * Encoded in the low 8 bits of the descriptor byte when the - * Major Type is 7. - */ -typedef enum CborSimpleTypes { - FalseValue = 20, - TrueValue = 21, - NullValue = 22, - UndefinedValue = 23, - SimpleTypeInNextByte = 24, /* not really a simple type */ - HalfPrecisionFloat = 25, /* ditto */ - SinglePrecisionFloat = 26, /* ditto */ - DoublePrecisionFloat = 27, /* ditto */ - Break = 31 -} CborSimpleTypes; - -enum { - SmallValueBitLength = 5U, - SmallValueMask = (1U << SmallValueBitLength) - 1, /* 31 */ - Value8Bit = 24U, - Value16Bit = 25U, - Value32Bit = 26U, - Value64Bit = 27U, - IndefiniteLength = 31U, - - MajorTypeShift = SmallValueBitLength, - MajorTypeMask = (int) (~0U << MajorTypeShift), - - BreakByte = (unsigned)Break | (SimpleTypesType << MajorTypeShift) -}; - -#endif /* CBORCONSTANTS_P_H */ diff --git a/third_party/tinycbor/cborencoder.c b/third_party/tinycbor/cborencoder.c deleted file mode 100644 index cfe73defe7..0000000000 --- a/third_party/tinycbor/cborencoder.c +++ /dev/null @@ -1,626 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS 1 -#endif - -#include "cbor.h" -#include "cborconstants_p.h" -#include "compilersupport_p.h" - -#include -#include -#include - -#include "assert_p.h" /* Always include last */ - -/** - * \defgroup CborEncoding Encoding to CBOR - * \brief Group of functions used to encode data to CBOR. - * - * CborEncoder is used to encode data into a CBOR stream. The outermost - * CborEncoder is initialized by calling cbor_encoder_init(), with the buffer - * where the CBOR stream will be stored. The outermost CborEncoder is usually - * used to encode exactly one item, most often an array or map. It is possible - * to encode more than one item, but care must then be taken on the decoder - * side to ensure the state is reset after each item was decoded. - * - * Nested CborEncoder objects are created using cbor_encoder_create_array() and - * cbor_encoder_create_map(), later closed with cbor_encoder_close_container() - * or cbor_encoder_close_container_checked(). The pairs of creation and closing - * must be exactly matched and their parameters are always the same. - * - * CborEncoder writes directly to the user-supplied buffer, without extra - * buffering. CborEncoder does not allocate memory and CborEncoder objects are - * usually created on the stack of the encoding functions. - * - * The example below initializes a CborEncoder object with a buffer and encodes - * a single integer. - * - * \code - * uint8_t buf[16]; - * CborEncoder encoder; - * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); - * cbor_encode_int(&encoder, some_value); - * \endcode - * - * As explained before, usually the outermost CborEncoder object is used to add - * one array or map, which in turn contains multiple elements. The example - * below creates a CBOR map with one element: a key "foo" and a boolean value. - * - * \code - * uint8_t buf[16]; - * CborEncoder encoder, mapEncoder; - * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); - * cbor_encoder_create_map(&encoder, &mapEncoder, 1); - * cbor_encode_text_stringz(&mapEncoder, "foo"); - * cbor_encode_boolean(&mapEncoder, some_value); - * cbor_encoder_close_container(&encoder, &mapEncoder); - * \endcode - * - *

Error checking and buffer size

- * - * All functions operating on CborEncoder return a condition of type CborError. - * If the encoding was successful, they return CborNoError. Some functions do - * extra checking on the input provided and may return some other error - * conditions (for example, cbor_encode_simple_value() checks that the type is - * of the correct type). - * - * In addition, all functions check whether the buffer has enough bytes to - * encode the item being appended. If that is not possible, they return - * CborErrorOutOfMemory. - * - * It is possible to continue with the encoding of data past the first function - * that returns CborErrorOutOfMemory. CborEncoder functions will not overrun - * the buffer, but will instead count how many more bytes are needed to - * complete the encoding. At the end, you can obtain that count by calling - * cbor_encoder_get_extra_bytes_needed(). - * - * \section1 Finalizing the encoding - * - * Once all items have been appended and the containers have all been properly - * closed, the user-supplied buffer will contain the CBOR stream and may be - * immediately used. To obtain the size of the buffer, call - * cbor_encoder_get_buffer_size() with the original buffer pointer. - * - * The example below illustrates how one can encode an item with error checking - * and then pass on the buffer for network sending. - * - * \code - * uint8_t buf[16]; - * CborError err; - * CborEncoder encoder, mapEncoder; - * cbor_encoder_init(&encoder, &buf, sizeof(buf), 0); - * err = cbor_encoder_create_map(&encoder, &mapEncoder, 1); - * if (!err) - * return err; - * err = cbor_encode_text_stringz(&mapEncoder, "foo"); - * if (!err) - * return err; - * err = cbor_encode_boolean(&mapEncoder, some_value); - * if (!err) - * return err; - * err = cbor_encoder_close_container_checked(&encoder, &mapEncoder); - * if (!err) - * return err; - * - * size_t len = cbor_encoder_get_buffer_size(&encoder, buf); - * send_payload(buf, len); - * return CborNoError; - * \endcode - * - * Finally, the example below illustrates expands on the one above and also - * deals with dynamically growing the buffer if the initial allocation wasn't - * big enough. Note the two places where the error checking was replaced with - * an assertion, showing where the author assumes no error can occur. - * - * \code - * uint8_t *encode_string_array(const char **strings, int n, size_t *bufsize) - * { - * CborError err; - * CborEncoder encoder, arrayEncoder; - * size_t size = 256; - * uint8_t *buf = NULL; - * - * while (1) { - * int i; - * size_t more_bytes; - * uint8_t *nbuf = realloc(buf, size); - * if (nbuf == NULL) - * goto error; - * buf = nbuf; - * - * cbor_encoder_init(&encoder, &buf, size, 0); - * err = cbor_encoder_create_array(&encoder, &arrayEncoder, n); - * assert(err); // can't fail, the buffer is always big enough - * - * for (i = 0; i < n; ++i) { - * err = cbor_encode_text_stringz(&arrayEncoder, strings[i]); - * if (err && err != CborErrorOutOfMemory) - * goto error; - * } - * - * err = cbor_encoder_close_container_checked(&encoder, &arrayEncoder); - * assert(err); // shouldn't fail! - * - * more_bytes = cbor_encoder_get_extra_bytes_needed(encoder); - * if (more_size) { - * // buffer wasn't big enough, try again - * size += more_bytes; - * continue; - * } - * - * *bufsize = cbor_encoder_get_buffer_size(encoder, buf); - * return buf; - * } - * error: - * free(buf); - * return NULL; - * } - * \endcode - */ - -/** - * \addtogroup CborEncoding - * @{ - */ - -/** - * \struct CborEncoder - * Structure used to encode to CBOR. - */ - -/** - * Initializes a CborEncoder structure \a encoder by pointing it to buffer \a - * buffer of size \a size. The \a flags field is currently unused and must be - * zero. - */ -void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags) -{ - encoder->data.ptr = buffer; - encoder->end = buffer + size; - encoder->added = 0; - encoder->flags = flags; -} - -static inline void put16(void *where, uint16_t v) -{ - v = cbor_htons(v); - memcpy(where, &v, sizeof(v)); -} - -/* Note: Since this is currently only used in situations where OOM is the only - * valid error, we KNOW this to be true. Thus, this function now returns just 'true', - * but if in the future, any function starts returning a non-OOM error, this will need - * to be changed to the test. At the moment, this is done to prevent more branches - * being created in the tinycbor output */ -static inline bool isOomError(CborError err) -{ - (void) err; - return true; -} - -static inline void put32(void *where, uint32_t v) -{ - v = cbor_htonl(v); - memcpy(where, &v, sizeof(v)); -} - -static inline void put64(void *where, uint64_t v) -{ - v = cbor_htonll(v); - memcpy(where, &v, sizeof(v)); -} - -static inline bool would_overflow(CborEncoder *encoder, size_t len) -{ - ptrdiff_t remaining = (ptrdiff_t)encoder->end; - remaining -= remaining ? (ptrdiff_t)encoder->data.ptr : encoder->data.bytes_needed; - remaining -= (ptrdiff_t)len; - return unlikely(remaining < 0); -} - -static inline void advance_ptr(CborEncoder *encoder, size_t n) -{ - if (encoder->end) - encoder->data.ptr += n; - else - encoder->data.bytes_needed += n; -} - -static inline CborError append_to_buffer(CborEncoder *encoder, const void *data, size_t len) -{ - if (would_overflow(encoder, len)) { - if (encoder->end != NULL) { - len -= encoder->end - encoder->data.ptr; - encoder->end = NULL; - encoder->data.bytes_needed = 0; - } - - advance_ptr(encoder, len); - return CborErrorOutOfMemory; - } - - memcpy(encoder->data.ptr, data, len); - encoder->data.ptr += len; - return CborNoError; -} - -static inline CborError append_byte_to_buffer(CborEncoder *encoder, uint8_t byte) -{ - return append_to_buffer(encoder, &byte, 1); -} - -static inline CborError encode_number_no_update(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) -{ - /* Little-endian would have been so much more convenient here: - * We could just write at the beginning of buf but append_to_buffer - * only the necessary bytes. - * Since it has to be big endian, do it the other way around: - * write from the end. */ - uint64_t buf[2]; - uint8_t *const bufend = (uint8_t *)buf + sizeof(buf); - uint8_t *bufstart = bufend - 1; - put64(buf + 1, ui); /* we probably have a bunch of zeros in the beginning */ - - if (ui < Value8Bit) { - *bufstart += shiftedMajorType; - } else { - uint8_t more = 0; - if (ui > 0xffU) - ++more; - if (ui > 0xffffU) - ++more; - if (ui > 0xffffffffU) - ++more; - bufstart -= (size_t)1 << more; - *bufstart = shiftedMajorType + Value8Bit + more; - } - - return append_to_buffer(encoder, bufstart, bufend - bufstart); -} - -static inline CborError encode_number(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) -{ - ++encoder->added; - return encode_number_no_update(encoder, ui, shiftedMajorType); -} - -/** - * Appends the unsigned 64-bit integer \a value to the CBOR stream provided by - * \a encoder. - * - * \sa cbor_encode_negative_int, cbor_encode_int - */ -CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value) -{ - return encode_number(encoder, value, UnsignedIntegerType << MajorTypeShift); -} - -/** - * Appends the negative 64-bit integer whose absolute value is \a - * absolute_value to the CBOR stream provided by \a encoder. - * - * \sa cbor_encode_uint, cbor_encode_int - */ -CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value) -{ - return encode_number(encoder, absolute_value, NegativeIntegerType << MajorTypeShift); -} - -/** - * Appends the signed 64-bit integer \a value to the CBOR stream provided by - * \a encoder. - * - * \sa cbor_encode_negative_int, cbor_encode_uint - */ -CborError cbor_encode_int(CborEncoder *encoder, int64_t value) -{ - /* adapted from code in RFC 7049 appendix C (pseudocode) */ - uint64_t ui = value >> 63; /* extend sign to whole length */ - uint8_t majorType = ui & 0x20; /* extract major type */ - ui ^= value; /* complement negatives */ - return encode_number(encoder, ui, majorType); -} - -/** - * Appends the CBOR Simple Type of value \a value to the CBOR stream provided by - * \a encoder. - * - * This function may return error CborErrorIllegalSimpleType if the \a value - * variable contains a number that is not a valid simple type. - */ -CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value) -{ -#ifndef CBOR_ENCODER_NO_CHECK_USER - /* check if this is a valid simple type */ - if (value >= HalfPrecisionFloat && value <= Break) - return CborErrorIllegalSimpleType; -#endif - return encode_number(encoder, value, SimpleTypesType << MajorTypeShift); -} - -/** - * Appends the floating-point value of type \a fpType and pointed to by \a - * value to the CBOR stream provided by \a encoder. The value of \a fpType must - * be one of CborHalfFloatType, CborFloatType or CborDoubleType, otherwise the - * behavior of this function is undefined. - * - * This function is useful for code that needs to pass through floating point - * values but does not wish to have the actual floating-point code. - * - * \sa cbor_encode_half_float, cbor_encode_float, cbor_encode_double - */ -CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value) -{ - uint8_t buf[1 + sizeof(uint64_t)]; - assert(fpType == CborHalfFloatType || fpType == CborFloatType || fpType == CborDoubleType); - buf[0] = fpType; - - unsigned size = 2U << (fpType - CborHalfFloatType); - if (size == 8) - put64(buf + 1, *(const uint64_t*)value); - else if (size == 4) - put32(buf + 1, *(const uint32_t*)value); - else - put16(buf + 1, *(const uint16_t*)value); - ++encoder->added; - return append_to_buffer(encoder, buf, size + 1); -} - -/** - * Appends the CBOR tag \a tag to the CBOR stream provided by \a encoder. - * - * \sa CborTag - */ -CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag) -{ - /* tags don't count towards the number of elements in an array or map */ - return encode_number_no_update(encoder, tag, TagType << MajorTypeShift); -} - -static CborError encode_string(CborEncoder *encoder, size_t length, uint8_t shiftedMajorType, const void *string) -{ - CborError err = encode_number(encoder, length, shiftedMajorType); - if (err && !isOomError(err)) - return err; - return append_to_buffer(encoder, string, length); -} - -/** - * \fn CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) - * - * Appends the null-terminated text string \a string to the CBOR stream - * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but - * TinyCBOR makes no verification of correctness. The terminating null is not - * included in the stream. - * - * \sa cbor_encode_text_string, cbor_encode_byte_string - */ - -/** - * Appends the text string \a string of length \a length to the CBOR stream - * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but - * TinyCBOR makes no verification of correctness. - * - * \sa CborError cbor_encode_text_stringz, cbor_encode_byte_string - */ -CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length) -{ - return encode_string(encoder, length, ByteStringType << MajorTypeShift, string); -} - -/** - * Appends the byte string \a string of length \a length to the CBOR stream - * provided by \a encoder. CBOR byte strings are arbitrary raw data. - * - * \sa cbor_encode_text_stringz, cbor_encode_text_string - */ -CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length) -{ - return encode_string(encoder, length, TextStringType << MajorTypeShift, string); -} - -#ifdef __GNUC__ -__attribute__((noinline)) -#endif -static CborError create_container(CborEncoder *encoder, CborEncoder *container, size_t length, uint8_t shiftedMajorType) -{ - CborError err; - container->data.ptr = encoder->data.ptr; - container->end = encoder->end; - ++encoder->added; - container->added = 0; - - cbor_static_assert(((MapType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == CborIteratorFlag_ContainerIsMap); - cbor_static_assert(((ArrayType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == 0); - container->flags = shiftedMajorType & CborIteratorFlag_ContainerIsMap; - - if (length == CborIndefiniteLength) { - container->flags |= CborIteratorFlag_UnknownLength; - err = append_byte_to_buffer(container, shiftedMajorType + IndefiniteLength); - } else { - err = encode_number_no_update(container, length, shiftedMajorType); - } - return err; -} - -/** - * Creates a CBOR array in the CBOR stream provided by \a encoder and - * initializes \a arrayEncoder so that items can be added to the array using - * the CborEncoder functions. The array must be terminated by calling either - * cbor_encoder_close_container() or cbor_encoder_close_container_checked() - * with the same \a encoder and \a arrayEncoder parameters. - * - * The number of items inserted into the array must be exactly \a length items, - * otherwise the stream is invalid. If the number of items is not known when - * creating the array, the constant \ref CborIndefiniteLength may be passed as - * length instead. - * - * \sa cbor_encoder_create_map - */ -CborError cbor_encoder_create_array(CborEncoder *encoder, CborEncoder *arrayEncoder, size_t length) -{ - return create_container(encoder, arrayEncoder, length, ArrayType << MajorTypeShift); -} - -/** - * Creates a CBOR map in the CBOR stream provided by \a encoder and - * initializes \a mapEncoder so that items can be added to the map using - * the CborEncoder functions. The map must be terminated by calling either - * cbor_encoder_close_container() or cbor_encoder_close_container_checked() - * with the same \a encoder and \a mapEncoder parameters. - * - * The number of pair of items inserted into the map must be exactly \a length - * items, otherwise the stream is invalid. If the number of items is not known - * when creating the map, the constant \ref CborIndefiniteLength may be passed as - * length instead. - * - * \b{Implementation limitation:} TinyCBOR cannot encode more than SIZE_MAX/2 - * key-value pairs in the stream. If the length \a length is larger than this - * value, this function returns error CborErrorDataTooLarge. - * - * \sa cbor_encoder_create_array - */ -CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length) -{ - if (length != CborIndefiniteLength && length > SIZE_MAX / 2) - return CborErrorDataTooLarge; - return create_container(encoder, mapEncoder, length, MapType << MajorTypeShift); -} - -/** - * Closes the CBOR container (array or map) provided by \a containerEncoder and - * updates the CBOR stream provided by \a encoder. Both parameters must be the - * same as were passed to cbor_encoder_create_array() or - * cbor_encoder_create_map(). - * - * This function does not verify that the number of items (or pair of items, in - * the case of a map) was correct. To execute that verification, call - * cbor_encoder_close_container_checked() instead. - * - * \sa cbor_encoder_create_array(), cbor_encoder_create_map() - */ -CborError cbor_encoder_close_container(CborEncoder *encoder, const CborEncoder *containerEncoder) -{ - if (encoder->end) - encoder->data.ptr = containerEncoder->data.ptr; - else - encoder->data.bytes_needed = containerEncoder->data.bytes_needed; - encoder->end = containerEncoder->end; - if (containerEncoder->flags & CborIteratorFlag_UnknownLength) - return append_byte_to_buffer(encoder, BreakByte); - return CborNoError; -} - -/** - * \fn CborError cbor_encode_boolean(CborEncoder *encoder, bool value) - * - * Appends the boolean value \a value to the CBOR stream provided by \a encoder. - */ - -/** - * \fn CborError cbor_encode_null(CborEncoder *encoder) - * - * Appends the CBOR type representing a null value to the CBOR stream provided - * by \a encoder. - * - * \sa cbor_encode_undefined() - */ - -/** - * \fn CborError cbor_encode_undefined(CborEncoder *encoder) - * - * Appends the CBOR type representing an undefined value to the CBOR stream - * provided by \a encoder. - * - * \sa cbor_encode_null() - */ - -/** - * \fn CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) - * - * Appends the IEEE 754 half-precision (16-bit) floating point value pointed to - * by \a value to the CBOR stream provided by \a encoder. - * - * \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double() - */ - -/** - * \fn CborError cbor_encode_float(CborEncoder *encoder, float value) - * - * Appends the IEEE 754 single-precision (32-bit) floating point value \a value - * to the CBOR stream provided by \a encoder. - * - * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_double() - */ - -/** - * \fn CborError cbor_encode_double(CborEncoder *encoder, double value) - * - * Appends the IEEE 754 double-precision (64-bit) floating point value \a value - * to the CBOR stream provided by \a encoder. - * - * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float() - */ - -/** - * \fn size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) - * - * Returns the total size of the buffer starting at \a buffer after the - * encoding finished without errors. The \a encoder and \a buffer arguments - * must be the same as supplied to cbor_encoder_init(). - * - * If the encoding process had errors, the return value of this function is - * meaningless. If the only errors were CborErrorOutOfMemory, instead use - * cbor_encoder_get_extra_bytes_needed() to find out by how much to grow the - * buffer before encoding again. - * - * See \ref CborEncoding for an example of using this function. - * - * \sa cbor_encoder_init(), cbor_encoder_get_extra_bytes_needed(), CborEncoding - */ - -/** - * \fn size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) - * - * Returns how many more bytes the original buffer supplied to - * cbor_encoder_init() needs to be extended by so that no CborErrorOutOfMemory - * condition will happen for the encoding. If the buffer was big enough, this - * function returns 0. The \a encoder must be the original argument as passed - * to cbor_encoder_init(). - * - * This function is usually called after an encoding sequence ended with one or - * more CborErrorOutOfMemory errors, but no other error. If any other error - * happened, the return value of this function is meaningless. - * - * See \ref CborEncoding for an example of using this function. - * - * \sa cbor_encoder_init(), cbor_encoder_get_buffer_size(), CborEncoding - */ - -/** @} */ diff --git a/third_party/tinycbor/cborencoder_close_container_checked.c b/third_party/tinycbor/cborencoder_close_container_checked.c deleted file mode 100644 index 9a1f549bdb..0000000000 --- a/third_party/tinycbor/cborencoder_close_container_checked.c +++ /dev/null @@ -1,82 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS 1 -#endif - -#include "cbor.h" -#include "cborconstants_p.h" -#include "compilersupport_p.h" -#include "extract_number_p.h" - -#include - -#include "assert_p.h" /* Always include last */ - -/** - * \addtogroup CborEncoding - * @{ - */ - -/** - * - * Closes the CBOR container (array or map) provided by \a containerEncoder and - * updates the CBOR stream provided by \a encoder. Both parameters must be the - * same as were passed to cbor_encoder_create_array() or - * cbor_encoder_create_map(). - * - * Unlike cbor_encoder_close_container(), this function checks that the number - * of items (or pair of items, in the case of a map) was correct. If the number - * of items inserted does not match the length originally passed to - * cbor_encoder_create_array() or cbor_encoder_create_map(), this function - * returns either CborErrorTooFewItems or CborErrorTooManyItems. - * - * \sa cbor_encoder_create_array(), cbor_encoder_create_map() - */ -CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder) -{ - const uint8_t *ptr = encoder->data.ptr; - CborError err = cbor_encoder_close_container(encoder, containerEncoder); - if (containerEncoder->flags & CborIteratorFlag_UnknownLength || encoder->end == NULL) - return err; - - /* check what the original length was */ - uint64_t actually_added; - err = extract_number(&ptr, encoder->data.ptr, &actually_added); - if (err) - return err; - - if (containerEncoder->flags & CborIteratorFlag_ContainerIsMap) { - if (actually_added > SIZE_MAX / 2) - return CborErrorDataTooLarge; - actually_added *= 2; - } - return actually_added == containerEncoder->added ? CborNoError : - actually_added < containerEncoder->added ? CborErrorTooManyItems : CborErrorTooFewItems; -} - -/** @} */ diff --git a/third_party/tinycbor/cborerrorstrings.c b/third_party/tinycbor/cborerrorstrings.c deleted file mode 100644 index d2fe42fcd1..0000000000 --- a/third_party/tinycbor/cborerrorstrings.c +++ /dev/null @@ -1,165 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#include "cbor.h" - -#ifndef _ -# define _(msg) msg -#endif - -/** - * \enum CborError - * \ingroup CborGlobals - * The CborError enum contains the possible error values used by the CBOR encoder and decoder. - * - * TinyCBOR functions report success by returning CborNoError, or one error - * condition by returning one of the values below. One exception is the - * out-of-memory condition (CborErrorOutOfMemory), which the functions for \ref - * CborEncoding may report in bit-wise OR with other conditions. - * - * This technique allows code to determine whether the only error condition was - * a lack of buffer space, which may not be a fatal condition if the buffer can - * be resized. Additionally, the functions for \ref CborEncoding may continue - * to be used even after CborErrorOutOfMemory is returned, and instead they - * will simply calculate the extra space needed. - * - * \value CborNoError No error occurred - * \omitvalue CborUnknownError - * \value CborErrorUnknownLength Request for the length of an array, map or string whose length is not provided in the CBOR stream - * \value CborErrorAdvancePastEOF Not enough data in the stream to decode item (decoding would advance past end of stream) - * \value CborErrorIO An I/O error occurred, probably due to an out-of-memory situation - * \value CborErrorGarbageAtEnd Bytes exist past the end of the CBOR stream - * \value CborErrorUnexpectedEOF End of stream reached unexpectedly - * \value CborErrorUnexpectedBreak A CBOR break byte was found where not expected - * \value CborErrorUnknownType An unknown type (future extension to CBOR) was found in the stream - * \value CborErrorIllegalType An invalid type was found while parsing a chunked CBOR string - * \value CborErrorIllegalNumber An illegal initial byte (encoding unspecified additional information) was found - * \value CborErrorIllegalSimpleType An illegal encoding of a CBOR Simple Type of value less than 32 was found - * \omitvalue CborErrorUnknownSimpleType - * \omitvalue CborErrorUnknownTag - * \omitvalue CborErrorInappropriateTagForType - * \omitvalue CborErrorDuplicateObjectKeys - * \value CborErrorInvalidUtf8TextString Illegal UTF-8 encoding found while parsing CBOR Text String - * \value CborErrorTooManyItems Too many items were added to CBOR map or array of pre-determined length - * \value CborErrorTooFewItems Too few items were added to CBOR map or array of pre-determeined length - * \value CborErrorDataTooLarge Data item size exceeds TinyCBOR's implementation limits - * \value CborErrorNestingTooDeep Data item nesting exceeds TinyCBOR's implementation limits - * \omitvalue CborErrorUnsupportedType - * \value CborErrorJsonObjectKeyIsAggregate Conversion to JSON failed because the key in a map is a CBOR map or array - * \value CborErrorJsonObjectKeyNotString Conversion to JSON failed because the key in a map is not a text string - * \value CborErrorOutOfMemory During CBOR encoding, the buffer provided is insufficient for encoding the data item; - * in other situations, TinyCBOR failed to allocate memory - * \value CborErrorInternalError An internal error occurred in TinyCBOR - */ - -/** - * \ingroup CborGlobals - * Returns the error string corresponding to the CBOR error condition \a error. - */ -const char *cbor_error_string(CborError error) -{ - switch (error) { - case CborNoError: - return ""; - - case CborUnknownError: - return _("unknown error"); - - case CborErrorOutOfMemory: - return _("out of memory/need more memory"); - - case CborErrorUnknownLength: - return _("unknown length (attempted to get the length of a map/array/string of indeterminate length"); - - case CborErrorAdvancePastEOF: - return _("attempted to advance past EOF"); - - case CborErrorIO: - return _("I/O error"); - - case CborErrorGarbageAtEnd: - return _("garbage after the end of the content"); - - case CborErrorUnexpectedEOF: - return _("unexpected end of data"); - - case CborErrorUnexpectedBreak: - return _("unexpected 'break' byte"); - - case CborErrorUnknownType: - return _("illegal byte (encodes future extension type)"); - - case CborErrorIllegalType: - return _("mismatched string type in chunked string"); - - case CborErrorIllegalNumber: - return _("illegal initial byte (encodes unspecified additional information)"); - - case CborErrorIllegalSimpleType: - return _("illegal encoding of simple type smaller than 32"); - - case CborErrorUnknownSimpleType: - return _("unknown simple type"); - - case CborErrorUnknownTag: - return _("unknown tag"); - - case CborErrorInappropriateTagForType: - return _("inappropriate tag for type"); - - case CborErrorDuplicateObjectKeys: - return _("duplicate keys in object"); - - case CborErrorInvalidUtf8TextString: - return _("invalid UTF-8 content in string"); - - case CborErrorTooManyItems: - return _("too many items added to encoder"); - - case CborErrorTooFewItems: - return _("too few items added to encoder"); - - case CborErrorDataTooLarge: - return _("internal error: data too large"); - - case CborErrorNestingTooDeep: - return _("internal error: too many nested containers found in recursive function"); - - case CborErrorUnsupportedType: - return _("unsupported type"); - - case CborErrorJsonObjectKeyIsAggregate: - return _("conversion to JSON failed: key in object is an array or map"); - - case CborErrorJsonObjectKeyNotString: - return _("conversion to JSON failed: key in object is not a string"); - - case CborErrorJsonNotImplemented: - return _("conversion to JSON failed: open_memstream unavailable"); - - case CborErrorInternalError: - return _("internal error"); - } - return cbor_error_string(CborUnknownError); -} diff --git a/third_party/tinycbor/cborparser.c b/third_party/tinycbor/cborparser.c deleted file mode 100644 index 70cc7153af..0000000000 --- a/third_party/tinycbor/cborparser.c +++ /dev/null @@ -1,1300 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS 1 -#endif - -#include "cbor.h" -#include "cborconstants_p.h" -#include "compilersupport_p.h" -#include "extract_number_p.h" - -#include -#include - -#include "assert_p.h" /* Always include last */ - -#ifndef CBOR_PARSER_MAX_RECURSIONS -# define CBOR_PARSER_MAX_RECURSIONS 1024 -#endif - -/** - * \defgroup CborParsing Parsing CBOR streams - * \brief Group of functions used to parse CBOR streams. - * - * TinyCBOR provides functions for pull-based stream parsing of a CBOR-encoded - * payload. The main data type for the parsing is a CborValue, which behaves - * like an iterator and can be used to extract the encoded data. It is first - * initialized with a call to cbor_parser_init() and is usually used to extract - * exactly one item, most often an array or map. - * - * Nested CborValue objects can be parsed using cbor_value_enter_container(). - * Each call to cbor_value_enter_container() must be matched by a call to - * cbor_value_leave_container(), with the exact same parameters. - * - * The example below initializes a CborParser object, begins the parsing with a - * CborValue and decodes a single integer: - * - * \code - * int extract_int(const uint8_t *buffer, size_t len) - * { - * CborParser parser; - * CborValue value; - * int result; - * cbor_parser_init(buffer, len, 0, &buffer, &value); - * cbor_value_get_int(&value, &result); - * return result; - * } - * \endcode - * - * The code above does no error checking, which means it assumes the data comes - * from a source trusted to send one properly-encoded integer. The following - * example does the exact same operation, but includes error parsing and - * returns 0 on parsing failure: - * - * \code - * int extract_int(const uint8_t *buffer, size_t len) - * { - * CborParser parser; - * CborValue value; - * int result; - * if (cbor_parser_init(buffer, len, 0, &buffer, &value) != CborNoError) - * return 0; - * if (!cbor_value_is_integer(&value) || - * cbor_value_get_int(&value, &result) != CborNoError) - * return 0; - * return result; - * } - * \endcode - * - * Note, in the example above, that one can't distinguish a parsing failure - * from an encoded value of zero. Reporting a parsing error is left as an - * exercise to the reader. - * - * The code above does not execute a range-check either: it is possible that - * the value decoded from the CBOR stream encodes a number larger than what can - * be represented in a variable of type \c{int}. If detecting that case is - * important, the code should call cbor_value_get_int_checked() instead. - * - *

Memory and parsing constraints

- * - * TinyCBOR is designed to run with little memory and with minimal overhead. - * Except where otherwise noted, the parser functions always run on constant - * time (O(1)), do not recurse and never allocate memory (thus, stack usage is - * bounded and is O(1)). - * - *

Error handling and preconditions

- * - * All functions operating on a CborValue return a CborError condition, with - * CborNoError standing for the normal situation in which no parsing error - * occurred. All functions may return parsing errors in case the stream cannot - * be decoded properly, be it due to corrupted data or due to reaching the end - * of the input buffer. - * - * Error conditions must not be ignored. All decoder functions have undefined - * behavior if called after an error has been reported, and may crash. - * - * Some functions are also documented to have preconditions, like - * cbor_value_get_int() requiring that the input be an integral value. - * Violation of preconditions also results in undefined behavior and the - * program may crash. - */ - -/** - * \addtogroup CborParsing - * @{ - */ - -/** - * \struct CborValue - * - * This type contains one value parsed from the CBOR stream. Each CborValue - * behaves as an iterator in a StAX-style parser. - * - * \if privatedocs - * Implementation details: the CborValue contains these fields: - * \list - * \li ptr: pointer to the actual data - * \li flags: flags from the decoder - * \li extra: partially decoded integer value (0, 1 or 2 bytes) - * \li remaining: remaining items in this collection after this item or UINT32_MAX if length is unknown - * \endlist - * \endif - */ - -static CborError extract_length(const CborParser *parser, const uint8_t **ptr, size_t *len) -{ - uint64_t v; - CborError err = extract_number(ptr, parser->end, &v); - if (err) { - *len = 0; - return err; - } - - *len = (size_t)v; - if (v != *len) - return CborErrorDataTooLarge; - return CborNoError; -} - -static bool is_fixed_type(uint8_t type) -{ - return type != CborTextStringType && type != CborByteStringType && type != CborArrayType && - type != CborMapType; -} - -static CborError preparse_value(CborValue *it) -{ - const CborParser *parser = it->parser; - it->type = CborInvalidType; - - /* are we at the end? */ - if (it->ptr == parser->end) - return CborErrorUnexpectedEOF; - - uint8_t descriptor = *it->ptr; - uint8_t type = descriptor & MajorTypeMask; - it->type = type; - it->flags = 0; - it->extra = (descriptor &= SmallValueMask); - - if (descriptor > Value64Bit) { - if (unlikely(descriptor != IndefiniteLength)) - return type == CborSimpleType ? CborErrorUnknownType : CborErrorIllegalNumber; - if (likely(!is_fixed_type(type))) { - /* special case */ - it->flags |= CborIteratorFlag_UnknownLength; - it->type = type; - return CborNoError; - } - return type == CborSimpleType ? CborErrorUnexpectedBreak : CborErrorIllegalNumber; - } - - size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); - if (bytesNeeded + 1 > (size_t)(parser->end - it->ptr)) - return CborErrorUnexpectedEOF; - - uint8_t majortype = type >> MajorTypeShift; - if (majortype == NegativeIntegerType) { - it->flags |= CborIteratorFlag_NegativeInteger; - it->type = CborIntegerType; - } else if (majortype == SimpleTypesType) { - switch (descriptor) { - case FalseValue: - it->extra = false; - it->type = CborBooleanType; - break; - - case SinglePrecisionFloat: - case DoublePrecisionFloat: - it->flags |= CborIteratorFlag_IntegerValueTooLarge; - /* fall through */ - case TrueValue: - case NullValue: - case UndefinedValue: - case HalfPrecisionFloat: - it->type = *it->ptr; - break; - - case SimpleTypeInNextByte: - it->extra = (uint8_t)it->ptr[1]; -#ifndef CBOR_PARSER_NO_STRICT_CHECKS - if (unlikely(it->extra < 32)) { - it->type = CborInvalidType; - return CborErrorIllegalSimpleType; - } -#endif - break; - - case 28: - case 29: - case 30: - case Break: - assert(false); /* these conditions can't be reached */ - return CborErrorUnexpectedBreak; - } - return CborNoError; - } - - /* try to decode up to 16 bits */ - if (descriptor < Value8Bit) - return CborNoError; - - if (descriptor == Value8Bit) - it->extra = (uint8_t)it->ptr[1]; - else if (descriptor == Value16Bit) - it->extra = get16(it->ptr + 1); - else - it->flags |= CborIteratorFlag_IntegerValueTooLarge; /* Value32Bit or Value64Bit */ - return CborNoError; -} - -static CborError preparse_next_value(CborValue *it) -{ - if (it->remaining != UINT32_MAX) { - /* don't decrement the item count if the current item is tag: they don't count */ - if (it->type != CborTagType && !--it->remaining) { - it->type = CborInvalidType; - return CborNoError; - } - } else if (it->remaining == UINT32_MAX && it->ptr != it->parser->end && *it->ptr == (uint8_t)BreakByte) { - /* end of map or array */ - ++it->ptr; - it->type = CborInvalidType; - it->remaining = 0; - return CborNoError; - } - - return preparse_value(it); -} - -static CborError advance_internal(CborValue *it) -{ - uint64_t length; - CborError err = extract_number(&it->ptr, it->parser->end, &length); - assert(err == CborNoError); - - if (it->type == CborByteStringType || it->type == CborTextStringType) { - assert(length == (size_t)length); - assert((it->flags & CborIteratorFlag_UnknownLength) == 0); - it->ptr += length; - } - - return preparse_next_value(it); -} - -/** \internal - * - * Decodes the CBOR integer value when it is larger than the 16 bits available - * in value->extra. This function requires that value->flags have the - * CborIteratorFlag_IntegerValueTooLarge flag set. - * - * This function is also used to extract single- and double-precision floating - * point values (SinglePrecisionFloat == Value32Bit and DoublePrecisionFloat == - * Value64Bit). - */ -uint64_t _cbor_value_decode_int64_internal(const CborValue *value) -{ - assert(value->flags & CborIteratorFlag_IntegerValueTooLarge || - value->type == CborFloatType || value->type == CborDoubleType); - - /* since the additional information can only be Value32Bit or Value64Bit, - * we just need to test for the one bit those two options differ */ - assert((*value->ptr & SmallValueMask) == Value32Bit || (*value->ptr & SmallValueMask) == Value64Bit); - if ((*value->ptr & 1) == (Value32Bit & 1)) - return get32(value->ptr + 1); - - assert((*value->ptr & SmallValueMask) == Value64Bit); - return get64(value->ptr + 1); -} - -/** - * Initializes the CBOR parser for parsing \a size bytes beginning at \a - * buffer. Parsing will use flags set in \a flags. The iterator to the first - * element is returned in \a it. - * - * The \a parser structure needs to remain valid throughout the decoding - * process. It is not thread-safe to share one CborParser among multiple - * threads iterating at the same time, but the object can be copied so multiple - * threads can iterate. - */ -CborError cbor_parser_init(const uint8_t *buffer, size_t size, int flags, CborParser *parser, CborValue *it) -{ - memset(parser, 0, sizeof(*parser)); - parser->end = buffer + size; - parser->flags = flags; - it->parser = parser; - it->ptr = buffer; - it->remaining = 1; /* there's one type altogether, usually an array or map */ - return preparse_value(it); -} - -/** - * \fn bool cbor_value_at_end(const CborValue *it) - * - * Returns true if \a it has reached the end of the iteration, usually when - * advancing after the last item in an array or map. - * - * In the case of the outermost CborValue object, this function returns true - * after decoding a single element. A pointer to the first byte of the - * remaining data (if any) can be obtained with cbor_value_get_next_byte(). - * - * \sa cbor_value_advance(), cbor_value_is_valid(), cbor_value_get_next_byte() - */ - -/** - * \fn const uint8_t *cbor_value_get_next_byte(const CborValue *it) - * - * Returns a pointer to the next byte that would be decoded if this CborValue - * object were advanced. - * - * This function is useful if cbor_value_at_end() returns true for the - * outermost CborValue: the pointer returned is the first byte of the data - * remaining in the buffer, if any. Code can decide whether to begin decoding a - * new CBOR data stream from this point, or parse some other data appended to - * the same buffer. - * - * This function may be used even after a parsing error. If that occurred, - * then this function returns a pointer to where the parsing error occurred. - * Note that the error recovery is not precise and the pointer may not indicate - * the exact byte containing bad data. - * - * \sa cbor_value_at_end() - */ - -/** - * \fn bool cbor_value_is_valid(const CborValue *it) - * - * Returns true if the iterator \a it contains a valid value. Invalid iterators - * happen when iteration reaches the end of a container (see \ref - * cbor_value_at_end()) or when a search function resulted in no matches. - * - * \sa cbor_value_advance(), cbor_valie_at_end(), cbor_value_get_type() - */ - -/** - * Advances the CBOR value \a it by one fixed-size position. Fixed-size types - * are: integers, tags, simple types (including boolean, null and undefined - * values) and floating point types. - * - * If the type is not of fixed size, this function has undefined behavior. Code - * must be sure that the current type is one of the fixed-size types before - * calling this function. This function is provided because it can guarantee - * that runs in constant time (O(1)). - * - * If the caller is not able to determine whether the type is fixed or not, code - * can use the cbor_value_advance() function instead. - * - * \sa cbor_value_at_end(), cbor_value_advance(), cbor_value_enter_container(), cbor_value_leave_container() - */ -CborError cbor_value_advance_fixed(CborValue *it) -{ - assert(it->type != CborInvalidType); - assert(is_fixed_type(it->type)); - if (!it->remaining) - return CborErrorAdvancePastEOF; - return advance_internal(it); -} - -static CborError advance_recursive(CborValue *it, int nestingLevel) -{ - if (is_fixed_type(it->type)) - return advance_internal(it); - - if (!cbor_value_is_container(it)) { - size_t len = SIZE_MAX; - return _cbor_value_copy_string(it, NULL, &len, it); - } - - /* map or array */ - if (nestingLevel == CBOR_PARSER_MAX_RECURSIONS) - return CborErrorNestingTooDeep; - - CborError err; - CborValue recursed; - err = cbor_value_enter_container(it, &recursed); - if (err) - return err; - while (!cbor_value_at_end(&recursed)) { - err = advance_recursive(&recursed, nestingLevel + 1); - if (err) - return err; - } - return cbor_value_leave_container(it, &recursed); -} - - -/** - * Advances the CBOR value \a it by one element, skipping over containers. - * Unlike cbor_value_advance_fixed(), this function can be called on a CBOR - * value of any type. However, if the type is a container (map or array) or a - * string with a chunked payload, this function will not run in constant time - * and will recurse into itself (it will run on O(n) time for the number of - * elements or chunks and will use O(n) memory for the number of nested - * containers). - * - * \sa cbor_value_at_end(), cbor_value_advance_fixed(), cbor_value_enter_container(), cbor_value_leave_container() - */ -CborError cbor_value_advance(CborValue *it) -{ - assert(it->type != CborInvalidType); - if (!it->remaining) - return CborErrorAdvancePastEOF; - return advance_recursive(it, 0); -} - -/** - * \fn bool cbor_value_is_tag(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR tag. - * - * \sa cbor_value_get_tag(), cbor_value_skip_tag() - */ - -/** - * \fn CborError cbor_value_get_tag(const CborValue *value, CborTag *result) - * - * Retrieves the CBOR tag value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to a CBOR tag value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_tag is recommended. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_tag() - */ - -/** - * Advances the CBOR value \a it until it no longer points to a tag. If \a it is - * already not pointing to a tag, then this function returns it unchanged. - * - * This function does not run in constant time: it will run on O(n) for n being - * the number of tags. It does use constant memory (O(1) memory requirements). - * - * \sa cbor_value_advance_fixed(), cbor_value_advance() - */ -CborError cbor_value_skip_tag(CborValue *it) -{ - while (cbor_value_is_tag(it)) { - CborError err = cbor_value_advance_fixed(it); - if (err) - return err; - } - return CborNoError; -} - -/** - * \fn bool cbor_value_is_container(const CborValue *it) - * - * Returns true if the \a it value is a container and requires recursion in - * order to decode (maps and arrays), false otherwise. - */ - -/** - * Creates a CborValue iterator pointing to the first element of the container - * represented by \a it and saves it in \a recursed. The \a it container object - * needs to be kept and passed again to cbor_value_leave_container() in order - * to continue iterating past this container. - * - * The \a it CborValue iterator must point to a container. - * - * \sa cbor_value_is_container(), cbor_value_leave_container(), cbor_value_advance() - */ -CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed) -{ - CborError err; - assert(cbor_value_is_container(it)); - *recursed = *it; - - if (it->flags & CborIteratorFlag_UnknownLength) { - recursed->remaining = UINT32_MAX; - ++recursed->ptr; - err = preparse_value(recursed); - if (err != CborErrorUnexpectedBreak) - return err; - /* actually, break was expected here - * it's just an empty container */ - ++recursed->ptr; - } else { - uint64_t len; - err = extract_number(&recursed->ptr, recursed->parser->end, &len); - assert(err == CborNoError); - - recursed->remaining = (uint32_t)len; - if (recursed->remaining != len || len == UINT32_MAX) { - /* back track the pointer to indicate where the error occurred */ - recursed->ptr = it->ptr; - return CborErrorDataTooLarge; - } - if (recursed->type == CborMapType) { - /* maps have keys and values, so we need to multiply by 2 */ - if (recursed->remaining > UINT32_MAX / 2) { - /* back track the pointer to indicate where the error occurred */ - recursed->ptr = it->ptr; - return CborErrorDataTooLarge; - } - recursed->remaining *= 2; - } - if (len != 0) - return preparse_value(recursed); - } - - /* the case of the empty container */ - recursed->type = CborInvalidType; - recursed->remaining = 0; - return CborNoError; -} - -/** - * Updates \a it to point to the next element after the container. The \a - * recursed object needs to point to the element obtained either by advancing - * the last element of the container (via cbor_value_advance(), - * cbor_value_advance_fixed(), a nested cbor_value_leave_container(), or the \c - * next pointer from cbor_value_copy_string() or cbor_value_dup_string()). - * - * The \a it and \a recursed parameters must be the exact same as passed to - * cbor_value_enter_container(). - * - * \sa cbor_value_enter_container(), cbor_value_at_end() - */ -CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed) -{ - assert(cbor_value_is_container(it)); - assert(recursed->type == CborInvalidType); - it->ptr = recursed->ptr; - return preparse_next_value(it); -} - - -/** - * \fn CborType cbor_value_get_type(const CborValue *value) - * - * Returns the type of the CBOR value that the iterator \a value points to. If - * \a value does not point to a valid value, this function returns \ref - * CborInvalidType. - * - * TinyCBOR also provides functions to test directly if a given CborValue object - * is of a given type, like cbor_value_is_text_string() and cbor_value_is_null(). - * - * \sa cbor_value_is_valid() - */ - -/** - * \fn bool cbor_value_is_null(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR null type. - * - * \sa cbor_value_is_valid(), cbor_value_is_undefined() - */ - -/** - * \fn bool cbor_value_is_undefined(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR undefined type. - * - * \sa cbor_value_is_valid(), cbor_value_is_null() - */ - -/** - * \fn bool cbor_value_is_boolean(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR boolean - * type (true or false). - * - * \sa cbor_value_is_valid(), cbor_value_get_boolean() - */ - -/** - * \fn CborError cbor_value_get_boolean(const CborValue *value, bool *result) - * - * Retrieves the boolean value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to a boolean value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_boolean is recommended. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_boolean() - */ - -/** - * \fn bool cbor_value_is_simple_type(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR Simple Type - * type (other than true, false, null and undefined). - * - * \sa cbor_value_is_valid(), cbor_value_get_simple_type() - */ - -/** - * \fn CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) - * - * Retrieves the CBOR Simple Type value that \a value points to and stores it - * in \a result. If the iterator \a value does not point to a simple_type - * value, the behavior is undefined, so checking with \ref cbor_value_get_type - * or with \ref cbor_value_is_simple_type is recommended. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_simple_type() - */ - -/** - * \fn bool cbor_value_is_integer(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR integer - * type. - * - * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_uint64, cbor_value_get_raw_integer - */ - -/** - * \fn bool cbor_value_is_unsigned_integer(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR unsigned - * integer type (positive values or zero). - * - * \sa cbor_value_is_valid(), cbor_value_get_uint64() - */ - -/** - * \fn bool cbor_value_is_negative_integer(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR negative - * integer type. - * - * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_raw_integer - */ - -/** - * \fn CborError cbor_value_get_int(const CborValue *value, int *result) - * - * Retrieves the CBOR integer value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to an integer value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_integer is recommended. - * - * Note that this function does not do range-checking: integral values that do - * not fit in a variable of type \c{int} are silently truncated to fit. Use - * cbor_value_get_int_checked() that is not acceptable. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() - */ - -/** - * \fn CborError cbor_value_get_int64(const CborValue *value, int64_t *result) - * - * Retrieves the CBOR integer value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to an integer value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_integer is recommended. - * - * Note that this function does not do range-checking: integral values that do - * not fit in a variable of type \c{int64_t} are silently truncated to fit. Use - * cbor_value_get_int64_checked() that is not acceptable. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() - */ - -/** - * \fn CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) - * - * Retrieves the CBOR integer value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to an unsigned integer - * value, the behavior is undefined, so checking with \ref cbor_value_get_type - * or with \ref cbor_value_is_unsigned_integer is recommended. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_unsigned_integer() - */ - -/** - * \fn CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) - * - * Retrieves the CBOR integer value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to an integer value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_integer is recommended. - * - * This function is provided because CBOR negative integers can assume values - * that cannot be represented with normal 64-bit integer variables. - * - * If the integer is unsigned (that is, if cbor_value_is_unsigned_integer() - * returns true), then \a result will contain the actual value. If the integer - * is negative, then \a result will contain the absolute value of that integer, - * minus one. That is, \c {actual = -result - 1}. On architectures using two's - * complement for representation of negative integers, it is equivalent to say - * that \a result will contain the bitwise negation of the actual value. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() - */ - -/** - * Retrieves the CBOR integer value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to an integer value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_integer is recommended. - * - * Unlike cbor_value_get_int64(), this function performs a check to see if the - * stored integer fits in \a result without data loss. If the number is outside - * the valid range for the data type, this function returns the recoverable - * error CborErrorDataTooLarge. In that case, use either - * cbor_value_get_uint64() (if the number is positive) or - * cbor_value_get_raw_integer(). - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64() - */ -CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result) -{ - assert(cbor_value_is_integer(value)); - uint64_t v = _cbor_value_extract_int64_helper(value); - - /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): - * "[if] the new type is signed and the value cannot be represented in it; either the - * result is implementation-defined or an implementation-defined signal is raised." - * - * The range for int64_t is -2^63 to 2^63-1 (int64_t is required to be - * two's complement, C11 7.20.1.1 paragraph 3), which in CBOR is - * represented the same way, differing only on the "sign bit" (the major - * type). - */ - - if (unlikely(v > (uint64_t)INT64_MAX)) - return CborErrorDataTooLarge; - - *result = v; - if (value->flags & CborIteratorFlag_NegativeInteger) - *result = -*result - 1; - return CborNoError; -} - -/** - * Retrieves the CBOR integer value that \a value points to and stores it in \a - * result. If the iterator \a value does not point to an integer value, the - * behavior is undefined, so checking with \ref cbor_value_get_type or with - * \ref cbor_value_is_integer is recommended. - * - * Unlike cbor_value_get_int(), this function performs a check to see if the - * stored integer fits in \a result without data loss. If the number is outside - * the valid range for the data type, this function returns the recoverable - * error CborErrorDataTooLarge. In that case, use one of the other integer - * functions to obtain the value. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64(), - * cbor_value_get_uint64(), cbor_value_get_int64_checked(), cbor_value_get_raw_integer() - */ -CborError cbor_value_get_int_checked(const CborValue *value, int *result) -{ - assert(cbor_value_is_integer(value)); - uint64_t v = _cbor_value_extract_int64_helper(value); - - /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): - * "[if] the new type is signed and the value cannot be represented in it; either the - * result is implementation-defined or an implementation-defined signal is raised." - * - * But we can convert from signed to unsigned without fault (paragraph 2). - * - * The range for int is implementation-defined and int is not guaranteed use - * two's complement representation (int32_t is). - */ - - if (value->flags & CborIteratorFlag_NegativeInteger) { - if (unlikely(v > (unsigned) -(INT_MIN + 1))) - return CborErrorDataTooLarge; - - *result = (int)v; - *result = -*result - 1; - } else { - if (unlikely(v > (uint64_t)INT_MAX)) - return CborErrorDataTooLarge; - - *result = (int)v; - } - return CborNoError; - -} - -/** - * \fn bool cbor_value_is_length_known(const CborValue *value) - * - * Returns true if the length of this type is known without calculation. That - * is, if the length of this CBOR string, map or array is encoded in the data - * stream, this function returns true. If the length is not encoded, it returns - * false. - * - * If the length is known, code can call cbor_value_get_string_length(), - * cbor_value_get_array_length() or cbor_value_get_map_length() to obtain the - * length. If the length is not known but is necessary, code can use the - * cbor_value_calculate_string_length() function (no equivalent function is - * provided for maps and arrays). - */ - -/** - * \fn bool cbor_value_is_text_string(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR text - * string. CBOR text strings are UTF-8 encoded and usually contain - * human-readable text. - * - * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), - * cbor_value_copy_text_string(), cbor_value_dup_text_string() - */ - -/** - * \fn bool cbor_value_is_byte_string(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR text - * string. CBOR byte strings are binary data with no specified encoding or - * format. - * - * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), - * cbor_value_copy_byte_string(), cbor_value_dup_byte_string() - */ - -/** - * \fn CborError cbor_value_get_string_length(const CborValue *value, size_t *length) - * - * Extracts the length of the byte or text string that \a value points to and - * stores it in \a result. If the iterator \a value does not point to a text - * string or a byte string, the behaviour is undefined, so checking with \ref - * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref - * cbor_value_is_byte_string is recommended. - * - * If the length of this string is not encoded in the CBOR data stream, this - * function will return the recoverable error CborErrorUnknownLength. You may - * also check whether that is the case by using cbor_value_is_length_known(). - * - * If the length of the string is required but the length was not encoded, use - * cbor_value_calculate_string_length(), but note that that function does not - * run in constant time. - * - * \note On 32-bit platforms, this function will return error condition of \ref - * CborErrorDataTooLarge if the stream indicates a length that is too big to - * fit in 32-bit. - * - * \sa cbor_value_is_valid(), cbor_value_is_length_known(), cbor_value_calculate_string_length() - */ - -/** - * Calculates the length of the byte or text string that \a value points to and - * stores it in \a len. If the iterator \a value does not point to a text - * string or a byte string, the behaviour is undefined, so checking with \ref - * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref - * cbor_value_is_byte_string is recommended. - * - * This function is different from cbor_value_get_string_length() in that it - * calculates the length even for strings sent in chunks. For that reason, this - * function may not run in constant time (it will run in O(n) time on the - * number of chunks). It does use constant memory (O(1)). - * - * \note On 32-bit platforms, this function will return error condition of \ref - * CborErrorDataTooLarge if the stream indicates a length that is too big to - * fit in 32-bit. - * - * \sa cbor_value_get_string_length(), cbor_value_copy_string(), cbor_value_is_length_known() - */ -CborError cbor_value_calculate_string_length(const CborValue *value, size_t *len) -{ - *len = SIZE_MAX; - return _cbor_value_copy_string(value, NULL, len, NULL); -} - -/* We return uintptr_t so that we can pass memcpy directly as the iteration - * function. The choice is to optimize for memcpy, which is used in the base - * parser API (cbor_value_copy_string), while memcmp is used in convenience API - * only. */ -typedef uintptr_t (*IterateFunction)(char *, const uint8_t *, size_t); - -static uintptr_t iterate_noop(char *dest, const uint8_t *src, size_t len) -{ - (void)dest; - (void)src; - (void)len; - return true; -} - -static uintptr_t iterate_memcmp(char *s1, const uint8_t *s2, size_t len) -{ - return memcmp(s1, (const char *)s2, len) == 0; -} - -static uintptr_t iterate_memcpy(char *dest, const uint8_t *src, size_t len) -{ - return (uintptr_t)memcpy(dest, src, len); -} - -static CborError iterate_string_chunks(const CborValue *value, char *buffer, size_t *buflen, - bool *result, CborValue *next, IterateFunction func) -{ - assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); - - size_t total; - CborError err; - const uint8_t *ptr = value->ptr; - if (cbor_value_is_length_known(value)) { - /* easy case: fixed length */ - err = extract_length(value->parser, &ptr, &total); - if (err) - return err; - if (total > (size_t)(value->parser->end - ptr)) - return CborErrorUnexpectedEOF; - if (total <= *buflen) - *result = !!func(buffer, ptr, total); - else - *result = false; - ptr += total; - } else { - /* chunked */ - ++ptr; - total = 0; - *result = true; - while (true) { - size_t chunkLen; - size_t newTotal; - - if (ptr == value->parser->end) - return CborErrorUnexpectedEOF; - - if (*ptr == (uint8_t)BreakByte) { - ++ptr; - break; - } - - /* is this the right type? */ - if ((*ptr & MajorTypeMask) != value->type) - return CborErrorIllegalType; - - err = extract_length(value->parser, &ptr, &chunkLen); - if (err) - return err; - - if (unlikely(add_check_overflow(total, chunkLen, &newTotal))) - return CborErrorDataTooLarge; - - if (chunkLen > (size_t)(value->parser->end - ptr)) - return CborErrorUnexpectedEOF; - - if (*result && *buflen >= newTotal) - *result = !!func(buffer + total, ptr, chunkLen); - else - *result = false; - - ptr += chunkLen; - total = newTotal; - } - } - - /* is there enough room for the ending NUL byte? */ - if (*result && *buflen > total) { - uint8_t nul[] = { 0 }; - *result = !!func(buffer + total, nul, 1); - } - *buflen = total; - - if (next) { - *next = *value; - next->ptr = ptr; - return preparse_next_value(next); - } - return CborNoError; -} - -/** - * \fn CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) - * - * Copies the string pointed by \a value into the buffer provided at \a buffer - * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not - * copy anything and will only update the \a next value. - * - * If the iterator \a value does not point to a text string, the behaviour is - * undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_text_string is recommended. - * - * If the provided buffer length was too small, this function returns an error - * condition of \ref CborErrorOutOfMemory. If you need to calculate the length - * of the string in order to preallocate a buffer, use - * cbor_value_calculate_string_length(). - * - * On success, this function sets the number of bytes copied to \c{*buflen}. If - * the buffer is large enough, this function will insert a null byte after the - * last copied byte, to facilitate manipulation of text strings. That byte is - * not included in the returned value of \c{*buflen}. - * - * The \a next pointer, if not null, will be updated to point to the next item - * after this string. If \a value points to the last item, then \a next will be - * invalid. - * - * This function may not run in constant time (it will run in O(n) time on the - * number of chunks). It requires constant memory (O(1)). - * - * \note This function does not perform UTF-8 validation on the incoming text - * string. - * - * \sa cbor_value_dup_text_string(), cbor_value_copy_byte_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() - */ - -/** - * \fn CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, size_t *buflen, CborValue *next) - * - * Copies the string pointed by \a value into the buffer provided at \a buffer - * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not - * copy anything and will only update the \a next value. - * - * If the iterator \a value does not point to a byte string, the behaviour is - * undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_byte_string is recommended. - * - * If the provided buffer length was too small, this function returns an error - * condition of \ref CborErrorOutOfMemory. If you need to calculate the length - * of the string in order to preallocate a buffer, use - * cbor_value_calculate_string_length(). - * - * On success, this function sets the number of bytes copied to \c{*buflen}. If - * the buffer is large enough, this function will insert a null byte after the - * last copied byte, to facilitate manipulation of null-terminated strings. - * That byte is not included in the returned value of \c{*buflen}. - * - * The \a next pointer, if not null, will be updated to point to the next item - * after this string. If \a value points to the last item, then \a next will be - * invalid. - * - * This function may not run in constant time (it will run in O(n) time on the - * number of chunks). It requires constant memory (O(1)). - * - * \sa cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() - */ - -CborError _cbor_value_copy_string(const CborValue *value, void *buffer, - size_t *buflen, CborValue *next) -{ - bool copied_all; - CborError err = iterate_string_chunks(value, (char*)buffer, buflen, &copied_all, next, - buffer ? iterate_memcpy : iterate_noop); - return err ? err : - copied_all ? CborNoError : CborErrorOutOfMemory; -} - -/** - * Compares the entry \a value with the string \a string and store the result - * in \a result. If the value is different from \a string \a result will - * contain \c false. - * - * The entry at \a value may be a tagged string. If \a is not a string or a - * tagged string, the comparison result will be false. - * - * CBOR requires text strings to be encoded in UTF-8, but this function does - * not validate either the strings in the stream or the string \a string to be - * matched. Moreover, comparison is done on strict codepoint comparison, - * without any Unicode normalization. - * - * This function may not run in constant time (it will run in O(n) time on the - * number of chunks). It requires constant memory (O(1)). - * - * \sa cbor_value_skip_tag(), cbor_value_copy_text_string() - */ -CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result) -{ - CborValue copy = *value; - CborError err = cbor_value_skip_tag(©); - if (err) - return err; - if (!cbor_value_is_text_string(©)) { - *result = false; - return CborNoError; - } - - size_t len = strlen(string); - return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); -} - -/** - * \fn bool cbor_value_is_array(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR array. - * - * \sa cbor_value_is_valid(), cbor_value_is_map() - */ - -/** - * \fn CborError cbor_value_get_array_length(const CborValue *value, size_t *length) - * - * Extracts the length of the CBOR array that \a value points to and stores it - * in \a result. If the iterator \a value does not point to a CBOR array, the - * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_array is recommended. - * - * If the length of this array is not encoded in the CBOR data stream, this - * function will return the recoverable error CborErrorUnknownLength. You may - * also check whether that is the case by using cbor_value_is_length_known(). - * - * \note On 32-bit platforms, this function will return error condition of \ref - * CborErrorDataTooLarge if the stream indicates a length that is too big to - * fit in 32-bit. - * - * \sa cbor_value_is_valid(), cbor_value_is_length_known() - */ - -/** - * \fn bool cbor_value_is_map(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR map. - * - * \sa cbor_value_is_valid(), cbor_value_is_array() - */ - -/** - * \fn CborError cbor_value_get_map_length(const CborValue *value, size_t *length) - * - * Extracts the length of the CBOR map that \a value points to and stores it in - * \a result. If the iterator \a value does not point to a CBOR map, the - * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_map is recommended. - * - * If the length of this map is not encoded in the CBOR data stream, this - * function will return the recoverable error CborErrorUnknownLength. You may - * also check whether that is the case by using cbor_value_is_length_known(). - * - * \note On 32-bit platforms, this function will return error condition of \ref - * CborErrorDataTooLarge if the stream indicates a length that is too big to - * fit in 32-bit. - * - * \sa cbor_value_is_valid(), cbor_value_is_length_known() - */ - -/** - * Attempts to find the value in map \a map that corresponds to the text string - * entry \a string. If the iterator \a value does not point to a CBOR map, the - * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_map is recommended. - * - * If the item is found, it is stored in \a result. If no item is found - * matching the key, then \a result will contain an element of type \ref - * CborInvalidType. Matching is performed using - * cbor_value_text_string_equals(), so tagged strings will also match. - * - * This function has a time complexity of O(n) where n is the number of - * elements in the map to be searched. In addition, this function is has O(n) - * memory requirement based on the number of nested containers (maps or arrays) - * found as elements of this map. - * - * \sa cbor_value_is_valid(), cbor_value_text_string_equals(), cbor_value_advance() - */ -CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) -{ - assert(cbor_value_is_map(map)); - size_t len = strlen(string); - CborError err = cbor_value_enter_container(map, element); - if (err) - goto error; - - while (!cbor_value_at_end(element)) { - /* find the non-tag so we can compare */ - err = cbor_value_skip_tag(element); - if (err) - goto error; - if (cbor_value_is_text_string(element)) { - bool equals; - size_t dummyLen = len; - err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, - &equals, element, iterate_memcmp); - if (err) - goto error; - if (equals) - return preparse_value(element); - } else { - /* skip this key */ - err = cbor_value_advance(element); - if (err) - goto error; - } - - /* skip this value */ - err = cbor_value_skip_tag(element); - if (err) - goto error; - err = cbor_value_advance(element); - if (err) - goto error; - } - - /* not found */ - element->type = CborInvalidType; - return CborNoError; - -error: - element->type = CborInvalidType; - return err; -} - -/** - * \fn bool cbor_value_is_float(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR - * single-precision floating point (32-bit). - * - * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_half_float() - */ - -/** - * \fn CborError cbor_value_get_float(const CborValue *value, float *result) - * - * Retrieves the CBOR single-precision floating point (32-bit) value that \a - * value points to and stores it in \a result. If the iterator \a value does - * not point to a single-precision floating point value, the behavior is - * undefined, so checking with \ref cbor_value_get_type or with \ref - * cbor_value_is_float is recommended. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_float(), cbor_value_get_double() - */ - -/** - * \fn bool cbor_value_is_double(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR - * double-precision floating point (64-bit). - * - * \sa cbor_value_is_valid(), cbor_value_is_float(), cbor_value_is_half_float() - */ - -/** - * \fn CborError cbor_value_get_double(const CborValue *value, float *result) - * - * Retrieves the CBOR double-precision floating point (64-bit) value that \a - * value points to and stores it in \a result. If the iterator \a value does - * not point to a double-precision floating point value, the behavior is - * undefined, so checking with \ref cbor_value_get_type or with \ref - * cbor_value_is_double is recommended. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_double(), cbor_value_get_float() - */ - -/** - * \fn bool cbor_value_is_half_float(const CborValue *value) - * - * Returns true if the iterator \a value is valid and points to a CBOR - * single-precision floating point (16-bit). - * - * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_float() - */ - -/** - * Retrieves the CBOR half-precision floating point (16-bit) value that \a - * value points to and stores it in \a result. If the iterator \a value does - * not point to a half-precision floating point value, the behavior is - * undefined, so checking with \ref cbor_value_get_type or with \ref - * cbor_value_is_half_float is recommended. - * - * Note: since the C language does not have a standard type for half-precision - * floating point, this function takes a \c{void *} as a parameter for the - * storage area, which must be at least 16 bits wide. - * - * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_float() - */ -CborError cbor_value_get_half_float(const CborValue *value, void *result) -{ - assert(cbor_value_is_half_float(value)); - - /* size has been computed already */ - uint16_t v = get16(value->ptr + 1); - memcpy(result, &v, sizeof(v)); - return CborNoError; -} - -/** @} */ diff --git a/third_party/tinycbor/cborparser_dup_string.c b/third_party/tinycbor/cborparser_dup_string.c deleted file mode 100644 index 60dbdbeb95..0000000000 --- a/third_party/tinycbor/cborparser_dup_string.c +++ /dev/null @@ -1,113 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS 1 -#endif - -#include "cbor.h" -#include - -/** - * \fn CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, size_t *buflen, CborValue *next) - * - * Allocates memory for the string pointed by \a value and copies it into this - * buffer. The pointer to the buffer is stored in \a buffer and the number of - * bytes copied is stored in \a len (those variables must not be NULL). - * - * If the iterator \a value does not point to a text string, the behaviour is - * undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_text_string is recommended. - * - * If \c malloc returns a NULL pointer, this function will return error - * condition \ref CborErrorOutOfMemory. - * - * On success, \c{*buffer} will contain a valid pointer that must be freed by - * calling \c{free()}. This is the case even for zero-length strings. - * - * The \a next pointer, if not null, will be updated to point to the next item - * after this string. If \a value points to the last item, then \a next will be - * invalid. - * - * This function may not run in constant time (it will run in O(n) time on the - * number of chunks). It requires constant memory (O(1)) in addition to the - * malloc'ed block. - * - * \note This function does not perform UTF-8 validation on the incoming text - * string. - * - * \sa cbor_value_copy_text_string(), cbor_value_dup_byte_string() - */ - -/** - * \fn CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, size_t *buflen, CborValue *next) - * - * Allocates memory for the string pointed by \a value and copies it into this - * buffer. The pointer to the buffer is stored in \a buffer and the number of - * bytes copied is stored in \a len (those variables must not be NULL). - * - * If the iterator \a value does not point to a byte string, the behaviour is - * undefined, so checking with \ref cbor_value_get_type or \ref - * cbor_value_is_byte_string is recommended. - * - * If \c malloc returns a NULL pointer, this function will return error - * condition \ref CborErrorOutOfMemory. - * - * On success, \c{*buffer} will contain a valid pointer that must be freed by - * calling \c{free()}. This is the case even for zero-length strings. - * - * The \a next pointer, if not null, will be updated to point to the next item - * after this string. If \a value points to the last item, then \a next will be - * invalid. - * - * This function may not run in constant time (it will run in O(n) time on the - * number of chunks). It requires constant memory (O(1)) in addition to the - * malloc'ed block. - * - * \sa cbor_value_copy_byte_string(), cbor_value_dup_text_string() - */ -CborError _cbor_value_dup_string(const CborValue *value, void **buffer, size_t *buflen, CborValue *next) -{ - assert(buffer); - assert(buflen); - *buflen = SIZE_MAX; - CborError err = _cbor_value_copy_string(value, NULL, buflen, NULL); - if (err) - return err; - - ++*buflen; - *buffer = malloc(*buflen); - if (!*buffer) { - /* out of memory */ - return CborErrorOutOfMemory; - } - err = _cbor_value_copy_string(value, *buffer, buflen, next); - if (err) { - free(*buffer); - return err; - } - return CborNoError; -} diff --git a/third_party/tinycbor/cborpretty.c b/third_party/tinycbor/cborpretty.c deleted file mode 100644 index 4b1a6b6ab6..0000000000 --- a/third_party/tinycbor/cborpretty.c +++ /dev/null @@ -1,471 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#ifndef __STDC_LIMIT_MACROS -# define __STDC_LIMIT_MACROS 1 -#endif - -#include "cbor.h" -#include "compilersupport_p.h" -#include "math_support_p.h" - -#include -#include -#include -#include -#include -#include - -/** - * \defgroup CborPretty Converting CBOR to text - * \brief Group of functions used to convert CBOR to text form. - * - * This group contains two functions that are can be used to convert one - * CborValue object to a text representation. This module attempts to follow - * the recommendations from RFC 7049 section 6 "Diagnostic Notation", though it - * has a few differences. They are noted below. - * - * TinyCBOR does not provide a way to convert from the text representation back - * to encoded form. To produce a text form meant to be parsed, CborToJson is - * recommended instead. - * - * Either of the functions in this section will attempt to convert exactly one - * CborValue object to text. Those functions may return any error documented - * for the functions for CborParsing. In addition, if the C standard library - * stream functions return with error, the text conversion will return with - * error CborErrorIO. - * - * These functions also perform UTF-8 validation in CBOR text strings. If they - * encounter a sequence of bytes that not permitted in UTF-8, they will return - * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points - * in UTF-8. - * - * \warning The output type produced by these functions is not guaranteed to - * remain stable. A future update of TinyCBOR may produce different output for - * the same input and parsers may be unable to handle them. - * - * \sa CborParsing, CborToJson, cbor_parser_init() - */ - -/** - * \addtogroup CborPretty - * @{ - *

Text format

- * - * As described in RFC 7049 section 6 "Diagnostic Notation", the format is - * largely borrowed from JSON, but modified to suit CBOR's different data - * types. TinyCBOR makes further modifications to distinguish different, but - * similar values. - * - * CBOR values are currently encoded as follows: - * \par Integrals (unsigned and negative) - * Base-10 (decimal) text representation of the value - * \par Byte strings: - * "h'" followed by the Base16 (hex) representation of the binary data, followed by an ending quote (') - * \par Text strings: - * C-style escaped string in quotes, with C11/C++11 escaping of Unicode codepoints above U+007F. - * \par Tags: - * Tag value, with the tagged value in parentheses. No special encoding of the tagged value is performed. - * \par Simple types: - * "simple(nn)" where \c nn is the simple value - * \par Null: - * \c null - * \par Undefined: - * \c undefined - * \par Booleans: - * \c true or \c false - * \par Floating point: - * If NaN or infinite, the actual words \c NaN or \c infinite. - * Otherwise, the decimal representation with as many digits as necessary to ensure no loss of information, - * with float values suffixed by "f" and half-float values suffixed by "f16" (doubles have no suffix). A dot is always present. - * \par Arrays: - * Comma-separated list of elements, enclosed in square brackets ("[" and "]"). - * If the array length is indeterminate, an underscore ("_") appears immediately after the opening bracket. - * \par Maps: - * Comma-separated list of key-value pairs, with the key and value separated - * by a colon (":"), enclosed in curly braces ("{" and "}"). - * If the map length is indeterminate, an underscore ("_") appears immediately after the opening brace. - */ - -static int hexDump(FILE *out, const uint8_t *buffer, size_t n) -{ - while (n--) { - int r = fprintf(out, "%02" PRIx8, *buffer++); - if (r < 0) - return r; - } - return 0; /* should be n * 2, but we don't have the original n anymore */ -} - -/* This function decodes buffer as UTF-8 and prints as escaped UTF-16. - * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */ -static int utf8EscapedDump(FILE *out, const char *buffer, size_t n) -{ - uint32_t uc; - while (n--) { - uc = (uint8_t)*buffer++; - if (uc < 0x80) { - /* single-byte UTF-8 */ - if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') { - if (fprintf(out, "%c", (char)uc) < 0) - return CborErrorIO; - continue; - } - - /* print as an escape sequence */ - char escaped = (char)uc; - switch (uc) { - case '"': - case '\\': - break; - case '\b': - escaped = 'b'; - break; - case '\f': - escaped = 'f'; - break; - case '\n': - escaped = 'n'; - break; - case '\r': - escaped = 'r'; - break; - case '\t': - escaped = 't'; - break; - default: - goto print_utf16; - } - if (fprintf(out, "\\%c", escaped) < 0) - return CborErrorIO; - continue; - } - - /* multi-byte UTF-8, decode it */ - unsigned charsNeeded; - uint32_t min_uc; - if (unlikely(uc <= 0xC1)) - return CborErrorInvalidUtf8TextString; - if (uc < 0xE0) { - /* two-byte UTF-8 */ - charsNeeded = 2; - min_uc = 0x80; - uc &= 0x1f; - } else if (uc < 0xF0) { - /* three-byte UTF-8 */ - charsNeeded = 3; - min_uc = 0x800; - uc &= 0x0f; - } else if (uc < 0xF5) { - /* four-byte UTF-8 */ - charsNeeded = 4; - min_uc = 0x10000; - uc &= 0x07; - } else { - return CborErrorInvalidUtf8TextString; - } - - if (n < charsNeeded - 1) - return CborErrorInvalidUtf8TextString; - n -= charsNeeded - 1; - - /* first continuation character */ - uint8_t b = (uint8_t)*buffer++; - if ((b & 0xc0) != 0x80) - return CborErrorInvalidUtf8TextString; - uc <<= 6; - uc |= b & 0x3f; - - if (charsNeeded > 2) { - /* second continuation character */ - b = (uint8_t)*buffer++; - if ((b & 0xc0) != 0x80) - return CborErrorInvalidUtf8TextString; - uc <<= 6; - uc |= b & 0x3f; - - if (charsNeeded > 3) { - /* third continuation character */ - b = (uint8_t)*buffer++; - if ((b & 0xc0) != 0x80) - return CborErrorInvalidUtf8TextString; - uc <<= 6; - uc |= b & 0x3f; - } - } - - /* overlong sequence? surrogate pair? out or range? */ - if (uc < min_uc || uc - 0xd800U < 2048U || uc > 0x10ffff) - return CborErrorInvalidUtf8TextString; - - /* now print the sequence */ - if (charsNeeded > 3) { - /* needs surrogate pairs */ - if (fprintf(out, "\\u%04" PRIX32 "\\u%04" PRIX32, - (uc >> 10) + 0xd7c0, /* high surrogate */ - (uc % 0x0400) + 0xdc00) < 0) - return CborErrorIO; - } else { -print_utf16: - /* no surrogate pair needed */ - if (fprintf(out, "\\u%04" PRIX32, uc) < 0) - return CborErrorIO; - } - } - return CborNoError; -} - -static CborError value_to_pretty(FILE *out, CborValue *it); -static CborError container_to_pretty(FILE *out, CborValue *it, CborType containerType) -{ - const char *comma = ""; - while (!cbor_value_at_end(it)) { - if (fprintf(out, "%s", comma) < 0) - return CborErrorIO; - comma = ", "; - - CborError err = value_to_pretty(out, it); - if (err) - return err; - - if (containerType == CborArrayType) - continue; - - /* map: that was the key, so get the value */ - if (fprintf(out, ": ") < 0) - return CborErrorIO; - err = value_to_pretty(out, it); - if (err) - return err; - } - return CborNoError; -} - -static CborError value_to_pretty(FILE *out, CborValue *it) -{ - CborError err; - CborType type = cbor_value_get_type(it); - switch (type) { - case CborArrayType: - case CborMapType: { - /* recursive type */ - CborValue recursed; - - if (fprintf(out, type == CborArrayType ? "[" : "{") < 0) - return CborErrorIO; - if (!cbor_value_is_length_known(it)) { - if (fprintf(out, "_ ") < 0) - return CborErrorIO; - } - - err = cbor_value_enter_container(it, &recursed); - if (err) { - it->ptr = recursed.ptr; - return err; /* parse error */ - } - err = container_to_pretty(out, &recursed, type); - if (err) { - it->ptr = recursed.ptr; - return err; /* parse error */ - } - err = cbor_value_leave_container(it, &recursed); - if (err) - return err; /* parse error */ - - if (fprintf(out, type == CborArrayType ? "]" : "}") < 0) - return CborErrorIO; - return CborNoError; - } - - case CborIntegerType: { - uint64_t val; - cbor_value_get_raw_integer(it, &val); /* can't fail */ - - if (cbor_value_is_unsigned_integer(it)) { - if (fprintf(out, "%" PRIu64, val) < 0) - return CborErrorIO; - } else { - /* CBOR stores the negative number X as -1 - X - * (that is, -1 is stored as 0, -2 as 1 and so forth) */ - if (++val) { /* unsigned overflow may happen */ - if (fprintf(out, "-%" PRIu64, val) < 0) - return CborErrorIO; - } else { - /* overflown - * 0xffff`ffff`ffff`ffff + 1 = - * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */ - if (fprintf(out, "-18446744073709551616") < 0) - return CborErrorIO; - } - } - break; - } - - case CborByteStringType:{ - size_t n = 0; - uint8_t *buffer; - err = cbor_value_dup_byte_string(it, &buffer, &n, it); - if (err) - return err; - - bool failed = fprintf(out, "h'") < 0 || hexDump(out, buffer, n) < 0 || fprintf(out, "'") < 0; - free(buffer); - return failed ? CborErrorIO : CborNoError; - } - - case CborTextStringType: { - size_t n = 0; - char *buffer; - err = cbor_value_dup_text_string(it, &buffer, &n, it); - if (err) - return err; - - err = CborNoError; - bool failed = fprintf(out, "\"") < 0 - || (err = utf8EscapedDump(out, buffer, n)) != CborNoError - || fprintf(out, "\"") < 0; - free(buffer); - return err != CborNoError ? err : - failed ? CborErrorIO : CborNoError; - } - - case CborTagType: { - CborTag tag; - cbor_value_get_tag(it, &tag); /* can't fail */ - if (fprintf(out, "%" PRIu64 "(", tag) < 0) - return CborErrorIO; - err = cbor_value_advance_fixed(it); - if (err) - return err; - err = value_to_pretty(out, it); - if (err) - return err; - if (fprintf(out, ")") < 0) - return CborErrorIO; - return CborNoError; - } - - case CborSimpleType: { - uint8_t simple_type; - cbor_value_get_simple_type(it, &simple_type); /* can't fail */ - if (fprintf(out, "simple(%" PRIu8 ")", simple_type) < 0) - return CborErrorIO; - break; - } - - case CborNullType: - if (fprintf(out, "null") < 0) - return CborErrorIO; - break; - - case CborUndefinedType: - if (fprintf(out, "undefined") < 0) - return CborErrorIO; - break; - - case CborBooleanType: { - bool val; - cbor_value_get_boolean(it, &val); /* can't fail */ - if (fprintf(out, val ? "true" : "false") < 0) - return CborErrorIO; - break; - } - - case CborDoubleType: { - const char *suffix; - double val; - if (false) { - float f; - case CborFloatType: - cbor_value_get_float(it, &f); - val = f; - suffix = "f"; - } else if (false) { - uint16_t f16; - case CborHalfFloatType: - cbor_value_get_half_float(it, &f16); - val = decode_half(f16); - suffix = "f16"; - } else { - cbor_value_get_double(it, &val); - suffix = ""; - } - - int r = fpclassify(val); - if (r == FP_NAN || r == FP_INFINITE) - suffix = ""; - - uint64_t ival = (uint64_t)fabs(val); - if (ival == fabs(val)) { - /* this double value fits in a 64-bit integer, so show it as such - * (followed by a floating point suffix, to disambiguate) */ - r = fprintf(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix); - } else { - /* this number is definitely not a 64-bit integer */ - r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix); - } - if (r < 0) - return CborErrorIO; - break; - } - - case CborInvalidType: - if (fprintf(out, "invalid") < 0) - return CborErrorIO; - return CborErrorUnknownType; - } - - err = cbor_value_advance_fixed(it); - return err; -} - -/** - * \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value) - * - * Converts the current CBOR type pointed by \a value to its textual - * representation and writes it to the \a out stream. If an error occurs, this - * function returns an error code similar to CborParsing. - * - * \sa cbor_value_to_pretty_advance(), cbor_value_to_json_advance() - */ - -/** - * Converts the current CBOR type pointed by \a value to its textual - * representation and writes it to the \a out stream. If an error occurs, this - * function returns an error code similar to CborParsing. - * - * If no error ocurred, this function advances \a value to the next element. - * Often, concatenating the text representation of multiple elements can be - * done by appending a comma to the output stream. - * - * \sa cbor_value_to_pretty(), cbor_value_to_json_advance() - */ -CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value) -{ - return value_to_pretty(out, value); -} - -/** @} */ diff --git a/third_party/tinycbor/compilersupport_p.h b/third_party/tinycbor/compilersupport_p.h deleted file mode 100644 index dc8597db83..0000000000 --- a/third_party/tinycbor/compilersupport_p.h +++ /dev/null @@ -1,234 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#ifndef COMPILERSUPPORT_H -#define COMPILERSUPPORT_H - -#include "cbor.h" - -#ifndef _BSD_SOURCE -# define _BSD_SOURCE -#endif -#ifndef _DEFAULT_SOURCE -# define _DEFAULT_SOURCE -#endif -#include -#include -#include -#include -#include - -#ifndef __cplusplus -# include -#endif - -#ifdef __F16C__ -# include -#endif - -#if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L || __cpp_static_assert >= 200410 -# define cbor_static_assert(x) static_assert(x, #x) -#elif !defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) && (__STDC_VERSION__ > 199901L) -# define cbor_static_assert(x) _Static_assert(x, #x) -#else -# define cbor_static_assert(x) ((void)sizeof(char[2*!!(x) - 1])) -#endif -#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) -/* inline is a keyword */ -#else -/* use the definition from cbor.h */ -# define inline CBOR_INLINE -#endif - -#ifndef STRINGIFY -#define STRINGIFY(x) STRINGIFY2(x) -#endif -#define STRINGIFY2(x) #x - -#if !defined(UINT32_MAX) || !defined(INT64_MAX) -/* C89? We can define UINT32_MAX portably, but not INT64_MAX */ -# error "Your system has stdint.h but that doesn't define UINT32_MAX or INT64_MAX" -#endif - -#ifndef DBL_DECIMAL_DIG -/* DBL_DECIMAL_DIG is C11 */ -# define DBL_DECIMAL_DIG 17 -#endif -#define DBL_DECIMAL_DIG_STR STRINGIFY(DBL_DECIMAL_DIG) - -#ifndef __has_builtin -# define __has_builtin(x) 0 -#endif - - -/* Disable this optimization for TI ARM compiler v18 or higher because it has some issues -with these intrinsics. */ -#if !defined(__TI_COMPILER_VERSION__) || __TI_COMPILER_VERSION__ < 18000000 -#if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) || \ - (__has_builtin(__builtin_bswap64) && __has_builtin(__builtin_bswap32)) -# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define cbor_ntohll __builtin_bswap64 -# define cbor_htonll __builtin_bswap64 -# define cbor_ntohl __builtin_bswap32 -# define cbor_htonl __builtin_bswap32 -# ifdef __INTEL_COMPILER -# define cbor_ntohs _bswap16 -# define cbor_htons _bswap16 -# elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 608) || __has_builtin(__builtin_bswap16) -# define cbor_ntohs __builtin_bswap16 -# define cbor_htons __builtin_bswap16 -# else -# define cbor_ntohs(x) (((uint16_t)x >> 8) | ((uint16_t)x << 8)) -# define cbor_htons cbor_ntohs -# endif -# else -# define cbor_ntohll -# define cbor_htonll -# define cbor_ntohl -# define cbor_htonl -# define cbor_ntohs -# define cbor_htons -# endif -#elif defined(__sun) -# include -#elif defined(_MSC_VER) -/* MSVC, which implies Windows, which implies little-endian and sizeof(long) == 4 */ -# define cbor_ntohll _byteswap_uint64 -# define cbor_htonll _byteswap_uint64 -# define cbor_ntohl _byteswap_ulong -# define cbor_htonl _byteswap_ulong -# define cbor_ntohs _byteswap_ushort -# define cbor_htons _byteswap_ushort -#endif -#endif -#ifndef cbor_ntohs -# define cbor_ntohs(x) (((uint16_t)x >> 8) | ((uint16_t)x << 8)) -# define cbor_htons cbor_ntohs -//# include -//# define cbor_ntohs ntohs -//# define cbor_htons htons -#endif -#ifndef cbor_ntohl -//# include -//# define cbor_ntohl ntohl -//# define cbor_htonl htonl -# define cbor_ntohl(x) ((((uint32_t)x >> 24) & 0xff) | (((uint32_t)x >> 8) & 0xff00) | (((uint32_t)x & 0xff00) << 8) | (((uint32_t)x & 0xff) << 24)) -# define cbor_htonl cbor_ntohl -#endif -#ifndef cbor_ntohll -# define cbor_ntohll ntohll -# define cbor_htonll htonll -/* ntohll isn't usually defined */ -# ifndef ntohll -# if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# define ntohll -# define htonll -# elif defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define ntohll(x) ((ntohl((uint32_t)(x)) * UINT64_C(0x100000000)) + (ntohl((x) >> 32))) -# define htonll ntohll -# elif __little_endian__ == 1 -# define ntohll(x) ((cbor_ntohl(((uint32_t)(x))) * UINT64_C(0x100000000)) + (cbor_ntohl(((x) >> 32)))) -# define htonll ntohll -# else -# error "Unable to determine byte order!" -# endif -# endif -#endif - - -#ifdef __cplusplus -# define CONST_CAST(t, v) const_cast(v) -#else -/* C-style const_cast without triggering a warning with -Wcast-qual */ -# define CONST_CAST(t, v) (t)(uintptr_t)(v) -#endif - -#ifdef __GNUC__ -#ifndef likely -# define likely(x) __builtin_expect(!!(x), 1) -#endif -#ifndef unlikely -# define unlikely(x) __builtin_expect(!!(x), 0) -#endif -# define unreachable() __builtin_unreachable() -#elif defined(_MSC_VER) -# define likely(x) (x) -# define unlikely(x) (x) -# define unreachable() __assume(0) -#else -# define likely(x) (x) -# define unlikely(x) (x) -# define unreachable() do {} while (0) -#endif - - - -static inline bool add_check_overflow(size_t v1, size_t v2, size_t *r) -{ -#if ((defined(__GNUC__) && (__GNUC__ >= 5)) && !defined(__INTEL_COMPILER)) || __has_builtin(__builtin_add_overflow) - return __builtin_add_overflow(v1, v2, r); -#else - /* unsigned additions are well-defined */ - *r = v1 + v2; - return v1 > v1 + v2; -#endif -} - -static inline unsigned short encode_half(double val) -{ -#ifdef __F16C__ - return _cvtss_sh(val, 3); -#else - uint64_t v; - memcpy(&v, &val, sizeof(v)); - int sign = v >> 63 << 15; - int exp = (v >> 52) & 0x7ff; - int mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ - exp -= 1023; - if (exp == 1024) { - /* infinity or NaN */ - exp = 16; - mant >>= 1; - } else if (exp >= 16) { - /* overflow, as largest number */ - exp = 15; - mant = 1023; - } else if (exp >= -14) { - /* regular normal */ - } else if (exp >= -24) { - /* subnormal */ - mant |= 1024; - mant >>= -(exp + 14); - exp = -15; - } else { - /* underflow, make zero */ - return 0; - } - - /* safe cast here as bit operations above guarantee not to overflow */ - return (unsigned short)(sign | ((exp + 15) << 10) | mant); -#endif -} - -#endif /* COMPILERSUPPORT_H */ diff --git a/third_party/tinycbor/extract_number_p.h b/third_party/tinycbor/extract_number_p.h deleted file mode 100644 index b65ca4419e..0000000000 --- a/third_party/tinycbor/extract_number_p.h +++ /dev/null @@ -1,78 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#include "cbor.h" -#include "cborconstants_p.h" -#include "compilersupport_p.h" -#include - -static inline uint16_t get16(const uint8_t *ptr) -{ - uint16_t result; - memcpy(&result, ptr, sizeof(result)); - return cbor_ntohs(result); -} - -static inline uint32_t get32(const uint8_t *ptr) -{ - uint32_t result; - memcpy(&result, ptr, sizeof(result)); - return cbor_ntohl(result); -} - -static inline uint64_t get64(const uint8_t *ptr) -{ - uint64_t result; - memcpy(&result, ptr, sizeof(result)); - return cbor_ntohll(result); -} - -static CborError extract_number(const uint8_t **ptr, const uint8_t *end, uint64_t *len) -{ - uint8_t additional_information = **ptr & SmallValueMask; - ++*ptr; - if (additional_information < Value8Bit) { - *len = additional_information; - return CborNoError; - } - if (unlikely(additional_information > Value64Bit)) - return CborErrorIllegalNumber; - - size_t bytesNeeded = (size_t)(1 << (additional_information - Value8Bit)); - if (unlikely(bytesNeeded > (size_t)(end - *ptr))) { - return CborErrorUnexpectedEOF; - } else if (bytesNeeded == 1) { - *len = (uint8_t)(*ptr)[0]; - } else if (bytesNeeded == 2) { - *len = get16(*ptr); - } else if (bytesNeeded == 4) { - *len = get32(*ptr); - } else { - *len = get64(*ptr); - } - *ptr += bytesNeeded; - return CborNoError; -} diff --git a/third_party/tinycbor/math_support_p.h b/third_party/tinycbor/math_support_p.h deleted file mode 100644 index 676f781cc1..0000000000 --- a/third_party/tinycbor/math_support_p.h +++ /dev/null @@ -1,47 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Intel Corporation -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -** -****************************************************************************/ - -#ifndef MATH_SUPPORT_H -#define MATH_SUPPORT_H - -#include - -/* this function was copied & adapted from RFC 7049 Appendix D */ -static inline double decode_half(unsigned short half) -{ -#ifdef __F16C__ - return _cvtsh_ss(half); -#else - int exp = (half >> 10) & 0x1f; - int mant = half & 0x3ff; - double val; - if (exp == 0) val = ldexp(mant, -24); - else if (exp != 31) val = ldexp(mant + 1024, exp - 25); - else val = mant == 0 ? INFINITY : NAN; - return half & 0x8000 ? -val : val; -#endif -} - -#endif // MATH_SUPPORT_H - diff --git a/third_party/tinycbor/tinycbor b/third_party/tinycbor/tinycbor new file mode 160000 index 0000000000..d94ca09aa9 --- /dev/null +++ b/third_party/tinycbor/tinycbor @@ -0,0 +1 @@ +Subproject commit d94ca09aa91f5b3c581527aa8bca179a82b79874 diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt new file mode 100644 index 0000000000..5ba0619ed0 --- /dev/null +++ b/third_party/unity/CMakeLists.txt @@ -0,0 +1,5 @@ +# Unity test framework. +add_library( unity SHARED + unity/unity.c + unity/fixture/unity_fixture.c + unity/fixture/unity_memory_mt.c ) diff --git a/tests/unity/fixture/unity_fixture.c b/third_party/unity/unity/fixture/unity_fixture.c similarity index 100% rename from tests/unity/fixture/unity_fixture.c rename to third_party/unity/unity/fixture/unity_fixture.c diff --git a/tests/unity/fixture/unity_fixture.h b/third_party/unity/unity/fixture/unity_fixture.h similarity index 100% rename from tests/unity/fixture/unity_fixture.h rename to third_party/unity/unity/fixture/unity_fixture.h diff --git a/tests/unity/fixture/unity_fixture_internals.h b/third_party/unity/unity/fixture/unity_fixture_internals.h similarity index 100% rename from tests/unity/fixture/unity_fixture_internals.h rename to third_party/unity/unity/fixture/unity_fixture_internals.h diff --git a/tests/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h similarity index 100% rename from tests/unity/fixture/unity_fixture_malloc_overrides.h rename to third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h diff --git a/tests/unity/fixture/unity_memory_mt.c b/third_party/unity/unity/fixture/unity_memory_mt.c similarity index 100% rename from tests/unity/fixture/unity_memory_mt.c rename to third_party/unity/unity/fixture/unity_memory_mt.c diff --git a/tests/unity/unity.c b/third_party/unity/unity/unity.c similarity index 100% rename from tests/unity/unity.c rename to third_party/unity/unity/unity.c diff --git a/tests/unity/unity.h b/third_party/unity/unity/unity.h similarity index 100% rename from tests/unity/unity.h rename to third_party/unity/unity/unity.h diff --git a/tests/unity/unity_internals.h b/third_party/unity/unity/unity_internals.h similarity index 100% rename from tests/unity/unity_internals.h rename to third_party/unity/unity/unity_internals.h diff --git a/third_party/unity/unity/unity_memory_mt.c b/third_party/unity/unity/unity_memory_mt.c new file mode 100644 index 0000000000..b663b9a61a --- /dev/null +++ b/third_party/unity/unity/unity_memory_mt.c @@ -0,0 +1,89 @@ +/* Wrappers that make the unity memory functions thread-safe. Implemented for + * POSIX systems. */ + +#include "unity_fixture.h" +#include "unity_fixture_malloc_overrides.h" +#include + +pthread_mutex_t CriticalSectionMutex = PTHREAD_MUTEX_INITIALIZER; + +void UnityMalloc_StartTest(void) +{ + pthread_mutex_lock(&CriticalSectionMutex); + UnityMalloc_StartTest(); + pthread_mutex_unlock(&CriticalSectionMutex); +} + +void UnityMalloc_EndTest(void) +{ + pthread_mutex_lock(&CriticalSectionMutex); + UnityMalloc_EndTest(); + pthread_mutex_unlock(&CriticalSectionMutex); +} + +void UnityMalloc_MakeMallocFailAfterCount(int countdown) +{ + pthread_mutex_lock(&CriticalSectionMutex); + UnityMalloc_MakeMallocFailAfterCount(countdown); + pthread_mutex_unlock(&CriticalSectionMutex); +} + +void* unity_malloc_mt(size_t size) +{ + void* mem = NULL; + + if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + + mem = unity_malloc(size); + + if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) + { + unity_free(mem); + mem = NULL; + } + + return mem; +} + +void* unity_calloc_mt(size_t num, size_t size) +{ + void* mem = NULL; + + if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + + mem = unity_calloc(num, size); + + if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) + { + unity_free(mem); + mem = NULL; + } + + return mem; +} + +void* unity_realloc_mt(void * oldMem, size_t size) +{ + void* mem = NULL; + + if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + + mem = unity_realloc(oldMem, size); + + if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) + { + unity_free(mem); + mem = NULL; + } + + return mem; +} + +void unity_free_mt(void * mem) +{ + pthread_mutex_lock(&CriticalSectionMutex); + + unity_free(mem); + + pthread_mutex_unlock(&CriticalSectionMutex); +} From 9b5cc12ea3a013e335a7a85bc0d1ffdbfa6890f0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 12 Apr 2019 13:03:44 -0700 Subject: [PATCH 092/844] Fix compiler warnings in CI (#370) --- lib/include/aws_iot_defender.h | 2 +- lib/source/common/iot_taskpool.c | 2 +- .../aws_iot_static_memory_shadow.c | 4 ++-- .../static_memory/iot_static_memory_metrics.c | 2 +- .../static_memory/iot_static_memory_mqtt.c | 4 ++-- .../iot_static_memory_serializer.c | 4 ++-- .../static_memory/iot_static_memory_taskpool.c | 4 ++-- lib/source/mqtt/iot_mqtt_network.c | 2 +- tests/common/unit/iot_tests_taskpool.c | 18 +++++++++--------- tests/mqtt/unit/iot_tests_mqtt_api.c | 4 ++-- tests/shadow/unit/aws_iot_tests_shadow_api.c | 4 ++-- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 0a539bedcf..50af5e53ce 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -117,7 +117,7 @@ * @brief Intializers of data handles. */ /**@{ */ -#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER { 0 } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ +#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER { .mqttNetworkInfo = { 0 } } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ /**@} */ /** diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index f95db764ff..14f417ac63 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -76,7 +76,7 @@ * the system libraries as well. The system task pool needs to be initialized before any library is used or * before any code that posts jobs to the task pool runs. */ -IotTaskPool_t _IotSystemTaskPool = { 0 }; +IotTaskPool_t _IotSystemTaskPool = { .dispatchQueue = { 0 } }; /** @endcond */ diff --git a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c index 9a2e417d34..23731b8361 100644 --- a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c +++ b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c @@ -94,8 +94,8 @@ extern void IotStaticMemory_ReturnInUse( void * ptr, /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief Shadow operation in-use flags. */ -static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief Shadow operations. */ +static bool _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief Shadow operation in-use flags. */ +static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Shadow operations. */ static bool _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0 }; /**< @brief Shadow subscription in-use flags. */ static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ _SHADOW_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Shadow subscriptions. */ diff --git a/lib/source/common/static_memory/iot_static_memory_metrics.c b/lib/source/common/static_memory/iot_static_memory_metrics.c index 48aeb61eaf..2c3d00297b 100644 --- a/lib/source/common/static_memory/iot_static_memory_metrics.c +++ b/lib/source/common/static_memory/iot_static_memory_metrics.c @@ -66,7 +66,7 @@ * Static memory buffers and flags, allocated and zeroed at compile-time. */ static bool _inUseTcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { 0 }; - static IotMetricsTcpConnection_t _tcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { { 0 } }; + static IotMetricsTcpConnection_t _tcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { { .link = { 0 } } }; /*-----------------------------------------------------------*/ diff --git a/lib/source/common/static_memory/iot_static_memory_mqtt.c b/lib/source/common/static_memory/iot_static_memory_mqtt.c index cebe4e7bd5..949249251f 100644 --- a/lib/source/common/static_memory/iot_static_memory_mqtt.c +++ b/lib/source/common/static_memory/iot_static_memory_mqtt.c @@ -103,8 +103,8 @@ extern void IotStaticMemory_ReturnInUse( void * ptr, static bool _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ] = { 0 }; /**< @brief MQTT connection in-use flags. */ static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ -static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ -static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operations. */ +static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ +static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief MQTT operations. */ static bool _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ _MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ diff --git a/lib/source/common/static_memory/iot_static_memory_serializer.c b/lib/source/common/static_memory/iot_static_memory_serializer.c index a4227ffd02..b3172cc81a 100644 --- a/lib/source/common/static_memory/iot_static_memory_serializer.c +++ b/lib/source/common/static_memory/iot_static_memory_serializer.c @@ -101,13 +101,13 @@ * Static memory buffers and flags, allocated and zeroed at compile-time. */ static bool _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0 }; - static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { 0 } }; + static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { .data = { 0 } } }; static bool _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0 }; static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; static bool _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0 }; - static _cborValueWrapper_t _cborValues[IOT_SERIALIZER_CBOR_VALUES] = { { 0 } }; + static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { .isOutermost = false } }; static bool _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0 }; static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; diff --git a/lib/source/common/static_memory/iot_static_memory_taskpool.c b/lib/source/common/static_memory/iot_static_memory_taskpool.c index 42f93e115b..14269a484c 100644 --- a/lib/source/common/static_memory/iot_static_memory_taskpool.c +++ b/lib/source/common/static_memory/iot_static_memory_taskpool.c @@ -71,8 +71,8 @@ extern void IotStaticMemory_ReturnInUse( void * ptr, static bool _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool jobs in-use flags. */ static IotTaskPoolJob_t _pTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; /**< @brief Task pool jobs. */ -static bool _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer event in-use flags. */ -static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer events. */ +static bool _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer event in-use flags. */ +static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = { 0 } } }; /**< @brief Task pool timer events. */ /*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index c0f3e3e077..2c60df154a 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -861,7 +861,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, void * pReceiveContext ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttPacket_t incomingPacket = { 0 }; + _mqttPacket_t incomingPacket = { .pMqttConnection = NULL }; /* Cast context to correct type. */ _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pReceiveContext; diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index e6d985735f..d4c1b53dd7 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -444,7 +444,7 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) /* Create a task pool a tweak max threads up & down. */ { uint32_t count; - IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ] = { 0 }; + IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ] = { { 0 } }; IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -809,7 +809,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) IotTaskPool_t taskPool; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TAKPOOL_NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobBlockingUserContext_t userContext = { 0 }; + JobBlockingUserContext_t userContext; /* Initialize user context. */ TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); @@ -879,7 +879,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) /* Use a taskpool with not enough threads. */ const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TASKPOOL_NUMBER_OF_THREADS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobBlockingUserContext_t userContext = { 0 }; + JobBlockingUserContext_t userContext; /* Initialize user context. */ TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); @@ -1465,10 +1465,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; #else maxJobs = 10; - IotTaskPoolJob_t tpJobs[ 10 ] = { 0 }; + IotTaskPoolJob_t tpJobs[ 10 ] = { { 0 } }; #endif /* Create all jobs. */ @@ -1590,10 +1590,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; #else maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; #endif /* Schedule all jobs. */ @@ -1694,10 +1694,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; #else maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t jobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + IotTaskPoolJob_t jobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; #endif /* Initialize user context. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index ce2cc3944f..1844b8b948 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -324,8 +324,8 @@ static size_t _dupChecker( void * pSendContext, * for the AWS IoT MQTT server. */ #if _AWS_IOT_MQTT_SERVER == true static uint16_t lastPacketIdentifier = 0; - _mqttPacket_t publishPacket = { 0 }; - _mqttOperation_t publishOperation = { 0 }; + _mqttPacket_t publishPacket = { .pMqttConnection = NULL }; + _mqttOperation_t publishOperation = { .link = { 0 } }; publishPacket.type = publishFlags; publishPacket.pIncomingPublish = &publishOperation; diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index f9cfa0bc56..9deeab70d4 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -232,8 +232,8 @@ static size_t _sendSuccess( void * pSendContext, size_t messageLength ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t deserializedPublish = { 0 }; - _mqttPacket_t mqttPacket = { 0 }; + _mqttOperation_t deserializedPublish = { .link = { 0 } }; + _mqttPacket_t mqttPacket = { .pMqttConnection = NULL }; _receiveContext_t receiveContext = { 0 }; /* Ignore the send context. */ From de7993c6350c482093ead291a9058f0185d70760 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Fri, 12 Apr 2019 14:23:20 -0700 Subject: [PATCH 093/844] Changed IotQueue to IotDeQueue (double ended queue) and added DequeueHead/Tail. Because this queue is implemented as a list, this does not cause any overhead. (#372) --- lib/include/iot_linear_containers.h | 125 +++++++++++++----- lib/include/types/iot_taskpool_types.h | 2 +- lib/source/common/iot_taskpool.c | 25 ++-- .../common/unit/iot_tests_linear_containers.c | 12 +- tests/mqtt/unit/iot_tests_mqtt_receive.c | 6 +- 5 files changed, 116 insertions(+), 54 deletions(-) diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index f0cbdb15c8..621fe761d0 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -66,7 +66,7 @@ typedef IotLink_t IotListDouble_t; * @ingroup linear_containers_datatypes_listqueue * @brief Represents a queue. */ -typedef IotLink_t IotQueue_t; +typedef IotLink_t IotDeQueue_t; /** * @constantspage{linear_containers,linear containers library} @@ -87,7 +87,7 @@ typedef IotLink_t IotQueue_t; /* @[define_linear_containers_initializers] */ #define IOT_LINK_INITIALIZER { 0 } /**< @brief Initializer for an #IotLink_t. */ #define IOT_LIST_DOUBLE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotListDouble_t. */ -#define IOT_QUEUE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotQueue_t. */ +#define IOT_DEQUEUE_INITIALIZER IOT_LINK_INITIALIZER /**< @brief Initializer for an #IotDeQueue_t. */ /* @[define_linear_containers_initializers] */ /** @@ -154,9 +154,12 @@ typedef IotLink_t IotQueue_t; * - @functionname{linear_containers_function_queue_create} * - @functionname{linear_containers_function_queue_count} * - @functionname{linear_containers_function_queue_isempty} - * - @functionname{linear_containers_function_queue_peek} - * - @functionname{linear_containers_function_queue_enqueue} - * - @functionname{linear_containers_function_queue_dequeue} + * - @functionname{linear_containers_function_queue_peekhead} + * - @functionname{linear_containers_function_queue_peektail} + * - @functionname{linear_containers_function_queue_enqueuehead} + * - @functionname{linear_containers_function_queue_dequeuehead} + * - @functionname{linear_containers_function_queue_enqueuetail} + * - @functionname{linear_containers_function_queue_dequeuetail} * - @functionname{linear_containers_function_queue_remove} * - @functionname{linear_containers_function_queue_removeall} * - @functionname{linear_containers_function_queue_removeallmatches} @@ -181,15 +184,18 @@ typedef IotLink_t IotQueue_t; * @functionpage{IotListDouble_FindFirstMatch,linear_containers,list_double_findfirstmatch} * @functionpage{IotListDouble_RemoveFirstMatch,linear_containers,list_double_removefirstmatch} * @functionpage{IotListDouble_RemoveAllMatches,linear_containers,list_double_removeallmatches} - * @functionpage{IotQueue_Create,linear_containers,queue_create} - * @functionpage{IotQueue_Count,linear_containers,queue_count} - * @functionpage{IotQueue_IsEmpty,linear_containers,queue_isempty} - * @functionpage{IotQueue_Peek,linear_containers,queue_peek} - * @functionpage{IotQueue_Enqueue,linear_containers,queue_enqueue} - * @functionpage{IotQueue_Dequeue,linear_containers,queue_dequeue} - * @functionpage{IotQueue_Remove,linear_containers,queue_remove} - * @functionpage{IotQueue_RemoveAll,linear_containers,queue_removeall} - * @functionpage{IotQueue_RemoveAllMatches,linear_containers,queue_removeallmatches} + * @functionpage{IotDeQueue_Create,linear_containers,queue_create} + * @functionpage{IotDeQueue_Count,linear_containers,queue_count} + * @functionpage{IotDeQueue_IsEmpty,linear_containers,queue_isempty} + * @functionpage{IotDeQueue_PeekHead,linear_containers,queue_peekhead} + * @functionpage{IotDeQueue_PeekTail,linear_containers,queue_peektail} + * @functionpage{IotDeQueue_EnqueueHead,linear_containers,queue_enqueuehead} + * @functionpage{IotDeQueue_DequeueHead,linear_containers,queue_dequeuehead} + * @functionpage{IotDeQueue_EnqueueTail,linear_containers,queue_enqueuetail} + * @functionpage{IotDeQueue_DequeueTail,linear_containers,queue_dequeuetail} + * @functionpage{IotDeQueue_Remove,linear_containers,queue_remove} + * @functionpage{IotDeQueue_RemoveAll,linear_containers,queue_removeall} + * @functionpage{IotDeQueue_RemoveAllMatches,linear_containers,queue_removeallmatches} */ /** @@ -748,30 +754,30 @@ static inline void IotListDouble_RemoveAllMatches( IotListDouble_t * const pList /** * @brief Create a new queue. * - * This function initializes a new queue. It must be called on an uninitialized - * #IotQueue_t before calling any other queue function. This function must not be - * called on an already-initialized #IotQueue_t. + * This function initializes a new double-ended queue. It must be called on an uninitialized + * #IotDeQueue_t before calling any other queue function. This function must not be + * called on an already-initialized #IotDeQueue_t. * * This function will not fail. * * @param[in] pQueue Pointer to the memory that will hold the new queue. */ /* @[declare_linear_containers_queue_create] */ -static inline void IotQueue_Create( IotQueue_t * const pQueue ) +static inline void IotDeQueue_Create( IotDeQueue_t * const pQueue ) /* @[declare_linear_containers_queue_create] */ { IotListDouble_Create( pQueue ); } /** - * @brief Return the number of elements contained in an #IotQueue_t. + * @brief Return the number of elements contained in an #IotDeQueue_t. * * @param[in] pQueue The queue with the elements to count. * * @return The number of items elements in the queue. */ /* @[declare_linear_containers_queue_count] */ -static inline size_t IotQueue_Count( const IotQueue_t * const pQueue ) +static inline size_t IotDeQueue_Count( const IotDeQueue_t * const pQueue ) /* @[declare_linear_containers_queue_count] */ { return IotListDouble_Count( pQueue ); @@ -786,7 +792,7 @@ static inline size_t IotQueue_Count( const IotQueue_t * const pQueue ) * */ /* @[declare_linear_containers_queue_isempty] */ -static inline bool IotQueue_IsEmpty( const IotQueue_t * const pQueue ) +static inline bool IotDeQueue_IsEmpty( const IotDeQueue_t * const pQueue ) /* @[declare_linear_containers_queue_isempty] */ { return IotListDouble_IsEmpty( pQueue ); @@ -802,29 +808,46 @@ static inline bool IotQueue_IsEmpty( const IotQueue_t * const pQueue ) * queue; `NULL` if the queue is empty. The macro #IotLink_Container may be used * to determine the address of the link's container. */ -/* @[declare_linear_containers_queue_peek] */ -static inline IotLink_t * IotQueue_Peek( const IotQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_peek] */ +/* @[declare_linear_containers_queue_peekhead] */ +static inline IotLink_t * IotDeQueue_PeekHead( const IotDeQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_peekhead] */ { return IotListDouble_PeekHead( pQueue ); } /** - * @brief Add an element to the queue. + * @brief Return an #IotLink_t representing the element at the back of the queue + * without removing it. + * + * @param[in] pQueue The queue to peek. + * + * @return Pointer to an #IotLink_t representing the element at the head of the + * queue; `NULL` if the queue is empty. The macro #IotLink_Container may be used + * to determine the address of the link's container. + */ +/* @[declare_linear_containers_queue_peektail] */ +static inline IotLink_t * IotDeQueue_PeekTail( const IotDeQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_peektail] */ +{ + return IotListDouble_PeekTail( pQueue ); +} + +/** + * @brief Add an element at the head of the queue. * * @param[in] pQueue The queue that will hold the new element. * @param[in] pLink Pointer to the new element's link member. */ -/* @[declare_linear_containers_queue_enqueue] */ -static inline void IotQueue_Enqueue( IotQueue_t * const pQueue, +/* @[declare_linear_containers_queue_enqueuehead] */ +static inline void IotDeQueue_EnqueueHead( IotDeQueue_t * const pQueue, IotLink_t * const pLink ) -/* @[declare_linear_containers_queue_enqueue] */ +/* @[declare_linear_containers_queue_enqueuehead] */ { - IotListDouble_InsertTail( pQueue, pLink ); + IotListDouble_InsertHead( pQueue, pLink ); } /** - * @brief Remove the oldest element in the queue. + * @brief Remove an element at the head of the queue. * * @param[in] pQueue The queue that holds the element to remove. * @@ -832,20 +855,50 @@ static inline void IotQueue_Enqueue( IotQueue_t * const pQueue, * if the queue is empty. The macro #IotLink_Container may be used to determine * the address of the link's container. */ -/* @[declare_linear_containers_queue_dequeue] */ -static inline IotLink_t * IotQueue_Dequeue( IotQueue_t * const pQueue ) -/* @[declare_linear_containers_queue_dequeue] */ +/* @[declare_linear_containers_queue_dequeuehead] */ +static inline IotLink_t * IotDeQueue_DequeueHead( IotDeQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_dequeuehead] */ { return IotListDouble_RemoveHead( pQueue ); } +/** + * @brief Add an element at the tail of the queue. + * + * @param[in] pQueue The queue that will hold the new element. + * @param[in] pLink Pointer to the new element's link member. + */ +/* @[declare_linear_containers_queue_enqueuetail] */ +static inline void IotDeQueue_EnqueueTail( IotDeQueue_t * const pQueue, + IotLink_t * const pLink ) +/* @[declare_linear_containers_queue_enqueuetail] */ +{ + IotListDouble_InsertTail( pQueue, pLink ); +} + +/** + * @brief Remove an element at the tail of the queue. + * + * @param[in] pQueue The queue that holds the element to remove. + * + * @return Pointer to an #IotLink_t representing the removed queue element; `NULL` + * if the queue is empty. The macro #IotLink_Container may be used to determine + * the address of the link's container. + */ +/* @[declare_linear_containers_queue_dequeuetail] */ +static inline IotLink_t * IotDeQueue_DequeueTail( IotDeQueue_t * const pQueue ) +/* @[declare_linear_containers_queue_dequeuetail] */ +{ + return IotListDouble_RemoveTail( pQueue ); +} + /** * @brief Remove a single element from a queue. * * @param[in] pLink The element to remove. */ /* @[declare_linear_containers_queue_remove] */ -static inline void IotQueue_Remove( IotLink_t * const pLink ) +static inline void IotDeQueue_Remove( IotLink_t * const pLink ) /* @[declare_linear_containers_queue_remove] */ { IotListDouble_Remove( pLink ); @@ -863,7 +916,7 @@ static inline void IotQueue_Remove( IotLink_t * const pLink ) * or its value is `0`. */ /* @[declare_linear_containers_queue_removeall] */ -static inline void IotQueue_RemoveAll( IotQueue_t * const pQueue, +static inline void IotDeQueue_RemoveAll( IotDeQueue_t * const pQueue, void ( * freeElement )( void * ), size_t linkOffset ) /* @[declare_linear_containers_queue_removeall] */ @@ -888,7 +941,7 @@ static inline void IotQueue_RemoveAll( IotQueue_t * const pQueue, * or its value is `0`. */ /* @[declare_linear_containers_queue_removeallmatches] */ -static inline void IotQueue_RemoveAllMatches( IotQueue_t * const pQueue, +static inline void IotDeQueue_RemoveAllMatches( IotDeQueue_t * const pQueue, bool ( * isMatch )( const IotLink_t *, void * ), void * pMatch, void ( * freeElement )( void * ), diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index e578c92011..91f70f12c3 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -272,7 +272,7 @@ typedef struct IotTaskPoolCache */ typedef struct IotTaskPool { - IotQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ + IotDeQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ IotTaskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 14f417ac63..1d84c98e49 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -318,7 +318,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) { pItemLink = NULL; - pItemLink = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + pItemLink = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); if( pItemLink != NULL ) { @@ -941,7 +941,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ /* Initialize a job data structures that require no de-initialization. * All other data structures carry a value of 'NULL' before initailization. */ - IotQueue_Create( &pTaskPool->dispatchQueue ); + IotDeQueue_Create( &pTaskPool->dispatchQueue ); IotListDouble_Create( &pTaskPool->timerEventsList ); pTaskPool->minThreads = pInfo->minThreads; @@ -1100,7 +1100,7 @@ static void _taskPoolWorker( void * pUserContext ) if( jobAvailable == true ) { /* Dequeue the first job in FIFO order. */ - pFirst = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + pFirst = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */ if( pFirst != NULL ) @@ -1150,7 +1150,7 @@ static void _taskPoolWorker( void * pUserContext ) IotLink_t * pItem = NULL; /* Dequeue the next job from the dispatch queue. */ - pItem = IotQueue_Dequeue( &pTaskPool->dispatchQueue ); + pItem = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); /* If there is no job left in the dispatch queue, update the worker status and leave. */ if( pItem == NULL ) @@ -1178,7 +1178,7 @@ static void _taskPoolWorker( void * pUserContext ) static void _initJobsCache( IotTaskPoolCache_t * const pCache ) { - IotQueue_Create( &pCache->freeList ); + IotDeQueue_Create( &pCache->freeList ); pCache->freeCount = 0; } @@ -1388,8 +1388,17 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, if( TASKPOOL_SUCCEEDED( status ) ) { - /* Append the job to the dispatch queue. */ - IotQueue_Enqueue( &pTaskPool->dispatchQueue, &pJob->link ); + /* Append the job to the dispatch queue. + * Put the job at the front, if it is a high priority job. */ + if(mustGrow == true ) + { + IotDeQueue_EnqueueHead( &pTaskPool->dispatchQueue, &pJob->link ); + } + else + { + IotDeQueue_EnqueueTail( &pTaskPool->dispatchQueue, &pJob->link ); + } + /* Signal a worker to pick up the job. */ IotSemaphore_Post( &pTaskPool->dispatchSignal ); @@ -1480,7 +1489,7 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, /* A scheduled work items must be in the dispatch queue. */ IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) ); - IotQueue_Remove( &pJob->link ); + IotDeQueue_Remove( &pJob->link ); } /* If the job current status is 'deferred' then the job has to be pending diff --git a/tests/common/unit/iot_tests_linear_containers.c b/tests/common/unit/iot_tests_linear_containers.c index 1d70c4992e..3ab4c58f05 100644 --- a/tests/common/unit/iot_tests_linear_containers.c +++ b/tests/common/unit/iot_tests_linear_containers.c @@ -75,7 +75,7 @@ TEST_GROUP_RUNNER( Common_Unit_Linear_Containers ) TEST( Common_Unit_Linear_Containers, ListQueueEmpty ) { IotListDouble_t list = IOT_LIST_DOUBLE_INITIALIZER; - IotQueue_t queue = IOT_QUEUE_INITIALIZER; + IotDeQueue_t queue = IOT_DEQUEUE_INITIALIZER; /* Create an empty list. */ IotListDouble_Create( &list ); @@ -91,13 +91,13 @@ TEST( Common_Unit_Linear_Containers, ListQueueEmpty ) TEST_ASSERT_EQUAL_PTR( NULL, IotListDouble_RemoveFirstMatch( &list, NULL, NULL, 0 ) ); /* Create an empty queue. */ - IotQueue_Create( &queue ); + IotDeQueue_Create( &queue ); /* Check appropriate return values for an empty queue. */ - TEST_ASSERT_EQUAL( 0, IotQueue_Count( &queue ) ); - TEST_ASSERT_EQUAL_INT( true, IotQueue_IsEmpty( &queue ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotQueue_Peek( &queue ) ); - TEST_ASSERT_EQUAL_PTR( NULL, IotQueue_Dequeue( &queue ) ); + TEST_ASSERT_EQUAL( 0, IotDeQueue_Count( &queue ) ); + TEST_ASSERT_EQUAL_INT( true, IotDeQueue_IsEmpty( &queue ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotDeQueue_PeekHead( &queue ) ); + TEST_ASSERT_EQUAL_PTR( NULL, IotDeQueue_DequeueHead( &queue ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 4d0e985ec1..dd6757a119 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -286,7 +286,7 @@ static void _operationResetAndPush( _mqttOperation_t * pOperation ) { pOperation->status = IOT_MQTT_STATUS_PENDING; pOperation->jobReference = 1; - IotQueue_Enqueue( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); + IotDeQueue_EnqueueHead( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); } /*-----------------------------------------------------------*/ @@ -1258,7 +1258,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Remove unprocessed PUBLISH if present. */ if( IotLink_IsLinked( &( publish.link ) ) == true ) { - IotQueue_Remove( &( publish.link ) ); + IotDeQueue_Remove( &( publish.link ) ); } IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); @@ -1621,7 +1621,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Remove unprocessed UNSUBSCRIBE if present. */ if( IotLink_IsLinked( &( unsubscribe.link ) ) == true ) { - IotQueue_Remove( &( unsubscribe.link ) ); + IotDeQueue_Remove( &( unsubscribe.link ) ); } IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); From ceac0a674693055fdc629fd5c06c01f77fb3521d Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 12 Apr 2019 14:33:59 -0700 Subject: [PATCH 094/844] Remove config file define (#373) --- CMakeLists.txt | 13 +++++-------- .../DeserializeConnack/DeserializeConnack_harness.c | 2 +- .../DeserializePingresp_harness.c | 2 +- .../DeserializePuback/DeserializePuback_harness.c | 2 +- .../DeserializePublish/DeserializePublish_harness.c | 2 +- .../DeserializeSuback/DeserializeSuback_harness.c | 2 +- .../DeserializeUnsuback_harness.c | 2 +- cbmc/proofs/Makefile.common | 2 +- demos/{ => app}/posix/CMakeLists.txt | 4 ++-- demos/{ => app}/posix/iot_demo_arguments_posix.c | 6 ++---- demos/{ => app}/posix/iot_demo_posix.c | 6 ++---- demos/include/iot_demo_arguments.h | 3 +-- demos/include/iot_demo_logging.h | 6 ++---- demos/{include/iot_demo_config.h => iot_config.h} | 6 +++--- demos/{ => source}/aws_iot_demo_shadow.c | 6 ++---- demos/{ => source}/iot_demo_mqtt.c | 6 ++---- doc/config/common | 4 ++-- doc/guide/style.txt | 8 ++------ doc/lib/mqtt.txt | 2 +- doc/mainpage.txt | 10 ++-------- lib/include/aws_iot_defender.h | 6 ++---- lib/include/aws_iot_shadow.h | 6 ++---- lib/include/iot_common.h | 6 ++---- lib/include/iot_linear_containers.h | 6 ++---- lib/include/iot_logging_setup.h | 6 ++---- lib/include/iot_mqtt.h | 6 ++---- lib/include/iot_serializer.h | 8 +++----- lib/include/iot_taskpool.h | 6 ++---- lib/include/platform/iot_clock.h | 6 ++---- lib/include/platform/iot_metrics.h | 5 ++--- lib/include/platform/iot_threads.h | 6 ++---- lib/include/private/aws_iot_defender_internal.h | 6 ++---- lib/include/private/aws_iot_shadow_internal.h | 6 ++---- lib/include/private/iot_error.h | 6 ++---- lib/include/private/iot_logging.h | 6 ++---- lib/include/private/iot_mqtt_internal.h | 6 ++---- lib/include/private/iot_static_memory.h | 6 ++---- lib/include/private/iot_taskpool_internal.h | 3 +++ lib/include/types/aws_iot_shadow_types.h | 6 ++---- lib/include/types/iot_mqtt_types.h | 6 ++---- lib/include/types/iot_platform_types.h | 6 ++---- lib/include/types/iot_taskpool_types.h | 6 ++---- lib/source/common/iot_common.c | 6 ++---- lib/source/common/iot_logging.c | 6 ++---- lib/source/common/iot_taskpool.c | 3 +++ .../static_memory/aws_iot_static_memory_defender.c | 6 ++---- .../static_memory/aws_iot_static_memory_shadow.c | 6 ++---- .../common/static_memory/iot_static_memory_common.c | 6 ++---- .../static_memory/iot_static_memory_metrics.c | 6 ++---- .../common/static_memory/iot_static_memory_mqtt.c | 6 ++---- .../static_memory/iot_static_memory_serializer.c | 6 ++---- .../static_memory/iot_static_memory_taskpool.c | 6 ++---- lib/source/defender/aws_iot_defender_api.c | 9 +++++++++ lib/source/defender/aws_iot_defender_collector.c | 2 ++ lib/source/mqtt/iot_mqtt_api.c | 6 ++---- lib/source/mqtt/iot_mqtt_network.c | 6 ++---- lib/source/mqtt/iot_mqtt_operation.c | 6 ++---- lib/source/mqtt/iot_mqtt_serialize.c | 6 ++---- lib/source/mqtt/iot_mqtt_subscription.c | 6 ++---- lib/source/mqtt/iot_mqtt_validate.c | 6 ++---- lib/source/serializer/iot_json_utils.c | 6 ++---- lib/source/shadow/aws_iot_shadow_api.c | 6 ++---- lib/source/shadow/aws_iot_shadow_operation.c | 6 ++---- lib/source/shadow/aws_iot_shadow_parser.c | 6 ++---- lib/source/shadow/aws_iot_shadow_subscription.c | 6 ++---- platform/include/posix/iot_network_openssl.h | 6 ++---- platform/source/posix/iot_clock_posix.c | 6 ++---- platform/source/posix/iot_threads_posix.c | 6 ++---- platform/source/posix/linux/iot_metrics.c | 6 ++---- .../posix/linux/iot_network_openssl_metrics.c | 1 + platform/source/posix/network/iot_network_openssl.c | 6 ++---- tests/common/unit/iot_tests_atomic.c | 6 ++---- tests/common/unit/iot_tests_linear_containers.c | 6 ++---- tests/common/unit/iot_tests_taskpool.c | 6 ++---- tests/defender/aws_iot_tests_defender_api.c | 3 +++ tests/{iot_tests_config.h => iot_config.h} | 0 tests/mqtt/system/iot_tests_mqtt_stress.c | 6 ++---- tests/mqtt/system/iot_tests_mqtt_system.c | 6 ++---- tests/mqtt/unit/iot_tests_mqtt_api.c | 6 ++---- tests/mqtt/unit/iot_tests_mqtt_receive.c | 6 ++---- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 6 ++---- tests/mqtt/unit/iot_tests_mqtt_validate.c | 6 ++---- tests/shadow/system/aws_iot_tests_shadow_system.c | 6 ++---- tests/shadow/unit/aws_iot_tests_shadow_api.c | 6 ++---- tests/shadow/unit/aws_iot_tests_shadow_parser.c | 6 ++---- 85 files changed, 173 insertions(+), 291 deletions(-) rename demos/{ => app}/posix/CMakeLists.txt (85%) rename demos/{ => app}/posix/iot_demo_arguments_posix.c (98%) rename demos/{ => app}/posix/iot_demo_posix.c (98%) rename demos/{include/iot_demo_config.h => iot_config.h} (97%) rename demos/{ => source}/aws_iot_demo_shadow.c (99%) rename demos/{ => source}/iot_demo_mqtt.c (99%) rename tests/{iot_tests_config.h => iot_config.h} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c33a5204be..a6cb81c081 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,10 +32,10 @@ include_directories( ${PROJECT_SOURCE_DIR}/lib/include # TinyCBOR include path. include_directories( ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) -# Demo include path. +# Demo include path. Always required because the tests also build the demos. include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) -# Use either the demo or test configuration file. +# Set additional include paths for demos and tests. if( ${IOT_BUILD_TESTS} ) # Define the constant to enable test access. add_definitions( -DIOT_BUILD_TESTS=1 ) @@ -48,13 +48,10 @@ if( ${IOT_BUILD_TESTS} ) include_directories( third_party/unity/unity third_party/unity/unity/fixture ) - # Tests config file. - add_definitions( -DIOT_CONFIG_FILE="iot_tests_config.h" ) - # Build unity test framework. add_subdirectory( third_party/unity ) else() - add_definitions( -DIOT_CONFIG_FILE="iot_demo_config.h" ) + include_directories( ${PROJECT_SOURCE_DIR}/demos ) endif() # Platform libraries. @@ -81,9 +78,9 @@ add_subdirectory( lib/source/defender ) # TinyCBOR library (third-party). add_subdirectory( third_party/tinycbor ) -# Demo executables. +# Determine the demo executable to build based on system. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - add_subdirectory( demos/posix ) + add_subdirectory( demos/app/posix ) endif() # Test executables. diff --git a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c index c20690710e..5fe781252c 100644 --- a/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c +++ b/cbmc/proofs/DeserializeConnack/DeserializeConnack_harness.c @@ -1,4 +1,4 @@ -#include IOT_CONFIG_FILE +#include "iot_config.h" #include "private/iot_mqtt_internal.h" #include diff --git a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c index 6a47813e19..2a60dbd3f4 100644 --- a/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c +++ b/cbmc/proofs/DeserializePingresp/DeserializePingresp_harness.c @@ -1,4 +1,4 @@ -#include IOT_CONFIG_FILE +#include "iot_config.h" #include "private/iot_mqtt_internal.h" #include diff --git a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c index f3c94e3d85..53181fd209 100644 --- a/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c +++ b/cbmc/proofs/DeserializePuback/DeserializePuback_harness.c @@ -1,4 +1,4 @@ -#include IOT_CONFIG_FILE +#include "iot_config.h" #include "private/iot_mqtt_internal.h" #include diff --git a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c index f64a0f0929..92ced46caf 100644 --- a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c +++ b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c @@ -1,4 +1,4 @@ -#include IOT_CONFIG_FILE +#include "iot_config.h" #include "private/iot_mqtt_internal.h" #include diff --git a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c index e9e6390401..0152d5e3ec 100644 --- a/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c +++ b/cbmc/proofs/DeserializeSuback/DeserializeSuback_harness.c @@ -1,4 +1,4 @@ -#include IOT_CONFIG_FILE +#include "iot_config.h" #include "private/iot_mqtt_internal.h" #include diff --git a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c index e6dbb4feaa..de6a5a7996 100644 --- a/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c +++ b/cbmc/proofs/DeserializeUnsuback/DeserializeUnsuback_harness.c @@ -1,4 +1,4 @@ -#include IOT_CONFIG_FILE +#include "iot_config.h" #include "private/iot_mqtt_internal.h" #include diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 9a23c2f05b..5d322a4914 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -21,10 +21,10 @@ VIEWER ?= cbmc-viewer INC = \ -I$(MQTT)/lib/include \ -I$(MQTT)/platform/include \ + -I$(MQTT)/demos \ -I$(MQTT)/demos/include \ DEF += \ - -DIOT_CONFIG_FILE=\"iot_demo_config.h\" \ -DIOT_SDK_VERSION=\"4.0.0\" \ -DIOT_SYSTEM_TYPES_FILE=\"posix/iot_platform_types_posix.h\" \ -Diotmqtt_EXPORTS \ diff --git a/demos/posix/CMakeLists.txt b/demos/app/posix/CMakeLists.txt similarity index 85% rename from demos/posix/CMakeLists.txt rename to demos/app/posix/CMakeLists.txt index 3c9ac77d01..27023ebe74 100644 --- a/demos/posix/CMakeLists.txt +++ b/demos/app/posix/CMakeLists.txt @@ -3,7 +3,7 @@ set( DEMO_COMMON_SOURCE_FILES "iot_demo_arguments_posix.c;iot_demo_posix.c" ) # MQTT demo source files. add_executable( iot_demo_mqtt - ${CMAKE_SOURCE_DIR}/demos/iot_demo_mqtt.c + ${CMAKE_SOURCE_DIR}/demos/source/iot_demo_mqtt.c ${DEMO_COMMON_SOURCE_FILES} ) # Select the demo function for the MQTT demo. @@ -15,7 +15,7 @@ target_link_libraries( iot_demo_mqtt iotplatform iotmqtt ) # Shadow demo source files. add_executable( aws_iot_demo_shadow - ${CMAKE_SOURCE_DIR}/demos/aws_iot_demo_shadow.c + ${CMAKE_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c ${DEMO_COMMON_SOURCE_FILES} ) # Select the demo function for the Shadow demo. diff --git a/demos/posix/iot_demo_arguments_posix.c b/demos/app/posix/iot_demo_arguments_posix.c similarity index 98% rename from demos/posix/iot_demo_arguments_posix.c rename to demos/app/posix/iot_demo_arguments_posix.c index ec2a982a3d..ecb6d8f9b2 100644 --- a/demos/posix/iot_demo_arguments_posix.c +++ b/demos/app/posix/iot_demo_arguments_posix.c @@ -25,10 +25,8 @@ * systems. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/demos/posix/iot_demo_posix.c b/demos/app/posix/iot_demo_posix.c similarity index 98% rename from demos/posix/iot_demo_posix.c rename to demos/app/posix/iot_demo_posix.c index f7dc7bef28..de54df2d48 100644 --- a/demos/posix/iot_demo_posix.c +++ b/demos/app/posix/iot_demo_posix.c @@ -24,10 +24,8 @@ * @brief Generic demo runner for POSIX systems. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index e9521526d0..5f5e8075d1 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -37,8 +37,7 @@ * Each demo will use one of these structs to hold its arguments. * * The default values of this struct may be set using compile-time constants, - * either through a [config file](@ref IOT_CONFIG_FILE) or a compiler option - * like `-D`. + * either through the config file or a compiler option like `-D`. * * The default values may be overridden using command line arguments. If a default * value was not set, then a valid value must be set using a command line argument. diff --git a/demos/include/iot_demo_logging.h b/demos/include/iot_demo_logging.h index eb41adff90..46822995bb 100644 --- a/demos/include/iot_demo_logging.h +++ b/demos/include/iot_demo_logging.h @@ -27,10 +27,8 @@ #ifndef _IOT_DEMO_LOGGING_H_ #define _IOT_DEMO_LOGGING_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Configure logs for the demos. The demos will have a log level of: * - IOT_LOG_LEVEL_DEMO if defined. diff --git a/demos/include/iot_demo_config.h b/demos/iot_config.h similarity index 97% rename from demos/include/iot_demo_config.h rename to demos/iot_config.h index 0de2f47e2b..666913cca6 100644 --- a/demos/include/iot_demo_config.h +++ b/demos/iot_config.h @@ -21,8 +21,8 @@ /* This file contains configuration settings for the demos. */ -#ifndef _IOT_DEMO_CONFIG_H_ -#define _IOT_DEMO_CONFIG_H_ +#ifndef IOT_CONFIG_H_ +#define IOT_CONFIG_H_ /* Server endpoints used for the demos. May be overridden with command line * options at runtime. */ @@ -69,4 +69,4 @@ * layer based on the host operating system. */ #include IOT_SYSTEM_TYPES_FILE -#endif /* ifndef _IOT_DEMO_CONFIG_H_ */ +#endif /* ifndef IOT_CONFIG_H_ */ diff --git a/demos/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c similarity index 99% rename from demos/aws_iot_demo_shadow.c rename to demos/source/aws_iot_demo_shadow.c index e5fab4b409..5e65e19ea3 100644 --- a/demos/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -27,10 +27,8 @@ * "powerOn" in a remote device. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/demos/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c similarity index 99% rename from demos/iot_demo_mqtt.c rename to demos/source/iot_demo_mqtt.c index 84d5c5e0a8..9b71ce0c56 100644 --- a/demos/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -24,10 +24,8 @@ * @brief Demonstrates usage of the MQTT library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/doc/config/common b/doc/config/common index 52cf8e583f..7341ac9f77 100644 --- a/doc/config/common +++ b/doc/config/common @@ -61,7 +61,7 @@ MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES # Include path for expanding Unity test macros. -INCLUDE_PATH = tests/unity/fixture +INCLUDE_PATH = third_party/unity/unity/fixture # Expand the TEST macro, but ignore the _run functions generated from macro expansion. EXPAND_AS_DEFINED = TEST @@ -71,7 +71,7 @@ EXCLUDE_SYMBOLS += "TEST_*_run" ALIASES += dependencies{2}="@section \1_dependencies Dependencies^^@brief Dependencies of the \2.^^^^" # Alias for starting a configuration settings page. -ALIASES += describeconfig="Configuration settings are C pre-processor constants. They can be set with a @c #`define` in an @ref IOT_CONFIG_FILE or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." +ALIASES += describeconfig="Configuration settings are C pre-processor constants. They can be set with a @c #`define` in the config file (`iot_config.h`) or by using a compiler option such as `-D` in gcc. If a configuration setting is not defined, the library will use a \"sensible\" default value (unless otherwise noted). Because they are compile-time constants, a library must be rebuilt if a configuration setting is changed." ALIASES += configpage{2}="@page \1_config Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^@par configpagemarker" ALIASES += configpage{4}="@page \1_config \3 Configuration^^@brief Configuration settings of the \2.^^^^@describeconfig^^^^The settings on this page only affect the [\2](@ref \1). In addition to the settings on this page, them \2 will also be affected by [settings that affect all \4](@ref global_\4_config).^^@par configpagemarker" ALIASES += globalconfigpage{3}="@page global_\1_config Global \2 Configuration^^^^@describeconfig^^@brief Configuration settings that affect all \3.^^@par configpagemarker" diff --git a/doc/guide/style.txt b/doc/guide/style.txt index 0a05cae3a4..1ccbaa8576 100644 --- a/doc/guide/style.txt +++ b/doc/guide/style.txt @@ -27,7 +27,7 @@ The coding style aims to produce code that is readable and easy to debug. An exa @code{c} /*-----------------------------------------------------------*/ @endcode -- All files must include @ref IOT_CONFIG_FILE at the top of the file before any other includes. +- All files must include `iot_config.h` at the top of the file before any other includes. - `static` functions must have a declaration at the top of the file and be implemented before any application-facing functions. @subsection guide_developer_styleguide_codingstyle_typeguidelines Type guidelines @@ -44,11 +44,7 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab @code{c} /* Included headers are at the top of the file. The config file include is always first. */ - -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE -#include IOT_CONFIG_FILE -#endif +#include "iot_config.h" /* Standard includes are immediately after the config file. They are sorted alphabetically. * They use angle brackets <> around the file name. */ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index ebd6b9a530..7040afe181 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -240,7 +240,7 @@ Metrics allow AWS IoT to prioritize engineering resources based on SDK usage. SD @section IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES @brief Set this to `1` to allow the MQTT packet serializer and deserializer functions to be overridden. -Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #IotMqttSerializer_t for a list of functions that can be overridden. If this setting is `1`, the serializer [initialization](@ref _IotMqtt_InitSerialize) and [cleanup](@ref _IotMqtt_CleanupSerialize) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default serializer initialization and cleanup functions. They must have the following signatures: +Serializer overrides allow the functions that generate and decode MQTT packets to be overridden. When this setting is `1`, each connection may have different serializer and deserializer functions. This allows the MQTT library to be used as a general-purpose transport library (with limitations). Currently, this setting is used to support MQTT over Bluetooth Low-Energy on Amazon FreeRTOS. See #IotMqttSerializer_t for a list of functions that can be overridden. If this setting is `1`, the [initialization](@ref mqtt_function_init) and [cleanup](@ref mqtt_function_cleanup) functions may be extended by defining `_IotMqtt_InitSerializeAdditional` and `_IotMqtt_CleanupSerializeAdditional`. These functions will be called along with the default initialization and cleanup functions. They must have the following signatures: @code{c} #include diff --git a/doc/mainpage.txt b/doc/mainpage.txt index 34a0cd1eb2..d6c68e5eda 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -14,6 +14,8 @@ Design goals of this SDK include: /** @page global_config Global Configuration +Configuration settings should be set using compiler arguments (like `-D` in gcc) or through a config file. The config file for this SDK must be called `iot_config.h` and be present in the include path. + The following pages describe configuration settings that affect multiple components of this SDK. - @subpage global_library_config
Settings that affect all libraries. @@ -26,14 +28,6 @@ The following pages describe configuration settings that affect multiple compone /** @globalconfigpage{library,Library,libraries} -@section IOT_CONFIG_FILE -@brief Configuration header for the libraries in this SDK. - -Define a file to be included in all library source files before any other files. Unlike other settings, this one must be set using a compiler option, such as `-D` for gcc. The option should specify a filename that is in the include path; for example, `-DIOT_CONFIG_FILE="iot_config.h"` (quotes may need to be escaped for some compilers). - -@configpossible A string representing a file name. This file must be in the compiler's include path.
-@configdefault None. A configuration header will not be used if this value is undefined. - @section IOT_LOG_LEVEL_GLOBAL @brief Set a default log level. diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 50af5e53ce..aa7558b544 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -47,10 +47,8 @@ #ifndef _AWS_IOT_DEFENDER_H_ #define _AWS_IOT_DEFENDER_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 9988d334cf..53bbb4ed9f 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -27,10 +27,8 @@ #ifndef _AWS_IOT_SHADOW_H_ #define _AWS_IOT_SHADOW_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Shadow types include. */ #include "types/aws_iot_shadow_types.h" diff --git a/lib/include/iot_common.h b/lib/include/iot_common.h index 251c28edc2..b3040a0611 100644 --- a/lib/include/iot_common.h +++ b/lib/include/iot_common.h @@ -28,10 +28,8 @@ #ifndef _IOT_COMMON_H_ #define _IOT_COMMON_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 621fe761d0..d0449a2478 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -27,10 +27,8 @@ #ifndef _IOT_LINEAR_CONTAINERS_H_ #define _IOT_LINEAR_CONTAINERS_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/iot_logging_setup.h b/lib/include/iot_logging_setup.h index 08b3ece91b..a8a82c0e9a 100644 --- a/lib/include/iot_logging_setup.h +++ b/lib/include/iot_logging_setup.h @@ -27,10 +27,8 @@ #ifndef _IOT_LOGGING_SETUP_H_ #define _IOT_LOGGING_SETUP_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Logging include. Because it's included here, iot_logging.h never needs * to be included in source. */ diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 08d4ca711d..fe12bc7ffe 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -27,10 +27,8 @@ #ifndef _IOT_MQTT_H_ #define _IOT_MQTT_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* MQTT types include. */ #include "types/iot_mqtt_types.h" diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 6183dd2509..7de7fa428b 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -31,17 +31,15 @@ #ifndef _IOT_SERIALIZER_H_ #define _IOT_SERIALIZER_H_ +/* The config header is always included first. */ +#include "iot_config.h" + /* Standard includes. */ #include #include #include #include -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif - #if IOT_SERIALIZER_ENABLE_ASSERTS == 1 #ifndef IotSerializer_Assert #include diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 5e2ab8eba8..9a16306bdd 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -27,10 +27,8 @@ #ifndef IOT_TASKPOOL_H_ #define IOT_TASKPOOL_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/platform/iot_clock.h b/lib/include/platform/iot_clock.h index d858575c89..6af9c37e3b 100644 --- a/lib/include/platform/iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -27,10 +27,8 @@ #ifndef _IOT_CLOCK_H_ #define _IOT_CLOCK_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/platform/iot_metrics.h b/lib/include/platform/iot_metrics.h index 16dc76dd93..1a1d8f2f43 100644 --- a/lib/include/platform/iot_metrics.h +++ b/lib/include/platform/iot_metrics.h @@ -30,9 +30,8 @@ #include -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" #include "iot_linear_containers.h" diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index fa17755605..49f0bf77b5 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -27,10 +27,8 @@ #ifndef _IOT_THREADS_H_ #define _IOT_THREADS_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 39cb80ef71..4ca8d64e76 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -27,10 +27,8 @@ #ifndef _AWS_IOT_DEFENDER_INTERNAL_H_ #define _AWS_IOT_DEFENDER_INTERNAL_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Defender include. */ #include "aws_iot_defender.h" diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 2e2a1d6dea..ee4c89d37c 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -28,10 +28,8 @@ #ifndef _AWS_IOT_SHADOW_INTERNAL_H_ #define _AWS_IOT_SHADOW_INTERNAL_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Linear containers (lists and queues) include. */ #include "iot_linear_containers.h" diff --git a/lib/include/private/iot_error.h b/lib/include/private/iot_error.h index b9129c73ab..c40cc1d064 100644 --- a/lib/include/private/iot_error.h +++ b/lib/include/private/iot_error.h @@ -30,10 +30,8 @@ #ifndef _IOT_ERROR_H_ #define _IOT_ERROR_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /** * @brief Declare the status variable and an initial value. diff --git a/lib/include/private/iot_logging.h b/lib/include/private/iot_logging.h index 64dc06aab1..5e0c2096a1 100644 --- a/lib/include/private/iot_logging.h +++ b/lib/include/private/iot_logging.h @@ -33,10 +33,8 @@ #ifndef _IOT_LOGGING_H_ #define _IOT_LOGGING_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 6a12e28c1c..c217cbdd14 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -28,10 +28,8 @@ #ifndef _IOT_MQTT_INTERNAL_H_ #define _IOT_MQTT_INTERNAL_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Linear containers (lists and queues) include. */ #include "iot_linear_containers.h" diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 59067e7622..5eb0365f2b 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -25,10 +25,8 @@ * @ref IOT_STATIC_MEMORY_ONLY is `1`. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* The functions in this file should only exist in static memory only mode, hence * the check for IOT_STATIC_MEMORY_ONLY in the double inclusion guard. */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index e43131991c..e15c7e2501 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -28,6 +28,9 @@ #ifndef IOT_TASKPOOL_INTERNAL_H_ #define IOT_TASKPOOL_INTERNAL_H_ +/* The config header is always included first. */ +#include "iot_config.h" + /* Task pool include. */ #include "private/iot_error.h" #include "iot_taskpool.h" diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 2d4ac8523b..613620b6d5 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -27,10 +27,8 @@ #ifndef _AWS_IOT_SHADOW_TYPES_H_ #define _AWS_IOT_SHADOW_TYPES_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* MQTT types include. */ #include "types/iot_mqtt_types.h" diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 243131834c..7f5a4f6251 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -27,10 +27,8 @@ #ifndef _IOT_MQTT_TYPES_H_ #define _IOT_MQTT_TYPES_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/include/types/iot_platform_types.h b/lib/include/types/iot_platform_types.h index fbc59c065b..6a6330933c 100644 --- a/lib/include/types/iot_platform_types.h +++ b/lib/include/types/iot_platform_types.h @@ -27,10 +27,8 @@ #ifndef _IOT_PLATFORM_TYPES_H_ #define _IOT_PLATFORM_TYPES_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /*------------------------- Thread management types -------------------------*/ diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 91f70f12c3..0ea5f970e6 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -27,10 +27,8 @@ #ifndef IOT_TASKPOOL_TYPES_H_ #define IOT_TASKPOOL_TYPES_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_common.c index 05f30d90b8..00b67971ce 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_common.c @@ -19,10 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Common include. */ #include "iot_common.h" diff --git a/lib/source/common/iot_logging.c b/lib/source/common/iot_logging.c index 21fc60b6f2..752ec5aa58 100644 --- a/lib/source/common/iot_logging.c +++ b/lib/source/common/iot_logging.c @@ -24,10 +24,8 @@ * @brief Implementation of logging functions from iot_logging.h */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 1d84c98e49..d18e00f93d 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -24,6 +24,9 @@ * @brief Implements the task pool functions in iot_taskpool.h */ +/* The config header is always included first. */ +#include "iot_config.h" + /* Standard includes. */ #include #include diff --git a/lib/source/common/static_memory/aws_iot_static_memory_defender.c b/lib/source/common/static_memory/aws_iot_static_memory_defender.c index 45d68347f8..15ca5c574a 100644 --- a/lib/source/common/static_memory/aws_iot_static_memory_defender.c +++ b/lib/source/common/static_memory/aws_iot_static_memory_defender.c @@ -19,10 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c index 23731b8361..cc73cfeb9d 100644 --- a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c +++ b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c @@ -24,10 +24,8 @@ * @brief Implementation of Shadow static memory functions in iot_static_memory.h */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/common/static_memory/iot_static_memory_common.c b/lib/source/common/static_memory/iot_static_memory_common.c index 80c5abc7a3..75273d7692 100644 --- a/lib/source/common/static_memory/iot_static_memory_common.c +++ b/lib/source/common/static_memory/iot_static_memory_common.c @@ -24,10 +24,8 @@ * @brief Implementation of common static memory functions in iot_static_memory.h */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/common/static_memory/iot_static_memory_metrics.c b/lib/source/common/static_memory/iot_static_memory_metrics.c index 2c3d00297b..de2553ea26 100644 --- a/lib/source/common/static_memory/iot_static_memory_metrics.c +++ b/lib/source/common/static_memory/iot_static_memory_metrics.c @@ -19,10 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/common/static_memory/iot_static_memory_mqtt.c b/lib/source/common/static_memory/iot_static_memory_mqtt.c index 949249251f..de33af7a9f 100644 --- a/lib/source/common/static_memory/iot_static_memory_mqtt.c +++ b/lib/source/common/static_memory/iot_static_memory_mqtt.c @@ -24,10 +24,8 @@ * @brief Implementation of MQTT static memory functions in iot_static_memory.h */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/common/static_memory/iot_static_memory_serializer.c b/lib/source/common/static_memory/iot_static_memory_serializer.c index b3172cc81a..2818d32fa1 100644 --- a/lib/source/common/static_memory/iot_static_memory_serializer.c +++ b/lib/source/common/static_memory/iot_static_memory_serializer.c @@ -19,10 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/common/static_memory/iot_static_memory_taskpool.c b/lib/source/common/static_memory/iot_static_memory_taskpool.c index 14269a484c..401593d527 100644 --- a/lib/source/common/static_memory/iot_static_memory_taskpool.c +++ b/lib/source/common/static_memory/iot_static_memory_taskpool.c @@ -24,10 +24,8 @@ * @brief Implementation of task pool static memory functions in iot_static_memory.h */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* This file should only be compiled if dynamic memory allocation is forbidden. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 70247740c8..060379d3a7 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -140,6 +140,9 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; + /* Silence warnigns when asserts are disabled. */ + ( void ) taskPoolError; + /* Initialize flow control states to false. */ bool buildTopicsNamesSuccess = false, doneSemaphoreCreateSuccess = false, @@ -388,6 +391,10 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, if( reportPublished ) { IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJob ); + + /* Silence warnigns when asserts are disabled. */ + ( void ) taskPoolError; + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, @@ -467,6 +474,8 @@ static void _disconnectRoutine( IotTaskPool_t * pTaskPool, AwsIotDefenderInternal_MqttDisconnect(); /* Re-create metrics job. */ IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); + /* Silence warnigns when asserts are disabled. */ + ( void ) taskPoolError; AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Re-schedule metrics job with period as deferred interval. */ diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index 5f49b3ae76..78e3427d18 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -103,6 +103,7 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj void assertSuccess( IotSerializerError_t error ) { + ( void ) error; AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS ); } @@ -110,6 +111,7 @@ void assertSuccess( IotSerializerError_t error ) void assertSuccessOrBufferToSmall( IotSerializerError_t error ) { + ( void ) error; AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS || error == IOT_SERIALIZER_BUFFER_TOO_SMALL ); } diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index f4c29ef5b9..5216cf38c3 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -24,10 +24,8 @@ * @brief Implements most user-facing functions of the MQTT library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 2c60df154a..050124ff81 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -24,10 +24,8 @@ * @brief Implements functions involving transport layer connections. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 2e1bc9b53d..6a70d5e659 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -24,10 +24,8 @@ * @brief Implements functions that process MQTT operations. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index d90236626f..27be7ec830 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -24,10 +24,8 @@ * @brief Implements functions that generate and decode MQTT network packets. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index fd013b011a..b7ebcade98 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -24,10 +24,8 @@ * @brief Implements functions that manage subscriptions for an MQTT connection. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index 3e35668df2..22b718d345 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -24,10 +24,8 @@ * @brief Implements functions that validate the structs of the MQTT library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Error handling include. */ #include "private/iot_error.h" diff --git a/lib/source/serializer/iot_json_utils.c b/lib/source/serializer/iot_json_utils.c index 327762fcd2..4306a90e30 100644 --- a/lib/source/serializer/iot_json_utils.c +++ b/lib/source/serializer/iot_json_utils.c @@ -24,10 +24,8 @@ * @brief Implements the functions in iot_json_utils.h */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index a218ed3c83..0d1aa2e9f0 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -24,10 +24,8 @@ * @brief Implements the user-facing functions of the Shadow library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 6120cd6a9f..30aede8929 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -24,10 +24,8 @@ * @brief Implements functions that process Shadow operations. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index 58638a3ad0..a9d40fb451 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -24,10 +24,8 @@ * @brief Implements topic name and JSON parsing functions of the Shadow library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index b9a00a911a..9055e390ad 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -25,10 +25,8 @@ * subscription list. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index c2001a3a0a..2e78302643 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -28,10 +28,8 @@ #ifndef _IOT_NETWORK_OPENSSL_H_ #define _IOT_NETWORK_OPENSSL_H_ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* POSIX types include. */ #ifdef POSIX_TYPES_HEADER diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index 1ec2af99f0..ddb5ab8999 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -24,10 +24,8 @@ * @brief Implementation of the functions in iot_clock.h for POSIX systems. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* POSIX includes. Allow the default POSIX headers to be overridden. */ #ifdef POSIX_ERRNO_HEADER diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 31696322bd..6bb761cb4a 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -24,10 +24,8 @@ * @brief Implementation of the functions in iot_threads.h for POSIX systems. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* POSIX includes. Allow the default POSIX headers to be overridden. */ #ifdef POSIX_ERRNO_HEADER diff --git a/platform/source/posix/linux/iot_metrics.c b/platform/source/posix/linux/iot_metrics.c index 4114342f01..6e8e49ca66 100644 --- a/platform/source/posix/linux/iot_metrics.c +++ b/platform/source/posix/linux/iot_metrics.c @@ -19,10 +19,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Metrics include. */ #include "platform/iot_metrics.h" diff --git a/platform/source/posix/linux/iot_network_openssl_metrics.c b/platform/source/posix/linux/iot_network_openssl_metrics.c index 24a4f6985b..54505e5509 100644 --- a/platform/source/posix/linux/iot_network_openssl_metrics.c +++ b/platform/source/posix/linux/iot_network_openssl_metrics.c @@ -80,6 +80,7 @@ static IotNetworkError_t _metricsCreate( void * pConnectionInfo, /* Get the ip and port from the peer socket. */ int ret = getpeername( socket, ( struct sockaddr * ) &socketAddressIpv4, &socklen ); + ( void ) ret; /* Assert its succees. */ IotMetrics_Assert( ret == 0 ); diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 084b31fa9b..bb0de70265 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -25,10 +25,8 @@ * for POSIX systems with OpenSSL. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index 47785d986f..b2f624789a 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -30,10 +30,8 @@ * can be examined. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/common/unit/iot_tests_linear_containers.c b/tests/common/unit/iot_tests_linear_containers.c index 3ab4c58f05..6a58ddcd80 100644 --- a/tests/common/unit/iot_tests_linear_containers.c +++ b/tests/common/unit/iot_tests_linear_containers.c @@ -24,10 +24,8 @@ * @brief Tests for linear containers (lists and queues). */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Linear containers include. */ #include "iot_linear_containers.h" diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index d4c1b53dd7..f6b4afd85e 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -24,10 +24,8 @@ * @brief Tests for task pool. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index dd7ff28f39..da73b348bf 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -19,6 +19,9 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* The config header is always included first. */ +#include "iot_config.h" + /* Standard includes. */ #include #include diff --git a/tests/iot_tests_config.h b/tests/iot_config.h similarity index 100% rename from tests/iot_tests_config.h rename to tests/iot_config.h diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 0d4c32d91f..062be04931 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -28,10 +28,8 @@ * on a stable local network (not the Internet). */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 0e37c53054..ea8d5e88f4 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -24,10 +24,8 @@ * @brief Full system tests for the MQTT library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 1844b8b948..a297697885 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -24,10 +24,8 @@ * @brief Tests for the user-facing API functions (declared in iot_mqtt.h). */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index dd6757a119..8af9f90920 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -24,10 +24,8 @@ * @brief Tests for the function @ref mqtt_function_receivecallback. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 121575c12c..45c82b4432 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -24,10 +24,8 @@ * @brief Tests for the functions in iot_mqtt_subscription.c */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 57f209751d..5318d5149a 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -24,10 +24,8 @@ * @brief Tests for the functions in iot_mqtt_validate.c */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 7a03339461..73ca51c115 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -24,10 +24,8 @@ * @brief Full system tests for the AWS IoT Shadow library. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 9deeab70d4..916697edc8 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -24,10 +24,8 @@ * @brief Tests for the user-facing API functions (declared in aws_iot_shadow.h). */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 42218ba436..451f5fa16c 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -24,10 +24,8 @@ * @brief Tests for the Shadow topic name and JSON parser functions. */ -/* Build using a config header, if provided. */ -#ifdef IOT_CONFIG_FILE - #include IOT_CONFIG_FILE -#endif +/* The config header is always included first. */ +#include "iot_config.h" /* Standard includes. */ #include From 96713ae62e37669bac3d15cc12a6938cef556e63 Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Sat, 13 Apr 2019 17:30:21 -0700 Subject: [PATCH 095/844] Fixed function name in task pool docs. (#374) * Changed IotQueue to IotDeQueue (double ended queue) and added DequeueHead/Tail. Because this queue is implemented as a list, this does not cause any overhead. * Fixed function name in task pool docs --- lib/include/iot_taskpool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 9a16306bdd..ddd8766391 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -64,7 +64,7 @@ * @functionpage{IotTaskPool_Destroy,taskpool,destroy} * @functionpage{IotTaskPool_SetMaxThreads,taskpool,setmaxthreads} * @functionpage{IotTaskPool_CreateJob,taskpool,createjob} - * @functionpage{IotTaskPool_CreateJob,taskpool,createrecyclablejob} + * @functionpage{IotTaskPool_CreateRecyclableJob,taskpool,createrecyclablejob} * @functionpage{IotTaskPool_DestroyRecyclableJob,taskpool,destroyrecyclablejob} * @functionpage{IotTaskPool_RecycleJob,taskpool,recyclejob} * @functionpage{IotTaskPool_Schedule,taskpool,schedule} From 080414b96fdf0bfc9c56794081e6297b2ae40f31 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 15 Apr 2019 10:10:41 -0700 Subject: [PATCH 096/844] Rename init functions (#375) --- demos/app/posix/iot_demo_posix.c | 61 ++++++++++++++----- demos/source/aws_iot_demo_shadow.c | 38 +++--------- demos/source/iot_demo_mqtt.c | 25 ++------ lib/include/{iot_common.h => iot_init.h} | 24 ++++---- lib/include/private/iot_static_memory.h | 4 +- lib/source/common/CMakeLists.txt | 2 +- .../common/{iot_common.c => iot_init.c} | 21 ++++--- tests/defender/aws_iot_tests_defender.c | 12 ++-- tests/mqtt/system/iot_tests_mqtt_stress.c | 14 ++--- tests/mqtt/system/iot_tests_mqtt_system.c | 14 ++--- tests/mqtt/unit/iot_tests_mqtt_api.c | 8 +-- tests/mqtt/unit/iot_tests_mqtt_receive.c | 10 +-- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 10 +-- tests/mqtt/unit/iot_tests_mqtt_validate.c | 8 +-- tests/serializer/iot_tests_serializer.c | 8 +-- tests/shadow/aws_iot_tests_shadow.c | 3 - .../system/aws_iot_tests_shadow_system.c | 14 ++--- tests/shadow/unit/aws_iot_tests_shadow_api.c | 12 ++-- .../shadow/unit/aws_iot_tests_shadow_parser.c | 3 - 19 files changed, 143 insertions(+), 148 deletions(-) rename lib/include/{iot_common.h => iot_init.h} (81%) rename lib/source/common/{iot_common.c => iot_init.c} (89%) diff --git a/demos/app/posix/iot_demo_posix.c b/demos/app/posix/iot_demo_posix.c index de54df2d48..781e78ccc0 100644 --- a/demos/app/posix/iot_demo_posix.c +++ b/demos/app/posix/iot_demo_posix.c @@ -32,8 +32,8 @@ #include #include -/* Common libraries include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Common demo includes. */ #include "iot_demo_arguments.h" @@ -68,6 +68,9 @@ int main( int argc, /* Status returned from network stack initialization. */ IotNetworkError_t networkInitStatus = IOT_NETWORK_SUCCESS; + /* Flags for tracking which cleanup functions must be called. */ + bool sdkInitialized = false, networkInitialized = false; + /* Arguments for this demo. */ IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; @@ -111,21 +114,32 @@ int main( int argc, /* Set the pointer to the credentials. */ pCredentials = &credentials; } + } + else + { + /* Failed to parse arguments. */ + status = EXIT_FAILURE; + } + + /* Call the SDK initialization function. */ + if( status == EXIT_SUCCESS ) + { + sdkInitialized = IotSdk_Init(); + + if( sdkInitialized == false ) + { + status = EXIT_FAILURE; + } + } - /* Initialize the network stack. */ + /* Initialize the network stack. */ + if( status == EXIT_SUCCESS ) + { networkInitStatus = IotNetworkOpenssl_Init(); if( networkInitStatus == IOT_NETWORK_SUCCESS ) { - /* Run the demo. */ - status = RunDemo( demoArguments.awsIotMqttMode, - demoArguments.pIdentifier, - &serverInfo, - pCredentials, - IOT_NETWORK_INTERFACE_OPENSSL ); - - /* Clean up the network stack. */ - IotNetworkOpenssl_Cleanup(); + networkInitialized = true; } else { @@ -133,10 +147,27 @@ int main( int argc, status = EXIT_FAILURE; } } - else + + /* Run the demo. */ + if( status == EXIT_SUCCESS ) { - /* Error parsing arguments. */ - status = EXIT_FAILURE; + status = RunDemo( demoArguments.awsIotMqttMode, + demoArguments.pIdentifier, + &serverInfo, + pCredentials, + IOT_NETWORK_INTERFACE_OPENSSL ); + } + + /* Clean up the SDK if initialized. */ + if( sdkInitialized == true ) + { + IotSdk_Cleanup(); + } + + /* Clean up the network stack if initialized. */ + if( networkInitialized == true ) + { + IotNetworkOpenssl_Cleanup(); } /* Log the demo status. */ diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 5e65e19ea3..8f4cc4dcd1 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -42,9 +42,6 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" -/* Common libraries include. */ -#include "iot_common.h" - /* MQTT include. */ #include "iot_mqtt.h" @@ -430,7 +427,7 @@ static void _shadowUpdatedCallback( void * pCallbackContext, /*-----------------------------------------------------------*/ /** - * @brief Initialize the common libraries, the MQTT library, and the Shadow library. + * @brief Initialize the the MQTT library and the Shadow library. * * @return `EXIT_SUCCESS` if all libraries were successfully initialized; * `EXIT_FAILURE` otherwise. @@ -442,29 +439,18 @@ static int _initializeDemo( void ) AwsIotShadowError_t shadowInitStatus = AWS_IOT_SHADOW_SUCCESS; /* Flags to track cleanup on error. */ - bool commonInitialized = false, mqttInitialized = false; + bool mqttInitialized = false; - /* Initialize the common libraries. */ - commonInitialized = IotCommon_Init(); + /* Initialize the MQTT library. */ + mqttInitStatus = IotMqtt_Init(); - if( commonInitialized == false ) + if( mqttInitStatus == IOT_MQTT_SUCCESS ) { - status = EXIT_FAILURE; + mqttInitialized = true; } - - /* Initialize the MQTT library. */ - if( status == EXIT_SUCCESS ) + else { - mqttInitStatus = IotMqtt_Init(); - - if( mqttInitStatus == IOT_MQTT_SUCCESS ) - { - mqttInitialized = true; - } - else - { - status = EXIT_FAILURE; - } + status = EXIT_FAILURE; } /* Initialize the Shadow library. */ @@ -482,11 +468,6 @@ static int _initializeDemo( void ) /* Clean up on error. */ if( status == EXIT_FAILURE ) { - if( commonInitialized == true ) - { - IotCommon_Cleanup(); - } - if( mqttInitialized == true ) { IotMqtt_Cleanup(); @@ -499,13 +480,12 @@ static int _initializeDemo( void ) /*-----------------------------------------------------------*/ /** - * @brief Clean up the common libraries, the MQTT library, and the Shadow library. + * @brief Clean up the the MQTT library and the Shadow library. */ static void _cleanupDemo( void ) { AwsIotShadow_Cleanup(); IotMqtt_Cleanup(); - IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index 9b71ce0c56..0e845044e6 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -39,9 +39,6 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" -/* Common libraries include. */ -#include "iot_common.h" - /* MQTT include. */ #include "iot_mqtt.h" @@ -343,7 +340,7 @@ static void _mqttSubscriptionCallback( void * param1, /*-----------------------------------------------------------*/ /** - * @brief Initialize the common libraries and the MQTT library. + * @brief Initialize the MQTT library. * * @return `EXIT_SUCCESS` if all libraries were successfully initialized; * `EXIT_FAILURE` otherwise. @@ -353,22 +350,11 @@ static int _initializeDemo( void ) int status = EXIT_SUCCESS; IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; - if( IotCommon_Init() == true ) - { - mqttInitStatus = IotMqtt_Init(); - - if( mqttInitStatus != IOT_MQTT_SUCCESS ) - { - /* Failed to initialize MQTT library. */ - status = EXIT_FAILURE; + mqttInitStatus = IotMqtt_Init(); - /* Clean up the common libraries if MQTT could not be initialized. */ - IotCommon_Cleanup(); - } - } - else + if( mqttInitStatus != IOT_MQTT_SUCCESS ) { - /* Failed to initialize common libraries. */ + /* Failed to initialize MQTT library. */ status = EXIT_FAILURE; } @@ -378,12 +364,11 @@ static int _initializeDemo( void ) /*-----------------------------------------------------------*/ /** - * @brief Clean up the common libraries and the MQTT library. + * @brief Clean up the MQTT library. */ static void _cleanupDemo( void ) { IotMqtt_Cleanup(); - IotCommon_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/lib/include/iot_common.h b/lib/include/iot_init.h similarity index 81% rename from lib/include/iot_common.h rename to lib/include/iot_init.h index b3040a0611..4496393a45 100644 --- a/lib/include/iot_common.h +++ b/lib/include/iot_init.h @@ -20,13 +20,13 @@ */ /** - * @file iot_common.h - * @brief Provides function signatures for intialization and cleanup of common - * libraries. + * @file iot_init.h + * @brief Provides function signatures for common intialization and cleanup of + * this SDK. */ -#ifndef _IOT_COMMON_H_ -#define _IOT_COMMON_H_ +#ifndef IOT_INIT_H_ +#define IOT_INIT_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -35,29 +35,29 @@ #include /** - * @brief One-time initialization function for all common libraries. + * @brief One-time initialization function for this SDK. * * This function initializes common libraries, such as static memory and task * pool. It must be called once (and only once) before calling any other * function in this SDK. Calling this function more than once without first - * calling `IotCommon_Cleanup` may result in a crash. + * calling `IotSdk_Cleanup` may result in a crash. * * @return `true` if initialization succeeded; `false` otherwise. Logs may be * printed in case of failure. * * @warning No thread-safety guarantees are provided for this function. */ -bool IotCommon_Init( void ); +bool IotSdk_Init( void ); /** * @brief One-time deinitialization function for all common libraries. * - * This function frees resources taken in `IotCommon_Init`. No other function - * in this SDK may be called after calling this function unless `IotCommon_Init` + * This function frees resources taken in `IotSdk_Init`. No other function + * in this SDK may be called after calling this function unless `IotSdk_Init` * is called again. * * @warning No thread-safety guarantees are provided for this function. */ -void IotCommon_Cleanup( void ); +void IotSdk_Cleanup( void ); -#endif +#endif /* IOT_INIT_H_ */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 5eb0365f2b..68d10980a9 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -77,7 +77,7 @@ * * @return `true` if initialization succeeded; `false` otherwise. * - * @attention This function is called by `IotCommon_Init` and does not need to be + * @attention This function is called by `IotSdk_Init` and does not need to be * called by itself. * * @warning No thread-safety guarantees are provided for this function. @@ -96,7 +96,7 @@ bool IotStaticMemory_Init( void ); * returns, @ref static_memory_function_init must be called again before * calling any other static memory function. * - * @attention This function is called by `IotCommon_Cleanup` and does not need + * @attention This function is called by `IotSdk_Cleanup` and does not need * to be called by itself. * * @warning No thread-safety guarantees are provided for this function. diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 65a2abf0fb..b64d452ba1 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,6 +1,6 @@ # Common libraries source files. add_library( iotcommon SHARED - iot_common.c + iot_init.c iot_logging.c iot_taskpool.c static_memory/iot_static_memory_common.c diff --git a/lib/source/common/iot_common.c b/lib/source/common/iot_init.c similarity index 89% rename from lib/source/common/iot_common.c rename to lib/source/common/iot_init.c index 00b67971ce..d912311588 100644 --- a/lib/source/common/iot_common.c +++ b/lib/source/common/iot_init.c @@ -19,11 +19,16 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file iot_init.c + * @brief Implements functions for common intialization and cleanup of this SDK. + */ + /* The config header is always included first. */ #include "iot_config.h" -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Task pool include. */ #include "iot_taskpool.h" @@ -44,18 +49,18 @@ #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif -#define _LIBRARY_LOG_NAME ( "COMMON" ) +#define _LIBRARY_LOG_NAME ( "INIT" ) #include "iot_logging_setup.h" /*-----------------------------------------------------------*/ -bool IotCommon_Init( void ) +bool IotSdk_Init( void ) { _IOT_FUNCTION_ENTRY( bool, true ); IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; - if (status == true) + if( status == true ) { status = IotMetrics_Init(); } @@ -93,7 +98,7 @@ bool IotCommon_Init( void ) } else { - IotLogInfo( "Common libraries successfully initialized." ); + IotLogInfo( "SDK successfully initialized." ); } _IOT_FUNCTION_CLEANUP_END(); @@ -101,13 +106,13 @@ bool IotCommon_Init( void ) /*-----------------------------------------------------------*/ -void IotCommon_Cleanup( void ) +void IotSdk_Cleanup( void ) { IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); /* This log message must be printed before static memory management is * cleaned up. */ - IotLogInfo( "Common libraries cleanup done." ); + IotLogInfo( "SDK cleanup done." ); /* Cleanup static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index e4ff857eba..26c6066c4c 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -31,8 +31,8 @@ /* Network includes. */ #include "posix/iot_network_openssl.h" -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* MQTT include. */ #include "iot_mqtt.h" @@ -85,8 +85,8 @@ int main( int argc, return EXIT_FAILURE; } - /* Initialize the common libraries before running the tests. */ - if( IotCommon_Init() == false ) + /* Initialize the SDK before running the tests. */ + if( IotSdk_Init() == false ) { return EXIT_FAILURE; } @@ -113,8 +113,8 @@ int main( int argc, /* Run short tests. */ RUN_TEST_GROUP(Full_DEFENDER); - /* Clean up common libraries. */ - IotCommon_Cleanup(); + /* Clean up SDK. */ + IotSdk_Cleanup(); /* Clean up the network stack. */ IotNetworkOpenssl_Cleanup(); diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 062be04931..520149847d 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -37,8 +37,8 @@ /* POSIX includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* MQTT include. */ #include "iot_mqtt.h" @@ -406,10 +406,10 @@ TEST_SETUP( MQTT_Stress ) IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - /* Initialize common components. */ - if( IotCommon_Init() == false ) + /* Initialize SDK. */ + if( IotSdk_Init() == false ) { - TEST_FAIL_MESSAGE( "Failed to initialize common components." ); + TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); } /* Set up the network stack. */ @@ -510,8 +510,8 @@ TEST_TEAR_DOWN( MQTT_Stress ) /* Clean up the network stack. */ IotTestNetwork_Cleanup(); - /* Clean up common components. */ - IotCommon_Cleanup(); + /* Clean up SDK. */ + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index ea8d5e88f4..3cac184655 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -30,8 +30,8 @@ /* Standard includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -593,10 +593,10 @@ TEST_SETUP( MQTT_System ) _unsubscribeSerializerOverride = false; _disconnectSerializerOverride = false; - /* Initialize common components. */ - if( IotCommon_Init() == false ) + /* Initialize SDK. */ + if( IotSdk_Init() == false ) { - TEST_FAIL_MESSAGE( "Failed to initialize common components." ); + TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); } /* Call the network stack initialization function. */ @@ -654,8 +654,8 @@ TEST_TEAR_DOWN( MQTT_System ) /* Clean up the network stack. */ IotTestNetwork_Cleanup(); - /* Clean up common components. */ - IotCommon_Cleanup(); + /* Clean up SDK. */ + IotSdk_Cleanup(); /* Clear the connection pointer. */ _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index a297697885..458c1e1002 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -30,8 +30,8 @@ /* Standard includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -529,7 +529,7 @@ TEST_SETUP( MQTT_Unit_API ) _disconnectCallbackCount = 0; /* Initialize libraries. */ - TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); } @@ -541,7 +541,7 @@ TEST_SETUP( MQTT_Unit_API ) TEST_TEAR_DOWN( MQTT_Unit_API ) { IotMqtt_Cleanup(); - IotCommon_Cleanup(); + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 8af9f90920..e0227dca12 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -30,8 +30,8 @@ /* Standard includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -518,8 +518,8 @@ TEST_SETUP( MQTT_Unit_Receive ) static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - /* Initialize common components. */ - TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + /* Initialize SDK. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); @@ -578,7 +578,7 @@ TEST_TEAR_DOWN( MQTT_Unit_Receive ) /* Clean up resources taken in test setup. */ IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); IotMqtt_Cleanup(); - IotCommon_Cleanup(); + IotSdk_Cleanup(); /* Check that the tests used a deserializer override. */ TEST_ASSERT_EQUAL_INT( true, _deserializeOverrideCalled ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 45c82b4432..7f6e99c7c3 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -30,8 +30,8 @@ /* Standard includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -352,8 +352,8 @@ TEST_SETUP( MQTT_Unit_Subscription ) static IotNetworkInterface_t networkInterface = { 0 }; IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - /* Initialize common components. */ - TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + /* Initialize SDK. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); @@ -385,7 +385,7 @@ TEST_TEAR_DOWN( MQTT_Unit_Subscription ) /* Clean up libraries. */ IotMqtt_Cleanup(); - IotCommon_Cleanup(); + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 5318d5149a..651a8013b5 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -30,8 +30,8 @@ /* Standard includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" @@ -75,7 +75,7 @@ TEST_GROUP( MQTT_Unit_Validate ); */ TEST_SETUP( MQTT_Unit_Validate ) { - TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); } /*-----------------------------------------------------------*/ @@ -85,7 +85,7 @@ TEST_SETUP( MQTT_Unit_Validate ) */ TEST_TEAR_DOWN( MQTT_Unit_Validate ) { - IotCommon_Cleanup(); + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/serializer/iot_tests_serializer.c b/tests/serializer/iot_tests_serializer.c index eb2da17935..b26c258e06 100644 --- a/tests/serializer/iot_tests_serializer.c +++ b/tests/serializer/iot_tests_serializer.c @@ -28,8 +28,8 @@ /* POSIX includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -80,7 +80,7 @@ int main( int argc, } /* Initialize the common libraries before running the tests. */ - if( IotCommon_Init() == false ) + if( IotSdk_Init() == false ) { return EXIT_FAILURE; } @@ -96,7 +96,7 @@ int main( int argc, RUN_TEST_GROUP( Full_Serializer_CBOR ); /* Clean up common libraries. */ - IotCommon_Cleanup(); + IotSdk_Cleanup(); /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index ff8e0a7828..9a82d8213a 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -36,9 +36,6 @@ /* Error handling include. */ #include "private/iot_error.h" -/* Common include. */ -#include "iot_common.h" - /* Test framework includes. */ #include "unity_fixture.h" diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 73ca51c115..2a077307df 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -30,8 +30,8 @@ /* Standard includes. */ #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" @@ -445,10 +445,10 @@ TEST_SETUP( Shadow_System ) IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - /* Initialize common components. */ - if( IotCommon_Init() == false ) + /* Initialize SDK. */ + if( IotSdk_Init() == false ) { - TEST_FAIL_MESSAGE( "Failed to initialize common components." ); + TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); } /* Set up the network stack. */ @@ -537,8 +537,8 @@ TEST_TEAR_DOWN( Shadow_System ) /* Clean up the network stack. */ IotTestNetwork_Cleanup(); - /* Clean up common components. */ - IotCommon_Cleanup(); + /* Clean up SDK. */ + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 916697edc8..7deacb6120 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -31,8 +31,8 @@ #include #include -/* Common include. */ -#include "iot_common.h" +/* SDK initialization include. */ +#include "iot_init.h" /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" @@ -374,8 +374,8 @@ TEST_SETUP( Shadow_Unit_API ) { IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - /* Initialize common components. */ - TEST_ASSERT_EQUAL_INT( true, IotCommon_Init() ); + /* Initialize SDK. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); /* Initialize the MQTT library. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); @@ -424,8 +424,8 @@ TEST_TEAR_DOWN( Shadow_Unit_API ) /* Clean up the MQTT library. */ IotMqtt_Cleanup(); - /* Clean up common components. */ - IotCommon_Cleanup(); + /* Clean up SDK. */ + IotSdk_Cleanup(); /* Destroy the receive thread timer. */ IotClock_TimerDestroy( &_receiveTimer ); diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 451f5fa16c..729410d2d5 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -31,9 +31,6 @@ #include #include -/* Common include. */ -#include "iot_common.h" - /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. From 3e7bb25ec9883b418643431e22ea388cc63c2fb8 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Tue, 16 Apr 2019 10:21:29 -0700 Subject: [PATCH 097/844] sync latest serializer, metrics and defender library from AFR (#377) --- demos/iot_config.h | 23 ++-- lib/include/aws_iot_defender.h | 113 +++++++++++------- lib/include/platform/iot_metrics.h | 38 +++++- .../private/aws_iot_defender_internal.h | 19 ++- lib/include/private/iot_static_memory.h | 30 +++++ .../static_memory/iot_static_memory_metrics.c | 56 ++++++++- lib/source/defender/aws_iot_defender_api.c | 32 ++--- .../defender/aws_iot_defender_collector.c | 9 +- .../cbor/iot_serializer_tinycbor_decoder.c | 83 ++++++++----- platform/source/posix/linux/iot_metrics.c | 37 +++++- .../posix/linux/iot_network_openssl_metrics.c | 13 +- tests/defender/aws_iot_tests_defender_api.c | 2 + tests/iot_config.h | 43 ++++--- 13 files changed, 351 insertions(+), 147 deletions(-) diff --git a/demos/iot_config.h b/demos/iot_config.h index 666913cca6..f52bc6d005 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -42,8 +42,8 @@ /* #define IOT_DEMO_IDENTIFIER "" */ /* MQTT demo configuration. The demo publishes bursts of messages. */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ -#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_COUNT ( 10 ) /* Number of message bursts. */ +#define IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ( 10 ) /* Number of messages published in each burst. */ /* Shadow demo configuration. The demo publishes periodic Shadow updates and responds * to changing Shadows. */ @@ -51,19 +51,22 @@ #define AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ( 3000 ) /* Period of Shadow updates. */ /* Enable asserts in linear containers and MQTT. */ -#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) -#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) +#define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) +#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) /* Library logging configuration. IOT_LOG_LEVEL_GLOBAL provides a global log * level for all libraries; the library-specific settings override the global * setting. If both the library-specific and global settings are undefined, * no logs will be printed. */ -#define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO -#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO -#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO -#define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO -#define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO -#define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO +#define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO +#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO +#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO +#define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO +#define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO + +/* This is supposed to be defined as the socket data type. In linux, it is "int". */ +#define IotMetricsConnectionId_t int /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index aa7558b544..20c72c9524 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -38,10 +38,32 @@ * Amazon FreeRTOS provides a library that allows your Amazon FreeRTOS-based devices to work with AWS IoT Device Defender. * * ## Dependencies - * * MQTT library - * * Serializer library - * * Platform(POSIX) libraries - * * Metrics library + * + * @dot "Device Defender Library dependencies" + * digraph library_dependencies + * { + * node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + * edge[fontname=Helvetica, fontsize=10]; + * + * defender[label="Device Defender", fillcolor="#cc00ccff"]; + * + * logging[label="Logging", fillcolor="#aed8a9ff", URL="@ref logging"]; + * static_memory[label="Static memory", fillcolor="#aed8a9ff", URL="@ref static_memory"]; + * linear_containers[label="List/Queue", fillcolor="#aed8a9ff", URL="@ref linear_containers"]; + * taskpool[label="Taskpool", fillcolor="#aed8a9ff", URL="@ref taskpool"] + * mqtt[label="MQTT", fillcolor="#aed8a9ff", URL="@ref mqtt"] + * platform_threads[label="Thread", fillcolor="#e89025ff", URL="@ref platform_threads"]; + * platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; + * + * defender -> linear_containers; + * defender -> logging [label=" if logging enabled", style="dashed"]; + * defender -> static_memory [label=" if static memory only", style="dashed"]; + * defender -> platform_threads; + * defender -> platform_clock; + * defender -> taskpool; + * defender -> mqtt; + * } + * @enddot */ #ifndef _AWS_IOT_DEFENDER_H_ @@ -54,9 +76,6 @@ #include #include -/* Network include. */ -#include "platform/iot_network.h" - /* MQTT include. */ #include "iot_mqtt.h" @@ -85,6 +104,9 @@ * @name Metrics Flags * * @brief Bit flags or metrics used by @ref defender_function_setmetrics function. + * + * These metrics are subset of metrics supported by AWS IoT Device Defender service. For details, + * refer to developer document of [AWS IoT Device Defender](https://docs.aws.amazon.com/iot/latest/developerguide/device-defender-detect.html#DetectMetricsMessages). */ /**@{ */ #define AWS_IOT_DEFENDER_METRICS_ALL 0xffffffff /**< Flag to indicate including all metrics. */ @@ -115,7 +137,9 @@ * @brief Intializers of data handles. */ /**@{ */ -#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER { .mqttNetworkInfo = { 0 } } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ +#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER \ + { .mqttNetworkInfo = { 0 } \ + } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ /**@} */ /** @@ -207,7 +231,7 @@ typedef struct AwsIotDefenderStartInfo { IotMqttNetworkInfo_t mqttNetworkInfo; /**< MQTT Network info used by defender (required). */ IotMqttConnectInfo_t mqttConnectionInfo; /**< MQTT connection info used by defender (required). */ - AwsIotDefenderCallback_t callback; /**< Callback function parameter(optional). */ + AwsIotDefenderCallback_t callback; /**< Callback function parameter (optional). */ } AwsIotDefenderStartInfo_t; /** @@ -218,7 +242,6 @@ typedef struct AwsIotDefenderStartInfo * - @functionname{defender_function_setperiod} * - @functionname{defender_function_getperiod} * - @functionname{defender_function_strerror} - * - @functionname{defender_function_geteventerror} */ /** @@ -228,7 +251,6 @@ typedef struct AwsIotDefenderStartInfo * @functionpage{AwsIotDefender_SetPeriod,defender,setperiod} * @functionpage{AwsIotDefender_GetPeriod,defender,getperiod} * @functionpage{AwsIotDefender_strerror,defender,strerror} - * @functionpage{AwsIotDefender_GetEventError,defender,geteventerror} */ /** @@ -274,60 +296,67 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me * Example: * * @code{c} - * void logDefenderCallback(void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo) + * + * // assume valid IotMqttNetworkInfo_t and IotMqttConnectInfo_t are created. + * const IotMqttNetworkInfo_t _mqttNetworkInfo; + * const IotMqttConnectInfo_t _mqttConnectionInfo; + * + * void logDefenderCallback( void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) * { - * const char * pEventStr = AwsIotDefender_DescribeEventType(pCallbackInfo->eventType); - * // log pEventStr + * if ( pCallbackInfo->eventType == AWS_IOT_DEFENDER_METRICS_ACCEPTED ) + * { + * // log info: metrics report accepted by defender service is a happy case + * } + * else + * { + * // log error: pCallbackInfo->eventType + * } * - * if (pCallbackInfo->pPayload != NULL) + * if ( pCallbackInfo->pPayload != NULL ) * { - * // log pCallbackInfo->pPayload which has length pCallbackInfo->payloadLength + * // log info: pCallbackInfo->pPayload with length pCallbackInfo->payloadLength * } * - * if (pCallbackInfo->pMetricsReport != NULL) + * if ( pCallbackInfo->pMetricsReport != NULL ) * { - * // log pCallbackInfo->pMetricsReport which has length pCallbackInfo->metricsReportLength + * // log info: pCallbackInfo->pMetricsReport with length pCallbackInfo->metricsReportLength * } * } * * void startDefender() * { - * // assume a valid AwsIotNetworkTlsInfo_t is created. - * const AwsIotNetworkTlsInfo_t tlsInfo; - * * // define a simple callback function which simply logs - * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback,.param1 = NULL }; + * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback, .param1 = NULL }; * * // define parameters of AwsIotDefender_Start function - * const AwsIotDefenderStartInfo_t startInfo = { - * .tlsInfo = tlsInfo, // copy TLS info - * .pAwsIotEndpoint = "iot endpoint", - * .port = 8883, - * .pThingName = "some thing name", - * .thingNameLength = strlen("some thing name"), - * .callback = callback }; + * const AwsIotDefenderStartInfo_t startInfo = + * { + * .mqttNetworkInfo = _mqttNetworkInfo, + * .mqttConnectionInfo = _mqttConnectionInfo, + * .callback = callback + * }; * * // specify two TCP connections metrics: total count and local port * AwsIotDefenderError_t error = AwsIotDefender_SetMetrics(AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, * AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL | * AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ); * - * if (error == AWS_IOT_DEFENDER_SUCCESS) + * if ( error == AWS_IOT_DEFENDER_SUCCESS ) * { * // set metrics report period to 10 minutes (600 seconds) - * error = AwsIotDefender_SetPeriod(600); + * error = AwsIotDefender_SetPeriod( 600 ); * } * * if (error == AWS_IOT_DEFENDER_SUCCESS) * { * // start the defender - * error = AwsIotDefender_Start(&startInfo); + * error = AwsIotDefender_Start( &startInfo ); * } * - * if (error != AWS_IOT_DEFENDER_SUCCESS) + * if ( error != AWS_IOT_DEFENDER_SUCCESS ) * { - * const char * pError = AwsIotDefender_strerror(error); - * // log pError + * const char * pError = AwsIotDefender_strerror( error ); + * // log error: pError * } * } * @@ -346,6 +375,9 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /** * @brief Stop the defender agent. * + * It waits for the current metrics-publishing iteration to finish before freeing the resource allocated. + * It also clears the metrics set previously so that user is expected to SetMetrics again before restarting defender agent. + * * @warning This function must be called after successfully calling @ref defender_function_start. * @warning This function is not thread safe. */ @@ -373,6 +405,8 @@ AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ); /** * @brief Get period in seconds. + * + * @return Current period in seconds */ /* @[declare_defender_getperiod] */ uint32_t AwsIotDefender_GetPeriod( void ); @@ -380,16 +414,11 @@ uint32_t AwsIotDefender_GetPeriod( void ); /** * @brief Return a string that describes #AwsIotDefenderError_t + * + * @return A string that describes given #AwsIotDefenderError_t */ /* @[declare_defender_strerror] */ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); /* @[declare_defender_strerror] */ -/** - * @brief Return a string that describes #AwsIotDefenderEventType_t - */ -/* @[declare_defender_geteventerror] */ -const char * AwsIotDefender_GetEventError(); -/* @[declare_defender_geteventerror] */ - #endif /* end of include guard: _AWS_IOT_DEFENDER_H_ */ diff --git a/lib/include/platform/iot_metrics.h b/lib/include/platform/iot_metrics.h index 1a1d8f2f43..571a6a3974 100644 --- a/lib/include/platform/iot_metrics.h +++ b/lib/include/platform/iot_metrics.h @@ -75,6 +75,24 @@ #define IotMetrics_FreeTcpConnection Iot_FreeMetricsTcpConnection #endif +/** + * @brief Allocate an array of uint8_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #ifndef IotMetrics_MallocIpAddress + #define IotMetrics_MallocIpAddress Iot_MallocMetricsIpAddress + #endif + +/** + * @brief Free an array of uint8_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #ifndef IotMetrics_FreeIpAddress + #define IotMetrics_FreeIpAddress Iot_FreeMetricsIpAddress + #endif + #else /* if IOT_STATIC_MEMORY_ONLY */ #include @@ -86,12 +104,24 @@ #define IotMetrics_FreeTcpConnection free #endif + #ifndef IotMetrics_MallocIpAddress + #define IotMetrics_MallocIpAddress malloc + #endif + + #ifndef IotMetrics_FreeIpAddress + #define IotMetrics_FreeIpAddress free + #endif + #endif /* if IOT_STATIC_MEMORY_ONLY */ +/* This data type varies on different platform. */ #ifndef IotMetricsConnectionId_t - #define IotMetricsConnectionId_t uint32_t + #error "'IotMetricsConnectionId_t' must be defended as the socket handle data type." #endif +/* xxx.xxx.xxx.xxx\0 */ +#define IOT_METRICS_MAX_IP_STRING_LENGTH 16 + /** * Data handle of TCP connection */ @@ -99,7 +129,9 @@ typedef struct IotMetricsTcpConnection { IotLink_t link; IotMetricsConnectionId_t id; - char * pRemoteIP; /* This is limited to IPv4. */ + /* This is limited to IPv4. */ + uint32_t remoteIp; /* In host byte order. */ + char * pRemoteIp; uint16_t remotePort; /* In host order. */ } IotMetricsTcpConnection_t; @@ -116,7 +148,7 @@ typedef struct IotMetricsListCallback /** * This function must be called before any other functions. */ -bool IotMetrics_Init(); +bool IotMetrics_Init( void ); /** * Record one TCP connection metric. diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 4ca8d64e76..fe13ac9ae1 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -143,10 +143,23 @@ * @brief Configuration settings of the Defender library * @par configpagemarker * + * @section AWS_IOT_SECURE_SOCKETS_METRICS_ENABLED + * @brief Enable secure sockets metrics + * Possible values: 0 or 1
+ * Recommended values: 1
+ * Default value (if undefined): 0
+ * + * This macro must be defined for device defender library to collect sockets metrics correctly. + * Without defining it, the behavior is unknown. + * + * @code{c} + * #define AWS_IOT_SECURE_SOCKETS_METRICS_ENABLED (1) + * @endcode + * * @section AWS_IOT_DEFENDER_FORMAT * @brief Default format for metrics data serialization. * - * Possible values: #AWS_IOT_DEFENDER_FORMAT_CBOR or #AWS_IOT_DEFENDER_FORMAT_JSON
+ * Possible values: #AWS_IOT_DEFENDER_FORMAT_CBOR (JSON is not supported for now)
* Recommended values: Cbor is more compact than Json, thus more efficient.
* Default value (if undefined): #AWS_IOT_DEFENDER_FORMAT_CBOR
* @@ -318,13 +331,13 @@ IotMqttError_t AwsIotDefenderInternal_MqttConnect(); * Subscribe accept/reject defender topics. */ IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, - IotMqttCallbackInfo_t rejectCallback ); + IotMqttCallbackInfo_t rejectCallback ); /** * Publish metrics data to defender topic. */ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, - size_t dataLength ); + size_t dataLength ); /** * Disconnect with AWS MQTT. diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 68d10980a9..4f7e7babec 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -453,6 +453,36 @@ void * Iot_MallocMetricsTcpConnection( size_t size ); void Iot_FreeMetricsTcpConnection( void * ptr ); /* @[declare_static_memory_freemetricstcpconnection] */ +/** + * @brief Allocates memory to hold data for a new Metrics IP Address. + * + * This function is the analog of [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) + * for Metrics IP Address. + * + * @param[in] size Requested size for the ip address. + * + * @return Pointer to a Metrics IP Address. If the size argument is larger than + * the fixed size of a Metrics IP Address object or no free Metrics IP Address + * are available, `NULL` is returned. + */ +/* @[declare_static_memory_mallocmetricsipaddress] */ +void * Iot_MallocMetricsIpAddress(size_t size); +/* @[declare_static_memory_mallocmetricsipaddress] */ + +/** + * @brief Frees an in-use Metrics IP Address. + * + * This function is the analog of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) + * for Metrics IP Address. + * + * @param[in] ptr Pointer to an active Metrics IP Address to free. + */ +/* @[declare_static_memory_freemetricsipaddress] */ +void Iot_FreeMetricsIpAddress(void * ptr); +/* @[declare_static_memory_freemetricsipaddress] */ + /** * @brief Allocates memory to hold data for a new Serializer Cbor Encoder. * diff --git a/lib/source/common/static_memory/iot_static_memory_metrics.c b/lib/source/common/static_memory/iot_static_memory_metrics.c index de2553ea26..353f99e325 100644 --- a/lib/source/common/static_memory/iot_static_memory_metrics.c +++ b/lib/source/common/static_memory/iot_static_memory_metrics.c @@ -40,11 +40,21 @@ #define IOT_METRICS_TCP_CONNECTIONS ( 10 ) #endif + #ifndef IOT_METRICS_IP_ADDRESSES + #define IOT_METRICS_IP_ADDRESSES ( 10 ) + #endif + /* Validate static memory configuration settings. */ #if IOT_METRICS_TCP_CONNECTIONS <= 0 #error "IOT_METRICS_TCP_CONNECTIONS cannot be 0 or negative." #endif + #if IOT_METRICS_IP_ADDRESSES <= 0 + #error "IOT_METRICS_IP_ADDRESSES cannot be 0 or negative." + #endif + + #define _METRICS_TCP_CONNECTION_SIZE ( sizeof( IotMetricsTcpConnection_t ) * IOT_METRICS_TCP_CONNECTIONS ) + /*-----------------------------------------------------------*/ /* Extern declarations of common static memory functions in iot_static_memory_common.c @@ -64,7 +74,10 @@ * Static memory buffers and flags, allocated and zeroed at compile-time. */ static bool _inUseTcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { 0 }; - static IotMetricsTcpConnection_t _tcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { { .link = { 0 } } }; + static IotMetricsTcpConnection_t _tcpConnections[ IOT_METRICS_TCP_CONNECTIONS ][ _METRICS_TCP_CONNECTION_SIZE ] = { { { .link = { 0 } } } }; + + static bool _inUseIpAddresses[ IOT_METRICS_IP_ADDRESSES ] = { 0 }; + static char _ipAddresses[ IOT_METRICS_IP_ADDRESSES ][ IOT_METRICS_MAX_IP_STRING_LENGTH ] = { { 0 } }; /*-----------------------------------------------------------*/ @@ -73,14 +86,14 @@ int freeIndex = -1; void * pNewTcpConnection = NULL; - if( size == sizeof( IotMetricsTcpConnection_t ) ) + if( size <= _METRICS_TCP_CONNECTION_SIZE ) { freeIndex = IotStaticMemory_FindFree( _inUseTcpConnections, IOT_METRICS_TCP_CONNECTIONS ); if( freeIndex != -1 ) { - pNewTcpConnection = &( _tcpConnections[ freeIndex ] ); + pNewTcpConnection = &( _tcpConnections[ freeIndex ][ 0 ] ); } } @@ -95,7 +108,42 @@ _tcpConnections, _inUseTcpConnections, IOT_METRICS_TCP_CONNECTIONS, - sizeof( IotMetricsTcpConnection_t ) ); + _METRICS_TCP_CONNECTION_SIZE ); + } + + +/*-----------------------------------------------------------*/ + + void * Iot_MallocMetricsIpAddress( size_t size ) + { + int freeIndex = -1; + void * pNewIpAddress = NULL; + + if( size <= IOT_METRICS_MAX_IP_STRING_LENGTH ) + { + /* Get the index of a free Shadow subscription. */ + freeIndex = IotStaticMemory_FindFree( _inUseIpAddresses, + IOT_METRICS_IP_ADDRESSES ); + + if( freeIndex != -1 ) + { + pNewIpAddress = &( _ipAddresses[ freeIndex ][ 0 ] ); + } + } + + return pNewIpAddress; + } + +/*-----------------------------------------------------------*/ + + void Iot_FreeMetricsIpAddress( void * ptr ) + { + /* Return the in-use Shadow subscription. */ + IotStaticMemory_ReturnInUse( ptr, + _ipAddresses, + _inUseIpAddresses, + IOT_METRICS_IP_ADDRESSES, + IOT_METRICS_MAX_IP_STRING_LENGTH ); } #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 060379d3a7..95418e9f57 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -88,9 +88,6 @@ static bool _started = false; /* Internal copy of startInfo so that user's input doesn't have to be valid all the time. */ AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; -/* For debug purpose. */ -const char * _AwsIotDefenderEventError = "None"; - /*-----------------------------------------------------------*/ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, @@ -327,11 +324,6 @@ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) return pErrorNames[ error ]; } -/*-----------------------------------------------------------*/ -const char * AwsIotDefender_GetEventError() -{ - return _AwsIotDefenderEventError; -} /*-----------------------------------------------------------*/ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, @@ -354,7 +346,8 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - bool mqttConnected = true, reportCreated = true, reportPublished = false; + bool mqttConnected = false; + bool reportCreated = false; const IotMqttCallbackInfo_t acceptCallbackInfo = { .function = _acceptCallback, .pCallbackContext = NULL }; const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .pCallbackContext = NULL }; @@ -362,12 +355,14 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, /* Step 1: connect to MQTT. */ mqttError = AwsIotDefenderInternal_MqttConnect(); - if( ( mqttConnected = ( mqttError == IOT_MQTT_SUCCESS ) ) ) + if( mqttError == IOT_MQTT_SUCCESS ) { + mqttConnected = true; + + /* Step 2: subscribe to accept/reject MQTT topics. */ mqttError = AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, rejectCallbackInfo ); - /* Step 2: subscribe to accept/reject MQTT topics. */ if( mqttError == IOT_MQTT_SUCCESS ) { /* Step 3: create serialized metrics report. */ @@ -380,7 +375,7 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, mqttError = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), AwsIotDefenderInternal_GetReportBufferSize() ); - if( ( reportPublished = ( mqttError == IOT_MQTT_SUCCESS ) ) ) + if( mqttError == IOT_MQTT_SUCCESS ) { IotLogDebug( "Metrics report has been published successfully." ); } @@ -388,7 +383,8 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, } } - if( reportPublished ) + /* If no MQTT error and report has been created, it indicates everything is good. */ + if( ( mqttError == IOT_MQTT_SUCCESS ) && reportCreated ) { IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJob ); @@ -401,25 +397,21 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, &_disconnectJob, _defenderToMilliseconds( AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS ) ); } - /* Something is wrong during the above process. */ else { - /* Either MQTT functions had problem or report failed to be created. */ - AwsIotDefender_Assert( mqttError != IOT_MQTT_SUCCESS || !reportCreated ); - AwsIotDefenderEventType_t eventType; /* Set event type to only two possible categories. */ - if( reportCreated ) + if( mqttError != IOT_MQTT_SUCCESS ) { eventType = AWS_IOT_DEFENDER_FAILURE_MQTT; - _AwsIotDefenderEventError = IotMqtt_strerror( mqttError ); + IotLogError( "Failed to perform MQTT operations, with error %s.", IotMqtt_strerror( mqttError ) ); } else { eventType = AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT; /* As of today, no memory is the only reason to fail metrics report creation. */ - _AwsIotDefenderEventError = "Failed to create metrics report due to no memory."; + IotLogError( "Failed to create metrics report due to no memory." ); } /* Invoke user's callback if there is. */ diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index 78e3427d18..abc3b972ef 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -84,6 +84,9 @@ static uint32_t _metricsFlagSnapshot[ _DEFENDER_METRICS_GROUP_COUNT ]; /* Report id integer. */ uint64_t _AwsIotDefenderReportId = 0; +/* Static storage holding the string of remote address: "ip:port". */ +static char _remoteAddr[ _REMOTE_ADDR_LENGTH ] = ""; + /*---------------------- Helper Functions -------------------------*/ static void copyMetricsFlag(); @@ -421,8 +424,6 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj uint8_t hasTotal = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) > 0; uint8_t hasRemoteAddr = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) > 0; - char remoteAddr[ _REMOTE_ADDR_LENGTH ] = ""; - void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall : assertSuccess; @@ -470,10 +471,10 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj /* add remote address */ if( hasRemoteAddr ) { - sprintf( remoteAddr, "%s:%d", _metrics.tcpConns.pArray[ i ].pRemoteIP, _metrics.tcpConns.pArray[ i ].remotePort ); + sprintf( _remoteAddr, "%s:%d", _metrics.tcpConns.pArray[ i ].pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); serializerError = _AwsIotDefenderEncoder.appendKeyValue( &connectionMap, _REMOTE_ADDR_TAG, - IotSerializer_ScalarTextString( remoteAddr ) ); + IotSerializer_ScalarTextString( _remoteAddr ) ); assertNoError( serializerError ); } diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c index 79a8fce45d..5d9359047a 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c @@ -170,20 +170,49 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal if( _isArrayOrMap( dataType ) ) { /* Save to decoder object's handle. */ - pDecoderObject->pHandle = ( void * ) pCborValueWrapper; + pDecoderObject->pHandle = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + + if( pDecoderObject->pHandle == NULL ) + { + return IOT_SERIALIZER_OUT_OF_MEMORY; + } + + memcpy( pDecoderObject->pHandle, pCborValueWrapper, sizeof( _cborValueWrapper_t ) ); } else /* Create scalar object. */ { switch( dataType ) { + case IOT_SERIALIZER_SCALAR_BOOL: + { + bool value = false; + cborError = cbor_value_get_boolean( pCborValue, &( value ) ); + + if( cborError == CborNoError ) + { + pDecoderObject->value.booleanValue = value; + } + else + { + returnedError = IOT_SERIALIZER_INTERNAL_FAILURE; + } + + break; + } + case IOT_SERIALIZER_SCALAR_SIGNED_INT: { int i = 0; cborError = cbor_value_get_int( pCborValue, &i ); - /* TODO: assert no error on _createDecoderObject */ - - pDecoderObject->value.signedInt = i; + if( cborError == CborNoError ) + { + pDecoderObject->value.signedInt = i; + } + else + { + returnedError = IOT_SERIALIZER_INTERNAL_FAILURE; + } break; } @@ -245,13 +274,10 @@ static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject size_t maxSize ) { CborParser * pCborParser = IotSerializer_MallocCborParser( sizeof( CborParser ) ); - _cborValueWrapper_t * pCborValueWrapper = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + _cborValueWrapper_t cborValueWrapper = { .isOutermost = 0 }; - if( ( pCborParser == NULL ) || ( pCborValueWrapper == NULL ) ) + if( pCborParser == NULL ) { - IotSerializer_FreeCborParser( pCborParser ); - IotSerializer_FreeCborValue( pCborValueWrapper ); - return IOT_SERIALIZER_OUT_OF_MEMORY; } @@ -263,23 +289,28 @@ static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject maxSize, 0, pCborParser, - &pCborValueWrapper->cborValue ); + &cborValueWrapper.cborValue ); /* If init succeeds, create the decoder object. */ if( cborError == CborNoError ) { - pCborValueWrapper->isOutermost = true; + cborValueWrapper.isOutermost = true; - returnedError = _createDecoderObject( pCborValueWrapper, pDecoderObject ); + returnedError = _createDecoderObject( &cborValueWrapper, pDecoderObject ); } /* If there is any error or decoder object is a scalar type, free the cbor resources. */ - if( cborError || returnedError || !_isArrayOrMap( pDecoderObject->type ) ) + if( cborError || returnedError ) { /* pDecoderObject is untouched. */ IotSerializer_FreeCborParser( pCborParser ); - IotSerializer_FreeCborValue( pCborValueWrapper ); + + if( _isArrayOrMap( pDecoderObject->type ) && + ( pDecoderObject->pHandle != NULL ) ) + { + IotSerializer_FreeCborValue( pDecoderObject->pHandle ); + } } _translateErrorCode( cborError, &returnedError ); @@ -327,6 +358,8 @@ static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject const char * pKey, IotSerializerDecoderObject_t * pValueObject ) { + _cborValueWrapper_t newCborValueWrapper; + if( pDecoderObject->type != IOT_SERIALIZER_CONTAINER_MAP ) { return IOT_SERIALIZER_INVALID_INPUT; @@ -337,43 +370,31 @@ static IotSerializerError_t _find( IotSerializerDecoderObject_t * pDecoderObject _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); - _cborValueWrapper_t * pNewCborValueWrapper = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); - - if( pNewCborValueWrapper == NULL ) - { - return IOT_SERIALIZER_OUT_OF_MEMORY; - } - /* Set this object not to be outermost one. */ - pNewCborValueWrapper->isOutermost = false; + newCborValueWrapper.isOutermost = false; cborError = cbor_value_map_find_value( &pCborValueWrapper->cborValue, pKey, - &pNewCborValueWrapper->cborValue ); + &newCborValueWrapper.cborValue ); if( cborError == CborNoError ) { /* If not found the element in map. */ - if( pNewCborValueWrapper->cborValue.type == CborInvalidType ) + if( newCborValueWrapper.cborValue.type == CborInvalidType ) { /* pValueObject is untouched. */ + returnedError = IOT_SERIALIZER_NOT_FOUND; } else { - returnedError = _createDecoderObject( pNewCborValueWrapper, pValueObject ); + returnedError = _createDecoderObject( &newCborValueWrapper, pValueObject ); } } _translateErrorCode( cborError, &returnedError ); - /* If there is any error or decoder object is a scalar type, free the cbor resources. */ - if( returnedError || !_isArrayOrMap( pValueObject->type ) ) - { - IotSerializer_FreeCborValue( pNewCborValueWrapper ); - } - return returnedError; } diff --git a/platform/source/posix/linux/iot_metrics.c b/platform/source/posix/linux/iot_metrics.c index 6e8e49ca66..c749dbd8ad 100644 --- a/platform/source/posix/linux/iot_metrics.c +++ b/platform/source/posix/linux/iot_metrics.c @@ -22,12 +22,21 @@ /* The config header is always included first. */ #include "iot_config.h" +/* String include. */ +#include + /* Metrics include. */ #include "platform/iot_metrics.h" /* Platform threads include. */ #include "platform/iot_threads.h" +/* Network socket includes. */ +#include +#include +#include +#include + /* Compare function to identify the TCP connection id. */ static bool _tcpConnectionMatch( const IotLink_t * pLink, void * pId ); @@ -84,8 +93,27 @@ void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ) /* Copy TCP connection to the new one. */ *pNewTcpConnection = *pTcpConnection; - /* Insert to the list. */ - IotListDouble_InsertTail( &_connectionsList, &( pNewTcpConnection->link ) ); + /* Allocate memory for ip string. */ + pNewTcpConnection->pRemoteIp = IotMetrics_MallocIpAddress( IOT_METRICS_MAX_IP_STRING_LENGTH * sizeof( char ) ); + + if( pNewTcpConnection->pRemoteIp != NULL ) + { + /* Convert IP to string. */ + struct in_addr remoteIpAddr = { pNewTcpConnection->remoteIp }; + char * pConvertedIp = inet_ntoa( remoteIpAddr ); + strcpy( pNewTcpConnection->pRemoteIp, pConvertedIp ); + + /* Convert port and IP to host byte order. */ + pNewTcpConnection->remotePort = ntohs( pNewTcpConnection->remotePort ); + pNewTcpConnection->remoteIp = ntohl( pNewTcpConnection->remoteIp ); + + /* Insert to the list. */ + IotListDouble_InsertTail( &_connectionsList, &( pNewTcpConnection->link ) ); + } + else + { + IotMetrics_FreeTcpConnection( pNewTcpConnection ); + } } } @@ -105,7 +133,10 @@ void IotMetrics_RemoveTcpConnection( IotMetricsConnectionId_t tcpConnectionId ) if( pFoundConnectionLink != NULL ) { - IotMetrics_FreeTcpConnection( IotLink_Container( IotMetricsTcpConnection_t, pFoundConnectionLink, link ) ); + IotMetricsTcpConnection_t * pFoundTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, pFoundConnectionLink, link ); + + IotMetrics_FreeIpAddress( pFoundTcpConnection->pRemoteIp ); + IotMetrics_FreeTcpConnection(pFoundTcpConnection); } IotMutex_Unlock( &_mutex ); diff --git a/platform/source/posix/linux/iot_network_openssl_metrics.c b/platform/source/posix/linux/iot_network_openssl_metrics.c index 54505e5509..179cdfc89c 100644 --- a/platform/source/posix/linux/iot_network_openssl_metrics.c +++ b/platform/source/posix/linux/iot_network_openssl_metrics.c @@ -75,8 +75,7 @@ static IotNetworkError_t _metricsCreate( void * pConnectionInfo, struct sockaddr_in socketAddressIpv4; socklen_t socklen = sizeof( struct sockaddr_in ); - /* Initialize a tcp connection with socket as id. */ - IotMetricsTcpConnection_t connection = { .id = socket }; + IotMetricsTcpConnection_t connection; /* Get the ip and port from the peer socket. */ int ret = getpeername( socket, ( struct sockaddr * ) &socketAddressIpv4, &socklen ); @@ -85,11 +84,9 @@ static IotNetworkError_t _metricsCreate( void * pConnectionInfo, /* Assert its succees. */ IotMetrics_Assert( ret == 0 ); - /* Convert ip value from integer(network byte order) to string. */ - connection.pRemoteIP = inet_ntoa( socketAddressIpv4.sin_addr ); - - /* Convert port value from network byte order to host byte order. */ - connection.remotePort = ntohs( socketAddressIpv4.sin_port ); + connection.id = ( IotMetricsConnectionId_t ) socket; + connection.remotePort = socketAddressIpv4.sin_port; + connection.remoteIp = socketAddressIpv4.sin_addr.s_addr; /* Add this metircs, a established TCP connection. */ IotMetrics_AddTcpConnection( &connection ); @@ -112,7 +109,7 @@ static IotNetworkError_t _metricsClose( void * pConnection ) if( error == IOT_NETWORK_SUCCESS ) { /* Remove this metircs by its id. */ - IotMetrics_RemoveTcpConnection( socket ); + IotMetrics_RemoveTcpConnection( ( IotMetricsConnectionId_t ) socket ); } return error; diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index da73b348bf..75d8ea5ef1 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -851,6 +851,8 @@ static void _verifyTcpConections( int total, error = _Decoder.next( connIterator ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); + + _Decoder.destroy( &connMap ); } va_end( valist ); diff --git a/tests/iot_config.h b/tests/iot_config.h index b88921b6e1..3f09781249 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -22,8 +22,8 @@ /* This file contains configuration settings for the tests. Currently, the tests * must run on POSIX systems. */ -#ifndef _IOT_TESTS_CONFIG_H_ -#define _IOT_TESTS_CONFIG_H_ +#ifndef IOT_CONFIG_H_ +#define IOT_CONFIG_H_ /* Test framework include. */ #include "unity_fixture_malloc_overrides.h" @@ -73,14 +73,14 @@ #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) /* Metrics library configuration. */ -#define IOT_METRICS_ENABLE_ASSERTS ( 1 ) +#define IOT_METRICS_ENABLE_ASSERTS ( 1 ) /* Serializer library configuration. */ -#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) +#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) /* Defender library configuration. */ -#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) -#define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) +#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ @@ -92,15 +92,15 @@ /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ -#define IotThreads_Malloc unity_malloc_mt -#define IotThreads_Free unity_free_mt -#define IotNetwork_Malloc unity_malloc_mt -#define IotNetwork_Free unity_free_mt -#define IotLogging_Malloc unity_malloc_mt -#define IotLogging_Free unity_free_mt +#define IotThreads_Malloc unity_malloc_mt +#define IotThreads_Free unity_free_mt +#define IotNetwork_Malloc unity_malloc_mt +#define IotNetwork_Free unity_free_mt +#define IotLogging_Malloc unity_malloc_mt +#define IotLogging_Free unity_free_mt /* #define IotLogging_StaticBufferSize */ -#define IotTest_Malloc unity_malloc_mt -#define IotTest_Free unity_free_mt +#define IotTest_Malloc unity_malloc_mt +#define IotTest_Free unity_free_mt /* Memory allocation function configuration for libraries affected by * IOT_STATIC_MEMORY_ONLY. */ @@ -127,6 +127,8 @@ #define AwsIotShadow_FreeSubscription unity_free_mt #define IotMetrics_MallocTcpConnection unity_malloc_mt #define IotMetrics_FreeTcpConnection unity_free_mt + #define IotMetrics_MallocIpAddress unity_malloc_mt + #define IotMetrics_FreeIpAddress unity_free_mt #define IotSerializer_MallocCborEncoder unity_malloc_mt #define IotSerializer_FreeCborEncoder unity_free_mt #define IotSerializer_MallocCborParser unity_malloc_mt @@ -174,8 +176,11 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; #define IotTestNetwork_Init IotNetworkOpenssl_Init #define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup +/* This is supposed to be defined as the socket data type. In linux, it is "int". */ +#define IotMetricsConnectionId_t int + /* Macro for placing inline assembly in test code. */ -#define IOT_TEST_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) +#define IOT_TEST_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) #ifndef __GNUC__ #error "Unsupported compiler. Only gcc and clang are supported." @@ -189,7 +194,7 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; /* Define a custom logging puts function. This function allows coverage * testing of logging functions, but prevents excessive logs from being * printed. */ - #define IotLogging_Puts _coveragePuts + #define IotLogging_Puts _coveragePuts /* Includes for coverage logging puts. */ #include @@ -198,7 +203,7 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; /* Logging output function that only prints messages from demo executables. * May be unused, hence the gcc unused attribute (not portable!) */ - static int __attribute__ ( ( unused ) ) _coveragePuts( const char * pMessage ) + static int __attribute__( ( unused ) ) _coveragePuts( const char * pMessage ) { bool printMessage = false; @@ -233,10 +238,10 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; /* Puts should return a nonzero value. */ return 1; } -#endif +#endif /* if IOT_TEST_COVERAGE == 1 */ /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ #include IOT_SYSTEM_TYPES_FILE -#endif /* ifndef _IOT_TESTS_CONFIG_H_ */ +#endif /* ifndef IOT_CONFIG_H_ */ From 4dfeabce2e133994387fef87b2cf083882790181 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 16 Apr 2019 11:03:21 -0700 Subject: [PATCH 098/844] Remove anonymous structs and unions from MQTT (#376) --- demos/source/aws_iot_demo_shadow.c | 4 +- demos/source/iot_demo_mqtt.c | 34 +-- lib/include/private/iot_mqtt_internal.h | 8 +- lib/include/types/iot_mqtt_types.h | 6 +- lib/source/common/iot_init.c | 5 - lib/source/defender/aws_iot_defender_api.c | 12 +- lib/source/mqtt/iot_mqtt_api.c | 92 ++++---- lib/source/mqtt/iot_mqtt_network.c | 26 +-- lib/source/mqtt/iot_mqtt_operation.c | 200 +++++++++--------- lib/source/mqtt/iot_mqtt_serialize.c | 4 +- lib/source/mqtt/iot_mqtt_subscription.c | 8 +- lib/source/mqtt/iot_mqtt_validate.c | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 4 +- lib/source/shadow/aws_iot_shadow_operation.c | 22 +- platform/source/posix/CMakeLists.txt | 3 +- tests/defender/aws_iot_tests_defender_api.c | 4 +- tests/mqtt/system/iot_tests_mqtt_stress.c | 16 +- tests/mqtt/system/iot_tests_mqtt_system.c | 18 +- tests/mqtt/unit/iot_tests_mqtt_api.c | 22 +- tests/mqtt/unit/iot_tests_mqtt_receive.c | 64 +++--- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 40 ++-- tests/mqtt/unit/iot_tests_mqtt_validate.c | 8 +- .../system/aws_iot_tests_shadow_system.c | 4 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 4 +- 24 files changed, 305 insertions(+), 305 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 8f4cc4dcd1..9fcefd04af 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -528,8 +528,8 @@ static int _establishMqttConnection( const char * pIdentifier, /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ networkInfo.createNetworkConnection = true; - networkInfo.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; networkInfo.pNetworkInterface = pNetworkInterface; /* Set the members of the connection info not set by the initializer. */ diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index 0e845044e6..493011282f 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -206,18 +206,18 @@ static void _operationCompleteCallback( void * param1, /* Print the status of the completed operation. A PUBLISH operation is * successful when transmitted over the network. */ - if( pOperation->operation.result == IOT_MQTT_SUCCESS ) + if( pOperation->u.operation.result == IOT_MQTT_SUCCESS ) { IotLogInfo( "MQTT %s %d successfully sent.", - IotMqtt_OperationType( pOperation->operation.type ), + IotMqtt_OperationType( pOperation->u.operation.type ), ( int ) publishCount ); } else { IotLogError( "MQTT %s %d could not be sent. Error %s.", - IotMqtt_OperationType( pOperation->operation.type ), + IotMqtt_OperationType( pOperation->u.operation.type ), ( int ) publishCount, - IotMqtt_strerror( pOperation->operation.result ) ); + IotMqtt_strerror( pOperation->u.operation.result ) ); } } @@ -240,7 +240,7 @@ static void _mqttSubscriptionCallback( void * param1, int acknowledgementLength = 0; size_t messageNumberIndex = 0, messageNumberLength = 1; IotSemaphore_t * pPublishesReceived = ( IotSemaphore_t * ) param1; - const char * pPayload = pPublish->message.info.pPayload; + const char * pPayload = pPublish->u.message.info.pPayload; char pAcknowledgementMessage[ _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; IotMqttPublishInfo_t acknowledgementInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; @@ -251,17 +251,17 @@ static void _mqttSubscriptionCallback( void * param1, "Publish retain flag: %d\r\n" "Publish QoS: %d\r\n" "Publish payload: %.*s", - pPublish->message.topicFilterLength, - pPublish->message.pTopicFilter, - pPublish->message.info.topicNameLength, - pPublish->message.info.pTopicName, - pPublish->message.info.retain, - pPublish->message.info.qos, - pPublish->message.info.payloadLength, + pPublish->u.message.topicFilterLength, + pPublish->u.message.pTopicFilter, + pPublish->u.message.info.topicNameLength, + pPublish->u.message.info.pTopicName, + pPublish->u.message.info.retain, + pPublish->u.message.info.qos, + pPublish->u.message.info.payloadLength, pPayload ); /* Find the message number inside of the PUBLISH message. */ - for( messageNumberIndex = 0; messageNumberIndex < pPublish->message.info.payloadLength; messageNumberIndex++ ) + for( messageNumberIndex = 0; messageNumberIndex < pPublish->u.message.info.payloadLength; messageNumberIndex++ ) { /* The payload that was published contained ASCII characters, so find * beginning of the message number by checking for ASCII digits. */ @@ -273,10 +273,10 @@ static void _mqttSubscriptionCallback( void * param1, } /* Check that a message number was found within the PUBLISH payload. */ - if( messageNumberIndex < pPublish->message.info.payloadLength ) + if( messageNumberIndex < pPublish->u.message.info.payloadLength ) { /* Compute the length of the message number. */ - while( ( messageNumberIndex + messageNumberLength < pPublish->message.info.payloadLength ) && + while( ( messageNumberIndex + messageNumberLength < pPublish->u.message.info.payloadLength ) && ( *( pPayload + messageNumberIndex + messageNumberLength ) >= '0' ) && ( *( pPayload + messageNumberIndex + messageNumberLength ) <= '9' ) ) { @@ -406,8 +406,8 @@ static int _establishMqttConnection( bool awsIotMqttMode, /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ networkInfo.createNetworkConnection = true; - networkInfo.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; networkInfo.pNetworkInterface = pNetworkInterface; /* Set the members of the connection info not set by the initializer. */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index c217cbdd14..0884441442 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -369,15 +369,15 @@ typedef struct _mqttOperation uint32_t limit; uint32_t nextPeriod; } retry; - }; + } operation; /* If incomingPublish is true, this struct is valid. */ struct { IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ - }; - }; + } publish; + } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ } _mqttOperation_t; /** @@ -401,7 +401,7 @@ typedef struct _mqttPacket * when deserializing PUBLISHes. */ _mqttOperation_t * pIncomingPublish; - }; + } u; /**< @brief Valid member depends on packet being decoded. */ uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 7f5a4f6251..6d2032b2a5 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -459,7 +459,7 @@ typedef struct IotMqttCallbackParam /* Valid when a connection is disconnected. */ IotMqttDisconnectReason_t disconnectReason; /**< @brief Why the MQTT connection was disconnected. */ - }; + } u /**< @brief Valid member depends on callback type. */; } IotMqttCallbackParam_t; /** @@ -953,7 +953,7 @@ typedef struct IotMqttNetworkInfo * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ void * pNetworkCredentialInfo; - }; + } setup; /** * @brief An established transport-layer network connection. @@ -963,7 +963,7 @@ typedef struct IotMqttNetworkInfo * valid when #IotMqttNetworkInfo_t::createNetworkConnection is `false`. */ void * pNetworkConnection; - }; + } u /**< @brief Valid member depends of IotMqttNetworkInfo_t.createNetworkConnection. */; /** * @brief The network functions used by the new MQTT connection. diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index d912311588..909e78d38e 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -60,11 +60,6 @@ bool IotSdk_Init( void ) IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; - if( status == true ) - { - status = IotMetrics_Init(); - } - /* Initialize static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 bool staticMemoryInitialized = IotStaticMemory_Init(); diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 95418e9f57..8654706f75 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -490,7 +490,7 @@ void _acceptCallback( void * pArgument, /* In accepted case, report and MQTT message must exist. */ AwsIotDefender_Assert( AwsIotDefenderInternal_GetReportBuffer() ); - AwsIotDefender_Assert( pPublish->message.info.pPayload ); + AwsIotDefender_Assert( pPublish->u.message.info.pPayload ); /* Invoke user's callback with accept event. */ AwsIotDefenderCallbackInfo_t callbackInfo; @@ -502,8 +502,8 @@ void _acceptCallback( void * pArgument, callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); - callbackInfo.pPayload = pPublish->message.info.pPayload; - callbackInfo.payloadLength = pPublish->message.info.payloadLength; + callbackInfo.pPayload = pPublish->u.message.info.pPayload; + callbackInfo.payloadLength = pPublish->u.message.info.payloadLength; _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); } @@ -519,7 +519,7 @@ void _rejectCallback( void * pArgument, IotLogError( "Metrics report was rejected by defender service." ); /* In rejected case, MQTT message must exist. */ - AwsIotDefender_Assert( pPublish->message.info.pPayload ); + AwsIotDefender_Assert( pPublish->u.message.info.pPayload ); /* Invoke user's callback with rejected event. */ AwsIotDefenderCallbackInfo_t callbackInfo; @@ -531,8 +531,8 @@ void _rejectCallback( void * pArgument, callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); - callbackInfo.pPayload = pPublish->message.info.pPayload; - callbackInfo.payloadLength = pPublish->message.info.payloadLength; + callbackInfo.pPayload = pPublish->u.message.info.pPayload; + callbackInfo.payloadLength = pPublish->u.message.info.payloadLength; _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); } diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 5216cf38c3..5f3f155f41 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -672,16 +672,16 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Check the subscription operation data and set the operation type. */ - IotMqtt_Assert( pSubscriptionOperation->status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( pSubscriptionOperation->retry.limit == 0 ); - pSubscriptionOperation->type = operation; + IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.retry.limit == 0 ); + pSubscriptionOperation->u.operation.type = operation; /* Generate a subscription packet from the subscription list. */ status = serializeSubscription( pSubscriptionList, subscriptionCount, - &( pSubscriptionOperation->pMqttPacket ), - &( pSubscriptionOperation->packetSize ), - &( pSubscriptionOperation->packetIdentifier ) ); + &( pSubscriptionOperation->u.operation.pMqttPacket ), + &( pSubscriptionOperation->u.operation.packetSize ), + &( pSubscriptionOperation->u.operation.packetIdentifier ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -689,14 +689,14 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pSubscriptionOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pSubscriptionOperation->packetSize > 0 ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); /* Add the subscription list for a SUBSCRIBE. */ if( operation == IOT_MQTT_SUBSCRIBE ) { status = _IotMqtt_AddSubscriptions( mqttConnection, - pSubscriptionOperation->packetIdentifier, + pSubscriptionOperation->u.operation.packetIdentifier, pSubscriptionList, subscriptionCount ); @@ -726,7 +726,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( operation == IOT_MQTT_SUBSCRIBE ) { _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, - pSubscriptionOperation->packetIdentifier, + pSubscriptionOperation->u.operation.packetIdentifier, -1 ); } @@ -985,8 +985,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, * network connection. */ if( pNetworkInfo->createNetworkConnection == true ) { - networkStatus = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->pNetworkServerInfo, - pNetworkInfo->pNetworkCredentialInfo, + networkStatus = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->u.setup.pNetworkServerInfo, + pNetworkInfo->u.setup.pNetworkCredentialInfo, &pNetworkConnection ); if( networkStatus == IOT_NETWORK_SUCCESS ) @@ -1004,7 +1004,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - pNetworkConnection = pNetworkInfo->pNetworkConnection; + pNetworkConnection = pNetworkInfo->u.pNetworkConnection; networkCreated = true; } @@ -1064,13 +1064,13 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Ensure the members set by operation creation and serialization * are appropriate for a blocking CONNECT. */ - IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->retry.limit == 0 ); + IotMqtt_Assert( pOperation->u.operation.retry.limit == 0 ); /* Set the operation type. */ - pOperation->type = IOT_MQTT_CONNECT; + pOperation->u.operation.type = IOT_MQTT_CONNECT; /* Add previous session subscriptions. */ if( pConnectInfo->pPreviousSubscriptions != NULL ) @@ -1118,8 +1118,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ status = serializeConnect( pConnectInfo, - &( pOperation->pMqttPacket ), - &( pOperation->packetSize ) ); + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -1131,8 +1131,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->packetSize > 0 ); + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); /* Add the CONNECT operation to the send queue for network transmission. */ status = _IotMqtt_ScheduleOperation( pOperation, @@ -1273,13 +1273,13 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { /* Ensure that the members set by operation creation and serialization * are appropriate for a blocking DISCONNECT. */ - IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->retry.limit == 0 ); + IotMqtt_Assert( pOperation->u.operation.retry.limit == 0 ); /* Set the operation type. */ - pOperation->type = IOT_MQTT_DISCONNECT; + pOperation->u.operation.type = IOT_MQTT_DISCONNECT; /* Choose a disconnect serializer. */ IotMqttError_t ( * serializeDisconnect )( uint8_t **, @@ -1304,8 +1304,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Generate a DISCONNECT packet. */ - status = serializeDisconnect( &( pOperation->pMqttPacket ), - &( pOperation->packetSize ) ); + status = serializeDisconnect( &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); } else { @@ -1315,8 +1315,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, if( status == IOT_MQTT_SUCCESS ) { /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->packetSize > 0 ); + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); /* Schedule the DISCONNECT operation for network transmission. */ if( _IotMqtt_ScheduleOperation( pOperation, @@ -1601,8 +1601,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } /* Check the PUBLISH operation data and set the operation type. */ - IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); - pOperation->type = IOT_MQTT_PUBLISH_TO_SERVER; + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; /* Choose a PUBLISH serializer function. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 @@ -1626,7 +1626,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ if( mqttConnection->awsIotMqttMode == true ) { - pPacketIdentifierHigh = &( pOperation->pPacketIdentifierHigh ); + pPacketIdentifierHigh = &( pOperation->u.operation.pPacketIdentifierHigh ); } else { @@ -1635,9 +1635,9 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* Generate a PUBLISH packet from pPublishInfo. */ status = serializePublish( pPublishInfo, - &( pOperation->pMqttPacket ), - &( pOperation->packetSize ), - &( pOperation->packetIdentifier ), + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ), + &( pOperation->u.operation.packetIdentifier ), pPacketIdentifierHigh ); if( status != IOT_MQTT_SUCCESS ) @@ -1650,8 +1650,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->packetSize > 0 ); + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); /* Initialize PUBLISH retry if retryLimit is set. */ if( pPublishInfo->retryLimit > 0 ) @@ -1659,8 +1659,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* A QoS 0 PUBLISH may not be retried. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - pOperation->retry.limit = pPublishInfo->retryLimit; - pOperation->retry.nextPeriod = pPublishInfo->retryMs; + pOperation->u.operation.retry.limit = pPublishInfo->retryLimit; + pOperation->u.operation.retry.nextPeriod = pPublishInfo->retryMs; } else { @@ -1836,7 +1836,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, IotLogError( "(MQTT connection %p, %s operation %p) MQTT connection is closed. " "Operation cannot be waited on.", pMqttConnection, - IotMqtt_OperationType( operation->type ), + IotMqtt_OperationType( operation->u.operation.type ), operation ); status = IOT_MQTT_NETWORK_ERROR; @@ -1845,7 +1845,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, { IotLogInfo( "(MQTT connection %p, %s operation %p) Waiting for operation completion.", pMqttConnection, - IotMqtt_OperationType( operation->type ), + IotMqtt_OperationType( operation->u.operation.type ), operation ); } @@ -1854,7 +1854,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /* Only wait on an operation if the MQTT connection is active. */ if( status == IOT_MQTT_SUCCESS ) { - if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), + if( IotSemaphore_TimedWait( &( operation->u.operation.notify.waitSemaphore ), timeoutMs ) == false ) { status = IOT_MQTT_TIMEOUT; @@ -1863,7 +1863,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, ( void ) _IotMqtt_DecrementOperationReferences( operation, true ); /* Clean up lingering subscriptions from a timed-out SUBSCRIBE. */ - if( operation->type == IOT_MQTT_SUBSCRIBE ) + if( operation->u.operation.type == IOT_MQTT_SUBSCRIBE ) { IotLogDebug( "(MQTT connection %p, SUBSCRIBE operation %p) Cleaning up" " subscriptions of timed-out SUBSCRIBE.", @@ -1871,7 +1871,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, operation ); _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, - operation->packetIdentifier, + operation->u.operation.packetIdentifier, -1 ); } else @@ -1882,12 +1882,12 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, else { /* Retrieve the status of the completed operation. */ - status = operation->status; + status = operation->u.operation.status; } IotLogInfo( "(MQTT connection %p, %s operation %p) Wait complete with result %s.", pMqttConnection, - IotMqtt_OperationType( operation->type ), + IotMqtt_OperationType( operation->u.operation.type ), operation, IotMqtt_strerror( status ) ); } diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 050124ff81..6baf02a7fb 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -297,7 +297,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { - pOperation->status = status; + pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation, true ); } else @@ -326,7 +326,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); pOperation->incomingPublish = true; pOperation->pMqttConnection = pMqttConnection; - pIncomingPacket->pIncomingPublish = pOperation; + pIncomingPacket->u.pIncomingPublish = pOperation; } /* Choose a PUBLISH deserializer. */ @@ -356,7 +356,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( status == IOT_MQTT_SUCCESS ) { /* Send a PUBACK for QoS 1 PUBLISH. */ - if( pOperation->publishInfo.qos == IOT_MQTT_QOS_1 ) + if( pOperation->u.publish.publishInfo.qos == IOT_MQTT_QOS_1 ) { _sendPuback( pMqttConnection, pIncomingPacket->packetIdentifier ); } @@ -366,7 +366,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ - pOperation->pReceivedData = pIncomingPacket->pRemainingData; + pOperation->u.publish.pReceivedData = pIncomingPacket->pRemainingData; pIncomingPacket->pRemainingData = NULL; /* Add the PUBLISH to the list of operations pending processing. */ @@ -396,11 +396,11 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( status != IOT_MQTT_SUCCESS ) { /* Check ownership of the received MQTT packet. */ - if( pOperation->pReceivedData != NULL ) + if( pOperation->u.publish.pReceivedData != NULL ) { /* Retrieve the pointer MQTT packet pointer so it may be freed later. */ IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); - pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->pReceivedData; + pIncomingPacket->pRemainingData = ( uint8_t * ) pOperation->u.publish.pReceivedData; } else { @@ -463,7 +463,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { - pOperation->status = status; + pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation, true ); } else @@ -498,7 +498,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Deserialize SUBACK and notify of result. */ - pIncomingPacket->pMqttConnection = pMqttConnection; + pIncomingPacket->u.pMqttConnection = pMqttConnection; status = deserialize( pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_SUBSCRIBE, @@ -506,7 +506,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { - pOperation->status = status; + pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation, true ); } else @@ -548,7 +548,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { - pOperation->status = status; + pOperation->u.operation.status = status; _IotMqtt_Notify( pOperation, true ); } else @@ -756,7 +756,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason { IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; - IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -842,7 +842,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason { /* Set the members of the callback parameter. */ callbackParam.mqttConnection = pMqttConnection; - callbackParam.disconnectReason = disconnectReason; + callbackParam.u.disconnectReason = disconnectReason; pMqttConnection->disconnectCallback.function( pMqttConnection->disconnectCallback.pCallbackContext, &callbackParam ); @@ -859,7 +859,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, void * pReceiveContext ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttPacket_t incomingPacket = { .pMqttConnection = NULL }; + _mqttPacket_t incomingPacket = { .u.pMqttConnection = NULL }; /* Cast context to correct type. */ _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pReceiveContext; diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 6a70d5e659..a241645153 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -104,7 +104,7 @@ static bool _mqttOperation_match( const IotLink_t * pOperationLink, _operationMatchParam_t * pParam = ( _operationMatchParam_t * ) pMatch; /* Check for matching operations. */ - if( pParam->type == pOperation->type ) + if( pParam->type == pOperation->u.operation.type ) { /* Check for matching packet identifiers. */ if( pParam->pPacketIdentifier == NULL ) @@ -113,7 +113,7 @@ static bool _mqttOperation_match( const IotLink_t * pOperationLink, } else { - match = ( *( pParam->pPacketIdentifier ) == pOperation->packetIdentifier ); + match = ( *( pParam->pPacketIdentifier ) == pOperation->u.operation.packetIdentifier ); } } else @@ -155,29 +155,29 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Only PUBLISH may be retried. */ - IotMqtt_Assert( pOperation->type == IOT_MQTT_PUBLISH_TO_SERVER ); + IotMqtt_Assert( pOperation->u.operation.type == IOT_MQTT_PUBLISH_TO_SERVER ); /* Check if the retry limit is exhausted. */ - if( pOperation->retry.count > pOperation->retry.limit ) + if( pOperation->u.operation.retry.count > pOperation->u.operation.retry.limit ) { /* The retry count may be at most one more than the retry limit, which * accounts for the final check for a PUBACK. */ - IotMqtt_Assert( pOperation->retry.count == pOperation->retry.limit + 1 ); + IotMqtt_Assert( pOperation->u.operation.retry.count == pOperation->u.operation.retry.limit + 1 ); IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", pMqttConnection, pOperation, - pOperation->retry.limit ); + pOperation->u.operation.retry.limit ); status = false; } /* Check if this is the first retry. */ - else if( pOperation->retry.count == 1 ) + else if( pOperation->u.operation.retry.count == 1 ) { /* Always set the DUP flag on the first retry. */ - publishSetDup( pOperation->pMqttPacket, - pOperation->pPacketIdentifierHigh, - &( pOperation->packetIdentifier ) ); + publishSetDup( pOperation->u.operation.pMqttPacket, + pOperation->u.operation.pPacketIdentifierHigh, + &( pOperation->u.operation.packetIdentifier ) ); } else { @@ -185,9 +185,9 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) * identifier) must be reset on every retry. */ if( pMqttConnection->awsIotMqttMode == true ) { - publishSetDup( pOperation->pMqttPacket, - pOperation->pPacketIdentifierHigh, - &( pOperation->packetIdentifier ) ); + publishSetDup( pOperation->u.operation.pMqttPacket, + pOperation->u.operation.pPacketIdentifierHigh, + &( pOperation->u.operation.packetIdentifier ) ); } else { @@ -209,14 +209,14 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) /* This function should never be called with retry count greater than * retry limit. */ - IotMqtt_Assert( pOperation->retry.count <= pOperation->retry.limit ); + IotMqtt_Assert( pOperation->u.operation.retry.count <= pOperation->u.operation.retry.limit ); /* Increment the retry count. */ - ( pOperation->retry.count )++; + ( pOperation->u.operation.retry.count )++; /* Check for a response shortly for the final retry. Otherwise, calculate the * next retry period. */ - if( pOperation->retry.count > pOperation->retry.limit ) + if( pOperation->u.operation.retry.count > pOperation->u.operation.retry.limit ) { scheduleDelay = IOT_MQTT_RESPONSE_WAIT_MS; @@ -228,14 +228,14 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) } else { - scheduleDelay = pOperation->retry.nextPeriod; + scheduleDelay = pOperation->u.operation.retry.nextPeriod; /* Double the retry peiod, subject to a ceiling value. */ - pOperation->retry.nextPeriod *= 2; + pOperation->u.operation.retry.nextPeriod *= 2; - if( pOperation->retry.nextPeriod > IOT_MQTT_RETRY_MS_CEILING ) + if( pOperation->u.operation.retry.nextPeriod > IOT_MQTT_RETRY_MS_CEILING ) { - pOperation->retry.nextPeriod = IOT_MQTT_RETRY_MS_CEILING; + pOperation->u.operation.retry.nextPeriod = IOT_MQTT_RETRY_MS_CEILING; } else { @@ -245,12 +245,12 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", pMqttConnection, pOperation, - ( unsigned long ) pOperation->retry.count, - ( unsigned long ) pOperation->retry.limit, + ( unsigned long ) pOperation->u.operation.retry.count, + ( unsigned long ) pOperation->u.operation.retry.limit, ( unsigned long ) scheduleDelay ); /* Check if this is the first retry. */ - firstRetry = ( pOperation->retry.count == 1 ); + firstRetry = ( pOperation->u.operation.retry.count == 1 ); /* On the first retry, the PUBLISH will be moved from the pending processing * list to the pending responses list. Lock the connection references mutex @@ -373,9 +373,9 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, /* Initialize the some members of the new operation. */ pOperation->pMqttConnection = pMqttConnection; - pOperation->jobReference = 1; - pOperation->flags = flags; - pOperation->status = IOT_MQTT_STATUS_PENDING; + pOperation->u.operation.jobReference = 1; + pOperation->u.operation.flags = flags; + pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; } /* Check if the waitable flag is set. If it is, create a semaphore to @@ -383,7 +383,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, if( waitable == true ) { /* Create a semaphore to wait on for a waitable operation. */ - if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + if( IotSemaphore_Create( &( pOperation->u.operation.notify.waitSemaphore ), 0, 1 ) == false ) { IotLogError( "(MQTT connection %p) Failed to create semaphore for " "waitable operation.", @@ -395,7 +395,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { /* A waitable operation is created with an additional reference for the * Wait function. */ - ( pOperation->jobReference )++; + ( pOperation->u.operation.jobReference )++; } } else @@ -404,7 +404,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, * information. */ if( pCallbackInfo != NULL ) { - pOperation->notify.callback = *pCallbackInfo; + pOperation->u.operation.notify.callback = *pCallbackInfo; } else { @@ -473,7 +473,7 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, { IotLogDebug( "(MQTT connection %p, %s operation %p) Job canceled.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } else @@ -490,22 +490,22 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pOperation->jobReference--; + pOperation->u.operation.jobReference--; IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed" " from %ld to %ld.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation, - pOperation->jobReference + 1, - pOperation->jobReference ); + pOperation->u.operation.jobReference + 1, + pOperation->u.operation.jobReference ); /* The job reference count must be 0 or 1 after the decrement. */ - IotMqtt_Assert( ( pOperation->jobReference == 0 ) || - ( pOperation->jobReference == 1 ) ); + IotMqtt_Assert( ( pOperation->u.operation.jobReference == 0 ) || + ( pOperation->u.operation.jobReference == 1 ) ); /* This operation may be destroyed if its reference count is 0. */ - if( pOperation->jobReference == 0 ) + if( pOperation->u.operation.jobReference == 0 ) { destroyOperation = true; } @@ -536,12 +536,12 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, IotLogDebug( "(MQTT connection %p, %s operation %p) Destroying operation.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); /* The job reference count must be between 0 and 2. */ - IotMqtt_Assert( ( pOperation->jobReference >= 0 ) && - ( pOperation->jobReference <= 2 ) ); + IotMqtt_Assert( ( pOperation->u.operation.jobReference >= 0 ) && + ( pOperation->u.operation.jobReference <= 2 ) ); /* Jobs to be destroyed should be removed from the MQTT connection's * lists. */ @@ -551,7 +551,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, { IotLogDebug( "(MQTT connection %p, %s operation %p) Removed operation from connection lists.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation, pMqttConnection ); @@ -561,14 +561,14 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, { IotLogDebug( "(MQTT connection %p, %s operation %p) Operation was not present in connection lists.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); /* Free any allocated MQTT packet. */ - if( pOperation->pMqttPacket != NULL ) + if( pOperation->u.operation.pMqttPacket != NULL ) { #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 if( pMqttConnection->pSerializer != NULL ) @@ -588,29 +588,29 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - freePacket( pOperation->pMqttPacket ); + freePacket( pOperation->u.operation.pMqttPacket ); IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } else { IotLogDebug( "(MQTT connection %p, %s operation %p) Operation has no allocated MQTT packet.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } /* Check if a wait semaphore was created for this operation. */ - if( ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) + if( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ) { - IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( pOperation->u.operation.notify.waitSemaphore ) ); IotLogDebug( "(MQTT connection %p, %s operation %p) Wait semaphore destroyed.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } else @@ -620,7 +620,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); /* Free the memory used to hold operation data. */ @@ -768,7 +768,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, void * pContext ) { _mqttOperation_t * pOperation = pContext; - IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; /* Check parameters. The task pool and job parameter is not used when asserts * are disabled. */ @@ -793,15 +793,15 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, IotMutex_Unlock( &( pOperation->pMqttConnection->referencesMutex ) ); /* Process the current PUBLISH. */ - callbackParam.message.info = pOperation->publishInfo; + callbackParam.u.message.info = pOperation->u.publish.publishInfo; _IotMqtt_InvokeSubscriptionCallback( pOperation->pMqttConnection, &callbackParam ); /* Free any buffers associated with the current PUBLISH message. */ - if( pOperation->pReceivedData != NULL ) + if( pOperation->u.publish.pReceivedData != NULL ) { - IotMqtt_FreeMessage( ( void * ) pOperation->pReceivedData ); + IotMqtt_FreeMessage( ( void * ) pOperation->u.publish.pReceivedData ); } else { @@ -831,19 +831,19 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, IotMqtt_Assert( pSendJob == &( pOperation->job ) ); /* The given operation must have an allocated packet and be waiting for a status. */ - IotMqtt_Assert( pOperation->pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->packetSize != 0 ); - IotMqtt_Assert( pOperation->status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize != 0 ); + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); /* Check if this operation is waitable. */ - waitable = ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; /* Check PUBLISH retry counts and limits. */ - if( pOperation->retry.limit > 0 ) + if( pOperation->u.operation.retry.limit > 0 ) { if( _checkRetryLimit( pOperation ) == false ) { - pOperation->status = IOT_MQTT_RETRY_NO_RESPONSE; + pOperation->u.operation.status = IOT_MQTT_RETRY_NO_RESPONSE; } else { @@ -856,40 +856,40 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } /* Send an operation that is waiting for a response. */ - if( pOperation->status == IOT_MQTT_STATUS_PENDING ) + if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) { IotLogDebug( "(MQTT connection %p, %s operation %p) Sending MQTT packet.", pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); /* Transmit the MQTT packet from the operation over the network. */ bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, - pOperation->pMqttPacket, - pOperation->packetSize ); + pOperation->u.operation.pMqttPacket, + pOperation->u.operation.packetSize ); /* Check transmission status. */ - if( bytesSent != pOperation->packetSize ) + if( bytesSent != pOperation->u.operation.packetSize ) { - pOperation->status = IOT_MQTT_NETWORK_ERROR; + pOperation->u.operation.status = IOT_MQTT_NETWORK_ERROR; } else { /* DISCONNECT operations are considered successful upon successful * transmission. In addition, non-waitable operations with no callback * may also be considered successful. */ - if( pOperation->type == IOT_MQTT_DISCONNECT ) + if( pOperation->u.operation.type == IOT_MQTT_DISCONNECT ) { /* DISCONNECT operations are always waitable. */ IotMqtt_Assert( waitable == true ); - pOperation->status = IOT_MQTT_SUCCESS; + pOperation->u.operation.status = IOT_MQTT_SUCCESS; } else if( waitable == false ) { - if( pOperation->notify.callback.function == NULL ) + if( pOperation->u.operation.notify.callback.function == NULL ) { - pOperation->status = IOT_MQTT_SUCCESS; + pOperation->u.operation.status = IOT_MQTT_SUCCESS; } else { @@ -908,14 +908,14 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } /* Check if this operation requires further processing. */ - if( pOperation->status == IOT_MQTT_STATUS_PENDING ) + if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) { /* Check if this operation should be scheduled for retransmission. */ - if( pOperation->retry.limit > 0 ) + if( pOperation->u.operation.retry.limit > 0 ) { if( _scheduleNextRetry( pOperation ) == false ) { - pOperation->status = IOT_MQTT_SCHEDULING_ERROR; + pOperation->u.operation.status = IOT_MQTT_SCHEDULING_ERROR; } else { @@ -978,7 +978,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, if( networkPending == false ) { /* Notify of operation completion if this job set a status. */ - if( pOperation->status != IOT_MQTT_STATUS_PENDING ) + if( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ) { _IotMqtt_Notify( pOperation, false ); } @@ -1001,7 +1001,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, void * pContext ) { _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; - IotMqttCallbackParam_t callbackParam = { .operation = { 0 } }; + IotMqttCallbackParam_t callbackParam = { .u.operation = { 0 } }; /* Check parameters. The task pool and job parameter is not used when asserts * are disabled. */ @@ -1011,17 +1011,17 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, IotMqtt_Assert( pOperationJob == &( pOperation->job ) ); /* The operation's callback function and status must be set. */ - IotMqtt_Assert( pOperation->notify.callback.function != NULL ); - IotMqtt_Assert( pOperation->status != IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( pOperation->u.operation.notify.callback.function != NULL ); + IotMqtt_Assert( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ); callbackParam.mqttConnection = pOperation->pMqttConnection; - callbackParam.operation.type = pOperation->type; - callbackParam.operation.reference = pOperation; - callbackParam.operation.result = pOperation->status; + callbackParam.u.operation.type = pOperation->u.operation.type; + callbackParam.u.operation.reference = pOperation; + callbackParam.u.operation.result = pOperation->u.operation.status; /* Invoke the user callback function. */ - pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, - &callbackParam ); + pOperation->u.operation.notify.callback.function( pOperation->u.operation.notify.callback.pCallbackContext, + &callbackParam ); /* Attempt to destroy the operation once the user callback returns. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) @@ -1067,7 +1067,7 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule operation job, error %s.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation, IotTaskPool_strerror( taskPoolStatus ) ); @@ -1124,11 +1124,11 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { /* Get operation pointer and check if it is waitable. */ pResult = IotLink_Container( _mqttOperation_t, pResultLink, link ); - waitable = ( pResult->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + waitable = ( pResult->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; /* Check if the matched operation is a PUBLISH with retry. If it is, cancel * the retry job. */ - if( pResult->retry.limit > 0 ) + if( pResult->u.operation.retry.limit > 0 ) { taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &( pResult->job ), @@ -1144,27 +1144,27 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, { /* Check job reference counts. A waitable operation should have a * count of 2; a non-waitable operation should have a count of 1. */ - IotMqtt_Assert( pResult->jobReference == ( 1 + ( waitable == true ) ) ); + IotMqtt_Assert( pResult->u.operation.jobReference == ( 1 + ( waitable == true ) ) ); } } else { /* An operation with no retry in the pending responses list should * always have a job reference of 1. */ - IotMqtt_Assert( pResult->jobReference == 1 ); + IotMqtt_Assert( pResult->u.operation.jobReference == 1 ); /* Increment job references of a waitable operation to prevent Wait from * destroying this operation if it times out. */ if( waitable == true ) { - ( pResult->jobReference )++; + ( pResult->u.operation.jobReference )++; IotLogDebug( "(MQTT connection %p, %s operation %p) Job reference changed from %ld to %ld.", pMqttConnection, IotMqtt_OperationType( type ), pResult, - ( long int ) ( pResult->jobReference - 1 ), - ( long int ) ( pResult->jobReference ) ); + ( long int ) ( pResult->u.operation.jobReference - 1 ), + ( long int ) ( pResult->u.operation.jobReference ) ); } } } @@ -1200,13 +1200,13 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation, _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; /* Check if operation is waitable. */ - bool waitable = ( pOperation->flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; + bool waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; /* Remove any lingering subscriptions if a SUBSCRIBE failed. Rejected * subscriptions are removed by the deserializer, so not removed here. */ - if( pOperation->type == IOT_MQTT_SUBSCRIBE ) + if( pOperation->u.operation.type == IOT_MQTT_SUBSCRIBE ) { - switch( pOperation->status ) + switch( pOperation->u.operation.status ) { case IOT_MQTT_SUCCESS: break; @@ -1216,7 +1216,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation, default: _IotMqtt_RemoveSubscriptionByPacket( pOperation->pMqttConnection, - pOperation->packetIdentifier, + pOperation->u.operation.packetIdentifier, -1 ); break; } @@ -1229,21 +1229,21 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation, /* For a waitable operation, post to the wait semaphore. */ if( waitable == true ) { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " "notified of completion.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } else { /* Non-waitable operation should have job reference of 1. */ - IotMqtt_Assert( pOperation->jobReference == 1 ); + IotMqtt_Assert( pOperation->u.operation.jobReference == 1 ); /* Schedule an invocation of the callback. */ - if( pOperation->notify.callback.function != NULL ) + if( pOperation->u.operation.notify.callback.function != NULL ) { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -1255,7 +1255,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation, { IotLogDebug( "(MQTT connection %p, %s operation %p) Callback scheduled.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); /* A completed operation should not be linked. */ @@ -1270,7 +1270,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation, { IotLogWarn( "(MQTT connection %p, %s operation %p) Failed to schedule callback.", pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->type ), + IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); } diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 27be7ec830..c6db92546d 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -1173,7 +1173,7 @@ void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) { _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - IotMqttPublishInfo_t * pOutput = &( pPublish->pIncomingPublish->publishInfo ); + IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); uint8_t publishFlags = 0; const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; @@ -1627,7 +1627,7 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) "Topic filter %lu refused.", ( unsigned long ) i ); /* Remove a rejected subscription from the subscription manager. */ - _IotMqtt_RemoveSubscriptionByPacket( pSuback->pMqttConnection, + _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, pSuback->packetIdentifier, ( int32_t ) i ); diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index b7ebcade98..5ab3344a13 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -404,8 +404,8 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, IotMqttCallbackParam_t * ) = NULL; _topicMatchParams_t topicMatchParams = { - .pTopicName = pCallbackParam->message.info.pTopicName, - .topicNameLength = pCallbackParam->message.info.topicNameLength, + .pTopicName = pCallbackParam->u.message.info.pTopicName, + .topicNameLength = pCallbackParam->u.message.info.topicNameLength, .exactMatchOnly = false }; @@ -451,8 +451,8 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, /* Set the members of the callback parameter. */ pCallbackParam->mqttConnection = pMqttConnection; - pCallbackParam->message.pTopicFilter = pSubscription->pTopicFilter; - pCallbackParam->message.topicFilterLength = pSubscription->topicFilterLength; + pCallbackParam->u.message.pTopicFilter = pSubscription->pTopicFilter; + pCallbackParam->u.message.topicFilterLength = pSubscription->topicFilterLength; /* Invoke the subscription callback. */ callbackFunction( pCallbackContext, pCallbackParam ); diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index 22b718d345..a50bc977c2 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -326,7 +326,7 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) } /* Check that reference is waitable. */ - if( ( operation->flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) + if( ( operation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) != IOT_MQTT_FLAG_WAITABLE ) { IotLogError( "Operation is not waitable." ); diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 0d1aa2e9f0..19e9f4f402 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -556,8 +556,8 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, callbackParam.mqttConnection = pMessage->mqttConnection; callbackParam.pThingName = pSubscription->pThingName; callbackParam.thingNameLength = pSubscription->thingNameLength; - callbackParam.callback.pDocument = pMessage->message.info.pPayload; - callbackParam.callback.documentLength = pMessage->message.info.payloadLength; + callbackParam.callback.pDocument = pMessage->u.message.info.pPayload; + callbackParam.callback.documentLength = pMessage->u.message.info.payloadLength; /* Invoke the callback function. */ pSubscription->callbacks[ type ].function( pSubscription->callbacks[ type ].pCallbackContext, diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 30aede8929..f778b1ec15 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -231,13 +231,13 @@ static void _commonOperationCallback( _shadowOperationType_t type, /* Set the response document for a Shadow UPDATE. */ if( type == _SHADOW_UPDATE ) { - param.pDocument = pMessage->message.info.pPayload; - param.documentLength = pMessage->message.info.payloadLength; + param.pDocument = pMessage->u.message.info.pPayload; + param.documentLength = pMessage->u.message.info.payloadLength; } /* Parse the Thing Name from the MQTT topic name. */ - if( _AwsIotShadow_ParseThingName( pMessage->message.info.pTopicName, - pMessage->message.info.topicNameLength, + if( _AwsIotShadow_ParseThingName( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength, &( param.pThingName ), &( param.thingNameLength ) ) != AWS_IOT_SHADOW_SUCCESS ) { @@ -282,12 +282,12 @@ static void _commonOperationCallback( _shadowOperationType_t type, AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); IotLogDebug( "Received Shadow response on topic %.*s", - pMessage->message.info.topicNameLength, - pMessage->message.info.pTopicName ); + pMessage->u.message.info.topicNameLength, + pMessage->u.message.info.pTopicName ); /* Parse the status from the topic name. */ - status = _AwsIotShadow_ParseShadowStatus( pMessage->message.info.pTopicName, - pMessage->message.info.topicNameLength ); + status = _AwsIotShadow_ParseShadowStatus( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength ); switch( status ) { @@ -302,7 +302,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, if( type == _SHADOW_GET ) { pOperation->status = _processAcceptedGet( pOperation, - &( pMessage->message.info ) ); + &( pMessage->u.message.info ) ); } else { @@ -317,8 +317,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, pOperation->pSubscription->thingNameLength, pOperation->pSubscription->pThingName ); - pOperation->status = _AwsIotShadow_ParseErrorDocument( pMessage->message.info.pPayload, - pMessage->message.info.payloadLength ); + pOperation->status = _AwsIotShadow_ParseErrorDocument( pMessage->u.message.info.pPayload, + pMessage->u.message.info.payloadLength ); break; default: diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index a6c20c977d..c91fa043ea 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -17,7 +17,7 @@ if( NOT ${CMAKE_USE_PTHREADS_INIT} ) endif() # Check for required POSIX types. -set( CMAKE_EXTRA_INCLUDE_FILES "semaphore.h" ) +set( CMAKE_EXTRA_INCLUDE_FILES "pthread.h" "time.h" "semaphore.h" ) list( APPEND REQUIRED_POSIX_TYPES pthread_t pthread_attr_t @@ -34,7 +34,6 @@ foreach( POSIX_TYPE ${REQUIRED_POSIX_TYPES} ) endforeach() # Check for some required POSIX functions. This is not intended to be a comprehensive list. -set( CMAKE_REQUIRED_INCLUDES "time.h" ) set( CMAKE_REQUIRED_LIBRARIES rt Threads::Threads ) list( APPEND REQUIRED_POSIX_FUNCTIONS clock_gettime time localtime_r strftime timer_create timer_delete diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 75d8ea5ef1..6bdef323fd 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -163,8 +163,8 @@ TEST_SETUP( Full_DEFENDER ) /* Set fields of start info. */ _startInfo.mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; _startInfo.mqttNetworkInfo.createNetworkConnection = true; - _startInfo.mqttNetworkInfo.pNetworkServerInfo = &_serverInfo; - _startInfo.mqttNetworkInfo.pNetworkCredentialInfo = &_credential; + _startInfo.mqttNetworkInfo.u.setup.pNetworkServerInfo = &_serverInfo; + _startInfo.mqttNetworkInfo.u.setup.pNetworkCredentialInfo = &_credential; _startInfo.mqttNetworkInfo.pNetworkInterface = &_IotNetworkOpensslMetrics; diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 520149847d..33d3754092 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -279,18 +279,18 @@ static void _publishReceived( void * pArgument, /* Increment the received PUBLISH counter if the received message matches * what was published. */ - if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && - ( strncmp( _pSamplePayload, pPublish->message.info.pPayload, _samplePayloadLength ) == 0 ) && - ( pPublish->message.info.topicNameLength == _topicNameLength ) && - ( pPublish->message.info.qos == IOT_MQTT_QOS_1 ) ) + if( ( pPublish->u.message.info.payloadLength == _samplePayloadLength ) && + ( strncmp( _pSamplePayload, pPublish->u.message.info.pPayload, _samplePayloadLength ) == 0 ) && + ( pPublish->u.message.info.topicNameLength == _topicNameLength ) && + ( pPublish->u.message.info.qos == IOT_MQTT_QOS_1 ) ) { IotSemaphore_Post( &receivedPublishCounter ); } else { IotLogWarn( "Received an unknown message on subscription %.*s: %.*s", - pPublish->message.info.topicNameLength, pPublish->message.info.pTopicName, - pPublish->message.info.payloadLength, pPublish->message.info.pPayload ); + pPublish->u.message.info.topicNameLength, pPublish->u.message.info.pTopicName, + pPublish->u.message.info.payloadLength, pPublish->u.message.info.pPayload ); } } @@ -448,11 +448,11 @@ TEST_SETUP( MQTT_Stress ) /* Set the MQTT network setup parameters. */ ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); _networkInfo.createNetworkConnection = true; - _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; #endif /* Set the members of the connect info. */ diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 3cac184655..555617dee6 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -347,8 +347,8 @@ static void _publishReceived( void * pArgument, IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; /* If the received messages matches what was sent, unblock the waiting thread. */ - if( ( pPublish->message.info.payloadLength == _samplePayloadLength ) && - ( strncmp( pPublish->message.info.pPayload, + if( ( pPublish->u.message.info.payloadLength == _samplePayloadLength ) && + ( strncmp( pPublish->u.message.info.pPayload, _pSamplePayload, _samplePayloadLength ) == 0 ) ) { @@ -369,9 +369,9 @@ static void _operationComplete( void * pArgument, /* If the operation information matches the parameters and the operation was * successful, unblock the waiting thread. */ - if( ( pParams->expectedOperation == pOperation->operation.type ) && - ( pParams->operation == pOperation->operation.reference ) && - ( pOperation->operation.result == IOT_MQTT_SUCCESS ) ) + if( ( pParams->expectedOperation == pOperation->u.operation.type ) && + ( pParams->operation == pOperation->u.operation.reference ) && + ( pOperation->u.operation.result == IOT_MQTT_SUCCESS ) ) { IotSemaphore_Post( &( pParams->waitSem ) ); } @@ -632,12 +632,12 @@ TEST_SETUP( MQTT_System ) /* Set the MQTT network setup parameters. */ ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); _networkInfo.createNetworkConnection = true; - _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; _networkInfo.pMqttSerializer = _pMqttSerializer; #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; #endif } @@ -851,10 +851,10 @@ TEST( MQTT_System, LastWillAndTestament ) /* Establish an independent MQTT over TCP connection to receive a Last * Will and Testament message. */ lwtNetworkInfo.createNetworkConnection = true; - lwtNetworkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + lwtNetworkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; #if IOT_TEST_SECURED_CONNECTION == 1 - lwtNetworkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + lwtNetworkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; #endif lwtNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 458c1e1002..00bfd708d7 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -322,11 +322,11 @@ static size_t _dupChecker( void * pSendContext, * for the AWS IoT MQTT server. */ #if _AWS_IOT_MQTT_SERVER == true static uint16_t lastPacketIdentifier = 0; - _mqttPacket_t publishPacket = { .pMqttConnection = NULL }; + _mqttPacket_t publishPacket = { .u.pMqttConnection = NULL }; _mqttOperation_t publishOperation = { .link = { 0 } }; publishPacket.type = publishFlags; - publishPacket.pIncomingPublish = &publishOperation; + publishPacket.u.pIncomingPublish = &publishOperation; publishPacket.remainingLength = 8 + _TEST_TOPIC_NAME_LENGTH; publishPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - publishPacket.remainingLength ); #endif @@ -470,7 +470,7 @@ static void _disconnectCallback( void * pCallbackContext, IotMqttDisconnectReason_t * pExpectedReason = ( IotMqttDisconnectReason_t * ) pCallbackContext; /* Only increment counter if the reasons match. */ - if( pCallbackParam->disconnectReason == *pExpectedReason ) + if( pCallbackParam->u.disconnectReason == *pExpectedReason ) { _disconnectCallbackCount++; } @@ -496,7 +496,7 @@ static void _decrementReferencesJob( IotTaskPool_t * pTaskPool, if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == false ) { /* Unblock the main test thread. */ - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); } } @@ -597,7 +597,7 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) /* Check reference counts and list placement. */ TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, _pMqttConnection->references ); - TEST_ASSERT_EQUAL_INT32( 2, pOperation->jobReference ); + TEST_ASSERT_EQUAL_INT32( 2, pOperation->u.operation.jobReference ); TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( _pMqttConnection->pendingProcessing ), NULL, NULL, @@ -612,11 +612,11 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) 0 ) ); /* Wait for the job to complete. */ - IotSemaphore_Wait( &( pOperation->notify.waitSemaphore ) ); + IotSemaphore_Wait( &( pOperation->u.operation.notify.waitSemaphore ) ); /* Check reference counts after job completion. */ TEST_ASSERT_EQUAL_INT32( 1 + keepAliveReference, _pMqttConnection->references ); - TEST_ASSERT_EQUAL_INT32( 1, pOperation->jobReference ); + TEST_ASSERT_EQUAL_INT32( 1, pOperation->u.operation.jobReference ); TEST_ASSERT_EQUAL_PTR( &( pOperation->link ), IotListDouble_FindFirstMatch( &( _pMqttConnection->pendingProcessing ), NULL, NULL, @@ -665,9 +665,9 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) &pOperation ) ); /* Set an arbitrary MQTT packet for the operation. */ - pOperation->type = IOT_MQTT_DISCONNECT; - pOperation->pMqttPacket = pPacket; - pOperation->packetSize = 2; + pOperation->u.operation.type = IOT_MQTT_DISCONNECT; + pOperation->u.operation.pMqttPacket = pPacket; + pOperation->u.operation.packetSize = 2; /* Schedule the send job. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_ScheduleOperation( pOperation, @@ -683,7 +683,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) /* Check reference count after a timed out wait. */ IotMutex_Lock( &( _pMqttConnection->referencesMutex ) ); - TEST_ASSERT_EQUAL_INT32( 1, pOperation->jobReference ); + TEST_ASSERT_EQUAL_INT32( 1, pOperation->u.operation.jobReference ); IotMutex_Unlock( &( _pMqttConnection->referencesMutex ) ); /* Disconnect the MQTT connection. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index e0227dca12..e05a937f93 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -114,12 +114,16 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Initializer for operations in the tests. */ -#define _INITIALIZE_OPERATION( name ) \ - { \ - .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ - .job = { 0 }, .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ - .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, .notify = { .callback = { 0 } }, \ - .status = IOT_MQTT_STATUS_PENDING, .retry = { 0 } \ +#define _INITIALIZE_OPERATION( name ) \ + { \ + .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, .job = { 0 }, \ + .u.operation = \ + { \ + .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ + .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ + .notify= { .callback = { 0 } }, .status = IOT_MQTT_STATUS_PENDING, \ + .retry = { 0 } \ + } \ } /*-----------------------------------------------------------*/ @@ -282,8 +286,8 @@ static IotMqttError_t _deserializePingresp( _mqttPacket_t * pPingresp ) */ static void _operationResetAndPush( _mqttOperation_t * pOperation ) { - pOperation->status = IOT_MQTT_STATUS_PENDING; - pOperation->jobReference = 1; + pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; + pOperation->u.operation.jobReference = 1; IotDeQueue_EnqueueHead( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); } @@ -311,7 +315,7 @@ static bool _processBuffer( const _mqttOperation_t * pOperation, /* Check expected result if operation is given. */ if( pOperation != NULL ) { - status = ( expectedResult == pOperation->status ); + status = ( expectedResult == pOperation->u.operation.status ); } return status; @@ -394,13 +398,13 @@ static void _publishCallback( void * pCallbackContext, /* QoS 2 is valid for these tests, but currently unsupported by the MQTT library. * Change the QoS to 0 so that QoS validation passes. */ - pPublish->message.info.qos = IOT_MQTT_QOS_0; + pPublish->u.message.info.qos = IOT_MQTT_QOS_0; /* Check that the parameters to this function are valid. */ if( ( _IotMqtt_ValidatePublish( _AWS_IOT_MQTT_SERVER, - &( pPublish->message.info ) ) == true ) && - ( pPublish->message.info.topicNameLength == _TEST_TOPIC_LENGTH ) && - ( strncmp( _TEST_TOPIC_NAME, pPublish->message.info.pTopicName, _TEST_TOPIC_LENGTH ) == 0 ) ) + &( pPublish->u.message.info ) ) == true ) && + ( pPublish->u.message.info.topicNameLength == _TEST_TOPIC_LENGTH ) && + ( strncmp( _TEST_TOPIC_NAME, pPublish->u.message.info.pTopicName, _TEST_TOPIC_LENGTH ) == 0 ) ) { IotSemaphore_Post( pInvokeCount ); } @@ -495,7 +499,7 @@ static void _disconnectCallback( void * pCallbackContext, /* Silence warnings about unused parameters. */ ( void ) pCallbackContext; - if( pCallbackParam->disconnectReason == IOT_MQTT_BAD_PACKET_RECEIVED ) + if( pCallbackParam->u.disconnectReason == IOT_MQTT_BAD_PACKET_RECEIVED ) { _disconnectCallbackCalled = true; } @@ -753,7 +757,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -804,7 +808,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) IOT_MQTT_SERVER_REFUSED ) ); } - IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( connect.u.operation.notify.waitSemaphore ) ); /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); @@ -823,7 +827,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -927,7 +931,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) _disconnectCallbackCalled = false; } - IotSemaphore_Destroy( &( connect.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( connect.u.operation.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1138,7 +1142,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -1162,7 +1166,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) IOT_MQTT_SUCCESS ) ); } - IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( publish.u.operation.notify.waitSemaphore ) ); /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); @@ -1181,7 +1185,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -1259,7 +1263,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) IotDeQueue_Remove( &( publish.link ) ); } - IotSemaphore_Destroy( &( publish.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( publish.u.operation.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1276,7 +1280,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -1361,7 +1365,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) NULL ) ); } - IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( subscribe.u.operation.notify.waitSemaphore ) ); /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); @@ -1380,7 +1384,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -1486,7 +1490,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) _disconnectCallbackCalled = false; } - IotSemaphore_Destroy( &( subscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( subscribe.u.operation.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ @@ -1501,7 +1505,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -1525,7 +1529,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) IOT_MQTT_SUCCESS ) ); } - IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( unsubscribe.u.operation.notify.waitSemaphore ) ); /* Network close function should not have been invoked. */ TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); @@ -1544,7 +1548,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.notify.waitSemaphore ), + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.u.operation.notify.waitSemaphore ), 0, 10 ) ); @@ -1622,7 +1626,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) IotDeQueue_Remove( &( unsubscribe.link ) ); } - IotSemaphore_Destroy( &( unsubscribe.notify.waitSemaphore ) ); + IotSemaphore_Destroy( &( unsubscribe.u.operation.notify.waitSemaphore ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 7f6e99c7c3..d2fec38233 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -244,18 +244,18 @@ static void _publishCallback( void * pArgument, /* If the topic filter doesn't match the topic name, ensure that the topic * filter contains a wildcard. */ - if( pPublish->message.topicFilterLength != pPublish->message.info.topicNameLength ) + if( pPublish->u.message.topicFilterLength != pPublish->u.message.info.topicNameLength ) { - for( i = 0; i < pPublish->message.topicFilterLength; i++ ) + for( i = 0; i < pPublish->u.message.topicFilterLength; i++ ) { - if( ( pPublish->message.pTopicFilter[ i ] == '+' ) || - ( pPublish->message.pTopicFilter[ i ] == '#' ) ) + if( ( pPublish->u.message.pTopicFilter[ i ] == '+' ) || + ( pPublish->u.message.pTopicFilter[ i ] == '#' ) ) { break; } } - TEST_ASSERT_LESS_THAN_UINT16( pPublish->message.topicFilterLength, i ); + TEST_ASSERT_LESS_THAN_UINT16( pPublish->u.message.topicFilterLength, i ); } /* Ensure that the MQTT connection was set correctly. */ @@ -264,7 +264,7 @@ static void _publishCallback( void * pArgument, /* Ensure that publish info is valid. */ TEST_ASSERT_EQUAL_INT( true, _IotMqtt_ValidatePublish( _AWS_IOT_MQTT_SERVER, - &( pPublish->message.info ) ) ); + &( pPublish->u.message.info ) ) ); } /*-----------------------------------------------------------*/ @@ -307,7 +307,7 @@ static void _multithreadTestThread( void * pArgument ) subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], _MT_TOPIC_FILTER_LENGTH, _MT_TOPIC_FILTER_FORMAT, - &i, + ( void * ) &i, ( unsigned long ) i ); if( _IotMqtt_AddSubscriptions( _pMqttConnection, @@ -826,7 +826,7 @@ TEST( MQTT_Unit_Subscription, ProcessPublish ) { bool callbackInvoked = false; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; /* Set the subscription and corresponding publish info. */ subscription.pTopicFilter = "/test"; @@ -834,10 +834,10 @@ TEST( MQTT_Unit_Subscription, ProcessPublish ) subscription.callback.function = _publishCallback; subscription.callback.pCallbackContext = &callbackInvoked; - callbackParam.message.info.pTopicName = "/test"; - callbackParam.message.info.topicNameLength = 5; - callbackParam.message.info.pPayload = ""; - callbackParam.message.info.payloadLength = 0; + callbackParam.u.message.info.pTopicName = "/test"; + callbackParam.u.message.info.topicNameLength = 5; + callbackParam.u.message.info.pPayload = ""; + callbackParam.u.message.info.payloadLength = 0; /* Add the subscription. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, @@ -867,7 +867,7 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) { bool callbackInvoked[ 3 ] = { false }; IotMqttSubscription_t subscription[ 3 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - IotMqttCallbackParam_t callbackParam = { .message = { 0 } }; + IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; /* Set the subscription info. */ subscription[ 0 ].pTopicFilter = "/test"; @@ -886,10 +886,10 @@ TEST( MQTT_Unit_Subscription, ProcessPublishMultiple ) subscription[ 2 ].callback.pCallbackContext = &( callbackInvoked[ 2 ] ); /* Create a PUBLISH that matches all 3 subscriptions. */ - callbackParam.message.info.pTopicName = "/test"; - callbackParam.message.info.topicNameLength = 5; - callbackParam.message.info.pPayload = ""; - callbackParam.message.info.payloadLength = 0; + callbackParam.u.message.info.pTopicName = "/test"; + callbackParam.u.message.info.topicNameLength = 5; + callbackParam.u.message.info.pPayload = ""; + callbackParam.u.message.info.payloadLength = 0; /* Add the subscriptions. */ TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, @@ -964,9 +964,9 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) ( void ) memset( pIncomingPublish[ i ], 0x00, sizeof( _mqttOperation_t ) ); pIncomingPublish[ i ]->incomingPublish = true; pIncomingPublish[ i ]->pMqttConnection = _pMqttConnection; - pIncomingPublish[ i ]->publishInfo.pTopicName = "/test"; - pIncomingPublish[ i ]->publishInfo.topicNameLength = 5; - pIncomingPublish[ i ]->publishInfo.pPayload = ""; + pIncomingPublish[ i ]->u.publish.publishInfo.pTopicName = "/test"; + pIncomingPublish[ i ]->u.publish.publishInfo.topicNameLength = 5; + pIncomingPublish[ i ]->u.publish.publishInfo.pPayload = ""; IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), &( pIncomingPublish[ i ]->link ) ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 651a8013b5..8f55ef02ce 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -59,7 +59,9 @@ /** * @brief A non-NULL function pointer. */ -#define _FUNCTION_POINTER ( ( void * ) 0x1 ) +#define _FUNCTION_POINTER \ + ( ( void ( * )( void *, \ + IotMqttCallbackParam_t * ) ) 0x1 ) /*-----------------------------------------------------------*/ @@ -268,12 +270,12 @@ TEST( MQTT_Unit_Validate, ValidateOperation ) TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Non-waitable reference. */ - operation->flags = 0; + operation->u.operation.flags = 0; validateStatus = _IotMqtt_ValidateOperation( operation ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Waitable (valid) reference. */ - operation->flags = IOT_MQTT_FLAG_WAITABLE; + operation->u.operation.flags = IOT_MQTT_FLAG_WAITABLE; validateStatus = _IotMqtt_ValidateOperation( operation ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); } diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 2a077307df..25985c3ec2 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -472,11 +472,11 @@ TEST_SETUP( Shadow_System ) /* Set the MQTT network setup parameters. */ ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); _networkInfo.createNetworkConnection = true; - _networkInfo.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.pNetworkCredentialInfo = ( void * ) &_credentials; + _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; #endif #ifdef IOT_TEST_MQTT_SERIALIZER diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 7deacb6120..686b94eecc 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -231,7 +231,7 @@ static size_t _sendSuccess( void * pSendContext, { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttOperation_t deserializedPublish = { .link = { 0 } }; - _mqttPacket_t mqttPacket = { .pMqttConnection = NULL }; + _mqttPacket_t mqttPacket = { .u.pMqttConnection = NULL }; _receiveContext_t receiveContext = { 0 }; /* Ignore the send context. */ @@ -296,7 +296,7 @@ static size_t _sendSuccess( void * pSendContext, } else { - mqttPacket.pIncomingPublish = &deserializedPublish; + mqttPacket.u.pIncomingPublish = &deserializedPublish; mqttPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - mqttPacket.remainingLength ); status = _IotMqtt_DeserializePublish( &mqttPacket ); From 5c64c3dc80990fada30dea01511642d3ba1199cf Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 16 Apr 2019 12:54:03 -0700 Subject: [PATCH 099/844] Remove anonymous structs from serializer (#378) --- lib/include/iot_serializer.h | 16 ++++----- .../cbor/iot_serializer_tinycbor_decoder.c | 34 +++++++++---------- .../cbor/iot_serializer_tinycbor_encoder.c | 8 ++--- tests/defender/aws_iot_tests_defender_api.c | 18 +++++----- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 7de7fa428b..010c6abf56 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -179,13 +179,13 @@ /* helper macro to create scalar data */ #define IotSerializer_ScalarSignedInt( signedIntValue ) \ - ( IotSerializerScalarData_t ) { .value = { .signedInt = ( signedIntValue ) }, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT } \ + ( IotSerializerScalarData_t ) { .value = { .u.signedInt = ( signedIntValue ) }, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT } \ #define IotSerializer_ScalarTextString( pTextStringValue ) \ - ( IotSerializerScalarData_t ) { .value = { .pString = ( ( uint8_t * ) pTextStringValue ), .stringLength = strlen( pTextStringValue ) }, .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ + ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( ( uint8_t * ) pTextStringValue ), .u.string.length = strlen( pTextStringValue ) }, .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ -#define IotSerializer_ScalarByteString( pByteStringValue, length ) \ - ( IotSerializerScalarData_t ) { .value = { .pString = ( pByteStringValue ), .stringLength = ( length ) }, .type = IOT_SERIALIZER_SCALAR_BYTE_STRING } \ +#define IotSerializer_ScalarByteString( pByteStringValue, pByteStringLength ) \ + ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( pByteStringValue ), .u.string.length = ( pByteStringLength ) }, .type = IOT_SERIALIZER_SCALAR_BYTE_STRING } \ /* Determine if an object is a container. */ #define IotSerializer_IsContainer( object ) \ @@ -234,10 +234,10 @@ typedef struct IotSerializerScalarValue struct { uint8_t * pString; - size_t stringLength; - }; + size_t length; + } string; bool booleanValue; - }; + } u; } IotSerializerScalarValue_t; /* scalar data handle used in encoder */ @@ -265,7 +265,7 @@ typedef struct IotSerializerDecoderObject void * pHandle; /* if the type is a container, the scalarValue is unuseful */ IotSerializerScalarValue_t value; - }; + } u; } IotSerializerDecoderObject_t; typedef void * IotSerializerDecoderIterator_t; diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c index 5d9359047a..0f46e48c65 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c @@ -34,9 +34,9 @@ #include "iot_serializer.h" #include "cbor.h" -#define _castDecoderObjectToCborValue( pDecoderObject ) ( ( pDecoderObject )->pHandle ) +#define _castDecoderObjectToCborValue( pDecoderObject ) ( ( pDecoderObject )->u.pHandle ) -#define _castDecoderIteratorToCborValue( iterator ) ( ( ( IotSerializerDecoderObject_t * ) iterator )->pHandle ) +#define _castDecoderIteratorToCborValue( iterator ) ( ( ( IotSerializerDecoderObject_t * ) iterator )->u.pHandle ) #define _isArrayOrMap( dataType ) ( ( ( dataType ) == IOT_SERIALIZER_CONTAINER_ARRAY ) || ( ( dataType ) == IOT_SERIALIZER_CONTAINER_MAP ) ) @@ -170,14 +170,14 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal if( _isArrayOrMap( dataType ) ) { /* Save to decoder object's handle. */ - pDecoderObject->pHandle = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + pDecoderObject->u.pHandle = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); - if( pDecoderObject->pHandle == NULL ) + if( pDecoderObject->u.pHandle == NULL ) { return IOT_SERIALIZER_OUT_OF_MEMORY; } - memcpy( pDecoderObject->pHandle, pCborValueWrapper, sizeof( _cborValueWrapper_t ) ); + memcpy( pDecoderObject->u.pHandle, pCborValueWrapper, sizeof( _cborValueWrapper_t ) ); } else /* Create scalar object. */ { @@ -190,7 +190,7 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal if( cborError == CborNoError ) { - pDecoderObject->value.booleanValue = value; + pDecoderObject->u.value.u.booleanValue = value; } else { @@ -207,7 +207,7 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal if( cborError == CborNoError ) { - pDecoderObject->value.signedInt = i; + pDecoderObject->u.value.u.signedInt = i; } else { @@ -224,23 +224,23 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal { cborError = cbor_value_copy_byte_string( pCborValue, - pDecoderObject->value.pString, - &( pDecoderObject->value.stringLength ), + pDecoderObject->u.value.u.string.pString, + &( pDecoderObject->u.value.u.string.length ), &next ); } else { cborError = cbor_value_copy_text_string( pCborValue, - ( char * ) pDecoderObject->value.pString, - &( pDecoderObject->value.stringLength ), + ( char * ) pDecoderObject->u.value.u.string.pString, + &( pDecoderObject->u.value.u.string.length ), &next ); } if( cborError != CborNoError ) { if( ( cborError == CborErrorOutOfMemory ) && - ( pDecoderObject->value.pString == NULL ) && + ( pDecoderObject->u.value.u.string.pString == NULL ) && ( cbor_value_is_length_known( pCborValue ) ) ) { @@ -248,7 +248,7 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal * If its a finite length text/byte string, and user have passed a null length buffer, * we avoid copying the string by storing pointer to the start of the string. */ - pDecoderObject->value.pString = ( uint8_t * ) ( cbor_value_get_next_byte( &next ) - ( pDecoderObject->value.stringLength ) ); + pDecoderObject->u.value.u.string.pString = ( uint8_t * ) ( cbor_value_get_next_byte( &next ) - ( pDecoderObject->u.value.u.string.length ) ); } else { @@ -307,9 +307,9 @@ static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject IotSerializer_FreeCborParser( pCborParser ); if( _isArrayOrMap( pDecoderObject->type ) && - ( pDecoderObject->pHandle != NULL ) ) + ( pDecoderObject->u.pHandle != NULL ) ) { - IotSerializer_FreeCborValue( pDecoderObject->pHandle ); + IotSerializer_FreeCborValue( pDecoderObject->u.pHandle ); } } @@ -339,7 +339,7 @@ static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ) IotSerializer_FreeCborValue( pCborValueWrapper ); /* Reset decoder object's handle to NULL. */ - pDecoderObject->pHandle = NULL; + pDecoderObject->u.pHandle = NULL; } /*-----------------------------------------------------------*/ @@ -421,7 +421,7 @@ static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObje pNewObject->type = pDecoderObject->type; pNewCborValueWrapper->isOutermost = false; - pNewObject->pHandle = ( void * ) pNewCborValueWrapper; + pNewObject->u.pHandle = ( void * ) pNewCborValueWrapper; *pIterator = ( IotSerializerDecoderIterator_t ) pNewObject; diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c index 9e9872ec0e..c6aa12ffb0 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c @@ -273,19 +273,19 @@ static IotSerializerError_t _append( IotSerializerEncoderObject_t * pEncoderObje switch( scalarData.type ) { case IOT_SERIALIZER_SCALAR_SIGNED_INT: - cborError = cbor_encode_int( pCborEncoder, scalarData.value.signedInt ); + cborError = cbor_encode_int( pCborEncoder, scalarData.value.u.signedInt ); break; case IOT_SERIALIZER_SCALAR_TEXT_STRING: - cborError = cbor_encode_text_string( pCborEncoder, ( char * ) scalarData.value.pString, scalarData.value.stringLength ); + cborError = cbor_encode_text_string( pCborEncoder, ( char * ) scalarData.value.u.string.pString, scalarData.value.u.string.length ); break; case IOT_SERIALIZER_SCALAR_BYTE_STRING: - cborError = cbor_encode_byte_string( pCborEncoder, scalarData.value.pString, scalarData.value.stringLength ); + cborError = cbor_encode_byte_string( pCborEncoder, scalarData.value.u.string.pString, scalarData.value.u.string.length ); break; case IOT_SERIALIZER_SCALAR_BOOL: - cborError = cbor_encode_boolean( pCborEncoder, scalarData.value.booleanValue ); + cborError = cbor_encode_boolean( pCborEncoder, scalarData.value.u.booleanValue ); break; case IOT_SERIALIZER_SCALAR_NULL: diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 6bdef323fd..3cb9493a42 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -665,8 +665,8 @@ static void _assertRejectDueToThrottle() TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, statusDetailsObject.type ); - errorCodeObject.value.pString = ( uint8_t * ) errorCode; - errorCodeObject.value.stringLength = 12; + errorCodeObject.u.value.u.string.pString = ( uint8_t * ) errorCode; + errorCodeObject.u.value.u.string.length = 12; error = _Decoder.find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); @@ -674,7 +674,7 @@ static void _assertRejectDueToThrottle() TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, errorCodeObject.type ); - TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) errorCodeObject.value.pString, "Throttled", errorCodeObject.value.stringLength ) ); + TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) errorCodeObject.u.value.u.string.pString, "Throttled", errorCodeObject.u.value.u.string.length ) ); _Decoder.destroy( &statusDetailsObject ); _Decoder.destroy( &decoderObject ); @@ -714,8 +714,8 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) IotSerializerDecoderObject_t statusObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; char status[ 10 ] = ""; - statusObject.value.pString = ( uint8_t * ) status; - statusObject.value.stringLength = 10; + statusObject.u.value.u.string.pString = ( uint8_t * ) status; + statusObject.u.value.u.string.length = 10; error = _Decoder.find( &decoderObject, "status", &statusObject ); @@ -723,7 +723,7 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, statusObject.type ); - TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) statusObject.value.pString, "ACCEPTED", statusObject.value.stringLength ) ); + TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) statusObject.u.value.u.string.pString, "ACCEPTED", statusObject.u.value.u.string.length ) ); _Decoder.destroy( &statusObject ); _Decoder.destroy( &decoderObject ); @@ -793,7 +793,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_SIGNED_INT, totalObject.type ); - TEST_ASSERT_EQUAL( total, totalObject.value.signedInt ); + TEST_ASSERT_EQUAL( total, totalObject.u.value.u.signedInt ); } else { @@ -840,8 +840,8 @@ static void _verifyTcpConections( int total, /* Verify the passed address matching. */ TEST_ASSERT_EQUAL_STRING_LEN( va_arg( valist, char * ), - remoteAddrObject.value.pString, - remoteAddrObject.value.stringLength ); + remoteAddrObject.u.value.u.string.pString, + remoteAddrObject.u.value.u.string.length ); } else { From 19d2d97b8b1a62b949d011fb9bbea6aa26fdcc96 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Tue, 16 Apr 2019 17:10:02 -0400 Subject: [PATCH 100/844] Modify CBMC proofs to update the tinycbor submodule. (#380) TinyCBOR was submoduled out as a third-party dependency. This pull request modifies the CBMC proof build flow to update the TinyCBOR submodule before building MQTT for CBMC. --- cbmc/proofs/Makefile.common | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 5d322a4914..1ec06f6816 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -35,7 +35,8 @@ CFLAGS += $(CFLAGS2) $(INC) $(DEF) $(GOTO_CC) -o $@ $(CFLAGS) $< $(MQTT)/build: - (mkdir -p $(MQTT)/build; cd $(MQTT)/build; cmake ..) 2>&1 \ + (cd $(MQTT) && git submodule init && git submodule update && \ + mkdir build && cd build && cmake ..) 2>&1 \ | tee $(ENTRY)0.txt \ ; exit $${PIPESTATUS[0]} From c7e55d1e1f9f78b5b2dbd44d2cb69be2ed9d31b8 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 16 Apr 2019 14:28:08 -0700 Subject: [PATCH 101/844] Remove anonymous structs and unions from Shadow (#379) --- demos/source/aws_iot_demo_shadow.c | 43 +++++---- demos/source/iot_demo_mqtt.c | 23 +++-- doc/config/defender | 1 + .../private/aws_iot_defender_internal.h | 16 ++-- lib/include/private/aws_iot_shadow_internal.h | 2 +- lib/include/types/aws_iot_shadow_types.h | 4 +- lib/include/types/iot_mqtt_types.h | 2 +- lib/source/common/iot_logging.c | 29 +----- .../defender/aws_iot_defender_collector.c | 28 +++--- lib/source/defender/aws_iot_defender_mqtt.c | 8 +- lib/source/mqtt/iot_mqtt_api.c | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 28 +++--- lib/source/shadow/aws_iot_shadow_operation.c | 40 ++++----- platform/source/posix/linux/iot_metrics.c | 2 +- .../posix/network/iot_network_openssl.c | 2 +- tests/defender/aws_iot_tests_defender_api.c | 18 ++-- tests/mqtt/access/iot_test_access_mqtt_api.c | 4 + .../iot_test_access_mqtt_subscription.c | 6 ++ tests/mqtt/system/iot_tests_mqtt_system.c | 14 +-- tests/mqtt/unit/iot_tests_mqtt_receive.c | 4 +- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 14 +-- .../system/aws_iot_tests_shadow_system.c | 66 ++++++-------- tests/shadow/unit/aws_iot_tests_shadow_api.c | 16 ++-- .../shadow/unit/aws_iot_tests_shadow_parser.c | 14 +-- .../fixture/unity_fixture_malloc_overrides.h | 2 + third_party/unity/unity/unity_memory_mt.c | 89 ------------------- 26 files changed, 162 insertions(+), 315 deletions(-) delete mode 100644 third_party/unity/unity/unity_memory_mt.c diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 9fcefd04af..0d68c53360 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -32,6 +32,7 @@ /* Standard includes. */ #include +#include #include #include @@ -51,19 +52,6 @@ /* JSON utilities include. */ #include "iot_json_utils.h" -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int snprintf( char *, - size_t, - const char *, - ... ); -/** @endcond */ - /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -150,6 +138,15 @@ extern int snprintf( char *, /*-----------------------------------------------------------*/ +/* Declaration of demo function. */ +int RunShadowDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ); + +/*-----------------------------------------------------------*/ + /** * @brief Parses a key in the "state" section of a Shadow delta document. * @@ -280,8 +277,8 @@ static void _shadowDeltaCallback( void * pCallbackContext, static char pUpdateDocument[ _EXPECTED_REPORTED_JSON_SIZE + 1 ] = { 0 }; /* Check if there is a different "powerOn" state in the Shadow. */ - deltaFound = _getDelta( pCallbackParam->callback.pDocument, - pCallbackParam->callback.documentLength, + deltaFound = _getDelta( pCallbackParam->u.callback.pDocument, + pCallbackParam->u.callback.documentLength, "powerOn", &pDelta, &deltaLength ); @@ -315,8 +312,8 @@ static void _shadowDeltaCallback( void * pCallbackContext, /* Set the common members to report the new state. */ updateDocument.pThingName = pCallbackParam->pThingName; updateDocument.thingNameLength = pCallbackParam->thingNameLength; - updateDocument.update.pUpdateDocument = pUpdateDocument; - updateDocument.update.updateDocumentLength = _EXPECTED_REPORTED_JSON_SIZE; + updateDocument.u.update.pUpdateDocument = pUpdateDocument; + updateDocument.u.update.updateDocumentLength = _EXPECTED_REPORTED_JSON_SIZE; /* Generate a Shadow document for the reported state. To keep the client * token within 6 characters, it is modded by 1000000. */ @@ -386,15 +383,15 @@ static void _shadowUpdatedCallback( void * pCallbackContext, ( void ) pCallbackContext; /* Find the previous Shadow document. */ - previousFound = _getUpdatedState( pCallbackParam->callback.pDocument, - pCallbackParam->callback.documentLength, + previousFound = _getUpdatedState( pCallbackParam->u.callback.pDocument, + pCallbackParam->u.callback.documentLength, "previous", &pPrevious, &previousLength ); /* Find the current Shadow document. */ - currentFound = _getUpdatedState( pCallbackParam->callback.pDocument, - pCallbackParam->callback.documentLength, + currentFound = _getUpdatedState( pCallbackParam->u.callback.pDocument, + pCallbackParam->u.callback.documentLength, "current", &pCurrent, ¤tLength ); @@ -693,8 +690,8 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, /* Set the common members of the Shadow update document info. */ updateDocument.pThingName = pThingName; updateDocument.thingNameLength = thingNameLength; - updateDocument.update.pUpdateDocument = pUpdateDocument; - updateDocument.update.updateDocumentLength = _EXPECTED_DESIRED_JSON_SIZE; + updateDocument.u.update.pUpdateDocument = pUpdateDocument; + updateDocument.u.update.updateDocumentLength = _EXPECTED_DESIRED_JSON_SIZE; /* Publish Shadow updates at a set period. */ for( i = 1; i <= AWS_IOT_DEMO_SHADOW_UPDATE_COUNT; i++ ) diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index 493011282f..8101a67715 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -29,6 +29,7 @@ /* Standard includes. */ #include +#include #include #include @@ -42,19 +43,6 @@ /* MQTT include. */ #include "iot_mqtt.h" -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int snprintf( char *, - size_t, - const char *, - ... ); -/** @endcond */ - /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -187,6 +175,15 @@ extern int snprintf( char *, /*-----------------------------------------------------------*/ +/* Declaration of demo function. */ +int RunMqttDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ); + +/*-----------------------------------------------------------*/ + /** * @brief Called by the MQTT library when an operation completes. * diff --git a/doc/config/defender b/doc/config/defender index 99164235b0..11b787c92a 100644 --- a/doc/config/defender +++ b/doc/config/defender @@ -26,5 +26,6 @@ TAGFILES = doc/tag/main.tag=../main \ doc/tag/mqtt.tag=../mqtt \ doc/tag/logging.tag=../logging \ doc/tag/static_memory.tag=../static_memory \ + doc/tag/taskpool.tag=../taskpool \ doc/tag/platform.tag=../platform \ doc/tag/linear_containers.tag=../linear_containers diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index fe13ac9ae1..441c858d9c 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -295,37 +295,37 @@ typedef struct _defenderMetrics /** * Create a report, memory is allocated inside the function. */ -bool AwsIotDefenderInternal_CreateReport(); +bool AwsIotDefenderInternal_CreateReport( void ); /** * Get the buffer pointer of report. */ -uint8_t * AwsIotDefenderInternal_GetReportBuffer(); +uint8_t * AwsIotDefenderInternal_GetReportBuffer( void ); /** * Get the buffer size of report. */ -size_t AwsIotDefenderInternal_GetReportBufferSize(); +size_t AwsIotDefenderInternal_GetReportBufferSize( void ); /** * Delete a report when it is useless. Internally, memory will be freed. */ -void AwsIotDefenderInternal_DeleteReport(); +void AwsIotDefenderInternal_DeleteReport( void ); /** * Build three topics names used by defender library. */ -AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames(); +AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ); /** * Free the memory of three topics names. */ -void AwsIotDefenderInternal_DeleteTopicsNames(); +void AwsIotDefenderInternal_DeleteTopicsNames( void ); /** * Connect to AWS with MQTT. */ -IotMqttError_t AwsIotDefenderInternal_MqttConnect(); +IotMqttError_t AwsIotDefenderInternal_MqttConnect( void ); /** * Subscribe accept/reject defender topics. @@ -342,7 +342,7 @@ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, /** * Disconnect with AWS MQTT. */ -void AwsIotDefenderInternal_MqttDisconnect(); +void AwsIotDefenderInternal_MqttDisconnect( void ); /*----------------- Below this line are INTERNAL global variables --------------------*/ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index ee4c89d37c..c757600766 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -408,7 +408,7 @@ typedef struct _shadowOperation const char * pClientToken; /**< @brief Client token in update document. */ size_t clientTokenLength; /**< @brief Length of client token. */ } update; - }; + } u; /**< @brief Valid member depends on _shadowOperation_t.type. */ /* How to notify of an operation's completion. */ union diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 613620b6d5..4a0f6ca4fa 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -396,7 +396,7 @@ typedef struct AwsIotShadowCallbackParam const char * pDocument; /**< @brief Shadow delta or updated document. */ size_t documentLength; /**< @brief Length of Shadow delta or updated document. */ } callback; /**< @brief Shadow document from an incoming delta or updated topic. */ - }; + } u; /**< @brief Valid member depends on callback type. */ } AwsIotShadowCallbackParam_t; /** @@ -473,7 +473,7 @@ typedef struct AwsIotShadowDocumentInfo const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ } update; /**< @brief Valid members for @ref shadow_function_update. */ - }; + } u; /**< @brief Valid member depends on operation type. */ } AwsIotShadowDocumentInfo_t; /*------------------------ Shadow defined constants -------------------------*/ diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 6d2032b2a5..27586bba58 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -459,7 +459,7 @@ typedef struct IotMqttCallbackParam /* Valid when a connection is disconnected. */ IotMqttDisconnectReason_t disconnectReason; /**< @brief Why the MQTT connection was disconnected. */ - } u /**< @brief Valid member depends on callback type. */; + } u; /**< @brief Valid member depends on callback type. */ } IotMqttCallbackParam_t; /** diff --git a/lib/source/common/iot_logging.c b/lib/source/common/iot_logging.c index 752ec5aa58..df27ba9c91 100644 --- a/lib/source/common/iot_logging.c +++ b/lib/source/common/iot_logging.c @@ -29,6 +29,7 @@ /* Standard includes. */ #include +#include #include /* Platform clock include. */ @@ -37,26 +38,6 @@ /* Logging includes. */ #include "private/iot_logging.h" -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int sprintf( char *, - const char *, - ... ); -extern int snprintf( char *, - size_t, - const char *, - ... ); -extern int vsnprintf( char *, - size_t, - const char *, - va_list ); -/** @endcond */ - /*-----------------------------------------------------------*/ /* This implementation assumes the following values for the log level constants. @@ -86,14 +67,6 @@ extern int vsnprintf( char *, * function is used. */ #ifndef IotLogging_Puts - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - */ -extern int puts( const char * ); -/** @endcond */ - #define IotLogging_Puts puts #endif diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index abc3b972ef..2710f61952 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -89,16 +89,20 @@ static char _remoteAddr[ _REMOTE_ADDR_LENGTH ] = ""; /*---------------------- Helper Functions -------------------------*/ -static void copyMetricsFlag(); +void assertSuccess( IotSerializerError_t error ); -static bool getLatestMetricsData(); +void assertSuccessOrBufferToSmall( IotSerializerError_t error ); -static void freeMetricsData(); +static void copyMetricsFlag( void ); + +static bool getLatestMetricsData( void ); + +static void freeMetricsData( void ); static void tcpConnectionsCallback( void * param1, IotListDouble_t * pTcpConnectionsMetricsList ); -static void _serialize(); +static void _serialize( void ); static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObject ); @@ -120,14 +124,14 @@ void assertSuccessOrBufferToSmall( IotSerializerError_t error ) /*-----------------------------------------------------------*/ -uint8_t * AwsIotDefenderInternal_GetReportBuffer() +uint8_t * AwsIotDefenderInternal_GetReportBuffer( void ) { return _report.pDataBuffer; } /*-----------------------------------------------------------*/ -size_t AwsIotDefenderInternal_GetReportBufferSize() +size_t AwsIotDefenderInternal_GetReportBufferSize( void ) { /* Encoder might over-calculate the needed size. Therefor encoded size might be smaller than buffer size: _report.size. */ return _report.pDataBuffer == NULL ? 0 @@ -136,7 +140,7 @@ size_t AwsIotDefenderInternal_GetReportBufferSize() /*-----------------------------------------------------------*/ -bool AwsIotDefenderInternal_CreateReport() +bool AwsIotDefenderInternal_CreateReport( void ) { /* Assert report buffer is not allocated. */ AwsIotDefender_Assert( _report.pDataBuffer == NULL && _report.size == 0 ); @@ -192,7 +196,7 @@ bool AwsIotDefenderInternal_CreateReport() /*-----------------------------------------------------------*/ -void AwsIotDefenderInternal_DeleteReport() +void AwsIotDefenderInternal_DeleteReport( void ) { /* Destroy the encoder object. */ _AwsIotDefenderEncoder.destroy( &( _report.object ) ); @@ -222,7 +226,7 @@ void AwsIotDefenderInternal_DeleteReport() * } * } */ -static void _serialize() +static void _serialize( void ) { IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; @@ -311,7 +315,7 @@ static void _serialize() /*-----------------------------------------------------------*/ -static void copyMetricsFlag() +static void copyMetricsFlag( void ) { /* Copy the metrics flags to snapshot so that it is unlocked quicker. */ IotMutex_Lock( &_AwsIotDefenderMetrics.mutex ); @@ -324,7 +328,7 @@ static void copyMetricsFlag() /*-----------------------------------------------------------*/ -static bool getLatestMetricsData() +static bool getLatestMetricsData( void ) { bool result = true; @@ -345,7 +349,7 @@ static bool getLatestMetricsData() /*-----------------------------------------------------------*/ -static void freeMetricsData() +static void freeMetricsData( void ) { if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) { diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index 81ef414ae1..eeaaefede9 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -46,7 +46,7 @@ static char * _pRejectTopic = NULL; /*-----------------------------------------------------------*/ -AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames() +AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) { AwsIotDefenderError_t returnedError = AWS_IOT_DEFENDER_SUCCESS; @@ -96,7 +96,7 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames() /*-----------------------------------------------------------*/ -void AwsIotDefenderInternal_DeleteTopicsNames() +void AwsIotDefenderInternal_DeleteTopicsNames( void ) { AwsIotDefender_FreeTopic( _pPublishTopic ); AwsIotDefender_FreeTopic( _pAcceptTopic ); @@ -108,7 +108,7 @@ void AwsIotDefenderInternal_DeleteTopicsNames() /*-----------------------------------------------------------*/ -IotMqttError_t AwsIotDefenderInternal_MqttConnect() +IotMqttError_t AwsIotDefenderInternal_MqttConnect( void ) { return IotMqtt_Connect( &_startInfo.mqttNetworkInfo, &_startInfo.mqttConnectionInfo, @@ -162,7 +162,7 @@ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, /*-----------------------------------------------------------*/ -void AwsIotDefenderInternal_MqttDisconnect() +void AwsIotDefenderInternal_MqttDisconnect( void ) { IotMqtt_Disconnect( _mqttConnection, false ); } diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 5f3f155f41..459aa383fa 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -875,7 +875,7 @@ IotMqttError_t IotMqtt_Init( void ) /*-----------------------------------------------------------*/ -void IotMqtt_Cleanup() +void IotMqtt_Cleanup( void ) { /* Call any additional serializer cleanup initialization function if serializer * overrides are enabled. */ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 19e9f4f402..baf6ccb2a4 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -280,7 +280,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, { /* Check memory allocation function for waitable GET. */ if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && - ( pDocumentInfo->get.mallocDocument == NULL ) ) + ( pDocumentInfo->u.get.mallocDocument == NULL ) ) { IotLogError( "Memory allocation function must be set for waitable Shadow GET." ); @@ -291,8 +291,8 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, else { /* Check UPDATE document pointer and length. */ - if( ( pDocumentInfo->update.pUpdateDocument == NULL ) || - ( pDocumentInfo->update.updateDocumentLength == 0 ) ) + if( ( pDocumentInfo->u.update.pUpdateDocument == NULL ) || + ( pDocumentInfo->u.update.updateDocumentLength == 0 ) ) { IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" " have length 0." ); @@ -556,8 +556,8 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, callbackParam.mqttConnection = pMessage->mqttConnection; callbackParam.pThingName = pSubscription->pThingName; callbackParam.thingNameLength = pSubscription->thingNameLength; - callbackParam.callback.pDocument = pMessage->u.message.info.pPayload; - callbackParam.callback.documentLength = pMessage->u.message.info.payloadLength; + callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; + callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; /* Invoke the callback function. */ pSubscription->callbacks[ type ].function( pSubscription->callbacks[ type ].pCallbackContext, @@ -791,7 +791,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); /* Copy the memory allocation function. */ - pOperation->get.mallocDocument = pGetInfo->get.mallocDocument; + pOperation->u.get.mallocDocument = pGetInfo->u.get.mallocDocument; /* Set the reference if provided. This must be done before the Shadow operation * is processed. */ @@ -889,8 +889,8 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, } /* Check UPDATE document for a client token. */ - if( IotJsonUtils_FindJsonValue( pUpdateInfo->update.pUpdateDocument, - pUpdateInfo->update.updateDocumentLength, + if( IotJsonUtils_FindJsonValue( pUpdateInfo->u.update.pUpdateDocument, + pUpdateInfo->u.update.updateDocumentLength, _CLIENT_TOKEN_KEY, _CLIENT_TOKEN_KEY_LENGTH, &pClientToken, @@ -930,9 +930,9 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); /* Allocate memory for the client token. */ - pOperation->update.pClientToken = AwsIotShadow_MallocString( clientTokenLength ); + pOperation->u.update.pClientToken = AwsIotShadow_MallocString( clientTokenLength ); - if( pOperation->update.pClientToken == NULL ) + if( pOperation->u.update.pClientToken == NULL ) { IotLogError( "Failed to allocate memory for Shadow update client token." ); _AwsIotShadow_DestroyOperation( pOperation ); @@ -942,10 +942,10 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /* Copy the client token. The client token must be copied in case the application * frees the buffer containing it. */ - ( void ) memcpy( ( void * ) pOperation->update.pClientToken, + ( void ) memcpy( ( void * ) pOperation->u.update.pClientToken, pClientToken, clientTokenLength ); - pOperation->update.clientTokenLength = clientTokenLength; + pOperation->u.update.clientTokenLength = clientTokenLength; /* Set the reference if provided. This must be done before the Shadow operation * is processed. */ @@ -1067,8 +1067,8 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, if( ( operation->type == _SHADOW_GET ) && ( status == AWS_IOT_SHADOW_SUCCESS ) ) { - *pShadowDocument = operation->get.pDocument; - *pShadowDocumentLength = operation->get.documentLength; + *pShadowDocument = operation->u.get.pDocument; + *pShadowDocumentLength = operation->u.get.documentLength; } /* Destroy the Shadow operation. */ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index f778b1ec15..1218b31278 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -180,8 +180,8 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, /* Check document pointers. */ AwsIotShadow_Assert( pParam->pDocument != NULL ); AwsIotShadow_Assert( pParam->documentLength > 0 ); - AwsIotShadow_Assert( pOperation->update.pClientToken != NULL ); - AwsIotShadow_Assert( pOperation->update.clientTokenLength > 0 ); + AwsIotShadow_Assert( pOperation->u.update.pClientToken != NULL ); + AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0 ); IotLogDebug( "Verifying client tokens for Shadow UPDATE." ); @@ -197,9 +197,9 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, * matches. */ if( match == true ) { - match = ( clientTokenLength == pOperation->update.clientTokenLength ) && + match = ( clientTokenLength == pOperation->u.update.clientTokenLength ) && ( strncmp( pClientToken, - pOperation->update.pClientToken, + pOperation->u.update.pClientToken, clientTokenLength ) == 0 ); } else @@ -381,20 +381,20 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, * info. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { - pOperation->get.pDocument = pPublishInfo->pPayload; - pOperation->get.documentLength = pPublishInfo->payloadLength; + pOperation->u.get.pDocument = pPublishInfo->pPayload; + pOperation->u.get.documentLength = pPublishInfo->payloadLength; } else { IotLogDebug( "Allocating new buffer for waitable Shadow GET." ); /* Parameter validation should not have allowed a NULL malloc function. */ - AwsIotShadow_Assert( pOperation->get.mallocDocument != NULL ); + AwsIotShadow_Assert( pOperation->u.get.mallocDocument != NULL ); /* Allocate a buffer for the retrieved document. */ - pOperation->get.pDocument = pOperation->get.mallocDocument( pPublishInfo->payloadLength ); + pOperation->u.get.pDocument = pOperation->u.get.mallocDocument( pPublishInfo->payloadLength ); - if( pOperation->get.pDocument == NULL ) + if( pOperation->u.get.pDocument == NULL ) { IotLogError( "Failed to allocate buffer for retrieved Shadow document." ); @@ -403,10 +403,10 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, else { /* Copy the retrieved document. */ - ( void ) memcpy( ( void * ) pOperation->get.pDocument, + ( void ) memcpy( ( void * ) pOperation->u.get.pDocument, pPublishInfo->pPayload, pPublishInfo->payloadLength ); - pOperation->get.documentLength = pPublishInfo->payloadLength; + pOperation->u.get.documentLength = pPublishInfo->payloadLength; } } @@ -506,11 +506,11 @@ void _AwsIotShadow_DestroyOperation( void * pData ) /* If this is a Shadow update, free any allocated client token. */ if( ( pOperation->type == _SHADOW_UPDATE ) && - ( pOperation->update.pClientToken != NULL ) ) + ( pOperation->u.update.pClientToken != NULL ) ) { - AwsIotShadow_Assert( pOperation->update.clientTokenLength > 0 ); + AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0 ); - AwsIotShadow_FreeString( ( void * ) ( pOperation->update.pClientToken ) ); + AwsIotShadow_FreeString( ( void * ) ( pOperation->u.update.pClientToken ) ); } /* Free the memory used to hold operation data. */ @@ -733,8 +733,8 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ if( pOperation->type == _SHADOW_UPDATE ) { - publishInfo.pPayload = pDocumentInfo->update.pUpdateDocument; - publishInfo.payloadLength = pDocumentInfo->update.updateDocumentLength; + publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; + publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; } /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, @@ -854,16 +854,16 @@ void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) /* Set the common members of the callback parameter. */ callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; callbackParam.mqttConnection = pOperation->mqttConnection; - callbackParam.operation.result = pOperation->status; - callbackParam.operation.reference = pOperation; + callbackParam.u.operation.result = pOperation->status; + callbackParam.u.operation.reference = pOperation; callbackParam.pThingName = pSubscription->pThingName; callbackParam.thingNameLength = pSubscription->thingNameLength; /* Set the members of the callback parameter for a received document. */ if( pOperation->type == _SHADOW_GET ) { - callbackParam.operation.get.pDocument = pOperation->get.pDocument; - callbackParam.operation.get.documentLength = pOperation->get.documentLength; + callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; + callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; } pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, diff --git a/platform/source/posix/linux/iot_metrics.c b/platform/source/posix/linux/iot_metrics.c index c749dbd8ad..2a2e5a9d7c 100644 --- a/platform/source/posix/linux/iot_metrics.c +++ b/platform/source/posix/linux/iot_metrics.c @@ -59,7 +59,7 @@ static bool _tcpConnectionMatch( const IotLink_t * pLink, /*-----------------------------------------------------------*/ -bool IotMetrics_Init() +bool IotMetrics_Init( void ) { bool result = false; diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index bb0de70265..2c21b95049 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -391,7 +391,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, * * @param[in] pNetworkConnection An established TCP connection. * @param[in] pServerName Remote host name, used for server name indication. - * @param[in] pCredentials TLS setup parameters. + * @param[in] pOpensslCredentials TLS setup parameters. * * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. */ diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 3cb9493a42..0338e9dda4 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -117,18 +117,18 @@ static void _assertEvent( AwsIotDefenderEventType_t event, static void _waitForMetricsAccepted( uint32_t timeoutSec ); /* Verify common section of metrics report. */ -static void _verifyMetricsCommon(); +static void _verifyMetricsCommon( void ); /* Verify tcp connections in metrics report. */ static void _verifyTcpConections( int total, ... ); /* Indicate this test doesn't actually publish report. */ -static void _publishMetricsNotNeeded(); +static void _publishMetricsNotNeeded( void ); -static void _resetCalbackInfo(); +static void _resetCalbackInfo( void ); -static char * _getIotAddress(); +static char * _getIotAddress( void ); TEST_GROUP( Full_DEFENDER ); @@ -597,7 +597,7 @@ static void _copyDataCallbackFunction( void * param1, /*-----------------------------------------------------------*/ -static void _publishMetricsNotNeeded() +static void _publishMetricsNotNeeded( void ) { /* Given a dummy IoT endpoint to fail network connection. */ _serverInfo.pHostName = "dummy endpoint"; @@ -605,7 +605,7 @@ static void _publishMetricsNotNeeded() /*-----------------------------------------------------------*/ -static void _resetCalbackInfo() +static void _resetCalbackInfo( void ) { /* Clean data buffer. */ memset( _payloadBuffer, 0, _PAYLOAD_MAX_SIZE ); @@ -642,7 +642,7 @@ static void _assertEvent( AwsIotDefenderEventType_t event, /*-----------------------------------------------------------*/ /* Assert the cause of rejection is throttle. */ -static void _assertRejectDueToThrottle() +static void _assertRejectDueToThrottle( void ) { TEST_ASSERT_NOT_NULL( _callbackInfo.pPayload ); TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.payloadLength ); @@ -731,7 +731,7 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) /*-----------------------------------------------------------*/ -static void _verifyMetricsCommon() +static void _verifyMetricsCommon( void ) { TEST_ASSERT_NOT_NULL( _callbackInfo.pMetricsReport ); TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.metricsReportLength ); @@ -890,7 +890,7 @@ static void _verifyTcpConections( int total, /*-----------------------------------------------------------*/ -static char * _getIotAddress() +static char * _getIotAddress( void ) { static char iotAddress[ _MAX_ADDRESS_LENGTH ]; diff --git a/tests/mqtt/access/iot_test_access_mqtt_api.c b/tests/mqtt/access/iot_test_access_mqtt_api.c index 341fcec6b8..5116d3d69e 100644 --- a/tests/mqtt/access/iot_test_access_mqtt_api.c +++ b/tests/mqtt/access/iot_test_access_mqtt_api.c @@ -28,6 +28,10 @@ * compiled by itself. */ +_mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, + const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds ); + /*-----------------------------------------------------------*/ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, diff --git a/tests/mqtt/access/iot_test_access_mqtt_subscription.c b/tests/mqtt/access/iot_test_access_mqtt_subscription.c index 55cee5e850..fca7f2930a 100644 --- a/tests/mqtt/access/iot_test_access_mqtt_subscription.c +++ b/tests/mqtt/access/iot_test_access_mqtt_subscription.c @@ -28,6 +28,12 @@ * and never compiled by itself. */ +bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); + +bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, + void * pMatch ); + /*-----------------------------------------------------------*/ bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 555617dee6..fb50001ef8 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -28,6 +28,7 @@ #include "iot_config.h" /* Standard includes. */ +#include #include /* SDK initialization include. */ @@ -46,19 +47,6 @@ /* Test framework includes. */ #include "unity_fixture.h" -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int snprintf( char *, - size_t, - const char *, - ... ); -/** @endcond */ - /*-----------------------------------------------------------*/ /** diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index e05a937f93..796b856c73 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -281,14 +281,14 @@ static IotMqttError_t _deserializePingresp( _mqttPacket_t * pPingresp ) /*-----------------------------------------------------------*/ /** - * @brief Reset the status of an #_mqttOperation_t and push it to the queue of + * @brief Reset the status of an #_mqttOperation_t and push it to the list of * MQTT operations awaiting network response. */ static void _operationResetAndPush( _mqttOperation_t * pOperation ) { pOperation->u.operation.status = IOT_MQTT_STATUS_PENDING; pOperation->u.operation.jobReference = 1; - IotDeQueue_EnqueueHead( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); + IotListDouble_InsertHead( &( _pMqttConnection->pendingResponse ), &( pOperation->link ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index d2fec38233..92320d76c9 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -28,6 +28,7 @@ #include "iot_config.h" /* Standard includes. */ +#include #include /* SDK initialization include. */ @@ -46,19 +47,6 @@ /* MQTT test access include. */ #include "iot_test_access_mqtt.h" -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int snprintf( char *, - size_t, - const char *, - ... ); -/** @endcond */ - /*-----------------------------------------------------------*/ /** diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 25985c3ec2..285e9a0497 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -28,6 +28,7 @@ #include "iot_config.h" /* Standard includes. */ +#include #include /* SDK initialization include. */ @@ -58,19 +59,6 @@ #error "Shadow API unit tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." #endif -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int snprintf( char *, - size_t, - const char *, - ... ); -/** @endcond */ - /*-----------------------------------------------------------*/ /** @@ -159,8 +147,8 @@ static void _operationComplete( void * pArgument, /* Check parameters against received operation information. */ AwsIotShadow_Assert( pOperation->callbackType == pParams->expectedType ); AwsIotShadow_Assert( pOperation->mqttConnection == _mqttConnection ); - AwsIotShadow_Assert( pOperation->operation.result == AWS_IOT_SHADOW_SUCCESS ); - AwsIotShadow_Assert( pOperation->operation.reference == pParams->operation ); + AwsIotShadow_Assert( pOperation->u.operation.result == AWS_IOT_SHADOW_SUCCESS ); + AwsIotShadow_Assert( pOperation->u.operation.reference == pParams->operation ); AwsIotShadow_Assert( pOperation->thingNameLength == _THING_NAME_LENGTH ); AwsIotShadow_Assert( strncmp( pOperation->pThingName, AWS_IOT_TEST_SHADOW_THING_NAME, @@ -169,10 +157,10 @@ static void _operationComplete( void * pArgument, /* Check the retrieved Shadow document. */ if( pOperation->callbackType == AWS_IOT_SHADOW_GET_COMPLETE ) { - AwsIotShadow_Assert( pOperation->operation.get.documentLength > 0 ); + AwsIotShadow_Assert( pOperation->u.operation.get.documentLength > 0 ); - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pOperation->operation.get.pDocument, - pOperation->operation.get.documentLength, + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pOperation->u.operation.get.pDocument, + pOperation->u.operation.get.documentLength, "key", 3, &pJsonValue, @@ -203,8 +191,8 @@ static void _deltaCallback( void * pArgument, AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); /* Check delta document state. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, "key", 3, &pValue, @@ -213,8 +201,8 @@ static void _deltaCallback( void * pArgument, AwsIotShadow_Assert( strncmp( pValue, "true", valueLength ) == 0 ); /* Check delta document client token. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, "clientToken", 11, &pClientToken, @@ -243,8 +231,8 @@ static void _updatedCallback( void * pArgument, AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); /* Check updated document previous state. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, "previous", 8, &pPrevious, @@ -255,8 +243,8 @@ static void _updatedCallback( void * pArgument, 26 ) == 0 ); /* Check updated document current state. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, "current", 7, &pCurrent, @@ -267,8 +255,8 @@ static void _updatedCallback( void * pArgument, 33 ) == 0 ); /* Check updated document client token. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->callback.pDocument, - pCallback->callback.documentLength, + AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, "clientToken", 11, &pClientToken, @@ -310,8 +298,8 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) callbackParam.expectedType = AWS_IOT_SHADOW_UPDATE_COMPLETE; /* Set the members of the Shadow document info for UPDATE. */ - documentInfo.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; - documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; + documentInfo.u.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; + documentInfo.u.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ status = AwsIotShadow_Update( _mqttConnection, @@ -382,8 +370,8 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) documentInfo.qos = qos; /* Set the members of the Shadow document info for UPDATE. */ - documentInfo.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; - documentInfo.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; + documentInfo.u.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; + documentInfo.u.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ status = AwsIotShadow_TimedUpdate( _mqttConnection, @@ -393,7 +381,7 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Set the members of the Shadow document info for GET. */ - documentInfo.get.mallocDocument = IotTest_Malloc; + documentInfo.u.get.mallocDocument = IotTest_Malloc; /* Retrieve the Shadow document. */ status = AwsIotShadow_TimedGet( _mqttConnection, @@ -618,8 +606,8 @@ TEST( Shadow_System, DeltaCallback ) /* Set a desired state in the Update document. */ updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; updateDocument.thingNameLength = _THING_NAME_LENGTH; - updateDocument.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; - updateDocument.update.updateDocumentLength = 65; + updateDocument.u.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; + updateDocument.u.update.updateDocumentLength = 65; if( TEST_PROTECT() ) { @@ -639,8 +627,8 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Set a different reported state in the Update document. */ - updateDocument.update.pUpdateDocument = "{\"state\": {\"reported\": {\"key\": false}}, \"clientToken\":\"shadowtest\"}"; - updateDocument.update.updateDocumentLength = 67; + updateDocument.u.update.pUpdateDocument = "{\"state\": {\"reported\": {\"key\": false}}, \"clientToken\":\"shadowtest\"}"; + updateDocument.u.update.updateDocumentLength = 67; /* Create a Shadow document with a reported state. */ status = AwsIotShadow_TimedUpdate( _mqttConnection, @@ -696,8 +684,8 @@ TEST( Shadow_System, UpdatedCallback ) /* Set a desired state in the Update document. */ updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; updateDocument.thingNameLength = _THING_NAME_LENGTH; - updateDocument.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; - updateDocument.update.updateDocumentLength = 65; + updateDocument.u.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; + updateDocument.u.update.updateDocumentLength = 65; if( TEST_PROTECT() ) { diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 686b94eecc..03fb038bee 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -615,8 +615,8 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Update with no client token. */ - documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}}"; - documentInfo.update.updateDocumentLength = 29; + documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}}"; + documentInfo.u.update.updateDocumentLength = 29; status = AwsIotShadow_Update( _pMqttConnection, &documentInfo, 0, @@ -625,9 +625,9 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Client token too long. */ - documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}},\"clientToken\": " - "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; - documentInfo.update.updateDocumentLength = 146; + documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}},\"clientToken\": " + "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; + documentInfo.u.update.updateDocumentLength = 146; status = AwsIotShadow_Update( _pMqttConnection, &documentInfo, 0, @@ -740,7 +740,7 @@ TEST( Shadow_Unit_API, GetMallocFail ) documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; documentInfo.qos = IOT_MQTT_QOS_1; - documentInfo.get.mallocDocument = IotTest_Malloc; + documentInfo.u.get.mallocDocument = IotTest_Malloc; for( i = 0; ; i++ ) { @@ -804,8 +804,8 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) documentInfo.pThingName = _TEST_THING_NAME; documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; documentInfo.qos = IOT_MQTT_QOS_1; - documentInfo.update.pUpdateDocument = "{\"state\":{\"reported\":{null}},\"clientToken\":\"TEST\"}"; - documentInfo.update.updateDocumentLength = 50; + documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}},\"clientToken\":\"TEST\"}"; + documentInfo.u.update.updateDocumentLength = 50; for( i = 0; ; i++ ) { diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 729410d2d5..1ae645b6cb 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -29,21 +29,9 @@ /* Standard includes. */ #include +#include #include -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Including stdio.h also brings in unwanted (and conflicting) symbols on some - * platforms. Therefore, any functions in stdio.h needed in this file have an - * extern declaration here. */ -extern int vsnprintf( char *, - size_t, - const char *, - va_list ); -/** @endcond */ - /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h index aa9696cf73..2a2b942e3f 100644 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h @@ -45,6 +45,8 @@ void* unity_realloc(void * oldMem, size_t size); void unity_free(void * mem); /* Thread-safety wrappers for the unity memory functions. */ +extern pthread_mutex_t CriticalSectionMutex; + void* unity_malloc_mt(size_t size); void* unity_calloc_mt(size_t num, size_t size); void* unity_realloc_mt(void * oldMem, size_t size); diff --git a/third_party/unity/unity/unity_memory_mt.c b/third_party/unity/unity/unity_memory_mt.c deleted file mode 100644 index b663b9a61a..0000000000 --- a/third_party/unity/unity/unity_memory_mt.c +++ /dev/null @@ -1,89 +0,0 @@ -/* Wrappers that make the unity memory functions thread-safe. Implemented for - * POSIX systems. */ - -#include "unity_fixture.h" -#include "unity_fixture_malloc_overrides.h" -#include - -pthread_mutex_t CriticalSectionMutex = PTHREAD_MUTEX_INITIALIZER; - -void UnityMalloc_StartTest(void) -{ - pthread_mutex_lock(&CriticalSectionMutex); - UnityMalloc_StartTest(); - pthread_mutex_unlock(&CriticalSectionMutex); -} - -void UnityMalloc_EndTest(void) -{ - pthread_mutex_lock(&CriticalSectionMutex); - UnityMalloc_EndTest(); - pthread_mutex_unlock(&CriticalSectionMutex); -} - -void UnityMalloc_MakeMallocFailAfterCount(int countdown) -{ - pthread_mutex_lock(&CriticalSectionMutex); - UnityMalloc_MakeMallocFailAfterCount(countdown); - pthread_mutex_unlock(&CriticalSectionMutex); -} - -void* unity_malloc_mt(size_t size) -{ - void* mem = NULL; - - if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; - - mem = unity_malloc(size); - - if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) - { - unity_free(mem); - mem = NULL; - } - - return mem; -} - -void* unity_calloc_mt(size_t num, size_t size) -{ - void* mem = NULL; - - if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; - - mem = unity_calloc(num, size); - - if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) - { - unity_free(mem); - mem = NULL; - } - - return mem; -} - -void* unity_realloc_mt(void * oldMem, size_t size) -{ - void* mem = NULL; - - if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; - - mem = unity_realloc(oldMem, size); - - if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) - { - unity_free(mem); - mem = NULL; - } - - return mem; -} - -void unity_free_mt(void * mem) -{ - pthread_mutex_lock(&CriticalSectionMutex); - - unity_free(mem); - - pthread_mutex_unlock(&CriticalSectionMutex); -} From 0d2004f30f5b553896a50d2bdebbf48546a50d92 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 17 Apr 2019 12:43:00 -0700 Subject: [PATCH 102/844] Update documentation (#381) --- README.md | 2 +- doc/guide/building.txt | 4 +- doc/guide/developer.txt | 2 + doc/guide/porting.txt | 123 +++++++++++++++++++++++++++++++++++++++ doc/mainpage.txt | 11 ++++ lib/include/iot_atomic.h | 15 +++-- 6 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 doc/guide/porting.txt diff --git a/README.md b/README.md index f4331e2255..5ae1db5917 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. * 4. [Create an AWS IoT Policy](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-policy.html) 5. [Attach an AWS IoT Policy to a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/attach-policy-to-certificate.html) 6. [Attach a Certificate to a Thing](https://docs.aws.amazon.com/iot/latest/developerguide/attach-cert-thing.html) -2. *Optional:* Set the following `#define` in [iot_demo_config.h](demos/include/iot_demo_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. +2. *Optional:* Set the following `#define` in [iot_config.h](demos/iot_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. - Set `IOT_DEMO_IDENTIFIER` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. - Set `IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 12976527f1..1bce430d10 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -19,7 +19,7 @@ In addition, credentials and Things for AWS IoT must be provisioned before runni @section building_demo Building the demo applications @brief How to build the demo applications. -Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/iot_demo_config.h`. Any undefined settings will use a default value when possible. +Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/iot_config.h`. Any undefined settings will use a default value when possible. The demo applications build with CMake. @@ -103,7 +103,7 @@ See @ref IOT_DEMO_IDENTIFIER for the compile-time default settings of this optio @section building_tests Building and running the tests @brief How to build and run the SDK tests. -Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_tests_config.h`. +Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_config.h`. Unlike the demos, the tests are currently only supported on POSIX systems that support all of the POSIX prerequisites. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. @code{sh} diff --git a/doc/guide/developer.txt b/doc/guide/developer.txt index 46e88dd5ba..8e9b7873ba 100644 --- a/doc/guide/developer.txt +++ b/doc/guide/developer.txt @@ -3,6 +3,8 @@ @brief Guide for maintaining and contributing code to this project. This guide contains the following pages. All pages assume the reader has intermediate familiarity with this SDK. +- @subpage guide_developer_porting
+ @copybrief guide_developer_porting - @subpage guide_developer_styleguide
@copybrief guide_developer_styleguide */ diff --git a/doc/guide/porting.txt b/doc/guide/porting.txt new file mode 100644 index 0000000000..605d604281 --- /dev/null +++ b/doc/guide/porting.txt @@ -0,0 +1,123 @@ +/** +@page guide_developer_porting Porting Guide +@brief Guide for porting this SDK to a new platform. + +@section guide_developer_porting_build Porting the build system +@brief Guide for porting the SDK's build system. + +The CMake-based build system present in [the SDK's GitHub repo](https://github.com/aws/aws-iot-device-sdk-embedded-C) targets desktop systems and builds libraries into shared libraries. The build selects the [types of the platform layer](@ref platform_datatypes_handles) based on the detected host OS and automatically configures the platform layer. See @ref building for more information on the build system. + +In general, this SDK should build with C compilers in C99 mode. Currently, we do not guarantee builds with a C++ compiler. Compilers that provide intrinsics for atomic operations are recommended; see @ref guide_developer_porting_atomic for more information. + +@subsection guide_developer_porting_directory_layout Directory layout +@brief The following directories contain the SDK's source code and are relevant for porting. + +Of the directories listed below, only `platform/` should be modified during porting. Some directories present in the GitHub repo (such as `cbmc`, `doc`, and `scripts`) are not relevant for porting and therefore not listed. + +As relative paths from the SDK's root directory: +- `demos/`
+ SDK sample applications. These files do not need to be ported, but are useful as a starting point. + - `app/`
+ Contains demo applications for various systems, most importantly the `main()` functions. + - `include/`
+ Headers only needed to build the demos. These do not need to be ported. + - `source/`
+ Platform-independent demo sources. These do not need to be ported. + - `iot_config.h`
+ The config header for the demo applications. Useful as an example. +- `lib/`
+ Platform-independent SDK files. This directory (and all its subdirectories) should be copied and not modified. + - `include/`
+ Library headers. + - `platform/`
+ Interface of the platform layer, to be implemented in the `platform/` directory.
+ - `private/`
+ Library internal headers. + - `types/`
+ Library types headers. + - `source/`
+ Library source files. + - `common, defender, mqtt, ...`
+ Individual library sources are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. +- `platform/`
+ Platform-dependent SDK files that must be ported. The existing ports in this directory may be useful examples.
+ - `include/`
+ Platform header files. Unlike the headers in `lib/include/`, headers in this directory may directly use system types.
+ - `atomic/`
+ Existing ports of atomic operations. See @ref guide_developer_porting_atomic for how to create a new port.
+ - `posix, ...`
+ Headers for existing ports of the [platform layer](@ref platform), named after the platform they target. See @ref guide_developer_porting_platform for how to create a new port.
+ - `source/` + - `posix, ...`
+ Headers for existing ports of the [platform layer](@ref platform), named after the platform they target. See @ref guide_developer_porting_platform for how to create a new port.
+- `tests/`
+ SDK tests that can be used to verify ports. + - `common, defender, mqtt, ...`
+ Individual library tests are contained in directories matching the library name. When building tests, @ref IOT_BUILD_TESTS should be set to `1` globally. +- `third_party/`
+ Third-party library code. This directory (and all its subdirectories) should be copied and not modified. + - `tinycbor/`
+ [Intel's tinyCBOR](https://github.com/intel/tinycbor), consumed as a Git submodule. + - `unity/`
+ [Unity test framework](https://github.com/ThrowTheSwitch/Unity). Modifications were made to make its `malloc` overrides thread-safe. + +@subsection guide_developer_porting_include_paths Include paths +@brief The following paths should be passed as include paths to the compiler when building this SDK. + +Include paths that are always required: +- The path of the [config file](@ref global_config), `iot_config.h`. For example: + - In the SDK demos, `iot_config.h` is in `demos/`. + - In the SDK tests, `iot_config.h` is in `tests/`. +- `lib/include` +- `platform/include` +- `third_party/tinycbor/tinycbor/src` (when building the serializer library) + +Additional include paths required to build the demos: +- `demos/include` + +Additional include paths required to build the tests: +- `third_party/unity/unity` +- `third_party/unity/unity/fixture` +- `tests/mqtt/access` (when building the MQTT or Shadow tests) + +In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1` globally when building tests. + +@section guide_developer_porting_platform Porting the platform layer +@brief Guide for porting the SDK's [platform layer](@ref platform). + +@see [Platform layer](@ref platform) + +@subsection guide_developer_porting_platform_types Platform types +@brief [Types](@ref platform_datatypes_handles) that must be set in the platform layer. + +@see [Platform types](@ref platform_datatypes_handles) for a list of all types that must be set.
+`platform/include/posix/iot_platform_types_posix.h` for an example of setting the types on POSIX systems. + +The platform types should be set in the [config file](@ref global_config), `iot_config.h`, or in another header included by the config file. As an example, the header `iot_platform_types_posix.h` sets the platform types to the matching POSIX system types. This header is then included in `iot_config.h` by the provided CMake build system. + +@attention Any type configuration headers included by the config file must never include other library files. Since this file will be included by every SDK source file, take care not to include too many unnecessary symbols. + +@subsection guide_developer_porting_platform_functions Platform functions +@brief [Functions](@ref platform_functions) that must be implemented for the platform layer. + +@see [Platform functions](@ref platform_functions) for a list of all functions that must be implemented.
+`lib/include/platform` for the interfaces of the platform functions.
+`platform/source/posix` for examples of functions implemented for POSIX systems. + +@section guide_developer_porting_atomic Porting atomic operations +@brief Guide for porting the SDK's atomic operations. + +Porting atomic operations is not required to use the SDK. However, atomic operations improve the performance of all libraries; therefore, porting them is recommended. + +Unlike the platform layer, which relies on APIs provided by the host operating system, the SDK's atomic ports rely on facilities provided by compilers. Many compilers, particularly recent versions that support C11 atomic features, provide compiler intrinsics for atomic operations. The header `lib/include/iot_atomic.h` will select an atomic port to use based on the detected compiler. If no suitable atomic port is available, then a generic atomic port will be used. The generic atomic port is slower than native compiler ports, but will work on all systems. + +To provide a new compiler port, the preprocessor constant `IOT_ATOMIC_USE_PORT` should be defined to `1` in the [config file](@ref global_config). When `IOT_ATOMIC_USE_PORT` is `1`, a new compiler port should be implemented in a file named `iot_atomic_port.h`. This file should be created in `platform/include/atomic/`, as it will be included with the directive `#include "atomic/iot_atomic_port.h"`. + +@see `lib/include/iot_atomic.h` for the header that selects the atomic port to use.
+`platform/include/atomic/iot_atomic_gcc.h` for a port that uses GNU compiler intrinsics. + +@section guide_developer_porting_metrics Porting Device Defender metrics +@brief Guide for porting metrics reported by Device Defender. + +This section is currently a placeholder. +*/ diff --git a/doc/mainpage.txt b/doc/mainpage.txt index d6c68e5eda..bf697b15bd 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -139,6 +139,17 @@ In the demos specific to AWS IoT, this identifier becomes the Thing Name, which /** @globalconfigpage{tests,Test,tests} +@section IOT_BUILD_TESTS +@brief Specifies that the tests are being built. + +This setting modifies the behavior of the libraries under test. For example, it may expose internal variables or functions for testing. + +`IOT_BUILD_TESTS` will be automatically configured during build and generally does not need to be defined. + +@configpossible `1` (tests are being built) or `0` (tests are not being built)
+@configrecommended This must always be set to `1` if building any test files. Otherwise, it should be set to `0`.
+@configdefault `0` + @section IOT_TEST_SECURED_CONNECTION @brief Determines if the tests use a TLS-secured connection with the remote host. diff --git a/lib/include/iot_atomic.h b/lib/include/iot_atomic.h index 41c91b9e37..154b353bae 100644 --- a/lib/include/iot_atomic.h +++ b/lib/include/iot_atomic.h @@ -23,8 +23,10 @@ * @file iot_atomic.h * @brief Chooses the appropriate atomic operations header. * - * This file first checks if the operating system is Amazon FreeRTOS. If so, the - * atomic header provided with Amazon FreeRTOS is used. + * This file first checks if an atomic port is provided. + * + * Otherwise, if the operating system is Amazon FreeRTOS, the atomic header + * provided with Amazon FreeRTOS is used. * * Otherwise, this file checks the compiler and chooses an appropriate atomic * header depending on the compiler. @@ -37,7 +39,10 @@ #ifndef IOT_ATOMIC_H_ #define IOT_ATOMIC_H_ -#if defined( __free_rtos__ ) +/* Use an atomic port if provided. */ +#if IOT_ATOMIC_USE_PORT == 1 + #include "atomic/iot_atomic_port.h" +#elif defined( __free_rtos__ ) /* Use the FreeRTOS atomic operation header on FreeRTOS. */ #include "atomic.h" #elif defined( __GNUC__ ) @@ -60,9 +65,9 @@ #define IOT_ATOMIC_GENERIC 1 #endif #endif /* ifdef __clang__ */ -#else /* if defined( __free_rtos__ ) */ +#else /* if IOT_ATOMIC_USE_PORT == 1 */ #define IOT_ATOMIC_GENERIC 1 -#endif /* if defined( __free_rtos__ ) */ +#endif /* if IOT_ATOMIC_USE_PORT == 1 */ /* Include the generic atomic header if no supported compiler was found. */ #if ( IOT_ATOMIC_GENERIC == 1 ) From 2a16a76e256b2374a2539dc456dfd18828231892 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Wed, 17 Apr 2019 17:01:20 -0400 Subject: [PATCH 103/844] Clone TinyCBOR for CBMC proofs in continuous integration (#382) The build flow for the CBMC proofs does a "git submodule init" and "git submodule update" to pull in the TinyCBOR submodule before building MQTT for CBMC. In continuous integration, however, we pull down a straight tar file of the commit and don't actually clone the repository, and the CBMC build flow fails at "git submodule init" in continuous integration with the error "not a git repository". This pull request just manually clones the TinyCBOR into place if it is missing. --- cbmc/proofs/Makefile.common | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 1ec06f6816..8a83d5fc0a 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -34,9 +34,12 @@ CFLAGS += $(CFLAGS2) $(INC) $(DEF) %.goto : %.c $(GOTO_CC) -o $@ $(CFLAGS) $< -$(MQTT)/build: - (cd $(MQTT) && git submodule init && git submodule update && \ - mkdir build && cd build && cmake ..) 2>&1 \ +$(MQTT)/third_party/tinycbor/tinycbor/README: + cd $(MQTT)/third_party/tinycbor && \ + git clone https://github.com/intel/tinycbor.git + +$(MQTT)/build: $(MQTT)/third_party/tinycbor/tinycbor/README + (cd $(MQTT) && mkdir -p build && cd build && cmake ..) 2>&1 \ | tee $(ENTRY)0.txt \ ; exit $${PIPESTATUS[0]} From 18e5e869e0108d4799be9f4833bc17448e166ad6 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Wed, 17 Apr 2019 18:48:59 -0400 Subject: [PATCH 104/844] Fix CBMC proof of DeserializePublish (anonymous unions) (#383) Recent commits removed anonymous unions and structs from MQTT. This pull request adds the new names to the CBMC proof harness for DeserializePublish. --- cbmc/proofs/DeserializePublish/DeserializePublish_harness.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c index 92ced46caf..a0616940dd 100644 --- a/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c +++ b/cbmc/proofs/DeserializePublish/DeserializePublish_harness.c @@ -13,11 +13,11 @@ void harness() publishInfo.pPayload = malloc( publishInfo.payloadLength ); _mqttOperation_t operation; - operation.publishInfo = publishInfo; + operation.u.publish.publishInfo = publishInfo; _mqttPacket_t publish; publish.pRemainingData = malloc( sizeof( uint8_t ) * publish.remainingLength ); - publish.pIncomingPublish = &operation; + publish.u.pIncomingPublish = &operation; _IotMqtt_DeserializePublish( &publish ); From 85813e65fdd76ceba0078aa97ce02dec417c6b44 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 18 Apr 2019 16:09:50 -0700 Subject: [PATCH 105/844] Make MQTT metrics string more generic (#384) --- CMakeLists.txt | 1 - cbmc/proofs/Makefile.common | 1 - demos/include/iot_demo_arguments.h | 6 ++-- demos/include/iot_demo_logging.h | 6 ++-- lib/include/aws_iot_defender.h | 6 ++-- lib/include/aws_iot_shadow.h | 6 ++-- lib/include/iot_json_utils.h | 6 ++-- lib/include/iot_linear_containers.h | 6 ++-- lib/include/iot_logging_setup.h | 6 ++-- lib/include/iot_mqtt.h | 6 ++-- lib/include/iot_serializer.h | 6 ++-- lib/include/platform/iot_clock.h | 6 ++-- lib/include/platform/iot_metrics.h | 6 ++-- lib/include/platform/iot_network.h | 6 ++-- lib/include/platform/iot_threads.h | 6 ++-- .../private/aws_iot_defender_internal.h | 6 ++-- lib/include/private/aws_iot_shadow_internal.h | 6 ++-- lib/include/private/iot_error.h | 6 ++-- lib/include/private/iot_logging.h | 6 ++-- lib/include/private/iot_mqtt_internal.h | 6 ++-- lib/include/private/iot_static_memory.h | 6 ++-- lib/include/types/aws_iot_shadow_types.h | 4 +-- lib/include/types/iot_mqtt_types.h | 6 ++-- lib/include/types/iot_platform_types.h | 6 ++-- lib/source/mqtt/iot_mqtt_serialize.c | 33 ++++++++++++++----- platform/include/posix/iot_network_openssl.h | 6 ++-- .../include/posix/iot_platform_types_posix.h | 6 ++-- tests/mqtt/access/iot_test_access_mqtt.h | 6 ++-- 28 files changed, 99 insertions(+), 84 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6cb81c081..9e88457f48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required( VERSION 3.5.0 ) project( AwsIotDeviceSdkC VERSION 4.0.0 LANGUAGES C ) -add_definitions( -DIOT_SDK_VERSION="${PROJECT_VERSION}" ) # Use C99. set( CMAKE_C_STANDARD 99 ) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 8a83d5fc0a..54e59e6fda 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -25,7 +25,6 @@ INC = \ -I$(MQTT)/demos/include \ DEF += \ - -DIOT_SDK_VERSION=\"4.0.0\" \ -DIOT_SYSTEM_TYPES_FILE=\"posix/iot_platform_types_posix.h\" \ -Diotmqtt_EXPORTS \ diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index 5f5e8075d1..c809f165d8 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -25,8 +25,8 @@ * arguments */ -#ifndef _IOT_DEMO_ARGUMENTS_H_ -#define _IOT_DEMO_ARGUMENTS_H_ +#ifndef IOT_DEMO_ARGUMENTS_H_ +#define IOT_DEMO_ARGUMENTS_H_ /* Standard includes. */ #include @@ -95,4 +95,4 @@ bool IotDemo_ParseArguments( int argc, char ** argv, IotDemoArguments_t * pArguments ); -#endif /* ifndef _IOT_DEMO_ARGUMENTS_H_ */ +#endif /* ifndef IOT_DEMO_ARGUMENTS_H_ */ diff --git a/demos/include/iot_demo_logging.h b/demos/include/iot_demo_logging.h index 46822995bb..4904a11c64 100644 --- a/demos/include/iot_demo_logging.h +++ b/demos/include/iot_demo_logging.h @@ -24,8 +24,8 @@ * @brief Sets the log level for all demos. */ -#ifndef _IOT_DEMO_LOGGING_H_ -#define _IOT_DEMO_LOGGING_H_ +#ifndef IOT_DEMO_LOGGING_H_ +#define IOT_DEMO_LOGGING_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -51,4 +51,4 @@ /* Include the logging setup header. This enables the logs. */ #include "iot_logging_setup.h" -#endif /* ifndef _IOT_DEMO_LOGGING_H_ */ +#endif /* ifndef IOT_DEMO_LOGGING_H_ */ diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 20c72c9524..653f4092c0 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -66,8 +66,8 @@ * @enddot */ -#ifndef _AWS_IOT_DEFENDER_H_ -#define _AWS_IOT_DEFENDER_H_ +#ifndef AWS_IOT_DEFENDER_H_ +#define AWS_IOT_DEFENDER_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -421,4 +421,4 @@ uint32_t AwsIotDefender_GetPeriod( void ); const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); /* @[declare_defender_strerror] */ -#endif /* end of include guard: _AWS_IOT_DEFENDER_H_ */ +#endif /* end of include guard: AWS_IOT_DEFENDER_H_ */ diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 53bbb4ed9f..67d67899c4 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -24,8 +24,8 @@ * @brief User-facing functions of the Shadow library. */ -#ifndef _AWS_IOT_SHADOW_H_ -#define _AWS_IOT_SHADOW_H_ +#ifndef AWS_IOT_SHADOW_H_ +#define AWS_IOT_SHADOW_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -878,4 +878,4 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio const char * AwsIotShadow_strerror( AwsIotShadowError_t status ); /* @[declare_shadow_strerror] */ -#endif /* ifndef _AWS_IOT_SHADOW_H_ */ +#endif /* ifndef AWS_IOT_SHADOW_H_ */ diff --git a/lib/include/iot_json_utils.h b/lib/include/iot_json_utils.h index 9b7464e963..a8fc0144f9 100644 --- a/lib/include/iot_json_utils.h +++ b/lib/include/iot_json_utils.h @@ -24,8 +24,8 @@ * @brief Declares JSON utility functions. */ -#ifndef _IOT_JSON_UTILS_H_ -#define _IOT_JSON_UTILS_H_ +#ifndef IOT_JSON_UTILS_H_ +#define IOT_JSON_UTILS_H_ /* Standard includes. */ #include @@ -38,4 +38,4 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, const char ** pJsonValue, size_t * pJsonValueLength ); -#endif /* ifndef _IOT_JSON_UTILS_H_ */ +#endif /* ifndef IOT_JSON_UTILS_H_ */ diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index d0449a2478..f548d595ec 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -24,8 +24,8 @@ * @brief Declares and implements doubly-linked lists and queues. */ -#ifndef _IOT_LINEAR_CONTAINERS_H_ -#define _IOT_LINEAR_CONTAINERS_H_ +#ifndef IOT_LINEAR_CONTAINERS_H_ +#define IOT_LINEAR_CONTAINERS_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -949,4 +949,4 @@ static inline void IotDeQueue_RemoveAllMatches( IotDeQueue_t * const pQueue, IotListDouble_RemoveAllMatches( pQueue, isMatch, pMatch, freeElement, linkOffset ); } -#endif /* _IOT_LINEAR_CONTAINERS_H_ */ +#endif /* IOT_LINEAR_CONTAINERS_H_ */ diff --git a/lib/include/iot_logging_setup.h b/lib/include/iot_logging_setup.h index a8a82c0e9a..58279d1620 100644 --- a/lib/include/iot_logging_setup.h +++ b/lib/include/iot_logging_setup.h @@ -24,8 +24,8 @@ * @brief Defines the logging macro #IotLog. */ -#ifndef _IOT_LOGGING_SETUP_H_ -#define _IOT_LOGGING_SETUP_H_ +#ifndef IOT_LOGGING_SETUP_H_ +#define IOT_LOGGING_SETUP_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -216,4 +216,4 @@ #endif #endif -#endif /* ifndef _IOT_LOGGING_SETUP_H_ */ +#endif /* ifndef IOT_LOGGING_SETUP_H_ */ diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index fe12bc7ffe..dbb04b290a 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -24,8 +24,8 @@ * @brief User-facing functions of the MQTT 3.1.1 library. */ -#ifndef _IOT_MQTT_H_ -#define _IOT_MQTT_H_ +#ifndef IOT_MQTT_H_ +#define IOT_MQTT_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -816,4 +816,4 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, IotMqttSubscription_t * pCurrentSubscription ); /* @[declare_mqtt_issubscribed] */ -#endif /* ifndef _IOT_MQTT_H_ */ +#endif /* ifndef IOT_MQTT_H_ */ diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 010c6abf56..e56f8ad468 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -28,8 +28,8 @@ * The implementations can be CBOR or JSON. */ -#ifndef _IOT_SERIALIZER_H_ -#define _IOT_SERIALIZER_H_ +#ifndef IOT_SERIALIZER_H_ +#define IOT_SERIALIZER_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -478,4 +478,4 @@ extern IotSerializerEncodeInterface_t _IotSerializerJsonEncoder; extern IotSerializerDecodeInterface_t _IotSerializerJsonDecoder; -#endif /* ifndef _IOT_SERIALIZER_H_ */ +#endif /* ifndef IOT_SERIALIZER_H_ */ diff --git a/lib/include/platform/iot_clock.h b/lib/include/platform/iot_clock.h index 6af9c37e3b..99a4d83875 100644 --- a/lib/include/platform/iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -24,8 +24,8 @@ * @brief Time-related functions used by libraries in this SDK. */ -#ifndef _IOT_CLOCK_H_ -#define _IOT_CLOCK_H_ +#ifndef IOT_CLOCK_H_ +#define IOT_CLOCK_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -209,4 +209,4 @@ bool IotClock_TimerArm( IotTimer_t * pTimer, uint32_t periodMs ); /* @[declare_platform_clock_timerarm] */ -#endif /* ifndef _IOT_CLOCK_H_ */ +#endif /* ifndef IOT_CLOCK_H_ */ diff --git a/lib/include/platform/iot_metrics.h b/lib/include/platform/iot_metrics.h index 571a6a3974..e2441ae28c 100644 --- a/lib/include/platform/iot_metrics.h +++ b/lib/include/platform/iot_metrics.h @@ -25,8 +25,8 @@ * */ -#ifndef _IOT_METRICS_H_ -#define _IOT_METRICS_H_ +#ifndef IOT_METRICS_H_ +#define IOT_METRICS_H_ #include @@ -165,4 +165,4 @@ void IotMetrics_RemoveTcpConnection( IotMetricsConnectionId_t tcpConnectionId ); */ void IotMetrics_ProcessTcpConnections( IotMetricsListCallback_t tcpConnectionsCallback ); -#endif /* ifndef _IOT_METRICS_H_ */ +#endif /* ifndef IOT_METRICS_H_ */ diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index 62bb9b186b..1584823c78 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -24,8 +24,8 @@ * @brief Abstraction of network functions used by libraries in this SDK. */ -#ifndef _IOT_NETWORK_H_ -#define _IOT_NETWORK_H_ +#ifndef IOT_NETWORK_H_ +#define IOT_NETWORK_H_ /* Standard includes. */ #include @@ -231,4 +231,4 @@ typedef struct IotNetworkInterface /* @[declare_platform_network_destroy] */ } IotNetworkInterface_t; -#endif /* ifndef _IOT_NETWORK_H_ */ +#endif /* ifndef IOT_NETWORK_H_ */ diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index 49f0bf77b5..2791a1d245 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -24,8 +24,8 @@ * @brief Threading and synchronization functions used by libraries in this SDK. */ -#ifndef _IOT_THREADS_H_ -#define _IOT_THREADS_H_ +#ifndef IOT_THREADS_H_ +#define IOT_THREADS_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -348,4 +348,4 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, void IotSemaphore_Post( IotSemaphore_t * pSemaphore ); /* @[declare_platform_threads_semaphorepost] */ -#endif /* ifndef _IOT_THREADS_H_ */ +#endif /* ifndef IOT_THREADS_H_ */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 441c858d9c..1e04e6f35c 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -24,8 +24,8 @@ * typical application code. */ -#ifndef _AWS_IOT_DEFENDER_INTERNAL_H_ -#define _AWS_IOT_DEFENDER_INTERNAL_H_ +#ifndef AWS_IOT_DEFENDER_INTERNAL_H_ +#define AWS_IOT_DEFENDER_INTERNAL_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -348,4 +348,4 @@ void AwsIotDefenderInternal_MqttDisconnect( void ); extern _defenderMetrics_t _AwsIotDefenderMetrics; -#endif /* ifndef _AWS_IOT_DEFENDER_INTERNAL_H_ */ +#endif /* ifndef AWS_IOT_DEFENDER_INTERNAL_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index c757600766..aaeaa9dcc8 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -25,8 +25,8 @@ * typical application code. */ -#ifndef _AWS_IOT_SHADOW_INTERNAL_H_ -#define _AWS_IOT_SHADOW_INTERNAL_H_ +#ifndef AWS_IOT_SHADOW_INTERNAL_H_ +#define AWS_IOT_SHADOW_INTERNAL_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -668,4 +668,4 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ); -#endif /* ifndef _AWS_IOT_SHADOW_INTERNAL_H_ */ +#endif /* ifndef AWS_IOT_SHADOW_INTERNAL_H_ */ diff --git a/lib/include/private/iot_error.h b/lib/include/private/iot_error.h index c40cc1d064..03409b0b2c 100644 --- a/lib/include/private/iot_error.h +++ b/lib/include/private/iot_error.h @@ -27,8 +27,8 @@ * by setting the library prefix. */ -#ifndef _IOT_ERROR_H_ -#define _IOT_ERROR_H_ +#ifndef IOT_ERROR_H_ +#define IOT_ERROR_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -110,4 +110,4 @@ #define _IOT_VALIDATE_PARAMETER( libraryPrefix, condition ) \ _IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( libraryPrefix ## _BAD_PARAMETER, condition ) -#endif /* ifndef _IOT_ERROR_H_ */ +#endif /* ifndef IOT_ERROR_H_ */ diff --git a/lib/include/private/iot_logging.h b/lib/include/private/iot_logging.h index 5e0c2096a1..8d0dd4fdc7 100644 --- a/lib/include/private/iot_logging.h +++ b/lib/include/private/iot_logging.h @@ -30,8 +30,8 @@ * @see iot_logging_setup.h */ -#ifndef _IOT_LOGGING_H_ -#define _IOT_LOGGING_H_ +#ifndef IOT_LOGGING_H_ +#define IOT_LOGGING_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -222,4 +222,4 @@ void IotLog_GenericPrintBuffer( const char * const pLibraryName, size_t bufferSize ); /* @[declare_logging_genericprintbuffer] */ -#endif /* ifndef _IOT_LOGGING_H_ */ +#endif /* ifndef IOT_LOGGING_H_ */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 0884441442..07d88a3499 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -25,8 +25,8 @@ * typical application code. */ -#ifndef _IOT_MQTT_INTERNAL_H_ -#define _IOT_MQTT_INTERNAL_H_ +#ifndef IOT_MQTT_INTERNAL_H_ +#define IOT_MQTT_INTERNAL_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -931,4 +931,4 @@ bool _IotMqtt_GetNextByte( void * pNetworkConnection, void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, _mqttConnection_t * pMqttConnection ); -#endif /* ifndef _IOT_MQTT_INTERNAL_H_ */ +#endif /* ifndef IOT_MQTT_INTERNAL_H_ */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 4f7e7babec..69dcf0b8ea 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -30,8 +30,8 @@ /* The functions in this file should only exist in static memory only mode, hence * the check for IOT_STATIC_MEMORY_ONLY in the double inclusion guard. */ -#if !defined( _IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) -#define _IOT_STATIC_MEMORY_H_ +#if !defined( IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) +#define IOT_STATIC_MEMORY_H_ /* Standard includes. */ #include @@ -669,4 +669,4 @@ void * AwsIot_MallocDefenderTopic( size_t size ); void AwsIot_FreeDefenderTopic( void * ptr ); /* @[declare_static_memory_freedefendertopic] */ -#endif /* if !defined( _IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ +#endif /* if !defined( IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 4a0f6ca4fa..9f25c7f7c2 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -24,8 +24,8 @@ * @brief Types of the Thing Shadow library. */ -#ifndef _AWS_IOT_SHADOW_TYPES_H_ -#define _AWS_IOT_SHADOW_TYPES_H_ +#ifndef AWS_IOT_SHADOW_TYPES_H_ +#define AWS_IOT_SHADOW_TYPES_H_ /* The config header is always included first. */ #include "iot_config.h" diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 27586bba58..efc68c04cd 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -24,8 +24,8 @@ * @brief Types of the MQTT library. */ -#ifndef _IOT_MQTT_TYPES_H_ -#define _IOT_MQTT_TYPES_H_ +#ifndef IOT_MQTT_TYPES_H_ +#define IOT_MQTT_TYPES_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -1080,4 +1080,4 @@ typedef struct IotMqttNetworkInfo */ #define IOT_MQTT_FLAG_CLEANUP_ONLY ( 0x00000001 ) -#endif /* ifndef _IOT_MQTT_TYPES_H_ */ +#endif /* ifndef IOT_MQTT_TYPES_H_ */ diff --git a/lib/include/types/iot_platform_types.h b/lib/include/types/iot_platform_types.h index 6a6330933c..5d9c1c2b42 100644 --- a/lib/include/types/iot_platform_types.h +++ b/lib/include/types/iot_platform_types.h @@ -24,8 +24,8 @@ * @brief Types of the platform layer. */ -#ifndef _IOT_PLATFORM_TYPES_H_ -#define _IOT_PLATFORM_TYPES_H_ +#ifndef IOT_PLATFORM_TYPES_H_ +#define IOT_PLATFORM_TYPES_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -114,4 +114,4 @@ typedef void ( * IotThreadRoutine_t )( void * ); */ typedef _IotSystemTimer_t IotTimer_t; -#endif /* ifndef _IOT_PLATFORM_TYPES_H_ */ +#endif /* ifndef IOT_PLATFORM_TYPES_H_ */ diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index c6db92546d..3541e56247 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -152,13 +152,30 @@ * Username for metrics with AWS IoT. */ #if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 - #ifndef IOT_SDK_VERSION - #error "IOT_SDK_VERSION must be defined." + +/** + * @brief Check if an SDK name is defined. If not, specify "C SDK". + */ + #ifdef IOT_SDK_NAME + #define METRICS_SDK_NAME IOT_SDK_NAME + #else + #define METRICS_SDK_NAME "C" #endif - #define _AWS_IOT_METRICS_USERNAME ( "?SDK=C&Version=" IOT_SDK_VERSION ) /**< @brief Specify "C SDK" and SDK version. */ - #define _AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( _AWS_IOT_METRICS_USERNAME ) - 1 ) /**< @brief Length of #_AWS_IOT_METRICS_USERNAME. */ -#endif +/** + * @brief In the metrics string, include the platform name if defined. + */ + #ifdef IOT_PLATFORM_NAME + #define AWS_IOT_METRICS_USERNAME "?SDK=" METRICS_SDK_NAME "&Version=4.0.0&Platform=" IOT_PLATFORM_NAME + #else + #define AWS_IOT_METRICS_USERNAME "?SDK=" METRICS_SDK_NAME "&Version=4.0.0" + #endif + +/** + * @brief Length of #AWS_IOT_METRICS_USERNAME. + */ + #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1 ) +#endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ /*-----------------------------------------------------------*/ @@ -422,7 +439,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, if( pConnectInfo->awsIotMqttMode == true ) { #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - connectPacketSize += _AWS_IOT_METRICS_USERNAME_LENGTH + sizeof( uint16_t ); + connectPacketSize += AWS_IOT_METRICS_USERNAME_LENGTH + sizeof( uint16_t ); #endif } else @@ -825,8 +842,8 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI "Recompile with AWS_IOT_MQTT_ENABLE_METRICS set to 0 to disable." ); pBuffer = _encodeString( pBuffer, - _AWS_IOT_METRICS_USERNAME, - _AWS_IOT_METRICS_USERNAME_LENGTH ); + AWS_IOT_METRICS_USERNAME, + AWS_IOT_METRICS_USERNAME_LENGTH ); #endif } else diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 2e78302643..d78c2bf51c 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -25,8 +25,8 @@ * POSIX systems with OpenSSL. */ -#ifndef _IOT_NETWORK_OPENSSL_H_ -#define _IOT_NETWORK_OPENSSL_H_ +#ifndef IOT_NETWORK_OPENSSL_H_ +#define IOT_NETWORK_OPENSSL_H_ /* The config header is always included first. */ #include "iot_config.h" @@ -232,4 +232,4 @@ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); extern const IotNetworkInterface_t IotNetworkOpenssl; /** @endcond */ -#endif /* ifndef _IOT_NETWORK_OPENSSL_H_ */ +#endif /* ifndef IOT_NETWORK_OPENSSL_H_ */ diff --git a/platform/include/posix/iot_platform_types_posix.h b/platform/include/posix/iot_platform_types_posix.h index 52bd80f8d5..655f76bac6 100644 --- a/platform/include/posix/iot_platform_types_posix.h +++ b/platform/include/posix/iot_platform_types_posix.h @@ -24,8 +24,8 @@ * @brief Definitions of platform layer types on POSIX systems. */ -#ifndef _IOT_PLATFORM_TYPES_POSIX_H_ -#define _IOT_PLATFORM_TYPES_POSIX_H_ +#ifndef IOT_PLATFORM_TYPES_POSIX_H_ +#define IOT_PLATFORM_TYPES_POSIX_H_ /* POSIX includes. Allow the default POSIX headers to be overridden. */ #ifdef POSIX_TYPES_HEADER @@ -59,4 +59,4 @@ typedef struct _IotSystemTimer void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ } _IotSystemTimer_t; -#endif /* ifndef _IOT_PLATFORM_TYPES_POSIX_H_ */ +#endif /* ifndef IOT_PLATFORM_TYPES_POSIX_H_ */ diff --git a/tests/mqtt/access/iot_test_access_mqtt.h b/tests/mqtt/access/iot_test_access_mqtt.h index 0c8ed62ca6..b1294f1749 100644 --- a/tests/mqtt/access/iot_test_access_mqtt.h +++ b/tests/mqtt/access/iot_test_access_mqtt.h @@ -25,8 +25,8 @@ * and variables of the MQTT library. */ -#ifndef _IOT_TEST_ACCESS_MQTT_H_ -#define _IOT_TEST_ACCESS_MQTT_H_ +#ifndef IOT_TEST_ACCESS_MQTT_H_ +#define IOT_TEST_ACCESS_MQTT_H_ /*--------------------------- iot_mqtt_api.c ---------------------------*/ @@ -87,4 +87,4 @@ bool IotTestMqtt_topicMatch( const IotLink_t * pSubscriptionLink, bool IotTestMqtt_packetMatch( const IotLink_t * pSubscriptionLink, void * pMatch ); -#endif /* ifndef _IOT_TEST_ACCESS_MQTT_H_ */ +#endif /* ifndef IOT_TEST_ACCESS_MQTT_H_ */ From 67a296594e8577196ced3f1f4d3917d64f98b4b0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 19 Apr 2019 13:17:38 -0700 Subject: [PATCH 106/844] Make network destroy safe from receive callback (#385) --- doc/guide/porting.txt | 6 +- lib/include/platform/iot_network.h | 10 ++-- lib/include/private/iot_mqtt_internal.h | 15 +---- lib/source/mqtt/iot_mqtt_api.c | 55 +++++++------------ lib/source/mqtt/iot_mqtt_network.c | 8 +-- lib/source/mqtt/iot_mqtt_operation.c | 18 +++--- lib/source/mqtt/iot_mqtt_subscription.c | 2 +- {lib => platform}/include/iot_atomic.h | 0 .../posix/network/iot_network_openssl.c | 29 ++++++++-- 9 files changed, 67 insertions(+), 76 deletions(-) rename {lib => platform}/include/iot_atomic.h (100%) diff --git a/doc/guide/porting.txt b/doc/guide/porting.txt index 605d604281..105401dbe2 100644 --- a/doc/guide/porting.txt +++ b/doc/guide/porting.txt @@ -29,7 +29,7 @@ As relative paths from the SDK's root directory: Platform-independent SDK files. This directory (and all its subdirectories) should be copied and not modified. - `include/`
Library headers. - - `platform/`
+ - `platform/`
Interface of the platform layer, to be implemented in the `platform/` directory.
- `private/`
Library internal headers. @@ -109,11 +109,11 @@ The platform types should be set in the [config file](@ref global_config), `iot_ Porting atomic operations is not required to use the SDK. However, atomic operations improve the performance of all libraries; therefore, porting them is recommended. -Unlike the platform layer, which relies on APIs provided by the host operating system, the SDK's atomic ports rely on facilities provided by compilers. Many compilers, particularly recent versions that support C11 atomic features, provide compiler intrinsics for atomic operations. The header `lib/include/iot_atomic.h` will select an atomic port to use based on the detected compiler. If no suitable atomic port is available, then a generic atomic port will be used. The generic atomic port is slower than native compiler ports, but will work on all systems. +Unlike the platform layer, which relies on APIs provided by the host operating system, the SDK's atomic ports rely on facilities provided by compilers. Many compilers, particularly recent versions that support C11 atomic features, provide compiler intrinsics for atomic operations. The header `platform/include/iot_atomic.h` will select an atomic port to use based on the detected compiler. If no suitable atomic port is available, then a generic atomic port will be used. The generic atomic port is slower than native compiler ports, but will work on all systems. To provide a new compiler port, the preprocessor constant `IOT_ATOMIC_USE_PORT` should be defined to `1` in the [config file](@ref global_config). When `IOT_ATOMIC_USE_PORT` is `1`, a new compiler port should be implemented in a file named `iot_atomic_port.h`. This file should be created in `platform/include/atomic/`, as it will be included with the directive `#include "atomic/iot_atomic_port.h"`. -@see `lib/include/iot_atomic.h` for the header that selects the atomic port to use.
+@see `platform/include/iot_atomic.h` for the header that selects the atomic port to use.
`platform/include/atomic/iot_atomic_gcc.h` for a port that uses GNU compiler intrinsics. @section guide_developer_porting_metrics Porting Device Defender metrics diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index 1584823c78..0a9a4a511a 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -127,11 +127,8 @@ typedef struct IotNetworkInterface * detached thread. * * Each network connection may only have one receive callback at any time. - * If this function is called for a connection that already has a receive - * callback, the existing callback should be replaced. If the `receiveCallback` - * parameter is `NULL`, any existing receive callback should be removed. In - * addition, @ref platform_network_function_close is expected to remove any - * active receive callbacks. + * @ref platform_network_function_close is expected to remove any active + * receive callbacks. * * @param[in] pConnection The connection to associate with the receive callback. * @param[in] receiveCallback The function to invoke for incoming network data. @@ -224,7 +221,8 @@ typedef struct IotNetworkInterface * @return Any #IotNetworkError_t, as defined by the network stack. * * @attention No function should be called on the network connection after - * calling this function. + * calling this function. This function must be safe to call from a + * [receive callback](@ref platform_network_function_receivecallback). */ /* @[declare_platform_network_destroy] */ IotNetworkError_t ( * destroy )( void * pConnection ); diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 07d88a3499..368272bba6 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -726,11 +726,8 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, * the operation completes. * * @param[in] pOperation The operation which completed. - * @param[in] receiveCallback Whether this operation is being destroyed from - * the receive callback. This affects the MQTT operation cleanup routine. */ -void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, - bool receiveCallback ); +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); /** * @brief Task pool routine for processing an MQTT connection's keep-alive. @@ -813,15 +810,12 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, * @brief Notify of a completed MQTT operation. * * @param[in] pOperation The MQTT operation which completed. - * @param[in] receiveCallback Whether this notification is coming from the receive - * callback. This affects the MQTT operation cleanup routine. * * Depending on the parameters passed to a user-facing MQTT function, the * notification will cause @ref mqtt_function_wait to return or invoke a * user-provided callback. */ -void _IotMqtt_Notify( _mqttOperation_t * pOperation, - bool receiveCallback ); +void _IotMqtt_Notify( _mqttOperation_t * pOperation ); /*----------------- MQTT subscription management functions ------------------*/ @@ -896,11 +890,8 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection * Also destroys an unreferenced MQTT connection. * * @param[in] pMqttConnection The referenced MQTT connection. - * @param[in] receiveCallback Whether the reference count is being changed from - * the receive callback. This affects the MQTT operation cleanup routine. */ -void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection, - bool receiveCallback ); +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ); /** * @brief Read the next available byte on a network connection. diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 459aa383fa..a934e3d348 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -121,11 +121,8 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, * @brief Destroys the members of an MQTT connection. * * @param[in] pMqttConnection Which connection to destroy. - * @param[in] receiveCallback Whether the connection is being destroyed from the - * receive callback. This affects the MQTT operation cleanup routine. */ -static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection, - bool receiveCallback ); +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); /** * @brief The common component of both @ref mqtt_function_subscribe and @ref @@ -226,7 +223,7 @@ static void _mqttOperation_tryDestroy( void * pData ) /* Decrement reference count and destroy operation if possible. */ if( _IotMqtt_DecrementOperationReferences( pOperation, true ) == true ) { - _IotMqtt_DestroyOperation( pOperation, false ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -465,8 +462,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection, - bool receiveCallback ) +static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) { IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; @@ -506,28 +502,20 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection, offsetof( _mqttSubscription_t, link ) ); IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - /* Destroy an owned network connection if this function is not being called - * from the receive callback. */ - if( receiveCallback == false ) + /* Destroy an owned network connection. */ + if( pMqttConnection->ownNetworkConnection == true ) { - if( pMqttConnection->ownNetworkConnection == true ) - { - networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); + networkStatus = pMqttConnection->pNetworkInterface->destroy( pMqttConnection->pNetworkConnection ); - if( networkStatus != IOT_NETWORK_SUCCESS ) - { - IotLogWarn( "(MQTT connection %p) Failed to destroy network connection.", - pMqttConnection ); - } - else - { - IotLogInfo( "(MQTT connection %p) Network connection destroyed.", - pMqttConnection ); - } + if( networkStatus != IOT_NETWORK_SUCCESS ) + { + IotLogWarn( "(MQTT connection %p) Failed to destroy network connection.", + pMqttConnection ); } else { - _EMPTY_ELSE_MARKER; + IotLogInfo( "(MQTT connection %p) Network connection destroyed.", + pMqttConnection ); } } else @@ -746,7 +734,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { if( pSubscriptionOperation != NULL ) { - _IotMqtt_DestroyOperation( pSubscriptionOperation, false ); + _IotMqtt_DestroyOperation( pSubscriptionOperation ); } } else @@ -797,8 +785,7 @@ bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection /*-----------------------------------------------------------*/ -void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection, - bool receiveCallback ) +void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection ) { bool destroyConnection = false; @@ -831,7 +818,7 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection { IotLogDebug( "(MQTT connection %p) Connection will be destroyed now.", pMqttConnection ); - _destroyMqttConnection( pMqttConnection, receiveCallback ); + _destroyMqttConnection( pMqttConnection ); } else { @@ -1213,7 +1200,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pOperation != NULL ) { - _IotMqtt_DestroyOperation( pOperation, false ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -1222,7 +1209,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pNewMqttConnection != NULL ) { - _destroyMqttConnection( pNewMqttConnection, false ); + _destroyMqttConnection( pNewMqttConnection ); } else { @@ -1325,7 +1312,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, { IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", mqttConnection ); - _IotMqtt_DestroyOperation( pOperation, false ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -1387,7 +1374,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); /* Decrement the connection reference count and destroy it if possible. */ - _IotMqtt_DecrementConnectionReferences( mqttConnection, false ); + _IotMqtt_DecrementConnectionReferences( mqttConnection ); } /*-----------------------------------------------------------*/ @@ -1731,7 +1718,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, { if( pOperation != NULL ) { - _IotMqtt_DestroyOperation( pOperation, false ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -1899,7 +1886,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /* Wait is finished; decrement operation reference count. */ if( _IotMqtt_DecrementOperationReferences( operation, false ) == true ) { - _IotMqtt_DestroyOperation( operation, false ); + _IotMqtt_DestroyOperation( operation ); } else { diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 6baf02a7fb..d373201b5b 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -298,7 +298,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation, true ); + _IotMqtt_Notify( pOperation ); } else { @@ -464,7 +464,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation, true ); + _IotMqtt_Notify( pOperation ); } else { @@ -507,7 +507,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation, true ); + _IotMqtt_Notify( pOperation ); } else { @@ -549,7 +549,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( pOperation != NULL ) { pOperation->u.operation.status = status; - _IotMqtt_Notify( pOperation, true ); + _IotMqtt_Notify( pOperation ); } else { diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index a241645153..63cff67ad1 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -428,7 +428,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { if( decrementOnError == true ) { - _IotMqtt_DecrementConnectionReferences( pMqttConnection, false ); + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } else { @@ -526,8 +526,7 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, /*-----------------------------------------------------------*/ -void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, - bool receiveCallback ) +void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) { _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; @@ -628,7 +627,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation, /* Decrement the MQTT connection's reference count after destroying an * operation. */ - _IotMqtt_DecrementConnectionReferences( pMqttConnection, receiveCallback ); + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } /*-----------------------------------------------------------*/ @@ -969,7 +968,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /* Destroy the operation or notify of completion if necessary. */ if( destroyOperation == true ) { - _IotMqtt_DestroyOperation( pOperation, false ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -980,7 +979,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /* Notify of operation completion if this job set a status. */ if( pOperation->u.operation.status != IOT_MQTT_STATUS_PENDING ) { - _IotMqtt_Notify( pOperation, false ); + _IotMqtt_Notify( pOperation ); } else { @@ -1026,7 +1025,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, /* Attempt to destroy the operation once the user callback returns. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) { - _IotMqtt_DestroyOperation( pOperation, false ); + _IotMqtt_DestroyOperation( pOperation ); } else { @@ -1193,8 +1192,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ -void _IotMqtt_Notify( _mqttOperation_t * pOperation, - bool receiveCallback ) +void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { IotMqttError_t status = IOT_MQTT_SCHEDULING_ERROR; _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; @@ -1288,7 +1286,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation, /* Decrement reference count of operations with no callback. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) { - _IotMqtt_DestroyOperation( pOperation, receiveCallback ); + _IotMqtt_DestroyOperation( pOperation ); } else { diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 5ab3344a13..72bc48a7f6 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -495,7 +495,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, IotMutex_Unlock( &( pMqttConnection->subscriptionMutex ) ); - _IotMqtt_DecrementConnectionReferences( pMqttConnection, false ); + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } /*-----------------------------------------------------------*/ diff --git a/lib/include/iot_atomic.h b/platform/include/iot_atomic.h similarity index 100% rename from lib/include/iot_atomic.h rename to platform/include/iot_atomic.h diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 2c21b95049..5aeb5a890c 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -969,16 +969,33 @@ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - /* Wait for the receive thread to terminate. */ + /* Check if a receive thread needs to be cleaned up. */ if( pNetworkConnection->receiveThreadCreated == true ) { - posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + if( pthread_self() == pNetworkConnection->receiveThread ) + { + /* If this function is being called from the receive thread, detach + * it so no other thread needs to clean it up. */ + posixError = pthread_detach( pNetworkConnection->receiveThread ); - if( posixError != 0 ) + if( posixError != 0 ) + { + IotLogWarn( "Failed to detach receive thread for socket %d. errno=%d.", + pNetworkConnection->socket, + posixError ); + } + } + else { - IotLogWarn( "Failed to join receive thread for socket %d. errno=%d.", - pNetworkConnection->socket, - posixError ); + /* Wait for the receive thread to exit. */ + posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + + if( posixError != 0 ) + { + IotLogWarn( "Failed to join receive thread for socket %d. errno=%d.", + pNetworkConnection->socket, + posixError ); + } } } From eac89d34d8fcab19bcf8443f66e3bce7221ba51e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 22 Apr 2019 13:49:20 -0700 Subject: [PATCH 107/844] Fix warnings on IAR compiler (#387) --- lib/include/types/iot_mqtt_types.h | 4 +- lib/source/defender/aws_iot_defender_api.c | 49 +++++++++++++------- lib/source/defender/aws_iot_defender_mqtt.c | 6 +-- lib/source/mqtt/iot_mqtt_operation.c | 10 ++-- lib/source/shadow/aws_iot_shadow_api.c | 10 ++-- lib/source/shadow/aws_iot_shadow_operation.c | 4 +- 6 files changed, 47 insertions(+), 36 deletions(-) diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index efc68c04cd..4dfbce6492 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -1044,9 +1044,9 @@ typedef struct IotMqttNetworkInfo /** @brief Initializer for #IotMqttConnectInfo_t. */ #define IOT_MQTT_CONNECT_INFO_INITIALIZER { .cleanSession = true } /** @brief Initializer for #IotMqttPublishInfo_t. */ -#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { 0 } +#define IOT_MQTT_PUBLISH_INFO_INITIALIZER { .qos = IOT_MQTT_QOS_0 } /** @brief Initializer for #IotMqttSubscription_t. */ -#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { 0 } +#define IOT_MQTT_SUBSCRIPTION_INITIALIZER { .qos = IOT_MQTT_QOS_0 } /** @brief Initializer for #IotMqttCallbackInfo_t. */ #define IOT_MQTT_CALLBACK_INFO_INITIALIZER { 0 } /** @brief Initializer for #IotMqttConnection_t. */ diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 8654706f75..49c3abbd88 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -302,27 +302,40 @@ uint32_t AwsIotDefender_GetPeriod( void ) const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) { - /* The string returned if the parameter is invalid. */ - static const char * pInvalidError = "INVALID ERROR"; - /* Lookup table of Defender errors. */ - static const char * pErrorNames[] = - { - "SUCCESS", /* AWS_IOT_DEFENDER_SUCCESS */ - "INLVALID INPUT", /* AWS_IOT_DEFENDER_INVALID_INPUT */ - "ALREADY STARTED", /* AWS_IOT_DEFENDER_ALREADY_STARTED */ - "PERIOD TOO SHORT", /* AWS_IOT_DEFENDER_PERIOD_TOO_SHORT */ - "NO MEMORY", /* AWS_IOT_DEFENDER_ERROR_NO_MEMORY */ - "INTERNAL FAILURE" /* AWS_IOT_DEFENDER_INTERNAL_FAILURE */ - }; - - /* Check that the parameter is valid. */ - if( ( error < 0 ) || - ( error >= ( sizeof( pErrorNames ) / sizeof( pErrorNames[ 0 ] ) ) ) ) + const char * pMessage = NULL; + + switch( error ) { - return pInvalidError; + case AWS_IOT_DEFENDER_SUCCESS: + pMessage = "SUCCESS"; + break; + + case AWS_IOT_DEFENDER_INVALID_INPUT: + pMessage = "INVALID INPUT"; + break; + + case AWS_IOT_DEFENDER_ALREADY_STARTED: + pMessage = "ALREADY STARTED"; + break; + + case AWS_IOT_DEFENDER_PERIOD_TOO_SHORT: + pMessage = "PERIOD TOO SHORT"; + break; + + case AWS_IOT_DEFENDER_ERROR_NO_MEMORY: + pMessage = "NO MEMORY"; + break; + + case AWS_IOT_DEFENDER_INTERNAL_FAILURE: + pMessage = "INTERNAL FAILURE"; + break; + + default: + pMessage = "INVALID STATUS"; + break; } - return pErrorNames[ error ]; + return pMessage; } /*-----------------------------------------------------------*/ diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index eeaaefede9..5da9473cc0 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -124,12 +124,12 @@ IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t accep /* subscribe to two topics: accept and reject. */ IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - subscriptions[ 0 ].qos = 0; + subscriptions[ 0 ].qos = IOT_MQTT_QOS_0; subscriptions[ 0 ].pTopicFilter = _pAcceptTopic; subscriptions[ 0 ].topicFilterLength = ( uint16_t ) strlen( _pAcceptTopic ); subscriptions[ 0 ].callback = acceptCallback; - subscriptions[ 1 ].qos = 0; + subscriptions[ 1 ].qos = IOT_MQTT_QOS_0; subscriptions[ 1 ].pTopicFilter = _pRejectTopic; subscriptions[ 1 ].topicFilterLength = ( uint16_t ) strlen( _pRejectTopic ); subscriptions[ 1 ].callback = rejectCallback; @@ -148,7 +148,7 @@ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, { IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - publishInfo.qos = 0; + publishInfo.qos = IOT_MQTT_QOS_0; publishInfo.pTopicName = _pPublishTopic; publishInfo.topicNameLength = ( uint16_t ) strlen( _pPublishTopic ); publishInfo.pPayload = pData; diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 63cff67ad1..388e44b5a0 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -767,7 +767,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, void * pContext ) { _mqttOperation_t * pOperation = pContext; - IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; + IotMqttCallbackParam_t callbackParam = { .mqttConnection = NULL }; /* Check parameters. The task pool and job parameter is not used when asserts * are disabled. */ @@ -1000,7 +1000,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, void * pContext ) { _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; - IotMqttCallbackParam_t callbackParam = { .u.operation = { 0 } }; + IotMqttCallbackParam_t callbackParam = { 0 }; /* Check parameters. The task pool and job parameter is not used when asserts * are disabled. */ @@ -1090,7 +1090,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; _mqttOperation_t * pResult = NULL; IotLink_t * pResultLink = NULL; - _operationMatchParam_t param = { 0 }; + _operationMatchParam_t param = { .type = type, .pPacketIdentifier = pPacketIdentifier }; if( pPacketIdentifier != NULL ) { @@ -1107,10 +1107,6 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotMqtt_OperationType( type ) ); } - /* Set the search parameters. */ - param.type = type; - param.pPacketIdentifier = pPacketIdentifier; - /* Find and remove the first matching element in the list. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pResultLink = IotListDouble_RemoveFirstMatch( &( pMqttConnection->pendingResponse ), diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index baf6ccb2a4..f219a7050d 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -316,7 +316,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio _shadowSubscription_t * pSubscription = NULL; /* Check parameters. */ - if( _validateThingNameFlags( type + _SHADOW_OPERATION_COUNT, /* Shadow callbacks are enumerated after the operations. */ + if( _validateThingNameFlags( ( _shadowOperationType_t ) ( type + _SHADOW_OPERATION_COUNT ), pThingName, thingNameLength, 0, @@ -546,13 +546,15 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, const _shadowSubscription_t * pSubscription, IotMqttCallbackParam_t * pMessage ) { - AwsIotShadowCallbackParam_t callbackParam = { 0 }; + AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; /* Ensure that a callback function is set. */ AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); - /* Set the members of the callback param. */ - callbackParam.callbackType = type + _SHADOW_OPERATION_COUNT; /* Shadow callbacks are enumerated after the operations. */ + /* Set the callback type. Shadow callbacks are enumerated after the operations. */ + callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( type + _SHADOW_OPERATION_COUNT ); + + /* Set the remaining members of the callback param. */ callbackParam.mqttConnection = pMessage->mqttConnection; callbackParam.pThingName = pSubscription->pThingName; callbackParam.thingNameLength = pSubscription->thingNameLength; diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 1218b31278..24b2d1a377 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -222,7 +222,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, _shadowOperation_t * pOperation = NULL; IotLink_t * pOperationLink = NULL; _shadowOperationStatus_t status = _UNKNOWN_STATUS; - _operationMatchParams_t param = { 0 }; + _operationMatchParams_t param = { .type = ( _shadowOperationType_t ) 0 }; uint32_t flags = 0; /* Set operation type to search. */ @@ -819,7 +819,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) { - AwsIotShadowCallbackParam_t callbackParam = { 0 }; + AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; _shadowSubscription_t * pSubscription = pOperation->pSubscription, * pRemovedSubscription = NULL; From ab53e512efe41aa07945dacbc7be641eb634d21c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 23 Apr 2019 11:31:10 -0700 Subject: [PATCH 108/844] Fix possible overflow in PUBLISH packet size calculation (#388) --- lib/source/mqtt/iot_mqtt_serialize.c | 40 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 3541e56247..076a7361c4 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -494,7 +494,7 @@ static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, size_t * pPacketSize ) { bool status = true; - size_t publishPacketSize = 0; + size_t publishPacketSize = 0, payloadLimit = 0; /* The variable header of a PUBLISH packet always contains the topic name. */ publishPacketSize += pPublishInfo->topicNameLength + sizeof( uint16_t ); @@ -510,24 +510,40 @@ static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, _EMPTY_ELSE_MARKER; } - /* Add the length of the PUBLISH payload. At this point, the "Remaining length" - * has been calculated. Return error if the "Remaining length" exceeds what is - * allowed by MQTT 3.1.1. Otherwise, set the output parameter. */ - publishPacketSize += pPublishInfo->payloadLength; + /* Calculate the maximum allowed size of the payload for the given parameters. + * This calculation excludes the "Remaining length" encoding, whose size is not + * yet known. */ + payloadLimit = _MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1; - if( publishPacketSize > _MQTT_MAX_REMAINING_LENGTH ) + /* Ensure that the given payload fits within the calculated limit. */ + if( pPublishInfo->payloadLength > payloadLimit ) { status = false; } else { - *pRemainingLength = publishPacketSize; + /* Add the length of the PUBLISH payload. At this point, the "Remaining length" + * has been calculated. */ + publishPacketSize += pPublishInfo->payloadLength; - /* Calculate the full size of the MQTT PUBLISH packet by adding the size of - * the "Remaining Length" field plus 1 byte for the "Packet Type" field. Set - * the pPacketSize output parameter. */ - publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); - *pPacketSize = publishPacketSize; + /* Now that the "Remaining length" is known, recalculate the payload limit + * based on the size of its encoding. */ + payloadLimit -= _remainingLengthEncodedSize( publishPacketSize ); + + /* Check that the given payload fits within the size allowed by MQTT spec. */ + if( pPublishInfo->payloadLength > payloadLimit ) + { + status = false; + } + else + { + /* Set the "Remaining length" output parameter and calculate the full + * size of the PUBLISH packet. */ + *pRemainingLength = publishPacketSize; + + publishPacketSize += 1 + _remainingLengthEncodedSize( publishPacketSize ); + *pPacketSize = publishPacketSize; + } } return status; From 641d764684e29847d5d82893137fc1f35e7079fe Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 23 Apr 2019 12:58:35 -0700 Subject: [PATCH 109/844] Fix some compiler warnings (#389) --- lib/include/iot_serializer.h | 2 +- tests/mqtt/system/iot_tests_mqtt_system.c | 28 +++++++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index e56f8ad468..0a29300365 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -173,7 +173,7 @@ #define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_ARRAY } -#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED, 0 } +#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED } #define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index fb50001ef8..f606761875 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -454,20 +454,30 @@ static void _subscribePublishWait( IotMqttQos_t qos ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttNetworkInfo_t networkInfo = _networkInfo; - IotMqttSerializer_t serializer = *_pMqttSerializer; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotSemaphore_t waitSem; - /* Set the serializer overrides. */ - serializer.freePacket = _freePacket; - serializer.serialize.connect = _serializeConnect; - serializer.serialize.publish = _serializePublish; - serializer.serialize.puback = _serializePuback; - serializer.serialize.subscribe = _serializeSubscribe; - serializer.serialize.unsubscribe = _serializeUnsubscribe; - serializer.serialize.disconnect = _serializeDisconnect; + /* The serializer must be static so that it remains in scope for the lifetime + * of the MQTT connection (after this function returns). */ + static IotMqttSerializer_t serializer = IOT_MQTT_SERIALIZER_INITIALIZER; + + /* Initialize the serializer on the first run, when any function pointer + * in the serializer is NULL. */ + if( serializer.freePacket == NULL ) + { + serializer = *_pMqttSerializer; + serializer.freePacket = _freePacket; + serializer.serialize.connect = _serializeConnect; + serializer.serialize.publish = _serializePublish; + serializer.serialize.puback = _serializePuback; + serializer.serialize.subscribe = _serializeSubscribe; + serializer.serialize.unsubscribe = _serializeUnsubscribe; + serializer.serialize.disconnect = _serializeDisconnect; + } + + /* Set the serializer function pointers. */ networkInfo.pMqttSerializer = &serializer; /* Create the wait semaphore. */ From de5b55207be0ae28e27e2e7be3d71313aba0fba5 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 23 Apr 2019 15:24:49 -0700 Subject: [PATCH 110/844] Fix warnings from TI compiler (#390) --- lib/source/common/iot_taskpool.c | 6 +++--- tests/mqtt/system/iot_tests_mqtt_system.c | 4 ++-- tests/mqtt/unit/iot_tests_mqtt_validate.c | 13 ++++--------- tests/shadow/system/aws_iot_tests_shadow_system.c | 2 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 2 +- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index d18e00f93d..031e7d20f3 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -1391,7 +1391,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, if( TASKPOOL_SUCCEEDED( status ) ) { - /* Append the job to the dispatch queue. + /* Append the job to the dispatch queue. * Put the job at the front, if it is a high priority job. */ if(mustGrow == true ) { @@ -1401,7 +1401,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, { IotDeQueue_EnqueueTail( &pTaskPool->dispatchQueue, &pJob->link ); } - + /* Signal a worker to pick up the job. */ IotSemaphore_Post( &pTaskPool->dispatchSignal ); @@ -1641,7 +1641,7 @@ static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, IotTaskPool_Assert( delta > 0 ); - if( IotClock_TimerArm( pTimer, delta, 0 ) == false ) + if( IotClock_TimerArm( pTimer, ( uint32_t ) delta, 0 ) == false ) { IotLogWarn( "Failed to re-arm timer for task pool" ); } diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index f606761875..762bdaad98 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -708,7 +708,7 @@ TEST( MQTT_System, SubscribePublishAsync ) IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { 0 }; + _operationCompleteParams_t callbackParam = { .expectedOperation = ( IotMqttOperationType_t ) 0 }; IotSemaphore_t publishWaitSem; /* Initialize members of the operation callback info. */ @@ -1134,7 +1134,7 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with a completion callback. */ - subscription.qos = 1; + subscription.qos = IOT_MQTT_QOS_1; subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 8f55ef02ce..2a35f6ec4b 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -198,19 +198,14 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) publishInfo.payloadLength = 0; /* Negative QoS or QoS > 2. */ - publishInfo.qos = -1; + publishInfo.qos = ( IotMqttQos_t ) -1; validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - publishInfo.qos = 3; + publishInfo.qos = ( IotMqttQos_t ) 3; validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); publishInfo.qos = IOT_MQTT_QOS_0; - /* Negative retry limit. */ - publishInfo.retryLimit = -1; - validateStatus = _IotMqtt_ValidatePublish( false, &publishInfo ); - TEST_ASSERT_EQUAL_INT( false, validateStatus ); - /* Positive retry limit with no period. */ publishInfo.retryLimit = 1; publishInfo.retryMs = 0; @@ -318,10 +313,10 @@ TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* One subscription with invalid QoS. */ - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = -1; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) -1; validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = 3; + pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) 3; validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* QoS is not validated for UNSUBSCRIBE. */ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 285e9a0497..3a7960aa5a 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -278,7 +278,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { 0 }; + _operationCompleteParams_t callbackParam = { .expectedType = ( AwsIotShadowCallbackType_t ) 0 }; /* Initialize the members of the operation callback info. */ callbackInfo.pCallbackContext = &callbackParam; diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 03fb038bee..c332903a26 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -579,7 +579,7 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; /* Invalid QoS. */ - documentInfo.qos = 3; + documentInfo.qos = ( IotMqttQos_t ) 3; status = AwsIotShadow_Get( _pMqttConnection, &documentInfo, AWS_IOT_SHADOW_FLAG_WAITABLE, From ecbed2eb6e47f89743ee25241610ed2f00a8d341 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 24 Apr 2019 16:20:08 -0700 Subject: [PATCH 111/844] Fix compiler errors on TI compiler (#391) --- tests/serializer/iot_test_serializer_cbor.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/serializer/iot_test_serializer_cbor.c b/tests/serializer/iot_test_serializer_cbor.c index a329aab20f..d478eb8035 100644 --- a/tests/serializer/iot_test_serializer_cbor.c +++ b/tests/serializer/iot_test_serializer_cbor.c @@ -24,6 +24,7 @@ */ /* Standard includes. */ +#include #include #include #include @@ -88,7 +89,7 @@ TEST_GROUP_RUNNER( Full_Serializer_CBOR ) TEST( Full_Serializer_CBOR, Encoder_init_with_null_buffer ) { - IotSerializerEncoderObject_t encoderObject = { 0 }; + IotSerializerEncoderObject_t encoderObject = { .type = ( IotSerializerDataType_t ) 0 }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _encoder.init( &encoderObject, NULL, 0 ) ); @@ -234,6 +235,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_map ) TEST( Full_Serializer_CBOR, Encoder_open_array ) { + uint8_t i = 0; IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; int64_t numberArray[] = { 3, 2, 1 }; @@ -241,7 +243,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_array ) TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _encoder.openContainer( &_encoderObject, &arrayObject, 3 ) ); - for( uint8_t i = 0; i < 3; i++ ) + for( i = 0; i < 3; i++ ) { TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _encoder.append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); @@ -264,7 +266,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_array ) TEST_ASSERT_EQUAL( CborNoError, cbor_value_enter_container( &outermostValue, &arrayValue ) ); - for( uint8_t i = 0; i < 3; i++ ) + for( i = 0; i < 3; i++ ) { TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &arrayValue ) ); TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &arrayValue, &number ) ); @@ -325,6 +327,7 @@ TEST( Full_Serializer_CBOR, Encoder_map_nest_map ) TEST( Full_Serializer_CBOR, Encoder_map_nest_array ) { + uint8_t i = 0; IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; @@ -336,7 +339,7 @@ TEST( Full_Serializer_CBOR, Encoder_map_nest_array ) TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _encoder.openContainerWithKey( &mapObject, "array", &arrayObject, 3 ) ); - for( uint8_t i = 0; i < 3; i++ ) + for( i = 0; i < 3; i++ ) { TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _encoder.append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); @@ -367,7 +370,7 @@ TEST( Full_Serializer_CBOR, Encoder_map_nest_array ) TEST_ASSERT_EQUAL( CborNoError, cbor_value_enter_container( &array, &arrayElement ) ); - for( uint8_t i = 0; i < 3; i++ ) + for( i = 0; i < 3; i++ ) { TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &arrayElement ) ); TEST_ASSERT_EQUAL( CborNoError, cbor_value_get_int64( &arrayElement, &number ) ); From d004b98944d0445160a62408583ffedbe774d3d7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 25 Apr 2019 15:05:15 -0700 Subject: [PATCH 112/844] Fix race condition between network and reschedule (#393) --- lib/source/mqtt/iot_mqtt_operation.c | 75 ++++++++++++++++++---------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 388e44b5a0..2242dac724 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -278,7 +278,7 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) if( firstRetry == true ) { /* Operation must be linked. */ - IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) ); + IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == true ); /* Transfer to pending response list. */ IotListDouble_Remove( &( pOperation->link ) ); @@ -290,6 +290,10 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } /* The references mutex only needs to be unlocked on the first retry, since * only the first retry manipulates the connection lists. */ @@ -371,7 +375,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, /* Clear the operation data. */ ( void ) memset( pOperation, 0x00, sizeof( _mqttOperation_t ) ); - /* Initialize the some members of the new operation. */ + /* Initialize some members of the new operation. */ pOperation->pMqttConnection = pMqttConnection; pOperation->u.operation.jobReference = 1; pOperation->u.operation.flags = flags; @@ -444,6 +448,10 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } _IOT_FUNCTION_CLEANUP_END(); } @@ -918,13 +926,13 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + /* A successfully scheduled PUBLISH retry is awaiting a response + * from the network. */ + networkPending = true; } } else { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - /* Decrement reference count to signal completion of send job. Check * if the operation should be destroyed. */ if( waitable == true ) @@ -937,10 +945,11 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } /* If the operation should not be destroyed, transfer it from the - * pending processing to the pending response list. Do not transfer - * operations with retries. */ + * pending processing to the pending response list. */ if( destroyOperation == false ) { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + /* Operation must be linked. */ IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) ); @@ -949,6 +958,8 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), &( pOperation->link ) ); + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + /* This operation is now awaiting a response from the network. */ networkPending = true; } @@ -956,8 +967,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, { _EMPTY_ELSE_MARKER; } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } } else @@ -1220,18 +1229,8 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) _EMPTY_ELSE_MARKER; } - /* For a waitable operation, post to the wait semaphore. */ - if( waitable == true ) - { - IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); - - IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " - "notified of completion.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); - } - else + /* Schedule callback invocation for non-waitable operation. */ + if( waitable == false ) { /* Non-waitable operation should have job reference of 1. */ IotMqtt_Assert( pOperation->u.operation.jobReference == 1 ); @@ -1252,11 +1251,17 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) IotMqtt_OperationType( pOperation->u.operation.type ), pOperation ); - /* A completed operation should not be linked. */ - IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == false ); - /* Place the scheduled operation back in the list of operations pending * processing. */ + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } + IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), &( pOperation->link ) ); } @@ -1275,11 +1280,15 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) _EMPTY_ELSE_MARKER; } } + else + { + _EMPTY_ELSE_MARKER; + } /* Operations that weren't scheduled may be destroyed. */ if( status == IOT_MQTT_SCHEDULING_ERROR ) { - /* Decrement reference count of operations with no callback. */ + /* Decrement reference count of operations not scheduled. */ if( _IotMqtt_DecrementOperationReferences( pOperation, false ) == true ) { _IotMqtt_DestroyOperation( pOperation ); @@ -1288,6 +1297,22 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) { _EMPTY_ELSE_MARKER; } + + /* Post to a waitable operation's semaphore. */ + if( waitable == true ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " + "notified of completion.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); + + IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); + } + else + { + _EMPTY_ELSE_MARKER; + } } else { From e3439b902f3d3729903ff39f94ba8b7d74253577 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 26 Apr 2019 14:04:19 -0700 Subject: [PATCH 113/844] Rename constants to avoid reserved identifiers (#394) --- demos/app/posix/iot_demo_arguments_posix.c | 14 +- demos/include/iot_demo_logging.h | 8 +- demos/source/aws_iot_demo_shadow.c | 50 +- demos/source/iot_demo_mqtt.c | 112 ++--- doc/config/common | 6 +- doc/guide/style.txt | 2 +- doc/lib/logging.txt | 26 +- lib/include/iot_logging_setup.h | 42 +- .../private/aws_iot_defender_internal.h | 22 +- lib/include/private/aws_iot_shadow_internal.h | 84 ++-- lib/include/private/iot_error.h | 28 +- lib/include/private/iot_logging.h | 14 +- lib/include/private/iot_mqtt_internal.h | 48 +- lib/include/private/iot_taskpool_internal.h | 26 +- lib/source/common/iot_init.c | 16 +- lib/source/common/iot_logging.c | 18 +- .../aws_iot_static_memory_shadow.c | 4 +- .../static_memory/iot_static_memory_mqtt.c | 4 +- lib/source/defender/aws_iot_defender_api.c | 8 +- .../defender/aws_iot_defender_collector.c | 124 ++--- lib/source/defender/aws_iot_defender_mqtt.c | 26 +- lib/source/mqtt/iot_mqtt_api.c | 260 +++++----- lib/source/mqtt/iot_mqtt_network.c | 126 ++--- lib/source/mqtt/iot_mqtt_operation.c | 102 ++-- lib/source/mqtt/iot_mqtt_serialize.c | 446 +++++++++--------- lib/source/mqtt/iot_mqtt_subscription.c | 56 +-- lib/source/mqtt/iot_mqtt_validate.c | 186 ++++---- lib/source/shadow/aws_iot_shadow_api.c | 34 +- lib/source/shadow/aws_iot_shadow_operation.c | 34 +- lib/source/shadow/aws_iot_shadow_parser.c | 60 +-- .../shadow/aws_iot_shadow_subscription.c | 58 +-- platform/source/posix/iot_clock_posix.c | 34 +- platform/source/posix/iot_threads_posix.c | 22 +- .../posix/network/iot_network_openssl.c | 70 +-- tests/common/iot_tests_common.c | 10 +- tests/defender/aws_iot_tests_defender_api.c | 102 ++-- tests/iot_config.h | 2 +- tests/mqtt/access/iot_test_access_mqtt.h | 6 +- tests/mqtt/iot_tests_mqtt.c | 10 +- tests/mqtt/system/iot_tests_mqtt_stress.c | 34 +- tests/mqtt/system/iot_tests_mqtt_system.c | 36 +- tests/mqtt/unit/iot_tests_mqtt_api.c | 200 ++++---- tests/mqtt/unit/iot_tests_mqtt_receive.c | 160 +++---- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 186 ++++---- tests/mqtt/unit/iot_tests_mqtt_validate.c | 134 +++--- tests/serializer/iot_test_serializer_cbor.c | 22 +- tests/shadow/aws_iot_tests_shadow.c | 10 +- .../system/aws_iot_tests_shadow_system.c | 44 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 94 ++-- .../shadow/unit/aws_iot_tests_shadow_parser.c | 10 +- 50 files changed, 1615 insertions(+), 1615 deletions(-) diff --git a/demos/app/posix/iot_demo_arguments_posix.c b/demos/app/posix/iot_demo_arguments_posix.c index ecb6d8f9b2..42d6bc937c 100644 --- a/demos/app/posix/iot_demo_arguments_posix.c +++ b/demos/app/posix/iot_demo_arguments_posix.c @@ -98,7 +98,7 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) static bool _validateArguments( const IotDemoArguments_t * pArguments ) { /* Declare a status variable for this function. */ - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); /* Check that a server was set. */ if( ( pArguments->pHostName == NULL ) || @@ -106,7 +106,7 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) { IotLogError( "MQTT server not set. Exiting." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Check that a server port was set. */ @@ -114,7 +114,7 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) { IotLogError( "MQTT server port not set. Exiting." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Check credentials for a secured connection. */ @@ -126,7 +126,7 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) { IotLogError( "Root CA path not set. Exiting." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Check that a client certificate path was set. */ @@ -135,7 +135,7 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) { IotLogError( "Client certificate path not set. Exiting." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Check that a client certificate private key was set. */ @@ -144,12 +144,12 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) { IotLogError( "Client certificate private key not set. Exiting." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } } /* No cleanup is required for this function. */ - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/demos/include/iot_demo_logging.h b/demos/include/iot_demo_logging.h index 4904a11c64..b73ee11e4b 100644 --- a/demos/include/iot_demo_logging.h +++ b/demos/include/iot_demo_logging.h @@ -36,17 +36,17 @@ * - IOT_LOG_NONE if neither IOT_LOG_LEVEL_DEMO nor IOT_LOG_LEVEL_GLOBAL are defined. */ #ifdef IOT_LOG_LEVEL_DEMO - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_DEMO + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_DEMO #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif /* Set the library name to print with the demos. */ -#define _LIBRARY_LOG_NAME ( "DEMO" ) +#define LIBRARY_LOG_NAME ( "DEMO" ) /* Include the logging setup header. This enables the logs. */ #include "iot_logging_setup.h" diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 0d68c53360..2bdcddad71 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -79,12 +79,12 @@ * * An MQTT ping request will be sent periodically at this interval. */ -#define _KEEP_ALIVE_SECONDS ( 60 ) +#define KEEP_ALIVE_SECONDS ( 60 ) /** * @brief The timeout for Shadow and MQTT operations in this demo. */ -#define _TIMEOUT_MS ( 5000 ) +#define TIMEOUT_MS ( 5000 ) /** * @brief Format string representing a Shadow document with a "desired" state. @@ -93,7 +93,7 @@ * token must be unique at any given time, but may be reused once the update is * completed. For this demo, a timestamp is used for a client token. */ -#define _SHADOW_DESIRED_JSON \ +#define SHADOW_DESIRED_JSON \ "{" \ "\"state\":{" \ "\"desired\":{" \ @@ -104,12 +104,12 @@ "}" /** - * @brief The expected size of #_SHADOW_DESIRED_JSON. + * @brief The expected size of #SHADOW_DESIRED_JSON. * - * Because all the format specifiers in #_SHADOW_DESIRED_JSON include a length, + * Because all the format specifiers in #SHADOW_DESIRED_JSON include a length, * its full size is known at compile-time. */ -#define _EXPECTED_DESIRED_JSON_SIZE ( sizeof( _SHADOW_DESIRED_JSON ) - 4 ) +#define EXPECTED_DESIRED_JSON_SIZE ( sizeof( SHADOW_DESIRED_JSON ) - 4 ) /** * @brief Format string representing a Shadow document with a "reported" state. @@ -118,7 +118,7 @@ * token must be unique at any given time, but may be reused once the update is * completed. For this demo, a timestamp is used for a client token. */ -#define _SHADOW_REPORTED_JSON \ +#define SHADOW_REPORTED_JSON \ "{" \ "\"state\":{" \ "\"reported\":{" \ @@ -129,12 +129,12 @@ "}" /** - * @brief The expected size of #_SHADOW_REPORTED_JSON. + * @brief The expected size of #SHADOW_REPORTED_JSON. * - * Because all the format specifiers in #_SHADOW_REPORTED_JSON include a length, + * Because all the format specifiers in #SHADOW_REPORTED_JSON include a length, * its full size is known at compile-time. */ -#define _EXPECTED_REPORTED_JSON_SIZE ( sizeof( _SHADOW_REPORTED_JSON ) - 4 ) +#define EXPECTED_REPORTED_JSON_SIZE ( sizeof( SHADOW_REPORTED_JSON ) - 4 ) /*-----------------------------------------------------------*/ @@ -274,7 +274,7 @@ static void _shadowDeltaCallback( void * pCallbackContext, /* A buffer containing the update document. It has static duration to prevent * it from being placed on the call stack. */ - static char pUpdateDocument[ _EXPECTED_REPORTED_JSON_SIZE + 1 ] = { 0 }; + static char pUpdateDocument[ EXPECTED_REPORTED_JSON_SIZE + 1 ] = { 0 }; /* Check if there is a different "powerOn" state in the Shadow. */ deltaFound = _getDelta( pCallbackParam->u.callback.pDocument, @@ -313,17 +313,17 @@ static void _shadowDeltaCallback( void * pCallbackContext, updateDocument.pThingName = pCallbackParam->pThingName; updateDocument.thingNameLength = pCallbackParam->thingNameLength; updateDocument.u.update.pUpdateDocument = pUpdateDocument; - updateDocument.u.update.updateDocumentLength = _EXPECTED_REPORTED_JSON_SIZE; + updateDocument.u.update.updateDocumentLength = EXPECTED_REPORTED_JSON_SIZE; /* Generate a Shadow document for the reported state. To keep the client * token within 6 characters, it is modded by 1000000. */ updateDocumentLength = snprintf( pUpdateDocument, - _EXPECTED_REPORTED_JSON_SIZE + 1, - _SHADOW_REPORTED_JSON, + EXPECTED_REPORTED_JSON_SIZE + 1, + SHADOW_REPORTED_JSON, currentState, ( long long unsigned ) IotClock_GetTimeMs() % 1000000 ); - if( ( size_t ) updateDocumentLength != _EXPECTED_REPORTED_JSON_SIZE ) + if( ( size_t ) updateDocumentLength != EXPECTED_REPORTED_JSON_SIZE ) { IotLogError( "Failed to generate reported state document for Shadow update." ); } @@ -532,7 +532,7 @@ static int _establishMqttConnection( const char * pIdentifier, /* Set the members of the connection info not set by the initializer. */ connectInfo.awsIotMqttMode = true; connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ connectInfo.pClientIdentifier = pIdentifier; @@ -546,7 +546,7 @@ static int _establishMqttConnection( const char * pIdentifier, /* Establish the MQTT connection. */ connectStatus = IotMqtt_Connect( &networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, pMqttConnection ); if( connectStatus != IOT_MQTT_SUCCESS ) @@ -641,7 +641,7 @@ static void _clearShadowDocument( IotMqttConnection_t mqttConnection, pThingName, thingNameLength, 0, - _TIMEOUT_MS ); + TIMEOUT_MS ); /* Check for return values of "SUCCESS" and "NOT FOUND". Both of these values * mean that the Shadow document is now empty. */ @@ -685,13 +685,13 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, /* A buffer containing the update document. It has static duration to prevent * it from being placed on the call stack. */ - static char pUpdateDocument[ _EXPECTED_DESIRED_JSON_SIZE + 1 ] = { 0 }; + static char pUpdateDocument[ EXPECTED_DESIRED_JSON_SIZE + 1 ] = { 0 }; /* Set the common members of the Shadow update document info. */ updateDocument.pThingName = pThingName; updateDocument.thingNameLength = thingNameLength; updateDocument.u.update.pUpdateDocument = pUpdateDocument; - updateDocument.u.update.updateDocumentLength = _EXPECTED_DESIRED_JSON_SIZE; + updateDocument.u.update.updateDocumentLength = EXPECTED_DESIRED_JSON_SIZE; /* Publish Shadow updates at a set period. */ for( i = 1; i <= AWS_IOT_DEMO_SHADOW_UPDATE_COUNT; i++ ) @@ -702,14 +702,14 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, /* Generate a Shadow desired state document, using a timestamp for the client * token. To keep the client token within 6 characters, it is modded by 1000000. */ status = snprintf( pUpdateDocument, - _EXPECTED_DESIRED_JSON_SIZE + 1, - _SHADOW_DESIRED_JSON, + EXPECTED_DESIRED_JSON_SIZE + 1, + SHADOW_DESIRED_JSON, desiredState, ( long long unsigned ) IotClock_GetTimeMs() % 1000000 ); /* Check for errors from snprintf. The expected value is the length of * the desired JSON document less the format specifier for the state. */ - if( ( size_t ) status != _EXPECTED_DESIRED_JSON_SIZE ) + if( ( size_t ) status != EXPECTED_DESIRED_JSON_SIZE ) { IotLogError( "Failed to generate desired state document for Shadow update" " %d of %d.", i, AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ); @@ -735,7 +735,7 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, updateStatus = AwsIotShadow_TimedUpdate( mqttConnection, &updateDocument, AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - _TIMEOUT_MS ); + TIMEOUT_MS ); /* Check the status of the Shadow update. */ if( updateStatus != AWS_IOT_SHADOW_SUCCESS ) @@ -755,7 +755,7 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ); /* Wait for the delta callback to change its state before continuing. */ - if( IotSemaphore_TimedWait( pDeltaSemaphore, _TIMEOUT_MS ) == false ) + if( IotSemaphore_TimedWait( pDeltaSemaphore, TIMEOUT_MS ) == false ) { IotLogError( "Timed out waiting on delta callback to change state." ); diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index 8101a67715..70565bcd03 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -75,26 +75,26 @@ * This prefix is also used to generate topic names and topic filters used in this * demo. */ -#define _CLIENT_IDENTIFIER_PREFIX "iotdemo" +#define CLIENT_IDENTIFIER_PREFIX "iotdemo" /** * @brief The longest client identifier that an MQTT server must accept (as defined * by the MQTT 3.1.1 spec) is 23 characters. Add 1 to include the length of the NULL * terminator. */ -#define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) +#define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) /** * @brief The keep-alive interval used for this demo. * * An MQTT ping request will be sent periodically at this interval. */ -#define _KEEP_ALIVE_SECONDS ( 60 ) +#define KEEP_ALIVE_SECONDS ( 60 ) /** * @brief The timeout for MQTT operations in this demo. */ -#define _MQTT_TIMEOUT_MS ( 5000 ) +#define MQTT_TIMEOUT_MS ( 5000 ) /** * @brief The Last Will and Testament topic name in this demo. @@ -102,76 +102,76 @@ * The MQTT server will publish a message to this topic name if this client is * unexpectedly disconnected. */ -#define _WILL_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/will" +#define WILL_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/will" /** - * @brief The length of #_WILL_TOPIC_NAME. + * @brief The length of #WILL_TOPIC_NAME. */ -#define _WILL_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _WILL_TOPIC_NAME ) - 1 ) ) +#define WILL_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( WILL_TOPIC_NAME ) - 1 ) ) /** - * @brief The message to publish to #_WILL_TOPIC_NAME. + * @brief The message to publish to #WILL_TOPIC_NAME. */ -#define _WILL_MESSAGE "MQTT demo unexpectedly disconnected." +#define WILL_MESSAGE "MQTT demo unexpectedly disconnected." /** - * @brief The length of #_WILL_MESSAGE. + * @brief The length of #WILL_MESSAGE. */ -#define _WILL_MESSAGE_LENGTH ( ( size_t ) ( sizeof( _WILL_MESSAGE ) - 1 ) ) +#define WILL_MESSAGE_LENGTH ( ( size_t ) ( sizeof( WILL_MESSAGE ) - 1 ) ) /** * @brief How many topic filters will be used in this demo. */ -#define _TOPIC_FILTER_COUNT ( 4 ) +#define TOPIC_FILTER_COUNT ( 4 ) /** * @brief The length of each topic filter. * * For convenience, all topic filters are the same length. */ -#define _TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1" ) - 1 ) ) +#define TOPIC_FILTER_LENGTH ( ( uint16_t ) ( sizeof( IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1" ) - 1 ) ) /** * @brief Format string of the PUBLISH messages in this demo. */ -#define _PUBLISH_PAYLOAD_FORMAT "Hello world %d!" +#define PUBLISH_PAYLOAD_FORMAT "Hello world %d!" /** * @brief Size of the buffer that holds the PUBLISH messages in this demo. */ -#define _PUBLISH_PAYLOAD_BUFFER_LENGTH ( sizeof( _PUBLISH_PAYLOAD_FORMAT ) + 2 ) +#define PUBLISH_PAYLOAD_BUFFER_LENGTH ( sizeof( PUBLISH_PAYLOAD_FORMAT ) + 2 ) /** * @brief The maximum number of times each PUBLISH in this demo will be retried. */ -#define _PUBLISH_RETRY_LIMIT ( 10 ) +#define PUBLISH_RETRY_LIMIT ( 10 ) /** * @brief A PUBLISH message is retried if no response is received within this * time. */ -#define _PUBLISH_RETRY_MS ( 1000 ) +#define PUBLISH_RETRY_MS ( 1000 ) /** * @brief The topic name on which acknowledgement messages for incoming publishes * should be published. */ -#define _ACKNOWLEDGEMENT_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/acknowledgements" +#define ACKNOWLEDGEMENT_TOPIC_NAME IOT_DEMO_MQTT_TOPIC_PREFIX "/acknowledgements" /** - * @brief The length of #_ACKNOWLEDGEMENT_TOPIC_NAME. + * @brief The length of #ACKNOWLEDGEMENT_TOPIC_NAME. */ -#define _ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _ACKNOWLEDGEMENT_TOPIC_NAME ) - 1 ) ) +#define ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( ACKNOWLEDGEMENT_TOPIC_NAME ) - 1 ) ) /** * @brief Format string of PUBLISH acknowledgement messages in this demo. */ -#define _ACKNOWLEDGEMENT_MESSAGE_FORMAT "Client has received PUBLISH %.*s from server." +#define ACKNOWLEDGEMENT_MESSAGE_FORMAT "Client has received PUBLISH %.*s from server." /** * @brief Size of the buffers that hold acknowledgement messages in this demo. */ -#define _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ( sizeof( _ACKNOWLEDGEMENT_MESSAGE_FORMAT ) + 2 ) +#define ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ( sizeof( ACKNOWLEDGEMENT_MESSAGE_FORMAT ) + 2 ) /*-----------------------------------------------------------*/ @@ -238,7 +238,7 @@ static void _mqttSubscriptionCallback( void * param1, size_t messageNumberIndex = 0, messageNumberLength = 1; IotSemaphore_t * pPublishesReceived = ( IotSemaphore_t * ) param1; const char * pPayload = pPublish->u.message.info.pPayload; - char pAcknowledgementMessage[ _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; + char pAcknowledgementMessage[ ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH ] = { 0 }; IotMqttPublishInfo_t acknowledgementInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Print information about the incoming PUBLISH message. */ @@ -282,8 +282,8 @@ static void _mqttSubscriptionCallback( void * param1, /* Generate an acknowledgement message. */ acknowledgementLength = snprintf( pAcknowledgementMessage, - _ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH, - _ACKNOWLEDGEMENT_MESSAGE_FORMAT, + ACKNOWLEDGEMENT_MESSAGE_BUFFER_LENGTH, + ACKNOWLEDGEMENT_MESSAGE_FORMAT, ( int ) messageNumberLength, pPayload + messageNumberIndex ); @@ -298,12 +298,12 @@ static void _mqttSubscriptionCallback( void * param1, { /* Set the members of the publish info for the acknowledgement message. */ acknowledgementInfo.qos = IOT_MQTT_QOS_1; - acknowledgementInfo.pTopicName = _ACKNOWLEDGEMENT_TOPIC_NAME; - acknowledgementInfo.topicNameLength = _ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH; + acknowledgementInfo.pTopicName = ACKNOWLEDGEMENT_TOPIC_NAME; + acknowledgementInfo.topicNameLength = ACKNOWLEDGEMENT_TOPIC_NAME_LENGTH; acknowledgementInfo.pPayload = pAcknowledgementMessage; acknowledgementInfo.payloadLength = ( size_t ) acknowledgementLength; - acknowledgementInfo.retryMs = _PUBLISH_RETRY_MS; - acknowledgementInfo.retryLimit = _PUBLISH_RETRY_LIMIT; + acknowledgementInfo.retryMs = PUBLISH_RETRY_MS; + acknowledgementInfo.retryLimit = PUBLISH_RETRY_LIMIT; /* Send the acknowledgement for the received message. This demo program * will not be notified on the status of the acknowledgement because @@ -398,7 +398,7 @@ static int _establishMqttConnection( bool awsIotMqttMode, IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - char pClientIdentifierBuffer[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ @@ -410,16 +410,16 @@ static int _establishMqttConnection( bool awsIotMqttMode, /* Set the members of the connection info not set by the initializer. */ connectInfo.awsIotMqttMode = awsIotMqttMode; connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = _KEEP_ALIVE_SECONDS; + connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; connectInfo.pWillInfo = &willInfo; /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects * unexpectedly. */ - willInfo.pTopicName = _WILL_TOPIC_NAME; - willInfo.topicNameLength = _WILL_TOPIC_NAME_LENGTH; - willInfo.pPayload = _WILL_MESSAGE; - willInfo.payloadLength = _WILL_MESSAGE_LENGTH; + willInfo.pTopicName = WILL_TOPIC_NAME; + willInfo.topicNameLength = WILL_TOPIC_NAME_LENGTH; + willInfo.pPayload = WILL_MESSAGE; + willInfo.payloadLength = WILL_MESSAGE_LENGTH; /* Use the parameter client identifier if provided. Otherwise, generate a * unique client identifier. */ @@ -434,8 +434,8 @@ static int _establishMqttConnection( bool awsIotMqttMode, * generate this unique client identifier by appending a timestamp to a common * prefix. */ status = snprintf( pClientIdentifierBuffer, - _CLIENT_IDENTIFIER_MAX_LENGTH, - _CLIENT_IDENTIFIER_PREFIX "%lu", + CLIENT_IDENTIFIER_MAX_LENGTH, + CLIENT_IDENTIFIER_PREFIX "%lu", ( long unsigned int ) IotClock_GetTimeMs() ); /* Check for errors from snprintf. */ @@ -464,7 +464,7 @@ static int _establishMqttConnection( bool awsIotMqttMode, connectStatus = IotMqtt_Connect( &networkInfo, &connectInfo, - _MQTT_TIMEOUT_MS, + MQTT_TIMEOUT_MS, pMqttConnection ); if( connectStatus != IOT_MQTT_SUCCESS ) @@ -501,14 +501,14 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, int status = EXIT_SUCCESS; int32_t i = 0; IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t pSubscriptions[ _TOPIC_FILTER_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t pSubscriptions[ TOPIC_FILTER_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Set the members of the subscription list. */ - for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) + for( i = 0; i < TOPIC_FILTER_COUNT; i++ ) { pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; pSubscriptions[ i ].pTopicFilter = pTopicFilters[ i ]; - pSubscriptions[ i ].topicFilterLength = _TOPIC_FILTER_LENGTH; + pSubscriptions[ i ].topicFilterLength = TOPIC_FILTER_LENGTH; pSubscriptions[ i ].callback.pCallbackContext = pCallbackParameter; pSubscriptions[ i ].callback.function = _mqttSubscriptionCallback; } @@ -518,9 +518,9 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, { subscriptionStatus = IotMqtt_TimedSubscribe( mqttConnection, pSubscriptions, - _TOPIC_FILTER_COUNT, + TOPIC_FILTER_COUNT, 0, - _MQTT_TIMEOUT_MS ); + MQTT_TIMEOUT_MS ); /* Check the status of SUBSCRIBE. */ switch( subscriptionStatus ) @@ -532,7 +532,7 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, case IOT_MQTT_SERVER_REFUSED: /* Check which subscriptions were rejected before exiting the demo. */ - for( i = 0; i < _TOPIC_FILTER_COUNT; i++ ) + for( i = 0; i < TOPIC_FILTER_COUNT; i++ ) { if( IotMqtt_IsSubscribed( mqttConnection, pSubscriptions[ i ].pTopicFilter, @@ -564,9 +564,9 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, { subscriptionStatus = IotMqtt_TimedUnsubscribe( mqttConnection, pSubscriptions, - _TOPIC_FILTER_COUNT, + TOPIC_FILTER_COUNT, 0, - _MQTT_TIMEOUT_MS ); + MQTT_TIMEOUT_MS ); /* Check the status of UNSUBSCRIBE. */ if( subscriptionStatus != IOT_MQTT_SUCCESS ) @@ -609,7 +609,7 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotMqttCallbackInfo_t publishComplete = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - char pPublishPayload[ _PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; + char pPublishPayload[ PUBLISH_PAYLOAD_BUFFER_LENGTH ] = { 0 }; /* The MQTT library should invoke this callback when a PUBLISH message * is successfully transmitted. */ @@ -617,10 +617,10 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, /* Set the common members of the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.topicNameLength = _TOPIC_FILTER_LENGTH; + publishInfo.topicNameLength = TOPIC_FILTER_LENGTH; publishInfo.pPayload = pPublishPayload; - publishInfo.retryMs = _PUBLISH_RETRY_MS; - publishInfo.retryLimit = _PUBLISH_RETRY_LIMIT; + publishInfo.retryMs = PUBLISH_RETRY_MS; + publishInfo.retryLimit = PUBLISH_RETRY_LIMIT; /* Loop to PUBLISH all messages of this demo. */ for( publishCount = 0; @@ -639,12 +639,12 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, publishComplete.pCallbackContext = ( void * ) publishCount; /* Choose a topic name (round-robin through the array of topic names). */ - publishInfo.pTopicName = pTopicNames[ publishCount % _TOPIC_FILTER_COUNT ]; + publishInfo.pTopicName = pTopicNames[ publishCount % TOPIC_FILTER_COUNT ]; /* Generate the payload for the PUBLISH. */ status = snprintf( pPublishPayload, - _PUBLISH_PAYLOAD_BUFFER_LENGTH, - _PUBLISH_PAYLOAD_FORMAT, + PUBLISH_PAYLOAD_BUFFER_LENGTH, + PUBLISH_PAYLOAD_FORMAT, ( int ) publishCount ); /* Check for errors from snprintf. */ @@ -692,7 +692,7 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { if( IotSemaphore_TimedWait( pPublishReceivedCounter, - _MQTT_TIMEOUT_MS ) == false ) + MQTT_TIMEOUT_MS ) == false ) { IotLogError( "Timed out waiting for incoming PUBLISH messages." ); status = EXIT_FAILURE; @@ -720,7 +720,7 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) { if( IotSemaphore_TimedWait( pPublishReceivedCounter, - _MQTT_TIMEOUT_MS ) == false ) + MQTT_TIMEOUT_MS ) == false ) { IotLogError( "Timed out waiting for incoming PUBLISH messages." ); status = EXIT_FAILURE; @@ -771,7 +771,7 @@ int RunMqttDemo( bool awsIotMqttMode, IotSemaphore_t publishesReceived; /* Topics used as both topic filters and topic names in this demo. */ - const char * pTopics[ _TOPIC_FILTER_COUNT ] = + const char * pTopics[ TOPIC_FILTER_COUNT ] = { IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/1", IOT_DEMO_MQTT_TOPIC_PREFIX "/topic/2", diff --git a/doc/config/common b/doc/config/common index 7341ac9f77..08dd8d5c01 100644 --- a/doc/config/common +++ b/doc/config/common @@ -22,11 +22,11 @@ QUIET = YES # Define the following preprocessor constants when generating documentation. PREDEFINED = "DOXYGEN=1" \ "IOT_STATIC_MEMORY_ONLY=1" \ - "_LIBRARY_LOG_LEVEL=IOT_LOG_DEBUG" \ - "_LIBRARY_LOG_NAME=\"DOXYGEN\"" + "LIBRARY_LOG_LEVEL=IOT_LOG_DEBUG" \ + "LIBRARY_LOG_NAME=\"DOXYGEN\"" # Ignore the constants used for setting log levels and names. -EXCLUDE_SYMBOLS = "_LIBRARY_LOG_*" +EXCLUDE_SYMBOLS = "LIBRARY_LOG_*" # Configure Doxygen for C. OPTIMIZE_OUTPUT_FOR_C = YES diff --git a/doc/guide/style.txt b/doc/guide/style.txt index 1ccbaa8576..55a45b0aad 100644 --- a/doc/guide/style.txt +++ b/doc/guide/style.txt @@ -200,7 +200,7 @@ The naming convention aims to differentiate this SDK's files, variables, and fun @formattableentry{`IOT_`LIBRARY`_`DESCRIPTION
`AWS_IOT_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in application-facing library header files,`IOT_MQTT_SUCCESS` (iot_mqtt.h)
`AWS_IOT_SHADOW_SUCCESS` (aws_iot_shadow.h)} @formattableentry{`IOT_DEMO_`DESCRIPTION
`IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests,`IOT_DEMO_MQTT_PUBLISH_BURST_SIZE`
`IOT_TEST_MQTT_THREADS`} @formattableentry{`AWS_IOT_DEMO_`DESCRIPTION
`AWS_IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests specific to AWS IoT,`AWS_IOT_DEMO_THING_NAME`
`AWS_IOT_TEST_SHADOW_THING_NAME`} -@formattableentry{`_`DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal namesin AWS-specific libraries,`_MQTT_PACKET_TYPE_CONNECT`
(iot_mqtt_internal.h)
`_SHADOW_OPERATION_COUNT`
(aws_iot_shadow_internal.h)} +@formattableentry{`_`DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal namesin AWS-specific libraries,`MQTT_PACKET_TYPE_CONNECT`
(iot_mqtt_internal.h)
`SHADOW_OPERATION_COUNT`
(aws_iot_shadow_internal.h)} Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `IOT_` or `AWS_IOT_`, while names intended for internal use must begin with only `_`. diff --git a/doc/lib/logging.txt b/doc/lib/logging.txt index 20c3299eb6..3c2ac42d6b 100644 --- a/doc/lib/logging.txt +++ b/doc/lib/logging.txt @@ -36,28 +36,28 @@ Currently, the logging library has the following dependencies: @section logging_setup_use Setup and use @brief How to set up and use the logging library. -The file iot_logging_setup.h should be included to configure logging for a single source file. Before including iot_logging_setup.h, the constants @ref _LIBRARY_LOG_LEVEL and @ref _LIBRARY_LOG_NAME must be defined. +The file iot_logging_setup.h should be included to configure logging for a single source file. Before including iot_logging_setup.h, the constants @ref LIBRARY_LOG_LEVEL and @ref LIBRARY_LOG_NAME must be defined. For example, to configure all the "SAMPLE" library to print all messages below the [info](@ref IOT_LOG_INFO) log level: @code{c} // Print log messages up to the "info" level. -#define _LIBRARY_LOG_LEVEL IOT_LOG_INFO +#define LIBRARY_LOG_LEVEL IOT_LOG_INFO // Print library name "SAMPLE". -#define _LIBRARY_LOG_NAME "SAMPLE" +#define LIBRARY_LOG_NAME "SAMPLE" -// Including this header defines the logging macros using _LIBRARY_LOG_LEVEL and -// _LIBRARY_LOG_NAME. +// Including this header defines the logging macros using LIBRARY_LOG_LEVEL and +// LIBRARY_LOG_NAME. #include "iot_logging_setup.h" int main( void ) { // After including iot_logging_setup.h, the logging functions can be used. - IotLogError( "Error." ); // Will be printed because IOT_LOG_ERROR <= _LIBRARY_LOG_LEVEL - IotLogWarn( "Warning. " ); // Will be printed because IOT_LOG_WARN <= _LIBRARY_LOG_LEVEL - IotLogInfo( "Info." ); // Will be printed because IOT_LOG_INFO <= _LIBRARY_LOG_LEVEL - IotLogDebug( "Debug." ); // Will not be printed because IOT_LOG_DEBUG > _LIBRARY_LOG_LEVEL + IotLogError( "Error." ); // Will be printed because IOT_LOG_ERROR <= LIBRARY_LOG_LEVEL + IotLogWarn( "Warning. " ); // Will be printed because IOT_LOG_WARN <= LIBRARY_LOG_LEVEL + IotLogInfo( "Info." ); // Will be printed because IOT_LOG_INFO <= LIBRARY_LOG_LEVEL + IotLogDebug( "Debug." ); // Will not be printed because IOT_LOG_DEBUG > LIBRARY_LOG_LEVEL return 0; } @@ -75,22 +75,22 @@ The code above will print something like the following: /** @configpage{logging,logging library} -@section _LIBRARY_LOG_LEVEL +@section LIBRARY_LOG_LEVEL @brief The log level for a source file. Sets the log level for a single source file. A log message will only be printed if its level is less than this setting. -Both this constant and @ref _LIBRARY_LOG_NAME must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. +Both this constant and @ref LIBRARY_LOG_NAME must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. @configpossible One of the @ref logging_constants_levels. @note This value must be defined if iot_logging_setup.h is included. The library will not provide a default value for this setting. -@section _LIBRARY_LOG_NAME +@section LIBRARY_LOG_NAME @brief The library name printed in log messages. Sets the library name for a single source file. By default, all log messages contain the library name. The library name may be disabled for a single log message by setting passing an #IotLogConfig_t with [hideLibraryName](@ref IotLogConfig_t.hideLibraryName) set to `true`. -Both this constant and @ref _LIBRARY_LOG_LEVEL must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. +Both this constant and @ref LIBRARY_LOG_LEVEL must be defined before including iot_logging_setup.h. See @ref logging_setup_use for an example on how to use this setting. @configpossible Any string. @note This value must be defined if iot_logging_setup.h is included. The library will not provide a default value for this setting. diff --git a/lib/include/iot_logging_setup.h b/lib/include/iot_logging_setup.h index 58279d1620..35ace4b19f 100644 --- a/lib/include/iot_logging_setup.h +++ b/lib/include/iot_logging_setup.h @@ -45,9 +45,9 @@ * logging function to call. * * This function prints a single log message. It is available when @ref - * _LIBRARY_LOG_LEVEL is not #IOT_LOG_NONE. Log messages automatically + * LIBRARY_LOG_LEVEL is not #IOT_LOG_NONE. Log messages automatically * include the [log level](@ref logging_constants_levels), [library name] - * (@ref _LIBRARY_LOG_NAME), and time. An optional @ref IotLogConfig_t may + * (@ref LIBRARY_LOG_NAME), and time. An optional @ref IotLogConfig_t may * be passed to this function to hide information for a single log message. * * The logging library must be set up before this function may be called. See @@ -79,14 +79,14 @@ /** * @def IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) * @brief Log the contents of buffer as bytes. Only available when @ref - * _LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. + * LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. * * This function prints the bytes located at a given memory address. It is * intended for debugging only, and is therefore only available when @ref - * _LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. + * LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. * * Log messages printed by this function always include the [log level] - * (@ref logging_constants_levels), [library name](@ref _LIBRARY_LOG_NAME), + * (@ref logging_constants_levels), [library name](@ref LIBRARY_LOG_NAME), * and time. In addition, this function may print an optional header `pHeader` * before it prints the contents of the buffer. This function does not have an * #IotLogConfig_t parameter. @@ -164,23 +164,23 @@ * @endcode */ -/* Check that _LIBRARY_LOG_LEVEL is defined and has a valid value. */ -#if !defined( _LIBRARY_LOG_LEVEL ) || \ - ( _LIBRARY_LOG_LEVEL != IOT_LOG_NONE && \ - _LIBRARY_LOG_LEVEL != IOT_LOG_ERROR && \ - _LIBRARY_LOG_LEVEL != IOT_LOG_WARN && \ - _LIBRARY_LOG_LEVEL != IOT_LOG_INFO && \ - _LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) - #error "Please define _LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." -/* Check that _LIBRARY_LOG_NAME is defined and has a valid value. */ -#elif !defined( _LIBRARY_LOG_NAME ) - #error "Please define _LIBRARY_LOG_NAME." +/* Check that LIBRARY_LOG_LEVEL is defined and has a valid value. */ +#if !defined( LIBRARY_LOG_LEVEL ) || \ + ( LIBRARY_LOG_LEVEL != IOT_LOG_NONE && \ + LIBRARY_LOG_LEVEL != IOT_LOG_ERROR && \ + LIBRARY_LOG_LEVEL != IOT_LOG_WARN && \ + LIBRARY_LOG_LEVEL != IOT_LOG_INFO && \ + LIBRARY_LOG_LEVEL != IOT_LOG_DEBUG ) + #error "Please define LIBRARY_LOG_LEVEL as either IOT_LOG_NONE, IOT_LOG_ERROR, IOT_LOG_WARN, IOT_LOG_INFO, or IOT_LOG_DEBUG." +/* Check that LIBRARY_LOG_NAME is defined and has a valid value. */ +#elif !defined( LIBRARY_LOG_NAME ) + #error "Please define LIBRARY_LOG_NAME." #else /* Define IotLog if the log level is greater than "none". */ - #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE #define IotLog( messageLevel, pLogConfig, ... ) \ - IotLog_Generic( _LIBRARY_LOG_LEVEL, \ - _LIBRARY_LOG_NAME, \ + IotLog_Generic( LIBRARY_LOG_LEVEL, \ + LIBRARY_LOG_NAME, \ messageLevel, \ pLogConfig, \ __VA_ARGS__ ) @@ -192,9 +192,9 @@ #define IotLogDebug( ... ) IotLog( IOT_LOG_DEBUG, NULL, __VA_ARGS__ ) /* If log level is DEBUG, enable the function to print buffers. */ - #if _LIBRARY_LOG_LEVEL >= IOT_LOG_DEBUG + #if LIBRARY_LOG_LEVEL >= IOT_LOG_DEBUG #define IotLog_PrintBuffer( pHeader, pBuffer, bufferSize ) \ - IotLog_GenericPrintBuffer( _LIBRARY_LOG_NAME, \ + IotLog_GenericPrintBuffer( LIBRARY_LOG_NAME, \ pHeader, \ pBuffer, \ bufferSize ) diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 1e04e6f35c..a24be12ff8 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -62,16 +62,16 @@ /* Configure logs for Defender functions. */ #ifdef AWS_IOT_LOG_LEVEL_DEFENDER - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEFENDER + #define LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_DEFENDER #else #ifdef AWS_IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "Defender" ) +#define LIBRARY_LOG_NAME ( "Defender" ) #include "iot_logging_setup.h" /* @@ -235,22 +235,22 @@ /*----------------- Below this line is INTERNAL used only --------------------*/ /* This MUST be consistent with enum AwsIotDefenderMetricsGroup_t. */ -#define _DEFENDER_METRICS_GROUP_COUNT 1 +#define DEFENDER_METRICS_GROUP_COUNT 1 /** * Define encoder/decoder based on configuration AWS_IOT_DEFENDER_FORMAT. */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - #define _DEFENDER_FORMAT "cbor" - #define _AwsIotDefenderEncoder _IotSerializerCborEncoder /**< Global defined in iot_serializer.h . */ - #define _AwsIotDefenderDecoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ + #define DEFENDER_FORMAT "cbor" + #define _defenderEncoder _IotSerializerCborEncoder /**< Global defined in iot_serializer.h . */ + #define _defenderDecoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ #elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON #define _DEFENDER_FORMAT "json" - #define _AwsIotDefenderEncoder _IotSerializerJsonEncoder /**< Global defined in iot_serializer.h . */ - #define _AwsIotDefenderDecoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ + #define _defenderEncoder _IotSerializerJsonEncoder /**< Global defined in iot_serializer.h . */ + #define _defenderDecoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ #else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ #error "AWS_IOT_DEFENDER_FORMAT must be either AWS_IOT_DEFENDER_FORMAT_CBOR or AWS_IOT_DEFENDER_FORMAT_JSON." @@ -282,7 +282,7 @@ typedef struct _defenderMetrics /** * Array of bit-flag of metrics. The index is enum value of AwsIotDefenderMetricsGroup_t. */ - uint32_t metricsFlag[ _DEFENDER_METRICS_GROUP_COUNT ]; + uint32_t metricsFlag[ DEFENDER_METRICS_GROUP_COUNT ]; /** * Mutex to protect _AwsIotDefenderMetricsFlag referenced by: diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index aaeaa9dcc8..3d1d667ec8 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -60,16 +60,16 @@ /* Configure logs for Shadow functions. */ #ifdef AWS_IOT_LOG_LEVEL_SHADOW - #define _LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_SHADOW + #define LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_SHADOW #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "Shadow" ) +#define LIBRARY_LOG_NAME ( "Shadow" ) #include "iot_logging_setup.h" /* @@ -175,14 +175,14 @@ * @brief The longest Thing Name accepted by the Shadow service, per the [AWS IoT * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). */ -#define _MAX_THING_NAME_LENGTH ( 128 ) +#define MAX_THING_NAME_LENGTH ( 128 ) /** * @brief The number of currently available Shadow operations. * * The 3 Shadow operations are DELETE, GET, and UPDATE. */ -#define _SHADOW_OPERATION_COUNT ( 3 ) +#define SHADOW_OPERATION_COUNT ( 3 ) /** * @brief The number of currently available Shadow callbacks. @@ -190,109 +190,109 @@ * The 2 Shadow callbacks are `update/delta` (AKA "Delta") and `/update/documents` * (AKA "Updated"). */ -#define _SHADOW_CALLBACK_COUNT ( 2 ) +#define SHADOW_CALLBACK_COUNT ( 2 ) /** * @brief The common prefix of all Shadow MQTT topics, per the [AWS IoT Shadow * spec](https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html). */ -#define _SHADOW_TOPIC_PREFIX "$aws/things/" +#define SHADOW_TOPIC_PREFIX "$aws/things/" /** - * @brief The length of #_SHADOW_TOPIC_PREFIX. + * @brief The length of #SHADOW_TOPIC_PREFIX. */ -#define _SHADOW_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_TOPIC_PREFIX ) - 1 ) ) +#define SHADOW_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_TOPIC_PREFIX ) - 1 ) ) /** * @brief The string representing a Shadow DELETE operation in a Shadow MQTT topic. */ -#define _SHADOW_DELETE_OPERATION_STRING "/shadow/delete" +#define SHADOW_DELETE_OPERATION_STRING "/shadow/delete" /** - * @brief The length of #_SHADOW_DELETE_OPERATION_STRING. + * @brief The length of #SHADOW_DELETE_OPERATION_STRING. */ -#define _SHADOW_DELETE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_DELETE_OPERATION_STRING ) - 1 ) ) +#define SHADOW_DELETE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELETE_OPERATION_STRING ) - 1 ) ) /** * @brief The string representing a Shadow GET operation in a Shadow MQTT topic. */ -#define _SHADOW_GET_OPERATION_STRING "/shadow/get" +#define SHADOW_GET_OPERATION_STRING "/shadow/get" /** - * @brief The length of #_SHADOW_GET_OPERATION_STRING. + * @brief The length of #SHADOW_GET_OPERATION_STRING. */ -#define _SHADOW_GET_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_GET_OPERATION_STRING ) - 1 ) ) +#define SHADOW_GET_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_GET_OPERATION_STRING ) - 1 ) ) /** * @brief The string representing a Shadow UPDATE operation in a Shadow MQTT topic. */ -#define _SHADOW_UPDATE_OPERATION_STRING "/shadow/update" +#define SHADOW_UPDATE_OPERATION_STRING "/shadow/update" /** - * @brief The length of #_SHADOW_UPDATE_OPERATION_STRING. + * @brief The length of #SHADOW_UPDATE_OPERATION_STRING. */ -#define _SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_UPDATE_OPERATION_STRING ) - 1 ) ) +#define SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATE_OPERATION_STRING ) - 1 ) ) /** * @brief The suffix for a Shadow operation "accepted" topic. */ -#define _SHADOW_ACCEPTED_SUFFIX "/accepted" +#define SHADOW_ACCEPTED_SUFFIX "/accepted" /** - * @brief The length of #_SHADOW_ACCEPTED_SUFFIX. + * @brief The length of #SHADOW_ACCEPTED_SUFFIX. */ -#define _SHADOW_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_ACCEPTED_SUFFIX ) - 1 ) ) +#define SHADOW_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_ACCEPTED_SUFFIX ) - 1 ) ) /** * @brief The suffix for a Shadow operation "rejected" topic. */ -#define _SHADOW_REJECTED_SUFFIX "/rejected" +#define SHADOW_REJECTED_SUFFIX "/rejected" /** - * @brief The length of #_SHADOW_REJECTED_SUFFIX. + * @brief The length of #SHADOW_REJECTED_SUFFIX. */ -#define _SHADOW_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_REJECTED_SUFFIX ) - 1 ) ) +#define SHADOW_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_REJECTED_SUFFIX ) - 1 ) ) /** * @brief The suffix for a Shadow delta topic. */ -#define _SHADOW_DELTA_SUFFIX "/delta" +#define SHADOW_DELTA_SUFFIX "/delta" /** - * @brief The length of #_SHADOW_DELTA_SUFFIX. + * @brief The length of #SHADOW_DELTA_SUFFIX. */ -#define _SHADOW_DELTA_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_DELTA_SUFFIX ) - 1 ) ) +#define SHADOW_DELTA_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_DELTA_SUFFIX ) - 1 ) ) /** * @brief The suffix for a Shadow updated topic. */ -#define _SHADOW_UPDATED_SUFFIX "/documents" +#define SHADOW_UPDATED_SUFFIX "/documents" /** - * @brief The length of #_SHADOW_UPDATED_SUFFIX. + * @brief The length of #SHADOW_UPDATED_SUFFIX. */ -#define _SHADOW_UPDATED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( _SHADOW_UPDATED_SUFFIX ) - 1 ) ) +#define SHADOW_UPDATED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATED_SUFFIX ) - 1 ) ) /** * @brief The length of the longest Shadow suffix. */ -#define _SHADOW_LONGEST_SUFFIX_LENGTH _SHADOW_UPDATED_SUFFIX_LENGTH +#define SHADOW_LONGEST_SUFFIX_LENGTH SHADOW_UPDATED_SUFFIX_LENGTH /** * @brief The JSON key used to represent client tokens in a Shadow update document. */ -#define _CLIENT_TOKEN_KEY "clientToken" +#define CLIENT_TOKEN_KEY "clientToken" /** - * @brief The length of #_CLIENT_TOKEN_KEY. + * @brief The length of #CLIENT_TOKEN_KEY. */ -#define _CLIENT_TOKEN_KEY_LENGTH ( sizeof( _CLIENT_TOKEN_KEY ) - 1 ) +#define CLIENT_TOKEN_KEY_LENGTH ( sizeof( CLIENT_TOKEN_KEY ) - 1 ) /** * @brief The longest client token accepted by the Shadow service, per AWS IoT * service limits. */ -#define _MAX_CLIENT_TOKEN_LENGTH ( 64 ) +#define MAX_CLIENT_TOKEN_LENGTH ( 64 ) /** * @brief A flag to represent persistent subscriptions in a Shadow subscriptions @@ -301,7 +301,7 @@ * Its value is negative to distinguish it from valid subscription counts, which * are 0 or positive. */ -#define _PERSISTENT_SUBSCRIPTION ( -1 ) +#define PERSISTENT_SUBSCRIPTION ( -1 ) /*----------------------- Shadow internal data types ------------------------*/ @@ -427,8 +427,8 @@ typedef struct _shadowSubscription { IotLink_t link; /**< @brief List link member. */ - int32_t references[ _SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ - AwsIotShadowCallbackInfo_t callbacks[ _SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ + int32_t references[ SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ + AwsIotShadowCallbackInfo_t callbacks[ SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ /** * @brief Buffer allocated for removing Shadow topics. @@ -443,7 +443,7 @@ typedef struct _shadowSubscription } _shadowSubscription_t; /* Declarations of names printed in logs. */ -#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE extern const char * const _pAwsIotShadowOperationNames[]; extern const char * const _pAwsIotShadowCallbackNames[]; #endif @@ -496,7 +496,7 @@ void _AwsIotShadow_DestroyOperation( void * pData ); * * @warning This function does not check the length of `pTopicBuffer`! Any provided * buffer must be large enough to accommodate the full Shadow topic, plus - * #_SHADOW_LONGEST_SUFFIX_LENGTH. + * #SHADOW_LONGEST_SUFFIX_LENGTH. * * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY. This function * will not return #AWS_IOT_SHADOW_NO_MEMORY when a buffer is provided. @@ -620,7 +620,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe * * @warning This function does not check the length of `pTopicBuffer`! Any provided * buffer must be large enough to accommodate the full Shadow topic, plus - * #_SHADOW_LONGEST_SUFFIX_LENGTH. + * #SHADOW_LONGEST_SUFFIX_LENGTH. * * @note This function should be called with the subscription list mutex locked. */ diff --git a/lib/include/private/iot_error.h b/lib/include/private/iot_error.h index 03409b0b2c..fadaf2413f 100644 --- a/lib/include/private/iot_error.h +++ b/lib/include/private/iot_error.h @@ -41,23 +41,23 @@ * @param[in] statusType The type of the status variable for this function. * @param[in] initialValue The initial value to assign to the status variable. */ -#define _IOT_FUNCTION_ENTRY( statusType, initialValue ) statusType status = initialValue +#define IOT_FUNCTION_ENTRY( statusType, initialValue ) statusType status = initialValue /** * @brief Declares the label that begins a cleanup section. * * This macro should be placed at the end of a function and followed by - * #_IOT_FUNCTION_CLEANUP_END. + * #IOT_FUNCTION_CLEANUP_END. */ -#define _IOT_FUNCTION_CLEANUP_BEGIN() iotCleanup: +#define IOT_FUNCTION_CLEANUP_BEGIN() iotCleanup: /** * @brief Declares the end of a cleanup section. * * This macro should be placed at the end of a function and preceded by - * #_IOT_FUNCTION_CLEANUP_BEGIN. + * #IOT_FUNCTION_CLEANUP_BEGIN. */ -#define _IOT_FUNCTION_CLEANUP_END() return status +#define IOT_FUNCTION_CLEANUP_END() return status /** * @brief Declares an empty cleanup section. @@ -65,19 +65,19 @@ * This macro should be placed at the end of a function to exit on error if no * cleanup is required. */ -#define _IOT_FUNCTION_EXIT_NO_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN(); _IOT_FUNCTION_CLEANUP_END() +#define IOT_FUNCTION_EXIT_NO_CLEANUP() IOT_FUNCTION_CLEANUP_BEGIN(); IOT_FUNCTION_CLEANUP_END() /** * @brief Jump to the cleanup section. */ -#define _IOT_GOTO_CLEANUP() goto iotCleanup +#define IOT_GOTO_CLEANUP() goto iotCleanup /** * @brief Assign a value to the status variable and jump to the cleanup section. * * @param[in] statusValue The value to assign to the status variable. */ -#define _IOT_SET_AND_GOTO_CLEANUP( statusValue ) { status = ( statusValue ); _IOT_GOTO_CLEANUP(); } +#define IOT_SET_AND_GOTO_CLEANUP( statusValue ) { status = ( statusValue ); IOT_GOTO_CLEANUP(); } /** * @brief Jump to the cleanup section if a condition is `false`. @@ -87,7 +87,7 @@ * * @param[in] condition The condition to check. */ -#define _IOT_GOTO_CLEANUP_IF_FALSE( condition ) { if( ( condition ) == false ) { _IOT_GOTO_CLEANUP(); } } +#define IOT_GOTO_CLEANUP_IF_FALSE( condition ) { if( ( condition ) == false ) { IOT_GOTO_CLEANUP(); } } /** * @brief Assign a value to the status variable and jump to the cleanup section @@ -96,9 +96,9 @@ * @param[in] statusValue The value to assign to the status variable. * @param[in] condition The condition to check. */ -#define _IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( statusValue, condition ) \ - if( ( condition ) == false ) \ - _IOT_SET_AND_GOTO_CLEANUP( statusValue ) +#define IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( statusValue, condition ) \ + if( ( condition ) == false ) \ + IOT_SET_AND_GOTO_CLEANUP( statusValue ) /** * @brief Check a condition; if `false`, assign the "Bad parameter" status value @@ -107,7 +107,7 @@ * @param[in] libraryPrefix The library prefix of the status variable. * @param[in] condition The condition to check. */ -#define _IOT_VALIDATE_PARAMETER( libraryPrefix, condition ) \ - _IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( libraryPrefix ## _BAD_PARAMETER, condition ) +#define IOT_VALIDATE_PARAMETER( libraryPrefix, condition ) \ + IOT_SET_AND_GOTO_CLEANUP_IF_FALSE( libraryPrefix ## _BAD_PARAMETER, condition ) #endif /* ifndef IOT_ERROR_H_ */ diff --git a/lib/include/private/iot_logging.h b/lib/include/private/iot_logging.h index 8d0dd4fdc7..39368f48c0 100644 --- a/lib/include/private/iot_logging.h +++ b/lib/include/private/iot_logging.h @@ -47,7 +47,7 @@ * @section logging_constants_levels Log levels * @brief Log levels for the libraries in this SDK. * - * Each library should specify a log level by setting @ref _LIBRARY_LOG_LEVEL. + * Each library should specify a log level by setting @ref LIBRARY_LOG_LEVEL. * All log messages with a level at or below the specified level will be printed * for that library. * @@ -68,7 +68,7 @@ * @brief No log messages. * * Log messages with this level will be silently discarded. When @ref - * _LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no [logging functions] + * LIBRARY_LOG_LEVEL is #IOT_LOG_NONE, logging is disabled and no [logging functions] * (@ref logging_functions) can be called. */ #define IOT_LOG_NONE 0 @@ -168,7 +168,7 @@ typedef struct IotLogConfig * This function is the generic logging function shared across all libraries. * The library-specific logging function @ref logging_function_log is implemented * using this function. Like @ref logging_function_log, this function is only - * available when @ref _LIBRARY_LOG_LEVEL is #IOT_LOG_NONE. + * available when @ref LIBRARY_LOG_LEVEL is #IOT_LOG_NONE. * * In most cases, the library-specific logging function @ref logging_function_log * should be called instead of this function. @@ -176,8 +176,8 @@ typedef struct IotLogConfig * @param[in] libraryLogSetting The log level setting of the library, used to * determine if the log message should be printed. Must be one of the @ref * logging_constants_levels. - * @param[in] pLibraryName The library name to print. See @ref _LIBRARY_LOG_NAME. - * @param[in] messageLevel The log level of the this message. See @ref _LIBRARY_LOG_LEVEL. + * @param[in] pLibraryName The library name to print. See @ref LIBRARY_LOG_NAME. + * @param[in] messageLevel The log level of the this message. See @ref LIBRARY_LOG_LEVEL. * @param[in] pLogConfig Pointer to a #IotLogConfig_t. Optional; pass `NULL` to ignore. * @param[in] pFormat Format string for the log message. * @param[in] ... Arguments for format specification. @@ -199,12 +199,12 @@ void IotLog_Generic( int libraryLogSetting, * This function is the generic buffer logging function shared across all libraries. * The library-specific buffer logging function @ref logging_function_printbuffer is * implemented using this function. Like @ref logging_function_printbuffer, this - * function is only available when @ref _LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. + * function is only available when @ref LIBRARY_LOG_LEVEL is #IOT_LOG_DEBUG. * * In most cases, the library-specific buffer logging function @ref * logging_function_printbuffer should be called instead of this function. * - * @param[in] pLibraryName The library name to print with the log. See @ref _LIBRARY_LOG_NAME. + * @param[in] pLibraryName The library name to print with the log. See @ref LIBRARY_LOG_NAME. * @param[in] pHeader A message to print before printing the buffer. * @param[in] pBuffer The buffer to print. * @param[in] bufferSize The number of bytes in `pBuffer` to print. diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 368272bba6..951fd2078d 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -60,16 +60,16 @@ /* Configure logs for MQTT functions. */ #ifdef IOT_LOG_LEVEL_MQTT - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_MQTT #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "MQTT" ) +#define LIBRARY_LOG_NAME ( "MQTT" ) #include "iot_logging_setup.h" /* @@ -213,8 +213,8 @@ * this is defined to nothing. When running code coverage testing, this is defined * to an assembly NOP. */ -#ifndef _EMPTY_ELSE_MARKER - #define _EMPTY_ELSE_MARKER +#ifndef EMPTY_ELSE_MARKER + #define EMPTY_ELSE_MARKER #endif /* @@ -225,11 +225,11 @@ * * Used to validate parameters if when connecting to an AWS IoT MQTT server. */ -#define _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ -#define _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ +#define AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ /* * MQTT control packet type and flags. Always the first byte of an MQTT @@ -238,24 +238,24 @@ * For details, see * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757 */ -#define _MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ -#define _MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ -#define _MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ -#define _MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ -#define _MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ -#define _MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ -#define _MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ -#define _MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */ +#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */ +#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */ +#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */ +#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */ +#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */ +#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */ /** * @brief A value that represents an invalid remaining length. * * This value is greater than what is allowed by the MQTT specification. */ -#define _MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) +#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) /*---------------------- MQTT internal data structures ----------------------*/ @@ -482,7 +482,7 @@ uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, * @param[in] pNetworkInterface Function pointers used to interact with the * network. * - * @return The remaining length; #_MQTT_REMAINING_LENGTH_INVALID on error. + * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. */ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index e15c7e2501..4c536ae9e8 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -50,47 +50,47 @@ /** * @brief Jump to the cleanup area. */ -#define TASKPOOL_GOTO_CLEANUP() _IOT_GOTO_CLEANUP() +#define TASKPOOL_GOTO_CLEANUP() IOT_GOTO_CLEANUP() /** * @brief Declare the storage for the error status variable. */ -#define TASKPOOL_FUNCTION_ENTRY( result ) _IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) +#define TASKPOOL_FUNCTION_ENTRY( result ) IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) /** * @brief Check error and leave in case of failure. */ -#define TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( TASKPOOL_FAILED( status = ( expr ) ) ) { _IOT_GOTO_CLEANUP(); } } +#define TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( TASKPOOL_FAILED( status = ( expr ) ) ) { IOT_GOTO_CLEANUP(); } } /** * @brief Exit if an argument is NULL. */ -#define TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) +#define TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ptr ) IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ptr != NULL ) ) /** * @brief Exit if an argument is NULL. */ -#define TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) _IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) +#define TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( expr ) IOT_VALIDATE_PARAMETER( IOT_TASKPOOL, ( ( expr ) == false ) ) /** * @brief Set error and leave. */ -#define TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) _IOT_SET_AND_GOTO_CLEANUP( expr ) +#define TASKPOOL_SET_AND_GOTO_CLEANUP( expr ) IOT_SET_AND_GOTO_CLEANUP( expr ) /** * @brief Initialize error and declare start of cleanup area. */ -#define TASKPOOL_FUNCTION_CLEANUP() _IOT_FUNCTION_CLEANUP_BEGIN() +#define TASKPOOL_FUNCTION_CLEANUP() IOT_FUNCTION_CLEANUP_BEGIN() /** * @brief Initialize error and declare end of cleanup area. */ -#define TASKPOOL_FUNCTION_CLEANUP_END() _IOT_FUNCTION_CLEANUP_END() +#define TASKPOOL_FUNCTION_CLEANUP_END() IOT_FUNCTION_CLEANUP_END() /** * @brief Create an empty cleanup area. */ -#define TASKPOOL_NO_FUNCTION_CLEANUP() _IOT_FUNCTION_EXIT_NO_CLEANUP() +#define TASKPOOL_NO_FUNCTION_CLEANUP() IOT_FUNCTION_EXIT_NO_CLEANUP() /** * @brief Does not create a cleanup area. @@ -117,16 +117,16 @@ /* Configure logs for TASKPOOL functions. */ #ifdef IOT_LOG_LEVEL_TASKPOOL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_TASKPOOL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_TASKPOOL #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "TASKPOOL" ) +#define LIBRARY_LOG_NAME ( "TASKPOOL" ) #include "iot_logging_setup.h" /** diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index 909e78d38e..3133b011ea 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -44,19 +44,19 @@ /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif -#define _LIBRARY_LOG_NAME ( "INIT" ) +#define LIBRARY_LOG_NAME ( "INIT" ) #include "iot_logging_setup.h" /*-----------------------------------------------------------*/ bool IotSdk_Init( void ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; @@ -67,7 +67,7 @@ bool IotSdk_Init( void ) if( staticMemoryInitialized == false ) { IotLogError( "Failed to initialize static memory." ); - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } #endif @@ -77,10 +77,10 @@ bool IotSdk_Init( void ) if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) { IotLogError( "Failed to create system task pool." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status == false ) { @@ -96,7 +96,7 @@ bool IotSdk_Init( void ) IotLogInfo( "SDK successfully initialized." ); } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/common/iot_logging.c b/lib/source/common/iot_logging.c index df27ba9c91..8ba2dff074 100644 --- a/lib/source/common/iot_logging.c +++ b/lib/source/common/iot_logging.c @@ -123,19 +123,19 @@ * * @see @ref platform_clock_function_gettimestring */ -#define _MAX_TIMESTRING_LENGTH ( 64 ) +#define MAX_TIMESTRING_LENGTH ( 64 ) /** * @brief The longest string in #_pLogLevelStrings (below), plus 3 to accommodate * `[]` and a null-terminator. */ -#define _MAX_LOG_LEVEL_LENGTH ( 8 ) +#define MAX_LOG_LEVEL_LENGTH ( 8 ) /** * @brief How many bytes @ref logging_function_genericprintbuffer should output on * each line. */ -#define _BYTES_PER_LINE ( 16 ) +#define BYTES_PER_LINE ( 16 ) /*-----------------------------------------------------------*/ @@ -207,7 +207,7 @@ void IotLog_Generic( int libraryLogSetting, if( ( pLogConfig == NULL ) || ( pLogConfig->hideLogLevel == false ) ) { /* Add length of log level if requested. */ - bufferSize += _MAX_LOG_LEVEL_LENGTH; + bufferSize += MAX_LOG_LEVEL_LENGTH; } /* Estimate the amount of buffer needed for this log message. */ @@ -220,7 +220,7 @@ void IotLog_Generic( int libraryLogSetting, if( ( pLogConfig == NULL ) || ( pLogConfig->hideTimestring == false ) ) { /* Add length of timestring if requested. */ - bufferSize += _MAX_TIMESTRING_LENGTH; + bufferSize += MAX_TIMESTRING_LENGTH; } /* Add 64 as an initial (arbitrary) guess for the length of the message. */ @@ -401,8 +401,8 @@ void IotLog_GenericPrintBuffer( const char * const pLibraryName, /* Allocate memory to hold each line of the log message. Since each byte * of pBuffer is printed in 4 characters (2 digits, a space, and a null- - * terminator), the size of each line is 4 * _BYTES_PER_LINE. */ - char * pMessageBuffer = IotLogging_Malloc( 4 * _BYTES_PER_LINE ); + * terminator), the size of each line is 4 * BYTES_PER_LINE. */ + char * pMessageBuffer = IotLogging_Malloc( 4 * BYTES_PER_LINE ); /* Exit if no memory is available. */ if( pMessageBuffer == NULL ) @@ -423,9 +423,9 @@ void IotLog_GenericPrintBuffer( const char * const pLibraryName, /* Print each byte in pBuffer. */ for( i = 0; i < bufferSize; i++ ) { - /* Print a line if _BYTES_PER_LINE is reached. But don't print a line + /* Print a line if BYTES_PER_LINE is reached. But don't print a line * at the beginning (when i=0). */ - if( ( i % _BYTES_PER_LINE == 0 ) && ( i != 0 ) ) + if( ( i % BYTES_PER_LINE == 0 ) && ( i != 0 ) ) { IotLogging_Puts( pMessageBuffer ); diff --git a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c index cc73cfeb9d..b3f75e0bf9 100644 --- a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c +++ b/lib/source/common/static_memory/aws_iot_static_memory_shadow.c @@ -69,10 +69,10 @@ * @brief The size of a static memory Shadow subscription. * * Since the pThingName member of #_shadowSubscription_t is variable-length, - * the constant #_MAX_THING_NAME_LENGTH is used for the length of + * the constant #MAX_THING_NAME_LENGTH is used for the length of * #_shadowSubscription_t.pThingName. */ -#define _SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + _MAX_THING_NAME_LENGTH ) +#define _SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + MAX_THING_NAME_LENGTH ) /*-----------------------------------------------------------*/ diff --git a/lib/source/common/static_memory/iot_static_memory_mqtt.c b/lib/source/common/static_memory/iot_static_memory_mqtt.c index de33af7a9f..7c5d3ce092 100644 --- a/lib/source/common/static_memory/iot_static_memory_mqtt.c +++ b/lib/source/common/static_memory/iot_static_memory_mqtt.c @@ -75,10 +75,10 @@ * @brief The size of a static memory MQTT subscription. * * Since the pTopic member of #_mqttSubscription_t is variable-length, the constant - * #_AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of + * #AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of * #_mqttSubscription_t.pTopicFilter. */ -#define _MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) +#define _MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) /*-----------------------------------------------------------*/ diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 49c3abbd88..73a3e1e2f1 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -28,9 +28,9 @@ /* Clock include. */ #include "platform/iot_clock.h" -#define _WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) +#define WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) -#if _WAIT_METRICS_JOB_MAX_SECONDS < AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS +#if WAIT_METRICS_JOB_MAX_SECONDS < AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS #error "_WAIT_METRICS_JOB_MAX_SECONDS must be greater than AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS." #endif @@ -93,7 +93,7 @@ AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, uint32_t metrics ) { - if( metricsGroup >= _DEFENDER_METRICS_GROUP_COUNT ) + if( metricsGroup >= DEFENDER_METRICS_GROUP_COUNT ) { IotLogError( "Input metrics group is invalid. Please use AwsIotDefenderMetricsGroup_t data type." ); @@ -238,7 +238,7 @@ void AwsIotDefender_Stop( void ) if( taskPoolError != IOT_TASKPOOL_SUCCESS ) { IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); - IotClock_SleepMs( _WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); + IotClock_SleepMs( WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); } /* Destroy metrics' mutex. */ diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index 2710f61952..a5e9182e52 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -29,22 +29,22 @@ #include "platform/iot_clock.h" -#define _HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) -#define _REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) -#define _VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) -#define _VERSION_1_0 "1.0" /* Used by defender service to indicate the schema change of report, e.g. adding new field. */ -#define _METRICS_TAG AwsIotDefenderInternal_SelectTag( "metrics", "met" ) +#define HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) +#define REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) +#define VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) +#define VERSION_1_0 "1.0" /* Used by defender service to indicate the schema change of report, e.g. adding new field. */ +#define METRICS_TAG AwsIotDefenderInternal_SelectTag( "metrics", "met" ) /* 15 for IP + 1 for ":" + 5 for port + 1 terminator * For example: "192.168.0.1:8000\0" */ -#define _REMOTE_ADDR_LENGTH 22 +#define REMOTE_ADDR_LENGTH 22 -#define _TCP_CONN_TAG AwsIotDefenderInternal_SelectTag( "tcp_connections", "tc" ) -#define _EST_CONN_TAG AwsIotDefenderInternal_SelectTag( "established_connections", "ec" ) -#define _TOTAL_TAG AwsIotDefenderInternal_SelectTag( "total", "t" ) -#define _CONN_TAG AwsIotDefenderInternal_SelectTag( "connections", "cs" ) -#define _REMOTE_ADDR_TAG AwsIotDefenderInternal_SelectTag( "remote_addr", "rad" ) +#define TCP_CONN_TAG AwsIotDefenderInternal_SelectTag( "tcp_connections", "tc" ) +#define EST_CONN_TAG AwsIotDefenderInternal_SelectTag( "established_connections", "ec" ) +#define TOTAL_TAG AwsIotDefenderInternal_SelectTag( "total", "t" ) +#define CONN_TAG AwsIotDefenderInternal_SelectTag( "connections", "cs" ) +#define REMOTE_ADDR_TAG AwsIotDefenderInternal_SelectTag( "remote_addr", "rad" ) /** * Structure to hold a metrics report. @@ -79,13 +79,13 @@ static _metricsReport_t _report = static _metrics_t _metrics = { 0 }; /* Define a "snapshot" global array of metrics flag. */ -static uint32_t _metricsFlagSnapshot[ _DEFENDER_METRICS_GROUP_COUNT ]; +static uint32_t _metricsFlagSnapshot[ DEFENDER_METRICS_GROUP_COUNT ]; /* Report id integer. */ uint64_t _AwsIotDefenderReportId = 0; /* Static storage holding the string of remote address: "ip:port". */ -static char _remoteAddr[ _REMOTE_ADDR_LENGTH ] = ""; +static char _remoteAddr[ REMOTE_ADDR_LENGTH ] = ""; /*---------------------- Helper Functions -------------------------*/ @@ -135,7 +135,7 @@ size_t AwsIotDefenderInternal_GetReportBufferSize( void ) { /* Encoder might over-calculate the needed size. Therefor encoded size might be smaller than buffer size: _report.size. */ return _report.pDataBuffer == NULL ? 0 - : _AwsIotDefenderEncoder.getEncodedSize( &_report.object, _report.pDataBuffer ); + : _defenderEncoder.getEncodedSize( &_report.object, _report.pDataBuffer ); } /*-----------------------------------------------------------*/ @@ -166,10 +166,10 @@ bool AwsIotDefenderInternal_CreateReport( void ) _serialize(); /* Get the calculated required size. */ - dataSize = _AwsIotDefenderEncoder.getExtraBufferSizeNeeded( pEncoderObject ); + dataSize = _defenderEncoder.getExtraBufferSizeNeeded( pEncoderObject ); /* Clean the encoder object handle. */ - _AwsIotDefenderEncoder.destroy( pEncoderObject ); + _defenderEncoder.destroy( pEncoderObject ); /* Allocate memory once. */ pReportBuffer = AwsIotDefender_MallocReport( dataSize * sizeof( uint8_t ) ); @@ -199,7 +199,7 @@ bool AwsIotDefenderInternal_CreateReport( void ) void AwsIotDefenderInternal_DeleteReport( void ) { /* Destroy the encoder object. */ - _AwsIotDefenderEncoder.destroy( &( _report.object ) ); + _defenderEncoder.destroy( &( _report.object ) ); /* Free the memory of data buffer. */ AwsIotDefender_FreeReport( _report.pDataBuffer ); @@ -243,50 +243,50 @@ static void _serialize( void ) uint8_t metricsGroupCount = 0; uint32_t i = 0; - serializerError = _AwsIotDefenderEncoder.init( pEncoderObject, _report.pDataBuffer, _report.size ); + serializerError = _defenderEncoder.init( pEncoderObject, _report.pDataBuffer, _report.size ); assertNoError( serializerError ); /* Create the outermost map with 2 keys: "header", "metrics". */ - serializerError = _AwsIotDefenderEncoder.openContainer( pEncoderObject, &reportMap, 2 ); + serializerError = _defenderEncoder.openContainer( pEncoderObject, &reportMap, 2 ); assertNoError( serializerError ); /* Create the "header" map with 2 keys: "report_id", "version". */ - serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &reportMap, - _HEADER_TAG, - &headerMap, - 2 ); + serializerError = _defenderEncoder.openContainerWithKey( &reportMap, + HEADER_TAG, + &headerMap, + 2 ); assertNoError( serializerError ); /* Append key-value pair of "report_Id" which uses clock time. */ - serializerError = _AwsIotDefenderEncoder.appendKeyValue( &headerMap, - _REPORTID_TAG, - IotSerializer_ScalarSignedInt( _AwsIotDefenderReportId ) ); + serializerError = _defenderEncoder.appendKeyValue( &headerMap, + REPORTID_TAG, + IotSerializer_ScalarSignedInt( _AwsIotDefenderReportId ) ); assertNoError( serializerError ); /* Append key-value pair of "version". */ - serializerError = _AwsIotDefenderEncoder.appendKeyValue( &headerMap, - _VERSION_TAG, - IotSerializer_ScalarTextString( _VERSION_1_0 ) ); + serializerError = _defenderEncoder.appendKeyValue( &headerMap, + VERSION_TAG, + IotSerializer_ScalarTextString( VERSION_1_0 ) ); assertNoError( serializerError ); /* Close the "header" map. */ - serializerError = _AwsIotDefenderEncoder.closeContainer( &reportMap, &headerMap ); + serializerError = _defenderEncoder.closeContainer( &reportMap, &headerMap ); assertNoError( serializerError ); /* Count how many metrics groups user specified. */ - for( i = 0; i < _DEFENDER_METRICS_GROUP_COUNT; i++ ) + for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) { metricsGroupCount += _metricsFlagSnapshot[ i ] > 0; } /* Create the "metrics" map with number of keys as the number of metrics groups. */ - serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &reportMap, - _METRICS_TAG, - &metricsMap, - metricsGroupCount ); + serializerError = _defenderEncoder.openContainerWithKey( &reportMap, + METRICS_TAG, + &metricsMap, + metricsGroupCount ); assertNoError( serializerError ); - for( i = 0; i < _DEFENDER_METRICS_GROUP_COUNT; i++ ) + for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) { /* Skip if this metrics group has 0 metrics flag. */ if( _metricsFlagSnapshot[ i ] ) @@ -305,11 +305,11 @@ static void _serialize( void ) } /* Close the "metrics" map. */ - serializerError = _AwsIotDefenderEncoder.closeContainer( &reportMap, &metricsMap ); + serializerError = _defenderEncoder.closeContainer( &reportMap, &metricsMap ); assertNoError( serializerError ); /* Close the "report" map. */ - serializerError = _AwsIotDefenderEncoder.closeContainer( pEncoderObject, &reportMap ); + serializerError = _defenderEncoder.closeContainer( pEncoderObject, &reportMap ); assertNoError( serializerError ); } @@ -434,20 +434,20 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj uint32_t i = 0; /* Create the "tcp_connections" map with 1 key "established_connections" */ - serializerError = _AwsIotDefenderEncoder.openContainerWithKey( pMetricsObject, - _TCP_CONN_TAG, - &tcpConnectionMap, - 1 ); + serializerError = _defenderEncoder.openContainerWithKey( pMetricsObject, + TCP_CONN_TAG, + &tcpConnectionMap, + 1 ); assertNoError( serializerError ); /* if user specify any metrics under "established_connections" */ if( hasEstablishedConnections ) { /* Create the "established_connections" map with "total" and/or "connections". */ - serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &tcpConnectionMap, - _EST_CONN_TAG, - &establishedMap, - hasConnections + hasTotal ); + serializerError = _defenderEncoder.openContainerWithKey( &tcpConnectionMap, + EST_CONN_TAG, + &establishedMap, + hasConnections + hasTotal ); assertNoError( serializerError ); /* if user specify any metrics under "connections" and there are at least one connection */ @@ -456,10 +456,10 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj AwsIotDefender_Assert( _metrics.tcpConns.pArray != NULL ); /* create array "connections" under "established_connections" */ - serializerError = _AwsIotDefenderEncoder.openContainerWithKey( &establishedMap, - _CONN_TAG, - &connectionsArray, - _metrics.tcpConns.count ); + serializerError = _defenderEncoder.openContainerWithKey( &establishedMap, + CONN_TAG, + &connectionsArray, + _metrics.tcpConns.count ); assertNoError( serializerError ); for( i = 0; i < _metrics.tcpConns.count; i++ ) @@ -467,9 +467,9 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj IotSerializerEncoderObject_t connectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; /* open a map under "connections" */ - serializerError = _AwsIotDefenderEncoder.openContainer( &connectionsArray, - &connectionMap, - hasRemoteAddr ); + serializerError = _defenderEncoder.openContainer( &connectionsArray, + &connectionMap, + hasRemoteAddr ); assertNoError( serializerError ); /* add remote address */ @@ -477,31 +477,31 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj { sprintf( _remoteAddr, "%s:%d", _metrics.tcpConns.pArray[ i ].pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); - serializerError = _AwsIotDefenderEncoder.appendKeyValue( &connectionMap, _REMOTE_ADDR_TAG, - IotSerializer_ScalarTextString( _remoteAddr ) ); + serializerError = _defenderEncoder.appendKeyValue( &connectionMap, REMOTE_ADDR_TAG, + IotSerializer_ScalarTextString( _remoteAddr ) ); assertNoError( serializerError ); } - serializerError = _AwsIotDefenderEncoder.closeContainer( &connectionsArray, &connectionMap ); + serializerError = _defenderEncoder.closeContainer( &connectionsArray, &connectionMap ); assertNoError( serializerError ); } - serializerError = _AwsIotDefenderEncoder.closeContainer( &establishedMap, &connectionsArray ); + serializerError = _defenderEncoder.closeContainer( &establishedMap, &connectionsArray ); assertNoError( serializerError ); } if( hasTotal ) { - serializerError = _AwsIotDefenderEncoder.appendKeyValue( &establishedMap, - _TOTAL_TAG, - IotSerializer_ScalarSignedInt( _metrics.tcpConns.count ) ); + serializerError = _defenderEncoder.appendKeyValue( &establishedMap, + TOTAL_TAG, + IotSerializer_ScalarSignedInt( _metrics.tcpConns.count ) ); assertNoError( serializerError ); } - serializerError = _AwsIotDefenderEncoder.closeContainer( &tcpConnectionMap, &establishedMap ); + serializerError = _defenderEncoder.closeContainer( &tcpConnectionMap, &establishedMap ); assertNoError( serializerError ); } - serializerError = _AwsIotDefenderEncoder.closeContainer( pMetricsObject, &tcpConnectionMap ); + serializerError = _defenderEncoder.closeContainer( pMetricsObject, &tcpConnectionMap ); assertNoError( serializerError ); } diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index 5da9473cc0..f473153f6e 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -25,13 +25,13 @@ #include "private/aws_iot_defender_internal.h" /* Define topics segments used by defender. */ -#define _TOPIC_PREFIX "$aws/things/" +#define TOPIC_PREFIX "$aws/things/" -#define _TOPIC_SUFFIX_PUBLISH "/defender/metrics/" _DEFENDER_FORMAT +#define TOPIC_SUFFIX_PUBLISH "/defender/metrics/" DEFENDER_FORMAT -#define _TOPIC_SUFFIX_ACCEPTED _TOPIC_SUFFIX_PUBLISH "/accepted" +#define TOPIC_SUFFIX_ACCEPTED TOPIC_SUFFIX_PUBLISH "/accepted" -#define _TOPIC_SUFFIX_REJECTED _TOPIC_SUFFIX_PUBLISH "/rejected" +#define TOPIC_SUFFIX_REJECTED TOPIC_SUFFIX_PUBLISH "/rejected" extern AwsIotDefenderStartInfo_t _startInfo; @@ -54,9 +54,9 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) uint16_t thingNameLength = _startInfo.mqttConnectionInfo.clientIdentifierLength; /* Calculate topics lengths. Plus one for string terminator. */ - size_t publishTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_PUBLISH ) + 1; - size_t acceptTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_ACCEPTED ) + 1; - size_t rejectTopicLength = strlen( _TOPIC_PREFIX ) + thingNameLength + strlen( _TOPIC_SUFFIX_REJECTED ) + 1; + size_t publishTopicLength = strlen( TOPIC_PREFIX ) + thingNameLength + strlen( TOPIC_SUFFIX_PUBLISH ) + 1; + size_t acceptTopicLength = strlen( TOPIC_PREFIX ) + thingNameLength + strlen( TOPIC_SUFFIX_ACCEPTED ) + 1; + size_t rejectTopicLength = strlen( TOPIC_PREFIX ) + thingNameLength + strlen( TOPIC_SUFFIX_REJECTED ) + 1; /* Allocate memory for each of them. */ char * pPublishTopic = AwsIotDefender_MallocTopic( publishTopicLength * sizeof( char ) ); @@ -78,17 +78,17 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) _pAcceptTopic = pAcceptTopic; _pRejectTopic = pRejectTopic; - strcpy( _pPublishTopic, _TOPIC_PREFIX ); + strcpy( _pPublishTopic, TOPIC_PREFIX ); strncat( _pPublishTopic, pThingName, thingNameLength ); - strcat( _pPublishTopic, _TOPIC_SUFFIX_PUBLISH ); + strcat( _pPublishTopic, TOPIC_SUFFIX_PUBLISH ); - strcpy( _pAcceptTopic, _TOPIC_PREFIX ); + strcpy( _pAcceptTopic, TOPIC_PREFIX ); strncat( _pAcceptTopic, pThingName, thingNameLength ); - strcat( _pAcceptTopic, _TOPIC_SUFFIX_ACCEPTED ); + strcat( _pAcceptTopic, TOPIC_SUFFIX_ACCEPTED ); - strcpy( _pRejectTopic, _TOPIC_PREFIX ); + strcpy( _pRejectTopic, TOPIC_PREFIX ); strncat( _pRejectTopic, pThingName, thingNameLength ); - strcat( _pRejectTopic, _TOPIC_SUFFIX_REJECTED ); + strcat( _pRejectTopic, TOPIC_SUFFIX_REJECTED ); } return returnedError; diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index a934e3d348..625a086101 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -180,7 +180,7 @@ static void _mqttSubscription_tryDestroy( void * pData ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -215,7 +215,7 @@ static void _mqttOperation_tryDestroy( void * pData ) else { /* The executing job will process the PUBLISH, so nothing is done here. */ - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -227,7 +227,7 @@ static void _mqttOperation_tryDestroy( void * pData ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } } @@ -263,12 +263,12 @@ static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -299,7 +299,7 @@ static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Keep-alive references its MQTT connection, so increment reference. */ @@ -315,7 +315,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); _mqttConnection_t * pMqttConnection = NULL; bool referencesMutexCreated = false, subscriptionMutexCreated = false; @@ -326,7 +326,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotLogError( "Failed to allocate memory for new connection." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { @@ -348,11 +348,11 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotLogError( "Failed to create references mutex for new connection." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Create the subscription mutex for a new connection. */ @@ -362,11 +362,11 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, { IotLogError( "Failed to create subscription mutex for new connection." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Create the new connection's subscription and operation lists. */ @@ -378,26 +378,26 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, * Adjust the user-provided keep-alive interval based on these requirements. */ if( awsIotMqttMode == true ) { - if( keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + if( keepAliveSeconds < AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; + keepAliveSeconds = AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; } - else if( keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + else if( keepAliveSeconds > AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; } else if( keepAliveSeconds == 0 ) { - keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; + keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check if keep-alive is active for this connection. */ @@ -407,20 +407,20 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, keepAliveSeconds, pMqttConnection ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Clean up mutexes and connection if this function failed. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status == false ) { @@ -430,7 +430,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( referencesMutexCreated == true ) @@ -439,7 +439,7 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pMqttConnection != NULL ) @@ -449,12 +449,12 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return pMqttConnection; @@ -483,7 +483,7 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* A connection to be destroyed should have no keep-alive and at most 1 @@ -520,7 +520,7 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Destroy mutexes. */ @@ -543,7 +543,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * pOperationReference ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pSubscriptionOperation = NULL; /* Subscription serializer function. */ @@ -563,11 +563,11 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, pSubscriptionList, subscriptionCount ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that a reference pointer is provided for a waitable operation. */ @@ -578,16 +578,16 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotLogError( "Reference must be provided for a waitable %s.", IotMqtt_OperationType( operation ) ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Choose a subscription serialize function. */ @@ -604,12 +604,12 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ } @@ -626,12 +626,12 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ } @@ -645,7 +645,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Create a subscription operation. */ @@ -656,7 +656,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } /* Check the subscription operation data and set the operation type. */ @@ -673,7 +673,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } /* Check the serialized MQTT packet. */ @@ -690,7 +690,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } } @@ -724,11 +724,11 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; } - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } /* Clean up if this function failed. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status != IOT_MQTT_SUCCESS ) { @@ -746,7 +746,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotMqtt_OperationType( operation ) ); } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -808,7 +808,7 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -822,7 +822,7 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -842,7 +842,7 @@ IotMqttError_t IotMqtt_Init( void ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -882,7 +882,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, uint32_t timeoutMs, IotMqttConnection_t * pMqttConnection ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); bool networkCreated = false, ownNetworkConnection = false; IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; @@ -898,21 +898,21 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Network info must not be NULL. */ if( pNetworkInfo == NULL ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Validate network interface and connect info. */ if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* If will info is provided, check that it is valid. */ @@ -921,7 +921,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( _IotMqtt_ValidatePublish( pConnectInfo->awsIotMqttMode, pConnectInfo->pWillInfo ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else if( pConnectInfo->pWillInfo->payloadLength > UINT16_MAX ) { @@ -929,16 +929,16 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, * applies only to will messages, and not normal PUBLISH messages. */ IotLogError( "Will payload cannot be larger than 65535." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* If previous subscriptions are provided, check that they are valid. */ @@ -951,21 +951,21 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, pConnectInfo->pPreviousSubscriptions, pConnectInfo->previousSubscriptionCount ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Create a new MQTT connection if requested. Otherwise, copy the existing @@ -986,7 +986,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); } } else @@ -1004,7 +1004,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( pNewMqttConnection == NULL ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { @@ -1027,11 +1027,11 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, { IotLogError( "Failed to set MQTT network receive callback." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Create a CONNECT operation. */ @@ -1042,11 +1042,11 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Ensure the members set by operation creation and serialization @@ -1072,16 +1072,16 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Choose a CONNECT serializer function. */ @@ -1094,12 +1094,12 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -1110,11 +1110,11 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the serialized MQTT packet. */ @@ -1155,24 +1155,24 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SCHEDULING_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SCHEDULING_ERROR ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status != IOT_MQTT_SUCCESS ) { @@ -1195,7 +1195,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pOperation != NULL ) @@ -1204,7 +1204,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pNewMqttConnection != NULL ) @@ -1213,7 +1213,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -1224,7 +1224,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, *pMqttConnection = pNewMqttConnection; } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -1281,12 +1281,12 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -1296,7 +1296,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( status == IOT_MQTT_SUCCESS ) @@ -1339,17 +1339,17 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Close the underlying network connection. This also cleans up keep-alive. */ @@ -1424,7 +1424,7 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Ensure that a status was set. */ @@ -1480,7 +1480,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Ensure that a status was set. */ @@ -1497,7 +1497,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * pPublishOperation ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pOperation = NULL; uint8_t ** pPacketIdentifierHigh = NULL; @@ -1512,11 +1512,11 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, pPublishInfo ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that no notification is requested for a QoS 0 publish. */ @@ -1526,17 +1526,17 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, { IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else if( ( flags & IOT_MQTT_FLAG_WAITABLE ) != 0 ) { IotLogError( "QoS 0 PUBLISH should not have notification parameters set." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pPublishOperation != NULL ) @@ -1545,12 +1545,12 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that a reference pointer is provided for a waitable operation. */ @@ -1560,16 +1560,16 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, { IotLogError( "Reference must be provided for a waitable PUBLISH." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Create a PUBLISH operation. */ @@ -1580,11 +1580,11 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the PUBLISH operation data and set the operation type. */ @@ -1601,12 +1601,12 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -1617,7 +1617,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Generate a PUBLISH packet from pPublishInfo. */ @@ -1629,11 +1629,11 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, if( status != IOT_MQTT_SUCCESS ) { - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the serialized MQTT packet. */ @@ -1651,12 +1651,12 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Set the reference, if provided. */ @@ -1668,12 +1668,12 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Add the PUBLISH operation to the send queue for network transmission. */ @@ -1695,24 +1695,24 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Clean up the PUBLISH operation if this function fails. Otherwise, set the * appropriate return code based on QoS. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status != IOT_MQTT_SUCCESS ) { @@ -1722,7 +1722,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -1733,14 +1733,14 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotLogInfo( "(MQTT connection %p) MQTT PUBLISH operation queued.", mqttConnection ); } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -1765,7 +1765,7 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Call the asynchronous PUBLISH function. */ @@ -1784,12 +1784,12 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return status; @@ -1810,7 +1810,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the MQTT connection status. */ @@ -1863,7 +1863,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -1880,7 +1880,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Wait is finished; decrement operation reference count. */ @@ -1890,12 +1890,12 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return status; diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index d373201b5b..7280616f1a 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -95,12 +95,12 @@ static bool _incomingPacketValid( uint8_t packetType ) switch( packetType & 0xf0 ) { /* Valid incoming packet types. */ - case _MQTT_PACKET_TYPE_CONNACK: - case _MQTT_PACKET_TYPE_PUBLISH: - case _MQTT_PACKET_TYPE_PUBACK: - case _MQTT_PACKET_TYPE_SUBACK: - case _MQTT_PACKET_TYPE_UNSUBACK: - case _MQTT_PACKET_TYPE_PINGRESP: + case MQTT_PACKET_TYPE_CONNACK: + case MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_SUBACK: + case MQTT_PACKET_TYPE_UNSUBACK: + case MQTT_PACKET_TYPE_PINGRESP: break; /* Any other packet type is invalid. */ @@ -118,7 +118,7 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, const _mqttConnection_t * pMqttConnection, _mqttPacket_t * pIncomingPacket ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t dataBytesRead = 0; /* Default functions for retrieving packet type and length. */ @@ -141,7 +141,7 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pMqttConnection->pSerializer->getRemainingLength != NULL ) @@ -150,12 +150,12 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -170,24 +170,24 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, pMqttConnection, pIncomingPacket->type ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Read the remaining length. */ pIncomingPacket->remainingLength = getRemainingLength( pNetworkConnection, pMqttConnection->pNetworkInterface ); - if( pIncomingPacket->remainingLength == _MQTT_REMAINING_LENGTH_INVALID ) + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Allocate a buffer for the remaining data and read the data. */ @@ -197,11 +197,11 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( pIncomingPacket->pRemainingData == NULL ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } dataBytesRead = pMqttConnection->pNetworkInterface->receive( pNetworkConnection, @@ -210,20 +210,20 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( dataBytesRead != pIncomingPacket->remainingLength ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Clean up on error. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status != IOT_MQTT_SUCCESS ) { @@ -233,15 +233,15 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -265,7 +265,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne /* Mask out the low bits of packet type to ignore flags. */ switch( ( pIncomingPacket->type & 0xf0 ) ) { - case _MQTT_PACKET_TYPE_CONNACK: + case MQTT_PACKET_TYPE_CONNACK: IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); /* Choose CONNACK deserializer. */ @@ -280,12 +280,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -302,12 +302,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; - case _MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBLISH: IotLogDebug( "(MQTT connection %p) PUBLISH in data stream.", pMqttConnection ); /* Allocate memory to handle the incoming PUBLISH. */ @@ -341,12 +341,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -362,7 +362,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Transfer ownership of the received MQTT packet to the PUBLISH operation. */ @@ -389,7 +389,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Free PUBLISH operation on error. */ @@ -404,7 +404,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Remove operation from pending processing list. */ @@ -416,7 +416,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -426,12 +426,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; - case _MQTT_PACKET_TYPE_PUBACK: + case MQTT_PACKET_TYPE_PUBACK: IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); /* Choose PUBACK deserializer. */ @@ -446,12 +446,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -468,12 +468,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; - case _MQTT_PACKET_TYPE_SUBACK: + case MQTT_PACKET_TYPE_SUBACK: IotLogDebug( "(MQTT connection %p) SUBACK in data stream.", pMqttConnection ); /* Choose SUBACK deserializer. */ @@ -488,12 +488,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -511,12 +511,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; - case _MQTT_PACKET_TYPE_UNSUBACK: + case MQTT_PACKET_TYPE_UNSUBACK: IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); /* Choose UNSUBACK deserializer. */ @@ -531,12 +531,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -553,14 +553,14 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; default: /* The only remaining valid type is PINGRESP. */ - IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == _MQTT_PACKET_TYPE_PINGRESP ); + IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == MQTT_PACKET_TYPE_PINGRESP ); IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); /* Choose PINGRESP deserializer. */ @@ -575,12 +575,12 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -608,7 +608,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; @@ -622,7 +622,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return status; @@ -657,12 +657,12 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 @@ -674,12 +674,12 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -805,12 +805,12 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -849,7 +849,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -882,12 +882,12 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Close the network connection on a bad response. */ @@ -901,7 +901,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 2242dac724..887d865dd2 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -118,7 +118,7 @@ static bool _mqttOperation_match( const IotLink_t * pOperationLink, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return match; @@ -145,12 +145,12 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -191,7 +191,7 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -239,7 +239,7 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", @@ -261,7 +261,7 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -287,12 +287,12 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* The references mutex only needs to be unlocked on the first retry, since @@ -303,7 +303,7 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return( status == IOT_MQTT_SUCCESS ); @@ -316,7 +316,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, const IotMqttCallbackInfo_t * pCallbackInfo, _mqttOperation_t ** pNewOperation ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); bool decrementOnError = false; _mqttOperation_t * pOperation = NULL; bool waitable = ( ( flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); @@ -332,12 +332,12 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotLogDebug( "(MQTT connection %p) Creating new operation record.", @@ -351,7 +351,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, " for a closed connection", pMqttConnection ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); } else { @@ -368,7 +368,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, "operation record.", pMqttConnection ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { @@ -393,7 +393,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, "waitable operation.", pMqttConnection ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { @@ -412,7 +412,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -426,7 +426,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, *pNewOperation = pOperation; /* Clean up operation and decrement reference count if this function failed. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status != IOT_MQTT_SUCCESS ) { @@ -436,7 +436,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pOperation != NULL ) @@ -445,15 +445,15 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -486,12 +486,12 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Decrement job reference count. */ @@ -519,14 +519,14 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return destroyOperation; @@ -586,12 +586,12 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ @@ -622,7 +622,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotLogDebug( "(MQTT connection %p, %s operation %p) Operation record destroyed.", @@ -751,7 +751,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Close the connection on failures. */ @@ -762,7 +762,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -794,7 +794,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pOperation->pMqttConnection->referencesMutex ) ); @@ -812,7 +812,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Free the incoming PUBLISH operation. */ @@ -854,12 +854,12 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Send an operation that is waiting for a response. */ @@ -900,18 +900,18 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check if this operation requires further processing. */ @@ -941,7 +941,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* If the operation should not be destroyed, transfer it from the @@ -965,13 +965,13 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Destroy the operation or notify of completion if necessary. */ @@ -992,12 +992,12 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } } @@ -1038,7 +1038,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -1083,7 +1083,7 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return status; @@ -1174,7 +1174,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -1226,7 +1226,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Schedule callback invocation for non-waitable operation. */ @@ -1259,7 +1259,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotListDouble_InsertHead( &( pMqttConnection->pendingProcessing ), @@ -1277,12 +1277,12 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Operations that weren't scheduled may be destroyed. */ @@ -1295,7 +1295,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Post to a waitable operation's semaphore. */ @@ -1311,7 +1311,7 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 076a7361c4..1df5920130 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -47,15 +47,15 @@ /* * Macros for reading the high and low byte of a 2-byte unsigned int. */ -#define _UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ -#define _UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ /** * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. * * @param[in] ptr A uint8_t* that points to the high byte. */ -#define _UINT16_DECODE( ptr ) \ +#define UINT16_DECODE( ptr ) \ ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) @@ -65,7 +65,7 @@ * @param[in] x The unsigned int to set. * @param[in] position Which bit to set. */ -#define _UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01 << position ) ) ) +#define UINT8_SET_BIT( x, position ) ( x = ( uint8_t ) ( x | ( 0x01 << position ) ) ) /** * @brief Macro for checking if a bit is set in a 1-byte unsigned int. @@ -73,39 +73,39 @@ * @param[in] x The unsigned int to check. * @param[in] position Which bit to check. */ -#define _UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01 << position ) ) == ( 0x01 << position ) ) +#define UINT8_CHECK_BIT( x, position ) ( ( x & ( 0x01 << position ) ) == ( 0x01 << position ) ) /* * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT * packet. */ -#define _MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ -#define _MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ -#define _MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ -#define _MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ -#define _MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ -#define _MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ -#define _MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ /* * Positions of each flag in the first byte of an MQTT PUBLISH packet's * fixed header. */ -#define _MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ -#define _MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ -#define _MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ -#define _MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ +#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ +#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ +#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ /** * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. */ -#define _MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) /** * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT * packet is this value. */ -#define _MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) +#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL ) /** * @brief The maximum possible size of a CONNECT packet. @@ -113,40 +113,40 @@ * All strings in a CONNECT packet are constrained to 2-byte lengths, giving a * maximum length smaller than the max "Remaining Length" constant above. */ -#define _MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) +#define MQTT_PACKET_CONNECT_MAX_SIZE ( 327700UL ) /* * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec. */ -#define _MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ -#define _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01 ) /**< @brief The "Session Present" bit is always the lowest bit. */ +#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01 ) /**< @brief The "Session Present" bit is always the lowest bit. */ /* * Constants relating to PUBLISH and PUBACK packets, defined by MQTT * 3.1.1 spec. */ -#define _MQTT_PACKET_PUBLISH_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid PUBLISH packet. */ -#define _MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ -#define _MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_PUBLISH_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid PUBLISH packet. */ +#define MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ +#define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ /* * Constants relating to SUBACK and UNSUBACK packets, defined by MQTT * 3.1.1 spec. */ -#define _MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid SUBACK packet. */ -#define _MQTT_PACKET_UNSUBACK_SIZE ( 4 ) /**< @brief An UNSUBACK packet is always 4 bytes in size. */ -#define _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ +#define MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid SUBACK packet. */ +#define MQTT_PACKET_UNSUBACK_SIZE ( 4 ) /**< @brief An UNSUBACK packet is always 4 bytes in size. */ +#define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ /* * Constants relating to PINGREQ and PINGRESP packets, defined by MQTT 3.1.1 spec. */ -#define _MQTT_PACKET_PINGREQ_SIZE ( 2 ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ -#define _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0 ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ +#define MQTT_PACKET_PINGREQ_SIZE ( 2 ) /**< @brief A PINGREQ packet is always 2 bytes in size. */ +#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0 ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */ /* * Constants relating to DISCONNECT packets, defined by MQTT 3.1.1 spec. */ -#define _MQTT_PACKET_DISCONNECT_SIZE ( 2 ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ +#define MQTT_PACKET_DISCONNECT_SIZE ( 2 ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ /* * Username for metrics with AWS IoT. @@ -283,7 +283,7 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /*-----------------------------------------------------------*/ -#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE /** * @brief If logging is enabled, define a log configuration that only prints the log @@ -318,7 +318,7 @@ static size_t _remainingLengthEncodedSize( size_t length ) size_t encodedSize = 0; /* length should have already been checked before calling this function. */ - IotMqtt_Assert( length <= _MQTT_MAX_REMAINING_LENGTH ); + IotMqtt_Assert( length <= MQTT_MAX_REMAINING_LENGTH ); /* Determine how many bytes are needed to encode length. * The values below are taken from the MQTT 3.1.1 spec. */ @@ -363,11 +363,11 @@ static uint8_t * _encodeRemainingLength( uint8_t * pDestination, /* Set the high bit of this byte, indicating that there's more data. */ if( length > 0 ) { - _UINT8_SET_BIT( lengthByte, 7 ); + UINT8_SET_BIT( lengthByte, 7 ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Output a single encoded byte. */ @@ -385,11 +385,11 @@ static uint8_t * _encodeString( uint8_t * pDestination, uint16_t sourceLength ) { /* The first byte of a UTF-8 string is the high byte of the string length. */ - *pDestination = _UINT16_HIGH_BYTE( sourceLength ); + *pDestination = UINT16_HIGH_BYTE( sourceLength ); pDestination++; /* The second byte of a UTF-8 string is the low byte of the string length. */ - *pDestination = _UINT16_LOW_BYTE( sourceLength ); + *pDestination = UINT16_LOW_BYTE( sourceLength ); pDestination++; /* Copy the string into pDestination. */ @@ -420,7 +420,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Add the lengths of the will message and topic name if provided. */ @@ -431,7 +431,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Depending on the status of metrics, add the length of the metrics username @@ -452,7 +452,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pConnectInfo->pPassword != NULL ) @@ -461,7 +461,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -474,7 +474,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, connectPacketSize += 1 + _remainingLengthEncodedSize( connectPacketSize ); /* Check that the CONNECT packet is within the bounds of the MQTT spec. */ - if( connectPacketSize > _MQTT_PACKET_CONNECT_MAX_SIZE ) + if( connectPacketSize > MQTT_PACKET_CONNECT_MAX_SIZE ) { status = false; } @@ -507,13 +507,13 @@ static bool _publishPacketSize( const IotMqttPublishInfo_t * pPublishInfo, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Calculate the maximum allowed size of the payload for the given parameters. * This calculation excludes the "Remaining length" encoding, whose size is not * yet known. */ - payloadLimit = _MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1; + payloadLimit = MQTT_MAX_REMAINING_LENGTH - publishPacketSize - 1; /* Ensure that the given payload fits within the calculated limit. */ if( pPublishInfo->payloadLength > payloadLimit ) @@ -581,14 +581,14 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } /* At this point, the "Remaining length" has been calculated. Return error * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise, * set the output parameter.*/ - if( subscriptionPacketSize > _MQTT_MAX_REMAINING_LENGTH ) + if( subscriptionPacketSize > MQTT_MAX_REMAINING_LENGTH ) { status = false; } @@ -634,7 +634,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, { if( multiplier > 2097152 ) /* 128 ^ 3 */ { - remainingLength = _MQTT_REMAINING_LENGTH_INVALID; + remainingLength = MQTT_REMAINING_LENGTH_INVALID; break; } else @@ -649,20 +649,20 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, } else { - remainingLength = _MQTT_REMAINING_LENGTH_INVALID; + remainingLength = MQTT_REMAINING_LENGTH_INVALID; break; } } } while( ( encodedByte & 0x80 ) != 0 ); /* Check that the decoded remaining length conforms to the MQTT specification. */ - if( remainingLength != _MQTT_REMAINING_LENGTH_INVALID ) + if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) { expectedSize = _remainingLengthEncodedSize( remainingLength ); if( bytesDecoded != expectedSize ) { - remainingLength = _MQTT_REMAINING_LENGTH_INVALID; + remainingLength = MQTT_REMAINING_LENGTH_INVALID; } else { @@ -672,7 +672,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return remainingLength; @@ -684,7 +684,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI uint8_t ** pConnectPacket, size_t * pPacketSize ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint8_t connectFlags = 0; size_t remainingLength = 0, connectPacketSize = 0; uint8_t * pBuffer = NULL; @@ -695,13 +695,13 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI { IotLogError( "Connect packet length exceeds %lu, which is the maximum" " size allowed by MQTT 3.1.1.", - _MQTT_PACKET_CONNECT_MAX_SIZE ); + MQTT_PACKET_CONNECT_MAX_SIZE ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Total size of the connect packet should be larger than the "Remaining length" @@ -716,11 +716,11 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI { IotLogError( "Failed to allocate memory for CONNECT packet." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -728,7 +728,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI *pPacketSize = connectPacketSize; /* The first byte in the CONNECT packet is the control packet type. */ - *pBuffer = _MQTT_PACKET_TYPE_CONNECT; + *pBuffer = MQTT_PACKET_TYPE_CONNECT; pBuffer++; /* The remaining length of the CONNECT packet is encoded starting from the @@ -741,17 +741,17 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI pBuffer = _encodeString( pBuffer, "MQTT", 4 ); /* The MQTT protocol version is the second byte of the variable header. */ - *pBuffer = _MQTT_VERSION_3_1_1; + *pBuffer = MQTT_VERSION_3_1_1; pBuffer++; /* Set the CONNECT flags based on the given parameters. */ if( pConnectInfo->cleanSession == true ) { - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_CLEAN ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Username and password depend on MQTT mode. */ @@ -760,7 +760,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server * never uses a password. */ #if AWS_IOT_MQTT_ENABLE_METRICS == 1 - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_USERNAME ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); #endif } else @@ -768,37 +768,37 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI /* Set the flags for username and password if provided. */ if( pConnectInfo->pUserName != NULL ) { - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_USERNAME ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pConnectInfo->pPassword != NULL ) { - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_PASSWORD ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } /* Set will flag if an LWT is provided. */ if( pConnectInfo->pWillInfo != NULL ) { - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL ); /* Flags only need to be changed for will QoS 1 and 2. */ switch( pConnectInfo->pWillInfo->qos ) { case IOT_MQTT_QOS_1: - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS1 ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 ); break; case IOT_MQTT_QOS_2: - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_QOS2 ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 ); break; default: @@ -807,24 +807,24 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI if( pConnectInfo->pWillInfo->retain == true ) { - _UINT8_SET_BIT( connectFlags, _MQTT_CONNECT_FLAG_WILL_RETAIN ); + UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } *pBuffer = connectFlags; pBuffer++; /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */ - *pBuffer = _UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); - *( pBuffer + 1 ) = _UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); + *pBuffer = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds ); pBuffer += 2; /* Write the client identifier into the CONNECT packet. */ @@ -845,7 +845,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* If metrics are enabled, write the metrics username into the CONNECT packet. @@ -872,7 +872,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pConnectInfo->pPassword != NULL ) @@ -883,7 +883,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -894,19 +894,19 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI /* Print out the serialized CONNECT packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); const uint8_t * pRemainingData = pConnack->pRemainingData; /* If logging is enabled, declare the CONNACK response code strings. The * fourth byte of CONNACK indexes into this array for the corresponding response. */ - #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE static const char * pConnackResponses[ 6 ] = { "Connection accepted.", /* 0 */ @@ -919,34 +919,34 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) #endif /* Check that the control packet type is 0x20. */ - if( pConnack->type != _MQTT_PACKET_TYPE_CONNACK ) + if( pConnack->type != MQTT_PACKET_TYPE_CONNACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", pConnack->type ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* According to MQTT 3.1.1, the second byte of CONNACK must specify a * "Remaining length" of 2. */ - if( pConnack->remainingLength != _MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "CONNACK does not have remaining length of %d.", - _MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + MQTT_PACKET_CONNACK_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the reserved bits in CONNACK. The high 7 bits of the second byte @@ -957,17 +957,17 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) &_logHideAll, "Reserved bits in CONNACK incorrect." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Determine if the "Session Present" bit it set. This is the lowest bit of * the second byte in CONNACK. */ - if( ( pRemainingData[ 0 ] & _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - == _MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -977,11 +977,11 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) * "Session Present" bit is set. */ if( pRemainingData[ 1 ] != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -999,16 +999,16 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) "CONNACK response %hhu is not valid.", pRemainingData[ 1 ] ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Print the appropriate message for the CONNACK response code if logs are * enabled. */ - #if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE IotLog( IOT_LOG_DEBUG, &_logHideAll, "%s", @@ -1018,14 +1018,14 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) /* A nonzero CONNACK response code means the connection was refused. */ if( pRemainingData[ 1 ] > 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1036,7 +1036,7 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI uint16_t * pPacketIdentifier, uint8_t ** pPacketIdentifierHigh ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint8_t publishFlags = 0; uint16_t packetIdentifier = 0; size_t remainingLength = 0, publishPacketSize = 0; @@ -1048,13 +1048,13 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI { IotLogError( "Publish packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", - _MQTT_MAX_REMAINING_LENGTH ); + MQTT_MAX_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Total size of the publish packet should be larger than the "Remaining length" @@ -1069,11 +1069,11 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI { IotLogError( "Failed to allocate memory for PUBLISH packet." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -1081,28 +1081,28 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI *pPacketSize = publishPacketSize; /* The first byte of a PUBLISH packet contains the packet type and flags. */ - publishFlags = _MQTT_PACKET_TYPE_PUBLISH; + publishFlags = MQTT_PACKET_TYPE_PUBLISH; if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { - _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); } else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) { - _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS2 ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pPublishInfo->retain == true ) { - _UINT8_SET_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } *pBuffer = publishFlags; @@ -1132,17 +1132,17 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Place the packet identifier into the PUBLISH packet. */ - *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); pBuffer += 2; } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* The payload is placed after the packet identifier. */ @@ -1153,7 +1153,7 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Ensure that the difference between the end and beginning of the buffer @@ -1163,7 +1163,7 @@ IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishI /* Print out the serialized PUBLISH packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1184,18 +1184,18 @@ void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, newPacketIdentifier = _nextPacketIdentifier(); IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", - _UINT16_DECODE( pPacketIdentifierHigh ), + UINT16_DECODE( pPacketIdentifierHigh ), newPacketIdentifier ); /* Replace the packet identifier. */ - *pPacketIdentifierHigh = _UINT16_HIGH_BYTE( newPacketIdentifier ); - *( pPacketIdentifierHigh + 1 ) = _UINT16_LOW_BYTE( newPacketIdentifier ); + *pPacketIdentifierHigh = UINT16_HIGH_BYTE( newPacketIdentifier ); + *( pPacketIdentifierHigh + 1 ) = UINT16_LOW_BYTE( newPacketIdentifier ); *pNewPacketIdentifier = newPacketIdentifier; } else { /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ - _UINT8_SET_BIT( *pPublishPacket, _MQTT_PUBLISH_FLAG_DUP ); + UINT8_SET_BIT( *pPublishPacket, MQTT_PUBLISH_FLAG_DUP ); IotLogDebug( "PUBLISH DUP flag set." ); } @@ -1205,7 +1205,7 @@ void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); uint8_t publishFlags = 0; const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; @@ -1214,33 +1214,33 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) publishFlags = pPublish->type; /* Parse the Retain bit. */ - pOutput->retain = _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_RETAIN ); + pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); IotLog( IOT_LOG_DEBUG, &_logHideAll, "Retain bit is %d.", pOutput->retain ); /* Check for QoS 2. */ - if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS2 ) == true ) + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) { /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ - if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ) == true ) + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, "Bad QoS: 3." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } pOutput->qos = IOT_MQTT_QOS_2; } /* Check for QoS 1. */ - else if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_QOS1 ) == true ) + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) { pOutput->qos = IOT_MQTT_QOS_1; } @@ -1255,7 +1255,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) "QoS is %d.", pOutput->qos ); /* Parse the DUP bit. */ - if( _UINT8_CHECK_BIT( publishFlags, _MQTT_PUBLISH_FLAG_DUP ) == true ) + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) { IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1279,11 +1279,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "QoS 0 PUBLISH cannot have a remaining length less than 3." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -1297,17 +1297,17 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } /* Extract the topic name starting from the first byte of the variable header. * The topic name string starts at byte 3 in the variable header. */ - pOutput->topicNameLength = _UINT16_DECODE( pVariableHeader ); + pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); /* Sanity checks for topic name length and "Remaining length". */ if( pOutput->qos == IOT_MQTT_QOS_0 ) @@ -1320,11 +1320,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Remaining length cannot be less than variable header length." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -1337,11 +1337,11 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Remaining length cannot be less than variable header length." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -1361,7 +1361,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) if( pOutput->qos > IOT_MQTT_QOS_0 ) { - pPublish->packetIdentifier = _UINT16_DECODE( pPacketIdentifierHigh ); + pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1370,16 +1370,16 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) /* Packet identifier cannot be 0. */ if( pPublish->packetIdentifier == 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain @@ -1399,7 +1399,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) &_logHideAll, "Payload length %hu.", pOutput->payloadLength ); - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1411,7 +1411,7 @@ IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, IotMqttError_t status = IOT_MQTT_SUCCESS; /* Allocate memory for PUBACK. */ - uint8_t * pBuffer = IotMqtt_MallocMessage( _MQTT_PACKET_PUBACK_SIZE ); + uint8_t * pBuffer = IotMqtt_MallocMessage( MQTT_PACKET_PUBACK_SIZE ); if( pBuffer == NULL ) { @@ -1423,16 +1423,16 @@ IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, { /* Set the output parameters. The remainder of this function always succeeds. */ *pPubackPacket = pBuffer; - *pPacketSize = _MQTT_PACKET_PUBACK_SIZE; + *pPacketSize = MQTT_PACKET_PUBACK_SIZE; /* Set the 4 bytes in PUBACK. */ - pBuffer[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; - pBuffer[ 1 ] = _MQTT_PACKET_PUBACK_REMAINING_LENGTH; - pBuffer[ 2 ] = _UINT16_HIGH_BYTE( packetIdentifier ); - pBuffer[ 3 ] = _UINT16_LOW_BYTE( packetIdentifier ); + pBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBACK; + pBuffer[ 1 ] = MQTT_PACKET_PUBACK_REMAINING_LENGTH; + pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetIdentifier ); + pBuffer[ 3 ] = UINT16_LOW_BYTE( packetIdentifier ); /* Print out the serialized PUBACK packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, _MQTT_PACKET_PUBACK_SIZE ); + IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, MQTT_PACKET_PUBACK_SIZE ); } return status; @@ -1442,25 +1442,25 @@ IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); /* Check the "Remaining length" of the received PUBACK. */ - if( pPuback->remainingLength != _MQTT_PACKET_PUBACK_REMAINING_LENGTH ) + if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "PUBACK does not have remaining length of %d.", - _MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + MQTT_PACKET_PUBACK_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ - pPuback->packetIdentifier = _UINT16_DECODE( pPuback->pRemainingData ); + pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1469,30 +1469,30 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) /* Packet identifier cannot be 0. */ if( pPuback->packetIdentifier == 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that the control packet type is 0x40 (this must be done after the * packet identifier is parsed). */ - if( pPuback->type != _MQTT_PACKET_TYPE_PUBACK ) + if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", pPuback->type ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1503,7 +1503,7 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc size_t * pPacketSize, uint16_t * pPacketIdentifier ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t i = 0, subscribePacketSize = 0, remainingLength = 0; uint16_t packetIdentifier = 0; uint8_t * pBuffer = NULL; @@ -1518,13 +1518,13 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc { IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", - _MQTT_MAX_REMAINING_LENGTH ); + MQTT_MAX_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Total size of the subscribe packet should be larger than the "Remaining length" @@ -1539,11 +1539,11 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc { IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -1551,7 +1551,7 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc *pPacketSize = subscribePacketSize; /* The first byte in SUBSCRIBE is the packet type. */ - *pBuffer = _MQTT_PACKET_TYPE_SUBSCRIBE; + *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; pBuffer++; /* Encode the "Remaining length" starting from the second byte. */ @@ -1563,8 +1563,8 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc IotMqtt_Assert( packetIdentifier != 0 ); /* Place the packet identifier into the SUBSCRIBE packet. */ - *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); pBuffer += 2; /* Serialize each subscription topic filter and QoS. */ @@ -1586,14 +1586,14 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t i = 0, remainingLength = pSuback->remainingLength; uint8_t subscriptionStatus = 0; const uint8_t * pVariableHeader = pSuback->pRemainingData; @@ -1606,15 +1606,15 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) &_logHideAll, "SUBACK cannot have a remaining length less than 3." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ - pSuback->packetIdentifier = _UINT16_DECODE( pVariableHeader ); + pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); IotLog( IOT_LOG_DEBUG, &_logHideAll, @@ -1622,18 +1622,18 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) /* Check that the control packet type is 0x90 (this must be done after the * packet identifier is parsed). */ - if( pSuback->type != _MQTT_PACKET_TYPE_SUBACK ) + if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", pSuback->type ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Iterate through each status byte in the SUBACK packet. */ @@ -1685,11 +1685,11 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1700,7 +1700,7 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub size_t * pPacketSize, uint16_t * pPacketIdentifier ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t i = 0, unsubscribePacketSize = 0, remainingLength = 0; uint16_t packetIdentifier = 0; uint8_t * pBuffer = NULL; @@ -1715,13 +1715,13 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub { IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", - _MQTT_MAX_REMAINING_LENGTH ); + MQTT_MAX_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Total size of the unsubscribe packet should be larger than the "Remaining length" @@ -1736,11 +1736,11 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub { IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Set the output parameters. The remainder of this function always succeeds. */ @@ -1748,7 +1748,7 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub *pPacketSize = unsubscribePacketSize; /* The first byte in UNSUBSCRIBE is the packet type. */ - *pBuffer = _MQTT_PACKET_TYPE_UNSUBSCRIBE; + *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; pBuffer++; /* Encode the "Remaining length" starting from the second byte. */ @@ -1760,8 +1760,8 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub IotMqtt_Assert( packetIdentifier != 0 ); /* Place the packet identifier into the UNSUBSCRIBE packet. */ - *pBuffer = _UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = _UINT16_LOW_BYTE( packetIdentifier ); + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); pBuffer += 2; /* Serialize each subscription topic filter. */ @@ -1779,41 +1779,41 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ - if( pUnsuback->remainingLength != _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) + if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "UNSUBACK does not have remaining length of %d.", - _MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); + MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ - pUnsuback->packetIdentifier = _UINT16_DECODE( pUnsuback->pRemainingData ); + pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); /* Packet identifier cannot be 0. */ if( pUnsuback->packetIdentifier == 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotLog( IOT_LOG_DEBUG, @@ -1822,21 +1822,21 @@ IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) /* Check that the control packet type is 0xb0 (this must be done after the * packet identifier is parsed). */ - if( pUnsuback->type != _MQTT_PACKET_TYPE_UNSUBACK ) + if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", pUnsuback->type ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1845,18 +1845,18 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, size_t * pPacketSize ) { /* PINGREQ packets are always the same. */ - static const uint8_t pPingreq[ _MQTT_PACKET_PINGREQ_SIZE ] = + static const uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = { - _MQTT_PACKET_TYPE_PINGREQ, + MQTT_PACKET_TYPE_PINGREQ, 0x00 }; /* Set the output parameters. */ *pPingreqPacket = ( uint8_t * ) pPingreq; - *pPacketSize = _MQTT_PACKET_PINGREQ_SIZE; + *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; /* Print out the PINGREQ packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, _MQTT_PACKET_PINGREQ_SIZE ); + IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, MQTT_PACKET_PINGREQ_SIZE ); return IOT_MQTT_SUCCESS; } @@ -1865,39 +1865,39 @@ IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) { - _IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); /* Check that the control packet type is 0xd0. */ - if( pPingresp->type != _MQTT_PACKET_TYPE_PINGRESP ) + if( pPingresp->type != MQTT_PACKET_TYPE_PINGRESP ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "Bad control packet type 0x%02x.", pPingresp->type ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the "Remaining length" (second byte) of the received PINGRESP. */ - if( pPingresp->remainingLength != _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) + if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { IotLog( IOT_LOG_ERROR, &_logHideAll, "PINGRESP does not have remaining length of %d.", - _MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1906,18 +1906,18 @@ IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, size_t * pPacketSize ) { /* DISCONNECT packets are always the same. */ - static const uint8_t pDisconnect[ _MQTT_PACKET_DISCONNECT_SIZE ] = + static const uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = { - _MQTT_PACKET_TYPE_DISCONNECT, + MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; /* Set the output parameters. */ *pDisconnectPacket = ( uint8_t * ) pDisconnect; - *pPacketSize = _MQTT_PACKET_DISCONNECT_SIZE; + *pPacketSize = MQTT_PACKET_DISCONNECT_SIZE; /* Print out the DISCONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, _MQTT_PACKET_DISCONNECT_SIZE ); + IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, MQTT_PACKET_DISCONNECT_SIZE ); return IOT_MQTT_SUCCESS; } @@ -1930,20 +1930,20 @@ void _IotMqtt_FreePacket( uint8_t * pPacket ) /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static * memory. */ - if( packetType != _MQTT_PACKET_TYPE_DISCONNECT ) + if( packetType != MQTT_PACKET_TYPE_DISCONNECT ) { - if( packetType != _MQTT_PACKET_TYPE_PINGREQ ) + if( packetType != MQTT_PACKET_TYPE_PINGREQ ) { IotMqtt_FreeMessage( pPacket ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 72bc48a7f6..0fa360ce4d 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -93,7 +93,7 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, static bool _topicMatch( const IotLink_t * pSubscriptionLink, void * pMatch ) { - _IOT_FUNCTION_ENTRY( bool, false ); + IOT_FUNCTION_ENTRY( bool, false ); uint16_t nameIndex = 0, filterIndex = 0; /* Because this function is called from a container function, the given link @@ -116,22 +116,22 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { status = ( strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0 ); - _IOT_GOTO_CLEANUP(); + IOT_GOTO_CLEANUP(); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* If the topic lengths are different but an exact match is required, return * false. */ if( pParam->exactMatchOnly == true ) { - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) ) @@ -151,26 +151,26 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { if( pTopicFilter[ filterIndex + 2 ] == '#' ) { - _IOT_SET_AND_GOTO_CLEANUP( true ); + IOT_SET_AND_GOTO_CLEANUP( true ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Filter "sport/+" also matches the "sport/" but not "sport". */ @@ -180,21 +180,21 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { if( pTopicFilter[ filterIndex + 1 ] == '+' ) { - _IOT_SET_AND_GOTO_CLEANUP( true ); + IOT_SET_AND_GOTO_CLEANUP( true ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else @@ -217,13 +217,13 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, { /* Subsequent characters don't need to be checked if the for the * multi-level wildcard. */ - _IOT_SET_AND_GOTO_CLEANUP( true ); + IOT_SET_AND_GOTO_CLEANUP( true ); } else { /* Any character mismatch other than '+' or '#' means the topic * name does not match the topic filter. */ - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } } @@ -235,14 +235,14 @@ static bool _topicMatch( const IotLink_t * pSubscriptionLink, /* If the end of both strings has been reached, they match. */ if( ( nameIndex == topicNameLength ) && ( filterIndex == topicFilterLength ) ) { - _IOT_SET_AND_GOTO_CLEANUP( true ); + IOT_SET_AND_GOTO_CLEANUP( true ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -293,12 +293,12 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return match; @@ -385,7 +385,7 @@ IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } return status; @@ -429,7 +429,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Subscription found. Calculate pointer to subscription object. */ @@ -481,12 +481,12 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Move current link pointer. */ @@ -572,7 +572,7 @@ void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnecti } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } @@ -621,14 +621,14 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } status = true; } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } IotMutex_Unlock( &( mqttConnection->subscriptionMutex ) ); diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index a50bc977c2..48b6f138fd 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -37,18 +37,18 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); /* Check for NULL. */ if( pConnectInfo == NULL ) { IotLogError( "MQTT connection information cannot be NULL." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that a client identifier was set. */ @@ -56,11 +56,11 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogError( "Client identifier must be set." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check for a zero-length client identifier. Zero-length client identifiers @@ -73,16 +73,16 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogError( "A zero-length client identifier cannot be used with a clean session." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that the number of persistent session subscriptions is valid. */ @@ -94,21 +94,21 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogError( "Previous subscription count cannot be 0." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* In MQTT 3.1.1, servers are not obligated to accept client identifiers longer @@ -121,23 +121,23 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check for compatibility with the AWS IoT MQTT service limits. */ if( pConnectInfo->awsIotMqttMode == true ) { /* Check that client identifier is within the service limit. */ - if( pConnectInfo->clientIdentifierLength > _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ) + if( pConnectInfo->clientIdentifierLength > AWS_IOT_MQTT_SERVER_MAX_CLIENTID ) { IotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", - _AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); + AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check for compliance with AWS IoT keep-alive limits. */ @@ -145,33 +145,33 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IotLogWarn( "AWS IoT does not support disabling keep-alive. Default keep-alive " "of %d seconds will be used.", - _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); } - else if( pConnectInfo->keepAliveSeconds < _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) + else if( pConnectInfo->keepAliveSeconds < AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) { IotLogWarn( "AWS IoT does not support keep-alive intervals less than %d seconds. " "An interval of %d seconds will be used.", - _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, - _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); + AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, + AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); } - else if( pConnectInfo->keepAliveSeconds > _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) + else if( pConnectInfo->keepAliveSeconds > AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) { IotLogWarn( "AWS IoT does not support keep-alive intervals greater than %d seconds. " "An interval of %d seconds will be used.", - _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, - _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); + AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, + AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -179,18 +179,18 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, const IotMqttPublishInfo_t * pPublishInfo ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); /* Check for NULL. */ if( pPublishInfo == NULL ) { IotLogError( "Publish information cannot be NULL." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check topic name for NULL or zero-length. */ @@ -200,18 +200,18 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pPublishInfo->topicNameLength == 0 ) { IotLogError( "Publish topic name length cannot be 0." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Only allow NULL payloads with zero length. */ @@ -221,16 +221,16 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, { IotLogError( "Nonzero payload length cannot have a NULL payload." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check for a valid QoS. */ @@ -240,16 +240,16 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, { IotLogError( "Publish QoS must be either 0 or 1." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check the retry parameters. */ @@ -259,16 +259,16 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, { IotLogError( "Publish retry time must be positive." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check for compatibility with AWS IoT MQTT server. */ @@ -279,50 +279,50 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, { IotLogError( "AWS IoT does not support retained publish messages." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check topic name length. */ - if( pPublishInfo->topicNameLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + if( pPublishInfo->topicNameLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) { IotLogError( "AWS IoT does not support topic names longer than %d bytes.", - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); /* Check for NULL. */ if( operation == NULL ) { IotLogError( "Operation reference cannot be NULL." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that reference is waitable. */ @@ -330,14 +330,14 @@ bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) { IotLogError( "Operation is not waitable." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -347,7 +347,7 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, const IotMqttSubscription_t * pListStart, size_t listSize ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); size_t i = 0; uint16_t j = 0; const IotMqttSubscription_t * pListElement = NULL; @@ -361,43 +361,43 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription list pointer cannot be NULL." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( listSize == 0 ) { IotLogError( "Empty subscription list." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* AWS IoT supports at most 8 topic filters in a single SUBSCRIBE packet. */ if( awsIotMqttMode == true ) { - if( listSize > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) + if( listSize > AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) { IotLogError( "AWS IoT does not support more than %d topic filters per " "subscription request.", - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); + AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } for( i = 0; i < listSize; i++ ) @@ -413,32 +413,32 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription QoS must be either 0 or 1." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pListElement->callback.function == NULL ) { IotLogError( "Callback function must be set." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check subscription topic filter. */ @@ -446,43 +446,43 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, { IotLogError( "Subscription topic filter cannot be NULL." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } if( pListElement->topicFilterLength == 0 ) { IotLogError( "Subscription topic filter length cannot be 0." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check for compatibility with AWS IoT MQTT server. */ if( awsIotMqttMode == true ) { /* Check topic filter length. */ - if( pListElement->topicFilterLength > _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) + if( pListElement->topicFilterLength > AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) { IotLogError( "AWS IoT does not support topic filters longer than %d bytes.", - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Check that the wildcards '+' and '#' are being used correctly. */ @@ -502,16 +502,16 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Unless '+' is the last character in the filter, it must be succeeded by '/'. */ @@ -523,16 +523,16 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; @@ -547,11 +547,11 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } /* Unless '#' is standalone, it must be preceded by '/'. */ @@ -563,16 +563,16 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, pListElement->topicFilterLength, pListElement->pTopicFilter ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } } else { - _EMPTY_ELSE_MARKER; + EMPTY_ELSE_MARKER; } break; @@ -583,7 +583,7 @@ bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation, } } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index f219a7050d..aa9396f469 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -154,7 +154,7 @@ static void _updatedCallbackWrapper( void * pArgument, */ uint32_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; -#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE /** * @brief Printable names for the Shadow callbacks. @@ -187,12 +187,12 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, return AWS_IOT_SHADOW_BAD_PARAMETER; } - if( thingNameLength > _MAX_THING_NAME_LENGTH ) + if( thingNameLength > MAX_THING_NAME_LENGTH ) { IotLogError( "Thing Name length of %lu exceeds the maximum allowed" "length of %d.", ( unsigned long ) thingNameLength, - _MAX_THING_NAME_LENGTH ); + MAX_THING_NAME_LENGTH ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -316,7 +316,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio _shadowSubscription_t * pSubscription = NULL; /* Check parameters. */ - if( _validateThingNameFlags( ( _shadowOperationType_t ) ( type + _SHADOW_OPERATION_COUNT ), + if( _validateThingNameFlags( ( _shadowOperationType_t ) ( type + SHADOW_OPERATION_COUNT ), pThingName, thingNameLength, 0, @@ -432,21 +432,21 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt uint16_t operationTopicLength = 0; /* Lookup table for Shadow callback suffixes. */ - const char * const pCallbackSuffix[ _SHADOW_CALLBACK_COUNT ] = + const char * const pCallbackSuffix[ SHADOW_CALLBACK_COUNT ] = { - _SHADOW_DELTA_SUFFIX, /* Delta callback. */ - _SHADOW_UPDATED_SUFFIX /* Updated callback. */ + SHADOW_DELTA_SUFFIX, /* Delta callback. */ + SHADOW_UPDATED_SUFFIX /* Updated callback. */ }; /* Lookup table for Shadow callback suffix lengths. */ - const uint16_t pCallbackSuffixLength[ _SHADOW_CALLBACK_COUNT ] = + const uint16_t pCallbackSuffixLength[ SHADOW_CALLBACK_COUNT ] = { - _SHADOW_DELTA_SUFFIX_LENGTH, /* Delta callback. */ - _SHADOW_UPDATED_SUFFIX_LENGTH /* Updated callback. */ + SHADOW_DELTA_SUFFIX_LENGTH, /* Delta callback. */ + SHADOW_UPDATED_SUFFIX_LENGTH /* Updated callback. */ }; /* Lookup table for Shadow callback function wrappers. */ - const _mqttCallbackFunction_t pCallbackWrapper[ _SHADOW_CALLBACK_COUNT ] = + const _mqttCallbackFunction_t pCallbackWrapper[ SHADOW_CALLBACK_COUNT ] = { _deltaCallbackWrapper, /* Delta callback. */ _updatedCallbackWrapper, /* Updated callback. */ @@ -552,7 +552,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); /* Set the callback type. Shadow callbacks are enumerated after the operations. */ - callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( type + _SHADOW_OPERATION_COUNT ); + callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( type + SHADOW_OPERATION_COUNT ); /* Set the remaining members of the callback param. */ callbackParam.mqttConnection = pMessage->mqttConnection; @@ -893,13 +893,13 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /* Check UPDATE document for a client token. */ if( IotJsonUtils_FindJsonValue( pUpdateInfo->u.update.pUpdateDocument, pUpdateInfo->u.update.updateDocumentLength, - _CLIENT_TOKEN_KEY, - _CLIENT_TOKEN_KEY_LENGTH, + CLIENT_TOKEN_KEY, + CLIENT_TOKEN_KEY_LENGTH, &pClientToken, &clientTokenLength ) == false ) { IotLogError( "Shadow document for Shadow UPDATE must have a %s key.", - _CLIENT_TOKEN_KEY ); + CLIENT_TOKEN_KEY ); return AWS_IOT_SHADOW_BAD_PARAMETER; } @@ -907,10 +907,10 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /* Check the client token length. It must be greater than the length of its * enclosing double quotes (2) and less than the maximum allowed by the Shadow * service. */ - if( ( clientTokenLength < 2 ) || ( clientTokenLength > _MAX_CLIENT_TOKEN_LENGTH ) ) + if( ( clientTokenLength < 2 ) || ( clientTokenLength > MAX_CLIENT_TOKEN_LENGTH ) ) { IotLogError( "Client token length must be between 2 and %d (including " - "enclosing quotes).", _MAX_CLIENT_TOKEN_LENGTH + 2 ); + "enclosing quotes).", MAX_CLIENT_TOKEN_LENGTH + 2 ); return AWS_IOT_SHADOW_BAD_PARAMETER; } diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 24b2d1a377..e08101c4ea 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -124,7 +124,7 @@ static void _updateCallback( void * pArgument, /*-----------------------------------------------------------*/ -#if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE /** * @brief Printable names for each of the Shadow operations. @@ -137,7 +137,7 @@ static void _updateCallback( void * pArgument, "SET DELTA", "SET UPDATED" }; -#endif /* if _LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ +#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ /** * @brief List of active Shadow operations awaiting a response from the Shadow @@ -188,8 +188,8 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, /* Check for the client token in the UPDATE response document. */ match = IotJsonUtils_FindJsonValue( pParam->pDocument, pParam->documentLength, - _CLIENT_TOKEN_KEY, - _CLIENT_TOKEN_KEY_LENGTH, + CLIENT_TOKEN_KEY, + CLIENT_TOKEN_KEY_LENGTH, &pClientToken, &clientTokenLength ); @@ -530,19 +530,19 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty char * pBuffer = NULL; /* Lookup table for Shadow operation strings. */ - const char * const pOperationString[ _SHADOW_OPERATION_COUNT ] = + const char * const pOperationString[ SHADOW_OPERATION_COUNT ] = { - _SHADOW_DELETE_OPERATION_STRING, /* Shadow delete operation. */ - _SHADOW_GET_OPERATION_STRING, /* Shadow get operation. */ - _SHADOW_UPDATE_OPERATION_STRING /* Shadow update operation. */ + SHADOW_DELETE_OPERATION_STRING, /* Shadow delete operation. */ + SHADOW_GET_OPERATION_STRING, /* Shadow get operation. */ + SHADOW_UPDATE_OPERATION_STRING /* Shadow update operation. */ }; /* Lookup table for Shadow operation string lengths. */ - const uint16_t pOperationStringLength[ _SHADOW_OPERATION_COUNT ] = + const uint16_t pOperationStringLength[ SHADOW_OPERATION_COUNT ] = { - _SHADOW_DELETE_OPERATION_STRING_LENGTH, /* Shadow delete operation. */ - _SHADOW_GET_OPERATION_STRING_LENGTH, /* Shadow get operation. */ - _SHADOW_UPDATE_OPERATION_STRING_LENGTH /* Shadow update operation. */ + SHADOW_DELETE_OPERATION_STRING_LENGTH, /* Shadow delete operation. */ + SHADOW_GET_OPERATION_STRING_LENGTH, /* Shadow get operation. */ + SHADOW_UPDATE_OPERATION_STRING_LENGTH /* Shadow update operation. */ }; /* Only Shadow delete, get, and update operation types should be passed to this @@ -552,10 +552,10 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty ( type == _SHADOW_UPDATE ) ); /* Calculate the required topic buffer length. */ - bufferLength = ( uint16_t ) ( _SHADOW_TOPIC_PREFIX_LENGTH + + bufferLength = ( uint16_t ) ( SHADOW_TOPIC_PREFIX_LENGTH + thingNameLength + pOperationStringLength[ type ] + - _SHADOW_LONGEST_SUFFIX_LENGTH ); + SHADOW_LONGEST_SUFFIX_LENGTH ); /* Allocate memory for the topic buffer if no topic buffer is given. */ if( *pTopicBuffer == NULL ) @@ -574,8 +574,8 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty } /* Copy the Shadow topic prefix into the topic buffer. */ - ( void ) memcpy( pBuffer, _SHADOW_TOPIC_PREFIX, _SHADOW_TOPIC_PREFIX_LENGTH ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + _SHADOW_TOPIC_PREFIX_LENGTH ); + ( void ) memcpy( pBuffer, SHADOW_TOPIC_PREFIX, SHADOW_TOPIC_PREFIX_LENGTH ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + SHADOW_TOPIC_PREFIX_LENGTH ); /* Copy the Thing Name into the topic buffer. */ ( void ) memcpy( pBuffer + operationTopicLength, pThingName, thingNameLength ); @@ -618,7 +618,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Lookup table for Shadow operation callbacks. */ - const _mqttCallbackFunction_t shadowCallbacks[ _SHADOW_OPERATION_COUNT ] = + const _mqttCallbackFunction_t shadowCallbacks[ SHADOW_OPERATION_COUNT ] = { _deleteCallback, _getCallback, diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index a9d40fb451..eb4f17d5ac 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -42,33 +42,33 @@ /** * @brief The JSON key for the error code in a Shadow error document. */ -#define _ERROR_DOCUMENT_CODE_KEY "code" +#define ERROR_DOCUMENT_CODE_KEY "code" /** - * @brief The length of #_ERROR_DOCUMENT_CODE_KEY. + * @brief The length of #ERROR_DOCUMENT_CODE_KEY. */ -#define _ERROR_DOCUMENT_CODE_KEY_LENGTH ( sizeof( _ERROR_DOCUMENT_CODE_KEY ) - 1 ) +#define ERROR_DOCUMENT_CODE_KEY_LENGTH ( sizeof( ERROR_DOCUMENT_CODE_KEY ) - 1 ) /** * @brief The JSON key for the error message in a Shadow error document. */ -#define _ERROR_DOCUMENT_MESSAGE_KEY "message" +#define ERROR_DOCUMENT_MESSAGE_KEY "message" /** - * @brief The length of #_ERROR_DOCUMENT_MESSAGE_KEY. + * @brief The length of #ERROR_DOCUMENT_MESSAGE_KEY. */ -#define _ERROR_DOCUMENT_MESSAGE_KEY_LENGTH ( sizeof( _ERROR_DOCUMENT_MESSAGE_KEY ) - 1 ) +#define ERROR_DOCUMENT_MESSAGE_KEY_LENGTH ( sizeof( ERROR_DOCUMENT_MESSAGE_KEY ) - 1 ) /** * @brief The minimum possible length of a Shadow topic name, per the Shadow * spec. */ -#define _MINIMUM_SHADOW_TOPIC_NAME_LENGTH \ - ( _SHADOW_TOPIC_PREFIX_LENGTH + \ - ( uint16_t ) sizeof( _SHADOW_GET_OPERATION_STRING ) + \ - ( _SHADOW_ACCEPTED_SUFFIX_LENGTH < _SHADOW_REJECTED_SUFFIX_LENGTH ? \ - _SHADOW_ACCEPTED_SUFFIX_LENGTH : \ - _SHADOW_REJECTED_SUFFIX_LENGTH ) ) +#define MINIMUM_SHADOW_TOPIC_NAME_LENGTH \ + ( SHADOW_TOPIC_PREFIX_LENGTH + \ + ( uint16_t ) sizeof( SHADOW_GET_OPERATION_STRING ) + \ + ( SHADOW_ACCEPTED_SUFFIX_LENGTH < SHADOW_REJECTED_SUFFIX_LENGTH ? \ + SHADOW_ACCEPTED_SUFFIX_LENGTH : \ + SHADOW_REJECTED_SUFFIX_LENGTH ) ) /*-----------------------------------------------------------*/ @@ -140,10 +140,10 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam /* Check that the Shadow status topic name is at least as long as the * "accepted" suffix. */ - if( topicNameLength > _SHADOW_ACCEPTED_SUFFIX_LENGTH ) + if( topicNameLength > SHADOW_ACCEPTED_SUFFIX_LENGTH ) { /* Calculate where the "accepted" suffix should start. */ - pSuffixStart = pTopicName + topicNameLength - _SHADOW_ACCEPTED_SUFFIX_LENGTH; + pSuffixStart = pTopicName + topicNameLength - SHADOW_ACCEPTED_SUFFIX_LENGTH; /* pSuffixStart must be in pTopicName. */ AwsIotShadow_Assert( ( pSuffixStart > pTopicName ) && @@ -151,8 +151,8 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam /* Check if the end of the Shadow status topic name is "accepted". */ if( strncmp( pSuffixStart, - _SHADOW_ACCEPTED_SUFFIX, - _SHADOW_ACCEPTED_SUFFIX_LENGTH ) == 0 ) + SHADOW_ACCEPTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ) == 0 ) { return _SHADOW_ACCEPTED; } @@ -160,10 +160,10 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam /* Check that the Shadow status topic name is at least as long as the * "rejected" suffix. */ - if( topicNameLength > _SHADOW_REJECTED_SUFFIX_LENGTH ) + if( topicNameLength > SHADOW_REJECTED_SUFFIX_LENGTH ) { /* Calculate where the "rejected" suffix should start. */ - pSuffixStart = pTopicName + topicNameLength - _SHADOW_REJECTED_SUFFIX_LENGTH; + pSuffixStart = pTopicName + topicNameLength - SHADOW_REJECTED_SUFFIX_LENGTH; /* pSuffixStart must be in pTopicName. */ AwsIotShadow_Assert( ( pSuffixStart > pTopicName ) && @@ -171,8 +171,8 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam /* Check if the end of the Shadow status topic name is "rejected". */ if( strncmp( pSuffixStart, - _SHADOW_REJECTED_SUFFIX, - _SHADOW_REJECTED_SUFFIX_LENGTH ) == 0 ) + SHADOW_REJECTED_SUFFIX, + SHADOW_REJECTED_SUFFIX_LENGTH ) == 0 ) { return _SHADOW_REJECTED; } @@ -194,8 +194,8 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen /* Parse the code from the error document. */ if( IotJsonUtils_FindJsonValue( pErrorDocument, errorDocumentLength, - _ERROR_DOCUMENT_CODE_KEY, - _ERROR_DOCUMENT_CODE_KEY_LENGTH, + ERROR_DOCUMENT_CODE_KEY, + ERROR_DOCUMENT_CODE_KEY_LENGTH, &pCode, &codeLength ) == false ) { @@ -218,8 +218,8 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen * a message. */ if( IotJsonUtils_FindJsonValue( pErrorDocument, errorDocumentLength, - _ERROR_DOCUMENT_MESSAGE_KEY, - _ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, + ERROR_DOCUMENT_MESSAGE_KEY, + ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, &pMessage, &messageLength ) == true ) { @@ -253,24 +253,24 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, size_t thingNameLength = 0; /* Check that the topic name length exceeds the minimum possible length. */ - if( topicNameLength < _MINIMUM_SHADOW_TOPIC_NAME_LENGTH ) + if( topicNameLength < MINIMUM_SHADOW_TOPIC_NAME_LENGTH ) { return AWS_IOT_SHADOW_BAD_RESPONSE; } /* All Shadow topic names must start with the same prefix. */ - if( strncmp( _SHADOW_TOPIC_PREFIX, + if( strncmp( SHADOW_TOPIC_PREFIX, pTopicName, - _SHADOW_TOPIC_PREFIX_LENGTH ) != 0 ) + SHADOW_TOPIC_PREFIX_LENGTH ) != 0 ) { return AWS_IOT_SHADOW_BAD_RESPONSE; } /* The Thing Name starts immediately after the topic prefix. */ - pThingNameStart = pTopicName + _SHADOW_TOPIC_PREFIX_LENGTH; + pThingNameStart = pTopicName + SHADOW_TOPIC_PREFIX_LENGTH; /* Calculate the length of the Thing Name. */ - while( ( thingNameLength + _SHADOW_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) && + while( ( thingNameLength + SHADOW_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) && ( pThingNameStart[ thingNameLength ] != '/' ) ) { thingNameLength++; @@ -278,7 +278,7 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, /* The end of the topic name was reached without finding a '/'. The topic * name is invalid. */ - if( thingNameLength + _SHADOW_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) + if( thingNameLength + SHADOW_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) { return AWS_IOT_SHADOW_BAD_RESPONSE; } diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 9055e390ad..a56a7b1674 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -261,7 +261,7 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, /* If any Shadow operation's subscription reference count is not 0, then the * subscription cannot be removed. */ - for( i = 0; i < _SHADOW_OPERATION_COUNT; i++ ) + for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) { if( pSubscription->references[ i ] > 0 ) { @@ -273,7 +273,7 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, return; } - else if( pSubscription->references[ i ] == _PERSISTENT_SUBSCRIPTION ) + else if( pSubscription->references[ i ] == PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " "Subscription will not be removed.", @@ -285,7 +285,7 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, } /* If any Shadow callbacks are active, then the subscription cannot be removed. */ - for( i = 0; i < _SHADOW_CALLBACK_COUNT; i++ ) + for( i = 0; i < SHADOW_CALLBACK_COUNT; i++ ) { if( pSubscription->callbacks[ i ].function != NULL ) { @@ -346,7 +346,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe _shadowSubscription_t * pSubscription = pOperation->pSubscription; /* Do nothing if this operation has persistent subscriptions. */ - if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ type ] == PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " "count will not be incremented.", @@ -366,9 +366,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe { /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - _SHADOW_ACCEPTED_SUFFIX, - _SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + SHADOW_ACCEPTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); /* There should not be an active subscription for the accepted topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -390,9 +390,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - _SHADOW_REJECTED_SUFFIX, - _SHADOW_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_REJECTED_SUFFIX_LENGTH ); + SHADOW_REJECTED_SUFFIX, + SHADOW_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_REJECTED_SUFFIX_LENGTH ); /* There should not be an active subscription for the rejected topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -412,9 +412,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Failed to add subscription to Shadow "rejected" topic. Remove * subscription for the Shadow "accepted" topic. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - _SHADOW_ACCEPTED_SUFFIX, - _SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + SHADOW_ACCEPTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, pTopicBuffer, @@ -441,7 +441,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Otherwise, set the persistent subscriptions flag. */ else { - pSubscription->references[ type ] = _PERSISTENT_SUBSCRIPTION; + pSubscription->references[ type ] = PERSISTENT_SUBSCRIPTION; IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", _pAwsIotShadowOperationNames[ type ], @@ -464,7 +464,7 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, uint16_t operationTopicLength = 0; /* Do nothing if this Shadow operation has persistent subscriptions. */ - if( pSubscription->references[ type ] == _PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ type ] == PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " "count will not be decremented.", @@ -501,9 +501,9 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - _SHADOW_ACCEPTED_SUFFIX, - _SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + SHADOW_ACCEPTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); /* There should be an active subscription for the accepted topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -520,9 +520,9 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - _SHADOW_REJECTED_SUFFIX, - _SHADOW_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + SHADOW_REJECTED_SUFFIX, + SHADOW_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); /* There should be an active subscription for the accepted topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -584,7 +584,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); - for( i = 0; i < _SHADOW_OPERATION_COUNT; i++ ) + for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) { if( ( flags & ( 0x1UL << i ) ) != 0 ) { @@ -596,7 +596,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Subscription must have a topic buffer. */ AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); - if( pSubscription->references[ i ] == _PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ i ] == PERSISTENT_SUBSCRIPTION ) { /* Generate the prefix of the Shadow topic. This function will not * fail when given a buffer. */ @@ -608,9 +608,9 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Remove the "accepted" topic. */ ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, - _SHADOW_ACCEPTED_SUFFIX, - _SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + SHADOW_ACCEPTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); removeAcceptedStatus = _modifyOperationSubscriptions( mqttConnection, pSubscription->pTopicBuffer, @@ -625,10 +625,10 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Remove the "rejected" topic. */ ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, - _SHADOW_REJECTED_SUFFIX, - _SHADOW_ACCEPTED_SUFFIX_LENGTH ); + SHADOW_REJECTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ); topicFilterLength = ( uint16_t ) ( operationTopicLength + - _SHADOW_REJECTED_SUFFIX_LENGTH ); + SHADOW_REJECTED_SUFFIX_LENGTH ); removeRejectedStatus = _modifyOperationSubscriptions( mqttConnection, pSubscription->pTopicBuffer, diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index ddb5ab8999..1dfb11a66a 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -54,16 +54,16 @@ /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "CLOCK" ) +#define LIBRARY_LOG_NAME ( "CLOCK" ) #include "iot_logging_setup.h" /*-----------------------------------------------------------*/ @@ -74,14 +74,14 @@ * For more information on timestring formats, see [this link.] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) */ -#define _TIMESTRING_FORMAT ( "%F %R:%S" ) +#define TIMESTRING_FORMAT ( "%F %R:%S" ) /* * Time conversion constants. */ -#define _NANOSECONDS_PER_SECOND ( 1000000000 ) /**< @brief Nanoseconds per second. */ -#define _NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ -#define _MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ +#define NANOSECONDS_PER_SECOND ( 1000000000 ) /**< @brief Nanoseconds per second. */ +#define NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ +#define MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ /*-----------------------------------------------------------*/ @@ -127,17 +127,17 @@ bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, if( clock_gettime( CLOCK_REALTIME, &systemTime ) == 0 ) { /* Add the nanoseconds value to the time. */ - systemTime.tv_nsec += ( long ) ( ( timeoutMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); + systemTime.tv_nsec += ( long ) ( ( timeoutMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND ); /* Check for overflow of nanoseconds value. */ - if( systemTime.tv_nsec >= _NANOSECONDS_PER_SECOND ) + if( systemTime.tv_nsec >= NANOSECONDS_PER_SECOND ) { - systemTime.tv_nsec -= _NANOSECONDS_PER_SECOND; + systemTime.tv_nsec -= NANOSECONDS_PER_SECOND; systemTime.tv_sec++; } /* Add the seconds value to the timeout. */ - systemTime.tv_sec += ( time_t ) ( timeoutMs / _MILLISECONDS_PER_SECOND ); + systemTime.tv_sec += ( time_t ) ( timeoutMs / MILLISECONDS_PER_SECOND ); /* Set the output parameter. */ *pOutput = systemTime; @@ -173,7 +173,7 @@ bool IotClock_GetTimestring( char * pBuffer, if( status == true ) { /* Convert the localTime struct to a string. */ - timestringLength = strftime( pBuffer, bufferSize, _TIMESTRING_FORMAT, &localTime ); + timestringLength = strftime( pBuffer, bufferSize, TIMESTRING_FORMAT, &localTime ); /* Check for error from strftime. */ if( timestringLength == 0 ) @@ -213,8 +213,8 @@ void IotClock_SleepMs( uint32_t sleepTimeMs ) /* Convert parameter to timespec. */ struct timespec sleepTime = { - .tv_sec = sleepTimeMs / _MILLISECONDS_PER_SECOND, - .tv_nsec = ( sleepTimeMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND + .tv_sec = sleepTimeMs / MILLISECONDS_PER_SECOND, + .tv_nsec = ( sleepTimeMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND }; if( nanosleep( &sleepTime, NULL ) == -1 ) @@ -302,8 +302,8 @@ bool IotClock_TimerArm( IotTimer_t * pTimer, /* Calculate the timer expiration period. */ if( periodMs > 0 ) { - timerExpiration.it_interval.tv_sec = ( time_t ) ( periodMs / _MILLISECONDS_PER_SECOND ); - timerExpiration.it_interval.tv_nsec = ( long ) ( ( periodMs % _MILLISECONDS_PER_SECOND ) * _NANOSECONDS_PER_MILLISECOND ); + timerExpiration.it_interval.tv_sec = ( time_t ) ( periodMs / MILLISECONDS_PER_SECOND ); + timerExpiration.it_interval.tv_nsec = ( long ) ( ( periodMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND ); } /* Arm the underlying POSIX timer. */ diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 6bb761cb4a..a6b2d18f2f 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -57,16 +57,16 @@ /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "THREAD" ) +#define LIBRARY_LOG_NAME ( "THREAD" ) #include "iot_logging_setup.h" /* @@ -142,7 +142,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, int32_t priority, size_t stackSize ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); int posixErrno = 0; bool threadAttibutesCreated = false; _threadInfo_t * pThreadInfo = NULL; @@ -162,7 +162,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, { IotLogError( "Failed to allocate memory for new thread." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Set up thread attributes object. */ @@ -173,7 +173,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IotLogError( "Failed to initialize thread attributes. errno=%d.", posixErrno ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } threadAttibutesCreated = true; @@ -187,7 +187,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IotLogError( "Failed to set detached thread attribute. errno=%d.", posixErrno ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Set the thread routine and argument. */ @@ -204,10 +204,10 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, { IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); /* Destroy thread attributes object. */ if( threadAttibutesCreated == true ) @@ -230,7 +230,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, } } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/network/iot_network_openssl.c index 5aeb5a890c..0126087146 100644 --- a/platform/source/posix/network/iot_network_openssl.c +++ b/platform/source/posix/network/iot_network_openssl.c @@ -61,16 +61,16 @@ /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_NETWORK - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK #else #ifdef IOT_LOG_LEVEL_GLOBAL - #define _LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL #else - #define _LIBRARY_LOG_LEVEL IOT_LOG_NONE + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE #endif #endif -#define _LIBRARY_LOG_NAME ( "NET" ) +#define LIBRARY_LOG_NAME ( "NET" ) #include "iot_logging_setup.h" /* @@ -199,7 +199,7 @@ static void * _networkReceiveThread( void * pArgument ) */ static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) { - _IOT_FUNCTION_ENTRY( int, 0 ); + IOT_FUNCTION_ENTRY( int, 0 ); int tcpSocket = -1; const uint16_t netPort = htons( pServerInfo->port ); struct addrinfo * pListHead = NULL, * pAddressInfo = NULL; @@ -213,7 +213,7 @@ static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) { IotLogError( "DNS lookup failed. %s.", gai_strerror( status ) ); - _IOT_SET_AND_GOTO_CLEANUP( -1 ); + IOT_SET_AND_GOTO_CLEANUP( -1 ); } IotLogDebug( "Successfully received DNS records." ); @@ -268,10 +268,10 @@ static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) { IotLogError( "Failed to connect to all retrieved DNS records." ); - _IOT_SET_AND_GOTO_CLEANUP( -1 ); + IOT_SET_AND_GOTO_CLEANUP( -1 ); } - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); /* Free DNS records. */ if( pListHead != NULL ) @@ -285,7 +285,7 @@ static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) status = tcpSocket; } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -307,7 +307,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, const char * pClientCertPath, const char * pCertPrivateKeyPath ) { - _IOT_FUNCTION_ENTRY( bool, true ); + IOT_FUNCTION_ENTRY( bool, true ); X509 * pRootCa = NULL; /* OpenSSL does not provide a single function for reading and loading certificates @@ -319,7 +319,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, { IotLogError( "Failed to open %s", pRootCAPath ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Read the root CA into an X509 object, then close its file handle. */ @@ -334,7 +334,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, { IotLogError( "Failed to parse root CA." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } /* Add the root CA to certificate store. */ @@ -343,7 +343,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, { IotLogError( "Failed to add root CA to certificate store." ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } IotLogInfo( "Successfully imported root CA." ); @@ -355,7 +355,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, IotLogError( "Failed to import client certificate at %s", pClientCertPath ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } IotLogInfo( "Successfully imported client certificate." ); @@ -368,12 +368,12 @@ static bool _readCredentials( SSL_CTX * pSslContext, IotLogError( "Failed to import client certificate private key at %s", pCertPrivateKeyPath ); - _IOT_SET_AND_GOTO_CLEANUP( false ); + IOT_SET_AND_GOTO_CLEANUP( false ); } IotLogInfo( "Successfully imported client certificate private key." ); - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); /* Free the root CA object. */ if( pRootCa != NULL ) @@ -381,7 +381,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, X509_free( pRootCa ); } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -399,7 +399,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, const char * pServerName, const IotNetworkCredentialsOpenssl_t * pOpensslCredentials ) { - _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); SSL_CTX * pSslContext = NULL; /* Create a new SSL context. */ @@ -413,7 +413,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "Failed to create new SSL context." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } /* Set auto retry mode for the blocking calls to SSL_read and SSL_write. The @@ -427,7 +427,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, pOpensslCredentials->pClientCertPath, pOpensslCredentials->pPrivateKeyPath ) == false ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Create a new SSL connection context */ @@ -437,7 +437,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "Failed to create new SSL connection context." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } /* Enable SSL peer verification. */ @@ -450,7 +450,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "Failed to set SSL socket %d.", pNetworkConnection->socket ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } /* Set up ALPN if requested. */ @@ -464,7 +464,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "Failed to set ALPN protos." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } } @@ -481,7 +481,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, IotLogError( "Failed to set max send fragment length %lu.", ( unsigned long ) pOpensslCredentials->maxFragmentLength ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } /* In supported versions of OpenSSL, change the size of the read buffer @@ -504,7 +504,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "Failed to set server name %s for SNI.", pServerName ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } } @@ -513,7 +513,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "TLS handshake failed." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } IotLogInfo( "TLS handshake succeeded." ); @@ -523,12 +523,12 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogError( "Peer certificate verification failed." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } IotLogInfo( "Peer certificate verified. TLS connection established." ); - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); /* Free the SSL context. */ if( pSslContext != NULL ) @@ -546,7 +546,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, } } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -643,7 +643,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, void * pCredentialInfo, void ** pConnection ) { - _IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int tcpSocket = -1; bool sslMutexCreated = false; _networkConnection_t * pNewNetworkConnection = NULL; @@ -660,7 +660,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, { IotLogError( "Failed to allocate memory for new network connection." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_NO_MEMORY ); } /* Clear connection data. */ @@ -671,7 +671,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, if( tcpSocket == -1 ) { - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } else { @@ -693,7 +693,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, { IotLogError( "Failed to create network send mutex." ); - _IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } status = _tlsSetup( pNewNetworkConnection, @@ -702,7 +702,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, } /* Clean up on error. */ - _IOT_FUNCTION_CLEANUP_BEGIN(); + IOT_FUNCTION_CLEANUP_BEGIN(); if( status != IOT_NETWORK_SUCCESS ) { @@ -727,7 +727,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, *pNetworkConnection = pNewNetworkConnection; } - _IOT_FUNCTION_CLEANUP_END(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index 920e3a3557..dcc008efb5 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -65,7 +65,7 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { - _IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Silence warnings about unused parameters. */ @@ -78,12 +78,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -101,10 +101,10 @@ int main( int argc, /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 0338e9dda4..e4830b9854 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -46,32 +46,32 @@ #include "unity_fixture.h" /* Total time to wait for a state to be true. */ -#define _WAIT_STATE_TOTAL_SECONDS 5 +#define WAIT_STATE_TOTAL_SECONDS 5 /* Time interval for defender agent to publish metrics. It will be throttled if too frequent. */ /* TODO: if we can change "thingname" in each test, this can be lowered. */ -#define _DEFENDER_PUBLISH_INTERVAL_SECONDS 20 +#define DEFENDER_PUBLISH_INTERVAL_SECONDS 20 /* Estimated max size of message payload received in MQTT callback. */ -#define _PAYLOAD_MAX_SIZE 200 +#define PAYLOAD_MAX_SIZE 200 /* Estimated max size of metrics report defender published. */ -#define _METRICS_MAX_SIZE 200 +#define METRICS_MAX_SIZE 200 /* Max size of address: IP + port. */ -#define _MAX_ADDRESS_LENGTH 25 +#define MAX_ADDRESS_LENGTH 25 /* Use a big number to represent no event happened in defender. */ -#define _NO_EVENT 10000 +#define NO_EVENT 10000 /* Define a decoder based on chosen format. */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - #define _Decoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ + #define _decoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ #elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - #define _Decoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ + #define _decoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ #endif @@ -84,8 +84,8 @@ static IotNetworkCredentialsOpenssl_t _credential = IOT_TEST_NETWORK_CREDENTIALS extern const IotNetworkInterface_t _IotNetworkOpensslMetrics; /*------------------ global variables -----------------------------*/ -static uint8_t _payloadBuffer[ _PAYLOAD_MAX_SIZE ]; -static uint8_t _metricsBuffer[ _METRICS_MAX_SIZE ]; +static uint8_t _payloadBuffer[ PAYLOAD_MAX_SIZE ]; +static uint8_t _metricsBuffer[ METRICS_MAX_SIZE ]; static AwsIotDefenderCallback_t _testCallback; @@ -183,7 +183,7 @@ TEST_TEAR_DOWN( Full_DEFENDER ) if( ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_ACCEPTED ) || ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) ) { - sleep( _DEFENDER_PUBLISH_INTERVAL_SECONDS ); + sleep( DEFENDER_PUBLISH_INTERVAL_SECONDS ); } IotSemaphore_Destroy( &_callbackInfoSem ); @@ -319,7 +319,7 @@ TEST( Full_DEFENDER, SetMetrics_with_invalid_metrics_group ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); /* Assert metrics flag in each metrics group remains 0. */ - for( i = 0; i < _DEFENDER_METRICS_GROUP_COUNT; i++ ) + for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) { TEST_ASSERT_EQUAL( 0, _AwsIotDefenderMetrics.metricsFlag[ i ] ); } @@ -364,7 +364,7 @@ TEST( Full_DEFENDER, Start_with_wrong_network_information ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - _assertEvent( AWS_IOT_DEFENDER_FAILURE_MQTT, _WAIT_STATE_TOTAL_SECONDS ); + _assertEvent( AWS_IOT_DEFENDER_FAILURE_MQTT, WAIT_STATE_TOTAL_SECONDS ); } TEST( Full_DEFENDER, Start_should_return_success ) @@ -402,7 +402,7 @@ TEST( Full_DEFENDER, Metrics_empty_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); _verifyTcpConections( 0 ); @@ -430,7 +430,7 @@ TEST( Full_DEFENDER, Metrics_TCP_connections_all_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); _verifyTcpConections( 1, pIotAddress ); @@ -455,7 +455,7 @@ TEST( Full_DEFENDER, Metrics_TCP_connections_total_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); _verifyTcpConections( 1 ); @@ -483,7 +483,7 @@ TEST( Full_DEFENDER, Metrics_TCP_connections_remote_addr_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); _verifyTcpConections( 1, pIotAddress ); @@ -506,7 +506,7 @@ TEST( Full_DEFENDER, Restart_and_updated_metrics_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); _verifyTcpConections( 1, pIotAddress ); @@ -516,7 +516,7 @@ TEST( Full_DEFENDER, Restart_and_updated_metrics_are_published ) /* Reset _callbackInfo before restarting. */ _resetCalbackInfo(); - sleep( _DEFENDER_PUBLISH_INTERVAL_SECONDS ); + sleep( DEFENDER_PUBLISH_INTERVAL_SECONDS ); TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ) ); @@ -527,7 +527,7 @@ TEST( Full_DEFENDER, Restart_and_updated_metrics_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); /* Wait certain time for _reportAccepted to be true. */ - _waitForMetricsAccepted( _WAIT_STATE_TOTAL_SECONDS ); + _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); _verifyTcpConections( 1, pIotAddress ); @@ -608,8 +608,8 @@ static void _publishMetricsNotNeeded( void ) static void _resetCalbackInfo( void ) { /* Clean data buffer. */ - memset( _payloadBuffer, 0, _PAYLOAD_MAX_SIZE ); - memset( _metricsBuffer, 0, _METRICS_MAX_SIZE ); + memset( _payloadBuffer, 0, PAYLOAD_MAX_SIZE ); + memset( _metricsBuffer, 0, METRICS_MAX_SIZE ); /* Reset callback info. */ _callbackInfo = ( AwsIotDefenderCallbackInfo_t ) { @@ -617,7 +617,7 @@ static void _resetCalbackInfo( void ) .metricsReportLength = 0, .pPayload = _payloadBuffer, .payloadLength = 0, - .eventType = _NO_EVENT + .eventType = NO_EVENT }; } @@ -653,13 +653,13 @@ static void _assertRejectDueToThrottle( void ) char errorCode[ 12 ] = ""; - IotSerializerError_t error = _Decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); + IotSerializerError_t error = _decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, decoderObject.type ); - error = _Decoder.find( &decoderObject, "statusDetails", &statusDetailsObject ); + error = _decoder.find( &decoderObject, "statusDetails", &statusDetailsObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -668,7 +668,7 @@ static void _assertRejectDueToThrottle( void ) errorCodeObject.u.value.u.string.pString = ( uint8_t * ) errorCode; errorCodeObject.u.value.u.string.length = 12; - error = _Decoder.find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); + error = _decoder.find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -676,8 +676,8 @@ static void _assertRejectDueToThrottle( void ) TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) errorCodeObject.u.value.u.string.pString, "Throttled", errorCodeObject.u.value.u.string.length ) ); - _Decoder.destroy( &statusDetailsObject ); - _Decoder.destroy( &decoderObject ); + _decoder.destroy( &statusDetailsObject ); + _decoder.destroy( &decoderObject ); } /*-----------------------------------------------------------*/ @@ -705,7 +705,7 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) IotSerializerDecoderObject_t decoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerError_t error = _Decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); + IotSerializerError_t error = _decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -717,7 +717,7 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) statusObject.u.value.u.string.pString = ( uint8_t * ) status; statusObject.u.value.u.string.length = 10; - error = _Decoder.find( &decoderObject, "status", &statusObject ); + error = _decoder.find( &decoderObject, "status", &statusObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -725,8 +725,8 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) statusObject.u.value.u.string.pString, "ACCEPTED", statusObject.u.value.u.string.length ) ); - _Decoder.destroy( &statusObject ); - _Decoder.destroy( &decoderObject ); + _decoder.destroy( &statusObject ); + _decoder.destroy( &decoderObject ); } /*-----------------------------------------------------------*/ @@ -736,13 +736,13 @@ static void _verifyMetricsCommon( void ) TEST_ASSERT_NOT_NULL( _callbackInfo.pMetricsReport ); TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.metricsReportLength ); - IotSerializerError_t error = _Decoder.init( &_decoderObject, _callbackInfo.pMetricsReport, _callbackInfo.metricsReportLength ); + IotSerializerError_t error = _decoder.init( &_decoderObject, _callbackInfo.pMetricsReport, _callbackInfo.metricsReportLength ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, _decoderObject.type ); - error = _Decoder.find( &_decoderObject, "metrics", &_metricsObject ); + error = _decoder.find( &_decoderObject, "metrics", &_metricsObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -761,7 +761,7 @@ static void _verifyTcpConections( int total, /* Assert find a "tcp_connections" map in "metrics" */ IotSerializerDecoderObject_t tcpConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerError_t error = _Decoder.find( &_metricsObject, "tcp_connections", &tcpConnObject ); + IotSerializerError_t error = _decoder.find( &_metricsObject, "tcp_connections", &tcpConnObject ); /* If any TCP connections flag is specified. */ if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_ALL ) @@ -773,7 +773,7 @@ static void _verifyTcpConections( int total, IotSerializerDecoderObject_t estConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _Decoder.find( &tcpConnObject, "established_connections", &estConnObject ); + error = _decoder.find( &tcpConnObject, "established_connections", &estConnObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) { @@ -784,7 +784,7 @@ static void _verifyTcpConections( int total, IotSerializerDecoderObject_t totalObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _Decoder.find( &estConnObject, "total", &totalObject ); + error = _decoder.find( &estConnObject, "total", &totalObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) { @@ -804,7 +804,7 @@ static void _verifyTcpConections( int total, IotSerializerDecoderObject_t connsObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; IotSerializerDecoderIterator_t connIterator = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - error = _Decoder.find( &estConnObject, "connections", &connsObject ); + error = _decoder.find( &estConnObject, "connections", &connsObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) { @@ -812,7 +812,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_ARRAY, connsObject.type ); - error = _Decoder.stepIn( &connsObject, &connIterator ); + error = _decoder.stepIn( &connsObject, &connIterator ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); /* Create argument list for expected remote addresses. */ @@ -823,13 +823,13 @@ static void _verifyTcpConections( int total, { /* Assert find one "connection" map in "connections" */ IotSerializerDecoderObject_t connMap = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _Decoder.get( connIterator, &connMap ); + error = _decoder.get( connIterator, &connMap ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, connMap.type ); IotSerializerDecoderObject_t remoteAddrObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _Decoder.find( &connMap, "remote_addr", &remoteAddrObject ); + error = _decoder.find( &connMap, "remote_addr", &remoteAddrObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) { @@ -849,17 +849,17 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - error = _Decoder.next( connIterator ); + error = _decoder.next( connIterator ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - _Decoder.destroy( &connMap ); + _decoder.destroy( &connMap ); } va_end( valist ); - TEST_ASSERT_TRUE( _Decoder.isEndOfContainer( connIterator ) ); + TEST_ASSERT_TRUE( _decoder.isEndOfContainer( connIterator ) ); - _Decoder.stepOut( connIterator, &connsObject ); + _decoder.stepOut( connIterator, &connsObject ); } else { @@ -867,7 +867,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - _Decoder.destroy( &connsObject ); + _decoder.destroy( &connsObject ); } else { @@ -875,7 +875,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - _Decoder.destroy( &estConnObject ); + _decoder.destroy( &estConnObject ); } else { @@ -883,16 +883,16 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - _Decoder.destroy( &tcpConnObject ); - _Decoder.destroy( &_metricsObject ); - _Decoder.destroy( &_decoderObject ); + _decoder.destroy( &tcpConnObject ); + _decoder.destroy( &_metricsObject ); + _decoder.destroy( &_decoderObject ); } /*-----------------------------------------------------------*/ static char * _getIotAddress( void ) { - static char iotAddress[ _MAX_ADDRESS_LENGTH ]; + static char iotAddress[ MAX_ADDRESS_LENGTH ]; struct addrinfo * pListHead = NULL; char * pIotAddressIp = NULL; diff --git a/tests/iot_config.h b/tests/iot_config.h index 3f09781249..887b9badab 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -189,7 +189,7 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; /* Configure code coverage testing if enabled. */ #if IOT_TEST_COVERAGE == 1 /* Define the empty else marker if test coverage is enabled. */ - #define _EMPTY_ELSE_MARKER IOT_TEST_ASM_VOLATILE( "nop" ) + #define EMPTY_ELSE_MARKER IOT_TEST_ASM_VOLATILE( "nop" ) /* Define a custom logging puts function. This function allows coverage * testing of logging functions, but prevents excessive logs from being diff --git a/tests/mqtt/access/iot_test_access_mqtt.h b/tests/mqtt/access/iot_test_access_mqtt.h index b1294f1749..ffc801cf34 100644 --- a/tests/mqtt/access/iot_test_access_mqtt.h +++ b/tests/mqtt/access/iot_test_access_mqtt.h @@ -44,15 +44,15 @@ _mqttConnection_t * IotTestMqtt_createMqttConnection( bool awsIotMqttMode, /* * Macros for reading the high and low byte of a 2-byte unsigned int. */ -#define _UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ -#define _UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ +#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( x >> 8 ) ) /**< @brief Get high byte. */ +#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( x & 0x00ff ) ) /**< @brief Get low byte. */ /** * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes. * * @param[in] ptr A uint8_t* that points to the high byte. */ -#define _UINT16_DECODE( ptr ) \ +#define UINT16_DECODE( ptr ) \ ( uint16_t ) ( ( ( ( uint16_t ) ( *( ptr ) ) ) << 8 ) | \ ( ( uint16_t ) ( *( ptr + 1 ) ) ) ) diff --git a/tests/mqtt/iot_tests_mqtt.c b/tests/mqtt/iot_tests_mqtt.c index b4434059c1..2151e1defc 100644 --- a/tests/mqtt/iot_tests_mqtt.c +++ b/tests/mqtt/iot_tests_mqtt.c @@ -64,7 +64,7 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { - _IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Set a signal handler for segmentation faults and assertion failures. */ @@ -73,12 +73,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -105,10 +105,10 @@ int main( int argc, /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 33d3754092..20cc4e5019 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -62,17 +62,17 @@ /* The tests in this file run for a long time, so set up logging to track their * progress. */ -#define _LIBRARY_LOG_LEVEL IOT_LOG_INFO -#define _LIBRARY_LOG_NAME ( "MQTT_STRESS" ) +#define LIBRARY_LOG_LEVEL IOT_LOG_INFO +#define LIBRARY_LOG_NAME ( "MQTT_STRESS" ) #include "iot_logging_setup.h" /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ #if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true + #define AWS_IOT_MQTT_SERVER true #else - #define _AWS_IOT_MQTT_SERVER false + #define AWS_IOT_MQTT_SERVER false /* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ #undef IOT_MQTT_CONNECT_INFO_INITIALIZER @@ -122,21 +122,21 @@ /** * @brief Number of test topic names. */ -#define _TEST_TOPIC_NAME_COUNT ( 8 ) +#define TEST_TOPIC_NAME_COUNT ( 8 ) /** * @brief The maximum number of PUBLISH messages that will be received by a * single test. */ -#define _MAX_RECEIVED_PUBLISH ( IOT_TEST_MQTT_THREADS * IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) +#define MAX_RECEIVED_PUBLISH ( IOT_TEST_MQTT_THREADS * IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) /** * @brief The maximum length of an MQTT client identifier. */ #ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) ) + #define CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) ) #else - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) + #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) #endif /*-----------------------------------------------------------*/ @@ -197,7 +197,7 @@ static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; * * For convenience, all topic names are the same length. */ -static const char * const _pTopicNames[ _TEST_TOPIC_NAME_COUNT ] = +static const char * const _pTopicNames[ TEST_TOPIC_NAME_COUNT ] = { IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0", IOT_TEST_MQTT_TOPIC_PREFIX "/stress/1", @@ -226,7 +226,7 @@ static IotSemaphore_t receivedPublishCounter; /** * @brief Buffer holding the client identifier used for the tests. */ -static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; +static char _pClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; /*-----------------------------------------------------------*/ @@ -337,7 +337,7 @@ static void * _publishThread( void * pArgument ) for( i = 0; i < pParams->publishLimit; ) { /* Choose a topic name. */ - publishInfo.pTopicName = _pTopicNames[ i % _TEST_TOPIC_NAME_COUNT ]; + publishInfo.pTopicName = _pTopicNames[ i % TEST_TOPIC_NAME_COUNT ]; /* PUBLISH the message. */ status = IotMqtt_Publish( _mqttConnection, @@ -404,7 +404,7 @@ TEST_SETUP( MQTT_Stress ) { int32_t i = 0; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t pSubscriptions[ _TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t pSubscriptions[ TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Initialize SDK. */ if( IotSdk_Init() == false ) @@ -430,19 +430,19 @@ TEST_SETUP( MQTT_Stress ) /* Create the publish counter semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &receivedPublishCounter, 0, - _MAX_RECEIVED_PUBLISH ) ); + MAX_RECEIVED_PUBLISH ) ); /* Generate a new, unique client identifier based on the time if no client * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER ( void ) snprintf( _pClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, + CLIENT_IDENTIFIER_MAX_LENGTH, "iot%llu", ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( _pClientIdentifier, IOT_TEST_MQTT_CLIENT_IDENTIFIER, - _CLIENT_IDENTIFIER_MAX_LENGTH ); + CLIENT_IDENTIFIER_MAX_LENGTH ); #endif /* Set the MQTT network setup parameters. */ @@ -462,7 +462,7 @@ TEST_SETUP( MQTT_Stress ) connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); /* Set the members of the subscriptions */ - for( i = 0; i < _TEST_TOPIC_NAME_COUNT; i++ ) + for( i = 0; i < TEST_TOPIC_NAME_COUNT; i++ ) { pSubscriptions[ i ].pTopicFilter = _pTopicNames[ i ]; pSubscriptions[ i ].topicFilterLength = _topicNameLength; @@ -481,7 +481,7 @@ TEST_SETUP( MQTT_Stress ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_TimedSubscribe( _mqttConnection, pSubscriptions, - _TEST_TOPIC_NAME_COUNT, + TEST_TOPIC_NAME_COUNT, 0, IOT_TEST_MQTT_TIMEOUT_MS ) ); } diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 762bdaad98..0654e591ef 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -67,9 +67,9 @@ * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ #if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true + #define AWS_IOT_MQTT_SERVER true #else - #define _AWS_IOT_MQTT_SERVER false + #define AWS_IOT_MQTT_SERVER false #endif /** @@ -82,9 +82,9 @@ * obligated to accept plus a NULL terminator. */ #ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) + #define CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + 4 ) #else - #define _CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) + #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) #endif /*-----------------------------------------------------------*/ @@ -192,7 +192,7 @@ static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; /** * @brief Buffer holding the client identifier used for the tests. */ -static char _pClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; +static char _pClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; /* * Track if the serializer overrides were called for a test. @@ -486,7 +486,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) if( TEST_PROTECT() ) { /* Set the members of the MQTT connection info. */ - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -613,13 +613,13 @@ TEST_SETUP( MQTT_System ) * identifier is defined. Otherwise, copy the provided client identifier. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER ( void ) snprintf( _pClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, + CLIENT_IDENTIFIER_MAX_LENGTH, "iot%llu", ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( _pClientIdentifier, IOT_TEST_MQTT_CLIENT_IDENTIFIER, - _CLIENT_IDENTIFIER_MAX_LENGTH ); + CLIENT_IDENTIFIER_MAX_LENGTH ); #endif /* Set the overrides for the default serializers. */ @@ -722,7 +722,7 @@ TEST( MQTT_System, SubscribePublishAsync ) subscription.callback.pCallbackContext = &publishWaitSem; /* Initialize members of the connect info. */ - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -823,7 +823,7 @@ TEST( MQTT_System, LastWillAndTestament ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttNetworkInfo_t lwtNetworkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - char pLwtListenerClientIdentifier[ _CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + char pLwtListenerClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; IotMqttConnection_t lwtListener = IOT_MQTT_CONNECTION_INITIALIZER; IotMqttConnectInfo_t lwtConnectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER, connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; @@ -837,13 +837,13 @@ TEST( MQTT_System, LastWillAndTestament ) /* Generate a client identifier for LWT listener. */ #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER ( void ) snprintf( pLwtListenerClientIdentifier, - _CLIENT_IDENTIFIER_MAX_LENGTH, + CLIENT_IDENTIFIER_MAX_LENGTH, "iotlwt%llu", ( long long unsigned int ) IotClock_GetTimeMs() ); #else ( void ) strncpy( pLwtListenerClientIdentifier, IOT_TEST_MQTT_CLIENT_IDENTIFIER "LWT", - _CLIENT_IDENTIFIER_MAX_LENGTH ); + CLIENT_IDENTIFIER_MAX_LENGTH ); #endif /* Establish an independent MQTT over TCP connection to receive a Last @@ -857,7 +857,7 @@ TEST( MQTT_System, LastWillAndTestament ) lwtNetworkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - lwtConnectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + lwtConnectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; lwtConnectInfo.cleanSession = true; lwtConnectInfo.pClientIdentifier = pLwtListenerClientIdentifier; lwtConnectInfo.clientIdentifierLength = ( uint16_t ) strlen( lwtConnectInfo.pClientIdentifier ); @@ -886,7 +886,7 @@ TEST( MQTT_System, LastWillAndTestament ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Create a connection that requests the LWT. */ - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = true; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -969,7 +969,7 @@ TEST( MQTT_System, RestorePreviousSession ) _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /* Re-establish the MQTT connection with a previous session. */ - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; @@ -1045,7 +1045,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) IotMqttOperation_t pPublishOperation[ 3 ] = { IOT_MQTT_OPERATION_INITIALIZER }; /* Set the client identifier and length. */ - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -1120,7 +1120,7 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); @@ -1192,7 +1192,7 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 00bfd708d7..80364936c4 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -52,45 +52,45 @@ * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ #if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true + #define AWS_IOT_MQTT_SERVER true #else - #define _AWS_IOT_MQTT_SERVER false + #define AWS_IOT_MQTT_SERVER false #endif /** * @brief Timeout to use for the tests. This can be short, but should allow time * for other threads to run. */ -#define _TIMEOUT_MS ( 400 ) +#define TIMEOUT_MS ( 400 ) /** * @brief A short keep-alive interval to use for the keep-alive tests. It may be * shorter than the minimum 1 second specified by the MQTT spec. */ -#define _SHORT_KEEP_ALIVE_MS ( 100 ) +#define SHORT_KEEP_ALIVE_MS ( 100 ) /** * @brief The number of times the periodic keep-alive should run. */ -#define _KEEP_ALIVE_COUNT ( 10 ) +#define KEEP_ALIVE_COUNT ( 10 ) /* * Client identifier and length to use for the MQTT API tests. */ -#define _CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ -#define _CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( _CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ +#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ /* * Will topic name and length to use for the MQTT API tests. */ -#define _TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ -#define _TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( _TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ /** * @brief A non-NULL function pointer to use for subscription callback. This * "function" should cause a crash if actually called. */ -#define _SUBSCRIPTION_CALLBACK \ +#define SUBSCRIPTION_CALLBACK \ ( ( void ( * )( void *, \ IotMqttCallbackParam_t * ) ) 0x01 ) @@ -102,20 +102,20 @@ * completion. Therefore, this function simply uses the value below as an estimate * for the maximum number of times DISCONNECT will use malloc. */ -#define _DISCONNECT_MALLOC_LIMIT ( 20 ) +#define DISCONNECT_MALLOC_LIMIT ( 20 ) /* * Constants that affect the behavior of #TEST_MQTT_Unit_API_PublishDuplicates. */ -#define _DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ -#define _DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ -#define _DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. +#define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ +#define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ +#define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. * Duplicates are sent using an exponential backoff strategy. */ /** @brief The minimum amount of time the test can take. */ -#define _DUP_CHECK_MINIMUM_WAIT \ - ( _DUP_CHECK_RETRY_MS + \ - 2 * _DUP_CHECK_RETRY_MS + \ - 4 * _DUP_CHECK_RETRY_MS + \ +#define DUP_CHECK_MINIMUM_WAIT \ + ( DUP_CHECK_RETRY_MS + \ + 2 * DUP_CHECK_RETRY_MS + \ + 4 * DUP_CHECK_RETRY_MS + \ IOT_MQTT_RESPONSE_WAIT_MS ) /*-----------------------------------------------------------*/ @@ -166,7 +166,7 @@ static void _incomingPingresp( void * pArgument ) ( void ) pArgument; /* This test will not work if the response wait time is too small. */ - #if IOT_MQTT_RESPONSE_WAIT_MS < ( 2 * _SHORT_KEEP_ALIVE_MS + 100 ) + #if IOT_MQTT_RESPONSE_WAIT_MS < ( 2 * SHORT_KEEP_ALIVE_MS + 100 ) #error "IOT_MQTT_RESPONSE_WAIT_MS too small for keep-alive tests." #endif @@ -178,16 +178,16 @@ static void _incomingPingresp( void * pArgument ) invokeCount++; /* Sleep to simulate the network round-trip time. */ - IotClock_SleepMs( 2 * _SHORT_KEEP_ALIVE_MS ); + IotClock_SleepMs( 2 * SHORT_KEEP_ALIVE_MS ); /* Respond with a PINGRESP. */ - if( invokeCount <= _KEEP_ALIVE_COUNT ) + if( invokeCount <= KEEP_ALIVE_COUNT ) { /* Log a status with Unity, as this test may take a while. */ UnityPrint( "KeepAlivePeriodic " ); UnityPrintNumber( ( UNITY_INT ) invokeCount ); UnityPrint( " of " ); - UnityPrintNumber( ( UNITY_INT ) _KEEP_ALIVE_COUNT ); + UnityPrintNumber( ( UNITY_INT ) KEEP_ALIVE_COUNT ); UnityPrint( " DONE at " ); UnityPrintNumber( ( UNITY_INT ) IotClock_GetTimeMs() ); UnityPrint( " ms" ); @@ -320,19 +320,19 @@ static size_t _dupChecker( void * pSendContext, /* Declare the remaining variables required to check packet identifier * for the AWS IoT MQTT server. */ - #if _AWS_IOT_MQTT_SERVER == true + #if AWS_IOT_MQTT_SERVER == true static uint16_t lastPacketIdentifier = 0; _mqttPacket_t publishPacket = { .u.pMqttConnection = NULL }; _mqttOperation_t publishOperation = { .link = { 0 } }; publishPacket.type = publishFlags; publishPacket.u.pIncomingPublish = &publishOperation; - publishPacket.remainingLength = 8 + _TEST_TOPIC_NAME_LENGTH; + publishPacket.remainingLength = 8 + TEST_TOPIC_NAME_LENGTH; publishPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - publishPacket.remainingLength ); #endif /* Ignore any MQTT packet that's not a PUBLISH. */ - if( ( publishFlags & 0xf0 ) != _MQTT_PACKET_TYPE_PUBLISH ) + if( ( publishFlags & 0xf0 ) != MQTT_PACKET_TYPE_PUBLISH ) { return messageLength; } @@ -342,7 +342,7 @@ static size_t _dupChecker( void * pSendContext, /* Check how many times this function has been called. */ if( runCount == 1 ) { - #if _AWS_IOT_MQTT_SERVER == true + #if AWS_IOT_MQTT_SERVER == true /* Deserialize the PUBLISH to read the packet identifier. */ if( _IotMqtt_DeserializePublish( &publishPacket ) != IOT_MQTT_SUCCESS ) { @@ -352,20 +352,20 @@ static size_t _dupChecker( void * pSendContext, { lastPacketIdentifier = publishPacket.packetIdentifier; } - #else /* if _AWS_IOT_MQTT_SERVER == true */ + #else /* if AWS_IOT_MQTT_SERVER == true */ /* DUP flag should not be set on this function's first run. */ if( ( publishFlags & 0x08 ) == 0x08 ) { status = false; } - #endif /* if _AWS_IOT_MQTT_SERVER == true */ + #endif /* if AWS_IOT_MQTT_SERVER == true */ } else { /* Only check the packet again if the previous run checks passed. */ if( status == true ) { - #if _AWS_IOT_MQTT_SERVER == true + #if AWS_IOT_MQTT_SERVER == true /* Deserialize the PUBLISH to read the packet identifier. */ if( _IotMqtt_DeserializePublish( &publishPacket ) != IOT_MQTT_SUCCESS ) { @@ -377,17 +377,17 @@ static size_t _dupChecker( void * pSendContext, status = ( publishPacket.packetIdentifier != lastPacketIdentifier ); lastPacketIdentifier = publishPacket.packetIdentifier; } - #else /* if _AWS_IOT_MQTT_SERVER == true */ + #else /* if AWS_IOT_MQTT_SERVER == true */ /* DUP flag should be set when this function runs again. */ if( ( publishFlags & 0x08 ) != 0x08 ) { status = false; } - #endif /* if _AWS_IOT_MQTT_SERVER == true */ + #endif /* if AWS_IOT_MQTT_SERVER == true */ } /* Write the check result on the last expected run of this function. */ - if( runCount == _DUP_CHECK_RETRY_LIMIT ) + if( runCount == DUP_CHECK_RETRY_LIMIT ) { *pDupCheckResult = status; } @@ -408,7 +408,7 @@ static size_t _receivePingresp( void * pReceiveContext, { size_t bytesReceived = 0; static size_t receiveIndex = 0; - const uint8_t pPingresp[ 2 ] = { _MQTT_PACKET_TYPE_PINGRESP, 0x00 }; + const uint8_t pPingresp[ 2 ] = { MQTT_PACKET_TYPE_PINGRESP, 0x00 }; /* Silence warnings about unused parameters. */ ( void ) pReceiveContext; @@ -578,7 +578,7 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) _mqttOperation_t * pOperation = NULL; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -639,7 +639,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) IotSemaphore_t waitSem; /* An arbitrary MQTT packet for this test. */ - static uint8_t pPacket[ 2 ] = { _MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; + static uint8_t pPacket[ 2 ] = { MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -650,7 +650,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) _networkInterface.send = _sendDelay; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -717,18 +717,18 @@ TEST( MQTT_Unit_API, ConnectParameters ) /* Check that the network interface is validated. */ status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Check that the connection info is validated. */ status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; /* Connect with bad previous session subscription. */ connectInfo.cleanSession = false; @@ -736,18 +736,18 @@ TEST( MQTT_Unit_API, ConnectParameters ) connectInfo.previousSubscriptionCount = 1; status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Connect with bad subscription count. */ connectInfo.previousSubscriptionCount = 0; - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); connectInfo.previousSubscriptionCount = 1; @@ -756,18 +756,18 @@ TEST( MQTT_Unit_API, ConnectParameters ) connectInfo.pWillInfo = &willInfo; status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - willInfo.pTopicName = _TEST_TOPIC_NAME; - willInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + willInfo.pTopicName = TEST_TOPIC_NAME; + willInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; /* Check that a will message longer than 65535 is not allowed. */ willInfo.pPayload = ""; willInfo.payloadLength = 65536; status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); willInfo.payloadLength = 0; @@ -797,8 +797,8 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) _networkInterface.close = _close; connectInfo.keepAliveSeconds = 100; connectInfo.cleanSession = true; - connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; for( i = 0; ; i++ ) { @@ -808,7 +808,7 @@ TEST( MQTT_Unit_API, ConnectMallocFail ) * this call. */ status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); /* If the return value is timeout, then all memory allocation succeeded @@ -843,11 +843,11 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) _networkInterface.close = _close; connectInfo.cleanSession = false; connectInfo.keepAliveSeconds = 100; - connectInfo.pClientIdentifier = _CLIENT_IDENTIFIER; - connectInfo.clientIdentifierLength = _CLIENT_IDENTIFIER_LENGTH; - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; @@ -860,7 +860,7 @@ TEST( MQTT_Unit_API, ConnectRestoreSessionMallocFail ) * various times during this call. */ status = IotMqtt_Connect( &_networkInfo, &connectInfo, - _TIMEOUT_MS, + TIMEOUT_MS, &_pMqttConnection ); /* If the return value is timeout, then all memory allocation succeeded @@ -895,13 +895,13 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) _networkInfo.disconnectCallback.pCallbackContext = &expectedReason; _networkInfo.disconnectCallback.function = _disconnectCallback; - for( i = 0; i < _DISCONNECT_MALLOC_LIMIT; i++ ) + for( i = 0; i < DISCONNECT_MALLOC_LIMIT; i++ ) { /* Allow unlimited use of malloc during connection initialization. */ UnityMalloc_MakeMallocFailAfterCount( -1 ); /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -936,7 +936,7 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) _networkInterface.send = _sendSuccess; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -946,8 +946,8 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) /* Check that the publish info is validated. */ status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; /* Check that a QoS 0 publish is refused if a notification is requested. */ status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishOperation ); @@ -979,14 +979,14 @@ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) _networkInterface.send = _sendSuccess; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the necessary members of publish info. */ - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; if( TEST_PROTECT() ) { @@ -1032,15 +1032,15 @@ TEST( MQTT_Unit_API, PublishQoS1 ) _networkInterface.send = _sendSuccess; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; if( TEST_PROTECT() ) { @@ -1077,7 +1077,7 @@ TEST( MQTT_Unit_API, PublishQoS1 ) * 1 PUBLISH to be cleaned up. */ if( status == IOT_MQTT_STATUS_PENDING ) { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( publishOperation, _TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( publishOperation, TIMEOUT_MS ) ); break; } @@ -1113,7 +1113,7 @@ TEST( MQTT_Unit_API, PublishDuplicates ) _networkInterface.send = _dupChecker; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -1124,12 +1124,12 @@ TEST( MQTT_Unit_API, PublishDuplicates ) /* Set the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = _TEST_TOPIC_NAME; - publishInfo.topicNameLength = _TEST_TOPIC_NAME_LENGTH; + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; publishInfo.pPayload = "test"; publishInfo.payloadLength = 4; - publishInfo.retryMs = _DUP_CHECK_RETRY_MS; - publishInfo.retryLimit = _DUP_CHECK_RETRY_LIMIT; + publishInfo.retryMs = DUP_CHECK_RETRY_MS; + publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; startTime = IotClock_GetTimeMs(); @@ -1146,13 +1146,13 @@ TEST( MQTT_Unit_API, PublishDuplicates ) /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is * expected. */ TEST_ASSERT_EQUAL( IOT_MQTT_RETRY_NO_RESPONSE, - IotMqtt_Wait( publishOperation, _DUP_CHECK_TIMEOUT ) ); + IotMqtt_Wait( publishOperation, DUP_CHECK_TIMEOUT ) ); /* Check the result of the DUP check. */ TEST_ASSERT_EQUAL_INT( true, dupCheckResult ); /* Check that at least the minimum wait time elapsed. */ - TEST_ASSERT_TRUE( startTime + _DUP_CHECK_MINIMUM_WAIT <= IotClock_GetTimeMs() ); + TEST_ASSERT_TRUE( startTime + DUP_CHECK_MINIMUM_WAIT <= IotClock_GetTimeMs() ); } /* Clean up MQTT connection. */ @@ -1178,7 +1178,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -1200,9 +1200,9 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) &subscribeOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; /* A reference must be provided for a waitable SUBSCRIBE. */ status = IotMqtt_Subscribe( _pMqttConnection, @@ -1241,15 +1241,15 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) _networkInterface.send = _sendSuccess; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the necessary members of the subscription. */ - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; if( TEST_PROTECT() ) { @@ -1270,7 +1270,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) * the SUBSCRIBE to be cleaned up. */ if( status == IOT_MQTT_STATUS_PENDING ) { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( subscribeOperation, _TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( subscribeOperation, TIMEOUT_MS ) ); break; } @@ -1303,15 +1303,15 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) _networkInterface.send = _sendSuccess; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set the necessary members of the subscription. */ - subscription.pTopicFilter = _TEST_TOPIC_NAME; - subscription.topicFilterLength = _TEST_TOPIC_NAME_LENGTH; - subscription.callback.function = _SUBSCRIPTION_CALLBACK; + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; if( TEST_PROTECT() ) { @@ -1332,7 +1332,7 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) * the UNSUBSCRIBE to be cleaned up. */ if( status == IOT_MQTT_STATUS_PENDING ) { - TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( unsubscribeOperation, _TIMEOUT_MS ) ); + TEST_ASSERT_EQUAL( IOT_MQTT_TIMEOUT, IotMqtt_Wait( unsubscribeOperation, TIMEOUT_MS ) ); break; } @@ -1359,8 +1359,8 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) IotMqttDisconnectReason_t expectedReason = IOT_MQTT_KEEP_ALIVE_TIMEOUT; /* An estimate for the amount of time this test requires. */ - const uint32_t sleepTimeMs = ( _KEEP_ALIVE_COUNT * _SHORT_KEEP_ALIVE_MS ) + - ( IOT_MQTT_RESPONSE_WAIT_MS * _KEEP_ALIVE_COUNT ) + 1500; + const uint32_t sleepTimeMs = ( KEEP_ALIVE_COUNT * SHORT_KEEP_ALIVE_MS ) + + ( IOT_MQTT_RESPONSE_WAIT_MS * KEEP_ALIVE_COUNT ) + 1500; /* Print a newline so this test may log its status. */ UNITY_PRINT_EOL(); @@ -1373,14 +1373,14 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) _networkInfo.disconnectCallback.function = _disconnectCallback; /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 1 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set a short keep-alive interval so this test runs faster. */ - _pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; - _pMqttConnection->nextKeepAliveMs = _SHORT_KEEP_ALIVE_MS; + _pMqttConnection->keepAliveMs = SHORT_KEEP_ALIVE_MS; + _pMqttConnection->nextKeepAliveMs = SHORT_KEEP_ALIVE_MS; /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, @@ -1395,7 +1395,7 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); /* Check the counters for PINGREQ send and close. */ - TEST_ASSERT_EQUAL_INT32( _KEEP_ALIVE_COUNT + 1, _pingreqSendCount ); + TEST_ASSERT_EQUAL_INT32( KEEP_ALIVE_COUNT + 1, _pingreqSendCount ); TEST_ASSERT_EQUAL_INT32( 2, _closeCount ); /* Check that the disconnect callback was invoked once (with reason @@ -1421,7 +1421,7 @@ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) if( TEST_PROTECT() ) { /* Create a new MQTT connection. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &_networkInfo, 1 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -1430,8 +1430,8 @@ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) _pMqttConnection->pNetworkConnection = &waitSem; /* Set a short keep-alive interval so this test runs faster. */ - _pMqttConnection->keepAliveMs = _SHORT_KEEP_ALIVE_MS; - _pMqttConnection->nextKeepAliveMs = _SHORT_KEEP_ALIVE_MS; + _pMqttConnection->keepAliveMs = SHORT_KEEP_ALIVE_MS; + _pMqttConnection->nextKeepAliveMs = SHORT_KEEP_ALIVE_MS; /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 796b856c73..1979069c4e 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -49,9 +49,9 @@ * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ #if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true + #define AWS_IOT_MQTT_SERVER true #else - #define _AWS_IOT_MQTT_SERVER false + #define AWS_IOT_MQTT_SERVER false #endif /** @brief Default CONNACK packet for the receive tests. */ @@ -91,22 +91,22 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Topic name and filter used in the tests. */ -#define _TEST_TOPIC_NAME "/test/topic" +#define TEST_TOPIC_NAME "/test/topic" /** - * @brief Length of #_TEST_TOPIC_NAME. + * @brief Length of #TEST_TOPIC_NAME. */ -#define _TEST_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( _TEST_TOPIC_NAME ) - 1 ) ) +#define TEST_TOPIC_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /** * @brief Timeout for waiting on a PUBLISH callback. */ -#define _PUBLISH_CALLBACK_TIMEOUT ( 1000 ) +#define PUBLISH_CALLBACK_TIMEOUT ( 1000 ) /** * @brief Declare a buffer holding a packet and its size. */ -#define _DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ +#define DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ const size_t sizeName = sizeof( pTemplate ); \ ( void ) memcpy( bufferName, pTemplate, sizeName ); @@ -114,7 +114,7 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Initializer for operations in the tests. */ -#define _INITIALIZE_OPERATION( name ) \ +#define INITIALIZE_OPERATION( name ) \ { \ .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, .job = { 0 }, \ .u.operation = \ @@ -365,7 +365,7 @@ static bool _processPublish( const uint8_t * pPublish, for( i = 0; i < expectedInvokeCount; i++ ) { waitResult = IotSemaphore_TimedWait( &invokeCount, - _PUBLISH_CALLBACK_TIMEOUT ); + PUBLISH_CALLBACK_TIMEOUT ); if( waitResult == false ) { @@ -401,10 +401,10 @@ static void _publishCallback( void * pCallbackContext, pPublish->u.message.info.qos = IOT_MQTT_QOS_0; /* Check that the parameters to this function are valid. */ - if( ( _IotMqtt_ValidatePublish( _AWS_IOT_MQTT_SERVER, + if( ( _IotMqtt_ValidatePublish( AWS_IOT_MQTT_SERVER, &( pPublish->u.message.info ) ) == true ) && - ( pPublish->u.message.info.topicNameLength == _TEST_TOPIC_LENGTH ) && - ( strncmp( _TEST_TOPIC_NAME, pPublish->u.message.info.pTopicName, _TEST_TOPIC_LENGTH ) == 0 ) ) + ( pPublish->u.message.info.topicNameLength == TEST_TOPIC_LENGTH ) && + ( strncmp( TEST_TOPIC_NAME, pPublish->u.message.info.pTopicName, TEST_TOPIC_LENGTH ) == 0 ) ) { IotSemaphore_Post( pInvokeCount ); } @@ -545,7 +545,7 @@ TEST_SETUP( MQTT_Unit_Receive ) networkInfo.disconnectCallback.function = _disconnectCallback; /* Initialize the MQTT connection used by the tests. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -554,8 +554,8 @@ TEST_SETUP( MQTT_Unit_Receive ) _pMqttConnection->pSerializer = &serializer; /* Set the members of the subscription. */ - _subscription.pTopicFilter = _TEST_TOPIC_NAME; - _subscription.topicFilterLength = _TEST_TOPIC_LENGTH; + _subscription.pTopicFilter = TEST_TOPIC_NAME; + _subscription.topicFilterLength = TEST_TOPIC_LENGTH; _subscription.callback.function = _publishCallback; /* Add the subscription to the MQTT connection. */ @@ -685,7 +685,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.pData = pRemainingLength; receiveContext.dataLength = 4; - TEST_ASSERT_EQUAL( _MQTT_REMAINING_LENGTH_INVALID, + TEST_ASSERT_EQUAL( MQTT_REMAINING_LENGTH_INVALID, _IotMqtt_GetRemainingLength( &receiveContext, &_networkInterface ) ); } @@ -700,7 +700,7 @@ TEST( MQTT_Unit_Receive, DecodeRemainingLength ) receiveContext.pData = pRemainingLength; receiveContext.dataLength = 4; - TEST_ASSERT_EQUAL( _MQTT_REMAINING_LENGTH_INVALID, + TEST_ASSERT_EQUAL( MQTT_REMAINING_LENGTH_INVALID, _IotMqtt_GetRemainingLength( &receiveContext, &_networkInterface ) ); } @@ -753,7 +753,7 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) TEST( MQTT_Unit_Receive, ConnackValid ) { uint8_t i = 0; - _mqttOperation_t connect = _INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); + _mqttOperation_t connect = INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -764,7 +764,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Even though no CONNECT is in the receive queue, 4 bytes should still be * processed (should not crash). */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, connackSize, @@ -773,7 +773,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Process a valid, successful CONNACK with no SP flag. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, @@ -783,7 +783,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Process a valid, successful CONNACK with SP flag set. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 2 ] = 0x01; _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, @@ -795,7 +795,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) /* Check each of the CONNACK failure codes, which range from 1 to 5. */ for( i = 0x01; i < 0x06; i++ ) { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); /* Set the CONNECT state and code. */ _operationResetAndPush( &connect ); @@ -823,7 +823,7 @@ TEST( MQTT_Unit_Receive, ConnackValid ) */ TEST( MQTT_Unit_Receive, ConnackInvalid ) { - _mqttOperation_t connect = _INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); + _mqttOperation_t connect = INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -833,7 +833,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* An incomplete CONNACK should not be processed, and no status should be set. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, @@ -849,7 +849,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* The CONNACK control packet type must be 0x20. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 0 ] = 0x21; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, @@ -865,7 +865,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* A CONNACK must have a remaining length of 2. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 1 ] = 0x03; _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, @@ -882,7 +882,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* The reserved bits in CONNACK must be 0. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 2 ] = 0x80; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, pConnack, @@ -898,7 +898,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* The fourth byte of CONNACK must be 0 if the SP flag is set. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 2 ] = 0x01; pConnack[ 3 ] = 0x01; _operationResetAndPush( &connect ); @@ -916,7 +916,7 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) /* CONNACK return codes cannot be above 5. */ { - _DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); + DECLARE_PACKET( _pConnackTemplate, pConnack, connackSize ); pConnack[ 3 ] = 0x06; _operationResetAndPush( &connect ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &connect, @@ -944,7 +944,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) { /* Process a valid QoS 0 PUBLISH. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, 1 ) ); @@ -953,7 +953,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) /* Process a valid QoS 1 PUBLISH. Prevent an attempt to send PUBACK by * making no memory available for the PUBACK. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 0 ] = 0x32; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, @@ -962,7 +962,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) /* Process a valid QoS 2 PUBLISH. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 0 ] = 0x34; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, @@ -971,7 +971,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) /* Process a valid PUBLISH with DUP flag set. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 0 ] = 0x38; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, @@ -981,7 +981,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) /* Remove the subscription. Even though there is no matching subscription, * all bytes of the PUBLISH should still be processed (should not crash). */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, &_subscription, @@ -1008,7 +1008,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Attempting to process a packet smaller than 5 bytes should result in no * bytes processed. 5 bytes is the minimum size of a PUBLISH. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, 4, 0 ) ); @@ -1022,7 +1022,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* The PUBLISH cannot have a QoS of 3. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 0 ] = 0x36; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, @@ -1037,7 +1037,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Attempt to process a PUBLISH with an invalid "Remaining length". */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 1 ] = 0xff; pPublish[ 2 ] = 0xff; pPublish[ 3 ] = 0xff; @@ -1055,7 +1055,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Attempt to process a PUBLISH where some bytes could not be received. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 2 ] = 0x03; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, @@ -1072,7 +1072,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) * spec allows. */ { /* QoS 0. */ - _DECLARE_PACKET( _pPublishTemplate, pPublish0, publish0Size ); + DECLARE_PACKET( _pPublishTemplate, pPublish0, publish0Size ); pPublish0[ 1 ] = 0x02; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish0, publish0Size, @@ -1083,7 +1083,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) _networkCloseCalled = false; /* QoS 1. */ - _DECLARE_PACKET( _pPublishTemplate, pPublish1, publish1Size ); + DECLARE_PACKET( _pPublishTemplate, pPublish1, publish1Size ); pPublish1[ 0 ] = 0x32; pPublish1[ 1 ] = 0x04; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish1, @@ -1100,7 +1100,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Attempt to process a PUBLISH with a "Remaining length" smaller than the * end of the variable header. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 1 ] = 0x0a; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, publishSize, @@ -1115,7 +1115,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) /* Attempt to process a PUBLISH with packet identifier 0. */ { - _DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); + DECLARE_PACKET( _pPublishTemplate, pPublish, publishSize ); pPublish[ 0 ] = 0x32; pPublish[ 17 ] = 0x00; TEST_ASSERT_EQUAL_INT( true, _processPublish( pPublish, @@ -1138,7 +1138,7 @@ TEST( MQTT_Unit_Receive, PublishInvalid ) */ TEST( MQTT_Unit_Receive, PubackValid ) { - _mqttOperation_t publish = _INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); + _mqttOperation_t publish = INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1149,7 +1149,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) /* Even though no PUBLISH is in the receive queue, 4 bytes should still be * processed (should not crash). */ { - _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize, @@ -1158,7 +1158,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) /* Process a valid PUBACK. */ { - _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); _operationResetAndPush( &publish ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, @@ -1181,7 +1181,7 @@ TEST( MQTT_Unit_Receive, PubackValid ) */ TEST( MQTT_Unit_Receive, PubackInvalid ) { - _mqttOperation_t publish = _INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); + _mqttOperation_t publish = INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1193,7 +1193,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* An incomplete PUBACK should not be processed, and no status should be set. */ { - _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, pubackSize - 1, @@ -1208,7 +1208,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* The PUBACK control packet type must be 0x40. */ { - _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); pPuback[ 0 ] = 0x41; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, @@ -1226,7 +1226,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* A PUBACK must have a remaining length of 2. */ { - _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); pPuback[ 1 ] = 0x03; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, @@ -1243,7 +1243,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) /* The packet identifier in PUBACK cannot be 0. No status should be set if * packet identifier 0 is received. */ { - _DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); + DECLARE_PACKET( _pPubackTemplate, pPuback, pubackSize ); pPuback[ 3 ] = 0x00; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &publish, pPuback, @@ -1275,7 +1275,7 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) TEST( MQTT_Unit_Receive, SubackValid ) { _mqttSubscription_t * pNewSubscription = NULL; - _mqttOperation_t subscribe = _INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); + _mqttOperation_t subscribe = INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); IotMqttSubscription_t pSubscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Create the wait semaphore so notifications don't crash. The value of @@ -1287,13 +1287,13 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Add 2 additional subscriptions to the MQTT connection. */ pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_1; pSubscriptions[ 0 ].callback.function = _publishCallback; - pSubscriptions[ 0 ].pTopicFilter = _TEST_TOPIC_NAME "1"; - pSubscriptions[ 0 ].topicFilterLength = _TEST_TOPIC_LENGTH + 1; + pSubscriptions[ 0 ].pTopicFilter = TEST_TOPIC_NAME "1"; + pSubscriptions[ 0 ].topicFilterLength = TEST_TOPIC_LENGTH + 1; pSubscriptions[ 1 ].qos = IOT_MQTT_QOS_1; pSubscriptions[ 1 ].callback.function = _publishCallback; - pSubscriptions[ 1 ].pTopicFilter = _TEST_TOPIC_NAME "2"; - pSubscriptions[ 1 ].topicFilterLength = _TEST_TOPIC_LENGTH + 1; + pSubscriptions[ 1 ].pTopicFilter = TEST_TOPIC_NAME "2"; + pSubscriptions[ 1 ].topicFilterLength = TEST_TOPIC_LENGTH + 1; TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _IotMqtt_AddSubscriptions( _pMqttConnection, 1, @@ -1314,7 +1314,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Even though no SUBSCRIBE is in the receive queue, all bytes of the SUBACK * should still be processed (should not crash). */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, subackSize, @@ -1324,7 +1324,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Process a valid SUBACK where all subscriptions are successful. */ { IotMqttSubscription_t currentSubscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); _operationResetAndPush( &subscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, @@ -1344,7 +1344,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Process a valid SUBACK where some subscriptions were rejected. */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 4 ] = 0x80; pSuback[ 6 ] = 0x80; _operationResetAndPush( &subscribe ); @@ -1356,8 +1356,8 @@ TEST( MQTT_Unit_Receive, SubackValid ) /* Check that rejected subscriptions were removed from the subscription * list. */ TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, - _TEST_TOPIC_NAME, - _TEST_TOPIC_LENGTH, + TEST_TOPIC_NAME, + TEST_TOPIC_LENGTH, NULL ) ); TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, pSubscriptions[ 1 ].pTopicFilter, @@ -1380,7 +1380,7 @@ TEST( MQTT_Unit_Receive, SubackValid ) */ TEST( MQTT_Unit_Receive, SubackInvalid ) { - _mqttOperation_t subscribe = _INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); + _mqttOperation_t subscribe = INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1393,7 +1393,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Attempting to process a packet smaller than 5 bytes should result in no * bytes processed. 5 bytes is the minimum size of a SUBACK. */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, 4, @@ -1408,7 +1408,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Attempt to process a SUBACK with an invalid "Remaining length". */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 1 ] = 0xff; pSuback[ 2 ] = 0xff; pSuback[ 3 ] = 0xff; @@ -1427,7 +1427,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Attempt to process a SUBACK larger than the size of the data stream. */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 1 ] = 0x52; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, @@ -1444,7 +1444,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Attempt to process a SUBACK with a "Remaining length" smaller than the * spec allows. */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 1 ] = 0x02; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, @@ -1460,7 +1460,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* Attempt to process a SUBACK with a bad return code. */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 6 ] = 0xff; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, @@ -1476,7 +1476,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) /* The SUBACK control packet type must be 0x90. */ { - _DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); + DECLARE_PACKET( _pSubackTemplate, pSuback, subackSize ); pSuback[ 0 ] = 0x91; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &subscribe, pSuback, @@ -1501,7 +1501,7 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) */ TEST( MQTT_Unit_Receive, UnsubackValid ) { - _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); + _mqttOperation_t unsubscribe = INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1512,7 +1512,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) /* Even though no UNSUBSCRIBE is in the receive queue, 4 bytes should still be * processed (should not crash). */ { - _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize, @@ -1521,7 +1521,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) /* Process a valid UNSUBACK. */ { - _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); _operationResetAndPush( &unsubscribe ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, @@ -1544,7 +1544,7 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) */ TEST( MQTT_Unit_Receive, UnsubackInvalid ) { - _mqttOperation_t unsubscribe = _INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); + _mqttOperation_t unsubscribe = INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ @@ -1556,7 +1556,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* An incomplete UNSUBACK should not be processed, and no status should be set. */ { - _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, unsubackSize - 1, @@ -1571,7 +1571,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* The UNSUBACK control packet type must be 0xb0. */ { - _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); pUnsuback[ 0 ] = 0xb1; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, @@ -1589,7 +1589,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* An UNSUBACK must have a remaining length of 2. */ { - _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); pUnsuback[ 1 ] = 0x03; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, @@ -1606,7 +1606,7 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) /* The packet identifier in UNSUBACK cannot be 0. No status should be set if * packet identifier 0 is received. */ { - _DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); + DECLARE_PACKET( _pUnsubackTemplate, pUnsuback, unsubackSize ); pUnsuback[ 3 ] = 0x00; TEST_ASSERT_EQUAL_INT( true, _processBuffer( &unsubscribe, pUnsuback, @@ -1642,7 +1642,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) { _pMqttConnection->keepAliveFailure = false; - _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, @@ -1657,7 +1657,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) { _pMqttConnection->keepAliveFailure = true; - _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize, @@ -1673,7 +1673,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) { _pMqttConnection->keepAliveFailure = true; - _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, pingrespSize - 1, @@ -1690,7 +1690,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) { _pMqttConnection->keepAliveFailure = true; - _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); pPingresp[ 1 ] = 0x01; TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, @@ -1708,7 +1708,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) { _pMqttConnection->keepAliveFailure = true; - _DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); + DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); pPingresp[ 0 ] = 0xd1; TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, pPingresp, diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 92320d76c9..62cc4431db 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -64,30 +64,30 @@ * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ #if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true + #define AWS_IOT_MQTT_SERVER true #else - #define _AWS_IOT_MQTT_SERVER false + #define AWS_IOT_MQTT_SERVER false #endif /* * Constants relating to the test subscription list. */ -#define _LIST_ITEM_COUNT ( 10 ) /**< @brief Number of subscriptions. */ -#define _TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ -#define _TEST_TOPIC_FILTER_LENGTH ( sizeof( _TEST_TOPIC_FILTER_FORMAT ) + 1 ) /**< @brief Maximum length of each topic filter. */ +#define LIST_ITEM_COUNT ( 10 ) /**< @brief Number of subscriptions. */ +#define TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ +#define TEST_TOPIC_FILTER_LENGTH ( sizeof( TEST_TOPIC_FILTER_FORMAT ) + 1 ) /**< @brief Maximum length of each topic filter. */ /* * Constants relating to the multithreaded subscription test. */ -#define _MT_THREAD_COUNT ( 8 ) /**< @brief Number of threads. */ -#define _MT_TOPIC_FILTER_FORMAT ( "/%p-%lu" ) /**< @brief Format of each topic filter. */ -#define _MT_TOPIC_FILTER_LENGTH ( 16 ) /**< @brief Maximum length of each topic filter. */ +#define MT_THREAD_COUNT ( 8 ) /**< @brief Number of threads. */ +#define MT_TOPIC_FILTER_FORMAT ( "/%p-%lu" ) /**< @brief Format of each topic filter. */ +#define MT_TOPIC_FILTER_LENGTH ( 16 ) /**< @brief Maximum length of each topic filter. */ /** * @brief A non-NULL function pointer to use for subscription callback. This * "function" should cause a crash if actually called. */ -#define _CALLBACK_FUNCTION \ +#define CALLBACK_FUNCTION \ ( ( void ( * )( void *, \ IotMqttCallbackParam_t * ) ) 0x1 ) @@ -96,7 +96,7 @@ * and #TEST_MQTT_Unit_Subscription_TopicFilterMatchFalse_ should be shorter than this * length. */ -#define _TOPIC_FILTER_MATCH_MAX_LENGTH ( 32 ) +#define TOPIC_FILTER_MATCH_MAX_LENGTH ( 32 ) /** * @brief Macro to check a single topic name against a topic filter. @@ -110,7 +110,7 @@ * @note This macro may only be used when a #_mqttSubscription_t pointer named pTopicFilter * is in scope. */ -#define _TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ +#define TEST_TOPIC_MATCH( topicNameString, topicFilterString, exactMatch, expectedResult ) \ { \ _topicMatchParams_t _topicMatchParams = { 0 }; \ _topicMatchParams.pTopicName = topicNameString; \ @@ -118,7 +118,7 @@ _topicMatchParams.exactMatchOnly = exactMatch; \ \ pTopicFilter->topicFilterLength = ( uint16_t ) snprintf( pTopicFilter->pTopicFilter, \ - _TOPIC_FILTER_MATCH_MAX_LENGTH, \ + TOPIC_FILTER_MATCH_MAX_LENGTH, \ topicFilterString ); \ \ TEST_ASSERT_EQUAL_INT( expectedResult, \ @@ -157,18 +157,18 @@ static void _populateList( void ) size_t i = 0; _mqttSubscription_t * pSubscription = NULL; - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - pSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); + pSubscription = IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + TEST_TOPIC_FILTER_LENGTH ); TEST_ASSERT_NOT_NULL( pSubscription ); - ( void ) memset( pSubscription, 0x00, sizeof( _mqttSubscription_t ) + _TEST_TOPIC_FILTER_LENGTH ); + ( void ) memset( pSubscription, 0x00, sizeof( _mqttSubscription_t ) + TEST_TOPIC_FILTER_LENGTH ); pSubscription->packetInfo.identifier = 1; pSubscription->packetInfo.order = i; - pSubscription->callback.function = _CALLBACK_FUNCTION; + pSubscription->callback.function = CALLBACK_FUNCTION; pSubscription->topicFilterLength = ( uint16_t ) snprintf( pSubscription->pTopicFilter, - _TEST_TOPIC_FILTER_LENGTH, - _TEST_TOPIC_FILTER_FORMAT, + TEST_TOPIC_FILTER_LENGTH, + TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); IotListDouble_InsertHead( &( _pMqttConnection->subscriptionList ), @@ -251,7 +251,7 @@ static void _publishCallback( void * pArgument, /* Ensure that publish info is valid. */ TEST_ASSERT_EQUAL_INT( true, - _IotMqtt_ValidatePublish( _AWS_IOT_MQTT_SERVER, + _IotMqtt_ValidatePublish( AWS_IOT_MQTT_SERVER, &( pPublish->u.message.info ) ) ); } @@ -281,20 +281,20 @@ static void _multithreadTestThread( void * pArgument ) { size_t i = 0; bool * pThreadResult = ( bool * ) pArgument; - char pTopicFilters[ _LIST_ITEM_COUNT ][ _MT_TOPIC_FILTER_LENGTH ] = { { 0 } }; - IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + char pTopicFilters[ LIST_ITEM_COUNT ][ MT_TOPIC_FILTER_LENGTH ] = { { 0 } }; + IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Synchronize with the other threads before starting the test. */ IotSemaphore_Wait( &( _mtTestStart ) ); /* Add items to the subscription list. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - subscription[ i ].callback.function = _CALLBACK_FUNCTION; + subscription[ i ].callback.function = CALLBACK_FUNCTION; subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - _MT_TOPIC_FILTER_LENGTH, - _MT_TOPIC_FILTER_FORMAT, + MT_TOPIC_FILTER_LENGTH, + MT_TOPIC_FILTER_FORMAT, ( void * ) &i, ( unsigned long ) i ); @@ -310,7 +310,7 @@ static void _multithreadTestThread( void * pArgument ) } /* Remove the previously added items from the list. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, &( subscription[ i ] ), @@ -349,7 +349,7 @@ TEST_SETUP( MQTT_Unit_Subscription ) networkInfo.pNetworkInterface = &networkInterface; /* Create an MQTT connection with empty network info. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( _AWS_IOT_MQTT_SERVER, + _pMqttConnection = IotTestMqtt_createMqttConnection( AWS_IOT_MQTT_SERVER, &networkInfo, 0 ); TEST_ASSERT_NOT_NULL( _pMqttConnection ); @@ -502,7 +502,7 @@ TEST( MQTT_Unit_Subscription, ListFindByPacket ) TEST_ASSERT_NOT_EQUAL( NULL, pSubscription ); /* Packet present, order not present. */ - packetMatchParams.order = _LIST_ITEM_COUNT; + packetMatchParams.order = LIST_ITEM_COUNT; pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), NULL, IotTestMqtt_packetMatch, @@ -520,7 +520,7 @@ TEST( MQTT_Unit_Subscription, ListFindByPacket ) /* Packet and order not present. */ packetMatchParams.packetIdentifier = 0; - packetMatchParams.order = _LIST_ITEM_COUNT; + packetMatchParams.order = LIST_ITEM_COUNT; pSubscriptionLink = IotListDouble_FindFirstMatch( &( _pMqttConnection->subscriptionList ), NULL, IotTestMqtt_packetMatch, @@ -545,7 +545,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) _populateList(); /* Remove all subscriptions by packet one-by-one. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, 1, @@ -571,8 +571,8 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) { size_t i = 0; - char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; - IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + char pTopicFilters[ LIST_ITEM_COUNT ][ TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; + IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* On empty list (should not crash). */ subscription[ 0 ].pTopicFilter = "/topic"; @@ -585,11 +585,11 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) subscription[ 0 ].pTopicFilter = pTopicFilters[ 0 ]; /* Removal one-by-one. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { subscription[ 0 ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ 0 ], - _TEST_TOPIC_FILTER_LENGTH, - _TEST_TOPIC_FILTER_FORMAT, + TEST_TOPIC_FILTER_LENGTH, + TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, @@ -605,18 +605,18 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ) TEST_ASSERT_EQUAL_INT( false, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); /* Removal all at once. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - _TEST_TOPIC_FILTER_LENGTH, - _TEST_TOPIC_FILTER_FORMAT, + TEST_TOPIC_FILTER_LENGTH, + TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); } _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, subscription, - _LIST_ITEM_COUNT ); + LIST_ITEM_COUNT ); /* List should be empty. */ TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); @@ -633,18 +633,18 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) _mqttSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; _topicMatchParams_t topicMatchParams = { 0 }; - char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; + char pTopicFilters[ LIST_ITEM_COUNT ][ TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Set valid values in the subscription list. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - subscription[ i ].callback.function = _CALLBACK_FUNCTION; + subscription[ i ].callback.function = CALLBACK_FUNCTION; subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - _TEST_TOPIC_FILTER_LENGTH, - _TEST_TOPIC_FILTER_FORMAT, + TEST_TOPIC_FILTER_LENGTH, + TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); } @@ -652,7 +652,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) status = _IotMqtt_AddSubscriptions( _pMqttConnection, 1, subscription, - _LIST_ITEM_COUNT ); + LIST_ITEM_COUNT ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Change the callback information, but not the topic filter. */ @@ -702,30 +702,30 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) { size_t i = 0; - char pTopicFilters[ _LIST_ITEM_COUNT ][ _TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; + char pTopicFilters[ LIST_ITEM_COUNT ][ TEST_TOPIC_FILTER_LENGTH ] = { { 0 } }; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription[ _LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* Set valid values in the subscription list. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - subscription[ i ].callback.function = _CALLBACK_FUNCTION; + subscription[ i ].callback.function = CALLBACK_FUNCTION; subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - _TEST_TOPIC_FILTER_LENGTH, - _TEST_TOPIC_FILTER_FORMAT, + TEST_TOPIC_FILTER_LENGTH, + TEST_TOPIC_FILTER_FORMAT, ( unsigned long ) i ); } /* Set malloc to fail at various points. */ - for( i = 0; i < _LIST_ITEM_COUNT; i++ ) + for( i = 0; i < LIST_ITEM_COUNT; i++ ) { UnityMalloc_MakeMallocFailAfterCount( ( int ) i ); status = _IotMqtt_AddSubscriptions( _pMqttConnection, 1, subscription, - _LIST_ITEM_COUNT ); + LIST_ITEM_COUNT ); if( status == IOT_MQTT_SUCCESS ) { @@ -745,14 +745,14 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) { int32_t i = 0, threadsCreated = 0, threadsExited = 0; - bool threadResults[ _MT_THREAD_COUNT ] = { 0 }; + bool threadResults[ MT_THREAD_COUNT ] = { 0 }; /* Create the synchronization semaphores. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestStart ), 0, _MT_THREAD_COUNT ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestExit ), 0, _MT_THREAD_COUNT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestStart ), 0, MT_THREAD_COUNT ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestExit ), 0, MT_THREAD_COUNT ) ); /* Spawn threads for the test. */ - for( i = 0; i < _MT_THREAD_COUNT; i++ ) + for( i = 0; i < MT_THREAD_COUNT; i++ ) { if( Iot_CreateDetachedThread( _multithreadTestThread, &( threadResults[ i ] ), @@ -788,10 +788,10 @@ TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) { /* Check how many threads ran. */ TEST_ASSERT_EQUAL_INT( threadsCreated, threadsExited ); - TEST_ASSERT_EQUAL_INT( threadsCreated, _MT_THREAD_COUNT ); + TEST_ASSERT_EQUAL_INT( threadsCreated, MT_THREAD_COUNT ); /* Check the results of the test threads. */ - for( i = 0; i < _MT_THREAD_COUNT; i++ ) + for( i = 0; i < MT_THREAD_COUNT; i++ ) { TEST_ASSERT_EQUAL_INT( true, threadResults[ i ] ); } @@ -1023,37 +1023,37 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) { _mqttSubscription_t * pTopicFilter = - IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); + IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + TOPIC_FILTER_MATCH_MAX_LENGTH ); TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); if( TEST_PROTECT() ) { /* Exact matching. */ - _TEST_TOPIC_MATCH( "/exact", "/exact", true, true ); - _TEST_TOPIC_MATCH( "/exact", "/exact", false, true ); + TEST_TOPIC_MATCH( "/exact", "/exact", true, true ); + TEST_TOPIC_MATCH( "/exact", "/exact", false, true ); /* Topic level wildcard matching. */ - _TEST_TOPIC_MATCH( "/aws", "/+", false, true ); - _TEST_TOPIC_MATCH( "/aws/iot", "/aws/+", false, true ); - _TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/shadow", false, true ); - _TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/+", false, true ); - _TEST_TOPIC_MATCH( "aws/", "aws/+", false, true ); - _TEST_TOPIC_MATCH( "/aws", "+/+", false, true ); - _TEST_TOPIC_MATCH( "aws//iot", "aws/+/iot", false, true ); - _TEST_TOPIC_MATCH( "aws//iot", "aws//+", false, true ); - _TEST_TOPIC_MATCH( "aws///iot", "aws/+/+/iot", false, true ); + TEST_TOPIC_MATCH( "/aws", "/+", false, true ); + TEST_TOPIC_MATCH( "/aws/iot", "/aws/+", false, true ); + TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/shadow", false, true ); + TEST_TOPIC_MATCH( "/aws/iot/shadow", "/aws/+/+", false, true ); + TEST_TOPIC_MATCH( "aws/", "aws/+", false, true ); + TEST_TOPIC_MATCH( "/aws", "+/+", false, true ); + TEST_TOPIC_MATCH( "aws//iot", "aws/+/iot", false, true ); + TEST_TOPIC_MATCH( "aws//iot", "aws//+", false, true ); + TEST_TOPIC_MATCH( "aws///iot", "aws/+/+/iot", false, true ); /* Multi level wildcard matching. */ - _TEST_TOPIC_MATCH( "/aws/iot/shadow", "#", false, true ); - _TEST_TOPIC_MATCH( "aws/iot/shadow", "#", false, true ); - _TEST_TOPIC_MATCH( "/aws/iot/shadow", "/#", false, true ); - _TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/iot/#", false, true ); - _TEST_TOPIC_MATCH( "aws/iot/shadow/thing", "aws/iot/#", false, true ); - _TEST_TOPIC_MATCH( "aws", "aws/#", false, true ); + TEST_TOPIC_MATCH( "/aws/iot/shadow", "#", false, true ); + TEST_TOPIC_MATCH( "aws/iot/shadow", "#", false, true ); + TEST_TOPIC_MATCH( "/aws/iot/shadow", "/#", false, true ); + TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/iot/#", false, true ); + TEST_TOPIC_MATCH( "aws/iot/shadow/thing", "aws/iot/#", false, true ); + TEST_TOPIC_MATCH( "aws", "aws/#", false, true ); /* Both topic level and multi level wildcard. */ - _TEST_TOPIC_MATCH( "aws/iot/shadow/thing/temp", "aws/+/shadow/#", false, true ); + TEST_TOPIC_MATCH( "aws/iot/shadow/thing/temp", "aws/+/shadow/#", false, true ); } IotMqtt_FreeSubscription( pTopicFilter ); @@ -1068,36 +1068,36 @@ TEST( MQTT_Unit_Subscription, TopicFilterMatchTrue ) TEST( MQTT_Unit_Subscription, TopicFilterMatchFalse ) { _mqttSubscription_t * pTopicFilter = - IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + _TOPIC_FILTER_MATCH_MAX_LENGTH ); + IotMqtt_MallocSubscription( sizeof( _mqttSubscription_t ) + TOPIC_FILTER_MATCH_MAX_LENGTH ); TEST_ASSERT_NOT_EQUAL( NULL, pTopicFilter ); if( TEST_PROTECT() ) { /* Topic filter longer than filter name. */ - _TEST_TOPIC_MATCH( "/short", "/toolong", true, false ); - _TEST_TOPIC_MATCH( "/short", "/toolong", false, false ); + TEST_TOPIC_MATCH( "/short", "/toolong", true, false ); + TEST_TOPIC_MATCH( "/short", "/toolong", false, false ); /* Case mismatch. */ - _TEST_TOPIC_MATCH( "/exact", "/eXaCt", true, false ); - _TEST_TOPIC_MATCH( "/exact", "/ExAcT", false, false ); + TEST_TOPIC_MATCH( "/exact", "/eXaCt", true, false ); + TEST_TOPIC_MATCH( "/exact", "/ExAcT", false, false ); /* Substrings should not match. */ - _TEST_TOPIC_MATCH( "aws/", "aws/iot", true, false ); - _TEST_TOPIC_MATCH( "aws/", "aws/iot", false, false ); + TEST_TOPIC_MATCH( "aws/", "aws/iot", true, false ); + TEST_TOPIC_MATCH( "aws/", "aws/iot", false, false ); /* Topic level wildcard matching. */ - _TEST_TOPIC_MATCH( "aws", "aws/", false, false ); - _TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+", false, false ); - _TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+/thing", false, false ); - _TEST_TOPIC_MATCH( "/aws", "+", false, false ); + TEST_TOPIC_MATCH( "aws", "aws/", false, false ); + TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+", false, false ); + TEST_TOPIC_MATCH( "aws/iot/shadow", "aws/+/thing", false, false ); + TEST_TOPIC_MATCH( "/aws", "+", false, false ); /* Multi level wildcard matching. */ - _TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/#", false, false ); - _TEST_TOPIC_MATCH( "aws/iot", "/#", false, false ); + TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/#", false, false ); + TEST_TOPIC_MATCH( "aws/iot", "/#", false, false ); /* Both topic level and multi level wildcard. */ - _TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/+/#", false, false ); + TEST_TOPIC_MATCH( "aws/iot/shadow", "iot/+/#", false, false ); } IotMqtt_FreeSubscription( pTopicFilter ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 2a35f6ec4b..0eac921563 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -45,21 +45,21 @@ * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ #if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define _AWS_IOT_MQTT_SERVER true + #define AWS_IOT_MQTT_SERVER true #else - #define _AWS_IOT_MQTT_SERVER false + #define AWS_IOT_MQTT_SERVER false #endif /** * @brief Length of the subscription array used in * #TEST_MQTT_Unit_Validate_ValidateSubscriptionList_. */ -#define _SUBSCRIPTION_COUNT ( _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) +#define SUBSCRIPTION_COUNT ( AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ) /** * @brief A non-NULL function pointer. */ -#define _FUNCTION_POINTER \ +#define FUNCTION_POINTER \ ( ( void ( * )( void *, \ IotMqttCallbackParam_t * ) ) 0x1 ) @@ -113,7 +113,7 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) bool validateStatus = false; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - connectInfo.awsIotMqttMode = _AWS_IOT_MQTT_SERVER; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; /* NULL parameter. */ validateStatus = _IotMqtt_ValidateConnect( NULL ); @@ -137,9 +137,9 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* AWS IoT MQTT service limit tests. */ - #if _AWS_IOT_MQTT_SERVER == true + #if AWS_IOT_MQTT_SERVER == true /* Client identifier too long. */ - connectInfo.clientIdentifierLength = _AWS_IOT_MQTT_SERVER_MAX_CLIENTID + 1; + connectInfo.clientIdentifierLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID + 1; validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); connectInfo.clientIdentifierLength = 24; @@ -150,15 +150,15 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Keep-alive too small. */ - connectInfo.keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE - 1; + connectInfo.keepAliveSeconds = AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE - 1; validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Keep-alive too large. */ - connectInfo.keepAliveSeconds = _AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE + 1; + connectInfo.keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE + 1; validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - #endif /* if _AWS_IOT_MQTT_SERVER == true */ + #endif /* if AWS_IOT_MQTT_SERVER == true */ } /*-----------------------------------------------------------*/ @@ -224,7 +224,7 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* AWS IoT MQTT service limit tests. */ - #if _AWS_IOT_MQTT_SERVER == true + #if AWS_IOT_MQTT_SERVER == true /* QoS 2. */ publishInfo.qos = IOT_MQTT_QOS_2; validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); @@ -238,10 +238,10 @@ TEST( MQTT_Unit_Validate, ValidatePublish ) publishInfo.retain = false; /* Topic name too long. */ - publishInfo.topicNameLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; + publishInfo.topicNameLength = AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; validateStatus = _IotMqtt_ValidatePublish( true, &publishInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - #endif /* if _AWS_IOT_MQTT_SERVER == true */ + #endif /* if AWS_IOT_MQTT_SERVER == true */ } /*-----------------------------------------------------------*/ @@ -287,7 +287,7 @@ TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) { size_t i = 0; bool validateStatus = false; - IotMqttSubscription_t pSubscriptions[ _SUBSCRIPTION_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + IotMqttSubscription_t pSubscriptions[ SUBSCRIPTION_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; /* NULL parameter. */ validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, NULL, 1 ); @@ -298,105 +298,105 @@ TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Uninitialized subscriptions. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Initialize all subscriptions to valid values. */ - for( i = 0; i < _SUBSCRIPTION_COUNT; i++ ) + for( i = 0; i < SUBSCRIPTION_COUNT; i++ ) { pSubscriptions[ i ].pTopicFilter = "/test"; pSubscriptions[ i ].topicFilterLength = 5; - pSubscriptions[ i ].callback.function = _FUNCTION_POINTER; + pSubscriptions[ i ].callback.function = FUNCTION_POINTER; } - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* One subscription with invalid QoS. */ - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) -1; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) -1; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) 3; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].qos = ( IotMqttQos_t ) 3; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* QoS is not validated for UNSUBSCRIBE. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].qos = IOT_MQTT_QOS_0; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].qos = IOT_MQTT_QOS_0; /* One subscription with no callback. */ - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].callback.function = NULL; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].callback.function = NULL; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* Callback is not validated for UNSUBSCRIBE. */ - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_UNSUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].callback.function = _FUNCTION_POINTER; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].callback.function = FUNCTION_POINTER; /* Valid subscription filters. */ - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 1; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 1; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/#"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/#"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+/"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+/"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "/+"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/+/+/+"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 7; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+/+/+/+"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 7; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* Invalid subscription filters. */ - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/#"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "#/#"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 3; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a#"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a#"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a+"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "a+"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+a"; - pSubscriptions[ _SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; - validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, _SUBSCRIPTION_COUNT ); + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].pTopicFilter = "+a"; + pSubscriptions[ SUBSCRIPTION_COUNT - 1 ].topicFilterLength = 2; + validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, false, pSubscriptions, SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* AWS IoT MQTT service limit tests. */ - #if _AWS_IOT_MQTT_SERVER == true + #if AWS_IOT_MQTT_SERVER == true /* Too many subscriptions. */ validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, true, pSubscriptions, - _AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE + 1 ); + AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE + 1 ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); /* QoS 2. */ @@ -404,18 +404,18 @@ TEST( MQTT_Unit_Validate, ValidateSubscriptionList ) validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, true, pSubscriptions, - _SUBSCRIPTION_COUNT ); + SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); pSubscriptions[ 0 ].qos = IOT_MQTT_QOS_0; /* Topic filter too long. */ - pSubscriptions[ 0 ].topicFilterLength = _AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; + pSubscriptions[ 0 ].topicFilterLength = AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH + 1; validateStatus = _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, true, pSubscriptions, - _SUBSCRIPTION_COUNT ); + SUBSCRIPTION_COUNT ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - #endif /* if _AWS_IOT_MQTT_SERVER == true */ + #endif /* if AWS_IOT_MQTT_SERVER == true */ } /*-----------------------------------------------------------*/ diff --git a/tests/serializer/iot_test_serializer_cbor.c b/tests/serializer/iot_test_serializer_cbor.c index d478eb8035..f5eccded9c 100644 --- a/tests/serializer/iot_test_serializer_cbor.c +++ b/tests/serializer/iot_test_serializer_cbor.c @@ -41,22 +41,22 @@ #define _encoder _IotSerializerCborEncoder #define _decoder _IotSerializerCborDecoder -#define _BUFFER_SIZE 100 +#define BUFFER_SIZE 100 static IotSerializerEncoderObject_t _encoderObject; -uint8_t _buffer[ _BUFFER_SIZE ]; +uint8_t _buffer[ BUFFER_SIZE ]; TEST_GROUP( Full_Serializer_CBOR ); TEST_SETUP( Full_Serializer_CBOR ) { /* Reset buffer to zero. */ - memset( _buffer, 0, _BUFFER_SIZE ); + memset( _buffer, 0, BUFFER_SIZE ); /* Init encoder object with buffer. */ TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.init( &_encoderObject, _buffer, _BUFFER_SIZE ) ); + _encoder.init( &_encoderObject, _buffer, BUFFER_SIZE ) ); } TEST_TEAR_DOWN( Full_Serializer_CBOR ) @@ -124,7 +124,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_integer ) CborValue outermostValue; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); TEST_ASSERT_EQUAL( CborIntegerType, cbor_value_get_type( &outermostValue ) ); @@ -150,7 +150,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_text_string ) CborValue outermostValue; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); TEST_ASSERT_EQUAL( CborTextStringType, cbor_value_get_type( &outermostValue ) ); @@ -175,7 +175,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_byte_string ) CborValue outermostValue; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); TEST_ASSERT_EQUAL( CborByteStringType, cbor_value_get_type( &outermostValue ) ); @@ -217,7 +217,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_map ) CborValue outermostValue, value; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermostValue ) ); @@ -259,7 +259,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_array ) int64_t number = 0; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); TEST_ASSERT_EQUAL( CborArrayType, cbor_value_get_type( &outermostValue ) ); @@ -304,7 +304,7 @@ TEST( Full_Serializer_CBOR, Encoder_map_nest_map ) CborValue outermostValue, map1, str; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermostValue ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermostValue ) ); TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermostValue ) ); @@ -358,7 +358,7 @@ TEST( Full_Serializer_CBOR, Encoder_map_nest_array ) int64_t number = 0; TEST_ASSERT_EQUAL( CborNoError, - cbor_parser_init( _buffer, _BUFFER_SIZE, 0, &parser, &outermost ) ); + cbor_parser_init( _buffer, BUFFER_SIZE, 0, &parser, &outermost ) ); TEST_ASSERT_EQUAL( CborMapType, cbor_value_get_type( &outermost ) ); diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index 9a82d8213a..53428511f2 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -64,7 +64,7 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { - _IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Set a signal handler for segmentation faults and assertion failures. */ @@ -73,12 +73,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -108,10 +108,10 @@ int main( int argc, /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - _IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - _IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 3a7960aa5a..5b25317b11 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -83,17 +83,17 @@ /** * @brief The length of @ref AWS_IOT_TEST_SHADOW_THING_NAME. */ -#define _THING_NAME_LENGTH ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ) +#define THING_NAME_LENGTH ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ) /** * @brief The Shadow document used for these tests. */ -#define _TEST_SHADOW_DOCUMENT "{\"state\":{\"reported\":{\"key\":\"value\"}},\"clientToken\":\"shadowtest\"}" +#define TEST_SHADOW_DOCUMENT "{\"state\":{\"reported\":{\"key\":\"value\"}},\"clientToken\":\"shadowtest\"}" /** - * @brief The length of #_TEST_SHADOW_DOCUMENT. + * @brief The length of #TEST_SHADOW_DOCUMENT. */ -#define _TEST_SHADOW_DOCUMENT_LENGTH ( sizeof( _TEST_SHADOW_DOCUMENT ) - 1 ) +#define TEST_SHADOW_DOCUMENT_LENGTH ( sizeof( TEST_SHADOW_DOCUMENT ) - 1 ) /*-----------------------------------------------------------*/ @@ -149,10 +149,10 @@ static void _operationComplete( void * pArgument, AwsIotShadow_Assert( pOperation->mqttConnection == _mqttConnection ); AwsIotShadow_Assert( pOperation->u.operation.result == AWS_IOT_SHADOW_SUCCESS ); AwsIotShadow_Assert( pOperation->u.operation.reference == pParams->operation ); - AwsIotShadow_Assert( pOperation->thingNameLength == _THING_NAME_LENGTH ); + AwsIotShadow_Assert( pOperation->thingNameLength == THING_NAME_LENGTH ); AwsIotShadow_Assert( strncmp( pOperation->pThingName, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH ) == 0 ); + THING_NAME_LENGTH ) == 0 ); /* Check the retrieved Shadow document. */ if( pOperation->callbackType == AWS_IOT_SHADOW_GET_COMPLETE ) @@ -286,7 +286,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) /* Initialize the common members of the Shadow document info. */ documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - documentInfo.thingNameLength = _THING_NAME_LENGTH; + documentInfo.thingNameLength = THING_NAME_LENGTH; documentInfo.qos = qos; /* Create the wait semaphore for operations. */ @@ -298,8 +298,8 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) callbackParam.expectedType = AWS_IOT_SHADOW_UPDATE_COMPLETE; /* Set the members of the Shadow document info for UPDATE. */ - documentInfo.u.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; - documentInfo.u.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; + documentInfo.u.update.pUpdateDocument = TEST_SHADOW_DOCUMENT; + documentInfo.u.update.updateDocumentLength = TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ status = AwsIotShadow_Update( _mqttConnection, @@ -337,7 +337,7 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) /* Delete the Shadow document. */ status = AwsIotShadow_Delete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, &callbackInfo, &( callbackParam.operation ) ); @@ -366,12 +366,12 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) /* Initialize the common members of the Shadow document info. */ documentInfo.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - documentInfo.thingNameLength = _THING_NAME_LENGTH; + documentInfo.thingNameLength = THING_NAME_LENGTH; documentInfo.qos = qos; /* Set the members of the Shadow document info for UPDATE. */ - documentInfo.u.update.pUpdateDocument = _TEST_SHADOW_DOCUMENT; - documentInfo.u.update.updateDocumentLength = _TEST_SHADOW_DOCUMENT_LENGTH; + documentInfo.u.update.pUpdateDocument = TEST_SHADOW_DOCUMENT; + documentInfo.u.update.updateDocumentLength = TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ status = AwsIotShadow_TimedUpdate( _mqttConnection, @@ -410,7 +410,7 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) /* Delete the Shadow document. */ status = AwsIotShadow_TimedDelete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); @@ -490,7 +490,7 @@ TEST_SETUP( Shadow_System ) /* Delete any existing Shadow so all tests start with no Shadow. */ status = AwsIotShadow_TimedDelete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, AWS_IOT_TEST_SHADOW_TIMEOUT ); @@ -605,7 +605,7 @@ TEST( Shadow_System, DeltaCallback ) /* Set a desired state in the Update document. */ updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - updateDocument.thingNameLength = _THING_NAME_LENGTH; + updateDocument.thingNameLength = THING_NAME_LENGTH; updateDocument.u.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; updateDocument.u.update.updateDocumentLength = 65; @@ -614,7 +614,7 @@ TEST( Shadow_System, DeltaCallback ) /* Set the delta callback. */ status = AwsIotShadow_SetDeltaCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, &deltaCallback ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); @@ -646,7 +646,7 @@ TEST( Shadow_System, DeltaCallback ) /* Remove the delta callback. */ status = AwsIotShadow_SetDeltaCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); @@ -654,7 +654,7 @@ TEST( Shadow_System, DeltaCallback ) /* Remove persistent subscriptions for Shadow Update. */ status = AwsIotShadow_RemovePersistentSubscriptions( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); } @@ -683,7 +683,7 @@ TEST( Shadow_System, UpdatedCallback ) /* Set a desired state in the Update document. */ updateDocument.pThingName = AWS_IOT_TEST_SHADOW_THING_NAME; - updateDocument.thingNameLength = _THING_NAME_LENGTH; + updateDocument.thingNameLength = THING_NAME_LENGTH; updateDocument.u.update.pUpdateDocument = "{\"state\": {\"desired\": {\"key\": true}}, \"clientToken\":\"shadowtest\"}"; updateDocument.u.update.updateDocumentLength = 65; @@ -692,7 +692,7 @@ TEST( Shadow_System, UpdatedCallback ) /* Set the updated callback. */ status = AwsIotShadow_SetUpdatedCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, &updatedCallback ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); @@ -713,7 +713,7 @@ TEST( Shadow_System, UpdatedCallback ) /* Remove the updated callback. */ status = AwsIotShadow_SetUpdatedCallback( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, - _THING_NAME_LENGTH, + THING_NAME_LENGTH, 0, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index c332903a26..01127e8085 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -38,15 +38,15 @@ #include "private/aws_iot_shadow_internal.h" /* Undefine logging configuration set in Shadow internal header. */ -#undef _LIBRARY_LOG_NAME -#undef _LIBRARY_LOG_LEVEL +#undef LIBRARY_LOG_NAME +#undef LIBRARY_LOG_LEVEL /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" /* Undefine logging configuration set in MQTT internal header. */ -#undef _LIBRARY_LOG_NAME -#undef _LIBRARY_LOG_LEVEL +#undef LIBRARY_LOG_NAME +#undef LIBRARY_LOG_LEVEL /* Platform layer includes. */ #include "platform/iot_clock.h" @@ -87,24 +87,24 @@ /** * @brief The Thing Name shared among all the tests. */ -#define _TEST_THING_NAME "TestThingName" +#define TEST_THING_NAME "TestThingName" /** - * @brief The length of #_TEST_THING_NAME. + * @brief The length of #TEST_THING_NAME. */ -#define _TEST_THING_NAME_LENGTH ( sizeof( _TEST_THING_NAME ) - 1 ) +#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) /** * @brief A delay that simulates the time required for an MQTT packet to be sent * to the server and for the server to send a response. */ -#define _NETWORK_ROUND_TRIP_TIME_MS ( 25 ) +#define NETWORK_ROUND_TRIP_TIME_MS ( 25 ) /** * @brief The maximum size of any MQTT acknowledgement packet (e.g. SUBACK, * PUBACK, UNSUBACK) used in these tests. */ -#define _ACKNOWLEDGEMENT_PACKET_SIZE ( 5 ) +#define ACKNOWLEDGEMENT_PACKET_SIZE ( 5 ) /*-----------------------------------------------------------*/ @@ -160,11 +160,11 @@ static uint16_t _lastPacketIdentifier = 0; */ static void _receiveThread( void * pArgument ) { - uint8_t pReceivedData[ _ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; + uint8_t pReceivedData[ ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; _receiveContext_t receiveContext = { 0 }; receiveContext.pData = pReceivedData; - receiveContext.dataLength = _ACKNOWLEDGEMENT_PACKET_SIZE; + receiveContext.dataLength = ACKNOWLEDGEMENT_PACKET_SIZE; /* Silence warnings about unused parameters. */ ( void ) pArgument; @@ -177,30 +177,30 @@ static void _receiveThread( void * pArgument ) AwsIotShadow_Assert( _lastPacketIdentifier != 0 ); /* Set the packet identifier in the ACK packet. */ - pReceivedData[ 2 ] = _UINT16_HIGH_BYTE( _lastPacketIdentifier ); - pReceivedData[ 3 ] = _UINT16_LOW_BYTE( _lastPacketIdentifier ); + pReceivedData[ 2 ] = UINT16_HIGH_BYTE( _lastPacketIdentifier ); + pReceivedData[ 3 ] = UINT16_LOW_BYTE( _lastPacketIdentifier ); /* Create the corresponding ACK packet based on the last packet type. */ switch( _lastPacketType ) { - case _MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBLISH: - pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_PUBACK; + pReceivedData[ 0 ] = MQTT_PACKET_TYPE_PUBACK; pReceivedData[ 1 ] = 2; receiveContext.dataLength = 4; break; - case _MQTT_PACKET_TYPE_SUBSCRIBE: + case MQTT_PACKET_TYPE_SUBSCRIBE: - pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_SUBACK; + pReceivedData[ 0 ] = MQTT_PACKET_TYPE_SUBACK; pReceivedData[ 1 ] = 3; pReceivedData[ 4 ] = 1; receiveContext.dataLength = 5; break; - case _MQTT_PACKET_TYPE_UNSUBSCRIBE: + case MQTT_PACKET_TYPE_UNSUBSCRIBE: - pReceivedData[ 0 ] = _MQTT_PACKET_TYPE_UNSUBACK; + pReceivedData[ 0 ] = MQTT_PACKET_TYPE_UNSUBACK; pReceivedData[ 1 ] = 2; receiveContext.dataLength = 4; break; @@ -250,17 +250,17 @@ static size_t _sendSuccess( void * pSendContext, /* Read the remaining length. */ mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( &receiveContext, &_networkInterface ); - AwsIotShadow_Assert( mqttPacket.remainingLength != _MQTT_REMAINING_LENGTH_INVALID ); + AwsIotShadow_Assert( mqttPacket.remainingLength != MQTT_REMAINING_LENGTH_INVALID ); /* Set the last packet type based on the outgoing message. */ switch( mqttPacket.type & 0xf0 ) { - case _MQTT_PACKET_TYPE_PUBLISH: + case MQTT_PACKET_TYPE_PUBLISH: /* Only set the last packet type to PUBLISH for QoS 1. */ if( ( ( *pMessage & 0x06 ) >> 1 ) == 1 ) { - _lastPacketType = _MQTT_PACKET_TYPE_PUBLISH; + _lastPacketType = MQTT_PACKET_TYPE_PUBLISH; } else { @@ -270,12 +270,12 @@ static size_t _sendSuccess( void * pSendContext, break; - case ( _MQTT_PACKET_TYPE_SUBSCRIBE & 0xf0 ): - _lastPacketType = _MQTT_PACKET_TYPE_SUBSCRIBE; + case ( MQTT_PACKET_TYPE_SUBSCRIBE & 0xf0 ): + _lastPacketType = MQTT_PACKET_TYPE_SUBSCRIBE; break; - case ( _MQTT_PACKET_TYPE_UNSUBSCRIBE & 0xf0 ): - _lastPacketType = _MQTT_PACKET_TYPE_UNSUBSCRIBE; + case ( MQTT_PACKET_TYPE_UNSUBSCRIBE & 0xf0 ): + _lastPacketType = MQTT_PACKET_TYPE_UNSUBSCRIBE; break; default: @@ -289,9 +289,9 @@ static size_t _sendSuccess( void * pSendContext, if( _lastPacketType != 0 ) { /* Save the packet identifier as the last packet identifier. */ - if( _lastPacketType != _MQTT_PACKET_TYPE_PUBLISH ) + if( _lastPacketType != MQTT_PACKET_TYPE_PUBLISH ) { - _lastPacketIdentifier = _UINT16_DECODE( receiveContext.pData + receiveContext.dataIndex ); + _lastPacketIdentifier = UINT16_DECODE( receiveContext.pData + receiveContext.dataIndex ); status = IOT_MQTT_SUCCESS; } else @@ -308,7 +308,7 @@ static size_t _sendSuccess( void * pSendContext, /* Set the receive thread to run after a "network round-trip". */ AwsIotShadow_Assert( IotClock_TimerArm( &_receiveTimer, - _NETWORK_ROUND_TRIP_TIME_MS, + NETWORK_ROUND_TRIP_TIME_MS, 0 ) == true ); } @@ -511,8 +511,8 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) /* Thing Name too long. */ status = AwsIotShadow_Delete( _pMqttConnection, - _TEST_THING_NAME, - _MAX_THING_NAME_LENGTH + 1, + TEST_THING_NAME, + MAX_THING_NAME_LENGTH + 1, 0, NULL, NULL ); @@ -520,8 +520,8 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) /* No reference with waitable operation. */ status = AwsIotShadow_Delete( _pMqttConnection, - _TEST_THING_NAME, - _TEST_THING_NAME_LENGTH, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, NULL ); @@ -529,16 +529,16 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) /* Both callback and waitable flag set. */ status = AwsIotShadow_Delete( _pMqttConnection, - _TEST_THING_NAME, - _TEST_THING_NAME_LENGTH, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_WAITABLE, &callbackInfo, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* No callback for non-waitable GET. */ - documentInfo.pThingName = _TEST_THING_NAME; - documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + documentInfo.pThingName = TEST_THING_NAME; + documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; status = AwsIotShadow_Get( _pMqttConnection, &documentInfo, 0, @@ -548,8 +548,8 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) /* Callback function not set. */ status = AwsIotShadow_Delete( _pMqttConnection, - _TEST_THING_NAME, - _TEST_THING_NAME_LENGTH, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, 0, &callbackInfo, &operation ); @@ -575,8 +575,8 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) NULL, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - documentInfo.pThingName = _TEST_THING_NAME; - documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + documentInfo.pThingName = TEST_THING_NAME; + documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; /* Invalid QoS. */ documentInfo.qos = ( IotMqttQos_t ) 3; @@ -685,8 +685,8 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) /* Call Shadow DELETE. Memory allocation will fail at various times * during this call. */ status = AwsIotShadow_Delete( _pMqttConnection, - _TEST_THING_NAME, - _TEST_THING_NAME_LENGTH, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, AWS_IOT_SHADOW_FLAG_WAITABLE, NULL, &deleteOperation ); @@ -737,8 +737,8 @@ TEST( Shadow_Unit_API, GetMallocFail ) _AwsIotShadowMqttTimeoutMs = 75; /* Set the members of the document info. */ - documentInfo.pThingName = _TEST_THING_NAME; - documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + documentInfo.pThingName = TEST_THING_NAME; + documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; documentInfo.qos = IOT_MQTT_QOS_1; documentInfo.u.get.mallocDocument = IotTest_Malloc; @@ -801,8 +801,8 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) _AwsIotShadowMqttTimeoutMs = 75; /* Set the members of the document info. */ - documentInfo.pThingName = _TEST_THING_NAME; - documentInfo.thingNameLength = _TEST_THING_NAME_LENGTH; + documentInfo.pThingName = TEST_THING_NAME; + documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; documentInfo.qos = IOT_MQTT_QOS_1; documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}},\"clientToken\":\"TEST\"}"; documentInfo.u.update.updateDocumentLength = 50; diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 1ae645b6cb..d9df83f184 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -46,7 +46,7 @@ /** * @brief The size of the buffers allocated for holding Shadow error documents. */ -#define _ERROR_DOCUMENT_BUFFER_SIZE ( 128 ) +#define ERROR_DOCUMENT_BUFFER_SIZE ( 128 ) /*-----------------------------------------------------------*/ @@ -94,14 +94,14 @@ static void _generateParseErrorDocument( char * pErrorDocument, /* Generate an error document. */ va_start( arguments, pFormat ); errorDocumentLength = vsnprintf( pErrorDocument, - _ERROR_DOCUMENT_BUFFER_SIZE, + ERROR_DOCUMENT_BUFFER_SIZE, pFormat, arguments ); va_end( arguments ); /* Check for errors from vsnprintf. */ TEST_ASSERT_GREATER_THAN_INT( 0, errorDocumentLength ); - TEST_ASSERT_LESS_THAN_INT( _ERROR_DOCUMENT_BUFFER_SIZE, errorDocumentLength ); + TEST_ASSERT_LESS_THAN_INT( ERROR_DOCUMENT_BUFFER_SIZE, errorDocumentLength ); /* Parse the error document and check the result. */ TEST_ASSERT_EQUAL( expectedCode, @@ -366,7 +366,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) TEST( Shadow_Unit_Parser, ErrorDocument ) { size_t i = 0; - char pErrorDocument[ _ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; + char pErrorDocument[ ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; AwsIotShadowError_t pValidErrorCodes[] = { AWS_IOT_SHADOW_BAD_REQUEST, @@ -398,7 +398,7 @@ TEST( Shadow_Unit_Parser, ErrorDocument ) */ TEST( Shadow_Unit_Parser, ErrorDocumentInvalid ) { - char pErrorDocument[ _ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; + char pErrorDocument[ ERROR_DOCUMENT_BUFFER_SIZE ] = { 0 }; /* Parse an error document with an unknown code. */ _generateParseErrorDocument( pErrorDocument, From 4d91ff87ccc6b9a5150e184e7a24a51effcc84ff Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 26 Apr 2019 14:53:43 -0700 Subject: [PATCH 114/844] Disable all warnings in third party code (#395) --- CMakeLists.txt | 4 ++-- third_party/tinycbor/CMakeLists.txt | 6 ++++++ third_party/unity/CMakeLists.txt | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e88457f48..84c6cacd03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ include_directories( ${PROJECT_SOURCE_DIR}/lib/include ${PROJECT_SOURCE_DIR}/platform/include ) # TinyCBOR include path. -include_directories( ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) +include_directories( SYSTEM ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) # Demo include path. Always required because the tests also build the demos. include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) @@ -44,7 +44,7 @@ if( ${IOT_BUILD_TESTS} ) tests/mqtt/access ) # Unity test framework include paths. - include_directories( third_party/unity/unity + include_directories( SYSTEM third_party/unity/unity third_party/unity/unity/fixture ) # Build unity test framework. diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 0ff394b922..8faf7601a8 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -10,3 +10,9 @@ add_library( tinycbor SHARED # Link math library. target_link_libraries( tinycbor m ) + +# Disable all warnings for this third-party library. +target_compile_options( tinycbor + PRIVATE + $<$,$,$>: + -w> ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index 5ba0619ed0..38c3ad0178 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -3,3 +3,9 @@ add_library( unity SHARED unity/unity.c unity/fixture/unity_fixture.c unity/fixture/unity_memory_mt.c ) + +# Disable all warnings for this third-party library. +target_compile_options( unity + PRIVATE + $<$,$,$>: + -w> ) From 0248ccdef8a677f1c433ca79d6ef555b5d7750ed Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 26 Apr 2019 15:21:07 -0700 Subject: [PATCH 115/844] Remove unused macros and variables (#396) --- demos/app/posix/iot_demo_arguments_posix.c | 1 - lib/source/common/iot_taskpool.c | 6 ------ lib/source/defender/aws_iot_defender_collector.c | 4 ++-- lib/source/mqtt/iot_mqtt_serialize.c | 2 -- tests/common/unit/iot_tests_atomic.c | 1 - tests/common/unit/iot_tests_taskpool.c | 5 ++--- tests/mqtt/system/iot_tests_mqtt_stress.c | 1 + tests/mqtt/unit/iot_tests_mqtt_subscription.c | 2 +- tests/serializer/iot_test_serializer_cbor.c | 2 +- 9 files changed, 7 insertions(+), 17 deletions(-) diff --git a/demos/app/posix/iot_demo_arguments_posix.c b/demos/app/posix/iot_demo_arguments_posix.c index 42d6bc937c..bf96e1b802 100644 --- a/demos/app/posix/iot_demo_arguments_posix.c +++ b/demos/app/posix/iot_demo_arguments_posix.c @@ -249,7 +249,6 @@ bool IotDemo_ParseArguments( int argc, /* The default case should not be executed. */ default: abort(); - break; } } diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 031e7d20f3..f71c647fee 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -46,12 +46,6 @@ */ #define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) -/** - * @brief Try entering a critical section by trying and lock a mutex. - * - */ -#define TASKPOOL_TRY_ENTER_CRITICAL() IotMutex_TryLock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) - /** * @brief Exit a critical section by unlocking a mutex. * diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index a5e9182e52..4e6c4e301e 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -82,7 +82,7 @@ static _metrics_t _metrics = { 0 }; static uint32_t _metricsFlagSnapshot[ DEFENDER_METRICS_GROUP_COUNT ]; /* Report id integer. */ -uint64_t _AwsIotDefenderReportId = 0; +static uint64_t _AwsIotDefenderReportId = 0; /* Static storage holding the string of remote address: "ip:port". */ static char _remoteAddr[ REMOTE_ADDR_LENGTH ] = ""; @@ -260,7 +260,7 @@ static void _serialize( void ) /* Append key-value pair of "report_Id" which uses clock time. */ serializerError = _defenderEncoder.appendKeyValue( &headerMap, REPORTID_TAG, - IotSerializer_ScalarSignedInt( _AwsIotDefenderReportId ) ); + IotSerializer_ScalarSignedInt( ( int64_t ) _AwsIotDefenderReportId ) ); assertNoError( serializerError ); /* Append key-value pair of "version". */ diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 1df5920130..de1abf5cf1 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -125,7 +125,6 @@ * Constants relating to PUBLISH and PUBACK packets, defined by MQTT * 3.1.1 spec. */ -#define MQTT_PACKET_PUBLISH_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid PUBLISH packet. */ #define MQTT_PACKET_PUBACK_SIZE ( 4 ) /**< @brief A PUBACK packet is always 4 bytes in size. */ #define MQTT_PACKET_PUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief A PUBACK packet always has a "Remaining length" of 2. */ @@ -134,7 +133,6 @@ * 3.1.1 spec. */ #define MQTT_PACKET_SUBACK_MINIMUM_SIZE ( 5 ) /**< @brief The size of the smallest valid SUBACK packet. */ -#define MQTT_PACKET_UNSUBACK_SIZE ( 4 ) /**< @brief An UNSUBACK packet is always 4 bytes in size. */ #define MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief An UNSUBACK packet always has a "Remaining length" of 2. */ /* diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index b2f624789a..8a7b8fc903 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -49,7 +49,6 @@ #define MAGIC_NUMBER_32BIT_3 ( 0x0000000F ) #define MAGIC_NUMBER_8BIT_1 ( 0xA5 ) -#define MAGIC_NUMBER_8BIT_2 ( 0xF0 ) /*-----------------------------------------------------------*/ diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index f6b4afd85e..a6762ff7b5 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -358,7 +358,7 @@ static void BlankExecution( IotTaskPool_t * pTaskPool, /** * @brief Legal initialization configurations. */ -IotTaskPoolInfo_t tpInfoLegal[ LEGAL_INFOS ] = +static IotTaskPoolInfo_t tpInfoLegal[ LEGAL_INFOS ] = { { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, @@ -368,7 +368,7 @@ IotTaskPoolInfo_t tpInfoLegal[ LEGAL_INFOS ] = /** * @brief Illegal initialization configurations. */ -IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = +static IotTaskPoolInfo_t tpInfoIllegal[ ILLEGAL_INFOS ] = { { .minThreads = 0, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, { .minThreads = 1, .maxThreads = 0, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }, @@ -441,7 +441,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) /* Create a task pool a tweak max threads up & down. */ { - uint32_t count; IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ] = { { 0 } }; IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c index 20cc4e5019..f7047a16d3 100644 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ b/tests/mqtt/system/iot_tests_mqtt_stress.c @@ -457,6 +457,7 @@ TEST_SETUP( MQTT_Stress ) /* Set the members of the connect info. */ connectInfo.cleanSession = true; + connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 62cc4431db..767accf547 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -535,7 +535,7 @@ TEST( MQTT_Unit_Subscription, ListFindByPacket ) */ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) { - long i = 0; + int32_t i = 0; /* On empty list (should not crash). */ _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, diff --git a/tests/serializer/iot_test_serializer_cbor.c b/tests/serializer/iot_test_serializer_cbor.c index f5eccded9c..4173e53215 100644 --- a/tests/serializer/iot_test_serializer_cbor.c +++ b/tests/serializer/iot_test_serializer_cbor.c @@ -45,7 +45,7 @@ static IotSerializerEncoderObject_t _encoderObject; -uint8_t _buffer[ BUFFER_SIZE ]; +static uint8_t _buffer[ BUFFER_SIZE ]; TEST_GROUP( Full_Serializer_CBOR ); From 7fa2685dfc29166d61e7c8ec0c9c5a5c5798d78c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 26 Apr 2019 15:37:55 -0700 Subject: [PATCH 116/844] Update style guide documentation (#397) --- doc/guide/style.txt | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/doc/guide/style.txt b/doc/guide/style.txt index 55a45b0aad..7b04e3718e 100644 --- a/doc/guide/style.txt +++ b/doc/guide/style.txt @@ -51,7 +51,6 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Standard includes. */ #include -#include #include #include #include @@ -61,6 +60,9 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* Library internal include. */ #include "private/iot_library_internal.h" +/* Error handling include (include only when needed). */ +#include "private/iot_error.h" + /* Library application-facing headers are included last. They use quotes "" around the file name. */ /* Library include. */ @@ -72,11 +74,11 @@ See @ref guide_developer_styleguide_naming for how to name the functions, variab /* When possible, parentheses () should be placed around contant values and a type * should be specified. */ -#define _LIBRARY_CONSTANT ( ( int32_t ) 10 ) +#define LIBRARY_CONSTANT ( ( int32_t ) 10 ) -#define _LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ - { \ /* Function-like macros are surrounded by curly braces {}. */ - macro_body( argument ); \ +#define LIBRARY_FUNCTION_MACRO( argument ) \ /* Line continuators are right-aligned. */ + { \ /* Function-like macros are surrounded by curly braces {}. */ + macro_body( argument ); \ } /*-----------------------------------------------------------*/ @@ -135,24 +137,26 @@ static bool _libraryStaticFunction( void * pArgument, int32_t localVariable = 0; int32_t * pLocalPointer = ( int32_t * ) pArgument; + /* Error handling setup, which declares a status variable type and initial + * value. */ + IOT_FUNCTION_ENTRY( bool, true ); + /* All functions make generous use of the logging library. */ IotLogInfo( "Performing calculation..." ); - /* Checking parameters at the beginning of functions and returning on bad - * parameter values is encouraged. */ if( ( pArgument == NULL ) || ( argumentLength == 0 ) ) /* Note the parentheses and spacing in if statements */ { IotLogError( "Bad parameters." ); - return false; + /* Error handling exit. */ + IOT_SET_AND_GOTO_CLEANUP( false ); } for( i = 0; i < argumentLength; i++ ) /* Note the spacing in the for loop. */ { localVariable += IotLibrary_ExternalFunction( pArgument ); - /* Use portable format specifier for fixed-width integer. */ - IotLogDebug( "Current value is " PRId32 ".", localVariable ); + IotLogDebug( "Current value is %d.", ( int ) localVariable ); } if( localVariable < 0 ) @@ -162,7 +166,8 @@ static bool _libraryStaticFunction( void * pArgument, IotLogInfo( "Calculation done." ); - return true; + /* Error handling exit. */ + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /* A separator is placed between all function implementations. */ @@ -172,7 +177,7 @@ static bool _libraryStaticFunction( void * pArgument, bool IotLibrary_ApplicationFunction( void ) /* Functions with no arguments have void in their argument list. */ { - _LIBRARY_FUNCTION_MACRO( _globalArray ); + LIBRARY_FUNCTION_MACRO( _globalArray ); return true; } @@ -189,7 +194,7 @@ The naming convention aims to differentiate this SDK's files, variables, and fun - The first characters of all publicly visible names should identify the name as part of this SDK.
Example: For general-purpose libraries (such as MQTT), names start with `iot_` or `Iot`.
Example: For libraries specific to AWS IoT (such as Shadow), names start with `aws_iot_` or `AwsIot`. -- Words in names should be ordered with the most general word first and the most specific word last. Names for internal use begin with `_`.
+- Words in names should be ordered with the most general word first and the most specific word last.
Example: `iot_mqtt_api.c` identifies a file as part of the general MQTT library. `_IotMqtt_ValidateConnect` identifies a function as part of the Internal component of the general MQTT library. - Names should avoid using abbreviations. @@ -200,9 +205,9 @@ The naming convention aims to differentiate this SDK's files, variables, and fun @formattableentry{`IOT_`LIBRARY`_`DESCRIPTION
`AWS_IOT_`LIBRARY`_`DESCRIPTION,Defined constants and enum values in application-facing library header files,`IOT_MQTT_SUCCESS` (iot_mqtt.h)
`AWS_IOT_SHADOW_SUCCESS` (aws_iot_shadow.h)} @formattableentry{`IOT_DEMO_`DESCRIPTION
`IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests,`IOT_DEMO_MQTT_PUBLISH_BURST_SIZE`
`IOT_TEST_MQTT_THREADS`} @formattableentry{`AWS_IOT_DEMO_`DESCRIPTION
`AWS_IOT_TEST_`DESCRIPTION,Defined constants and enum values in demos and tests specific to AWS IoT,`AWS_IOT_DEMO_THING_NAME`
`AWS_IOT_TEST_SHADOW_THING_NAME`} -@formattableentry{`_`DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal namesin AWS-specific libraries,`MQTT_PACKET_TYPE_CONNECT`
(iot_mqtt_internal.h)
`SHADOW_OPERATION_COUNT`
(aws_iot_shadow_internal.h)} +@formattableentry{DESCRIPTION,Internal constants and enum values
No `AWS_` prefix for internal names in AWS-specific libraries,`MQTT_PACKET_TYPE_CONNECT`
(iot_mqtt_internal.h)
`SHADOW_OPERATION_COUNT`
(aws_iot_shadow_internal.h)} -Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `IOT_` or `AWS_IOT_`, while names intended for internal use must begin with only `_`. +Names of constants and enum values should contain only uppercase letters and underscores. All names intended for application use must begin with `IOT_` or `AWS_IOT_`. @subsection guide_developer_styleguide_naming_files Files @brief Naming convention for files. From f394d404d690a3b3d940168b70eaa659d9437613 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 29 Apr 2019 12:27:20 -0700 Subject: [PATCH 117/844] Enhancements to CMake build system (#398) --- CMakeLists.txt | 16 ++++++++++++++++ lib/source/common/CMakeLists.txt | 2 +- lib/source/defender/CMakeLists.txt | 2 +- lib/source/mqtt/CMakeLists.txt | 2 +- lib/source/serializer/CMakeLists.txt | 2 +- lib/source/shadow/CMakeLists.txt | 2 +- platform/include/iot_atomic.h | 6 ------ platform/source/posix/CMakeLists.txt | 2 +- scripts/ci_test_common.sh | 5 ++--- scripts/ci_test_coverage.sh | 3 +-- scripts/ci_test_mqtt.sh | 5 ++--- scripts/ci_test_shadow.sh | 5 ++--- third_party/tinycbor/CMakeLists.txt | 2 +- third_party/unity/CMakeLists.txt | 2 +- 14 files changed, 31 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84c6cacd03..d57adcc1b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,17 @@ if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) message( FATAL_ERROR "In-source build is not allowed. Please build in a separate directory, such as ${PROJECT_SOURCE_DIR}/build." ) endif() +# Configure options to always show in CMake GUI. +option( BUILD_SHARED_LIBS + "Set this to ON to build all libraries as shared libraries. When OFF, libraries build as static libraries." + ON ) +option( IOT_ATOMIC_USE_PORT + "Set this to ON to use a custom atomic port. When OFF, the build system will choose an atomic port." + OFF ) +option( IOT_BUILD_TESTS + "Set this to ON to build both demo and test executables. When OFF, only demo executables are built." + OFF ) + # Check for system support. list( APPEND SUPPORTED_SYSTEMS "Linux" ) @@ -53,6 +64,11 @@ else() include_directories( ${PROJECT_SOURCE_DIR}/demos ) endif() +# Use a custom atomic port if the CMake option is set. +if( ${IOT_ATOMIC_USE_PORT} ) + add_definitions( -DIOT_ATOMIC_USE_PORT=1 ) +endif() + # Platform libraries. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index b64d452ba1..5eeb3df47f 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,5 +1,5 @@ # Common libraries source files. -add_library( iotcommon SHARED +add_library( iotcommon iot_init.c iot_logging.c iot_taskpool.c diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt index 0dfcd46bdc..b2cb2e7c85 100644 --- a/lib/source/defender/CMakeLists.txt +++ b/lib/source/defender/CMakeLists.txt @@ -1,5 +1,5 @@ # Serializer library source files. -add_library( awsiotdefender SHARED +add_library( awsiotdefender aws_iot_defender_api.c aws_iot_defender_collector.c aws_iot_defender_mqtt.c ) diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index b7646a52ad..82de0252f1 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,5 +1,5 @@ # MQTT library source files. -add_library( iotmqtt SHARED +add_library( iotmqtt iot_mqtt_api.c iot_mqtt_network.c iot_mqtt_operation.c diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index e2ef090cb6..fc75ec35eb 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -1,5 +1,5 @@ # Serializer library source files. -add_library( iotserializer SHARED +add_library( iotserializer iot_json_utils.c cbor/iot_serializer_tinycbor_decoder.c cbor/iot_serializer_tinycbor_encoder.c ) diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index b3db46817d..834053517e 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -1,5 +1,5 @@ # Shadow library source files. -add_library( awsiotshadow SHARED +add_library( awsiotshadow aws_iot_shadow_api.c aws_iot_shadow_operation.c aws_iot_shadow_parser.c diff --git a/platform/include/iot_atomic.h b/platform/include/iot_atomic.h index 154b353bae..50060b175d 100644 --- a/platform/include/iot_atomic.h +++ b/platform/include/iot_atomic.h @@ -25,9 +25,6 @@ * * This file first checks if an atomic port is provided. * - * Otherwise, if the operating system is Amazon FreeRTOS, the atomic header - * provided with Amazon FreeRTOS is used. - * * Otherwise, this file checks the compiler and chooses an appropriate atomic * header depending on the compiler. * @@ -42,9 +39,6 @@ /* Use an atomic port if provided. */ #if IOT_ATOMIC_USE_PORT == 1 #include "atomic/iot_atomic_port.h" -#elif defined( __free_rtos__ ) - /* Use the FreeRTOS atomic operation header on FreeRTOS. */ - #include "atomic.h" #elif defined( __GNUC__ ) /* Both clang and gcc define __GNUC__, but only clang defines __clang__ */ #ifdef __clang__ diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index c91fa043ea..76cf8b1fd9 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -71,7 +71,7 @@ if( NOT DEFINED TLS_LIBRARY_LINKER_FLAG ) endif() # Platform libraries source files. -add_library( iotplatform SHARED +add_library( iotplatform iot_clock_posix.c iot_threads_posix.c ${NETWORK_SOURCE_FILE} diff --git a/scripts/ci_test_common.sh b/scripts/ci_test_common.sh index 6a28100386..3b9a4f6008 100644 --- a/scripts/ci_test_common.sh +++ b/scripts/ci_test_common.sh @@ -10,15 +10,14 @@ CMAKE_FLAGS="$COMPILER_OPTIONS" # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 +make -j2 iot_tests_common # Run common tests. ./bin/iot_tests_common # Rebuild in static memory mode. -rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 +make -j2 iot_tests_common # Run common tests in static memory mode. ./bin/iot_tests_common diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index d2a509a67c..79a192e932 100644 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -20,9 +20,8 @@ make -j2 ./bin/aws_iot_tests_shadow ./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" -# Generate code coverage results, excluding Unity test framework, demo files, tests files, and third party files. +# Generate code coverage results, excluding demo files, tests files, and third party files. lcov --directory . --capture --output-file coverage.info -lcov --remove coverage.info '*unity*' --output-file coverage.info lcov --remove coverage.info '*demo*' --output-file coverage.info lcov --remove coverage.info '*tests*' --output-file coverage.info lcov --remove coverage.info '*third_party*' --output-file coverage.info diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index 96c11503c9..21d2fdb768 100644 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -29,15 +29,14 @@ fi # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 +make -j2 iot_tests_mqtt iot_demo_mqtt # Run tests and demos. run_tests_and_demos # Rebuild in static memory mode. -rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 +make -j2 iot_tests_mqtt # Run tests in static memory mode. ./bin/iot_tests_mqtt diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index 346e43f6ce..985a172f25 100644 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -23,15 +23,14 @@ CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\" # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 +make -j2 aws_iot_tests_shadow aws_iot_demo_shadow # Run tests and demos. run_tests_and_demos # Rebuild in static memory mode. -rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 +make -j2 aws_iot_tests_shadow aws_iot_demo_shadow # Run tests and demos in static memory mode. run_tests_and_demos diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 8faf7601a8..150be0cb3c 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -1,5 +1,5 @@ # Tinycbor library source files. -add_library( tinycbor SHARED +add_library( tinycbor tinycbor/src/cborencoder.c tinycbor/src/cborencoder_close_container_checked.c tinycbor/src/cborerrorstrings.c diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index 38c3ad0178..cbcf255f6c 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -1,5 +1,5 @@ # Unity test framework. -add_library( unity SHARED +add_library( unity unity/unity.c unity/fixture/unity_fixture.c unity/fixture/unity_memory_mt.c ) From 47d55be8527bff6fc7f64eb04fa1bedfd24dc2c0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 1 May 2019 09:27:27 -0700 Subject: [PATCH 118/844] Automatically clone TinyCBOR if not available (#401) --- .gitmodules | 3 --- CMakeLists.txt | 3 +++ cbmc/proofs/Makefile.common | 8 ++------ doc/guide/porting.txt | 2 +- third_party/tinycbor/CMakeLists.txt | 21 ++++++++++++++++++++- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0944d04412..2c12f1d5d2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "third_party/unity/unity"] - path = third_party/unity/unity - url = https://github.com/ThrowTheSwitch/Unity.git [submodule "third_party/tinycbor/tinycbor"] path = third_party/tinycbor/tinycbor url = https://github.com/intel/tinycbor.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d57adcc1b8..409d092d1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,9 @@ option( IOT_ATOMIC_USE_PORT option( IOT_BUILD_TESTS "Set this to ON to build both demo and test executables. When OFF, only demo executables are built." OFF ) +option( IOT_BUILD_CLONE_SUBMODULES + "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." + ON ) # Check for system support. list( APPEND SUPPORTED_SYSTEMS "Linux" ) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 54e59e6fda..53c52dcd39 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -33,12 +33,8 @@ CFLAGS += $(CFLAGS2) $(INC) $(DEF) %.goto : %.c $(GOTO_CC) -o $@ $(CFLAGS) $< -$(MQTT)/third_party/tinycbor/tinycbor/README: - cd $(MQTT)/third_party/tinycbor && \ - git clone https://github.com/intel/tinycbor.git - -$(MQTT)/build: $(MQTT)/third_party/tinycbor/tinycbor/README - (cd $(MQTT) && mkdir -p build && cd build && cmake ..) 2>&1 \ +$(MQTT)/build: + (mkdir -p $(MQTT)/build; cd $(MQTT)/build; cmake ..) 2>&1 \ | tee $(ENTRY)0.txt \ ; exit $${PIPESTATUS[0]} diff --git a/doc/guide/porting.txt b/doc/guide/porting.txt index 105401dbe2..a36aef0470 100644 --- a/doc/guide/porting.txt +++ b/doc/guide/porting.txt @@ -57,7 +57,7 @@ As relative paths from the SDK's root directory: - `third_party/`
Third-party library code. This directory (and all its subdirectories) should be copied and not modified. - `tinycbor/`
- [Intel's tinyCBOR](https://github.com/intel/tinycbor), consumed as a Git submodule. + [Intel's TinyCBOR](https://github.com/intel/tinycbor), consumed as a Git submodule. - `unity/`
[Unity test framework](https://github.com/ThrowTheSwitch/Unity). Modifications were made to make its `malloc` overrides thread-safe. diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 150be0cb3c..e355f04a71 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -1,4 +1,23 @@ -# Tinycbor library source files. +# Check if the TinyCBOR source directory exists. +if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src/ ) + # Attempt to clone the TinyCBOR submodule. + if( ${IOT_BUILD_CLONE_SUBMODULES} ) + find_package( Git REQUIRED ) + + message( "Cloning submodule TinyCBOR" ) + execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init third_party/tinycbor/tinycbor + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE TINYCBOR_CLONE_RESULT ) + + if( NOT ${TINYCBOR_CLONE_RESULT} STREQUAL "0" ) + message( FATAL_ERROR "Failed to clone TinyCBOR submodule." ) + endif() + else() + message( FATAL_ERROR "The required submodule TinyCBOR does not exist. Either clone it manually, or set IOT_BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) + endif() +endif() + +# TinyCBOR library source files. add_library( tinycbor tinycbor/src/cborencoder.c tinycbor/src/cborencoder_close_container_checked.c From ae7cff4539ff920d858461419aa4b71aa89d9b20 Mon Sep 17 00:00:00 2001 From: qiutongs Date: Wed, 1 May 2019 11:39:23 -0700 Subject: [PATCH 119/844] add metrics init in defender test (#402) --- lib/source/common/iot_init.c | 3 --- tests/defender/aws_iot_tests_defender.c | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index 3133b011ea..eec4f69938 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -33,9 +33,6 @@ /* Task pool include. */ #include "iot_taskpool.h" -/* Metrics include. */ -#include "platform/iot_metrics.h" - /* Static memory include (if dynamic memory allocation is disabled). */ #include "private/iot_static_memory.h" diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index 26c6066c4c..b35a7a4195 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -34,6 +34,9 @@ /* SDK initialization include. */ #include "iot_init.h" +/* Metrics include. */ +#include "platform/iot_metrics.h" + /* MQTT include. */ #include "iot_mqtt.h" @@ -91,6 +94,11 @@ int main( int argc, return EXIT_FAILURE; } + if (IotMetrics_Init() == false) + { + return EXIT_FAILURE; + } + /* Set up the network stack. */ if ( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) { From e61f0d84611c4cc1cfba9dfada5d1c0e266e9413 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 2 May 2019 10:26:51 -0700 Subject: [PATCH 120/844] Move Defender doc mainpage out of header (#403) --- doc/config/platform | 6 ++- doc/lib/defender.txt | 53 +++++++++++++++++++ lib/include/aws_iot_defender.h | 46 +--------------- platform/source/posix/CMakeLists.txt | 2 +- .../posix/{network => }/iot_network_openssl.c | 0 third_party/tinycbor/CMakeLists.txt | 2 +- 6 files changed, 61 insertions(+), 48 deletions(-) create mode 100644 doc/lib/defender.txt rename platform/source/posix/{network => }/iot_network_openssl.c (100%) diff --git a/doc/config/platform b/doc/config/platform index c26f141ccd..4a1a6b736e 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -19,8 +19,9 @@ INPUT = doc/lib \ # Library file names. FILE_PATTERNS = platform.txt \ - iot_network.h \ iot_clock.h \ + iot_metrics.h \ + iot_network.h \ iot_threads.h \ iot_platform_types.h @@ -28,4 +29,5 @@ FILE_PATTERNS = platform.txt \ TAGFILES = doc/tag/main.tag=../main \ doc/tag/logging.tag=../logging \ doc/tag/mqtt.tag=../mqtt \ - doc/tag/shadow.tag=../shadow + doc/tag/shadow.tag=../shadow \ + doc/tag/defender.tag=../defender diff --git a/doc/lib/defender.txt b/doc/lib/defender.txt new file mode 100644 index 0000000000..4440d67b27 --- /dev/null +++ b/doc/lib/defender.txt @@ -0,0 +1,53 @@ +/** +@mainpage +@anchor defender +@brief AWS IoT Device Defender library. + +> AWS IoT Device Defender is a fully managed service that helps you secure your fleet of IoT devices. AWS IoT Device Defender continuously audits your IoT configurations to make sure that they aren’t deviating from security bestractices. A configuration is a set of technical controls you set to help keep information secure when devices are communicating with each other and the cloud. AWS IoT Device Defender makes it easy to maintain and enforce IoTonfigurations, such as ensuring device identity, authenticating and authorizing devices, and encrypting device data. AWS IoT Device Defender continuously audits the IoT configurations on your devices against a set of predefinedecurity best practices. AWS IoT Device Defender sends an alert if there are any gaps in your IoT configuration that might create a security risk, such as identity certificates being shared across multiple devices or a device with aevoked identity certificate trying to connect to AWS IoT Core. + +Description of Device Defender from [AWS IoT documentation](https://aws.amazon.com/iot-device-defender/)
+ +This library provides an API for interacting with Device Defender. + +@dependencies{defender,Defender library} +@dot "Device Defender direct dependencies" +digraph defender_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + + defender[label="Device Defender", fillcolor="#cc00ccff"]; + + subgraph + { + mqtt[label="MQTT", fillcolor="#cc00ccff", URL="@ref mqtt"]; + } + subgraph + { + node[fillcolor="#aed8a9ff"]; + rank = same; + logging[label="Logging", URL="@ref logging"]; + static_memory[label="Static memory", URL="@ref static_memory"]; + linear_containers[label="List/Queue", URL="@ref linear_containers"]; + taskpool[label="Taskpool", URL="@ref taskpool"]; + } + subgraph + { + node[fillcolor="#e89025ff"]; + rank = same; + platform_threads[label="Thread", URL="@ref platform_threads"]; + platform_clock[label="Clock", URL="@ref platform_clock"]; + platform_metrics[label="Metrics", URL="@ref platform_metrics"]; + } + + defender -> mqtt; + defender -> linear_containers; + defender -> logging[label=" if logging enabled", style="dashed"]; + defender -> static_memory[label=" if static memory only", style="dashed"]; + defender -> taskpool; + defender -> platform_threads; + defender -> platform_clock; + defender -> platform_metrics; +} +@enddot +*/ diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index 653f4092c0..c113659150 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -21,49 +21,7 @@ /** * @file aws_iot_defender.h - * @brief User-facing functions and structs of AWS IoT Device Defender libraries - * - * [Link to AWS documentation](https://docs.aws.amazon.com/iot/latest/developerguide/device-defender-detect.html) - * - */ - -/** - * @mainpage - * - * ## Introduction - * AWS IoT Device Defender is an IoT security service that allows you to audit the configuration of your devices, - * monitor connected devices to detect abnormal behavior, and to mitigate security risks. - * Part of it relies on an dedicated agent to collect device-side metrics and send to AWS Iot. - * - * Amazon FreeRTOS provides a library that allows your Amazon FreeRTOS-based devices to work with AWS IoT Device Defender. - * - * ## Dependencies - * - * @dot "Device Defender Library dependencies" - * digraph library_dependencies - * { - * node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; - * edge[fontname=Helvetica, fontsize=10]; - * - * defender[label="Device Defender", fillcolor="#cc00ccff"]; - * - * logging[label="Logging", fillcolor="#aed8a9ff", URL="@ref logging"]; - * static_memory[label="Static memory", fillcolor="#aed8a9ff", URL="@ref static_memory"]; - * linear_containers[label="List/Queue", fillcolor="#aed8a9ff", URL="@ref linear_containers"]; - * taskpool[label="Taskpool", fillcolor="#aed8a9ff", URL="@ref taskpool"] - * mqtt[label="MQTT", fillcolor="#aed8a9ff", URL="@ref mqtt"] - * platform_threads[label="Thread", fillcolor="#e89025ff", URL="@ref platform_threads"]; - * platform_clock[label="Clock", fillcolor="#e89025ff", URL="@ref platform_clock"]; - * - * defender -> linear_containers; - * defender -> logging [label=" if logging enabled", style="dashed"]; - * defender -> static_memory [label=" if static memory only", style="dashed"]; - * defender -> platform_threads; - * defender -> platform_clock; - * defender -> taskpool; - * defender -> mqtt; - * } - * @enddot + * @brief User-facing functions and structs of AWS IoT Device Defender library. */ #ifndef AWS_IOT_DEFENDER_H_ @@ -421,4 +379,4 @@ uint32_t AwsIotDefender_GetPeriod( void ); const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); /* @[declare_defender_strerror] */ -#endif /* end of include guard: AWS_IOT_DEFENDER_H_ */ +#endif /* AWS_IOT_DEFENDER_H_ */ diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 76cf8b1fd9..bf54445105 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -59,7 +59,7 @@ if( ${OPENSSL_FOUND} ) endif() # Choose OpenSSL network source file. - set( NETWORK_SOURCE_FILE network/iot_network_openssl.c ) + set( NETWORK_SOURCE_FILE iot_network_openssl.c ) # Link OpenSSL. set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) diff --git a/platform/source/posix/network/iot_network_openssl.c b/platform/source/posix/iot_network_openssl.c similarity index 100% rename from platform/source/posix/network/iot_network_openssl.c rename to platform/source/posix/iot_network_openssl.c diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index e355f04a71..0b96cdf53f 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -4,7 +4,7 @@ if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src/ ) if( ${IOT_BUILD_CLONE_SUBMODULES} ) find_package( Git REQUIRED ) - message( "Cloning submodule TinyCBOR" ) + message( "Cloning submodule TinyCBOR." ) execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init third_party/tinycbor/tinycbor WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE TINYCBOR_CLONE_RESULT ) From 2c2d46a099de82edcac3bbafdc16c2fbedd42e4b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 2 May 2019 14:36:18 -0700 Subject: [PATCH 121/844] Make OpenSSL network implementation compatible with IPv6 (#404) --- platform/source/posix/iot_network_openssl.c | 42 ++++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/platform/source/posix/iot_network_openssl.c b/platform/source/posix/iot_network_openssl.c index 0126087146..da804efc63 100644 --- a/platform/source/posix/iot_network_openssl.c +++ b/platform/source/posix/iot_network_openssl.c @@ -102,12 +102,12 @@ */ typedef struct _networkConnection { - int socket; /**< @brief Socket associated with this connection. */ - SSL * pSslContext; /**< @brief SSL context for connection. */ - IotMutex_t sslMutex; /**< @brief Prevents concurrent use of the SSL context. */ + int socket; /**< @brief Socket associated with this connection. */ + SSL * pSslContext; /**< @brief SSL context for connection. */ + IotMutex_t sslMutex; /**< @brief Prevents concurrent use of the SSL context. */ - bool receiveThreadCreated; /**< @brief Whether a receive thread exists for this connection. */ - pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ + bool receiveThreadCreated; /**< @brief Whether a receive thread exists for this connection. */ + pthread_t receiveThread; /**< @brief Thread that handles receiving on this connection. */ IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ void * pReceiveContext; /**< @brief The context for the receive callback. */ @@ -203,7 +203,8 @@ static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) int tcpSocket = -1; const uint16_t netPort = htons( pServerInfo->port ); struct addrinfo * pListHead = NULL, * pAddressInfo = NULL; - struct sockaddr_in * pServer = NULL; + struct sockaddr * pServer = NULL; + socklen_t serverLength = 0; /* Perform a DNS lookup of host name. */ IotLogDebug( "Performing DNS lookup of %s", pServerInfo->pHostName ); @@ -234,15 +235,28 @@ static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) continue; } - /* Set the port for the connection. */ IotLogDebug( "Socket creation successful. Attempting connection." ); - pServer = ( struct sockaddr_in * ) ( pAddressInfo->ai_addr ); - pServer->sin_port = netPort; + + /* Set the port for the connection based on protocol. */ + pServer = pAddressInfo->ai_addr; + + if( pServer->sa_family == AF_INET ) + { + /* IPv4. */ + ( ( struct sockaddr_in * ) pServer )->sin_port = netPort; + serverLength = sizeof( struct sockaddr_in ); + } + else + { + /* IPv6. */ + ( ( struct sockaddr_in6 * ) pServer )->sin6_port = netPort; + serverLength = sizeof( struct sockaddr_in6 ); + } /* Attempt to connect. */ if( connect( tcpSocket, - ( struct sockaddr * ) pServer, - sizeof( struct sockaddr ) ) == -1 ) + pServer, + serverLength ) == -1 ) { /* Connect failed; close socket and try next record. */ if( close( tcpSocket ) != 0 ) @@ -651,7 +665,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, /* Cast function parameters to correct types. */ const IotNetworkServerInfoOpenssl_t * const pServerInfo = pConnectionInfo; const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials = pCredentialInfo; - _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t** const ) pConnection; + _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t ** const ) pConnection; /* Allocate memory for a new connection. */ pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); @@ -757,8 +771,8 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, if( posixError != 0 ) { IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pNetworkConnection->socket, - posixError ); + pNetworkConnection->socket, + posixError ); status = IOT_NETWORK_SYSTEM_ERROR; } else From da172b50f7965a50def4df52919305ffba04ec27 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 6 May 2019 09:00:34 -0700 Subject: [PATCH 122/844] Move Defender metrics into platform (#405) --- demos/iot_config.h | 3 - doc/config/platform | 4 +- doc/lib/platform.txt | 40 ++- lib/include/iot_linear_containers.h | 2 +- lib/include/platform/iot_metrics.h | 165 ++++------- lib/include/types/iot_platform_types.h | 33 +++ lib/source/common/CMakeLists.txt | 1 - .../static_memory/iot_static_memory_metrics.c | 149 ---------- .../defender/aws_iot_defender_collector.c | 266 ++++++------------ platform/include/iot_network_metrics.h | 57 ++++ platform/include/posix/iot_network_openssl.h | 9 +- platform/source/network/iot_network_metrics.c | 259 +++++++++++++++++ platform/source/posix/CMakeLists.txt | 5 +- platform/source/posix/iot_network_openssl.c | 92 ++++++ platform/source/posix/linux/iot_metrics.c | 160 ----------- .../posix/linux/iot_network_openssl_metrics.c | 116 -------- tests/defender/aws_iot_tests_defender_api.c | 7 +- tests/iot_config.h | 7 - 18 files changed, 633 insertions(+), 742 deletions(-) delete mode 100644 lib/source/common/static_memory/iot_static_memory_metrics.c create mode 100644 platform/include/iot_network_metrics.h create mode 100644 platform/source/network/iot_network_metrics.c delete mode 100644 platform/source/posix/linux/iot_metrics.c delete mode 100644 platform/source/posix/linux/iot_network_openssl_metrics.c diff --git a/demos/iot_config.h b/demos/iot_config.h index f52bc6d005..8f3cbd0384 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -65,9 +65,6 @@ #define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO -/* This is supposed to be defined as the socket data type. In linux, it is "int". */ -#define IotMetricsConnectionId_t int - /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ #include IOT_SYSTEM_TYPES_FILE diff --git a/doc/config/platform b/doc/config/platform index 4a1a6b736e..2067aa8471 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -20,9 +20,9 @@ INPUT = doc/lib \ # Library file names. FILE_PATTERNS = platform.txt \ iot_clock.h \ - iot_metrics.h \ - iot_network.h \ iot_threads.h \ + iot_network.h \ + iot_metrics.h \ iot_platform_types.h # External tag files required by this library. diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 2057d45121..ae25395358 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -10,13 +10,16 @@ All system calls (including networking) used in this SDK's libraries go through @copybrief platform_threads - @ref platform_network
@copybrief platform_network +- @ref platform_metrics
+ @copybrief platform_metrics -All of the functions in each component will need to be implemented to port the libraries to a new platform. Currently, implementations exist for the following platforms: +The following implementations are provided with this SDK as porting samples: Component | Supported platforms --------- | ------------------- @ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_network | [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) +@ref platform_metrics | Sample implementation using the @ref platform_network abstraction
This implementation is not intended for production use. */ /** @@ -56,6 +59,39 @@ Currently, the platform clock component has the following dependencies: - The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. */ +/** +@page platform_metrics Metrics +@brief @copybrief iot_metrics.h + +Device Defender reports metrics to monitor devices. This component of the platform layer is only required by Device Defender. It does not need to be implemented if Device Defender is not used. + +@dependencies{platform_metrics,platform metrics component} +@dot "Metrics direct dependencies" +digraph clock_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph + { + platform_metrics[label="Metrics", fillcolor="#e89025ff"]; + } + subgraph + { + rank = same; + rankdir = LR; + operating_system[label="Operating system", fillcolor="#999999ff"] + standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; + } + platform_metrics -> operating_system; + platform_metrics -> standard_library; +} +@enddot + +The platform metrics component is meant to query its data directory from the operating system. + +@note Platform metrics implementations are generally not portable, since they depend on non-portable operating system APIs. Because maintaining OS-specific implementations is beyond the scope of this SDK, the provided metrics implementation is a sample that calls in to other platform components, instead of the operating system. +*/ + /** @page platform_network Networking @brief @copybrief iot_network.h @@ -199,4 +235,6 @@ To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HE @copybrief platform_network_functions - @subpage platform_threads_functions
@copybrief platform_threads_functions +- @subpage platform_metrics_functions
+ @copybrief platform_metrics_functions */ diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index f548d595ec..42b65871e7 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -438,7 +438,7 @@ static inline void IotListDouble_InsertAfter( IotLink_t * const pElement, /* @[declare_linear_containers_list_double_insertsorted] */ static inline void IotListDouble_InsertSorted( IotListDouble_t * const pList, IotLink_t * const pLink, - int ( *compare )( const IotLink_t * const, const IotLink_t * const ) ) + int32_t ( *compare )( const IotLink_t * const, const IotLink_t * const ) ) /* @[declare_linear_containers_list_double_insertsorted] */ { /* This function must not be called with NULL parameters. */ diff --git a/lib/include/platform/iot_metrics.h b/lib/include/platform/iot_metrics.h index e2441ae28c..199c6148d5 100644 --- a/lib/include/platform/iot_metrics.h +++ b/lib/include/platform/iot_metrics.h @@ -21,148 +21,79 @@ /** * @file iot_metrics.h - * @brief User-facing functions and structs of Metrics library + * @brief Functions for retrieving [Device Defender](@ref defender) metrics. * + * The functions in this header are only required by Device Defender. They do not + * need to be implemented if Device Defender is not used. */ #ifndef IOT_METRICS_H_ #define IOT_METRICS_H_ -#include - /* The config header is always included first. */ #include "iot_config.h" -#include "iot_linear_containers.h" - -/** - * @def IotMetrics_Assert( expression ) - * @brief Assertion macro for the Metrics library. - * - * @param[in] expression Expression to be evaluated. - */ -#if IOT_METRICS_ENABLE_ASSERTS == 1 - #ifndef IotMetrics_Assert - #include - #define IotMetrics_Assert( expression ) assert( expression ) - #endif -#else - #define IotMetrics_Assert( expression ) -#endif - -/* - * Provide default values for undefined memory allocation functions based on - * the usage of dynamic memory allocation. - */ -#if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" - -/** - * @brief Allocate an array of uint8_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef IotMetrics_MallocTcpConnection - #define IotMetrics_MallocTcpConnection Iot_MallocMetricsTcpConnection - #endif - -/** - * @brief Free an array of uint8_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef IotMetrics_FreeTcpConnection - #define IotMetrics_FreeTcpConnection Iot_FreeMetricsTcpConnection - #endif +/* Standard includes. */ +#include -/** - * @brief Allocate an array of uint8_t. This function should have the same - * signature as [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #ifndef IotMetrics_MallocIpAddress - #define IotMetrics_MallocIpAddress Iot_MallocMetricsIpAddress - #endif - -/** - * @brief Free an array of uint8_t. This function should have the same - * signature as [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #ifndef IotMetrics_FreeIpAddress - #define IotMetrics_FreeIpAddress Iot_FreeMetricsIpAddress - #endif - -#else /* if IOT_STATIC_MEMORY_ONLY */ - #include - - #ifndef IotMetrics_MallocTcpConnection - #define IotMetrics_MallocTcpConnection malloc - #endif - - #ifndef IotMetrics_FreeTcpConnection - #define IotMetrics_FreeTcpConnection free - #endif - - #ifndef IotMetrics_MallocIpAddress - #define IotMetrics_MallocIpAddress malloc - #endif - - #ifndef IotMetrics_FreeIpAddress - #define IotMetrics_FreeIpAddress free - #endif - -#endif /* if IOT_STATIC_MEMORY_ONLY */ - -/* This data type varies on different platform. */ -#ifndef IotMetricsConnectionId_t - #error "'IotMetricsConnectionId_t' must be defended as the socket handle data type." -#endif - -/* xxx.xxx.xxx.xxx\0 */ -#define IOT_METRICS_MAX_IP_STRING_LENGTH 16 +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" /** - * Data handle of TCP connection + * @functionspage{platform_metrics,platform metrics component,Metrics} + * - @functionname{platform_metrics_function_init} + * - @functionname{platform_metrics_function_cleanup} + * - @functionname{platform_metrics_function_gettcpconnections} */ -typedef struct IotMetricsTcpConnection -{ - IotLink_t link; - IotMetricsConnectionId_t id; - /* This is limited to IPv4. */ - uint32_t remoteIp; /* In host byte order. */ - char * pRemoteIp; - uint16_t remotePort; /* In host order. */ -} IotMetricsTcpConnection_t; /** - * Callback data handle to process specific metrics. + * @functionpage{IotMetrics_Init,platform_metrics,init} + * @functionpage{IotMetrics_Cleanup,platform_metrics,cleanup} + * @functionpage{IotMetrics_GetTcpConnections,platform_metrics,gettcpconnections} */ -typedef struct IotMetricsListCallback -{ - void * param1; - void ( * function )( void * param1, - IotListDouble_t * pMetricsList ); /* pMetricsList is guaranteed a valid metrics list. */ -} IotMetricsListCallback_t; /** - * This function must be called before any other functions. + * @brief One-time initialization function for the platform metrics component. + * + * This function initializes the platform metrics component. It must be called + * once (and only once) before calling any other metrics or [Device Defender function] + * (@ref defender_functions). Calling this function more than once without first + * calling @ref platform_metrics_function_cleanup may result in a crash. + * + * @return `true` is initialization succeeded; `false` otherwise. + * + * @warning No thread-safety guarantees are provided for this function. */ +/* @[declare_platform_metrics_init] */ bool IotMetrics_Init( void ); +/* @[declare_platform_metrics_init] */ /** - * Record one TCP connection metric. - */ -void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ); - -/** - * Remove one TCP connection metric. + * @brief One-time deinitialization function for the platform metrics component. + * + * This function frees resources taken in @ref platform_metrics_function_init. + * No other metrics or [Device Defender functions](@ref defender_functions) may + * be called unless @ref platform_metrics_function_init is called again. + * + * @warning No thread-safety guarantees are provided for this function. */ -void IotMetrics_RemoveTcpConnection( IotMetricsConnectionId_t tcpConnectionId ); +/* @[declare_platform_metrics_cleanup] */ +void IotMetrics_Cleanup( void ); +/* @[declare_platform_metrics_cleanup] */ /** - * Use a callback to process a list of TCP connections + * @brief Retrieve a list of active TCP connections from the system. + * + * The provided connections are reported by Device Defender. + * + * @param[in] pContext Context passed as the first parameter of `metricsCallback`. + * @param[in] metricsCallback Called by this function to provide the list of TCP + * connections. The list given by this function is should not be used after the + * callback returns. */ -void IotMetrics_ProcessTcpConnections( IotMetricsListCallback_t tcpConnectionsCallback ); +/* @[declare_platform_metrics_gettcpconnections] */ +void IotMetrics_GetTcpConnections( void * pContext, + void ( * metricsCallback )( void *, const IotListDouble_t * ) ); +/* @[declare_platform_metrics_gettcpconnections] */ #endif /* ifndef IOT_METRICS_H_ */ diff --git a/lib/include/types/iot_platform_types.h b/lib/include/types/iot_platform_types.h index 5d9c1c2b42..8405adcb51 100644 --- a/lib/include/types/iot_platform_types.h +++ b/lib/include/types/iot_platform_types.h @@ -30,6 +30,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Linear containers (lists and queues) include for metrics types. */ +#include "iot_linear_containers.h" + /*------------------------- Thread management types -------------------------*/ /** @@ -114,4 +117,34 @@ typedef void ( * IotThreadRoutine_t )( void * ); */ typedef _IotSystemTimer_t IotTimer_t; +/*------------------------------ Metrics types ------------------------------*/ + +/** + * @brief The length of the buffer used to store IP addresses for metrics. + * + * This is the length of the longest IPv6 address plus space for the port number + * and NULL terminator. + */ +#define IOT_METRICS_IP_ADDRESS_LENGTH 54 + +/** + * @brief Represents a TCP connection to a remote IPv4 server. + * + * A list of these is provided by @ref platform_metrics_function_gettcpconnections. + */ +typedef struct IotMetricsTcpConnection +{ + IotLink_t link; /**< @brief List link member. */ + void * pNetworkContext; /**< @brief Context that may be used by metrics or Defender. */ + size_t addressLength; /**< @brief The length of the address stored in #IotMetricsTcpConnection_t.pRemoteAddress. */ + + /** + * @brief NULL-terminated IP address and port in text format. + * + * IPv4 addresses will be in the format `xxx.xxx.xxx.xxx:port`. + * IPv6 addresses will be in the format `[xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:port`. + */ + char pRemoteAddress[ IOT_METRICS_IP_ADDRESS_LENGTH ]; +} IotMetricsTcpConnection_t; + #endif /* ifndef IOT_PLATFORM_TYPES_H_ */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 5eeb3df47f..23852fe155 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -7,7 +7,6 @@ add_library( iotcommon static_memory/iot_static_memory_taskpool.c static_memory/iot_static_memory_mqtt.c static_memory/aws_iot_static_memory_shadow.c - static_memory/iot_static_memory_metrics.c static_memory/iot_static_memory_serializer.c static_memory/aws_iot_static_memory_defender.c ) diff --git a/lib/source/common/static_memory/iot_static_memory_metrics.c b/lib/source/common/static_memory/iot_static_memory_metrics.c deleted file mode 100644 index 353f99e325..0000000000 --- a/lib/source/common/static_memory/iot_static_memory_metrics.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ - #include - #include - #include - -/* Static memory include. */ - #include "private/iot_static_memory.h" - -/* Metrics include. */ - #include "platform/iot_metrics.h" - - #ifndef IOT_METRICS_TCP_CONNECTIONS - #define IOT_METRICS_TCP_CONNECTIONS ( 10 ) - #endif - - #ifndef IOT_METRICS_IP_ADDRESSES - #define IOT_METRICS_IP_ADDRESSES ( 10 ) - #endif - -/* Validate static memory configuration settings. */ - #if IOT_METRICS_TCP_CONNECTIONS <= 0 - #error "IOT_METRICS_TCP_CONNECTIONS cannot be 0 or negative." - #endif - - #if IOT_METRICS_IP_ADDRESSES <= 0 - #error "IOT_METRICS_IP_ADDRESSES cannot be 0 or negative." - #endif - - #define _METRICS_TCP_CONNECTION_SIZE ( sizeof( IotMetricsTcpConnection_t ) * IOT_METRICS_TCP_CONNECTIONS ) - -/*-----------------------------------------------------------*/ - -/* Extern declarations of common static memory functions in iot_static_memory_common.c - * Because these functions are specific to this static memory implementation, they are - * not placed in the common static memory header file. */ - extern int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); - extern void IotStaticMemory_ReturnInUse( void * ptr, - void * const pTcpConnection, - bool * const pInUse, - int limit, - size_t elementSize ); - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ - static bool _inUseTcpConnections[ IOT_METRICS_TCP_CONNECTIONS ] = { 0 }; - static IotMetricsTcpConnection_t _tcpConnections[ IOT_METRICS_TCP_CONNECTIONS ][ _METRICS_TCP_CONNECTION_SIZE ] = { { { .link = { 0 } } } }; - - static bool _inUseIpAddresses[ IOT_METRICS_IP_ADDRESSES ] = { 0 }; - static char _ipAddresses[ IOT_METRICS_IP_ADDRESSES ][ IOT_METRICS_MAX_IP_STRING_LENGTH ] = { { 0 } }; - -/*-----------------------------------------------------------*/ - - void * Iot_MallocMetricsTcpConnection( size_t size ) - { - int freeIndex = -1; - void * pNewTcpConnection = NULL; - - if( size <= _METRICS_TCP_CONNECTION_SIZE ) - { - freeIndex = IotStaticMemory_FindFree( _inUseTcpConnections, - IOT_METRICS_TCP_CONNECTIONS ); - - if( freeIndex != -1 ) - { - pNewTcpConnection = &( _tcpConnections[ freeIndex ][ 0 ] ); - } - } - - return pNewTcpConnection; - } - -/*-----------------------------------------------------------*/ - - void Iot_FreeMetricsTcpConnection( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _tcpConnections, - _inUseTcpConnections, - IOT_METRICS_TCP_CONNECTIONS, - _METRICS_TCP_CONNECTION_SIZE ); - } - - -/*-----------------------------------------------------------*/ - - void * Iot_MallocMetricsIpAddress( size_t size ) - { - int freeIndex = -1; - void * pNewIpAddress = NULL; - - if( size <= IOT_METRICS_MAX_IP_STRING_LENGTH ) - { - /* Get the index of a free Shadow subscription. */ - freeIndex = IotStaticMemory_FindFree( _inUseIpAddresses, - IOT_METRICS_IP_ADDRESSES ); - - if( freeIndex != -1 ) - { - pNewIpAddress = &( _ipAddresses[ freeIndex ][ 0 ] ); - } - } - - return pNewIpAddress; - } - -/*-----------------------------------------------------------*/ - - void Iot_FreeMetricsIpAddress( void * ptr ) - { - /* Return the in-use Shadow subscription. */ - IotStaticMemory_ReturnInUse( ptr, - _ipAddresses, - _inUseIpAddresses, - IOT_METRICS_IP_ADDRESSES, - IOT_METRICS_MAX_IP_STRING_LENGTH ); - } - -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index 4e6c4e301e..5a51b18149 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -29,22 +29,20 @@ #include "platform/iot_clock.h" -#define HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) -#define REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) -#define VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) -#define VERSION_1_0 "1.0" /* Used by defender service to indicate the schema change of report, e.g. adding new field. */ -#define METRICS_TAG AwsIotDefenderInternal_SelectTag( "metrics", "met" ) - -/* 15 for IP + 1 for ":" + 5 for port + 1 terminator - * For example: "192.168.0.1:8000\0" - */ -#define REMOTE_ADDR_LENGTH 22 +/* Used in debugging. It will decode the report with cbor format and print to stdout. */ +#define DEBUG_CBOR_PRINT 0 + +#define HEADER_TAG AwsIotDefenderInternal_SelectTag( "header", "hed" ) +#define REPORTID_TAG AwsIotDefenderInternal_SelectTag( "report_id", "rid" ) +#define VERSION_TAG AwsIotDefenderInternal_SelectTag( "version", "v" ) +#define VERSION_1_0 "1.0" /* Used by defender service to indicate the schema change of report, e.g. adding new field. */ +#define METRICS_TAG AwsIotDefenderInternal_SelectTag( "metrics", "met" ) -#define TCP_CONN_TAG AwsIotDefenderInternal_SelectTag( "tcp_connections", "tc" ) -#define EST_CONN_TAG AwsIotDefenderInternal_SelectTag( "established_connections", "ec" ) -#define TOTAL_TAG AwsIotDefenderInternal_SelectTag( "total", "t" ) -#define CONN_TAG AwsIotDefenderInternal_SelectTag( "connections", "cs" ) -#define REMOTE_ADDR_TAG AwsIotDefenderInternal_SelectTag( "remote_addr", "rad" ) +#define TCP_CONN_TAG AwsIotDefenderInternal_SelectTag( "tcp_connections", "tc" ) +#define EST_CONN_TAG AwsIotDefenderInternal_SelectTag( "established_connections", "ec" ) +#define TOTAL_TAG AwsIotDefenderInternal_SelectTag( "total", "t" ) +#define CONN_TAG AwsIotDefenderInternal_SelectTag( "connections", "cs" ) +#define REMOTE_ADDR_TAG AwsIotDefenderInternal_SelectTag( "remote_addr", "rad" ) /** * Structure to hold a metrics report. @@ -56,17 +54,6 @@ typedef struct _metricsReport size_t size; /* Raw data size. */ } _metricsReport_t; -typedef struct _tcpConns -{ - IotMetricsTcpConnection_t * pArray; - uint8_t count; -} _tcpConns_t; - -typedef struct _metrics -{ - _tcpConns_t tcpConns; -} _metrics_t; - /* Initialize metrics report. */ static _metricsReport_t _report = { @@ -75,40 +62,32 @@ static _metricsReport_t _report = .size = 0 }; -/* Initialize metrics data. */ -static _metrics_t _metrics = { 0 }; - /* Define a "snapshot" global array of metrics flag. */ static uint32_t _metricsFlagSnapshot[ DEFENDER_METRICS_GROUP_COUNT ]; /* Report id integer. */ static uint64_t _AwsIotDefenderReportId = 0; -/* Static storage holding the string of remote address: "ip:port". */ -static char _remoteAddr[ REMOTE_ADDR_LENGTH ] = ""; - /*---------------------- Helper Functions -------------------------*/ -void assertSuccess( IotSerializerError_t error ); - -void assertSuccessOrBufferToSmall( IotSerializerError_t error ); - -static void copyMetricsFlag( void ); +static void _assertSuccess( IotSerializerError_t error ); -static bool getLatestMetricsData( void ); +static void _assertSuccessOrBufferToSmall( IotSerializerError_t error ); -static void freeMetricsData( void ); - -static void tcpConnectionsCallback( void * param1, - IotListDouble_t * pTcpConnectionsMetricsList ); +static void _copyMetricsFlag( void ); static void _serialize( void ); -static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObject ); +static void _serializeTcpConnections( void * param1, + const IotListDouble_t * pTcpConnectionsMetricsList ); + +#if DEBUG_CBOR_PRINT == 1 + static void _printReport(); +#endif /*-----------------------------------------------------------*/ -void assertSuccess( IotSerializerError_t error ) +void _assertSuccess( IotSerializerError_t error ) { ( void ) error; AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS ); @@ -116,7 +95,7 @@ void assertSuccess( IotSerializerError_t error ) /*-----------------------------------------------------------*/ -void assertSuccessOrBufferToSmall( IotSerializerError_t error ) +void _assertSuccessOrBufferToSmall( IotSerializerError_t error ) { ( void ) error; AwsIotDefender_Assert( error == IOT_SERIALIZER_SUCCESS || error == IOT_SERIALIZER_BUFFER_TOO_SMALL ); @@ -148,47 +127,44 @@ bool AwsIotDefenderInternal_CreateReport( void ) bool result = true; IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); + size_t dataSize = 0; uint8_t * pReportBuffer = NULL; /* Copy the metrics flag user specified. */ - copyMetricsFlag(); + _copyMetricsFlag(); - /* Get latest metrics data. */ - result = getLatestMetricsData(); - - if( result ) - { - /* Generate report id based on current time. */ - _AwsIotDefenderReportId = IotClock_GetTimeMs(); + /* Generate report id based on current time. */ + _AwsIotDefenderReportId = IotClock_GetTimeMs(); - /* Dry-run serialization to calculate the required size. */ - _serialize(); + /* Dry-run serialization to calculate the required size. */ + _serialize(); - /* Get the calculated required size. */ - dataSize = _defenderEncoder.getExtraBufferSizeNeeded( pEncoderObject ); + /* Get the calculated required size. */ + dataSize = _defenderEncoder.getExtraBufferSizeNeeded( pEncoderObject ); - /* Clean the encoder object handle. */ - _defenderEncoder.destroy( pEncoderObject ); + /* Clean the encoder object handle. */ + _defenderEncoder.destroy( pEncoderObject ); - /* Allocate memory once. */ - pReportBuffer = AwsIotDefender_MallocReport( dataSize * sizeof( uint8_t ) ); + /* Allocate memory once. */ + pReportBuffer = AwsIotDefender_MallocReport( dataSize * sizeof( uint8_t ) ); - if( pReportBuffer != NULL ) - { - _report.pDataBuffer = pReportBuffer; - _report.size = dataSize; + if( pReportBuffer != NULL ) + { + _report.pDataBuffer = pReportBuffer; + _report.size = dataSize; - /* Actual serialization. */ - _serialize(); - } - else - { - result = false; - } + /* Actual serialization. */ + _serialize(); - /* Metrics data can be freed. */ - freeMetricsData(); + /* Ouput the report to stdout if debugging mode is enabled. */ + #if DEBUG_CBOR_PRINT == 1 + _printReport(); + #endif + } + else + { + result = false; } return result; @@ -208,10 +184,6 @@ void AwsIotDefenderInternal_DeleteReport( void ) _report.pDataBuffer = NULL; _report.size = 0; _report.object = ( IotSerializerEncoderObject_t ) IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM; - - _metrics = ( _metrics_t ) { - 0 - }; } /* @@ -237,8 +209,8 @@ static void _serialize( void ) IotSerializerEncoderObject_t metricsMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; /* Define an assert function for serialization returned error. */ - void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall - : assertSuccess; + void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? _assertSuccessOrBufferToSmall + : _assertSuccess; uint8_t metricsGroupCount = 0; uint32_t i = 0; @@ -294,7 +266,7 @@ static void _serialize( void ) switch( i ) { case AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS: - _serializeTcpConnections( &metricsMap ); + IotMetrics_GetTcpConnections( ( void * ) &metricsMap, _serializeTcpConnections ); break; default: @@ -315,7 +287,7 @@ static void _serialize( void ) /*-----------------------------------------------------------*/ -static void copyMetricsFlag( void ) +static void _copyMetricsFlag( void ) { /* Copy the metrics flags to snapshot so that it is unlocked quicker. */ IotMutex_Lock( &_AwsIotDefenderMetrics.mutex ); @@ -328,89 +300,11 @@ static void copyMetricsFlag( void ) /*-----------------------------------------------------------*/ -static bool getLatestMetricsData( void ) -{ - bool result = true; - - /* Get TCP connections metrics data. */ - if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) - { - IotMetricsListCallback_t tcpConnectionscallback; - - - tcpConnectionscallback.function = tcpConnectionsCallback; - tcpConnectionscallback.param1 = ( bool * ) &result; - - IotMetrics_ProcessTcpConnections( tcpConnectionscallback ); - } - - return result; -} - -/*-----------------------------------------------------------*/ - -static void freeMetricsData( void ) -{ - if( _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ) - { - if( _metrics.tcpConns.pArray != NULL ) - { - IotMetrics_FreeTcpConnection( _metrics.tcpConns.pArray ); - } - - _metrics.tcpConns.pArray = NULL; - _metrics.tcpConns.count = 0; - } -} - -/*-----------------------------------------------------------*/ - -static void tcpConnectionsCallback( void * param1, - IotListDouble_t * pTcpConnectionsMetricsList ) +static void _serializeTcpConnections( void * param1, + const IotListDouble_t * pTcpConnectionsMetricsList ) { - bool * pResult = ( bool * ) param1; - - IotLink_t * pConnectionLink = IotListDouble_PeekHead( pTcpConnectionsMetricsList ); - IotMetricsTcpConnection_t * pConnection = NULL; - - uint32_t i = 0; - size_t total = IotListDouble_Count( pTcpConnectionsMetricsList ); - - /* If there is at least one TCP connection. */ - if( total > 0 ) - { - /* Allocate memory to copy TCP connections metrics data. */ - _metrics.tcpConns.pArray = IotMetrics_MallocTcpConnection( total * sizeof( IotMetricsTcpConnection_t ) ); - - /* Set whether success of memory allocation to the output pointer. */ - *pResult = _metrics.tcpConns.pArray != NULL; - - if( *pResult ) - { - /* Set count only the memory allocation succeeds. */ - _metrics.tcpConns.count = ( uint8_t ) total; - - /* At least one element in the list*/ - AwsIotDefender_Assert( pConnectionLink ); - - for( i = 0; i < total; i++ ) - { - pConnection = IotLink_Container( IotMetricsTcpConnection_t, pConnectionLink, link ); - - /* Copy to new allocated array. */ - _metrics.tcpConns.pArray[ i ] = *pConnection; - - /* Iterate to next one. */ - pConnectionLink = pConnectionLink->pNext; - } - } - } -} + IotSerializerEncoderObject_t * pMetricsObject = ( IotSerializerEncoderObject_t * ) param1; -/*-----------------------------------------------------------*/ - -static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObject ) -{ AwsIotDefender_Assert( pMetricsObject != NULL ); IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; @@ -419,19 +313,22 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj IotSerializerEncoderObject_t establishedMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; IotSerializerEncoderObject_t connectionsArray = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; - uint32_t tcpConnFlag = _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ]; + IotLink_t * pListIterator = NULL; + IotMetricsTcpConnection_t * pMetricsTcpConnection = NULL; + + size_t total = IotListDouble_Count( pTcpConnectionsMetricsList ); + + uint32_t tcpConnFlag = _metricsFlagSnapshot[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ]; uint8_t hasEstablishedConnections = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) > 0; /* Whether "connections" should show up is not only determined by user input, but also if there is at least 1 connection. */ uint8_t hasConnections = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) > 0 && - ( _metrics.tcpConns.count > 0 ); + ( total > 0 ); uint8_t hasTotal = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) > 0; uint8_t hasRemoteAddr = ( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) > 0; - void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? assertSuccessOrBufferToSmall - : assertSuccess; - - uint32_t i = 0; + void (* assertNoError)( IotSerializerError_t ) = _report.pDataBuffer == NULL ? _assertSuccessOrBufferToSmall + : _assertSuccess; /* Create the "tcp_connections" map with 1 key "established_connections" */ serializerError = _defenderEncoder.openContainerWithKey( pMetricsObject, @@ -453,16 +350,14 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj /* if user specify any metrics under "connections" and there are at least one connection */ if( hasConnections ) { - AwsIotDefender_Assert( _metrics.tcpConns.pArray != NULL ); - /* create array "connections" under "established_connections" */ serializerError = _defenderEncoder.openContainerWithKey( &establishedMap, CONN_TAG, &connectionsArray, - _metrics.tcpConns.count ); + total ); assertNoError( serializerError ); - for( i = 0; i < _metrics.tcpConns.count; i++ ) + IotContainers_ForEach( pTcpConnectionsMetricsList, pListIterator ) { IotSerializerEncoderObject_t connectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; @@ -475,10 +370,10 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj /* add remote address */ if( hasRemoteAddr ) { - sprintf( _remoteAddr, "%s:%d", _metrics.tcpConns.pArray[ i ].pRemoteIp, _metrics.tcpConns.pArray[ i ].remotePort ); + pMetricsTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, pListIterator, link ); serializerError = _defenderEncoder.appendKeyValue( &connectionMap, REMOTE_ADDR_TAG, - IotSerializer_ScalarTextString( _remoteAddr ) ); + IotSerializer_ScalarTextString( pMetricsTcpConnection->pRemoteAddress ) ); assertNoError( serializerError ); } @@ -494,7 +389,7 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj { serializerError = _defenderEncoder.appendKeyValue( &establishedMap, TOTAL_TAG, - IotSerializer_ScalarSignedInt( _metrics.tcpConns.count ) ); + IotSerializer_ScalarSignedInt( total ) ); assertNoError( serializerError ); } @@ -505,3 +400,22 @@ static void _serializeTcpConnections( IotSerializerEncoderObject_t * pMetricsObj serializerError = _defenderEncoder.closeContainer( pMetricsObject, &tcpConnectionMap ); assertNoError( serializerError ); } + +#if DEBUG_CBOR_PRINT == 1 + #include "cbor.h" + /*-----------------------------------------------------------*/ + + static void _printReport() + { + CborParser cborParser; + CborValue cborValue; + + cbor_parser_init( + _report.pDataBuffer, + _report.size, + 0, + &cborParser, + &cborValue ); + cbor_value_to_pretty( stdout, &cborValue ); + } +#endif /* if DEBUG_CBOR_PRINT == 1 */ diff --git a/platform/include/iot_network_metrics.h b/platform/include/iot_network_metrics.h new file mode 100644 index 0000000000..59076d0992 --- /dev/null +++ b/platform/include/iot_network_metrics.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_network_metrics.h + * @brief Declares the network stack functions with Device Defender metrics. + * + * This file does not provide a different networking implementation; it only wraps + * existing network implementations with the necessary metrics functions. + */ + +#ifndef IOT_NETWORK_METRICS_H_ +#define IOT_NETWORK_METRICS_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Platform metrics header. */ +#include "platform/iot_metrics.h" + +/* Platform network include. */ +#include "platform/iot_network.h" + +/** + * @brief Provides a pointer to an #IotNetworkInterface_t that uses that provides + * metrics. + */ +#define IOT_NETWORK_INTERFACE_METRICS ( &( IotNetworkMetrics ) ) + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Declaration of a network interface struct with metrics. + */ +extern const IotNetworkInterface_t IotNetworkMetrics; +/** @endcond */ + +#endif /* ifndef IOT_NETWORK_METRICS_H_ */ diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index d78c2bf51c..cbf93ea89a 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -41,9 +41,6 @@ /* Standard bool include. */ #include -/* OpenSSL types include. */ -#include - /* Platform types include. */ #include "types/iot_platform_types.h" @@ -223,6 +220,12 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ); */ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); +/** + * @brief Used by metrics to retrieve remote server and port of a connection. + */ +void IotNetworkOpenssl_GetServerInfo( void * pConnection, + IotMetricsTcpConnection_t * pServerInfo ); + /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. diff --git a/platform/source/network/iot_network_metrics.c b/platform/source/network/iot_network_metrics.c new file mode 100644 index 0000000000..1e20faae19 --- /dev/null +++ b/platform/source/network/iot_network_metrics.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_network_metrics.c + * @brief Implementation of the functions in iot_metrics.h that wraps functions + * from the networking abstraction. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Metrics networking include. */ +#include "iot_network_metrics.h" + +/* + * Provide default values for undefined memory allocation functions. + */ +#ifndef IotNetwork_Malloc + #include + +/** + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define IotNetwork_Malloc malloc +#endif +#ifndef IotNetwork_Free + #include + +/** + * @brief Free memory. This function should have the same signature as + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define IotNetwork_Free free +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps the network connection creation function with metrics. + */ +static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, + void * pCredentialInfo, + void ** pConnection ); + +/** + * @brief Wraps the network connection close function with metrics. + */ +static IotNetworkError_t _metricsNetworkClose( void * pConnection ); + +/** + * @brief Used to match metrics connection records by network connection. + * + * @param[in] pConnectionLink Pointer to a metrics connection record's link element. + * @param[in] pContext The network connection to match. + * + * @return `true` if the given metrics connection record matches the given + * network connection; `false` otherwise. + */ +static bool _connectionMatch( const IotLink_t * pConnectionLink, + void * pContext ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Holds a list of active TCP connections. + */ +static IotListDouble_t _connectionList = IOT_LIST_DOUBLE_INITIALIZER; + +/** + * @brief Protects #_connectionList from concurrent access. + */ +static IotMutex_t _connectionListMutex; + +/*-----------------------------------------------------------*/ + +/* OpenSSL networking include. */ +#include "posix/iot_network_openssl.h" + +/** + * @brief Pointer to the metrics-wrapped network creation function. + */ +static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkOpenssl_Create; + +/** + * @brief Pointer to the metrics-wrapped network close function. + */ +static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkOpenssl_Close; + +/** + * @brief Pointer to the function that retrieves the server info for a connection. + */ +static void ( * _networkServerInfo )( void *, + IotMetricsTcpConnection_t * ) = IotNetworkOpenssl_GetServerInfo; + +/** + * @brief An #IotNetworkInterface_t that wraps network abstration functions with + * metrics. + */ +const IotNetworkInterface_t IotNetworkMetrics = +{ + .create = _metricsNetworkCreate, + .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .send = IotNetworkOpenssl_Send, + .receive = IotNetworkOpenssl_Receive, + .close = _metricsNetworkClose, + .destroy = IotNetworkOpenssl_Destroy +}; + +/*-----------------------------------------------------------*/ + +static bool _connectionMatch( const IotLink_t * pConnectionLink, + void * pContext ) +{ + /* Retrieve the pointer the the TCP connection record. The given link + * pointer is never NULL. */ + IotMetricsTcpConnection_t * pTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, + pConnectionLink, + link ); + + return( pTcpConnection->pNetworkContext == pContext ); +} + +/*-----------------------------------------------------------*/ + +static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, + void * pCredentialInfo, + void ** pConnection ) +{ + IotMetricsTcpConnection_t * pTcpConnection = NULL; + IotNetworkError_t status = IOT_NETWORK_SUCCESS; + + /* Allocate memory for a new metrics connection. */ + pTcpConnection = IotNetwork_Malloc( sizeof( IotMetricsTcpConnection_t ) ); + + if( pTcpConnection != NULL ) + { + ( void ) memset( pTcpConnection, 0x00, sizeof( IotMetricsTcpConnection_t ) ); + + /* Call the wrapped network create function. */ + status = _networkCreate( pConnectionInfo, + pCredentialInfo, + pConnection ); + + /* Add the new metrics connection to the list of connections. */ + if( status == IOT_NETWORK_SUCCESS ) + { + /* Get the connection's IPv4 address and port. */ + pTcpConnection->pNetworkContext = *pConnection; + + _networkServerInfo( pTcpConnection->pNetworkContext, + pTcpConnection ); + + /* Add the new connection to the list of connections. */ + IotMutex_Lock( &_connectionListMutex ); + IotListDouble_InsertHead( &_connectionList, &( pTcpConnection->link ) ); + IotMutex_Unlock( &_connectionListMutex ); + } + else + { + /* Free the new metrics connection on error. */ + IotNetwork_Free( pTcpConnection ); + } + } + else + { + status = IOT_NETWORK_NO_MEMORY; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static IotNetworkError_t _metricsNetworkClose( void * pConnection ) +{ + IotLink_t * pConnectionLink = NULL; + IotMetricsTcpConnection_t * pTcpConnection = NULL; + + /* Call the wrapped network close function. */ + IotNetworkError_t status = _networkClose( pConnection ); + + /* Remove the connection from the list of active connections and free it. */ + if( status == IOT_NETWORK_SUCCESS ) + { + IotMutex_Lock( &_connectionListMutex ); + pConnectionLink = IotListDouble_RemoveFirstMatch( &_connectionList, + NULL, + _connectionMatch, + pConnection ); + IotMutex_Unlock( &_connectionListMutex ); + + if( pConnectionLink != NULL ) + { + pTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, + pConnectionLink, + link ); + + IotNetwork_Free( pTcpConnection ); + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +bool IotMetrics_Init( void ) +{ + IotListDouble_Create( &_connectionList ); + + return IotMutex_Create( &_connectionListMutex, false ); +} + +/*-----------------------------------------------------------*/ + +void IotMetrics_Cleanup( void ) +{ + IotMutex_Destroy( &_connectionListMutex ); +} + +/*-----------------------------------------------------------*/ + +void IotMetrics_GetTcpConnections( void * pContext, + void ( * metricsCallback )( void *, const IotListDouble_t * ) ) +{ + /* Provide the connection list. Ensure that it is not modified elsewhere by + * locking the connection list mutex. */ + IotMutex_Lock( &_connectionListMutex ); + metricsCallback( pContext, &_connectionList ); + IotMutex_Unlock( &_connectionListMutex ); +} + +/*-----------------------------------------------------------*/ diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index bf54445105..d4afd7d394 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -74,9 +74,8 @@ endif() add_library( iotplatform iot_clock_posix.c iot_threads_posix.c - ${NETWORK_SOURCE_FILE} - linux/iot_metrics.c - linux/iot_network_openssl_metrics.c ) + ../network/iot_network_metrics.c + ${NETWORK_SOURCE_FILE} ) # Library version. set_target_properties( iotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) diff --git a/platform/source/posix/iot_network_openssl.c b/platform/source/posix/iot_network_openssl.c index da804efc63..b0e0f2761c 100644 --- a/platform/source/posix/iot_network_openssl.c +++ b/platform/source/posix/iot_network_openssl.c @@ -42,6 +42,7 @@ #include /* Sockets includes. */ +#include #include #include @@ -1042,3 +1043,94 @@ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) } /*-----------------------------------------------------------*/ + +void IotNetworkOpenssl_GetServerInfo( void * pConnection, + IotMetricsTcpConnection_t * pServerInfo ) +{ + int status = 0, portLength = 0; + struct sockaddr_storage server = { 0 }; + socklen_t length = sizeof( struct sockaddr_storage ); + const void * pServerAddress = NULL; + char * pAddressStart = NULL; + const char * pPortFormat = NULL; + uint16_t remotePort = 0; + size_t addressLength = 0; + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pConnection; + + /* Get peer info. */ + status = getpeername( pNetworkConnection->socket, + ( struct sockaddr * ) &server, + &length ); + + if( status == 0 ) + { + /* Calculate the pointer to the IP address and get the remote port based + * on protocol version. */ + if( server.ss_family == AF_INET ) + { + /* IPv4. */ + pServerAddress = &( ( ( struct sockaddr_in * ) &server )->sin_addr ); + remotePort = ntohs( ( ( struct sockaddr_in * ) &server )->sin_port ); + + /* Print the IPv4 address at the start of the address buffer. */ + pAddressStart = pServerInfo->pRemoteAddress; + addressLength = IOT_METRICS_IP_ADDRESS_LENGTH; + pPortFormat = ":%hu"; + } + else + { + /* IPv6. */ + pServerAddress = &( ( ( struct sockaddr_in6 * ) &server )->sin6_addr ); + remotePort = ntohs( ( ( struct sockaddr_in6 * ) &server )->sin6_port ); + + /* Enclose the IPv6 address with []. */ + pServerInfo->pRemoteAddress[ 0 ] = '['; + pAddressStart = pServerInfo->pRemoteAddress + 1; + addressLength = IOT_METRICS_IP_ADDRESS_LENGTH - 1; + pPortFormat = "]:%hu"; + } + + /* Convert IP address to text. */ + if( inet_ntop( server.ss_family, + pServerAddress, + pAddressStart, + addressLength ) != NULL ) + { + /* Add the port to the end of the address. */ + addressLength = strlen( pServerInfo->pRemoteAddress ); + + portLength = snprintf( &( pServerInfo->pRemoteAddress[ addressLength ] ), + 7, + pPortFormat, + remotePort ); + + if( portLength > 0 ) + { + pServerInfo->addressLength = addressLength + ( size_t ) portLength; + + IotLogInfo( "(Socket %d) Collecting network metrics for %s.", + pNetworkConnection->socket, + pServerInfo->pRemoteAddress ); + } + else + { + IotLogError( "(Socket %d) Failed to add port to IP address buffer." ); + } + } + else + { + IotLogError( "(Socket %d) Failed to convert IP address to text format.", + pNetworkConnection->socket ); + } + } + else + { + IotLogError( "(Socket %d) Failed to query peer name. errno=%d.", + pNetworkConnection->socket, + errno ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/platform/source/posix/linux/iot_metrics.c b/platform/source/posix/linux/iot_metrics.c deleted file mode 100644 index 2a2e5a9d7c..0000000000 --- a/platform/source/posix/linux/iot_metrics.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* String include. */ -#include - -/* Metrics include. */ -#include "platform/iot_metrics.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Network socket includes. */ -#include -#include -#include -#include - -/* Compare function to identify the TCP connection id. */ -static bool _tcpConnectionMatch( const IotLink_t * pLink, - void * pId ); - -/*------------------- Global Variables ------------------------*/ - -static IotListDouble_t _connectionsList = IOT_LIST_DOUBLE_INITIALIZER; -static IotMutex_t _mutex; - -/*-----------------------------------------------------------*/ - -static bool _tcpConnectionMatch( const IotLink_t * pLink, - void * pId ) -{ - IotMetrics_Assert( pLink != NULL ); - IotMetrics_Assert( pId != NULL ); - - return *( ( IotMetricsConnectionId_t * ) pId ) == IotLink_Container( IotMetricsTcpConnection_t, pLink, link )->id; -} - -/*-----------------------------------------------------------*/ - -bool IotMetrics_Init( void ) -{ - bool result = false; - - if( IotMutex_Create( &_mutex, false ) ) - { - IotListDouble_Create( &_connectionsList ); - result = true; - } - - return result; -} - -/*-----------------------------------------------------------*/ - -void IotMetrics_AddTcpConnection( IotMetricsTcpConnection_t * pTcpConnection ) -{ - IotMetrics_Assert( pTcpConnection != NULL ); - - IotMutex_Lock( &_mutex ); - - /* Only add if it doesn't exist in the connectionsList. */ - if( IotListDouble_FindFirstMatch( &_connectionsList, - NULL, - _tcpConnectionMatch, - &pTcpConnection->id ) == NULL ) - { - IotMetricsTcpConnection_t * pNewTcpConnection = IotMetrics_MallocTcpConnection( sizeof( IotMetricsTcpConnection_t ) ); - - if( pNewTcpConnection != NULL ) - { - /* Copy TCP connection to the new one. */ - *pNewTcpConnection = *pTcpConnection; - - /* Allocate memory for ip string. */ - pNewTcpConnection->pRemoteIp = IotMetrics_MallocIpAddress( IOT_METRICS_MAX_IP_STRING_LENGTH * sizeof( char ) ); - - if( pNewTcpConnection->pRemoteIp != NULL ) - { - /* Convert IP to string. */ - struct in_addr remoteIpAddr = { pNewTcpConnection->remoteIp }; - char * pConvertedIp = inet_ntoa( remoteIpAddr ); - strcpy( pNewTcpConnection->pRemoteIp, pConvertedIp ); - - /* Convert port and IP to host byte order. */ - pNewTcpConnection->remotePort = ntohs( pNewTcpConnection->remotePort ); - pNewTcpConnection->remoteIp = ntohl( pNewTcpConnection->remoteIp ); - - /* Insert to the list. */ - IotListDouble_InsertTail( &_connectionsList, &( pNewTcpConnection->link ) ); - } - else - { - IotMetrics_FreeTcpConnection( pNewTcpConnection ); - } - } - } - - IotMutex_Unlock( &_mutex ); -} - -/*-----------------------------------------------------------*/ - -void IotMetrics_RemoveTcpConnection( IotMetricsConnectionId_t tcpConnectionId ) -{ - IotMutex_Lock( &_mutex ); - - IotLink_t * pFoundConnectionLink = IotListDouble_RemoveFirstMatch( &_connectionsList, - NULL, - _tcpConnectionMatch, - &tcpConnectionId ); - - if( pFoundConnectionLink != NULL ) - { - IotMetricsTcpConnection_t * pFoundTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, pFoundConnectionLink, link ); - - IotMetrics_FreeIpAddress( pFoundTcpConnection->pRemoteIp ); - IotMetrics_FreeTcpConnection(pFoundTcpConnection); - } - - IotMutex_Unlock( &_mutex ); -} - -/*-----------------------------------------------------------*/ - -void IotMetrics_ProcessTcpConnections( IotMetricsListCallback_t tcpConnectionsCallback ) -{ - if( tcpConnectionsCallback.function != NULL ) - { - IotMutex_Lock( &_mutex ); - - /* Execute the callback function. */ - tcpConnectionsCallback.function( tcpConnectionsCallback.param1, &_connectionsList ); - - IotMutex_Unlock( &_mutex ); - } - - /* If no callback function is provided, simply return. */ -} diff --git a/platform/source/posix/linux/iot_network_openssl_metrics.c b/platform/source/posix/linux/iot_network_openssl_metrics.c deleted file mode 100644 index 179cdfc89c..0000000000 --- a/platform/source/posix/linux/iot_network_openssl_metrics.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * This file servers as an example. - * It tracks the socket metrics in network interface layer and uses openssl as underlying network. - */ - -/* Linux network includes. */ -#include -#include -#include -#include - -/* Network include. */ -#include "platform/iot_network.h" -#include "posix/iot_network_openssl.h" - -/* Metrics include. */ -#include "platform/iot_metrics.h" - -/* Metrics wrapper of create interface. */ -static IotNetworkError_t _metricsCreate( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ); - -/* Metrics wrapper of close interface. */ -static IotNetworkError_t _metricsClose( void * pConnection ); - -/* Metrics network interface which is on top of openssl network interface. */ -const IotNetworkInterface_t _IotNetworkOpensslMetrics = -{ - .create = _metricsCreate, - .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, - .send = IotNetworkOpenssl_Send, - .receive = IotNetworkOpenssl_Receive, - .close = _metricsClose, - .destroy = IotNetworkOpenssl_Destroy -}; - -/*-----------------------------------------------------------*/ - -static IotNetworkError_t _metricsCreate( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ) -{ - /* Call the openssl create. */ - IotNetworkError_t error = IotNetworkOpenssl_Create( pConnectionInfo, pCredentialInfo, pConnection ); - - /* Add socket metrics only if creation succeeded. */ - if( error == IOT_NETWORK_SUCCESS ) - { - /* This is an hacky to to get the socket integer value. Not recommended in production code. */ - int socket = **( ( int ** ) pConnection ); - - /* Assume this is Ipv4. */ - struct sockaddr_in socketAddressIpv4; - socklen_t socklen = sizeof( struct sockaddr_in ); - - IotMetricsTcpConnection_t connection; - - /* Get the ip and port from the peer socket. */ - int ret = getpeername( socket, ( struct sockaddr * ) &socketAddressIpv4, &socklen ); - ( void ) ret; - - /* Assert its succees. */ - IotMetrics_Assert( ret == 0 ); - - connection.id = ( IotMetricsConnectionId_t ) socket; - connection.remotePort = socketAddressIpv4.sin_port; - connection.remoteIp = socketAddressIpv4.sin_addr.s_addr; - - /* Add this metircs, a established TCP connection. */ - IotMetrics_AddTcpConnection( &connection ); - } - - return error; -} - -/*-----------------------------------------------------------*/ - -static IotNetworkError_t _metricsClose( void * pConnection ) -{ - /* This is an hacky to to get the socket integer value. Not recommended in production code. */ - int socket = *( ( int * ) pConnection ); - - /* Call the openssl close. */ - IotNetworkError_t error = IotNetworkOpenssl_Close( pConnection ); - - /* Remove socket metrics only if close succeeded. */ - if( error == IOT_NETWORK_SUCCESS ) - { - /* Remove this metircs by its id. */ - IotMetrics_RemoveTcpConnection( ( IotMetricsConnectionId_t ) socket ); - } - - return error; -} diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index e4830b9854..b3010b6bef 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -38,6 +38,8 @@ /* Openssl includes. */ #include "posix/iot_network_openssl.h" +#include "iot_network_metrics.h" + /* Serializer includes. */ #include "iot_serializer.h" @@ -46,7 +48,7 @@ #include "unity_fixture.h" /* Total time to wait for a state to be true. */ -#define WAIT_STATE_TOTAL_SECONDS 5 +#define WAIT_STATE_TOTAL_SECONDS 10 /* Time interval for defender agent to publish metrics. It will be throttled if too frequent. */ /* TODO: if we can change "thingname" in each test, this can be lowered. */ @@ -81,7 +83,6 @@ static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .par static IotNetworkServerInfoOpenssl_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; static IotNetworkCredentialsOpenssl_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -extern const IotNetworkInterface_t _IotNetworkOpensslMetrics; /*------------------ global variables -----------------------------*/ static uint8_t _payloadBuffer[ PAYLOAD_MAX_SIZE ]; @@ -166,7 +167,7 @@ TEST_SETUP( Full_DEFENDER ) _startInfo.mqttNetworkInfo.u.setup.pNetworkServerInfo = &_serverInfo; _startInfo.mqttNetworkInfo.u.setup.pNetworkCredentialInfo = &_credential; - _startInfo.mqttNetworkInfo.pNetworkInterface = &_IotNetworkOpensslMetrics; + _startInfo.mqttNetworkInfo.pNetworkInterface = &IotNetworkMetrics; _startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; _startInfo.mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; diff --git a/tests/iot_config.h b/tests/iot_config.h index 887b9badab..3a6d9693ab 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -125,10 +125,6 @@ #define AwsIotShadow_FreeString unity_free_mt #define AwsIotShadow_MallocSubscription unity_malloc_mt #define AwsIotShadow_FreeSubscription unity_free_mt - #define IotMetrics_MallocTcpConnection unity_malloc_mt - #define IotMetrics_FreeTcpConnection unity_free_mt - #define IotMetrics_MallocIpAddress unity_malloc_mt - #define IotMetrics_FreeIpAddress unity_free_mt #define IotSerializer_MallocCborEncoder unity_malloc_mt #define IotSerializer_FreeCborEncoder unity_free_mt #define IotSerializer_MallocCborParser unity_malloc_mt @@ -176,9 +172,6 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; #define IotTestNetwork_Init IotNetworkOpenssl_Init #define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup -/* This is supposed to be defined as the socket data type. In linux, it is "int". */ -#define IotMetricsConnectionId_t int - /* Macro for placing inline assembly in test code. */ #define IOT_TEST_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) From 7866d6252a8af8d31d596e90446ab5b5b5265bb1 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 6 May 2019 11:05:25 -0700 Subject: [PATCH 123/844] Move static memory sources with their libraries (#406) --- doc/config/static_memory | 7 +- doc/lib/mqtt.txt | 10 +- doc/lib/shadow.txt | 8 +- doc/lib/static_memory.txt | 84 +-- lib/include/iot_serializer.h | 61 +- .../private/aws_iot_defender_internal.h | 37 +- lib/include/private/aws_iot_shadow_internal.h | 32 +- lib/include/private/iot_mqtt_internal.h | 44 +- lib/include/private/iot_static_memory.h | 632 +++--------------- lib/include/private/iot_taskpool_internal.h | 62 +- lib/source/common/CMakeLists.txt | 8 +- .../iot_static_memory_common.c | 54 +- ...askpool.c => iot_taskpool_static_memory.c} | 29 +- .../aws_iot_static_memory_defender.c | 149 ----- .../iot_static_memory_serializer.c | 241 ------- lib/source/mqtt/CMakeLists.txt | 1 + .../iot_mqtt_static_memory.c} | 55 +- lib/source/serializer/CMakeLists.txt | 1 + .../serializer/iot_serializer_static_memory.c | 229 +++++++ lib/source/shadow/CMakeLists.txt | 1 + .../aws_iot_shadow_static_memory.c} | 37 +- tests/iot_config.h | 17 +- 22 files changed, 499 insertions(+), 1300 deletions(-) rename lib/source/common/{static_memory => }/iot_static_memory_common.c (77%) rename lib/source/common/{static_memory/iot_static_memory_taskpool.c => iot_taskpool_static_memory.c} (81%) delete mode 100644 lib/source/common/static_memory/aws_iot_static_memory_defender.c delete mode 100644 lib/source/common/static_memory/iot_static_memory_serializer.c rename lib/source/{common/static_memory/iot_static_memory_mqtt.c => mqtt/iot_mqtt_static_memory.c} (80%) create mode 100644 lib/source/serializer/iot_serializer_static_memory.c rename lib/source/{common/static_memory/aws_iot_static_memory_shadow.c => shadow/aws_iot_shadow_static_memory.c} (79%) diff --git a/doc/config/static_memory b/doc/config/static_memory index ece30d90b8..b29bebdb17 100644 --- a/doc/config/static_memory +++ b/doc/config/static_memory @@ -15,12 +15,11 @@ GENERATE_TAGFILE = doc/tag/static_memory.tag # Directories containing library source code. INPUT = doc/lib \ lib/include/private \ - lib/source/common/static_memory + lib/source/common # Library file names. -FILE_PATTERNS = *static_memory*.h *static_memory*.c *static_memory*.txt +FILE_PATTERNS = *static_memory*.h *static_memory_common*.c *static_memory*.txt # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ - doc/tag/mqtt.tag=../mqtt \ - doc/tag/shadow.tag=../shadow + doc/tag/platform.tag=../platform diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 7040afe181..42168a6894 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -286,7 +286,7 @@ QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. T @configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. @section mqtt_config_memory Memory allocation -@brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the MQTT library. +@brief The following functions may be re-implemented for the MQTT library. - #IotMqtt_MallocConnection
@copybrief IotMqtt_MallocConnection - #IotMqtt_FreeConnection
@@ -303,4 +303,12 @@ QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. T @copybrief IotMqtt_MallocSubscription - #IotMqtt_FreeSubscription
@copybrief IotMqtt_FreeSubscription + +When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for MQTT. +- @anchor IOT_MQTT_CONNECTIONS IOT_MQTT_CONNECTIONS
+ Number of MQTT connections that may be opened. Defaults to `1` if undefined. +- @anchor IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS
+ Maximum number of MQTT operations that may be in-progress at any time. Defaults to `10` if undefined. +- @anchor IOT_MQTT_SUBSCRIPTIONS IOT_MQTT_SUBSCRIPTIONS
+ Maximum number of MQTT subscriptions at any time. Defaults to `8` if undefined. */ diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 4179045f88..2e9181d9da 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -147,7 +147,7 @@ Log messages from the Shadow library at or below this setting will be printed. @configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`; otherwise, nothing. @section shadow_config_memory Memory allocation -@brief If @ref IOT_STATIC_MEMORY_ONLY is `1`, then the following functions must be re-implemented for the Shadow library. +@brief The following functions may be re-implemented for the Shadow library. - #AwsIotShadow_MallocOperation
@copybrief AwsIotShadow_MallocOperation - #AwsIotShadow_FreeOperation
@@ -160,4 +160,10 @@ Log messages from the Shadow library at or below this setting will be printed. @copybrief AwsIotShadow_MallocSubscription - #AwsIotShadow_FreeSubscription
@copybrief AwsIotShadow_FreeSubscription + +When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for Shadow. +- @anchor AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS
+ Maximum number of Shadow operations that may be in-progress at any time. Defaults to `10` if undefined. +- @anchor AWS_IOT_SHADOW_SUBSCRIPTIONS AWS_IOT_SHADOW_SUBSCRIPTIONS
+ Maximum number of Shadow subscriptions at any time. Defaults to `2` if undefined. */ diff --git a/doc/lib/static_memory.txt b/doc/lib/static_memory.txt index 060e6cc615..f333c2e4d2 100644 --- a/doc/lib/static_memory.txt +++ b/doc/lib/static_memory.txt @@ -3,34 +3,9 @@ @anchor static_memory @brief @copybrief iot_static_memory.h

-The static memory component provides statically-allocated buffers that are used instead of dynamic memory allocation when @ref IOT_STATIC_MEMORY_ONLY is `1`. Using static memory only does not guarantee that memory allocation will always succeed; it's possible for all statically-allocated buffers to be in-use. However, static memory only can guarantee the availability of at least a certain amount of resources. Because space must be reserved for statically-allocated buffers, binaries compiled with static memory only will be larger. +The static memory component manages statically-allocated buffers for other libraries that are used instead of dynamic memory allocation when @ref IOT_STATIC_MEMORY_ONLY is `1`. Using static memory only does not guarantee that memory allocation will always succeed; it's possible for all statically-allocated buffers to be in-use. However, static memory only can guarantee the availability of at least a certain amount of resources. Because space must be reserved for statically-allocated buffers, binaries compiled with static memory only will be larger. -@section static_memory_types Types of buffers -@brief The types of statically-allocated buffers provided by the static memory component. - -@subsection static_memory_types_messagebuffers Message buffers -Message buffers are fixed-size buffers used for strings, such as log messages or bytes transmitted over a network. Their size and number can be configured with the constants @ref IOT_MESSAGE_BUFFERS (number) and @ref IOT_MESSAGE_BUFFER_SIZE (size of each message buffer). Message buffers may be used by any library, and are analogous to the generic buffers allocated by [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) (though all message buffers are the same size). - -@subsection static_memory_mqtt MQTT static buffers -@brief Statically-allocated buffers used by the [MQTT library](@ref mqtt). - -@subsubsection static_memory_types_mqttconnections Connections -MQTT connections correspond to a statically-allocated MQTT session between a single client and server. In static memory mode, the number of simultaneous, active MQTT connections is controlled by the constant @ref IOT_MQTT_CONNECTIONS. - -@subsubsection static_memory_types_mqttoperations Operations -MQTT operations correspond to in-progress requests between a client and the MQTT server, such as CONNECT, PUBLISH, or publish acknowledgement (PUBACK). In static memory mode, the number of simultaneous, active MQTT operations is controlled by the constant @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. - -@subsubsection static_memory_types_mqttsubscriptions Subscriptions -MQTT subscriptions store records on callbacks registered for MQTT topic filters. In static memory mode, the number of simultaneous, active MQTT subscriptions (across all connections) is controlled by the constant @ref IOT_MQTT_SUBSCRIPTIONS. - -@subsection static_memory_shadow Shadow static buffers -@brief Statically-allocated buffers used by the [Shadow library](@ref shadow). - -@subsubsection static_memory_types_shadowoperations Operations -Shadow operations correspond to in-progress Shadow [DELETE](@ref shadow_function_delete), [GET](@ref shadow_function_get), or [UPDATE](@ref shadow_function_update) operations. In static memory mode, the number of simultaneous, active Shadow operations is controlled by the constant @ref AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS. - -@subsubsection static_memory_types_shadowsubscriptions Subscriptions -A Shadow subscriptions object groups MQTT subscriptions for a Thing. Shadow operations require pairs of subscriptions to determine their status (i.e. `/accepted` or `/rejected`); these subscriptions (as well as subscriptions for Shadow callbacks) are tracked by a Shadow subscriptions object. In static memory mode, the number of simultaneous, active Shadow subscriptions is controlled by the constant @ref AWS_IOT_SHADOW_SUBSCRIPTIONS. +This component primarily provides functions for libraries to manage static buffers, @ref static_memory_function_findfree and @ref static_memory_function_returninuse. By itself it provides "message buffers", intended to hold strings, such as log messages or bytes transmitted over a network. @dependencies{static_memory,static memory component} @dot "Static memory direct dependencies" @@ -41,74 +16,29 @@ digraph static_memory_dependencies subgraph { rank = same; - operating_system[label="Operating system", fillcolor="#999999ff"] + platform_threads[label="Thread management", fillcolor="#e89025ff", URL="@ref platform_threads"]; standard_library[label="Standard library\nstdbool, stddef, string", fillcolor="#d15555ff"]; } - static_memory -> operating_system; + static_memory -> platform_threads; static_memory -> standard_library; } @enddot - -Currently, the static memory component has the following dependencies: -- The operating system must provide a mechanism to implement critical sections. */ /** @configpage{static_memory,statically-allocated buffer pools} @section IOT_MESSAGE_BUFFERS -@brief The number of statically-allocated [message buffers](@ref static_memory_types_messagebuffers). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. +@brief The number of statically-allocated message buffers. This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. -@see @ref static_memory_types_messagebuffers +Message buffers are fixed-size buffers used for strings, such as log messages or bytes transmitted over a network. Their size and number can be configured with the constants @ref IOT_MESSAGE_BUFFERS (number) and @ref IOT_MESSAGE_BUFFER_SIZE (size of each message buffer). Message buffers may be used by any library, and are analogous to the generic buffers allocated by [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) (though all message buffers are the same size). @configpossible Any positive integer.
@configdefault `8` @section IOT_MESSAGE_BUFFER_SIZE -@brief The size (in bytes) of each statically-allocated [message buffer](@ref static_memory_types_messagebuffers). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@see @ref static_memory_types_messagebuffers +@brief The size (in bytes) of each statically-allocated message buffer. This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. @configpossible Any positive integer.
@configdefault `1024` - -@section IOT_MQTT_CONNECTIONS -@brief The number of statically-allocated [MQTT connections](@ref static_memory_types_mqttconnections). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@see [MQTT connections](@ref static_memory_types_mqttconnections) - -@configpossible Any positive integer.
-@configdefault `1` - -@section IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS -@brief The number of statically-allocated [MQTT operations](@ref static_memory_types_mqttoperations). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@see [In-progress MQTT operations](@ref static_memory_types_mqttoperations) - -@configpossible Any positive integer.
-@configdefault `10` - -@section IOT_MQTT_SUBSCRIPTIONS -@brief The number of statically-allocated [MQTT subscriptions](@ref static_memory_types_mqttsubscriptions). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@see [MQTT subscriptions](@ref static_memory_types_mqttsubscriptions) - -@configpossible Any positive integer.
-@configdefault `8` - -@section AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS -@brief The number of statically-allocated [Shadow operations](@ref static_memory_types_shadowoperations). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@see [In-progress Shadow operations](@ref static_memory_types_shadowoperations) - -@configpossible Any positive integer.
-@configdefault `10` - -@section AWS_IOT_SHADOW_SUBSCRIPTIONS -@brief The number of statically-allocated [Shadow subscriptions](@ref static_memory_types_shadowsubscriptions). This setting has no effect if @ref IOT_STATIC_MEMORY_ONLY is `0`. - -@see [Shadow subscriptions objects](@ref static_memory_types_shadowsubscriptions) - -@configpossible Any positive integer.
-@configdefault `2` */ diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 0a29300365..9a0c930af6 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -61,108 +61,83 @@ * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotSerializer_MallocCborEncoder - #define IotSerializer_MallocCborEncoder Iot_MallocSerializerCborEncoder - #endif + void * IotSerializer_MallocCborEncoder( size_t size ); /** * @brief Free an array of uint8_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotSerializer_FreeCborEncoder - #define IotSerializer_FreeCborEncoder Iot_FreeSerializerCborEncoder - #endif + void IotSerializer_FreeCborEncoder( void * ptr ); /** * @brief Allocate an array of uint8_t. This function should have the same * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotSerializer_MallocCborParser - #define IotSerializer_MallocCborParser Iot_MallocSerializerCborParser - #endif + void * IotSerializer_MallocCborParser( size_t size ); /** * @brief Free an array of uint8_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotSerializer_FreeCborParser - #define IotSerializer_FreeCborParser Iot_FreeSerializerCborParser - #endif + void IotSerializer_FreeCborParser( void * ptr ); /** * @brief Allocate an array of uint8_t. This function should have the same * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotSerializer_MallocCborValue - #define IotSerializer_MallocCborValue Iot_MallocSerializerCborValue - #endif + void * IotSerializer_MallocCborValue( size_t size ); /** * @brief Free an array of uint8_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotSerializer_FreeCborValue - #define IotSerializer_FreeCborValue Iot_FreeSerializerCborValue - #endif + void IotSerializer_FreeCborValue( void * ptr ); /** * @brief Allocate an array of uint8_t. This function should have the same * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotSerializer_MallocDecoderObject - #define IotSerializer_MallocDecoderObject Iot_MallocSerializerDecoderObject - #endif + void * IotSerializer_MallocDecoderObject( size_t size ); /** * @brief Free an array of uint8_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotSerializer_FreeDecoderObject - #define IotSerializer_FreeDecoderObject Iot_FreeSerializerDecoderObject - #endif - + void IotSerializer_FreeDecoderObject( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY */ #include #ifndef IotSerializer_MallocCborEncoder - #define IotSerializer_MallocCborEncoder malloc + #define IotSerializer_MallocCborEncoder malloc #endif - #ifndef IotSerializer_FreeCborEncoder - #define IotSerializer_FreeCborEncoder free + #define IotSerializer_FreeCborEncoder free #endif - #ifndef IotSerializer_MallocCborParser - #define IotSerializer_MallocCborParser malloc + #define IotSerializer_MallocCborParser malloc #endif - #ifndef IotSerializer_FreeCborParser - #define IotSerializer_FreeCborParser free + #define IotSerializer_FreeCborParser free #endif - #ifndef IotSerializer_MallocCborValue - #define IotSerializer_MallocCborValue malloc + #define IotSerializer_MallocCborValue malloc #endif - #ifndef IotSerializer_FreeCborValue - #define IotSerializer_FreeCborValue free + #define IotSerializer_FreeCborValue free #endif - #ifndef IotSerializer_MallocDecoderObject #define IotSerializer_MallocDecoderObject malloc #endif - #ifndef IotSerializer_FreeDecoderObject - #define IotSerializer_FreeDecoderObject free + #define IotSerializer_FreeDecoderObject free #endif - #endif /* if IOT_STATIC_MEMORY_ONLY */ #define IOT_SERIALIZER_INDEFINITE_LENGTH 0xffffffff @@ -178,13 +153,13 @@ #define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL /* helper macro to create scalar data */ -#define IotSerializer_ScalarSignedInt( signedIntValue ) \ +#define IotSerializer_ScalarSignedInt( signedIntValue ) \ ( IotSerializerScalarData_t ) { .value = { .u.signedInt = ( signedIntValue ) }, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT } \ -#define IotSerializer_ScalarTextString( pTextStringValue ) \ +#define IotSerializer_ScalarTextString( pTextStringValue ) \ ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( ( uint8_t * ) pTextStringValue ), .u.string.length = strlen( pTextStringValue ) }, .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ -#define IotSerializer_ScalarByteString( pByteStringValue, pByteStringLength ) \ +#define IotSerializer_ScalarByteString( pByteStringValue, pByteStringLength ) \ ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( pByteStringValue ), .u.string.length = ( pByteStringLength ) }, .type = IOT_SERIALIZER_SCALAR_BYTE_STRING } \ /* Determine if an object is a container. */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index a24be12ff8..8bbaa1489c 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -82,41 +82,32 @@ #include "private/iot_static_memory.h" /** - * @brief Allocate an array of uint8_t. This function should have the same + * @brief Allocate a Defender report. This function should have the same * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef AwsIotDefender_MallocReport - #define AwsIotDefender_MallocReport AwsIot_MallocDefenderReport - #endif + #define AwsIotDefender_MallocReport Iot_MallocMessageBuffer /** - * @brief Free an array of uint8_t. This function should have the same + * @brief Free a Defender report. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef AwsIotDefender_FreeReport - #define AwsIotDefender_FreeReport AwsIot_FreeDefenderReport - #endif + #define AwsIotDefender_FreeReport Iot_FreeMessageBuffer /** - * @brief Allocate an array of char. This function should have the same + * @brief Allocate a Defender topic. This function should have the same * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef AwsIotDefender_MallocTopic - #define AwsIotDefender_MallocTopic AwsIot_MallocDefenderTopic - #endif + #define AwsIotDefender_MallocTopic Iot_MallocMessageBuffer /** - * @brief Free an array of char. This function should have the same + * @brief Free a Defender topic. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef AwsIotDefender_FreeTopic - #define AwsIotDefender_FreeTopic AwsIot_FreeDefenderTopic - #endif - + #define AwsIotDefender_FreeTopic Iot_FreeMessageBuffer #else /* if IOT_STATIC_MEMORY_ONLY */ #include @@ -242,15 +233,15 @@ */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - #define DEFENDER_FORMAT "cbor" - #define _defenderEncoder _IotSerializerCborEncoder /**< Global defined in iot_serializer.h . */ - #define _defenderDecoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ + #define DEFENDER_FORMAT "cbor" + #define _defenderEncoder _IotSerializerCborEncoder /**< Global defined in iot_serializer.h . */ + #define _defenderDecoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ #elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - #define _DEFENDER_FORMAT "json" - #define _defenderEncoder _IotSerializerJsonEncoder /**< Global defined in iot_serializer.h . */ - #define _defenderDecoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ + #define DEFENDER_FORMAT "json" + #define _defenderEncoder _IotSerializerJsonEncoder /**< Global defined in iot_serializer.h . */ + #define _defenderDecoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ #else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ #error "AWS_IOT_DEFENDER_FORMAT must be either AWS_IOT_DEFENDER_FORMAT_CBOR or AWS_IOT_DEFENDER_FORMAT_JSON." diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 3d1d667ec8..ce757b919b 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -84,54 +84,42 @@ * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef AwsIotShadow_MallocOperation - #define AwsIotShadow_MallocOperation AwsIot_MallocShadowOperation - #endif + void * AwsIotShadow_MallocOperation( size_t size ); /** * @brief Free a #_shadowOperation_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef AwsIotShadow_FreeOperation - #define AwsIotShadow_FreeOperation AwsIot_FreeShadowOperation - #endif + void AwsIotShadow_FreeOperation( void * ptr ); /** * @brief Allocate a buffer for a short string, used for topic names or client * tokens. This function should have the same signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef AwsIotShadow_MallocString - #define AwsIotShadow_MallocString Iot_MallocMessageBuffer - #endif + #define AwsIotShadow_MallocString Iot_MallocMessageBuffer /** * @brief Free a string. This function should have the same signature as * [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef AwsIotShadow_FreeString - #define AwsIotShadow_FreeString Iot_FreeMessageBuffer - #endif + #define AwsIotShadow_FreeString Iot_FreeMessageBuffer /** * @brief Allocate a #_shadowSubscription_t. This function should have the * same signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef AwsIotShadow_MallocSubscription - #define AwsIotShadow_MallocSubscription AwsIot_MallocShadowSubscription - #endif + void * AwsIotShadow_MallocSubscription( size_t size ); /** * @brief Free a #_shadowSubscription_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef AwsIotShadow_FreeSubscription - #define AwsIotShadow_FreeSubscription AwsIot_FreeShadowSubscription - #endif + void AwsIotShadow_FreeSubscription( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ #include @@ -408,7 +396,7 @@ typedef struct _shadowOperation const char * pClientToken; /**< @brief Client token in update document. */ size_t clientTokenLength; /**< @brief Length of client token. */ } update; - } u; /**< @brief Valid member depends on _shadowOperation_t.type. */ + } u; /**< @brief Valid member depends on _shadowOperation_t.type. */ /* How to notify of an operation's completion. */ union @@ -425,10 +413,10 @@ typedef struct _shadowOperation */ typedef struct _shadowSubscription { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - int32_t references[ SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ - AwsIotShadowCallbackInfo_t callbacks[ SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ + int32_t references[ SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ + AwsIotShadowCallbackInfo_t callbacks[ SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ /** * @brief Buffer allocated for removing Shadow topics. diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 951fd2078d..cf738b0d5f 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -84,72 +84,56 @@ * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotMqtt_MallocConnection - #define IotMqtt_MallocConnection Iot_MallocMqttConnection - #endif + void * IotMqtt_MallocConnection( size_t size ); /** * @brief Free an #_mqttConnection_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotMqtt_FreeConnection - #define IotMqtt_FreeConnection Iot_FreeMqttConnection - #endif + void IotMqtt_FreeConnection( void * ptr ); /** * @brief Allocate memory for an MQTT packet. This function should have the * same signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotMqtt_MallocMessage - #define IotMqtt_MallocMessage Iot_MallocMessageBuffer - #endif + #define IotMqtt_MallocMessage Iot_MallocMessageBuffer /** * @brief Free an MQTT packet. This function should have the same signature * as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotMqtt_FreeMessage - #define IotMqtt_FreeMessage Iot_FreeMessageBuffer - #endif + #define IotMqtt_FreeMessage Iot_FreeMessageBuffer /** * @brief Allocate an #_mqttOperation_t. This function should have the same * signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotMqtt_MallocOperation - #define IotMqtt_MallocOperation Iot_MallocMqttOperation - #endif + void * IotMqtt_MallocOperation( size_t size ); /** * @brief Free an #_mqttOperation_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotMqtt_FreeOperation - #define IotMqtt_FreeOperation Iot_FreeMqttOperation - #endif + void IotMqtt_FreeOperation( void * ptr ); /** * @brief Allocate an #_mqttSubscription_t. This function should have the * same signature as [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ - #ifndef IotMqtt_MallocSubscription - #define IotMqtt_MallocSubscription Iot_MallocMqttSubscription - #endif + void * IotMqtt_MallocSubscription( size_t size ); /** * @brief Free an #_mqttSubscription_t. This function should have the same * signature as [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). */ - #ifndef IotMqtt_FreeSubscription - #define IotMqtt_FreeSubscription Iot_FreeMqttSubscription - #endif + void IotMqtt_FreeSubscription( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ #include @@ -345,10 +329,10 @@ typedef struct _mqttOperation struct { /* Basic operation information. */ - int32_t jobReference; /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */ - IotMqttOperationType_t type; /**< @brief What operation this structure represents. */ - uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ - uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ + int32_t jobReference; /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */ + IotMqttOperationType_t type; /**< @brief What operation this structure represents. */ + uint32_t flags; /**< @brief Flags passed to the function that created this operation. */ + uint16_t packetIdentifier; /**< @brief The packet identifier used with this operation. */ /* Serialized packet and size. */ uint8_t * pMqttPacket; /**< @brief The MQTT packet to send over the network. */ @@ -377,7 +361,7 @@ typedef struct _mqttOperation IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */ const void * pReceivedData; /**< @brief Any buffer associated with this PUBLISH that should be freed. */ } publish; - } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ + } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ } _mqttOperation_t; /** @@ -401,7 +385,7 @@ typedef struct _mqttPacket * when deserializing PUBLISHes. */ _mqttOperation_t * pIncomingPublish; - } u; /**< @brief Valid member depends on packet being decoded. */ + } u; /**< @brief Valid member depends on packet being decoded. */ uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 69dcf0b8ea..94dda74dd0 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -21,7 +21,7 @@ /** * @file iot_static_memory.h - * @brief Functions for managing static buffers. Only used when + * @brief Common functions for managing static buffers. Only used when * @ref IOT_STATIC_MEMORY_ONLY is `1`. */ @@ -36,28 +36,17 @@ /* Standard includes. */ #include #include +#include /** * @functionspage{static_memory,static memory component} * - @functionname{static_memory_function_init} * - @functionname{static_memory_function_cleanup} + * - @functionname{static_memory_function_findfree} + * - @functionname{static_memory_function_returninuse} * - @functionname{static_memory_function_messagebuffersize} * - @functionname{static_memory_function_mallocmessagebuffer} * - @functionname{static_memory_function_freemessagebuffer} - * - @functionname{static_memory_function_malloctaskpooljob} - * - @functionname{static_memory_function_freetaskpooljob} - * - @functionname{static_memory_function_malloctaskpooltimerevent} - * - @functionname{static_memory_function_freetaskpooltimerevent} - * - @functionname{static_memory_function_mallocmqttconnection} - * - @functionname{static_memory_function_freemqttconnection} - * - @functionname{static_memory_function_mallocmqttoperation} - * - @functionname{static_memory_function_freemqttoperation} - * - @functionname{static_memory_function_mallocmqttsubscription} - * - @functionname{static_memory_function_freemqttsubscription} - * - @functionname{static_memory_function_mallocshadowoperation} - * - @functionname{static_memory_function_freeshadowoperation} - * - @functionname{static_memory_function_mallocshadowsubscription} - * - @functionname{static_memory_function_freeshadowsubscription} */ /*----------------------- Initialization and cleanup ------------------------*/ @@ -107,6 +96,102 @@ bool IotStaticMemory_Init( void ); void IotStaticMemory_Cleanup( void ); /* @[declare_static_memory_cleanup] */ +/*------------------------- Buffer allocation and free ----------------------*/ + +/** + * @functionpage{IotStaticMemory_FindFree,static_memory,findfree} + * @functionpage{IotStaticMemory_ReturnInUse,static_memory,returninuse} + */ + +/** + * @brief Find a free buffer using the "in-use" flags. + * + * If a free buffer is found, this function marks the buffer in-use. This function + * is common to the static memory implementation. + * + * @param[in] pInUse The "in-use" flags to search. + * @param[in] limit How many flags to check, i.e. the size of `pInUse`. + * + * @return The index of a free buffer; `-1` if no free buffers are available. + * + * Example: + * @code{c} + * // To use this function, first declare two arrays. One provides the statically-allocated + * // objects, the other provides flags to determine which objects are in-use. + * #define NUMBER_OF_OBJECTS ... + * #define OBJECT_SIZE ... + * static bool _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; + * static uint8_t _pObjects[ NUMBER_OF_OBJECTS ][ OBJECT_SIZE ] = { { 0 } }; // Placeholder for objects. + * + * // The function to statically allocate objects. Must have the same signature + * // as malloc(). + * void * Iot_MallocObject( size_t size ) + * { + * int32_t freeIndex = -1; + * void * pNewObject = NULL; + * + * // Check that sizes match. + * if( size != OBJECT_SIZE ) + * { + * // Get the index of a free object. + * freeIndex = IotStaticMemory_FindFree( _pInUseMessageBuffers, + * IOT_MESSAGE_BUFFERS ); + * + * if( freeIndex != -1 ) + * { + * pNewBuffer = &( _pMessageBuffers[ freeIndex ][ 0 ] ); + * } + * } + * + * return pNewBuffer; + * } + * @endcode + */ +/* @[declare_static_memory_findfree] */ +int32_t IotStaticMemory_FindFree( bool * pInUse, + size_t limit ); +/* @[declare_static_memory_findfree] */ + +/** + * @brief Return an "in-use" buffer. + * + * This function is common to the static memory implementation. + * + * @param[in] ptr Pointer to the buffer to return. + * @param[in] pPool The pool of buffers that the in-use buffer was allocated from. + * @param[in] pInUse The "in-use" flags for pPool. + * @param[in] limit How many buffers (and flags) to check while searching for ptr. + * @param[in] elementSize The size of a single element in pPool. + * + * Example: + * @code{c} + * // To use this function, first declare two arrays. One provides the statically-allocated + * // objects, the other provides flags to determine which objects are in-use. + * #define NUMBER_OF_OBJECTS ... + * #define OBJECT_SIZE ... + * static bool _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; + * static uint8_t _pObjects[ NUMBER_OF_OBJECTS ][ OBJECT_SIZE ] = { { 0 } }; // Placeholder for objects. + * + * // The function to free statically-allocated objects. Must have the same signature + * // as free(). + * void Iot_FreeObject( void * ptr ) + * { + * IotStaticMemory_ReturnInUse( ptr, + * _pObjects, + * _pInUseObjects, + * NUMBER_OF_OBJECTS, + * OBJECT_SIZE ); + * } + * @endcode + */ +/* @[declare_static_memory_returninuse] */ +void IotStaticMemory_ReturnInUse( void * ptr, + void * pPool, + bool * pInUse, + size_t limit, + size_t elementSize ); +/* @[declare_static_memory_returninuse] */ + /*------------------------ Message buffer management ------------------------*/ /** @@ -116,8 +201,7 @@ void IotStaticMemory_Cleanup( void ); */ /** - * @brief Get the fixed size of a [message buffer] - * (@ref static_memory_types_messagebuffers). + * @brief Get the fixed size of a message buffer. * * The size of the message buffers are known at compile time, but it is a [constant] * (@ref IOT_MESSAGE_BUFFER_SIZE) that may not be visible to all source files. @@ -130,8 +214,7 @@ size_t Iot_MessageBufferSize( void ); /* @[declare_static_memory_messagebuffersize] */ /** - * @brief Get an empty [message buffer] - * (@ref static_memory_types_messagebuffers). + * @brief Get an empty message buffer. * * This function is the analog of [malloc] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) @@ -148,8 +231,7 @@ void * Iot_MallocMessageBuffer( size_t size ); /* @[declare_static_memory_mallocmessagebuffer] */ /** - * @brief Free an in-use [message buffer] - * (@ref static_memory_types_messagebuffers). + * @brief Free an in-use message buffer. * * This function is the analog of [free] * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) @@ -161,512 +243,4 @@ void * Iot_MallocMessageBuffer( size_t size ); void Iot_FreeMessageBuffer( void * ptr ); /* @[declare_static_memory_freemessagebuffer] */ -/*------------------- Task pool job and event management --------------------*/ - -/** - * @functionpage{Iot_MallocTaskPoolJob,static_memory,malloctaskpooljob} - * @functionpage{Iot_FreeTaskPoolJob,static_memory,freetaskpooljob} - * @functionpage{Iot_MallocTaskPoolTimerEvent,static_memory,malloctaskpooltimerevent} - * @functionpage{Iot_FreeTaskPoolTimerEvent,static_memory,freetaskpooltimerevent} - */ - -/** - * @brief Allocates memory to hold data for a new task pool job. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for task pool jobs. - * - * @param[in] size Must be equal to the size of a task pool job. - * - * @return Pointer to a task pool job. - */ -/* @[declare_static_memory_malloctaskpooljob] */ -void * Iot_MallocTaskPoolJob( size_t size ); -/* @[declare_static_memory_malloctaskpooljob] */ - -/** - * @brief Frees an in-use task pool job. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for task pool jobs. - * - * @param[in] ptr Pointer to an active task pool job to free. - */ -/* @[declare_static_memory_freetaskpooljob] */ -void Iot_FreeTaskPoolJob( void * ptr ); -/* @[declare_static_memory_freetaskpooljob] */ - -/** - * @brief Allocates memory to hold data for a new task pool timer event. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for task pool timer events. - * - * @param[in] size Must be equal to the size of a task pool timer event. - * - * @return Pointer to a task pool timer event. - */ -/* @[declare_static_memory_malloctaskpooltimerevent] */ -void * Iot_MallocTaskPoolTimerEvent( size_t size ); -/* @[declare_static_memory_malloctaskpooltimerevent] */ - -/** - * @brief Frees an in-use task pool timer event. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for task pool timer events. - * - * @param[in] ptr Pointer to an active task pool timer event to free. - */ -/* @[declare_static_memory_freetaskpooltimerevent] */ -void Iot_FreeTaskPoolTimerEvent( void * ptr ); -/* @[declare_static_memory_freetaskpooltimerevent] */ - -/*----------------------- MQTT connection management ------------------------*/ - -/** - * @functionpage{Iot_MallocMqttConnection,static_memory,mallocmqttconnection} - * @functionpage{Iot_FreeMqttConnection,static_memory,freemqttconnection} - */ - -/** - * @brief Allocates memory to hold data for a new [MQTT connection] - * (@ref static_memory_types_mqttconnections). - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for MQTT connections. - * - * @param[in] size Must be equal to sizeof( #_mqttConnection_t ). - * - * @return Pointer to an MQTT connection. - */ -/* @[declare_static_memory_mallocmqttconnection] */ -void * Iot_MallocMqttConnection( size_t size ); -/* @[declare_static_memory_mallocmqttconnection] */ - -/** - * @brief Frees an in-use [MQTT connection] - * (@ref static_memory_types_mqttconnections). - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for MQTT connections. - * - * @param[in] ptr Pointer to an active MQTT connection to free. - */ -/* @[declare_static_memory_freemqttconnection] */ -void Iot_FreeMqttConnection( void * ptr ); -/* @[declare_static_memory_freemqttconnection] */ - -/*------------------------ MQTT operation management ------------------------*/ - -/** - * @functionpage{Iot_MallocMqttOperation,static_memory,mallocmqttoperation} - * @functionpage{Iot_FreeMqttOperation,static_memory,freemqttoperation} - */ - -/** - * @brief Allocates memory to hold data for a new [MQTT operation] - * (@ref static_memory_types_mqttoperations). - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for MQTT operations. - * - * @param[in] size Must be equal to sizeof( #_mqttOperation_t ). - * - * @return Pointer to an MQTT operation. - */ -/* @[declare_static_memory_mallocmqttoperation] */ -void * Iot_MallocMqttOperation( size_t size ); -/* @[declare_static_memory_mallocmqttoperation] */ - -/** - * @brief Frees an in-use [MQTT operation] - * (@ref static_memory_types_mqttoperations). - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for MQTT operations. - * - * @param[in] ptr Pointer to an active MQTT operation to free. - */ -/* @[declare_static_memory_freemqttoperation] */ -void Iot_FreeMqttOperation( void * ptr ); -/* @[declare_static_memory_freemqttoperation] */ - -/*---------------------- MQTT subscription management -----------------------*/ - -/** - * @functionpage{Iot_MallocMqttSubscription,static_memory,mallocmqttsubscription} - * @functionpage{Iot_FreeMqttSubscription,static_memory,freemqttsubscription} - */ - -/** - * @brief Allocates memory to hold data for a new [MQTT subscription] - * (@ref static_memory_types_mqttsubscriptions). - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for subscriptions. - * - * @param[in] size Requested size for the subscription. Because each subscription - * contains a different topic, subscriptions will be different sizes. This value - * should be checked to make sure that the statically-allocated subscription - * object is large enough to accommodate the new topic filter. - * - * @return Pointer to an MQTT subscription. If the size argument is larger than - * the fixed size of an MQTT subscription object or no free MQTT subscriptions - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocmqttsubscription] */ -void * Iot_MallocMqttSubscription( size_t size ); -/* @[declare_static_memory_mallocmqttsubscription] */ - -/** - * @brief Frees an in-use [MQTT subscription] - * (@ref static_memory_types_mqttsubscriptions). - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for MQTT subscriptions. - * - * @param[in] ptr Pointer to an active MQTT subscription to free. - */ -/* @[declare_static_memory_freemqttsubscription] */ -void Iot_FreeMqttSubscription( void * ptr ); -/* @[declare_static_memory_freemqttsubscription] */ - -/*---------------------- Shadow operation management ------------------------*/ - -/** - * @functionpage{AwsIot_MallocShadowOperation,static_memory,mallocshadowoperation} - * @functionpage{AwsIot_FreeShadowOperation,static_memory,freeshadowoperation} - */ - -/** - * @brief Allocates memory to hold data for a new [Shadow operation] - * (@ref static_memory_types_shadowoperations). - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Shadow operations. - * - * @param[in] size Must be equal to sizeof( #_shadowOperation_t ). - * - * @return Pointer to a Shadow operation. - */ -/* @[declare_static_memory_mallocshadowoperation] */ -void * AwsIot_MallocShadowOperation( size_t size ); -/* @[declare_static_memory_mallocshadowoperation] */ - -/** - * @brief Frees an in-use [Shadow operation] - * (@ref static_memory_types_shadowoperations). - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Shadow operations. - * - * @param[in] ptr Pointer to an active Shadow operation to free. - */ -/* @[declare_static_memory_freeshadowoperation] */ -void AwsIot_FreeShadowOperation( void * ptr ); -/* @[declare_static_memory_freeshadowoperation] */ - -/*--------------------- Shadow subscription management ----------------------*/ - -/** - * @functionpage{AwsIot_MallocShadowSubscription,static_memory,mallocshadowsubscription} - * @functionpage{AwsIot_FreeShadowSubscription,static_memory,freeshadowsubscription} - */ - -/** - * @brief Allocates memory to hold data for a new [Shadow subscription] - * (@ref static_memory_types_shadowsubscriptions). - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Shadow subscriptions. - * - * @param[in] size Requested size for the subscription. Because each subscription - * contains a different Thing Name, subscriptions will be different sizes. This - * value should be checked to make sure that the statically-allocated subscription - * object is large enough to accommodate the new Thing Name. - * - * @return Pointer to a Shadow subscription. If the size argument is larger than - * the fixed size of a Shadow subscription object or no free Shadow subscriptions - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocshadowsubscription] */ -void * AwsIot_MallocShadowSubscription( size_t size ); -/* @[declare_static_memory_mallocshadowsubscription] */ - -/** - * @brief Frees an in-use [Shadow subscription] - * (@ref static_memory_types_shadowsubscriptions). - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Shadow subscriptions. - * - * @param[in] ptr Pointer to an active Shadow subscription to free. - */ -/* @[declare_static_memory_freeshadowsubscription] */ -void AwsIot_FreeShadowSubscription( void * ptr ); -/* @[declare_static_memory_freeshadowsubscription] */ - -/*------------------------- Metrics data management -------------------------*/ - -/** - * @brief Allocates memory to hold data for a new metrics TCP Connection. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for metrics TCP Connections. - * - * @param[in] size Requested size for the tcp connection. - * - * @return Pointer to a Metrics TCP Connection. If the size argument is larger than - * the fixed size of a Metrics TCP Connection object or no free Metrics TCP Connections - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocmetricstcpconnection] */ -void * Iot_MallocMetricsTcpConnection( size_t size ); -/* @[declare_static_memory_mallocmetricstcpconnection] */ - -/** - * @brief Frees an in-use metrics TCP Connection. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Metrics TCP Connections. - * - * @param[in] ptr Pointer to an active Metrics TCP Connection to free. - */ -/* @[declare_static_memory_freemetricstcpconnection] */ -void Iot_FreeMetricsTcpConnection( void * ptr ); -/* @[declare_static_memory_freemetricstcpconnection] */ - -/** - * @brief Allocates memory to hold data for a new Metrics IP Address. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Metrics IP Address. - * - * @param[in] size Requested size for the ip address. - * - * @return Pointer to a Metrics IP Address. If the size argument is larger than - * the fixed size of a Metrics IP Address object or no free Metrics IP Address - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocmetricsipaddress] */ -void * Iot_MallocMetricsIpAddress(size_t size); -/* @[declare_static_memory_mallocmetricsipaddress] */ - -/** - * @brief Frees an in-use Metrics IP Address. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Metrics IP Address. - * - * @param[in] ptr Pointer to an active Metrics IP Address to free. - */ -/* @[declare_static_memory_freemetricsipaddress] */ -void Iot_FreeMetricsIpAddress(void * ptr); -/* @[declare_static_memory_freemetricsipaddress] */ - -/** - * @brief Allocates memory to hold data for a new Serializer Cbor Encoder. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Serializer Cbor Encoder. - * - * @param[in] size Requested size for the serializer cbor encoder. - * - * @return Pointer to a Serializer Cbor Encoder. If the size argument is larger than - * the fixed size of a Serializer Cbor Encoder object or no free Serializer Cbor Encoder - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocserializercborencoder] */ -void * Iot_MallocSerializerCborEncoder( size_t size ); -/* @[declare_static_memory_mallocserializercborencoder] */ - -/** - * @brief Frees an in-use Serializer Cbor Encoder. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Serializer Cbor Encoder. - * - * @param[in] ptr Pointer to an active Serializer Cbor Encoder to free. - */ -/* @[declare_static_memory_freeserializercborencoder] */ -void Iot_FreeSerializerCborEncoder( void * ptr ); -/* @[declare_static_memory_freeserializercborencoder] */ - -/** - * @brief Allocates memory to hold data for a new Serializer Cbor Parser. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Serializer Cbor Parser. - * - * @param[in] size Requested size for the serializer cbor parser. - * - * @return Pointer to a Serializer Cbor Parser. If the size argument is larger than - * the fixed size of a Serializer Cbor Parser object or no free Serializer Cbor Parser - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocserializercborparser] */ -void * Iot_MallocSerializerCborParser( size_t size ); -/* @[declare_static_memory_mallocserializercborparser] */ - -/** - * @brief Frees an in-use Serializer Cbor Parser. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Serializer Cbor Parser. - * - * @param[in] ptr Pointer to an active Serializer Cbor Parser to free. - */ -/* @[declare_static_memory_freeserializercborparser] */ -void Iot_FreeSerializerCborParser( void * ptr ); -/* @[declare_static_memory_freeserializercborparser] */ - -/** - * @brief Allocates memory to hold data for a new Serializer Cbor Value. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Serializer Cbor Value. - * - * @param[in] size Requested size for the serializer cbor value. - * - * @return Pointer to a Serializer Cbor Parser. If the size argument is larger than - * the fixed size of a Serializer Cbor Value object or no free Serializer Cbor Value - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocserializercborvalue] */ -void * Iot_MallocSerializerCborValue( size_t size ); -/* @[declare_static_memory_mallocserializercborvalue] */ - -/** - * @brief Frees an in-use Serializer Cbor Value. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Serializer Cbor Value. - * - * @param[in] ptr Pointer to an active Serializer Cbor Value to free. - */ -/* @[declare_static_memory_freeserializercborvalue] */ -void Iot_FreeSerializerCborValue( void * ptr ); -/* @[declare_static_memory_freeserializercborvalue] */ - -/** - * @brief Allocates memory to hold data for a new Serializer Decoder Object. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Serializer Decoder Object. - * - * @param[in] size Requested size for the serializer decoder object. - * - * @return Pointer to a Serializer Decoder Object. If the size argument is larger than - * the fixed size of a Serializer Decoder Object object or no free Serializer Decoder Object - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocserializerdecoderobject] */ -void * Iot_MallocSerializerDecoderObject( size_t size ); -/* @[declare_static_memory_mallocserializerdecoderobject] */ - -/** - * @brief Frees an in-use Serializer Decoder Object. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Serializer Decoder Object. - * - * @param[in] ptr Pointer to an active Serializer Decoder Object to free. - */ -/* @[declare_static_memory_freeserializerdecoderobject] */ -void Iot_FreeSerializerDecoderObject( void * ptr ); -/* @[declare_static_memory_freeserializerdecoderobject] */ - -/** - * @brief Allocates memory to hold data for a new Defender Report. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Defender Report. - * - * @param[in] size Requested size for the report. Because each report - * contains a different metrics content, reports will be different sizes. This - * value should be checked to make sure that the statically-allocated report - * object is large enough to accommodate all the metrics data. - * - * @return Pointer to a Defender Report. If the size argument is larger than - * the fixed size of a Defender Report object or no free Defender Report - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocdefenderreport] */ -void * AwsIot_MallocDefenderReport( size_t size ); -/* @[declare_static_memory_mallocdefenderreport] */ - -/** - * @brief Frees an in-use Defender Report. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Defender Report. - * - * @param[in] ptr Pointer to an active Defender Report to free. - */ -/* @[declare_static_memory_freedefenderreport] */ -void AwsIot_FreeDefenderReport( void * ptr ); -/* @[declare_static_memory_freedefenderreport] */ - -/** - * @brief Allocates memory to hold data for a new Defender Topic. - * - * This function is the analog of [malloc] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) - * for Defender Topic. - * - * @param[in] size Requested size for the topic. Because each topic - * contains a different thing name, topics will be different sizes. This - * value should be checked to make sure that the statically-allocated topic - * object is large enough to accommodate all the topics. - * - * @return Pointer to a Defender Topic. If the size argument is larger than - * the fixed size of a Defender Topic object or no free Defender Topic - * are available, `NULL` is returned. - */ -/* @[declare_static_memory_mallocdefendertopic] */ -void * AwsIot_MallocDefenderTopic( size_t size ); -/* @[declare_static_memory_mallocdefendertopic] */ - -/** - * @brief Frees an in-use Defender Topic. - * - * This function is the analog of [free] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html) - * for Defender Topic. - * - * @param[in] ptr Pointer to an active Defender Topic to free. - */ -/* @[declare_static_memory_freedefendertopic] */ -void AwsIot_FreeDefenderTopic( void * ptr ); -/* @[declare_static_memory_freedefendertopic] */ - #endif /* if !defined( IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index 4c536ae9e8..a7e71b873f 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -40,27 +40,29 @@ /** * @brief Every public API return an enumeration value with an undelying value of 0 in case of success. */ -#define TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) +#define TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) /** * @brief Every public API returns an enumeration value with an undelying value different than 0 in case of success. */ -#define TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) +#define TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) /** * @brief Jump to the cleanup area. */ -#define TASKPOOL_GOTO_CLEANUP() IOT_GOTO_CLEANUP() +#define TASKPOOL_GOTO_CLEANUP() IOT_GOTO_CLEANUP() /** * @brief Declare the storage for the error status variable. */ -#define TASKPOOL_FUNCTION_ENTRY( result ) IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) +#define TASKPOOL_FUNCTION_ENTRY( result ) IOT_FUNCTION_ENTRY( IotTaskPoolError_t, result ) /** * @brief Check error and leave in case of failure. */ -#define TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) { if( TASKPOOL_FAILED( status = ( expr ) ) ) { IOT_GOTO_CLEANUP(); } } +#define TASKPOOL_ON_ERROR_GOTO_CLEANUP( expr ) \ + { if( TASKPOOL_FAILED( status = ( expr ) ) ) { IOT_GOTO_CLEANUP(); } \ + } /** * @brief Exit if an argument is NULL. @@ -129,9 +131,9 @@ #define LIBRARY_LOG_NAME ( "TASKPOOL" ) #include "iot_logging_setup.h" -/** - * @brief Overridable allocator and deallocator: provide default values for undefined memory - * allocation functions based on the usage of dynamic memory allocation. +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 #include "private/iot_static_memory.h" @@ -140,60 +142,40 @@ * @brief Allocate an #IotTaskPoolJob_t. This function should have the * same signature as [malloc]. */ - #ifndef IotTaskPool_MallocJob - #define IotTaskPool_MallocJob Iot_MallocTaskPoolJob - #endif + void * IotTaskPool_MallocJob( size_t size ); /** - * @brief Allocate an #_taskPoolTimerEvent_t. This function should have the - * same signature as [malloc]. + * @brief Free an #IotTaskPoolJob_t. This function should have the same + * signature as [free] */ - #ifndef IotTaskPool_MallocTimerEvent - #define IotTaskPool_MallocTimerEvent Iot_MallocTaskPoolTimerEvent - #endif + void IotTaskPool_FreeJob( void * ptr ); /** - * @brief Free an #IotTaskPoolJob_t. This function should have the same - * signature as [free] + * @brief Allocate an #_taskPoolTimerEvent_t. This function should have the + * same signature as [malloc]. */ - #ifndef IotTaskPool_FreeJob - #define IotTaskPool_FreeJob Iot_FreeTaskPoolJob - #endif + void * IotTaskPool_MallocTimerEvent( size_t size ); /** * @brief Free an #_taskPoolTimerEvent_t. This function should have the * same signature as[ free ]. */ - #ifndef IotTaskPool_FreeTimerEvent - #define IotTaskPool_FreeTimerEvent Iot_FreeTaskPoolTimerEvent - #endif - + void IotTaskPool_FreeTimerEvent( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #include -/** - * @brief Overridable allocator. - */ #ifndef IotTaskPool_MallocJob #define IotTaskPool_MallocJob malloc #endif - + #ifndef IotTaskPool_FreeJob + #define IotTaskPool_FreeJob free + #endif #ifndef IotTaskPool_MallocTimerEvent #define IotTaskPool_MallocTimerEvent malloc #endif - -/** - * @brief Overridable deallocator. - */ - #ifndef IotTaskPool_FreeJob - #define IotTaskPool_FreeJob free - #endif - #ifndef IotTaskPool_FreeTimerEvent - #define IotTaskPool_FreeTimerEvent free + #define IotTaskPool_FreeTimerEvent free #endif - #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /* ---------------------------------------------------------------------------------------------- */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 23852fe155..2ffabf27e5 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -2,13 +2,9 @@ add_library( iotcommon iot_init.c iot_logging.c + iot_static_memory_common.c iot_taskpool.c - static_memory/iot_static_memory_common.c - static_memory/iot_static_memory_taskpool.c - static_memory/iot_static_memory_mqtt.c - static_memory/aws_iot_static_memory_shadow.c - static_memory/iot_static_memory_serializer.c - static_memory/aws_iot_static_memory_defender.c ) + iot_taskpool_static_memory.c ) # Library version. set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) diff --git a/lib/source/common/static_memory/iot_static_memory_common.c b/lib/source/common/iot_static_memory_common.c similarity index 77% rename from lib/source/common/static_memory/iot_static_memory_common.c rename to lib/source/common/iot_static_memory_common.c index 75273d7692..e1ceb489b7 100644 --- a/lib/source/common/static_memory/iot_static_memory_common.c +++ b/lib/source/common/iot_static_memory_common.c @@ -68,39 +68,6 @@ /*-----------------------------------------------------------*/ -/** - * @brief Find a free buffer using the "in-use" flags. - * - * If a free buffer is found, this function marks the buffer in-use. This function - * is common to the static memory implementation. - * - * @param[in] pInUse The "in-use" flags to search. - * @param[in] limit How many flags to check. - * - * @return The index of a free buffer; -1 if no free buffers are available. - */ -int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); - -/** - * @brief Return an "in-use" buffer. - * - * This function is common to the static memory implementation. - * - * @param[in] ptr Pointer to the buffer to return. - * @param[in] pPool The pool of buffers that the in-use buffer was allocation from. - * @param[in] pInUse The "in-use" flags for pPool. - * @param[in] limit How many buffers (and flags) to check while searching for ptr. - * @param[in] elementSize The size of a single element in pPool. - */ -void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); - -/*-----------------------------------------------------------*/ - /** * @brief Guards access to critical sections. */ @@ -114,10 +81,11 @@ static char _pMessageBuffers[ IOT_MESSAGE_BUFFERS ][ IOT_MESSAGE_BUFFER_SIZE ] = /*-----------------------------------------------------------*/ -int IotStaticMemory_FindFree( bool * const pInUse, - int limit ) +int32_t IotStaticMemory_FindFree( bool * pInUse, + size_t limit ) { - int i = 0, freeIndex = -1; + size_t i = 0; + int32_t freeIndex = -1; /* Perform the search for a free buffer in a critical section. */ IotMutex_Lock( &( _mutex ) ); @@ -128,7 +96,7 @@ int IotStaticMemory_FindFree( bool * const pInUse, { /* If a free buffer is found, mark it "in-use" and return its index. */ pInUse[ i ] = true; - freeIndex = i; + freeIndex = ( int32_t ) i; break; } } @@ -142,12 +110,12 @@ int IotStaticMemory_FindFree( bool * const pInUse, /*-----------------------------------------------------------*/ void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, + void * pPool, + bool * pInUse, + size_t limit, size_t elementSize ) { - int i = 0; + size_t i = 0; uint8_t * element = NULL; /* Clear ptr. */ @@ -160,7 +128,7 @@ void IotStaticMemory_ReturnInUse( void * ptr, for( i = 0; i < limit; i++ ) { /* Calculate address of the i-th element in pPool. */ - element = ( ( uint8_t * ) pPool ) + elementSize * ( size_t ) i; + element = ( ( uint8_t * ) pPool ) + elementSize * i; /* Check for a match. */ if( ( ( void * ) element == ptr ) && @@ -200,7 +168,7 @@ size_t Iot_MessageBufferSize( void ) void * Iot_MallocMessageBuffer( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewBuffer = NULL; /* Check that size is within the fixed message buffer size. */ diff --git a/lib/source/common/static_memory/iot_static_memory_taskpool.c b/lib/source/common/iot_taskpool_static_memory.c similarity index 81% rename from lib/source/common/static_memory/iot_static_memory_taskpool.c rename to lib/source/common/iot_taskpool_static_memory.c index 401593d527..6b799bb4fd 100644 --- a/lib/source/common/static_memory/iot_static_memory_taskpool.c +++ b/lib/source/common/iot_taskpool_static_memory.c @@ -20,8 +20,8 @@ */ /** - * @file iot_static_memory_taskpool.c - * @brief Implementation of task pool static memory functions in iot_static_memory.h + * @file iot_taskpool_static_memory.c + * @brief Implementation of task pool static memory functions. */ /* The config header is always included first. */ @@ -50,19 +50,6 @@ /*-----------------------------------------------------------*/ -/* Extern declarations of common static memory functions in iot_static_memory_common.c - * Because these functions are specific to this static memory implementation, they are - * not placed in the common static memory header file. */ -extern int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); -extern void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); - -/*-----------------------------------------------------------*/ - /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ @@ -74,9 +61,9 @@ static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LI /*-----------------------------------------------------------*/ -void * Iot_MallocTaskPoolJob( size_t size ) +void * IotTaskPool_MallocJob( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewJob = NULL; /* Check size argument. */ @@ -97,7 +84,7 @@ void * Iot_MallocTaskPoolJob( size_t size ) /*-----------------------------------------------------------*/ -void Iot_FreeTaskPoolJob( void * ptr ) +void IotTaskPool_FreeJob( void * ptr ) { /* Return the in-use task pool job. */ IotStaticMemory_ReturnInUse( ptr, @@ -109,9 +96,9 @@ void Iot_FreeTaskPoolJob( void * ptr ) /*-----------------------------------------------------------*/ -void * Iot_MallocTaskPoolTimerEvent( size_t size ) +void * IotTaskPool_MallocTimerEvent( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewTimerEvent = NULL; /* Check size argument. */ @@ -132,7 +119,7 @@ void * Iot_MallocTaskPoolTimerEvent( size_t size ) /*-----------------------------------------------------------*/ -void Iot_FreeTaskPoolTimerEvent( void * ptr ) +void IotTaskPool_FreeTimerEvent( void * ptr ) { /* Return the in-use task pool timer event. */ IotStaticMemory_ReturnInUse( ptr, diff --git a/lib/source/common/static_memory/aws_iot_static_memory_defender.c b/lib/source/common/static_memory/aws_iot_static_memory_defender.c deleted file mode 100644 index 15ca5c574a..0000000000 --- a/lib/source/common/static_memory/aws_iot_static_memory_defender.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ - #include - #include - #include - -/* Static memory include. */ - #include "private/iot_static_memory.h" - -/* Defender include. */ - #include "private/aws_iot_defender_internal.h" - - #ifndef AWS_IOT_DEFENDER_REPORTS - #define AWS_IOT_DEFENDER_REPORTS ( 1 ) - #endif - - #ifndef AWS_IOT_DEFENDER_TOPICS - #define AWS_IOT_DEFENDER_TOPICS ( 3 ) - #endif - -/* Validate static memory configuration settings. */ - #if AWS_IOT_DEFENDER_REPORTS <= 0 - #error "AWS_IOT_DEFENDER_REPORTS cannot be 0 or negative." - #endif - - #if AWS_IOT_DEFENDER_TOPICS <= 0 - #error "AWS_IOT_DEFENDER_TOPICS cannot be 0 or negative." - #endif - - #define _DEFENDER_REPORT_SIZE 200 - -/* Prefix(30) + esimated "thing name"(128) + sufix(30). */ - #define _DEFENDER_TOPIC_SIZE 200 - -/*-----------------------------------------------------------*/ - -/* Extern declarations of common static memory functions in iot_static_memory_common.c - * Because these functions are specific to this static memory implementation, they are - * not placed in the common static memory header file. */ - extern int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); - extern void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ - static bool _inUseDefenderReports[ AWS_IOT_DEFENDER_REPORTS ] = { 0 }; - static uint8_t _defenderReports[ AWS_IOT_DEFENDER_REPORTS ][ _DEFENDER_REPORT_SIZE ] = { { 0 } }; - - static bool _inUseDefenderTopics[ AWS_IOT_DEFENDER_TOPICS ] = { 0 }; - static char _defenderTopics[ AWS_IOT_DEFENDER_TOPICS ][ _DEFENDER_TOPIC_SIZE ] = { { 0 } }; - -/*-----------------------------------------------------------*/ - - void * AwsIot_MallocDefenderReport( size_t size ) - { - int freeIndex = -1; - void * pNewReport = NULL; - - if( size <= _DEFENDER_REPORT_SIZE ) - { - freeIndex = IotStaticMemory_FindFree( _inUseDefenderReports, - AWS_IOT_DEFENDER_REPORTS ); - - if( freeIndex != -1 ) - { - pNewReport = &( _defenderReports[ freeIndex ][ 0 ] ); - } - } - - return pNewReport; - } - -/*-----------------------------------------------------------*/ - - void AwsIot_FreeDefenderReport( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _defenderReports, - _inUseDefenderReports, - AWS_IOT_DEFENDER_REPORTS, - _DEFENDER_REPORT_SIZE ); - } - -/*-----------------------------------------------------------*/ - - void * AwsIot_MallocDefenderTopic( size_t size ) - { - int freeIndex = -1; - void * pNewTopic = NULL; - - if( size <= _DEFENDER_TOPIC_SIZE ) - { - freeIndex = IotStaticMemory_FindFree( _inUseDefenderTopics, - AWS_IOT_DEFENDER_TOPICS ); - - if( freeIndex != -1 ) - { - pNewTopic = &( _defenderTopics[ freeIndex ][ 0 ] ); - } - } - - return pNewTopic; - } - -/*-----------------------------------------------------------*/ - - void AwsIot_FreeDefenderTopic( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _defenderTopics, - _inUseDefenderTopics, - AWS_IOT_DEFENDER_TOPICS, - _DEFENDER_TOPIC_SIZE ); - } - -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/common/static_memory/iot_static_memory_serializer.c b/lib/source/common/static_memory/iot_static_memory_serializer.c deleted file mode 100644 index 2818d32fa1..0000000000 --- a/lib/source/common/static_memory/iot_static_memory_serializer.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* This file should only be compiled if dynamic memory allocation is forbidden. */ -#if IOT_STATIC_MEMORY_ONLY == 1 - -/* Standard includes. */ - #include - #include - #include - -/* Static memory include. */ - #include "private/iot_static_memory.h" - -/* Metrics include. */ - #include "iot_serializer.h" - - #include "cbor.h" - - #ifndef IOT_SERIALIZER_CBOR_ENCODERS - #define IOT_SERIALIZER_CBOR_ENCODERS ( 10 ) - #endif - - #ifndef IOT_SERIALIZER_CBOR_PARSERS - #define IOT_SERIALIZER_CBOR_PARSERS ( 10 ) - #endif - - #ifndef IOT_SERIALIZER_CBOR_VALUES - #define IOT_SERIALIZER_CBOR_VALUES ( 10 ) - #endif - - #ifndef IOT_SERIALIZER_DECODER_OBJECTS - #define IOT_SERIALIZER_DECODER_OBJECTS ( 10 ) - #endif - -/* Validate static memory configuration settings. */ - #if IOT_SERIALIZER_CBOR_ENCODERS <= 0 - #error "IOT_SERIALIZER_CBOR_ENCODERS cannot be 0 or negative." - #endif - - #if IOT_SERIALIZER_CBOR_PARSERS <= 0 - #error "IOT_SERIALIZER_CBOR_PARSERS cannot be 0 or negative." - #endif - - #if IOT_SERIALIZER_CBOR_VALUES <= 0 - #error "IOT_SERIALIZER_CBOR_VALUES cannot be 0 or negative." - #endif - - #if IOT_SERIALIZER_DECODER_OBJECTS <= 0 - #error "IOT_SERIALIZER_DECODER_OBJECTS cannot be 0 or negative." - #endif - - /** - * @todo Placeholder. - */ - typedef struct _cborValueWrapper - { - CborValue cborValue; /**< @brief Placeholder. */ - bool isOutermost; /**< @brief Placeholder. */ - } _cborValueWrapper_t; - -/*-----------------------------------------------------------*/ - -/* Extern declarations of common static memory functions in iot_static_memory_common.c - * Because these functions are specific to this static memory implementation, they are - * not placed in the common static memory header file. */ - extern int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); - extern void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); - -/*-----------------------------------------------------------*/ - -/* - * Static memory buffers and flags, allocated and zeroed at compile-time. - */ - static bool _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0 }; - static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { .data = { 0 } } }; - - static bool _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0 }; - static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; - - static bool _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0 }; - static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { .isOutermost = false } }; - - static bool _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0 }; - static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; - -/*-----------------------------------------------------------*/ - - void * Iot_MallocSerializerCborEncoder( size_t size ) - { - int freeIndex = -1; - void * pNewCborEncoder = NULL; - - if( size == sizeof( CborEncoder ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseCborEncoders, - IOT_SERIALIZER_CBOR_ENCODERS ); - - if( freeIndex != -1 ) - { - pNewCborEncoder = &( _cborEncoders[ freeIndex ] ); - } - } - - return pNewCborEncoder; - } - -/*-----------------------------------------------------------*/ - - void Iot_FreeSerializerCborEncoder( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _cborEncoders, - _inUseCborEncoders, - IOT_SERIALIZER_CBOR_ENCODERS, - sizeof( CborEncoder ) ); - } - -/*-----------------------------------------------------------*/ - - void * Iot_MallocSerializerCborParser( size_t size ) - { - int freeIndex = -1; - void * pNewCborParser = NULL; - - if( size == sizeof( CborParser ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseCborParsers, - IOT_SERIALIZER_CBOR_PARSERS ); - - if( freeIndex != -1 ) - { - pNewCborParser = &( _cborParsers[ freeIndex ] ); - } - } - - return pNewCborParser; - } - -/*-----------------------------------------------------------*/ - - void Iot_FreeSerializerCborParser( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _cborParsers, - _inUseCborParsers, - IOT_SERIALIZER_CBOR_PARSERS, - sizeof( CborParser ) ); - } - -/*-----------------------------------------------------------*/ - - void * Iot_MallocSerializerCborValue( size_t size ) - { - int freeIndex = -1; - void * pNewCborValue = NULL; - - if( size == sizeof( _cborValueWrapper_t ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseCborValues, - IOT_SERIALIZER_CBOR_VALUES ); - - if( freeIndex != -1 ) - { - pNewCborValue = &( _cborValues[ freeIndex ] ); - } - } - - return pNewCborValue; - } - -/*-----------------------------------------------------------*/ - - void Iot_FreeSerializerCborValue( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _cborValues, - _inUseCborValues, - IOT_SERIALIZER_CBOR_VALUES, - sizeof( _cborValueWrapper_t ) ); - } - -/*-----------------------------------------------------------*/ - - void * Iot_MallocSerializerDecoderObject( size_t size ) - { - int freeIndex = -1; - void * pNewDecoderObject = NULL; - - if( size == sizeof( IotSerializerDecoderObject_t ) ) - { - freeIndex = IotStaticMemory_FindFree( _inUseDecoderObjects, - IOT_SERIALIZER_DECODER_OBJECTS ); - - if( freeIndex != -1 ) - { - pNewDecoderObject = &( _decoderObjects[ freeIndex ] ); - } - } - - return pNewDecoderObject; - } - -/*-----------------------------------------------------------*/ - - void Iot_FreeSerializerDecoderObject( void * ptr ) - { - IotStaticMemory_ReturnInUse( ptr, - _decoderObjects, - _inUseDecoderObjects, - IOT_SERIALIZER_DECODER_OBJECTS, - sizeof( IotSerializerDecoderObject_t ) ); - } - -#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index 82de0252f1..73616afb14 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -4,6 +4,7 @@ add_library( iotmqtt iot_mqtt_network.c iot_mqtt_operation.c iot_mqtt_serialize.c + iot_mqtt_static_memory.c iot_mqtt_subscription.c iot_mqtt_validate.c ) diff --git a/lib/source/common/static_memory/iot_static_memory_mqtt.c b/lib/source/mqtt/iot_mqtt_static_memory.c similarity index 80% rename from lib/source/common/static_memory/iot_static_memory_mqtt.c rename to lib/source/mqtt/iot_mqtt_static_memory.c index 7c5d3ce092..81afd5a171 100644 --- a/lib/source/common/static_memory/iot_static_memory_mqtt.c +++ b/lib/source/mqtt/iot_mqtt_static_memory.c @@ -20,8 +20,8 @@ */ /** - * @file iot_static_memory_mqtt.c - * @brief Implementation of MQTT static memory functions in iot_static_memory.h + * @file iot_mqtt_static_memory.c + * @brief Implementation of MQTT static memory functions. */ /* The config header is always included first. */ @@ -52,24 +52,24 @@ #ifndef IOT_MQTT_CONNECTIONS #define IOT_MQTT_CONNECTIONS ( 1 ) #endif -#ifndef IOT_MQTT_SUBSCRIPTIONS - #define IOT_MQTT_SUBSCRIPTIONS ( 8 ) -#endif #ifndef IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) #endif +#ifndef IOT_MQTT_SUBSCRIPTIONS + #define IOT_MQTT_SUBSCRIPTIONS ( 8 ) +#endif /** @endcond */ /* Validate static memory configuration settings. */ #if IOT_MQTT_CONNECTIONS <= 0 #error "IOT_MQTT_CONNECTIONS cannot be 0 or negative." #endif -#if IOT_MQTT_SUBSCRIPTIONS <= 0 - #error "IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." -#endif #if IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS <= 0 #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." #endif +#if IOT_MQTT_SUBSCRIPTIONS <= 0 + #error "IOT_MQTT_SUBSCRIPTIONS cannot be 0 or negative." +#endif /** * @brief The size of a static memory MQTT subscription. @@ -78,20 +78,7 @@ * #AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH is used for the length of * #_mqttSubscription_t.pTopicFilter. */ -#define _MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) - -/*-----------------------------------------------------------*/ - -/* Extern declarations of common static memory functions in iot_static_memory_common.c - * Because these functions are specific to this static memory implementation, they are - * not placed in the common static memory header file. */ -extern int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); -extern void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); +#define MQTT_SUBSCRIPTION_SIZE ( sizeof( _mqttSubscription_t ) + AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ) /*-----------------------------------------------------------*/ @@ -105,13 +92,13 @@ static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 } static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief MQTT operations. */ static bool _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ -static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ _MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ +static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ /*-----------------------------------------------------------*/ -void * Iot_MallocMqttConnection( size_t size ) +void * IotMqtt_MallocConnection( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewConnection = NULL; /* Check size argument. */ @@ -132,7 +119,7 @@ void * Iot_MallocMqttConnection( size_t size ) /*-----------------------------------------------------------*/ -void Iot_FreeMqttConnection( void * ptr ) +void IotMqtt_FreeConnection( void * ptr ) { /* Return the in-use MQTT connection. */ IotStaticMemory_ReturnInUse( ptr, @@ -144,9 +131,9 @@ void Iot_FreeMqttConnection( void * ptr ) /*-----------------------------------------------------------*/ -void * Iot_MallocMqttOperation( size_t size ) +void * IotMqtt_MallocOperation( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewOperation = NULL; /* Check size argument. */ @@ -167,7 +154,7 @@ void * Iot_MallocMqttOperation( size_t size ) /*-----------------------------------------------------------*/ -void Iot_FreeMqttOperation( void * ptr ) +void IotMqtt_FreeOperation( void * ptr ) { /* Return the in-use MQTT operation. */ IotStaticMemory_ReturnInUse( ptr, @@ -179,12 +166,12 @@ void Iot_FreeMqttOperation( void * ptr ) /*-----------------------------------------------------------*/ -void * Iot_MallocMqttSubscription( size_t size ) +void * IotMqtt_MallocSubscription( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewSubscription = NULL; - if( size <= _MQTT_SUBSCRIPTION_SIZE ) + if( size <= MQTT_SUBSCRIPTION_SIZE ) { /* Get the index of a free MQTT subscription. */ freeIndex = IotStaticMemory_FindFree( _pInUseMqttSubscriptions, @@ -201,14 +188,14 @@ void * Iot_MallocMqttSubscription( size_t size ) /*-----------------------------------------------------------*/ -void Iot_FreeMqttSubscription( void * ptr ) +void IotMqtt_FreeSubscription( void * ptr ) { /* Return the in-use MQTT subscription. */ IotStaticMemory_ReturnInUse( ptr, _pMqttSubscriptions, _pInUseMqttSubscriptions, IOT_MQTT_SUBSCRIPTIONS, - _MQTT_SUBSCRIPTION_SIZE ); + MQTT_SUBSCRIPTION_SIZE ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index fc75ec35eb..829d030b34 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -1,6 +1,7 @@ # Serializer library source files. add_library( iotserializer iot_json_utils.c + iot_serializer_static_memory.c cbor/iot_serializer_tinycbor_decoder.c cbor/iot_serializer_tinycbor_encoder.c ) diff --git a/lib/source/serializer/iot_serializer_static_memory.c b/lib/source/serializer/iot_serializer_static_memory.c new file mode 100644 index 0000000000..2be5d2d524 --- /dev/null +++ b/lib/source/serializer/iot_serializer_static_memory.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* Static memory include. */ +#include "private/iot_static_memory.h" + +/* Metrics include. */ +#include "iot_serializer.h" + +/* TinyCBOR include. */ +#include "cbor.h" + +#ifndef IOT_SERIALIZER_CBOR_ENCODERS + #define IOT_SERIALIZER_CBOR_ENCODERS ( 10 ) +#endif + +#ifndef IOT_SERIALIZER_CBOR_PARSERS + #define IOT_SERIALIZER_CBOR_PARSERS ( 10 ) +#endif + +#ifndef IOT_SERIALIZER_CBOR_VALUES + #define IOT_SERIALIZER_CBOR_VALUES ( 10 ) +#endif + +#ifndef IOT_SERIALIZER_DECODER_OBJECTS + #define IOT_SERIALIZER_DECODER_OBJECTS ( 10 ) +#endif + +/* Validate static memory configuration settings. */ +#if IOT_SERIALIZER_CBOR_ENCODERS <= 0 + #error "IOT_SERIALIZER_CBOR_ENCODERS cannot be 0 or negative." +#endif + +#if IOT_SERIALIZER_CBOR_PARSERS <= 0 + #error "IOT_SERIALIZER_CBOR_PARSERS cannot be 0 or negative." +#endif + +#if IOT_SERIALIZER_CBOR_VALUES <= 0 + #error "IOT_SERIALIZER_CBOR_VALUES cannot be 0 or negative." +#endif + +#if IOT_SERIALIZER_DECODER_OBJECTS <= 0 + #error "IOT_SERIALIZER_DECODER_OBJECTS cannot be 0 or negative." +#endif + +/** + * @todo Placeholder. + */ +typedef struct _cborValueWrapper +{ + CborValue cborValue; /**< @brief Placeholder. */ + bool isOutermost; /**< @brief Placeholder. */ +} _cborValueWrapper_t; + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static bool _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0 }; +static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { .data = { 0 } } }; + +static bool _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0 }; +static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; + +static bool _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0 }; +static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { .isOutermost = false } }; + +static bool _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0 }; +static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; + +/*-----------------------------------------------------------*/ + +void * IotSerializer_MallocCborEncoder( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewCborEncoder = NULL; + + if( size == sizeof( CborEncoder ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborEncoders, + IOT_SERIALIZER_CBOR_ENCODERS ); + + if( freeIndex != -1 ) + { + pNewCborEncoder = &( _cborEncoders[ freeIndex ] ); + } + } + + return pNewCborEncoder; +} + +/*-----------------------------------------------------------*/ + +void IotSerializer_FreeCborEncoder( void * ptr ) +{ + IotStaticMemory_ReturnInUse( ptr, + _cborEncoders, + _inUseCborEncoders, + IOT_SERIALIZER_CBOR_ENCODERS, + sizeof( CborEncoder ) ); +} + +/*-----------------------------------------------------------*/ + +void * IotSerializer_MallocCborParser( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewCborParser = NULL; + + if( size == sizeof( CborParser ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborParsers, + IOT_SERIALIZER_CBOR_PARSERS ); + + if( freeIndex != -1 ) + { + pNewCborParser = &( _cborParsers[ freeIndex ] ); + } + } + + return pNewCborParser; +} + +/*-----------------------------------------------------------*/ + +void IotSerializer_FreeCborParser( void * ptr ) +{ + IotStaticMemory_ReturnInUse( ptr, + _cborParsers, + _inUseCborParsers, + IOT_SERIALIZER_CBOR_PARSERS, + sizeof( CborParser ) ); +} + +/*-----------------------------------------------------------*/ + +void * IotSerializer_MallocCborValue( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewCborValue = NULL; + + if( size == sizeof( _cborValueWrapper_t ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseCborValues, + IOT_SERIALIZER_CBOR_VALUES ); + + if( freeIndex != -1 ) + { + pNewCborValue = &( _cborValues[ freeIndex ] ); + } + } + + return pNewCborValue; +} + +/*-----------------------------------------------------------*/ + +void IotSerializer_FreeCborValue( void * ptr ) +{ + IotStaticMemory_ReturnInUse( ptr, + _cborValues, + _inUseCborValues, + IOT_SERIALIZER_CBOR_VALUES, + sizeof( _cborValueWrapper_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * IotSerializer_MallocDecoderObject( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewDecoderObject = NULL; + + if( size == sizeof( IotSerializerDecoderObject_t ) ) + { + freeIndex = IotStaticMemory_FindFree( _inUseDecoderObjects, + IOT_SERIALIZER_DECODER_OBJECTS ); + + if( freeIndex != -1 ) + { + pNewDecoderObject = &( _decoderObjects[ freeIndex ] ); + } + } + + return pNewDecoderObject; +} + +/*-----------------------------------------------------------*/ + +void IotSerializer_FreeDecoderObject( void * ptr ) +{ + IotStaticMemory_ReturnInUse( ptr, + _decoderObjects, + _inUseDecoderObjects, + IOT_SERIALIZER_DECODER_OBJECTS, + sizeof( IotSerializerDecoderObject_t ) ); +} + +#endif diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index 834053517e..15799c9f59 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -3,6 +3,7 @@ add_library( awsiotshadow aws_iot_shadow_api.c aws_iot_shadow_operation.c aws_iot_shadow_parser.c + aws_iot_shadow_static_memory.c aws_iot_shadow_subscription.c ) # Library version. diff --git a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c b/lib/source/shadow/aws_iot_shadow_static_memory.c similarity index 79% rename from lib/source/common/static_memory/aws_iot_static_memory_shadow.c rename to lib/source/shadow/aws_iot_shadow_static_memory.c index b3f75e0bf9..3d60514e98 100644 --- a/lib/source/common/static_memory/aws_iot_static_memory_shadow.c +++ b/lib/source/shadow/aws_iot_shadow_static_memory.c @@ -20,8 +20,8 @@ */ /** - * @file aws_iot_static_memory_shadow.c - * @brief Implementation of Shadow static memory functions in iot_static_memory.h + * @file aws_iot_shadow_static_memory.c + * @brief Implementation of Shadow static memory functions. */ /* The config header is always included first. */ @@ -72,20 +72,7 @@ * the constant #MAX_THING_NAME_LENGTH is used for the length of * #_shadowSubscription_t.pThingName. */ -#define _SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + MAX_THING_NAME_LENGTH ) - -/*-----------------------------------------------------------*/ - -/* Extern declarations of common static memory functions in iot_static_memory_common.c - * Because these functions are specific to this static memory implementation, they are - * not placed in the common static memory header file. */ -extern int IotStaticMemory_FindFree( bool * const pInUse, - int limit ); -extern void IotStaticMemory_ReturnInUse( void * ptr, - void * const pPool, - bool * const pInUse, - int limit, - size_t elementSize ); +#define SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + MAX_THING_NAME_LENGTH ) /*-----------------------------------------------------------*/ @@ -96,13 +83,13 @@ static bool _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Shadow operations. */ static bool _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0 }; /**< @brief Shadow subscription in-use flags. */ -static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ _SHADOW_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Shadow subscriptions. */ +static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ SHADOW_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Shadow subscriptions. */ /*-----------------------------------------------------------*/ -void * AwsIot_MallocShadowOperation( size_t size ) +void * AwsIotShadow_MallocOperation( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewOperation = NULL; /* Check size argument. */ @@ -123,7 +110,7 @@ void * AwsIot_MallocShadowOperation( size_t size ) /*-----------------------------------------------------------*/ -void AwsIot_FreeShadowOperation( void * ptr ) +void AwsIotShadow_FreeOperation( void * ptr ) { /* Return the in-use Shadow operation. */ IotStaticMemory_ReturnInUse( ptr, @@ -135,12 +122,12 @@ void AwsIot_FreeShadowOperation( void * ptr ) /*-----------------------------------------------------------*/ -void * AwsIot_MallocShadowSubscription( size_t size ) +void * AwsIotShadow_MallocSubscription( size_t size ) { - int freeIndex = -1; + int32_t freeIndex = -1; void * pNewSubscription = NULL; - if( size <= _SHADOW_SUBSCRIPTION_SIZE ) + if( size <= SHADOW_SUBSCRIPTION_SIZE ) { /* Get the index of a free Shadow subscription. */ freeIndex = IotStaticMemory_FindFree( _pInUseShadowSubscriptions, @@ -157,14 +144,14 @@ void * AwsIot_MallocShadowSubscription( size_t size ) /*-----------------------------------------------------------*/ -void AwsIot_FreeShadowSubscription( void * ptr ) +void AwsIotShadow_FreeSubscription( void * ptr ) { /* Return the in-use Shadow subscription. */ IotStaticMemory_ReturnInUse( ptr, _pShadowSubscriptions, _pInUseShadowSubscriptions, AWS_IOT_SHADOW_SUBSCRIPTIONS, - _SHADOW_SUBSCRIPTION_SIZE ); + SHADOW_SUBSCRIPTION_SIZE ); } /*-----------------------------------------------------------*/ diff --git a/tests/iot_config.h b/tests/iot_config.h index 3a6d9693ab..b630014036 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -72,9 +72,6 @@ /* Shadow library configuration. */ #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) -/* Metrics library configuration. */ -#define IOT_METRICS_ENABLE_ASSERTS ( 1 ) - /* Serializer library configuration. */ #define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) @@ -117,14 +114,6 @@ #define IotMqtt_FreeOperation unity_free_mt #define IotMqtt_MallocSubscription unity_malloc_mt #define IotMqtt_FreeSubscription unity_free_mt - #define IotMqtt_MallocTimerEvent unity_malloc_mt - #define IotMqtt_FreeTimerEvent unity_free_mt - #define AwsIotShadow_MallocOperation unity_malloc_mt - #define AwsIotShadow_FreeOperation unity_free_mt - #define AwsIotShadow_MallocString unity_malloc_mt - #define AwsIotShadow_FreeString unity_free_mt - #define AwsIotShadow_MallocSubscription unity_malloc_mt - #define AwsIotShadow_FreeSubscription unity_free_mt #define IotSerializer_MallocCborEncoder unity_malloc_mt #define IotSerializer_FreeCborEncoder unity_free_mt #define IotSerializer_MallocCborParser unity_malloc_mt @@ -137,6 +126,12 @@ #define AwsIotDefender_FreeReport unity_free_mt #define AwsIotDefender_MallocTopic unity_malloc_mt #define AwsIotDefender_FreeTopic unity_free_mt + #define AwsIotShadow_MallocOperation unity_malloc_mt + #define AwsIotShadow_FreeOperation unity_free_mt + #define AwsIotShadow_MallocString unity_malloc_mt + #define AwsIotShadow_FreeString unity_free_mt + #define AwsIotShadow_MallocSubscription unity_malloc_mt + #define AwsIotShadow_FreeSubscription unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* Network header to include in the tests. */ From d47df2754b21980a2cc49255bc66ba8cbddb4041 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 6 May 2019 15:21:19 -0700 Subject: [PATCH 124/844] Move common network structs to shared header (#407) --- demos/app/posix/iot_demo_posix.c | 10 +-- lib/include/platform/iot_network.h | 57 ++++++++++++++++ platform/include/posix/iot_network_openssl.h | 72 +++----------------- platform/source/posix/iot_network_openssl.c | 14 ++-- tests/defender/aws_iot_tests_defender_api.c | 6 +- tests/iot_config.h | 12 ++-- 6 files changed, 87 insertions(+), 84 deletions(-) diff --git a/demos/app/posix/iot_demo_posix.c b/demos/app/posix/iot_demo_posix.c index 781e78ccc0..b145212591 100644 --- a/demos/app/posix/iot_demo_posix.c +++ b/demos/app/posix/iot_demo_posix.c @@ -75,8 +75,8 @@ int main( int argc, IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; /* Network server info and credentials. */ - IotNetworkServerInfoOpenssl_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; - IotNetworkCredentialsOpenssl_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, + IotNetworkServerInfo_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; + IotNetworkCredentials_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, * pCredentials = NULL; /* Set default identifier if defined. The identifier is used as either the @@ -99,9 +99,9 @@ int main( int argc, if( demoArguments.securedConnection == true ) { /* Set credential paths. */ - credentials.pClientCertPath = demoArguments.pClientCertPath; - credentials.pPrivateKeyPath = demoArguments.pPrivateKeyPath; - credentials.pRootCaPath = demoArguments.pRootCaPath; + credentials.pClientCert = demoArguments.pClientCertPath; + credentials.pPrivateKey = demoArguments.pPrivateKeyPath; + credentials.pRootCa = demoArguments.pRootCaPath; /* By default, the credential initializer enables ALPN with AWS IoT, * which only works over port 443. Disable ALPN if another port is diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index 0a9a4a511a..a94becb354 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -229,4 +229,61 @@ typedef struct IotNetworkInterface /* @[declare_platform_network_destroy] */ } IotNetworkInterface_t; +/** + * @ingroup platform_datatypes_paramstructs + * @brief Information on the remote server for connection setup. + * + * May be passed to #IotNetworkInterface_t.create as `pConnectionInfo`. This + * structure contains commonly-used parameters, but may be replaced with an + * alternative. + */ +typedef struct IotNetworkServerInfo +{ + const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ + uint16_t port; /**< @brief Server port in host-order. */ +} IotNetworkServerInfo_t; + +/** + * @ingroup platform_datatypes_paramstructs + * @brief Contains the credentials necessary for connection setup. + * + * May be passed to #IotNetworkInterface_t.create as `pCredentialInfo`. This + * structure contains commonly-used parameters, but may be replaced with an + * alternative. + */ +typedef struct IotNetworkCredentials +{ + /** + * @brief Set this to a non-NULL value to use ALPN. + * + * This string must be NULL-terminated. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char * pAlpnProtos; + + /** + * @brief Set this to a non-zero value to use TLS max fragment length + * negotiation (TLS MFLN). + * + * @note The network stack may have a minimum value for this parameter and + * may return an error if this parameter is too small. + */ + size_t maxFragmentLength; + + /** + * @brief Disable server name indication (SNI) for a TLS session. + */ + bool disableSni; + + const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ + size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials_t.pRootCa. */ + const char * pClientCert; /**< @brief String representing the client certificate. */ + size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials_t.pClientCert. */ + const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ + size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials_t.pPrivateKey. */ +} IotNetworkCredentials_t; + #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index cbf93ea89a..1b5cb9a957 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -56,76 +56,21 @@ typedef struct _networkConnection IotNetworkConnectionOpenssl_t; /** - * @brief Information on the remote server for connection setup. + * @brief Provides a default value for an #IotNetworkServerInfo_t. * - * Passed to #IotNetworkOpenssl_Create as `pConnectionInfo`. + * All instances of #IotNetworkServerInfo_t should be initialized with + * this constant when using this OpenSSL network stack. * - * All instances of #IotNetworkServerInfoOpenssl_t should be initialized with - * #IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER. - */ -typedef struct IotNetworkServerInfoOpenssl -{ - const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ - uint16_t port; /**< @brief Server port in host-order. */ -} IotNetworkServerInfoOpenssl_t; - -/** - * @brief Contains the credentials necessary for connection setup with OpenSSL. - * - * Passed to #IotNetworkOpenssl_Create as `pCredentialInfo`. - * - * All instances of #IotNetworkCredentialsOpenssl_t should be initialized with either - * #AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER (for connections to AWS IoT) or - * #IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER (for other connections). - */ -typedef struct IotNetworkCredentialsOpenssl -{ - /** - * @brief Set this to a non-NULL value to use ALPN. - * - * This string must be NULL-terminated. - * - * See [this link] - * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) - * for more information. - */ - const char * pAlpnProtos; - - /** - * @brief Set this to a non-zero value to use TLS max fragment length - * negotiation (TLS MFLN). - * - * @note OpenSSL may have a minimum value for this parameter; - * #IotNetworkOpenssl_Create may return an error if this parameter is too small. - */ - size_t maxFragmentLength; - - /** - * @brief Disable server name indication (SNI) for a TLS session. - */ - bool disableSni; - - /* These paths must be NULL-terminated. */ - const char * pRootCaPath; /**< @brief Filesystem path of a trusted server root certificate. */ - const char * pClientCertPath; /**< @brief Filesystem path of the client certificate. */ - const char * pPrivateKeyPath; /**< @brief Filesystem path of the client certificate's private key. */ -} IotNetworkCredentialsOpenssl_t; - -/** - * @brief Provides a default value for an #IotNetworkServerInfoOpenssl_t. - * - * All instances of #IotNetworkServerInfoOpenssl_t should be initialized with - * this constant. - * - * @warning Failing to initialize an #IotNetworkServerInfoOpenssl_t with this - * initializer may result in undefined behavior! + * @warning Failing to initialize an #IotNetworkServerInfo_t may result in + * a crash! * @note This initializer may change at any time in future versions, but its * name will remain the same. */ #define IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER { 0 } /** - * @brief Initialize an #IotNetworkCredentialsOpenssl_t for AWS IoT with ALPN enabled. + * @brief Initialize an #IotNetworkCredentials_t for AWS IoT with ALPN enabled + * when using this OpenSSL network stack. * * @note This initializer may change at any time in future versions, but its * name will remain the same. @@ -136,7 +81,8 @@ typedef struct IotNetworkCredentialsOpenssl } /** - * @brief Generic initializer for an #IotNetworkCredentialsOpenssl_t. + * @brief Generic initializer for an #IotNetworkCredentials_t when using this + * OpenSSL network stack. * * @note This initializer may change at any time in future versions, but its * name will remain the same. diff --git a/platform/source/posix/iot_network_openssl.c b/platform/source/posix/iot_network_openssl.c index b0e0f2761c..382488b3b6 100644 --- a/platform/source/posix/iot_network_openssl.c +++ b/platform/source/posix/iot_network_openssl.c @@ -198,7 +198,7 @@ static void * _networkReceiveThread( void * pArgument ) * * @return A connected TCP socket number; `-1` if the DNS lookup failed. */ -static int _dnsLookup( const IotNetworkServerInfoOpenssl_t * pServerInfo ) +static int _dnsLookup( const IotNetworkServerInfo_t * pServerInfo ) { IOT_FUNCTION_ENTRY( int, 0 ); int tcpSocket = -1; @@ -412,7 +412,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, */ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, const char * pServerName, - const IotNetworkCredentialsOpenssl_t * pOpensslCredentials ) + const IotNetworkCredentials_t * pOpensslCredentials ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); SSL_CTX * pSslContext = NULL; @@ -438,9 +438,9 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* Import all credentials. */ if( _readCredentials( pSslContext, - pOpensslCredentials->pRootCaPath, - pOpensslCredentials->pClientCertPath, - pOpensslCredentials->pPrivateKeyPath ) == false ) + pOpensslCredentials->pRootCa, + pOpensslCredentials->pClientCert, + pOpensslCredentials->pPrivateKey ) == false ) { IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -664,8 +664,8 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, _networkConnection_t * pNewNetworkConnection = NULL; /* Cast function parameters to correct types. */ - const IotNetworkServerInfoOpenssl_t * const pServerInfo = pConnectionInfo; - const IotNetworkCredentialsOpenssl_t * const pOpensslCredentials = pCredentialInfo; + const IotNetworkServerInfo_t * const pServerInfo = pConnectionInfo; + const IotNetworkCredentials_t * const pOpensslCredentials = pCredentialInfo; _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t ** const ) pConnection; /* Allocate memory for a new connection. */ diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index b3010b6bef..7092b40511 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -80,8 +80,8 @@ /* Empty callback structure passed to startInfo. */ static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .param1 = NULL }; -static IotNetworkServerInfoOpenssl_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; -static IotNetworkCredentialsOpenssl_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +static IotNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; +static IotNetworkCredentials_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; /*------------------ global variables -----------------------------*/ @@ -159,7 +159,7 @@ TEST_SETUP( Full_DEFENDER ) } /* Reset server info. */ - _serverInfo = ( IotNetworkServerInfoOpenssl_t ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + _serverInfo = ( IotNetworkServerInfo_t ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /* Set fields of start info. */ _startInfo.mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; diff --git a/tests/iot_config.h b/tests/iot_config.h index b630014036..4ebeaa5c47 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -138,9 +138,9 @@ #define IOT_TEST_NETWORK_HEADER "posix/iot_network_openssl.h" /* Network types to use in the tests. These are forward declarations. */ -typedef struct _networkConnection IotTestNetworkConnection_t; -typedef struct IotNetworkServerInfoOpenssl IotTestNetworkServerInfo_t; -typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; +typedef struct _networkConnection IotTestNetworkConnection_t; +typedef struct IotNetworkServerInfo IotTestNetworkServerInfo_t; +typedef struct IotNetworkCredentials IotTestNetworkCredentials_t; /* Initializers for the tests' network types. */ #define IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER \ @@ -152,9 +152,9 @@ typedef struct IotNetworkCredentialsOpenssl IotTestNetworkCredentials_t; #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER \ { \ .pAlpnProtos = "\x0ex-amzn-mqtt-ca", \ - .pRootCaPath = IOT_TEST_ROOT_CA, \ - .pClientCertPath = IOT_TEST_CLIENT_CERT, \ - .pPrivateKeyPath = IOT_TEST_PRIVATE_KEY \ + .pRootCa = IOT_TEST_ROOT_CA, \ + .pClientCert = IOT_TEST_CLIENT_CERT, \ + .pPrivateKey = IOT_TEST_PRIVATE_KEY \ } #else #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER From 9248ad2eadd9694c230da23326e5db80bcf423cf Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 8 May 2019 09:57:25 -0700 Subject: [PATCH 125/844] Add mbed TLS network stack implementation (#408) --- .gitmodules | 3 + .travis.yml | 18 +- CMakeLists.txt | 15 + README.md | 1 - demos/app/posix/iot_demo_arguments_posix.c | 7 + demos/app/posix/iot_demo_posix.c | 35 +- doc/lib/platform.txt | 2 +- platform/include/iot_network_mbedtls.h | 161 +++ platform/include/posix/iot_network_openssl.h | 7 - platform/source/network/iot_network_mbedtls.c | 1111 +++++++++++++++++ platform/source/network/iot_network_metrics.c | 100 +- platform/source/posix/CMakeLists.txt | 40 +- platform/source/posix/iot_network_openssl.c | 54 +- scripts/ci_test_mqtt.sh | 4 +- scripts/ci_test_shadow.sh | 4 +- tests/defender/aws_iot_tests_defender.c | 66 +- tests/defender/aws_iot_tests_defender_api.c | 97 +- tests/iot_config.h | 44 +- third_party/mbedtls/CMakeLists.txt | 71 ++ third_party/mbedtls/iot_config_mbedtls.h | 139 +++ third_party/mbedtls/iot_mbedtls_threading.c | 80 ++ third_party/mbedtls/mbedtls | 1 + third_party/mbedtls/threading_alt.h | 59 + .../fixture/unity_fixture_malloc_overrides.h | 13 +- .../unity/unity/fixture/unity_memory_mt.c | 5 + 25 files changed, 1914 insertions(+), 223 deletions(-) create mode 100644 platform/include/iot_network_mbedtls.h create mode 100644 platform/source/network/iot_network_mbedtls.c create mode 100644 third_party/mbedtls/CMakeLists.txt create mode 100644 third_party/mbedtls/iot_config_mbedtls.h create mode 100644 third_party/mbedtls/iot_mbedtls_threading.c create mode 160000 third_party/mbedtls/mbedtls create mode 100644 third_party/mbedtls/threading_alt.h diff --git a/.gitmodules b/.gitmodules index 2c12f1d5d2..7dc15df6f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third_party/tinycbor/tinycbor"] path = third_party/tinycbor/tinycbor url = https://github.com/intel/tinycbor.git +[submodule "third_party/mbedtls/mbedtls"] + path = third_party/mbedtls/mbedtls + url = https://github.com/ARMmbed/mbedtls.git diff --git a/.travis.yml b/.travis.yml index e9bc85c781..f0a78a054c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,20 +12,20 @@ language: c compiler: - clang +# Matrix of tests to run. jobs: include: - # Run code coverage job for commit builds. + - env: RUN_TEST=common + - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls + - env: RUN_TEST=mqtt NETWORK_STACK=openssl + - env: RUN_TEST=shadow NETWORK_STACK=mbedtls + - if: type = push + env: RUN_TEST=shadow NETWORK_STACK=openssl + - env: RUN_TEST=defender - if: type = push compiler: gcc env: RUN_TEST=coverage -# Matrix of tests to always run. -env: - - RUN_TEST=common # Common libraries (linear containers, common libraries, etc.) - - RUN_TEST=mqtt # MQTT - - RUN_TEST=shadow # AWS IoT Thing Shadows - - RUN_TEST=defender # AWS IoT Device Defender - # Update repositories. before_install: - sudo apt-get update @@ -43,6 +43,8 @@ script: - export IOT_IDENTIFIER="$IOT_IDENTIFIER_PREFIX$RUN_TEST" # Set default compiler options. Individual test scripts may override this. - export COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" + # Choose the network abstraction. + - if [ "$NETWORK_STACK" = "openssl" ]; then export IOT_NETWORK_USE_OPENSSL=1; else export IOT_NETWORK_USE_OPENSSL=0; fi # Get AWS credentials when not a pull request build. - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then mkdir credentials; fi - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O credentials/AmazonRootCA1.pem; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 409d092d1d..b53f545586 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,9 @@ include_directories( ${PROJECT_SOURCE_DIR}/lib/include # TinyCBOR include path. include_directories( SYSTEM ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) +# mbed TLS include path. +include_directories( SYSTEM ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/include ) + # Demo include path. Always required because the tests also build the demos. include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) @@ -74,6 +77,15 @@ endif() # Platform libraries. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + # Provide an option to use the OpenSSL network abstraction on Linux. + option( IOT_NETWORK_USE_OPENSSL + "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." + OFF ) + + if( ${IOT_NETWORK_USE_OPENSSL} ) + add_definitions( -DIOT_NETWORK_USE_OPENSSL=1 ) + endif() + add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) add_subdirectory( platform/source/posix ) endif() @@ -96,6 +108,9 @@ add_subdirectory( lib/source/defender ) # TinyCBOR library (third-party). add_subdirectory( third_party/tinycbor ) +# mbed TLS library (third-party). +add_subdirectory( third_party/mbedtls ) + # Determine the demo executable to build based on system. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( demos/app/posix ) diff --git a/README.md b/README.md index 5ae1db5917..221fcf2501 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ This Beta library is a new design that inherits from both the AWS IoT Device SDK Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: - Auto-reconnect for MQTT connections. -- mbedTLS network stack. - Shadow JSON document generator. - Jobs API. - Build support for Apple macOS. diff --git a/demos/app/posix/iot_demo_arguments_posix.c b/demos/app/posix/iot_demo_arguments_posix.c index bf96e1b802..7434125a59 100644 --- a/demos/app/posix/iot_demo_arguments_posix.c +++ b/demos/app/posix/iot_demo_arguments_posix.c @@ -254,6 +254,13 @@ bool IotDemo_ParseArguments( int argc, IotLogInfo( "Command line arguments successfully parsed." ); + /* AWS IoT only uses secured connections; disable AWS IoT mode if the connection + * is unsecured. */ + if( pArguments->securedConnection == false ) + { + pArguments->awsIotMqttMode = false; + } + IotLogDebug( "AWS IoT MQTT mode: %s", pArguments->awsIotMqttMode == true ? "true" : "false" ); IotLogDebug( "Secured connection: %s", pArguments->securedConnection == true ? "true" : "false" ); IotLogDebug( "Host: %s", pArguments->pHostName ); diff --git a/demos/app/posix/iot_demo_posix.c b/demos/app/posix/iot_demo_posix.c index b145212591..c1fbd2fe71 100644 --- a/demos/app/posix/iot_demo_posix.c +++ b/demos/app/posix/iot_demo_posix.c @@ -39,8 +39,29 @@ #include "iot_demo_arguments.h" #include "iot_demo_logging.h" -/* POSIX+OpenSSL network include. */ -#include "posix/iot_network_openssl.h" +/* Choose the appropriate network header, initializers, and initialization + * function. */ +#if IOT_NETWORK_USE_OPENSSL == 1 + /* POSIX+OpenSSL network include. */ + #include "posix/iot_network_openssl.h" + + #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL + #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER + #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER + + #define IotDemoNetwork_Init IotNetworkOpenssl_Init + #define IotDemoNetwork_Cleanup IotNetworkOpenssl_Cleanup +#else + /* mbed TLS network include. */ + #include "iot_network_mbedtls.h" + + #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS + #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER + #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER + + #define IotDemoNetwork_Init IotNetworkMbedtls_Init + #define IotDemoNetwork_Cleanup IotNetworkMbedtls_Cleanup +#endif /* This file calls a generic placeholder demo function. The build system selects * the actual demo function to run by defining it. */ @@ -75,8 +96,8 @@ int main( int argc, IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; /* Network server info and credentials. */ - IotNetworkServerInfo_t serverInfo = IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER; - IotNetworkCredentials_t credentials = AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER, + IotNetworkServerInfo_t serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; + IotNetworkCredentials_t credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, * pCredentials = NULL; /* Set default identifier if defined. The identifier is used as either the @@ -135,7 +156,7 @@ int main( int argc, /* Initialize the network stack. */ if( status == EXIT_SUCCESS ) { - networkInitStatus = IotNetworkOpenssl_Init(); + networkInitStatus = IotDemoNetwork_Init(); if( networkInitStatus == IOT_NETWORK_SUCCESS ) { @@ -155,7 +176,7 @@ int main( int argc, demoArguments.pIdentifier, &serverInfo, pCredentials, - IOT_NETWORK_INTERFACE_OPENSSL ); + IOT_DEMO_NETWORK_INTERFACE ); } /* Clean up the SDK if initialized. */ @@ -167,7 +188,7 @@ int main( int argc, /* Clean up the network stack if initialized. */ if( networkInitialized == true ) { - IotNetworkOpenssl_Cleanup(); + IotDemoNetwork_Cleanup(); } /* Log the demo status. */ diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index ae25395358..4bb0291733 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -18,7 +18,7 @@ Component | Supported platforms --------- | ------------------- @ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) -@ref platform_network | [Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) +@ref platform_network | [mbed TLS](https://tls.mbed.org/)
[Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) @ref platform_metrics | Sample implementation using the @ref platform_network abstraction
This implementation is not intended for production use. */ diff --git a/platform/include/iot_network_mbedtls.h b/platform/include/iot_network_mbedtls.h new file mode 100644 index 0000000000..678082cb5f --- /dev/null +++ b/platform/include/iot_network_mbedtls.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_network_mbedtls.h + * @brief Declares the network stack functions specified in iot_network.h for + * mbed TLS. + */ + +#ifndef IOT_NETWORK_MBEDTLS_H_ +#define IOT_NETWORK_MBEDTLS_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Platform types include. */ +#include "types/iot_platform_types.h" + +/* Platform network include. */ +#include "platform/iot_network.h" + +/** + * @brief Provides a default value for an #IotNetworkServerInfo_t. + * + * All instances of #IotNetworkServerInfo_t should be initialized with + * this constant when using this mbed TLS network stack. + * + * @warning Failing to initialize an #IotNetworkServerInfo_t may result in + * a crash! + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER { 0 } + +/** + * @brief Initialize an #IotNetworkCredentials_t for AWS IoT with ALPN enabled + * when using this mbed TLS network stack. + * + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER \ + { \ + .pAlpnProtos = "x-amzn-mqtt-ca" \ + } + +/** + * @brief Generic initializer for an #IotNetworkCredentials_t when using this + * mbed TLS network stack. + * + * @note This initializer may change at any time in future versions, but its + * name will remain the same. + */ +#define IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER { 0 } + +/** + * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions + * declared in this file. + */ +#define IOT_NETWORK_INTERFACE_MBEDTLS ( &( IotNetworkMbedtls ) ) + + /** + * @brief One-time initialization function for this network stack. + * + * This function performs internal setup of this network stack. It must be + * called once (and only once) before calling any other function in this network + * stack. Calling this function more than once without first calling + * #IotNetworkMbedtls_Cleanup may result in a crash. + * + * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_FAILURE. + * + * @warning No thread-safety guarantees are provided for this function. + */ +IotNetworkError_t IotNetworkMbedtls_Init( void ); + +/** + * @brief One-time deinitialization function for this network stack. + * + * This function frees resources taken in #IotNetworkMbedtls_Init. It should be + * called after destroying all network connections to clean up this network + * stack. After this function returns, #IotNetworkMbedtls_Init must be called + * again before calling any other function in this network stack. + * + * @warning No thread-safety guarantees are provided for this function. Do not + * call this function if any network connections exist! + */ +void IotNetworkMbedtls_Cleanup( void ); + +/** + * @brief An implementation of #IotNetworkInterface_t::create for mbed TLS. + */ +IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, + void * pCredentialInfo, + void ** pConnection ); + +/** + * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for + * mbed TLS. + */ +IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ); + +/** + * @brief An implementation of #IotNetworkInterface_t::send for mbed TLS. + */ +size_t IotNetworkMbedtls_Send( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ); + +/** + * @brief An implementation of #IotNetworkInterface_t::receive for mbed TLS. + */ +size_t IotNetworkMbedtls_Receive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ); + +/** + * @brief An implementation of #IotNetworkInterface_t::close for mbed TLS. + */ +IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ); + +/** + * @brief An implementation of #IotNetworkInterface_t::destroy for mbed TLS. + */ +IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ); + +/** + * @brief Used by metrics to retrieve remote server and port of a connection. + */ +void IotNetworkMbedtls_GetServerInfo( void * pConnection, + IotMetricsTcpConnection_t * pServerInfo ); + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Declaration of a network interface struct using the functions in this file. + */ +extern const IotNetworkInterface_t IotNetworkMbedtls; +/** @endcond */ + +#endif /* ifndef IOT_NETWORK_MBEDTLS_H_ */ diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 1b5cb9a957..77b9c8cb28 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -31,13 +31,6 @@ /* The config header is always included first. */ #include "iot_config.h" -/* POSIX types include. */ -#ifdef POSIX_TYPES_HEADER - #include POSIX_TYPES_HEADER -#else - #include -#endif - /* Standard bool include. */ #include diff --git a/platform/source/network/iot_network_mbedtls.c b/platform/source/network/iot_network_mbedtls.c new file mode 100644 index 0000000000..775826a2c5 --- /dev/null +++ b/platform/source/network/iot_network_mbedtls.c @@ -0,0 +1,1111 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_network_mbedtls.c + * @brief Implementation of the network interface functions in iot_network.h + * for mbed TLS. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* mbed TLS network include. */ +#include "iot_network_mbedtls.h" + +/* mbed TLS includes. */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Platform clock include. */ +#include "platform/iot_clock.h" + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Atomic include. */ +#include "iot_atomic.h" + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_NETWORK + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "NET" ) +#include "iot_logging_setup.h" + +/* Logging macro for mbed TLS errors. */ +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #define _logMbedtlsError( error, pConnection, pMessage ) \ + { \ + char pErrorMessage[ 80 ] = { 0 }; \ + mbedtls_strerror( error, pErrorMessage, 80 ); \ + \ + if( pConnection != NULL ) \ + { \ + IotLogError( "(Network connection %p) %s error: %s. ", \ + pConnection, \ + pMessage, \ + pErrorMessage ); \ + } \ + else \ + { \ + IotLogError( "%s error: %s. ", \ + pMessage, \ + pErrorMessage ); \ + } \ + } +#else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + #define _logMbedtlsError( error, pConnection, pMessage ) +#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + +/* + * Provide default values for undefined memory allocation functions. + */ +#ifndef IotNetwork_Malloc + #include + +/** + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define IotNetwork_Malloc malloc +#endif +#ifndef IotNetwork_Free + #include + +/** + * @brief Free memory. This function should have the same signature as + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define IotNetwork_Free free +#endif + +/** + * @brief The timeout for the mbed TLS poll call in the receive thread. + * + * After the timeout expires, the receive thread will check will queries the + * connection flags to ensure that the connection is still open. Therefore, + * this flag represents the maximum time it takes for the receive thread to + * detect a closed connection. + * + * This timeout is also used to wait for all receive threads to exit during + * global cleanup. + * + * Since this value only affects the shutdown sequence, it generally does not + * need to be changed. + */ +#ifndef IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS + #define IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ( 1000UL ) +#endif + +/* Flags to track connection state. */ +#define FLAG_SECURED ( 0x00000001UL ) /**< @brief Secured connection. */ +#define FLAG_HAS_RECEIVE_CALLBACK ( 0x00000002UL ) /**< @brief Connection has receive callback. */ +#define FLAG_CLOSED ( 0x00000004UL ) /**< @brief Connection is closed. */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Represents a network connection. + */ +typedef struct _networkConnection +{ + uint32_t flags; /**< @brief Connection state flags. */ + mbedtls_net_context networkContext; /**< @brief mbed TLS wrapper for system's sockets. */ + IotMutex_t networkMutex; /**< @brief Protects this network context from concurrent access. */ + + IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ + void * pReceiveContext; /**< @brief The context for the receive callback. */ + IotSemaphore_t destroyNotification; /**< @brief Notifies the receive callback that the connection was destroyed. */ + + /** + * @brief Secured connection context. Valid if `secured` is `true`. + */ + struct + { + /* ALPN protocols formatted for mbed TLS. The second element of this array + * is always NULL. */ + const char * pAlpnProtos[ 2 ]; + + mbedtls_ssl_config config; /**< @brief SSL connection configuration. */ + mbedtls_ssl_context context; /**< @brief SSL connection context. */ + + /** + * @brief Credentials for SSL connection. + */ + struct + { + mbedtls_x509_crt rootCa; /**< @brief Root CA certificate. */ + mbedtls_x509_crt clientCert; /**< @brief Client certificate. */ + mbedtls_pk_context privateKey; /**< @brief Client certificate private key. */ + } credentials; + } ssl; +} _networkConnection_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief mbed TLS entropy context for generation of random numbers. + */ +static mbedtls_entropy_context _entropyContext; + +/** + * @brief mbed TLS CTR DRBG context for generation of random numbers. + */ +static mbedtls_ctr_drbg_context _ctrDrbgContext; + +/** + * @brief Tracks the number of active receive threads. + */ +static uint32_t _receiveThreadCount = 0; + +/** + * @brief An #IotNetworkInterface_t that uses the functions in this file. + */ +const IotNetworkInterface_t IotNetworkMbedtls = +{ + .create = IotNetworkMbedtls_Create, + .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, + .send = IotNetworkMbedtls_Send, + .receive = IotNetworkMbedtls_Receive, + .close = IotNetworkMbedtls_Close, + .destroy = IotNetworkMbedtls_Destroy +}; + +/*-----------------------------------------------------------*/ + +/** + * @brief Initialize the mbed TLS structures in a network connection. + * + * @param[in] pNetworkConnection The network connection to initialize. + */ +static void _sslContextInit( _networkConnection_t * pNetworkConnection ) +{ + mbedtls_ssl_config_init( &( pNetworkConnection->ssl.config ) ); + mbedtls_x509_crt_init( &( pNetworkConnection->ssl.credentials.rootCa ) ); + mbedtls_x509_crt_init( &( pNetworkConnection->ssl.credentials.clientCert ) ); + mbedtls_pk_init( &( pNetworkConnection->ssl.credentials.privateKey ) ); + mbedtls_ssl_init( &( pNetworkConnection->ssl.context ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Free the mbed TLS structures in a network connection. + * + * @param[in] pNetworkConnection The network connection with the contexts to free. + */ +static void _sslContextFree( _networkConnection_t * pNetworkConnection ) +{ + mbedtls_ssl_free( &( pNetworkConnection->ssl.context ) ); + mbedtls_pk_free( &( pNetworkConnection->ssl.credentials.privateKey ) ); + mbedtls_x509_crt_free( &( pNetworkConnection->ssl.credentials.clientCert ) ); + mbedtls_x509_crt_free( &( pNetworkConnection->ssl.credentials.rootCa ) ); + mbedtls_ssl_config_free( &( pNetworkConnection->ssl.config ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Destroy a network connection. + * + * @param[in] pNetworkConnection The network connection to destroy. + */ +static void _destroyConnection( _networkConnection_t * pNetworkConnection ) +{ + /* Clean up the SSL context of secured connections. */ + if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + { + _sslContextFree( pNetworkConnection ); + } + + /* Shutdown and close the network connection. */ + mbedtls_net_free( &( pNetworkConnection->networkContext ) ); + + /* Destroy synchronization objects. */ + IotMutex_Destroy( &( pNetworkConnection->networkMutex ) ); + + if( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == FLAG_HAS_RECEIVE_CALLBACK ) + { + IotSemaphore_Destroy( &( pNetworkConnection->destroyNotification ) ); + } + + /* Free memory. */ + IotNetwork_Free( pNetworkConnection ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Network receive thread. + * + * This thread polls the network socket and reads data when data is available. + * It then invokes the receive callback, if any. + * + * @param[in] pArgument The connection associated with this receive thread. + */ +static void _receiveThread( void * pArgument ) +{ + bool connectionClosed = false; + int pollStatus = 0; + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pArgument; + + /* Continuously poll the network connection for events. */ + while( true ) + { + pollStatus = mbedtls_net_poll( &( pNetworkConnection->networkContext ), + MBEDTLS_NET_POLL_READ, + IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); + + if( pollStatus < 0 ) + { + /* Error during poll. */ + _logMbedtlsError( pollStatus, pNetworkConnection, "Error polling network connection." ); + break; + } + else + { + IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + + /* Check if connection is closed. */ + connectionClosed = ( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ); + + if( connectionClosed == false ) + { + /* Invoke receive callback if data is available. */ + if( pollStatus == MBEDTLS_NET_POLL_READ ) + { + pNetworkConnection->receiveCallback( pNetworkConnection, + pNetworkConnection->pReceiveContext ); + } + } + + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + + /* Exit if the connection is closed. */ + if( connectionClosed == true ) + { + break; + } + } + } + + /* Wait for the call to network destroy, then destroy the connection. */ + IotSemaphore_Wait( &( pNetworkConnection->destroyNotification ) ); + _destroyConnection( pNetworkConnection ); + + IotLogDebug( "(Network connection %p) Receive thread terminating.", pNetworkConnection ); + + ( void ) Atomic_Decrement_u32( &_receiveThreadCount ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Reads credentials from the filesystem. + * + * Uses mbed TLS to import the root CA certificate, client certificate, and + * client certificate private key. + * @param[in] pNetworkConnection Network connection for the imported credentials. + * @param[in] pRootCaPath Path to the root CA certificate. + * @param[in] pClientCertPath Path to the client certificate. + * @param[in] pCertPrivateKeyPath Path to the client certificate private key. + * + * @return `true` if all credentials were successfully read; `false` otherwise. + */ +static bool _readCredentials( _networkConnection_t * pNetworkConnection, + const char * pRootCaPath, + const char * pClientCertPath, + const char * pCertPrivateKeyPath ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + int mbedtlsError = 0; + + /* Read the root CA certificate. */ + mbedtlsError = mbedtls_x509_crt_parse_file( &( pNetworkConnection->ssl.credentials.rootCa ), + pRootCaPath ); + + if( mbedtlsError < 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to read root CA certificate file." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else if( mbedtlsError > 0 ) + { + IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", + pRootCaPath, + mbedtlsError ); + } + + /* Read the client certificate. */ + mbedtlsError = mbedtls_x509_crt_parse_file( &( pNetworkConnection->ssl.credentials.clientCert ), + pClientCertPath ); + + if( mbedtlsError < 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to read client certificate file." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else if( mbedtlsError > 0 ) + { + IotLogWarn( "Failed to parse all certificates in %s; %d were parsed.", + pClientCertPath, + mbedtlsError ); + } + + /* Read the client certificate private key. */ + mbedtlsError = mbedtls_pk_parse_keyfile( &( pNetworkConnection->ssl.credentials.privateKey ), + pCertPrivateKeyPath, + NULL ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to read client certificate private key file." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Set the credentials in the SSL configuration. */ + mbedtls_ssl_conf_ca_chain( &( pNetworkConnection->ssl.config ), + &( pNetworkConnection->ssl.credentials.rootCa ), + NULL ); + + mbedtlsError = mbedtls_ssl_conf_own_cert( &( pNetworkConnection->ssl.config ), + &( pNetworkConnection->ssl.credentials.clientCert ), + &( pNetworkConnection->ssl.credentials.privateKey ) ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to configure credentials." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Set up TLS on a TCP connection. + * + * @param[in] pNetworkConnection An established TCP connection. + * @param[in] pServerName Remote host name, used for server name indication. + * @param[in] pMbedtlsCredentials TLS setup parameters. + * + * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. + */ +static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, + const char * pServerName, + const IotNetworkCredentials_t * pMbedtlsCredentials ) +{ + IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + int mbedtlsError = 0; + bool fragmentLengthValid = true; + unsigned char mflCode = 0; + uint32_t verifyResult = 0; + + /* Flags to track initialization. */ + bool sslContextInitialized = false; + + /* Initialize SSL configuration. */ + _sslContextInit( pNetworkConnection ); + sslContextInitialized = true; + + mbedtlsError = mbedtls_ssl_config_defaults( &( pNetworkConnection->ssl.config ), + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set default SSL configuration." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Set SSL authmode and the RNG context. */ + mbedtls_ssl_conf_authmode( &( pNetworkConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); + mbedtls_ssl_conf_rng( &( pNetworkConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); + + if( _readCredentials( pNetworkConnection, + pMbedtlsCredentials->pRootCa, + pMbedtlsCredentials->pClientCert, + pMbedtlsCredentials->pPrivateKey ) == false ) + { + IotLogError( "(Network connection %p) Failed to read credentials.", + pNetworkConnection ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Set up ALPN if requested. */ + if( pMbedtlsCredentials->pAlpnProtos != NULL ) + { + /* mbed TLS expects a NULL-terminated array of protocols. These pointers + * must remain in-scope for the lifetime of the connection, so they are + * stored as part of the connection context. */ + pNetworkConnection->ssl.pAlpnProtos[ 0 ] = pMbedtlsCredentials->pAlpnProtos; + pNetworkConnection->ssl.pAlpnProtos[ 1 ] = NULL; + + mbedtlsError = mbedtls_ssl_conf_alpn_protocols( &( pNetworkConnection->ssl.config ), + pNetworkConnection->ssl.pAlpnProtos ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set ALPN protocols." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + } + + /* Set TLS MFLN if requested. */ + if( pMbedtlsCredentials->maxFragmentLength > 0 ) + { + /* Check for a supported fragment length. mbed TLS only supports 4 values. */ + switch( pMbedtlsCredentials->maxFragmentLength ) + { + case 512UL: + mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_512; + break; + + case 1024UL: + mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_1024; + break; + + case 2048UL: + mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_2048; + break; + + case 4096UL: + mflCode = MBEDTLS_SSL_MAX_FRAG_LEN_4096; + break; + + default: + IotLogWarn( "Ignoring unsupported max fragment length %lu. Supported " + "values are 512, 1024, 2048, or 4096.", + pMbedtlsCredentials->maxFragmentLength ); + fragmentLengthValid = false; + break; + } + + /* Set MFLN if a valid fragment length is given. */ + if( fragmentLengthValid == true ) + { + mbedtlsError = mbedtls_ssl_conf_max_frag_len( &( pNetworkConnection->ssl.config ), + mflCode ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set TLS MFLN." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + } + } + + /* Initialize the mbed TLS secured connection context. */ + mbedtlsError = mbedtls_ssl_setup( &( pNetworkConnection->ssl.context ), + &( pNetworkConnection->ssl.config ) ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set up mbed TLS SSL context." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Set the underlying IO for the TLS connection. */ + mbedtls_ssl_set_bio( &( pNetworkConnection->ssl.context ), + &( pNetworkConnection->networkContext ), + mbedtls_net_send, + NULL, + mbedtls_net_recv_timeout ); + + /* Enable SNI if requested. */ + if( pMbedtlsCredentials->disableSni == false ) + { + mbedtlsError = mbedtls_ssl_set_hostname( &( pNetworkConnection->ssl.context ), + pServerName ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set server name." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + } + + /* Perform the TLS handshake. */ + do + { + mbedtlsError = mbedtls_ssl_handshake( &( pNetworkConnection->ssl.context ) ); + } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to perform SSL handshake." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Check result of certificate verification. */ + verifyResult = mbedtls_ssl_get_verify_result( &( pNetworkConnection->ssl.context ) ); + + if( verifyResult != 0 ) + { + IotLogError( "Failed to verify server certificate, result %lu.", + verifyResult ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Clean up on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_NETWORK_SUCCESS ) + { + if( sslContextInitialized == true ) + { + _sslContextFree( pNetworkConnection ); + } + } + else + { + /* TLS setup succeeded; set the secured flag. */ + pNetworkConnection->flags |= FLAG_SECURED; + + IotLogInfo( "(Network connection %p) TLS handshake successful.", + pNetworkConnection ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkMbedtls_Init( void ) +{ + IotNetworkError_t status = IOT_NETWORK_SUCCESS; + int mbedtlsError = 0; + + /* Clear the counter of receive threads. */ + _receiveThreadCount = 0; + + /* Set the mutex functions for mbed TLS thread safety. */ + mbedtls_threading_set_alt( mbedtlsMutex_Init, + mbedtlsMutex_Free, + mbedtlsMutex_Lock, + mbedtlsMutex_Unlock ); + + /* Initialize contexts for random number generation. */ + mbedtls_entropy_init( &_entropyContext ); + mbedtls_ctr_drbg_init( &_ctrDrbgContext ); + + /* Seed the random number generator. */ + mbedtlsError = mbedtls_ctr_drbg_seed( &_ctrDrbgContext, + mbedtls_entropy_func, + &_entropyContext, + NULL, + 0 ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, NULL, "Failed to seed PRNG in initialization." ); + status = IOT_NETWORK_FAILURE; + } + else + { + IotLogInfo( "Network library initialized." ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotNetworkMbedtls_Cleanup( void ) +{ + /* Atomically read the receive thread count by adding 0 to it. Sleep and + * wait for all receive threads to exit. */ + while( Atomic_Add_u32( &_receiveThreadCount, 0 ) > 0 ) + { + IotClock_SleepMs( IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); + } + + /* Free the contexts for random number generation. */ + mbedtls_ctr_drbg_free( &_ctrDrbgContext ); + mbedtls_entropy_free( &_entropyContext ); + + /* Clear the mutex functions for mbed TLS thread safety. */ + mbedtls_threading_free_alt(); + + IotLogInfo( "Network library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, + void * pCredentialInfo, + void ** pConnection ) +{ + IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + int mbedtlsError = 0; + _networkConnection_t * pNewNetworkConnection = NULL; + char pServerPort[ 6 ] = { 0 }; + + /* Flags to track initialization. */ + bool networkContextInitialized = false, networkMutexCreated = false; + + /* Cast function parameters to correct types. */ + const IotNetworkServerInfo_t * const pServerInfo = pConnectionInfo; + const IotNetworkCredentials_t * const pMbedtlsCredentials = pCredentialInfo; + _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t ** const ) pConnection; + + /* Allocate memory for a new connection. */ + pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); + + if( pNewNetworkConnection == NULL ) + { + IotLogError( "Failed to allocate memory for new network connection." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_NO_MEMORY ); + } + + /* Clear connection data. */ + memset( pNewNetworkConnection, 0x00, sizeof( _networkConnection_t ) ); + + /* Initialize the network context mutex. */ + networkMutexCreated = IotMutex_Create( &( pNewNetworkConnection->networkMutex ), + true ); + + /* Initialize mbed TLS network context. */ + mbedtls_net_init( &( pNewNetworkConnection->networkContext ) ); + networkContextInitialized = true; + + /* mbed TLS expects the port to be a decimal string. */ + mbedtlsError = snprintf( pServerPort, 6, "%hu", pServerInfo->port ); + + if( mbedtlsError < 0 ) + { + IotLogError( "Failed to convert port %hu to decimal string.", + pServerInfo->port ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Establish a TCP connection. */ + mbedtlsError = mbedtls_net_connect( &( pNewNetworkConnection->networkContext ), + pServerInfo->pHostName, + pServerPort, + MBEDTLS_NET_PROTO_TCP ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, NULL, "Failed to establish connection." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Set the mbed TLS network context to blocking mode. */ + mbedtlsError = mbedtls_net_set_block( &( pNewNetworkConnection->networkContext ) ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNewNetworkConnection, "Failed to set blocking mode." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Set up TLS if credentials are given. */ + if( pMbedtlsCredentials != NULL ) + { + status = _tlsSetup( pNewNetworkConnection, + pServerInfo->pHostName, + pMbedtlsCredentials ); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status != IOT_NETWORK_SUCCESS ) + { + if( networkMutexCreated == true ) + { + IotMutex_Destroy( &( pNewNetworkConnection->networkMutex ) ); + } + + if( networkContextInitialized == true ) + { + mbedtls_net_free( &( pNewNetworkConnection->networkContext ) ); + } + + if( pNewNetworkConnection != NULL ) + { + IotNetwork_Free( pNewNetworkConnection ); + } + } + else + { + IotLogInfo( "(Network connection %p) New network connection established.", + pNewNetworkConnection ); + + /* Set the output parameter. */ + *pNetworkConnection = pNewNetworkConnection; + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( void * pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ) +{ + IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pConnection; + + /* Flags to track initialization. */ + bool notifyInitialized = false, countIncremented = false; + + /* Initialize the semaphore that notifies the receive thread of connection + * destruction. */ + notifyInitialized = IotSemaphore_Create( &( pNetworkConnection->destroyNotification ), + 0, + 1 ); + + if( notifyInitialized == false ) + { + IotLogError( "(Network connection %p) Failed to create semaphore for " + "receive thread.", pNetworkConnection ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + } + + /* Set the callback and parameter. */ + pNetworkConnection->receiveCallback = receiveCallback; + pNetworkConnection->pReceiveContext = pContext; + + /* Set the receive callback flag and increment the count of receive threads. */ + pNetworkConnection->flags |= FLAG_HAS_RECEIVE_CALLBACK; + ( void ) Atomic_Increment_u32( &_receiveThreadCount ); + countIncremented = true; + + /* Create the thread to receive incoming data. */ + if( Iot_CreateDetachedThread( _receiveThread, + pNetworkConnection, + IOT_THREAD_DEFAULT_PRIORITY, + IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) + { + IotLogError( "(Network connection %p) Failed to create thread for receiving data.", + pNetworkConnection ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); + } + + /* Clean up on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != IOT_NETWORK_SUCCESS ) + { + if( notifyInitialized == true ) + { + IotSemaphore_Destroy( &( pNetworkConnection->destroyNotification ) ); + } + + if( countIncremented == true ) + { + pNetworkConnection->flags &= ~FLAG_HAS_RECEIVE_CALLBACK; + ( void ) Atomic_Decrement_u32( &_receiveThreadCount ); + } + } + else + { + IotLogDebug( "(Network connection %p) Receive callback set.", + pNetworkConnection ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +size_t IotNetworkMbedtls_Send( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ) +{ + int mbedtlsError = 0; + size_t bytesSent = 0; + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pConnection; + + IotLogDebug( "(Network connection %p) Sending %lu bytes.", + pNetworkConnection, + ( unsigned long ) messageLength ); + + IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + + /* Check that the connection is open before sending. */ + if( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ) + { + IotLogError( "(Network connection %p) Cannot send; connection has been marked closed.", + pNetworkConnection ); + } + else + { + /* Check that it's possible to send right now. */ + mbedtlsError = mbedtls_net_poll( &( pNetworkConnection->networkContext ), + MBEDTLS_NET_POLL_WRITE, + 0 ); + + if( mbedtlsError == MBEDTLS_NET_POLL_WRITE ) + { + /* Choose the send function based on state of the SSL context. */ + if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + { + /* Secured send. */ + while( bytesSent < messageLength ) + { + mbedtlsError = mbedtls_ssl_write( &( pNetworkConnection->ssl.context ), + pMessage + bytesSent, + messageLength - bytesSent ); + + if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) ) + { + /* Call SSL write again with the same arguments. */ + continue; + } + else if( mbedtlsError < 0 ) + { + /* Error sending, exit. */ + break; + } + else + { + bytesSent += ( size_t ) mbedtlsError; + } + } + } + else + { + /* Unsecured send. */ + mbedtlsError = mbedtls_net_send( &( pNetworkConnection->networkContext ), + pMessage, + messageLength ); + + if( mbedtlsError > 0 ) + { + bytesSent = ( size_t ) mbedtlsError; + } + } + + /* Log errors. */ + if( mbedtlsError < 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to send." ); + bytesSent = 0; + } + } + else + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Cannot send right now." ); + } + } + + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + + return bytesSent; +} + +/*-----------------------------------------------------------*/ + +size_t IotNetworkMbedtls_Receive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ) +{ + int mbedtlsError = 0; + size_t bytesReceived = 0; + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pConnection; + + IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + + /* Check that the connection is open before sending. */ + if( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ) + { + IotLogError( "(Network connection %p) Cannot receive; connection has been marked closed.", + pNetworkConnection ); + } + else + { + while( bytesReceived < bytesRequested ) + { + /* Choose the receive function based on state of the SSL context. */ + if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + { + mbedtlsError = mbedtls_ssl_read( &( pNetworkConnection->ssl.context ), + pBuffer + bytesReceived, + bytesRequested - bytesReceived ); + } + else + { + mbedtlsError = mbedtls_net_recv( &( pNetworkConnection->networkContext ), + pBuffer + bytesReceived, + bytesRequested - bytesReceived ); + } + + if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) ) + { + /* Call receive again with the same arguments. */ + continue; + } + else if( mbedtlsError < 0 ) + { + /* Error receiving, exit. */ + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to receive." ); + break; + } + else + { + bytesReceived += ( size_t ) mbedtlsError; + } + } + } + + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ) +{ + int mbedtlsError = 0; + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pConnection; + + IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + + /* Notify the server that the SSL connection is being closed. */ + if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + { + do + { + mbedtlsError = mbedtls_ssl_close_notify( &( pNetworkConnection->ssl.context ) ); + } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); + + if( mbedtlsError != 0 ) + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to notify peer of SSL connection close." ); + } + else + { + IotLogInfo( "(Network connection %p) TLS session terminated.", + pNetworkConnection ); + } + } + + /* Mark the connection as closed. The mbed TLS function to close a connection + * is not safe to call in a multithreaded system; therefore, the close is faked + * by setting a flag that prevents further use of the connection. The connection + * is actually closed by the destroy function. */ + pNetworkConnection->flags |= FLAG_CLOSED; + + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + + IotLogInfo( "(Network connection %p) Connection closed.", pNetworkConnection ); + + return IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ) +{ + bool destroyConnection = false; + + /* Cast function parameter to correct type. */ + _networkConnection_t * const pNetworkConnection = pConnection; + + IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + + /* Check if this connection has a receive callback. If it does not, it can + * be destroyed here. Otherwise, notify the receive callback that destroy + * has been called and rely on the receive callback to clean up. */ + destroyConnection = ( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == 0 ); + + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + + if( destroyConnection == true ) + { + _destroyConnection( pNetworkConnection ); + } + else + { + IotSemaphore_Post( &( pNetworkConnection->destroyNotification ) ); + } + + return IOT_NETWORK_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void IotNetworkMbedtls_GetServerInfo( void * pConnection, + IotMetricsTcpConnection_t * pServerInfo ) +{ +} + +/*-----------------------------------------------------------*/ diff --git a/platform/source/network/iot_network_metrics.c b/platform/source/network/iot_network_metrics.c index 1e20faae19..53832d66ea 100644 --- a/platform/source/network/iot_network_metrics.c +++ b/platform/source/network/iot_network_metrics.c @@ -99,38 +99,74 @@ static IotMutex_t _connectionListMutex; /*-----------------------------------------------------------*/ -/* OpenSSL networking include. */ -#include "posix/iot_network_openssl.h" - -/** - * @brief Pointer to the metrics-wrapped network creation function. - */ -static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkOpenssl_Create; - -/** - * @brief Pointer to the metrics-wrapped network close function. - */ -static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkOpenssl_Close; - -/** - * @brief Pointer to the function that retrieves the server info for a connection. - */ -static void ( * _networkServerInfo )( void *, - IotMetricsTcpConnection_t * ) = IotNetworkOpenssl_GetServerInfo; - -/** - * @brief An #IotNetworkInterface_t that wraps network abstration functions with - * metrics. - */ -const IotNetworkInterface_t IotNetworkMetrics = -{ - .create = _metricsNetworkCreate, - .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, - .send = IotNetworkOpenssl_Send, - .receive = IotNetworkOpenssl_Receive, - .close = _metricsNetworkClose, - .destroy = IotNetworkOpenssl_Destroy -}; +/* Choose the appropriate network abstraction implementation. */ +#if IOT_NETWORK_USE_OPENSSL == 1 + /* OpenSSL networking include. */ + #include "posix/iot_network_openssl.h" + + /** + * @brief Pointer to the metrics-wrapped network creation function. + */ + static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkOpenssl_Create; + + /** + * @brief Pointer to the metrics-wrapped network close function. + */ + static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkOpenssl_Close; + + /** + * @brief Pointer to the function that retrieves the server info for a connection. + */ + static void ( * _networkServerInfo )( void *, + IotMetricsTcpConnection_t * ) = IotNetworkOpenssl_GetServerInfo; + + /** + * @brief An #IotNetworkInterface_t that wraps network abstraction functions with + * metrics. + */ + const IotNetworkInterface_t IotNetworkMetrics = + { + .create = _metricsNetworkCreate, + .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .send = IotNetworkOpenssl_Send, + .receive = IotNetworkOpenssl_Receive, + .close = _metricsNetworkClose, + .destroy = IotNetworkOpenssl_Destroy + }; +#else + /* mbed TLS networking include. */ + #include "iot_network_mbedtls.h" + + /** + * @brief Pointer to the metrics-wrapped network creation function. + */ + static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkMbedtls_Create; + + /** + * @brief Pointer to the metrics-wrapped network close function. + */ + static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkMbedtls_Close; + + /** + * @brief Pointer to the function that retrieves the server info for a connection. + */ + static void ( * _networkServerInfo )( void *, + IotMetricsTcpConnection_t * ) = IotNetworkMbedtls_GetServerInfo; + + /** + * @brief An #IotNetworkInterface_t that wraps network abstraction functions with + * metrics. + */ + const IotNetworkInterface_t IotNetworkMetrics = + { + .create = _metricsNetworkCreate, + .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, + .send = IotNetworkMbedtls_Send, + .receive = IotNetworkMbedtls_Receive, + .close = _metricsNetworkClose, + .destroy = IotNetworkMbedtls_Destroy + }; +#endif /*-----------------------------------------------------------*/ diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index d4afd7d394..33bfee2538 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -49,25 +49,26 @@ foreach( POSIX_FUNCTION ${REQUIRED_POSIX_FUNCTIONS} ) endif() endforeach() -# Check for OpenSSL. -find_package( OpenSSL ) - -# Minimum supported OpenSSL version is 1.0.2g. -if( ${OPENSSL_FOUND} ) - if( ${OPENSSL_VERSION} STRLESS "1.0.2g" ) - message( FATAL_ERROR "OpenSSL 1.0.2g or later required, found ${OPENSSL_VERSION}." ) +# Choose either OpenSSL or mbed TLS. +if( ${IOT_NETWORK_USE_OPENSSL} ) + # Check for OpenSSL. + find_package( OpenSSL ) + + # Minimum supported OpenSSL version is 1.0.2g. + if( ${OPENSSL_FOUND} ) + if( ${OPENSSL_VERSION} STRLESS "1.0.2g" ) + message( FATAL_ERROR "OpenSSL 1.0.2g or later required, found ${OPENSSL_VERSION}." ) + endif() + + # Choose OpenSSL network source file. + set( NETWORK_SOURCE_FILE iot_network_openssl.c ) + + # Link OpenSSL. + set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) endif() - - # Choose OpenSSL network source file. - set( NETWORK_SOURCE_FILE iot_network_openssl.c ) - - # Link OpenSSL. - set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) -endif() - -# Ensure that a compatible TLS library was found. -if( NOT DEFINED TLS_LIBRARY_LINKER_FLAG ) - message( FATAL_ERROR "No compatible TLS library was found." ) +else() + set( NETWORK_SOURCE_FILE ../network/iot_network_mbedtls.c ) + set( TLS_LIBRARY_LINKER_FLAG mbedtls ) endif() # Platform libraries source files. @@ -77,8 +78,5 @@ add_library( iotplatform ../network/iot_network_metrics.c ${NETWORK_SOURCE_FILE} ) -# Library version. -set_target_properties( iotplatform PROPERTIES VERSION ${PROJECT_VERSION} ) - # Link required libraries. target_link_libraries( iotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) diff --git a/platform/source/posix/iot_network_openssl.c b/platform/source/posix/iot_network_openssl.c index 382488b3b6..a14d6af7de 100644 --- a/platform/source/posix/iot_network_openssl.c +++ b/platform/source/posix/iot_network_openssl.c @@ -177,8 +177,7 @@ static void * _networkReceiveThread( void * pArgument ) break; } - /* Invoke the callback function. But if there's no callback to invoke, - * terminate this thread. */ + /* Invoke the callback function. */ pNetworkConnection->receiveCallback( pNetworkConnection, pNetworkConnection->pReceiveContext ); } @@ -311,14 +310,14 @@ static int _dnsLookup( const IotNetworkServerInfo_t * pServerInfo ) * Uses OpenSSL to import the root CA certificate, client certificate, and * client certificate private key. * @param[in] pSslContext Destination for the imported credentials. - * @param[in] pRootCAPath Path to the root CA certificate. + * @param[in] pRootCaPath Path to the root CA certificate. * @param[in] pClientCertPath Path to the client certificate. * @param[in] pCertPrivateKeyPath Path to the client certificate private key. * * @return `true` if all credentials were successfully read; `false` otherwise. */ static bool _readCredentials( SSL_CTX * pSslContext, - const char * pRootCAPath, + const char * pRootCaPath, const char * pClientCertPath, const char * pCertPrivateKeyPath ) { @@ -327,12 +326,12 @@ static bool _readCredentials( SSL_CTX * pSslContext, /* OpenSSL does not provide a single function for reading and loading certificates * from files into stores, so the file API must be called. */ - IotLogDebug( "Opening root certificate %s", pRootCAPath ); - FILE * pRootCaFile = fopen( pRootCAPath, "r" ); + IotLogDebug( "Opening root certificate %s", pRootCaPath ); + FILE * pRootCaFile = fopen( pRootCaPath, "r" ); if( pRootCaFile == NULL ) { - IotLogError( "Failed to open %s", pRootCAPath ); + IotLogError( "Failed to open %s", pRootCaPath ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -342,7 +341,7 @@ static bool _readCredentials( SSL_CTX * pSslContext, if( fclose( pRootCaFile ) != 0 ) { - IotLogWarn( "Failed to close file %s", pRootCAPath ); + IotLogWarn( "Failed to close file %s", pRootCaPath ); } if( pRootCa == NULL ) @@ -510,6 +509,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, #endif } + /* Enable SNI if requested. */ if( pOpensslCredentials->disableSni == false ) { IotLogDebug( "Setting server name %s for SNI.", pServerName ); @@ -757,29 +757,25 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - /* Create a new receive thread if a callback is given. */ - if( receiveCallback != NULL ) - { - /* Update the callback and parameter. */ - pNetworkConnection->receiveCallback = receiveCallback; - pNetworkConnection->pReceiveContext = pContext; + /* Set the callback and parameter. */ + pNetworkConnection->receiveCallback = receiveCallback; + pNetworkConnection->pReceiveContext = pContext; - posixError = pthread_create( &pNetworkConnection->receiveThread, - NULL, - _networkReceiveThread, - pNetworkConnection ); + posixError = pthread_create( &pNetworkConnection->receiveThread, + NULL, + _networkReceiveThread, + pNetworkConnection ); - if( posixError != 0 ) - { - IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pNetworkConnection->socket, - posixError ); - status = IOT_NETWORK_SYSTEM_ERROR; - } - else - { - pNetworkConnection->receiveThreadCreated = true; - } + if( posixError != 0 ) + { + IotLogError( "Failed to create socket %d network receive thread. errno=%d.", + pNetworkConnection->socket, + posixError ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pNetworkConnection->receiveThreadCreated = true; } return status; diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index 21d2fdb768..7844dd33cf 100644 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -28,14 +28,14 @@ else fi # Build executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" make -j2 iot_tests_mqtt iot_demo_mqtt # Run tests and demos. run_tests_and_demos # Rebuild in static memory mode. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" make -j2 iot_tests_mqtt # Run tests in static memory mode. diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index 985a172f25..07ad924f39 100644 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -22,14 +22,14 @@ run_tests_and_demos() { CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS=1 $COMPILER_OPTIONS" # Build executables. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" make -j2 aws_iot_tests_shadow aws_iot_demo_shadow # Run tests and demos. run_tests_and_demos # Rebuild in static memory mode. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" make -j2 aws_iot_tests_shadow aws_iot_demo_shadow # Run tests and demos in static memory mode. diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index b35a7a4195..9c794c49b8 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -28,17 +28,8 @@ /* POSIX includes. */ #include -/* Network includes. */ -#include "posix/iot_network_openssl.h" - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Metrics include. */ -#include "platform/iot_metrics.h" - -/* MQTT include. */ -#include "iot_mqtt.h" +/* Error handling include. */ +#include "private/iot_error.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -54,12 +45,12 @@ static void _signalHandler( int signum ) if( signum == SIGSEGV ) { printf( "\nSegmentation fault.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } else if( signum == SIGABRT ) { printf( "\nAssertion failed.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } } @@ -68,11 +59,12 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Silence warnings about unused parameters. */ - ( void )argc; - ( void )argv; + ( void ) argc; + ( void ) argv; /* Set a signal handler for segmentation faults and assertion failures. */ ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); @@ -80,35 +72,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; - } - - /* Initialize the SDK before running the tests. */ - if( IotSdk_Init() == false ) - { - return EXIT_FAILURE; - } - - if (IotMetrics_Init() == false) - { - return EXIT_FAILURE; - } - - /* Set up the network stack. */ - if ( IotNetworkOpenssl_Init() != IOT_NETWORK_SUCCESS ) - { - return EXIT_FAILURE; - } - - /* Initialize the MQTT library before running the tests. */ - if ( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - return EXIT_FAILURE; + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -118,25 +87,16 @@ int main( int argc, UnityFixture.GroupFilter = NULL; UNITY_BEGIN(); - /* Run short tests. */ - RUN_TEST_GROUP(Full_DEFENDER); - - /* Clean up SDK. */ - IotSdk_Cleanup(); - - /* Clean up the network stack. */ - IotNetworkOpenssl_Cleanup(); - - /* Clean up MQTT library. */ - IotMqtt_Cleanup(); + /* Run Defender tests. */ + RUN_TEST_GROUP( Defender_System ); /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - return EXIT_FAILURE; + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - return EXIT_SUCCESS; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/aws_iot_tests_defender_api.c index 7092b40511..0a6b7aa625 100644 --- a/tests/defender/aws_iot_tests_defender_api.c +++ b/tests/defender/aws_iot_tests_defender_api.c @@ -35,11 +35,13 @@ /* Defender internal includes. */ #include "private/aws_iot_defender_internal.h" -/* Openssl includes. */ -#include "posix/iot_network_openssl.h" - #include "iot_network_metrics.h" +#include "iot_init.h" + +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER + /* Serializer includes. */ #include "iot_serializer.h" @@ -131,10 +133,30 @@ static void _resetCalbackInfo( void ); static char * _getIotAddress( void ); -TEST_GROUP( Full_DEFENDER ); +TEST_GROUP( Defender_System ); -TEST_SETUP( Full_DEFENDER ) +TEST_SETUP( Defender_System ) { + if( IotSdk_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); + } + + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize network stack." ); + } + + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT." ); + } + + if( IotMetrics_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize metrics." ); + } + /* Create a binary semaphore with initial value 0. */ if( !IotSemaphore_Create( &_callbackInfoSem, 0, 1 ) ) { @@ -176,7 +198,7 @@ TEST_SETUP( Full_DEFENDER ) _startInfo.callback = _EMPTY_CALLBACK; } -TEST_TEAR_DOWN( Full_DEFENDER ) +TEST_TEAR_DOWN( Defender_System ) { AwsIotDefender_Stop(); @@ -188,16 +210,21 @@ TEST_TEAR_DOWN( Full_DEFENDER ) } IotSemaphore_Destroy( &_callbackInfoSem ); + + IotMetrics_Cleanup(); + IotMqtt_Cleanup(); + IotTestNetwork_Cleanup(); + IotSdk_Cleanup(); } -TEST_GROUP_RUNNER( Full_DEFENDER ) +TEST_GROUP_RUNNER( Defender_System ) { /* * Setup: none * Action: call Start API with invliad IoT endpoint * Expectation: Start API returns network connection failure */ - RUN_TEST_CASE( Full_DEFENDER, Start_with_wrong_network_information ); + RUN_TEST_CASE( Defender_System, Start_with_wrong_network_information ); /* * Setup: defender not started yet @@ -206,7 +233,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - SetMetrics API return invalid input * - global metrics flag array are untouched */ - RUN_TEST_CASE( Full_DEFENDER, SetMetrics_with_invalid_metrics_group ); + RUN_TEST_CASE( Defender_System, SetMetrics_with_invalid_metrics_group ); /* * Setup: defender not started yet @@ -215,7 +242,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - SetMetrics API return success * - global metrics flag array are updated correctly */ - RUN_TEST_CASE( Full_DEFENDER, SetMetrics_with_TCP_connections_all ); + RUN_TEST_CASE( Defender_System, SetMetrics_with_TCP_connections_all ); /* * Setup: defender is started @@ -224,7 +251,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - SetMetrics API return success * - global metrics flag array are updated correctly */ - RUN_TEST_CASE( Full_DEFENDER, SetMetrics_after_defender_started ); + RUN_TEST_CASE( Defender_System, SetMetrics_after_defender_started ); /* * Setup: defender not started yet @@ -232,7 +259,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * Expectation: * - SetPeriod API return "period too short" error */ - /*RUN_TEST_CASE( Full_DEFENDER, SetPeriod_too_short ); */ + /*RUN_TEST_CASE( Defender_System, SetPeriod_too_short ); */ /* * Setup: defender not started yet @@ -240,7 +267,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * Expectation: * - SetPeriod API return success */ - RUN_TEST_CASE( Full_DEFENDER, SetPeriod_with_proper_value ); + RUN_TEST_CASE( Defender_System, SetPeriod_with_proper_value ); /* * Setup: defender is started @@ -248,28 +275,28 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * Expectation: * - SetPeriod API return success */ - RUN_TEST_CASE( Full_DEFENDER, SetPeriod_after_started ); + RUN_TEST_CASE( Defender_System, SetPeriod_after_started ); /* * Setup: kept from publishing metrics report * Action: call Start API with correct network information * Expectation: Start API return success */ - RUN_TEST_CASE( Full_DEFENDER, Start_should_return_success ); + RUN_TEST_CASE( Defender_System, Start_should_return_success ); /* * Setup: call Start API the first time; kept from publishing metrics report * Action: call Start API second time * Expectation: Start API return "already started" error */ - RUN_TEST_CASE( Full_DEFENDER, Start_should_return_err_if_already_started ); + RUN_TEST_CASE( Defender_System, Start_should_return_err_if_already_started ); /* * Setup: not set any metrics; register test callback * Action: call Start API * Expectation: metrics are accepted by defender service */ - RUN_TEST_CASE( Full_DEFENDER, Metrics_empty_are_published ); + RUN_TEST_CASE( Defender_System, Metrics_empty_are_published ); /* * Setup: set "tcp connections" with "all metrics"; register test callback @@ -278,7 +305,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - metrics are accepted by defender service * - verify metrics report has correct content */ - RUN_TEST_CASE( Full_DEFENDER, Metrics_TCP_connections_all_are_published ); + RUN_TEST_CASE( Defender_System, Metrics_TCP_connections_all_are_published ); /* * Setup: set "tcp connections" with "total count"; register test callback @@ -287,7 +314,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - metrics are accepted by defender service * - verify metrics report has correct content */ - RUN_TEST_CASE( Full_DEFENDER, Metrics_TCP_connections_total_are_published ); + RUN_TEST_CASE( Defender_System, Metrics_TCP_connections_total_are_published ); /* * Setup: set "tcp connections" with "remote address"; register test callback @@ -296,7 +323,7 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - metrics are accepted by defender service * - verify metrics report has correct content */ - RUN_TEST_CASE( Full_DEFENDER, Metrics_TCP_connections_remote_addr_are_published ); + RUN_TEST_CASE( Defender_System, Metrics_TCP_connections_remote_addr_are_published ); /* * Setup: set "tcp connections" with "total count"; register test callback; call Start API @@ -305,10 +332,10 @@ TEST_GROUP_RUNNER( Full_DEFENDER ) * - metrics are accepted by defender service in both times * - verify metrics report has correct content respectively in both times */ - RUN_TEST_CASE( Full_DEFENDER, Restart_and_updated_metrics_are_published ); + RUN_TEST_CASE( Defender_System, Restart_and_updated_metrics_are_published ); } -TEST( Full_DEFENDER, SetMetrics_with_invalid_metrics_group ) +TEST( Defender_System, SetMetrics_with_invalid_metrics_group ) { uint8_t i = 0; @@ -326,7 +353,7 @@ TEST( Full_DEFENDER, SetMetrics_with_invalid_metrics_group ) } } -TEST( Full_DEFENDER, SetMetrics_with_TCP_connections_all ) +TEST( Defender_System, SetMetrics_with_TCP_connections_all ) { /* Set "all metrics" for TCP connections metrics group. */ AwsIotDefenderError_t error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, @@ -337,7 +364,7 @@ TEST( Full_DEFENDER, SetMetrics_with_TCP_connections_all ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); } -TEST( Full_DEFENDER, SetMetrics_after_defender_started ) +TEST( Defender_System, SetMetrics_after_defender_started ) { _publishMetricsNotNeeded(); @@ -354,7 +381,7 @@ TEST( Full_DEFENDER, SetMetrics_after_defender_started ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); } -TEST( Full_DEFENDER, Start_with_wrong_network_information ) +TEST( Defender_System, Start_with_wrong_network_information ) { _publishMetricsNotNeeded(); @@ -368,7 +395,7 @@ TEST( Full_DEFENDER, Start_with_wrong_network_information ) _assertEvent( AWS_IOT_DEFENDER_FAILURE_MQTT, WAIT_STATE_TOTAL_SECONDS ); } -TEST( Full_DEFENDER, Start_should_return_success ) +TEST( Defender_System, Start_should_return_success ) { _publishMetricsNotNeeded(); @@ -377,7 +404,7 @@ TEST( Full_DEFENDER, Start_should_return_success ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); } -TEST( Full_DEFENDER, Start_should_return_err_if_already_started ) +TEST( Defender_System, Start_should_return_err_if_already_started ) { _publishMetricsNotNeeded(); @@ -391,7 +418,7 @@ TEST( Full_DEFENDER, Start_should_return_err_if_already_started ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_ALREADY_STARTED, error ); } -TEST( Full_DEFENDER, Metrics_empty_are_published ) +TEST( Defender_System, Metrics_empty_are_published ) { AwsIotDefenderError_t error; @@ -409,7 +436,7 @@ TEST( Full_DEFENDER, Metrics_empty_are_published ) _verifyTcpConections( 0 ); } -TEST( Full_DEFENDER, Metrics_TCP_connections_all_are_published ) +TEST( Defender_System, Metrics_TCP_connections_all_are_published ) { AwsIotDefenderError_t error; @@ -437,7 +464,7 @@ TEST( Full_DEFENDER, Metrics_TCP_connections_all_are_published ) _verifyTcpConections( 1, pIotAddress ); } -TEST( Full_DEFENDER, Metrics_TCP_connections_total_are_published ) +TEST( Defender_System, Metrics_TCP_connections_total_are_published ) { AwsIotDefenderError_t error; @@ -462,7 +489,7 @@ TEST( Full_DEFENDER, Metrics_TCP_connections_total_are_published ) _verifyTcpConections( 1 ); } -TEST( Full_DEFENDER, Metrics_TCP_connections_remote_addr_are_published ) +TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) { AwsIotDefenderError_t error; @@ -490,7 +517,7 @@ TEST( Full_DEFENDER, Metrics_TCP_connections_remote_addr_are_published ) _verifyTcpConections( 1, pIotAddress ); } -TEST( Full_DEFENDER, Restart_and_updated_metrics_are_published ) +TEST( Defender_System, Restart_and_updated_metrics_are_published ) { char * pIotAddress = NULL; @@ -534,19 +561,19 @@ TEST( Full_DEFENDER, Restart_and_updated_metrics_are_published ) _verifyTcpConections( 1, pIotAddress ); } -TEST( Full_DEFENDER, SetPeriod_too_short ) +TEST( Defender_System, SetPeriod_too_short ) { TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_PERIOD_TOO_SHORT, AwsIotDefender_SetPeriod( 299 ) ); } -TEST( Full_DEFENDER, SetPeriod_with_proper_value ) +TEST( Defender_System, SetPeriod_with_proper_value ) { TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetPeriod( 301 ) ); TEST_ASSERT_EQUAL( 301, AwsIotDefender_GetPeriod() ); } -TEST( Full_DEFENDER, SetPeriod_after_started ) +TEST( Defender_System, SetPeriod_after_started ) { _publishMetricsNotNeeded(); diff --git a/tests/iot_config.h b/tests/iot_config.h index 4ebeaa5c47..319d62912b 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -25,6 +25,10 @@ #ifndef IOT_CONFIG_H_ #define IOT_CONFIG_H_ +/* The build system will choose the appropriate system types file for the platform + * layer based on the host operating system. */ +#include IOT_SYSTEM_TYPES_FILE + /* Test framework include. */ #include "unity_fixture_malloc_overrides.h" @@ -134,39 +138,51 @@ #define AwsIotShadow_FreeSubscription unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ -/* Network header to include in the tests. */ -#define IOT_TEST_NETWORK_HEADER "posix/iot_network_openssl.h" - /* Network types to use in the tests. These are forward declarations. */ typedef struct _networkConnection IotTestNetworkConnection_t; typedef struct IotNetworkServerInfo IotTestNetworkServerInfo_t; typedef struct IotNetworkCredentials IotTestNetworkCredentials_t; +/* Choose the appropriate network abstraction implementation. */ +#if IOT_NETWORK_USE_OPENSSL == 1 + /* POSIX+OpenSSL network include. */ + #define IOT_TEST_NETWORK_HEADER "posix/iot_network_openssl.h" + + #define IOT_TEST_ALPN_PROTOS "\x0ex-amzn-mqtt-ca" + #define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL + + #define IotTestNetwork_Init IotNetworkOpenssl_Init + #define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup +#else + /* mbed TLS network include. */ + #define IOT_TEST_NETWORK_HEADER "iot_network_mbedtls.h" + + #define IOT_TEST_ALPN_PROTOS "x-amzn-mqtt-ca" + #define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS + + #define IotTestNetwork_Init IotNetworkMbedtls_Init + #define IotTestNetwork_Cleanup IotNetworkMbedtls_Cleanup +#endif + /* Initializers for the tests' network types. */ #define IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER \ { \ .pHostName = IOT_TEST_SERVER, \ .port = IOT_TEST_PORT \ } + #if IOT_TEST_SECURED_CONNECTION == 1 #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER \ { \ - .pAlpnProtos = "\x0ex-amzn-mqtt-ca", \ + .pAlpnProtos = IOT_TEST_ALPN_PROTOS, \ .pRootCa = IOT_TEST_ROOT_CA, \ .pClientCert = IOT_TEST_CLIENT_CERT, \ .pPrivateKey = IOT_TEST_PRIVATE_KEY \ } #else - #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER + #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER { 0 } #endif -/* Network interface to use in the tests. */ -#define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL - -/* Network initialization and cleanup functions to use in the tests. */ -#define IotTestNetwork_Init IotNetworkOpenssl_Init -#define IotTestNetwork_Cleanup IotNetworkOpenssl_Cleanup - /* Macro for placing inline assembly in test code. */ #define IOT_TEST_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) @@ -228,8 +244,4 @@ typedef struct IotNetworkCredentials IotTestNetworkCredentials_t; } #endif /* if IOT_TEST_COVERAGE == 1 */ -/* The build system will choose the appropriate system types file for the platform - * layer based on the host operating system. */ -#include IOT_SYSTEM_TYPES_FILE - #endif /* ifndef IOT_CONFIG_H_ */ diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt new file mode 100644 index 0000000000..654893b4c6 --- /dev/null +++ b/third_party/mbedtls/CMakeLists.txt @@ -0,0 +1,71 @@ +# Check if the mbed TLS source directory exists. +if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/library/ ) + # Attempt to clone the mbed TLS submodule. + if( ${IOT_BUILD_CLONE_SUBMODULES} ) + find_package( Git REQUIRED ) + + message( "Cloning submodule mbed TLS." ) + execute_process( COMMAND ${GIT_EXECUTABLE} submodule update --init third_party/mbedtls/mbedtls + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE MBEDTLS_CLONE_RESULT ) + + if( NOT ${MBEDTLS_CLONE_RESULT} STREQUAL "0" ) + message( FATAL_ERROR "Failed to clone mbed TLS submodule." ) + endif() + else() + message( FATAL_ERROR "The required submodule mbed TLS does not exist. Either clone it manually, or set IOT_BUILD_CLONE_SUBMODULES to 1 to automatically clone it during build." ) + endif() +endif() + +# mbed TLS source files. +add_library( mbedtls + iot_mbedtls_threading.c + mbedtls/library/aes.c + mbedtls/library/aesni.c + mbedtls/library/asn1parse.c + mbedtls/library/asn1write.c + mbedtls/library/base64.c + mbedtls/library/bignum.c + mbedtls/library/ecdh.c + mbedtls/library/ecdsa.c + mbedtls/library/ecp.c + mbedtls/library/ecp_curves.c + mbedtls/library/entropy.c + mbedtls/library/entropy_poll.c + mbedtls/library/error.c + mbedtls/library/cipher.c + mbedtls/library/cipher_wrap.c + mbedtls/library/ctr_drbg.c + mbedtls/library/hmac_drbg.c + mbedtls/library/md.c + mbedtls/library/md_wrap.c + mbedtls/library/net_sockets.c + mbedtls/library/oid.c + mbedtls/library/pem.c + mbedtls/library/pk.c + mbedtls/library/pkparse.c + mbedtls/library/pk_wrap.c + mbedtls/library/platform_util.c + mbedtls/library/rsa.c + mbedtls/library/rsa_internal.c + mbedtls/library/sha256.c + mbedtls/library/sha512.c + mbedtls/library/ssl_ciphersuites.c + mbedtls/library/ssl_cli.c + mbedtls/library/ssl_tls.c + mbedtls/library/timing.c + mbedtls/library/threading.c + mbedtls/library/x509.c + mbedtls/library/x509_crt.c ) + +# mbed TLS config header. +target_include_directories( mbedtls + PUBLIC ${PROJECT_SOURCE_DIR}/third_party/mbedtls ) +target_compile_definitions( mbedtls + PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) + +# Disable all warnings for this third-party library. +target_compile_options( mbedtls + PRIVATE + $<$,$,$>: + -w> ) diff --git a/third_party/mbedtls/iot_config_mbedtls.h b/third_party/mbedtls/iot_config_mbedtls.h new file mode 100644 index 0000000000..e2e6987429 --- /dev/null +++ b/third_party/mbedtls/iot_config_mbedtls.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* This file configures the mbed TLS library built with this SDK. */ + +#ifndef IOT_CONFIG_MBEDTLS_H_ +#define IOT_CONFIG_MBEDTLS_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* System support for assembly and time. */ +#define MBEDTLS_HAVE_ASM +#define MBEDTLS_HAVE_TIME +#define MBEDTLS_HAVE_TIME_DATE + +/* Remove deprecated functions to prevent their use. */ +#define MBEDTLS_DEPRECATED_REMOVED + +/* Enabled block cipher modes of operation. */ +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_CIPHER_MODE_CFB +#define MBEDTLS_CIPHER_MODE_CTR +#define MBEDTLS_CIPHER_MODE_OFB +#define MBEDTLS_CIPHER_MODE_XTS + +/* Enabled block cipher padding modes. */ +#define MBEDTLS_CIPHER_PADDING_ONE_AND_ZEROS +#define MBEDTLS_CIPHER_PADDING_ZEROS_AND_LEN +#define MBEDTLS_CIPHER_PADDING_ZEROS + +/* Disable weak cipher suites. */ +#define MBEDTLS_REMOVE_ARC4_CIPHERSUITES +#define MBEDTLS_REMOVE_3DES_CIPHERSUITES + +/* Enabled eliptic curves. */ +#define MBEDTLS_ECP_DP_SECP192R1_ENABLED +#define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP192K1_ENABLED +#define MBEDTLS_ECP_DP_SECP224K1_ENABLED +#define MBEDTLS_ECP_DP_SECP256K1_ENABLED +#define MBEDTLS_ECP_DP_BP256R1_ENABLED +#define MBEDTLS_ECP_DP_BP384R1_ENABLED +#define MBEDTLS_ECP_DP_BP512R1_ENABLED +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_ECP_DP_CURVE448_ENABLED + +/* Enable NIST curves optimization and deterministic ECDSA. */ +#define MBEDTLS_ECP_NIST_OPTIM +#define MBEDTLS_ECDSA_DETERMINISTIC + +/* Enable TLS cipher suites supported by AWS IoT. */ +#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED + +/* Enable use of the filesystem. */ +#define MBEDTLS_FS_IO + +/* Enable Encrypt-then-MAC and Extended Master Secret. */ +#define MBEDTLS_SSL_ENCRYPT_THEN_MAC +#define MBEDTLS_SSL_EXTENDED_MASTER_SECRET + +/* Enable hardware acceleration in the SSL module. */ +#define MBEDTLS_SSL_HW_RECORD_ACCEL + +/* Enable SSL max fragment length, ALPN, and SNI. */ +#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +#define MBEDTLS_SSL_ALPN +#define MBEDTLS_SSL_SERVER_NAME_INDICATION + +/* Enable TLS v1.2 only. */ +#define MBEDTLS_SSL_PROTO_TLS1_2 + +/* Enable verification of key usage and extended key usage. */ +#define MBEDTLS_X509_CHECK_KEY_USAGE +#define MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE + +/* Use x86 AES-NI instructions. */ +#define MBEDTLS_AESNI_C + +/* Enabled mbed TLS modules. */ +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_AES_C +#define MBEDTLS_BASE64_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECP_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_ERROR_C +#define MBEDTLS_HMAC_DRBG_C +#define MBEDTLS_MD_C +#define MBEDTLS_NET_C +#define MBEDTLS_OID_C +#define MBEDTLS_PEM_PARSE_C +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_PKCS1_V15 +#define MBEDTLS_RSA_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA512_C +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_TLS_C +#define MBEDTLS_TIMING_C +#define MBEDTLS_X509_USE_C +#define MBEDTLS_X509_CRT_PARSE_C + +/* Use platform mutexes in mbed TLS. */ +#define MBEDTLS_THREADING_C +#define MBEDTLS_THREADING_ALT + +/* Validate mbed TLS configuration. */ +#include "mbedtls/check_config.h" + +#endif diff --git a/third_party/mbedtls/iot_mbedtls_threading.c b/third_party/mbedtls/iot_mbedtls_threading.c new file mode 100644 index 0000000000..4d36b108a8 --- /dev/null +++ b/third_party/mbedtls/iot_mbedtls_threading.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* This file implements mutex functions for mbed TLS using platform mutexes. */ + +/* mbed TLS threading include. This includes the "threading_alt.h" header. */ +#include + +/*-----------------------------------------------------------*/ + +void mbedtlsMutex_Init( mbedtls_threading_mutex_t * pMutex ) +{ + pMutex->valid = IotMutex_Create( &( pMutex->mutex ), false ); +} + +/*-----------------------------------------------------------*/ + +void mbedtlsMutex_Free( mbedtls_threading_mutex_t * pMutex ) +{ + if( pMutex->valid == true ) + { + IotMutex_Destroy( &( pMutex->mutex ) ); + } +} + +/*-----------------------------------------------------------*/ + +int mbedtlsMutex_Lock( mbedtls_threading_mutex_t * pMutex ) +{ + int status = 0; + + if( pMutex->valid == false ) + { + status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + else + { + IotMutex_Lock( &( pMutex->mutex ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +int mbedtlsMutex_Unlock( mbedtls_threading_mutex_t * pMutex ) +{ + int status = 0; + + if( pMutex->valid == false ) + { + status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + else + { + IotMutex_Unlock( &( pMutex->mutex ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/third_party/mbedtls/mbedtls b/third_party/mbedtls/mbedtls new file mode 160000 index 0000000000..535ee4a35b --- /dev/null +++ b/third_party/mbedtls/mbedtls @@ -0,0 +1 @@ +Subproject commit 535ee4a35b9c4ddd059451b8fa5b201bfc89fbcf diff --git a/third_party/mbedtls/threading_alt.h b/third_party/mbedtls/threading_alt.h new file mode 100644 index 0000000000..c21e639979 --- /dev/null +++ b/third_party/mbedtls/threading_alt.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* This file declares mutex functions for mbed TLS using platform mutexes. */ + +#ifndef THREADING_ALT_H_ +#define THREADING_ALT_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Represents a mutex used by mbed TLS. */ +typedef struct mbedtls_threading_mutex +{ + /* Whether this mutex is valid. */ + bool valid; + /* The wrapped platform mutex. */ + IotMutex_t mutex; +} mbedtls_threading_mutex_t; + +/* Initializes a new mutex. Sets the valid member of mbedtls_threading_mutex_t. */ +void mbedtlsMutex_Init( mbedtls_threading_mutex_t * pMutex ); + +/* Frees a mutex. */ +void mbedtlsMutex_Free( mbedtls_threading_mutex_t * pMutex ); + +/* Locks a mutex. Returns 0 on success; one of MBEDTLS_ERR_THREADING_BAD_INPUT_DATA + * or MBEDTLS_ERR_THREADING_MUTEX_ERROR on error. */ +int mbedtlsMutex_Lock( mbedtls_threading_mutex_t * pMutex ); + +/* Unlocks a mutex. Returns 0 on success; one of MBEDTLS_ERR_THREADING_BAD_INPUT_DATA + * or MBEDTLS_ERR_THREADING_MUTEX_ERROR on error. */ +int mbedtlsMutex_Unlock( mbedtls_threading_mutex_t * pMutex ); + +#endif /* ifndef THREADING_ALT_H_ */ diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h index 2a2b942e3f..0b5d833fb9 100644 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h @@ -34,15 +34,10 @@ extern void UNITY_FIXTURE_FREE(void* ptr); #endif -#define malloc unity_malloc -#define calloc unity_calloc -#define realloc unity_realloc -#define free unity_free - -void* unity_malloc(size_t size); -void* unity_calloc(size_t num, size_t size); -void* unity_realloc(void * oldMem, size_t size); -void unity_free(void * mem); +#define malloc unity_malloc_mt +#define calloc unity_calloc_mt +#define realloc unity_realloc_mt +#define free unity_free_mt /* Thread-safety wrappers for the unity memory functions. */ extern pthread_mutex_t CriticalSectionMutex; diff --git a/third_party/unity/unity/fixture/unity_memory_mt.c b/third_party/unity/unity/fixture/unity_memory_mt.c index 319bdd1a36..e8a9681d76 100644 --- a/third_party/unity/unity/fixture/unity_memory_mt.c +++ b/third_party/unity/unity/fixture/unity_memory_mt.c @@ -6,6 +6,11 @@ pthread_mutex_t CriticalSectionMutex = PTHREAD_MUTEX_INITIALIZER; +extern void* unity_malloc(size_t size); +extern void* unity_calloc(size_t num, size_t size); +extern void* unity_realloc(void* oldMem, size_t size); +extern void unity_free_mt(void* mem); + void* unity_malloc_mt(size_t size) { void* mem = NULL; From 36231d19574cd8bf47ce830ce7eeb3bb0ec25b15 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 8 May 2019 10:37:32 -0700 Subject: [PATCH 126/844] Move third-party includes to their CMakeLists (#409) --- CMakeLists.txt | 10 ---------- platform/source/posix/CMakeLists.txt | 4 ++++ third_party/mbedtls/CMakeLists.txt | 12 +++++++++--- third_party/tinycbor/CMakeLists.txt | 4 ++++ third_party/unity/CMakeLists.txt | 5 +++++ 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b53f545586..f137f1622d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,12 +42,6 @@ set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) include_directories( ${PROJECT_SOURCE_DIR}/lib/include ${PROJECT_SOURCE_DIR}/platform/include ) -# TinyCBOR include path. -include_directories( SYSTEM ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) - -# mbed TLS include path. -include_directories( SYSTEM ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/include ) - # Demo include path. Always required because the tests also build the demos. include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) @@ -60,10 +54,6 @@ if( ${IOT_BUILD_TESTS} ) include_directories( ${PROJECT_SOURCE_DIR}/tests tests/mqtt/access ) - # Unity test framework include paths. - include_directories( SYSTEM third_party/unity/unity - third_party/unity/unity/fixture ) - # Build unity test framework. add_subdirectory( third_party/unity ) else() diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 33bfee2538..8c7696cadf 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -80,3 +80,7 @@ add_library( iotplatform # Link required libraries. target_link_libraries( iotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotplatform unity ) +endif() diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index 654893b4c6..a83be2b72c 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -58,12 +58,18 @@ add_library( mbedtls mbedtls/library/x509.c mbedtls/library/x509_crt.c ) -# mbed TLS config header. -target_include_directories( mbedtls - PUBLIC ${PROJECT_SOURCE_DIR}/third_party/mbedtls ) +# mbed TLS config header and include directories. +target_include_directories( mbedtls SYSTEM + PUBLIC ${PROJECT_SOURCE_DIR}/third_party/mbedtls + PUBLIC ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/include ) target_compile_definitions( mbedtls PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) +# Link the unity test framework when testing. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( mbedtls unity ) +endif() + # Disable all warnings for this third-party library. target_compile_options( mbedtls PRIVATE diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 0b96cdf53f..5f92c37984 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -27,6 +27,10 @@ add_library( tinycbor tinycbor/src/cborpretty.c tinycbor/src/cborpretty_stdio.c ) +# TinyCBOR include path. +target_include_directories( tinycbor SYSTEM + PUBLIC ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) + # Link math library. target_link_libraries( tinycbor m ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index cbcf255f6c..94b7b3324e 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -4,6 +4,11 @@ add_library( unity unity/fixture/unity_fixture.c unity/fixture/unity_memory_mt.c ) +# Unity test framework include paths. +target_include_directories( unity SYSTEM + PUBLIC ${PROJECT_SOURCE_DIR}/third_party/unity/unity + PUBLIC ${PROJECT_SOURCE_DIR}/third_party/unity/unity/fixture ) + # Disable all warnings for this third-party library. target_compile_options( unity PRIVATE From e92331c2a5075b852bd47a57e36988e1f9ba89f2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 8 May 2019 11:47:35 -0700 Subject: [PATCH 127/844] Update main README (#410) --- README.md | 53 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 221fcf2501..1dcb5d2e58 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWS IoT Device SDK C v4.0.0 Beta 1 +# AWS IoT Device SDK C v4.0.0 Beta **[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/index.html)** @@ -9,56 +9,71 @@ The AWS IoT Device SDK for C is a collection of C99 source files that allow appl This library, currently a Beta release, will supersede both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. +## Beta Features + +This Beta library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. In addition, it provides the following new features: +- Asynchronous API for both MQTT and Thing Shadow. +- Multithreaded API by default (removed the `yield` function). +- More efficient platform layer (especially timers). +- Complete separation of MQTT and network stack, allowing MQTT to run over any network stack. +- Configurable memory allocation (static-only or dynamic). Memory allocation functions may also be set by the user. +- Network stack based on OpenSSL. +- MQTT persistent session support. + +Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: +- Auto-reconnect for MQTT connections. +- Shadow JSON document generator. +- Jobs API. +- Build support for Apple macOS. + ## Building and Running Demos +**Main documentation page:** [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html) + This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. **As of now, this Beta release only builds on Linux.** ### Prerequisites - Linux system with support for POSIX threads and timers. - CMake 3.5.0 or later. + +If using the OpenSSL network implementation: - OpenSSL development libraries and header files, version 1.0.2g or later. This is usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. ### Build Steps -1. Complete the first 6 steps in the guide [Getting Started with AWS IoT](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html). The guide mentions the AWS IoT Button, but you do not need one to use this SDK. +1. Clone the source code and submodules. This SDK uses third-party libraries as submodules in the `third_party` directory. + - If the source code is downloaded via `git clone`, nothing further needs to be done. The CMake build system can automatically clone submodules in this case. + - For any other download, the submodules must be downloaded and placed in their respective `third_party` directory. + - [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17) → `third_party/mbedtls/mbedtls` + - [tinyCBOR](https://github.com/intel/tinycbor) → `third_party/tinycbor/tinycbor` +2. Complete the first 6 steps in the guide [Getting Started with AWS IoT](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html). The guide mentions the AWS IoT Button, but you do not need one to use this SDK. 1. [Sign in to the AWS IoT Console](https://docs.aws.amazon.com/iot/latest/developerguide/iot-console-signin.html) 2. [Register a Device in the Registry](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html) 3. [Create and Activate a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html) 4. [Create an AWS IoT Policy](https://docs.aws.amazon.com/iot/latest/developerguide/create-iot-policy.html) 5. [Attach an AWS IoT Policy to a Device Certificate](https://docs.aws.amazon.com/iot/latest/developerguide/attach-policy-to-certificate.html) 6. [Attach a Certificate to a Thing](https://docs.aws.amazon.com/iot/latest/developerguide/attach-cert-thing.html) -2. *Optional:* Set the following `#define` in [iot_config.h](demos/iot_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. +3. *Optional:* Set the following `#define` in [iot_config.h](demos/iot_config.h). You may skip this step and instead pass these configuration settings as command line options when running the demos. - Set `IOT_DEMO_IDENTIFIER` to the Thing Name you set in [step 1.2](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html). The corresponding command line option for this constant is `-i`. - Set `IOT_DEMO_SERVER` to your custom endpoint. This is found on the *Settings* page of the AWS IoT Console and has a format of `ABCDEFG1234567.iot.us-east-2.amazonaws.com`. The corresponding command line option for this constant is `-h`. - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. -3. Make a build directory in the SDK's root directory and `cd` into it. +4. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build cd build ``` -4. Run CMake, then `make`. This builds the demo executables and places them in `build/bin`. +5. Run CMake, then `make`. This builds the demo executables and places them in `build/bin`. ```sh cmake .. make ``` -## Beta Features +See the documentation page [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html) for a list of options that can be used to configure the build system. -This Beta library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. In addition, it provides the following new features: -- Asynchronous API for both MQTT and Thing Shadow. -- Multithreaded API by default (removed the `yield` function). -- More efficient platform layer (especially timers). -- Complete separation of MQTT and network stack, allowing MQTT to run over any network stack. -- Configurable memory allocation (static-only or dynamic). Memory allocation functions may also be set by the user. -- Network stack based on OpenSSL. -- MQTT persistent session support. +## Porting the SDK -Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: -- Auto-reconnect for MQTT connections. -- Shadow JSON document generator. -- Jobs API. -- Build support for Apple macOS. +Please refer to the [Porting Guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/guide_developer_porting.html) for instructions on porting this SDK. ## License From f7a148aca499a667d1fa6f90dc4f861de124b6cf Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Wed, 8 May 2019 15:07:39 -0700 Subject: [PATCH 128/844] Opaque types for task pool. (#386) --- doc/lib/taskpool.txt | 1 + lib/include/iot_mqtt.h | 2 +- lib/include/iot_taskpool.h | 79 ++- lib/include/private/iot_mqtt_internal.h | 48 +- lib/include/private/iot_static_memory.h | 2 +- lib/include/private/iot_taskpool_internal.h | 97 +++- lib/include/types/iot_taskpool_types.h | 140 +++-- lib/source/common/iot_taskpool.c | 487 +++++++++++------- .../common/iot_taskpool_static_memory.c | 49 +- lib/source/defender/aws_iot_defender_api.c | 44 +- lib/source/mqtt/iot_mqtt_api.c | 9 +- lib/source/mqtt/iot_mqtt_network.c | 2 +- lib/source/mqtt/iot_mqtt_operation.c | 36 +- tests/common/unit/iot_tests_taskpool.c | 348 +++++++------ tests/iot_config.h | 3 + tests/mqtt/unit/iot_tests_mqtt_api.c | 11 +- tests/mqtt/unit/iot_tests_mqtt_receive.c | 3 +- 17 files changed, 815 insertions(+), 546 deletions(-) diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index 52d34a7971..d7f444f998 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -135,4 +135,5 @@ Log messages from the task pool library at or below this setting will be printed @paramstructs{taskpool,task pool} @functionpointers{taskpool,task pool} @structs{taskpool,task pool} +@handles{taskpool,task pool} */ diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index dbb04b290a..7c2e27e63d 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -251,7 +251,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, const IotMqttConnectInfo_t * pConnectInfo, uint32_t timeoutMs, - IotMqttConnection_t * pMqttConnection ); + IotMqttConnection_t * const pMqttConnection ); /* @[declare_mqtt_connect] */ /** diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index ddd8766391..0395815e9a 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -38,7 +38,7 @@ /* Task pool types. */ #include "types/iot_taskpool_types.h" -/*------------------------- TASKPOOL defined constants --------------------------*/ +/*------------------------- Task Pool library functions --------------------------*/ /** * @functionspage{taskpool,task pool library} @@ -55,6 +55,8 @@ * - @functionname{taskpool_function_scheduledeferred} * - @functionname{taskpool_function_getstatus} * - @functionname{taskpool_function_trycancel} + * - @functionname{taskpool_function_getjobstoragefromhandle} + * - @functionname{taskpool_function_strerror} */ /** @@ -71,6 +73,8 @@ * @functionpage{IotTaskPool_ScheduleDeferred,taskpool,scheduledeferred} * @functionpage{IotTaskPool_GetStatus,taskpool,getstatus} * @functionpage{IotTaskPool_TryCancel,taskpool,trycancel} + * @functionpage{IotTaskPool_GetJobStorageFromHandle,taskpool,getjobstoragefromhandle} + * @functionpage{IotTaskPool_strerror,taskpool,strerror} */ /** @@ -116,7 +120,7 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c * */ /* @[declare_taskpool_getsystemtaskpool] */ -IotTaskPool_t * IotTaskPool_GetSystemTaskPool( void ); +IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ); /* @[declare_taskpool_getsystemtaskpool] */ /** @@ -131,7 +135,7 @@ IotTaskPool_t * IotTaskPool_GetSystemTaskPool( void ); * pool. * * @param[in] pInfo A pointer to the task pool initialization data. - * @param[in,out] pTaskPool A pointer to the task pool storage to be initialized. + * @param[out] pTaskPool A pointer to the task pool handle to be used after initialization. * The pointer `pTaskPool` will hold a valid handle only if (@ref IotTaskPool_Create) * completes succesfully. * @@ -165,7 +169,7 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, * */ /* @[declare_taskpool_destroy] */ -IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ); +IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t taskPool ); /* @[declare_taskpool_destroy] */ /** @@ -188,7 +192,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ); * */ /* @[declare_taskpool_setmaxthreads] */ -IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, +IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPool, uint32_t maxThreads ); /* @[declare_taskpool_setmaxthreads] */ @@ -199,6 +203,7 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, * * @param[in] userCallback A user-specified callback for the job. * @param[in] pUserContext A user-specified context for the callback. + * @param[in] pJobStorage The storage for the job data structure. * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this * function returns succesfully. This handle can be used to inspect the job status with * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... @@ -210,8 +215,9 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, * */ /* @[declare_taskpool_createjob] */ -IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallback, - void * const pUserContext, +IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJobStorage_t * const pJobStorage, IotTaskPoolJob_t * const pJob ); /* @[declare_taskpool_createjob] */ @@ -224,7 +230,7 @@ IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallbac * @param[in] pTaskPool A handle to the task pool for which to create a recyclable job. * @param[in] userCallback A user-specified callback for the job. * @param[in] pUserContext A user-specified context for the callback. - * @param[out] ppJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this + * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this * function returns succesfully. This handle can be used to inspect the job status with * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... * @@ -240,10 +246,10 @@ IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallbac * */ /* @[declare_taskpool_createrecyclablejob] */ -IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskPool, - const IotTaskPoolRoutine_t userCallback, - void * const pUserContext, - IotTaskPoolJob_t ** const ppJob ); +IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPool, + IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJob_t * const pJob ); /* @[declare_taskpool_createrecyclablejob] */ /** @@ -273,8 +279,8 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP * */ /* @[declare_taskpool_destroyrecyclablejob] */ -IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ); +IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPool, + IotTaskPoolJob_t job ); /* @[declare_taskpool_destroyrecyclablejob] */ /** @@ -308,8 +314,8 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask * */ /* @[declare_taskpool_recyclejob] */ -IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ); +IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, + IotTaskPoolJob_t job ); /* @[declare_taskpool_recyclejob] */ /** @@ -347,7 +353,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * } JobUserContext_t; * * // An example of a user callback to invoke through a task pool thread. - * static void ExecutionCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t * pJob, void * context ) + * static void ExecutionCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t pJob, void * context ) * { * ( void )pTaskPool; * ( void )pJob; @@ -399,8 +405,8 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, * @endcode */ /* @[declare_taskpool_schedule] */ -IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, uint32_t flags ); /* @[declare_taskpool_schedule] */ @@ -430,8 +436,8 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, * */ /* @[declare_taskpool_scheduledeferred] */ -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, uint32_t timeMs ); /* @[declare_taskpool_scheduledeferred] */ @@ -452,8 +458,8 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool * the calling thread has a chance to inspect it. */ /* @[declare_taskpool_getstatus] */ -IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, - const IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, IotTaskPoolJobStatus_t * const pStatus ); /* @[declare_taskpool_getstatus] */ @@ -481,11 +487,26 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, * */ /* @[declare_taskpool_trycancel] */ -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, + IotTaskPoolJob_t job, IotTaskPoolJobStatus_t * const pStatus ); /* @[declare_taskpool_trycancel] */ +/** + * @brief Returns a pointer to the job storage from an instance of a job handle + * of type @ref IotTaskPoolJob_t. This function is guarateed to succeed for a + * valid job handle. + * + * @param[in] pJob The job handle. + * + * @return A pointer to the storage associated with the job handle `pJob`. + * + * @warning If the `pJob` handle used is invalid, the results will be undefined. + */ +/* @[declare_taskpool_getjobstoragefromhandle] */ +IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t job ); +/* @[declare_taskpool_getjobstoragefromhandle] */ + /** * @brief Returns a string that describes an @ref IotTaskPoolError_t. * @@ -507,6 +528,14 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, const char * IotTaskPool_strerror( IotTaskPoolError_t status ); /* @[declare_taskpool_strerror] */ +/** + * @brief The maximum number of task pools to be created when using + * a memory pool. + */ +#ifndef IOT_TASKPOOLS +#define IOT_TASKPOOLS ( 4 ) +#endif + /** * @brief The maximum number of jobs to cache. */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index cf738b0d5f..d75fd756d5 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -258,21 +258,22 @@ typedef struct _mqttConnection const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ #endif - bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ - IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ - int32_t references; /**< @brief Counts callbacks and operations using this connection. */ - IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ - IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ - - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ - - bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ - uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ - uint32_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ - IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ - uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ - size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ + bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ + IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ + int32_t references; /**< @brief Counts callbacks and operations using this connection. */ + IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ + IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ + + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ + + bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ + uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ + uint32_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ + IotTaskPoolJobStorage_t keepAliveJobStorage; /**< @brief Task pool job for processing this connection's keep-alive. */ + IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ + uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ + size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ } _mqttConnection_t; /** @@ -321,6 +322,7 @@ typedef struct _mqttOperation bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ _mqttConnection_t * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ + IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ union @@ -720,8 +722,8 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ); * @param[in] pKeepAliveJob Pointer the an MQTT connection's keep-alive job. * @param[in] pContext Pointer to an MQTT connection, passed as an opaque context. */ -void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pKeepAliveJob, +void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pKeepAliveJob, void * pContext ); /** @@ -732,8 +734,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, * @param[in] pContext Pointer to the incoming PUBLISH operation, passed as an * opaque context. */ -void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pPublishJob, +void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pPublishJob, void * pContext ); /** @@ -744,8 +746,8 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, * @param[in] pContext Pointer to the operation to send, passed as an opaque * context. */ -void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pSendJob, +void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pSendJob, void * pContext ); /** @@ -756,8 +758,8 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, * @param[in] pContext Pointer to the completed operation, passed as an opaque * context. */ -void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pOperationJob, +void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pOperationJob, void * pContext ); /** diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 94dda74dd0..e4b1ccaeec 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -242,5 +242,5 @@ void * Iot_MallocMessageBuffer( size_t size ); /* @[declare_static_memory_freemessagebuffer] */ void Iot_FreeMessageBuffer( void * ptr ); /* @[declare_static_memory_freemessagebuffer] */ - + #endif /* if !defined( IOT_STATIC_MEMORY_H_ ) && ( IOT_STATIC_MEMORY_ONLY == 1 ) */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index a7e71b873f..e047193707 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -138,6 +138,18 @@ #if IOT_STATIC_MEMORY_ONLY == 1 #include "private/iot_static_memory.h" +/** + * @brief Allocate an #_taskPool_t. This function should have the + * same signature as [malloc]. + */ + void * IotTaskPool_MallocTaskPool( size_t size ); + +/** + * @brief Free an #_taskPool_t. This function should have the + * same signature as [malloc]. + */ + void IotTaskPool_FreeTaskPool( void * ptr ); + /** * @brief Allocate an #IotTaskPoolJob_t. This function should have the * same signature as [malloc]. @@ -146,7 +158,8 @@ /** * @brief Free an #IotTaskPoolJob_t. This function should have the same - * signature as [free] + * same signature as [malloc]. + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). */ void IotTaskPool_FreeJob( void * ptr ); @@ -161,21 +174,34 @@ * same signature as[ free ]. */ void IotTaskPool_FreeTimerEvent( void * ptr ); + #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ #include + #ifndef IotTaskPool_MallocTaskPool + #define IotTaskPool_MallocTaskPool malloc + #endif + + #ifndef IotTaskPool_FreeTaskPool + #define IotTaskPool_FreeTaskPool free + #endif + #ifndef IotTaskPool_MallocJob #define IotTaskPool_MallocJob malloc #endif + #ifndef IotTaskPool_FreeJob - #define IotTaskPool_FreeJob free + #define IotTaskPool_FreeJob free #endif + #ifndef IotTaskPool_MallocTimerEvent #define IotTaskPool_MallocTimerEvent malloc #endif + #ifndef IotTaskPool_FreeTimerEvent #define IotTaskPool_FreeTimerEvent free #endif + #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /* ---------------------------------------------------------------------------------------------- */ @@ -189,6 +215,60 @@ #define IOT_TASK_POOL_INTERNAL_STATIC ( ( uint32_t ) 0x00000001 ) /* Flag to mark a job as user-allocated. */ /** @endcond */ +/** + * @brief Task pool jobs cache. + * + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. + * + */ +typedef struct _taskPoolCache +{ + IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ + uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ +} _taskPoolCache_t; + +/** + * @brief The task pool data structure keeps track of the internal state and the signals for the dispatcher threads. + * The task pool is a thread safe data structure. + * + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. + * + */ +typedef struct _taskPool +{ + IotDeQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ + IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ + _taskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ + uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ + uint32_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ + uint32_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ + uint32_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ + uint32_t stackSize; /**< @brief The stack size for all task pool threads. */ + int32_t priority; /**< @brief The priority for all task pool threads. */ + IotSemaphore_t dispatchSignal; /**< @brief The synchronization object on which threads are waiting for incoming jobs. */ + IotSemaphore_t startStopSignal; /**< @brief The synchronization object for threads to signal start and stop condition. */ + IotTimer_t timer; /**< @brief The timer for deferred jobs. */ + IotMutex_t lock; /**< @brief The lock to protect the task pool data structure access. */ +} _taskPool_t; + +/** + * @brief The job data structure keeps track of the user callback and context, as well as the status of the job. + * + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. + * + */ +typedef struct _taskPoolJob +{ + IotLink_t link; /**< @brief The link to insert the job in the dispatch queue. */ + IotTaskPoolRoutine_t userCallback; /**< @brief The user provided callback. */ + void * pUserContext; /**< @brief The user provided context. */ + uint32_t flags; /**< @brief Internal flags. */ + IotTaskPoolJobStatus_t status; /**< @brief The status for the job. */ +} _taskPoolJob_t; + /** * @brief Represents an operation that is subject to a timer. * @@ -198,19 +278,8 @@ typedef struct _taskPoolTimerEvent { IotLink_t link; /**< @brief List link member. */ - uint64_t expirationTime; /**< @brief When this event should be processed. */ - IotTaskPoolJob_t * pJob; /**< @brief The task pool job associated with this event. */ + _taskPoolJob_t * pJob; /**< @brief The task pool job associated with this event. */ } _taskPoolTimerEvent_t; - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * System task pool. - */ -extern IotTaskPool_t _IotSystemTaskPool; -/** @endcond */ - #endif /* ifndef IOT_TASKPOOL_INTERNAL_H_ */ diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 0ea5f970e6..6cc5cd2de3 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -191,14 +191,58 @@ typedef enum IotTaskPoolJobStatus IOT_TASKPOOL_STATUS_UNDEFINED, } IotTaskPoolJobStatus_t; +/*------------------------- Task pool types and handles --------------------------*/ + +/** + * @ingroup taskpool_datatypes_handles + * @brief Opaque handle of a Task Pool instance. + * + * This type identifies a Task Pool instance, which is valid after a successful call + * to @ref taskpool_function_createsystemtaskpool or @ref taskpool_function_create. A + * variable of this type is passed as the first + * argument to [Task Pool library functions](@ref taskpool_functions) to identify which + * task pool that function acts on. + * + * A call to @ref taskpool_function_destroy makes a task pool handle invalid. Once + * @ref taskpool_function_destroy returns, the task handle should no longer + * be used. + * + * @initializer{IotTaskPool_t,IOT_TASKPOOL_INITIALIZER} + */ +typedef struct _taskPool * IotTaskPool_t; + +/** + * @ingroup taskpool_datatypes_structs + * @brief The job storage data structure provides the storage for a statically allocated Task Pool Job instance. + * + * @warning This is a system-level data type that should not be modified or used directly in any application. + * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. + * + */ +typedef struct IotTaskPoolJobStorage +{ + IotLink_t dummy1; /**< @brief Placeholder. */ + void * dummy2; /**< @brief Placeholder. */ + void * dummy3; /**< @brief Placeholder. */ + uint32_t dummy4; /**< @brief Placeholder. */ + IotTaskPoolJobStatus_t dummy5; /**< @brief Placeholder. */ +} IotTaskPoolJobStorage_t; + /** - * @cond DOXYGEN_IGNORE - * @brief Forward declarations for task pool types. + * @ingroup taskpool_datatypes_handles + * @brief Opaque handle of a Task Pool Job. + * + * This type identifies a Task Pool Job instance, which is valid after a successful call + * to @ref taskpool_function_createjob or @ref taskpool_function_createrecyclablejob. + * + * A call to @ref taskpool_function_recyclejob or @ref taskpool_function_destroyrecyclablejob makes a + * task pool job handle invalid. Once @ref taskpool_function_recyclejob or + * @ref taskpool_function_destroyrecyclablejob returns, the task job handle should no longer be used. + * + * @initializer{IotTaskPoolJob_t,IOT_TASKPOOL_JOB_INITIALIZER} * */ -struct IotTaskPool; -struct IotTaskPoolJob; -/** @endcond */ +typedef struct _taskPoolJob * IotTaskPoolJob_t; /*------------------------- Task pool parameter structs --------------------------*/ @@ -211,8 +255,8 @@ struct IotTaskPoolJob; * calling @ref IotTaskPool_Schedule. * */ -typedef void ( * IotTaskPoolRoutine_t )( struct IotTaskPool * pTaskPool, - struct IotTaskPoolJob * pJob, +typedef void ( * IotTaskPoolRoutine_t )( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pUserContext ); /** @@ -242,66 +286,6 @@ typedef struct IotTaskPoolInfo int32_t priority; /**< @brief priority for every task pool thread. The priority for each thread is fixed after the task pool is created and cannot be changed. */ } IotTaskPoolInfo_t; -/*------------------------- Task pool handles structs --------------------------*/ - -/** - * @ingroup taskpool_datatypes_structs - * @brief Task pool jobs cache. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct IotTaskPoolCache -{ - IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ - uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ -} IotTaskPoolCache_t; - - -/** - * @ingroup taskpool_datatypes_structs - * @brief The task pool data structure keeps track of the internal state and the signals for the dispatcher threads. - * The task pool is a thread safe data structure. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct IotTaskPool -{ - IotDeQueue_t dispatchQueue; /**< @brief The queue for the jobs waiting to be executed. */ - IotListDouble_t timerEventsList; /**< @brief The timeouts queue for all deferred jobs waiting to be executed. */ - IotTaskPoolCache_t jobsCache; /**< @brief A cache to re-use jobs in order to limit memory allocations. */ - uint32_t minThreads; /**< @brief The minimum number of threads for the task pool. */ - uint32_t maxThreads; /**< @brief The maximum number of threads for the task pool. */ - uint32_t activeThreads; /**< @brief The number of threads in the task pool at any given time. */ - uint32_t activeJobs; /**< @brief The number of active jobs in the task pool at any given time. */ - uint32_t stackSize; /**< @brief The stack size for all task pool threads. */ - int32_t priority; /**< @brief The priority for all task pool threads. */ - IotSemaphore_t dispatchSignal; /**< @brief The synchronization object on which threads are waiting for incoming jobs. */ - IotSemaphore_t startStopSignal; /**< @brief The synchronization object for threads to signal start and stop condition. */ - IotTimer_t timer; /**< @brief The timer for deferred jobs. */ - IotMutex_t lock; /**< @brief The lock to protect the task pool data structure access. */ -} IotTaskPool_t; - -/** - * @ingroup taskpool_datatypes_structs - * @brief The job data structure keeps track of the user callback and context, as well as the status of the job. - * - * @warning This is a system-level data type that should not be modified or used directly in any application. - * @warning This is a system-level data type that can and will change across different versions of the platform, with no regards for backward compatibility. - * - */ -typedef struct IotTaskPoolJob -{ - IotTaskPoolRoutine_t userCallback; /**< @brief The user provided callback. */ - void * pUserContext; /**< @brief The user provided context. */ - IotLink_t link; /**< @brief The link to insert the job in the dispatch queue. */ - uint32_t flags; /**< @brief Internal flags. */ - IotTaskPoolJobStatus_t status; /**< @brief The status for the job. */ -} IotTaskPoolJob_t; - /*------------------------- TASKPOOL defined constants --------------------------*/ /** @@ -336,12 +320,22 @@ typedef struct IotTaskPoolJob * */ /* @[define_taskpool_initializers] */ -#define IOT_TASKPOOL_INFO_INITIALIZER_SMALL { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a small #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a medium #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_LARGE { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a large #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_XLARGE { .minThreads = 2, .maxThreads = 4, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /**< @brief Initializer for a very large #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM /**< @brief Initializer for a typical #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INITIALIZER { 0 } /**< @brief Initializer for a #IotTaskPoolJob_t. */ +/** @brief Initializer for a small #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_SMALL { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +/** @brief Initializer for a medium #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +/** @brief Initializer for a large #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_LARGE { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +/** @brief Initializer for a very large #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER_XLARGE { .minThreads = 2, .maxThreads = 4, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +/** @brief Initializer for a typical #IotTaskPoolInfo_t. */ +#define IOT_TASKPOOL_INFO_INITIALIZER IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM +/** @brief Initializer for a #IotTaskPool_t. */ +#define IOT_TASKPOOL_INITIALIZER NULL +/** @brief Initializer for a #IotTaskPoolJobStorage_t. */ +#define IOT_TASKPOOL_JOB_STORAGE_INITIALIZER { { NULL, NULL }, NULL, NULL, 0, IOT_TASKPOOL_STATUS_UNDEFINED } +/** @brief Initializer for a #IotTaskPoolJob_t. */ +#define IOT_TASKPOOL_JOB_INITIALIZER NULL /* @[define_taskpool_initializers] */ /** diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index f71c647fee..75b6b20d70 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -44,13 +44,13 @@ * @brief Enter a critical section by locking a mutex. * */ -#define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( pTaskPool->lock ) ) /** * @brief Exit a critical section by unlocking a mutex. * */ -#define TASKPOOL_EXIT_CRITICAL() IotMutex_Unlock( &( ( ( IotTaskPool_t * ) pTaskPool )->lock ) ) +#define TASKPOOL_EXIT_CRITICAL() IotMutex_Unlock( &( pTaskPool->lock ) ) /** * @brief Maximum semaphore value for wait operations. @@ -62,10 +62,9 @@ */ #define TASKPOOL_JOB_RESCHEDULE_DELAY_MS ( 10ULL ) -/* ---------------------------------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------------------- */ /** - * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. * * @brief The system task pool handle for all libraries to use. @@ -73,16 +72,7 @@ * the system libraries as well. The system task pool needs to be initialized before any library is used or * before any code that posts jobs to the task pool runs. */ -IotTaskPool_t _IotSystemTaskPool = { .dispatchQueue = { 0 } }; - -/** @endcond */ - -/* ---------------------------------------------------------------------------------------------- */ - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - */ +_taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER }; /* -------------- Convenience functions to create/recycle/destroy jobs -------------- */ @@ -91,14 +81,17 @@ IotTaskPool_t _IotSystemTaskPool = { .dispatchQueue = { 0 } }; * * @param[in] pCache The pre-allocated instance of the cache to initialize. */ -static void _initJobsCache( IotTaskPoolCache_t * const pCache ); +static void _initJobsCache( _taskPoolCache_t * const pCache ); /** * @brief Initialize a job. * * @param[in] pJob The job to initialize. + * @param[in] userCallback The user callback for the job. + * @param[in] pUserContext The context tp be passed to the callback. + * @param[in] isStatic A flag to indicate whether the job is statically or synamically allocated. */ -static void _initializeJob( IotTaskPoolJob_t * const pJob, +static void _initializeJob( _taskPoolJob_t * const pJob, IotTaskPoolRoutine_t userCallback, void * pUserContext, bool isStatic ); @@ -108,7 +101,7 @@ static void _initializeJob( IotTaskPoolJob_t * const pJob, * * @param[in] pCache The instance of the cache to extract the job from. */ -static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache ); +static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache ); /** * Recycles one instance of a job into the cache or, if the cache is full, it destroys it. @@ -117,8 +110,8 @@ static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache * @param[in] pJob The job to recycle. * */ -static void _recycleJob( IotTaskPoolCache_t * const pCache, - IotTaskPoolJob_t * const pJob ); +static void _recycleJob( _taskPoolCache_t * const pCache, + _taskPoolJob_t * const pJob ); /** * Destroys one instance of a job. @@ -126,7 +119,7 @@ static void _recycleJob( IotTaskPoolCache_t * const pCache, * @param[in] pJob The job to destroy. * */ -static void _destroyJob( IotTaskPoolJob_t * const pJob ); +static void _destroyJob( _taskPoolJob_t * const pJob ); /* -------------- The worker thread procedure for a task pool thread -------------- */ @@ -149,13 +142,32 @@ static void _taskPoolWorker( void * pUserContext ); static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, const IotLink_t * const pTimerEventLink2 ); +/** + * Reschedules the timer for handling deferred jobs to the next timeout. + * + * param[in] pTimer The timer to reschedule. + * param[in] pFirstTimerEvent The timer event that carries the timeout and job inforamtion. + */ static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, _taskPoolTimerEvent_t * const pFirstTimerEvent ); +/** + * The task pool timer procedure for scheduling deferred jobs. + * + * param[in] pArgument An opaque pointer for timer procedure context. + */ static void _timerThread( void * pArgument ); /* -------------- Convenience functions to create/initialize/destroy the task pool -------------- */ +/** + * Parameter validation for a task pool initialization. + * + * @param[in] pInfo The initialization information for the task pool. + * + */ +bool _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ); + /** * Initializes a pre-allocated instance of a task pool. * @@ -164,18 +176,17 @@ static void _timerThread( void * pArgument ); * */ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, - IotTaskPool_t * const pTaskPool ); + _taskPool_t * const pTaskPool ); /** * Initializes a pre-allocated instance of a task pool. * * @param[in] pInfo The initialization information for the task pool. - * @param[in] pTaskPoolBuffer A storage to build the task pool when staic allocation is chosen. - * @param[out] pTaskPool The handle to the created task pool. + * @param[out] pTaskPool A pointer to the task pool data structure to initialize. * */ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, - IotTaskPool_t * const pTaskPool ); + _taskPool_t * const pTaskPool ); /** * Destroys one instance of a task pool. @@ -183,7 +194,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo * @param[in] pTaskPool The task pool to destroy. * */ -static void _destroyTaskPool( IotTaskPool_t * const pTaskPool ); +static void _destroyTaskPool( _taskPool_t * const pTaskPool ); /** * Check for the exit condition. @@ -191,15 +202,16 @@ static void _destroyTaskPool( IotTaskPool_t * const pTaskPool ); * @param[in] pTaskPool The task pool to destroy. * */ -static bool _IsShutdownStarted( const IotTaskPool_t * const pTaskPool ); +static bool _IsShutdownStarted( const _taskPool_t * const pTaskPool ); /** * Set the exit condition. * * @param[in] pTaskPool The task pool to destroy. + * @param[in] threads The number of threads active in the task pool at shutdown time. * */ -static void _signalShutdown( IotTaskPool_t * const pTaskPool, +static void _signalShutdown( _taskPool_t * const pTaskPool, uint32_t threads ); /** @@ -207,10 +219,11 @@ static void _signalShutdown( IotTaskPool_t * const pTaskPool, * * @param[in] pTaskPool The task pool to scheduel the job with. * @param[in] pJob The job to schedule. + * @param[in] flags The job flags. * */ -static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, + _taskPoolJob_t * const pJob, uint32_t flags ); /** @@ -231,8 +244,8 @@ static bool _matchJobByPointer( const IotLink_t * const pLink, * @param[out] pStatus The status of the job at the time of cancellation. * */ -static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, + _taskPoolJob_t * const pJob, IotTaskPoolJobStatus_t * const pStatus ); /** @@ -240,16 +253,17 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, * * @param[in] pTaskPool The task pool to safely extract a job from. * @param[in] pJob The job to extract. + * @param[in] atCompletion A flag to indicate whether the job is being scheduled or + * was completed already. * */ -static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +static IotTaskPoolError_t _trySafeExtraction( _taskPool_t * const pTaskPool, + _taskPoolJob_t * const pJob, bool atCompletion ); -/** @endcond */ /* ---------------------------------------------------------------------------------------------- */ -IotTaskPool_t * IotTaskPool_GetSystemTaskPool( void ) +IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ) { return &_IotSystemTaskPool; } @@ -260,7 +274,11 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Create( pInfo, &_IotSystemTaskPool ) ); + /* Parameter checking. */ + TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) ); + + /* Create the system task pool pool. */ + TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, &_IotSystemTaskPool ) ); TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -272,22 +290,55 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTempTaskPool = NULL; + + /* Verify that the task pool storage is valid. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTaskPool ) ); + /* Parameter checking. */ + TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) ); + + /* Allocate the memory for the task pool */ + pTempTaskPool = ( _taskPool_t * )IotTaskPool_MallocTaskPool( sizeof( _taskPool_t ) ); - TASKPOOL_NO_FUNCTION_CLEANUP(); + if( pTempTaskPool == NULL ) + { + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + + memset( pTempTaskPool, 0x00, sizeof( _taskPool_t ) ); + + TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTempTaskPool ) ); + + TASKPOOL_FUNCTION_CLEANUP(); + + if( TASKPOOL_FAILED(status) ) + { + if(pTempTaskPool != NULL) + { + IotTaskPool_FreeTaskPool( pTempTaskPool ); + } + } + else + { + *pTaskPool = pTempTaskPool; + } + + + TASKPOOL_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) +IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t taskPoolHandle ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); uint32_t count; bool completeShutdown = true; + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + /* Track how many threads the task pool owns. */ uint32_t activeThreads; @@ -319,7 +370,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) if( pItemLink != NULL ) { - IotTaskPoolJob_t * pJob = IotLink_Container( IotTaskPoolJob_t, pItemLink, link ); + _taskPoolJob_t * pJob = IotLink_Container( _taskPoolJob_t, pItemLink, link ); _destroyJob( pJob ); } @@ -343,6 +394,8 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) if( pTimerEvent->expirationTime <= now ) { + IotLogDebug( "Shutdown will be deferred to the timer thread" ); + /* Timer may have fired already! Let the timer thread destroy * complete the taskpool destruction sequence. */ completeShutdown = false; @@ -376,7 +429,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) if( pItemLink != NULL ) { - IotTaskPoolJob_t * pJob = IotLink_Container( IotTaskPoolJob_t, pItemLink, link ); + _taskPoolJob_t * pJob = IotLink_Container( _taskPoolJob_t, pItemLink, link ); _destroyJob( pJob ); } @@ -400,6 +453,12 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) if( completeShutdown == true ) { _destroyTaskPool( pTaskPool ); + + /* Do not free the system task pool which is statically allocated. */ + if( pTaskPool != &_IotSystemTaskPool ) + { + IotTaskPool_FreeTaskPool( pTaskPool ); + } } TASKPOOL_NO_FUNCTION_CLEANUP(); @@ -407,13 +466,15 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t * pTaskPool ) /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, +IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPoolHandle, uint32_t maxThreads ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); uint32_t count, i; + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pTaskPool->minThreads > maxThreads ); @@ -460,38 +521,44 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t * pTaskPool, /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_CreateJob( const IotTaskPoolRoutine_t userCallback, - void * const pUserContext, - IotTaskPoolJob_t * const pJob ) +IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJobStorage_t * const pJobStorage, + IotTaskPoolJob_t * const ppJob ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobStorage ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); /* Build a job around the user-provided storage. */ - _initializeJob( pJob, userCallback, pUserContext, true ); + _initializeJob( ( _taskPoolJob_t * ) pJobStorage, userCallback, pUserContext, true ); + + *ppJob = ( IotTaskPoolJob_t )pJobStorage; TASKPOOL_NO_FUNCTION_CLEANUP(); } /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskPool, - const IotTaskPoolRoutine_t userCallback, - void * const pUserContext, - IotTaskPoolJob_t ** const ppJob ) +IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle, + IotTaskPoolRoutine_t userCallback, + void * pUserContext, + IotTaskPoolJob_t * const ppJob ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + { - IotTaskPoolJob_t * pTempJob = NULL; + _taskPoolJob_t * pTempJob = NULL; TASKPOOL_ENTER_CRITICAL(); { @@ -507,7 +574,6 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP } TASKPOOL_EXIT_CRITICAL(); - /* Initialize all members. */ if( pTempJob == NULL ) { IotLogInfo( "Failed to allocate a job." ); @@ -525,14 +591,17 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t * const pTaskP /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ) +IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPoolHandle, + IotTaskPoolJob_t pJobHandle ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobHandle ); + + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + _taskPoolJob_t * pJob1 = ( _taskPoolJob_t * )pJobHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -542,7 +611,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask status = IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS; } /* Do not destroy statically allocated jobs. */ - else if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == IOT_TASK_POOL_INTERNAL_STATIC ) + else if( ( pJob1->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == IOT_TASK_POOL_INTERNAL_STATIC ) { IotLogWarn( "Attempt to destroy a statically allocated job." ); @@ -550,7 +619,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask } else { - status = _trySafeExtraction( pTaskPool, pJob, true ); + status = _trySafeExtraction( pTaskPool, pJob1, true ); } } TASKPOOL_EXIT_CRITICAL(); @@ -558,9 +627,9 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask if( TASKPOOL_SUCCEEDED( status ) ) { /* At this point, the job must not be in any queue or list. */ - IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); + IotTaskPool_Assert( IotLink_IsLinked( &pJob1->link ) == false ); - _destroyJob( pJob ); + _destroyJob( pJob1 ); } TASKPOOL_NO_FUNCTION_CLEANUP(); @@ -568,15 +637,17 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t * const pTask /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob ) +IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPoolHandle, + IotTaskPoolJob_t pJob ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ @@ -612,17 +683,19 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t * const pTaskPool, /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPoolHandle, + IotTaskPoolJob_t pJob, uint32_t flags ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + TASKPOOL_ENTER_CRITICAL(); { /* Bail out early if this task pool is shutting down. */ @@ -648,16 +721,18 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t * const pTaskPool, /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, + IotTaskPoolJob_t pJob, uint32_t timeMs ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + if( timeMs == 0UL ) { TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, pJob, 0 ) ); @@ -676,7 +751,10 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool /* If all safety checks completed, proceed. */ if( TASKPOOL_SUCCEEDED( _trySafeExtraction( pTaskPool, pJob, false ) ) ) { - _taskPoolTimerEvent_t * pTimerEvent = IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); + IotLink_t * pTimerEventLink; + uint64_t now; + + _taskPoolTimerEvent_t * pTimerEvent = ( _taskPoolTimerEvent_t * )IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); if( pTimerEvent == NULL ) { @@ -685,14 +763,14 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } - IotLink_t * pTimerEventLink; + memset( pTimerEvent, 0x00, sizeof( _taskPoolTimerEvent_t ) ); - uint64_t now = IotClock_GetTimeMs(); + now = IotClock_GetTimeMs(); pTimerEvent->link.pNext = NULL; pTimerEvent->link.pPrevious = NULL; pTimerEvent->expirationTime = now + timeMs; - pTimerEvent->pJob = pJob; + pTimerEvent->pJob = ( _taskPoolJob_t * )pJob; /* Append the timer event to the timer list. */ IotListDouble_InsertSorted( &pTaskPool->timerEventsList, &pTimerEvent->link, _timerEventCompare ); @@ -728,17 +806,19 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t * const pTaskPool /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, - const IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPoolHandle, + IotTaskPoolJob_t pJob, IotTaskPoolJobStatus_t * const pStatus ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; TASKPOOL_ENTER_CRITICAL(); @@ -760,16 +840,18 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t * const pTaskPool, /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle, + IotTaskPoolJob_t pJob, IotTaskPoolJobStatus_t * const pStatus ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); + TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); + _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + if( pStatus != NULL ) { *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; @@ -792,6 +874,11 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t * const pTaskPool, TASKPOOL_NO_FUNCTION_CLEANUP(); } +IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t pJob ) +{ + return ( IotTaskPoolJobStorage_t * )pJob; +} + const char * IotTaskPool_strerror( IotTaskPoolError_t status ) { const char * pMessage = NULL; @@ -834,103 +921,28 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) /* ---------------------------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------------------------- */ -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - */ - -static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, - IotTaskPool_t * const pTaskPool ) +bool _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); - uint32_t count; - uint32_t threadsCreated = 0; - /* Check input values for consistency. */ - /* Parameter checking. */ - TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < 1UL ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < 1UL ); - /* Initialize all internal data structure prior to creating all threads. */ - TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); - - /* Create the timer mutex for a new connection. */ - if( IotClock_TimerCreate( &( pTaskPool->timer ), _timerThread, pTaskPool ) == false ) - { - IotLogError( "Failed to create timer for task pool." ); - - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - IotTaskPool_Assert( pInfo->minThreads == pTaskPool->minThreads ); - IotTaskPool_Assert( pInfo->maxThreads == pTaskPool->maxThreads ); - - /* The task pool will initialize the minimum number of threads reqeusted by the user upon start. */ - /* When a thread is created, it will signal a semaphore to signify that it is about to wait on incoming */ - /* jobs. A thread can be woken up for exit or for new jobs only at that point in time. */ - /* The exit condition is setting the maximum number of threads to 0. */ - - /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ - for( ; threadsCreated < pTaskPool->minThreads; ) - { - /* Create one thread. */ - if( Iot_CreateDetachedThread( _taskPoolWorker, - pTaskPool, - pTaskPool->priority, - pTaskPool->stackSize ) == false ) - { - IotLogError( "Could not create worker thread! Exiting..." ); - - /* If creating one thread fails, set error condition and exit the loop. */ - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } - - /* Upon successful thread creation, increase the number of active threads. */ - pTaskPool->activeThreads++; - - ++threadsCreated; - } - - TASKPOOL_FUNCTION_CLEANUP(); - - /* Wait for threads to be ready to wait on the condition, so that threads are actually able to receive messages. */ - for( count = 0; count < threadsCreated; ++count ) - { - IotSemaphore_Wait( &pTaskPool->startStopSignal ); - } - - /* In case of failure, wait on the created threads to exit. */ - if( TASKPOOL_FAILED( status ) ) - { - /* Set the exit condition for the newly created threads. */ - _signalShutdown( pTaskPool, threadsCreated ); - - /* Signal all threads to exit. */ - for( count = 0; count < threadsCreated; ++count ) - { - IotSemaphore_Wait( &pTaskPool->startStopSignal ); - } - - _destroyTaskPool( pTaskPool ); - } - - TASKPOOL_FUNCTION_CLEANUP_END(); + TASKPOOL_NO_FUNCTION_CLEANUP(); } -/*-----------------------------------------------------------*/ - static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, - IotTaskPool_t * const pTaskPool ) + _taskPool_t * const pTaskPool ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); bool semStartStopInit = false; bool lockInit = false; bool semDispatchInit = false; + bool timerInit = false; /* Zero out all data structures. */ memset( ( void * ) pTaskPool, 0x00, sizeof( IotTaskPool_t ) ); @@ -949,18 +961,28 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ _initJobsCache( &pTaskPool->jobsCache ); /* Initialize the semaphore to ensure all threads have started. */ - if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, TASKPOOL_MAX_SEM_VALUE ) ) + if( IotSemaphore_Create( &pTaskPool->startStopSignal, 0, TASKPOOL_MAX_SEM_VALUE ) == true ) { semStartStopInit = true; - if( IotMutex_Create( &pTaskPool->lock, true ) ) + if( IotMutex_Create( &pTaskPool->lock, true ) == true ) { lockInit = true; /* Initialize the semaphore for waiting for incoming work. */ - if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, TASKPOOL_MAX_SEM_VALUE ) ) + if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, TASKPOOL_MAX_SEM_VALUE ) == true ) { semDispatchInit = true; + + /* Create the timer mutex for a new connection. */ + if( IotClock_TimerCreate( &( pTaskPool->timer ), _timerThread, pTaskPool ) == true ) + { + timerInit = true; + } + else + { + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } } else { @@ -981,20 +1003,97 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ if( TASKPOOL_FAILED( status ) ) { - if( semStartStopInit ) + if( semStartStopInit == true ) { IotSemaphore_Destroy( &pTaskPool->startStopSignal ); } - if( lockInit ) + if( lockInit == true ) { IotMutex_Destroy( &pTaskPool->lock ); } - if( semDispatchInit ) + if( semDispatchInit == true ) { IotSemaphore_Destroy( &pTaskPool->dispatchSignal ); } + + if( timerInit == true ) + { + IotClock_TimerDestroy( &pTaskPool->timer ); + } + } + + TASKPOOL_FUNCTION_CLEANUP_END(); +} + +static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, + _taskPool_t * const pTaskPool ) +{ + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + + uint32_t count; + uint32_t threadsCreated = 0; + bool controlInit = false; + + /* Initialize all internal data structure prior to creating all threads. */ + TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); + + controlInit = true; + + IotTaskPool_Assert( pInfo->minThreads == pTaskPool->minThreads ); + IotTaskPool_Assert( pInfo->maxThreads == pTaskPool->maxThreads ); + + /* The task pool will initialize the minimum number of threads reqeusted by the user upon start. */ + /* When a thread is created, it will signal a semaphore to signify that it is about to wait on incoming */ + /* jobs. A thread can be woken up for exit or for new jobs only at that point in time. */ + /* The exit condition is setting the maximum number of threads to 0. */ + + /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ + for( ; threadsCreated < pTaskPool->minThreads; ) + { + /* Create one thread. */ + if( Iot_CreateDetachedThread( _taskPoolWorker, + pTaskPool, + pTaskPool->priority, + pTaskPool->stackSize ) == false ) + { + IotLogError( "Could not create worker thread! Exiting..." ); + + /* If creating one thread fails, set error condition and exit the loop. */ + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } + + /* Upon successful thread creation, increase the number of active threads. */ + pTaskPool->activeThreads++; + + ++threadsCreated; + } + + TASKPOOL_FUNCTION_CLEANUP(); + + /* Wait for threads to be ready to wait on the condition, so that threads are actually able to receive messages. */ + for( count = 0; count < threadsCreated; ++count ) + { + IotSemaphore_Wait( &pTaskPool->startStopSignal ); + } + + /* In case of failure, wait on the created threads to exit. */ + if( TASKPOOL_FAILED( status ) ) + { + /* Set the exit condition for the newly created threads. */ + _signalShutdown( pTaskPool, threadsCreated ); + + /* Signal all threads to exit. */ + for( count = 0; count < threadsCreated; ++count ) + { + IotSemaphore_Wait( &pTaskPool->startStopSignal ); + } + + if(controlInit == true) + { + _destroyTaskPool( pTaskPool ); + } } TASKPOOL_FUNCTION_CLEANUP_END(); @@ -1002,7 +1101,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ /*-----------------------------------------------------------*/ -static void _destroyTaskPool( IotTaskPool_t * const pTaskPool ) +static void _destroyTaskPool( _taskPool_t * const pTaskPool ) { IotClock_TimerDestroy( &pTaskPool->timer ); IotSemaphore_Destroy( &pTaskPool->dispatchSignal ); @@ -1020,7 +1119,7 @@ static void _taskPoolWorker( void * pUserContext ) bool running = true; /* Extract pTaskPool pointer from context. */ - IotTaskPool_t * pTaskPool = ( IotTaskPool_t * ) pUserContext; + _taskPool_t * pTaskPool = ( _taskPool_t * ) pUserContext; /* Signal that this worker completed initialization and it is ready to receive notifications. */ IotSemaphore_Post( &pTaskPool->startStopSignal ); @@ -1033,7 +1132,7 @@ static void _taskPoolWorker( void * pUserContext ) { bool jobAvailable; IotLink_t * pFirst; - IotTaskPoolJob_t * pJob = NULL; + _taskPoolJob_t * pJob = NULL; /* Wait on incoming notifications. If waiting on the semaphore return with timeout, then * it means that this thread should consider shutting down for the task pool to fold back @@ -1103,7 +1202,7 @@ static void _taskPoolWorker( void * pUserContext ) if( pFirst != NULL ) { /* Extract the job from its link. */ - pJob = IotLink_Container( IotTaskPoolJob_t, pFirst, link ); + pJob = IotLink_Container( _taskPoolJob_t, pFirst, link ); /* Update status to 'executing'. */ pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; @@ -1159,7 +1258,7 @@ static void _taskPoolWorker( void * pUserContext ) } else { - pJob = IotLink_Container( IotTaskPoolJob_t, pItem, link ); + pJob = IotLink_Container( _taskPoolJob_t, pItem, link ); userCallback = pJob->userCallback; } @@ -1173,7 +1272,7 @@ static void _taskPoolWorker( void * pUserContext ) /* ---------------------------------------------------------------------------------------------- */ -static void _initJobsCache( IotTaskPoolCache_t * const pCache ) +static void _initJobsCache( _taskPoolCache_t * const pCache ) { IotDeQueue_Create( &pCache->freeList ); @@ -1182,7 +1281,7 @@ static void _initJobsCache( IotTaskPoolCache_t * const pCache ) /*-----------------------------------------------------------*/ -static void _initializeJob( IotTaskPoolJob_t * const pJob, +static void _initializeJob( _taskPoolJob_t * const pJob, IotTaskPoolRoutine_t userCallback, void * pUserContext, bool isStatic ) @@ -1203,24 +1302,24 @@ static void _initializeJob( IotTaskPoolJob_t * const pJob, } } -static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache ) +static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache ) { - IotTaskPoolJob_t * pJob = NULL; + _taskPoolJob_t * pJob = NULL; IotLink_t * pLink = IotListDouble_RemoveHead( &( pCache->freeList ) ); if( pLink != NULL ) { - pJob = IotLink_Container( IotTaskPoolJob_t, pLink, link ); + pJob = IotLink_Container( _taskPoolJob_t, pLink, link ); } /* If there is no available job in the cache, then allocate one. */ if( pJob == NULL ) { - pJob = ( IotTaskPoolJob_t * ) IotTaskPool_MallocJob( sizeof( IotTaskPoolJob_t ) ); + pJob = ( _taskPoolJob_t * ) IotTaskPool_MallocJob( sizeof( _taskPoolJob_t ) ); if( pJob != NULL ) { - memset( pJob, 0x00, sizeof( IotTaskPoolJob_t ) ); + memset( pJob, 0x00, sizeof( _taskPoolJob_t ) ); } else { @@ -1241,8 +1340,8 @@ static IotTaskPoolJob_t * _fetchOrAllocateJob( IotTaskPoolCache_t * const pCache /*-----------------------------------------------------------*/ -static void _recycleJob( IotTaskPoolCache_t * const pCache, - IotTaskPoolJob_t * const pJob ) +static void _recycleJob( _taskPoolCache_t * const pCache, + _taskPoolJob_t * const pJob ) { /* We should never try and recycling a job that is linked into some queue. */ IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false ); @@ -1271,7 +1370,7 @@ static void _recycleJob( IotTaskPoolCache_t * const pCache, /*-----------------------------------------------------------*/ -static void _destroyJob( IotTaskPoolJob_t * const pJob ) +static void _destroyJob( _taskPoolJob_t * const pJob ) { /* Destroy user data, for added safety & security. */ pJob->userCallback = NULL; @@ -1289,14 +1388,14 @@ static void _destroyJob( IotTaskPoolJob_t * const pJob ) /* ---------------------------------------------------------------------------------------------- */ -static bool _IsShutdownStarted( const IotTaskPool_t * const pTaskPool ) +static bool _IsShutdownStarted( const _taskPool_t * const pTaskPool ) { return( pTaskPool->maxThreads == 0UL ); } /*-----------------------------------------------------------*/ -static void _signalShutdown( IotTaskPool_t * const pTaskPool, +static void _signalShutdown( _taskPool_t * const pTaskPool, uint32_t threads ) { uint32_t count; @@ -1313,8 +1412,8 @@ static void _signalShutdown( IotTaskPool_t * const pTaskPool, /* ---------------------------------------------------------------------------------------------- */ -static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, + _taskPoolJob_t * const pJob, uint32_t flags ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -1389,6 +1488,8 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, * Put the job at the front, if it is a high priority job. */ if(mustGrow == true ) { + IotLogDebug( "High priority job: placing job at the head of the queue." ); + IotDeQueue_EnqueueHead( &pTaskPool->dispatchQueue, &pJob->link ); } else @@ -1418,7 +1519,7 @@ static IotTaskPoolError_t _scheduleInternal( IotTaskPool_t * const pTaskPool, static bool _matchJobByPointer( const IotLink_t * const pLink, void * pMatch ) { - const IotTaskPoolJob_t * const pJob = ( IotTaskPoolJob_t * ) pMatch; + const _taskPoolJob_t * const pJob = ( _taskPoolJob_t * ) pMatch; const _taskPoolTimerEvent_t * const pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); @@ -1432,8 +1533,8 @@ static bool _matchJobByPointer( const IotLink_t * const pLink, /*-----------------------------------------------------------*/ -static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, + _taskPoolJob_t * const pJob, IotTaskPoolJobStatus_t * const pStatus ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -1537,8 +1638,8 @@ static IotTaskPoolError_t _tryCancelInternal( IotTaskPool_t * const pTaskPool, /*-----------------------------------------------------------*/ -static IotTaskPoolError_t _trySafeExtraction( IotTaskPool_t * const pTaskPool, - IotTaskPoolJob_t * const pJob, +static IotTaskPoolError_t _trySafeExtraction( _taskPool_t * const pTaskPool, + _taskPoolJob_t * const pJob, bool atCompletion ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -1645,7 +1746,7 @@ static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, static void _timerThread( void * pArgument ) { - IotTaskPool_t * pTaskPool = ( IotTaskPool_t * ) pArgument; + _taskPool_t * pTaskPool = ( _taskPool_t * ) pArgument; _taskPoolTimerEvent_t * pTimerEvent = NULL; IotLogDebug( "Timer thread started for task pool %p.", pTaskPool ); @@ -1663,6 +1764,8 @@ static void _timerThread( void * pArgument ) /* Complete the shutdown sequence. */ _destroyTaskPool( pTaskPool ); + + IotTaskPool_FreeTaskPool( pTaskPool ); return; } @@ -1717,5 +1820,3 @@ static void _timerThread( void * pArgument ) } TASKPOOL_EXIT_CRITICAL(); } - -/** @endcond */ diff --git a/lib/source/common/iot_taskpool_static_memory.c b/lib/source/common/iot_taskpool_static_memory.c index 6b799bb4fd..7d0a4a5a7c 100644 --- a/lib/source/common/iot_taskpool_static_memory.c +++ b/lib/source/common/iot_taskpool_static_memory.c @@ -53,11 +53,48 @@ /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool jobs in-use flags. */ -static IotTaskPoolJob_t _pTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; /**< @brief Task pool jobs. */ +static bool _pInUseTaskPools[ IOT_TASKPOOLS ] = { 0 }; /**< @brief Task pools in-use flags. */ +static _taskPool_t _pTaskPools[ IOT_TASKPOOLS ] = { { .dispatchQueue = IOT_DEQUEUE_INITIALIZER } }; /**< @brief Task pools. */ -static bool _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer event in-use flags. */ -static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = { 0 } } }; /**< @brief Task pool timer events. */ +static bool _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool jobs in-use flags. */ +static _taskPoolJob_t _pTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = IOT_LINK_INITIALIZER } }; /**< @brief Task pool jobs. */ + +static bool _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer event in-use flags. */ +static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = { 0 } } }; /**< @brief Task pool timer events. */ + +/*-----------------------------------------------------------*/ + +void * IotTaskPool_MallocTaskPool( size_t size ) +{ + int freeIndex = -1; + void * pNewTaskPool = NULL; + + /* Check size argument. */ + if( size == sizeof( _taskPool_t ) ) + { + /* Find a free task pool job. */ + freeIndex = IotStaticMemory_FindFree( _pInUseTaskPools, IOT_TASKPOOLS ); + + if( freeIndex != -1 ) + { + pNewTaskPool = &( _pTaskPools[ freeIndex ] ); + } + } + + return pNewTaskPool; +} + +/*-----------------------------------------------------------*/ + +void IotTaskPool_FreeTaskPool( void * ptr ) +{ + /* Return the in-use task pool job. */ + IotStaticMemory_ReturnInUse( ptr, + _pTaskPools, + _pInUseTaskPools, + IOT_TASKPOOLS, + sizeof( _taskPool_t ) ); +} /*-----------------------------------------------------------*/ @@ -67,7 +104,7 @@ void * IotTaskPool_MallocJob( size_t size ) void * pNewJob = NULL; /* Check size argument. */ - if( size == sizeof( IotTaskPoolJob_t ) ) + if( size == sizeof( _taskPoolJob_t ) ) { /* Find a free task pool job. */ freeIndex = IotStaticMemory_FindFree( _pInUseTaskPoolJobs, @@ -91,7 +128,7 @@ void IotTaskPool_FreeJob( void * ptr ) _pTaskPoolJobs, _pInUseTaskPoolJobs, IOT_TASKPOOL_JOBS_RECYCLE_LIMIT, - sizeof( IotTaskPoolJob_t ) ); + sizeof( _taskPoolJob_t ) ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 73a3e1e2f1..cf31c12820 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -49,13 +49,13 @@ void _rejectCallback( void * pArgument, /** * Callback routine of _metricsPublishJob. */ -static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pUserContext ); /* Callback routine of _disconnectJob. */ -static void _disconnectRoutine( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void _disconnectRoutine( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pUserContext ); @@ -73,9 +73,11 @@ _defenderMetrics_t _AwsIotDefenderMetrics = */ static uint32_t _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); -static IotTaskPoolJob_t _metricsPublishJob = { 0 }; +static IotTaskPoolJobStorage_t _metricsPublishJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; +static IotTaskPoolJob_t _metricsPublishJob = IOT_TASKPOOL_JOB_INITIALIZER; -static IotTaskPoolJob_t _disconnectJob = { 0 }; +static IotTaskPoolJobStorage_t _disconnectJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; +static IotTaskPoolJob_t _disconnectJob = IOT_TASKPOOL_JOB_INITIALIZER; static IotSemaphore_t _doneSem; @@ -168,15 +170,15 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn if( metricsMutexCreateSuccess ) { /* Create disconnect job. */ - taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJob ); + taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJobStorage, &_disconnectJob ); AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Create metrics job. */ - taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); + taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Schedule metrics job. */ - taskPoolError = IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, 0 ); + taskPoolError = IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, 0 ); AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Everything is good. Declare success. */ @@ -232,7 +234,7 @@ void AwsIotDefender_Stop( void ) IotSemaphore_Wait( &_doneSem ); IotTaskPoolJobStatus_t status; - IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, &status ); + IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, &status ); /* If cancel failed, let it sleep for a while and hope everything finishes. */ if( taskPoolError != IOT_TASKPOOL_SUCCESS ) @@ -339,8 +341,8 @@ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) } /*-----------------------------------------------------------*/ -static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pUserContext ) { /* Unsed parameter; silence the compiler. */ @@ -395,19 +397,17 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, } } } - /* If no MQTT error and report has been created, it indicates everything is good. */ if( ( mqttError == IOT_MQTT_SUCCESS ) && reportCreated ) { - IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJob ); + IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJobStorage, &_disconnectJob ); /* Silence warnigns when asserts are disabled. */ - ( void ) taskPoolError; - + ( void ) taskPoolError; AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - &_disconnectJob, + _disconnectJob, _defenderToMilliseconds( AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS ) ); } else @@ -464,8 +464,8 @@ static void _metricsPublishRoutine( IotTaskPool_t * pTaskPool, /*-----------------------------------------------------------*/ -static void _disconnectRoutine( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void _disconnectRoutine( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pUserContext ) { /* Unsed parameter; silence the compiler. */ @@ -478,13 +478,15 @@ static void _disconnectRoutine( IotTaskPool_t * pTaskPool, AwsIotDefenderInternal_DeleteReport(); AwsIotDefenderInternal_MqttDisconnect(); /* Re-create metrics job. */ - IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJob ); + IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); + /* Silence warnigns when asserts are disabled. */ ( void ) taskPoolError; + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Re-schedule metrics job with period as deferred interval. */ - taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, &_metricsPublishJob, _periodMilliSecond ); + taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, _periodMilliSecond ); AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); IotSemaphore_Post( &_doneSem ); diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 625a086101..3d1658ef0d 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -196,7 +196,7 @@ static void _mqttOperation_tryDestroy( void * pData ) { /* Cancel the incoming PUBLISH operation's job. */ taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - &( pOperation->job ), + pOperation->job, NULL ); /* If the operation's job was not canceled, it must be already executing. @@ -209,7 +209,7 @@ static void _mqttOperation_tryDestroy( void * pData ) { /* Job was canceled. Process incoming PUBLISH now to clean up. */ _IotMqtt_ProcessIncomingPublish( IOT_SYSTEM_TASKPOOL, - &( pOperation->job ), + pOperation->job, pOperation ); } else @@ -287,6 +287,7 @@ static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, /* Create the task pool job that processes keep-alive. */ jobStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, pMqttConnection, + &( pMqttConnection->keepAliveJobStorage ), &( pMqttConnection->keepAliveJob ) ); /* Task pool job creation for a pre-allocated job should never fail. @@ -880,7 +881,7 @@ void IotMqtt_Cleanup( void ) IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, const IotMqttConnectInfo_t * pConnectInfo, uint32_t timeoutMs, - IotMqttConnection_t * pMqttConnection ) + IotMqttConnection_t * const pMqttConnection ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); bool networkCreated = false, ownNetworkConnection = false; @@ -1150,7 +1151,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotLogDebug( "Scheduling first MQTT keep-alive job." ); taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - &( pNewMqttConnection->keepAliveJob ), + pNewMqttConnection->keepAliveJob, pNewMqttConnection->nextKeepAliveMs ); if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 7280616f1a..039e2e24ab 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -775,7 +775,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason /* Attempt to cancel the keep-alive job. */ taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - &( pMqttConnection->keepAliveJob ), + pMqttConnection->keepAliveJob, NULL ); /* If the keep-alive job was not canceled, it must be already executing. diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 887d865dd2..be912e6dd5 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -469,7 +469,7 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, if( cancelJob == true ) { taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - &( pOperation->job ), + pOperation->job, NULL ); /* If the operation's job was not canceled, it must be already executing. @@ -640,8 +640,8 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) /*-----------------------------------------------------------*/ -void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pKeepAliveJob, +void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pKeepAliveJob, void * pContext ) { bool status = true; @@ -653,7 +653,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, /* Check parameters. */ IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); - IotMqtt_Assert( pKeepAliveJob == &( pMqttConnection->keepAliveJob ) ); + IotMqtt_Assert( pKeepAliveJob == pMqttConnection->keepAliveJob ); /* Check that keep-alive interval is valid. The MQTT spec states its maximum * value is 65,535 seconds. */ @@ -668,7 +668,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, /* Re-create the keep-alive job for rescheduling. This should never fail. */ taskPoolStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, pContext, - pKeepAliveJob ); + IotTaskPool_GetJobStorageFromHandle( pKeepAliveJob ), + &pKeepAliveJob ); IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -770,8 +771,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t * pTaskPool, /*-----------------------------------------------------------*/ -void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pPublishJob, +void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pPublishJob, void * pContext ) { _mqttOperation_t * pOperation = pContext; @@ -783,7 +784,7 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, ( void ) pPublishJob; IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pOperation->incomingPublish == true ); - IotMqtt_Assert( pPublishJob == &( pOperation->job ) ); + IotMqtt_Assert( pPublishJob == pOperation->job ); /* Remove the operation from the pending processing list. */ IotMutex_Lock( &( pOperation->pMqttConnection->referencesMutex ) ); @@ -821,8 +822,8 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t * pTaskPool, /*-----------------------------------------------------------*/ -void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pSendJob, +void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pSendJob, void * pContext ) { size_t bytesSent = 0; @@ -835,7 +836,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, ( void ) pTaskPool; ( void ) pSendJob; IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); - IotMqtt_Assert( pSendJob == &( pOperation->job ) ); + IotMqtt_Assert( pSendJob == pOperation->job ); /* The given operation must have an allocated packet and be waiting for a status. */ IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); @@ -1004,8 +1005,8 @@ void _IotMqtt_ProcessSend( IotTaskPool_t * pTaskPool, /*-----------------------------------------------------------*/ -void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pOperationJob, +void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pOperationJob, void * pContext ) { _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; @@ -1016,7 +1017,7 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t * pTaskPool, ( void ) pTaskPool; ( void ) pOperationJob; IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); - IotMqtt_Assert( pOperationJob == &( pOperation->job ) ); + IotMqtt_Assert( pOperationJob == pOperation->job ); /* The operation's callback function and status must be set. */ IotMqtt_Assert( pOperation->u.operation.notify.callback.function != NULL ); @@ -1059,12 +1060,13 @@ IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation, /* Creating a new job should never fail when parameters are valid. */ taskPoolStatus = IotTaskPool_CreateJob( jobRoutine, pOperation, + &( pOperation->jobStorage ), &( pOperation->job ) ); IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); /* Schedule the new job with a delay. */ taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - &( pOperation->job ), + pOperation->job, delay ); if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) @@ -1135,7 +1137,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, if( pResult->u.operation.retry.limit > 0 ) { taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - &( pResult->job ), + pResult->job, NULL ); /* If the retry job could not be canceled, then it is currently @@ -1181,7 +1183,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, if( pResult != NULL ) { - IotLogDebug( "(MQTT connection %p) Found operation %s.", + IotLogDebug( "(MQTT connection %p) Found operation %s." , pMqttConnection, IotMqtt_OperationType( type ) ); } diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index a6762ff7b5..530fbd7a3b 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -57,6 +57,11 @@ typedef struct JobUserContext uint32_t counter; /**< @brief A counter to keep track of callback invokations. */ } JobUserContext_t; +/** + * @brief The initializer for the user context. + */ +#define IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER { { 0 }, 0 }; + /** * @brief A simple user context to prove the taskpool grows as expected. */ @@ -100,7 +105,6 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) { RUN_TEST_CASE( Common_Unit_Task_Pool, Error ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyMaxThreads ); - RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyMaxThreads ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ); RUN_TEST_CASE( Common_Unit_Task_Pool, CreateRecyclableJob ); @@ -216,15 +220,15 @@ static void EmulateWorkLong() /** * @brief A callback that does not recycle its job. */ -static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void ExecutionWithoutDestroyCb( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pContext ) { JobUserContext_t * pUserContext; IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; - TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); @@ -242,15 +246,15 @@ static void ExecutionWithoutDestroyCb( IotTaskPool_t * pTaskPool, /** * @brief A callback that blocks. */ -static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pContext ) { JobBlockingUserContext_t * pUserContext; IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; - TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); @@ -269,15 +273,15 @@ static void ExecutionBlockingWithoutDestroyCb( IotTaskPool_t * pTaskPool, /** * @brief A callback that recycles its job. */ -static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void ExecutionWithRecycleCb( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pContext ) { JobUserContext_t * pUserContext; IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; - TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); @@ -297,15 +301,15 @@ static void ExecutionWithRecycleCb( IotTaskPool_t * pTaskPool, /** * @brief A callback that takes a long time and does not recycle its job. */ -static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void ExecutionLongWithoutDestroyCb( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pContext ) { JobUserContext_t * pUserContext; IotTaskPoolError_t error; IotTaskPoolJobStatus_t status; - TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); @@ -323,8 +327,8 @@ static void ExecutionLongWithoutDestroyCb( IotTaskPool_t * pTaskPool, /** * @brief A callback that does not recycle its job. */ -static void BlankExecution( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void BlankExecution( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pContext ) { IotTaskPoolError_t error; @@ -334,7 +338,7 @@ static void BlankExecution( IotTaskPool_t * pTaskPool, ( void ) pJob; ( void ) pContext; - TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); + //TEST_ASSERT( IotLink_IsLinked( &pJob->link ) == false ); error = IotTaskPool_GetStatus( pTaskPool, pJob, &status ); TEST_ASSERT( ( status == IOT_TASKPOOL_STATUS_COMPLETED ) || ( status == IOT_TASKPOOL_STATUS_UNDEFINED ) ); @@ -422,13 +426,13 @@ TEST( Common_Unit_Task_Pool, Error ) TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) { uint32_t count; - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; /* Some legal and illegal create/destroy patterns. */ for( count = 0; count < LEGAL_INFOS; ++count ) { TEST_ASSERT( IotTaskPool_Create( &tpInfoLegal[ count ], &taskPool ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } for( count = 0; count < ILLEGAL_INFOS; ++count ) @@ -441,35 +445,37 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) /* Create a task pool a tweak max threads up & down. */ { - IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ] = { { 0 } }; + uint32_t count; + IotTaskPoolJobStorage_t jobsStorage[ 2 * TEST_TASKPOOL_MAX_THREADS ]; + IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ]; IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); if( TEST_PROTECT() ) { - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 5 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 3 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 4 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 7 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ - TEST_ASSERT( IotTaskPool_SetMaxThreads( &taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 5 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 3 ) == IOT_TASKPOOL_SUCCESS ); /* lower. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 4 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 7 ) == IOT_TASKPOOL_SUCCESS ); /* higher. */ + TEST_ASSERT( IotTaskPool_SetMaxThreads( taskPool, 1 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* less than min threads. */ /* Initialize more jobs than max threads. */ for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) { /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } /* Schedule all jobs to make the task pool grow. */ for( count = 0; count < 2 * TEST_TASKPOOL_MAX_THREADS; ++count ) { /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } } @@ -480,7 +486,7 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) */ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -489,51 +495,57 @@ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) { /* Trivial parameter validation. */ { + IotTaskPoolJobStorage_t jobStorage; IotTaskPoolJob_t job; /* NULL callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_CreateJob( NULL, NULL, &jobStorage, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + /* NULL storage pointer. */ + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, NULL, &job ) == IOT_TASKPOOL_BAD_PARAMETER ); } /* Create/Destroy. */ { + IotTaskPoolJobStorage_t jobStorage; IotTaskPoolJob_t job; /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); /* Illegally recycle legal static job. */ - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); } /* Create/Destroy. */ { + IotTaskPoolJobStorage_t jobStorage; IotTaskPoolJob_t job; IotTaskPoolJobStatus_t jobStatusAtCancellation; /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); /* Schedule deferred, then try to illegally destroy, then cancel */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, &job, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); - TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, &job, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_ScheduleDeferred( taskPool, job, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + TEST_ASSERT( IotTaskPool_TryCancel( taskPool, job, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); } /* Create/Destroy. */ { + IotTaskPoolJobStorage_t jobStorage; IotTaskPoolJob_t job; /* Create legal static job. */ - TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &BlankExecution, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); /* Schedule immediate, then try to illegally destroy it. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &job, 0 ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, &job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, job, 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, job ) == IOT_TASKPOOL_ILLEGAL_OPERATION ); } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -543,7 +555,7 @@ TEST( Common_Unit_Task_Pool, CreateDestroyJobError ) */ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -552,62 +564,62 @@ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) { /* Trivial parameter validation jobs. */ { - IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; /* NULL callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, NULL, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, NULL, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL engine handle. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( NULL, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL job handle. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, NULL ) == IOT_TASKPOOL_BAD_PARAMETER ); } /* Create/Destroy. */ { - IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); /* Recycle the job. */ - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); } /* Create/Schedule/Destroy. */ { - IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); /* Schedule deferred, then try to destroy it. */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_ScheduleDeferred( taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); } /* Create/Recycle. */ { - IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); /* Illegally recycle legal static job. */ - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); } /* Create/Schedule/Cancel/Recycle. */ { - IotTaskPoolJob_t * pJob = NULL; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; IotTaskPoolJobStatus_t jobStatusAtCancellation; /* Create legal recyclable job. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &BlankExecution, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); /* Schedule deferred, then try to cancel it and finally recycle it. */ - TEST_ASSERT( IotTaskPool_ScheduleDeferred( &taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_TryCancel( &taskPool, pJob, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_ScheduleDeferred( taskPool, pJob, ONE_HOUR_FROM_NOW_MS ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_TryCancel( taskPool, pJob, &jobStatusAtCancellation ) == IOT_TASKPOOL_SUCCESS ); TEST_ASSERT( jobStatusAtCancellation == IOT_TASKPOOL_STATUS_DEFERRED ); - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -617,7 +629,7 @@ TEST( Common_Unit_Task_Pool, CreateDestroyRecycleRecyclableJobError ) */ TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -631,26 +643,26 @@ TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 jobLimit = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + IotTaskPoolJob_t pJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; #else jobLimit = 2 * IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * pJobs[ 2 * TEST_TASKPOOL_ITERATIONS ] = { 0 }; + IotTaskPoolJob_t pJobs[ 2 * TEST_TASKPOOL_ITERATIONS ]; #endif for( count = 0; count < jobLimit; ++count ) { - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, &pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); TEST_ASSERT( pJobs[ count ] != NULL ); } for( count = 0; count < jobLimit; ++count ) { - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -660,24 +672,25 @@ TEST( Common_Unit_Task_Pool, CreateRecyclableJob ) */ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); if( TEST_PROTECT() ) { + IotTaskPoolJobStorage_t jobStorage; IotTaskPoolJob_t job; - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, NULL, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, NULL, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); /* NULL Task Pool Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( NULL, &job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( NULL, job, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -687,13 +700,15 @@ TEST( Common_Unit_Task_Pool, ScheduleTasksError ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) { - IotTaskPool_t taskPool; - IotTaskPoolJob_t * pRecyclableJob; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; + IotTaskPoolJob_t pRecyclableJob = IOT_TASKPOOL_JOB_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ] = { { 0 } }; - IotTaskPoolJob_t tpDeferredJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ] = { { 0 } }; + IotTaskPoolJobStorage_t tpJobsStorage[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; + IotTaskPoolJobStorage_t tpDeferredJobsStorage[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; + IotTaskPoolJob_t tpDeferredJobs[ TEST_TASKPOOL_LONG_JOBS_NUMBER ]; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -701,8 +716,8 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Create a recyclable job we will never schedule, just to have it in the cache for code coverage purposes. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); - TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( &taskPool, pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, &pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_DestroyRecyclableJob( taskPool, pRecyclableJob ) == IOT_TASKPOOL_SUCCESS ); if( TEST_PROTECT() ) { @@ -715,9 +730,9 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -738,9 +753,9 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobsStorage[ count ], &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); + errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); switch( errorSchedule ) { @@ -761,7 +776,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) } /* Destroy the taskpool. It will empty all queues. */ - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -774,26 +789,26 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) */ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); if( TEST_PROTECT() ) { - IotTaskPoolJob_t * pJob; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, NULL, &pJob ) == IOT_TASKPOOL_SUCCESS ); /* NULL Task Pool Handle. */ TEST_ASSERT( IotTaskPool_Schedule( NULL, pJob, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* NULL Work item Handle. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, NULL, 0 ) == IOT_TASKPOOL_BAD_PARAMETER ); /* Recycle the job, so we do not leak it. */ - TEST_ASSERT( IotTaskPool_RecycleJob( &taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_RecycleJob( taskPool, pJob ) == IOT_TASKPOOL_SUCCESS ); } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -803,7 +818,7 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TAKPOOL_NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; JobBlockingUserContext_t userContext; @@ -819,6 +834,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) /* Statically allocated job, schedule one, then wait. */ { uint32_t count; + IotTaskPoolJobStorage_t jobsStorage[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ @@ -826,12 +842,12 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) { - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } count = 0; @@ -857,7 +873,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotSemaphore_Destroy( &userContext.signal ); @@ -871,7 +887,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; /* Use a taskpool with not enough threads. */ const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TASKPOOL_NUMBER_OF_THREADS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; @@ -889,6 +905,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) /* Statically allocated job, schedule one, then wait. */ { uint32_t count; + IotTaskPoolJobStorage_t jobsStorage[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ @@ -896,17 +913,17 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } /* After scheduling _NUMBER_OF_JOBS - 1 jobs the task pool is maxed out, only a high pri task can make it grow more. */ for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_THREADS; ++count ) { - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } /*Schedule a high pri task can make it grow more. */ - TEST_ASSERT( IotTaskPool_Schedule( &taskPool, &jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], IOT_TASKPOOL_JOB_HIGH_PRIORITY ) == IOT_TASKPOOL_SUCCESS ); count = 0; @@ -931,7 +948,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotSemaphore_Destroy( &userContext.signal ); @@ -945,10 +962,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT_TRUE( IotMutex_Create( &userContext.lock, false ) ); @@ -961,14 +978,15 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) { uint32_t count; uint32_t scheduled = 0; - IotTaskPoolJob_t job; + IotTaskPoolJobStorage_t jobStorage; + IotTaskPoolJob_t job = IOT_TASKPOOL_JOB_INITIALIZER; for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &job, 0 ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, job, 0 ); switch( errorSchedule ) { @@ -1019,7 +1037,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1032,10 +1050,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1048,14 +1066,15 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) { uint32_t count; uint32_t scheduled = 0; + IotTaskPoolJobStorage_t jobStorage; IotTaskPoolJob_t job; for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &job ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &job, 10 + ( rand() % 50 ) ); + IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, job, 10 + ( rand() % 50 ) ); switch( errorSchedule ) { @@ -1106,7 +1125,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1119,10 +1138,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1135,14 +1154,14 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) { uint32_t count; uint32_t scheduled = 0; - IotTaskPoolJob_t * pJob; + IotTaskPoolJob_t pJob = IOT_TASKPOOL_JOB_INITIALIZER; for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, pJob, 0 ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, pJob, 0 ); switch( errorSchedule ) { @@ -1189,7 +1208,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1202,10 +1221,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1218,14 +1237,15 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) { uint32_t count; uint32_t scheduled = 0; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; + IotTaskPoolJobStorage_t tpJobsStorage[ TEST_TASKPOOL_ITERATIONS ]; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -1264,7 +1284,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1276,10 +1296,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1296,18 +1316,18 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; #else maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; #endif for( count = 0; count < maxJobs; ++count ) { /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( &taskPool, tpJobs[ count ], 0 ); + IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -1346,7 +1366,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1359,10 +1379,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1379,18 +1399,18 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t * tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; #else maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t * tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { 0 }; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; #endif for( count = 0; count < maxJobs; ++count ) { /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateRecyclableJob( &taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); - IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); + IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); switch( errorSchedule ) { @@ -1429,7 +1449,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1442,10 +1462,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1462,17 +1482,19 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; + IotTaskPoolJobStorage_t tpJobsStorage[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; #else maxJobs = 10; - IotTaskPoolJob_t tpJobs[ 10 ] = { { 0 } }; + IotTaskPoolJobStorage_t tpJobsStorage[ 10 ]; + IotTaskPoolJob_t tpJobs[ 10 ]; #endif /* Create all jobs. */ for( count = 0; count < maxJobs; ++count ) { /* Shedule the job to be recycled in the callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } /* Schedule all jobs. */ @@ -1481,7 +1503,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) IotTaskPoolError_t errorSchedule; /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); switch( errorSchedule ) { @@ -1508,7 +1530,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) { IotTaskPoolError_t errorReSchedule; - errorReSchedule = IotTaskPool_Schedule( &taskPool, &tpJobs[ count ], 0 ); + errorReSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); switch( errorReSchedule ) { @@ -1554,7 +1576,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1567,10 +1589,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) */ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) { - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* Initialize user context. */ TEST_ASSERT( IotMutex_Create( &userContext.lock, false ) ); @@ -1587,10 +1609,12 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; + IotTaskPoolJobStorage_t tpJobsStorage[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; + IotTaskPoolJob_t tpJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; #else maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; + IotTaskPoolJobStorage_t tpJobsStorage[ TEST_TASKPOOL_ITERATIONS ]; + IotTaskPoolJob_t tpJobs[ TEST_TASKPOOL_ITERATIONS ]; #endif /* Schedule all jobs. */ @@ -1599,10 +1623,10 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) IotTaskPoolError_t errorSchedule; /* Shedule the job to be recycle in the callback. */ - TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], ONE_HOUR_FROM_NOW_MS ); + errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], ONE_HOUR_FROM_NOW_MS ); switch( errorSchedule ) { @@ -1625,7 +1649,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) { IotTaskPoolError_t errorReSchedule; - errorReSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &tpJobs[ count ], 10 + ( rand() % 500 ) ); + errorReSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); switch( errorReSchedule ) { @@ -1667,7 +1691,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) } } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); @@ -1681,20 +1705,22 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) { uint32_t count, maxJobs; - IotTaskPool_t taskPool; + IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; uint32_t canceled = 0; uint32_t scheduled = 0; - JobUserContext_t userContext = { 0 }; + JobUserContext_t userContext = IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER; /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 maxJobs = IOT_TASKPOOL_JOBS_RECYCLE_LIMIT; - IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { 0 } }; + IotTaskPoolJobStorage_t jobsStorage[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; + IotTaskPoolJob_t jobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ]; #else maxJobs = TEST_TASKPOOL_ITERATIONS; - IotTaskPoolJob_t jobs[ TEST_TASKPOOL_ITERATIONS ] = { { 0 } }; + IotTaskPoolJobStorage_t jobsStorage[ TEST_TASKPOOL_ITERATIONS ]; + IotTaskPoolJob_t jobs[ TEST_TASKPOOL_ITERATIONS ]; #endif /* Initialize user context. */ @@ -1709,7 +1735,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) { IotTaskPoolError_t errorSchedule; - IotTaskPoolError_t errorCreate = IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobs[ count ] ); + IotTaskPoolError_t errorCreate = IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ); switch( errorCreate ) { @@ -1727,7 +1753,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) TEST_ASSERT( false ); } - errorSchedule = IotTaskPool_ScheduleDeferred( &taskPool, &jobs[ count ], 10 + ( rand() % 20 ) ); + errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, jobs[ count ], 10 + ( rand() % 20 ) ); switch( errorSchedule ) { @@ -1753,7 +1779,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) IotTaskPoolJobStatus_t statusAtCancellation = IOT_TASKPOOL_STATUS_READY; IotTaskPoolJobStatus_t statusAfterCancellation = IOT_TASKPOOL_STATUS_READY; - error = IotTaskPool_TryCancel( &taskPool, &jobs[ count ], &statusAtCancellation ); + error = IotTaskPool_TryCancel( taskPool, jobs[ count ], &statusAtCancellation ); switch( error ) { @@ -1767,13 +1793,13 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) ( statusAtCancellation == IOT_TASKPOOL_STATUS_CANCELED ) ); - TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_GetStatus( taskPool, jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); TEST_ASSERT( statusAfterCancellation == IOT_TASKPOOL_STATUS_CANCELED ); break; case IOT_TASKPOOL_CANCEL_FAILED: TEST_ASSERT( ( statusAtCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); - TEST_ASSERT( IotTaskPool_GetStatus( &taskPool, &jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_GetStatus( taskPool, jobs[ count ], &statusAfterCancellation ) == IOT_TASKPOOL_SUCCESS ); TEST_ASSERT( ( statusAfterCancellation == IOT_TASKPOOL_STATUS_COMPLETED ) ); break; @@ -1797,7 +1823,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_CancelTasks ) TEST_ASSERT( ( scheduled - canceled ) == userContext.counter ); } - TEST_ASSERT( IotTaskPool_Destroy( &taskPool ) == IOT_TASKPOOL_SUCCESS ); + TEST_ASSERT( IotTaskPool_Destroy( taskPool ) == IOT_TASKPOOL_SUCCESS ); /* Destroy user context. */ IotMutex_Destroy( &userContext.lock ); diff --git a/tests/iot_config.h b/tests/iot_config.h index 319d62912b..3254622c54 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -89,6 +89,7 @@ #define IOT_MQTT_CONNECTIONS ( 2 ) #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) + #define IOT_TASKPOOLS ( 4 ) #endif /* Memory allocation function configuration. Note that these functions will not @@ -106,6 +107,8 @@ /* Memory allocation function configuration for libraries affected by * IOT_STATIC_MEMORY_ONLY. */ #if IOT_STATIC_MEMORY_ONLY == 0 + #define IotTaskPool_MallocTaskPool unity_malloc_mt + #define IotTaskPool_FreeTaskPool unity_free_mt #define IotTaskPool_MallocJob unity_malloc_mt #define IotTaskPool_FreeJob unity_free_mt #define IotTaskPool_MallocTimerEvent unity_malloc_mt diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 80364936c4..1b5f7fce95 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -482,8 +482,8 @@ static void _disconnectCallback( void * pCallbackContext, * @brief A task pool job routine that decrements an MQTT operation's job * reference count. */ -static void _decrementReferencesJob( IotTaskPool_t * pTaskPool, - IotTaskPoolJob_t * pJob, +static void _decrementReferencesJob( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, void * pContext ) { _mqttOperation_t * pOperation = ( _mqttOperation_t * ) pContext; @@ -606,9 +606,10 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) /* Schedule a job that destroys the operation. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateJob( _decrementReferencesJob, pOperation, + &( pOperation->jobStorage ), &( pOperation->job ) ) ); TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, - &( pOperation->job ), + pOperation->job, 0 ) ); /* Wait for the job to complete. */ @@ -1385,7 +1386,7 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - &( _pMqttConnection->keepAliveJob ), + _pMqttConnection->keepAliveJob, _pMqttConnection->nextKeepAliveMs ) ); /* Sleep to allow ample time for periodic PINGREQ sends and PINGRESP responses. */ @@ -1436,7 +1437,7 @@ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - &( _pMqttConnection->keepAliveJob ), + _pMqttConnection->keepAliveJob, _pMqttConnection->nextKeepAliveMs ) ); /* Wait for the keep-alive job to send a PINGREQ. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 1979069c4e..c1a7cc0069 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -116,7 +116,8 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; */ #define INITIALIZE_OPERATION( name ) \ { \ - .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, .job = { 0 }, \ + .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ + .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ .u.operation = \ { \ .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ From f6b02e59c919ee6969fe3af0c7687ad90ded352f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 9 May 2019 08:52:19 -0700 Subject: [PATCH 129/844] Organize libraries in CMakeLists (#412) --- CMakeLists.txt | 10 +- demos/app/posix/CMakeLists.txt | 4 +- lib/source/common/CMakeLists.txt | 39 ++++++-- lib/source/defender/CMakeLists.txt | 23 +++-- lib/source/mqtt/CMakeLists.txt | 31 +++++-- lib/source/serializer/CMakeLists.txt | 24 +++-- lib/source/shadow/CMakeLists.txt | 27 ++++-- tests/CMakeLists.txt | 5 - tests/common/CMakeLists.txt | 16 +++- tests/defender/CMakeLists.txt | 13 ++- .../aws_iot_tests_defender_system.c} | 0 tests/mqtt/CMakeLists.txt | 27 ++++-- tests/serializer/CMakeLists.txt | 18 +++- tests/serializer/iot_tests_serializer.c | 39 ++++---- .../iot_tests_serializer_cbor.c} | 51 +++++----- tests/shadow/CMakeLists.txt | 21 ++++- third_party/mbedtls/CMakeLists.txt | 93 +++++++++++-------- third_party/mbedtls/iot_config_mbedtls.h | 1 + third_party/tinycbor/CMakeLists.txt | 26 ++++-- third_party/unity/CMakeLists.txt | 25 ++++- 20 files changed, 333 insertions(+), 160 deletions(-) rename tests/defender/{aws_iot_tests_defender_api.c => system/aws_iot_tests_defender_system.c} (100%) rename tests/serializer/{iot_test_serializer_cbor.c => unit/iot_tests_serializer_cbor.c} (90%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f137f1622d..b222fde17c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,12 @@ # Project information. cmake_minimum_required( VERSION 3.5.0 ) -project( AwsIotDeviceSdkC +project( AwsIotDeviceSdkEmbeddedC VERSION 4.0.0 LANGUAGES C ) +# Allow the project to be organized into folders. +set_property( GLOBAL PROPERTY USE_FOLDERS ON) + # Use C99. set( CMAKE_C_STANDARD 99 ) set( CMAKE_C_STANDARD_REQUIRED ON ) @@ -28,7 +31,7 @@ option( IOT_BUILD_CLONE_SUBMODULES ON ) # Check for system support. -list( APPEND SUPPORTED_SYSTEMS "Linux" ) +list( APPEND SUPPORTED_SYSTEMS "Linux" "Windows" ) if( NOT ${CMAKE_SYSTEM_NAME} IN_LIST SUPPORTED_SYSTEMS ) message( FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}. Currently supported systems are: ${SUPPORTED_SYSTEMS}." ) @@ -78,6 +81,9 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) add_subdirectory( platform/source/posix ) +elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + add_definitions( -DIOT_SYSTEM_TYPES_FILE="windows/iot_platform_types_windows.h" ) + add_subdirectory( platform/source/windows ) endif() # Common libraries (linear containers, logging, etc.) diff --git a/demos/app/posix/CMakeLists.txt b/demos/app/posix/CMakeLists.txt index 27023ebe74..f8412ed6f0 100644 --- a/demos/app/posix/CMakeLists.txt +++ b/demos/app/posix/CMakeLists.txt @@ -1,5 +1,7 @@ # Common demo files. -set( DEMO_COMMON_SOURCE_FILES "iot_demo_arguments_posix.c;iot_demo_posix.c" ) +set( DEMO_COMMON_SOURCE_FILES + iot_demo_arguments_posix.c + iot_demo_posix.c ) # MQTT demo source files. add_executable( iot_demo_mqtt diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 2ffabf27e5..d677300c1b 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,13 +1,29 @@ # Common libraries source files. -add_library( iotcommon - iot_init.c - iot_logging.c - iot_static_memory_common.c - iot_taskpool.c - iot_taskpool_static_memory.c ) +set( COMMON_SOURCES + iot_init.c + iot_logging.c + iot_static_memory_common.c + iot_taskpool.c + iot_taskpool_static_memory.c ) + +# Lists of common header files. These are only used for directory organization +# (not for build). +set( COMMON_PUBLIC_HEADERS + ${PROJECT_SOURCE_DIR}/lib/include/iot_init.h + ${PROJECT_SOURCE_DIR}/lib/include/iot_linear_containers.h + ${PROJECT_SOURCE_DIR}/lib/include/iot_logging_setup.h + ${PROJECT_SOURCE_DIR}/lib/include/iot_taskpool.h ) +set( COMMON_PRIVATE_HEADERS + ${PROJECT_SOURCE_DIR}/lib/include/private/iot_error.h + ${PROJECT_SOURCE_DIR}/lib/include/private/iot_logging.h + ${PROJECT_SOURCE_DIR}/lib/include/private/iot_static_memory.h + ${PROJECT_SOURCE_DIR}/lib/include/private/iot_taskpool_internal.h ) +set( COMMON_TYPES_HEADERS + ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h ) -# Library version. -set_target_properties( iotcommon PROPERTIES VERSION ${PROJECT_VERSION} ) +# Common library target. +add_library( iotcommon + ${COMMON_SOURCES} ${COMMON_PUBLIC_HEADERS} ${COMMON_PRIVATE_HEADERS} ${COMMON_TYPES_HEADERS} ) # Link required libraries. target_link_libraries( iotcommon iotplatform ) @@ -16,3 +32,10 @@ target_link_libraries( iotcommon iotplatform ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotcommon unity ) endif() + +# Organization of common libraries in folders. +set_property( TARGET iotcommon PROPERTY FOLDER "lib" ) +source_group( include FILES ${COMMON_PUBLIC_HEADERS} ) +source_group( include\\private FILES ${COMMON_PRIVATE_HEADERS} ) +source_group( include\\types FILES ${COMMON_TYPES_HEADERS} ) +source_group( source FILES ${COMMON_SOURCES} ) diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt index b2cb2e7c85..3fd5d8d2f1 100644 --- a/lib/source/defender/CMakeLists.txt +++ b/lib/source/defender/CMakeLists.txt @@ -1,11 +1,14 @@ -# Serializer library source files. -add_library( awsiotdefender - aws_iot_defender_api.c - aws_iot_defender_collector.c - aws_iot_defender_mqtt.c ) +# Defender library source files. +set( DEFENDER_SOURCES + aws_iot_defender_api.c + aws_iot_defender_collector.c + aws_iot_defender_mqtt.c ) -# Library version. -set_target_properties( awsiotdefender PROPERTIES VERSION ${PROJECT_VERSION} ) +# Defender library target. +add_library( awsiotdefender + ${DEFENDER_SOURCES} + ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_defender.h + ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_defender_internal.h ) # Link required libraries. target_link_libraries( awsiotdefender iotserializer iotmqtt iotplatform ) @@ -14,3 +17,9 @@ target_link_libraries( awsiotdefender iotserializer iotmqtt iotplatform ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( awsiotdefender unity ) endif() + +# Organization of Defender in folders. +set_property( TARGET awsiotdefender PROPERTY FOLDER "lib" ) +source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_defender.h ) +source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_defender_internal.h ) +source_group( source FILES ${DEFENDER_SOURCES} ) diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index 73616afb14..c6d59cdb53 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -1,15 +1,19 @@ # MQTT library source files. -add_library( iotmqtt - iot_mqtt_api.c - iot_mqtt_network.c - iot_mqtt_operation.c - iot_mqtt_serialize.c - iot_mqtt_static_memory.c - iot_mqtt_subscription.c - iot_mqtt_validate.c ) +set( MQTT_SOURCES + iot_mqtt_api.c + iot_mqtt_network.c + iot_mqtt_operation.c + iot_mqtt_serialize.c + iot_mqtt_static_memory.c + iot_mqtt_subscription.c + iot_mqtt_validate.c ) -# Library version. -set_target_properties( iotmqtt PROPERTIES VERSION ${PROJECT_VERSION} ) +# MQTT library target. +add_library( iotmqtt + ${MQTT_SOURCES} + ${PROJECT_SOURCE_DIR}/lib/include/iot_mqtt.h + ${PROJECT_SOURCE_DIR}/lib/include/types/iot_mqtt_types.h + ${PROJECT_SOURCE_DIR}/lib/include/private/iot_mqtt_internal.h ) # Link required libraries. target_link_libraries( iotmqtt iotcommon iotplatform ) @@ -18,3 +22,10 @@ target_link_libraries( iotmqtt iotcommon iotplatform ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotmqtt unity ) endif() + +# Organization of MQTT in folders. +set_property( TARGET iotmqtt PROPERTY FOLDER "lib" ) +source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/iot_mqtt.h ) +source_group( include\\types FILES ${PROJECT_SOURCE_DIR}/lib/include/types/iot_mqtt_types.h ) +source_group( include\\private FILES ${PROJECT_SOURCE_DIR}/lib/include/private/iot_mqtt_internal.h ) +source_group( source FILES ${MQTT_SOURCES} ) diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index 829d030b34..27fbb057d2 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -1,12 +1,15 @@ # Serializer library source files. -add_library( iotserializer - iot_json_utils.c - iot_serializer_static_memory.c - cbor/iot_serializer_tinycbor_decoder.c - cbor/iot_serializer_tinycbor_encoder.c ) +set( SERIALIZER_SOURCES + iot_json_utils.c + iot_serializer_static_memory.c + cbor/iot_serializer_tinycbor_decoder.c + cbor/iot_serializer_tinycbor_encoder.c ) -# Library version. -set_target_properties( iotserializer PROPERTIES VERSION ${PROJECT_VERSION} ) +# Serializer library target. +add_library( iotserializer + ${SERIALIZER_SOURCES} + ${PROJECT_SOURCE_DIR}/lib/include/iot_serializer.h + ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) # Link required libraries. target_link_libraries( iotserializer tinycbor ) @@ -15,3 +18,10 @@ target_link_libraries( iotserializer tinycbor ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotserializer unity ) endif() + +# Organization of Serializer in folders. +set_property( TARGET iotserializer PROPERTY FOLDER "lib" ) +source_group( include FILES + ${PROJECT_SOURCE_DIR}/lib/include/iot_serializer.h + ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) +source_group( source FILES ${SERIALIZER_SOURCES} ) diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index 15799c9f59..a3fec4979a 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -1,13 +1,17 @@ # Shadow library source files. -add_library( awsiotshadow - aws_iot_shadow_api.c - aws_iot_shadow_operation.c - aws_iot_shadow_parser.c - aws_iot_shadow_static_memory.c - aws_iot_shadow_subscription.c ) +set( SHADOW_SOURCES + aws_iot_shadow_api.c + aws_iot_shadow_operation.c + aws_iot_shadow_parser.c + aws_iot_shadow_static_memory.c + aws_iot_shadow_subscription.c ) -# Library version. -set_target_properties( awsiotshadow PROPERTIES VERSION ${PROJECT_VERSION} ) +# Shadow library target. +add_library( awsiotshadow + ${SHADOW_SOURCES} + ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_shadow.h + ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_shadow_types.h + ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_shadow_internal.h ) # Link required libraries. target_link_libraries( awsiotshadow iotserializer iotplatform iotmqtt ) @@ -16,3 +20,10 @@ target_link_libraries( awsiotshadow iotserializer iotplatform iotmqtt ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( awsiotshadow unity ) endif() + +# Organization of Shadow in folders. +set_property( TARGET awsiotshadow PROPERTY FOLDER "lib" ) +source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_shadow.h ) +source_group( include\\types ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_shadow_types.h ) +source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_shadow_internal.h ) +source_group( source FILES ${SHADOW_SOURCES} ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bc94d35435..9c4746a3a8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,3 @@ -# Tests are currently only supported on Linux. -if( NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - message( FATAL_ERROR "Currently, tests must run on Linux. Detected system: ${CMAKE_SYSTEM_NAME}." ) -endif() - # Common tests. add_subdirectory( common ) diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 6496f2967a..580996bb67 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -1,9 +1,19 @@ +# Common unit test sources. +set( COMMON_UNIT_TEST_SOURCES + unit/iot_tests_linear_containers.c + unit/iot_tests_taskpool.c + unit/iot_tests_atomic.c ) + # Common tests executable. add_executable( iot_tests_common + ${COMMON_UNIT_TEST_SOURCES} iot_tests_common.c - unit/iot_tests_linear_containers.c - unit/iot_tests_taskpool.c - unit/iot_tests_atomic.c ) + ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Common tests library dependencies. target_link_libraries( iot_tests_common unity iotcommon ) + +# Organization of common tests in folders. +set_property( TARGET iot_tests_common PROPERTY FOLDER "tests" ) +source_group( unit FILES ${COMMON_UNIT_TEST_SOURCES} ) +source_group( "" FILES iot_tests_common.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt index de3763f12b..0891e58c38 100644 --- a/tests/defender/CMakeLists.txt +++ b/tests/defender/CMakeLists.txt @@ -1,10 +1,17 @@ +# Defender system test sources. +set( DEFENDER_SYSTEM_TEST_SOURCES + system/aws_iot_tests_defender_system.c ) + # Defender tests executable. add_executable( aws_iot_tests_defender + ${DEFENDER_SYSTEM_TEST_SOURCES} aws_iot_tests_defender.c - aws_iot_tests_defender_api.c ) + ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Defender tests library dependencies. target_link_libraries( aws_iot_tests_defender iotcommon iotplatform iotmqtt awsiotdefender unity ) -# Not produce warning on missing braces case. -target_compile_options( aws_iot_tests_defender PRIVATE "-Wno-missing-braces") \ No newline at end of file +# Organization of Defender tests in folders. +set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER "tests" ) +source_group( system FILES ${DEFENDER_SYSTEM_TEST_SOURCES} ) +source_group( "" FILES aws_iot_tests_defender.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) diff --git a/tests/defender/aws_iot_tests_defender_api.c b/tests/defender/system/aws_iot_tests_defender_system.c similarity index 100% rename from tests/defender/aws_iot_tests_defender_api.c rename to tests/defender/system/aws_iot_tests_defender_system.c diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index 4252bb1dd6..5603e8e26e 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -1,12 +1,27 @@ +# MQTT system test sources. +set( MQTT_SYSTEM_TEST_SOURCES + system/iot_tests_mqtt_system.c + system/iot_tests_mqtt_stress.c ) + +# MQTT unit test sources. +set( MQTT_UNIT_TEST_SOURCES + unit/iot_tests_mqtt_api.c + unit/iot_tests_mqtt_receive.c + unit/iot_tests_mqtt_subscription.c + unit/iot_tests_mqtt_validate.c ) + # MQTT tests executable. add_executable( iot_tests_mqtt + ${MQTT_SYSTEM_TEST_SOURCES} + ${MQTT_UNIT_TEST_SOURCES} iot_tests_mqtt.c - unit/iot_tests_mqtt_api.c - unit/iot_tests_mqtt_receive.c - unit/iot_tests_mqtt_subscription.c - unit/iot_tests_mqtt_validate.c - system/iot_tests_mqtt_system.c - system/iot_tests_mqtt_stress.c ) + ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # MQTT tests library dependencies. target_link_libraries( iot_tests_mqtt iotcommon iotplatform iotmqtt unity pthread ) + +# Organization of MQTT tests in folders. +set_property( TARGET iot_tests_mqtt PROPERTY FOLDER "tests" ) +source_group( system FILES ${MQTT_SYSTEM_TEST_SOURCES} ) +source_group( unit FILES ${MQTT_UNIT_TEST_SOURCES} ) +source_group( "" FILES iot_tests_mqtt.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt index 50b1126602..82586b2181 100644 --- a/tests/serializer/CMakeLists.txt +++ b/tests/serializer/CMakeLists.txt @@ -1,7 +1,17 @@ -# Shadow tests executable. +# Serializer unit test sources. +set( SERIALIZER_UNIT_TEST_SOURCES + unit/iot_tests_serializer_cbor.c ) + +# Serializer tests executable. add_executable( iot_tests_serializer + ${SERIALIZER_UNIT_TEST_SOURCES} iot_tests_serializer.c - iot_test_serializer_cbor.c ) + ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) + +# Serializer tests library dependencies. +target_link_libraries( iot_tests_serializer iotcommon iotplatform iotserializer unity ) -# Shadow tests library dependencies. -target_link_libraries( iot_tests_serializer iotcommon iotplatform iotserializer unity ) \ No newline at end of file +# Organization of Serializer tests in folders. +set_property( TARGET iot_tests_serializer PROPERTY FOLDER "tests" ) +source_group( unit FILES ${SERIALIZER_UNIT_TEST_SOURCES} ) +source_group( "" FILES iot_tests_serializer.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) diff --git a/tests/serializer/iot_tests_serializer.c b/tests/serializer/iot_tests_serializer.c index b26c258e06..07749bcfde 100644 --- a/tests/serializer/iot_tests_serializer.c +++ b/tests/serializer/iot_tests_serializer.c @@ -19,6 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/** + * @file iot_tests_serializer.c + * @brief Test runner for the Serializer tests on POSIX systems. + */ + /* Standard includes. */ #include #include @@ -28,8 +33,8 @@ /* POSIX includes. */ #include -/* SDK initialization include. */ -#include "iot_init.h" +/* Error handling include. */ +#include "private/iot_error.h" /* Test framework includes. */ #include "unity_fixture.h" @@ -45,12 +50,12 @@ static void _signalHandler( int signum ) if( signum == SIGSEGV ) { printf( "\nSegmentation fault.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } else if( signum == SIGABRT ) { printf( "\nAssertion failed.\n" ); - exit( EXIT_FAILURE ); + _Exit( EXIT_FAILURE ); } } @@ -59,11 +64,12 @@ static void _signalHandler( int signum ) int main( int argc, char ** argv ) { + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); struct sigaction signalAction; /* Silence warnings about unused parameters. */ - ( void )argc; - ( void )argv; + ( void ) argc; + ( void ) argv; /* Set a signal handler for segmentation faults and assertion failures. */ ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); @@ -71,18 +77,12 @@ int main( int argc, if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) { - return EXIT_FAILURE; - } - - /* Initialize the common libraries before running the tests. */ - if( IotSdk_Init() == false ) - { - return EXIT_FAILURE; + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } /* Unity setup. */ @@ -92,19 +92,16 @@ int main( int argc, UnityFixture.GroupFilter = NULL; UNITY_BEGIN(); - /* Run short tests. */ - RUN_TEST_GROUP( Full_Serializer_CBOR ); - - /* Clean up common libraries. */ - IotSdk_Cleanup(); + /* Run unit tests. */ + RUN_TEST_GROUP( Serializer_Unit_CBOR ); /* Return failure if any tests failed. */ if( UNITY_END() != 0 ) { - return EXIT_FAILURE; + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - return EXIT_SUCCESS; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/serializer/iot_test_serializer_cbor.c b/tests/serializer/unit/iot_tests_serializer_cbor.c similarity index 90% rename from tests/serializer/iot_test_serializer_cbor.c rename to tests/serializer/unit/iot_tests_serializer_cbor.c index 4173e53215..5c8082f950 100644 --- a/tests/serializer/iot_test_serializer_cbor.c +++ b/tests/serializer/unit/iot_tests_serializer_cbor.c @@ -30,6 +30,9 @@ #include #include +/* SDK initialization include. */ +#include "iot_init.h" + /* Unity framework includes. */ #include "unity_fixture.h" #include "unity.h" @@ -47,10 +50,12 @@ static IotSerializerEncoderObject_t _encoderObject; static uint8_t _buffer[ BUFFER_SIZE ]; -TEST_GROUP( Full_Serializer_CBOR ); +TEST_GROUP( Serializer_Unit_CBOR ); -TEST_SETUP( Full_Serializer_CBOR ) +TEST_SETUP( Serializer_Unit_CBOR ) { + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + /* Reset buffer to zero. */ memset( _buffer, 0, BUFFER_SIZE ); @@ -59,35 +64,37 @@ TEST_SETUP( Full_Serializer_CBOR ) _encoder.init( &_encoderObject, _buffer, BUFFER_SIZE ) ); } -TEST_TEAR_DOWN( Full_Serializer_CBOR ) +TEST_TEAR_DOWN( Serializer_Unit_CBOR ) { /* Destroy encoder object. */ _encoder.destroy( &_encoderObject ); TEST_ASSERT_NULL( _encoderObject.pHandle ); + + IotSdk_Cleanup(); } /* TODO: * - append NULL * - append bool */ -TEST_GROUP_RUNNER( Full_Serializer_CBOR ) +TEST_GROUP_RUNNER( Serializer_Unit_CBOR ) { - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_init_with_null_buffer ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_append_integer ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_append_text_string ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_append_byte_string ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_append_integer ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_append_text_string ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_append_byte_string ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_open_a_scalar ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_open_map ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_open_array ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_open_a_scalar ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_open_map ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_open_array ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_map_nest_map ); - RUN_TEST_CASE( Full_Serializer_CBOR, Encoder_map_nest_array ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_map_nest_map ); + RUN_TEST_CASE( Serializer_Unit_CBOR, Encoder_map_nest_array ); } -TEST( Full_Serializer_CBOR, Encoder_init_with_null_buffer ) +TEST( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ) { IotSerializerEncoderObject_t encoderObject = { .type = ( IotSerializerDataType_t ) 0 }; @@ -111,7 +118,7 @@ TEST( Full_Serializer_CBOR, Encoder_init_with_null_buffer ) TEST_ASSERT_NULL( encoderObject.pHandle ); } -TEST( Full_Serializer_CBOR, Encoder_append_integer ) +TEST( Serializer_Unit_CBOR, Encoder_append_integer ) { int64_t value = 6; @@ -137,7 +144,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_integer ) TEST_ASSERT_EQUAL( 1, _encoder.getEncodedSize( &_encoderObject, _buffer ) ); } -TEST( Full_Serializer_CBOR, Encoder_append_text_string ) +TEST( Serializer_Unit_CBOR, Encoder_append_text_string ) { char * str = "hello world"; @@ -161,7 +168,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_text_string ) TEST_ASSERT_TRUE( equal ); } -TEST( Full_Serializer_CBOR, Encoder_append_byte_string ) +TEST( Serializer_Unit_CBOR, Encoder_append_byte_string ) { uint8_t inputBytes[] = "hello world"; size_t inputLength = strlen( ( const char * ) inputBytes ); @@ -190,7 +197,7 @@ TEST( Full_Serializer_CBOR, Encoder_append_byte_string ) TEST_ASSERT_EQUAL( 0, strcmp( ( const char * ) inputBytes, ( const char * ) outputBytes ) ); } -TEST( Full_Serializer_CBOR, Encoder_open_a_scalar ) +TEST( Serializer_Unit_CBOR, Encoder_open_a_scalar ) { IotSerializerEncoderObject_t integerObject = { .pHandle = NULL, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT }; @@ -198,7 +205,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_a_scalar ) _encoder.openContainer( &_encoderObject, &integerObject, 1 ) ); } -TEST( Full_Serializer_CBOR, Encoder_open_map ) +TEST( Serializer_Unit_CBOR, Encoder_open_map ) { IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; @@ -233,7 +240,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_map ) TEST_ASSERT_TRUE( equal ); } -TEST( Full_Serializer_CBOR, Encoder_open_array ) +TEST( Serializer_Unit_CBOR, Encoder_open_array ) { uint8_t i = 0; IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; @@ -278,7 +285,7 @@ TEST( Full_Serializer_CBOR, Encoder_open_array ) TEST_ASSERT_TRUE( cbor_value_at_end( &arrayValue ) ); } -TEST( Full_Serializer_CBOR, Encoder_map_nest_map ) +TEST( Serializer_Unit_CBOR, Encoder_map_nest_map ) { IotSerializerEncoderObject_t mapObject_1 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; IotSerializerEncoderObject_t mapObject_2 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; @@ -325,7 +332,7 @@ TEST( Full_Serializer_CBOR, Encoder_map_nest_map ) TEST_ASSERT_TRUE( equal ); } -TEST( Full_Serializer_CBOR, Encoder_map_nest_array ) +TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) { uint8_t i = 0; IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index 9eecb847d9..6333db76b7 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -1,9 +1,24 @@ +# Shadow system test sources. +set( SHADOW_SYSTEM_TEST_SOURCES + system/aws_iot_tests_shadow_system.c ) + +# Shadow unit test sources. +set( SHADOW_UNIT_TEST_SOURCES + unit/aws_iot_tests_shadow_api.c + unit/aws_iot_tests_shadow_parser.c ) + # Shadow tests executable. add_executable( aws_iot_tests_shadow + ${SHADOW_SYSTEM_TEST_SOURCES} + ${SHADOW_UNIT_TEST_SOURCES} aws_iot_tests_shadow.c - unit/aws_iot_tests_shadow_api.c - unit/aws_iot_tests_shadow_parser.c - system/aws_iot_tests_shadow_system.c ) + ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Shadow tests library dependencies. target_link_libraries( aws_iot_tests_shadow iotcommon iotplatform iotmqtt awsiotshadow unity ) + +# Organization of Shadow tests in folders. +set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER "tests" ) +source_group( system FILES ${SHADOW_SYSTEM_TEST_SOURCES} ) +source_group( unit FILES ${SHADOW_UNIT_TEST_SOURCES} ) +source_group( "" FILES aws_iot_tests_shadow.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index a83be2b72c..6eb0d2ed93 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -18,45 +18,54 @@ if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/library/ ) endif() # mbed TLS source files. +set( MBEDTLS_SOURCES + iot_mbedtls_threading.c + mbedtls/library/aes.c + mbedtls/library/aesni.c + mbedtls/library/asn1parse.c + mbedtls/library/asn1write.c + mbedtls/library/base64.c + mbedtls/library/bignum.c + mbedtls/library/ecdh.c + mbedtls/library/ecdsa.c + mbedtls/library/ecp.c + mbedtls/library/ecp_curves.c + mbedtls/library/entropy.c + mbedtls/library/entropy_poll.c + mbedtls/library/error.c + mbedtls/library/cipher.c + mbedtls/library/cipher_wrap.c + mbedtls/library/ctr_drbg.c + mbedtls/library/hmac_drbg.c + mbedtls/library/md.c + mbedtls/library/md_wrap.c + mbedtls/library/net_sockets.c + mbedtls/library/oid.c + mbedtls/library/pem.c + mbedtls/library/pk.c + mbedtls/library/pkparse.c + mbedtls/library/pk_wrap.c + mbedtls/library/platform.c + mbedtls/library/platform_util.c + mbedtls/library/rsa.c + mbedtls/library/rsa_internal.c + mbedtls/library/sha256.c + mbedtls/library/sha512.c + mbedtls/library/ssl_ciphersuites.c + mbedtls/library/ssl_cli.c + mbedtls/library/ssl_tls.c + mbedtls/library/timing.c + mbedtls/library/threading.c + mbedtls/library/x509.c + mbedtls/library/x509_crt.c ) + +# mbed TLS headers (for folder organization only). +file( GLOB MBEDTLS_HEADERS "mbedtls/include/mbedtls/*.h" ) + +# mbed TLS library target. add_library( mbedtls - iot_mbedtls_threading.c - mbedtls/library/aes.c - mbedtls/library/aesni.c - mbedtls/library/asn1parse.c - mbedtls/library/asn1write.c - mbedtls/library/base64.c - mbedtls/library/bignum.c - mbedtls/library/ecdh.c - mbedtls/library/ecdsa.c - mbedtls/library/ecp.c - mbedtls/library/ecp_curves.c - mbedtls/library/entropy.c - mbedtls/library/entropy_poll.c - mbedtls/library/error.c - mbedtls/library/cipher.c - mbedtls/library/cipher_wrap.c - mbedtls/library/ctr_drbg.c - mbedtls/library/hmac_drbg.c - mbedtls/library/md.c - mbedtls/library/md_wrap.c - mbedtls/library/net_sockets.c - mbedtls/library/oid.c - mbedtls/library/pem.c - mbedtls/library/pk.c - mbedtls/library/pkparse.c - mbedtls/library/pk_wrap.c - mbedtls/library/platform_util.c - mbedtls/library/rsa.c - mbedtls/library/rsa_internal.c - mbedtls/library/sha256.c - mbedtls/library/sha512.c - mbedtls/library/ssl_ciphersuites.c - mbedtls/library/ssl_cli.c - mbedtls/library/ssl_tls.c - mbedtls/library/timing.c - mbedtls/library/threading.c - mbedtls/library/x509.c - mbedtls/library/x509_crt.c ) + ${MBEDTLS_SOURCES} ${MBEDTLS_HEADERS} + iot_config_mbedtls.h threading_alt.h ) # mbed TLS config header and include directories. target_include_directories( mbedtls SYSTEM @@ -65,7 +74,7 @@ target_include_directories( mbedtls SYSTEM target_compile_definitions( mbedtls PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) -# Link the unity test framework when testing. +# Link the Unity test framework when testing. if( ${IOT_BUILD_TESTS} ) target_link_libraries( mbedtls unity ) endif() @@ -75,3 +84,9 @@ target_compile_options( mbedtls PRIVATE $<$,$,$>: -w> ) + +# Organization of mbed TLS in folders. +set_property( TARGET mbedtls PROPERTY FOLDER "third_party" ) +source_group( include\\mbedtls FILES ${MBEDTLS_HEADERS} ) +source_group( source FILES ${MBEDTLS_SOURCES} ) +source_group( "" FILES iot_config_mbedtls.h threading_alt.h ) diff --git a/third_party/mbedtls/iot_config_mbedtls.h b/third_party/mbedtls/iot_config_mbedtls.h index e2e6987429..d56130997f 100644 --- a/third_party/mbedtls/iot_config_mbedtls.h +++ b/third_party/mbedtls/iot_config_mbedtls.h @@ -120,6 +120,7 @@ #define MBEDTLS_PK_C #define MBEDTLS_PK_PARSE_C #define MBEDTLS_PKCS1_V15 +#define MBEDTLS_PLATFORM_C #define MBEDTLS_RSA_C #define MBEDTLS_SHA256_C #define MBEDTLS_SHA512_C diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 5f92c37984..7f7cea4286 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -18,14 +18,21 @@ if( NOT EXISTS ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src/ ) endif() # TinyCBOR library source files. +set( TINYCBOR_SOURCES + tinycbor/src/cborencoder.c + tinycbor/src/cborencoder_close_container_checked.c + tinycbor/src/cborerrorstrings.c + tinycbor/src/cborparser.c + tinycbor/src/cborparser_dup_string.c + tinycbor/src/cborpretty.c + tinycbor/src/cborpretty_stdio.c ) + +# TinyCBOR headers (for folder organization only). +file( GLOB TINYCBOR_HEADERS "tinycbor/src/*.h" ) + +# TinyCBOR library target. add_library( tinycbor - tinycbor/src/cborencoder.c - tinycbor/src/cborencoder_close_container_checked.c - tinycbor/src/cborerrorstrings.c - tinycbor/src/cborparser.c - tinycbor/src/cborparser_dup_string.c - tinycbor/src/cborpretty.c - tinycbor/src/cborpretty_stdio.c ) + ${TINYCBOR_SOURCES} ${TINYCBOR_HEADERS} ) # TinyCBOR include path. target_include_directories( tinycbor SYSTEM @@ -39,3 +46,8 @@ target_compile_options( tinycbor PRIVATE $<$,$,$>: -w> ) + +# Organization of TinyCBOR in folders. +set_property( TARGET tinycbor PROPERTY FOLDER "third_party" ) +source_group( include FILES ${TINYCBOR_HEADERS} ) +source_group( source FILES ${TINYCBOR_SOURCES} ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index 94b7b3324e..174db1511b 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -1,8 +1,20 @@ -# Unity test framework. +# Unity test framework source files. +set( UNITY_SOURCES + unity/unity.c + unity/fixture/unity_fixture.c + unity/fixture/unity_memory_mt.c ) + +# Unity test framework headers (for folder organization only). +set( UNITY_HEADERS + unity/unity.h + unity/unity_internals.h + unity/fixture/unity_fixture.h + unity/fixture/unity_fixture_internals.h + unity/fixture/unity_fixture_malloc_overrides.h ) + +# Unity test framework target. add_library( unity - unity/unity.c - unity/fixture/unity_fixture.c - unity/fixture/unity_memory_mt.c ) + ${UNITY_SOURCES} ${UNITY_HEADERS} ) # Unity test framework include paths. target_include_directories( unity SYSTEM @@ -14,3 +26,8 @@ target_compile_options( unity PRIVATE $<$,$,$>: -w> ) + +# Organization of Unity in folders. +set_property( TARGET unity PROPERTY FOLDER "third_party" ) +source_group( include FILES ${UNITY_HEADERS} ) +source_group( source FILES ${UNITY_SOURCES} ) From cfd056b1c97761ccf8533217e258ee2afbcd3039 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 9 May 2019 09:43:21 -0700 Subject: [PATCH 130/844] Clean up dependencies in CMakeLists (#413) --- CMakeLists.txt | 4 +- demos/app/posix/CMakeLists.txt | 12 +- lib/source/common/CMakeLists.txt | 4 +- lib/source/defender/CMakeLists.txt | 4 +- lib/source/mqtt/CMakeLists.txt | 4 +- lib/source/serializer/CMakeLists.txt | 4 +- lib/source/shadow/CMakeLists.txt | 4 +- platform/source/posix/CMakeLists.txt | 9 +- tests/common/CMakeLists.txt | 2 +- tests/defender/CMakeLists.txt | 2 +- tests/mqtt/CMakeLists.txt | 5 +- tests/mqtt/iot_tests_mqtt.c | 1 - tests/mqtt/system/iot_tests_mqtt_stress.c | 641 ---------------------- tests/serializer/CMakeLists.txt | 2 +- tests/shadow/CMakeLists.txt | 2 +- third_party/mbedtls/CMakeLists.txt | 6 +- third_party/tinycbor/CMakeLists.txt | 10 +- third_party/unity/CMakeLists.txt | 4 +- 18 files changed, 48 insertions(+), 672 deletions(-) delete mode 100644 tests/mqtt/system/iot_tests_mqtt_stress.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b222fde17c..646a90eb16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project( AwsIotDeviceSdkEmbeddedC LANGUAGES C ) # Allow the project to be organized into folders. -set_property( GLOBAL PROPERTY USE_FOLDERS ON) +set_property( GLOBAL PROPERTY USE_FOLDERS ON ) # Use C99. set( CMAKE_C_STANDARD 99 ) @@ -110,6 +110,8 @@ add_subdirectory( third_party/mbedtls ) # Determine the demo executable to build based on system. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_subdirectory( demos/app/posix ) +elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + add_subdirectory( demos/app/windows ) endif() # Test executables. diff --git a/demos/app/posix/CMakeLists.txt b/demos/app/posix/CMakeLists.txt index f8412ed6f0..e405d5b905 100644 --- a/demos/app/posix/CMakeLists.txt +++ b/demos/app/posix/CMakeLists.txt @@ -13,7 +13,11 @@ target_compile_definitions( iot_demo_mqtt PRIVATE RunDemo=RunMqttDemo ) # MQTT demo library dependencies. -target_link_libraries( iot_demo_mqtt iotplatform iotmqtt ) +target_link_libraries( iot_demo_mqtt PRIVATE iotmqtt ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iot_demo_mqtt PRIVATE unity ) +endif() # Shadow demo source files. add_executable( aws_iot_demo_shadow @@ -25,4 +29,8 @@ target_compile_definitions( aws_iot_demo_shadow PRIVATE RunDemo=RunShadowDemo ) # Shadow demo library dependencies. -target_link_libraries( aws_iot_demo_shadow iotplatform iotmqtt awsiotshadow ) +target_link_libraries( aws_iot_demo_shadow PRIVATE awsiotshadow ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( aws_iot_demo_shadow PRIVATE unity ) +endif() diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index d677300c1b..8fec8cac8e 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -26,11 +26,11 @@ add_library( iotcommon ${COMMON_SOURCES} ${COMMON_PUBLIC_HEADERS} ${COMMON_PRIVATE_HEADERS} ${COMMON_TYPES_HEADERS} ) # Link required libraries. -target_link_libraries( iotcommon iotplatform ) +target_link_libraries( iotcommon PUBLIC iotplatform ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotcommon unity ) + target_link_libraries( iotcommon PRIVATE unity ) endif() # Organization of common libraries in folders. diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt index 3fd5d8d2f1..ea5d5d9813 100644 --- a/lib/source/defender/CMakeLists.txt +++ b/lib/source/defender/CMakeLists.txt @@ -11,11 +11,11 @@ add_library( awsiotdefender ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_defender_internal.h ) # Link required libraries. -target_link_libraries( awsiotdefender iotserializer iotmqtt iotplatform ) +target_link_libraries( awsiotdefender PUBLIC iotserializer iotmqtt ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotdefender unity ) + target_link_libraries( awsiotdefender PRIVATE unity ) endif() # Organization of Defender in folders. diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index c6d59cdb53..c8b949227d 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -16,11 +16,11 @@ add_library( iotmqtt ${PROJECT_SOURCE_DIR}/lib/include/private/iot_mqtt_internal.h ) # Link required libraries. -target_link_libraries( iotmqtt iotcommon iotplatform ) +target_link_libraries( iotmqtt PUBLIC iotcommon ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotmqtt unity ) + target_link_libraries( iotmqtt PRIVATE unity ) endif() # Organization of MQTT in folders. diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index 27fbb057d2..91e65d12fc 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -12,11 +12,11 @@ add_library( iotserializer ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) # Link required libraries. -target_link_libraries( iotserializer tinycbor ) +target_link_libraries( iotserializer PUBLIC tinycbor ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotserializer unity ) + target_link_libraries( iotserializer PRIVATE unity ) endif() # Organization of Serializer in folders. diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index a3fec4979a..85bab5822c 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -14,11 +14,11 @@ add_library( awsiotshadow ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_shadow_internal.h ) # Link required libraries. -target_link_libraries( awsiotshadow iotserializer iotplatform iotmqtt ) +target_link_libraries( awsiotshadow PUBLIC iotserializer iotmqtt ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotshadow unity ) + target_link_libraries( awsiotshadow PRIVATE unity ) endif() # Organization of Shadow in folders. diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 8c7696cadf..66f7199156 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -67,7 +67,7 @@ if( ${IOT_NETWORK_USE_OPENSSL} ) set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) endif() else() - set( NETWORK_SOURCE_FILE ../network/iot_network_mbedtls.c ) + set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_mbedtls.c ) set( TLS_LIBRARY_LINKER_FLAG mbedtls ) endif() @@ -75,12 +75,13 @@ endif() add_library( iotplatform iot_clock_posix.c iot_threads_posix.c - ../network/iot_network_metrics.c + ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c ${NETWORK_SOURCE_FILE} ) # Link required libraries. -target_link_libraries( iotplatform Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) +target_link_libraries( iotplatform + PRIVATE Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotplatform unity ) + target_link_libraries( iotplatform PRIVATE unity ) endif() diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 580996bb67..11dc357fe1 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -11,7 +11,7 @@ add_executable( iot_tests_common ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Common tests library dependencies. -target_link_libraries( iot_tests_common unity iotcommon ) +target_link_libraries( iot_tests_common PRIVATE iotcommon unity ) # Organization of common tests in folders. set_property( TARGET iot_tests_common PROPERTY FOLDER "tests" ) diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt index 0891e58c38..4e9e495eb3 100644 --- a/tests/defender/CMakeLists.txt +++ b/tests/defender/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable( aws_iot_tests_defender ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Defender tests library dependencies. -target_link_libraries( aws_iot_tests_defender iotcommon iotplatform iotmqtt awsiotdefender unity ) +target_link_libraries( aws_iot_tests_defender PRIVATE awsiotdefender unity ) # Organization of Defender tests in folders. set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER "tests" ) diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index 5603e8e26e..d70ff9ba35 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -1,7 +1,6 @@ # MQTT system test sources. set( MQTT_SYSTEM_TEST_SOURCES - system/iot_tests_mqtt_system.c - system/iot_tests_mqtt_stress.c ) + system/iot_tests_mqtt_system.c ) # MQTT unit test sources. set( MQTT_UNIT_TEST_SOURCES @@ -18,7 +17,7 @@ add_executable( iot_tests_mqtt ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # MQTT tests library dependencies. -target_link_libraries( iot_tests_mqtt iotcommon iotplatform iotmqtt unity pthread ) +target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt unity ) # Organization of MQTT tests in folders. set_property( TARGET iot_tests_mqtt PROPERTY FOLDER "tests" ) diff --git a/tests/mqtt/iot_tests_mqtt.c b/tests/mqtt/iot_tests_mqtt.c index 2151e1defc..1e02a4f489 100644 --- a/tests/mqtt/iot_tests_mqtt.c +++ b/tests/mqtt/iot_tests_mqtt.c @@ -99,7 +99,6 @@ int main( int argc, * -l command line argument was given. */ if( getopt( argc, argv, "l" ) != -1 ) { - RUN_TEST_GROUP( MQTT_Stress ); } /* Return failure if any tests failed. */ diff --git a/tests/mqtt/system/iot_tests_mqtt_stress.c b/tests/mqtt/system/iot_tests_mqtt_stress.c deleted file mode 100644 index f7047a16d3..0000000000 --- a/tests/mqtt/system/iot_tests_mqtt_stress.c +++ /dev/null @@ -1,641 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_mqtt_stress.c - * @brief Stress tests for the MQTT library. - * - * The tests in this file run far longer than other tests, and may easily fail - * due to poor network conditions. For best results, these tests should be run - * on a stable local network (not the Internet). - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* POSIX includes. */ -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* MQTT include. */ -#include "iot_mqtt.h" - -/* POSIX includes. */ -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif - -/* Platform layer include. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - -/* Test network header include. */ -#include IOT_TEST_NETWORK_HEADER - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* The tests in this file run for a long time, so set up logging to track their - * progress. */ -#define LIBRARY_LOG_LEVEL IOT_LOG_INFO -#define LIBRARY_LOG_NAME ( "MQTT_STRESS" ) -#include "iot_logging_setup.h" - -/** - * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). - */ -#if !defined( IOT_TEST_MQTT_MOSQUITTO ) || IOT_TEST_MQTT_MOSQUITTO == 0 - #define AWS_IOT_MQTT_SERVER true -#else - #define AWS_IOT_MQTT_SERVER false - -/* Redefine the connect info initializer if not using an AWS IoT MQTT server. */ - #undef IOT_MQTT_CONNECT_INFO_INITIALIZER - #define IOT_MQTT_CONNECT_INFO_INITIALIZER { 0 } -#endif - -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Provide default values of test configuration constants. - */ -#ifndef IOT_TEST_MQTT_TIMEOUT_MS - #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) -#endif -#ifndef IOT_TEST_MQTT_TOPIC_PREFIX - #define IOT_TEST_MQTT_TOPIC_PREFIX "iotmqtttest" -#endif -#ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S - #if IOT_TEST_MQTT_MOSQUITTO == 1 - #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 5 ) - #else - #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) - #endif -#endif -#ifndef IOT_TEST_MQTT_RETRY_MS - #define IOT_TEST_MQTT_RETRY_MS ( 350 ) -#endif -#ifndef IOT_TEST_MQTT_RETRY_LIMIT - #define IOT_TEST_MQTT_RETRY_LIMIT ( 32 ) -#endif -#ifndef IOT_TEST_MQTT_DECONGEST_S - #define IOT_TEST_MQTT_DECONGEST_S ( 30 ) -#endif -#ifndef IOT_TEST_MQTT_THREADS - #define IOT_TEST_MQTT_THREADS ( 16 ) -#endif -#ifndef IOT_TEST_MQTT_PUBLISHES_PER_THREAD - #ifdef IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS - #define IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ) - #else - #define IOT_TEST_MQTT_PUBLISHES_PER_THREAD ( 100 ) - #endif -#endif -/** @endcond */ - -/** - * @brief Number of test topic names. - */ -#define TEST_TOPIC_NAME_COUNT ( 8 ) - -/** - * @brief The maximum number of PUBLISH messages that will be received by a - * single test. - */ -#define MAX_RECEIVED_PUBLISH ( IOT_TEST_MQTT_THREADS * IOT_TEST_MQTT_PUBLISHES_PER_THREAD ) - -/** - * @brief The maximum length of an MQTT client identifier. - */ -#ifdef IOT_TEST_MQTT_CLIENT_IDENTIFIER - #define CLIENT_IDENTIFIER_MAX_LENGTH ( sizeof( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) ) -#else - #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Parameter 1 of #_publishThread. - */ -typedef struct _publishParams -{ - int threadNumber; /**< @brief ID number of this publish thread. */ - uint32_t publishPeriodMs; /**< @brief How long to wait (in milliseconds) between each publish. */ - unsigned publishLimit; /**< @brief How many publishes this thread will send. */ - IotMqttError_t status; /**< @brief Final status of this publish thread. */ -} _publishParams_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Network server info to share among the tests. - */ -static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; - -/** - * @brief Network credential info to share among the tests. - */ -#if IOT_TEST_SECURED_CONNECTION == 1 - static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; -#endif - -/** - * @brief An MQTT network setup parameter to share among the tests. - */ -static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - -/** - * @brief An MQTT connection to share among the tests. - */ -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief Filler text to publish. - */ -static const char _pSamplePayload[] = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor" - " incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis " - "nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " - "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu" - " fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in" - " culpa qui officia deserunt mollit anim id est laborum."; - -/** - * @brief Length of #_pSamplePayload. - */ -static const size_t _samplePayloadLength = sizeof( _pSamplePayload ) - 1; - -/** - * @brief Topic names used in the stress tests. - * - * For convenience, all topic names are the same length. - */ -static const char * const _pTopicNames[ TEST_TOPIC_NAME_COUNT ] = -{ - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/1", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/2", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/3", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/4", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/5", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/6", - IOT_TEST_MQTT_TOPIC_PREFIX "/stress/7" -}; - -/** - * @brief Length of topic names used in the stress tests. - * - * For convenience, all topic names are the same length. - */ -static const uint16_t _topicNameLength = ( uint16_t ) sizeof( IOT_TEST_MQTT_TOPIC_PREFIX "/stress/0" ) - 1; - -/** - * @brief Counts how many subscriptions were received for each test. - * - * Used in conjunction with #_publishReceived. - */ -static IotSemaphore_t receivedPublishCounter; - -/** - * @brief Buffer holding the client identifier used for the tests. - */ -static char _pClientIdentifier[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - -/*-----------------------------------------------------------*/ - -/** - * @brief Checks that the MQTT connection is still usable by sending a PUBLISH. - * - * @return The result of the PUBLISH. - */ -static IotMqttError_t _checkConnection( void ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER; - - /* Set the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = _pTopicNames[ 0 ]; - publishInfo.topicNameLength = _topicNameLength; - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryMs = IOT_TEST_MQTT_RETRY_MS; - publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; - - /* Send a PUBLISH. */ - status = IotMqtt_Publish( _mqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishOperation ); - - if( status != IOT_MQTT_STATUS_PENDING ) - { - return status; - } - - /* Return the result of the PUBLISH. */ - return IotMqtt_Wait( publishOperation, - IOT_TEST_MQTT_TIMEOUT_MS ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Subscription callback function. - */ -static void _publishReceived( void * pArgument, - IotMqttCallbackParam_t * pPublish ) -{ - ( void ) pArgument; - - /* Increment the received PUBLISH counter if the received message matches - * what was published. */ - if( ( pPublish->u.message.info.payloadLength == _samplePayloadLength ) && - ( strncmp( _pSamplePayload, pPublish->u.message.info.pPayload, _samplePayloadLength ) == 0 ) && - ( pPublish->u.message.info.topicNameLength == _topicNameLength ) && - ( pPublish->u.message.info.qos == IOT_MQTT_QOS_1 ) ) - { - IotSemaphore_Post( &receivedPublishCounter ); - } - else - { - IotLogWarn( "Received an unknown message on subscription %.*s: %.*s", - pPublish->u.message.info.topicNameLength, pPublish->u.message.info.pTopicName, - pPublish->u.message.info.payloadLength, pPublish->u.message.info.pPayload ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Callback function that blocks for a long time. - */ -static void _blockingCallback( void * pArgument, - IotMqttCallbackParam_t * param ) -{ - IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; - const uint32_t blockTimeMs = ( 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ) * 1000; - - ( void ) param; - - IotLogInfo( "Callback blocking for %lu milliseconds.", blockTimeMs ); - IotClock_SleepMs( blockTimeMs ); - IotSemaphore_Post( pWaitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Periodically sends PUBLISH messages. - * - * @param[in] pArgument Pointer to a #_publishParams_t. - */ -static void * _publishThread( void * pArgument ) -{ - unsigned i = 0; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _publishParams_t * pParams = ( _publishParams_t * ) pArgument; - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - - /* Set the publish info. */ - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.topicNameLength = _topicNameLength; - publishInfo.retryMs = IOT_TEST_MQTT_RETRY_MS; - publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; - - for( i = 0; i < pParams->publishLimit; ) - { - /* Choose a topic name. */ - publishInfo.pTopicName = _pTopicNames[ i % TEST_TOPIC_NAME_COUNT ]; - - /* PUBLISH the message. */ - status = IotMqtt_Publish( _mqttConnection, - &publishInfo, - 0, - NULL, - NULL ); - - /* The stress tests may exhaust all memory available to the MQTT library. - * If no memory is available, wait some time for resources to be released. */ - if( status == IOT_MQTT_NO_MEMORY ) - { - IotLogInfo( "Thread %d: no memory available on PUBLISH %d." - " Sleeping for %d seconds.", - pParams->threadNumber, - i, - IOT_TEST_MQTT_DECONGEST_S ); - IotClock_SleepMs( IOT_TEST_MQTT_DECONGEST_S * 1000 ); - continue; - } - /* If the PUBLISH failed, exit this thread. */ - else if( status != IOT_MQTT_STATUS_PENDING ) - { - IotLogError( "Thread %d encountered error %d publishing message %d.", - status, i ); - break; - } - - /* Only increment the loop counter if the PUBLISH succeeded. */ - i++; - - /* Occasionally print an update on how many PUBLISH messages this thread - * has sent. */ - if( ( i % 25 == 0 ) || - ( i == pParams->publishLimit ) ) - { - IotLogInfo( "Thread %d has published %d of %d messages.", - pParams->threadNumber, i, pParams->publishLimit ); - } - - /* Sleep until the next PUBLISH should be sent. */ - IotClock_SleepMs( pParams->publishPeriodMs ); - } - - /* Set the thread's last status. */ - pParams->status = status; - - return NULL; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group for MQTT stress tests. - */ -TEST_GROUP( MQTT_Stress ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for MQTT stress tests. - */ -TEST_SETUP( MQTT_Stress ) -{ - int32_t i = 0; - IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - IotMqttSubscription_t pSubscriptions[ TEST_TOPIC_NAME_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* Initialize SDK. */ - if( IotSdk_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); - } - - /* Set up the network stack. */ - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to set up network stack." ); - } - - /* Initialize the MQTT library. */ - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - - /* Print a new line. */ - UNITY_PRINT_EOL(); - - /* Create the publish counter semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &receivedPublishCounter, - 0, - MAX_RECEIVED_PUBLISH ) ); - - /* Generate a new, unique client identifier based on the time if no client - * identifier is defined. Otherwise, copy the provided client identifier. */ - #ifndef IOT_TEST_MQTT_CLIENT_IDENTIFIER - ( void ) snprintf( _pClientIdentifier, - CLIENT_IDENTIFIER_MAX_LENGTH, - "iot%llu", - ( long long unsigned int ) IotClock_GetTimeMs() ); - #else - ( void ) strncpy( _pClientIdentifier, - IOT_TEST_MQTT_CLIENT_IDENTIFIER, - CLIENT_IDENTIFIER_MAX_LENGTH ); - #endif - - /* Set the MQTT network setup parameters. */ - ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - _networkInfo.createNetworkConnection = true; - _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; - - #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; - #endif - - /* Set the members of the connect info. */ - connectInfo.cleanSession = true; - connectInfo.awsIotMqttMode = AWS_IOT_MQTT_SERVER; - connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - connectInfo.pClientIdentifier = _pClientIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); - - /* Set the members of the subscriptions */ - for( i = 0; i < TEST_TOPIC_NAME_COUNT; i++ ) - { - pSubscriptions[ i ].pTopicFilter = _pTopicNames[ i ]; - pSubscriptions[ i ].topicFilterLength = _topicNameLength; - pSubscriptions[ i ].qos = IOT_MQTT_QOS_1; - pSubscriptions[ i ].callback.function = _publishReceived; - } - - /* Establish the MQTT connection. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ) ); - - /* Subscribe to the test topic filters. */ - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, - IotMqtt_TimedSubscribe( _mqttConnection, - pSubscriptions, - TEST_TOPIC_NAME_COUNT, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ) ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test tear down for MQTT stress tests. - */ -TEST_TEAR_DOWN( MQTT_Stress ) -{ - /* Destroy the PUBLISH counter semaphore. */ - IotSemaphore_Destroy( &receivedPublishCounter ); - - /* Disconnect the MQTT connection. Unsubscribe is not called; the subscriptions - * should be cleaned up by Disconnect. */ - if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) - { - IotMqtt_Disconnect( _mqttConnection, 0 ); - _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - } - - /* Clean up the MQTT library. */ - IotMqtt_Cleanup(); - - /* Clean up the network stack. */ - IotTestNetwork_Cleanup(); - - /* Clean up SDK. */ - IotSdk_Cleanup(); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test group runner for MQTT stress tests. - */ -TEST_GROUP_RUNNER( MQTT_Stress ) -{ - RUN_TEST_CASE( MQTT_Stress, KeepAlive ); - RUN_TEST_CASE( MQTT_Stress, BlockingCallback ); - RUN_TEST_CASE( MQTT_Stress, ClientClosesConnection ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that keep-alive keeps an idle connection open. - */ -TEST( MQTT_Stress, KeepAlive ) -{ - const uint32_t sleepTimeMs = ( 5 * IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ) * 1000; - - /* Send no MQTT packets for a long time. The keep-alive must be used to keep - * the connection open. */ - IotLogInfo( "KeepAlive test sleeping for %lu milliseconds.", sleepTimeMs ); - IotClock_SleepMs( sleepTimeMs ); - - /* Send a PUBLISH to verify that the connection is still usable. */ - IotLogInfo( "KeepAlive test checking MQTT connection." ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _checkConnection() ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test that the MQTT connection is not closed if a user callback blocks. - */ -TEST( MQTT_Stress, BlockingCallback ) -{ - IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - IotMqttCallbackInfo_t callbackInfo = IOT_MQTT_CALLBACK_INFO_INITIALIZER; - IotSemaphore_t waitSem; - - callbackInfo.function = _blockingCallback; - callbackInfo.pCallbackContext = &waitSem; - - publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = _pTopicNames[ 0 ]; - publishInfo.topicNameLength = _topicNameLength; - publishInfo.pPayload = _pSamplePayload; - publishInfo.payloadLength = _samplePayloadLength; - publishInfo.retryMs = IOT_TEST_MQTT_RETRY_MS; - publishInfo.retryLimit = IOT_TEST_MQTT_RETRY_LIMIT; - - /* Create the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Call a function that will invoke the blocking callback. */ - TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, - IotMqtt_Publish( _mqttConnection, - &publishInfo, - 0, - &callbackInfo, - NULL ) ); - - /* Wait for the callback to return, then check that the connection is - * still usable. */ - IotSemaphore_Wait( &waitSem ); - IotLogInfo( "BlockingCallback test checking MQTT connection." ); - TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, _checkConnection() ); - } - - IotSemaphore_Destroy( &waitSem ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Test the behavior of the MQTT library when the network connection is - * closed by the client. - */ -TEST( MQTT_Stress, ClientClosesConnection ) -{ - int i = 0, threadsCreated = 0, threadsJoined = 0; - pthread_t publishThreads[ IOT_TEST_MQTT_THREADS ] = { 0 }; - _publishParams_t publishThreadParams[ IOT_TEST_MQTT_THREADS ] = { 0 }; - - /* Set the parameters for each thread. */ - for( i = 0; i < IOT_TEST_MQTT_THREADS; i++ ) - { - publishThreadParams[ i ].threadNumber = i; - publishThreadParams[ i ].publishPeriodMs = 500; - publishThreadParams[ i ].publishLimit = IOT_TEST_MQTT_PUBLISHES_PER_THREAD; - } - - IotLogInfo( "ClientClosesConnection test creating threads." ); - - /* Spawn the threads for the test. */ - for( i = 0; i < IOT_TEST_MQTT_THREADS; i++ ) - { - if( pthread_create( &( publishThreads[ i ] ), - NULL, - _publishThread, - &( publishThreadParams[ i ] ) ) != 0 ) - { - break; - } - } - - /* Record how many threads were created. */ - threadsCreated = i; - - /* Wait for all created threads to finish. */ - for( i = 0; i < threadsCreated; i++ ) - { - if( pthread_join( publishThreads[ i ], NULL ) == 0 ) - { - threadsJoined++; - } - } -} - -/*-----------------------------------------------------------*/ diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt index 82586b2181..9875352ebe 100644 --- a/tests/serializer/CMakeLists.txt +++ b/tests/serializer/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable( iot_tests_serializer ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Serializer tests library dependencies. -target_link_libraries( iot_tests_serializer iotcommon iotplatform iotserializer unity ) +target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotcommon unity ) # Organization of Serializer tests in folders. set_property( TARGET iot_tests_serializer PROPERTY FOLDER "tests" ) diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index 6333db76b7..ea55702c74 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -15,7 +15,7 @@ add_executable( aws_iot_tests_shadow ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Shadow tests library dependencies. -target_link_libraries( aws_iot_tests_shadow iotcommon iotplatform iotmqtt awsiotshadow unity ) +target_link_libraries( aws_iot_tests_shadow PRIVATE awsiotshadow unity ) # Organization of Shadow tests in folders. set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER "tests" ) diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index 6eb0d2ed93..efb82cc0d5 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -76,14 +76,16 @@ target_compile_definitions( mbedtls # Link the Unity test framework when testing. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( mbedtls unity ) + target_link_libraries( mbedtls PRIVATE unity ) endif() # Disable all warnings for this third-party library. target_compile_options( mbedtls PRIVATE $<$,$,$>: - -w> ) + -w> + $<$: + /W0 /D_CRT_SECURE_NO_WARNINGS> ) # Organization of mbed TLS in folders. set_property( TARGET mbedtls PROPERTY FOLDER "third_party" ) diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 7f7cea4286..371cef3cf3 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -38,14 +38,18 @@ add_library( tinycbor target_include_directories( tinycbor SYSTEM PUBLIC ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) -# Link math library. -target_link_libraries( tinycbor m ) +# Link math library. This is not needed on Windows. +if( NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + target_link_libraries( tinycbor PRIVATE m ) +endif() # Disable all warnings for this third-party library. target_compile_options( tinycbor PRIVATE $<$,$,$>: - -w> ) + -w> + $<$: + /W0 /D_CRT_SECURE_NO_WARNINGS> ) # Organization of TinyCBOR in folders. set_property( TARGET tinycbor PROPERTY FOLDER "third_party" ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index 174db1511b..248e8a75eb 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -25,7 +25,9 @@ target_include_directories( unity SYSTEM target_compile_options( unity PRIVATE $<$,$,$>: - -w> ) + -w> + $<$: + /W0 /D_CRT_SECURE_NO_WARNINGS> ) # Organization of Unity in folders. set_property( TARGET unity PROPERTY FOLDER "third_party" ) From 80980030162f527778b4fb7dfb41f346ed30df9a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 9 May 2019 13:07:26 -0700 Subject: [PATCH 131/844] Resolve DLL linking errors (#414) --- CMakeLists.txt | 1 + lib/source/common/CMakeLists.txt | 10 +++++++--- platform/source/posix/CMakeLists.txt | 17 +++++++++++++++-- third_party/mbedtls/CMakeLists.txt | 10 ++++++++++ 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 646a90eb16..ad0afeb31f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) add_subdirectory( platform/source/posix ) elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON ) add_definitions( -DIOT_SYSTEM_TYPES_FILE="windows/iot_platform_types_windows.h" ) add_subdirectory( platform/source/windows ) endif() diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 8fec8cac8e..1813fad12b 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,7 +1,6 @@ -# Common libraries source files. +# Common libraries source files. The logging source is built in an interface library. set( COMMON_SOURCES iot_init.c - iot_logging.c iot_static_memory_common.c iot_taskpool.c iot_taskpool_static_memory.c ) @@ -21,12 +20,17 @@ set( COMMON_PRIVATE_HEADERS set( COMMON_TYPES_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h ) +# Make logging an interface library so that it may be linked independently. +add_library( iotlogging INTERFACE ) +target_sources( iotlogging INTERFACE ${PROJECT_SOURCE_DIR}/lib/source/common/iot_logging.c ) + # Common library target. add_library( iotcommon ${COMMON_SOURCES} ${COMMON_PUBLIC_HEADERS} ${COMMON_PRIVATE_HEADERS} ${COMMON_TYPES_HEADERS} ) # Link required libraries. target_link_libraries( iotcommon PUBLIC iotplatform ) +target_link_libraries( iotcommon PRIVATE iotlogging ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) @@ -38,4 +42,4 @@ set_property( TARGET iotcommon PROPERTY FOLDER "lib" ) source_group( include FILES ${COMMON_PUBLIC_HEADERS} ) source_group( include\\private FILES ${COMMON_PRIVATE_HEADERS} ) source_group( include\\types FILES ${COMMON_TYPES_HEADERS} ) -source_group( source FILES ${COMMON_SOURCES} ) +source_group( source FILES ${COMMON_SOURCES} iot_logging.c ) diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 66f7199156..54a84a2469 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -61,7 +61,7 @@ if( ${IOT_NETWORK_USE_OPENSSL} ) endif() # Choose OpenSSL network source file. - set( NETWORK_SOURCE_FILE iot_network_openssl.c ) + set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_network_openssl.c ) # Link OpenSSL. set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) @@ -71,6 +71,19 @@ else() set( TLS_LIBRARY_LINKER_FLAG mbedtls ) endif() +# Split platform into interface libraries. +add_library( iotplatformclock INTERFACE ) +target_sources( iotplatformclock INTERFACE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_clock_posix.c ) +target_link_libraries( iotplatformclock INTERFACE rt ) + +add_library( iotplatformthreads INTERFACE ) +target_sources( iotplatformthreads INTERFACE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_threads_posix.c ) +target_link_libraries( iotplatformthreads INTERFACE Threads::Threads ) + +add_library( iotplatformnetwork INTERFACE ) +target_sources( iotplatformnetwork INTERFACE ${NETWORK_SOURCE_FILE} ) +target_link_libraries( iotplatformnetwork INTERFACE ${TLS_LIBRARY_LINKER_FLAG} ) + # Platform libraries source files. add_library( iotplatform iot_clock_posix.c @@ -80,7 +93,7 @@ add_library( iotplatform # Link required libraries. target_link_libraries( iotplatform - PRIVATE Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) + PRIVATE iotplatformclock iotplatformthreads iotplatformnetwork ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotplatform PRIVATE unity ) diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index efb82cc0d5..41495a267f 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -59,6 +59,13 @@ set( MBEDTLS_SOURCES mbedtls/library/x509.c mbedtls/library/x509_crt.c ) +# Choose the correct interface library source. +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + list( APPEND MBEDTLS_SOURCES ${PROJECT_SOURCE_DIR}/platform/source/posix/iot_threads_posix.c ) +elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + list( APPEND MBEDTLS_SOURCES ${PROJECT_SOURCE_DIR}/platform/source/windows/iot_threads_windows.c ) +endif() + # mbed TLS headers (for folder organization only). file( GLOB MBEDTLS_HEADERS "mbedtls/include/mbedtls/*.h" ) @@ -74,6 +81,9 @@ target_include_directories( mbedtls SYSTEM target_compile_definitions( mbedtls PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) +# Link the platform threading component. +target_link_libraries( mbedtls PRIVATE iotplatformthreads ) + # Link the Unity test framework when testing. if( ${IOT_BUILD_TESTS} ) target_link_libraries( mbedtls PRIVATE unity ) From 6cce27d5737b33c9f9b38eb90c1dfd66a65deda9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 9 May 2019 14:27:02 -0700 Subject: [PATCH 132/844] Add documentation check to CI build (#415) --- .travis.yml | 8 ++++- doc/lib/mqtt.txt | 54 +----------------------------- doc/lib/shadow.txt | 6 ++-- lib/include/iot_taskpool.h | 68 +++++++++++++++++++------------------- scripts/ci_test_doc.sh | 26 +++++++++++---- 5 files changed, 65 insertions(+), 97 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0a78a054c..6d4e9872ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ jobs: - if: type = push compiler: gcc env: RUN_TEST=coverage + - if: type = pull_request + env: RUN_TEST=doc # Update repositories. before_install: @@ -33,7 +35,11 @@ before_install: # Install dependencies. install: # Dependencies required across the entire build matrix. - - sudo apt-get install -y cmake libssl-dev + - sudo apt-get install -y cmake + # Install OpenSSL if needed. + - if [ "$NETWORK_STACK" = "openssl" ]; then sudo apt-get install -y libssl-dev; fi + # Install graphviz only for documentation builds. + - if [ "$RUN_TEST" = "doc" ]; then sudo apt-get install -y graphviz; fi # Install lcov and coveralls only for coverage builds. - if [ "$RUN_TEST" = "coverage" ]; then sudo apt-get install -y lcov; pip install --user cpp-coveralls; fi diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 42168a6894..f4b73c25a9 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -143,7 +143,7 @@ When this setting is `1`, the MQTT tests will be built to test against an unsecu @configpossible `0` (use AWS IoT MQTT server) or `1` (use public Mosquitto server)
@configdefault `0` -
The settings below only affect the [system](@ref iot_tests_mqtt_system.c) and [stress](@ref iot_tests_mqtt_stress.c) tests.
+
The settings below only affect the [system](@ref iot_tests_mqtt_system.c) tests.
@section IOT_TEST_MQTT_TIMEOUT_MS @brief Timeout in milliseconds for MQTT operations. @@ -161,58 +161,6 @@ This string will be prepended as the common part of topic filters as a way to di @configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
@configdefault `"iotmqtttest"` - -
The settings below only affect the [stress](@ref iot_tests_mqtt_stress.c) tests.
- -@section IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S -@brief The keep-alive interval for the stress tests. - -MQTT PINGREQ packets will be sent at this interval. - -@configpossible Any positive integer.
-@configrecommended This value should be the shortest keep-alive interval supported by the connection.
-@configdefault `30` when using the AWS IoT MQTT server; `5` otherwise - -@section IOT_TEST_MQTT_RETRY_MS -@brief The value of #IotMqttPublishInfo_t.retryMs used in the stress tests. - -Any lost publish messages will be retransmitted at this time. - -@configpossible Any positive integer.
-@configrecommended This setting should be at least `250` to avoid excessive network congestion caused by retransmissions.
-@configdefault `350` - -@section IOT_TEST_MQTT_RETRY_LIMIT -@brief The value of #IotMqttPublishInfo_t.retryLimit used in the stress tests. - -Any lost publish messages will be retried up to this limit. - -@configpossible Any positive integer.
-@configdefault `32` - -@section IOT_TEST_MQTT_DECONGEST_S -@brief The time to wait for resources to be freed. - -Should an MQTT operation fail due to insufficient resources (such as a return value of @ref IOT_MQTT_NO_MEMORY), the test thread will sleep for this amount to time before retrying to wait for resources to be freed. Larger values for this setting provide a higher change for a successful retry, but will cause the tests to run longer. - -@configpossible Any positive integer.
-@configdefault `30` - -@section IOT_TEST_MQTT_THREADS -@brief The number of threads to use in the stress tests. - -The number of threads to spawn for the stress tests. Up to @ref IOT_TEST_MQTT_THREADS `*` @ref IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. - -@configpossible Any positive integer.
-@configdefault `16` - -@section IOT_TEST_MQTT_PUBLISHES_PER_THREAD -@brief The number of publish messages each thread in the stress test should send. - -Up to @ref IOT_TEST_MQTT_THREADS `*` @ref IOT_TEST_MQTT_PUBLISHES_PER_THREAD publish messages are sent by stress test. - -@configpossible Any non-negative integer.
-@configdefault When @ref IOT_STATIC_MEMORY_ONLY is `1`, this value defaults to @ref IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS. Otherwise, it defaults to `100`. */ /** diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 2e9181d9da..e5db19c36b 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -106,9 +106,11 @@ Thing Names are used to manage devices with AWS IoT. No default value is prov @section shadow_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S @brief The keep-alive interval to use in the Shadow system tests. -This constant is shared with the [MQTT tests](@ref mqtt_tests). +MQTT PINGREQ packets will be sent at this interval. -@see @ref IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S on the MQTT tests config page. +@configpossible Any positive integer.
+@configrecommended This value should be the shortest keep-alive interval supported by the connection.
+@configdefault `30` */ /** diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 0395815e9a..0b62ada61d 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -158,10 +158,10 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, * to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. * Calling this fuction release all underlying resources. After calling this function, any job scheduled but not yet executed * will be cancelled and destroyed. - * The `pTaskPool` instance will no longer be valid after this function returns. + * The `taskPool` instance will no longer be valid after this function returns. * - * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or - * @ref IotTaskPool_CreateSystemTaskPool. The `pTaskPool` instance will no longer be valid after this function returns. + * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or + * @ref IotTaskPool_CreateSystemTaskPool. The `taskPool` instance will no longer be valid after this function returns. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -176,12 +176,12 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t taskPool ); * @brief Sets the maximum number of threads for one instance of a task pool. * * This function sets the maximum number of threads for the task pool - * pointed to by `pTaskPool`. + * pointed to by `taskPool`. * * If the number of currently active threads in the task pool is greater than `maxThreads`, this * function causes the task pool to shrink the number of active threads. * - * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with + * @param[in] taskPool A handle to the task pool that must have been previously initialized with * a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. * @param[in] maxThreads The maximum number of threads for the task pool. * @@ -227,7 +227,7 @@ IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, * A recyclable job does not need to be allocated twice, but it can rather be reused through * subsequent calls to @ref IotTaskPool_CreateRecyclableJob. * - * @param[in] pTaskPool A handle to the task pool for which to create a recyclable job. + * @param[in] taskPool A handle to the task pool for which to create a recyclable job. * @param[in] userCallback A user-specified callback for the job. * @param[in] pUserContext A user-specified context for the callback. * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this @@ -261,8 +261,8 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPool, * An attempt to destroy a job that was scheduled but not yet executed or canceled, may result in a * @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. * - * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * @param[in] pJob A handle to a job that was create with a call to @ref IotTaskPool_CreateJob. + * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. + * @param[in] job A handle to a job that was create with a call to @ref IotTaskPool_CreateJob. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -293,8 +293,8 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPool, * that was previously scheduled but not completed or canceled cannot be safely recycled. An attempt to do so will result * in an @ref IOT_TASKPOOL_ILLEGAL_OPERATION error. * - * @param[in] pTaskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create. - * @param[out] pJob A pointer to a job that was create with a call to @ref IotTaskPool_CreateJob. + * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create. + * @param[out] job A pointer to a job that was create with a call to @ref IotTaskPool_CreateJob. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -302,8 +302,8 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPool, * - #IOT_TASKPOOL_ILLEGAL_OPERATION * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * - * @warning The `pTaskPool` used in this function should be the same - * used to create the job pointed to by `pJob`, or the results will be undefined. + * @warning The `taskPool` used in this function should be the same + * used to create the job pointed to by `job`, or the results will be undefined. * * @warning Attempting to call this function on a statically allocated job will result in @ref IOT_TASKPOOL_ILLEGAL_OPERATION * error. @@ -320,14 +320,14 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, /** * @brief This function schedules a job created with @ref IotTaskPool_CreateJob or @ref IotTaskPool_CreateRecyclableJob - * against the task pool pointed to by `pTaskPool`. + * against the task pool pointed to by `taskPool`. * * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool * library. * - * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. + * @param[in] taskPool A handle to the task pool that must have been previously initialized with. * a call to @ref IotTaskPool_Create. - * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. + * @param[in] job A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. * @param[in] flags Flags to be passed by the user, e.g. to identify the job as high priority by specifying #IOT_TASKPOOL_JOB_HIGH_PRIORITY. * * @return One of the following: @@ -341,7 +341,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, * @note This function will not allocate memory, so it is guaranteed to succeed if the paramters are correct and the task pool * was correctly initialized, and not yet destroyed. * - * @warning The `pTaskPool` used in this function should be the same used to create the job pointed to by `pJob`, or the + * @warning The `taskPool` used in this function should be the same used to create the job pointed to by `job`, or the * results will be undefined. * * Example @@ -412,14 +412,14 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPool, /** * @brief This function schedules a job created with @ref IotTaskPool_CreateJob against the task pool - * pointed to by `pTaskPool` to be executed after a user-defined time interval. + * pointed to by `taskPool` to be executed after a user-defined time interval. * * See @ref taskpool_design for a description of the jobs lifetime and interaction with the threads used in the task pool * library. * - * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with. + * @param[in] taskPool A handle to the task pool that must have been previously initialized with. * a call to @ref IotTaskPool_Create. - * @param[in] pJob A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. + * @param[in] job A job to schedule for execution. This must be first initialized with a call to @ref IotTaskPool_CreateJob. * @param[in] timeMs The time in milliseconds to wait before scheduling the job. * * @return One of the following: @@ -431,8 +431,8 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPool, * * @note This function will not allocate memory. * - * @warning The `pTaskPool` used in this function should be the same - * used to create the job pointed to by `pJob`, or the results will be undefined. + * @warning The `taskPool` used in this function should be the same + * used to create the job pointed to by `job`, or the results will be undefined. * */ /* @[declare_taskpool_scheduledeferred] */ @@ -444,9 +444,9 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPool, /** * @brief This function retrieves the current status of a job. * - * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with + * @param[in] taskPool A handle to the task pool that must have been previously initialized with * a call to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * @param[in] pJob The job to cancel. + * @param[in] job The job to cancel. * @param[out] pStatus The status of the job at the time of cancellation. * * @return One of the following: @@ -471,9 +471,9 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPool, * @ref IotTaskPool_TryCancel on a job whose status is @ref IOT_TASKPOOL_STATUS_COMPLETED, * or #IOT_TASKPOOL_STATUS_CANCELED will yield a #IOT_TASKPOOL_CANCEL_FAILED return result. * - * @param[in] pTaskPool A handle to the task pool that must have been previously initialized with + * @param[in] taskPool A handle to the task pool that must have been previously initialized with * a call to @ref IotTaskPool_Create. - * @param[in] pJob The job to cancel. + * @param[in] job The job to cancel. * @param[out] pStatus The status of the job at the time of cancellation. * * @return One of the following: @@ -482,8 +482,8 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPool, * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * - #IOT_TASKPOOL_CANCEL_FAILED * - * @warning The `pTaskPool` used in this function should be the same - * used to create the job pointed to by `pJob`, or the results will be undefined. + * @warning The `taskPool` used in this function should be the same + * used to create the job pointed to by `job`, or the results will be undefined. * */ /* @[declare_taskpool_trycancel] */ @@ -493,15 +493,15 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, /* @[declare_taskpool_trycancel] */ /** - * @brief Returns a pointer to the job storage from an instance of a job handle - * of type @ref IotTaskPoolJob_t. This function is guarateed to succeed for a + * @brief Returns a pointer to the job storage from an instance of a job handle + * of type @ref IotTaskPoolJob_t. This function is guarateed to succeed for a * valid job handle. * - * @param[in] pJob The job handle. + * @param[in] job The job handle. * - * @return A pointer to the storage associated with the job handle `pJob`. - * - * @warning If the `pJob` handle used is invalid, the results will be undefined. + * @return A pointer to the storage associated with the job handle `job`. + * + * @warning If the `job` handle used is invalid, the results will be undefined. */ /* @[declare_taskpool_getjobstoragefromhandle] */ IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t job ); @@ -529,7 +529,7 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ); /* @[declare_taskpool_strerror] */ /** - * @brief The maximum number of task pools to be created when using + * @brief The maximum number of task pools to be created when using * a memory pool. */ #ifndef IOT_TASKPOOLS diff --git a/scripts/ci_test_doc.sh b/scripts/ci_test_doc.sh index b1a7aff38a..0c3486b806 100644 --- a/scripts/ci_test_doc.sh +++ b/scripts/ci_test_doc.sh @@ -1,15 +1,27 @@ #!/bin/sh -# Check for doxygen. -command -v doxygen > /dev/null || { echo "Doxygen not found. Exiting."; exit 1; } +# Check arguments when running locally. If running on CI, install the correct +# version of doxygen. +if [ -z "$TRAVIS_PULL_REQUEST" ]; then + if [ $# -ne 1 ]; then + echo "Usage: ./generate_doc.sh sdk_root_directory" + exit 1 + fi -if [ $# -ne 1 ]; then - echo "Usage: ./generate_doc.sh sdk_root_directory" - exit 1 + # Change to SDK root directory. + cd $1 +else + set -ev + wget -O doxygen_source.tar.gz https://downloads.sourceforge.net/project/doxygen/rel-1.8.14/doxygen-1.8.14.src.tar.gz + tar xf doxygen_source.tar.gz + cmake doxygen-1.8.14 -DCMAKE_CXX_FLAGS="-w" + make -j2 + sudo make install + cd .. fi -# Change to SDK root directory. -cd $1 +# Check for doxygen. +command -v doxygen > /dev/null || { echo "Doxygen not found. Exiting."; exit 1; } # Create tag directory if needed. mkdir -p doc/tag From e49b5c7fe24f79ea9d35af37a943c549f97c741c Mon Sep 17 00:00:00 2001 From: Lorenzo Tessiore Date: Thu, 9 May 2019 15:14:07 -0700 Subject: [PATCH 133/844] Renamed variables in a an opaque type. (#411) * Renamed variables in a an opaque type * Fixed task pool doxygen documentation --- lib/include/iot_taskpool.h | 14 +++++++------- lib/include/types/iot_taskpool_types.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 0b62ada61d..0b141bd3eb 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -353,10 +353,10 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, * } JobUserContext_t; * * // An example of a user callback to invoke through a task pool thread. - * static void ExecutionCb( IotTaskPool_t * pTaskPool, IotTaskPoolJob_t pJob, void * context ) + * static void ExecutionCb( IotTaskPool_t taskPool, IotTaskPoolJob_t job, void * context ) * { - * ( void )pTaskPool; - * ( void )pJob; + * ( void )taskPool; + * ( void )job; * * JobUserContext_t * pUserContext = ( JobUserContext_t * )context; * @@ -367,7 +367,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, * { * JobUserContext_t userContext = { 0 }; * IotTaskPoolJob_t job; - * IotTaskPool_t * pTaskPool; + * IotTaskPool_t taskPool; * * // Configure the task pool to hold at least two threads and three at the maximum. * // Provide proper stack size and priority per the application needs. @@ -375,12 +375,12 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, * const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 3, .stackSize = 512, .priority = 0 }; * * // Create a task pool. - * IotTaskPool_Create( &tpInfo, &pTaskPool ); + * IotTaskPool_Create( &tpInfo, &taskPool ); * * // Statically allocate one job, schedule it. * IotTaskPool_CreateJob( &ExecutionCb, &userContext, &job ); * - * IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( pTaskPool, &job, 0 ); + * IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, &job, 0 ); * * switch ( errorSchedule ) * { @@ -400,7 +400,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPool, * // ... Perform other operations ... * // * - * IotTaskPool_Destroy( pTaskPool ); + * IotTaskPool_Destroy( taskPool ); * } * @endcode */ diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 6cc5cd2de3..89252fce27 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -221,11 +221,11 @@ typedef struct _taskPool * IotTaskPool_t; */ typedef struct IotTaskPoolJobStorage { - IotLink_t dummy1; /**< @brief Placeholder. */ + IotLink_t link; /**< @brief Placeholder. */ void * dummy2; /**< @brief Placeholder. */ void * dummy3; /**< @brief Placeholder. */ uint32_t dummy4; /**< @brief Placeholder. */ - IotTaskPoolJobStatus_t dummy5; /**< @brief Placeholder. */ + IotTaskPoolJobStatus_t status; /**< @brief Placeholder. */ } IotTaskPoolJobStorage_t; /** From 6ac63df2ebc799e1f70a4a538baa685253ce5040 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 10 May 2019 08:25:24 -0700 Subject: [PATCH 134/844] Remove usage of interface libraries (#416) --- CMakeLists.txt | 1 - lib/source/common/CMakeLists.txt | 10 +++------- lib/source/serializer/CMakeLists.txt | 2 +- platform/source/posix/CMakeLists.txt | 15 +-------------- third_party/mbedtls/CMakeLists.txt | 16 +++------------- 5 files changed, 8 insertions(+), 36 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ad0afeb31f..646a90eb16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,6 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) add_subdirectory( platform/source/posix ) elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON ) add_definitions( -DIOT_SYSTEM_TYPES_FILE="windows/iot_platform_types_windows.h" ) add_subdirectory( platform/source/windows ) endif() diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 1813fad12b..8fec8cac8e 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,6 +1,7 @@ -# Common libraries source files. The logging source is built in an interface library. +# Common libraries source files. set( COMMON_SOURCES iot_init.c + iot_logging.c iot_static_memory_common.c iot_taskpool.c iot_taskpool_static_memory.c ) @@ -20,17 +21,12 @@ set( COMMON_PRIVATE_HEADERS set( COMMON_TYPES_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h ) -# Make logging an interface library so that it may be linked independently. -add_library( iotlogging INTERFACE ) -target_sources( iotlogging INTERFACE ${PROJECT_SOURCE_DIR}/lib/source/common/iot_logging.c ) - # Common library target. add_library( iotcommon ${COMMON_SOURCES} ${COMMON_PUBLIC_HEADERS} ${COMMON_PRIVATE_HEADERS} ${COMMON_TYPES_HEADERS} ) # Link required libraries. target_link_libraries( iotcommon PUBLIC iotplatform ) -target_link_libraries( iotcommon PRIVATE iotlogging ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) @@ -42,4 +38,4 @@ set_property( TARGET iotcommon PROPERTY FOLDER "lib" ) source_group( include FILES ${COMMON_PUBLIC_HEADERS} ) source_group( include\\private FILES ${COMMON_PRIVATE_HEADERS} ) source_group( include\\types FILES ${COMMON_TYPES_HEADERS} ) -source_group( source FILES ${COMMON_SOURCES} iot_logging.c ) +source_group( source FILES ${COMMON_SOURCES} ) diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index 91e65d12fc..8279ccd257 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -12,7 +12,7 @@ add_library( iotserializer ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) # Link required libraries. -target_link_libraries( iotserializer PUBLIC tinycbor ) +target_link_libraries( iotserializer PRIVATE tinycbor ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 54a84a2469..5e84b711a0 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -71,19 +71,6 @@ else() set( TLS_LIBRARY_LINKER_FLAG mbedtls ) endif() -# Split platform into interface libraries. -add_library( iotplatformclock INTERFACE ) -target_sources( iotplatformclock INTERFACE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_clock_posix.c ) -target_link_libraries( iotplatformclock INTERFACE rt ) - -add_library( iotplatformthreads INTERFACE ) -target_sources( iotplatformthreads INTERFACE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_threads_posix.c ) -target_link_libraries( iotplatformthreads INTERFACE Threads::Threads ) - -add_library( iotplatformnetwork INTERFACE ) -target_sources( iotplatformnetwork INTERFACE ${NETWORK_SOURCE_FILE} ) -target_link_libraries( iotplatformnetwork INTERFACE ${TLS_LIBRARY_LINKER_FLAG} ) - # Platform libraries source files. add_library( iotplatform iot_clock_posix.c @@ -93,7 +80,7 @@ add_library( iotplatform # Link required libraries. target_link_libraries( iotplatform - PRIVATE iotplatformclock iotplatformthreads iotplatformnetwork ) + PRIVATE Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotplatform PRIVATE unity ) diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index 41495a267f..f1fe098bad 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -19,7 +19,6 @@ endif() # mbed TLS source files. set( MBEDTLS_SOURCES - iot_mbedtls_threading.c mbedtls/library/aes.c mbedtls/library/aesni.c mbedtls/library/asn1parse.c @@ -59,20 +58,14 @@ set( MBEDTLS_SOURCES mbedtls/library/x509.c mbedtls/library/x509_crt.c ) -# Choose the correct interface library source. -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - list( APPEND MBEDTLS_SOURCES ${PROJECT_SOURCE_DIR}/platform/source/posix/iot_threads_posix.c ) -elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - list( APPEND MBEDTLS_SOURCES ${PROJECT_SOURCE_DIR}/platform/source/windows/iot_threads_windows.c ) -endif() - # mbed TLS headers (for folder organization only). file( GLOB MBEDTLS_HEADERS "mbedtls/include/mbedtls/*.h" ) # mbed TLS library target. add_library( mbedtls ${MBEDTLS_SOURCES} ${MBEDTLS_HEADERS} - iot_config_mbedtls.h threading_alt.h ) + iot_config_mbedtls.h threading_alt.h + iot_mbedtls_threading.c ) # mbed TLS config header and include directories. target_include_directories( mbedtls SYSTEM @@ -81,9 +74,6 @@ target_include_directories( mbedtls SYSTEM target_compile_definitions( mbedtls PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) -# Link the platform threading component. -target_link_libraries( mbedtls PRIVATE iotplatformthreads ) - # Link the Unity test framework when testing. if( ${IOT_BUILD_TESTS} ) target_link_libraries( mbedtls PRIVATE unity ) @@ -101,4 +91,4 @@ target_compile_options( mbedtls set_property( TARGET mbedtls PROPERTY FOLDER "third_party" ) source_group( include\\mbedtls FILES ${MBEDTLS_HEADERS} ) source_group( source FILES ${MBEDTLS_SOURCES} ) -source_group( "" FILES iot_config_mbedtls.h threading_alt.h ) +source_group( "" FILES iot_config_mbedtls.h threading_alt.h iot_mbedtls_threading.c ) From 9db95624c17486c2816ddfc6f2f9e0f12cfb9e96 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 10 May 2019 10:16:56 -0700 Subject: [PATCH 135/844] Fix build of serializer tests (#417) --- tests/serializer/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt index 9875352ebe..30b3410142 100644 --- a/tests/serializer/CMakeLists.txt +++ b/tests/serializer/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable( iot_tests_serializer ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Serializer tests library dependencies. -target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotcommon unity ) +target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotcommon unity tinycbor ) # Organization of Serializer tests in folders. set_property( TARGET iot_tests_serializer PROPERTY FOLDER "tests" ) From 37fb874b20c95004d4857eef3bac10566c11bbc6 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 10 May 2019 14:23:08 -0700 Subject: [PATCH 136/844] Make demo runner cross platform (#418) --- CMakeLists.txt | 8 +- demos/app/CMakeLists.txt | 60 ++++ demos/app/iot_demo.c | 325 +++++++++++++++++++++ demos/app/posix/CMakeLists.txt | 36 --- demos/app/posix/iot_demo_arguments_posix.c | 123 +------- demos/app/posix/iot_demo_posix.c | 207 ------------- demos/include/iot_demo_arguments.h | 7 +- platform/source/posix/iot_threads_posix.c | 24 +- 8 files changed, 402 insertions(+), 388 deletions(-) create mode 100644 demos/app/CMakeLists.txt create mode 100644 demos/app/iot_demo.c delete mode 100644 demos/app/posix/CMakeLists.txt delete mode 100644 demos/app/posix/iot_demo_posix.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 646a90eb16..ecc73c71fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,12 +107,8 @@ add_subdirectory( third_party/tinycbor ) # mbed TLS library (third-party). add_subdirectory( third_party/mbedtls ) -# Determine the demo executable to build based on system. -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - add_subdirectory( demos/app/posix ) -elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - add_subdirectory( demos/app/windows ) -endif() +# Build demo executable. +add_subdirectory( demos/app ) # Test executables. if( ${IOT_BUILD_TESTS} ) diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt new file mode 100644 index 0000000000..c04f9aefe1 --- /dev/null +++ b/demos/app/CMakeLists.txt @@ -0,0 +1,60 @@ +# Source file for parsing arguments. +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + set( DEMO_ARGUMENTS_SOURCE_FILE posix/iot_demo_arguments_posix.c ) +endif() + +# Common demo source files. +set( DEMO_COMMON_SOURCES + ${DEMO_ARGUMENTS_SOURCE_FILE} + iot_demo.c ) + +# Common demo header files. +set( DEMO_COMMON_HEADERS + ${PROJECT_SOURCE_DIR}/demos/include/iot_demo_arguments.h + ${PROJECT_SOURCE_DIR}/demos/include/iot_demo_logging.h ) + +# MQTT demo source files. +add_executable( iot_demo_mqtt + ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c + ${DEMO_COMMON_SOURCES} + ${DEMO_COMMON_HEADERS} ) + +# Select the demo function for the MQTT demo. +target_compile_definitions( iot_demo_mqtt + PRIVATE RunDemo=RunMqttDemo ) + +# MQTT demo library dependencies. +target_link_libraries( iot_demo_mqtt PRIVATE iotmqtt ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iot_demo_mqtt PRIVATE unity ) +endif() + +# Organization of MQTT demo in folders. +set_property( TARGET iot_demo_mqtt PROPERTY FOLDER "demos" ) +source_group( include FILES ${DEMO_COMMON_HEADERS} ) +source_group( "" FILES ${DEMO_COMMON_SOURCES} + ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c ) + +# Shadow demo source files. +add_executable( aws_iot_demo_shadow + ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c + ${DEMO_COMMON_SOURCES} + ${DEMO_COMMON_HEADERS} ) + +# Select the demo function for the Shadow demo. +target_compile_definitions( aws_iot_demo_shadow + PRIVATE RunDemo=RunShadowDemo ) + +# Shadow demo library dependencies. +target_link_libraries( aws_iot_demo_shadow PRIVATE awsiotshadow ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( aws_iot_demo_shadow PRIVATE unity ) +endif() + +# Organization of Shadow demo in folders. +set_property( TARGET aws_iot_demo_shadow PROPERTY FOLDER "demos" ) +source_group( include FILES ${DEMO_COMMON_HEADERS} ) +source_group( "" FILES ${DEMO_COMMON_SOURCES} + ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c ) diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c new file mode 100644 index 0000000000..10ccc4b5fa --- /dev/null +++ b/demos/app/iot_demo.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_demo.c + * @brief Generic demo runner. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include +#include + +/* SDK initialization include. */ +#include "iot_init.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Common demo includes. */ +#include "iot_demo_arguments.h" +#include "iot_demo_logging.h" + +/* Choose the appropriate network header, initializers, and initialization + * function. */ +#if IOT_NETWORK_USE_OPENSSL == 1 + /* POSIX+OpenSSL network include. */ + #include "posix/iot_network_openssl.h" + + #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL + #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER + #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER + + #define IotDemoNetwork_Init IotNetworkOpenssl_Init + #define IotDemoNetwork_Cleanup IotNetworkOpenssl_Cleanup +#else + /* mbed TLS network include. */ + #include "iot_network_mbedtls.h" + + #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS + #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER + #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER + + #define IotDemoNetwork_Init IotNetworkMbedtls_Init + #define IotDemoNetwork_Cleanup IotNetworkMbedtls_Cleanup +#endif /* if IOT_NETWORK_USE_OPENSSL == 1 */ + +/* This file calls a generic placeholder demo function. The build system selects + * the actual demo function to run by defining it. */ +#ifndef RunDemo + #error "Demo function undefined." +#endif + +/*-----------------------------------------------------------*/ + +/* Declaration of generic demo function. */ +extern int RunDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Set the default values of an #IotDemoArguments_t based on compile-time + * defined constants. + * + * @param[out] pArguments Default values will be placed here. + */ +static void _setDefaultArguments( IotDemoArguments_t * pArguments ) +{ + /* Default to AWS IoT MQTT mode. */ + pArguments->awsIotMqttMode = true; + + /* Set default secured connection status if defined. */ + #ifdef IOT_DEMO_SECURED_CONNECTION + pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; + #endif + + /* Set default MQTT server if defined. */ + #ifdef IOT_DEMO_SERVER + pArguments->pHostName = IOT_DEMO_SERVER; + #endif + + /* Set default MQTT server port if defined. */ + #ifdef IOT_DEMO_PORT + pArguments->port = IOT_DEMO_PORT; + #endif + + /* Set default root CA path if defined. */ + #ifdef IOT_DEMO_ROOT_CA + pArguments->pRootCaPath = IOT_DEMO_ROOT_CA; + #endif + + /* Set default client certificate path if defined. */ + #ifdef IOT_DEMO_CLIENT_CERT + pArguments->pClientCertPath = IOT_DEMO_CLIENT_CERT; + #endif + + /* Set default client certificate private key path if defined. */ + #ifdef IOT_DEMO_PRIVATE_KEY + pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; + #endif +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Validates the members of an #IotDemoArguments_t. + * + * @param[in] pArguments The #IotDemoArguments_t to validate. + * + * @return `true` if every member of the #IotDemoArguments_t is valid; `false` + * otherwise. + */ +static bool _validateArguments( const IotDemoArguments_t * pArguments ) +{ + /* Declare a status variable for this function. */ + IOT_FUNCTION_ENTRY( bool, true ); + + /* Check that a server was set. */ + if( ( pArguments->pHostName == NULL ) || + ( strlen( pArguments->pHostName ) == 0 ) ) + { + IotLogError( "MQTT server not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a server port was set. */ + if( pArguments->port == 0 ) + { + IotLogError( "MQTT server port not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check credentials for a secured connection. */ + if( pArguments->securedConnection == true ) + { + /* Check that a root CA path was set. */ + if( ( pArguments->pRootCaPath == NULL ) || + ( strlen( pArguments->pRootCaPath ) == 0 ) ) + { + IotLogError( "Root CA path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + IotLogError( "Client certificate private key not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + else + { + if( pArguments->awsIotMqttMode == true ) + { + IotLogError( "AWS IoT does not support unsecured connections." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + /* No cleanup is required for this function. */ + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + /* Return value of this function and the exit status of this program. */ + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + + /* Status returned from network stack initialization. */ + IotNetworkError_t networkInitStatus = IOT_NETWORK_SUCCESS; + + /* Flags for tracking which cleanup functions must be called. */ + bool sdkInitialized = false, networkInitialized = false; + + /* Arguments for this demo. */ + IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; + + /* Network server info and credentials. */ + IotNetworkServerInfo_t serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; + IotNetworkCredentials_t credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, + * pCredentials = NULL; + + /* Set default identifier if defined. The identifier is used as either the + * MQTT client identifier or the Thing Name, which identifies this client to + * the cloud. */ + #ifdef IOT_DEMO_IDENTIFIER + demoArguments.pIdentifier = IOT_DEMO_IDENTIFIER; + #endif + + /* Load the default demo arguments from the demo config header. */ + _setDefaultArguments( &demoArguments ); + + /* Parse any command line arguments. */ + IotDemo_ParseArguments( argc, + argv, + &demoArguments ); + + /* Validate arguments. */ + if( _validateArguments( &demoArguments ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + /* Set the members of the server info. */ + serverInfo.pHostName = demoArguments.pHostName; + serverInfo.port = demoArguments.port; + + /* For a secured connection, set the members of the credentials. */ + if( demoArguments.securedConnection == true ) + { + /* Set credential paths. */ + credentials.pClientCert = demoArguments.pClientCertPath; + credentials.pPrivateKey = demoArguments.pPrivateKeyPath; + credentials.pRootCa = demoArguments.pRootCaPath; + + /* By default, the credential initializer enables ALPN with AWS IoT, + * which only works over port 443. Disable ALPN if another port is + * used. */ + if( demoArguments.port != 443 ) + { + credentials.pAlpnProtos = NULL; + } + + /* Set the pointer to the credentials. */ + pCredentials = &credentials; + } + + /* Call the SDK initialization function. */ + sdkInitialized = IotSdk_Init(); + + if( sdkInitialized == false ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + /* Initialize the network stack. */ + networkInitStatus = IotDemoNetwork_Init(); + + if( networkInitStatus == IOT_NETWORK_SUCCESS ) + { + networkInitialized = true; + } + else + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + /* Run the demo. */ + status = RunDemo( demoArguments.awsIotMqttMode, + demoArguments.pIdentifier, + &serverInfo, + pCredentials, + IOT_DEMO_NETWORK_INTERFACE ); + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up the network stack if initialized. */ + if( networkInitialized == true ) + { + IotDemoNetwork_Cleanup(); + } + + /* Clean up the SDK if initialized. */ + if( sdkInitialized == true ) + { + IotSdk_Cleanup(); + } + + /* Log the demo status. */ + if( status == EXIT_SUCCESS ) + { + IotLogInfo( "Demo completed successfully." ); + } + else + { + IotLogError( "Error occurred while running demo." ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ diff --git a/demos/app/posix/CMakeLists.txt b/demos/app/posix/CMakeLists.txt deleted file mode 100644 index e405d5b905..0000000000 --- a/demos/app/posix/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -# Common demo files. -set( DEMO_COMMON_SOURCE_FILES - iot_demo_arguments_posix.c - iot_demo_posix.c ) - -# MQTT demo source files. -add_executable( iot_demo_mqtt - ${CMAKE_SOURCE_DIR}/demos/source/iot_demo_mqtt.c - ${DEMO_COMMON_SOURCE_FILES} ) - -# Select the demo function for the MQTT demo. -target_compile_definitions( iot_demo_mqtt - PRIVATE RunDemo=RunMqttDemo ) - -# MQTT demo library dependencies. -target_link_libraries( iot_demo_mqtt PRIVATE iotmqtt ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iot_demo_mqtt PRIVATE unity ) -endif() - -# Shadow demo source files. -add_executable( aws_iot_demo_shadow - ${CMAKE_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c - ${DEMO_COMMON_SOURCE_FILES} ) - -# Select the demo function for the Shadow demo. -target_compile_definitions( aws_iot_demo_shadow - PRIVATE RunDemo=RunShadowDemo ) - -# Shadow demo library dependencies. -target_link_libraries( aws_iot_demo_shadow PRIVATE awsiotshadow ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( aws_iot_demo_shadow PRIVATE unity ) -endif() diff --git a/demos/app/posix/iot_demo_arguments_posix.c b/demos/app/posix/iot_demo_arguments_posix.c index 7434125a59..12b9f281e0 100644 --- a/demos/app/posix/iot_demo_arguments_posix.c +++ b/demos/app/posix/iot_demo_arguments_posix.c @@ -31,139 +31,21 @@ /* Standard includes. */ #include #include -#include #include -/* Error handling include. */ -#include "private/iot_error.h" - /* Common demo includes. */ #include "iot_demo_arguments.h" #include "iot_demo_logging.h" /*-----------------------------------------------------------*/ -/** - * @brief Set the default values of an #IotDemoArguments_t based on compile-time - * defined constants. - * - * @param[out] pArguments Default values will be placed here. - */ -static void _setDefaultArguments( IotDemoArguments_t * pArguments ) -{ - /* Default to AWS IoT MQTT mode. */ - pArguments->awsIotMqttMode = true; - - /* Set default secured connection status if defined. */ - #ifdef IOT_DEMO_SECURED_CONNECTION - pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; - #endif - - /* Set default MQTT server if defined. */ - #ifdef IOT_DEMO_SERVER - pArguments->pHostName = IOT_DEMO_SERVER; - #endif - - /* Set default MQTT server port if defined. */ - #ifdef IOT_DEMO_PORT - pArguments->port = IOT_DEMO_PORT; - #endif - - /* Set default root CA path if defined. */ - #ifdef IOT_DEMO_ROOT_CA - pArguments->pRootCaPath = IOT_DEMO_ROOT_CA; - #endif - - /* Set default client certificate path if defined. */ - #ifdef IOT_DEMO_CLIENT_CERT - pArguments->pClientCertPath = IOT_DEMO_CLIENT_CERT; - #endif - - /* Set default client certificate private key path if defined. */ - #ifdef IOT_DEMO_PRIVATE_KEY - pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; - #endif -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Validates the members of an #IotDemoArguments_t. - * - * @param[in] pArguments The #IotDemoArguments_t to validate. - * - * @return `true` if every member of the #IotDemoArguments_t is valid; `false` - * otherwise. - */ -static bool _validateArguments( const IotDemoArguments_t * pArguments ) -{ - /* Declare a status variable for this function. */ - IOT_FUNCTION_ENTRY( bool, true ); - - /* Check that a server was set. */ - if( ( pArguments->pHostName == NULL ) || - ( strlen( pArguments->pHostName ) == 0 ) ) - { - IotLogError( "MQTT server not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a server port was set. */ - if( pArguments->port == 0 ) - { - IotLogError( "MQTT server port not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check credentials for a secured connection. */ - if( pArguments->securedConnection == true ) - { - /* Check that a root CA path was set. */ - if( ( pArguments->pRootCaPath == NULL ) || - ( strlen( pArguments->pRootCaPath ) == 0 ) ) - { - IotLogError( "Root CA path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Client certificate private key not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* No cleanup is required for this function. */ - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -bool IotDemo_ParseArguments( int argc, +void IotDemo_ParseArguments( int argc, char ** argv, IotDemoArguments_t * pArguments ) { int option = 0; unsigned long int port = 0; - /* Set default arguments based on compile-time constants. */ - _setDefaultArguments( pArguments ); - IotLogInfo( "Parsing command line arguments." ); /* Silence any error or warning messages printed by the system. The demos @@ -268,9 +150,6 @@ bool IotDemo_ParseArguments( int argc, IotLogDebug( "Root CA: %s", pArguments->pRootCaPath ); IotLogDebug( "Client certificate: %s", pArguments->pClientCertPath ); IotLogDebug( "Private key: %s", pArguments->pPrivateKeyPath ); - - /* Validate the arguments and return the value of that check. */ - return _validateArguments( pArguments ); } /*-----------------------------------------------------------*/ diff --git a/demos/app/posix/iot_demo_posix.c b/demos/app/posix/iot_demo_posix.c deleted file mode 100644 index c1fbd2fe71..0000000000 --- a/demos/app/posix/iot_demo_posix.c +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_posix.c - * @brief Generic demo runner for POSIX systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* SDK initialization include. */ -#include "iot_init.h" - -/* Common demo includes. */ -#include "iot_demo_arguments.h" -#include "iot_demo_logging.h" - -/* Choose the appropriate network header, initializers, and initialization - * function. */ -#if IOT_NETWORK_USE_OPENSSL == 1 - /* POSIX+OpenSSL network include. */ - #include "posix/iot_network_openssl.h" - - #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL - #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER - #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_OPENSSL_INITIALIZER - - #define IotDemoNetwork_Init IotNetworkOpenssl_Init - #define IotDemoNetwork_Cleanup IotNetworkOpenssl_Cleanup -#else - /* mbed TLS network include. */ - #include "iot_network_mbedtls.h" - - #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_MBEDTLS - #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER - #define IOT_DEMO_CREDENTIALS_INITIALIZER AWS_IOT_NETWORK_CREDENTIALS_MBEDTLS_INITIALIZER - - #define IotDemoNetwork_Init IotNetworkMbedtls_Init - #define IotDemoNetwork_Cleanup IotNetworkMbedtls_Cleanup -#endif - -/* This file calls a generic placeholder demo function. The build system selects - * the actual demo function to run by defining it. */ -#ifndef RunDemo - #error "Demo function undefined." -#endif - -/*-----------------------------------------------------------*/ - -/* Declaration of generic demo function. */ -extern int RunDemo( bool awsIotMqttMode, - const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ); - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - /* Return value of this function and the exit status of this program. */ - int status = EXIT_SUCCESS; - - /* Status returned from network stack initialization. */ - IotNetworkError_t networkInitStatus = IOT_NETWORK_SUCCESS; - - /* Flags for tracking which cleanup functions must be called. */ - bool sdkInitialized = false, networkInitialized = false; - - /* Arguments for this demo. */ - IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; - - /* Network server info and credentials. */ - IotNetworkServerInfo_t serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; - IotNetworkCredentials_t credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, - * pCredentials = NULL; - - /* Set default identifier if defined. The identifier is used as either the - * MQTT client identifier or the Thing Name, which identifies this client to - * the cloud. */ - #ifdef IOT_DEMO_IDENTIFIER - demoArguments.pIdentifier = IOT_DEMO_IDENTIFIER; - #endif - - /* Parse any command line arguments. */ - if( IotDemo_ParseArguments( argc, - argv, - &demoArguments ) == true ) - { - /* Set the members of the server info. */ - serverInfo.pHostName = demoArguments.pHostName; - serverInfo.port = demoArguments.port; - - /* For a secured connection, set the members of the credentials. */ - if( demoArguments.securedConnection == true ) - { - /* Set credential paths. */ - credentials.pClientCert = demoArguments.pClientCertPath; - credentials.pPrivateKey = demoArguments.pPrivateKeyPath; - credentials.pRootCa = demoArguments.pRootCaPath; - - /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Disable ALPN if another port is - * used. */ - if( demoArguments.port != 443 ) - { - credentials.pAlpnProtos = NULL; - } - - /* Set the pointer to the credentials. */ - pCredentials = &credentials; - } - } - else - { - /* Failed to parse arguments. */ - status = EXIT_FAILURE; - } - - /* Call the SDK initialization function. */ - if( status == EXIT_SUCCESS ) - { - sdkInitialized = IotSdk_Init(); - - if( sdkInitialized == false ) - { - status = EXIT_FAILURE; - } - } - - /* Initialize the network stack. */ - if( status == EXIT_SUCCESS ) - { - networkInitStatus = IotDemoNetwork_Init(); - - if( networkInitStatus == IOT_NETWORK_SUCCESS ) - { - networkInitialized = true; - } - else - { - /* Network stack failed to initialize. */ - status = EXIT_FAILURE; - } - } - - /* Run the demo. */ - if( status == EXIT_SUCCESS ) - { - status = RunDemo( demoArguments.awsIotMqttMode, - demoArguments.pIdentifier, - &serverInfo, - pCredentials, - IOT_DEMO_NETWORK_INTERFACE ); - } - - /* Clean up the SDK if initialized. */ - if( sdkInitialized == true ) - { - IotSdk_Cleanup(); - } - - /* Clean up the network stack if initialized. */ - if( networkInitialized == true ) - { - IotDemoNetwork_Cleanup(); - } - - /* Log the demo status. */ - if( status == EXIT_SUCCESS ) - { - IotLogInfo( "Demo completed successfully." ); - } - else - { - IotLogError( "Error occurred while running demo." ); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index c809f165d8..0a738ff117 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -29,6 +29,7 @@ #define IOT_DEMO_ARGUMENTS_H_ /* Standard includes. */ +#include #include /** @@ -86,12 +87,8 @@ typedef struct IotDemoArguments * @param[in] argc The argument count originally passed to main(). * @param[in] argv The argument vector originally passed to main(). * @param[out] pArguments Set to the arguments parsed from the command line. - * - * @return `true` if arguments were successfully parsed and all necessary variables - * were set; `false` otherwise. If this function returns `false`, the demo program - * should exit. */ -bool IotDemo_ParseArguments( int argc, +void IotDemo_ParseArguments( int argc, char ** argv, IotDemoArguments_t * pArguments ); diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index a6b2d18f2f..dd06f212d1 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -93,6 +93,17 @@ /*-----------------------------------------------------------*/ +/** + * @brief Holds information about an active detached thread. + */ +typedef struct _threadInfo +{ + void * pArgument; /**< @brief First argument to `threadRoutine`. */ + IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ +} _threadInfo_t; + +/*-----------------------------------------------------------*/ + /** * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. * @@ -108,17 +119,6 @@ extern bool IotClock_TimeoutToTimespec( uint32_t timeoutMs, /*-----------------------------------------------------------*/ -/** - * @brief Holds information about an active detached thread. - */ -typedef struct _threadInfo -{ - void * pArgument; /**< @brief First argument to `threadRoutine`. */ - IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ -} _threadInfo_t; - -/*-----------------------------------------------------------*/ - static void * _threadRoutineWrapper( void * pArgument ) { _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; @@ -155,7 +155,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IotLogDebug( "Creating new thread." ); - /* Allocate memory for the new thread. */ + /* Allocate memory for the new thread info. */ pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); if( pThreadInfo == NULL ) From 3a89bf8c74473906fc80c17b542f82a152686c23 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 10 May 2019 15:14:47 -0700 Subject: [PATCH 137/844] Check identifier in Shadow demo. (#419) --- demos/source/aws_iot_demo_shadow.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 2bdcddad71..4250ba3f33 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -798,7 +798,7 @@ int RunShadowDemo( bool awsIotMqttMode, IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /* Length of Shadow Thing Name. */ - const size_t thingNameLength = strlen( pIdentifier ); + size_t thingNameLength = 0; /* Allows the Shadow update function to wait for the delta callback to complete * a state change before continuing. */ @@ -811,8 +811,30 @@ int RunShadowDemo( bool awsIotMqttMode, * to AWS IoT, so this value is hardcoded to true whenever needed. */ ( void ) awsIotMqttMode; + /* Determine the length of the Thing Name. */ + if( pIdentifier != NULL ) + { + thingNameLength = strlen( pIdentifier ); + + if( thingNameLength == 0 ) + { + IotLogError( "The length of the Thing Name (identifier) must be nonzero." ); + + status = EXIT_FAILURE; + } + } + else + { + IotLogError( "A Thing Name (identifier) must be provided for the Shadow demo." ); + + status = EXIT_FAILURE; + } + /* Initialize the libraries required for this demo. */ - status = _initializeDemo(); + if( status == EXIT_SUCCESS ) + { + status = _initializeDemo(); + } if( status == EXIT_SUCCESS ) { From 18c9e47620c09856c29ebff8f7a07d038f7947d3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 13 May 2019 09:12:01 -0700 Subject: [PATCH 138/844] Move atomic documentation to generic header (#420) --- platform/include/atomic/iot_atomic_gcc.h | 225 +++++++++-------------- tests/common/unit/iot_tests_atomic.c | 8 +- tests/common/unit/iot_tests_taskpool.c | 2 +- tests/defender/CMakeLists.txt | 2 +- 4 files changed, 91 insertions(+), 146 deletions(-) diff --git a/platform/include/atomic/iot_atomic_gcc.h b/platform/include/atomic/iot_atomic_gcc.h index f133cf7975..54bc858163 100644 --- a/platform/include/atomic/iot_atomic_gcc.h +++ b/platform/include/atomic/iot_atomic_gcc.h @@ -26,8 +26,8 @@ * Compatible with GCC and clang. */ -#ifndef IOT_ATOMIC_GCC_H -#define IOT_ATOMIC_GCC_H +#ifndef IOT_ATOMIC_GCC_H_ +#define IOT_ATOMIC_GCC_H_ /* Standard includes. */ #include @@ -38,208 +38,153 @@ */ #define FORCE_INLINE inline __attribute__( ( always_inline ) ) -/*----------------------- Swap and Compare-and-Swap -------------------------*/ +/*---------------- Swap and compare-and-swap ------------------*/ /** - * Atomic compare-and-swap - * - * @brief Performs an atomic compare-and-swap operation on the specified values. - * - * @param[in, out] pDestination Pointer to memory location from where value is to be loaded and checked. - * @param[in] ulExchange If condition meets, write this value to memory. - * @param[in] ulComparand Swap condition, checks and waits for *pDestination to be equal to ulComparand. - * - * @return unsigned integer of value 1 or 0. 1 for swapped, 0 for not swapped. - * - * @note This function only swaps *pDestination with ulExchange, if previous *pDestination value equals ulComparand. + * @brief Implementation of atomic compare-and-swap for gcc. */ static FORCE_INLINE uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestination, - uint32_t ulExchange, - uint32_t ulComparand ) + uint32_t newValue, + uint32_t comparand ) { - uint32_t ulReturnValue = 0; - - if( __atomic_compare_exchange( pDestination, &ulComparand, &ulExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) + uint32_t swapped = 0; + + if( __atomic_compare_exchange( pDestination, + &comparand, + &newValue, + false, + __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST ) == true ) { - ulReturnValue = 1; + swapped = 1; } - return ulReturnValue; + return swapped; } +/*-----------------------------------------------------------*/ + /** - * Atomic swap (pointers) - * - * @brief Atomically sets the address pointed to by *ppDestination to the value of *pExchange. - * - * @param[in, out] ppDestination Pointer to memory location from where a pointer value is to be loaded and writen back to. - * @param[in] pExchange Pointer value to be written to *ppDestination. - * - * @return The initial value of *ppDestination. + * @brief Implementation of atomic pointer swap for gcc. */ -static FORCE_INLINE void * Atomic_SwapPointers_p32( void * volatile * ppDestination, - void * pExchange ) +static FORCE_INLINE void * Atomic_Swap_Pointer( void * volatile * pDestination, + void * pNewValue ) { - void * pReturnValue; + void * pOldValue = NULL; - __atomic_exchange( ppDestination, &pExchange, &pReturnValue, __ATOMIC_SEQ_CST ); + __atomic_exchange( pDestination, &pNewValue, &pOldValue, __ATOMIC_SEQ_CST ); - return pReturnValue; + return pOldValue; } +/*-----------------------------------------------------------*/ + /** - * Atomic compare-and-swap (pointers) - * - * @brief Performs an atomic compare-and-swap operation on the specified pointer values. - * - * @param[in, out] ppDestination Pointer to memory location from where a pointer value is to be loaded and checked. - * @param[in] pExchange If condition meets, write this value to memory. - * @param[in] pComparand Swap condition, checks and waits for *ppDestination to be equal to *pComparand. - * - * @return unsigned integer of value 1 or 0. 1 for swapped, 0 for not swapped. - * - * @note This function only swaps *ppDestination with pExchange, if previous *ppDestination value equals pComparand. + * @brief Implementation of atomic pointer compare-and-swap for gcc. */ -static FORCE_INLINE uint32_t Atomic_CompareAndSwapPointers_p32( void * volatile * ppDestination, - void * pExchange, - void * pComparand ) +static FORCE_INLINE uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pDestination, + void * pNewValue, + void * pComparand ) { - uint32_t ulReturnValue = 0; - - if( __atomic_compare_exchange( ppDestination, &pComparand, &pExchange, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) + uint32_t swapped = 0; + + if( __atomic_compare_exchange( pDestination, + &pComparand, + &pNewValue, + false, + __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST ) == true ) { - ulReturnValue = 1; + swapped = 1; } - return ulReturnValue; + return swapped; } - -/*----------------------------- Arithmetic ------------------------------*/ +/*----------------------- Arithmetic --------------------------*/ /** - * Atomic add - * - * @brief Adds count to the value of the specified 32-bit variable as an atomic operation. - * - * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. - * @param[in] lCount Value to be added to *pAddend. - * - * @return previous *pAddend value. + * @brief Implementation of atomic addition for gcc. */ -static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAddend, - uint32_t lCount ) +static FORCE_INLINE uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, + uint32_t addend ) { - return __atomic_fetch_add( pAddend, lCount, __ATOMIC_SEQ_CST ); + return __atomic_fetch_add( pAugend, addend, __ATOMIC_SEQ_CST ); } +/*-----------------------------------------------------------*/ + /** - * Atomic sub - * - * @brief Subtracts count from the value of the specified 32-bit variable as an atomic operation. - * - * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. - * @param[in] lCount Value to be subtract from *pAddend. - * - * @return previous *pAddend value. + * @brief Implementation of atomic subtraction for gcc. */ -static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pAddend, - uint32_t lCount ) +static FORCE_INLINE uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, + uint32_t subtrahend ) { - return __atomic_fetch_sub( pAddend, lCount, __ATOMIC_SEQ_CST ); + return __atomic_fetch_sub( pMinuend, subtrahend, __ATOMIC_SEQ_CST ); } +/*-----------------------------------------------------------*/ + /** - * Atomic increment - * - * @brief Increments the value of the specified 32-bit variable as an atomic operation. - * - * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. - * - * @return *pAddend value before increment. + * @brief Implementation of atomic increment for gcc. */ -static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAddend ) +static FORCE_INLINE uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) { - return __atomic_fetch_add( pAddend, 1, __ATOMIC_SEQ_CST ); + return __atomic_fetch_add( pAugend, 1, __ATOMIC_SEQ_CST ); } +/*-----------------------------------------------------------*/ + /** - * Atomic decrement - * - * @brief Decrements the value of the specified 32-bit variable as an atomic operation. - * - * @param[in,out] pAddend Pointer to memory location from where value is to be loaded and written back to. - * - * @return *pAddend value before decrement. + * @brief Implementation of atomic decrement for gcc. */ -static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pAddend ) +static FORCE_INLINE uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend ) { - return __atomic_fetch_sub( pAddend, 1, __ATOMIC_SEQ_CST ); + return __atomic_fetch_sub( pMinuend, 1, __ATOMIC_SEQ_CST ); } -/*----------------------------- Bitwise Logical ------------------------------*/ +/*--------------------- Bitwise logic -------------------------*/ /** - * Atomic OR - * - * @brief Performs an atomic OR operation on the specified values. - * - * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. - * @param [in] ulValue Value to be ORed with *pDestination. - * - * @return The original value of *pDestination. + * @brief Implementation of atomic OR for gcc. */ -static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pDestination, - uint32_t ulValue ) +static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, + uint32_t mask ) { - return __atomic_fetch_or( pDestination, ulValue, __ATOMIC_SEQ_CST ); + return __atomic_fetch_or( pOperand, mask, __ATOMIC_SEQ_CST ); } +/*-----------------------------------------------------------*/ + /** - * Atomic AND - * - * @brief Performs an atomic AND operation on the specified values. - * - * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. - * @param [in] ulValue Value to be ANDed with *pDestination. - * - * @return The original value of *pDestination. + * @brief Implementation of atomic AND for gcc. */ -static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pDestination, - uint32_t ulValue ) +static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, + uint32_t mask ) { - return __atomic_fetch_and( pDestination, ulValue, __ATOMIC_SEQ_CST ); + return __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ); } +/*-----------------------------------------------------------*/ + /** - * Atomic NAND - * - * @brief Performs an atomic NAND operation on the specified values. - * - * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. - * @param [in] ulValue Value to be NANDed with *pDestination. - * - * @return The original value of *pDestination. + * @brief Implementation of atomic NAND for gcc. */ -static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pDestination, - uint32_t ulValue ) +static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, + uint32_t mask ) { - return __atomic_fetch_nand( pDestination, ulValue, __ATOMIC_SEQ_CST ); + return __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ); } +/*-----------------------------------------------------------*/ + /** - * Atomic XOR - * @brief Performs an atomic XOR operation on the specified values. - * - * @param [in, out] pDestination Pointer to memory location from where value is to be loaded and written back to. - * @param [in] ulValue Value to be XORed with *pDestination. - * - * @return The original value of *pDestination. + * @brief Implementation of atomic XOR for gcc. */ -static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pDestination, - uint32_t ulValue ) +static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, + uint32_t mask ) { - return __atomic_fetch_xor( pDestination, ulValue, __ATOMIC_SEQ_CST ); + return __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ); } -#endif /* IOT_ATOMIC_GCC_H */ +#endif /* IOT_ATOMIC_GCC_H_ */ diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index 8a7b8fc903..c27ef27861 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -135,7 +135,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pReturnValue_32 = NULL; IOT_TEST_ASM_VOLATILE( "atomic_xchg_32bit: " ); - pReturnValue_32 = Atomic_SwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32 ); + pReturnValue_32 = Atomic_Swap_Pointer( ( void ** ) &pSwapDestination_32, pSwapNewValue_32 ); IOT_TEST_ASM_VOLATILE( "atomic_xchg_32bit_end: " ); TEST_ASSERT_MESSAGE( pSwapDestination_32 == &ulCasNewValue_32, "Atomic_SwapPointers_p32 -- did not swap." ); @@ -147,7 +147,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pReturnValue_8 = NULL; IOT_TEST_ASM_VOLATILE( "atomic_xchg_8bit: nop" ); - pReturnValue_8 = Atomic_SwapPointers_p32( ( void ** ) &pSwapDestination_8, pSwapNewValue_8 ); + pReturnValue_8 = Atomic_Swap_Pointer( ( void ** ) &pSwapDestination_8, pSwapNewValue_8 ); IOT_TEST_ASM_VOLATILE( "atomic_xchg_8bit_end: nop" ); TEST_ASSERT_MESSAGE( pSwapDestination_8 == &uCasComparator_8, "Atomic_SwapPointers_p32 -- did not swap." ); @@ -158,7 +158,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapNewValue_32 = &ulCasNewValue_32; IOT_TEST_ASM_VOLATILE( "atomic_CAS_pointers: nop" ); - ulReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); + ulReturnValue_32 = Atomic_CompareAndSwap_Pointer( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); IOT_TEST_ASM_VOLATILE( "atomic_CAS_pointers_end: nop" ); TEST_ASSERT_MESSAGE( ( intptr_t ) pSwapDestination_32 == ( intptr_t ) pSwapNewValue_32, "Atomic_CompareAndSwapPointers_p32 -- did not swap." ); @@ -350,7 +350,7 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) pCasNewValue_32 = &ulCasNewValue_32; IOT_TEST_ASM_VOLATILE( "atomic_cas_pointers_neq: " ); - ulReturnValue_32 = Atomic_CompareAndSwapPointers_p32( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); + ulReturnValue_32 = Atomic_CompareAndSwap_Pointer( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); IOT_TEST_ASM_VOLATILE( "atomic_cas_pointers_neq_end: " ); TEST_ASSERT_MESSAGE( ( intptr_t ) pCasDestination_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 530fbd7a3b..0654553f22 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -60,7 +60,7 @@ typedef struct JobUserContext /** * @brief The initializer for the user context. */ -#define IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER { { 0 }, 0 }; +#define IOT_TASKPOOL_TEST_JOB_CONTEXT_INITIALIZER { .counter = 0 }; /** * @brief A simple user context to prove the taskpool grows as expected. diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt index 4e9e495eb3..c815415efa 100644 --- a/tests/defender/CMakeLists.txt +++ b/tests/defender/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable( aws_iot_tests_defender ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) # Defender tests library dependencies. -target_link_libraries( aws_iot_tests_defender PRIVATE awsiotdefender unity ) +target_link_libraries( aws_iot_tests_defender PRIVATE awsiotdefender tinycbor unity ) # Organization of Defender tests in folders. set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER "tests" ) From 04e98e6d330eba144e3129d75b3bee4a3b11f0dd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 13 May 2019 10:50:09 -0700 Subject: [PATCH 139/844] Add generic atomic implementation (#421) --- doc/config/main | 3 +- doc/config/platform | 6 +- doc/guide/{ => developer}/developer.txt | 0 doc/guide/{ => developer}/porting.txt | 40 +-- doc/guide/{ => developer}/style.txt | 0 doc/lib/platform.txt | 200 +++++++---- lib/source/common/iot_init.c | 37 +- platform/include/atomic/iot_atomic_gcc.h | 22 +- platform/include/atomic/iot_atomic_generic.h | 358 +++++++++++++++++++ 9 files changed, 551 insertions(+), 115 deletions(-) rename doc/guide/{ => developer}/developer.txt (100%) rename doc/guide/{ => developer}/porting.txt (71%) rename doc/guide/{ => developer}/style.txt (100%) create mode 100644 platform/include/atomic/iot_atomic_generic.h diff --git a/doc/config/main b/doc/config/main index ea9e618f36..48932c13ef 100644 --- a/doc/config/main +++ b/doc/config/main @@ -13,7 +13,8 @@ GENERATE_TAGFILE = doc/tag/main.tag # Input directories. INPUT = doc/ \ - doc/guide + doc/guide \ + doc/guide/developer # Library file names. FILE_PATTERNS = *.txt diff --git a/doc/config/platform b/doc/config/platform index 2067aa8471..db9ddb67e2 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -15,7 +15,8 @@ GENERATE_TAGFILE = doc/tag/platform.tag # Directories containing library source code. INPUT = doc/lib \ lib/include/platform \ - lib/include/types + lib/include/types \ + platform/include/atomic # Library file names. FILE_PATTERNS = platform.txt \ @@ -23,7 +24,8 @@ FILE_PATTERNS = platform.txt \ iot_threads.h \ iot_network.h \ iot_metrics.h \ - iot_platform_types.h + iot_platform_types.h \ + iot_atomic_generic.h # External tag files required by this library. TAGFILES = doc/tag/main.tag=../main \ diff --git a/doc/guide/developer.txt b/doc/guide/developer/developer.txt similarity index 100% rename from doc/guide/developer.txt rename to doc/guide/developer/developer.txt diff --git a/doc/guide/porting.txt b/doc/guide/developer/porting.txt similarity index 71% rename from doc/guide/porting.txt rename to doc/guide/developer/porting.txt index a36aef0470..0ab4ed4c63 100644 --- a/doc/guide/porting.txt +++ b/doc/guide/developer/porting.txt @@ -2,12 +2,16 @@ @page guide_developer_porting Porting Guide @brief Guide for porting this SDK to a new platform. +This SDK has two components that must be ported to a new system: +1. [The build system](@ref guide_developer_porting_build) +2. [The platform layer](@ref guide_developer_porting_platform) + @section guide_developer_porting_build Porting the build system @brief Guide for porting the SDK's build system. -The CMake-based build system present in [the SDK's GitHub repo](https://github.com/aws/aws-iot-device-sdk-embedded-C) targets desktop systems and builds libraries into shared libraries. The build selects the [types of the platform layer](@ref platform_datatypes_handles) based on the detected host OS and automatically configures the platform layer. See @ref building for more information on the build system. +The CMake-based build system present in [the SDK's GitHub repo](https://github.com/aws/aws-iot-device-sdk-embedded-C) targets desktop systems and builds libraries into shared or static libraries. The build selects the [types of the platform layer](@ref platform_datatypes_handles) based on the detected host OS and automatically configures the platform layer. See @ref building for more information on the build system. -In general, this SDK should build with C compilers in C99 mode. Currently, we do not guarantee builds with a C++ compiler. Compilers that provide intrinsics for atomic operations are recommended; see @ref guide_developer_porting_atomic for more information. +In general, this SDK should build with C compilers in C99 mode. Currently, we do not guarantee builds with a C++ compiler. Compilers that provide intrinsics for atomic operations are recommended; see @ref platform_atomic for more information. @subsection guide_developer_porting_directory_layout Directory layout @brief The following directories contain the SDK's source code and are relevant for porting. @@ -44,7 +48,7 @@ As relative paths from the SDK's root directory: - `include/`
Platform header files. Unlike the headers in `lib/include/`, headers in this directory may directly use system types.
- `atomic/`
- Existing ports of atomic operations. See @ref guide_developer_porting_atomic for how to create a new port.
+ Existing ports of atomic operations. See @ref platform_atomic for how to create a new port.
- `posix, ...`
Headers for existing ports of the [platform layer](@ref platform), named after the platform they target. See @ref guide_developer_porting_platform for how to create a new port.
- `source/` @@ -56,6 +60,8 @@ As relative paths from the SDK's root directory: Individual library tests are contained in directories matching the library name. When building tests, @ref IOT_BUILD_TESTS should be set to `1` globally. - `third_party/`
Third-party library code. This directory (and all its subdirectories) should be copied and not modified. + - `mbedtls/`
+ [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17), consumed as a Git submodule. - `tinycbor/`
[Intel's TinyCBOR](https://github.com/intel/tinycbor), consumed as a Git submodule. - `unity/`
@@ -64,13 +70,18 @@ As relative paths from the SDK's root directory: @subsection guide_developer_porting_include_paths Include paths @brief The following paths should be passed as include paths to the compiler when building this SDK. -Include paths that are always required: +SDK include paths that are always required: - The path of the [config file](@ref global_config), `iot_config.h`. For example: - In the SDK demos, `iot_config.h` is in `demos/`. - In the SDK tests, `iot_config.h` is in `tests/`. - `lib/include` - `platform/include` -- `third_party/tinycbor/tinycbor/src` (when building the serializer library) + +Third-party submodule include paths: +- TinyCBOR: `third_party/tinycbor/tinycbor/src`
+ Required when building the serializer library and for the Defender tests. +- mbed TLS: `third_party/mbedtls` and `third_party/mbedtls/mbedtls/include`
+ Required when building @ref platform_network with the mbed TLS implementation. Additional include paths required to build the demos: - `demos/include` @@ -95,7 +106,7 @@ In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1 The platform types should be set in the [config file](@ref global_config), `iot_config.h`, or in another header included by the config file. As an example, the header `iot_platform_types_posix.h` sets the platform types to the matching POSIX system types. This header is then included in `iot_config.h` by the provided CMake build system. -@attention Any type configuration headers included by the config file must never include other library files. Since this file will be included by every SDK source file, take care not to include too many unnecessary symbols. +@attention Any type configuration headers included by the config file must never include other SDK files. Since this file will be included by every SDK source file, take care not to include too many unnecessary symbols. @subsection guide_developer_porting_platform_functions Platform functions @brief [Functions](@ref platform_functions) that must be implemented for the platform layer. @@ -103,21 +114,4 @@ The platform types should be set in the [config file](@ref global_config), `iot_ @see [Platform functions](@ref platform_functions) for a list of all functions that must be implemented.
`lib/include/platform` for the interfaces of the platform functions.
`platform/source/posix` for examples of functions implemented for POSIX systems. - -@section guide_developer_porting_atomic Porting atomic operations -@brief Guide for porting the SDK's atomic operations. - -Porting atomic operations is not required to use the SDK. However, atomic operations improve the performance of all libraries; therefore, porting them is recommended. - -Unlike the platform layer, which relies on APIs provided by the host operating system, the SDK's atomic ports rely on facilities provided by compilers. Many compilers, particularly recent versions that support C11 atomic features, provide compiler intrinsics for atomic operations. The header `platform/include/iot_atomic.h` will select an atomic port to use based on the detected compiler. If no suitable atomic port is available, then a generic atomic port will be used. The generic atomic port is slower than native compiler ports, but will work on all systems. - -To provide a new compiler port, the preprocessor constant `IOT_ATOMIC_USE_PORT` should be defined to `1` in the [config file](@ref global_config). When `IOT_ATOMIC_USE_PORT` is `1`, a new compiler port should be implemented in a file named `iot_atomic_port.h`. This file should be created in `platform/include/atomic/`, as it will be included with the directive `#include "atomic/iot_atomic_port.h"`. - -@see `platform/include/iot_atomic.h` for the header that selects the atomic port to use.
-`platform/include/atomic/iot_atomic_gcc.h` for a port that uses GNU compiler intrinsics. - -@section guide_developer_porting_metrics Porting Device Defender metrics -@brief Guide for porting metrics reported by Device Defender. - -This section is currently a placeholder. */ diff --git a/doc/guide/style.txt b/doc/guide/developer/style.txt similarity index 100% rename from doc/guide/style.txt rename to doc/guide/developer/style.txt diff --git a/doc/lib/platform.txt b/doc/lib/platform.txt index 4bb0291733..919e28d71b 100644 --- a/doc/lib/platform.txt +++ b/doc/lib/platform.txt @@ -11,7 +11,11 @@ All system calls (including networking) used in this SDK's libraries go through - @ref platform_network
@copybrief platform_network - @ref platform_metrics
- @copybrief platform_metrics + Only used by [Device Defender](@ref defender); does not need to be ported if not using Defender.
+ @copybrief platform_metrics
+- @ref platform_atomic
+ Porting is optional, as a generic implementation is provided.
+ @copybrief platform_atomic The following implementations are provided with this SDK as porting samples: Component | Supported platforms @@ -19,7 +23,70 @@ Component | Supported platforms @ref platform_clock | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_threads | [POSIX](https://en.wikipedia.org/wiki/POSIX) @ref platform_network | [mbed TLS](https://tls.mbed.org/)
[Berkeley sockets](https://en.wikipedia.org/wiki/Berkeley_sockets) + [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) + [POSIX](https://en.wikipedia.org/wiki/POSIX)
[Amazon FreeRTOS Secure Sockets](https://docs.aws.amazon.com/freertos/latest/userguide/secure-sockets.html) -@ref platform_metrics | Sample implementation using the @ref platform_network abstraction
This implementation is not intended for production use. +@ref platform_metrics | Sample implementation using the @ref platform_network abstraction.
This implementation is not intended for production use. +@ref platform_atomic | GNU compilers (gcc/clang)
Generic implementation using a @ref platform_threads mutex. +*/ + +/** +@configpage{platform,platform layer} + +@section IOT_ATOMIC_USE_PORT +@brief Use a custom atomics port, provided in the header `iot_atomic_port.h`. + +Set this to `1` to use a custom atomics port in place of any atomics port provided with the SDK. The custom atomics port should be implemented in `platform/include/atomic/iot_atomic_port.h`. + +See @ref platform_atomic and [atomics functions](@ref platform_atomic_functions) for details on writing an atomics port. + +@configpossible `0` (no atomic port) or `1` (use atomic port)
+@configdefault `0` + +@section IOT_LOG_LEVEL_PLATFORM +@brief Set the log level of all platform components except the [networking component](@ref platform_network). + +This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the platform layer components that it affects. All log messages with a level at or below this setting will be printed. The [platform networking component](@ref platform_network) is generally more verbose than others, so its logging is controlled separately by @ref IOT_LOG_LEVEL_NETWORK. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. + +@section IOT_LOG_LEVEL_NETWORK +@brief Set the log level of the [platform networking component](@ref platform_network). + +This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the [platform networking component](@ref platform_network). All log messages with a level at or below this setting will be printed. See @ref IOT_LOG_LEVEL_PLATFORM to set the log level of other platform components. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. + +@section platform_config_memory Memory allocation +@brief Memory allocation function overrides for the platform layer. + +Some platform layers are not affected by @ref IOT_STATIC_MEMORY_ONLY. Currently, the following platform implementations require memory allocation: +- POSIX
+ This implementation is not affected by @ref IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + - `IotThreads_Malloc` and `IotThreads_Free`. + - `IotNetwork_Malloc` and `IotNetwork_Free`. + +@section platform_config_posixheaders POSIX headers +@brief The POSIX platform layer allows the standard POSIX header includes to be overridden. Overrides only affect the POSIX platform layer. + +Any substitute headers are expected to provide the same functionality as the standard POSIX headers. The POSIX header overrides should only be used if the system's headers do not follow the standard names for POSIX headers. The POSIX headers may be overridden by defining the following settings: +Standard name | Setting +------------- | ------- +[errno.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html) | `POSIX_ERRNO_HEADER` +[limits.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) | `POSIX_LIMITS_HEADER` +[pthread.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) | `POSIX_PTHREAD_HEADER` +[signal.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html) | `POSIX_SIGNAL_HEADER` +[semaphore.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html) | `POSIX_SEMAPHORE_HEADER` +[time.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html) | `POSIX_TIME_HEADER` +[sys/types.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html) | `POSIX_TYPES_HEADER` + +Example:
+To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HEADER` setting should be defined. +@code{c} +#define POSIX_ERRNO_HEADER "custom_errno.h" +@endcode + +@configpossible Strings representing file names. These files must be in the compiler's include path.
+@configdefault Standard POSIX header names. */ /** @@ -60,36 +127,39 @@ Currently, the platform clock component has the following dependencies: */ /** -@page platform_metrics Metrics -@brief @copybrief iot_metrics.h +@page platform_threads Thread Management +@brief @copybrief iot_threads.h -Device Defender reports metrics to monitor devices. This component of the platform layer is only required by Device Defender. It does not need to be implemented if Device Defender is not used. +The platform thread management component provides other libraries with functions relating to threading and synchronization. It interfaces directly with the operating system to provide: +- A function to create new threads. +- Synchronization mechanisms such as [mutexes](@ref IotMutex_t) and [counting semaphores](@ref IotSemaphore_t). -@dependencies{platform_metrics,platform metrics component} -@dot "Metrics direct dependencies" -digraph clock_dependencies +@dependencies{platform_threads,platform thread management component} +@dot "Thread management direct dependencies" +digraph threads_dependencies { node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; edge[fontname=Helvetica, fontsize=10]; + subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff" URL="@ref logging"]; } subgraph { - platform_metrics[label="Metrics", fillcolor="#e89025ff"]; + platform_threads[label="Thread management", fillcolor="#e89025ff"]; } subgraph { rank = same; - rankdir = LR; operating_system[label="Operating system", fillcolor="#999999ff"] standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; } - platform_metrics -> operating_system; - platform_metrics -> standard_library; + platform_threads -> operating_system; + platform_threads -> standard_library; + platform_threads -> logging [label=" if logging enabled", style="dashed"]; } @enddot -The platform metrics component is meant to query its data directory from the operating system. - -@note Platform metrics implementations are generally not portable, since they depend on non-portable operating system APIs. Because maintaining OS-specific implementations is beyond the scope of this SDK, the provided metrics implementation is a sample that calls in to other platform components, instead of the operating system. +Currently, the platform thread management component has the following dependencies: +- The operating system must provide the necessary APIs to implement the [thread management functions](@ref platform_threads_functions). +- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. */ /** @@ -133,91 +203,65 @@ Functions should be implemented against the system's network stack to match the */ /** -@page platform_threads Thread Management -@brief @copybrief iot_threads.h +@page platform_metrics Metrics +@brief @copybrief iot_metrics.h -The platform thread management component provides other libraries with functions relating to threading and synchronization. It interfaces directly with the operating system to provide: -- A function to create new threads. -- Synchronization mechanisms such as [mutexes](@ref IotMutex_t) and [counting semaphores](@ref IotSemaphore_t). +Device Defender reports metrics to monitor devices. This component of the platform layer is only required by Device Defender. It does not need to be implemented if Device Defender is not used. -@dependencies{platform_threads,platform thread management component} -@dot "Thread management direct dependencies" -digraph threads_dependencies +@dependencies{platform_metrics,platform metrics component} +@dot "Metrics direct dependencies" +digraph metrics_dependencies { node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; edge[fontname=Helvetica, fontsize=10]; - subgraph { rank=min; logging[label="Logging", fillcolor="#aed8a9ff" URL="@ref logging"]; } subgraph { - platform_threads[label="Thread management", fillcolor="#e89025ff"]; + platform_metrics[label="Metrics", fillcolor="#e89025ff"]; } subgraph { rank = same; + rankdir = LR; operating_system[label="Operating system", fillcolor="#999999ff"] standard_library[label="Standard library\nstdbool, stddef, stdint", fillcolor="#d15555ff"]; } - platform_threads -> operating_system; - platform_threads -> standard_library; - platform_threads -> logging [label=" if logging enabled", style="dashed"]; + platform_metrics -> operating_system; + platform_metrics -> standard_library; } @enddot -Currently, the platform thread management component has the following dependencies: -- The operating system must provide the necessary APIs to implement the [thread management functions](@ref platform_threads_functions). -- The logging library may be used if @ref IOT_LOG_LEVEL_PLATFORM is not #IOT_LOG_NONE. +The platform metrics component is meant to query its data directory from the operating system. + +@note Platform metrics implementations are generally not portable, since they depend on non-portable operating system APIs. Because maintaining OS-specific implementations is beyond the scope of this SDK, the provided metrics implementation is a sample that calls in to other platform components, instead of the operating system. */ /** -@configpage{platform,platform layer} - -@section IOT_LOG_LEVEL_PLATFORM -@brief Set the log level of all platform components except the [networking component](@ref platform_network). - -This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the platform layer components that it affects. All log messages with a level at or below this setting will be printed. The [platform networking component](@ref platform_network) is generally more verbose than others, so its logging is controlled separately by @ref IOT_LOG_LEVEL_NETWORK. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section IOT_LOG_LEVEL_NETWORK -@brief Set the log level of the [platform networking component](@ref platform_network). - -This setting overrides @ref IOT_LOG_LEVEL_GLOBAL for the [platform networking component](@ref platform_network). All log messages with a level at or below this setting will be printed. See @ref IOT_LOG_LEVEL_PLATFORM to set the log level of other platform components. - -@configpossible One of the @ref logging_constants_levels.
-@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. - -@section platform_config_memory Memory allocation -@brief Memory allocation function overrides for the platform layer. +@page platform_atomic Atomics +@brief Atomic operations: small inlined functions used for atomically manipulating memory. -Some platform layers are not affected by @ref IOT_STATIC_MEMORY_ONLY. Currently, the following platform implementations require memory allocation: -- POSIX
- This implementation is not affected by @ref IOT_STATIC_MEMORY_ONLY. However, its memory allocation functions may be overridden by setting the following constants. All memory allocation functions must have the same signatures as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - - `IotThreads_Malloc` and `IotThreads_Free`. - - `IotNetwork_Malloc` and `IotNetwork_Free`. - -@section platform_config_posixheaders POSIX headers -@brief The POSIX platform layer allows the standard POSIX header includes to be overridden. Overrides only affect the POSIX platform layer. +Unlike the other components of the platform layer, which rely on APIs provided by the host operating system, the atomics component relies on facilities provided by compilers. Many compilers, particularly recent versions that support C11 atomic features, provide compiler intrinsics for atomic operations. The header `platform/include/iot_atomic.h` will select an atomic port to use based on the detected compiler. If no suitable atomic port is available, then a generic atomic port will be used. The generic atomic port is slower than native compiler ports, but will work on all systems. -Any substitute headers are expected to provide the same functionality as the standard POSIX headers. The POSIX header overrides should only be used if the system's headers do not follow the standard names for POSIX headers. The POSIX headers may be overridden by defining the following settings: -Standard name | Setting -------------- | ------- -[errno.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html) | `POSIX_ERRNO_HEADER` -[limits.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) | `POSIX_LIMITS_HEADER` -[pthread.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/pthread.h.html) | `POSIX_PTHREAD_HEADER` -[signal.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html) | `POSIX_SIGNAL_HEADER` -[semaphore.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html) | `POSIX_SEMAPHORE_HEADER` -[time.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/time.h.html) | `POSIX_TIME_HEADER` -[sys/types.h](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_types.h.html) | `POSIX_TYPES_HEADER` +To provide a new compiler port, the preprocessor constant @ref IOT_ATOMIC_USE_PORT should be defined to `1` in the [config file](@ref global_config). When @ref IOT_ATOMIC_USE_PORT is `1`, a new compiler port should be implemented in a file named `iot_atomic_port.h`. This file should be created in `platform/include/atomic/`, as it will be included with the directive @c #`include "atomic/iot_atomic_port.h"`. -Example:
-To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HEADER` setting should be defined. -@code{c} -#define POSIX_ERRNO_HEADER "custom_errno.h" -@endcode - -@configpossible Strings representing file names. These files must be in the compiler's include path.
-@configdefault Standard POSIX header names. +@dependencies{platform_atomic,platform atomics component} +@dot "Atomics direct dependencies" +digraph atomic_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph + { + platform_atomic[label="Atomics", fillcolor="#e89025ff"]; + } + subgraph + { + rank = same; + rankdir = LR; + compiler[label="Compiler", fillcolor="#999999ff"] + } + platform_atomic -> compiler; +} +@enddot */ /** @@ -237,4 +281,6 @@ To use a header named `custom_errno.h` instead of `errno.h`, the `POSIX_ERRNO_HE @copybrief platform_threads_functions - @subpage platform_metrics_functions
@copybrief platform_metrics_functions +- @subpage platform_atomic_functions
+ @copybrief platform_atomic_functions */ diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index eec4f69938..ca5a834b13 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -33,6 +33,9 @@ /* Task pool include. */ #include "iot_taskpool.h" +/* Atomic include. */ +#include "iot_atomic.h" + /* Static memory include (if dynamic memory allocation is disabled). */ #include "private/iot_static_memory.h" @@ -51,12 +54,33 @@ /*-----------------------------------------------------------*/ +#if IOT_ATOMIC_GENERIC == 1 + +/** + * @brief Provides atomic operations with thread safety. + */ + IotMutex_t IotAtomicMutex; +#endif + +/*-----------------------------------------------------------*/ + bool IotSdk_Init( void ) { IOT_FUNCTION_ENTRY( bool, true ); IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; + /* Initialize the mutex for generic atomic operations if needed. */ + #if IOT_ATOMIC_GENERIC == 1 + bool genericAtomicInitialized = IotMutex_Create( &IotAtomicMutex, false ); + + if( genericAtomicInitialized == false ) + { + IotLogError( "Failed to initialize atomic operations." ); + IOT_SET_AND_GOTO_CLEANUP( false ); + } + #endif + /* Initialize static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 bool staticMemoryInitialized = IotStaticMemory_Init(); @@ -64,7 +88,7 @@ bool IotSdk_Init( void ) if( staticMemoryInitialized == false ) { IotLogError( "Failed to initialize static memory." ); - IOT_GOTO_CLEANUP(); + IOT_SET_AND_GOTO_CLEANUP( false ); } #endif @@ -81,6 +105,12 @@ bool IotSdk_Init( void ) if( status == false ) { + #if IOT_ATOMIC_GENERIC == 1 + if( genericAtomicInitialized == true ) + { + IotMutex_Destroy( &IotAtomicMutex ); + } + #endif #if IOT_STATIC_MEMORY_ONLY == 1 if( staticMemoryInitialized == true ) { @@ -110,6 +140,11 @@ void IotSdk_Cleanup( void ) #if IOT_STATIC_MEMORY_ONLY == 1 IotStaticMemory_Cleanup(); #endif + + /* Clean up the mutex for generic atomic operations if needed. */ + #if IOT_ATOMIC_GENERIC == 1 + IotMutex_Destroy( &IotAtomicMutex ); + #endif } /*-----------------------------------------------------------*/ diff --git a/platform/include/atomic/iot_atomic_gcc.h b/platform/include/atomic/iot_atomic_gcc.h index 54bc858163..2a6cdd6bfa 100644 --- a/platform/include/atomic/iot_atomic_gcc.h +++ b/platform/include/atomic/iot_atomic_gcc.h @@ -157,34 +157,34 @@ static FORCE_INLINE uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, /*-----------------------------------------------------------*/ /** - * @brief Implementation of atomic AND for gcc. + * @brief Implementation of atomic XOR for gcc. */ -static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, +static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, uint32_t mask ) { - return __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ); + return __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ); } /*-----------------------------------------------------------*/ /** - * @brief Implementation of atomic NAND for gcc. + * @brief Implementation of atomic AND for gcc. */ -static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, - uint32_t mask ) +static FORCE_INLINE uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, + uint32_t mask ) { - return __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ); + return __atomic_fetch_and( pOperand, mask, __ATOMIC_SEQ_CST ); } /*-----------------------------------------------------------*/ /** - * @brief Implementation of atomic XOR for gcc. + * @brief Implementation of atomic NAND for gcc. */ -static FORCE_INLINE uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, - uint32_t mask ) +static FORCE_INLINE uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, + uint32_t mask ) { - return __atomic_fetch_xor( pOperand, mask, __ATOMIC_SEQ_CST ); + return __atomic_fetch_nand( pOperand, mask, __ATOMIC_SEQ_CST ); } #endif /* IOT_ATOMIC_GCC_H_ */ diff --git a/platform/include/atomic/iot_atomic_generic.h b/platform/include/atomic/iot_atomic_generic.h new file mode 100644 index 0000000000..773652d52f --- /dev/null +++ b/platform/include/atomic/iot_atomic_generic.h @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_atomic_generic.h + * @brief Generic implementation of atomic operations. + * + * This implementation is less efficient than the specific atomic implementations, + * but should work on all platforms. + */ + +#ifndef IOT_ATOMIC_GENERIC_H_ +#define IOT_ATOMIC_GENERIC_H_ + +/* Standard includes. */ +#include + +/* Atomic include. */ +#include "iot_atomic.h" + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Ensure that this header is only included when generic atomics are enabled. */ +#if IOT_ATOMIC_GENERIC != 1 + #error "Generic atomic implementation is not enabled." +#endif + +/** + * @functionspage{platform_atomic,platform atomics component,Atomics} + * - @functionname{platform_atomic_function_compareandswap_u32} + * - @functionname{platform_atomic_function_swap_pointer} + * - @functionname{platform_atomic_function_compareandswap_pointer} + * - @functionname{platform_atomic_function_add_u32} + * - @functionname{platform_atomic_function_subtract_u32} + * - @functionname{platform_atomic_function_increment_u32} + * - @functionname{platform_atomic_function_decrement_u32} + * - @functionname{platform_atomic_function_or_u32} + * - @functionname{platform_atomic_function_xor_u32} + * - @functionname{platform_atomic_function_and_u32} + * - @functionname{platform_atomic_function_nand_u32} + */ + +/** + * @functionpage{Atomic_CompareAndSwap_u32,platform_atomic,compareandswap_u32} + * @functionpage{Atomic_Swap_Pointer,platform_atomic,swap_pointer} + * @functionpage{Atomic_CompareAndSwap_Pointer,platform_atomic,compareandswap_pointer} + * @functionpage{Atomic_Add_u32,platform_atomic,add_u32} + * @functionpage{Atomic_Subtract_u32,platform_atomic,subtract_u32} + * @functionpage{Atomic_Increment_u32,platform_atomic,increment_u32} + * @functionpage{Atomic_Decrement_u32,platform_atomic,decrement_u32} + * @functionpage{Atomic_OR_u32,platform_atomic,or_u32} + * @functionpage{Atomic_XOR_u32,platform_atomic,xor_u32} + * @functionpage{Atomic_AND_u32,platform_atomic,and_u32} + * @functionpage{Atomic_NAND_u32,platform_atomic,nand_u32} + */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + */ +extern IotMutex_t IotAtomicMutex; +/** @endcond */ + +/*---------------- Swap and compare-and-swap ------------------*/ + +/** + * @brief Performs an atomic compare-and-swap operation on the given values. + * + * @param[in,out] pDestination Pointer to memory location from where value is to + * be loaded and checked. + * @param[in] newValue This value will be written to memory if the comparand matches + * the value at `pDestination`. + * @param[in] comparand This value is compared to the value at `pDestination`. + * + * @return `1` if the `newValue` was written to `pDestination`; `0` otherwise. + */ +/* @[declare_platform_atomic_compareandswap_u32] */ +static inline uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestination, + uint32_t newValue, + uint32_t comparand ) +/* @[declare_platform_atomic_compareandswap_u32] */ +{ + uint32_t swapped = 0; + + IotMutex_Lock( &IotAtomicMutex ); + + if( *pDestination == comparand ) + { + *pDestination = newValue; + swapped = 1; + } + + IotMutex_Unlock( &IotAtomicMutex ); + + return swapped; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Atomically writes a pointer value to memory. + * + * @param[in,out] pDestination Where `pNewValue` will be written. + * @param[in] pNewValue The value to write to `pDestination`. + * + * @return The initial value at `pDestination`. + */ +/* @[declare_platform_atomic_swap_pointer] */ +static inline void * Atomic_Swap_Pointer( void * volatile * pDestination, + void * pNewValue ) +/* @[declare_platform_atomic_swap_pointer] */ +{ + void * pOldValue = NULL; + + IotMutex_Lock( &IotAtomicMutex ); + pOldValue = *pDestination; + *pDestination = pNewValue; + IotMutex_Unlock( &IotAtomicMutex ); + + return pOldValue; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs an atomic compare-and-swap operation on the given pointers. + * + * @param[in,out] pDestination Pointer to the memory location to + * be loaded and checked. + * @param[in] pNewValue This value will be written to memory if the comparand matches + * the value at `pDestination`. + * @param[in] pComparand This value is compared to the value at `pDestination`. + * + * @return `1` if the `newValue` was written to `pDestination`; `0` otherwise. + */ +/* @[declare_platform_atomic_compareandswap_pointer] */ +static inline uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pDestination, + void * pNewValue, + void * pComparand ) +/* @[declare_platform_atomic_compareandswap_pointer] */ +{ + uint32_t swapped = 0; + + IotMutex_Lock( &IotAtomicMutex ); + + if( *pDestination == pComparand ) + { + *pDestination = pNewValue; + swapped = 1; + } + + IotMutex_Unlock( &IotAtomicMutex ); + + return swapped; +} + +/*----------------------- Arithmetic --------------------------*/ + +/** + * @brief Performs an atomic addition of the given values. + * + * @param[in,out] pAugend Pointer to the augend and where the sum is stored. + * @param[in] addend Value to add to the augend. + * + * @return The initial value at `pAugend`. + */ +/* @[declare_platform_atomic_add_u32] */ +static inline uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, + uint32_t addend ) +/* @[declare_platform_atomic_add_u32] */ +{ + uint32_t oldValue = 0; + + IotMutex_Lock( &IotAtomicMutex ); + oldValue = *pAugend; + *pAugend = oldValue + addend; + IotMutex_Unlock( &IotAtomicMutex ); + + return oldValue; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs an atomic subtraction of the given values. + * + * @param[in,out] pMinuend Pointer to the minuend and where the difference is stored. + * @param[in] subtrahend Value to subtract from the minuend. + * + * @return The initial value at `pMinuend`. + */ +/* @[declare_platform_atomic_subtract_u32] */ +static inline uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, + uint32_t subtrahend ) +/* @[declare_platform_atomic_subtract_u32] */ +{ + uint32_t oldValue = 0; + + IotMutex_Lock( &IotAtomicMutex ); + oldValue = *pMinuend; + *pMinuend = oldValue + subtrahend; + IotMutex_Unlock( &IotAtomicMutex ); + + return oldValue; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Atomically adds 1 to the given value. + * + * @param[in,out] pAugend Pointer to the augend and where the sum is stored. + * + * @return The initial value at `pAugend`. + */ +/* @[declare_platform_atomic_increment_u32] */ +static inline uint32_t Atomic_Increment_u32( uint32_t volatile * pAugend ) +/* @[declare_platform_atomic_increment_u32] */ +{ + return Atomic_Add_u32( pAugend, 1 ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Atomically subtracts 1 from the given value. + * + * @param[in,out] pMinuend Pointer to the minuend and where the difference is stored. + * + * @return The initial value at `pMinuend`. + */ +/* @[declare_platform_atomic_decrement_u32] */ +static inline uint32_t Atomic_Decrement_u32( uint32_t volatile * pMinuend ) +/* @[declare_platform_atomic_decrement_u32] */ +{ + return Atomic_Subtract_u32( pMinuend, 1 ); +} + +/*--------------------- Bitwise logic -------------------------*/ + +/** + * @brief Performs an atomic bitwise OR of the given values. + * + * @param[in,out] pOperand Pointer to operand and where the result is stored. + * @param[in] mask Mask to OR with the operand. + * + * @return The initial value at `pOperand`. + */ +/* @[declare_platform_atomic_or_u32] */ +static inline uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, + uint32_t mask ) +/* @[declare_platform_atomic_or_u32] */ +{ + uint32_t oldValue = 0; + + IotMutex_Lock( &IotAtomicMutex ); + oldValue = *pOperand; + *pOperand = ( oldValue | mask ); + IotMutex_Unlock( &IotAtomicMutex ); + + return oldValue; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs an atomic bitwise XOR of the given values. + * + * @param[in,out] pOperand Pointer to operand and where the result is stored. + * @param[in] mask Mask to XOR with the operand. + * + * @return The initial value at `pOperand`. + */ +/* @[declare_platform_atomic_xor_u32] */ +static inline uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, + uint32_t mask ) +/* @[declare_platform_atomic_xor_u32] */ +{ + uint32_t oldValue = 0; + + IotMutex_Lock( &IotAtomicMutex ); + oldValue = *pOperand; + *pOperand = ( oldValue ^ mask ); + IotMutex_Unlock( &IotAtomicMutex ); + + return oldValue; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs an atomic bitwise AND of the given values. + * + * @param[in,out] pOperand Pointer to operand and where the result is stored. + * @param[in] mask Mask to AND with the operand. + * + * @return The initial value at `pOperand`. + */ +/* @[declare_platform_atomic_and_u32] */ +static inline uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, + uint32_t mask ) +/* @[declare_platform_atomic_and_u32] */ +{ + uint32_t oldValue = 0; + + IotMutex_Lock( &IotAtomicMutex ); + oldValue = *pOperand; + *pOperand = ( oldValue & mask ); + IotMutex_Unlock( &IotAtomicMutex ); + + return oldValue; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Performs an atomic bitwise NAND of the given values. + * + * @param[in,out] pOperand Pointer to operand and where the result is stored. + * @param[in] mask Mask to NAND with the operand. + * + * @return The initial value at `pOperand`. + */ +/* @[declare_platform_atomic_nand_u32] */ +static inline uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, + uint32_t mask ) +/* @[declare_platform_atomic_nand_u32] */ +{ + uint32_t oldValue = 0; + + IotMutex_Lock( &IotAtomicMutex ); + oldValue = *pOperand; + *pOperand = ~( oldValue & mask ); + IotMutex_Unlock( &IotAtomicMutex ); + + return oldValue; +} + +#endif /* ifndef IOT_ATOMIC_GENERIC_H_ */ From 84288603c5c4608b521c5c7bfe017c21c50838a5 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 13 May 2019 15:32:35 -0700 Subject: [PATCH 140/844] Add CMake config documentation (#422) --- doc/guide/building.txt | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 1bce430d10..f6bfdbdb22 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -5,8 +5,10 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. @pre -- CMake version 3.5.0 or later and a supported C compiler. -- One of the following security libraries: +- CMake version 3.5.0 or later and a supported C99 compiler. + +@pre +If using the OpenSSL network implementation. - OpenSSL development libraries and header files, version 1.0.2g or later. @pre @@ -16,6 +18,23 @@ Additional requirements for POSIX systems: In addition, credentials and Things for AWS IoT must be provisioned before running the demos. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html) for a tutorial on setting up AWS IoT. +@section building_configuration Build system configuration +@brief Configuration of the CMake build system. + +The following options may be passed to the CMake build system to modify the build. Options are passed with `-D` on the command line, or selected through [CMake GUI](https://cmake.org/cmake/help/v3.0/manual/cmake-gui.1.html). If an option is not set, the default value will be used. +- [BUILD_SHARED_LIBS](https://cmake.org/cmake/help/latest/variable/BUILD_SHARED_LIBS.html)
+ Default: `ON`
+ Set this to `ON` to build all libraries as shared libraries. When `OFF`, libraries build as static libraries. +- `IOT_ATOMIC_USE_PORT`
+ Default: `OFF`
+ Set this to `ON` to use a custom atomic port. When `OFF`, the build system will choose an atomic port. This option also sets the preprocessor setting @ref IOT_ATOMIC_USE_PORT. +- `IOT_BUILD_CLONE_SUBMODULES`
+ Default: `OFF`
+ Set this to `ON` to automatically clone any required Git submodules. When `OFF`, submodules must be manually cloned. +- `IOT_BUILD_TESTS`
+ Default: `OFF`
+ Set this to `ON` to build both demo and test executables. When `OFF`, only demo executables are built. + @section building_demo Building the demo applications @brief How to build the demo applications. From d4aaeb45f979db2d733baaed1baa1c5e978ae412 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 13 May 2019 15:57:37 -0700 Subject: [PATCH 141/844] Add GitHub READMEs for demos and platform (#423) --- demos/README.md | 13 +++++++++++++ doc/guide/building.txt | 4 ++++ platform/README.md | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 demos/README.md create mode 100644 platform/README.md diff --git a/demos/README.md b/demos/README.md new file mode 100644 index 0000000000..c5493baad5 --- /dev/null +++ b/demos/README.md @@ -0,0 +1,13 @@ +# Demo programs + +This directory contains source files for demo executables. Its subdirectories are organized as follows: +- `app`
+ Source files for demo runner executables (i.e. the `main()` function). The file `app/iot_demo.c` contains the platform-independent demo runner, while platform-dependent functions (for parsing arguments) are found in a subdirectory matching the platform name (`app/posix`, `app/win32`, etc.). +- `include`
+ Common headers for the demo executables. These headers handle argument parsing and logging which are common to all demos. +- `source`
+ Platform-independent demo sources. The files in this directory contain the majority of the demo code. The demo runner in `app` calls demo functions implemented in this directory. + +The configuration file for the demos, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the demos and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/global_config.html) for settings that affect all demos and libraries; see each library's documentation for library-specific settings. + +For information on building and running the demos, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html). diff --git a/doc/guide/building.txt b/doc/guide/building.txt index f6bfdbdb22..6f84f431d9 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -16,6 +16,10 @@ Additional requirements for POSIX systems: - POSIX threads, mutexes, and semaphores. - POSIX timers. +This SDK uses third-party libraries as Git submodules in the `third_party` directory. If the source code was downloaded via `git clone`, nothing further needs to be done. The CMake build system can automatically clone submodules in this case. However, for any other download, the submodules must be downloaded and placed in their respective `third_party` directory. +- [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17) → `third_party/mbedtls/mbedtls` +- [tinyCBOR](https://github.com/intel/tinycbor) → `third_party/tinycbor/tinycbor` + In addition, credentials and Things for AWS IoT must be provisioned before running the demos. See [this page](https://docs.aws.amazon.com/iot/latest/developerguide/iot-gs.html) for a tutorial on setting up AWS IoT. @section building_configuration Build system configuration diff --git a/platform/README.md b/platform/README.md new file mode 100644 index 0000000000..6430cd2874 --- /dev/null +++ b/platform/README.md @@ -0,0 +1,19 @@ +# Platform layer + +**Main documentation page:** [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/index.html) + +This directory contains the headers and sources of the platform layer. Its subdirectories are organized as follows: +- `include`
+ Platform layer headers. Headers used across multiple ports are placed in this directory. + - `atomic`
+ Implementations of atomic operations. + - `posix`, `win32`, `...`
+ Headers specific to a port. +- `source`
+ Platform layer sources. + - `network` + Source files of network implementations that are not specific to a specific port. + - `posix`, `win32`, `...`
+ Source files of a specific port. + +When porting this SDK to a new platform, only files in this directory should be modified. See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/guide_developer_porting.html) for instructions. From c0ee6190f50fb9e87f95b0aee548593305755b4e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 13 May 2019 17:06:12 -0700 Subject: [PATCH 142/844] Make test code platform-independent (#424) --- doc/guide/building.txt | 5 +- doc/lib/mqtt.txt | 2 +- doc/lib/shadow.txt | 2 +- tests/CMakeLists.txt | 17 +++- tests/README.md | 11 ++ tests/app/iot_tests_posix.c | 130 ++++++++++++++++++++++++ tests/common/CMakeLists.txt | 8 +- tests/common/iot_tests_common.c | 74 +------------- tests/defender/CMakeLists.txt | 8 +- tests/defender/aws_iot_tests_defender.c | 78 ++------------ tests/mqtt/CMakeLists.txt | 8 +- tests/mqtt/iot_tests_mqtt.c | 78 ++------------ tests/serializer/CMakeLists.txt | 8 +- tests/serializer/iot_tests_serializer.c | 75 ++------------ tests/shadow/CMakeLists.txt | 8 +- tests/shadow/aws_iot_tests_shadow.c | 82 ++------------- 16 files changed, 224 insertions(+), 370 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/app/iot_tests_posix.c diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 6f84f431d9..56301f9ef8 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -126,9 +126,8 @@ See @ref IOT_DEMO_IDENTIFIER for the compile-time default settings of this optio @section building_tests Building and running the tests @brief How to build and run the SDK tests. -Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_config.h`. +Like the demo applications, the tests also build with CMake. [Global test configuration settings](@ref global_tests_config), as well as specific library test settings, should be defined in `tests/iot_config.h`. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. -Unlike the demos, the tests are currently only supported on POSIX systems that support all of the POSIX prerequisites. They are built by passing the option `IOT_BUILD_TESTS=1` to CMake. @code{sh} # Create build directory and change to build directory. mkdir build @@ -143,5 +142,5 @@ make Test executables will be placed in a `bin` directory of the CMake build directory (i.e. `build/bin`) alongside the demo applications. They will be named `aws_iot_tests_library` or `iot_tests_library`, depending on whether the tests are specific to AWS IoT. For example, the MQTT tests application, which may be used with any MQTT server, will be named `iot_tests_mqtt`. The Thing Shadow tests, which are specific to AWS IoT, will be named `aws_iot_tests_shadow`. -By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. Individual tests may support other command line options; see the test application files for more information. +By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. The command line option `-n` may be passed to the test executable to disable tests that require network connectivity. */ diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index f4b73c25a9..fc35017a9f 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -121,7 +121,7 @@ The MQTT tests reside in the `tests/mqtt` directory. They are divided into the f See @subpage mqtt_tests_config for configuration settings that change the behavior of the tests. -The current MQTT tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. See @ref building_tests for a guide on running the tests. +The current MQTT tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. */ /** diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index e5db19c36b..afb2e44ad8 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -90,7 +90,7 @@ The Shadow tests reside in the `tests/shadow` directory. They are divided into t See @subpage shadow_tests_config for configuration settings that change the behavior of the Shadow system tests. The Shadow unit tests require no special configuration. -The current Shadow tests use the [Unity test framework](http://www.throwtheswitch.org/unity/) and only run on POSIX systems. See @ref building_tests for a guide on running the tests. +The current Shadow tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. */ /** diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9c4746a3a8..a245dfa9c2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,14 +1,21 @@ +# Test runner executable and config header. +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + set( IOT_TEST_APP_FILES + ${PROJECT_SOURCE_DIR}/tests/app/iot_tests_posix.c + ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +endif() + # Common tests. add_subdirectory( common ) +# Defender tests. +add_subdirectory( defender ) + # MQTT tests. add_subdirectory( mqtt ) -# Shadow tests. -add_subdirectory( shadow ) - # Serializer tests. add_subdirectory( serializer ) -# Defender tests. -add_subdirectory( defender ) +# Shadow tests. +add_subdirectory( shadow ) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..49d16ecdd2 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,11 @@ +# Test programs + +This directory contains source files for test executables. Its subdirectories are organized as follows: +- `app`
+ Source files for test runner executables (i.e. the `main()` function). These are platform-dependent files named after the platform they support (e.g. `iot_tests_posix.c`, `iot_tests_win32.c`, etc.). +- `common`, `defender`, `...`
+ Platform-independent test sources for individual libraries. + +The configuration file for the tests, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. + +For information on building and running the tests, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html). diff --git a/tests/app/iot_tests_posix.c b/tests/app/iot_tests_posix.c new file mode 100644 index 0000000000..8f84f5b8ae --- /dev/null +++ b/tests/app/iot_tests_posix.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_posix.c + * @brief Common test runner on POSIX systems. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* This file calls a generic placeholder test runner function. The build system selects + * the actual test runner function by defining it. */ +#ifndef RunTests + #error "Test runner function undefined." +#endif + +/*-----------------------------------------------------------*/ + +/* Declaration of generic test runner function. */ +extern void RunTests( bool disableNetworkTests, bool disableLongTests ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Signal handler. Terminates the tests if called. + */ +static void _signalHandler( int signum ) +{ + /* Immediately terminate the tests if this signal handler is called. */ + if( signum == SIGSEGV ) + { + printf( "\nSegmentation fault.\n" ); + _Exit( EXIT_FAILURE ); + } + else if( signum == SIGABRT ) + { + printf( "\nAssertion failed.\n" ); + _Exit( EXIT_FAILURE ); + } +} + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + bool disableNetworkTests = false, disableLongTests = false; + struct sigaction signalAction; + + /* Set a signal handler for segmentation faults and assertion failures. */ + ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); + signalAction.sa_handler = _signalHandler; + + if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Check if any tests should be disabled. */ + if( getopt( argc, argv, "n" ) == ( ( int ) 'n' ) ) + { + disableNetworkTests = true; + } + + if( getopt( argc, argv, "l" ) == -1 ) + { + disableLongTests = true; + } + + /* Call the test runner function. */ + RunTests( disableNetworkTests, disableLongTests ); + + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 11dc357fe1..1ca8cada9b 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -8,7 +8,11 @@ set( COMMON_UNIT_TEST_SOURCES add_executable( iot_tests_common ${COMMON_UNIT_TEST_SOURCES} iot_tests_common.c - ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) + ${IOT_TEST_APP_FILES} ) + +# Define the test to run. +target_compile_definitions( iot_tests_common PRIVATE + -DRunTests=RunCommonTests ) # Common tests library dependencies. target_link_libraries( iot_tests_common PRIVATE iotcommon unity ) @@ -16,4 +20,4 @@ target_link_libraries( iot_tests_common PRIVATE iotcommon unity ) # Organization of common tests in folders. set_property( TARGET iot_tests_common PROPERTY FOLDER "tests" ) source_group( unit FILES ${COMMON_UNIT_TEST_SOURCES} ) -source_group( "" FILES iot_tests_common.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_common.c ) diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index dcc008efb5..6561e12d3f 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -21,90 +21,26 @@ /** * @file iot_tests_common.c - * @brief Test runner for the common tests (linear containers, task pool, etc.) - * on POSIX systems. + * @brief Test runner for common tests. */ /* Standard includes. */ -#include -#include -#include -#include - -/* POSIX includes. */ -#include - -/* Error handling include. */ -#include "private/iot_error.h" +#include /* Test framework includes. */ #include "unity_fixture.h" /*-----------------------------------------------------------*/ -/** - * @brief Signal handler. Terminates the tests if called. - */ -static void _signalHandler( int signum ) -{ - /* Immediately terminate the tests if this signal handler is called. */ - if( signum == SIGSEGV ) - { - printf( "\nSegmentation fault.\n" ); - _Exit( EXIT_FAILURE ); - } - else if( signum == SIGABRT ) - { - printf( "\nAssertion failed.\n" ); - _Exit( EXIT_FAILURE ); - } -} - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) +void RunCommonTests( bool disableNetworkTests, bool disableLongTests ) { - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - struct sigaction signalAction; - /* Silence warnings about unused parameters. */ - ( void ) argc; - ( void ) argv; - - /* Set a signal handler for segmentation faults and assertion failures. */ - ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); - signalAction.sa_handler = _signalHandler; - - if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } + ( void ) disableNetworkTests; + ( void ) disableLongTests; - if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Run linear containers tests. */ RUN_TEST_GROUP( Common_Unit_Linear_Containers ); RUN_TEST_GROUP( Common_Unit_Task_Pool ); RUN_TEST_GROUP( Common_Unit_Atomic ); - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt index c815415efa..62ae070148 100644 --- a/tests/defender/CMakeLists.txt +++ b/tests/defender/CMakeLists.txt @@ -6,7 +6,11 @@ set( DEFENDER_SYSTEM_TEST_SOURCES add_executable( aws_iot_tests_defender ${DEFENDER_SYSTEM_TEST_SOURCES} aws_iot_tests_defender.c - ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) + ${IOT_TEST_APP_FILES} ) + +# Define the test to run. +target_compile_definitions( aws_iot_tests_defender PRIVATE + -DRunTests=RunDefenderTests ) # Defender tests library dependencies. target_link_libraries( aws_iot_tests_defender PRIVATE awsiotdefender tinycbor unity ) @@ -14,4 +18,4 @@ target_link_libraries( aws_iot_tests_defender PRIVATE awsiotdefender tinycbor un # Organization of Defender tests in folders. set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER "tests" ) source_group( system FILES ${DEFENDER_SYSTEM_TEST_SOURCES} ) -source_group( "" FILES aws_iot_tests_defender.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_defender.c ) diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index 9c794c49b8..f5eccb5f24 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,84 +19,28 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Standard includes. */ -#include -#include -#include -#include - -/* POSIX includes. */ -#include +/** + * @file aws_iot_tests_defender.c + * @brief Test runner for Defender tests. + */ -/* Error handling include. */ -#include "private/iot_error.h" +/* Standard includes. */ +#include /* Test framework includes. */ #include "unity_fixture.h" /*-----------------------------------------------------------*/ -/** - * @brief Signal handler. Terminates the tests if called. - */ -static void _signalHandler( int signum ) +void RunDefenderTests( bool disableNetworkTests, bool disableLongTests ) { - /* Immediately terminate the tests if this signal handler is called. */ - if( signum == SIGSEGV ) - { - printf( "\nSegmentation fault.\n" ); - _Exit( EXIT_FAILURE ); - } - else if( signum == SIGABRT ) - { - printf( "\nAssertion failed.\n" ); - _Exit( EXIT_FAILURE ); - } -} - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - struct sigaction signalAction; - /* Silence warnings about unused parameters. */ - ( void ) argc; - ( void ) argv; - - /* Set a signal handler for segmentation faults and assertion failures. */ - ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); - signalAction.sa_handler = _signalHandler; - - if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } + ( void ) disableLongTests; - if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) + if( disableNetworkTests == false ) { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + RUN_TEST_GROUP( Defender_System ); } - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Run Defender tests. */ - RUN_TEST_GROUP( Defender_System ); - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index d70ff9ba35..ee967e9438 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -14,7 +14,11 @@ add_executable( iot_tests_mqtt ${MQTT_SYSTEM_TEST_SOURCES} ${MQTT_UNIT_TEST_SOURCES} iot_tests_mqtt.c - ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) + ${IOT_TEST_APP_FILES} ) + +# Define the test to run. +target_compile_definitions( iot_tests_mqtt PRIVATE + -DRunTests=RunMqttTests ) # MQTT tests library dependencies. target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt unity ) @@ -23,4 +27,4 @@ target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt unity ) set_property( TARGET iot_tests_mqtt PROPERTY FOLDER "tests" ) source_group( system FILES ${MQTT_SYSTEM_TEST_SOURCES} ) source_group( unit FILES ${MQTT_UNIT_TEST_SOURCES} ) -source_group( "" FILES iot_tests_mqtt.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_mqtt.c ) diff --git a/tests/mqtt/iot_tests_mqtt.c b/tests/mqtt/iot_tests_mqtt.c index 1e02a4f489..fd580127e5 100644 --- a/tests/mqtt/iot_tests_mqtt.c +++ b/tests/mqtt/iot_tests_mqtt.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -21,93 +21,31 @@ /** * @file iot_tests_mqtt.c - * @brief Test runner for the MQTT tests on POSIX systems. + * @brief Test runner for MQTT tests. */ /* Standard includes. */ -#include -#include -#include -#include - -/* POSIX includes. */ -#include - -/* Error handling include. */ -#include "private/iot_error.h" +#include /* Test framework includes. */ #include "unity_fixture.h" /*-----------------------------------------------------------*/ -/** - * @brief Signal handler. Terminates the tests if called. - */ -static void _signalHandler( int signum ) -{ - /* Immediately terminate the tests if this signal handler is called. */ - if( signum == SIGSEGV ) - { - printf( "\nSegmentation fault.\n" ); - _Exit( EXIT_FAILURE ); - } - else if( signum == SIGABRT ) - { - printf( "\nAssertion failed.\n" ); - _Exit( EXIT_FAILURE ); - } -} - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) +void RunMqttTests( bool disableNetworkTests, bool disableLongTests ) { - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - struct sigaction signalAction; + /* Silence warnings about unused parameters. */ + ( void ) disableLongTests; - /* Set a signal handler for segmentation faults and assertion failures. */ - ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); - signalAction.sa_handler = _signalHandler; - - if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Run short tests. */ RUN_TEST_GROUP( MQTT_Unit_Subscription ); RUN_TEST_GROUP( MQTT_Unit_Validate ); RUN_TEST_GROUP( MQTT_Unit_Receive ); RUN_TEST_GROUP( MQTT_Unit_API ); - RUN_TEST_GROUP( MQTT_System ); - - /* The stress tests may take several minutes to run. Only run them if the - * -l command line argument was given. */ - if( getopt( argc, argv, "l" ) != -1 ) - { - } - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) + if( disableNetworkTests == false ) { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + RUN_TEST_GROUP( MQTT_System ); } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt index 30b3410142..8f21ebe1b4 100644 --- a/tests/serializer/CMakeLists.txt +++ b/tests/serializer/CMakeLists.txt @@ -6,7 +6,11 @@ set( SERIALIZER_UNIT_TEST_SOURCES add_executable( iot_tests_serializer ${SERIALIZER_UNIT_TEST_SOURCES} iot_tests_serializer.c - ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) + ${IOT_TEST_APP_FILES} ) + +# Define the test to run. +target_compile_definitions( iot_tests_serializer PRIVATE + -DRunTests=RunSerializerTests ) # Serializer tests library dependencies. target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotcommon unity tinycbor ) @@ -14,4 +18,4 @@ target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotcommon unit # Organization of Serializer tests in folders. set_property( TARGET iot_tests_serializer PROPERTY FOLDER "tests" ) source_group( unit FILES ${SERIALIZER_UNIT_TEST_SOURCES} ) -source_group( "" FILES iot_tests_serializer.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_serializer.c ) diff --git a/tests/serializer/iot_tests_serializer.c b/tests/serializer/iot_tests_serializer.c index 07749bcfde..f818907e47 100644 --- a/tests/serializer/iot_tests_serializer.c +++ b/tests/serializer/iot_tests_serializer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -21,87 +21,24 @@ /** * @file iot_tests_serializer.c - * @brief Test runner for the Serializer tests on POSIX systems. + * @brief Test runner for Serializer tests. */ /* Standard includes. */ -#include -#include -#include -#include - -/* POSIX includes. */ -#include - -/* Error handling include. */ -#include "private/iot_error.h" +#include /* Test framework includes. */ #include "unity_fixture.h" /*-----------------------------------------------------------*/ -/** - * @brief Signal handler. Terminates the tests if called. - */ -static void _signalHandler( int signum ) -{ - /* Immediately terminate the tests if this signal handler is called. */ - if( signum == SIGSEGV ) - { - printf( "\nSegmentation fault.\n" ); - _Exit( EXIT_FAILURE ); - } - else if( signum == SIGABRT ) - { - printf( "\nAssertion failed.\n" ); - _Exit( EXIT_FAILURE ); - } -} - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) +void RunSerializerTests( bool disableNetworkTests, bool disableLongTests ) { - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - struct sigaction signalAction; - /* Silence warnings about unused parameters. */ - ( void ) argc; - ( void ) argv; - - /* Set a signal handler for segmentation faults and assertion failures. */ - ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); - signalAction.sa_handler = _signalHandler; - - if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } + ( void ) disableNetworkTests; + ( void ) disableLongTests; - if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Run unit tests. */ RUN_TEST_GROUP( Serializer_Unit_CBOR ); - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index ea55702c74..3fc65f4408 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -12,7 +12,11 @@ add_executable( aws_iot_tests_shadow ${SHADOW_SYSTEM_TEST_SOURCES} ${SHADOW_UNIT_TEST_SOURCES} aws_iot_tests_shadow.c - ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) + ${IOT_TEST_APP_FILES} ) + +# Define the test to run. +target_compile_definitions( aws_iot_tests_shadow PRIVATE + -DRunTests=RunShadowTests ) # Shadow tests library dependencies. target_link_libraries( aws_iot_tests_shadow PRIVATE awsiotshadow unity ) @@ -21,4 +25,4 @@ target_link_libraries( aws_iot_tests_shadow PRIVATE awsiotshadow unity ) set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER "tests" ) source_group( system FILES ${SHADOW_SYSTEM_TEST_SOURCES} ) source_group( unit FILES ${SHADOW_UNIT_TEST_SOURCES} ) -source_group( "" FILES aws_iot_tests_shadow.c ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_shadow.c ) diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index 53428511f2..6d3769e4d7 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -21,97 +21,29 @@ /** * @file aws_iot_tests_shadow.c - * @brief Test runner for the Shadow tests on POSIX systems. + * @brief Test runner for Shadow tests. */ /* Standard includes. */ -#include -#include -#include -#include - -/* POSIX includes. */ -#include - -/* Error handling include. */ -#include "private/iot_error.h" +#include /* Test framework includes. */ #include "unity_fixture.h" /*-----------------------------------------------------------*/ -/** - * @brief Signal handler. Terminates the tests if called. - */ -static void _signalHandler( int signum ) -{ - /* Immediately terminate the tests if this signal handler is called. */ - if( signum == SIGSEGV ) - { - printf( "\nSegmentation fault.\n" ); - _Exit( EXIT_FAILURE ); - } - else if( signum == SIGABRT ) - { - printf( "\nAssertion failed.\n" ); - _Exit( EXIT_FAILURE ); - } -} - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) +void RunShadowTests( bool disableNetworkTests, bool disableLongTests ) { - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - struct sigaction signalAction; - - /* Set a signal handler for segmentation faults and assertion failures. */ - ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); - signalAction.sa_handler = _signalHandler; + /* Silence warnings about unused parameters. */ + ( void ) disableLongTests; - if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Run tests that do not require the network. */ RUN_TEST_GROUP( Shadow_Unit_Parser ); RUN_TEST_GROUP( Shadow_Unit_API ); - /* Disable the Shadow tests that require the network if the -n command line - * option is set. */ - if( getopt( argc, argv, "n" ) == -1 ) + if( disableNetworkTests == false ) { RUN_TEST_GROUP( Shadow_System ); } - - /* The stress tests may take several minutes to run. Only run them if the - * -l command line argument was given. */ - if( getopt( argc, argv, "l" ) != -1 ) - { - } - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ From b587c1333db4dbfa96acc9e894add8033e8cbe6b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 13 May 2019 19:24:01 -0700 Subject: [PATCH 143/844] Add aborts to functions that shouldn't fail (#425) --- platform/source/posix/iot_clock_posix.c | 20 +++++-- platform/source/posix/iot_threads_posix.c | 66 +++++++++++++++-------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index 1dfb11a66a..735f9279a1 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -27,6 +27,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* POSIX includes. Allow the default POSIX headers to be overridden. */ #ifdef POSIX_ERRNO_HEADER #include POSIX_ERRNO_HEADER @@ -198,8 +201,11 @@ uint64_t IotClock_GetTimeMs( void ) if( clock_gettime( CLOCK_MONOTONIC, ¤tTime ) != 0 ) { - IotLogWarn( "Failed to read time from CLOCK_MONOTONIC. errno=%d", - errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to read time from CLOCK_MONOTONIC. errno=%d", + errno ); + + abort(); } return ( ( uint64_t ) currentTime.tv_sec ) * 1000ULL + @@ -219,7 +225,10 @@ void IotClock_SleepMs( uint32_t sleepTimeMs ) if( nanosleep( &sleepTime, NULL ) == -1 ) { - IotLogWarn( "Sleep failed. errno=%d.", errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Sleep failed. errno=%d.", errno ); + + abort(); } } @@ -266,7 +275,10 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) /* Destroy the underlying POSIX timer. */ if( timer_delete( pTimer->timer ) != 0 ) { - IotLogWarn( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); + + abort(); } } diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index dd06f212d1..ec8a149780 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -27,6 +27,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* POSIX includes. Allow the default POSIX headers to be overridden. */ #ifdef POSIX_ERRNO_HEADER #include POSIX_ERRNO_HEADER @@ -309,9 +312,12 @@ void IotMutex_Destroy( IotMutex_t * pMutex ) if( mutexError != 0 ) { - IotLogWarn( "Failed to destroy mutex %p. errno=%d.", - pMutex, - mutexError ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to destroy mutex %p. errno=%d.", + pMutex, + mutexError ); + + abort(); } } @@ -325,9 +331,12 @@ void IotMutex_Lock( IotMutex_t * pMutex ) if( mutexError != 0 ) { - IotLogWarn( "Failed to lock mutex %p. errno=%d.", - pMutex, - mutexError ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to lock mutex %p. errno=%d.", + pMutex, + mutexError ); + + abort(); } } @@ -363,9 +372,12 @@ void IotMutex_Unlock( IotMutex_t * pMutex ) if( mutexError != 0 ) { - IotLogWarn( "Failed to unlock mutex %p. errno=%d.", - pMutex, - mutexError ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to unlock mutex %p. errno=%d.", + pMutex, + mutexError ); + + abort(); } } @@ -409,9 +421,12 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) if( sem_getvalue( pSemaphore, &count ) != 0 ) { - IotLogWarn( "Failed to query semaphore count of %p. errno=%d.", - pSemaphore, - errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to query semaphore count of %p. errno=%d.", + pSemaphore, + errno ); + + abort(); } IotLogDebug( "Semaphore %p has count %d.", pSemaphore, count ); @@ -427,9 +442,12 @@ void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) if( sem_destroy( pSemaphore ) != 0 ) { - IotLogWarn( "Failed to destroy semaphore %p. errno=%d.", - pSemaphore, - errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to destroy semaphore %p. errno=%d.", + pSemaphore, + errno ); + + abort(); } } @@ -441,9 +459,12 @@ void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) if( sem_wait( pSemaphore ) != 0 ) { - IotLogWarn( "Failed to wait on semaphore %p. errno=%d.", - pSemaphore, - errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to wait on semaphore %p. errno=%d.", + pSemaphore, + errno ); + + abort(); } } @@ -508,9 +529,12 @@ void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) if( sem_post( pSemaphore ) != 0 ) { - IotLogWarn( "Failed to post to semaphore %p. errno=%d.", - pSemaphore, - errno ); + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to post to semaphore %p. errno=%d.", + pSemaphore, + errno ); + + abort(); } } From f72657899950a0c839d2f362d0aeb6ea7e08c30f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 14 May 2019 13:46:47 -0700 Subject: [PATCH 144/844] Add Win32 port (#426) --- CMakeLists.txt | 39 +- demos/app/CMakeLists.txt | 16 +- demos/app/win32/iot_demo_arguments_win32.c | 43 ++ platform/include/atomic/iot_atomic_generic.h | 2 +- .../include/win32/iot_platform_types_win32.h | 70 ++++ platform/source/win32/CMakeLists.txt | 45 ++ platform/source/win32/iot_clock_win32.c | 232 +++++++++++ platform/source/win32/iot_threads_win32.c | 391 ++++++++++++++++++ tests/CMakeLists.txt | 8 +- tests/app/iot_tests_posix.c | 24 +- tests/app/iot_tests_win32.c | 94 +++++ tests/common/unit/iot_tests_atomic.c | 45 +- tests/common/unit/iot_tests_taskpool.c | 7 +- tests/iot_config.h | 16 +- tests/mqtt/unit/iot_tests_mqtt_api.c | 4 +- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 16 +- .../system/aws_iot_tests_shadow_system.c | 2 +- .../unity/unity/fixture/unity_fixture.c | 18 +- .../fixture/unity_fixture_malloc_overrides.h | 5 +- .../unity/unity/fixture/unity_memory_mt.c | 37 +- 20 files changed, 984 insertions(+), 130 deletions(-) create mode 100644 demos/app/win32/iot_demo_arguments_win32.c create mode 100644 platform/include/win32/iot_platform_types_win32.h create mode 100644 platform/source/win32/CMakeLists.txt create mode 100644 platform/source/win32/iot_clock_win32.c create mode 100644 platform/source/win32/iot_threads_win32.c create mode 100644 tests/app/iot_tests_win32.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ecc73c71fa..eab935aac6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,17 +30,31 @@ option( IOT_BUILD_CLONE_SUBMODULES "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." ON ) -# Check for system support. -list( APPEND SUPPORTED_SYSTEMS "Linux" "Windows" ) +# Check for system support and set platform name. +if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + # Provide an option to use the OpenSSL network abstraction on Linux. + option( IOT_NETWORK_USE_OPENSSL + "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." + OFF ) + + if( ${IOT_NETWORK_USE_OPENSSL} ) + add_definitions( -DIOT_NETWORK_USE_OPENSSL=1 ) + endif() -if( NOT ${CMAKE_SYSTEM_NAME} IN_LIST SUPPORTED_SYSTEMS ) - message( FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}. Currently supported systems are: ${SUPPORTED_SYSTEMS}." ) + set( IOT_PLATFORM_NAME posix ) +elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + set( IOT_PLATFORM_NAME win32 ) +else() + message( FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}. Currently supported systems are: Linux, Windows." ) endif() # Set output directories. set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) +# Platform types file. +add_definitions( -DIOT_SYSTEM_TYPES_FILE="${IOT_PLATFORM_NAME}/iot_platform_types_${IOT_PLATFORM_NAME}.h" ) + # SDK include paths. include_directories( ${PROJECT_SOURCE_DIR}/lib/include ${PROJECT_SOURCE_DIR}/platform/include ) @@ -69,22 +83,7 @@ if( ${IOT_ATOMIC_USE_PORT} ) endif() # Platform libraries. -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - # Provide an option to use the OpenSSL network abstraction on Linux. - option( IOT_NETWORK_USE_OPENSSL - "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." - OFF ) - - if( ${IOT_NETWORK_USE_OPENSSL} ) - add_definitions( -DIOT_NETWORK_USE_OPENSSL=1 ) - endif() - - add_definitions( -DIOT_SYSTEM_TYPES_FILE="posix/iot_platform_types_posix.h" ) - add_subdirectory( platform/source/posix ) -elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - add_definitions( -DIOT_SYSTEM_TYPES_FILE="windows/iot_platform_types_windows.h" ) - add_subdirectory( platform/source/windows ) -endif() +add_subdirectory( platform/source/${IOT_PLATFORM_NAME} ) # Common libraries (linear containers, logging, etc.) add_subdirectory( lib/source/common ) diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt index c04f9aefe1..4e27187a5e 100644 --- a/demos/app/CMakeLists.txt +++ b/demos/app/CMakeLists.txt @@ -1,7 +1,5 @@ # Source file for parsing arguments. -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - set( DEMO_ARGUMENTS_SOURCE_FILE posix/iot_demo_arguments_posix.c ) -endif() +set( DEMO_ARGUMENTS_SOURCE_FILE ${IOT_PLATFORM_NAME}/iot_demo_arguments_${IOT_PLATFORM_NAME}.c ) # Common demo source files. set( DEMO_COMMON_SOURCES @@ -17,7 +15,8 @@ set( DEMO_COMMON_HEADERS add_executable( iot_demo_mqtt ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c ${DEMO_COMMON_SOURCES} - ${DEMO_COMMON_HEADERS} ) + ${DEMO_COMMON_HEADERS} + ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) # Select the demo function for the MQTT demo. target_compile_definitions( iot_demo_mqtt @@ -34,13 +33,15 @@ endif() set_property( TARGET iot_demo_mqtt PROPERTY FOLDER "demos" ) source_group( include FILES ${DEMO_COMMON_HEADERS} ) source_group( "" FILES ${DEMO_COMMON_SOURCES} - ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c ) + ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c + ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) # Shadow demo source files. add_executable( aws_iot_demo_shadow ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c ${DEMO_COMMON_SOURCES} - ${DEMO_COMMON_HEADERS} ) + ${DEMO_COMMON_HEADERS} + ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) # Select the demo function for the Shadow demo. target_compile_definitions( aws_iot_demo_shadow @@ -57,4 +58,5 @@ endif() set_property( TARGET aws_iot_demo_shadow PROPERTY FOLDER "demos" ) source_group( include FILES ${DEMO_COMMON_HEADERS} ) source_group( "" FILES ${DEMO_COMMON_SOURCES} - ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c ) + ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c + ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) diff --git a/demos/app/win32/iot_demo_arguments_win32.c b/demos/app/win32/iot_demo_arguments_win32.c new file mode 100644 index 0000000000..8648187089 --- /dev/null +++ b/demos/app/win32/iot_demo_arguments_win32.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_demo_arguments_win32.c + * @brief Implements a function for retrieving command line arguments on Win32 + * systems. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Common demo includes. */ +#include "iot_demo_arguments.h" +#include "iot_demo_logging.h" + +/*-----------------------------------------------------------*/ + +void IotDemo_ParseArguments( int argc, + char ** argv, + IotDemoArguments_t * pArguments ) +{ +} + +/*-----------------------------------------------------------*/ diff --git a/platform/include/atomic/iot_atomic_generic.h b/platform/include/atomic/iot_atomic_generic.h index 773652d52f..b832925b8e 100644 --- a/platform/include/atomic/iot_atomic_generic.h +++ b/platform/include/atomic/iot_atomic_generic.h @@ -217,7 +217,7 @@ static inline uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, IotMutex_Lock( &IotAtomicMutex ); oldValue = *pMinuend; - *pMinuend = oldValue + subtrahend; + *pMinuend = oldValue - subtrahend; IotMutex_Unlock( &IotAtomicMutex ); return oldValue; diff --git a/platform/include/win32/iot_platform_types_win32.h b/platform/include/win32/iot_platform_types_win32.h new file mode 100644 index 0000000000..5056680039 --- /dev/null +++ b/platform/include/win32/iot_platform_types_win32.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_platform_types_win32.h + * @brief Definitions of platform layer types on Win32 systems. + */ + +#ifndef IOT_PLATFORM_TYPES_WIN32_H_ +#define IOT_PLATFORM_TYPES_WIN32_H_ + +/* Standard includes. */ +#include + +/* Win32 includes. WinSock2 is needed to prevent the usage of the WinSock + * header in Windows.h */ +#include +#include + +/** + * @brief The "mutex" type on Win32 systems. + * + * The Win32 critical section object is used in place of mutex because it + * consumes less resources and does not need to be shared between processes. + */ +typedef CRITICAL_SECTION _IotSystemMutex_t; + +/** + * @brief The semaphore type on Win32 systems. + * + * Because the Win32 API does not provide a way to obtain a semaphore count, + * the count must be tracked with the semaphore. As noted by the API documentation, + * semaphore count should not be relied on as it may change before it can be tested. + * Semaphore count is currently only used in tests. + */ +typedef struct _IotSystemSemaphore +{ + uint32_t count; /**< @brief The current count of the semaphore. */ + HANDLE semaphore; /**< @brief Handle to the Win32 semaphore. */ +} _IotSystemSemaphore_t; + +/** + * @brief Represents an #IotTimer_t on Win32 systems. + */ +typedef struct _IotSystemTimer +{ + HANDLE timer; /**< @brief Handle of the Win32 timer. */ + void * pArgument; /**< @brief First argument to threadRoutine. */ + void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ +} _IotSystemTimer_t; + +#endif /* ifndef IOT_PLATFORM_TYPES_WIN32_H_ */ diff --git a/platform/source/win32/CMakeLists.txt b/platform/source/win32/CMakeLists.txt new file mode 100644 index 0000000000..9dec783fa9 --- /dev/null +++ b/platform/source/win32/CMakeLists.txt @@ -0,0 +1,45 @@ +# Platform interface headers. +set( PLATFORM_INTERFACE_HEADERS + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_clock.h + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_metrics.h + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_network.h + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_threads.h ) + +# Platform types headers. +set( PLATFORM_TYPES_HEADERS + ${CMAKE_SOURCE_DIR}/lib/include/types/iot_platform_types.h + ${CMAKE_SOURCE_DIR}/platform/include/win32/iot_platform_types_win32.h ) + +# Platform common headers. +set( PLATFORM_COMMON_HEADERS + ${CMAKE_SOURCE_DIR}/platform/include/iot_atomic.h + ${CMAKE_SOURCE_DIR}/platform/include/atomic/iot_atomic_generic.h + ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h + ${CMAKE_SOURCE_DIR}/platform/include/iot_network_metrics.h ) + +# Platform library source files. +set( PLATFORM_SOURCES + iot_clock_win32.c + iot_threads_win32.c + ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_mbedtls.c + ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c ) + +# Platform library target. +add_library( iotplatform + ${PLATFORM_SOURCES} + ${PLATFORM_INTERFACE_HEADERS} + ${PLATFORM_TYPES_HEADERS} + ${PLATFORM_COMMON_HEADERS} ) + +# Link required libraries. +target_link_libraries( iotplatform PRIVATE mbedtls ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotplatform PRIVATE unity ) +endif() + +# Organization of platform in folders. +source_group( interface FILES ${PLATFORM_INTERFACE_HEADERS} ) +source_group( include\\types FILES ${PLATFORM_TYPES_HEADERS} ) +source_group( include FILES ${PLATFORM_COMMON_HEADERS} ) +source_group( source FILES ${PLATFORM_SOURCES} ) diff --git a/platform/source/win32/iot_clock_win32.c b/platform/source/win32/iot_clock_win32.c new file mode 100644 index 0000000000..a2dadafc2a --- /dev/null +++ b/platform/source/win32/iot_clock_win32.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_clock_win32.c + * @brief Implementation of the functions in iot_clock.h for Win32 systems. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* Platform clock include. */ +#include "platform/iot_clock.h" + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "CLOCK" ) +#include "iot_logging_setup.h" + +/* Logging macro for Win32 errors. */ +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #include + + #define _logWin32TimerError( pTimer, pMessage ) \ + { \ + char * pErrorMessage = NULL; \ + \ + FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | \ + FORMAT_MESSAGE_FROM_SYSTEM | \ + FORMAT_MESSAGE_IGNORE_INSERTS, \ + NULL, \ + GetLastError(), \ + 0, \ + ( LPSTR ) ( &pErrorMessage ), \ + 0, \ + NULL ); \ + pErrorMessage[ strlen( pErrorMessage ) - 3 ] = '\0'; \ + \ + IotLogError( "(Timer %p) %s %s.", pTimer, pMessage, pErrorMessage ); \ + LocalFree( pErrorMessage ); \ + } +#else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + #define _logWin32TimerError( pTimer, pMessage ) +#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps an #IotThreadRoutine_t with a Win32-compliant one. + * + * @param[in] pArgument The value passed as `Parameter` to `CreateTimerQueueTimer`. + * @param[in] timerOrWaitFired Ignored. + */ +static VOID CALLBACK _timerExpirationWrapper( _In_ PVOID pArgument, + _In_ BOOLEAN timerOrWaitFired ); + +/*-----------------------------------------------------------*/ + +static VOID CALLBACK _timerExpirationWrapper( _In_ PVOID pArgument, + _In_ BOOLEAN timerOrWaitFired ) +{ + IotTimer_t * pTimer = ( IotTimer_t * ) pArgument; + + /* Silence warnings about unused parameters. */ + UNREFERENCED_PARAMETER( timerOrWaitFired ); + + /* Call the wrapped thread routine. */ + pTimer->threadRoutine( pTimer->pArgument ); +} + +/*-----------------------------------------------------------*/ + +bool IotClock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ) +{ + bool status = true; + int timestringLength = 0; + SYSTEMTIME systemTime = { 0 }; + + /* Get the Win32 system time and format it in the given buffer. */ + GetSystemTime( &systemTime ); + + timestringLength = snprintf( pBuffer, + bufferSize, + "%u-%02u-%02u %02u:%02u:%02u.%03u", + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond, + systemTime.wMilliseconds ); + + /* Check for errors from snprintf. */ + if( timestringLength < 0 ) + { + /* Encoding error. */ + status = false; + } + else if( ( size_t ) timestringLength >= bufferSize ) + { + /* Buffer too small. */ + status = false; + } + else + { + /* Success; set the output parameter. */ + *pTimestringLength = ( size_t ) timestringLength; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +uint64_t IotClock_GetTimeMs( void ) +{ + return ( uint64_t ) GetTickCount64(); +} + +/*-----------------------------------------------------------*/ + +void IotClock_SleepMs( uint32_t sleepTimeMs ) +{ + Sleep( ( DWORD ) sleepTimeMs ); +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerCreate( IotTimer_t * pNewTimer, + IotThreadRoutine_t expirationRoutine, + void * pArgument ) +{ + /* Set the members of the new timer. */ + pNewTimer->pArgument = pArgument; + pNewTimer->threadRoutine = expirationRoutine; + pNewTimer->timer = INVALID_HANDLE_VALUE; + + return true; +} + +/*-----------------------------------------------------------*/ + +void IotClock_TimerDestroy( IotTimer_t * pTimer ) +{ + BOOL timerStatus = 0; + + if( pTimer->timer != INVALID_HANDLE_VALUE ) + { + timerStatus = DeleteTimerQueueTimer( NULL, + pTimer->timer, + NULL ); + + if( timerStatus == 0 ) + { + _logWin32TimerError( pTimer, "Failed to destroy timer." ); + abort(); + } + + IotLogDebug( "(Timer %p) Timer destroyed.", pTimer ); + pTimer->timer = INVALID_HANDLE_VALUE; + } +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerArm( IotTimer_t * pTimer, + uint32_t relativeTimeoutMs, + uint32_t periodMs ) +{ + BOOL timerStatus = 0; + + /* Any previous timer must be destroyed before scheduling a new one in the + * Win32 API. */ + IotClock_TimerDestroy( pTimer ); + + timerStatus = CreateTimerQueueTimer( &( pTimer->timer ), + NULL, + _timerExpirationWrapper, + pTimer, + ( DWORD ) relativeTimeoutMs, + ( DWORD ) periodMs, + WT_EXECUTEDEFAULT ); + + if( timerStatus == 0 ) + { + _logWin32TimerError( pTimer, "Failed to create timer." ); + } + else + { + IotLogDebug( "(Timer %p) Timer armed with timeout %lu and period %lu.", + pTimer, + ( unsigned long ) relativeTimeoutMs, + ( unsigned long ) periodMs ); + } + + return( timerStatus != 0 ); +} + +/*-----------------------------------------------------------*/ diff --git a/platform/source/win32/iot_threads_win32.c b/platform/source/win32/iot_threads_win32.c new file mode 100644 index 0000000000..242eb948f4 --- /dev/null +++ b/platform/source/win32/iot_threads_win32.c @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_threads_win32.c + * @brief Implementation of the functions in iot_threads.h for Win32 systems. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Windows include. */ +#include + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Atomic include. */ +#include "iot_atomic.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "THREAD" ) +#include "iot_logging_setup.h" + +/* Logging macro for Win32 errors. */ +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + #include + + #define _logWin32Error( pName, pObject, pMessage ) \ + { \ + char * pErrorMessage = NULL; \ + \ + FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | \ + FORMAT_MESSAGE_FROM_SYSTEM | \ + FORMAT_MESSAGE_IGNORE_INSERTS, \ + NULL, \ + GetLastError(), \ + 0, \ + ( LPSTR ) ( &pErrorMessage ), \ + 0, \ + NULL ); \ + pErrorMessage[ strlen( pErrorMessage ) - 3 ] = '\0'; \ + \ + IotLogError( "(%s %p) %s %s.", pName, pObject, pMessage, pErrorMessage ); \ + LocalFree( pErrorMessage ); \ + } +#else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + #define _logWin32Error( pName, pObject, pMessage ) +#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + +/* + * Provide default values for undefined memory allocation functions. + */ +#ifndef IotThreads_Malloc + #include + +/** + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define IotThreads_Malloc malloc +#endif +#ifndef IotThreads_Free + #include + +/** + * @brief Free memory. This function should have the same signature as + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define IotThreads_Free free +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Holds information about an active detached thread. + */ +typedef struct _threadInfo +{ + void * pArgument; /**< @brief First argument to `threadRoutine`. */ + IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ +} _threadInfo_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps an #IotThreadRoutine_t with a Win32-compliant one. + * + * @param[in] pArgument The value passed as `lpParameter` to `CreateThread`. + * + * @return Does not return, calls `ExitThread` with an exit code of `0`. + */ +static DWORD WINAPI _threadRoutineWrapper( _In_ LPVOID pArgument ); + +/*-----------------------------------------------------------*/ + +static DWORD WINAPI _threadRoutineWrapper( _In_ LPVOID pArgument ) +{ + /* Cast argument to correct type. */ + _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; + + /* Read thread routine and argument, then free thread info. */ + IotThreadRoutine_t threadRoutine = pThreadInfo->threadRoutine; + void * pThreadRoutineArgument = pThreadInfo->pArgument; + + IotThreads_Free( pThreadInfo ); + + /* Run the thread routine. */ + threadRoutine( pThreadRoutineArgument ); + + ExitThread( 0 ); +} + +/*-----------------------------------------------------------*/ + +bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, + void * pArgument, + int32_t priority, + size_t stackSize ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + HANDLE newThread = NULL; + _threadInfo_t * pThreadInfo = NULL; + + /* Determine the stack size of the thread to create. */ + SIZE_T threadStackSize = 0; + + if( stackSize != IOT_THREAD_DEFAULT_STACK_SIZE ) + { + threadStackSize = ( SIZE_T ) stackSize; + } + + /* Allocate memory for a new thread info. */ + pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); + + if( pThreadInfo == NULL ) + { + IotLogError( "Failed to allocate memory for new thread." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Set the members of the thread info and create a new thread. */ + pThreadInfo->pArgument = pArgument; + pThreadInfo->threadRoutine = threadRoutine; + + newThread = CreateThread( NULL, + threadStackSize, + _threadRoutineWrapper, + pThreadInfo, + 0, + NULL ); + + if( newThread == NULL ) + { + IotLogError( "Failed to create new thread." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + IotLogDebug( "(Thread %p) New thread created.", newThread ); + + /* Close the thread handle; it's not needed. This won't terminate the thread. */ + if( CloseHandle( newThread ) == 0 ) + { + _logWin32Error( "Thread", newThread, "Failed to close thread handle." ); + abort(); + } + } + + /* Clean up on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status == false ) + { + if( pThreadInfo != NULL ) + { + IotThreads_Free( pThreadInfo ); + } + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +bool IotMutex_Create( IotMutex_t * pNewMutex, + bool recursive ) +{ + /* The Win32 critical section is always recursive, so this parameter is + * ignored. */ + UNREFERENCED_PARAMETER( recursive ); + + InitializeCriticalSection( pNewMutex ); + + return true; +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Destroy( IotMutex_t * pMutex ) +{ + DeleteCriticalSection( pMutex ); +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Lock( IotMutex_t * pMutex ) +{ + EnterCriticalSection( pMutex ); +} + +/*-----------------------------------------------------------*/ + +bool IotMutex_TryLock( IotMutex_t * pMutex ) +{ + bool status = true; + + if( TryEnterCriticalSection( pMutex ) == 0 ) + { + IotLogDebug( "(Mutex %p) Mutex not available.", pMutex ); + + status = false; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Unlock( IotMutex_t * pMutex ) +{ + LeaveCriticalSection( pMutex ); +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ) +{ + bool status = true; + + /* Create a Win32 semaphore exclusive to this process. */ + pNewSemaphore->semaphore = CreateSemaphoreA( NULL, + ( LONG ) initialValue, + ( LONG ) maxValue, + NULL ); + + if( pNewSemaphore->semaphore == NULL ) + { + _logWin32Error( "Semaphore", pNewSemaphore, "Failed to create new semaphore." ); + + status = false; + } + else + { + /* Set initial semaphore count. */ + pNewSemaphore->count = initialValue; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) +{ + if( CloseHandle( pSemaphore->semaphore ) == 0 ) + { + /* This should never happen, log an error and abort if it does. */ + _logWin32Error( "Semaphore", pSemaphore, "Failed to close semaphore handle." ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) +{ + return Atomic_Add_u32( &( pSemaphore->count ), 0 ); +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) +{ + if( WaitForSingleObject( pSemaphore->semaphore, INFINITE ) != WAIT_OBJECT_0 ) + { + /* This should never happen, log an error and abort if it does. */ + _logWin32Error( "Semaphore", pSemaphore, "Failed to wait on semaphore." ); + + abort(); + } + + if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) + { + IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) +{ + return IotSemaphore_TimedWait( pSemaphore, 0 ); +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, + uint32_t timeoutMs ) +{ + DWORD status = 0; + + status = WaitForSingleObject( pSemaphore->semaphore, ( DWORD ) timeoutMs ); + + if( status == WAIT_OBJECT_0 ) + { + if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) + { + IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); + + abort(); + } + } + else if( status != WAIT_TIMEOUT ) + { + /* This should never happen, log an error and abort if it does. */ + _logWin32Error( "Semaphore", pSemaphore, "Failed to wait on semaphore." ); + + abort(); + } + + return( status == WAIT_OBJECT_0 ); +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) +{ + Atomic_Increment_u32( &( pSemaphore->count ) ); + + if( ReleaseSemaphore( pSemaphore->semaphore, 1, NULL ) == 0 ) + { + /* This should never happen, log an error and abort if it does. */ + _logWin32Error( "Semaphore", pSemaphore, "Failed to post to semaphore." ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a245dfa9c2..9a26452926 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,9 +1,7 @@ # Test runner executable and config header. -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - set( IOT_TEST_APP_FILES - ${PROJECT_SOURCE_DIR}/tests/app/iot_tests_posix.c - ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) -endif() +set( IOT_TEST_APP_FILES + ${PROJECT_SOURCE_DIR}/tests/iot_config.h + ${PROJECT_SOURCE_DIR}/tests/app/iot_tests_${IOT_PLATFORM_NAME}.c ) # Common tests. add_subdirectory( common ) diff --git a/tests/app/iot_tests_posix.c b/tests/app/iot_tests_posix.c index 8f84f5b8ae..263a00d50a 100644 --- a/tests/app/iot_tests_posix.c +++ b/tests/app/iot_tests_posix.c @@ -40,6 +40,9 @@ /* Error handling include. */ #include "private/iot_error.h" +/* Platform threads include. */ +#include "platform/iot_threads.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -52,7 +55,8 @@ /*-----------------------------------------------------------*/ /* Declaration of generic test runner function. */ -extern void RunTests( bool disableNetworkTests, bool disableLongTests ); +extern void RunTests( bool disableNetworkTests, + bool disableLongTests ); /*-----------------------------------------------------------*/ @@ -80,6 +84,7 @@ int main( int argc, char ** argv ) { IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + bool mallocMutexCreated = false; bool disableNetworkTests = false, disableLongTests = false; struct sigaction signalAction; @@ -97,6 +102,14 @@ int main( int argc, IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } + /* Create the mutex that guards the Unity malloc overrides. */ + mallocMutexCreated = IotMutex_Create( &UnityMallocMutex, false ); + + if( mallocMutexCreated == false ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + /* Unity setup. */ UnityFixture.Verbose = 1; UnityFixture.RepeatCount = 1; @@ -124,7 +137,14 @@ int main( int argc, IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( mallocMutexCreated == true ) + { + IotMutex_Destroy( &UnityMallocMutex ); + } + + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/tests/app/iot_tests_win32.c b/tests/app/iot_tests_win32.c new file mode 100644 index 0000000000..51b626faba --- /dev/null +++ b/tests/app/iot_tests_win32.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_win32.c + * @brief Common test runner on Win32 systems. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* This file calls a generic placeholder test runner function. The build system selects + * the actual test runner function by defining it. */ +#ifndef RunTests + #error "Test runner function undefined." +#endif + +/*-----------------------------------------------------------*/ + +/* Declaration of generic test runner function. */ +extern void RunTests( bool disableNetworkTests, + bool disableLongTests ); + +/*-----------------------------------------------------------*/ + +int main( int argc, + char ** argv ) +{ + IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); + bool mallocMutexCreated = false; + bool disableNetworkTests = false, disableLongTests = false; + + /* Create the mutex that guards the Unity malloc overrides. */ + mallocMutexCreated = IotMutex_Create( &UnityMallocMutex, false ); + + if( mallocMutexCreated == false ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + + /* Call the test runner function. */ + RunTests( disableNetworkTests, disableLongTests ); + + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( mallocMutexCreated == true ) + { + IotMutex_Destroy( &UnityMallocMutex ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index c27ef27861..121db8e060 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -36,6 +36,9 @@ /* Standard includes. */ #include +/* SDK initialization include. */ +#include "iot_init.h" + /* Atomic include. */ #include "iot_atomic.h" @@ -64,6 +67,7 @@ TEST_GROUP( Common_Unit_Atomic ); */ TEST_SETUP( Common_Unit_Atomic ) { + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); } /*-----------------------------------------------------------*/ @@ -73,6 +77,7 @@ TEST_SETUP( Common_Unit_Atomic ) */ TEST_TEAR_DOWN( Common_Unit_Atomic ) { + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ @@ -112,9 +117,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) ulCasComparator_32 = MAGIC_NUMBER_32BIT_1; ulCasNewValue_32 = MAGIC_NUMBER_32BIT_2; - IOT_TEST_ASM_VOLATILE( "atomic_cas_1: " ); ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_cas_1_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == ulCasNewValue_32, "Atomic_CompareAndSwap_u32 -- did not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); @@ -122,9 +125,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) /* #2 -- CAS, comparator from the same mem location. */ ulCasDestination_32 = MAGIC_NUMBER_32BIT_1; - IOT_TEST_ASM_VOLATILE( "atomic_cas_2: " ); ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, MAGIC_NUMBER_32BIT_2, ulCasDestination_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_cas_2_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_2, "Atomic_CompareAndSwap_u32 -- did not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwap_u32 -- expected return value true." ); @@ -134,9 +135,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapNewValue_32 = &ulCasNewValue_32; pReturnValue_32 = NULL; - IOT_TEST_ASM_VOLATILE( "atomic_xchg_32bit: " ); pReturnValue_32 = Atomic_Swap_Pointer( ( void ** ) &pSwapDestination_32, pSwapNewValue_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_xchg_32bit_end: " ); TEST_ASSERT_MESSAGE( pSwapDestination_32 == &ulCasNewValue_32, "Atomic_SwapPointers_p32 -- did not swap." ); TEST_ASSERT_MESSAGE( pReturnValue_32 == &ulCasDestination_32, "Atomic_SwapPointers_p32 -- expected to return previous value." ); @@ -146,9 +145,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapNewValue_8 = &uCasComparator_8; pReturnValue_8 = NULL; - IOT_TEST_ASM_VOLATILE( "atomic_xchg_8bit: nop" ); pReturnValue_8 = Atomic_Swap_Pointer( ( void ** ) &pSwapDestination_8, pSwapNewValue_8 ); - IOT_TEST_ASM_VOLATILE( "atomic_xchg_8bit_end: nop" ); TEST_ASSERT_MESSAGE( pSwapDestination_8 == &uCasComparator_8, "Atomic_SwapPointers_p32 -- did not swap." ); TEST_ASSERT_MESSAGE( pReturnValue_8 == &uCasDestination_8, "Atomic_SwapPointers_p32 -- expected to return previous value." ); @@ -157,9 +154,7 @@ TEST( Common_Unit_Atomic, AtomicCasHappyPath ) pSwapDestination_32 = &ulCasDestination_32; pSwapNewValue_32 = &ulCasNewValue_32; - IOT_TEST_ASM_VOLATILE( "atomic_CAS_pointers: nop" ); ulReturnValue_32 = Atomic_CompareAndSwap_Pointer( ( void ** ) &pSwapDestination_32, pSwapNewValue_32, &ulCasDestination_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_CAS_pointers_end: nop" ); TEST_ASSERT_MESSAGE( ( intptr_t ) pSwapDestination_32 == ( intptr_t ) pSwapNewValue_32, "Atomic_CompareAndSwapPointers_p32 -- did not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 1, "Atomic_CompareAndSwapPointers_p32 -- expected return value true." ); @@ -179,12 +174,6 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) uint8_t uAddend_8; - /* asm (built-in function) implementation -- - * for curiosity, see what instructions add-register and add-immediate are using. */ - - /* #0 -- Some examples for user -- - * casting number in range 0x80000000-0xFFFFFFFF. - * IOT_TEST_ASM_VOLATILE is omitted, as this is the normal user caller routine. */ uAddend_32 = ( uint32_t ) 0xFFFFFFFE; /* signed 0xFFFFFFFE is -2, 2's complement. */ uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); @@ -212,9 +201,7 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) uAddend_32 = 0; uDelta_32 = 1; - IOT_TEST_ASM_VOLATILE( "atomic_add_reg: " ); uReturnValue_32 = Atomic_Add_u32( &uAddend_32, uDelta_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_add_reg_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); @@ -222,9 +209,7 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #2 -- add immediate */ uAddend_32 = 0; - IOT_TEST_ASM_VOLATILE( "atomic_add_imme: " ); uReturnValue_32 = Atomic_Add_u32( &uAddend_32, 1 ); - IOT_TEST_ASM_VOLATILE( "atomic_add_imme_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Add_u32 -- did not add immediate number correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Add_u32 -- expected return value 0." ); @@ -233,18 +218,14 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) uAddend_8 = 1; uAddend_32 = ( uint32_t ) uAddend_8; - IOT_TEST_ASM_VOLATILE( "atomic_add_8bit: " ); uReturnValue_32 = Atomic_Add_u32( &uAddend_32, UINT8_MAX ); - IOT_TEST_ASM_VOLATILE( "atomic_add_8bit_end: " ); TEST_ASSERT_MESSAGE( ( uint8_t ) uReturnValue_32 == 1, "Atomic_Add_u32 -- did not roll over correctly." ); /* #4 -- sub, almost but not underflow */ uAddend_32 = 1; - IOT_TEST_ASM_VOLATILE( "atomic_sub_reg: " ); uReturnValue_32 = Atomic_Subtract_u32( &uAddend_32, 1 ); - IOT_TEST_ASM_VOLATILE( "atomic_sub_reg_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Subtract_u32 -- did not subtract correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Subtract_u32 -- expected return value 1." ); @@ -252,9 +233,7 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #5 -- inc, sanity check */ uAddend_32 = 0; - IOT_TEST_ASM_VOLATILE( "atomic_inc: " ); uReturnValue_32 = Atomic_Increment_u32( &uAddend_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_inc_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 1, "Atomic_Increment_u32 -- did not increment correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 0, "Atomic_Increment_u32 -- expected return value 0." ); @@ -262,9 +241,7 @@ TEST( Common_Unit_Atomic, AtomicArithmeticHappyPath ) /* #6 -- dec, sanity check */ uAddend_32 = 1; - IOT_TEST_ASM_VOLATILE( "atomic_dec: " ); uReturnValue_32 = Atomic_Decrement_u32( &uAddend_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_dec_end: " ); TEST_ASSERT_MESSAGE( uAddend_32 == 0, "Atomic_Decrement_u32 -- did not decrement correctly." ); TEST_ASSERT_MESSAGE( uReturnValue_32 == 1, "Atomic_Decrement_u32 -- expected return value 1." ); @@ -280,9 +257,7 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_1; ulOp2 = MAGIC_NUMBER_32BIT_2; - IOT_TEST_ASM_VOLATILE( "atomic_and: " ); ulReturnValue = Atomic_AND_u32( &ulOp1, ulOp2 ); - IOT_TEST_ASM_VOLATILE( "atomic_and_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0xA0A0A0A0, "Atomic_AND_u32 -- did not ANDed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_AND_u32 -- expected return value 0xA5A5A5A5." ); @@ -291,9 +266,7 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_2; ulOp2 = MAGIC_NUMBER_32BIT_3; - IOT_TEST_ASM_VOLATILE( "atomic_or: " ); ulReturnValue = Atomic_OR_u32( &ulOp1, ulOp2 ); - IOT_TEST_ASM_VOLATILE( "atomic_or_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0xF0F0F0FF, "Atomic_OR_u32 -- did not ORed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_2, "Atomic_AND_u32 -- expected return value 0xF0F0F0F0." ); @@ -302,9 +275,7 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_1; ulOp2 = MAGIC_NUMBER_32BIT_2; - IOT_TEST_ASM_VOLATILE( "atomic_nand: " ); ulReturnValue = Atomic_NAND_u32( &ulOp1, ulOp2 ); - IOT_TEST_ASM_VOLATILE( "atomic_nand_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0x5F5F5F5F, "Atomic_NAND_u32 -- did not NANDed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_NAND_u32 -- expected return value 0xA5A5A5A5." ); @@ -313,9 +284,7 @@ TEST( Common_Unit_Atomic, AtomicBitwiseHappyPath ) ulOp1 = MAGIC_NUMBER_32BIT_1; ulOp2 = MAGIC_NUMBER_32BIT_2; - IOT_TEST_ASM_VOLATILE( "atomic_xor: " ); ulReturnValue = Atomic_XOR_u32( &ulOp1, ulOp2 ); - IOT_TEST_ASM_VOLATILE( "atomic_XOR_end: " ); TEST_ASSERT_MESSAGE( ulOp1 == 0x55555555, "Atomic_XOR_u32 -- did not XORed correctly." ); TEST_ASSERT_MESSAGE( ulReturnValue == MAGIC_NUMBER_32BIT_1, "Atomic_XOR_u32 -- expected return value 0xA5A5A5A5." ); @@ -337,9 +306,7 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) ulCasComparator_32 = MAGIC_NUMBER_32BIT_2; ulCasNewValue_32 = MAGIC_NUMBER_32BIT_3; - IOT_TEST_ASM_VOLATILE( "atomic_cas_neq: " ); ulReturnValue_32 = Atomic_CompareAndSwap_u32( &ulCasDestination_32, ulCasNewValue_32, ulCasComparator_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_cas_neq_end: " ); TEST_ASSERT_MESSAGE( ulCasDestination_32 == MAGIC_NUMBER_32BIT_1, "Atomic_CompareAndSwap_u32 -- should not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwap_u32 -- should not swap." ); @@ -349,9 +316,7 @@ TEST( Common_Unit_Atomic, AtomicCasFailToSwap ) pCasComparator_32 = &ulCasComparator_32; pCasNewValue_32 = &ulCasNewValue_32; - IOT_TEST_ASM_VOLATILE( "atomic_cas_pointers_neq: " ); ulReturnValue_32 = Atomic_CompareAndSwap_Pointer( ( void ** ) &pCasDestination_32, pCasNewValue_32, pCasComparator_32 ); - IOT_TEST_ASM_VOLATILE( "atomic_cas_pointers_neq_end: " ); TEST_ASSERT_MESSAGE( ( intptr_t ) pCasDestination_32 == ( intptr_t ) &ulCasDestination_32, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); TEST_ASSERT_MESSAGE( ulReturnValue_32 == 0, "Atomic_CompareAndSwapPointers_p32 -- should not swap." ); diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 0654553f22..96f1581543 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -33,11 +33,14 @@ #include #include +/* SDK initialization include. */ +#include "iot_init.h" + /* Platform layer includes. */ #include "platform/iot_threads.h" #include "platform/iot_clock.h" -/* MQTT internal include. */ +/* Task pool internal include. */ #include "private/iot_taskpool_internal.h" /* Task pool include. */ @@ -85,6 +88,7 @@ TEST_GROUP( Common_Unit_Task_Pool ); */ TEST_SETUP( Common_Unit_Task_Pool ) { + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); } /*-----------------------------------------------------------*/ @@ -94,6 +98,7 @@ TEST_SETUP( Common_Unit_Task_Pool ) */ TEST_TEAR_DOWN( Common_Unit_Task_Pool ) { + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ diff --git a/tests/iot_config.h b/tests/iot_config.h index 3254622c54..abaa4a20e9 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -68,6 +68,9 @@ /* Linear containers library configuration. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) +/* Task pool library configuration. */ +#define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) + /* MQTT library configuration. */ #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_METRICS ( 0 ) @@ -186,17 +189,14 @@ typedef struct IotNetworkCredentials IotTestNetworkCredentials_t; #define IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER { 0 } #endif -/* Macro for placing inline assembly in test code. */ -#define IOT_TEST_ASM_VOLATILE( x ) __asm__ __volatile__ ( x ) - -#ifndef __GNUC__ - #error "Unsupported compiler. Only gcc and clang are supported." -#endif - /* Configure code coverage testing if enabled. */ #if IOT_TEST_COVERAGE == 1 + #ifndef __GNUC__ + #error "Unsupported compiler. Only gcc and clang are supported for coverage." + #endif + /* Define the empty else marker if test coverage is enabled. */ - #define EMPTY_ELSE_MARKER IOT_TEST_ASM_VOLATILE( "nop" ) + #define EMPTY_ELSE_MARKER __asm__ __volatile__ ( "nop" ) /* Define a custom logging puts function. This function allows coverage * testing of logging functions, but prevents excessive logs from being diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 1b5f7fce95..a800e2339c 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -640,7 +640,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) IotSemaphore_t waitSem; /* An arbitrary MQTT packet for this test. */ - static uint8_t pPacket[ 2 ] = { MQTT_PACKET_TYPE_DISCONNECT, 0x00 }; + static uint8_t pPacket[ 2 ] = { MQTT_PACKET_TYPE_PINGREQ, 0x00 }; /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -666,7 +666,7 @@ TEST( MQTT_Unit_API, OperationWaitTimeout ) &pOperation ) ); /* Set an arbitrary MQTT packet for the operation. */ - pOperation->u.operation.type = IOT_MQTT_DISCONNECT; + pOperation->u.operation.type = IOT_MQTT_PINGREQ; pOperation->u.operation.pMqttPacket = pPacket; pOperation->u.operation.packetSize = 2; diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 767accf547..1cce3eb21a 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -72,8 +72,8 @@ /* * Constants relating to the test subscription list. */ -#define LIST_ITEM_COUNT ( 10 ) /**< @brief Number of subscriptions. */ -#define TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ +#define LIST_ITEM_COUNT ( 10 ) /**< @brief Number of subscriptions. */ +#define TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ #define TEST_TOPIC_FILTER_LENGTH ( sizeof( TEST_TOPIC_FILTER_FORMAT ) + 1 ) /**< @brief Maximum length of each topic filter. */ /* @@ -87,8 +87,8 @@ * @brief A non-NULL function pointer to use for subscription callback. This * "function" should cause a crash if actually called. */ -#define CALLBACK_FUNCTION \ - ( ( void ( * )( void *, \ +#define SUBSCRIPTION_CALLBACK_FUNCTION \ + ( ( void ( * )( void *, \ IotMqttCallbackParam_t * ) ) 0x1 ) /** @@ -165,7 +165,7 @@ static void _populateList( void ) ( void ) memset( pSubscription, 0x00, sizeof( _mqttSubscription_t ) + TEST_TOPIC_FILTER_LENGTH ); pSubscription->packetInfo.identifier = 1; pSubscription->packetInfo.order = i; - pSubscription->callback.function = CALLBACK_FUNCTION; + pSubscription->callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; pSubscription->topicFilterLength = ( uint16_t ) snprintf( pSubscription->pTopicFilter, TEST_TOPIC_FILTER_LENGTH, TEST_TOPIC_FILTER_FORMAT, @@ -290,7 +290,7 @@ static void _multithreadTestThread( void * pArgument ) /* Add items to the subscription list. */ for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - subscription[ i ].callback.function = CALLBACK_FUNCTION; + subscription[ i ].callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], MT_TOPIC_FILTER_LENGTH, @@ -640,7 +640,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddDuplicate ) /* Set valid values in the subscription list. */ for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - subscription[ i ].callback.function = CALLBACK_FUNCTION; + subscription[ i ].callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], TEST_TOPIC_FILTER_LENGTH, @@ -709,7 +709,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) /* Set valid values in the subscription list. */ for( i = 0; i < LIST_ITEM_COUNT; i++ ) { - subscription[ i ].callback.function = CALLBACK_FUNCTION; + subscription[ i ].callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; subscription[ i ].pTopicFilter = pTopicFilters[ i ]; subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], TEST_TOPIC_FILTER_LENGTH, diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 5b25317b11..c1dc38e481 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -597,7 +597,7 @@ TEST( Shadow_System, DeltaCallback ) IotSemaphore_t waitSem; /* Create a semaphore to wait on. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 2 ) ); /* Set the delta callback information. */ deltaCallback.pCallbackContext = &waitSem; diff --git a/third_party/unity/unity/fixture/unity_fixture.c b/third_party/unity/unity/fixture/unity_fixture.c index c2f3ca34f9..65e267596c 100644 --- a/third_party/unity/unity/fixture/unity_fixture.c +++ b/third_party/unity/unity/fixture/unity_fixture.c @@ -10,7 +10,8 @@ #include /* For thread safety. */ -#include +#include "iot_config.h" +#include "platform/iot_threads.h" struct UNITY_FIXTURE_T UnityFixture; @@ -137,34 +138,33 @@ void UnityIgnoreTest(const char* printableName, const char* group, const char* n #define MALLOC_DONT_FAIL -1 static int malloc_count; static int malloc_fail_countdown = MALLOC_DONT_FAIL; -extern pthread_mutex_t CriticalSectionMutex; void UnityMalloc_StartTest(void) { - pthread_mutex_lock(&CriticalSectionMutex); + IotMutex_Lock(&UnityMallocMutex); malloc_count = 0; malloc_fail_countdown = MALLOC_DONT_FAIL; - pthread_mutex_unlock(&CriticalSectionMutex); + IotMutex_Unlock(&UnityMallocMutex); } void UnityMalloc_EndTest(void) { - pthread_mutex_lock(&CriticalSectionMutex); + IotMutex_Lock(&UnityMallocMutex); malloc_fail_countdown = MALLOC_DONT_FAIL; if (malloc_count != 0) { - pthread_mutex_unlock(&CriticalSectionMutex); + IotMutex_Unlock(&UnityMallocMutex); UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "This test leaks!"); } - pthread_mutex_unlock(&CriticalSectionMutex); + IotMutex_Unlock(&UnityMallocMutex); } void UnityMalloc_MakeMallocFailAfterCount(int countdown) { - pthread_mutex_lock(&CriticalSectionMutex); + IotMutex_Lock(&UnityMallocMutex); malloc_fail_countdown = countdown; - pthread_mutex_unlock(&CriticalSectionMutex); + IotMutex_Unlock(&UnityMallocMutex); } /* These definitions are always included from unity_fixture_malloc_overrides.h */ diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h index 0b5d833fb9..4bd8f0a600 100644 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h @@ -10,6 +10,9 @@ #include +#include IOT_SYSTEM_TYPES_FILE +typedef _IotSystemMutex_t IotMutex_t; + #ifdef UNITY_EXCLUDE_STDLIB_MALLOC /* Define this macro to remove the use of stdlib.h, malloc, and free. * Many embedded systems do not have a heap or malloc/free by default. @@ -40,7 +43,7 @@ #define free unity_free_mt /* Thread-safety wrappers for the unity memory functions. */ -extern pthread_mutex_t CriticalSectionMutex; +extern IotMutex_t UnityMallocMutex; void* unity_malloc_mt(size_t size); void* unity_calloc_mt(size_t num, size_t size); diff --git a/third_party/unity/unity/fixture/unity_memory_mt.c b/third_party/unity/unity/fixture/unity_memory_mt.c index e8a9681d76..012904d059 100644 --- a/third_party/unity/unity/fixture/unity_memory_mt.c +++ b/third_party/unity/unity/fixture/unity_memory_mt.c @@ -1,10 +1,9 @@ -/* Wrappers that make the unity memory functions thread-safe. Implemented for - * POSIX systems. */ +/* Wrappers that make the unity memory functions thread-safe. */ -#include "unity_fixture_malloc_overrides.h" -#include +#include "iot_config.h" +#include "platform/iot_threads.h" -pthread_mutex_t CriticalSectionMutex = PTHREAD_MUTEX_INITIALIZER; +IotMutex_t UnityMallocMutex; extern void* unity_malloc(size_t size); extern void* unity_calloc(size_t num, size_t size); @@ -15,15 +14,11 @@ void* unity_malloc_mt(size_t size) { void* mem = NULL; - if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + IotMutex_Lock(&UnityMallocMutex); mem = unity_malloc(size); - if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) - { - unity_free(mem); - mem = NULL; - } + IotMutex_Unlock(&UnityMallocMutex); return mem; } @@ -32,15 +27,11 @@ void* unity_calloc_mt(size_t num, size_t size) { void* mem = NULL; - if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + IotMutex_Lock(&UnityMallocMutex); mem = unity_calloc(num, size); - if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) - { - unity_free(mem); - mem = NULL; - } + IotMutex_Unlock(&UnityMallocMutex); return mem; } @@ -49,24 +40,20 @@ void* unity_realloc_mt(void * oldMem, size_t size) { void* mem = NULL; - if (pthread_mutex_lock(&CriticalSectionMutex) != 0) return NULL; + IotMutex_Lock(&UnityMallocMutex); mem = unity_realloc(oldMem, size); - if (pthread_mutex_unlock(&CriticalSectionMutex) != 0) - { - unity_free(mem); - mem = NULL; - } + IotMutex_Unlock(&UnityMallocMutex); return mem; } void unity_free_mt(void * mem) { - pthread_mutex_lock(&CriticalSectionMutex); + IotMutex_Lock(&UnityMallocMutex); unity_free(mem); - pthread_mutex_unlock(&CriticalSectionMutex); + IotMutex_Unlock(&UnityMallocMutex); } From 0e7fab99c364bf12deb3b3b7b09455cb566bebe9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 14 May 2019 16:15:26 -0700 Subject: [PATCH 145/844] Add retries in tests. (#427) --- .travis.yml | 7 +- platform/source/posix/iot_threads_posix.c | 47 ++------ platform/source/win32/iot_clock_win32.c | 33 ++++-- scripts/ci_test_coverage.sh | 3 +- tests/mqtt/system/iot_tests_mqtt_system.c | 105 ++++++++++++------ .../system/aws_iot_tests_shadow_system.c | 28 ++++- 6 files changed, 128 insertions(+), 95 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d4e9872ae..505ffc3f57 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,10 +18,7 @@ jobs: - env: RUN_TEST=common - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - env: RUN_TEST=mqtt NETWORK_STACK=openssl - - env: RUN_TEST=shadow NETWORK_STACK=mbedtls - - if: type = push - env: RUN_TEST=shadow NETWORK_STACK=openssl - - env: RUN_TEST=defender + - env: RUN_TEST=shadow - if: type = push compiler: gcc env: RUN_TEST=coverage @@ -34,8 +31,6 @@ before_install: # Install dependencies. install: - # Dependencies required across the entire build matrix. - - sudo apt-get install -y cmake # Install OpenSSL if needed. - if [ "$NETWORK_STACK" = "openssl" ]; then sudo apt-get install -y libssl-dev; fi # Install graphviz only for documentation builds. diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index ec8a149780..995b10fa6c 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -156,8 +156,6 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, ( void ) priority; ( void ) stackSize; - IotLogDebug( "Creating new thread." ); - /* Allocate memory for the new thread info. */ pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); @@ -247,8 +245,6 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, if( recursive == true ) { - IotLogDebug( "Creating new recursive mutex %p.", pNewMutex ); - /* Create new mutex attributes object. */ mutexError = pthread_mutexattr_init( &mutexAttributes ); @@ -277,10 +273,6 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, } } } - else - { - IotLogDebug( "Creating new mutex %p.", pNewMutex ); - } mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); @@ -306,8 +298,6 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, void IotMutex_Destroy( IotMutex_t * pMutex ) { - IotLogDebug( "Destroying mutex %p.", pMutex ); - int mutexError = pthread_mutex_destroy( pMutex ); if( mutexError != 0 ) @@ -316,7 +306,7 @@ void IotMutex_Destroy( IotMutex_t * pMutex ) IotLogError( "Failed to destroy mutex %p. errno=%d.", pMutex, mutexError ); - + abort(); } } @@ -325,8 +315,6 @@ void IotMutex_Destroy( IotMutex_t * pMutex ) void IotMutex_Lock( IotMutex_t * pMutex ) { - IotLogDebug( "Locking mutex %p.", pMutex ); - int mutexError = pthread_mutex_lock( pMutex ); if( mutexError != 0 ) @@ -335,7 +323,7 @@ void IotMutex_Lock( IotMutex_t * pMutex ) IotLogError( "Failed to lock mutex %p. errno=%d.", pMutex, mutexError ); - + abort(); } } @@ -345,9 +333,6 @@ void IotMutex_Lock( IotMutex_t * pMutex ) bool IotMutex_TryLock( IotMutex_t * pMutex ) { bool status = true; - - IotLogDebug( "Attempting to lock mutex %p.", pMutex ); - int mutexError = pthread_mutex_trylock( pMutex ); if( mutexError != 0 ) @@ -366,8 +351,6 @@ bool IotMutex_TryLock( IotMutex_t * pMutex ) void IotMutex_Unlock( IotMutex_t * pMutex ) { - IotLogDebug( "Unlocking mutex %p.", pMutex ); - int mutexError = pthread_mutex_unlock( pMutex ); if( mutexError != 0 ) @@ -376,7 +359,7 @@ void IotMutex_Unlock( IotMutex_t * pMutex ) IotLogError( "Failed to unlock mutex %p. errno=%d.", pMutex, mutexError ); - + abort(); } } @@ -389,8 +372,6 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, { bool status = true; - IotLogDebug( "Creating new semaphore %p.", pNewSemaphore ); - if( maxValue > ( uint32_t ) SEM_VALUE_MAX ) { IotLogError( "%lu is larger than the maximum value a semaphore may" @@ -425,12 +406,10 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) IotLogError( "Failed to query semaphore count of %p. errno=%d.", pSemaphore, errno ); - + abort(); } - IotLogDebug( "Semaphore %p has count %d.", pSemaphore, count ); - return ( uint32_t ) count; } @@ -438,15 +417,13 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) { - IotLogDebug( "Destroying semaphore %p.", pSemaphore ); - if( sem_destroy( pSemaphore ) != 0 ) { /* This block should not be reached; log an error and abort if it is. */ IotLogError( "Failed to destroy semaphore %p. errno=%d.", pSemaphore, errno ); - + abort(); } } @@ -455,15 +432,13 @@ void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) { - IotLogDebug( "Waiting on semaphore %p.", pSemaphore ); - if( sem_wait( pSemaphore ) != 0 ) { /* This block should not be reached; log an error and abort if it is. */ IotLogError( "Failed to wait on semaphore %p. errno=%d.", pSemaphore, errno ); - + abort(); } } @@ -474,8 +449,6 @@ bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) { bool status = true; - IotLogDebug( "Attempting to wait on semaphore %p.", pSemaphore ); - if( sem_trywait( pSemaphore ) != 0 ) { IotLogDebug( "Semaphore %p is not available. errno=%d.", @@ -496,10 +469,6 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, bool status = true; struct timespec timeout = { 0 }; - IotLogDebug( "Attempting to wait on semaphore %p with timeout %llu.", - pSemaphore, - timeoutMs ); - if( IotClock_TimeoutToTimespec( timeoutMs, &timeout ) == false ) { IotLogError( "Invalid timeout." ); @@ -525,15 +494,13 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) { - IotLogDebug( "Posting to semaphore %p.", pSemaphore ); - if( sem_post( pSemaphore ) != 0 ) { /* This block should not be reached; log an error and abort if it is. */ IotLogError( "Failed to post to semaphore %p. errno=%d.", pSemaphore, errno ); - + abort(); } } diff --git a/platform/source/win32/iot_clock_win32.c b/platform/source/win32/iot_clock_win32.c index a2dadafc2a..b787b209f1 100644 --- a/platform/source/win32/iot_clock_win32.c +++ b/platform/source/win32/iot_clock_win32.c @@ -179,14 +179,33 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) if( pTimer->timer != INVALID_HANDLE_VALUE ) { - timerStatus = DeleteTimerQueueTimer( NULL, - pTimer->timer, - NULL ); - - if( timerStatus == 0 ) + /* Per the Win32 API docs, DeleteTimerQueueTimer should be retried until + * successful. */ + while( true ) { - _logWin32TimerError( pTimer, "Failed to destroy timer." ); - abort(); + timerStatus = DeleteTimerQueueTimer( NULL, + pTimer->timer, + NULL ); + + /* Check if the timer was successfully deleted. */ + if( timerStatus == 0 ) + { + if( GetLastError() == ERROR_IO_PENDING ) + { + /* Nothing further needs to be done for ERROR_IO_PENDING. */ + break; + } + else + { + /* Sleep a short time before trying again. */ + Sleep( 100 ); + } + } + else + { + /* Timer was successfully deleted. */ + break; + } } IotLogDebug( "(Timer %p) Timer destroyed.", pTimer ); diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 79a192e932..16c307c32e 100644 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -20,9 +20,10 @@ make -j2 ./bin/aws_iot_tests_shadow ./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" -# Generate code coverage results, excluding demo files, tests files, and third party files. +# Generate code coverage results, but only for files in lib/. lcov --directory . --capture --output-file coverage.info lcov --remove coverage.info '*demo*' --output-file coverage.info +lcov --remove coverage.info '*platform*' --output-file coverage.info lcov --remove coverage.info '*tests*' --output-file coverage.info lcov --remove coverage.info '*third_party*' --output-file coverage.info diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 0654e591ef..bdc98b38fa 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -570,6 +570,39 @@ static void _subscribePublishWait( IotMqttQos_t qos ) /*-----------------------------------------------------------*/ +/** + * @brief Wraps @ref mqtt_function_connect with retries. + */ +static IotMqttError_t _connectWithRetry( const IotMqttNetworkInfo_t * pNetworkInfo, + const IotMqttConnectInfo_t * pConnectInfo, + uint32_t timeoutMs, + IotMqttConnection_t * const pMqttConnection ) +{ + int32_t i = 0; + uint32_t sleepTimeMs = 1000; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + + /* Retry up to 5 times. */ + for( i = 0; i < 5; i++ ) + { + status = IotMqtt_Connect( pNetworkInfo, pConnectInfo, timeoutMs, pMqttConnection ); + + /* If the server fails to respond and times out, retry. */ + if( status != IOT_MQTT_TIMEOUT ) + { + break; + } + + /* Sleep for some time before retrying. */ + IotClock_SleepMs( sleepTimeMs ); + sleepTimeMs *= 2; + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT system tests. */ @@ -745,10 +778,10 @@ TEST( MQTT_System, SubscribePublishAsync ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -864,10 +897,10 @@ TEST( MQTT_System, LastWillAndTestament ) if( TEST_PROTECT() ) { - status = IotMqtt_Connect( &lwtNetworkInfo, - &lwtConnectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &lwtListener ); + status = _connectWithRetry( &lwtNetworkInfo, + &lwtConnectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &lwtListener ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -897,10 +930,10 @@ TEST( MQTT_System, LastWillAndTestament ) willInfo.pPayload = _pSamplePayload; willInfo.payloadLength = _samplePayloadLength; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Abruptly close the MQTT connection. This should cause the LWT @@ -945,10 +978,10 @@ TEST( MQTT_System, RestorePreviousSession ) if( TEST_PROTECT() ) { /* Establish a persistent MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Add a subscription. */ @@ -973,10 +1006,10 @@ TEST( MQTT_System, RestorePreviousSession ) connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ @@ -1019,10 +1052,10 @@ TEST( MQTT_System, RestorePreviousSession ) * session to clean up persistent sessions on the MQTT server created by this * test. */ connectInfo.cleanSession = true; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); if( status == IOT_MQTT_SUCCESS ) { @@ -1059,10 +1092,10 @@ TEST( MQTT_System, WaitAfterDisconnect ) publishInfo.retryMs = 5000; /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -1127,10 +1160,10 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with a completion callback. */ @@ -1199,10 +1232,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _connectWithRetry( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with to the test topics. */ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index c1dc38e481..1131c36c49 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -41,6 +41,7 @@ #include "iot_json_utils.h" /* Platform layer includes. */ +#include "platform/iot_clock.h" #include "platform/iot_threads.h" /* Test network header include. */ @@ -430,6 +431,9 @@ TEST_GROUP( Shadow_System ); */ TEST_SETUP( Shadow_System ) { + int32_t i = 0; + uint32_t sleepTimeMs = 1000; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -478,11 +482,25 @@ TEST_SETUP( Shadow_System ) connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - /* Establish an MQTT connection. */ - if( IotMqtt_Connect( &_networkInfo, - &connectInfo, - AWS_IOT_TEST_SHADOW_TIMEOUT, - &_mqttConnection ) != IOT_MQTT_SUCCESS ) + /* Establish an MQTT connection. Retry up to 5 times. */ + for( i = 0; i < 5; i++ ) + { + connectStatus = IotMqtt_Connect( &_networkInfo, + &connectInfo, + AWS_IOT_TEST_SHADOW_TIMEOUT, + &_mqttConnection ); + + if( connectStatus != IOT_MQTT_TIMEOUT ) + { + break; + } + + /* Sleep for some time before retrying. */ + IotClock_SleepMs( sleepTimeMs ); + sleepTimeMs *= 2; + } + + if( connectStatus != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); } From 7a9e8dbcebac812472095dd1ddb95ba4e612196f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 15 May 2019 10:07:55 -0700 Subject: [PATCH 146/844] Adjust client identifiers in CI (#428) --- .travis.yml | 1 + demos/app/iot_demo.c | 14 +-- tests/mqtt/system/iot_tests_mqtt_system.c | 105 ++++++------------ .../system/aws_iot_tests_shadow_system.c | 28 +---- 4 files changed, 49 insertions(+), 99 deletions(-) diff --git a/.travis.yml b/.travis.yml index 505ffc3f57..803e892d5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,6 +42,7 @@ install: script: # Set identifier (client identifier OR Thing Name). - export IOT_IDENTIFIER="$IOT_IDENTIFIER_PREFIX$RUN_TEST" + - if [ "$NETWORK_STACK" = "openssl" ]; then export IOT_IDENTIFIER="${IOT_IDENTIFIER}ossl"; fi # Set default compiler options. Individual test scripts may override this. - export COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" # Choose the network abstraction. diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 10ccc4b5fa..8e81769469 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -94,6 +94,13 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) /* Default to AWS IoT MQTT mode. */ pArguments->awsIotMqttMode = true; + /* Set default identifier if defined. The identifier is used as either the + * MQTT client identifier or the Thing Name, which identifies this client to + * the cloud. */ + #ifdef IOT_DEMO_IDENTIFIER + pArguments->pIdentifier = IOT_DEMO_IDENTIFIER; + #endif + /* Set default secured connection status if defined. */ #ifdef IOT_DEMO_SECURED_CONNECTION pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; @@ -223,13 +230,6 @@ int main( int argc, IotNetworkCredentials_t credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, * pCredentials = NULL; - /* Set default identifier if defined. The identifier is used as either the - * MQTT client identifier or the Thing Name, which identifies this client to - * the cloud. */ - #ifdef IOT_DEMO_IDENTIFIER - demoArguments.pIdentifier = IOT_DEMO_IDENTIFIER; - #endif - /* Load the default demo arguments from the demo config header. */ _setDefaultArguments( &demoArguments ); diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index bdc98b38fa..0654e591ef 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -570,39 +570,6 @@ static void _subscribePublishWait( IotMqttQos_t qos ) /*-----------------------------------------------------------*/ -/** - * @brief Wraps @ref mqtt_function_connect with retries. - */ -static IotMqttError_t _connectWithRetry( const IotMqttNetworkInfo_t * pNetworkInfo, - const IotMqttConnectInfo_t * pConnectInfo, - uint32_t timeoutMs, - IotMqttConnection_t * const pMqttConnection ) -{ - int32_t i = 0; - uint32_t sleepTimeMs = 1000; - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - - /* Retry up to 5 times. */ - for( i = 0; i < 5; i++ ) - { - status = IotMqtt_Connect( pNetworkInfo, pConnectInfo, timeoutMs, pMqttConnection ); - - /* If the server fails to respond and times out, retry. */ - if( status != IOT_MQTT_TIMEOUT ) - { - break; - } - - /* Sleep for some time before retrying. */ - IotClock_SleepMs( sleepTimeMs ); - sleepTimeMs *= 2; - } - - return status; -} - -/*-----------------------------------------------------------*/ - /** * @brief Test group for MQTT system tests. */ @@ -778,10 +745,10 @@ TEST( MQTT_System, SubscribePublishAsync ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -897,10 +864,10 @@ TEST( MQTT_System, LastWillAndTestament ) if( TEST_PROTECT() ) { - status = _connectWithRetry( &lwtNetworkInfo, - &lwtConnectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &lwtListener ); + status = IotMqtt_Connect( &lwtNetworkInfo, + &lwtConnectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &lwtListener ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -930,10 +897,10 @@ TEST( MQTT_System, LastWillAndTestament ) willInfo.pPayload = _pSamplePayload; willInfo.payloadLength = _samplePayloadLength; - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Abruptly close the MQTT connection. This should cause the LWT @@ -978,10 +945,10 @@ TEST( MQTT_System, RestorePreviousSession ) if( TEST_PROTECT() ) { /* Establish a persistent MQTT connection. */ - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Add a subscription. */ @@ -1006,10 +973,10 @@ TEST( MQTT_System, RestorePreviousSession ) connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ @@ -1052,10 +1019,10 @@ TEST( MQTT_System, RestorePreviousSession ) * session to clean up persistent sessions on the MQTT server created by this * test. */ connectInfo.cleanSession = true; - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); if( status == IOT_MQTT_SUCCESS ) { @@ -1092,10 +1059,10 @@ TEST( MQTT_System, WaitAfterDisconnect ) publishInfo.retryMs = 5000; /* Establish the MQTT connection. */ - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -1160,10 +1127,10 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with a completion callback. */ @@ -1232,10 +1199,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = _connectWithRetry( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with to the test topics. */ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 1131c36c49..c1dc38e481 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -41,7 +41,6 @@ #include "iot_json_utils.h" /* Platform layer includes. */ -#include "platform/iot_clock.h" #include "platform/iot_threads.h" /* Test network header include. */ @@ -431,9 +430,6 @@ TEST_GROUP( Shadow_System ); */ TEST_SETUP( Shadow_System ) { - int32_t i = 0; - uint32_t sleepTimeMs = 1000; - IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -482,25 +478,11 @@ TEST_SETUP( Shadow_System ) connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - /* Establish an MQTT connection. Retry up to 5 times. */ - for( i = 0; i < 5; i++ ) - { - connectStatus = IotMqtt_Connect( &_networkInfo, - &connectInfo, - AWS_IOT_TEST_SHADOW_TIMEOUT, - &_mqttConnection ); - - if( connectStatus != IOT_MQTT_TIMEOUT ) - { - break; - } - - /* Sleep for some time before retrying. */ - IotClock_SleepMs( sleepTimeMs ); - sleepTimeMs *= 2; - } - - if( connectStatus != IOT_MQTT_SUCCESS ) + /* Establish an MQTT connection. */ + if( IotMqtt_Connect( &_networkInfo, + &connectInfo, + AWS_IOT_TEST_SHADOW_TIMEOUT, + &_mqttConnection ) != IOT_MQTT_SUCCESS ) { TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); } From a16991906b0f3946532cb13653a30e5e722fa239 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 15 May 2019 10:45:58 -0700 Subject: [PATCH 147/844] Sync demo changes from AFR (#429) --- demos/source/aws_iot_demo_shadow.c | 20 ++++++++++---------- demos/source/iot_demo_mqtt.c | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 4250ba3f33..3545a9365f 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -97,10 +97,10 @@ "{" \ "\"state\":{" \ "\"desired\":{" \ - "\"powerOn\":%.1d" \ + "\"powerOn\":%01d" \ "}" \ "}," \ - "\"clientToken\":\"%.6llu\"" \ + "\"clientToken\":\"%06lu\"" \ "}" /** @@ -109,7 +109,7 @@ * Because all the format specifiers in #SHADOW_DESIRED_JSON include a length, * its full size is known at compile-time. */ -#define EXPECTED_DESIRED_JSON_SIZE ( sizeof( SHADOW_DESIRED_JSON ) - 4 ) +#define EXPECTED_DESIRED_JSON_SIZE ( sizeof( SHADOW_DESIRED_JSON ) - 3 ) /** * @brief Format string representing a Shadow document with a "reported" state. @@ -122,10 +122,10 @@ "{" \ "\"state\":{" \ "\"reported\":{" \ - "\"powerOn\":%.1d" \ + "\"powerOn\":%01d" \ "}" \ "}," \ - "\"clientToken\":\"%.6llu\"" \ + "\"clientToken\":\"%06lu\"" \ "}" /** @@ -134,7 +134,7 @@ * Because all the format specifiers in #SHADOW_REPORTED_JSON include a length, * its full size is known at compile-time. */ -#define EXPECTED_REPORTED_JSON_SIZE ( sizeof( SHADOW_REPORTED_JSON ) - 4 ) +#define EXPECTED_REPORTED_JSON_SIZE ( sizeof( SHADOW_REPORTED_JSON ) - 3 ) /*-----------------------------------------------------------*/ @@ -320,8 +320,8 @@ static void _shadowDeltaCallback( void * pCallbackContext, updateDocumentLength = snprintf( pUpdateDocument, EXPECTED_REPORTED_JSON_SIZE + 1, SHADOW_REPORTED_JSON, - currentState, - ( long long unsigned ) IotClock_GetTimeMs() % 1000000 ); + ( int ) currentState, + ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) ); if( ( size_t ) updateDocumentLength != EXPECTED_REPORTED_JSON_SIZE ) { @@ -704,8 +704,8 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, status = snprintf( pUpdateDocument, EXPECTED_DESIRED_JSON_SIZE + 1, SHADOW_DESIRED_JSON, - desiredState, - ( long long unsigned ) IotClock_GetTimeMs() % 1000000 ); + ( int ) desiredState, + ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) ); /* Check for errors from snprintf. The expected value is the length of * the desired JSON document less the format specifier for the state. */ diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index 70565bcd03..d2b5b4aa21 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -407,6 +407,10 @@ static int _establishMqttConnection( bool awsIotMqttMode, networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; networkInfo.pNetworkInterface = pNetworkInterface; + #if ( IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 ) && defined( IOT_DEMO_MQTT_SERIALIZER ) + networkInfo.pSerializer = IOT_DEMO_MQTT_SERIALIZER; + #endif + /* Set the members of the connection info not set by the initializer. */ connectInfo.awsIotMqttMode = awsIotMqttMode; connectInfo.cleanSession = true; From 1a72b6defed6a2cd527a710f1fc1e0b23f8655d5 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 15 May 2019 13:36:43 -0700 Subject: [PATCH 148/844] Compare subscription struct by member (#430) --- tests/mqtt/unit/iot_tests_mqtt_receive.c | 40 +++++++++++++----------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index c1a7cc0069..1ea0aa2a24 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -106,25 +106,24 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; /** * @brief Declare a buffer holding a packet and its size. */ -#define DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ - uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ - const size_t sizeName = sizeof( pTemplate ); \ +#define DECLARE_PACKET( pTemplate, bufferName, sizeName ) \ + uint8_t bufferName[ sizeof( pTemplate ) ] = { 0 }; \ + const size_t sizeName = sizeof( pTemplate ); \ ( void ) memcpy( bufferName, pTemplate, sizeName ); /** * @brief Initializer for operations in the tests. */ -#define INITIALIZE_OPERATION( name ) \ - { \ - .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ - .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ - .u.operation = \ - { \ - .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ - .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ - .notify= { .callback = { 0 } }, .status = IOT_MQTT_STATUS_PENDING, \ - .retry = { 0 } \ - } \ +#define INITIALIZE_OPERATION( name ) \ + { \ + .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ + .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ + .u.operation = \ + { \ + .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ + .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ + .notify = { .callback = { 0 } }, .status = IOT_MQTT_STATUS_PENDING, .retry = { 0 } \ + } \ } /*-----------------------------------------------------------*/ @@ -1337,10 +1336,15 @@ TEST( MQTT_Unit_Receive, SubackValid ) pSubscriptions[ 0 ].pTopicFilter, pSubscriptions[ 0 ].topicFilterLength, ¤tSubscription ) ); - currentSubscription.qos = pSubscriptions[ 0 ].qos; - TEST_ASSERT_EQUAL_MEMORY( &pSubscriptions[ 0 ], - ¤tSubscription, - sizeof( IotMqttSubscription_t ) ); + TEST_ASSERT_EQUAL_UINT16( currentSubscription.topicFilterLength, + pSubscriptions[ 0 ].topicFilterLength ); + TEST_ASSERT_EQUAL_STRING_LEN( currentSubscription.pTopicFilter, + pSubscriptions[ 0 ].pTopicFilter, + currentSubscription.topicFilterLength ); + TEST_ASSERT_EQUAL_PTR( currentSubscription.callback.function, + pSubscriptions[ 0 ].callback.function ); + TEST_ASSERT_EQUAL_PTR( currentSubscription.callback.pCallbackContext, + pSubscriptions[ 0 ].callback.pCallbackContext ); } /* Process a valid SUBACK where some subscriptions were rejected. */ From cebf8bc0af997d6ba995d46d575c82b2acb8d98f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 16 May 2019 09:35:43 -0700 Subject: [PATCH 149/844] Delete SubscriptionMultithreaded tests (#431) --- lib/source/common/iot_logging.c | 6 +- lib/source/mqtt/iot_mqtt_subscription.c | 6 +- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 137 ------------------ 3 files changed, 5 insertions(+), 144 deletions(-) diff --git a/lib/source/common/iot_logging.c b/lib/source/common/iot_logging.c index 8ba2dff074..f7d4a3dd85 100644 --- a/lib/source/common/iot_logging.c +++ b/lib/source/common/iot_logging.c @@ -433,10 +433,8 @@ void IotLog_GenericPrintBuffer( const char * const pLibraryName, offset = 0; } - /* Print a single byte into pMessageBuffer. This call to sprintf won't - * cause any buffer overflows because it always prints 4 characters per - * invocation and sufficient memory has been allocated. */ - ( void ) sprintf( pMessageBuffer + offset, "%02x ", pBuffer[ i ] ); + /* Print a single byte into pMessageBuffer. */ + ( void ) snprintf( pMessageBuffer + offset, 4, "%02x ", pBuffer[ i ] ); /* Move the offset where the next character is printed. */ offset += 3; diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 0fa360ce4d..7405fb7434 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -364,9 +364,9 @@ IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection, pNewSubscription->packetInfo.order = i; pNewSubscription->callback = pSubscriptionList[ i ].callback; pNewSubscription->topicFilterLength = pSubscriptionList[ i ].topicFilterLength; - ( void ) strncpy( pNewSubscription->pTopicFilter, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + ( void ) memcpy( pNewSubscription->pTopicFilter, + pSubscriptionList[ i ].pTopicFilter, + ( size_t ) ( pSubscriptionList[ i ].topicFilterLength ) ); IotListDouble_InsertHead( &( pMqttConnection->subscriptionList ), &( pNewSubscription->link ) ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 1cce3eb21a..154d3ccb38 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -76,13 +76,6 @@ #define TEST_TOPIC_FILTER_FORMAT ( "/test%lu" ) /**< @brief Format of each topic filter. */ #define TEST_TOPIC_FILTER_LENGTH ( sizeof( TEST_TOPIC_FILTER_FORMAT ) + 1 ) /**< @brief Maximum length of each topic filter. */ -/* - * Constants relating to the multithreaded subscription test. - */ -#define MT_THREAD_COUNT ( 8 ) /**< @brief Number of threads. */ -#define MT_TOPIC_FILTER_FORMAT ( "/%p-%lu" ) /**< @brief Format of each topic filter. */ -#define MT_TOPIC_FILTER_LENGTH ( 16 ) /**< @brief Maximum length of each topic filter. */ - /** * @brief A non-NULL function pointer to use for subscription callback. This * "function" should cause a crash if actually called. @@ -137,16 +130,6 @@ static bool _connectionCreated = false; */ static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; -/** - * @brief Synchronizes threads in the multithreaded test at start. - */ -static IotSemaphore_t _mtTestStart; - -/** - * @brief Synchronizes threads in the multithreaded test at exit. - */ -static IotSemaphore_t _mtTestExit; - /*-----------------------------------------------------------*/ /** @@ -274,57 +257,6 @@ static void _blockingCallback( void * pArgument, /*-----------------------------------------------------------*/ -/** - * @brief Thread routing of the multithreaded test. - */ -static void _multithreadTestThread( void * pArgument ) -{ - size_t i = 0; - bool * pThreadResult = ( bool * ) pArgument; - char pTopicFilters[ LIST_ITEM_COUNT ][ MT_TOPIC_FILTER_LENGTH ] = { { 0 } }; - IotMqttSubscription_t subscription[ LIST_ITEM_COUNT ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; - - /* Synchronize with the other threads before starting the test. */ - IotSemaphore_Wait( &( _mtTestStart ) ); - - /* Add items to the subscription list. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - subscription[ i ].callback.function = SUBSCRIPTION_CALLBACK_FUNCTION; - subscription[ i ].pTopicFilter = pTopicFilters[ i ]; - subscription[ i ].topicFilterLength = ( uint16_t ) snprintf( pTopicFilters[ i ], - MT_TOPIC_FILTER_LENGTH, - MT_TOPIC_FILTER_FORMAT, - ( void * ) &i, - ( unsigned long ) i ); - - if( _IotMqtt_AddSubscriptions( _pMqttConnection, - 1, - &( subscription[ i ] ), - 1 ) != IOT_MQTT_SUCCESS ) - { - *pThreadResult = false; - - return; - } - } - - /* Remove the previously added items from the list. */ - for( i = 0; i < LIST_ITEM_COUNT; i++ ) - { - _IotMqtt_RemoveSubscriptionByTopicFilter( _pMqttConnection, - &( subscription[ i ] ), - 1 ); - } - - *pThreadResult = true; - - /* Synchronize with the other threads before exiting the test. */ - IotSemaphore_Post( &( _mtTestExit ) ); -} - -/*-----------------------------------------------------------*/ - /** * @brief Test group for MQTT subscription tests. */ @@ -390,7 +322,6 @@ TEST_GROUP_RUNNER( MQTT_Unit_Subscription ) RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionRemoveByTopicFilter ); RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddDuplicate ); RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionAddMallocFail ); - RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionMultithreaded ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublish ); RUN_TEST_CASE( MQTT_Unit_Subscription, ProcessPublishMultiple ); RUN_TEST_CASE( MQTT_Unit_Subscription, SubscriptionReferences ); @@ -739,74 +670,6 @@ TEST( MQTT_Unit_Subscription, SubscriptionAddMallocFail ) /*-----------------------------------------------------------*/ -/** - * @brief Tests adding and removing subscriptions in a multithreaded environment. - */ -TEST( MQTT_Unit_Subscription, SubscriptionMultithreaded ) -{ - int32_t i = 0, threadsCreated = 0, threadsExited = 0; - bool threadResults[ MT_THREAD_COUNT ] = { 0 }; - - /* Create the synchronization semaphores. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestStart ), 0, MT_THREAD_COUNT ) ); - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( _mtTestExit ), 0, MT_THREAD_COUNT ) ); - - /* Spawn threads for the test. */ - for( i = 0; i < MT_THREAD_COUNT; i++ ) - { - if( Iot_CreateDetachedThread( _multithreadTestThread, - &( threadResults[ i ] ), - IOT_THREAD_DEFAULT_PRIORITY, - IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) - { - break; - } - } - - /* Record how many threads were created. */ - threadsCreated = i; - - /* Signal all created threads to start. */ - for( i = 0; i < threadsCreated; i++ ) - { - IotSemaphore_Post( &_mtTestStart ); - } - - /* Wait for all created threads to finish. */ - for( i = 0; i < threadsCreated; i++ ) - { - if( IotSemaphore_TimedWait( &_mtTestExit, - IOT_TEST_MQTT_TIMEOUT_MS ) == false ) - { - break; - } - - threadsExited++; - } - - if( TEST_PROTECT() ) - { - /* Check how many threads ran. */ - TEST_ASSERT_EQUAL_INT( threadsCreated, threadsExited ); - TEST_ASSERT_EQUAL_INT( threadsCreated, MT_THREAD_COUNT ); - - /* Check the results of the test threads. */ - for( i = 0; i < MT_THREAD_COUNT; i++ ) - { - TEST_ASSERT_EQUAL_INT( true, threadResults[ i ] ); - } - - /* The subscription list should be empty. */ - TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); - } - - /* Destroy the synchronization semaphores. */ - IotSemaphore_Destroy( &_mtTestStart ); - IotSemaphore_Destroy( &_mtTestExit ); -} - -/*-----------------------------------------------------------*/ - /** * @brief Tests invoking subscription callbacks with PUBLISH messages. */ From 3d1fd7271689b275610a648acad6c1ca0b72efdd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 16 May 2019 13:46:33 -0700 Subject: [PATCH 150/844] Make argument parsing platform independent (#432) --- demos/README.md | 2 +- demos/app/CMakeLists.txt | 7 +- demos/app/iot_demo.c | 141 +--------- demos/app/iot_demo_arguments.c | 279 +++++++++++++++++++ demos/app/posix/iot_demo_arguments_posix.c | 155 ----------- demos/app/win32/iot_demo_arguments_win32.c | 43 --- demos/include/iot_demo_arguments.h | 4 +- tests/CMakeLists.txt | 2 +- tests/README.md | 4 +- tests/app/iot_tests_win32.c | 94 ------- tests/{app/iot_tests_posix.c => iot_tests.c} | 101 ++++--- 11 files changed, 347 insertions(+), 485 deletions(-) create mode 100644 demos/app/iot_demo_arguments.c delete mode 100644 demos/app/posix/iot_demo_arguments_posix.c delete mode 100644 demos/app/win32/iot_demo_arguments_win32.c delete mode 100644 tests/app/iot_tests_win32.c rename tests/{app/iot_tests_posix.c => iot_tests.c} (64%) diff --git a/demos/README.md b/demos/README.md index c5493baad5..29b8b38a5f 100644 --- a/demos/README.md +++ b/demos/README.md @@ -2,7 +2,7 @@ This directory contains source files for demo executables. Its subdirectories are organized as follows: - `app`
- Source files for demo runner executables (i.e. the `main()` function). The file `app/iot_demo.c` contains the platform-independent demo runner, while platform-dependent functions (for parsing arguments) are found in a subdirectory matching the platform name (`app/posix`, `app/win32`, etc.). + Source files for demo runner executables (i.e. the `main()` function). - `include`
Common headers for the demo executables. These headers handle argument parsing and logging which are common to all demos. - `source`
diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt index 4e27187a5e..1910fa4004 100644 --- a/demos/app/CMakeLists.txt +++ b/demos/app/CMakeLists.txt @@ -1,10 +1,7 @@ -# Source file for parsing arguments. -set( DEMO_ARGUMENTS_SOURCE_FILE ${IOT_PLATFORM_NAME}/iot_demo_arguments_${IOT_PLATFORM_NAME}.c ) - # Common demo source files. set( DEMO_COMMON_SOURCES - ${DEMO_ARGUMENTS_SOURCE_FILE} - iot_demo.c ) + iot_demo.c + iot_demo_arguments.c ) # Common demo header files. set( DEMO_COMMON_HEADERS diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 8e81769469..55e640ba59 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -83,133 +83,6 @@ extern int RunDemo( bool awsIotMqttMode, /*-----------------------------------------------------------*/ -/** - * @brief Set the default values of an #IotDemoArguments_t based on compile-time - * defined constants. - * - * @param[out] pArguments Default values will be placed here. - */ -static void _setDefaultArguments( IotDemoArguments_t * pArguments ) -{ - /* Default to AWS IoT MQTT mode. */ - pArguments->awsIotMqttMode = true; - - /* Set default identifier if defined. The identifier is used as either the - * MQTT client identifier or the Thing Name, which identifies this client to - * the cloud. */ - #ifdef IOT_DEMO_IDENTIFIER - pArguments->pIdentifier = IOT_DEMO_IDENTIFIER; - #endif - - /* Set default secured connection status if defined. */ - #ifdef IOT_DEMO_SECURED_CONNECTION - pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; - #endif - - /* Set default MQTT server if defined. */ - #ifdef IOT_DEMO_SERVER - pArguments->pHostName = IOT_DEMO_SERVER; - #endif - - /* Set default MQTT server port if defined. */ - #ifdef IOT_DEMO_PORT - pArguments->port = IOT_DEMO_PORT; - #endif - - /* Set default root CA path if defined. */ - #ifdef IOT_DEMO_ROOT_CA - pArguments->pRootCaPath = IOT_DEMO_ROOT_CA; - #endif - - /* Set default client certificate path if defined. */ - #ifdef IOT_DEMO_CLIENT_CERT - pArguments->pClientCertPath = IOT_DEMO_CLIENT_CERT; - #endif - - /* Set default client certificate private key path if defined. */ - #ifdef IOT_DEMO_PRIVATE_KEY - pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; - #endif -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Validates the members of an #IotDemoArguments_t. - * - * @param[in] pArguments The #IotDemoArguments_t to validate. - * - * @return `true` if every member of the #IotDemoArguments_t is valid; `false` - * otherwise. - */ -static bool _validateArguments( const IotDemoArguments_t * pArguments ) -{ - /* Declare a status variable for this function. */ - IOT_FUNCTION_ENTRY( bool, true ); - - /* Check that a server was set. */ - if( ( pArguments->pHostName == NULL ) || - ( strlen( pArguments->pHostName ) == 0 ) ) - { - IotLogError( "MQTT server not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a server port was set. */ - if( pArguments->port == 0 ) - { - IotLogError( "MQTT server port not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check credentials for a secured connection. */ - if( pArguments->securedConnection == true ) - { - /* Check that a root CA path was set. */ - if( ( pArguments->pRootCaPath == NULL ) || - ( strlen( pArguments->pRootCaPath ) == 0 ) ) - { - IotLogError( "Root CA path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Client certificate private key not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - else - { - if( pArguments->awsIotMqttMode == true ) - { - IotLogError( "AWS IoT does not support unsecured connections." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - /* No cleanup is required for this function. */ - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - int main( int argc, char ** argv ) { @@ -230,16 +103,10 @@ int main( int argc, IotNetworkCredentials_t credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, * pCredentials = NULL; - /* Load the default demo arguments from the demo config header. */ - _setDefaultArguments( &demoArguments ); - - /* Parse any command line arguments. */ - IotDemo_ParseArguments( argc, - argv, - &demoArguments ); - - /* Validate arguments. */ - if( _validateArguments( &demoArguments ) == false ) + /* Parse and validate any command line arguments. */ + if( IotDemo_ParseArguments( argc, + argv, + &demoArguments ) == false ) { IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c new file mode 100644 index 0000000000..dc24a3d847 --- /dev/null +++ b/demos/app/iot_demo_arguments.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_demo_arguments.c + * @brief Implements a function for retrieving command line arguments. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Common demo includes. */ +#include "iot_demo_arguments.h" +#include "iot_demo_logging.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Set the default values of an #IotDemoArguments_t based on compile-time + * defined constants. + * + * @param[out] pArguments Default values will be placed here. + */ +static void _setDefaultArguments( IotDemoArguments_t * pArguments ) +{ + /* Default to AWS IoT MQTT mode. */ + pArguments->awsIotMqttMode = true; + + /* Set default identifier if defined. The identifier is used as either the + * MQTT client identifier or the Thing Name, which identifies this client to + * the cloud. */ + #ifdef IOT_DEMO_IDENTIFIER + pArguments->pIdentifier = IOT_DEMO_IDENTIFIER; + #endif + + /* Set default secured connection status if defined. */ + #ifdef IOT_DEMO_SECURED_CONNECTION + pArguments->securedConnection = IOT_DEMO_SECURED_CONNECTION; + #endif + + /* Set default MQTT server if defined. */ + #ifdef IOT_DEMO_SERVER + pArguments->pHostName = IOT_DEMO_SERVER; + #endif + + /* Set default MQTT server port if defined. */ + #ifdef IOT_DEMO_PORT + pArguments->port = IOT_DEMO_PORT; + #endif + + /* Set default root CA path if defined. */ + #ifdef IOT_DEMO_ROOT_CA + pArguments->pRootCaPath = IOT_DEMO_ROOT_CA; + #endif + + /* Set default client certificate path if defined. */ + #ifdef IOT_DEMO_CLIENT_CERT + pArguments->pClientCertPath = IOT_DEMO_CLIENT_CERT; + #endif + + /* Set default client certificate private key path if defined. */ + #ifdef IOT_DEMO_PRIVATE_KEY + pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; + #endif +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Validates the members of an #IotDemoArguments_t. + * + * @param[in] pArguments The #IotDemoArguments_t to validate. + * + * @return `true` if every member of the #IotDemoArguments_t is valid; `false` + * otherwise. + */ +static bool _validateArguments( const IotDemoArguments_t * pArguments ) +{ + /* Declare a status variable for this function. */ + IOT_FUNCTION_ENTRY( bool, true ); + + /* Check that a server was set. */ + if( ( pArguments->pHostName == NULL ) || + ( strlen( pArguments->pHostName ) == 0 ) ) + { + IotLogError( "MQTT server not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a server port was set. */ + if( pArguments->port == 0 ) + { + IotLogError( "MQTT server port not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check credentials for a secured connection. */ + if( pArguments->securedConnection == true ) + { + /* Check that a root CA path was set. */ + if( ( pArguments->pRootCaPath == NULL ) || + ( strlen( pArguments->pRootCaPath ) == 0 ) ) + { + IotLogError( "Root CA path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + IotLogError( "Client certificate private key not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + else + { + if( pArguments->awsIotMqttMode == true ) + { + IotLogError( "AWS IoT does not support unsecured connections." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + /* No cleanup is required for this function. */ + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +bool IotDemo_ParseArguments( int argc, + char ** argv, + IotDemoArguments_t * pArguments ) +{ + int i = 1; + const char * pOption = NULL; + unsigned long int port = 0; + size_t optionLength = 0; + + /* Load the default demo arguments from the demo config header. */ + _setDefaultArguments( pArguments ); + + for( i = 1; i < argc; i++ ) + { + /* Get argument string and length. */ + pOption = argv[ i ]; + optionLength = strlen( pOption ); + + /* Valid options have the format "-X", so they must be 2 characters long. */ + if( optionLength != 2 ) + { + IotLogWarn( "Ignoring invalid option %s.", pOption ); + + continue; + } + + /* The first character of a valid option must be '-'. */ + if( pOption[ 0 ] != '-' ) + { + IotLogWarn( "Ignoring invalid option %s.", pOption ); + + continue; + } + + switch( pOption[ 1 ] ) + { + /* Secured connection. */ + case 's': + pArguments->securedConnection = true; + break; + + /* Unsecured connection. */ + case 'u': + pArguments->securedConnection = false; + break; + + /* MQTT server is not AWS. */ + case 'n': + pArguments->awsIotMqttMode = false; + break; + + /* Server. */ + case 'h': + i++; + pArguments->pHostName = argv[ i ]; + break; + + /* Server port. */ + case 'p': + i++; + + /* Convert argument string to unsigned int. */ + port = strtoul( argv[ i ], NULL, 10 ); + + /* Check that port is valid. */ + if( ( port == 0 ) || ( port > UINT16_MAX ) ) + { + IotLogWarn( "Ignoring invalid port %lu.", port ); + } + else + { + pArguments->port = ( uint16_t ) port; + } + + break; + + /* Root CA path. */ + case 'r': + i++; + pArguments->pRootCaPath = argv[ i ]; + break; + + /* Client certificate path. */ + case 'c': + i++; + pArguments->pClientCertPath = argv[ i ]; + break; + + /* Client certificate private key path. */ + case 'k': + i++; + pArguments->pPrivateKeyPath = argv[ i ]; + break; + + /* Client identifier or Thing Name. */ + case 'i': + i++; + pArguments->pIdentifier = argv[ i ]; + break; + + default: + IotLogWarn( "Ignoring unknown option %s.", pOption ); + break; + } + } + + return _validateArguments( pArguments ); +} + +/*-----------------------------------------------------------*/ diff --git a/demos/app/posix/iot_demo_arguments_posix.c b/demos/app/posix/iot_demo_arguments_posix.c deleted file mode 100644 index 12b9f281e0..0000000000 --- a/demos/app/posix/iot_demo_arguments_posix.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_arguments_posix.c - * @brief Implements a function for retrieving command line arguments on POSIX - * systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include -#include - -/* Common demo includes. */ -#include "iot_demo_arguments.h" -#include "iot_demo_logging.h" - -/*-----------------------------------------------------------*/ - -void IotDemo_ParseArguments( int argc, - char ** argv, - IotDemoArguments_t * pArguments ) -{ - int option = 0; - unsigned long int port = 0; - - IotLogInfo( "Parsing command line arguments." ); - - /* Silence any error or warning messages printed by the system. The demos - * will use the logging library instead. */ - opterr = 0; - - /* Retrieve all command line arguments. */ - while( ( option = getopt( argc, argv, ":sunh:p:r:c:k:i:" ) ) != -1 ) - { - switch( option ) - { - /* Secured connection. */ - case ( int ) ( 's' ): - pArguments->securedConnection = true; - - break; - - /* Unsecured connection. */ - case ( int ) ( 'u' ): - pArguments->securedConnection = false; - - break; - - /* MQTT server is not AWS IoT. */ - case ( int ) ( 'n' ): - pArguments->awsIotMqttMode = false; - break; - - /* Server. */ - case ( int ) ( 'h' ): - pArguments->pHostName = optarg; - break; - - /* Server port. */ - case ( int ) ( 'p' ): - /* Convert argument string to unsigned int. */ - port = strtoul( optarg, NULL, 10 ); - - /* Check that port is valid. */ - if( ( port == 0 ) || ( port > UINT16_MAX ) ) - { - IotLogWarn( "Ignoring invalid port '%lu'.", port ); - } - else - { - pArguments->port = ( uint16_t ) port; - } - - break; - - /* Root CA path. */ - case ( int ) ( 'r' ): - pArguments->pRootCaPath = optarg; - break; - - /* Client certificate path. */ - case ( int ) ( 'c' ): - pArguments->pClientCertPath = optarg; - break; - - /* Client certificate private key path. */ - case ( int ) ( 'k' ): - pArguments->pPrivateKeyPath = optarg; - break; - - /* Client identifier or Thing Name. */ - case ( int ) ( 'i' ): - pArguments->pIdentifier = optarg; - break; - - /* Unknown argument. */ - case ( int ) ( '?' ): - IotLogWarn( "Ignoring unknown argument '-%c'.", ( char ) optopt ); - break; - - /* Argument known, but missing value. */ - case ( int ) ( ':' ): - IotLogWarn( "Ignoring invalid argument '-%c'. Option '-%c' requires a value.", - ( char ) optopt, - ( char ) optopt ); - break; - - /* The default case should not be executed. */ - default: - abort(); - } - } - - IotLogInfo( "Command line arguments successfully parsed." ); - - /* AWS IoT only uses secured connections; disable AWS IoT mode if the connection - * is unsecured. */ - if( pArguments->securedConnection == false ) - { - pArguments->awsIotMqttMode = false; - } - - IotLogDebug( "AWS IoT MQTT mode: %s", pArguments->awsIotMqttMode == true ? "true" : "false" ); - IotLogDebug( "Secured connection: %s", pArguments->securedConnection == true ? "true" : "false" ); - IotLogDebug( "Host: %s", pArguments->pHostName ); - IotLogDebug( "Port: %hu", pArguments->port ); - IotLogDebug( "Root CA: %s", pArguments->pRootCaPath ); - IotLogDebug( "Client certificate: %s", pArguments->pClientCertPath ); - IotLogDebug( "Private key: %s", pArguments->pPrivateKeyPath ); -} - -/*-----------------------------------------------------------*/ diff --git a/demos/app/win32/iot_demo_arguments_win32.c b/demos/app/win32/iot_demo_arguments_win32.c deleted file mode 100644 index 8648187089..0000000000 --- a/demos/app/win32/iot_demo_arguments_win32.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_demo_arguments_win32.c - * @brief Implements a function for retrieving command line arguments on Win32 - * systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Common demo includes. */ -#include "iot_demo_arguments.h" -#include "iot_demo_logging.h" - -/*-----------------------------------------------------------*/ - -void IotDemo_ParseArguments( int argc, - char ** argv, - IotDemoArguments_t * pArguments ) -{ -} - -/*-----------------------------------------------------------*/ diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index 0a738ff117..8f1921f392 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -87,8 +87,10 @@ typedef struct IotDemoArguments * @param[in] argc The argument count originally passed to main(). * @param[in] argv The argument vector originally passed to main(). * @param[out] pArguments Set to the arguments parsed from the command line. + * + * @return `true` if all arguments are valid; `false` otherwise. */ -void IotDemo_ParseArguments( int argc, +bool IotDemo_ParseArguments( int argc, char ** argv, IotDemoArguments_t * pArguments ); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9a26452926..ca865ad6f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,7 @@ # Test runner executable and config header. set( IOT_TEST_APP_FILES ${PROJECT_SOURCE_DIR}/tests/iot_config.h - ${PROJECT_SOURCE_DIR}/tests/app/iot_tests_${IOT_PLATFORM_NAME}.c ) + ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) # Common tests. add_subdirectory( common ) diff --git a/tests/README.md b/tests/README.md index 49d16ecdd2..a777da7161 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,11 +1,11 @@ # Test programs This directory contains source files for test executables. Its subdirectories are organized as follows: -- `app`
- Source files for test runner executables (i.e. the `main()` function). These are platform-dependent files named after the platform they support (e.g. `iot_tests_posix.c`, `iot_tests_win32.c`, etc.). - `common`, `defender`, `...`
Platform-independent test sources for individual libraries. +The test executable source (i.e. the `main()` function) is `iot_tests.c`. + The configuration file for the tests, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. For information on building and running the tests, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html). diff --git a/tests/app/iot_tests_win32.c b/tests/app/iot_tests_win32.c deleted file mode 100644 index 51b626faba..0000000000 --- a/tests/app/iot_tests_win32.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_tests_win32.c - * @brief Common test runner on Win32 systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Error handling include. */ -#include "private/iot_error.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Test framework includes. */ -#include "unity_fixture.h" - -/* This file calls a generic placeholder test runner function. The build system selects - * the actual test runner function by defining it. */ -#ifndef RunTests - #error "Test runner function undefined." -#endif - -/*-----------------------------------------------------------*/ - -/* Declaration of generic test runner function. */ -extern void RunTests( bool disableNetworkTests, - bool disableLongTests ); - -/*-----------------------------------------------------------*/ - -int main( int argc, - char ** argv ) -{ - IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); - bool mallocMutexCreated = false; - bool disableNetworkTests = false, disableLongTests = false; - - /* Create the mutex that guards the Unity malloc overrides. */ - mallocMutexCreated = IotMutex_Create( &UnityMallocMutex, false ); - - if( mallocMutexCreated == false ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Call the test runner function. */ - RunTests( disableNetworkTests, disableLongTests ); - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( mallocMutexCreated == true ) - { - IotMutex_Destroy( &UnityMallocMutex ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ diff --git a/tests/app/iot_tests_posix.c b/tests/iot_tests.c similarity index 64% rename from tests/app/iot_tests_posix.c rename to tests/iot_tests.c index 263a00d50a..224a7692f2 100644 --- a/tests/app/iot_tests_posix.c +++ b/tests/iot_tests.c @@ -20,22 +20,15 @@ */ /** - * @file iot_tests_posix.c - * @brief Common test runner on POSIX systems. + * @file iot_tests.c + * @brief Common test runner. */ /* The config header is always included first. */ #include "iot_config.h" /* Standard includes. */ -#include -#include -#include #include -#include - -/* POSIX includes. */ -#include /* Error handling include. */ #include "private/iot_error.h" @@ -61,20 +54,59 @@ extern void RunTests( bool disableNetworkTests, /*-----------------------------------------------------------*/ /** - * @brief Signal handler. Terminates the tests if called. + * @brief Parses command line arguments. + * + * @param[in] argc Number of arguments passed to main(). + * @param[in] argv Arguments vector passed to main(). + * @param[out] pDisableNetworkTests Set to `true` if `-n` is given, `false` otherwise. + * @param[out] pDisableLongTests Set to `true` if `-l` is not given, `true` otherwise. */ -static void _signalHandler( int signum ) +static void _parseArguments( int argc, + char ** argv, + bool * pDisableNetworkTests, + bool * pDisableLongTests ) { - /* Immediately terminate the tests if this signal handler is called. */ - if( signum == SIGSEGV ) - { - printf( "\nSegmentation fault.\n" ); - _Exit( EXIT_FAILURE ); - } - else if( signum == SIGABRT ) + int i = 1; + const char * pOption = NULL; + size_t optionLength = 0; + + /* Set default values. */ + *pDisableNetworkTests = false; + *pDisableLongTests = true; + + for( i = 1; i < argc; i++ ) { - printf( "\nAssertion failed.\n" ); - _Exit( EXIT_FAILURE ); + /* Get argument string and length. */ + pOption = argv[ i ]; + optionLength = strlen( pOption ); + + /* Valid options have the format "-X", so they must be 2 characters long. */ + if( optionLength != 2 ) + { + continue; + } + + /* The first character of a valid option must be '-'. */ + if( pOption[ 0 ] != '-' ) + { + continue; + } + + switch( pOption[ 1 ] ) + { + /* Disable tests requiring network if -n is given. */ + case 'n': + *pDisableNetworkTests = true; + break; + + /* Enable long tests if -l is given. */ + case 'l': + *pDisableLongTests = false; + break; + + default: + break; + } } } @@ -86,21 +118,6 @@ int main( int argc, IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); bool mallocMutexCreated = false; bool disableNetworkTests = false, disableLongTests = false; - struct sigaction signalAction; - - /* Set a signal handler for segmentation faults and assertion failures. */ - ( void ) memset( &signalAction, 0x00, sizeof( struct sigaction ) ); - signalAction.sa_handler = _signalHandler; - - if( sigaction( SIGSEGV, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } - - if( sigaction( SIGABRT, &signalAction, NULL ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } /* Create the mutex that guards the Unity malloc overrides. */ mallocMutexCreated = IotMutex_Create( &UnityMallocMutex, false ); @@ -110,6 +127,9 @@ int main( int argc, IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } + /* Parse command-line arguments. */ + _parseArguments( argc, argv, &disableNetworkTests, &disableLongTests ); + /* Unity setup. */ UnityFixture.Verbose = 1; UnityFixture.RepeatCount = 1; @@ -117,17 +137,6 @@ int main( int argc, UnityFixture.GroupFilter = NULL; UNITY_BEGIN(); - /* Check if any tests should be disabled. */ - if( getopt( argc, argv, "n" ) == ( ( int ) 'n' ) ) - { - disableNetworkTests = true; - } - - if( getopt( argc, argv, "l" ) == -1 ) - { - disableLongTests = true; - } - /* Call the test runner function. */ RunTests( disableNetworkTests, disableLongTests ); From 75b6a1aa4782cfe86566effffd1823734a1e02ad Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 17 May 2019 15:57:04 -0700 Subject: [PATCH 151/844] Remove direct calls to platform in third_party (#433) --- lib/source/common/iot_init.c | 44 ++++++--- platform/include/atomic/iot_atomic_generic.h | 39 ++++---- platform/source/network/iot_network_mbedtls.c | 89 ++++++++++++++++++- tests/iot_tests.c | 31 ++++++- third_party/mbedtls/CMakeLists.txt | 6 +- third_party/mbedtls/iot_mbedtls_threading.c | 80 ----------------- third_party/mbedtls/threading_alt.h | 23 +---- third_party/tinycbor/CMakeLists.txt | 3 + third_party/unity/CMakeLists.txt | 3 + .../unity/unity/fixture/unity_fixture.c | 18 ++-- .../fixture/unity_fixture_malloc_overrides.h | 8 +- .../unity/unity/fixture/unity_memory_mt.c | 48 +++++++--- 12 files changed, 226 insertions(+), 166 deletions(-) delete mode 100644 third_party/mbedtls/iot_mbedtls_threading.c diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index ca5a834b13..ddaf9a181c 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -54,13 +54,30 @@ /*-----------------------------------------------------------*/ -#if IOT_ATOMIC_GENERIC == 1 +/* A mutex for critical sections is needed for the generic atomic implementation + * or test framework. */ +#if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) /** - * @brief Provides atomic operations with thread safety. + * @brief Provides critical sections. */ - IotMutex_t IotAtomicMutex; -#endif + static IotMutex_t _atomicMutex; + +/*-----------------------------------------------------------*/ + + void Iot_EnterCritical( void ) + { + IotMutex_Lock( &_atomicMutex ); + } + +/*-----------------------------------------------------------*/ + + void Iot_ExitCritical( void ) + { + IotMutex_Unlock( &_atomicMutex ); + } + +#endif /* if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) */ /*-----------------------------------------------------------*/ @@ -70,17 +87,22 @@ bool IotSdk_Init( void ) IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; - /* Initialize the mutex for generic atomic operations if needed. */ - #if IOT_ATOMIC_GENERIC == 1 - bool genericAtomicInitialized = IotMutex_Create( &IotAtomicMutex, false ); + /* Initialize the mutex for atomic operations if needed. */ + #if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) + bool atomicInitialized = IotMutex_Create( &_atomicMutex, false ); - if( genericAtomicInitialized == false ) + if( atomicInitialized == false ) { IotLogError( "Failed to initialize atomic operations." ); IOT_SET_AND_GOTO_CLEANUP( false ); } #endif + /* Provide the functions for test framework critical sections. */ + #if IOT_BUILD_TESTS == 1 + unity_provide_critical_section( Iot_EnterCritical, Iot_ExitCritical ); + #endif + /* Initialize static memory if dynamic memory allocation is disabled. */ #if IOT_STATIC_MEMORY_ONLY == 1 bool staticMemoryInitialized = IotStaticMemory_Init(); @@ -106,9 +128,9 @@ bool IotSdk_Init( void ) if( status == false ) { #if IOT_ATOMIC_GENERIC == 1 - if( genericAtomicInitialized == true ) + if( atomicInitialized == true ) { - IotMutex_Destroy( &IotAtomicMutex ); + IotMutex_Destroy( &_atomicMutex ); } #endif #if IOT_STATIC_MEMORY_ONLY == 1 @@ -143,7 +165,7 @@ void IotSdk_Cleanup( void ) /* Clean up the mutex for generic atomic operations if needed. */ #if IOT_ATOMIC_GENERIC == 1 - IotMutex_Destroy( &IotAtomicMutex ); + IotMutex_Destroy( &_atomicMutex ); #endif } diff --git a/platform/include/atomic/iot_atomic_generic.h b/platform/include/atomic/iot_atomic_generic.h index b832925b8e..45cad30ac0 100644 --- a/platform/include/atomic/iot_atomic_generic.h +++ b/platform/include/atomic/iot_atomic_generic.h @@ -77,7 +77,8 @@ * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. */ -extern IotMutex_t IotAtomicMutex; +extern void Iot_EnterCritical( void ); +extern void Iot_ExitCritical( void ); /** @endcond */ /*---------------- Swap and compare-and-swap ------------------*/ @@ -101,7 +102,7 @@ static inline uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestinati { uint32_t swapped = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); if( *pDestination == comparand ) { @@ -109,7 +110,7 @@ static inline uint32_t Atomic_CompareAndSwap_u32( uint32_t volatile * pDestinati swapped = 1; } - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return swapped; } @@ -131,10 +132,10 @@ static inline void * Atomic_Swap_Pointer( void * volatile * pDestination, { void * pOldValue = NULL; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); pOldValue = *pDestination; *pDestination = pNewValue; - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return pOldValue; } @@ -160,7 +161,7 @@ static inline uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pDestina { uint32_t swapped = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); if( *pDestination == pComparand ) { @@ -168,7 +169,7 @@ static inline uint32_t Atomic_CompareAndSwap_Pointer( void * volatile * pDestina swapped = 1; } - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return swapped; } @@ -190,10 +191,10 @@ static inline uint32_t Atomic_Add_u32( uint32_t volatile * pAugend, { uint32_t oldValue = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); oldValue = *pAugend; *pAugend = oldValue + addend; - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return oldValue; } @@ -215,10 +216,10 @@ static inline uint32_t Atomic_Subtract_u32( uint32_t volatile * pMinuend, { uint32_t oldValue = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); oldValue = *pMinuend; *pMinuend = oldValue - subtrahend; - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return oldValue; } @@ -272,10 +273,10 @@ static inline uint32_t Atomic_OR_u32( uint32_t volatile * pOperand, { uint32_t oldValue = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); oldValue = *pOperand; *pOperand = ( oldValue | mask ); - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return oldValue; } @@ -297,10 +298,10 @@ static inline uint32_t Atomic_XOR_u32( uint32_t volatile * pOperand, { uint32_t oldValue = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); oldValue = *pOperand; *pOperand = ( oldValue ^ mask ); - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return oldValue; } @@ -322,10 +323,10 @@ static inline uint32_t Atomic_AND_u32( uint32_t volatile * pOperand, { uint32_t oldValue = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); oldValue = *pOperand; *pOperand = ( oldValue & mask ); - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return oldValue; } @@ -347,10 +348,10 @@ static inline uint32_t Atomic_NAND_u32( uint32_t volatile * pOperand, { uint32_t oldValue = 0; - IotMutex_Lock( &IotAtomicMutex ); + Iot_EnterCritical(); oldValue = *pOperand; *pOperand = ~( oldValue & mask ); - IotMutex_Unlock( &IotAtomicMutex ); + Iot_ExitCritical(); return oldValue; } diff --git a/platform/source/network/iot_network_mbedtls.c b/platform/source/network/iot_network_mbedtls.c index 775826a2c5..b7ea3f2faa 100644 --- a/platform/source/network/iot_network_mbedtls.c +++ b/platform/source/network/iot_network_mbedtls.c @@ -212,6 +212,87 @@ const IotNetworkInterface_t IotNetworkMbedtls = /*-----------------------------------------------------------*/ +/** + * @brief Initializes a new mutex. Used by mbed TLS to provide thread-safety. + * + * Sets the valid member of `mbedtls_threading_mutex_t`. + * + * @param[in] pMutex The mutex to initialize. + */ +static void _mbedtlsMutexInit( mbedtls_threading_mutex_t * pMutex ) +{ + pMutex->valid = IotMutex_Create( &( pMutex->mutex ), false ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Frees a mutex. Used by mbed TLS to provide thread-safety. + * + * @param[in] pMutex The mutex to destroy. + */ +static void _mbedtlsMutexFree( mbedtls_threading_mutex_t * pMutex ) +{ + if( pMutex->valid == true ) + { + IotMutex_Destroy( &( pMutex->mutex ) ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Locks a mutex. Used by mbed TLS to provide thread-safety. + * + * @param[in] pMutex The mutex to lock. + * + * @return `0` on success; one of `MBEDTLS_ERR_THREADING_BAD_INPUT_DATA` + * or `MBEDTLS_ERR_THREADING_MUTEX_ERROR` on error. + */ +static int _mbedtlsMutexLock( mbedtls_threading_mutex_t * pMutex ) +{ + int status = 0; + + if( pMutex->valid == false ) + { + status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + else + { + IotMutex_Lock( &( pMutex->mutex ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Unlocks a mutex. Used by mbed TLS to provide thread-safety. + * + * @param[in] pMutex The mutex to unlock. + * + * @return `0` on success; one of `MBEDTLS_ERR_THREADING_BAD_INPUT_DATA` + * or `MBEDTLS_ERR_THREADING_MUTEX_ERROR` on error. + */ +static int _mbedtlsMutexUnlock( mbedtls_threading_mutex_t * pMutex ) +{ + int status = 0; + + if( pMutex->valid == false ) + { + status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + else + { + IotMutex_Unlock( &( pMutex->mutex ) ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief Initialize the mbed TLS structures in a network connection. * @@ -636,10 +717,10 @@ IotNetworkError_t IotNetworkMbedtls_Init( void ) _receiveThreadCount = 0; /* Set the mutex functions for mbed TLS thread safety. */ - mbedtls_threading_set_alt( mbedtlsMutex_Init, - mbedtlsMutex_Free, - mbedtlsMutex_Lock, - mbedtlsMutex_Unlock ); + mbedtls_threading_set_alt( _mbedtlsMutexInit, + _mbedtlsMutexFree, + _mbedtlsMutexLock, + _mbedtlsMutexUnlock ); /* Initialize contexts for random number generation. */ mbedtls_entropy_init( &_entropyContext ); diff --git a/tests/iot_tests.c b/tests/iot_tests.c index 224a7692f2..e925253519 100644 --- a/tests/iot_tests.c +++ b/tests/iot_tests.c @@ -53,6 +53,33 @@ extern void RunTests( bool disableNetworkTests, /*-----------------------------------------------------------*/ +/** + * @brief Used to provide unity_malloc with critical sections. + */ +static IotMutex_t _unityMallocMutex; + +/*-----------------------------------------------------------*/ + +/** + * @brief Enter a critical section for unity_malloc. + */ +static void _unityEnterCritical( void ) +{ + IotMutex_Lock( &_unityMallocMutex ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Exit a critical section for unity_malloc. + */ +static void _unityExitCritical( void ) +{ + IotMutex_Unlock( &_unityMallocMutex ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Parses command line arguments. * @@ -120,7 +147,7 @@ int main( int argc, bool disableNetworkTests = false, disableLongTests = false; /* Create the mutex that guards the Unity malloc overrides. */ - mallocMutexCreated = IotMutex_Create( &UnityMallocMutex, false ); + mallocMutexCreated = IotMutex_Create( &_unityMallocMutex, false ); if( mallocMutexCreated == false ) { @@ -150,7 +177,7 @@ int main( int argc, if( mallocMutexCreated == true ) { - IotMutex_Destroy( &UnityMallocMutex ); + IotMutex_Destroy( &_unityMallocMutex ); } IOT_FUNCTION_CLEANUP_END(); diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index f1fe098bad..ba89bfaaad 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -64,8 +64,7 @@ file( GLOB MBEDTLS_HEADERS "mbedtls/include/mbedtls/*.h" ) # mbed TLS library target. add_library( mbedtls ${MBEDTLS_SOURCES} ${MBEDTLS_HEADERS} - iot_config_mbedtls.h threading_alt.h - iot_mbedtls_threading.c ) + iot_config_mbedtls.h threading_alt.h ) # mbed TLS config header and include directories. target_include_directories( mbedtls SYSTEM @@ -87,6 +86,9 @@ target_compile_options( mbedtls $<$: /W0 /D_CRT_SECURE_NO_WARNINGS> ) +# Automatically export all symbols when building DLLs on Windows. +set_property( TARGET mbedtls PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) + # Organization of mbed TLS in folders. set_property( TARGET mbedtls PROPERTY FOLDER "third_party" ) source_group( include\\mbedtls FILES ${MBEDTLS_HEADERS} ) diff --git a/third_party/mbedtls/iot_mbedtls_threading.c b/third_party/mbedtls/iot_mbedtls_threading.c deleted file mode 100644 index 4d36b108a8..0000000000 --- a/third_party/mbedtls/iot_mbedtls_threading.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* This file implements mutex functions for mbed TLS using platform mutexes. */ - -/* mbed TLS threading include. This includes the "threading_alt.h" header. */ -#include - -/*-----------------------------------------------------------*/ - -void mbedtlsMutex_Init( mbedtls_threading_mutex_t * pMutex ) -{ - pMutex->valid = IotMutex_Create( &( pMutex->mutex ), false ); -} - -/*-----------------------------------------------------------*/ - -void mbedtlsMutex_Free( mbedtls_threading_mutex_t * pMutex ) -{ - if( pMutex->valid == true ) - { - IotMutex_Destroy( &( pMutex->mutex ) ); - } -} - -/*-----------------------------------------------------------*/ - -int mbedtlsMutex_Lock( mbedtls_threading_mutex_t * pMutex ) -{ - int status = 0; - - if( pMutex->valid == false ) - { - status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; - } - else - { - IotMutex_Lock( &( pMutex->mutex ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -int mbedtlsMutex_Unlock( mbedtls_threading_mutex_t * pMutex ) -{ - int status = 0; - - if( pMutex->valid == false ) - { - status = MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; - } - else - { - IotMutex_Unlock( &( pMutex->mutex ) ); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/third_party/mbedtls/threading_alt.h b/third_party/mbedtls/threading_alt.h index c21e639979..84f8abab79 100644 --- a/third_party/mbedtls/threading_alt.h +++ b/third_party/mbedtls/threading_alt.h @@ -24,14 +24,11 @@ #ifndef THREADING_ALT_H_ #define THREADING_ALT_H_ -/* The config header is always included first. */ -#include "iot_config.h" - /* Standard includes. */ #include -/* Platform threads include. */ -#include "platform/iot_threads.h" +/* System types include. */ +#include IOT_SYSTEM_TYPES_FILE /* Represents a mutex used by mbed TLS. */ typedef struct mbedtls_threading_mutex @@ -39,21 +36,7 @@ typedef struct mbedtls_threading_mutex /* Whether this mutex is valid. */ bool valid; /* The wrapped platform mutex. */ - IotMutex_t mutex; + _IotSystemMutex_t mutex; } mbedtls_threading_mutex_t; -/* Initializes a new mutex. Sets the valid member of mbedtls_threading_mutex_t. */ -void mbedtlsMutex_Init( mbedtls_threading_mutex_t * pMutex ); - -/* Frees a mutex. */ -void mbedtlsMutex_Free( mbedtls_threading_mutex_t * pMutex ); - -/* Locks a mutex. Returns 0 on success; one of MBEDTLS_ERR_THREADING_BAD_INPUT_DATA - * or MBEDTLS_ERR_THREADING_MUTEX_ERROR on error. */ -int mbedtlsMutex_Lock( mbedtls_threading_mutex_t * pMutex ); - -/* Unlocks a mutex. Returns 0 on success; one of MBEDTLS_ERR_THREADING_BAD_INPUT_DATA - * or MBEDTLS_ERR_THREADING_MUTEX_ERROR on error. */ -int mbedtlsMutex_Unlock( mbedtls_threading_mutex_t * pMutex ); - #endif /* ifndef THREADING_ALT_H_ */ diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 371cef3cf3..bb7385cf31 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -51,6 +51,9 @@ target_compile_options( tinycbor $<$: /W0 /D_CRT_SECURE_NO_WARNINGS> ) +# Automatically export all symbols when building DLLs on Windows. +set_property( TARGET tinycbor PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) + # Organization of TinyCBOR in folders. set_property( TARGET tinycbor PROPERTY FOLDER "third_party" ) source_group( include FILES ${TINYCBOR_HEADERS} ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index 248e8a75eb..d542e06d3c 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -29,6 +29,9 @@ target_compile_options( unity $<$: /W0 /D_CRT_SECURE_NO_WARNINGS> ) +# Automatically export all symbols when building DLLs on Windows. +set_property( TARGET unity PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) + # Organization of Unity in folders. set_property( TARGET unity PROPERTY FOLDER "third_party" ) source_group( include FILES ${UNITY_HEADERS} ) diff --git a/third_party/unity/unity/fixture/unity_fixture.c b/third_party/unity/unity/fixture/unity_fixture.c index 65e267596c..6894a573ab 100644 --- a/third_party/unity/unity/fixture/unity_fixture.c +++ b/third_party/unity/unity/fixture/unity_fixture.c @@ -9,10 +9,6 @@ #include "unity_internals.h" #include -/* For thread safety. */ -#include "iot_config.h" -#include "platform/iot_threads.h" - struct UNITY_FIXTURE_T UnityFixture; /* If you decide to use the function pointer approach. @@ -141,30 +137,30 @@ static int malloc_fail_countdown = MALLOC_DONT_FAIL; void UnityMalloc_StartTest(void) { - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); malloc_count = 0; malloc_fail_countdown = MALLOC_DONT_FAIL; - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); } void UnityMalloc_EndTest(void) { - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); malloc_fail_countdown = MALLOC_DONT_FAIL; if (malloc_count != 0) { - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); UNITY_TEST_FAIL(Unity.CurrentTestLineNumber, "This test leaks!"); } - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); } void UnityMalloc_MakeMallocFailAfterCount(int countdown) { - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); malloc_fail_countdown = countdown; - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); } /* These definitions are always included from unity_fixture_malloc_overrides.h */ diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h index 4bd8f0a600..e5d10cb991 100644 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h @@ -10,9 +10,6 @@ #include -#include IOT_SYSTEM_TYPES_FILE -typedef _IotSystemMutex_t IotMutex_t; - #ifdef UNITY_EXCLUDE_STDLIB_MALLOC /* Define this macro to remove the use of stdlib.h, malloc, and free. * Many embedded systems do not have a heap or malloc/free by default. @@ -42,8 +39,9 @@ typedef _IotSystemMutex_t IotMutex_t; #define realloc unity_realloc_mt #define free unity_free_mt -/* Thread-safety wrappers for the unity memory functions. */ -extern IotMutex_t UnityMallocMutex; +void unity_provide_critical_section(void(*start)(void), void(*end)(void)); +void unity_enter_critical_section(void); +void unity_exit_critical_section(void); void* unity_malloc_mt(size_t size); void* unity_calloc_mt(size_t num, size_t size); diff --git a/third_party/unity/unity/fixture/unity_memory_mt.c b/third_party/unity/unity/fixture/unity_memory_mt.c index 012904d059..7905a3dfbc 100644 --- a/third_party/unity/unity/fixture/unity_memory_mt.c +++ b/third_party/unity/unity/fixture/unity_memory_mt.c @@ -1,24 +1,48 @@ /* Wrappers that make the unity memory functions thread-safe. */ -#include "iot_config.h" -#include "platform/iot_threads.h" - -IotMutex_t UnityMallocMutex; +#include +/* unity memory functions. */ extern void* unity_malloc(size_t size); extern void* unity_calloc(size_t num, size_t size); extern void* unity_realloc(void* oldMem, size_t size); extern void unity_free_mt(void* mem); +/* Function pointers to begin/end critical section functions. */ +static void(*unity_critical_section_start)(void) = NULL; +static void(*unity_critical_section_end)(void) = NULL; + +void unity_provide_critical_section(void(*start)(void), void(*end)(void)) +{ + unity_critical_section_start = start; + unity_critical_section_end = end; +} + +void unity_enter_critical_section(void) +{ + if(unity_critical_section_start != NULL) + { + unity_critical_section_start(); + } +} + +void unity_exit_critical_section(void) +{ + if(unity_critical_section_end != NULL) + { + unity_critical_section_end(); + } +} + void* unity_malloc_mt(size_t size) { void* mem = NULL; - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); mem = unity_malloc(size); - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); return mem; } @@ -27,11 +51,11 @@ void* unity_calloc_mt(size_t num, size_t size) { void* mem = NULL; - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); mem = unity_calloc(num, size); - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); return mem; } @@ -40,20 +64,20 @@ void* unity_realloc_mt(void * oldMem, size_t size) { void* mem = NULL; - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); mem = unity_realloc(oldMem, size); - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); return mem; } void unity_free_mt(void * mem) { - IotMutex_Lock(&UnityMallocMutex); + unity_enter_critical_section(); unity_free(mem); - IotMutex_Unlock(&UnityMallocMutex); + unity_exit_critical_section(); } From 7c8455676fb6cb5f4a370567feb55f5f51a4cf73 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 17 May 2019 16:47:13 -0700 Subject: [PATCH 152/844] Resolve dependencies in dynamic libraries (#436) --- CMakeLists.txt | 42 ++++++++++++++++++++++++ lib/source/common/CMakeLists.txt | 41 ++++++++++-------------- lib/source/common/iot_init.c | 2 ++ lib/source/mqtt/CMakeLists.txt | 2 +- platform/source/posix/CMakeLists.txt | 37 +++++++++++++-------- platform/source/win32/CMakeLists.txt | 48 ++++++++-------------------- tests/common/CMakeLists.txt | 2 +- tests/serializer/CMakeLists.txt | 2 +- 8 files changed, 101 insertions(+), 75 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eab935aac6..ee5c5a1821 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,12 +82,54 @@ if( ${IOT_ATOMIC_USE_PORT} ) add_definitions( -DIOT_ATOMIC_USE_PORT=1 ) endif() +# Platform headers. +set( PLATFORM_INTERFACE_HEADERS + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_clock.h + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_metrics.h + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_network.h + ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_threads.h ) + +set( PLATFORM_TYPES_HEADERS + ${CMAKE_SOURCE_DIR}/lib/include/types/iot_platform_types.h + ${CMAKE_SOURCE_DIR}/platform/include/${IOT_PLATFORM_NAME}/iot_platform_types_${IOT_PLATFORM_NAME}.h ) + +set( PLATFORM_COMMON_HEADERS + ${CMAKE_SOURCE_DIR}/platform/include/iot_atomic.h + ${CMAKE_SOURCE_DIR}/platform/include/iot_network_metrics.h ) + # Platform libraries. add_subdirectory( platform/source/${IOT_PLATFORM_NAME} ) # Common libraries (linear containers, logging, etc.) add_subdirectory( lib/source/common ) +# The sources of the common libraries and platform layer will be built into a +# single library that is used as a dependency by other libraries. +add_library( iotbase + ${PLATFORM_INTERFACE_HEADERS} + ${PLATFORM_TYPES_HEADERS} + ${PLATFORM_COMMON_HEADERS} + ${COMMON_PUBLIC_HEADERS} + ${COMMON_PRIVATE_HEADERS} + ${COMMON_TYPES_HEADERS} ) + +# Link required libraries. +target_link_libraries( iotbase PRIVATE iotplatform iotcommon ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotbase PRIVATE unity ) +endif() + +# Organization of iotbase in folders. +source_group( platform\\interface FILES ${PLATFORM_INTERFACE_HEADERS} ) +source_group( platform\\include FILES ${PLATFORM_COMMON_HEADERS} ) +source_group( platform\\include\\types FILES ${PLATFORM_TYPES_HEADERS} ) +source_group( platform\\source FILES ${PLATFORM_SOURCES} ) +source_group( common\\include FILES ${COMMON_PUBLIC_HEADERS} ) +source_group( common\\include\\private FILES ${COMMON_PRIVATE_HEADERS} ) +source_group( common\\include\\types FILES ${COMMON_TYPES_HEADERS} ) +source_group( common\\source FILES ${COMMON_SOURCES} ) + # MQTT library. add_subdirectory( lib/source/mqtt ) diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 8fec8cac8e..03129416bd 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -1,10 +1,10 @@ # Common libraries source files. set( COMMON_SOURCES - iot_init.c - iot_logging.c - iot_static_memory_common.c - iot_taskpool.c - iot_taskpool_static_memory.c ) + ${PROJECT_SOURCE_DIR}/lib/source/common/iot_init.c + ${PROJECT_SOURCE_DIR}/lib/source/common/iot_logging.c + ${PROJECT_SOURCE_DIR}/lib/source/common/iot_static_memory_common.c + ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool.c + ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool_static_memory.c ) # Lists of common header files. These are only used for directory organization # (not for build). @@ -12,30 +12,23 @@ set( COMMON_PUBLIC_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/iot_init.h ${PROJECT_SOURCE_DIR}/lib/include/iot_linear_containers.h ${PROJECT_SOURCE_DIR}/lib/include/iot_logging_setup.h - ${PROJECT_SOURCE_DIR}/lib/include/iot_taskpool.h ) + ${PROJECT_SOURCE_DIR}/lib/include/iot_taskpool.h + PARENT_SCOPE ) set( COMMON_PRIVATE_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/private/iot_error.h ${PROJECT_SOURCE_DIR}/lib/include/private/iot_logging.h ${PROJECT_SOURCE_DIR}/lib/include/private/iot_static_memory.h - ${PROJECT_SOURCE_DIR}/lib/include/private/iot_taskpool_internal.h ) + ${PROJECT_SOURCE_DIR}/lib/include/private/iot_taskpool_internal.h + PARENT_SCOPE ) set( COMMON_TYPES_HEADERS - ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h ) + ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h + PARENT_SCOPE ) -# Common library target. -add_library( iotcommon - ${COMMON_SOURCES} ${COMMON_PUBLIC_HEADERS} ${COMMON_PRIVATE_HEADERS} ${COMMON_TYPES_HEADERS} ) +# Add common as an interface library. It will be built into iotbase. +add_library( iotcommon INTERFACE ) -# Link required libraries. -target_link_libraries( iotcommon PUBLIC iotplatform ) +target_sources( iotcommon INTERFACE + ${COMMON_SOURCES} ) -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotcommon PRIVATE unity ) -endif() - -# Organization of common libraries in folders. -set_property( TARGET iotcommon PROPERTY FOLDER "lib" ) -source_group( include FILES ${COMMON_PUBLIC_HEADERS} ) -source_group( include\\private FILES ${COMMON_PRIVATE_HEADERS} ) -source_group( include\\types FILES ${COMMON_TYPES_HEADERS} ) -source_group( source FILES ${COMMON_SOURCES} ) +# Set common sources in the parent scope for directory organization. +set( COMMON_SOURCES ${COMMON_SOURCES} PARENT_SCOPE ) diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index ddaf9a181c..817a080808 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -57,6 +57,8 @@ /* A mutex for critical sections is needed for the generic atomic implementation * or test framework. */ #if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) + /* Platform threads include. */ + #include "platform/iot_threads.h" /** * @brief Provides critical sections. diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt index c8b949227d..3499583c17 100644 --- a/lib/source/mqtt/CMakeLists.txt +++ b/lib/source/mqtt/CMakeLists.txt @@ -16,7 +16,7 @@ add_library( iotmqtt ${PROJECT_SOURCE_DIR}/lib/include/private/iot_mqtt_internal.h ) # Link required libraries. -target_link_libraries( iotmqtt PUBLIC iotcommon ) +target_link_libraries( iotmqtt PUBLIC iotbase ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/platform/source/posix/CMakeLists.txt b/platform/source/posix/CMakeLists.txt index 5e84b711a0..d497439ba9 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/source/posix/CMakeLists.txt @@ -61,27 +61,38 @@ if( ${IOT_NETWORK_USE_OPENSSL} ) endif() # Choose OpenSSL network source file. + set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/posix/iot_network_openssl.h ) set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_network_openssl.c ) # Link OpenSSL. set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) endif() else() + set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h ) set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_mbedtls.c ) set( TLS_LIBRARY_LINKER_FLAG mbedtls ) endif() +# Add the network header for this platform. +set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} + ${NETWORK_HEADER} + PARENT_SCOPE ) + # Platform libraries source files. -add_library( iotplatform - iot_clock_posix.c - iot_threads_posix.c - ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c - ${NETWORK_SOURCE_FILE} ) - -# Link required libraries. -target_link_libraries( iotplatform - PRIVATE Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotplatform PRIVATE unity ) -endif() +set( PLATFORM_SOURCES + ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_clock_posix.c + ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_threads_posix.c + ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c + ${NETWORK_SOURCE_FILE} ) + +# Add platform as an interface library. It will be built into iotbase. +add_library( iotplatform INTERFACE ) + +target_sources( iotplatform INTERFACE + ${PLATFORM_SOURCES} ) + +# Set the dependencies of this platform layer. +target_link_libraries( iotplatform INTERFACE Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) + +# Set platform sources in the parent scope for directory organization. +set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) diff --git a/platform/source/win32/CMakeLists.txt b/platform/source/win32/CMakeLists.txt index 9dec783fa9..7533cc3292 100644 --- a/platform/source/win32/CMakeLists.txt +++ b/platform/source/win32/CMakeLists.txt @@ -1,45 +1,23 @@ -# Platform interface headers. -set( PLATFORM_INTERFACE_HEADERS - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_clock.h - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_metrics.h - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_network.h - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_threads.h ) - -# Platform types headers. -set( PLATFORM_TYPES_HEADERS - ${CMAKE_SOURCE_DIR}/lib/include/types/iot_platform_types.h - ${CMAKE_SOURCE_DIR}/platform/include/win32/iot_platform_types_win32.h ) - -# Platform common headers. -set( PLATFORM_COMMON_HEADERS - ${CMAKE_SOURCE_DIR}/platform/include/iot_atomic.h - ${CMAKE_SOURCE_DIR}/platform/include/atomic/iot_atomic_generic.h +# Add the network header for this platform. +set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h - ${CMAKE_SOURCE_DIR}/platform/include/iot_network_metrics.h ) + PARENT_SCOPE ) # Platform library source files. set( PLATFORM_SOURCES - iot_clock_win32.c - iot_threads_win32.c + ${CMAKE_SOURCE_DIR}/platform/source/win32/iot_clock_win32.c + ${CMAKE_SOURCE_DIR}/platform/source/win32/iot_threads_win32.c ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_mbedtls.c ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c ) -# Platform library target. -add_library( iotplatform - ${PLATFORM_SOURCES} - ${PLATFORM_INTERFACE_HEADERS} - ${PLATFORM_TYPES_HEADERS} - ${PLATFORM_COMMON_HEADERS} ) +# Add platform as an interface library. It will be built into iotbase. +add_library( iotplatform INTERFACE ) -# Link required libraries. -target_link_libraries( iotplatform PRIVATE mbedtls ) +target_sources( iotplatform INTERFACE + ${PLATFORM_SOURCES} ) -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotplatform PRIVATE unity ) -endif() +# This platform layer uses mbed TLS. +target_link_libraries( iotplatform INTERFACE mbedtls ) -# Organization of platform in folders. -source_group( interface FILES ${PLATFORM_INTERFACE_HEADERS} ) -source_group( include\\types FILES ${PLATFORM_TYPES_HEADERS} ) -source_group( include FILES ${PLATFORM_COMMON_HEADERS} ) -source_group( source FILES ${PLATFORM_SOURCES} ) +# Set platform sources in the parent scope for directory organization. +set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index 1ca8cada9b..f164e6da18 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -15,7 +15,7 @@ target_compile_definitions( iot_tests_common PRIVATE -DRunTests=RunCommonTests ) # Common tests library dependencies. -target_link_libraries( iot_tests_common PRIVATE iotcommon unity ) +target_link_libraries( iot_tests_common PRIVATE iotbase unity ) # Organization of common tests in folders. set_property( TARGET iot_tests_common PROPERTY FOLDER "tests" ) diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt index 8f21ebe1b4..797394ec12 100644 --- a/tests/serializer/CMakeLists.txt +++ b/tests/serializer/CMakeLists.txt @@ -13,7 +13,7 @@ target_compile_definitions( iot_tests_serializer PRIVATE -DRunTests=RunSerializerTests ) # Serializer tests library dependencies. -target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotcommon unity tinycbor ) +target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotbase unity tinycbor ) # Organization of Serializer tests in folders. set_property( TARGET iot_tests_serializer PROPERTY FOLDER "tests" ) From bc1938a10352c13444fecdb1f4076fb1af83b288 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 20 May 2019 13:13:21 -0700 Subject: [PATCH 153/844] Configure test projects to build equivalent demos (#437) --- demos/app/CMakeLists.txt | 7 + lib/include/private/iot_static_memory.h | 58 +------ lib/source/common/iot_init.c | 38 +---- lib/source/common/iot_static_memory_common.c | 58 ++----- .../common/iot_taskpool_static_memory.c | 7 +- lib/source/mqtt/iot_mqtt_static_memory.c | 7 +- .../serializer/iot_serializer_static_memory.c | 9 +- .../shadow/aws_iot_shadow_static_memory.c | 4 +- .../shadow/aws_iot_shadow_subscription.c | 2 +- scripts/ci_test_mqtt.sh | 23 +-- scripts/ci_test_shadow.sh | 4 +- tests/iot_config.h | 28 ++++ tests/iot_tests.c | 156 ++++++++++-------- 13 files changed, 163 insertions(+), 238 deletions(-) diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt index 1910fa4004..cd57f5942e 100644 --- a/demos/app/CMakeLists.txt +++ b/demos/app/CMakeLists.txt @@ -3,6 +3,13 @@ set( DEMO_COMMON_SOURCES iot_demo.c iot_demo_arguments.c ) +# When testing the demos, add the test sources and redefine the demo main function. +if( ${IOT_BUILD_TESTS} ) + list( APPEND DEMO_COMMON_SOURCES ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) + set_property( SOURCE iot_demo.c PROPERTY COMPILE_DEFINITIONS main=DemoMain ) + set_property( SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c PROPERTY COMPILE_DEFINITIONS IOT_TEST_DEMO=1 ) +endif() + # Common demo header files. set( DEMO_COMMON_HEADERS ${PROJECT_SOURCE_DIR}/demos/include/iot_demo_arguments.h diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index e4b1ccaeec..51014289d8 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -34,14 +34,11 @@ #define IOT_STATIC_MEMORY_H_ /* Standard includes. */ -#include #include #include /** * @functionspage{static_memory,static memory component} - * - @functionname{static_memory_function_init} - * - @functionname{static_memory_function_cleanup} * - @functionname{static_memory_function_findfree} * - @functionname{static_memory_function_returninuse} * - @functionname{static_memory_function_messagebuffersize} @@ -49,53 +46,6 @@ * - @functionname{static_memory_function_freemessagebuffer} */ -/*----------------------- Initialization and cleanup ------------------------*/ - -/** - * @functionpage{IotStaticMemory_Init,static_memory,init} - * @functionpage{IotStaticMemory_Cleanup,static_memory,cleanup} - */ - -/** - * @brief One-time initialization function for static memory. - * - * This function performs internal setup of static memory. It must be called - * once (and only once) before calling any other static memory function. - * Calling this function more than once without first calling - * @ref static_memory_function_cleanup may result in a crash. - * - * @return `true` if initialization succeeded; `false` otherwise. - * - * @attention This function is called by `IotSdk_Init` and does not need to be - * called by itself. - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see static_memory_function_cleanup - */ -/* @[declare_static_memory_init] */ -bool IotStaticMemory_Init( void ); -/* @[declare_static_memory_init] */ - -/** - * @brief One-time deinitialization function for static memory. - * - * This function frees resources taken in @ref static_memory_function_init. - * It should be called after to clean up static memory. After this function - * returns, @ref static_memory_function_init must be called again before - * calling any other static memory function. - * - * @attention This function is called by `IotSdk_Cleanup` and does not need - * to be called by itself. - * - * @warning No thread-safety guarantees are provided for this function. - * - * @see static_memory_function_init - */ -/* @[declare_static_memory_cleanup] */ -void IotStaticMemory_Cleanup( void ); -/* @[declare_static_memory_cleanup] */ - /*------------------------- Buffer allocation and free ----------------------*/ /** @@ -120,7 +70,7 @@ void IotStaticMemory_Cleanup( void ); * // objects, the other provides flags to determine which objects are in-use. * #define NUMBER_OF_OBJECTS ... * #define OBJECT_SIZE ... - * static bool _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; + * static uint32_t _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; * static uint8_t _pObjects[ NUMBER_OF_OBJECTS ][ OBJECT_SIZE ] = { { 0 } }; // Placeholder for objects. * * // The function to statically allocate objects. Must have the same signature @@ -148,7 +98,7 @@ void IotStaticMemory_Cleanup( void ); * @endcode */ /* @[declare_static_memory_findfree] */ -int32_t IotStaticMemory_FindFree( bool * pInUse, +int32_t IotStaticMemory_FindFree( uint32_t * pInUse, size_t limit ); /* @[declare_static_memory_findfree] */ @@ -169,7 +119,7 @@ int32_t IotStaticMemory_FindFree( bool * pInUse, * // objects, the other provides flags to determine which objects are in-use. * #define NUMBER_OF_OBJECTS ... * #define OBJECT_SIZE ... - * static bool _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; + * static uint32_t _pInUseObjects[ NUMBER_OF_OBJECTS ] = { 0 }; * static uint8_t _pObjects[ NUMBER_OF_OBJECTS ][ OBJECT_SIZE ] = { { 0 } }; // Placeholder for objects. * * // The function to free statically-allocated objects. Must have the same signature @@ -187,7 +137,7 @@ int32_t IotStaticMemory_FindFree( bool * pInUse, /* @[declare_static_memory_returninuse] */ void IotStaticMemory_ReturnInUse( void * ptr, void * pPool, - bool * pInUse, + uint32_t * pInUse, size_t limit, size_t elementSize ); /* @[declare_static_memory_returninuse] */ diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index 817a080808..a82ac21299 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -54,9 +54,8 @@ /*-----------------------------------------------------------*/ -/* A mutex for critical sections is needed for the generic atomic implementation - * or test framework. */ -#if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) +/* A mutex for critical sections is needed for the generic atomic implementation. */ +#if ( IOT_ATOMIC_GENERIC == 1 ) /* Platform threads include. */ #include "platform/iot_threads.h" @@ -79,7 +78,7 @@ IotMutex_Unlock( &_atomicMutex ); } -#endif /* if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) */ +#endif /* if ( IOT_ATOMIC_GENERIC == 1 ) */ /*-----------------------------------------------------------*/ @@ -90,7 +89,7 @@ bool IotSdk_Init( void ) IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_LARGE; /* Initialize the mutex for atomic operations if needed. */ - #if ( IOT_ATOMIC_GENERIC == 1 ) || ( IOT_BUILD_TESTS == 1 ) + #if ( IOT_ATOMIC_GENERIC == 1 ) bool atomicInitialized = IotMutex_Create( &_atomicMutex, false ); if( atomicInitialized == false ) @@ -100,22 +99,6 @@ bool IotSdk_Init( void ) } #endif - /* Provide the functions for test framework critical sections. */ - #if IOT_BUILD_TESTS == 1 - unity_provide_critical_section( Iot_EnterCritical, Iot_ExitCritical ); - #endif - - /* Initialize static memory if dynamic memory allocation is disabled. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - bool staticMemoryInitialized = IotStaticMemory_Init(); - - if( staticMemoryInitialized == false ) - { - IotLogError( "Failed to initialize static memory." ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - #endif - /* Create system task pool. */ taskPoolStatus = IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ); @@ -135,12 +118,6 @@ bool IotSdk_Init( void ) IotMutex_Destroy( &_atomicMutex ); } #endif - #if IOT_STATIC_MEMORY_ONLY == 1 - if( staticMemoryInitialized == true ) - { - IotStaticMemory_Cleanup(); - } - #endif } else { @@ -160,13 +137,8 @@ void IotSdk_Cleanup( void ) * cleaned up. */ IotLogInfo( "SDK cleanup done." ); - /* Cleanup static memory if dynamic memory allocation is disabled. */ - #if IOT_STATIC_MEMORY_ONLY == 1 - IotStaticMemory_Cleanup(); - #endif - /* Clean up the mutex for generic atomic operations if needed. */ - #if IOT_ATOMIC_GENERIC == 1 + #if ( IOT_ATOMIC_GENERIC == 1 ) IotMutex_Destroy( &_atomicMutex ); #endif } diff --git a/lib/source/common/iot_static_memory_common.c b/lib/source/common/iot_static_memory_common.c index e1ceb489b7..15773c0b12 100644 --- a/lib/source/common/iot_static_memory_common.c +++ b/lib/source/common/iot_static_memory_common.c @@ -42,6 +42,9 @@ /* Static memory include. */ #include "private/iot_static_memory.h" +/* Atomic include. */ +#include "iot_atomic.h" + /*-----------------------------------------------------------*/ /** @@ -68,42 +71,29 @@ /*-----------------------------------------------------------*/ -/** - * @brief Guards access to critical sections. - */ -static IotMutex_t _mutex; - /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseMessageBuffers[ IOT_MESSAGE_BUFFERS ] = { 0 }; /**< @brief Message buffer in-use flags. */ +static uint32_t _pInUseMessageBuffers[ IOT_MESSAGE_BUFFERS ] = { 0U }; /**< @brief Message buffer in-use flags. */ static char _pMessageBuffers[ IOT_MESSAGE_BUFFERS ][ IOT_MESSAGE_BUFFER_SIZE ] = { { 0 } }; /**< @brief Message buffers. */ /*-----------------------------------------------------------*/ -int32_t IotStaticMemory_FindFree( bool * pInUse, +int32_t IotStaticMemory_FindFree( uint32_t * pInUse, size_t limit ) { size_t i = 0; int32_t freeIndex = -1; - /* Perform the search for a free buffer in a critical section. */ - IotMutex_Lock( &( _mutex ) ); - for( i = 0; i < limit; i++ ) { - if( pInUse[ i ] == false ) + if( Atomic_CompareAndSwap_u32( &( pInUse[ i ] ), 1U, 0U ) == 1U ) { - /* If a free buffer is found, mark it "in-use" and return its index. */ - pInUse[ i ] = true; freeIndex = ( int32_t ) i; break; } } - /* Exit the critical section. */ - IotMutex_Unlock( &( _mutex ) ); - return freeIndex; } @@ -111,50 +101,30 @@ int32_t IotStaticMemory_FindFree( bool * pInUse, void IotStaticMemory_ReturnInUse( void * ptr, void * pPool, - bool * pInUse, + uint32_t * pInUse, size_t limit, size_t elementSize ) { size_t i = 0; - uint8_t * element = NULL; + uint8_t * pElement = NULL; /* Clear ptr. */ ( void ) memset( ptr, 0x00, elementSize ); - /* Perform a search for ptr to make sure it's part of pPool. This search - * is done in a critical section. */ - IotMutex_Lock( &( _mutex ) ); - for( i = 0; i < limit; i++ ) { /* Calculate address of the i-th element in pPool. */ - element = ( ( uint8_t * ) pPool ) + elementSize * i; + pElement = ( ( uint8_t * ) pPool ) + elementSize * i; /* Check for a match. */ - if( ( ( void * ) element == ptr ) && - ( pInUse[ i ] == true ) ) + if( pElement == ptr ) { - pInUse[ i ] = false; - break; + if( Atomic_CompareAndSwap_u32( &( pInUse[ i ] ), 0, 1 ) == 1 ) + { + break; + } } } - - /* Exit the critical section. */ - IotMutex_Unlock( &( _mutex ) ); -} - -/*-----------------------------------------------------------*/ - -bool IotStaticMemory_Init( void ) -{ - return IotMutex_Create( &( _mutex ), false ); -} - -/*-----------------------------------------------------------*/ - -void IotStaticMemory_Cleanup( void ) -{ - IotMutex_Destroy( &( _mutex ) ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/common/iot_taskpool_static_memory.c b/lib/source/common/iot_taskpool_static_memory.c index 7d0a4a5a7c..7b30b8d1bd 100644 --- a/lib/source/common/iot_taskpool_static_memory.c +++ b/lib/source/common/iot_taskpool_static_memory.c @@ -31,7 +31,6 @@ #if IOT_STATIC_MEMORY_ONLY == 1 /* Standard includes. */ -#include #include #include @@ -53,13 +52,13 @@ /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseTaskPools[ IOT_TASKPOOLS ] = { 0 }; /**< @brief Task pools in-use flags. */ +static uint32_t _pInUseTaskPools[ IOT_TASKPOOLS ] = { 0U }; /**< @brief Task pools in-use flags. */ static _taskPool_t _pTaskPools[ IOT_TASKPOOLS ] = { { .dispatchQueue = IOT_DEQUEUE_INITIALIZER } }; /**< @brief Task pools. */ -static bool _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool jobs in-use flags. */ +static uint32_t _pInUseTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0U }; /**< @brief Task pool jobs in-use flags. */ static _taskPoolJob_t _pTaskPoolJobs[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = IOT_LINK_INITIALIZER } }; /**< @brief Task pool jobs. */ -static bool _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0 }; /**< @brief Task pool timer event in-use flags. */ +static uint32_t _pInUseTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { 0U }; /**< @brief Task pool timer event in-use flags. */ static _taskPoolTimerEvent_t _pTaskPoolTimerEvents[ IOT_TASKPOOL_JOBS_RECYCLE_LIMIT ] = { { .link = { 0 } } }; /**< @brief Task pool timer events. */ /*-----------------------------------------------------------*/ diff --git a/lib/source/mqtt/iot_mqtt_static_memory.c b/lib/source/mqtt/iot_mqtt_static_memory.c index 81afd5a171..8b6e56acec 100644 --- a/lib/source/mqtt/iot_mqtt_static_memory.c +++ b/lib/source/mqtt/iot_mqtt_static_memory.c @@ -31,7 +31,6 @@ #if IOT_STATIC_MEMORY_ONLY == 1 /* Standard includes. */ -#include #include #include @@ -85,13 +84,13 @@ /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ] = { 0 }; /**< @brief MQTT connection in-use flags. */ +static uint32_t _pInUseMqttConnections[ IOT_MQTT_CONNECTIONS ] = { 0U }; /**< @brief MQTT connection in-use flags. */ static _mqttConnection_t _pMqttConnections[ IOT_MQTT_CONNECTIONS ] = { { 0 } }; /**< @brief MQTT connections. */ -static bool _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief MQTT operation in-use flags. */ +static uint32_t _pInUseMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief MQTT operation in-use flags. */ static _mqttOperation_t _pMqttOperations[ IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief MQTT operations. */ -static bool _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0 }; /**< @brief MQTT subscription in-use flags. */ +static uint32_t _pInUseMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ] = { 0U }; /**< @brief MQTT subscription in-use flags. */ static char _pMqttSubscriptions[ IOT_MQTT_SUBSCRIPTIONS ][ MQTT_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief MQTT subscriptions. */ /*-----------------------------------------------------------*/ diff --git a/lib/source/serializer/iot_serializer_static_memory.c b/lib/source/serializer/iot_serializer_static_memory.c index 2be5d2d524..15240507b7 100644 --- a/lib/source/serializer/iot_serializer_static_memory.c +++ b/lib/source/serializer/iot_serializer_static_memory.c @@ -26,7 +26,6 @@ #if IOT_STATIC_MEMORY_ONLY == 1 /* Standard includes. */ -#include #include #include @@ -86,16 +85,16 @@ typedef struct _cborValueWrapper /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0 }; +static uint32_t _inUseCborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { 0U }; static CborEncoder _cborEncoders[ IOT_SERIALIZER_CBOR_ENCODERS ] = { { .data = { 0 } } }; -static bool _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0 }; +static uint32_t _inUseCborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { 0U }; static CborParser _cborParsers[ IOT_SERIALIZER_CBOR_PARSERS ] = { { 0 } }; -static bool _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0 }; +static uint32_t _inUseCborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { 0U }; static _cborValueWrapper_t _cborValues[ IOT_SERIALIZER_CBOR_VALUES ] = { { .isOutermost = false } }; -static bool _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0 }; +static uint32_t _inUseDecoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { 0U }; static IotSerializerDecoderObject_t _decoderObjects[ IOT_SERIALIZER_DECODER_OBJECTS ] = { { 0 } }; /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_static_memory.c b/lib/source/shadow/aws_iot_shadow_static_memory.c index 3d60514e98..9eb64d53a0 100644 --- a/lib/source/shadow/aws_iot_shadow_static_memory.c +++ b/lib/source/shadow/aws_iot_shadow_static_memory.c @@ -79,10 +79,10 @@ /* * Static memory buffers and flags, allocated and zeroed at compile-time. */ -static bool _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0 }; /**< @brief Shadow operation in-use flags. */ +static uint32_t _pInUseShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief Shadow operation in-use flags. */ static _shadowOperation_t _pShadowOperations[ AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Shadow operations. */ -static bool _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0 }; /**< @brief Shadow subscription in-use flags. */ +static uint32_t _pInUseShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ] = { 0U }; /**< @brief Shadow subscription in-use flags. */ static char _pShadowSubscriptions[ AWS_IOT_SHADOW_SUBSCRIPTIONS ][ SHADOW_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Shadow subscriptions. */ /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index a56a7b1674..da81f970fd 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -219,7 +219,7 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, /* Set the Thing Name length and copy the Thing Name into the new subscription. */ pSubscription->thingNameLength = thingNameLength; - ( void ) strncpy( pSubscription->pThingName, pThingName, thingNameLength ); + ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); /* Add the new subscription to the subscription list. */ IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ), diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index 7844dd33cf..ec6284b9f9 100644 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -5,26 +5,14 @@ # Exit on any nonzero return code. set -e -# Function for running the existing test and demo executables. -run_tests_and_demos() { - # Run MQTT tests. - ./bin/iot_tests_mqtt - - # Run MQTT demo with appropriate arguments. - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - # Run against AWS IoT. - ./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" - else - # Run against Mosquitto. - ./bin/iot_demo_mqtt -h test.mosquitto.org -p 1883 -n - fi -} +CMAKE_FLAGS="-DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" " # For pull request builds, run against test.mosquitto.org. Otherwise, run against AWS IoT. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" + CMAKE_FLAGS+="$AWS_IOT_CREDENTIAL_DEFINES $COMPILER_OPTIONS" else - CMAKE_FLAGS="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" + CMAKE_FLAGS+="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" + DEMO_OPTIONS="-n" fi # Build executables. @@ -32,7 +20,8 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL= make -j2 iot_tests_mqtt iot_demo_mqtt # Run tests and demos. -run_tests_and_demos +./bin/iot_tests_mqtt +./bin/iot_demo_mqtt $DEMO_OPTIONS # Rebuild in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index 07ad924f39..749d985bfa 100644 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -11,7 +11,7 @@ run_tests_and_demos() { # run only the unit tests (credentials are not available for pull request builds). if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./bin/aws_iot_tests_shadow - ./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" + ./bin/aws_iot_demo_shadow else # Run only Shadow unit tests. ./bin/aws_iot_tests_shadow -n @@ -19,7 +19,7 @@ run_tests_and_demos() { } # CMake compiler flags for building Shadow. -CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_DEMO=IOT_LOG_INFO -DAWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS=1 $COMPILER_OPTIONS" +CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS=1 $COMPILER_OPTIONS" # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" diff --git a/tests/iot_config.h b/tests/iot_config.h index abaa4a20e9..fdd876b33b 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -65,6 +65,34 @@ #define AWS_IOT_TEST_SHADOW_THING_NAME "" #endif +/* Log level for testing the demos. */ +#define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO + +/* Set the equivalent demo defines. */ +#ifdef IOT_TEST_SECURED_CONNECTION + #define IOT_DEMO_SECURED_CONNECTION IOT_TEST_SECURED_CONNECTION +#endif +#ifdef IOT_TEST_SERVER + #define IOT_DEMO_SERVER IOT_TEST_SERVER +#endif +#ifdef IOT_TEST_PORT + #define IOT_DEMO_PORT IOT_TEST_PORT +#endif +#ifdef IOT_TEST_ROOT_CA + #define IOT_DEMO_ROOT_CA IOT_TEST_ROOT_CA +#endif +#ifdef IOT_TEST_CLIENT_CERT + #define IOT_DEMO_CLIENT_CERT IOT_TEST_CLIENT_CERT +#endif +#ifdef IOT_TEST_PRIVATE_KEY + #define IOT_DEMO_PRIVATE_KEY IOT_TEST_PRIVATE_KEY +#endif +#if defined( IOT_TEST_MQTT_CLIENT_IDENTIFIER ) + #define IOT_DEMO_IDENTIFIER IOT_TEST_MQTT_CLIENT_IDENTIFIER +#elif defined( AWS_IOT_TEST_SHADOW_THING_NAME ) + #define IOT_DEMO_IDENTIFIER AWS_IOT_TEST_SHADOW_THING_NAME +#endif + /* Linear containers library configuration. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) diff --git a/tests/iot_tests.c b/tests/iot_tests.c index e925253519..bfd2ae6cc6 100644 --- a/tests/iot_tests.c +++ b/tests/iot_tests.c @@ -39,17 +39,19 @@ /* Test framework includes. */ #include "unity_fixture.h" -/* This file calls a generic placeholder test runner function. The build system selects - * the actual test runner function by defining it. */ -#ifndef RunTests - #error "Test runner function undefined." -#endif - /*-----------------------------------------------------------*/ -/* Declaration of generic test runner function. */ -extern void RunTests( bool disableNetworkTests, - bool disableLongTests ); +/* This file calls a generic placeholder test runner function. The build system selects + * the actual function by defining it. When testing demos applications, the demo main + * function is called. */ +#if IOT_TEST_DEMO == 1 + extern int DemoMain( int argc, + char ** argv ); + +#else + extern void RunTests( bool disableNetworkTests, + bool disableLongTests ); +#endif /*-----------------------------------------------------------*/ @@ -63,7 +65,7 @@ static IotMutex_t _unityMallocMutex; /** * @brief Enter a critical section for unity_malloc. */ -static void _unityEnterCritical( void ) +static void IotTest_EnterCritical( void ) { IotMutex_Lock( &_unityMallocMutex ); } @@ -73,7 +75,7 @@ static void _unityEnterCritical( void ) /** * @brief Exit a critical section for unity_malloc. */ -static void _unityExitCritical( void ) +static void IotTest_ExitCritical( void ) { IotMutex_Unlock( &_unityMallocMutex ); } @@ -88,54 +90,56 @@ static void _unityExitCritical( void ) * @param[out] pDisableNetworkTests Set to `true` if `-n` is given, `false` otherwise. * @param[out] pDisableLongTests Set to `true` if `-l` is not given, `true` otherwise. */ -static void _parseArguments( int argc, - char ** argv, - bool * pDisableNetworkTests, - bool * pDisableLongTests ) -{ - int i = 1; - const char * pOption = NULL; - size_t optionLength = 0; - - /* Set default values. */ - *pDisableNetworkTests = false; - *pDisableLongTests = true; - - for( i = 1; i < argc; i++ ) +#if IOT_TEST_DEMO != 1 + static void _parseArguments( int argc, + char ** argv, + bool * pDisableNetworkTests, + bool * pDisableLongTests ) { - /* Get argument string and length. */ - pOption = argv[ i ]; - optionLength = strlen( pOption ); - - /* Valid options have the format "-X", so they must be 2 characters long. */ - if( optionLength != 2 ) - { - continue; - } + int i = 1; + const char * pOption = NULL; + size_t optionLength = 0; - /* The first character of a valid option must be '-'. */ - if( pOption[ 0 ] != '-' ) - { - continue; - } + /* Set default values. */ + *pDisableNetworkTests = false; + *pDisableLongTests = true; - switch( pOption[ 1 ] ) + for( i = 1; i < argc; i++ ) { - /* Disable tests requiring network if -n is given. */ - case 'n': - *pDisableNetworkTests = true; - break; - - /* Enable long tests if -l is given. */ - case 'l': - *pDisableLongTests = false; - break; - - default: - break; + /* Get argument string and length. */ + pOption = argv[ i ]; + optionLength = strlen( pOption ); + + /* Valid options have the format "-X", so they must be 2 characters long. */ + if( optionLength != 2 ) + { + continue; + } + + /* The first character of a valid option must be '-'. */ + if( pOption[ 0 ] != '-' ) + { + continue; + } + + switch( pOption[ 1 ] ) + { + /* Disable tests requiring network if -n is given. */ + case 'n': + *pDisableNetworkTests = true; + break; + + /* Enable long tests if -l is given. */ + case 'l': + *pDisableLongTests = false; + break; + + default: + break; + } } } -} +#endif /* if IOT_TEST_DEMO == 1 */ /*-----------------------------------------------------------*/ @@ -144,7 +148,6 @@ int main( int argc, { IOT_FUNCTION_ENTRY( int, EXIT_SUCCESS ); bool mallocMutexCreated = false; - bool disableNetworkTests = false, disableLongTests = false; /* Create the mutex that guards the Unity malloc overrides. */ mallocMutexCreated = IotMutex_Create( &_unityMallocMutex, false ); @@ -154,24 +157,33 @@ int main( int argc, IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); } - /* Parse command-line arguments. */ - _parseArguments( argc, argv, &disableNetworkTests, &disableLongTests ); - - /* Unity setup. */ - UnityFixture.Verbose = 1; - UnityFixture.RepeatCount = 1; - UnityFixture.NameFilter = NULL; - UnityFixture.GroupFilter = NULL; - UNITY_BEGIN(); - - /* Call the test runner function. */ - RunTests( disableNetworkTests, disableLongTests ); - - /* Return failure if any tests failed. */ - if( UNITY_END() != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); - } + /* Provide Unity with functions for critical sections. */ + unity_provide_critical_section( IotTest_EnterCritical, IotTest_ExitCritical ); + + /* This file is also used to test the demos. When testing demos, the demo main + * function is called. */ + #if IOT_TEST_DEMO == 1 + status = DemoMain( argc, argv ); + #else + /* Unity setup. */ + UnityFixture.Verbose = 1; + UnityFixture.RepeatCount = 1; + UnityFixture.NameFilter = NULL; + UnityFixture.GroupFilter = NULL; + UNITY_BEGIN(); + /* Parse command-line arguments for the tests. */ + bool disableNetworkTests = false, disableLongTests = false; + _parseArguments( argc, argv, &disableNetworkTests, &disableLongTests ); + + /* Call the test runner function. */ + RunTests( disableNetworkTests, disableLongTests ); + + /* Return failure if any tests failed. */ + if( UNITY_END() != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( EXIT_FAILURE ); + } + #endif /* if IOT_TEST_DEMO == 1 */ IOT_FUNCTION_CLEANUP_BEGIN(); From 84a1c57c886130756687e1e1fcb80922754e6507 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 21 May 2019 10:26:41 -0700 Subject: [PATCH 154/844] Add documentation on automated tests (#438) --- doc/guide/developer/automated_tests.txt | 141 ++++++++++++++++++++++++ doc/guide/developer/developer.txt | 2 + 2 files changed, 143 insertions(+) create mode 100644 doc/guide/developer/automated_tests.txt diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt new file mode 100644 index 0000000000..1c7bde7139 --- /dev/null +++ b/doc/guide/developer/automated_tests.txt @@ -0,0 +1,141 @@ +/** +@page guide_developer_automated_tests Automated Tests +@brief Guide to this SDK's automated testing setup. + +The SDK's GitHub repo relies on the following tools to provide automated testing of commits and pull requests. +- [Travis CI](https://travis-ci.org/) is the primary service used for testing.
+ See: [aws-iot-device-sdk-embedded-C on Travis CI](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) +- [Coveralls](https://coveralls.io/) provides code coverage results.
+ See: [aws-iot-device-sdk-embedded-C on Coveralls](https://coveralls.io/github/aws/aws-iot-device-sdk-embedded-C) + +The following files on the GitHub repo control the automated tests: +- `.travis.yml`
+ Provides the job matrix to run on Travis CI. +- `scripts/` directory
+ Shell scripts (`sh`) that run on Travis CI servers. See @ref guide_developer_automated_tests_scripts for more information. + - The scripts that test the libraries are named after the library they test. For example, `ci_test_mqtt.sh` tests the MQTT library. + - `ci_test_doc.sh` tests the documentation build. Runs for pull requests only. + - `ci_test_coverage.sh` generates and submits coverage results to Coveralls. Runs for commits only. + +@section guide_developer_automated_tests_commit_pr Commit vs. Pull Request +@brief The automated tests distinguish between GitHub commits and pull requests. Different sets of tests run on commits and pull requests. + +Because pull requests come from other repos and contain unknown code, Travis CI will not provide encrypted environment variables for pull requests. Therefore, pull requests are tested against unsecured servers. The following tests are run for pull requests. +- Common (`ci_test_common.sh`): this script tests common components like the task pool, atomics, etc. These tests do not use the network. +- MQTT (`ci_test_mqtt.sh`): the MQTT tests run on an unsecured connection against the broker provided by [test.mosquitto.org](http://test.mosquitto.org/). +- Shadow (`ci_test_shadow.sh`): only the Shadow unit tests are run for a pull request build. These tests do not use the network. +- Documentation (`ci_test_doc.sh`): checks that the documentation builds against the code in the pull request. + +@note Rarely, the broker at test.mosquitto.org goes offline. This will cause all of the [MQTT system tests](@ref iot_tests_mqtt_system.c) to fail. Should this happen, try again in a few hours. + +For commits, the credentials necessary for connecting to AWS IoT are provided by Travis CI. The following tests are run for commits. +- Common (`ci_test_common.sh`): runs the same tests as the common pull request tests. +- MQTT (`ci_test_mqtt.sh`): runs the same MQTT tests as the MQTT pull request tests, but against the AWS IoT MQTT broker instead of test.mosquitto.org. +- Shadow (`ci_test_shadow.sh`): runs both the Shadow unit tests and the Shadow system tests. Requires connection to AWS IoT. +- Coverage (`ci_test_coverage.sh`): Gathers code coverage data and submits it to Coveralls. + +@note Rarely, the Shadow tests on a GitHub fork will fail with a `429 TOO MANY REQUESTS` error. If this happens, restart the Shadow tests. The main C SDK GitHub repo has an increased limit for Shadow requests, so it should not encounter this error. + +Because different sets of tests are run for commits and pull requests, both commit and pull request checks should be used to validate code changes. The commit checks can be set up to run on a GitHub fork, where they will run whenever a new commit is pushed to the fork. When a pull request is made from the fork to the main repo, the pull request checks will run. + +@section guide_developer_automated_tests_credentials_policy AWS Credentials and Policy +@brief How to set up AWS IoT credentials for commit checks. + +Forking the main GitHub repo copies the Travis CI scripts, but does not copy any credentials, which are provided by Travis CI's [encrypted environment variable](https://docs.travis-ci.com/user/environment-variables/) feature. AWS IoT credentials are required to run the commit checks. No credentials are required to run pull request checks. + +@subsection guide_developer_automated_tests_credentials_policy_formatting Formatting the client certificate and private key +@brief The AWS IoT client certificate and private key must be formatted to be acceptable for use in a CI script. + +The credentials are written from Travis encrypted environment variables using the following command: +``` +echo -e $AWS_IOT_CLIENT_CERT > credentials/clientCert.pem +``` + +Therefore, all given credentials must be formatted for `echo -e`. As an example, consider the following certificate: +``` +-----BEGIN CERTIFICATE----- +MIIB8TCCAZugAwIBAwIBADANBgkqhkiG9w0BAQQFADBfMRMwEQYDVQQDEwpuZXRh +cHAuY29tMQswCQYDVQQGEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNV +BAoTADEJMAcGA1UECxMAMQ8wDQYJKoZIhvcNAQkBFgAwHhcNMTAwNDI2MTk0OTI4 +WhcNMTAwNTI2MTk0OTI4WjBfMRMwEQYDVQQDEwpuZXRhcHAuY29tMQswCQYDVQQG +EwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNVBAoTADEJMAcGA1UECxMA +MQ8wDQYJKoZIhvcNAQkBFgAwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAyXrK2sry +-----END CERTIFICATE----- +``` + +It should be formatted as follows for Travis CI: +``` +-----BEGIN\ CERTIFICATE-----\\nMIIB8TCCAZugAwIBAwIBADANBgkqhkiG9w0BAQQFADBfMRMwEQYDVQQDEwpuZXRh\\ncHAuY29tMQswCQYDVQQGEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNV\\nBAoTADEJMAcGA1UECxMAMQ8wDQYJKoZIhvcNAQkBFgAwHhcNMTAwNDI2MTk0OTI4\\nWhcNMTAwNTI2MTk0OTI4WjBfMRMwEQYDVQQDEwpuZXRhcHAuY29tMQswCQYDVQQG\\nEwJVUzEJMAcGA1UECBMAMQkwBwYDVQQHEwAxCTAHBgNVBAoTADEJMAcGA1UECxMA\\nMQ8wDQYJKoZIhvcNAQkBFgAwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAyXrK2sry\\n-----END\ CERTIFICATE----- +``` + +Note the spaces have an escape character `\` before them, as in `-----BEGIN\ CERTIFICATE-----`. Line breaks have been removed and replaced with `\\n`. + +Similar formatting should be applied to the matching private key. + +@subsection guide_developer_automated_tests_credentials_policy_travis Setting the credentials on Travis CI +@brief The AWS IoT endpoint, client certificate, and client certificate private key are set on Travis CI's "Settings" page. + +To edit the credentials, go to the "Settings" page on Travis CI. This is usually `/settings`; for example, `https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C/settings` for the main repo. Under "Environment Variables", set the following variables: +| Name | Value | Notes | +| --------------------- | ----- | ----- | +| AWS_IOT_CLIENT_CERT | [Formatted client certificate](@ref guide_developer_automated_tests_credentials_policy_formatting) | The AWS IoT client certificate.
This value does not need to be hidden in the build log. | +| AWS_IOT_ENDPOINT | `ABCDEFG1234567.iot..amazonaws.com` | The AWS IoT endpoint.
This value does not need to be hidden in the build log. | +| AWS_IOT_PRIVATE_KEY | [Formatted private key](@ref guide_developer_automated_tests_credentials_policy_formatting) | The private key matching the AWS IoT client certificate. @attention This value should be hidden in the build log! | +| IOT_IDENTIFIER_PREFIX | `CSDKCI` | This string is prepended to all MQTT client identifiers used by the CI.
It may be anything, but must match the client identifier in the AWS IoT policy.
The example policy below uses `CSDKCI`.
This value does not need to be hidden in the build log. | + +@subsection guide_developer_automated_tests_credentials_policy_aws Setting the AWS IoT policy +@brief The certificates used with Travis CI must have an appropriate policy attached. + +AWS recommends using the most restrictive policy possible. The following policy should be used for the CI. +@code{json} +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "iot:Connect", + "Resource": [ + "arn:aws:iot:::client/CSDKCIcoverage", + "arn:aws:iot:::client/CSDKCIcoverageLWT", + "arn:aws:iot:::client/CSDKCImqtt", + "arn:aws:iot:::client/CSDKCImqttLWT", + "arn:aws:iot:::client/CSDKCImqttossl", + "arn:aws:iot:::client/CSDKCImqttosslLWT", + "arn:aws:iot:::client/CSDKCIshadow", + "arn:aws:iot:::client/CSDKCIdefender" + ] + }, + { + "Effect": "Allow", + "Action": [ + "iot:Publish", + "iot:Receive" + ], + "Resource": [ + "arn:aws:iot:::topic/${iot:ClientId}/*", + "arn:aws:iot:::topic/CSDKCIcoverage/LastWillAndTestament", + "arn:aws:iot:::topic/CSDKCImqtt/LastWillAndTestament", + "arn:aws:iot:::topic/CSDKCImqttossl/LastWillAndTestament", + "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/shadow/*" + ] + }, + { + "Effect": "Allow", + "Action": "iot:Subscribe", + "Resource": [ + "arn:aws:iot:::topicfilter/${iot:ClientId}/*", + "arn:aws:iot:::topicfilter/CSDKCIcoverage/LastWillAndTestament", + "arn:aws:iot:::topicfilter/CSDKCImqtt/LastWillAndTestament", + "arn:aws:iot:::topicfilter/CSDKCImqttossl/LastWillAndTestament", + "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/shadow/*" + ] + } + ] +} +@endcode + +This policy only allows the CI to interact with topics that contain a matching client identifier (which is prefixed with the Travis CI variable `IOT_IDENTIFIER_PREFIX`). A few special topics used by the MQTT tests are also present. The policy will need to be updated if any new topics are used by the tests in the future. + +@section guide_developer_automated_tests_scripts Travis CI Scripts +@brief +*/ diff --git a/doc/guide/developer/developer.txt b/doc/guide/developer/developer.txt index 8e9b7873ba..9b45a2c295 100644 --- a/doc/guide/developer/developer.txt +++ b/doc/guide/developer/developer.txt @@ -7,4 +7,6 @@ This guide contains the following pages. All pages assume the reader has interme @copybrief guide_developer_porting - @subpage guide_developer_styleguide
@copybrief guide_developer_styleguide +- @subpage guide_developer_automated_tests
+ @copybrief guide_developer_automated_tests */ From 6978ff1402e35e8f076e3f0e56ac9ab74b8a4434 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 21 May 2019 14:37:47 -0700 Subject: [PATCH 155/844] Fix Windows DLL builds (#439) --- CMakeLists.txt | 20 +++- lib/include/iot_serializer.h | 8 +- .../private/aws_iot_defender_internal.h | 12 +-- lib/source/common/iot_taskpool.c | 2 +- lib/source/defender/aws_iot_defender_api.c | 16 ++- .../defender/aws_iot_defender_collector.c | 97 ++++++++++--------- .../cbor/iot_serializer_tinycbor_decoder.c | 11 ++- .../cbor/iot_serializer_tinycbor_encoder.c | 11 ++- platform/include/iot_network_mbedtls.h | 16 ++- platform/include/iot_network_metrics.h | 10 +- platform/include/posix/iot_network_openssl.h | 16 ++- platform/source/network/iot_network_mbedtls.c | 9 +- platform/source/network/iot_network_metrics.c | 11 ++- platform/source/posix/iot_network_openssl.c | 9 +- .../system/aws_iot_tests_defender_system.c | 74 ++++++-------- .../unit/iot_tests_serializer_cbor.c | 63 ++++++------ third_party/mbedtls/CMakeLists.txt | 3 - third_party/tinycbor/CMakeLists.txt | 3 - third_party/unity/CMakeLists.txt | 3 - 19 files changed, 213 insertions(+), 181 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee5c5a1821..15c033ac6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,9 +17,6 @@ if( ${PROJECT_SOURCE_DIR} STREQUAL ${PROJECT_BINARY_DIR} ) endif() # Configure options to always show in CMake GUI. -option( BUILD_SHARED_LIBS - "Set this to ON to build all libraries as shared libraries. When OFF, libraries build as static libraries." - ON ) option( IOT_ATOMIC_USE_PORT "Set this to ON to use a custom atomic port. When OFF, the build system will choose an atomic port." OFF ) @@ -30,6 +27,20 @@ option( IOT_BUILD_CLONE_SUBMODULES "Set this to ON to automatically clone any required Git submodules. When OFF, submodules must be manually cloned." ON ) +# Unity test framework does not export the correct symbols for DLLs. +# Do not allow allow shared libraries to be built when building tests on Windows. +if( ${IOT_BUILD_TESTS} AND ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + set( ALLOW_SHARED_LIBRARIES OFF ) +else() + set( ALLOW_SHARED_LIBRARIES ON ) +endif() + +include( CMakeDependentOption ) +CMAKE_DEPENDENT_OPTION( BUILD_SHARED_LIBS + "Set this to ON to build all libraries as shared libraries. When OFF, libraries build as static libraries." + ON "${ALLOW_SHARED_LIBRARIES}" + OFF ) + # Check for system support and set platform name. if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) # Provide an option to use the OpenSSL network abstraction on Linux. @@ -44,6 +55,9 @@ if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) set( IOT_PLATFORM_NAME posix ) elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) set( IOT_PLATFORM_NAME win32 ) + + # Export all symbols when building Windows DLLs. + set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) else() message( FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}. Currently supported systems are: Linux, Windows." ) endif() diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 9a0c930af6..7016f59133 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -445,12 +445,12 @@ typedef struct IotSerializerDecodeInterface } IotSerializerDecodeInterface_t; /* Global reference of CBOR/JSON encoder and decoder. */ -extern IotSerializerEncodeInterface_t _IotSerializerCborEncoder; +const IotSerializerEncodeInterface_t * IotSerializer_GetCborEncoder( void ); -extern IotSerializerDecodeInterface_t _IotSerializerCborDecoder; +const IotSerializerDecodeInterface_t * IotSerializer_GetCborDecoder( void ); -extern IotSerializerEncodeInterface_t _IotSerializerJsonEncoder; +const IotSerializerEncodeInterface_t * IotSerializer_GetJsonEncoder( void ); -extern IotSerializerDecodeInterface_t _IotSerializerJsonDecoder; +const IotSerializerDecodeInterface_t * IotSerializer_GetJsonDecoder( void ); #endif /* ifndef IOT_SERIALIZER_H_ */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index 8bbaa1489c..c0559a3bfa 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -232,20 +232,11 @@ * Define encoder/decoder based on configuration AWS_IOT_DEFENDER_FORMAT. */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - #define DEFENDER_FORMAT "cbor" - #define _defenderEncoder _IotSerializerCborEncoder /**< Global defined in iot_serializer.h . */ - #define _defenderDecoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ - #elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - #define DEFENDER_FORMAT "json" - #define _defenderEncoder _IotSerializerJsonEncoder /**< Global defined in iot_serializer.h . */ - #define _defenderDecoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ - #else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ #error "AWS_IOT_DEFENDER_FORMAT must be either AWS_IOT_DEFENDER_FORMAT_CBOR or AWS_IOT_DEFENDER_FORMAT_JSON." - #endif /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ /** @@ -339,4 +330,7 @@ void AwsIotDefenderInternal_MqttDisconnect( void ); extern _defenderMetrics_t _AwsIotDefenderMetrics; +extern const IotSerializerEncodeInterface_t * _pAwsIotDefenderEncoder; +extern const IotSerializerDecodeInterface_t * _pAwsIotDefenderDecoder; + #endif /* ifndef AWS_IOT_DEFENDER_INTERNAL_H_ */ diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 75b6b20d70..33510d89ca 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -72,7 +72,7 @@ * the system libraries as well. The system task pool needs to be initialized before any library is used or * before any code that posts jobs to the task pool runs. */ -_taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER }; +static _taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER }; /* -------------- Convenience functions to create/recycle/destroy jobs -------------- */ diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index cf31c12820..fdfaa9e51d 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -149,6 +149,15 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn if( !_started ) { + /* Get the pointers to the encoder function tables. */ + #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR + _pAwsIotDefenderDecoder = IotSerializer_GetCborDecoder(); + _pAwsIotDefenderEncoder = IotSerializer_GetCborEncoder(); + #else + _pAwsIotDefenderDecoder = IotSerializer_GetJsonDecoder(); + _pAwsIotDefenderEncoder = IotSerializer_GetJsonEncoder(); + #endif + /* copy input start info into global variable _startInfo */ _startInfo = *pStartInfo; @@ -397,13 +406,14 @@ static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, } } } + /* If no MQTT error and report has been created, it indicates everything is good. */ if( ( mqttError == IOT_MQTT_SUCCESS ) && reportCreated ) { IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJobStorage, &_disconnectJob ); /* Silence warnigns when asserts are disabled. */ - ( void ) taskPoolError; + ( void ) taskPoolError; AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, @@ -479,10 +489,10 @@ static void _disconnectRoutine( IotTaskPool_t pTaskPool, AwsIotDefenderInternal_MqttDisconnect(); /* Re-create metrics job. */ IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); - + /* Silence warnigns when asserts are disabled. */ ( void ) taskPoolError; - + AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Re-schedule metrics job with period as deferred interval. */ diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index 5a51b18149..c20821ade7 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -68,6 +68,9 @@ static uint32_t _metricsFlagSnapshot[ DEFENDER_METRICS_GROUP_COUNT ]; /* Report id integer. */ static uint64_t _AwsIotDefenderReportId = 0; +const IotSerializerEncodeInterface_t * _pAwsIotDefenderEncoder = NULL; +const IotSerializerDecodeInterface_t * _pAwsIotDefenderDecoder = NULL; + /*---------------------- Helper Functions -------------------------*/ static void _assertSuccess( IotSerializerError_t error ); @@ -114,7 +117,7 @@ size_t AwsIotDefenderInternal_GetReportBufferSize( void ) { /* Encoder might over-calculate the needed size. Therefor encoded size might be smaller than buffer size: _report.size. */ return _report.pDataBuffer == NULL ? 0 - : _defenderEncoder.getEncodedSize( &_report.object, _report.pDataBuffer ); + : _pAwsIotDefenderEncoder->getEncodedSize( &_report.object, _report.pDataBuffer ); } /*-----------------------------------------------------------*/ @@ -141,10 +144,10 @@ bool AwsIotDefenderInternal_CreateReport( void ) _serialize(); /* Get the calculated required size. */ - dataSize = _defenderEncoder.getExtraBufferSizeNeeded( pEncoderObject ); + dataSize = _pAwsIotDefenderEncoder->getExtraBufferSizeNeeded( pEncoderObject ); /* Clean the encoder object handle. */ - _defenderEncoder.destroy( pEncoderObject ); + _pAwsIotDefenderEncoder->destroy( pEncoderObject ); /* Allocate memory once. */ pReportBuffer = AwsIotDefender_MallocReport( dataSize * sizeof( uint8_t ) ); @@ -175,7 +178,7 @@ bool AwsIotDefenderInternal_CreateReport( void ) void AwsIotDefenderInternal_DeleteReport( void ) { /* Destroy the encoder object. */ - _defenderEncoder.destroy( &( _report.object ) ); + _pAwsIotDefenderEncoder->destroy( &( _report.object ) ); /* Free the memory of data buffer. */ AwsIotDefender_FreeReport( _report.pDataBuffer ); @@ -215,34 +218,34 @@ static void _serialize( void ) uint8_t metricsGroupCount = 0; uint32_t i = 0; - serializerError = _defenderEncoder.init( pEncoderObject, _report.pDataBuffer, _report.size ); + serializerError = _pAwsIotDefenderEncoder->init( pEncoderObject, _report.pDataBuffer, _report.size ); assertNoError( serializerError ); /* Create the outermost map with 2 keys: "header", "metrics". */ - serializerError = _defenderEncoder.openContainer( pEncoderObject, &reportMap, 2 ); + serializerError = _pAwsIotDefenderEncoder->openContainer( pEncoderObject, &reportMap, 2 ); assertNoError( serializerError ); /* Create the "header" map with 2 keys: "report_id", "version". */ - serializerError = _defenderEncoder.openContainerWithKey( &reportMap, - HEADER_TAG, - &headerMap, - 2 ); + serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &reportMap, + HEADER_TAG, + &headerMap, + 2 ); assertNoError( serializerError ); /* Append key-value pair of "report_Id" which uses clock time. */ - serializerError = _defenderEncoder.appendKeyValue( &headerMap, - REPORTID_TAG, - IotSerializer_ScalarSignedInt( ( int64_t ) _AwsIotDefenderReportId ) ); + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &headerMap, + REPORTID_TAG, + IotSerializer_ScalarSignedInt( ( int64_t ) _AwsIotDefenderReportId ) ); assertNoError( serializerError ); /* Append key-value pair of "version". */ - serializerError = _defenderEncoder.appendKeyValue( &headerMap, - VERSION_TAG, - IotSerializer_ScalarTextString( VERSION_1_0 ) ); + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &headerMap, + VERSION_TAG, + IotSerializer_ScalarTextString( VERSION_1_0 ) ); assertNoError( serializerError ); /* Close the "header" map. */ - serializerError = _defenderEncoder.closeContainer( &reportMap, &headerMap ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( &reportMap, &headerMap ); assertNoError( serializerError ); /* Count how many metrics groups user specified. */ @@ -252,10 +255,10 @@ static void _serialize( void ) } /* Create the "metrics" map with number of keys as the number of metrics groups. */ - serializerError = _defenderEncoder.openContainerWithKey( &reportMap, - METRICS_TAG, - &metricsMap, - metricsGroupCount ); + serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &reportMap, + METRICS_TAG, + &metricsMap, + metricsGroupCount ); assertNoError( serializerError ); for( i = 0; i < DEFENDER_METRICS_GROUP_COUNT; i++ ) @@ -277,11 +280,11 @@ static void _serialize( void ) } /* Close the "metrics" map. */ - serializerError = _defenderEncoder.closeContainer( &reportMap, &metricsMap ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( &reportMap, &metricsMap ); assertNoError( serializerError ); /* Close the "report" map. */ - serializerError = _defenderEncoder.closeContainer( pEncoderObject, &reportMap ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( pEncoderObject, &reportMap ); assertNoError( serializerError ); } @@ -331,30 +334,30 @@ static void _serializeTcpConnections( void * param1, : _assertSuccess; /* Create the "tcp_connections" map with 1 key "established_connections" */ - serializerError = _defenderEncoder.openContainerWithKey( pMetricsObject, - TCP_CONN_TAG, - &tcpConnectionMap, - 1 ); + serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( pMetricsObject, + TCP_CONN_TAG, + &tcpConnectionMap, + 1 ); assertNoError( serializerError ); /* if user specify any metrics under "established_connections" */ if( hasEstablishedConnections ) { /* Create the "established_connections" map with "total" and/or "connections". */ - serializerError = _defenderEncoder.openContainerWithKey( &tcpConnectionMap, - EST_CONN_TAG, - &establishedMap, - hasConnections + hasTotal ); + serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &tcpConnectionMap, + EST_CONN_TAG, + &establishedMap, + hasConnections + hasTotal ); assertNoError( serializerError ); /* if user specify any metrics under "connections" and there are at least one connection */ if( hasConnections ) { /* create array "connections" under "established_connections" */ - serializerError = _defenderEncoder.openContainerWithKey( &establishedMap, - CONN_TAG, - &connectionsArray, - total ); + serializerError = _pAwsIotDefenderEncoder->openContainerWithKey( &establishedMap, + CONN_TAG, + &connectionsArray, + total ); assertNoError( serializerError ); IotContainers_ForEach( pTcpConnectionsMetricsList, pListIterator ) @@ -362,9 +365,9 @@ static void _serializeTcpConnections( void * param1, IotSerializerEncoderObject_t connectionMap = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; /* open a map under "connections" */ - serializerError = _defenderEncoder.openContainer( &connectionsArray, - &connectionMap, - hasRemoteAddr ); + serializerError = _pAwsIotDefenderEncoder->openContainer( &connectionsArray, + &connectionMap, + hasRemoteAddr ); assertNoError( serializerError ); /* add remote address */ @@ -372,32 +375,32 @@ static void _serializeTcpConnections( void * param1, { pMetricsTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, pListIterator, link ); - serializerError = _defenderEncoder.appendKeyValue( &connectionMap, REMOTE_ADDR_TAG, - IotSerializer_ScalarTextString( pMetricsTcpConnection->pRemoteAddress ) ); + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &connectionMap, REMOTE_ADDR_TAG, + IotSerializer_ScalarTextString( pMetricsTcpConnection->pRemoteAddress ) ); assertNoError( serializerError ); } - serializerError = _defenderEncoder.closeContainer( &connectionsArray, &connectionMap ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( &connectionsArray, &connectionMap ); assertNoError( serializerError ); } - serializerError = _defenderEncoder.closeContainer( &establishedMap, &connectionsArray ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( &establishedMap, &connectionsArray ); assertNoError( serializerError ); } if( hasTotal ) { - serializerError = _defenderEncoder.appendKeyValue( &establishedMap, - TOTAL_TAG, - IotSerializer_ScalarSignedInt( total ) ); + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &establishedMap, + TOTAL_TAG, + IotSerializer_ScalarSignedInt( total ) ); assertNoError( serializerError ); } - serializerError = _defenderEncoder.closeContainer( &tcpConnectionMap, &establishedMap ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( &tcpConnectionMap, &establishedMap ); assertNoError( serializerError ); } - serializerError = _defenderEncoder.closeContainer( pMetricsObject, &tcpConnectionMap ); + serializerError = _pAwsIotDefenderEncoder->closeContainer( pMetricsObject, &tcpConnectionMap ); assertNoError( serializerError ); } diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c index 0f46e48c65..8e3fcaf344 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c @@ -60,7 +60,7 @@ static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ); static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ); -IotSerializerDecodeInterface_t _IotSerializerCborDecoder = +static const IotSerializerDecodeInterface_t _cborDecoder = { .init = _init, .get = _get, @@ -483,3 +483,12 @@ static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ) return cbor_value_at_end( &pCborValueWrapper->cborValue ); } + +/*-----------------------------------------------------------*/ + +const IotSerializerDecodeInterface_t * IotSerializer_GetCborDecoder( void ) +{ + return &_cborDecoder; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c index c6aa12ffb0..77a2aef8a2 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c @@ -62,7 +62,7 @@ static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEnc IotSerializerScalarData_t scalarData ); -IotSerializerEncodeInterface_t _IotSerializerCborEncoder = +static const IotSerializerEncodeInterface_t _cborEncoder = { .getEncodedSize = _getEncodedSize, .getExtraBufferSizeNeeded = _getExtraBufferSizeNeeded, @@ -318,3 +318,12 @@ static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEnc return returnedError; } + +/*-----------------------------------------------------------*/ + +const IotSerializerEncodeInterface_t * IotSerializer_GetCborEncoder( void ) +{ + return &_cborEncoder; +} + +/*-----------------------------------------------------------*/ diff --git a/platform/include/iot_network_mbedtls.h b/platform/include/iot_network_mbedtls.h index 678082cb5f..604bf622e1 100644 --- a/platform/include/iot_network_mbedtls.h +++ b/platform/include/iot_network_mbedtls.h @@ -75,7 +75,12 @@ * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions * declared in this file. */ -#define IOT_NETWORK_INTERFACE_MBEDTLS ( &( IotNetworkMbedtls ) ) +#define IOT_NETWORK_INTERFACE_MBEDTLS ( IotNetworkMbedtls_GetInterface() ) + +/** + * @brief Retrieve the network interface using the functions in this file. + */ +const IotNetworkInterface_t * IotNetworkMbedtls_GetInterface( void ); /** * @brief One-time initialization function for this network stack. @@ -149,13 +154,4 @@ IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ); void IotNetworkMbedtls_GetServerInfo( void * pConnection, IotMetricsTcpConnection_t * pServerInfo ); -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Declaration of a network interface struct using the functions in this file. - */ -extern const IotNetworkInterface_t IotNetworkMbedtls; -/** @endcond */ - #endif /* ifndef IOT_NETWORK_MBEDTLS_H_ */ diff --git a/platform/include/iot_network_metrics.h b/platform/include/iot_network_metrics.h index 59076d0992..4acb9eb73c 100644 --- a/platform/include/iot_network_metrics.h +++ b/platform/include/iot_network_metrics.h @@ -43,15 +43,11 @@ * @brief Provides a pointer to an #IotNetworkInterface_t that uses that provides * metrics. */ -#define IOT_NETWORK_INTERFACE_METRICS ( &( IotNetworkMetrics ) ) +#define IOT_NETWORK_INTERFACE_METRICS ( IotNetworkMetrics_GetInterface() ) /** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Declaration of a network interface struct with metrics. + * @brief Retrieve the network interface with metrics. */ -extern const IotNetworkInterface_t IotNetworkMetrics; -/** @endcond */ +const IotNetworkInterface_t * IotNetworkMetrics_GetInterface( void ); #endif /* ifndef IOT_NETWORK_METRICS_H_ */ diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/posix/iot_network_openssl.h index 77b9c8cb28..2cc096fc91 100644 --- a/platform/include/posix/iot_network_openssl.h +++ b/platform/include/posix/iot_network_openssl.h @@ -86,7 +86,12 @@ typedef struct _networkConnection IotNetworkConnectionOpenssl_t; * @brief Provides a pointer to an #IotNetworkInterface_t that uses the functions * declared in this file. */ -#define IOT_NETWORK_INTERFACE_OPENSSL ( &( IotNetworkOpenssl ) ) +#define IOT_NETWORK_INTERFACE_OPENSSL ( IotNetworkOpenssl_GetInterface() ) + +/** + * @brief Retrieve the network interface using the functions in this file. + */ +const IotNetworkInterface_t * IotNetworkOpenssl_GetInterface( void ); /** * @brief One-time initialization function for this network stack. @@ -165,13 +170,4 @@ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); void IotNetworkOpenssl_GetServerInfo( void * pConnection, IotMetricsTcpConnection_t * pServerInfo ); -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Declaration of a network interface struct using the functions in this file. - */ -extern const IotNetworkInterface_t IotNetworkOpenssl; -/** @endcond */ - #endif /* ifndef IOT_NETWORK_OPENSSL_H_ */ diff --git a/platform/source/network/iot_network_mbedtls.c b/platform/source/network/iot_network_mbedtls.c index b7ea3f2faa..933cede9a5 100644 --- a/platform/source/network/iot_network_mbedtls.c +++ b/platform/source/network/iot_network_mbedtls.c @@ -200,7 +200,7 @@ static uint32_t _receiveThreadCount = 0; /** * @brief An #IotNetworkInterface_t that uses the functions in this file. */ -const IotNetworkInterface_t IotNetworkMbedtls = +static const IotNetworkInterface_t _networkMbedtls = { .create = IotNetworkMbedtls_Create, .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, @@ -708,6 +708,13 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /*-----------------------------------------------------------*/ +const IotNetworkInterface_t * IotNetworkMbedtls_GetInterface( void ) +{ + return &_networkMbedtls; +} + +/*-----------------------------------------------------------*/ + IotNetworkError_t IotNetworkMbedtls_Init( void ) { IotNetworkError_t status = IOT_NETWORK_SUCCESS; diff --git a/platform/source/network/iot_network_metrics.c b/platform/source/network/iot_network_metrics.c index 53832d66ea..6f1bc227db 100644 --- a/platform/source/network/iot_network_metrics.c +++ b/platform/source/network/iot_network_metrics.c @@ -124,7 +124,7 @@ static IotMutex_t _connectionListMutex; * @brief An #IotNetworkInterface_t that wraps network abstraction functions with * metrics. */ - const IotNetworkInterface_t IotNetworkMetrics = + static const IotNetworkInterface_t _networkMetrics = { .create = _metricsNetworkCreate, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, @@ -157,7 +157,7 @@ static IotMutex_t _connectionListMutex; * @brief An #IotNetworkInterface_t that wraps network abstraction functions with * metrics. */ - const IotNetworkInterface_t IotNetworkMetrics = + static const IotNetworkInterface_t _networkMetrics = { .create = _metricsNetworkCreate, .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, @@ -266,6 +266,13 @@ static IotNetworkError_t _metricsNetworkClose( void * pConnection ) /*-----------------------------------------------------------*/ +const IotNetworkInterface_t * IotNetworkMetrics_GetInterface( void ) +{ + return &_networkMetrics; +} + +/*-----------------------------------------------------------*/ + bool IotMetrics_Init( void ) { IotListDouble_Create( &_connectionList ); diff --git a/platform/source/posix/iot_network_openssl.c b/platform/source/posix/iot_network_openssl.c index a14d6af7de..0aada38166 100644 --- a/platform/source/posix/iot_network_openssl.c +++ b/platform/source/posix/iot_network_openssl.c @@ -119,7 +119,7 @@ typedef struct _networkConnection /** * @brief An #IotNetworkInterface_t that uses the functions in this file. */ -const IotNetworkInterface_t IotNetworkOpenssl = +static const IotNetworkInterface_t _networkOpenssl = { .create = IotNetworkOpenssl_Create, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, @@ -605,6 +605,13 @@ static void _tlsClose( _networkConnection_t * pNetworkConnection ) /*-----------------------------------------------------------*/ +const IotNetworkInterface_t * IotNetworkOpenssl_GetInterface( void ) +{ + return &_networkOpenssl; +} + +/*-----------------------------------------------------------*/ + IotNetworkError_t IotNetworkOpenssl_Init( void ) { IotNetworkError_t status = IOT_NETWORK_SUCCESS; diff --git a/tests/defender/system/aws_iot_tests_defender_system.c b/tests/defender/system/aws_iot_tests_defender_system.c index 0a6b7aa625..0f2a76911a 100644 --- a/tests/defender/system/aws_iot_tests_defender_system.c +++ b/tests/defender/system/aws_iot_tests_defender_system.c @@ -24,7 +24,6 @@ /* Standard includes. */ #include -#include #include #include @@ -32,6 +31,8 @@ #include #include +#include "platform/iot_clock.h" + /* Defender internal includes. */ #include "private/aws_iot_defender_internal.h" @@ -68,17 +69,6 @@ /* Use a big number to represent no event happened in defender. */ #define NO_EVENT 10000 -/* Define a decoder based on chosen format. */ -#if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - - #define _decoder _IotSerializerCborDecoder /**< Global defined in iot_serializer.h . */ - -#elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - - #define _decoder _IotSerializerJsonDecoder /**< Global defined in iot_serializer.h . */ - -#endif - /* Empty callback structure passed to startInfo. */ static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .param1 = NULL }; @@ -189,7 +179,7 @@ TEST_SETUP( Defender_System ) _startInfo.mqttNetworkInfo.u.setup.pNetworkServerInfo = &_serverInfo; _startInfo.mqttNetworkInfo.u.setup.pNetworkCredentialInfo = &_credential; - _startInfo.mqttNetworkInfo.pNetworkInterface = &IotNetworkMetrics; + _startInfo.mqttNetworkInfo.pNetworkInterface = IotNetworkMetrics_GetInterface(); _startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; _startInfo.mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; @@ -206,7 +196,7 @@ TEST_TEAR_DOWN( Defender_System ) if( ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_ACCEPTED ) || ( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) ) { - sleep( DEFENDER_PUBLISH_INTERVAL_SECONDS ); + IotClock_SleepMs( DEFENDER_PUBLISH_INTERVAL_SECONDS * 1000 ); } IotSemaphore_Destroy( &_callbackInfoSem ); @@ -544,7 +534,7 @@ TEST( Defender_System, Restart_and_updated_metrics_are_published ) /* Reset _callbackInfo before restarting. */ _resetCalbackInfo(); - sleep( DEFENDER_PUBLISH_INTERVAL_SECONDS ); + IotClock_SleepMs( DEFENDER_PUBLISH_INTERVAL_SECONDS ); TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ) ); @@ -681,13 +671,13 @@ static void _assertRejectDueToThrottle( void ) char errorCode[ 12 ] = ""; - IotSerializerError_t error = _decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); + IotSerializerError_t error = _pAwsIotDefenderDecoder->init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, decoderObject.type ); - error = _decoder.find( &decoderObject, "statusDetails", &statusDetailsObject ); + error = _pAwsIotDefenderDecoder->find( &decoderObject, "statusDetails", &statusDetailsObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -696,7 +686,7 @@ static void _assertRejectDueToThrottle( void ) errorCodeObject.u.value.u.string.pString = ( uint8_t * ) errorCode; errorCodeObject.u.value.u.string.length = 12; - error = _decoder.find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); + error = _pAwsIotDefenderDecoder->find( &statusDetailsObject, "ErrorCode", &errorCodeObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -704,8 +694,8 @@ static void _assertRejectDueToThrottle( void ) TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) errorCodeObject.u.value.u.string.pString, "Throttled", errorCodeObject.u.value.u.string.length ) ); - _decoder.destroy( &statusDetailsObject ); - _decoder.destroy( &decoderObject ); + _pAwsIotDefenderDecoder->destroy( &statusDetailsObject ); + _pAwsIotDefenderDecoder->destroy( &decoderObject ); } /*-----------------------------------------------------------*/ @@ -733,7 +723,7 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) IotSerializerDecoderObject_t decoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerError_t error = _decoder.init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); + IotSerializerError_t error = _pAwsIotDefenderDecoder->init( &decoderObject, _callbackInfo.pPayload, _callbackInfo.payloadLength ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -745,7 +735,7 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) statusObject.u.value.u.string.pString = ( uint8_t * ) status; statusObject.u.value.u.string.length = 10; - error = _decoder.find( &decoderObject, "status", &statusObject ); + error = _pAwsIotDefenderDecoder->find( &decoderObject, "status", &statusObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -753,8 +743,8 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ) TEST_ASSERT_EQUAL( 0, strncmp( ( const char * ) statusObject.u.value.u.string.pString, "ACCEPTED", statusObject.u.value.u.string.length ) ); - _decoder.destroy( &statusObject ); - _decoder.destroy( &decoderObject ); + _pAwsIotDefenderDecoder->destroy( &statusObject ); + _pAwsIotDefenderDecoder->destroy( &decoderObject ); } /*-----------------------------------------------------------*/ @@ -764,13 +754,13 @@ static void _verifyMetricsCommon( void ) TEST_ASSERT_NOT_NULL( _callbackInfo.pMetricsReport ); TEST_ASSERT_GREATER_THAN( 0, _callbackInfo.metricsReportLength ); - IotSerializerError_t error = _decoder.init( &_decoderObject, _callbackInfo.pMetricsReport, _callbackInfo.metricsReportLength ); + IotSerializerError_t error = _pAwsIotDefenderDecoder->init( &_decoderObject, _callbackInfo.pMetricsReport, _callbackInfo.metricsReportLength ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, _decoderObject.type ); - error = _decoder.find( &_decoderObject, "metrics", &_metricsObject ); + error = _pAwsIotDefenderDecoder->find( &_decoderObject, "metrics", &_metricsObject ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); @@ -789,7 +779,7 @@ static void _verifyTcpConections( int total, /* Assert find a "tcp_connections" map in "metrics" */ IotSerializerDecoderObject_t tcpConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - IotSerializerError_t error = _decoder.find( &_metricsObject, "tcp_connections", &tcpConnObject ); + IotSerializerError_t error = _pAwsIotDefenderDecoder->find( &_metricsObject, "tcp_connections", &tcpConnObject ); /* If any TCP connections flag is specified. */ if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_ALL ) @@ -801,7 +791,7 @@ static void _verifyTcpConections( int total, IotSerializerDecoderObject_t estConnObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _decoder.find( &tcpConnObject, "established_connections", &estConnObject ); + error = _pAwsIotDefenderDecoder->find( &tcpConnObject, "established_connections", &estConnObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED ) { @@ -812,7 +802,7 @@ static void _verifyTcpConections( int total, IotSerializerDecoderObject_t totalObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _decoder.find( &estConnObject, "total", &totalObject ); + error = _pAwsIotDefenderDecoder->find( &estConnObject, "total", &totalObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) { @@ -832,7 +822,7 @@ static void _verifyTcpConections( int total, IotSerializerDecoderObject_t connsObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; IotSerializerDecoderIterator_t connIterator = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; - error = _decoder.find( &estConnObject, "connections", &connsObject ); + error = _pAwsIotDefenderDecoder->find( &estConnObject, "connections", &connsObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_CONNECTIONS ) { @@ -840,7 +830,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_ARRAY, connsObject.type ); - error = _decoder.stepIn( &connsObject, &connIterator ); + error = _pAwsIotDefenderDecoder->stepIn( &connsObject, &connIterator ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); /* Create argument list for expected remote addresses. */ @@ -851,13 +841,13 @@ static void _verifyTcpConections( int total, { /* Assert find one "connection" map in "connections" */ IotSerializerDecoderObject_t connMap = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _decoder.get( connIterator, &connMap ); + error = _pAwsIotDefenderDecoder->get( connIterator, &connMap ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, connMap.type ); IotSerializerDecoderObject_t remoteAddrObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; - error = _decoder.find( &connMap, "remote_addr", &remoteAddrObject ); + error = _pAwsIotDefenderDecoder->find( &connMap, "remote_addr", &remoteAddrObject ); if( tcpConnFlag & AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_REMOTE_ADDR ) { @@ -877,17 +867,17 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - error = _decoder.next( connIterator ); + error = _pAwsIotDefenderDecoder->next( connIterator ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - _decoder.destroy( &connMap ); + _pAwsIotDefenderDecoder->destroy( &connMap ); } va_end( valist ); - TEST_ASSERT_TRUE( _decoder.isEndOfContainer( connIterator ) ); + TEST_ASSERT_TRUE( _pAwsIotDefenderDecoder->isEndOfContainer( connIterator ) ); - _decoder.stepOut( connIterator, &connsObject ); + _pAwsIotDefenderDecoder->stepOut( connIterator, &connsObject ); } else { @@ -895,7 +885,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - _decoder.destroy( &connsObject ); + _pAwsIotDefenderDecoder->destroy( &connsObject ); } else { @@ -903,7 +893,7 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - _decoder.destroy( &estConnObject ); + _pAwsIotDefenderDecoder->destroy( &estConnObject ); } else { @@ -911,9 +901,9 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_FOUND, error ); } - _decoder.destroy( &tcpConnObject ); - _decoder.destroy( &_metricsObject ); - _decoder.destroy( &_decoderObject ); + _pAwsIotDefenderDecoder->destroy( &tcpConnObject ); + _pAwsIotDefenderDecoder->destroy( &_metricsObject ); + _pAwsIotDefenderDecoder->destroy( &_decoderObject ); } /*-----------------------------------------------------------*/ diff --git a/tests/serializer/unit/iot_tests_serializer_cbor.c b/tests/serializer/unit/iot_tests_serializer_cbor.c index 5c8082f950..34ba632014 100644 --- a/tests/serializer/unit/iot_tests_serializer_cbor.c +++ b/tests/serializer/unit/iot_tests_serializer_cbor.c @@ -41,11 +41,11 @@ #include "iot_serializer.h" #include "cbor.h" -#define _encoder _IotSerializerCborEncoder -#define _decoder _IotSerializerCborDecoder - #define BUFFER_SIZE 100 +static const IotSerializerEncodeInterface_t * _pCborEncoder = NULL; +static const IotSerializerDecodeInterface_t * _pCborDecoder = NULL; + static IotSerializerEncoderObject_t _encoderObject; static uint8_t _buffer[ BUFFER_SIZE ]; @@ -56,18 +56,21 @@ TEST_SETUP( Serializer_Unit_CBOR ) { TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + _pCborEncoder = IotSerializer_GetCborEncoder(); + _pCborDecoder = IotSerializer_GetCborDecoder(); + /* Reset buffer to zero. */ memset( _buffer, 0, BUFFER_SIZE ); /* Init encoder object with buffer. */ TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.init( &_encoderObject, _buffer, BUFFER_SIZE ) ); + _pCborEncoder->init( &_encoderObject, _buffer, BUFFER_SIZE ) ); } TEST_TEAR_DOWN( Serializer_Unit_CBOR ) { /* Destroy encoder object. */ - _encoder.destroy( &_encoderObject ); + _pCborEncoder->destroy( &_encoderObject ); TEST_ASSERT_NULL( _encoderObject.pHandle ); @@ -99,7 +102,7 @@ TEST( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ) IotSerializerEncoderObject_t encoderObject = { .type = ( IotSerializerDataType_t ) 0 }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.init( &encoderObject, NULL, 0 ) ); + _pCborEncoder->init( &encoderObject, NULL, 0 ) ); /* Set the type to stream. */ TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_STREAM, encoderObject.type ); @@ -108,12 +111,12 @@ TEST( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ) TEST_ASSERT_NOT_NULL( encoderObject.pHandle ); /* Append an integer. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_BUFFER_TOO_SMALL, _encoder.append( &encoderObject, IotSerializer_ScalarSignedInt( 1 ) ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_BUFFER_TOO_SMALL, _pCborEncoder->append( &encoderObject, IotSerializer_ScalarSignedInt( 1 ) ) ); /* Needed buffer size is 1 to encode integer "1". */ - TEST_ASSERT_EQUAL( 1, _encoder.getExtraBufferSizeNeeded( &encoderObject ) ); + TEST_ASSERT_EQUAL( 1, _pCborEncoder->getExtraBufferSizeNeeded( &encoderObject ) ); - _encoder.destroy( &encoderObject ); + _pCborEncoder->destroy( &encoderObject ); TEST_ASSERT_NULL( encoderObject.pHandle ); } @@ -123,7 +126,7 @@ TEST( Serializer_Unit_CBOR, Encoder_append_integer ) int64_t value = 6; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.append( &_encoderObject, IotSerializer_ScalarSignedInt( value ) ) ); + _pCborEncoder->append( &_encoderObject, IotSerializer_ScalarSignedInt( value ) ) ); /* --- Verification --- */ @@ -141,7 +144,7 @@ TEST( Serializer_Unit_CBOR, Encoder_append_integer ) TEST_ASSERT_EQUAL( value, result ); /* Encoded size is 1. */ - TEST_ASSERT_EQUAL( 1, _encoder.getEncodedSize( &_encoderObject, _buffer ) ); + TEST_ASSERT_EQUAL( 1, _pCborEncoder->getEncodedSize( &_encoderObject, _buffer ) ); } TEST( Serializer_Unit_CBOR, Encoder_append_text_string ) @@ -149,7 +152,7 @@ TEST( Serializer_Unit_CBOR, Encoder_append_text_string ) char * str = "hello world"; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.append( &_encoderObject, IotSerializer_ScalarTextString( str ) ) ); + _pCborEncoder->append( &_encoderObject, IotSerializer_ScalarTextString( str ) ) ); /* --- Verification --- */ @@ -174,7 +177,7 @@ TEST( Serializer_Unit_CBOR, Encoder_append_byte_string ) size_t inputLength = strlen( ( const char * ) inputBytes ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.append( &_encoderObject, IotSerializer_ScalarByteString( inputBytes, inputLength ) ) ); + _pCborEncoder->append( &_encoderObject, IotSerializer_ScalarByteString( inputBytes, inputLength ) ) ); /* --- Verification --- */ @@ -202,7 +205,7 @@ TEST( Serializer_Unit_CBOR, Encoder_open_a_scalar ) IotSerializerEncoderObject_t integerObject = { .pHandle = NULL, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_INVALID_INPUT, - _encoder.openContainer( &_encoderObject, &integerObject, 1 ) ); + _pCborEncoder->openContainer( &_encoderObject, &integerObject, 1 ) ); } TEST( Serializer_Unit_CBOR, Encoder_open_map ) @@ -210,13 +213,13 @@ TEST( Serializer_Unit_CBOR, Encoder_open_map ) IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.openContainer( &_encoderObject, &mapObject, 1 ) ); + _pCborEncoder->openContainer( &_encoderObject, &mapObject, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.appendKeyValue( &mapObject, "key", IotSerializer_ScalarTextString( "value" ) ) ); + _pCborEncoder->appendKeyValue( &mapObject, "key", IotSerializer_ScalarTextString( "value" ) ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.closeContainer( &_encoderObject, &mapObject ) ); + _pCborEncoder->closeContainer( &_encoderObject, &mapObject ) ); /* --- Verification --- */ @@ -248,16 +251,16 @@ TEST( Serializer_Unit_CBOR, Encoder_open_array ) int64_t numberArray[] = { 3, 2, 1 }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.openContainer( &_encoderObject, &arrayObject, 3 ) ); + _pCborEncoder->openContainer( &_encoderObject, &arrayObject, 3 ) ); for( i = 0; i < 3; i++ ) { TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); + _pCborEncoder->append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); } TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.closeContainer( &_encoderObject, &arrayObject ) ); + _pCborEncoder->closeContainer( &_encoderObject, &arrayObject ) ); /* --- Verification --- */ @@ -291,19 +294,19 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_map ) IotSerializerEncoderObject_t mapObject_2 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.openContainer( &_encoderObject, &mapObject_1, 1 ) ); + _pCborEncoder->openContainer( &_encoderObject, &mapObject_1, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.openContainerWithKey( &mapObject_1, "map1", &mapObject_2, 1 ) ); + _pCborEncoder->openContainerWithKey( &mapObject_1, "map1", &mapObject_2, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.appendKeyValue( &mapObject_2, "key", IotSerializer_ScalarTextString( "value" ) ) ); + _pCborEncoder->appendKeyValue( &mapObject_2, "key", IotSerializer_ScalarTextString( "value" ) ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.closeContainer( &mapObject_1, &mapObject_2 ) ); + _pCborEncoder->closeContainer( &mapObject_1, &mapObject_2 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.closeContainer( &_encoderObject, &mapObject_1 ) ); + _pCborEncoder->closeContainer( &_encoderObject, &mapObject_1 ) ); /* --- Verification --- */ @@ -341,22 +344,22 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) int64_t numberArray[] = { 3, 2, 1 }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.openContainer( &_encoderObject, &mapObject, 1 ) ); + _pCborEncoder->openContainer( &_encoderObject, &mapObject, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.openContainerWithKey( &mapObject, "array", &arrayObject, 3 ) ); + _pCborEncoder->openContainerWithKey( &mapObject, "array", &arrayObject, 3 ) ); for( i = 0; i < 3; i++ ) { TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); + _pCborEncoder->append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); } TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.closeContainer( &mapObject, &arrayObject ) ); + _pCborEncoder->closeContainer( &mapObject, &arrayObject ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _encoder.closeContainer( &_encoderObject, &mapObject ) ); + _pCborEncoder->closeContainer( &_encoderObject, &mapObject ) ); /* --- Verification --- */ diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index ba89bfaaad..80db7ceae4 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -86,9 +86,6 @@ target_compile_options( mbedtls $<$: /W0 /D_CRT_SECURE_NO_WARNINGS> ) -# Automatically export all symbols when building DLLs on Windows. -set_property( TARGET mbedtls PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) - # Organization of mbed TLS in folders. set_property( TARGET mbedtls PROPERTY FOLDER "third_party" ) source_group( include\\mbedtls FILES ${MBEDTLS_HEADERS} ) diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index bb7385cf31..371cef3cf3 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -51,9 +51,6 @@ target_compile_options( tinycbor $<$: /W0 /D_CRT_SECURE_NO_WARNINGS> ) -# Automatically export all symbols when building DLLs on Windows. -set_property( TARGET tinycbor PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) - # Organization of TinyCBOR in folders. set_property( TARGET tinycbor PROPERTY FOLDER "third_party" ) source_group( include FILES ${TINYCBOR_HEADERS} ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index d542e06d3c..248e8a75eb 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -29,9 +29,6 @@ target_compile_options( unity $<$: /W0 /D_CRT_SECURE_NO_WARNINGS> ) -# Automatically export all symbols when building DLLs on Windows. -set_property( TARGET unity PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) - # Organization of Unity in folders. set_property( TARGET unity PROPERTY FOLDER "third_party" ) source_group( include FILES ${UNITY_HEADERS} ) From a91c122485e829c0750b7c8030aad871e7ba5cea Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 23 May 2019 10:47:31 -0700 Subject: [PATCH 156/844] List all log settings in demo config (#440) --- demos/iot_config.h | 9 +++++++-- tests/iot_config.h | 28 ++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/demos/iot_config.h b/demos/iot_config.h index 8f3cbd0384..d20c1de9c4 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -50,9 +50,12 @@ #define AWS_IOT_DEMO_SHADOW_UPDATE_COUNT ( 20 ) /* Number of updates to publish. */ #define AWS_IOT_DEMO_SHADOW_UPDATE_PERIOD_MS ( 3000 ) /* Period of Shadow updates. */ -/* Enable asserts in linear containers and MQTT. */ +/* Enable asserts in the libraries. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) +#define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) /* Library logging configuration. IOT_LOG_LEVEL_GLOBAL provides a global log * level for all libraries; the library-specific settings override the global @@ -60,10 +63,12 @@ * no logs will be printed. */ #define IOT_LOG_LEVEL_GLOBAL IOT_LOG_INFO #define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO -#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_INFO +#define IOT_LOG_LEVEL_PLATFORM IOT_LOG_NONE #define IOT_LOG_LEVEL_NETWORK IOT_LOG_INFO +#define IOT_LOG_LEVEL_TASKPOOL IOT_LOG_NONE #define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_DEFENDER IOT_LOG_INFO /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ diff --git a/tests/iot_config.h b/tests/iot_config.h index fdd876b33b..e0b597a7a8 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -93,25 +93,17 @@ #define IOT_DEMO_IDENTIFIER AWS_IOT_TEST_SHADOW_THING_NAME #endif -/* Linear containers library configuration. */ +/* Enable asserts in the libraries. */ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) - -/* Task pool library configuration. */ +#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) #define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) /* MQTT library configuration. */ -#define IOT_MQTT_ENABLE_ASSERTS ( 1 ) -#define IOT_MQTT_ENABLE_METRICS ( 0 ) #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) -/* Shadow library configuration. */ -#define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) - -/* Serializer library configuration. */ -#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) - /* Defender library configuration. */ -#define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) /* Static memory resource settings for the tests. These values must be large @@ -144,6 +136,7 @@ #define IotTaskPool_FreeJob unity_free_mt #define IotTaskPool_MallocTimerEvent unity_malloc_mt #define IotTaskPool_FreeTimerEvent unity_free_mt + #define IotMqtt_MallocMessage unity_malloc_mt #define IotMqtt_FreeMessage unity_free_mt #define IotMqtt_MallocConnection unity_malloc_mt @@ -152,6 +145,7 @@ #define IotMqtt_FreeOperation unity_free_mt #define IotMqtt_MallocSubscription unity_malloc_mt #define IotMqtt_FreeSubscription unity_free_mt + #define IotSerializer_MallocCborEncoder unity_malloc_mt #define IotSerializer_FreeCborEncoder unity_free_mt #define IotSerializer_MallocCborParser unity_malloc_mt @@ -160,16 +154,18 @@ #define IotSerializer_FreeCborValue unity_free_mt #define IotSerializer_MallocDecoderObject unity_malloc_mt #define IotSerializer_FreeDecoderObject unity_free_mt - #define AwsIotDefender_MallocReport unity_malloc_mt - #define AwsIotDefender_FreeReport unity_free_mt - #define AwsIotDefender_MallocTopic unity_malloc_mt - #define AwsIotDefender_FreeTopic unity_free_mt + #define AwsIotShadow_MallocOperation unity_malloc_mt #define AwsIotShadow_FreeOperation unity_free_mt #define AwsIotShadow_MallocString unity_malloc_mt #define AwsIotShadow_FreeString unity_free_mt #define AwsIotShadow_MallocSubscription unity_malloc_mt #define AwsIotShadow_FreeSubscription unity_free_mt + + #define AwsIotDefender_MallocReport unity_malloc_mt + #define AwsIotDefender_FreeReport unity_free_mt + #define AwsIotDefender_MallocTopic unity_malloc_mt + #define AwsIotDefender_FreeTopic unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* Network types to use in the tests. These are forward declarations. */ From 617f86ecfeb203a1c6a7b11f9f5b3dbdaab58e82 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 23 May 2019 14:10:27 -0700 Subject: [PATCH 157/844] Consistent use of const for output parameters (#441) --- lib/include/aws_iot_shadow.h | 14 +++++++------- lib/include/iot_mqtt.h | 8 ++++---- lib/include/iot_taskpool.h | 6 +++--- lib/source/common/iot_taskpool.c | 18 +++++++++--------- lib/source/mqtt/iot_mqtt_api.c | 10 +++++----- lib/source/mqtt/iot_mqtt_subscription.c | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 14 +++++++------- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 67d67899c4..8f63448fde 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -173,7 +173,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, size_t thingNameLength, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * pDeleteOperation ); + AwsIotShadowOperation_t * const pDeleteOperation ); /* @[declare_shadow_delete] */ /** @@ -294,7 +294,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * pGetOperation ); + AwsIotShadowOperation_t * const pGetOperation ); /* @[declare_shadow_get] */ /** @@ -332,8 +332,8 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, uint32_t timeoutMs, - const char ** pShadowDocument, - size_t * pShadowDocumentLength ); + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ); /* @[declare_shadow_timedget] */ /** @@ -429,7 +429,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * pUpdateOperation ); + AwsIotShadowOperation_t * const pUpdateOperation ); /* @[declare_shadow_update] */ /** @@ -579,8 +579,8 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection /* @[declare_shadow_wait] */ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, uint32_t timeoutMs, - const char ** pShadowDocument, - size_t * pShadowDocumentLength ); + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ); /* @[declare_shadow_wait] */ /** diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 7c2e27e63d..97cacd5f99 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -423,7 +423,7 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pSubscribeOperation ); + IotMqttOperation_t * const pSubscribeOperation ); /* @[declare_mqtt_subscribe] */ /** @@ -508,7 +508,7 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pUnsubscribeOperation ); + IotMqttOperation_t * const pUnsubscribeOperation ); /* @[declare_mqtt_unsubscribe] */ /** @@ -638,7 +638,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pPublishOperation ); + IotMqttOperation_t * const pPublishOperation ); /* @[declare_mqtt_publish] */ /** @@ -813,7 +813,7 @@ const char * IotMqtt_OperationType( IotMqttOperationType_t operation ); bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, const char * pTopicFilter, uint16_t topicFilterLength, - IotMqttSubscription_t * pCurrentSubscription ); + IotMqttSubscription_t * const pCurrentSubscription ); /* @[declare_mqtt_issubscribed] */ #endif /* ifndef IOT_MQTT_H_ */ diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 0b141bd3eb..6277da46ef 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -103,7 +103,7 @@ * */ /* @[declare_taskpool_createsystemtaskpool] */ -IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ); +IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * pInfo ); /* @[declare_taskpool_createsystemtaskpool] */ /** @@ -146,7 +146,7 @@ IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ); * */ /* @[declare_taskpool_create] */ -IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, +IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, IotTaskPool_t * const pTaskPool ); /* @[declare_taskpool_create] */ @@ -203,7 +203,7 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPool, * * @param[in] userCallback A user-specified callback for the job. * @param[in] pUserContext A user-specified context for the callback. - * @param[in] pJobStorage The storage for the job data structure. + * @param[in,out] pJobStorage The storage for the job data structure. * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this * function returns succesfully. This handle can be used to inspect the job status with * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 33510d89ca..d74c72079d 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -253,7 +253,7 @@ static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, * * @param[in] pTaskPool The task pool to safely extract a job from. * @param[in] pJob The job to extract. - * @param[in] atCompletion A flag to indicate whether the job is being scheduled or + * @param[in] atCompletion A flag to indicate whether the job is being scheduled or * was completed already. * */ @@ -270,7 +270,7 @@ IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ) /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ) +IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * pInfo ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -285,19 +285,19 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, +IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, IotTaskPool_t * const pTaskPool ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); _taskPool_t * pTempTaskPool = NULL; - /* Verify that the task pool storage is valid. */ + /* Verify that the task pool storage is valid. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); /* Parameter checking. */ TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) ); - + /* Allocate the memory for the task pool */ pTempTaskPool = ( _taskPool_t * )IotTaskPool_MallocTaskPool( sizeof( _taskPool_t ) ); @@ -305,7 +305,7 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, { TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); } - + memset( pTempTaskPool, 0x00, sizeof( _taskPool_t ) ); TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, pTempTaskPool ) ); @@ -323,7 +323,7 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, { *pTaskPool = pTempTaskPool; } - + TASKPOOL_FUNCTION_CLEANUP_END(); } @@ -973,7 +973,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ if( IotSemaphore_Create( &pTaskPool->dispatchSignal, 0, TASKPOOL_MAX_SEM_VALUE ) == true ) { semDispatchInit = true; - + /* Create the timer mutex for a new connection. */ if( IotClock_TimerCreate( &( pTaskPool->timer ), _timerThread, pTaskPool ) == true ) { @@ -1764,7 +1764,7 @@ static void _timerThread( void * pArgument ) /* Complete the shutdown sequence. */ _destroyTaskPool( pTaskPool ); - + IotTaskPool_FreeTaskPool( pTaskPool ); return; diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 3d1658ef0d..9fcb7f3fd2 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -137,7 +137,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pOperationReference ); + IotMqttOperation_t * const pOperationReference ); /*-----------------------------------------------------------*/ @@ -542,7 +542,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pOperationReference ) + IotMqttOperation_t * const pOperationReference ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pSubscriptionOperation = NULL; @@ -1385,7 +1385,7 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pSubscribeOperation ) + IotMqttOperation_t * const pSubscribeOperation ) { return _subscriptionCommon( IOT_MQTT_SUBSCRIBE, mqttConnection, @@ -1441,7 +1441,7 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pUnsubscribeOperation ) + IotMqttOperation_t * const pUnsubscribeOperation ) { return _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, mqttConnection, @@ -1496,7 +1496,7 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, const IotMqttPublishInfo_t * pPublishInfo, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * pPublishOperation ) + IotMqttOperation_t * const pPublishOperation ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pOperation = NULL; diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index 7405fb7434..a052f373c4 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -584,7 +584,7 @@ void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnecti bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, const char * pTopicFilter, uint16_t topicFilterLength, - IotMqttSubscription_t * pCurrentSubscription ) + IotMqttSubscription_t * const pCurrentSubscription ) { bool status = false; _mqttSubscription_t * pSubscription = NULL; diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index aa9396f469..b8edcf8882 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -653,7 +653,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, size_t thingNameLength, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * pDeleteOperation ) + AwsIotShadowOperation_t * const pDeleteOperation ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -750,7 +750,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * pGetOperation ) + AwsIotShadowOperation_t * const pGetOperation ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -825,8 +825,8 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pGetInfo, uint32_t flags, uint32_t timeoutMs, - const char ** pShadowDocument, - size_t * pShadowDocumentLength ) + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -862,7 +862,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowDocumentInfo_t * pUpdateInfo, uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * pUpdateOperation ) + AwsIotShadowOperation_t * const pUpdateOperation ) { _shadowOperation_t * pOperation = NULL; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -1009,8 +1009,8 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, uint32_t timeoutMs, - const char ** pShadowDocument, - size_t * pShadowDocumentLength ) + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; From 1c951d7777f7cb8f2773936a87fe7dbd1174ef5f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 23 May 2019 14:28:27 -0700 Subject: [PATCH 158/844] Check all platform resources freed when testing (#442) --- platform/source/posix/iot_clock_posix.c | 21 ++++++- platform/source/posix/iot_threads_posix.c | 56 +++++++++++++------ .../unity/unity/fixture/unity_fixture.c | 14 +++++ .../fixture/unity_fixture_malloc_overrides.h | 3 + 4 files changed, 76 insertions(+), 18 deletions(-) diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index 735f9279a1..d6f66a0324 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -69,6 +69,15 @@ #define LIBRARY_LOG_NAME ( "CLOCK" ) #include "iot_logging_setup.h" +/* When building tests, the Unity framework's malloc overrides are used to track + * calls to platform resource creation and destruction. This ensures that all + * platform resources are destroyed before the tests finish. When not testing, + * define the Unity malloc functions to nothing. */ +#if IOT_BUILD_TESTS != 1 + #define UnityMalloc_IncrementMallocCount() + #define UnityMalloc_DecrementMallocCount() +#endif + /*-----------------------------------------------------------*/ /** @@ -227,7 +236,7 @@ void IotClock_SleepMs( uint32_t sleepTimeMs ) { /* This block should not be reached; log an error and abort if it is. */ IotLogError( "Sleep failed. errno=%d.", errno ); - + abort(); } } @@ -262,6 +271,11 @@ bool IotClock_TimerCreate( IotTimer_t * pNewTimer, IotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); status = false; } + else + { + /* Increment the number of platform resources in use. */ + UnityMalloc_IncrementMallocCount(); + } return status; } @@ -272,12 +286,15 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) { IotLogDebug( "Destroying timer %p.", pTimer ); + /* Decrement the number of platform resources in use. */ + UnityMalloc_DecrementMallocCount(); + /* Destroy the underlying POSIX timer. */ if( timer_delete( pTimer->timer ) != 0 ) { /* This block should not be reached; log an error and abort if it is. */ IotLogError( "Failed to destroy timer %p. errno=%d.", pTimer, errno ); - + abort(); } } diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 995b10fa6c..74a1e9b8e7 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -94,6 +94,15 @@ #define IotThreads_Free free #endif +/* When building tests, the Unity framework's malloc overrides are used to track + * calls to platform resource creation and destruction. This ensures that all + * platform resources are destroyed before the tests finish. When not testing, + * define the Unity malloc functions to nothing. */ +#if IOT_BUILD_TESTS != 1 + #define UnityMalloc_IncrementMallocCount() + #define UnityMalloc_DecrementMallocCount() +#endif + /*-----------------------------------------------------------*/ /** @@ -239,7 +248,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ) { - bool status = true; + IOT_FUNCTION_ENTRY( bool, true ); int mutexError = 0; pthread_mutexattr_t mutexAttributes, * pMutexAttributes = NULL; @@ -253,24 +262,21 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, IotLogError( "Failed to initialize mutex attributes. errno=%d.", mutexError ); - status = false; + IOT_SET_AND_GOTO_CLEANUP( false ); } - if( status == true ) - { - pMutexAttributes = &mutexAttributes; + pMutexAttributes = &mutexAttributes; - /* Set recursive mutex type. */ - mutexError = pthread_mutexattr_settype( &mutexAttributes, - PTHREAD_MUTEX_RECURSIVE ); + /* Set recursive mutex type. */ + mutexError = pthread_mutexattr_settype( &mutexAttributes, + PTHREAD_MUTEX_RECURSIVE ); - if( mutexError != 0 ) - { - IotLogError( "Failed to set recursive mutex type. errno=%d.", - mutexError ); + if( mutexError != 0 ) + { + IotLogError( "Failed to set recursive mutex type. errno=%d.", + mutexError ); - status = false; - } + IOT_SET_AND_GOTO_CLEANUP( false ); } } @@ -282,22 +288,32 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, pNewMutex, mutexError ); - status = false; + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + /* Increment the number of platform resources in use. */ + UnityMalloc_IncrementMallocCount(); } + IOT_FUNCTION_CLEANUP_BEGIN(); + /* Destroy any created mutex attributes. */ if( pMutexAttributes != NULL ) { ( void ) pthread_mutexattr_destroy( &mutexAttributes ); } - return status; + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ void IotMutex_Destroy( IotMutex_t * pMutex ) { + /* Decrement the number of platform resources in use. */ + UnityMalloc_DecrementMallocCount(); + int mutexError = pthread_mutex_destroy( pMutex ); if( mutexError != 0 ) @@ -389,6 +405,11 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, status = false; } + else + { + /* Increment the number of platform resources in use. */ + UnityMalloc_IncrementMallocCount(); + } } return status; @@ -417,6 +438,9 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) { + /* Decrement the number of platform resources in use. */ + UnityMalloc_DecrementMallocCount(); + if( sem_destroy( pSemaphore ) != 0 ) { /* This block should not be reached; log an error and abort if it is. */ diff --git a/third_party/unity/unity/fixture/unity_fixture.c b/third_party/unity/unity/fixture/unity_fixture.c index 6894a573ab..4c6fab5eef 100644 --- a/third_party/unity/unity/fixture/unity_fixture.c +++ b/third_party/unity/unity/fixture/unity_fixture.c @@ -163,6 +163,20 @@ void UnityMalloc_MakeMallocFailAfterCount(int countdown) unity_exit_critical_section(); } +void UnityMalloc_IncrementMallocCount(void) +{ + unity_enter_critical_section(); + malloc_count++; + unity_exit_critical_section(); +} + +void UnityMalloc_DecrementMallocCount(void) +{ + unity_enter_critical_section(); + malloc_count--; + unity_exit_critical_section(); +} + /* These definitions are always included from unity_fixture_malloc_overrides.h */ /* We undef to use them or avoid conflict with per the C standard */ #undef malloc diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h index e5d10cb991..2c6a7a7eaf 100644 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h @@ -39,6 +39,9 @@ #define realloc unity_realloc_mt #define free unity_free_mt +void UnityMalloc_IncrementMallocCount(void); +void UnityMalloc_DecrementMallocCount(void); + void unity_provide_critical_section(void(*start)(void), void(*end)(void)); void unity_enter_critical_section(void); void unity_exit_critical_section(void); From 1c29971ad857ef201852b22df52406f2e71f3513 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 24 May 2019 10:10:45 -0700 Subject: [PATCH 159/844] Allow POSIX platform allocation failures to be mocked (#443) --- platform/source/posix/iot_clock_posix.c | 39 +++++++------- platform/source/posix/iot_threads_posix.c | 52 ++++++++++--------- scripts/ci_test_coverage.sh | 3 +- .../unity/unity/fixture/unity_fixture.c | 24 +++++++-- .../fixture/unity_fixture_malloc_overrides.h | 5 +- 5 files changed, 71 insertions(+), 52 deletions(-) diff --git a/platform/source/posix/iot_clock_posix.c b/platform/source/posix/iot_clock_posix.c index d6f66a0324..54ca5c7a95 100644 --- a/platform/source/posix/iot_clock_posix.c +++ b/platform/source/posix/iot_clock_posix.c @@ -74,8 +74,8 @@ * platform resources are destroyed before the tests finish. When not testing, * define the Unity malloc functions to nothing. */ #if IOT_BUILD_TESTS != 1 - #define UnityMalloc_IncrementMallocCount() - #define UnityMalloc_DecrementMallocCount() + #define UnityMalloc_AllocateResource() true + #define UnityMalloc_FreeResource() #endif /*-----------------------------------------------------------*/ @@ -247,7 +247,7 @@ bool IotClock_TimerCreate( IotTimer_t * pNewTimer, IotThreadRoutine_t expirationRoutine, void * pArgument ) { - bool status = true; + bool status = UnityMalloc_AllocateResource(); struct sigevent expirationNotification = { .sigev_notify = SIGEV_THREAD, @@ -257,24 +257,23 @@ bool IotClock_TimerCreate( IotTimer_t * pNewTimer, .sigev_notify_attributes = NULL }; - IotLogDebug( "Creating new timer %p.", pNewTimer ); + if( status == true ) + { + IotLogDebug( "Creating new timer %p.", pNewTimer ); - /* Set the timer expiration routine and argument. */ - pNewTimer->threadRoutine = expirationRoutine; - pNewTimer->pArgument = pArgument; + /* Set the timer expiration routine and argument. */ + pNewTimer->threadRoutine = expirationRoutine; + pNewTimer->pArgument = pArgument; - /* Create the underlying POSIX timer. */ - if( timer_create( CLOCK_REALTIME, - &expirationNotification, - &( pNewTimer->timer ) ) != 0 ) - { - IotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); - status = false; - } - else - { - /* Increment the number of platform resources in use. */ - UnityMalloc_IncrementMallocCount(); + /* Create the underlying POSIX timer. */ + if( timer_create( CLOCK_REALTIME, + &expirationNotification, + &( pNewTimer->timer ) ) != 0 ) + { + IotLogError( "Failed to create new timer %p. errno=%d.", pNewTimer, errno ); + UnityMalloc_FreeResource(); + status = false; + } } return status; @@ -287,7 +286,7 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) IotLogDebug( "Destroying timer %p.", pTimer ); /* Decrement the number of platform resources in use. */ - UnityMalloc_DecrementMallocCount(); + UnityMalloc_FreeResource(); /* Destroy the underlying POSIX timer. */ if( timer_delete( pTimer->timer ) != 0 ) diff --git a/platform/source/posix/iot_threads_posix.c b/platform/source/posix/iot_threads_posix.c index 74a1e9b8e7..688481d369 100644 --- a/platform/source/posix/iot_threads_posix.c +++ b/platform/source/posix/iot_threads_posix.c @@ -99,8 +99,8 @@ * platform resources are destroyed before the tests finish. When not testing, * define the Unity malloc functions to nothing. */ #if IOT_BUILD_TESTS != 1 - #define UnityMalloc_IncrementMallocCount() - #define UnityMalloc_DecrementMallocCount() + #define UnityMalloc_AllocateResource() true + #define UnityMalloc_FreeResource() #endif /*-----------------------------------------------------------*/ @@ -280,20 +280,21 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, } } - mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); + status = UnityMalloc_AllocateResource(); - if( mutexError != 0 ) + if( status == true ) { - IotLogError( "Failed to create new mutex %p. errno=%d.", - pNewMutex, - mutexError ); + mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - /* Increment the number of platform resources in use. */ - UnityMalloc_IncrementMallocCount(); + if( mutexError != 0 ) + { + IotLogError( "Failed to create new mutex %p. errno=%d.", + pNewMutex, + mutexError ); + + UnityMalloc_FreeResource(); + IOT_SET_AND_GOTO_CLEANUP( false ); + } } IOT_FUNCTION_CLEANUP_BEGIN(); @@ -312,7 +313,7 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, void IotMutex_Destroy( IotMutex_t * pMutex ) { /* Decrement the number of platform resources in use. */ - UnityMalloc_DecrementMallocCount(); + UnityMalloc_FreeResource(); int mutexError = pthread_mutex_destroy( pMutex ); @@ -397,18 +398,19 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, } else { - if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) - { - IotLogError( "Failed to create new semaphore %p. errno=%d.", - pNewSemaphore, - errno ); + status = UnityMalloc_AllocateResource(); - status = false; - } - else + if( status == true ) { - /* Increment the number of platform resources in use. */ - UnityMalloc_IncrementMallocCount(); + if( sem_init( pNewSemaphore, 0, ( unsigned int ) initialValue ) != 0 ) + { + IotLogError( "Failed to create new semaphore %p. errno=%d.", + pNewSemaphore, + errno ); + + UnityMalloc_FreeResource(); + status = false; + } } } @@ -439,7 +441,7 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) { /* Decrement the number of platform resources in use. */ - UnityMalloc_DecrementMallocCount(); + UnityMalloc_FreeResource(); if( sem_destroy( pSemaphore ) != 0 ) { diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 16c307c32e..3a6c675f43 100644 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -1,6 +1,7 @@ #!/bin/sh # Travis CI uses this script to build an submit code coverage results. +# It does not run tests that require the network. # Exit on any nonzero return code. set -ev @@ -14,11 +15,9 @@ make -j2 # Run MQTT tests and demo against AWS IoT with code coverage. ./bin/iot_tests_mqtt -./bin/iot_demo_mqtt -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" # Run Shadow tests and demo with code coverage. ./bin/aws_iot_tests_shadow -./bin/aws_iot_demo_shadow -h "$AWS_IOT_ENDPOINT" -p 443 -s -r ../credentials/AmazonRootCA1.pem -c ../credentials/clientCert.pem -k ../credentials/privateKey.pem -i "$IOT_IDENTIFIER" # Generate code coverage results, but only for files in lib/. lcov --directory . --capture --output-file coverage.info diff --git a/third_party/unity/unity/fixture/unity_fixture.c b/third_party/unity/unity/fixture/unity_fixture.c index 4c6fab5eef..725e44b996 100644 --- a/third_party/unity/unity/fixture/unity_fixture.c +++ b/third_party/unity/unity/fixture/unity_fixture.c @@ -163,14 +163,32 @@ void UnityMalloc_MakeMallocFailAfterCount(int countdown) unity_exit_critical_section(); } -void UnityMalloc_IncrementMallocCount(void) +bool UnityMalloc_AllocateResource(void) { + bool status = true; + unity_enter_critical_section(); - malloc_count++; + + switch(malloc_fail_countdown) + { + case MALLOC_DONT_FAIL: + malloc_count++; + break; + case 0: + status = false; + break; + default: + malloc_count++; + malloc_fail_countdown--; + break; + } + unity_exit_critical_section(); + + return status; } -void UnityMalloc_DecrementMallocCount(void) +void UnityMalloc_FreeResource(void) { unity_enter_critical_section(); malloc_count--; diff --git a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h index 2c6a7a7eaf..2356faa00b 100644 --- a/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h +++ b/third_party/unity/unity/fixture/unity_fixture_malloc_overrides.h @@ -8,6 +8,7 @@ #ifndef UNITY_FIXTURE_MALLOC_OVERRIDES_H_ #define UNITY_FIXTURE_MALLOC_OVERRIDES_H_ +#include #include #ifdef UNITY_EXCLUDE_STDLIB_MALLOC @@ -39,8 +40,8 @@ #define realloc unity_realloc_mt #define free unity_free_mt -void UnityMalloc_IncrementMallocCount(void); -void UnityMalloc_DecrementMallocCount(void); +bool UnityMalloc_AllocateResource(void); +void UnityMalloc_FreeResource(void); void unity_provide_critical_section(void(*start)(void), void(*end)(void)); void unity_enter_critical_section(void); From fb6f97cec0772b22ed6d35ef86cdd11839ed275c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 24 May 2019 11:23:49 -0700 Subject: [PATCH 160/844] Add setup to automated test docs (#444) --- doc/guide/developer/automated_tests.txt | 30 +++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index 1c7bde7139..4673530124 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -12,11 +12,36 @@ The following files on the GitHub repo control the automated tests: - `.travis.yml`
Provides the job matrix to run on Travis CI. - `scripts/` directory
- Shell scripts (`sh`) that run on Travis CI servers. See @ref guide_developer_automated_tests_scripts for more information. + Shell scripts (`sh`) that run on Travis CI servers. - The scripts that test the libraries are named after the library they test. For example, `ci_test_mqtt.sh` tests the MQTT library. - `ci_test_doc.sh` tests the documentation build. Runs for pull requests only. - `ci_test_coverage.sh` generates and submits coverage results to Coveralls. Runs for commits only. +@section guide_developer_automated_tests_setup Setup +@brief How to set up the automated testing services. + +All of the automated testing services have web interfaces that can be connected to a GitHub account. + +@subsection guide_developer_automated_tests_setup_travis Travis CI +@brief How to set up Travis CI. + +Link to Travis CI: https://travis-ci.org/ + +1. Click Sign in with GitHub in the upper right corner to log in with your GitHub account. On your first login, Travis CI will prompt for GitHub permissions access. +2. Once signed in, hover over your GitHub account in the upper right corner. Click Settings in the menu that pops up. +3. On the Settings page, toggle the switch next to your fork of the C SDK to On. +4. Travis CI is now set up. See @ref guide_developer_automated_tests_credentials_policy_travis for adding commit credentials to Travis CI. + +@subsection guide_developer_automated_tests_setup_coveralls Coveralls +@brief How to set up Coveralls. + +Link to Coveralls: https://coveralls.io/ + +1. Click SIGN IN in the upper right corner. Then select GITHUB SIGN IN to log in with your GitHub account. On your first login, Coveralls will prompt for GitHub permissions access. +2. Once signed in, hover over the menu on the left and click ADD REPOS. +3. On the ADD REPO page, toggle the switch next to your fork of the C SDK to ON. +4. Coveralls is now set up up. Travis CI will automatically submit coverage data on commit check builds. + @section guide_developer_automated_tests_commit_pr Commit vs. Pull Request @brief The automated tests distinguish between GitHub commits and pull requests. Different sets of tests run on commits and pull requests. @@ -135,7 +160,4 @@ AWS recommends using the most restrictive policy possible. The following policy @endcode This policy only allows the CI to interact with topics that contain a matching client identifier (which is prefixed with the Travis CI variable `IOT_IDENTIFIER_PREFIX`). A few special topics used by the MQTT tests are also present. The policy will need to be updated if any new topics are used by the tests in the future. - -@section guide_developer_automated_tests_scripts Travis CI Scripts -@brief */ From c7581190f801c34de9ea88e686cf47ff6ce3c2b6 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 28 May 2019 13:10:44 -0700 Subject: [PATCH 161/844] Add template for porting (#445) --- CMakeLists.txt | 7 +- README.md | 2 + demos/app/iot_demo.c | 4 +- doc/guide/developer/porting.txt | 26 +-- platform/README.md | 18 +- .../include/{posix => }/iot_network_openssl.h | 0 .../network/iot_network_mbedtls.c | 0 .../network/iot_network_metrics.c | 2 +- .../{source => ports}/posix/CMakeLists.txt | 12 +- .../posix/source}/iot_clock_posix.c | 0 .../posix/source}/iot_network_openssl.c | 2 +- .../posix/source}/iot_threads_posix.c | 0 .../posix/types}/iot_platform_types_posix.h | 0 .../template/source/iot_clock_template.c | 111 +++++++++++ .../template/source/iot_threads_template.c | 177 ++++++++++++++++++ .../types/iot_platform_types_template.h | 45 +++++ .../{source => ports}/win32/CMakeLists.txt | 8 +- .../win32/source}/iot_clock_win32.c | 0 .../win32/source}/iot_threads_win32.c | 3 - .../win32/types}/iot_platform_types_win32.h | 0 tests/iot_config.h | 4 +- 21 files changed, 379 insertions(+), 42 deletions(-) rename platform/include/{posix => }/iot_network_openssl.h (100%) rename platform/{source => }/network/iot_network_mbedtls.c (100%) rename platform/{source => }/network/iot_network_metrics.c (99%) rename platform/{source => ports}/posix/CMakeLists.txt (87%) rename platform/{source/posix => ports/posix/source}/iot_clock_posix.c (100%) rename platform/{source/posix => ports/posix/source}/iot_network_openssl.c (99%) rename platform/{source/posix => ports/posix/source}/iot_threads_posix.c (100%) rename platform/{include/posix => ports/posix/types}/iot_platform_types_posix.h (100%) create mode 100644 platform/ports/template/source/iot_clock_template.c create mode 100644 platform/ports/template/source/iot_threads_template.c create mode 100644 platform/ports/template/types/iot_platform_types_template.h rename platform/{source => ports}/win32/CMakeLists.txt (70%) rename platform/{source/win32 => ports/win32/source}/iot_clock_win32.c (100%) rename platform/{source/win32 => ports/win32/source}/iot_threads_win32.c (99%) rename platform/{include/win32 => ports/win32/types}/iot_platform_types_win32.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 15c033ac6f..3a4a2a2b36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,8 @@ set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) # Platform types file. -add_definitions( -DIOT_SYSTEM_TYPES_FILE="${IOT_PLATFORM_NAME}/iot_platform_types_${IOT_PLATFORM_NAME}.h" ) +include_directories( ${PROJECT_SOURCE_DIR}/platform/ports/${IOT_PLATFORM_NAME}/types ) +add_definitions( -DIOT_SYSTEM_TYPES_FILE="iot_platform_types_${IOT_PLATFORM_NAME}.h" ) # SDK include paths. include_directories( ${PROJECT_SOURCE_DIR}/lib/include @@ -105,14 +106,14 @@ set( PLATFORM_INTERFACE_HEADERS set( PLATFORM_TYPES_HEADERS ${CMAKE_SOURCE_DIR}/lib/include/types/iot_platform_types.h - ${CMAKE_SOURCE_DIR}/platform/include/${IOT_PLATFORM_NAME}/iot_platform_types_${IOT_PLATFORM_NAME}.h ) + ${CMAKE_SOURCE_DIR}/platform/ports/${IOT_PLATFORM_NAME}/types/iot_platform_types_${IOT_PLATFORM_NAME}.h ) set( PLATFORM_COMMON_HEADERS ${CMAKE_SOURCE_DIR}/platform/include/iot_atomic.h ${CMAKE_SOURCE_DIR}/platform/include/iot_network_metrics.h ) # Platform libraries. -add_subdirectory( platform/source/${IOT_PLATFORM_NAME} ) +add_subdirectory( platform/ports/${IOT_PLATFORM_NAME} ) # Common libraries (linear containers, logging, etc.) add_subdirectory( lib/source/common ) diff --git a/README.md b/README.md index 1dcb5d2e58..ae17e3993c 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ See the documentation page [Building the SDK](https://docs.aws.amazon.com/freert Please refer to the [Porting Guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/guide_developer_porting.html) for instructions on porting this SDK. +Existing ports (which may be used as examples) are present in `platform/ports`. A blank template for implementing new ports is in `platform/ports/template`. + ## License This library is licensed under the [MIT License](LICENSE). diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 55e640ba59..1d485db17f 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -45,8 +45,8 @@ /* Choose the appropriate network header, initializers, and initialization * function. */ #if IOT_NETWORK_USE_OPENSSL == 1 - /* POSIX+OpenSSL network include. */ - #include "posix/iot_network_openssl.h" + /* OpenSSL network include. */ + #include "iot_network_openssl.h" #define IOT_DEMO_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL #define IOT_DEMO_SERVER_INFO_INITIALIZER IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER diff --git a/doc/guide/developer/porting.txt b/doc/guide/developer/porting.txt index 0ab4ed4c63..e16b248a84 100644 --- a/doc/guide/developer/porting.txt +++ b/doc/guide/developer/porting.txt @@ -16,7 +16,7 @@ In general, this SDK should build with C compilers in C99 mode. Currently, we do @subsection guide_developer_porting_directory_layout Directory layout @brief The following directories contain the SDK's source code and are relevant for porting. -Of the directories listed below, only `platform/` should be modified during porting. Some directories present in the GitHub repo (such as `cbmc`, `doc`, and `scripts`) are not relevant for porting and therefore not listed. +Of the directories listed below, only `platform/` should be modified during porting. Empty templates of a new port are placed in `platform/ports/template`. Some directories present in the GitHub repo (such as `cbmc`, `doc`, and `scripts`) are not relevant for porting and therefore not listed. As relative paths from the SDK's root directory: - `demos/`
@@ -45,15 +45,18 @@ As relative paths from the SDK's root directory: Individual library sources are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. - `platform/`
Platform-dependent SDK files that must be ported. The existing ports in this directory may be useful examples.
- - `include/`
- Platform header files. Unlike the headers in `lib/include/`, headers in this directory may directly use system types.
+ - `include/`
+ Platform layer headers that are not specific to a specific port, such as the atomic and network headers. - `atomic/`
- Existing ports of atomic operations. See @ref platform_atomic for how to create a new port.
- - `posix, ...`
- Headers for existing ports of the [platform layer](@ref platform), named after the platform they target. See @ref guide_developer_porting_platform for how to create a new port.
- - `source/` - - `posix, ...`
- Headers for existing ports of the [platform layer](@ref platform), named after the platform they target. See @ref guide_developer_porting_platform for how to create a new port.
+ Existing ports of atomic operations. See @ref platform_atomic for how to create a new port. New atomic ports should be placed here.
+ - `network/`
+ Implementations of the platform network layer that are not specific to a port. + - `ports`
+ Existing ports that can be used as a reference. New ports should also be placed here.
+ - `posix, win32, ...`
+ Port source and headers are placed in a directory matching the port name. + - `template`
+ Empty port with stubbed out functions. This should be the starting point of a new port.
- `tests/`
SDK tests that can be used to verify ports. - `common, defender, mqtt, ...`
@@ -74,6 +77,7 @@ SDK include paths that are always required: - The path of the [config file](@ref global_config), `iot_config.h`. For example: - In the SDK demos, `iot_config.h` is in `demos/`. - In the SDK tests, `iot_config.h` is in `tests/`. +- The path of the port types file. For example, `platform/ports/posix/types` when using the POSIX port. - `lib/include` - `platform/include` @@ -102,7 +106,7 @@ In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1 @brief [Types](@ref platform_datatypes_handles) that must be set in the platform layer. @see [Platform types](@ref platform_datatypes_handles) for a list of all types that must be set.
-`platform/include/posix/iot_platform_types_posix.h` for an example of setting the types on POSIX systems. +`platform/ports/posix/types/iot_platform_types_posix.h` for an example of setting the types on POSIX systems. The platform types should be set in the [config file](@ref global_config), `iot_config.h`, or in another header included by the config file. As an example, the header `iot_platform_types_posix.h` sets the platform types to the matching POSIX system types. This header is then included in `iot_config.h` by the provided CMake build system. @@ -113,5 +117,5 @@ The platform types should be set in the [config file](@ref global_config), `iot_ @see [Platform functions](@ref platform_functions) for a list of all functions that must be implemented.
`lib/include/platform` for the interfaces of the platform functions.
-`platform/source/posix` for examples of functions implemented for POSIX systems. +`platform/ports/posix/source` for examples of functions implemented for POSIX systems. */ diff --git a/platform/README.md b/platform/README.md index 6430cd2874..e3a51356e7 100644 --- a/platform/README.md +++ b/platform/README.md @@ -2,18 +2,18 @@ **Main documentation page:** [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/index.html) -This directory contains the headers and sources of the platform layer. Its subdirectories are organized as follows: +This directory contains the headers and sources of the platform layer, implemented for various ports. Its subdirectories are organized as follows: - `include`
- Platform layer headers. Headers used across multiple ports are placed in this directory. + Platform layer headers that are not specific to a specific port, such as the atomic and network headers. - `atomic`
Implementations of atomic operations. - - `posix`, `win32`, `...`
- Headers specific to a port. -- `source`
- Platform layer sources. - - `network` - Source files of network implementations that are not specific to a specific port. - - `posix`, `win32`, `...`
+- `network`
+ Implementations of the platform network layer that are not specific to a port. +- `ports`
Source files of a specific port. + - `posix`, `win32`, `...`
+ Port sources are in a directory named after their target. + - `template`
+ Empty port sources containing stubbed-out functions. When porting this SDK to a new platform, only files in this directory should be modified. See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/guide_developer_porting.html) for instructions. diff --git a/platform/include/posix/iot_network_openssl.h b/platform/include/iot_network_openssl.h similarity index 100% rename from platform/include/posix/iot_network_openssl.h rename to platform/include/iot_network_openssl.h diff --git a/platform/source/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c similarity index 100% rename from platform/source/network/iot_network_mbedtls.c rename to platform/network/iot_network_mbedtls.c diff --git a/platform/source/network/iot_network_metrics.c b/platform/network/iot_network_metrics.c similarity index 99% rename from platform/source/network/iot_network_metrics.c rename to platform/network/iot_network_metrics.c index 6f1bc227db..788440f27d 100644 --- a/platform/source/network/iot_network_metrics.c +++ b/platform/network/iot_network_metrics.c @@ -102,7 +102,7 @@ static IotMutex_t _connectionListMutex; /* Choose the appropriate network abstraction implementation. */ #if IOT_NETWORK_USE_OPENSSL == 1 /* OpenSSL networking include. */ - #include "posix/iot_network_openssl.h" + #include "iot_network_openssl.h" /** * @brief Pointer to the metrics-wrapped network creation function. diff --git a/platform/source/posix/CMakeLists.txt b/platform/ports/posix/CMakeLists.txt similarity index 87% rename from platform/source/posix/CMakeLists.txt rename to platform/ports/posix/CMakeLists.txt index d497439ba9..2312e4daea 100644 --- a/platform/source/posix/CMakeLists.txt +++ b/platform/ports/posix/CMakeLists.txt @@ -61,15 +61,15 @@ if( ${IOT_NETWORK_USE_OPENSSL} ) endif() # Choose OpenSSL network source file. - set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/posix/iot_network_openssl.h ) - set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_network_openssl.c ) + set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/iot_network_openssl.h ) + set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/ports/posix/source/iot_network_openssl.c ) # Link OpenSSL. set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) endif() else() set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h ) - set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_mbedtls.c ) + set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c ) set( TLS_LIBRARY_LINKER_FLAG mbedtls ) endif() @@ -80,9 +80,9 @@ set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} # Platform libraries source files. set( PLATFORM_SOURCES - ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_clock_posix.c - ${CMAKE_SOURCE_DIR}/platform/source/posix/iot_threads_posix.c - ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c + ${CMAKE_SOURCE_DIR}/platform/ports/posix/source/iot_clock_posix.c + ${CMAKE_SOURCE_DIR}/platform/ports/posix/source/iot_threads_posix.c + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ${NETWORK_SOURCE_FILE} ) # Add platform as an interface library. It will be built into iotbase. diff --git a/platform/source/posix/iot_clock_posix.c b/platform/ports/posix/source/iot_clock_posix.c similarity index 100% rename from platform/source/posix/iot_clock_posix.c rename to platform/ports/posix/source/iot_clock_posix.c diff --git a/platform/source/posix/iot_network_openssl.c b/platform/ports/posix/source/iot_network_openssl.c similarity index 99% rename from platform/source/posix/iot_network_openssl.c rename to platform/ports/posix/source/iot_network_openssl.c index 0aada38166..9e8516eefc 100644 --- a/platform/source/posix/iot_network_openssl.c +++ b/platform/ports/posix/source/iot_network_openssl.c @@ -52,7 +52,7 @@ #include /* POSIX+OpenSSL network include. */ -#include "posix/iot_network_openssl.h" +#include "iot_network_openssl.h" /* Platform threads include. */ #include "platform/iot_threads.h" diff --git a/platform/source/posix/iot_threads_posix.c b/platform/ports/posix/source/iot_threads_posix.c similarity index 100% rename from platform/source/posix/iot_threads_posix.c rename to platform/ports/posix/source/iot_threads_posix.c diff --git a/platform/include/posix/iot_platform_types_posix.h b/platform/ports/posix/types/iot_platform_types_posix.h similarity index 100% rename from platform/include/posix/iot_platform_types_posix.h rename to platform/ports/posix/types/iot_platform_types_posix.h diff --git a/platform/ports/template/source/iot_clock_template.c b/platform/ports/template/source/iot_clock_template.c new file mode 100644 index 0000000000..55e3ace1e8 --- /dev/null +++ b/platform/ports/template/source/iot_clock_template.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_clock_template.c + * @brief Template implementation of the functions in iot_clock.h + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Platform clock include. */ +#include "platform/iot_clock.h" + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "CLOCK" ) +#include "iot_logging_setup.h" + +/*-----------------------------------------------------------*/ + +bool IotClock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_gettimestring.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +uint64_t IotClock_GetTimeMs( void ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_gettimems.html + */ + return 0; +} + +/*-----------------------------------------------------------*/ + +void IotClock_SleepMs( uint32_t sleepTimeMs ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_sleepms.html + */ +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerCreate( IotTimer_t * pNewTimer, + IotThreadRoutine_t expirationRoutine, + void * pArgument ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_timercreate.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +void IotClock_TimerDestroy( IotTimer_t * pTimer ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_timerdestroy.html + */ +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerArm( IotTimer_t * pTimer, + uint32_t relativeTimeoutMs, + uint32_t periodMs ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_timerarm.html + */ + return false; +} + +/*-----------------------------------------------------------*/ diff --git a/platform/ports/template/source/iot_threads_template.c b/platform/ports/template/source/iot_threads_template.c new file mode 100644 index 0000000000..c9fe318e8a --- /dev/null +++ b/platform/ports/template/source/iot_threads_template.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_threads_template.c + * @brief Template implementation of the functions in iot_threads.h + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "THREAD" ) +#include "iot_logging_setup.h" + +/*-----------------------------------------------------------*/ + +bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, + void * pArgument, + int32_t priority, + size_t stackSize ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_createdetachedthread.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexcreate.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Destroy( IotMutex_t * pMutex ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexdestroy.html + */ +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Lock( IotMutex_t * pMutex ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexlock.html + */ +} + +/*-----------------------------------------------------------*/ + +bool IotMutex_TryLock( IotMutex_t * pMutex ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutextrylock.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Unlock( IotMutex_t * pMutex ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexunlock.html + */ +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphorecreate.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoredestroy.html + */ +} + +/*-----------------------------------------------------------*/ + +uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoregetcount.html + */ + return 0; +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphorewait.html + */ +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoretrywait.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, + uint32_t timeoutMs ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoretimedwait.html + */ + return false; +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) +{ + /* Implement this function as specified here: + * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphorepost.html + */ +} + +/*-----------------------------------------------------------*/ diff --git a/platform/ports/template/types/iot_platform_types_template.h b/platform/ports/template/types/iot_platform_types_template.h new file mode 100644 index 0000000000..f39a22828d --- /dev/null +++ b/platform/ports/template/types/iot_platform_types_template.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_platform_types_template.h + * @brief Template definitions of platform layer types. + */ + +#ifndef IOT_PLATFORM_TYPES_TEMPLATE_H_ +#define IOT_PLATFORM_TYPES_TEMPLATE_H_ + +/** + * @brief Set this to the target system's mutex type. + */ +typedef void * _IotSystemMutex_t; + +/** + * @brief Set this to the target system's semaphore type. + */ +typedef void * _IotSystemSemaphore_t; + +/** + * @brief Set this to the target system's timer type. + */ +typedef void * _IotSystemTimer_t; + +#endif /* ifndef IOT_PLATFORM_TYPES_TEMPLATE_H_ */ diff --git a/platform/source/win32/CMakeLists.txt b/platform/ports/win32/CMakeLists.txt similarity index 70% rename from platform/source/win32/CMakeLists.txt rename to platform/ports/win32/CMakeLists.txt index 7533cc3292..a89f549b70 100644 --- a/platform/source/win32/CMakeLists.txt +++ b/platform/ports/win32/CMakeLists.txt @@ -5,10 +5,10 @@ set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} # Platform library source files. set( PLATFORM_SOURCES - ${CMAKE_SOURCE_DIR}/platform/source/win32/iot_clock_win32.c - ${CMAKE_SOURCE_DIR}/platform/source/win32/iot_threads_win32.c - ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_mbedtls.c - ${CMAKE_SOURCE_DIR}/platform/source/network/iot_network_metrics.c ) + ${CMAKE_SOURCE_DIR}/platform/ports/win32/source/iot_clock_win32.c + ${CMAKE_SOURCE_DIR}/platform/ports/win32/source/iot_threads_win32.c + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ) # Add platform as an interface library. It will be built into iotbase. add_library( iotplatform INTERFACE ) diff --git a/platform/source/win32/iot_clock_win32.c b/platform/ports/win32/source/iot_clock_win32.c similarity index 100% rename from platform/source/win32/iot_clock_win32.c rename to platform/ports/win32/source/iot_clock_win32.c diff --git a/platform/source/win32/iot_threads_win32.c b/platform/ports/win32/source/iot_threads_win32.c similarity index 99% rename from platform/source/win32/iot_threads_win32.c rename to platform/ports/win32/source/iot_threads_win32.c index 242eb948f4..edbde1e173 100644 --- a/platform/source/win32/iot_threads_win32.c +++ b/platform/ports/win32/source/iot_threads_win32.c @@ -30,9 +30,6 @@ /* Standard includes. */ #include -/* Windows include. */ -#include - /* Platform threads include. */ #include "platform/iot_threads.h" diff --git a/platform/include/win32/iot_platform_types_win32.h b/platform/ports/win32/types/iot_platform_types_win32.h similarity index 100% rename from platform/include/win32/iot_platform_types_win32.h rename to platform/ports/win32/types/iot_platform_types_win32.h diff --git a/tests/iot_config.h b/tests/iot_config.h index e0b597a7a8..c19a2ff608 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -175,8 +175,8 @@ typedef struct IotNetworkCredentials IotTestNetworkCredentials_t; /* Choose the appropriate network abstraction implementation. */ #if IOT_NETWORK_USE_OPENSSL == 1 - /* POSIX+OpenSSL network include. */ - #define IOT_TEST_NETWORK_HEADER "posix/iot_network_openssl.h" + /* OpenSSL network include. */ + #define IOT_TEST_NETWORK_HEADER "iot_network_openssl.h" #define IOT_TEST_ALPN_PROTOS "\x0ex-amzn-mqtt-ca" #define IOT_TEST_NETWORK_INTERFACE IOT_NETWORK_INTERFACE_OPENSSL From 10f21ea4ce1282e1e102c79f92fed8246221633b Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Fri, 31 May 2019 12:11:08 -0400 Subject: [PATCH 162/844] Repair include path used to build MQTT for CBMC (#446) --- cbmc/proofs/Makefile.common | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index 53c52dcd39..b1fa382eb9 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -19,16 +19,17 @@ VIEWER ?= cbmc-viewer # Build goto binaries with options taken from top-level cmake INC = \ + -I$(MQTT)/platform/ports/posix/types \ -I$(MQTT)/lib/include \ -I$(MQTT)/platform/include \ - -I$(MQTT)/demos \ -I$(MQTT)/demos/include \ + -I$(MQTT)/demos \ DEF += \ - -DIOT_SYSTEM_TYPES_FILE=\"posix/iot_platform_types_posix.h\" \ + -DIOT_SYSTEM_TYPES_FILE=\"iot_platform_types_posix.h\" \ -Diotmqtt_EXPORTS \ -CFLAGS += $(CFLAGS2) $(INC) $(DEF) +CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 %.goto : %.c $(GOTO_CC) -o $@ $(CFLAGS) $< From e085b1c5bd2fe1e0f580bf64655bff8549245712 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 31 May 2019 12:04:10 -0700 Subject: [PATCH 163/844] Fix warnings from IAR compiler (#447) --- lib/source/common/iot_taskpool.c | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index d74c72079d..a30da9bf8f 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -72,7 +72,7 @@ * the system libraries as well. The system task pool needs to be initialized before any library is used or * before any code that posts jobs to the task pool runs. */ -static _taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER }; +_taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER }; /* -------------- Convenience functions to create/recycle/destroy jobs -------------- */ @@ -166,7 +166,7 @@ static void _timerThread( void * pArgument ); * @param[in] pInfo The initialization information for the task pool. * */ -bool _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ); +IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ); /** * Initializes a pre-allocated instance of a task pool. @@ -270,7 +270,7 @@ IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ) /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * pInfo ) +IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -285,7 +285,7 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * p /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, +IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, IotTaskPool_t * const pTaskPool ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -548,6 +548,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle void * pUserContext, IotTaskPoolJob_t * const ppJob ) { + _taskPool_t * pTaskPool = NULL; TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ @@ -555,7 +556,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; { _taskPoolJob_t * pTempJob = NULL; @@ -595,13 +596,15 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPoolHandl IotTaskPoolJob_t pJobHandle ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTaskPool = NULL; + _taskPoolJob_t * pJob1 = NULL; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobHandle ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; - _taskPoolJob_t * pJob1 = ( _taskPoolJob_t * )pJobHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; + pJob1 = ( _taskPoolJob_t * )pJobHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -641,12 +644,13 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPoolHandle, IotTaskPoolJob_t pJob ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTaskPool = NULL; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -688,13 +692,14 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPoolHandle, uint32_t flags ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTaskPool = NULL; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -726,12 +731,13 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, uint32_t timeMs ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTaskPool = NULL; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; if( timeMs == 0UL ) { @@ -811,13 +817,14 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPoolHandle, IotTaskPoolJobStatus_t * const pStatus ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTaskPool = NULL; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; @@ -845,12 +852,13 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle, IotTaskPoolJobStatus_t * const pStatus ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); + _taskPool_t * pTaskPool = NULL; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * )taskPoolHandle; if( pStatus != NULL ) { @@ -921,7 +929,7 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) /* ---------------------------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------------------------- */ -bool _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ) +IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); From fb2a91957a1f19a0df8605eba749691bea69c914 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 31 May 2019 16:28:11 -0700 Subject: [PATCH 164/844] Make internal task pool function static (#448) --- lib/source/common/iot_taskpool.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index a30da9bf8f..630281ac36 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -166,7 +166,7 @@ static void _timerThread( void * pArgument ); * @param[in] pInfo The initialization information for the task pool. * */ -IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ); +static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ); /** * Initializes a pre-allocated instance of a task pool. @@ -929,7 +929,7 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) /* ---------------------------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------------------------------- */ -IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ) +static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); From b295f347719156d8156c944a476c5b2761001f92 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 6 Jun 2019 13:55:54 -0700 Subject: [PATCH 165/844] Fix search of MQTT operations pending response (#449) --- CMakeLists.txt | 7 +++++++ lib/source/common/iot_taskpool.c | 4 ++-- lib/source/mqtt/iot_mqtt_operation.c | 21 ++++++++++++--------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a4a2a2b36..1028d2ca87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,3 +170,10 @@ add_subdirectory( demos/app ) if( ${IOT_BUILD_TESTS} ) add_subdirectory( tests/ ) endif() + +# Set startup projects in Visual Studio. +if( ${IOT_BUILD_TESTS} ) + set_property( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT iot_tests_mqtt ) +else() + set_property( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT iot_demo_mqtt ) +endif() diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 630281ac36..592be3a5fd 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -270,7 +270,7 @@ IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ) /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ) +IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * pInfo ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); @@ -285,7 +285,7 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * c /*-----------------------------------------------------------*/ -IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * const pInfo, +IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, IotTaskPool_t * const pTaskPool ) { TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index be912e6dd5..46d483a7f7 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -230,7 +230,7 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) { scheduleDelay = pOperation->u.operation.retry.nextPeriod; - /* Double the retry peiod, subject to a ceiling value. */ + /* Double the retry period, subject to a ceiling value. */ pOperation->u.operation.retry.nextPeriod *= 2; if( pOperation->u.operation.retry.nextPeriod > IOT_MQTT_RETRY_MS_CEILING ) @@ -328,7 +328,7 @@ IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection, { IotLogError( "Callback should not be set for a waitable operation." ); - return IOT_MQTT_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { @@ -1120,10 +1120,10 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, /* Find and remove the first matching element in the list. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - pResultLink = IotListDouble_RemoveFirstMatch( &( pMqttConnection->pendingResponse ), - NULL, - _mqttOperation_match, - ¶m ); + pResultLink = IotListDouble_FindFirstMatch( &( pMqttConnection->pendingResponse ), + NULL, + _mqttOperation_match, + ¶m ); /* Check if a match was found. */ if( pResultLink != NULL ) @@ -1179,13 +1179,14 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, EMPTY_ELSE_MARKER; } - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - if( pResult != NULL ) { - IotLogDebug( "(MQTT connection %p) Found operation %s." , + IotLogDebug( "(MQTT connection %p) Found operation %s.", pMqttConnection, IotMqtt_OperationType( type ) ); + + /* Remove the matched operation from the list. */ + IotListDouble_Remove( &( pResult->link ) ); } else { @@ -1194,6 +1195,8 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotMqtt_OperationType( type ) ); } + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + return pResult; } From 67d11b174752c52853172880ec128d4975e0a64e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 6 Jun 2019 14:13:58 -0700 Subject: [PATCH 166/844] Throttle Shadow system tests per AWS service limits (#450) --- scripts/ci_test_shadow.sh | 2 ++ tests/shadow/system/aws_iot_tests_shadow_system.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index 749d985bfa..b29343ed51 100644 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -9,8 +9,10 @@ set -e run_tests_and_demos() { # For commit builds, run the full Shadow demo and tests. For pull request builds, # run only the unit tests (credentials are not available for pull request builds). + # Sleep for 1.1 seconds between the runs to respect AWS service limits. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then ./bin/aws_iot_tests_shadow + sleep 1.1 ./bin/aws_iot_demo_shadow else # Run only Shadow unit tests. diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index c1dc38e481..516f3d10f7 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -41,6 +41,7 @@ #include "iot_json_utils.h" /* Platform layer includes. */ +#include "platform/iot_clock.h" #include "platform/iot_threads.h" /* Test network header include. */ @@ -430,6 +431,8 @@ TEST_GROUP( Shadow_System ); */ TEST_SETUP( Shadow_System ) { + static uint64_t lastConnectTime = 0; + uint64_t elapsedTime = 0; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; @@ -478,6 +481,15 @@ TEST_SETUP( Shadow_System ) connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. + * Wait until 1100 ms have elapsed since the last connection. */ + elapsedTime = IotClock_GetTimeMs() - lastConnectTime; + + if( elapsedTime < 1100ULL ) + { + IotClock_SleepMs( 1100UL - ( uint32_t ) elapsedTime ); + } + /* Establish an MQTT connection. */ if( IotMqtt_Connect( &_networkInfo, &connectInfo, @@ -487,6 +499,9 @@ TEST_SETUP( Shadow_System ) TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); } + /* Update the time of the last MQTT connect. */ + lastConnectTime = IotClock_GetTimeMs(); + /* Delete any existing Shadow so all tests start with no Shadow. */ status = AwsIotShadow_TimedDelete( _mqttConnection, AWS_IOT_TEST_SHADOW_THING_NAME, From c18d83ccbf405d16954c1c53c14623b988044d19 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 6 Jun 2019 14:53:20 -0700 Subject: [PATCH 167/844] Use unique identifiers in MQTT system tests (#451) --- doc/guide/developer/automated_tests.txt | 17 +----- doc/lib/mqtt.txt | 8 --- lib/include/aws_iot_shadow.h | 4 ++ scripts/ci_test_coverage.sh | 2 +- scripts/ci_test_mqtt.sh | 5 +- tests/mqtt/system/iot_tests_mqtt_system.c | 72 +++++++++++++++++------ 6 files changed, 64 insertions(+), 44 deletions(-) diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index 4673530124..2f94a3c90a 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -120,14 +120,7 @@ AWS recommends using the most restrictive policy possible. The following policy "Effect": "Allow", "Action": "iot:Connect", "Resource": [ - "arn:aws:iot:::client/CSDKCIcoverage", - "arn:aws:iot:::client/CSDKCIcoverageLWT", - "arn:aws:iot:::client/CSDKCImqtt", - "arn:aws:iot:::client/CSDKCImqttLWT", - "arn:aws:iot:::client/CSDKCImqttossl", - "arn:aws:iot:::client/CSDKCImqttosslLWT", - "arn:aws:iot:::client/CSDKCIshadow", - "arn:aws:iot:::client/CSDKCIdefender" + "arn:aws:iot:::client/*" ] }, { @@ -138,9 +131,7 @@ AWS recommends using the most restrictive policy possible. The following policy ], "Resource": [ "arn:aws:iot:::topic/${iot:ClientId}/*", - "arn:aws:iot:::topic/CSDKCIcoverage/LastWillAndTestament", - "arn:aws:iot:::topic/CSDKCImqtt/LastWillAndTestament", - "arn:aws:iot:::topic/CSDKCImqttossl/LastWillAndTestament", + "arn:aws:iot:::topic/*/LastWillAndTestament", "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/shadow/*" ] }, @@ -149,9 +140,7 @@ AWS recommends using the most restrictive policy possible. The following policy "Action": "iot:Subscribe", "Resource": [ "arn:aws:iot:::topicfilter/${iot:ClientId}/*", - "arn:aws:iot:::topicfilter/CSDKCIcoverage/LastWillAndTestament", - "arn:aws:iot:::topicfilter/CSDKCImqtt/LastWillAndTestament", - "arn:aws:iot:::topicfilter/CSDKCImqttossl/LastWillAndTestament", + "arn:aws:iot:::topicfilter/*/LastWillAndTestament", "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/shadow/*" ] } diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index fc35017a9f..90fd1405b1 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -153,14 +153,6 @@ This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait @configpossible Any non-negative integer.
@configrecommended This setting should be at least `1000`.
@configdefault `5000` - -@section IOT_TEST_MQTT_TOPIC_PREFIX -@brief The string prepended to topic filters. - -This string will be prepended as the common part of topic filters as a way to differentiate MQTT traffic generated by the tests. - -@configpossible Any string that adheres to the [specification for MQTT client identifiers](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349242).
-@configdefault `"iotmqtttest"` */ /** diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 8f63448fde..44ac0ee4f5 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -76,6 +76,10 @@ * Calling this function more than once without first calling @ref * shadow_function_cleanup may result in a crash. * + * @param[in] mqttTimeout The amount of time (in milliseconds) that the Shadow + * library will wait for MQTT operations. Optional; set this to `0` to use + * @ref AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS. + * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS * - #AWS_IOT_SHADOW_INIT_FAILED diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 3a6c675f43..b6d0e0ffa6 100644 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -7,7 +7,7 @@ set -ev # Build tests and demos against AWS IoT with coverage. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" make -j2 # Run common tests with code coverage. diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index ec6284b9f9..21722ad00d 100644 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -5,14 +5,15 @@ # Exit on any nonzero return code. set -e -CMAKE_FLAGS="-DIOT_TEST_MQTT_CLIENT_IDENTIFIER=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" " +CMAKE_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" " # For pull request builds, run against test.mosquitto.org. Otherwise, run against AWS IoT. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then CMAKE_FLAGS+="$AWS_IOT_CREDENTIAL_DEFINES $COMPILER_OPTIONS" + DEMO_OPTIONS="-i $IOT_IDENTIFIER" else CMAKE_FLAGS+="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" - DEMO_OPTIONS="-n" + DEMO_OPTIONS="-n -i $IOT_IDENTIFIER" fi # Build executables. diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 0654e591ef..4e10865a0c 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -56,10 +56,7 @@ * Provide default values of test configuration constants. */ #ifndef IOT_TEST_MQTT_TIMEOUT_MS - #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) -#endif -#ifndef IOT_TEST_MQTT_TOPIC_PREFIX - #define IOT_TEST_MQTT_TOPIC_PREFIX "iotmqtttest" + #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) #endif /** @endcond */ @@ -87,6 +84,21 @@ #define CLIENT_IDENTIFIER_MAX_LENGTH ( 24 ) #endif +/** + * @brief Generates a topic by suffixing the client identifier with a suffix. + * + * @param[in] bufferName The name of the buffer for the topic. + * @param[in] suffix The suffix to place at the end of the client identifier. + */ +#define GENERATE_TOPIC_WITH_SUFFIX( bufferName, suffix ) \ + char bufferName[ CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ) ] = { 0 }; \ + ( void ) snprintf( bufferName, \ + CLIENT_IDENTIFIER_MAX_LENGTH + sizeof( suffix ), \ + "%s%s", \ + _pClientIdentifier, \ + suffix ); + + /*-----------------------------------------------------------*/ /** @@ -380,8 +392,8 @@ static void _reentrantCallback( void * pArgument, IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; - /* Topic used in this test. */ - const char * const pTopic = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); const uint16_t topicLength = ( uint16_t ) strlen( pTopic ); publishInfo.qos = IOT_MQTT_QOS_1; @@ -480,6 +492,9 @@ static void _subscribePublishWait( IotMqttQos_t qos ) /* Set the serializer function pointers. */ networkInfo.pMqttSerializer = &serializer; + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/SubscribePublishWait" ); + /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -502,7 +517,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) { /* Set the members of the subscription. */ subscription.qos = qos; - subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; + subscription.pTopicFilter = pTopic; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; subscription.callback.pCallbackContext = &waitSem; @@ -518,7 +533,7 @@ static void _subscribePublishWait( IotMqttQos_t qos ) /* Set the members of the publish info. */ publishInfo.qos = qos; - publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishWait"; + publishInfo.pTopicName = pTopic; publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; @@ -711,12 +726,15 @@ TEST( MQTT_System, SubscribePublishAsync ) _operationCompleteParams_t callbackParam = { .expectedOperation = ( IotMqttOperationType_t ) 0 }; IotSemaphore_t publishWaitSem; + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/SubscribePublishAsync" ); + /* Initialize members of the operation callback info. */ callbackInfo.function = _operationComplete; callbackInfo.pCallbackContext = &callbackParam; /* Initialize members of the subscription. */ - subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; + subscription.pTopicFilter = pTopic; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; subscription.callback.pCallbackContext = &publishWaitSem; @@ -729,7 +747,7 @@ TEST( MQTT_System, SubscribePublishAsync ) /* Initialize members of the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/SubscribePublishAsync"; + publishInfo.pTopicName = pTopic; publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; @@ -831,6 +849,9 @@ TEST( MQTT_System, LastWillAndTestament ) IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotSemaphore_t waitSem; + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/LastWillAndTestament" ); + /* Create the wait semaphore. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -873,7 +894,7 @@ TEST( MQTT_System, LastWillAndTestament ) if( TEST_PROTECT() ) { /* Register a subscription for the LWT. */ - willSubscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willSubscription.pTopicFilter = pTopic; willSubscription.topicFilterLength = ( uint16_t ) strlen( willSubscription.pTopicFilter ); willSubscription.callback.function = _publishReceived; willSubscription.callback.pCallbackContext = &waitSem; @@ -892,7 +913,7 @@ TEST( MQTT_System, LastWillAndTestament ) connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); connectInfo.pWillInfo = &willInfo; - willInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/LastWillAndTestament"; + willInfo.pTopicName = pTopic; willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); willInfo.pPayload = _pSamplePayload; willInfo.payloadLength = _samplePayloadLength; @@ -934,6 +955,9 @@ TEST( MQTT_System, RestorePreviousSession ) IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; IotSemaphore_t waitSem; + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/RestorePreviousSession" ); + /* Create the wait semaphore for operations. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &waitSem, 0, 1 ) ); @@ -952,7 +976,7 @@ TEST( MQTT_System, RestorePreviousSession ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Add a subscription. */ - subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; + subscription.pTopicFilter = pTopic; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.pCallbackContext = &waitSem; subscription.callback.function = _publishReceived; @@ -980,7 +1004,7 @@ TEST( MQTT_System, RestorePreviousSession ) TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ - publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/RestorePreviousSession"; + publishInfo.pTopicName = pTopic; publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; @@ -1049,9 +1073,12 @@ TEST( MQTT_System, WaitAfterDisconnect ) connectInfo.pClientIdentifier = _pClientIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/WaitAfterDisconnect" ); + /* Set the members of the publish info. */ publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/WaitAfterDisconnect"; + publishInfo.pTopicName = pTopic; publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; @@ -1111,6 +1138,9 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) * for test completion. */ IotSemaphore_t pWaitSemaphores[ 2 ]; + /* The topic to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic, "/Reentrancy" ); + /* Create the semaphores. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); @@ -1135,7 +1165,7 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) /* Subscribe with a completion callback. */ subscription.qos = IOT_MQTT_QOS_1; - subscription.pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; + subscription.pTopicFilter = pTopic; subscription.topicFilterLength = ( uint16_t ) strlen( subscription.pTopicFilter ); subscription.callback.function = _publishReceived; subscription.callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); @@ -1183,6 +1213,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) * for test completion. */ IotSemaphore_t pWaitSemaphores[ 2 ]; + /* The topics to use for this test. */ + GENERATE_TOPIC_WITH_SUFFIX( pTopic1, "/IncomingPublishReentrancy" ); + GENERATE_TOPIC_WITH_SUFFIX( pTopic2, "/Reentrancy" ); + /* Create the semaphores. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( pWaitSemaphores[ 0 ] ), 0, 1 ) ); @@ -1207,13 +1241,13 @@ TEST( MQTT_System, IncomingPublishReentrancy ) /* Subscribe with to the test topics. */ pSubscription[ 0 ].qos = IOT_MQTT_QOS_1; - pSubscription[ 0 ].pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/IncomingPublishReentrancy"; + pSubscription[ 0 ].pTopicFilter = pTopic1; pSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 0 ].pTopicFilter ); pSubscription[ 0 ].callback.function = _reentrantCallback; pSubscription[ 0 ].callback.pCallbackContext = pWaitSemaphores; pSubscription[ 1 ].qos = IOT_MQTT_QOS_1; - pSubscription[ 1 ].pTopicFilter = IOT_TEST_MQTT_TOPIC_PREFIX "/Reentrancy"; + pSubscription[ 1 ].pTopicFilter = pTopic2; pSubscription[ 1 ].topicFilterLength = ( uint16_t ) strlen( pSubscription[ 1 ].pTopicFilter ); pSubscription[ 1 ].callback.function = _publishReceived; pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); @@ -1227,7 +1261,7 @@ TEST( MQTT_System, IncomingPublishReentrancy ) /* Publish a message to the test topic. */ publishInfo.qos = IOT_MQTT_QOS_1; - publishInfo.pTopicName = IOT_TEST_MQTT_TOPIC_PREFIX "/IncomingPublishReentrancy"; + publishInfo.pTopicName = pTopic1; publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; From 13e42867c54ed98fa106a243b550b7765a84a6ad Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 11 Jun 2019 11:09:00 -0700 Subject: [PATCH 168/844] Refactor some Shadow files for coding standard (#452) --- demos/source/aws_iot_demo_shadow.c | 5 +- lib/include/private/aws_iot_shadow_internal.h | 20 +- lib/source/shadow/aws_iot_shadow_api.c | 467 ++++++++++-------- lib/source/shadow/aws_iot_shadow_operation.c | 32 +- lib/source/shadow/aws_iot_shadow_parser.c | 74 +-- .../shadow/aws_iot_shadow_subscription.c | 255 +++++----- tests/shadow/unit/aws_iot_tests_shadow_api.c | 2 +- .../shadow/unit/aws_iot_tests_shadow_parser.c | 14 +- 8 files changed, 479 insertions(+), 390 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 3545a9365f..75d3aeaef0 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -341,9 +341,10 @@ static void _shadowDeltaCallback( void * pCallbackContext, if( updateStatus != AWS_IOT_SHADOW_STATUS_PENDING ) { - IotLogWarn( "%.*s failed to report new state.", + IotLogWarn( "%.*s failed to report new state, error %s.", pCallbackParam->thingNameLength, - pCallbackParam->pThingName ); + pCallbackParam->pThingName, + updateStatus ); } else { diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index ce757b919b..94396fe6f8 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -327,13 +327,13 @@ typedef void ( * _mqttCallbackFunction_t )( void *, typedef enum _shadowOperationType { /* Shadow operations. */ - _SHADOW_DELETE = 0, /**< @ref shadow_function_delete */ - _SHADOW_GET = 1, /**< @ref shadow_function_get */ - _SHADOW_UPDATE = 2, /**< @ref shadow_function_update */ + SHADOW_DELETE = 0, /**< @ref shadow_function_delete */ + SHADOW_GET = 1, /**< @ref shadow_function_get */ + SHADOW_UPDATE = 2, /**< @ref shadow_function_update */ /* Shadow callbacks. */ - _SET_DELTA_CALLBACK = 3, /**< @ref shadow_function_setdeltacallback */ - _SET_UPDATED_CALLBACK = 4 /**< @ref shadow_function_setupdatedcallback */ + SET_DELTA_CALLBACK = 3, /**< @ref shadow_function_setdeltacallback */ + SET_UPDATED_CALLBACK = 4 /**< @ref shadow_function_setupdatedcallback */ } _shadowOperationType_t; /** @@ -341,8 +341,8 @@ typedef enum _shadowOperationType */ typedef enum _shadowCallbackType { - _DELTA_CALLBACK = 0, /**< Delta callback. */ - _UPDATED_CALLBACK = 1 /**< Updated callback. */ + DELTA_CALLBACK = 0, /**< Delta callback. */ + UPDATED_CALLBACK = 1 /**< Updated callback. */ } _shadowCallbackType_t; /** @@ -351,9 +351,9 @@ typedef enum _shadowCallbackType */ typedef enum _shadowOperationStatus { - _SHADOW_ACCEPTED = 0, /**< Shadow operation accepted. */ - _SHADOW_REJECTED = 1, /**< Shadow operation rejected. */ - _UNKNOWN_STATUS = 2 /**< Parsed value matched neither accepted nor rejected. */ + SHADOW_ACCEPTED = 0, /**< Shadow operation accepted. */ + SHADOW_REJECTED = 1, /**< Shadow operation rejected. */ + UNKNOWN_STATUS = 2 /**< Parsed value matched neither accepted nor rejected. */ } _shadowOperationStatus_t; /** diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index b8edcf8882..1e452e3dc7 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -33,6 +33,9 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -175,6 +178,8 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, const AwsIotShadowCallbackInfo_t * pCallbackInfo, const AwsIotShadowOperation_t * pOperation ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + /* Type is not used when logging is disabled. */ ( void ) type; @@ -184,7 +189,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, IotLogError( "Thing name for Shadow %s cannot be NULL or have length 0.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } if( thingNameLength > MAX_THING_NAME_LENGTH ) @@ -194,7 +199,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, ( unsigned long ) thingNameLength, MAX_THING_NAME_LENGTH ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* Check the waitable operation flag. */ @@ -206,7 +211,7 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, IotLogError( "Reference must be set for a waitable Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* A callback should not be set for a waitable operation. */ @@ -215,18 +220,18 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, IotLogError( "Callback should not be set for a waitable Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } } /* A callback info must be passed to a non-waitable GET. */ - if( ( type == _SHADOW_GET ) && + if( ( type == SHADOW_GET ) && ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) && ( pCallbackInfo == NULL ) ) { IotLogError( "Callback info must be provided for non-waitable Shadow GET." ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* Check that a callback function is set. */ @@ -236,10 +241,10 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, IotLogError( "Callback function must be set for Shadow %s callback.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } - return AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -248,8 +253,10 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, uint32_t flags, const AwsIotShadowDocumentInfo_t * pDocumentInfo ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + /* This function should only be called for Shadow GET or UPDATE. */ - AwsIotShadow_Assert( ( type == _SHADOW_GET ) || ( type == _SHADOW_UPDATE ) ); + AwsIotShadow_Assert( ( type == SHADOW_GET ) || ( type == SHADOW_UPDATE ) ); /* Check QoS. */ if( pDocumentInfo->qos != IOT_MQTT_QOS_0 ) @@ -259,7 +266,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, IotLogError( "QoS for Shadow %d must be 0 or 1.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } } @@ -271,12 +278,12 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, IotLogError( "Retry time of Shadow %s must be positive.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } } /* Check members relevant to a Shadow GET. */ - if( type == _SHADOW_GET ) + if( type == SHADOW_GET ) { /* Check memory allocation function for waitable GET. */ if( ( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) && @@ -284,7 +291,7 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, { IotLogError( "Memory allocation function must be set for waitable Shadow GET." ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } } /* Check members relevant to a Shadow UPDATE. */ @@ -297,11 +304,11 @@ static AwsIotShadowError_t _validateDocumentInfo( _shadowOperationType_t type, IotLogError( "Shadow document for Shadow UPDATE cannot be NULL or" " have length 0." ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } } - return AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -312,18 +319,21 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio size_t thingNameLength, const AwsIotShadowCallbackInfo_t * pCallbackInfo ) { - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + bool subscriptionMutexLocked = false; _shadowSubscription_t * pSubscription = NULL; /* Check parameters. */ - if( _validateThingNameFlags( ( _shadowOperationType_t ) ( type + SHADOW_OPERATION_COUNT ), - pThingName, - thingNameLength, - 0, - pCallbackInfo, - NULL ) != AWS_IOT_SHADOW_SUCCESS ) + status = _validateThingNameFlags( ( _shadowOperationType_t ) ( type + SHADOW_OPERATION_COUNT ), + pThingName, + thingNameLength, + 0, + pCallbackInfo, + NULL ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_GOTO_CLEANUP(); } IotLogInfo( "(%.*s) Modifying Shadow %s callback.", @@ -334,6 +344,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio /* Lock the subscription list mutex to check for an existing subscription * object. */ IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + subscriptionMutexLocked = true; /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ @@ -344,70 +355,73 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio { /* No existing subscription was found, and no new subscription could be * allocated. */ - status = AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } + + /* Check for an existing callback. */ + if( pSubscription->callbacks[ type ].function != NULL ) + { + /* Replace existing callback. */ + if( pCallbackInfo != NULL ) + { + IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + + pSubscription->callbacks[ type ] = *pCallbackInfo; + } + /* Remove existing callback. */ + else + { + IotLogInfo( "(%.*s) Removing existing %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + + /* Unsubscribe, then clear the callback information. */ + ( void ) _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_TimedUnsubscribe ); + ( void ) memset( &( pSubscription->callbacks[ type ] ), + 0x00, + sizeof( AwsIotShadowCallbackInfo_t ) ); + + /* Check if this subscription object can be removed. */ + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); + } + } + /* No existing callback. */ else { - /* Check for an existing callback. */ - if( pSubscription->callbacks[ type ].function != NULL ) + /* Add new callback. */ + if( pCallbackInfo != NULL ) { - /* Replace existing callback. */ - if( pCallbackInfo != NULL ) - { - IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - pSubscription->callbacks[ type ] = *pCallbackInfo; - } - /* Remove existing callback. */ - else - { - IotLogInfo( "(%.*s) Removing existing %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - /* Unsubscribe, then clear the callback information. */ - ( void ) _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_TimedUnsubscribe ); - ( void ) memset( &( pSubscription->callbacks[ type ] ), - 0x00, - sizeof( AwsIotShadowCallbackInfo_t ) ); - - /* Check if this subscription object can be removed. */ - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); - } + IotLogInfo( "(%.*s) Adding new %s callback.", + thingNameLength, + pThingName, + _pAwsIotShadowCallbackNames[ type ] ); + + pSubscription->callbacks[ type ] = *pCallbackInfo; + status = _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_TimedSubscribe ); } - /* No existing callback. */ + /* Do nothing; set return value to success. */ else { - /* Add new callback. */ - if( pCallbackInfo != NULL ) - { - IotLogInfo( "(%.*s) Adding new %s callback.", - thingNameLength, - pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - - pSubscription->callbacks[ type ] = *pCallbackInfo; - status = _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_TimedSubscribe ); - } - /* Do nothing; set return value to success. */ - else - { - status = AWS_IOT_SHADOW_SUCCESS; - } + status = AWS_IOT_SHADOW_SUCCESS; } } - IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( subscriptionMutexLocked == true ) + { + IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + } IotLogInfo( "(%.*s) Shadow %s callback operation complete with result %s.", thingNameLength, @@ -415,7 +429,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio _pAwsIotShadowCallbackNames[ type ], AwsIotShadow_strerror( status ) ); - return status; + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -425,7 +439,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt _shadowSubscription_t * pSubscription, _mqttOperationFunction_t mqttOperation ) { - AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; char * pTopicFilter = NULL; @@ -464,13 +478,15 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt /* Generate the prefix portion of the Shadow callback topic filter. Both * callbacks share the same callback as the Shadow Update operation. */ - if( _AwsIotShadow_GenerateShadowTopic( _SHADOW_UPDATE, - pSubscription->pThingName, - pSubscription->thingNameLength, - &pTopicFilter, - &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) + status = _AwsIotShadow_GenerateShadowTopic( SHADOW_UPDATE, + pSubscription->pThingName, + pSubscription->thingNameLength, + &pTopicFilter, + &operationTopicLength ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_GOTO_CLEANUP(); } /* Place the callback suffix in the topic filter. */ @@ -510,21 +526,19 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ if( mqttStatus == IOT_MQTT_NO_MEMORY ) { - status = AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } else { - status = AWS_IOT_SHADOW_MQTT_ERROR; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_MQTT_ERROR ); } } - else - { - IotLogDebug( "Successfully %s %.*s Shadow %s callback.", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowCallbackNames[ type ] ); - } + + IotLogDebug( "Successfully %s %.*s Shadow %s callback.", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowCallbackNames[ type ] ); /* MQTT subscribe should check the subscription topic buffer. */ if( mqttOperation == IotMqtt_TimedSubscribe ) @@ -537,7 +551,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt } } - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -571,7 +585,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, static void _deltaCallbackWrapper( void * pArgument, IotMqttCallbackParam_t * pMessage ) { - _callbackWrapperCommon( _DELTA_CALLBACK, pArgument, pMessage ); + _callbackWrapperCommon( DELTA_CALLBACK, pArgument, pMessage ); } /*-----------------------------------------------------------*/ @@ -579,28 +593,36 @@ static void _deltaCallbackWrapper( void * pArgument, static void _updatedCallbackWrapper( void * pArgument, IotMqttCallbackParam_t * pMessage ) { - _callbackWrapperCommon( _UPDATED_CALLBACK, pArgument, pMessage ); + _callbackWrapperCommon( UPDATED_CALLBACK, pArgument, pMessage ); } /*-----------------------------------------------------------*/ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); + + /* Flags to track cleanup. */ + bool pendingOperationsMutexCreated = false, subscriptionsMutexCreated = false; + /* Create the Shadow pending operation list mutex. */ - if( IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ), false ) == false ) + pendingOperationsMutexCreated = IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ), false ); + + if( pendingOperationsMutexCreated == false ) { IotLogError( "Failed to create Shadow pending operation list." ); - return AWS_IOT_SHADOW_INIT_FAILED; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_INIT_FAILED ); } /* Create the Shadow subscription list mutex. */ - if( IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ), false ) == false ) + subscriptionsMutexCreated = IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ), false ); + + if( subscriptionsMutexCreated == false ) { IotLogError( "Failed to create Shadow subscription list." ); - IotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); - return AWS_IOT_SHADOW_INIT_FAILED; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_INIT_FAILED ); } /* Create Shadow linear containers. */ @@ -613,9 +635,22 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; } - IotLogInfo( "Shadow library successfully initialized." ); + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status != AWS_IOT_SHADOW_SUCCESS ) + { + if( pendingOperationsMutexCreated == true ) + { + IotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); + } + } + else + { + IotLogInfo( "Shadow library successfully initialized." ); + } - return AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -655,34 +690,38 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, const AwsIotShadowCallbackInfo_t * pCallbackInfo, AwsIotShadowOperation_t * const pDeleteOperation ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; /* Validate the Thing Name and flags for Shadow DELETE. */ - if( _validateThingNameFlags( _SHADOW_DELETE, - pThingName, - thingNameLength, - flags, - pCallbackInfo, - pDeleteOperation ) != AWS_IOT_SHADOW_SUCCESS ) + status = _validateThingNameFlags( SHADOW_DELETE, + pThingName, + thingNameLength, + flags, + pCallbackInfo, + pDeleteOperation ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* The Thing Name or some flag was invalid. */ - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_GOTO_CLEANUP(); } /* Allocate a new Shadow operation for DELETE. */ - if( _AwsIotShadow_CreateOperation( &pOperation, - _SHADOW_DELETE, - flags, - pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + status = _AwsIotShadow_CreateOperation( &pOperation, + SHADOW_DELETE, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* No memory for a new Shadow operation. */ - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_GOTO_CLEANUP(); } /* Check the members set by Shadow operation creation. */ AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == _SHADOW_DELETE ); + AwsIotShadow_Assert( pOperation->type == SHADOW_DELETE ); AwsIotShadow_Assert( pOperation->flags == flags ); AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); @@ -707,7 +746,7 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, *pDeleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; } - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -752,43 +791,49 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, const AwsIotShadowCallbackInfo_t * pCallbackInfo, AwsIotShadowOperation_t * const pGetOperation ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; /* Validate the Thing Name and flags for Shadow GET. */ - if( _validateThingNameFlags( _SHADOW_GET, - pGetInfo->pThingName, - pGetInfo->thingNameLength, - flags, - pCallbackInfo, - pGetOperation ) != AWS_IOT_SHADOW_SUCCESS ) + status = _validateThingNameFlags( SHADOW_GET, + pGetInfo->pThingName, + pGetInfo->thingNameLength, + flags, + pCallbackInfo, + pGetOperation ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* The Thing Name or some flag was invalid. */ - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_GOTO_CLEANUP(); } /* Validate the document info for Shadow GET. */ - if( _validateDocumentInfo( _SHADOW_GET, - flags, - pGetInfo ) != AWS_IOT_SHADOW_SUCCESS ) + status = _validateDocumentInfo( SHADOW_GET, + flags, + pGetInfo ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* Document info was invalid. */ - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_GOTO_CLEANUP(); } /* Allocate a new Shadow operation for GET. */ - if( _AwsIotShadow_CreateOperation( &pOperation, - _SHADOW_GET, - flags, - pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + status = _AwsIotShadow_CreateOperation( &pOperation, + SHADOW_GET, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* No memory for a new Shadow operation. */ - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_GOTO_CLEANUP(); } /* Check the members set by Shadow operation creation. */ AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == _SHADOW_GET ); + AwsIotShadow_Assert( pOperation->type == SHADOW_GET ); AwsIotShadow_Assert( pOperation->flags == flags ); AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); @@ -816,7 +861,7 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, *pGetOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; } - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -864,30 +909,34 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, const AwsIotShadowCallbackInfo_t * pCallbackInfo, AwsIotShadowOperation_t * const pUpdateOperation ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; const char * pClientToken = NULL; size_t clientTokenLength = 0; /* Validate the Thing Name and flags for Shadow UPDATE. */ - if( _validateThingNameFlags( _SHADOW_UPDATE, - pUpdateInfo->pThingName, - pUpdateInfo->thingNameLength, - flags, - pCallbackInfo, - pUpdateOperation ) != AWS_IOT_SHADOW_SUCCESS ) + status = _validateThingNameFlags( SHADOW_UPDATE, + pUpdateInfo->pThingName, + pUpdateInfo->thingNameLength, + flags, + pCallbackInfo, + pUpdateOperation ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* The Thing Name or some flag was invalid. */ - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_GOTO_CLEANUP(); } /* Validate the document info for Shadow UPDATE. */ - if( _validateDocumentInfo( _SHADOW_UPDATE, - flags, - pUpdateInfo ) != AWS_IOT_SHADOW_SUCCESS ) + status = _validateDocumentInfo( SHADOW_UPDATE, + flags, + pUpdateInfo ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* Document info was invalid. */ - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_GOTO_CLEANUP(); } /* Check UPDATE document for a client token. */ @@ -901,7 +950,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, IotLogError( "Shadow document for Shadow UPDATE must have a %s key.", CLIENT_TOKEN_KEY ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* Check the client token length. It must be greater than the length of its @@ -912,22 +961,24 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, IotLogError( "Client token length must be between 2 and %d (including " "enclosing quotes).", MAX_CLIENT_TOKEN_LENGTH + 2 ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* Allocate a new Shadow operation for UPDATE. */ - if( _AwsIotShadow_CreateOperation( &pOperation, - _SHADOW_UPDATE, - flags, - pCallbackInfo ) != AWS_IOT_SHADOW_SUCCESS ) + status = _AwsIotShadow_CreateOperation( &pOperation, + SHADOW_UPDATE, + flags, + pCallbackInfo ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { /* No memory for a new Shadow operation. */ - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_GOTO_CLEANUP(); } /* Check the members set by Shadow operation creation. */ AwsIotShadow_Assert( pOperation != NULL ); - AwsIotShadow_Assert( pOperation->type == _SHADOW_UPDATE ); + AwsIotShadow_Assert( pOperation->type == SHADOW_UPDATE ); AwsIotShadow_Assert( pOperation->flags == flags ); AwsIotShadow_Assert( pOperation->status == AWS_IOT_SHADOW_STATUS_PENDING ); @@ -939,7 +990,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, IotLogError( "Failed to allocate memory for Shadow update client token." ); _AwsIotShadow_DestroyOperation( pOperation ); - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } /* Copy the client token. The client token must be copied in case the application @@ -970,7 +1021,7 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, *pUpdateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; } - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1012,14 +1063,14 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, const char ** const pShadowDocument, size_t * const pShadowDocumentLength ) { - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); /* Check that reference is set. */ if( operation == NULL ) { IotLogError( "Operation reference cannot be NULL." ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* Check that reference is waitable. */ @@ -1027,17 +1078,17 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, { IotLogError( "Operation is not waitable." ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } /* Check that output parameters are set for a Shadow GET. */ - if( operation->type == _SHADOW_GET ) + if( operation->type == SHADOW_GET ) { if( ( pShadowDocument == NULL ) || ( pShadowDocumentLength == NULL ) ) { IotLogError( "Output buffer and size pointer must be set for Shadow GET." ); - return AWS_IOT_SHADOW_BAD_PARAMETER; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } } @@ -1066,7 +1117,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); /* Set the output parameters for Shadow GET. */ - if( ( operation->type == _SHADOW_GET ) && + if( ( operation->type == SHADOW_GET ) && ( status == AWS_IOT_SHADOW_SUCCESS ) ) { *pShadowDocument = operation->u.get.pDocument; @@ -1076,7 +1127,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, /* Destroy the Shadow operation. */ _AwsIotShadow_DestroyOperation( operation ); - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -1091,7 +1142,7 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConne ( void ) flags; return _setCallbackCommon( mqttConnection, - _DELTA_CALLBACK, + DELTA_CALLBACK, pThingName, thingNameLength, pDeltaCallback ); @@ -1109,7 +1160,7 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon ( void ) flags; return _setCallbackCommon( mqttConnection, - _UPDATED_CALLBACK, + UPDATED_CALLBACK, pThingName, thingNameLength, pUpdatedCallback ); @@ -1119,80 +1170,84 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon const char * AwsIotShadow_strerror( AwsIotShadowError_t status ) { + const char * pMessage = NULL; + switch( status ) { case AWS_IOT_SHADOW_SUCCESS: - - return "SUCCESS"; + pMessage = "SUCCESS"; + break; case AWS_IOT_SHADOW_STATUS_PENDING: - - return "STATUS PENDING"; + pMessage = "STATUS PENDING"; + break; case AWS_IOT_SHADOW_INIT_FAILED: - - return "INITIALIZATION FAILED"; + pMessage = "INITIALIZATION FAILED"; + break; case AWS_IOT_SHADOW_BAD_PARAMETER: - - return "BAD PARAMETER"; + pMessage = "BAD PARAMETER"; + break; case AWS_IOT_SHADOW_NO_MEMORY: - - return "NO MEMORY"; + pMessage = "NO MEMORY"; + break; case AWS_IOT_SHADOW_MQTT_ERROR: - - return "MQTT LIBRARY ERROR"; + pMessage = "MQTT LIBRARY ERROR"; + break; case AWS_IOT_SHADOW_BAD_RESPONSE: - - return "BAD RESPONSE RECEIVED"; + pMessage = "BAD RESPONSE RECEIVED"; + break; case AWS_IOT_SHADOW_TIMEOUT: - - return "TIMEOUT"; + pMessage = "TIMEOUT"; + break; case AWS_IOT_SHADOW_BAD_REQUEST: - - return "REJECTED: 400 BAD REQUEST"; + pMessage = "REJECTED: 400 BAD REQUEST"; + break; case AWS_IOT_SHADOW_UNAUTHORIZED: - - return "REJECTED: 401 UNAUTHORIZED"; + pMessage = "REJECTED: 401 UNAUTHORIZED"; + break; case AWS_IOT_SHADOW_FORBIDDEN: - - return "REJECTED: 403 FORBIDDEN"; + pMessage = "REJECTED: 403 FORBIDDEN"; + break; case AWS_IOT_SHADOW_NOT_FOUND: - - return "REJECTED: 404 NOT FOUND"; + pMessage = "REJECTED: 404 NOT FOUND"; + break; case AWS_IOT_SHADOW_CONFLICT: - - return "REJECTED: 409 VERSION CONFLICT"; + pMessage = "REJECTED: 409 VERSION CONFLICT"; + break; case AWS_IOT_SHADOW_TOO_LARGE: - - return "REJECTED: 413 PAYLOAD TOO LARGE"; + pMessage = "REJECTED: 413 PAYLOAD TOO LARGE"; + break; case AWS_IOT_SHADOW_UNSUPPORTED: - - return "REJECTED: 415 UNSUPPORTED ENCODING"; + pMessage = "REJECTED: 415 UNSUPPORTED ENCODING"; + break; case AWS_IOT_SHADOW_TOO_MANY_REQUESTS: - - return "REJECTED: 429 TOO MANY REQUESTS"; + pMessage = "REJECTED: 429 TOO MANY REQUESTS"; + break; case AWS_IOT_SHADOW_SERVER_ERROR: - - return "500 SERVER ERROR"; + pMessage = "500 SERVER ERROR"; + break; default: - - return "INVALID STATUS"; + pMessage = "INVALID STATUS"; + break; } + + return pMessage; } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index e08101c4ea..52ee8b9a4b 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -175,7 +175,7 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, pParam->thingNameLength ) == 0 ); /* For a Shadow UPDATE operation, compare the client tokens. */ - if( ( match == true ) && ( pOperation->type == _SHADOW_UPDATE ) ) + if( ( match == true ) && ( pOperation->type == SHADOW_UPDATE ) ) { /* Check document pointers. */ AwsIotShadow_Assert( pParam->pDocument != NULL ); @@ -221,7 +221,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, { _shadowOperation_t * pOperation = NULL; IotLink_t * pOperationLink = NULL; - _shadowOperationStatus_t status = _UNKNOWN_STATUS; + _shadowOperationStatus_t status = UNKNOWN_STATUS; _operationMatchParams_t param = { .type = ( _shadowOperationType_t ) 0 }; uint32_t flags = 0; @@ -229,7 +229,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, param.type = type; /* Set the response document for a Shadow UPDATE. */ - if( type == _SHADOW_UPDATE ) + if( type == SHADOW_UPDATE ) { param.pDocument = pMessage->u.message.info.pPayload; param.documentLength = pMessage->u.message.info.payloadLength; @@ -291,7 +291,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, switch( status ) { - case _SHADOW_ACCEPTED: + case SHADOW_ACCEPTED: IotLogInfo( "Shadow %s of %.*s was ACCEPTED.", _pAwsIotShadowOperationNames[ type ], pOperation->pSubscription->thingNameLength, @@ -299,7 +299,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, /* Process the retrieved document for a Shadow GET. Otherwise, set * status to success. */ - if( type == _SHADOW_GET ) + if( type == SHADOW_GET ) { pOperation->status = _processAcceptedGet( pOperation, &( pMessage->u.message.info ) ); @@ -311,7 +311,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, break; - case _SHADOW_REJECTED: + case SHADOW_REJECTED: IotLogWarn( "Shadow %s of %.*s was REJECTED.", _pAwsIotShadowOperationNames[ type ], pOperation->pSubscription->thingNameLength, @@ -353,7 +353,7 @@ static void _deleteCallback( void * pArgument, /* Silence warnings about unused parameter. */ ( void ) pArgument; - _commonOperationCallback( _SHADOW_DELETE, pMessage ); + _commonOperationCallback( SHADOW_DELETE, pMessage ); } /*-----------------------------------------------------------*/ @@ -364,7 +364,7 @@ static void _getCallback( void * pArgument, /* Silence warnings about unused parameter. */ ( void ) pArgument; - _commonOperationCallback( _SHADOW_GET, pMessage ); + _commonOperationCallback( SHADOW_GET, pMessage ); } /*-----------------------------------------------------------*/ @@ -421,7 +421,7 @@ static void _updateCallback( void * pArgument, /* Silence warnings about unused parameter. */ ( void ) pArgument; - _commonOperationCallback( _SHADOW_UPDATE, pMessage ); + _commonOperationCallback( SHADOW_UPDATE, pMessage ); } /*-----------------------------------------------------------*/ @@ -505,7 +505,7 @@ void _AwsIotShadow_DestroyOperation( void * pData ) } /* If this is a Shadow update, free any allocated client token. */ - if( ( pOperation->type == _SHADOW_UPDATE ) && + if( ( pOperation->type == SHADOW_UPDATE ) && ( pOperation->u.update.pClientToken != NULL ) ) { AwsIotShadow_Assert( pOperation->u.update.clientTokenLength > 0 ); @@ -547,9 +547,9 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty /* Only Shadow delete, get, and update operation types should be passed to this * function. */ - AwsIotShadow_Assert( ( type == _SHADOW_DELETE ) || - ( type == _SHADOW_GET ) || - ( type == _SHADOW_UPDATE ) ); + AwsIotShadow_Assert( ( type == SHADOW_DELETE ) || + ( type == SHADOW_GET ) || + ( type == SHADOW_UPDATE ) ); /* Calculate the required topic buffer length. */ bufferLength = ( uint16_t ) ( SHADOW_TOPIC_PREFIX_LENGTH + @@ -716,7 +716,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn publishInfo.pTopicName ); /* Set the document info if this operation is not a Shadow DELETE. */ - if( pOperation->type != _SHADOW_DELETE ) + if( pOperation->type != SHADOW_DELETE ) { publishInfo.qos = pDocumentInfo->qos; publishInfo.retryLimit = pDocumentInfo->retryLimit; @@ -731,7 +731,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn } /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ - if( pOperation->type == _SHADOW_UPDATE ) + if( pOperation->type == SHADOW_UPDATE ) { publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; @@ -860,7 +860,7 @@ void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) callbackParam.thingNameLength = pSubscription->thingNameLength; /* Set the members of the callback parameter for a received document. */ - if( pOperation->type == _SHADOW_GET ) + if( pOperation->type == SHADOW_GET ) { callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index eb4f17d5ac..29d19a18db 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -34,6 +34,9 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* JSON utilities include. */ #include "iot_json_utils.h" @@ -86,49 +89,53 @@ static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ); static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ) { + AwsIotShadowError_t errorCode = AWS_IOT_SHADOW_STATUS_PENDING; + /* Convert the Shadow response code to an AwsIotShadowError_t. */ switch( code ) { case 400UL: - - return AWS_IOT_SHADOW_BAD_REQUEST; + errorCode = AWS_IOT_SHADOW_BAD_REQUEST; + break; case 401UL: - - return AWS_IOT_SHADOW_UNAUTHORIZED; + errorCode = AWS_IOT_SHADOW_UNAUTHORIZED; + break; case 403UL: - - return AWS_IOT_SHADOW_FORBIDDEN; + errorCode = AWS_IOT_SHADOW_FORBIDDEN; + break; case 404UL: - - return AWS_IOT_SHADOW_NOT_FOUND; + errorCode = AWS_IOT_SHADOW_NOT_FOUND; + break; case 409UL: - - return AWS_IOT_SHADOW_CONFLICT; + errorCode = AWS_IOT_SHADOW_CONFLICT; + break; case 413UL: - - return AWS_IOT_SHADOW_TOO_LARGE; + errorCode = AWS_IOT_SHADOW_TOO_LARGE; + break; case 415UL: - - return AWS_IOT_SHADOW_UNSUPPORTED; + errorCode = AWS_IOT_SHADOW_UNSUPPORTED; + break; case 429UL: - - return AWS_IOT_SHADOW_TOO_MANY_REQUESTS; + errorCode = AWS_IOT_SHADOW_TOO_MANY_REQUESTS; + break; case 500UL: - - return AWS_IOT_SHADOW_SERVER_ERROR; + errorCode = AWS_IOT_SHADOW_SERVER_ERROR; + break; default: - - return AWS_IOT_SHADOW_BAD_RESPONSE; + errorCode = AWS_IOT_SHADOW_BAD_RESPONSE; + break; } + + return errorCode; } /*-----------------------------------------------------------*/ @@ -136,6 +143,7 @@ static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ) _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicName, size_t topicNameLength ) { + IOT_FUNCTION_ENTRY( _shadowOperationStatus_t, UNKNOWN_STATUS ); const char * pSuffixStart = NULL; /* Check that the Shadow status topic name is at least as long as the @@ -154,7 +162,7 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam SHADOW_ACCEPTED_SUFFIX, SHADOW_ACCEPTED_SUFFIX_LENGTH ) == 0 ) { - return _SHADOW_ACCEPTED; + IOT_SET_AND_GOTO_CLEANUP( SHADOW_ACCEPTED ); } } @@ -174,12 +182,11 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam SHADOW_REJECTED_SUFFIX, SHADOW_REJECTED_SUFFIX_LENGTH ) == 0 ) { - return _SHADOW_REJECTED; + IOT_SET_AND_GOTO_CLEANUP( SHADOW_REJECTED ); } } - /* The topic name matched neither "accepted" nor "rejected". */ - return _UNKNOWN_STATUS; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -187,6 +194,7 @@ _shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicNam AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); const char * pCode = NULL, * pMessage = NULL; size_t codeLength = 0, messageLength = 0; uint32_t code = 0; @@ -204,7 +212,7 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen errorDocumentLength, pErrorDocument ); - return AWS_IOT_SHADOW_BAD_RESPONSE; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); } /* Code must be in error document. */ @@ -236,10 +244,13 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen pErrorDocument ); /* An error document must contain a message; if it does not, then it is invalid. */ - return AWS_IOT_SHADOW_BAD_RESPONSE; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); } - return _codeToShadowStatus( code ); + /* Convert a successfully parsed JSON code to a Shadow status. */ + status = _codeToShadowStatus( code ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -249,13 +260,14 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, const char ** pThingName, size_t * pThingNameLength ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); const char * pThingNameStart = NULL; size_t thingNameLength = 0; /* Check that the topic name length exceeds the minimum possible length. */ if( topicNameLength < MINIMUM_SHADOW_TOPIC_NAME_LENGTH ) { - return AWS_IOT_SHADOW_BAD_RESPONSE; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); } /* All Shadow topic names must start with the same prefix. */ @@ -263,7 +275,7 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, pTopicName, SHADOW_TOPIC_PREFIX_LENGTH ) != 0 ) { - return AWS_IOT_SHADOW_BAD_RESPONSE; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); } /* The Thing Name starts immediately after the topic prefix. */ @@ -280,14 +292,14 @@ AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, * name is invalid. */ if( thingNameLength + SHADOW_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) { - return AWS_IOT_SHADOW_BAD_RESPONSE; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); } /* Set the output parameters. */ *pThingName = pThingNameStart; *pThingNameLength = thingNameLength; - return AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index da81f970fd..49dda12a3a 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -34,6 +34,9 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* Platform layer includes. */ #include "platform/iot_threads.h" @@ -131,6 +134,7 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mq _mqttCallbackFunction_t callback, _mqttOperationFunction_t mqttOperation ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; @@ -171,20 +175,18 @@ static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mq /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ if( mqttStatus == IOT_MQTT_NO_MEMORY ) { - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } - return AWS_IOT_SHADOW_MQTT_ERROR; - } - else - { - IotLogDebug( "Successfully %s %.*s", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", - topicFilterLength, - pTopicFilter ); + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_MQTT_ERROR ); } - return AWS_IOT_SHADOW_STATUS_PENDING; + IotLogDebug( "Successfully %s %.*s", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + topicFilterLength, + pTopicFilter ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -253,14 +255,15 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, _shadowSubscription_t ** pRemovedSubscription ) { - int i = 0; + int32_t i = 0; + bool removeSubscription = true; IotLogDebug( "Checking if subscription object for %.*s can be removed.", pSubscription->thingNameLength, pSubscription->pThingName ); - /* If any Shadow operation's subscription reference count is not 0, then the - * subscription cannot be removed. */ + /* Check for active operations. If any Shadow operation's subscription + * reference count is not 0, then the subscription cannot be removed. */ for( i = 0; i < SHADOW_OPERATION_COUNT; i++ ) { if( pSubscription->references[ i ] > 0 ) @@ -271,7 +274,7 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, pSubscription->thingNameLength, pSubscription->pThingName ); - return; + removeSubscription = false; } else if( pSubscription->references[ i ] == PERSISTENT_SUBSCRIPTION ) { @@ -280,42 +283,56 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, pSubscription->thingNameLength, pSubscription->pThingName ); - return; + removeSubscription = false; + } + + if( removeSubscription == false ) + { + break; } } - /* If any Shadow callbacks are active, then the subscription cannot be removed. */ - for( i = 0; i < SHADOW_CALLBACK_COUNT; i++ ) + /* Check for active subscriptions. If any Shadow callbacks are active, then the + * subscription cannot be removed. */ + if( removeSubscription == true ) { - if( pSubscription->callbacks[ i ].function != NULL ) + for( i = 0; i < SHADOW_CALLBACK_COUNT; i++ ) { - IotLogDebug( "Found active Shadow %s callback for %.*s subscription object. " - "Subscription cannot be removed yet.", - _pAwsIotShadowCallbackNames[ i ], - pSubscription->thingNameLength, - pSubscription->pThingName ); - - return; + if( pSubscription->callbacks[ i ].function != NULL ) + { + IotLogDebug( "Found active Shadow %s callback for %.*s subscription object. " + "Subscription cannot be removed yet.", + _pAwsIotShadowCallbackNames[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + removeSubscription = false; + break; + } } } - /* No Shadow operation subscription references or active Shadow callbacks. - * Remove the subscription object. */ - IotListDouble_Remove( &( pSubscription->link ) ); + /* Remove the subscription if unused. */ + if( removeSubscription == true ) + { + /* No Shadow operation subscription references or active Shadow callbacks. + * Remove the subscription object. */ + IotListDouble_Remove( &( pSubscription->link ) ); - IotLogDebug( "Removed subscription object for %.*s.", - pSubscription->thingNameLength, - pSubscription->pThingName ); + IotLogDebug( "Removed subscription object for %.*s.", + pSubscription->thingNameLength, + pSubscription->pThingName ); - /* If the caller requested the removed subscription, set the output parameter. - * Otherwise, free the memory used by the subscription. */ - if( pRemovedSubscription != NULL ) - { - *pRemovedSubscription = pSubscription; - } - else - { - _AwsIotShadow_DestroySubscription( pSubscription ); + /* If the caller requested the removed subscription, set the output parameter. + * Otherwise, free the memory used by the subscription. */ + if( pRemovedSubscription != NULL ) + { + *pRemovedSubscription = pSubscription; + } + else + { + _AwsIotShadow_DestroySubscription( pSubscription ); + } } } @@ -340,8 +357,8 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe uint16_t operationTopicLength, _mqttCallbackFunction_t callback ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); uint16_t topicFilterLength = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; const _shadowOperationType_t type = pOperation->type; _shadowSubscription_t * pSubscription = pOperation->pSubscription; @@ -354,7 +371,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe pSubscription->thingNameLength, pSubscription->pThingName ); - return AWS_IOT_SHADOW_STATUS_PENDING; + IOT_GOTO_CLEANUP(); } /* When persistent subscriptions are not present, the reference count must @@ -385,7 +402,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe if( status != AWS_IOT_SHADOW_STATUS_PENDING ) { - return status; + IOT_GOTO_CLEANUP(); } /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ @@ -422,7 +439,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe callback, IotMqtt_TimedUnsubscribe ); - return status; + IOT_GOTO_CLEANUP(); } } @@ -449,7 +466,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe pSubscription->pThingName ); } - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -464,83 +481,83 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, uint16_t operationTopicLength = 0; /* Do nothing if this Shadow operation has persistent subscriptions. */ - if( pSubscription->references[ type ] == PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ type ] != PERSISTENT_SUBSCRIPTION ) { - IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " - "count will not be decremented.", - _pAwsIotShadowOperationNames[ type ], - pSubscription->thingNameLength, - pSubscription->pThingName ); + /* Decrement the number of subscription references for this operation. + * Ensure that it's positive. */ + ( pSubscription->references[ type ] )--; + AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); - return; - } - - /* Decrement the number of subscription references for this operation. - * Ensure that it's positive. */ - ( pSubscription->references[ type ] )--; - AwsIotShadow_Assert( pSubscription->references[ type ] >= 0 ); + /* Check if the number of references has reached 0. */ + if( pSubscription->references[ type ] == 0 ) + { + IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotShadowOperationNames[ type ] ); - /* Check if the number of references has reached 0. */ - if( pSubscription->references[ type ] == 0 ) - { - IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", - pSubscription->thingNameLength, - pSubscription->pThingName, - _pAwsIotShadowOperationNames[ type ] ); + /* Subscription must have a topic buffer. */ + AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); - /* Subscription must have a topic buffer. */ - AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); + /* Generate the prefix of the Shadow topic. This function will not + * fail when given a buffer. */ + ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) type, + pSubscription->pThingName, + pSubscription->thingNameLength, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); - /* Generate the prefix of the Shadow topic. This function will not - * fail when given a buffer. */ - ( void ) _AwsIotShadow_GenerateShadowTopic( ( _shadowOperationType_t ) type, - pSubscription->pThingName, - pSubscription->thingNameLength, - &( pSubscription->pTopicBuffer ), - &operationTopicLength ); + /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + SHADOW_ACCEPTED_SUFFIX, + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); - /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_ACCEPTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + /* There should be an active subscription for the accepted topic. */ + AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == true ); - /* There should be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == true ); + /* Remove the subscription from the Shadow "accepted" topic. */ + ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL, + IotMqtt_TimedUnsubscribe ); - /* Remove the subscription from the Shadow "accepted" topic. */ - ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL, - IotMqtt_TimedUnsubscribe ); + /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ + ( void ) memcpy( pTopicBuffer + operationTopicLength, + SHADOW_REJECTED_SUFFIX, + SHADOW_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); - /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_REJECTED_SUFFIX, - SHADOW_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + /* There should be an active subscription for the accepted topic. */ + AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL ) == true ); - /* There should be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == true ); + /* Remove the subscription from the Shadow "rejected" topic. */ + ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, + pTopicBuffer, + topicFilterLength, + NULL, + IotMqtt_TimedUnsubscribe ); + } - /* Remove the subscription from the Shadow "rejected" topic. */ - ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL, - IotMqtt_TimedUnsubscribe ); + /* Check if this subscription should be deleted. */ + _AwsIotShadow_RemoveSubscription( pSubscription, + pRemovedSubscription ); + } + else + { + IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " + "count will not be decremented.", + _pAwsIotShadowOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); } - - /* Check if this subscription should be deleted. */ - _AwsIotShadow_RemoveSubscription( pSubscription, - pRemovedSubscription ); } /*-----------------------------------------------------------*/ @@ -550,9 +567,10 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio size_t thingNameLength, uint32_t flags ) { - int i = 0; + int32_t i = 0; uint16_t operationTopicLength = 0, topicFilterLength = 0; - AwsIotShadowError_t removeAcceptedStatus = AWS_IOT_SHADOW_STATUS_PENDING, + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING, + removeAcceptedStatus = AWS_IOT_SHADOW_STATUS_PENDING, removeRejectedStatus = AWS_IOT_SHADOW_STATUS_PENDING; _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; @@ -666,15 +684,18 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Check the results of the MQTT unsubscribes. */ if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) { - return removeAcceptedStatus; + status = removeAcceptedStatus; } - - if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + else if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + status = removeRejectedStatus; + } + else { - return removeRejectedStatus; + status = AWS_IOT_SHADOW_SUCCESS; } - return AWS_IOT_SHADOW_SUCCESS; + return status; } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 01127e8085..fd316c6aee 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -659,7 +659,7 @@ TEST( Shadow_Unit_API, WaitInvalidParameters ) /* NULL output parameters for Shadow GET. */ operation.flags = AWS_IOT_SHADOW_FLAG_WAITABLE; - operation.type = _SHADOW_GET; + operation.type = SHADOW_GET; status = AwsIotShadow_Wait( &operation, 0, NULL, NULL ); } diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index d9df83f184..37dcce1f4e 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -188,17 +188,17 @@ TEST_GROUP_RUNNER( Shadow_Unit_Parser ) */ TEST( Shadow_Unit_Parser, StatusValid ) { - _shadowOperationStatus_t status = _UNKNOWN_STATUS; + _shadowOperationStatus_t status = UNKNOWN_STATUS; /* Parse "accepted" status. */ status = _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/accepted", 39 ); - TEST_ASSERT_EQUAL( _SHADOW_ACCEPTED, status ); + TEST_ASSERT_EQUAL( SHADOW_ACCEPTED, status ); /* Parse "rejected" status. */ status = _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/rejected", 39 ); - TEST_ASSERT_EQUAL( _SHADOW_REJECTED, status ); + TEST_ASSERT_EQUAL( SHADOW_REJECTED, status ); } /*-----------------------------------------------------------*/ @@ -209,22 +209,22 @@ TEST( Shadow_Unit_Parser, StatusValid ) TEST( Shadow_Unit_Parser, StatusInvalid ) { /* Topic too short. */ - TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + TEST_ASSERT_EQUAL( UNKNOWN_STATUS, _AwsIotShadow_ParseShadowStatus( "accepted", 8 ) ); /* Topic missing last character. */ - TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + TEST_ASSERT_EQUAL( UNKNOWN_STATUS, _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/accepte", 38 ) ); /* Topic missing level separator. */ - TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + TEST_ASSERT_EQUAL( UNKNOWN_STATUS, _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadowaccepted", 38 ) ); /* Topic suffix isn't "accepted" or "rejected". */ - TEST_ASSERT_EQUAL( _UNKNOWN_STATUS, + TEST_ASSERT_EQUAL( UNKNOWN_STATUS, _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/unknown", 38 ) ); } From e0b059927746b023a1984f2cfce31d4f50c209eb Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 11 Jun 2019 11:40:46 -0700 Subject: [PATCH 169/844] Add missing call to Shadow strerror (#453) --- demos/source/aws_iot_demo_shadow.c | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index 75d3aeaef0..a8e1de7d83 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -93,14 +93,14 @@ * token must be unique at any given time, but may be reused once the update is * completed. For this demo, a timestamp is used for a client token. */ -#define SHADOW_DESIRED_JSON \ - "{" \ - "\"state\":{" \ - "\"desired\":{" \ - "\"powerOn\":%01d" \ - "}" \ - "}," \ - "\"clientToken\":\"%06lu\"" \ +#define SHADOW_DESIRED_JSON \ + "{" \ + "\"state\":{" \ + "\"desired\":{" \ + "\"powerOn\":%01d" \ + "}" \ + "}," \ + "\"clientToken\":\"%06lu\"" \ "}" /** @@ -118,14 +118,14 @@ * token must be unique at any given time, but may be reused once the update is * completed. For this demo, a timestamp is used for a client token. */ -#define SHADOW_REPORTED_JSON \ - "{" \ - "\"state\":{" \ - "\"reported\":{" \ - "\"powerOn\":%01d" \ - "}" \ - "}," \ - "\"clientToken\":\"%06lu\"" \ +#define SHADOW_REPORTED_JSON \ + "{" \ + "\"state\":{" \ + "\"reported\":{" \ + "\"powerOn\":%01d" \ + "}" \ + "}," \ + "\"clientToken\":\"%06lu\"" \ "}" /** @@ -344,7 +344,7 @@ static void _shadowDeltaCallback( void * pCallbackContext, IotLogWarn( "%.*s failed to report new state, error %s.", pCallbackParam->thingNameLength, pCallbackParam->pThingName, - updateStatus ); + AwsIotShadow_strerror( updateStatus ) ); } else { From f18fa455dc5e1b37482d718112435479e7a1eeb9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 11 Jun 2019 15:17:30 -0700 Subject: [PATCH 170/844] Provide additional port options in CMakeLists (#454) --- CMakeLists.txt | 41 ++++++++++++++++---------- platform/ports/template/CMakeLists.txt | 37 +++++++++++++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 platform/ports/template/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 1028d2ca87..148b096b1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,25 +41,34 @@ CMAKE_DEPENDENT_OPTION( BUILD_SHARED_LIBS ON "${ALLOW_SHARED_LIBRARIES}" OFF ) -# Check for system support and set platform name. -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - # Provide an option to use the OpenSSL network abstraction on Linux. - option( IOT_NETWORK_USE_OPENSSL - "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." - OFF ) - - if( ${IOT_NETWORK_USE_OPENSSL} ) - add_definitions( -DIOT_NETWORK_USE_OPENSSL=1 ) +# Set the platform named based on the host OS if not defined. +if( NOT DEFINED IOT_PLATFORM_NAME ) + if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) + set( IOT_PLATFORM_NAME "posix" CACHE STRING "Port to use for building the SDK." ) + + # Provide an option to use the OpenSSL network abstraction on Linux. + option( IOT_NETWORK_USE_OPENSSL + "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." + OFF ) + elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + set( IOT_PLATFORM_NAME "win32" CACHE STRING "Port to use for building the SDK." ) + + # Export all symbols when building Windows DLLs. + set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) endif() +endif() - set( IOT_PLATFORM_NAME posix ) -elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - set( IOT_PLATFORM_NAME win32 ) +if( ${IOT_NETWORK_USE_OPENSSL} ) + add_definitions( -DIOT_NETWORK_USE_OPENSSL=1 ) +endif() - # Export all symbols when building Windows DLLs. - set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) -else() - message( FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}. Currently supported systems are: Linux, Windows." ) +# Validate the platform name. +if( NOT DEFINED IOT_PLATFORM_NAME ) + message( FATAL_ERROR "IOT_PLATFORM_NAME was not set and could not be automatically determined." ) +endif() + +if( NOT EXISTS ${PROJECT_SOURCE_DIR}/platform/ports/${IOT_PLATFORM_NAME} ) + message( FATAL_ERROR "A port for ${IOT_PLATFORM_NAME} does not exist in platform/ports." ) endif() # Set output directories. diff --git a/platform/ports/template/CMakeLists.txt b/platform/ports/template/CMakeLists.txt new file mode 100644 index 0000000000..56b64d34b3 --- /dev/null +++ b/platform/ports/template/CMakeLists.txt @@ -0,0 +1,37 @@ +# This CMakeLists is a template for new ports. It provides the minimal +# configuration for building, but nothing more. + +# Warn that the template port only builds. It will not create usable libraries. +message( WARNING "This is a template port that contains only stubs. Libraries built with this port will not work!") + +# Add the mbed TLS header in the template. The mbed TLS network port is supposed +# to be platform-independent, so it is built in the template. +set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} + ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h + PARENT_SCOPE ) + +# Template platform sources. Except for the network sources, these files contain +# only stubs. +set( PLATFORM_SOURCES + # Stubs + ${CMAKE_SOURCE_DIR}/platform/ports/template/source/iot_clock_template.c + ${CMAKE_SOURCE_DIR}/platform/ports/template/source/iot_threads_template.c + + # Network sources + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ) + +# ANYTHING BELOW THIS LINE TYPICALLY DOES NOT NEED TO BE MODIFIED. + +# Add platform as an interface library. It will be built into iotbase. +add_library( iotplatform INTERFACE ) + +target_sources( iotplatform INTERFACE + ${PLATFORM_SOURCES} ) + +# This template platform layer uses mbed TLS, since it builds the mbed TLS network +# sources. +target_link_libraries( iotplatform INTERFACE mbedtls ) + +# Set platform sources in the parent scope for directory organization. +set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) From 0e4e816a3625db6aa736fe88201fdfedda8805bd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 13 Jun 2019 18:33:18 -0400 Subject: [PATCH 171/844] Allow AWS IoT metrics username to be overridden (#455) --- lib/source/mqtt/iot_mqtt_serialize.c | 51 +++++++++++----------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index de1abf5cf1..18d44935e5 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -79,27 +79,27 @@ * Positions of each flag in the "Connect Flag" field of an MQTT CONNECT * packet. */ -#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ -#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ -#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ -#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ -#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ -#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ -#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ +#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */ +#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */ +#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS1. */ +#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS2. */ +#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */ +#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */ +#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief Username present. */ /* * Positions of each flag in the first byte of an MQTT PUBLISH packet's * fixed header. */ -#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ -#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ -#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ -#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ +#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief Message retain flag. */ +#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief Publish QoS 1. */ +#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief Publish QoS 2. */ +#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief Duplicate message. */ /** * @brief The constant specifying MQTT version 3.1.1. Placed in the CONNECT packet. */ -#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) +#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U ) /** * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT @@ -146,33 +146,20 @@ */ #define MQTT_PACKET_DISCONNECT_SIZE ( 2 ) /**< @brief A DISCONNECT packet is always 2 bytes in size. */ -/* - * Username for metrics with AWS IoT. - */ +/* Username for metrics with AWS IoT. */ #if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 + #ifndef AWS_IOT_METRICS_USERNAME /** - * @brief Check if an SDK name is defined. If not, specify "C SDK". + * @brief Specify C SDK and version. */ - #ifdef IOT_SDK_NAME - #define METRICS_SDK_NAME IOT_SDK_NAME - #else - #define METRICS_SDK_NAME "C" - #endif - -/** - * @brief In the metrics string, include the platform name if defined. - */ - #ifdef IOT_PLATFORM_NAME - #define AWS_IOT_METRICS_USERNAME "?SDK=" METRICS_SDK_NAME "&Version=4.0.0&Platform=" IOT_PLATFORM_NAME - #else - #define AWS_IOT_METRICS_USERNAME "?SDK=" METRICS_SDK_NAME "&Version=4.0.0" - #endif + #define AWS_IOT_METRICS_USERNAME "?SDK=C&Version=4.0.0" /** - * @brief Length of #AWS_IOT_METRICS_USERNAME. + * @brief The length of #AWS_IOT_METRICS_USERNAME. */ - #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1 ) + #define AWS_IOT_METRICS_USERNAME_LENGTH ( ( uint16_t ) sizeof( AWS_IOT_METRICS_USERNAME ) - 1 ) + #endif /* ifndef AWS_IOT_METRICS_USERNAME */ #endif /* if AWS_IOT_MQTT_ENABLE_METRICS == 1 || DOXYGEN == 1 */ /*-----------------------------------------------------------*/ From 40e13f9086b3f92378aee28d6491714236a9be8a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 14 Jun 2019 13:28:21 -0400 Subject: [PATCH 172/844] Use time conversion defines in POSIX platform (#456) --- platform/ports/posix/source/iot_clock_posix.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/ports/posix/source/iot_clock_posix.c b/platform/ports/posix/source/iot_clock_posix.c index 54ca5c7a95..d1fcee0214 100644 --- a/platform/ports/posix/source/iot_clock_posix.c +++ b/platform/ports/posix/source/iot_clock_posix.c @@ -217,8 +217,8 @@ uint64_t IotClock_GetTimeMs( void ) abort(); } - return ( ( uint64_t ) currentTime.tv_sec ) * 1000ULL + - ( ( uint64_t ) currentTime.tv_nsec ) / 1000000ULL; + return ( ( uint64_t ) currentTime.tv_sec ) * MILLISECONDS_PER_SECOND + + ( ( uint64_t ) currentTime.tv_nsec ) / NANOSECONDS_PER_MILLISECOND; } /*-----------------------------------------------------------*/ From 419c72908d9e6d21a8ab8c156dad5feaedcfef68 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 17 Jun 2019 12:46:09 -0400 Subject: [PATCH 173/844] Add empty files for Jobs library (#458) --- .travis.yml | 1 + CMakeLists.txt | 3 + doc/config/jobs | 32 +++++++++ doc/config/layout_main.xml | 1 + doc/lib/jobs.txt | 5 ++ lib/include/aws_iot_jobs.h | 38 +++++++++++ lib/include/private/aws_iot_jobs_internal.h | 37 +++++++++++ lib/include/types/aws_iot_jobs_types.h | 33 ++++++++++ lib/source/jobs/CMakeLists.txt | 25 +++++++ lib/source/jobs/aws_iot_jobs_api.c | 28 ++++++++ scripts/ci_test_jobs.sh | 23 +++++++ tests/CMakeLists.txt | 3 + tests/jobs/CMakeLists.txt | 21 ++++++ tests/jobs/aws_iot_tests_jobs.c | 45 +++++++++++++ tests/jobs/unit/aws_iot_tests_jobs_api.c | 73 +++++++++++++++++++++ 15 files changed, 368 insertions(+) create mode 100644 doc/config/jobs create mode 100644 doc/lib/jobs.txt create mode 100644 lib/include/aws_iot_jobs.h create mode 100644 lib/include/private/aws_iot_jobs_internal.h create mode 100644 lib/include/types/aws_iot_jobs_types.h create mode 100644 lib/source/jobs/CMakeLists.txt create mode 100644 lib/source/jobs/aws_iot_jobs_api.c create mode 100644 scripts/ci_test_jobs.sh create mode 100644 tests/jobs/CMakeLists.txt create mode 100644 tests/jobs/aws_iot_tests_jobs.c create mode 100644 tests/jobs/unit/aws_iot_tests_jobs_api.c diff --git a/.travis.yml b/.travis.yml index 803e892d5d..e70397bc6b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ jobs: - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - env: RUN_TEST=mqtt NETWORK_STACK=openssl - env: RUN_TEST=shadow + - env: RUN_TEST=jobs - if: type = push compiler: gcc env: RUN_TEST=coverage diff --git a/CMakeLists.txt b/CMakeLists.txt index 148b096b1c..613f251cf2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,9 @@ add_subdirectory( lib/source/serializer ) # Defender library. add_subdirectory( lib/source/defender ) +# Jobs library. +add_subdirectory( lib/source/jobs ) + # TinyCBOR library (third-party). add_subdirectory( third_party/tinycbor ) diff --git a/doc/config/jobs b/doc/config/jobs new file mode 100644 index 0000000000..8bab0afb46 --- /dev/null +++ b/doc/config/jobs @@ -0,0 +1,32 @@ +# Include common configuration options. +@INCLUDE_PATH = doc/config +@INCLUDE = common + +# Basic project information. +PROJECT_NAME = "Jobs" +PROJECT_BRIEF = "AWS IoT Jobs library" + +# Library documentation output directory. +HTML_OUTPUT = jobs + +# Generate Doxygen tag file for this library. +GENERATE_TAGFILE = doc/tag/jobs.tag + +# Directories containing library source code. +INPUT = doc/lib/ \ + lib/include \ + lib/include/private \ + lib/include/types \ + lib/source/jobs \ + demos \ + tests/jobs/unit + +# Library file names. +FILE_PATTERNS = *jobs*.h *jobs*.c *jobs*.txt + +# External tag files required by this library. +TAGFILES = doc/tag/main.tag=../main \ + doc/tag/mqtt.tag=../mqtt \ + doc/tag/logging.tag=../logging \ + doc/tag/static_memory.tag=../static_memory \ + doc/tag/platform.tag=../platform diff --git a/doc/config/layout_main.xml b/doc/config/layout_main.xml index e8ce5edbbe..e2014d4de6 100644 --- a/doc/config/layout_main.xml +++ b/doc/config/layout_main.xml @@ -9,6 +9,7 @@ + diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt new file mode 100644 index 0000000000..094b3e7215 --- /dev/null +++ b/doc/lib/jobs.txt @@ -0,0 +1,5 @@ +/** +@mainpage +@anchor jobs +@brief AWS IoT Device Jobs library. +*/ diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h new file mode 100644 index 0000000000..1207922c2a --- /dev/null +++ b/lib/include/aws_iot_jobs.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs.h + * @brief User-facing functions of the Jobs library. + */ + +#ifndef AWS_IOT_JOBS_H_ +#define AWS_IOT_JOBS_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Jobs types include. */ +#include "types/aws_iot_jobs_types.h" + +/*------------------------- Jobs library functions --------------------------*/ + +#endif diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h new file mode 100644 index 0000000000..b7d2aade5a --- /dev/null +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_internal.h + * @brief Internal header of Jobs library. This header should not be included in + * typical application code. + */ + +#ifndef AWS_IOT_JOBS_INTERNAL_H_ +#define AWS_IOT_JOBS_INTERNAL_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Jobs include. */ +#include "aws_iot_jobs.h" + +#endif diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h new file mode 100644 index 0000000000..68ac336c54 --- /dev/null +++ b/lib/include/types/aws_iot_jobs_types.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_types.h + * @brief Types of the Jobs library. + */ + +#ifndef AWS_IOT_JOBS_TYPES_H_ +#define AWS_IOT_JOBS_TYPES_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +#endif diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt new file mode 100644 index 0000000000..76ac373075 --- /dev/null +++ b/lib/source/jobs/CMakeLists.txt @@ -0,0 +1,25 @@ +# Jobs library source files. +set( JOBS_SOURCES + aws_iot_jobs_api.c ) + +# Jobs library target. +add_library( awsiotjobs + ${JOBS_SOURCES} + ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_jobs.h + ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_jobs_types.h + ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_jobs_internal.h ) + +# Link required libraries. +target_link_libraries( awsiotjobs PUBLIC iotmqtt ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotjobs PRIVATE unity ) +endif() + +# Organization of Jobs in folders. +set_property( TARGET awsiotjobs PROPERTY FOLDER "lib" ) +source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_jobs.h ) +source_group( include\\types ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_jobs_types.h ) +source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_jobs_internal.h ) +source_group( source FILES ${JOBS_SOURCES} ) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c new file mode 100644 index 0000000000..38c5a0cf5e --- /dev/null +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_api.c + * @brief Implements the user-facing functions of the Jobs library. + */ + +/* The config header is always included first. */ +#include "iot_config.h" diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh new file mode 100644 index 0000000000..a3a078da23 --- /dev/null +++ b/scripts/ci_test_jobs.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# Travis CI uses this script to test the Jobs library. + +# Exit on any nonzero return code. +set -e + +# CMake compiler flags for building Jobs. +CMAKE_FLAGS="$COMPILER_OPTIONS" + +# Build executables. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" +make -j2 aws_iot_tests_jobs + +# Run tests. +./bin/aws_iot_tests_jobs + +# Rebuild in static memory mode. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" +make -j2 aws_iot_tests_jobs + +# Run tests in static memory mode. +./bin/aws_iot_tests_jobs diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ca865ad6f8..c185ebc6de 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,3 +17,6 @@ add_subdirectory( serializer ) # Shadow tests. add_subdirectory( shadow ) + +# Jobs tests. +add_subdirectory( jobs ) diff --git a/tests/jobs/CMakeLists.txt b/tests/jobs/CMakeLists.txt new file mode 100644 index 0000000000..b5d596d258 --- /dev/null +++ b/tests/jobs/CMakeLists.txt @@ -0,0 +1,21 @@ +# Jobs unit test sources. +set( JOBS_UNIT_TEST_SOURCES + unit/aws_iot_tests_jobs_api.c ) + +# Jobs tests executable. +add_executable( aws_iot_tests_jobs + ${JOBS_UNIT_TEST_SOURCES} + aws_iot_tests_jobs.c + ${IOT_TEST_APP_FILES} ) + +# Define the test to run. +target_compile_definitions( aws_iot_tests_jobs PRIVATE + -DRunTests=RunJobsTests ) + +# Jobs tests library dependencies. +target_link_libraries( aws_iot_tests_jobs PRIVATE awsiotjobs unity ) + +# Organization of Jobs tests in folders. +set_property( TARGET aws_iot_tests_jobs PROPERTY FOLDER "tests" ) +source_group( unit FILES ${JOBS_UNIT_TEST_SOURCES} ) +source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_jobs.c ) diff --git a/tests/jobs/aws_iot_tests_jobs.c b/tests/jobs/aws_iot_tests_jobs.c new file mode 100644 index 0000000000..3b7b6e756d --- /dev/null +++ b/tests/jobs/aws_iot_tests_jobs.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_jobs.c + * @brief Test runner for Jobs tests. + */ + +/* Standard includes. */ +#include + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +void RunJobsTests( bool disableNetworkTests, + bool disableLongTests ) +{ + /* Silence warnings about unused parameters. */ + ( void ) disableNetworkTests; + ( void ) disableLongTests; + + RUN_TEST_GROUP( Jobs_Unit_API ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c new file mode 100644 index 0000000000..48fba72857 --- /dev/null +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_jobs_api.c + * @brief Tests for the user-facing API functions (declared in aws_iot_jobs.h). + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* SDK initialization include. */ +#include "iot_init.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Jobs API tests. + */ +TEST_GROUP( Jobs_Unit_API ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Jobs API tests. + */ +TEST_SETUP( Jobs_Unit_API ) +{ + /* Initialize SDK. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Jobs API tests. + */ +TEST_TEAR_DOWN( Jobs_Unit_API ) +{ + IotSdk_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Jobs API tests. + */ +TEST_GROUP_RUNNER( Jobs_Unit_API ) +{ +} + +/*-----------------------------------------------------------*/ From 63fff8a8b79385c64822f1b0c8b5c39b31670238 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 17 Jun 2019 14:50:19 -0400 Subject: [PATCH 174/844] Add files for common components of Shadow and Jobs (#459) --- CMakeLists.txt | 1 + lib/include/private/aws_iot.h | 54 ++++++++++++++++ lib/include/private/aws_iot_shadow_internal.h | 9 +-- lib/source/common/CMakeLists.txt | 8 ++- lib/source/common/aws_iot/aws_iot_validate.c | 61 +++++++++++++++++++ lib/source/shadow/aws_iot_shadow_api.c | 14 +---- lib/source/shadow/aws_iot_shadow_operation.c | 7 ++- .../shadow/aws_iot_shadow_static_memory.c | 4 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 2 +- 9 files changed, 135 insertions(+), 25 deletions(-) create mode 100644 lib/include/private/aws_iot.h create mode 100644 lib/source/common/aws_iot/aws_iot_validate.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 613f251cf2..eab7e0136f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,7 @@ source_group( platform\\source FILES ${PLATFORM_SOURCES} ) source_group( common\\include FILES ${COMMON_PUBLIC_HEADERS} ) source_group( common\\include\\private FILES ${COMMON_PRIVATE_HEADERS} ) source_group( common\\include\\types FILES ${COMMON_TYPES_HEADERS} ) +source_group( common\\source\\aws_iot FILES ${AWS_IOT_COMMON_SOURCES} ) source_group( common\\source FILES ${COMMON_SOURCES} ) # MQTT library. diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h new file mode 100644 index 0000000000..41de561977 --- /dev/null +++ b/lib/include/private/aws_iot.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot.h + * @brief Provides routines and constants that are common to AWS IoT libraries. + * This header should not be included in typical application code. + */ + +#ifndef AWS_IOT_H_ +#define AWS_IOT_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/** + * @brief The longest Thing Name accepted by the Shadow service, per the [AWS IoT + * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). + */ +#define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) + +/** + * @brief Checks that a Thing Name is valid for AWS IoT. + * + * @param[in] pThingName Thing Name to validate. + * @param[in] thingNameLength Length of `pThingName`. + * + * @return `true` if `pThingName` is valid; `false` otherwise. + */ +bool AwsIot_ValidateThingName( const char * pThingName, + size_t thingNameLength ); + +#endif /* ifndef AWS_IOT_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 94396fe6f8..4b7a62218c 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -40,6 +40,9 @@ /* Shadow include. */ #include "aws_iot_shadow.h" +/* AWS IoT include. */ +#include "aws_iot.h" + /** * @def AwsIotShadow_Assert( expression ) * @brief Assertion macro for the Shadow library. @@ -159,12 +162,6 @@ #endif /** @endcond */ -/** - * @brief The longest Thing Name accepted by the Shadow service, per the [AWS IoT - * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). - */ -#define MAX_THING_NAME_LENGTH ( 128 ) - /** * @brief The number of currently available Shadow operations. * diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 03129416bd..7850e788ce 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -6,6 +6,9 @@ set( COMMON_SOURCES ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool.c ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool_static_memory.c ) +set( AWS_IOT_COMMON_SOURCES + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c ) + # Lists of common header files. These are only used for directory organization # (not for build). set( COMMON_PUBLIC_HEADERS @@ -19,6 +22,7 @@ set( COMMON_PRIVATE_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/private/iot_logging.h ${PROJECT_SOURCE_DIR}/lib/include/private/iot_static_memory.h ${PROJECT_SOURCE_DIR}/lib/include/private/iot_taskpool_internal.h + ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot.h PARENT_SCOPE ) set( COMMON_TYPES_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h @@ -28,7 +32,9 @@ set( COMMON_TYPES_HEADERS add_library( iotcommon INTERFACE ) target_sources( iotcommon INTERFACE - ${COMMON_SOURCES} ) + ${COMMON_SOURCES} + ${AWS_IOT_COMMON_SOURCES} ) # Set common sources in the parent scope for directory organization. set( COMMON_SOURCES ${COMMON_SOURCES} PARENT_SCOPE ) +set( AWS_IOT_COMMON_SOURCES ${AWS_IOT_COMMON_SOURCES} PARENT_SCOPE ) diff --git a/lib/source/common/aws_iot/aws_iot_validate.c b/lib/source/common/aws_iot/aws_iot_validate.c new file mode 100644 index 0000000000..96df3564ca --- /dev/null +++ b/lib/source/common/aws_iot/aws_iot_validate.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_validate.c + * @brief Validates Thing Names and other parameters to AWS IoT. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* AWS IoT include. */ +#include "private/aws_iot.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/*-----------------------------------------------------------*/ + +bool AwsIot_ValidateThingName( const char * pThingName, + size_t thingNameLength ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + + if( pThingName == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + if( thingNameLength == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + if( thingNameLength > AWS_IOT_MAX_THING_NAME_LENGTH ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 1e452e3dc7..d187262d03 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -184,24 +184,14 @@ static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, ( void ) type; /* Check Thing Name. */ - if( ( pThingName == NULL ) || ( thingNameLength == 0 ) ) + if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == false ) { - IotLogError( "Thing name for Shadow %s cannot be NULL or have length 0.", + IotLogError( "Thing Name for Shadow %s is not valid.", _pAwsIotShadowOperationNames[ type ] ); IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } - if( thingNameLength > MAX_THING_NAME_LENGTH ) - { - IotLogError( "Thing Name length of %lu exceeds the maximum allowed" - "length of %d.", - ( unsigned long ) thingNameLength, - MAX_THING_NAME_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } - /* Check the waitable operation flag. */ if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 52ee8b9a4b..a4688a8c0b 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -269,7 +269,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, { pOperation = IotLink_Container( _shadowOperation_t, pOperationLink, link ); - /* Remove a non-waitable operation from the pending operation list. */ + /* Remove a non-waitable operation from the pending operation list. + * Waitable operations are removed by the Wait function. */ if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == 0 ) { IotListDouble_Remove( &( pOperation->link ) ); @@ -337,8 +338,8 @@ static void _commonOperationCallback( _shadowOperationType_t type, /* Notify of operation completion. */ _AwsIotShadow_Notify( pOperation ); - /* For waitable operations, unlock the pending operation list mutex to signal - * this function's completion. */ + /* For waitable operations, unlock the pending operation list mutex to allow + * the Wait function to run. */ if( ( flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); diff --git a/lib/source/shadow/aws_iot_shadow_static_memory.c b/lib/source/shadow/aws_iot_shadow_static_memory.c index 9eb64d53a0..045cd8d921 100644 --- a/lib/source/shadow/aws_iot_shadow_static_memory.c +++ b/lib/source/shadow/aws_iot_shadow_static_memory.c @@ -69,10 +69,10 @@ * @brief The size of a static memory Shadow subscription. * * Since the pThingName member of #_shadowSubscription_t is variable-length, - * the constant #MAX_THING_NAME_LENGTH is used for the length of + * the constant `AWS_IOT_MAX_THING_NAME_LENGTH` is used for the length of * #_shadowSubscription_t.pThingName. */ -#define SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + MAX_THING_NAME_LENGTH ) +#define SHADOW_SUBSCRIPTION_SIZE ( sizeof( _shadowSubscription_t ) + AWS_IOT_MAX_THING_NAME_LENGTH ) /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index fd316c6aee..29dc469b22 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -512,7 +512,7 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) /* Thing Name too long. */ status = AwsIotShadow_Delete( _pMqttConnection, TEST_THING_NAME, - MAX_THING_NAME_LENGTH + 1, + AWS_IOT_MAX_THING_NAME_LENGTH + 1, 0, NULL, NULL ); From 197b9dd3dd762304eed090e4416d0ccaca358448 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 17 Jun 2019 15:46:16 -0400 Subject: [PATCH 175/844] Move Shadow status parsing to common (#460) --- lib/include/private/aws_iot.h | 45 +++++++++- lib/include/private/aws_iot_shadow_internal.h | 42 ---------- lib/source/common/CMakeLists.txt | 3 +- lib/source/common/aws_iot/aws_iot_parser.c | 82 +++++++++++++++++++ lib/source/shadow/aws_iot_shadow_operation.c | 10 +-- lib/source/shadow/aws_iot_shadow_parser.c | 65 ++------------- .../shadow/aws_iot_shadow_subscription.c | 42 +++++----- .../shadow/unit/aws_iot_tests_shadow_parser.c | 38 ++++----- 8 files changed, 180 insertions(+), 147 deletions(-) create mode 100644 lib/source/common/aws_iot/aws_iot_parser.c diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 41de561977..e7ba199b23 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -33,13 +33,45 @@ /* Standard includes. */ #include +#include /** - * @brief The longest Thing Name accepted by the Shadow service, per the [AWS IoT + * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). */ #define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) +/** + * @brief The suffix for an AWS IoT operation "accepted" topic. + */ +#define AWS_IOT_ACCEPTED_SUFFIX "/accepted" + +/** + * @brief The length of #AWS_IOT_ACCEPTED_SUFFIX. + */ +#define AWS_IOT_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_ACCEPTED_SUFFIX ) - 1 ) ) + +/** + * @brief The suffix for an AWS IoT operation "rejected" topic. + */ +#define AWS_IOT_REJECTED_SUFFIX "/rejected" + +/** + * @brief The length of #AWS_IOT_REJECTED_SUFFIX. + */ +#define AWS_IOT_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) ) + +/** + * @brief Enumerations representing each of the statuses that may be parsed + * from a topic. + */ +typedef enum AwsIotStatus +{ + AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */ + AWS_IOT_REJECTED = 1, /**< Operation rejected. */ + AWS_IOT_UNKNOWN = 2 /**< Unknown status (neither accepted nor rejected). */ +} AwsIotStatus_t; + /** * @brief Checks that a Thing Name is valid for AWS IoT. * @@ -51,4 +83,15 @@ bool AwsIot_ValidateThingName( const char * pThingName, size_t thingNameLength ); +/** + * @brief Parse the operation status (accepted or rejected) from an MQTT topic. + * + * @param[in] pTopicName The topic to parse. + * @param[in] topicNameLength The length of `pTopicName`. + * + * @return Any #AwsIotStatus_t. + */ +AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, + uint16_t topicNameLength ); + #endif /* ifndef AWS_IOT_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 4b7a62218c..a436c1a870 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -218,26 +218,6 @@ */ #define SHADOW_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_UPDATE_OPERATION_STRING ) - 1 ) ) -/** - * @brief The suffix for a Shadow operation "accepted" topic. - */ -#define SHADOW_ACCEPTED_SUFFIX "/accepted" - -/** - * @brief The length of #SHADOW_ACCEPTED_SUFFIX. - */ -#define SHADOW_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_ACCEPTED_SUFFIX ) - 1 ) ) - -/** - * @brief The suffix for a Shadow operation "rejected" topic. - */ -#define SHADOW_REJECTED_SUFFIX "/rejected" - -/** - * @brief The length of #SHADOW_REJECTED_SUFFIX. - */ -#define SHADOW_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_REJECTED_SUFFIX ) - 1 ) ) - /** * @brief The suffix for a Shadow delta topic. */ @@ -342,17 +322,6 @@ typedef enum _shadowCallbackType UPDATED_CALLBACK = 1 /**< Updated callback. */ } _shadowCallbackType_t; -/** - * @brief Enumerations representing each of the statuses that may be parsed - * from a Shadow status topic. - */ -typedef enum _shadowOperationStatus -{ - SHADOW_ACCEPTED = 0, /**< Shadow operation accepted. */ - SHADOW_REJECTED = 1, /**< Shadow operation rejected. */ - UNKNOWN_STATUS = 2 /**< Parsed value matched neither accepted nor rejected. */ -} _shadowOperationStatus_t; - /** * @brief Internal structure representing a single Shadow operation (DELETE, * GET, or UPDATE). @@ -615,17 +584,6 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, /*------------------------- Shadow parser functions -------------------------*/ -/** - * @brief Parse the operation status (accepted or rejected) from a Shadow topic. - * - * @param[in] pTopicName The topic to parse. - * @param[in] topicNameLength The length of `pTopicName`. - * - * @return Any #_shadowOperationStatus_t. - */ -_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicName, - size_t topicNameLength ); - /** * @brief Parse the Thing Name from a Shadow topic. * diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 7850e788ce..6f8db38629 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -7,7 +7,8 @@ set( COMMON_SOURCES ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool_static_memory.c ) set( AWS_IOT_COMMON_SOURCES - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c ) + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c ) # Lists of common header files. These are only used for directory organization # (not for build). diff --git a/lib/source/common/aws_iot/aws_iot_parser.c b/lib/source/common/aws_iot/aws_iot_parser.c new file mode 100644 index 0000000000..4113847cb1 --- /dev/null +++ b/lib/source/common/aws_iot/aws_iot_parser.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_parser.c + * @brief Parses topics for Thing Name and status. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* AWS IoT include. */ +#include "private/aws_iot.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/*-----------------------------------------------------------*/ + +AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, + uint16_t topicNameLength ) +{ + IOT_FUNCTION_ENTRY( AwsIotStatus_t, AWS_IOT_UNKNOWN ); + const char * pSuffixStart = NULL; + + /* Check that the status topic name is at least as long as the + * "accepted" suffix. */ + if( topicNameLength > AWS_IOT_ACCEPTED_SUFFIX_LENGTH ) + { + /* Calculate where the "accepted" suffix should start. */ + pSuffixStart = pTopicName + topicNameLength - AWS_IOT_ACCEPTED_SUFFIX_LENGTH; + + /* Check if the end of the status topic name is "/accepted". */ + if( strncmp( pSuffixStart, + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ) == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_ACCEPTED ); + } + } + + /* Check that the status topic name is at least as long as the + * "rejected" suffix. */ + if( topicNameLength > AWS_IOT_REJECTED_SUFFIX_LENGTH ) + { + /* Calculate where the "rejected" suffix should start. */ + pSuffixStart = pTopicName + topicNameLength - AWS_IOT_REJECTED_SUFFIX_LENGTH; + + /* Check if the end of the status topic name is "/rejected". */ + if( strncmp( pSuffixStart, + AWS_IOT_REJECTED_SUFFIX, + AWS_IOT_REJECTED_SUFFIX_LENGTH ) == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_REJECTED ); + } + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index a4688a8c0b..ff66a06b37 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -221,7 +221,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, { _shadowOperation_t * pOperation = NULL; IotLink_t * pOperationLink = NULL; - _shadowOperationStatus_t status = UNKNOWN_STATUS; + AwsIotStatus_t status = AWS_IOT_UNKNOWN; _operationMatchParams_t param = { .type = ( _shadowOperationType_t ) 0 }; uint32_t flags = 0; @@ -287,12 +287,12 @@ static void _commonOperationCallback( _shadowOperationType_t type, pMessage->u.message.info.pTopicName ); /* Parse the status from the topic name. */ - status = _AwsIotShadow_ParseShadowStatus( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength ); + status = AwsIot_ParseStatus( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength ); switch( status ) { - case SHADOW_ACCEPTED: + case AWS_IOT_ACCEPTED: IotLogInfo( "Shadow %s of %.*s was ACCEPTED.", _pAwsIotShadowOperationNames[ type ], pOperation->pSubscription->thingNameLength, @@ -312,7 +312,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, break; - case SHADOW_REJECTED: + case AWS_IOT_REJECTED: IotLogWarn( "Shadow %s of %.*s was REJECTED.", _pAwsIotShadowOperationNames[ type ], pOperation->pSubscription->thingNameLength, diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index 29d19a18db..642efa7590 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -21,7 +21,7 @@ /** * @file aws_iot_shadow_parser.c - * @brief Implements topic name and JSON parsing functions of the Shadow library. + * @brief Implements JSON parsing functions of the Shadow library. */ /* The config header is always included first. */ @@ -66,12 +66,12 @@ * @brief The minimum possible length of a Shadow topic name, per the Shadow * spec. */ -#define MINIMUM_SHADOW_TOPIC_NAME_LENGTH \ - ( SHADOW_TOPIC_PREFIX_LENGTH + \ - ( uint16_t ) sizeof( SHADOW_GET_OPERATION_STRING ) + \ - ( SHADOW_ACCEPTED_SUFFIX_LENGTH < SHADOW_REJECTED_SUFFIX_LENGTH ? \ - SHADOW_ACCEPTED_SUFFIX_LENGTH : \ - SHADOW_REJECTED_SUFFIX_LENGTH ) ) +#define MINIMUM_SHADOW_TOPIC_NAME_LENGTH \ + ( SHADOW_TOPIC_PREFIX_LENGTH + \ + ( uint16_t ) sizeof( SHADOW_GET_OPERATION_STRING ) + \ + ( AWS_IOT_ACCEPTED_SUFFIX_LENGTH < AWS_IOT_REJECTED_SUFFIX_LENGTH ? \ + AWS_IOT_ACCEPTED_SUFFIX_LENGTH : \ + AWS_IOT_REJECTED_SUFFIX_LENGTH ) ) /*-----------------------------------------------------------*/ @@ -140,57 +140,6 @@ static AwsIotShadowError_t _codeToShadowStatus( uint32_t code ) /*-----------------------------------------------------------*/ -_shadowOperationStatus_t _AwsIotShadow_ParseShadowStatus( const char * pTopicName, - size_t topicNameLength ) -{ - IOT_FUNCTION_ENTRY( _shadowOperationStatus_t, UNKNOWN_STATUS ); - const char * pSuffixStart = NULL; - - /* Check that the Shadow status topic name is at least as long as the - * "accepted" suffix. */ - if( topicNameLength > SHADOW_ACCEPTED_SUFFIX_LENGTH ) - { - /* Calculate where the "accepted" suffix should start. */ - pSuffixStart = pTopicName + topicNameLength - SHADOW_ACCEPTED_SUFFIX_LENGTH; - - /* pSuffixStart must be in pTopicName. */ - AwsIotShadow_Assert( ( pSuffixStart > pTopicName ) && - ( pSuffixStart < pTopicName + topicNameLength ) ); - - /* Check if the end of the Shadow status topic name is "accepted". */ - if( strncmp( pSuffixStart, - SHADOW_ACCEPTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ) == 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( SHADOW_ACCEPTED ); - } - } - - /* Check that the Shadow status topic name is at least as long as the - * "rejected" suffix. */ - if( topicNameLength > SHADOW_REJECTED_SUFFIX_LENGTH ) - { - /* Calculate where the "rejected" suffix should start. */ - pSuffixStart = pTopicName + topicNameLength - SHADOW_REJECTED_SUFFIX_LENGTH; - - /* pSuffixStart must be in pTopicName. */ - AwsIotShadow_Assert( ( pSuffixStart > pTopicName ) && - ( pSuffixStart < pTopicName + topicNameLength ) ); - - /* Check if the end of the Shadow status topic name is "rejected". */ - if( strncmp( pSuffixStart, - SHADOW_REJECTED_SUFFIX, - SHADOW_REJECTED_SUFFIX_LENGTH ) == 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( SHADOW_REJECTED ); - } - } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ) { diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 49dda12a3a..a8d9975c17 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -383,9 +383,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe { /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_ACCEPTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); /* There should not be an active subscription for the accepted topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -407,9 +407,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_REJECTED_SUFFIX, - SHADOW_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_REJECTED_SUFFIX_LENGTH ); + AWS_IOT_REJECTED_SUFFIX, + AWS_IOT_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_REJECTED_SUFFIX_LENGTH ); /* There should not be an active subscription for the rejected topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -429,9 +429,9 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Failed to add subscription to Shadow "rejected" topic. Remove * subscription for the Shadow "accepted" topic. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_ACCEPTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, pTopicBuffer, @@ -509,9 +509,9 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_ACCEPTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); /* There should be an active subscription for the accepted topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -528,9 +528,9 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ ( void ) memcpy( pTopicBuffer + operationTopicLength, - SHADOW_REJECTED_SUFFIX, - SHADOW_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + AWS_IOT_REJECTED_SUFFIX, + AWS_IOT_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); /* There should be an active subscription for the accepted topic. */ AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, @@ -626,9 +626,9 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Remove the "accepted" topic. */ ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, - SHADOW_ACCEPTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + SHADOW_ACCEPTED_SUFFIX_LENGTH ); + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); removeAcceptedStatus = _modifyOperationSubscriptions( mqttConnection, pSubscription->pTopicBuffer, @@ -643,10 +643,10 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Remove the "rejected" topic. */ ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, - SHADOW_REJECTED_SUFFIX, - SHADOW_ACCEPTED_SUFFIX_LENGTH ); + AWS_IOT_REJECTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); topicFilterLength = ( uint16_t ) ( operationTopicLength + - SHADOW_REJECTED_SUFFIX_LENGTH ); + AWS_IOT_REJECTED_SUFFIX_LENGTH ); removeRejectedStatus = _modifyOperationSubscriptions( mqttConnection, pSubscription->pTopicBuffer, diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 37dcce1f4e..ae2d8f42ff 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -188,17 +188,17 @@ TEST_GROUP_RUNNER( Shadow_Unit_Parser ) */ TEST( Shadow_Unit_Parser, StatusValid ) { - _shadowOperationStatus_t status = UNKNOWN_STATUS; + AwsIotStatus_t status = AWS_IOT_UNKNOWN; /* Parse "accepted" status. */ - status = _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/accepted", - 39 ); - TEST_ASSERT_EQUAL( SHADOW_ACCEPTED, status ); + status = AwsIot_ParseStatus( "$aws/things/Test_device/shadow/accepted", + 39 ); + TEST_ASSERT_EQUAL( AWS_IOT_ACCEPTED, status ); /* Parse "rejected" status. */ - status = _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/rejected", - 39 ); - TEST_ASSERT_EQUAL( SHADOW_REJECTED, status ); + status = AwsIot_ParseStatus( "$aws/things/Test_device/shadow/rejected", + 39 ); + TEST_ASSERT_EQUAL( AWS_IOT_REJECTED, status ); } /*-----------------------------------------------------------*/ @@ -209,24 +209,24 @@ TEST( Shadow_Unit_Parser, StatusValid ) TEST( Shadow_Unit_Parser, StatusInvalid ) { /* Topic too short. */ - TEST_ASSERT_EQUAL( UNKNOWN_STATUS, - _AwsIotShadow_ParseShadowStatus( "accepted", - 8 ) ); + TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, + AwsIot_ParseStatus( "accepted", + 8 ) ); /* Topic missing last character. */ - TEST_ASSERT_EQUAL( UNKNOWN_STATUS, - _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/accepte", - 38 ) ); + TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, + AwsIot_ParseStatus( "$aws/things/Test_device/shadow/accepte", + 38 ) ); /* Topic missing level separator. */ - TEST_ASSERT_EQUAL( UNKNOWN_STATUS, - _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadowaccepted", - 38 ) ); + TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, + AwsIot_ParseStatus( "$aws/things/Test_device/shadowaccepted", + 38 ) ); /* Topic suffix isn't "accepted" or "rejected". */ - TEST_ASSERT_EQUAL( UNKNOWN_STATUS, - _AwsIotShadow_ParseShadowStatus( "$aws/things/Test_device/shadow/unknown", - 38 ) ); + TEST_ASSERT_EQUAL( AWS_IOT_UNKNOWN, + AwsIot_ParseStatus( "$aws/things/Test_device/shadow/unknown", + 38 ) ); } /*-----------------------------------------------------------*/ From 1be6ac5957db4c7c5c5a764eaa70b4c5ffad60bb Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 17 Jun 2019 16:26:43 -0400 Subject: [PATCH 176/844] Move Shadow Thing Name parsing to common (#461) --- lib/include/private/aws_iot.h | 33 ++++++++-- lib/include/private/aws_iot_shadow_internal.h | 26 -------- lib/source/common/aws_iot/aws_iot_parser.c | 63 +++++++++++++++++++ lib/source/shadow/aws_iot_shadow_operation.c | 16 ++--- lib/source/shadow/aws_iot_shadow_parser.c | 60 ------------------ .../shadow/unit/aws_iot_tests_shadow_parser.c | 24 +++---- 6 files changed, 112 insertions(+), 110 deletions(-) diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index e7ba199b23..e2209939ad 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -39,7 +39,17 @@ * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). */ -#define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) +#define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) + +/** + * @brief The common prefix of all AWS IoT MQTT topics. + */ +#define AWS_IOT_TOPIC_PREFIX "$aws/things/" + +/** + * @brief The length of #AWS_IOT_TOPIC_PREFIX. + */ +#define AWS_IOT_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) ) /** * @brief The suffix for an AWS IoT operation "accepted" topic. @@ -67,9 +77,9 @@ */ typedef enum AwsIotStatus { - AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */ - AWS_IOT_REJECTED = 1, /**< Operation rejected. */ - AWS_IOT_UNKNOWN = 2 /**< Unknown status (neither accepted nor rejected). */ + AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */ + AWS_IOT_REJECTED = 1, /**< Operation rejected. */ + AWS_IOT_UNKNOWN = 2 /**< Unknown status (neither accepted nor rejected). */ } AwsIotStatus_t; /** @@ -83,6 +93,21 @@ typedef enum AwsIotStatus bool AwsIot_ValidateThingName( const char * pThingName, size_t thingNameLength ); +/** + * @brief Parse the Thing Name from an MQTT topic. + * + * @param[in] pTopicName The topic to parse. + * @param[in] topicNameLength The length of `pTopicName`. + * @param[out] pThingName Set to point to the Thing Name. + * @param[out] pThingNameLength Set to the length of the Thing Name. + * + * @return `true` if a Thing Name was successfully parsed; `false` otherwise. + */ +bool AwsIot_ParseThingName( const char * pTopicName, + uint16_t topicNameLength, + const char ** pThingName, + size_t * pThingNameLength ); + /** * @brief Parse the operation status (accepted or rejected) from an MQTT topic. * diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index a436c1a870..8738748a0e 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -177,17 +177,6 @@ */ #define SHADOW_CALLBACK_COUNT ( 2 ) -/** - * @brief The common prefix of all Shadow MQTT topics, per the [AWS IoT Shadow - * spec](https://docs.aws.amazon.com/iot/latest/developerguide/device-shadow-mqtt.html). - */ -#define SHADOW_TOPIC_PREFIX "$aws/things/" - -/** - * @brief The length of #SHADOW_TOPIC_PREFIX. - */ -#define SHADOW_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( SHADOW_TOPIC_PREFIX ) - 1 ) ) - /** * @brief The string representing a Shadow DELETE operation in a Shadow MQTT topic. */ @@ -584,21 +573,6 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, /*------------------------- Shadow parser functions -------------------------*/ -/** - * @brief Parse the Thing Name from a Shadow topic. - * - * @param[in] pTopicName The topic to parse. - * @param[in] topicNameLength The length of `pTopicName`. - * @param[out] pThingName Set to point to the Thing Name. - * @param[out] pThingNameLength Set to the length of the Thing Name. - * - * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_BAD_RESPONSE. - */ -AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, - uint16_t topicNameLength, - const char ** pThingName, - size_t * pThingNameLength ); - /** * @brief Parse a Shadow error document. * diff --git a/lib/source/common/aws_iot/aws_iot_parser.c b/lib/source/common/aws_iot/aws_iot_parser.c index 4113847cb1..0880927527 100644 --- a/lib/source/common/aws_iot/aws_iot_parser.c +++ b/lib/source/common/aws_iot/aws_iot_parser.c @@ -36,6 +36,69 @@ /* Error handling include. */ #include "private/iot_error.h" +/** + * @brief Minimum allowed topic length for an AWS IoT status topic. + * + * Topics must contain at least: + * - The common prefix + * - The suffix "/accepted" or "/rejected" + * - 1 character for the Thing Name + * - 2 characters for the operation name and the enclosing slashes + */ +#define MINIMUM_TOPIC_NAME_LENGTH \ + ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH + \ + AWS_IOT_ACCEPTED_SUFFIX_LENGTH + \ + 1 + 2 ) + +/*-----------------------------------------------------------*/ + +bool AwsIot_ParseThingName( const char * pTopicName, + uint16_t topicNameLength, + const char ** pThingName, + size_t * pThingNameLength ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + const char * pThingNameStart = NULL; + size_t thingNameLength = 0; + + /* Check that the topic name is at least as long as the minimum allowed. */ + if( topicNameLength < MINIMUM_TOPIC_NAME_LENGTH ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that the given topic starts with the common prefix. */ + if( strncmp( AWS_IOT_TOPIC_PREFIX, + pTopicName, + AWS_IOT_TOPIC_PREFIX_LENGTH ) != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* The Thing Name starts immediately after the topic prefix. */ + pThingNameStart = pTopicName + AWS_IOT_TOPIC_PREFIX_LENGTH; + + /* Calculate the length of the Thing Name, which is terminated with a '/'. */ + while( ( thingNameLength + AWS_IOT_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) && + ( pThingNameStart[ thingNameLength ] != '/' ) ) + { + thingNameLength++; + } + + /* The end of the topic name was reached without finding a '/'. The topic + * name is invalid. */ + if( thingNameLength + AWS_IOT_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Set the output parameters. */ + *pThingName = pThingNameStart; + *pThingNameLength = thingNameLength; + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + /*-----------------------------------------------------------*/ AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index ff66a06b37..4d8e0b6b97 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -236,10 +236,10 @@ static void _commonOperationCallback( _shadowOperationType_t type, } /* Parse the Thing Name from the MQTT topic name. */ - if( _AwsIotShadow_ParseThingName( pMessage->u.message.info.pTopicName, - pMessage->u.message.info.topicNameLength, - &( param.pThingName ), - &( param.thingNameLength ) ) != AWS_IOT_SHADOW_SUCCESS ) + if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength, + &( param.pThingName ), + &( param.thingNameLength ) ) == false ) { return; } @@ -553,7 +553,7 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty ( type == SHADOW_UPDATE ) ); /* Calculate the required topic buffer length. */ - bufferLength = ( uint16_t ) ( SHADOW_TOPIC_PREFIX_LENGTH + + bufferLength = ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH + thingNameLength + pOperationStringLength[ type ] + SHADOW_LONGEST_SUFFIX_LENGTH ); @@ -574,9 +574,9 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty pBuffer = *pTopicBuffer; } - /* Copy the Shadow topic prefix into the topic buffer. */ - ( void ) memcpy( pBuffer, SHADOW_TOPIC_PREFIX, SHADOW_TOPIC_PREFIX_LENGTH ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + SHADOW_TOPIC_PREFIX_LENGTH ); + /* Copy the AWS IoT topic prefix into the topic buffer. */ + ( void ) memcpy( pBuffer, AWS_IOT_TOPIC_PREFIX, AWS_IOT_TOPIC_PREFIX_LENGTH ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_TOPIC_PREFIX_LENGTH ); /* Copy the Thing Name into the topic buffer. */ ( void ) memcpy( pBuffer + operationTopicLength, pThingName, thingNameLength ); diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index 642efa7590..c0b4fe3c0f 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -62,17 +62,6 @@ */ #define ERROR_DOCUMENT_MESSAGE_KEY_LENGTH ( sizeof( ERROR_DOCUMENT_MESSAGE_KEY ) - 1 ) -/** - * @brief The minimum possible length of a Shadow topic name, per the Shadow - * spec. - */ -#define MINIMUM_SHADOW_TOPIC_NAME_LENGTH \ - ( SHADOW_TOPIC_PREFIX_LENGTH + \ - ( uint16_t ) sizeof( SHADOW_GET_OPERATION_STRING ) + \ - ( AWS_IOT_ACCEPTED_SUFFIX_LENGTH < AWS_IOT_REJECTED_SUFFIX_LENGTH ? \ - AWS_IOT_ACCEPTED_SUFFIX_LENGTH : \ - AWS_IOT_REJECTED_SUFFIX_LENGTH ) ) - /*-----------------------------------------------------------*/ /** @@ -203,52 +192,3 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen } /*-----------------------------------------------------------*/ - -AwsIotShadowError_t _AwsIotShadow_ParseThingName( const char * pTopicName, - uint16_t topicNameLength, - const char ** pThingName, - size_t * pThingNameLength ) -{ - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); - const char * pThingNameStart = NULL; - size_t thingNameLength = 0; - - /* Check that the topic name length exceeds the minimum possible length. */ - if( topicNameLength < MINIMUM_SHADOW_TOPIC_NAME_LENGTH ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); - } - - /* All Shadow topic names must start with the same prefix. */ - if( strncmp( SHADOW_TOPIC_PREFIX, - pTopicName, - SHADOW_TOPIC_PREFIX_LENGTH ) != 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); - } - - /* The Thing Name starts immediately after the topic prefix. */ - pThingNameStart = pTopicName + SHADOW_TOPIC_PREFIX_LENGTH; - - /* Calculate the length of the Thing Name. */ - while( ( thingNameLength + SHADOW_TOPIC_PREFIX_LENGTH < ( size_t ) topicNameLength ) && - ( pThingNameStart[ thingNameLength ] != '/' ) ) - { - thingNameLength++; - } - - /* The end of the topic name was reached without finding a '/'. The topic - * name is invalid. */ - if( thingNameLength + SHADOW_TOPIC_PREFIX_LENGTH >= ( size_t ) topicNameLength ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_RESPONSE ); - } - - /* Set the output parameters. */ - *pThingName = pThingNameStart; - *pThingNameLength = thingNameLength; - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index ae2d8f42ff..e17368353e 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -115,21 +115,21 @@ static void _generateParseErrorDocument( char * pErrorDocument, * @brief Wrapper for parsing Shadow Thing Names and checking the result. */ static void _parseThingName( const char * pTopicName, - AwsIotShadowError_t expectedResult, + bool expectedResult, const char * pExpectedThingName ) { - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + bool status = true; uint16_t topicNameLength = ( uint16_t ) strlen( pTopicName ); const char * pThingName = NULL; size_t thingNameLength = 0; - status = _AwsIotShadow_ParseThingName( pTopicName, - topicNameLength, - &pThingName, - &thingNameLength ); + status = AwsIot_ParseThingName( pTopicName, + topicNameLength, + &pThingName, + &thingNameLength ); TEST_ASSERT_EQUAL( expectedResult, status ); - if( expectedResult == AWS_IOT_SHADOW_SUCCESS ) + if( expectedResult == true ) { TEST_ASSERT_EQUAL( strlen( pExpectedThingName ), thingNameLength ); TEST_ASSERT_EQUAL_STRING_LEN( pExpectedThingName, pThingName, thingNameLength ); @@ -432,27 +432,27 @@ TEST( Shadow_Unit_Parser, ThingName ) { /* Valid operation topic. */ _parseThingName( "$aws/things/TEST/shadow/get/accepted", - AWS_IOT_SHADOW_SUCCESS, + true, "TEST" ); /* Valid callback topic. */ _parseThingName( "$aws/things/TEST/shadow/update/delta", - AWS_IOT_SHADOW_SUCCESS, + true, "TEST" ); /* Topic too short. */ _parseThingName( "$aws/things/TEST/", - AWS_IOT_SHADOW_BAD_RESPONSE, + false, "TEST" ); /* Incorrect prefix. */ _parseThingName( "$awsshadow/TEST/shadow/update/accepted", - AWS_IOT_SHADOW_BAD_RESPONSE, + false, "TEST" ); /* Thing Name unterminated. */ _parseThingName( "$aws/things/TESTTESTTESTTESTTESTTEST", - AWS_IOT_SHADOW_BAD_RESPONSE, + false, "TESTTESTTESTTESTTESTTEST" ); } From 2fbdb92c7ab8603eb95fa891a8d108183ea9e6db Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 18 Jun 2019 11:01:28 -0400 Subject: [PATCH 177/844] Move client token parsing to AWS IoT common (#462) --- CMakeLists.txt | 2 +- lib/include/private/aws_iot.h | 23 ++++++++- lib/include/private/aws_iot_shadow_internal.h | 16 ------- lib/source/common/aws_iot/aws_iot_parser.c | 47 +++++++++++++++++++ lib/source/shadow/aws_iot_shadow_api.c | 27 ++--------- lib/source/shadow/aws_iot_shadow_operation.c | 13 ++--- 6 files changed, 78 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eab7e0136f..963e952e05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,7 +138,7 @@ add_library( iotbase ${COMMON_TYPES_HEADERS} ) # Link required libraries. -target_link_libraries( iotbase PRIVATE iotplatform iotcommon ) +target_link_libraries( iotbase PRIVATE iotplatform iotcommon iotserializer ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotbase PRIVATE unity ) diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index e2209939ad..6fb6f4cd3d 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -44,12 +44,12 @@ /** * @brief The common prefix of all AWS IoT MQTT topics. */ -#define AWS_IOT_TOPIC_PREFIX "$aws/things/" +#define AWS_IOT_TOPIC_PREFIX "$aws/things/" /** * @brief The length of #AWS_IOT_TOPIC_PREFIX. */ -#define AWS_IOT_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) ) +#define AWS_IOT_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) ) /** * @brief The suffix for an AWS IoT operation "accepted" topic. @@ -93,6 +93,25 @@ typedef enum AwsIotStatus bool AwsIot_ValidateThingName( const char * pThingName, size_t thingNameLength ); +/** + * @brief Extracts the client token from a JSON document. + * + * The client token is used to differentiate AWS IoT operations. It is unique per + * operation. + * + * @param[in] pJsonDocument The JSON document to parse. + * @param[in] jsonDocumentLength The length of `pJsonDocument`. + * @param[out] pClientToken Set to point to the client token in `pJsonDocument`. + * @param[out] pClientTokenLength Set to the length of the client token. + * + * @return `true` if the client token was found; `false` otherwise. The output + * parameters are only valid if this function returns `true`. + */ +bool AwsIot_GetClientToken( const char * pJsonDocument, + size_t jsonDocumentLength, + const char ** pClientToken, + size_t * pClientTokenLength ); + /** * @brief Parse the Thing Name from an MQTT topic. * diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 8738748a0e..ae19189bea 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -232,22 +232,6 @@ */ #define SHADOW_LONGEST_SUFFIX_LENGTH SHADOW_UPDATED_SUFFIX_LENGTH -/** - * @brief The JSON key used to represent client tokens in a Shadow update document. - */ -#define CLIENT_TOKEN_KEY "clientToken" - -/** - * @brief The length of #CLIENT_TOKEN_KEY. - */ -#define CLIENT_TOKEN_KEY_LENGTH ( sizeof( CLIENT_TOKEN_KEY ) - 1 ) - -/** - * @brief The longest client token accepted by the Shadow service, per AWS IoT - * service limits. - */ -#define MAX_CLIENT_TOKEN_LENGTH ( 64 ) - /** * @brief A flag to represent persistent subscriptions in a Shadow subscriptions * object. diff --git a/lib/source/common/aws_iot/aws_iot_parser.c b/lib/source/common/aws_iot/aws_iot_parser.c index 0880927527..5f3a8841f6 100644 --- a/lib/source/common/aws_iot/aws_iot_parser.c +++ b/lib/source/common/aws_iot/aws_iot_parser.c @@ -36,6 +36,9 @@ /* Error handling include. */ #include "private/iot_error.h" +/* JSON utils include. */ +#include "iot_json_utils.h" + /** * @brief Minimum allowed topic length for an AWS IoT status topic. * @@ -50,6 +53,50 @@ AWS_IOT_ACCEPTED_SUFFIX_LENGTH + \ 1 + 2 ) +/** + * @brief The JSON key used to represent client tokens for AWS IoT. + */ +#define CLIENT_TOKEN_KEY "clientToken" + +/** + * @brief The length of #CLIENT_TOKEN_KEY. + */ +#define CLIENT_TOKEN_KEY_LENGTH ( sizeof( CLIENT_TOKEN_KEY ) - 1 ) + +/** + * @brief The longest client token accepted by AWS IoT service, per AWS IoT + * service limits. + */ +#define MAX_CLIENT_TOKEN_LENGTH ( 64 ) + +/*-----------------------------------------------------------*/ + +bool AwsIot_GetClientToken( const char * pJsonDocument, + size_t jsonDocumentLength, + const char ** pClientToken, + size_t * pClientTokenLength ) +{ + /* Extract the client token from the JSON document. */ + bool status = IotJsonUtils_FindJsonValue( pJsonDocument, + jsonDocumentLength, + CLIENT_TOKEN_KEY, + CLIENT_TOKEN_KEY_LENGTH, + pClientToken, + pClientTokenLength ); + + if( status == true ) + { + /* Check that the length of the client token is valid. */ + if( ( *pClientTokenLength < 2 ) || + ( *pClientTokenLength > MAX_CLIENT_TOKEN_LENGTH ) ) + { + status = false; + } + } + + return status; +} + /*-----------------------------------------------------------*/ bool AwsIot_ParseThingName( const char * pTopicName, diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index d187262d03..9b5aa78f38 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -39,9 +39,6 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" -/* JSON utilities include. */ -#include "iot_json_utils.h" - /* MQTT include. */ #include "iot_mqtt.h" @@ -930,26 +927,12 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, } /* Check UPDATE document for a client token. */ - if( IotJsonUtils_FindJsonValue( pUpdateInfo->u.update.pUpdateDocument, - pUpdateInfo->u.update.updateDocumentLength, - CLIENT_TOKEN_KEY, - CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ) == false ) - { - IotLogError( "Shadow document for Shadow UPDATE must have a %s key.", - CLIENT_TOKEN_KEY ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); - } - - /* Check the client token length. It must be greater than the length of its - * enclosing double quotes (2) and less than the maximum allowed by the Shadow - * service. */ - if( ( clientTokenLength < 2 ) || ( clientTokenLength > MAX_CLIENT_TOKEN_LENGTH ) ) + if( AwsIot_GetClientToken( pUpdateInfo->u.update.pUpdateDocument, + pUpdateInfo->u.update.updateDocumentLength, + &pClientToken, + &clientTokenLength ) == false ) { - IotLogError( "Client token length must be between 2 and %d (including " - "enclosing quotes).", MAX_CLIENT_TOKEN_LENGTH + 2 ); + IotLogError( "Shadow document for Shadow UPDATE does not contain a valid client token." ); IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_BAD_PARAMETER ); } diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 4d8e0b6b97..4c2ff28269 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -36,9 +36,6 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" -/* JSON utils include. */ -#include "iot_json_utils.h" - /* MQTT include. */ #include "iot_mqtt.h" @@ -186,12 +183,10 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, IotLogDebug( "Verifying client tokens for Shadow UPDATE." ); /* Check for the client token in the UPDATE response document. */ - match = IotJsonUtils_FindJsonValue( pParam->pDocument, - pParam->documentLength, - CLIENT_TOKEN_KEY, - CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ); + match = AwsIot_GetClientToken( pParam->pDocument, + pParam->documentLength, + &pClientToken, + &clientTokenLength ); /* If the UPDATE response document has a client token, check that it * matches. */ From 27a0349280d60fa388947d1fbaa281b184144b92 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 18 Jun 2019 11:26:40 -0400 Subject: [PATCH 178/844] Separate AWS IoT common from common (#463) --- CMakeLists.txt | 2 +- lib/source/common/CMakeLists.txt | 31 +++++++++++++++++++++++------- lib/source/defender/CMakeLists.txt | 2 +- lib/source/jobs/CMakeLists.txt | 2 +- lib/source/shadow/CMakeLists.txt | 2 +- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 963e952e05..eab7e0136f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,7 +138,7 @@ add_library( iotbase ${COMMON_TYPES_HEADERS} ) # Link required libraries. -target_link_libraries( iotbase PRIVATE iotplatform iotcommon iotserializer ) +target_link_libraries( iotbase PRIVATE iotplatform iotcommon ) if( ${IOT_BUILD_TESTS} ) target_link_libraries( iotbase PRIVATE unity ) diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 6f8db38629..733884e442 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -6,10 +6,6 @@ set( COMMON_SOURCES ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool.c ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool_static_memory.c ) -set( AWS_IOT_COMMON_SOURCES - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c ) - # Lists of common header files. These are only used for directory organization # (not for build). set( COMMON_PUBLIC_HEADERS @@ -23,7 +19,6 @@ set( COMMON_PRIVATE_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/private/iot_logging.h ${PROJECT_SOURCE_DIR}/lib/include/private/iot_static_memory.h ${PROJECT_SOURCE_DIR}/lib/include/private/iot_taskpool_internal.h - ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot.h PARENT_SCOPE ) set( COMMON_TYPES_HEADERS ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h @@ -33,9 +28,31 @@ set( COMMON_TYPES_HEADERS add_library( iotcommon INTERFACE ) target_sources( iotcommon INTERFACE - ${COMMON_SOURCES} - ${AWS_IOT_COMMON_SOURCES} ) + ${COMMON_SOURCES} ) # Set common sources in the parent scope for directory organization. set( COMMON_SOURCES ${COMMON_SOURCES} PARENT_SCOPE ) set( AWS_IOT_COMMON_SOURCES ${AWS_IOT_COMMON_SOURCES} PARENT_SCOPE ) + +# AWS IoT common sources. +set( AWS_IOT_COMMON_SOURCES + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c ) + +# AWS IoT common library target. +add_library( awsiotcommon + ${AWS_IOT_COMMON_SOURCES} + ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot.h ) + +# Link required libraries. +target_link_libraries( awsiotcommon + PUBLIC iotmqtt iotserializer ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotcommon PRIVATE unity ) +endif() + +# Organization of AWS IoT common sources in folders. +set_property( TARGET awsiotcommon PROPERTY FOLDER "lib" ) +source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot.h ) +source_group( source FILES ${AWS_IOT_COMMON_SOURCES} ) diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt index ea5d5d9813..13e323b90d 100644 --- a/lib/source/defender/CMakeLists.txt +++ b/lib/source/defender/CMakeLists.txt @@ -11,7 +11,7 @@ add_library( awsiotdefender ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_defender_internal.h ) # Link required libraries. -target_link_libraries( awsiotdefender PUBLIC iotserializer iotmqtt ) +target_link_libraries( awsiotdefender PUBLIC awsiotcommon ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt index 76ac373075..d978479683 100644 --- a/lib/source/jobs/CMakeLists.txt +++ b/lib/source/jobs/CMakeLists.txt @@ -10,7 +10,7 @@ add_library( awsiotjobs ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_jobs_internal.h ) # Link required libraries. -target_link_libraries( awsiotjobs PUBLIC iotmqtt ) +target_link_libraries( awsiotjobs PUBLIC awsiotcommon ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt index 85bab5822c..bfb82f99b5 100644 --- a/lib/source/shadow/CMakeLists.txt +++ b/lib/source/shadow/CMakeLists.txt @@ -14,7 +14,7 @@ add_library( awsiotshadow ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_shadow_internal.h ) # Link required libraries. -target_link_libraries( awsiotshadow PUBLIC iotserializer iotmqtt ) +target_link_libraries( awsiotshadow PUBLIC awsiotcommon ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) From 67fcca46539a6dec1c411050909733390cb9fb48 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 18 Jun 2019 16:26:46 -0400 Subject: [PATCH 179/844] Make function for generating AWS IoT topics common (#464) --- lib/include/private/aws_iot.h | 39 +++- lib/source/common/CMakeLists.txt | 3 +- lib/source/common/aws_iot/aws_iot_operation.c | 99 ++++++++++ lib/source/shadow/aws_iot_shadow_operation.c | 173 ++++++++---------- 4 files changed, 214 insertions(+), 100 deletions(-) create mode 100644 lib/source/common/aws_iot/aws_iot_operation.c diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 6fb6f4cd3d..392168794d 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -77,11 +77,24 @@ */ typedef enum AwsIotStatus { - AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */ - AWS_IOT_REJECTED = 1, /**< Operation rejected. */ - AWS_IOT_UNKNOWN = 2 /**< Unknown status (neither accepted nor rejected). */ + AWS_IOT_ACCEPTED = 0, /**< Operation accepted. */ + AWS_IOT_REJECTED = 1, /**< Operation rejected. */ + AWS_IOT_UNKNOWN = 2 /**< Unknown status (neither accepted nor rejected). */ } AwsIotStatus_t; +/** + * @brief Information required to generate a topic for AWS IoT. + */ +typedef struct AwsIotTopicInfo +{ + const char * pThingName; /**< @brief The Thing Name associated with the operation. */ + size_t thingNameLength; /**< @brief The length of `pThingName`. */ + const char * pOperationName; /**< @brief The operation name to place in the topic. */ + uint16_t operationNameLength; /**< @brief The length of `pOperationName`. */ + uint16_t longestSuffixLength; /**< @brief The length of longest suffix that will be placed at the end of the topic. */ + void * ( *mallocString )( size_t size ); /**< @brief Function used to allocate a string, if needed. */ +} AwsIotTopicInfo_t; + /** * @brief Checks that a Thing Name is valid for AWS IoT. * @@ -138,4 +151,24 @@ bool AwsIot_ParseThingName( const char * pTopicName, AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, uint16_t topicNameLength ); +/** + * @brief Generate a topic to use for an AWS IoT operation. + * + * @param[in] pTopicInfo Information needed to generate an AWS IoT topic. + * @param[in,out] pTopicBuffer Where to place the generated topic. An existing + * buffer may be passed in. If `NULL`, this function will attempt to allocate a + * new buffer. + * @param[out] pOperationTopicLength Set to the length of the generated topic. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must be long enough to accomodate the Thing Name, operation name, and + * any other suffixes. + * + * @return `true` if the topic was successfully generated; `false` otherwise. + * This function will always succeed when an input buffer is provided. + */ +bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, + char ** pTopicBuffer, + uint16_t * pOperationTopicLength ); + #endif /* ifndef AWS_IOT_H_ */ diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 733884e442..756151cadf 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -37,7 +37,8 @@ set( AWS_IOT_COMMON_SOURCES ${AWS_IOT_COMMON_SOURCES} PARENT_SCOPE ) # AWS IoT common sources. set( AWS_IOT_COMMON_SOURCES ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c ) + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_operation.c ) # AWS IoT common library target. add_library( awsiotcommon diff --git a/lib/source/common/aws_iot/aws_iot_operation.c b/lib/source/common/aws_iot/aws_iot_operation.c new file mode 100644 index 0000000000..a12523bea1 --- /dev/null +++ b/lib/source/common/aws_iot/aws_iot_operation.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_operation.c + * @brief Functions for common AWS IoT operations. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* AWS IoT include. */ +#include "private/aws_iot.h" + +/*-----------------------------------------------------------*/ + +bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, + char ** pTopicBuffer, + uint16_t * pOperationTopicLength ) +{ + bool status = true; + uint16_t bufferLength = 0; + uint16_t operationTopicLength = 0; + char * pBuffer = NULL; + + /* Calculate the required topic buffer length. */ + bufferLength = ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH + + pTopicInfo->thingNameLength + + pTopicInfo->operationNameLength + + pTopicInfo->longestSuffixLength ); + + /* Allocate memory for the topic buffer if no topic buffer is given. */ + if( *pTopicBuffer == NULL ) + { + pBuffer = pTopicInfo->mallocString( ( size_t ) bufferLength ); + + if( pBuffer == NULL ) + { + status = false; + } + } + /* Otherwise, use the given topic buffer. */ + else + { + pBuffer = *pTopicBuffer; + } + + if( status == true ) + { + /* Copy the AWS IoT topic prefix into the topic buffer. */ + ( void ) memcpy( pBuffer, AWS_IOT_TOPIC_PREFIX, AWS_IOT_TOPIC_PREFIX_LENGTH ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_TOPIC_PREFIX_LENGTH ); + + /* Copy the Thing Name into the topic buffer. */ + ( void ) memcpy( pBuffer + operationTopicLength, + pTopicInfo->pThingName, + pTopicInfo->thingNameLength ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + pTopicInfo->thingNameLength ); + + /* Copy the Shadow operation string into the topic buffer. */ + ( void ) memcpy( pBuffer + operationTopicLength, + pTopicInfo->pOperationName, + pTopicInfo->operationNameLength ); + operationTopicLength = ( uint16_t ) ( operationTopicLength + pTopicInfo->operationNameLength ); + + /* Set the output parameters. */ + if( *pTopicBuffer == NULL ) + { + *pTopicBuffer = pBuffer; + } + + *pOperationTopicLength = operationTopicLength; + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 4c2ff28269..703f69a08c 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -39,6 +39,9 @@ /* MQTT include. */ #include "iot_mqtt.h" +/* Error handling include. */ +#include "private/iot_error.h" + /*-----------------------------------------------------------*/ /** @@ -427,6 +430,7 @@ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOpe uint32_t flags, const AwsIotShadowCallbackInfo_t * pCallbackInfo ) { + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); _shadowOperation_t * pOperation = NULL; IotLogDebug( "Creating operation record for Shadow %s.", @@ -440,7 +444,7 @@ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOpe IotLogError( "Failed to allocate memory for Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } /* Clear the operation data. */ @@ -455,9 +459,7 @@ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOpe IotLogError( "Failed to create semaphore for waitable Shadow %s.", _pAwsIotShadowOperationNames[ type ] ); - AwsIotShadow_FreeOperation( pOperation ); - - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } } else @@ -475,10 +477,22 @@ AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOpe pOperation->flags = flags; pOperation->status = AWS_IOT_SHADOW_STATUS_PENDING; - /* Set the output parameter. */ - *pNewOperation = pOperation; + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status != AWS_IOT_SHADOW_SUCCESS ) + { + if( pOperation != NULL ) + { + AwsIotShadow_FreeOperation( pOperation ); + } + } + else + { + /* Set the output parameter. */ + *pNewOperation = pOperation; + } - return AWS_IOT_SHADOW_SUCCESS; + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ @@ -521,9 +535,8 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty char ** pTopicBuffer, uint16_t * pOperationTopicLength ) { - uint16_t bufferLength = 0; - uint16_t operationTopicLength = 0; - char * pBuffer = NULL; + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + AwsIotTopicInfo_t topicInfo = { 0 }; /* Lookup table for Shadow operation strings. */ const char * const pOperationString[ SHADOW_OPERATION_COUNT ] = @@ -547,54 +560,22 @@ AwsIotShadowError_t _AwsIotShadow_GenerateShadowTopic( _shadowOperationType_t ty ( type == SHADOW_GET ) || ( type == SHADOW_UPDATE ) ); - /* Calculate the required topic buffer length. */ - bufferLength = ( uint16_t ) ( AWS_IOT_TOPIC_PREFIX_LENGTH + - thingNameLength + - pOperationStringLength[ type ] + - SHADOW_LONGEST_SUFFIX_LENGTH ); - - /* Allocate memory for the topic buffer if no topic buffer is given. */ - if( *pTopicBuffer == NULL ) + /* Set the members needed to generate an operation topic. */ + topicInfo.pThingName = pThingName; + topicInfo.thingNameLength = thingNameLength; + topicInfo.pOperationName = pOperationString[ type ]; + topicInfo.operationNameLength = pOperationStringLength[ type ]; + topicInfo.longestSuffixLength = SHADOW_LONGEST_SUFFIX_LENGTH; + topicInfo.mallocString = AwsIotShadow_MallocString; + + if( AwsIot_GenerateOperationTopic( &topicInfo, + pTopicBuffer, + pOperationTopicLength ) == false ) { - pBuffer = AwsIotShadow_MallocString( ( size_t ) bufferLength ); - - if( pBuffer == NULL ) - { - return AWS_IOT_SHADOW_NO_MEMORY; - } - } - /* Otherwise, use the given topic buffer. */ - else - { - pBuffer = *pTopicBuffer; - } - - /* Copy the AWS IoT topic prefix into the topic buffer. */ - ( void ) memcpy( pBuffer, AWS_IOT_TOPIC_PREFIX, AWS_IOT_TOPIC_PREFIX_LENGTH ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_TOPIC_PREFIX_LENGTH ); - - /* Copy the Thing Name into the topic buffer. */ - ( void ) memcpy( pBuffer + operationTopicLength, pThingName, thingNameLength ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + thingNameLength ); - - /* Copy the Shadow operation string into the topic buffer. */ - ( void ) memcpy( pBuffer + operationTopicLength, - pOperationString[ type ], - pOperationStringLength[ type ] ); - operationTopicLength = ( uint16_t ) ( operationTopicLength + pOperationStringLength[ type ] ); - - /* Ensure that the topic length is in the topic buffer. */ - AwsIotShadow_Assert( operationTopicLength < bufferLength ); - - /* Set the output parameters. */ - if( *pTopicBuffer == NULL ) - { - *pTopicBuffer = pBuffer; + status = AWS_IOT_SHADOW_NO_MEMORY; } - *pOperationTopicLength = operationTopicLength; - - return AWS_IOT_SHADOW_SUCCESS; + return status; } /*-----------------------------------------------------------*/ @@ -823,56 +804,56 @@ void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) { IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - - return; } - - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pSubscription->pTopicBuffer, - &pRemovedSubscription ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - /* Set the subscription pointer used for the user callback based on whether - * a subscription was removed from the list. */ - if( pRemovedSubscription != NULL ) + else { - pSubscription = pRemovedSubscription; - } + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + _AwsIotShadow_DecrementReferences( pOperation, + pSubscription->pTopicBuffer, + &pRemovedSubscription ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + + /* Set the subscription pointer used for the user callback based on whether + * a subscription was removed from the list. */ + if( pRemovedSubscription != NULL ) + { + pSubscription = pRemovedSubscription; + } - AwsIotShadow_Assert( pSubscription != NULL ); + AwsIotShadow_Assert( pSubscription != NULL ); - /* Invoke the user callback if provided. */ - if( pOperation->notify.callback.function != NULL ) - { - /* Set the common members of the callback parameter. */ - callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; - callbackParam.mqttConnection = pOperation->mqttConnection; - callbackParam.u.operation.result = pOperation->status; - callbackParam.u.operation.reference = pOperation; - callbackParam.pThingName = pSubscription->pThingName; - callbackParam.thingNameLength = pSubscription->thingNameLength; - - /* Set the members of the callback parameter for a received document. */ - if( pOperation->type == SHADOW_GET ) + /* Invoke the user callback if provided. */ + if( pOperation->notify.callback.function != NULL ) { - callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; - callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; + /* Set the common members of the callback parameter. */ + callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; + callbackParam.mqttConnection = pOperation->mqttConnection; + callbackParam.u.operation.result = pOperation->status; + callbackParam.u.operation.reference = pOperation; + callbackParam.pThingName = pSubscription->pThingName; + callbackParam.thingNameLength = pSubscription->thingNameLength; + + /* Set the members of the callback parameter for a received document. */ + if( pOperation->type == SHADOW_GET ) + { + callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; + callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; + } + + pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, + &callbackParam ); } - pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, - &callbackParam ); - } + /* Destroy a removed subscription. */ + if( pRemovedSubscription != NULL ) + { + _AwsIotShadow_DestroySubscription( pRemovedSubscription ); + } - /* Destroy a removed subscription. */ - if( pRemovedSubscription != NULL ) - { - _AwsIotShadow_DestroySubscription( pRemovedSubscription ); + _AwsIotShadow_DestroyOperation( pOperation ); } - - _AwsIotShadow_DestroyOperation( pOperation ); } /*-----------------------------------------------------------*/ From 0a3b3f8002c0c8efd23b31dc7c4a7cd39c826940 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 18 Jun 2019 17:50:01 -0400 Subject: [PATCH 180/844] Lock references mutex when changing packet identifier (#465) --- lib/source/mqtt/iot_mqtt_operation.c | 52 +++++++++++++++++++++------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 46d483a7f7..c116edf5b7 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -129,7 +129,7 @@ static bool _mqttOperation_match( const IotLink_t * pOperationLink, static bool _checkRetryLimit( _mqttOperation_t * pOperation ) { _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - bool status = true; + bool status = true, setDup = false; /* Choose a set DUP function. */ void ( * publishSetDup )( uint8_t *, @@ -171,23 +171,51 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) status = false; } - /* Check if this is the first retry. */ - else if( pOperation->u.operation.retry.count == 1 ) - { - /* Always set the DUP flag on the first retry. */ - publishSetDup( pOperation->u.operation.pMqttPacket, - pOperation->u.operation.pPacketIdentifierHigh, - &( pOperation->u.operation.packetIdentifier ) ); - } else { - /* In AWS IoT MQTT mode, the DUP flag (really a change to the packet - * identifier) must be reset on every retry. */ - if( pMqttConnection->awsIotMqttMode == true ) + if( pOperation->u.operation.retry.count == 1 ) + { + /* The DUP flag should always be set on the first retry. */ + setDup = true; + } + else if( pMqttConnection->awsIotMqttMode == true ) + { + /* In AWS IoT MQTT mode, the DUP flag (really a change to the packet + * identifier) must be reset on every retry. */ + setDup = true; + } + else { + EMPTY_ELSE_MARKER; + } + + if( setDup == true ) + { + /* In AWS IoT MQTT mode, the references mutex must be locked to + * prevent the packet identifier from being read while it is being + * changed. */ + if( pMqttConnection->awsIotMqttMode == true ) + { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Always set the DUP flag on the first retry. */ publishSetDup( pOperation->u.operation.pMqttPacket, pOperation->u.operation.pPacketIdentifierHigh, &( pOperation->u.operation.packetIdentifier ) ); + + if( pMqttConnection->awsIotMqttMode == true ) + { + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + } + else + { + EMPTY_ELSE_MARKER; + } } else { From 9f348cfb1920d2d768188a33771a6208d9f270e6 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 19 Jun 2019 13:41:25 -0400 Subject: [PATCH 181/844] Add signatures of Jobs functions (#467) --- lib/include/aws_iot_jobs.h | 204 ++++++- lib/include/types/aws_iot_jobs_types.h | 684 ++++++++++++++++++++++- lib/include/types/aws_iot_shadow_types.h | 9 +- lib/source/jobs/aws_iot_jobs_api.c | 137 +++++ 4 files changed, 1028 insertions(+), 6 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 1207922c2a..2d949d23ee 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -35,4 +35,206 @@ /*------------------------- Jobs library functions --------------------------*/ -#endif +/** + * @functionspage{jobs,Jobs library} + * - @functionname{jobs_function_getpending} + * - @functionname{jobs_function_timedgetpending} + * - @functionname{jobs_function_startnext} + * - @functionname{jobs_function_timedstartnext} + * - @functionname{jobs_function_describe} + * - @functionname{jobs_function_timeddescribe} + * - @functionname{jobs_function_update} + * - @functionname{jobs_function_timedupdate} + * - @functionname{jobs_function_wait} + * - @functionname{jobs_function_setnotifypendingcallback} + * - @functionname{jobs_function_setnotifynextcallback} + * - @functionname{jobs_function_removepersistentsubscriptions} + * - @functionname{jobs_function_strerror} + * - @functionname{jobs_function_statename} + */ + +/** + * @functionpage{AwsIotJobs_GetPending,jobs,getpending} + * @functionpage{AwsIotJobs_TimedGetPending,jobs,timedgetpending} + * @functionpage{AwsIotJobs_StartNext,jobs,startnext} + * @functionpage{AwsIotJobs_TimedStartNext,jobs,timedstartnext} + * @functionpage{AwsIotJobs_Describe,jobs,describe} + * @functionpage{AwsIotJobs_TimedDescribe,jobs,timeddescribe} + * @functionpage{AwsIotJobs_Update,jobs,update} + * @functionpage{AwsIotJobs_TimedUpdate,jobs,timedupdate} + * @functionpage{AwsIotJobs_Wait,jobs,wait} + * @functionpage{AwsIotJobs_SetNotifyPendingCallback,jobs,setnotifypendingcallback} + * @functionpage{AwsIotJobs_SetNotifyNextCallback,jobs,setnotifynextcallback} + * @functionpage{AwsIotJobs_RemovePersistentSubscriptions,jobs,removepersistentsubscriptions} + * @functionpage{AwsIotJobs_strerror,jobs,strerror} + * @functionpage{AwsIotJobs_StateName,jobs,statename} + */ + +/** + * @brief Get the list of all pending jobs for a Thing and receive an asynchronous + * notification when the response arrives. + */ +/* @[declare_jobs_getpending] */ +AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsCallbackInfo_t pCallbackInfo, + AwsIotJobsOperation_t * const pGetPendingOperation ); +/* @[declare_jobs_getpending] */ + +/** + * @brief Get the list of all pending jobs for a Thing with a timeout for receiving + * the response. + */ +/* @[declare_jobs_timedgetpending] */ +AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t timeout ); +/* @[declare_jobs_timedgetpending] */ + +/** + * @brief Start the next pending job execution for a Thing and receive an asynchronous + * notification when the response arrives. + */ +/* @[declare_jobs_startnext] */ +AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const AwsIotJobsCallbackInfo_t pCallbackInfo, + AwsIotJobsOperation_t * const pStartNextOperation ); +/* @[declare_jobs_startnext] */ + +/** + * @brief Start the next pending job execution for a Thing with a timeout for + * receiving the response. + */ +/* @[declare_jobs_timedstartnext] */ +AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t timeout ); +/* @[declare_jobs_timedstartnext] */ + +/** + * @brief Get detailed information about a job execution and receive an asynchronous + * notification when the response arrives. + */ +/* @[declare_jobs_describe] */ +AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pJobsOperation ); +/* @[declare_jobs_describe] */ + +/** + * @brief Get detailed information about a job execution with a timeout for receiving + * the response. + */ +/* @[declare_jobs_timeddescribe] */ +AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t timeout ); +/* @[declare_jobs_timeddescribe] */ + +/** + * @brief Update the status of a job execution and receive an asynchronous + * notification when the Job update completes. + */ +/* @[declare_jobs_update] */ +AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pUpdateOperation ); +/* @[declare_jobs_update] */ + +/** + * @brief Update the status of a job execution with a timeout for receiving the + * response. + */ +/* @[declare_jobs_timedupdate] */ +AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t timeout ); +/* @[declare_jobs_timedupdate] */ + +/** + * @brief Wait for a Jobs operation to complete. + */ +/* @[declare_jobs_wait] */ +AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, + uint32_t timeoutMs, + const char ** const pResponse, + size_t * const pResponseLength ); +/* @[declare_jobs_wait] */ + +/** + * @brief Set a callback to be invoked when the list of pending Jobs changes. + */ +/* @[declare_jobs_setnotifypendingcallback] */ +AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pNotifyPendingCallback ); +/* @[declare_jobs_setnotifypendingcallback] */ + +/** + * @brief Set a callback to be invoked when the next pending Job changes. + */ +/* @[declare_jobs_setnotifynextcallback] */ +AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pNotifyNextCallback ); +/* @[declare_jobs_setnotifynextcallback] */ + +/** + * @brief Remove persistent Jobs operation topic subscriptions. + */ +/* @[declare_jobs_removepersistentsubscriptions] */ +AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags ); +/* @[declare_jobs_removepersistentsubscriptions] */ + +/*-------------------------- Jobs helper functions --------------------------*/ + +/** + * @brief Returns a string that describes an #AwsIotJobsError_t. + * + * Like POSIX's `strerror`, this function returns a string describing a return + * code. In this case, the return code is a Jobs library error code, `status`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] status The status to describe. + * + * @return A read-only string that describes `status`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_jobs_strerror] */ +const char * AwsIotJobs_strerror( AwsIotJobsError_t status ); +/* @[declare_jobs_strerror] */ + +/** + * @brief Returns a string that describes an #AwsIotJobState_t. + * + * This function returns a string describing a Job state, `state`. + * + * The string returned by this function MUST be treated as read-only: any + * attempt to modify its contents may result in a crash. Therefore, this function + * is limited to usage in logging. + * + * @param[in] state The job state to describe. + * + * @return A read-only string that describes `state`. + * + * @warning The string returned by this function must never be modified. + */ +/* @[declare_jobs_statename] */ +const char * AwsIotJobs_StateName( AwsIotJobState_t state ); +/* @[declare_jobs_statename] */ + +#endif /* ifndef AWS_IOT_JOBS_H_ */ diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 68ac336c54..56c57564cc 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -30,4 +30,686 @@ /* The config header is always included first. */ #include "iot_config.h" -#endif +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + +/*---------------------------- Jobs handle types ----------------------------*/ + +/** + * @handles{jobs,Jobs library} + */ + +/** + * @ingroup jobs_datatypes_handles + * @brief Opaque handle that references an in-progress Jobs operation. + */ +typedef struct _jobsOperation * AwsIotJobsOperation_t; + +/*-------------------------- Jobs enumerated types --------------------------*/ + +/** + * @enums{jobs,Jobs library} + */ + +/** + * @ingroup jobs_datatypes_enums + * @brief Return codes of [Jobs functions](@ref jobs_functions). + * + * The function @ref jobs_function_strerror can be used to get a return code's + * description. + * + * The values between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE + * may be returned by the Jobs service upon failure of a Jobs operation. See [this page] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#jobs-mqtt-error-response) + * for more information. + */ +typedef enum AwsIotJobsError +{ + /** + * @brief Jobs operation completed successfully. + */ + AWS_IOT_JOBS_SUCCESS = 0, + + /** + * @brief Jobs operation queued, awaiting result. + */ + AWS_IOT_JOBS_STATUS_PENDING, + + /** + * @brief Initialization failed. + */ + AWS_IOT_JOBS_INIT_FAILED, + + /** + * @brief At least one parameter is invalid. + */ + AWS_IOT_JOBS_BAD_PARAMETER, + + /** + * @brief Jobs operation failed because of memory allocation failure. + */ + AWS_IOT_JOBS_NO_MEMORY, + + /** + * @brief Jobs operation failed because of failure in MQTT library. + */ + AWS_IOT_JOBS_MQTT_ERROR, + + /** + * @brief Response received from Jobs service not understood. + */ + AWS_IOT_JOBS_BAD_RESPONSE, + + /** + * @brief A blocking Jobs operation timed out. + */ + AWS_IOT_JOBS_TIMEOUT, + + /** + * @brief Jobs operation failed: A request was sent to an unknown topic. + */ + AWS_IOT_JOBS_INVALID_TOPIC, + + /** + * @brief Jobs operation failed: The contents of the request were not understood. + * + * Jobs requests must be UTF-8 encoded JSON documents. + */ + AWS_IOT_JOBS_INVALID_JSON, + + /** + * @brief Jobs operation failed: The contents of the request were invalid. + */ + AWS_IOT_JOBS_INVALID_REQUEST, + + /** + * @brief Jobs operation failed: An update attempted to change the job execution + * to an invalid state. + */ + AWS_IOT_JOBS_INVALID_STATE, + + /** + * @brief Jobs operation failed: The specified job execution does not exist. + */ + AWS_IOT_JOBS_NOT_FOUND, + + /** + * @brief Jobs operation failed: The Jobs service expected a version that did + * not match what was in the request. + */ + AWS_IOT_JOBS_VERSION_MISMATCH, + + /** + * @brief Jobs operation failed: The Jobs service encountered an internal error. + */ + AWS_IOT_JOBS_INTERNAL_ERROR, + + /** + * @brief Jobs operation failed: The request was throttled. + */ + AWS_IOT_JOBS_THROTTLED, + + /** + * @brief Jobs operation failed: Attempt to describe a Job in a terminal state. + */ + AWS_IOT_JOBS_TERMINAL_STATE +} AwsIotJobsError_t; + +/** + * @ingroup jobs_datatypes_enums + * @brief Possible states of jobs. + * + * The function @ref jobs_function_statename can be used to get a state's + * description. + * + * See [this page] + * (https://docs.aws.amazon.com/iot/latest/apireference/API_iot-jobs-data_JobExecutionState.html) + * for more information on Job states. + */ +typedef enum AwsIotJobsState +{ + /** + * @brief A Job is queued and awaiting execution. + */ + AWS_IOT_JOB_STATE_QUEUED, + + /** + * @brief A Job is currently executing. + */ + AWS_IOT_JOB_STATE_IN_PROGRESS, + + /** + * @brief A Job has failed. This is a terminal state. + */ + AWS_IOT_JOB_STATE_FAILED, + + /** + * @brief A Job has succeeded. This is a terminal state. + */ + AWS_IOT_JOB_STATE_SUCCEEDED, + + /** + * @brief A Job was canceled. This is a terminal state. + */ + AWS_IOT_JOB_STATE_CANCELED, + + /** + * @brief A Job's timer has expired. This is a terminal state. + * + * Jobs are considered timed out if they remain [IN_PROGRESS] + * (@ref AWS_IOT_JOB_STATE_IN_PROGRESS) for more than their + * `inProgressTimeoutInMinutes` property or a [Job update](@ref jobs_function_update) + * was not sent within [stepTimeoutInMinutes](@ref AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes). + */ + AWS_IOT_JOB_STATE_TIMED_OUT, + + /** + * @brief A Job was rejected by the device. This is a terminal state. + */ + AWS_IOT_JOB_STATE_REJECTED, + + /** + * @brief A Job was removed. This is a terminal state. + */ + AWS_IOT_JOB_STATE_REMOVED +} AwsIotJobState_t; + +/** + * @ingroup jobs_datatypes_enums + * @brief Types of Jobs library callbacks. + * + * One of these values will be placed in #AwsIotJobsCallbackParam_t.callbackType + * to identify the reason for invoking a callback function. + */ +typedef enum AwsIotJobsCallbackType +{ + AWS_IOT_JOBS_GET_PENDING_COMPLETE, /**< Callback invoked because a [Jobs get pending](@ref jobs_function_getpending) completed. */ + AWS_IOT_JOBS_START_NEXT_COMPLETE, /**< Callback invoked because a [Jobs start next](@ref jobs_function_startnext) completed. */ + AWS_IOT_JOBS_DESCRIBE_COMPLETE, /**< Callback invoked because a [Jobs describe](@ref jobs_function_describe) completed. */ + AWS_IOT_JOBS_UPDATE_COMPLETE, /**< Callback invoked because a [Jobs update](@ref jobs_function_update) completed. */ + AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK, /**< Callback invoked for an incoming message on a [Jobs notify-pending](@ref jobs_function_setnotifypendingcallback) topic. */ + AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK /**< Callback invoked for an incoming message on a [Jobs notify-next](@ref jobs_function_setnotifynextcallback) topic. */ +} AwsIotJobsCallbackType_t; + +/*-------------------------- Jobs parameter structs -------------------------*/ + +/** + * @paramstructs{jobs,Jobs library} + */ + +/** + * @ingroup jobs_datatypes_paramstructs + * @brief Parameter to a Jobs callback function. + * + * @paramfor Jobs callback functions + * + * The Jobs library passes this struct to a callback function whenever a + * Jobs operation completes or a message is received on a Jobs notify-pending + * or notify-next topic. + * + * The valid members of this struct are different based on + * #AwsIotJobsCallbackParam_t.callbackType. If the callback type is + * #AWS_IOT_JOBS_GET_PENDING_COMPLETE, #AWS_IOT_JOBS_START_NEXT_COMPLETE, + * #AWS_IOT_JOBS_DESCRIBE_COMPLETE, or #AWS_IOT_JOBS_UPDATE_COMPLETE, then + * #AwsIotJobsCallbackParam_t.operation is valid. Otherwise, if the callback type + * is #AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK or #AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK, + * then #AwsIotJobsCallbackParam_t.callback is valid. + * + * @attention Any pointers in this callback parameter may be freed as soon as the + * [callback function](@ref AwsIotJobsCallbackInfo_t.function) returns. Therefore, + * data must be copied if it is needed after the callback function returns. + * @attention The Jobs library may set strings that are not NULL-terminated. + * + * @see #AwsIotJobsCallbackInfo_t for the signature of a callback function. + */ +typedef struct AwsIotJobsCallbackParam +{ + AwsIotJobsCallbackType_t callbackType; /**< @brief Reason for invoking the Jobs callback function to provide context. */ + + const char * pThingName; /**< @brief The Thing Name associated with this Jobs callback. */ + size_t thingNameLength; /**< @brief Length of #AwsIotJobsCallbackParam_t.pThingName. */ + + IotMqttConnection_t mqttConnection; /**< @brief The MQTT connection associated with the Jobs callback. */ + + union + { + /* Valid for completed Jobs operations. */ + struct + { + AwsIotJobsError_t result; /**< @brief Result of Jobs operation, e.g. succeeded or failed. */ + AwsIotJobsOperation_t reference; /**< @brief Reference to the Jobs operation that completed. */ + + const char * pResponse; /**< @brief Response retrieved from the Jobs service. */ + size_t responseLength; /**< @brief Length of retrieved response. */ + } operation; /**< @brief Information on a completed Jobs operation. */ + + /* Valid for a message on a Jobs notify-pending or notify-next topic. */ + struct + { + const char * pDocument; /**< @brief Job execution document received on callback. */ + size_t documentLength; /**< @brief Length of job execution document. */ + } callback; /**< @brief Jobs document from an incoming delta or updated topic. */ + } u; /**< @brief Valid member depends on callback type. */ +} AwsIotJobsCallbackParam_t; + +/** + * @ingroup jobs_datatypes_paramstructs + * @brief Information on a user-provided Jobs callback function. + * + * @paramfor @ref jobs_function_getpending, @ref jobs_function_startnext, + * @ref jobs_function_describe, @ref jobs_function_update, + * @ref jobs_function_setnotifypendingcallback, @ref jobs_function_setnotifynextcallback + * + * Provides a function to be invoked when a Jobs operation completes or when a + * Jobs document is received on a callback topic (notify-pending or notify-next). + * + * @initializer{AwsIotJobsCallbackInfo_t,AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER} + */ +typedef struct AwsIotJobsCallbackInfo +{ + void * pCallbackContext; /**< @brief The first parameter to pass to the callback function. */ + + /** + * @brief User-provided callback function signature. + * + * @param[in] void* #AwsIotJobsCallbackInfo_t.pCallbackContext + * @param[in] AwsIotJobsCallbackParam_t* Details on the outcome of the Jobs + * operation or an incoming Job document. + * + * @see #AwsIotJobsCallbackParam_t for more information on the second parameter. + */ + void ( * function )( void *, + AwsIotJobsCallbackParam_t ); +} AwsIotJobsCallbackInfo_t; + +/** + * @ingroup jobs_datatypes_paramstructs + * @brief Common information provided to Jobs requests. + * + * @paramfor @ref jobs_function_getpending, @ref jobs_function_startnext, + * @ref jobs_function_describe, @ref jobs_function_update + * + * @initializer{AwsIotJobsRequestInfo_t,AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER} + */ +typedef struct AwsIotJobsRequestInfo +{ + /** + * @brief The MQTT connection to use for the Jobs request. + */ + IotMqttConnection_t mqttConnection; + + /** + * @brief Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + */ + uint32_t flags; + + /** + * @brief The Thing Name associated with the Job. + */ + const char * pThingName; + + /** + * @brief Length of #AwsIotJobsRequestInfo_t.pThingName. + */ + size_t thingNameLength; + + /** + * @brief The Job ID to update. + * + * This may be set to #AWS_IOT_JOBS_NEXT_JOB to update the next pending Job. + * When using #AWS_IOT_JOBS_NEXT_JOB, #AwsIotJobsRequestInfo_t.jobIdLength + * should be set to #AWS_IOT_JOBS_NEXT_JOB_LENGTH. + */ + const char * pJobId; + + /** + * @brief Length of #AwsIotJobsRequestInfo_t.pJobId. + */ + size_t jobIdLength; + + /** + * @brief An arbitrary string that identifies this Job update to the Jobs + * service. + * + * The recommended value is #AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE. + */ + const char * pClientToken; + + /** + * @brief Length of #AwsIotJobsRequestInfo_t.pClientToken. + */ + size_t clientTokenLength; +} AwsIotJobsRequestInfo_t; + +/** + * @ingroup jobs_datatypes_paramstructs + * @brief Information on a Job update for @ref jobs_function_update. + * + * @paramfor jobs_function_update + * + * @initializer{AwsIotJobsUpdateInfo_t,AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER} + */ +typedef struct AwsIotJobsUpdateInfo +{ + /** + * @brief The new status to report as a Job execution update. + * + * Valid values are: + * - #AWS_IOT_JOB_STATE_IN_PROGRESS + * - #AWS_IOT_JOB_STATE_FAILED + * - #AWS_IOT_JOB_STATE_SUCCEEDED + * - #AWS_IOT_JOB_STATE_REJECTED + */ + AwsIotJobState_t newStatus; + + /** + * @brief The expected current version of job execution. + * + * Each time a Job update is sent (for the same `JobId`), the version stored + * on the AWS IoT Jobs service is updated. If this value does not match the + * value stored by the Jobs service, the Job update is rejected with the code + * #AWS_IOT_JOBS_VERSION_MISMATCH. + * + * This value is useful for ensuring the order of Job updates, i.e. that the + * Jobs service does not overwrite a later update with a previous one. If not + * needed, it can be set to #AWS_IOT_JOBS_NO_VERSION. + */ + uint32_t expectedVersion; + + /** + * @brief An application-defined value that identifies a Job execution on a + * specific device. + * + * The Jobs service provides commands for tracking the status of Job execution + * on a specific target. Therefore, this value is used to provide a unique + * identifier of a specific Job execution on a specific target. + * + * This value is optional. It may be set to #AWS_IOT_JOBS_NO_EXECUTION_NUMBER + * if not needed. + */ + int32_t executionNumber; + + /** + * @brief The amount of time (in minutes) before a new Job update must be reported. + * + * If this timeout expires without a new Job update being reported (for the same + * `jobId`), the Job's status is set to #AWS_IOT_JOB_STATE_TIMED_OUT. Sending a + * new Job update will reset this step timeout; a value of #AWS_IOT_JOBS_NO_TIMEOUT + * will clear any previous step timeout. + * + * Valid values are between 1 and 10,080 (7 days). This value is optional. It may + * be set to #AWS_IOT_JOBS_NO_TIMEOUT if not needed. + */ + int32_t stepTimeoutInMinutes; + + /** + * @brief Whether the Job response document should contain the `JobExecutionState`. + * + * The default value is `false`. + */ + bool includeJobExecutionState; + + /** + * @brief Whether the Job response document should contain the `JobDocument`. + * + * The default value is `false`. + */ + bool includeJobDocument; + + /** + * @brief An application-defined set of JSON name-value pairs that describe + * the status of Job execution. + * + * This value is optional. It may be set to #AWS_IOT_JOBS_NO_STATUS_DETAILS + * if not needed. + */ + const char * pStatusDetails; + + /** + * @brief Length of #AwsIotJobsUpdateInfo_t.pStatusDetails. + */ + size_t statusDetailsLength; +} AwsIotJobsUpdateInfo_t; + +/*------------------------- Jobs defined constants --------------------------*/ + +/** + * @brief Set #AwsIotJobsRequestInfo_t.pJobId to this value to use the next pending + * Job as the Job ID. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_NEXT_JOB ( "$next" ) + +/** + * @brief Length of #AWS_IOT_JOBS_NEXT_JOB. + * + * Set #AwsIotJobsRequestInfo_t.jobIdLength to this value when using + * #AWS_IOT_JOBS_NEXT_JOB. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_NEXT_JOB_LENGTH ( sizeof( AWS_IOT_JOBS_NEXT_JOB ) - 1 ) + +/** + * @brief Set #AwsIotJobsRequestInfo_t.pClientToken to this value to automatically + * generate a client token for the Jobs request. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ( NULL ) + +/** + * @brief Set #AwsIotJobsUpdateInfo_t.expectedVersion to this value to omit the + * version in the Jobs request. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_NO_VERSION ( 0 ) + +/** + * @brief Set #AwsIotJobsUpdateInfo_t.executionNumber to this value or pass it to + * @ref jobs_function_describe to omit the execution number in the Jobs request. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_NO_EXECUTION_NUMBER ( -1 ) + +/** + * @brief Set #AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes to this value to omit the + * step timeout in the Jobs request. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_NO_TIMEOUT ( -1 ) + +/** + * @brief Set #AwsIotJobsUpdateInfo_t.pStatusDetails to this value to omit the + * status details in the Jobs request. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_NO_STATUS_DETAILS ( NULL ) + +/** + * @constantspage{jobs,Jobs library} + * + * @section jobs_constants_initializers Jobs Initializers + * @brief Provides default values for the data types of the Jobs library. + * + * @snippet this define_jobs_initializers + * + * All user-facing data types of the Jobs library should be initialized + * using one of the following. + * + * @warning Failing to initialize a Jobs data type with the appropriate + * initializer may result in undefined behavior! + * @note The initializers may change at any time in future versions, but their + * names will remain the same. + * + * Example + * @code{c} + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + * AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * @endcode + * + * @section jobs_constants_flags Jobs Function Flags + * @brief Flags that modify the behavior of Jobs library functions. + * + * Flags should be bitwise-ORed with each other to change the behavior of + * Jobs library functions. + * + * The following flags are valid for the Jobs operation functions: + * @ref jobs_function_getpending, @ref jobs_function_startnext, + * @ref jobs_function_describe, @ref jobs_function_update, + * and their Timed variants. + * - #AWS_IOT_JOBS_FLAG_WAITABLE
+ * @copybrief AWS_IOT_JOBS_FLAG_WAITABLE + * - #AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS + * + * The following flags are valid for @ref jobs_function_removepersistentsubscriptions. + * These flags are not valid for the Jobs operation functions. + * - #AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS + * - #AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS + * - #AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS + * - #AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS
+ * @copybrief AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS + * + * @note The values of the flags may change at any time in future versions, but + * their names will remain the same. Additionally, flags which may be used at + * the same time will be bitwise-exclusive of each other. + */ + +/* @[define_jobs_initializers] */ +#define AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotJobsCallbackInfo_t. */ +/** @brief Initializer for #AwsIotJobsRequestInfo_t. */ +#define AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER \ + { .pClentToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE } +/** @brief Initializer for #AwsIotJobsUpdateInfo_t. */ +#define AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER \ + { .newStatus = AWS_IOT_JOB_STATE_IN_PROGRESS, \ + .expectedVersion = AWS_IOT_JOBS_NO_VERSION, \ + .executionNumber = AWS_IOT_JOBS_NO_EXECUTION_NUMBER, \ + .stepTimeoutInMinutes = AWS_IOT_JOBS_NO_TIMEOUT, \ + .includeJobExecutionState = false, \ + .includeJobExecutionDocument = false, \ + .pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS } +#define AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ +/* @[define_jobs_initializers] */ + +/** + * @brief Allows the use of @ref jobs_function_wait for blocking until completion. + * + * This flag is only valid if passed to the functions @ref jobs_function_getpending, + * @ref jobs_function_startnext, @ref jobs_function_describe, or @ref jobs_function_update. + * + * An #AwsIotJobsOperation_t MUST be provided if this flag is set. + * Additionally, an #AwsIotJobsCallbackInfo_t MUST NOT be provided. + * + * @note If this flag is set, @ref jobs_function_wait MUST be called to + * clean up resources. + */ +#define AWS_IOT_JOBS_FLAG_WAITABLE ( 0x00000001 ) + +/** + * @brief Maintain the subscriptions for the Jobs operation topics, even after + * this function returns. + * + * This flag is only valid if passed to the functions @ref jobs_function_getpending, + * @ref jobs_function_startnext, @ref jobs_function_describe, or @ref jobs_function_update, + * and their Timed variants. + * + * The Jobs service reports results of Jobs operations by publishing + * messages to MQTT topics. By default, the Job operation functions subscribe to the + * necessary topics, wait for the Jobs service to publish the result of the + * Jobs operation, then unsubscribe from those topics. This workflow is suitable + * for infrequent Jobs operations, but is inefficient for frequent, periodic + * Jobs operations (where subscriptions for the Jobs operation topics would be + * constantly added and removed). + * + * This flag causes the Jobs operation functions to maintain Jobs operation + * topic subscriptions, even after the function returns. These subscriptions + * may then be used by a future call to the same function. + * + * This flags only needs to be set once, after which subscriptions are maintained + * and reused for a specific Thing Name and Jobs function. The function @ref + * jobs_function_removepersistentsubscriptions may be used to remove + * subscriptions maintained by this flag. + */ +#define AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) + +/** + * @brief Remove the persistent subscriptions from a Jobs get pending operation. + * + * This flag is only valid if passed to the function @ref + * jobs_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref jobs_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref jobs_function_getpending or @ref jobs_function_timedgetpending. + * + * @warning Do not call @ref jobs_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Jobs get pending operations. + */ +#define AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ( 0x00000001 ) + +/** + * @brief Remove the persistent subscriptions from a Jobs start next operation. + * + * This flag is only valid if passed to the function @ref + * jobs_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref jobs_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref jobs_function_startnext or @ref jobs_function_timedstartnext. + * + * @warning Do not call @ref jobs_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Jobs start next operations. + */ +#define AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS ( 0x00000002 ) + +/** + * @brief Remove the persistent subscriptions from a Jobs describe operation. + * + * This flag is only valid if passed to the function @ref + * jobs_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref jobs_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref jobs_function_describe or @ref jobs_function_timeddescribe. + * + * @warning Do not call @ref jobs_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Jobs describe operations. + */ +#define AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ( 0x00000004 ) + +/** + * @brief Remove the persistent subscriptions from a Jobs update operation. + * + * This flag is only valid if passed to the function @ref + * jobs_function_removepersistentsubscriptions. + * + * This flag may be passed to @ref jobs_function_removepersistentsubscriptions + * to remove any subscriptions for a specific Thing Name maintained by a previous + * call to @ref jobs_function_update or @ref jobs_function_timedupdate. + * + * @warning Do not call @ref jobs_function_removepersistentsubscriptions with + * this flag for Thing Names with any in-progress Jobs update operations. + */ +#define AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000008 ) + +#endif /* ifndef AWS_IOT_JOBS_TYPES_H_ */ diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 9f25c7f7c2..496219465b 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -554,7 +554,8 @@ typedef struct AwsIotShadowDocumentInfo * this function returns. * * This flag is only valid if passed to the functions @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update. + * @ref shadow_function_get, @ref shadow_function_update, or their Timed + * variants. * * The Shadow service reports results of Shadow operations by publishing * messages to MQTT topics. By default, the functions @ref shadow_function_delete, @@ -585,7 +586,7 @@ typedef struct AwsIotShadowDocumentInfo * * This flag may be passed to @ref shadow_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_delete. + * call to @ref shadow_function_delete or @ref shadow_function_timeddelete. * * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow delete operations. @@ -600,7 +601,7 @@ typedef struct AwsIotShadowDocumentInfo * * This flag may be passed to @ref shadow_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_get. + * call to @ref shadow_function_get or @ref shadow_function_timedget. * * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow get operations. @@ -615,7 +616,7 @@ typedef struct AwsIotShadowDocumentInfo * * This flag may be passed to @ref shadow_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_update. + * call to @ref shadow_function_update or @ref shadow_function_timedupdate. * * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow update operations. diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 38c5a0cf5e..f7f40d72b9 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -26,3 +26,140 @@ /* The config header is always included first. */ #include "iot_config.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/*-----------------------------------------------------------*/ + +const char * AwsIotJobs_strerror( AwsIotJobsError_t status ) +{ + const char * pMessage = NULL; + + switch( status ) + { + case AWS_IOT_JOBS_SUCCESS: + pMessage = "SUCCESS"; + break; + + case AWS_IOT_JOBS_STATUS_PENDING: + pMessage = "STATUS PENDING"; + break; + + case AWS_IOT_JOBS_INIT_FAILED: + pMessage = "INIT FAILED"; + break; + + case AWS_IOT_JOBS_BAD_PARAMETER: + pMessage = "BAD PARAMETER"; + break; + + case AWS_IOT_JOBS_NO_MEMORY: + pMessage = "NO MEMORY"; + break; + + case AWS_IOT_JOBS_MQTT_ERROR: + pMessage = "MQTT ERROR"; + break; + + case AWS_IOT_JOBS_BAD_RESPONSE: + pMessage = "BAD RESPONSE"; + break; + + case AWS_IOT_JOBS_TIMEOUT: + pMessage = "TIMEOUT"; + break; + + case AWS_IOT_JOBS_INVALID_TOPIC: + pMessage = "FAILED: INVALID TOPIC"; + break; + + case AWS_IOT_JOBS_INVALID_JSON: + pMessage = "FAILED: INVALID JSON"; + break; + + case AWS_IOT_JOBS_INVALID_REQUEST: + pMessage = "FAILED: INVALID REQUEST"; + break; + + case AWS_IOT_JOBS_INVALID_STATE: + pMessage = "FAILED: INVALID STATE TRANSITION"; + break; + + case AWS_IOT_JOBS_NOT_FOUND: + pMessage = "FAILED: NOT FOUND"; + break; + + case AWS_IOT_JOBS_VERSION_MISMATCH: + pMessage = "FAILED: VERSION MISMATCH"; + break; + + case AWS_IOT_JOBS_INTERNAL_ERROR: + pMessage = "FAILED: INTERNAL ERROR"; + break; + + case AWS_IOT_JOBS_THROTTLED: + pMessage = "FAILED: THROTTLED"; + break; + + case AWS_IOT_JOBS_TERMINAL_STATE: + pMessage = "FAILED: TERMINAL STATE"; + break; + + default: + pMessage = "INVALID STATUS"; + break; + } + + return pMessage; +} + +/*-----------------------------------------------------------*/ + +const char * AwsIotJobs_StateName( AwsIotJobState_t state ) +{ + const char * pMessage = NULL; + + switch( state ) + { + case AWS_IOT_JOB_STATE_QUEUED: + pMessage = "QUEUED"; + break; + + case AWS_IOT_JOB_STATE_IN_PROGRESS: + pMessage = "IN PROGRESS"; + break; + + case AWS_IOT_JOB_STATE_FAILED: + pMessage = "FAILED"; + break; + + case AWS_IOT_JOB_STATE_SUCCEEDED: + pMessage = "SUCCEEDED"; + break; + + case AWS_IOT_JOB_STATE_CANCELED: + pMessage = "CANCELED"; + break; + + case AWS_IOT_JOB_STATE_TIMED_OUT: + pMessage = "TIMED OUT"; + break; + + case AWS_IOT_JOB_STATE_REJECTED: + pMessage = "REJECTED"; + break; + + case AWS_IOT_JOB_STATE_REMOVED: + pMessage = "REMOVED"; + break; + + default: + pMessage = "INVALID STATE"; + break; + } + + return pMessage; +} + +/*-----------------------------------------------------------*/ From 9c060042690a0fff819f8064c614242821e33d1a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 19 Jun 2019 14:33:15 -0400 Subject: [PATCH 182/844] Add Jobs configuration and doc (#468) --- demos/iot_config.h | 2 + doc/config/jobs | 1 + doc/lib/jobs.txt | 110 ++++++++++++ lib/include/aws_iot_jobs.h | 45 +++++ lib/include/private/aws_iot_jobs_internal.h | 150 +++++++++++++++++ lib/include/private/aws_iot_shadow_internal.h | 68 ++++---- lib/include/types/aws_iot_jobs_types.h | 35 +++- lib/include/types/aws_iot_shadow_types.h | 2 +- lib/source/jobs/CMakeLists.txt | 3 +- lib/source/jobs/aws_iot_jobs_static_memory.c | 159 ++++++++++++++++++ tests/iot_config.h | 8 + 11 files changed, 536 insertions(+), 47 deletions(-) create mode 100644 lib/source/jobs/aws_iot_jobs_static_memory.c diff --git a/demos/iot_config.h b/demos/iot_config.h index d20c1de9c4..1f3a2ff627 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -56,6 +56,7 @@ #define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_JOBS_ENABLE_ASSERTS ( 1 ) /* Library logging configuration. IOT_LOG_LEVEL_GLOBAL provides a global log * level for all libraries; the library-specific settings override the global @@ -69,6 +70,7 @@ #define IOT_LOG_LEVEL_MQTT IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_SHADOW IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_DEFENDER IOT_LOG_INFO +#define AWS_IOT_LOG_LEVEL_JOBS IOT_LOG_INFO /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ diff --git a/doc/config/jobs b/doc/config/jobs index 8bab0afb46..c2dd27affa 100644 --- a/doc/config/jobs +++ b/doc/config/jobs @@ -28,5 +28,6 @@ FILE_PATTERNS = *jobs*.h *jobs*.c *jobs*.txt TAGFILES = doc/tag/main.tag=../main \ doc/tag/mqtt.tag=../mqtt \ doc/tag/logging.tag=../logging \ + doc/tag/linear_containers.tag=../linear_containers \ doc/tag/static_memory.tag=../static_memory \ doc/tag/platform.tag=../platform diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index 094b3e7215..88bdef3fac 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -2,4 +2,114 @@ @mainpage @anchor jobs @brief AWS IoT Device Jobs library. + +> AWS IoT jobs can be used to define a set of remote operations that are sent to and executed on one or more devices connected to AWS IoT. + +Description of Jobs from [AWS IoT documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-jobs.html)
+ +This library provides an API based on the [Jobs APIs available over MQTT](https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#jobs-mqtt-api). Features of this library include: +- Both fully asynchronous and blocking API functions. +- API functions for interacting with Jobs and registering notifications for pending Jobs. + +@dependencies{jobs,Jobs library} +@dot "Jobs direct dependencies" +digraph jobs_dependencies +{ + node[shape=box, fontname=Helvetica, fontsize=10, style=filled]; + edge[fontname=Helvetica, fontsize=10]; + subgraph + { + jobs[label="Jobs", fillcolor="#cc00ccff"]; + mqtt[label="MQTT", fillcolor="#cc00ccff", URL="@ref mqtt"]; + } + subgraph + { + node[fillcolor="#aed8a9ff"]; + rank = same; + linear_containers[label="List/Queue", URL="@ref linear_containers"]; + logging[label="Logging", URL="@ref logging"]; + static_memory[label="Static memory", URL="@ref static_memory"]; + } + jobs -> mqtt; + jobs -> linear_containers; + jobs -> logging [label=" if logging enabled", style="dashed"]; + jobs -> static_memory [label=" if static memory only", style="dashed"]; +} +@enddot + +Currently, the Jobs library has the following dependencies: +- The MQTT library for sending the messages that interact with the Jobs service. See [this page](@ref mqtt_dependencies) for the dependencies of the MQTT library, which are not shown in the graph above. +- The queue library for maintaining the data structures for managing in-progress Jobs operations. +- The logging library may be used if @ref AWS_IOT_LOG_LEVEL_JOBS is not #IOT_LOG_NONE. + +In addition to the components above, the Jobs library also depends on C standard library headers. +*/ + +/** +@page jobs_tests Tests +@brief Tests written for the Jobs library. + +The Jobs tests reside in the `tests/jobs` directory. They are divided into the following subdirectories: +- `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT test access files.](@ref mqtt_tests) + +The Jobs unit tests require no special configuration. + +The current Jobs tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. +*/ + +/** +@configpage{jobs,Jobs library} + +@section AWS_IOT_JOBS_ENABLE_ASSERTS +@brief Set this to `1` to perform sanity checks when using the Jobs library. + +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotJobs_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. + +@configpossible `0` (asserts disabled) or `1` (asserts enabled)
+@configrecommended `1` when debugging; `0` in production code.
+@configdefault `0` + +@section AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS +@brief Set the default timeout (in milliseconds) for [MQTT library](@ref mqtt_functions) called by the Jobs library. + +If the `mqttTimeout` argument of @ref jobs_function_init is `0`, the Jobs library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_timedsubscribe, @ref mqtt_function_timedunsubscribe, and @ref mqtt_function_timedpublish to limit amount of time an MQTT function may block. + +@configpossible Any positive integer.
+@configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
+@configdefault `5000` + +@section AWS_IOT_LOG_LEVEL_JOBS +@brief Set the log level of the Jobs library. + +Log messages from the Jobs library at or below this setting will be printed. + +@configpossible One of the @ref logging_constants_levels.
+@configdefault @ref IOT_LOG_LEVEL_GLOBAL; if that is undefined, then #IOT_LOG_NONE. + +@section AwsIotJobs_Assert +@brief Assertion function used when @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`. + +@configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
+@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`; otherwise, nothing. + +@section jobs_config_memory Memory allocation +@brief The following functions may be re-implemented for the Jobs library. +- #AwsIotJobs_MallocOperation
+ @copybrief AwsIotJobs_MallocOperation +- #AwsIotJobs_FreeOperation
+ @copybrief AwsIotJobs_FreeOperation +- #AwsIotJobs_MallocString
+ @copybrief AwsIotJobs_MallocString +- #AwsIotJobs_FreeString
+ @copybrief AwsIotJobs_FreeString +- #AwsIotJobs_MallocSubscription
+ @copybrief AwsIotJobs_MallocSubscription +- #AwsIotJobs_FreeSubscription
+ @copybrief AwsIotJobs_FreeSubscription + +When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for Jobs. +- @anchor AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS
+ Maximum number of Jobs operations that may be in-progress at any time. Defaults to `10` if undefined. +- @anchor AWS_IOT_JOBS_SUBSCRIPTIONS AWS_IOT_JOBS_SUBSCRIPTIONS
+ Maximum number of Jobs subscriptions at any time. Defaults to `2` if undefined. */ diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 2d949d23ee..735afc5778 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -37,6 +37,8 @@ /** * @functionspage{jobs,Jobs library} + * - @functionname{jobs_function_init} + * - @functionname{jobs_function_cleanup} * - @functionname{jobs_function_getpending} * - @functionname{jobs_function_timedgetpending} * - @functionname{jobs_function_startnext} @@ -54,6 +56,8 @@ */ /** + * @functionpage{AwsIotJobs_Init,jobs,init} + * @functionpage{AwsIotJobs_Cleanup,jobs,cleanup} * @functionpage{AwsIotJobs_GetPending,jobs,getpending} * @functionpage{AwsIotJobs_TimedGetPending,jobs,timedgetpending} * @functionpage{AwsIotJobs_StartNext,jobs,startnext} @@ -70,6 +74,47 @@ * @functionpage{AwsIotJobs_StateName,jobs,statename} */ +/** + * @brief One-time initialization function for the Jobs library. + * + * This function performs internal setup of the Jobs library. It must be + * called once (and only once) before calling any other Jobs function. + * Calling this function more than once without first calling @ref + * jobs_function_cleanup may result in a crash. + * + * @param[in] mqttTimeout The amount of time (in milliseconds) that the Jobs + * library will wait for MQTT operations. Optional; set this to `0` to use + * @ref AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_INIT_FAILED + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref jobs_function_cleanup + */ +/* @[declare_jobs_init] */ +AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeout ); +/* @[declare_jobs_init] */ + +/** + * @brief One-time deinitialization function for the Jobs library. + * + * This function frees resources taken in @ref jobs_function_init and deletes + * any [persistent subscriptions.](@ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS) + * It should be called to clean up the Jobs library. After this function returns, + * @ref jobs_function_init must be called again before calling any other Jobs + * function. + * + * @warning No thread-safety guarantees are provided for this function. + * + * @see @ref jobs_function_init + */ +/* @[declare_jobs_cleanup] */ +void AwsIotJobs_Cleanup( void ); +/* @[declare_jobs_cleanup] */ + /** * @brief Get the list of all pending jobs for a Thing and receive an asynchronous * notification when the response arrives. diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index b7d2aade5a..38dd768893 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -31,7 +31,157 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Linear containers (lists and queues) include. */ +#include "iot_linear_containers.h" + /* Jobs include. */ #include "aws_iot_jobs.h" +/* AWS IoT include. */ +#include "aws_iot.h" + +/** + * @def AwsIotJobs_Assert( expression ) + * @brief Assertion macro for the Jobs library. + * + * Set @ref AWS_IOT_JOBS_ENABLE_ASSERTS to `1` to enable assertions in the Jobs + * library. + * + * @param[in] expression Expression to be evaluated. + */ +#if AWS_IOT_JOBS_ENABLE_ASSERTS == 1 + #ifndef AwsIotJobs_Assert + #include + #define AwsIotJobs_Assert( expression ) assert( expression ) + #endif +#else + #define AwsIotJobs_Assert( expression ) +#endif + +/* Configure logs for Jobs functions. */ +#ifdef AWS_IOT_LOG_LEVEL_JOBS + #define LIBRARY_LOG_LEVEL AWS_IOT_LOG_LEVEL_JOBS +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "Jobs" ) +#include "iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions based on + * the usage of dynamic memory allocation. + */ +#if IOT_STATIC_MEMORY_ONLY == 1 + #include "private/iot_static_memory.h" + +/** + * @brief Allocate a #_jobsOperation_t. This function should have the same + * signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + void * AwsIotJobs_MallocOperation( size_t size ); + +/** + * @brief Free a #_jobsOperation_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + void AwsIotJobs_FreeOperation( void * ptr ); + +/** + * @brief Allocate a buffer for a short string, used for topic names or client + * tokens. This function should have the same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define AwsIotJobs_MallocString Iot_MallocMessageBuffer + +/** + * @brief Free a string. This function should have the same signature as + * [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define AwsIotJobs_FreeString Iot_FreeMessageBuffer + +/** + * @brief Allocate a #_jobsSubscription_t. This function should have the + * same signature as [malloc] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + void * AwsIotJobs_MallocSubscription( size_t size ); + +/** + * @brief Free a #_jobsSubscription_t. This function should have the same + * signature as [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + void AwsIotJobs_FreeSubscription( void * ptr ); +#else /* if IOT_STATIC_MEMORY_ONLY == 1 */ + #include + + #ifndef AwsIotJobs_MallocOperation + #define AwsIotJobs_MallocOperation malloc + #endif + + #ifndef AwsIotJobs_FreeOperation + #define AwsIotJobs_FreeOperation free + #endif + + #ifndef AwsIotJobs_MallocString + #define AwsIotJobs_MallocString malloc + #endif + + #ifndef AwsIotJobs_FreeString + #define AwsIotJobs_FreeString free + #endif + + #ifndef AwsIotJobs_MallocSubscription + #define AwsIotJobs_MallocSubscription malloc + #endif + + #ifndef AwsIotJobs_FreeSubscription + #define AwsIotJobs_FreeSubscription free + #endif +#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + #define AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS ( 5000 ) #endif +/** @endcond */ + +/*------------------------ Jobs internal data types -------------------------*/ + +/** + * @brief Represents a Jobs subscriptions object. + * + * These structures are stored in a list. + */ +typedef struct _jobsSubscription +{ + IotLink_t link; /**< @brief List link member. */ + + size_t thingNameLength; /**< @brief Length of Thing Name. */ + char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ +} _jobsSubscription_t; + +/** + * @brief Internal structure representing a single Jobs operation. + * + * A list of these structures keeps track of all in-progress Jobs operations. + */ +typedef struct _jobsOperation +{ + IotLink_t link; /**< @brief List link member. */ +} _jobsOperation_t; + +#endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index ae19189bea..9947c998bd 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -243,16 +243,6 @@ /*----------------------- Shadow internal data types ------------------------*/ -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declarations. - */ -struct _shadowOperation; -struct _shadowSubscription; -/** @endcond */ - /** * @brief Function pointer representing an MQTT timed operation. * @@ -295,6 +285,30 @@ typedef enum _shadowCallbackType UPDATED_CALLBACK = 1 /**< Updated callback. */ } _shadowCallbackType_t; +/** + * @brief Represents a Shadow subscriptions object. + * + * These structures are stored in a list. + */ +typedef struct _shadowSubscription +{ + IotLink_t link; /**< @brief List link member. */ + + int32_t references[ SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ + AwsIotShadowCallbackInfo_t callbacks[ SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ + + /** + * @brief Buffer allocated for removing Shadow topics. + * + * This buffer is pre-allocated to ensure that memory is available when + * unsubscribing. + */ + char * pTopicBuffer; + + size_t thingNameLength; /**< @brief Length of Thing Name. */ + char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ +} _shadowSubscription_t; + /** * @brief Internal structure representing a single Shadow operation (DELETE, * GET, or UPDATE). @@ -306,12 +320,12 @@ typedef struct _shadowOperation IotLink_t link; /**< @brief List link member. */ /* Basic operation information. */ - _shadowOperationType_t type; /**< @brief Operation type. */ - uint32_t flags; /**< @brief Flags passed to operation API function. */ - AwsIotShadowError_t status; /**< @brief Status of operation. */ + _shadowOperationType_t type; /**< @brief Operation type. */ + uint32_t flags; /**< @brief Flags passed to operation API function. */ + AwsIotShadowError_t status; /**< @brief Status of operation. */ - IotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ - struct _shadowSubscription * pSubscription; /**< @brief Shadow subscriptions object associated with this operation. */ + IotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ + _shadowSubscription_t * pSubscription; /**< @brief Shadow subscriptions object associated with this operation. */ union { @@ -345,30 +359,6 @@ typedef struct _shadowOperation } notify; /**< @brief How to notify of an operation's completion. */ } _shadowOperation_t; -/** - * @brief Represents a Shadow subscriptions object. - * - * These structures are stored in a list. - */ -typedef struct _shadowSubscription -{ - IotLink_t link; /**< @brief List link member. */ - - int32_t references[ SHADOW_OPERATION_COUNT ]; /**< @brief Reference counter for Shadow operation topics. */ - AwsIotShadowCallbackInfo_t callbacks[ SHADOW_CALLBACK_COUNT ]; /**< @brief Shadow callbacks for this Thing. */ - - /** - * @brief Buffer allocated for removing Shadow topics. - * - * This buffer is pre-allocated to ensure that memory is available when - * unsubscribing. - */ - char * pTopicBuffer; - - size_t thingNameLength; /**< @brief Length of Thing Name. */ - char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ -} _shadowSubscription_t; - /* Declarations of names printed in logs. */ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE extern const char * const _pAwsIotShadowOperationNames[]; diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 56c57564cc..2c07efda59 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -42,6 +42,23 @@ /** * @ingroup jobs_datatypes_handles * @brief Opaque handle that references an in-progress Jobs operation. + * + * Set as an output parameter of @ref jobs_function_getpending, @ref jobs_function_startnext, + * @ref jobs_function_describe, and @ref jobs_function_update. These functions send a + * message to the Jobs service requesting a Jobs operation; the result of this operation + * is unknown until the Jobs service sends a response. Therefore, this handle serves as a + * reference to Jobs operations awaiting a response from the Jobs service. + * + * This reference will be valid from the successful return of @ref jobs_function_getpending, + * @ref jobs_function_startnext, @ref jobs_function_describe, and @ref jobs_function_update. + * The reference becomes invalid once the [completion callback](@ref AwsIotJobsCallbackInfo_t) + * is invoked, or @ref jobs_function_wait returns. + * + * @initializer{AwsIotJobsOperation_t,AWS_IOT_JOBS_OPERATION_INITIALIZER} + * + * @see @ref jobs_function_wait and #AWS_IOT_JOBS_FLAG_WAITABLE for waiting on + * a reference; or #AwsIotJobsCallbackInfo_t and #AwsIotJobsCallbackParam_t for an + * asynchronous notification of completion. */ typedef struct _jobsOperation * AwsIotJobsOperation_t; @@ -67,6 +84,12 @@ typedef enum AwsIotJobsError { /** * @brief Jobs operation completed successfully. + * + * Functions that may return this value: + * + * Will also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) + * when successful. */ AWS_IOT_JOBS_SUCCESS = 0, @@ -608,7 +631,7 @@ typedef struct AwsIotJobsUpdateInfo .includeJobExecutionState = false, \ .includeJobExecutionDocument = false, \ .pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS } -#define AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ +#define AWS_IOT_JOBS_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ /* @[define_jobs_initializers] */ /** @@ -623,7 +646,7 @@ typedef struct AwsIotJobsUpdateInfo * @note If this flag is set, @ref jobs_function_wait MUST be called to * clean up resources. */ -#define AWS_IOT_JOBS_FLAG_WAITABLE ( 0x00000001 ) +#define AWS_IOT_JOBS_FLAG_WAITABLE ( 0x00000001 ) /** * @brief Maintain the subscriptions for the Jobs operation topics, even after @@ -650,7 +673,7 @@ typedef struct AwsIotJobsUpdateInfo * jobs_function_removepersistentsubscriptions may be used to remove * subscriptions maintained by this flag. */ -#define AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) +#define AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ( 0x00000002 ) /** * @brief Remove the persistent subscriptions from a Jobs get pending operation. @@ -680,7 +703,7 @@ typedef struct AwsIotJobsUpdateInfo * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs start next operations. */ -#define AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS ( 0x00000002 ) +#define AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS ( 0x00000002 ) /** * @brief Remove the persistent subscriptions from a Jobs describe operation. @@ -695,7 +718,7 @@ typedef struct AwsIotJobsUpdateInfo * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs describe operations. */ -#define AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ( 0x00000004 ) +#define AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ( 0x00000004 ) /** * @brief Remove the persistent subscriptions from a Jobs update operation. @@ -710,6 +733,6 @@ typedef struct AwsIotJobsUpdateInfo * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs update operations. */ -#define AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000008 ) +#define AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ( 0x00000008 ) #endif /* ifndef AWS_IOT_JOBS_TYPES_H_ */ diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 496219465b..911846d7a1 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -57,7 +57,7 @@ * @initializer{AwsIotShadowOperation_t,AWS_IOT_SHADOW_OPERATION_INITIALIZER} * * @see @ref shadow_function_wait and #AWS_IOT_SHADOW_FLAG_WAITABLE for waiting on - * a reference. #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an + * a reference; or #AwsIotShadowCallbackInfo_t and #AwsIotShadowCallbackParam_t for an * asynchronous notification of completion. */ typedef struct _shadowOperation * AwsIotShadowOperation_t; diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt index d978479683..40319e88bb 100644 --- a/lib/source/jobs/CMakeLists.txt +++ b/lib/source/jobs/CMakeLists.txt @@ -1,6 +1,7 @@ # Jobs library source files. set( JOBS_SOURCES - aws_iot_jobs_api.c ) + aws_iot_jobs_api.c + aws_iot_jobs_static_memory.c ) # Jobs library target. add_library( awsiotjobs diff --git a/lib/source/jobs/aws_iot_jobs_static_memory.c b/lib/source/jobs/aws_iot_jobs_static_memory.c new file mode 100644 index 0000000000..195944f111 --- /dev/null +++ b/lib/source/jobs/aws_iot_jobs_static_memory.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_static_memory.c + * @brief Implementation of Jobs static memory functions. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* This file should only be compiled if dynamic memory allocation is forbidden. */ +#if IOT_STATIC_MEMORY_ONLY == 1 + +/* Standard includes. */ +#include +#include +#include + +/* Static memory include. */ +#include "private/iot_static_memory.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values for undefined configuration constants. + */ +#ifndef AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS + #define AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ( 10 ) +#endif +#ifndef AWS_IOT_JOBS_SUBSCRIPTIONS + #define AWS_IOT_JOBS_SUBSCRIPTIONS ( 2 ) +#endif +/** @endcond */ + +/* Validate static memory configuration settings. */ +#if AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS <= 0 + #error "AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS cannot be 0 or negative." +#endif +#if AWS_IOT_JOBS_SUBSCRIPTIONS <= 0 + #error "AWS_IOT_JOBS_SUBSCRIPTIONS cannot be 0 or negative." +#endif + +/** + * @brief The size of a static memory Jobs subscription. + * + * Since the pThingName member of #_jobsSubscription_t is variable-length, + * the constant `AWS_IOT_MAX_THING_NAME_LENGTH` is used for the length of + * #_jobsSubscription_t.pThingName. + */ +#define JOBS_SUBSCRIPTION_SIZE ( sizeof( _jobsSubscription_t ) + AWS_IOT_MAX_THING_NAME_LENGTH ) + +/*-----------------------------------------------------------*/ + +/* + * Static memory buffers and flags, allocated and zeroed at compile-time. + */ +static uint32_t _pInUseJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief Jobs operation in-use flags. */ +static _jobsOperation_t _pJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Jobs operations. */ + +static uint32_t _pInUseJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ] = { 0U }; /**< @brief Jobs subscription in-use flags. */ +static char _pJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ][ JOBS_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Jobs subscriptions. */ + +/*-----------------------------------------------------------*/ + +void * AwsIotJobs_MallocOperation( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewOperation = NULL; + + /* Check size argument. */ + if( size == sizeof( _jobsOperation_t ) ) + { + /* Find a free Jobs operation. */ + freeIndex = IotStaticMemory_FindFree( _pInUseJobsOperations, + AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ); + + if( freeIndex != -1 ) + { + pNewOperation = &( _pJobsOperations[ freeIndex ] ); + } + } + + return pNewOperation; +} + +/*-----------------------------------------------------------*/ + +void AwsIotJobs_FreeOperation( void * ptr ) +{ + /* Return the in-use Jobs operation. */ + IotStaticMemory_ReturnInUse( ptr, + _pJobsOperations, + _pInUseJobsOperations, + AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS, + sizeof( _jobsOperation_t ) ); +} + +/*-----------------------------------------------------------*/ + +void * AwsIotJobs_MallocSubscription( size_t size ) +{ + int32_t freeIndex = -1; + void * pNewSubscription = NULL; + + if( size <= JOBS_SUBSCRIPTION_SIZE ) + { + /* Get the index of a free Jobs subscription. */ + freeIndex = IotStaticMemory_FindFree( _pInUseJobsSubscriptions, + AWS_IOT_JOBS_SUBSCRIPTIONS ); + + if( freeIndex != -1 ) + { + pNewSubscription = &( _pJobsSubscriptions[ freeIndex ][ 0 ] ); + } + } + + return pNewSubscription; +} + +/*-----------------------------------------------------------*/ + +void AwsIotJobs_FreeSubscription( void * ptr ) +{ + /* Return the in-use Jobs subscription. */ + IotStaticMemory_ReturnInUse( ptr, + _pJobsSubscriptions, + _pInUseJobsSubscriptions, + AWS_IOT_JOBS_SUBSCRIPTIONS, + JOBS_SUBSCRIPTION_SIZE ); +} + +/*-----------------------------------------------------------*/ + +#endif diff --git a/tests/iot_config.h b/tests/iot_config.h index c19a2ff608..f08711b32a 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -99,6 +99,7 @@ #define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) +#define AWS_IOT_JOBS_ENABLE_ASSERTS ( 1 ) /* MQTT library configuration. */ #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES ( 1 ) @@ -166,6 +167,13 @@ #define AwsIotDefender_FreeReport unity_free_mt #define AwsIotDefender_MallocTopic unity_malloc_mt #define AwsIotDefender_FreeTopic unity_free_mt + + #define AwsIotJobs_MallocOperation unity_malloc_mt + #define AwsIotJobs_FreeOperation unity_free_mt + #define AwsIotJobs_MallocString unity_malloc_mt + #define AwsIotJobs_FreeString unity_free_mt + #define AwsIotJobs_MallocSubscription unity_malloc_mt + #define AwsIotJobs_FreeSubscription unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* Network types to use in the tests. These are forward declarations. */ From 08cfd19f67f67574a71a2077d5d6334a3f02c839 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 19 Jun 2019 16:08:36 -0400 Subject: [PATCH 183/844] Expand doc of Jobs types (#469) --- lib/include/aws_iot_jobs.h | 21 +++ lib/include/types/aws_iot_jobs_types.h | 183 ++++++++++++++++++++++++- 2 files changed, 200 insertions(+), 4 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 735afc5778..e972faf6ab 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -118,6 +118,27 @@ void AwsIotJobs_Cleanup( void ); /** * @brief Get the list of all pending jobs for a Thing and receive an asynchronous * notification when the response arrives. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pGetPendingOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Jobs operation + * completes. + * + * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully + * queuing the Jobs operation. + * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t + * or #AwsIotJobs_Wait), the status will be #AWS_IOT_JOBS_SUCCESS. + * @return Should the Jobs operation fail, the status will be one of: + * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. + * + * @see @ref jobs_function_timedgetpending for a blocking variant of this function. */ /* @[declare_jobs_getpending] */ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 2c07efda59..ab70d5b46c 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -86,6 +86,15 @@ typedef enum AwsIotJobsError * @brief Jobs operation completed successfully. * * Functions that may return this value: + * - @ref jobs_function_init + * - @ref jobs_function_wait + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_setnotifypendingcallback + * - @ref jobs_function_setnotifynextcallback + * - @ref jobs_function_removepersistentsubscriptions * * Will also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) @@ -95,41 +104,105 @@ typedef enum AwsIotJobsError /** * @brief Jobs operation queued, awaiting result. + * + * Functions that may return this value: + * - @ref jobs_function_getpending + * - @ref jobs_function_startnext + * - @ref jobs_function_describe + * - @ref jobs_function_update */ AWS_IOT_JOBS_STATUS_PENDING, /** * @brief Initialization failed. + * + * Functions that may return this value: + * - @ref jobs_function_init */ AWS_IOT_JOBS_INIT_FAILED, /** * @brief At least one parameter is invalid. + * + * Functions that may return this value: + * - @ref jobs_function_getpending and @ref jobs_function_timedgetpending + * - @ref jobs_function_startnext and @ref jobs_function_timedstartnext + * - @ref jobs_function_describe and @ref jobs_function_timeddescribe + * - @ref jobs_function_update and @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * - @ref jobs_function_setnotifypendingcallback + * - @ref jobs_function_setnotifynextcallback */ AWS_IOT_JOBS_BAD_PARAMETER, /** * @brief Jobs operation failed because of memory allocation failure. + * + * Functions that may return this value: + * - @ref jobs_function_getpending and @ref jobs_function_timedgetpending + * - @ref jobs_function_startnext and @ref jobs_function_timedstartnext + * - @ref jobs_function_describe and @ref jobs_function_timeddescribe + * - @ref jobs_function_update and @ref jobs_function_timedupdate + * - @ref jobs_function_setnotifypendingcallback + * - @ref jobs_function_setnotifynextcallback */ AWS_IOT_JOBS_NO_MEMORY, /** * @brief Jobs operation failed because of failure in MQTT library. + * + * Functions that may return this value: + * - @ref jobs_function_getpending and @ref jobs_function_timedgetpending + * - @ref jobs_function_startnext and @ref jobs_function_timedstartnext + * - @ref jobs_function_describe and @ref jobs_function_timeddescribe + * - @ref jobs_function_update and @ref jobs_function_timedupdate + * - @ref jobs_function_setnotifypendingcallback + * - @ref jobs_function_setnotifynextcallback + * - @ref jobs_function_removepersistentsubscriptions */ AWS_IOT_JOBS_MQTT_ERROR, /** * @brief Response received from Jobs service not understood. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ AWS_IOT_JOBS_BAD_RESPONSE, /** * @brief A blocking Jobs operation timed out. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * - @ref jobs_function_setnotifypendingcallback + * - @ref jobs_function_setnotifynextcallback */ AWS_IOT_JOBS_TIMEOUT, /** * @brief Jobs operation failed: A request was sent to an unknown topic. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ AWS_IOT_JOBS_INVALID_TOPIC, @@ -137,43 +210,117 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: The contents of the request were not understood. * * Jobs requests must be UTF-8 encoded JSON documents. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ AWS_IOT_JOBS_INVALID_JSON, /** * @brief Jobs operation failed: The contents of the request were invalid. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ AWS_IOT_JOBS_INVALID_REQUEST, /** * @brief Jobs operation failed: An update attempted to change the job execution * to an invalid state. + * + * Functions that may return this value: + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) + * following a call to @ref jobs_function_startnext or @ref jobs_function_update. */ AWS_IOT_JOBS_INVALID_STATE, /** * @brief Jobs operation failed: The specified job execution does not exist. + * + * * Functions that may return this value: + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) + * following a call to @ref jobs_function_describe or @ref jobs_function_update. */ AWS_IOT_JOBS_NOT_FOUND, /** * @brief Jobs operation failed: The Jobs service expected a version that did * not match what was in the request. + * + * * Functions that may return this value: + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) + * following a call to @ref jobs_function_update. */ AWS_IOT_JOBS_VERSION_MISMATCH, /** * @brief Jobs operation failed: The Jobs service encountered an internal error. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ AWS_IOT_JOBS_INTERNAL_ERROR, /** * @brief Jobs operation failed: The request was throttled. + * + * Functions that may return this value: + * - @ref jobs_function_timedgetpending + * - @ref jobs_function_timedstartnext + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_timedupdate + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ AWS_IOT_JOBS_THROTTLED, /** * @brief Jobs operation failed: Attempt to describe a Job in a terminal state. + * + * Functions that may return this value: + * - @ref jobs_function_timeddescribe + * - @ref jobs_function_wait + * + * May also be the value of a Jobs operation completion callback's
+ * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) + * following a call to @ref jobs_function_describe. */ AWS_IOT_JOBS_TERMINAL_STATE } AwsIotJobsError_t; @@ -349,8 +496,10 @@ typedef struct AwsIotJobsCallbackInfo * @ingroup jobs_datatypes_paramstructs * @brief Common information provided to Jobs requests. * - * @paramfor @ref jobs_function_getpending, @ref jobs_function_startnext, - * @ref jobs_function_describe, @ref jobs_function_update + * @paramfor @ref jobs_function_getpending, @ref jobs_function_timedgetpending, + * @ref jobs_function_startnext, @ref jobs_function_timedstartnext + * @ref jobs_function_describe, @ref jobs_function_timeddescribe, + * @ref jobs_function_update, @ref jobs_function_timedupdate * * @initializer{AwsIotJobsRequestInfo_t,AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER} */ @@ -383,11 +532,19 @@ typedef struct AwsIotJobsRequestInfo * This may be set to #AWS_IOT_JOBS_NEXT_JOB to update the next pending Job. * When using #AWS_IOT_JOBS_NEXT_JOB, #AwsIotJobsRequestInfo_t.jobIdLength * should be set to #AWS_IOT_JOBS_NEXT_JOB_LENGTH. + * + * This parameter is ignored for calls to @ref jobs_function_getpending, + * @ref jobs_function_timedgetpending, @ref jobs_function_startnext, + * and @ref jobs_function_timedstartnext. */ const char * pJobId; /** * @brief Length of #AwsIotJobsRequestInfo_t.pJobId. + * + * This parameter is ignored for calls to @ref jobs_function_getpending, + * @ref jobs_function_timedgetpending, @ref jobs_function_startnext, + * and @ref jobs_function_timedstartnext. */ size_t jobIdLength; @@ -407,9 +564,11 @@ typedef struct AwsIotJobsRequestInfo /** * @ingroup jobs_datatypes_paramstructs - * @brief Information on a Job update for @ref jobs_function_update. + * @brief Information on a Job update for @ref jobs_function_startnext and + * @ref jobs_function_update. These functions modify a Job's state. * - * @paramfor jobs_function_update + * @paramfor @ref jobs_function_startnext, @ref jobs_function_timedstartnext, + * @ref jobs_function_update, and @ref jobs_function_timedupdate. * * @initializer{AwsIotJobsUpdateInfo_t,AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER} */ @@ -423,6 +582,10 @@ typedef struct AwsIotJobsUpdateInfo * - #AWS_IOT_JOB_STATE_FAILED * - #AWS_IOT_JOB_STATE_SUCCEEDED * - #AWS_IOT_JOB_STATE_REJECTED + * + * This parameter is ignored for calls to @ref jobs_function_startnext and + * @ref jobs_function_timedstartnext. These functions always set the state + * to #AWS_IOT_JOB_STATE_IN_PROGRESS. */ AwsIotJobState_t newStatus; @@ -437,6 +600,9 @@ typedef struct AwsIotJobsUpdateInfo * This value is useful for ensuring the order of Job updates, i.e. that the * Jobs service does not overwrite a later update with a previous one. If not * needed, it can be set to #AWS_IOT_JOBS_NO_VERSION. + * + * This parameter is ignored for calls to @ref jobs_function_startnext and + * @ref jobs_function_timedstartnext. */ uint32_t expectedVersion; @@ -450,6 +616,9 @@ typedef struct AwsIotJobsUpdateInfo * * This value is optional. It may be set to #AWS_IOT_JOBS_NO_EXECUTION_NUMBER * if not needed. + * + * This parameter is ignored for calls to @ref jobs_function_startnext and + * @ref jobs_function_timedstartnext. */ int32_t executionNumber; @@ -470,6 +639,9 @@ typedef struct AwsIotJobsUpdateInfo * @brief Whether the Job response document should contain the `JobExecutionState`. * * The default value is `false`. + * + * This parameter is ignored for calls to @ref jobs_function_startnext and + * @ref jobs_function_timedstartnext. */ bool includeJobExecutionState; @@ -477,6 +649,9 @@ typedef struct AwsIotJobsUpdateInfo * @brief Whether the Job response document should contain the `JobDocument`. * * The default value is `false`. + * + * This parameter is ignored for calls to @ref jobs_function_startnext and + * @ref jobs_function_timedstartnext. */ bool includeJobDocument; From a8298004a30bf95638b7b2cbb7bdd46ec75ffafc Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 19 Jun 2019 17:55:05 -0400 Subject: [PATCH 184/844] Add Jobs init and cleanup (#470) --- lib/include/aws_iot_jobs.h | 4 +- lib/include/private/aws_iot.h | 20 +++++ lib/include/private/aws_iot_jobs_internal.h | 30 ++++++++ lib/source/common/aws_iot/aws_iot_operation.c | 52 +++++++++++++ lib/source/jobs/CMakeLists.txt | 4 +- lib/source/jobs/aws_iot_jobs_api.c | 77 +++++++++++++++++++ lib/source/jobs/aws_iot_jobs_operation.c | 55 +++++++++++++ lib/source/jobs/aws_iot_jobs_subscription.c | 53 +++++++++++++ lib/source/shadow/aws_iot_shadow_api.c | 55 ++++--------- scripts/ci_test_common.sh | 0 scripts/ci_test_coverage.sh | 7 +- scripts/ci_test_defender.sh | 0 scripts/ci_test_doc.sh | 0 scripts/ci_test_mqtt.sh | 0 scripts/ci_test_shadow.sh | 0 tests/jobs/unit/aws_iot_tests_jobs_api.c | 58 ++++++++++++++ tests/shadow/unit/aws_iot_tests_shadow_api.c | 8 ++ 17 files changed, 378 insertions(+), 45 deletions(-) create mode 100644 lib/source/jobs/aws_iot_jobs_operation.c create mode 100644 lib/source/jobs/aws_iot_jobs_subscription.c mode change 100644 => 100755 scripts/ci_test_common.sh mode change 100644 => 100755 scripts/ci_test_coverage.sh mode change 100644 => 100755 scripts/ci_test_defender.sh mode change 100644 => 100755 scripts/ci_test_doc.sh mode change 100644 => 100755 scripts/ci_test_mqtt.sh mode change 100644 => 100755 scripts/ci_test_shadow.sh diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index e972faf6ab..96f47522cb 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -82,7 +82,7 @@ * Calling this function more than once without first calling @ref * jobs_function_cleanup may result in a crash. * - * @param[in] mqttTimeout The amount of time (in milliseconds) that the Jobs + * @param[in] mqttTimeoutMs The amount of time (in milliseconds) that the Jobs * library will wait for MQTT operations. Optional; set this to `0` to use * @ref AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS. * @@ -95,7 +95,7 @@ * @see @ref jobs_function_cleanup */ /* @[declare_jobs_init] */ -AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeout ); +AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ); /* @[declare_jobs_init] */ /** diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 392168794d..4da168eaca 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -35,6 +35,9 @@ #include #include +/* Platform types include. */ +#include "types/iot_platform_types.h" + /** * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). @@ -95,6 +98,23 @@ typedef struct AwsIotTopicInfo void * ( *mallocString )( size_t size ); /**< @brief Function used to allocate a string, if needed. */ } AwsIotTopicInfo_t; +/** + * @brief Initializes the lists used by AWS IoT operations. + * + * @param[in] pPendingOperationsList The list that holds pending operations for + * a library. + * @param[in] pSubscriptionsList The list that holds subscriptions for a library. + * @param[in] pPendingOperationsMutex The mutex that guards the pending operations + * list. + * @param[in] pSubscriptionsMutex The mutex that guards the subscriptions list. + * + * @return `true` if all initialization succeeded; `false` otherwise. + */ +bool AwsIot_InitLists( IotListDouble_t * pPendingOperationsList, + IotListDouble_t * pSubscriptionsList, + IotMutex_t * pPendingOperationsMutex, + IotMutex_t * pSubscriptionsMutex ); + /** * @brief Checks that a Thing Name is valid for AWS IoT. * diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 38dd768893..b68475f27c 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -184,4 +184,34 @@ typedef struct _jobsOperation IotLink_t link; /**< @brief List link member. */ } _jobsOperation_t; +/* Declarations of variables for internal Jobs files. */ +extern uint32_t _AwsIotJobsMqttTimeoutMs; +extern IotListDouble_t _AwsIotJobsPendingOperations; +extern IotListDouble_t _AwsIotJobsSubscriptions; +extern IotMutex_t _AwsIotJobsPendingOperationsMutex; +extern IotMutex_t _AwsIotJobsSubscriptionsMutex; + +/*------------------------ Jobs operation functions -------------------------*/ + +/** + * @brief Free resources used to record a Jobs operation. This is called when + * the operation completes. + * + * @param[in] pData The operation which completed. This parameter is of type + * `void*` to match the signature of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ +void _AwsIotJobs_DestroyOperation( void * pData ); + +/*----------------------- Jobs subscription functions -----------------------*/ + +/** + * @brief Free resources used for a Jobs subscription object. + * + * @param[in] pData The subscription object to destroy. This parameter is of type + * `void*` to match the signature of [free] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ +void _AwsIotJobs_DestroySubscription( void * pData ); + #endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/lib/source/common/aws_iot/aws_iot_operation.c b/lib/source/common/aws_iot/aws_iot_operation.c index a12523bea1..aaa2bc6039 100644 --- a/lib/source/common/aws_iot/aws_iot_operation.c +++ b/lib/source/common/aws_iot/aws_iot_operation.c @@ -30,9 +30,61 @@ /* Standard includes. */ #include +/* Platform threads include. */ +#include "platform/iot_threads.h" + /* AWS IoT include. */ #include "private/aws_iot.h" +/* Error handling include. */ +#include "private/iot_error.h" + +/*-----------------------------------------------------------*/ + +bool AwsIot_InitLists( IotListDouble_t * pPendingOperationsList, + IotListDouble_t * pSubscriptionsList, + IotMutex_t * pPendingOperationsMutex, + IotMutex_t * pSubscriptionsMutex ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + + /* Flags to track cleanup. */ + bool operationsMutexCreated = false, subscriptionsMutexCreated = false; + + /* Create the mutex guarding the pending operations list. */ + operationsMutexCreated = IotMutex_Create( pPendingOperationsMutex, false ); + + if( operationsMutexCreated == false ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Create the mutex guarding the subscriptions list. */ + subscriptionsMutexCreated = IotMutex_Create( pSubscriptionsMutex, false ); + + if( subscriptionsMutexCreated == false ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Initialize lists. */ + IotListDouble_Create( pPendingOperationsList ); + IotListDouble_Create( pSubscriptionsList ); + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status == false ) + { + if( operationsMutexCreated == true ) + { + IotMutex_Destroy( pPendingOperationsMutex ); + } + } + + IOT_FUNCTION_CLEANUP_END(); +} + /*-----------------------------------------------------------*/ bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt index 40319e88bb..385d6cfb17 100644 --- a/lib/source/jobs/CMakeLists.txt +++ b/lib/source/jobs/CMakeLists.txt @@ -1,7 +1,9 @@ # Jobs library source files. set( JOBS_SOURCES aws_iot_jobs_api.c - aws_iot_jobs_static_memory.c ) + aws_iot_jobs_operation.c + aws_iot_jobs_static_memory.c + aws_iot_jobs_subscription.c ) # Jobs library target. add_library( awsiotjobs diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index f7f40d72b9..320df0f43b 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -27,9 +27,86 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Platform threads include. */ +#include "platform/iot_threads.h" + /* Jobs internal include. */ #include "private/aws_iot_jobs_internal.h" +/* Validate Jobs configuration settings. */ +#if AWS_IOT_JOBS_ENABLE_ASSERTS != 0 && AWS_IOT_JOBS_ENABLE_ASSERTS != 1 + #error "AWS_IOT_JOBS_ENABLE_ASSERTS must be 0 or 1." +#endif +#if AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS <= 0 + #error "AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS cannot be 0 or negative." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Timeout used for MQTT operations. + */ +uint32_t _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; + + bool listInitStatus = AwsIot_InitLists( &_AwsIotJobsPendingOperations, + &_AwsIotJobsSubscriptions, + &_AwsIotJobsPendingOperationsMutex, + &_AwsIotJobsSubscriptionsMutex ); + + if( listInitStatus == false ) + { + IotLogInfo( "Failed to create Jobs lists." ); + + status = AWS_IOT_JOBS_INIT_FAILED; + } + else + { + /* Save the MQTT timeout option. */ + if( mqttTimeoutMs != 0 ) + { + _AwsIotJobsMqttTimeoutMs = mqttTimeoutMs; + } + + IotLogInfo( "Shadow library successfully initialized." ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void AwsIotJobs_Cleanup( void ) +{ + /* Remove and free all items in the Jobs pending operation list. */ + IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); + IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, + _AwsIotJobs_DestroyOperation, + offsetof( _jobsOperation_t, link ) ); + IotMutex_Unlock( &_AwsIotJobsPendingOperationsMutex ); + + /* Remove and free all items in the Jobs subscription list. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + IotListDouble_RemoveAll( &_AwsIotJobsSubscriptions, + _AwsIotJobs_DestroySubscription, + offsetof( _jobsSubscription_t, link ) ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + + /* Restore the default MQTT timeout. */ + _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; + + /* Destroy Shadow library mutexes. */ + IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); + IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); + + IotLogInfo( "Jobs library cleanup done." ); +} + /*-----------------------------------------------------------*/ const char * AwsIotJobs_strerror( AwsIotJobsError_t status ) diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c new file mode 100644 index 0000000000..f9685a76fc --- /dev/null +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_operation.c + * @brief Implements functions that process Jobs operations. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief List of active Jobs operations awaiting a response from the Jobs + * service. + */ +IotListDouble_t _AwsIotJobsPendingOperations = { 0 }; + +/** + * @brief Protects #_AwsIotJobsPendingOperations from concurrent access. + */ +IotMutex_t _AwsIotJobsPendingOperationsMutex; + +/*-----------------------------------------------------------*/ + +void _AwsIotJobs_DestroyOperation( void * pData ) +{ + _jobsOperation_t * pOperation = ( _jobsOperation_t * ) pData; + + AwsIotJobs_Assert( pOperation != NULL ); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c new file mode 100644 index 0000000000..cf560580cb --- /dev/null +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_subscription.c + * @brief Implements functions for interacting with the Jobs library's + * subscription list. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief List of active Jobs subscriptions objects. + */ +IotListDouble_t _AwsIotJobsSubscriptions = { 0 }; + +/** + * @brief Protects #_AwsIotJobsSubscriptions from concurrent access. + */ +IotMutex_t _AwsIotJobsSubscriptionsMutex; + +/*-----------------------------------------------------------*/ + +void _AwsIotJobs_DestroySubscription( void * pData ) +{ + _jobsSubscription_t * pSubscription = ( _jobsSubscription_t * ) pData; +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 9b5aa78f38..ddcb5fdd9b 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -587,57 +587,32 @@ static void _updatedCallbackWrapper( void * pArgument, AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); - - /* Flags to track cleanup. */ - bool pendingOperationsMutexCreated = false, subscriptionsMutexCreated = false; + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; - /* Create the Shadow pending operation list mutex. */ - pendingOperationsMutexCreated = IotMutex_Create( &( _AwsIotShadowPendingOperationsMutex ), false ); + /* Initialize Shadow lists and mutexes. */ + bool listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, + &_AwsIotShadowSubscriptions, + &_AwsIotShadowPendingOperationsMutex, + &_AwsIotShadowSubscriptionsMutex ); - if( pendingOperationsMutexCreated == false ) + if( listInitStatus == false ) { - IotLogError( "Failed to create Shadow pending operation list." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_INIT_FAILED ); - } - - /* Create the Shadow subscription list mutex. */ - subscriptionsMutexCreated = IotMutex_Create( &( _AwsIotShadowSubscriptionsMutex ), false ); + IotLogInfo( "Failed to create Shadow lists." ); - if( subscriptionsMutexCreated == false ) - { - IotLogError( "Failed to create Shadow subscription list." ); - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_INIT_FAILED ); - } - - /* Create Shadow linear containers. */ - IotListDouble_Create( &( _AwsIotShadowPendingOperations ) ); - IotListDouble_Create( &( _AwsIotShadowSubscriptions ) ); - - /* Save the MQTT timeout option. */ - if( mqttTimeoutMs != 0 ) - { - _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; + status = AWS_IOT_SHADOW_INIT_FAILED; } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Clean up on error. */ - if( status != AWS_IOT_SHADOW_SUCCESS ) + else { - if( pendingOperationsMutexCreated == true ) + /* Save the MQTT timeout option. */ + if( mqttTimeoutMs != 0 ) { - IotMutex_Destroy( &_AwsIotShadowPendingOperationsMutex ); + _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; } - } - else - { + IotLogInfo( "Shadow library successfully initialized." ); } - IOT_FUNCTION_CLEANUP_END(); + return status; } /*-----------------------------------------------------------*/ diff --git a/scripts/ci_test_common.sh b/scripts/ci_test_common.sh old mode 100644 new mode 100755 diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh old mode 100644 new mode 100755 index b6d0e0ffa6..b30e34bfbe --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -13,12 +13,15 @@ make -j2 # Run common tests with code coverage. ./bin/iot_tests_common -# Run MQTT tests and demo against AWS IoT with code coverage. +# Run MQTT tests against AWS IoT with code coverage. ./bin/iot_tests_mqtt -# Run Shadow tests and demo with code coverage. +# Run Shadow tests with code coverage. ./bin/aws_iot_tests_shadow +# Run Jobs tests with code coverage. +./bin/aws_iot_tests_jobs + # Generate code coverage results, but only for files in lib/. lcov --directory . --capture --output-file coverage.info lcov --remove coverage.info '*demo*' --output-file coverage.info diff --git a/scripts/ci_test_defender.sh b/scripts/ci_test_defender.sh old mode 100644 new mode 100755 diff --git a/scripts/ci_test_doc.sh b/scripts/ci_test_doc.sh old mode 100644 new mode 100755 diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh old mode 100644 new mode 100755 diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh old mode 100644 new mode 100755 diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 48fba72857..5283a6c27e 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -30,6 +30,12 @@ /* SDK initialization include. */ #include "iot_init.h" +/* MQTT include. */ +#include "iot_mqtt.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + /* Test framework includes. */ #include "unity_fixture.h" @@ -49,6 +55,12 @@ TEST_SETUP( Jobs_Unit_API ) { /* Initialize SDK. */ TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + + /* Initialize the MQTT library. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); + + /* Initialize the Jobs library. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); } /*-----------------------------------------------------------*/ @@ -58,6 +70,9 @@ TEST_SETUP( Jobs_Unit_API ) */ TEST_TEAR_DOWN( Jobs_Unit_API ) { + /* Clean up libraries. */ + AwsIotJobs_Cleanup(); + IotMqtt_Cleanup(); IotSdk_Cleanup(); } @@ -68,6 +83,49 @@ TEST_TEAR_DOWN( Jobs_Unit_API ) */ TEST_GROUP_RUNNER( Jobs_Unit_API ) { + RUN_TEST_CASE( Jobs_Unit_API, Init ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the function @ref jobs_function_init. + */ +TEST( Jobs_Unit_API, Init ) +{ + int32_t i = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + + /* Check that test set up set the default value. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotJobsMqttTimeoutMs ); + + /* The Jobs library was already initialized by test set up. Clean it up + * before running this test. */ + AwsIotJobs_Cleanup(); + + /* Set a MQTT timeout. */ + AwsIotJobs_Init( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + 1 ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotJobsMqttTimeoutMs ); + + /* Cleanup should restore the default MQTT timeout. */ + AwsIotJobs_Cleanup(); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotJobsMqttTimeoutMs ); + + /* Test jobs initialization with mutex creation failures. */ + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + status = AwsIotJobs_Init( 0 ); + + /* Check that the status is either success or "INIT FAILED". */ + if( status == AWS_IOT_JOBS_SUCCESS ) + { + break; + } + + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_INIT_FAILED, status ); + } } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 29dc469b22..6ada5c480b 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -502,6 +502,14 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + status = AwsIotShadow_Delete( _pMqttConnection, + TEST_THING_NAME, + 0, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); + status = AwsIotShadow_Update( _pMqttConnection, &documentInfo, 0, From 91fb9d4e79a1e38102563fcd8bf01feb838fdd93 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 10:58:17 -0400 Subject: [PATCH 185/844] Remove unneeded casts in DeserializePublish (#472) --- lib/source/mqtt/iot_mqtt_serialize.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 18d44935e5..7274c8229c 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -1371,12 +1371,12 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) * a packet identifer, but QoS 0 PUBLISH packets do not. */ if( pOutput->qos == IOT_MQTT_QOS_0 ) { - pOutput->payloadLength = ( uint16_t ) ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh; } else { - pOutput->payloadLength = ( uint16_t ) ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } From 0aac02873051df05092f2b9acbef67d883bea7b3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 12:41:46 -0400 Subject: [PATCH 186/844] Add Jobs request validation (#473) --- lib/include/aws_iot_jobs.h | 14 ++- lib/include/private/aws_iot_jobs_internal.h | 21 ++++ lib/include/types/aws_iot_jobs_types.h | 10 +- lib/source/jobs/aws_iot_jobs_api.c | 131 +++++++++++++++++++- lib/source/jobs/aws_iot_jobs_operation.c | 16 +++ tests/jobs/unit/aws_iot_tests_jobs_api.c | 74 +++++++++++ 6 files changed, 254 insertions(+), 12 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 96f47522cb..15f17724e2 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -120,6 +120,8 @@ void AwsIotJobs_Cleanup( void ); * notification when the response arrives. * * @param[in] pRequestInfo Jobs request parameters. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. * @param[in] pCallbackInfo Asynchronous notification of this function's completion. * @param[out] pGetPendingOperation Set to a handle by which this operation may be referenced * after this function returns. This reference is invalidated once the Jobs operation @@ -142,7 +144,8 @@ void AwsIotJobs_Cleanup( void ); */ /* @[declare_jobs_getpending] */ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsCallbackInfo_t pCallbackInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, AwsIotJobsOperation_t * const pGetPendingOperation ); /* @[declare_jobs_getpending] */ @@ -152,6 +155,7 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques */ /* @[declare_jobs_timedgetpending] */ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, uint32_t timeout ); /* @[declare_jobs_timedgetpending] */ @@ -162,7 +166,8 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR /* @[declare_jobs_startnext] */ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, - const AwsIotJobsCallbackInfo_t pCallbackInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, AwsIotJobsOperation_t * const pStartNextOperation ); /* @[declare_jobs_startnext] */ @@ -173,6 +178,7 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest /* @[declare_jobs_timedstartnext] */ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, uint32_t timeout ); /* @[declare_jobs_timedstartnext] */ @@ -184,6 +190,7 @@ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRe AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, int32_t executionNumber, bool includeJobDocument, + uint32_t flags, const AwsIotJobsCallbackInfo_t * pCallbackInfo, AwsIotJobsOperation_t * const pJobsOperation ); /* @[declare_jobs_describe] */ @@ -196,6 +203,7 @@ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestI AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pRequestInfo, int32_t executionNumber, bool includeJobDocument, + uint32_t flags, uint32_t timeout ); /* @[declare_jobs_timeddescribe] */ @@ -206,6 +214,7 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq /* @[declare_jobs_update] */ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, const AwsIotJobsCallbackInfo_t * pCallbackInfo, AwsIotJobsOperation_t * const pUpdateOperation ); /* @[declare_jobs_update] */ @@ -217,6 +226,7 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf /* @[declare_jobs_timedupdate] */ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, uint32_t timeout ); /* @[declare_jobs_timedupdate] */ diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index b68475f27c..94edeeaac7 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -161,6 +161,22 @@ /*------------------------ Jobs internal data types -------------------------*/ +/** + * @brief Enumerations representing each of the Jobs library's API functions. + */ +typedef enum _jobsOperationType +{ + /* Jobs operations. */ + JOBS_GET_PENDING = 0, /**< @ref jobs_function_getpending */ + JOBS_START_NEXT = 1, /**< @ref jobs_function_startnext */ + JOBS_DESCRIBE = 2, /**< @ref jobs_function_describe */ + JOBS_UPDATE = 3, /**< @ref jobs_function_update */ + + /* Jobs callbacks. */ + SET_NOTIFY_PENDING_CALLBACK = 4, /**< @ref jobs_function_setnotifypendingcallback */ + SET_NOTIFY_NEXT_CALLBACK = 5 /**< @ref jobs_function_setnotifynextcallback */ +} _jobsOperationType_t; + /** * @brief Represents a Jobs subscriptions object. * @@ -184,6 +200,11 @@ typedef struct _jobsOperation IotLink_t link; /**< @brief List link member. */ } _jobsOperation_t; +/* Declarations of names printed in logs. */ +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + extern const char * const _pAwsIotJobsOperationNames[]; +#endif + /* Declarations of variables for internal Jobs files. */ extern uint32_t _AwsIotJobsMqttTimeoutMs; extern IotListDouble_t _AwsIotJobsPendingOperations; diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index ab70d5b46c..c1a5217b7d 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -489,7 +489,7 @@ typedef struct AwsIotJobsCallbackInfo * @see #AwsIotJobsCallbackParam_t for more information on the second parameter. */ void ( * function )( void *, - AwsIotJobsCallbackParam_t ); + AwsIotJobsCallbackParam_t * ); } AwsIotJobsCallbackInfo_t; /** @@ -510,12 +510,6 @@ typedef struct AwsIotJobsRequestInfo */ IotMqttConnection_t mqttConnection; - /** - * @brief Flags which modify the behavior of the Jobs request. See - * @ref jobs_constants_flags. - */ - uint32_t flags; - /** * @brief The Thing Name associated with the Job. */ @@ -796,7 +790,7 @@ typedef struct AwsIotJobsUpdateInfo #define AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotJobsCallbackInfo_t. */ /** @brief Initializer for #AwsIotJobsRequestInfo_t. */ #define AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER \ - { .pClentToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE } + { .pClientToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE } /** @brief Initializer for #AwsIotJobsUpdateInfo_t. */ #define AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER \ { .newStatus = AWS_IOT_JOB_STATE_IN_PROGRESS, \ diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 320df0f43b..2525543ff9 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -33,6 +33,9 @@ /* Jobs internal include. */ #include "private/aws_iot_jobs_internal.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* Validate Jobs configuration settings. */ #if AWS_IOT_JOBS_ENABLE_ASSERTS != 0 && AWS_IOT_JOBS_ENABLE_ASSERTS != 1 #error "AWS_IOT_JOBS_ENABLE_ASSERTS must be 0 or 1." @@ -43,6 +46,25 @@ /*-----------------------------------------------------------*/ +/** + * @brief Validate the #AwsIotJobsRequestInfo_t passed to a Jobs API function. + * + * @param[in] type The Jobs API function type. + * @param[in] pRequestInfo The request info passed to a Jobs API function. + * @param[in] flags Flags used by the Jobs API function. + * @param[in] pCallbackInfo The callback info passed with the request info. + * @param[in] pOperation Operation reference pointer passed to a Jobs API function. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_BAD_PARAMETER. + */ +static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + const AwsIotJobsOperation_t * pOperation ); + +/*-----------------------------------------------------------*/ + /** * @brief Timeout used for MQTT operations. */ @@ -50,6 +72,85 @@ uint32_t _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; /*-----------------------------------------------------------*/ +static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + const AwsIotJobsOperation_t * pOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + + /* Type is not used when logging is disabled. */ + ( void ) type; + + /* Check that the given MQTT connection is valid. */ + if( pRequestInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) + { + IotLogError( "MQTT connection is not initialized for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* Check Thing Name. */ + if( AwsIot_ValidateThingName( pRequestInfo->pThingName, + pRequestInfo->thingNameLength ) == false ) + { + IotLogError( "Thing Name is not valid for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* Checks for waitable operations. */ + if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) + { + if( pOperation == NULL ) + { + IotLogError( "Reference must be provided for a waitable Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + if( pCallbackInfo != NULL ) + { + IotLogError( "Callback should not be set for a waitable Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + /* Check that a callback function is set. */ + if( pCallbackInfo != NULL ) + { + if( pCallbackInfo->function == NULL ) + { + IotLogError( "Callback function must be set for Jobs %s callback.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + /* Check that Thing Name length is set. */ + if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + if( pRequestInfo->clientTokenLength == 0 ) + { + IotLogError( "Client token length must be set for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) { AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; @@ -73,7 +174,7 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) _AwsIotJobsMqttTimeoutMs = mqttTimeoutMs; } - IotLogInfo( "Shadow library successfully initialized." ); + IotLogInfo( "Jobs library successfully initialized." ); } return status; @@ -81,6 +182,32 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pGetPendingOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + + /* Check Thing Name. */ + status = _validateRequestInfo( JOBS_GET_PENDING, + pRequestInfo, + flags, + pCallbackInfo, + pGetPendingOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + void AwsIotJobs_Cleanup( void ) { /* Remove and free all items in the Jobs pending operation list. */ @@ -100,7 +227,7 @@ void AwsIotJobs_Cleanup( void ) /* Restore the default MQTT timeout. */ _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; - /* Destroy Shadow library mutexes. */ + /* Destroy Jobs library mutexes. */ IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index f9685a76fc..d102bbcfa7 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -32,6 +32,22 @@ /*-----------------------------------------------------------*/ +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + +/** + * @brief Printable names for each of the Jobs operations. + */ + const char * const _pAwsIotJobsOperationNames[] = + { + "GET PENDING", + "START NEXT", + "DESCRIBE", + "UPDATE", + "SET NOTIFY-PENDING", + "SET NOTIFY-NEXT" + }; +#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ + /** * @brief List of active Jobs operations awaiting a response from the Jobs * service. diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 5283a6c27e..efe950069c 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -41,6 +41,18 @@ /*-----------------------------------------------------------*/ +/** + * @brief The Thing Name shared among all the tests. + */ +#define TEST_THING_NAME "TestThingName" + +/** + * @brief The length of #TEST_THING_NAME. + */ +#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs API tests. */ @@ -84,6 +96,7 @@ TEST_TEAR_DOWN( Jobs_Unit_API ) TEST_GROUP_RUNNER( Jobs_Unit_API ) { RUN_TEST_CASE( Jobs_Unit_API, Init ); + RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidParameters ); } /*-----------------------------------------------------------*/ @@ -129,3 +142,64 @@ TEST( Jobs_Unit_API, Init ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of Jobs operation functions with various + * invalid parameters. + */ +TEST( Jobs_Unit_API, OperationInvalidParameters ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + + /* Uninitialized MQTT connection. */ + status = AwsIotJobs_GetPending( &requestInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.mqttConnection = ( IotMqttConnection_t ) 0x1; + + /* Invalid Thing Name. */ + status = AwsIotJobs_GetPending( &requestInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + + /* No reference with waitable operation. */ + status = AwsIotJobs_GetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Both callback and waitable flag set. */ + status = AwsIotJobs_GetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + &callbackInfo, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Callback function not set. */ + status = AwsIotJobs_GetPending( &requestInfo, + 0, + &callbackInfo, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Client token length not set. */ + requestInfo.pClientToken = "test"; + requestInfo.clientTokenLength = 0; + status = AwsIotJobs_GetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + 0, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ From 81fea3c283fc77165ca1f51ac95a4a2d1fc4f89f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 12:52:18 -0400 Subject: [PATCH 187/844] Add file for Jobs JSON functions (#474) --- lib/include/private/aws_iot_jobs_internal.h | 20 ++++++++++ lib/source/jobs/CMakeLists.txt | 1 + lib/source/jobs/aws_iot_jobs_api.c | 16 +++++++- lib/source/jobs/aws_iot_jobs_json.c | 44 +++++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 lib/source/jobs/aws_iot_jobs_json.c diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 94edeeaac7..55b351b2e7 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -235,4 +235,24 @@ void _AwsIotJobs_DestroyOperation( void * pData ); */ void _AwsIotJobs_DestroySubscription( void * pData ); +/*--------------------------- Jobs JSON functions ---------------------------*/ + +/** + * @brief Generates a Jobs JSON request document from an #AwsIotJobsRequestInfo_t + * and an #AwsIotJobsUpdateInfo_t. + * + * @param[in] type The type of Jobs operation for the request. + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[out] pRequestJson Set to a buffer containing the request JSON. + * @param[out] pRequestJsonLength Set to the length of the request JSON. + * + * @return #AWS_IOT_JOBS_SUCCESS on success; otherwise, #AWS_IOT_JOBS_NO_MEMORY. + */ +AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const char ** pRequestJson, + size_t * pRequestJsonLength ); + #endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt index 385d6cfb17..30c3b77ca7 100644 --- a/lib/source/jobs/CMakeLists.txt +++ b/lib/source/jobs/CMakeLists.txt @@ -1,6 +1,7 @@ # Jobs library source files. set( JOBS_SOURCES aws_iot_jobs_api.c + aws_iot_jobs_json.c aws_iot_jobs_operation.c aws_iot_jobs_static_memory.c aws_iot_jobs_subscription.c ) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 2525543ff9..9957b1ad2c 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -187,7 +187,9 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques const AwsIotJobsCallbackInfo_t * pCallbackInfo, AwsIotJobsOperation_t * const pGetPendingOperation ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + const char * pRequestJson = NULL; + size_t requestJsonLength = 0; /* Check Thing Name. */ status = _validateRequestInfo( JOBS_GET_PENDING, @@ -201,6 +203,18 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques IOT_GOTO_CLEANUP(); } + /* Generate the request JSON for the Jobs request. */ + status = _AwsIotJobs_GenerateJsonRequest( JOBS_GET_PENDING, + pRequestInfo, + NULL, + &pRequestJson, + &requestJsonLength ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + IOT_FUNCTION_CLEANUP_BEGIN(); IOT_FUNCTION_CLEANUP_END(); diff --git a/lib/source/jobs/aws_iot_jobs_json.c b/lib/source/jobs/aws_iot_jobs_json.c new file mode 100644 index 0000000000..e4660add81 --- /dev/null +++ b/lib/source/jobs/aws_iot_jobs_json.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_json.c + * @brief Implements functions that generate and parse Jobs JSON documents. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const char ** pRequestJson, + size_t * pRequestJsonLength ) +{ + return AWS_IOT_JOBS_SUCCESS; +} + +/*-----------------------------------------------------------*/ From e75dac698cbd80e8f89d6bff7e66d09f3484f1fd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 15:31:24 -0400 Subject: [PATCH 188/844] Increment task pool active thread count with lock (#475) --- lib/source/common/iot_taskpool.c | 96 +++++++++++++++++++------------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index 592be3a5fd..c52c8d2f01 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -44,13 +44,13 @@ * @brief Enter a critical section by locking a mutex. * */ -#define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( pTaskPool->lock ) ) +#define TASKPOOL_ENTER_CRITICAL() IotMutex_Lock( &( pTaskPool->lock ) ) /** * @brief Exit a critical section by unlocking a mutex. * */ -#define TASKPOOL_EXIT_CRITICAL() IotMutex_Unlock( &( pTaskPool->lock ) ) +#define TASKPOOL_EXIT_CRITICAL() IotMutex_Unlock( &( pTaskPool->lock ) ) /** * @brief Maximum semaphore value for wait operations. @@ -299,7 +299,7 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) ); /* Allocate the memory for the task pool */ - pTempTaskPool = ( _taskPool_t * )IotTaskPool_MallocTaskPool( sizeof( _taskPool_t ) ); + pTempTaskPool = ( _taskPool_t * ) IotTaskPool_MallocTaskPool( sizeof( _taskPool_t ) ); if( pTempTaskPool == NULL ) { @@ -312,9 +312,9 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, TASKPOOL_FUNCTION_CLEANUP(); - if( TASKPOOL_FAILED(status) ) + if( TASKPOOL_FAILED( status ) ) { - if(pTempTaskPool != NULL) + if( pTempTaskPool != NULL ) { IotTaskPool_FreeTaskPool( pTempTaskPool ); } @@ -324,7 +324,6 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, *pTaskPool = pTempTaskPool; } - TASKPOOL_FUNCTION_CLEANUP_END(); } @@ -337,7 +336,7 @@ IotTaskPoolError_t IotTaskPool_Destroy( IotTaskPool_t taskPoolHandle ) uint32_t count; bool completeShutdown = true; - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + _taskPool_t * pTaskPool = ( _taskPool_t * ) taskPoolHandle; /* Track how many threads the task pool owns. */ uint32_t activeThreads; @@ -473,7 +472,7 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPoolHandle, uint32_t count, i; - _taskPool_t * pTaskPool = ( _taskPool_t * )taskPoolHandle; + _taskPool_t * pTaskPool = ( _taskPool_t * ) taskPoolHandle; /* Parameter checking. */ TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool ); @@ -536,7 +535,7 @@ IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, /* Build a job around the user-provided storage. */ _initializeJob( ( _taskPoolJob_t * ) pJobStorage, userCallback, pUserContext, true ); - *ppJob = ( IotTaskPoolJob_t )pJobStorage; + *ppJob = ( IotTaskPoolJob_t ) pJobStorage; TASKPOOL_NO_FUNCTION_CLEANUP(); } @@ -549,6 +548,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle IotTaskPoolJob_t * const ppJob ) { _taskPool_t * pTaskPool = NULL; + TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS ); /* Parameter checking. */ @@ -556,7 +556,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; { _taskPoolJob_t * pTempJob = NULL; @@ -603,8 +603,8 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPoolHandl TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobHandle ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; - pJob1 = ( _taskPoolJob_t * )pJobHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; + pJob1 = ( _taskPoolJob_t * ) pJobHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -650,7 +650,7 @@ IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPoolHandle, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -699,7 +699,7 @@ IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPoolHandle, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; TASKPOOL_ENTER_CRITICAL(); { @@ -737,7 +737,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; if( timeMs == 0UL ) { @@ -760,7 +760,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, IotLink_t * pTimerEventLink; uint64_t now; - _taskPoolTimerEvent_t * pTimerEvent = ( _taskPoolTimerEvent_t * )IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); + _taskPoolTimerEvent_t * pTimerEvent = ( _taskPoolTimerEvent_t * ) IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) ); if( pTimerEvent == NULL ) { @@ -776,7 +776,7 @@ IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, pTimerEvent->link.pNext = NULL; pTimerEvent->link.pPrevious = NULL; pTimerEvent->expirationTime = now + timeMs; - pTimerEvent->pJob = ( _taskPoolJob_t * )pJob; + pTimerEvent->pJob = ( _taskPoolJob_t * ) pJob; /* Append the timer event to the timer list. */ IotListDouble_InsertSorted( &pTaskPool->timerEventsList, &pTimerEvent->link, _timerEventCompare ); @@ -824,7 +824,7 @@ IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPoolHandle, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; @@ -858,7 +858,7 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle, TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( taskPoolHandle ); TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob ); - pTaskPool = ( _taskPool_t * )taskPoolHandle; + pTaskPool = ( _taskPool_t * ) taskPoolHandle; if( pStatus != NULL ) { @@ -882,11 +882,15 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle, TASKPOOL_NO_FUNCTION_CLEANUP(); } +/*-----------------------------------------------------------*/ + IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t pJob ) { - return ( IotTaskPoolJobStorage_t * )pJob; + return ( IotTaskPoolJobStorage_t * ) pJob; } +/*-----------------------------------------------------------*/ + const char * IotTaskPool_strerror( IotTaskPoolError_t status ) { const char * pMessage = NULL; @@ -942,6 +946,8 @@ static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPool TASKPOOL_NO_FUNCTION_CLEANUP(); } +/*-----------------------------------------------------------*/ + static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_t * const pInfo, _taskPool_t * const pTaskPool ) { @@ -1035,6 +1041,8 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ TASKPOOL_FUNCTION_CLEANUP_END(); } +/*-----------------------------------------------------------*/ + static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo, _taskPool_t * const pTaskPool ) { @@ -1042,7 +1050,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo uint32_t count; uint32_t threadsCreated = 0; - bool controlInit = false; + bool controlInit = false, threadCreated = false; /* Initialize all internal data structure prior to creating all threads. */ TASKPOOL_ON_ERROR_GOTO_CLEANUP( _initTaskPoolControlStructures( pInfo, pTaskPool ) ); @@ -1052,31 +1060,40 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo IotTaskPool_Assert( pInfo->minThreads == pTaskPool->minThreads ); IotTaskPool_Assert( pInfo->maxThreads == pTaskPool->maxThreads ); - /* The task pool will initialize the minimum number of threads reqeusted by the user upon start. */ + /* The task pool will initialize the minimum number of threads requested by the user upon start. */ /* When a thread is created, it will signal a semaphore to signify that it is about to wait on incoming */ /* jobs. A thread can be woken up for exit or for new jobs only at that point in time. */ /* The exit condition is setting the maximum number of threads to 0. */ - /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ - for( ; threadsCreated < pTaskPool->minThreads; ) + TASKPOOL_ENTER_CRITICAL(); { - /* Create one thread. */ - if( Iot_CreateDetachedThread( _taskPoolWorker, - pTaskPool, - pTaskPool->priority, - pTaskPool->stackSize ) == false ) + /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ + for( ; threadsCreated < pTaskPool->minThreads; ) { - IotLogError( "Could not create worker thread! Exiting..." ); + /* Create one thread. */ + threadCreated = Iot_CreateDetachedThread( _taskPoolWorker, + pTaskPool, + pTaskPool->priority, + pTaskPool->stackSize ); - /* If creating one thread fails, set error condition and exit the loop. */ - TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); - } + if( threadCreated == true ) + { + /* Upon successful thread creation, increase the number of active threads. */ + pTaskPool->activeThreads++; + } + else + { + TASKPOOL_EXIT_CRITICAL(); + + IotLogError( "Could not create worker thread! Exiting..." ); - /* Upon successful thread creation, increase the number of active threads. */ - pTaskPool->activeThreads++; + TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY ); + } - ++threadsCreated; + ++threadsCreated; + } } + TASKPOOL_EXIT_CRITICAL(); TASKPOOL_FUNCTION_CLEANUP(); @@ -1098,7 +1115,7 @@ static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo IotSemaphore_Wait( &pTaskPool->startStopSignal ); } - if(controlInit == true) + if( controlInit == true ) { _destroyTaskPool( pTaskPool ); } @@ -1310,6 +1327,8 @@ static void _initializeJob( _taskPoolJob_t * const pJob, } } +/*-----------------------------------------------------------*/ + static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache ) { _taskPoolJob_t * pJob = NULL; @@ -1494,7 +1513,7 @@ static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, { /* Append the job to the dispatch queue. * Put the job at the front, if it is a high priority job. */ - if(mustGrow == true ) + if( mustGrow == true ) { IotLogDebug( "High priority job: placing job at the head of the queue." ); @@ -1505,7 +1524,6 @@ static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, IotDeQueue_EnqueueTail( &pTaskPool->dispatchQueue, &pJob->link ); } - /* Signal a worker to pick up the job. */ IotSemaphore_Post( &pTaskPool->dispatchSignal ); } From 8f493b83d09497b3e5eeb60be964a2c5128390e7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 16:13:29 -0400 Subject: [PATCH 189/844] Add Jobs structs and defines (#476) --- lib/include/private/aws_iot_jobs_internal.h | 146 +++++++++++++++++++- lib/include/types/aws_iot_jobs_types.h | 10 ++ 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 55b351b2e7..a5d0cf1a4f 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -159,6 +159,106 @@ #endif /** @endcond */ +/** + * @brief The number of currently available Jobs operations. + * + * The 4 Jobs operations are GET PENDING, START NEXT, DESCRIBE, and UPDATE. + */ +#define JOBS_OPERATION_COUNT ( 4 ) + +/** + * @brief The number of currently available Jobs callbacks. + * + * The 2 Jobs callbacks are `jobs/notify` (AKA "Notify Pending") and + * `/jobs/notify-next` (AKA "Notify Next"). + */ +#define JOBS_CALLBACK_COUNT ( 2 ) + +/** + * @brief The string representing a Jobs GET PENDING operation in a Jobs MQTT topic. + */ +#define JOBS_GET_PENDING_OPERATION_STRING "/jobs/get" + +/** + * @brief The length of #JOBS_GET_PENDING_OPERATION_STRING. + */ +#define JOBS_GET_PENDING_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_GET_PENDING_OPERATION_STRING ) - 1 ) ) + +/** + * @brief The string representing a Jobs START NEXT operation in a Jobs MQTT topic. + */ +#define JOBS_START_NEXT_OPERATION_STRING "/jobs/start-next" + +/** + * @brief The length of #JOBS_START_NEXT_OPERATION_STRING. + */ +#define JOBS_START_NEXT_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_START_NEXT_OPERATION_STRING ) - 1 ) ) + +/** + * @brief The string representing a Jobs DESCRIBE operation in a Jobs MQTT topic. + * + * The %s is a placeholder for a Job ID. + */ +#define JOBS_DESCRIBE_OPERATION_STRING "/jobs/%s/get" + +/** + * @brief The length of #JOBS_DESCRIBE_OPERATION_STRING. + * + * This length excludes the length of the placeholder %s. + */ +#define JOBS_DESCRIBE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_DESCRIBE_OPERATION_STRING ) - 3 ) ) + +/** + * @brief The string representing a Jobs UPDATE operation in a Jobs MQTT topic. + * + * The %s is a placeholder for a Job ID. + */ +#define JOBS_UPDATE_OPERATION_STRING "/jobs/%s/update" + +/** + * @brief The length of #JOBS_UPDATE_OPERATION_STRING. + * + * This length excludes the length of the placeholder %s. + */ +#define JOBS_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_UPDATE_OPERATION_STRING ) - 3 ) ) + +/** + * @brief The string representing the Jobs MQTT topic for receiving notifications + * of pending Jobs. + */ +#define JOBS_NOTIFY_PENDING_CALLBACK_STRING "/jobs/notify" + +/** + * @brief The length of #JOBS_NOTIFY_PENDING_CALLBACK_STRING. + */ +#define JOBS_NOTIFY_PENDING_CALLBACK_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_NOTIFY_PENDING_CALLBACK_STRING ) - 1 ) ) + +/** + * @brief The string representing the Jobs MQTT topic for receiving notifications + * of the next pending Job. + */ +#define JOBS_NOTIFY_NEXT_CALLBACK_STRING "/jobs/notify-next" + +/** + * @brief The length of #JOBS_NOTIFY_NEXT_CALLBACK_STRING. + */ +#define JOBS_NOTIFY_NEXT_CALLBACK_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_NOTIFY_NEXT_CALLBACK_STRING ) - 1 ) ) + +/** + * @brief The maximum length of a Job ID, per AWS IoT Service Limits. + * + * See https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits + */ +#define JOBS_MAX_ID_LENGTH ( 64 ) + +/** + * @brief The length of the longest Jobs topic suffix. + * + * This is the length of the longest Job ID plus the length of the "UPDATE" + * operation suffix. + */ +#define JOBS_LONGEST_SUFFIX_LENGTH ( JOBS_MAX_ID_LENGTH + JOBS_UPDATE_OPERATION_STRING_LENGTH ) + /*------------------------ Jobs internal data types -------------------------*/ /** @@ -184,7 +284,18 @@ typedef enum _jobsOperationType */ typedef struct _jobsSubscription { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ + + int32_t references[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counter for Jobs operation topics. */ + AwsIotJobsCallbackInfo_t callback[ JOBS_CALLBACK_COUNT ]; /**< @brief Jobs callbacks for this Thing. */ + + /** + * @brief Buffer allocated for removing Jobs topics. + * + * This buffer is pre-allocated to ensure that memory is available when + * unsubscribing. + */ + char * pTopicBuffer; size_t thingNameLength; /**< @brief Length of Thing Name. */ char pThingName[]; /**< @brief Thing Name associated with this subscriptions object. */ @@ -198,6 +309,39 @@ typedef struct _jobsSubscription typedef struct _jobsOperation { IotLink_t link; /**< @brief List link member. */ + + /* Basic operation information. */ + _jobsOperationType_t type; /**< @brief Operation type. */ + uint32_t flags; /**< @brief Flags passed to operation API function. */ + AwsIotJobsError_t status; /**< @brief Status of operation. */ + + IotMqttConnection_t mqttConnection; /**< @brief MQTT connection associated with this operation. */ + _jobsSubscription_t * pSubscription; /**< @brief Jobs subscriptions object associated with this operation. */ + + /* Jobs request information. */ + const char * pJobsRequest; /**< @brief JSON document to send to the Jobs service. */ + size_t jobsRequestLength; /**< @brief Length of #_jobsOperation_t.pJobsRequest. */ + + const char * pClientToken; /**< @brief Client token sent with request. */ + size_t clientTokenLength; /**< @brief Length of #_jobsOperation_t.pClientToken. */ + + /* Jobs response information. */ + const char * pJobsResponse; /**< @brief Response received from the Jobs service. */ + size_t jobsResponseLength; /**< @brief Length of #_jobsOperation_t.pJobsResponse. */ + + /** + * @brief Function to allocate memory for an incoming Jobs response. + * + * Only used when the flag #AWS_IOT_JOBS_FLAG_WAITABLE is set. + */ + void * ( *mallocResponse )( size_t ); + + /* How to notify of an operation's completion. */ + union + { + IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref jobs_function_wait. */ + AwsIotJobsCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ + } notify; /**< @brief How to notify of an operation's completion. */ } _jobsOperation_t; /* Declarations of names printed in logs. */ diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index c1a5217b7d..fbd5befeed 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -510,6 +510,13 @@ typedef struct AwsIotJobsRequestInfo */ IotMqttConnection_t mqttConnection; + /** + * @brief Function to allocate memory for an incoming response. + * + * This only needs to be set if #AWS_IOT_JOBS_FLAG_WAITABLE is passed. + */ + void * ( *mallocResponse )( size_t ); + /** * @brief The Thing Name associated with the Job. */ @@ -812,6 +819,9 @@ typedef struct AwsIotJobsUpdateInfo * An #AwsIotJobsOperation_t MUST be provided if this flag is set. * Additionally, an #AwsIotJobsCallbackInfo_t MUST NOT be provided. * + * When this flag is set, #AwsIotJobsRequestInfo_t.mallocResponse must be set + * to a function that can be used to allocate memory to hold an incoming response. + * * @note If this flag is set, @ref jobs_function_wait MUST be called to * clean up resources. */ From c1a9a4b8461dbd3602a6a4f47337a1b18b7ead0b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 17:07:22 -0400 Subject: [PATCH 190/844] Move operation subscriptions to AWS IoT common (#477) --- lib/include/private/aws_iot.h | 52 ++++ lib/include/private/aws_iot_shadow_internal.h | 20 +- lib/source/common/CMakeLists.txt | 5 +- .../common/aws_iot/aws_iot_subscription.c | 161 ++++++++++ lib/source/shadow/aws_iot_shadow_api.c | 6 +- lib/source/shadow/aws_iot_shadow_operation.c | 2 +- .../shadow/aws_iot_shadow_subscription.c | 276 ++++-------------- 7 files changed, 280 insertions(+), 242 deletions(-) create mode 100644 lib/source/common/aws_iot/aws_iot_subscription.c diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 4da168eaca..2228af6725 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -38,6 +38,9 @@ /* Platform types include. */ #include "types/iot_platform_types.h" +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + /** * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). @@ -74,6 +77,24 @@ */ #define AWS_IOT_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) ) +/** + * @brief Function pointer representing an MQTT timed operation. + * + * Currently, this is used to represent @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + */ +typedef IotMqttError_t ( * AwsIotMqttFunction_t )( IotMqttConnection_t, + const IotMqttSubscription_t *, + size_t, + uint32_t, + uint32_t ); + +/** + * @brief Function pointer representing an MQTT library callback function. + */ +typedef void ( * AwsIotMqttCallbackFunction_t )( void *, + IotMqttCallbackParam_t * ); + /** * @brief Enumerations representing each of the statuses that may be parsed * from a topic. @@ -98,6 +119,23 @@ typedef struct AwsIotTopicInfo void * ( *mallocString )( size_t size ); /**< @brief Function used to allocate a string, if needed. */ } AwsIotTopicInfo_t; +/** + * @brief Information needed to modify AWS IoT subscription topics. + * + * @warning The buffer passed as `pTopicFilterBase` must be large enough to + * accommodate the "/accepted" and "/rejected" suffixes. + */ +typedef struct AwsIotSubscriptionInfo_t +{ + IotMqttConnection_t mqttConnection; /**< @brief The MQTT connection to use. */ + AwsIotMqttCallbackFunction_t callbackFunction; /**< @brief Callback function for MQTT subscribe. */ + uint32_t timeout; /**< @brief Timeout for MQTT function. */ + + /* Topic filter. */ + char * pTopicFilterBase; /**< @brief Contains the base topic filter, without "/accepted" or "/rejected". */ + uint16_t topicFilterBaseLength; /**< @brief Length of the base topic filter. */ +} AwsIotSubscriptionInfo_t; + /** * @brief Initializes the lists used by AWS IoT operations. * @@ -191,4 +229,18 @@ bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, char ** pTopicBuffer, uint16_t * pOperationTopicLength ); +/** + * @brief Add or remove subscriptions for AWS IoT operations. + * + * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + * @param[in] pSubscriptionInfo Information needed to process an MQTT + * operation. + * + * @return See the return values of @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + */ +IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, + const AwsIotSubscriptionInfo_t * pSubscriptionInfo ); + #endif /* ifndef AWS_IOT_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 9947c998bd..acb0dcca53 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -243,24 +243,6 @@ /*----------------------- Shadow internal data types ------------------------*/ -/** - * @brief Function pointer representing an MQTT timed operation. - * - * Currently, this is used to represent @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. - */ -typedef IotMqttError_t ( * _mqttOperationFunction_t )( IotMqttConnection_t, - const IotMqttSubscription_t *, - size_t, - uint32_t, - uint32_t ); - -/** - * @brief Function pointer representing an MQTT library callback function. - */ -typedef void ( * _mqttCallbackFunction_t )( void *, - IotMqttCallbackParam_t * ); - /** * @brief Enumerations representing each of the Shadow library's API functions. */ @@ -520,7 +502,7 @@ void _AwsIotShadow_DestroySubscription( void * pData ); AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation, char * pTopicBuffer, uint16_t operationTopicLength, - _mqttCallbackFunction_t callback ); + AwsIotMqttCallbackFunction_t callback ); /** * @brief Decrement the reference count of a Shadow subscriptions object. diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt index 756151cadf..a85a2b2592 100644 --- a/lib/source/common/CMakeLists.txt +++ b/lib/source/common/CMakeLists.txt @@ -36,9 +36,10 @@ set( AWS_IOT_COMMON_SOURCES ${AWS_IOT_COMMON_SOURCES} PARENT_SCOPE ) # AWS IoT common sources. set( AWS_IOT_COMMON_SOURCES - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_operation.c ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_operation.c ) + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_subscription.c + ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c ) # AWS IoT common library target. add_library( awsiotcommon diff --git a/lib/source/common/aws_iot/aws_iot_subscription.c b/lib/source/common/aws_iot/aws_iot_subscription.c new file mode 100644 index 0000000000..98151667ce --- /dev/null +++ b/lib/source/common/aws_iot/aws_iot_subscription.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_subscription.c + * @brief Functions for common AWS IoT subscriptions. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* AWS IoT include. */ +#include "private/aws_iot.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/** + * @brief Modify subscriptions, either by unsubscribing or subscribing. + * + * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or @ref + * mqtt_function_timedunsubscribe. + * @param[in] pSubscriptionInfo Information needed to process an MQTT + * operation. + * @param[in] pTopicFilter The topic filter to modify. + * @param[in] topicFilterLength The length of `pTopicFilter`. + * + * @return See the return values of @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + */ +static IotMqttError_t _modifySubscriptions( AwsIotMqttFunction_t mqttOperation, + const AwsIotSubscriptionInfo_t * pSubscriptionInfo, + const char * pTopicFilter, + uint16_t topicFilterLength ); + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _modifySubscriptions( AwsIotMqttFunction_t mqttOperation, + const AwsIotSubscriptionInfo_t * pSubscriptionInfo, + const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + + /* Per the AWS IoT documentation, topic subscriptions are QoS 1. */ + subscription.qos = IOT_MQTT_QOS_1; + + /* Set the members of the subscription parameter. */ + subscription.pTopicFilter = pTopicFilter; + subscription.topicFilterLength = topicFilterLength; + subscription.callback.pCallbackContext = NULL; + subscription.callback.function = pSubscriptionInfo->callbackFunction; + + /* Call the MQTT operation function. */ + status = mqttOperation( pSubscriptionInfo->mqttConnection, + &subscription, + 1, + 0, + pSubscriptionInfo->timeout ); + + return status; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, + const AwsIotSubscriptionInfo_t * pSubscriptionInfo ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_STATUS_PENDING ); + IotMqttError_t acceptedStatus = IOT_MQTT_STATUS_PENDING; + uint16_t topicFilterLength = 0; + + /* Place the topic "accepted" suffix at the end of the topic buffer. */ + ( void ) memcpy( pSubscriptionInfo->pTopicFilterBase + + pSubscriptionInfo->topicFilterBaseLength, + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength + + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + + /* Modify the subscription to the "accepted" topic. */ + acceptedStatus = _modifySubscriptions( mqttOperation, + pSubscriptionInfo, + pSubscriptionInfo->pTopicFilterBase, + topicFilterLength ); + + if( acceptedStatus != IOT_MQTT_SUCCESS ) + { + status = acceptedStatus; + + IOT_GOTO_CLEANUP(); + } + + /* Place the topic "rejected" suffix at the end of the topic buffer. */ + ( void ) memcpy( pSubscriptionInfo->pTopicFilterBase + + pSubscriptionInfo->topicFilterBaseLength, + AWS_IOT_REJECTED_SUFFIX, + AWS_IOT_REJECTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength + + AWS_IOT_REJECTED_SUFFIX_LENGTH ); + + /* Modify the subscription to the "rejected" topic. */ + status = _modifySubscriptions( mqttOperation, + pSubscriptionInfo, + pSubscriptionInfo->pTopicFilterBase, + topicFilterLength ); + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status != IOT_MQTT_SUCCESS ) + { + /* Remove the subscription to the "accepted" topic if the subscription + * to the "rejected" topic failed. */ + if( ( mqttOperation == IotMqtt_TimedSubscribe ) && + ( acceptedStatus == IOT_MQTT_SUCCESS ) ) + { + /* Place the topic "accepted" suffix at the end of the topic buffer. */ + ( void ) memcpy( pSubscriptionInfo->pTopicFilterBase + + pSubscriptionInfo->topicFilterBaseLength, + AWS_IOT_ACCEPTED_SUFFIX, + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength + + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + + ( void ) _modifySubscriptions( IotMqtt_TimedUnsubscribe, + pSubscriptionInfo, + pSubscriptionInfo->pTopicFilterBase, + topicFilterLength ); + } + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index ddcb5fdd9b..3cd76d469b 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -115,7 +115,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, _shadowSubscription_t * pSubscription, - _mqttOperationFunction_t mqttOperation ); + AwsIotMqttFunction_t mqttOperation ); /** * @brief Common function for incoming Shadow callbacks. @@ -424,7 +424,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, _shadowSubscription_t * pSubscription, - _mqttOperationFunction_t mqttOperation ) + AwsIotMqttFunction_t mqttOperation ) { IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; @@ -447,7 +447,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt }; /* Lookup table for Shadow callback function wrappers. */ - const _mqttCallbackFunction_t pCallbackWrapper[ SHADOW_CALLBACK_COUNT ] = + const AwsIotMqttCallbackFunction_t pCallbackWrapper[ SHADOW_CALLBACK_COUNT ] = { _deltaCallbackWrapper, /* Delta callback. */ _updatedCallbackWrapper, /* Updated callback. */ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 703f69a08c..2f15987962 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -595,7 +595,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; /* Lookup table for Shadow operation callbacks. */ - const _mqttCallbackFunction_t shadowCallbacks[ SHADOW_OPERATION_COUNT ] = + const AwsIotMqttCallbackFunction_t shadowCallbacks[ SHADOW_OPERATION_COUNT ] = { _deleteCallback, _getCallback, diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index a8d9975c17..1cdf83ecc6 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -68,25 +68,6 @@ typedef struct _thingName static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, void * pMatch ); -/** - * @brief Modify Shadow subscriptions, either by unsubscribing or subscribing. - * - * @param[in] mqttConnection The MQTT connection to use. - * @param[in] pTopicFilter The topic filter to modify. - * @param[in] topicFilterLength The length of `pTopicFilter`. - * @param[in] callback The callback function to execute for an incoming message. - * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or @ref - * mqtt_function_timedunsubscribe. - * - * @return #AWS_IOT_SHADOW_STATUS_PENDING on success; otherwise - * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. - */ -static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mqttConnection, - const char * pTopicFilter, - uint16_t topicFilterLength, - _mqttCallbackFunction_t callback, - _mqttOperationFunction_t mqttOperation ); - /*-----------------------------------------------------------*/ /** @@ -128,69 +109,6 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ -static AwsIotShadowError_t _modifyOperationSubscriptions( IotMqttConnection_t mqttConnection, - const char * pTopicFilter, - uint16_t topicFilterLength, - _mqttCallbackFunction_t callback, - _mqttOperationFunction_t mqttOperation ) -{ - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); - IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; - IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; - - /* The MQTT operation function pointer must be either Subscribe or Unsubscribe. */ - AwsIotShadow_Assert( ( mqttOperation == IotMqtt_TimedSubscribe ) || - ( mqttOperation == IotMqtt_TimedUnsubscribe ) ); - - /* Per the AWS IoT documentation, Shadow topic subscriptions are QoS 1. */ - subscription.qos = IOT_MQTT_QOS_1; - - IotLogDebug( "%s Shadow subscription for %.*s", - mqttOperation == IotMqtt_TimedSubscribe ? "Adding" : "Removing", - topicFilterLength, - pTopicFilter ); - - /* Set the members of the subscription parameter. */ - subscription.pTopicFilter = pTopicFilter; - subscription.topicFilterLength = topicFilterLength; - subscription.callback.pCallbackContext = NULL; - subscription.callback.function = callback; - - /* Call the MQTT operation function. */ - mqttStatus = mqttOperation( mqttConnection, - &subscription, - 1, - 0, - _AwsIotShadowMqttTimeoutMs ); - - /* Check the result of the MQTT operation. */ - if( mqttStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to %s %.*s, error %s.", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", - topicFilterLength, - pTopicFilter, - IotMqtt_strerror( mqttStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( mqttStatus == IOT_MQTT_NO_MEMORY ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); - } - - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_MQTT_ERROR ); - } - - IotLogDebug( "Successfully %s %.*s", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", - topicFilterLength, - pTopicFilter ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, size_t thingNameLength ) { @@ -355,12 +273,13 @@ void _AwsIotShadow_DestroySubscription( void * pData ) AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOperation, char * pTopicBuffer, uint16_t operationTopicLength, - _mqttCallbackFunction_t callback ) + AwsIotMqttCallbackFunction_t callback ) { IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); - uint16_t topicFilterLength = 0; const _shadowOperationType_t type = pOperation->type; _shadowSubscription_t * pSubscription = pOperation->pSubscription; + IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; + AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; /* Do nothing if this operation has persistent subscriptions. */ if( pSubscription->references[ type ] == PERSISTENT_SUBSCRIPTION ) @@ -381,64 +300,34 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Check if there are any existing references for this operation. */ if( pSubscription->references[ type ] == 0 ) { - /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - - /* There should not be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == false ); - - /* Add a subscription to the Shadow "accepted" topic. */ - status = _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - callback, - IotMqtt_TimedSubscribe ); - - if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + /* Set the parameters needed to add subscriptions. */ + subscriptionInfo.mqttConnection = pOperation->mqttConnection; + subscriptionInfo.callbackFunction = callback; + subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; + + subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedSubscribe, + &subscriptionInfo ); + + /* Convert MQTT return code to Shadow return code. */ + switch( subscriptionStatus ) { - IOT_GOTO_CLEANUP(); - } + case IOT_MQTT_SUCCESS: + status = AWS_IOT_SHADOW_STATUS_PENDING; + break; - /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - AWS_IOT_REJECTED_SUFFIX, - AWS_IOT_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_REJECTED_SUFFIX_LENGTH ); - - /* There should not be an active subscription for the rejected topic. */ - AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == false ); - - /* Add a subscription to the Shadow "rejected" topic. */ - status = _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - callback, - IotMqtt_TimedSubscribe ); + case IOT_MQTT_NO_MEMORY: + status = AWS_IOT_SHADOW_NO_MEMORY; + break; + + default: + status = AWS_IOT_SHADOW_MQTT_ERROR; + break; + } if( status != AWS_IOT_SHADOW_STATUS_PENDING ) { - /* Failed to add subscription to Shadow "rejected" topic. Remove - * subscription for the Shadow "accepted" topic. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - - ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - callback, - IotMqtt_TimedUnsubscribe ); - IOT_GOTO_CLEANUP(); } } @@ -479,6 +368,7 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, const _shadowOperationType_t type = pOperation->type; _shadowSubscription_t * pSubscription = pOperation->pSubscription; uint16_t operationTopicLength = 0; + AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; /* Do nothing if this Shadow operation has persistent subscriptions. */ if( pSubscription->references[ type ] != PERSISTENT_SUBSCRIPTION ) @@ -507,43 +397,14 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, &( pSubscription->pTopicBuffer ), &operationTopicLength ); - /* Place the topic "accepted" suffix at the end of the Shadow topic buffer. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - - /* There should be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == true ); - - /* Remove the subscription from the Shadow "accepted" topic. */ - ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL, - IotMqtt_TimedUnsubscribe ); - - /* Place the topic "rejected" suffix at the end of the Shadow topic buffer. */ - ( void ) memcpy( pTopicBuffer + operationTopicLength, - AWS_IOT_REJECTED_SUFFIX, - AWS_IOT_REJECTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - - /* There should be an active subscription for the accepted topic. */ - AwsIotShadow_Assert( IotMqtt_IsSubscribed( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL ) == true ); - - /* Remove the subscription from the Shadow "rejected" topic. */ - ( void ) _modifyOperationSubscriptions( pOperation->mqttConnection, - pTopicBuffer, - topicFilterLength, - NULL, - IotMqtt_TimedUnsubscribe ); + /* Set the parameters needed to remove subscriptions. */ + subscriptionInfo.mqttConnection = pOperation->mqttConnection; + subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; + + ( void ) AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + &subscriptionInfo ); } /* Check if this subscription should be deleted. */ @@ -568,10 +429,10 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio uint32_t flags ) { int32_t i = 0; - uint16_t operationTopicLength = 0, topicFilterLength = 0; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING, - removeAcceptedStatus = AWS_IOT_SHADOW_STATUS_PENDING, - removeRejectedStatus = AWS_IOT_SHADOW_STATUS_PENDING; + uint16_t operationTopicLength = 0; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING; + AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; _thingName_t thingName = @@ -624,37 +485,32 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio &( pSubscription->pTopicBuffer ), &operationTopicLength ); - /* Remove the "accepted" topic. */ - ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, - AWS_IOT_ACCEPTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); + /* Set the parameters needed to remove subscriptions. */ + subscriptionInfo.mqttConnection = mqttConnection; + subscriptionInfo.timeout = _AwsIotShadowMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; - removeAcceptedStatus = _modifyOperationSubscriptions( mqttConnection, - pSubscription->pTopicBuffer, - topicFilterLength, - NULL, - IotMqtt_TimedUnsubscribe ); + unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + &subscriptionInfo ); - if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + /* Convert MQTT return code to Shadow return code. */ + switch( unsubscribeStatus ) { - break; - } + case IOT_MQTT_SUCCESS: + status = AWS_IOT_SHADOW_SUCCESS; + break; - /* Remove the "rejected" topic. */ - ( void ) memcpy( pSubscription->pTopicBuffer + operationTopicLength, - AWS_IOT_REJECTED_SUFFIX, - AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - topicFilterLength = ( uint16_t ) ( operationTopicLength + - AWS_IOT_REJECTED_SUFFIX_LENGTH ); + case IOT_MQTT_NO_MEMORY: + status = AWS_IOT_SHADOW_NO_MEMORY; + break; - removeRejectedStatus = _modifyOperationSubscriptions( mqttConnection, - pSubscription->pTopicBuffer, - topicFilterLength, - NULL, - IotMqtt_TimedUnsubscribe ); + default: + status = AWS_IOT_SHADOW_MQTT_ERROR; + break; + } - if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + if( status != AWS_IOT_SHADOW_SUCCESS ) { break; } @@ -681,20 +537,6 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); - /* Check the results of the MQTT unsubscribes. */ - if( removeAcceptedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) - { - status = removeAcceptedStatus; - } - else if( removeRejectedStatus != AWS_IOT_SHADOW_STATUS_PENDING ) - { - status = removeRejectedStatus; - } - else - { - status = AWS_IOT_SHADOW_SUCCESS; - } - return status; } From 33eb91ee96f7845d229a85ac9b923f0d61cb3e66 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 20 Jun 2019 20:07:02 -0400 Subject: [PATCH 191/844] Move MQTT mocks to separate files (#478) --- doc/lib/jobs.txt | 2 +- doc/lib/mqtt.txt | 1 + doc/lib/shadow.txt | 2 +- tests/CMakeLists.txt | 5 + tests/mqtt/mock/iot_tests_mqtt_mock.c | 405 +++++++++++++++++++ tests/mqtt/mock/iot_tests_mqtt_mock.h | 62 +++ tests/shadow/CMakeLists.txt | 8 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 328 +-------------- 8 files changed, 495 insertions(+), 318 deletions(-) create mode 100644 tests/mqtt/mock/iot_tests_mqtt_mock.c create mode 100644 tests/mqtt/mock/iot_tests_mqtt_mock.h diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index 88bdef3fac..add3ab171b 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -50,7 +50,7 @@ In addition to the components above, the Jobs library also depends on C standard @brief Tests written for the Jobs library. The Jobs tests reside in the `tests/jobs` directory. They are divided into the following subdirectories: -- `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT test access files.](@ref mqtt_tests) +- `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) The Jobs unit tests require no special configuration. diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 90fd1405b1..d69ef52a97 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -116,6 +116,7 @@ This setting can be increased for a longer demo. The MQTT demo publishes a total The MQTT tests reside in the `tests/mqtt` directory. They are divided into the following subdirectories: - `access`: Helper files that allow access to internal variables and functions of the MQTT library. +- `mock`: Simulates network responses to MQTT packets; used for testing other libraries that depend on MQTT. - `system`: MQTT system and stress tests. These tests require a network connection. Stress tests may run for a long time, so they are not run unless the command line option `-l` is given to the test executable. - `unit`: MQTT unit tests. These tests do not require a network connection. diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index afb2e44ad8..9d28ce3abd 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -86,7 +86,7 @@ This value may be `0`, which causes a new Shadow update to be sent as soon as th The Shadow tests reside in the `tests/shadow` directory. They are divided into the following subdirectories: - `system`: Shadow system tests. These tests require a network connection and AWS IoT credentials. The command line option `-n` may be passed to the test executable to disable these tests. -- `unit`: Shadow unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT test access files.](@ref mqtt_tests) +- `unit`: Shadow unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) See @subpage shadow_tests_config for configuration settings that change the behavior of the Shadow system tests. The Shadow unit tests require no special configuration. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c185ebc6de..a71e4c753d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,11 @@ set( IOT_TEST_APP_FILES ${PROJECT_SOURCE_DIR}/tests/iot_config.h ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) +# MQTT mock sources. +set( MQTT_MOCK_SOURCES + ${PROJECT_SOURCE_DIR}/tests/mqtt/mock/iot_tests_mqtt_mock.c + ${PROJECT_SOURCE_DIR}/tests/mqtt/mock/iot_tests_mqtt_mock.h ) + # Common tests. add_subdirectory( common ) diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.c b/tests/mqtt/mock/iot_tests_mqtt_mock.c new file mode 100644 index 0000000000..472f86532c --- /dev/null +++ b/tests/mqtt/mock/iot_tests_mqtt_mock.c @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_mqtt_mock.c + * @brief Implements functions to mock MQTT network responses. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* MQTT internal include. */ +#include "private/iot_mqtt_internal.h" + +/* MQTT mock include. */ +#include "iot_tests_mqtt_mock.h" + +/* MQTT test access include. */ +#include "iot_test_access_mqtt.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" +#include "platform/iot_threads.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief A delay that simulates the time required for an MQTT packet to be sent + * to the server and for the server to send a response. + */ +#define NETWORK_ROUND_TRIP_TIME_MS ( 25 ) + +/** + * @brief The maximum size of any MQTT acknowledgement packet (e.g. SUBACK, + * PUBACK, UNSUBACK) used in these tests. + */ +#define ACKNOWLEDGEMENT_PACKET_SIZE ( 5 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Context for calls to the network receive function. + */ +typedef struct _receiveContext +{ + const uint8_t * pData; /**< @brief The data to receive. */ + size_t dataLength; /**< @brief Length of data. */ + size_t dataIndex; /**< @brief Next byte of data to read. */ +} _receiveContext_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief The MQTT connection object shared among all the tests. + */ +static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/** + * @brief The #IotNetworkInterface_t to share among the tests. + */ +static IotNetworkInterface_t _networkInterface = { 0 }; + +/** + * @brief Timer used to simulate a response from the network. + */ +static IotTimer_t _receiveTimer; + +/** + * @brief Synchronizes the MQTT send and receive threads in these tests. + */ +static IotMutex_t _lastPacketMutex; + +/** + * @brief The type of the last packet sent by the send thread. + * + * Must be one of: PUBLISH, SUBSCRIBE, UNSUBSCRIBE. + */ +static uint8_t _lastPacketType = 0; + +/** + * @brief The packet identifier of the last packet send by the send thread. + */ +static uint16_t _lastPacketIdentifier = 0; + +/*-----------------------------------------------------------*/ + +/** + * @brief Invokes the MQTT receive callback to simulate a response received from + * the network. + */ +static void _receiveThread( void * pArgument ) +{ + uint8_t pReceivedData[ ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; + _receiveContext_t receiveContext = { 0 }; + + receiveContext.pData = pReceivedData; + receiveContext.dataLength = ACKNOWLEDGEMENT_PACKET_SIZE; + + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + /* Lock mutex to read and process the last packet sent. */ + IotMutex_Lock( &_lastPacketMutex ); + + /* Ensure that the last packet type and identifier were set. */ + IotTest_Assert( _lastPacketType != 0 ); + IotTest_Assert( _lastPacketIdentifier != 0 ); + + /* Set the packet identifier in the ACK packet. */ + pReceivedData[ 2 ] = UINT16_HIGH_BYTE( _lastPacketIdentifier ); + pReceivedData[ 3 ] = UINT16_LOW_BYTE( _lastPacketIdentifier ); + + /* Create the corresponding ACK packet based on the last packet type. */ + switch( _lastPacketType ) + { + case MQTT_PACKET_TYPE_PUBLISH: + + pReceivedData[ 0 ] = MQTT_PACKET_TYPE_PUBACK; + pReceivedData[ 1 ] = 2; + receiveContext.dataLength = 4; + break; + + case MQTT_PACKET_TYPE_SUBSCRIBE: + + pReceivedData[ 0 ] = MQTT_PACKET_TYPE_SUBACK; + pReceivedData[ 1 ] = 3; + pReceivedData[ 4 ] = 1; + receiveContext.dataLength = 5; + break; + + case MQTT_PACKET_TYPE_UNSUBSCRIBE: + + pReceivedData[ 0 ] = MQTT_PACKET_TYPE_UNSUBACK; + pReceivedData[ 1 ] = 2; + receiveContext.dataLength = 4; + break; + + default: + + /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and + * UNSUBSCRIBE. Abort if any other packet is found. */ + IotTest_Assert( 0 ); + } + + /* Call the MQTT receive callback to process the ACK packet. */ + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); + + IotMutex_Unlock( &_lastPacketMutex ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief A send function that always "succeeds". It also sets the receive + * timer to respond with an ACK when necessary. + */ +static size_t _sendSuccess( void * pSendContext, + const uint8_t * pMessage, + size_t messageLength ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t deserializedPublish = { .link = { 0 } }; + _mqttPacket_t mqttPacket = { .u.pMqttConnection = NULL }; + _receiveContext_t receiveContext = { 0 }; + + /* Ignore the send context. */ + ( void ) pSendContext; + + /* Read the packet type, which is the first byte in the message. */ + mqttPacket.type = *pMessage; + + /* Set the members of the receive context. */ + receiveContext.pData = pMessage + 1; + receiveContext.dataLength = messageLength; + + /* Lock the mutex to modify the information on the last packet sent. */ + IotMutex_Lock( &_lastPacketMutex ); + + /* Read the remaining length. */ + mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( &receiveContext, + &_networkInterface ); + IotTest_Assert( mqttPacket.remainingLength != MQTT_REMAINING_LENGTH_INVALID ); + + /* Set the last packet type based on the outgoing message. */ + switch( mqttPacket.type & 0xf0 ) + { + case MQTT_PACKET_TYPE_PUBLISH: + + /* Only set the last packet type to PUBLISH for QoS 1. */ + if( ( ( *pMessage & 0x06 ) >> 1 ) == 1 ) + { + _lastPacketType = MQTT_PACKET_TYPE_PUBLISH; + } + else + { + _lastPacketType = 0; + _lastPacketIdentifier = 0; + } + + break; + + case ( MQTT_PACKET_TYPE_SUBSCRIBE & 0xf0 ): + _lastPacketType = MQTT_PACKET_TYPE_SUBSCRIBE; + break; + + case ( MQTT_PACKET_TYPE_UNSUBSCRIBE & 0xf0 ): + _lastPacketType = MQTT_PACKET_TYPE_UNSUBSCRIBE; + break; + + default: + + /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and + * UNSUBSCRIBE. Abort if any other packet is found. */ + IotTest_Assert( 0 ); + } + + /* Check if a network response is needed. */ + if( _lastPacketType != 0 ) + { + /* Save the packet identifier as the last packet identifier. */ + if( _lastPacketType != MQTT_PACKET_TYPE_PUBLISH ) + { + _lastPacketIdentifier = UINT16_DECODE( receiveContext.pData + receiveContext.dataIndex ); + status = IOT_MQTT_SUCCESS; + } + else + { + mqttPacket.u.pIncomingPublish = &deserializedPublish; + mqttPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - mqttPacket.remainingLength ); + + status = _IotMqtt_DeserializePublish( &mqttPacket ); + _lastPacketIdentifier = mqttPacket.packetIdentifier; + } + + IotTest_Assert( status == IOT_MQTT_SUCCESS ); + IotTest_Assert( _lastPacketIdentifier != 0 ); + + /* Set the receive thread to run after a "network round-trip". */ + IotTest_Assert( IotClock_TimerArm( &_receiveTimer, + NETWORK_ROUND_TRIP_TIME_MS, + 0 ) == true ); + } + + IotMutex_Unlock( &_lastPacketMutex ); + + /* Return the message length to simulate a successful send. */ + return messageLength; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Simulates a network receive function. + */ +static size_t _receive( void * pConnection, + uint8_t * pBuffer, + size_t bytesRequested ) +{ + size_t bytesReceived = 0; + _receiveContext_t * pReceiveContext = pConnection; + + IotTest_Assert( bytesRequested != 0 ); + IotTest_Assert( pReceiveContext->dataIndex < pReceiveContext->dataLength ); + + /* Calculate how much data to copy. */ + const size_t dataAvailable = pReceiveContext->dataLength - pReceiveContext->dataIndex; + + if( bytesRequested > dataAvailable ) + { + bytesReceived = dataAvailable; + } + else + { + bytesReceived = bytesRequested; + } + + /* Copy data into given buffer. */ + if( bytesReceived > 0 ) + { + ( void ) memcpy( pBuffer, + pReceiveContext->pData + pReceiveContext->dataIndex, + bytesReceived ); + + pReceiveContext->dataIndex += bytesReceived; + } + + return bytesReceived; +} + +/*-----------------------------------------------------------*/ + +bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + + /* Flags to track clean up */ + bool packetMutexCreated = false, timerCreated = false; + + /* Clear the last packet type and identifier. */ + _lastPacketType = 0; + _lastPacketIdentifier = 0; + + /* Set the network interface send and receive functions. */ + ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); + _networkInterface.send = _sendSuccess; + _networkInterface.receive = _receive; + networkInfo.pNetworkInterface = &_networkInterface; + + /* Create the mutex that synchronizes the receive callback and send thread. */ + packetMutexCreated = IotMutex_Create( &_lastPacketMutex, false ); + + if( packetMutexCreated == false ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Create the receive thread timer. */ + timerCreated = IotClock_TimerCreate( &_receiveTimer, + _receiveThread, + NULL ); + + if( timerCreated == false ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Initialize the MQTT connection object. */ + _pMqttConnection = IotTestMqtt_createMqttConnection( false, + &networkInfo, + 0 ); + + if( _pMqttConnection == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status == false ) + { + if( packetMutexCreated == true ) + { + IotMutex_Destroy( &_lastPacketMutex ); + } + + if( timerCreated == true ) + { + IotClock_TimerDestroy( &_receiveTimer ); + } + } + else + { + /* Set the output parameter. */ + *pMqttConnection = _pMqttConnection; + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +void IotTest_MqttMockCleanup( void ) +{ + /* Clean up the MQTT connection object. */ + IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + + /* Destroy the receive thread timer. */ + IotClock_TimerDestroy( &_receiveTimer ); + + /* Wait for the receive thread to finish and release the last packet mutex. */ + IotMutex_Lock( &_lastPacketMutex ); + + /* Destroy the last packet mutex. */ + IotMutex_Unlock( &_lastPacketMutex ); + IotMutex_Destroy( &_lastPacketMutex ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.h b/tests/mqtt/mock/iot_tests_mqtt_mock.h new file mode 100644 index 0000000000..a898e4595c --- /dev/null +++ b/tests/mqtt/mock/iot_tests_mqtt_mock.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_tests_mqtt_mock.h + * @brief Declares functions to mock MQTT network responses. + * + * The function in this file are not thread safe; only one MQTT operation + * should be mocked at any time. + */ + +#ifndef IOT_TESTS_MQTT_MOCK_H_ +#define IOT_TESTS_MQTT_MOCK_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + +/* Define an assertion function to use. */ +#ifndef IotTest_Assert + #include + #define IotTest_Assert assert +#endif + +/** + * @brief Initialize an MQTT connection to mock. + * + * @param[out] pMqttConnection Set to an MQTT connection handle on success. + * + * @return `true` if all initialization succeeded; `false` otherwise. + */ +bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ); + +/** + * @brief Clean up the MQTT connection mocking. + */ +void IotTest_MqttMockCleanup( void ); + +#endif /* ifndef IOT_TESTS_MQTT_MOCK_H_ */ diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt index 3fc65f4408..1add87e10e 100644 --- a/tests/shadow/CMakeLists.txt +++ b/tests/shadow/CMakeLists.txt @@ -12,7 +12,12 @@ add_executable( aws_iot_tests_shadow ${SHADOW_SYSTEM_TEST_SOURCES} ${SHADOW_UNIT_TEST_SOURCES} aws_iot_tests_shadow.c - ${IOT_TEST_APP_FILES} ) + ${IOT_TEST_APP_FILES} + ${MQTT_MOCK_SOURCES} ) + +# Include directory for MQTT mock. +target_include_directories( aws_iot_tests_shadow PRIVATE + ${PROJECT_SOURCE_DIR}/tests/mqtt/mock ) # Define the test to run. target_compile_definitions( aws_iot_tests_shadow PRIVATE @@ -25,4 +30,5 @@ target_link_libraries( aws_iot_tests_shadow PRIVATE awsiotshadow unity ) set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER "tests" ) source_group( system FILES ${SHADOW_SYSTEM_TEST_SOURCES} ) source_group( unit FILES ${SHADOW_UNIT_TEST_SOURCES} ) +source_group( mock FILES ${MQTT_MOCK_SOURCES} ) source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_shadow.c ) diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 6ada5c480b..5b4f6c4e72 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -37,26 +37,14 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" -/* Undefine logging configuration set in Shadow internal header. */ -#undef LIBRARY_LOG_NAME -#undef LIBRARY_LOG_LEVEL - -/* MQTT internal include. */ -#include "private/iot_mqtt_internal.h" - -/* Undefine logging configuration set in MQTT internal header. */ -#undef LIBRARY_LOG_NAME -#undef LIBRARY_LOG_LEVEL - -/* Platform layer includes. */ -#include "platform/iot_clock.h" -#include "platform/iot_threads.h" - /* Test framework includes. */ #include "unity_fixture.h" -/* MQTT test access include. */ -#include "iot_test_access_mqtt.h" +/* MQTT include. */ +#include "iot_mqtt.h" + +/* MQTT mock include. */ +#include "iot_tests_mqtt_mock.h" /* Require Shadow library asserts to be enabled for these tests. The Shadow * assert function is used to abort the tests on failure from the MQTT send @@ -87,276 +75,19 @@ /** * @brief The Thing Name shared among all the tests. */ -#define TEST_THING_NAME "TestThingName" +#define TEST_THING_NAME "TestThingName" /** * @brief The length of #TEST_THING_NAME. */ -#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) - -/** - * @brief A delay that simulates the time required for an MQTT packet to be sent - * to the server and for the server to send a response. - */ -#define NETWORK_ROUND_TRIP_TIME_MS ( 25 ) - -/** - * @brief The maximum size of any MQTT acknowledgement packet (e.g. SUBACK, - * PUBACK, UNSUBACK) used in these tests. - */ -#define ACKNOWLEDGEMENT_PACKET_SIZE ( 5 ) - -/*-----------------------------------------------------------*/ - -/** - * @brief Context for calls to the network receive function. - */ -typedef struct _receiveContext -{ - const uint8_t * pData; /**< @brief The data to receive. */ - size_t dataLength; /**< @brief Length of data. */ - size_t dataIndex; /**< @brief Next byte of data to read. */ -} _receiveContext_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief The MQTT connection object shared among all the tests. - */ -static _mqttConnection_t * _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; - -/** - * @brief The #IotNetworkInterface_t to share among the tests. - */ -static IotNetworkInterface_t _networkInterface = { 0 }; - -/** - * @brief Timer used to simulate a response from the network. - */ -static IotTimer_t _receiveTimer; - -/** - * @brief Synchronizes the MQTT send and receive threads in these tests. - */ -static IotMutex_t _lastPacketMutex; - -/** - * @brief The type of the last packet sent by the send thread. - * - * Must be one of: PUBLISH, SUBSCRIBE, UNSUBSCRIBE. - */ -static uint8_t _lastPacketType = 0; - -/** - * @brief The packet identifier of the last packet send by the send thread. - */ -static uint16_t _lastPacketIdentifier = 0; +#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) /*-----------------------------------------------------------*/ /** - * @brief Invokes the MQTT receive callback to simulate a response received from - * the network. + * @brief The MQTT connection shared among the tests. */ -static void _receiveThread( void * pArgument ) -{ - uint8_t pReceivedData[ ACKNOWLEDGEMENT_PACKET_SIZE ] = { 0 }; - _receiveContext_t receiveContext = { 0 }; - - receiveContext.pData = pReceivedData; - receiveContext.dataLength = ACKNOWLEDGEMENT_PACKET_SIZE; - - /* Silence warnings about unused parameters. */ - ( void ) pArgument; - - /* Lock mutex to read and process the last packet sent. */ - IotMutex_Lock( &_lastPacketMutex ); - - /* Ensure that the last packet type and identifier were set. */ - AwsIotShadow_Assert( _lastPacketType != 0 ); - AwsIotShadow_Assert( _lastPacketIdentifier != 0 ); - - /* Set the packet identifier in the ACK packet. */ - pReceivedData[ 2 ] = UINT16_HIGH_BYTE( _lastPacketIdentifier ); - pReceivedData[ 3 ] = UINT16_LOW_BYTE( _lastPacketIdentifier ); - - /* Create the corresponding ACK packet based on the last packet type. */ - switch( _lastPacketType ) - { - case MQTT_PACKET_TYPE_PUBLISH: - - pReceivedData[ 0 ] = MQTT_PACKET_TYPE_PUBACK; - pReceivedData[ 1 ] = 2; - receiveContext.dataLength = 4; - break; - - case MQTT_PACKET_TYPE_SUBSCRIBE: - - pReceivedData[ 0 ] = MQTT_PACKET_TYPE_SUBACK; - pReceivedData[ 1 ] = 3; - pReceivedData[ 4 ] = 1; - receiveContext.dataLength = 5; - break; - - case MQTT_PACKET_TYPE_UNSUBSCRIBE: - - pReceivedData[ 0 ] = MQTT_PACKET_TYPE_UNSUBACK; - pReceivedData[ 1 ] = 2; - receiveContext.dataLength = 4; - break; - - default: - - /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and - * UNSUBSCRIBE. Abort if any other packet is found. */ - AwsIotShadow_Assert( 0 ); - } - - /* Call the MQTT receive callback to process the ACK packet. */ - IotMqtt_ReceiveCallback( &receiveContext, - _pMqttConnection ); - - IotMutex_Unlock( &_lastPacketMutex ); -} - -/*-----------------------------------------------------------*/ - -/** - * @brief A send function that always "succeeds". It also sets the receive - * timer to respond with an ACK when necessary. - */ -static size_t _sendSuccess( void * pSendContext, - const uint8_t * pMessage, - size_t messageLength ) -{ - IotMqttError_t status = IOT_MQTT_STATUS_PENDING; - _mqttOperation_t deserializedPublish = { .link = { 0 } }; - _mqttPacket_t mqttPacket = { .u.pMqttConnection = NULL }; - _receiveContext_t receiveContext = { 0 }; - - /* Ignore the send context. */ - ( void ) pSendContext; - - /* Read the packet type, which is the first byte in the message. */ - mqttPacket.type = *pMessage; - - /* Set the members of the receive context. */ - receiveContext.pData = pMessage + 1; - receiveContext.dataLength = messageLength; - - /* Lock the mutex to modify the information on the last packet sent. */ - IotMutex_Lock( &_lastPacketMutex ); - - /* Read the remaining length. */ - mqttPacket.remainingLength = _IotMqtt_GetRemainingLength( &receiveContext, - &_networkInterface ); - AwsIotShadow_Assert( mqttPacket.remainingLength != MQTT_REMAINING_LENGTH_INVALID ); - - /* Set the last packet type based on the outgoing message. */ - switch( mqttPacket.type & 0xf0 ) - { - case MQTT_PACKET_TYPE_PUBLISH: - - /* Only set the last packet type to PUBLISH for QoS 1. */ - if( ( ( *pMessage & 0x06 ) >> 1 ) == 1 ) - { - _lastPacketType = MQTT_PACKET_TYPE_PUBLISH; - } - else - { - _lastPacketType = 0; - _lastPacketIdentifier = 0; - } - - break; - - case ( MQTT_PACKET_TYPE_SUBSCRIBE & 0xf0 ): - _lastPacketType = MQTT_PACKET_TYPE_SUBSCRIBE; - break; - - case ( MQTT_PACKET_TYPE_UNSUBSCRIBE & 0xf0 ): - _lastPacketType = MQTT_PACKET_TYPE_UNSUBSCRIBE; - break; - - default: - - /* The only valid outgoing packets are PUBLISH, SUBSCRIBE, and - * UNSUBSCRIBE. Abort if any other packet is found. */ - AwsIotShadow_Assert( 0 ); - } - - /* Check if a network response is needed. */ - if( _lastPacketType != 0 ) - { - /* Save the packet identifier as the last packet identifier. */ - if( _lastPacketType != MQTT_PACKET_TYPE_PUBLISH ) - { - _lastPacketIdentifier = UINT16_DECODE( receiveContext.pData + receiveContext.dataIndex ); - status = IOT_MQTT_SUCCESS; - } - else - { - mqttPacket.u.pIncomingPublish = &deserializedPublish; - mqttPacket.pRemainingData = ( uint8_t * ) pMessage + ( messageLength - mqttPacket.remainingLength ); - - status = _IotMqtt_DeserializePublish( &mqttPacket ); - _lastPacketIdentifier = mqttPacket.packetIdentifier; - } - - AwsIotShadow_Assert( status == IOT_MQTT_SUCCESS ); - AwsIotShadow_Assert( _lastPacketIdentifier != 0 ); - - /* Set the receive thread to run after a "network round-trip". */ - AwsIotShadow_Assert( IotClock_TimerArm( &_receiveTimer, - NETWORK_ROUND_TRIP_TIME_MS, - 0 ) == true ); - } - - IotMutex_Unlock( &_lastPacketMutex ); - - /* Return the message length to simulate a successful send. */ - return messageLength; -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Simulates a network receive function. - */ -static size_t _receive( void * pConnection, - uint8_t * pBuffer, - size_t bytesRequested ) -{ - size_t bytesReceived = 0; - _receiveContext_t * pReceiveContext = pConnection; - - AwsIotShadow_Assert( bytesRequested != 0 ); - AwsIotShadow_Assert( pReceiveContext->dataIndex < pReceiveContext->dataLength ); - - /* Calculate how much data to copy. */ - const size_t dataAvailable = pReceiveContext->dataLength - pReceiveContext->dataIndex; - - if( bytesRequested > dataAvailable ) - { - bytesReceived = dataAvailable; - } - else - { - bytesReceived = bytesRequested; - } - - /* Copy data into given buffer. */ - if( bytesReceived > 0 ) - { - ( void ) memcpy( pBuffer, - pReceiveContext->pData + pReceiveContext->dataIndex, - bytesReceived ); - - pReceiveContext->dataIndex += bytesReceived; - } - - return bytesReceived; -} +static IotMqttConnection_t _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*-----------------------------------------------------------*/ @@ -372,8 +103,6 @@ TEST_GROUP( Shadow_Unit_API ); */ TEST_SETUP( Shadow_Unit_API ) { - IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - /* Initialize SDK. */ TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); @@ -383,29 +112,8 @@ TEST_SETUP( Shadow_Unit_API ) /* Initialize the Shadow library. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); - /* Clear the last packet type and identifier. */ - _lastPacketType = 0; - _lastPacketIdentifier = 0; - - /* Create the mutex that synchronizes the receive callback and send thread. */ - TEST_ASSERT_EQUAL_INT( true, IotMutex_Create( &_lastPacketMutex, false ) ); - - /* Create the receive thread timer. */ - IotClock_TimerCreate( &_receiveTimer, - _receiveThread, - NULL ); - - /* Set the network interface send function. */ - ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); - _networkInterface.send = _sendSuccess; - _networkInterface.receive = _receive; - networkInfo.pNetworkInterface = &_networkInterface; - - /* Initialize the MQTT connection object to use for the Shadow tests. */ - _pMqttConnection = IotTestMqtt_createMqttConnection( false, - &networkInfo, - 0 ); - TEST_ASSERT_NOT_NULL( _pMqttConnection ); + /* Initialize MQTT mock. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_pMqttConnection ) ); } /*-----------------------------------------------------------*/ @@ -415,8 +123,8 @@ TEST_SETUP( Shadow_Unit_API ) */ TEST_TEAR_DOWN( Shadow_Unit_API ) { - /* Clean up the MQTT connection object. */ - IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); + /* Clean up MQTT mock. */ + IotTest_MqttMockCleanup(); /* Clean up the Shadow library. */ AwsIotShadow_Cleanup(); @@ -426,16 +134,6 @@ TEST_TEAR_DOWN( Shadow_Unit_API ) /* Clean up SDK. */ IotSdk_Cleanup(); - - /* Destroy the receive thread timer. */ - IotClock_TimerDestroy( &_receiveTimer ); - - /* Wait for the receive thread to finish and release the last packet mutex. */ - IotMutex_Lock( &_lastPacketMutex ); - - /* Destroy the last packet mutex. */ - IotMutex_Unlock( &_lastPacketMutex ); - IotMutex_Destroy( &_lastPacketMutex ); } /*-----------------------------------------------------------*/ From 2c32e2b152a2227b02d64d5861f69b010a4ca486 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:09:01 -0400 Subject: [PATCH 192/844] Update link to documentation (#479) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae17e3993c..b1f2fca605 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AWS IoT Device SDK C v4.0.0 Beta -**[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/index.html)** +**[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/index.html)** [![Build Status](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C.svg?branch=v4_beta)](https://travis-ci.org/aws/aws-iot-device-sdk-embedded-C) [![Coverage Status](https://coveralls.io/repos/github/aws/aws-iot-device-sdk-embedded-C/badge.svg?branch=v4_beta)](https://coveralls.io/github/aws/aws-iot-device-sdk-embedded-C?branch=v4_beta) From 0633108544311d8a9da1bf61068867bdb717a3c0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:24:13 -0400 Subject: [PATCH 193/844] Move client token JSON define (#480) --- lib/include/private/aws_iot.h | 10 ++++++++++ lib/source/common/aws_iot/aws_iot_parser.c | 14 ++------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 2228af6725..15399d936a 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -77,6 +77,16 @@ */ #define AWS_IOT_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) ) +/** + * @brief The JSON key used to represent client tokens for AWS IoT. + */ +#define AWS_IOT_CLIENT_TOKEN_KEY "clientToken" + +/** + * @brief The length of #AWS_IOT_CLIENT_TOKEN_KEY. + */ +#define AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ( sizeof( AWS_IOT_CLIENT_TOKEN_KEY ) - 1 ) + /** * @brief Function pointer representing an MQTT timed operation. * diff --git a/lib/source/common/aws_iot/aws_iot_parser.c b/lib/source/common/aws_iot/aws_iot_parser.c index 5f3a8841f6..b0091c1211 100644 --- a/lib/source/common/aws_iot/aws_iot_parser.c +++ b/lib/source/common/aws_iot/aws_iot_parser.c @@ -53,16 +53,6 @@ AWS_IOT_ACCEPTED_SUFFIX_LENGTH + \ 1 + 2 ) -/** - * @brief The JSON key used to represent client tokens for AWS IoT. - */ -#define CLIENT_TOKEN_KEY "clientToken" - -/** - * @brief The length of #CLIENT_TOKEN_KEY. - */ -#define CLIENT_TOKEN_KEY_LENGTH ( sizeof( CLIENT_TOKEN_KEY ) - 1 ) - /** * @brief The longest client token accepted by AWS IoT service, per AWS IoT * service limits. @@ -79,8 +69,8 @@ bool AwsIot_GetClientToken( const char * pJsonDocument, /* Extract the client token from the JSON document. */ bool status = IotJsonUtils_FindJsonValue( pJsonDocument, jsonDocumentLength, - CLIENT_TOKEN_KEY, - CLIENT_TOKEN_KEY_LENGTH, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, pClientToken, pClientTokenLength ); From 14a14c70b512cee6050f6c3c16375ddca9e63e42 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 22 Jun 2019 15:18:45 -0400 Subject: [PATCH 194/844] Refactor Shadow process operation (#481) --- lib/include/private/aws_iot_shadow_internal.h | 2 +- lib/source/shadow/aws_iot_shadow_operation.c | 348 ++++++++++-------- .../shadow/aws_iot_shadow_subscription.c | 7 +- tests/jobs/CMakeLists.txt | 8 +- 4 files changed, 213 insertions(+), 152 deletions(-) diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index acb0dcca53..e9f444ed74 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -494,7 +494,7 @@ void _AwsIotShadow_DestroySubscription( void * pData ); * buffer must already contain the Shadow operation topic, plus enough space for the * status suffix. * - * @return #AWS_IOT_SHADOW_STATUS_PENDING on success. On error, one of + * @return #AWS_IOT_SHADOW_SUCCESS on success. On error, one of * #AWS_IOT_SHADOW_NO_MEMORY or #AWS_IOT_SHADOW_MQTT_ERROR. * * @note This function should be called with the subscription list mutex locked. diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 2f15987962..0328120fd6 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -122,6 +122,29 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, static void _updateCallback( void * pArgument, IotMqttCallbackParam_t * pMessage ); +/** + * @brief Get a Shadow subscription to use with a Shadow operation. + * + * This function may use an existing Shadow subscription, or it may allocate a + * new one. + * + * @param[in] pThingName Thing Name associated with operation. + * @param[in] thingNameLength Length of `pThingName`. + * @param[in] pTopicBuffer Contains the topic to use for subscribing. + * @param[in] operationTopicLength The length of the base topic in `pTopicBuffer`. + * @param[in] pOperation Shadow operation that needs a subscription. + * @param[out] pFreeTopicBuffer Whether the caller may free `pTopicBuffer` + * (which may be assigned to a subscription). + * + * @return #AWS_IOT_SHADOW_SUCCESS or #AWS_IOT_SHADOW_NO_MEMORY + */ +static AwsIotShadowError_t _findSubscription( const char * pThingName, + size_t thingNameLength, + char * pTopicBuffer, + uint16_t operationTopicLength, + _shadowOperation_t * pOperation, + bool * pFreeTopicBuffer ); + /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -425,6 +448,86 @@ static void _updateCallback( void * pArgument, /*-----------------------------------------------------------*/ +static AwsIotShadowError_t _findSubscription( const char * pThingName, + size_t thingNameLength, + char * pTopicBuffer, + uint16_t operationTopicLength, + _shadowOperation_t * pOperation, + bool * pFreeTopicBuffer ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + _shadowSubscription_t * pSubscription = NULL; + + /* Lookup table for Shadow operation callbacks. */ + const AwsIotMqttCallbackFunction_t shadowCallbacks[ SHADOW_OPERATION_COUNT ] = + { + _deleteCallback, + _getCallback, + _updateCallback + }; + + /* Lock the subscriptions mutex for exclusive access. */ + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + + /* Check for an existing subscription. This function will attempt to allocate + * a new subscription if not found. */ + pSubscription = _AwsIotShadow_FindSubscription( pThingName, + thingNameLength ); + + if( pSubscription == NULL ) + { + status = AWS_IOT_SHADOW_NO_MEMORY; + } + else + { + /* Ensure that the subscription Thing Name matches. */ + AwsIotShadow_Assert( pSubscription != NULL ); + AwsIotShadow_Assert( pSubscription->thingNameLength == thingNameLength ); + AwsIotShadow_Assert( strncmp( pSubscription->pThingName, + pThingName, + thingNameLength ) == 0 ); + + /* Set the subscription object for the Shadow operation. */ + pOperation->pSubscription = pSubscription; + + /* Assign the topic buffer to the subscription to use for unsubscribing if + * the subscription has no topic buffer. */ + if( pSubscription->pTopicBuffer == NULL ) + { + pSubscription->pTopicBuffer = pTopicBuffer; + + /* Don't free the topic buffer if it was allocated to the subscription. */ + *pFreeTopicBuffer = false; + } + else + { + *pFreeTopicBuffer = true; + } + + /* Increment the reference count for this Shadow operation's + * subscriptions. */ + status = _AwsIotShadow_IncrementReferences( pOperation, + pTopicBuffer, + operationTopicLength, + shadowCallbacks[ pOperation->type ] ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) + { + /* Failed to add subscriptions for a Shadow operation. The reference + * count was not incremented. Check if this subscription should be + * deleted. */ + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); + } + } + + /* Unlock the Shadow subscription list mutex. */ + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + + return status; +} + +/*-----------------------------------------------------------*/ + AwsIotShadowError_t _AwsIotShadow_CreateOperation( _shadowOperation_t ** pNewOperation, _shadowOperationType_t type, uint32_t flags, @@ -586,22 +689,13 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn _shadowOperation_t * pOperation, const AwsIotShadowDocumentInfo_t * pDocumentInfo ) { - _shadowSubscription_t * pSubscription = NULL; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; char * pTopicBuffer = NULL; uint16_t operationTopicLength = 0; bool freeTopicBuffer = true; IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - /* Lookup table for Shadow operation callbacks. */ - const AwsIotMqttCallbackFunction_t shadowCallbacks[ SHADOW_OPERATION_COUNT ] = - { - _deleteCallback, - _getCallback, - _updateCallback - }; - IotLogDebug( "Processing Shadow operation %s for Thing %.*s.", _pAwsIotShadowOperationNames[ pOperation->type ], thingNameLength, @@ -611,171 +705,127 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn pOperation->mqttConnection = mqttConnection; /* Generate the operation topic buffer. */ - if( _AwsIotShadow_GenerateShadowTopic( pOperation->type, - pThingName, - thingNameLength, - &pTopicBuffer, - &operationTopicLength ) != AWS_IOT_SHADOW_SUCCESS ) + status = _AwsIotShadow_GenerateShadowTopic( pOperation->type, + pThingName, + thingNameLength, + &pTopicBuffer, + &operationTopicLength ); + + if( status != AWS_IOT_SHADOW_SUCCESS ) { IotLogError( "No memory for Shadow operation topic buffer." ); - _AwsIotShadow_DestroyOperation( pOperation ); - - return AWS_IOT_SHADOW_NO_MEMORY; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); } - /* Lock the subscription list mutex for exclusive access. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + /* Get a subscription object for this Shadow operation. */ + status = _findSubscription( pThingName, + thingNameLength, + pTopicBuffer, + operationTopicLength, + pOperation, + &freeTopicBuffer ); - /* Check for an existing subscription. This function will attempt to allocate - * a new subscription if not found. */ - pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength ); - - if( pSubscription == NULL ) + if( status != AWS_IOT_SHADOW_SUCCESS ) { - /* No existing subscription was found, and no new subscription could be - * allocated. */ - status = AWS_IOT_SHADOW_NO_MEMORY; + /* No subscription was found and no subscription could be allocated. */ + IOT_GOTO_CLEANUP(); } - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) - { - /* Ensure that the subscription Thing Name matches. */ - AwsIotShadow_Assert( pSubscription != NULL ); - AwsIotShadow_Assert( pSubscription->thingNameLength == thingNameLength ); - AwsIotShadow_Assert( strncmp( pSubscription->pThingName, - pThingName, - thingNameLength ) == 0 ); - - /* Set the subscription object for the Shadow operation. */ - pOperation->pSubscription = pSubscription; + /* Set the operation topic name. */ + publishInfo.pTopicName = pTopicBuffer; + publishInfo.topicNameLength = operationTopicLength; - /* Assign the topic buffer to the subscription to use for unsubscribing if - * the subscription has no topic buffer. */ - if( pSubscription->pTopicBuffer == NULL ) - { - pSubscription->pTopicBuffer = pTopicBuffer; - - /* This function should not free the topic buffer. */ - freeTopicBuffer = false; - } + IotLogDebug( "Shadow %s message will be published to topic %.*s", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.topicNameLength, + publishInfo.pTopicName ); - /* Increment the reference count for this Shadow operation's - * subscriptions. */ - status = _AwsIotShadow_IncrementReferences( pOperation, - pTopicBuffer, - operationTopicLength, - shadowCallbacks[ pOperation->type ] ); + /* Set the document info if this operation is not a Shadow DELETE. */ + if( pOperation->type != SHADOW_DELETE ) + { + publishInfo.qos = pDocumentInfo->qos; + publishInfo.retryLimit = pDocumentInfo->retryLimit; + publishInfo.retryMs = pDocumentInfo->retryMs; - if( status != AWS_IOT_SHADOW_STATUS_PENDING ) - { - /* Failed to add subscriptions for a Shadow operation. The reference - * count was not incremented. Check if this subscription should be - * deleted. */ - _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); - } + IotLogDebug( "Shadow %s message will be published at QoS %d with " + "retryLimit %d and retryMs %llu.", + _pAwsIotShadowOperationNames[ pOperation->type ], + publishInfo.qos, + publishInfo.retryLimit, + publishInfo.retryMs ); } - /* Unlock the Shadow subscription list mutex. */ - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ + if( pOperation->type == SHADOW_UPDATE ) + { + publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; + publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; + } - /* Check that all memory allocation and subscriptions succeeded. */ - if( status == AWS_IOT_SHADOW_STATUS_PENDING ) + /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, + * per the Shadow spec. */ + else { - /* Set the operation topic name. */ - publishInfo.pTopicName = pTopicBuffer; - publishInfo.topicNameLength = operationTopicLength; + publishInfo.pPayload = ""; + publishInfo.payloadLength = 0; + } - IotLogDebug( "Shadow %s message will be published to topic %.*s", + /* Add Shadow operation to the pending operations list. */ + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), + &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + + /* Publish to the Shadow topic name. */ + publishStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotShadowMqttTimeoutMs ); + + /* Check for errors from the MQTT publish. */ + if( publishStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.topicNameLength, - publishInfo.pTopicName ); + thingNameLength, + pThingName, + IotMqtt_strerror( publishStatus ) ); - /* Set the document info if this operation is not a Shadow DELETE. */ - if( pOperation->type != SHADOW_DELETE ) + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + if( publishStatus == IOT_MQTT_NO_MEMORY ) { - publishInfo.qos = pDocumentInfo->qos; - publishInfo.retryLimit = pDocumentInfo->retryLimit; - publishInfo.retryMs = pDocumentInfo->retryMs; - - IotLogDebug( "Shadow %s message will be published at QoS %d with " - "retryLimit %d and retryMs %llu.", - _pAwsIotShadowOperationNames[ pOperation->type ], - publishInfo.qos, - publishInfo.retryLimit, - publishInfo.retryMs ); + status = AWS_IOT_SHADOW_NO_MEMORY; } - - /* Set the PUBLISH payload to the update document for Shadow UPDATE. */ - if( pOperation->type == SHADOW_UPDATE ) + else { - publishInfo.pPayload = pDocumentInfo->u.update.pUpdateDocument; - publishInfo.payloadLength = pDocumentInfo->u.update.updateDocumentLength; + status = AWS_IOT_SHADOW_MQTT_ERROR; } - /* Set the PUBLISH payload to an empty string for Shadow DELETE and GET, - * per the Shadow spec. */ - else + /* If the "keep subscriptions" flag is not set, decrement the reference + * count. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) { - publishInfo.pPayload = ""; - publishInfo.payloadLength = 0; + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + _AwsIotShadow_DecrementReferences( pOperation, + pTopicBuffer, + NULL ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); } - /* Add Shadow operation to the pending operations list. */ + /* Remove Shadow operation from the pending operations list. */ IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_InsertHead( &( _AwsIotShadowPendingOperations ), - &( pOperation->link ) ); + IotListDouble_Remove( &( pOperation->link ) ); IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - - /* Publish to the Shadow topic name. */ - publishStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotShadowMqttTimeoutMs ); - - /* Check for errors from the MQTT publish. */ - if( publishStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to publish MQTT message to %s %.*s Shadow, error %s.", - _pAwsIotShadowOperationNames[ pOperation->type ], - thingNameLength, - pThingName, - IotMqtt_strerror( publishStatus ) ); - - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( publishStatus == IOT_MQTT_NO_MEMORY ) - { - status = AWS_IOT_SHADOW_NO_MEMORY; - } - else - { - status = AWS_IOT_SHADOW_MQTT_ERROR; - } - - /* If the "keep subscriptions" flag is not set, decrement the reference - * count. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) - { - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pTopicBuffer, - NULL ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - } - - /* Remove Shadow operation from the pending operations list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_Remove( &( pOperation->link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); - } - else - { - IotLogDebug( "Shadow %s PUBLISH message successfully sent.", - _pAwsIotShadowOperationNames[ pOperation->type ] ); - } + } + else + { + IotLogDebug( "Shadow %s PUBLISH message successfully sent.", + _pAwsIotShadowOperationNames[ pOperation->type ] ); } + IOT_FUNCTION_CLEANUP_BEGIN(); + /* Free the topic buffer used by this function if it was not assigned to a * subscription. */ if( ( freeTopicBuffer == true ) && ( pTopicBuffer != NULL ) ) @@ -784,12 +834,18 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn } /* Destroy the Shadow operation on failure. */ - if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + if( status != AWS_IOT_SHADOW_SUCCESS ) { _AwsIotShadow_DestroyOperation( pOperation ); } + else + { + /* Convert successful return code to "status pending", as the Shadow + * library is now waiting for a response from the service. */ + status = AWS_IOT_SHADOW_STATUS_PENDING; + } - return status; + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 1cdf83ecc6..30a7237bef 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -275,7 +275,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe uint16_t operationTopicLength, AwsIotMqttCallbackFunction_t callback ) { - IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_SUCCESS ); const _shadowOperationType_t type = pOperation->type; _shadowSubscription_t * pSubscription = pOperation->pSubscription; IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; @@ -314,7 +314,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe switch( subscriptionStatus ) { case IOT_MQTT_SUCCESS: - status = AWS_IOT_SHADOW_STATUS_PENDING; + status = AWS_IOT_SHADOW_SUCCESS; break; case IOT_MQTT_NO_MEMORY: @@ -326,7 +326,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe break; } - if( status != AWS_IOT_SHADOW_STATUS_PENDING ) + if( status != AWS_IOT_SHADOW_SUCCESS ) { IOT_GOTO_CLEANUP(); } @@ -364,7 +364,6 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, char * pTopicBuffer, _shadowSubscription_t ** pRemovedSubscription ) { - uint16_t topicFilterLength = 0; const _shadowOperationType_t type = pOperation->type; _shadowSubscription_t * pSubscription = pOperation->pSubscription; uint16_t operationTopicLength = 0; diff --git a/tests/jobs/CMakeLists.txt b/tests/jobs/CMakeLists.txt index b5d596d258..d0f3c27013 100644 --- a/tests/jobs/CMakeLists.txt +++ b/tests/jobs/CMakeLists.txt @@ -6,7 +6,12 @@ set( JOBS_UNIT_TEST_SOURCES add_executable( aws_iot_tests_jobs ${JOBS_UNIT_TEST_SOURCES} aws_iot_tests_jobs.c - ${IOT_TEST_APP_FILES} ) + ${IOT_TEST_APP_FILES} + ${MQTT_MOCK_SOURCES} ) + +# Include directory for MQTT mock. +target_include_directories( aws_iot_tests_jobs PRIVATE + ${PROJECT_SOURCE_DIR}/tests/mqtt/mock ) # Define the test to run. target_compile_definitions( aws_iot_tests_jobs PRIVATE @@ -18,4 +23,5 @@ target_link_libraries( aws_iot_tests_jobs PRIVATE awsiotjobs unity ) # Organization of Jobs tests in folders. set_property( TARGET aws_iot_tests_jobs PROPERTY FOLDER "tests" ) source_group( unit FILES ${JOBS_UNIT_TEST_SOURCES} ) +source_group( mock FILES ${MQTT_MOCK_SOURCES} ) source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_jobs.c ) From 36479728df83efb6dfca3334af485f474c3dfb8d Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 22 Jun 2019 15:39:26 -0400 Subject: [PATCH 195/844] Add generation of Jobs Get Pending JSON (#482) --- lib/include/private/aws_iot_jobs_internal.h | 37 +++- lib/source/jobs/CMakeLists.txt | 2 +- lib/source/jobs/aws_iot_jobs_api.c | 14 -- lib/source/jobs/aws_iot_jobs_json.c | 44 ----- lib/source/jobs/aws_iot_jobs_serialize.c | 191 ++++++++++++++++++++ 5 files changed, 225 insertions(+), 63 deletions(-) delete mode 100644 lib/source/jobs/aws_iot_jobs_json.c create mode 100644 lib/source/jobs/aws_iot_jobs_serialize.c diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index a5d0cf1a4f..c657c1da16 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -358,6 +358,25 @@ extern IotMutex_t _AwsIotJobsSubscriptionsMutex; /*------------------------ Jobs operation functions -------------------------*/ +/** + * @brief Create a record for a new in-progress Jobs operation. + * + * @param[in] type The type of Jobs operation for the request. + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] flags Flags variables passed to a user-facing Jobs function. + * @param[in] pCallbackInfo User-provided callback function and parameter. + * @param[out] pNewOperation Set to point to the new operation on success. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY + */ +AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + _jobsOperation_t ** pNewOperation ); + /** * @brief Free resources used to record a Jobs operation. This is called when * the operation completes. @@ -368,6 +387,18 @@ extern IotMutex_t _AwsIotJobsSubscriptionsMutex; */ void _AwsIotJobs_DestroyOperation( void * pData ); +/** + * @brief Process a Jobs operation by sending the necessary MQTT packets. + * + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pOperation Operation data to process. + * + * @return #AWS_IOT_JOBS_STATUS_PENDING on success. On error, one of + * #AWS_IOT_JOBS_NO_MEMORY or #AWS_IOT_JOBS_MQTT_ERROR. + */ +AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * pRequestInfo, + _jobsOperation_t * pOperation ); + /*----------------------- Jobs subscription functions -----------------------*/ /** @@ -388,15 +419,13 @@ void _AwsIotJobs_DestroySubscription( void * pData ); * @param[in] type The type of Jobs operation for the request. * @param[in] pRequestInfo Common Jobs request parameters. * @param[in] pUpdateInfo Jobs update parameters. - * @param[out] pRequestJson Set to a buffer containing the request JSON. - * @param[out] pRequestJsonLength Set to the length of the request JSON. + * @param[in] pOperation Operation associated with the Jobs request. * * @return #AWS_IOT_JOBS_SUCCESS on success; otherwise, #AWS_IOT_JOBS_NO_MEMORY. */ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, - const char ** pRequestJson, - size_t * pRequestJsonLength ); + _jobsOperation_t * pOperation ); #endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt index 30c3b77ca7..930474d3a5 100644 --- a/lib/source/jobs/CMakeLists.txt +++ b/lib/source/jobs/CMakeLists.txt @@ -1,7 +1,7 @@ # Jobs library source files. set( JOBS_SOURCES aws_iot_jobs_api.c - aws_iot_jobs_json.c + aws_iot_jobs_serialize.c aws_iot_jobs_operation.c aws_iot_jobs_static_memory.c aws_iot_jobs_subscription.c ) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 9957b1ad2c..ed92ec646f 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -188,8 +188,6 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques AwsIotJobsOperation_t * const pGetPendingOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - const char * pRequestJson = NULL; - size_t requestJsonLength = 0; /* Check Thing Name. */ status = _validateRequestInfo( JOBS_GET_PENDING, @@ -203,18 +201,6 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques IOT_GOTO_CLEANUP(); } - /* Generate the request JSON for the Jobs request. */ - status = _AwsIotJobs_GenerateJsonRequest( JOBS_GET_PENDING, - pRequestInfo, - NULL, - &pRequestJson, - &requestJsonLength ); - - if( status != AWS_IOT_JOBS_SUCCESS ) - { - IOT_GOTO_CLEANUP(); - } - IOT_FUNCTION_CLEANUP_BEGIN(); IOT_FUNCTION_CLEANUP_END(); diff --git a/lib/source/jobs/aws_iot_jobs_json.c b/lib/source/jobs/aws_iot_jobs_json.c deleted file mode 100644 index e4660add81..0000000000 --- a/lib/source/jobs/aws_iot_jobs_json.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file aws_iot_jobs_json.c - * @brief Implements functions that generate and parse Jobs JSON documents. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" - -/*-----------------------------------------------------------*/ - -AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, - const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - const char ** pRequestJson, - size_t * pRequestJsonLength ) -{ - return AWS_IOT_JOBS_SUCCESS; -} - -/*-----------------------------------------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c new file mode 100644 index 0000000000..ade70130a5 --- /dev/null +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_jobs_serialize.c + * @brief Implements functions that generate and parse Jobs JSON documents. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/** + * @brief The length of client tokens generated by this library. + */ +#define CLIENT_TOKEN_AUTOGENERATE_LENGTH ( 8 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Generate and place a client token in the given buffer. + * + * @param[in] pBuffer The buffer where the client token is placed. + * + * @warning This function does not check the length of `pBuffer`! Any provided + * buffer must be large enough to accommodate #CLIENT_TOKEN_AUTOGENERATE_LENGTH + * characters. + */ +static void _generateClientToken( char * pBuffer ); + +/** + * @brief Generates a request JSON for a GET PENDING operation. + * + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pOperation Operation associated with the Jobs request. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY + */ +static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + _jobsOperation_t * pOperation ); + +/*-----------------------------------------------------------*/ + +static void _generateClientToken( char * pBuffer ) +{ + /* Take the address of the given buffer, truncated to 8 characters. This + * provides a client token that is very likely to be unique while in use. */ + uint32_t clientToken = ( uint32_t ) ( ( uint64_t ) pBuffer % 100000000ULL ); + + ( void ) snprintf( pBuffer, + CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1, + "%08u", clientToken ); +} + +/*-----------------------------------------------------------*/ + +static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + _jobsOperation_t * pOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + char * pJobsRequest = NULL; + size_t copyOffset = 0; + + /* At the very least, the request will contain: {"clientToken":""} */ + size_t requestLength = AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7; + + /* Add the length of the client token. */ + if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + requestLength += pRequestInfo->clientTokenLength; + } + else + { + requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + } + + /* Allocate memory for the request JSON. */ + pJobsRequest = AwsIotJobs_MallocString( requestLength ); + + if( pJobsRequest == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); + + /* Construct the request JSON, which consists of just a clientToken key. */ + ( void ) memcpy( pJobsRequest, "{\"", 2 ); + copyOffset = 2; + + ( void ) memcpy( pJobsRequest + copyOffset, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); + copyOffset += AWS_IOT_CLIENT_TOKEN_KEY_LENGTH; + + ( void ) memcpy( pJobsRequest + copyOffset, "\":\"", 3 ); + copyOffset += 3; + + if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + _generateClientToken( pJobsRequest + copyOffset ); + copyOffset += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + + pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2; + } + else + { + ( void ) memcpy( pJobsRequest + copyOffset, + pRequestInfo->pClientToken, + pRequestInfo->clientTokenLength ); + copyOffset += pRequestInfo->clientTokenLength; + + pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; + } + + ( void ) memcpy( pJobsRequest + copyOffset, "\"}", 2 ); + + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; + pOperation->pClientToken = pJobsRequest + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 4; + + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset + 2 == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + _jobsOperation_t * pOperation ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + + /* Generate request based on the Job operation type. */ + switch( type ) + { + case JOBS_GET_PENDING: + status = _generateGetPendingRequest( pRequestInfo, pOperation ); + break; + + case JOBS_START_NEXT: + break; + + case JOBS_DESCRIBE: + break; + + default: + /* The only remaining valid type is UPDATE. */ + AwsIotJobs_Assert( type == JOBS_UPDATE ); + break; + } + + return status; +} + +/*-----------------------------------------------------------*/ From 9429f5d87e81bfaa6cb2141c7e363a91fe0a7605 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 22 Jun 2019 17:23:47 -0400 Subject: [PATCH 196/844] Move Thing Name struct and flag to AWS IoT common (#483) --- lib/include/private/aws_iot.h | 33 +++++++++++++++---- lib/include/private/aws_iot_shadow_internal.h | 9 ----- lib/source/jobs/aws_iot_jobs_serialize.c | 4 +++ .../shadow/aws_iot_shadow_subscription.c | 29 +++++----------- 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 15399d936a..c3cec705ee 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -45,37 +45,37 @@ * @brief The longest Thing Name accepted by AWS IoT, per the [AWS IoT * Service Limits](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#limits_iot). */ -#define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) +#define AWS_IOT_MAX_THING_NAME_LENGTH ( 128 ) /** * @brief The common prefix of all AWS IoT MQTT topics. */ -#define AWS_IOT_TOPIC_PREFIX "$aws/things/" +#define AWS_IOT_TOPIC_PREFIX "$aws/things/" /** * @brief The length of #AWS_IOT_TOPIC_PREFIX. */ -#define AWS_IOT_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) ) +#define AWS_IOT_TOPIC_PREFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_TOPIC_PREFIX ) - 1 ) ) /** * @brief The suffix for an AWS IoT operation "accepted" topic. */ -#define AWS_IOT_ACCEPTED_SUFFIX "/accepted" +#define AWS_IOT_ACCEPTED_SUFFIX "/accepted" /** * @brief The length of #AWS_IOT_ACCEPTED_SUFFIX. */ -#define AWS_IOT_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_ACCEPTED_SUFFIX ) - 1 ) ) +#define AWS_IOT_ACCEPTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_ACCEPTED_SUFFIX ) - 1 ) ) /** * @brief The suffix for an AWS IoT operation "rejected" topic. */ -#define AWS_IOT_REJECTED_SUFFIX "/rejected" +#define AWS_IOT_REJECTED_SUFFIX "/rejected" /** * @brief The length of #AWS_IOT_REJECTED_SUFFIX. */ -#define AWS_IOT_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) ) +#define AWS_IOT_REJECTED_SUFFIX_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_REJECTED_SUFFIX ) - 1 ) ) /** * @brief The JSON key used to represent client tokens for AWS IoT. @@ -87,6 +87,16 @@ */ #define AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ( sizeof( AWS_IOT_CLIENT_TOKEN_KEY ) - 1 ) + +/** + * @brief A flag to represent persistent subscriptions in a subscriptions + * object. + * + * Its value is negative to distinguish it from valid subscription counts, which + * are 0 or positive. + */ +#define AWS_IOT_PERSISTENT_SUBSCRIPTION ( -1 ) + /** * @brief Function pointer representing an MQTT timed operation. * @@ -146,6 +156,15 @@ typedef struct AwsIotSubscriptionInfo_t uint16_t topicFilterBaseLength; /**< @brief Length of the base topic filter. */ } AwsIotSubscriptionInfo_t; +/** + * @brief Thing Name and length, used to match subscriptions. + */ +typedef struct AwsIotThingName +{ + const char * pThingName; /**< @brief Thing Name to compare. */ + size_t thingNameLength; /**< @brief Length of `pThingName`. */ +} AwsIotThingName_t; + /** * @brief Initializes the lists used by AWS IoT operations. * diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index e9f444ed74..ce6c377356 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -232,15 +232,6 @@ */ #define SHADOW_LONGEST_SUFFIX_LENGTH SHADOW_UPDATED_SUFFIX_LENGTH -/** - * @brief A flag to represent persistent subscriptions in a Shadow subscriptions - * object. - * - * Its value is negative to distinguish it from valid subscription counts, which - * are 0 or positive. - */ -#define PERSISTENT_SUBSCRIPTION ( -1 ) - /*----------------------- Shadow internal data types ------------------------*/ /** diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index ade70130a5..d9218ff158 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -154,6 +154,10 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo AwsIotJobs_Assert( pOperation->pClientToken < pOperation->pJobsRequest + pOperation->jobsRequestLength ); + IotLogDebug( "Jobs GET PENDING request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 30a7237bef..dfa2c523e8 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -45,23 +45,12 @@ /*-----------------------------------------------------------*/ -/** - * @brief First parameter to #_shadowSubscription_match. - */ -typedef struct _thingName -{ - const char * pThingName; /**< @brief Thing Name to compare. */ - size_t thingNameLength; /**< @brief Length of `pThingName`. */ -} _thingName_t; - -/*-----------------------------------------------------------*/ - /** * @brief Match two #_shadowSubscription_t by Thing Name. * * @param[in] pSubscriptionLink Pointer to the link member of a #_shadowSubscription_t * containing the Thing Name to check. - * @param[in] pMatch Pointer to a #_thingName_t. + * @param[in] pMatch Pointer to an `AwsIotThingName_t`. * * @return `true` if the Thing Names match; `false` otherwise. */ @@ -94,7 +83,7 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, const _shadowSubscription_t * pSubscription = IotLink_Container( _shadowSubscription_t, pSubscriptionLink, link ); - const _thingName_t * pThingName = ( _thingName_t * ) pMatch; + const AwsIotThingName_t * pThingName = ( AwsIotThingName_t * ) pMatch; if( pThingName->thingNameLength == pSubscription->thingNameLength ) { @@ -114,7 +103,7 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, { _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - _thingName_t thingName = + AwsIotThingName_t thingName = { .pThingName = pThingName, .thingNameLength = thingNameLength @@ -194,7 +183,7 @@ void _AwsIotShadow_RemoveSubscription( _shadowSubscription_t * pSubscription, removeSubscription = false; } - else if( pSubscription->references[ i ] == PERSISTENT_SUBSCRIPTION ) + else if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " "Subscription will not be removed.", @@ -282,7 +271,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; /* Do nothing if this operation has persistent subscriptions. */ - if( pSubscription->references[ type ] == PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Shadow %s for %.*s has persistent subscriptions. Reference " "count will not be incremented.", @@ -347,7 +336,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe /* Otherwise, set the persistent subscriptions flag. */ else { - pSubscription->references[ type ] = PERSISTENT_SUBSCRIPTION; + pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; IotLogDebug( "Set persistent subscriptions flag for Shadow %s of %.*s.", _pAwsIotShadowOperationNames[ type ], @@ -370,7 +359,7 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; /* Do nothing if this Shadow operation has persistent subscriptions. */ - if( pSubscription->references[ type ] != PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION ) { /* Decrement the number of subscription references for this operation. * Ensure that it's positive. */ @@ -434,7 +423,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - _thingName_t thingName = + AwsIotThingName_t thingName = { .pThingName = pThingName, .thingNameLength = thingNameLength @@ -474,7 +463,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio /* Subscription must have a topic buffer. */ AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); - if( pSubscription->references[ i ] == PERSISTENT_SUBSCRIPTION ) + if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { /* Generate the prefix of the Shadow topic. This function will not * fail when given a buffer. */ From 43239de0ff617dda89065e778e3c6d1ced3981e9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sun, 23 Jun 2019 11:02:38 -0400 Subject: [PATCH 197/844] Add Jobs process operation (#484) --- lib/include/private/aws_iot_jobs_internal.h | 135 ++++- lib/source/jobs/aws_iot_jobs_api.c | 46 +- lib/source/jobs/aws_iot_jobs_operation.c | 541 +++++++++++++++++++ lib/source/jobs/aws_iot_jobs_static_memory.c | 15 +- lib/source/jobs/aws_iot_jobs_subscription.c | 362 +++++++++++++ tests/jobs/unit/aws_iot_tests_jobs_api.c | 95 +++- tests/mqtt/mock/iot_tests_mqtt_mock.c | 15 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 7 - 8 files changed, 1181 insertions(+), 35 deletions(-) diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index c657c1da16..580158f439 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -197,30 +197,28 @@ /** * @brief The string representing a Jobs DESCRIBE operation in a Jobs MQTT topic. * - * The %s is a placeholder for a Job ID. + * This string should be placed after a Job ID. */ -#define JOBS_DESCRIBE_OPERATION_STRING "/jobs/%s/get" +#define JOBS_DESCRIBE_OPERATION_STRING "/get" /** * @brief The length of #JOBS_DESCRIBE_OPERATION_STRING. - * - * This length excludes the length of the placeholder %s. */ -#define JOBS_DESCRIBE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_DESCRIBE_OPERATION_STRING ) - 3 ) ) +#define JOBS_DESCRIBE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_DESCRIBE_OPERATION_STRING ) - 1 ) ) /** * @brief The string representing a Jobs UPDATE operation in a Jobs MQTT topic. * - * The %s is a placeholder for a Job ID. + * This string should be placed after a Job ID. */ -#define JOBS_UPDATE_OPERATION_STRING "/jobs/%s/update" +#define JOBS_UPDATE_OPERATION_STRING "/update" /** * @brief The length of #JOBS_UPDATE_OPERATION_STRING. * * This length excludes the length of the placeholder %s. */ -#define JOBS_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_UPDATE_OPERATION_STRING ) - 3 ) ) +#define JOBS_UPDATE_OPERATION_STRING_LENGTH ( ( uint16_t ) ( sizeof( JOBS_UPDATE_OPERATION_STRING ) - 1 ) ) /** * @brief The string representing the Jobs MQTT topic for receiving notifications @@ -254,10 +252,10 @@ /** * @brief The length of the longest Jobs topic suffix. * - * This is the length of the longest Job ID plus the length of the "UPDATE" - * operation suffix. + * This is the length of the longest Job ID, plus the length of the "UPDATE" + * operation suffix, plus the length of "/jobs/". */ -#define JOBS_LONGEST_SUFFIX_LENGTH ( JOBS_MAX_ID_LENGTH + JOBS_UPDATE_OPERATION_STRING_LENGTH ) +#define JOBS_LONGEST_SUFFIX_LENGTH ( JOBS_MAX_ID_LENGTH + JOBS_UPDATE_OPERATION_STRING_LENGTH + 6 ) /*------------------------ Jobs internal data types -------------------------*/ @@ -284,10 +282,10 @@ typedef enum _jobsOperationType */ typedef struct _jobsSubscription { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - int32_t references[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counter for Jobs operation topics. */ - AwsIotJobsCallbackInfo_t callback[ JOBS_CALLBACK_COUNT ]; /**< @brief Jobs callbacks for this Thing. */ + int32_t references[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counter for Jobs operation topics. */ + AwsIotJobsCallbackInfo_t callbacks[ JOBS_CALLBACK_COUNT ]; /**< @brief Jobs callbacks for this Thing. */ /** * @brief Buffer allocated for removing Jobs topics. @@ -342,11 +340,15 @@ typedef struct _jobsOperation IotSemaphore_t waitSemaphore; /**< @brief Semaphore to be used with @ref jobs_function_wait. */ AwsIotJobsCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */ } notify; /**< @brief How to notify of an operation's completion. */ + + size_t jobIdLength; /**< @brief Length of #_jobsOperation_t.pJobId. */ + char pJobId[]; /**< @brief Job ID, saved for DESCRIBE and UPDATE operations. */ } _jobsOperation_t; /* Declarations of names printed in logs. */ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE extern const char * const _pAwsIotJobsOperationNames[]; + extern const char * const _pAwsIotJobsCallbackNames[]; #endif /* Declarations of variables for internal Jobs files. */ @@ -387,6 +389,29 @@ AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, */ void _AwsIotJobs_DestroyOperation( void * pData ); +/** + * @brief Fill a buffer with a Jobs topic. + * + * @param[in] type One of: GET PENDING, START NEXT, DESCRIBE, or UPDATE. + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[out] pTopicBuffer Address of the buffer for the Jobs topic. If the + * pointer at this address is `NULL`, this function will allocate a new buffer; + * otherwise, it will use the provided buffer. + * @param[out] pOperationTopicLength Length of the Jobs operation topic (excluding + * any suffix) placed in `pTopicBuffer`. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must be large enough to accommodate the full Jobs topic, plus + * #JOBS_LONGEST_SUFFIX_LENGTH. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY. This function + * will not return #AWS_IOT_JOBS_NO_MEMORY when a buffer is provided. + */ +AwsIotJobsError_t _AwsIotJobs_GenerateJobsTopic( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + char ** pTopicBuffer, + uint16_t * pOperationTopicLength ); + /** * @brief Process a Jobs operation by sending the necessary MQTT packets. * @@ -401,6 +426,37 @@ AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * /*----------------------- Jobs subscription functions -----------------------*/ +/** + * @brief Find a Jobs subscription object. Creates a new subscription object + * and adds it to the subscription list if not found. + * + * @param[in] pThingName Thing Name in the subscription object. + * @param[in] thingNameLength Length of `pThingName`. + * + * @return Pointer to a Jobs subscription object, either found or newly + * allocated. Returns `NULL` if no subscription object is found and a new + * subscription object could not be allocated. + * + * @note This function should be called with the subscription list mutex locked. + */ +_jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, + size_t thingNameLength ); + +/** + * @brief Remove a Jobs subscription object from the subscription list if + * unreferenced. + * + * @param[in] pSubscription Subscription object to check. If this object has no + * active references, it is removed from the subscription list. + * @param[out] pRemovedSubscription Removed subscription object, if any. Optional; + * pass `NULL` to ignore. If not `NULL`, this parameter will be set to the removed + * subscription and that subscription will not be destroyed. + * + * @note This function should be called with the subscription list mutex locked. + */ +void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, + _jobsSubscription_t ** pRemovedSubscription ); + /** * @brief Free resources used for a Jobs subscription object. * @@ -410,7 +466,56 @@ AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * */ void _AwsIotJobs_DestroySubscription( void * pData ); -/*--------------------------- Jobs JSON functions ---------------------------*/ +/** + * @brief Increment the reference count of a Jobs subscriptions object. + * + * Also adds MQTT subscriptions if necessary. + * + * @param[in] pOperation The operation for which the reference count should be + * incremented. + * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if + * subscriptions need to be added. + * @param[in] operationTopicLength The length of the operation topic in `pTopicBuffer`. + * @param[in] callback MQTT callback function for when this operation completes. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must already contain the Jobs operation topic, plus enough space for the + * status suffix. + * + * @return #AWS_IOT_JOBS_SUCCESS on success. On error, one of + * #AWS_IOT_JOBS_NO_MEMORY or #AWS_IOT_JOBS_MQTT_ERROR. + * + * @note This function should be called with the subscription list mutex locked. + */ +AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation, + char * pTopicBuffer, + uint16_t operationTopicLength, + AwsIotMqttCallbackFunction_t callback ); + +/** + * @brief Decrement the reference count of a Jobs subscriptions object. + * + * Also removed MQTT subscriptions and deletes the subscription object if necessary. + * + * @param[in] pOperation The operation for which the reference count should be + * decremented. + * @param[in] pTopicBuffer Topic buffer containing the operation topic, used if + * subscriptions need to be removed. + * @param[out] pRemovedSubscription Set to point to a removed subscription. + * Optional; pass `NULL` to ignore. If not `NULL`, this function will not destroy + * a removed subscription. + * + * @warning This function does not check the length of `pTopicBuffer`! Any provided + * buffer must be large enough to accommodate the full Jobs topic, plus + * #JOBS_LONGEST_SUFFIX_LENGTH. + * + * @note This function should be called with the subscription list mutex locked. + */ +void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, + char * pTopicBuffer, + _jobsSubscription_t ** pRemovedSubscription ); + +/*------------------------ Jobs serializer functions ------------------------*/ /** * @brief Generates a Jobs JSON request document from an #AwsIotJobsRequestInfo_t diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index ed92ec646f..72855def2f 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -70,6 +70,18 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, */ uint32_t _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; +#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + +/** + * @brief Printable names for the Jobs callbacks. + */ + const char * const _pAwsIotJobsCallbackNames[] = + { + "NOTIFY PENDING", + "NOTIFY NEXT" + }; +#endif + /*-----------------------------------------------------------*/ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, @@ -188,6 +200,7 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques AwsIotJobsOperation_t * const pGetPendingOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + _jobsOperation_t * pOperation = NULL; /* Check Thing Name. */ status = _validateRequestInfo( JOBS_GET_PENDING, @@ -201,9 +214,38 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques IOT_GOTO_CLEANUP(); } - IOT_FUNCTION_CLEANUP_BEGIN(); + /* Allocate a new Jobs operation. */ + status = _AwsIotJobs_CreateOperation( JOBS_GET_PENDING, + pRequestInfo, + NULL, + flags, + pCallbackInfo, + &pOperation ); - IOT_FUNCTION_CLEANUP_END(); + if( status != AWS_IOT_JOBS_SUCCESS ) + { + /* No memory for Jobs operation. */ + IOT_GOTO_CLEANUP(); + } + + /* Set the reference if provided. This must be done before the Jobs operation + * is processed. */ + if( pGetPendingOperation != NULL ) + { + *pGetPendingOperation = pOperation; + } + + /* Process the Jobs operation. This subscribes to any required topics and + * sends the MQTT message for the Jobs operation. */ + status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); + + /* If the Jobs operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pGetPendingOperation != NULL ) ) + { + *pGetPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index d102bbcfa7..cec7d8fc1e 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -27,9 +27,21 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* Jobs internal include. */ #include "private/aws_iot_jobs_internal.h" +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -48,6 +60,67 @@ }; #endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ +/*-----------------------------------------------------------*/ + +/** + * @brief Invoked when a Jobs response is received for Jobs GET PENDING. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). + */ +static void _getPendingCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ); + +/** + * @brief Invoked when a Jobs response is received for a Jobs START NEXT. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). + */ +static void _startNextCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ); + +/** + * @brief Invoked when a Jobs response is received for Jobs DESCRIBE. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). + */ +static void _describeCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ); + +/** + * @brief Invoked when a Jobs response is received for a Jobs UPDATE. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). + */ +static void _updateCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ); + +/** + * @brief Get a Jobs subscription to use with a Jobs operation. + * + * This function may use an existing Jobs subscription, or it may allocate a + * new one. + * + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pTopicBuffer Contains the topic to use for subscribing. + * @param[in] operationTopicLength The length of the base topic in `pTopicBuffer`. + * @param[in] pOperation Jobs operation that needs a subscription. + * @param[out] pFreeTopicBuffer Whether the caller may free `pTopicBuffer` + * (which may be assigned to a subscription). + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY + */ +static AwsIotJobsError_t _findSubscription( const AwsIotJobsRequestInfo_t * pRequestInfo, + char * pTopicBuffer, + uint16_t operationTopicLength, + _jobsOperation_t * pOperation, + bool * pFreeTopicBuffer ); + +/*-----------------------------------------------------------*/ + /** * @brief List of active Jobs operations awaiting a response from the Jobs * service. @@ -61,11 +134,479 @@ IotMutex_t _AwsIotJobsPendingOperationsMutex; /*-----------------------------------------------------------*/ +static void _getPendingCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; +} + +/*-----------------------------------------------------------*/ + +static void _startNextCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; +} + +/*-----------------------------------------------------------*/ + +static void _describeCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; +} + +/*-----------------------------------------------------------*/ + +static void _updateCallback( void * pArgument, + IotMqttCallbackParam_t * pMessage ) +{ + /* Silence warnings about unused parameter. */ + ( void ) pArgument; +} + +/*-----------------------------------------------------------*/ + +static AwsIotJobsError_t _findSubscription( const AwsIotJobsRequestInfo_t * pRequestInfo, + char * pTopicBuffer, + uint16_t operationTopicLength, + _jobsOperation_t * pOperation, + bool * pFreeTopicBuffer ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; + _jobsSubscription_t * pSubscription = NULL; + + /* Lookup table for Jobs operation callbacks. */ + const AwsIotMqttCallbackFunction_t jobsCallbacks[ JOBS_OPERATION_COUNT ] = + { + _getPendingCallback, + _startNextCallback, + _describeCallback, + _updateCallback + }; + + /* Lock the subscriptions mutex for exclusive access. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + + /* Check for an existing subscription. This function will attempt to allocate + * a new subscription if not found. */ + pSubscription = _AwsIotJobs_FindSubscription( pRequestInfo->pThingName, + pRequestInfo->thingNameLength ); + + if( pSubscription == NULL ) + { + status = AWS_IOT_JOBS_NO_MEMORY; + } + else + { + /* Ensure that the subscription Thing Name matches. */ + AwsIotJobs_Assert( pSubscription != NULL ); + AwsIotJobs_Assert( pSubscription->thingNameLength == pRequestInfo->thingNameLength ); + AwsIotJobs_Assert( strncmp( pSubscription->pThingName, + pRequestInfo->pThingName, + pRequestInfo->thingNameLength ) == 0 ); + + /* Set the subscription object for the Jobs operation. */ + pOperation->pSubscription = pSubscription; + + /* Assign the topic buffer to the subscription to use for unsubscribing if + * the subscription has no topic buffer. */ + if( pSubscription->pTopicBuffer == NULL ) + { + pSubscription->pTopicBuffer = pTopicBuffer; + + /* Don't free the topic buffer if it was allocated to the subscription. */ + *pFreeTopicBuffer = false; + } + else + { + *pFreeTopicBuffer = true; + } + + /* Increment the reference count for this Jobs operation's + * subscriptions. */ + status = _AwsIotJobs_IncrementReferences( pOperation, + pTopicBuffer, + operationTopicLength, + jobsCallbacks[ pOperation->type ] ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + /* Failed to add subscriptions for a Jobs operation. The reference + * count was not incremented. Check if this subscription should be + * deleted. */ + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); + } + } + + /* Unlock the Jobs subscription list mutex. */ + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + _jobsOperation_t ** pNewOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + size_t operationSize = sizeof( _jobsOperation_t ); + _jobsOperation_t * pOperation = NULL; + + IotLogDebug( "Creating operation record for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + /* The Job ID must be saved for DESCRIBE and UPDATE operations. */ + if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) + { + /* Ensure a valid Job ID is provided. */ + AwsIotJobs_Assert( pRequestInfo->pJobId != NULL ); + AwsIotJobs_Assert( pRequestInfo->jobIdLength > 1 ); + AwsIotJobs_Assert( pRequestInfo->jobIdLength <= JOBS_MAX_ID_LENGTH ); + + operationSize += pRequestInfo->jobIdLength; + } + + /* Allocate memory for a new Jobs operation. */ + pOperation = AwsIotJobs_MallocOperation( operationSize ); + + if( pOperation == NULL ) + { + IotLogError( "Failed to allocate memory for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + /* Clear the operation data. */ + ( void ) memset( pOperation, 0x00, sizeof( _jobsOperation_t ) ); + + /* Set the remaining common members of the Jobs operation. */ + pOperation->type = type; + pOperation->flags = flags; + pOperation->status = AWS_IOT_JOBS_STATUS_PENDING; + + /* Save the Job ID for DESCRIBE and UPDATE operations. */ + if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) + { + pOperation->jobIdLength = pRequestInfo->jobIdLength; + + ( void ) memcpy( pOperation->pJobId, pRequestInfo->pJobId, pRequestInfo->jobIdLength ); + } + + /* Generate a Jobs request document. */ + status = _AwsIotJobs_GenerateJsonRequest( type, + pRequestInfo, + pUpdateInfo, + pOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Check if the waitable flag is set. If it is, create a semaphore to + * wait on. */ + if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) + { + if( IotSemaphore_Create( &( pOperation->notify.waitSemaphore ), 0, 1 ) == false ) + { + IotLogError( "Failed to create semaphore for waitable Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + } + else + { + /* If the waitable flag isn't set but a callback is, copy the callback + * information. */ + if( pCallbackInfo != NULL ) + { + pOperation->notify.callback = *pCallbackInfo; + } + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Clean up on error. */ + if( status != AWS_IOT_JOBS_SUCCESS ) + { + if( pOperation != NULL ) + { + if( pOperation->pJobsRequest != NULL ) + { + AwsIotJobs_FreeString( ( void * ) ( pOperation->pJobsRequest ) ); + } + + AwsIotJobs_FreeOperation( pOperation ); + } + } + else + { + /* Set the output parameter. */ + *pNewOperation = pOperation; + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + void _AwsIotJobs_DestroyOperation( void * pData ) { _jobsOperation_t * pOperation = ( _jobsOperation_t * ) pData; AwsIotJobs_Assert( pOperation != NULL ); + + IotLogDebug( "Destroying Jobs operation %s.", + _pAwsIotJobsOperationNames[ pOperation->type ] ); + + /* Check if a wait semaphore was created for this operation. */ + if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) + { + /* Destroy the wait semaphore */ + IotSemaphore_Destroy( &( pOperation->notify.waitSemaphore ) ); + } + + /* Free any Jobs request documents. */ + if( pOperation->pJobsRequest != NULL ) + { + AwsIotJobs_Assert( pOperation->jobsRequestLength > 0 ); + + AwsIotJobs_FreeString( ( void * ) ( pOperation->pJobsRequest ) ); + } + + /* Free the memory used to hold operation data. */ + AwsIotJobs_FreeOperation( pOperation ); +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t _AwsIotJobs_GenerateJobsTopic( _jobsOperationType_t type, + const AwsIotJobsRequestInfo_t * pRequestInfo, + char ** pTopicBuffer, + uint16_t * pOperationTopicLength ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; + AwsIotTopicInfo_t topicInfo = { 0 }; + char pJobOperationName[ JOBS_LONGEST_SUFFIX_LENGTH ] = { 0 }; + uint16_t operationNameLength = 0; + + /* Lookup table for Jobs operation strings. */ + const char * const pOperationString[ JOBS_OPERATION_COUNT ] = + { + JOBS_GET_PENDING_OPERATION_STRING, /* Jobs get pending operation. */ + JOBS_START_NEXT_OPERATION_STRING, /* Jobs start next operation. */ + JOBS_DESCRIBE_OPERATION_STRING, /* Jobs describe operation */ + JOBS_UPDATE_OPERATION_STRING /* Jobs update operation. */ + }; + + /* Lookup table for Jobs operation string lengths. */ + const uint16_t pOperationStringLength[ JOBS_OPERATION_COUNT ] = + { + JOBS_GET_PENDING_OPERATION_STRING_LENGTH, /* Jobs get pending operation */ + JOBS_START_NEXT_OPERATION_STRING_LENGTH, /* Jobs start next operation. */ + JOBS_DESCRIBE_OPERATION_STRING_LENGTH, /* Jobs describe operation */ + JOBS_UPDATE_OPERATION_STRING_LENGTH /* Jobs update operation. */ + }; + + /* Ensure type is valid. */ + AwsIotJobs_Assert( ( type == JOBS_GET_PENDING ) || ( type == JOBS_START_NEXT ) || + ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ); + + /* Set the members needed to generate an operation topic. */ + topicInfo.pThingName = pRequestInfo->pThingName; + topicInfo.thingNameLength = pRequestInfo->thingNameLength; + topicInfo.longestSuffixLength = JOBS_LONGEST_SUFFIX_LENGTH; + topicInfo.mallocString = AwsIotJobs_MallocString; + + /* Job operations that require a Job ID require additional processing to + * create an operation name with the Job ID. */ + if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) + { + /* Ensure Job ID length is valid. */ + AwsIotJobs_Assert( pRequestInfo->jobIdLength > 0 ); + AwsIotJobs_Assert( pRequestInfo->jobIdLength <= JOBS_MAX_ID_LENGTH ); + + /* Construct the Jobs operation name with the Job ID. */ + ( void ) memcpy( pJobOperationName, "/jobs/", 6 ); + operationNameLength = 5; + + ( void ) memcpy( pJobOperationName + operationNameLength, + pRequestInfo->pJobId, + pRequestInfo->jobIdLength ); + operationNameLength = ( uint16_t ) ( pRequestInfo->jobIdLength + operationNameLength ); + + ( void ) memcpy( pJobOperationName + operationNameLength, + pOperationString[ type ], + pOperationStringLength[ type ] ); + operationNameLength = ( uint16_t ) ( operationNameLength + pOperationStringLength[ type ] ); + + topicInfo.pOperationName = pJobOperationName; + topicInfo.operationNameLength = operationNameLength; + } + else + { + topicInfo.pOperationName = pOperationString[ type ]; + topicInfo.operationNameLength = pOperationStringLength[ type ]; + } + + if( AwsIot_GenerateOperationTopic( &topicInfo, + pTopicBuffer, + pOperationTopicLength ) == false ) + { + status = AWS_IOT_JOBS_NO_MEMORY; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * pRequestInfo, + _jobsOperation_t * pOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + char * pTopicBuffer = NULL; + uint16_t operationTopicLength = 0; + bool freeTopicBuffer = true; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttError_t publishStatus = IOT_MQTT_STATUS_PENDING; + + IotLogDebug( "Processing Jobs operation %s for Thing %.*s.", + _pAwsIotJobsOperationNames[ pOperation->type ], + pRequestInfo->thingNameLength, + pRequestInfo->pThingName ); + + /* Set the operation's MQTT connection. */ + pOperation->mqttConnection = pRequestInfo->mqttConnection; + + /* Generate the operation topic buffer. */ + status = _AwsIotJobs_GenerateJobsTopic( pOperation->type, + pRequestInfo, + &pTopicBuffer, + &operationTopicLength ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IotLogError( "No memory for Jobs operation topic buffer." ); + + IOT_GOTO_CLEANUP(); + } + + /* Get a subscription object for this Jobs operation. */ + status = _findSubscription( pRequestInfo, + pTopicBuffer, + operationTopicLength, + pOperation, + &freeTopicBuffer ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + /* No subscription was found and no subscription could be allocated. */ + IOT_GOTO_CLEANUP(); + } + + /* Jobs already has an acknowledgement mechanism, so sending the message at + * QoS 1 provides no benefit. */ + publishInfo.qos = IOT_MQTT_QOS_0; + + /* Set the payload as the Jobs request. */ + publishInfo.pPayload = pOperation->pJobsRequest; + publishInfo.payloadLength = pOperation->jobsRequestLength; + + /* Set the operation topic name. */ + publishInfo.pTopicName = pTopicBuffer; + publishInfo.topicNameLength = operationTopicLength; + + IotLogDebug( "Jobs %s message will be published to topic %.*s", + _pAwsIotJobsOperationNames[ pOperation->type ], + publishInfo.topicNameLength, + publishInfo.pTopicName ); + + /* Add Jobs operation to the pending operations list. */ + IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); + IotListDouble_InsertHead( &( _AwsIotJobsPendingOperations ), + &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); + + /* Publish to the Jobs topic name. */ + publishStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotJobsMqttTimeoutMs ); + + if( publishStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to publish MQTT message to %s %.*s Jobs, error %s.", + _pAwsIotJobsOperationNames[ pOperation->type ], + pRequestInfo->thingNameLength, + pRequestInfo->pThingName, + IotMqtt_strerror( publishStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Jobs "NO MEMORY" error. */ + if( publishStatus == IOT_MQTT_NO_MEMORY ) + { + status = AWS_IOT_JOBS_NO_MEMORY; + } + else + { + status = AWS_IOT_JOBS_MQTT_ERROR; + } + + /* If the "keep subscriptions" flag is not set, decrement the reference + * count. */ + if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + { + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + _AwsIotJobs_DecrementReferences( pOperation, + pTopicBuffer, + NULL ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + } + + /* Remove Jobs operation from the pending operations list. */ + IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); + IotListDouble_Remove( &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); + } + else + { + IotLogDebug( "Jobs %s PUBLISH message successfully sent.", + _pAwsIotJobsOperationNames[ pOperation->type ] ); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Free the topic buffer used by this function if it was not assigned to a + * subscription. */ + if( ( freeTopicBuffer == true ) && ( pTopicBuffer != NULL ) ) + { + AwsIotJobs_FreeString( pTopicBuffer ); + } + + /* Destroy the Jobs operation on failure. */ + if( status != AWS_IOT_JOBS_SUCCESS ) + { + _AwsIotJobs_DestroyOperation( pOperation ); + } + else + { + /* Convert successful return code to "status pending", as the Jobs + * library is now waiting for a response from the service. */ + status = AWS_IOT_JOBS_STATUS_PENDING; + } + + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_static_memory.c b/lib/source/jobs/aws_iot_jobs_static_memory.c index 195944f111..23851fcb2f 100644 --- a/lib/source/jobs/aws_iot_jobs_static_memory.c +++ b/lib/source/jobs/aws_iot_jobs_static_memory.c @@ -65,6 +65,15 @@ #error "AWS_IOT_JOBS_SUBSCRIPTIONS cannot be 0 or negative." #endif +/** + * @brief The size of a static memory Jobs operation. + * + * Since the pJobId member of #_jobsOperation_t is variable-length, + * the constant `JOBS_MAX_ID_LENGTH` is used for the length of + * #_jobsOperation_t.pJobId. + */ +#define JOBS_OPERATION_SIZE ( sizeof( _jobsOperation_t ) + JOBS_MAX_ID_LENGTH ) + /** * @brief The size of a static memory Jobs subscription. * @@ -80,7 +89,7 @@ * Static memory buffers and flags, allocated and zeroed at compile-time. */ static uint32_t _pInUseJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief Jobs operation in-use flags. */ -static _jobsOperation_t _pJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ] = { { .link = { 0 } } }; /**< @brief Jobs operations. */ +static _jobsOperation_t _pJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ][ JOBS_OPERATION_SIZE ] = { { 0 } }; /**< @brief Jobs operations. */ static uint32_t _pInUseJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ] = { 0U }; /**< @brief Jobs subscription in-use flags. */ static char _pJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ][ JOBS_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Jobs subscriptions. */ @@ -93,7 +102,7 @@ void * AwsIotJobs_MallocOperation( size_t size ) void * pNewOperation = NULL; /* Check size argument. */ - if( size == sizeof( _jobsOperation_t ) ) + if( size <= JOBS_OPERATION_SIZE ) { /* Find a free Jobs operation. */ freeIndex = IotStaticMemory_FindFree( _pInUseJobsOperations, @@ -117,7 +126,7 @@ void AwsIotJobs_FreeOperation( void * ptr ) _pJobsOperations, _pInUseJobsOperations, AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS, - sizeof( _jobsOperation_t ) ); + JOBS_OPERATION_SIZE ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index cf560580cb..0e367bc4de 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -28,9 +28,32 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* Jobs internal include. */ #include "private/aws_iot_jobs_internal.h" +/* Error handling include. */ +#include "private/iot_error.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Match two #_jobsSubscription_t by Thing Name. + * + * @param[in] pSubscriptionLink Pointer to the link member of a #_jobsSubscription_t + * containing the Thing Name to check. + * @param[in] pMatch Pointer to a `AwsIotThingName_t`. + * + * @return `true` if the Thing Names match; `false` otherwise. + */ +static bool _jobsSubscription_match( const IotLink_t * pSubscriptionLink, + void * pMatch ); + /*-----------------------------------------------------------*/ /** @@ -45,9 +68,348 @@ IotMutex_t _AwsIotJobsSubscriptionsMutex; /*-----------------------------------------------------------*/ +static bool _jobsSubscription_match( const IotLink_t * pSubscriptionLink, + void * pMatch ) +{ + bool match = false; + + /* Because this function is called from a container function, the given link + * must never be NULL. */ + AwsIotJobs_Assert( pSubscriptionLink != NULL ); + + const _jobsSubscription_t * pSubscription = IotLink_Container( _jobsSubscription_t, + pSubscriptionLink, + link ); + const AwsIotThingName_t * pThingName = ( AwsIotThingName_t * ) pMatch; + + if( pThingName->thingNameLength == pSubscription->thingNameLength ) + { + /* Check for matching Thing Names. */ + match = ( strncmp( pThingName->pThingName, + pSubscription->pThingName, + pThingName->thingNameLength ) == 0 ); + } + + return match; +} + +/*-----------------------------------------------------------*/ + +_jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, + size_t thingNameLength ) +{ + _jobsSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; + AwsIotThingName_t thingName = + { + .pThingName = pThingName, + .thingNameLength = thingNameLength + }; + + /* Search the list for an existing subscription for Thing Name. */ + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotJobsSubscriptions ), + NULL, + _jobsSubscription_match, + &thingName ); + + /* Check if a subscription was found. */ + if( pSubscriptionLink == NULL ) + { + /* No subscription found. Allocate a new subscription. */ + pSubscription = AwsIotJobs_MallocSubscription( sizeof( _jobsSubscription_t ) + thingNameLength ); + + if( pSubscription != NULL ) + { + /* Clear the new subscription. */ + ( void ) memset( pSubscription, 0x00, sizeof( _jobsSubscription_t ) + thingNameLength ); + + /* Set the Thing Name length and copy the Thing Name into the new subscription. */ + pSubscription->thingNameLength = thingNameLength; + ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); + + /* Add the new subscription to the subscription list. */ + IotListDouble_InsertHead( &( _AwsIotJobsSubscriptions ), + &( pSubscription->link ) ); + + IotLogDebug( "Created new Jobs subscriptions object for %.*s.", + thingNameLength, + pThingName ); + } + else + { + IotLogError( "Failed to allocate memory for %.*s Jobs subscriptions.", + thingNameLength, + pThingName ); + } + } + else + { + IotLogDebug( "Found existing Jobs subscriptions object for %.*s.", + thingNameLength, + pThingName ); + + pSubscription = IotLink_Container( _jobsSubscription_t, pSubscriptionLink, link ); + } + + return pSubscription; +} + +/*-----------------------------------------------------------*/ + +void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, + _jobsSubscription_t ** pRemovedSubscription ) +{ + int32_t i = 0; + bool removeSubscription = true; + + IotLogDebug( "Checking if subscription object for %.*s can be removed.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + /* Check for active operations. If any Jobs operation's subscription + * reference count is not 0, then the subscription cannot be removed. */ + for( i = 0; i < JOBS_OPERATION_COUNT; i++ ) + { + if( pSubscription->references[ i ] > 0 ) + { + IotLogDebug( "Reference count %ld for %.*s subscription object. " + "Subscription cannot be removed yet.", + ( long int ) pSubscription->references[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + removeSubscription = false; + } + else if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) + { + IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " + "Subscription will not be removed.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + removeSubscription = false; + } + + if( removeSubscription == false ) + { + break; + } + } + + /* Check for active subscriptions. If any Jobs callbacks are active, then the + * subscription cannot be removed. */ + if( removeSubscription == true ) + { + for( i = 0; i < JOBS_CALLBACK_COUNT; i++ ) + { + if( pSubscription->callbacks[ i ].function != NULL ) + { + IotLogDebug( "Found active Jobs %s callback for %.*s subscription object. " + "Subscription cannot be removed yet.", + _pAwsIotJobsCallbackNames[ i ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + removeSubscription = false; + break; + } + } + } + + /* Remove the subscription if unused. */ + if( removeSubscription == true ) + { + /* No Jobs operation subscription references or active Jobs callbacks. + * Remove the subscription object. */ + IotListDouble_Remove( &( pSubscription->link ) ); + + IotLogDebug( "Removed subscription object for %.*s.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + /* If the caller requested the removed subscription, set the output parameter. + * Otherwise, free the memory used by the subscription. */ + if( pRemovedSubscription != NULL ) + { + *pRemovedSubscription = pSubscription; + } + else + { + _AwsIotJobs_DestroySubscription( pSubscription ); + } + } +} + +/*-----------------------------------------------------------*/ + void _AwsIotJobs_DestroySubscription( void * pData ) { _jobsSubscription_t * pSubscription = ( _jobsSubscription_t * ) pData; + + /* Free the topic buffer. It should not be NULL. */ + AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); + AwsIotJobs_FreeString( pSubscription->pTopicBuffer ); + + /* Free memory used by subscription. */ + AwsIotJobs_FreeSubscription( pSubscription ); +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation, + char * pTopicBuffer, + uint16_t operationTopicLength, + AwsIotMqttCallbackFunction_t callback ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + const _jobsOperationType_t type = pOperation->type; + _jobsSubscription_t * pSubscription = pOperation->pSubscription; + IotMqttError_t subscriptionStatus = IOT_MQTT_STATUS_PENDING; + AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; + + /* Do nothing if this operation has persistent subscriptions. */ + if( pSubscription->references[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) + { + IotLogDebug( "Jobs %s for %.*s has persistent subscriptions. Reference " + "count will not be incremented.", + _pAwsIotJobsOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + + IOT_GOTO_CLEANUP(); + } + + /* When persistent subscriptions are not present, the reference count must + * not be negative. */ + AwsIotJobs_Assert( pSubscription->references[ type ] >= 0 ); + + /* Check if there are any existing references for this operation. */ + if( pSubscription->references[ type ] == 0 ) + { + /* Set the parameters needed to add subscriptions. */ + subscriptionInfo.mqttConnection = pOperation->mqttConnection; + subscriptionInfo.callbackFunction = callback; + subscriptionInfo.timeout = _AwsIotJobsMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; + + subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedSubscribe, + &subscriptionInfo ); + + /* Convert MQTT return code to Jobs return code. */ + switch( subscriptionStatus ) + { + case IOT_MQTT_SUCCESS: + status = AWS_IOT_JOBS_SUCCESS; + break; + + case IOT_MQTT_NO_MEMORY: + status = AWS_IOT_JOBS_NO_MEMORY; + break; + + default: + status = AWS_IOT_JOBS_MQTT_ERROR; + break; + } + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + } + + /* Increment the number of subscription references for this operation when + * the keep subscriptions flag is not set. */ + if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) + { + ( pSubscription->references[ type ] )++; + + IotLogDebug( "Jobs %s subscriptions for %.*s now has count %d.", + _pAwsIotJobsOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName, + pSubscription->references[ type ] ); + } + /* Otherwise, set the persistent subscriptions flag. */ + else + { + pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; + + IotLogDebug( "Set persistent subscriptions flag for Jobs %s of %.*s.", + _pAwsIotJobsOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, + char * pTopicBuffer, + _jobsSubscription_t ** pRemovedSubscription ) +{ + const _jobsOperationType_t type = pOperation->type; + _jobsSubscription_t * pSubscription = pOperation->pSubscription; + uint16_t operationTopicLength = 0; + AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + + /* Do nothing if this Jobs operation has persistent subscriptions. */ + if( pSubscription->references[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION ) + { + /* Decrement the number of subscription references for this operation. + * Ensure that it's positive. */ + ( pSubscription->references[ type ] )--; + AwsIotJobs_Assert( pSubscription->references[ type ] >= 0 ); + + /* Check if the number of references has reached 0. */ + if( pSubscription->references[ type ] == 0 ) + { + IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotJobsOperationNames[ type ] ); + + /* Subscription must have a topic buffer. */ + AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); + + /* Set the parameters needed to generate a Jobs topic. */ + requestInfo.pThingName = pSubscription->pThingName; + requestInfo.thingNameLength = pSubscription->thingNameLength; + requestInfo.pJobId = pOperation->pJobId; + requestInfo.jobIdLength = pOperation->jobIdLength; + + /* Generate the prefix of the Jobs topic. This function will not + * fail when given a buffer. */ + ( void ) _AwsIotJobs_GenerateJobsTopic( ( _jobsOperationType_t ) type, + &requestInfo, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); + + /* Set the parameters needed to remove subscriptions. */ + subscriptionInfo.mqttConnection = pOperation->mqttConnection; + subscriptionInfo.timeout = _AwsIotJobsMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; + + ( void ) AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + &subscriptionInfo ); + } + + /* Check if this subscription should be deleted. */ + _AwsIotJobs_RemoveSubscription( pSubscription, + pRemovedSubscription ); + } + else + { + IotLogDebug( "Jobs %s for %.*s has persistent subscriptions. Reference " + "count will not be decremented.", + _pAwsIotJobsOperationNames[ type ], + pSubscription->thingNameLength, + pSubscription->pThingName ); + } } /*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index efe950069c..eb303cc977 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -30,15 +30,35 @@ /* SDK initialization include. */ #include "iot_init.h" +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + /* MQTT include. */ #include "iot_mqtt.h" -/* Jobs internal include. */ -#include "private/aws_iot_jobs_internal.h" +/* MQTT mock include. */ +#include "iot_tests_mqtt_mock.h" /* Test framework includes. */ #include "unity_fixture.h" +/** + * @brief Whether to check the number of MQTT library errors in the malloc + * failure tests. + * + * Should only be checked if malloc overrides are available and not testing for + * code coverage. In static memory mode, there should be no MQTT library errors. + */ +#if ( IOT_TEST_COVERAGE == 1 ) || ( IOT_TEST_NO_MALLOC_OVERRIDES == 1 ) + #define CHECK_MQTT_ERROR_COUNT( expected, actual ) +#else + #if ( IOT_STATIC_MEMORY_ONLY == 1 ) + #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( 0, actual ) + #else + #define CHECK_MQTT_ERROR_COUNT( expected, actual ) TEST_ASSERT_EQUAL( expected, actual ) + #endif +#endif + /*-----------------------------------------------------------*/ /** @@ -53,6 +73,13 @@ /*-----------------------------------------------------------*/ +/** + * @brief The MQTT connection shared among the tests. + */ +static IotMqttConnection_t _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs API tests. */ @@ -73,6 +100,9 @@ TEST_SETUP( Jobs_Unit_API ) /* Initialize the Jobs library. */ TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); + + /* Initialize MQTT mock. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_pMqttConnection ) ); } /*-----------------------------------------------------------*/ @@ -82,6 +112,9 @@ TEST_SETUP( Jobs_Unit_API ) */ TEST_TEAR_DOWN( Jobs_Unit_API ) { + /* Clean up MQTT mock. */ + IotTest_MqttMockCleanup(); + /* Clean up libraries. */ AwsIotJobs_Cleanup(); IotMqtt_Cleanup(); @@ -97,6 +130,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) { RUN_TEST_CASE( Jobs_Unit_API, Init ); RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidParameters ); + RUN_TEST_CASE( Jobs_Unit_API, GetPendingMallocFail ); } /*-----------------------------------------------------------*/ @@ -203,3 +237,60 @@ TEST( Jobs_Unit_API, OperationInvalidParameters ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref jobs_function_getpending when memory + * allocation fails at various points. + */ +TEST( Jobs_Unit_API, GetPendingMallocFail ) +{ + int32_t i = 0, mqttErrorCount = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + + /* Set a short timeout so this test runs faster. */ + _AwsIotJobsMqttTimeoutMs = 75; + + /* Set MQTT connection. */ + requestInfo.mqttConnection = _pMqttConnection; + + /* Set the Thing Name and length. */ + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Jobs GET PENDING. Memory allocation will fail at various times + * during this call. */ + status = AwsIotJobs_GetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &getPendingOperation ); + + /* Once the Jobs GET PENDING call succeeds, wait for it to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + break; + } + + /* Count the number of MQTT library errors. Otherwise, check that the error + * is a "No memory" error. */ + if( status == AWS_IOT_JOBS_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); + } + } + + /* Allow 3 MQTT library errors, which are caused by failure to allocate memory + * for incoming packets (SUBSCRIBE, PUBLISH). */ + CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.c b/tests/mqtt/mock/iot_tests_mqtt_mock.c index 472f86532c..07394711b7 100644 --- a/tests/mqtt/mock/iot_tests_mqtt_mock.c +++ b/tests/mqtt/mock/iot_tests_mqtt_mock.c @@ -322,10 +322,6 @@ bool IotTest_MqttMockInit( IotMqttConnection_t * pMqttConnection ) /* Flags to track clean up */ bool packetMutexCreated = false, timerCreated = false; - /* Clear the last packet type and identifier. */ - _lastPacketType = 0; - _lastPacketIdentifier = 0; - /* Set the network interface send and receive functions. */ ( void ) memset( &_networkInterface, 0x00, sizeof( IotNetworkInterface_t ) ); _networkInterface.send = _sendSuccess; @@ -394,11 +390,18 @@ void IotTest_MqttMockCleanup( void ) /* Destroy the receive thread timer. */ IotClock_TimerDestroy( &_receiveTimer ); - /* Wait for the receive thread to finish and release the last packet mutex. */ + /* Wait a short time for the timer thread to finish. */ + IotClock_SleepMs( NETWORK_ROUND_TRIP_TIME_MS * 2 ); + + /* Clear the last packet type and identifier. */ IotMutex_Lock( &_lastPacketMutex ); - /* Destroy the last packet mutex. */ + _lastPacketType = 0; + _lastPacketIdentifier = 0; + IotMutex_Unlock( &_lastPacketMutex ); + + /* Destroy the last packet mutex. */ IotMutex_Destroy( &_lastPacketMutex ); } diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 5b4f6c4e72..b9fd6c6a1d 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -46,13 +46,6 @@ /* MQTT mock include. */ #include "iot_tests_mqtt_mock.h" -/* Require Shadow library asserts to be enabled for these tests. The Shadow - * assert function is used to abort the tests on failure from the MQTT send - * or receive threads. */ -#if AWS_IOT_SHADOW_ENABLE_ASSERTS == 0 - #error "Shadow API unit tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." -#endif - /** * @brief Whether to check the number of MQTT library errors in the malloc * failure tests. From 95a5efae0a26c535e1297b0c42c5003a8af58be3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sun, 23 Jun 2019 12:04:34 -0400 Subject: [PATCH 198/844] Add Jobs wait function (#485) --- lib/source/jobs/aws_iot_jobs_api.c | 117 ++++++++++++++++--- tests/jobs/unit/aws_iot_tests_jobs_api.c | 46 +++++++- tests/shadow/unit/aws_iot_tests_shadow_api.c | 8 +- 3 files changed, 144 insertions(+), 27 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 72855def2f..390bb48b8b 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -125,6 +125,14 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); } + if( pRequestInfo->mallocResponse == NULL ) + { + IotLogError( "Memory allocation function must be set for waitable Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + if( pCallbackInfo != NULL ) { IotLogError( "Callback should not be set for a waitable Jobs %s.", @@ -194,6 +202,34 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) /*-----------------------------------------------------------*/ +void AwsIotJobs_Cleanup( void ) +{ + /* Remove and free all items in the Jobs pending operation list. */ + IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); + IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, + _AwsIotJobs_DestroyOperation, + offsetof( _jobsOperation_t, link ) ); + IotMutex_Unlock( &_AwsIotJobsPendingOperationsMutex ); + + /* Remove and free all items in the Jobs subscription list. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + IotListDouble_RemoveAll( &_AwsIotJobsSubscriptions, + _AwsIotJobs_DestroySubscription, + offsetof( _jobsSubscription_t, link ) ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + + /* Restore the default MQTT timeout. */ + _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; + + /* Destroy Jobs library mutexes. */ + IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); + IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); + + IotLogInfo( "Jobs library cleanup done." ); +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, uint32_t flags, const AwsIotJobsCallbackInfo_t * pCallbackInfo, @@ -250,30 +286,77 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques /*-----------------------------------------------------------*/ -void AwsIotJobs_Cleanup( void ) +AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, + uint32_t timeoutMs, + const char ** const pResponse, + size_t * const pResponseLength ) { - /* Remove and free all items in the Jobs pending operation list. */ - IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); - IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, - _AwsIotJobs_DestroyOperation, - offsetof( _jobsOperation_t, link ) ); - IotMutex_Unlock( &_AwsIotJobsPendingOperationsMutex ); + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); - /* Remove and free all items in the Jobs subscription list. */ + /* Check that reference is set. */ + if( operation == NULL ) + { + IotLogError( "Operation reference cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* Check that reference is waitable. */ + if( ( operation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 ) + { + IotLogError( "Operation is not waitable." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* Check that output parameters are set. */ + if( ( pResponse == NULL ) || ( pResponseLength == NULL ) ) + { + IotLogError( "Output buffer and size pointer must be set for Jobs %s.", + _pAwsIotJobsOperationNames[ operation->type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* Wait for a response to the Jobs operation. */ + if( IotSemaphore_TimedWait( &( operation->notify.waitSemaphore ), + timeoutMs ) == true ) + { + status = operation->status; + } + else + { + status = AWS_IOT_JOBS_TIMEOUT; + } + + /* Remove the completed operation from the pending operation list. */ + IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); + IotListDouble_Remove( &( operation->link ) ); + IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); + + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - IotListDouble_RemoveAll( &_AwsIotJobsSubscriptions, - _AwsIotJobs_DestroySubscription, - offsetof( _jobsSubscription_t, link ) ); + _AwsIotJobs_DecrementReferences( operation, + operation->pSubscription->pTopicBuffer, + NULL ); IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - /* Restore the default MQTT timeout. */ - _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; + /* Set the output parameters. Jobs responses are available on success or + * when the Jobs service returns an error document. */ + if( ( status == AWS_IOT_JOBS_SUCCESS ) || ( status > AWS_IOT_JOBS_INVALID_TOPIC ) ) + { + AwsIotJobs_Assert( operation->pJobsResponse != NULL ); + AwsIotJobs_Assert( operation->jobsResponseLength > 0 ); - /* Destroy Jobs library mutexes. */ - IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); - IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); + *pResponse = operation->pJobsResponse; + *pResponseLength = operation->jobsResponseLength; + } - IotLogInfo( "Jobs library cleanup done." ); + /* Destroy the Jobs operation. */ + _AwsIotJobs_DestroyOperation( operation ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index eb303cc977..a86bb628ee 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -130,6 +130,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) { RUN_TEST_CASE( Jobs_Unit_API, Init ); RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidParameters ); + RUN_TEST_CASE( Jobs_Unit_API, WaitInvalidParameters ); RUN_TEST_CASE( Jobs_Unit_API, GetPendingMallocFail ); } @@ -219,6 +220,13 @@ TEST( Jobs_Unit_API, OperationInvalidParameters ) &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + /* Malloc function not set. */ + status = AwsIotJobs_GetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + /* Callback function not set. */ status = AwsIotJobs_GetPending( &requestInfo, 0, @@ -238,6 +246,30 @@ TEST( Jobs_Unit_API, OperationInvalidParameters ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of @ref jobs_function_wait with various + * invalid parameters. + */ +TEST( Jobs_Unit_API, WaitInvalidParameters ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + + /* NULL reference. */ + status = AwsIotJobs_Wait( NULL, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* No waitable flag set. */ + status = AwsIotJobs_Wait( &operation, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* NULL output parameters. */ + operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; + status = AwsIotJobs_Wait( &operation, 0, NULL, NULL ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref jobs_function_getpending when memory * allocation fails at various points. @@ -248,16 +280,17 @@ TEST( Jobs_Unit_API, GetPendingMallocFail ) AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + const char * pResponse = NULL; + size_t responseLength = 0; /* Set a short timeout so this test runs faster. */ _AwsIotJobsMqttTimeoutMs = 75; - /* Set MQTT connection. */ + /* Set the members of the request info. */ requestInfo.mqttConnection = _pMqttConnection; - - /* Set the Thing Name and length. */ requestInfo.pThingName = TEST_THING_NAME; requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + requestInfo.mallocResponse = IotTest_Malloc; for( i = 0; ; i++ ) { @@ -273,6 +306,13 @@ TEST( Jobs_Unit_API, GetPendingMallocFail ) /* Once the Jobs GET PENDING call succeeds, wait for it to complete. */ if( status == AWS_IOT_JOBS_STATUS_PENDING ) { + /* No response will be received from the network, so the Jobs operation + * is expected to time out. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, + AwsIotJobs_Wait( getPendingOperation, + 0, + &pResponse, + &responseLength ) ); break; } diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index b9fd6c6a1d..f5afa2a744 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -27,10 +27,6 @@ /* The config header is always included first. */ #include "iot_config.h" -/* Standard includes. */ -#include -#include - /* SDK initialization include. */ #include "iot_init.h" @@ -344,9 +340,7 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) TEST( Shadow_Unit_API, WaitInvalidParameters ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - _shadowOperation_t operation; - - ( void ) memset( &operation, 0x00, sizeof( _shadowOperation_t ) ); + _shadowOperation_t operation = { .link = { 0 } }; /* NULL reference. */ status = AwsIotShadow_Wait( NULL, 0, NULL, NULL ); From 31e911dbf6d2bda86c3cc7aaa53a4a5f21ffc708 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 12:03:29 -0400 Subject: [PATCH 199/844] Add Jobs update info validation (#486) --- lib/include/private/aws_iot_jobs_internal.h | 7 ++ lib/include/types/aws_iot_jobs_types.h | 2 +- lib/source/jobs/aws_iot_jobs_api.c | 112 +++++++++++++++++++- tests/jobs/unit/aws_iot_tests_jobs_api.c | 66 ++++++++++-- 4 files changed, 177 insertions(+), 10 deletions(-) diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 580158f439..d2bb0c403a 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -249,6 +249,13 @@ */ #define JOBS_MAX_ID_LENGTH ( 64 ) +/** + * @brief The maximum value of the Jobs step timeout, per AWS IoT Service Limits. + * + * See https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits + */ +#define JOBS_MAX_TIMEOUT ( 10080 ) + /** * @brief The length of the longest Jobs topic suffix. * diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index fbd5befeed..53ff613e41 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -805,7 +805,7 @@ typedef struct AwsIotJobsUpdateInfo .executionNumber = AWS_IOT_JOBS_NO_EXECUTION_NUMBER, \ .stepTimeoutInMinutes = AWS_IOT_JOBS_NO_TIMEOUT, \ .includeJobExecutionState = false, \ - .includeJobExecutionDocument = false, \ + .includeJobDocument = false, \ .pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS } #define AWS_IOT_JOBS_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ /* @[define_jobs_initializers] */ diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 390bb48b8b..edd41c5629 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -63,6 +63,17 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, const AwsIotJobsCallbackInfo_t * pCallbackInfo, const AwsIotJobsOperation_t * pOperation ); +/** + * @brief Validate the #AwsIotJobsUpdateInfo_t passed to a Jobs API function. + * + * @param[in] type The Jobs API function type. + * @param[in] pUpdateInfo The update info passed to a Jobs API function. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_BAD_PARAMETER. + */ +static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, + const AwsIotJobsUpdateInfo_t * pUpdateInfo ); + /*-----------------------------------------------------------*/ /** @@ -171,6 +182,70 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, /*-----------------------------------------------------------*/ +static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, + const AwsIotJobsUpdateInfo_t * pUpdateInfo ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + + /* Only START NEXT and UPDATE operations need an update info. */ + AwsIotJobs_Assert( ( type == JOBS_START_NEXT ) || ( type == JOBS_UPDATE ) ); + + /* Check that Job status to report is valid for Jobs UPDATE. */ + if( type == JOBS_UPDATE ) + { + switch( pUpdateInfo->newStatus ) + { + case AWS_IOT_JOB_STATE_IN_PROGRESS: + case AWS_IOT_JOB_STATE_FAILED: + case AWS_IOT_JOB_STATE_SUCCEEDED: + case AWS_IOT_JOB_STATE_REJECTED: + break; + + default: + IotLogError( "Job state %s is not valid for Jobs UPDATE.", + AwsIotJobs_StateName( pUpdateInfo->newStatus ) ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + /* Check that step timeout is valid. */ + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + if( pUpdateInfo->stepTimeoutInMinutes < 1 ) + { + IotLogError( "Step timeout for Jobs %s must be at least 1.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + else if( pUpdateInfo->stepTimeoutInMinutes > JOBS_MAX_TIMEOUT ) + { + IotLogError( "Step timeout for Jobs %s cannot exceed %d.", + _pAwsIotJobsOperationNames[ type ], + JOBS_MAX_TIMEOUT ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + /* Check status details. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + if( pUpdateInfo->statusDetailsLength == 0 ) + { + IotLogError( "Status details length not set for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) { AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; @@ -238,7 +313,7 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; - /* Check Thing Name. */ + /* Check request info. */ status = _validateRequestInfo( JOBS_GET_PENDING, pRequestInfo, flags, @@ -286,6 +361,41 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pStartNextOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + _jobsOperation_t * pOperation = NULL; + + /* Check request info. */ + status = _validateRequestInfo( JOBS_START_NEXT, + pRequestInfo, + flags, + pCallbackInfo, + pStartNextOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Check update info. */ + status = _validateUpdateInfo( JOBS_START_NEXT, + pUpdateInfo ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, uint32_t timeoutMs, const char ** const pResponse, diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index a86bb628ee..2bdee86a69 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -129,7 +129,8 @@ TEST_TEAR_DOWN( Jobs_Unit_API ) TEST_GROUP_RUNNER( Jobs_Unit_API ) { RUN_TEST_CASE( Jobs_Unit_API, Init ); - RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidParameters ); + RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidRequestInfo ); + RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidUpdateInfo ); RUN_TEST_CASE( Jobs_Unit_API, WaitInvalidParameters ); RUN_TEST_CASE( Jobs_Unit_API, GetPendingMallocFail ); } @@ -180,12 +181,13 @@ TEST( Jobs_Unit_API, Init ) /** * @brief Tests the behavior of Jobs operation functions with various - * invalid parameters. + * invalid #AwsIotJobsRequestInfo_t. */ -TEST( Jobs_Unit_API, OperationInvalidParameters ) +TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; @@ -195,7 +197,7 @@ TEST( Jobs_Unit_API, OperationInvalidParameters ) NULL, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - requestInfo.mqttConnection = ( IotMqttConnection_t ) 0x1; + requestInfo.mqttConnection = _pMqttConnection; /* Invalid Thing Name. */ status = AwsIotJobs_GetPending( &requestInfo, @@ -207,10 +209,11 @@ TEST( Jobs_Unit_API, OperationInvalidParameters ) requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; /* No reference with waitable operation. */ - status = AwsIotJobs_GetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - NULL ); + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Both callback and waitable flag set. */ @@ -246,6 +249,53 @@ TEST( Jobs_Unit_API, OperationInvalidParameters ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of Jobs operation functions with various + * invalid #AwsIotJobsUpdateInfo_t. + */ +TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + + /* Set the members of the request info. */ + requestInfo.mqttConnection = _pMqttConnection; + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + requestInfo.mallocResponse = IotTest_Malloc; + + /* Step timeout too small. */ + updateInfo.stepTimeoutInMinutes = 0; + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Step timeout too large. */ + updateInfo.stepTimeoutInMinutes = JOBS_MAX_TIMEOUT + 1; + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_NO_TIMEOUT; + + /* Status details length not set. */ + updateInfo.pStatusDetails = "test"; + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref jobs_function_wait with various * invalid parameters. From fbe8b9ef5288e438086ba0f66a6e8f66b7e95560 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 13:22:28 -0400 Subject: [PATCH 200/844] Add Jobs serialize test file (#487) --- lib/source/jobs/aws_iot_jobs_serialize.c | 108 +++++++++++--- tests/jobs/CMakeLists.txt | 3 +- tests/jobs/aws_iot_tests_jobs.c | 1 + .../jobs/unit/aws_iot_tests_jobs_serialize.c | 140 ++++++++++++++++++ 4 files changed, 229 insertions(+), 23 deletions(-) create mode 100644 tests/jobs/unit/aws_iot_tests_jobs_serialize.c diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index d9218ff158..a46b30b951 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -37,11 +37,52 @@ /* Error handling include. */ #include "private/iot_error.h" +/** + * @brief Minimum length of a Jobs request. + * + * At the very least, the request will contain: {"clientToken":""} + */ +#define MINIMUM_REQUEST_LENGTH ( AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7 ) + /** * @brief The length of client tokens generated by this library. */ #define CLIENT_TOKEN_AUTOGENERATE_LENGTH ( 8 ) +/** + * @brief JSON key representing Jobs status details. + */ +#define STATUS_DETAILS_KEY "statusDetails" + +/** + * @brief Length of #STATUS_DETAILS_KEY. + */ +#define STATUS_DETAILS_KEY_LENGTH ( sizeof( STATUS_DETAILS_KEY ) - 1 ) + +/** + * @brief JSON key representing Jobs step timeout. + */ +#define STEP_TIMEOUT_KEY "stepTimeoutInMinutes" + +/** + * @brief Length of #STEP_TIMEOUT_KEY. + */ +#define STEP_TIMEOUT_KEY_LENGTH ( sizeof( STEP_TIMEOUT_KEY ) - 1 ) + +/** + * @brief Append a string to a buffer. + * + * Also updates `copyOffset` with `stringLength`. + * + * @param[in] pBuffer Start of a buffer. + * @param[in] copyOffset Offset in `pBuffer` where `pString` will be placed. + * @param[in] pString The string to append. + * @param[in] stringLength Length of `pString`. + */ +#define APPEND_STRING( pBuffer, copyOffset, pString, stringLength ) \ + ( void ) memcpy( pBuffer + copyOffset, pString, stringLength ); \ + copyOffset += stringLength; + /*-----------------------------------------------------------*/ /** @@ -66,17 +107,37 @@ static void _generateClientToken( char * pBuffer ); static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, _jobsOperation_t * pOperation ); +/** + * @brief Generates a request JSON for a START NEXT operation. + * + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] pOperation Operation associated with the Jobs request. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY + */ +static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + _jobsOperation_t * pOperation ); + /*-----------------------------------------------------------*/ static void _generateClientToken( char * pBuffer ) { + int clientTokenLength = 0; + + /* Client token length is not used when asserts are disabled. */ + ( void ) clientTokenLength; + /* Take the address of the given buffer, truncated to 8 characters. This * provides a client token that is very likely to be unique while in use. */ uint32_t clientToken = ( uint32_t ) ( ( uint64_t ) pBuffer % 100000000ULL ); - ( void ) snprintf( pBuffer, - CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1, - "%08u", clientToken ); + clientTokenLength = snprintf( pBuffer, + CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1, + "%08u", clientToken ); + + AwsIotJobs_Assert( clientTokenLength == CLIENT_TOKEN_AUTOGENERATE_LENGTH ); } /*-----------------------------------------------------------*/ @@ -87,13 +148,13 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); char * pJobsRequest = NULL; size_t copyOffset = 0; - - /* At the very least, the request will contain: {"clientToken":""} */ - size_t requestLength = AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7; + size_t requestLength = MINIMUM_REQUEST_LENGTH; /* Add the length of the client token. */ if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) { + AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); + requestLength += pRequestInfo->clientTokenLength; } else @@ -113,16 +174,9 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo ( void ) memset( pJobsRequest, 0x00, requestLength ); /* Construct the request JSON, which consists of just a clientToken key. */ - ( void ) memcpy( pJobsRequest, "{\"", 2 ); - copyOffset = 2; - - ( void ) memcpy( pJobsRequest + copyOffset, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); - copyOffset += AWS_IOT_CLIENT_TOKEN_KEY_LENGTH; - - ( void ) memcpy( pJobsRequest + copyOffset, "\":\"", 3 ); - copyOffset += 3; + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + APPEND_STRING( pJobsRequest, copyOffset, AWS_IOT_CLIENT_TOKEN_KEY, AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) { @@ -133,15 +187,15 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo } else { - ( void ) memcpy( pJobsRequest + copyOffset, - pRequestInfo->pClientToken, - pRequestInfo->clientTokenLength ); - copyOffset += pRequestInfo->clientTokenLength; + APPEND_STRING( pJobsRequest, + copyOffset, + pRequestInfo->pClientToken, + pRequestInfo->clientTokenLength ); pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; } - ( void ) memcpy( pJobsRequest + copyOffset, "\"}", 2 ); + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); /* Set the output parameters. */ pOperation->pJobsRequest = pJobsRequest; @@ -149,7 +203,7 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo pOperation->pClientToken = pJobsRequest + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 4; /* Ensure offsets are valid. */ - AwsIotJobs_Assert( copyOffset + 2 == requestLength ); + AwsIotJobs_Assert( copyOffset == requestLength ); AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); AwsIotJobs_Assert( pOperation->pClientToken < pOperation->pJobsRequest + pOperation->jobsRequestLength ); @@ -163,6 +217,15 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo /*-----------------------------------------------------------*/ +static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + _jobsOperation_t * pOperation ) +{ + return AWS_IOT_JOBS_NO_MEMORY; +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, @@ -178,6 +241,7 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, break; case JOBS_START_NEXT: + status = _generateStartNextRequest( pRequestInfo, pUpdateInfo, pOperation ); break; case JOBS_DESCRIBE: diff --git a/tests/jobs/CMakeLists.txt b/tests/jobs/CMakeLists.txt index d0f3c27013..c95cdffb7b 100644 --- a/tests/jobs/CMakeLists.txt +++ b/tests/jobs/CMakeLists.txt @@ -1,6 +1,7 @@ # Jobs unit test sources. set( JOBS_UNIT_TEST_SOURCES - unit/aws_iot_tests_jobs_api.c ) + unit/aws_iot_tests_jobs_api.c + unit/aws_iot_tests_jobs_serialize.c ) # Jobs tests executable. add_executable( aws_iot_tests_jobs diff --git a/tests/jobs/aws_iot_tests_jobs.c b/tests/jobs/aws_iot_tests_jobs.c index 3b7b6e756d..4e62486999 100644 --- a/tests/jobs/aws_iot_tests_jobs.c +++ b/tests/jobs/aws_iot_tests_jobs.c @@ -40,6 +40,7 @@ void RunJobsTests( bool disableNetworkTests, ( void ) disableLongTests; RUN_TEST_GROUP( Jobs_Unit_API ); + RUN_TEST_GROUP( Jobs_Unit_Serialize ) } /*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c new file mode 100644 index 0000000000..b6ecbf080b --- /dev/null +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_jobs_serialize.c + * @brief Tests for the Jobs JSON functions. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* SDK initialization include. */ +#include "iot_init.h" + +/* Jobs internal include. */ +#include "private/aws_iot_jobs_internal.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/* JSON utilities include. */ +#include "iot_json_utils.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Jobs Serialize tests. + */ +TEST_GROUP( Jobs_Unit_Serialize ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Jobs Serialize tests. + */ +TEST_SETUP( Jobs_Unit_Serialize ) +{ + /* Initialize SDK. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + + /* Initialize the Jobs library. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Jobs Serialize tests. + */ +TEST_TEAR_DOWN( Jobs_Unit_Serialize ) +{ + /* Clean up libraries. */ + AwsIotJobs_Cleanup(); + IotSdk_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Jobs Serialize tests. + */ +TEST_GROUP_RUNNER( Jobs_Unit_Serialize ) +{ + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeGetPending ) +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of JSON documents for GET PENDING requests. + */ +TEST( Jobs_Unit_Serialize, SerializeGetPending ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + const char * pClientToken = NULL; + size_t clientTokenLength = 0; + + /* Generate request with autogenerated client token. */ + status = _AwsIotJobs_GenerateJsonRequest( JOBS_GET_PENDING, + &requestInfo, + NULL, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that a client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ) ); + TEST_ASSERT_EQUAL_PTR( pClientToken, operation.pClientToken ); + TEST_ASSERT_EQUAL( clientTokenLength, operation.clientTokenLength ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request with custom client token. */ + requestInfo.pClientToken = "test"; + requestInfo.clientTokenLength = 4; + status = _AwsIotJobs_GenerateJsonRequest( JOBS_GET_PENDING, + &requestInfo, + NULL, + &operation ); + + /* Check that the custom client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ) ); + TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", operation.pClientToken, 6 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ From b284ca834f9856308a29760b41ff72b477e28a37 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 15:25:26 -0400 Subject: [PATCH 201/844] Add Jobs START NEXT (#488) --- lib/source/jobs/aws_iot_jobs_api.c | 31 +++ lib/source/jobs/aws_iot_jobs_serialize.c | 129 +++++++++++- tests/jobs/unit/aws_iot_tests_jobs_api.c | 77 ++++++- .../jobs/unit/aws_iot_tests_jobs_serialize.c | 198 +++++++++++++++++- 4 files changed, 428 insertions(+), 7 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index edd41c5629..98bc188645 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -391,6 +391,37 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest IOT_GOTO_CLEANUP(); } + /* Allocate a new Jobs operation. */ + status = _AwsIotJobs_CreateOperation( JOBS_START_NEXT, + pRequestInfo, + pUpdateInfo, + flags, + pCallbackInfo, + &pOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + /* No memory for Jobs operation. */ + IOT_GOTO_CLEANUP(); + } + + /* Set the reference if provided. This must be done before the Jobs operation + * is processed. */ + if( pStartNextOperation != NULL ) + { + *pStartNextOperation = pOperation; + } + + /* Process the Jobs operation. This subscribes to any required topics and + * sends the MQTT message for the Jobs operation. */ + status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); + + /* If the Jobs operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pStartNextOperation != NULL ) ) + { + *pStartNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + } + IOT_FUNCTION_EXIT_NO_CLEANUP(); } diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index a46b30b951..f835a7d2f9 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -81,7 +81,7 @@ */ #define APPEND_STRING( pBuffer, copyOffset, pString, stringLength ) \ ( void ) memcpy( pBuffer + copyOffset, pString, stringLength ); \ - copyOffset += stringLength; + copyOffset += ( size_t ) stringLength; /*-----------------------------------------------------------*/ @@ -221,7 +221,132 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ const AwsIotJobsUpdateInfo_t * pUpdateInfo, _jobsOperation_t * pOperation ) { - return AWS_IOT_JOBS_NO_MEMORY; + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + char * pJobsRequest = NULL; + size_t copyOffset = 0; + size_t requestLength = MINIMUM_REQUEST_LENGTH; + char pStepTimeout[ 6 ] = { 0 }; + int stepTimeoutLength = 0; + + /* Add the length of status details if provided. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + /* Add 4 for the 2 quotes, colon, and comma. */ + requestLength += STATUS_DETAILS_KEY_LENGTH + 4; + requestLength += pUpdateInfo->statusDetailsLength; + } + + /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ + requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; + + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + /* Convert the step timeout to a string. */ + stepTimeoutLength = snprintf( pStepTimeout, 6, "%d", pUpdateInfo->stepTimeoutInMinutes ); + AwsIotJobs_Assert( stepTimeoutLength > 0 ); + AwsIotJobs_Assert( stepTimeoutLength < 6 ); + } + else + { + /* Step timeout will be set to -1. */ + pStepTimeout[ 0 ] = '-'; + pStepTimeout[ 1 ] = '1'; + stepTimeoutLength = 2; + } + + requestLength += ( size_t ) stepTimeoutLength; + + /* Add the length of the client token. */ + if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); + + requestLength += pRequestInfo->clientTokenLength; + } + else + { + requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + } + + /* Allocate memory for the request JSON. */ + pJobsRequest = AwsIotJobs_MallocString( requestLength ); + + if( pJobsRequest == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); + + /* Construct the request JSON. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + + /* Add status details if present. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + APPEND_STRING( pJobsRequest, copyOffset, STATUS_DETAILS_KEY, STATUS_DETAILS_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":", 2 ); + APPEND_STRING( pJobsRequest, + copyOffset, + pUpdateInfo->pStatusDetails, + pUpdateInfo->statusDetailsLength ); + APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); + } + + /* Add step timeout. */ + APPEND_STRING( pJobsRequest, + copyOffset, + STEP_TIMEOUT_KEY, + STEP_TIMEOUT_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":", 2 ); + APPEND_STRING( pJobsRequest, copyOffset, pStepTimeout, stepTimeoutLength ); + APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); + + /* Add client token. */ + APPEND_STRING( pJobsRequest, + copyOffset, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); + + /* Set the pointer to the client token. */ + pOperation->pClientToken = pJobsRequest + copyOffset - 1; + + if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + _generateClientToken( pJobsRequest + copyOffset ); + copyOffset += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + + pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2; + } + else + { + APPEND_STRING( pJobsRequest, + copyOffset, + pRequestInfo->pClientToken, + pRequestInfo->clientTokenLength ); + + pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; + } + + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); + + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; + + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); + + IotLogDebug( "Jobs START NEXT request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 2bdee86a69..56b4c25549 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -133,6 +133,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidUpdateInfo ); RUN_TEST_CASE( Jobs_Unit_API, WaitInvalidParameters ); RUN_TEST_CASE( Jobs_Unit_API, GetPendingMallocFail ); + RUN_TEST_CASE( Jobs_Unit_API, StartNextMallocFail ); } /*-----------------------------------------------------------*/ @@ -216,17 +217,18 @@ TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - /* Both callback and waitable flag set. */ + /* Malloc function not set. */ status = AwsIotJobs_GetPending( &requestInfo, AWS_IOT_JOBS_FLAG_WAITABLE, - &callbackInfo, + NULL, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.mallocResponse = IotTest_Malloc; - /* Malloc function not set. */ + /* Both callback and waitable flag set. */ status = AwsIotJobs_GetPending( &requestInfo, AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, + &callbackInfo, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); @@ -384,3 +386,70 @@ TEST( Jobs_Unit_API, GetPendingMallocFail ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref jobs_function_startnext when memory + * allocation fails at various points. + */ +TEST( Jobs_Unit_API, StartNextMallocFail ) +{ + int32_t i = 0, mqttErrorCount = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + const char * pResponse = NULL; + size_t responseLength = 0; + + /* Set a short timeout so this test runs faster. */ + _AwsIotJobsMqttTimeoutMs = 75; + + /* Set the members of the request info. */ + requestInfo.mqttConnection = _pMqttConnection; + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + requestInfo.mallocResponse = IotTest_Malloc; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Jobs START NEXT. Memory allocation will fail at various times + * during this call. */ + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &startNextOperation ); + + /* Once the Jobs START NEXT call succeeds, wait for it to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + /* No response will be received from the network, so the Jobs operation + * is expected to time out. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, + AwsIotJobs_Wait( startNextOperation, + 0, + &pResponse, + &responseLength ) ); + break; + } + + /* Count the number of MQTT library errors. Otherwise, check that the error + * is a "No memory" error. */ + if( status == AWS_IOT_JOBS_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); + } + } + + /* Allow 3 MQTT library errors, which are caused by failure to allocate memory + * for incoming packets (SUBSCRIBE, PUBLISH). */ + CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index b6ecbf080b..46a6c8ea3a 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -79,7 +79,10 @@ TEST_TEAR_DOWN( Jobs_Unit_Serialize ) */ TEST_GROUP_RUNNER( Jobs_Unit_Serialize ) { - RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeGetPending ) + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeGetPending ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextClientToken ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ); } /*-----------------------------------------------------------*/ @@ -130,6 +133,7 @@ TEST( Jobs_Unit_Serialize, SerializeGetPending ) AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, &pClientToken, &clientTokenLength ) ); + TEST_ASSERT_EQUAL_PTR( pClientToken, operation.pClientToken ); TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", operation.pClientToken, 6 ); @@ -138,3 +142,195 @@ TEST( Jobs_Unit_Serialize, SerializeGetPending ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of client token in JSON documents for START NEXT + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + /* Generate request with only autogenerated client token. */ + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &updateInfo, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that a client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); + TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); + + /* Check that the step token is -1. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 2, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request with custom client token. */ + requestInfo.pClientToken = "test"; + requestInfo.clientTokenLength = 4; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &updateInfo, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the custom client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); + TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of step timeout in JSON documents for START NEXT + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + /* Generate request with step timeout. */ + updateInfo.stepTimeoutInMinutes = 3600; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &updateInfo, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the step timeout is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 4, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "3600", pJsonValue, 4 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of status details in JSON documents for START NEXT + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + /* Generate request with status details. */ + updateInfo.pStatusDetails = "{\"status\":0}"; + updateInfo.statusDetailsLength = 12; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &updateInfo, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the status details are present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "statusDetails", + 13, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 12, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request with status details, step timeout, and custom client token. */ + updateInfo.stepTimeoutInMinutes = 10080; + requestInfo.pClientToken = "test"; + requestInfo.clientTokenLength = 4; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &updateInfo, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the status details are present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "statusDetails", + 13, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 12, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); + + /* Check that step timeout is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 5, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "10080", pJsonValue, 5 ); + + /* Check that the custom client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); + TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ From aebe8e5ce23d6cfd681521a303585d2f175ef5e0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 16:02:08 -0400 Subject: [PATCH 202/844] Modify parameters of Jobs serialize for DESCRIBE (#489) --- lib/include/private/aws_iot_jobs_internal.h | 24 +++++++++++++++---- lib/source/jobs/aws_iot_jobs_api.c | 5 +++- lib/source/jobs/aws_iot_jobs_operation.c | 4 ++-- lib/source/jobs/aws_iot_jobs_serialize.c | 6 +++-- .../jobs/unit/aws_iot_tests_jobs_serialize.c | 19 +++++++++++---- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index d2bb0c403a..a3f3c465e0 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -282,6 +282,20 @@ typedef enum _jobsOperationType SET_NOTIFY_NEXT_CALLBACK = 5 /**< @ref jobs_function_setnotifynextcallback */ } _jobsOperationType_t; +/** + * @brief Parameter to #_AwsIotJobs_GenerateJsonRequest. + */ +typedef union _jsonRequestContents +{ + const AwsIotJobsUpdateInfo_t * pUpdateInfo; /**< @brief Valid for #JOBS_START_NEXT and #JOBS_UPDATE. */ + + struct + { + int32_t executionNumber; /**< @brief Execution number. */ + bool includeJobDocument; /**< @brief Whether the response should include the Job document. */ + } describe; /**< @brief Valid for #JOBS_DESCRIBE. */ +} _jsonRequestContents_t; + /** * @brief Represents a Jobs subscriptions object. * @@ -372,7 +386,8 @@ extern IotMutex_t _AwsIotJobsSubscriptionsMutex; * * @param[in] type The type of Jobs operation for the request. * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] pRequestContents Additional values to place in tne JSON document, + * depending on `type`. * @param[in] flags Flags variables passed to a user-facing Jobs function. * @param[in] pCallbackInfo User-provided callback function and parameter. * @param[out] pNewOperation Set to point to the new operation on success. @@ -381,7 +396,7 @@ extern IotMutex_t _AwsIotJobsSubscriptionsMutex; */ AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const _jsonRequestContents_t * pRequestContents, uint32_t flags, const AwsIotJobsCallbackInfo_t * pCallbackInfo, _jobsOperation_t ** pNewOperation ); @@ -530,14 +545,15 @@ void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, * * @param[in] type The type of Jobs operation for the request. * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] pRequestContents Additional values to place in tne JSON document, + * depending on `type`. * @param[in] pOperation Operation associated with the Jobs request. * * @return #AWS_IOT_JOBS_SUCCESS on success; otherwise, #AWS_IOT_JOBS_NO_MEMORY. */ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const _jsonRequestContents_t * pRequestContents, _jobsOperation_t * pOperation ); #endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 98bc188645..a2a979b30f 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -369,6 +369,7 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; + _jsonRequestContents_t requestContents = { 0 }; /* Check request info. */ status = _validateRequestInfo( JOBS_START_NEXT, @@ -392,9 +393,11 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest } /* Allocate a new Jobs operation. */ + requestContents.pUpdateInfo = pUpdateInfo; + status = _AwsIotJobs_CreateOperation( JOBS_START_NEXT, pRequestInfo, - pUpdateInfo, + &requestContents, flags, pCallbackInfo, &pOperation ); diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index cec7d8fc1e..34422d14c2 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -252,7 +252,7 @@ static AwsIotJobsError_t _findSubscription( const AwsIotJobsRequestInfo_t * pReq AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const _jsonRequestContents_t * pRequestContents, uint32_t flags, const AwsIotJobsCallbackInfo_t * pCallbackInfo, _jobsOperation_t ** pNewOperation ) @@ -305,7 +305,7 @@ AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, /* Generate a Jobs request document. */ status = _AwsIotJobs_GenerateJsonRequest( type, pRequestInfo, - pUpdateInfo, + pRequestContents, pOperation ); if( status != AWS_IOT_JOBS_SUCCESS ) diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index f835a7d2f9..d38daf8af6 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -353,7 +353,7 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, + const _jsonRequestContents_t * pRequestContents, _jobsOperation_t * pOperation ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; @@ -366,7 +366,9 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, break; case JOBS_START_NEXT: - status = _generateStartNextRequest( pRequestInfo, pUpdateInfo, pOperation ); + status = _generateStartNextRequest( pRequestInfo, + pRequestContents->pUpdateInfo, + pOperation ); break; case JOBS_DESCRIBE: diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index 46a6c8ea3a..8f6250a9b2 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -153,13 +153,16 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) _jobsOperation_t operation = { .link = { 0 } }; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; const char * pJsonValue = NULL; size_t jsonValueLength = 0; + requestContents.pUpdateInfo = &updateInfo; + /* Generate request with only autogenerated client token. */ status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, &requestInfo, - &updateInfo, + &requestContents, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); @@ -192,7 +195,7 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, &requestInfo, - &updateInfo, + &requestContents, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); @@ -223,15 +226,18 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) _jobsOperation_t operation = { .link = { 0 } }; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; const char * pJsonValue = NULL; size_t jsonValueLength = 0; + requestContents.pUpdateInfo = &updateInfo; + /* Generate request with step timeout. */ updateInfo.stepTimeoutInMinutes = 3600; status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, &requestInfo, - &updateInfo, + &requestContents, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); @@ -261,16 +267,19 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) _jobsOperation_t operation = { .link = { 0 } }; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; const char * pJsonValue = NULL; size_t jsonValueLength = 0; + requestContents.pUpdateInfo = &updateInfo; + /* Generate request with status details. */ updateInfo.pStatusDetails = "{\"status\":0}"; updateInfo.statusDetailsLength = 12; status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, &requestInfo, - &updateInfo, + &requestContents, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); @@ -294,7 +303,7 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, &requestInfo, - &updateInfo, + &requestContents, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); From bfc723f471af054bcbcd3091231944cf1bb58c6a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 17:15:13 -0400 Subject: [PATCH 203/844] Add Jobs serialize DESCRIBE function (#490) --- lib/source/jobs/aws_iot_jobs_serialize.c | 251 ++++++++++++++---- .../jobs/unit/aws_iot_tests_jobs_serialize.c | 66 +++++ 2 files changed, 269 insertions(+), 48 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index d38daf8af6..24f61c2e49 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -69,6 +69,34 @@ */ #define STEP_TIMEOUT_KEY_LENGTH ( sizeof( STEP_TIMEOUT_KEY ) - 1 ) +/** + * @brief JSON key representing the "include Job document" flag. + */ +#define INCLUDE_JOB_DOCUMENT_KEY "includeJobDocument" + +/** + * @brief Length of #INCLUDE_JOB_DOCUMENT_KEY. + */ +#define INCLUDE_JOB_DOCUMENT_KEY_LENGTH ( sizeof( INCLUDE_JOB_DOCUMENT_KEY ) - 1 ) + +/** + * @brief JSON key representing the Jobs execution number. + */ +#define EXECUTION_NUMBER_KEY "executionNumber" + +/** + * @brief Length of #EXECUTION_NUMBER_KEY. + */ +#define EXECUTION_NUMBER_KEY_LENGTH ( sizeof( EXECUTION_NUMBER_KEY ) - 1 ) + +/** + * @brief Maximum length of the execution number when represented as a string. + * + * The execution number is a 32-bit integer. This can be represented in 10 digits, + * plus 1 for a possible negative sign, plus a NULL-terminator. + */ +#define EXECUTION_NUMBER_STRING_LENGTH ( 12 ) + /** * @brief Append a string to a buffer. * @@ -86,15 +114,23 @@ /*-----------------------------------------------------------*/ /** - * @brief Generate and place a client token in the given buffer. + * @brief Place a client token in the given buffer. * * @param[in] pBuffer The buffer where the client token is placed. + * @param[in] copyOffset Offset in `pBuffer` where client token is placed. + * @param[in] pRequestInfo Contains information on a client token to place. + * @param[out] pOperation Location and length of client token are written here. * * @warning This function does not check the length of `pBuffer`! Any provided * buffer must be large enough to accommodate #CLIENT_TOKEN_AUTOGENERATE_LENGTH * characters. + * + * @return A value of `copyOffset` after the client token. */ -static void _generateClientToken( char * pBuffer ); +static size_t _appendClientToken( char * pBuffer, + size_t copyOffset, + const AwsIotJobsRequestInfo_t * pRequestInfo, + _jobsOperation_t * pOperation ); /** * @brief Generates a request JSON for a GET PENDING operation. @@ -120,24 +156,66 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ const AwsIotJobsUpdateInfo_t * pUpdateInfo, _jobsOperation_t * pOperation ); +/** + * @brief Generates a request JSON for a DESCRIBE operation. + * + * @param[in] pRequestInfo Common jobs request parameters. + * @param[in] executionNumber Job execution number to include in request. + * @param[in] includeJobDocument Whether the response should include the Job document. + * @param[in] pOperation Operation associated with the Jobs request. + * + * @return #AWS_IOT_JOBS_SUCCESS or #AWS_IOT_JOBS_NO_MEMORY. + */ +static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + _jobsOperation_t * pOperation ); + /*-----------------------------------------------------------*/ -static void _generateClientToken( char * pBuffer ) +static size_t _appendClientToken( char * pBuffer, + size_t copyOffset, + const AwsIotJobsRequestInfo_t * pRequestInfo, + _jobsOperation_t * pOperation ) { int clientTokenLength = 0; + uint32_t clientToken = 0; - /* Client token length is not used when asserts are disabled. */ - ( void ) clientTokenLength; + /* Place the client token key in the buffer. */ + APPEND_STRING( pBuffer, + copyOffset, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); + APPEND_STRING( pBuffer, copyOffset, "\":\"", 3 ); - /* Take the address of the given buffer, truncated to 8 characters. This - * provides a client token that is very likely to be unique while in use. */ - uint32_t clientToken = ( uint32_t ) ( ( uint64_t ) pBuffer % 100000000ULL ); + /* Set the pointer to the client token. */ + pOperation->pClientToken = pBuffer + copyOffset - 1; + + if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + /* Take the address of the given buffer, truncated to 8 characters. This + * provides a client token that is very likely to be unique while in use. */ + clientToken = ( uint32_t ) ( ( uint64_t ) pBuffer % 100000000ULL ); + + clientTokenLength = snprintf( pBuffer + copyOffset, + CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1, + "%08u", clientToken ); + AwsIotJobs_Assert( clientTokenLength == CLIENT_TOKEN_AUTOGENERATE_LENGTH ); + + copyOffset += ( size_t ) clientTokenLength; + pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2; + } + else + { + APPEND_STRING( pBuffer, + copyOffset, + pRequestInfo->pClientToken, + pRequestInfo->clientTokenLength ); - clientTokenLength = snprintf( pBuffer, - CLIENT_TOKEN_AUTOGENERATE_LENGTH + 1, - "%08u", clientToken ); + pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; + } - AwsIotJobs_Assert( clientTokenLength == CLIENT_TOKEN_AUTOGENERATE_LENGTH ); + return copyOffset; } /*-----------------------------------------------------------*/ @@ -175,32 +253,12 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo /* Construct the request JSON, which consists of just a clientToken key. */ APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); - APPEND_STRING( pJobsRequest, copyOffset, AWS_IOT_CLIENT_TOKEN_KEY, AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); - - if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) - { - _generateClientToken( pJobsRequest + copyOffset ); - copyOffset += CLIENT_TOKEN_AUTOGENERATE_LENGTH; - - pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2; - } - else - { - APPEND_STRING( pJobsRequest, - copyOffset, - pRequestInfo->pClientToken, - pRequestInfo->clientTokenLength ); - - pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; - } - + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); /* Set the output parameters. */ pOperation->pJobsRequest = pJobsRequest; pOperation->jobsRequestLength = requestLength; - pOperation->pClientToken = pJobsRequest + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 4; /* Ensure offsets are valid. */ AwsIotJobs_Assert( copyOffset == requestLength ); @@ -304,32 +362,125 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); /* Add client token. */ - APPEND_STRING( pJobsRequest, - copyOffset, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); - /* Set the pointer to the client token. */ - pOperation->pClientToken = pJobsRequest + copyOffset - 1; + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); - if( pRequestInfo->pClientToken == AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; + + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); + + IotLogDebug( "Jobs START NEXT request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + _jobsOperation_t * pOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + char * pJobsRequest = NULL; + size_t copyOffset = 0; + size_t requestLength = MINIMUM_REQUEST_LENGTH; + char pExecutionNumber[ EXECUTION_NUMBER_STRING_LENGTH ] = { 0 }; + int executionNumberLength = 0; + + /* Add the "include job document" flag if false. The default value is true, + * so the flag is not needed if true. */ + if( includeJobDocument == false ) { - _generateClientToken( pJobsRequest + copyOffset ); - copyOffset += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + /* Add the length of "includeJobDocument" plus 4 for 2 quotes, a colon, + * and a comma. */ + requestLength += INCLUDE_JOB_DOCUMENT_KEY_LENGTH + 4; - pOperation->clientTokenLength = CLIENT_TOKEN_AUTOGENERATE_LENGTH + 2; + /* Add the length of "false". */ + requestLength += 5; + } + + /* Add the length of the execution number if present. */ + if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) + { + /* Convert the execution number to a string. */ + executionNumberLength = snprintf( pExecutionNumber, + EXECUTION_NUMBER_STRING_LENGTH, + "%d", + executionNumber ); + AwsIotJobs_Assert( executionNumberLength > 0 ); + AwsIotJobs_Assert( executionNumberLength < EXECUTION_NUMBER_STRING_LENGTH ); + + requestLength += EXECUTION_NUMBER_KEY_LENGTH + 4; + requestLength += ( size_t ) executionNumberLength; + } + + /* Add the length of the client token. */ + if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); + + requestLength += pRequestInfo->clientTokenLength; } else + { + requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + } + + /* Allocate memory for the request JSON. */ + pJobsRequest = AwsIotJobs_MallocString( requestLength ); + + if( pJobsRequest == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); + + /* Construct the request JSON. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + + /* Add the "include job document" flag if false. */ + if( includeJobDocument == false ) { APPEND_STRING( pJobsRequest, copyOffset, - pRequestInfo->pClientToken, - pRequestInfo->clientTokenLength ); + INCLUDE_JOB_DOCUMENT_KEY, + INCLUDE_JOB_DOCUMENT_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":false,\"", 9 ); + } - pOperation->clientTokenLength = pRequestInfo->clientTokenLength + 2; + /* Add the length of the execution number if present. */ + if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) + { + APPEND_STRING( pJobsRequest, + copyOffset, + EXECUTION_NUMBER_KEY, + EXECUTION_NUMBER_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, + copyOffset, + "\":", + 2 ); + APPEND_STRING( pJobsRequest, + copyOffset, + pExecutionNumber, + executionNumberLength ); + APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); } + /* Add client token. */ + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); /* Set the output parameters. */ @@ -342,7 +493,7 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ AwsIotJobs_Assert( pOperation->pClientToken < pOperation->pJobsRequest + pOperation->jobsRequestLength ); - IotLogDebug( "Jobs START NEXT request: %.*s", + IotLogDebug( "Jobs DESCRIBE request: %.*s", pOperation->jobsRequestLength, pOperation->pJobsRequest ); @@ -372,6 +523,10 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, break; case JOBS_DESCRIBE: + status = _generateDescribeRequest( pRequestInfo, + pRequestContents->describe.executionNumber, + pRequestContents->describe.includeJobDocument, + pOperation ); break; default: diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index 8f6250a9b2..730562999c 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -83,6 +83,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_Serialize ) RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextClientToken ); RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ); RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeDescribe ); } /*-----------------------------------------------------------*/ @@ -343,3 +344,68 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of status details in JSON documents for DESCRIBE + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeDescribe ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + /* Generate request with only autogenerated client token. */ + requestContents.describe.includeJobDocument = true; + requestContents.describe.executionNumber = AWS_IOT_JOBS_NO_EXECUTION_NUMBER; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_DESCRIBE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that a client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); + TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request with all parameters. */ + requestContents.describe.includeJobDocument = false; + requestContents.describe.executionNumber = 555555555; + requestInfo.pClientToken = "test"; + requestInfo.clientTokenLength = 4; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_DESCRIBE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the custom client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); + TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ From d567e236d95193adb69ff3ddd7ccf0e6ef7a5dc6 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 21:17:31 -0400 Subject: [PATCH 204/844] Add Jobs serialize UPDATE function (#491) --- lib/include/private/aws_iot.h | 4 + lib/include/private/aws_iot_jobs_internal.h | 11 + lib/source/jobs/aws_iot_jobs_api.c | 42 +- lib/source/jobs/aws_iot_jobs_serialize.c | 547 ++++++++++++++++-- .../jobs/unit/aws_iot_tests_jobs_serialize.c | 271 ++++++++- 5 files changed, 824 insertions(+), 51 deletions(-) diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index c3cec705ee..53b1d5aa82 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -87,6 +87,10 @@ */ #define AWS_IOT_CLIENT_TOKEN_KEY_LENGTH ( sizeof( AWS_IOT_CLIENT_TOKEN_KEY ) - 1 ) +/** + * @brief The length of the longest client token allowed by AWS IoT. + */ +#define AWS_IOT_CLIENT_TOKEN_MAX_LENGTH ( 64 ) /** * @brief A flag to represent persistent subscriptions in a subscriptions diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index a3f3c465e0..f6ec7ae300 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -256,6 +256,17 @@ */ #define JOBS_MAX_TIMEOUT ( 10080 ) +/** + * @brief A limit on the maximum length of a Jobs status details, per AWS IoT + * Service Limits. + * + * See https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits + * + * This is actually the limit on the length of an entire Jobs document; but the + * status details must also not exceed this length, + */ +#define JOBS_MAX_STATUS_DETAILS_LENGTH ( 32768 ) + /** * @brief The length of the longest Jobs topic suffix. * diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index a2a979b30f..a5ac0e5614 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -103,9 +103,6 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); - /* Type is not used when logging is disabled. */ - ( void ) type; - /* Check that the given MQTT connection is valid. */ if( pRequestInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) { @@ -165,7 +162,7 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, } } - /* Check that Thing Name length is set. */ + /* Check client token length. */ if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) { if( pRequestInfo->clientTokenLength == 0 ) @@ -175,6 +172,36 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); } + + if( pRequestInfo->clientTokenLength > AWS_IOT_CLIENT_TOKEN_MAX_LENGTH ) + { + IotLogError( "Client token for Jobs %s cannot be longer than %d.", + _pAwsIotJobsOperationNames[ type ], + AWS_IOT_CLIENT_TOKEN_MAX_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + /* Check Job ID for DESCRIBE and UPDATE. */ + if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) + { + if( ( pRequestInfo->pJobId == NULL ) || ( pRequestInfo->jobIdLength == 0 ) ) + { + IotLogError( "Job ID must be set for Jobs %s.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + if( pRequestInfo->jobIdLength > JOBS_MAX_ID_LENGTH ) + { + IotLogError( "Job ID for Jobs %s cannot be longer than %d.", + _pAwsIotJobsOperationNames[ type ], + JOBS_MAX_ID_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } } IOT_FUNCTION_EXIT_NO_CLEANUP(); @@ -239,6 +266,13 @@ static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); } + + if( pUpdateInfo->statusDetailsLength > JOBS_MAX_STATUS_DETAILS_LENGTH ) + { + IotLogError( "Status details length for Jobs %s cannot exceed %d.", + _pAwsIotJobsOperationNames[ type ], + JOBS_MAX_STATUS_DETAILS_LENGTH ); + } } IOT_FUNCTION_EXIT_NO_CLEANUP(); diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index 24f61c2e49..d013e9ca5f 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -42,52 +42,98 @@ * * At the very least, the request will contain: {"clientToken":""} */ -#define MINIMUM_REQUEST_LENGTH ( AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7 ) +#define MINIMUM_REQUEST_LENGTH ( AWS_IOT_CLIENT_TOKEN_KEY_LENGTH + 7 ) /** * @brief The length of client tokens generated by this library. */ -#define CLIENT_TOKEN_AUTOGENERATE_LENGTH ( 8 ) +#define CLIENT_TOKEN_AUTOGENERATE_LENGTH ( 8 ) + +/** + * @brief JSON key representing Jobs status. + */ +#define STATUS_KEY "status" + +/** + * @brief Length of #STATUS_KEY. + */ +#define STATUS_KEY_LENGTH ( sizeof( STATUS_KEY ) - 1 ) /** * @brief JSON key representing Jobs status details. */ -#define STATUS_DETAILS_KEY "statusDetails" +#define STATUS_DETAILS_KEY "statusDetails" /** * @brief Length of #STATUS_DETAILS_KEY. */ -#define STATUS_DETAILS_KEY_LENGTH ( sizeof( STATUS_DETAILS_KEY ) - 1 ) +#define STATUS_DETAILS_KEY_LENGTH ( sizeof( STATUS_DETAILS_KEY ) - 1 ) + +/** + * @brief JSON key representing Jobs expected version. + */ +#define EXPECTED_VERSION_KEY "expectedVersion" + +/** + * @brief Length of #EXPECTED_VERSION_KEY. + */ +#define EXPECTED_VERSION_KEY_LENGTH ( sizeof( EXPECTED_VERSION_KEY ) - 1 ) + +/** + * @brief Maximum length of the expected version when represented as a string. + * + * The expected version is a 32-bit unsigned integer. This can be represented in + * 10 digits plus a NULL-terminator. + */ +#define EXPECTED_VERSION_STRING_LENGTH ( 11 ) /** * @brief JSON key representing Jobs step timeout. */ -#define STEP_TIMEOUT_KEY "stepTimeoutInMinutes" +#define STEP_TIMEOUT_KEY "stepTimeoutInMinutes" /** * @brief Length of #STEP_TIMEOUT_KEY. */ -#define STEP_TIMEOUT_KEY_LENGTH ( sizeof( STEP_TIMEOUT_KEY ) - 1 ) +#define STEP_TIMEOUT_KEY_LENGTH ( sizeof( STEP_TIMEOUT_KEY ) - 1 ) + +/** + * @brief Maximum length of the step timeout when represented as a string. + * + * The step timeout is in the range of [-1,10080]. This can be represented as + * 5 digits plus a NULL-terminator. + */ +#define STEP_TIMEOUT_STRING_LENGTH ( 6 ) /** * @brief JSON key representing the "include Job document" flag. */ -#define INCLUDE_JOB_DOCUMENT_KEY "includeJobDocument" +#define INCLUDE_JOB_DOCUMENT_KEY "includeJobDocument" + +/** + * @brief JSON key representing the "include Job Execution state" flag. + */ +#define INCLUDE_JOB_EXECUTION_STATE_KEY "includeJobExecutionState" + +/** + * @brief Length of #INCLUDE_JOB_EXECUTION_STATE_KEY. + */ +#define INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH ( sizeof( INCLUDE_JOB_EXECUTION_STATE_KEY ) - 1 ) /** * @brief Length of #INCLUDE_JOB_DOCUMENT_KEY. */ -#define INCLUDE_JOB_DOCUMENT_KEY_LENGTH ( sizeof( INCLUDE_JOB_DOCUMENT_KEY ) - 1 ) +#define INCLUDE_JOB_DOCUMENT_KEY_LENGTH ( sizeof( INCLUDE_JOB_DOCUMENT_KEY ) - 1 ) /** * @brief JSON key representing the Jobs execution number. */ -#define EXECUTION_NUMBER_KEY "executionNumber" +#define EXECUTION_NUMBER_KEY "executionNumber" /** * @brief Length of #EXECUTION_NUMBER_KEY. */ -#define EXECUTION_NUMBER_KEY_LENGTH ( sizeof( EXECUTION_NUMBER_KEY ) - 1 ) +#define EXECUTION_NUMBER_KEY_LENGTH ( sizeof( EXECUTION_NUMBER_KEY ) - 1 ) /** * @brief Maximum length of the execution number when represented as a string. @@ -95,7 +141,7 @@ * The execution number is a 32-bit integer. This can be represented in 10 digits, * plus 1 for a possible negative sign, plus a NULL-terminator. */ -#define EXECUTION_NUMBER_STRING_LENGTH ( 12 ) +#define EXECUTION_NUMBER_STRING_LENGTH ( 12 ) /** * @brief Append a string to a buffer. @@ -113,6 +159,81 @@ /*-----------------------------------------------------------*/ +/** + * @brief Place a JSON boolean flag in the given buffer. + * + * @param[in] pBuffer The buffer where the flag is placed. + * @param[in] copyOffset Offset in `pBuffer` where the flag is placed. + * @param[in] pFlagName Either #INCLUDE_JOB_DOCUMENT_KEY or #INCLUDE_JOB_EXECUTION_STATE_KEY. + * @param[in] flagNameLength Either #INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH or + * #INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH + * @param[in] value Either `true` or `false`. + * + * @warning This function does not check the length of `pBuffer`! Any provided + * buffer must be large enough to accommodate the flag and value. + * + * @return A value of `copyOffset` after the flag. + */ +static size_t _appendFlag( char * pBuffer, + size_t copyOffset, + const char * pFlagName, + size_t flagNameLength, + bool value ); + +/** + * @brief Place Job status details in the given buffer. + * + * @param[in] pBuffer The buffer where the status details are placed. + * @param[in] copyOffset Offset in `pBuffer` where the status details are placed. + * @param[in] pStatusDetails The status details to place in the buffer. + * @param[in] statusDetailsLength Length of `pStatusDetails`. + * + * @warning This function does not check the length of `pBuffer`! Any provided + * buffer must be large enough to accommodate the status details. + * + * @return A value of `copyOffset` after the status details. + */ +static size_t _appendStatusDetails( char * pBuffer, + size_t copyOffset, + const char * pStatusDetails, + size_t statusDetailsLength ); + +/** + * @brief Place Job execution number in the given buffer. + * + * @param[in] pBuffer The buffer where the execution number is placed. + * @param[in] copyOffset Offset in `pBuffer` where the execution number is placed. + * @param[in] pExecutionNumber The execution number to place in the buffer. + * @param[in] executionNumberLength Length of `pExecutionNumber`. + * + * @warning This function does not check the length of `pBuffer`! Any provided + * buffer must be large enough to accommodate the execution number. + * + * @return A value of `copyOffset` after the execution number. + */ +static size_t _appendExecutionNumber( char * pBuffer, + size_t copyOffset, + const char * pExecutionNumber, + size_t executionNumberLength ); + +/** + * @brief Place Job step timeout in the given buffer. + * + * @param[in] pBuffer The buffer where the step timeout is placed. + * @param[in] copyOffset Offset in `pBuffer` where the step timeout is placed. + * @param[in] pStepTimeout The step timeout to place in the buffer. + * @param[in] stepTimeoutLength Length of `pStepTimeout`. + * + * @warning This function does not check the length of `pBuffer`! Any provided + * buffer must be large enough to accommodate the step timeout. + * + * @return A value of `copyOffset` after the step timeout. + */ +static size_t _appendStepTimeout( char * pBuffer, + size_t copyOffset, + const char * pStepTimeout, + size_t stepTimeoutLength ); + /** * @brief Place a client token in the given buffer. * @@ -171,6 +292,105 @@ static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t bool includeJobDocument, _jobsOperation_t * pOperation ); +/** + * @brief Generates a request JSON for an UPDATE operation. + * + * @param[in] pRequestInfo Common Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] pOperation Operation associated with the Jobs request. + */ +static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + _jobsOperation_t * pOperation ); + +/*-----------------------------------------------------------*/ + +static size_t _appendFlag( char * pBuffer, + size_t copyOffset, + const char * pFlagName, + size_t flagNameLength, + bool value ) +{ + if( value == true ) + { + APPEND_STRING( pBuffer, + copyOffset, + pFlagName, + flagNameLength ); + APPEND_STRING( pBuffer, copyOffset, "\":true,\"", 8 ); + } + else + { + APPEND_STRING( pBuffer, + copyOffset, + pFlagName, + flagNameLength ); + APPEND_STRING( pBuffer, copyOffset, "\":false,\"", 9 ); + } + + return copyOffset; +} + +/*-----------------------------------------------------------*/ + +static size_t _appendStatusDetails( char * pBuffer, + size_t copyOffset, + const char * pStatusDetails, + size_t statusDetailsLength ) +{ + APPEND_STRING( pBuffer, copyOffset, STATUS_DETAILS_KEY, STATUS_DETAILS_KEY_LENGTH ); + APPEND_STRING( pBuffer, copyOffset, "\":", 2 ); + APPEND_STRING( pBuffer, + copyOffset, + pStatusDetails, + statusDetailsLength ); + APPEND_STRING( pBuffer, copyOffset, ",\"", 2 ); + + return copyOffset; +} + +/*-----------------------------------------------------------*/ + +static size_t _appendExecutionNumber( char * pBuffer, + size_t copyOffset, + const char * pExecutionNumber, + size_t executionNumberLength ) +{ + APPEND_STRING( pBuffer, + copyOffset, + EXECUTION_NUMBER_KEY, + EXECUTION_NUMBER_KEY_LENGTH ); + APPEND_STRING( pBuffer, + copyOffset, + "\":", + 2 ); + APPEND_STRING( pBuffer, + copyOffset, + pExecutionNumber, + executionNumberLength ); + APPEND_STRING( pBuffer, copyOffset, ",\"", 2 ); + + return copyOffset; +} + +/*-----------------------------------------------------------*/ + +static size_t _appendStepTimeout( char * pBuffer, + size_t copyOffset, + const char * pStepTimeout, + size_t stepTimeoutLength ) +{ + APPEND_STRING( pBuffer, + copyOffset, + STEP_TIMEOUT_KEY, + STEP_TIMEOUT_KEY_LENGTH ); + APPEND_STRING( pBuffer, copyOffset, "\":", 2 ); + APPEND_STRING( pBuffer, copyOffset, pStepTimeout, stepTimeoutLength ); + APPEND_STRING( pBuffer, copyOffset, ",\"", 2 ); + + return copyOffset; +} + /*-----------------------------------------------------------*/ static size_t _appendClientToken( char * pBuffer, @@ -245,6 +465,8 @@ static AwsIotJobsError_t _generateGetPendingRequest( const AwsIotJobsRequestInfo if( pJobsRequest == NULL ) { + IotLogError( "No memory for Jobs GET PENDING request." ); + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); } @@ -283,7 +505,7 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ char * pJobsRequest = NULL; size_t copyOffset = 0; size_t requestLength = MINIMUM_REQUEST_LENGTH; - char pStepTimeout[ 6 ] = { 0 }; + char pStepTimeout[ STEP_TIMEOUT_STRING_LENGTH ] = { 0 }; int stepTimeoutLength = 0; /* Add the length of status details if provided. */ @@ -300,9 +522,12 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) { /* Convert the step timeout to a string. */ - stepTimeoutLength = snprintf( pStepTimeout, 6, "%d", pUpdateInfo->stepTimeoutInMinutes ); + stepTimeoutLength = snprintf( pStepTimeout, + STEP_TIMEOUT_STRING_LENGTH, + "%d", + pUpdateInfo->stepTimeoutInMinutes ); AwsIotJobs_Assert( stepTimeoutLength > 0 ); - AwsIotJobs_Assert( stepTimeoutLength < 6 ); + AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); } else { @@ -331,6 +556,8 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ if( pJobsRequest == NULL ) { + IotLogError( "No memory for Jobs START NEXT request." ); + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); } @@ -343,23 +570,17 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ /* Add status details if present. */ if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) { - APPEND_STRING( pJobsRequest, copyOffset, STATUS_DETAILS_KEY, STATUS_DETAILS_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":", 2 ); - APPEND_STRING( pJobsRequest, - copyOffset, - pUpdateInfo->pStatusDetails, - pUpdateInfo->statusDetailsLength ); - APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); + copyOffset = _appendStatusDetails( pJobsRequest, + copyOffset, + pUpdateInfo->pStatusDetails, + pUpdateInfo->statusDetailsLength ); } /* Add step timeout. */ - APPEND_STRING( pJobsRequest, - copyOffset, - STEP_TIMEOUT_KEY, - STEP_TIMEOUT_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":", 2 ); - APPEND_STRING( pJobsRequest, copyOffset, pStepTimeout, stepTimeoutLength ); - APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); + copyOffset = _appendStepTimeout( pJobsRequest, + copyOffset, + pStepTimeout, + stepTimeoutLength ); /* Add client token. */ copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); @@ -441,6 +662,8 @@ static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t if( pJobsRequest == NULL ) { + IotLogError( "No memory for Jobs DESCRIBE request." ); + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); } @@ -453,29 +676,20 @@ static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t /* Add the "include job document" flag if false. */ if( includeJobDocument == false ) { - APPEND_STRING( pJobsRequest, - copyOffset, - INCLUDE_JOB_DOCUMENT_KEY, - INCLUDE_JOB_DOCUMENT_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, copyOffset, "\":false,\"", 9 ); + copyOffset = _appendFlag( pJobsRequest, + copyOffset, + INCLUDE_JOB_DOCUMENT_KEY, + INCLUDE_JOB_DOCUMENT_KEY_LENGTH, + false ); } /* Add the length of the execution number if present. */ if( executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) { - APPEND_STRING( pJobsRequest, - copyOffset, - EXECUTION_NUMBER_KEY, - EXECUTION_NUMBER_KEY_LENGTH ); - APPEND_STRING( pJobsRequest, - copyOffset, - "\":", - 2 ); - APPEND_STRING( pJobsRequest, - copyOffset, - pExecutionNumber, - executionNumberLength ); - APPEND_STRING( pJobsRequest, copyOffset, ",\"", 2 ); + copyOffset = _appendExecutionNumber( pJobsRequest, + copyOffset, + pExecutionNumber, + ( size_t ) executionNumberLength ); } /* Add client token. */ @@ -502,6 +716,243 @@ static AwsIotJobsError_t _generateDescribeRequest( const AwsIotJobsRequestInfo_t /*-----------------------------------------------------------*/ +static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + _jobsOperation_t * pOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + char * pJobsRequest = NULL; + size_t copyOffset = 0; + size_t requestLength = MINIMUM_REQUEST_LENGTH; + const char * pStatus = NULL; + size_t statusLength = 0; + char pExpectedVersion[ EXPECTED_VERSION_STRING_LENGTH ] = { 0 }; + char pExecutionNumber[ EXECUTION_NUMBER_STRING_LENGTH ] = { 0 }; + char pStepTimeout[ STEP_TIMEOUT_STRING_LENGTH ] = { 0 }; + int expectedVersionLength = 0, executionNumberLength = 0, stepTimeoutLength = 0; + + /* Determine the status string and length to report to the Jobs service. + * Add 6 for the 4 quotes, colon, and comma. */ + requestLength += STATUS_KEY_LENGTH + 6; + + switch( pUpdateInfo->newStatus ) + { + case AWS_IOT_JOB_STATE_IN_PROGRESS: + pStatus = "IN_PROGRESS"; + break; + + case AWS_IOT_JOB_STATE_FAILED: + pStatus = "FAILED"; + break; + + case AWS_IOT_JOB_STATE_SUCCEEDED: + pStatus = "SUCCEEDED"; + break; + + default: + /* The only remaining valid state is REJECTED. */ + AwsIotJobs_Assert( pUpdateInfo->newStatus == AWS_IOT_JOB_STATE_REJECTED ); + pStatus = "REJECTED"; + break; + } + + statusLength = strlen( pStatus ); + requestLength += statusLength; + + /* Add the length of status details if provided. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + /* Add 4 for the 2 quotes, colon, and comma. */ + requestLength += STATUS_DETAILS_KEY_LENGTH + 4; + requestLength += pUpdateInfo->statusDetailsLength; + } + + /* Add the expected version if provided. */ + if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION ) + { + /* Convert the expected version to a string. */ + expectedVersionLength = snprintf( pExpectedVersion, + EXPECTED_VERSION_STRING_LENGTH, + "%u", + pUpdateInfo->expectedVersion ); + AwsIotJobs_Assert( expectedVersionLength > 0 ); + AwsIotJobs_Assert( expectedVersionLength < EXPECTED_VERSION_STRING_LENGTH ); + + /* Add 6 for the 4 quotes, colon, and comma. */ + requestLength += EXPECTED_VERSION_KEY_LENGTH + 6; + requestLength += ( size_t ) expectedVersionLength; + } + + /* Add the length of the execution number if present. */ + if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) + { + /* Convert the execution number to a string. */ + executionNumberLength = snprintf( pExecutionNumber, + EXECUTION_NUMBER_STRING_LENGTH, + "%d", + pUpdateInfo->executionNumber ); + AwsIotJobs_Assert( executionNumberLength > 0 ); + AwsIotJobs_Assert( executionNumberLength < EXECUTION_NUMBER_STRING_LENGTH ); + + requestLength += EXECUTION_NUMBER_KEY_LENGTH + 4; + requestLength += ( size_t ) executionNumberLength; + } + + /* Add the flags if true. The default values are false, so the flags are not + * needed if false. */ + if( pUpdateInfo->includeJobExecutionState == true ) + { + /* Add the length of "includeJobExecutionState" plus 4 for 2 quotes, a colon, + * and a comma. */ + requestLength += INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH + 4; + + /* Add the length of "true". */ + requestLength += 4; + } + + if( pUpdateInfo->includeJobDocument == true ) + { + /* Add the length of "includeJobDocument" plus 4 for 2 quotes, a colon, + * and a comma. */ + requestLength += INCLUDE_JOB_DOCUMENT_KEY_LENGTH + 4; + + /* Add the length of "true". */ + requestLength += 4; + } + + /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ + requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; + + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + /* Convert the step timeout to a string. */ + stepTimeoutLength = snprintf( pStepTimeout, + STEP_TIMEOUT_STRING_LENGTH, + "%d", + pUpdateInfo->stepTimeoutInMinutes ); + AwsIotJobs_Assert( stepTimeoutLength > 0 ); + AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); + } + else + { + /* Step timeout will be set to -1. */ + pStepTimeout[ 0 ] = '-'; + pStepTimeout[ 1 ] = '1'; + stepTimeoutLength = 2; + } + + requestLength += ( size_t ) stepTimeoutLength; + + /* Add the length of the client token. */ + if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) + { + AwsIotJobs_Assert( pRequestInfo->clientTokenLength > 0 ); + + requestLength += pRequestInfo->clientTokenLength; + } + else + { + requestLength += CLIENT_TOKEN_AUTOGENERATE_LENGTH; + } + + /* Allocate memory for the request JSON. */ + pJobsRequest = AwsIotJobs_MallocString( requestLength ); + + if( pJobsRequest == NULL ) + { + IotLogError( "No memory for Jobs UPDATE request." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + /* Clear the request JSON. */ + ( void ) memset( pJobsRequest, 0x00, requestLength ); + + /* Construct the request JSON. */ + APPEND_STRING( pJobsRequest, copyOffset, "{\"", 2 ); + + /* Add the status. */ + APPEND_STRING( pJobsRequest, copyOffset, STATUS_KEY, STATUS_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); + APPEND_STRING( pJobsRequest, copyOffset, pStatus, statusLength ); + APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); + + /* Add status details if present. */ + if( pUpdateInfo->pStatusDetails != AWS_IOT_JOBS_NO_STATUS_DETAILS ) + { + copyOffset = _appendStatusDetails( pJobsRequest, + copyOffset, + pUpdateInfo->pStatusDetails, + pUpdateInfo->statusDetailsLength ); + } + + /* Add expected version. */ + if( pUpdateInfo->expectedVersion != AWS_IOT_JOBS_NO_VERSION ) + { + APPEND_STRING( pJobsRequest, + copyOffset, + EXPECTED_VERSION_KEY, + EXPECTED_VERSION_KEY_LENGTH ); + APPEND_STRING( pJobsRequest, copyOffset, "\":\"", 3 ); + APPEND_STRING( pJobsRequest, copyOffset, pExpectedVersion, expectedVersionLength ); + APPEND_STRING( pJobsRequest, copyOffset, "\",\"", 3 ); + } + + /* Add execution number. */ + if( pUpdateInfo->executionNumber != AWS_IOT_JOBS_NO_EXECUTION_NUMBER ) + { + copyOffset = _appendExecutionNumber( pJobsRequest, + copyOffset, + pExecutionNumber, + executionNumberLength ); + } + + /* Add flags if not default values. */ + if( pUpdateInfo->includeJobExecutionState == true ) + { + copyOffset = _appendFlag( pJobsRequest, + copyOffset, + INCLUDE_JOB_EXECUTION_STATE_KEY, + INCLUDE_JOB_EXECUTION_STATE_KEY_LENGTH, + true ); + } + + if( pUpdateInfo->includeJobDocument == true ) + { + copyOffset = _appendFlag( pJobsRequest, + copyOffset, + INCLUDE_JOB_DOCUMENT_KEY, + INCLUDE_JOB_DOCUMENT_KEY_LENGTH, + true ); + } + + /* Add step timeout. */ + copyOffset = _appendStepTimeout( pJobsRequest, copyOffset, pStepTimeout, stepTimeoutLength ); + + /* Add the client token. */ + copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); + + APPEND_STRING( pJobsRequest, copyOffset, "\"}", 2 ); + + /* Set the output parameters. */ + pOperation->pJobsRequest = pJobsRequest; + pOperation->jobsRequestLength = requestLength; + + /* Ensure offsets are valid. */ + AwsIotJobs_Assert( copyOffset == requestLength ); + AwsIotJobs_Assert( pOperation->pClientToken > pOperation->pJobsRequest ); + AwsIotJobs_Assert( pOperation->pClientToken < + pOperation->pJobsRequest + pOperation->jobsRequestLength ); + + IotLogDebug( "Jobs UPDATE request: %.*s", + pOperation->jobsRequestLength, + pOperation->pJobsRequest ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, const _jsonRequestContents_t * pRequestContents, @@ -532,6 +983,10 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, default: /* The only remaining valid type is UPDATE. */ AwsIotJobs_Assert( type == JOBS_UPDATE ); + + status = _generateUpdateRequest( pRequestInfo, + pRequestContents->pUpdateInfo, + pOperation ); break; } diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index 730562999c..b9877b3fce 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -27,6 +27,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* SDK initialization include. */ #include "iot_init.h" @@ -84,6 +87,9 @@ TEST_GROUP_RUNNER( Jobs_Unit_Serialize ) RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ); RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ); RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeDescribe ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateStatus ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateNumbers ); + RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateFlags ); } /*-----------------------------------------------------------*/ @@ -177,7 +183,7 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); - /* Check that the step token is -1. */ + /* Check that the step timeout is -1. */ TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, operation.jobsRequestLength, "stepTimeoutInMinutes", @@ -409,3 +415,266 @@ TEST( Jobs_Unit_Serialize, SerializeDescribe ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of status in JSON documents for UPDATE + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeUpdateStatus ) +{ + uint32_t i = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + /* Valid statuses to check. */ + const AwsIotJobState_t pValidStatus[ 4 ] = + { + AWS_IOT_JOB_STATE_IN_PROGRESS, AWS_IOT_JOB_STATE_FAILED, + AWS_IOT_JOB_STATE_SUCCEEDED, AWS_IOT_JOB_STATE_REJECTED + }; + + const char * const pValidStatusStrings[ 4 ] = + { + "\"IN_PROGRESS\"", "\"FAILED\"", "\"SUCCEEDED\"", "\"REJECTED\"" + }; + + requestContents.pUpdateInfo = &updateInfo; + + /* Generate request documents with valid statuses. */ + for( i = 0; i < ( sizeof( pValidStatus ) / sizeof( pValidStatus[ 0 ] ) ); i++ ) + { + updateInfo.newStatus = pValidStatus[ i ]; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check the status value. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "status", + 6, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( strlen( pValidStatusStrings[ i ] ), jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( pValidStatusStrings[ i ], pJsonValue, jsonValueLength ); + + /* Check that the step timeout is -1. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 2, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); + + /* Check that a client token is present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); + TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + } + + /* Generate request with status details. */ + updateInfo.pStatusDetails = "{\"status\":0}"; + updateInfo.statusDetailsLength = 12; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the status details are present. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "statusDetails", + 13, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 12, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of numbers in JSON documents for UPDATE + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + requestContents.pUpdateInfo = &updateInfo; + + /* Generate request document with expected version. */ + updateInfo.expectedVersion = 1; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check the expected version. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "expectedVersion", + 15, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 3, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "\"1\"", pJsonValue, 3 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request document with execution number. */ + updateInfo.executionNumber = 555555555; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check the execution number. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "executionNumber", + 15, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 9, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "555555555", pJsonValue, 9 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request document with step timeout. */ + updateInfo.stepTimeoutInMinutes = 10080; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check the step timeout. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 5, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "10080", pJsonValue, 5 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests generation of flags in JSON documents for UPDATE + * requests. + */ +TEST( Jobs_Unit_Serialize, SerializeUpdateFlags ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + _jobsOperation_t operation = { .link = { 0 } }; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + _jsonRequestContents_t requestContents = { 0 }; + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + requestContents.pUpdateInfo = &updateInfo; + + /* Generate a request without any flags set. */ + requestInfo.pClientToken = "test"; + requestInfo.clientTokenLength = 4; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that no flags are present. */ + TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobDocument", + 18, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobExecutionState", + 24, + &pJsonValue, + &jsonValueLength ) ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate a request with flags set. */ + updateInfo.includeJobDocument = true; + updateInfo.includeJobExecutionState = true; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that flags are set. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobDocument", + 18, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 4, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "true", pJsonValue, 4 ); + + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobExecutionState", + 24, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 4, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "true", pJsonValue, 4 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); +} + +/*-----------------------------------------------------------*/ From e573e37e900e4c7bf71e0088718c739ef54f5817 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 24 Jun 2019 22:20:15 -0400 Subject: [PATCH 205/844] Add Jobs DESCRIBE and UPDATE functions (#492) --- lib/include/aws_iot_jobs.h | 2 +- lib/source/jobs/aws_iot_jobs_api.c | 131 +++++++++++ lib/source/jobs/aws_iot_jobs_static_memory.c | 2 +- lib/source/serializer/CMakeLists.txt | 2 +- tests/jobs/unit/aws_iot_tests_jobs_api.c | 230 ++++++++++--------- 5 files changed, 256 insertions(+), 111 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 15f17724e2..c30c4cc885 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -192,7 +192,7 @@ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestI bool includeJobDocument, uint32_t flags, const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pJobsOperation ); + AwsIotJobsOperation_t * const pDescribeOperation ); /* @[declare_jobs_describe] */ /** diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index a5ac0e5614..2d0fbb725c 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -464,6 +464,137 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pDescribeOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + _jobsOperation_t * pOperation = NULL; + _jsonRequestContents_t requestContents = { 0 }; + + /* Check request info. */ + status = _validateRequestInfo( JOBS_DESCRIBE, + pRequestInfo, + flags, + pCallbackInfo, + pDescribeOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Allocate a new Jobs operation. */ + requestContents.describe.executionNumber = executionNumber; + requestContents.describe.includeJobDocument = includeJobDocument; + + status = _AwsIotJobs_CreateOperation( JOBS_DESCRIBE, + pRequestInfo, + &requestContents, + flags, + pCallbackInfo, + &pOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + /* No memory for Jobs operation. */ + IOT_GOTO_CLEANUP(); + } + + /* Set the reference if provided. This must be done before the Jobs operation + * is processed. */ + if( pDescribeOperation != NULL ) + { + *pDescribeOperation = pOperation; + } + + /* Process the Jobs operation. This subscribes to any required topics and + * sends the MQTT message for the Jobs operation. */ + status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); + + /* If the Jobs operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pDescribeOperation != NULL ) ) + { + *pDescribeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pUpdateOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + _jobsOperation_t * pOperation = NULL; + _jsonRequestContents_t requestContents = { 0 }; + + /* Check request info. */ + status = _validateRequestInfo( JOBS_UPDATE, + pRequestInfo, + flags, + pCallbackInfo, + pUpdateOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Check update info. */ + status = _validateUpdateInfo( JOBS_UPDATE, + pUpdateInfo ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Allocate a new Jobs operation. */ + requestContents.pUpdateInfo = pUpdateInfo; + + status = _AwsIotJobs_CreateOperation( JOBS_UPDATE, + pRequestInfo, + &requestContents, + flags, + pCallbackInfo, + &pOperation ); + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + /* No memory for Jobs operation. */ + IOT_GOTO_CLEANUP(); + } + + /* Set the reference if provided. This must be done before the Jobs operation + * is processed. */ + if( pUpdateOperation != NULL ) + { + *pUpdateOperation = pOperation; + } + + /* Process the Jobs operation. This subscribes to any required topics and + * sends the MQTT message for the Jobs operation. */ + status = _AwsIotJobs_ProcessOperation( pRequestInfo, pOperation ); + + /* If the Jobs operation failed, clear the now invalid reference. */ + if( ( status != AWS_IOT_JOBS_STATUS_PENDING ) && ( pUpdateOperation != NULL ) ) + { + *pUpdateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, uint32_t timeoutMs, const char ** const pResponse, diff --git a/lib/source/jobs/aws_iot_jobs_static_memory.c b/lib/source/jobs/aws_iot_jobs_static_memory.c index 23851fcb2f..8af0f55bd2 100644 --- a/lib/source/jobs/aws_iot_jobs_static_memory.c +++ b/lib/source/jobs/aws_iot_jobs_static_memory.c @@ -89,7 +89,7 @@ * Static memory buffers and flags, allocated and zeroed at compile-time. */ static uint32_t _pInUseJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ] = { 0U }; /**< @brief Jobs operation in-use flags. */ -static _jobsOperation_t _pJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ][ JOBS_OPERATION_SIZE ] = { { 0 } }; /**< @brief Jobs operations. */ +static char _pJobsOperations[ AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS ][ JOBS_OPERATION_SIZE ] = { { 0 } }; /**< @brief Jobs operations. */ static uint32_t _pInUseJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ] = { 0U }; /**< @brief Jobs subscription in-use flags. */ static char _pJobsSubscriptions[ AWS_IOT_JOBS_SUBSCRIPTIONS ][ JOBS_SUBSCRIPTION_SIZE ] = { { 0 } }; /**< @brief Jobs subscriptions. */ diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt index 8279ccd257..58a5bc608f 100644 --- a/lib/source/serializer/CMakeLists.txt +++ b/lib/source/serializer/CMakeLists.txt @@ -12,7 +12,7 @@ add_library( iotserializer ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) # Link required libraries. -target_link_libraries( iotserializer PRIVATE tinycbor ) +target_link_libraries( iotserializer PRIVATE iotbase tinycbor ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 56b4c25549..8595551dac 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -80,6 +80,106 @@ static IotMqttConnection_t _pMqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*-----------------------------------------------------------*/ +/** + * @brief Common code of the MallocFail tests. + */ +static void _jobsMallocFail( _jobsOperationType_t type ) +{ + int32_t i = 0, mqttErrorCount = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + const char * pResponse = NULL; + size_t responseLength = 0; + + /* Set a short timeout so this test runs faster. */ + _AwsIotJobsMqttTimeoutMs = 75; + + /* Set the members of the request info. */ + requestInfo.mqttConnection = _pMqttConnection; + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + requestInfo.mallocResponse = IotTest_Malloc; + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Jobs operation. Memory allocation will fail at various times + * during this call. */ + switch( type ) + { + case JOBS_GET_PENDING: + status = AwsIotJobs_GetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); + break; + + case JOBS_START_NEXT: + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); + break; + + case JOBS_DESCRIBE: + status = AwsIotJobs_Describe( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + false, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); + break; + + default: + /* The only remaining valid type is update. */ + TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); + + status = AwsIotJobs_Update( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); + break; + } + + /* Once the Jobs operation call succeeds, wait for it to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + /* No response will be received from the network, so the Jobs operation + * is expected to time out. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, + AwsIotJobs_Wait( operation, + 0, + &pResponse, + &responseLength ) ); + break; + } + + /* Count the number of MQTT library errors. Otherwise, check that the error + * is a "No memory" error. */ + if( status == AWS_IOT_JOBS_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); + } + } + + /* Allow 3 MQTT library errors, which are caused by failure to allocate memory + * for incoming packets (SUBSCRIBE, PUBLISH). */ + CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs API tests. */ @@ -134,6 +234,8 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) RUN_TEST_CASE( Jobs_Unit_API, WaitInvalidParameters ); RUN_TEST_CASE( Jobs_Unit_API, GetPendingMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, StartNextMallocFail ); + RUN_TEST_CASE( Jobs_Unit_API, DescribeMallocFail ); + RUN_TEST_CASE( Jobs_Unit_API, UpdateMallocFail ); } /*-----------------------------------------------------------*/ @@ -328,61 +430,7 @@ TEST( Jobs_Unit_API, WaitInvalidParameters ) */ TEST( Jobs_Unit_API, GetPendingMallocFail ) { - int32_t i = 0, mqttErrorCount = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - const char * pResponse = NULL; - size_t responseLength = 0; - - /* Set a short timeout so this test runs faster. */ - _AwsIotJobsMqttTimeoutMs = 75; - - /* Set the members of the request info. */ - requestInfo.mqttConnection = _pMqttConnection; - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - requestInfo.mallocResponse = IotTest_Malloc; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); - - /* Call Jobs GET PENDING. Memory allocation will fail at various times - * during this call. */ - status = AwsIotJobs_GetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &getPendingOperation ); - - /* Once the Jobs GET PENDING call succeeds, wait for it to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - /* No response will be received from the network, so the Jobs operation - * is expected to time out. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, - AwsIotJobs_Wait( getPendingOperation, - 0, - &pResponse, - &responseLength ) ); - break; - } - - /* Count the number of MQTT library errors. Otherwise, check that the error - * is a "No memory" error. */ - if( status == AWS_IOT_JOBS_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); - } - } - - /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH). */ - CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); + _jobsMallocFail( JOBS_GET_PENDING ); } /*-----------------------------------------------------------*/ @@ -393,63 +441,29 @@ TEST( Jobs_Unit_API, GetPendingMallocFail ) */ TEST( Jobs_Unit_API, StartNextMallocFail ) { - int32_t i = 0, mqttErrorCount = 0; - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; - AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - const char * pResponse = NULL; - size_t responseLength = 0; - - /* Set a short timeout so this test runs faster. */ - _AwsIotJobsMqttTimeoutMs = 75; - - /* Set the members of the request info. */ - requestInfo.mqttConnection = _pMqttConnection; - requestInfo.pThingName = TEST_THING_NAME; - requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; - requestInfo.mallocResponse = IotTest_Malloc; - - for( i = 0; ; i++ ) - { - UnityMalloc_MakeMallocFailAfterCount( i ); + _jobsMallocFail( JOBS_START_NEXT ); +} - /* Call Jobs START NEXT. Memory allocation will fail at various times - * during this call. */ - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &startNextOperation ); +/*-----------------------------------------------------------*/ - /* Once the Jobs START NEXT call succeeds, wait for it to complete. */ - if( status == AWS_IOT_JOBS_STATUS_PENDING ) - { - /* No response will be received from the network, so the Jobs operation - * is expected to time out. */ - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, - AwsIotJobs_Wait( startNextOperation, - 0, - &pResponse, - &responseLength ) ); - break; - } +/** + * @brief Tests the behavior of @ref jobs_function_describe when memory + * allocation fails at various points. + */ +TEST( Jobs_Unit_API, DescribeMallocFail ) +{ + _jobsMallocFail( JOBS_DESCRIBE ); +} - /* Count the number of MQTT library errors. Otherwise, check that the error - * is a "No memory" error. */ - if( status == AWS_IOT_JOBS_MQTT_ERROR ) - { - mqttErrorCount++; - } - else - { - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); - } - } +/*-----------------------------------------------------------*/ - /* Allow 3 MQTT library errors, which are caused by failure to allocate memory - * for incoming packets (SUBSCRIBE, PUBLISH). */ - CHECK_MQTT_ERROR_COUNT( 2, mqttErrorCount ); +/** + * @brief Tests the behavior of @ref jobs_function_update when memory + * allocation fails at various points. + */ +TEST( Jobs_Unit_API, UpdateMallocFail ) +{ + _jobsMallocFail( JOBS_UPDATE ); } /*-----------------------------------------------------------*/ From 185599fab9c14e12bbc14df1dd2a2d09254b921d Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:32:23 -0400 Subject: [PATCH 206/844] Improve code coverage of Jobs tests (#493) --- lib/source/jobs/aws_iot_jobs_api.c | 2 + tests/jobs/unit/aws_iot_tests_jobs_api.c | 99 ++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 2d0fbb725c..183823b6eb 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -272,6 +272,8 @@ static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, IotLogError( "Status details length for Jobs %s cannot exceed %d.", _pAwsIotJobsOperationNames[ type ], JOBS_MAX_STATUS_DETAILS_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); } } diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 8595551dac..a126599e4d 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -27,6 +27,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* SDK initialization include. */ #include "iot_init.h" @@ -229,6 +232,7 @@ TEST_TEAR_DOWN( Jobs_Unit_API ) TEST_GROUP_RUNNER( Jobs_Unit_API ) { RUN_TEST_CASE( Jobs_Unit_API, Init ); + RUN_TEST_CASE( Jobs_Unit_API, StringCoverage ); RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidRequestInfo ); RUN_TEST_CASE( Jobs_Unit_API, OperationInvalidUpdateInfo ); RUN_TEST_CASE( Jobs_Unit_API, WaitInvalidParameters ); @@ -282,6 +286,48 @@ TEST( Jobs_Unit_API, Init ) /*-----------------------------------------------------------*/ +/** + * @brief Provides code coverage of the Jobs enum-to-string functions, + * @ref jobs_function_strerror and @ref jobs_function_statename. + */ +TEST( Jobs_Unit_API, StringCoverage ) +{ + int32_t i = 0; + const char * pMessage = NULL; + + /* For each Jobs Error, check the returned string. */ + while( true ) + { + pMessage = AwsIotJobs_strerror( ( AwsIotJobsError_t ) i ); + TEST_ASSERT_NOT_NULL( pMessage ); + + if( strncmp( "INVALID STATUS", pMessage, 14 ) == 0 ) + { + break; + } + + i++; + } + + /* For each Jobs State, check the returned string. */ + i = 0; + + while( true ) + { + pMessage = AwsIotJobs_StateName( ( AwsIotJobState_t ) i ); + TEST_ASSERT_NOT_NULL( pMessage ); + + if( strncmp( "INVALID STATE", pMessage, 13 ) == 0 ) + { + break; + } + + i++; + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of Jobs operation functions with various * invalid #AwsIotJobsRequestInfo_t. @@ -349,6 +395,36 @@ TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) 0, &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Client token too long. */ + requestInfo.clientTokenLength = AWS_IOT_CLIENT_TOKEN_MAX_LENGTH + 1; + + status = AwsIotJobs_GetPending( &requestInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.pClientToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE; + + /* Job ID not set. */ + status = AwsIotJobs_Describe( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + false, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Job ID too long. */ + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.jobIdLength = JOBS_MAX_ID_LENGTH + 1; + + status = AwsIotJobs_Update( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); } /*-----------------------------------------------------------*/ @@ -396,6 +472,29 @@ TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) NULL, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Status details too large. */ + updateInfo.statusDetailsLength = JOBS_MAX_STATUS_DETAILS_LENGTH + 1; + + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + updateInfo.pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS; + + /* Invalid UPDATE state. */ + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + updateInfo.newStatus = AWS_IOT_JOB_STATE_QUEUED; + + status = AwsIotJobs_Update( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); } /*-----------------------------------------------------------*/ From 928c4957c416bf2c05b2fe7727683f71b6771aca Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 25 Jun 2019 16:41:12 -0400 Subject: [PATCH 207/844] Add Jobs system tests file (#494) --- doc/lib/jobs.txt | 23 +- tests/iot_config.h | 5 + tests/jobs/CMakeLists.txt | 1 + tests/jobs/aws_iot_tests_jobs.c | 8 +- tests/jobs/system/aws_iot_tests_jobs_system.c | 220 ++++++++++++++++++ .../system/aws_iot_tests_shadow_system.c | 2 +- 6 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 tests/jobs/system/aws_iot_tests_jobs_system.c diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index add3ab171b..b9ee8f0df1 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -50,13 +50,34 @@ In addition to the components above, the Jobs library also depends on C standard @brief Tests written for the Jobs library. The Jobs tests reside in the `tests/jobs` directory. They are divided into the following subdirectories: +- `system`: Jobs system tests. These tests require a network connection and AWS IoT credentials. The command line option `-n` may be passed to the test executable to disable these tests. - `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) -The Jobs unit tests require no special configuration. +See @subpage jobs_tests_config for configuration settings that change the behavior of the Jobs system tests. The Jobs unit tests require no special configuration. The current Jobs tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. */ +/** +@configpage{jobs_tests,Jobs system tests,Test,tests} + +@section AWS_IOT_TEST_JOBS_THING_NAME +@brief The Thing Name to use in the Jobs system tests. + +Thing Names are used to manage devices with AWS IoT. No default value is provided for Thing Names, so this constant must be defined. In addition to the Thing Name, AWS IoT credentials ([root CA certificate](@ref IOT_TEST_ROOT_CA), [client certificate](@ref IOT_TEST_CLIENT_CERT), and [client certificate private key](@ref IOT_TEST_PRIVATE_KEY)) must be provided to run the system tests. The [AWS IoT policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must also be properly configured. + +@configpossible A string representing an AWS IoT Thing Name. + +@section jobs_IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S +@brief The keep-alive interval to use in the Jobs system tests. + +MQTT PINGREQ packets will be sent at this interval. + +@configpossible Any positive integer.
+@configrecommended This value should be the shortest keep-alive interval supported by the connection.
+@configdefault `30` +*/ + /** @configpage{jobs,Jobs library} diff --git a/tests/iot_config.h b/tests/iot_config.h index f08711b32a..794f42748b 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -65,6 +65,11 @@ #define AWS_IOT_TEST_SHADOW_THING_NAME "" #endif +/* Jobs tests configuration. */ +#ifndef AWS_IOT_TEST_JOBS_THING_NAME + #define AWS_IOT_TEST_JOBS_THING_NAME "" +#endif + /* Log level for testing the demos. */ #define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO diff --git a/tests/jobs/CMakeLists.txt b/tests/jobs/CMakeLists.txt index c95cdffb7b..a1f7840f89 100644 --- a/tests/jobs/CMakeLists.txt +++ b/tests/jobs/CMakeLists.txt @@ -1,5 +1,6 @@ # Jobs unit test sources. set( JOBS_UNIT_TEST_SOURCES + system/aws_iot_tests_jobs_system.c unit/aws_iot_tests_jobs_api.c unit/aws_iot_tests_jobs_serialize.c ) diff --git a/tests/jobs/aws_iot_tests_jobs.c b/tests/jobs/aws_iot_tests_jobs.c index 4e62486999..3005416899 100644 --- a/tests/jobs/aws_iot_tests_jobs.c +++ b/tests/jobs/aws_iot_tests_jobs.c @@ -36,11 +36,15 @@ void RunJobsTests( bool disableNetworkTests, bool disableLongTests ) { /* Silence warnings about unused parameters. */ - ( void ) disableNetworkTests; ( void ) disableLongTests; RUN_TEST_GROUP( Jobs_Unit_API ); - RUN_TEST_GROUP( Jobs_Unit_Serialize ) + RUN_TEST_GROUP( Jobs_Unit_Serialize ); + + if( disableNetworkTests == false ) + { + RUN_TEST_GROUP( Jobs_System ); + } } /*-----------------------------------------------------------*/ diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c new file mode 100644 index 0000000000..350252f646 --- /dev/null +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_jobs_system.c + * @brief Full system tests for the AWS IoT Jobs library. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* SDK initialization include. */ +#include "iot_init.h" + +/* SDK initialization include. */ +#include "iot_init.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/* Jobs include. */ +#include "aws_iot_jobs.h" + +/* Test network header include. */ +#include IOT_TEST_NETWORK_HEADER + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Provide default values of test configuration constants. + */ +#ifndef IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S + #define IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S ( 30 ) +#endif +#ifndef AWS_IOT_TEST_JOBS_TIMEOUT + #define AWS_IOT_TEST_JOBS_TIMEOUT ( 5000 ) +#endif +/** @endcond */ + +/* Thing Name must be defined for these tests. */ +#ifndef AWS_IOT_TEST_JOBS_THING_NAME + #error "Please define AWS_IOT_TEST_JOBS_THING_NAME." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Network server info to share among the tests. + */ +static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + +/** + * @brief Network credential info to share among the tests. + */ +#if IOT_TEST_SECURED_CONNECTION == 1 + static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +#endif + +/** + * @brief An MQTT network setup parameter to share among the tests. + */ +static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + +/** + * @brief An MQTT connection to share among the tests. + */ +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Jobs system tests. + */ +TEST_GROUP( Jobs_System ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Jobs system tests. + */ +TEST_SETUP( Jobs_System ) +{ + static uint64_t lastConnectTime = 0; + uint64_t elapsedTime = 0; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Initialize SDK. */ + if( IotSdk_Init() == false ) + { + TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); + } + + /* Set up the network stack. */ + if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to set up network stack." ); + } + + /* Initialize the MQTT library. */ + if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); + } + + /* Initialize the Jobs library. */ + if( AwsIotJobs_Init( 0 ) != AWS_IOT_JOBS_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to initialize Jobs library." ); + } + + /* Set the MQTT network setup parameters. */ + ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + _networkInfo.createNetworkConnection = true; + _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; + _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + + #if IOT_TEST_SECURED_CONNECTION == 1 + _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; + #endif + + #ifdef IOT_TEST_MQTT_SERIALIZER + _networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + #endif + + /* Set the members of the connect info. Use the Jobs Thing Name as the MQTT + * client identifier. */ + connectInfo.awsIotMqttMode = true; + connectInfo.pClientIdentifier = AWS_IOT_TEST_JOBS_THING_NAME; + connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; + + /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. + * Wait until 1100 ms have elapsed since the last connection. */ + elapsedTime = IotClock_GetTimeMs() - lastConnectTime; + + if( elapsedTime < 1100ULL ) + { + IotClock_SleepMs( 1100UL - ( uint32_t ) elapsedTime ); + } + + /* Establish an MQTT connection. */ + if( IotMqtt_Connect( &_networkInfo, + &connectInfo, + AWS_IOT_TEST_JOBS_TIMEOUT, + &_mqttConnection ) != IOT_MQTT_SUCCESS ) + { + TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Jobs tests." ); + } + + /* Update the time of the last MQTT connect. */ + lastConnectTime = IotClock_GetTimeMs(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Jobs system tests. + */ +TEST_TEAR_DOWN( Jobs_System ) +{ + /* Disconnect the MQTT connection if it was created. */ + if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) + { + IotMqtt_Disconnect( _mqttConnection, 0 ); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + } + + /* Clean up the Jobs library. */ + AwsIotJobs_Cleanup(); + + /* Clean up the MQTT library. */ + IotMqtt_Cleanup(); + + /* Clean up the network stack. */ + IotTestNetwork_Cleanup(); + + /* Clean up SDK. */ + IotSdk_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Jobs system tests. + */ +TEST_GROUP_RUNNER( Jobs_System ) +{ +} + +/*-----------------------------------------------------------*/ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 516f3d10f7..f8a554a1bb 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -57,7 +57,7 @@ * assert function is used to abort the tests on failure from the Shadow operation * complete callback. */ #if AWS_IOT_SHADOW_ENABLE_ASSERTS == 0 - #error "Shadow API unit tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." + #error "Shadow system tests require AWS_IOT_SHADOW_ENABLE_ASSERTS to be 1." #endif /*-----------------------------------------------------------*/ From 7c8c4f372138e928353cdbf502839d172b0ee133 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:42:38 -0400 Subject: [PATCH 208/844] Add Jobs response type (#495) --- lib/include/aws_iot_jobs.h | 15 ++++++++----- lib/include/types/aws_iot_jobs_types.h | 23 +++++++++++++++++++- lib/source/jobs/aws_iot_jobs_api.c | 13 +++++------ lib/source/shadow/aws_iot_shadow_operation.c | 8 +++++-- tests/jobs/unit/aws_iot_tests_jobs_api.c | 14 +++++------- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index c30c4cc885..19cdd531f9 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -156,7 +156,8 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques /* @[declare_jobs_timedgetpending] */ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, uint32_t flags, - uint32_t timeout ); + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); /* @[declare_jobs_timedgetpending] */ /** @@ -179,7 +180,8 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, uint32_t flags, - uint32_t timeout ); + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); /* @[declare_jobs_timedstartnext] */ /** @@ -204,7 +206,8 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq int32_t executionNumber, bool includeJobDocument, uint32_t flags, - uint32_t timeout ); + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); /* @[declare_jobs_timeddescribe] */ /** @@ -227,7 +230,8 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, uint32_t flags, - uint32_t timeout ); + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); /* @[declare_jobs_timedupdate] */ /** @@ -236,8 +240,7 @@ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pReque /* @[declare_jobs_wait] */ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, uint32_t timeoutMs, - const char ** const pResponse, - size_t * const pResponseLength ); + AwsIotJobsResponse_t * const pJobsResponse ); /* @[declare_jobs_wait] */ /** diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 53ff613e41..3b2fcce007 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -563,6 +563,25 @@ typedef struct AwsIotJobsRequestInfo size_t clientTokenLength; } AwsIotJobsRequestInfo_t; +/** + * @ingroup jobs_datatypes_paramstructs + * @brief Output parameter of blocking Jobs API functions. + * + * @paramfor @ref jobs_function_timedgetpending, @ref jobs_function_timedstartnext, + * @ref jobs_function_timeddescribe, @ref jobs_function_timedupdate, + * @ref jobs_function_wait + * + * Provides the response received from the Jobs service. The buffer for the + * response is allocated with #AwsIotJobsRequestInfo_t.mallocResponse. + * + * @initializer{AwsIotJobsResponse_t,AWS_IOT_JOBS_RESPONSE_INITIALIZER} + */ +typedef struct AwsIotJobsResponse +{ + const char * pJobsResponse; /**< @brief JSON response received from the Jobs service. */ + size_t jobsResponseLength; /**< @brief Length of #AwsIotJobsResponse_t.pJobsResponse. */ +} AwsIotJobsResponse_t; + /** * @ingroup jobs_datatypes_paramstructs * @brief Information on a Job update for @ref jobs_function_startnext and @@ -760,6 +779,7 @@ typedef struct AwsIotJobsUpdateInfo * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; * AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; * @endcode * * @section jobs_constants_flags Jobs Function Flags @@ -807,7 +827,8 @@ typedef struct AwsIotJobsUpdateInfo .includeJobExecutionState = false, \ .includeJobDocument = false, \ .pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS } -#define AWS_IOT_JOBS_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ +#define AWS_IOT_JOBS_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotJobsOperation_t. */ +#define AWS_IOT_JOBS_RESPONSE_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotJobsResponse_t. */ /* @[define_jobs_initializers] */ /** diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 183823b6eb..cc6eb348cc 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -599,8 +599,7 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, uint32_t timeoutMs, - const char ** const pResponse, - size_t * const pResponseLength ) + AwsIotJobsResponse_t * pJobsResponse ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); @@ -620,10 +619,10 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); } - /* Check that output parameters are set. */ - if( ( pResponse == NULL ) || ( pResponseLength == NULL ) ) + /* Check that output parameter is set. */ + if( pJobsResponse == NULL ) { - IotLogError( "Output buffer and size pointer must be set for Jobs %s.", + IotLogError( "Output Jobs response parameter must be set for Jobs %s.", _pAwsIotJobsOperationNames[ operation->type ] ); IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); @@ -660,8 +659,8 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, AwsIotJobs_Assert( operation->pJobsResponse != NULL ); AwsIotJobs_Assert( operation->jobsResponseLength > 0 ); - *pResponse = operation->pJobsResponse; - *pResponseLength = operation->jobsResponseLength; + pJobsResponse->pJobsResponse = operation->pJobsResponse; + pJobsResponse->jobsResponseLength = operation->jobsResponseLength; } /* Destroy the Jobs operation. */ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 0328120fd6..11f12ab97a 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -262,7 +262,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, &( param.pThingName ), &( param.thingNameLength ) ) == false ) { - return; + IOT_GOTO_CLEANUP(); } /* Lock the pending operations list for exclusive access. */ @@ -284,7 +284,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, IotLogWarn( "Shadow %s callback received an unknown operation.", _pAwsIotShadowOperationNames[ type ] ); - return; + IOT_GOTO_CLEANUP(); } else { @@ -365,6 +365,10 @@ static void _commonOperationCallback( _shadowOperationType_t type, { IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); } + + /* This function has no return value and no cleanup, but uses the cleanup + * label to exit on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); } /*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index a126599e4d..b46e23df06 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -93,8 +93,7 @@ static void _jobsMallocFail( _jobsOperationType_t type ) AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - const char * pResponse = NULL; - size_t responseLength = 0; + AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; /* Set a short timeout so this test runs faster. */ _AwsIotJobsMqttTimeoutMs = 75; @@ -159,8 +158,7 @@ static void _jobsMallocFail( _jobsOperationType_t type ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_TIMEOUT, AwsIotJobs_Wait( operation, 0, - &pResponse, - &responseLength ) ); + &jobsResponse ) ); break; } @@ -509,16 +507,16 @@ TEST( Jobs_Unit_API, WaitInvalidParameters ) _jobsOperation_t operation = { .link = { 0 } }; /* NULL reference. */ - status = AwsIotJobs_Wait( NULL, 0, NULL, NULL ); + status = AwsIotJobs_Wait( NULL, 0, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* No waitable flag set. */ - status = AwsIotJobs_Wait( &operation, 0, NULL, NULL ); + status = AwsIotJobs_Wait( &operation, 0, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); - /* NULL output parameters. */ + /* NULL output parameter. */ operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; - status = AwsIotJobs_Wait( &operation, 0, NULL, NULL ); + status = AwsIotJobs_Wait( &operation, 0, NULL ); } /*-----------------------------------------------------------*/ From 79e18c79702903d186124e6a01d06e7a5c5773c5 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 25 Jun 2019 19:19:38 -0400 Subject: [PATCH 209/844] Fix documentation links (#496) --- README.md | 6 ++--- demos/README.md | 4 +-- platform/README.md | 4 +-- .../template/source/iot_clock_template.c | 12 ++++----- .../template/source/iot_threads_template.c | 26 +++++++++---------- tests/README.md | 4 +-- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index b1f2fca605..38762a11f3 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are ## Building and Running Demos -**Main documentation page:** [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html) +**Main documentation page:** [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. **As of now, this Beta release only builds on Linux.** @@ -69,11 +69,11 @@ If using the OpenSSL network implementation: make ``` -See the documentation page [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html) for a list of options that can be used to configure the build system. +See the documentation page [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) for a list of options that can be used to configure the build system. ## Porting the SDK -Please refer to the [Porting Guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/guide_developer_porting.html) for instructions on porting this SDK. +Please refer to the [Porting Guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions on porting this SDK. Existing ports (which may be used as examples) are present in `platform/ports`. A blank template for implementing new ports is in `platform/ports/template`. diff --git a/demos/README.md b/demos/README.md index 29b8b38a5f..ef0ae7351a 100644 --- a/demos/README.md +++ b/demos/README.md @@ -8,6 +8,6 @@ This directory contains source files for demo executables. Its subdirectories ar - `source`
Platform-independent demo sources. The files in this directory contain the majority of the demo code. The demo runner in `app` calls demo functions implemented in this directory. -The configuration file for the demos, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the demos and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/global_config.html) for settings that affect all demos and libraries; see each library's documentation for library-specific settings. +The configuration file for the demos, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the demos and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all demos and libraries; see each library's documentation for library-specific settings. -For information on building and running the demos, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html). +For information on building and running the demos, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html). diff --git a/platform/README.md b/platform/README.md index e3a51356e7..85fd880895 100644 --- a/platform/README.md +++ b/platform/README.md @@ -1,6 +1,6 @@ # Platform layer -**Main documentation page:** [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/index.html) +**Main documentation page:** [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/index.html) This directory contains the headers and sources of the platform layer, implemented for various ports. Its subdirectories are organized as follows: - `include`
@@ -16,4 +16,4 @@ This directory contains the headers and sources of the platform layer, implement - `template`
Empty port sources containing stubbed-out functions. -When porting this SDK to a new platform, only files in this directory should be modified. See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/guide_developer_porting.html) for instructions. +When porting this SDK to a new platform, only files in this directory should be modified. See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions. diff --git a/platform/ports/template/source/iot_clock_template.c b/platform/ports/template/source/iot_clock_template.c index 55e3ace1e8..7ab136dc35 100644 --- a/platform/ports/template/source/iot_clock_template.c +++ b/platform/ports/template/source/iot_clock_template.c @@ -51,7 +51,7 @@ bool IotClock_GetTimestring( char * pBuffer, size_t * pTimestringLength ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_gettimestring.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_gettimestring.html */ return false; } @@ -61,7 +61,7 @@ bool IotClock_GetTimestring( char * pBuffer, uint64_t IotClock_GetTimeMs( void ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_gettimems.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_gettimems.html */ return 0; } @@ -71,7 +71,7 @@ uint64_t IotClock_GetTimeMs( void ) void IotClock_SleepMs( uint32_t sleepTimeMs ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_sleepms.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_sleepms.html */ } @@ -82,7 +82,7 @@ bool IotClock_TimerCreate( IotTimer_t * pNewTimer, void * pArgument ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_timercreate.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_timercreate.html */ return false; } @@ -92,7 +92,7 @@ bool IotClock_TimerCreate( IotTimer_t * pNewTimer, void IotClock_TimerDestroy( IotTimer_t * pTimer ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_timerdestroy.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_timerdestroy.html */ } @@ -103,7 +103,7 @@ bool IotClock_TimerArm( IotTimer_t * pTimer, uint32_t periodMs ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_clock_function_timerarm.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_clock_function_timerarm.html */ return false; } diff --git a/platform/ports/template/source/iot_threads_template.c b/platform/ports/template/source/iot_threads_template.c index c9fe318e8a..72a9b53412 100644 --- a/platform/ports/template/source/iot_threads_template.c +++ b/platform/ports/template/source/iot_threads_template.c @@ -52,7 +52,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, size_t stackSize ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_createdetachedthread.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_createdetachedthread.html */ return false; } @@ -62,7 +62,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexcreate.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexcreate.html */ return false; } @@ -72,7 +72,7 @@ bool IotMutex_Create( IotMutex_t * pNewMutex, bool recursive ) void IotMutex_Destroy( IotMutex_t * pMutex ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexdestroy.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexdestroy.html */ } @@ -81,7 +81,7 @@ void IotMutex_Destroy( IotMutex_t * pMutex ) void IotMutex_Lock( IotMutex_t * pMutex ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexlock.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexlock.html */ } @@ -90,7 +90,7 @@ void IotMutex_Lock( IotMutex_t * pMutex ) bool IotMutex_TryLock( IotMutex_t * pMutex ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutextrylock.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutextrylock.html */ return false; } @@ -100,7 +100,7 @@ bool IotMutex_TryLock( IotMutex_t * pMutex ) void IotMutex_Unlock( IotMutex_t * pMutex ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_mutexunlock.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_mutexunlock.html */ } @@ -111,7 +111,7 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, uint32_t maxValue ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphorecreate.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphorecreate.html */ return false; } @@ -121,7 +121,7 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoredestroy.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoredestroy.html */ } @@ -130,7 +130,7 @@ void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoregetcount.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoregetcount.html */ return 0; } @@ -140,7 +140,7 @@ uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphorewait.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphorewait.html */ } @@ -149,7 +149,7 @@ void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoretrywait.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoretrywait.html */ return false; } @@ -160,7 +160,7 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, uint32_t timeoutMs ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphoretimedwait.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphoretimedwait.html */ return false; } @@ -170,7 +170,7 @@ bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) { /* Implement this function as specified here: - * https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/platform/platform_threads_function_semaphorepost.html + * https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/platform_threads_function_semaphorepost.html */ } diff --git a/tests/README.md b/tests/README.md index a777da7161..7a9a22db22 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,6 +6,6 @@ This directory contains source files for test executables. Its subdirectories ar The test executable source (i.e. the `main()` function) is `iot_tests.c`. -The configuration file for the tests, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. +The configuration file for the tests, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. -For information on building and running the tests, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/html3/main/building.html). +For information on building and running the tests, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html). From 16c7a9868e1007a9bd74e6f2e5603bf2ce1201db Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 25 Jun 2019 22:04:40 -0400 Subject: [PATCH 210/844] Add Jobs response processing (#497) --- doc/guide/developer/automated_tests.txt | 6 +- lib/include/private/aws_iot_jobs_internal.h | 14 + lib/include/private/aws_iot_shadow_internal.h | 11 - lib/include/types/aws_iot_jobs_types.h | 12 +- lib/source/jobs/aws_iot_jobs_api.c | 30 ++ lib/source/jobs/aws_iot_jobs_operation.c | 270 ++++++++++++++++++ lib/source/jobs/aws_iot_jobs_serialize.c | 78 +++++ lib/source/shadow/aws_iot_shadow_operation.c | 143 +++++----- scripts/ci_test_coverage.sh | 2 +- scripts/ci_test_jobs.sh | 18 +- tests/jobs/system/aws_iot_tests_jobs_system.c | 32 +++ 11 files changed, 527 insertions(+), 89 deletions(-) diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index 2f94a3c90a..06ff91bbba 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -132,7 +132,8 @@ AWS recommends using the most restrictive policy possible. The following policy "Resource": [ "arn:aws:iot:::topic/${iot:ClientId}/*", "arn:aws:iot:::topic/*/LastWillAndTestament", - "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/shadow/*" + "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/shadow/*", + "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/jobs/*" ] }, { @@ -141,7 +142,8 @@ AWS recommends using the most restrictive policy possible. The following policy "Resource": [ "arn:aws:iot:::topicfilter/${iot:ClientId}/*", "arn:aws:iot:::topicfilter/*/LastWillAndTestament", - "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/shadow/*" + "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/shadow/*", + "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/jobs/*" ] } ] diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index f6ec7ae300..e9b2daf0a6 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -567,4 +567,18 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const _jsonRequestContents_t * pRequestContents, _jobsOperation_t * pOperation ); +/** + * @brief Parse a response received from the Jobs service. + * + * @param[in] status Either ACCEPTED or REJECTED. + * @param[in] pResponse The response received from the Jobs service. + * @param[in] responseLength Length of `pResponse`. + * @param[out] pOperation Associated Jobs operation, where parse results are + * written. + */ +void _AwsIotJobs_ParseResponse( AwsIotStatus_t status, + const char * pResponse, + size_t responseLength, + _jobsOperation_t * pOperation ); + #endif /* ifndef AWS_IOT_JOBS_INTERNAL_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index ce6c377356..144c2fe66e 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -416,17 +416,6 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn _shadowOperation_t * pOperation, const AwsIotShadowDocumentInfo_t * pDocumentInfo ); -/** - * @brief Notify of a completed Shadow operation. - * - * @param[in] pOperation The operation which completed. - * - * Depending on the parameters passed to a user-facing Shadow function, the - * notification will cause @ref shadow_function_wait to return or invoke a - * user-provided callback. - */ -void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ); - /*---------------------- Shadow subscription functions ----------------------*/ /** diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 3b2fcce007..c9d52f4ef3 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -393,12 +393,12 @@ typedef enum AwsIotJobsState */ typedef enum AwsIotJobsCallbackType { - AWS_IOT_JOBS_GET_PENDING_COMPLETE, /**< Callback invoked because a [Jobs get pending](@ref jobs_function_getpending) completed. */ - AWS_IOT_JOBS_START_NEXT_COMPLETE, /**< Callback invoked because a [Jobs start next](@ref jobs_function_startnext) completed. */ - AWS_IOT_JOBS_DESCRIBE_COMPLETE, /**< Callback invoked because a [Jobs describe](@ref jobs_function_describe) completed. */ - AWS_IOT_JOBS_UPDATE_COMPLETE, /**< Callback invoked because a [Jobs update](@ref jobs_function_update) completed. */ - AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK, /**< Callback invoked for an incoming message on a [Jobs notify-pending](@ref jobs_function_setnotifypendingcallback) topic. */ - AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK /**< Callback invoked for an incoming message on a [Jobs notify-next](@ref jobs_function_setnotifynextcallback) topic. */ + AWS_IOT_JOBS_GET_PENDING_COMPLETE = 0, /**< Callback invoked because a [Jobs get pending](@ref jobs_function_getpending) completed. */ + AWS_IOT_JOBS_START_NEXT_COMPLETE = 1, /**< Callback invoked because a [Jobs start next](@ref jobs_function_startnext) completed. */ + AWS_IOT_JOBS_DESCRIBE_COMPLETE = 2, /**< Callback invoked because a [Jobs describe](@ref jobs_function_describe) completed. */ + AWS_IOT_JOBS_UPDATE_COMPLETE = 3, /**< Callback invoked because a [Jobs update](@ref jobs_function_update) completed. */ + AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK = 4, /**< Callback invoked for an incoming message on a [Jobs notify-pending](@ref jobs_function_setnotifypendingcallback) topic. */ + AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK = 5 /**< Callback invoked for an incoming message on a [Jobs notify-next](@ref jobs_function_setnotifynextcallback) topic. */ } AwsIotJobsCallbackType_t; /*-------------------------- Jobs parameter structs -------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index cc6eb348cc..76aa099473 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -397,6 +397,36 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + uint32_t timeout, + AwsIotJobsResponse_t * const pJobsResponse ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_JOBS_FLAG_WAITABLE; + + /* Call the asynchronous Jobs Get Pending function. */ + status = AwsIotJobs_GetPending( pRequestInfo, + flags, + NULL, + &getPendingOperation ); + + /* Wait for the Jobs Get Pending operation to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + status = AwsIotJobs_Wait( getPendingOperation, + timeout, + pJobsResponse ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, uint32_t flags, diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index 34422d14c2..948a31dd49 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -62,6 +62,33 @@ /*-----------------------------------------------------------*/ +/** + * @brief First parameter to #_jobsOperation_match. + */ +typedef struct _operationMatchParams +{ + _jobsOperationType_t type; /**< @brief GET PENDING, START NEXT, DESCRIBE, or UPDATE. */ + const char * pThingName; /**< @brief Thing Name of Jobs operation. */ + size_t thingNameLength; /**< @brief Length of #_operationMatchParams_t.pThingName. */ + const char * pResponse; /**< @brief Jobs response document. */ + size_t responseLength; /**< @brief Length of #_operationMatchParams_t.pResponse. */ +} _operationMatchParams_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Match a received Jobs response with a Jobs operation awaiting a + * response. + * + * @param[in] pOperationLink Pointer to the link member of the #_jobsOperation_t + * to check. + * @param[in] pMatch Pointer to an #_operationMatchParams_t. + * + * @return `true` if `pMatch` matches the received response; `false` otherwise. + */ +static bool _jobsOperation_match( const IotLink_t * pOperationLink, + void * pMatch ); + /** * @brief Invoked when a Jobs response is received for Jobs GET PENDING. * @@ -98,6 +125,26 @@ static void _describeCallback( void * pArgument, static void _updateCallback( void * pArgument, IotMqttCallbackParam_t * pMessage ); +/** + * @brief Common function for processing received Jobs responses. + * + * @param[in] type GET PENDING, START NEXT, DESCRIBE, or UPDATE. + * @param[in] pMessage Received Jobs response (as an MQTT PUBLISH message). + */ +static void _commonOperationCallback( _jobsOperationType_t type, + IotMqttCallbackParam_t * pMessage ); + +/** + * @brief Notify of a completed Jobs operation. + * + * @param[in] pOperation The operation which completed. + * + * Depending on the parameters passed to a user-facing Jobs function, the + * notification will cause @ref jobs_function_wait to return or invoke a + * user-provided callback. + */ +static void _notifyCompletion( _jobsOperation_t * pOperation ); + /** * @brief Get a Jobs subscription to use with a Jobs operation. * @@ -134,11 +181,67 @@ IotMutex_t _AwsIotJobsPendingOperationsMutex; /*-----------------------------------------------------------*/ +static bool _jobsOperation_match( const IotLink_t * pOperationLink, + void * pMatch ) +{ + /* Because this function is called from a container function, the given link + * must never be NULL. */ + AwsIotJobs_Assert( pOperationLink != NULL ); + + _jobsOperation_t * pOperation = IotLink_Container( _jobsOperation_t, + pOperationLink, + link ); + _operationMatchParams_t * pParam = ( _operationMatchParams_t * ) pMatch; + _jobsSubscription_t * pSubscription = pOperation->pSubscription; + const char * pClientToken = NULL; + size_t clientTokenLength = 0; + + /* Check for matching Thing Name and operation type. */ + bool match = ( pOperation->type == pParam->type ) && + ( pParam->thingNameLength == pSubscription->thingNameLength ) && + ( strncmp( pParam->pThingName, + pSubscription->pThingName, + pParam->thingNameLength ) == 0 ); + + if( match == true ) + { + IotLogDebug( "Verifying client tokens for Jobs %s.", + _pAwsIotJobsOperationNames[ pParam->type ] ); + + /* Check the response for a client token. */ + match = AwsIot_GetClientToken( pParam->pResponse, + pParam->responseLength, + &pClientToken, + &clientTokenLength ); + + /* If the response contains a client token, check for a match. */ + if( match == true ) + { + match = ( pOperation->clientTokenLength == clientTokenLength ) && + ( strncmp( pOperation->pClientToken, pClientToken, clientTokenLength ) == 0 ); + } + else + { + IotLogWarn( "Received a Jobs %s response with no client token. " + "This is possibly a response to a bad JSON document:\r\n%.*s", + _pAwsIotJobsOperationNames[ pParam->type ], + pParam->responseLength, + pParam->pResponse ); + } + } + + return match; +} + +/*-----------------------------------------------------------*/ + static void _getPendingCallback( void * pArgument, IotMqttCallbackParam_t * pMessage ) { /* Silence warnings about unused parameter. */ ( void ) pArgument; + + _commonOperationCallback( JOBS_GET_PENDING, pMessage ); } /*-----------------------------------------------------------*/ @@ -148,6 +251,8 @@ static void _startNextCallback( void * pArgument, { /* Silence warnings about unused parameter. */ ( void ) pArgument; + + _commonOperationCallback( JOBS_START_NEXT, pMessage ); } /*-----------------------------------------------------------*/ @@ -157,6 +262,8 @@ static void _describeCallback( void * pArgument, { /* Silence warnings about unused parameter. */ ( void ) pArgument; + + _commonOperationCallback( JOBS_DESCRIBE, pMessage ); } /*-----------------------------------------------------------*/ @@ -166,6 +273,168 @@ static void _updateCallback( void * pArgument, { /* Silence warnings about unused parameter. */ ( void ) pArgument; + + _commonOperationCallback( JOBS_UPDATE, pMessage ); +} + +/*-----------------------------------------------------------*/ + +static void _commonOperationCallback( _jobsOperationType_t type, + IotMqttCallbackParam_t * pMessage ) +{ + _jobsOperation_t * pOperation = NULL; + IotLink_t * pOperationLink = NULL; + _operationMatchParams_t param = { 0 }; + AwsIotStatus_t status = AWS_IOT_UNKNOWN; + uint32_t flags = 0; + + /* Set operation type and response. */ + param.type = type; + param.pResponse = pMessage->u.message.info.pPayload; + param.responseLength = pMessage->u.message.info.payloadLength; + + /* Parse the Thing Name from the MQTT topic name. */ + if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength, + &( param.pThingName ), + &( param.thingNameLength ) ) == false ) + { + IOT_GOTO_CLEANUP(); + } + + /* Lock the pending operations list for exclusive access. */ + IotMutex_Lock( &( _AwsIotJobsPendingOperationsMutex ) ); + + /* Search for a matching pending operation. */ + pOperationLink = IotListDouble_FindFirstMatch( &_AwsIotJobsPendingOperations, + NULL, + _jobsOperation_match, + ¶m ); + + /* Find and remove the first Jobs operation of the given type. */ + if( pOperationLink == NULL ) + { + /* Operation is not pending. It may have already been processed. Return + * without doing anything */ + IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); + + IotLogWarn( "Jobs %s callback received an unknown operation.", + _pAwsIotJobsOperationNames[ type ] ); + + IOT_GOTO_CLEANUP(); + } + else + { + pOperation = IotLink_Container( _jobsOperation_t, pOperationLink, link ); + + /* Copy the flags from the Jobs operation. */ + flags = pOperation->flags; + + /* Remove a non-waitable operation from the pending operation list. + * Waitable operations are removed by the Wait function. */ + if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 ) + { + IotListDouble_Remove( &( pOperation->link ) ); + IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); + } + } + + /* Parse the status from the topic name. */ + status = AwsIot_ParseStatus( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength ); + + switch( status ) + { + case AWS_IOT_ACCEPTED: + case AWS_IOT_REJECTED: + _AwsIotJobs_ParseResponse( status, + pMessage->u.message.info.pPayload, + pMessage->u.message.info.payloadLength, + pOperation ); + break; + + default: + IotLogWarn( "Unknown status for %s of %.*s Jobs. Ignoring message.", + _pAwsIotJobsOperationNames[ type ], + pOperation->pSubscription->thingNameLength, + pOperation->pSubscription->pThingName ); + + pOperation->status = AWS_IOT_JOBS_BAD_RESPONSE; + + break; + } + + _notifyCompletion( pOperation ); + + /* For waitable operations, unlock the pending operation list mutex to allow + * the Wait function to run. */ + if( ( flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) + { + IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); + } + + /* This function has no return value and no cleanup, but uses the cleanup + * label to exit on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); +} + +/*-----------------------------------------------------------*/ + +static void _notifyCompletion( _jobsOperation_t * pOperation ) +{ + AwsIotJobsCallbackParam_t callbackParam = { .callbackType = ( AwsIotJobsCallbackType_t ) 0 }; + _jobsSubscription_t * pSubscription = pOperation->pSubscription, + * pRemovedSubscription = NULL; + + /* If the operation is waiting, post to its wait semaphore and return. */ + if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == AWS_IOT_JOBS_FLAG_WAITABLE ) + { + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + } + else + { + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + _AwsIotJobs_DecrementReferences( pOperation, + pSubscription->pTopicBuffer, + &pRemovedSubscription ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + + /* Set the subscription pointer used for the user callback based on whether + * a subscription was removed from the list. */ + if( pRemovedSubscription != NULL ) + { + pSubscription = pRemovedSubscription; + } + + AwsIotJobs_Assert( pSubscription != NULL ); + + /* Invoke the user callback if provided. */ + if( pOperation->notify.callback.function != NULL ) + { + /* Set the common members of the callback parameter. */ + callbackParam.callbackType = ( AwsIotJobsCallbackType_t ) pOperation->type; + callbackParam.mqttConnection = pOperation->mqttConnection; + callbackParam.pThingName = pSubscription->pThingName; + callbackParam.thingNameLength = pSubscription->thingNameLength; + callbackParam.u.operation.result = pOperation->status; + callbackParam.u.operation.reference = pOperation; + callbackParam.u.operation.pResponse = pOperation->pJobsResponse; + callbackParam.u.operation.responseLength = pOperation->jobsResponseLength; + + pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, + &callbackParam ); + } + + /* Destroy a removed subscription. */ + if( pRemovedSubscription != NULL ) + { + _AwsIotJobs_DestroySubscription( pRemovedSubscription ); + } + + _AwsIotJobs_DestroyOperation( pOperation ); + } } /*-----------------------------------------------------------*/ @@ -293,6 +562,7 @@ AwsIotJobsError_t _AwsIotJobs_CreateOperation( _jobsOperationType_t type, pOperation->type = type; pOperation->flags = flags; pOperation->status = AWS_IOT_JOBS_STATUS_PENDING; + pOperation->mallocResponse = pRequestInfo->mallocResponse; /* Save the Job ID for DESCRIBE and UPDATE operations. */ if( ( type == JOBS_DESCRIBE ) || ( type == JOBS_UPDATE ) ) diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index d013e9ca5f..d5c3479308 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -303,6 +303,18 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * const AwsIotJobsUpdateInfo_t * pUpdateInfo, _jobsOperation_t * pOperation ); +/** + * @brief Parse an error from a Jobs error document. + * + * @param[in] pErrorDocument Jobs error document. + * @param[in] errorDocumentLength Length of `pErrorDocument`. + * + * @return A Jobs error code between #AWS_IOT_JOBS_INVALID_TOPIC and + * #AWS_IOT_JOBS_TERMINAL_STATE. + */ +static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, + size_t errorDocumentLength ); + /*-----------------------------------------------------------*/ static size_t _appendFlag( char * pBuffer, @@ -953,6 +965,13 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * /*-----------------------------------------------------------*/ +static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, + size_t errorDocumentLength ) +{ +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, const _jsonRequestContents_t * pRequestContents, @@ -994,3 +1013,62 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJsonRequest( _jobsOperationType_t type, } /*-----------------------------------------------------------*/ + +void _AwsIotJobs_ParseResponse( AwsIotStatus_t status, + const char * pResponse, + size_t responseLength, + _jobsOperation_t * pOperation ) +{ + AwsIotJobs_Assert( pOperation->status == AWS_IOT_JOBS_STATUS_PENDING ); + + /* A non-waitable operation can re-use the pointers from the publish info, + * since those are guaranteed to be in-scope throughout the user callback. + * But a waitable operation must copy the data from the publish info because + * AwsIotJobs_Wait may be called after the MQTT library frees the publish + * info. */ + if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_WAITABLE ) == 0 ) + { + pOperation->pJobsResponse = pResponse; + pOperation->jobsResponseLength = responseLength; + } + else + { + IotLogDebug( "Allocating new buffer for waitable Jobs %s.", + _pAwsIotJobsOperationNames[ pOperation->type ] ); + + /* Parameter validation should not have allowed a NULL malloc function. */ + AwsIotJobs_Assert( pOperation->mallocResponse != NULL ); + + /* Allocate a buffer for the retrieved document. */ + pOperation->pJobsResponse = pOperation->mallocResponse( responseLength ); + + if( pOperation->pJobsResponse == NULL ) + { + IotLogError( "Failed to allocate buffer for retrieved Jobs %s response.", + _pAwsIotJobsOperationNames[ pOperation->type ] ); + + pOperation->status = AWS_IOT_JOBS_NO_MEMORY; + } + else + { + /* Copy the response. */ + ( void ) memcpy( ( void * ) pOperation->pJobsResponse, pResponse, responseLength ); + pOperation->jobsResponseLength = responseLength; + } + } + + /* Set the status of the Jobs operation. */ + if( pOperation->status == AWS_IOT_JOBS_STATUS_PENDING ) + { + if( status == AWS_IOT_ACCEPTED ) + { + pOperation->status = AWS_IOT_JOBS_SUCCESS; + } + else + { + pOperation->status = _parseErrorDocument( pResponse, responseLength ); + } + } +} + +/*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index 11f12ab97a..d7129fa9f0 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -122,6 +122,17 @@ static AwsIotShadowError_t _processAcceptedGet( _shadowOperation_t * pOperation, static void _updateCallback( void * pArgument, IotMqttCallbackParam_t * pMessage ); +/** + * @brief Notify of a completed Shadow operation. + * + * @param[in] pOperation The operation which completed. + * + * Depending on the parameters passed to a user-facing Shadow function, the + * notification will cause @ref shadow_function_wait to return or invoke a + * user-provided callback. + */ +static void _notifyCompletion( _shadowOperation_t * pOperation ); + /** * @brief Get a Shadow subscription to use with a Shadow operation. * @@ -226,7 +237,7 @@ static bool _shadowOperation_match( const IotLink_t * pOperationLink, else { IotLogWarn( "Received a Shadow UPDATE response with no client token. " - "This is possibly a response to a bad JSON document:\n%.*s", + "This is possibly a response to a bad JSON document:\r\n%.*s", pParam->documentLength, pParam->pDocument ); } @@ -357,7 +368,7 @@ static void _commonOperationCallback( _shadowOperationType_t type, flags = pOperation->flags; /* Notify of operation completion. */ - _AwsIotShadow_Notify( pOperation ); + _notifyCompletion( pOperation ); /* For waitable operations, unlock the pending operation list mutex to allow * the Wait function to run. */ @@ -452,6 +463,70 @@ static void _updateCallback( void * pArgument, /*-----------------------------------------------------------*/ +static void _notifyCompletion( _shadowOperation_t * pOperation ) +{ + AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; + _shadowSubscription_t * pSubscription = pOperation->pSubscription, + * pRemovedSubscription = NULL; + + /* If the operation is waiting, post to its wait semaphore and return. */ + if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) + { + IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); + } + else + { + /* Decrement the reference count. This also removes subscriptions if the + * count reaches 0. */ + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + _AwsIotShadow_DecrementReferences( pOperation, + pSubscription->pTopicBuffer, + &pRemovedSubscription ); + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + + /* Set the subscription pointer used for the user callback based on whether + * a subscription was removed from the list. */ + if( pRemovedSubscription != NULL ) + { + pSubscription = pRemovedSubscription; + } + + AwsIotShadow_Assert( pSubscription != NULL ); + + /* Invoke the user callback if provided. */ + if( pOperation->notify.callback.function != NULL ) + { + /* Set the common members of the callback parameter. */ + callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; + callbackParam.mqttConnection = pOperation->mqttConnection; + callbackParam.u.operation.result = pOperation->status; + callbackParam.u.operation.reference = pOperation; + callbackParam.pThingName = pSubscription->pThingName; + callbackParam.thingNameLength = pSubscription->thingNameLength; + + /* Set the members of the callback parameter for a received document. */ + if( pOperation->type == SHADOW_GET ) + { + callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; + callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; + } + + pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, + &callbackParam ); + } + + /* Destroy a removed subscription. */ + if( pRemovedSubscription != NULL ) + { + _AwsIotShadow_DestroySubscription( pRemovedSubscription ); + } + + _AwsIotShadow_DestroyOperation( pOperation ); + } +} + +/*-----------------------------------------------------------*/ + static AwsIotShadowError_t _findSubscription( const char * pThingName, size_t thingNameLength, char * pTopicBuffer, @@ -853,67 +928,3 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn } /*-----------------------------------------------------------*/ - -void _AwsIotShadow_Notify( _shadowOperation_t * pOperation ) -{ - AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; - _shadowSubscription_t * pSubscription = pOperation->pSubscription, - * pRemovedSubscription = NULL; - - /* If the operation is waiting, post to its wait semaphore and return. */ - if( ( pOperation->flags & AWS_IOT_SHADOW_FLAG_WAITABLE ) == AWS_IOT_SHADOW_FLAG_WAITABLE ) - { - IotSemaphore_Post( &( pOperation->notify.waitSemaphore ) ); - } - else - { - /* Decrement the reference count. This also removes subscriptions if the - * count reaches 0. */ - IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); - _AwsIotShadow_DecrementReferences( pOperation, - pSubscription->pTopicBuffer, - &pRemovedSubscription ); - IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); - - /* Set the subscription pointer used for the user callback based on whether - * a subscription was removed from the list. */ - if( pRemovedSubscription != NULL ) - { - pSubscription = pRemovedSubscription; - } - - AwsIotShadow_Assert( pSubscription != NULL ); - - /* Invoke the user callback if provided. */ - if( pOperation->notify.callback.function != NULL ) - { - /* Set the common members of the callback parameter. */ - callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) pOperation->type; - callbackParam.mqttConnection = pOperation->mqttConnection; - callbackParam.u.operation.result = pOperation->status; - callbackParam.u.operation.reference = pOperation; - callbackParam.pThingName = pSubscription->pThingName; - callbackParam.thingNameLength = pSubscription->thingNameLength; - - /* Set the members of the callback parameter for a received document. */ - if( pOperation->type == SHADOW_GET ) - { - callbackParam.u.operation.get.pDocument = pOperation->u.get.pDocument; - callbackParam.u.operation.get.documentLength = pOperation->u.get.documentLength; - } - - pOperation->notify.callback.function( pOperation->notify.callback.pCallbackContext, - &callbackParam ); - } - - /* Destroy a removed subscription. */ - if( pRemovedSubscription != NULL ) - { - _AwsIotShadow_DestroySubscription( pRemovedSubscription ); - } - - _AwsIotShadow_DestroyOperation( pOperation ); - } -} - -/*-----------------------------------------------------------*/ diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index b30e34bfbe..f6a943aacc 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -7,7 +7,7 @@ set -ev # Build tests and demos against AWS IoT with coverage. -cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_JOBS_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" make -j2 # Run common tests with code coverage. diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh index a3a078da23..6ee89ba134 100644 --- a/scripts/ci_test_jobs.sh +++ b/scripts/ci_test_jobs.sh @@ -5,19 +5,31 @@ # Exit on any nonzero return code. set -e +# Function for running the existing test executables. +run_tests() { + # For commit builds, run the full Jobs tests. For pull request builds, + # run only the unit tests (credentials are not available for pull request builds). + if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + ./bin/aws_iot_tests_jobs + else + # Run only Jobs unit tests. + ./bin/aws_iot_tests_jobs -n + fi +} + # CMake compiler flags for building Jobs. -CMAKE_FLAGS="$COMPILER_OPTIONS" +CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_JOBS_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS" make -j2 aws_iot_tests_jobs # Run tests. -./bin/aws_iot_tests_jobs +run_tests # Rebuild in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" make -j2 aws_iot_tests_jobs # Run tests in static memory mode. -./bin/aws_iot_tests_jobs +run_tests diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 350252f646..d568f95265 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -215,6 +215,38 @@ TEST_TEAR_DOWN( Jobs_System ) */ TEST_GROUP_RUNNER( Jobs_System ) { + RUN_TEST_CASE( Jobs_System, GetPendingBlocking ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Retrieves a list of Jobs using @ref jobs_function_timedgetpending. + */ +TEST( Jobs_System, GetPendingBlocking ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + + requestInfo.mqttConnection = _mqttConnection; + requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; + requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + requestInfo.mallocResponse = IotTest_Malloc; + + /* Get pending Jobs. */ + status = AwsIotJobs_TimedGetPending( &requestInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check the Jobs response. */ + TEST_ASSERT_NOT_NULL( jobsResponse.pJobsResponse ); + TEST_ASSERT_GREATER_THAN( 0, jobsResponse.jobsResponseLength ); + + /* Free the allocated Jobs response. */ + IotTest_Free( ( void * ) jobsResponse.pJobsResponse ); } /*-----------------------------------------------------------*/ From f8d17adcd26257964f8032866222d7b4b13a1ccd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 26 Jun 2019 11:31:12 -0400 Subject: [PATCH 211/844] Add Jobs GET PENDING async test (#498) --- tests/jobs/system/aws_iot_tests_jobs_system.c | 99 ++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index d568f95265..4d0a42ae88 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -38,12 +38,13 @@ /* Platform layer includes. */ #include "platform/iot_clock.h" +#include "platform/iot_threads.h" /* MQTT include. */ #include "iot_mqtt.h" /* Jobs include. */ -#include "aws_iot_jobs.h" +#include "private/aws_iot_jobs_internal.h" /* Test network header include. */ #include IOT_TEST_NETWORK_HEADER @@ -72,6 +73,26 @@ #error "Please define AWS_IOT_TEST_JOBS_THING_NAME." #endif +/* Require Jobs library asserts to be enabled for these tests. The Jobs + * assert function is used to abort the tests on failure from the Jobs operation + * complete callback. */ +#if AWS_IOT_JOBS_ENABLE_ASSERTS == 0 + #error "Jobs system tests require AWS_IOT_JOBS_ENABLE_ASSERTS to be 1." +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Parameter 1 of #_operationComplete. + */ +typedef struct _operationCompleteParams +{ + AwsIotJobsCallbackType_t expectedType; /**< @brief Expected callback type. */ + AwsIotJobsError_t expectedResult; /**< @brief Expected operation result. */ + IotSemaphore_t waitSem; /**< @brief Used to unblock waiting test thread. */ + AwsIotJobsOperation_t operation; /**< @brief Reference to expected completed operation. */ +} _operationCompleteParams_t; + /*-----------------------------------------------------------*/ /** @@ -98,6 +119,35 @@ static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*-----------------------------------------------------------*/ +/** + * @brief Jobs operation completion callback function. Checks parameters + * and unblocks the main test thread. + */ +static void _operationComplete( void * pArgument, + AwsIotJobsCallbackParam_t * pOperation ) +{ + _operationCompleteParams_t * pParams = ( _operationCompleteParams_t * ) pArgument; + + /* Check parameters against expected values. */ + AwsIotJobs_Assert( pParams->expectedType == pOperation->callbackType ); + AwsIotJobs_Assert( pParams->operation == pOperation->u.operation.reference ); + AwsIotJobs_Assert( pParams->expectedResult == pOperation->u.operation.result ); + + AwsIotJobs_Assert( pOperation->mqttConnection == _mqttConnection ); + AwsIotJobs_Assert( pOperation->thingNameLength == sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + AwsIotJobs_Assert( strncmp( pOperation->pThingName, + AWS_IOT_TEST_JOBS_THING_NAME, + pOperation->thingNameLength ) == 0 ); + + AwsIotJobs_Assert( pOperation->u.operation.pResponse != NULL ); + AwsIotJobs_Assert( pOperation->u.operation.responseLength > 0 ); + + /* Unblock the main test thead. */ + IotSemaphore_Post( &( pParams->waitSem ) ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs system tests. */ @@ -215,11 +265,57 @@ TEST_TEAR_DOWN( Jobs_System ) */ TEST_GROUP_RUNNER( Jobs_System ) { + RUN_TEST_CASE( Jobs_System, GetPendingAsync ); RUN_TEST_CASE( Jobs_System, GetPendingBlocking ); } /*-----------------------------------------------------------*/ +/** + * @brief Retrieves a list of Jobs using @ref jobs_function_timedgetpending. + */ +TEST( Jobs_System, GetPendingAsync ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + _operationCompleteParams_t callbackParam = { .expectedType = ( AwsIotJobsCallbackType_t ) 0 }; + + /* Initialize the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the callback information. */ + callbackParam.expectedType = AWS_IOT_JOBS_GET_PENDING_COMPLETE; + callbackParam.expectedResult = AWS_IOT_JOBS_SUCCESS; + callbackInfo.function = _operationComplete; + callbackInfo.pCallbackContext = &callbackParam; + + /* Set the Jobs request parameters. */ + requestInfo.mqttConnection = _mqttConnection; + requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; + requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + + /* Get pending Jobs. */ + status = AwsIotJobs_GetPending( &requestInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_STATUS_PENDING, status ); + + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for pending Jobs." ); + } + } + + IotSemaphore_Destroy( &( callbackParam.waitSem ) ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Retrieves a list of Jobs using @ref jobs_function_timedgetpending. */ @@ -229,6 +325,7 @@ TEST( Jobs_System, GetPendingBlocking ) AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + /* Set the Jobs request parameters. */ requestInfo.mqttConnection = _mqttConnection; requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); From 9660e27f9d92ab11be9ffc983c4fe473f88c2847 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 26 Jun 2019 11:47:57 -0400 Subject: [PATCH 212/844] Add Jobs error response parsing (#499) --- lib/source/jobs/aws_iot_jobs_serialize.c | 123 +++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index d5c3479308..f46cc86570 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -37,6 +37,9 @@ /* Error handling include. */ #include "private/iot_error.h" +/* JSON utilities include. */ +#include "iot_json_utils.h" + /** * @brief Minimum length of a Jobs request. * @@ -143,6 +146,16 @@ */ #define EXECUTION_NUMBER_STRING_LENGTH ( 12 ) +/** + * @brief JSON key representing Jobs error code in error responses. + */ +#define CODE_KEY "code" + +/** + * @brief Length of #CODE_KEY. + */ +#define CODE_KEY_LENGTH ( sizeof( CODE_KEY ) - 1 ) + /** * @brief Append a string to a buffer. * @@ -968,6 +981,116 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, size_t errorDocumentLength ) { + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + const char * pCode = NULL; + size_t codeLength = 0; + + /* Find the error code. */ + if( IotJsonUtils_FindJsonValue( pErrorDocument, + errorDocumentLength, + CODE_KEY, + CODE_KEY_LENGTH, + &pCode, + &codeLength ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_RESPONSE ); + } + + /* Match the JSON error code to a Jobs return value. Assume invalid status + * unless matched.*/ + status = AWS_IOT_JOBS_BAD_RESPONSE; + + switch( codeLength ) + { + /* InvalidJson */ + case 13: + + if( strncmp( "\"InvalidJson\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_JSON; + } + + break; + + /* InvalidTopic */ + case 14: + + if( strncmp( "\"InvalidTopic\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_TOPIC; + } + + break; + + /* InternalError */ + case 15: + + if( strncmp( "\"InternalError\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INTERNAL_ERROR; + } + + break; + + /* InvalidRequest */ + case 16: + + if( strncmp( "\"InvalidRequest\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_REQUEST; + } + + break; + + /* VersionMismatch */ + case 17: + + if( strncmp( "\"VersionMismatch\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_VERSION_MISMATCH; + } + + break; + + /* ResourceNotFound, RequestThrottled */ + case 18: + + if( strncmp( "\"ResourceNotFound\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_NOT_FOUND; + } + else if( strncmp( "\"RequestThrottled\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_THROTTLED; + } + + break; + + /* TerminalStateReached */ + case 22: + + if( strncmp( "\"TerminalStateReached\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_TERMINAL_STATE; + } + + break; + + /* InvalidStateTransition */ + case 24: + + if( strncmp( "\"InvalidStateTransition\"", pCode, codeLength ) == 0 ) + { + status = AWS_IOT_JOBS_INVALID_STATE; + } + + break; + + default: + break; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ From 3139d34e4ca70b35e04707b5bcbfad47a101ec58 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 29 Jun 2019 12:10:36 +0800 Subject: [PATCH 213/844] Don't always include step timeout in Jobs request (#501) --- lib/include/types/aws_iot_jobs_types.h | 14 ++- lib/source/jobs/aws_iot_jobs_api.c | 3 +- lib/source/jobs/aws_iot_jobs_serialize.c | 106 +++++++++------- tests/jobs/system/aws_iot_tests_jobs_system.c | 117 ++++++++++++------ tests/jobs/unit/aws_iot_tests_jobs_api.c | 4 +- .../jobs/unit/aws_iot_tests_jobs_serialize.c | 87 ++++++++++--- 6 files changed, 227 insertions(+), 104 deletions(-) diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index c9d52f4ef3..922912bbaf 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -746,7 +746,19 @@ typedef struct AwsIotJobsUpdateInfo * @note The value of this constant may change at any time in future versions, but * its name will remain the same. */ -#define AWS_IOT_JOBS_NO_TIMEOUT ( -1 ) +#define AWS_IOT_JOBS_NO_TIMEOUT ( 0 ) + +/** + * @brief Set #AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes to this value to cancel + * any previously set step timeout. + * + * The Jobs service will return an (InvalidRequest)[@ref AWS_IOT_JOBS_INVALID_REQUEST] + * error if this value is used without an existing step timeout. + * + * @note The value of this constant may change at any time in future versions, but + * its name will remain the same. + */ +#define AWS_IOT_JOBS_CANCEL_TIMEOUT ( -1 ) /** * @brief Set #AwsIotJobsUpdateInfo_t.pStatusDetails to this value to omit the diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 76aa099473..12e82eb257 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -237,7 +237,8 @@ static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, } /* Check that step timeout is valid. */ - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + if( ( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) && + ( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_CANCEL_TIMEOUT ) ) { if( pUpdateInfo->stepTimeoutInMinutes < 1 ) { diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index f46cc86570..acf2c92b92 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -541,28 +541,31 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ requestLength += pUpdateInfo->statusDetailsLength; } - /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ - requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; - if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) { - /* Convert the step timeout to a string. */ - stepTimeoutLength = snprintf( pStepTimeout, - STEP_TIMEOUT_STRING_LENGTH, - "%d", - pUpdateInfo->stepTimeoutInMinutes ); - AwsIotJobs_Assert( stepTimeoutLength > 0 ); - AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); - } - else - { - /* Step timeout will be set to -1. */ - pStepTimeout[ 0 ] = '-'; - pStepTimeout[ 1 ] = '1'; - stepTimeoutLength = 2; - } + /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ + requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; + + if( pUpdateInfo->stepTimeoutInMinutes == AWS_IOT_JOBS_CANCEL_TIMEOUT ) + { + /* Step timeout will be set to -1. */ + pStepTimeout[ 0 ] = '-'; + pStepTimeout[ 1 ] = '1'; + stepTimeoutLength = 2; + } + else + { + /* Convert the step timeout to a string. */ + stepTimeoutLength = snprintf( pStepTimeout, + STEP_TIMEOUT_STRING_LENGTH, + "%d", + pUpdateInfo->stepTimeoutInMinutes ); + AwsIotJobs_Assert( stepTimeoutLength > 0 ); + AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); + } - requestLength += ( size_t ) stepTimeoutLength; + requestLength += ( size_t ) stepTimeoutLength; + } /* Add the length of the client token. */ if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) @@ -601,11 +604,14 @@ static AwsIotJobsError_t _generateStartNextRequest( const AwsIotJobsRequestInfo_ pUpdateInfo->statusDetailsLength ); } - /* Add step timeout. */ - copyOffset = _appendStepTimeout( pJobsRequest, - copyOffset, - pStepTimeout, - stepTimeoutLength ); + /* Add step timeout if present. */ + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + copyOffset = _appendStepTimeout( pJobsRequest, + copyOffset, + pStepTimeout, + stepTimeoutLength ); + } /* Add client token. */ copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); @@ -845,28 +851,32 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * requestLength += 4; } - /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ - requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; - + /* Add the step timeout if provided. */ if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) { - /* Convert the step timeout to a string. */ - stepTimeoutLength = snprintf( pStepTimeout, - STEP_TIMEOUT_STRING_LENGTH, - "%d", - pUpdateInfo->stepTimeoutInMinutes ); - AwsIotJobs_Assert( stepTimeoutLength > 0 ); - AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); - } - else - { - /* Step timeout will be set to -1. */ - pStepTimeout[ 0 ] = '-'; - pStepTimeout[ 1 ] = '1'; - stepTimeoutLength = 2; - } + /* Calculate the length of the step timeout. Add 4 for the 2 quotes, colon, and comma. */ + requestLength += STEP_TIMEOUT_KEY_LENGTH + 4; + + if( pUpdateInfo->stepTimeoutInMinutes == AWS_IOT_JOBS_CANCEL_TIMEOUT ) + { + /* Step timeout will be set to -1. */ + pStepTimeout[ 0 ] = '-'; + pStepTimeout[ 1 ] = '1'; + stepTimeoutLength = 2; + } + else + { + /* Convert the step timeout to a string. */ + stepTimeoutLength = snprintf( pStepTimeout, + STEP_TIMEOUT_STRING_LENGTH, + "%d", + pUpdateInfo->stepTimeoutInMinutes ); + AwsIotJobs_Assert( stepTimeoutLength > 0 ); + AwsIotJobs_Assert( stepTimeoutLength < STEP_TIMEOUT_STRING_LENGTH ); + } - requestLength += ( size_t ) stepTimeoutLength; + requestLength += ( size_t ) stepTimeoutLength; + } /* Add the length of the client token. */ if( pRequestInfo->pClientToken != AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE ) @@ -951,8 +961,14 @@ static AwsIotJobsError_t _generateUpdateRequest( const AwsIotJobsRequestInfo_t * true ); } - /* Add step timeout. */ - copyOffset = _appendStepTimeout( pJobsRequest, copyOffset, pStepTimeout, stepTimeoutLength ); + /* Add step timeout if provided. */ + if( pUpdateInfo->stepTimeoutInMinutes != AWS_IOT_JOBS_NO_TIMEOUT ) + { + copyOffset = _appendStepTimeout( pJobsRequest, + copyOffset, + pStepTimeout, + stepTimeoutLength ); + } /* Add the client token. */ copyOffset = _appendClientToken( pJobsRequest, copyOffset, pRequestInfo, pOperation ); diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 4d0a42ae88..23a48cd4e0 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -148,6 +148,77 @@ static void _operationComplete( void * pArgument, /*-----------------------------------------------------------*/ +/** + * @brief Common code of the Jobs Async tests. + */ +static void _jobsAsyncTest( _jobsOperationType_t type, + AwsIotJobsError_t expectedResult ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + _operationCompleteParams_t callbackParam = { .expectedType = ( AwsIotJobsCallbackType_t ) 0 }; + + /* Initialize the wait semaphore. */ + TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); + + if( TEST_PROTECT() ) + { + /* Set the callback information. */ + callbackParam.expectedType = ( AwsIotJobsCallbackType_t ) type; + callbackParam.expectedResult = expectedResult; + callbackInfo.function = _operationComplete; + callbackInfo.pCallbackContext = &callbackParam; + + /* Set the Jobs request parameters. */ + requestInfo.mqttConnection = _mqttConnection; + requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; + requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + + /* Call Jobs function. */ + switch( type ) + { + case JOBS_GET_PENDING: + status = AwsIotJobs_GetPending( &requestInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); + break; + + case JOBS_START_NEXT: + status = AwsIotJobs_StartNext( &requestInfo, + &updateInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); + break; + + case JOBS_DESCRIBE: + break; + + default: + /* The only remaining valid type is UPDATE. */ + TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); + break; + } + + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_STATUS_PENDING, status ); + + if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), + AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for pending Jobs." ); + } + } + + IotSemaphore_Destroy( &( callbackParam.waitSem ) ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs system tests. */ @@ -267,6 +338,8 @@ TEST_GROUP_RUNNER( Jobs_System ) { RUN_TEST_CASE( Jobs_System, GetPendingAsync ); RUN_TEST_CASE( Jobs_System, GetPendingBlocking ); + + RUN_TEST_CASE( Jobs_System, StartNextAsync ); } /*-----------------------------------------------------------*/ @@ -276,42 +349,7 @@ TEST_GROUP_RUNNER( Jobs_System ) */ TEST( Jobs_System, GetPendingAsync ) { - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; - _operationCompleteParams_t callbackParam = { .expectedType = ( AwsIotJobsCallbackType_t ) 0 }; - - /* Initialize the wait semaphore. */ - TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( callbackParam.waitSem ), 0, 1 ) ); - - if( TEST_PROTECT() ) - { - /* Set the callback information. */ - callbackParam.expectedType = AWS_IOT_JOBS_GET_PENDING_COMPLETE; - callbackParam.expectedResult = AWS_IOT_JOBS_SUCCESS; - callbackInfo.function = _operationComplete; - callbackInfo.pCallbackContext = &callbackParam; - - /* Set the Jobs request parameters. */ - requestInfo.mqttConnection = _mqttConnection; - requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; - requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - - /* Get pending Jobs. */ - status = AwsIotJobs_GetPending( &requestInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_STATUS_PENDING, status ); - - if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), - AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) - { - TEST_FAIL_MESSAGE( "Timed out waiting for pending Jobs." ); - } - } - - IotSemaphore_Destroy( &( callbackParam.waitSem ) ); + _jobsAsyncTest( JOBS_GET_PENDING, AWS_IOT_JOBS_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -347,3 +385,10 @@ TEST( Jobs_System, GetPendingBlocking ) } /*-----------------------------------------------------------*/ + +TEST( Jobs_System, StartNextAsync ) +{ + _jobsAsyncTest( JOBS_START_NEXT, AWS_IOT_JOBS_SUCCESS ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index b46e23df06..af2b2bf6f7 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -443,8 +443,8 @@ TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; requestInfo.mallocResponse = IotTest_Malloc; - /* Step timeout too small. */ - updateInfo.stepTimeoutInMinutes = 0; + /* Negative, invalid step timeout. */ + updateInfo.stepTimeoutInMinutes = -5; status = AwsIotJobs_StartNext( &requestInfo, &updateInfo, 0, diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index b9877b3fce..6f4822e26d 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -183,16 +183,6 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); - /* Check that the step timeout is -1. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 2, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); - /* Free request document generated by serializer. */ AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); @@ -239,6 +229,45 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) requestContents.pUpdateInfo = &updateInfo; + /* Step timeout should not be present if not provided. */ + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request using the "cancel timeout" value. */ + updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_CANCEL_TIMEOUT; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_START_NEXT, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the step timeout is -1. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 2, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + /* Generate request with step timeout. */ updateInfo.stepTimeoutInMinutes = 3600; @@ -466,15 +495,13 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateStatus ) TEST_ASSERT_EQUAL( strlen( pValidStatusStrings[ i ] ), jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( pValidStatusStrings[ i ], pJsonValue, jsonValueLength ); - /* Check that the step timeout is -1. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL( 2, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); + /* Check that the step timeout is not present. */ + TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); /* Check that a client token is present. */ TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, @@ -597,6 +624,28 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) /* Free request document generated by serializer. */ AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); + + /* Generate request using the "cancel timeout" value. */ + updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_CANCEL_TIMEOUT; + + status = _AwsIotJobs_GenerateJsonRequest( JOBS_UPDATE, + &requestInfo, + &requestContents, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that step timeout is -1. */ + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL( 2, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); + + /* Free request document generated by serializer. */ + AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); } /*-----------------------------------------------------------*/ From 6e9625a225c28bac7d6351bcdfabefd9785c231e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 29 Jun 2019 12:46:58 +0800 Subject: [PATCH 214/844] Add system tests for Jobs Start Next and Describe (#502) --- lib/source/jobs/aws_iot_jobs_api.c | 70 +++++++++- lib/source/jobs/aws_iot_jobs_operation.c | 2 +- tests/jobs/system/aws_iot_tests_jobs_system.c | 124 ++++++++++++++---- 3 files changed, 171 insertions(+), 25 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 12e82eb257..4c8acbec76 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -400,7 +400,7 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, uint32_t flags, - uint32_t timeout, + uint32_t timeoutMs, AwsIotJobsResponse_t * const pJobsResponse ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; @@ -419,7 +419,7 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR if( status == AWS_IOT_JOBS_STATUS_PENDING ) { status = AwsIotJobs_Wait( getPendingOperation, - timeout, + timeoutMs, pJobsResponse ); } @@ -497,6 +497,38 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_JOBS_FLAG_WAITABLE; + + /* Call the asynchronous Jobs Start Next function. */ + status = AwsIotJobs_StartNext( pRequestInfo, + pUpdateInfo, + flags, + NULL, + &startNextOperation ); + + /* Wait for the Jobs Start Next operation to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + status = AwsIotJobs_Wait( startNextOperation, + timeoutMs, + pJobsResponse ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, int32_t executionNumber, bool includeJobDocument, @@ -559,6 +591,40 @@ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestI /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t describeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_JOBS_FLAG_WAITABLE; + + /* Call the asynchronous Jobs Describe function. */ + status = AwsIotJobs_Describe( pRequestInfo, + executionNumber, + includeJobDocument, + flags, + NULL, + &describeOperation ); + + /* Wait for the Jobs Describe operation to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + status = AwsIotJobs_Wait( describeOperation, + timeoutMs, + pJobsResponse ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, const AwsIotJobsUpdateInfo_t * pUpdateInfo, uint32_t flags, diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index 948a31dd49..59d55f963c 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -709,7 +709,7 @@ AwsIotJobsError_t _AwsIotJobs_GenerateJobsTopic( _jobsOperationType_t type, /* Construct the Jobs operation name with the Job ID. */ ( void ) memcpy( pJobOperationName, "/jobs/", 6 ); - operationNameLength = 5; + operationNameLength = 6; ( void ) memcpy( pJobOperationName + operationNameLength, pRequestInfo->pJobId, diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 23a48cd4e0..279792df52 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -197,6 +197,12 @@ static void _jobsAsyncTest( _jobsOperationType_t type, break; case JOBS_DESCRIBE: + status = AwsIotJobs_Describe( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + true, + 0, + &callbackInfo, + &( callbackParam.operation ) ); break; default: @@ -219,6 +225,67 @@ static void _jobsAsyncTest( _jobsOperationType_t type, /*-----------------------------------------------------------*/ +static void _jobsBlockingTest( _jobsOperationType_t type, + AwsIotJobsError_t expectedResult ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + + /* Set the Jobs request parameters. */ + requestInfo.mqttConnection = _mqttConnection; + requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; + requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + requestInfo.mallocResponse = IotTest_Malloc; + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + + /* Call Jobs function. */ + switch( type ) + { + case JOBS_GET_PENDING: + status = AwsIotJobs_TimedGetPending( &requestInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); + break; + + case JOBS_START_NEXT: + status = AwsIotJobs_TimedStartNext( &requestInfo, + &updateInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); + break; + + case JOBS_DESCRIBE: + status = AwsIotJobs_TimedDescribe( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + true, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); + break; + + default: + /* The only remaining valid type is UPDATE. */ + TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); + break; + } + + TEST_ASSERT_EQUAL( expectedResult, status ); + + /* Check the Jobs response. */ + TEST_ASSERT_NOT_NULL( jobsResponse.pJobsResponse ); + TEST_ASSERT_GREATER_THAN( 0, jobsResponse.jobsResponseLength ); + + /* Free the allocated Jobs response. */ + IotTest_Free( ( void * ) jobsResponse.pJobsResponse ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs system tests. */ @@ -338,8 +405,10 @@ TEST_GROUP_RUNNER( Jobs_System ) { RUN_TEST_CASE( Jobs_System, GetPendingAsync ); RUN_TEST_CASE( Jobs_System, GetPendingBlocking ); - RUN_TEST_CASE( Jobs_System, StartNextAsync ); + RUN_TEST_CASE( Jobs_System, StartNextBlocking ); + RUN_TEST_CASE( Jobs_System, DescribeAsync ); + RUN_TEST_CASE( Jobs_System, DescribeBlocking ); } /*-----------------------------------------------------------*/ @@ -359,36 +428,47 @@ TEST( Jobs_System, GetPendingAsync ) */ TEST( Jobs_System, GetPendingBlocking ) { - AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; - AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; - AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + _jobsBlockingTest( JOBS_GET_PENDING, AWS_IOT_JOBS_SUCCESS ); +} - /* Set the Jobs request parameters. */ - requestInfo.mqttConnection = _mqttConnection; - requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; - requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); - requestInfo.mallocResponse = IotTest_Malloc; +/*-----------------------------------------------------------*/ - /* Get pending Jobs. */ - status = AwsIotJobs_TimedGetPending( &requestInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); - TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); +/** + * @brief Starts the next Job using @ref jobs_function_startnext. + */ +TEST( Jobs_System, StartNextAsync ) +{ + _jobsAsyncTest( JOBS_START_NEXT, AWS_IOT_JOBS_SUCCESS ); +} - /* Check the Jobs response. */ - TEST_ASSERT_NOT_NULL( jobsResponse.pJobsResponse ); - TEST_ASSERT_GREATER_THAN( 0, jobsResponse.jobsResponseLength ); +/*-----------------------------------------------------------*/ - /* Free the allocated Jobs response. */ - IotTest_Free( ( void * ) jobsResponse.pJobsResponse ); +/** + * @brief Starts the next Job using @ref jobs_function_timedstartnext. + */ +TEST( Jobs_System, StartNextBlocking ) +{ + _jobsBlockingTest( JOBS_START_NEXT, AWS_IOT_JOBS_SUCCESS ); } /*-----------------------------------------------------------*/ -TEST( Jobs_System, StartNextAsync ) +/** + * @brief Describe a Job @ref jobs_function_describe. + */ +TEST( Jobs_System, DescribeAsync ) { - _jobsAsyncTest( JOBS_START_NEXT, AWS_IOT_JOBS_SUCCESS ); + _jobsAsyncTest( JOBS_DESCRIBE, AWS_IOT_JOBS_SUCCESS ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Describe a Job @ref jobs_function_timeddescribe. + */ +TEST( Jobs_System, DescribeBlocking ) +{ + _jobsBlockingTest( JOBS_DESCRIBE, AWS_IOT_JOBS_SUCCESS ); } /*-----------------------------------------------------------*/ From 95ce69b0b0d34d9bb7818a67d847ed409d07c336 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Sat, 29 Jun 2019 13:23:32 +0800 Subject: [PATCH 215/844] Add tests for Jobs Update (#503) --- lib/source/jobs/aws_iot_jobs_api.c | 49 +++++++++++++++++++ tests/jobs/system/aws_iot_tests_jobs_system.c | 48 +++++++++++++++++- tests/jobs/unit/aws_iot_tests_jobs_api.c | 17 +++++-- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 4c8acbec76..de69c1c3cb 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -27,6 +27,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* Platform threads include. */ #include "platform/iot_threads.h" @@ -204,6 +207,20 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, } } + /* A Job ID (not $next job) must be specified for UPDATE. */ + if( type == JOBS_UPDATE ) + { + if( ( pRequestInfo->jobIdLength == AWS_IOT_JOBS_NEXT_JOB_LENGTH ) && + ( strncmp( AWS_IOT_JOBS_NEXT_JOB, + pRequestInfo->pJobId, + AWS_IOT_JOBS_NEXT_JOB_LENGTH ) == 0 ) ) + { + IotLogError( "Job ID $next is not valid for Jobs UPDATE." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + IOT_FUNCTION_EXIT_NO_CLEANUP(); } @@ -694,6 +711,38 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + + /* Set the waitable flag. */ + flags |= AWS_IOT_JOBS_FLAG_WAITABLE; + + /* Call the asynchronous Jobs Update function. */ + status = AwsIotJobs_Update( pRequestInfo, + pUpdateInfo, + flags, + NULL, + &updateOperation ); + + /* Wait for the Jobs Update operation to complete. */ + if( status == AWS_IOT_JOBS_STATUS_PENDING ) + { + status = AwsIotJobs_Wait( updateOperation, + timeoutMs, + pJobsResponse ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, uint32_t timeoutMs, AwsIotJobsResponse_t * pJobsResponse ) diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 279792df52..ad55e85a50 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -208,6 +208,16 @@ static void _jobsAsyncTest( _jobsOperationType_t type, default: /* The only remaining valid type is UPDATE. */ TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); + + /* Set a Job ID that doesn't exist. */ + requestInfo.pJobId = "NOTAJOBID"; + requestInfo.jobIdLength = 9; + + status = AwsIotJobs_Update( &requestInfo, + &updateInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); break; } @@ -271,6 +281,16 @@ static void _jobsBlockingTest( _jobsOperationType_t type, default: /* The only remaining valid type is UPDATE. */ TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); + + /* Set a Job ID that doesn't exist. */ + requestInfo.pJobId = "NOTAJOBID"; + requestInfo.jobIdLength = 9; + + status = AwsIotJobs_TimedUpdate( &requestInfo, + &updateInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); break; } @@ -409,6 +429,8 @@ TEST_GROUP_RUNNER( Jobs_System ) RUN_TEST_CASE( Jobs_System, StartNextBlocking ); RUN_TEST_CASE( Jobs_System, DescribeAsync ); RUN_TEST_CASE( Jobs_System, DescribeBlocking ); + RUN_TEST_CASE( Jobs_System, UpdateAsync ); + RUN_TEST_CASE( Jobs_System, UpdateBlocking ); } /*-----------------------------------------------------------*/ @@ -454,7 +476,7 @@ TEST( Jobs_System, StartNextBlocking ) /*-----------------------------------------------------------*/ /** - * @brief Describe a Job @ref jobs_function_describe. + * @brief Describe a Job using @ref jobs_function_describe. */ TEST( Jobs_System, DescribeAsync ) { @@ -464,7 +486,7 @@ TEST( Jobs_System, DescribeAsync ) /*-----------------------------------------------------------*/ /** - * @brief Describe a Job @ref jobs_function_timeddescribe. + * @brief Describe a Job using @ref jobs_function_timeddescribe. */ TEST( Jobs_System, DescribeBlocking ) { @@ -472,3 +494,25 @@ TEST( Jobs_System, DescribeBlocking ) } /*-----------------------------------------------------------*/ + +/** + * @brief Update a Job status using @ref jobs_function_update. + */ +TEST( Jobs_System, UpdateAsync ) +{ + /* The Job ID used in this test does not exist, expect NOT_FOUND. */ + _jobsAsyncTest( JOBS_UPDATE, AWS_IOT_JOBS_NOT_FOUND ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Update a Job status using @ref jobs_function_timedupdate. + */ +TEST( Jobs_System, UpdateBlocking ) +{ + /* The Job ID used in this test does not exist, expect NOT_FOUND. */ + _jobsBlockingTest( JOBS_UPDATE, AWS_IOT_JOBS_NOT_FOUND ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index af2b2bf6f7..8a1546add3 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -103,8 +103,8 @@ static void _jobsMallocFail( _jobsOperationType_t type ) requestInfo.pThingName = TEST_THING_NAME; requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; requestInfo.mallocResponse = IotTest_Malloc; - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; - requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + requestInfo.pJobId = "jobid"; + requestInfo.jobIdLength = 5; for( i = 0; ; i++ ) { @@ -414,7 +414,7 @@ TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Job ID too long. */ - requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.pJobId = "jobid"; requestInfo.jobIdLength = JOBS_MAX_ID_LENGTH + 1; status = AwsIotJobs_Update( &requestInfo, @@ -423,6 +423,17 @@ TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) NULL, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Using $next with UPDATE is invalid. */ + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + + status = AwsIotJobs_Update( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); } /*-----------------------------------------------------------*/ From 5525338c420dfb5fdc9c2564eb61056b95f66ba8 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:18:42 +0800 Subject: [PATCH 216/844] Don't hold network mutex in receive callback (#505) --- platform/network/iot_network_mbedtls.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index 933cede9a5..01c05cdd66 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -386,10 +386,10 @@ static void _receiveThread( void * pArgument ) } else { - IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); - /* Check if connection is closed. */ + IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); connectionClosed = ( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ); + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); if( connectionClosed == false ) { @@ -400,11 +400,7 @@ static void _receiveThread( void * pArgument ) pNetworkConnection->pReceiveContext ); } } - - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); - - /* Exit if the connection is closed. */ - if( connectionClosed == true ) + else { break; } From 89fd6161e0847deaf3d48df66b1bfeaf3837a353 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 2 Jul 2019 13:45:53 +0800 Subject: [PATCH 217/844] Add system test for Jobs RemovePersistentSubscriptions (#506) --- lib/include/aws_iot_jobs.h | 4 +- lib/include/types/aws_iot_jobs_types.h | 1 + lib/source/jobs/aws_iot_jobs_subscription.c | 153 ++++++++++++++++++ tests/jobs/system/aws_iot_tests_jobs_system.c | 59 +++++++ 4 files changed, 214 insertions(+), 3 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 19cdd531f9..14be41b407 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -269,9 +269,7 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConn * @brief Remove persistent Jobs operation topic subscriptions. */ /* @[declare_jobs_removepersistentsubscriptions] */ -AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, +AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequestInfo_t * pRequestInfo, uint32_t flags ); /* @[declare_jobs_removepersistentsubscriptions] */ diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 922912bbaf..ff3e8e1dd2 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -132,6 +132,7 @@ typedef enum AwsIotJobsError * - @ref jobs_function_wait * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback + * - @ref jobs_function_removepersistentsubscriptions */ AWS_IOT_JOBS_BAD_PARAMETER, diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 0e367bc4de..1e5208b499 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -37,6 +37,9 @@ /* Error handling include. */ #include "private/iot_error.h" +/* Platform layer includes. */ +#include "platform/iot_threads.h" + /* MQTT include. */ #include "iot_mqtt.h" @@ -413,3 +416,153 @@ void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, } /*-----------------------------------------------------------*/ + +AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + int32_t i = 0; + uint16_t operationTopicLength = 0; + IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING; + AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; + _jobsSubscription_t * pSubscription = NULL; + IotLink_t * pSubscriptionLink = NULL; + AwsIotThingName_t thingName = + { + .pThingName = pRequestInfo->pThingName, + .thingNameLength = pRequestInfo->thingNameLength + }; + + IotLogInfo( "Removing persistent subscriptions for %.*s.", + pRequestInfo->thingNameLength, + pRequestInfo->pThingName ); + + /* Check parameters. */ + if( pRequestInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) + { + IotLogError( "MQTT connection is not initialized." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + if( AwsIot_ValidateThingName( pRequestInfo->pThingName, + pRequestInfo->thingNameLength ) == false ) + { + IotLogError( "Thing Name is not valid." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + if( ( ( flags & AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ) != 0 ) || + ( ( flags & AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS ) != 0 ) ) + { + if( ( pRequestInfo->pJobId == NULL ) || ( pRequestInfo->jobIdLength == 0 ) ) + { + IotLogError( "Job ID must be set." ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + if( pRequestInfo->jobIdLength > JOBS_MAX_ID_LENGTH ) + { + IotLogError( "Job ID cannot be longer than %d.", + JOBS_MAX_ID_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + IotMutex_Lock( &( _AwsIotJobsSubscriptionsMutex ) ); + + /* Search the list for an existing subscription for Thing Name. */ + pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotJobsSubscriptions ), + NULL, + _jobsSubscription_match, + &thingName ); + + if( pSubscriptionLink != NULL ) + { + IotLogDebug( "Found subscription object for %.*s. Checking for persistent " + "subscriptions to remove.", + pRequestInfo->thingNameLength, + pRequestInfo->pThingName ); + + pSubscription = IotLink_Container( _jobsSubscription_t, pSubscriptionLink, link ); + + for( i = 0; i < JOBS_OPERATION_COUNT; i++ ) + { + if( ( flags & ( 0x1UL << i ) ) != 0 ) + { + IotLogDebug( "Removing %.*s %s persistent subscriptions.", + pRequestInfo->thingNameLength, + pRequestInfo->pThingName, + _pAwsIotJobsOperationNames[ i ] ); + + /* Subscription must have a topic buffer. */ + AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); + + if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) + { + /* Generate the prefix of the Jobs topic. This function will not + * fail when given a buffer. */ + ( void ) _AwsIotJobs_GenerateJobsTopic( ( _jobsOperationType_t ) i, + pRequestInfo, + &( pSubscription->pTopicBuffer ), + &operationTopicLength ); + + /* Set the parameters needed to remove subscriptions. */ + subscriptionInfo.mqttConnection = pRequestInfo->mqttConnection; + subscriptionInfo.timeout = _AwsIotJobsMqttTimeoutMs; + subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer; + subscriptionInfo.topicFilterBaseLength = operationTopicLength; + + unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + &subscriptionInfo ); + + /* Convert MQTT return code to Shadow return code. */ + switch( unsubscribeStatus ) + { + case IOT_MQTT_SUCCESS: + status = AWS_IOT_JOBS_SUCCESS; + break; + + case IOT_MQTT_NO_MEMORY: + status = AWS_IOT_JOBS_NO_MEMORY; + break; + + default: + status = AWS_IOT_JOBS_MQTT_ERROR; + break; + } + + if( status != AWS_IOT_JOBS_SUCCESS ) + { + break; + } + + /* Clear the persistent subscriptions flag. */ + pSubscription->references[ i ] = 0; + } + else + { + IotLogDebug( "%.*s %s does not have persistent subscriptions.", + pRequestInfo->thingNameLength, + pRequestInfo->pThingName, + _pAwsIotJobsOperationNames[ i ] ); + } + } + } + } + else + { + IotLogWarn( "No subscription object found for %.*s", + pRequestInfo->thingNameLength, + pRequestInfo->pThingName ); + } + + IotMutex_Unlock( &( _AwsIotJobsSubscriptionsMutex ) ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index ad55e85a50..fde34e42ad 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -431,6 +431,7 @@ TEST_GROUP_RUNNER( Jobs_System ) RUN_TEST_CASE( Jobs_System, DescribeBlocking ); RUN_TEST_CASE( Jobs_System, UpdateAsync ); RUN_TEST_CASE( Jobs_System, UpdateBlocking ); + RUN_TEST_CASE( Jobs_System, PersistentSubscriptions ); } /*-----------------------------------------------------------*/ @@ -516,3 +517,61 @@ TEST( Jobs_System, UpdateBlocking ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests a Job operation with perisistent subscriptions. + */ +TEST( Jobs_System, PersistentSubscriptions ) +{ + uint64_t startTime = 0, elapsedTime1 = 0, elapsedTime2 = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + + /* Set the Jobs request parameters. */ + requestInfo.mqttConnection = _mqttConnection; + requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; + requestInfo.thingNameLength = ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + requestInfo.mallocResponse = IotTest_Malloc; + + /* Time a Jobs function that sets persistent subscriptions. */ + startTime = IotClock_GetTimeMs(); + status = AwsIotJobs_TimedGetPending( &requestInfo, + AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS, + AWS_IOT_TEST_JOBS_TIMEOUT, + &response ); + elapsedTime1 = IotClock_GetTimeMs() - startTime; + + /* Check results. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + TEST_ASSERT_NOT_NULL( response.pJobsResponse ); + TEST_ASSERT_GREATER_THAN( 0, response.jobsResponseLength ); + + IotTest_Free( ( void * ) response.pJobsResponse ); + + /* Time a Jobs functions that has persistent subscriptions set. */ + startTime = IotClock_GetTimeMs(); + status = AwsIotJobs_TimedGetPending( &requestInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &response ); + elapsedTime2 = IotClock_GetTimeMs() - startTime; + + /* Check results */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + TEST_ASSERT_NOT_NULL( response.pJobsResponse ); + TEST_ASSERT_GREATER_THAN( 0, response.jobsResponseLength ); + + IotTest_Free( ( void * ) response.pJobsResponse ); + + /* Becuase the second operation has persistent subscriptions and does not + * need to subscribe to anything, it should be significantly faster. */ + TEST_ASSERT_LESS_THAN( elapsedTime1, elapsedTime2 ); + + /* Remove persistent subscriptions. */ + status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, + AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ From 943b6d0e0590d7544d72495cc50c47473d928a9a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:20:30 +0800 Subject: [PATCH 218/844] Add unit test for Jobs RemovePersistentSubscriptions (#507) --- lib/source/jobs/aws_iot_jobs_subscription.c | 2 +- tests/jobs/unit/aws_iot_tests_jobs_api.c | 58 ++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 1e5208b499..d32dd88045 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -420,7 +420,7 @@ void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequestInfo_t * pRequestInfo, uint32_t flags ) { - IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); int32_t i = 0; uint16_t operationTopicLength = 0; IotMqttError_t unsubscribeStatus = IOT_MQTT_STATUS_PENDING; diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 8a1546add3..a72b574e9e 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -238,6 +238,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) RUN_TEST_CASE( Jobs_Unit_API, StartNextMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, DescribeMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, UpdateMallocFail ); + RUN_TEST_CASE( Jobs_Unit_API, RemovePersistentSubscriptions ); } /*-----------------------------------------------------------*/ @@ -494,9 +495,21 @@ TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) updateInfo.pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS; /* Invalid UPDATE state. */ + updateInfo.newStatus = AWS_IOT_JOB_STATE_QUEUED; + requestInfo.pJobId = "jobid"; + requestInfo.jobIdLength = 5; + + status = AwsIotJobs_Update( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + updateInfo.newStatus = AWS_IOT_JOB_STATE_IN_PROGRESS; + + /* Invalid UPDATE Job ID. */ requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - updateInfo.newStatus = AWS_IOT_JOB_STATE_QUEUED; status = AwsIotJobs_Update( &requestInfo, &updateInfo, @@ -575,3 +588,46 @@ TEST( Jobs_Unit_API, UpdateMallocFail ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of @ref jobs_function_removepersistentsubscriptions + * with various parameters. + */ +TEST( Jobs_Unit_API, RemovePersistentSubscriptions ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + + /* MQTT connection not set. */ + status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, + AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.mqttConnection = _pMqttConnection; + + /* Thing Name not set. */ + status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, + AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + + /* Job ID not set for DESCRIBE/UPDATE. */ + status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, + AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + + /* Job ID too long. */ + requestInfo.jobIdLength = JOBS_MAX_ID_LENGTH + 1; + status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, + AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + + /* No subscription present. */ + status = AwsIotJobs_RemovePersistentSubscriptions( &requestInfo, + AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ From 18a32a7fee5306c966192e1520c58849c54f55ba Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:51:09 +0800 Subject: [PATCH 219/844] Add test for Jobs response parsing (#508) --- .../jobs/unit/aws_iot_tests_jobs_serialize.c | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index 6f4822e26d..6361755657 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -90,6 +90,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_Serialize ) RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateStatus ); RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateNumbers ); RUN_TEST_CASE( Jobs_Unit_Serialize, SerializeUpdateFlags ); + RUN_TEST_CASE( Jobs_Unit_Serialize, ParseErrorResponse ); } /*-----------------------------------------------------------*/ @@ -727,3 +728,78 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateFlags ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing of Jobs error responses. + */ +TEST( Jobs_Unit_Serialize, ParseErrorResponse ) +{ + size_t i = 0; + _jobsOperation_t operation = { .link = { 0 } }; + + /* Test for failure to allocate memory for response. */ + operation.status = AWS_IOT_JOBS_STATUS_PENDING; + operation.flags = AWS_IOT_JOBS_FLAG_WAITABLE; + operation.mallocResponse = IotTest_Malloc; + + UnityMalloc_MakeMallocFailAfterCount( 0 ); + + _AwsIotJobs_ParseResponse( AWS_IOT_ACCEPTED, "{}", 2, &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, operation.status ); + UnityMalloc_MakeMallocFailAfterCount( -1 ); + operation.flags = 0; + + /* Test parsing of error responses. */ + const char * pErrorResponses[] = + { + "{\"code\": \"InvalidTopic\"}", + "{\"code\": \"InvalidJson\"}", + "{\"code\": \"InvalidRequest\"}", + "{\"code\": \"InvalidStateTransition\"}", + "{\"code\": \"ResourceNotFound\"}", + "{\"code\": \"VersionMismatch\"}", + "{\"code\": \"InternalError\"}", + "{\"code\": \"RequestThrottled\"}", + "{\"code\": \"TerminalStateReached\"}" + }; + const AwsIotJobsError_t pExpectedResponse[] = + { + AWS_IOT_JOBS_INVALID_TOPIC, + AWS_IOT_JOBS_INVALID_JSON, + AWS_IOT_JOBS_INVALID_REQUEST, + AWS_IOT_JOBS_INVALID_STATE, + AWS_IOT_JOBS_NOT_FOUND, + AWS_IOT_JOBS_VERSION_MISMATCH, + AWS_IOT_JOBS_INTERNAL_ERROR, + AWS_IOT_JOBS_THROTTLED, + AWS_IOT_JOBS_TERMINAL_STATE + }; + + for( i = 0; i < ( sizeof( pErrorResponses ) / sizeof( pErrorResponses[ 0 ] ) ); i++ ) + { + operation.status = AWS_IOT_JOBS_STATUS_PENDING; + + _AwsIotJobs_ParseResponse( AWS_IOT_REJECTED, + pErrorResponses[ i ], + strlen( pErrorResponses[ i ] ), + &operation ); + TEST_ASSERT_EQUAL( pExpectedResponse[ i ], operation.status ); + } + + /* Test parsing of invalid code. */ + operation.status = AWS_IOT_JOBS_STATUS_PENDING; + _AwsIotJobs_ParseResponse( AWS_IOT_REJECTED, + "{\"code\": \"NotAnErrorCodeNotAnErrorCode\"}", + 40, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_RESPONSE, operation.status ); + + operation.status = AWS_IOT_JOBS_STATUS_PENDING; + _AwsIotJobs_ParseResponse( AWS_IOT_REJECTED, + "{}", + 2, + &operation ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_RESPONSE, operation.status ); +} + +/*-----------------------------------------------------------*/ From 489b3cf2410df984aae97777ef3302eb3df33196 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 8 Jul 2019 12:28:42 +0800 Subject: [PATCH 220/844] Add test for MQTT PUBACK (#510) --- lib/source/mqtt/iot_mqtt_network.c | 92 +++++++++++++----------- tests/mqtt/unit/iot_tests_mqtt_receive.c | 64 ++++++++++++----- 2 files changed, 95 insertions(+), 61 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 039e2e24ab..a8f4a4c779 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -633,15 +633,13 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne static void _sendPuback( _mqttConnection_t * pMqttConnection, uint16_t packetIdentifier ) { - IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; - uint8_t * pPuback = NULL; - size_t pubackSize = 0, bytesSent = 0; + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + _mqttOperation_t * pPubackOperation = NULL; - /* Default PUBACK serializer and free packet functions. */ + /* Default PUBACK serializer function. */ IotMqttError_t ( * serializePuback )( uint16_t, uint8_t **, size_t * ) = _IotMqtt_SerializePuback; - void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", pMqttConnection, @@ -665,57 +663,65 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, EMPTY_ELSE_MARKER; } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->freePacket != NULL ) - { - freePacket = pMqttConnection->pSerializer->freePacket; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Create a PUBACK operation. */ + status = _IotMqtt_CreateOperation( pMqttConnection, + 0, + NULL, + &pPubackOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + + /* Set the operation type. */ + pPubackOperation->u.operation.type = IOT_MQTT_PUBACK; /* Generate a PUBACK packet from the packet identifier. */ - serializeStatus = serializePuback( packetIdentifier, - &pPuback, - &pubackSize ); + status = serializePuback( packetIdentifier, + &( pPubackOperation->u.operation.pMqttPacket ), + &( pPubackOperation->u.operation.packetSize ) ); - if( serializeStatus != IOT_MQTT_SUCCESS ) + if( status != IOT_MQTT_SUCCESS ) { - IotLogWarn( "(MQTT connection %p) Failed to generate PUBACK packet for " - "received PUBLISH %hu.", - pMqttConnection, - packetIdentifier ); + IOT_GOTO_CLEANUP(); + } + + /* Add the PUBACK operation to the send queue for network transmission. */ + status = _IotMqtt_ScheduleOperation( pPubackOperation, + _IotMqtt_ProcessSend, + 0 ); + + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "(MQTT connection %p) Failed to enqueue PUBACK for sending.", + pMqttConnection ); + + IOT_GOTO_CLEANUP(); } else { - bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, - pPuback, - pubackSize ); + EMPTY_ELSE_MARKER; + } + + /* Clean up on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); - if( bytesSent != pubackSize ) + if( status != IOT_MQTT_SUCCESS ) + { + if( pPubackOperation != NULL ) { - IotLogWarn( "(MQTT connection %p) Failed to send PUBACK for received" - " PUBLISH %hu.", - pMqttConnection, - packetIdentifier ); + _IotMqtt_DestroyOperation( pPubackOperation ); } else { - IotLogDebug( "(MQTT connection %p) PUBACK for received PUBLISH %hu sent.", - pMqttConnection, - packetIdentifier ); + EMPTY_ELSE_MARKER; } - - freePacket( pPuback ); + } + else + { + EMPTY_ELSE_MARKER; } } diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 1ea0aa2a24..daa46be6e6 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -208,6 +208,20 @@ static size_t _getRemainingLength( void * pNetworkConnection, /*-----------------------------------------------------------*/ +/** + * @brief Serializer override for PUBACK. + */ +static IotMqttError_t _serializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) +{ + return _IotMqtt_SerializePuback( packetIdentifier, + pPubackPacket, + pPacketSize ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Deserializer override for CONNACK. */ @@ -412,24 +426,6 @@ static void _publishCallback( void * pCallbackContext, /*-----------------------------------------------------------*/ -/** - * @brief A PUBACK serializer function that does nothing, but always returns failure. - * - * Prevents any tests on QoS 1 PUBLISH packets from sending any PUBACKS. - */ -static IotMqttError_t _serializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) -{ - ( void ) packetIdentifier; - ( void ) pPubackPacket; - ( void ) pPacketSize; - - return IOT_MQTT_NO_MEMORY; -} - -/*-----------------------------------------------------------*/ - /** * @brief Simulates a network receive function. */ @@ -473,6 +469,37 @@ static size_t _receive( void * pConnection, /*-----------------------------------------------------------*/ +/** + * @brief A network send function that checks the message is a PUBACK. + */ +static size_t _checkPuback( void * pConnection, + const uint8_t * pMessage, + size_t messageLength ) +{ + _mqttPacket_t pubackPacket = { .u = { 0 } }; + IotMqttError_t deserializeStatus = IOT_MQTT_STATUS_PENDING; + + /* Silence warnings about unused parameters. */ + ( void ) pConnection; + + /* All PUBACK packets should be 4 bytes long. */ + TEST_ASSERT_EQUAL( 4, messageLength ); + + /* Deserialize the PUBACK. */ + pubackPacket.type = MQTT_PACKET_TYPE_PUBACK; + pubackPacket.remainingLength = 2; + pubackPacket.pRemainingData = ( uint8_t * ) ( pMessage + 2 ); + deserializeStatus = _IotMqtt_DeserializePuback( &pubackPacket ); + + /* Check the results of the PUBACK deserialization. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, deserializeStatus ); + TEST_ASSERT_EQUAL( 1, pubackPacket.packetIdentifier ); + + return messageLength; +} + +/*-----------------------------------------------------------*/ + /** * @brief A network close function that reports if it was invoked. */ @@ -539,6 +566,7 @@ TEST_SETUP( MQTT_Unit_Receive ) serializer.getPacketType = _getPacketType; serializer.getRemainingLength = _getRemainingLength; + _networkInterface.send = _checkPuback; _networkInterface.receive = _receive; _networkInterface.close = _close; networkInfo.pNetworkInterface = &_networkInterface; From 237915a48b2e9357b8c9630277d0566e29637cc2 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 8 Jul 2019 17:13:28 +0800 Subject: [PATCH 221/844] Add MQTT periodic operation members (#511) --- lib/include/private/iot_mqtt_internal.h | 157 ++++++++++-------- lib/source/mqtt/iot_mqtt_api.c | 81 +++++---- lib/source/mqtt/iot_mqtt_network.c | 37 +++-- lib/source/mqtt/iot_mqtt_operation.c | 68 ++++---- tests/mqtt/unit/iot_tests_mqtt_api.c | 28 ++-- tests/mqtt/unit/iot_tests_mqtt_receive.c | 23 +-- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 2 +- 7 files changed, 224 insertions(+), 172 deletions(-) diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index d75fd756d5..8bdc73020b 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -244,69 +244,13 @@ /*---------------------- MQTT internal data structures ----------------------*/ /** - * @brief Represents an MQTT connection. - */ -typedef struct _mqttConnection -{ - bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ - bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ - void * pNetworkConnection; /**< @brief References the transport-layer network connection. */ - const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ - IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ - #endif - - bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ - IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ - int32_t references; /**< @brief Counts callbacks and operations using this connection. */ - IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ - IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ - - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ - - bool keepAliveFailure; /**< @brief Failure flag for keep-alive operation. */ - uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ - uint32_t nextKeepAliveMs; /**< @brief Relative delay for next keep-alive job. */ - IotTaskPoolJobStorage_t keepAliveJobStorage; /**< @brief Task pool job for processing this connection's keep-alive. */ - IotTaskPoolJob_t keepAliveJob; /**< @brief Task pool job for processing this connection's keep-alive. */ - uint8_t * pPingreqPacket; /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */ - size_t pingreqPacketSize; /**< @brief The size of an allocated PINGREQ packet. */ -} _mqttConnection_t; - -/** - * @brief Represents a subscription stored in an MQTT connection. + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declaration of MQTT connection type. */ -typedef struct _mqttSubscription -{ - IotLink_t link; /**< @brief List link member. */ - - int32_t references; /**< @brief How many subscription callbacks are using this subscription. */ - - /** - * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for - * this subscription. - * - * If there are active subscription callbacks, @ref mqtt_function_unsubscribe - * cannot remove this subscription. Instead, it will set this flag, which - * schedules the removal of this subscription once all subscription callbacks - * terminate. - */ - bool unsubscribed; - - struct - { - uint16_t identifier; /**< @brief Packet identifier. */ - size_t order; /**< @brief Order in the packet's list of subscriptions. */ - } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ - - IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ - - uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ - char pTopicFilter[]; /**< @brief The subscription topic filter. */ -} _mqttSubscription_t; +struct _mqttConnection; +/** @endcond */ /** * @brief Internal structure representing a single MQTT operation, such as @@ -317,13 +261,13 @@ typedef struct _mqttSubscription typedef struct _mqttOperation { /* Pointers to neighboring queue elements. */ - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ - _mqttConnection_t * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ + bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ + struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ - IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ - IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ + IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ + IotTaskPoolJob_t job; /**< @brief Task pool job associated with this operation. */ union { @@ -349,12 +293,22 @@ typedef struct _mqttOperation } notify; /**< @brief How to notify of this operation's completion. */ IotMqttError_t status; /**< @brief Result of this operation. This is reported once a response is received. */ - struct + union { - uint32_t count; - uint32_t limit; - uint32_t nextPeriod; - } retry; + struct + { + uint32_t count; /**< @brief Current number of retries. */ + uint32_t limit; /**< @brief Maximum number of retries allowed. */ + uint32_t nextPeriodMs; /**< @brief Next retry period. */ + } retry; /**< @brief Additional information for PUBLISH retry. */ + + struct + { + uint32_t failure; /**< @brief Flag tracking keep-alive status. */ + uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ + uint32_t nextPeriodMs; /**< @brief Relative delay for next keep-alive job. */ + } ping; /**< @brief Additional information for keep-alive pings. */ + } periodic; /**< @brief Additional information for periodic operations. */ } operation; /* If incomingPublish is true, this struct is valid. */ @@ -366,6 +320,65 @@ typedef struct _mqttOperation } u; /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */ } _mqttOperation_t; +/** + * @brief Represents an MQTT connection. + */ +typedef struct _mqttConnection +{ + bool awsIotMqttMode; /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */ + bool ownNetworkConnection; /**< @brief Whether this MQTT connection owns its network connection. */ + void * pNetworkConnection; /**< @brief References the transport-layer network connection. */ + const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ + IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ + + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ + #endif + + bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ + IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ + int32_t references; /**< @brief Counts callbacks and operations using this connection. */ + IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ + IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ + + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ + + _mqttOperation_t pingreq; /**< @brief Operation used for MQTT keep-alive. */ +} _mqttConnection_t; + +/** + * @brief Represents a subscription stored in an MQTT connection. + */ +typedef struct _mqttSubscription +{ + IotLink_t link; /**< @brief List link member. */ + + int32_t references; /**< @brief How many subscription callbacks are using this subscription. */ + + /** + * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for + * this subscription. + * + * If there are active subscription callbacks, @ref mqtt_function_unsubscribe + * cannot remove this subscription. Instead, it will set this flag, which + * schedules the removal of this subscription once all subscription callbacks + * terminate. + */ + bool unsubscribed; + + struct + { + uint16_t identifier; /**< @brief Packet identifier. */ + size_t order; /**< @brief Order in the packet's list of subscriptions. */ + } packetInfo; /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */ + + IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */ + + uint16_t topicFilterLength; /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */ + char pTopicFilter[]; /**< @brief The subscription topic filter. */ +} _mqttSubscription_t; + /** * @brief Represents an MQTT packet received from the network. * diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 9fcb7f3fd2..bc2ea45d12 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -90,7 +90,7 @@ static void _mqttSubscription_tryDestroy( void * pData ); static void _mqttOperation_tryDestroy( void * pData ); /** - * @brief Create a keep-alive job for an MQTT connection. + * @brief Initialize the keep-alive operation for an MQTT connection. * * @param[in] pNetworkInfo User-provided network information for the new * connection. @@ -99,9 +99,9 @@ static void _mqttOperation_tryDestroy( void * pData ); * * @return `true` if the keep-alive job was successfully created; `false` otherwise. */ -static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds, - _mqttConnection_t * pMqttConnection ); +static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds, + _mqttConnection_t * pMqttConnection ); /** * @brief Creates a new MQTT connection and initializes its members. @@ -234,9 +234,9 @@ static void _mqttOperation_tryDestroy( void * pData ) /*-----------------------------------------------------------*/ -static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, - uint16_t keepAliveSeconds, - _mqttConnection_t * pMqttConnection ) +static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo, + uint16_t keepAliveSeconds, + _mqttConnection_t * pMqttConnection ) { bool status = true; IotMqttError_t serializeStatus = IOT_MQTT_SUCCESS; @@ -249,9 +249,12 @@ static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqttError_t ( * serializePingreq )( uint8_t **, size_t * ) = _IotMqtt_SerializePingreq; + /* Set PINGREQ operation members. */ + pMqttConnection->pingreq.u.operation.type = IOT_MQTT_PINGREQ; + /* Convert the keep-alive interval to milliseconds. */ - pMqttConnection->keepAliveMs = keepAliveSeconds * 1000; - pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = keepAliveSeconds * 1000; + pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = keepAliveSeconds * 1000; /* Choose a PINGREQ serializer function. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 @@ -273,8 +276,8 @@ static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* Generate a PINGREQ packet. */ - serializeStatus = serializePingreq( &( pMqttConnection->pPingreqPacket ), - &( pMqttConnection->pingreqPacketSize ) ); + serializeStatus = serializePingreq( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), + &( pMqttConnection->pingreq.u.operation.packetSize ) ); if( serializeStatus != IOT_MQTT_SUCCESS ) { @@ -287,8 +290,8 @@ static bool _createKeepAliveJob( const IotMqttNetworkInfo_t * pNetworkInfo, /* Create the task pool job that processes keep-alive. */ jobStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, pMqttConnection, - &( pMqttConnection->keepAliveJobStorage ), - &( pMqttConnection->keepAliveJob ) ); + &( pMqttConnection->pingreq.jobStorage ), + &( pMqttConnection->pingreq.job ) ); /* Task pool job creation for a pre-allocated job should never fail. * Abort the program if it does. */ @@ -404,9 +407,9 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, /* Check if keep-alive is active for this connection. */ if( keepAliveSeconds != 0 ) { - if( _createKeepAliveJob( pNetworkInfo, - keepAliveSeconds, - pMqttConnection ) == false ) + if( _createKeepAliveOperation( pNetworkInfo, + keepAliveSeconds, + pMqttConnection ) == false ) { IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -467,17 +470,31 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) { IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; + /* Default free packet function. */ + void (* freePacket)( uint8_t * ) = _IotMqtt_FreePacket; + /* Clean up keep-alive if still allocated. */ - if( pMqttConnection->keepAliveMs != 0 ) + if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) { IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); - _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); + /* Choose a function to free the packet. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->freePacket != NULL ) + { + freePacket = pMqttConnection->pSerializer->freePacket; + } + } + #endif + + freePacket( pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ - pMqttConnection->keepAliveMs = 0; - pMqttConnection->pPingreqPacket = NULL; - pMqttConnection->pingreqPacketSize = 0; + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; + pMqttConnection->pingreq.u.operation.pMqttPacket = NULL; + pMqttConnection->pingreq.u.operation.packetSize = 0; /* Decrement reference count. */ pMqttConnection->references--; @@ -490,9 +507,9 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) /* A connection to be destroyed should have no keep-alive and at most 1 * reference. */ IotMqtt_Assert( pMqttConnection->references <= 1 ); - IotMqtt_Assert( pMqttConnection->keepAliveMs == 0 ); - IotMqtt_Assert( pMqttConnection->pPingreqPacket == NULL ); - IotMqtt_Assert( pMqttConnection->pingreqPacketSize == 0 ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs == 0 ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.pMqttPacket == NULL ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize == 0 ); /* Remove all subscriptions. */ IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); @@ -662,7 +679,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Check the subscription operation data and set the operation type. */ IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( pSubscriptionOperation->u.operation.retry.limit == 0 ); + IotMqtt_Assert( pSubscriptionOperation->u.operation.periodic.retry.limit == 0 ); pSubscriptionOperation->u.operation.type = operation; /* Generate a subscription packet from the subscription list. */ @@ -1055,7 +1072,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.retry.limit == 0 ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); /* Set the operation type. */ pOperation->u.operation.type = IOT_MQTT_CONNECT; @@ -1146,13 +1163,13 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, if( status == IOT_MQTT_SUCCESS ) { /* Check if a keep-alive job should be scheduled. */ - if( pNewMqttConnection->keepAliveMs != 0 ) + if( pNewMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) { IotLogDebug( "Scheduling first MQTT keep-alive job." ); taskPoolStatus = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - pNewMqttConnection->keepAliveJob, - pNewMqttConnection->nextKeepAliveMs ); + pNewMqttConnection->pingreq.job, + pNewMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ); if( taskPoolStatus != IOT_TASKPOOL_SUCCESS ) { @@ -1264,7 +1281,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.retry.limit == 0 ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); /* Set the operation type. */ pOperation->u.operation.type = IOT_MQTT_DISCONNECT; @@ -1647,8 +1664,8 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /* A QoS 0 PUBLISH may not be retried. */ if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - pOperation->u.operation.retry.limit = pPublishInfo->retryLimit; - pOperation->u.operation.retry.nextPeriod = pPublishInfo->retryMs; + pOperation->u.operation.periodic.retry.limit = pPublishInfo->retryLimit; + pOperation->u.operation.periodic.retry.nextPeriodMs = pPublishInfo->retryMs; } else { diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index a8f4a4c779..04605d276e 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -591,7 +591,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - if( pMqttConnection->keepAliveFailure == false ) + if( pMqttConnection->pingreq.u.operation.periodic.ping.failure == 0 ) { IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", pMqttConnection ); @@ -601,7 +601,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", pMqttConnection ); - pMqttConnection->keepAliveFailure = false; + pMqttConnection->pingreq.u.operation.periodic.ping.failure = 0; } IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); @@ -764,16 +764,19 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; + /* Default free packet function. */ + void (* freePacket)( uint8_t * ) = _IotMqtt_FreePacket; + /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pMqttConnection->disconnected = true; - pMqttConnection->keepAliveFailure = true; + pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; - if( pMqttConnection->keepAliveMs != 0 ) + if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) { /* Keep-alive must have a PINGREQ allocated. */ - IotMqtt_Assert( pMqttConnection->pPingreqPacket != NULL ); - IotMqtt_Assert( pMqttConnection->pingreqPacketSize != 0 ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pMqttConnection->pingreq.u.operation.packetSize != 0 ); /* PINGREQ provides a reference to the connection, so reference count must * be nonzero. */ @@ -781,7 +784,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason /* Attempt to cancel the keep-alive job. */ taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, - pMqttConnection->keepAliveJob, + pMqttConnection->pingreq.job, NULL ); /* If the keep-alive job was not canceled, it must be already executing. @@ -793,13 +796,23 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason * the executing keep-alive job will clean up itself. */ if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { - /* Clean up PINGREQ packet and job. */ - _IotMqtt_FreePacket( pMqttConnection->pPingreqPacket ); + /* Choose a function to free the packet. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( pMqttConnection->pSerializer != NULL ) + { + if( pMqttConnection->pSerializer->freePacket != NULL ) + { + freePacket = pMqttConnection->pSerializer->freePacket; + } + } + #endif + + freePacket( pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ - pMqttConnection->keepAliveMs = 0; - pMqttConnection->pPingreqPacket = NULL; - pMqttConnection->pingreqPacketSize = 0; + pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; + pMqttConnection->pingreq.u.operation.pMqttPacket = NULL; + pMqttConnection->pingreq.u.operation.packetSize = 0; /* Keep-alive is cleaned up; decrement reference count. Since this * function must be followed with a call to DISCONNECT, a check to diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index c116edf5b7..be3b4f5672 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -158,22 +158,23 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) IotMqtt_Assert( pOperation->u.operation.type == IOT_MQTT_PUBLISH_TO_SERVER ); /* Check if the retry limit is exhausted. */ - if( pOperation->u.operation.retry.count > pOperation->u.operation.retry.limit ) + if( pOperation->u.operation.periodic.retry.count > pOperation->u.operation.periodic.retry.limit ) { /* The retry count may be at most one more than the retry limit, which * accounts for the final check for a PUBACK. */ - IotMqtt_Assert( pOperation->u.operation.retry.count == pOperation->u.operation.retry.limit + 1 ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.count == + pOperation->u.operation.periodic.retry.limit + 1 ); IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) No response received after %lu retries.", pMqttConnection, pOperation, - pOperation->u.operation.retry.limit ); + pOperation->u.operation.periodic.retry.limit ); status = false; } else { - if( pOperation->u.operation.retry.count == 1 ) + if( pOperation->u.operation.periodic.retry.count == 1 ) { /* The DUP flag should always be set on the first retry. */ setDup = true; @@ -237,14 +238,16 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) /* This function should never be called with retry count greater than * retry limit. */ - IotMqtt_Assert( pOperation->u.operation.retry.count <= pOperation->u.operation.retry.limit ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.count <= + pOperation->u.operation.periodic.retry.limit ); /* Increment the retry count. */ - ( pOperation->u.operation.retry.count )++; + ( pOperation->u.operation.periodic.retry.count )++; /* Check for a response shortly for the final retry. Otherwise, calculate the * next retry period. */ - if( pOperation->u.operation.retry.count > pOperation->u.operation.retry.limit ) + if( pOperation->u.operation.periodic.retry.count > + pOperation->u.operation.periodic.retry.limit ) { scheduleDelay = IOT_MQTT_RESPONSE_WAIT_MS; @@ -256,14 +259,14 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) } else { - scheduleDelay = pOperation->u.operation.retry.nextPeriod; + scheduleDelay = pOperation->u.operation.periodic.retry.nextPeriodMs; /* Double the retry period, subject to a ceiling value. */ - pOperation->u.operation.retry.nextPeriod *= 2; + pOperation->u.operation.periodic.retry.nextPeriodMs *= 2; - if( pOperation->u.operation.retry.nextPeriod > IOT_MQTT_RETRY_MS_CEILING ) + if( pOperation->u.operation.periodic.retry.nextPeriodMs > IOT_MQTT_RETRY_MS_CEILING ) { - pOperation->u.operation.retry.nextPeriod = IOT_MQTT_RETRY_MS_CEILING; + pOperation->u.operation.periodic.retry.nextPeriodMs = IOT_MQTT_RETRY_MS_CEILING; } else { @@ -273,12 +276,12 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) IotLogDebug( "(MQTT connection %p, PUBLISH operation %p) Scheduling retry %lu of %lu in %lu ms.", pMqttConnection, pOperation, - ( unsigned long ) pOperation->u.operation.retry.count, - ( unsigned long ) pOperation->u.operation.retry.limit, + ( unsigned long ) pOperation->u.operation.periodic.retry.count, + ( unsigned long ) pOperation->u.operation.periodic.retry.limit, ( unsigned long ) scheduleDelay ); /* Check if this is the first retry. */ - firstRetry = ( pOperation->u.operation.retry.count == 1 ); + firstRetry = ( pOperation->u.operation.periodic.retry.count == 1 ); /* On the first retry, the PUBLISH will be moved from the pending processing * list to the pending responses list. Lock the connection references mutex @@ -678,18 +681,21 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, /* Retrieve the MQTT connection from the context. */ _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; + _mqttOperation_t * pPingreqOperation = &( pMqttConnection->pingreq ); /* Check parameters. */ IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); - IotMqtt_Assert( pKeepAliveJob == pMqttConnection->keepAliveJob ); + IotMqtt_Assert( pKeepAliveJob == pPingreqOperation->job ); /* Check that keep-alive interval is valid. The MQTT spec states its maximum * value is 65,535 seconds. */ - IotMqtt_Assert( pMqttConnection->keepAliveMs <= 65535000 ); + IotMqtt_Assert( pPingreqOperation->u.operation.periodic.ping.keepAliveMs <= 65535000 ); /* Only two values are valid for the next keep alive job delay. */ - IotMqtt_Assert( ( pMqttConnection->nextKeepAliveMs == pMqttConnection->keepAliveMs ) || - ( pMqttConnection->nextKeepAliveMs == IOT_MQTT_RESPONSE_WAIT_MS ) ); + IotMqtt_Assert( ( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == + pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) || + ( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs + == IOT_MQTT_RESPONSE_WAIT_MS ) ); IotLogDebug( "(MQTT connection %p) Keep-alive job started.", pMqttConnection ); @@ -703,7 +709,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); /* Determine whether to send a PINGREQ or check for PINGRESP. */ - if( pMqttConnection->nextKeepAliveMs == pMqttConnection->keepAliveMs ) + if( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == + pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) { IotLogDebug( "(MQTT connection %p) Sending PINGREQ.", pMqttConnection ); @@ -711,10 +718,10 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, * more important than other operations. Bypass the queue of jobs for * operations by directly sending the PINGREQ in this job. */ bytesSent = pMqttConnection->pNetworkInterface->send( pMqttConnection->pNetworkConnection, - pMqttConnection->pPingreqPacket, - pMqttConnection->pingreqPacketSize ); + pPingreqOperation->u.operation.pMqttPacket, + pPingreqOperation->u.operation.packetSize ); - if( bytesSent != pMqttConnection->pingreqPacketSize ) + if( bytesSent != pPingreqOperation->u.operation.packetSize ) { IotLogError( "(MQTT connection %p) Failed to send PINGREQ.", pMqttConnection ); status = false; @@ -723,10 +730,10 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { /* Assume the keep-alive will fail. The network receive callback will * clear the failure flag upon receiving a PINGRESP. */ - pMqttConnection->keepAliveFailure = true; + pPingreqOperation->u.operation.periodic.ping.failure = 1; /* Schedule a check for PINGRESP. */ - pMqttConnection->nextKeepAliveMs = IOT_MQTT_RESPONSE_WAIT_MS; + pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; IotLogDebug( "(MQTT connection %p) PINGREQ sent. Scheduling check for PINGRESP in %d ms.", pMqttConnection, @@ -737,12 +744,13 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { IotLogDebug( "(MQTT connection %p) Checking for PINGRESP.", pMqttConnection ); - if( pMqttConnection->keepAliveFailure == false ) + if( pPingreqOperation->u.operation.periodic.ping.failure == 0 ) { IotLogDebug( "(MQTT connection %p) PINGRESP was received.", pMqttConnection ); /* PINGRESP was received. Schedule the next PINGREQ transmission. */ - pMqttConnection->nextKeepAliveMs = pMqttConnection->keepAliveMs; + pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = + pPingreqOperation->u.operation.periodic.ping.keepAliveMs; } else { @@ -761,7 +769,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { taskPoolStatus = IotTaskPool_ScheduleDeferred( pTaskPool, pKeepAliveJob, - pMqttConnection->nextKeepAliveMs ); + pPingreqOperation->u.operation.periodic.ping.nextPeriodMs ); if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { @@ -875,7 +883,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, waitable = ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) == IOT_MQTT_FLAG_WAITABLE; /* Check PUBLISH retry counts and limits. */ - if( pOperation->u.operation.retry.limit > 0 ) + if( pOperation->u.operation.periodic.retry.limit > 0 ) { if( _checkRetryLimit( pOperation ) == false ) { @@ -947,7 +955,7 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, if( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ) { /* Check if this operation should be scheduled for retransmission. */ - if( pOperation->u.operation.retry.limit > 0 ) + if( pOperation->u.operation.periodic.retry.limit > 0 ) { if( _scheduleNextRetry( pOperation ) == false ) { @@ -1162,7 +1170,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, /* Check if the matched operation is a PUBLISH with retry. If it is, cancel * the retry job. */ - if( pResult->u.operation.retry.limit > 0 ) + if( pResult->u.operation.periodic.retry.limit > 0 ) { taskPoolStatus = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, pResult->job, diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index a800e2339c..2a5d70424c 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -77,21 +77,21 @@ /* * Client identifier and length to use for the MQTT API tests. */ -#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ +#define CLIENT_IDENTIFIER ( "test" ) /**< @brief Client identifier. */ #define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) /**< @brief Length of client identifier. */ /* * Will topic name and length to use for the MQTT API tests. */ -#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ +#define TEST_TOPIC_NAME ( "/test/topic" ) /**< @brief An arbitrary topic name. */ #define TEST_TOPIC_NAME_LENGTH ( ( uint16_t ) ( sizeof( TEST_TOPIC_NAME ) - 1 ) ) /**< @brief Length of topic name. */ /** * @brief A non-NULL function pointer to use for subscription callback. This * "function" should cause a crash if actually called. */ -#define SUBSCRIPTION_CALLBACK \ - ( ( void ( * )( void *, \ +#define SUBSCRIPTION_CALLBACK \ + ( ( void ( * )( void *, \ IotMqttCallbackParam_t * ) ) 0x01 ) /** @@ -110,7 +110,7 @@ #define DUP_CHECK_RETRY_MS ( 100 ) /**< @brief When to start sending duplicate packets. */ #define DUP_CHECK_RETRY_LIMIT ( 3 ) /**< @brief How many duplicate packets to send. */ #define DUP_CHECK_TIMEOUT ( 3000 ) /**< @brief Total time allowed to send all duplicate packets. - * Duplicates are sent using an exponential backoff strategy. */ + * Duplicates are sent using an exponential backoff strategy. */ /** @brief The minimum amount of time the test can take. */ #define DUP_CHECK_MINIMUM_WAIT \ ( DUP_CHECK_RETRY_MS + \ @@ -584,7 +584,7 @@ TEST( MQTT_Unit_API, OperationCreateDestroy ) TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Adjustment to reference count based on keep-alive status. */ - const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->keepAliveMs != 0 ) ? 1 : 0 ); + const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) ? 1 : 0 ); /* A new MQTT connection should only have a possible reference for keep-alive. */ TEST_ASSERT_EQUAL_INT32( keepAliveReference, _pMqttConnection->references ); @@ -1380,14 +1380,14 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Set a short keep-alive interval so this test runs faster. */ - _pMqttConnection->keepAliveMs = SHORT_KEEP_ALIVE_MS; - _pMqttConnection->nextKeepAliveMs = SHORT_KEEP_ALIVE_MS; + _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; + _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - _pMqttConnection->keepAliveJob, - _pMqttConnection->nextKeepAliveMs ) ); + _pMqttConnection->pingreq.job, + _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ) ); /* Sleep to allow ample time for periodic PINGREQ sends and PINGRESP responses. */ IotClock_SleepMs( sleepTimeMs ); @@ -1431,14 +1431,14 @@ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) _pMqttConnection->pNetworkConnection = &waitSem; /* Set a short keep-alive interval so this test runs faster. */ - _pMqttConnection->keepAliveMs = SHORT_KEEP_ALIVE_MS; - _pMqttConnection->nextKeepAliveMs = SHORT_KEEP_ALIVE_MS; + _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = SHORT_KEEP_ALIVE_MS; + _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = SHORT_KEEP_ALIVE_MS; /* Schedule the initial PINGREQ. */ TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - _pMqttConnection->keepAliveJob, - _pMqttConnection->nextKeepAliveMs ) ); + _pMqttConnection->pingreq.job, + _pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs ) ); /* Wait for the keep-alive job to send a PINGREQ. */ IotSemaphore_Wait( &waitSem ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index daa46be6e6..75485a3860 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -122,7 +122,8 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; { \ .jobReference = 1, .type = name, .flags = IOT_MQTT_FLAG_WAITABLE, \ .packetIdentifier = 1, .pMqttPacket = NULL, .packetSize = 0, \ - .notify = { .callback = { 0 } }, .status = IOT_MQTT_STATUS_PENDING, .retry = { 0 } \ + .notify = { .callback = { 0 } }, .status = IOT_MQTT_STATUS_PENDING, \ + .periodic = { .retry = { 0 } } \ } \ } @@ -1673,7 +1674,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) /* Even though no PINGREQ is expected, the keep-alive failure flag should * be cleared (should not crash). */ { - _pMqttConnection->keepAliveFailure = false; + _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 0; DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, @@ -1681,14 +1682,14 @@ TEST( MQTT_Unit_Receive, Pingresp ) pingrespSize, IOT_MQTT_SUCCESS ) ); - TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( 0, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } /* Process a valid PINGRESP. */ { - _pMqttConnection->keepAliveFailure = true; + _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, @@ -1696,7 +1697,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) pingrespSize, IOT_MQTT_SUCCESS ) ); - TEST_ASSERT_EQUAL_INT( false, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( 0, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); } @@ -1704,7 +1705,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) /* An incomplete PINGRESP should not be processed, and the keep-alive failure * flag should not be cleared. */ { - _pMqttConnection->keepAliveFailure = true; + _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); TEST_ASSERT_EQUAL_INT( true, _processBuffer( NULL, @@ -1712,7 +1713,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) pingrespSize - 1, IOT_MQTT_SUCCESS ) ); - TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; @@ -1721,7 +1722,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) /* A PINGRESP should have a remaining length of 0. */ { - _pMqttConnection->keepAliveFailure = true; + _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); pPingresp[ 1 ] = 0x01; @@ -1730,7 +1731,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) pingrespSize, IOT_MQTT_SUCCESS ) ); - TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; @@ -1739,7 +1740,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) /* The PINGRESP control packet type must be 0xd0. */ { - _pMqttConnection->keepAliveFailure = true; + _pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; DECLARE_PACKET( _pPingrespTemplate, pPingresp, pingrespSize ); pPingresp[ 0 ] = 0xd1; @@ -1748,7 +1749,7 @@ TEST( MQTT_Unit_Receive, Pingresp ) pingrespSize, IOT_MQTT_SUCCESS ) ); - TEST_ASSERT_EQUAL_INT( true, _pMqttConnection->keepAliveFailure ); + TEST_ASSERT_EQUAL_INT( 1, _pMqttConnection->pingreq.u.operation.periodic.ping.failure ); TEST_ASSERT_EQUAL_INT( true, _networkCloseCalled ); TEST_ASSERT_EQUAL_INT( true, _disconnectCallbackCalled ); _networkCloseCalled = false; diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 154d3ccb38..4ca78334f4 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -777,7 +777,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) IotSemaphore_t waitSem; /* Adjustment to reference count based on keep-alive status. */ - const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->keepAliveMs != 0 ) ? 1 : 0 ); + const int32_t keepAliveReference = 1 + ( ( _pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) ? 1 : 0 ); #if ( IOT_STATIC_MEMORY_ONLY == 1 ) && ( IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS < 3 ) #error "IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS must be at least 3 for SubscriptionReferences test." From 6709ef23d49367d5e9aabcaa397ec7bc1bfc62bd Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 9 Jul 2019 12:05:01 +0800 Subject: [PATCH 222/844] Fix keep-alive cleanup on systems returning EBUSY (#512) --- doc/guide/developer/porting.txt | 1 + lib/source/mqtt/iot_mqtt_network.c | 28 +++++++++++++++---- lib/source/mqtt/iot_mqtt_operation.c | 18 +++++------- tests/mqtt/unit/iot_tests_mqtt_api.c | 15 ++++++---- tests/mqtt/unit/iot_tests_mqtt_subscription.c | 1 + 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/doc/guide/developer/porting.txt b/doc/guide/developer/porting.txt index e16b248a84..f1c49fd4be 100644 --- a/doc/guide/developer/porting.txt +++ b/doc/guide/developer/porting.txt @@ -94,6 +94,7 @@ Additional include paths required to build the tests: - `third_party/unity/unity` - `third_party/unity/unity/fixture` - `tests/mqtt/access` (when building the MQTT or Shadow tests) +- `tests/mqtt/mock` (when building tests that use the MQTT mocks, such as Shadow or Jobs) In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1` globally when building tests. diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 04605d276e..1d397095f3 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -763,9 +763,17 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; IotNetworkError_t closeStatus = IOT_NETWORK_SUCCESS; IotMqttCallbackParam_t callbackParam = { .u.message = { 0 } }; + void * pNetworkConnection = NULL, * pDisconnectCallbackContext = NULL; + + /* Disconnect callback function. */ + void ( * disconnectCallback )( void *, + IotMqttCallbackParam_t * ) = NULL; + + /* Network close function. */ + IotNetworkError_t ( * closeConnection) ( void * ) = NULL; /* Default free packet function. */ - void (* freePacket)( uint8_t * ) = _IotMqtt_FreePacket; + void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -832,12 +840,20 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason EMPTY_ELSE_MARKER; } + /* Copy the function pointers and contexts, as the MQTT connection may be + * modified after the mutex is released. */ + disconnectCallback = pMqttConnection->disconnectCallback.function; + pDisconnectCallbackContext = pMqttConnection->disconnectCallback.pCallbackContext; + + closeConnection = pMqttConnection->pNetworkInterface->close; + pNetworkConnection = pMqttConnection->pNetworkConnection; + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); /* Close the network connection. */ - if( pMqttConnection->pNetworkInterface->close != NULL ) + if( closeConnection != NULL ) { - closeStatus = pMqttConnection->pNetworkInterface->close( pMqttConnection->pNetworkConnection ); + closeStatus = closeConnection( pNetworkConnection ); if( closeStatus == IOT_NETWORK_SUCCESS ) { @@ -857,14 +873,14 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason } /* Invoke the disconnect callback. */ - if( pMqttConnection->disconnectCallback.function != NULL ) + if( disconnectCallback != NULL ) { /* Set the members of the callback parameter. */ callbackParam.mqttConnection = pMqttConnection; callbackParam.u.disconnectReason = disconnectReason; - pMqttConnection->disconnectCallback.function( pMqttConnection->disconnectCallback.pCallbackContext, - &callbackParam ); + disconnectCallback( pDisconnectCallbackContext, + &callbackParam ); } else { diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index be3b4f5672..f43ba080bf 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -791,6 +791,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, EMPTY_ELSE_MARKER; } + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); + /* Close the connection on failures. */ if( status == false ) { @@ -801,8 +803,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { EMPTY_ELSE_MARKER; } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } /*-----------------------------------------------------------*/ @@ -831,6 +831,8 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, } else { + /* This operation may have already been removed by cleanup of pending + * operations (called from Disconnect). In that case, do nothing here. */ EMPTY_ELSE_MARKER; } @@ -842,15 +844,9 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, _IotMqtt_InvokeSubscriptionCallback( pOperation->pMqttConnection, &callbackParam ); - /* Free any buffers associated with the current PUBLISH message. */ - if( pOperation->u.publish.pReceivedData != NULL ) - { - IotMqtt_FreeMessage( ( void * ) pOperation->u.publish.pReceivedData ); - } - else - { - EMPTY_ELSE_MARKER; - } + /* Free buffers associated with the current PUBLISH message. */ + IotMqtt_Assert( pOperation->u.publish.pReceivedData != NULL ); + IotMqtt_FreeMessage( ( void * ) pOperation->u.publish.pReceivedData ); /* Free the incoming PUBLISH operation. */ IotMqtt_FreeOperation( pOperation ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 2a5d70424c..d8e3605e4d 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -46,6 +46,9 @@ /* MQTT test access include. */ #include "iot_test_access_mqtt.h" +/* Atomics include. */ +#include "iot_atomic.h" + /*-----------------------------------------------------------*/ /** @@ -128,17 +131,17 @@ static bool _publishSetDupCalled = false; /** * @brief Counts how many time #_sendPingreq has been called. */ -static int32_t _pingreqSendCount = 0; +static uint32_t _pingreqSendCount = 0; /** * @brief Counts how many times #_close has been called. */ -static int32_t _closeCount = 0; +static uint32_t _closeCount = 0; /** * @brief Counts how many times #_disconnectCallback has been called. */ -static int32_t _disconnectCallbackCount = 0; +static uint32_t _disconnectCallbackCount = 0; /** * @brief An MQTT connection to share among the tests. @@ -454,7 +457,7 @@ static IotNetworkError_t _close( void * pCloseContext ) /* Silence warnings about unused parameters. */ ( void ) pCloseContext; - _closeCount++; + Atomic_Increment_u32( &_closeCount ); return IOT_NETWORK_SUCCESS; } @@ -472,7 +475,7 @@ static void _disconnectCallback( void * pCallbackContext, /* Only increment counter if the reasons match. */ if( pCallbackParam->u.disconnectReason == *pExpectedReason ) { - _disconnectCallbackCount++; + Atomic_Increment_u32( &_disconnectCallbackCount ); } } @@ -1401,7 +1404,7 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) /* Check that the disconnect callback was invoked once (with reason * "keep-alive timeout"). */ - TEST_ASSERT_EQUAL_INT32( 1, _disconnectCallbackCount ); + TEST_ASSERT_EQUAL_INT32( 1, Atomic_Add_u32( &_disconnectCallbackCount, 0 ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index 4ca78334f4..ec114bf83a 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -818,6 +818,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionReferences ) pIncomingPublish[ i ]->u.publish.publishInfo.pTopicName = "/test"; pIncomingPublish[ i ]->u.publish.publishInfo.topicNameLength = 5; pIncomingPublish[ i ]->u.publish.publishInfo.pPayload = ""; + pIncomingPublish[ i ]->u.publish.pReceivedData = IotMqtt_MallocMessage( 1 ); IotListDouble_InsertHead( &( _pMqttConnection->pendingProcessing ), &( pIncomingPublish[ i ]->link ) ); From dd1e0a5e0005e836688f32e2d20f0d635cdd9bde Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 10 Jul 2019 14:15:15 +0800 Subject: [PATCH 223/844] Create Jobs with automated tests (#513) --- .travis.yml | 11 ++++-- doc/config/main | 3 +- doc/guide/developer/automated_tests.txt | 14 ++++++++ doc/lib/jobs.txt | 48 ++++++++++++++++++++++++- scripts/ci_test_coverage.sh | 27 ++++++++++++++ scripts/ci_test_jobs.sh | 31 ++++++++++++++++ 6 files changed, 130 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index e70397bc6b..3aeb96da09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,10 +34,17 @@ before_install: install: # Install OpenSSL if needed. - if [ "$NETWORK_STACK" = "openssl" ]; then sudo apt-get install -y libssl-dev; fi + # Install dependencies for Jobs tests. + - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sudo apt-get install -y python3-setuptools python3-pip athena-jot; fi + - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then pip3 install --user wheel; fi + - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then pip3 install --user awscli; fi # Install graphviz only for documentation builds. - if [ "$RUN_TEST" = "doc" ]; then sudo apt-get install -y graphviz; fi - # Install lcov and coveralls only for coverage builds. - - if [ "$RUN_TEST" = "coverage" ]; then sudo apt-get install -y lcov; pip install --user cpp-coveralls; fi + # Install dependencies for coverage builds. + - if [ "$RUN_TEST" = "coverage" ]; then sudo apt-get install -y python3-setuptools python3-pip lcov athena-jot; fi + - if [ "$RUN_TEST" = "coverage" ]; then pip3 install --user wheel; fi + - if [ "$RUN_TEST" = "coverage" ]; then pip3 install --user cpp-coveralls; fi + - if [ "$RUN_TEST" = "coverage" ]; then pip3 install --user awscli; fi # Run the test script based on matrix environment variable. script: diff --git a/doc/config/main b/doc/config/main index 48932c13ef..d17138bbae 100644 --- a/doc/config/main +++ b/doc/config/main @@ -26,7 +26,8 @@ AUTOLINK_SUPPORT = NO TAGFILES = doc/tag/logging.tag=../logging \ doc/tag/platform.tag=../platform \ doc/tag/mqtt.tag=../mqtt \ - doc/tag/shadow.tag=../shadow + doc/tag/shadow.tag=../shadow \ + doc/tag/jobs.tag=../jobs # Use the Main page layout file LAYOUT_FILE = doc/config/layout_main.xml diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index 06ff91bbba..d47fdeaf14 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -151,4 +151,18 @@ AWS recommends using the most restrictive policy possible. The following policy @endcode This policy only allows the CI to interact with topics that contain a matching client identifier (which is prefixed with the Travis CI variable `IOT_IDENTIFIER_PREFIX`). A few special topics used by the MQTT tests are also present. The policy will need to be updated if any new topics are used by the tests in the future. + +@section guide_developer_automated_tests_jobs Additional Travis CI setup for Jobs system tests +@brief Additional CI settings for running the [Jobs system tests](@ref jobs_tests). + +The Jobs system tests use AWS CLI to create and delete Jobs. Therefore, additional setup is needed in Travis CI. + +@pre See @ref jobs_system_tests_setup first to set up an AWS account for the Jobs system tests. Note that the coverage tests require an additional Thing to be registered (but can reuse the same AWS credentials). + +Under the "Environment Variables" section of the Travis CI "Settings" page, set the following additional environment variables. +| Name | Value | Notes | +| --------------------- | ----- | ----- | +| AWS_ACCESS_KEY_ID | The AWS access key for the Jobs tests IAM user | @attention This value should be hidden in the build log! | +| AWS_DEFAULT_REGION | The AWS region to use with the Jobs tests | Should be the region where the Jobs test Thing is registered.
This value does not need to be hidden in the build log. | +| AWS_SECRET_ACCESS_KEY | The AWS secret access key for the Jobs tests IAM user | @attention This value should be hidden in the build log! | */ diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index b9ee8f0df1..0eb29a02ec 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -50,12 +50,58 @@ In addition to the components above, the Jobs library also depends on C standard @brief Tests written for the Jobs library. The Jobs tests reside in the `tests/jobs` directory. They are divided into the following subdirectories: -- `system`: Jobs system tests. These tests require a network connection and AWS IoT credentials. The command line option `-n` may be passed to the test executable to disable these tests. +- `system`: Jobs system tests. These tests require a network connection and AWS IoT credentials. They also need an AWS account and registered Thing; see @ref jobs_system_tests_setup for instructions to configure an AWS account to run these tests. The command line option `-n` may be passed to the test executable to disable these tests. - `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) See @subpage jobs_tests_config for configuration settings that change the behavior of the Jobs system tests. The Jobs unit tests require no special configuration. The current Jobs tests use the [Unity test framework](http://www.throwtheswitch.org/unity/). See @ref building_tests for a guide on running the tests. + +@section jobs_system_tests_setup Setting up Jobs system tests +@brief How to set up the Jobs system tests. + +@pre The steps below assume basic familiarity with AWS and AWS CLI. + +The Jobs system tests require Jobs to be created with an AWS account. AWS does not provide the functionality to create Jobs using the Device API; therefore, Jobs will have to be created using another method. + +See @ref guide_developer_automated_tests_jobs for additional setup for running the Jobs tests on Travis CI. + +1. Register a Thing for the tests. This needs to be done only once.
+Follow [this guide](https://docs.aws.amazon.com/iot/latest/developerguide/register-device.html) to register a new Thing. +2. Create an IAM user for the tests. This needs to be done only once.
+Follow [this guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) to create an IAM user for the Jobs tests. + - Only programmatic access is required. + - Save the access key and secret access key for this user. + - The following policy grants the necessary permissions for this user. Replace `` and `` with your AWS region and account number, respectively; replace `` with the name of the Thing registered in step 1. +``` +{ + "Version": "2012-10-17", + "Statement": { + "Effect": "Allow", + "Action": [ + "iot:CreateJob", + "iot:DeleteJob", + "iot:DeleteJobExecution" + ], + "Resource": [ + "arn:aws:iot:::job/*", + "arn:aws:iot:::thing/" + ] + } +} +``` +3. Create two Jobs for the tests. This must be done every time the system tests are run.
+At least two new jobs should be in the `QUEUED` state each time the tests are run. To create a Job, the following AWS CLI command may be used. Replace `UniqueId` with a Job ID that is unique, and `ThingARN` with the ARN of the Thing registered in step 1. The `--document` argument can be followed by any JSON string. +```sh +aws iot create-job \ + --job-id UniqueId \ + --targets ThingARN \ + --document '{"action":"print","message":"Hello world!"}' +``` +When the Jobs tests are finished, the Jobs it uses are no longer needed. Jobs can be deleted with the following command, where `UniqueId` is the Job ID used to create the Job. +```sh +aws iot delete-job --job-id UniqueId --force +``` */ /** diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index f6a943aacc..1b8c66c634 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -6,6 +6,31 @@ # Exit on any nonzero return code. set -ev +# Query the AWS account ID. +AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account') + +# Prefix of Job IDs used in the tests, set by the create_jobs function. +JOB_PREFIX="" + +# Function to create Jobs to use in the tests. +create_jobs() { + JOB_PREFIX=$IOT_IDENTIFIER-$(jot -r 1 1 100000) + aws iot create-job \ + --job-id $JOB_PREFIX-1 \ + --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ + --document '{"action":"print","message":"Hello world!"}' + aws iot create-job \ + --job-id $JOB_PREFIX-2 \ + --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ + --document '{"action":"print","message":"Hello world!"}' +} + +# Function to delete Jobs and clean up the tests. +delete_jobs() { + aws iot delete-job --job-id $JOB_PREFIX-1 --force + aws iot delete-job --job-id $JOB_PREFIX-2 --force +} + # Build tests and demos against AWS IoT with coverage. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_SHADOW_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DAWS_IOT_TEST_JOBS_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG -DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" -DIOT_TEST_COVERAGE=1 --coverage" make -j2 @@ -20,7 +45,9 @@ make -j2 ./bin/aws_iot_tests_shadow # Run Jobs tests with code coverage. +create_jobs ./bin/aws_iot_tests_jobs +delete_jobs # Generate code coverage results, but only for files in lib/. lcov --directory . --capture --output-file coverage.info diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh index 6ee89ba134..a95dda4ffc 100644 --- a/scripts/ci_test_jobs.sh +++ b/scripts/ci_test_jobs.sh @@ -5,6 +5,14 @@ # Exit on any nonzero return code. set -e +# Query the AWS account ID. +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + AWS_ACCOUNT_ID=$(aws sts get-caller-identity --output text --query 'Account') +fi + +# Prefix of Job IDs used in the tests, set by the create_jobs function. +JOB_PREFIX="" + # Function for running the existing test executables. run_tests() { # For commit builds, run the full Jobs tests. For pull request builds, @@ -17,6 +25,25 @@ run_tests() { fi } +# Function to create Jobs to use in the tests. +create_jobs() { + JOB_PREFIX=$IOT_IDENTIFIER-$(jot -r 1 1 100000) + aws iot create-job \ + --job-id $JOB_PREFIX-1 \ + --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ + --document '{"action":"print","message":"Hello world!"}' + aws iot create-job \ + --job-id $JOB_PREFIX-2 \ + --targets arn:aws:iot:$AWS_DEFAULT_REGION:$AWS_ACCOUNT_ID:thing/$IOT_IDENTIFIER \ + --document '{"action":"print","message":"Hello world!"}' +} + +# Function to delete Jobs and clean up the tests. +delete_jobs() { + aws iot delete-job --job-id $JOB_PREFIX-1 --force + aws iot delete-job --job-id $JOB_PREFIX-2 --force +} + # CMake compiler flags for building Jobs. CMAKE_FLAGS="$AWS_IOT_CREDENTIAL_DEFINES -DAWS_IOT_TEST_JOBS_THING_NAME=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" @@ -25,11 +52,15 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FL make -j2 aws_iot_tests_jobs # Run tests. +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi run_tests +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi # Rebuild in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" make -j2 aws_iot_tests_jobs # Run tests in static memory mode. +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi run_tests +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi From a8c70973721991c4e68e20c8cdc13675d147dd70 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 10 Jul 2019 19:02:48 +0800 Subject: [PATCH 224/844] Run Jobs tests on CI with actual Jobs (#514) --- scripts/ci_test_coverage.sh | 2 + scripts/ci_test_jobs.sh | 3 + tests/jobs/system/aws_iot_tests_jobs_system.c | 146 ++++++++++++++++-- 3 files changed, 136 insertions(+), 15 deletions(-) diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 1b8c66c634..2b309e2d2d 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -46,8 +46,10 @@ make -j2 # Run Jobs tests with code coverage. create_jobs +trap "delete_jobs" EXIT ./bin/aws_iot_tests_jobs delete_jobs +trap - EXIT # Generate code coverage results, but only for files in lib/. lcov --directory . --capture --output-file coverage.info diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh index a95dda4ffc..3387d172b1 100644 --- a/scripts/ci_test_jobs.sh +++ b/scripts/ci_test_jobs.sh @@ -53,6 +53,7 @@ make -j2 aws_iot_tests_jobs # Run tests. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then trap "delete_jobs" EXIT; fi run_tests if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi @@ -64,3 +65,5 @@ make -j2 aws_iot_tests_jobs if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then create_jobs; fi run_tests if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then delete_jobs; fi + +if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then trap - EXIT; fi diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index fde34e42ad..83f8ada4da 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -52,6 +52,9 @@ /* Test framework includes. */ #include "unity_fixture.h" +/* JSON utilities include. */ +#include "iot_json_utils.h" + /*-----------------------------------------------------------*/ /** @@ -117,6 +120,16 @@ static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; */ static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; +/** + * @brief Job IDs retrieved from the AWS IoT Jobs service. + */ +static char _pJobIds[ 2 ][ JOBS_MAX_ID_LENGTH + 1 ] = { { 0 } }; + +/** + * @brief Lengths of the Job IDs retrieved from the AWS IoT Jobs service. + */ +static size_t _pJobIdLengths[ 2 ] = { 0 }; + /*-----------------------------------------------------------*/ /** @@ -148,6 +161,71 @@ static void _operationComplete( void * pArgument, /*-----------------------------------------------------------*/ +/** + * @brief Parses Job IDs from a GET PENDING Jobs response. + */ +static void _parseJobIds( const AwsIotJobsResponse_t * pJobsResponse ) +{ + bool status = true; + int32_t i = 0; + const char * pInProgressJobs = NULL, * pParseStart = NULL, * pJobId = NULL; + size_t inProgressJobsLength = 0, parseLength = 0, jobIdLength = 0; + + /* In-progress Jobs for this device will interfere with the tests; fail if + * any in-progress Jobs are present. */ + status = IotJsonUtils_FindJsonValue( pJobsResponse->pJobsResponse, + pJobsResponse->jobsResponseLength, + "inProgressJobs", 14, + &pInProgressJobs, + &inProgressJobsLength ); + TEST_ASSERT_EQUAL_INT( true, status ); + TEST_ASSERT_NOT_NULL( pInProgressJobs ); + TEST_ASSERT_EQUAL_MESSAGE( 2, inProgressJobsLength, "In-progress Jobs detected. Tests will not run." ); + + /* Parse for the list of queued Jobs. This is where parsing for Job IDs will + * start. */ + status = IotJsonUtils_FindJsonValue( pJobsResponse->pJobsResponse, + pJobsResponse->jobsResponseLength, + "queuedJobs", + 10, + &pParseStart, + &parseLength ); + TEST_ASSERT_EQUAL_INT_MESSAGE( true, status, "Response did not contain any queued Jobs." ); + TEST_ASSERT_NOT_NULL( pParseStart ); + TEST_ASSERT_GREATER_THAN( 0, parseLength ); + + /* Parse the Job IDs of the first two queued Jobs. */ + for( i = 0; i < 2; i++ ) + { + status = IotJsonUtils_FindJsonValue( pParseStart, + parseLength, + "jobId", + 5, + &pJobId, + &jobIdLength ); + TEST_ASSERT_EQUAL_INT_MESSAGE( true, status, "Response did not contain enough queued Jobs." ); + TEST_ASSERT_NOT_NULL( pJobId ); + TEST_ASSERT_GREATER_THAN( 0, jobIdLength ); + TEST_ASSERT_LESS_THAN_MESSAGE( JOBS_MAX_ID_LENGTH, + jobIdLength - 2, + "Response contains a Job ID that is too long." ); + + /* Job ID must start and end with quotes, as it is a string. */ + TEST_ASSERT_EQUAL( '"', *pJobId ); + TEST_ASSERT_EQUAL( '"', *( pJobId + jobIdLength - 1 ) ); + + /* Copy the Job ID, excluding the quotes. Save its length too. */ + ( void ) memcpy( _pJobIds[ i ], pJobId + 1, jobIdLength - 2 ); + _pJobIdLengths[ i ] = jobIdLength - 2; + + /* To find the next Job ID, it's sufficient to search again after the current one. */ + parseLength -= ( pJobId - pParseStart ); + pParseStart = pJobId; + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Common code of the Jobs Async tests. */ @@ -210,8 +288,8 @@ static void _jobsAsyncTest( _jobsOperationType_t type, TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); /* Set a Job ID that doesn't exist. */ - requestInfo.pJobId = "NOTAJOBID"; - requestInfo.jobIdLength = 9; + requestInfo.pJobId = _pJobIds[ 0 ]; + requestInfo.jobIdLength = _pJobIdLengths[ 0 ]; status = AwsIotJobs_Update( &requestInfo, &updateInfo, @@ -242,6 +320,8 @@ static void _jobsBlockingTest( _jobsOperationType_t type, AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + const char * pJobId = NULL; + size_t jobIdLength = 0; /* Set the Jobs request parameters. */ requestInfo.mqttConnection = _mqttConnection; @@ -282,9 +362,8 @@ static void _jobsBlockingTest( _jobsOperationType_t type, /* The only remaining valid type is UPDATE. */ TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); - /* Set a Job ID that doesn't exist. */ - requestInfo.pJobId = "NOTAJOBID"; - requestInfo.jobIdLength = 9; + requestInfo.pJobId = _pJobIds[ 0 ]; + requestInfo.jobIdLength = _pJobIdLengths[ 0 ]; status = AwsIotJobs_TimedUpdate( &requestInfo, &updateInfo, @@ -300,6 +379,37 @@ static void _jobsBlockingTest( _jobsOperationType_t type, TEST_ASSERT_NOT_NULL( jobsResponse.pJobsResponse ); TEST_ASSERT_GREATER_THAN( 0, jobsResponse.jobsResponseLength ); + /* Save the list of queued Jobs. */ + if( type == JOBS_GET_PENDING ) + { + _parseJobIds( &jobsResponse ); + + /* Job IDs must be unique; check that the parsed IDs are different. */ + if( _pJobIdLengths[ 0 ] == _pJobIdLengths[ 1 ] ) + { + TEST_ASSERT_NOT_EQUAL( 0, strncmp( _pJobIds[ 0 ], + _pJobIds[ 1 ], + _pJobIdLengths[ 0 ] ) ); + } + } + else + { + /* Check that the Job ID matches the first queued Job. The Jobs service + * provides an ordering guarantee. Don't check for UPDATE; its response + * does not include the Job ID. */ + if( type != JOBS_UPDATE ) + { + TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( jobsResponse.pJobsResponse, + jobsResponse.jobsResponseLength, + "jobId", + 5, + &pJobId, + &jobIdLength ) ); + TEST_ASSERT_EQUAL( _pJobIdLengths[ 0 ], jobIdLength - 2 ); + TEST_ASSERT_EQUAL_STRING_LEN( _pJobIds[ 0 ], pJobId + 1, _pJobIdLengths[ 0 ] ); + } + } + /* Free the allocated Jobs response. */ IotTest_Free( ( void * ) jobsResponse.pJobsResponse ); } @@ -423,14 +533,22 @@ TEST_TEAR_DOWN( Jobs_System ) */ TEST_GROUP_RUNNER( Jobs_System ) { + /* The tests for Get Pending must run first, as they retrieve the list of + * Jobs for the other tests. */ RUN_TEST_CASE( Jobs_System, GetPendingAsync ); RUN_TEST_CASE( Jobs_System, GetPendingBlocking ); - RUN_TEST_CASE( Jobs_System, StartNextAsync ); - RUN_TEST_CASE( Jobs_System, StartNextBlocking ); - RUN_TEST_CASE( Jobs_System, DescribeAsync ); - RUN_TEST_CASE( Jobs_System, DescribeBlocking ); - RUN_TEST_CASE( Jobs_System, UpdateAsync ); - RUN_TEST_CASE( Jobs_System, UpdateBlocking ); + + /* Only run the following tests if 2 queued Jobs are available. */ + if( ( _pJobIdLengths[ 0 ] > 0 ) && ( _pJobIdLengths[ 1 ] > 0 ) ) + { + RUN_TEST_CASE( Jobs_System, StartNextAsync ); + RUN_TEST_CASE( Jobs_System, StartNextBlocking ); + RUN_TEST_CASE( Jobs_System, DescribeAsync ); + RUN_TEST_CASE( Jobs_System, DescribeBlocking ); + RUN_TEST_CASE( Jobs_System, UpdateAsync ); + RUN_TEST_CASE( Jobs_System, UpdateBlocking ); + } + RUN_TEST_CASE( Jobs_System, PersistentSubscriptions ); } @@ -501,8 +619,7 @@ TEST( Jobs_System, DescribeBlocking ) */ TEST( Jobs_System, UpdateAsync ) { - /* The Job ID used in this test does not exist, expect NOT_FOUND. */ - _jobsAsyncTest( JOBS_UPDATE, AWS_IOT_JOBS_NOT_FOUND ); + _jobsAsyncTest( JOBS_UPDATE, AWS_IOT_JOBS_SUCCESS ); } /*-----------------------------------------------------------*/ @@ -512,8 +629,7 @@ TEST( Jobs_System, UpdateAsync ) */ TEST( Jobs_System, UpdateBlocking ) { - /* The Job ID used in this test does not exist, expect NOT_FOUND. */ - _jobsBlockingTest( JOBS_UPDATE, AWS_IOT_JOBS_NOT_FOUND ); + _jobsBlockingTest( JOBS_UPDATE, AWS_IOT_JOBS_SUCCESS ); } /*-----------------------------------------------------------*/ From be6ab1e0363178d08498a4324e416cae0da6cc7e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 10 Jul 2019 21:23:43 +0800 Subject: [PATCH 225/844] Allow either pending Job to match in test (#515) --- tests/jobs/system/aws_iot_tests_jobs_system.c | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 83f8ada4da..87fb82fc94 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -316,6 +316,8 @@ static void _jobsAsyncTest( _jobsOperationType_t type, static void _jobsBlockingTest( _jobsOperationType_t type, AwsIotJobsError_t expectedResult ) { + int32_t i = 0; + bool jobIdMatch = false; AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; @@ -394,9 +396,8 @@ static void _jobsBlockingTest( _jobsOperationType_t type, } else { - /* Check that the Job ID matches the first queued Job. The Jobs service - * provides an ordering guarantee. Don't check for UPDATE; its response - * does not include the Job ID. */ + /* Check that the Job ID matches one of the queued jobs. Don't check for + * UPDATE; its response does not include the Job ID. */ if( type != JOBS_UPDATE ) { TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( jobsResponse.pJobsResponse, @@ -405,8 +406,20 @@ static void _jobsBlockingTest( _jobsOperationType_t type, 5, &pJobId, &jobIdLength ) ); - TEST_ASSERT_EQUAL( _pJobIdLengths[ 0 ], jobIdLength - 2 ); - TEST_ASSERT_EQUAL_STRING_LEN( _pJobIds[ 0 ], pJobId + 1, _pJobIdLengths[ 0 ] ); + + for( i = 0; i < 2; i++ ) + { + if( _pJobIdLengths[ i ] == jobIdLength - 2 ) + { + if( strncmp( _pJobIds[ i ], pJobId + 1, jobIdLength - 2 ) == 0 ) + { + jobIdMatch = true; + break; + } + } + } + + TEST_ASSERT_EQUAL_INT( true, jobIdMatch ); } } From 0d894331248a1247eac726dac81cee5f396df723 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 11 Jul 2019 12:30:47 +0800 Subject: [PATCH 226/844] Add test for Shadow set callback malloc failures (#516) --- lib/source/jobs/aws_iot_jobs_subscription.c | 8 +-- lib/source/shadow/aws_iot_shadow_api.c | 24 +++++++-- .../shadow/aws_iot_shadow_subscription.c | 8 +-- tests/shadow/unit/aws_iot_tests_shadow_api.c | 49 +++++++++++++++++++ 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index d32dd88045..07a4ef673d 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -249,9 +249,11 @@ void _AwsIotJobs_DestroySubscription( void * pData ) { _jobsSubscription_t * pSubscription = ( _jobsSubscription_t * ) pData; - /* Free the topic buffer. It should not be NULL. */ - AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); - AwsIotJobs_FreeString( pSubscription->pTopicBuffer ); + /* Free the topic buffer if allocated. */ + if( pSubscription->pTopicBuffer != NULL ) + { + AwsIotJobs_FreeString( pSubscription->pTopicBuffer ); + } /* Free memory used by subscription. */ AwsIotJobs_FreeSubscription( pSubscription ); diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 3cd76d469b..7cd53f5e90 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -111,6 +111,9 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio * @param[in] pSubscription Shadow subscriptions object for callback. * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or * @ref mqtt_function_timedunsubscribe. + * + * @return #AWS_IOT_SHADOW_SUCCESS, #AWS_IOT_SHADOW_NO_MEMORY, or + * #AWS_IOT_SHADOW_MQTT_ERROR. */ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, _shadowCallbackType_t type, @@ -132,7 +135,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, /** * @brief Invoked when a document is received on the Shadow DELTA callback. * - * @param[in] pArgument Ignored. + * @param[in] pArgument Associated Shadow subscription. * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). */ static void _deltaCallbackWrapper( void * pArgument, @@ -141,7 +144,7 @@ static void _deltaCallbackWrapper( void * pArgument, /** * @brief Invoked when a document is received on the Shadow UPDATED callback. * - * @param[in] pArgument Ignored. + * @param[in] pArgument Associated Shadow subscription. * @param[in] pMessage The received UPDATED document (as an MQTT PUBLISH message). */ static void _updatedCallbackWrapper( void * pArgument, @@ -390,11 +393,20 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio pThingName, _pAwsIotShadowCallbackNames[ type ] ); - pSubscription->callbacks[ type ] = *pCallbackInfo; status = _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, IotMqtt_TimedSubscribe ); + + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + pSubscription->callbacks[ type ] = *pCallbackInfo; + } + else + { + /* On failure, check if this subscription can be removed. */ + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); + } } /* Do nothing; set return value to success. */ else @@ -527,18 +539,20 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt pSubscription->pThingName, _pAwsIotShadowCallbackNames[ type ] ); + IOT_FUNCTION_CLEANUP_BEGIN(); + /* MQTT subscribe should check the subscription topic buffer. */ if( mqttOperation == IotMqtt_TimedSubscribe ) { /* If the current subscription has no topic buffer, assign it the current - * topic buffer. Otherwise, free the current topic buffer. */ + * topic buffer. */ if( pSubscription->pTopicBuffer == NULL ) { pSubscription->pTopicBuffer = pTopicFilter; } } - IOT_FUNCTION_EXIT_NO_CLEANUP(); + IOT_FUNCTION_CLEANUP_END(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index dfa2c523e8..db0f2460c1 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -249,9 +249,11 @@ void _AwsIotShadow_DestroySubscription( void * pData ) { _shadowSubscription_t * pSubscription = ( _shadowSubscription_t * ) pData; - /* Free the topic buffer. It should not be NULL. */ - AwsIotShadow_Assert( pSubscription->pTopicBuffer != NULL ); - AwsIotShadow_FreeString( pSubscription->pTopicBuffer ); + /* Free the topic buffer if allocated. */ + if( pSubscription->pTopicBuffer != NULL ) + { + AwsIotShadow_FreeString( pSubscription->pTopicBuffer ); + } /* Free memory used by subscription. */ AwsIotShadow_FreeSubscription( pSubscription ); diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index f5afa2a744..abf46bbb7c 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -139,6 +139,7 @@ TEST_GROUP_RUNNER( Shadow_Unit_API ) RUN_TEST_CASE( Shadow_Unit_API, DeleteMallocFail ); RUN_TEST_CASE( Shadow_Unit_API, GetMallocFail ); RUN_TEST_CASE( Shadow_Unit_API, UpdateMallocFail ); + RUN_TEST_CASE( Shadow_Unit_API, SetCallbackMallocFail ); } /*-----------------------------------------------------------*/ @@ -543,3 +544,51 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of the Shadow set callback functions when memory + * allocation fails at various points. + */ +TEST( Shadow_Unit_API, SetCallbackMallocFail ) +{ + int32_t i = 0, mqttErrorCount = 0; + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; + + /* Set a short timeout so this test runs faster. */ + _AwsIotShadowMqttTimeoutMs = 75; + + /* A non-NULL callback function. */ + callbackInfo.function = ( void ( * )( void *, AwsIotShadowCallbackParam_t * ) ) 0x01; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Shadow set callback. Memory allocation will fail at various times + * during this call. */ + status = AwsIotShadow_SetDeltaCallback( _pMqttConnection, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + 0, + &callbackInfo ); + + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + break; + } + else if( status == AWS_IOT_SHADOW_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NO_MEMORY, status ); + } + } + + /* Allow 1 MQTT error, caused by failure to allocate memory for a SUBACK. */ + CHECK_MQTT_ERROR_COUNT( 1, mqttErrorCount ); +} + +/*-----------------------------------------------------------*/ From 52d3024edd0bcea5b639bbe049fd10b0c40ea8f0 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 11 Jul 2019 16:02:50 +0800 Subject: [PATCH 227/844] Search lists for incoming Shadow callbacks (#517) --- lib/include/private/aws_iot_jobs_internal.h | 16 +- lib/include/private/aws_iot_shadow_internal.h | 7 +- lib/source/jobs/aws_iot_jobs_api.c | 370 ++++++++++++++++++ lib/source/jobs/aws_iot_jobs_operation.c | 3 +- lib/source/jobs/aws_iot_jobs_subscription.c | 48 +-- lib/source/shadow/aws_iot_shadow_api.c | 65 ++- lib/source/shadow/aws_iot_shadow_operation.c | 3 +- .../shadow/aws_iot_shadow_subscription.c | 48 +-- tests/jobs/system/aws_iot_tests_jobs_system.c | 64 +++ tests/jobs/unit/aws_iot_tests_jobs_api.c | 49 +++ 10 files changed, 610 insertions(+), 63 deletions(-) diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index e9b2daf0a6..ae80c99b75 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -293,6 +293,15 @@ typedef enum _jobsOperationType SET_NOTIFY_NEXT_CALLBACK = 5 /**< @ref jobs_function_setnotifynextcallback */ } _jobsOperationType_t; +/** + * @brief Enumerations representing each of the Jobs callback functions. + */ +typedef enum _jobsCallbackType +{ + NOTIFY_PENDING_CALLBACK = 0, /**< Pending Job notification callback. */ + NOTIFY_NEXT_CALLBACK = 1 /**< Next Job notification callback. */ +} _jobsCallbackType_t; + /** * @brief Parameter to #_AwsIotJobs_GenerateJsonRequest. */ @@ -460,11 +469,13 @@ AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * /*----------------------- Jobs subscription functions -----------------------*/ /** - * @brief Find a Jobs subscription object. Creates a new subscription object + * @brief Find a Jobs subscription object. May create a new subscription object * and adds it to the subscription list if not found. * * @param[in] pThingName Thing Name in the subscription object. * @param[in] thingNameLength Length of `pThingName`. + * @param[in] createIfNotFound If `true`, attempt to create a new subscription + * object if no match is found. * * @return Pointer to a Jobs subscription object, either found or newly * allocated. Returns `NULL` if no subscription object is found and a new @@ -473,7 +484,8 @@ AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * * @note This function should be called with the subscription list mutex locked. */ _jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, - size_t thingNameLength ); + size_t thingNameLength, + bool createIfNotFound ); /** * @brief Remove a Jobs subscription object from the subscription list if diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 144c2fe66e..9308b6d34c 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -419,11 +419,13 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn /*---------------------- Shadow subscription functions ----------------------*/ /** - * @brief Find a Shadow subscription object. Creates a new subscription object + * @brief Find a Shadow subscription object. May create a new subscription object * and adds it to the subscription list if not found. * * @param[in] pThingName Thing Name in the subscription object. * @param[in] thingNameLength Length of `pThingName`. + * @param[in] createIfNotFound If `true`, attempt to create a new subscription + * object if no match is found. * * @return Pointer to a Shadow subscription object, either found or newly * allocated. Returns `NULL` if no subscription object is found and a new @@ -432,7 +434,8 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn * @note This function should be called with the subscription list mutex locked. */ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, - size_t thingNameLength ); + size_t thingNameLength, + bool createIfNotFound ); /** * @brief Remove a Shadow subscription object from the subscription list if diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index de69c1c3cb..2ba745750a 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -39,6 +39,9 @@ /* Error handling include. */ #include "private/iot_error.h" +/* MQTT include. */ +#include "iot_mqtt.h" + /* Validate Jobs configuration settings. */ #if AWS_IOT_JOBS_ENABLE_ASSERTS != 0 && AWS_IOT_JOBS_ENABLE_ASSERTS != 1 #error "AWS_IOT_JOBS_ENABLE_ASSERTS must be 0 or 1." @@ -77,6 +80,60 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, const AwsIotJobsUpdateInfo_t * pUpdateInfo ); +/** + * @brief Common function for setting Jobs callbacks. + * + * @param[in] mqttConnection The MQTT connection to use. + * @param[in] type Type of Jobs callback. + * @param[in] pThingName Thing Name for Jobs callback. + * @param[in] thingNameLength Length of `pThingName`. + * @param[in] pCallbackInfo Callback information to set. + * + * @return #AWS_IOT_JOBS_SUCCESS, #AWS_IOT_JOBS_BAD_PARAMETER, + * #AWS_IOT_JOBS_NO_MEMORY, or #AWS_IOT_JOBS_MQTT_ERROR. + */ +static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, + _jobsCallbackType_t type, + const char * pThingName, + size_t thingNameLength, + const AwsIotJobsCallbackInfo_t * pCallbackInfo ); + +/** + * @brief Change the subscriptions for Jobs callbacks, either by subscribing + * or unsubscribing. + * + * @param[in] mqttConnection The MQTT connection to use. + * @param[in] type Type of Jobs callback. + * @param[in] pSubscription Jobs subscriptions object for callback. + * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or + * @ref mqtt_function_timedunsubscribe. + * + * @return #AWS_IOT_JOBS_SUCCESS, #AWS_IOT_JOBS_NO_MEMORY, or + * #AWS_IOT_JOBS_MQTT_ERROR. + */ +static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, + _jobsCallbackType_t type, + _jobsSubscription_t * pSubscription, + AwsIotMqttFunction_t mqttOperation ); + +/** + * @brief Invoked when a document is received on the Jobs NOTIFY PENDING callback. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage The received Jobs document (as an MQTT PUBLISH message). + */ +static void _notifyPendingCallbackWrapper( void * pArgument, + IotMqttCallbackParam_t * pMessage ); + +/** + * @brief Invoked when a document is received on the Jobs NOTIFY NEXT callback. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage The received Jobs document (as an MQTT PUBLISH message). + */ +static void _notifyNextCallbackWrapper( void * pArgument, + IotMqttCallbackParam_t * pMessage ); + /*-----------------------------------------------------------*/ /** @@ -300,6 +357,283 @@ static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, /*-----------------------------------------------------------*/ +static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, + _jobsCallbackType_t type, + const char * pThingName, + size_t thingNameLength, + const AwsIotJobsCallbackInfo_t * pCallbackInfo ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + bool subscriptionMutexLocked = false; + _jobsSubscription_t * pSubscription = NULL; + + /* Validate Thing Name. */ + if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == false ) + { + IotLogError( "Thing Name for Jobs %s callback is not valid.", + _pAwsIotJobsCallbackNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* Check that a callback function is provided. */ + if( pCallbackInfo != NULL ) + { + if( pCallbackInfo->function == NULL ) + { + IotLogError( "Callback function must be provided for Jobs %s callback.", + _pAwsIotJobsCallbackNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + } + + IotLogInfo( "(%.*s) Modifying Jobs %s callback.", + thingNameLength, + pThingName, + _pAwsIotJobsCallbackNames[ type ] ); + + /* Lock the subscription list mutex to check for an existing subscription + * object. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + subscriptionMutexLocked = true; + + /* Check for an existing subscription. This function will attempt to allocate + * a new subscription if not found. */ + pSubscription = _AwsIotJobs_FindSubscription( pThingName, thingNameLength, true ); + + if( pSubscription == NULL ) + { + /* No existing subscription was found, and no new subscription could be + * allocated. */ + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + /* Check for an existing callback. */ + if( pSubscription->callbacks[ type ].function != NULL ) + { + /* Replace existing callback. */ + if( pCallbackInfo != NULL ) + { + IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", + thingNameLength, + pThingName, + _pAwsIotJobsCallbackNames[ type ] ); + + pSubscription->callbacks[ type ] = *pCallbackInfo; + } + /* Remove existing callback. */ + else + { + IotLogInfo( "(%.*s) Removing existing %s callback.", + thingNameLength, + pThingName, + _pAwsIotJobsCallbackNames[ type ] ); + + /* Unsubscribe, then clear the callback information. */ + ( void ) _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_TimedUnsubscribe ); + ( void ) memset( &( pSubscription->callbacks[ type ] ), + 0x00, + sizeof( AwsIotJobsCallbackInfo_t ) ); + + /* Check if this subscription object can be removed. */ + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); + } + } + /* No existing callback. */ + else + { + /* Add new callback. */ + if( pCallbackInfo != NULL ) + { + IotLogInfo( "(%.*s) Adding new %s callback.", + thingNameLength, + pThingName, + _pAwsIotJobsCallbackNames[ type ] ); + + status = _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_TimedSubscribe ); + + if( status == AWS_IOT_JOBS_SUCCESS ) + { + pSubscription->callbacks[ type ] = *pCallbackInfo; + } + else + { + /* On failure, check if this subscription can be removed. */ + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); + } + } + /* Do nothing; set return value to success. */ + else + { + status = AWS_IOT_JOBS_SUCCESS; + } + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( subscriptionMutexLocked == true ) + { + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + } + + IotLogInfo( "(%.*s) Jobs %s callback operation complete with result %s.", + thingNameLength, + pThingName, + _pAwsIotJobsCallbackNames[ type ], + AwsIotJobs_strerror( status ) ); + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttConnection, + _jobsCallbackType_t type, + _jobsSubscription_t * pSubscription, + AwsIotMqttFunction_t mqttOperation ) +{ + IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + AwsIotTopicInfo_t topicInfo = { 0 }; + char * pTopicFilter = NULL; + uint16_t topicFilterLength = 0; + + /* Lookup table for Jobs callback suffixes. */ + const char * const pCallbackSuffix[ JOBS_CALLBACK_COUNT ] = + { + JOBS_NOTIFY_PENDING_CALLBACK_STRING, /* Notify pending callback. */ + JOBS_NOTIFY_NEXT_CALLBACK_STRING /* Notify next callback. */ + }; + + /* Lookup table for Jobs callback suffix lengths. */ + const uint16_t pCallbackSuffixLength[ JOBS_CALLBACK_COUNT ] = + { + JOBS_NOTIFY_PENDING_CALLBACK_STRING_LENGTH, /* Notify pending callback. */ + JOBS_NOTIFY_NEXT_CALLBACK_STRING_LENGTH /* Notify next callback. */ + }; + + /* Lookup table for Jobs callback function wrappers. */ + const AwsIotMqttCallbackFunction_t pCallbackWrapper[ JOBS_CALLBACK_COUNT ] = + { + _notifyPendingCallbackWrapper, /* Notify pending callback. */ + _notifyNextCallbackWrapper, /* Notify next callback. */ + }; + + /* MQTT operation may only be subscribe or unsubscribe. */ + AwsIotJobs_Assert( ( mqttOperation == IotMqtt_TimedSubscribe ) || + ( mqttOperation == IotMqtt_TimedUnsubscribe ) ); + + /* Use the subscription's topic buffer if available. */ + if( pSubscription->pTopicBuffer != NULL ) + { + pTopicFilter = pSubscription->pTopicBuffer; + } + + /* Generate the topic for the MQTT subscription. */ + topicInfo.pThingName = pSubscription->pThingName; + topicInfo.thingNameLength = pSubscription->thingNameLength; + topicInfo.longestSuffixLength = JOBS_LONGEST_SUFFIX_LENGTH; + topicInfo.mallocString = AwsIotJobs_MallocString; + topicInfo.pOperationName = pCallbackSuffix[ type ]; + topicInfo.operationNameLength = pCallbackSuffixLength[ type ]; + + if( AwsIot_GenerateOperationTopic( &topicInfo, + &pTopicFilter, + &topicFilterLength ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + + IotLogDebug( "%s subscription for %.*s", + mqttOperation == IotMqtt_TimedSubscribe ? "Adding" : "Removing", + topicFilterLength, + pTopicFilter ); + + /* Set the members of the MQTT subscription. */ + subscription.qos = IOT_MQTT_QOS_1; + subscription.pTopicFilter = pTopicFilter; + subscription.topicFilterLength = topicFilterLength; + subscription.callback.pCallbackContext = NULL; + subscription.callback.function = pCallbackWrapper[ type ]; + + /* Call the MQTT operation function. */ + mqttStatus = mqttOperation( mqttConnection, + &subscription, + 1, + 0, + _AwsIotJobsMqttTimeoutMs ); + + /* Check the result of the MQTT operation. */ + if( mqttStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotJobsCallbackNames[ type ], + IotMqtt_strerror( mqttStatus ) ); + + /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + if( mqttStatus == IOT_MQTT_NO_MEMORY ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + } + else + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_MQTT_ERROR ); + } + } + + IotLogDebug( "Successfully %s %.*s Jobs %s callback.", + mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + pSubscription->thingNameLength, + pSubscription->pThingName, + _pAwsIotJobsCallbackNames[ type ] ); + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* MQTT subscribe should check the subscription topic buffer. */ + if( mqttOperation == IotMqtt_TimedSubscribe ) + { + /* If the current subscription has no topic buffer, assign it the current + * topic buffer. */ + if( pSubscription->pTopicBuffer == NULL ) + { + pSubscription->pTopicBuffer = pTopicFilter; + } + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +static void _notifyPendingCallbackWrapper( void * pArgument, + IotMqttCallbackParam_t * pMessage ) +{ + /* Silence warnings about unused parameters. */ + ( void ) pArgument; +} + +/*-----------------------------------------------------------*/ + +static void _notifyNextCallbackWrapper( void * pArgument, + IotMqttCallbackParam_t * pMessage ) +{ + /* Silence warnings about unused parameters. */ + ( void ) pArgument; +} + +/*-----------------------------------------------------------*/ + AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) { AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; @@ -817,6 +1151,42 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, /*-----------------------------------------------------------*/ +AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pNotifyPendingCallback ) +{ + /* Flags are not currently used by this function. */ + ( void ) flags; + + return _setCallbackCommon( mqttConnection, + NOTIFY_PENDING_CALLBACK, + pThingName, + thingNameLength, + pNotifyPendingCallback ); +} + +/*-----------------------------------------------------------*/ + +AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pNotifyNextCallback ) +{ + /* Flags are not currently used by this function. */ + ( void ) flags; + + return _setCallbackCommon( mqttConnection, + NOTIFY_NEXT_CALLBACK, + pThingName, + thingNameLength, + pNotifyNextCallback ); +} + +/*-----------------------------------------------------------*/ + const char * AwsIotJobs_strerror( AwsIotJobsError_t status ) { const char * pMessage = NULL; diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index 59d55f963c..77e23c1c0f 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -463,7 +463,8 @@ static AwsIotJobsError_t _findSubscription( const AwsIotJobsRequestInfo_t * pReq /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ pSubscription = _AwsIotJobs_FindSubscription( pRequestInfo->pThingName, - pRequestInfo->thingNameLength ); + pRequestInfo->thingNameLength, + true ); if( pSubscription == NULL ) { diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 07a4ef673d..2a8faaad02 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -99,7 +99,8 @@ static bool _jobsSubscription_match( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ _jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, - size_t thingNameLength ) + size_t thingNameLength, + bool createIfNotFound ) { _jobsSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; @@ -118,31 +119,34 @@ _jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, /* Check if a subscription was found. */ if( pSubscriptionLink == NULL ) { - /* No subscription found. Allocate a new subscription. */ - pSubscription = AwsIotJobs_MallocSubscription( sizeof( _jobsSubscription_t ) + thingNameLength ); - - if( pSubscription != NULL ) + if( createIfNotFound == true ) { - /* Clear the new subscription. */ - ( void ) memset( pSubscription, 0x00, sizeof( _jobsSubscription_t ) + thingNameLength ); + /* No subscription found. Allocate a new subscription. */ + pSubscription = AwsIotJobs_MallocSubscription( sizeof( _jobsSubscription_t ) + thingNameLength ); - /* Set the Thing Name length and copy the Thing Name into the new subscription. */ - pSubscription->thingNameLength = thingNameLength; - ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); + if( pSubscription != NULL ) + { + /* Clear the new subscription. */ + ( void ) memset( pSubscription, 0x00, sizeof( _jobsSubscription_t ) + thingNameLength ); - /* Add the new subscription to the subscription list. */ - IotListDouble_InsertHead( &( _AwsIotJobsSubscriptions ), - &( pSubscription->link ) ); + /* Set the Thing Name length and copy the Thing Name into the new subscription. */ + pSubscription->thingNameLength = thingNameLength; + ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); - IotLogDebug( "Created new Jobs subscriptions object for %.*s.", - thingNameLength, - pThingName ); - } - else - { - IotLogError( "Failed to allocate memory for %.*s Jobs subscriptions.", - thingNameLength, - pThingName ); + /* Add the new subscription to the subscription list. */ + IotListDouble_InsertHead( &( _AwsIotJobsSubscriptions ), + &( pSubscription->link ) ); + + IotLogDebug( "Created new Jobs subscriptions object for %.*s.", + thingNameLength, + pThingName ); + } + else + { + IotLogError( "Failed to allocate memory for %.*s Jobs subscriptions.", + thingNameLength, + pThingName ); + } } } else diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 7cd53f5e90..690e37caee 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -124,18 +124,16 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt * @brief Common function for incoming Shadow callbacks. * * @param[in] type Shadow callback type. - * @param[in] pSubscription Shadow subscriptions object for callback. * @param[in] pMessage The received Shadow callback document (as an MQTT PUBLISH * message). */ static void _callbackWrapperCommon( _shadowCallbackType_t type, - const _shadowSubscription_t * pSubscription, IotMqttCallbackParam_t * pMessage ); /** * @brief Invoked when a document is received on the Shadow DELTA callback. * - * @param[in] pArgument Associated Shadow subscription. + * @param[in] pArgument Ignored. * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). */ static void _deltaCallbackWrapper( void * pArgument, @@ -144,7 +142,7 @@ static void _deltaCallbackWrapper( void * pArgument, /** * @brief Invoked when a document is received on the Shadow UPDATED callback. * - * @param[in] pArgument Associated Shadow subscription. + * @param[in] pArgument Ignored. * @param[in] pMessage The received UPDATED document (as an MQTT PUBLISH message). */ static void _updatedCallbackWrapper( void * pArgument, @@ -339,7 +337,8 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength ); + thingNameLength, + true ); if( pSubscription == NULL ) { @@ -502,7 +501,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt subscription.qos = IOT_MQTT_QOS_1; subscription.pTopicFilter = pTopicFilter; subscription.topicFilterLength = ( uint16_t ) ( operationTopicLength + pCallbackSuffixLength[ type ] ); - subscription.callback.pCallbackContext = ( void * ) pSubscription; + subscription.callback.pCallbackContext = NULL; subscription.callback.function = pCallbackWrapper[ type ]; /* Call the MQTT operation function. */ @@ -558,27 +557,61 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt /*-----------------------------------------------------------*/ static void _callbackWrapperCommon( _shadowCallbackType_t type, - const _shadowSubscription_t * pSubscription, IotMqttCallbackParam_t * pMessage ) { + AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; AwsIotShadowCallbackParam_t callbackParam = { .callbackType = ( AwsIotShadowCallbackType_t ) 0 }; + _shadowSubscription_t * pSubscription = NULL; + const char * pThingName = NULL; + size_t thingNameLength = 0; + + /* Parse the Thing Name from the topic. */ + if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength, + &pThingName, + &thingNameLength ) == false ) + { + IOT_GOTO_CLEANUP(); + } + + /* Search for a matching subscription. */ + IotMutex_Lock( &_AwsIotShadowSubscriptionsMutex ); + + pSubscription = _AwsIotShadow_FindSubscription( pThingName, + thingNameLength, + false ); + + if( pSubscription == NULL ) + { + /* No subscription found. */ + IOT_GOTO_CLEANUP(); + } /* Ensure that a callback function is set. */ AwsIotShadow_Assert( pSubscription->callbacks[ type ].function != NULL ); + /* Copy the subscription callback info, as the subscription may be modified + * when the subscriptions mutex is released. */ + callbackInfo = pSubscription->callbacks[ type ]; + + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); + /* Set the callback type. Shadow callbacks are enumerated after the operations. */ callbackParam.callbackType = ( AwsIotShadowCallbackType_t ) ( type + SHADOW_OPERATION_COUNT ); /* Set the remaining members of the callback param. */ callbackParam.mqttConnection = pMessage->mqttConnection; - callbackParam.pThingName = pSubscription->pThingName; - callbackParam.thingNameLength = pSubscription->thingNameLength; + callbackParam.pThingName = pThingName; + callbackParam.thingNameLength = thingNameLength; callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; /* Invoke the callback function. */ - pSubscription->callbacks[ type ].function( pSubscription->callbacks[ type ].pCallbackContext, - &callbackParam ); + callbackInfo.function( callbackInfo.pCallbackContext, + &callbackParam ); + + /* This function uses cleanup sections to exit on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); } /*-----------------------------------------------------------*/ @@ -586,7 +619,10 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, static void _deltaCallbackWrapper( void * pArgument, IotMqttCallbackParam_t * pMessage ) { - _callbackWrapperCommon( DELTA_CALLBACK, pArgument, pMessage ); + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + _callbackWrapperCommon( DELTA_CALLBACK, pMessage ); } /*-----------------------------------------------------------*/ @@ -594,7 +630,10 @@ static void _deltaCallbackWrapper( void * pArgument, static void _updatedCallbackWrapper( void * pArgument, IotMqttCallbackParam_t * pMessage ) { - _callbackWrapperCommon( UPDATED_CALLBACK, pArgument, pMessage ); + /* Silence warnings about unused parameters. */ + ( void ) pArgument; + + _callbackWrapperCommon( UPDATED_CALLBACK, pMessage ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index d7129fa9f0..be0e59be21 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -551,7 +551,8 @@ static AwsIotShadowError_t _findSubscription( const char * pThingName, /* Check for an existing subscription. This function will attempt to allocate * a new subscription if not found. */ pSubscription = _AwsIotShadow_FindSubscription( pThingName, - thingNameLength ); + thingNameLength, + true ); if( pSubscription == NULL ) { diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index db0f2460c1..8ba28e28a2 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -99,7 +99,8 @@ static bool _shadowSubscription_match( const IotLink_t * pSubscriptionLink, /*-----------------------------------------------------------*/ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, - size_t thingNameLength ) + size_t thingNameLength, + bool createIfNotFound ) { _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; @@ -118,31 +119,34 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, /* Check if a subscription was found. */ if( pSubscriptionLink == NULL ) { - /* No subscription found. Allocate a new subscription. */ - pSubscription = AwsIotShadow_MallocSubscription( sizeof( _shadowSubscription_t ) + thingNameLength ); - - if( pSubscription != NULL ) + if( createIfNotFound == true ) { - /* Clear the new subscription. */ - ( void ) memset( pSubscription, 0x00, sizeof( _shadowSubscription_t ) + thingNameLength ); + /* No subscription found. Allocate a new subscription. */ + pSubscription = AwsIotShadow_MallocSubscription( sizeof( _shadowSubscription_t ) + thingNameLength ); - /* Set the Thing Name length and copy the Thing Name into the new subscription. */ - pSubscription->thingNameLength = thingNameLength; - ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); + if( pSubscription != NULL ) + { + /* Clear the new subscription. */ + ( void ) memset( pSubscription, 0x00, sizeof( _shadowSubscription_t ) + thingNameLength ); - /* Add the new subscription to the subscription list. */ - IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ), - &( pSubscription->link ) ); + /* Set the Thing Name length and copy the Thing Name into the new subscription. */ + pSubscription->thingNameLength = thingNameLength; + ( void ) memcpy( pSubscription->pThingName, pThingName, thingNameLength ); - IotLogDebug( "Created new Shadow subscriptions object for %.*s.", - thingNameLength, - pThingName ); - } - else - { - IotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.", - thingNameLength, - pThingName ); + /* Add the new subscription to the subscription list. */ + IotListDouble_InsertHead( &( _AwsIotShadowSubscriptions ), + &( pSubscription->link ) ); + + IotLogDebug( "Created new Shadow subscriptions object for %.*s.", + thingNameLength, + pThingName ); + } + else + { + IotLogError( "Failed to allocate memory for %.*s Shadow subscriptions.", + thingNameLength, + pThingName ); + } } } else diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 87fb82fc94..6a93578f85 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -161,6 +161,17 @@ static void _operationComplete( void * pArgument, /*-----------------------------------------------------------*/ +/** + * @brief Jobs GET PENDING and GET NEXT callback. Checks parameters and unblocks + * the main test thread. + */ +static void _jobsCallback( void * pArgument, + AwsIotJobsCallbackParam_t * pCallbackParam ) +{ +} + +/*-----------------------------------------------------------*/ + /** * @brief Parses Job IDs from a GET PENDING Jobs response. */ @@ -560,6 +571,7 @@ TEST_GROUP_RUNNER( Jobs_System ) RUN_TEST_CASE( Jobs_System, DescribeBlocking ); RUN_TEST_CASE( Jobs_System, UpdateAsync ); RUN_TEST_CASE( Jobs_System, UpdateBlocking ); + RUN_TEST_CASE( Jobs_System, JobsCallbacks ); } RUN_TEST_CASE( Jobs_System, PersistentSubscriptions ); @@ -704,3 +716,55 @@ TEST( Jobs_System, PersistentSubscriptions ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the Jobs callbacks. + */ +TEST( Jobs_System, JobsCallbacks ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + IotSemaphore_t waitSem; + + /* Create the wait semaphore. */ + IotSemaphore_Create( &waitSem, 0, 2 ); + + /* Set the callback function and context. */ + callbackInfo.pCallbackContext = &waitSem; + callbackInfo.function = _jobsCallback; + + /* Set the Jobs callbacks to notify of Jobs changes. */ + status = AwsIotJobs_SetNotifyNextCallback( _mqttConnection, + AWS_IOT_TEST_JOBS_THING_NAME, + sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, + 0, + &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + status = AwsIotJobs_SetNotifyPendingCallback( _mqttConnection, + AWS_IOT_TEST_JOBS_THING_NAME, + sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, + 0, + &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Remove Jobs callbacks. */ + status = AwsIotJobs_SetNotifyNextCallback( _mqttConnection, + AWS_IOT_TEST_JOBS_THING_NAME, + sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + status = AwsIotJobs_SetNotifyPendingCallback( _mqttConnection, + AWS_IOT_TEST_JOBS_THING_NAME, + sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, + 0, + NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Destroy the wait semaphore. */ + IotSemaphore_Destroy( &waitSem ); +} + +/*-----------------------------------------------------------*/ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index a72b574e9e..d28143c77f 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -239,6 +239,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) RUN_TEST_CASE( Jobs_Unit_API, DescribeMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, UpdateMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, RemovePersistentSubscriptions ); + RUN_TEST_CASE( Jobs_Unit_API, SetCallbackMallocFail ); } /*-----------------------------------------------------------*/ @@ -631,3 +632,51 @@ TEST( Jobs_Unit_API, RemovePersistentSubscriptions ) } /*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of the Jobs set callback functions when memory + * allocation fails at various points. + */ +TEST( Jobs_Unit_API, SetCallbackMallocFail ) +{ + int32_t i = 0, mqttErrorCount = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + + /* Set a short timeout so this test runs faster. */ + _AwsIotJobsMqttTimeoutMs = 75; + + /* A non-NULL callback function. */ + callbackInfo.function = ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x01; + + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + /* Call Jobs set callback. Memory allocation will fail at various times + * during this call. */ + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + 0, + &callbackInfo ); + + if( status == AWS_IOT_JOBS_SUCCESS ) + { + break; + } + else if( status == AWS_IOT_JOBS_MQTT_ERROR ) + { + mqttErrorCount++; + } + else + { + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); + } + } + + /* Allow 1 MQTT error, caused by failure to allocate memory for a SUBACK. */ + CHECK_MQTT_ERROR_COUNT( 1, mqttErrorCount ); +} + +/*-----------------------------------------------------------*/ From 1549524142cc124124420196987b1461b3440a99 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 11 Jul 2019 17:11:32 +0800 Subject: [PATCH 228/844] Add tests for Jobs callbacks (#518) --- lib/source/jobs/aws_iot_jobs_api.c | 79 ++++++++++++++++- lib/source/shadow/aws_iot_shadow_api.c | 1 + tests/jobs/system/aws_iot_tests_jobs_system.c | 87 ++++++++++++++++++- 3 files changed, 164 insertions(+), 3 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 2ba745750a..4ec46775bb 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -134,6 +134,16 @@ static void _notifyPendingCallbackWrapper( void * pArgument, static void _notifyNextCallbackWrapper( void * pArgument, IotMqttCallbackParam_t * pMessage ); +/** + * @brief Common function for incoming Jobs callbacks. + * + * @param[in] type Jobs callback type. + * @param[in] pMessage The received Jobs callback document (as an MQTT PUBLISH + * message). + */ +static void _callbackWrapperCommon( _jobsCallbackType_t type, + IotMqttCallbackParam_t * pMessage ); + /*-----------------------------------------------------------*/ /** @@ -581,7 +591,7 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC _pAwsIotJobsCallbackNames[ type ], IotMqtt_strerror( mqttStatus ) ); - /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ + /* Convert the MQTT "NO MEMORY" error to a Jobs "NO MEMORY" error. */ if( mqttStatus == IOT_MQTT_NO_MEMORY ) { IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); @@ -621,6 +631,9 @@ static void _notifyPendingCallbackWrapper( void * pArgument, { /* Silence warnings about unused parameters. */ ( void ) pArgument; + + _callbackWrapperCommon( NOTIFY_PENDING_CALLBACK, + pMessage ); } /*-----------------------------------------------------------*/ @@ -630,6 +643,70 @@ static void _notifyNextCallbackWrapper( void * pArgument, { /* Silence warnings about unused parameters. */ ( void ) pArgument; + + _callbackWrapperCommon( NOTIFY_NEXT_CALLBACK, + pMessage ); +} + +/*-----------------------------------------------------------*/ + +static void _callbackWrapperCommon( _jobsCallbackType_t type, + IotMqttCallbackParam_t * pMessage ) +{ + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + AwsIotJobsCallbackParam_t callbackParam = { .callbackType = ( AwsIotJobsCallbackType_t ) 0 }; + _jobsSubscription_t * pSubscription = NULL; + const char * pThingName = NULL; + size_t thingNameLength = 0; + + /* Parse the Thing Name from the topic. */ + if( AwsIot_ParseThingName( pMessage->u.message.info.pTopicName, + pMessage->u.message.info.topicNameLength, + &pThingName, + &thingNameLength ) == false ) + { + IOT_GOTO_CLEANUP(); + } + + /* Search for a matching subscription. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + + pSubscription = _AwsIotJobs_FindSubscription( pThingName, + thingNameLength, + false ); + + if( pSubscription == NULL ) + { + /* No subscription found. */ + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + IOT_GOTO_CLEANUP(); + } + + /* Ensure that a callback function is set. */ + AwsIotJobs_Assert( pSubscription->callbacks[ type ].function != NULL ); + + /* Copy the subscription callback info, as the subscription may be modified + * when the subscriptions mutex is released. */ + callbackInfo = pSubscription->callbacks[ type ]; + + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + + /* Set the callback type. Jobs callbacks are enumerated after the operations. */ + callbackParam.callbackType = ( AwsIotJobsCallbackType_t ) ( type + JOBS_OPERATION_COUNT ); + + /* Set the remaining members of the callback param. */ + callbackParam.mqttConnection = pMessage->mqttConnection; + callbackParam.pThingName = pThingName; + callbackParam.thingNameLength = thingNameLength; + callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; + callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; + + /* Invoke the callback function. */ + callbackInfo.function( callbackInfo.pCallbackContext, + &callbackParam ); + + /* This function uses cleanup sections to exit on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); } /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 690e37caee..ffb2038b54 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -584,6 +584,7 @@ static void _callbackWrapperCommon( _shadowCallbackType_t type, if( pSubscription == NULL ) { /* No subscription found. */ + IotMutex_Unlock( &_AwsIotShadowSubscriptionsMutex ); IOT_GOTO_CLEANUP(); } diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 6a93578f85..657cc40a9f 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -130,6 +130,11 @@ static char _pJobIds[ 2 ][ JOBS_MAX_ID_LENGTH + 1 ] = { { 0 } }; */ static size_t _pJobIdLengths[ 2 ] = { 0 }; +/** + * @brief Identifies which of the two Jobs is set to status IN_PROGRESS. + */ +static uint32_t _inProgressJob = 0; + /*-----------------------------------------------------------*/ /** @@ -168,6 +173,44 @@ static void _operationComplete( void * pArgument, static void _jobsCallback( void * pArgument, AwsIotJobsCallbackParam_t * pCallbackParam ) { + IotSemaphore_t * pWaitSem = ( IotSemaphore_t * ) pArgument; + uint32_t checkJobId = 0; + const char * pJobId = NULL; + size_t jobIdLength = 0; + + /* Check parameters against expected values. */ + AwsIotJobs_Assert( pCallbackParam->mqttConnection == _mqttConnection ); + AwsIotJobs_Assert( pCallbackParam->thingNameLength == sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); + AwsIotJobs_Assert( strncmp( pCallbackParam->pThingName, + AWS_IOT_TEST_JOBS_THING_NAME, + pCallbackParam->thingNameLength ) == 0 ); + + /* Check the Job ID that was not previously IN_PROGRESS. */ + if( _inProgressJob == 0 ) + { + checkJobId = 1; + } + else + { + checkJobId = 0; + } + + /* Parse the next Job ID. */ + AwsIotJobs_Assert( IotJsonUtils_FindJsonValue( pCallbackParam->u.callback.pDocument, + pCallbackParam->u.callback.documentLength, + "jobId", + 5, + &pJobId, + &jobIdLength ) == true ); + + /* Verify that the previously queued Job is next. */ + AwsIotJobs_Assert( jobIdLength - 2 == _pJobIdLengths[ checkJobId ] ); + AwsIotJobs_Assert( strncmp( _pJobIds[ checkJobId ], + pJobId + 1, + _pJobIdLengths[ checkJobId ] ) == 0 ); + + /* Unblock the main test thead. */ + IotSemaphore_Post( pWaitSem ); } /*-----------------------------------------------------------*/ @@ -375,8 +418,8 @@ static void _jobsBlockingTest( _jobsOperationType_t type, /* The only remaining valid type is UPDATE. */ TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); - requestInfo.pJobId = _pJobIds[ 0 ]; - requestInfo.jobIdLength = _pJobIdLengths[ 0 ]; + requestInfo.pJobId = _pJobIds[ _inProgressJob ]; + requestInfo.jobIdLength = _pJobIdLengths[ _inProgressJob ]; status = AwsIotJobs_TimedUpdate( &requestInfo, &updateInfo, @@ -425,6 +468,13 @@ static void _jobsBlockingTest( _jobsOperationType_t type, if( strncmp( _pJobIds[ i ], pJobId + 1, jobIdLength - 2 ) == 0 ) { jobIdMatch = true; + + /* Mark which Job was started. */ + if( type == JOBS_START_NEXT ) + { + _inProgressJob = i; + } + break; } } @@ -724,6 +774,9 @@ TEST( Jobs_System, JobsCallbacks ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; IotSemaphore_t waitSem; /* Create the wait semaphore. */ @@ -748,6 +801,36 @@ TEST( Jobs_System, JobsCallbacks ) &callbackInfo ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + /* Set a Job status to completed. This should trigger both Jobs + * callbacks. */ + requestInfo.pJobId = _pJobIds[ _inProgressJob ]; + requestInfo.jobIdLength = _pJobIdLengths[ _inProgressJob ]; + requestInfo.mallocResponse = IotTest_Malloc; + requestInfo.mqttConnection = _mqttConnection; + requestInfo.pThingName = AWS_IOT_TEST_JOBS_THING_NAME; + requestInfo.thingNameLength = sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1; + + updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; + + status = AwsIotJobs_TimedUpdate( &requestInfo, + &updateInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &response ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + IotTest_Free( ( void * ) response.pJobsResponse ); + + /* Wait for both callbacks to be invoked. */ + if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for Jobs callback." ); + } + + if( IotSemaphore_TimedWait( &waitSem, AWS_IOT_TEST_JOBS_TIMEOUT ) == false ) + { + TEST_FAIL_MESSAGE( "Timed out waiting for Jobs callback." ); + } + /* Remove Jobs callbacks. */ status = AwsIotJobs_SetNotifyNextCallback( _mqttConnection, AWS_IOT_TEST_JOBS_THING_NAME, From 37a87b2c50ccc3d1c78e2d0a35c2bb57b428ca8e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 12 Jul 2019 13:44:48 +0800 Subject: [PATCH 229/844] Add documentation for Jobs functions (#520) --- lib/include/aws_iot_jobs.h | 584 ++++++++++++++++++++++++++++++++++- lib/include/aws_iot_shadow.h | 10 +- 2 files changed, 588 insertions(+), 6 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 14be41b407..528d60794d 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -119,6 +119,13 @@ void AwsIotJobs_Cleanup( void ); * @brief Get the list of all pending jobs for a Thing and receive an asynchronous * notification when the response arrives. * + * This function implements the [GetPendingJobExecutions] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-getpendingjobexecutions) + * command of the Jobs API, which gets the list of all Jobs for a Thing that are + * not in a terminal state. The list of retrieved Jobs is returned as the `pResponse` + * member in #AwsIotJobsCallbackParam_t, or through the #AwsIotJobsResponse_t + * parameter of @ref jobs_function_wait. + * * @param[in] pRequestInfo Jobs request parameters. * @param[in] flags Flags which modify the behavior of the Jobs request. See * @ref jobs_constants_flags. @@ -133,7 +140,7 @@ void AwsIotJobs_Cleanup( void ); * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t - * or #AwsIotJobs_Wait), the status will be #AWS_IOT_JOBS_SUCCESS. + * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. * @return Should the Jobs operation fail, the status will be one of: * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) * - #AWS_IOT_JOBS_MQTT_ERROR @@ -141,6 +148,36 @@ void AwsIotJobs_Cleanup( void ); * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. * * @see @ref jobs_function_timedgetpending for a blocking variant of this function. + * + * Example + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * // Signature of Jobs callback function. + * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); + * + * AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * // Set the request info. + * requestInfo.mqttConnection = _mqttConnection; + * requestInfo.pThingName = THING_NAME; + * requestInfo.thingNameLength = THING_NAME_LENGTH; + * + * // Set the callback function to invoke. + * callbackInfo.function = _jobsCallback; + * + * // Queue Jobs GET PENDING. + * AwsIotJobsError_t getPendingResult = AwsIotJobs_GetPending( &requestInfo, + * 0, + * &callbackInfo, + * &getPendingOperation ); + * + * // GET PENDING should have returned AWS_IOT_JOBS_STATUS_PENDING. The function + * // _jobsCallback will be invoked once the Jobs response is received. + * @endcode */ /* @[declare_jobs_getpending] */ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -152,6 +189,27 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques /** * @brief Get the list of all pending jobs for a Thing with a timeout for receiving * the response. + * + * This function queues a Jobs GET PENDING, then waits for the result. Internally, + * this function is a call to @ref jobs_function_getpending followed by + * @ref jobs_function_wait. See @ref jobs_function_getpending for more information + * on the Jobs GET PENDING command. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] timeoutMs If a response is not received within this timeout, this + * function returns #AWS_IOT_JOBS_TIMEOUT. + * @param[out] pJobsResponse The response received from the Jobs service. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - #AWS_IOT_JOBS_TIMEOUT + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ /* @[declare_jobs_timedgetpending] */ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -163,6 +221,68 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR /** * @brief Start the next pending job execution for a Thing and receive an asynchronous * notification when the response arrives. + * + * This function implements the [StartNextPendingJobExecution] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-startnextpendingjobexecution) + * command of the Jobs API, which gets and starts the next pending job execution + * for a Thing. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pStartNextOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Jobs operation + * completes. + * + * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully + * queuing the Jobs operation. + * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t + * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. + * @return Should the Jobs operation fail, the status will be one of: + * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. + * + * @see @ref jobs_function_timedstartnext for a blocking variant of this function. + * + * Example + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * // Signature of Jobs callback function. + * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); + * + * AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * // Set the request info. The update info generally does not need to be + * // changed, as its defaults are suitable. + * requestInfo.mqttConnection = _mqttConnection; + * requestInfo.pThingName = THING_NAME; + * requestInfo.thingNameLength = THING_NAME_LENGTH; + * + * // Set the callback function to invoke. + * callbackInfo.function = _jobsCallback; + * + * // Queue Jobs START NEXT. + * AwsIotJobsError_t startNextResult = AwsIotJobs_StartNext( &requestInfo, + * &updateInfo, + * 0, + * &callbackInfo, + * &startNextOperation ); + * + * // START NEXT should have returned AWS_IOT_JOBS_STATUS_PENDING. The function + * // _jobsCallback will be invoked once the Jobs response is received. + * @endcode */ /* @[declare_jobs_startnext] */ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -175,6 +295,28 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest /** * @brief Start the next pending job execution for a Thing with a timeout for * receiving the response. + * + * This function queues a Jobs START NEXT, then waits for the result. Internally, + * this function is a call to @ref jobs_function_startnext followed by + * @ref jobs_function_wait. See @ref jobs_function_startnext for more information + * on the Jobs START NEXT command. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] timeoutMs If a response is not received within this timeout, this + * function returns #AWS_IOT_JOBS_TIMEOUT. + * @param[out] pJobsResponse The response received from the Jobs service. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - #AWS_IOT_JOBS_TIMEOUT + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ /* @[declare_jobs_timedstartnext] */ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -187,6 +329,75 @@ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRe /** * @brief Get detailed information about a job execution and receive an asynchronous * notification when the response arrives. + * + * This function implements the [DescribeJobExecution] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-describejobexecution) + * command of the Jobs API, which gets detailed information about a job execution. + * The description is returned as the `pResponse` member in #AwsIotJobsCallbackParam_t, + * or through the #AwsIotJobsResponse_t parameter of @ref jobs_function_wait. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] executionNumber The execution number to describe. Optional; pass + * #AWS_IOT_JOBS_NO_EXECUTION_NUMBER to ignore. + * @param[in] includeJobDocument Whether the response should include the full + * Job document. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pDescribeOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Jobs operation + * completes. + * + * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully + * queuing the Jobs operation. + * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t + * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. + * @return Should the Jobs operation fail, the status will be one of: + * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. + * + * @see @ref jobs_function_timeddescribe for a blocking variant of this function. + * + * Example + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * // Signature of Jobs callback function. + * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); + * + * AwsIotJobsOperation_t describeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * // Set the request info. + * requestInfo.mqttConnection = _mqttConnection; + * requestInfo.pThingName = THING_NAME; + * requestInfo.thingNameLength = THING_NAME_LENGTH; + * + * // Describe the next Job. Or, this may be set to a specific Job ID. + * requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; + * requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; + * + * // Set the callback function to invoke. + * callbackInfo.function = _jobsCallback; + * + * // Queue Jobs DESCRIBE. + * AwsIotJobsError_t describeResult = AwsIotJobs_Describe( &requestInfo, + * AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + * false, + * 0, + * &callbackInfo, + * &describeOperation ); + * + * // DESCRIBE should have returned AWS_IOT_JOBS_STATUS_PENDING. The function + * // _jobsCallback will be invoked once the Jobs response is received. + * @endcode */ /* @[declare_jobs_describe] */ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -200,6 +411,31 @@ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestI /** * @brief Get detailed information about a job execution with a timeout for receiving * the response. + * + * This function queues a Jobs DESCRIBE, then waits for the result. Internally, + * this function is a call to @ref jobs_function_describe followed by + * @ref jobs_function_wait. See @ref jobs_function_describe for more information + * on the Jobs DESCRIBE command. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] executionNumber The execution number to describe. Optional; pass + * #AWS_IOT_JOBS_NO_EXECUTION_NUMBER to ignore. + * @param[in] includeJobDocument Whether the response should include the full + * Job document. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] timeoutMs If a response is not received within this timeout, this + * function returns #AWS_IOT_JOBS_TIMEOUT. + * @param[out] pJobsResponse The response received from the Jobs service. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - #AWS_IOT_JOBS_TIMEOUT + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ /* @[declare_jobs_timeddescribe] */ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -213,6 +449,73 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq /** * @brief Update the status of a job execution and receive an asynchronous * notification when the Job update completes. + * + * This function implements the [UpdateJobExecution] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-updatejobexecution) + * command of the Jobs API, which updates the status of a Job execution. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[out] pUpdateOperation Set to a handle by which this operation may be referenced + * after this function returns. This reference is invalidated once the Jobs operation + * completes. + * + * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully + * queuing the Jobs operation. + * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t + * or @ref jobs_function_wait), the status will be #AWS_IOT_JOBS_SUCCESS. + * @return Should the Jobs operation fail, the status will be one of: + * - #AWS_IOT_JOBS_NO_MEMORY (Memory could not be allocated for incoming document) + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. + * + * @see @ref jobs_function_timedupdate for a blocking variant of this function. + * + * Example + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * // Signature of Jobs callback function. + * void _jobsCallback( void * pCallbackContext, AwsIotJobsCallbackParam_t * pCallbackParam ); + * + * AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * // Set the request info. + * requestInfo.mqttConnection = _mqttConnection; + * requestInfo.pThingName = THING_NAME; + * requestInfo.thingNameLength = THING_NAME_LENGTH; + * + * // A Job ID must be set. AWS_IOT_JOBS_NEXT_JOB is not valid for UPDATE. + * requestInfo.pJobId = "job-id"; + * requestInfo.jobIdLength = 6; + * + * // Set the update info. + * updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; + * + * // Set the callback function to invoke. + * callbackInfo.function = _jobsCallback; + * + * // Queue Jobs UPDATE. + * AwsIotJobsError_t updateResult = AwsIotJobs_Update( &requestInfo, + * &updateInfo, + * 0, + * &callbackInfo, + * &updateOperation ); + * + * // UPDATE should have returned AWS_IOT_JOBS_STATUS_PENDING. The function + * // _jobsCallback will be invoked once the Jobs response is received. + * @endcode */ /* @[declare_jobs_update] */ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -225,6 +528,28 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf /** * @brief Update the status of a job execution with a timeout for receiving the * response. + * + * This function queues a Jobs UPDATE, then waits for the result. Internally, + * this function is a call to @ref jobs_function_update followed by + * @ref jobs_function_wait. See @ref jobs_function_update for more information + * on the Jobs UPDATE command. + * + * @param[in] pRequestInfo Jobs request parameters. + * @param[in] pUpdateInfo Jobs update parameters. + * @param[in] flags Flags which modify the behavior of the Jobs request. See + * @ref jobs_constants_flags. + * @param[in] timeoutMs If a response is not received within this timeout, this + * function returns #AWS_IOT_JOBS_TIMEOUT. + * @param[out] pJobsResponse The response received from the Jobs service. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - #AWS_IOT_JOBS_TIMEOUT + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ /* @[declare_jobs_timedupdate] */ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, @@ -236,6 +561,85 @@ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pReque /** * @brief Wait for a Jobs operation to complete. + * + * This function blocks to wait for a [GET PENDING](@ref jobs_function_getpending), + * [START NEXT](@ref jobs_function_startnext), [DESCRIBE](@ref jobs_function_describe), + * or [UPDATE](@ref jobs_function_update) operation to complete. These operations are + * by default asynchronous; the function calls queue an operation for processing, + * and a callback is invoked once the operation is complete. + * + * To use this function, the flag #AWS_IOT_JOBS_FLAG_WAITABLE must have been + * set in the operation's function call. Additionally, this function must always + * be called with any waitable operation to clean up resources. + * + * Regardless of its return value, this function always clean up resources used + * by the waitable operation. This means `operation` is invalidated as soon as + * this function returns, even if it returns #AWS_IOT_JOBS_TIMEOUT or another + * error. + * + * @param[in] operation Reference to the Jobs operation to wait for. The flag + * #AWS_IOT_JOBS_FLAG_WAITABLE must have been set for this operation. + * @param[in] timeoutMs How long to wait before returning #AWS_IOT_JOBS_TIMEOUT. + * @param[out] pJobsResponse The response received from the Jobs service. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_BAD_RESPONSE + * - #AWS_IOT_JOBS_TIMEOUT + * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. + * + * Example: + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + * AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + * AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + * AwsIotJobsResponse_t jobsResponse = AWS_IOT_JOBS_RESPONSE_INITIALIZER; + * + * // Set the request info. + * requestInfo.mqttConnection = _mqttConnection; + * requestInfo.pThingName = THING_NAME; + * requestInfo.thingNameLength = THING_NAME_LENGTH; + * + * // Set the function used to allocte memory for an incoming response. + * requestInfo.mallocResponse = malloc; + * + * // A Job ID must be set. AWS_IOT_JOBS_NEXT_JOB is not valid for UPDATE. + * requestInfo.pJobId = "job-id"; + * requestInfo.jobIdLength = 6; + * + * // Set the update info. + * updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; + * + * // Queue Jobs UPDATE. + * AwsIotJobsError_t updateResult = AwsIotJobs_Update( &requestInfo, + * &updateInfo, + * AWS_IOT_JOBS_FLAG_WAITABLE, + * NULL, + * &updateOperation ); + * + * // UPDATE should have returned AWS_IOT_JOBS_STATUS_PENDING. The call to wait + * // returns once the result of the UPDATE is available or the timeout expires. + * if( updateResult == AWS_IOT_JOBS_STATUS_PENDING ) + * { + * updateResult = AwsIotJobs_Wait( updateOperation, 5000, &jobsResponse ); + * + * if( updateResult == AWS_IOT_JOBS_SUCCESS ) + * { + * // Jobs operation succeeded. Do something with the Jobs response. + * + * // Once the Jobs response is no longer needed, free it. + * free( jobsResponse.pJobsResponse ); + * } + * else + * { + * // Jobs operation failed. + * } + * } + * @endcode */ /* @[declare_jobs_wait] */ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, @@ -245,6 +649,79 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, /** * @brief Set a callback to be invoked when the list of pending Jobs changes. + * + * The Jobs service publishes a [JobExecutionsChanged] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-jobexecutionschanged) + * message to the `jobs/notify` topic whenever a Job execution is added to or + * removed from the list of pending Job executions for a Thing. The message sent is + * useful for monitoring the list of pending Job executions. + * + * A NOTIFY PENDING callback may be invoked whenever a message is published + * to `jobs/notify`. Each Thing may have a single NOTIFY PENDING callback set. This + * function modifies the NOTIFY PENDING callback for a specific Thing depending on + * the `pNotifyPendingCallback` parameter and the presence of any existing NOTIFY + * PENDING callback. + * - When no existing NOTIFY PENDING callback exists for a specific Thing, a new + * callback is added. + * - If there is an existing NOTIFY PENDING callback and `pNotifyPendingCallback` is not `NULL`, + * then the existing callback function and parameter are replaced with `pNotifyPendingCallback`. + * - If there is an existing NOTIFY PENDING callback and `pNotifyPendingCallback` is `NULL`, + * then the callback is removed. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription to `jobs/notify`. + * @param[in] pThingName The subscription to `jobs/notify` will be added for + * this Thing Name. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags This parameter is for future-compatibility. Currently, flags are + * not supported for this function and this parameter is ignored. + * @param[in] pNotifyPendingCallback Callback function to invoke for incoming messages. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_TIMEOUT + * + * @see @ref jobs_function_setnotifynextcallback for the function to register callbacks + * for next Job changes. + * + * Example: + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * AwsIotJobsError_t result = AWS_IOT_JOBS_STATUS_PENDING; + * AwsIotJobsCallbackInfo_t notifyPendingCallback = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * // _jobsCallback will be invoked when any messages are received. + * notifyPendingCallback.function = _jobsCallback; + * + * // Set the NOTIFY PENDING callback for the Thing "Test_device". + * result = AwsIotJobs_SetNotifyPendingCallback( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * 0, + * ¬ifyPendingCallback ); + * + * // Check if the callback was successfully set. + * if( status == AWS_IOT_JOBS_SUCCESS ) + * { + * // The callback will now be invoked whenever the list of pending Job + * // executions changes. + * + * // Once the callback is no longer needed, it may be removed by passing + * // NULL as pNotifyPendingCallback. + * status = AwsIotJobs_SetNotifyPendingCallback( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * 0, + * NULL ); + * + * // The return value from removing a callback should always be success. + * assert( status == AWS_IOT_JOBS_SUCCESS ); + * } + * @endcode */ /* @[declare_jobs_setnotifypendingcallback] */ AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttConnection, @@ -256,6 +733,79 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttC /** * @brief Set a callback to be invoked when the next pending Job changes. + * + * The Jobs service publishes a [NextJobExecutionChanged] + * (https://docs.aws.amazon.com/iot/latest/developerguide/jobs-api.html#mqtt-nextjobexecutionchanged) + * message to the `jobs/notify-next` topic whenever the next Job execution in + * the list of pending Job executions changes for a Thing. The message sent is + * useful for being notified of changes to the next Job. + * + * A NOTIFY NEXT callback may be invoked whenever a message is published + * to `jobs/notify-next`. Each Thing may have a single NOTIFY NEXT callback set. This + * function modifies the NOTIFY NEXT callback for a specific Thing depending on + * the `pNotifyNextCallback` parameter and the presence of any existing NOTIFY + * NEXT callback. + * - When no existing NOTIFY NEXT callback exists for a specific Thing, a new + * callback is added. + * - If there is an existing NOTIFY NEXT callback and `pNotifyNextCallback` is not `NULL`, + * then the existing callback function and parameter are replaced with `pNotifyNextCallback`. + * - If there is an existing NOTIFY NEXT callback and `pNotifyNextCallback` is `NULL`, + * then the callback is removed. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription to `jobs/notify-next`. + * @param[in] pThingName The subscription to `jobs/notify-next` will be added for + * this Thing Name. + * @param[in] thingNameLength The length of `pThingName`. + * @param[in] flags This parameter is for future-compatibility. Currently, flags are + * not supported for this function and this parameter is ignored. + * @param[in] pNotifyNextCallback Callback function to invoke for incoming messages. + * + * @return One of the following: + * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_BAD_PARAMETER + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * - #AWS_IOT_JOBS_TIMEOUT + * + * @see @ref jobs_function_setnotifypendingcallback for the function to register callbacks + * for all pending Job changes. + * + * Example: + * @code{c} + * #define THING_NAME "Test_device" + * #define THING_NAME_LENGTH ( sizeof( THING_NAME ) - 1 ) + * + * AwsIotJobsError_t result = AWS_IOT_JOBS_STATUS_PENDING; + * AwsIotJobsCallbackInfo_t notifyNextCallback = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * // _jobsCallback will be invoked when any messages are received. + * notifyNextCallback.function = _jobsCallback; + * + * // Set the NOTIFY NEXT callback for the Thing "Test_device". + * result = AwsIotJobs_SetNotifyNextCallback( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * 0, + * ¬ifyNextCallback ); + * + * // Check if the callback was successfully set. + * if( status == AWS_IOT_JOBS_SUCCESS ) + * { + * // The callback will now be invoked whenever the next pending Job + * // execution changes. + * + * // Once the callback is no longer needed, it may be removed by passing + * // NULL as pNotifyNextCallback. + * status = AwsIotJobs_SetNotifyNextCallback( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * 0, + * NULL ); + * + * // The return value from removing a callback should always be success. + * assert( status == AWS_IOT_JOBS_SUCCESS ); + * } + * @endcode */ /* @[declare_jobs_setnotifynextcallback] */ AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConnection, @@ -267,6 +817,38 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConn /** * @brief Remove persistent Jobs operation topic subscriptions. + * + * Passing the flag @ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS to @ref jobs_function_getpending, + * @ref jobs_function_startnext, @ref jobs_function_describe, @ref jobs_function_update, + * or their Timed variants causes the Jobs operation topic subscriptions to be + * maintained for future calls to the same function. If a persistent subscription for a + * Jobs topic are no longer needed, this function may be used to remove it. + * + * @param[in] pRequestInfo Jobs request info. Only the [pThingName] + * (@ref #AwsIotJobsRequestInfo_t.pThingName), [thingNameLength] + * (@ref #AwsIotJobsRequestInfo_t.thingNameLength), and [mqttConnection] + * (@ref #AwsIotJobsRequestInfo_t.mqttConnection) members need to be set for this + * function. + * @param[in] flags Flags that determine which subscriptions to remove. Valid values are + * the bitwise OR of the following individual flags: + * - @ref AWS_IOT_JOBS_FLAG_REMOVE_GET_PENDING_SUBSCRIPTIONS + * - @ref AWS_IOT_JOBS_FLAG_REMOVE_START_NEXT_SUBSCRIPTIONS + * - @ref AWS_IOT_JOBS_FLAG_REMOVE_DESCRIBE_SUBSCRIPTIONS + * - @ref AWS_IOT_JOBS_FLAG_REMOVE_UPDATE_SUBSCRIPTIONS + * + * @return On success: + * - #AWS_IOT_JOBS_SUCCESS + * @return If an MQTT UNSUBSCRIBE packet cannot be sent, one of the following: + * - #AWS_IOT_JOBS_NO_MEMORY + * - #AWS_IOT_JOBS_MQTT_ERROR + * + * @note @ref jobs_function_cleanup removes persistent sessions as well. + * + * @warning This function is not safe to call with any in-progress operations! + * It also does not affect NOTIFY PENDING and NOTIFY NEXT callbacks registered + * with @ref jobs_function_setnotifypendingcallback and + * @ref jobs_function_setnotifynextcallback, respectively. (See documentation for + * those functions on how to remove their callbacks). */ /* @[declare_jobs_removepersistentsubscriptions] */ AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequestInfo_t * pRequestInfo, diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 44ac0ee4f5..b320500bff 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -156,7 +156,7 @@ void AwsIotShadow_Cleanup( void ); * AwsIotShadowError_t deleteResult = AwsIotShadow_Delete( mqttConnection, * THING_NAME, * THING_NAME_LENGTH, - * 0, + * AWS_IOT_SHADOW_FLAG_WAITABLE, * NULL, * &deleteOperation ); * @@ -825,10 +825,10 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon * @brief Remove persistent Thing Shadow operation topic subscriptions. * * Passing the flag @ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS to @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update causes the Shadow operation topic - * subscriptions to be maintained for future calls to the same function. If a persistent - * subscription for a Shadow topic are no longer needed, this function may be used to - * remove it. + * @ref shadow_function_get, @ref shadow_function_update, or their Timed variants + * causes the Shadow operation topic subscriptions to be maintained for future calls to the + * same function. If a persistent subscription for a Shadow topic are no longer needed, + * this function may be used to remove it. * * @param[in] mqttConnection The MQTT connection associated with the persistent subscription. * @param[in] pThingName The Thing Name associated with the persistent subscription. From d9a7e01065719125c94ba07c1d7e41787f0c8953 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 16 Jul 2019 11:42:13 +0800 Subject: [PATCH 230/844] Flush incoming data on MQTT receive malloc failure (#521) --- lib/source/mqtt/iot_mqtt_network.c | 41 +++++++++++++++++++++++ tests/mqtt/unit/iot_tests_mqtt_receive.c | 42 ++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index 1d397095f3..e0829eb58d 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -85,6 +85,22 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne static void _sendPuback( _mqttConnection_t * pMqttConnection, uint16_t packetIdentifier ); +/** + * @brief Flush a packet from the stream of incoming data. + * + * This function is called when memory for a packet cannot be allocated. The + * packet is flushed from the stream of incoming data so that the next packet + * may be read. + * + * @param[in] pNetworkConnection Network connection to use for receive, which + * may be different from the network connection associated with the MQTT connection. + * @param[in] pMqttConnection The associated MQTT connection. + * @param[in] length The length of the packet to flush. + */ +static void _flushPacket( void * pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + size_t length ); + /*-----------------------------------------------------------*/ static bool _incomingPacketValid( uint8_t packetType ) @@ -197,6 +213,14 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, if( pIncomingPacket->pRemainingData == NULL ) { + IotLogError( "(MQTT connection %p) Failed to allocate buffer of length " + "%lu for incoming packet type %lu.", + pMqttConnection, + ( unsigned long ) pIncomingPacket->remainingLength, + ( unsigned long ) pIncomingPacket->type ); + + _flushPacket( pNetworkConnection, pMqttConnection, pIncomingPacket->remainingLength ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else @@ -727,6 +751,23 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, /*-----------------------------------------------------------*/ +static void _flushPacket( void * pNetworkConnection, + const _mqttConnection_t * pMqttConnection, + size_t length ) +{ + size_t bytesFlushed = 0; + uint8_t receivedByte = 0; + + for( bytesFlushed = 0; bytesFlushed < length; bytesFlushed++ ) + { + ( void ) _IotMqtt_GetNextByte( pNetworkConnection, + pMqttConnection->pNetworkInterface, + &receivedByte ); + } +} + +/*-----------------------------------------------------------*/ + bool _IotMqtt_GetNextByte( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface, uint8_t * pIncomingByte ) diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 75485a3860..5790e3d3ef 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -628,6 +628,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_Receive ) { RUN_TEST_CASE( MQTT_Unit_Receive, DecodeRemainingLength ); RUN_TEST_CASE( MQTT_Unit_Receive, InvalidPacket ); + RUN_TEST_CASE( MQTT_Unit_Receive, ReceiveMallocFail ); RUN_TEST_CASE( MQTT_Unit_Receive, ConnackValid ); RUN_TEST_CASE( MQTT_Unit_Receive, ConnackInvalid ); RUN_TEST_CASE( MQTT_Unit_Receive, PublishValid ); @@ -775,6 +776,47 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of @ref mqtt_function_receivecallback when memory + * allocation fails. + */ +TEST( MQTT_Unit_Receive, ReceiveMallocFail ) +{ + _receiveContext_t receiveContext = { 0 }; + + /* Data stream to process. Contains 2 SUBACKs. */ + const uint8_t pDataStream[] = + { + 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02, + 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02 + }; + + /* Logging uses malloc and will interfere with this test. */ + #if LIBRARY_LOG_LEVEL != IOT_LOG_NONE + #error "This test does not work when logging is enabled." + #endif + + /* Set the members of the receive context. */ + receiveContext.pData = pDataStream; + receiveContext.dataLength = sizeof( pDataStream ); + + /* Set malloc to fail and process the first SUBACK. */ + UnityMalloc_MakeMallocFailAfterCount( 0 ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); + + /* Allow the use of malloc and process the second SUBACK. */ + UnityMalloc_MakeMallocFailAfterCount( -1 ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); + + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of @ref mqtt_function_receivecallback with a spec-compliant * CONNACK. From 83eedb6970598a6e893181771bae7fe4dbd7377f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 16 Jul 2019 12:13:01 +0800 Subject: [PATCH 231/844] Fix CI coverage builds (#522) --- tests/mqtt/unit/iot_tests_mqtt_receive.c | 57 +++++++++++++----------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index 5790e3d3ef..eef146d8c1 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -782,37 +782,42 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) */ TEST( MQTT_Unit_Receive, ReceiveMallocFail ) { - _receiveContext_t receiveContext = { 0 }; - - /* Data stream to process. Contains 2 SUBACKs. */ - const uint8_t pDataStream[] = - { - 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02, - 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02 - }; + /* Logging uses malloc and will interfere with this test. Only run if logging + * is disabled. */ + #if ( LIBRARY_LOG_LEVEL == IOT_LOG_NONE ) + _receiveContext_t receiveContext = { 0 }; - /* Logging uses malloc and will interfere with this test. */ - #if LIBRARY_LOG_LEVEL != IOT_LOG_NONE - #error "This test does not work when logging is enabled." - #endif + /* Data stream to process. Contains 2 SUBACKs. */ + const uint8_t pDataStream[] = + { + 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02, + 0x90, 0x05, 0x00, 0x01, 0x00, 0x01, 0x02 + }; - /* Set the members of the receive context. */ - receiveContext.pData = pDataStream; - receiveContext.dataLength = sizeof( pDataStream ); + /* Set the members of the receive context. */ + receiveContext.pData = pDataStream; + receiveContext.dataLength = sizeof( pDataStream ); - /* Set malloc to fail and process the first SUBACK. */ - UnityMalloc_MakeMallocFailAfterCount( 0 ); - IotMqtt_ReceiveCallback( &receiveContext, - _pMqttConnection ); + /* Set malloc to fail and process the first SUBACK. */ + UnityMalloc_MakeMallocFailAfterCount( 0 ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); - /* Allow the use of malloc and process the second SUBACK. */ - UnityMalloc_MakeMallocFailAfterCount( -1 ); - IotMqtt_ReceiveCallback( &receiveContext, - _pMqttConnection ); + /* Allow the use of malloc and process the second SUBACK. */ + UnityMalloc_MakeMallocFailAfterCount( -1 ); + IotMqtt_ReceiveCallback( &receiveContext, + _pMqttConnection ); - /* Network close function should not have been invoked. */ - TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); - TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); + /* Network close function should not have been invoked. */ + TEST_ASSERT_EQUAL_INT( false, _networkCloseCalled ); + TEST_ASSERT_EQUAL_INT( false, _disconnectCallbackCalled ); + #else + /* Test tear down for this test group checks that deserializer overrides + * were called. Set these values to true so that the checks pass. */ + _deserializeOverrideCalled = true; + _getPacketTypeCalled = true; + _getRemainingLengthCalled = true; + #endif /* if LIBRARY_LOG_LEVEL != IOT_LOG_NONE */ } /*-----------------------------------------------------------*/ From e30b4f3d4c97fd845dbaf1419d8290eca798c38e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 16 Jul 2019 13:00:47 +0800 Subject: [PATCH 232/844] Use atomics in MQTT keep-alive (#523) --- lib/source/mqtt/iot_mqtt_network.c | 22 ++++++++++------------ lib/source/mqtt/iot_mqtt_operation.c | 18 ++++++++++++------ platform/network/iot_network_mbedtls.c | 6 ++++-- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index e0829eb58d..e1e3db30b9 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -39,6 +39,9 @@ /* Platform layer includes. */ #include "platform/iot_threads.h" +/* Atomics include. */ +#include "iot_atomic.h" + /*-----------------------------------------------------------*/ /** @@ -613,22 +616,18 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne if( status == IOT_MQTT_SUCCESS ) { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - - if( pMqttConnection->pingreq.u.operation.periodic.ping.failure == 0 ) + if( Atomic_CompareAndSwap_u32( &( pMqttConnection->pingreq.u.operation.periodic.ping.failure ), + 0, + 1 ) == 1 ) { - IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", - pMqttConnection ); + IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", + pMqttConnection ); } else { - IotLogDebug( "(MQTT connection %p) PINGRESP successfully parsed.", - pMqttConnection ); - - pMqttConnection->pingreq.u.operation.periodic.ping.failure = 0; + IotLogWarn( "(MQTT connection %p) Unexpected PINGRESP received.", + pMqttConnection ); } - - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } else { @@ -819,7 +818,6 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pMqttConnection->disconnected = true; - pMqttConnection->pingreq.u.operation.periodic.ping.failure = 1; if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) { diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index f43ba080bf..7d50f0290f 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -40,6 +40,9 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* Atomics include. */ +#include "iot_atomic.h" + /*-----------------------------------------------------------*/ /** @@ -676,9 +679,13 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, void * pContext ) { bool status = true; + uint32_t swapStatus = 0; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; size_t bytesSent = 0; + /* Swap status is not checked when asserts are disabled. */ + ( void ) swapStatus; + /* Retrieve the MQTT connection from the context. */ _mqttConnection_t * pMqttConnection = ( _mqttConnection_t * ) pContext; _mqttOperation_t * pPingreqOperation = &( pMqttConnection->pingreq ); @@ -706,8 +713,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, &pKeepAliveJob ); IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - /* Determine whether to send a PINGREQ or check for PINGRESP. */ if( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) @@ -730,7 +735,10 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { /* Assume the keep-alive will fail. The network receive callback will * clear the failure flag upon receiving a PINGRESP. */ - pPingreqOperation->u.operation.periodic.ping.failure = 1; + swapStatus = Atomic_CompareAndSwap_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), + 1, + 0 ); + IotMqtt_Assert( swapStatus == 1 ); /* Schedule a check for PINGRESP. */ pPingreqOperation->u.operation.periodic.ping.nextPeriodMs = IOT_MQTT_RESPONSE_WAIT_MS; @@ -744,7 +752,7 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { IotLogDebug( "(MQTT connection %p) Checking for PINGRESP.", pMqttConnection ); - if( pPingreqOperation->u.operation.periodic.ping.failure == 0 ) + if( Atomic_Add_u32( &( pPingreqOperation->u.operation.periodic.ping.failure ), 0 ) == 0 ) { IotLogDebug( "(MQTT connection %p) PINGRESP was received.", pMqttConnection ); @@ -791,8 +799,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, EMPTY_ELSE_MARKER; } - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - /* Close the connection on failures. */ if( status == false ) { diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index 01c05cdd66..6f1fbc746a 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -389,7 +389,6 @@ static void _receiveThread( void * pArgument ) /* Check if connection is closed. */ IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); connectionClosed = ( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ); - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); if( connectionClosed == false ) { @@ -400,7 +399,10 @@ static void _receiveThread( void * pArgument ) pNetworkConnection->pReceiveContext ); } } - else + + IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + + if( connectionClosed == true ) { break; } From d30c1e77585b77f91b855fd2b2f85548c872adb8 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 17 Jul 2019 23:38:59 +0800 Subject: [PATCH 233/844] Minor logging/test fix (#525) --- lib/source/mqtt/iot_mqtt_operation.c | 4 ++-- tests/mqtt/system/iot_tests_mqtt_system.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 7d50f0290f..fd5b546543 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -539,8 +539,8 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, pMqttConnection, IotMqtt_OperationType( pOperation->u.operation.type ), pOperation, - pOperation->u.operation.jobReference + 1, - pOperation->u.operation.jobReference ); + ( long ) ( pOperation->u.operation.jobReference + 1 ), + ( long ) ( pOperation->u.operation.jobReference ) ); /* The job reference count must be 0 or 1 after the decrement. */ IotMqtt_Assert( ( pOperation->u.operation.jobReference == 0 ) || diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 4e10865a0c..5d21512061 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -688,7 +688,7 @@ TEST_GROUP_RUNNER( MQTT_System ) RUN_TEST_CASE( MQTT_System, RestorePreviousSession ); RUN_TEST_CASE( MQTT_System, WaitAfterDisconnect ); RUN_TEST_CASE( MQTT_System, SubscribeCompleteReentrancy ); - RUN_TEST_CASE( MQTT_System, IncomingPublishReentrancy ) + RUN_TEST_CASE( MQTT_System, IncomingPublishReentrancy ); } /*-----------------------------------------------------------*/ From 6aed9b209469fb5d91759aa0d231467c3a5adc43 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 18 Jul 2019 05:51:54 +0800 Subject: [PATCH 234/844] Fix log message in keep alive job (#526) --- lib/source/mqtt/iot_mqtt_operation.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index fd5b546543..64cc0c7ef6 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -781,9 +781,9 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { - IotLogDebug( "(MQTT connection %p) Next keep-alive job in %d ms.", + IotLogDebug( "(MQTT connection %p) Next keep-alive job in %lu ms.", pMqttConnection, - IOT_MQTT_RESPONSE_WAIT_MS ); + ( unsigned long ) pPingreqOperation->u.operation.periodic.ping.nextPeriodMs ); } else { From 526989a69215fc991eb737258a4d1b6181b50c15 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 5 Aug 2019 14:28:32 -0700 Subject: [PATCH 235/844] Fix spelling error in comment (#531) --- lib/include/types/iot_mqtt_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 4dfbce6492..fdd9b6f2e9 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -683,7 +683,7 @@ typedef struct IotMqttConnectInfo * @ingroup mqtt_datatypes_paramstructs * @brief Function pointers for MQTT packet serializer overrides. * - * These funciton pointers allow the MQTT serialization and deserialization functions + * These function pointers allow the MQTT serialization and deserialization functions * to be overridden for an MQTT connection. The compile-time setting * @ref IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES must be `1` to enable this functionality. * See the #IotMqttSerializer_t::serialize and #IotMqttSerializer_t::deserialize From d0c7eefd0e805a92222636837f4eafdcb2bf8f6c Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 6 Aug 2019 15:18:02 -0700 Subject: [PATCH 236/844] Fix MQTT demo calculation for counting incoming messages (#532) --- demos/source/iot_demo_mqtt.c | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index d2b5b4aa21..eb9fd5eb2b 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -687,8 +687,8 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, /* If a complete burst of messages has been published, wait for an equal * number of messages to be received. Note that messages may be received * out-of-order, especially if a message was lost and had to be retried. */ - if( ( publishCount > 0 ) && - ( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE == 0 ) ) + if( ( publishCount % IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) == + ( IOT_DEMO_MQTT_PUBLISH_BURST_SIZE - 1 ) ) { IotLogInfo( "Waiting for %d publishes to be received.", IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ); @@ -715,30 +715,6 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, } } - /* Wait for the messages in the last burst to be received. This should also - * wait for all previously published messages. */ - if( status == EXIT_SUCCESS ) - { - IotLogInfo( "Waiting for all publishes to be received." ); - - for( i = 0; i < IOT_DEMO_MQTT_PUBLISH_BURST_SIZE; i++ ) - { - if( IotSemaphore_TimedWait( pPublishReceivedCounter, - MQTT_TIMEOUT_MS ) == false ) - { - IotLogError( "Timed out waiting for incoming PUBLISH messages." ); - status = EXIT_FAILURE; - - break; - } - } - - if( i == IOT_DEMO_MQTT_PUBLISH_BURST_SIZE ) - { - IotLogInfo( "All publishes received." ); - } - } - return status; } From 1e41752181c92b8bdd479a3c2b3b12f318be1029 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 12 Aug 2019 09:47:24 -0700 Subject: [PATCH 237/844] Minor fixes in MQTT test code (#534) --- lib/source/jobs/aws_iot_jobs_api.c | 2 +- lib/source/mqtt/iot_mqtt_operation.c | 30 +++++++++++------------ tests/mqtt/system/iot_tests_mqtt_system.c | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 4ec46775bb..9849dc58d3 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -1156,7 +1156,7 @@ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pReque AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, uint32_t timeoutMs, - AwsIotJobsResponse_t * pJobsResponse ) + AwsIotJobsResponse_t * const pJobsResponse ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 64cc0c7ef6..b501f26c44 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -1338,23 +1338,21 @@ void _IotMqtt_Notify( _mqttOperation_t * pOperation ) } else { - EMPTY_ELSE_MARKER; - } - - /* Post to a waitable operation's semaphore. */ - if( waitable == true ) - { - IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " - "notified of completion.", - pOperation->pMqttConnection, - IotMqtt_OperationType( pOperation->u.operation.type ), - pOperation ); + /* Post to a waitable operation's semaphore. */ + if( waitable == true ) + { + IotLogDebug( "(MQTT connection %p, %s operation %p) Waitable operation " + "notified of completion.", + pOperation->pMqttConnection, + IotMqtt_OperationType( pOperation->u.operation.type ), + pOperation ); - IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); - } - else - { - EMPTY_ELSE_MARKER; + IotSemaphore_Post( &( pOperation->u.operation.notify.waitSemaphore ) ); + } + else + { + EMPTY_ELSE_MARKER; + } } } else diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 5d21512061..6044b4df5f 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -1116,7 +1116,7 @@ TEST( MQTT_System, WaitAfterDisconnect ) * timing of publish versus disconnect, so the statuses are not checked. */ for( i = 0; i < 3; i++ ) { - status = IotMqtt_Wait( pPublishOperation[ i ], 100 ); + ( void ) IotMqtt_Wait( pPublishOperation[ i ], 100 ); } } } From 15b31068a1842a616f088021364a1c93c02a88a7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 13 Aug 2019 13:22:14 -0700 Subject: [PATCH 238/844] Invoke send directly in blocking MQTT functions (#533) --- lib/include/iot_mqtt.h | 6 +- lib/include/private/iot_mqtt_internal.h | 14 +- lib/source/mqtt/iot_mqtt_api.c | 219 +++++++++++----------- lib/source/mqtt/iot_mqtt_operation.c | 5 - lib/source/mqtt/iot_mqtt_validate.c | 26 --- tests/mqtt/CMakeLists.txt | 8 +- tests/mqtt/mock/iot_tests_mqtt_mock.c | 6 +- tests/mqtt/unit/iot_tests_mqtt_api.c | 68 +++++++ tests/mqtt/unit/iot_tests_mqtt_validate.c | 15 -- 9 files changed, 200 insertions(+), 167 deletions(-) diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 97cacd5f99..b6775e2454 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -206,13 +206,13 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * * // Example network abstraction types. * IotNetworkServerInfo_t serverInfo = { ... }; - * IotNetworkCredentialInfo_t credentialInfo = { ... }; + * IotNetworkCredentials_t credentialInfo = { ... }; * IotNetworkInterface_t networkInterface = { ... }; * * // Example using a generic network implementation. * networkInfo.createNetworkConnection = true; - * networkInfo.pNetworkServerInfo = &serverInfo; - * networkInfo.pNetworkCredentialInfo = &credentialInfo; + * networkInfo.u.setup.pNetworkServerInfo = &serverInfo; + * networkInfo.u.setup.pNetworkCredentialInfo = &credentialInfo; * networkInfo.pNetworkInterface = &networkInterface; * * // Set the members of the connection info (password and username not used). diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 8bdc73020b..097d8f84f7 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -209,8 +209,6 @@ * * Used to validate parameters if when connecting to an AWS IoT MQTT server. */ -#define AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ( 30 ) /**< @brief Minumum keep-alive interval accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */ #define AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ #define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ #define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ @@ -241,6 +239,16 @@ */ #define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 ) +/** + * @brief When this flag is passed, MQTT functions will execute jobs on the calling + * thread, bypassing the task pool. + * + * This flag is used along with @ref mqtt_constants_flags, but is intended for internal + * use only. Nevertheless, its value must be bitwise exclusive of all conflicting + * @ref mqtt_constants_flags. + */ +#define MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ( 0x80000000 ) + /*---------------------- MQTT internal data structures ----------------------*/ /** @@ -305,7 +313,7 @@ typedef struct _mqttOperation struct { uint32_t failure; /**< @brief Flag tracking keep-alive status. */ - uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ + uint32_t keepAliveMs; /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */ uint32_t nextPeriodMs; /**< @brief Relative delay for next keep-alive job. */ } ping; /**< @brief Additional information for keep-alive pings. */ } periodic; /**< @brief Additional information for periodic operations. */ diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index bc2ea45d12..dbcf8bd316 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -378,32 +378,6 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, IotListDouble_Create( &( pMqttConnection->pendingProcessing ) ); IotListDouble_Create( &( pMqttConnection->pendingResponse ) ); - /* AWS IoT service limits set minimum and maximum values for keep-alive interval. - * Adjust the user-provided keep-alive interval based on these requirements. */ - if( awsIotMqttMode == true ) - { - if( keepAliveSeconds < AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) - { - keepAliveSeconds = AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE; - } - else if( keepAliveSeconds > AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) - { - keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; - } - else if( keepAliveSeconds == 0 ) - { - keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - /* Check if keep-alive is active for this connection. */ if( keepAliveSeconds != 0 ) { @@ -676,6 +650,10 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { IOT_GOTO_CLEANUP(); } + else + { + EMPTY_ELSE_MARKER; + } /* Check the subscription operation data and set the operation type. */ IotMqtt_Assert( pSubscriptionOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); @@ -693,6 +671,10 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { IOT_GOTO_CLEANUP(); } + else + { + EMPTY_ELSE_MARKER; + } /* Check the serialized MQTT packet. */ IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); @@ -710,6 +692,10 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { IOT_GOTO_CLEANUP(); } + else + { + EMPTY_ELSE_MARKER; + } } /* Set the reference, if provided. */ @@ -717,32 +703,51 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { *pOperationReference = pSubscriptionOperation; } + else + { + EMPTY_ELSE_MARKER; + } - /* Schedule the subscription operation for network transmission. */ - status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, - _IotMqtt_ProcessSend, - 0 ); - - if( status != IOT_MQTT_SUCCESS ) + /* Send the SUBSCRIBE packet. */ + if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) + { + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pSubscriptionOperation->job, pSubscriptionOperation ); + } + else { - IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", - mqttConnection, - IotMqtt_OperationType( operation ) ); + status = _IotMqtt_ScheduleOperation( pSubscriptionOperation, + _IotMqtt_ProcessSend, + 0 ); - if( operation == IOT_MQTT_SUBSCRIBE ) + if( status != IOT_MQTT_SUCCESS ) { - _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, - pSubscriptionOperation->u.operation.packetIdentifier, - -1 ); - } + IotLogError( "(MQTT connection %p) Failed to schedule %s for sending.", + mqttConnection, + IotMqtt_OperationType( operation ) ); - /* Clear the previously set (and now invalid) reference. */ - if( pOperationReference != NULL ) - { - *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; - } + if( operation == IOT_MQTT_SUBSCRIBE ) + { + _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, + pSubscriptionOperation->u.operation.packetIdentifier, + -1 ); + } + else + { + EMPTY_ELSE_MARKER; + } - IOT_GOTO_CLEANUP(); + /* Clear the previously set (and now invalid) reference. */ + if( pOperationReference != NULL ) + { + *pOperationReference = IOT_MQTT_OPERATION_INITIALIZER; + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_GOTO_CLEANUP(); + } } /* Clean up if this function failed. */ @@ -754,6 +759,10 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { _IotMqtt_DestroyOperation( pSubscriptionOperation ); } + else + { + EMPTY_ELSE_MARKER; + } } else { @@ -1139,25 +1148,15 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); - /* Add the CONNECT operation to the send queue for network transmission. */ - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - 0 ); + /* Send the CONNECT packet. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); - if( status != IOT_MQTT_SUCCESS ) - { - IotLogError( "Failed to enqueue CONNECT for sending." ); - } - else - { - /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ - status = IotMqtt_Wait( pOperation, - timeoutMs ); + /* Wait for the CONNECT operation to complete, i.e. wait for CONNACK. */ + status = IotMqtt_Wait( pOperation, timeoutMs ); - /* The call to wait cleans up the CONNECT operation, so set the pointer - * to NULL. */ - pOperation = NULL; - } + /* The call to wait cleans up the CONNECT operation, so set the pointer + * to NULL. */ + pOperation = NULL; /* When a connection is successfully established, schedule keep-alive job. */ if( status == IOT_MQTT_SUCCESS ) @@ -1323,36 +1322,27 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); - /* Schedule the DISCONNECT operation for network transmission. */ - if( _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - 0 ) != IOT_MQTT_SUCCESS ) + /* Send the DISCONNECT packet. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + + /* Wait a short time for the DISCONNECT packet to be transmitted. */ + status = IotMqtt_Wait( pOperation, + IOT_MQTT_RESPONSE_WAIT_MS ); + + /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, + * or NETWORK ERROR. */ + if( status == IOT_MQTT_SUCCESS ) { - IotLogWarn( "(MQTT connection %p) Failed to schedule DISCONNECT for sending.", - mqttConnection ); - _IotMqtt_DestroyOperation( pOperation ); + IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); } else { - /* Wait a short time for the DISCONNECT packet to be transmitted. */ - status = IotMqtt_Wait( pOperation, - IOT_MQTT_RESPONSE_WAIT_MS ); - - /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, - * or NETWORK ERROR. */ - if( status == IOT_MQTT_SUCCESS ) - { - IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); - } - else - { - IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || - ( status == IOT_MQTT_NETWORK_ERROR ) ); + IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || + ( status == IOT_MQTT_NETWORK_ERROR ) ); - IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", - mqttConnection, - IotMqtt_strerror( status ) ); - } + IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", + mqttConnection, + IotMqtt_strerror( status ) ); } } else @@ -1431,7 +1421,7 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, status = IotMqtt_Subscribe( mqttConnection, pSubscriptionList, subscriptionCount, - IOT_MQTT_FLAG_WAITABLE, + IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, NULL, &subscribeOperation ); @@ -1487,7 +1477,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, status = IotMqtt_Unsubscribe( mqttConnection, pSubscriptionList, subscriptionCount, - IOT_MQTT_FLAG_WAITABLE, + IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, NULL, &unsubscribeOperation ); @@ -1694,38 +1684,45 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, EMPTY_ELSE_MARKER; } - /* Add the PUBLISH operation to the send queue for network transmission. */ - status = _IotMqtt_ScheduleOperation( pOperation, - _IotMqtt_ProcessSend, - 0 ); - - if( status != IOT_MQTT_SUCCESS ) + /* Send the PUBLISH packet. */ + if( ( flags & MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) == MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ) { - IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", - mqttConnection ); + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + } + else + { + status = _IotMqtt_ScheduleOperation( pOperation, + _IotMqtt_ProcessSend, + 0 ); - /* Clear the previously set (and now invalid) reference. */ - if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) + if( status != IOT_MQTT_SUCCESS ) { - if( pPublishOperation != NULL ) + IotLogError( "(MQTT connection %p) Failed to enqueue PUBLISH for sending.", + mqttConnection ); + + /* Clear the previously set (and now invalid) reference. */ + if( pPublishInfo->qos != IOT_MQTT_QOS_0 ) { - *pPublishOperation = IOT_MQTT_OPERATION_INITIALIZER; + if( pPublishOperation != NULL ) + { + *pPublishOperation = IOT_MQTT_OPERATION_INITIALIZER; + } + else + { + EMPTY_ELSE_MARKER; + } } else { EMPTY_ELSE_MARKER; } + + IOT_GOTO_CLEANUP(); } else { EMPTY_ELSE_MARKER; } - - IOT_GOTO_CLEANUP(); - } - else - { - EMPTY_ELSE_MARKER; } /* Clean up the PUBLISH operation if this function fails. Otherwise, set the @@ -1772,13 +1769,13 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, * pPublishOperation = NULL; - /* Clear the flags. */ - flags = 0; + /* Clear the flags, setting only the "serial" flag. */ + flags = MQTT_INTERNAL_FLAG_BLOCK_ON_SEND; /* Set the waitable flag and reference for QoS 1 PUBLISH. */ if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { - flags = IOT_MQTT_FLAG_WAITABLE; + flags |= IOT_MQTT_FLAG_WAITABLE; pPublishOperation = &publishOperation; } else diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index b501f26c44..708d25e196 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -506,11 +506,6 @@ bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation, pOperation->job, NULL ); - /* If the operation's job was not canceled, it must be already executing. - * Any other return value is invalid. */ - IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || - ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); - if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { IotLogDebug( "(MQTT connection %p, %s operation %p) Job canceled.", diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index 48b6f138fd..3bb477503e 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -139,32 +139,6 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { EMPTY_ELSE_MARKER; } - - /* Check for compliance with AWS IoT keep-alive limits. */ - if( pConnectInfo->keepAliveSeconds == 0 ) - { - IotLogWarn( "AWS IoT does not support disabling keep-alive. Default keep-alive " - "of %d seconds will be used.", - AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); - } - else if( pConnectInfo->keepAliveSeconds < AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ) - { - IotLogWarn( "AWS IoT does not support keep-alive intervals less than %d seconds. " - "An interval of %d seconds will be used.", - AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE, - AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE ); - } - else if( pConnectInfo->keepAliveSeconds > AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ) - { - IotLogWarn( "AWS IoT does not support keep-alive intervals greater than %d seconds. " - "An interval of %d seconds will be used.", - AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE, - AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE ); - } - else - { - EMPTY_ELSE_MARKER; - } } else { diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt index ee967e9438..e1d8e35bc2 100644 --- a/tests/mqtt/CMakeLists.txt +++ b/tests/mqtt/CMakeLists.txt @@ -14,7 +14,12 @@ add_executable( iot_tests_mqtt ${MQTT_SYSTEM_TEST_SOURCES} ${MQTT_UNIT_TEST_SOURCES} iot_tests_mqtt.c - ${IOT_TEST_APP_FILES} ) + ${IOT_TEST_APP_FILES} + ${MQTT_MOCK_SOURCES} ) + +# Include directory for MQTT mock. +target_include_directories( iot_tests_mqtt PRIVATE + ${PROJECT_SOURCE_DIR}/tests/mqtt/mock ) # Define the test to run. target_compile_definitions( iot_tests_mqtt PRIVATE @@ -27,4 +32,5 @@ target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt unity ) set_property( TARGET iot_tests_mqtt PROPERTY FOLDER "tests" ) source_group( system FILES ${MQTT_SYSTEM_TEST_SOURCES} ) source_group( unit FILES ${MQTT_UNIT_TEST_SOURCES} ) +source_group( mock FILES ${MQTT_MOCK_SOURCES} ) source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_mqtt.c ) diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.c b/tests/mqtt/mock/iot_tests_mqtt_mock.c index 07394711b7..ad775b1d0d 100644 --- a/tests/mqtt/mock/iot_tests_mqtt_mock.c +++ b/tests/mqtt/mock/iot_tests_mqtt_mock.c @@ -261,9 +261,9 @@ static size_t _sendSuccess( void * pSendContext, IotTest_Assert( _lastPacketIdentifier != 0 ); /* Set the receive thread to run after a "network round-trip". */ - IotTest_Assert( IotClock_TimerArm( &_receiveTimer, - NETWORK_ROUND_TRIP_TIME_MS, - 0 ) == true ); + ( void ) IotClock_TimerArm( &_receiveTimer, + NETWORK_ROUND_TRIP_TIME_MS, + 0 ); } IotMutex_Unlock( &_lastPacketMutex ); diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index d8e3605e4d..a6baf54efe 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -46,6 +46,9 @@ /* MQTT test access include. */ #include "iot_test_access_mqtt.h" +/* MQTT mock include. */ +#include "iot_tests_mqtt_mock.h" + /* Atomics include. */ #include "iot_atomic.h" @@ -567,6 +570,7 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, SubscribeUnsubscribeParameters ); RUN_TEST_CASE( MQTT_Unit_API, SubscribeMallocFail ); RUN_TEST_CASE( MQTT_Unit_API, UnsubscribeMallocFail ); + RUN_TEST_CASE( MQTT_Unit_API, SingleThreaded ); RUN_TEST_CASE( MQTT_Unit_API, KeepAlivePeriodic ); RUN_TEST_CASE( MQTT_Unit_API, KeepAliveJobCleanup ); } @@ -1354,6 +1358,70 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) /*-----------------------------------------------------------*/ +/** + * @brief Test that MQTT can work in a single thread without the task pool. + */ +TEST( MQTT_Unit_API, SingleThreaded ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotTaskPoolInfo_t taskPoolInfo = IOT_TASKPOOL_INFO_INITIALIZER_SMALL; + + /* Shut down the system task pool to test if MQTT works without it. */ + IotTaskPool_Destroy( IOT_SYSTEM_TASKPOOL ); + + /* Set the members of the subscription. */ + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; + + /* Set the members of the publish info. */ + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + publishInfo.pPayload = "test"; + publishInfo.payloadLength = 4; + publishInfo.qos = IOT_MQTT_QOS_1; + + if( TEST_PROTECT() ) + { + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); + + /* Add a subscription. */ + status = IotMqtt_TimedSubscribe( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Transmit a message with no retry. */ + status = IotMqtt_TimedPublish( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Remove the subscription. */ + status = IotMqtt_TimedUnsubscribe( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Re-initialize the system task pool. The task pool must be available to + * send messages with a retry. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); + + /* Transmit a message with a retry. */ + publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; + publishInfo.retryMs = DUP_CHECK_RETRY_MS; + status = IotMqtt_TimedPublish( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + IotTest_MqttMockCleanup(); + } + else + { + /* Re-initialize the system task pool for test tear down. */ + TEST_ASSERT_EQUAL( IOT_TASKPOOL_SUCCESS, IotTaskPool_CreateSystemTaskPool( &taskPoolInfo ) ); + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests keep-alive handling and ensures that it is periodic. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 0eac921563..7659114408 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -143,21 +143,6 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); connectInfo.clientIdentifierLength = 24; - - /* Keep-alive disabled. */ - connectInfo.keepAliveSeconds = 0; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Keep-alive too small. */ - connectInfo.keepAliveSeconds = AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE - 1; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); - - /* Keep-alive too large. */ - connectInfo.keepAliveSeconds = AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE + 1; - validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); - TEST_ASSERT_EQUAL_INT( true, validateStatus ); #endif /* if AWS_IOT_MQTT_SERVER == true */ } From 5c3c51d3b9d0bc0783d119ae26b591d58eb9356e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 15 Aug 2019 10:43:46 -0700 Subject: [PATCH 239/844] Fix warnings on MSVC (#535) --- lib/source/mqtt/iot_mqtt_operation.c | 10 +++++--- lib/source/mqtt/iot_mqtt_subscription.c | 34 ++++++++++++------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 708d25e196..5a978a305c 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -819,7 +819,6 @@ void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool, * are disabled. */ ( void ) pTaskPool; ( void ) pPublishJob; - IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pOperation->incomingPublish == true ); IotMqtt_Assert( pPublishJob == pOperation->job ); @@ -868,7 +867,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, * are disabled. */ ( void ) pTaskPool; ( void ) pSendJob; - IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pSendJob == pOperation->job ); /* The given operation must have an allocated packet and be waiting for a status. */ @@ -1134,7 +1132,11 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; _mqttOperation_t * pResult = NULL; IotLink_t * pResultLink = NULL; - _operationMatchParam_t param = { .type = type, .pPacketIdentifier = pPacketIdentifier }; + _operationMatchParam_t operationMatchParams = { 0 }; + + /* Set the members of the search parameter. */ + operationMatchParams.type = type; + operationMatchParams.pPacketIdentifier = pPacketIdentifier; if( pPacketIdentifier != NULL ) { @@ -1156,7 +1158,7 @@ _mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection, pResultLink = IotListDouble_FindFirstMatch( &( pMqttConnection->pendingResponse ), NULL, _mqttOperation_match, - ¶m ); + &operationMatchParams ); /* Check if a match was found. */ if( pResultLink != NULL ) diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index a052f373c4..e3a3233933 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -402,12 +402,12 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, void ( * callbackFunction )( void *, IotMqttCallbackParam_t * ) = NULL; - _topicMatchParams_t topicMatchParams = - { - .pTopicName = pCallbackParam->u.message.info.pTopicName, - .topicNameLength = pCallbackParam->u.message.info.topicNameLength, - .exactMatchOnly = false - }; + _topicMatchParams_t topicMatchParams = { 0 }; + + /* Set the members of the search parameter. */ + topicMatchParams.pTopicName = pCallbackParam->u.message.info.pTopicName; + topicMatchParams.topicNameLength = pCallbackParam->u.message.info.topicNameLength; + topicMatchParams.exactMatchOnly = false; /* Prevent any other thread from modifying the subscription list while this * function is searching. */ @@ -504,11 +504,11 @@ void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, uint16_t packetIdentifier, int32_t order ) { - const _packetMatchParams_t packetMatchParams = - { - .packetIdentifier = packetIdentifier, - .order = order - }; + _packetMatchParams_t packetMatchParams = { 0 }; + + /* Set the members of the search parameter. */ + packetMatchParams.packetIdentifier = packetIdentifier; + packetMatchParams.order = order; IotMutex_Lock( &( pMqttConnection->subscriptionMutex ) ); IotListDouble_RemoveAllMatches( &( pMqttConnection->subscriptionList ), @@ -589,12 +589,12 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, bool status = false; _mqttSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - _topicMatchParams_t topicMatchParams = - { - .pTopicName = pTopicFilter, - .topicNameLength = topicFilterLength, - .exactMatchOnly = true - }; + _topicMatchParams_t topicMatchParams = { 0 }; + + /* Set the members of the search parameter. */ + topicMatchParams.pTopicName = pTopicFilter; + topicMatchParams.topicNameLength = topicFilterLength; + topicMatchParams.exactMatchOnly = true; /* Prevent any other thread from modifying the subscription list while this * function is running. */ From 75f90d289e669eb7fe10184aee307a0762d1d869 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 21 Aug 2019 10:03:27 -0700 Subject: [PATCH 240/844] Minor doc fix (#539) --- doc/guide/developer/automated_tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index d47fdeaf14..444e4670b5 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -133,7 +133,7 @@ AWS recommends using the most restrictive policy possible. The following policy "arn:aws:iot:::topic/${iot:ClientId}/*", "arn:aws:iot:::topic/*/LastWillAndTestament", "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/shadow/*", - "arn:aws:iot:::topicfilter/$aws/things/${iot:ClientId}/jobs/*" + "arn:aws:iot:::topic/$aws/things/${iot:ClientId}/jobs/*" ] }, { From 561da98cc79e6881ec2f97e4cecb81384a91f25e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 28 Aug 2019 10:58:14 -0700 Subject: [PATCH 241/844] Add connection clean up in keep-alive job (#542) --- lib/source/mqtt/iot_mqtt_network.c | 5 ----- lib/source/mqtt/iot_mqtt_operation.c | 21 ++++++++++++++------- platform/network/iot_network_mbedtls.c | 2 +- tests/mqtt/unit/iot_tests_mqtt_api.c | 3 +++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index e1e3db30b9..f453390a72 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -834,11 +834,6 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason pMqttConnection->pingreq.job, NULL ); - /* If the keep-alive job was not canceled, it must be already executing. - * Any other return value is invalid. */ - IotMqtt_Assert( ( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) || - ( taskPoolStatus == IOT_TASKPOOL_CANCEL_FAILED ) ); - /* Clean up keep-alive if its job was successfully canceled. Otherwise, * the executing keep-alive job will clean up itself. */ if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 5a978a305c..23f305312c 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -701,13 +701,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, IotLogDebug( "(MQTT connection %p) Keep-alive job started.", pMqttConnection ); - /* Re-create the keep-alive job for rescheduling. This should never fail. */ - taskPoolStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, - pContext, - IotTaskPool_GetJobStorageFromHandle( pKeepAliveJob ), - &pKeepAliveJob ); - IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); - /* Determine whether to send a PINGREQ or check for PINGRESP. */ if( pPingreqOperation->u.operation.periodic.ping.nextPeriodMs == pPingreqOperation->u.operation.periodic.ping.keepAliveMs ) @@ -770,6 +763,15 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, * response shortly. */ if( status == true ) { + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + + /* Re-create the keep-alive job for rescheduling. This should never fail. */ + taskPoolStatus = IotTaskPool_CreateJob( _IotMqtt_ProcessKeepAlive, + pContext, + IotTaskPool_GetJobStorageFromHandle( pKeepAliveJob ), + &pKeepAliveJob ); + IotMqtt_Assert( taskPoolStatus == IOT_TASKPOOL_SUCCESS ); + taskPoolStatus = IotTaskPool_ScheduleDeferred( pTaskPool, pKeepAliveJob, pPingreqOperation->u.operation.periodic.ping.nextPeriodMs ); @@ -788,6 +790,8 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, status = false; } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } else { @@ -799,6 +803,9 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, { _IotMqtt_CloseNetworkConnection( IOT_MQTT_KEEP_ALIVE_TIMEOUT, pMqttConnection ); + + /* Keep-alive has failed and will no longer use this MQTT connection. */ + _IotMqtt_DecrementConnectionReferences( pMqttConnection ); } else { diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index 6f1fbc746a..51a362f2a9 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -157,7 +157,7 @@ typedef struct _networkConnection IotSemaphore_t destroyNotification; /**< @brief Notifies the receive callback that the connection was destroyed. */ /** - * @brief Secured connection context. Valid if `secured` is `true`. + * @brief Secured connection context. Valid if #FLAG_SECURED is set. */ struct { diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index a6baf54efe..d1684027a4 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -252,6 +252,9 @@ static size_t _sendSuccess( void * pSendContext, if( pWaitSem != NULL ) { IotSemaphore_Post( pWaitSem ); + + /* Yield the processor to context switch. */ + IotClock_SleepMs( 10 ); } /* This function returns the message length to simulate a successful send. */ From dc8481b589dbeae0e66a02cb8a4d04bdeaade66a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 30 Aug 2019 12:07:46 -0700 Subject: [PATCH 242/844] Don't assert on linked operations (#544) --- lib/source/mqtt/iot_mqtt_operation.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 23f305312c..59977566f6 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -311,11 +311,11 @@ static bool _scheduleNextRetry( _mqttOperation_t * pOperation ) * list to the pending responses list on the first retry. */ if( firstRetry == true ) { - /* Operation must be linked. */ - IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) == true ); + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } - /* Transfer to pending response list. */ - IotListDouble_Remove( &( pOperation->link ) ); IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), &( pOperation->link ) ); } @@ -989,11 +989,11 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - /* Operation must be linked. */ - IotMqtt_Assert( IotLink_IsLinked( &( pOperation->link ) ) ); + if( IotLink_IsLinked( &( pOperation->link ) ) == true ) + { + IotListDouble_Remove( &( pOperation->link ) ); + } - /* Transfer to pending response list. */ - IotListDouble_Remove( &( pOperation->link ) ); IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), &( pOperation->link ) ); From 7c376de1bf8e0abe2599e7716ed8af78db2d30d8 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 30 Aug 2019 13:04:02 -0700 Subject: [PATCH 243/844] Resolve sanitizer warnings (#545) --- lib/source/mqtt/iot_mqtt_operation.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 59977566f6..765d52b237 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -974,6 +974,8 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { /* Decrement reference count to signal completion of send job. Check * if the operation should be destroyed. */ + IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); + if( waitable == true ) { destroyOperation = _IotMqtt_DecrementOperationReferences( pOperation, false ); @@ -987,8 +989,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, * pending processing to the pending response list. */ if( destroyOperation == false ) { - IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); - if( IotLink_IsLinked( &( pOperation->link ) ) == true ) { IotListDouble_Remove( &( pOperation->link ) ); @@ -997,8 +997,6 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, IotListDouble_InsertHead( &( pMqttConnection->pendingResponse ), &( pOperation->link ) ); - IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); - /* This operation is now awaiting a response from the network. */ networkPending = true; } @@ -1006,6 +1004,8 @@ void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool, { EMPTY_ELSE_MARKER; } + + IotMutex_Unlock( &( pMqttConnection->referencesMutex ) ); } } else From 8203ad4b45bf6650f44ff1caaf7b7312e092447a Mon Sep 17 00:00:00 2001 From: Hein Tibosch Date: Fri, 30 Aug 2019 22:07:32 +0200 Subject: [PATCH 244/844] iot_demo_mqtt.c : check identifier for v4_beta (#543) --- demos/source/iot_demo_mqtt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index eb9fd5eb2b..f79a06e01e 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -427,7 +427,7 @@ static int _establishMqttConnection( bool awsIotMqttMode, /* Use the parameter client identifier if provided. Otherwise, generate a * unique client identifier. */ - if( pIdentifier != NULL ) + if( ( pIdentifier != NULL ) && ( pIdentifier[ 0 ] != '\0' ) ) { connectInfo.pClientIdentifier = pIdentifier; connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); From 1f44671f6728c4398428738067efc91a7a035c29 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 4 Sep 2019 13:47:51 -0700 Subject: [PATCH 245/844] Fix file organization in Jobs tests (#547) --- tests/jobs/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/jobs/CMakeLists.txt b/tests/jobs/CMakeLists.txt index a1f7840f89..f7d17ed596 100644 --- a/tests/jobs/CMakeLists.txt +++ b/tests/jobs/CMakeLists.txt @@ -1,11 +1,11 @@ # Jobs unit test sources. set( JOBS_UNIT_TEST_SOURCES - system/aws_iot_tests_jobs_system.c unit/aws_iot_tests_jobs_api.c unit/aws_iot_tests_jobs_serialize.c ) # Jobs tests executable. add_executable( aws_iot_tests_jobs + system/aws_iot_tests_jobs_system.c ${JOBS_UNIT_TEST_SOURCES} aws_iot_tests_jobs.c ${IOT_TEST_APP_FILES} @@ -24,6 +24,7 @@ target_link_libraries( aws_iot_tests_jobs PRIVATE awsiotjobs unity ) # Organization of Jobs tests in folders. set_property( TARGET aws_iot_tests_jobs PROPERTY FOLDER "tests" ) +source_group( system FILES system/aws_iot_tests_jobs_system.c ) source_group( unit FILES ${JOBS_UNIT_TEST_SOURCES} ) source_group( mock FILES ${MQTT_MOCK_SOURCES} ) source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_jobs.c ) From 31ab516793d5f6f17cdfb71e41e1db7b6f6a09d9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 10 Sep 2019 10:00:26 -0700 Subject: [PATCH 246/844] Run Mosquitto locally for PR checks (#552) --- .travis.yml | 2 ++ doc/guide/developer/automated_tests.txt | 6 ++---- doc/guide/developer/style.txt | 1 - doc/lib/mqtt.txt | 4 ++-- scripts/ci_test_mqtt.sh | 4 ++-- tests/iot_config.h | 9 +++++++-- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3aeb96da09..7e3e849841 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,8 @@ before_install: install: # Install OpenSSL if needed. - if [ "$NETWORK_STACK" = "openssl" ]; then sudo apt-get install -y libssl-dev; fi + # Install Mosquitto for MQTT pull request builds. + - if [ "$RUN_TEST" = "mqtt" ] && [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sudo apt-get install -y mosquitto; fi # Install dependencies for Jobs tests. - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sudo apt-get install -y python3-setuptools python3-pip athena-jot; fi - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then pip3 install --user wheel; fi diff --git a/doc/guide/developer/automated_tests.txt b/doc/guide/developer/automated_tests.txt index 444e4670b5..5a6bd21913 100644 --- a/doc/guide/developer/automated_tests.txt +++ b/doc/guide/developer/automated_tests.txt @@ -47,15 +47,13 @@ All of the automated testing services have web interfaces that can be connected Because pull requests come from other repos and contain unknown code, Travis CI will not provide encrypted environment variables for pull requests. Therefore, pull requests are tested against unsecured servers. The following tests are run for pull requests. - Common (`ci_test_common.sh`): this script tests common components like the task pool, atomics, etc. These tests do not use the network. -- MQTT (`ci_test_mqtt.sh`): the MQTT tests run on an unsecured connection against the broker provided by [test.mosquitto.org](http://test.mosquitto.org/). +- MQTT (`ci_test_mqtt.sh`): the MQTT tests run on an unsecured connection against a locally-installed Mosquitto broker on the Travis CI VM. - Shadow (`ci_test_shadow.sh`): only the Shadow unit tests are run for a pull request build. These tests do not use the network. - Documentation (`ci_test_doc.sh`): checks that the documentation builds against the code in the pull request. -@note Rarely, the broker at test.mosquitto.org goes offline. This will cause all of the [MQTT system tests](@ref iot_tests_mqtt_system.c) to fail. Should this happen, try again in a few hours. - For commits, the credentials necessary for connecting to AWS IoT are provided by Travis CI. The following tests are run for commits. - Common (`ci_test_common.sh`): runs the same tests as the common pull request tests. -- MQTT (`ci_test_mqtt.sh`): runs the same MQTT tests as the MQTT pull request tests, but against the AWS IoT MQTT broker instead of test.mosquitto.org. +- MQTT (`ci_test_mqtt.sh`): runs the same MQTT tests as the MQTT pull request tests, but against the AWS IoT MQTT broker instead of a Mosquitto broker. - Shadow (`ci_test_shadow.sh`): runs both the Shadow unit tests and the Shadow system tests. Requires connection to AWS IoT. - Coverage (`ci_test_coverage.sh`): Gathers code coverage data and submits it to Coveralls. diff --git a/doc/guide/developer/style.txt b/doc/guide/developer/style.txt index 7b04e3718e..6b6261f281 100644 --- a/doc/guide/developer/style.txt +++ b/doc/guide/developer/style.txt @@ -35,7 +35,6 @@ The coding style aims to produce code that is readable and easy to debug. An exa - Only fixed-width integer types should be used. Exceptions for `bool` and types required by third-party APIs. - The default integer in the libraries should be 32 bits wide, i.e. `int32_t` or `uint32_t`. - Sizes and lengths should be represented with `size_t`, and Boolean variables with `bool`. -- The portable format specifiers in `` should be used for logging fixed-width integers. @subsection guide_developer_styleguide_codingstyle_example Example File @brief An example file that follows the coding style rules. diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index d69ef52a97..aa1415d76a 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -137,9 +137,9 @@ No two clients may connect using the same client identifier simultaneously. If t @configdefault The tests will generate a unique client identifier if this setting is undefined. @section IOT_TEST_MQTT_MOSQUITTO -@brief Test the MQTT library against the [public Mosquitto test server](https://test.mosquitto.org/). +@brief Test the MQTT library against the [a Mosquitto test server](https://test.mosquitto.org/). -When this setting is `1`, the MQTT tests will be built to test against an unsecured [public Mosquitto test server](https://test.mosquitto.org/). This allows the MQTT library to be tested against a fully-compliant MQTT server. Because the connection is unsecured, no credentials are needed for testing against Mosquitto. +When this setting is `1`, the MQTT tests will be built to test against an unsecured [a Mosquitto test server](https://test.mosquitto.org/). This allows the MQTT library to be tested against a fully-compliant MQTT server. Because the connection is unsecured, no credentials are needed for testing against Mosquitto. A Mosquitto test server may also be installed locally. @configpossible `0` (use AWS IoT MQTT server) or `1` (use public Mosquitto server)
@configdefault `0` diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index 21722ad00d..b31aa73d1e 100755 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -12,8 +12,8 @@ if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then CMAKE_FLAGS+="$AWS_IOT_CREDENTIAL_DEFINES $COMPILER_OPTIONS" DEMO_OPTIONS="-i $IOT_IDENTIFIER" else - CMAKE_FLAGS+="-DIOT_TEST_MQTT_MOSQUITTO=1 $COMPILER_OPTIONS" - DEMO_OPTIONS="-n -i $IOT_IDENTIFIER" + CMAKE_FLAGS+="-DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\" $COMPILER_OPTIONS" + DEMO_OPTIONS="-n -i $IOT_IDENTIFIER -u -h localhost -p 1883" fi # Build executables. diff --git a/tests/iot_config.h b/tests/iot_config.h index 794f42748b..93abdae4d9 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -36,8 +36,13 @@ #if IOT_TEST_MQTT_MOSQUITTO == 1 /* Mosquitto test server. */ #define IOT_TEST_SECURED_CONNECTION ( 0 ) - #define IOT_TEST_SERVER "test.mosquitto.org" - #define IOT_TEST_PORT ( 1883 ) + + #ifndef IOT_TEST_SERVER + #define IOT_TEST_SERVER "test.mosquitto.org" + #endif + #ifndef IOT_TEST_PORT + #define IOT_TEST_PORT ( 1883 ) + #endif #else /* AWS IoT MQTT server. */ #define IOT_TEST_SECURED_CONNECTION ( 1 ) From 9adcc260d0c1fd8d234a2abea71c6ebee1235144 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 11 Sep 2019 14:27:45 -0700 Subject: [PATCH 247/844] fix:aws defender unit and integration tests (#551) --- platform/network/iot_network_mbedtls.c | 97 +++++++++++++++++++ .../win32/types/iot_platform_types_win32.h | 1 + .../system/aws_iot_tests_defender_system.c | 65 ++----------- 3 files changed, 107 insertions(+), 56 deletions(-) diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index 51a362f2a9..7433070905 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -32,6 +32,12 @@ #include #include +#if !defined( _WIN32 ) && !defined( _WIN64 ) + #include + #include + #include +#endif + /* mbed TLS network include. */ #include "iot_network_mbedtls.h" @@ -1192,6 +1198,97 @@ IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ) void IotNetworkMbedtls_GetServerInfo( void * pConnection, IotMetricsTcpConnection_t * pServerInfo ) { + int status = 0, portLength = 0; + struct sockaddr_storage server = { 0 }; + size_t length = sizeof( struct sockaddr_storage ); + const void * pServerAddress = NULL; + char * pAddressStart = NULL; + const char * pPortFormat = NULL; + uint16_t remotePort = 0; + size_t addressLength = 0; + + /* Cast function parameter to correct type. */ + if( ( pConnection != NULL ) && ( pServerInfo != NULL ) ) + { + _networkConnection_t * const pNetworkConnection = pConnection; + + /* Get peer info. */ + status = getpeername( pNetworkConnection->networkContext.fd, + ( struct sockaddr * ) &server, + &length ); + } + else + { + status = IOT_NETWORK_BAD_PARAMETER; + } + + if( status == 0 ) + { + /* Calculate the pointer to the IP address and get the remote port based + * on protocol version. */ + if( server.ss_family == AF_INET ) + { + /* IPv4. */ + pServerAddress = &( ( ( struct sockaddr_in * ) &server )->sin_addr ); + remotePort = ntohs( ( ( struct sockaddr_in * ) &server )->sin_port ); + + /* Print the IPv4 address at the start of the address buffer. */ + pAddressStart = pServerInfo->pRemoteAddress; + addressLength = IOT_METRICS_IP_ADDRESS_LENGTH; + pPortFormat = ":%hu"; + } + else + { + /* IPv6. */ + pServerAddress = &( ( ( struct sockaddr_in6 * ) &server )->sin6_addr ); + remotePort = ntohs( ( ( struct sockaddr_in6 * ) &server )->sin6_port ); + + /* Enclose the IPv6 address with []. */ + pServerInfo->pRemoteAddress[ 0 ] = '['; + pAddressStart = pServerInfo->pRemoteAddress + 1; + addressLength = IOT_METRICS_IP_ADDRESS_LENGTH - 1; + pPortFormat = "]:%hu"; + } + + /* Convert IP address to text. */ + if( inet_ntop( server.ss_family, + pServerAddress, + pAddressStart, + addressLength ) != NULL ) + { + /* Add the port to the end of the address. */ + addressLength = strlen( pServerInfo->pRemoteAddress ); + + portLength = snprintf( &( pServerInfo->pRemoteAddress[ addressLength ] ), + 7, + pPortFormat, + remotePort ); + + if( portLength > 0 ) + { + pServerInfo->addressLength = addressLength + ( size_t ) portLength; + + IotLogInfo( "(Socket %d) Collecting network metrics for %s.", + pNetworkConnection->socket, + pServerInfo->pRemoteAddress ); + } + else + { + IotLogError( "(Socket %d) Failed to add port to IP address buffer." ); + } + } + else + { + IotLogError( "(Socket %d) Failed to convert IP address to text format.", + pNetworkConnection->socket ); + } + } + else + { + IotLogError( "(Socket %d) Failed to query peer name. errno=%d.", + pNetworkConnection->socket, + errno ); + } } /*-----------------------------------------------------------*/ diff --git a/platform/ports/win32/types/iot_platform_types_win32.h b/platform/ports/win32/types/iot_platform_types_win32.h index 5056680039..6d499251c9 100644 --- a/platform/ports/win32/types/iot_platform_types_win32.h +++ b/platform/ports/win32/types/iot_platform_types_win32.h @@ -33,6 +33,7 @@ /* Win32 includes. WinSock2 is needed to prevent the usage of the WinSock * header in Windows.h */ #include +#include #include /** diff --git a/tests/defender/system/aws_iot_tests_defender_system.c b/tests/defender/system/aws_iot_tests_defender_system.c index 0f2a76911a..7cca68fc12 100644 --- a/tests/defender/system/aws_iot_tests_defender_system.c +++ b/tests/defender/system/aws_iot_tests_defender_system.c @@ -27,10 +27,6 @@ #include #include -/* Network includes. */ -#include -#include - #include "platform/iot_clock.h" /* Defender internal includes. */ @@ -113,16 +109,13 @@ static void _waitForMetricsAccepted( uint32_t timeoutSec ); static void _verifyMetricsCommon( void ); /* Verify tcp connections in metrics report. */ -static void _verifyTcpConections( int total, - ... ); +static void _verifyTcpConnections( int total ); /* Indicate this test doesn't actually publish report. */ static void _publishMetricsNotNeeded( void ); static void _resetCalbackInfo( void ); -static char * _getIotAddress( void ); - TEST_GROUP( Defender_System ); TEST_SETUP( Defender_System ) @@ -183,7 +176,7 @@ TEST_SETUP( Defender_System ) _startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; _startInfo.mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; - _startInfo.mqttConnectionInfo.clientIdentifierLength = strlen( AWS_IOT_TEST_SHADOW_THING_NAME ); + _startInfo.mqttConnectionInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_SHADOW_THING_NAME ); _startInfo.callback = _EMPTY_CALLBACK; } @@ -423,7 +416,7 @@ TEST( Defender_System, Metrics_empty_are_published ) _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); - _verifyTcpConections( 0 ); + _verifyTcpConnections( 0 ); } TEST( Defender_System, Metrics_TCP_connections_all_are_published ) @@ -439,9 +432,6 @@ TEST( Defender_System, Metrics_TCP_connections_all_are_published ) /* Set test callback to verify report. */ _startInfo.callback = _testCallback; - /* Get Iot address from DNS. */ - char * pIotAddress = _getIotAddress(); - /* Start defender. */ error = AwsIotDefender_Start( &_startInfo ); @@ -451,7 +441,7 @@ TEST( Defender_System, Metrics_TCP_connections_all_are_published ) _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); - _verifyTcpConections( 1, pIotAddress ); + _verifyTcpConnections( 1 ); } TEST( Defender_System, Metrics_TCP_connections_total_are_published ) @@ -476,7 +466,7 @@ TEST( Defender_System, Metrics_TCP_connections_total_are_published ) _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); - _verifyTcpConections( 1 ); + _verifyTcpConnections( 1 ); } TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) @@ -492,9 +482,6 @@ TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) /* Set test callback to verify report. */ _startInfo.callback = _testCallback; - /* Get Iot address from DNS. */ - char * pIotAddress = _getIotAddress(); - /* Start defender. */ error = AwsIotDefender_Start( &_startInfo ); @@ -504,7 +491,7 @@ TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); - _verifyTcpConections( 1, pIotAddress ); + _verifyTcpConnections( 1 ); } TEST( Defender_System, Restart_and_updated_metrics_are_published ) @@ -518,8 +505,6 @@ TEST( Defender_System, Restart_and_updated_metrics_are_published ) /* Set test callback to verify report. */ _startInfo.callback = _testCallback; - pIotAddress = _getIotAddress(); - /* Start defender. */ TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); @@ -527,7 +512,7 @@ TEST( Defender_System, Restart_and_updated_metrics_are_published ) _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); - _verifyTcpConections( 1, pIotAddress ); + _verifyTcpConnections( 1 ); AwsIotDefender_Stop(); @@ -539,8 +524,6 @@ TEST( Defender_System, Restart_and_updated_metrics_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ) ); - pIotAddress = _getIotAddress(); - /* Restart defender. */ TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); @@ -548,7 +531,7 @@ TEST( Defender_System, Restart_and_updated_metrics_are_published ) _waitForMetricsAccepted( WAIT_STATE_TOTAL_SECONDS ); _verifyMetricsCommon(); - _verifyTcpConections( 1, pIotAddress ); + _verifyTcpConnections( 1 ); } TEST( Defender_System, SetPeriod_too_short ) @@ -769,8 +752,7 @@ static void _verifyMetricsCommon( void ) /*-----------------------------------------------------------*/ -static void _verifyTcpConections( int total, - ... ) +static void _verifyTcpConnections( int total ) { uint8_t i = 0; @@ -833,10 +815,6 @@ static void _verifyTcpConections( int total, error = _pAwsIotDefenderDecoder->stepIn( &connsObject, &connIterator ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); - /* Create argument list for expected remote addresses. */ - va_list valist; - va_start( valist, total ); - for( i = 0; i < total; i++ ) { /* Assert find one "connection" map in "connections" */ @@ -855,11 +833,6 @@ static void _verifyTcpConections( int total, TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, error ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_TEXT_STRING, remoteAddrObject.type ); - - /* Verify the passed address matching. */ - TEST_ASSERT_EQUAL_STRING_LEN( va_arg( valist, char * ), - remoteAddrObject.u.value.u.string.pString, - remoteAddrObject.u.value.u.string.length ); } else { @@ -873,8 +846,6 @@ static void _verifyTcpConections( int total, _pAwsIotDefenderDecoder->destroy( &connMap ); } - va_end( valist ); - TEST_ASSERT_TRUE( _pAwsIotDefenderDecoder->isEndOfContainer( connIterator ) ); _pAwsIotDefenderDecoder->stepOut( connIterator, &connsObject ); @@ -907,21 +878,3 @@ static void _verifyTcpConections( int total, } /*-----------------------------------------------------------*/ - -static char * _getIotAddress( void ) -{ - static char iotAddress[ MAX_ADDRESS_LENGTH ]; - - struct addrinfo * pListHead = NULL; - char * pIotAddressIp = NULL; - - /* Query DNS to get all the records. */ - getaddrinfo( IOT_TEST_SERVER, NULL, NULL, &pListHead ); - - /* Convert the first record to string format of IP. */ - pIotAddressIp = inet_ntoa( ( ( struct sockaddr_in * ) pListHead->ai_addr )->sin_addr ); - - sprintf( iotAddress, "%s:%d", pIotAddressIp, IOT_TEST_PORT ); - - return iotAddress; -} From 258e0eec839fa3e4f11d12642f8e03ecb062ec39 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Thu, 12 Sep 2019 09:19:11 -0700 Subject: [PATCH 248/844] Fix log messages (#553) --- platform/network/iot_network_mbedtls.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index 7433070905..53f319a78a 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -1206,16 +1206,17 @@ void IotNetworkMbedtls_GetServerInfo( void * pConnection, const char * pPortFormat = NULL; uint16_t remotePort = 0; size_t addressLength = 0; + const _networkConnection_t * pNetworkConnection = NULL; /* Cast function parameter to correct type. */ if( ( pConnection != NULL ) && ( pServerInfo != NULL ) ) { - _networkConnection_t * const pNetworkConnection = pConnection; + pNetworkConnection = pConnection; /* Get peer info. */ status = getpeername( pNetworkConnection->networkContext.fd, ( struct sockaddr * ) &server, - &length ); + ( int * ) &length ); } else { @@ -1269,7 +1270,7 @@ void IotNetworkMbedtls_GetServerInfo( void * pConnection, pServerInfo->addressLength = addressLength + ( size_t ) portLength; IotLogInfo( "(Socket %d) Collecting network metrics for %s.", - pNetworkConnection->socket, + pNetworkConnection->networkContext.fd, pServerInfo->pRemoteAddress ); } else @@ -1280,14 +1281,13 @@ void IotNetworkMbedtls_GetServerInfo( void * pConnection, else { IotLogError( "(Socket %d) Failed to convert IP address to text format.", - pNetworkConnection->socket ); + pNetworkConnection->networkContext.fd ); } } else { - IotLogError( "(Socket %d) Failed to query peer name. errno=%d.", - pNetworkConnection->socket, - errno ); + IotLogError( "(Socket %d) Failed to query peer name.", + pNetworkConnection->networkContext.fd ); } } From 0a81b2602a0b0ad87307a7db5216bda2439bf90e Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 12 Sep 2019 10:06:54 -0700 Subject: [PATCH 249/844] Change argument of getpeername to socklen_t (#554) --- platform/network/iot_network_mbedtls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index 53f319a78a..f3d6a27bca 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -1200,7 +1200,7 @@ void IotNetworkMbedtls_GetServerInfo( void * pConnection, { int status = 0, portLength = 0; struct sockaddr_storage server = { 0 }; - size_t length = sizeof( struct sockaddr_storage ); + socklen_t length = sizeof( struct sockaddr_storage ); const void * pServerAddress = NULL; char * pAddressStart = NULL; const char * pPortFormat = NULL; @@ -1216,7 +1216,7 @@ void IotNetworkMbedtls_GetServerInfo( void * pConnection, /* Get peer info. */ status = getpeername( pNetworkConnection->networkContext.fd, ( struct sockaddr * ) &server, - ( int * ) &length ); + &length ); } else { From f529da9493112cf17a14d463eedc2971750ec2ee Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 12 Sep 2019 11:41:21 -0700 Subject: [PATCH 250/844] Resolve Shadow demo issues in CI (#555) --- demos/source/aws_iot_demo_shadow.c | 112 +++++++++++++++-------------- tests/iot_config.h | 1 + 2 files changed, 61 insertions(+), 52 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index a8e1de7d83..bcade38183 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -268,6 +268,7 @@ static void _shadowDeltaCallback( void * pCallbackContext, int updateDocumentLength = 0; AwsIotShadowError_t updateStatus = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t updateDocument = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + int32_t newState = 0; /* Stored state. */ static int32_t currentState = 0; @@ -288,79 +289,86 @@ static void _shadowDeltaCallback( void * pCallbackContext, /* Change the current state based on the value in the delta document. */ if( *pDelta == '0' ) { - IotLogInfo( "%.*s changing state from %d to 0.", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName, - currentState ); - - currentState = 0; + newState = 0; } else if( *pDelta == '1' ) { - IotLogInfo( "%.*s changing state from %d to 1.", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName, - currentState ); - - currentState = 1; + newState = 1; } else { IotLogWarn( "Unknown powerOn state parsed from delta document." ); - } - /* Set the common members to report the new state. */ - updateDocument.pThingName = pCallbackParam->pThingName; - updateDocument.thingNameLength = pCallbackParam->thingNameLength; - updateDocument.u.update.pUpdateDocument = pUpdateDocument; - updateDocument.u.update.updateDocumentLength = EXPECTED_REPORTED_JSON_SIZE; - - /* Generate a Shadow document for the reported state. To keep the client - * token within 6 characters, it is modded by 1000000. */ - updateDocumentLength = snprintf( pUpdateDocument, - EXPECTED_REPORTED_JSON_SIZE + 1, - SHADOW_REPORTED_JSON, - ( int ) currentState, - ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) ); - - if( ( size_t ) updateDocumentLength != EXPECTED_REPORTED_JSON_SIZE ) - { - IotLogError( "Failed to generate reported state document for Shadow update." ); + /* Set new state to current state to ignore the delta document. */ + newState = currentState; } - else + + if( newState != currentState ) { - /* Send the Shadow update. Its result is not checked, as the Shadow updated - * callback will report if the Shadow was successfully updated. Because the - * Shadow is constantly updated in this demo, the "Keep Subscriptions" flag - * is passed to this function. */ - updateStatus = AwsIotShadow_Update( pCallbackParam->mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - NULL, - NULL ); - - if( updateStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + /* Toggle state. */ + IotLogInfo( "%.*s changing state from %d to %d.", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName, + currentState, + newState ); + + currentState = newState; + + /* Set the common members to report the new state. */ + updateDocument.pThingName = pCallbackParam->pThingName; + updateDocument.thingNameLength = pCallbackParam->thingNameLength; + updateDocument.u.update.pUpdateDocument = pUpdateDocument; + updateDocument.u.update.updateDocumentLength = EXPECTED_REPORTED_JSON_SIZE; + + /* Generate a Shadow document for the reported state. To keep the client + * token within 6 characters, it is modded by 1000000. */ + updateDocumentLength = snprintf( pUpdateDocument, + EXPECTED_REPORTED_JSON_SIZE + 1, + SHADOW_REPORTED_JSON, + ( int ) currentState, + ( long unsigned ) ( IotClock_GetTimeMs() % 1000000 ) ); + + if( ( size_t ) updateDocumentLength != EXPECTED_REPORTED_JSON_SIZE ) { - IotLogWarn( "%.*s failed to report new state, error %s.", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName, - AwsIotShadow_strerror( updateStatus ) ); + IotLogError( "Failed to generate reported state document for Shadow update." ); } else { - IotLogInfo( "%.*s sent new state report.", - pCallbackParam->thingNameLength, - pCallbackParam->pThingName ); + /* Send the Shadow update. Its result is not checked, as the Shadow updated + * callback will report if the Shadow was successfully updated. Because the + * Shadow is constantly updated in this demo, the "Keep Subscriptions" flag + * is passed to this function. */ + updateStatus = AwsIotShadow_Update( pCallbackParam->mqttConnection, + &updateDocument, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, + NULL, + NULL ); + + if( updateStatus != AWS_IOT_SHADOW_STATUS_PENDING ) + { + IotLogWarn( "%.*s failed to report new state, error %s.", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName, + AwsIotShadow_strerror( updateStatus ) ); + } + else + { + IotLogInfo( "%.*s sent new state report: %.*s", + pCallbackParam->thingNameLength, + pCallbackParam->pThingName, + EXPECTED_REPORTED_JSON_SIZE, + pUpdateDocument ); + } } + + /* Post to the delta semaphore to unblock the thread sending Shadow updates. */ + IotSemaphore_Post( pDeltaSemaphore ); } } else { IotLogWarn( "Failed to parse powerOn state from delta document." ); } - - /* Post to the delta semaphore to unblock the thread sending Shadow updates. */ - IotSemaphore_Post( pDeltaSemaphore ); } /*-----------------------------------------------------------*/ diff --git a/tests/iot_config.h b/tests/iot_config.h index 93abdae4d9..a027cb2d99 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -120,6 +120,7 @@ /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ #if IOT_STATIC_MEMORY_ONLY == 1 + #define IOT_MESSAGE_BUFFERS ( 16 ) #define IOT_MQTT_CONNECTIONS ( 2 ) #define IOT_MQTT_MAX_IN_PROGRESS_OPERATIONS ( 10 ) #define IOT_MQTT_SUBSCRIPTIONS ( 80 ) From fcb14ee63f8c493332e8e011ee8857a5b82fa57f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 12 Sep 2019 12:01:44 -0700 Subject: [PATCH 251/844] Use correct demo config header when testing (#556) --- demos/app/CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt index cd57f5942e..75034bd940 100644 --- a/demos/app/CMakeLists.txt +++ b/demos/app/CMakeLists.txt @@ -8,6 +8,9 @@ if( ${IOT_BUILD_TESTS} ) list( APPEND DEMO_COMMON_SOURCES ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) set_property( SOURCE iot_demo.c PROPERTY COMPILE_DEFINITIONS main=DemoMain ) set_property( SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c PROPERTY COMPILE_DEFINITIONS IOT_TEST_DEMO=1 ) + set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +else() + set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) endif() # Common demo header files. @@ -20,7 +23,7 @@ add_executable( iot_demo_mqtt ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c ${DEMO_COMMON_SOURCES} ${DEMO_COMMON_HEADERS} - ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) + ${CONFIG_HEADER} ) # Select the demo function for the MQTT demo. target_compile_definitions( iot_demo_mqtt @@ -38,14 +41,14 @@ set_property( TARGET iot_demo_mqtt PROPERTY FOLDER "demos" ) source_group( include FILES ${DEMO_COMMON_HEADERS} ) source_group( "" FILES ${DEMO_COMMON_SOURCES} ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c - ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) + ${CONFIG_HEADER} ) # Shadow demo source files. add_executable( aws_iot_demo_shadow ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c ${DEMO_COMMON_SOURCES} ${DEMO_COMMON_HEADERS} - ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) + ${CONFIG_HEADER} ) # Select the demo function for the Shadow demo. target_compile_definitions( aws_iot_demo_shadow @@ -63,4 +66,4 @@ set_property( TARGET aws_iot_demo_shadow PROPERTY FOLDER "demos" ) source_group( include FILES ${DEMO_COMMON_HEADERS} ) source_group( "" FILES ${DEMO_COMMON_SOURCES} ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c - ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) + ${CONFIG_HEADER} ) From 4e687361fd89094a4d66f176dff1e1c142d98c61 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 13 Sep 2019 11:16:14 -0700 Subject: [PATCH 252/844] Rename functions in MQTT (#557) --- demos/source/iot_demo_mqtt.c | 44 ++-- doc/lib/jobs.txt | 2 +- doc/lib/shadow.txt | 2 +- lib/include/aws_iot_jobs.h | 2 +- lib/include/aws_iot_shadow.h | 2 +- lib/include/iot_mqtt.h | 212 ++++++++++-------- lib/include/private/aws_iot.h | 16 +- lib/include/private/iot_mqtt_internal.h | 9 +- lib/include/types/aws_iot_jobs_types.h | 4 +- lib/include/types/aws_iot_shadow_types.h | 5 +- lib/include/types/iot_mqtt_types.h | 105 ++++----- .../common/aws_iot/aws_iot_subscription.c | 12 +- lib/source/defender/aws_iot_defender_mqtt.c | 18 +- lib/source/jobs/aws_iot_jobs_api.c | 20 +- lib/source/jobs/aws_iot_jobs_operation.c | 8 +- lib/source/jobs/aws_iot_jobs_subscription.c | 6 +- lib/source/mqtt/iot_mqtt_api.c | 102 ++++----- lib/source/shadow/aws_iot_shadow_api.c | 20 +- lib/source/shadow/aws_iot_shadow_operation.c | 8 +- .../shadow/aws_iot_shadow_subscription.c | 6 +- tests/mqtt/system/iot_tests_mqtt_system.c | 150 ++++++------- tests/mqtt/unit/iot_tests_mqtt_api.c | 146 ++++++------ 22 files changed, 456 insertions(+), 443 deletions(-) diff --git a/demos/source/iot_demo_mqtt.c b/demos/source/iot_demo_mqtt.c index f79a06e01e..17a34e2674 100644 --- a/demos/source/iot_demo_mqtt.c +++ b/demos/source/iot_demo_mqtt.c @@ -311,19 +311,19 @@ static void _mqttSubscriptionCallback( void * param1, * the MQTT library will still guarantee at-least-once delivery (subject * to the retransmission strategy) because the acknowledgement message is * sent at QoS 1. */ - if( IotMqtt_Publish( pPublish->mqttConnection, - &acknowledgementInfo, - 0, - NULL, - NULL ) == IOT_MQTT_STATUS_PENDING ) + if( IotMqtt_PublishAsync( pPublish->mqttConnection, + &acknowledgementInfo, + 0, + NULL, + NULL ) == IOT_MQTT_STATUS_PENDING ) { - IotLogInfo( "Acknowledgment message for PUBLISH %.*s will be sent.", + IotLogInfo( "Acknowledgement message for PUBLISH %.*s will be sent.", ( int ) messageNumberLength, pPayload + messageNumberIndex ); } else { - IotLogWarn( "Acknowledgment message for PUBLISH %.*s will NOT be sent.", + IotLogWarn( "Acknowledgement message for PUBLISH %.*s will NOT be sent.", ( int ) messageNumberLength, pPayload + messageNumberIndex ); } @@ -520,11 +520,11 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, /* Modify subscriptions by either subscribing or unsubscribing. */ if( operation == IOT_MQTT_SUBSCRIBE ) { - subscriptionStatus = IotMqtt_TimedSubscribe( mqttConnection, - pSubscriptions, - TOPIC_FILTER_COUNT, - 0, - MQTT_TIMEOUT_MS ); + subscriptionStatus = IotMqtt_SubscribeSync( mqttConnection, + pSubscriptions, + TOPIC_FILTER_COUNT, + 0, + MQTT_TIMEOUT_MS ); /* Check the status of SUBSCRIBE. */ switch( subscriptionStatus ) @@ -566,11 +566,11 @@ static int _modifySubscriptions( IotMqttConnection_t mqttConnection, } else if( operation == IOT_MQTT_UNSUBSCRIBE ) { - subscriptionStatus = IotMqtt_TimedUnsubscribe( mqttConnection, - pSubscriptions, - TOPIC_FILTER_COUNT, - 0, - MQTT_TIMEOUT_MS ); + subscriptionStatus = IotMqtt_UnsubscribeSync( mqttConnection, + pSubscriptions, + TOPIC_FILTER_COUNT, + 0, + MQTT_TIMEOUT_MS ); /* Check the status of UNSUBSCRIBE. */ if( subscriptionStatus != IOT_MQTT_SUCCESS ) @@ -668,11 +668,11 @@ static int _publishAllMessages( IotMqttConnection_t mqttConnection, /* PUBLISH a message. This is an asynchronous function that notifies of * completion through a callback. */ - publishStatus = IotMqtt_Publish( mqttConnection, - &publishInfo, - 0, - &publishComplete, - NULL ); + publishStatus = IotMqtt_PublishAsync( mqttConnection, + &publishInfo, + 0, + &publishComplete, + NULL ); if( publishStatus != IOT_MQTT_STATUS_PENDING ) { diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index 0eb29a02ec..dfa2fe18b5 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -139,7 +139,7 @@ Asserts are useful for debugging, but should be disabled in production code. If @section AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS @brief Set the default timeout (in milliseconds) for [MQTT library](@ref mqtt_functions) called by the Jobs library. -If the `mqttTimeout` argument of @ref jobs_function_init is `0`, the Jobs library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_timedsubscribe, @ref mqtt_function_timedunsubscribe, and @ref mqtt_function_timedpublish to limit amount of time an MQTT function may block. +If the `mqttTimeout` argument of @ref jobs_function_init is `0`, the Jobs library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync, and @ref mqtt_function_publishsync to limit amount of time an MQTT function may block. @configpossible Any positive integer.
@configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 9d28ce3abd..9134d64c3d 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -128,7 +128,7 @@ Asserts are useful for debugging, but should be disabled in production code. If @section AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS @brief Set the default timeout (in milliseconds) for [MQTT library](@ref mqtt_functions) called by the Shadow library. -If the `mqttTimeout` argument of @ref shadow_function_init is `0`, the Shadow library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_timedsubscribe, @ref mqtt_function_timedunsubscribe, and @ref mqtt_function_timedpublish to limit amount of time an MQTT function may block. +If the `mqttTimeout` argument of @ref shadow_function_init is `0`, the Shadow library uses this setting for MQTT timeouts. This timeout is passed to functions such as @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync, and @ref mqtt_function_publishsync to limit amount of time an MQTT function may block. @configpossible Any positive integer.
@configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 528d60794d..c3dd027d07 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -820,7 +820,7 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConn * * Passing the flag @ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS to @ref jobs_function_getpending, * @ref jobs_function_startnext, @ref jobs_function_describe, @ref jobs_function_update, - * or their Timed variants causes the Jobs operation topic subscriptions to be + * or their blocking versions causes the Jobs operation topic subscriptions to be * maintained for future calls to the same function. If a persistent subscription for a * Jobs topic are no longer needed, this function may be used to remove it. * diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index b320500bff..ef7388d523 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -825,7 +825,7 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon * @brief Remove persistent Thing Shadow operation topic subscriptions. * * Passing the flag @ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS to @ref shadow_function_delete, - * @ref shadow_function_get, @ref shadow_function_update, or their Timed variants + * @ref shadow_function_get, @ref shadow_function_update, or their blocking versions. * causes the Shadow operation topic subscriptions to be maintained for future calls to the * same function. If a persistent subscription for a Shadow topic are no longer needed, * this function may be used to remove it. diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index b6775e2454..9fd1383291 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -42,12 +42,12 @@ * - @functionname{mqtt_function_receivecallback} * - @functionname{mqtt_function_connect} * - @functionname{mqtt_function_disconnect} - * - @functionname{mqtt_function_subscribe} - * - @functionname{mqtt_function_timedsubscribe} - * - @functionname{mqtt_function_unsubscribe} - * - @functionname{mqtt_function_timedunsubscribe} - * - @functionname{mqtt_function_publish} - * - @functionname{mqtt_function_timedpublish} + * - @functionname{mqtt_function_subscribeasync} + * - @functionname{mqtt_function_subscribesync} + * - @functionname{mqtt_function_unsubscribeasync} + * - @functionname{mqtt_function_unsubscribesync} + * - @functionname{mqtt_function_publishasync} + * - @functionname{mqtt_function_publishsync} * - @functionname{mqtt_function_wait} * - @functionname{mqtt_function_strerror} * - @functionname{mqtt_function_operationtype} @@ -60,12 +60,12 @@ * @functionpage{IotMqtt_ReceiveCallback,mqtt,receivecallback} * @functionpage{IotMqtt_Connect,mqtt,connect} * @functionpage{IotMqtt_Disconnect,mqtt,disconnect} - * @functionpage{IotMqtt_Subscribe,mqtt,subscribe} - * @functionpage{IotMqtt_TimedSubscribe,mqtt,timedsubscribe} - * @functionpage{IotMqtt_Unsubscribe,mqtt,unsubscribe} - * @functionpage{IotMqtt_TimedUnsubscribe,mqtt,timedunsubscribe} - * @functionpage{IotMqtt_Publish,mqtt,publish} - * @functionpage{IotMqtt_TimedPublish,mqtt,timedpublish} + * @functionpage{IotMqtt_SubscribeAsync,mqtt,subscribeasync} + * @functionpage{IotMqtt_SubscribeSync,mqtt,subscribesync} + * @functionpage{IotMqtt_UnsubscribeAsync,mqtt,unsubscribeasync} + * @functionpage{IotMqtt_UnsubscribeSync,mqtt,unsubscribesync} + * @functionpage{IotMqtt_PublishAsync,mqtt,publishasync} + * @functionpage{IotMqtt_PublishSync,mqtt,publishsync} * @functionpage{IotMqtt_Wait,mqtt,wait} * @functionpage{IotMqtt_strerror,mqtt,strerror} * @functionpage{IotMqtt_OperationType,mqtt,operationtype} @@ -167,8 +167,8 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * (@ref IotMqttPublishInfo_t.retryLimit) members of #IotMqttPublishInfo_t * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL. * - * Unlike @ref mqtt_function_publish, @ref mqtt_function_subscribe, and - * @ref mqtt_function_unsubscribe, this function is always blocking. Additionally, + * Unlike @ref mqtt_function_publishasync, @ref mqtt_function_subscribeasync, and + * @ref mqtt_function_unsubscribeasync, this function is always blocking. Additionally, * because the MQTT connection acknowledgement packet (CONNACK packet) does not * contain any information on which CONNECT packet it acknowledges, only one * CONNECT operation may be in progress at any time. This means that parallel @@ -341,8 +341,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. - * @see @ref mqtt_function_unsubscribe for the function that removes subscriptions. + * @see @ref mqtt_function_subscribesync for a blocking variant of this function. + * @see @ref mqtt_function_unsubscribeasync for the function that removes subscriptions. * * Example * @code{c} @@ -367,12 +367,12 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * pSubscriptions[ i ].callback.function = subscriptionCallback; * } * - * IotMqttError_t result = IotMqtt_Subscribe( mqttConnection, - * pSubscriptions, - * NUMBER_OF_SUBSCRIPTIONS, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &lastOperation ); + * IotMqttError_t result = IotMqtt_SubscribeAsync( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); * * // Subscribe returns IOT_MQTT_STATUS_PENDING when successful. Wait up to * // 5 seconds for the operation to complete. @@ -387,12 +387,12 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * // Wait for messages on the subscription topic filters... * * // Unsubscribe once the subscriptions are no longer needed. - * result = IotMqtt_Unsubscribe( mqttConnection, - * pSubscriptions, - * NUMBER_OF_SUBSCRIPTIONS, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &lastOperation ); + * result = IotMqtt_UnsubscribeAsync( mqttConnection, + * pSubscriptions, + * NUMBER_OF_SUBSCRIPTIONS, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &lastOperation ); * * // UNSUBSCRIBE returns IOT_MQTT_STATUS_PENDING when successful. * // Wait up to 5 seconds for the operation to complete. @@ -417,22 +417,22 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * } * @endcode */ -/* @[declare_mqtt_subscribe] */ -IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pSubscribeOperation ); -/* @[declare_mqtt_subscribe] */ +/* @[declare_mqtt_subscribeasync] */ +IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pSubscribeOperation ); +/* @[declare_mqtt_subscribeasync] */ /** * @brief Subscribes to the given array of topic filters with a timeout. * * This function transmits an MQTT SUBSCRIBE packet to the server, then waits for * a server response to the packet. Internally, this function is a call to @ref - * mqtt_function_subscribe followed by @ref mqtt_function_wait. See @ref - * mqtt_function_subscribe for more information about the MQTT SUBSCRIBE operation. + * mqtt_function_subscribeasync followed by @ref mqtt_function_wait. See @ref + * mqtt_function_subscribeasync for more information about the MQTT SUBSCRIBE operation. * * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid * for subscription QoS. @@ -457,13 +457,13 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_TIMEOUT * - #IOT_MQTT_SERVER_REFUSED */ -/* @[declare_mqtt_timedsubscribe] */ -IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_mqtt_timedsubscribe] */ +/* @[declare_mqtt_subscribesync] */ +IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_mqtt_subscribesync] */ /** * @brief Unsubscribes from the given array of topic filters and receive an asynchronous @@ -499,25 +499,25 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - * @see @ref mqtt_function_timedsubscribe for a blocking variant of this function. - * @see @ref mqtt_function_subscribe for the function that adds subscriptions. + * @see @ref mqtt_function_unsubscribesync for a blocking variant of this function. + * @see @ref mqtt_function_subscribeasync for the function that adds subscriptions. */ -/* @[declare_mqtt_unsubscribe] */ -IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pUnsubscribeOperation ); -/* @[declare_mqtt_unsubscribe] */ +/* @[declare_mqtt_unsubscribeasync] */ +IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pUnsubscribeOperation ); +/* @[declare_mqtt_unsubscribeasync] */ /** * @brief Unsubscribes from a given array of topic filters with a timeout. * * This function transmits an MQTT UNSUBSCRIBE packet to the server, then waits * for a server response to the packet. Internally, this function is a call to - * @ref mqtt_function_unsubscribe followed by @ref mqtt_function_wait. See @ref - * mqtt_function_unsubscribe for more information about the MQTT UNSUBSCRIBE + * @ref mqtt_function_unsubscribeasync followed by @ref mqtt_function_wait. See @ref + * mqtt_function_unsubscribeasync for more information about the MQTT UNSUBSCRIBE * operation. * * @param[in] mqttConnection The MQTT connection used for the subscription. @@ -538,13 +538,13 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_SCHEDULING_ERROR * - #IOT_MQTT_BAD_RESPONSE */ -/* @[declare_mqtt_timedunsubscribe] */ -IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_mqtt_timedunsubscribe] */ +/* @[declare_mqtt_unsubscribesync] */ +IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_mqtt_unsubscribesync] */ /** * @brief Publishes a message to the given topic name and receive an asynchronous @@ -589,7 +589,7 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * @note The parameters `pCallbackInfo` and `pPublishOperation` should only be used for QoS * 1 publishes. For QoS 0, they should both be `NULL`. * - * @see @ref mqtt_function_timedpublish for a blocking variant of this function. + * @see @ref mqtt_function_publishsync for a blocking variant of this function. * * Example * @code{c} @@ -607,11 +607,11 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * publishInfo.payloadLength = 8; * * // QoS 0 publish should return IOT_MQTT_SUCCESS upon success. - * IotMqttError_t qos0Result = IotMqtt_Publish( mqttConnection, - * &publishInfo, - * 0, - * NULL, - * NULL ); + * IotMqttError_t qos0Result = IotMqtt_PublishAsync( mqttConnection, + * &publishInfo, + * 0, + * NULL, + * NULL ); * * // QoS 1 with retry example (using same topic name and payload as QoS 0 example): * IotMqttOperation_t qos1Operation = IOT_MQTT_OPERATION_INITIALIZER; @@ -620,11 +620,11 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * publishInfo.retryLimit = 5; // Retry up to 5 times. * * // QoS 1 publish should return IOT_MQTT_STATUS_PENDING upon success. - * IotMqttError_t qos1Result = IotMqtt_Publish( mqttConnection, - * &publishInfo, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &qos1Operation ); + * IotMqttError_t qos1Result = IotMqtt_PublishAsync( mqttConnection, + * &publishInfo, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &qos1Operation ); * * // Wait up to 5 seconds for the publish to complete. * if( qos1Result == IOT_MQTT_STATUS_PENDING ) @@ -633,21 +633,21 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, * } * @endcode */ -/* @[declare_mqtt_publish] */ -IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pPublishOperation ); -/* @[declare_mqtt_publish] */ +/* @[declare_mqtt_publishasync] */ +IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pPublishOperation ); +/* @[declare_mqtt_publishasync] */ /** * @brief Publish a message to the given topic name with a timeout. * * This function transmits an MQTT PUBLISH packet to the server, then waits for * a server response to the packet. Internally, this function is a call to @ref - * mqtt_function_publish followed by @ref mqtt_function_wait. See @ref - * mqtt_function_publish for more information about the MQTT PUBLISH operation. + * mqtt_function_publishasync followed by @ref mqtt_function_wait. See @ref + * mqtt_function_publishasync for more information about the MQTT PUBLISH operation. * * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid * for message QoS. @@ -671,19 +671,19 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs) * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). */ -/* @[declare_mqtt_timedpublish] */ -IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_mqtt_timedpublish] */ +/* @[declare_mqtt_publishsync] */ +IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_mqtt_publishsync] */ /** * @brief Waits for an operation to complete. * - * This function blocks to wait for a [subscribe](@ref mqtt_function_subscribe), - * [unsubscribe](@ref mqtt_function_unsubscribe), or [publish] - * (@ref mqtt_function_publish) to complete. These operations are by default + * This function blocks to wait for a [subscribe](@ref mqtt_function_subscribeasync), + * [unsubscribe](@ref mqtt_function_unsubscribeasync), or [publish] + * (@ref mqtt_function_publishasync) to complete. These operations are by default * asynchronous; the function calls queue an operation for processing, and a * callback is invoked once the operation is complete. * @@ -709,11 +709,11 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, * uint32_t timeoutMs = 5000; // 5 seconds * * // MQTT operation to wait for. - * IotMqttError_t result = IotMqtt_Publish( mqttConnection, - * &publishInfo, - * IOT_MQTT_FLAG_WAITABLE, - * NULL, - * &publishOperation ); + * IotMqttError_t result = IotMqtt_PublishAsync( mqttConnection, + * &publishInfo, + * IOT_MQTT_FLAG_WAITABLE, + * NULL, + * &publishOperation ); * * // Publish should have returned IOT_MQTT_STATUS_PENDING. The call to wait * // returns once the result of the publish is available or the timeout expires. @@ -794,7 +794,7 @@ const char * IotMqtt_OperationType( IotMqttOperationType_t operation ); * or UNSUBSCRIBE operations. * * One suitable use of this function is to check which subscriptions were rejected - * if @ref mqtt_function_subscribe returns #IOT_MQTT_SERVER_REFUSED; that return + * if @ref mqtt_function_subscribeasync returns #IOT_MQTT_SERVER_REFUSED; that return * code only means that at least one subscription was rejected. * * @param[in] mqttConnection The MQTT connection to check. @@ -816,4 +816,18 @@ bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection, IotMqttSubscription_t * const pCurrentSubscription ); /* @[declare_mqtt_issubscribed] */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Backwards compatibility macros for previous function names. + */ +#define IotMqtt_Subscribe IotMqtt_SubscribeAsync +#define IotMqtt_TimedSubscribe IotMqtt_SubscribeSync +#define IotMqtt_Unsubscribe IotMqtt_UnsubscribeAsync +#define IotMqtt_TimedUnsubscribe IotMqtt_UnsubscribeSync +#define IotMqtt_Publish IotMqtt_PublishAsync +#define IotMqtt_TimedPublish IotMqtt_PublishSync +/** @endcond */ + #endif /* ifndef IOT_MQTT_H_ */ diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 53b1d5aa82..0e01498c4b 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -102,10 +102,10 @@ #define AWS_IOT_PERSISTENT_SUBSCRIPTION ( -1 ) /** - * @brief Function pointer representing an MQTT timed operation. + * @brief Function pointer representing an MQTT blocking operation. * - * Currently, this is used to represent @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. + * Currently, this is used to represent @ref mqtt_function_subscribesync or + * @ref mqtt_function_unsubscribesync. */ typedef IotMqttError_t ( * AwsIotMqttFunction_t )( IotMqttConnection_t, const IotMqttSubscription_t *, @@ -252,7 +252,7 @@ AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, * @param[out] pOperationTopicLength Set to the length of the generated topic. * * @warning This function does not check the length of `pTopicBuffer`! Any provided - * buffer must be long enough to accomodate the Thing Name, operation name, and + * buffer must be long enough to accommodate the Thing Name, operation name, and * any other suffixes. * * @return `true` if the topic was successfully generated; `false` otherwise. @@ -265,13 +265,13 @@ bool AwsIot_GenerateOperationTopic( const AwsIotTopicInfo_t * pTopicInfo, /** * @brief Add or remove subscriptions for AWS IoT operations. * - * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. + * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or + * @ref mqtt_function_unsubscribesync. * @param[in] pSubscriptionInfo Information needed to process an MQTT * operation. * - * @return See the return values of @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. + * @return See the return values of @ref mqtt_function_subscribesync or + * @ref mqtt_function_unsubscribesync. */ IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, const AwsIotSubscriptionInfo_t * pSubscriptionInfo ); diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 097d8f84f7..cb79baca3a 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -365,13 +365,12 @@ typedef struct _mqttSubscription int32_t references; /**< @brief How many subscription callbacks are using this subscription. */ /** - * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for + * @brief Tracks whether an unsubscribe function has been called for * this subscription. * - * If there are active subscription callbacks, @ref mqtt_function_unsubscribe - * cannot remove this subscription. Instead, it will set this flag, which - * schedules the removal of this subscription once all subscription callbacks - * terminate. + * If there are active subscription callbacks, this subscription cannot be removed. + * Instead, this flag will be set, which schedules the removal of this subscription + * once all subscription callbacks terminate. */ bool unsubscribed; diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index ff3e8e1dd2..390a250697 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -804,7 +804,7 @@ typedef struct AwsIotJobsUpdateInfo * The following flags are valid for the Jobs operation functions: * @ref jobs_function_getpending, @ref jobs_function_startnext, * @ref jobs_function_describe, @ref jobs_function_update, - * and their Timed variants. + * and their blocking versions. * - #AWS_IOT_JOBS_FLAG_WAITABLE
* @copybrief AWS_IOT_JOBS_FLAG_WAITABLE * - #AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS
@@ -867,7 +867,7 @@ typedef struct AwsIotJobsUpdateInfo * * This flag is only valid if passed to the functions @ref jobs_function_getpending, * @ref jobs_function_startnext, @ref jobs_function_describe, or @ref jobs_function_update, - * and their Timed variants. + * and their blocking versions. * * The Jobs service reports results of Jobs operations by publishing * messages to MQTT topics. By default, the Job operation functions subscribe to the diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 911846d7a1..d6339a253e 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -509,7 +509,7 @@ typedef struct AwsIotShadowDocumentInfo * * The following flags are valid for the Shadow operation functions: * @ref shadow_function_delete, @ref shadow_function_get, @ref shadow_function_update, - * and their Timed variants. + * and their blocking versions. * - #AWS_IOT_SHADOW_FLAG_WAITABLE
* @copybrief AWS_IOT_SHADOW_FLAG_WAITABLE * - #AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS
@@ -554,8 +554,7 @@ typedef struct AwsIotShadowDocumentInfo * this function returns. * * This flag is only valid if passed to the functions @ref shadow_function_delete, - * @ref shadow_function_get, @ref shadow_function_update, or their Timed - * variants. + * @ref shadow_function_get, @ref shadow_function_update, or their blocking versions. * * The Shadow service reports results of Shadow operations by publishing * messages to MQTT topics. By default, the functions @ref shadow_function_delete, diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index fdd9b6f2e9..7d9b46a91b 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -69,13 +69,13 @@ typedef struct _mqttConnection * IotMqttConnection_t; * @ingroup mqtt_datatypes_handles * @brief Opaque handle that references an in-progress MQTT operation. * - * Set as an output parameter of @ref mqtt_function_publish, @ref mqtt_function_subscribe, - * and @ref mqtt_function_unsubscribe. These functions queue an MQTT operation; the result + * Set as an output parameter of @ref mqtt_function_publishasync, @ref mqtt_function_subscribeasync, + * and @ref mqtt_function_unsubscribeasync. These functions queue an MQTT operation; the result * of the operation is unknown until a response from the MQTT server is received. Therefore, * this handle serves as a reference to MQTT operations awaiting MQTT server response. * - * This reference will be valid from the successful return of @ref mqtt_function_publish, - * @ref mqtt_function_subscribe, or @ref mqtt_function_unsubscribe. The reference becomes + * This reference will be valid from the successful return of @ref mqtt_function_publishasync, + * @ref mqtt_function_subscribeasync, or @ref mqtt_function_unsubscribeasync. The reference becomes * invalid once the [completion callback](@ref IotMqttCallbackInfo_t) is invoked, or * @ref mqtt_function_wait returns. * @@ -107,11 +107,11 @@ typedef enum IotMqttError * * Functions that may return this value: * - @ref mqtt_function_connect - * - @ref mqtt_function_publish with QoS 0 parameter + * - @ref mqtt_function_publishasync with QoS 0 parameter * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishsync * * Will also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result when successful. @@ -122,9 +122,9 @@ typedef enum IotMqttError * @brief MQTT operation queued, awaiting result. * * Functions that may return this value: - * - @ref mqtt_function_subscribe - * - @ref mqtt_function_unsubscribe - * - @ref mqtt_function_publish with QoS 1 parameter + * - @ref mqtt_function_subscribeasync + * - @ref mqtt_function_unsubscribeasync + * - @ref mqtt_function_publishasync with QoS 1 parameter */ IOT_MQTT_STATUS_PENDING, @@ -141,9 +141,9 @@ typedef enum IotMqttError * * Functions that may return this value: * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribeasync and @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync * - @ref mqtt_function_wait */ IOT_MQTT_BAD_PARAMETER, @@ -153,9 +153,9 @@ typedef enum IotMqttError * * Functions that may return this value: * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribeasync and @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync */ IOT_MQTT_NO_MEMORY, @@ -167,9 +167,9 @@ typedef enum IotMqttError * Functions that may return this value: * - @ref mqtt_function_connect * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishsync * * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result. @@ -181,9 +181,9 @@ typedef enum IotMqttError * * Functions that may return this value: * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribe and @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_unsubscribe and @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_publish and @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribeasync and @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync */ IOT_MQTT_SCHEDULING_ERROR, @@ -193,9 +193,9 @@ typedef enum IotMqttError * Functions that may return this value: * - @ref mqtt_function_connect * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishsync * * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result. @@ -210,9 +210,9 @@ typedef enum IotMqttError * Functions that may return this value: * - @ref mqtt_function_connect * - @ref mqtt_function_wait - * - @ref mqtt_function_timedsubscribe - * - @ref mqtt_function_timedunsubscribe - * - @ref mqtt_function_timedpublish + * - @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishsync */ IOT_MQTT_TIMEOUT, @@ -223,13 +223,13 @@ typedef enum IotMqttError * - @ref mqtt_function_connect * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter * is associated with a SUBSCRIBE operation. - * - @ref mqtt_function_timedsubscribe + * - @ref mqtt_function_subscribesync * * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result for a SUBSCRIBE. * * @note If this value is returned and multiple subscriptions were passed to - * @ref mqtt_function_subscribe (or @ref mqtt_function_timedsubscribe), it's + * @ref mqtt_function_subscribeasync (or @ref mqtt_function_subscribesync), it's * still possible that some of the subscriptions succeeded. This value only * signifies that AT LEAST ONE subscription was rejected. The function @ref * mqtt_function_issubscribed can be used to determine which subscriptions @@ -244,7 +244,7 @@ typedef enum IotMqttError * Functions that may return this value: * - @ref mqtt_function_wait, but only when its #IotMqttOperation_t parameter * is associated with a QoS 1 PUBLISH operation - * - @ref mqtt_function_timedpublish + * - @ref mqtt_function_publishsync * * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. @@ -319,9 +319,9 @@ typedef enum IotMqttDisconnectReason * @ingroup mqtt_datatypes_paramstructs * @brief Information on a PUBLISH message. * - * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publish + * @paramfor @ref mqtt_function_connect, @ref mqtt_function_publishasync * - * Passed to @ref mqtt_function_publish as the message to publish and @ref + * Passed to @ref mqtt_function_publishasync as the message to publish and @ref * mqtt_function_connect as the Last Will and Testament (LWT) message. * * @initializer{IotMqttPublishInfo_t,IOT_MQTT_PUBLISH_INFO_INITIALIZER} @@ -466,8 +466,8 @@ typedef struct IotMqttCallbackParam * @ingroup mqtt_datatypes_paramstructs * @brief Information on a user-provided MQTT callback function. * - * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, - * and @ref mqtt_function_publish. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. + * @paramfor @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, + * and @ref mqtt_function_publishasync. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. * * Provides a function to be invoked when an operation completes or when a * server-to-client PUBLISH is received. @@ -475,7 +475,7 @@ typedef struct IotMqttCallbackParam * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} * * Below is an example for receiving an asynchronous notification on operation - * completion. See @ref mqtt_function_subscribe for an example of using this struct + * completion. See @ref mqtt_function_subscribeasync for an example of using this struct * with for incoming PUBLISH messages. * * @code{c} @@ -487,11 +487,11 @@ typedef struct IotMqttCallbackParam * callbackInfo.function = operationComplete; * * // Operation to wait for. - * IotMqttError_t result = IotMqtt_Publish( &mqttConnection, - * &publishInfo, - * 0, - * &callbackInfo, - * &reference ); + * IotMqttError_t result = IotMqtt_PublishAsync( &mqttConnection, + * &publishInfo, + * 0, + * &callbackInfo, + * &reference ); * * // Publish should have returned IOT_MQTT_STATUS_PENDING. Once a response * // is received, operationComplete is executed with the actual status passed @@ -519,11 +519,12 @@ typedef struct IotMqttCallbackInfo * @ingroup mqtt_datatypes_paramstructs * @brief Information on an MQTT subscription. * - * @paramfor @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe + * @paramfor @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, + * @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync * - * An array of these is passed to @ref mqtt_function_subscribe and @ref - * mqtt_function_unsubscribe. However, #IotMqttSubscription_t.callback and - * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribe. + * An array of these is passed to @ref mqtt_function_subscribeasync and @ref + * mqtt_function_unsubscribeasync. However, #IotMqttSubscription_t.callback and + * and #IotMqttSubscription_t.qos are ignored by @ref mqtt_function_unsubscribeasync. * * @initializer{IotMqttSubscription_t,IOT_MQTT_SUBSCRIPTION_INITIALIZER} * @@ -536,7 +537,7 @@ typedef struct IotMqttSubscription /** * @brief QoS of messages delivered on subscription. * - * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribe. + * Must be `0` or `1`. Ignored by @ref mqtt_function_unsubscribeasync. */ IotMqttQos_t qos; @@ -546,7 +547,7 @@ typedef struct IotMqttSubscription /** * @brief Callback to invoke when a message is received. * - * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribe. + * See #IotMqttCallbackInfo_t. Ignored by @ref mqtt_function_unsubscribeasync. */ IotMqttCallbackInfo_t callback; } IotMqttSubscription_t; @@ -1028,8 +1029,8 @@ typedef struct IotMqttNetworkInfo * @copybrief IOT_MQTT_FLAG_CLEANUP_ONLY * * Flags should be bitwise-ORed with each other to change the behavior of - * @ref mqtt_function_subscribe, @ref mqtt_function_unsubscribe, - * @ref mqtt_function_publish, or @ref mqtt_function_disconnect. + * @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, + * @ref mqtt_function_publishasync, their blocking versions; or @ref mqtt_function_disconnect. * * @note The values of the flags may change at any time in future versions, but * their names will remain the same. Additionally, flags that may be used together @@ -1058,8 +1059,8 @@ typedef struct IotMqttNetworkInfo /** * @brief Allows the use of @ref mqtt_function_wait for blocking until completion. * - * This flag is always valid for @ref mqtt_function_subscribe and - * @ref mqtt_function_unsubscribe. If passed to @ref mqtt_function_publish, + * This flag is always valid for @ref mqtt_function_subscribeasync and + * @ref mqtt_function_unsubscribeasync. If passed to @ref mqtt_function_publishasync, * the parameter [pPublishInfo->qos](@ref IotMqttPublishInfo_t.qos) must not be `0`. * * An #IotMqttOperation_t MUST be provided if this flag is set. Additionally, an diff --git a/lib/source/common/aws_iot/aws_iot_subscription.c b/lib/source/common/aws_iot/aws_iot_subscription.c index 98151667ce..99c650005d 100644 --- a/lib/source/common/aws_iot/aws_iot_subscription.c +++ b/lib/source/common/aws_iot/aws_iot_subscription.c @@ -42,15 +42,15 @@ /** * @brief Modify subscriptions, either by unsubscribing or subscribing. * - * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or @ref - * mqtt_function_timedunsubscribe. + * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or @ref + * mqtt_function_unsubscribesync. * @param[in] pSubscriptionInfo Information needed to process an MQTT * operation. * @param[in] pTopicFilter The topic filter to modify. * @param[in] topicFilterLength The length of `pTopicFilter`. * - * @return See the return values of @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. + * @return See the return values of @ref mqtt_function_subscribesync or + * @ref mqtt_function_unsubscribesync. */ static IotMqttError_t _modifySubscriptions( AwsIotMqttFunction_t mqttOperation, const AwsIotSubscriptionInfo_t * pSubscriptionInfo, @@ -137,7 +137,7 @@ IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, { /* Remove the subscription to the "accepted" topic if the subscription * to the "rejected" topic failed. */ - if( ( mqttOperation == IotMqtt_TimedSubscribe ) && + if( ( mqttOperation == IotMqtt_SubscribeSync ) && ( acceptedStatus == IOT_MQTT_SUCCESS ) ) { /* Place the topic "accepted" suffix at the end of the topic buffer. */ @@ -148,7 +148,7 @@ IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, topicFilterLength = ( uint16_t ) ( pSubscriptionInfo->topicFilterBaseLength + AWS_IOT_ACCEPTED_SUFFIX_LENGTH ); - ( void ) _modifySubscriptions( IotMqtt_TimedUnsubscribe, + ( void ) _modifySubscriptions( IotMqtt_UnsubscribeSync, pSubscriptionInfo, pSubscriptionInfo->pTopicFilterBase, topicFilterLength ); diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index f473153f6e..356af4a3c4 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -134,11 +134,11 @@ IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t accep subscriptions[ 1 ].topicFilterLength = ( uint16_t ) strlen( _pRejectTopic ); subscriptions[ 1 ].callback = rejectCallback; - return IotMqtt_TimedSubscribe( _mqttConnection, - subscriptions, - 2, - 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ); + return IotMqtt_SubscribeSync( _mqttConnection, + subscriptions, + 2, + 0, + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ); } /*-----------------------------------------------------------*/ @@ -154,10 +154,10 @@ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, publishInfo.pPayload = pData; publishInfo.payloadLength = dataLength; - return IotMqtt_TimedPublish( _mqttConnection, - &publishInfo, - 0, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ); + return IotMqtt_PublishSync( _mqttConnection, + &publishInfo, + 0, + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ); } /*-----------------------------------------------------------*/ diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 9849dc58d3..2ca8b7686d 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -105,8 +105,8 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, * @param[in] mqttConnection The MQTT connection to use. * @param[in] type Type of Jobs callback. * @param[in] pSubscription Jobs subscriptions object for callback. - * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. + * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or + * @ref mqtt_function_unsubscribesync. * * @return #AWS_IOT_JOBS_SUCCESS, #AWS_IOT_JOBS_NO_MEMORY, or * #AWS_IOT_JOBS_MQTT_ERROR. @@ -444,7 +444,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, ( void ) _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, - IotMqtt_TimedUnsubscribe ); + IotMqtt_UnsubscribeSync ); ( void ) memset( &( pSubscription->callbacks[ type ] ), 0x00, sizeof( AwsIotJobsCallbackInfo_t ) ); @@ -467,7 +467,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, status = _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, - IotMqtt_TimedSubscribe ); + IotMqtt_SubscribeSync ); if( status == AWS_IOT_JOBS_SUCCESS ) { @@ -538,8 +538,8 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC }; /* MQTT operation may only be subscribe or unsubscribe. */ - AwsIotJobs_Assert( ( mqttOperation == IotMqtt_TimedSubscribe ) || - ( mqttOperation == IotMqtt_TimedUnsubscribe ) ); + AwsIotJobs_Assert( ( mqttOperation == IotMqtt_SubscribeSync ) || + ( mqttOperation == IotMqtt_UnsubscribeSync ) ); /* Use the subscription's topic buffer if available. */ if( pSubscription->pTopicBuffer != NULL ) @@ -563,7 +563,7 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC } IotLogDebug( "%s subscription for %.*s", - mqttOperation == IotMqtt_TimedSubscribe ? "Adding" : "Removing", + mqttOperation == IotMqtt_SubscribeSync ? "Adding" : "Removing", topicFilterLength, pTopicFilter ); @@ -585,7 +585,7 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + mqttOperation == IotMqtt_SubscribeSync ? "subscribe to" : "unsubscribe from", pSubscription->thingNameLength, pSubscription->pThingName, _pAwsIotJobsCallbackNames[ type ], @@ -603,7 +603,7 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC } IotLogDebug( "Successfully %s %.*s Jobs %s callback.", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + mqttOperation == IotMqtt_SubscribeSync ? "subscribed to" : "unsubscribed from", pSubscription->thingNameLength, pSubscription->pThingName, _pAwsIotJobsCallbackNames[ type ] ); @@ -611,7 +611,7 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC IOT_FUNCTION_CLEANUP_BEGIN(); /* MQTT subscribe should check the subscription topic buffer. */ - if( mqttOperation == IotMqtt_TimedSubscribe ) + if( mqttOperation == IotMqtt_SubscribeSync ) { /* If the current subscription has no topic buffer, assign it the current * topic buffer. */ diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index 77e23c1c0f..c10df627c4 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -811,10 +811,10 @@ AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * IotMutex_Unlock( &( _AwsIotJobsPendingOperationsMutex ) ); /* Publish to the Jobs topic name. */ - publishStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotJobsMqttTimeoutMs ); + publishStatus = IotMqtt_PublishSync( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotJobsMqttTimeoutMs ); if( publishStatus != IOT_MQTT_SUCCESS ) { diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 2a8faaad02..6fb3f18908 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -302,7 +302,7 @@ AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation subscriptionInfo.pTopicFilterBase = pTopicBuffer; subscriptionInfo.topicFilterBaseLength = operationTopicLength; - subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedSubscribe, + subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync, &subscriptionInfo ); /* Convert MQTT return code to Jobs return code. */ @@ -403,7 +403,7 @@ void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, subscriptionInfo.pTopicFilterBase = pTopicBuffer; subscriptionInfo.topicFilterBaseLength = operationTopicLength; - ( void ) AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + ( void ) AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, &subscriptionInfo ); } @@ -522,7 +522,7 @@ AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequ subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer; subscriptionInfo.topicFilterBaseLength = operationTopicLength; - unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, &subscriptionInfo ); /* Convert MQTT return code to Shadow return code. */ diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index dbcf8bd316..f4d839d718 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -125,10 +125,10 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); /** - * @brief The common component of both @ref mqtt_function_subscribe and @ref - * mqtt_function_unsubscribe. + * @brief The common component of both @ref mqtt_function_subscribeasync and @ref + * mqtt_function_unsubscribeasync. * - * See @ref mqtt_function_subscribe or @ref mqtt_function_unsubscribe for a + * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a * description of the parameters and return values. */ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, @@ -1387,12 +1387,12 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pSubscribeOperation ) +IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pSubscribeOperation ) { return _subscriptionCommon( IOT_MQTT_SUBSCRIBE, mqttConnection, @@ -1405,11 +1405,11 @@ IotMqttError_t IotMqtt_Subscribe( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ) +IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t subscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; @@ -1418,12 +1418,12 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, ( void ) flags; /* Call the asynchronous SUBSCRIBE function. */ - status = IotMqtt_Subscribe( mqttConnection, - pSubscriptionList, - subscriptionCount, - IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, - NULL, - &subscribeOperation ); + status = IotMqtt_SubscribeAsync( mqttConnection, + pSubscriptionList, + subscriptionCount, + IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, + NULL, + &subscribeOperation ); /* Wait for the SUBSCRIBE operation to complete. */ if( status == IOT_MQTT_STATUS_PENDING ) @@ -1443,12 +1443,12 @@ IotMqttError_t IotMqtt_TimedSubscribe( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pUnsubscribeOperation ) +IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pUnsubscribeOperation ) { return _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, mqttConnection, @@ -1461,11 +1461,11 @@ IotMqttError_t IotMqtt_Unsubscribe( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - uint32_t timeoutMs ) +IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t unsubscribeOperation = IOT_MQTT_OPERATION_INITIALIZER; @@ -1474,12 +1474,12 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, ( void ) flags; /* Call the asynchronous UNSUBSCRIBE function. */ - status = IotMqtt_Unsubscribe( mqttConnection, - pSubscriptionList, - subscriptionCount, - IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, - NULL, - &unsubscribeOperation ); + status = IotMqtt_UnsubscribeAsync( mqttConnection, + pSubscriptionList, + subscriptionCount, + IOT_MQTT_FLAG_WAITABLE | MQTT_INTERNAL_FLAG_BLOCK_ON_SEND, + NULL, + &unsubscribeOperation ); /* Wait for the UNSUBSCRIBE operation to complete. */ if( status == IOT_MQTT_STATUS_PENDING ) @@ -1499,11 +1499,11 @@ IotMqttError_t IotMqtt_TimedUnsubscribe( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pPublishOperation ) +IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pPublishOperation ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); _mqttOperation_t * pOperation = NULL; @@ -1760,10 +1760,10 @@ IotMqttError_t IotMqtt_Publish( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, - const IotMqttPublishInfo_t * pPublishInfo, - uint32_t flags, - uint32_t timeoutMs ) +IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, + const IotMqttPublishInfo_t * pPublishInfo, + uint32_t flags, + uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_STATUS_PENDING; IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER, @@ -1784,11 +1784,11 @@ IotMqttError_t IotMqtt_TimedPublish( IotMqttConnection_t mqttConnection, } /* Call the asynchronous PUBLISH function. */ - status = IotMqtt_Publish( mqttConnection, - pPublishInfo, - flags, - NULL, - pPublishOperation ); + status = IotMqtt_PublishAsync( mqttConnection, + pPublishInfo, + flags, + NULL, + pPublishOperation ); /* Wait for a queued QoS 1 PUBLISH to complete. */ if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index ffb2038b54..784d588cf0 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -109,8 +109,8 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio * @param[in] mqttConnection The MQTT connection to use. * @param[in] type Type of Shadow callback. * @param[in] pSubscription Shadow subscriptions object for callback. - * @param[in] mqttOperation Either @ref mqtt_function_timedsubscribe or - * @ref mqtt_function_timedunsubscribe. + * @param[in] mqttOperation Either @ref mqtt_function_subscribesync or + * @ref mqtt_function_unsubscribesync. * * @return #AWS_IOT_SHADOW_SUCCESS, #AWS_IOT_SHADOW_NO_MEMORY, or * #AWS_IOT_SHADOW_MQTT_ERROR. @@ -372,7 +372,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio ( void ) _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, - IotMqtt_TimedUnsubscribe ); + IotMqtt_UnsubscribeSync ); ( void ) memset( &( pSubscription->callbacks[ type ] ), 0x00, sizeof( AwsIotShadowCallbackInfo_t ) ); @@ -395,7 +395,7 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio status = _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, - IotMqtt_TimedSubscribe ); + IotMqtt_SubscribeSync ); if( status == AWS_IOT_SHADOW_SUCCESS ) { @@ -465,8 +465,8 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt }; /* MQTT operation may only be subscribe or unsubscribe. */ - AwsIotShadow_Assert( ( mqttOperation == IotMqtt_TimedSubscribe ) || - ( mqttOperation == IotMqtt_TimedUnsubscribe ) ); + AwsIotShadow_Assert( ( mqttOperation == IotMqtt_SubscribeSync ) || + ( mqttOperation == IotMqtt_UnsubscribeSync ) ); /* Use the subscription's topic buffer if available. */ if( pSubscription->pTopicBuffer != NULL ) @@ -493,7 +493,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt pCallbackSuffixLength[ type ] ); IotLogDebug( "%s subscription for %.*s", - mqttOperation == IotMqtt_TimedSubscribe ? "Adding" : "Removing", + mqttOperation == IotMqtt_SubscribeSync ? "Adding" : "Removing", operationTopicLength + pCallbackSuffixLength[ type ], pTopicFilter ); @@ -515,7 +515,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "Failed to %s callback for %.*s %s callback, error %s.", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribe to" : "unsubscribe from", + mqttOperation == IotMqtt_SubscribeSync ? "subscribe to" : "unsubscribe from", pSubscription->thingNameLength, pSubscription->pThingName, _pAwsIotShadowCallbackNames[ type ], @@ -533,7 +533,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt } IotLogDebug( "Successfully %s %.*s Shadow %s callback.", - mqttOperation == IotMqtt_TimedSubscribe ? "subscribed to" : "unsubscribed from", + mqttOperation == IotMqtt_SubscribeSync ? "subscribed to" : "unsubscribed from", pSubscription->thingNameLength, pSubscription->pThingName, _pAwsIotShadowCallbackNames[ type ] ); @@ -541,7 +541,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt IOT_FUNCTION_CLEANUP_BEGIN(); /* MQTT subscribe should check the subscription topic buffer. */ - if( mqttOperation == IotMqtt_TimedSubscribe ) + if( mqttOperation == IotMqtt_SubscribeSync ) { /* If the current subscription has no topic buffer, assign it the current * topic buffer. */ diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index be0e59be21..b36478365c 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -858,10 +858,10 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); /* Publish to the Shadow topic name. */ - publishStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, - &publishInfo, - 0, - _AwsIotShadowMqttTimeoutMs ); + publishStatus = IotMqtt_PublishSync( pOperation->mqttConnection, + &publishInfo, + 0, + _AwsIotShadowMqttTimeoutMs ); /* Check for errors from the MQTT publish. */ if( publishStatus != IOT_MQTT_SUCCESS ) diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 8ba28e28a2..38fb02b88c 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -302,7 +302,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe subscriptionInfo.pTopicFilterBase = pTopicBuffer; subscriptionInfo.topicFilterBaseLength = operationTopicLength; - subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedSubscribe, + subscriptionStatus = AwsIot_ModifySubscriptions( IotMqtt_SubscribeSync, &subscriptionInfo ); /* Convert MQTT return code to Shadow return code. */ @@ -397,7 +397,7 @@ void _AwsIotShadow_DecrementReferences( _shadowOperation_t * pOperation, subscriptionInfo.pTopicFilterBase = pTopicBuffer; subscriptionInfo.topicFilterBaseLength = operationTopicLength; - ( void ) AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + ( void ) AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, &subscriptionInfo ); } @@ -485,7 +485,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio subscriptionInfo.pTopicFilterBase = pSubscription->pTopicBuffer; subscriptionInfo.topicFilterBaseLength = operationTopicLength; - unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_TimedUnsubscribe, + unsubscribeStatus = AwsIot_ModifySubscriptions( IotMqtt_UnsubscribeSync, &subscriptionInfo ); /* Convert MQTT return code to Shadow return code. */ diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 6044b4df5f..4fc0ff6af5 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -404,10 +404,10 @@ static void _reentrantCallback( void * pArgument, publishInfo.retryLimit = 3; publishInfo.retryMs = 5000; - mqttStatus = IotMqtt_TimedPublish( pOperation->mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + mqttStatus = IotMqtt_PublishSync( pOperation->mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); if( mqttStatus == IOT_MQTT_SUCCESS ) { @@ -425,12 +425,12 @@ static void _reentrantCallback( void * pArgument, subscription.pTopicFilter = pTopic; subscription.topicFilterLength = topicLength; - mqttStatus = IotMqtt_Unsubscribe( pOperation->mqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeOperation ); + mqttStatus = IotMqtt_UnsubscribeAsync( pOperation->mqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeOperation ); if( mqttStatus == IOT_MQTT_STATUS_PENDING ) { @@ -524,11 +524,11 @@ static void _subscribePublishWait( IotMqttQos_t qos ) /* Subscribe to the test topic filter using the blocking SUBSCRIBE * function. */ - status = IotMqtt_TimedSubscribe( _mqttConnection, - &subscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_SubscribeSync( _mqttConnection, + &subscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Set the members of the publish info. */ @@ -539,10 +539,10 @@ static void _subscribePublishWait( IotMqttQos_t qos ) publishInfo.payloadLength = _samplePayloadLength; /* Publish the message. */ - status = IotMqtt_TimedPublish( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_PublishSync( _mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); /* Wait for the message to be received. */ if( IotSemaphore_TimedWait( &waitSem, @@ -552,11 +552,11 @@ static void _subscribePublishWait( IotMqttQos_t qos ) } /* Unsubscribe from the test topic filter. */ - status = IotMqtt_TimedUnsubscribe( _mqttConnection, - &subscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_UnsubscribeSync( _mqttConnection, + &subscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); } @@ -773,12 +773,12 @@ TEST( MQTT_System, SubscribePublishAsync ) { /* Subscribe to the test topic filter. */ callbackParam.expectedOperation = IOT_MQTT_SUBSCRIBE; - status = IotMqtt_Subscribe( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = IotMqtt_SubscribeAsync( _mqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), IOT_TEST_MQTT_TIMEOUT_MS ) == false ) @@ -788,11 +788,11 @@ TEST( MQTT_System, SubscribePublishAsync ) /* Publish the message. */ callbackParam.expectedOperation = IOT_MQTT_PUBLISH_TO_SERVER; - status = IotMqtt_Publish( _mqttConnection, - &publishInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = IotMqtt_PublishAsync( _mqttConnection, + &publishInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), IOT_TEST_MQTT_TIMEOUT_MS ) == false ) @@ -809,12 +809,12 @@ TEST( MQTT_System, SubscribePublishAsync ) /* Unsubscribe from the test topic filter. */ callbackParam.expectedOperation = IOT_MQTT_UNSUBSCRIBE; - status = IotMqtt_Unsubscribe( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = IotMqtt_UnsubscribeAsync( _mqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), IOT_TEST_MQTT_TIMEOUT_MS ) == false ) @@ -899,11 +899,11 @@ TEST( MQTT_System, LastWillAndTestament ) willSubscription.callback.function = _publishReceived; willSubscription.callback.pCallbackContext = &waitSem; - status = IotMqtt_TimedSubscribe( lwtListener, - &willSubscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_SubscribeSync( lwtListener, + &willSubscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Create a connection that requests the LWT. */ @@ -981,11 +981,11 @@ TEST( MQTT_System, RestorePreviousSession ) subscription.callback.pCallbackContext = &waitSem; subscription.callback.function = _publishReceived; - status = IotMqtt_TimedSubscribe( _mqttConnection, - &subscription, - 1, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_SubscribeSync( _mqttConnection, + &subscription, + 1, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Disconnect the MQTT connection and clean up network connection. */ @@ -1009,10 +1009,10 @@ TEST( MQTT_System, RestorePreviousSession ) publishInfo.pPayload = _pSamplePayload; publishInfo.payloadLength = _samplePayloadLength; - status = IotMqtt_TimedPublish( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_PublishSync( _mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Wait for the message to be received. */ @@ -1097,11 +1097,11 @@ TEST( MQTT_System, WaitAfterDisconnect ) /* Publish a sequence of messages. */ for( i = 0; i < 3; i++ ) { - status = IotMqtt_Publish( _mqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &( pPublishOperation[ i ] ) ); + status = IotMqtt_PublishAsync( _mqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &( pPublishOperation[ i ] ) ); TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); } } @@ -1173,12 +1173,12 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) callbackInfo.function = _reentrantCallback; callbackInfo.pCallbackContext = pWaitSemaphores; - status = IotMqtt_Subscribe( _mqttConnection, - &subscription, - 1, - 0, - &callbackInfo, - NULL ); + status = IotMqtt_SubscribeAsync( _mqttConnection, + &subscription, + 1, + 0, + &callbackInfo, + NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, status ); /* Wait for the reentrant callback to complete. */ @@ -1252,11 +1252,11 @@ TEST( MQTT_System, IncomingPublishReentrancy ) pSubscription[ 1 ].callback.function = _publishReceived; pSubscription[ 1 ].callback.pCallbackContext = &( pWaitSemaphores[ 0 ] ); - status = IotMqtt_TimedSubscribe( _mqttConnection, - pSubscription, - 2, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_SubscribeSync( _mqttConnection, + pSubscription, + 2, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the test topic. */ @@ -1268,10 +1268,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) publishInfo.retryLimit = 3; publishInfo.retryMs = 5000; - status = IotMqtt_TimedPublish( _mqttConnection, - &publishInfo, - 0, - IOT_TEST_MQTT_TIMEOUT_MS ); + status = IotMqtt_PublishSync( _mqttConnection, + &publishInfo, + 0, + IOT_TEST_MQTT_TIMEOUT_MS ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Wait for the reentrant callback to complete. */ diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index d1684027a4..54dc549072 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -933,7 +933,7 @@ TEST( MQTT_Unit_API, DisconnectMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) with various + * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 0) with various * valid and invalid parameters. */ TEST( MQTT_Unit_API, PublishQoS0Parameters ) @@ -955,19 +955,19 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) if( TEST_PROTECT() ) { /* Check that the publish info is validated. */ - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, NULL, NULL ); + status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); publishInfo.pTopicName = TEST_TOPIC_NAME; publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; /* Check that a QoS 0 publish is refused if a notification is requested. */ - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishOperation ); + status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, IOT_MQTT_FLAG_WAITABLE, NULL, &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); + status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, &callbackInfo, NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* If valid parameters are passed, QoS 0 publish should always return success. */ - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, 0, &publishOperation ); + status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, 0, &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); } @@ -977,7 +977,7 @@ TEST( MQTT_Unit_API, PublishQoS0Parameters ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_publish (QoS 0) when memory + * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 0) when memory * allocation fails at various points. */ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) @@ -1007,7 +1007,7 @@ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) /* Call PUBLISH. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Publish( _pMqttConnection, &publishInfo, 0, NULL, NULL ); + status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); /* Once PUBLISH succeeds, the loop can exit. */ if( status == IOT_MQTT_SUCCESS ) @@ -1027,8 +1027,8 @@ TEST( MQTT_Unit_API, PublishQoS0MallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_publish (QoS 1) with various - * invalid parameters. Also tests the behavior of @ref mqtt_function_publish + * @brief Tests the behavior of @ref mqtt_function_publishasync (QoS 1) with various + * invalid parameters. Also tests the behavior of @ref mqtt_function_publishasync * (QoS 1) when memory allocation fails at various points. */ TEST( MQTT_Unit_API, PublishQoS1 ) @@ -1056,19 +1056,19 @@ TEST( MQTT_Unit_API, PublishQoS1 ) if( TEST_PROTECT() ) { /* Setting the waitable flag with no reference is not allowed. */ - status = IotMqtt_Publish( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); + status = IotMqtt_PublishAsync( _pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Setting both the waitable flag and callback info is not allowed. */ - status = IotMqtt_Publish( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - &callbackInfo, - &publishOperation ); + status = IotMqtt_PublishAsync( _pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + &callbackInfo, + &publishOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); /* Check QoS 1 PUBLISH behavior with malloc failures. */ @@ -1078,11 +1078,11 @@ TEST( MQTT_Unit_API, PublishQoS1 ) /* Call PUBLISH. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Publish( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishOperation ); + status = IotMqtt_PublishAsync( _pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishOperation ); /* If the PUBLISH succeeded, the loop can exit after waiting for the QoS * 1 PUBLISH to be cleaned up. */ @@ -1148,11 +1148,11 @@ TEST( MQTT_Unit_API, PublishDuplicates ) { /* Send a PUBLISH with retransmissions enabled. */ TEST_ASSERT_EQUAL( IOT_MQTT_STATUS_PENDING, - IotMqtt_Publish( _pMqttConnection, - &publishInfo, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &publishOperation ) ); + IotMqtt_PublishAsync( _pMqttConnection, + &publishInfo, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &publishOperation ) ); /* Since _dupChecker doesn't actually transmit a PUBLISH, no PUBACK is * expected. */ @@ -1179,8 +1179,8 @@ TEST( MQTT_Unit_API, PublishDuplicates ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_subscribe and - * @ref mqtt_function_unsubscribe with various invalid parameters. + * @brief Tests the behavior of @ref mqtt_function_subscribeasync and + * @ref mqtt_function_unsubscribeasync with various invalid parameters. */ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) { @@ -1195,20 +1195,20 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) TEST_ASSERT_NOT_NULL( _pMqttConnection ); /* Check that subscription info is validated. */ - status = IotMqtt_Subscribe( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeOperation ); + status = IotMqtt_SubscribeAsync( _pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_Unsubscribe( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeOperation ); + status = IotMqtt_UnsubscribeAsync( _pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeOperation ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); subscription.pTopicFilter = TEST_TOPIC_NAME; @@ -1216,20 +1216,20 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) subscription.callback.function = SUBSCRIPTION_CALLBACK; /* A reference must be provided for a waitable SUBSCRIBE. */ - status = IotMqtt_Subscribe( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); + status = IotMqtt_SubscribeAsync( _pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); - status = IotMqtt_Unsubscribe( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - NULL ); + status = IotMqtt_UnsubscribeAsync( _pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + NULL ); TEST_ASSERT_EQUAL( IOT_MQTT_BAD_PARAMETER, status ); IotMqtt_Disconnect( _pMqttConnection, IOT_MQTT_FLAG_CLEANUP_ONLY ); @@ -1238,7 +1238,7 @@ TEST( MQTT_Unit_API, SubscribeUnsubscribeParameters ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_subscribe when memory allocation + * @brief Tests the behavior of @ref mqtt_function_subscribeasync when memory allocation * fails at various points. */ TEST( MQTT_Unit_API, SubscribeMallocFail ) @@ -1270,12 +1270,12 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) /* Call SUBSCRIBE. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Subscribe( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &subscribeOperation ); + status = IotMqtt_SubscribeAsync( _pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &subscribeOperation ); /* If the SUBSCRIBE succeeded, the loop can exit after waiting for * the SUBSCRIBE to be cleaned up. */ @@ -1300,7 +1300,7 @@ TEST( MQTT_Unit_API, SubscribeMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_unsubscribe when memory + * @brief Tests the behavior of @ref mqtt_function_unsubscribeasync when memory * allocation fails at various points. */ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) @@ -1332,12 +1332,12 @@ TEST( MQTT_Unit_API, UnsubscribeMallocFail ) /* Call UNSUBSCRIBE. Memory allocation will fail at various times during * this call. */ - status = IotMqtt_Unsubscribe( _pMqttConnection, - &subscription, - 1, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &unsubscribeOperation ); + status = IotMqtt_UnsubscribeAsync( _pMqttConnection, + &subscription, + 1, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &unsubscribeOperation ); /* If the UNSUBSCRIBE succeeded, the loop can exit after waiting for * the UNSUBSCRIBE to be cleaned up. */ @@ -1393,15 +1393,15 @@ TEST( MQTT_Unit_API, SingleThreaded ) TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &mqttConnection ) ); /* Add a subscription. */ - status = IotMqtt_TimedSubscribe( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); + status = IotMqtt_SubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); /* Transmit a message with no retry. */ - status = IotMqtt_TimedPublish( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); + status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); /* Remove the subscription. */ - status = IotMqtt_TimedUnsubscribe( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); + status = IotMqtt_UnsubscribeSync( mqttConnection, &subscription, 1, 0, DUP_CHECK_TIMEOUT ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); /* Re-initialize the system task pool. The task pool must be available to @@ -1411,7 +1411,7 @@ TEST( MQTT_Unit_API, SingleThreaded ) /* Transmit a message with a retry. */ publishInfo.retryLimit = DUP_CHECK_RETRY_LIMIT; publishInfo.retryMs = DUP_CHECK_RETRY_MS; - status = IotMqtt_TimedPublish( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); + status = IotMqtt_PublishSync( mqttConnection, &publishInfo, 0, DUP_CHECK_TIMEOUT ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); IotTest_MqttMockCleanup(); From 8b3c957556684ffbf891fd8acb177ce8e80e6899 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 13 Sep 2019 12:14:46 -0700 Subject: [PATCH 253/844] Rename functions in Jobs (#558) --- lib/include/aws_iot_jobs.h | 228 +++++++++--------- lib/include/private/aws_iot_jobs_internal.h | 8 +- lib/include/types/aws_iot_jobs_types.h | 212 ++++++++-------- lib/source/jobs/aws_iot_jobs_api.c | 120 ++++----- tests/jobs/system/aws_iot_tests_jobs_system.c | 118 ++++----- tests/jobs/unit/aws_iot_tests_jobs_api.c | 206 ++++++++-------- 6 files changed, 446 insertions(+), 446 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index c3dd027d07..377be00248 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -39,14 +39,14 @@ * @functionspage{jobs,Jobs library} * - @functionname{jobs_function_init} * - @functionname{jobs_function_cleanup} - * - @functionname{jobs_function_getpending} - * - @functionname{jobs_function_timedgetpending} - * - @functionname{jobs_function_startnext} - * - @functionname{jobs_function_timedstartnext} - * - @functionname{jobs_function_describe} - * - @functionname{jobs_function_timeddescribe} - * - @functionname{jobs_function_update} - * - @functionname{jobs_function_timedupdate} + * - @functionname{jobs_function_getpendingasync} + * - @functionname{jobs_function_getpendingsync} + * - @functionname{jobs_function_startnextasync} + * - @functionname{jobs_function_startnextsync} + * - @functionname{jobs_function_describeasync} + * - @functionname{jobs_function_describesync} + * - @functionname{jobs_function_updateasync} + * - @functionname{jobs_function_updatesync} * - @functionname{jobs_function_wait} * - @functionname{jobs_function_setnotifypendingcallback} * - @functionname{jobs_function_setnotifynextcallback} @@ -58,14 +58,14 @@ /** * @functionpage{AwsIotJobs_Init,jobs,init} * @functionpage{AwsIotJobs_Cleanup,jobs,cleanup} - * @functionpage{AwsIotJobs_GetPending,jobs,getpending} - * @functionpage{AwsIotJobs_TimedGetPending,jobs,timedgetpending} - * @functionpage{AwsIotJobs_StartNext,jobs,startnext} - * @functionpage{AwsIotJobs_TimedStartNext,jobs,timedstartnext} - * @functionpage{AwsIotJobs_Describe,jobs,describe} - * @functionpage{AwsIotJobs_TimedDescribe,jobs,timeddescribe} - * @functionpage{AwsIotJobs_Update,jobs,update} - * @functionpage{AwsIotJobs_TimedUpdate,jobs,timedupdate} + * @functionpage{AwsIotJobs_GetPendingAsync,jobs,getpendingasync} + * @functionpage{AwsIotJobs_GetPendingSync,jobs,getpendingsync} + * @functionpage{AwsIotJobs_StartNextAsync,jobs,startnextasync} + * @functionpage{AwsIotJobs_StartNextSync,jobs,startnextsync} + * @functionpage{AwsIotJobs_DescribeAsync,jobs,describeasync} + * @functionpage{AwsIotJobs_DescribeSync,jobs,describesync} + * @functionpage{AwsIotJobs_UpdateAsync,jobs,updateasync} + * @functionpage{AwsIotJobs_UpdateSync,jobs,updatesync} * @functionpage{AwsIotJobs_Wait,jobs,wait} * @functionpage{AwsIotJobs_SetNotifyPendingCallback,jobs,setnotifypendingcallback} * @functionpage{AwsIotJobs_SetNotifyNextCallback,jobs,setnotifynextcallback} @@ -147,7 +147,7 @@ void AwsIotJobs_Cleanup( void ); * - #AWS_IOT_JOBS_BAD_RESPONSE * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. * - * @see @ref jobs_function_timedgetpending for a blocking variant of this function. + * @see @ref jobs_function_getpendingsync for a blocking variant of this function. * * Example * @code{c} @@ -170,29 +170,29 @@ void AwsIotJobs_Cleanup( void ); * callbackInfo.function = _jobsCallback; * * // Queue Jobs GET PENDING. - * AwsIotJobsError_t getPendingResult = AwsIotJobs_GetPending( &requestInfo, - * 0, - * &callbackInfo, - * &getPendingOperation ); + * AwsIotJobsError_t getPendingResult = AwsIotJobs_GetPendingAsync( &requestInfo, + * 0, + * &callbackInfo, + * &getPendingOperation ); * * // GET PENDING should have returned AWS_IOT_JOBS_STATUS_PENDING. The function * // _jobsCallback will be invoked once the Jobs response is received. * @endcode */ -/* @[declare_jobs_getpending] */ -AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pGetPendingOperation ); -/* @[declare_jobs_getpending] */ +/* @[declare_jobs_getpendingasync] */ +AwsIotJobsError_t AwsIotJobs_GetPendingAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pGetPendingOperation ); +/* @[declare_jobs_getpendingasync] */ /** * @brief Get the list of all pending jobs for a Thing with a timeout for receiving * the response. * * This function queues a Jobs GET PENDING, then waits for the result. Internally, - * this function is a call to @ref jobs_function_getpending followed by - * @ref jobs_function_wait. See @ref jobs_function_getpending for more information + * this function is a call to @ref jobs_function_getpendingasync followed by + * @ref jobs_function_wait. See @ref jobs_function_getpendingasync for more information * on the Jobs GET PENDING command. * * @param[in] pRequestInfo Jobs request parameters. @@ -211,12 +211,12 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques * - #AWS_IOT_JOBS_TIMEOUT * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ -/* @[declare_jobs_timedgetpending] */ -AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_timedgetpending] */ +/* @[declare_jobs_getpendingsync] */ +AwsIotJobsError_t AwsIotJobs_GetPendingSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); +/* @[declare_jobs_getpendingsync] */ /** * @brief Start the next pending job execution for a Thing and receive an asynchronous @@ -249,7 +249,7 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR * - #AWS_IOT_JOBS_BAD_RESPONSE * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. * - * @see @ref jobs_function_timedstartnext for a blocking variant of this function. + * @see @ref jobs_function_startnextsync for a blocking variant of this function. * * Example * @code{c} @@ -274,31 +274,31 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR * callbackInfo.function = _jobsCallback; * * // Queue Jobs START NEXT. - * AwsIotJobsError_t startNextResult = AwsIotJobs_StartNext( &requestInfo, - * &updateInfo, - * 0, - * &callbackInfo, - * &startNextOperation ); + * AwsIotJobsError_t startNextResult = AwsIotJobs_StartNextAsync( &requestInfo, + * &updateInfo, + * 0, + * &callbackInfo, + * &startNextOperation ); * * // START NEXT should have returned AWS_IOT_JOBS_STATUS_PENDING. The function * // _jobsCallback will be invoked once the Jobs response is received. * @endcode */ -/* @[declare_jobs_startnext] */ -AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pStartNextOperation ); -/* @[declare_jobs_startnext] */ +/* @[declare_jobs_startnextasync] */ +AwsIotJobsError_t AwsIotJobs_StartNextAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pStartNextOperation ); +/* @[declare_jobs_startnextasync] */ /** * @brief Start the next pending job execution for a Thing with a timeout for * receiving the response. * * This function queues a Jobs START NEXT, then waits for the result. Internally, - * this function is a call to @ref jobs_function_startnext followed by - * @ref jobs_function_wait. See @ref jobs_function_startnext for more information + * this function is a call to @ref jobs_function_startnextasync followed by + * @ref jobs_function_wait. See @ref jobs_function_startnextasync for more information * on the Jobs START NEXT command. * * @param[in] pRequestInfo Jobs request parameters. @@ -318,13 +318,13 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest * - #AWS_IOT_JOBS_TIMEOUT * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ -/* @[declare_jobs_timedstartnext] */ -AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_timedstartnext] */ +/* @[declare_jobs_startnextsync] */ +AwsIotJobsError_t AwsIotJobs_StartNextSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); +/* @[declare_jobs_startnextsync] */ /** * @brief Get detailed information about a job execution and receive an asynchronous @@ -361,7 +361,7 @@ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRe * - #AWS_IOT_JOBS_BAD_RESPONSE * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. * - * @see @ref jobs_function_timeddescribe for a blocking variant of this function. + * @see @ref jobs_function_describesync for a blocking variant of this function. * * Example * @code{c} @@ -388,33 +388,33 @@ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRe * callbackInfo.function = _jobsCallback; * * // Queue Jobs DESCRIBE. - * AwsIotJobsError_t describeResult = AwsIotJobs_Describe( &requestInfo, - * AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - * false, - * 0, - * &callbackInfo, - * &describeOperation ); + * AwsIotJobsError_t describeResult = AwsIotJobs_DescribeAsync( &requestInfo, + * AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + * false, + * 0, + * &callbackInfo, + * &describeOperation ); * * // DESCRIBE should have returned AWS_IOT_JOBS_STATUS_PENDING. The function * // _jobsCallback will be invoked once the Jobs response is received. * @endcode */ -/* @[declare_jobs_describe] */ -AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pDescribeOperation ); -/* @[declare_jobs_describe] */ +/* @[declare_jobs_describeasync] */ +AwsIotJobsError_t AwsIotJobs_DescribeAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pDescribeOperation ); +/* @[declare_jobs_describeasync] */ /** * @brief Get detailed information about a job execution with a timeout for receiving * the response. * * This function queues a Jobs DESCRIBE, then waits for the result. Internally, - * this function is a call to @ref jobs_function_describe followed by - * @ref jobs_function_wait. See @ref jobs_function_describe for more information + * this function is a call to @ref jobs_function_describeasync followed by + * @ref jobs_function_wait. See @ref jobs_function_describeasync for more information * on the Jobs DESCRIBE command. * * @param[in] pRequestInfo Jobs request parameters. @@ -437,14 +437,14 @@ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestI * - #AWS_IOT_JOBS_TIMEOUT * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ -/* @[declare_jobs_timeddescribe] */ -AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_timeddescribe] */ +/* @[declare_jobs_describesync] */ +AwsIotJobsError_t AwsIotJobs_DescribeSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); +/* @[declare_jobs_describesync] */ /** * @brief Update the status of a job execution and receive an asynchronous @@ -476,7 +476,7 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq * - #AWS_IOT_JOBS_BAD_RESPONSE * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. * - * @see @ref jobs_function_timedupdate for a blocking variant of this function. + * @see @ref jobs_function_updatesync for a blocking variant of this function. * * Example * @code{c} @@ -507,31 +507,31 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq * callbackInfo.function = _jobsCallback; * * // Queue Jobs UPDATE. - * AwsIotJobsError_t updateResult = AwsIotJobs_Update( &requestInfo, - * &updateInfo, - * 0, - * &callbackInfo, - * &updateOperation ); + * AwsIotJobsError_t updateResult = AwsIotJobs_UpdateAsync( &requestInfo, + * &updateInfo, + * 0, + * &callbackInfo, + * &updateOperation ); * * // UPDATE should have returned AWS_IOT_JOBS_STATUS_PENDING. The function * // _jobsCallback will be invoked once the Jobs response is received. * @endcode */ -/* @[declare_jobs_update] */ -AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pUpdateOperation ); -/* @[declare_jobs_update] */ +/* @[declare_jobs_updateasync] */ +AwsIotJobsError_t AwsIotJobs_UpdateAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pUpdateOperation ); +/* @[declare_jobs_updateasync] */ /** * @brief Update the status of a job execution with a timeout for receiving the * response. * * This function queues a Jobs UPDATE, then waits for the result. Internally, - * this function is a call to @ref jobs_function_update followed by - * @ref jobs_function_wait. See @ref jobs_function_update for more information + * this function is a call to @ref jobs_function_updateasync followed by + * @ref jobs_function_wait. See @ref jobs_function_updateasync for more information * on the Jobs UPDATE command. * * @param[in] pRequestInfo Jobs request parameters. @@ -551,20 +551,20 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf * - #AWS_IOT_JOBS_TIMEOUT * - A Jobs failure reason between #AWS_IOT_JOBS_INVALID_TOPIC and #AWS_IOT_JOBS_TERMINAL_STATE. */ -/* @[declare_jobs_timedupdate] */ -AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ); -/* @[declare_jobs_timedupdate] */ +/* @[declare_jobs_updatesync] */ +AwsIotJobsError_t AwsIotJobs_UpdateSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ); +/* @[declare_jobs_updatesync] */ /** * @brief Wait for a Jobs operation to complete. * - * This function blocks to wait for a [GET PENDING](@ref jobs_function_getpending), - * [START NEXT](@ref jobs_function_startnext), [DESCRIBE](@ref jobs_function_describe), - * or [UPDATE](@ref jobs_function_update) operation to complete. These operations are + * This function blocks to wait for a [GET PENDING](@ref jobs_function_getpendingasync), + * [START NEXT](@ref jobs_function_startnextasync), [DESCRIBE](@ref jobs_function_describeasync), + * or [UPDATE](@ref jobs_function_updateasync) operation to complete. These operations are * by default asynchronous; the function calls queue an operation for processing, * and a callback is invoked once the operation is complete. * @@ -615,11 +615,11 @@ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pReque * updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; * * // Queue Jobs UPDATE. - * AwsIotJobsError_t updateResult = AwsIotJobs_Update( &requestInfo, - * &updateInfo, - * AWS_IOT_JOBS_FLAG_WAITABLE, - * NULL, - * &updateOperation ); + * AwsIotJobsError_t updateResult = AwsIotJobs_UpdateAsync( &requestInfo, + * &updateInfo, + * AWS_IOT_JOBS_FLAG_WAITABLE, + * NULL, + * &updateOperation ); * * // UPDATE should have returned AWS_IOT_JOBS_STATUS_PENDING. The call to wait * // returns once the result of the UPDATE is available or the timeout expires. @@ -818,8 +818,8 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyNextCallback( IotMqttConnection_t mqttConn /** * @brief Remove persistent Jobs operation topic subscriptions. * - * Passing the flag @ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS to @ref jobs_function_getpending, - * @ref jobs_function_startnext, @ref jobs_function_describe, @ref jobs_function_update, + * Passing the flag @ref AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS to @ref jobs_function_getpendingasync, + * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, @ref jobs_function_updateasync, * or their blocking versions causes the Jobs operation topic subscriptions to be * maintained for future calls to the same function. If a persistent subscription for a * Jobs topic are no longer needed, this function may be used to remove it. diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index ae80c99b75..69ef60704e 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -283,10 +283,10 @@ typedef enum _jobsOperationType { /* Jobs operations. */ - JOBS_GET_PENDING = 0, /**< @ref jobs_function_getpending */ - JOBS_START_NEXT = 1, /**< @ref jobs_function_startnext */ - JOBS_DESCRIBE = 2, /**< @ref jobs_function_describe */ - JOBS_UPDATE = 3, /**< @ref jobs_function_update */ + JOBS_GET_PENDING = 0, /**< @ref jobs_function_getpendingasync */ + JOBS_START_NEXT = 1, /**< @ref jobs_function_startnextasync */ + JOBS_DESCRIBE = 2, /**< @ref jobs_function_describeasync */ + JOBS_UPDATE = 3, /**< @ref jobs_function_updateasync */ /* Jobs callbacks. */ SET_NOTIFY_PENDING_CALLBACK = 4, /**< @ref jobs_function_setnotifypendingcallback */ diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 390a250697..28541adf9c 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -43,14 +43,14 @@ * @ingroup jobs_datatypes_handles * @brief Opaque handle that references an in-progress Jobs operation. * - * Set as an output parameter of @ref jobs_function_getpending, @ref jobs_function_startnext, - * @ref jobs_function_describe, and @ref jobs_function_update. These functions send a + * Set as an output parameter of @ref jobs_function_getpendingasync, @ref jobs_function_startnextasync, + * @ref jobs_function_describeasync, and @ref jobs_function_updateasync. These functions send a * message to the Jobs service requesting a Jobs operation; the result of this operation * is unknown until the Jobs service sends a response. Therefore, this handle serves as a * reference to Jobs operations awaiting a response from the Jobs service. * - * This reference will be valid from the successful return of @ref jobs_function_getpending, - * @ref jobs_function_startnext, @ref jobs_function_describe, and @ref jobs_function_update. + * This reference will be valid from the successful return of @ref jobs_function_getpendingasync, + * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, and @ref jobs_function_updateasync. * The reference becomes invalid once the [completion callback](@ref AwsIotJobsCallbackInfo_t) * is invoked, or @ref jobs_function_wait returns. * @@ -88,10 +88,10 @@ typedef enum AwsIotJobsError * Functions that may return this value: * - @ref jobs_function_init * - @ref jobs_function_wait - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback * - @ref jobs_function_removepersistentsubscriptions @@ -106,10 +106,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation queued, awaiting result. * * Functions that may return this value: - * - @ref jobs_function_getpending - * - @ref jobs_function_startnext - * - @ref jobs_function_describe - * - @ref jobs_function_update + * - @ref jobs_function_getpendingasync + * - @ref jobs_function_startnextasync + * - @ref jobs_function_describeasync + * - @ref jobs_function_updateasync */ AWS_IOT_JOBS_STATUS_PENDING, @@ -125,10 +125,10 @@ typedef enum AwsIotJobsError * @brief At least one parameter is invalid. * * Functions that may return this value: - * - @ref jobs_function_getpending and @ref jobs_function_timedgetpending - * - @ref jobs_function_startnext and @ref jobs_function_timedstartnext - * - @ref jobs_function_describe and @ref jobs_function_timeddescribe - * - @ref jobs_function_update and @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync + * - @ref jobs_function_describeasync and @ref jobs_function_describesync + * - @ref jobs_function_updateasync and @ref jobs_function_updatesync * - @ref jobs_function_wait * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback @@ -140,10 +140,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed because of memory allocation failure. * * Functions that may return this value: - * - @ref jobs_function_getpending and @ref jobs_function_timedgetpending - * - @ref jobs_function_startnext and @ref jobs_function_timedstartnext - * - @ref jobs_function_describe and @ref jobs_function_timeddescribe - * - @ref jobs_function_update and @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync + * - @ref jobs_function_describeasync and @ref jobs_function_describesync + * - @ref jobs_function_updateasync and @ref jobs_function_updatesync * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback */ @@ -153,10 +153,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed because of failure in MQTT library. * * Functions that may return this value: - * - @ref jobs_function_getpending and @ref jobs_function_timedgetpending - * - @ref jobs_function_startnext and @ref jobs_function_timedstartnext - * - @ref jobs_function_describe and @ref jobs_function_timeddescribe - * - @ref jobs_function_update and @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync + * - @ref jobs_function_describeasync and @ref jobs_function_describesync + * - @ref jobs_function_updateasync and @ref jobs_function_updatesync * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback * - @ref jobs_function_removepersistentsubscriptions @@ -167,10 +167,10 @@ typedef enum AwsIotJobsError * @brief Response received from Jobs service not understood. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
@@ -182,10 +182,10 @@ typedef enum AwsIotJobsError * @brief A blocking Jobs operation timed out. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback @@ -196,10 +196,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: A request was sent to an unknown topic. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
@@ -213,10 +213,10 @@ typedef enum AwsIotJobsError * Jobs requests must be UTF-8 encoded JSON documents. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
@@ -228,10 +228,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: The contents of the request were invalid. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
@@ -244,13 +244,13 @@ typedef enum AwsIotJobsError * to an invalid state. * * Functions that may return this value: - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timedupdate + * - @ref jobs_function_startnextsync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_startnext or @ref jobs_function_update. + * following a call to @ref jobs_function_startnextasync or @ref jobs_function_updateasync. */ AWS_IOT_JOBS_INVALID_STATE, @@ -258,13 +258,13 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: The specified job execution does not exist. * * * Functions that may return this value: - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_describe or @ref jobs_function_update. + * following a call to @ref jobs_function_describeasync or @ref jobs_function_updateasync. */ AWS_IOT_JOBS_NOT_FOUND, @@ -273,12 +273,12 @@ typedef enum AwsIotJobsError * not match what was in the request. * * * Functions that may return this value: - * - @ref jobs_function_timedupdate + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_update. + * following a call to @ref jobs_function_updateasync. */ AWS_IOT_JOBS_VERSION_MISMATCH, @@ -286,10 +286,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: The Jobs service encountered an internal error. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
@@ -301,10 +301,10 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: The request was throttled. * * Functions that may return this value: - * - @ref jobs_function_timedgetpending - * - @ref jobs_function_timedstartnext - * - @ref jobs_function_timeddescribe - * - @ref jobs_function_timedupdate + * - @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextsync + * - @ref jobs_function_describesync + * - @ref jobs_function_updatesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
@@ -316,12 +316,12 @@ typedef enum AwsIotJobsError * @brief Jobs operation failed: Attempt to describe a Job in a terminal state. * * Functions that may return this value: - * - @ref jobs_function_timeddescribe + * - @ref jobs_function_describesync * - @ref jobs_function_wait * * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) - * following a call to @ref jobs_function_describe. + * following a call to @ref jobs_function_describeasync. */ AWS_IOT_JOBS_TERMINAL_STATE } AwsIotJobsError_t; @@ -369,7 +369,7 @@ typedef enum AwsIotJobsState * * Jobs are considered timed out if they remain [IN_PROGRESS] * (@ref AWS_IOT_JOB_STATE_IN_PROGRESS) for more than their - * `inProgressTimeoutInMinutes` property or a [Job update](@ref jobs_function_update) + * `inProgressTimeoutInMinutes` property or a [Job update](@ref jobs_function_updateasync) * was not sent within [stepTimeoutInMinutes](@ref AwsIotJobsUpdateInfo_t.stepTimeoutInMinutes). */ AWS_IOT_JOB_STATE_TIMED_OUT, @@ -394,10 +394,10 @@ typedef enum AwsIotJobsState */ typedef enum AwsIotJobsCallbackType { - AWS_IOT_JOBS_GET_PENDING_COMPLETE = 0, /**< Callback invoked because a [Jobs get pending](@ref jobs_function_getpending) completed. */ - AWS_IOT_JOBS_START_NEXT_COMPLETE = 1, /**< Callback invoked because a [Jobs start next](@ref jobs_function_startnext) completed. */ - AWS_IOT_JOBS_DESCRIBE_COMPLETE = 2, /**< Callback invoked because a [Jobs describe](@ref jobs_function_describe) completed. */ - AWS_IOT_JOBS_UPDATE_COMPLETE = 3, /**< Callback invoked because a [Jobs update](@ref jobs_function_update) completed. */ + AWS_IOT_JOBS_GET_PENDING_COMPLETE = 0, /**< Callback invoked because a [Jobs get pending](@ref jobs_function_getpendingasync) completed. */ + AWS_IOT_JOBS_START_NEXT_COMPLETE = 1, /**< Callback invoked because a [Jobs start next](@ref jobs_function_startnextasync) completed. */ + AWS_IOT_JOBS_DESCRIBE_COMPLETE = 2, /**< Callback invoked because a [Jobs describe](@ref jobs_function_describeasync) completed. */ + AWS_IOT_JOBS_UPDATE_COMPLETE = 3, /**< Callback invoked because a [Jobs update](@ref jobs_function_updateasync) completed. */ AWS_IOT_JOBS_NOTIFY_PENDING_CALLBACK = 4, /**< Callback invoked for an incoming message on a [Jobs notify-pending](@ref jobs_function_setnotifypendingcallback) topic. */ AWS_IOT_JOBS_NOTIFY_NEXT_CALLBACK = 5 /**< Callback invoked for an incoming message on a [Jobs notify-next](@ref jobs_function_setnotifynextcallback) topic. */ } AwsIotJobsCallbackType_t; @@ -467,8 +467,8 @@ typedef struct AwsIotJobsCallbackParam * @ingroup jobs_datatypes_paramstructs * @brief Information on a user-provided Jobs callback function. * - * @paramfor @ref jobs_function_getpending, @ref jobs_function_startnext, - * @ref jobs_function_describe, @ref jobs_function_update, + * @paramfor @ref jobs_function_getpendingasync, @ref jobs_function_startnextasync, + * @ref jobs_function_describeasync, @ref jobs_function_updateasync, * @ref jobs_function_setnotifypendingcallback, @ref jobs_function_setnotifynextcallback * * Provides a function to be invoked when a Jobs operation completes or when a @@ -497,10 +497,10 @@ typedef struct AwsIotJobsCallbackInfo * @ingroup jobs_datatypes_paramstructs * @brief Common information provided to Jobs requests. * - * @paramfor @ref jobs_function_getpending, @ref jobs_function_timedgetpending, - * @ref jobs_function_startnext, @ref jobs_function_timedstartnext - * @ref jobs_function_describe, @ref jobs_function_timeddescribe, - * @ref jobs_function_update, @ref jobs_function_timedupdate + * @paramfor @ref jobs_function_getpendingasync, @ref jobs_function_getpendingsync, + * @ref jobs_function_startnextasync, @ref jobs_function_startnextsync + * @ref jobs_function_describeasync, @ref jobs_function_describesync, + * @ref jobs_function_updateasync, @ref jobs_function_updatesync * * @initializer{AwsIotJobsRequestInfo_t,AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER} */ @@ -535,18 +535,18 @@ typedef struct AwsIotJobsRequestInfo * When using #AWS_IOT_JOBS_NEXT_JOB, #AwsIotJobsRequestInfo_t.jobIdLength * should be set to #AWS_IOT_JOBS_NEXT_JOB_LENGTH. * - * This parameter is ignored for calls to @ref jobs_function_getpending, - * @ref jobs_function_timedgetpending, @ref jobs_function_startnext, - * and @ref jobs_function_timedstartnext. + * This parameter is ignored for calls to @ref jobs_function_getpendingasync, + * @ref jobs_function_getpendingsync, @ref jobs_function_startnextasync, + * and @ref jobs_function_startnextsync. */ const char * pJobId; /** * @brief Length of #AwsIotJobsRequestInfo_t.pJobId. * - * This parameter is ignored for calls to @ref jobs_function_getpending, - * @ref jobs_function_timedgetpending, @ref jobs_function_startnext, - * and @ref jobs_function_timedstartnext. + * This parameter is ignored for calls to @ref jobs_function_getpendingasync, + * @ref jobs_function_getpendingsync, @ref jobs_function_startnextasync, + * and @ref jobs_function_startnextsync. */ size_t jobIdLength; @@ -568,8 +568,8 @@ typedef struct AwsIotJobsRequestInfo * @ingroup jobs_datatypes_paramstructs * @brief Output parameter of blocking Jobs API functions. * - * @paramfor @ref jobs_function_timedgetpending, @ref jobs_function_timedstartnext, - * @ref jobs_function_timeddescribe, @ref jobs_function_timedupdate, + * @paramfor @ref jobs_function_getpendingsync, @ref jobs_function_startnextsync, + * @ref jobs_function_describesync, @ref jobs_function_updatesync, * @ref jobs_function_wait * * Provides the response received from the Jobs service. The buffer for the @@ -585,11 +585,11 @@ typedef struct AwsIotJobsResponse /** * @ingroup jobs_datatypes_paramstructs - * @brief Information on a Job update for @ref jobs_function_startnext and - * @ref jobs_function_update. These functions modify a Job's state. + * @brief Information on a Job update for @ref jobs_function_startnextasync and + * @ref jobs_function_updateasync. These functions modify a Job's state. * - * @paramfor @ref jobs_function_startnext, @ref jobs_function_timedstartnext, - * @ref jobs_function_update, and @ref jobs_function_timedupdate. + * @paramfor @ref jobs_function_startnextasync, @ref jobs_function_startnextsync, + * @ref jobs_function_updateasync, and @ref jobs_function_updatesync. * * @initializer{AwsIotJobsUpdateInfo_t,AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER} */ @@ -604,8 +604,8 @@ typedef struct AwsIotJobsUpdateInfo * - #AWS_IOT_JOB_STATE_SUCCEEDED * - #AWS_IOT_JOB_STATE_REJECTED * - * This parameter is ignored for calls to @ref jobs_function_startnext and - * @ref jobs_function_timedstartnext. These functions always set the state + * This parameter is ignored for calls to @ref jobs_function_startnextasync and + * @ref jobs_function_startnextsync. These functions always set the state * to #AWS_IOT_JOB_STATE_IN_PROGRESS. */ AwsIotJobState_t newStatus; @@ -622,8 +622,8 @@ typedef struct AwsIotJobsUpdateInfo * Jobs service does not overwrite a later update with a previous one. If not * needed, it can be set to #AWS_IOT_JOBS_NO_VERSION. * - * This parameter is ignored for calls to @ref jobs_function_startnext and - * @ref jobs_function_timedstartnext. + * This parameter is ignored for calls to @ref jobs_function_startnextasync and + * @ref jobs_function_startnextsync. */ uint32_t expectedVersion; @@ -638,8 +638,8 @@ typedef struct AwsIotJobsUpdateInfo * This value is optional. It may be set to #AWS_IOT_JOBS_NO_EXECUTION_NUMBER * if not needed. * - * This parameter is ignored for calls to @ref jobs_function_startnext and - * @ref jobs_function_timedstartnext. + * This parameter is ignored for calls to @ref jobs_function_startnextasync and + * @ref jobs_function_startnextsync. */ int32_t executionNumber; @@ -661,8 +661,8 @@ typedef struct AwsIotJobsUpdateInfo * * The default value is `false`. * - * This parameter is ignored for calls to @ref jobs_function_startnext and - * @ref jobs_function_timedstartnext. + * This parameter is ignored for calls to @ref jobs_function_startnextasync and + * @ref jobs_function_startnextsync. */ bool includeJobExecutionState; @@ -671,8 +671,8 @@ typedef struct AwsIotJobsUpdateInfo * * The default value is `false`. * - * This parameter is ignored for calls to @ref jobs_function_startnext and - * @ref jobs_function_timedstartnext. + * This parameter is ignored for calls to @ref jobs_function_startnextasync and + * @ref jobs_function_startnextsync. */ bool includeJobDocument; @@ -733,7 +733,7 @@ typedef struct AwsIotJobsUpdateInfo /** * @brief Set #AwsIotJobsUpdateInfo_t.executionNumber to this value or pass it to - * @ref jobs_function_describe to omit the execution number in the Jobs request. + * @ref jobs_function_describeasync to omit the execution number in the Jobs request. * * @note The value of this constant may change at any time in future versions, but * its name will remain the same. @@ -802,8 +802,8 @@ typedef struct AwsIotJobsUpdateInfo * Jobs library functions. * * The following flags are valid for the Jobs operation functions: - * @ref jobs_function_getpending, @ref jobs_function_startnext, - * @ref jobs_function_describe, @ref jobs_function_update, + * @ref jobs_function_getpendingasync, @ref jobs_function_startnextasync, + * @ref jobs_function_describeasync, @ref jobs_function_updateasync, * and their blocking versions. * - #AWS_IOT_JOBS_FLAG_WAITABLE
* @copybrief AWS_IOT_JOBS_FLAG_WAITABLE @@ -847,8 +847,8 @@ typedef struct AwsIotJobsUpdateInfo /** * @brief Allows the use of @ref jobs_function_wait for blocking until completion. * - * This flag is only valid if passed to the functions @ref jobs_function_getpending, - * @ref jobs_function_startnext, @ref jobs_function_describe, or @ref jobs_function_update. + * This flag is only valid if passed to the functions @ref jobs_function_getpendingasync, + * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, or @ref jobs_function_updateasync. * * An #AwsIotJobsOperation_t MUST be provided if this flag is set. * Additionally, an #AwsIotJobsCallbackInfo_t MUST NOT be provided. @@ -865,8 +865,8 @@ typedef struct AwsIotJobsUpdateInfo * @brief Maintain the subscriptions for the Jobs operation topics, even after * this function returns. * - * This flag is only valid if passed to the functions @ref jobs_function_getpending, - * @ref jobs_function_startnext, @ref jobs_function_describe, or @ref jobs_function_update, + * This flag is only valid if passed to the functions @ref jobs_function_getpendingasync, + * @ref jobs_function_startnextasync, @ref jobs_function_describeasync, or @ref jobs_function_updateasync, * and their blocking versions. * * The Jobs service reports results of Jobs operations by publishing @@ -896,7 +896,7 @@ typedef struct AwsIotJobsUpdateInfo * * This flag may be passed to @ref jobs_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_getpending or @ref jobs_function_timedgetpending. + * call to @ref jobs_function_getpendingasync or @ref jobs_function_getpendingsync. * * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs get pending operations. @@ -911,7 +911,7 @@ typedef struct AwsIotJobsUpdateInfo * * This flag may be passed to @ref jobs_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_startnext or @ref jobs_function_timedstartnext. + * call to @ref jobs_function_startnextasync or @ref jobs_function_startnextsync. * * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs start next operations. @@ -926,7 +926,7 @@ typedef struct AwsIotJobsUpdateInfo * * This flag may be passed to @ref jobs_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_describe or @ref jobs_function_timeddescribe. + * call to @ref jobs_function_describeasync or @ref jobs_function_describesync. * * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs describe operations. @@ -941,7 +941,7 @@ typedef struct AwsIotJobsUpdateInfo * * This flag may be passed to @ref jobs_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref jobs_function_update or @ref jobs_function_timedupdate. + * call to @ref jobs_function_updateasync or @ref jobs_function_updatesync. * * @warning Do not call @ref jobs_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Jobs update operations. diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 2ca8b7686d..5c3696390e 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -770,10 +770,10 @@ void AwsIotJobs_Cleanup( void ) /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pGetPendingOperation ) +AwsIotJobsError_t AwsIotJobs_GetPendingAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pGetPendingOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; @@ -826,10 +826,10 @@ AwsIotJobsError_t AwsIotJobs_GetPending( const AwsIotJobsRequestInfo_t * pReques /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pRequestInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) +AwsIotJobsError_t AwsIotJobs_GetPendingSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsOperation_t getPendingOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; @@ -838,10 +838,10 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR flags |= AWS_IOT_JOBS_FLAG_WAITABLE; /* Call the asynchronous Jobs Get Pending function. */ - status = AwsIotJobs_GetPending( pRequestInfo, - flags, - NULL, - &getPendingOperation ); + status = AwsIotJobs_GetPendingAsync( pRequestInfo, + flags, + NULL, + &getPendingOperation ); /* Wait for the Jobs Get Pending operation to complete. */ if( status == AWS_IOT_JOBS_STATUS_PENDING ) @@ -856,11 +856,11 @@ AwsIotJobsError_t AwsIotJobs_TimedGetPending( const AwsIotJobsRequestInfo_t * pR /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pStartNextOperation ) +AwsIotJobsError_t AwsIotJobs_StartNextAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pStartNextOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; @@ -925,11 +925,11 @@ AwsIotJobsError_t AwsIotJobs_StartNext( const AwsIotJobsRequestInfo_t * pRequest /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) +AwsIotJobsError_t AwsIotJobs_StartNextSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsOperation_t startNextOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; @@ -938,11 +938,11 @@ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRe flags |= AWS_IOT_JOBS_FLAG_WAITABLE; /* Call the asynchronous Jobs Start Next function. */ - status = AwsIotJobs_StartNext( pRequestInfo, - pUpdateInfo, - flags, - NULL, - &startNextOperation ); + status = AwsIotJobs_StartNextAsync( pRequestInfo, + pUpdateInfo, + flags, + NULL, + &startNextOperation ); /* Wait for the Jobs Start Next operation to complete. */ if( status == AWS_IOT_JOBS_STATUS_PENDING ) @@ -957,12 +957,12 @@ AwsIotJobsError_t AwsIotJobs_TimedStartNext( const AwsIotJobsRequestInfo_t * pRe /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pDescribeOperation ) +AwsIotJobsError_t AwsIotJobs_DescribeAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pDescribeOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; @@ -1019,12 +1019,12 @@ AwsIotJobsError_t AwsIotJobs_Describe( const AwsIotJobsRequestInfo_t * pRequestI /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pRequestInfo, - int32_t executionNumber, - bool includeJobDocument, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) +AwsIotJobsError_t AwsIotJobs_DescribeSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + int32_t executionNumber, + bool includeJobDocument, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsOperation_t describeOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; @@ -1033,12 +1033,12 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq flags |= AWS_IOT_JOBS_FLAG_WAITABLE; /* Call the asynchronous Jobs Describe function. */ - status = AwsIotJobs_Describe( pRequestInfo, - executionNumber, - includeJobDocument, - flags, - NULL, - &describeOperation ); + status = AwsIotJobs_DescribeAsync( pRequestInfo, + executionNumber, + includeJobDocument, + flags, + NULL, + &describeOperation ); /* Wait for the Jobs Describe operation to complete. */ if( status == AWS_IOT_JOBS_STATUS_PENDING ) @@ -1053,11 +1053,11 @@ AwsIotJobsError_t AwsIotJobs_TimedDescribe( const AwsIotJobsRequestInfo_t * pReq /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotJobsCallbackInfo_t * pCallbackInfo, - AwsIotJobsOperation_t * const pUpdateOperation ) +AwsIotJobsError_t AwsIotJobs_UpdateAsync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotJobsCallbackInfo_t * pCallbackInfo, + AwsIotJobsOperation_t * const pUpdateOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; @@ -1122,11 +1122,11 @@ AwsIotJobsError_t AwsIotJobs_Update( const AwsIotJobsRequestInfo_t * pRequestInf /*-----------------------------------------------------------*/ -AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pRequestInfo, - const AwsIotJobsUpdateInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs, - AwsIotJobsResponse_t * const pJobsResponse ) +AwsIotJobsError_t AwsIotJobs_UpdateSync( const AwsIotJobsRequestInfo_t * pRequestInfo, + const AwsIotJobsUpdateInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs, + AwsIotJobsResponse_t * const pJobsResponse ) { AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsOperation_t updateOperation = AWS_IOT_JOBS_OPERATION_INITIALIZER; @@ -1135,11 +1135,11 @@ AwsIotJobsError_t AwsIotJobs_TimedUpdate( const AwsIotJobsRequestInfo_t * pReque flags |= AWS_IOT_JOBS_FLAG_WAITABLE; /* Call the asynchronous Jobs Update function. */ - status = AwsIotJobs_Update( pRequestInfo, - pUpdateInfo, - flags, - NULL, - &updateOperation ); + status = AwsIotJobs_UpdateAsync( pRequestInfo, + pUpdateInfo, + flags, + NULL, + &updateOperation ); /* Wait for the Jobs Update operation to complete. */ if( status == AWS_IOT_JOBS_STATUS_PENDING ) diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 657cc40a9f..0ac05e0142 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -314,27 +314,27 @@ static void _jobsAsyncTest( _jobsOperationType_t type, switch( type ) { case JOBS_GET_PENDING: - status = AwsIotJobs_GetPending( &requestInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); break; case JOBS_START_NEXT: - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); break; case JOBS_DESCRIBE: - status = AwsIotJobs_Describe( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - true, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotJobs_DescribeAsync( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + true, + 0, + &callbackInfo, + &( callbackParam.operation ) ); break; default: @@ -345,11 +345,11 @@ static void _jobsAsyncTest( _jobsOperationType_t type, requestInfo.pJobId = _pJobIds[ 0 ]; requestInfo.jobIdLength = _pJobIdLengths[ 0 ]; - status = AwsIotJobs_Update( &requestInfo, - &updateInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotJobs_UpdateAsync( &requestInfo, + &updateInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); break; } @@ -391,29 +391,29 @@ static void _jobsBlockingTest( _jobsOperationType_t type, switch( type ) { case JOBS_GET_PENDING: - status = AwsIotJobs_TimedGetPending( &requestInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); - break; - - case JOBS_START_NEXT: - status = AwsIotJobs_TimedStartNext( &requestInfo, - &updateInfo, + status = AwsIotJobs_GetPendingSync( &requestInfo, 0, AWS_IOT_TEST_JOBS_TIMEOUT, &jobsResponse ); break; - case JOBS_DESCRIBE: - status = AwsIotJobs_TimedDescribe( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - true, + case JOBS_START_NEXT: + status = AwsIotJobs_StartNextSync( &requestInfo, + &updateInfo, 0, AWS_IOT_TEST_JOBS_TIMEOUT, &jobsResponse ); break; + case JOBS_DESCRIBE: + status = AwsIotJobs_DescribeSync( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + true, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); + break; + default: /* The only remaining valid type is UPDATE. */ TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); @@ -421,11 +421,11 @@ static void _jobsBlockingTest( _jobsOperationType_t type, requestInfo.pJobId = _pJobIds[ _inProgressJob ]; requestInfo.jobIdLength = _pJobIdLengths[ _inProgressJob ]; - status = AwsIotJobs_TimedUpdate( &requestInfo, - &updateInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &jobsResponse ); + status = AwsIotJobs_UpdateSync( &requestInfo, + &updateInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &jobsResponse ); break; } @@ -630,7 +630,7 @@ TEST_GROUP_RUNNER( Jobs_System ) /*-----------------------------------------------------------*/ /** - * @brief Retrieves a list of Jobs using @ref jobs_function_timedgetpending. + * @brief Retrieves a list of Jobs using @ref jobs_function_getpendingasync. */ TEST( Jobs_System, GetPendingAsync ) { @@ -640,7 +640,7 @@ TEST( Jobs_System, GetPendingAsync ) /*-----------------------------------------------------------*/ /** - * @brief Retrieves a list of Jobs using @ref jobs_function_timedgetpending. + * @brief Retrieves a list of Jobs using @ref jobs_function_getpendingsync. */ TEST( Jobs_System, GetPendingBlocking ) { @@ -650,7 +650,7 @@ TEST( Jobs_System, GetPendingBlocking ) /*-----------------------------------------------------------*/ /** - * @brief Starts the next Job using @ref jobs_function_startnext. + * @brief Starts the next Job using @ref jobs_function_startnextasync. */ TEST( Jobs_System, StartNextAsync ) { @@ -660,7 +660,7 @@ TEST( Jobs_System, StartNextAsync ) /*-----------------------------------------------------------*/ /** - * @brief Starts the next Job using @ref jobs_function_timedstartnext. + * @brief Starts the next Job using @ref jobs_function_startnextsync. */ TEST( Jobs_System, StartNextBlocking ) { @@ -670,7 +670,7 @@ TEST( Jobs_System, StartNextBlocking ) /*-----------------------------------------------------------*/ /** - * @brief Describe a Job using @ref jobs_function_describe. + * @brief Describe a Job using @ref jobs_function_describeasync. */ TEST( Jobs_System, DescribeAsync ) { @@ -680,7 +680,7 @@ TEST( Jobs_System, DescribeAsync ) /*-----------------------------------------------------------*/ /** - * @brief Describe a Job using @ref jobs_function_timeddescribe. + * @brief Describe a Job using @ref jobs_function_describesync. */ TEST( Jobs_System, DescribeBlocking ) { @@ -690,7 +690,7 @@ TEST( Jobs_System, DescribeBlocking ) /*-----------------------------------------------------------*/ /** - * @brief Update a Job status using @ref jobs_function_update. + * @brief Update a Job status using @ref jobs_function_updateasync. */ TEST( Jobs_System, UpdateAsync ) { @@ -700,7 +700,7 @@ TEST( Jobs_System, UpdateAsync ) /*-----------------------------------------------------------*/ /** - * @brief Update a Job status using @ref jobs_function_timedupdate. + * @brief Update a Job status using @ref jobs_function_updatesync. */ TEST( Jobs_System, UpdateBlocking ) { @@ -727,10 +727,10 @@ TEST( Jobs_System, PersistentSubscriptions ) /* Time a Jobs function that sets persistent subscriptions. */ startTime = IotClock_GetTimeMs(); - status = AwsIotJobs_TimedGetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS, - AWS_IOT_TEST_JOBS_TIMEOUT, - &response ); + status = AwsIotJobs_GetPendingSync( &requestInfo, + AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS, + AWS_IOT_TEST_JOBS_TIMEOUT, + &response ); elapsedTime1 = IotClock_GetTimeMs() - startTime; /* Check results. */ @@ -742,10 +742,10 @@ TEST( Jobs_System, PersistentSubscriptions ) /* Time a Jobs functions that has persistent subscriptions set. */ startTime = IotClock_GetTimeMs(); - status = AwsIotJobs_TimedGetPending( &requestInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &response ); + status = AwsIotJobs_GetPendingSync( &requestInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &response ); elapsedTime2 = IotClock_GetTimeMs() - startTime; /* Check results */ @@ -812,11 +812,11 @@ TEST( Jobs_System, JobsCallbacks ) updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; - status = AwsIotJobs_TimedUpdate( &requestInfo, - &updateInfo, - 0, - AWS_IOT_TEST_JOBS_TIMEOUT, - &response ); + status = AwsIotJobs_UpdateSync( &requestInfo, + &updateInfo, + 0, + AWS_IOT_TEST_JOBS_TIMEOUT, + &response ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); IotTest_Free( ( void * ) response.pJobsResponse ); diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index d28143c77f..ad302665dc 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -115,38 +115,38 @@ static void _jobsMallocFail( _jobsOperationType_t type ) switch( type ) { case JOBS_GET_PENDING: - status = AwsIotJobs_GetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); break; case JOBS_START_NEXT: - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); break; case JOBS_DESCRIBE: - status = AwsIotJobs_Describe( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - false, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotJobs_DescribeAsync( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + false, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); break; default: /* The only remaining valid type is update. */ TEST_ASSERT_EQUAL( JOBS_UPDATE, type ); - status = AwsIotJobs_Update( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotJobs_UpdateAsync( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); break; } @@ -341,100 +341,100 @@ TEST( Jobs_Unit_API, OperationInvalidRequestInfo ) AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; /* Uninitialized MQTT connection. */ - status = AwsIotJobs_GetPending( &requestInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); requestInfo.mqttConnection = _pMqttConnection; /* Invalid Thing Name. */ - status = AwsIotJobs_GetPending( &requestInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); requestInfo.pThingName = TEST_THING_NAME; requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; /* No reference with waitable operation. */ - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - NULL ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Malloc function not set. */ - status = AwsIotJobs_GetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + NULL, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); requestInfo.mallocResponse = IotTest_Malloc; /* Both callback and waitable flag set. */ - status = AwsIotJobs_GetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - &callbackInfo, - &operation ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + &callbackInfo, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Callback function not set. */ - status = AwsIotJobs_GetPending( &requestInfo, - 0, - &callbackInfo, - NULL ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + 0, + &callbackInfo, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Client token length not set. */ requestInfo.pClientToken = "test"; requestInfo.clientTokenLength = 0; - status = AwsIotJobs_GetPending( &requestInfo, - AWS_IOT_JOBS_FLAG_WAITABLE, - 0, - &operation ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + AWS_IOT_JOBS_FLAG_WAITABLE, + 0, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Client token too long. */ requestInfo.clientTokenLength = AWS_IOT_CLIENT_TOKEN_MAX_LENGTH + 1; - status = AwsIotJobs_GetPending( &requestInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_GetPendingAsync( &requestInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); requestInfo.pClientToken = AWS_IOT_JOBS_CLIENT_TOKEN_AUTOGENERATE; /* Job ID not set. */ - status = AwsIotJobs_Describe( &requestInfo, - AWS_IOT_JOBS_NO_EXECUTION_NUMBER, - false, - 0, - NULL, - NULL ); + status = AwsIotJobs_DescribeAsync( &requestInfo, + AWS_IOT_JOBS_NO_EXECUTION_NUMBER, + false, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Job ID too long. */ requestInfo.pJobId = "jobid"; requestInfo.jobIdLength = JOBS_MAX_ID_LENGTH + 1; - status = AwsIotJobs_Update( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_UpdateAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Using $next with UPDATE is invalid. */ requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - status = AwsIotJobs_Update( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_UpdateAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); } @@ -458,40 +458,40 @@ TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) /* Negative, invalid step timeout. */ updateInfo.stepTimeoutInMinutes = -5; - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Step timeout too large. */ updateInfo.stepTimeoutInMinutes = JOBS_MAX_TIMEOUT + 1; - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); updateInfo.stepTimeoutInMinutes = AWS_IOT_JOBS_NO_TIMEOUT; /* Status details length not set. */ updateInfo.pStatusDetails = "test"; - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); /* Status details too large. */ updateInfo.statusDetailsLength = JOBS_MAX_STATUS_DETAILS_LENGTH + 1; - status = AwsIotJobs_StartNext( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_StartNextAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); updateInfo.pStatusDetails = AWS_IOT_JOBS_NO_STATUS_DETAILS; @@ -500,11 +500,11 @@ TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) requestInfo.pJobId = "jobid"; requestInfo.jobIdLength = 5; - status = AwsIotJobs_Update( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_UpdateAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); updateInfo.newStatus = AWS_IOT_JOB_STATE_IN_PROGRESS; @@ -512,11 +512,11 @@ TEST( Jobs_Unit_API, OperationInvalidUpdateInfo ) requestInfo.pJobId = AWS_IOT_JOBS_NEXT_JOB; requestInfo.jobIdLength = AWS_IOT_JOBS_NEXT_JOB_LENGTH; - status = AwsIotJobs_Update( &requestInfo, - &updateInfo, - 0, - NULL, - NULL ); + status = AwsIotJobs_UpdateAsync( &requestInfo, + &updateInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); } @@ -547,7 +547,7 @@ TEST( Jobs_Unit_API, WaitInvalidParameters ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref jobs_function_getpending when memory + * @brief Tests the behavior of @ref jobs_function_getpendingasync when memory * allocation fails at various points. */ TEST( Jobs_Unit_API, GetPendingMallocFail ) @@ -558,7 +558,7 @@ TEST( Jobs_Unit_API, GetPendingMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref jobs_function_startnext when memory + * @brief Tests the behavior of @ref jobs_function_startnextasync when memory * allocation fails at various points. */ TEST( Jobs_Unit_API, StartNextMallocFail ) @@ -569,7 +569,7 @@ TEST( Jobs_Unit_API, StartNextMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref jobs_function_describe when memory + * @brief Tests the behavior of @ref jobs_function_describeasync when memory * allocation fails at various points. */ TEST( Jobs_Unit_API, DescribeMallocFail ) @@ -580,7 +580,7 @@ TEST( Jobs_Unit_API, DescribeMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref jobs_function_update when memory + * @brief Tests the behavior of @ref jobs_function_updateasync when memory * allocation fails at various points. */ TEST( Jobs_Unit_API, UpdateMallocFail ) From 4bb24c064fb8bbadbd71dc0d4f4a6fc05a3e154b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 13 Sep 2019 15:21:22 -0700 Subject: [PATCH 254/844] Rename functions in Shadow (#559) --- demos/source/aws_iot_demo_shadow.c | 28 +-- doc/lib/shadow.txt | 2 +- lib/include/aws_iot_shadow.h | 230 ++++++++++-------- lib/include/private/aws_iot_shadow_internal.h | 6 +- lib/include/types/aws_iot_shadow_types.h | 158 ++++++------ lib/source/shadow/aws_iot_shadow_api.c | 94 +++---- .../system/aws_iot_tests_shadow_system.c | 96 ++++---- tests/shadow/unit/aws_iot_tests_shadow_api.c | 200 +++++++-------- 8 files changed, 414 insertions(+), 400 deletions(-) diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/source/aws_iot_demo_shadow.c index bcade38183..b288d3bb18 100644 --- a/demos/source/aws_iot_demo_shadow.c +++ b/demos/source/aws_iot_demo_shadow.c @@ -338,11 +338,11 @@ static void _shadowDeltaCallback( void * pCallbackContext, * callback will report if the Shadow was successfully updated. Because the * Shadow is constantly updated in this demo, the "Keep Subscriptions" flag * is passed to this function. */ - updateStatus = AwsIotShadow_Update( pCallbackParam->mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - NULL, - NULL ); + updateStatus = AwsIotShadow_UpdateAsync( pCallbackParam->mqttConnection, + &updateDocument, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, + NULL, + NULL ); if( updateStatus != AWS_IOT_SHADOW_STATUS_PENDING ) { @@ -646,11 +646,11 @@ static void _clearShadowDocument( IotMqttConnection_t mqttConnection, /* Delete any existing Shadow document so that this demo starts with an empty * Shadow. */ - deleteStatus = AwsIotShadow_TimedDelete( mqttConnection, - pThingName, - thingNameLength, - 0, - TIMEOUT_MS ); + deleteStatus = AwsIotShadow_DeleteSync( mqttConnection, + pThingName, + thingNameLength, + 0, + TIMEOUT_MS ); /* Check for return values of "SUCCESS" and "NOT FOUND". Both of these values * mean that the Shadow document is now empty. */ @@ -741,10 +741,10 @@ static int _sendShadowUpdates( IotSemaphore_t * pDeltaSemaphore, * Note that this flag only needs to be passed on the first call, but * passing it for subsequent calls is fine. */ - updateStatus = AwsIotShadow_TimedUpdate( mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - TIMEOUT_MS ); + updateStatus = AwsIotShadow_UpdateSync( mqttConnection, + &updateDocument, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, + TIMEOUT_MS ); /* Check the status of the Shadow update. */ if( updateStatus != AWS_IOT_SHADOW_SUCCESS ) diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 9134d64c3d..29618edbca 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -51,7 +51,7 @@ In addition to the components above, the Shadow library also depends on C standa @page shadow_demo Demo @brief The Shadow demo demonstrates usage of the Shadow library. -The demo program uses [Shadow updates](@ref shadow_function_update) and [delta callbacks](@ref shadow_function_setdeltacallback) to simulate toggling a remote device's state. It sends a Shadow update with a new desired state and waits for the device to change its reported state in response to the new desired state. In addition, a [Shadow updated callback](@ref shadow_function_setupdatedcallback) is used to print the changing Shadow states. +The demo program uses [Shadow updates](@ref shadow_function_updateasync) and [delta callbacks](@ref shadow_function_setdeltacallback) to simulate toggling a remote device's state. It sends a Shadow update with a new desired state and waits for the device to change its reported state in response to the new desired state. In addition, a [Shadow updated callback](@ref shadow_function_setupdatedcallback) is used to print the changing Shadow states. See @subpage shadow_demo_config for configuration settings that change the behavior of the demo. diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index ef7388d523..35b0a08a6d 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -39,12 +39,12 @@ * @functionspage{shadow,Shadow library} * - @functionname{shadow_function_init} * - @functionname{shadow_function_cleanup} - * - @functionname{shadow_function_delete} - * - @functionname{shadow_function_timeddelete} - * - @functionname{shadow_function_get} - * - @functionname{shadow_function_timedget} - * - @functionname{shadow_function_update} - * - @functionname{shadow_function_timedupdate} + * - @functionname{shadow_function_deleteasync} + * - @functionname{shadow_function_deletesync} + * - @functionname{shadow_function_getasync} + * - @functionname{shadow_function_getsync} + * - @functionname{shadow_function_updateasync} + * - @functionname{shadow_function_updatesync} * - @functionname{shadow_function_wait} * - @functionname{shadow_function_setdeltacallback} * - @functionname{shadow_function_setupdatedcallback} @@ -55,12 +55,12 @@ /** * @functionpage{AwsIotShadow_Init,shadow,init} * @functionpage{AwsIotShadow_Cleanup,shadow,cleanup} - * @functionpage{AwsIotShadow_Delete,shadow,delete} - * @functionpage{AwsIotShadow_TimedDelete,shadow,timeddelete} - * @functionpage{AwsIotShadow_Get,shadow,get} - * @functionpage{AwsIotShadow_TimedGet,shadow,timedget} - * @functionpage{AwsIotShadow_Update,shadow,update} - * @functionpage{AwsIotShadow_TimedUpdate,shadow,timedupdate} + * @functionpage{AwsIotShadow_DeleteAsync,shadow,deleteasync} + * @functionpage{AwsIotShadow_DeleteSync,shadow,deletesync} + * @functionpage{AwsIotShadow_GetAsync,shadow,getasync} + * @functionpage{AwsIotShadow_GetSync,shadow,getsync} + * @functionpage{AwsIotShadow_UpdateAsync,shadow,updateasync} + * @functionpage{AwsIotShadow_UpdateSync,shadow,updatesync} * @functionpage{AwsIotShadow_Wait,shadow,wait} * @functionpage{AwsIotShadow_SetDeltaCallback,shadow,setdeltacallback} * @functionpage{AwsIotShadow_SetUpdatedCallback,shadow,setupdatedcallback} @@ -142,7 +142,7 @@ void AwsIotShadow_Cleanup( void ); * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) * - * @see @ref shadow_function_timeddelete for a blocking variant of this function. + * @see @ref shadow_function_deletesync for a blocking variant of this function. * * Example * @code{c} @@ -153,12 +153,12 @@ void AwsIotShadow_Cleanup( void ); * AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; * * // Queue a Shadow delete. - * AwsIotShadowError_t deleteResult = AwsIotShadow_Delete( mqttConnection, - * THING_NAME, - * THING_NAME_LENGTH, - * AWS_IOT_SHADOW_FLAG_WAITABLE, - * NULL, - * &deleteOperation ); + * AwsIotShadowError_t deleteResult = AwsIotShadow_DeleteAsync( mqttConnection, + * THING_NAME, + * THING_NAME_LENGTH, + * AWS_IOT_SHADOW_FLAG_WAITABLE, + * NULL, + * &deleteOperation ); * * // Shadow delete should return AWS_IOT_SHADOW_STATUS_PENDING upon success. * if( deleteResult == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -171,21 +171,21 @@ void AwsIotShadow_Cleanup( void ); * } * @endcode */ -/* @[declare_shadow_delete] */ -AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pDeleteOperation ); -/* @[declare_shadow_delete] */ +/* @[declare_shadow_deleteasync] */ +AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowOperation_t * const pDeleteOperation ); +/* @[declare_shadow_deleteasync] */ /** * @brief Delete a Thing Shadow with a timeout. * * This function queues a Shadow delete, then waits for the result. Internally, this - * function is a call to @ref shadow_function_delete followed by @ref shadow_function_wait. - * See @ref shadow_function_delete for more information on the Shadow delete operation. + * function is a call to @ref shadow_function_deleteasync followed by @ref shadow_function_wait. + * See @ref shadow_function_deleteasync for more information on the Shadow delete operation. * * @param[in] mqttConnection The MQTT connection to use for Shadow delete. * @param[in] pThingName The Thing Name associated with the Shadow to delete. @@ -204,13 +204,13 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) */ -/* @[declare_shadow_timeddelete] */ -AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_shadow_timeddelete] */ +/* @[declare_shadow_deletesync] */ +AwsIotShadowError_t AwsIotShadow_DeleteSync( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_shadow_deletesync] */ /** * @brief Retrieve a Thing Shadow and receive an asynchronous notification when @@ -260,7 +260,7 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) * - * @see @ref shadow_function_timedget for a blocking variant of this function. + * @see @ref shadow_function_getsync for a blocking variant of this function. * * Example * @code{c} @@ -280,11 +280,11 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection * getCallback.function = _processRetrievedDocument; * * // Shadow get operation. - * result = AwsIotShadow_Get( mqttConnection, - * &getInfo, - * 0, - * &getCallback, - * NULL ); + * result = AwsIotShadow_GetAsync( mqttConnection, + * &getInfo, + * 0, + * &getCallback, + * NULL ); * * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The function * // _processRetrievedDocument will be invoked once the Shadow get completes. @@ -293,20 +293,20 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection * See @ref shadow_function_wait Example 2 for an example of using this * function with #AWS_IOT_SHADOW_FLAG_WAITABLE and @ref shadow_function_wait. */ -/* @[declare_shadow_get] */ -AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pGetOperation ); -/* @[declare_shadow_get] */ +/* @[declare_shadow_getasync] */ +AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pGetInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowOperation_t * const pGetOperation ); +/* @[declare_shadow_getasync] */ /** * @brief Retrieve a Thing Shadow with a timeout. * * This function queues a Shadow get, then waits for the result. Internally, this - * function is a call to @ref shadow_function_get followed by @ref shadow_function_wait. - * See @ref shadow_function_get for more information on the Shadow get operation. + * function is a call to @ref shadow_function_getasync followed by @ref shadow_function_wait. + * See @ref shadow_function_getasync for more information on the Shadow get operation. * * @param[in] mqttConnection The MQTT connection to use for Shadow get. * @param[in] pGetInfo Shadow document parameters. @@ -331,14 +331,14 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) */ -/* @[declare_shadow_timedget] */ -AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - uint32_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ); -/* @[declare_shadow_timedget] */ +/* @[declare_shadow_getsync] */ +AwsIotShadowError_t AwsIotShadow_GetSync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pGetInfo, + uint32_t flags, + uint32_t timeoutMs, + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ); +/* @[declare_shadow_getsync] */ /** * @brief Send a Thing Shadow update and receive an asynchronous notification when @@ -393,7 +393,7 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) * - * @see @ref shadow_function_timedupdate for a blocking variant of this function. + * @see @ref shadow_function_updatesync for a blocking variant of this function. * * Example * @code{c} @@ -415,11 +415,11 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, * updateCallback.function = _updateComplete; * * // Shadow update operation. - * result = AwsIotShadow_Update( mqttConnection, - * &updateInfo, - * 0, - * &updateCallback, - * NULL ); + * result = AwsIotShadow_UpdateAsync( mqttConnection, + * &updateInfo, + * 0, + * &updateCallback, + * NULL ); * * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The function * // _updateComplete will be invoked once the Shadow update completes. @@ -428,20 +428,20 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, * See @ref shadow_function_wait Example 1 for an example of using this * function with #AWS_IOT_SHADOW_FLAG_WAITABLE and @ref shadow_function_wait. */ -/* @[declare_shadow_update] */ -AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pUpdateOperation ); -/* @[declare_shadow_update] */ +/* @[declare_shadow_updateasync] */ +AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowOperation_t * const pUpdateOperation ); +/* @[declare_shadow_updateasync] */ /** * @brief Send a Thing Shadow update with a timeout. * * This function queues a Shadow update, then waits for the result. Internally, this - * function is a call to @ref shadow_function_update followed by @ref shadow_function_wait. - * See @ref shadow_function_update for more information on the Shadow update operation. + * function is a call to @ref shadow_function_updateasync followed by @ref shadow_function_wait. + * See @ref shadow_function_updateasync for more information on the Shadow update operation. * * @param[in] mqttConnection The MQTT connection to use for Shadow update. * @param[in] pUpdateInfo Shadow document parameters. @@ -459,18 +459,18 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, * - A Shadow service rejection reason between 400 (#AWS_IOT_SHADOW_BAD_REQUEST) * and 500 (#AWS_IOT_SHADOW_SERVER_ERROR) */ -/* @[declare_shadow_timedupdate] */ -AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs ); -/* @[declare_shadow_timedupdate] */ +/* @[declare_shadow_updatesync] */ +AwsIotShadowError_t AwsIotShadow_UpdateSync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs ); +/* @[declare_shadow_updatesync] */ /** * @brief Wait for a Shadow operation to complete. * - * This function blocks to wait for a [delete](@ref shadow_function_delete), - * [get](@ref shadow_function_get), or [update](@ref shadow_function_update) to + * This function blocks to wait for a [delete](@ref shadow_function_deleteasync), + * [get](@ref shadow_function_getasync), or [update](@ref shadow_function_updateasync) to * complete. These operations are by default asynchronous; the function calls * queue an operation for processing, and a callback is invoked once the operation * is complete. @@ -488,14 +488,14 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * #AWS_IOT_SHADOW_FLAG_WAITABLE must have been set for this operation. * @param[in] timeoutMs How long to wait before returning #AWS_IOT_SHADOW_TIMEOUT. * @param[out] pShadowDocument A pointer to a buffer containing the Shadow document - * retrieved by a [Shadow get](@ref shadow_function_get) is placed here. The buffer + * retrieved by a [Shadow get](@ref shadow_function_getasync) is placed here. The buffer * was allocated with the function #AwsIotShadowDocumentInfo_t.mallocDocument passed - * to @ref shadow_function_get. This parameter is only valid for a [Shadow get] - * (@ref shadow_function_get) and ignored for other Shadow operations. This output + * to @ref shadow_function_getasync. This parameter is only valid for a [Shadow get] + * (@ref shadow_function_getasync) and ignored for other Shadow operations. This output * parameter is only valid if this function returns #AWS_IOT_SHADOW_SUCCESS. * @param[out] pShadowDocumentLength The length of the Shadow document in * `pShadowDocument` is placed here. This parameter is only valid for a [Shadow get] - * (@ref shadow_function_get) and ignored for other Shadow operations. This output + * (@ref shadow_function_getasync) and ignored for other Shadow operations. This output * parameter is only valid if this function returns #AWS_IOT_SHADOW_SUCCESS. * * @return One of the following: @@ -516,11 +516,11 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * uint32_t timeout = 5000; // 5 seconds * * // Shadow update operation. - * result = AwsIotShadow_Update( mqttConnection, - * &updateInfo, - * AWS_IOT_SHADOW_FLAG_WAITABLE, - * NULL, - * &updateOperation ); + * result = AwsIotShadow_UpdateAsync( mqttConnection, + * &updateInfo, + * AWS_IOT_SHADOW_FLAG_WAITABLE, + * NULL, + * &updateOperation ); * * // Update should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait * // returns once the result of the update is available or the timeout expires. @@ -552,11 +552,11 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection * getInfo.get.mallocDocument = malloc; * * // Shadow get operation. - * result = AwsIotShadow_Get( mqttConnection, - * &getInfo, - * AWS_IOT_SHADOW_FLAG_WAITABLE, - * NULL, - * &getOperation ); + * result = AwsIotShadow_GetAsync( mqttConnection, + * &getInfo, + * AWS_IOT_SHADOW_FLAG_WAITABLE, + * NULL, + * &getOperation ); * * // Get should have returned AWS_IOT_SHADOW_STATUS_PENDING. The call to wait * // returns once the result of the get is available or the timeout expires. @@ -680,11 +680,11 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); * * // Send the Shadow document with different "reported" and desired states. - * result = AwsIotShadow_TimedUpdate( mqttConnection, - * &updateInfo, - * 0, - * 0, - * 5000 ); + * result = AwsIotShadow_UpdateSync( mqttConnection, + * &updateInfo, + * 0, + * 0, + * 5000 ); * * // After the update is successfully sent, the function _deltaCallbackFunction * // will be invoked once the Shadow service generates and sends a delta document. @@ -792,11 +792,11 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConne * updateInfo.update.updateDocumentLength = strlen( updateInfo.update.pUpdateDocument ); * * // Send the Shadow document. A successful update will trigger the updated callback. - * result = AwsIotShadow_TimedUpdate( mqttConnection, - * &updateInfo, - * 0, - * 0, - * 5000 ); + * result = AwsIotShadow_UpdateSync( mqttConnection, + * &updateInfo, + * 0, + * 0, + * 5000 ); * * // After a successful Shadow update, the updated callback will be invoked. * @@ -824,8 +824,8 @@ AwsIotShadowError_t AwsIotShadow_SetUpdatedCallback( IotMqttConnection_t mqttCon /** * @brief Remove persistent Thing Shadow operation topic subscriptions. * - * Passing the flag @ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS to @ref shadow_function_delete, - * @ref shadow_function_get, @ref shadow_function_update, or their blocking versions. + * Passing the flag @ref AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS to @ref shadow_function_deleteasync, + * @ref shadow_function_getasync, @ref shadow_function_updateasync, or their blocking versions. * causes the Shadow operation topic subscriptions to be maintained for future calls to the * same function. If a persistent subscription for a Shadow topic are no longer needed, * this function may be used to remove it. @@ -882,4 +882,18 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio const char * AwsIotShadow_strerror( AwsIotShadowError_t status ); /* @[declare_shadow_strerror] */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Backwards compatibility macros for previous function names. + */ +#define AwsIotShadow_Delete AwsIotShadow_DeleteAsync +#define AwsIotShadow_TimedDelete AwsIotShadow_DeleteSync +#define AwsIotShadow_Get AwsIotShadow_GetAsync +#define AwsIotShadow_TimedGet AwsIotShadow_GetSync +#define AwsIotShadow_Update AwsIotShadow_UpdateAsync +#define AwsIotShadow_TimedUpdate AwsIotShadow_UpdateSync +/** @endcond */ + #endif /* ifndef AWS_IOT_SHADOW_H_ */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 9308b6d34c..2fdc7701ba 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -240,9 +240,9 @@ typedef enum _shadowOperationType { /* Shadow operations. */ - SHADOW_DELETE = 0, /**< @ref shadow_function_delete */ - SHADOW_GET = 1, /**< @ref shadow_function_get */ - SHADOW_UPDATE = 2, /**< @ref shadow_function_update */ + SHADOW_DELETE = 0, /**< @ref shadow_function_deleteasync */ + SHADOW_GET = 1, /**< @ref shadow_function_getasync */ + SHADOW_UPDATE = 2, /**< @ref shadow_function_updateasync */ /* Shadow callbacks. */ SET_DELTA_CALLBACK = 3, /**< @ref shadow_function_setdeltacallback */ diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index d6339a253e..f039b7be2d 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -43,14 +43,14 @@ * @ingroup shadow_datatypes_handles * @brief Opaque handle that references an in-progress Shadow operation. * - * Set as an output parameter of @ref shadow_function_delete, @ref shadow_function_get, - * and @ref shadow_function_update. These functions send a message to the Shadow + * Set as an output parameter of @ref shadow_function_deleteasync, @ref shadow_function_getasync, + * and @ref shadow_function_updateasync. These functions send a message to the Shadow * service requesting a Shadow operation; the result of this operation is unknown * until the Shadow service sends a response. Therefore, this handle serves as a * reference to Shadow operations awaiting a response from the Shadow service. * - * This reference will be valid from the successful return of @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update. The reference becomes + * This reference will be valid from the successful return of @ref shadow_function_deleteasync, + * @ref shadow_function_getasync, or @ref shadow_function_updateasync. The reference becomes * invalid once the [completion callback](@ref AwsIotShadowCallbackInfo_t) is invoked, * or @ref shadow_function_wait returns. * @@ -89,9 +89,9 @@ typedef enum AwsIotShadowError * Functions that may return this value: * - @ref shadow_function_init * - @ref shadow_function_wait - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback * - @ref shadow_function_removepersistentsubscriptions @@ -106,9 +106,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation queued, awaiting result. * * Functions that may return this value: - * - @ref shadow_function_delete - * - @ref shadow_function_get - * - @ref shadow_function_update + * - @ref shadow_function_deleteasync + * - @ref shadow_function_getasync + * - @ref shadow_function_updateasync */ AWS_IOT_SHADOW_STATUS_PENDING, @@ -124,9 +124,9 @@ typedef enum AwsIotShadowError * @brief At least one parameter is invalid. * * Functions that may return this value: - * - @ref shadow_function_delete and @ref shadow_function_timeddelete - * - @ref shadow_function_get and @ref shadow_function_timedget - * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync + * - @ref shadow_function_getasync and @ref shadow_function_getsync + * - @ref shadow_function_updateasync and @ref shadow_function_updatesync * - @ref shadow_function_wait * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback @@ -137,9 +137,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation failed because of memory allocation failure. * * Functions that may return this value: - * - @ref shadow_function_delete and @ref shadow_function_timeddelete - * - @ref shadow_function_get and @ref shadow_function_timedget - * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync + * - @ref shadow_function_getasync and @ref shadow_function_getsync + * - @ref shadow_function_updateasync and @ref shadow_function_updatesync * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback */ @@ -152,9 +152,9 @@ typedef enum AwsIotShadowError * library. * * Functions that may return this value: - * - @ref shadow_function_delete and @ref shadow_function_timeddelete - * - @ref shadow_function_get and @ref shadow_function_timedget - * - @ref shadow_function_update and @ref shadow_function_timedupdate + * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync + * - @ref shadow_function_getasync and @ref shadow_function_getsync + * - @ref shadow_function_updateasync and @ref shadow_function_updatesync * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback * - @ref shadow_function_removepersistentsubscriptions @@ -165,9 +165,9 @@ typedef enum AwsIotShadowError * @brief Reponse received from Shadow service not understood. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -179,9 +179,9 @@ typedef enum AwsIotShadowError * @brief A blocking Shadow operation timed out. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback @@ -192,9 +192,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation rejected: Bad request. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -206,9 +206,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation rejected: Unauthorized. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -220,9 +220,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation rejected: Forbidden. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -234,9 +234,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation rejected: Thing not found. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -248,9 +248,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation rejected: Version conflict. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -263,9 +263,9 @@ typedef enum AwsIotShadowError * allowed. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -278,9 +278,9 @@ typedef enum AwsIotShadowError * encoding is UTF-8. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -293,9 +293,9 @@ typedef enum AwsIotShadowError * this error message when there are more than 10 in-flight requests. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -307,9 +307,9 @@ typedef enum AwsIotShadowError * @brief Shadow operation rejected: Internal service failure. * * Functions that may return this value: - * - @ref shadow_function_timeddelete - * - @ref shadow_function_timedget - * - @ref shadow_function_timedupdate + * - @ref shadow_function_deletesync + * - @ref shadow_function_getsync + * - @ref shadow_function_updatesync * - @ref shadow_function_wait * * May also be the value of a Shadow operation completion callback's
@@ -327,9 +327,9 @@ typedef enum AwsIotShadowError */ typedef enum AwsIotShadowCallbackType { - AWS_IOT_SHADOW_DELETE_COMPLETE, /**< Callback invoked because a [Shadow delete](@ref shadow_function_delete) completed. */ - AWS_IOT_SHADOW_GET_COMPLETE, /**< Callback invoked because a [Shadow get](@ref shadow_function_get) completed. */ - AWS_IOT_SHADOW_UPDATE_COMPLETE, /**< Callback invoked because a [Shadow update](@ref shadow_function_update) completed. */ + AWS_IOT_SHADOW_DELETE_COMPLETE, /**< Callback invoked because a [Shadow delete](@ref shadow_function_deleteasync) completed. */ + AWS_IOT_SHADOW_GET_COMPLETE, /**< Callback invoked because a [Shadow get](@ref shadow_function_getasync) completed. */ + AWS_IOT_SHADOW_UPDATE_COMPLETE, /**< Callback invoked because a [Shadow update](@ref shadow_function_updateasync) completed. */ AWS_IOT_SHADOW_DELTA_CALLBACK, /**< Callback invoked for an incoming message on a [Shadow delta](@ref shadow_function_setdeltacallback) topic. */ AWS_IOT_SHADOW_UPDATED_CALLBACK /**< Callback invoked for an incoming message on a [Shadow updated](@ref shadow_function_setupdatedcallback) topic. */ } AwsIotShadowCallbackType_t; @@ -384,7 +384,7 @@ typedef struct AwsIotShadowCallbackParam { const char * pDocument; /**< @brief Retrieved Shadow document. */ size_t documentLength; /**< @brief Length of retrieved Shadow document. */ - } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_get). */ + } get; /**< @brief Retrieved Shadow document, valid only for a completed [Shadow Get](@ref shadow_function_getasync). */ AwsIotShadowError_t result; /**< @brief Result of Shadow operation, e.g. succeeded or failed. */ AwsIotShadowOperation_t reference; /**< @brief Reference to the Shadow operation that completed. */ @@ -403,8 +403,8 @@ typedef struct AwsIotShadowCallbackParam * @ingroup shadow_datatypes_paramstructs * @brief Information on a user-provided Shadow callback function. * - * @paramfor @ref shadow_function_delete, @ref shadow_function_get, @ref - * shadow_function_update, @ref shadow_function_setdeltacallback, @ref + * @paramfor @ref shadow_function_deleteasync, @ref shadow_function_getasync, @ref + * shadow_function_updateasync, @ref shadow_function_setdeltacallback, @ref * shadow_function_setupdatedcallback * * Provides a function to be invoked when a Shadow operation completes or when a @@ -431,15 +431,15 @@ typedef struct AwsIotShadowCallbackInfo /** * @ingroup shadow_datatypes_paramstructs - * @brief Information on a Shadow document for @ref shadow_function_get or @ref - * shadow_function_update. + * @brief Information on a Shadow document for @ref shadow_function_getasync or @ref + * shadow_function_updateasync. * - * @paramfor @ref shadow_function_get, @ref shadow_function_update + * @paramfor @ref shadow_function_getasync, @ref shadow_function_updateasync * * The valid members of this struct are different based on whether this struct - * is passed to @ref shadow_function_get or @ref shadow_function_update. When - * passed to @ref shadow_function_get, the `get` member is valid. When passed to - * @ref shadow_function_update, the `update` member is valid. All other members + * is passed to @ref shadow_function_getasync or @ref shadow_function_updateasync. When + * passed to @ref shadow_function_getasync, the `get` member is valid. When passed to + * @ref shadow_function_updateasync, the `update` member is valid. All other members * must always be set. * * @initializer{AwsIotShadowDocumentInfo_t,AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER} @@ -462,17 +462,17 @@ typedef struct AwsIotShadowDocumentInfo * @brief Function to allocate memory for an incoming Shadow document. * * This only needs to be set if #AWS_IOT_SHADOW_FLAG_WAITABLE is passed to - * @ref shadow_function_get. + * @ref shadow_function_getasync. */ void *( *mallocDocument )( size_t ); - } get; /**< @brief Valid members for @ref shadow_function_get. */ + } get; /**< @brief Valid members for @ref shadow_function_getasync. */ /* Valid for Shadow update. */ struct { const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ - } update; /**< @brief Valid members for @ref shadow_function_update. */ + } update; /**< @brief Valid members for @ref shadow_function_updateasync. */ } u; /**< @brief Valid member depends on operation type. */ } AwsIotShadowDocumentInfo_t; @@ -508,7 +508,7 @@ typedef struct AwsIotShadowDocumentInfo * Shadow library functions. * * The following flags are valid for the Shadow operation functions: - * @ref shadow_function_delete, @ref shadow_function_get, @ref shadow_function_update, + * @ref shadow_function_deleteasync, @ref shadow_function_getasync, @ref shadow_function_updateasync, * and their blocking versions. * - #AWS_IOT_SHADOW_FLAG_WAITABLE
* @copybrief AWS_IOT_SHADOW_FLAG_WAITABLE @@ -538,8 +538,8 @@ typedef struct AwsIotShadowDocumentInfo /** * @brief Allows the use of @ref shadow_function_wait for blocking until completion. * - * This flag is only valid if passed to the functions @ref shadow_function_delete, - * @ref shadow_function_get, or @ref shadow_function_update. + * This flag is only valid if passed to the functions @ref shadow_function_deleteasync, + * @ref shadow_function_getasync, or @ref shadow_function_updateasync. * * An #AwsIotShadowOperation_t MUST be provided if this flag is set. * Additionally, an #AwsIotShadowCallbackInfo_t MUST NOT be provided. @@ -553,20 +553,20 @@ typedef struct AwsIotShadowDocumentInfo * @brief Maintain the subscriptions for the Shadow operation topics, even after * this function returns. * - * This flag is only valid if passed to the functions @ref shadow_function_delete, - * @ref shadow_function_get, @ref shadow_function_update, or their blocking versions. + * This flag is only valid if passed to the functions @ref shadow_function_deleteasync, + * @ref shadow_function_getasync, @ref shadow_function_updateasync, or their blocking versions. * * The Shadow service reports results of Shadow operations by publishing - * messages to MQTT topics. By default, the functions @ref shadow_function_delete, - * @ref shadow_function_get, and @ref shadow_function_update subscribe to the + * messages to MQTT topics. By default, the functions @ref shadow_function_deleteasync, + * @ref shadow_function_getasync, and @ref shadow_function_updateasync subscribe to the * necessary topics, wait for the Shadow service to publish the result of the * Shadow operation, then unsubscribe from those topics. This workflow is suitable * for infrequent Shadow operations, but is inefficient for frequent, periodic * Shadow operations (where subscriptions for the Shadow operation topics would be * constantly added and removed). * - * This flag causes @ref shadow_function_delete, @ref shadow_function_get, or - * @ref shadow_function_update to maintain Shadow operation topic subscriptions, + * This flag causes @ref shadow_function_deleteasync, @ref shadow_function_getasync, or + * @ref shadow_function_updateasync to maintain Shadow operation topic subscriptions, * even after the function returns. These subscriptions may then be used by a * future call to the same function. * @@ -585,7 +585,7 @@ typedef struct AwsIotShadowDocumentInfo * * This flag may be passed to @ref shadow_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_delete or @ref shadow_function_timeddelete. + * call to @ref shadow_function_deleteasync or @ref shadow_function_deletesync. * * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow delete operations. @@ -600,7 +600,7 @@ typedef struct AwsIotShadowDocumentInfo * * This flag may be passed to @ref shadow_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_get or @ref shadow_function_timedget. + * call to @ref shadow_function_getasync or @ref shadow_function_getsync. * * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow get operations. @@ -615,7 +615,7 @@ typedef struct AwsIotShadowDocumentInfo * * This flag may be passed to @ref shadow_function_removepersistentsubscriptions * to remove any subscriptions for a specific Thing Name maintained by a previous - * call to @ref shadow_function_update or @ref shadow_function_timedupdate. + * call to @ref shadow_function_updateasync or @ref shadow_function_updatesync. * * @warning Do not call @ref shadow_function_removepersistentsubscriptions with * this flag for Thing Names with any in-progress Shadow update operations. diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 784d588cf0..ea5a7ea56e 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -699,12 +699,12 @@ void AwsIotShadow_Cleanup( void ) /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pDeleteOperation ) +AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowOperation_t * const pDeleteOperation ) { IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; @@ -767,11 +767,11 @@ AwsIotShadowError_t AwsIotShadow_Delete( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection, - const char * pThingName, - size_t thingNameLength, - uint32_t flags, - uint32_t timeoutMs ) +AwsIotShadowError_t AwsIotShadow_DeleteSync( IotMqttConnection_t mqttConnection, + const char * pThingName, + size_t thingNameLength, + uint32_t flags, + uint32_t timeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowOperation_t deleteOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -780,12 +780,12 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; /* Call the asynchronous Shadow delete function. */ - status = AwsIotShadow_Delete( mqttConnection, - pThingName, - thingNameLength, - flags, - NULL, - &deleteOperation ); + status = AwsIotShadow_DeleteAsync( mqttConnection, + pThingName, + thingNameLength, + flags, + NULL, + &deleteOperation ); /* Wait for the Shadow delete operation to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -801,11 +801,11 @@ AwsIotShadowError_t AwsIotShadow_TimedDelete( IotMqttConnection_t mqttConnection /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pGetOperation ) +AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pGetInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowOperation_t * const pGetOperation ) { IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; @@ -882,12 +882,12 @@ AwsIotShadowError_t AwsIotShadow_Get( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pGetInfo, - uint32_t flags, - uint32_t timeoutMs, - const char ** const pShadowDocument, - size_t * const pShadowDocumentLength ) +AwsIotShadowError_t AwsIotShadow_GetSync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pGetInfo, + uint32_t flags, + uint32_t timeoutMs, + const char ** const pShadowDocument, + size_t * const pShadowDocumentLength ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowOperation_t getOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -896,11 +896,11 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; /* Call the asynchronous Shadow get function. */ - status = AwsIotShadow_Get( mqttConnection, - pGetInfo, - flags, - NULL, - &getOperation ); + status = AwsIotShadow_GetAsync( mqttConnection, + pGetInfo, + flags, + NULL, + &getOperation ); /* Wait for the Shadow get operation to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -919,11 +919,11 @@ AwsIotShadowError_t AwsIotShadow_TimedGet( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - const AwsIotShadowCallbackInfo_t * pCallbackInfo, - AwsIotShadowOperation_t * const pUpdateOperation ) +AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, + uint32_t flags, + const AwsIotShadowCallbackInfo_t * pCallbackInfo, + AwsIotShadowOperation_t * const pUpdateOperation ) { IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; @@ -1028,10 +1028,10 @@ AwsIotShadowError_t AwsIotShadow_Update( IotMqttConnection_t mqttConnection, /*-----------------------------------------------------------*/ -AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection, - const AwsIotShadowDocumentInfo_t * pUpdateInfo, - uint32_t flags, - uint32_t timeoutMs ) +AwsIotShadowError_t AwsIotShadow_UpdateSync( IotMqttConnection_t mqttConnection, + const AwsIotShadowDocumentInfo_t * pUpdateInfo, + uint32_t flags, + uint32_t timeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowOperation_t updateOperation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -1040,11 +1040,11 @@ AwsIotShadowError_t AwsIotShadow_TimedUpdate( IotMqttConnection_t mqttConnection flags |= AWS_IOT_SHADOW_FLAG_WAITABLE; /* Call the asynchronous Shadow update function. */ - status = AwsIotShadow_Update( mqttConnection, - pUpdateInfo, - flags, - NULL, - &updateOperation ); + status = AwsIotShadow_UpdateAsync( mqttConnection, + pUpdateInfo, + flags, + NULL, + &updateOperation ); /* Wait for the Shadow update operation to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index f8a554a1bb..501a1b3231 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -303,11 +303,11 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) documentInfo.u.update.updateDocumentLength = TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ - status = AwsIotShadow_Update( _mqttConnection, - &documentInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotShadow_UpdateAsync( _mqttConnection, + &documentInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) @@ -319,11 +319,11 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) callbackParam.expectedType = AWS_IOT_SHADOW_GET_COMPLETE; /* Retrieve the Shadow document. */ - status = AwsIotShadow_Get( _mqttConnection, - &documentInfo, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotShadow_GetAsync( _mqttConnection, + &documentInfo, + 0, + &callbackInfo, + &( callbackParam.operation ) ); TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_STATUS_PENDING, status ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), @@ -336,12 +336,12 @@ static void _updateGetDeleteAsync( IotMqttQos_t qos ) callbackParam.expectedType = AWS_IOT_SHADOW_DELETE_COMPLETE; /* Delete the Shadow document. */ - status = AwsIotShadow_Delete( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - &callbackInfo, - &( callbackParam.operation ) ); + status = AwsIotShadow_DeleteAsync( _mqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + THING_NAME_LENGTH, + 0, + &callbackInfo, + &( callbackParam.operation ) ); if( IotSemaphore_TimedWait( &( callbackParam.waitSem ), AWS_IOT_TEST_SHADOW_TIMEOUT ) == false ) @@ -375,22 +375,22 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) documentInfo.u.update.updateDocumentLength = TEST_SHADOW_DOCUMENT_LENGTH; /* Create a new Shadow document. */ - status = AwsIotShadow_TimedUpdate( _mqttConnection, - &documentInfo, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); + status = AwsIotShadow_UpdateSync( _mqttConnection, + &documentInfo, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Set the members of the Shadow document info for GET. */ documentInfo.u.get.mallocDocument = IotTest_Malloc; /* Retrieve the Shadow document. */ - status = AwsIotShadow_TimedGet( _mqttConnection, - &documentInfo, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT, - &pShadowDocument, - &shadowDocumentLength ); + status = AwsIotShadow_GetSync( _mqttConnection, + &documentInfo, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT, + &pShadowDocument, + &shadowDocumentLength ); TEST_ASSERT_EQUAL_INT( AWS_IOT_SHADOW_SUCCESS, status ); /* Check the retrieved Shadow document. */ @@ -409,11 +409,11 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) IotTest_Free( ( void * ) pShadowDocument ); /* Delete the Shadow document. */ - status = AwsIotShadow_TimedDelete( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); + status = AwsIotShadow_DeleteSync( _mqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + THING_NAME_LENGTH, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); } @@ -503,11 +503,11 @@ TEST_SETUP( Shadow_System ) lastConnectTime = IotClock_GetTimeMs(); /* Delete any existing Shadow so all tests start with no Shadow. */ - status = AwsIotShadow_TimedDelete( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); + status = AwsIotShadow_DeleteSync( _mqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + THING_NAME_LENGTH, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); /* Acceptable statuses are SUCCESS and NOT FOUND. Both of these statuses allow * the tests to start with no Shadow. */ @@ -635,10 +635,10 @@ TEST( Shadow_System, DeltaCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Create a Shadow document with a desired state. */ - status = AwsIotShadow_TimedUpdate( _mqttConnection, - &updateDocument, - AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, - AWS_IOT_TEST_SHADOW_TIMEOUT ); + status = AwsIotShadow_UpdateSync( _mqttConnection, + &updateDocument, + AWS_IOT_SHADOW_FLAG_KEEP_SUBSCRIPTIONS, + AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Set a different reported state in the Update document. */ @@ -646,10 +646,10 @@ TEST( Shadow_System, DeltaCallback ) updateDocument.u.update.updateDocumentLength = 67; /* Create a Shadow document with a reported state. */ - status = AwsIotShadow_TimedUpdate( _mqttConnection, - &updateDocument, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); + status = AwsIotShadow_UpdateSync( _mqttConnection, + &updateDocument, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Block on the wait semaphore until the delta callback is invoked. */ @@ -713,10 +713,10 @@ TEST( Shadow_System, UpdatedCallback ) TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Create a Shadow document. */ - status = AwsIotShadow_TimedUpdate( _mqttConnection, - &updateDocument, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); + status = AwsIotShadow_UpdateSync( _mqttConnection, + &updateDocument, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, status ); /* Block on the wait semaphore until the updated callback is invoked. */ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index abf46bbb7c..d93df9ac43 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -182,73 +182,73 @@ TEST( Shadow_Unit_API, OperationInvalidParameters ) AwsIotShadowCallbackInfo_t callbackInfo = AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER; /* Missing Thing Name. */ - status = AwsIotShadow_Delete( _pMqttConnection, - NULL, - 0, - 0, - NULL, - NULL ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + NULL, + 0, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - status = AwsIotShadow_Delete( _pMqttConnection, - TEST_THING_NAME, - 0, - 0, - NULL, - NULL ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + TEST_THING_NAME, + 0, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); - status = AwsIotShadow_Update( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); + status = AwsIotShadow_UpdateAsync( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Thing Name too long. */ - status = AwsIotShadow_Delete( _pMqttConnection, - TEST_THING_NAME, - AWS_IOT_MAX_THING_NAME_LENGTH + 1, - 0, - NULL, - NULL ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + TEST_THING_NAME, + AWS_IOT_MAX_THING_NAME_LENGTH + 1, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* No reference with waitable operation. */ - status = AwsIotShadow_Delete( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - NULL ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Both callback and waitable flag set. */ - status = AwsIotShadow_Delete( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_WAITABLE, - &callbackInfo, - &operation ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_WAITABLE, + &callbackInfo, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* No callback for non-waitable GET. */ documentInfo.pThingName = TEST_THING_NAME; documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - 0, - NULL, - NULL ); + status = AwsIotShadow_GetAsync( _pMqttConnection, + &documentInfo, + 0, + NULL, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Callback function not set. */ - status = AwsIotShadow_Delete( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - 0, - &callbackInfo, - &operation ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + 0, + &callbackInfo, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); } @@ -265,70 +265,70 @@ TEST( Shadow_Unit_API, DocumentInvalidParameters ) AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; /* Missing Thing Name. */ - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotShadow_GetAsync( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); documentInfo.pThingName = TEST_THING_NAME; documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; /* Invalid QoS. */ documentInfo.qos = ( IotMqttQos_t ) 3; - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotShadow_GetAsync( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); documentInfo.qos = IOT_MQTT_QOS_0; /* Invalid retry parameters. */ documentInfo.retryLimit = 1; - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotShadow_GetAsync( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); documentInfo.retryLimit = 0; /* Waitable Shadow get with no memory allocation function. */ - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &operation ); + status = AwsIotShadow_GetAsync( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &operation ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Update with no document. */ - status = AwsIotShadow_Update( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); + status = AwsIotShadow_UpdateAsync( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Update with no client token. */ documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}}"; documentInfo.u.update.updateDocumentLength = 29; - status = AwsIotShadow_Update( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); + status = AwsIotShadow_UpdateAsync( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); /* Client token too long. */ documentInfo.u.update.pUpdateDocument = "{\"state\":{\"reported\":{null}}},\"clientToken\": " "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\""; documentInfo.u.update.updateDocumentLength = 146; - status = AwsIotShadow_Update( _pMqttConnection, - &documentInfo, - 0, - 0, - NULL ); + status = AwsIotShadow_UpdateAsync( _pMqttConnection, + &documentInfo, + 0, + 0, + NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_BAD_PARAMETER, status ); } @@ -360,7 +360,7 @@ TEST( Shadow_Unit_API, WaitInvalidParameters ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref shadow_function_delete when memory + * @brief Tests the behavior of @ref shadow_function_deleteasync when memory * allocation fails at various points. */ TEST( Shadow_Unit_API, DeleteMallocFail ) @@ -378,12 +378,12 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) /* Call Shadow DELETE. Memory allocation will fail at various times * during this call. */ - status = AwsIotShadow_Delete( _pMqttConnection, - TEST_THING_NAME, - TEST_THING_NAME_LENGTH, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &deleteOperation ); + status = AwsIotShadow_DeleteAsync( _pMqttConnection, + TEST_THING_NAME, + TEST_THING_NAME_LENGTH, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &deleteOperation ); /* Once the Shadow DELETE call succeeds, wait for it to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -415,7 +415,7 @@ TEST( Shadow_Unit_API, DeleteMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref shadow_function_get when memory + * @brief Tests the behavior of @ref shadow_function_getasync when memory * allocation fails at various points. */ TEST( Shadow_Unit_API, GetMallocFail ) @@ -442,11 +442,11 @@ TEST( Shadow_Unit_API, GetMallocFail ) /* Call Shadow GET. Memory allocation will fail at various times * during this call. */ - status = AwsIotShadow_Get( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &getOperation ); + status = AwsIotShadow_GetAsync( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &getOperation ); /* Once the Shadow GET call succeeds, wait for it to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) @@ -481,7 +481,7 @@ TEST( Shadow_Unit_API, GetMallocFail ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref shadow_function_update when memory + * @brief Tests the behavior of @ref shadow_function_updateasync when memory * allocation fails at various points. */ TEST( Shadow_Unit_API, UpdateMallocFail ) @@ -507,11 +507,11 @@ TEST( Shadow_Unit_API, UpdateMallocFail ) /* Call Shadow UPDATE. Memory allocation will fail at various times * during this call. */ - status = AwsIotShadow_Update( _pMqttConnection, - &documentInfo, - AWS_IOT_SHADOW_FLAG_WAITABLE, - NULL, - &updateOperation ); + status = AwsIotShadow_UpdateAsync( _pMqttConnection, + &documentInfo, + AWS_IOT_SHADOW_FLAG_WAITABLE, + NULL, + &updateOperation ); /* Once the Shadow UPDATE call succeeds, wait for it to complete. */ if( status == AWS_IOT_SHADOW_STATUS_PENDING ) From 5e58d63a872d2017f86828109b470254742fdb2b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 16 Sep 2019 10:11:51 -0700 Subject: [PATCH 255/844] Fix Windows DLL demo build (#560) --- CMakeLists.txt | 7 ++++++- platform/ports/win32/CMakeLists.txt | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eab7e0136f..a3cefdd62d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,12 @@ if( NOT DEFINED IOT_PLATFORM_NAME ) set( IOT_PLATFORM_NAME "win32" CACHE STRING "Port to use for building the SDK." ) # Export all symbols when building Windows DLLs. - set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE ) + if( ${BUILD_SHARED_LIBS} ) + set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS + TRUE CACHE BOOL + "Export all symbols for Windows DLLs. This option must by enabled." + FORCE ) + endif() endif() endif() diff --git a/platform/ports/win32/CMakeLists.txt b/platform/ports/win32/CMakeLists.txt index a89f549b70..bd89009797 100644 --- a/platform/ports/win32/CMakeLists.txt +++ b/platform/ports/win32/CMakeLists.txt @@ -19,5 +19,8 @@ target_sources( iotplatform INTERFACE # This platform layer uses mbed TLS. target_link_libraries( iotplatform INTERFACE mbedtls ) +# Link the Winsock2 library. +target_link_libraries( iotplatform INTERFACE ws2_32 ) + # Set platform sources in the parent scope for directory organization. set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) From 1f89a4872c5ea99a031795cdeec1b4831ae8e442 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 16 Sep 2019 15:11:12 -0700 Subject: [PATCH 256/844] Return error in MQTT if init is not called (#561) --- lib/include/iot_mqtt.h | 7 + lib/include/types/iot_mqtt_types.h | 11 + lib/source/mqtt/iot_mqtt_api.c | 357 ++++++++++++------- tests/mqtt/unit/iot_tests_mqtt_api.c | 101 ++++++ tests/shadow/unit/aws_iot_tests_shadow_api.c | 46 +++ 5 files changed, 401 insertions(+), 121 deletions(-) diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index 9fd1383291..e3efccfb25 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -185,6 +185,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR @@ -338,6 +339,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_SERVER_REFUSED * @return If this function fails before queuing a subscribe operation, it will return * one of: + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * @@ -449,6 +451,7 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR @@ -496,6 +499,7 @@ IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_BAD_RESPONSE * @return If this function fails before queuing an unsubscribe operation, it will return * one of: + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * @@ -532,6 +536,7 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR @@ -583,6 +588,7 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). * @return If this function fails before queuing an publish operation (regardless * of QoS), it will return one of: + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * @@ -663,6 +669,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_INIT_FAILED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 7d9b46a91b..509ad7979d 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -131,8 +131,19 @@ typedef enum IotMqttError /** * @brief Initialization failed. * + * This value is returned by @ref mqtt_function_init when initialization fails, or + * by other API functions if they are called before @ref mqtt_function_init. + * * Functions that may return this value: * - @ref mqtt_function_init + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribeasync + * - @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribeasync + * - @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishasync + * - @ref mqtt_function_publishsync + * - @ref mqtt_function_wait */ IOT_MQTT_INIT_FAILED, diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index f4d839d718..f4171bff15 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -40,6 +40,9 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* Atomics include. */ +#include "iot_atomic.h" + /* Validate MQTT configuration settings. */ #if IOT_MQTT_ENABLE_ASSERTS != 0 && IOT_MQTT_ENABLE_ASSERTS != 1 #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." @@ -59,6 +62,13 @@ /*-----------------------------------------------------------*/ +/** + * @brief Check if the library is initialized. + * + * @return `true` if IotMqtt_Init was called; `false` otherwise. + */ +static bool _checkInit( void ); + /** * @brief Set the unsubscribed flag of an MQTT subscription. * @@ -141,6 +151,35 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /*-----------------------------------------------------------*/ +/** + * @brief Tracks whether @ref mqtt_function_init has been called. + * + * API functions will fail if @ref mqtt_function_init was not called. + */ +static uint32_t _initCalled = 0; + +/*-----------------------------------------------------------*/ + +static bool _checkInit( void ) +{ + bool status = true; + + if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + { + IotLogError( "IotMqtt_Init was not called." ); + + status = false; + } + else + { + EMPTY_ELSE_MARKER; + } + + return status; +} + +/*-----------------------------------------------------------*/ + static bool _mqttSubscription_setUnsubscribe( const IotLink_t * pSubscriptionLink, void * pMatch ) { @@ -549,6 +588,16 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || ( operation == IOT_MQTT_UNSUBSCRIBE ) ); + /* Check that IotMqtt_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + EMPTY_ELSE_MARKER; + } + /* Check that all elements in the subscription list are valid. */ if( _IotMqtt_ValidateSubscriptionList( operation, mqttConnection->awsIotMqttMode, @@ -859,29 +908,39 @@ IotMqttError_t IotMqtt_Init( void ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Call any additional serializer initialization function if serializer - * overrides are enabled. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef _IotMqtt_InitSerializeAdditional - if( _IotMqtt_InitSerializeAdditional() == false ) - { - status = IOT_MQTT_INIT_FAILED; - } - else - { - EMPTY_ELSE_MARKER; - } - #endif - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - - /* Log initialization status. */ - if( status != IOT_MQTT_SUCCESS ) + if( _checkInit() == false ) { - IotLogError( "Failed to initialize MQTT library serializer. " ); + /* Call any additional serializer initialization function if serializer + * overrides are enabled. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_InitSerializeAdditional + if( _IotMqtt_InitSerializeAdditional() == false ) + { + status = IOT_MQTT_INIT_FAILED; + } + else + { + EMPTY_ELSE_MARKER; + } + #endif + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Log initialization status. */ + if( status != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to initialize MQTT library serializer. " ); + } + else + { + /* Set the flag that specifies initialization is complete. */ + ( void ) Atomic_CompareAndSwap_u32( &( _initCalled ), 1, 0 ); + + IotLogInfo( "MQTT library successfully initialized." ); + } } else { - IotLogInfo( "MQTT library successfully initialized." ); + IotLogWarn( "IotMqtt_Init called with library already initialized." ); } return status; @@ -891,15 +950,24 @@ IotMqttError_t IotMqtt_Init( void ) void IotMqtt_Cleanup( void ) { - /* Call any additional serializer cleanup initialization function if serializer - * overrides are enabled. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - #ifdef _IotMqtt_CleanupSerializeAdditional - _IotMqtt_CleanupSerializeAdditional(); + uint32_t initCleared = Atomic_CompareAndSwap_u32( &( _initCalled ), 0, 1 ); + + if( initCleared == 1 ) + { + /* Call any additional serializer cleanup initialization function if serializer + * overrides are enabled. */ + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + #ifdef _IotMqtt_CleanupSerializeAdditional + _IotMqtt_CleanupSerializeAdditional(); + #endif #endif - #endif - IotLogInfo( "MQTT library cleanup done." ); + IotLogInfo( "MQTT library cleanup done." ); + } + else + { + IotLogWarn( "IotMqtt_Init was not called before IotMqtt_Cleanup." ); + } } /*-----------------------------------------------------------*/ @@ -922,6 +990,16 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, uint8_t **, size_t * ) = _IotMqtt_SerializeConnect; + /* Check that IotMqtt_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + EMPTY_ELSE_MARKER; + } + /* Network info must not be NULL. */ if( pNetworkInfo == NULL ) { @@ -1249,110 +1327,118 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, uint32_t flags ) { - bool disconnected = false; + bool disconnected = false, initCalled = false; IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttOperation_t * pOperation = NULL; - IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); + /* Check that IotMqtt_Init was called. */ + initCalled = _checkInit(); + + if( initCalled == false ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" + * flag is not set. */ + if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == IOT_MQTT_FLAG_CLEANUP_ONLY ) + { + IOT_GOTO_CLEANUP(); + } /* Read the connection status. */ IotMutex_Lock( &( mqttConnection->referencesMutex ) ); disconnected = mqttConnection->disconnected; IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - /* Only send a DISCONNECT packet if the connection is active and the "cleanup only" - * flag is not set. */ - if( disconnected == false ) + if( disconnected == true ) { - if( ( flags & IOT_MQTT_FLAG_CLEANUP_ONLY ) == 0 ) - { - /* Create a DISCONNECT operation. This function blocks until the DISCONNECT - * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ - status = _IotMqtt_CreateOperation( mqttConnection, - IOT_MQTT_FLAG_WAITABLE, - NULL, - &pOperation ); + IOT_GOTO_CLEANUP(); + } - if( status == IOT_MQTT_SUCCESS ) - { - /* Ensure that the members set by operation creation and serialization - * are appropriate for a blocking DISCONNECT. */ - IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); - IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) - == IOT_MQTT_FLAG_WAITABLE ); - IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); - - /* Set the operation type. */ - pOperation->u.operation.type = IOT_MQTT_DISCONNECT; - - /* Choose a disconnect serializer. */ - IotMqttError_t ( * serializeDisconnect )( uint8_t **, - size_t * ) = _IotMqtt_SerializeDisconnect; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( mqttConnection->pSerializer != NULL ) - { - if( mqttConnection->pSerializer->serialize.disconnect != NULL ) - { - serializeDisconnect = mqttConnection->pSerializer->serialize.disconnect; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - - /* Generate a DISCONNECT packet. */ - status = serializeDisconnect( &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); - } - else - { - EMPTY_ELSE_MARKER; - } + IotLogInfo( "(MQTT connection %p) Disconnecting connection.", mqttConnection ); - if( status == IOT_MQTT_SUCCESS ) - { - /* Check the serialized MQTT packet. */ - IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); - IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + /* Create a DISCONNECT operation. This function blocks until the DISCONNECT + * packet is sent, so it sets IOT_MQTT_FLAG_WAITABLE. */ + status = _IotMqtt_CreateOperation( mqttConnection, + IOT_MQTT_FLAG_WAITABLE, + NULL, + &pOperation ); + + if( status == IOT_MQTT_SUCCESS ) + { + /* Ensure that the members set by operation creation and serialization + * are appropriate for a blocking DISCONNECT. */ + IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); + IotMqtt_Assert( ( pOperation->u.operation.flags & IOT_MQTT_FLAG_WAITABLE ) + == IOT_MQTT_FLAG_WAITABLE ); + IotMqtt_Assert( pOperation->u.operation.periodic.retry.limit == 0 ); - /* Send the DISCONNECT packet. */ - _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + /* Set the operation type. */ + pOperation->u.operation.type = IOT_MQTT_DISCONNECT; - /* Wait a short time for the DISCONNECT packet to be transmitted. */ - status = IotMqtt_Wait( pOperation, - IOT_MQTT_RESPONSE_WAIT_MS ); + /* Choose a disconnect serializer. */ + IotMqttError_t ( * serializeDisconnect )( uint8_t **, + size_t * ) = _IotMqtt_SerializeDisconnect; - /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, - * or NETWORK ERROR. */ - if( status == IOT_MQTT_SUCCESS ) + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + if( mqttConnection->pSerializer != NULL ) + { + if( mqttConnection->pSerializer->serialize.disconnect != NULL ) { - IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); + serializeDisconnect = mqttConnection->pSerializer->serialize.disconnect; } else { - IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || - ( status == IOT_MQTT_NETWORK_ERROR ) ); - - IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", - mqttConnection, - IotMqtt_strerror( status ) ); + EMPTY_ELSE_MARKER; } } else { EMPTY_ELSE_MARKER; } + #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + + /* Generate a DISCONNECT packet. */ + status = serializeDisconnect( &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); + } + else + { + EMPTY_ELSE_MARKER; + } + + if( status == IOT_MQTT_SUCCESS ) + { + /* Check the serialized MQTT packet. */ + IotMqtt_Assert( pOperation->u.operation.pMqttPacket != NULL ); + IotMqtt_Assert( pOperation->u.operation.packetSize > 0 ); + + /* Send the DISCONNECT packet. */ + _IotMqtt_ProcessSend( IOT_SYSTEM_TASKPOOL, pOperation->job, pOperation ); + + /* Wait a short time for the DISCONNECT packet to be transmitted. */ + status = IotMqtt_Wait( pOperation, + IOT_MQTT_RESPONSE_WAIT_MS ); + + /* A wait on DISCONNECT should only ever return SUCCESS, TIMEOUT, + * or NETWORK ERROR. */ + if( status == IOT_MQTT_SUCCESS ) + { + IotLogInfo( "(MQTT connection %p) Connection disconnected.", mqttConnection ); } else { - EMPTY_ELSE_MARKER; + IotMqtt_Assert( ( status == IOT_MQTT_TIMEOUT ) || + ( status == IOT_MQTT_NETWORK_ERROR ) ); + + IotLogWarn( "(MQTT connection %p) DISCONNECT not sent, error %s.", + mqttConnection, + IotMqtt_strerror( status ) ); } } else @@ -1360,29 +1446,36 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, EMPTY_ELSE_MARKER; } - /* Close the underlying network connection. This also cleans up keep-alive. */ - _IotMqtt_CloseNetworkConnection( IOT_MQTT_DISCONNECT_CALLED, - mqttConnection ); + /* This function has no return value and no cleanup, but uses the cleanup + * label to exit on error. */ + IOT_FUNCTION_CLEANUP_BEGIN(); - /* Check if the connection may be destroyed. */ - IotMutex_Lock( &( mqttConnection->referencesMutex ) ); + if( initCalled == true ) + { + /* Close the underlying network connection. This also cleans up keep-alive. */ + _IotMqtt_CloseNetworkConnection( IOT_MQTT_DISCONNECT_CALLED, + mqttConnection ); - /* At this point, the connection should be marked disconnected. */ - IotMqtt_Assert( mqttConnection->disconnected == true ); + /* Check if the connection may be destroyed. */ + IotMutex_Lock( &( mqttConnection->referencesMutex ) ); - /* Attempt cancel and destroy each operation in the connection's lists. */ - IotListDouble_RemoveAll( &( mqttConnection->pendingProcessing ), - _mqttOperation_tryDestroy, - offsetof( _mqttOperation_t, link ) ); + /* At this point, the connection should be marked disconnected. */ + IotMqtt_Assert( mqttConnection->disconnected == true ); - IotListDouble_RemoveAll( &( mqttConnection->pendingResponse ), - _mqttOperation_tryDestroy, - offsetof( _mqttOperation_t, link ) ); + /* Attempt cancel and destroy each operation in the connection's lists. */ + IotListDouble_RemoveAll( &( mqttConnection->pendingProcessing ), + _mqttOperation_tryDestroy, + offsetof( _mqttOperation_t, link ) ); - IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); + IotListDouble_RemoveAll( &( mqttConnection->pendingResponse ), + _mqttOperation_tryDestroy, + offsetof( _mqttOperation_t, link ) ); + + IotMutex_Unlock( &( mqttConnection->referencesMutex ) ); - /* Decrement the connection reference count and destroy it if possible. */ - _IotMqtt_DecrementConnectionReferences( mqttConnection ); + /* Decrement the connection reference count and destroy it if possible. */ + _IotMqtt_DecrementConnectionReferences( mqttConnection ); + } } /*-----------------------------------------------------------*/ @@ -1516,6 +1609,16 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, uint16_t *, uint8_t ** ) = _IotMqtt_SerializePublish; + /* Check that IotMqtt_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + EMPTY_ELSE_MARKER; + } + /* Check that the PUBLISH information is valid. */ if( _IotMqtt_ValidatePublish( mqttConnection->awsIotMqttMode, pPublishInfo ) == false ) @@ -1816,7 +1919,17 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, uint32_t timeoutMs ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - _mqttConnection_t * pMqttConnection = operation->pMqttConnection; + _mqttConnection_t * pMqttConnection = NULL; + + /* Check that IotMqtt_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + } + else + { + EMPTY_ELSE_MARKER; + } /* Validate the given operation reference. */ if( _IotMqtt_ValidateOperation( operation ) == false ) @@ -1829,6 +1942,8 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, } /* Check the MQTT connection status. */ + pMqttConnection = operation->pMqttConnection; + if( status == IOT_MQTT_SUCCESS ) { IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -1913,7 +2028,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, EMPTY_ELSE_MARKER; } - return status; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 54dc549072..747700293c 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -560,6 +560,8 @@ TEST_TEAR_DOWN( MQTT_Unit_API ) */ TEST_GROUP_RUNNER( MQTT_Unit_API ) { + RUN_TEST_CASE( MQTT_Unit_API, Init ); + RUN_TEST_CASE( MQTT_Unit_API, StringCoverage ); RUN_TEST_CASE( MQTT_Unit_API, OperationCreateDestroy ); RUN_TEST_CASE( MQTT_Unit_API, OperationWaitTimeout ); RUN_TEST_CASE( MQTT_Unit_API, ConnectParameters ); @@ -580,6 +582,105 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the function @ref mqtt_function_init. + */ +TEST( MQTT_Unit_API, Init ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + IotMqttOperation_t operation = IOT_MQTT_OPERATION_INITIALIZER; + + /* Initialization was done in test set up. Clean up here before running this test. */ + IotMqtt_Cleanup(); + + /* Calling cleanup twice should not crash. */ + IotMqtt_Cleanup(); + + /* Calling API functions without calling IotMqtt_Init should fail. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + status = IotMqtt_Connect( &_networkInfo, + &connectInfo, + TIMEOUT_MS, + &_pMqttConnection ); + TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + + subscription.pTopicFilter = TEST_TOPIC_NAME; + subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; + subscription.callback.function = SUBSCRIPTION_CALLBACK; + status = IotMqtt_SubscribeAsync( _pMqttConnection, &subscription, 1, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + + status = IotMqtt_UnsubscribeAsync( _pMqttConnection, &subscription, 1, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + + publishInfo.pTopicName = TEST_TOPIC_NAME; + publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; + status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + + status = IotMqtt_Wait( operation, TIMEOUT_MS ); + + IotMqtt_Disconnect( _pMqttConnection, 0 ); + + /* Reinitialize for test cleanup. Calling init twice should not crash. */ + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, IotMqtt_Init() ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Provides code coverage of the MQTT enum-to-string functions, + * @ref mqtt_function_strerror and @ref mqtt_function_operationtype. + */ +TEST( MQTT_Unit_API, StringCoverage ) +{ + int32_t i = 0; + const char * pMessage = NULL; + + /* For each MQTT Error, check the returned string. */ + const char * pExitString = "INVALID STATUS"; + size_t exitStringLength = strlen( pExitString ); + + while( true ) + { + pMessage = IotMqtt_strerror( ( IotMqttError_t ) i ); + TEST_ASSERT_NOT_NULL( pMessage ); + + if( strncmp( pExitString, pMessage, exitStringLength ) == 0 ) + { + break; + } + + i++; + } + + /* For each MQTT Operation Type, check the returned string. */ + i = 0; + pExitString = "INVALID OPERATION"; + exitStringLength = strlen( pExitString ); + + while( true ) + { + pMessage = IotMqtt_OperationType( ( IotMqttError_t ) i ); + TEST_ASSERT_NOT_NULL( pMessage ); + + if( strncmp( pExitString, pMessage, exitStringLength ) == 0 ) + { + break; + } + + i++; + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Test reference counts as MQTT operations are created and destroyed. */ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index d93df9ac43..a325fd0b05 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -133,6 +133,7 @@ TEST_TEAR_DOWN( Shadow_Unit_API ) TEST_GROUP_RUNNER( Shadow_Unit_API ) { RUN_TEST_CASE( Shadow_Unit_API, Init ); + RUN_TEST_CASE( Shadow_Unit_API, StringCoverage ); RUN_TEST_CASE( Shadow_Unit_API, OperationInvalidParameters ); RUN_TEST_CASE( Shadow_Unit_API, DocumentInvalidParameters ); RUN_TEST_CASE( Shadow_Unit_API, WaitInvalidParameters ); @@ -170,6 +171,51 @@ TEST( Shadow_Unit_API, Init ) /*-----------------------------------------------------------*/ +/** + * @brief Provides code coverage of the Shadow enum-to-string function, + * @ref shadow_function_strerror. + */ +TEST( Shadow_Unit_API, StringCoverage ) +{ + int32_t i = 0; + const char * pMessage = NULL; + + const char * pInvalidStatus = "INVALID STATUS"; + size_t invalidStatusLength = strlen( pInvalidStatus ); + + /* For each Shadow Error, check the returned string. */ + while( true ) + { + pMessage = AwsIotShadow_strerror( ( AwsIotShadowError_t ) i ); + TEST_ASSERT_NOT_NULL( pMessage ); + + if( strncmp( pInvalidStatus, pMessage, invalidStatusLength ) == 0 ) + { + break; + } + + i++; + } + + /* For each rejection reason (from the Shadow service) check the returned string. */ + const AwsIotShadowError_t rejectionReasons[] = + { + AWS_IOT_SHADOW_BAD_REQUEST, AWS_IOT_SHADOW_UNAUTHORIZED, AWS_IOT_SHADOW_FORBIDDEN, + AWS_IOT_SHADOW_NOT_FOUND, AWS_IOT_SHADOW_CONFLICT, AWS_IOT_SHADOW_TOO_LARGE, + AWS_IOT_SHADOW_UNSUPPORTED, AWS_IOT_SHADOW_TOO_MANY_REQUESTS, AWS_IOT_SHADOW_SERVER_ERROR + }; + + for( i = 0; i < ( sizeof( rejectionReasons ) / sizeof( rejectionReasons[ 0 ] ) ); i++ ) + { + pMessage = AwsIotShadow_strerror( rejectionReasons[ i ] ); + TEST_ASSERT_NOT_NULL( pMessage ); + + TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); + } +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of Shadow operation functions with various * invalid parameters. From 33a67db84c0ae2f12a3787b340ed03c9592b0938 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 17 Sep 2019 09:31:14 -0700 Subject: [PATCH 257/844] Return error in Shadow if init is not called (#562) --- lib/include/aws_iot_shadow.h | 9 + lib/include/iot_mqtt.h | 16 +- lib/include/types/aws_iot_shadow_types.h | 23 ++- lib/include/types/iot_mqtt_types.h | 30 ++-- lib/source/mqtt/iot_mqtt_api.c | 12 +- lib/source/shadow/aws_iot_shadow_api.c | 155 ++++++++++++++---- tests/mqtt/unit/iot_tests_mqtt_api.c | 11 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 35 +++- .../shadow/unit/aws_iot_tests_shadow_parser.c | 9 + 9 files changed, 228 insertions(+), 72 deletions(-) diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index 35b0a08a6d..b06d91982f 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -132,6 +132,7 @@ void AwsIotShadow_Cleanup( void ); * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully * queuing a Shadow delete. * @return If this function fails before queuing a Shadow delete, it will return one of: + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * @return Upon successful completion of the Shadow delete (either through an #AwsIotShadowCallbackInfo_t @@ -196,6 +197,7 @@ AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * - #AWS_IOT_SHADOW_MQTT_ERROR @@ -249,6 +251,7 @@ AwsIotShadowError_t AwsIotShadow_DeleteSync( IotMqttConnection_t mqttConnection, * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully * queuing a Shadow get. * @return If this function fails before queuing a Shadow get, it will return one of: + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * @return Upon successful completion of the Shadow get (either through an #AwsIotShadowCallbackInfo_t @@ -323,6 +326,7 @@ AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * - #AWS_IOT_SHADOW_MQTT_ERROR @@ -383,6 +387,7 @@ AwsIotShadowError_t AwsIotShadow_GetSync( IotMqttConnection_t mqttConnection, * @return This function will return #AWS_IOT_SHADOW_STATUS_PENDING upon successfully * queuing a Shadow update. * @return If this function fails before queuing a Shadow update, it will return one of: + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * @return Upon successful completion of the Shadow update (either through an #AwsIotShadowCallbackInfo_t @@ -451,6 +456,7 @@ AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * - #AWS_IOT_SHADOW_MQTT_ERROR @@ -500,6 +506,7 @@ AwsIotShadowError_t AwsIotShadow_UpdateSync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_BAD_RESPONSE * - #AWS_IOT_SHADOW_TIMEOUT @@ -629,6 +636,7 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * - #AWS_IOT_SHADOW_MQTT_ERROR @@ -742,6 +750,7 @@ AwsIotShadowError_t AwsIotShadow_SetDeltaCallback( IotMqttConnection_t mqttConne * * @return One of the following: * - #AWS_IOT_SHADOW_SUCCESS + * - #AWS_IOT_SHADOW_NOT_INITIALIZED * - #AWS_IOT_SHADOW_BAD_PARAMETER * - #AWS_IOT_SHADOW_NO_MEMORY * - #AWS_IOT_SHADOW_MQTT_ERROR diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index e3efccfb25..a247fdaa31 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -82,7 +82,7 @@ * * @return One of the following: * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * * @warning No thread-safety guarantees are provided for this function. * @@ -185,7 +185,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR @@ -339,7 +339,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_SERVER_REFUSED * @return If this function fails before queuing a subscribe operation, it will return * one of: - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * @@ -451,7 +451,7 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR @@ -499,7 +499,7 @@ IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, * - #IOT_MQTT_BAD_RESPONSE * @return If this function fails before queuing an unsubscribe operation, it will return * one of: - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * @@ -536,7 +536,7 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR @@ -588,7 +588,7 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set). * @return If this function fails before queuing an publish operation (regardless * of QoS), it will return one of: - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * @@ -669,7 +669,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, * * @return One of the following: * - #IOT_MQTT_SUCCESS - * - #IOT_MQTT_INIT_FAILED + * - #IOT_MQTT_NOT_INITIALIZED * - #IOT_MQTT_BAD_PARAMETER * - #IOT_MQTT_NO_MEMORY * - #IOT_MQTT_NETWORK_ERROR diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index f039b7be2d..9f15d66bcc 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -188,6 +188,19 @@ typedef enum AwsIotShadowError */ AWS_IOT_SHADOW_TIMEOUT, + /** + * @brief An API function was called before @ref shadow_function_init. + * + * Functions that may return this value: + * - @ref shadow_function_deleteasync and @ref shadow_function_deletesync + * - @ref shadow_function_getasync and @ref shadow_function_getsync + * - @ref shadow_function_updateasync and @ref shadow_function_updatesync + * - @ref shadow_function_wait + * - @ref shadow_function_setdeltacallback + * - @ref shadow_function_setupdatedcallback + */ + AWS_IOT_SHADOW_NOT_INITIALIZED, + /** * @brief Shadow operation rejected: Bad request. * @@ -396,7 +409,7 @@ typedef struct AwsIotShadowCallbackParam const char * pDocument; /**< @brief Shadow delta or updated document. */ size_t documentLength; /**< @brief Length of Shadow delta or updated document. */ } callback; /**< @brief Shadow document from an incoming delta or updated topic. */ - } u; /**< @brief Valid member depends on callback type. */ + } u; /**< @brief Valid member depends on callback type. */ } AwsIotShadowCallbackParam_t; /** @@ -473,7 +486,7 @@ typedef struct AwsIotShadowDocumentInfo const char * pUpdateDocument; /**< @brief The Shadow document to send in the update. */ size_t updateDocumentLength; /**< @brief Length of Shadow update document. */ } update; /**< @brief Valid members for @ref shadow_function_updateasync. */ - } u; /**< @brief Valid member depends on operation type. */ + } u; /**< @brief Valid member depends on operation type. */ } AwsIotShadowDocumentInfo_t; /*------------------------ Shadow defined constants -------------------------*/ @@ -530,9 +543,9 @@ typedef struct AwsIotShadowDocumentInfo */ /* @[define_shadow_initializers] */ -#define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ -#define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ -#define AWS_IOT_SHADOW_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowOperation_t. */ +#define AWS_IOT_SHADOW_CALLBACK_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowCallbackInfo_t. */ +#define AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER { 0 } /**< @brief Initializer for #AwsIotShadowDocumentInfo_t. */ +#define AWS_IOT_SHADOW_OPERATION_INITIALIZER NULL /**< @brief Initializer for #AwsIotShadowOperation_t. */ /* @[define_shadow_initializers] */ /** diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 509ad7979d..24c8783ec2 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -131,19 +131,8 @@ typedef enum IotMqttError /** * @brief Initialization failed. * - * This value is returned by @ref mqtt_function_init when initialization fails, or - * by other API functions if they are called before @ref mqtt_function_init. - * * Functions that may return this value: * - @ref mqtt_function_init - * - @ref mqtt_function_connect - * - @ref mqtt_function_subscribeasync - * - @ref mqtt_function_subscribesync - * - @ref mqtt_function_unsubscribeasync - * - @ref mqtt_function_unsubscribesync - * - @ref mqtt_function_publishasync - * - @ref mqtt_function_publishsync - * - @ref mqtt_function_wait */ IOT_MQTT_INIT_FAILED, @@ -260,7 +249,22 @@ typedef enum IotMqttError * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. */ - IOT_MQTT_RETRY_NO_RESPONSE + IOT_MQTT_RETRY_NO_RESPONSE, + + /** + * @brief An API function was called before @ref mqtt_function_init. + * + * Functions that may return this value: + * - @ref mqtt_function_connect + * - @ref mqtt_function_subscribeasync + * - @ref mqtt_function_subscribesync + * - @ref mqtt_function_unsubscribeasync + * - @ref mqtt_function_unsubscribesync + * - @ref mqtt_function_publishasync + * - @ref mqtt_function_publishsync + * - @ref mqtt_function_wait + */ + IOT_MQTT_NOT_INITIALIZED } IotMqttError_t; /** @@ -470,7 +474,7 @@ typedef struct IotMqttCallbackParam /* Valid when a connection is disconnected. */ IotMqttDisconnectReason_t disconnectReason; /**< @brief Why the MQTT connection was disconnected. */ - } u; /**< @brief Valid member depends on callback type. */ + } u; /**< @brief Valid member depends on callback type. */ } IotMqttCallbackParam_t; /** diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index f4171bff15..8e5aabfd7c 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -591,7 +591,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); } else { @@ -993,7 +993,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); } else { @@ -1612,7 +1612,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); } else { @@ -1924,7 +1924,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_INIT_FAILED ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NOT_INITIALIZED ); } else { @@ -2083,6 +2083,10 @@ const char * IotMqtt_strerror( IotMqttError_t status ) pMessage = "NO RESPONSE"; break; + case IOT_MQTT_NOT_INITIALIZED: + pMessage = "NOT INITIALIZED"; + break; + default: pMessage = "INVALID STATUS"; break; diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index ea5a7ea56e..f58c47d5fa 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -42,6 +42,9 @@ /* MQTT include. */ #include "iot_mqtt.h" +/* Atomics include. */ +#include "iot_atomic.h" + /* Validate Shadow configuration settings. */ #if AWS_IOT_SHADOW_ENABLE_ASSERTS != 0 && AWS_IOT_SHADOW_ENABLE_ASSERTS != 1 #error "AWS_IOT_SHADOW_ENABLE_ASSERTS must be 0 or 1." @@ -52,6 +55,13 @@ /*-----------------------------------------------------------*/ +/** + * @brief Check if the library is initialized. + * + * @return `true` if AwsIotShadow_Init was called; `false` otherwise. + */ +static bool _checkInit( void ); + /** * @brief Checks Thing Name and flags parameters passed to Shadow API functions. * @@ -150,6 +160,13 @@ static void _updatedCallbackWrapper( void * pArgument, /*-----------------------------------------------------------*/ +/** + * @brief Tracks whether @ref shadow_function_init has been called. + * + * API functions will fail if @ref shadow_function_init was not called. + */ +static uint32_t _initCalled = 0; + /** * @brief Timeout used for MQTT operations. */ @@ -169,6 +186,22 @@ uint32_t _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; /*-----------------------------------------------------------*/ +static bool _checkInit( void ) +{ + bool status = true; + + if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + { + IotLogError( "IotMqtt_Init was not called." ); + + status = false; + } + + return status; +} + +/*-----------------------------------------------------------*/ + static AwsIotShadowError_t _validateThingNameFlags( _shadowOperationType_t type, const char * pThingName, size_t thingNameLength, @@ -311,6 +344,12 @@ static AwsIotShadowError_t _setCallbackCommon( IotMqttConnection_t mqttConnectio bool subscriptionMutexLocked = false; _shadowSubscription_t * pSubscription = NULL; + /* Check that AwsIotShadow_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + } + /* Check parameters. */ status = _validateThingNameFlags( ( _shadowOperationType_t ) ( type + SHADOW_OPERATION_COUNT ), pThingName, @@ -642,28 +681,39 @@ static void _updatedCallbackWrapper( void * pArgument, AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) { AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; + bool listInitStatus = false; - /* Initialize Shadow lists and mutexes. */ - bool listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, - &_AwsIotShadowSubscriptions, - &_AwsIotShadowPendingOperationsMutex, - &_AwsIotShadowSubscriptionsMutex ); - - if( listInitStatus == false ) + if( _checkInit() == false ) { - IotLogInfo( "Failed to create Shadow lists." ); + /* Initialize Shadow lists and mutexes. */ + listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, + &_AwsIotShadowSubscriptions, + &_AwsIotShadowPendingOperationsMutex, + &_AwsIotShadowSubscriptionsMutex ); - status = AWS_IOT_SHADOW_INIT_FAILED; - } - else - { - /* Save the MQTT timeout option. */ - if( mqttTimeoutMs != 0 ) + if( listInitStatus == false ) { - _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; + IotLogInfo( "Failed to create Shadow lists." ); + + status = AWS_IOT_SHADOW_INIT_FAILED; } + else + { + /* Save the MQTT timeout option. */ + if( mqttTimeoutMs != 0 ) + { + _AwsIotShadowMqttTimeoutMs = mqttTimeoutMs; + } - IotLogInfo( "Shadow library successfully initialized." ); + /* Set the flag that specifies initialization is complete. */ + ( void ) Atomic_CompareAndSwap_u32( &( _initCalled ), 1, 0 ); + + IotLogInfo( "Shadow library successfully initialized." ); + } + } + else + { + IotLogWarn( "AwsIotShadow_Init called with library already initialized." ); } return status; @@ -673,28 +723,37 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) void AwsIotShadow_Cleanup( void ) { - /* Remove and free all items in the Shadow pending operation list. */ - IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); - IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), - _AwsIotShadow_DestroyOperation, - offsetof( _shadowOperation_t, link ) ); - IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + uint32_t initCleared = Atomic_CompareAndSwap_u32( &( _initCalled ), 0, 1 ); - /* Remove and free all items in the Shadow subscription list. */ - IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); - IotListDouble_RemoveAll( &( _AwsIotShadowSubscriptions ), - _AwsIotShadow_DestroySubscription, - offsetof( _shadowSubscription_t, link ) ); - IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); + if( initCleared == 1 ) + { + /* Remove and free all items in the Shadow pending operation list. */ + IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); + IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), + _AwsIotShadow_DestroyOperation, + offsetof( _shadowOperation_t, link ) ); + IotMutex_Unlock( &( _AwsIotShadowPendingOperationsMutex ) ); + + /* Remove and free all items in the Shadow subscription list. */ + IotMutex_Lock( &( _AwsIotShadowSubscriptionsMutex ) ); + IotListDouble_RemoveAll( &( _AwsIotShadowSubscriptions ), + _AwsIotShadow_DestroySubscription, + offsetof( _shadowSubscription_t, link ) ); + IotMutex_Unlock( &( _AwsIotShadowSubscriptionsMutex ) ); - /* Destroy Shadow library mutexes. */ - IotMutex_Destroy( &( _AwsIotShadowPendingOperationsMutex ) ); - IotMutex_Destroy( &( _AwsIotShadowSubscriptionsMutex ) ); + /* Destroy Shadow library mutexes. */ + IotMutex_Destroy( &( _AwsIotShadowPendingOperationsMutex ) ); + IotMutex_Destroy( &( _AwsIotShadowSubscriptionsMutex ) ); - /* Restore the default MQTT timeout. */ - _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; + /* Restore the default MQTT timeout. */ + _AwsIotShadowMqttTimeoutMs = AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS; - IotLogInfo( "Shadow library cleanup done." ); + IotLogInfo( "Shadow library cleanup done." ); + } + else + { + IotLogWarn( "AwsIotShadow_Init was not called before AwsIotShadow_Cleanup." ); + } } /*-----------------------------------------------------------*/ @@ -709,6 +768,12 @@ AwsIotShadowError_t AwsIotShadow_DeleteAsync( IotMqttConnection_t mqttConnection IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; + /* Check that AwsIotShadow_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + } + /* Validate the Thing Name and flags for Shadow DELETE. */ status = _validateThingNameFlags( SHADOW_DELETE, pThingName, @@ -810,6 +875,12 @@ AwsIotShadowError_t AwsIotShadow_GetAsync( IotMqttConnection_t mqttConnection, IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); _shadowOperation_t * pOperation = NULL; + /* Check that AwsIotShadow_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + } + /* Validate the Thing Name and flags for Shadow GET. */ status = _validateThingNameFlags( SHADOW_GET, pGetInfo->pThingName, @@ -930,6 +1001,12 @@ AwsIotShadowError_t AwsIotShadow_UpdateAsync( IotMqttConnection_t mqttConnection const char * pClientToken = NULL; size_t clientTokenLength = 0; + /* Check that AwsIotShadow_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + } + /* Validate the Thing Name and flags for Shadow UPDATE. */ status = _validateThingNameFlags( SHADOW_UPDATE, pUpdateInfo->pThingName, @@ -1067,6 +1144,12 @@ AwsIotShadowError_t AwsIotShadow_Wait( AwsIotShadowOperation_t operation, { IOT_FUNCTION_ENTRY( AwsIotShadowError_t, AWS_IOT_SHADOW_STATUS_PENDING ); + /* Check that AwsIotShadow_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NOT_INITIALIZED ); + } + /* Check that reference is set. */ if( operation == NULL ) { @@ -1208,6 +1291,10 @@ const char * AwsIotShadow_strerror( AwsIotShadowError_t status ) pMessage = "TIMEOUT"; break; + case AWS_IOT_SHADOW_NOT_INITIALIZED: + pMessage = "NOT INITIALIZED"; + break; + case AWS_IOT_SHADOW_BAD_REQUEST: pMessage = "REJECTED: 400 BAD REQUEST"; break; diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index 747700293c..b5d2fe2594 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -607,23 +607,24 @@ TEST( MQTT_Unit_API, Init ) &connectInfo, TIMEOUT_MS, &_pMqttConnection ); - TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); subscription.pTopicFilter = TEST_TOPIC_NAME; subscription.topicFilterLength = TEST_TOPIC_NAME_LENGTH; subscription.callback.function = SUBSCRIPTION_CALLBACK; status = IotMqtt_SubscribeAsync( _pMqttConnection, &subscription, 1, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); status = IotMqtt_UnsubscribeAsync( _pMqttConnection, &subscription, 1, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); publishInfo.pTopicName = TEST_TOPIC_NAME; publishInfo.topicNameLength = TEST_TOPIC_NAME_LENGTH; status = IotMqtt_PublishAsync( _pMqttConnection, &publishInfo, 0, NULL, NULL ); - TEST_ASSERT_EQUAL( IOT_MQTT_INIT_FAILED, status ); + TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); status = IotMqtt_Wait( operation, TIMEOUT_MS ); + TEST_ASSERT_EQUAL( IOT_MQTT_NOT_INITIALIZED, status ); IotMqtt_Disconnect( _pMqttConnection, 0 ); @@ -667,7 +668,7 @@ TEST( MQTT_Unit_API, StringCoverage ) while( true ) { - pMessage = IotMqtt_OperationType( ( IotMqttError_t ) i ); + pMessage = IotMqtt_OperationType( ( IotMqttOperationType_t ) i ); TEST_ASSERT_NOT_NULL( pMessage ); if( strncmp( pExitString, pMessage, exitStringLength ) == 0 ) diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index a325fd0b05..0c33794dff 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -150,6 +150,10 @@ TEST_GROUP_RUNNER( Shadow_Unit_API ) */ TEST( Shadow_Unit_API, Init ) { + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; + AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; + /* Check that test set up set the default value. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); @@ -157,16 +161,41 @@ TEST( Shadow_Unit_API, Init ) * before running this test. */ AwsIotShadow_Cleanup(); + /* Calling cleanup twice should not crash. */ + AwsIotShadow_Cleanup(); + /* Set a MQTT timeout. */ - AwsIotShadow_Init( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1 ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1 ) ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotShadowMqttTimeoutMs ); /* Cleanup should restore the default MQTT timeout. */ AwsIotShadow_Cleanup(); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotShadowMqttTimeoutMs ); - /* Initialize the Shadow library for test clean up. */ - AwsIotShadow_Init( 0 ); + /* Calling API functions without calling AwsIotShadow_Init should fail. */ + status = AwsIotShadow_DeleteAsync( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + + documentInfo.pThingName = TEST_THING_NAME; + documentInfo.thingNameLength = TEST_THING_NAME_LENGTH; + status = AwsIotShadow_GetAsync( _pMqttConnection, &documentInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + + status = AwsIotShadow_UpdateAsync( _pMqttConnection, &documentInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + + status = AwsIotShadow_Wait( operation, 500, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + + status = AwsIotShadow_SetDeltaCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + + status = AwsIotShadow_SetUpdatedCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + + /* Initialize the Shadow library for test clean up. Calling init twice should not crash. */ + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); } /*-----------------------------------------------------------*/ diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index e17368353e..063e91f635 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -41,6 +41,9 @@ /* Test framework includes. */ #include "unity_fixture.h" +/* SDK initialization include. */ +#include "iot_init.h" + /*-----------------------------------------------------------*/ /** @@ -150,6 +153,9 @@ TEST_GROUP( Shadow_Unit_Parser ); */ TEST_SETUP( Shadow_Unit_Parser ) { + /* Initialize SDK. */ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + /* Initialize the Shadow library. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); } @@ -163,6 +169,9 @@ TEST_TEAR_DOWN( Shadow_Unit_Parser ) { /* Clean up the Shadow library. */ AwsIotShadow_Cleanup(); + + /* Clean up SDK. */ + IotSdk_Cleanup(); } /*-----------------------------------------------------------*/ From 3ae0d916c492acf0ffbe48aa0ed7111ff17cae30 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 17 Sep 2019 11:42:56 -0700 Subject: [PATCH 258/844] Fix Defender Compilation warnings (#563) * fix: compilation warnings for defender library Changed the code to use snprintf() instead of strcpy(), strcat() --- lib/source/defender/aws_iot_defender_mqtt.c | 29 ++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index 356af4a3c4..670079c501 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -19,6 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include /* Defender internal include. */ @@ -52,11 +53,12 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) const char * pThingName = _startInfo.mqttConnectionInfo.pClientIdentifier; uint16_t thingNameLength = _startInfo.mqttConnectionInfo.clientIdentifierLength; + size_t topicPrefixLength = strlen( TOPIC_PREFIX ); /* Calculate topics lengths. Plus one for string terminator. */ - size_t publishTopicLength = strlen( TOPIC_PREFIX ) + thingNameLength + strlen( TOPIC_SUFFIX_PUBLISH ) + 1; - size_t acceptTopicLength = strlen( TOPIC_PREFIX ) + thingNameLength + strlen( TOPIC_SUFFIX_ACCEPTED ) + 1; - size_t rejectTopicLength = strlen( TOPIC_PREFIX ) + thingNameLength + strlen( TOPIC_SUFFIX_REJECTED ) + 1; + size_t publishTopicLength = topicPrefixLength + thingNameLength + strlen( TOPIC_SUFFIX_PUBLISH ) + 1; + size_t acceptTopicLength = topicPrefixLength + thingNameLength + strlen( TOPIC_SUFFIX_ACCEPTED ) + 1; + size_t rejectTopicLength = topicPrefixLength + thingNameLength + strlen( TOPIC_SUFFIX_REJECTED ) + 1; /* Allocate memory for each of them. */ char * pPublishTopic = AwsIotDefender_MallocTopic( publishTopicLength * sizeof( char ) ); @@ -78,17 +80,20 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) _pAcceptTopic = pAcceptTopic; _pRejectTopic = pRejectTopic; - strcpy( _pPublishTopic, TOPIC_PREFIX ); - strncat( _pPublishTopic, pThingName, thingNameLength ); - strcat( _pPublishTopic, TOPIC_SUFFIX_PUBLISH ); + snprintf( _pPublishTopic, publishTopicLength, "%s%s%s", + TOPIC_PREFIX, + pThingName, + TOPIC_SUFFIX_PUBLISH ); - strcpy( _pAcceptTopic, TOPIC_PREFIX ); - strncat( _pAcceptTopic, pThingName, thingNameLength ); - strcat( _pAcceptTopic, TOPIC_SUFFIX_ACCEPTED ); + snprintf( _pAcceptTopic, acceptTopicLength, "%s%s%s", + TOPIC_PREFIX, + pThingName, + TOPIC_SUFFIX_ACCEPTED ); - strcpy( _pRejectTopic, TOPIC_PREFIX ); - strncat( _pRejectTopic, pThingName, thingNameLength ); - strcat( _pRejectTopic, TOPIC_SUFFIX_REJECTED ); + snprintf( _pRejectTopic, rejectTopicLength, "%s%s%s", + TOPIC_PREFIX, + pThingName, + TOPIC_SUFFIX_REJECTED ); } return returnedError; From 38efd713356ee1248bc7f9a59444256e87792ae4 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 17 Sep 2019 14:07:52 -0700 Subject: [PATCH 259/844] Make return enum values consistent across libraries (#564) --- lib/include/aws_iot_jobs.h | 11 +++++ lib/include/types/aws_iot_jobs_types.h | 46 ++++++++++++------- lib/include/types/aws_iot_shadow_types.h | 16 +++---- lib/include/types/iot_mqtt_types.h | 22 ++++----- lib/source/shadow/aws_iot_shadow_api.c | 2 +- tests/shadow/unit/aws_iot_tests_shadow_api.c | 47 +++++++++++++++----- 6 files changed, 96 insertions(+), 48 deletions(-) diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 377be00248..5c0859f510 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -137,6 +137,7 @@ void AwsIotJobs_Cleanup( void ); * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully * queuing the Jobs operation. * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t @@ -204,6 +205,7 @@ AwsIotJobsError_t AwsIotJobs_GetPendingAsync( const AwsIotJobsRequestInfo_t * pR * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * - #AWS_IOT_JOBS_MQTT_ERROR @@ -239,6 +241,7 @@ AwsIotJobsError_t AwsIotJobs_GetPendingSync( const AwsIotJobsRequestInfo_t * pRe * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully * queuing the Jobs operation. * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t @@ -311,6 +314,7 @@ AwsIotJobsError_t AwsIotJobs_StartNextAsync( const AwsIotJobsRequestInfo_t * pRe * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * - #AWS_IOT_JOBS_MQTT_ERROR @@ -351,6 +355,7 @@ AwsIotJobsError_t AwsIotJobs_StartNextSync( const AwsIotJobsRequestInfo_t * pReq * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully * queuing the Jobs operation. * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t @@ -430,6 +435,7 @@ AwsIotJobsError_t AwsIotJobs_DescribeAsync( const AwsIotJobsRequestInfo_t * pReq * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * - #AWS_IOT_JOBS_MQTT_ERROR @@ -466,6 +472,7 @@ AwsIotJobsError_t AwsIotJobs_DescribeSync( const AwsIotJobsRequestInfo_t * pRequ * @return This function will return #AWS_IOT_JOBS_STATUS_PENDING upon successfully * queuing the Jobs operation. * @return If this function fails before queuing the Jobs operation, it will return one of: + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * @return Upon successful completion of the Jobs operation (either through an #AwsIotJobsCallbackInfo_t @@ -544,6 +551,7 @@ AwsIotJobsError_t AwsIotJobs_UpdateAsync( const AwsIotJobsRequestInfo_t * pReque * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * - #AWS_IOT_JOBS_MQTT_ERROR @@ -584,6 +592,7 @@ AwsIotJobsError_t AwsIotJobs_UpdateSync( const AwsIotJobsRequestInfo_t * pReques * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_BAD_RESPONSE * - #AWS_IOT_JOBS_TIMEOUT @@ -678,6 +687,7 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * - #AWS_IOT_JOBS_MQTT_ERROR @@ -762,6 +772,7 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttC * * @return One of the following: * - #AWS_IOT_JOBS_SUCCESS + * - #AWS_IOT_JOBS_NOT_INITIALIZED * - #AWS_IOT_JOBS_BAD_PARAMETER * - #AWS_IOT_JOBS_NO_MEMORY * - #AWS_IOT_JOBS_MQTT_ERROR diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 28541adf9c..033790f9a9 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -111,7 +111,7 @@ typedef enum AwsIotJobsError * - @ref jobs_function_describeasync * - @ref jobs_function_updateasync */ - AWS_IOT_JOBS_STATUS_PENDING, + AWS_IOT_JOBS_STATUS_PENDING = 1, /** * @brief Initialization failed. @@ -119,7 +119,7 @@ typedef enum AwsIotJobsError * Functions that may return this value: * - @ref jobs_function_init */ - AWS_IOT_JOBS_INIT_FAILED, + AWS_IOT_JOBS_INIT_FAILED = 2, /** * @brief At least one parameter is invalid. @@ -134,7 +134,7 @@ typedef enum AwsIotJobsError * - @ref jobs_function_setnotifynextcallback * - @ref jobs_function_removepersistentsubscriptions */ - AWS_IOT_JOBS_BAD_PARAMETER, + AWS_IOT_JOBS_BAD_PARAMETER = 3, /** * @brief Jobs operation failed because of memory allocation failure. @@ -147,7 +147,7 @@ typedef enum AwsIotJobsError * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback */ - AWS_IOT_JOBS_NO_MEMORY, + AWS_IOT_JOBS_NO_MEMORY = 4, /** * @brief Jobs operation failed because of failure in MQTT library. @@ -161,7 +161,7 @@ typedef enum AwsIotJobsError * - @ref jobs_function_setnotifynextcallback * - @ref jobs_function_removepersistentsubscriptions */ - AWS_IOT_JOBS_MQTT_ERROR, + AWS_IOT_JOBS_MQTT_ERROR = 5, /** * @brief Response received from Jobs service not understood. @@ -176,7 +176,7 @@ typedef enum AwsIotJobsError * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ - AWS_IOT_JOBS_BAD_RESPONSE, + AWS_IOT_JOBS_BAD_RESPONSE = 7, /** * @brief A blocking Jobs operation timed out. @@ -190,7 +190,21 @@ typedef enum AwsIotJobsError * - @ref jobs_function_setnotifypendingcallback * - @ref jobs_function_setnotifynextcallback */ - AWS_IOT_JOBS_TIMEOUT, + AWS_IOT_JOBS_TIMEOUT = 8, + + /** + * @brief An API function was called before @ref jobs_function_init. + * + * Functions that may return this value: + * - @ref jobs_function_getpendingasync and @ref jobs_function_getpendingsync + * - @ref jobs_function_startnextasync and @ref jobs_function_startnextsync + * - @ref jobs_function_describeasync and @ref jobs_function_describesync + * - @ref jobs_function_updateasync and @ref jobs_function_updatesync + * - @ref jobs_function_wait + * - @ref jobs_function_setnotifypendingcallback + * - @ref jobs_function_setnotifynextcallback + */ + AWS_IOT_JOBS_NOT_INITIALIZED = 11, /** * @brief Jobs operation failed: A request was sent to an unknown topic. @@ -205,7 +219,7 @@ typedef enum AwsIotJobsError * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ - AWS_IOT_JOBS_INVALID_TOPIC, + AWS_IOT_JOBS_INVALID_TOPIC = 12, /** * @brief Jobs operation failed: The contents of the request were not understood. @@ -222,7 +236,7 @@ typedef enum AwsIotJobsError * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ - AWS_IOT_JOBS_INVALID_JSON, + AWS_IOT_JOBS_INVALID_JSON = 13, /** * @brief Jobs operation failed: The contents of the request were invalid. @@ -237,7 +251,7 @@ typedef enum AwsIotJobsError * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ - AWS_IOT_JOBS_INVALID_REQUEST, + AWS_IOT_JOBS_INVALID_REQUEST = 14, /** * @brief Jobs operation failed: An update attempted to change the job execution @@ -252,7 +266,7 @@ typedef enum AwsIotJobsError * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) * following a call to @ref jobs_function_startnextasync or @ref jobs_function_updateasync. */ - AWS_IOT_JOBS_INVALID_STATE, + AWS_IOT_JOBS_INVALID_STATE = 15, /** * @brief Jobs operation failed: The specified job execution does not exist. @@ -266,7 +280,7 @@ typedef enum AwsIotJobsError * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) * following a call to @ref jobs_function_describeasync or @ref jobs_function_updateasync. */ - AWS_IOT_JOBS_NOT_FOUND, + AWS_IOT_JOBS_NOT_FOUND = 16, /** * @brief Jobs operation failed: The Jobs service expected a version that did @@ -280,7 +294,7 @@ typedef enum AwsIotJobsError * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) * following a call to @ref jobs_function_updateasync. */ - AWS_IOT_JOBS_VERSION_MISMATCH, + AWS_IOT_JOBS_VERSION_MISMATCH = 17, /** * @brief Jobs operation failed: The Jobs service encountered an internal error. @@ -295,7 +309,7 @@ typedef enum AwsIotJobsError * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ - AWS_IOT_JOBS_INTERNAL_ERROR, + AWS_IOT_JOBS_INTERNAL_ERROR = 18, /** * @brief Jobs operation failed: The request was throttled. @@ -310,7 +324,7 @@ typedef enum AwsIotJobsError * May also be the value of a Jobs operation completion callback's
* [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result). */ - AWS_IOT_JOBS_THROTTLED, + AWS_IOT_JOBS_THROTTLED = 19, /** * @brief Jobs operation failed: Attempt to describe a Job in a terminal state. @@ -323,7 +337,7 @@ typedef enum AwsIotJobsError * [AwsIotJobsCallbackParam_t.operation.result](@ref AwsIotJobsCallbackParam_t.result) * following a call to @ref jobs_function_describeasync. */ - AWS_IOT_JOBS_TERMINAL_STATE + AWS_IOT_JOBS_TERMINAL_STATE = 20 } AwsIotJobsError_t; /** diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index 9f15d66bcc..b99ee48491 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -110,7 +110,7 @@ typedef enum AwsIotShadowError * - @ref shadow_function_getasync * - @ref shadow_function_updateasync */ - AWS_IOT_SHADOW_STATUS_PENDING, + AWS_IOT_SHADOW_STATUS_PENDING = 1, /** * @brief Initialization failed. @@ -118,7 +118,7 @@ typedef enum AwsIotShadowError * Functions that may return this value: * - @ref shadow_function_init */ - AWS_IOT_SHADOW_INIT_FAILED, + AWS_IOT_SHADOW_INIT_FAILED = 2, /** * @brief At least one parameter is invalid. @@ -131,7 +131,7 @@ typedef enum AwsIotShadowError * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback */ - AWS_IOT_SHADOW_BAD_PARAMETER, + AWS_IOT_SHADOW_BAD_PARAMETER = 3, /** * @brief Shadow operation failed because of memory allocation failure. @@ -143,7 +143,7 @@ typedef enum AwsIotShadowError * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback */ - AWS_IOT_SHADOW_NO_MEMORY, + AWS_IOT_SHADOW_NO_MEMORY = 4, /** * @brief Shadow operation failed because of failure in MQTT library. @@ -159,7 +159,7 @@ typedef enum AwsIotShadowError * - @ref shadow_function_setupdatedcallback * - @ref shadow_function_removepersistentsubscriptions */ - AWS_IOT_SHADOW_MQTT_ERROR, + AWS_IOT_SHADOW_MQTT_ERROR = 5, /** * @brief Reponse received from Shadow service not understood. @@ -173,7 +173,7 @@ typedef enum AwsIotShadowError * May also be the value of a Shadow operation completion callback's
* [AwsIotShadowCallbackParam_t.operation.result](@ref AwsIotShadowCallbackParam_t.result) */ - AWS_IOT_SHADOW_BAD_RESPONSE, + AWS_IOT_SHADOW_BAD_RESPONSE = 7, /** * @brief A blocking Shadow operation timed out. @@ -186,7 +186,7 @@ typedef enum AwsIotShadowError * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback */ - AWS_IOT_SHADOW_TIMEOUT, + AWS_IOT_SHADOW_TIMEOUT = 8, /** * @brief An API function was called before @ref shadow_function_init. @@ -199,7 +199,7 @@ typedef enum AwsIotShadowError * - @ref shadow_function_setdeltacallback * - @ref shadow_function_setupdatedcallback */ - AWS_IOT_SHADOW_NOT_INITIALIZED, + AWS_IOT_SHADOW_NOT_INITIALIZED = 11, /** * @brief Shadow operation rejected: Bad request. diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index 24c8783ec2..ae2a2a1144 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -126,7 +126,7 @@ typedef enum IotMqttError * - @ref mqtt_function_unsubscribeasync * - @ref mqtt_function_publishasync with QoS 1 parameter */ - IOT_MQTT_STATUS_PENDING, + IOT_MQTT_STATUS_PENDING = 1, /** * @brief Initialization failed. @@ -134,7 +134,7 @@ typedef enum IotMqttError * Functions that may return this value: * - @ref mqtt_function_init */ - IOT_MQTT_INIT_FAILED, + IOT_MQTT_INIT_FAILED = 2, /** * @brief At least one parameter is invalid. @@ -146,7 +146,7 @@ typedef enum IotMqttError * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync * - @ref mqtt_function_wait */ - IOT_MQTT_BAD_PARAMETER, + IOT_MQTT_BAD_PARAMETER = 3, /** * @brief MQTT operation failed because of memory allocation failure. @@ -157,7 +157,7 @@ typedef enum IotMqttError * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync */ - IOT_MQTT_NO_MEMORY, + IOT_MQTT_NO_MEMORY = 4, /** * @brief MQTT operation failed because the network was unusable. @@ -174,7 +174,7 @@ typedef enum IotMqttError * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result. */ - IOT_MQTT_NETWORK_ERROR, + IOT_MQTT_NETWORK_ERROR = 5, /** * @brief MQTT operation could not be scheduled, i.e. enqueued for sending. @@ -185,7 +185,7 @@ typedef enum IotMqttError * - @ref mqtt_function_unsubscribeasync and @ref mqtt_function_unsubscribesync * - @ref mqtt_function_publishasync and @ref mqtt_function_publishsync */ - IOT_MQTT_SCHEDULING_ERROR, + IOT_MQTT_SCHEDULING_ERROR = 6, /** * @brief MQTT response packet received from the network is malformed. @@ -202,7 +202,7 @@ typedef enum IotMqttError * * @note If this value is received, the network connection has been closed. */ - IOT_MQTT_BAD_RESPONSE, + IOT_MQTT_BAD_RESPONSE = 7, /** * @brief A blocking MQTT operation timed out. @@ -214,7 +214,7 @@ typedef enum IotMqttError * - @ref mqtt_function_unsubscribesync * - @ref mqtt_function_publishsync */ - IOT_MQTT_TIMEOUT, + IOT_MQTT_TIMEOUT = 8, /** * @brief A CONNECT or at least one subscription was refused by the server. @@ -235,7 +235,7 @@ typedef enum IotMqttError * mqtt_function_issubscribed can be used to determine which subscriptions * were accepted or rejected. */ - IOT_MQTT_SERVER_REFUSED, + IOT_MQTT_SERVER_REFUSED = 9, /** * @brief A QoS 1 PUBLISH received no response and [the retry limit] @@ -249,7 +249,7 @@ typedef enum IotMqttError * May also be the value of an operation completion callback's * #IotMqttCallbackParam_t.result for a QoS 1 PUBLISH. */ - IOT_MQTT_RETRY_NO_RESPONSE, + IOT_MQTT_RETRY_NO_RESPONSE = 10, /** * @brief An API function was called before @ref mqtt_function_init. @@ -264,7 +264,7 @@ typedef enum IotMqttError * - @ref mqtt_function_publishsync * - @ref mqtt_function_wait */ - IOT_MQTT_NOT_INITIALIZED + IOT_MQTT_NOT_INITIALIZED = 11 } IotMqttError_t; /** diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index f58c47d5fa..914be15e56 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -192,7 +192,7 @@ static bool _checkInit( void ) if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) { - IotLogError( "IotMqtt_Init was not called." ); + IotLogError( "AwsIotShadow_Init was not called." ); status = false; } diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 0c33794dff..3acfedba5e 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -150,6 +150,7 @@ TEST_GROUP_RUNNER( Shadow_Unit_API ) */ TEST( Shadow_Unit_API, Init ) { + int32_t i = 0; AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; AwsIotShadowDocumentInfo_t documentInfo = AWS_IOT_SHADOW_DOCUMENT_INFO_INITIALIZER; AwsIotShadowOperation_t operation = AWS_IOT_SHADOW_OPERATION_INITIALIZER; @@ -193,9 +194,24 @@ TEST( Shadow_Unit_API, Init ) status = AwsIotShadow_SetUpdatedCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_NOT_INITIALIZED, status ); + /* Test Shadow initialization with mutex creation failures. */ + for( i = 0; ; i++ ) + { + UnityMalloc_MakeMallocFailAfterCount( i ); + + status = AwsIotShadow_Init( 0 ); + + /* Check that the status is either success or "INIT FAILED". */ + if( status == AWS_IOT_SHADOW_SUCCESS ) + { + break; + } + + TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_INIT_FAILED, status ); + } + /* Initialize the Shadow library for test clean up. Calling init twice should not crash. */ TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); - TEST_ASSERT_EQUAL( AWS_IOT_SHADOW_SUCCESS, AwsIotShadow_Init( 0 ) ); } /*-----------------------------------------------------------*/ @@ -213,34 +229,41 @@ TEST( Shadow_Unit_API, StringCoverage ) size_t invalidStatusLength = strlen( pInvalidStatus ); /* For each Shadow Error, check the returned string. */ - while( true ) + const AwsIotShadowError_t pApiErrors[] = { - pMessage = AwsIotShadow_strerror( ( AwsIotShadowError_t ) i ); - TEST_ASSERT_NOT_NULL( pMessage ); + AWS_IOT_SHADOW_SUCCESS, AWS_IOT_SHADOW_STATUS_PENDING, AWS_IOT_SHADOW_INIT_FAILED, + AWS_IOT_SHADOW_BAD_PARAMETER, AWS_IOT_SHADOW_NO_MEMORY, AWS_IOT_SHADOW_MQTT_ERROR, + AWS_IOT_SHADOW_BAD_RESPONSE, AWS_IOT_SHADOW_TIMEOUT, AWS_IOT_SHADOW_NOT_INITIALIZED + }; - if( strncmp( pInvalidStatus, pMessage, invalidStatusLength ) == 0 ) - { - break; - } + for( i = 0; i < ( sizeof( pApiErrors ) / sizeof( pApiErrors[ 0 ] ) ); i++ ) + { + pMessage = AwsIotShadow_strerror( pApiErrors[ i ] ); + TEST_ASSERT_NOT_NULL( pMessage ); - i++; + TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); } /* For each rejection reason (from the Shadow service) check the returned string. */ - const AwsIotShadowError_t rejectionReasons[] = + const AwsIotShadowError_t pRejectionReasons[] = { AWS_IOT_SHADOW_BAD_REQUEST, AWS_IOT_SHADOW_UNAUTHORIZED, AWS_IOT_SHADOW_FORBIDDEN, AWS_IOT_SHADOW_NOT_FOUND, AWS_IOT_SHADOW_CONFLICT, AWS_IOT_SHADOW_TOO_LARGE, AWS_IOT_SHADOW_UNSUPPORTED, AWS_IOT_SHADOW_TOO_MANY_REQUESTS, AWS_IOT_SHADOW_SERVER_ERROR }; - for( i = 0; i < ( sizeof( rejectionReasons ) / sizeof( rejectionReasons[ 0 ] ) ); i++ ) + for( i = 0; i < ( sizeof( pRejectionReasons ) / sizeof( pRejectionReasons[ 0 ] ) ); i++ ) { - pMessage = AwsIotShadow_strerror( rejectionReasons[ i ] ); + pMessage = AwsIotShadow_strerror( pRejectionReasons[ i ] ); TEST_ASSERT_NOT_NULL( pMessage ); TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); } + + /* Check an invalid status. */ + pMessage = AwsIotShadow_strerror( ( AwsIotShadowError_t ) -1 ); + TEST_ASSERT_NOT_NULL( pMessage ); + TEST_ASSERT_EQUAL_STRING( pInvalidStatus, pMessage ); } /*-----------------------------------------------------------*/ From dbb3c820301442890334057a0d37e2991787a1c3 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 17 Sep 2019 15:16:21 -0700 Subject: [PATCH 260/844] Return error in Jobs if init is not called (#565) --- lib/source/jobs/aws_iot_jobs_api.c | 161 +++++++++++++++---- tests/jobs/unit/aws_iot_tests_jobs_api.c | 69 ++++++-- tests/shadow/unit/aws_iot_tests_shadow_api.c | 4 +- 3 files changed, 189 insertions(+), 45 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 5c3696390e..d7b1fe244f 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -42,6 +42,9 @@ /* MQTT include. */ #include "iot_mqtt.h" +/* Atomics include. */ +#include "iot_atomic.h" + /* Validate Jobs configuration settings. */ #if AWS_IOT_JOBS_ENABLE_ASSERTS != 0 && AWS_IOT_JOBS_ENABLE_ASSERTS != 1 #error "AWS_IOT_JOBS_ENABLE_ASSERTS must be 0 or 1." @@ -52,6 +55,13 @@ /*-----------------------------------------------------------*/ +/** + * @brief Check if the library is initialized. + * + * @return `true` if AwsIotJobs_Init was called; `false` otherwise. + */ +static bool _checkInit( void ); + /** * @brief Validate the #AwsIotJobsRequestInfo_t passed to a Jobs API function. * @@ -146,6 +156,13 @@ static void _callbackWrapperCommon( _jobsCallbackType_t type, /*-----------------------------------------------------------*/ +/** + * @brief Tracks whether @ref jobs_function_init has been called. + * + * API functions will fail if @ref jobs_function_init was not called. + */ +static uint32_t _initCalled = 0; + /** * @brief Timeout used for MQTT operations. */ @@ -165,6 +182,22 @@ uint32_t _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; /*-----------------------------------------------------------*/ +static bool _checkInit( void ) +{ + bool status = true; + + if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + { + IotLogError( "AwsIotJobs_Init was not called." ); + + status = false; + } + + return status; +} + +/*-----------------------------------------------------------*/ + static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, const AwsIotJobsRequestInfo_t * pRequestInfo, uint32_t flags, @@ -377,6 +410,12 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, bool subscriptionMutexLocked = false; _jobsSubscription_t * pSubscription = NULL; + /* Check that AwsIotJobs_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); + } + /* Validate Thing Name. */ if( AwsIot_ValidateThingName( pThingName, thingNameLength ) == false ) { @@ -714,27 +753,38 @@ static void _callbackWrapperCommon( _jobsCallbackType_t type, AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) { AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; + bool listInitStatus = false; - bool listInitStatus = AwsIot_InitLists( &_AwsIotJobsPendingOperations, - &_AwsIotJobsSubscriptions, - &_AwsIotJobsPendingOperationsMutex, - &_AwsIotJobsSubscriptionsMutex ); - - if( listInitStatus == false ) + if( _checkInit() == false ) { - IotLogInfo( "Failed to create Jobs lists." ); + listInitStatus = AwsIot_InitLists( &_AwsIotJobsPendingOperations, + &_AwsIotJobsSubscriptions, + &_AwsIotJobsPendingOperationsMutex, + &_AwsIotJobsSubscriptionsMutex ); - status = AWS_IOT_JOBS_INIT_FAILED; - } - else - { - /* Save the MQTT timeout option. */ - if( mqttTimeoutMs != 0 ) + if( listInitStatus == false ) { - _AwsIotJobsMqttTimeoutMs = mqttTimeoutMs; + IotLogInfo( "Failed to create Jobs lists." ); + + status = AWS_IOT_JOBS_INIT_FAILED; } + else + { + /* Save the MQTT timeout option. */ + if( mqttTimeoutMs != 0 ) + { + _AwsIotJobsMqttTimeoutMs = mqttTimeoutMs; + } - IotLogInfo( "Jobs library successfully initialized." ); + /* Set the flag that specifies initialization is complete. */ + ( void ) Atomic_CompareAndSwap_u32( &( _initCalled ), 1, 0 ); + + IotLogInfo( "Jobs library successfully initialized." ); + } + } + else + { + IotLogWarn( "AwsIotJobs_Init called with library already initialized." ); } return status; @@ -744,28 +794,37 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) void AwsIotJobs_Cleanup( void ) { - /* Remove and free all items in the Jobs pending operation list. */ - IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); - IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, - _AwsIotJobs_DestroyOperation, - offsetof( _jobsOperation_t, link ) ); - IotMutex_Unlock( &_AwsIotJobsPendingOperationsMutex ); - - /* Remove and free all items in the Jobs subscription list. */ - IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - IotListDouble_RemoveAll( &_AwsIotJobsSubscriptions, - _AwsIotJobs_DestroySubscription, - offsetof( _jobsSubscription_t, link ) ); - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + uint32_t initCleared = Atomic_CompareAndSwap_u32( &( _initCalled ), 0, 1 ); + + if( initCleared == 1 ) + { + /* Remove and free all items in the Jobs pending operation list. */ + IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); + IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, + _AwsIotJobs_DestroyOperation, + offsetof( _jobsOperation_t, link ) ); + IotMutex_Unlock( &_AwsIotJobsPendingOperationsMutex ); + + /* Remove and free all items in the Jobs subscription list. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + IotListDouble_RemoveAll( &_AwsIotJobsSubscriptions, + _AwsIotJobs_DestroySubscription, + offsetof( _jobsSubscription_t, link ) ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - /* Restore the default MQTT timeout. */ - _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; + /* Restore the default MQTT timeout. */ + _AwsIotJobsMqttTimeoutMs = AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS; - /* Destroy Jobs library mutexes. */ - IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); - IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); + /* Destroy Jobs library mutexes. */ + IotMutex_Destroy( &_AwsIotJobsPendingOperationsMutex ); + IotMutex_Destroy( &_AwsIotJobsSubscriptionsMutex ); - IotLogInfo( "Jobs library cleanup done." ); + IotLogInfo( "Jobs library cleanup done." ); + } + else + { + IotLogWarn( "AwsIotJobs_Init was not called before AwsIotShadow_Cleanup." ); + } } /*-----------------------------------------------------------*/ @@ -778,6 +837,12 @@ AwsIotJobsError_t AwsIotJobs_GetPendingAsync( const AwsIotJobsRequestInfo_t * pR IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); _jobsOperation_t * pOperation = NULL; + /* Check that AwsIotJobs_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); + } + /* Check request info. */ status = _validateRequestInfo( JOBS_GET_PENDING, pRequestInfo, @@ -866,6 +931,12 @@ AwsIotJobsError_t AwsIotJobs_StartNextAsync( const AwsIotJobsRequestInfo_t * pRe _jobsOperation_t * pOperation = NULL; _jsonRequestContents_t requestContents = { 0 }; + /* Check that AwsIotJobs_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); + } + /* Check request info. */ status = _validateRequestInfo( JOBS_START_NEXT, pRequestInfo, @@ -968,6 +1039,12 @@ AwsIotJobsError_t AwsIotJobs_DescribeAsync( const AwsIotJobsRequestInfo_t * pReq _jobsOperation_t * pOperation = NULL; _jsonRequestContents_t requestContents = { 0 }; + /* Check that AwsIotJobs_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); + } + /* Check request info. */ status = _validateRequestInfo( JOBS_DESCRIBE, pRequestInfo, @@ -1063,6 +1140,12 @@ AwsIotJobsError_t AwsIotJobs_UpdateAsync( const AwsIotJobsRequestInfo_t * pReque _jobsOperation_t * pOperation = NULL; _jsonRequestContents_t requestContents = { 0 }; + /* Check that AwsIotJobs_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); + } + /* Check request info. */ status = _validateRequestInfo( JOBS_UPDATE, pRequestInfo, @@ -1160,6 +1243,12 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_STATUS_PENDING ); + /* Check that AwsIotJobs_Init was called. */ + if( _checkInit() == false ) + { + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NOT_INITIALIZED ); + } + /* Check that reference is set. */ if( operation == NULL ) { @@ -1302,6 +1391,10 @@ const char * AwsIotJobs_strerror( AwsIotJobsError_t status ) pMessage = "TIMEOUT"; break; + case AWS_IOT_JOBS_NOT_INITIALIZED: + pMessage = "NOT INITIALIZED"; + break; + case AWS_IOT_JOBS_INVALID_TOPIC: pMessage = "FAILED: INVALID TOPIC"; break; diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index ad302665dc..ed50187ec9 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -251,6 +251,10 @@ TEST( Jobs_Unit_API, Init ) { int32_t i = 0; AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsOperation_t operation = AWS_IOT_JOBS_OPERATION_INITIALIZER; + AwsIotJobsResponse_t response = AWS_IOT_JOBS_RESPONSE_INITIALIZER; /* Check that test set up set the default value. */ TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotJobsMqttTimeoutMs ); @@ -259,6 +263,9 @@ TEST( Jobs_Unit_API, Init ) * before running this test. */ AwsIotJobs_Cleanup(); + /* Calling cleanup twice should not crash. */ + AwsIotJobs_Cleanup(); + /* Set a MQTT timeout. */ AwsIotJobs_Init( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + 1 ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS + 1, _AwsIotJobsMqttTimeoutMs ); @@ -267,7 +274,32 @@ TEST( Jobs_Unit_API, Init ) AwsIotJobs_Cleanup(); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS, _AwsIotJobsMqttTimeoutMs ); - /* Test jobs initialization with mutex creation failures. */ + /* Calling API functions without calling AwsIotJobs_Init should fail. */ + requestInfo.mqttConnection = _pMqttConnection; + requestInfo.pThingName = TEST_THING_NAME; + requestInfo.thingNameLength = TEST_THING_NAME_LENGTH; + status = AwsIotJobs_GetPendingAsync( &requestInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + status = AwsIotJobs_StartNextAsync( &requestInfo, &updateInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + status = AwsIotJobs_DescribeAsync( &requestInfo, AWS_IOT_JOBS_NO_EXECUTION_NUMBER, false, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + status = AwsIotJobs_UpdateAsync( &requestInfo, &updateInfo, 0, NULL, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + status = AwsIotJobs_Wait( operation, 500, &response ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + status = AwsIotJobs_SetNotifyPendingCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NOT_INITIALIZED, status ); + + /* Test Jobs initialization with mutex creation failures. */ for( i = 0; ; i++ ) { UnityMalloc_MakeMallocFailAfterCount( i ); @@ -282,6 +314,9 @@ TEST( Jobs_Unit_API, Init ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_INIT_FAILED, status ); } + + /* Initialize the Jobs library for test clean up. Calling init twice should not crash. */ + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, AwsIotJobs_Init( 0 ) ); } /*-----------------------------------------------------------*/ @@ -295,29 +330,43 @@ TEST( Jobs_Unit_API, StringCoverage ) int32_t i = 0; const char * pMessage = NULL; + const char * pInvalidStatus = "INVALID STATUS"; + size_t invalidStatusLength = strlen( pInvalidStatus ); + /* For each Jobs Error, check the returned string. */ - while( true ) + const AwsIotJobsError_t pApiErrors[] = + { + AWS_IOT_JOBS_SUCCESS, AWS_IOT_JOBS_STATUS_PENDING, AWS_IOT_JOBS_INIT_FAILED, + AWS_IOT_JOBS_BAD_PARAMETER, AWS_IOT_JOBS_NO_MEMORY, AWS_IOT_JOBS_MQTT_ERROR, + AWS_IOT_JOBS_BAD_RESPONSE, AWS_IOT_JOBS_TIMEOUT, AWS_IOT_JOBS_NOT_INITIALIZED, + AWS_IOT_JOBS_INVALID_TOPIC, AWS_IOT_JOBS_INVALID_JSON, AWS_IOT_JOBS_INVALID_REQUEST, + AWS_IOT_JOBS_INVALID_STATE, AWS_IOT_JOBS_NOT_FOUND, AWS_IOT_JOBS_VERSION_MISMATCH, + AWS_IOT_JOBS_INTERNAL_ERROR, AWS_IOT_JOBS_THROTTLED, AWS_IOT_JOBS_TERMINAL_STATE + }; + + for( i = 0; i < ( sizeof( pApiErrors ) / sizeof( pApiErrors[ 0 ] ) ); i++ ) { - pMessage = AwsIotJobs_strerror( ( AwsIotJobsError_t ) i ); + pMessage = AwsIotJobs_strerror( pApiErrors[ i ] ); TEST_ASSERT_NOT_NULL( pMessage ); - if( strncmp( "INVALID STATUS", pMessage, 14 ) == 0 ) - { - break; - } - - i++; + TEST_ASSERT_NOT_EQUAL( 0, strncmp( pInvalidStatus, pMessage, invalidStatusLength ) ); } + /* Check an invalid status. */ + pMessage = AwsIotJobs_strerror( ( AwsIotJobsError_t ) -1 ); + TEST_ASSERT_EQUAL_STRING( pInvalidStatus, pMessage ); + /* For each Jobs State, check the returned string. */ i = 0; + const char * pInvalidState = "INVALID STATE"; + size_t invalidStateLength = strlen( pInvalidState ); while( true ) { pMessage = AwsIotJobs_StateName( ( AwsIotJobState_t ) i ); TEST_ASSERT_NOT_NULL( pMessage ); - if( strncmp( "INVALID STATE", pMessage, 13 ) == 0 ) + if( strncmp( pInvalidState, pMessage, invalidStateLength ) == 0 ) { break; } diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 3acfedba5e..3be9ebd2c8 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -27,6 +27,9 @@ /* The config header is always included first. */ #include "iot_config.h" +/* Standard includes. */ +#include + /* SDK initialization include. */ #include "iot_init.h" @@ -262,7 +265,6 @@ TEST( Shadow_Unit_API, StringCoverage ) /* Check an invalid status. */ pMessage = AwsIotShadow_strerror( ( AwsIotShadowError_t ) -1 ); - TEST_ASSERT_NOT_NULL( pMessage ); TEST_ASSERT_EQUAL_STRING( pInvalidStatus, pMessage ); } From 447e55501704f230b903a0be19337c7fb532c368 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Tue, 17 Sep 2019 16:01:10 -0700 Subject: [PATCH 261/844] Use default defines for standard functions (#566) --- demos/iot_config.h | 8 +++ doc/lib/mqtt.txt | 6 +- doc/lib/taskpool.txt | 2 +- doc/mainpage.txt | 18 +++++ lib/include/iot_serializer.h | 68 +++++++++++++++---- .../private/aws_iot_defender_internal.h | 42 ++++++++---- lib/include/private/iot_mqtt_internal.h | 57 ++++++++++++---- lib/include/private/iot_taskpool_internal.h | 53 +++++++++++---- tests/iot_config.h | 35 ++-------- 9 files changed, 205 insertions(+), 84 deletions(-) diff --git a/demos/iot_config.h b/demos/iot_config.h index 1f3a2ff627..21a05fff5a 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -72,6 +72,14 @@ #define AWS_IOT_LOG_LEVEL_DEFENDER IOT_LOG_INFO #define AWS_IOT_LOG_LEVEL_JOBS IOT_LOG_INFO +/* Default assert and memory allocation functions. */ +#include +#include + +#define Iot_DefaultAssert assert +#define Iot_DefaultMalloc malloc +#define Iot_DefaultFree free + /* The build system will choose the appropriate system types file for the platform * layer based on the host operating system. */ #include IOT_SYSTEM_TYPES_FILE diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index aa1415d76a..3299636cf4 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -162,7 +162,7 @@ This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait @section IOT_MQTT_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the MQTT library. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotMqtt_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotMqtt_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
@@ -224,7 +224,7 @@ QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. T @brief Assertion function used when @ref IOT_MQTT_ENABLE_ASSERTS is `1`. @configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. +@configdefault @ref Iot_DefaultAssert if @ref IOT_MQTT_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the MQTT library will fail to build. @section mqtt_config_memory Memory allocation @brief The following functions may be re-implemented for the MQTT library. @@ -245,6 +245,8 @@ QoS 1 PUBLISH retransmissions follow a truncated exponential backoff strategy. T - #IotMqtt_FreeSubscription
@copybrief IotMqtt_FreeSubscription +If a custom implementation is not set for an MQTT memory allocation function, @ref Iot_DefaultMalloc will be used. If @ref Iot_DefaultMalloc are not set, the MQTT library will fail to build. + When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for MQTT. - @anchor IOT_MQTT_CONNECTIONS IOT_MQTT_CONNECTIONS
Number of MQTT connections that may be opened. Defaults to `1` if undefined. diff --git a/doc/lib/taskpool.txt b/doc/lib/taskpool.txt index d7f444f998..c1dff7d3af 100644 --- a/doc/lib/taskpool.txt +++ b/doc/lib/taskpool.txt @@ -115,7 +115,7 @@ The task pool will cache when the application calling @ref IotTaskPool_RecycleJo @section IOT_TASKPOOL_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the task pool library. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotTaskPool_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotTaskPool_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
diff --git a/doc/mainpage.txt b/doc/mainpage.txt index bf697b15bd..eff72d9037 100644 --- a/doc/mainpage.txt +++ b/doc/mainpage.txt @@ -46,6 +46,24 @@ When dynamic memory allocation is disabled, all of the memory allocation functio @configdefault `0` @attention This settings has no effect on system calls or third-party libraries; it may also have no effect on [platform layer] (@ref platform) implementations. + +@section Iot_DefaultMalloc IotDefault_Malloc and Iot_DefaultFree +@brief Set these to the names of the default memory allocation functions to use across all libraries. + +No default value is provided for this setting. If these functions are not set, libraries will fail to build. + +All libraries allow each memory allocation function to be overridden with a custom implementation. If a function does not have a custom implementation set, then these functions will be used. + +@configpossible Functions that match the signatures of [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) and [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + +@section Iot_DefaultAssert +@brief Set this to the name of the default assert function to use across all libraries. + +No default value is provided for this setting. If this function is not set, libraries will fail to build when asserts are enabled. + +All libraries allow the assert function to be overridden with a custom implementation. If a custom implementation assert function is not set, then this functions will be used. + +@configpossible A function that matches the signature of [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html). */ /** diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index 7016f59133..db79c3761b 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -42,12 +42,15 @@ #if IOT_SERIALIZER_ENABLE_ASSERTS == 1 #ifndef IotSerializer_Assert - #include - #define IotSerializer_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define IotSerializer_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for serializer, but IotSerializer_Assert is not defined" + #endif #endif -#else +#else /* if IOT_SERIALIZER_ENABLE_ASSERTS == 1 */ #define IotSerializer_Assert( expression ) -#endif +#endif /* if IOT_SERIALIZER_ENABLE_ASSERTS == 1 */ /* * Provide default values for undefined memory allocation functions based on @@ -112,31 +115,68 @@ */ void IotSerializer_FreeDecoderObject( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY */ - #include - #ifndef IotSerializer_MallocCborEncoder - #define IotSerializer_MallocCborEncoder malloc + #ifdef Iot_DefaultMalloc + #define IotSerializer_MallocCborEncoder Iot_DefaultMalloc + #else + #error "No malloc function defined for IotSerializer_MallocCborEncoder" + #endif #endif + #ifndef IotSerializer_FreeCborEncoder - #define IotSerializer_FreeCborEncoder free + #ifdef Iot_DefaultFree + #define IotSerializer_FreeCborEncoder Iot_DefaultFree + #else + #error "No free function defined for IotSerializer_FreeCborEncoder" + #endif #endif + #ifndef IotSerializer_MallocCborParser - #define IotSerializer_MallocCborParser malloc + #ifdef Iot_DefaultMalloc + #define IotSerializer_MallocCborParser Iot_DefaultMalloc + #else + #error "No malloc function defined for IotSerializer_MallocCborParser" + #endif #endif + #ifndef IotSerializer_FreeCborParser - #define IotSerializer_FreeCborParser free + #ifdef Iot_DefaultFree + #define IotSerializer_FreeCborParser Iot_DefaultFree + #else + #error "No free function defined for IotSerializer_FreeCborParser" + #endif #endif + #ifndef IotSerializer_MallocCborValue - #define IotSerializer_MallocCborValue malloc + #ifdef Iot_DefaultMalloc + #define IotSerializer_MallocCborValue Iot_DefaultMalloc + #else + #error "No malloc function defined for IotSerializer_MallocCborValue" + #endif #endif + #ifndef IotSerializer_FreeCborValue - #define IotSerializer_FreeCborValue free + #ifdef Iot_DefaultFree + #define IotSerializer_FreeCborValue Iot_DefaultFree + #else + #error "No free function defined for IotSerializer_FreeCborValue" + #endif #endif + #ifndef IotSerializer_MallocDecoderObject - #define IotSerializer_MallocDecoderObject malloc + #ifdef Iot_DefaultMalloc + #define IotSerializer_MallocDecoderObject Iot_DefaultMalloc + #else + #error "No malloc function defined for IotSerializer_MallocDecoderObject" + #endif #endif + #ifndef IotSerializer_FreeDecoderObject - #define IotSerializer_FreeDecoderObject free + #ifdef Iot_DefaultFree + #define IotSerializer_FreeDecoderObject Iot_DefaultFree + #else + #error "No free function defined for IotSerializer_FreeDecoderObject" + #endif #endif #endif /* if IOT_STATIC_MEMORY_ONLY */ diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index c0559a3bfa..c1820dd8bf 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -53,12 +53,15 @@ */ #if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 #ifndef AwsIotDefender_Assert - #include - #define AwsIotDefender_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define AwsIotDefender_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for Defender, but AwsIotDefender_Assert is not defined" + #endif #endif -#else +#else /* if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 */ #define AwsIotDefender_Assert( expression ) -#endif +#endif /* if AWS_IOT_DEFENDER_ENABLE_ASSERTS == 1 */ /* Configure logs for Defender functions. */ #ifdef AWS_IOT_LOG_LEVEL_DEFENDER @@ -109,24 +112,37 @@ */ #define AwsIotDefender_FreeTopic Iot_FreeMessageBuffer #else /* if IOT_STATIC_MEMORY_ONLY */ - #include - #ifndef AwsIotDefender_MallocReport - #define AwsIotDefender_MallocReport malloc + #ifdef Iot_DefaultMalloc + #define AwsIotDefender_MallocReport Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotDefender_MallocReport" + #endif #endif #ifndef AwsIotDefender_FreeReport - #define AwsIotDefender_FreeReport free + #ifdef Iot_DefaultFree + #define AwsIotDefender_FreeReport Iot_DefaultFree + #else + #error "No free function defined for AwsIotDefender_FreeReport" + #endif #endif #ifndef AwsIotDefender_MallocTopic - #define AwsIotDefender_MallocTopic malloc + #ifdef Iot_DefaultMalloc + #define AwsIotDefender_MallocTopic Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotDefender_MallocTopic" + #endif #endif #ifndef AwsIotDefender_FreeTopic - #define AwsIotDefender_FreeTopic free + #ifdef Iot_DefaultFree + #define AwsIotDefender_FreeTopic Iot_DefaultFree + #else + #error "No free function defined for AwsIotDefender_FreeTopic" + #endif #endif - #endif /* if IOT_STATIC_MEMORY_ONLY */ /** @@ -232,9 +248,9 @@ * Define encoder/decoder based on configuration AWS_IOT_DEFENDER_FORMAT. */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR - #define DEFENDER_FORMAT "cbor" + #define DEFENDER_FORMAT "cbor" #elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - #define DEFENDER_FORMAT "json" + #define DEFENDER_FORMAT "json" #else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ #error "AWS_IOT_DEFENDER_FORMAT must be either AWS_IOT_DEFENDER_FORMAT_CBOR or AWS_IOT_DEFENDER_FORMAT_JSON." #endif /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index cb79baca3a..29a1f100b4 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -51,8 +51,11 @@ */ #if IOT_MQTT_ENABLE_ASSERTS == 1 #ifndef IotMqtt_Assert - #include - #define IotMqtt_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define IotMqtt_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for MQTT, but IotMqtt_Assert is not defined" + #endif #endif #else #define IotMqtt_Assert( expression ) @@ -135,38 +138,68 @@ */ void IotMqtt_FreeSubscription( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #include - #ifndef IotMqtt_MallocConnection - #define IotMqtt_MallocConnection malloc + #ifdef Iot_DefaultMalloc + #define IotMqtt_MallocConnection Iot_DefaultMalloc + #else + #error "No malloc function defined for IotMqtt_MallocConnection" + #endif #endif #ifndef IotMqtt_FreeConnection - #define IotMqtt_FreeConnection free + #ifdef Iot_DefaultFree + #define IotMqtt_FreeConnection Iot_DefaultFree + #else + #error "No free function defined for IotMqtt_FreeConnection" + #endif #endif #ifndef IotMqtt_MallocMessage - #define IotMqtt_MallocMessage malloc + #ifdef Iot_DefaultMalloc + #define IotMqtt_MallocMessage Iot_DefaultMalloc + #else + #error "No malloc function defined for IotMqtt_MallocMessage" + #endif #endif #ifndef IotMqtt_FreeMessage - #define IotMqtt_FreeMessage free + #ifdef Iot_DefaultFree + #define IotMqtt_FreeMessage Iot_DefaultFree + #else + #error "No free function defined for IotMqtt_FreeMessage" + #endif #endif #ifndef IotMqtt_MallocOperation - #define IotMqtt_MallocOperation malloc + #ifdef Iot_DefaultMalloc + #define IotMqtt_MallocOperation Iot_DefaultMalloc + #else + #error "No malloc function defined for IotMqtt_MallocOperation" + #endif #endif #ifndef IotMqtt_FreeOperation - #define IotMqtt_FreeOperation free + #ifdef Iot_DefaultFree + #define IotMqtt_FreeOperation Iot_DefaultFree + #else + #error "No free function defined for IotMqtt_FreeOperation" + #endif #endif #ifndef IotMqtt_MallocSubscription - #define IotMqtt_MallocSubscription malloc + #ifdef Iot_DefaultMalloc + #define IotMqtt_MallocSubscription Iot_DefaultMalloc + #else + #error "No malloc function defined for IotMqtt_MallocSubscription" + #endif #endif #ifndef IotMqtt_FreeSubscription - #define IotMqtt_FreeSubscription free + #ifdef Iot_DefaultFree + #define IotMqtt_FreeSubscription Iot_DefaultFree + #else + #error "No free function defined for IotMqtt_FreeSubscription" + #endif #endif #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index e047193707..97230536a1 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -110,12 +110,15 @@ */ #if IOT_TASKPOOL_ENABLE_ASSERTS == 1 #ifndef IotTaskPool_Assert - #include - #define IotTaskPool_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define IotTaskPool_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for Task Pool, but IotTaskPool_Assert is not defined" + #endif #endif -#else +#else /* if IOT_TASKPOOL_ENABLE_ASSERTS == 1 */ #define IotTaskPool_Assert( expression ) -#endif +#endif /* if IOT_TASKPOOL_ENABLE_ASSERTS == 1 */ /* Configure logs for TASKPOOL functions. */ #ifdef IOT_LOG_LEVEL_TASKPOOL @@ -179,29 +182,52 @@ #include #ifndef IotTaskPool_MallocTaskPool - #define IotTaskPool_MallocTaskPool malloc + #ifdef Iot_DefaultMalloc + #define IotTaskPool_MallocTaskPool Iot_DefaultMalloc + #else + #error "No malloc function defined for IotTaskPool_MallocTaskPool" + #endif #endif #ifndef IotTaskPool_FreeTaskPool - #define IotTaskPool_FreeTaskPool free + #ifdef Iot_DefaultFree + #define IotTaskPool_FreeTaskPool Iot_DefaultFree + #else + #error "No free function defined for IotTaskPool_FreeTaskPool" + #endif #endif #ifndef IotTaskPool_MallocJob - #define IotTaskPool_MallocJob malloc + #ifdef Iot_DefaultMalloc + #define IotTaskPool_MallocJob Iot_DefaultMalloc + #else + #error "No malloc function defined for IotTaskPool_MallocJob" + #endif #endif #ifndef IotTaskPool_FreeJob - #define IotTaskPool_FreeJob free + #ifdef Iot_DefaultFree + #define IotTaskPool_FreeJob Iot_DefaultFree + #else + #error "No free function defined for IotTaskPool_FreeJob" + #endif #endif #ifndef IotTaskPool_MallocTimerEvent - #define IotTaskPool_MallocTimerEvent malloc + #ifdef Iot_DefaultMalloc + #define IotTaskPool_MallocTimerEvent Iot_DefaultMalloc + #else + #error "No malloc function defined for IotTaskPool_MallocTimerEvent" + #endif #endif - + #ifndef IotTaskPool_FreeTimerEvent - #define IotTaskPool_FreeTimerEvent free + #ifdef Iot_DefaultFree + #define IotTaskPool_FreeTimerEvent Iot_DefaultFree + #else + #error "No free function defined for IotTaskPool_FreeTimerEvent" + #endif #endif - #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ /* ---------------------------------------------------------------------------------------------- */ @@ -225,6 +251,7 @@ typedef struct _taskPoolCache { IotListDouble_t freeList; /**< @brief A list ot hold cached jobs. */ + uint32_t freeCount; /**< @brief A counter to track the number of jobs in the cache. */ } _taskPoolCache_t; @@ -272,7 +299,7 @@ typedef struct _taskPoolJob /** * @brief Represents an operation that is subject to a timer. * - * These events are queued per MQTT connection. They are sorted by their + * These events are queued per task pool. They are sorted by their * expiration time. */ typedef struct _taskPoolTimerEvent diff --git a/tests/iot_config.h b/tests/iot_config.h index a027cb2d99..e52e469501 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -127,6 +127,10 @@ #define IOT_TASKPOOLS ( 4 ) #endif +/* Default assert function. */ +#include +#define Iot_DefaultAssert assert + /* Memory allocation function configuration. Note that these functions will not * be affected by IOT_STATIC_MEMORY_ONLY. */ #define IotThreads_Malloc unity_malloc_mt @@ -142,30 +146,8 @@ /* Memory allocation function configuration for libraries affected by * IOT_STATIC_MEMORY_ONLY. */ #if IOT_STATIC_MEMORY_ONLY == 0 - #define IotTaskPool_MallocTaskPool unity_malloc_mt - #define IotTaskPool_FreeTaskPool unity_free_mt - #define IotTaskPool_MallocJob unity_malloc_mt - #define IotTaskPool_FreeJob unity_free_mt - #define IotTaskPool_MallocTimerEvent unity_malloc_mt - #define IotTaskPool_FreeTimerEvent unity_free_mt - - #define IotMqtt_MallocMessage unity_malloc_mt - #define IotMqtt_FreeMessage unity_free_mt - #define IotMqtt_MallocConnection unity_malloc_mt - #define IotMqtt_FreeConnection unity_free_mt - #define IotMqtt_MallocOperation unity_malloc_mt - #define IotMqtt_FreeOperation unity_free_mt - #define IotMqtt_MallocSubscription unity_malloc_mt - #define IotMqtt_FreeSubscription unity_free_mt - - #define IotSerializer_MallocCborEncoder unity_malloc_mt - #define IotSerializer_FreeCborEncoder unity_free_mt - #define IotSerializer_MallocCborParser unity_malloc_mt - #define IotSerializer_FreeCborParser unity_free_mt - #define IotSerializer_MallocCborValue unity_malloc_mt - #define IotSerializer_FreeCborValue unity_free_mt - #define IotSerializer_MallocDecoderObject unity_malloc_mt - #define IotSerializer_FreeDecoderObject unity_free_mt + #define Iot_DefaultMalloc unity_malloc_mt + #define Iot_DefaultFree unity_free_mt #define AwsIotShadow_MallocOperation unity_malloc_mt #define AwsIotShadow_FreeOperation unity_free_mt @@ -174,11 +156,6 @@ #define AwsIotShadow_MallocSubscription unity_malloc_mt #define AwsIotShadow_FreeSubscription unity_free_mt - #define AwsIotDefender_MallocReport unity_malloc_mt - #define AwsIotDefender_FreeReport unity_free_mt - #define AwsIotDefender_MallocTopic unity_malloc_mt - #define AwsIotDefender_FreeTopic unity_free_mt - #define AwsIotJobs_MallocOperation unity_malloc_mt #define AwsIotJobs_FreeOperation unity_free_mt #define AwsIotJobs_MallocString unity_malloc_mt From 1c2d104d71c925c4b6d2ae3b59918a6edf54d9ec Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 18 Sep 2019 10:04:32 -0700 Subject: [PATCH 262/844] Apply default function defines to Shadow and Jobs (#567) --- doc/guide/developer/porting.txt | 17 ++++++- doc/lib/jobs.txt | 6 ++- doc/lib/linear_containers.txt | 4 +- doc/lib/shadow.txt | 6 ++- lib/include/iot_linear_containers.h | 33 +++++++------ lib/include/private/aws_iot_jobs_internal.h | 45 +++++++++++++---- lib/include/private/aws_iot_shadow_internal.h | 49 ++++++++++++++----- tests/iot_config.h | 18 +------ 8 files changed, 117 insertions(+), 61 deletions(-) diff --git a/doc/guide/developer/porting.txt b/doc/guide/developer/porting.txt index f1c49fd4be..afc645b231 100644 --- a/doc/guide/developer/porting.txt +++ b/doc/guide/developer/porting.txt @@ -2,9 +2,10 @@ @page guide_developer_porting Porting Guide @brief Guide for porting this SDK to a new platform. -This SDK has two components that must be ported to a new system: +This SDK has three components that must be ported to a new system: 1. [The build system](@ref guide_developer_porting_build) -2. [The platform layer](@ref guide_developer_porting_platform) +2. [The config header](@ref guide_developer_config) +3. [The platform layer](@ref guide_developer_porting_platform) @section guide_developer_porting_build Porting the build system @brief Guide for porting the SDK's build system. @@ -98,6 +99,18 @@ Additional include paths required to build the tests: In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1` globally when building tests. +@section guide_developer_config Config header +@brief Settings that must be set in the config header, iot_config.h + +@see [Global configuration](@ref global_config)
+In addition, each library has its own configuration settings. + +At the very least, the config header must contain the following defines: + - Memory allocation functions must be set in the config header. See @ref Iot_DefaultMalloc for more details. + - Assert functions must be set if asserts are enabled for a library. See @ref Iot_DefaultAssert for more details. + +The platform types must also be set. See @ref guide_developer_porting_platform for more details. + @section guide_developer_porting_platform Porting the platform layer @brief Guide for porting the SDK's [platform layer](@ref platform). diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index dfa2fe18b5..0a7ad68deb 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -130,7 +130,7 @@ MQTT PINGREQ packets will be sent at this interval. @section AWS_IOT_JOBS_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the Jobs library. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotJobs_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotJobs_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
@@ -157,7 +157,7 @@ Log messages from the Jobs library at or below this setting will be printed. @brief Assertion function used when @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`. @configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`; otherwise, nothing. +@configdefault @ref Iot_DefaultAssert if @ref AWS_IOT_JOBS_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the Jobs library will fail to build. @section jobs_config_memory Memory allocation @brief The following functions may be re-implemented for the Jobs library. @@ -174,6 +174,8 @@ Log messages from the Jobs library at or below this setting will be printed. - #AwsIotJobs_FreeSubscription
@copybrief AwsIotJobs_FreeSubscription +If a custom implementation is not set for a Jobs memory allocation function, @ref Iot_DefaultMalloc will be used. If @ref Iot_DefaultMalloc are not set, the Jobs library will fail to build. + When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for Jobs. - @anchor AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS AWS_IOT_JOBS_MAX_IN_PROGRESS_OPERATIONS
Maximum number of Jobs operations that may be in-progress at any time. Defaults to `10` if undefined. diff --git a/doc/lib/linear_containers.txt b/doc/lib/linear_containers.txt index 3ec2a990fd..455117ad96 100644 --- a/doc/lib/linear_containers.txt +++ b/doc/lib/linear_containers.txt @@ -14,7 +14,7 @@ Currently, linear containers are implemented with `static inline` functions and @section IOT_CONTAINERS_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using linear containers. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotContainers_Assert can be defined to set the assert function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref IotContainers_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
@@ -24,5 +24,5 @@ Asserts are useful for debugging, but should be disabled in production code. If @brief Assertion function used when @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`. @configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`; otherwise, nothing. +@configdefault @ref Iot_DefaultAssert if @ref IOT_CONTAINERS_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the containers library will fail to build. */ diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 29618edbca..ff33953f72 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -119,7 +119,7 @@ MQTT PINGREQ packets will be sent at this interval. @section AWS_IOT_SHADOW_ENABLE_ASSERTS @brief Set this to `1` to perform sanity checks when using the Shadow library. -Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotShadow_Assert can be defined to set the assertion function; otherwise, the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function will be used. +Asserts are useful for debugging, but should be disabled in production code. If this is set to `1`, @ref AwsIotShadow_Assert can be defined to set the assertion function; otherwise, the @ref Iot_DefaultAssert will be used. @configpossible `0` (asserts disabled) or `1` (asserts enabled)
@configrecommended `1` when debugging; `0` in production code.
@@ -146,7 +146,7 @@ Log messages from the Shadow library at or below this setting will be printed. @brief Assertion function used when @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`. @configpossible Any function with the same signature as the standard library's [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function.
-@configdefault Standard library [assert](http://pubs.opengroup.org/onlinepubs/9699919799/functions/assert.html) function if @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`; otherwise, nothing. +@configdefault @ref Iot_DefaultAssert if @ref AWS_IOT_SHADOW_ENABLE_ASSERTS is `1`; otherwise, nothing. If @ref Iot_DefaultAssert is not defined when asserts are enabled, the Shadow library will fail to build. @section shadow_config_memory Memory allocation @brief The following functions may be re-implemented for the Shadow library. @@ -163,6 +163,8 @@ Log messages from the Shadow library at or below this setting will be printed. - #AwsIotShadow_FreeSubscription
@copybrief AwsIotShadow_FreeSubscription +If a custom implementation is not set for a Shadow memory allocation function, @ref Iot_DefaultMalloc will be used. If @ref Iot_DefaultMalloc are not set, the Shadow library will fail to build. + When @ref IOT_STATIC_MEMORY_ONLY is `1`, the following settings configure the number of statically-allocated buffers for Shadow. - @anchor AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS AWS_IOT_SHADOW_MAX_IN_PROGRESS_OPERATIONS
Maximum number of Shadow operations that may be in-progress at any time. Defaults to `10` if undefined. diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 42b65871e7..11ede95646 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -99,12 +99,15 @@ typedef IotLink_t IotDeQueue_t; */ #if IOT_CONTAINERS_ENABLE_ASSERTS == 1 #ifndef IotContainers_Assert - #include - #define IotContainers_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define IotContainers_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for containers, but IotContainers_Assert is not defined" + #endif #endif -#else +#else /* if IOT_CONTAINERS_ENABLE_ASSERTS == 1 */ #define IotContainers_Assert( expression ) -#endif +#endif /* if IOT_CONTAINERS_ENABLE_ASSERTS == 1 */ /** * @brief Calculates the starting address of a containing struct. @@ -124,9 +127,9 @@ typedef IotLink_t IotDeQueue_t; * @param[in] pStart The first element to iterate from. * @param[out] pLink Pointer to a container element. */ -#define IotContainers_ForEach( pStart, pLink ) \ - for( ( pLink ) = ( pStart )->pNext; \ - ( pLink ) != ( pStart ); \ +#define IotContainers_ForEach( pStart, pLink ) \ + for( ( pLink ) = ( pStart )->pNext; \ + ( pLink ) != ( pStart ); \ ( pLink ) = ( pLink )->pNext ) /** @@ -838,7 +841,7 @@ static inline IotLink_t * IotDeQueue_PeekTail( const IotDeQueue_t * const pQueue */ /* @[declare_linear_containers_queue_enqueuehead] */ static inline void IotDeQueue_EnqueueHead( IotDeQueue_t * const pQueue, - IotLink_t * const pLink ) + IotLink_t * const pLink ) /* @[declare_linear_containers_queue_enqueuehead] */ { IotListDouble_InsertHead( pQueue, pLink ); @@ -868,7 +871,7 @@ static inline IotLink_t * IotDeQueue_DequeueHead( IotDeQueue_t * const pQueue ) */ /* @[declare_linear_containers_queue_enqueuetail] */ static inline void IotDeQueue_EnqueueTail( IotDeQueue_t * const pQueue, - IotLink_t * const pLink ) + IotLink_t * const pLink ) /* @[declare_linear_containers_queue_enqueuetail] */ { IotListDouble_InsertTail( pQueue, pLink ); @@ -915,8 +918,8 @@ static inline void IotDeQueue_Remove( IotLink_t * const pLink ) */ /* @[declare_linear_containers_queue_removeall] */ static inline void IotDeQueue_RemoveAll( IotDeQueue_t * const pQueue, - void ( * freeElement )( void * ), - size_t linkOffset ) + void ( * freeElement )( void * ), + size_t linkOffset ) /* @[declare_linear_containers_queue_removeall] */ { IotListDouble_RemoveAll( pQueue, freeElement, linkOffset ); @@ -940,10 +943,10 @@ static inline void IotDeQueue_RemoveAll( IotDeQueue_t * const pQueue, */ /* @[declare_linear_containers_queue_removeallmatches] */ static inline void IotDeQueue_RemoveAllMatches( IotDeQueue_t * const pQueue, - bool ( * isMatch )( const IotLink_t *, void * ), - void * pMatch, - void ( * freeElement )( void * ), - size_t linkOffset ) + bool ( * isMatch )( const IotLink_t *, void * ), + void * pMatch, + void ( * freeElement )( void * ), + size_t linkOffset ) /* @[declare_linear_containers_queue_removeallmatches] */ { IotListDouble_RemoveAllMatches( pQueue, isMatch, pMatch, freeElement, linkOffset ); diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 69ef60704e..d5c949b1d7 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -51,8 +51,11 @@ */ #if AWS_IOT_JOBS_ENABLE_ASSERTS == 1 #ifndef AwsIotJobs_Assert - #include - #define AwsIotJobs_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define AwsIotJobs_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for Jobs, but AwsIotJobs_Assert is not defined" + #endif #endif #else #define AwsIotJobs_Assert( expression ) @@ -121,30 +124,52 @@ */ void AwsIotJobs_FreeSubscription( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #include - #ifndef AwsIotJobs_MallocOperation - #define AwsIotJobs_MallocOperation malloc + #ifdef Iot_DefaultMalloc + #define AwsIotJobs_MallocOperation Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotJobs_MallocOperation" + #endif #endif #ifndef AwsIotJobs_FreeOperation - #define AwsIotJobs_FreeOperation free + #ifdef Iot_DefaultFree + #define AwsIotJobs_FreeOperation Iot_DefaultFree + #else + #error "No free function defined for AwsIotJobs_FreeOperation" + #endif #endif #ifndef AwsIotJobs_MallocString - #define AwsIotJobs_MallocString malloc + #ifdef Iot_DefaultMalloc + #define AwsIotJobs_MallocString Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotJobs_MallocString" + #endif #endif #ifndef AwsIotJobs_FreeString - #define AwsIotJobs_FreeString free + #ifdef Iot_DefaultFree + #define AwsIotJobs_FreeString Iot_DefaultFree + #else + #error "No free function defined for AwsIotJobs_FreeString" + #endif #endif #ifndef AwsIotJobs_MallocSubscription - #define AwsIotJobs_MallocSubscription malloc + #ifdef Iot_DefaultMalloc + #define AwsIotJobs_MallocSubscription Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotJobs_MallocSubscription" + #endif #endif #ifndef AwsIotJobs_FreeSubscription - #define AwsIotJobs_FreeSubscription free + #ifdef Iot_DefaultFree + #define AwsIotJobs_FreeSubscription Iot_DefaultFree + #else + #error "No free function defined for AwsIotJobs_FreeSubscription" + #endif #endif #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index 2fdc7701ba..fea1a1163c 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -54,12 +54,15 @@ */ #if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 #ifndef AwsIotShadow_Assert - #include - #define AwsIotShadow_Assert( expression ) assert( expression ) + #ifdef Iot_DefaultAssert + #define AwsIotShadow_Assert( expression ) Iot_DefaultAssert( expression ) + #else + #error "Asserts are enabled for Shadow, but AwsIotShadow_Assert is not defined" + #endif #endif -#else +#else /* if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 */ #define AwsIotShadow_Assert( expression ) -#endif +#endif /* if AWS_IOT_SHADOW_ENABLE_ASSERTS == 1 */ /* Configure logs for Shadow functions. */ #ifdef AWS_IOT_LOG_LEVEL_SHADOW @@ -124,30 +127,52 @@ */ void AwsIotShadow_FreeSubscription( void * ptr ); #else /* if IOT_STATIC_MEMORY_ONLY == 1 */ - #include - #ifndef AwsIotShadow_MallocOperation - #define AwsIotShadow_MallocOperation malloc + #ifdef Iot_DefaultMalloc + #define AwsIotShadow_MallocOperation Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotShadow_MallocOperation" + #endif #endif #ifndef AwsIotShadow_FreeOperation - #define AwsIotShadow_FreeOperation free + #ifdef Iot_DefaultFree + #define AwsIotShadow_FreeOperation Iot_DefaultFree + #else + #error "No free function defined for AwsIotShadow_FreeOperation" + #endif #endif #ifndef AwsIotShadow_MallocString - #define AwsIotShadow_MallocString malloc + #ifdef Iot_DefaultMalloc + #define AwsIotShadow_MallocString Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotShadow_MallocString" + #endif #endif #ifndef AwsIotShadow_FreeString - #define AwsIotShadow_FreeString free + #ifdef Iot_DefaultFree + #define AwsIotShadow_FreeString Iot_DefaultFree + #else + #error "No free function defined for AwsIotShadow_FreeString" + #endif #endif #ifndef AwsIotShadow_MallocSubscription - #define AwsIotShadow_MallocSubscription malloc + #ifdef Iot_DefaultMalloc + #define AwsIotShadow_MallocSubscription Iot_DefaultMalloc + #else + #error "No malloc function defined for AwsIotShadow_MallocSubscription" + #endif #endif #ifndef AwsIotShadow_FreeSubscription - #define AwsIotShadow_FreeSubscription free + #ifdef Iot_DefaultFree + #define AwsIotShadow_FreeSubscription Iot_DefaultFree + #else + #error "No free function defined for AwsIotShadow_FreeSubscription" + #endif #endif #endif /* if IOT_STATIC_MEMORY_ONLY == 1 */ diff --git a/tests/iot_config.h b/tests/iot_config.h index e52e469501..4fa26ed9e5 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -19,8 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* This file contains configuration settings for the tests. Currently, the tests - * must run on POSIX systems. */ +/* This file contains configuration settings for the tests. */ #ifndef IOT_CONFIG_H_ #define IOT_CONFIG_H_ @@ -107,6 +106,7 @@ #define IOT_CONTAINERS_ENABLE_ASSERTS ( 1 ) #define IOT_MQTT_ENABLE_ASSERTS ( 1 ) #define IOT_TASKPOOL_ENABLE_ASSERTS ( 1 ) +#define IOT_SERIALIZER_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_SHADOW_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_DEFENDER_ENABLE_ASSERTS ( 1 ) #define AWS_IOT_JOBS_ENABLE_ASSERTS ( 1 ) @@ -148,20 +148,6 @@ #if IOT_STATIC_MEMORY_ONLY == 0 #define Iot_DefaultMalloc unity_malloc_mt #define Iot_DefaultFree unity_free_mt - - #define AwsIotShadow_MallocOperation unity_malloc_mt - #define AwsIotShadow_FreeOperation unity_free_mt - #define AwsIotShadow_MallocString unity_malloc_mt - #define AwsIotShadow_FreeString unity_free_mt - #define AwsIotShadow_MallocSubscription unity_malloc_mt - #define AwsIotShadow_FreeSubscription unity_free_mt - - #define AwsIotJobs_MallocOperation unity_malloc_mt - #define AwsIotJobs_FreeOperation unity_free_mt - #define AwsIotJobs_MallocString unity_malloc_mt - #define AwsIotJobs_FreeString unity_free_mt - #define AwsIotJobs_MallocSubscription unity_malloc_mt - #define AwsIotJobs_FreeSubscription unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ /* Network types to use in the tests. These are forward declarations. */ From 639a4cea642e00f02a13899a5f19cb38cb1d5993 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 18 Sep 2019 10:54:34 -0700 Subject: [PATCH 263/844] Add port for macOS (#568) --- CMakeLists.txt | 2 + platform/ports/macos/CMakeLists.txt | 30 ++ platform/ports/macos/source/iot_clock_macos.c | 248 +++++++++ .../ports/macos/source/iot_threads_macos.c | 472 ++++++++++++++++++ .../macos/types/iot_platform_types_macos.h | 74 +++ 5 files changed, 826 insertions(+) create mode 100644 platform/ports/macos/CMakeLists.txt create mode 100644 platform/ports/macos/source/iot_clock_macos.c create mode 100644 platform/ports/macos/source/iot_threads_macos.c create mode 100644 platform/ports/macos/types/iot_platform_types_macos.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a3cefdd62d..5078066708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,8 @@ if( NOT DEFINED IOT_PLATFORM_NAME ) "Export all symbols for Windows DLLs. This option must by enabled." FORCE ) endif() + elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" ) + set( IOT_PLATFORM_NAME "macos" CACHE STRING "Port to use for building the SDK." ) endif() endif() diff --git a/platform/ports/macos/CMakeLists.txt b/platform/ports/macos/CMakeLists.txt new file mode 100644 index 0000000000..d5dcc2711f --- /dev/null +++ b/platform/ports/macos/CMakeLists.txt @@ -0,0 +1,30 @@ +# Check for POSIX threads. +find_package( Threads REQUIRED ) + +if( NOT ${CMAKE_USE_PTHREADS_INIT} ) + message( FATAL_ERROR "POSIX threads required." ) +endif() + +# Add the network header for this platform. +set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} + ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h + PARENT_SCOPE ) + +# Platform library source files. +set( PLATFORM_SOURCES + ${CMAKE_SOURCE_DIR}/platform/ports/macos/source/iot_threads_macos.c + ${CMAKE_SOURCE_DIR}/platform/ports/macos/source/iot_clock_macos.c + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c + ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ) + +# Add platform as an interface library. It will be built into iotbase. +add_library( iotplatform INTERFACE ) + +target_sources( iotplatform INTERFACE + ${PLATFORM_SOURCES} ) + +# This platform layer uses mbed TLS. +target_link_libraries( iotplatform INTERFACE Threads::Threads mbedtls ) + +# Set platform sources in the parent scope for directory organization. +set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) diff --git a/platform/ports/macos/source/iot_clock_macos.c b/platform/ports/macos/source/iot_clock_macos.c new file mode 100644 index 0000000000..9a57a4ae58 --- /dev/null +++ b/platform/ports/macos/source/iot_clock_macos.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_clock_macos.c + * @brief Implementation of the functions in iot_clock.h for macOS. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Platform clock include. */ +#include "platform/iot_clock.h" + +/* Standard includes. */ +#include + +/* POSIX includes. Allow the default POSIX headers to be overridden. */ +#ifdef POSIX_ERRNO_HEADER + #include POSIX_ERRNO_HEADER +#else + #include +#endif +#ifdef POSIX_TIME_HEADER + #include POSIX_TIME_HEADER +#else + #include +#endif + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "CLOCK" ) +#include "iot_logging_setup.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief The format of timestrings printed in logs. + * + * For more information on timestring formats, see [this link.] + * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) + */ +#define TIMESTRING_FORMAT ( "%F %R:%S" ) + +/* + * Time conversion constants. + */ +#define NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ +#define MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps an #IotThreadRoutine_t with a GCD-compliant one. + * + * @param[in] pArgument The value passed to `dispatch_after_f`. + */ +static void _timerExpirationWrapper( void * pArgument ); + +/*-----------------------------------------------------------*/ + +static void _timerExpirationWrapper( void * pArgument ) +{ + IotTimer_t * pTimer = pArgument; + + /* Invoke the wrapped expiration routine. */ + pTimer->threadRoutine( pTimer->pArgument ); +} + +/*-----------------------------------------------------------*/ + +bool IotClock_GetTimestring( char * pBuffer, + size_t bufferSize, + size_t * pTimestringLength ) +{ + bool status = true; + const time_t unixTime = time( NULL ); + struct tm localTime = { 0 }; + size_t timestringLength = 0; + + /* localtime_r is the thread-safe variant of localtime. Its return value + * should be the pointer to the localTime struct. */ + if( localtime_r( &unixTime, &localTime ) != &localTime ) + { + status = false; + } + + if( status == true ) + { + /* Convert the localTime struct to a string. */ + timestringLength = strftime( pBuffer, bufferSize, TIMESTRING_FORMAT, &localTime ); + + /* Check for error from strftime. */ + if( timestringLength == 0 ) + { + status = false; + } + else + { + /* Set the output parameter. */ + *pTimestringLength = timestringLength; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +uint64_t IotClock_GetTimeMs( void ) +{ + struct timespec currentTime = { 0 }; + + if( clock_gettime( CLOCK_MONOTONIC, ¤tTime ) != 0 ) + { + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to read time from CLOCK_MONOTONIC. errno=%d", + errno ); + + abort(); + } + + return ( ( uint64_t ) currentTime.tv_sec ) * MILLISECONDS_PER_SECOND + + ( ( uint64_t ) currentTime.tv_nsec ) / NANOSECONDS_PER_MILLISECOND; +} + +/*-----------------------------------------------------------*/ + +void IotClock_SleepMs( uint32_t sleepTimeMs ) +{ + /* Convert parameter to timespec. */ + struct timespec sleepTime = + { + .tv_sec = sleepTimeMs / MILLISECONDS_PER_SECOND, + .tv_nsec = ( sleepTimeMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND + }; + + if( nanosleep( &sleepTime, NULL ) == -1 ) + { + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Sleep failed. errno=%d.", errno ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerCreate( IotTimer_t * pNewTimer, + IotThreadRoutine_t expirationRoutine, + void * pArgument ) +{ + pNewTimer->dispatchBlock = NULL; + + /* Save the expiration routine and argument for use later. */ + pNewTimer->pArgument = pArgument; + pNewTimer->threadRoutine = expirationRoutine; + + return true; +} + +/*-----------------------------------------------------------*/ + +void IotClock_TimerDestroy( IotTimer_t * pTimer ) +{ + /* Cancel any timer work that may already be scheduled. */ + if( pTimer->dispatchBlock != NULL ) + { + dispatch_block_cancel( pTimer->dispatchBlock ); + + /* dispatch_release only needs to be called if ARC is not being used. */ + #if( OS_OBJECT_HAVE_OBJC_SUPPORT == 0 ) + dispatch_release( pTimer->dispatchBlock ); + #endif + } +} + +/*-----------------------------------------------------------*/ + +bool IotClock_TimerArm( IotTimer_t * pTimer, + uint32_t relativeTimeoutMs, + uint32_t periodMs ) +{ + bool status = true; + + /* Get the handle to a global concurrent queue. Work will be submitted to + * this queue. */ + dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue( QOS_CLASS_DEFAULT, 0 ); + + /* Calculate when the timer should first expire. */ + int64_t delta = ( int64_t ) relativeTimeoutMs * NANOSECONDS_PER_MILLISECOND; + dispatch_time_t timeout = dispatch_time( DISPATCH_TIME_NOW, delta ); + + /* Save the period. */ + pTimer->periodMs = periodMs; + + /* Cancel any timer work that may already be scheduled (in case this call + * is a reschedule of an existing timer). */ + IotClock_TimerDestroy( pTimer ); + + /* Create a dispatch block to execute work on behalf of the timer. */ + pTimer->dispatchBlock = dispatch_block_create( 0, ^ { + _timerExpirationWrapper( pTimer ); + } ); + + if( pTimer->dispatchBlock == NULL ) + { + IotLogError( "Failed to create timer dispatch block." ); + + status = false; + } + else + { + /* Schedule the timer expiration to run at the timeout. */ + dispatch_after( timeout, globalDispatchQueue, pTimer->dispatchBlock ); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/platform/ports/macos/source/iot_threads_macos.c b/platform/ports/macos/source/iot_threads_macos.c new file mode 100644 index 0000000000..bcb67a9b9b --- /dev/null +++ b/platform/ports/macos/source/iot_threads_macos.c @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_threads_macos.c + * @brief Implementation of the functions in iot_threads.h for macOS. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Platform threads include. */ +#include "platform/iot_threads.h" + +/* Error handling include. */ +#include "private/iot_error.h" + +/* Atomic include. */ +#include "iot_atomic.h" + +/* POSIX includes. Allow the default POSIX headers to be overridden. */ +#ifdef POSIX_ERRNO_HEADER + #include POSIX_ERRNO_HEADER +#else + #include +#endif +#ifdef POSIX_PTHREAD_HEADER + #include POSIX_PTHREAD_HEADER +#else + #include +#endif + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_PLATFORM + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "THREAD" ) +#include "iot_logging_setup.h" + +/* + * Provide default values for undefined memory allocation functions. + */ +#ifndef IotThreads_Malloc + #include + +/** + * @brief Memory allocation. This function should have the same signature + * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). + */ + #define IotThreads_Malloc malloc +#endif +#ifndef IotThreads_Free + #include + +/** + * @brief Free memory. This function should have the same signature as + * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). + */ + #define IotThreads_Free free +#endif + +/*-----------------------------------------------------------*/ + +/** + * @brief Holds information about an active detached thread. + */ +typedef struct _threadInfo +{ + void * pArgument; /**< @brief First argument to `threadRoutine`. */ + IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ +} _threadInfo_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. + * + * @param[in] pArgument The value passed as `arg` to `pthread_create`. + * + * @return Always returns `NULL`. + */ +static void * _threadRoutineWrapper( void * pArgument ); + +/*-----------------------------------------------------------*/ + +static void * _threadRoutineWrapper( void * pArgument ) +{ + _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; + + /* Read thread routine and argument, then free thread info. */ + IotThreadRoutine_t threadRoutine = pThreadInfo->threadRoutine; + void * pThreadRoutineArgument = pThreadInfo->pArgument; + + IotThreads_Free( pThreadInfo ); + + /* Run the thread routine. */ + threadRoutine( pThreadRoutineArgument ); + + return NULL; +} + +/*-----------------------------------------------------------*/ + +bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, + void * pArgument, + int32_t priority, + size_t stackSize ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + int posixErrno = 0; + bool threadAttibutesCreated = false; + _threadInfo_t * pThreadInfo = NULL; + pthread_t newThread; + pthread_attr_t threadAttributes; + + /* Ignore priority and stack size. */ + ( void ) priority; + ( void ) stackSize; + + /* Allocate memory for the new thread info. */ + pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); + + if( pThreadInfo == NULL ) + { + IotLogError( "Failed to allocate memory for new thread." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Set up thread attributes object. */ + posixErrno = pthread_attr_init( &threadAttributes ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to initialize thread attributes. errno=%d.", + posixErrno ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + threadAttibutesCreated = true; + + /* Set the new thread to detached. */ + posixErrno = pthread_attr_setdetachstate( &threadAttributes, + PTHREAD_CREATE_DETACHED ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to set detached thread attribute. errno=%d.", + posixErrno ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Set the thread routine and argument. */ + pThreadInfo->threadRoutine = threadRoutine; + pThreadInfo->pArgument = pArgument; + + /* Create the underlying POSIX thread. */ + posixErrno = pthread_create( &newThread, + &threadAttributes, + _threadRoutineWrapper, + pThreadInfo ); + + if( posixErrno != 0 ) + { + IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Destroy thread attributes object. */ + if( threadAttibutesCreated == true ) + { + posixErrno = pthread_attr_destroy( &threadAttributes ); + + if( posixErrno != 0 ) + { + IotLogWarn( "Failed to destroy thread attributes. errno=%d.", + posixErrno ); + } + } + + /* Clean up on error. */ + if( status == false ) + { + if( pThreadInfo != NULL ) + { + IotThreads_Free( pThreadInfo ); + } + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +bool IotMutex_Create( IotMutex_t * pNewMutex, + bool recursive ) +{ + IOT_FUNCTION_ENTRY( bool, true ); + int mutexError = 0; + pthread_mutexattr_t mutexAttributes, * pMutexAttributes = NULL; + + if( recursive == true ) + { + /* Create new mutex attributes object. */ + mutexError = pthread_mutexattr_init( &mutexAttributes ); + + if( mutexError != 0 ) + { + IotLogError( "Failed to initialize mutex attributes. errno=%d.", + mutexError ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + pMutexAttributes = &mutexAttributes; + + /* Set recursive mutex type. */ + mutexError = pthread_mutexattr_settype( &mutexAttributes, + PTHREAD_MUTEX_RECURSIVE ); + + if( mutexError != 0 ) + { + IotLogError( "Failed to set recursive mutex type. errno=%d.", + mutexError ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + } + + mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); + + if( mutexError != 0 ) + { + IotLogError( "Failed to create new mutex %p. errno=%d.", + pNewMutex, + mutexError ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + IOT_FUNCTION_CLEANUP_BEGIN(); + + /* Destroy any created mutex attributes. */ + if( pMutexAttributes != NULL ) + { + ( void ) pthread_mutexattr_destroy( &mutexAttributes ); + } + + IOT_FUNCTION_CLEANUP_END(); +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Destroy( IotMutex_t * pMutex ) +{ + int mutexError = pthread_mutex_destroy( pMutex ); + + if( mutexError != 0 ) + { + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to destroy mutex %p. errno=%d.", + pMutex, + mutexError ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Lock( IotMutex_t * pMutex ) +{ + int mutexError = pthread_mutex_lock( pMutex ); + + if( mutexError != 0 ) + { + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to lock mutex %p. errno=%d.", + pMutex, + mutexError ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +bool IotMutex_TryLock( IotMutex_t * pMutex ) +{ + bool status = true; + int mutexError = pthread_mutex_trylock( pMutex ); + + if( mutexError != 0 ) + { + IotLogDebug( "Mutex mutex %p is not available. errno=%d.", + pMutex, + mutexError ); + + status = false; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotMutex_Unlock( IotMutex_t * pMutex ) +{ + int mutexError = pthread_mutex_unlock( pMutex ); + + if( mutexError != 0 ) + { + /* This block should not be reached; log an error and abort if it is. */ + IotLogError( "Failed to unlock mutex %p. errno=%d.", + pMutex, + mutexError ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, + uint32_t initialValue, + uint32_t maxValue ) +{ + bool status = true; + + /* Create a GCD semaphore. */ + pNewSemaphore->semaphore = dispatch_semaphore_create( ( long ) initialValue ); + + if( pNewSemaphore->semaphore == NULL ) + { + IotLogError( "Failed to create new dispatch semaphore." ); + status = false; + } + else + { + /* Set initial semaphore count. */ + pNewSemaphore->count = initialValue; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) +{ + /* dispatch_release only needs to be called if ARC is not being used. */ + #if( OS_OBJECT_HAVE_OBJC_SUPPORT == 0 ) + dispatch_release( pSemaphore->semaphore ); + #endif +} + +/*-----------------------------------------------------------*/ + +uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) +{ + return Atomic_Add_u32( &( pSemaphore->count ), 0 ); +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) +{ + long dispatchError = dispatch_semaphore_wait( pSemaphore->semaphore, + DISPATCH_TIME_FOREVER ); + + /* Waiting forever for a valid semaphore should not fail. */ + if( dispatchError != 0 ) + { + IotLogError( "(Semaphore %p) Failed to wait on dispatch semaphore, returned %ld.", + pSemaphore, + dispatchError ); + + abort(); + } + + if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) + { + IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); + + abort(); + } +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) +{ + return IotSemaphore_TimedWait( pSemaphore, 0 ); +} + +/*-----------------------------------------------------------*/ + +bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, + uint32_t timeoutMs ) +{ + bool status = true; + + /* Convert timeoutMs to nanoseconds. */ + int64_t delta = ( int64_t ) timeoutMs * 1000000LL; + + /* Create a dispatch time representation representing the timeout. */ + dispatch_time_t timeout = dispatch_time( DISPATCH_TIME_NOW, + delta ); + + /* Wait on the dispatch semaphore with a timeout. */ + long dispatchError = dispatch_semaphore_wait( pSemaphore->semaphore, + timeout ); + + if( dispatchError == 0 ) + { + if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) + { + IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); + + abort(); + } + } + else + { + status = false; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) +{ + Atomic_Increment_u32( &( pSemaphore->count ) ); + + /* Signal dispatch semaphore. The return value (whether a thread was woken) + * is ignored. */ + ( void ) dispatch_semaphore_signal( pSemaphore->semaphore ); +} + +/*-----------------------------------------------------------*/ diff --git a/platform/ports/macos/types/iot_platform_types_macos.h b/platform/ports/macos/types/iot_platform_types_macos.h new file mode 100644 index 0000000000..c139d7a45d --- /dev/null +++ b/platform/ports/macos/types/iot_platform_types_macos.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_platform_types_macos.h + * @brief Definitions of platform layer types on macOS systems. + */ + +#ifndef IOT_PLATFORM_TYPES_MACOS_H_ +#define IOT_PLATFORM_TYPES_MACOS_H_ + +/* POSIX includes that are available on macOS. */ +#ifdef POSIX_TYPES_HEADER + #include POSIX_TYPES_HEADER +#else + #include +#endif + +/* Grand Central Dispatch include. */ +#include + +/** + * @brief The native mutex type on macOS systems. + * + * macOS provides POSIX mutexes. + */ +typedef pthread_mutex_t _IotSystemMutex_t; + +/** + * @brief The native semaphore type on macOS systems. + * + * macOS provides a partial implementation of POSIX semaphores, but it does not + * provide all of the functions needed by this SDK. Grand Central Dispatch + * semaphores are used instead. + */ +typedef struct _IotSystemSemaphore +{ + uint32_t count; /**< @brief The current count of the semaphore. */ + dispatch_semaphore_t semaphore; /**< @brief Grand Central Dispatch semaphore. */ +} _IotSystemSemaphore_t; + +/** + * @brief The native timer type on macOS systems. + * + * POSIX timers are not available on macOS, so a timer implementation based on + * Grand Central Dispatch is used instead. + */ +typedef struct _IotSystemTimer +{ + dispatch_block_t dispatchBlock; /**< @brief A dispatch block that executes for the timer. */ + uint32_t periodMs; /**< @brief Expiration period of this timer, if any. */ + void * pArgument; /**< @brief First argument to threadRoutine. */ + void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ +} _IotSystemTimer_t; + +#endif /* ifndef IOT_PLATFORM_TYPES_MACOS_H_ */ From c23ad5f03e29b8a32999a11e14beeb3a32988ee7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 19 Sep 2019 11:09:40 -0700 Subject: [PATCH 264/844] Fix inaccurate log message (#570) --- lib/include/iot_init.h | 2 +- lib/source/jobs/aws_iot_jobs_api.c | 2 +- lib/source/mqtt/iot_mqtt_api.c | 2 +- lib/source/shadow/aws_iot_shadow_api.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/include/iot_init.h b/lib/include/iot_init.h index 4496393a45..734f33152e 100644 --- a/lib/include/iot_init.h +++ b/lib/include/iot_init.h @@ -21,7 +21,7 @@ /** * @file iot_init.h - * @brief Provides function signatures for common intialization and cleanup of + * @brief Provides function signatures for common initialization and cleanup of * this SDK. */ diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index d7b1fe244f..d0831b8fb9 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -755,7 +755,7 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; bool listInitStatus = false; - if( _checkInit() == false ) + if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) { listInitStatus = AwsIot_InitLists( &_AwsIotJobsPendingOperations, &_AwsIotJobsSubscriptions, diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 8e5aabfd7c..a4b915c66e 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -908,7 +908,7 @@ IotMqttError_t IotMqtt_Init( void ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - if( _checkInit() == false ) + if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) { /* Call any additional serializer initialization function if serializer * overrides are enabled. */ diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 914be15e56..ee7eec360e 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -683,7 +683,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; bool listInitStatus = false; - if( _checkInit() == false ) + if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) { /* Initialize Shadow lists and mutexes. */ listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, From 690df5fac9158baf9de9cc5ad6d2805215ebd385 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 19 Sep 2019 14:49:59 -0700 Subject: [PATCH 265/844] Call mbedtls_net_free in close (#571) --- platform/network/iot_network_mbedtls.c | 271 +++++++++++++------------ 1 file changed, 136 insertions(+), 135 deletions(-) diff --git a/platform/network/iot_network_mbedtls.c b/platform/network/iot_network_mbedtls.c index f3d6a27bca..f3527c0a4a 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/platform/network/iot_network_mbedtls.c @@ -145,7 +145,7 @@ /* Flags to track connection state. */ #define FLAG_SECURED ( 0x00000001UL ) /**< @brief Secured connection. */ #define FLAG_HAS_RECEIVE_CALLBACK ( 0x00000002UL ) /**< @brief Connection has receive callback. */ -#define FLAG_CLOSED ( 0x00000004UL ) /**< @brief Connection is closed. */ +#define FLAG_CONNECTION_DESTROYED ( 0x00000004UL ) /**< @brief Connection has been destroyed. */ /*-----------------------------------------------------------*/ @@ -156,10 +156,11 @@ typedef struct _networkConnection { uint32_t flags; /**< @brief Connection state flags. */ mbedtls_net_context networkContext; /**< @brief mbed TLS wrapper for system's sockets. */ - IotMutex_t networkMutex; /**< @brief Protects this network context from concurrent access. */ + IotMutex_t contextMutex; /**< @brief Protects this network context from concurrent access. */ IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ void * pReceiveContext; /**< @brief The context for the receive callback. */ + IotMutex_t callbackMutex; /**< @brief Synchronizes the receive callback with calls to destroy. */ IotSemaphore_t destroyNotification; /**< @brief Notifies the receive callback that the connection was destroyed. */ /** @@ -344,11 +345,9 @@ static void _destroyConnection( _networkConnection_t * pNetworkConnection ) _sslContextFree( pNetworkConnection ); } - /* Shutdown and close the network connection. */ - mbedtls_net_free( &( pNetworkConnection->networkContext ) ); - /* Destroy synchronization objects. */ - IotMutex_Destroy( &( pNetworkConnection->networkMutex ) ); + IotMutex_Destroy( &( pNetworkConnection->contextMutex ) ); + IotMutex_Destroy( &( pNetworkConnection->callbackMutex ) ); if( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == FLAG_HAS_RECEIVE_CALLBACK ) { @@ -371,16 +370,21 @@ static void _destroyConnection( _networkConnection_t * pNetworkConnection ) */ static void _receiveThread( void * pArgument ) { - bool connectionClosed = false; int pollStatus = 0; + mbedtls_net_context context; /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pArgument; + /* Record the context to poll. */ + IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); + context = pNetworkConnection->networkContext; + IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); + /* Continuously poll the network connection for events. */ while( true ) { - pollStatus = mbedtls_net_poll( &( pNetworkConnection->networkContext ), + pollStatus = mbedtls_net_poll( &context, MBEDTLS_NET_POLL_READ, IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); @@ -392,25 +396,20 @@ static void _receiveThread( void * pArgument ) } else { - /* Check if connection is closed. */ - IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); - connectionClosed = ( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ); - - if( connectionClosed == false ) + /* Invoke receive callback if data is available. */ + if( pollStatus == MBEDTLS_NET_POLL_READ ) { - /* Invoke receive callback if data is available. */ - if( pollStatus == MBEDTLS_NET_POLL_READ ) + IotMutex_Lock( &( pNetworkConnection->callbackMutex ) ); + + /* Only run the receive callback if the connection has not been + * destroyed. */ + if( ( pNetworkConnection->flags & FLAG_CONNECTION_DESTROYED ) == 0 ) { pNetworkConnection->receiveCallback( pNetworkConnection, pNetworkConnection->pReceiveContext ); } - } - - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); - if( connectionClosed == true ) - { - break; + IotMutex_Unlock( &( pNetworkConnection->callbackMutex ) ); } } } @@ -790,7 +789,7 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, char pServerPort[ 6 ] = { 0 }; /* Flags to track initialization. */ - bool networkContextInitialized = false, networkMutexCreated = false; + bool networkContextInitialized = false, networkMutexCreated = false, callbackMutexCreated = false; /* Cast function parameters to correct types. */ const IotNetworkServerInfo_t * const pServerInfo = pConnectionInfo; @@ -811,8 +810,26 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, memset( pNewNetworkConnection, 0x00, sizeof( _networkConnection_t ) ); /* Initialize the network context mutex. */ - networkMutexCreated = IotMutex_Create( &( pNewNetworkConnection->networkMutex ), - true ); + networkMutexCreated = IotMutex_Create( &( pNewNetworkConnection->contextMutex ), + false ); + + if( networkMutexCreated == false ) + { + IotLogError( "Failed to create mutex for network context." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } + + /* Initialize the mutex for the receive callback. */ + callbackMutexCreated = IotMutex_Create( &( pNewNetworkConnection->callbackMutex ), + true ); + + if( callbackMutexCreated == false ) + { + IotLogError( "Failed to create mutex for receive callback." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } /* Initialize mbed TLS network context. */ mbedtls_net_init( &( pNewNetworkConnection->networkContext ) ); @@ -867,7 +884,12 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, { if( networkMutexCreated == true ) { - IotMutex_Destroy( &( pNewNetworkConnection->networkMutex ) ); + IotMutex_Destroy( &( pNewNetworkConnection->contextMutex ) ); + } + + if( callbackMutexCreated == true ) + { + IotMutex_Destroy( &( pNewNetworkConnection->callbackMutex ) ); } if( networkContextInitialized == true ) @@ -982,77 +1004,68 @@ size_t IotNetworkMbedtls_Send( void * pConnection, pNetworkConnection, ( unsigned long ) messageLength ); - IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); - /* Check that the connection is open before sending. */ - if( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ) - { - IotLogError( "(Network connection %p) Cannot send; connection has been marked closed.", - pNetworkConnection ); - } - else - { - /* Check that it's possible to send right now. */ - mbedtlsError = mbedtls_net_poll( &( pNetworkConnection->networkContext ), - MBEDTLS_NET_POLL_WRITE, - 0 ); + /* Check that it's possible to send right now. */ + mbedtlsError = mbedtls_net_poll( &( pNetworkConnection->networkContext ), + MBEDTLS_NET_POLL_WRITE, + 0 ); - if( mbedtlsError == MBEDTLS_NET_POLL_WRITE ) + if( mbedtlsError == MBEDTLS_NET_POLL_WRITE ) + { + /* Choose the send function based on state of the SSL context. */ + if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) { - /* Choose the send function based on state of the SSL context. */ - if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + /* Secured send. */ + while( bytesSent < messageLength ) { - /* Secured send. */ - while( bytesSent < messageLength ) + mbedtlsError = mbedtls_ssl_write( &( pNetworkConnection->ssl.context ), + pMessage + bytesSent, + messageLength - bytesSent ); + + if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) ) { - mbedtlsError = mbedtls_ssl_write( &( pNetworkConnection->ssl.context ), - pMessage + bytesSent, - messageLength - bytesSent ); - - if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) ) - { - /* Call SSL write again with the same arguments. */ - continue; - } - else if( mbedtlsError < 0 ) - { - /* Error sending, exit. */ - break; - } - else - { - bytesSent += ( size_t ) mbedtlsError; - } + /* Call SSL write again with the same arguments. */ + continue; } - } - else - { - /* Unsecured send. */ - mbedtlsError = mbedtls_net_send( &( pNetworkConnection->networkContext ), - pMessage, - messageLength ); - - if( mbedtlsError > 0 ) + else if( mbedtlsError < 0 ) + { + /* Error sending, exit. */ + break; + } + else { - bytesSent = ( size_t ) mbedtlsError; + bytesSent += ( size_t ) mbedtlsError; } } + } + else + { + /* Unsecured send. */ + mbedtlsError = mbedtls_net_send( &( pNetworkConnection->networkContext ), + pMessage, + messageLength ); - /* Log errors. */ - if( mbedtlsError < 0 ) + if( mbedtlsError > 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to send." ); - bytesSent = 0; + bytesSent = ( size_t ) mbedtlsError; } } - else + + /* Log errors. */ + if( mbedtlsError < 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Cannot send right now." ); + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to send." ); + bytesSent = 0; } } + else + { + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Cannot send right now." ); + } - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); return bytesSent; } @@ -1069,53 +1082,46 @@ size_t IotNetworkMbedtls_Receive( void * pConnection, /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); - - /* Check that the connection is open before sending. */ - if( ( pNetworkConnection->flags & FLAG_CLOSED ) == FLAG_CLOSED ) + while( bytesReceived < bytesRequested ) { - IotLogError( "(Network connection %p) Cannot receive; connection has been marked closed.", - pNetworkConnection ); - } - else - { - while( bytesReceived < bytesRequested ) + IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); + + /* Choose the receive function based on state of the SSL context. */ + if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) { - /* Choose the receive function based on state of the SSL context. */ - if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) - { - mbedtlsError = mbedtls_ssl_read( &( pNetworkConnection->ssl.context ), - pBuffer + bytesReceived, - bytesRequested - bytesReceived ); - } - else - { - mbedtlsError = mbedtls_net_recv( &( pNetworkConnection->networkContext ), - pBuffer + bytesReceived, - bytesRequested - bytesReceived ); - } + mbedtlsError = mbedtls_ssl_read( &( pNetworkConnection->ssl.context ), + pBuffer + bytesReceived, + bytesRequested - bytesReceived ); + } + else + { + mbedtlsError = mbedtls_net_recv_timeout( &( pNetworkConnection->networkContext ), + pBuffer + bytesReceived, + bytesRequested - bytesReceived, + IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); + } - if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || - ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) ) - { - /* Call receive again with the same arguments. */ - continue; - } - else if( mbedtlsError < 0 ) - { - /* Error receiving, exit. */ - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to receive." ); - break; - } - else - { - bytesReceived += ( size_t ) mbedtlsError; - } + IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); + + if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_TIMEOUT ) ) + { + /* Call receive again with the same arguments. */ + continue; + } + else if( mbedtlsError < 0 ) + { + /* Error receiving, exit. */ + _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to receive." ); + break; + } + else + { + bytesReceived += ( size_t ) mbedtlsError; } } - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); - return bytesReceived; } @@ -1128,7 +1134,7 @@ IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ) /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); /* Notify the server that the SSL connection is being closed. */ if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) @@ -1150,13 +1156,10 @@ IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ) } } - /* Mark the connection as closed. The mbed TLS function to close a connection - * is not safe to call in a multithreaded system; therefore, the close is faked - * by setting a flag that prevents further use of the connection. The connection - * is actually closed by the destroy function. */ - pNetworkConnection->flags |= FLAG_CLOSED; + /* Shutdown and close the network connection. */ + mbedtls_net_free( &( pNetworkConnection->networkContext ) ); - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); + IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); IotLogInfo( "(Network connection %p) Connection closed.", pNetworkConnection ); @@ -1167,21 +1170,19 @@ IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ) IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ) { - bool destroyConnection = false; - /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - IotMutex_Lock( &( pNetworkConnection->networkMutex ) ); + /* Shutdown and close the network connection. */ + IotMutex_Lock( &( pNetworkConnection->callbackMutex ) ); + mbedtls_net_free( &( pNetworkConnection->networkContext ) ); + pNetworkConnection->flags |= FLAG_CONNECTION_DESTROYED; + IotMutex_Unlock( &( pNetworkConnection->callbackMutex ) ); /* Check if this connection has a receive callback. If it does not, it can * be destroyed here. Otherwise, notify the receive callback that destroy * has been called and rely on the receive callback to clean up. */ - destroyConnection = ( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == 0 ); - - IotMutex_Unlock( &( pNetworkConnection->networkMutex ) ); - - if( destroyConnection == true ) + if( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == 0 ) { _destroyConnection( pNetworkConnection ); } From 11854cd407ec75a758a710356884872d6870f213 Mon Sep 17 00:00:00 2001 From: Nathan Glimsdale Date: Thu, 19 Sep 2019 15:03:43 -0700 Subject: [PATCH 266/844] Defender Demo (#569) --- demos/app/CMakeLists.txt | 25 +++ demos/source/aws_iot_demo_defender.c | 248 +++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 demos/source/aws_iot_demo_defender.c diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt index 75034bd940..fbdde72465 100644 --- a/demos/app/CMakeLists.txt +++ b/demos/app/CMakeLists.txt @@ -67,3 +67,28 @@ source_group( include FILES ${DEMO_COMMON_HEADERS} ) source_group( "" FILES ${DEMO_COMMON_SOURCES} ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c ${CONFIG_HEADER} ) + +# Defender demo source files. +add_executable( aws_iot_demo_defender + ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_defender.c + ${DEMO_COMMON_SOURCES} + ${DEMO_COMMON_HEADERS} + ${CONFIG_HEADER} ) + +# Select the demo function for the Defender demo. +target_compile_definitions( aws_iot_demo_defender + PRIVATE RunDemo=RunDefenderDemo ) + +# Defender demo library dependencies. +target_link_libraries( aws_iot_demo_defender PRIVATE awsiotdefender ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( aws_iot_demo_defender PRIVATE unity ) +endif() + +# Organization of the Defender demo in folders. +set_property( TARGET aws_iot_demo_defender PROPERTY FOLDER "demos" ) +source_group( include FILES ${DEMO_COMMON_HEADERS} ) +source_group( "" FILES ${DEMO_COMMON_SOURCES} + ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_defender.c + ${CONFIG_HEADER} ) diff --git a/demos/source/aws_iot_demo_defender.c b/demos/source/aws_iot_demo_defender.c new file mode 100644 index 0000000000..385723948d --- /dev/null +++ b/demos/source/aws_iot_demo_defender.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Demo configuration includes. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include + +/* Demo logging include. */ +#include "iot_demo_logging.h" + +/* Platform includes for demo. */ +#include "platform/iot_clock.h" +#include "platform/iot_network.h" + +/* Defender includes. */ +#include "aws_iot_defender.h" + +/* Includes for initialization. */ +#include "iot_mqtt.h" +#include "iot_network_metrics.h" + +/** + * @brief Runs the defender demo. + * + * @return AWS_IOT_DEFENDER_SUCCESS on success, otherwise an error code indicating + * the cause of error. + */ +static AwsIotDefenderError_t _defenderDemo( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Starts the defender agent. + * + * @return AWS_IOT_DEFENDER_SUCCESS on success, otherwise an error code indicating + * the cause of error. + */ +static AwsIotDefenderError_t _startDefender( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Callback used to get notification of defender's events. + */ +static void _defenderCallback( void * param1, + AwsIotDefenderCallbackInfo_t * const pCallbackInfo ); + +/*-----------------------------------------------------------*/ + +int RunDefenderDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ) +{ + int status = EXIT_SUCCESS; + bool metricsInitStatus = false; + IotMqttError_t mqttInitStatus = IOT_MQTT_INIT_FAILED; + AwsIotDefenderError_t defenderResult; + + /* Unused parameters. */ + ( void ) awsIotMqttMode; + + /* Check parameter(s). */ + if( ( pIdentifier == NULL ) || ( pIdentifier[ 0 ] == '\0' ) ) + { + IotLogError( "The length of the Thing Name (identifier) must be nonzero." ); + status = EXIT_FAILURE; + } + + if( status == EXIT_SUCCESS ) + { + /* Initialize the MQTT library. */ + mqttInitStatus = IotMqtt_Init(); + + if( mqttInitStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "MQTT Initialization Failed." ); + status = EXIT_FAILURE; + } + } + + if( status == EXIT_SUCCESS ) + { + /* Initialize Metrics. */ + metricsInitStatus = IotMetrics_Init(); + + if( !metricsInitStatus ) + { + IotLogError( "IOT Metrics Initialization Failed." ); + status = EXIT_FAILURE; + } + } + + if( status == EXIT_SUCCESS ) + { + /* Run the demo. */ + defenderResult = _defenderDemo( pIdentifier, pNetworkServerInfo, pNetworkCredentialInfo, pNetworkInterface ); + + if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + { + status = EXIT_SUCCESS; + } + } + + /* Cleanup. */ + if( metricsInitStatus ) + { + IotMetrics_Cleanup(); + } + + if( mqttInitStatus == IOT_MQTT_SUCCESS ) + { + IotMqtt_Cleanup(); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void _defenderCallback( void * param1, + AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) +{ + ( void ) param1; + + IotLogInfo( "User's callback is invoked on event %d.", pCallbackInfo->eventType ); + + /* Print out the sent metrics report if there is. */ + if( pCallbackInfo->pMetricsReport != NULL ) + { + IotLogInfo( "Published metrics report." ); + } + else + { + IotLogError( "No metrics report was generated." ); + } + + if( pCallbackInfo->pPayload != NULL ) + { + IotLogInfo( "Received MQTT message." ); + } + else + { + IotLogError( "No message has been returned from subscribed topic." ); + } +} + +/*-----------------------------------------------------------*/ + +static AwsIotDefenderError_t _defenderDemo( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ) +{ + AwsIotDefenderError_t defenderResult; + + IotLogInfo( "----Device Defender Demo Start----" ); + + /* Specify all metrics in "tcp connections" group */ + defenderResult = + AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ); + + if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + { + /* Set metrics report period to 5 minutes(300 seconds) */ + defenderResult = AwsIotDefender_SetPeriod( 300 ); + } + + if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + { + /* Start the defender agent. */ + defenderResult = _startDefender( pIdentifier, pNetworkServerInfo, pNetworkCredentialInfo, pNetworkInterface ); + + if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + { + /* Let it run for 3 seconds */ + IotClock_SleepMs( 3000 ); + + /* Stop the defender agent. */ + AwsIotDefender_Stop(); + } + } + + IotLogInfo( "----Device Defender Demo End. Status: %d----.", defenderResult ); + + return defenderResult; +} + +/*-----------------------------------------------------------*/ + +static AwsIotDefenderError_t _startDefender( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ) +{ + const AwsIotDefenderCallback_t callback = { .function = _defenderCallback, .param1 = NULL }; + AwsIotDefenderError_t defenderResult; + + AwsIotDefenderStartInfo_t startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + + /* Set network information. */ + startInfo.mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; + startInfo.mqttNetworkInfo.createNetworkConnection = true; + startInfo.mqttNetworkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; + startInfo.mqttNetworkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; + + /* Set network interface. */ + startInfo.mqttNetworkInfo.pNetworkInterface = pNetworkInterface; + + /* Set MQTT connection information. */ + startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; + startInfo.mqttConnectionInfo.pClientIdentifier = pIdentifier; + startInfo.mqttConnectionInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); + + /* Set Defender callback. */ + startInfo.callback = callback; + + /* Invoke defender start API. */ + defenderResult = AwsIotDefender_Start( &startInfo ); + + return defenderResult; +} + +/*-----------------------------------------------------------*/ From 4ab908f20ca3bde509d4ff78dda9c03de73f1453 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Thu, 19 Sep 2019 15:59:46 -0700 Subject: [PATCH 267/844] fix: defender test module crash for mac os port. (#572) --- lib/source/defender/aws_iot_defender_api.c | 14 +++++++++----- platform/ports/macos/source/iot_clock_macos.c | 5 ----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index fdfaa9e51d..5c31e5ba28 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -28,7 +28,8 @@ /* Clock include. */ #include "platform/iot_clock.h" -#define WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) +#define WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) +#define MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ ( ( uint32_t ) 1 ) #if WAIT_METRICS_JOB_MAX_SECONDS < AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS #error "_WAIT_METRICS_JOB_MAX_SECONDS must be greater than AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS." @@ -95,7 +96,7 @@ AwsIotDefenderStartInfo_t _startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t metricsGroup, uint32_t metrics ) { - if( metricsGroup >= DEFENDER_METRICS_GROUP_COUNT ) + if( metricsGroup >= ( AwsIotDefenderMetricsGroup_t ) DEFENDER_METRICS_GROUP_COUNT ) { IotLogError( "Input metrics group is invalid. Please use AwsIotDefenderMetricsGroup_t data type." ); @@ -168,7 +169,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn if( buildTopicsNamesSuccess ) { /* Create a binary semaphore with initial value 1. */ - doneSemaphoreCreateSuccess = IotSemaphore_Create( &_doneSem, 1, 1 ); + doneSemaphoreCreateSuccess = IotSemaphore_Create( &_doneSem, MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ, 1 ); } if( doneSemaphoreCreateSuccess ) @@ -239,8 +240,11 @@ void AwsIotDefender_Stop( void ) return; } - /* First thing to do: wait for all the metrics processing to be done. */ - IotSemaphore_Wait( &_doneSem ); + /* Wait for all the metrics processing to be done, if there are outstanding requests */ + if( IotSemaphore_GetCount( &_doneSem ) > MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ ) + { + IotSemaphore_Wait( &_doneSem ); + } IotTaskPoolJobStatus_t status; IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, &status ); diff --git a/platform/ports/macos/source/iot_clock_macos.c b/platform/ports/macos/source/iot_clock_macos.c index 9a57a4ae58..69e9fe313f 100644 --- a/platform/ports/macos/source/iot_clock_macos.c +++ b/platform/ports/macos/source/iot_clock_macos.c @@ -194,11 +194,6 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) if( pTimer->dispatchBlock != NULL ) { dispatch_block_cancel( pTimer->dispatchBlock ); - - /* dispatch_release only needs to be called if ARC is not being used. */ - #if( OS_OBJECT_HAVE_OBJC_SUPPORT == 0 ) - dispatch_release( pTimer->dispatchBlock ); - #endif } } From 62d3a1358424db36dc3271435600cdcd3dae339b Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Fri, 20 Sep 2019 13:01:41 -0700 Subject: [PATCH 268/844] Release dispatch blocks in macOS port (#573) --- platform/ports/macos/source/iot_clock_macos.c | 5 +++++ platform/ports/macos/types/iot_platform_types_macos.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/platform/ports/macos/source/iot_clock_macos.c b/platform/ports/macos/source/iot_clock_macos.c index 69e9fe313f..6c05cba212 100644 --- a/platform/ports/macos/source/iot_clock_macos.c +++ b/platform/ports/macos/source/iot_clock_macos.c @@ -194,6 +194,11 @@ void IotClock_TimerDestroy( IotTimer_t * pTimer ) if( pTimer->dispatchBlock != NULL ) { dispatch_block_cancel( pTimer->dispatchBlock ); + + /* Block_release only needs to be called if ARC is not being used. */ + #if( OS_OBJECT_HAVE_OBJC_SUPPORT == 0 ) + Block_release( pTimer->dispatchBlock ); + #endif } } diff --git a/platform/ports/macos/types/iot_platform_types_macos.h b/platform/ports/macos/types/iot_platform_types_macos.h index c139d7a45d..5dd7acd358 100644 --- a/platform/ports/macos/types/iot_platform_types_macos.h +++ b/platform/ports/macos/types/iot_platform_types_macos.h @@ -37,6 +37,9 @@ /* Grand Central Dispatch include. */ #include +/* Dispatch blocks API include. */ +#include + /** * @brief The native mutex type on macOS systems. * From 03f00b2790cefc19fdcf456f3b18476449bbf028 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Thu, 26 Sep 2019 09:56:47 -0700 Subject: [PATCH 269/844] Adjust KeepAlive test timeout (#576) --- tests/mqtt/unit/iot_tests_mqtt_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index b5d2fe2594..d5bf1a6da5 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -1537,7 +1537,7 @@ TEST( MQTT_Unit_API, KeepAlivePeriodic ) /* An estimate for the amount of time this test requires. */ const uint32_t sleepTimeMs = ( KEEP_ALIVE_COUNT * SHORT_KEEP_ALIVE_MS ) + - ( IOT_MQTT_RESPONSE_WAIT_MS * KEEP_ALIVE_COUNT ) + 1500; + ( IOT_MQTT_RESPONSE_WAIT_MS * KEEP_ALIVE_COUNT ) + 2500; /* Print a newline so this test may log its status. */ UNITY_PRINT_EOL(); From 2b6d30cb0262061af945bc18b462a2735f67fb01 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 26 Sep 2019 12:59:59 -0700 Subject: [PATCH 270/844] Remove atomics from inits and cleanups (#575) --- lib/source/jobs/aws_iot_jobs_api.c | 15 ++++++--------- lib/source/mqtt/iot_mqtt_api.c | 15 ++++++--------- lib/source/shadow/aws_iot_shadow_api.c | 15 ++++++--------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index d0831b8fb9..89e36cc657 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -42,9 +42,6 @@ /* MQTT include. */ #include "iot_mqtt.h" -/* Atomics include. */ -#include "iot_atomic.h" - /* Validate Jobs configuration settings. */ #if AWS_IOT_JOBS_ENABLE_ASSERTS != 0 && AWS_IOT_JOBS_ENABLE_ASSERTS != 1 #error "AWS_IOT_JOBS_ENABLE_ASSERTS must be 0 or 1." @@ -186,7 +183,7 @@ static bool _checkInit( void ) { bool status = true; - if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + if( _initCalled == 0 ) { IotLogError( "AwsIotJobs_Init was not called." ); @@ -755,7 +752,7 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; bool listInitStatus = false; - if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + if( _initCalled == 0 ) { listInitStatus = AwsIot_InitLists( &_AwsIotJobsPendingOperations, &_AwsIotJobsSubscriptions, @@ -777,7 +774,7 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) } /* Set the flag that specifies initialization is complete. */ - ( void ) Atomic_CompareAndSwap_u32( &( _initCalled ), 1, 0 ); + _initCalled = 1; IotLogInfo( "Jobs library successfully initialized." ); } @@ -794,10 +791,10 @@ AwsIotJobsError_t AwsIotJobs_Init( uint32_t mqttTimeoutMs ) void AwsIotJobs_Cleanup( void ) { - uint32_t initCleared = Atomic_CompareAndSwap_u32( &( _initCalled ), 0, 1 ); - - if( initCleared == 1 ) + if( _initCalled == 1 ) { + _initCalled = 0; + /* Remove and free all items in the Jobs pending operation list. */ IotMutex_Lock( &_AwsIotJobsPendingOperationsMutex ); IotListDouble_RemoveAll( &_AwsIotJobsPendingOperations, diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index a4b915c66e..4e318cad89 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -40,9 +40,6 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" -/* Atomics include. */ -#include "iot_atomic.h" - /* Validate MQTT configuration settings. */ #if IOT_MQTT_ENABLE_ASSERTS != 0 && IOT_MQTT_ENABLE_ASSERTS != 1 #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." @@ -164,7 +161,7 @@ static bool _checkInit( void ) { bool status = true; - if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + if( _initCalled == 0 ) { IotLogError( "IotMqtt_Init was not called." ); @@ -908,7 +905,7 @@ IotMqttError_t IotMqtt_Init( void ) { IotMqttError_t status = IOT_MQTT_SUCCESS; - if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + if( _initCalled == 0 ) { /* Call any additional serializer initialization function if serializer * overrides are enabled. */ @@ -933,7 +930,7 @@ IotMqttError_t IotMqtt_Init( void ) else { /* Set the flag that specifies initialization is complete. */ - ( void ) Atomic_CompareAndSwap_u32( &( _initCalled ), 1, 0 ); + _initCalled = 1; IotLogInfo( "MQTT library successfully initialized." ); } @@ -950,10 +947,10 @@ IotMqttError_t IotMqtt_Init( void ) void IotMqtt_Cleanup( void ) { - uint32_t initCleared = Atomic_CompareAndSwap_u32( &( _initCalled ), 0, 1 ); - - if( initCleared == 1 ) + if( _initCalled == 1 ) { + _initCalled = 0; + /* Call any additional serializer cleanup initialization function if serializer * overrides are enabled. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index ee7eec360e..754d15d4d5 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -42,9 +42,6 @@ /* MQTT include. */ #include "iot_mqtt.h" -/* Atomics include. */ -#include "iot_atomic.h" - /* Validate Shadow configuration settings. */ #if AWS_IOT_SHADOW_ENABLE_ASSERTS != 0 && AWS_IOT_SHADOW_ENABLE_ASSERTS != 1 #error "AWS_IOT_SHADOW_ENABLE_ASSERTS must be 0 or 1." @@ -190,7 +187,7 @@ static bool _checkInit( void ) { bool status = true; - if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + if( _initCalled == 0 ) { IotLogError( "AwsIotShadow_Init was not called." ); @@ -683,7 +680,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) AwsIotShadowError_t status = AWS_IOT_SHADOW_SUCCESS; bool listInitStatus = false; - if( Atomic_Add_u32( &( _initCalled ), 0 ) == 0 ) + if( _initCalled == 0 ) { /* Initialize Shadow lists and mutexes. */ listInitStatus = AwsIot_InitLists( &_AwsIotShadowPendingOperations, @@ -706,7 +703,7 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) } /* Set the flag that specifies initialization is complete. */ - ( void ) Atomic_CompareAndSwap_u32( &( _initCalled ), 1, 0 ); + _initCalled = 1; IotLogInfo( "Shadow library successfully initialized." ); } @@ -723,10 +720,10 @@ AwsIotShadowError_t AwsIotShadow_Init( uint32_t mqttTimeoutMs ) void AwsIotShadow_Cleanup( void ) { - uint32_t initCleared = Atomic_CompareAndSwap_u32( &( _initCalled ), 0, 1 ); - - if( initCleared == 1 ) + if( _initCalled == 1 ) { + _initCalled = 0; + /* Remove and free all items in the Shadow pending operation list. */ IotMutex_Lock( &( _AwsIotShadowPendingOperationsMutex ) ); IotListDouble_RemoveAll( &( _AwsIotShadowPendingOperations ), From 79ccec553ee335bde019afc43def741b8bda78f9 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 26 Sep 2019 13:18:54 -0700 Subject: [PATCH 271/844] Add oldFunction parameter to Jobs callbacks (#574) --- doc/lib/jobs.txt | 10 ++ lib/include/aws_iot_jobs.h | 40 +++-- lib/include/private/aws_iot_jobs_internal.h | 5 +- lib/include/types/aws_iot_jobs_types.h | 39 +++++ lib/source/jobs/aws_iot_jobs_api.c | 148 +++++++++++++++--- lib/source/jobs/aws_iot_jobs_subscription.c | 30 ++-- tests/jobs/system/aws_iot_tests_jobs_system.c | 7 +- tests/jobs/unit/aws_iot_tests_jobs_api.c | 87 +++++++++- tests/shadow/unit/aws_iot_tests_shadow_api.c | 2 +- 9 files changed, 307 insertions(+), 61 deletions(-) diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index 0a7ad68deb..eff895adfb 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -145,6 +145,16 @@ If the `mqttTimeout` argument of @ref jobs_function_init is `0`, the Jobs librar @configrecommended This setting must be at least the network round-trip time, as an MQTT packet must be sent to the AWS IoT server and a response must be received. The recommended minimum value is `500`.
@configdefault `5000` +@section AWS_IOT_JOBS_NOTIFY_CALLBACKS +@brief Set the maximum number of Jobs Notify callbacks of each type for each Thing. + +This setting affects the behavior of @ref jobs_function_setnotifynextcallback and @ref jobs_function_setnotifypendingcallback. There is only one Job notification topic of each type for each Thing. This setting allows multiple callbacks to be registered for the single notification topic, allowing multiple applications to respond to the notifications received. + +All registered callbacks will be invoked when a notification arrives. The order in which the callbacks are invoked may vary. Therefore, it is the responsibility of each callback to ignore Job notifications that are not intended for it. + +@configpossible Any positive integer.
+@configdefault `1` + @section AWS_IOT_LOG_LEVEL_JOBS @brief Set the log level of the Jobs library. diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 5c0859f510..281dbed087 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -666,10 +666,10 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, * useful for monitoring the list of pending Job executions. * * A NOTIFY PENDING callback may be invoked whenever a message is published - * to `jobs/notify`. Each Thing may have a single NOTIFY PENDING callback set. This - * function modifies the NOTIFY PENDING callback for a specific Thing depending on - * the `pNotifyPendingCallback` parameter and the presence of any existing NOTIFY - * PENDING callback. + * to `jobs/notify`. Each Thing may have up to @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS + * NOTIFY PENDING callbacks set. This function modifies the NOTIFY PENDING callback + * for a specific Thing depending on the `pNotifyPendingCallback` parameter and the + * presence of any existing NOTIFY PENDING callback. * - When no existing NOTIFY PENDING callback exists for a specific Thing, a new * callback is added. * - If there is an existing NOTIFY PENDING callback and `pNotifyPendingCallback` is not `NULL`, @@ -677,6 +677,11 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, * - If there is an existing NOTIFY PENDING callback and `pNotifyPendingCallback` is `NULL`, * then the callback is removed. * + * The member @ref AwsIotJobsCallbackInfo_t.oldFunction must be used to select an + * already-registered callback function for replacement or removal when @ref + * AWS_IOT_JOBS_NOTIFY_CALLBACKS is greater than `1`. When multiple callbacks are + * set, all of them will be invoked when a message is received. + * * @param[in] mqttConnection The MQTT connection to use for the subscription to `jobs/notify`. * @param[in] pThingName The subscription to `jobs/notify` will be added for * this Thing Name. @@ -721,12 +726,15 @@ AwsIotJobsError_t AwsIotJobs_Wait( AwsIotJobsOperation_t operation, * // executions changes. * * // Once the callback is no longer needed, it may be removed by passing - * // NULL as pNotifyPendingCallback. + * // NULL as the callback function and specifying the function to remove. + * notifyPendingCallback.function = NULL; + * notifyPendingCallback.oldFunction = _jobsCallback; + * * status = AwsIotJobs_SetNotifyPendingCallback( mqttConnection, * THING_NAME, * THING_NAME_LENGTH, * 0, - * NULL ); + * ¬ifyPendingCallback ); * * // The return value from removing a callback should always be success. * assert( status == AWS_IOT_JOBS_SUCCESS ); @@ -751,10 +759,10 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttC * useful for being notified of changes to the next Job. * * A NOTIFY NEXT callback may be invoked whenever a message is published - * to `jobs/notify-next`. Each Thing may have a single NOTIFY NEXT callback set. This - * function modifies the NOTIFY NEXT callback for a specific Thing depending on - * the `pNotifyNextCallback` parameter and the presence of any existing NOTIFY - * NEXT callback. + * to `jobs/notify-next`. Each Thing may have up to @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS + * NOTIFY NEXT callbacks set. This function modifies the NOTIFY NEXT callback for + * a specific Thing depending on the `pNotifyNextCallback` parameter and the presence + * of any existing NOTIFY NEXT callback. * - When no existing NOTIFY NEXT callback exists for a specific Thing, a new * callback is added. * - If there is an existing NOTIFY NEXT callback and `pNotifyNextCallback` is not `NULL`, @@ -762,6 +770,11 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttC * - If there is an existing NOTIFY NEXT callback and `pNotifyNextCallback` is `NULL`, * then the callback is removed. * + * The member @ref AwsIotJobsCallbackInfo_t.oldFunction must be used to select an + * already-registered callback function for replacement or removal when @ref + * AWS_IOT_JOBS_NOTIFY_CALLBACKS is greater than `1`. When multiple callbacks are + * set, all of them will be invoked when a message is received. + * * @param[in] mqttConnection The MQTT connection to use for the subscription to `jobs/notify-next`. * @param[in] pThingName The subscription to `jobs/notify-next` will be added for * this Thing Name. @@ -806,12 +819,15 @@ AwsIotJobsError_t AwsIotJobs_SetNotifyPendingCallback( IotMqttConnection_t mqttC * // execution changes. * * // Once the callback is no longer needed, it may be removed by passing - * // NULL as pNotifyNextCallback. + * // NULL as the callback function and specifying the function to remove. + * notifyNextCallback.function = NULL; + * notifyNextCallback.oldFunction = _jobsCallback; + * * status = AwsIotJobs_SetNotifyNextCallback( mqttConnection, * THING_NAME, * THING_NAME_LENGTH, * 0, - * NULL ); + * ¬ifyNextCallback ); * * // The return value from removing a callback should always be success. * assert( status == AWS_IOT_JOBS_SUCCESS ); diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index d5c949b1d7..7d13e8929e 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -182,6 +182,9 @@ #ifndef AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS #define AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS ( 5000 ) #endif +#ifndef AWS_IOT_JOBS_NOTIFY_CALLBACKS + #define AWS_IOT_JOBS_NOTIFY_CALLBACKS ( 1 ) +#endif /** @endcond */ /** @@ -350,7 +353,7 @@ typedef struct _jobsSubscription { IotLink_t link; /**< @brief List link member. */ - int32_t references[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counter for Jobs operation topics. */ + int32_t operationReferences[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counters for Jobs operation topics. */ AwsIotJobsCallbackInfo_t callbacks[ JOBS_CALLBACK_COUNT ]; /**< @brief Jobs callbacks for this Thing. */ /** diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index 033790f9a9..b0ddae52db 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -505,6 +505,45 @@ typedef struct AwsIotJobsCallbackInfo */ void ( * function )( void *, AwsIotJobsCallbackParam_t * ); + + /** + * @brief Callback function to replace when passed to @ref jobs_function_setnotifynextcallback + * or @ref jobs_function_setnotifypendingcallback. + * + * This member is ignored by Jobs operation functions. + * + * The number of callbacks of each type that may be registered for each Thing + * is limited by @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS. If @ref AWS_IOT_JOBS_NOTIFY_CALLBACKS + * is `2`, that means that a maximum of `2` NOTIFY PENDING and `2` NOTIFY NEXT callbacks + * (`4` total callbacks) may be set. This member is used to replace an existing callback + * with a new one. + * + * To add a new callback: + * @code{c} + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * callbackInfo.function = _newCallback; + * callbackInfo.oldFunction = NULL; + * @endcode + * + * For example, if the function `_oldCallback()` is currently registered: + * - To replace `_oldCallback()` with a new callback function `_newCallback()`: + * @code{c} + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * callbackInfo.function = _newCallback; + * callbackInfo.oldFunction = _oldCallback; + * @endcode + * - To remove `_oldCallback()`: + * @code{c} + * AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + * + * callbackInfo.function = NULL; + * callbackInfo.oldFunction = _oldCallback; + * @endcode + */ + void ( * oldFunction )( void *, + AwsIotJobsCallbackParam_t * ); } AwsIotJobsCallbackInfo_t; /** diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 89e36cc657..9f3609feb6 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -49,6 +49,20 @@ #if AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS <= 0 #error "AWS_IOT_JOBS_DEFAULT_MQTT_TIMEOUT_MS cannot be 0 or negative." #endif +#if AWS_IOT_JOBS_NOTIFY_CALLBACKS <= 0 + #error "AWS_IOT_JOBS_NOTIFY_CALLBACKS cannot be 0 or negative." +#endif + +/** + * @brief Returned by @ref _getCallbackIndex when there's no space in the callback array. + */ +#define NO_SPACE_FOR_CALLBACK ( -1 ) + +/** + * @brief Returned by @ref _getCallbackIndex when a searching for an oldCallback that + * does not exist. + */ +#define OLD_CALLBACK_NOT_FOUND ( -2 ) /*-----------------------------------------------------------*/ @@ -87,6 +101,23 @@ static AwsIotJobsError_t _validateRequestInfo( _jobsOperationType_t type, static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, const AwsIotJobsUpdateInfo_t * pUpdateInfo ); +/** + * @brief Gets an element of the callback array to modify. + * + * @param[in] type The type of callback to modify. + * @param[in] pSubscription Subscription object that holds the callback array. + * @param[in] pCallbackInfo User provided callback info. + * + * @return The index of the callback array to modify; on error: + * - #NO_SPACE_FOR_CALLBACK if no free spaces are available + * - #OLD_CALLBACK_NOT_FOUND if an old callback to remove was specified, but that function does not exist. + * + * @note This function should be called with the subscription list mutex locked. + */ +static int32_t _getCallbackIndex( _jobsCallbackType_t type, + _jobsSubscription_t * pSubscription, + const AwsIotJobsCallbackInfo_t * pCallbackInfo ); + /** * @brief Common function for setting Jobs callbacks. * @@ -397,6 +428,44 @@ static AwsIotJobsError_t _validateUpdateInfo( _jobsOperationType_t type, /*-----------------------------------------------------------*/ +static int32_t _getCallbackIndex( _jobsCallbackType_t type, + _jobsSubscription_t * pSubscription, + const AwsIotJobsCallbackInfo_t * pCallbackInfo ) +{ + int32_t callbackIndex = 0; + + /* Find the matching oldFunction. */ + if( pCallbackInfo->oldFunction != NULL ) + { + if( pSubscription->callbacks[ type ].function == pCallbackInfo->oldFunction ) + { + callbackIndex = 0; + } + else + { + /* oldFunction not found. */ + callbackIndex = OLD_CALLBACK_NOT_FOUND; + } + } + /* Find space for a new callback. */ + else + { + if( pSubscription->callbacks[ type ].function == NULL ) + { + callbackIndex = 0; + } + else + { + /* No memory for new callback. */ + callbackIndex = NO_SPACE_FOR_CALLBACK; + } + } + + return callbackIndex; +} + +/*-----------------------------------------------------------*/ + static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, _jobsCallbackType_t type, const char * pThingName, @@ -406,6 +475,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); bool subscriptionMutexLocked = false; _jobsSubscription_t * pSubscription = NULL; + int32_t callbackIndex = 0; /* Check that AwsIotJobs_Init was called. */ if( _checkInit() == false ) @@ -422,12 +492,21 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); } - /* Check that a callback function is provided. */ - if( pCallbackInfo != NULL ) + /* Check that a callback parameter is provided. */ + if( pCallbackInfo == NULL ) { - if( pCallbackInfo->function == NULL ) + IotLogError( "Callback parameter must be provided for Jobs %s callback.", + _pAwsIotJobsCallbackNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + } + + /* The oldFunction member must be set when removing or replacing a callback. */ + if( pCallbackInfo->function == NULL ) + { + if( pCallbackInfo->oldFunction == NULL ) { - IotLogError( "Callback function must be provided for Jobs %s callback.", + IotLogError( "Both oldFunction and function pointers cannot be NULL for Jobs %s callback.", _pAwsIotJobsCallbackNames[ type ] ); IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); @@ -455,11 +534,36 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); } + /* Get the index of the callback element to modify. */ + callbackIndex = _getCallbackIndex( type, pSubscription, pCallbackInfo ); + + switch( callbackIndex ) + { + case NO_SPACE_FOR_CALLBACK: + IotLogError( "No memory for a new Jobs %s callback.", + _pAwsIotJobsCallbackNames[ type ] ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_NO_MEMORY ); + + case OLD_CALLBACK_NOT_FOUND: + IotLogWarn( "Requested replacement function for Jobs %s callback not found.", + _pAwsIotJobsCallbackNames[ type ] ); + + /* A subscription may have been allocated, but the callback operation can't + * proceed. Check if the subscription should be removed. */ + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); + + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_PARAMETER ); + + default: + break; + } + /* Check for an existing callback. */ if( pSubscription->callbacks[ type ].function != NULL ) { /* Replace existing callback. */ - if( pCallbackInfo != NULL ) + if( pCallbackInfo->function != NULL ) { IotLogInfo( "(%.*s) Found existing %s callback. Replacing callback.", thingNameLength, @@ -493,32 +597,24 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, else { /* Add new callback. */ - if( pCallbackInfo != NULL ) - { - IotLogInfo( "(%.*s) Adding new %s callback.", - thingNameLength, - pThingName, - _pAwsIotJobsCallbackNames[ type ] ); + IotLogInfo( "(%.*s) Adding new %s callback.", + thingNameLength, + pThingName, + _pAwsIotJobsCallbackNames[ type ] ); - status = _modifyCallbackSubscriptions( mqttConnection, - type, - pSubscription, - IotMqtt_SubscribeSync ); + status = _modifyCallbackSubscriptions( mqttConnection, + type, + pSubscription, + IotMqtt_SubscribeSync ); - if( status == AWS_IOT_JOBS_SUCCESS ) - { - pSubscription->callbacks[ type ] = *pCallbackInfo; - } - else - { - /* On failure, check if this subscription can be removed. */ - _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - } + if( status == AWS_IOT_JOBS_SUCCESS ) + { + pSubscription->callbacks[ type ] = *pCallbackInfo; } - /* Do nothing; set return value to success. */ else { - status = AWS_IOT_JOBS_SUCCESS; + /* On failure, check if this subscription can be removed. */ + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); } } diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 6fb3f18908..2cdf17696c 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -177,17 +177,17 @@ void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, * reference count is not 0, then the subscription cannot be removed. */ for( i = 0; i < JOBS_OPERATION_COUNT; i++ ) { - if( pSubscription->references[ i ] > 0 ) + if( pSubscription->operationReferences[ i ] > 0 ) { IotLogDebug( "Reference count %ld for %.*s subscription object. " "Subscription cannot be removed yet.", - ( long int ) pSubscription->references[ i ], + ( long int ) pSubscription->operationReferences[ i ], pSubscription->thingNameLength, pSubscription->pThingName ); removeSubscription = false; } - else if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) + else if( pSubscription->operationReferences[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Subscription object for %.*s has persistent subscriptions. " "Subscription will not be removed.", @@ -277,7 +277,7 @@ AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; /* Do nothing if this operation has persistent subscriptions. */ - if( pSubscription->references[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) + if( pSubscription->operationReferences[ type ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { IotLogDebug( "Jobs %s for %.*s has persistent subscriptions. Reference " "count will not be incremented.", @@ -290,10 +290,10 @@ AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation /* When persistent subscriptions are not present, the reference count must * not be negative. */ - AwsIotJobs_Assert( pSubscription->references[ type ] >= 0 ); + AwsIotJobs_Assert( pSubscription->operationReferences[ type ] >= 0 ); /* Check if there are any existing references for this operation. */ - if( pSubscription->references[ type ] == 0 ) + if( pSubscription->operationReferences[ type ] == 0 ) { /* Set the parameters needed to add subscriptions. */ subscriptionInfo.mqttConnection = pOperation->mqttConnection; @@ -331,18 +331,18 @@ AwsIotJobsError_t _AwsIotJobs_IncrementReferences( _jobsOperation_t * pOperation * the keep subscriptions flag is not set. */ if( ( pOperation->flags & AWS_IOT_JOBS_FLAG_KEEP_SUBSCRIPTIONS ) == 0 ) { - ( pSubscription->references[ type ] )++; + ( pSubscription->operationReferences[ type ] )++; IotLogDebug( "Jobs %s subscriptions for %.*s now has count %d.", _pAwsIotJobsOperationNames[ type ], pSubscription->thingNameLength, pSubscription->pThingName, - pSubscription->references[ type ] ); + pSubscription->operationReferences[ type ] ); } /* Otherwise, set the persistent subscriptions flag. */ else { - pSubscription->references[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; + pSubscription->operationReferences[ type ] = AWS_IOT_PERSISTENT_SUBSCRIPTION; IotLogDebug( "Set persistent subscriptions flag for Jobs %s of %.*s.", _pAwsIotJobsOperationNames[ type ], @@ -366,15 +366,15 @@ void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; /* Do nothing if this Jobs operation has persistent subscriptions. */ - if( pSubscription->references[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION ) + if( pSubscription->operationReferences[ type ] != AWS_IOT_PERSISTENT_SUBSCRIPTION ) { /* Decrement the number of subscription references for this operation. * Ensure that it's positive. */ - ( pSubscription->references[ type ] )--; - AwsIotJobs_Assert( pSubscription->references[ type ] >= 0 ); + ( pSubscription->operationReferences[ type ] )--; + AwsIotJobs_Assert( pSubscription->operationReferences[ type ] >= 0 ); /* Check if the number of references has reached 0. */ - if( pSubscription->references[ type ] == 0 ) + if( pSubscription->operationReferences[ type ] == 0 ) { IotLogDebug( "Reference count for %.*s %s is 0. Unsubscribing.", pSubscription->thingNameLength, @@ -507,7 +507,7 @@ AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequ /* Subscription must have a topic buffer. */ AwsIotJobs_Assert( pSubscription->pTopicBuffer != NULL ); - if( pSubscription->references[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) + if( pSubscription->operationReferences[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { /* Generate the prefix of the Jobs topic. This function will not * fail when given a buffer. */ @@ -547,7 +547,7 @@ AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequ } /* Clear the persistent subscriptions flag. */ - pSubscription->references[ i ] = 0; + pSubscription->operationReferences[ i ] = 0; } else { diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index 0ac05e0142..dc6a8be06c 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -832,18 +832,21 @@ TEST( Jobs_System, JobsCallbacks ) } /* Remove Jobs callbacks. */ + callbackInfo.function = NULL; + callbackInfo.oldFunction = _jobsCallback; + status = AwsIotJobs_SetNotifyNextCallback( _mqttConnection, AWS_IOT_TEST_JOBS_THING_NAME, sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, 0, - NULL ); + &callbackInfo ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); status = AwsIotJobs_SetNotifyPendingCallback( _mqttConnection, AWS_IOT_TEST_JOBS_THING_NAME, sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1, 0, - NULL ); + &callbackInfo ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Destroy the wait semaphore. */ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index ed50187ec9..6c517181c3 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -67,12 +67,24 @@ /** * @brief The Thing Name shared among all the tests. */ -#define TEST_THING_NAME "TestThingName" +#define TEST_THING_NAME "TestThingName" /** * @brief The length of #TEST_THING_NAME. */ -#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) +#define TEST_THING_NAME_LENGTH ( sizeof( TEST_THING_NAME ) - 1 ) + +/** + * @brief A non-NULL callback function that can be tested by Jobs, but is not + * expected to be invoked. + */ +#define JOBS_CALLBACK_FUNCTION ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x01 ) + +/** + * @brief Another non-NULL callback function that can be tested by Jobs, but is not + * expected to be invoked. + */ +#define JOBS_CALLBACK_FUNCTION_2 ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x02 ) /*-----------------------------------------------------------*/ @@ -239,6 +251,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) RUN_TEST_CASE( Jobs_Unit_API, DescribeMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, UpdateMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, RemovePersistentSubscriptions ); + RUN_TEST_CASE( Jobs_Unit_API, SetCallback ); RUN_TEST_CASE( Jobs_Unit_API, SetCallbackMallocFail ); } @@ -327,7 +340,7 @@ TEST( Jobs_Unit_API, Init ) */ TEST( Jobs_Unit_API, StringCoverage ) { - int32_t i = 0; + size_t i = 0; const char * pMessage = NULL; const char * pInvalidStatus = "INVALID STATUS"; @@ -682,6 +695,72 @@ TEST( Jobs_Unit_API, RemovePersistentSubscriptions ) /*-----------------------------------------------------------*/ +/** + * @brief Tests the behavior of the Jobs callback functions. + */ +TEST( Jobs_Unit_API, SetCallback ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + _jobsSubscription_t * pSubscription = NULL; + + /* Callback info must be provided. */ + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, NULL ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Thing Name must be valid. */ + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, NULL, 0, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Request to remove an unspecified callback. */ + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Request to remove a callback that doesn't exist. */ + callbackInfo.function = NULL; + callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_BAD_PARAMETER, status ); + + /* Set new callback. */ + callbackInfo.function = JOBS_CALLBACK_FUNCTION; + callbackInfo.oldFunction = NULL; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that new callback was set. */ + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NOT_NULL( pSubscription ); + TEST_ASSERT_EQUAL_PTR( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ].function, JOBS_CALLBACK_FUNCTION ); + + /* Replace existing function. */ + callbackInfo.function = JOBS_CALLBACK_FUNCTION_2; + callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that callback was replaced. */ + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NOT_NULL( pSubscription ); + TEST_ASSERT_EQUAL_PTR( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ].function, JOBS_CALLBACK_FUNCTION_2 ); + + /* Remove callback function. */ + callbackInfo.function = NULL; + callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_2; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the subscription object was deleted. */ + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NULL( pSubscription ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Tests the behavior of the Jobs set callback functions when memory * allocation fails at various points. @@ -696,7 +775,7 @@ TEST( Jobs_Unit_API, SetCallbackMallocFail ) _AwsIotJobsMqttTimeoutMs = 75; /* A non-NULL callback function. */ - callbackInfo.function = ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x01; + callbackInfo.function = JOBS_CALLBACK_FUNCTION; for( i = 0; ; i++ ) { diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 3be9ebd2c8..1fd809bb12 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -225,7 +225,7 @@ TEST( Shadow_Unit_API, Init ) */ TEST( Shadow_Unit_API, StringCoverage ) { - int32_t i = 0; + size_t i = 0; const char * pMessage = NULL; const char * pInvalidStatus = "INVALID STATUS"; From 86d51e311ff45fc3ab87a2f3963ac5bf999437e1 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 26 Sep 2019 13:55:25 -0700 Subject: [PATCH 272/844] Add parameters for Jobs MQTT commands (#578) --- lib/include/types/aws_iot_jobs_types.h | 6 ++++++ lib/source/jobs/aws_iot_jobs_operation.c | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index b0ddae52db..ffb5ff689c 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -564,6 +564,12 @@ typedef struct AwsIotJobsRequestInfo */ IotMqttConnection_t mqttConnection; + /* These members allow Jobs commands to be retried. Be careful that duplicate + * commands do no cause unexpected application behavior. Use of QoS 0 is recommended. */ + IotMqttQos_t qos; /**< @brief QoS when sending the Jobs command. See #IotMqttPublishInfo_t.qos. */ + uint32_t retryLimit; /**< @brief Maximum number of retries for the Jobs command. See #IotMqttPublishInfo_t.retryLimit. */ + uint32_t retryMs; /**< @brief First retry time for the Jobs command. See IotMqttPublishInfo_t.retryMs. */ + /** * @brief Function to allocate memory for an incoming response. * diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index c10df627c4..1afcf36ca7 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -787,9 +787,10 @@ AwsIotJobsError_t _AwsIotJobs_ProcessOperation( const AwsIotJobsRequestInfo_t * IOT_GOTO_CLEANUP(); } - /* Jobs already has an acknowledgement mechanism, so sending the message at - * QoS 1 provides no benefit. */ - publishInfo.qos = IOT_MQTT_QOS_0; + /* Set the members for PUBLISH retry. */ + publishInfo.qos = pRequestInfo->qos; + publishInfo.retryLimit = pRequestInfo->retryLimit; + publishInfo.retryMs = pRequestInfo->retryMs; /* Set the payload as the Jobs request. */ publishInfo.pPayload = pOperation->pJobsRequest; From db45988f0068d359b668697a19e52e743528c87c Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Thu, 26 Sep 2019 15:13:02 -0700 Subject: [PATCH 273/844] Update CONTRIBUTING.md (#579) --- CONTRIBUTING.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8908ec04b1..adb6cd9561 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,17 +24,18 @@ reported the issue. Please try to include as much information as you can. Detail Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 1. You are working against the latest source on the *master* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. +1. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +1. You open an issue to discuss any significant work - we would hate for your time to be wasted. To send us a pull request, please: 1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +1. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +1. Ensure that your contributions conform to the [style guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_styleguide.html). +1. Ensure local tests pass. +1. Commit to your fork using clear commit messages. +1. Send us a pull request, answering any default questions in the pull request interface. +1. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). From 816960e6dee842d251d98e6f1d3fadb27b13f83b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 26 Sep 2019 15:32:05 -0700 Subject: [PATCH 274/844] Make Jobs subscription list an array (#580) --- lib/include/private/aws_iot_jobs_internal.h | 9 +- lib/source/jobs/aws_iot_jobs_api.c | 91 ++++++++++++++------- lib/source/jobs/aws_iot_jobs_subscription.c | 36 ++++---- tests/jobs/unit/aws_iot_tests_jobs_api.c | 37 ++++++++- 4 files changed, 122 insertions(+), 51 deletions(-) diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 7d13e8929e..18a71cdb80 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -351,10 +351,13 @@ typedef union _jsonRequestContents */ typedef struct _jobsSubscription { - IotLink_t link; /**< @brief List link member. */ + IotLink_t link; /**< @brief List link member. */ - int32_t operationReferences[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counters for Jobs operation topics. */ - AwsIotJobsCallbackInfo_t callbacks[ JOBS_CALLBACK_COUNT ]; /**< @brief Jobs callbacks for this Thing. */ + int32_t operationReferences[ JOBS_OPERATION_COUNT ]; /**< @brief Reference counters for Jobs operation topics. */ + int32_t callbackReferences; /**< @brief Reference counter for Jobs callbacks. */ + + /** @brief Jobs callbacks for this Thing. */ + AwsIotJobsCallbackInfo_t callbacks[ JOBS_CALLBACK_COUNT ][ AWS_IOT_JOBS_NOTIFY_CALLBACKS ]; /** * @brief Buffer allocated for removing Jobs topics. diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index 9f3609feb6..e7d7b471cb 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -437,11 +437,16 @@ static int32_t _getCallbackIndex( _jobsCallbackType_t type, /* Find the matching oldFunction. */ if( pCallbackInfo->oldFunction != NULL ) { - if( pSubscription->callbacks[ type ].function == pCallbackInfo->oldFunction ) + for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) { - callbackIndex = 0; + if( pSubscription->callbacks[ type ][ callbackIndex ].function == pCallbackInfo->oldFunction ) + { + /* oldFunction found. */ + break; + } } - else + + if( callbackIndex == AWS_IOT_JOBS_NOTIFY_CALLBACKS ) { /* oldFunction not found. */ callbackIndex = OLD_CALLBACK_NOT_FOUND; @@ -450,11 +455,15 @@ static int32_t _getCallbackIndex( _jobsCallbackType_t type, /* Find space for a new callback. */ else { - if( pSubscription->callbacks[ type ].function == NULL ) + for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) { - callbackIndex = 0; + if( pSubscription->callbacks[ type ][ callbackIndex ].function == NULL ) + { + break; + } } - else + + if( callbackIndex == AWS_IOT_JOBS_NOTIFY_CALLBACKS ) { /* No memory for new callback. */ callbackIndex = NO_SPACE_FOR_CALLBACK; @@ -560,7 +569,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, } /* Check for an existing callback. */ - if( pSubscription->callbacks[ type ].function != NULL ) + if( pSubscription->callbacks[ type ][ callbackIndex ].function != NULL ) { /* Replace existing callback. */ if( pCallbackInfo->function != NULL ) @@ -570,7 +579,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, pThingName, _pAwsIotJobsCallbackNames[ type ] ); - pSubscription->callbacks[ type ] = *pCallbackInfo; + pSubscription->callbacks[ type ][ callbackIndex ] = *pCallbackInfo; } /* Remove existing callback. */ else @@ -585,7 +594,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, type, pSubscription, IotMqtt_UnsubscribeSync ); - ( void ) memset( &( pSubscription->callbacks[ type ] ), + ( void ) memset( &( pSubscription->callbacks[ type ][ callbackIndex ] ), 0x00, sizeof( AwsIotJobsCallbackInfo_t ) ); @@ -609,7 +618,7 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, if( status == AWS_IOT_JOBS_SUCCESS ) { - pSubscription->callbacks[ type ] = *pCallbackInfo; + pSubscription->callbacks[ type ][ callbackIndex ] = *pCallbackInfo; } else { @@ -785,6 +794,7 @@ static void _notifyNextCallbackWrapper( void * pArgument, static void _callbackWrapperCommon( _jobsCallbackType_t type, IotMqttCallbackParam_t * pMessage ) { + int32_t callbackIndex = 0; AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; AwsIotJobsCallbackParam_t callbackParam = { .callbackType = ( AwsIotJobsCallbackType_t ) 0 }; _jobsSubscription_t * pSubscription = NULL; @@ -807,35 +817,54 @@ static void _callbackWrapperCommon( _jobsCallbackType_t type, thingNameLength, false ); - if( pSubscription == NULL ) + if( pSubscription != NULL ) { - /* No subscription found. */ - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - IOT_GOTO_CLEANUP(); + /* Increment callback reference count to prevent the subscription object from being + * freed while callbacks are running. */ + pSubscription->callbackReferences++; } - /* Ensure that a callback function is set. */ - AwsIotJobs_Assert( pSubscription->callbacks[ type ].function != NULL ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - /* Copy the subscription callback info, as the subscription may be modified - * when the subscriptions mutex is released. */ - callbackInfo = pSubscription->callbacks[ type ]; + if( pSubscription != NULL ) + { + /* Invoke all callbacks for this Thing. */ + for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) + { + /* Copy the callback function and parameter, as it may be modified at any time. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); + callbackInfo = pSubscription->callbacks[ type ][ callbackIndex ]; + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); - IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + if( callbackInfo.function != NULL ) + { + /* Set the callback type. Jobs callbacks are enumerated after the operations. */ + callbackParam.callbackType = ( AwsIotJobsCallbackType_t ) ( type + JOBS_OPERATION_COUNT ); + + /* Set the remaining members of the callback param. */ + callbackParam.mqttConnection = pMessage->mqttConnection; + callbackParam.pThingName = pThingName; + callbackParam.thingNameLength = thingNameLength; + callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; + callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; + + /* Invoke the callback function. */ + callbackInfo.function( callbackInfo.pCallbackContext, + &callbackParam ); + } + } + + /* Callbacks are finished. Decrement reference count and check if subscription + * object should be destroyed. */ + IotMutex_Lock( &_AwsIotJobsSubscriptionsMutex ); - /* Set the callback type. Jobs callbacks are enumerated after the operations. */ - callbackParam.callbackType = ( AwsIotJobsCallbackType_t ) ( type + JOBS_OPERATION_COUNT ); + pSubscription->callbackReferences--; + AwsIotJobs_Assert( pSubscription->callbackReferences >= 0 ); - /* Set the remaining members of the callback param. */ - callbackParam.mqttConnection = pMessage->mqttConnection; - callbackParam.pThingName = pThingName; - callbackParam.thingNameLength = thingNameLength; - callbackParam.u.callback.pDocument = pMessage->u.message.info.pPayload; - callbackParam.u.callback.documentLength = pMessage->u.message.info.payloadLength; + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); - /* Invoke the callback function. */ - callbackInfo.function( callbackInfo.pCallbackContext, - &callbackParam ); + IotMutex_Unlock( &_AwsIotJobsSubscriptionsMutex ); + } /* This function uses cleanup sections to exit on error. */ IOT_FUNCTION_CLEANUP_BEGIN(); diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 2cdf17696c..71b88e4df5 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -166,8 +166,8 @@ _jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, _jobsSubscription_t ** pRemovedSubscription ) { - int32_t i = 0; - bool removeSubscription = true; + IOT_FUNCTION_ENTRY( bool, true ); + int32_t i = 0, callbackIndex = 0; IotLogDebug( "Checking if subscription object for %.*s can be removed.", pSubscription->thingNameLength, @@ -185,7 +185,7 @@ void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, pSubscription->thingNameLength, pSubscription->pThingName ); - removeSubscription = false; + IOT_SET_AND_GOTO_CLEANUP( false ); } else if( pSubscription->operationReferences[ i ] == AWS_IOT_PERSISTENT_SUBSCRIPTION ) { @@ -194,22 +194,27 @@ void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, pSubscription->thingNameLength, pSubscription->pThingName ); - removeSubscription = false; - } - - if( removeSubscription == false ) - { - break; + IOT_SET_AND_GOTO_CLEANUP( false ); } } /* Check for active subscriptions. If any Jobs callbacks are active, then the * subscription cannot be removed. */ - if( removeSubscription == true ) + if( pSubscription->callbackReferences > 0 ) + { + IotLogDebug( "Notify callbacks are using %.*s subscription object. " + "Subscription cannot be removed yet.", + pSubscription->thingNameLength, + pSubscription->pThingName ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + for( i = 0; i < JOBS_CALLBACK_COUNT; i++ ) { - for( i = 0; i < JOBS_CALLBACK_COUNT; i++ ) + for( callbackIndex = 0; callbackIndex < AWS_IOT_JOBS_NOTIFY_CALLBACKS; callbackIndex++ ) { - if( pSubscription->callbacks[ i ].function != NULL ) + if( pSubscription->callbacks[ i ][ callbackIndex ].function != NULL ) { IotLogDebug( "Found active Jobs %s callback for %.*s subscription object. " "Subscription cannot be removed yet.", @@ -217,14 +222,15 @@ void _AwsIotJobs_RemoveSubscription( _jobsSubscription_t * pSubscription, pSubscription->thingNameLength, pSubscription->pThingName ); - removeSubscription = false; - break; + IOT_SET_AND_GOTO_CLEANUP( false ); } } } /* Remove the subscription if unused. */ - if( removeSubscription == true ) + IOT_FUNCTION_CLEANUP_BEGIN(); + + if( status == true ) { /* No Jobs operation subscription references or active Jobs callbacks. * Remove the subscription object. */ diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 6c517181c3..d3040c0350 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -700,6 +700,8 @@ TEST( Jobs_Unit_API, RemovePersistentSubscriptions ) */ TEST( Jobs_Unit_API, SetCallback ) { + int32_t i = 0; + bool callbackFound = false; AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; _jobsSubscription_t * pSubscription = NULL; @@ -733,7 +735,17 @@ TEST( Jobs_Unit_API, SetCallback ) /* Check that new callback was set. */ pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_PTR( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ].function, JOBS_CALLBACK_FUNCTION ); + + for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + { + if( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ][ i ].function == JOBS_CALLBACK_FUNCTION ) + { + callbackFound = true; + break; + } + } + + TEST_ASSERT_EQUAL_INT( true, callbackFound ); /* Replace existing function. */ callbackInfo.function = JOBS_CALLBACK_FUNCTION_2; @@ -745,7 +757,28 @@ TEST( Jobs_Unit_API, SetCallback ) /* Check that callback was replaced. */ pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); TEST_ASSERT_NOT_NULL( pSubscription ); - TEST_ASSERT_EQUAL_PTR( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ].function, JOBS_CALLBACK_FUNCTION_2 ); + + callbackFound = false; + for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + { + if( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ][ i ].function == JOBS_CALLBACK_FUNCTION ) + { + break; + } + } + + TEST_ASSERT_EQUAL_INT( false, callbackFound ); + + for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + { + if( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ][ i ].function == JOBS_CALLBACK_FUNCTION_2 ) + { + callbackFound = true; + break; + } + } + + TEST_ASSERT_EQUAL_INT( true, callbackFound ); /* Remove callback function. */ callbackInfo.function = NULL; From 0ae927dd14db31350b5c467e1e6ee9d21715b016 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 30 Sep 2019 11:04:59 -0700 Subject: [PATCH 275/844] Add test for multiple Jobs notify callbacks (#581) --- lib/source/jobs/aws_iot_jobs_api.c | 19 ++- tests/iot_config.h | 3 + tests/jobs/unit/aws_iot_tests_jobs_api.c | 202 +++++++++++++++++++---- 3 files changed, 191 insertions(+), 33 deletions(-) diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index e7d7b471cb..c9f0184d76 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -589,14 +589,14 @@ static AwsIotJobsError_t _setCallbackCommon( IotMqttConnection_t mqttConnection, pThingName, _pAwsIotJobsCallbackNames[ type ] ); - /* Unsubscribe, then clear the callback information. */ + /* Clear the callback information and unsubscribe. */ + ( void ) memset( &( pSubscription->callbacks[ type ][ callbackIndex ] ), + 0x00, + sizeof( AwsIotJobsCallbackInfo_t ) ); ( void ) _modifyCallbackSubscriptions( mqttConnection, type, pSubscription, IotMqtt_UnsubscribeSync ); - ( void ) memset( &( pSubscription->callbacks[ type ][ callbackIndex ] ), - 0x00, - sizeof( AwsIotJobsCallbackInfo_t ) ); /* Check if this subscription object can be removed. */ _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); @@ -651,6 +651,7 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC AwsIotMqttFunction_t mqttOperation ) { IOT_FUNCTION_ENTRY( AwsIotJobsError_t, AWS_IOT_JOBS_SUCCESS ); + int32_t i = 0; IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; AwsIotTopicInfo_t topicInfo = { 0 }; @@ -682,6 +683,16 @@ static AwsIotJobsError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqttC AwsIotJobs_Assert( ( mqttOperation == IotMqtt_SubscribeSync ) || ( mqttOperation == IotMqtt_UnsubscribeSync ) ); + /* Check if any subscriptions are currently registered for this type. */ + for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + { + if( pSubscription->callbacks[ type ][ i ].function != NULL ) + { + /* No action is needed when another callback exists. */ + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_SUCCESS ); + } + } + /* Use the subscription's topic buffer if available. */ if( pSubscription->pTopicBuffer != NULL ) { diff --git a/tests/iot_config.h b/tests/iot_config.h index 4fa26ed9e5..49e0bca685 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -117,6 +117,9 @@ /* Defender library configuration. */ #define AWS_IOT_DEFENDER_USE_LONG_TAG ( 1 ) +/* Allow the use of multiple Jobs callbacks. */ +#define AWS_IOT_JOBS_NOTIFY_CALLBACKS ( 4 ) + /* Static memory resource settings for the tests. These values must be large * enough to support the stress tests. */ #if IOT_STATIC_MEMORY_ONLY == 1 diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index d3040c0350..7a3d15ea24 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -86,6 +86,12 @@ */ #define JOBS_CALLBACK_FUNCTION_2 ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x02 ) +/** + * @brief A third non-NULL callback function that can be tested by Jobs, but is not + * expected to be invoked. + */ +#define JOBS_CALLBACK_FUNCTION_3 ( ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) ) 0x03 ) + /*-----------------------------------------------------------*/ /** @@ -193,6 +199,30 @@ static void _jobsMallocFail( _jobsOperationType_t type ) /*-----------------------------------------------------------*/ +/** + * @brief Searches a Job subscription object for a callback function. + */ +static bool _checkForCallback( const _jobsSubscription_t * pSubscription, + _jobsCallbackType_t type, + void ( *callbackFunction )( void *, AwsIotJobsCallbackParam_t * ) ) +{ + int32_t i = 0; + bool callbackFound = false; + + for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + { + if( pSubscription->callbacks[ type ][ i ].function == callbackFunction ) + { + callbackFound = true; + break; + } + } + + return callbackFound; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for Jobs API tests. */ @@ -252,6 +282,7 @@ TEST_GROUP_RUNNER( Jobs_Unit_API ) RUN_TEST_CASE( Jobs_Unit_API, UpdateMallocFail ); RUN_TEST_CASE( Jobs_Unit_API, RemovePersistentSubscriptions ); RUN_TEST_CASE( Jobs_Unit_API, SetCallback ); + RUN_TEST_CASE( Jobs_Unit_API, SetCallbackMultiple ); RUN_TEST_CASE( Jobs_Unit_API, SetCallbackMallocFail ); } @@ -700,8 +731,6 @@ TEST( Jobs_Unit_API, RemovePersistentSubscriptions ) */ TEST( Jobs_Unit_API, SetCallback ) { - int32_t i = 0; - bool callbackFound = false; AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; _jobsSubscription_t * pSubscription = NULL; @@ -735,17 +764,9 @@ TEST( Jobs_Unit_API, SetCallback ) /* Check that new callback was set. */ pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); TEST_ASSERT_NOT_NULL( pSubscription ); - - for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) - { - if( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ][ i ].function == JOBS_CALLBACK_FUNCTION ) - { - callbackFound = true; - break; - } - } - - TEST_ASSERT_EQUAL_INT( true, callbackFound ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION ) ); /* Replace existing function. */ callbackInfo.function = JOBS_CALLBACK_FUNCTION_2; @@ -757,39 +778,162 @@ TEST( Jobs_Unit_API, SetCallback ) /* Check that callback was replaced. */ pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); TEST_ASSERT_NOT_NULL( pSubscription ); + TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION ) ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION_2 ) ); - callbackFound = false; - for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + /* Remove callback function. */ + callbackInfo.function = NULL; + callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_2; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Check that the subscription object was deleted. */ + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NULL( pSubscription ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests the behavior of the Jobs multiple callback functions. + */ +TEST( Jobs_Unit_API, SetCallbackMultiple ) +{ + int32_t i = 0; + AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + _jobsSubscription_t * pSubscription = NULL; + + /* This test requires AWS_IOT_JOBS_NOTIFY_CALLBACKS > 1 to allow multiple callbacks. */ + #if AWS_IOT_JOBS_NOTIFY_CALLBACKS < 2 + #error "Jobs SetCallbackMultiple test requires AWS_IOT_JOBS_NOTIFY_CALLBACKS to be at least 2." + #endif + + /* Set two Jobs callbacks. */ + callbackInfo.function = JOBS_CALLBACK_FUNCTION; + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + callbackInfo.function = JOBS_CALLBACK_FUNCTION_2; + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + /* Ensure that both callbacks were set. */ + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NOT_NULL( pSubscription ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION ) ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION_2 ) ); + + /* Fill all of the subscription object's callbacks. */ + for( i = 2; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) { - if( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ][ i ].function == JOBS_CALLBACK_FUNCTION ) - { - break; - } + /* A non-NULL function that is not expected to be invoked. */ + void ( * callbackFunction )( void *, + AwsIotJobsCallbackParam_t * ) = ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) )( i + 2 ); + + callbackInfo.function = callbackFunction; + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + callbackFunction ) ); } - TEST_ASSERT_EQUAL_INT( false, callbackFound ); + /* Try to set more callbacks than allowed. */ + callbackInfo.function = JOBS_CALLBACK_FUNCTION_3; + callbackInfo.oldFunction = NULL; - for( i = 0; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_NO_MEMORY, status ); + + /* Clear the subscription object (except for 2 callbacks). */ + for( i = 2; i < AWS_IOT_JOBS_NOTIFY_CALLBACKS; i++ ) { - if( pSubscription->callbacks[ NOTIFY_NEXT_CALLBACK ][ i ].function == JOBS_CALLBACK_FUNCTION_2 ) - { - callbackFound = true; - break; - } + void ( * callbackFunction )( void *, + AwsIotJobsCallbackParam_t * ) = ( void ( * )( void *, AwsIotJobsCallbackParam_t * ) )( i + 2 ); + + callbackInfo.function = NULL; + callbackInfo.oldFunction = callbackFunction; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + callbackFunction ) ); } - TEST_ASSERT_EQUAL_INT( true, callbackFound ); + /* Replace a callback. */ + callbackInfo.function = JOBS_CALLBACK_FUNCTION_3; + callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION; - /* Remove callback function. */ + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); + + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NOT_NULL( pSubscription ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION_3 ) ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION_2 ) ); + TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION ) ); + + /* Remove a callback. */ callbackInfo.function = NULL; callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_2; status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - /* Check that the subscription object was deleted. */ + pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); + TEST_ASSERT_NOT_NULL( pSubscription ); + TEST_ASSERT_EQUAL_INT( true, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION_3 ) ); + TEST_ASSERT_EQUAL_INT( false, _checkForCallback( pSubscription, + NOTIFY_NEXT_CALLBACK, + JOBS_CALLBACK_FUNCTION_2 ) ); + + /* The MQTT connection should still have a subscription for the Jobs topic, since one + * callback is still using it. */ + const char * const pNotifyTopic = AWS_IOT_TOPIC_PREFIX TEST_THING_NAME JOBS_NOTIFY_NEXT_CALLBACK_STRING; + const uint16_t notifyTopicLength = ( uint16_t ) strlen( pNotifyTopic ); + + TEST_ASSERT_EQUAL_INT( true, IotMqtt_IsSubscribed( _pMqttConnection, + pNotifyTopic, + notifyTopicLength, + NULL ) ); + + /* Remove the second callback. */ + callbackInfo.function = NULL; + callbackInfo.oldFunction = JOBS_CALLBACK_FUNCTION_3; + + status = AwsIotJobs_SetNotifyNextCallback( _pMqttConnection, TEST_THING_NAME, TEST_THING_NAME_LENGTH, 0, &callbackInfo ); + TEST_ASSERT_EQUAL_INT( AWS_IOT_JOBS_SUCCESS, status ); + + /* The subscription object should now be destroyed, and the MQTT subscription should + * be gone. */ pSubscription = _AwsIotJobs_FindSubscription( TEST_THING_NAME, TEST_THING_NAME_LENGTH, false ); TEST_ASSERT_NULL( pSubscription ); + + TEST_ASSERT_EQUAL_INT( false, IotMqtt_IsSubscribed( _pMqttConnection, + pNotifyTopic, + notifyTopicLength, + NULL ) ); } /*-----------------------------------------------------------*/ From 03f9c0100046631b0f2b9d101611d193f65eb42b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 30 Sep 2019 14:32:05 -0700 Subject: [PATCH 276/844] Update documentation for building (#584) --- README.md | 36 ++++++++++++++++++++---------------- doc/config/main | 3 +++ doc/guide/building.txt | 33 +++++++++++++++++++++++---------- doc/guide/cmake_build.png | Bin 0 -> 22992 bytes 4 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 doc/guide/cmake_build.png diff --git a/README.md b/README.md index 38762a11f3..99531ea2e9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# AWS IoT Device SDK C v4.0.0 Beta +# AWS IoT Device SDK C v4.0.0 **[Link to API documentation](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/index.html)** @@ -7,37 +7,37 @@ The AWS IoT Device SDK for C is a collection of C99 source files that allow applications to securely connect to the AWS IoT platform. It includes an MQTT 3.1.1 client, as well as libraries specific to AWS IoT, such as Thing Shadows. It is distributed in source form and may be built into firmware along with application code. -This library, currently a Beta release, will supersede both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. +This library supersedes both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. -## Beta Features +## Features -This Beta library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. In addition, it provides the following new features: +This library is a new design that inherits from both the AWS IoT Device SDK Embedded C and the libraries provided with Amazon FreeRTOS. In addition, it provides the following new features: - Asynchronous API for both MQTT and Thing Shadow. - Multithreaded API by default (removed the `yield` function). - More efficient platform layer (especially timers). - Complete separation of MQTT and network stack, allowing MQTT to run over any network stack. - Configurable memory allocation (static-only or dynamic). Memory allocation functions may also be set by the user. -- Network stack based on OpenSSL. +- Network stack based on OpenSSL when building for Linux. - MQTT persistent session support. +- Device Defender library. -Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features are missing: +Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features were removed: - Auto-reconnect for MQTT connections. - Shadow JSON document generator. -- Jobs API. -- Build support for Apple macOS. ## Building and Running Demos **Main documentation page:** [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) -This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. **As of now, this Beta release only builds on Linux.** +This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. This repo contains ready-to-use ports for Windows, macOS, and Linux. ### Prerequisites -- Linux system with support for POSIX threads and timers. -- CMake 3.5.0 or later. - -If using the OpenSSL network implementation: -- OpenSSL development libraries and header files, version 1.0.2g or later. This is usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. +- CMake 3.5.0 or later and a C99 compiler. +- A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. For reference, the version used by this repo's Travis CI builds are listed in parentheses. + - Linux system with POSIX thread and timer APIs. (CI tests on Ubuntu 16.04).
+ On Linux systems, the OpenSSL network implementation may be used instead of the default network implementation based on mbed TLS. This requires the installation of OpenSSL development libraries and header files, version 1.0.2g or later. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + - macOS system with POSIX thread APIs and Grand Central Dispatch. (CI tests on macOS 10.13). + - Windows system with the Windows 10 SDK and MSVC toolchain. (CI tests on Windows Server 1803 with Visual Studio 2017). ### Build Steps 1. Clone the source code and submodules. This SDK uses third-party libraries as submodules in the `third_party` directory. @@ -63,11 +63,15 @@ If using the OpenSSL network implementation: mkdir build cd build ``` -5. Run CMake, then `make`. This builds the demo executables and places them in `build/bin`. +5. Run CMake from the build directory. ```sh cmake .. - make ``` + CMake will generate a project based on the detected operating system. On Linux and macOS, the default project is a Makefile. To build the SDK with this Makefile, run `make`. + + On Windows, CMake will create a Visual Studio solution. Open this solution in Visual Studio to build it. + + You may also use CMake GUI. Specify the SDK's root directory as the source directory and the build directory created in step 4 as the build directory in CMake GUI. See the documentation page [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) for a list of options that can be used to configure the build system. diff --git a/doc/config/main b/doc/config/main index d17138bbae..99ab2b42a3 100644 --- a/doc/config/main +++ b/doc/config/main @@ -16,6 +16,9 @@ INPUT = doc/ \ doc/guide \ doc/guide/developer +# Path of CMake GUI image. +IMAGE_PATH += doc/guide + # Library file names. FILE_PATTERNS = *.txt diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 56301f9ef8..0c7e1c8425 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -5,16 +5,14 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. @pre -- CMake version 3.5.0 or later and a supported C99 compiler. +- CMake version 3.5.0 or later and a C99 compiler. @pre -If using the OpenSSL network implementation. - - OpenSSL development libraries and header files, version 1.0.2g or later. - -@pre -Additional requirements for POSIX systems: -- POSIX threads, mutexes, and semaphores. -- POSIX timers. +- A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. For reference, the version used by this repo's Travis CI builds are listed in parentheses. + - Linux system with POSIX thread and timer APIs. (CI tests on Ubuntu 16.04).
+ On Linux systems, the OpenSSL network implementation may be used instead of the default network implementation based on mbed TLS. This requires the installation of OpenSSL development libraries and header files, version 1.0.2g or later. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. + - macOS system with POSIX thread APIs and Grand Central Dispatch. (CI tests on macOS 10.13). + - Windows system with the Windows 10 SDK and MSVC toolchain. (CI tests on Windows Server 1803 with Visual Studio 2017). This SDK uses third-party libraries as Git submodules in the `third_party` directory. If the source code was downloaded via `git clone`, nothing further needs to be done. The CMake build system can automatically clone submodules in this case. However, for any other download, the submodules must be downloaded and placed in their respective `third_party` directory. - [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17) → `third_party/mbedtls/mbedtls` @@ -38,15 +36,19 @@ The following options may be passed to the CMake build system to modify the buil - `IOT_BUILD_TESTS`
Default: `OFF`
Set this to `ON` to build both demo and test executables. When `OFF`, only demo executables are built. +- `IOT_NETWORK_USE_OPENSSL` [Linux only]
+ Default: `OFF`
+ Set this to `ON` to use a network implementation based on OpenSSL instead of the default mbed TLS network implementation. If this is set to `ON`, OpenSSL development libraries and headers must be installed. @section building_demo Building the demo applications @brief How to build the demo applications. Before building the demos, all desired configuration settings should be defined. Configuration settings include global [library](@ref global_library_config) and [demo](@ref global_demos_config) settings, as well as settings for specific libraries and demos. Settings for demo applications should be placed in `demos/iot_config.h`. Any undefined settings will use a default value when possible. -The demo applications build with CMake. +@subsection building_demo_commmandline Command-line build +@brief How to build the demo applications on the command-line. -In-source CMake builds are not allowed. A build directory should be created under the SDK root directory. Since CMake is cross-platform, build steps will vary depending on host OS. On Linux, the demo applications are built as follows: +In-source CMake builds are not allowed. A build directory should be created under the SDK root directory. Since CMake is cross-platform, build steps will vary depending on host OS. On Linux and macOS, the demo applications are built as follows: @code{sh} # Create build directory and change to build directory. mkdir build @@ -74,6 +76,15 @@ cmake .. -DCMAKE_BUILD_TYPE=Debug Delete the build directory to remove all demo application executables and build files. +@subsection building_demo_gui CMake GUI build +@brief How to build the demo applications with CMake GUI. + +Alternatively, CMake GUI may be used instead of the command line. Specify the SDK's root directory in Where is the source code and a build directory in Where to build the binaries. Click Configure, then Generate. You may modify @ref building_configuration in the CMake GUI window. + +The screenshot below is from CMake GUI on Windows. Similar options appear when using other systems. + +@image html cmake_build.png + @section demo_commandlineoptions Demo command line options @brief Command line options of the demo applications. @@ -140,6 +151,8 @@ cmake .. -DIOT_BUILD_TESTS=1 make @endcode +The `IOT_BUILD_TESTS` option can also be set by a checkbox in CMake GUI. + Test executables will be placed in a `bin` directory of the CMake build directory (i.e. `build/bin`) alongside the demo applications. They will be named `aws_iot_tests_library` or `iot_tests_library`, depending on whether the tests are specific to AWS IoT. For example, the MQTT tests application, which may be used with any MQTT server, will be named `iot_tests_mqtt`. The Thing Shadow tests, which are specific to AWS IoT, will be named `aws_iot_tests_shadow`. By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. The command line option `-n` may be passed to the test executable to disable tests that require network connectivity. diff --git a/doc/guide/cmake_build.png b/doc/guide/cmake_build.png new file mode 100644 index 0000000000000000000000000000000000000000..f80847d8b4139666205ea0c6ea5f1b9f4fcf13bf GIT binary patch literal 22992 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 literal 0 HcmV?d00001 From 3782a7a353046257abf0613d89f29aab74be9634 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 3 Oct 2019 14:47:53 -0400 Subject: [PATCH 277/844] Add versioning information in library license headers (#585) --- lib/include/aws_iot_defender.h | 1 + lib/include/aws_iot_jobs.h | 1 + lib/include/aws_iot_shadow.h | 1 + lib/include/iot_init.h | 1 + lib/include/iot_json_utils.h | 1 + lib/include/iot_linear_containers.h | 1 + lib/include/iot_logging_setup.h | 1 + lib/include/iot_mqtt.h | 1 + lib/include/iot_serializer.h | 1 + lib/include/iot_taskpool.h | 1 + lib/include/platform/iot_clock.h | 1 + lib/include/platform/iot_metrics.h | 1 + lib/include/platform/iot_network.h | 1 + lib/include/platform/iot_threads.h | 1 + lib/include/private/aws_iot.h | 1 + lib/include/private/aws_iot_defender_internal.h | 1 + lib/include/private/aws_iot_jobs_internal.h | 1 + lib/include/private/aws_iot_shadow_internal.h | 1 + lib/include/private/iot_error.h | 1 + lib/include/private/iot_logging.h | 1 + lib/include/private/iot_mqtt_internal.h | 1 + lib/include/private/iot_static_memory.h | 1 + lib/include/private/iot_taskpool_internal.h | 1 + lib/include/types/aws_iot_jobs_types.h | 1 + lib/include/types/aws_iot_shadow_types.h | 1 + lib/include/types/iot_mqtt_types.h | 1 + lib/include/types/iot_platform_types.h | 1 + lib/include/types/iot_taskpool_types.h | 1 + lib/source/common/aws_iot/aws_iot_operation.c | 1 + lib/source/common/aws_iot/aws_iot_parser.c | 1 + lib/source/common/aws_iot/aws_iot_subscription.c | 1 + lib/source/common/aws_iot/aws_iot_validate.c | 1 + lib/source/common/iot_init.c | 1 + lib/source/common/iot_logging.c | 1 + lib/source/common/iot_static_memory_common.c | 1 + lib/source/common/iot_taskpool.c | 1 + lib/source/common/iot_taskpool_static_memory.c | 1 + lib/source/defender/aws_iot_defender_api.c | 1 + lib/source/defender/aws_iot_defender_collector.c | 1 + lib/source/defender/aws_iot_defender_mqtt.c | 1 + lib/source/jobs/aws_iot_jobs_api.c | 1 + lib/source/jobs/aws_iot_jobs_operation.c | 1 + lib/source/jobs/aws_iot_jobs_serialize.c | 1 + lib/source/jobs/aws_iot_jobs_static_memory.c | 1 + lib/source/jobs/aws_iot_jobs_subscription.c | 1 + lib/source/mqtt/iot_mqtt_api.c | 1 + lib/source/mqtt/iot_mqtt_network.c | 1 + lib/source/mqtt/iot_mqtt_operation.c | 1 + lib/source/mqtt/iot_mqtt_serialize.c | 1 + lib/source/mqtt/iot_mqtt_static_memory.c | 1 + lib/source/mqtt/iot_mqtt_subscription.c | 1 + lib/source/mqtt/iot_mqtt_validate.c | 1 + lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c | 5 +---- lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c | 5 +---- lib/source/serializer/iot_json_utils.c | 1 + lib/source/serializer/iot_serializer_static_memory.c | 1 + lib/source/shadow/aws_iot_shadow_api.c | 1 + lib/source/shadow/aws_iot_shadow_operation.c | 1 + lib/source/shadow/aws_iot_shadow_parser.c | 1 + lib/source/shadow/aws_iot_shadow_static_memory.c | 1 + lib/source/shadow/aws_iot_shadow_subscription.c | 1 + tests/common/iot_tests_common.c | 1 + tests/common/unit/iot_tests_atomic.c | 1 + tests/common/unit/iot_tests_linear_containers.c | 1 + tests/common/unit/iot_tests_taskpool.c | 1 + tests/defender/aws_iot_tests_defender.c | 1 + tests/defender/system/aws_iot_tests_defender_system.c | 1 + tests/jobs/aws_iot_tests_jobs.c | 1 + tests/jobs/system/aws_iot_tests_jobs_system.c | 1 + tests/jobs/unit/aws_iot_tests_jobs_api.c | 1 + tests/jobs/unit/aws_iot_tests_jobs_serialize.c | 1 + tests/mqtt/access/iot_test_access_mqtt.h | 1 + tests/mqtt/access/iot_test_access_mqtt_api.c | 1 + tests/mqtt/access/iot_test_access_mqtt_subscription.c | 1 + tests/mqtt/iot_tests_mqtt.c | 1 + tests/mqtt/mock/iot_tests_mqtt_mock.c | 1 + tests/mqtt/mock/iot_tests_mqtt_mock.h | 1 + tests/mqtt/system/iot_tests_mqtt_system.c | 1 + tests/mqtt/unit/iot_tests_mqtt_api.c | 1 + tests/mqtt/unit/iot_tests_mqtt_receive.c | 1 + tests/mqtt/unit/iot_tests_mqtt_subscription.c | 1 + tests/mqtt/unit/iot_tests_mqtt_validate.c | 1 + tests/serializer/iot_tests_serializer.c | 1 + tests/serializer/unit/iot_tests_serializer_cbor.c | 5 +---- tests/shadow/aws_iot_tests_shadow.c | 1 + tests/shadow/system/aws_iot_tests_shadow_system.c | 1 + tests/shadow/unit/aws_iot_tests_shadow_api.c | 1 + tests/shadow/unit/aws_iot_tests_shadow_parser.c | 1 + 88 files changed, 88 insertions(+), 12 deletions(-) diff --git a/lib/include/aws_iot_defender.h b/lib/include/aws_iot_defender.h index c113659150..32d3d5a0da 100644 --- a/lib/include/aws_iot_defender.h +++ b/lib/include/aws_iot_defender.h @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/aws_iot_jobs.h b/lib/include/aws_iot_jobs.h index 281dbed087..d1c31d0719 100644 --- a/lib/include/aws_iot_jobs.h +++ b/lib/include/aws_iot_jobs.h @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/aws_iot_shadow.h b/lib/include/aws_iot_shadow.h index b06d91982f..dee775f255 100644 --- a/lib/include/aws_iot_shadow.h +++ b/lib/include/aws_iot_shadow.h @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/iot_init.h b/lib/include/iot_init.h index 734f33152e..1c38c6ab78 100644 --- a/lib/include/iot_init.h +++ b/lib/include/iot_init.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/iot_json_utils.h b/lib/include/iot_json_utils.h index a8fc0144f9..32dc5e84fa 100644 --- a/lib/include/iot_json_utils.h +++ b/lib/include/iot_json_utils.h @@ -1,4 +1,5 @@ /* + * IoT Serializer V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/iot_linear_containers.h b/lib/include/iot_linear_containers.h index 11ede95646..5fe222f07e 100644 --- a/lib/include/iot_linear_containers.h +++ b/lib/include/iot_linear_containers.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/iot_logging_setup.h b/lib/include/iot_logging_setup.h index 35ace4b19f..91dd75845a 100644 --- a/lib/include/iot_logging_setup.h +++ b/lib/include/iot_logging_setup.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/iot_mqtt.h b/lib/include/iot_mqtt.h index a247fdaa31..b3818e1fff 100644 --- a/lib/include/iot_mqtt.h +++ b/lib/include/iot_mqtt.h @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/iot_serializer.h b/lib/include/iot_serializer.h index db79c3761b..1380796bba 100644 --- a/lib/include/iot_serializer.h +++ b/lib/include/iot_serializer.h @@ -1,4 +1,5 @@ /* + * IoT Serializer V1.1.0 * Amazon FreeRTOS System Initialization * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * diff --git a/lib/include/iot_taskpool.h b/lib/include/iot_taskpool.h index 6277da46ef..441b20883a 100644 --- a/lib/include/iot_taskpool.h +++ b/lib/include/iot_taskpool.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/platform/iot_clock.h b/lib/include/platform/iot_clock.h index 99a4d83875..98e69d2165 100644 --- a/lib/include/platform/iot_clock.h +++ b/lib/include/platform/iot_clock.h @@ -1,4 +1,5 @@ /* + * IoT Platform V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/platform/iot_metrics.h b/lib/include/platform/iot_metrics.h index 199c6148d5..7cd5741f1a 100644 --- a/lib/include/platform/iot_metrics.h +++ b/lib/include/platform/iot_metrics.h @@ -1,4 +1,5 @@ /* + * IoT Platform V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/platform/iot_network.h b/lib/include/platform/iot_network.h index a94becb354..b9174eac0e 100644 --- a/lib/include/platform/iot_network.h +++ b/lib/include/platform/iot_network.h @@ -1,4 +1,5 @@ /* + * IoT Platform V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/platform/iot_threads.h b/lib/include/platform/iot_threads.h index 2791a1d245..057fde0cbe 100644 --- a/lib/include/platform/iot_threads.h +++ b/lib/include/platform/iot_threads.h @@ -1,4 +1,5 @@ /* + * IoT Platform V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/aws_iot.h b/lib/include/private/aws_iot.h index 0e01498c4b..cfc75dd9e2 100644 --- a/lib/include/private/aws_iot.h +++ b/lib/include/private/aws_iot.h @@ -1,4 +1,5 @@ /* + * AWS IoT Common V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/aws_iot_defender_internal.h b/lib/include/private/aws_iot_defender_internal.h index c1820dd8bf..1f09149270 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/lib/include/private/aws_iot_defender_internal.h @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/aws_iot_jobs_internal.h b/lib/include/private/aws_iot_jobs_internal.h index 18a71cdb80..a94d3537c9 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/lib/include/private/aws_iot_jobs_internal.h @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/aws_iot_shadow_internal.h b/lib/include/private/aws_iot_shadow_internal.h index fea1a1163c..7e16284973 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/lib/include/private/aws_iot_shadow_internal.h @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/iot_error.h b/lib/include/private/iot_error.h index fadaf2413f..3b8dda2b8b 100644 --- a/lib/include/private/iot_error.h +++ b/lib/include/private/iot_error.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/iot_logging.h b/lib/include/private/iot_logging.h index 39368f48c0..bc3ec21bd0 100644 --- a/lib/include/private/iot_logging.h +++ b/lib/include/private/iot_logging.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/iot_mqtt_internal.h b/lib/include/private/iot_mqtt_internal.h index 29a1f100b4..2e7f70d470 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/lib/include/private/iot_mqtt_internal.h @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/iot_static_memory.h b/lib/include/private/iot_static_memory.h index 51014289d8..b51a6f0a68 100644 --- a/lib/include/private/iot_static_memory.h +++ b/lib/include/private/iot_static_memory.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/private/iot_taskpool_internal.h b/lib/include/private/iot_taskpool_internal.h index 97230536a1..af6d22a3eb 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/lib/include/private/iot_taskpool_internal.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/types/aws_iot_jobs_types.h b/lib/include/types/aws_iot_jobs_types.h index ffb5ff689c..0d91c8c59a 100644 --- a/lib/include/types/aws_iot_jobs_types.h +++ b/lib/include/types/aws_iot_jobs_types.h @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/types/aws_iot_shadow_types.h b/lib/include/types/aws_iot_shadow_types.h index b99ee48491..5103f88657 100644 --- a/lib/include/types/aws_iot_shadow_types.h +++ b/lib/include/types/aws_iot_shadow_types.h @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/types/iot_mqtt_types.h b/lib/include/types/iot_mqtt_types.h index ae2a2a1144..fa5cefca14 100644 --- a/lib/include/types/iot_mqtt_types.h +++ b/lib/include/types/iot_mqtt_types.h @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/types/iot_platform_types.h b/lib/include/types/iot_platform_types.h index 8405adcb51..18875ab7e1 100644 --- a/lib/include/types/iot_platform_types.h +++ b/lib/include/types/iot_platform_types.h @@ -1,4 +1,5 @@ /* + * IoT Platform V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/include/types/iot_taskpool_types.h b/lib/include/types/iot_taskpool_types.h index 89252fce27..26ff9b32b5 100644 --- a/lib/include/types/iot_taskpool_types.h +++ b/lib/include/types/iot_taskpool_types.h @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/aws_iot/aws_iot_operation.c b/lib/source/common/aws_iot/aws_iot_operation.c index aaa2bc6039..279d891947 100644 --- a/lib/source/common/aws_iot/aws_iot_operation.c +++ b/lib/source/common/aws_iot/aws_iot_operation.c @@ -1,4 +1,5 @@ /* + * AWS IoT Common V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/aws_iot/aws_iot_parser.c b/lib/source/common/aws_iot/aws_iot_parser.c index b0091c1211..05216b9914 100644 --- a/lib/source/common/aws_iot/aws_iot_parser.c +++ b/lib/source/common/aws_iot/aws_iot_parser.c @@ -1,4 +1,5 @@ /* + * AWS IoT Common V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/aws_iot/aws_iot_subscription.c b/lib/source/common/aws_iot/aws_iot_subscription.c index 99c650005d..771fa1da22 100644 --- a/lib/source/common/aws_iot/aws_iot_subscription.c +++ b/lib/source/common/aws_iot/aws_iot_subscription.c @@ -1,4 +1,5 @@ /* + * AWS IoT Common V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/aws_iot/aws_iot_validate.c b/lib/source/common/aws_iot/aws_iot_validate.c index 96df3564ca..e7c87497b0 100644 --- a/lib/source/common/aws_iot/aws_iot_validate.c +++ b/lib/source/common/aws_iot/aws_iot_validate.c @@ -1,4 +1,5 @@ /* + * AWS IoT Common V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/iot_init.c b/lib/source/common/iot_init.c index a82ac21299..f703c80348 100644 --- a/lib/source/common/iot_init.c +++ b/lib/source/common/iot_init.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/iot_logging.c b/lib/source/common/iot_logging.c index f7d4a3dd85..324241d43d 100644 --- a/lib/source/common/iot_logging.c +++ b/lib/source/common/iot_logging.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/iot_static_memory_common.c b/lib/source/common/iot_static_memory_common.c index 15773c0b12..aa26c15251 100644 --- a/lib/source/common/iot_static_memory_common.c +++ b/lib/source/common/iot_static_memory_common.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/iot_taskpool.c b/lib/source/common/iot_taskpool.c index c52c8d2f01..04b34d646c 100644 --- a/lib/source/common/iot_taskpool.c +++ b/lib/source/common/iot_taskpool.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/common/iot_taskpool_static_memory.c b/lib/source/common/iot_taskpool_static_memory.c index 7b30b8d1bd..552ad8e7ce 100644 --- a/lib/source/common/iot_taskpool_static_memory.c +++ b/lib/source/common/iot_taskpool_static_memory.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/defender/aws_iot_defender_api.c b/lib/source/defender/aws_iot_defender_api.c index 5c31e5ba28..ce89c47ed9 100644 --- a/lib/source/defender/aws_iot_defender_api.c +++ b/lib/source/defender/aws_iot_defender_api.c @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/defender/aws_iot_defender_collector.c b/lib/source/defender/aws_iot_defender_collector.c index c20821ade7..298c078c25 100644 --- a/lib/source/defender/aws_iot_defender_collector.c +++ b/lib/source/defender/aws_iot_defender_collector.c @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/lib/source/defender/aws_iot_defender_mqtt.c index 670079c501..641b09fda3 100644 --- a/lib/source/defender/aws_iot_defender_mqtt.c +++ b/lib/source/defender/aws_iot_defender_mqtt.c @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/lib/source/jobs/aws_iot_jobs_api.c index c9f0184d76..640da941dd 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/lib/source/jobs/aws_iot_jobs_api.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/lib/source/jobs/aws_iot_jobs_operation.c index 1afcf36ca7..03fdf0f1d5 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/lib/source/jobs/aws_iot_jobs_operation.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/lib/source/jobs/aws_iot_jobs_serialize.c index acf2c92b92..e3c92dbecb 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/lib/source/jobs/aws_iot_jobs_serialize.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/jobs/aws_iot_jobs_static_memory.c b/lib/source/jobs/aws_iot_jobs_static_memory.c index 8af0f55bd2..ddfaa2d981 100644 --- a/lib/source/jobs/aws_iot_jobs_static_memory.c +++ b/lib/source/jobs/aws_iot_jobs_static_memory.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/lib/source/jobs/aws_iot_jobs_subscription.c index 71b88e4df5..d4c106adb3 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/lib/source/jobs/aws_iot_jobs_subscription.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_api.c b/lib/source/mqtt/iot_mqtt_api.c index 4e318cad89..e581d9bcc4 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/lib/source/mqtt/iot_mqtt_api.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_network.c b/lib/source/mqtt/iot_mqtt_network.c index f453390a72..cb3266187e 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/lib/source/mqtt/iot_mqtt_network.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/lib/source/mqtt/iot_mqtt_operation.c index 765d52b237..e430338ac9 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/lib/source/mqtt/iot_mqtt_operation.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/lib/source/mqtt/iot_mqtt_serialize.c index 7274c8229c..0e54ad4a6f 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/lib/source/mqtt/iot_mqtt_serialize.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_static_memory.c b/lib/source/mqtt/iot_mqtt_static_memory.c index 8b6e56acec..345cecc0c0 100644 --- a/lib/source/mqtt/iot_mqtt_static_memory.c +++ b/lib/source/mqtt/iot_mqtt_static_memory.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/lib/source/mqtt/iot_mqtt_subscription.c index e3a3233933..2e813040e6 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/lib/source/mqtt/iot_mqtt_subscription.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/lib/source/mqtt/iot_mqtt_validate.c index 3bb477503e..e2885a5b11 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/lib/source/mqtt/iot_mqtt_validate.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c index 8e3fcaf344..d431fe3895 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c @@ -1,5 +1,5 @@ /* - * Amazon FreeRTOS System Initialization + * IoT Serializer V1.1.0 * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -18,9 +18,6 @@ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://aws.amazon.com/freertos - * http://www.FreeRTOS.org */ /** diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c index 77a2aef8a2..0cbfb7aae8 100644 --- a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c +++ b/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c @@ -1,5 +1,5 @@ /* - * Amazon FreeRTOS System Initialization + * IoT Serializer V1.1.0 * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -18,9 +18,6 @@ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://aws.amazon.com/freertos - * http://www.FreeRTOS.org */ /** diff --git a/lib/source/serializer/iot_json_utils.c b/lib/source/serializer/iot_json_utils.c index 4306a90e30..f976ef91fb 100644 --- a/lib/source/serializer/iot_json_utils.c +++ b/lib/source/serializer/iot_json_utils.c @@ -1,4 +1,5 @@ /* + * IoT Serializer V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/serializer/iot_serializer_static_memory.c b/lib/source/serializer/iot_serializer_static_memory.c index 15240507b7..94d2b10336 100644 --- a/lib/source/serializer/iot_serializer_static_memory.c +++ b/lib/source/serializer/iot_serializer_static_memory.c @@ -1,4 +1,5 @@ /* + * IoT Serializer V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/lib/source/shadow/aws_iot_shadow_api.c index 754d15d4d5..29e56621ba 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/lib/source/shadow/aws_iot_shadow_api.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/lib/source/shadow/aws_iot_shadow_operation.c index b36478365c..1f75572213 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/lib/source/shadow/aws_iot_shadow_operation.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/lib/source/shadow/aws_iot_shadow_parser.c index c0b4fe3c0f..74bbeb28a7 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/lib/source/shadow/aws_iot_shadow_parser.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/shadow/aws_iot_shadow_static_memory.c b/lib/source/shadow/aws_iot_shadow_static_memory.c index 045cd8d921..15204dca7c 100644 --- a/lib/source/shadow/aws_iot_shadow_static_memory.c +++ b/lib/source/shadow/aws_iot_shadow_static_memory.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/lib/source/shadow/aws_iot_shadow_subscription.c index 38fb02b88c..de904c03fb 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/lib/source/shadow/aws_iot_shadow_subscription.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/common/iot_tests_common.c b/tests/common/iot_tests_common.c index 6561e12d3f..b3e8739eb6 100644 --- a/tests/common/iot_tests_common.c +++ b/tests/common/iot_tests_common.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/common/unit/iot_tests_atomic.c b/tests/common/unit/iot_tests_atomic.c index 121db8e060..e07a785913 100644 --- a/tests/common/unit/iot_tests_atomic.c +++ b/tests/common/unit/iot_tests_atomic.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/common/unit/iot_tests_linear_containers.c b/tests/common/unit/iot_tests_linear_containers.c index 6a58ddcd80..a518305250 100644 --- a/tests/common/unit/iot_tests_linear_containers.c +++ b/tests/common/unit/iot_tests_linear_containers.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/common/unit/iot_tests_taskpool.c b/tests/common/unit/iot_tests_taskpool.c index 96f1581543..1072ed4d80 100644 --- a/tests/common/unit/iot_tests_taskpool.c +++ b/tests/common/unit/iot_tests_taskpool.c @@ -1,4 +1,5 @@ /* + * IoT Common V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/defender/aws_iot_tests_defender.c b/tests/defender/aws_iot_tests_defender.c index f5eccb5f24..5d876bbdc3 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/tests/defender/aws_iot_tests_defender.c @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/defender/system/aws_iot_tests_defender_system.c b/tests/defender/system/aws_iot_tests_defender_system.c index 7cca68fc12..bc24561596 100644 --- a/tests/defender/system/aws_iot_tests_defender_system.c +++ b/tests/defender/system/aws_iot_tests_defender_system.c @@ -1,4 +1,5 @@ /* + * AWS IoT Defender V2.0.1 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/jobs/aws_iot_tests_jobs.c b/tests/jobs/aws_iot_tests_jobs.c index 3005416899..6e4945ef3f 100644 --- a/tests/jobs/aws_iot_tests_jobs.c +++ b/tests/jobs/aws_iot_tests_jobs.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/tests/jobs/system/aws_iot_tests_jobs_system.c index dc6a8be06c..88d828b127 100644 --- a/tests/jobs/system/aws_iot_tests_jobs_system.c +++ b/tests/jobs/system/aws_iot_tests_jobs_system.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/tests/jobs/unit/aws_iot_tests_jobs_api.c index 7a3d15ea24..7184cc7f67 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_api.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_api.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c index 6361755657..8bd73fd0ac 100644 --- a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c +++ b/tests/jobs/unit/aws_iot_tests_jobs_serialize.c @@ -1,4 +1,5 @@ /* + * AWS IoT Jobs V1.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/access/iot_test_access_mqtt.h b/tests/mqtt/access/iot_test_access_mqtt.h index ffc801cf34..30e66c38cb 100644 --- a/tests/mqtt/access/iot_test_access_mqtt.h +++ b/tests/mqtt/access/iot_test_access_mqtt.h @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/access/iot_test_access_mqtt_api.c b/tests/mqtt/access/iot_test_access_mqtt_api.c index 5116d3d69e..298b3f4082 100644 --- a/tests/mqtt/access/iot_test_access_mqtt_api.c +++ b/tests/mqtt/access/iot_test_access_mqtt_api.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/access/iot_test_access_mqtt_subscription.c b/tests/mqtt/access/iot_test_access_mqtt_subscription.c index fca7f2930a..f898764754 100644 --- a/tests/mqtt/access/iot_test_access_mqtt_subscription.c +++ b/tests/mqtt/access/iot_test_access_mqtt_subscription.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/iot_tests_mqtt.c b/tests/mqtt/iot_tests_mqtt.c index fd580127e5..6322c12e60 100644 --- a/tests/mqtt/iot_tests_mqtt.c +++ b/tests/mqtt/iot_tests_mqtt.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.c b/tests/mqtt/mock/iot_tests_mqtt_mock.c index ad775b1d0d..1d17cfff1f 100644 --- a/tests/mqtt/mock/iot_tests_mqtt_mock.c +++ b/tests/mqtt/mock/iot_tests_mqtt_mock.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.h b/tests/mqtt/mock/iot_tests_mqtt_mock.h index a898e4595c..5f913c5453 100644 --- a/tests/mqtt/mock/iot_tests_mqtt_mock.h +++ b/tests/mqtt/mock/iot_tests_mqtt_mock.h @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/tests/mqtt/system/iot_tests_mqtt_system.c index 4fc0ff6af5..e43d066ca0 100644 --- a/tests/mqtt/system/iot_tests_mqtt_system.c +++ b/tests/mqtt/system/iot_tests_mqtt_system.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/tests/mqtt/unit/iot_tests_mqtt_api.c index d5bf1a6da5..0c89c3d988 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_api.c +++ b/tests/mqtt/unit/iot_tests_mqtt_api.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/tests/mqtt/unit/iot_tests_mqtt_receive.c index eef146d8c1..3108c1f76c 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_receive.c +++ b/tests/mqtt/unit/iot_tests_mqtt_receive.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/tests/mqtt/unit/iot_tests_mqtt_subscription.c index ec114bf83a..7974fc01ff 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_subscription.c +++ b/tests/mqtt/unit/iot_tests_mqtt_subscription.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/tests/mqtt/unit/iot_tests_mqtt_validate.c index 7659114408..c951ec498d 100644 --- a/tests/mqtt/unit/iot_tests_mqtt_validate.c +++ b/tests/mqtt/unit/iot_tests_mqtt_validate.c @@ -1,4 +1,5 @@ /* + * IoT MQTT V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/serializer/iot_tests_serializer.c b/tests/serializer/iot_tests_serializer.c index f818907e47..58302bf9a0 100644 --- a/tests/serializer/iot_tests_serializer.c +++ b/tests/serializer/iot_tests_serializer.c @@ -1,4 +1,5 @@ /* + * IoT Serializer V1.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/serializer/unit/iot_tests_serializer_cbor.c b/tests/serializer/unit/iot_tests_serializer_cbor.c index 34ba632014..66da2041fb 100644 --- a/tests/serializer/unit/iot_tests_serializer_cbor.c +++ b/tests/serializer/unit/iot_tests_serializer_cbor.c @@ -1,5 +1,5 @@ /* - * Amazon FreeRTOS Serializer Test + * IoT Serializer V1.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -18,9 +18,6 @@ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://aws.amazon.com/freertos - * http://www.FreeRTOS.org */ /* Standard includes. */ diff --git a/tests/shadow/aws_iot_tests_shadow.c b/tests/shadow/aws_iot_tests_shadow.c index 6d3769e4d7..99f5bf5ad0 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/tests/shadow/aws_iot_tests_shadow.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/tests/shadow/system/aws_iot_tests_shadow_system.c index 501a1b3231..7c45d54f29 100644 --- a/tests/shadow/system/aws_iot_tests_shadow_system.c +++ b/tests/shadow/system/aws_iot_tests_shadow_system.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/tests/shadow/unit/aws_iot_tests_shadow_api.c index 1fd809bb12..c28bd61762 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_api.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_api.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/tests/shadow/unit/aws_iot_tests_shadow_parser.c index 063e91f635..62ab20a360 100644 --- a/tests/shadow/unit/aws_iot_tests_shadow_parser.c +++ b/tests/shadow/unit/aws_iot_tests_shadow_parser.c @@ -1,4 +1,5 @@ /* + * AWS IoT Shadow V2.1.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of From 1df8254e6df2e015991699cbd77f6495ae28544d Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 3 Oct 2019 12:56:02 -0700 Subject: [PATCH 278/844] Separate Linux CI setup (#586) --- .travis.yml | 23 ++---------------- scripts/setup/ci_setup_linux.sh | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 scripts/setup/ci_setup_linux.sh diff --git a/.travis.yml b/.travis.yml index 7e3e849841..95b9da74e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,35 +26,16 @@ jobs: - if: type = pull_request env: RUN_TEST=doc -# Update repositories. -before_install: - - sudo apt-get update - # Install dependencies. install: - # Install OpenSSL if needed. - - if [ "$NETWORK_STACK" = "openssl" ]; then sudo apt-get install -y libssl-dev; fi - # Install Mosquitto for MQTT pull request builds. - - if [ "$RUN_TEST" = "mqtt" ] && [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sudo apt-get install -y mosquitto; fi - # Install dependencies for Jobs tests. - - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sudo apt-get install -y python3-setuptools python3-pip athena-jot; fi - - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then pip3 install --user wheel; fi - - if [ "$RUN_TEST" = "jobs" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then pip3 install --user awscli; fi - # Install graphviz only for documentation builds. - - if [ "$RUN_TEST" = "doc" ]; then sudo apt-get install -y graphviz; fi - # Install dependencies for coverage builds. - - if [ "$RUN_TEST" = "coverage" ]; then sudo apt-get install -y python3-setuptools python3-pip lcov athena-jot; fi - - if [ "$RUN_TEST" = "coverage" ]; then pip3 install --user wheel; fi - - if [ "$RUN_TEST" = "coverage" ]; then pip3 install --user cpp-coveralls; fi - - if [ "$RUN_TEST" = "coverage" ]; then pip3 install --user awscli; fi + # Run the OS-specific CI setup script. + - bash ./scripts/setup/ci_setup_$TRAVIS_OS_NAME.sh # Run the test script based on matrix environment variable. script: # Set identifier (client identifier OR Thing Name). - export IOT_IDENTIFIER="$IOT_IDENTIFIER_PREFIX$RUN_TEST" - if [ "$NETWORK_STACK" = "openssl" ]; then export IOT_IDENTIFIER="${IOT_IDENTIFIER}ossl"; fi - # Set default compiler options. Individual test scripts may override this. - - export COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" # Choose the network abstraction. - if [ "$NETWORK_STACK" = "openssl" ]; then export IOT_NETWORK_USE_OPENSSL=1; else export IOT_NETWORK_USE_OPENSSL=0; fi # Get AWS credentials when not a pull request build. diff --git a/scripts/setup/ci_setup_linux.sh b/scripts/setup/ci_setup_linux.sh new file mode 100644 index 0000000000..6c6ec4c272 --- /dev/null +++ b/scripts/setup/ci_setup_linux.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +# Travis CI uses this script to set up the test environment on Linux. + +# Update repositories. +sudo apt-get update + +# Install OpenSSL if needed. +if [ "$NETWORK_STACK" = "openssl" ]; then + sudo apt-get install -y libssl-dev; +fi + +# Set up for pull request builds. +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + # Install Mosquitto for MQTT pull request builds. + if [ "$RUN_TEST" = "mqtt" ]; then + sudo apt-get install -y mosquitto; + fi + + # Install graphviz for documentation builds. + if [ "$RUN_TEST" = "doc" ]; then + sudo apt-get install -y graphviz; + fi +# Set up for coverage builds. +else + # Install dependencies for Jobs tests. + # Coverage needs these too since it runs the Jobs tests. + if [ "$RUN_TEST" = "jobs" ] || [ "$RUN_TEST" = "coverage" ]; then + sudo apt-get install -y python3-setuptools python3-pip athena-jot; + pip3 install --user wheel; + pip3 install --user awscli; + fi + + # Install dependencies for coverage builds. + if [ "$RUN_TEST" = "coverage" ]; then + sudo apt-get install -y lcov; + pip3 install --user cpp-coveralls; + fi +fi + +# Set default compiler options for Linux. Individual test scripts may override this. +export COMPILER_OPTIONS="-Wall -Wextra -fsanitize=thread" From 1de334a5bb0580946958c0f1402135d7019216bc Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Fri, 4 Oct 2019 13:10:37 -0700 Subject: [PATCH 279/844] Add macOS MQTT build to Travis CI (#582) --- .gitignore | 3 +++ .travis.yml | 9 ++++++++- platform/ports/macos/source/iot_threads_macos.c | 3 +++ scripts/setup/ci_setup_osx.sh | 12 ++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 scripts/setup/ci_setup_osx.sh diff --git a/.gitignore b/.gitignore index 3ca54d3698..9bf604884d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ doc/tag/* # Ignore CMake build directory. build/ + +# Ignore macOS metadata files. +.DS_Store diff --git a/.travis.yml b/.travis.yml index 95b9da74e3..b481231acf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ branches: only: - v4_beta -# Build on Ubuntu 16.04. +# Build on Ubuntu 16.04 by default. os: linux dist: xenial @@ -25,6 +25,13 @@ jobs: env: RUN_TEST=coverage - if: type = pull_request env: RUN_TEST=doc + - os: osx + env: RUN_TEST=mqtt NETWORK_STACK=mbedtls + # Cache Homebrew for faster macOS builds. + cache: + directories: + - $HOME/Library/Caches/Homebrew + - /usr/local/Homebrew # Install dependencies. install: diff --git a/platform/ports/macos/source/iot_threads_macos.c b/platform/ports/macos/source/iot_threads_macos.c index bcb67a9b9b..c67e64ea0e 100644 --- a/platform/ports/macos/source/iot_threads_macos.c +++ b/platform/ports/macos/source/iot_threads_macos.c @@ -355,6 +355,9 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, uint32_t initialValue, uint32_t maxValue ) { + /* Unused parameter. */ + ( void ) maxValue; + bool status = true; /* Create a GCD semaphore. */ diff --git a/scripts/setup/ci_setup_osx.sh b/scripts/setup/ci_setup_osx.sh new file mode 100644 index 0000000000..b9b8acccb1 --- /dev/null +++ b/scripts/setup/ci_setup_osx.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Travis CI uses this script to set up the test environment on macOS. + +# Install and start Mosquitto for MQTT pull request builds. +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + brew install mosquitto + brew services start mosquitto +fi + +# Set compiler options for macOS. Silence field initializer warnings. +export COMPILER_OPTIONS="-Wall -Wextra -Wno-missing-field-initializers" From 05584e72a7e6e811178425ec015d6ff3b001339c Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Mon, 7 Oct 2019 15:24:36 -0700 Subject: [PATCH 280/844] Add Windows build to Travis CI (#587) --- .travis.yml | 5 ++++- scripts/ci_test_mqtt.sh | 27 ++++++++++++++++++++++----- scripts/setup/ci_setup_windows.sh | 10 ++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 scripts/setup/ci_setup_windows.sh diff --git a/.travis.yml b/.travis.yml index b481231acf..909703a7ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,11 +32,14 @@ jobs: directories: - $HOME/Library/Caches/Homebrew - /usr/local/Homebrew + - os: windows + env: RUN_TEST=mqtt NETWORK_STACK=mbedtls + compiler: msvc # Install dependencies. install: # Run the OS-specific CI setup script. - - bash ./scripts/setup/ci_setup_$TRAVIS_OS_NAME.sh + - source ./scripts/setup/ci_setup_$TRAVIS_OS_NAME.sh # Run the test script based on matrix environment variable. script: diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index b31aa73d1e..01e0a86311 100755 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -6,11 +6,15 @@ set -e CMAKE_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" " +TEST_OPTIONS="" # For pull request builds, run against test.mosquitto.org. Otherwise, run against AWS IoT. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then CMAKE_FLAGS+="$AWS_IOT_CREDENTIAL_DEFINES $COMPILER_OPTIONS" DEMO_OPTIONS="-i $IOT_IDENTIFIER" +elif [ "$TRAVIS_OS_NAME" = "windows" ]; then + CMAKE_FLAGS+=$COMPILER_OPTIONS + TEST_OPTIONS+="-n" else CMAKE_FLAGS+="-DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\" $COMPILER_OPTIONS" DEMO_OPTIONS="-n -i $IOT_IDENTIFIER -u -h localhost -p 1883" @@ -18,15 +22,28 @@ fi # Build executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -make -j2 iot_tests_mqtt iot_demo_mqtt +if [ "$TRAVIS_OS_NAME" != "windows" ]; then + make -j2 iot_tests_mqtt iot_demo_mqtt +else + MSBuild.exe tests/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal + MSBuild.exe demos/app/iot_demo_mqtt.vcxproj -m -clp:summary -verbosity:minimal + mv ./bin/Debug/* ./bin/ +fi # Run tests and demos. -./bin/iot_tests_mqtt -./bin/iot_demo_mqtt $DEMO_OPTIONS +./bin/iot_tests_mqtt $TEST_OPTIONS +if [ "$TRAVIS_OS_NAME" != "windows" ] || [ "$TRAVIS_PULL_REQUEST" = "false" ]; then + ./bin/iot_demo_mqtt $DEMO_OPTIONS +fi # Rebuild in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -make -j2 iot_tests_mqtt +if [ "$TRAVIS_OS_NAME" != "windows" ]; then + make -j2 iot_tests_mqtt +else + MSBuild.exe tests/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal + mv ./bin/Debug/* ./bin/ +fi # Run tests in static memory mode. -./bin/iot_tests_mqtt +./bin/iot_tests_mqtt $TEST_OPTIONS diff --git a/scripts/setup/ci_setup_windows.sh b/scripts/setup/ci_setup_windows.sh new file mode 100644 index 0000000000..1dc3a0dd71 --- /dev/null +++ b/scripts/setup/ci_setup_windows.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# Travis CI uses this script to set up the test environment on Windows. + +# Location of MSBuild.exe in the test environment +MSBUILD_PATH="/c/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin" +export PATH=$PATH:$MSBUILD_PATH + +# Use default compiler options for windows. +export COMPILER_OPTIONS="" From fee7dcc00a0aca67ae0cdbfd99653b3a570e700b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 7 Oct 2019 15:55:03 -0700 Subject: [PATCH 281/844] Rearrange directories (#589) --- .travis.yml | 5 - CMakeLists.txt | 120 ++++------------ README.md | 2 +- demos/CMakeLists.txt | 92 +++++++++++++ demos/README.md | 2 +- demos/app/CMakeLists.txt | 94 ------------- demos/app/iot_demo.c | 2 +- demos/app/iot_demo_arguments.c | 2 +- demos/{source => src}/aws_iot_demo_defender.c | 0 demos/{source => src}/aws_iot_demo_shadow.c | 0 demos/{source => src}/iot_demo_mqtt.c | 0 doc/config/defender | 8 +- doc/config/jobs | 12 +- doc/config/linear_containers | 2 +- doc/config/logging | 5 +- doc/config/mqtt | 17 +-- doc/config/platform | 6 +- doc/config/shadow | 15 +- doc/config/static_memory | 4 +- doc/config/taskpool | 11 +- lib/source/common/CMakeLists.txt | 60 -------- lib/source/defender/CMakeLists.txt | 25 ---- lib/source/jobs/CMakeLists.txt | 29 ---- lib/source/mqtt/CMakeLists.txt | 31 ----- lib/source/serializer/CMakeLists.txt | 27 ---- lib/source/shadow/CMakeLists.txt | 29 ---- libraries/aws/common/CMakeLists.txt | 29 ++++ .../aws/common/include}/aws_iot.h | 0 .../aws/common/src}/aws_iot_operation.c | 4 +- .../aws/common/src}/aws_iot_parser.c | 4 +- .../aws/common/src}/aws_iot_subscription.c | 4 +- .../aws/common/src}/aws_iot_validate.c | 4 +- libraries/aws/defender/CMakeLists.txt | 64 +++++++++ .../aws/defender}/include/aws_iot_defender.h | 0 .../aws/defender/src}/aws_iot_defender_api.c | 0 .../src}/aws_iot_defender_collector.c | 0 .../aws/defender/src}/aws_iot_defender_mqtt.c | 0 .../src}/private/aws_iot_defender_internal.h | 2 +- .../defender/test}/aws_iot_tests_defender.c | 6 + .../system/aws_iot_tests_defender_system.c | 0 libraries/aws/jobs/CMakeLists.txt | 75 ++++++++++ .../aws/jobs}/include/aws_iot_jobs.h | 0 .../jobs}/include/types/aws_iot_jobs_types.h | 0 .../aws/jobs/src}/aws_iot_jobs_api.c | 2 +- .../aws/jobs/src}/aws_iot_jobs_operation.c | 2 +- .../aws/jobs/src}/aws_iot_jobs_serialize.c | 2 +- .../jobs/src}/aws_iot_jobs_static_memory.c | 2 +- .../aws/jobs/src}/aws_iot_jobs_subscription.c | 2 +- .../jobs/src}/private/aws_iot_jobs_internal.h | 2 +- .../aws/jobs/test}/aws_iot_tests_jobs.c | 6 + .../test}/system/aws_iot_tests_jobs_system.c | 0 .../jobs/test}/unit/aws_iot_tests_jobs_api.c | 0 .../test}/unit/aws_iot_tests_jobs_serialize.c | 0 libraries/aws/shadow/CMakeLists.txt | 75 ++++++++++ .../aws/shadow}/include/aws_iot_shadow.h | 0 .../include/types/aws_iot_shadow_types.h | 0 .../aws/shadow/src}/aws_iot_shadow_api.c | 2 +- .../shadow/src}/aws_iot_shadow_operation.c | 2 +- .../aws/shadow/src}/aws_iot_shadow_parser.c | 2 +- .../src}/aws_iot_shadow_static_memory.c | 2 +- .../shadow/src}/aws_iot_shadow_subscription.c | 2 +- .../src}/private/aws_iot_shadow_internal.h | 2 +- .../aws/shadow/test}/aws_iot_tests_shadow.c | 6 + .../system/aws_iot_tests_shadow_system.c | 0 .../test}/unit/aws_iot_tests_shadow_api.c | 0 .../test}/unit/aws_iot_tests_shadow_parser.c | 0 .../platform/iot_clock.h | 0 .../platform/iot_metrics.h | 0 .../platform/iot_network.h | 0 .../platform/iot_threads.h | 0 .../platform}/types/iot_platform_types.h | 0 libraries/standard/common/CMakeLists.txt | 128 ++++++++++++++++++ .../standard/common/include}/iot_error.h | 0 .../standard/common}/include/iot_init.h | 0 .../common}/include/iot_linear_containers.h | 0 .../standard/common/include}/iot_logging.h | 0 .../common}/include/iot_logging_setup.h | 2 +- .../common/include}/iot_static_memory.h | 0 .../standard/common}/include/iot_taskpool.h | 0 .../include/types/iot_taskpool_types.h | 0 .../standard/common/src}/iot_init.c | 4 +- .../standard/common/src}/iot_logging.c | 4 +- .../common/src}/iot_static_memory_common.c | 2 +- .../standard/common/src}/iot_taskpool.c | 0 .../common/src}/iot_taskpool_static_memory.c | 2 +- .../src}/private/iot_taskpool_internal.h | 4 +- .../standard/common/test}/iot_tests_common.c | 6 + .../common/test}/unit/iot_tests_atomic.c | 0 .../test}/unit/iot_tests_linear_containers.c | 0 .../common/test}/unit/iot_tests_taskpool.c | 0 libraries/standard/mqtt/CMakeLists.txt | 99 ++++++++++++++ .../standard/mqtt}/include/iot_mqtt.h | 0 .../mqtt}/include/types/iot_mqtt_types.h | 0 .../standard/mqtt/src}/iot_mqtt_api.c | 2 +- .../standard/mqtt/src}/iot_mqtt_network.c | 2 +- .../standard/mqtt/src}/iot_mqtt_operation.c | 2 +- .../standard/mqtt/src}/iot_mqtt_serialize.c | 2 +- .../mqtt/src}/iot_mqtt_static_memory.c | 2 +- .../mqtt/src}/iot_mqtt_subscription.c | 2 +- .../standard/mqtt/src}/iot_mqtt_validate.c | 2 +- .../mqtt/src}/private/iot_mqtt_internal.h | 2 +- .../mqtt/test}/access/iot_test_access_mqtt.h | 0 .../test}/access/iot_test_access_mqtt_api.c | 0 .../iot_test_access_mqtt_subscription.c | 0 .../standard/mqtt/test}/iot_tests_mqtt.c | 6 + .../mqtt/test}/mock/iot_tests_mqtt_mock.c | 2 +- .../mqtt/test}/mock/iot_tests_mqtt_mock.h | 0 .../mqtt/test}/system/iot_tests_mqtt_system.c | 0 .../mqtt/test}/unit/iot_tests_mqtt_api.c | 0 .../mqtt/test}/unit/iot_tests_mqtt_receive.c | 0 .../test}/unit/iot_tests_mqtt_subscription.c | 0 .../mqtt/test}/unit/iot_tests_mqtt_validate.c | 0 libraries/standard/serializer/CMakeLists.txt | 58 ++++++++ .../serializer}/include/iot_json_utils.h | 0 .../serializer}/include/iot_serializer.h | 2 +- .../cbor/iot_serializer_tinycbor_decoder.c | 0 .../cbor/iot_serializer_tinycbor_encoder.c | 0 .../standard/serializer/src}/iot_json_utils.c | 0 .../src}/iot_serializer_static_memory.c | 2 +- .../serializer/test}/iot_tests_serializer.c | 6 + .../test}/unit/iot_tests_serializer_cbor.c | 0 platform/README.md | 19 --- platform/ports/macos/CMakeLists.txt | 30 ---- platform/ports/template/CMakeLists.txt | 37 ----- platform/ports/win32/CMakeLists.txt | 26 ---- ports/README.md | 19 +++ .../common}/include/atomic/iot_atomic_gcc.h | 0 .../include/atomic/iot_atomic_generic.h | 0 .../common}/include/iot_atomic.h | 0 .../common}/include/iot_network_mbedtls.h | 0 .../common}/include/iot_network_metrics.h | 0 .../common}/include/iot_network_openssl.h | 0 .../common/src}/iot_network_mbedtls.c | 2 +- .../common/src}/iot_network_metrics.c | 0 .../macos/include}/iot_platform_types_macos.h | 0 ports/macos/macos.cmake | 24 ++++ .../macos/src}/iot_clock_macos.c | 0 .../macos/src}/iot_threads_macos.c | 4 +- .../posix/include}/iot_platform_types_posix.h | 0 .../CMakeLists.txt => ports/posix/posix.cmake | 37 ++--- .../posix/src}/iot_clock_posix.c | 0 .../posix/src}/iot_network_openssl.c | 2 +- .../posix/src}/iot_threads_posix.c | 2 +- .../include}/iot_platform_types_template.h | 0 .../template/src}/iot_clock_template.c | 0 .../template/src}/iot_threads_template.c | 0 ports/template/template.cmake | 28 ++++ .../win32/include}/iot_platform_types_win32.h | 0 .../win32/src}/iot_clock_win32.c | 0 .../win32/src}/iot_threads_win32.c | 2 +- ports/win32/win32.cmake | 17 +++ scripts/ci_test_mqtt.sh | 72 ++++++---- scripts/setup/ci_setup_osx.sh | 6 - scripts/setup/ci_setup_windows.sh | 4 +- tests/README.md | 8 +- tests/common/CMakeLists.txt | 23 ---- tests/defender/CMakeLists.txt | 21 --- tests/iot_tests.c | 2 +- tests/jobs/CMakeLists.txt | 30 ---- tests/mqtt/CMakeLists.txt | 36 ----- tests/serializer/CMakeLists.txt | 21 --- tests/shadow/CMakeLists.txt | 34 ----- third_party/mbedtls/CMakeLists.txt | 11 +- third_party/mbedtls/iot_config_mbedtls.h | 3 - third_party/tinycbor/CMakeLists.txt | 4 +- third_party/unity/CMakeLists.txt | 6 +- 166 files changed, 938 insertions(+), 838 deletions(-) create mode 100644 demos/CMakeLists.txt delete mode 100644 demos/app/CMakeLists.txt rename demos/{source => src}/aws_iot_demo_defender.c (100%) rename demos/{source => src}/aws_iot_demo_shadow.c (100%) rename demos/{source => src}/iot_demo_mqtt.c (100%) delete mode 100644 lib/source/common/CMakeLists.txt delete mode 100644 lib/source/defender/CMakeLists.txt delete mode 100644 lib/source/jobs/CMakeLists.txt delete mode 100644 lib/source/mqtt/CMakeLists.txt delete mode 100644 lib/source/serializer/CMakeLists.txt delete mode 100644 lib/source/shadow/CMakeLists.txt create mode 100644 libraries/aws/common/CMakeLists.txt rename {lib/include/private => libraries/aws/common/include}/aws_iot.h (100%) rename {lib/source/common/aws_iot => libraries/aws/common/src}/aws_iot_operation.c (98%) rename {lib/source/common/aws_iot => libraries/aws/common/src}/aws_iot_parser.c (99%) rename {lib/source/common/aws_iot => libraries/aws/common/src}/aws_iot_subscription.c (99%) rename {lib/source/common/aws_iot => libraries/aws/common/src}/aws_iot_validate.c (97%) create mode 100644 libraries/aws/defender/CMakeLists.txt rename {lib => libraries/aws/defender}/include/aws_iot_defender.h (100%) rename {lib/source/defender => libraries/aws/defender/src}/aws_iot_defender_api.c (100%) rename {lib/source/defender => libraries/aws/defender/src}/aws_iot_defender_collector.c (100%) rename {lib/source/defender => libraries/aws/defender/src}/aws_iot_defender_mqtt.c (100%) rename {lib/include => libraries/aws/defender/src}/private/aws_iot_defender_internal.h (99%) rename {tests/defender => libraries/aws/defender/test}/aws_iot_tests_defender.c (89%) rename {tests/defender => libraries/aws/defender/test}/system/aws_iot_tests_defender_system.c (100%) create mode 100644 libraries/aws/jobs/CMakeLists.txt rename {lib => libraries/aws/jobs}/include/aws_iot_jobs.h (100%) rename {lib => libraries/aws/jobs}/include/types/aws_iot_jobs_types.h (100%) rename {lib/source/jobs => libraries/aws/jobs/src}/aws_iot_jobs_api.c (99%) rename {lib/source/jobs => libraries/aws/jobs/src}/aws_iot_jobs_operation.c (99%) rename {lib/source/jobs => libraries/aws/jobs/src}/aws_iot_jobs_serialize.c (99%) rename {lib/source/jobs => libraries/aws/jobs/src}/aws_iot_jobs_static_memory.c (99%) rename {lib/source/jobs => libraries/aws/jobs/src}/aws_iot_jobs_subscription.c (99%) rename {lib/include => libraries/aws/jobs/src}/private/aws_iot_jobs_internal.h (99%) rename {tests/jobs => libraries/aws/jobs/test}/aws_iot_tests_jobs.c (89%) rename {tests/jobs => libraries/aws/jobs/test}/system/aws_iot_tests_jobs_system.c (100%) rename {tests/jobs => libraries/aws/jobs/test}/unit/aws_iot_tests_jobs_api.c (100%) rename {tests/jobs => libraries/aws/jobs/test}/unit/aws_iot_tests_jobs_serialize.c (100%) create mode 100644 libraries/aws/shadow/CMakeLists.txt rename {lib => libraries/aws/shadow}/include/aws_iot_shadow.h (100%) rename {lib => libraries/aws/shadow}/include/types/aws_iot_shadow_types.h (100%) rename {lib/source/shadow => libraries/aws/shadow/src}/aws_iot_shadow_api.c (99%) rename {lib/source/shadow => libraries/aws/shadow/src}/aws_iot_shadow_operation.c (99%) rename {lib/source/shadow => libraries/aws/shadow/src}/aws_iot_shadow_parser.c (99%) rename {lib/source/shadow => libraries/aws/shadow/src}/aws_iot_shadow_static_memory.c (99%) rename {lib/source/shadow => libraries/aws/shadow/src}/aws_iot_shadow_subscription.c (99%) rename {lib/include => libraries/aws/shadow/src}/private/aws_iot_shadow_internal.h (99%) rename {tests/shadow => libraries/aws/shadow/test}/aws_iot_tests_shadow.c (89%) rename {tests/shadow => libraries/aws/shadow/test}/system/aws_iot_tests_shadow_system.c (100%) rename {tests/shadow => libraries/aws/shadow/test}/unit/aws_iot_tests_shadow_api.c (100%) rename {tests/shadow => libraries/aws/shadow/test}/unit/aws_iot_tests_shadow_parser.c (100%) rename {lib/include => libraries}/platform/iot_clock.h (100%) rename {lib/include => libraries}/platform/iot_metrics.h (100%) rename {lib/include => libraries}/platform/iot_network.h (100%) rename {lib/include => libraries}/platform/iot_threads.h (100%) rename {lib/include => libraries/platform}/types/iot_platform_types.h (100%) create mode 100644 libraries/standard/common/CMakeLists.txt rename {lib/include/private => libraries/standard/common/include}/iot_error.h (100%) rename {lib => libraries/standard/common}/include/iot_init.h (100%) rename {lib => libraries/standard/common}/include/iot_linear_containers.h (100%) rename {lib/include/private => libraries/standard/common/include}/iot_logging.h (100%) rename {lib => libraries/standard/common}/include/iot_logging_setup.h (99%) rename {lib/include/private => libraries/standard/common/include}/iot_static_memory.h (100%) rename {lib => libraries/standard/common}/include/iot_taskpool.h (100%) rename {lib => libraries/standard/common}/include/types/iot_taskpool_types.h (100%) rename {lib/source/common => libraries/standard/common/src}/iot_init.c (98%) rename {lib/source/common => libraries/standard/common/src}/iot_logging.c (99%) rename {lib/source/common => libraries/standard/common/src}/iot_static_memory_common.c (99%) rename {lib/source/common => libraries/standard/common/src}/iot_taskpool.c (100%) rename {lib/source/common => libraries/standard/common/src}/iot_taskpool_static_memory.c (99%) rename {lib/include => libraries/standard/common/src}/private/iot_taskpool_internal.h (99%) rename {tests/common => libraries/standard/common/test}/iot_tests_common.c (89%) rename {tests/common => libraries/standard/common/test}/unit/iot_tests_atomic.c (100%) rename {tests/common => libraries/standard/common/test}/unit/iot_tests_linear_containers.c (100%) rename {tests/common => libraries/standard/common/test}/unit/iot_tests_taskpool.c (100%) create mode 100644 libraries/standard/mqtt/CMakeLists.txt rename {lib => libraries/standard/mqtt}/include/iot_mqtt.h (100%) rename {lib => libraries/standard/mqtt}/include/types/iot_mqtt_types.h (100%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_api.c (99%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_network.c (99%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_operation.c (99%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_serialize.c (99%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_static_memory.c (99%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_subscription.c (99%) rename {lib/source/mqtt => libraries/standard/mqtt/src}/iot_mqtt_validate.c (99%) rename {lib/include => libraries/standard/mqtt/src}/private/iot_mqtt_internal.h (99%) rename {tests/mqtt => libraries/standard/mqtt/test}/access/iot_test_access_mqtt.h (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/access/iot_test_access_mqtt_api.c (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/access/iot_test_access_mqtt_subscription.c (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/iot_tests_mqtt.c (89%) rename {tests/mqtt => libraries/standard/mqtt/test}/mock/iot_tests_mqtt_mock.c (99%) rename {tests/mqtt => libraries/standard/mqtt/test}/mock/iot_tests_mqtt_mock.h (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/system/iot_tests_mqtt_system.c (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/unit/iot_tests_mqtt_api.c (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/unit/iot_tests_mqtt_receive.c (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/unit/iot_tests_mqtt_subscription.c (100%) rename {tests/mqtt => libraries/standard/mqtt/test}/unit/iot_tests_mqtt_validate.c (100%) create mode 100644 libraries/standard/serializer/CMakeLists.txt rename {lib => libraries/standard/serializer}/include/iot_json_utils.h (100%) rename {lib => libraries/standard/serializer}/include/iot_serializer.h (99%) rename {lib/source/serializer => libraries/standard/serializer/src}/cbor/iot_serializer_tinycbor_decoder.c (100%) rename {lib/source/serializer => libraries/standard/serializer/src}/cbor/iot_serializer_tinycbor_encoder.c (100%) rename {lib/source/serializer => libraries/standard/serializer/src}/iot_json_utils.c (100%) rename {lib/source/serializer => libraries/standard/serializer/src}/iot_serializer_static_memory.c (99%) rename {tests/serializer => libraries/standard/serializer/test}/iot_tests_serializer.c (88%) rename {tests/serializer => libraries/standard/serializer/test}/unit/iot_tests_serializer_cbor.c (100%) delete mode 100644 platform/README.md delete mode 100644 platform/ports/macos/CMakeLists.txt delete mode 100644 platform/ports/template/CMakeLists.txt delete mode 100644 platform/ports/win32/CMakeLists.txt create mode 100644 ports/README.md rename {platform => ports/common}/include/atomic/iot_atomic_gcc.h (100%) rename {platform => ports/common}/include/atomic/iot_atomic_generic.h (100%) rename {platform => ports/common}/include/iot_atomic.h (100%) rename {platform => ports/common}/include/iot_network_mbedtls.h (100%) rename {platform => ports/common}/include/iot_network_metrics.h (100%) rename {platform => ports/common}/include/iot_network_openssl.h (100%) rename {platform/network => ports/common/src}/iot_network_mbedtls.c (99%) rename {platform/network => ports/common/src}/iot_network_metrics.c (100%) rename {platform/ports/macos/types => ports/macos/include}/iot_platform_types_macos.h (100%) create mode 100644 ports/macos/macos.cmake rename {platform/ports/macos/source => ports/macos/src}/iot_clock_macos.c (100%) rename {platform/ports/macos/source => ports/macos/src}/iot_threads_macos.c (99%) rename {platform/ports/posix/types => ports/posix/include}/iot_platform_types_posix.h (100%) rename platform/ports/posix/CMakeLists.txt => ports/posix/posix.cmake (64%) rename {platform/ports/posix/source => ports/posix/src}/iot_clock_posix.c (100%) rename {platform/ports/posix/source => ports/posix/src}/iot_network_openssl.c (99%) rename {platform/ports/posix/source => ports/posix/src}/iot_threads_posix.c (99%) rename {platform/ports/template/types => ports/template/include}/iot_platform_types_template.h (100%) rename {platform/ports/template/source => ports/template/src}/iot_clock_template.c (100%) rename {platform/ports/template/source => ports/template/src}/iot_threads_template.c (100%) create mode 100644 ports/template/template.cmake rename {platform/ports/win32/types => ports/win32/include}/iot_platform_types_win32.h (100%) rename {platform/ports/win32/source => ports/win32/src}/iot_clock_win32.c (100%) rename {platform/ports/win32/source => ports/win32/src}/iot_threads_win32.c (99%) create mode 100644 ports/win32/win32.cmake delete mode 100644 tests/common/CMakeLists.txt delete mode 100644 tests/defender/CMakeLists.txt delete mode 100644 tests/jobs/CMakeLists.txt delete mode 100644 tests/mqtt/CMakeLists.txt delete mode 100644 tests/serializer/CMakeLists.txt delete mode 100644 tests/shadow/CMakeLists.txt diff --git a/.travis.yml b/.travis.yml index 909703a7ec..1ee1b7f7fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,6 @@ jobs: env: RUN_TEST=doc - os: osx env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - # Cache Homebrew for faster macOS builds. - cache: - directories: - - $HOME/Library/Caches/Homebrew - - /usr/local/Homebrew - os: windows env: RUN_TEST=mqtt NETWORK_STACK=mbedtls compiler: msvc diff --git a/CMakeLists.txt b/CMakeLists.txt index 5078066708..e51cf551ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,117 +65,55 @@ if( NOT DEFINED IOT_PLATFORM_NAME ) endif() endif() -if( ${IOT_NETWORK_USE_OPENSSL} ) - add_definitions( -DIOT_NETWORK_USE_OPENSSL=1 ) -endif() - # Validate the platform name. if( NOT DEFINED IOT_PLATFORM_NAME ) message( FATAL_ERROR "IOT_PLATFORM_NAME was not set and could not be automatically determined." ) endif() -if( NOT EXISTS ${PROJECT_SOURCE_DIR}/platform/ports/${IOT_PLATFORM_NAME} ) - message( FATAL_ERROR "A port for ${IOT_PLATFORM_NAME} does not exist in platform/ports." ) +if( NOT EXISTS ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME} ) + message( FATAL_ERROR "A port for ${IOT_PLATFORM_NAME} does not exist." ) endif() # Set output directories. set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) -# Platform types file. -include_directories( ${PROJECT_SOURCE_DIR}/platform/ports/${IOT_PLATFORM_NAME}/types ) -add_definitions( -DIOT_SYSTEM_TYPES_FILE="iot_platform_types_${IOT_PLATFORM_NAME}.h" ) - -# SDK include paths. -include_directories( ${PROJECT_SOURCE_DIR}/lib/include - ${PROJECT_SOURCE_DIR}/platform/include ) - -# Demo include path. Always required because the tests also build the demos. -include_directories( ${PROJECT_SOURCE_DIR}/demos/include ) - -# Set additional include paths for demos and tests. +# Set the path to the config header. if( ${IOT_BUILD_TESTS} ) - # Define the constant to enable test access. - add_definitions( -DIOT_BUILD_TESTS=1 ) - - # Test include paths. - include_directories( ${PROJECT_SOURCE_DIR}/tests - tests/mqtt/access ) - - # Build unity test framework. - add_subdirectory( third_party/unity ) + set( CONFIG_HEADER_PATH ${PROJECT_SOURCE_DIR}/tests ) + set( IOT_TEST_APP_SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) else() - include_directories( ${PROJECT_SOURCE_DIR}/demos ) -endif() - -# Use a custom atomic port if the CMake option is set. -if( ${IOT_ATOMIC_USE_PORT} ) - add_definitions( -DIOT_ATOMIC_USE_PORT=1 ) + set( CONFIG_HEADER_PATH ${PROJECT_SOURCE_DIR}/demos ) endif() -# Platform headers. -set( PLATFORM_INTERFACE_HEADERS - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_clock.h - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_metrics.h - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_network.h - ${CMAKE_SOURCE_DIR}/lib/include/platform/iot_threads.h ) - -set( PLATFORM_TYPES_HEADERS - ${CMAKE_SOURCE_DIR}/lib/include/types/iot_platform_types.h - ${CMAKE_SOURCE_DIR}/platform/ports/${IOT_PLATFORM_NAME}/types/iot_platform_types_${IOT_PLATFORM_NAME}.h ) - -set( PLATFORM_COMMON_HEADERS - ${CMAKE_SOURCE_DIR}/platform/include/iot_atomic.h - ${CMAKE_SOURCE_DIR}/platform/include/iot_network_metrics.h ) - -# Platform libraries. -add_subdirectory( platform/ports/${IOT_PLATFORM_NAME} ) - -# Common libraries (linear containers, logging, etc.) -add_subdirectory( lib/source/common ) - -# The sources of the common libraries and platform layer will be built into a -# single library that is used as a dependency by other libraries. -add_library( iotbase - ${PLATFORM_INTERFACE_HEADERS} - ${PLATFORM_TYPES_HEADERS} - ${PLATFORM_COMMON_HEADERS} - ${COMMON_PUBLIC_HEADERS} - ${COMMON_PRIVATE_HEADERS} - ${COMMON_TYPES_HEADERS} ) - -# Link required libraries. -target_link_libraries( iotbase PRIVATE iotplatform iotcommon ) - +# Build Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotbase PRIVATE unity ) + add_subdirectory( third_party/unity ) endif() -# Organization of iotbase in folders. -source_group( platform\\interface FILES ${PLATFORM_INTERFACE_HEADERS} ) -source_group( platform\\include FILES ${PLATFORM_COMMON_HEADERS} ) -source_group( platform\\include\\types FILES ${PLATFORM_TYPES_HEADERS} ) -source_group( platform\\source FILES ${PLATFORM_SOURCES} ) -source_group( common\\include FILES ${COMMON_PUBLIC_HEADERS} ) -source_group( common\\include\\private FILES ${COMMON_PRIVATE_HEADERS} ) -source_group( common\\include\\types FILES ${COMMON_TYPES_HEADERS} ) -source_group( common\\source\\aws_iot FILES ${AWS_IOT_COMMON_SOURCES} ) -source_group( common\\source FILES ${COMMON_SOURCES} ) +# Build the demos. +add_subdirectory( demos ) -# MQTT library. -add_subdirectory( lib/source/mqtt ) +# Common libraries and platform port. This creates the iotbase library target. +add_subdirectory( libraries/standard/common ) -# Shadow library. -add_subdirectory( lib/source/shadow ) +# MQTT library. +add_subdirectory( libraries/standard/mqtt ) # Serializer library. -add_subdirectory( lib/source/serializer ) +add_subdirectory( libraries/standard/serializer ) -# Defender library. -add_subdirectory( lib/source/defender ) +# AWS IoT common libraries. +add_subdirectory( libraries/aws/common ) -# Jobs library. -add_subdirectory( lib/source/jobs ) +# AWS IoT Shadow library. +add_subdirectory( libraries/aws/shadow ) + +# AWS IoT Jobs library. +add_subdirectory( libraries/aws/jobs ) + +# AWS IoT Device Defender library. +add_subdirectory( libraries/aws/defender ) # TinyCBOR library (third-party). add_subdirectory( third_party/tinycbor ) @@ -183,14 +121,6 @@ add_subdirectory( third_party/tinycbor ) # mbed TLS library (third-party). add_subdirectory( third_party/mbedtls ) -# Build demo executable. -add_subdirectory( demos/app ) - -# Test executables. -if( ${IOT_BUILD_TESTS} ) - add_subdirectory( tests/ ) -endif() - # Set startup projects in Visual Studio. if( ${IOT_BUILD_TESTS} ) set_property( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT iot_tests_mqtt ) diff --git a/README.md b/README.md index 99531ea2e9..0d2262b4e3 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ See the documentation page [Building the SDK](https://docs.aws.amazon.com/freert Please refer to the [Porting Guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions on porting this SDK. -Existing ports (which may be used as examples) are present in `platform/ports`. A blank template for implementing new ports is in `platform/ports/template`. +Existing ports (which may be used as examples) are present in `ports`. A blank template for implementing new ports is in `ports/template`. ## License diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt new file mode 100644 index 0000000000..4e849cae8b --- /dev/null +++ b/demos/CMakeLists.txt @@ -0,0 +1,92 @@ +# Common application source files. +set( DEMO_APP_SOURCES + app/iot_demo.c + app/iot_demo_arguments.c ) + +# When testing the demos, add the test sources and redefine the demo main function. +if( ${IOT_BUILD_TESTS} ) + list( APPEND DEMO_APP_SOURCES ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) + + set_property( SOURCE app/iot_demo.c PROPERTY COMPILE_DEFINITIONS main=DemoMain ) + set_property( SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c PROPERTY COMPILE_DEFINITIONS IOT_TEST_DEMO=1 ) + + set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) +else() + set( CONFIG_HEADER iot_config.h ) +endif() + +# Common demo header files. +set( DEMO_COMMON_HEADERS + include/iot_demo_arguments.h + include/iot_demo_logging.h ) + +# List all the demo functions, source files, and library dependencies here. +# +# The list order matters: Each demo must be at the same index in each list. +# For example, if the MQTT demo function is at DEMO_MAIN_FUNCTIONS[0], then +# the MQTT demo source must be at DEMO_SOURCES[0] and the MQTT library must +# be at DEMO_LIBRARY_DEPENDENCY[0]. +# +# If a demo has multiple dependencies, place a semicolon-separated list in +# DEMO_LIBRARY_DEPENDENCY. The semicolon itself must be escaped as \\\; +set( DEMO_MAIN_FUNCTIONS + RunMqttDemo + RunShadowDemo + RunDefenderDemo ) +set( DEMO_SOURCES + src/iot_demo_mqtt.c + src/aws_iot_demo_shadow.c + src/aws_iot_demo_defender.c ) +set( DEMO_LIBRARY_DEPENDENCY + iotmqtt + "awsiotshadow\\\;iotserializer" + awsiotdefender ) + +# Get the list length to iterate over it. Since CMake indexes from 0, subtract +# 1 for the iteration range. +list( LENGTH DEMO_MAIN_FUNCTIONS DEMO_COUNT ) +math( EXPR DEMO_COUNT "${DEMO_COUNT}-1" ) + +# Go through the list of demos and create an executable for each one. +foreach( i RANGE ${DEMO_COUNT} ) + # Read the demo function, source, and dependency from each list. + list( GET DEMO_MAIN_FUNCTIONS ${i} CURRENT_DEMO_FUNCTION ) + list( GET DEMO_SOURCES ${i} CURRENT_DEMO_SOURCE ) + list( GET DEMO_LIBRARY_DEPENDENCY ${i} CURRENT_DEMO_LIBRARY ) + + # Get the name of the demo executable. This is the name of the demo source + # without the .c file extension. + get_filename_component( DEMO_EXE_NAME ${CURRENT_DEMO_SOURCE} NAME_WE ) + + # Add the demo executable. + add_executable( ${DEMO_EXE_NAME} + ${CURRENT_DEMO_SOURCE} + ${DEMO_APP_SOURCES} + ${DEMO_COMMON_HEADERS} + ${CONFIG_HEADER} ) + + # Set the demo function to run. + target_compile_definitions( ${DEMO_EXE_NAME} + PRIVATE RunDemo=${CURRENT_DEMO_FUNCTION} ) + + # Add the include path for the demo application. + target_include_directories( ${DEMO_EXE_NAME} PRIVATE include ) + + # Link iotbase. All demos require this. + target_link_libraries( ${DEMO_EXE_NAME} PRIVATE iotbase ) + + # Link the unique dependency of each demo. + target_link_libraries( ${DEMO_EXE_NAME} PRIVATE ${CURRENT_DEMO_LIBRARY} ) + + # When building tests, link the Unity test framework. + if( ${IOT_BUILD_TESTS} ) + target_link_libraries( ${DEMO_EXE_NAME} PRIVATE unity ) + endif() + + # Organize the demo into folders. + set_property( TARGET ${DEMO_EXE_NAME} PROPERTY FOLDER demos ) + source_group( app FILES ${DEMO_APP_SOURCES} ) + source_group( include FILES ${DEMO_COMMON_HEADERS} ) + source_group( src FILES ${CURRENT_DEMO_SOURCE} ) + source_group( "" FILES ${CONFIG_HEADER} ) +endforeach() diff --git a/demos/README.md b/demos/README.md index ef0ae7351a..8181eff5f9 100644 --- a/demos/README.md +++ b/demos/README.md @@ -5,7 +5,7 @@ This directory contains source files for demo executables. Its subdirectories ar Source files for demo runner executables (i.e. the `main()` function). - `include`
Common headers for the demo executables. These headers handle argument parsing and logging which are common to all demos. -- `source`
+- `src`
Platform-independent demo sources. The files in this directory contain the majority of the demo code. The demo runner in `app` calls demo functions implemented in this directory. The configuration file for the demos, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the demos and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all demos and libraries; see each library's documentation for library-specific settings. diff --git a/demos/app/CMakeLists.txt b/demos/app/CMakeLists.txt deleted file mode 100644 index fbdde72465..0000000000 --- a/demos/app/CMakeLists.txt +++ /dev/null @@ -1,94 +0,0 @@ -# Common demo source files. -set( DEMO_COMMON_SOURCES - iot_demo.c - iot_demo_arguments.c ) - -# When testing the demos, add the test sources and redefine the demo main function. -if( ${IOT_BUILD_TESTS} ) - list( APPEND DEMO_COMMON_SOURCES ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) - set_property( SOURCE iot_demo.c PROPERTY COMPILE_DEFINITIONS main=DemoMain ) - set_property( SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c PROPERTY COMPILE_DEFINITIONS IOT_TEST_DEMO=1 ) - set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) -else() - set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/demos/iot_config.h ) -endif() - -# Common demo header files. -set( DEMO_COMMON_HEADERS - ${PROJECT_SOURCE_DIR}/demos/include/iot_demo_arguments.h - ${PROJECT_SOURCE_DIR}/demos/include/iot_demo_logging.h ) - -# MQTT demo source files. -add_executable( iot_demo_mqtt - ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c - ${DEMO_COMMON_SOURCES} - ${DEMO_COMMON_HEADERS} - ${CONFIG_HEADER} ) - -# Select the demo function for the MQTT demo. -target_compile_definitions( iot_demo_mqtt - PRIVATE RunDemo=RunMqttDemo ) - -# MQTT demo library dependencies. -target_link_libraries( iot_demo_mqtt PRIVATE iotmqtt ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iot_demo_mqtt PRIVATE unity ) -endif() - -# Organization of MQTT demo in folders. -set_property( TARGET iot_demo_mqtt PROPERTY FOLDER "demos" ) -source_group( include FILES ${DEMO_COMMON_HEADERS} ) -source_group( "" FILES ${DEMO_COMMON_SOURCES} - ${PROJECT_SOURCE_DIR}/demos/source/iot_demo_mqtt.c - ${CONFIG_HEADER} ) - -# Shadow demo source files. -add_executable( aws_iot_demo_shadow - ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c - ${DEMO_COMMON_SOURCES} - ${DEMO_COMMON_HEADERS} - ${CONFIG_HEADER} ) - -# Select the demo function for the Shadow demo. -target_compile_definitions( aws_iot_demo_shadow - PRIVATE RunDemo=RunShadowDemo ) - -# Shadow demo library dependencies. -target_link_libraries( aws_iot_demo_shadow PRIVATE awsiotshadow ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( aws_iot_demo_shadow PRIVATE unity ) -endif() - -# Organization of Shadow demo in folders. -set_property( TARGET aws_iot_demo_shadow PROPERTY FOLDER "demos" ) -source_group( include FILES ${DEMO_COMMON_HEADERS} ) -source_group( "" FILES ${DEMO_COMMON_SOURCES} - ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_shadow.c - ${CONFIG_HEADER} ) - -# Defender demo source files. -add_executable( aws_iot_demo_defender - ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_defender.c - ${DEMO_COMMON_SOURCES} - ${DEMO_COMMON_HEADERS} - ${CONFIG_HEADER} ) - -# Select the demo function for the Defender demo. -target_compile_definitions( aws_iot_demo_defender - PRIVATE RunDemo=RunDefenderDemo ) - -# Defender demo library dependencies. -target_link_libraries( aws_iot_demo_defender PRIVATE awsiotdefender ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( aws_iot_demo_defender PRIVATE unity ) -endif() - -# Organization of the Defender demo in folders. -set_property( TARGET aws_iot_demo_defender PROPERTY FOLDER "demos" ) -source_group( include FILES ${DEMO_COMMON_HEADERS} ) -source_group( "" FILES ${DEMO_COMMON_SOURCES} - ${PROJECT_SOURCE_DIR}/demos/source/aws_iot_demo_defender.c - ${CONFIG_HEADER} ) diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 1d485db17f..09e31ff795 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -36,7 +36,7 @@ #include "iot_init.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Common demo includes. */ #include "iot_demo_arguments.h" diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index dc24a3d847..d97974a58e 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -32,7 +32,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Common demo includes. */ #include "iot_demo_arguments.h" diff --git a/demos/source/aws_iot_demo_defender.c b/demos/src/aws_iot_demo_defender.c similarity index 100% rename from demos/source/aws_iot_demo_defender.c rename to demos/src/aws_iot_demo_defender.c diff --git a/demos/source/aws_iot_demo_shadow.c b/demos/src/aws_iot_demo_shadow.c similarity index 100% rename from demos/source/aws_iot_demo_shadow.c rename to demos/src/aws_iot_demo_shadow.c diff --git a/demos/source/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c similarity index 100% rename from demos/source/iot_demo_mqtt.c rename to demos/src/iot_demo_mqtt.c diff --git a/doc/config/defender b/doc/config/defender index 11b787c92a..b88a0ea76f 100644 --- a/doc/config/defender +++ b/doc/config/defender @@ -14,9 +14,11 @@ GENERATE_TAGFILE = doc/tag/defender.tag # Directories containing library source code. INPUT = doc/lib/ \ - lib/include \ - lib/include/private \ - lib/source/defender + libraries/aws/defender/include \ + libraries/aws/defender/src \ + libraries/aws/defender/src/private \ + libraries/aws/defender/test \ + libraries/aws/defender/test/system # Library file names. FILE_PATTERNS = *defender*.h *defender*.txt diff --git a/doc/config/jobs b/doc/config/jobs index c2dd27affa..c86bbb0e56 100644 --- a/doc/config/jobs +++ b/doc/config/jobs @@ -14,12 +14,12 @@ GENERATE_TAGFILE = doc/tag/jobs.tag # Directories containing library source code. INPUT = doc/lib/ \ - lib/include \ - lib/include/private \ - lib/include/types \ - lib/source/jobs \ - demos \ - tests/jobs/unit + libraries/aws/jobs/include \ + libraries/aws/jobs/include/types \ + libraries/aws/jobs/src \ + libraries/aws/jobs/src/private \ + libraries/aws/jobs/test \ + demos/src # Library file names. FILE_PATTERNS = *jobs*.h *jobs*.c *jobs*.txt diff --git a/doc/config/linear_containers b/doc/config/linear_containers index ca333fe113..69de8f6f03 100644 --- a/doc/config/linear_containers +++ b/doc/config/linear_containers @@ -14,7 +14,7 @@ GENERATE_TAGFILE = doc/tag/linear_containers.tag # Directories containing library source code. INPUT = doc/lib \ - lib/include/ + libraries/standard/common/include/ # Library file names. FILE_PATTERNS = *linear_containers*.h *linear_containers*.txt diff --git a/doc/config/logging b/doc/config/logging index c8b301ef79..dc8fa54fe7 100644 --- a/doc/config/logging +++ b/doc/config/logging @@ -14,9 +14,8 @@ GENERATE_TAGFILE = doc/tag/logging.tag # Directories containing library source code. INPUT = doc/lib \ - lib/include \ - lib/include/private \ - lib/source/common + libraries/standard/common/include \ + libraries/standard/common/src # Library file names. FILE_PATTERNS = *logging*.c *logging*.h *logging*.txt diff --git a/doc/config/mqtt b/doc/config/mqtt index a16e311eb5..504700b415 100644 --- a/doc/config/mqtt +++ b/doc/config/mqtt @@ -17,14 +17,15 @@ GENERATE_TAGFILE = doc/tag/mqtt.tag # Directories containing library source code. INPUT = doc/lib \ - lib/include \ - lib/include/types \ - lib/include/private \ - lib/source/mqtt \ - demos/ \ - tests/mqtt/unit \ - tests/mqtt/system \ - tests/mqtt/access + libraries/standard/mqtt/include \ + libraries/standard/mqtt/include/types \ + libraries/standard/mqtt/src \ + libraries/standard/mqtt/src/private \ + libraries/standard/mqtt/test \ + libraries/standard/mqtt/test/access \ + libraries/standard/mqtt/test/system \ + libraries/standard/mqtt/test/unit \ + demos/src # Library file names. FILE_PATTERNS = *mqtt*.c *mqtt*.h *mqtt*.txt diff --git a/doc/config/platform b/doc/config/platform index db9ddb67e2..a73537652c 100644 --- a/doc/config/platform +++ b/doc/config/platform @@ -14,9 +14,9 @@ GENERATE_TAGFILE = doc/tag/platform.tag # Directories containing library source code. INPUT = doc/lib \ - lib/include/platform \ - lib/include/types \ - platform/include/atomic + libraries/platform \ + libraries/platform/types \ + ports/common/include/atomic # Library file names. FILE_PATTERNS = platform.txt \ diff --git a/doc/config/shadow b/doc/config/shadow index cf76705a93..8ceff85479 100644 --- a/doc/config/shadow +++ b/doc/config/shadow @@ -14,13 +14,14 @@ GENERATE_TAGFILE = doc/tag/shadow.tag # Directories containing library source code. INPUT = doc/lib/ \ - lib/include \ - lib/include/private \ - lib/include/types \ - lib/source/shadow \ - demos \ - tests/shadow/unit \ - tests/shadow/system + libraries/aws/shadow/include \ + libraries/aws/shadow/include/types \ + libraries/aws/shadow/src \ + libraries/aws/shadow/src/private \ + libraries/aws/shadow/test \ + libraries/aws/shadow/test/system \ + libraries/aws/shadow/test/unit \ + demos/src # Library file names. FILE_PATTERNS = *shadow*.h *shadow*.c *shadow*.txt diff --git a/doc/config/static_memory b/doc/config/static_memory index b29bebdb17..6e666e1024 100644 --- a/doc/config/static_memory +++ b/doc/config/static_memory @@ -14,8 +14,8 @@ GENERATE_TAGFILE = doc/tag/static_memory.tag # Directories containing library source code. INPUT = doc/lib \ - lib/include/private \ - lib/source/common + libraries/standard/common/include \ + libraries/standard/common/src # Library file names. FILE_PATTERNS = *static_memory*.h *static_memory_common*.c *static_memory*.txt diff --git a/doc/config/taskpool b/doc/config/taskpool index 5f621d1260..36ec9b4071 100644 --- a/doc/config/taskpool +++ b/doc/config/taskpool @@ -15,11 +15,12 @@ GENERATE_TAGFILE = doc/tag/taskpool.tag # Directories containing library source code. INPUT = doc \ doc/lib \ - lib/include \ - lib/include/private \ - lib/include/types \ - lib/source/common \ - tests/common/unit + libraries/standard/common/include \ + libraries/standard/common/include/types \ + libraries/standard/common/src \ + libraries/standard/common/src/private \ + libraries/standard/common/test \ + libraries/standard/common/test/unit # Library file names. FILE_PATTERNS = *taskpool*.c *taskpool*.h *taskpool*.txt diff --git a/lib/source/common/CMakeLists.txt b/lib/source/common/CMakeLists.txt deleted file mode 100644 index a85a2b2592..0000000000 --- a/lib/source/common/CMakeLists.txt +++ /dev/null @@ -1,60 +0,0 @@ -# Common libraries source files. -set( COMMON_SOURCES - ${PROJECT_SOURCE_DIR}/lib/source/common/iot_init.c - ${PROJECT_SOURCE_DIR}/lib/source/common/iot_logging.c - ${PROJECT_SOURCE_DIR}/lib/source/common/iot_static_memory_common.c - ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool.c - ${PROJECT_SOURCE_DIR}/lib/source/common/iot_taskpool_static_memory.c ) - -# Lists of common header files. These are only used for directory organization -# (not for build). -set( COMMON_PUBLIC_HEADERS - ${PROJECT_SOURCE_DIR}/lib/include/iot_init.h - ${PROJECT_SOURCE_DIR}/lib/include/iot_linear_containers.h - ${PROJECT_SOURCE_DIR}/lib/include/iot_logging_setup.h - ${PROJECT_SOURCE_DIR}/lib/include/iot_taskpool.h - PARENT_SCOPE ) -set( COMMON_PRIVATE_HEADERS - ${PROJECT_SOURCE_DIR}/lib/include/private/iot_error.h - ${PROJECT_SOURCE_DIR}/lib/include/private/iot_logging.h - ${PROJECT_SOURCE_DIR}/lib/include/private/iot_static_memory.h - ${PROJECT_SOURCE_DIR}/lib/include/private/iot_taskpool_internal.h - PARENT_SCOPE ) -set( COMMON_TYPES_HEADERS - ${PROJECT_SOURCE_DIR}/lib/include/types/iot_taskpool_types.h - PARENT_SCOPE ) - -# Add common as an interface library. It will be built into iotbase. -add_library( iotcommon INTERFACE ) - -target_sources( iotcommon INTERFACE - ${COMMON_SOURCES} ) - -# Set common sources in the parent scope for directory organization. -set( COMMON_SOURCES ${COMMON_SOURCES} PARENT_SCOPE ) -set( AWS_IOT_COMMON_SOURCES ${AWS_IOT_COMMON_SOURCES} PARENT_SCOPE ) - -# AWS IoT common sources. -set( AWS_IOT_COMMON_SOURCES - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_operation.c - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_parser.c - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_subscription.c - ${PROJECT_SOURCE_DIR}/lib/source/common/aws_iot/aws_iot_validate.c ) - -# AWS IoT common library target. -add_library( awsiotcommon - ${AWS_IOT_COMMON_SOURCES} - ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot.h ) - -# Link required libraries. -target_link_libraries( awsiotcommon - PUBLIC iotmqtt iotserializer ) - -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotcommon PRIVATE unity ) -endif() - -# Organization of AWS IoT common sources in folders. -set_property( TARGET awsiotcommon PROPERTY FOLDER "lib" ) -source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot.h ) -source_group( source FILES ${AWS_IOT_COMMON_SOURCES} ) diff --git a/lib/source/defender/CMakeLists.txt b/lib/source/defender/CMakeLists.txt deleted file mode 100644 index 13e323b90d..0000000000 --- a/lib/source/defender/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# Defender library source files. -set( DEFENDER_SOURCES - aws_iot_defender_api.c - aws_iot_defender_collector.c - aws_iot_defender_mqtt.c ) - -# Defender library target. -add_library( awsiotdefender - ${DEFENDER_SOURCES} - ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_defender.h - ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_defender_internal.h ) - -# Link required libraries. -target_link_libraries( awsiotdefender PUBLIC awsiotcommon ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotdefender PRIVATE unity ) -endif() - -# Organization of Defender in folders. -set_property( TARGET awsiotdefender PROPERTY FOLDER "lib" ) -source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_defender.h ) -source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_defender_internal.h ) -source_group( source FILES ${DEFENDER_SOURCES} ) diff --git a/lib/source/jobs/CMakeLists.txt b/lib/source/jobs/CMakeLists.txt deleted file mode 100644 index 930474d3a5..0000000000 --- a/lib/source/jobs/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -# Jobs library source files. -set( JOBS_SOURCES - aws_iot_jobs_api.c - aws_iot_jobs_serialize.c - aws_iot_jobs_operation.c - aws_iot_jobs_static_memory.c - aws_iot_jobs_subscription.c ) - -# Jobs library target. -add_library( awsiotjobs - ${JOBS_SOURCES} - ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_jobs.h - ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_jobs_types.h - ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_jobs_internal.h ) - -# Link required libraries. -target_link_libraries( awsiotjobs PUBLIC awsiotcommon ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotjobs PRIVATE unity ) -endif() - -# Organization of Jobs in folders. -set_property( TARGET awsiotjobs PROPERTY FOLDER "lib" ) -source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_jobs.h ) -source_group( include\\types ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_jobs_types.h ) -source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_jobs_internal.h ) -source_group( source FILES ${JOBS_SOURCES} ) diff --git a/lib/source/mqtt/CMakeLists.txt b/lib/source/mqtt/CMakeLists.txt deleted file mode 100644 index 3499583c17..0000000000 --- a/lib/source/mqtt/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -# MQTT library source files. -set( MQTT_SOURCES - iot_mqtt_api.c - iot_mqtt_network.c - iot_mqtt_operation.c - iot_mqtt_serialize.c - iot_mqtt_static_memory.c - iot_mqtt_subscription.c - iot_mqtt_validate.c ) - -# MQTT library target. -add_library( iotmqtt - ${MQTT_SOURCES} - ${PROJECT_SOURCE_DIR}/lib/include/iot_mqtt.h - ${PROJECT_SOURCE_DIR}/lib/include/types/iot_mqtt_types.h - ${PROJECT_SOURCE_DIR}/lib/include/private/iot_mqtt_internal.h ) - -# Link required libraries. -target_link_libraries( iotmqtt PUBLIC iotbase ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotmqtt PRIVATE unity ) -endif() - -# Organization of MQTT in folders. -set_property( TARGET iotmqtt PROPERTY FOLDER "lib" ) -source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/iot_mqtt.h ) -source_group( include\\types FILES ${PROJECT_SOURCE_DIR}/lib/include/types/iot_mqtt_types.h ) -source_group( include\\private FILES ${PROJECT_SOURCE_DIR}/lib/include/private/iot_mqtt_internal.h ) -source_group( source FILES ${MQTT_SOURCES} ) diff --git a/lib/source/serializer/CMakeLists.txt b/lib/source/serializer/CMakeLists.txt deleted file mode 100644 index 58a5bc608f..0000000000 --- a/lib/source/serializer/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Serializer library source files. -set( SERIALIZER_SOURCES - iot_json_utils.c - iot_serializer_static_memory.c - cbor/iot_serializer_tinycbor_decoder.c - cbor/iot_serializer_tinycbor_encoder.c ) - -# Serializer library target. -add_library( iotserializer - ${SERIALIZER_SOURCES} - ${PROJECT_SOURCE_DIR}/lib/include/iot_serializer.h - ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) - -# Link required libraries. -target_link_libraries( iotserializer PRIVATE iotbase tinycbor ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( iotserializer PRIVATE unity ) -endif() - -# Organization of Serializer in folders. -set_property( TARGET iotserializer PROPERTY FOLDER "lib" ) -source_group( include FILES - ${PROJECT_SOURCE_DIR}/lib/include/iot_serializer.h - ${PROJECT_SOURCE_DIR}/lib/include/iot_json_utils.h ) -source_group( source FILES ${SERIALIZER_SOURCES} ) diff --git a/lib/source/shadow/CMakeLists.txt b/lib/source/shadow/CMakeLists.txt deleted file mode 100644 index bfb82f99b5..0000000000 --- a/lib/source/shadow/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -# Shadow library source files. -set( SHADOW_SOURCES - aws_iot_shadow_api.c - aws_iot_shadow_operation.c - aws_iot_shadow_parser.c - aws_iot_shadow_static_memory.c - aws_iot_shadow_subscription.c ) - -# Shadow library target. -add_library( awsiotshadow - ${SHADOW_SOURCES} - ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_shadow.h - ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_shadow_types.h - ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_shadow_internal.h ) - -# Link required libraries. -target_link_libraries( awsiotshadow PUBLIC awsiotcommon ) - -# Link Unity test framework when building tests. -if( ${IOT_BUILD_TESTS} ) - target_link_libraries( awsiotshadow PRIVATE unity ) -endif() - -# Organization of Shadow in folders. -set_property( TARGET awsiotshadow PROPERTY FOLDER "lib" ) -source_group( include FILES ${PROJECT_SOURCE_DIR}/lib/include/aws_iot_shadow.h ) -source_group( include\\types ${PROJECT_SOURCE_DIR}/lib/include/types/aws_iot_shadow_types.h ) -source_group( include\\private ${PROJECT_SOURCE_DIR}/lib/include/private/aws_iot_shadow_internal.h ) -source_group( source FILES ${SHADOW_SOURCES} ) diff --git a/libraries/aws/common/CMakeLists.txt b/libraries/aws/common/CMakeLists.txt new file mode 100644 index 0000000000..41d20eb866 --- /dev/null +++ b/libraries/aws/common/CMakeLists.txt @@ -0,0 +1,29 @@ +# AWS IoT common library source files. +set( AWS_IOT_COMMON_SOURCES + src/aws_iot_operation.c + src/aws_iot_parser.c + src/aws_iot_subscription.c + src/aws_iot_validate.c ) + +# AWS IoT common library target. +add_library( awsiotcommon + ${CONFIG_HEADER_PATH}/iot_config.h + ${AWS_IOT_COMMON_SOURCES} + include/aws_iot.h ) + +# AWS IoT common public include path. +target_include_directories( awsiotcommon PUBLIC include ) + +# Link required libraries. +target_link_libraries( awsiotcommon PRIVATE iotbase iotmqtt iotserializer ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotcommon PRIVATE unity ) +endif() + +# Organization of AWS IoT common in folders. +set_property( TARGET awsiotcommon PROPERTY FOLDER libraries/aws ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( include FILES include/aws_iot.h ) +source_group( src FILES ${AWS_IOT_COMMON_SOURCES} ) diff --git a/lib/include/private/aws_iot.h b/libraries/aws/common/include/aws_iot.h similarity index 100% rename from lib/include/private/aws_iot.h rename to libraries/aws/common/include/aws_iot.h diff --git a/lib/source/common/aws_iot/aws_iot_operation.c b/libraries/aws/common/src/aws_iot_operation.c similarity index 98% rename from lib/source/common/aws_iot/aws_iot_operation.c rename to libraries/aws/common/src/aws_iot_operation.c index 279d891947..f5436a000a 100644 --- a/lib/source/common/aws_iot/aws_iot_operation.c +++ b/libraries/aws/common/src/aws_iot_operation.c @@ -35,10 +35,10 @@ #include "platform/iot_threads.h" /* AWS IoT include. */ -#include "private/aws_iot.h" +#include "aws_iot.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /*-----------------------------------------------------------*/ diff --git a/lib/source/common/aws_iot/aws_iot_parser.c b/libraries/aws/common/src/aws_iot_parser.c similarity index 99% rename from lib/source/common/aws_iot/aws_iot_parser.c rename to libraries/aws/common/src/aws_iot_parser.c index 05216b9914..7da99ee5fc 100644 --- a/lib/source/common/aws_iot/aws_iot_parser.c +++ b/libraries/aws/common/src/aws_iot_parser.c @@ -32,10 +32,10 @@ #include /* AWS IoT include. */ -#include "private/aws_iot.h" +#include "aws_iot.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* JSON utils include. */ #include "iot_json_utils.h" diff --git a/lib/source/common/aws_iot/aws_iot_subscription.c b/libraries/aws/common/src/aws_iot_subscription.c similarity index 99% rename from lib/source/common/aws_iot/aws_iot_subscription.c rename to libraries/aws/common/src/aws_iot_subscription.c index 771fa1da22..0518ebf380 100644 --- a/lib/source/common/aws_iot/aws_iot_subscription.c +++ b/libraries/aws/common/src/aws_iot_subscription.c @@ -32,10 +32,10 @@ #include /* AWS IoT include. */ -#include "private/aws_iot.h" +#include "aws_iot.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT include. */ #include "iot_mqtt.h" diff --git a/lib/source/common/aws_iot/aws_iot_validate.c b/libraries/aws/common/src/aws_iot_validate.c similarity index 97% rename from lib/source/common/aws_iot/aws_iot_validate.c rename to libraries/aws/common/src/aws_iot_validate.c index e7c87497b0..fef3320e15 100644 --- a/lib/source/common/aws_iot/aws_iot_validate.c +++ b/libraries/aws/common/src/aws_iot_validate.c @@ -29,10 +29,10 @@ #include "iot_config.h" /* AWS IoT include. */ -#include "private/aws_iot.h" +#include "aws_iot.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/CMakeLists.txt b/libraries/aws/defender/CMakeLists.txt new file mode 100644 index 0000000000..e23f0da435 --- /dev/null +++ b/libraries/aws/defender/CMakeLists.txt @@ -0,0 +1,64 @@ +# Defender library source files. +set( DEFENDER_SOURCES + src/aws_iot_defender_api.c + src/aws_iot_defender_collector.c + src/aws_iot_defender_mqtt.c ) + +# Defender library target. +add_library( awsiotdefender + ${CONFIG_HEADER_PATH}/iot_config.h + ${DEFENDER_SOURCES} + include/aws_iot_defender.h + src/private/aws_iot_defender_internal.h ) + +# Defender public include path. +target_include_directories( awsiotdefender PUBLIC include ) + +# Link required libraries. +target_link_libraries( awsiotdefender PRIVATE awsiotcommon iotserializer iotbase ) + +# Defender is currently implemented on MQTT, so link MQTT as a public dependency. +target_link_libraries( awsiotdefender PUBLIC iotmqtt ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotdefender PRIVATE unity ) +endif() + +# Organization of Defender in folders. +set_property( TARGET awsiotdefender PROPERTY FOLDER libraries/aws ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( include FILES include/aws_iot_defender.h ) +source_group( src\\private src/private/aws_iot_defender_internal.h ) +source_group( src FILES ${DEFENDER_SOURCES} ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # Defender system test sources. + set( DEFENDER_SYSTEM_TEST_SOURCES + test/system/aws_iot_tests_defender_system.c ) + + # Defender tests executable. + add_executable( aws_iot_tests_defender + ${DEFENDER_SYSTEM_TEST_SOURCES} + test/aws_iot_tests_defender.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( aws_iot_tests_defender PRIVATE + -DRunTests=RunDefenderTests ) + + # The Defender tests need the internal Defender header. + target_include_directories( aws_iot_tests_defender PRIVATE src ) + + # Defender tests library dependencies. + target_link_libraries( aws_iot_tests_defender PRIVATE + awsiotdefender awsiotcommon iotserializer iotbase + unity tinycbor iot_mqtt_mock ) + + # Organization of Defender tests in folders. + set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER tests ) + source_group( system FILES ${DEFENDER_SYSTEM_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_defender.c ) +endif() diff --git a/lib/include/aws_iot_defender.h b/libraries/aws/defender/include/aws_iot_defender.h similarity index 100% rename from lib/include/aws_iot_defender.h rename to libraries/aws/defender/include/aws_iot_defender.h diff --git a/lib/source/defender/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c similarity index 100% rename from lib/source/defender/aws_iot_defender_api.c rename to libraries/aws/defender/src/aws_iot_defender_api.c diff --git a/lib/source/defender/aws_iot_defender_collector.c b/libraries/aws/defender/src/aws_iot_defender_collector.c similarity index 100% rename from lib/source/defender/aws_iot_defender_collector.c rename to libraries/aws/defender/src/aws_iot_defender_collector.c diff --git a/lib/source/defender/aws_iot_defender_mqtt.c b/libraries/aws/defender/src/aws_iot_defender_mqtt.c similarity index 100% rename from lib/source/defender/aws_iot_defender_mqtt.c rename to libraries/aws/defender/src/aws_iot_defender_mqtt.c diff --git a/lib/include/private/aws_iot_defender_internal.h b/libraries/aws/defender/src/private/aws_iot_defender_internal.h similarity index 99% rename from lib/include/private/aws_iot_defender_internal.h rename to libraries/aws/defender/src/private/aws_iot_defender_internal.h index 1f09149270..afae4745f0 100644 --- a/lib/include/private/aws_iot_defender_internal.h +++ b/libraries/aws/defender/src/private/aws_iot_defender_internal.h @@ -83,7 +83,7 @@ * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate a Defender report. This function should have the same diff --git a/tests/defender/aws_iot_tests_defender.c b/libraries/aws/defender/test/aws_iot_tests_defender.c similarity index 89% rename from tests/defender/aws_iot_tests_defender.c rename to libraries/aws/defender/test/aws_iot_tests_defender.c index 5d876bbdc3..94aeb327e7 100644 --- a/tests/defender/aws_iot_tests_defender.c +++ b/libraries/aws/defender/test/aws_iot_tests_defender.c @@ -33,6 +33,12 @@ /*-----------------------------------------------------------*/ +/** + * @brief Runs the Defender test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ void RunDefenderTests( bool disableNetworkTests, bool disableLongTests ) { /* Silence warnings about unused parameters. */ diff --git a/tests/defender/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c similarity index 100% rename from tests/defender/system/aws_iot_tests_defender_system.c rename to libraries/aws/defender/test/system/aws_iot_tests_defender_system.c diff --git a/libraries/aws/jobs/CMakeLists.txt b/libraries/aws/jobs/CMakeLists.txt new file mode 100644 index 0000000000..0f35e7429d --- /dev/null +++ b/libraries/aws/jobs/CMakeLists.txt @@ -0,0 +1,75 @@ +# Jobs library source files. +set( JOBS_SOURCES + src/aws_iot_jobs_api.c + src/aws_iot_jobs_serialize.c + src/aws_iot_jobs_operation.c + src/aws_iot_jobs_static_memory.c + src/aws_iot_jobs_subscription.c ) + +# Jobs library target. +add_library( awsiotjobs + ${CONFIG_HEADER_PATH}/iot_config.h + ${JOBS_SOURCES} + include/aws_iot_jobs.h + include/types/aws_iot_jobs_types.h + src/private/aws_iot_jobs_internal.h ) + +# Jobs public include path. +target_include_directories( awsiotjobs PUBLIC include ) + +# Link required libraries. +target_link_libraries( awsiotjobs PRIVATE awsiotcommon iotserializer iotbase ) + +# Jobs is currently implemented on MQTT, so link MQTT as a public dependency. +target_link_libraries( awsiotjobs PUBLIC iotmqtt ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotjobs PRIVATE unity ) +endif() + +# Organization of Jobs in folders. +set_property( TARGET awsiotjobs PROPERTY FOLDER libraries/aws ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( include FILES include/aws_iot_jobs.h ) +source_group( include\\types include/types/aws_iot_jobs_types.h ) +source_group( src\\private src/private/aws_iot_jobs_internal.h ) +source_group( src FILES ${JOBS_SOURCES} ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # Jobs system test sources. + set( JOBS_SYSTEM_TEST_SOURCES + test/system/aws_iot_tests_jobs_system.c ) + + # Jobs unit test sources. + set( JOBS_UNIT_TEST_SOURCES + test/unit/aws_iot_tests_jobs_api.c + test/unit/aws_iot_tests_jobs_serialize.c ) + + # Jobs tests executable. + add_executable( aws_iot_tests_jobs + ${JOBS_SYSTEM_TEST_SOURCES} + ${JOBS_UNIT_TEST_SOURCES} + test/aws_iot_tests_jobs.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( aws_iot_tests_jobs PRIVATE + -DRunTests=RunJobsTests ) + + # The Jobs tests need the internal Jobs header. + target_include_directories( aws_iot_tests_jobs PRIVATE src ) + + # Jobs tests library dependencies. + target_link_libraries( aws_iot_tests_jobs PRIVATE + awsiotjobs awsiotcommon iotserializer iotbase + unity iot_mqtt_mock ) + + # Organization of Jobs tests in folders. + set_property( TARGET aws_iot_tests_jobs PROPERTY FOLDER tests ) + source_group( system FILES ${JOBS_SYSTEM_TEST_SOURCES} ) + source_group( unit FILES ${JOBS_UNIT_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_jobs.c ) +endif() diff --git a/lib/include/aws_iot_jobs.h b/libraries/aws/jobs/include/aws_iot_jobs.h similarity index 100% rename from lib/include/aws_iot_jobs.h rename to libraries/aws/jobs/include/aws_iot_jobs.h diff --git a/lib/include/types/aws_iot_jobs_types.h b/libraries/aws/jobs/include/types/aws_iot_jobs_types.h similarity index 100% rename from lib/include/types/aws_iot_jobs_types.h rename to libraries/aws/jobs/include/types/aws_iot_jobs_types.h diff --git a/lib/source/jobs/aws_iot_jobs_api.c b/libraries/aws/jobs/src/aws_iot_jobs_api.c similarity index 99% rename from lib/source/jobs/aws_iot_jobs_api.c rename to libraries/aws/jobs/src/aws_iot_jobs_api.c index 640da941dd..13ded7b0d3 100644 --- a/lib/source/jobs/aws_iot_jobs_api.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_api.c @@ -38,7 +38,7 @@ #include "private/aws_iot_jobs_internal.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT include. */ #include "iot_mqtt.h" diff --git a/lib/source/jobs/aws_iot_jobs_operation.c b/libraries/aws/jobs/src/aws_iot_jobs_operation.c similarity index 99% rename from lib/source/jobs/aws_iot_jobs_operation.c rename to libraries/aws/jobs/src/aws_iot_jobs_operation.c index 03fdf0f1d5..a3142f4aeb 100644 --- a/lib/source/jobs/aws_iot_jobs_operation.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_operation.c @@ -38,7 +38,7 @@ #include "platform/iot_threads.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT include. */ #include "iot_mqtt.h" diff --git a/lib/source/jobs/aws_iot_jobs_serialize.c b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c similarity index 99% rename from lib/source/jobs/aws_iot_jobs_serialize.c rename to libraries/aws/jobs/src/aws_iot_jobs_serialize.c index e3c92dbecb..d4f6d9fc6b 100644 --- a/lib/source/jobs/aws_iot_jobs_serialize.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c @@ -36,7 +36,7 @@ #include "private/aws_iot_jobs_internal.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* JSON utilities include. */ #include "iot_json_utils.h" diff --git a/lib/source/jobs/aws_iot_jobs_static_memory.c b/libraries/aws/jobs/src/aws_iot_jobs_static_memory.c similarity index 99% rename from lib/source/jobs/aws_iot_jobs_static_memory.c rename to libraries/aws/jobs/src/aws_iot_jobs_static_memory.c index ddfaa2d981..6a48df2fee 100644 --- a/lib/source/jobs/aws_iot_jobs_static_memory.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_static_memory.c @@ -37,7 +37,7 @@ #include /* Static memory include. */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* Jobs internal include. */ #include "private/aws_iot_jobs_internal.h" diff --git a/lib/source/jobs/aws_iot_jobs_subscription.c b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c similarity index 99% rename from lib/source/jobs/aws_iot_jobs_subscription.c rename to libraries/aws/jobs/src/aws_iot_jobs_subscription.c index d4c106adb3..d73469af5a 100644 --- a/lib/source/jobs/aws_iot_jobs_subscription.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c @@ -36,7 +36,7 @@ #include "private/aws_iot_jobs_internal.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Platform layer includes. */ #include "platform/iot_threads.h" diff --git a/lib/include/private/aws_iot_jobs_internal.h b/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h similarity index 99% rename from lib/include/private/aws_iot_jobs_internal.h rename to libraries/aws/jobs/src/private/aws_iot_jobs_internal.h index a94d3537c9..1e68ee9b6e 100644 --- a/lib/include/private/aws_iot_jobs_internal.h +++ b/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h @@ -81,7 +81,7 @@ * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate a #_jobsOperation_t. This function should have the same diff --git a/tests/jobs/aws_iot_tests_jobs.c b/libraries/aws/jobs/test/aws_iot_tests_jobs.c similarity index 89% rename from tests/jobs/aws_iot_tests_jobs.c rename to libraries/aws/jobs/test/aws_iot_tests_jobs.c index 6e4945ef3f..d072bce191 100644 --- a/tests/jobs/aws_iot_tests_jobs.c +++ b/libraries/aws/jobs/test/aws_iot_tests_jobs.c @@ -33,6 +33,12 @@ /*-----------------------------------------------------------*/ +/** + * @brief Runs the Jobs test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ void RunJobsTests( bool disableNetworkTests, bool disableLongTests ) { diff --git a/tests/jobs/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c similarity index 100% rename from tests/jobs/system/aws_iot_tests_jobs_system.c rename to libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c diff --git a/tests/jobs/unit/aws_iot_tests_jobs_api.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c similarity index 100% rename from tests/jobs/unit/aws_iot_tests_jobs_api.c rename to libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c diff --git a/tests/jobs/unit/aws_iot_tests_jobs_serialize.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c similarity index 100% rename from tests/jobs/unit/aws_iot_tests_jobs_serialize.c rename to libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c diff --git a/libraries/aws/shadow/CMakeLists.txt b/libraries/aws/shadow/CMakeLists.txt new file mode 100644 index 0000000000..d26cd46280 --- /dev/null +++ b/libraries/aws/shadow/CMakeLists.txt @@ -0,0 +1,75 @@ +# Shadow library source files. +set( SHADOW_SOURCES + src/aws_iot_shadow_api.c + src/aws_iot_shadow_operation.c + src/aws_iot_shadow_parser.c + src/aws_iot_shadow_static_memory.c + src/aws_iot_shadow_subscription.c ) + +# Shadow library target. +add_library( awsiotshadow + ${CONFIG_HEADER_PATH}/iot_config.h + ${SHADOW_SOURCES} + include/aws_iot_shadow.h + include/types/aws_iot_shadow_types.h + src/private/aws_iot_shadow_internal.h ) + +# Shadow public include path. +target_include_directories( awsiotshadow PUBLIC include ) + +# Link required libraries. +target_link_libraries( awsiotshadow PRIVATE awsiotcommon iotserializer iotbase ) + +# Shadow is currently implemented on MQTT, so link MQTT as a public dependency. +target_link_libraries( awsiotshadow PUBLIC iotmqtt ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( awsiotshadow PRIVATE unity ) +endif() + +# Organization of Shadow in folders. +set_property( TARGET awsiotshadow PROPERTY FOLDER libraries/aws ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( include FILES include/aws_iot_shadow.h ) +source_group( include\\types include/types/aws_iot_shadow_types.h ) +source_group( src\\private src/private/aws_iot_shadow_internal.h ) +source_group( src FILES ${SHADOW_SOURCES} ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # Shadow system test sources. + set( SHADOW_SYSTEM_TEST_SOURCES + test/system/aws_iot_tests_shadow_system.c ) + + # Shadow unit test sources. + set( SHADOW_UNIT_TEST_SOURCES + test/unit/aws_iot_tests_shadow_api.c + test/unit/aws_iot_tests_shadow_parser.c ) + + # Shadow tests executable. + add_executable( aws_iot_tests_shadow + ${SHADOW_SYSTEM_TEST_SOURCES} + ${SHADOW_UNIT_TEST_SOURCES} + test/aws_iot_tests_shadow.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( aws_iot_tests_shadow PRIVATE + -DRunTests=RunShadowTests ) + + # The Shadow tests need the internal Shadow header. + target_include_directories( aws_iot_tests_shadow PRIVATE src ) + + # Shadow tests library dependencies. + target_link_libraries( aws_iot_tests_shadow PRIVATE + awsiotshadow awsiotcommon iotserializer iotbase + unity iot_mqtt_mock ) + + # Organization of Shadow tests in folders. + set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER tests ) + source_group( system FILES ${SHADOW_SYSTEM_TEST_SOURCES} ) + source_group( unit FILES ${SHADOW_UNIT_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_shadow.c ) +endif() diff --git a/lib/include/aws_iot_shadow.h b/libraries/aws/shadow/include/aws_iot_shadow.h similarity index 100% rename from lib/include/aws_iot_shadow.h rename to libraries/aws/shadow/include/aws_iot_shadow.h diff --git a/lib/include/types/aws_iot_shadow_types.h b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h similarity index 100% rename from lib/include/types/aws_iot_shadow_types.h rename to libraries/aws/shadow/include/types/aws_iot_shadow_types.h diff --git a/lib/source/shadow/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c similarity index 99% rename from lib/source/shadow/aws_iot_shadow_api.c rename to libraries/aws/shadow/src/aws_iot_shadow_api.c index 29e56621ba..2388ff6b76 100644 --- a/lib/source/shadow/aws_iot_shadow_api.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_api.c @@ -35,7 +35,7 @@ #include "private/aws_iot_shadow_internal.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Platform layer includes. */ #include "platform/iot_threads.h" diff --git a/lib/source/shadow/aws_iot_shadow_operation.c b/libraries/aws/shadow/src/aws_iot_shadow_operation.c similarity index 99% rename from lib/source/shadow/aws_iot_shadow_operation.c rename to libraries/aws/shadow/src/aws_iot_shadow_operation.c index 1f75572213..058048d453 100644 --- a/lib/source/shadow/aws_iot_shadow_operation.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_operation.c @@ -41,7 +41,7 @@ #include "iot_mqtt.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /*-----------------------------------------------------------*/ diff --git a/lib/source/shadow/aws_iot_shadow_parser.c b/libraries/aws/shadow/src/aws_iot_shadow_parser.c similarity index 99% rename from lib/source/shadow/aws_iot_shadow_parser.c rename to libraries/aws/shadow/src/aws_iot_shadow_parser.c index 74bbeb28a7..3d2b7ee893 100644 --- a/lib/source/shadow/aws_iot_shadow_parser.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_parser.c @@ -36,7 +36,7 @@ #include "private/aws_iot_shadow_internal.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* JSON utilities include. */ #include "iot_json_utils.h" diff --git a/lib/source/shadow/aws_iot_shadow_static_memory.c b/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c similarity index 99% rename from lib/source/shadow/aws_iot_shadow_static_memory.c rename to libraries/aws/shadow/src/aws_iot_shadow_static_memory.c index 15204dca7c..2e2d67d00f 100644 --- a/lib/source/shadow/aws_iot_shadow_static_memory.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_static_memory.c @@ -37,7 +37,7 @@ #include /* Static memory include. */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" diff --git a/lib/source/shadow/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c similarity index 99% rename from lib/source/shadow/aws_iot_shadow_subscription.c rename to libraries/aws/shadow/src/aws_iot_shadow_subscription.c index de904c03fb..deec5f51d6 100644 --- a/lib/source/shadow/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -36,7 +36,7 @@ #include "private/aws_iot_shadow_internal.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Platform layer includes. */ #include "platform/iot_threads.h" diff --git a/lib/include/private/aws_iot_shadow_internal.h b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h similarity index 99% rename from lib/include/private/aws_iot_shadow_internal.h rename to libraries/aws/shadow/src/private/aws_iot_shadow_internal.h index 7e16284973..b6ec2af511 100644 --- a/lib/include/private/aws_iot_shadow_internal.h +++ b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h @@ -84,7 +84,7 @@ * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate a #_shadowOperation_t. This function should have the same diff --git a/tests/shadow/aws_iot_tests_shadow.c b/libraries/aws/shadow/test/aws_iot_tests_shadow.c similarity index 89% rename from tests/shadow/aws_iot_tests_shadow.c rename to libraries/aws/shadow/test/aws_iot_tests_shadow.c index 99f5bf5ad0..efb74f1903 100644 --- a/tests/shadow/aws_iot_tests_shadow.c +++ b/libraries/aws/shadow/test/aws_iot_tests_shadow.c @@ -33,6 +33,12 @@ /*-----------------------------------------------------------*/ +/** + * @brief Runs the Shadow test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ void RunShadowTests( bool disableNetworkTests, bool disableLongTests ) { /* Silence warnings about unused parameters. */ diff --git a/tests/shadow/system/aws_iot_tests_shadow_system.c b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c similarity index 100% rename from tests/shadow/system/aws_iot_tests_shadow_system.c rename to libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c diff --git a/tests/shadow/unit/aws_iot_tests_shadow_api.c b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_api.c similarity index 100% rename from tests/shadow/unit/aws_iot_tests_shadow_api.c rename to libraries/aws/shadow/test/unit/aws_iot_tests_shadow_api.c diff --git a/tests/shadow/unit/aws_iot_tests_shadow_parser.c b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c similarity index 100% rename from tests/shadow/unit/aws_iot_tests_shadow_parser.c rename to libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c diff --git a/lib/include/platform/iot_clock.h b/libraries/platform/iot_clock.h similarity index 100% rename from lib/include/platform/iot_clock.h rename to libraries/platform/iot_clock.h diff --git a/lib/include/platform/iot_metrics.h b/libraries/platform/iot_metrics.h similarity index 100% rename from lib/include/platform/iot_metrics.h rename to libraries/platform/iot_metrics.h diff --git a/lib/include/platform/iot_network.h b/libraries/platform/iot_network.h similarity index 100% rename from lib/include/platform/iot_network.h rename to libraries/platform/iot_network.h diff --git a/lib/include/platform/iot_threads.h b/libraries/platform/iot_threads.h similarity index 100% rename from lib/include/platform/iot_threads.h rename to libraries/platform/iot_threads.h diff --git a/lib/include/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h similarity index 100% rename from lib/include/types/iot_platform_types.h rename to libraries/platform/types/iot_platform_types.h diff --git a/libraries/standard/common/CMakeLists.txt b/libraries/standard/common/CMakeLists.txt new file mode 100644 index 0000000000..8883d6052f --- /dev/null +++ b/libraries/standard/common/CMakeLists.txt @@ -0,0 +1,128 @@ +# Common libraries source files. +set( COMMON_SOURCES + src/iot_init.c + src/iot_logging.c + src/iot_static_memory_common.c + src/iot_taskpool.c + src/iot_taskpool_static_memory.c ) + +# Lists of common header files. These are only used for directory organization. +set( COMMON_PUBLIC_HEADERS + include/iot_error.h + include/iot_init.h + include/iot_linear_containers.h + include/iot_logging.h + include/iot_logging_setup.h + include/iot_static_memory.h + include/iot_taskpool.h ) +set( COMMON_PRIVATE_HEADERS + src/private/iot_taskpool_internal.h ) +set( COMMON_TYPES_HEADERS + include/types/iot_taskpool_types.h ) + +# List of the platform public headers. +set( PLATFORM_INTERFACE_DIRECTORY ${PROJECT_SOURCE_DIR}/libraries/platform ) +set( PORTS_DIRECTORY ${PROJECT_SOURCE_DIR}/ports ) + +set( PLATFORM_INTERFACE_HEADERS + ${PLATFORM_INTERFACE_DIRECTORY}/iot_clock.h + ${PLATFORM_INTERFACE_DIRECTORY}/iot_metrics.h + ${PLATFORM_INTERFACE_DIRECTORY}/iot_network.h + ${PLATFORM_INTERFACE_DIRECTORY}/iot_threads.h ) +set( PLATFORM_TYPES_HEADER + ${PLATFORM_INTERFACE_DIRECTORY}/types/iot_platform_types.h ) +set( PLATFORM_COMMON_HEADERS + ${PORTS_DIRECTORY}/common/include/iot_atomic.h + ${PORTS_DIRECTORY}/common/include/iot_network_metrics.h ) + +# Include the CMake file for this port. +include( ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME}/${IOT_PLATFORM_NAME}.cmake ) + +# The port CMake file must specify the types header and the platform sources. +if( NOT DEFINED PORT_TYPES_HEADER ) + message( FATAL_ERROR "Port CMake file did not set PORT_TYPES_HEADER." ) +endif() + +if( NOT DEFINED PLATFORM_SOURCES ) + message( FATAL_ERROR "Port CMake file did not set PLATFORM_SOURCES." ) +endif() + +# iotbase library target. This is a combination of the platform port and the +# common libraries. +add_library( iotbase + ${CONFIG_HEADER_PATH}/iot_config.h + ${COMMON_SOURCES} + ${COMMON_PUBLIC_HEADERS} + ${COMMON_PRIVATE_HEADERS} + ${COMMON_TYPES_HEADERS} + ${PLATFORM_INTERFACE_HEADERS} + ${PLATFORM_TYPES_HEADER} + ${PLATFORM_COMMON_HEADERS} + ${PLATFORM_SOURCES} + ${PORT_TYPES_HEADER} ) + +# Set the types header for the port. +target_compile_definitions( iotbase PUBLIC -DIOT_SYSTEM_TYPES_FILE="${PORT_TYPES_HEADER}" ) + +# Specify if OpenSSL is being used in a compiler define. +if( ${IOT_NETWORK_USE_OPENSSL} ) + target_compile_definitions( iotbase PUBLIC -DIOT_NETWORK_USE_OPENSSL=1 ) +endif() + +# Set the include directories for the common and platform libraries. +target_include_directories( iotbase + PUBLIC + ${CONFIG_HEADER_PATH} + ${PLATFORM_INTERFACE_DIRECTORY}/.. + ${PLATFORM_INTERFACE_DIRECTORY} + ${PORTS_DIRECTORY}/common/include + include/ ) + +# Link any dependencies required by this port. +target_link_libraries( iotbase PRIVATE ${PLATFORM_DEPENDENCIES} ) + +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotbase PRIVATE unity ) +endif() + +# Organization of iotbase in folders. +set_property( TARGET iotbase PROPERTY FOLDER libraries/standard ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( common\\include FILES ${COMMON_PUBLIC_HEADERS} ) +source_group( common\\include\\types FILES ${COMMON_TYPES_HEADERS} ) +source_group( common\\src FILES ${COMMON_SOURCES} ) +source_group( common\\src\\private FILES ${COMMON_PRIVATE_HEADERS} ) +source_group( platform\\include FILES ${PLATFORM_INTERFACE_HEADERS} ${PLATFORM_COMMON_HEADERS} ) +source_group( platform\\include\\types FILES ${PLATFORM_TYPES_HEADER} ${PORT_TYPES_HEADER} ) +source_group( platform\\src FILES ${PLATFORM_SOURCES} ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # Common unit test sources. + set( COMMON_UNIT_TEST_SOURCES + test/unit/iot_tests_linear_containers.c + test/unit/iot_tests_taskpool.c + test/unit/iot_tests_atomic.c ) + + # Common tests executable. + add_executable( iot_tests_common + ${COMMON_UNIT_TEST_SOURCES} + test/iot_tests_common.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( iot_tests_common PRIVATE + -DRunTests=RunCommonTests ) + + # The tests use internal headers. Add the include path for internal headers. + target_include_directories( iot_tests_common PRIVATE src ) + + # Common tests library dependencies. + target_link_libraries( iot_tests_common PRIVATE iotbase unity ) + + # Organization of common tests in folders. + set_property( TARGET iot_tests_common PROPERTY FOLDER tests ) + source_group( unit FILES ${COMMON_UNIT_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/iot_tests_common.c ) +endif() diff --git a/lib/include/private/iot_error.h b/libraries/standard/common/include/iot_error.h similarity index 100% rename from lib/include/private/iot_error.h rename to libraries/standard/common/include/iot_error.h diff --git a/lib/include/iot_init.h b/libraries/standard/common/include/iot_init.h similarity index 100% rename from lib/include/iot_init.h rename to libraries/standard/common/include/iot_init.h diff --git a/lib/include/iot_linear_containers.h b/libraries/standard/common/include/iot_linear_containers.h similarity index 100% rename from lib/include/iot_linear_containers.h rename to libraries/standard/common/include/iot_linear_containers.h diff --git a/lib/include/private/iot_logging.h b/libraries/standard/common/include/iot_logging.h similarity index 100% rename from lib/include/private/iot_logging.h rename to libraries/standard/common/include/iot_logging.h diff --git a/lib/include/iot_logging_setup.h b/libraries/standard/common/include/iot_logging_setup.h similarity index 99% rename from lib/include/iot_logging_setup.h rename to libraries/standard/common/include/iot_logging_setup.h index 91dd75845a..1d38841818 100644 --- a/lib/include/iot_logging_setup.h +++ b/libraries/standard/common/include/iot_logging_setup.h @@ -33,7 +33,7 @@ /* Logging include. Because it's included here, iot_logging.h never needs * to be included in source. */ -#include "private/iot_logging.h" +#include "iot_logging.h" /** * @functionpage{IotLog,logging,log} diff --git a/lib/include/private/iot_static_memory.h b/libraries/standard/common/include/iot_static_memory.h similarity index 100% rename from lib/include/private/iot_static_memory.h rename to libraries/standard/common/include/iot_static_memory.h diff --git a/lib/include/iot_taskpool.h b/libraries/standard/common/include/iot_taskpool.h similarity index 100% rename from lib/include/iot_taskpool.h rename to libraries/standard/common/include/iot_taskpool.h diff --git a/lib/include/types/iot_taskpool_types.h b/libraries/standard/common/include/types/iot_taskpool_types.h similarity index 100% rename from lib/include/types/iot_taskpool_types.h rename to libraries/standard/common/include/types/iot_taskpool_types.h diff --git a/lib/source/common/iot_init.c b/libraries/standard/common/src/iot_init.c similarity index 98% rename from lib/source/common/iot_init.c rename to libraries/standard/common/src/iot_init.c index f703c80348..4b58afac93 100644 --- a/lib/source/common/iot_init.c +++ b/libraries/standard/common/src/iot_init.c @@ -38,10 +38,10 @@ #include "iot_atomic.h" /* Static memory include (if dynamic memory allocation is disabled). */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_GLOBAL diff --git a/lib/source/common/iot_logging.c b/libraries/standard/common/src/iot_logging.c similarity index 99% rename from lib/source/common/iot_logging.c rename to libraries/standard/common/src/iot_logging.c index 324241d43d..985fd659f0 100644 --- a/lib/source/common/iot_logging.c +++ b/libraries/standard/common/src/iot_logging.c @@ -37,7 +37,7 @@ #include "platform/iot_clock.h" /* Logging includes. */ -#include "private/iot_logging.h" +#include "iot_logging.h" /*-----------------------------------------------------------*/ @@ -77,7 +77,7 @@ */ #if IOT_STATIC_MEMORY_ONLY == 1 /* Static memory allocation header. */ - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate a new logging buffer. This function must have the same diff --git a/lib/source/common/iot_static_memory_common.c b/libraries/standard/common/src/iot_static_memory_common.c similarity index 99% rename from lib/source/common/iot_static_memory_common.c rename to libraries/standard/common/src/iot_static_memory_common.c index aa26c15251..f289837933 100644 --- a/lib/source/common/iot_static_memory_common.c +++ b/libraries/standard/common/src/iot_static_memory_common.c @@ -41,7 +41,7 @@ #include "platform/iot_threads.h" /* Static memory include. */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* Atomic include. */ #include "iot_atomic.h" diff --git a/lib/source/common/iot_taskpool.c b/libraries/standard/common/src/iot_taskpool.c similarity index 100% rename from lib/source/common/iot_taskpool.c rename to libraries/standard/common/src/iot_taskpool.c diff --git a/lib/source/common/iot_taskpool_static_memory.c b/libraries/standard/common/src/iot_taskpool_static_memory.c similarity index 99% rename from lib/source/common/iot_taskpool_static_memory.c rename to libraries/standard/common/src/iot_taskpool_static_memory.c index 552ad8e7ce..fbab2e5723 100644 --- a/lib/source/common/iot_taskpool_static_memory.c +++ b/libraries/standard/common/src/iot_taskpool_static_memory.c @@ -36,7 +36,7 @@ #include /* Static memory include. */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* Task pool internal include. */ #include "private/iot_taskpool_internal.h" diff --git a/lib/include/private/iot_taskpool_internal.h b/libraries/standard/common/src/private/iot_taskpool_internal.h similarity index 99% rename from lib/include/private/iot_taskpool_internal.h rename to libraries/standard/common/src/private/iot_taskpool_internal.h index af6d22a3eb..509e9c416d 100644 --- a/lib/include/private/iot_taskpool_internal.h +++ b/libraries/standard/common/src/private/iot_taskpool_internal.h @@ -33,7 +33,7 @@ #include "iot_config.h" /* Task pool include. */ -#include "private/iot_error.h" +#include "iot_error.h" #include "iot_taskpool.h" /* Establish a few convenience macros to handle errors in a standard way. */ @@ -140,7 +140,7 @@ * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate an #_taskPool_t. This function should have the diff --git a/tests/common/iot_tests_common.c b/libraries/standard/common/test/iot_tests_common.c similarity index 89% rename from tests/common/iot_tests_common.c rename to libraries/standard/common/test/iot_tests_common.c index b3e8739eb6..d59efe6162 100644 --- a/tests/common/iot_tests_common.c +++ b/libraries/standard/common/test/iot_tests_common.c @@ -33,6 +33,12 @@ /*-----------------------------------------------------------*/ +/** + * @brief Runs the common test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ void RunCommonTests( bool disableNetworkTests, bool disableLongTests ) { /* Silence warnings about unused parameters. */ diff --git a/tests/common/unit/iot_tests_atomic.c b/libraries/standard/common/test/unit/iot_tests_atomic.c similarity index 100% rename from tests/common/unit/iot_tests_atomic.c rename to libraries/standard/common/test/unit/iot_tests_atomic.c diff --git a/tests/common/unit/iot_tests_linear_containers.c b/libraries/standard/common/test/unit/iot_tests_linear_containers.c similarity index 100% rename from tests/common/unit/iot_tests_linear_containers.c rename to libraries/standard/common/test/unit/iot_tests_linear_containers.c diff --git a/tests/common/unit/iot_tests_taskpool.c b/libraries/standard/common/test/unit/iot_tests_taskpool.c similarity index 100% rename from tests/common/unit/iot_tests_taskpool.c rename to libraries/standard/common/test/unit/iot_tests_taskpool.c diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt new file mode 100644 index 0000000000..f4bd1e0329 --- /dev/null +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -0,0 +1,99 @@ +# MQTT library source files. +set( MQTT_SOURCES + src/iot_mqtt_api.c + src/iot_mqtt_network.c + src/iot_mqtt_operation.c + src/iot_mqtt_serialize.c + src/iot_mqtt_static_memory.c + src/iot_mqtt_subscription.c + src/iot_mqtt_validate.c ) + +# MQTT library target. +add_library( iotmqtt + ${CONFIG_HEADER_PATH}/iot_config.h + ${MQTT_SOURCES} + include/iot_mqtt.h + include/types/iot_mqtt_types.h + src/private/iot_mqtt_internal.h ) + +# MQTT public include path. +target_include_directories( iotmqtt PUBLIC include ) + +# Link required libraries. +target_link_libraries( iotmqtt PRIVATE iotbase ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotmqtt PRIVATE unity ) +endif() + +# Organization of MQTT in folders. +set_property( TARGET iotmqtt PROPERTY FOLDER libraries/standard ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( include FILES include/iot_mqtt.h ) +source_group( include\\types FILES include/types/iot_mqtt_types.h ) +source_group( src FILES ${MQTT_SOURCES} ) +source_group( src\\private FILES src/private/iot_mqtt_internal.h ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # Enable test access in MQTT. + target_compile_definitions( iotmqtt PRIVATE -DIOT_BUILD_TESTS=1 ) + target_include_directories( iotmqtt PUBLIC test/access ) + + # This test library is used to mock MQTT connections. + add_library( iot_mqtt_mock + test/mock/iot_tests_mqtt_mock.c + test/mock/iot_tests_mqtt_mock.h + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Organization of MQTT mock library in folders. + set_property( TARGET iot_mqtt_mock PROPERTY FOLDER tests ) + source_group( "" FILES + test/mock/iot_tests_mqtt_mock.c + test/mock/iot_tests_mqtt_mock.h + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # The MQTT mock needs the internal MQTT header. + target_include_directories( iot_mqtt_mock + PRIVATE src + PUBLIC test/mock ) + + # Link required libraries for MQTT mock. + target_link_libraries( iot_mqtt_mock PRIVATE iotbase iotmqtt unity ) + + # MQTT system test sources. + set( MQTT_SYSTEM_TEST_SOURCES + test/system/iot_tests_mqtt_system.c ) + + # MQTT unit test sources. + set( MQTT_UNIT_TEST_SOURCES + test/unit/iot_tests_mqtt_api.c + test/unit/iot_tests_mqtt_receive.c + test/unit/iot_tests_mqtt_subscription.c + test/unit/iot_tests_mqtt_validate.c ) + + # MQTT tests executable. + add_executable( iot_tests_mqtt + ${MQTT_SYSTEM_TEST_SOURCES} + ${MQTT_UNIT_TEST_SOURCES} + test/iot_tests_mqtt.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( iot_tests_mqtt PRIVATE + -DRunTests=RunMqttTests ) + + # The MQTT tests need the internal MQTT header. + target_include_directories( iot_tests_mqtt PRIVATE src ) + + # MQTT tests library dependencies. + target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt iotbase unity iot_mqtt_mock ) + + # Organization of MQTT tests in folders. + set_property( TARGET iot_tests_mqtt PROPERTY FOLDER tests ) + source_group( system FILES ${MQTT_SYSTEM_TEST_SOURCES} ) + source_group( unit FILES ${MQTT_UNIT_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/iot_tests_mqtt.c ) +endif() diff --git a/lib/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h similarity index 100% rename from lib/include/iot_mqtt.h rename to libraries/standard/mqtt/include/iot_mqtt.h diff --git a/lib/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h similarity index 100% rename from lib/include/types/iot_mqtt_types.h rename to libraries/standard/mqtt/include/types/iot_mqtt_types.h diff --git a/lib/source/mqtt/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_api.c rename to libraries/standard/mqtt/src/iot_mqtt_api.c index e581d9bcc4..14052e16f9 100644 --- a/lib/source/mqtt/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -32,7 +32,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/source/mqtt/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_network.c rename to libraries/standard/mqtt/src/iot_mqtt_network.c index cb3266187e..2ec8f6114a 100644 --- a/lib/source/mqtt/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -32,7 +32,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/source/mqtt/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_operation.c rename to libraries/standard/mqtt/src/iot_mqtt_operation.c index e430338ac9..5a099120dd 100644 --- a/lib/source/mqtt/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -32,7 +32,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/source/mqtt/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_serialize.c rename to libraries/standard/mqtt/src/iot_mqtt_serialize.c index 0e54ad4a6f..fc3001dd9e 100644 --- a/lib/source/mqtt/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -32,7 +32,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT internal includes. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/source/mqtt/iot_mqtt_static_memory.c b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_static_memory.c rename to libraries/standard/mqtt/src/iot_mqtt_static_memory.c index 345cecc0c0..3186ff45eb 100644 --- a/lib/source/mqtt/iot_mqtt_static_memory.c +++ b/libraries/standard/mqtt/src/iot_mqtt_static_memory.c @@ -36,7 +36,7 @@ #include /* Static memory include. */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/source/mqtt/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_subscription.c rename to libraries/standard/mqtt/src/iot_mqtt_subscription.c index 2e813040e6..1f057bcb35 100644 --- a/lib/source/mqtt/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -33,7 +33,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/source/mqtt/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c similarity index 99% rename from lib/source/mqtt/iot_mqtt_validate.c rename to libraries/standard/mqtt/src/iot_mqtt_validate.c index e2885a5b11..16450708f4 100644 --- a/lib/source/mqtt/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -29,7 +29,7 @@ #include "iot_config.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" diff --git a/lib/include/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h similarity index 99% rename from lib/include/private/iot_mqtt_internal.h rename to libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 2e7f70d470..ae4fc4703b 100644 --- a/lib/include/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -81,7 +81,7 @@ * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate an #_mqttConnection_t. This function should have the same diff --git a/tests/mqtt/access/iot_test_access_mqtt.h b/libraries/standard/mqtt/test/access/iot_test_access_mqtt.h similarity index 100% rename from tests/mqtt/access/iot_test_access_mqtt.h rename to libraries/standard/mqtt/test/access/iot_test_access_mqtt.h diff --git a/tests/mqtt/access/iot_test_access_mqtt_api.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c similarity index 100% rename from tests/mqtt/access/iot_test_access_mqtt_api.c rename to libraries/standard/mqtt/test/access/iot_test_access_mqtt_api.c diff --git a/tests/mqtt/access/iot_test_access_mqtt_subscription.c b/libraries/standard/mqtt/test/access/iot_test_access_mqtt_subscription.c similarity index 100% rename from tests/mqtt/access/iot_test_access_mqtt_subscription.c rename to libraries/standard/mqtt/test/access/iot_test_access_mqtt_subscription.c diff --git a/tests/mqtt/iot_tests_mqtt.c b/libraries/standard/mqtt/test/iot_tests_mqtt.c similarity index 89% rename from tests/mqtt/iot_tests_mqtt.c rename to libraries/standard/mqtt/test/iot_tests_mqtt.c index 6322c12e60..841406d568 100644 --- a/tests/mqtt/iot_tests_mqtt.c +++ b/libraries/standard/mqtt/test/iot_tests_mqtt.c @@ -33,6 +33,12 @@ /*-----------------------------------------------------------*/ +/** + * @brief Runs the MQTT test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ void RunMqttTests( bool disableNetworkTests, bool disableLongTests ) { /* Silence warnings about unused parameters. */ diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.c b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c similarity index 99% rename from tests/mqtt/mock/iot_tests_mqtt_mock.c rename to libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c index 1d17cfff1f..70b38698bb 100644 --- a/tests/mqtt/mock/iot_tests_mqtt_mock.c +++ b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c @@ -45,7 +45,7 @@ #include "platform/iot_threads.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /*-----------------------------------------------------------*/ diff --git a/tests/mqtt/mock/iot_tests_mqtt_mock.h b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.h similarity index 100% rename from tests/mqtt/mock/iot_tests_mqtt_mock.h rename to libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.h diff --git a/tests/mqtt/system/iot_tests_mqtt_system.c b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c similarity index 100% rename from tests/mqtt/system/iot_tests_mqtt_system.c rename to libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c diff --git a/tests/mqtt/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c similarity index 100% rename from tests/mqtt/unit/iot_tests_mqtt_api.c rename to libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c diff --git a/tests/mqtt/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c similarity index 100% rename from tests/mqtt/unit/iot_tests_mqtt_receive.c rename to libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c diff --git a/tests/mqtt/unit/iot_tests_mqtt_subscription.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c similarity index 100% rename from tests/mqtt/unit/iot_tests_mqtt_subscription.c rename to libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c diff --git a/tests/mqtt/unit/iot_tests_mqtt_validate.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c similarity index 100% rename from tests/mqtt/unit/iot_tests_mqtt_validate.c rename to libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c diff --git a/libraries/standard/serializer/CMakeLists.txt b/libraries/standard/serializer/CMakeLists.txt new file mode 100644 index 0000000000..7e2bd11f2b --- /dev/null +++ b/libraries/standard/serializer/CMakeLists.txt @@ -0,0 +1,58 @@ +# Serializer library source files. +set( SERIALIZER_SOURCES + src/iot_json_utils.c + src/iot_serializer_static_memory.c + src/cbor/iot_serializer_tinycbor_decoder.c + src/cbor/iot_serializer_tinycbor_encoder.c ) + +# Serializer library target. +add_library( iotserializer + ${CONFIG_HEADER_PATH}/iot_config.h + ${SERIALIZER_SOURCES} + include/iot_serializer.h + include/iot_json_utils.h ) + +# Serializer public include path. +target_include_directories( iotserializer PUBLIC include ) + +# Link required libraries. +target_link_libraries( iotserializer PRIVATE iotbase tinycbor ) + +# Link Unity test framework when building tests. +if( ${IOT_BUILD_TESTS} ) + target_link_libraries( iotserializer PRIVATE unity ) +endif() + +# Organization of Serializer in folders. +set_property( TARGET iotserializer PROPERTY FOLDER libraries/standard ) +source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) +source_group( include FILES + include/iot_serializer.h + include/iot_json_utils.h ) +source_group( src FILES ${SERIALIZER_SOURCES} ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # Serializer unit test sources. + set( SERIALIZER_UNIT_TEST_SOURCES + test/unit/iot_tests_serializer_cbor.c ) + + # Serializer tests executable. + add_executable( iot_tests_serializer + ${SERIALIZER_UNIT_TEST_SOURCES} + test/iot_tests_serializer.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( iot_tests_serializer PRIVATE + -DRunTests=RunSerializerTests ) + + # Serializer tests library dependencies. + target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotbase unity tinycbor ) + + # Organization of serializer tests in folders. + set_property( TARGET iot_tests_serializer PROPERTY FOLDER tests ) + source_group( unit FILES ${SERIALIZER_UNIT_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/iot_tests_serializer.c ) +endif() diff --git a/lib/include/iot_json_utils.h b/libraries/standard/serializer/include/iot_json_utils.h similarity index 100% rename from lib/include/iot_json_utils.h rename to libraries/standard/serializer/include/iot_json_utils.h diff --git a/lib/include/iot_serializer.h b/libraries/standard/serializer/include/iot_serializer.h similarity index 99% rename from lib/include/iot_serializer.h rename to libraries/standard/serializer/include/iot_serializer.h index 1380796bba..8b29afc9dd 100644 --- a/lib/include/iot_serializer.h +++ b/libraries/standard/serializer/include/iot_serializer.h @@ -58,7 +58,7 @@ * the usage of dynamic memory allocation. */ #if IOT_STATIC_MEMORY_ONLY == 1 - #include "private/iot_static_memory.h" + #include "iot_static_memory.h" /** * @brief Allocate an array of uint8_t. This function should have the same diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c similarity index 100% rename from lib/source/serializer/cbor/iot_serializer_tinycbor_decoder.c rename to libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c diff --git a/lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c similarity index 100% rename from lib/source/serializer/cbor/iot_serializer_tinycbor_encoder.c rename to libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c diff --git a/lib/source/serializer/iot_json_utils.c b/libraries/standard/serializer/src/iot_json_utils.c similarity index 100% rename from lib/source/serializer/iot_json_utils.c rename to libraries/standard/serializer/src/iot_json_utils.c diff --git a/lib/source/serializer/iot_serializer_static_memory.c b/libraries/standard/serializer/src/iot_serializer_static_memory.c similarity index 99% rename from lib/source/serializer/iot_serializer_static_memory.c rename to libraries/standard/serializer/src/iot_serializer_static_memory.c index 94d2b10336..bf43906c5c 100644 --- a/lib/source/serializer/iot_serializer_static_memory.c +++ b/libraries/standard/serializer/src/iot_serializer_static_memory.c @@ -31,7 +31,7 @@ #include /* Static memory include. */ -#include "private/iot_static_memory.h" +#include "iot_static_memory.h" /* Metrics include. */ #include "iot_serializer.h" diff --git a/tests/serializer/iot_tests_serializer.c b/libraries/standard/serializer/test/iot_tests_serializer.c similarity index 88% rename from tests/serializer/iot_tests_serializer.c rename to libraries/standard/serializer/test/iot_tests_serializer.c index 58302bf9a0..8eaa776dd2 100644 --- a/tests/serializer/iot_tests_serializer.c +++ b/libraries/standard/serializer/test/iot_tests_serializer.c @@ -33,6 +33,12 @@ /*-----------------------------------------------------------*/ +/** + * @brief Runs the Serializer test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ void RunSerializerTests( bool disableNetworkTests, bool disableLongTests ) { /* Silence warnings about unused parameters. */ diff --git a/tests/serializer/unit/iot_tests_serializer_cbor.c b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c similarity index 100% rename from tests/serializer/unit/iot_tests_serializer_cbor.c rename to libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c diff --git a/platform/README.md b/platform/README.md deleted file mode 100644 index 85fd880895..0000000000 --- a/platform/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Platform layer - -**Main documentation page:** [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/index.html) - -This directory contains the headers and sources of the platform layer, implemented for various ports. Its subdirectories are organized as follows: -- `include`
- Platform layer headers that are not specific to a specific port, such as the atomic and network headers. - - `atomic`
- Implementations of atomic operations. -- `network`
- Implementations of the platform network layer that are not specific to a port. -- `ports`
- Source files of a specific port. - - `posix`, `win32`, `...`
- Port sources are in a directory named after their target. - - `template`
- Empty port sources containing stubbed-out functions. - -When porting this SDK to a new platform, only files in this directory should be modified. See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions. diff --git a/platform/ports/macos/CMakeLists.txt b/platform/ports/macos/CMakeLists.txt deleted file mode 100644 index d5dcc2711f..0000000000 --- a/platform/ports/macos/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -# Check for POSIX threads. -find_package( Threads REQUIRED ) - -if( NOT ${CMAKE_USE_PTHREADS_INIT} ) - message( FATAL_ERROR "POSIX threads required." ) -endif() - -# Add the network header for this platform. -set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} - ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h - PARENT_SCOPE ) - -# Platform library source files. -set( PLATFORM_SOURCES - ${CMAKE_SOURCE_DIR}/platform/ports/macos/source/iot_threads_macos.c - ${CMAKE_SOURCE_DIR}/platform/ports/macos/source/iot_clock_macos.c - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ) - -# Add platform as an interface library. It will be built into iotbase. -add_library( iotplatform INTERFACE ) - -target_sources( iotplatform INTERFACE - ${PLATFORM_SOURCES} ) - -# This platform layer uses mbed TLS. -target_link_libraries( iotplatform INTERFACE Threads::Threads mbedtls ) - -# Set platform sources in the parent scope for directory organization. -set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) diff --git a/platform/ports/template/CMakeLists.txt b/platform/ports/template/CMakeLists.txt deleted file mode 100644 index 56b64d34b3..0000000000 --- a/platform/ports/template/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -# This CMakeLists is a template for new ports. It provides the minimal -# configuration for building, but nothing more. - -# Warn that the template port only builds. It will not create usable libraries. -message( WARNING "This is a template port that contains only stubs. Libraries built with this port will not work!") - -# Add the mbed TLS header in the template. The mbed TLS network port is supposed -# to be platform-independent, so it is built in the template. -set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} - ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h - PARENT_SCOPE ) - -# Template platform sources. Except for the network sources, these files contain -# only stubs. -set( PLATFORM_SOURCES - # Stubs - ${CMAKE_SOURCE_DIR}/platform/ports/template/source/iot_clock_template.c - ${CMAKE_SOURCE_DIR}/platform/ports/template/source/iot_threads_template.c - - # Network sources - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ) - -# ANYTHING BELOW THIS LINE TYPICALLY DOES NOT NEED TO BE MODIFIED. - -# Add platform as an interface library. It will be built into iotbase. -add_library( iotplatform INTERFACE ) - -target_sources( iotplatform INTERFACE - ${PLATFORM_SOURCES} ) - -# This template platform layer uses mbed TLS, since it builds the mbed TLS network -# sources. -target_link_libraries( iotplatform INTERFACE mbedtls ) - -# Set platform sources in the parent scope for directory organization. -set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) diff --git a/platform/ports/win32/CMakeLists.txt b/platform/ports/win32/CMakeLists.txt deleted file mode 100644 index bd89009797..0000000000 --- a/platform/ports/win32/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Add the network header for this platform. -set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} - ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h - PARENT_SCOPE ) - -# Platform library source files. -set( PLATFORM_SOURCES - ${CMAKE_SOURCE_DIR}/platform/ports/win32/source/iot_clock_win32.c - ${CMAKE_SOURCE_DIR}/platform/ports/win32/source/iot_threads_win32.c - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c ) - -# Add platform as an interface library. It will be built into iotbase. -add_library( iotplatform INTERFACE ) - -target_sources( iotplatform INTERFACE - ${PLATFORM_SOURCES} ) - -# This platform layer uses mbed TLS. -target_link_libraries( iotplatform INTERFACE mbedtls ) - -# Link the Winsock2 library. -target_link_libraries( iotplatform INTERFACE ws2_32 ) - -# Set platform sources in the parent scope for directory organization. -set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) diff --git a/ports/README.md b/ports/README.md new file mode 100644 index 0000000000..ffb22cf864 --- /dev/null +++ b/ports/README.md @@ -0,0 +1,19 @@ +# Platform ports + +This directory contains the desktop OS ports. Each port implements the functions of the platform layer interface for a specific desktop OS. See [Platform layer](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/platform/index.html) for details on the platform layer interface. + +Its subdirectories are organized as follows: +- `common`
+ Port files that are not specific to a single port. These headers are used across all of the desktop OS ports. + - `include`
+ Port headers that are not specific to a single port, such as the atomic and network headers. + - `atomic`
+ Implementations of atomic operations. + - `src`
+ Port sources that are not specific to a single port, such as the network implementations. +- `template`
+ Empty port sources containing stubbed-out functions. The files in this directory may be used as a starting point for a new port. +- `posix`, `macos`, `win32`
+ Port sources and headers for a single implementation. The directory is named after the target OS. + +See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions on how to create a new port. diff --git a/platform/include/atomic/iot_atomic_gcc.h b/ports/common/include/atomic/iot_atomic_gcc.h similarity index 100% rename from platform/include/atomic/iot_atomic_gcc.h rename to ports/common/include/atomic/iot_atomic_gcc.h diff --git a/platform/include/atomic/iot_atomic_generic.h b/ports/common/include/atomic/iot_atomic_generic.h similarity index 100% rename from platform/include/atomic/iot_atomic_generic.h rename to ports/common/include/atomic/iot_atomic_generic.h diff --git a/platform/include/iot_atomic.h b/ports/common/include/iot_atomic.h similarity index 100% rename from platform/include/iot_atomic.h rename to ports/common/include/iot_atomic.h diff --git a/platform/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h similarity index 100% rename from platform/include/iot_network_mbedtls.h rename to ports/common/include/iot_network_mbedtls.h diff --git a/platform/include/iot_network_metrics.h b/ports/common/include/iot_network_metrics.h similarity index 100% rename from platform/include/iot_network_metrics.h rename to ports/common/include/iot_network_metrics.h diff --git a/platform/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h similarity index 100% rename from platform/include/iot_network_openssl.h rename to ports/common/include/iot_network_openssl.h diff --git a/platform/network/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c similarity index 99% rename from platform/network/iot_network_mbedtls.c rename to ports/common/src/iot_network_mbedtls.c index f3527c0a4a..8e44b1a141 100644 --- a/platform/network/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -52,7 +52,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Platform clock include. */ #include "platform/iot_clock.h" diff --git a/platform/network/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c similarity index 100% rename from platform/network/iot_network_metrics.c rename to ports/common/src/iot_network_metrics.c diff --git a/platform/ports/macos/types/iot_platform_types_macos.h b/ports/macos/include/iot_platform_types_macos.h similarity index 100% rename from platform/ports/macos/types/iot_platform_types_macos.h rename to ports/macos/include/iot_platform_types_macos.h diff --git a/ports/macos/macos.cmake b/ports/macos/macos.cmake new file mode 100644 index 0000000000..a6e5435b3d --- /dev/null +++ b/ports/macos/macos.cmake @@ -0,0 +1,24 @@ +# Check for POSIX threads. +find_package( Threads REQUIRED ) + +if( NOT ${CMAKE_USE_PTHREADS_INIT} ) + message( FATAL_ERROR "POSIX threads required." ) +endif() + +# Add the network header for this platform. +list( APPEND PLATFORM_COMMON_HEADERS + ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) + +# Platform library source files. +set( PLATFORM_SOURCES + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c + ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ) + +# Set the types header for this port. +set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) + +# Set the dependencies required by this port. +# On macOS, mbed TLS and POSIX threads are used. +set( PLATFORM_DEPENDENCIES mbedtls Threads::Threads ) diff --git a/platform/ports/macos/source/iot_clock_macos.c b/ports/macos/src/iot_clock_macos.c similarity index 100% rename from platform/ports/macos/source/iot_clock_macos.c rename to ports/macos/src/iot_clock_macos.c diff --git a/platform/ports/macos/source/iot_threads_macos.c b/ports/macos/src/iot_threads_macos.c similarity index 99% rename from platform/ports/macos/source/iot_threads_macos.c rename to ports/macos/src/iot_threads_macos.c index c67e64ea0e..7096e0c166 100644 --- a/platform/ports/macos/source/iot_threads_macos.c +++ b/ports/macos/src/iot_threads_macos.c @@ -31,7 +31,7 @@ #include "platform/iot_threads.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Atomic include. */ #include "iot_atomic.h" @@ -357,7 +357,7 @@ bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, { /* Unused parameter. */ ( void ) maxValue; - + bool status = true; /* Create a GCD semaphore. */ diff --git a/platform/ports/posix/types/iot_platform_types_posix.h b/ports/posix/include/iot_platform_types_posix.h similarity index 100% rename from platform/ports/posix/types/iot_platform_types_posix.h rename to ports/posix/include/iot_platform_types_posix.h diff --git a/platform/ports/posix/CMakeLists.txt b/ports/posix/posix.cmake similarity index 64% rename from platform/ports/posix/CMakeLists.txt rename to ports/posix/posix.cmake index 2312e4daea..6c55f4b928 100644 --- a/platform/ports/posix/CMakeLists.txt +++ b/ports/posix/posix.cmake @@ -61,38 +61,31 @@ if( ${IOT_NETWORK_USE_OPENSSL} ) endif() # Choose OpenSSL network source file. - set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/iot_network_openssl.h ) - set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/ports/posix/source/iot_network_openssl.c ) + set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_openssl.h ) + set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_network_openssl.c ) # Link OpenSSL. - set( TLS_LIBRARY_LINKER_FLAG OpenSSL::SSL OpenSSL::Crypto ) + set( PLATFORM_DEPENDENCIES OpenSSL::SSL OpenSSL::Crypto ) endif() else() - set( NETWORK_HEADER ${CMAKE_SOURCE_DIR}/platform/include/iot_network_mbedtls.h ) - set( NETWORK_SOURCE_FILE ${CMAKE_SOURCE_DIR}/platform/network/iot_network_mbedtls.c ) - set( TLS_LIBRARY_LINKER_FLAG mbedtls ) + set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_openssl.h ) + set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c ) + set( PLATFORM_DEPENDENCIES mbedtls ) endif() # Add the network header for this platform. -set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} - ${NETWORK_HEADER} - PARENT_SCOPE ) +list( APPEND PLATFORM_COMMON_HEADERS + ${NETWORK_HEADER} ) # Platform libraries source files. set( PLATFORM_SOURCES - ${CMAKE_SOURCE_DIR}/platform/ports/posix/source/iot_clock_posix.c - ${CMAKE_SOURCE_DIR}/platform/ports/posix/source/iot_threads_posix.c - ${CMAKE_SOURCE_DIR}/platform/network/iot_network_metrics.c + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ${NETWORK_SOURCE_FILE} ) -# Add platform as an interface library. It will be built into iotbase. -add_library( iotplatform INTERFACE ) +# Set the types header for this port. +set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) -target_sources( iotplatform INTERFACE - ${PLATFORM_SOURCES} ) - -# Set the dependencies of this platform layer. -target_link_libraries( iotplatform INTERFACE Threads::Threads rt ${TLS_LIBRARY_LINKER_FLAG} ) - -# Set platform sources in the parent scope for directory organization. -set( PLATFORM_SOURCES ${PLATFORM_SOURCES} PARENT_SCOPE ) +# Link POSIX threads and real-time library. +set( PLATFORM_DEPENDENCIES ${PLATFORM_DEPENDENCIES} Threads::Threads rt ) diff --git a/platform/ports/posix/source/iot_clock_posix.c b/ports/posix/src/iot_clock_posix.c similarity index 100% rename from platform/ports/posix/source/iot_clock_posix.c rename to ports/posix/src/iot_clock_posix.c diff --git a/platform/ports/posix/source/iot_network_openssl.c b/ports/posix/src/iot_network_openssl.c similarity index 99% rename from platform/ports/posix/source/iot_network_openssl.c rename to ports/posix/src/iot_network_openssl.c index 9e8516eefc..39ce7144e0 100644 --- a/platform/ports/posix/source/iot_network_openssl.c +++ b/ports/posix/src/iot_network_openssl.c @@ -58,7 +58,7 @@ #include "platform/iot_threads.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_NETWORK diff --git a/platform/ports/posix/source/iot_threads_posix.c b/ports/posix/src/iot_threads_posix.c similarity index 99% rename from platform/ports/posix/source/iot_threads_posix.c rename to ports/posix/src/iot_threads_posix.c index 688481d369..90e38c6ca7 100644 --- a/platform/ports/posix/source/iot_threads_posix.c +++ b/ports/posix/src/iot_threads_posix.c @@ -56,7 +56,7 @@ #include "platform/iot_threads.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM diff --git a/platform/ports/template/types/iot_platform_types_template.h b/ports/template/include/iot_platform_types_template.h similarity index 100% rename from platform/ports/template/types/iot_platform_types_template.h rename to ports/template/include/iot_platform_types_template.h diff --git a/platform/ports/template/source/iot_clock_template.c b/ports/template/src/iot_clock_template.c similarity index 100% rename from platform/ports/template/source/iot_clock_template.c rename to ports/template/src/iot_clock_template.c diff --git a/platform/ports/template/source/iot_threads_template.c b/ports/template/src/iot_threads_template.c similarity index 100% rename from platform/ports/template/source/iot_threads_template.c rename to ports/template/src/iot_threads_template.c diff --git a/ports/template/template.cmake b/ports/template/template.cmake new file mode 100644 index 0000000000..15e97bed11 --- /dev/null +++ b/ports/template/template.cmake @@ -0,0 +1,28 @@ +# This CMakeLists is a template for new ports. It provides the minimal +# configuration for building, but nothing more. + +# Warn that the template port only builds. It will not create usable libraries. +message( WARNING "This is a template port that contains only stubs. Libraries built with this port will not work!") + +# Add the mbed TLS header in the template. The mbed TLS network port is supposed +# to be platform-independent, so it is built in the template. +set( PLATFORM_COMMON_HEADERS ${PLATFORM_COMMON_HEADERS} + ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) + +# Template platform sources. Except for the network sources, these files contain +# only stubs. +set( PLATFORM_SOURCES + # Stubs + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c + + # Network sources + ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c + ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ) + +# Set the types header for this port. +set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) + +# Set the dependencies required by this port. +# As this template uses the mbed TLS network implementation, mbed TLS is linked. +set( PLATFORM_DEPENDENCIES mbedtls ) diff --git a/platform/ports/win32/types/iot_platform_types_win32.h b/ports/win32/include/iot_platform_types_win32.h similarity index 100% rename from platform/ports/win32/types/iot_platform_types_win32.h rename to ports/win32/include/iot_platform_types_win32.h diff --git a/platform/ports/win32/source/iot_clock_win32.c b/ports/win32/src/iot_clock_win32.c similarity index 100% rename from platform/ports/win32/source/iot_clock_win32.c rename to ports/win32/src/iot_clock_win32.c diff --git a/platform/ports/win32/source/iot_threads_win32.c b/ports/win32/src/iot_threads_win32.c similarity index 99% rename from platform/ports/win32/source/iot_threads_win32.c rename to ports/win32/src/iot_threads_win32.c index edbde1e173..64698fdcc1 100644 --- a/platform/ports/win32/source/iot_threads_win32.c +++ b/ports/win32/src/iot_threads_win32.c @@ -37,7 +37,7 @@ #include "iot_atomic.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Configure logs for the functions in this file. */ #ifdef IOT_LOG_LEVEL_PLATFORM diff --git a/ports/win32/win32.cmake b/ports/win32/win32.cmake new file mode 100644 index 0000000000..7da483c71c --- /dev/null +++ b/ports/win32/win32.cmake @@ -0,0 +1,17 @@ +# Add the network header for this platform. +list( APPEND PLATFORM_COMMON_HEADERS + ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) + +# Platform library source files. +set( PLATFORM_SOURCES + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c + ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c + ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ) + +# Set the types header for this port. +set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) + +# Set the dependencies required by this port. +# On Windows, mbed TLS and Winsock are used. +set( PLATFORM_DEPENDENCIES mbedtls ws2_32 ) diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index 01e0a86311..530076398b 100755 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -5,45 +5,59 @@ # Exit on any nonzero return code. set -e -CMAKE_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" " +CMAKE_FLAGS="-DIOT_DEMO_MQTT_TOPIC_PREFIX=\"\\\"$IOT_IDENTIFIER\\\"\" $COMPILER_OPTIONS" TEST_OPTIONS="" -# For pull request builds, run against test.mosquitto.org. Otherwise, run against AWS IoT. -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - CMAKE_FLAGS+="$AWS_IOT_CREDENTIAL_DEFINES $COMPILER_OPTIONS" - DEMO_OPTIONS="-i $IOT_IDENTIFIER" -elif [ "$TRAVIS_OS_NAME" = "windows" ]; then - CMAKE_FLAGS+=$COMPILER_OPTIONS - TEST_OPTIONS+="-n" +DEMO_OPTIONS="-i $IOT_IDENTIFIER" + +# For pull request builds on Linux, run against a local Mosquitto broker. +# +# On macOS and Windows, a local Mosquitto broker is not available. Only tests +# that do not use the network should run on these platforms. +# +# For commit builds all platforms will test against AWS IoT. +if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + if [ "$TRAVIS_OS_NAME" = "linux" ]; then + # Set the flags and options for a local Mosquitto broker on Linux. + CMAKE_FLAGS+=" -DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\"" + DEMO_OPTIONS+=" -n -u -h localhost -p 1883" + else + # Specify that the tests should not use the network on macOS and Windows. + TEST_OPTIONS="-n" + fi else - CMAKE_FLAGS+="-DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\" $COMPILER_OPTIONS" - DEMO_OPTIONS="-n -i $IOT_IDENTIFIER -u -h localhost -p 1883" + # Set credentials for AWS IoT. + CMAKE_FLAGS+=" $AWS_IOT_CREDENTIAL_DEFINES" fi -# Build executables. +# Build and run executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -if [ "$TRAVIS_OS_NAME" != "windows" ]; then - make -j2 iot_tests_mqtt iot_demo_mqtt + +if [ "$TRAVIS_OS_NAME" = "windows" ]; then + MSBuild.exe libraries/standard/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal + MSBuild.exe demos/iot_demo_mqtt.vcxproj -m -clp:summary -verbosity:minimal + + ./bin/Debug/iot_tests_mqtt.exe $TEST_OPTIONS else - MSBuild.exe tests/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal - MSBuild.exe demos/app/iot_demo_mqtt.vcxproj -m -clp:summary -verbosity:minimal - mv ./bin/Debug/* ./bin/ -fi + make -j2 iot_tests_mqtt iot_demo_mqtt + + ./bin/iot_tests_mqtt $TEST_OPTIONS -# Run tests and demos. -./bin/iot_tests_mqtt $TEST_OPTIONS -if [ "$TRAVIS_OS_NAME" != "windows" ] || [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - ./bin/iot_demo_mqtt $DEMO_OPTIONS + if [ "$TRAVIS_OS_NAME" = "linux" ]; then + ./bin/iot_demo_mqtt $DEMO_OPTIONS + fi fi -# Rebuild in static memory mode. +# Rebuild and run tests in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -if [ "$TRAVIS_OS_NAME" != "windows" ]; then - make -j2 iot_tests_mqtt + +if [ "$TRAVIS_OS_NAME" = "windows" ]; then + MSBuild.exe libraries/standard/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal + MSBuild.exe demos/iot_demo_mqtt.vcxproj -m -clp:summary -verbosity:minimal + + ./bin/Debug/iot_tests_mqtt.exe $TEST_OPTIONS else - MSBuild.exe tests/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal - mv ./bin/Debug/* ./bin/ -fi + make -j2 iot_tests_mqtt iot_demo_mqtt -# Run tests in static memory mode. -./bin/iot_tests_mqtt $TEST_OPTIONS + ./bin/iot_tests_mqtt $TEST_OPTIONS +fi diff --git a/scripts/setup/ci_setup_osx.sh b/scripts/setup/ci_setup_osx.sh index b9b8acccb1..d0926ec91f 100644 --- a/scripts/setup/ci_setup_osx.sh +++ b/scripts/setup/ci_setup_osx.sh @@ -2,11 +2,5 @@ # Travis CI uses this script to set up the test environment on macOS. -# Install and start Mosquitto for MQTT pull request builds. -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - brew install mosquitto - brew services start mosquitto -fi - # Set compiler options for macOS. Silence field initializer warnings. export COMPILER_OPTIONS="-Wall -Wextra -Wno-missing-field-initializers" diff --git a/scripts/setup/ci_setup_windows.sh b/scripts/setup/ci_setup_windows.sh index 1dc3a0dd71..0f2e311e75 100644 --- a/scripts/setup/ci_setup_windows.sh +++ b/scripts/setup/ci_setup_windows.sh @@ -2,9 +2,9 @@ # Travis CI uses this script to set up the test environment on Windows. -# Location of MSBuild.exe in the test environment +# Location of MSBuild.exe in the Travis CI test environment. MSBUILD_PATH="/c/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin" export PATH=$PATH:$MSBUILD_PATH -# Use default compiler options for windows. +# Use default compiler options for MSVC. export COMPILER_OPTIONS="" diff --git a/tests/README.md b/tests/README.md index 7a9a22db22..409789a5d6 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,11 +1,7 @@ # Test programs -This directory contains source files for test executables. Its subdirectories are organized as follows: -- `common`, `defender`, `...`
- Platform-independent test sources for individual libraries. +This directory contains source files for the test executables. The test executable source (i.e. the `main()` function) is `iot_tests.c` and the configuration header used when building the tests is `iot_config.h`. The configuration header is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. -The test executable source (i.e. the `main()` function) is `iot_tests.c`. - -The configuration file for the tests, `iot_config.h`, is in this directory. This file is intended to hold all configuration for the tests and libraries. See [Global configuration](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/global_config.html) for settings that affect all tests and libraries; see each library's documentation for library-specific settings. +The test code sources are placed with their libraries and not in this directory. For example, the sources for the MQTT tests are in `libraries/standard/mqtt/test`. For information on building and running the tests, see [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html). diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt deleted file mode 100644 index f164e6da18..0000000000 --- a/tests/common/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Common unit test sources. -set( COMMON_UNIT_TEST_SOURCES - unit/iot_tests_linear_containers.c - unit/iot_tests_taskpool.c - unit/iot_tests_atomic.c ) - -# Common tests executable. -add_executable( iot_tests_common - ${COMMON_UNIT_TEST_SOURCES} - iot_tests_common.c - ${IOT_TEST_APP_FILES} ) - -# Define the test to run. -target_compile_definitions( iot_tests_common PRIVATE - -DRunTests=RunCommonTests ) - -# Common tests library dependencies. -target_link_libraries( iot_tests_common PRIVATE iotbase unity ) - -# Organization of common tests in folders. -set_property( TARGET iot_tests_common PROPERTY FOLDER "tests" ) -source_group( unit FILES ${COMMON_UNIT_TEST_SOURCES} ) -source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_common.c ) diff --git a/tests/defender/CMakeLists.txt b/tests/defender/CMakeLists.txt deleted file mode 100644 index 62ae070148..0000000000 --- a/tests/defender/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Defender system test sources. -set( DEFENDER_SYSTEM_TEST_SOURCES - system/aws_iot_tests_defender_system.c ) - -# Defender tests executable. -add_executable( aws_iot_tests_defender - ${DEFENDER_SYSTEM_TEST_SOURCES} - aws_iot_tests_defender.c - ${IOT_TEST_APP_FILES} ) - -# Define the test to run. -target_compile_definitions( aws_iot_tests_defender PRIVATE - -DRunTests=RunDefenderTests ) - -# Defender tests library dependencies. -target_link_libraries( aws_iot_tests_defender PRIVATE awsiotdefender tinycbor unity ) - -# Organization of Defender tests in folders. -set_property( TARGET aws_iot_tests_defender PROPERTY FOLDER "tests" ) -source_group( system FILES ${DEFENDER_SYSTEM_TEST_SOURCES} ) -source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_defender.c ) diff --git a/tests/iot_tests.c b/tests/iot_tests.c index bfd2ae6cc6..2ce52fab2c 100644 --- a/tests/iot_tests.c +++ b/tests/iot_tests.c @@ -31,7 +31,7 @@ #include /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Platform threads include. */ #include "platform/iot_threads.h" diff --git a/tests/jobs/CMakeLists.txt b/tests/jobs/CMakeLists.txt deleted file mode 100644 index f7d17ed596..0000000000 --- a/tests/jobs/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -# Jobs unit test sources. -set( JOBS_UNIT_TEST_SOURCES - unit/aws_iot_tests_jobs_api.c - unit/aws_iot_tests_jobs_serialize.c ) - -# Jobs tests executable. -add_executable( aws_iot_tests_jobs - system/aws_iot_tests_jobs_system.c - ${JOBS_UNIT_TEST_SOURCES} - aws_iot_tests_jobs.c - ${IOT_TEST_APP_FILES} - ${MQTT_MOCK_SOURCES} ) - -# Include directory for MQTT mock. -target_include_directories( aws_iot_tests_jobs PRIVATE - ${PROJECT_SOURCE_DIR}/tests/mqtt/mock ) - -# Define the test to run. -target_compile_definitions( aws_iot_tests_jobs PRIVATE - -DRunTests=RunJobsTests ) - -# Jobs tests library dependencies. -target_link_libraries( aws_iot_tests_jobs PRIVATE awsiotjobs unity ) - -# Organization of Jobs tests in folders. -set_property( TARGET aws_iot_tests_jobs PROPERTY FOLDER "tests" ) -source_group( system FILES system/aws_iot_tests_jobs_system.c ) -source_group( unit FILES ${JOBS_UNIT_TEST_SOURCES} ) -source_group( mock FILES ${MQTT_MOCK_SOURCES} ) -source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_jobs.c ) diff --git a/tests/mqtt/CMakeLists.txt b/tests/mqtt/CMakeLists.txt deleted file mode 100644 index e1d8e35bc2..0000000000 --- a/tests/mqtt/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -# MQTT system test sources. -set( MQTT_SYSTEM_TEST_SOURCES - system/iot_tests_mqtt_system.c ) - -# MQTT unit test sources. -set( MQTT_UNIT_TEST_SOURCES - unit/iot_tests_mqtt_api.c - unit/iot_tests_mqtt_receive.c - unit/iot_tests_mqtt_subscription.c - unit/iot_tests_mqtt_validate.c ) - -# MQTT tests executable. -add_executable( iot_tests_mqtt - ${MQTT_SYSTEM_TEST_SOURCES} - ${MQTT_UNIT_TEST_SOURCES} - iot_tests_mqtt.c - ${IOT_TEST_APP_FILES} - ${MQTT_MOCK_SOURCES} ) - -# Include directory for MQTT mock. -target_include_directories( iot_tests_mqtt PRIVATE - ${PROJECT_SOURCE_DIR}/tests/mqtt/mock ) - -# Define the test to run. -target_compile_definitions( iot_tests_mqtt PRIVATE - -DRunTests=RunMqttTests ) - -# MQTT tests library dependencies. -target_link_libraries( iot_tests_mqtt PRIVATE iotmqtt unity ) - -# Organization of MQTT tests in folders. -set_property( TARGET iot_tests_mqtt PROPERTY FOLDER "tests" ) -source_group( system FILES ${MQTT_SYSTEM_TEST_SOURCES} ) -source_group( unit FILES ${MQTT_UNIT_TEST_SOURCES} ) -source_group( mock FILES ${MQTT_MOCK_SOURCES} ) -source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_mqtt.c ) diff --git a/tests/serializer/CMakeLists.txt b/tests/serializer/CMakeLists.txt deleted file mode 100644 index 797394ec12..0000000000 --- a/tests/serializer/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Serializer unit test sources. -set( SERIALIZER_UNIT_TEST_SOURCES - unit/iot_tests_serializer_cbor.c ) - -# Serializer tests executable. -add_executable( iot_tests_serializer - ${SERIALIZER_UNIT_TEST_SOURCES} - iot_tests_serializer.c - ${IOT_TEST_APP_FILES} ) - -# Define the test to run. -target_compile_definitions( iot_tests_serializer PRIVATE - -DRunTests=RunSerializerTests ) - -# Serializer tests library dependencies. -target_link_libraries( iot_tests_serializer PRIVATE iotserializer iotbase unity tinycbor ) - -# Organization of Serializer tests in folders. -set_property( TARGET iot_tests_serializer PROPERTY FOLDER "tests" ) -source_group( unit FILES ${SERIALIZER_UNIT_TEST_SOURCES} ) -source_group( "" FILES ${IOT_TEST_APP_FILES} iot_tests_serializer.c ) diff --git a/tests/shadow/CMakeLists.txt b/tests/shadow/CMakeLists.txt deleted file mode 100644 index 1add87e10e..0000000000 --- a/tests/shadow/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# Shadow system test sources. -set( SHADOW_SYSTEM_TEST_SOURCES - system/aws_iot_tests_shadow_system.c ) - -# Shadow unit test sources. -set( SHADOW_UNIT_TEST_SOURCES - unit/aws_iot_tests_shadow_api.c - unit/aws_iot_tests_shadow_parser.c ) - -# Shadow tests executable. -add_executable( aws_iot_tests_shadow - ${SHADOW_SYSTEM_TEST_SOURCES} - ${SHADOW_UNIT_TEST_SOURCES} - aws_iot_tests_shadow.c - ${IOT_TEST_APP_FILES} - ${MQTT_MOCK_SOURCES} ) - -# Include directory for MQTT mock. -target_include_directories( aws_iot_tests_shadow PRIVATE - ${PROJECT_SOURCE_DIR}/tests/mqtt/mock ) - -# Define the test to run. -target_compile_definitions( aws_iot_tests_shadow PRIVATE - -DRunTests=RunShadowTests ) - -# Shadow tests library dependencies. -target_link_libraries( aws_iot_tests_shadow PRIVATE awsiotshadow unity ) - -# Organization of Shadow tests in folders. -set_property( TARGET aws_iot_tests_shadow PROPERTY FOLDER "tests" ) -source_group( system FILES ${SHADOW_SYSTEM_TEST_SOURCES} ) -source_group( unit FILES ${SHADOW_UNIT_TEST_SOURCES} ) -source_group( mock FILES ${MQTT_MOCK_SOURCES} ) -source_group( "" FILES ${IOT_TEST_APP_FILES} aws_iot_tests_shadow.c ) diff --git a/third_party/mbedtls/CMakeLists.txt b/third_party/mbedtls/CMakeLists.txt index 80db7ceae4..14158457af 100644 --- a/third_party/mbedtls/CMakeLists.txt +++ b/third_party/mbedtls/CMakeLists.txt @@ -68,11 +68,16 @@ add_library( mbedtls # mbed TLS config header and include directories. target_include_directories( mbedtls SYSTEM - PUBLIC ${PROJECT_SOURCE_DIR}/third_party/mbedtls - PUBLIC ${PROJECT_SOURCE_DIR}/third_party/mbedtls/mbedtls/include ) + PUBLIC mbedtls/include . ) target_compile_definitions( mbedtls PUBLIC -DMBEDTLS_CONFIG_FILE="iot_config_mbedtls.h" ) +# The system types header is needed for the mbed TLS threading port. +target_include_directories( mbedtls + PRIVATE ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME}/include ) +target_compile_definitions( mbedtls + PRIVATE -DIOT_SYSTEM_TYPES_FILE="iot_platform_types_${IOT_PLATFORM_NAME}.h" ) + # Link the Unity test framework when testing. if( ${IOT_BUILD_TESTS} ) target_link_libraries( mbedtls PRIVATE unity ) @@ -87,7 +92,7 @@ target_compile_options( mbedtls /W0 /D_CRT_SECURE_NO_WARNINGS> ) # Organization of mbed TLS in folders. -set_property( TARGET mbedtls PROPERTY FOLDER "third_party" ) +set_property( TARGET mbedtls PROPERTY FOLDER third_party ) source_group( include\\mbedtls FILES ${MBEDTLS_HEADERS} ) source_group( source FILES ${MBEDTLS_SOURCES} ) source_group( "" FILES iot_config_mbedtls.h threading_alt.h iot_mbedtls_threading.c ) diff --git a/third_party/mbedtls/iot_config_mbedtls.h b/third_party/mbedtls/iot_config_mbedtls.h index d56130997f..fa9fb0c805 100644 --- a/third_party/mbedtls/iot_config_mbedtls.h +++ b/third_party/mbedtls/iot_config_mbedtls.h @@ -24,9 +24,6 @@ #ifndef IOT_CONFIG_MBEDTLS_H_ #define IOT_CONFIG_MBEDTLS_H_ -/* The config header is always included first. */ -#include "iot_config.h" - /* System support for assembly and time. */ #define MBEDTLS_HAVE_ASM #define MBEDTLS_HAVE_TIME diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 371cef3cf3..d44691a8e5 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -36,7 +36,7 @@ add_library( tinycbor # TinyCBOR include path. target_include_directories( tinycbor SYSTEM - PUBLIC ${PROJECT_SOURCE_DIR}/third_party/tinycbor/tinycbor/src ) + PUBLIC tinycbor/src ) # Link math library. This is not needed on Windows. if( NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) @@ -52,6 +52,6 @@ target_compile_options( tinycbor /W0 /D_CRT_SECURE_NO_WARNINGS> ) # Organization of TinyCBOR in folders. -set_property( TARGET tinycbor PROPERTY FOLDER "third_party" ) +set_property( TARGET tinycbor PROPERTY FOLDER third_party ) source_group( include FILES ${TINYCBOR_HEADERS} ) source_group( source FILES ${TINYCBOR_SOURCES} ) diff --git a/third_party/unity/CMakeLists.txt b/third_party/unity/CMakeLists.txt index 248e8a75eb..a7ef3c59c4 100644 --- a/third_party/unity/CMakeLists.txt +++ b/third_party/unity/CMakeLists.txt @@ -18,8 +18,8 @@ add_library( unity # Unity test framework include paths. target_include_directories( unity SYSTEM - PUBLIC ${PROJECT_SOURCE_DIR}/third_party/unity/unity - PUBLIC ${PROJECT_SOURCE_DIR}/third_party/unity/unity/fixture ) + PUBLIC unity + PUBLIC unity/fixture ) # Disable all warnings for this third-party library. target_compile_options( unity @@ -30,6 +30,6 @@ target_compile_options( unity /W0 /D_CRT_SECURE_NO_WARNINGS> ) # Organization of Unity in folders. -set_property( TARGET unity PROPERTY FOLDER "third_party" ) +set_property( TARGET unity PROPERTY FOLDER third_party ) source_group( include FILES ${UNITY_HEADERS} ) source_group( source FILES ${UNITY_SOURCES} ) From 2fbe8e3e848f760d15eb4e91c616daf9e53db31f Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Mon, 7 Oct 2019 19:47:41 -0400 Subject: [PATCH 282/844] Documentation: Update porting guide (#588) --- doc/guide/developer/porting.txt | 85 ++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/doc/guide/developer/porting.txt b/doc/guide/developer/porting.txt index afc645b231..54d723d9de 100644 --- a/doc/guide/developer/porting.txt +++ b/doc/guide/developer/porting.txt @@ -17,7 +17,7 @@ In general, this SDK should build with C compilers in C99 mode. Currently, we do @subsection guide_developer_porting_directory_layout Directory layout @brief The following directories contain the SDK's source code and are relevant for porting. -Of the directories listed below, only `platform/` should be modified during porting. Empty templates of a new port are placed in `platform/ports/template`. Some directories present in the GitHub repo (such as `cbmc`, `doc`, and `scripts`) are not relevant for porting and therefore not listed. +Of the directories listed below, only `ports/` should be modified during porting. Empty templates of a new port are placed in `ports/template`. Some directories present in the GitHub repo (such as `cbmc`, `doc`, and `scripts`) are not relevant for porting and therefore not listed. As relative paths from the SDK's root directory: - `demos/`
@@ -26,42 +26,48 @@ As relative paths from the SDK's root directory: Contains demo applications for various systems, most importantly the `main()` functions. - `include/`
Headers only needed to build the demos. These do not need to be ported. - - `source/`
+ - `src/`
Platform-independent demo sources. These do not need to be ported. - `iot_config.h`
The config header for the demo applications. Useful as an example. -- `lib/`
+- `libraries/`
Platform-independent SDK files. This directory (and all its subdirectories) should be copied and not modified. - - `include/`
- Library headers. - - `platform/`
- Interface of the platform layer, to be implemented in the `platform/` directory.
- - `private/`
- Library internal headers. + - `aws/`
+ Client libraries for AWS IoT services. Individual library headers, source files, and tests are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. + - `defender/`
+ Defender library headers, source files and tests. + - `include`
+ Defender library header files. + - `src`
+ Defender library source files. + - `test`
+ Defender library tests. + - `common, jobs, shadow, ...`
+ Other libraries have the same directory structure as the Defender library described above. + - `platform/`
+ Interface of the platform layer, to be implemented in the `ports/` directory.
- `types/`
- Library types headers. - - `source/`
- Library source files. - - `common, defender, mqtt, ...`
- Individual library sources are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. -- `platform/`
- Platform-dependent SDK files that must be ported. The existing ports in this directory may be useful examples.
- - `include/`
- Platform layer headers that are not specific to a specific port, such as the atomic and network headers. - - `atomic/`
- Existing ports of atomic operations. See @ref platform_atomic for how to create a new port. New atomic ports should be placed here.
- - `network/`
- Implementations of the platform network layer that are not specific to a port. - - `ports`
- Existing ports that can be used as a reference. New ports should also be placed here.
- - `posix, win32, ...`
- Port source and headers are placed in a directory matching the port name. - - `template`
- Empty port with stubbed out functions. This should be the starting point of a new port.
+ Platform types header. + - `standard`
+ Libraries which are not specific to AWS IoT services. Headers, source files, and tests are contained in directories matching the library name. All source files in the same directory should be built into a shared or static library. + - `common, mqtt, serializer, ...`
+ These libraries have the same directory structure as the Defender library described above. +- `ports/`
+ This directory contains the desktop OS ports. Each port implements the functions of the platform layer interface for a specific desktop OS.
+ - `common`
+ Port files that are not specific to a single port. These headers are used across all of the desktop OS ports. + - `include/`
+ Port headers that are not specific to a single port, such as the atomic and network headers. + - `atomic/`
+ Implementations of atomic operations. See @ref platform_atomic for how to create a new port. New atomic ports should be placed here.
+ - `src/`
+ Port sources that are not specific to a single port, such as the network implementations. + - `template`
+ Empty port sources containing stubbed-out functions. The files in this directory may be used as a starting point for a new port.
+ - `macos, posix, win32, ...`
+ Port sources and headers for a single implementation. The directory is named after the target OS.
- `tests/`
- SDK tests that can be used to verify ports. - - `common, defender, mqtt, ...`
- Individual library tests are contained in directories matching the library name. When building tests, @ref IOT_BUILD_TESTS should be set to `1` globally. + SDK test config file and test runner source. Individual library tests are in each library directory. When building tests, @ref IOT_BUILD_TESTS should be set to `1` globally. - `third_party/`
Third-party library code. This directory (and all its subdirectories) should be copied and not modified. - `mbedtls/`
@@ -78,9 +84,10 @@ SDK include paths that are always required: - The path of the [config file](@ref global_config), `iot_config.h`. For example: - In the SDK demos, `iot_config.h` is in `demos/`. - In the SDK tests, `iot_config.h` is in `tests/`. -- The path of the port types file. For example, `platform/ports/posix/types` when using the POSIX port. -- `lib/include` -- `platform/include` +- The path of the port types file. For example, `ports/posix/include` when using the POSIX port. +- `libraries/platform/` + +Each library has its headers in a different `include` directory. Library include directories are in `libraries/aws` for AWS IoT client libraries, and `libraries/standard` for other libraries. For example, the MQTT library's include directory is `libraries/standard/mqtt/include/` and the AWS IoT Shadow library's include directory is `libraries/aws/shadow/include/`. Each library's include directory must be added to the include path if that library is being used. Third-party submodule include paths: - TinyCBOR: `third_party/tinycbor/tinycbor/src`
@@ -94,8 +101,8 @@ Additional include paths required to build the demos: Additional include paths required to build the tests: - `third_party/unity/unity` - `third_party/unity/unity/fixture` -- `tests/mqtt/access` (when building the MQTT or Shadow tests) -- `tests/mqtt/mock` (when building tests that use the MQTT mocks, such as Shadow or Jobs) +- `libraries/standard/mqtt/test/access` (when building the MQTT or Shadow tests) +- `libraries/standard/mqtt/test/mock` (when building tests that use the MQTT mocks, such as Shadow or Jobs) In addition to the tests include paths, @ref IOT_BUILD_TESTS should be set to `1` globally when building tests. @@ -120,7 +127,7 @@ The platform types must also be set. See @ref guide_developer_porting_platform f @brief [Types](@ref platform_datatypes_handles) that must be set in the platform layer. @see [Platform types](@ref platform_datatypes_handles) for a list of all types that must be set.
-`platform/ports/posix/types/iot_platform_types_posix.h` for an example of setting the types on POSIX systems. +`ports/posix/include/iot_platform_types_posix.h` for an example of setting the types on POSIX systems. The platform types should be set in the [config file](@ref global_config), `iot_config.h`, or in another header included by the config file. As an example, the header `iot_platform_types_posix.h` sets the platform types to the matching POSIX system types. This header is then included in `iot_config.h` by the provided CMake build system. @@ -130,6 +137,6 @@ The platform types should be set in the [config file](@ref global_config), `iot_ @brief [Functions](@ref platform_functions) that must be implemented for the platform layer. @see [Platform functions](@ref platform_functions) for a list of all functions that must be implemented.
-`lib/include/platform` for the interfaces of the platform functions.
-`platform/ports/posix/source` for examples of functions implemented for POSIX systems. +`libraries/platform` for the interfaces of the platform functions.
+`ports/posix/src` for examples of functions implemented for POSIX systems. */ From 5dc21a0dcc771295b8dd8b9263b283b86580f0c0 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Mon, 7 Oct 2019 16:58:29 -0700 Subject: [PATCH 283/844] fix: Re-architect Defender Library. (#583) --- demos/src/aws_iot_demo_defender.c | 307 +++++++------ .../aws/defender/include/aws_iot_defender.h | 48 +- .../aws/defender/src/aws_iot_defender_api.c | 413 ++++++++++-------- .../aws/defender/src/aws_iot_defender_mqtt.c | 74 ++-- .../src/private/aws_iot_defender_internal.h | 14 +- .../system/aws_iot_tests_defender_system.c | 164 +++++-- .../jobs/test/unit/aws_iot_tests_jobs_api.c | 2 +- tests/iot_config.h | 5 + 8 files changed, 620 insertions(+), 407 deletions(-) diff --git a/demos/src/aws_iot_demo_defender.c b/demos/src/aws_iot_demo_defender.c index 385723948d..7a665ed4a5 100644 --- a/demos/src/aws_iot_demo_defender.c +++ b/demos/src/aws_iot_demo_defender.c @@ -41,35 +41,145 @@ #include "iot_network_metrics.h" /** - * @brief Runs the defender demo. + * @brief The keep-alive interval used for this demo. * - * @return AWS_IOT_DEFENDER_SUCCESS on success, otherwise an error code indicating - * the cause of error. + * An MQTT ping request will be sent periodically at this interval. */ -static AwsIotDefenderError_t _defenderDemo( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ); +#define KEEP_ALIVE_SECONDS ( 60 ) /** - * @brief Starts the defender agent. - * - * @return AWS_IOT_DEFENDER_SUCCESS on success, otherwise an error code indicating - * the cause of error. + * @brief The timeout for Defender and MQTT operations in this demo. */ -static AwsIotDefenderError_t _startDefender( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ); +#define TIMEOUT_MS ( 5000 ) + +/*-----------------------------------------------------------*/ /** * @brief Callback used to get notification of defender's events. + * + * @param[in] pCallbackContext context pointer passed by the application + * when callback is regiested in AwsIotDefender_Start() + * + * @param[im] pointer to AwsIotDefenderCallbackInfo_t containing status of + * publish + */ +void _defenderCallback( void * pCallbackContext, + AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) +{ + ( void ) pCallbackContext; + + IotLogInfo( "User's callback is invoked on event: %s.", AwsIotDefender_EventType( pCallbackInfo->eventType ) ); + + /* Callback info processing example . */ + if( pCallbackInfo->pMetricsReport != NULL ) + { + IotLogInfo( "Published metrics report." ); + } + else + { + IotLogError( "No metrics report was generated." ); + } + + if( pCallbackInfo->pPayload != NULL ) + { + IotLogInfo( "Received MQTT message." ); + } + else + { + IotLogError( "No message has been returned from subscribed topic." ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a new connection to the MQTT server for the Defender demo. + * + * @param[in] pIdentifier NULL-terminated MQTT client identifier. The Defender + * demo will use the Thing Name as the client identifier. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkInterface The network interface to use for this demo. + * @param[out] pMqttConnection Set to the handle to the new MQTT connection. + * + * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` + * otherwise. */ -static void _defenderCallback( void * param1, - AwsIotDefenderCallbackInfo_t * const pCallbackInfo ); +static int _establishMqttConnection( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface, + IotMqttConnection_t * pMqttConnection ) +{ + int status = EXIT_SUCCESS; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + if( pIdentifier == NULL ) + { + IotLogError( "Defender Thing Name must be provided." ); + + status = EXIT_FAILURE; + } + + if( status == EXIT_SUCCESS ) + { + /* Set the members of the network info not set by the initializer. This + * struct provided information on the transport layer to the MQTT connection. */ + networkInfo.createNetworkConnection = true; + networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.pNetworkInterface = pNetworkInterface; + + /* Set the members of the connection info not set by the initializer. */ + connectInfo.awsIotMqttMode = true; + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; + + /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ + connectInfo.pClientIdentifier = pIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); + + IotLogInfo( "Defender Thing Name is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); + + /* Establish the MQTT connection. */ + connectStatus = IotMqtt_Connect( &networkInfo, + &connectInfo, + TIMEOUT_MS, + pMqttConnection ); + + if( connectStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "MQTT CONNECT returned error %s.", + IotMqtt_strerror( connectStatus ) ); + + status = EXIT_FAILURE; + } + } + + return status; +} /*-----------------------------------------------------------*/ +/** + * @brief The function that runs the Defender demo, called by the demo runner. + * + * @param[in] awsIotMqttMode Ignored for the Defender demo. + * @param[in] pIdentifier NULL-terminated Defender Thing Name. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection for Defender. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection for Defender. + * @param[in] pNetworkInterface The network interface to use for this demo. + * + * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. + */ int RunDefenderDemo( bool awsIotMqttMode, const char * pIdentifier, void * pNetworkServerInfo, @@ -78,12 +188,17 @@ int RunDefenderDemo( bool awsIotMqttMode, { int status = EXIT_SUCCESS; bool metricsInitStatus = false; - IotMqttError_t mqttInitStatus = IOT_MQTT_INIT_FAILED; - AwsIotDefenderError_t defenderResult; + IotMqttError_t mqttStatus = IOT_MQTT_INIT_FAILED; + AwsIotDefenderError_t defenderResult = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + AwsIotDefenderStartInfo_t startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + const AwsIotDefenderCallback_t callback = { .function = _defenderCallback, .pCallbackContext = NULL }; + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /* Unused parameters. */ ( void ) awsIotMqttMode; + IotLogInfo( "----Device Defender Demo Start----" ); + /* Check parameter(s). */ if( ( pIdentifier == NULL ) || ( pIdentifier[ 0 ] == '\0' ) ) { @@ -94,9 +209,9 @@ int RunDefenderDemo( bool awsIotMqttMode, if( status == EXIT_SUCCESS ) { /* Initialize the MQTT library. */ - mqttInitStatus = IotMqtt_Init(); + mqttStatus = IotMqtt_Init(); - if( mqttInitStatus != IOT_MQTT_SUCCESS ) + if( mqttStatus != IOT_MQTT_SUCCESS ) { IotLogError( "MQTT Initialization Failed." ); status = EXIT_FAILURE; @@ -117,132 +232,76 @@ int RunDefenderDemo( bool awsIotMqttMode, if( status == EXIT_SUCCESS ) { - /* Run the demo. */ - defenderResult = _defenderDemo( pIdentifier, pNetworkServerInfo, pNetworkCredentialInfo, pNetworkInterface ); + /* Specify all metrics in "tcp connections" group */ + defenderResult = + AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ); - if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + if( defenderResult != AWS_IOT_DEFENDER_SUCCESS ) { - status = EXIT_SUCCESS; + status = EXIT_FAILURE; } } - /* Cleanup. */ - if( metricsInitStatus ) - { - IotMetrics_Cleanup(); - } - - if( mqttInitStatus == IOT_MQTT_SUCCESS ) - { - IotMqtt_Cleanup(); - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void _defenderCallback( void * param1, - AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) -{ - ( void ) param1; - - IotLogInfo( "User's callback is invoked on event %d.", pCallbackInfo->eventType ); - - /* Print out the sent metrics report if there is. */ - if( pCallbackInfo->pMetricsReport != NULL ) - { - IotLogInfo( "Published metrics report." ); - } - else - { - IotLogError( "No metrics report was generated." ); - } - - if( pCallbackInfo->pPayload != NULL ) - { - IotLogInfo( "Received MQTT message." ); - } - else - { - IotLogError( "No message has been returned from subscribed topic." ); - } -} - -/*-----------------------------------------------------------*/ - -static AwsIotDefenderError_t _defenderDemo( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ) -{ - AwsIotDefenderError_t defenderResult; - - IotLogInfo( "----Device Defender Demo Start----" ); - - /* Specify all metrics in "tcp connections" group */ - defenderResult = - AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_ALL ); - - if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + if( status == EXIT_SUCCESS ) { /* Set metrics report period to 5 minutes(300 seconds) */ defenderResult = AwsIotDefender_SetPeriod( 300 ); + + if( defenderResult != AWS_IOT_DEFENDER_SUCCESS ) + { + status = EXIT_FAILURE; + } } - if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) + if( status == EXIT_SUCCESS ) { - /* Start the defender agent. */ - defenderResult = _startDefender( pIdentifier, pNetworkServerInfo, pNetworkCredentialInfo, pNetworkInterface ); + /* Create MQTT Connection */ + mqttStatus = _establishMqttConnection( pIdentifier, + pNetworkServerInfo, + pNetworkCredentialInfo, + pNetworkInterface, + &mqttConnection ); + + if( mqttStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "Failed to Create MQTT Connection:%d", IotMqtt_strerror( mqttStatus ) ); + IotMqtt_Cleanup(); + defenderResult = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + } + else + { + /* Initialize start info and call defender Start API */ + startInfo.pClientIdentifier = pIdentifier; + startInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); + startInfo.callback = callback; + startInfo.mqttConnection = mqttConnection; + defenderResult = AwsIotDefender_Start( &startInfo ); + } if( defenderResult == AWS_IOT_DEFENDER_SUCCESS ) { /* Let it run for 3 seconds */ IotClock_SleepMs( 3000 ); - /* Stop the defender agent. */ AwsIotDefender_Stop(); + /* Disconnect MQTT */ + IotMqtt_Disconnect( mqttConnection, false ); } } - IotLogInfo( "----Device Defender Demo End. Status: %d----.", defenderResult ); - - return defenderResult; -} - -/*-----------------------------------------------------------*/ - -static AwsIotDefenderError_t _startDefender( const char * pIdentifier, - void * pNetworkServerInfo, - void * pNetworkCredentialInfo, - const IotNetworkInterface_t * pNetworkInterface ) -{ - const AwsIotDefenderCallback_t callback = { .function = _defenderCallback, .param1 = NULL }; - AwsIotDefenderError_t defenderResult; - - AwsIotDefenderStartInfo_t startInfo = AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - - /* Set network information. */ - startInfo.mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; - startInfo.mqttNetworkInfo.createNetworkConnection = true; - startInfo.mqttNetworkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; - startInfo.mqttNetworkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; - - /* Set network interface. */ - startInfo.mqttNetworkInfo.pNetworkInterface = pNetworkInterface; - - /* Set MQTT connection information. */ - startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; - startInfo.mqttConnectionInfo.pClientIdentifier = pIdentifier; - startInfo.mqttConnectionInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - - /* Set Defender callback. */ - startInfo.callback = callback; + /* Cleanup. */ + if( metricsInitStatus ) + { + IotMetrics_Cleanup(); + } - /* Invoke defender start API. */ - defenderResult = AwsIotDefender_Start( &startInfo ); + if( mqttStatus == IOT_MQTT_SUCCESS ) + { + IotMqtt_Cleanup(); + } - return defenderResult; + IotLogInfo( "----Device Defender Demo End. Status: %s----.", AwsIotDefender_strerror( defenderResult ) ); + return status; } /*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/include/aws_iot_defender.h b/libraries/aws/defender/include/aws_iot_defender.h index 32d3d5a0da..9214a6ed6d 100644 --- a/libraries/aws/defender/include/aws_iot_defender.h +++ b/libraries/aws/defender/include/aws_iot_defender.h @@ -93,11 +93,19 @@ * @anchor DefenderInitializers * @name Initializers * - * @brief Intializers of data handles. + * @brief Initializers of data handles. */ /**@{ */ -#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER \ - { .mqttNetworkInfo = { 0 } \ +#define AWS_IOT_DEFENDER_CALLBACK_INITIALIZER \ + { \ + .pCallbackContext = NULL, \ + .function = NULL \ + } +#define AWS_IOT_DEFENDER_START_INFO_INITIALIZER \ + { .pClientIdentifier = NULL, \ + .clientIdentifierLength = 0, \ + .mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER, \ + .callback = AWS_IOT_DEFENDER_CALLBACK_INITIALIZER \ } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ /**@} */ @@ -177,7 +185,7 @@ typedef struct AwsIotDefenderCallbackInfo */ typedef struct AwsIotDefenderCallback { - void * param1; /**< The first parameter to pass to the callback function(optional). */ + void * pCallbackContext; /**< The callback context for caller's use (optional). */ void ( * function )( void *, AwsIotDefenderCallbackInfo_t * const ); /**< Callback function signature(optional). */ } AwsIotDefenderCallback_t; @@ -188,9 +196,10 @@ typedef struct AwsIotDefenderCallback */ typedef struct AwsIotDefenderStartInfo { - IotMqttNetworkInfo_t mqttNetworkInfo; /**< MQTT Network info used by defender (required). */ - IotMqttConnectInfo_t mqttConnectionInfo; /**< MQTT connection info used by defender (required). */ - AwsIotDefenderCallback_t callback; /**< Callback function parameter (optional). */ + const char * pClientIdentifier; /**< @brief MQTT client identifier. */ + uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ + IotMqttConnection_t mqttConnection; /**< MQTT connection used by defender (required). */ + AwsIotDefenderCallback_t callback; /**< Callback function parameter (optional). */ } AwsIotDefenderStartInfo_t; /** @@ -201,6 +210,7 @@ typedef struct AwsIotDefenderStartInfo * - @functionname{defender_function_setperiod} * - @functionname{defender_function_getperiod} * - @functionname{defender_function_strerror} + * - @functionname{defender_function_EventType} */ /** @@ -210,6 +220,7 @@ typedef struct AwsIotDefenderStartInfo * @functionpage{AwsIotDefender_SetPeriod,defender,setperiod} * @functionpage{AwsIotDefender_GetPeriod,defender,getperiod} * @functionpage{AwsIotDefender_strerror,defender,strerror} + * @functionpage{AwsIotDefender_EventType,defender,EventType} */ /** @@ -256,9 +267,10 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me * * @code{c} * - * // assume valid IotMqttNetworkInfo_t and IotMqttConnectInfo_t are created. - * const IotMqttNetworkInfo_t _mqttNetworkInfo; - * const IotMqttConnectInfo_t _mqttConnectionInfo; + * // assume valid IotMqttConnection_t is created and available. + * const IotMqttConnection_t _mqttConnection; + * // use AWS thing name as client identifier + * const char * pClientIdentifier = "AwsThingName"; * * void logDefenderCallback( void * param1, AwsIotDefenderCallbackInfo_t * const pCallbackInfo ) * { @@ -285,13 +297,15 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me * void startDefender() * { * // define a simple callback function which simply logs - * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback, .param1 = NULL }; + * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback, .pCallbackContext = NULL }; * * // define parameters of AwsIotDefender_Start function + * // Note: This example assumes, connection is already established and metrics library is initialized. * const AwsIotDefenderStartInfo_t startInfo = * { - * .mqttNetworkInfo = _mqttNetworkInfo, - * .mqttConnectionInfo = _mqttConnectionInfo, + * .pClientIdentifier = pClientIdentifier, + * .clientIdentifierLength = strlen( pClientIdentifier ), + * .mqttConnection = _mqttConnection, * .callback = callback * }; * @@ -380,4 +394,12 @@ uint32_t AwsIotDefender_GetPeriod( void ); const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ); /* @[declare_defender_strerror] */ +/** + * @brief Return a string that describes #AwsIotDefenderEventType_t + * + * @return A string that describes given #AwsIotDefenderEventType_t + */ +/* @[declare_defender_EventType] */ +const char * AwsIotDefender_EventType( AwsIotDefenderEventType_t eventType ); +/* @[declare_defender_EventType] */ #endif /* AWS_IOT_DEFENDER_H_ */ diff --git a/libraries/aws/defender/src/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c index ce89c47ed9..0d1bec690c 100644 --- a/libraries/aws/defender/src/aws_iot_defender_api.c +++ b/libraries/aws/defender/src/aws_iot_defender_api.c @@ -20,6 +20,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/* The config header is always included first. */ +#include "iot_config.h" +/* Error handling include. */ +#include "private/iot_error.h" + /* Defender internal include. */ #include "private/aws_iot_defender_internal.h" @@ -37,37 +42,50 @@ #endif /** - * callback registerd on accept topic. + * callback registered on accept topic. */ void _acceptCallback( void * pArgument, IotMqttCallbackParam_t * const pPublish ); /** - * callback registerd on reject topic. + * callback registered on reject topic. */ void _rejectCallback( void * pArgument, IotMqttCallbackParam_t * const pPublish ); /** - * Callback routine of _metricsPublishJob. + * subscribe to defender MQTT topics. + */ +static IotMqttError_t _metricsSubscribeRoutine(); + +/** + * Publish to defender MQTT topic. */ static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, IotTaskPoolJob_t pJob, void * pUserContext ); -/* Callback routine of _disconnectJob. */ -static void _disconnectRoutine( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ); +/* Unsubscribe from defender MQTT topic. */ +static void _unsubscribeMqtt(); + +/* Code to handle application callback */ +void _handleApplicationCallback( AwsIotDefenderEventType_t event, + IotMqttCallbackParam_t * const pPublish ); /*------------------- Below are global variables. ---------------------------*/ +/* Restart defender state machine unless there is an error */ + + /* Define global metrics and initialize metrics flags array to zero. */ _defenderMetrics_t _AwsIotDefenderMetrics = { .metricsFlag = { 0 } }; +/* MQTT callback info for reporting accept or reject status for subscribe / unsubscribe */ +IotMqttCallbackInfo_t _acceptCallbackInfo = { .function = _acceptCallback, .pCallbackContext = NULL }; +IotMqttCallbackInfo_t _rejectCallbackInfo = { .function = _rejectCallback, .pCallbackContext = NULL }; /** * Period between reports in milliseconds. @@ -78,9 +96,7 @@ static uint32_t _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_D static IotTaskPoolJobStorage_t _metricsPublishJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; static IotTaskPoolJob_t _metricsPublishJob = IOT_TASKPOOL_JOB_INITIALIZER; -static IotTaskPoolJobStorage_t _disconnectJobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER; -static IotTaskPoolJob_t _disconnectJob = IOT_TASKPOOL_JOB_INITIALIZER; - +/* Semaphore to prevent stop during publish */ static IotSemaphore_t _doneSem; /* @@ -127,20 +143,20 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartInfo ) { - if( pStartInfo == NULL ) - { - IotLogError( "Input start info is invalid." ); + IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - return AWS_IOT_DEFENDER_INVALID_INPUT; + IOT_FUNCTION_ENTRY( AwsIotDefenderError_t, AWS_IOT_DEFENDER_SUCCESS ); + + if( ( pStartInfo == NULL ) || ( pStartInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) ) + { + IotLogError( "Input startInfo is invalid." ); + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_DEFENDER_INVALID_INPUT ); } /* Assert system task pool is pre-created! */ AwsIotDefender_Assert( IOT_SYSTEM_TASKPOOL != NULL ); - - AwsIotDefenderError_t defenderError = AWS_IOT_DEFENDER_INTERNAL_FAILURE; - - IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; - + /* Silence warnigns when asserts are disabled. */ ( void ) taskPoolError; @@ -163,43 +179,61 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /* copy input start info into global variable _startInfo */ _startInfo = *pStartInfo; - defenderError = AwsIotDefenderInternal_BuildTopicsNames(); + status = AwsIotDefenderInternal_BuildTopicsNames(); - buildTopicsNamesSuccess = ( defenderError == AWS_IOT_DEFENDER_SUCCESS ); + buildTopicsNamesSuccess = ( status == AWS_IOT_DEFENDER_SUCCESS ); if( buildTopicsNamesSuccess ) { /* Create a binary semaphore with initial value 1. */ doneSemaphoreCreateSuccess = IotSemaphore_Create( &_doneSem, MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ, 1 ); } + else + { + status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + } if( doneSemaphoreCreateSuccess ) { metricsMutexCreateSuccess = IotMutex_Create( &_AwsIotDefenderMetrics.mutex, false ); } + else + { + status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + } if( metricsMutexCreateSuccess ) { - /* Create disconnect job. */ - taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJobStorage, &_disconnectJob ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + /* Subscribe to Metrics Publish topic */ + mqttError = _metricsSubscribeRoutine(); + + if( mqttError != IOT_MQTT_SUCCESS ) + { + status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + } + } + else + { + status = AWS_IOT_DEFENDER_INTERNAL_FAILURE; + } - /* Create metrics job. */ + if( status == AWS_IOT_DEFENDER_SUCCESS ) + { + /* Create metrics publish job, which will periodically publish metrics */ taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); + /* Silence warnigns when asserts are disabled. */ + ( void ) taskPoolError; AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - - /* Schedule metrics job. */ + /* Schedule Publish Job */ taskPoolError = IotTaskPool_Schedule( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, 0 ); AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - - /* Everything is good. Declare success. */ _started = true; - defenderError = AWS_IOT_DEFENDER_SUCCESS; + status = AWS_IOT_DEFENDER_SUCCESS; IotLogInfo( "Defender agent has successfully started." ); } /* Do the cleanup jobs if not success. */ - if( defenderError != AWS_IOT_DEFENDER_SUCCESS ) + if( status != AWS_IOT_DEFENDER_SUCCESS ) { /* reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; @@ -219,15 +253,15 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); } - IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( defenderError ) ); + IotLogError( "Defender agent failed to start due to error %s.", AwsIotDefender_strerror( status ) ); } } else { - defenderError = AWS_IOT_DEFENDER_ALREADY_STARTED; + IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_DEFENDER_ALREADY_STARTED ); } - return defenderError; + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ @@ -237,57 +271,58 @@ void AwsIotDefender_Stop( void ) if( !_started ) { IotLogWarn( "Defender has not started yet." ); - - return; } - - /* Wait for all the metrics processing to be done, if there are outstanding requests */ - if( IotSemaphore_GetCount( &_doneSem ) > MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ ) + else { + /* Wait for all the metrics processing to be done, if there are outstanding requests */ IotSemaphore_Wait( &_doneSem ); - } - IotTaskPoolJobStatus_t status; - IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, &status ); + IotTaskPoolJobStatus_t status; + IotTaskPoolError_t taskPoolError = IotTaskPool_TryCancel( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, &status ); - /* If cancel failed, let it sleep for a while and hope everything finishes. */ - if( taskPoolError != IOT_TASKPOOL_SUCCESS ) - { - IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); - IotClock_SleepMs( WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); - } + /* If cancel failed, let it sleep for a while and hope everything finishes. */ + if( taskPoolError != IOT_TASKPOOL_SUCCESS ) + { + IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); + IotClock_SleepMs( WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); + } + + _unsubscribeMqtt(); + + /* Destroy metrics' mutex. */ + IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); - /* Destroy metrics' mutex. */ - IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); + /* Post so that taken semaphore is returned */ + IotSemaphore_Post( &_doneSem ); + /* Destroy 'done' semaphore. */ + IotSemaphore_Destroy( &_doneSem ); - /* Destroy 'done' semaphore. */ - IotSemaphore_Destroy( &_doneSem ); + /* Delete topics names. */ + AwsIotDefenderInternal_DeleteTopicsNames(); - /* Delete topics names. */ - AwsIotDefenderInternal_DeleteTopicsNames(); + /* Delete report if it was created */ + AwsIotDefenderInternal_DeleteReport(); - /* Reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ - _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; + /* Reset _startInfo to empty; otherwise next time defender might start with incorrect information. */ + _startInfo = ( AwsIotDefenderStartInfo_t ) AWS_IOT_DEFENDER_START_INFO_INITIALIZER; - /* Reset _periodMilliSecond to default value. */ - _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); + /* Reset _periodMilliSecond to default value. */ + _periodMilliSecond = _defenderToMilliseconds( AWS_IOT_DEFENDER_DEFAULT_PERIOD_SECONDS ); - /* Reset metrics flag array to 0. */ - memset( _AwsIotDefenderMetrics.metricsFlag, 0, sizeof( _AwsIotDefenderMetrics.metricsFlag ) ); + /* Reset metrics flag array to 0. */ + memset( _AwsIotDefenderMetrics.metricsFlag, 0, sizeof( _AwsIotDefenderMetrics.metricsFlag ) ); - /* Set to not started. */ - _started = false; + /* Set to not started. */ + _started = false; - IotLogInfo( "Defender agent has stopped." ); + IotLogInfo( "Defender agent has stopped." ); + } } /*-----------------------------------------------------------*/ AwsIotDefenderError_t AwsIotDefender_SetPeriod( uint32_t periodSeconds ) { - /* Input period cannot be too long, which will cause integer overflow. */ - AwsIotDefender_Assert( periodSeconds < _defenderToSeconds( UINT32_MAX ) ); - AwsIotDefenderError_t defenderError = AWS_IOT_DEFENDER_INTERNAL_FAILURE; /* period can not be too short unless this is test mode. */ @@ -355,158 +390,138 @@ const char * AwsIotDefender_strerror( AwsIotDefenderError_t error ) } /*-----------------------------------------------------------*/ -static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ) -{ - /* Unsed parameter; silence the compiler. */ - ( void ) pTaskPool; - ( void ) pJob; - ( void ) pUserContext; - IotLogDebug( "Metrics publish job starts." ); +const char * AwsIotDefender_EventType( AwsIotDefenderEventType_t eventType ) +{ + const char * pEvent = NULL; - if( !IotSemaphore_TryWait( &_doneSem ) ) + /* Convert defent event to string */ + switch( eventType ) { - IotLogWarn( "Defender has been stopped or the previous metrics is in process. No further action." ); + case AWS_IOT_DEFENDER_METRICS_ACCEPTED: + pEvent = "Defender Metrics accepted"; + break; - return; - } + case AWS_IOT_DEFENDER_METRICS_REJECTED: + pEvent = "Defender Metrics rejected"; + break; - IotMqttError_t mqttError = IOT_MQTT_SUCCESS; + case AWS_IOT_DEFENDER_FAILURE_MQTT: + pEvent = "Defender MQTT operation failed"; + break; - bool mqttConnected = false; - bool reportCreated = false; + case AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT: /**< Defender failed to create metrics report. */ + pEvent = "Defender failed to create metrics Report"; + break; - const IotMqttCallbackInfo_t acceptCallbackInfo = { .function = _acceptCallback, .pCallbackContext = NULL }; - const IotMqttCallbackInfo_t rejectCallbackInfo = { .function = _rejectCallback, .pCallbackContext = NULL }; + default: + pEvent = "Defender Unknown Event"; + } - /* Step 1: connect to MQTT. */ - mqttError = AwsIotDefenderInternal_MqttConnect(); + return pEvent; +} - if( mqttError == IOT_MQTT_SUCCESS ) - { - mqttConnected = true; +/*-----------------------------------------------------------*/ - /* Step 2: subscribe to accept/reject MQTT topics. */ - mqttError = AwsIotDefenderInternal_MqttSubscribe( acceptCallbackInfo, - rejectCallbackInfo ); +static IotMqttError_t _metricsSubscribeRoutine() +{ + IotLogDebug( "Metrics Subscribe starts." ); - if( mqttError == IOT_MQTT_SUCCESS ) - { - /* Step 3: create serialized metrics report. */ - reportCreated = AwsIotDefenderInternal_CreateReport(); + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - /* If Report is created successfully. */ - if( reportCreated ) - { - /* Step 4: publish report to defender topic. */ - mqttError = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), - AwsIotDefenderInternal_GetReportBufferSize() ); - - if( mqttError == IOT_MQTT_SUCCESS ) - { - IotLogDebug( "Metrics report has been published successfully." ); - } - } - } - } + /* Subscribe to accept/reject MQTT topics. */ + mqttError = AwsIotDefenderInternal_MqttSubscribe(); - /* If no MQTT error and report has been created, it indicates everything is good. */ - if( ( mqttError == IOT_MQTT_SUCCESS ) && reportCreated ) + if( mqttError != IOT_MQTT_SUCCESS ) { - IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _disconnectRoutine, NULL, &_disconnectJobStorage, &_disconnectJob ); + IotLogError( "Failed to perform MQTT operations, with error %s.", IotMqtt_strerror( mqttError ) ); + _unsubscribeMqtt(); + } - /* Silence warnigns when asserts are disabled. */ - ( void ) taskPoolError; - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + return mqttError; +} + +/*-----------------------------------------------------------*/ + +static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, + IotTaskPoolJob_t pJob, + void * pUserContext ) +{ + /* Unsed parameter; silence the compiler. */ + ( void ) pTaskPool; + ( void ) pJob; + ( void ) pUserContext; + + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; + bool reportCreated = false; + IotTaskPoolError_t taskPoolError = IOT_TASKPOOL_SUCCESS; - IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, - _disconnectJob, - _defenderToMilliseconds( AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS ) ); + if( !IotSemaphore_TryWait( &_doneSem ) ) + { + IotLogError( "Defender has been stopped or the previous metrics is in process. No further action." ); } else { - AwsIotDefenderEventType_t eventType; + /* Create serialized metrics report. */ + reportCreated = AwsIotDefenderInternal_CreateReport(); - /* Set event type to only two possible categories. */ - if( mqttError != IOT_MQTT_SUCCESS ) + /* If Report is created successfully. */ + if( reportCreated ) { - eventType = AWS_IOT_DEFENDER_FAILURE_MQTT; - IotLogError( "Failed to perform MQTT operations, with error %s.", IotMqtt_strerror( mqttError ) ); + /* Publish report to defender topic. */ + mqttError = AwsIotDefenderInternal_MqttPublish( AwsIotDefenderInternal_GetReportBuffer(), + AwsIotDefenderInternal_GetReportBufferSize() ); + + if( mqttError == IOT_MQTT_SUCCESS ) + { + IotLogDebug( "Metrics report has been published successfully." ); + } + else + { + IotLogError( "Failed to perform MQTT publish, with error %s.", IotMqtt_strerror( mqttError ) ); + } } else { - eventType = AWS_IOT_DEFENDER_FAILURE_METRICS_REPORT; - /* As of today, no memory is the only reason to fail metrics report creation. */ - IotLogError( "Failed to create metrics report due to no memory." ); + IotLogError( "Failed to create report" ); } - /* Invoke user's callback if there is. */ - if( _startInfo.callback.function != NULL ) + if( ( mqttError != IOT_MQTT_SUCCESS ) || ( !reportCreated ) ) { - AwsIotDefenderCallbackInfo_t callbackInfo; - - callbackInfo.eventType = eventType; - - /* No message to be given to user's callback */ - callbackInfo.pPayload = NULL; - callbackInfo.payloadLength = 0; - - /* It is possible the report buffer is NULL and size is 0. */ - callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); - callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); - - _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); - } + if( reportCreated ) + { + AwsIotDefenderInternal_DeleteReport(); + } - /* Clean up resources conditionally. */ - if( reportCreated ) - { - AwsIotDefenderInternal_DeleteReport(); + _unsubscribeMqtt(); + /* Invoke user's callback if there is. */ + _handleApplicationCallback( AWS_IOT_DEFENDER_FAILURE_MQTT, NULL ); } - - if( mqttConnected ) + else { - AwsIotDefenderInternal_MqttDisconnect(); + /* Re-schedule metrics job with period as deferred interval. */ + taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, _periodMilliSecond ); } + /* Give Done semaphore so AwsIotDefender_Stop() can proceed */ IotSemaphore_Post( &_doneSem ); } - IotLogDebug( "Metrics publish job ends." ); + IotLogDebug( "Publish job ends." ); } /*-----------------------------------------------------------*/ -static void _disconnectRoutine( IotTaskPool_t pTaskPool, - IotTaskPoolJob_t pJob, - void * pUserContext ) +void _unsubscribeMqtt() { - /* Unsed parameter; silence the compiler. */ - ( void ) pTaskPool; - ( void ) pJob; - ( void ) pUserContext; - - IotLogDebug( "Disconnect job starts." ); - - AwsIotDefenderInternal_DeleteReport(); - AwsIotDefenderInternal_MqttDisconnect(); - /* Re-create metrics job. */ - IotTaskPoolError_t taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); - - /* Silence warnigns when asserts are disabled. */ - ( void ) taskPoolError; - - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); - - /* Re-schedule metrics job with period as deferred interval. */ - taskPoolError = IotTaskPool_ScheduleDeferred( IOT_SYSTEM_TASKPOOL, _metricsPublishJob, _periodMilliSecond ); - AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; - IotSemaphore_Post( &_doneSem ); + mqttError = AwsIotDefenderInternal_MqttUnSubscribe(); - IotLogDebug( "Disconnect job ends." ); + if( mqttError != IOT_MQTT_SUCCESS ) + { + IotLogError( "Unsubscribe failed for defender agent" ); + } } /*-----------------------------------------------------------*/ @@ -523,20 +538,9 @@ void _acceptCallback( void * pArgument, AwsIotDefender_Assert( pPublish->u.message.info.pPayload ); /* Invoke user's callback with accept event. */ - AwsIotDefenderCallbackInfo_t callbackInfo; - - if( _startInfo.callback.function != NULL ) - { - callbackInfo.eventType = AWS_IOT_DEFENDER_METRICS_ACCEPTED; - - callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); - callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); - - callbackInfo.pPayload = pPublish->u.message.info.pPayload; - callbackInfo.payloadLength = pPublish->u.message.info.payloadLength; - - _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); - } + _handleApplicationCallback( AWS_IOT_DEFENDER_METRICS_ACCEPTED, pPublish ); + /* Delete report if exists */ + AwsIotDefenderInternal_DeleteReport(); } /*-----------------------------------------------------------*/ @@ -552,18 +556,39 @@ void _rejectCallback( void * pArgument, AwsIotDefender_Assert( pPublish->u.message.info.pPayload ); /* Invoke user's callback with rejected event. */ + _handleApplicationCallback( AWS_IOT_DEFENDER_METRICS_REJECTED, pPublish ); + /* Delete report if exists */ + AwsIotDefenderInternal_DeleteReport(); +} + +/*-----------------------------------------------------------*/ + +void _handleApplicationCallback( AwsIotDefenderEventType_t event, + IotMqttCallbackParam_t * const pPublish ) +{ + /* Invoke user's callback with event. */ AwsIotDefenderCallbackInfo_t callbackInfo; if( _startInfo.callback.function != NULL ) { - callbackInfo.eventType = AWS_IOT_DEFENDER_METRICS_REJECTED; + callbackInfo.eventType = event; callbackInfo.pMetricsReport = AwsIotDefenderInternal_GetReportBuffer(); callbackInfo.metricsReportLength = AwsIotDefenderInternal_GetReportBufferSize(); - callbackInfo.pPayload = pPublish->u.message.info.pPayload; - callbackInfo.payloadLength = pPublish->u.message.info.payloadLength; + if( pPublish == NULL ) + { + callbackInfo.pPayload = NULL; + callbackInfo.payloadLength = 0; + } + else + { + callbackInfo.pPayload = pPublish->u.message.info.pPayload; + callbackInfo.payloadLength = pPublish->u.message.info.payloadLength; + } - _startInfo.callback.function( _startInfo.callback.param1, &callbackInfo ); + _startInfo.callback.function( _startInfo.callback.pCallbackContext, &callbackInfo ); } } + +/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/src/aws_iot_defender_mqtt.c b/libraries/aws/defender/src/aws_iot_defender_mqtt.c index 641b09fda3..3816ccd367 100644 --- a/libraries/aws/defender/src/aws_iot_defender_mqtt.c +++ b/libraries/aws/defender/src/aws_iot_defender_mqtt.c @@ -35,16 +35,20 @@ #define TOPIC_SUFFIX_REJECTED TOPIC_SUFFIX_PUBLISH "/rejected" -extern AwsIotDefenderStartInfo_t _startInfo; +/*-----------------------------------------------------------*/ -/* defender internally manages network and mqtt connection */ -static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; +extern IotMqttCallbackInfo_t _acceptCallbackInfo; +extern IotMqttCallbackInfo_t _rejectCallbackInfo; +extern AwsIotDefenderStartInfo_t _startInfo; static char * _pPublishTopic = NULL; +static size_t _publishTopicLength = 0; static char * _pAcceptTopic = NULL; +static size_t _acceptTopicLength = 0; static char * _pRejectTopic = NULL; +static size_t _rejectTopicLength = 0; /*-----------------------------------------------------------*/ @@ -52,8 +56,8 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) { AwsIotDefenderError_t returnedError = AWS_IOT_DEFENDER_SUCCESS; - const char * pThingName = _startInfo.mqttConnectionInfo.pClientIdentifier; - uint16_t thingNameLength = _startInfo.mqttConnectionInfo.clientIdentifierLength; + const char * pThingName = _startInfo.pClientIdentifier; + uint16_t thingNameLength = _startInfo.clientIdentifierLength; size_t topicPrefixLength = strlen( TOPIC_PREFIX ); /* Calculate topics lengths. Plus one for string terminator. */ @@ -95,6 +99,10 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ) TOPIC_PREFIX, pThingName, TOPIC_SUFFIX_REJECTED ); + + _publishTopicLength = publishTopicLength -1; + _acceptTopicLength = acceptTopicLength -1 ; + _rejectTopicLength = rejectTopicLength -1 ; } return returnedError; @@ -110,37 +118,30 @@ void AwsIotDefenderInternal_DeleteTopicsNames( void ) _pPublishTopic = NULL; _pAcceptTopic = NULL; _pRejectTopic = NULL; -} -/*-----------------------------------------------------------*/ - -IotMqttError_t AwsIotDefenderInternal_MqttConnect( void ) -{ - return IotMqtt_Connect( &_startInfo.mqttNetworkInfo, - &_startInfo.mqttConnectionInfo, - _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_CONNECT_TIMEOUT_SECONDS ), - &_mqttConnection ); + _publishTopicLength = 0; + _acceptTopicLength = 0; + _rejectTopicLength = 0; } /*-----------------------------------------------------------*/ -IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, - IotMqttCallbackInfo_t rejectCallback ) +IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( void ) { /* subscribe to two topics: accept and reject. */ IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; subscriptions[ 0 ].qos = IOT_MQTT_QOS_0; subscriptions[ 0 ].pTopicFilter = _pAcceptTopic; - subscriptions[ 0 ].topicFilterLength = ( uint16_t ) strlen( _pAcceptTopic ); - subscriptions[ 0 ].callback = acceptCallback; + subscriptions[ 0 ].topicFilterLength = ( uint16_t ) _acceptTopicLength; + subscriptions[ 0 ].callback = _acceptCallbackInfo; subscriptions[ 1 ].qos = IOT_MQTT_QOS_0; subscriptions[ 1 ].pTopicFilter = _pRejectTopic; - subscriptions[ 1 ].topicFilterLength = ( uint16_t ) strlen( _pRejectTopic ); - subscriptions[ 1 ].callback = rejectCallback; + subscriptions[ 1 ].topicFilterLength = ( uint16_t ) _rejectTopicLength; + subscriptions[ 1 ].callback = _rejectCallbackInfo; - return IotMqtt_SubscribeSync( _mqttConnection, + return IotMqtt_SubscribeSync( _startInfo.mqttConnection, subscriptions, 2, 0, @@ -153,14 +154,14 @@ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, size_t dataLength ) { IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; - + publishInfo.qos = IOT_MQTT_QOS_0; publishInfo.pTopicName = _pPublishTopic; - publishInfo.topicNameLength = ( uint16_t ) strlen( _pPublishTopic ); + publishInfo.topicNameLength = ( uint16_t ) _publishTopicLength; publishInfo.pPayload = pData; publishInfo.payloadLength = dataLength; - - return IotMqtt_PublishSync( _mqttConnection, + /* Publish Defender Report */ + return IotMqtt_PublishSync( _startInfo.mqttConnection, &publishInfo, 0, _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_PUBLISH_TIMEOUT_SECONDS ) ); @@ -168,7 +169,26 @@ IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, /*-----------------------------------------------------------*/ -void AwsIotDefenderInternal_MqttDisconnect( void ) +IotMqttError_t AwsIotDefenderInternal_MqttUnSubscribe( void ) { - IotMqtt_Disconnect( _mqttConnection, false ); + /* unsubscribe to two topics: accept and reject. */ + IotMqttSubscription_t subscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER, IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + + subscriptions[ 0 ].qos = IOT_MQTT_QOS_0; + subscriptions[ 0 ].pTopicFilter = _pAcceptTopic; + subscriptions[ 0 ].topicFilterLength = ( uint16_t ) _acceptTopicLength; + subscriptions[ 0 ].callback = _acceptCallbackInfo; + + subscriptions[ 1 ].qos = IOT_MQTT_QOS_0; + subscriptions[ 1 ].pTopicFilter = _pRejectTopic; + subscriptions[ 1 ].topicFilterLength = ( uint16_t ) _rejectTopicLength; + subscriptions[ 1 ].callback = _rejectCallbackInfo; + + return IotMqtt_UnsubscribeSync( _startInfo.mqttConnection, + subscriptions, + 2, + 0, + _defenderToMilliseconds( AWS_IOT_DEFENDER_MQTT_SUBSCRIBE_TIMEOUT_SECONDS ) ); } + +/*-----------------------------------------------------------*/ diff --git a/libraries/aws/defender/src/private/aws_iot_defender_internal.h b/libraries/aws/defender/src/private/aws_iot_defender_internal.h index afae4745f0..5a6d59f2bf 100644 --- a/libraries/aws/defender/src/private/aws_iot_defender_internal.h +++ b/libraries/aws/defender/src/private/aws_iot_defender_internal.h @@ -321,16 +321,11 @@ AwsIotDefenderError_t AwsIotDefenderInternal_BuildTopicsNames( void ); */ void AwsIotDefenderInternal_DeleteTopicsNames( void ); -/** - * Connect to AWS with MQTT. - */ -IotMqttError_t AwsIotDefenderInternal_MqttConnect( void ); - /** * Subscribe accept/reject defender topics. */ -IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t acceptCallback, - IotMqttCallbackInfo_t rejectCallback ); + +IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( void ); /** * Publish metrics data to defender topic. @@ -338,6 +333,11 @@ IotMqttError_t AwsIotDefenderInternal_MqttSubscribe( IotMqttCallbackInfo_t accep IotMqttError_t AwsIotDefenderInternal_MqttPublish( uint8_t * pData, size_t dataLength ); +/** + * Unsubscribe accept/reject defender topics. + */ +IotMqttError_t AwsIotDefenderInternal_MqttUnSubscribe( void ); + /** * Disconnect with AWS MQTT. */ diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c index bc24561596..df93fc442f 100644 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c @@ -32,6 +32,7 @@ /* Defender internal includes. */ #include "private/aws_iot_defender_internal.h" +#include "iot_tests_mqtt_mock.h" #include "iot_network_metrics.h" @@ -67,10 +68,11 @@ #define NO_EVENT 10000 /* Empty callback structure passed to startInfo. */ -static const AwsIotDefenderCallback_t _EMPTY_CALLBACK = { .function = NULL, .param1 = NULL }; +static const AwsIotDefenderCallback_t _emptyCallback = { .function = NULL, .pCallbackContext = NULL }; static IotNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; static IotNetworkCredentials_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*------------------ global variables -----------------------------*/ @@ -92,6 +94,8 @@ static AwsIotDefenderCallbackInfo_t _callbackInfo; static IotSerializerDecoderObject_t _decoderObject; static IotSerializerDecoderObject_t _metricsObject; +static bool _mqttConnectionStarted = false; +static bool _mockedMqttConnection = false; /*------------------ Functions -----------------------------*/ /* Copy data from MQTT callback to local buffer. */ @@ -112,11 +116,11 @@ static void _verifyMetricsCommon( void ); /* Verify tcp connections in metrics report. */ static void _verifyTcpConnections( int total ); -/* Indicate this test doesn't actually publish report. */ -static void _publishMetricsNotNeeded( void ); - static void _resetCalbackInfo( void ); +static IotMqttError_t _startMqttConnection( void ); +static void _stopMqttConnection( void ); + TEST_GROUP( Defender_System ); TEST_SETUP( Defender_System ) @@ -149,12 +153,14 @@ TEST_SETUP( Defender_System ) _resetCalbackInfo(); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + _decoderObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; _metricsObject = ( IotSerializerDecoderObject_t ) IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; /* Reset test callback. */ _testCallback = ( AwsIotDefenderCallback_t ) { - .function = _copyDataCallbackFunction, .param1 = NULL + .function = _copyDataCallbackFunction, .pCallbackContext = NULL }; /* By default IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER enables ALPN. ALPN @@ -168,18 +174,9 @@ TEST_SETUP( Defender_System ) _serverInfo = ( IotNetworkServerInfo_t ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /* Set fields of start info. */ - _startInfo.mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; - _startInfo.mqttNetworkInfo.createNetworkConnection = true; - _startInfo.mqttNetworkInfo.u.setup.pNetworkServerInfo = &_serverInfo; - _startInfo.mqttNetworkInfo.u.setup.pNetworkCredentialInfo = &_credential; - - _startInfo.mqttNetworkInfo.pNetworkInterface = IotNetworkMetrics_GetInterface(); - - _startInfo.mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; - _startInfo.mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_SHADOW_THING_NAME; - _startInfo.mqttConnectionInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_SHADOW_THING_NAME ); - - _startInfo.callback = _EMPTY_CALLBACK; + _startInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; + _startInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_DEFENDER_THING_NAME ); + _startInfo.callback = _emptyCallback; } TEST_TEAR_DOWN( Defender_System ) @@ -194,7 +191,7 @@ TEST_TEAR_DOWN( Defender_System ) } IotSemaphore_Destroy( &_callbackInfoSem ); - + _stopMqttConnection(); IotMetrics_Cleanup(); IotMqtt_Cleanup(); IotTestNetwork_Cleanup(); @@ -205,10 +202,10 @@ TEST_GROUP_RUNNER( Defender_System ) { /* * Setup: none - * Action: call Start API with invliad IoT endpoint - * Expectation: Start API returns network connection failure + * Action: call Start API with invalid MQTT connection + * Expectation: Start API returns failure */ - RUN_TEST_CASE( Defender_System, Start_with_wrong_network_information ); + RUN_TEST_CASE( Defender_System, Start_with_invalid_mqtt_connection ); /* * Setup: defender not started yet @@ -243,7 +240,7 @@ TEST_GROUP_RUNNER( Defender_System ) * Expectation: * - SetPeriod API return "period too short" error */ - /*RUN_TEST_CASE( Defender_System, SetPeriod_too_short ); */ + RUN_TEST_CASE( Defender_System, SetPeriod_too_short ); /* * Setup: defender not started yet @@ -350,7 +347,10 @@ TEST( Defender_System, SetMetrics_with_TCP_connections_all ) TEST( Defender_System, SetMetrics_after_defender_started ) { - _publishMetricsNotNeeded(); + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); @@ -365,23 +365,22 @@ TEST( Defender_System, SetMetrics_after_defender_started ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_METRICS_ALL, _AwsIotDefenderMetrics.metricsFlag[ AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS ] ); } -TEST( Defender_System, Start_with_wrong_network_information ) +TEST( Defender_System, Start_with_invalid_mqtt_connection ) { - _publishMetricsNotNeeded(); - - /* Set test callback to verify report. */ - _startInfo.callback = _testCallback; + /* Set uninitialized MQTT connection */ + _startInfo.mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); - TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); - - _assertEvent( AWS_IOT_DEFENDER_FAILURE_MQTT, WAIT_STATE_TOTAL_SECONDS ); + TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_INVALID_INPUT, error ); } TEST( Defender_System, Start_should_return_success ) { - _publishMetricsNotNeeded(); + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); @@ -390,7 +389,10 @@ TEST( Defender_System, Start_should_return_success ) TEST( Defender_System, Start_should_return_err_if_already_started ) { - _publishMetricsNotNeeded(); + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; AwsIotDefenderError_t error = AwsIotDefender_Start( &_startInfo ); @@ -404,11 +406,17 @@ TEST( Defender_System, Start_should_return_err_if_already_started ) TEST( Defender_System, Metrics_empty_are_published ) { - AwsIotDefenderError_t error; + AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; /* Set test callback to verify report. */ _startInfo.callback = _testCallback; + /* start actual MQTT connection */ + mqttError = _startMqttConnection(); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); + _startInfo.mqttConnection = _mqttConnection; + /* Start defender. */ error = AwsIotDefender_Start( &_startInfo ); @@ -422,7 +430,13 @@ TEST( Defender_System, Metrics_empty_are_published ) TEST( Defender_System, Metrics_TCP_connections_all_are_published ) { - AwsIotDefenderError_t error; + AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; + + /* start actual MQTT connection */ + mqttError = _startMqttConnection(); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); + _startInfo.mqttConnection = _mqttConnection; /* Set "all metrics" for TCP connections metrics group. */ error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, @@ -447,7 +461,8 @@ TEST( Defender_System, Metrics_TCP_connections_all_are_published ) TEST( Defender_System, Metrics_TCP_connections_total_are_published ) { - AwsIotDefenderError_t error; + AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; /* Set "total count" for TCP connections metrics group. */ error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, @@ -455,6 +470,11 @@ TEST( Defender_System, Metrics_TCP_connections_total_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + /* start actual MQTT connection */ + mqttError = _startMqttConnection(); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); + _startInfo.mqttConnection = _mqttConnection; + /* Set test callback to verify report. */ _startInfo.callback = _testCallback; @@ -472,7 +492,8 @@ TEST( Defender_System, Metrics_TCP_connections_total_are_published ) TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) { - AwsIotDefenderError_t error; + AwsIotDefenderError_t error = AWS_IOT_DEFENDER_SUCCESS; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; /* Set "remote address" for TCP connections metrics group. */ error = AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, @@ -480,6 +501,11 @@ TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, error ); + /* start actual MQTT connection */ + mqttError = _startMqttConnection(); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); + _startInfo.mqttConnection = _mqttConnection; + /* Set test callback to verify report. */ _startInfo.callback = _testCallback; @@ -498,11 +524,17 @@ TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) TEST( Defender_System, Restart_and_updated_metrics_are_published ) { char * pIotAddress = NULL; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; /* Set "total count" for TCP connections metrics group. */ TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_SetMetrics( AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS, AWS_IOT_DEFENDER_METRICS_TCP_CONNECTIONS_ESTABLISHED_TOTAL ) ); + /* start actual MQTT connection */ + mqttError = _startMqttConnection(); + TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, mqttError ); + _startInfo.mqttConnection = _mqttConnection; + /* Set test callback to verify report. */ _startInfo.callback = _testCallback; @@ -549,7 +581,10 @@ TEST( Defender_System, SetPeriod_with_proper_value ) TEST( Defender_System, SetPeriod_after_started ) { - _publishMetricsNotNeeded(); + /* Set up a mocked MQTT connection. */ + TEST_ASSERT_EQUAL_INT( true, IotTest_MqttMockInit( &_mqttConnection ) ); + _mockedMqttConnection = true; + _startInfo.mqttConnection = _mqttConnection; TEST_ASSERT_EQUAL( AWS_IOT_DEFENDER_SUCCESS, AwsIotDefender_Start( &_startInfo ) ); @@ -599,10 +634,57 @@ static void _copyDataCallbackFunction( void * param1, /*-----------------------------------------------------------*/ -static void _publishMetricsNotNeeded( void ) +static IotMqttError_t _startMqttConnection( void ) { - /* Given a dummy IoT endpoint to fail network connection. */ - _serverInfo.pHostName = "dummy endpoint"; + IotMqttError_t mqttError = IOT_MQTT_SUCCESS; + IotMqttNetworkInfo_t mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; + IotMqttConnectInfo_t mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; + + if( !_mqttConnectionStarted ) + { + mqttNetworkInfo = ( IotMqttNetworkInfo_t ) IOT_MQTT_NETWORK_INFO_INITIALIZER; + mqttNetworkInfo.createNetworkConnection = true; + mqttNetworkInfo.u.setup.pNetworkServerInfo = &_serverInfo; + mqttNetworkInfo.u.setup.pNetworkCredentialInfo = &_credential; + + mqttNetworkInfo.pNetworkInterface = IotNetworkMetrics_GetInterface(); + + mqttConnectionInfo = ( IotMqttConnectInfo_t ) IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Set MQTT connection information. */ + mqttConnectionInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; + mqttConnectionInfo.clientIdentifierLength = ( uint16_t ) strlen( AWS_IOT_TEST_DEFENDER_THING_NAME ); + + mqttError = IotMqtt_Connect( &mqttNetworkInfo, + &mqttConnectionInfo, + 1000, + &_mqttConnection ); + + if( mqttError == IOT_MQTT_SUCCESS ) + { + _mqttConnectionStarted = true; + } + } + + return mqttError; +} + +/*-----------------------------------------------------------*/ + +static void _stopMqttConnection( void ) +{ + if( _mqttConnectionStarted ) + { + IotMqtt_Disconnect( _mqttConnection, false ); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + _mqttConnectionStarted = false; + } + if (_mockedMqttConnection) + { + IotTest_MqttMockCleanup(); + _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + _mockedMqttConnection = false; + } } /*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c index 7184cc7f67..81f3a6e807 100644 --- a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c +++ b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_api.c @@ -805,7 +805,7 @@ TEST( Jobs_Unit_API, SetCallback ) */ TEST( Jobs_Unit_API, SetCallbackMultiple ) { - int32_t i = 0; + intptr_t i = 0; AwsIotJobsError_t status = AWS_IOT_JOBS_STATUS_PENDING; AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; _jobsSubscription_t * pSubscription = NULL; diff --git a/tests/iot_config.h b/tests/iot_config.h index 49e0bca685..6190f1e1d7 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -74,6 +74,11 @@ #define AWS_IOT_TEST_JOBS_THING_NAME "" #endif +/* Defender tests configuration. */ +#ifndef AWS_IOT_TEST_DEFENDER_THING_NAME + #define AWS_IOT_TEST_DEFENDER_THING_NAME "" +#endif + /* Log level for testing the demos. */ #define IOT_LOG_LEVEL_DEMO IOT_LOG_INFO From 486db9ed913f0c20c1654e53e5cfc53de6c57c8b Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Mon, 7 Oct 2019 22:43:36 -0700 Subject: [PATCH 284/844] fix: defender build failure. (#590) --- libraries/aws/defender/src/aws_iot_defender_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/aws/defender/src/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c index 0d1bec690c..0bde52a9c9 100644 --- a/libraries/aws/defender/src/aws_iot_defender_api.c +++ b/libraries/aws/defender/src/aws_iot_defender_api.c @@ -23,7 +23,7 @@ /* The config header is always included first. */ #include "iot_config.h" /* Error handling include. */ -#include "private/iot_error.h" +#include "iot_error.h" /* Defender internal include. */ #include "private/aws_iot_defender_internal.h" From 28f5f4b3e36d1aac32d3ffbe0c4183fa382af86b Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 8 Oct 2019 16:57:24 -0700 Subject: [PATCH 285/844] chore: update version numbers and cleanup. (#593) Updated version number for Defender library to 3.0.0 --- demos/src/aws_iot_demo_defender.c | 49 +++++++++++-------- .../aws/defender/include/aws_iot_defender.h | 2 +- .../aws/defender/src/aws_iot_defender_api.c | 11 ++--- .../defender/src/aws_iot_defender_collector.c | 2 +- .../aws/defender/src/aws_iot_defender_mqtt.c | 2 +- .../src/private/aws_iot_defender_internal.h | 6 +-- .../defender/test/aws_iot_tests_defender.c | 2 +- .../system/aws_iot_tests_defender_system.c | 2 +- 8 files changed, 40 insertions(+), 36 deletions(-) diff --git a/demos/src/aws_iot_demo_defender.c b/demos/src/aws_iot_demo_defender.c index 7a665ed4a5..a7428369cd 100644 --- a/demos/src/aws_iot_demo_defender.c +++ b/demos/src/aws_iot_demo_defender.c @@ -45,21 +45,26 @@ * * An MQTT ping request will be sent periodically at this interval. */ -#define KEEP_ALIVE_SECONDS ( 60 ) +#define KEEP_ALIVE_SECONDS ( ( uint16_t ) 60 ) /** * @brief The timeout for Defender and MQTT operations in this demo. */ -#define TIMEOUT_MS ( 5000 ) +#define TIMEOUT_MS ( ( uint32_t ) 5000 ) + +/** + * @brief Defender metrics publish interval, 5 minutes (300 seconds) is minumum. + */ +#define DEFENDER_PUBLISH_INTERVAL ( ( uint32_t ) 300 ) /*-----------------------------------------------------------*/ /** * @brief Callback used to get notification of defender's events. - * + * * @param[in] pCallbackContext context pointer passed by the application * when callback is regiested in AwsIotDefender_Start() - * + * * @param[im] pointer to AwsIotDefenderCallbackInfo_t containing status of * publish */ @@ -70,23 +75,26 @@ void _defenderCallback( void * pCallbackContext, IotLogInfo( "User's callback is invoked on event: %s.", AwsIotDefender_EventType( pCallbackInfo->eventType ) ); - /* Callback info processing example . */ - if( pCallbackInfo->pMetricsReport != NULL ) + if( pCallbackInfo != NULL ) { - IotLogInfo( "Published metrics report." ); - } - else - { - IotLogError( "No metrics report was generated." ); - } + /* Callback info processing example . */ + if( pCallbackInfo->pMetricsReport != NULL ) + { + IotLogInfo( "Published metrics report." ); + } + else + { + IotLogError( "No metrics report was generated." ); + } - if( pCallbackInfo->pPayload != NULL ) - { - IotLogInfo( "Received MQTT message." ); - } - else - { - IotLogError( "No message has been returned from subscribed topic." ); + if( pCallbackInfo->pPayload != NULL ) + { + IotLogInfo( "Received MQTT message." ); + } + else + { + IotLogError( "No message has been returned from subscribed topic." ); + } } } @@ -244,8 +252,7 @@ int RunDefenderDemo( bool awsIotMqttMode, if( status == EXIT_SUCCESS ) { - /* Set metrics report period to 5 minutes(300 seconds) */ - defenderResult = AwsIotDefender_SetPeriod( 300 ); + defenderResult = AwsIotDefender_SetPeriod( DEFENDER_PUBLISH_INTERVAL ); if( defenderResult != AWS_IOT_DEFENDER_SUCCESS ) { diff --git a/libraries/aws/defender/include/aws_iot_defender.h b/libraries/aws/defender/include/aws_iot_defender.h index 9214a6ed6d..ddee4d0c85 100644 --- a/libraries/aws/defender/include/aws_iot_defender.h +++ b/libraries/aws/defender/include/aws_iot_defender.h @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/libraries/aws/defender/src/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c index 0bde52a9c9..cd27f968e7 100644 --- a/libraries/aws/defender/src/aws_iot_defender_api.c +++ b/libraries/aws/defender/src/aws_iot_defender_api.c @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -156,7 +156,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /* Assert system task pool is pre-created! */ AwsIotDefender_Assert( IOT_SYSTEM_TASKPOOL != NULL ); - + /* Silence warnigns when asserts are disabled. */ ( void ) taskPoolError; @@ -172,8 +172,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn _pAwsIotDefenderDecoder = IotSerializer_GetCborDecoder(); _pAwsIotDefenderEncoder = IotSerializer_GetCborEncoder(); #else - _pAwsIotDefenderDecoder = IotSerializer_GetJsonDecoder(); - _pAwsIotDefenderEncoder = IotSerializer_GetJsonEncoder(); + #error "AWS IOT Defender library supports only CBOR encoder." #endif /* copy input start info into global variable _startInfo */ @@ -286,9 +285,9 @@ void AwsIotDefender_Stop( void ) IotLogWarn( "Failed to cancel metrics publish job with return code %d and status %d.", taskPoolError, status ); IotClock_SleepMs( WAIT_METRICS_JOB_MAX_SECONDS * 1000 ); } - + _unsubscribeMqtt(); - + /* Destroy metrics' mutex. */ IotMutex_Destroy( &_AwsIotDefenderMetrics.mutex ); diff --git a/libraries/aws/defender/src/aws_iot_defender_collector.c b/libraries/aws/defender/src/aws_iot_defender_collector.c index 298c078c25..36cfebb5c0 100644 --- a/libraries/aws/defender/src/aws_iot_defender_collector.c +++ b/libraries/aws/defender/src/aws_iot_defender_collector.c @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/libraries/aws/defender/src/aws_iot_defender_mqtt.c b/libraries/aws/defender/src/aws_iot_defender_mqtt.c index 3816ccd367..c692cc9828 100644 --- a/libraries/aws/defender/src/aws_iot_defender_mqtt.c +++ b/libraries/aws/defender/src/aws_iot_defender_mqtt.c @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/libraries/aws/defender/src/private/aws_iot_defender_internal.h b/libraries/aws/defender/src/private/aws_iot_defender_internal.h index 5a6d59f2bf..e46281a5db 100644 --- a/libraries/aws/defender/src/private/aws_iot_defender_internal.h +++ b/libraries/aws/defender/src/private/aws_iot_defender_internal.h @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -250,10 +250,8 @@ */ #if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR #define DEFENDER_FORMAT "cbor" -#elif AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_JSON - #define DEFENDER_FORMAT "json" #else /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ - #error "AWS_IOT_DEFENDER_FORMAT must be either AWS_IOT_DEFENDER_FORMAT_CBOR or AWS_IOT_DEFENDER_FORMAT_JSON." + #error "AWS_IOT_DEFENDER_FORMAT must be AWS_IOT_DEFENDER_FORMAT_CBOR." #endif /* if AWS_IOT_DEFENDER_FORMAT == AWS_IOT_DEFENDER_FORMAT_CBOR */ /** diff --git a/libraries/aws/defender/test/aws_iot_tests_defender.c b/libraries/aws/defender/test/aws_iot_tests_defender.c index 94aeb327e7..9c260b8a15 100644 --- a/libraries/aws/defender/test/aws_iot_tests_defender.c +++ b/libraries/aws/defender/test/aws_iot_tests_defender.c @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c index df93fc442f..73fe2a89db 100644 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c @@ -1,5 +1,5 @@ /* - * AWS IoT Defender V2.0.1 + * AWS IoT Defender V3.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of From 9d261aac54ba178cfcb04a18ff8253d789ad28c6 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 10 Oct 2019 10:02:04 -0700 Subject: [PATCH 286/844] Add build check script (#592) --- .travis.yml | 16 +++- demos/CMakeLists.txt | 9 ++- .../defender/src/aws_iot_defender_collector.c | 24 +++++- .../system/aws_iot_tests_defender_system.c | 16 +--- .../aws/jobs/src/aws_iot_jobs_subscription.c | 18 ++--- .../shadow/src/aws_iot_shadow_subscription.c | 18 ++--- .../test/unit/aws_iot_tests_shadow_parser.c | 20 ++--- .../common/test/unit/iot_tests_taskpool.c | 1 - .../mqtt/test/unit/iot_tests_mqtt_receive.c | 18 ++++- .../cbor/iot_serializer_tinycbor_encoder.c | 13 +++- .../test/unit/iot_tests_serializer_cbor.c | 48 ++++++++++-- ports/common/src/iot_network_mbedtls.c | 76 ++++++++++--------- ports/posix/posix.cmake | 9 +-- ports/win32/src/iot_threads_win32.c | 3 + scripts/ci_test_build.sh | 37 +++++++++ scripts/setup/ci_setup_linux.sh | 0 scripts/setup/ci_setup_osx.sh | 0 scripts/setup/ci_setup_windows.sh | 0 18 files changed, 219 insertions(+), 107 deletions(-) create mode 100755 scripts/ci_test_build.sh mode change 100644 => 100755 scripts/setup/ci_setup_linux.sh mode change 100644 => 100755 scripts/setup/ci_setup_osx.sh mode change 100644 => 100755 scripts/setup/ci_setup_windows.sh diff --git a/.travis.yml b/.travis.yml index 1ee1b7f7fc..ab92dafdc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,20 @@ compiler: # Matrix of tests to run. jobs: include: + # Build checks for pull requests. + - if: type = pull_request + env: RUN_TEST=build + - if: type = pull_request + env: RUN_TEST=build + os: osx + - if: type = pull_request + env: RUN_TEST=build + os: windows + compiler: msvc + # Documentation check for pull requests. + - if: type = pull_request + env: RUN_TEST=doc + # Library tests. - env: RUN_TEST=common - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - env: RUN_TEST=mqtt NETWORK_STACK=openssl @@ -23,8 +37,6 @@ jobs: - if: type = push compiler: gcc env: RUN_TEST=coverage - - if: type = pull_request - env: RUN_TEST=doc - os: osx env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - os: windows diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 4e849cae8b..2e30eb3bf7 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -6,10 +6,7 @@ set( DEMO_APP_SOURCES # When testing the demos, add the test sources and redefine the demo main function. if( ${IOT_BUILD_TESTS} ) list( APPEND DEMO_APP_SOURCES ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) - set_property( SOURCE app/iot_demo.c PROPERTY COMPILE_DEFINITIONS main=DemoMain ) - set_property( SOURCE ${PROJECT_SOURCE_DIR}/tests/iot_tests.c PROPERTY COMPILE_DEFINITIONS IOT_TEST_DEMO=1 ) - set( CONFIG_HEADER ${PROJECT_SOURCE_DIR}/tests/iot_config.h ) else() set( CONFIG_HEADER iot_config.h ) @@ -69,6 +66,12 @@ foreach( i RANGE ${DEMO_COUNT} ) target_compile_definitions( ${DEMO_EXE_NAME} PRIVATE RunDemo=${CURRENT_DEMO_FUNCTION} ) + # Set additional defines when testing the demos. + if( ${IOT_BUILD_TESTS} ) + target_compile_definitions( ${DEMO_EXE_NAME} PRIVATE + IOT_TEST_DEMO=1 ) + endif() + # Add the include path for the demo application. target_include_directories( ${DEMO_EXE_NAME} PRIVATE include ) diff --git a/libraries/aws/defender/src/aws_iot_defender_collector.c b/libraries/aws/defender/src/aws_iot_defender_collector.c index 36cfebb5c0..14c2e48a18 100644 --- a/libraries/aws/defender/src/aws_iot_defender_collector.c +++ b/libraries/aws/defender/src/aws_iot_defender_collector.c @@ -204,6 +204,7 @@ void AwsIotDefenderInternal_DeleteReport( void ) */ static void _serialize( void ) { + IotSerializerScalarData_t scalarData = { 0 }; IotSerializerError_t serializerError = IOT_SERIALIZER_SUCCESS; IotSerializerEncoderObject_t * pEncoderObject = &( _report.object ); @@ -234,15 +235,22 @@ static void _serialize( void ) assertNoError( serializerError ); /* Append key-value pair of "report_Id" which uses clock time. */ + scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + scalarData.value.u.signedInt = ( int64_t ) _AwsIotDefenderReportId; + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &headerMap, REPORTID_TAG, - IotSerializer_ScalarSignedInt( ( int64_t ) _AwsIotDefenderReportId ) ); + scalarData ); assertNoError( serializerError ); /* Append key-value pair of "version". */ + scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + scalarData.value.u.string.pString = ( uint8_t * ) VERSION_1_0; + scalarData.value.u.string.length = sizeof( VERSION_1_0 ) - 1; + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &headerMap, VERSION_TAG, - IotSerializer_ScalarTextString( VERSION_1_0 ) ); + scalarData ); assertNoError( serializerError ); /* Close the "header" map. */ @@ -307,6 +315,7 @@ static void _copyMetricsFlag( void ) static void _serializeTcpConnections( void * param1, const IotListDouble_t * pTcpConnectionsMetricsList ) { + IotSerializerScalarData_t scalarData = { 0 }; IotSerializerEncoderObject_t * pMetricsObject = ( IotSerializerEncoderObject_t * ) param1; AwsIotDefender_Assert( pMetricsObject != NULL ); @@ -376,8 +385,12 @@ static void _serializeTcpConnections( void * param1, { pMetricsTcpConnection = IotLink_Container( IotMetricsTcpConnection_t, pListIterator, link ); + scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + scalarData.value.u.string.pString = ( uint8_t * ) pMetricsTcpConnection->pRemoteAddress; + scalarData.value.u.string.length = pMetricsTcpConnection->addressLength; + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &connectionMap, REMOTE_ADDR_TAG, - IotSerializer_ScalarTextString( pMetricsTcpConnection->pRemoteAddress ) ); + scalarData ); assertNoError( serializerError ); } @@ -391,9 +404,12 @@ static void _serializeTcpConnections( void * param1, if( hasTotal ) { + scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + scalarData.value.u.signedInt = ( int64_t ) total; + serializerError = _pAwsIotDefenderEncoder->appendKeyValue( &establishedMap, TOTAL_TAG, - IotSerializer_ScalarSignedInt( total ) ); + scalarData ); assertNoError( serializerError ); } diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c index 73fe2a89db..e610cfb68a 100644 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c @@ -104,9 +104,6 @@ static void _copyDataCallbackFunction( void * param1, static bool _waitForAnyEvent( uint32_t timeoutSec ); -static void _assertEvent( AwsIotDefenderEventType_t event, - uint32_t timeoutSec ); - /* Wait for metrics to be accepted by defender service, for maxinum timeout. */ static void _waitForMetricsAccepted( uint32_t timeoutSec ); @@ -523,7 +520,6 @@ TEST( Defender_System, Metrics_TCP_connections_remote_addr_are_published ) TEST( Defender_System, Restart_and_updated_metrics_are_published ) { - char * pIotAddress = NULL; IotMqttError_t mqttError = IOT_MQTT_SUCCESS; /* Set "total count" for TCP connections metrics group. */ @@ -680,7 +676,7 @@ static void _stopMqttConnection( void ) _mqttConnectionStarted = false; } if (_mockedMqttConnection) - { + { IotTest_MqttMockCleanup(); _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; _mockedMqttConnection = false; @@ -715,16 +711,6 @@ static bool _waitForAnyEvent( uint32_t timeoutSec ) /*-----------------------------------------------------------*/ -static void _assertEvent( AwsIotDefenderEventType_t event, - uint32_t timeoutSec ) -{ - _waitForAnyEvent( timeoutSec ); - - TEST_ASSERT_EQUAL( event, _callbackInfo.eventType ); -} - -/*-----------------------------------------------------------*/ - /* Assert the cause of rejection is throttle. */ static void _assertRejectDueToThrottle( void ) { diff --git a/libraries/aws/jobs/src/aws_iot_jobs_subscription.c b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c index d73469af5a..8487757679 100644 --- a/libraries/aws/jobs/src/aws_iot_jobs_subscription.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c @@ -105,11 +105,10 @@ _jobsSubscription_t * _AwsIotJobs_FindSubscription( const char * pThingName, { _jobsSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = - { - .pThingName = pThingName, - .thingNameLength = thingNameLength - }; + AwsIotThingName_t thingName = { 0 }; + + thingName.pThingName = pThingName; + thingName.thingNameLength = thingNameLength; /* Search the list for an existing subscription for Thing Name. */ pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotJobsSubscriptions ), @@ -440,11 +439,10 @@ AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequ AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; _jobsSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = - { - .pThingName = pRequestInfo->pThingName, - .thingNameLength = pRequestInfo->thingNameLength - }; + AwsIotThingName_t thingName = { 0 }; + + thingName.pThingName = pRequestInfo->pThingName; + thingName.thingNameLength = pRequestInfo->thingNameLength; IotLogInfo( "Removing persistent subscriptions for %.*s.", pRequestInfo->thingNameLength, diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index deec5f51d6..46c0ccae08 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -105,11 +105,10 @@ _shadowSubscription_t * _AwsIotShadow_FindSubscription( const char * pThingName, { _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = - { - .pThingName = pThingName, - .thingNameLength = thingNameLength - }; + AwsIotThingName_t thingName = { 0 }; + + thingName.pThingName = pThingName; + thingName.thingNameLength = thingNameLength; /* Search the list for an existing subscription for Thing Name. */ pSubscriptionLink = IotListDouble_FindFirstMatch( &( _AwsIotShadowSubscriptions ), @@ -430,11 +429,10 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio AwsIotSubscriptionInfo_t subscriptionInfo = { 0 }; _shadowSubscription_t * pSubscription = NULL; IotLink_t * pSubscriptionLink = NULL; - AwsIotThingName_t thingName = - { - .pThingName = pThingName, - .thingNameLength = thingNameLength - }; + AwsIotThingName_t thingName = { 0 }; + + thingName.pThingName = pThingName; + thingName.thingNameLength = thingNameLength; IotLogInfo( "Removing persistent subscriptions for %.*s.", thingNameLength, diff --git a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c index 62ab20a360..a284d21b98 100644 --- a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c +++ b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c @@ -248,7 +248,7 @@ TEST( Shadow_Unit_Parser, JsonValid ) { /* Parse JSON document with string, int, bool, and null. */ { - const char pJsonDocument[ 81 ] = "{\"name\" \n\r:\n \"John Smith\", \"age\" :\n\r 30, \n \"isAlive\" : true, \r \"spouse\":null}"; + const char pJsonDocument[ 82 ] = "{\"name\" \n\r:\n \"John Smith\", \"age\" :\n\r 30, \n \"isAlive\" : true, \r \"spouse\":null}"; size_t jsonDocumentLength = 81; _parseJson( true, pJsonDocument, jsonDocumentLength, "name", "\"John Smith\"", 12 ); @@ -262,7 +262,7 @@ TEST( Shadow_Unit_Parser, JsonValid ) /* Parse JSON document with objects and arrays. */ { - const char pJsonDocument[ 90 ] = "{\"object\" : { \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}}"; + const char pJsonDocument[ 91 ] = "{\"object\" : { \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}}"; size_t jsonDocumentLength = 90; _parseJson( true, pJsonDocument, jsonDocumentLength, "object", "{ \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}", 76 ); @@ -305,7 +305,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) { /* JSON key not followed by a : */ { - const char pJsonDocument[ 15 ] = "{\"string\" "; + const char pJsonDocument[ 16 ] = "{\"string\" "; size_t jsonDocumentLength = 15; _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); @@ -313,7 +313,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* JSON value not followed by a , */ { - const char pJsonDocument[ 29 ] = "{\"int\": 10 \"string\": \"hello\"}"; + const char pJsonDocument[ 30 ] = "{\"int\": 10 \"string\": \"hello\"}"; size_t jsonDocumentLength = 29; _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); @@ -321,7 +321,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* JSON key with no value. */ { - const char pJsonDocument[ 17 ] = "{\"string\": "; + const char pJsonDocument[ 18 ] = "{\"string\": "; size_t jsonDocumentLength = 17; _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); @@ -329,7 +329,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* Unterminated JSON primitive. */ { - const char pJsonDocument[ 11 ] = "{\"int\":1000"; + const char pJsonDocument[ 12 ] = "{\"int\":1000"; size_t jsonDocumentLength = 11; _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); @@ -337,7 +337,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* Unterminated JSON string (ending is an escaped quote). */ { - const char pJsonDocument[ 14 ] = "{\"string\": \"\\\""; + const char pJsonDocument[ 15 ] = "{\"string\": \"\\\""; size_t jsonDocumentLength = 14; _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); @@ -345,7 +345,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* Unterminated JSON string (ending is not a quote). */ { - const char pJsonDocument[ 14 ] = "{\"string\": \" \\"; + const char pJsonDocument[ 15 ] = "{\"string\": \" \\"; size_t jsonDocumentLength = 14; _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); @@ -353,7 +353,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* Unterminated JSON object. */ { - const char pJsonDocument[ 41 ] = "{\"object\": {\"key\": { \"nestedKey\":\"value\"}"; + const char pJsonDocument[ 42 ] = "{\"object\": {\"key\": { \"nestedKey\":\"value\"}"; size_t jsonDocumentLength = 41; _parseJson( false, pJsonDocument, jsonDocumentLength, "object", NULL, 0 ); @@ -361,7 +361,7 @@ TEST( Shadow_Unit_Parser, JsonInvalid ) /* Unterminated JSON array. */ { - const char pJsonDocument[ 26 ] = "{\"array\": [[1,2,3],[1,2,3]"; + const char pJsonDocument[ 27 ] = "{\"array\": [[1,2,3],[1,2,3]"; size_t jsonDocumentLength = 26; _parseJson( false, pJsonDocument, jsonDocumentLength, "array", NULL, 0 ); diff --git a/libraries/standard/common/test/unit/iot_tests_taskpool.c b/libraries/standard/common/test/unit/iot_tests_taskpool.c index 1072ed4d80..5e18b47c9a 100644 --- a/libraries/standard/common/test/unit/iot_tests_taskpool.c +++ b/libraries/standard/common/test/unit/iot_tests_taskpool.c @@ -451,7 +451,6 @@ TEST( Common_Unit_Task_Pool, CreateDestroyMaxThreads ) /* Create a task pool a tweak max threads up & down. */ { - uint32_t count; IotTaskPoolJobStorage_t jobsStorage[ 2 * TEST_TASKPOOL_MAX_THREADS ]; IotTaskPoolJob_t jobs[ 2 * TEST_TASKPOOL_MAX_THREADS ]; IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = 6, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index 3108c1f76c..c293520973 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -117,7 +117,7 @@ static const uint8_t _pPingrespTemplate[] = { 0xd0, 0x00 }; */ #define INITIALIZE_OPERATION( name ) \ { \ - .link = { 0 }, .incomingPublish = false, .pMqttConnection = _pMqttConnection, \ + .link = { 0 }, .incomingPublish = false, .pMqttConnection = NULL, \ .jobStorage = IOT_TASKPOOL_JOB_STORAGE_INITIALIZER, .job = IOT_TASKPOOL_JOB_INITIALIZER, \ .u.operation = \ { \ @@ -832,6 +832,8 @@ TEST( MQTT_Unit_Receive, ConnackValid ) uint8_t i = 0; _mqttOperation_t connect = INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); + connect.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.u.operation.notify.waitSemaphore ), @@ -902,6 +904,8 @@ TEST( MQTT_Unit_Receive, ConnackInvalid ) { _mqttOperation_t connect = INITIALIZE_OPERATION( IOT_MQTT_CONNECT ); + connect.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( connect.u.operation.notify.waitSemaphore ), @@ -1217,6 +1221,8 @@ TEST( MQTT_Unit_Receive, PubackValid ) { _mqttOperation_t publish = INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); + publish.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.u.operation.notify.waitSemaphore ), @@ -1260,6 +1266,8 @@ TEST( MQTT_Unit_Receive, PubackInvalid ) { _mqttOperation_t publish = INITIALIZE_OPERATION( IOT_MQTT_PUBLISH_TO_SERVER ); + publish.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( publish.u.operation.notify.waitSemaphore ), @@ -1355,6 +1363,8 @@ TEST( MQTT_Unit_Receive, SubackValid ) _mqttOperation_t subscribe = INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); IotMqttSubscription_t pSubscriptions[ 2 ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER }; + subscribe.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.u.operation.notify.waitSemaphore ), @@ -1464,6 +1474,8 @@ TEST( MQTT_Unit_Receive, SubackInvalid ) { _mqttOperation_t subscribe = INITIALIZE_OPERATION( IOT_MQTT_SUBSCRIBE ); + subscribe.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( subscribe.u.operation.notify.waitSemaphore ), @@ -1585,6 +1597,8 @@ TEST( MQTT_Unit_Receive, UnsubackValid ) { _mqttOperation_t unsubscribe = INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); + unsubscribe.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.u.operation.notify.waitSemaphore ), @@ -1628,6 +1642,8 @@ TEST( MQTT_Unit_Receive, UnsubackInvalid ) { _mqttOperation_t unsubscribe = INITIALIZE_OPERATION( IOT_MQTT_UNSUBSCRIBE ); + unsubscribe.pMqttConnection = _pMqttConnection; + /* Create the wait semaphore so notifications don't crash. The value of * this semaphore will not be checked, so the maxValue argument is arbitrary. */ TEST_ASSERT_EQUAL_INT( true, IotSemaphore_Create( &( unsubscribe.u.operation.notify.waitSemaphore ), diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c index 0cbfb7aae8..7551e6d49a 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c @@ -223,7 +223,11 @@ static IotSerializerError_t _openContainerWithKey( IotSerializerEncoderObject_t IotSerializerEncoderObject_t * pNewEncoderObject, size_t length ) { - IotSerializerScalarData_t keyScalarData = IotSerializer_ScalarTextString( pKey ); + IotSerializerScalarData_t keyScalarData = { 0 }; + + keyScalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + keyScalarData.value.u.string.pString = ( uint8_t * ) pKey; + keyScalarData.value.u.string.length = strlen( pKey ); IotSerializerError_t returnedError = _append( pEncoderObject, keyScalarData ); @@ -304,7 +308,12 @@ static IotSerializerError_t _appendKeyValue( IotSerializerEncoderObject_t * pEnc const char * pKey, IotSerializerScalarData_t scalarData ) { - IotSerializerScalarData_t keyScalarData = IotSerializer_ScalarTextString( pKey ); + IotSerializerScalarData_t keyScalarData = { 0 }; + + keyScalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + keyScalarData.value.u.string.pString = ( uint8_t * ) pKey; + keyScalarData.value.u.string.length = strlen( pKey ); + IotSerializerError_t returnedError = _append( pEncoderObject, keyScalarData ); /* Buffer too small is a special error case that serialization should continue. */ diff --git a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c index 66da2041fb..475c40ed8d 100644 --- a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c +++ b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c @@ -121,9 +121,13 @@ TEST( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ) TEST( Serializer_Unit_CBOR, Encoder_append_integer ) { int64_t value = 6; + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; + scalarData.value.u.signedInt = value; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &_encoderObject, IotSerializer_ScalarSignedInt( value ) ) ); + _pCborEncoder->append( &_encoderObject, scalarData ) ); /* --- Verification --- */ @@ -147,9 +151,14 @@ TEST( Serializer_Unit_CBOR, Encoder_append_integer ) TEST( Serializer_Unit_CBOR, Encoder_append_text_string ) { char * str = "hello world"; + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + scalarData.value.u.string.pString = ( uint8_t * ) str; + scalarData.value.u.string.length = strlen( str ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &_encoderObject, IotSerializer_ScalarTextString( str ) ) ); + _pCborEncoder->append( &_encoderObject, scalarData ) ); /* --- Verification --- */ @@ -172,9 +181,14 @@ TEST( Serializer_Unit_CBOR, Encoder_append_byte_string ) { uint8_t inputBytes[] = "hello world"; size_t inputLength = strlen( ( const char * ) inputBytes ); + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_BYTE_STRING; + scalarData.value.u.string.pString = ( uint8_t * ) inputBytes; + scalarData.value.u.string.length = inputLength; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &_encoderObject, IotSerializer_ScalarByteString( inputBytes, inputLength ) ) ); + _pCborEncoder->append( &_encoderObject, scalarData ) ); /* --- Verification --- */ @@ -208,12 +222,17 @@ TEST( Serializer_Unit_CBOR, Encoder_open_a_scalar ) TEST( Serializer_Unit_CBOR, Encoder_open_map ) { IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + scalarData.value.u.string.pString = ( uint8_t * ) "value"; + scalarData.value.u.string.length = 5; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborEncoder->openContainer( &_encoderObject, &mapObject, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->appendKeyValue( &mapObject, "key", IotSerializer_ScalarTextString( "value" ) ) ); + _pCborEncoder->appendKeyValue( &mapObject, "key", scalarData ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborEncoder->closeContainer( &_encoderObject, &mapObject ) ); @@ -246,14 +265,19 @@ TEST( Serializer_Unit_CBOR, Encoder_open_array ) IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; int64_t numberArray[] = { 3, 2, 1 }; + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborEncoder->openContainer( &_encoderObject, &arrayObject, 3 ) ); for( i = 0; i < 3; i++ ) { + scalarData.value.u.signedInt = numberArray[ i ]; + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); + _pCborEncoder->append( &arrayObject, scalarData ) ); } TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, @@ -289,6 +313,11 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_map ) { IotSerializerEncoderObject_t mapObject_1 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; IotSerializerEncoderObject_t mapObject_2 = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_TEXT_STRING; + scalarData.value.u.string.pString = ( uint8_t * ) "value"; + scalarData.value.u.string.length = 5; TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborEncoder->openContainer( &_encoderObject, &mapObject_1, 1 ) ); @@ -297,7 +326,7 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_map ) _pCborEncoder->openContainerWithKey( &mapObject_1, "map1", &mapObject_2, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->appendKeyValue( &mapObject_2, "key", IotSerializer_ScalarTextString( "value" ) ) ); + _pCborEncoder->appendKeyValue( &mapObject_2, "key", scalarData ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborEncoder->closeContainer( &mapObject_1, &mapObject_2 ) ); @@ -337,6 +366,9 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) uint8_t i = 0; IotSerializerEncoderObject_t mapObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP; IotSerializerEncoderObject_t arrayObject = IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY; + IotSerializerScalarData_t scalarData = { 0 }; + + scalarData.type = IOT_SERIALIZER_SCALAR_SIGNED_INT; int64_t numberArray[] = { 3, 2, 1 }; @@ -348,8 +380,10 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) for( i = 0; i < 3; i++ ) { + scalarData.value.u.signedInt = numberArray[ i ]; + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->append( &arrayObject, IotSerializer_ScalarSignedInt( numberArray[ i ] ) ) ); + _pCborEncoder->append( &arrayObject, scalarData ) ); } TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index 8e44b1a141..e3261b00c7 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -79,27 +79,29 @@ /* Logging macro for mbed TLS errors. */ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - #define _logMbedtlsError( error, pConnection, pMessage ) \ - { \ - char pErrorMessage[ 80 ] = { 0 }; \ - mbedtls_strerror( error, pErrorMessage, 80 ); \ - \ - if( pConnection != NULL ) \ - { \ - IotLogError( "(Network connection %p) %s error: %s. ", \ - pConnection, \ - pMessage, \ - pErrorMessage ); \ - } \ - else \ - { \ - IotLogError( "%s error: %s. ", \ - pMessage, \ - pErrorMessage ); \ - } \ + #define _logLibraryError( error, pMessage ) \ + { \ + char pErrorMessage[ 80 ] = { 0 }; \ + mbedtls_strerror( error, pErrorMessage, 80 ); \ + \ + IotLogError( "%s error: %s. ", \ + pMessage, \ + pErrorMessage ); \ + } + + #define _logConnectionError( error, pConnection, pMessage ) \ + { \ + char pErrorMessage[ 80 ] = { 0 }; \ + mbedtls_strerror( error, pErrorMessage, 80 ); \ + \ + IotLogError( "(Network connection %p) %s error: %s. ", \ + pConnection, \ + pMessage, \ + pErrorMessage ); \ } #else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - #define _logMbedtlsError( error, pConnection, pMessage ) + #define _logLibraryError( error, pMessage ) + #define _logConnectionError( error, pConnection, pMessage ) #endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ /* @@ -391,7 +393,7 @@ static void _receiveThread( void * pArgument ) if( pollStatus < 0 ) { /* Error during poll. */ - _logMbedtlsError( pollStatus, pNetworkConnection, "Error polling network connection." ); + _logConnectionError( pollStatus, pNetworkConnection, "Error polling network connection." ); break; } else @@ -451,7 +453,7 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, if( mbedtlsError < 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to read root CA certificate file." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to read root CA certificate file." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -468,7 +470,7 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, if( mbedtlsError < 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to read client certificate file." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to read client certificate file." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -486,7 +488,7 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to read client certificate private key file." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to read client certificate private key file." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -502,7 +504,7 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to configure credentials." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to configure credentials." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -545,7 +547,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set default SSL configuration." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set default SSL configuration." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -579,7 +581,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set ALPN protocols." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set ALPN protocols." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -623,7 +625,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set TLS MFLN." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set TLS MFLN." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -636,7 +638,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set up mbed TLS SSL context." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set up mbed TLS SSL context." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -656,7 +658,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to set server name." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set server name." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -671,7 +673,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to perform SSL handshake." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to perform SSL handshake." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -745,7 +747,7 @@ IotNetworkError_t IotNetworkMbedtls_Init( void ) if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, NULL, "Failed to seed PRNG in initialization." ); + _logLibraryError( mbedtlsError, "Failed to seed PRNG in initialization." ); status = IOT_NETWORK_FAILURE; } else @@ -854,7 +856,7 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, NULL, "Failed to establish connection." ); + _logLibraryError( mbedtlsError, "Failed to establish connection." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -864,7 +866,7 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNewNetworkConnection, "Failed to set blocking mode." ); + _logConnectionError( mbedtlsError, pNewNetworkConnection, "Failed to set blocking mode." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -1056,13 +1058,13 @@ size_t IotNetworkMbedtls_Send( void * pConnection, /* Log errors. */ if( mbedtlsError < 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to send." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to send." ); bytesSent = 0; } } else { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Cannot send right now." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Cannot send right now." ); } IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); @@ -1113,7 +1115,7 @@ size_t IotNetworkMbedtls_Receive( void * pConnection, else if( mbedtlsError < 0 ) { /* Error receiving, exit. */ - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to receive." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to receive." ); break; } else @@ -1147,7 +1149,7 @@ IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ) if( mbedtlsError != 0 ) { - _logMbedtlsError( mbedtlsError, pNetworkConnection, "Failed to notify peer of SSL connection close." ); + _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to notify peer of SSL connection close." ); } else { diff --git a/ports/posix/posix.cmake b/ports/posix/posix.cmake index 6c55f4b928..11ca9ea319 100644 --- a/ports/posix/posix.cmake +++ b/ports/posix/posix.cmake @@ -1,12 +1,11 @@ -include( CheckCCompilerFlag ) include( CheckTypeSize ) include( CheckFunctionExists ) -# Check that the -lrt flag works. -check_c_compiler_flag( -lrt HAS_C_FLAG_lrt ) +# Check that the POSIX realtime library is available. +find_library( LIB_REALTIME rt ) -if( NOT HAS_C_FLAG_lrt ) - message( FATAL_ERROR "Compiler flag -lrt must be supported." ) +if( ${LIB_REALTIME} STREQUAL "LIB_REALTIME-NOTFOUND" ) + message( FATAL_ERROR "POSIX realtime library (librt) is not available." ) endif() # Check for POSIX threads. diff --git a/ports/win32/src/iot_threads_win32.c b/ports/win32/src/iot_threads_win32.c index 64698fdcc1..d297dd9d59 100644 --- a/ports/win32/src/iot_threads_win32.c +++ b/ports/win32/src/iot_threads_win32.c @@ -153,6 +153,9 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, HANDLE newThread = NULL; _threadInfo_t * pThreadInfo = NULL; + /* Priority is not used on Windows. */ + ( void ) priority; + /* Determine the stack size of the thread to create. */ SIZE_T threadStackSize = 0; diff --git a/scripts/ci_test_build.sh b/scripts/ci_test_build.sh new file mode 100755 index 0000000000..cbaf0da097 --- /dev/null +++ b/scripts/ci_test_build.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +# Travis CI uses this script to test various build configurations. + +# Exit on any nonzero return code. +set -e + +# Treat warnings as errors. +if [ "$TRAVIS_COMPILER" = "clang" ]; then + COMPILER_OPTIONS+=" -Werror" +elif [ "$TRAVIS_COMPILER" = "msvc" ]; then + COMPILER_OPTIONS+=" /W4 /wd4200 /WX" +fi + +# Build demos. +cmake .. -DCMAKE_C_FLAGS="$COMPILER_OPTIONS" + +if [ "$TRAVIS_OS_NAME" = "windows" ]; then + MSBuild.exe ALL_BUILD.vcxproj -m -clp:summary -verbosity:minimal +else + make -j2 +fi + +# Unity places function declarations within other functions. +# Disable the MSVC warning about this. +if [ "$TRAVIS_COMPILER" = "msvc" ]; then + COMPILER_OPTIONS+=" /wd4210" +fi + +# Build tests. Enable all logging. +cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" + +if [ "$TRAVIS_OS_NAME" = "windows" ]; then + MSBuild.exe ALL_BUILD.vcxproj -m -clp:summary -verbosity:minimal +else + make -j2 +fi diff --git a/scripts/setup/ci_setup_linux.sh b/scripts/setup/ci_setup_linux.sh old mode 100644 new mode 100755 diff --git a/scripts/setup/ci_setup_osx.sh b/scripts/setup/ci_setup_osx.sh old mode 100644 new mode 100755 diff --git a/scripts/setup/ci_setup_windows.sh b/scripts/setup/ci_setup_windows.sh old mode 100644 new mode 100755 From 881736f0601af8892f06b14e7c3a7b0f93156760 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 10 Oct 2019 10:34:40 -0700 Subject: [PATCH 287/844] Update coverage script for new directory structure (#597) --- scripts/ci_test_build.sh | 1 + scripts/ci_test_coverage.sh | 4 ++-- tests/iot_tests.c | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ci_test_build.sh b/scripts/ci_test_build.sh index cbaf0da097..da8747f5b9 100755 --- a/scripts/ci_test_build.sh +++ b/scripts/ci_test_build.sh @@ -28,6 +28,7 @@ if [ "$TRAVIS_COMPILER" = "msvc" ]; then fi # Build tests. Enable all logging. +rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" if [ "$TRAVIS_OS_NAME" = "windows" ]; then diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index 2b309e2d2d..ac88b88509 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -51,10 +51,10 @@ trap "delete_jobs" EXIT delete_jobs trap - EXIT -# Generate code coverage results, but only for files in lib/. +# Generate code coverage results, but only for files in libraries/. lcov --directory . --capture --output-file coverage.info lcov --remove coverage.info '*demo*' --output-file coverage.info -lcov --remove coverage.info '*platform*' --output-file coverage.info +lcov --remove coverage.info '*ports*' --output-file coverage.info lcov --remove coverage.info '*tests*' --output-file coverage.info lcov --remove coverage.info '*third_party*' --output-file coverage.info diff --git a/tests/iot_tests.c b/tests/iot_tests.c index 2ce52fab2c..d284071634 100644 --- a/tests/iot_tests.c +++ b/tests/iot_tests.c @@ -47,7 +47,6 @@ #if IOT_TEST_DEMO == 1 extern int DemoMain( int argc, char ** argv ); - #else extern void RunTests( bool disableNetworkTests, bool disableLongTests ); From 23e273a2ff09d749f6c1485a77bc5e25d97755a9 Mon Sep 17 00:00:00 2001 From: markrtuttle Date: Thu, 10 Oct 2019 14:00:27 -0400 Subject: [PATCH 288/844] Modify CBMC proof builds for new source tree organization. (#596) --- cbmc/proofs/DeserializeConnack/Makefile | 2 +- cbmc/proofs/DeserializePingresp/Makefile | 2 +- cbmc/proofs/DeserializePuback/Makefile | 2 +- cbmc/proofs/DeserializePublish/Makefile | 2 +- cbmc/proofs/DeserializeSuback/Makefile | 4 ++-- cbmc/proofs/DeserializeUnsuback/Makefile | 2 +- cbmc/proofs/Makefile.common | 13 ++++++++----- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cbmc/proofs/DeserializeConnack/Makefile b/cbmc/proofs/DeserializeConnack/Makefile index 2860fd43e0..c2b0f10aef 100644 --- a/cbmc/proofs/DeserializeConnack/Makefile +++ b/cbmc/proofs/DeserializeConnack/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializePingresp/Makefile b/cbmc/proofs/DeserializePingresp/Makefile index 6389a45b7a..c5bca579f2 100644 --- a/cbmc/proofs/DeserializePingresp/Makefile +++ b/cbmc/proofs/DeserializePingresp/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializePuback/Makefile b/cbmc/proofs/DeserializePuback/Makefile index f16423b336..46515d8b2a 100644 --- a/cbmc/proofs/DeserializePuback/Makefile +++ b/cbmc/proofs/DeserializePuback/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializePublish/Makefile b/cbmc/proofs/DeserializePublish/Makefile index 2e8c084b9c..3d76675473 100644 --- a/cbmc/proofs/DeserializePublish/Makefile +++ b/cbmc/proofs/DeserializePublish/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/DeserializeSuback/Makefile b/cbmc/proofs/DeserializeSuback/Makefile index 8c61e7e4aa..854cf3ca49 100644 --- a/cbmc/proofs/DeserializeSuback/Makefile +++ b/cbmc/proofs/DeserializeSuback/Makefile @@ -11,8 +11,8 @@ UNWINDING = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_subscription.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_subscription.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ DEF = -DBUFFER_SIZE=$(BUFFER_SIZE) diff --git a/cbmc/proofs/DeserializeUnsuback/Makefile b/cbmc/proofs/DeserializeUnsuback/Makefile index be9f045857..f5a359a21e 100644 --- a/cbmc/proofs/DeserializeUnsuback/Makefile +++ b/cbmc/proofs/DeserializeUnsuback/Makefile @@ -5,6 +5,6 @@ ABSTRACTIONS = \ OBJS = \ $(ENTRY)_harness.goto \ - $(MQTT)/lib/source/mqtt/iot_mqtt_serialize.goto \ + $(MQTT)/libraries/standard/mqtt/src/iot_mqtt_serialize.goto \ include ../Makefile.common diff --git a/cbmc/proofs/Makefile.common b/cbmc/proofs/Makefile.common index b1fa382eb9..d0de15a72b 100644 --- a/cbmc/proofs/Makefile.common +++ b/cbmc/proofs/Makefile.common @@ -19,14 +19,17 @@ VIEWER ?= cbmc-viewer # Build goto binaries with options taken from top-level cmake INC = \ - -I$(MQTT)/platform/ports/posix/types \ - -I$(MQTT)/lib/include \ - -I$(MQTT)/platform/include \ - -I$(MQTT)/demos/include \ + -I$(MQTT)/libraries/standard/mqtt/include \ -I$(MQTT)/demos \ + -I$(MQTT)/libraries/platform/.. \ + -I$(MQTT)/libraries/platform \ + -I$(MQTT)/ports/common/include \ + -I$(MQTT)/libraries/standard/common/include \ + \ + -I$(MQTT)/libraries/standard/mqtt/src \ DEF += \ - -DIOT_SYSTEM_TYPES_FILE=\"iot_platform_types_posix.h\" \ + -DIOT_SYSTEM_TYPES_FILE=\"$(MQTT)/ports/posix/include/iot_platform_types_posix.h\" \ -Diotmqtt_EXPORTS \ CFLAGS += $(CFLAGS2) $(INC) $(DEF) -std=gnu99 From f2ee42bf17d4da5c7e199c885a266ac28bd9b727 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 10 Oct 2019 17:35:51 -0700 Subject: [PATCH 289/844] Add jobs demo (#598) --- demos/CMakeLists.txt | 9 +- demos/src/aws_iot_demo_jobs.c | 882 ++++++++++++++++++ demos/src/aws_iot_demo_shadow.c | 70 +- doc/lib/jobs.txt | 68 +- doc/lib/mqtt.txt | 2 +- doc/lib/shadow.txt | 2 +- doc/plantuml/images/jobs_demo.png | Bin 0 -> 48193 bytes doc/plantuml/jobs_demo.pu | 39 + .../jobs/include/types/aws_iot_jobs_types.h | 2 +- 9 files changed, 1027 insertions(+), 47 deletions(-) create mode 100644 demos/src/aws_iot_demo_jobs.c create mode 100644 doc/plantuml/images/jobs_demo.png create mode 100644 doc/plantuml/jobs_demo.pu diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 2e30eb3bf7..8fda1c7dd3 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -29,15 +29,18 @@ set( DEMO_COMMON_HEADERS set( DEMO_MAIN_FUNCTIONS RunMqttDemo RunShadowDemo - RunDefenderDemo ) + RunDefenderDemo + RunJobsDemo ) set( DEMO_SOURCES src/iot_demo_mqtt.c src/aws_iot_demo_shadow.c - src/aws_iot_demo_defender.c ) + src/aws_iot_demo_defender.c + src/aws_iot_demo_jobs.c ) set( DEMO_LIBRARY_DEPENDENCY iotmqtt "awsiotshadow\\\;iotserializer" - awsiotdefender ) + awsiotdefender + "awsiotjobs\\\;iotserializer" ) # Get the list length to iterate over it. Since CMake indexes from 0, subtract # 1 for the iteration range. diff --git a/demos/src/aws_iot_demo_jobs.c b/demos/src/aws_iot_demo_jobs.c new file mode 100644 index 0000000000..3fb342cd7a --- /dev/null +++ b/demos/src/aws_iot_demo_jobs.c @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_demo_jobs.c + * @brief Demonstrates use of the AWS IoT Jobs library. + * + * This program sets a Jobs Notify-Next callback and waits for Job documents to arrive. + * It will then take action based on the Job document. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include + +/* Set up logging for this demo. */ +#include "iot_demo_logging.h" + +/* Platform layer includes. */ +#include "platform/iot_clock.h" + +/* MQTT include. */ +#include "iot_mqtt.h" + +/* Jobs include. */ +#include "aws_iot_jobs.h" + +/* JSON utilities include. */ +#include "iot_json_utils.h" + +/* Atomics include. */ +#include "iot_atomic.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief The timeout for Jobs and MQTT operations in this demo. + */ +#define TIMEOUT_MS ( ( uint32_t ) 5000u ) + +/** + * @brief The keep-alive interval used for this demo. + * + * An MQTT ping request will be sent periodically at this interval. + */ +#define KEEP_ALIVE_SECONDS ( ( uint16_t ) 60u ) + +/** + * @brief The JSON key of the Job ID. + * + * Job documents are JSON documents received from the AWS IoT Jobs service. + * All Job documents will contain this key, whose value represents the unique + * identifier of a Job. + */ +#define JOB_ID_KEY "jobId" + +/** + * @brief The length of #JOB_ID_KEY. + */ +#define JOB_ID_KEY_LENGTH ( sizeof( JOB_ID_KEY ) - 1 ) + +/** + * @brief The JSON key of the Job document. + * + * Job documents are JSON documents received from the AWS IoT Jobs service. + * All Job documents will contain this key, whose value is an application-specific + * JSON document. + */ +#define JOB_DOC_KEY "jobDocument" + +/** + * @brief The length of #JOB_DOC_KEY. + */ +#define JOB_DOC_KEY_LENGTH ( sizeof( JOB_DOC_KEY ) - 1 ) + +/** + * @brief The JSON key whose value represents the action this demo should take. + * + * This demo program expects this key to be in the Job document. It is a key + * specific to this demo. + */ +#define JOB_ACTION_KEY "action" + +/** + * @brief The length of #JOB_ACTION_KEY. + */ +#define JOB_ACTION_KEY_LENGTH ( sizeof( JOB_ACTION_KEY ) - 1 ) + +/** + * @brief A message associated with the Job action. + * + * This demo program expects this key to be in the Job document if the "action" + * is either "publish" or "print". It represents the message that should be + * published or printed, respectively. + */ +#define JOB_MESSAGE_KEY "message" + +/** + * @brief The length of #JOB_MESSAGE_KEY. + */ +#define JOB_MESSAGE_KEY_LENGTH ( sizeof( JOB_MESSAGE_KEY ) - 1 ) + +/** + * @brief An MQTT topic associated with the Job "publish" action. + * + * This demo program expects this key to be in the Job document if the "action" + * is "publish". It represents the MQTT topic on which the message should be + * published. + */ +#define JOB_TOPIC_KEY "topic" + +/** + * @brief The length of #JOB_TOPIC_KEY. + */ +#define JOB_TOPIC_KEY_LENGTH ( sizeof( JOB_TOPIC_KEY ) - 1 ) + +/** + * @brief The minimum length of a string in a JSON Job document. + * + * At the very least the Job ID must have the quotes that identify it as a JSON + * string and 1 character for the string itself (the string must not be empty). + */ +#define JSON_STRING_MIN_LENGTH ( ( size_t ) 3 ) + +/** + * @brief The maximum length of a Job ID. + * + * This limit is defined by AWS service limits. See the following page for more + * information. + * + * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits + */ +#define JOB_ID_MAX_LENGTH ( ( size_t ) 64 ) + +/** + * @brief A value passed as context to #_operationCompleteCallback to specify that + * it should set the #JOBS_DEMO_FINISHED flag. + */ +#define JOBS_DEMO_SHOULD_EXIT ( ( void * ) ( ( intptr_t ) 1 ) ) + +/** + * @brief Flag value for signaling that the demo is still running. + * + * The initial value of #_exitFlag. + */ +#define JOBS_DEMO_RUNNING ( ( uint32_t ) 0 ) + +/** + * @brief Flag value for signaling that the demo is finished. + * + * #_exitFlag will be set to this when a Job document with { "action": "exit" } + * is received. + */ +#define JOBS_DEMO_FINISHED ( ( uint32_t ) 1 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Currently supported actions that a Job document can specify. + */ +typedef enum _jobAction +{ + JOB_ACTION_PRINT, /**< Print a message. */ + JOB_ACTION_PUBLISH, /**< Publish a message to an MQTT topic. */ + JOB_ACTION_EXIT, /**< Exit the demo. */ + JOB_ACTION_UNKNOWN /**< Unknown action. */ +} _jobAction_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Used to print log messages that do not contain any metadata. + */ +static IotLogConfig_t _logHideAll = { .hideLogLevel = true, .hideLibraryName = true, .hideTimestring = true }; + +/** + * @brief A flag that signals the end of the demo. + * + * When a Job document is received with { "action": "exit" }, the demo will set + * this flag and exit. + */ +static uint32_t _exitFlag = 0; + +/*-----------------------------------------------------------*/ + +/** + * @brief Initialize the libraries required for this demo. + * + * Initialize the MQTT and Jobs libraries. If the Jobs library fails + * to initialize, the MQTT library is cleaned up. + * + * @return `EXIT_SUCCESS` if all initialization succeeds; `EXIT_FAILURE` otherwise. + */ +static int _initializeDemo( void ) +{ + int status = EXIT_SUCCESS; + IotMqttError_t mqttInitStatus = IOT_MQTT_SUCCESS; + AwsIotJobsError_t jobsInitStatus = AWS_IOT_JOBS_SUCCESS; + + mqttInitStatus = IotMqtt_Init(); + + if( mqttInitStatus != IOT_MQTT_SUCCESS ) + { + status = EXIT_FAILURE; + } + else + { + jobsInitStatus = AwsIotJobs_Init( 0 ); + + if( jobsInitStatus != AWS_IOT_JOBS_SUCCESS ) + { + IotMqtt_Cleanup(); + status = EXIT_FAILURE; + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Clean up the libraries initialized by #_initializeDemo. + * + * @note Must not be called if #_initializeDemo was not successfully called. + */ +static void _cleanupDemo( void ) +{ + AwsIotJobs_Cleanup(); + IotMqtt_Cleanup(); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Establish a new connection to the MQTT server for the Jobs demo. + * + * @param[in] pIdentifier NULL-terminated MQTT client identifier. The Jobs + * demo will use the Thing Name as the client identifier. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection. + * @param[in] pNetworkInterface The network interface to use for this demo. + * @param[out] pMqttConnection Set to the handle to the new MQTT connection. + * + * @return `EXIT_SUCCESS` if the connection is successfully established; `EXIT_FAILURE` + * otherwise. + */ +static int _establishMqttConnection( const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface, + IotMqttConnection_t * const pMqttConnection ) +{ + int status = EXIT_SUCCESS; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; + IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; + + /* Set the members of the network info not set by the initializer. This + * struct provided information on the transport layer to the MQTT connection. */ + networkInfo.createNetworkConnection = true; + networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.pNetworkInterface = pNetworkInterface; + + /* Set the members of the connection info not set by the initializer. */ + connectInfo.awsIotMqttMode = true; + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; + + /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ + connectInfo.pClientIdentifier = pIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); + + IotLogInfo( "Thing Name is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); + + /* Establish the MQTT connection. */ + connectStatus = IotMqtt_Connect( &networkInfo, &connectInfo, TIMEOUT_MS, pMqttConnection ); + + if( connectStatus != IOT_MQTT_SUCCESS ) + { + IotLogError( "MQTT CONNECT returned error %s.", IotMqtt_strerror( connectStatus ) ); + + status = EXIT_FAILURE; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Converts a string in a Job document to a #_jobAction_t. + * + * @param[in] pAction The Job action as a string. + * @param[in] actionLength The length of `pAction`. + * + * @return A #_jobAction_t equivalent to the given string. + */ +static _jobAction_t _getAction( const char * pAction, + size_t actionLength ) +{ + _jobAction_t action = JOB_ACTION_UNKNOWN; + + if( strncmp( pAction, "print", actionLength ) == 0 ) + { + action = JOB_ACTION_PRINT; + } + else if( strncmp( pAction, "publish", actionLength ) == 0 ) + { + action = JOB_ACTION_PUBLISH; + } + else if( strncmp( pAction, "exit", actionLength ) == 0 ) + { + action = JOB_ACTION_EXIT; + } + + return action; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Extracts a JSON string from the Job document. + * + * @param[in] pJsonDoc The JSON document to search. + * @param[in] jsonDocLength Length of `pJsonDoc`. + * @param[in] pKey The JSON key to search for. + * @param[in] keyLength Length of `pKey`. + * @param[out] pValue The extracted JSON value. + * @param[out] valueLength Length of pValue. + * + * @return `true` if the key was found and the value is valid; `false` otherwise. + */ +static bool _getJsonString( const char * pJsonDoc, + size_t jsonDocLength, + const char * pKey, + size_t keyLength, + const char ** pValue, + size_t * valueLength ) +{ + bool keyFound = IotJsonUtils_FindJsonValue( pJsonDoc, + jsonDocLength, + pKey, + keyLength, + pValue, + valueLength ); + + if( keyFound == true ) + { + /* Exclude empty strings. */ + if( *valueLength < JSON_STRING_MIN_LENGTH ) + { + keyFound = false; + } + else + { + /* Adjust the value to remove the quotes. */ + ( *pValue )++; + ( *valueLength ) -= 2; + } + } + + return keyFound; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Job operation completion callback. This function is invoked when an + * asynchronous Job operation finishes. + * + * @param[in] pCallbackContext Set to a non-NULL value to exit the demo. + * @param[in] pCallbackParam Information on the Job operation that completed. + */ +static void _operationCompleteCallback( void * pCallbackContext, + AwsIotJobsCallbackParam_t * pCallbackParam ) +{ + /* This function is invoked when either a StartNext or Update completes. */ + if( pCallbackParam->callbackType == AWS_IOT_JOBS_START_NEXT_COMPLETE ) + { + IotLogInfo( "Job StartNext complete with result %s.", + AwsIotJobs_strerror( pCallbackParam->u.operation.result ) ); + } + else + { + IotLogInfo( "Job Update complete with result %s.", + AwsIotJobs_strerror( pCallbackParam->u.operation.result ) ); + } + + /* If a non-NULL context is given, set the flag to exit the demo. */ + if( pCallbackContext != NULL ) + { + ( void ) Atomic_CompareAndSwap_u32( &_exitFlag, JOBS_DEMO_FINISHED, JOBS_DEMO_RUNNING ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Process an action with a message, such as "print" or "publish". + * + * @param[in] mqttConnection The MQTT connection to use if the action is "publish". + * @param[in] action Either #JOB_ACTION_PRINT or #JOB_ACTION_PUBLISH. + * @param[in] pJobDoc A pointer to the Job document. + * @param[in] jobDocLength The length of the Job document. + * + * @return #AWS_IOT_JOB_STATE_SUCCEEDED on success; #AWS_IOT_JOB_STATE_FAILED otherwise. + */ +static AwsIotJobState_t _processMessage( IotMqttConnection_t mqttConnection, + _jobAction_t action, + const char * pJobDoc, + size_t jobDocLength ) +{ + AwsIotJobState_t status = AWS_IOT_JOB_STATE_SUCCEEDED; + IotMqttError_t mqttStatus = IOT_MQTT_STATUS_PENDING; + IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; + const char * pMessage = NULL, * pTopic = NULL; + size_t messageLength = 0, topicLength = 0; + + /* Both "print" and "publish" require a "message" key. Search the Job + * document for this key. */ + if( _getJsonString( pJobDoc, + jobDocLength, + JOB_MESSAGE_KEY, + JOB_MESSAGE_KEY_LENGTH, + &pMessage, + &messageLength ) == false ) + { + IotLogError( "Job document for \"print\" or \"publish\" does not contain a %s key.", + JOB_MESSAGE_KEY ); + + status = AWS_IOT_JOB_STATE_FAILED; + } + + if( status == AWS_IOT_JOB_STATE_SUCCEEDED ) + { + if( action == JOB_ACTION_PRINT ) + { + /* Print the given message if the action is "print". */ + IotLog( IOT_LOG_INFO, &_logHideAll, + "\r\n" + "/*-----------------------------------------------------------*/\r\n" + "\r\n" + "%.*s\r\n" + "\r\n" + "/*-----------------------------------------------------------*/\r\n" + "\r\n", messageLength, pMessage ); + } + else + { + /* Extract the topic if the action is "publish". */ + if( _getJsonString( pJobDoc, + jobDocLength, + JOB_TOPIC_KEY, + JOB_TOPIC_KEY_LENGTH, + &pTopic, + &topicLength ) == false ) + { + IotLogError( "Job document for action \"publish\" does not contain a %s key.", + JOB_TOPIC_KEY ); + + status = AWS_IOT_JOB_STATE_FAILED; + } + + if( status == AWS_IOT_JOB_STATE_SUCCEEDED ) + { + publishInfo.qos = IOT_MQTT_QOS_0; + publishInfo.pTopicName = pTopic; + publishInfo.topicNameLength = ( uint16_t ) topicLength; + publishInfo.pPayload = pMessage; + publishInfo.payloadLength = messageLength; + + mqttStatus = IotMqtt_PublishAsync( mqttConnection, &publishInfo, 0, NULL, NULL ); + + if( mqttStatus != IOT_MQTT_SUCCESS ) + { + status = AWS_IOT_JOB_STATE_FAILED; + } + } + } + } + + return status; +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Process a Job received from the Notify Next callback. + * + * @param[in] pJobInfo The parameter to the Notify Next callback that contains + * information about the received Job. + * @param[in] pJobId A pointer to the Job ID. + * @param[in] jobIdLength The length of the Job ID. + * @param[in] pJobDoc A pointer to the Job document. + * @param[in] jobDocLength The length of the Job document. + */ +static void _processJob( const AwsIotJobsCallbackParam_t * pJobInfo, + const char * pJobId, + size_t jobIdLength, + const char * pJobDoc, + size_t jobDocLength ) +{ + AwsIotJobsError_t status = AWS_IOT_JOBS_SUCCESS; + AwsIotJobsUpdateInfo_t updateInfo = AWS_IOT_JOBS_UPDATE_INFO_INITIALIZER; + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + const char * pAction = NULL; + size_t actionLength = 0; + _jobAction_t action = JOB_ACTION_UNKNOWN; + + IotLogInfo( "Job document received: %.*s", jobDocLength, pJobDoc ); + + /* Initialize the common parameter of Jobs requests. */ + AwsIotJobsRequestInfo_t requestInfo = AWS_IOT_JOBS_REQUEST_INFO_INITIALIZER; + + requestInfo.mqttConnection = pJobInfo->mqttConnection; + requestInfo.pThingName = pJobInfo->pThingName; + requestInfo.thingNameLength = pJobInfo->thingNameLength; + requestInfo.pJobId = pJobId; + requestInfo.jobIdLength = jobIdLength; + + /* Tell the Jobs service that the device has started working on the Job. + * Use the StartNext API to set the Job's status to IN_PROGRESS. */ + callbackInfo.function = _operationCompleteCallback; + + status = AwsIotJobs_StartNextAsync( &requestInfo, &updateInfo, 0, &callbackInfo, NULL ); + + IotLogInfo( "Jobs StartNext queued with result %s.", AwsIotJobs_strerror( status ) ); + + /* Get the action for this device. */ + if( _getJsonString( pJobDoc, + jobDocLength, + JOB_ACTION_KEY, + JOB_ACTION_KEY_LENGTH, + &pAction, + &actionLength ) == true ) + { + action = _getAction( pAction, actionLength ); + + switch( action ) + { + case JOB_ACTION_EXIT: + callbackInfo.pCallbackContext = JOBS_DEMO_SHOULD_EXIT; + updateInfo.newStatus = AWS_IOT_JOB_STATE_SUCCEEDED; + break; + + case JOB_ACTION_PRINT: + case JOB_ACTION_PUBLISH: + updateInfo.newStatus = _processMessage( pJobInfo->mqttConnection, + action, + pJobDoc, + jobDocLength ); + break; + + default: + IotLogError( "Received Job document with unknown action %.*s.", + actionLength, + pAction ); + + updateInfo.newStatus = AWS_IOT_JOB_STATE_FAILED; + break; + } + } + else + { + IotLogError( "Received Job document does not contain an %s key.", + JOB_ACTION_KEY ); + + /* The given Job document is not valid for this demo. */ + updateInfo.newStatus = AWS_IOT_JOB_STATE_FAILED; + } + + IotLogInfo( "Setting state of %.*s to %s.", + jobIdLength, + pJobId, + AwsIotJobs_StateName( updateInfo.newStatus ) ); + + /* Tell the Jobs service that the device has finished the Job. */ + status = AwsIotJobs_UpdateAsync( &requestInfo, &updateInfo, 0, &callbackInfo, NULL ); + + IotLogInfo( "Jobs Update queued with result %s.", AwsIotJobs_strerror( status ) ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Jobs Notify Next callback. This function is invoked when a new Job is + * received from the Jobs service. + * + * @param[in] pCallbackContext Ignored. + * @param[in] pCallbackInfo Contains the received Job. + */ +static void _jobsCallback( void * pCallbackContext, + AwsIotJobsCallbackParam_t * pCallbackInfo ) +{ + /* Flags to track the contents of the received Job document. */ + bool idKeyFound = false, docKeyFound = false; + + /* The Job ID and length */ + const char * pJobId = NULL; + size_t jobIdLength = 0; + + /* The Job document (which contains the action) and length. */ + const char * pJobDoc = NULL; + size_t jobDocLength = 0; + + /* Silence warnings about unused parameters. */ + ( void ) pCallbackContext; + + /* Get the Job ID. */ + idKeyFound = _getJsonString( pCallbackInfo->u.callback.pDocument, + pCallbackInfo->u.callback.documentLength, + JOB_ID_KEY, + JOB_ID_KEY_LENGTH, + &pJobId, + &jobIdLength ); + + if( idKeyFound == true ) + { + if( jobIdLength > JOB_ID_MAX_LENGTH ) + { + IotLogError( "Received Job ID %.*s longer than %lu, which is the " + "maximum allowed by AWS IoT. Ignoring Job.", + jobIdLength, + pJobId, + ( unsigned long ) JOB_ID_MAX_LENGTH ); + + idKeyFound = false; + } + else + { + IotLogInfo( "Job %.*s received.", jobIdLength, pJobId ); + } + } + + /* Get the Job document. */ + docKeyFound = IotJsonUtils_FindJsonValue( pCallbackInfo->u.callback.pDocument, + pCallbackInfo->u.callback.documentLength, + JOB_DOC_KEY, + JOB_DOC_KEY_LENGTH, + &pJobDoc, + &jobDocLength ); + + /* When both the Job ID and Job document are available, process the Job. */ + if( ( idKeyFound == true ) && ( docKeyFound == true ) ) + { + /* Process the Job document. */ + _processJob( pCallbackInfo, + pJobId, + jobIdLength, + pJobDoc, + jobDocLength ); + } + else + { + /* The Jobs service sends an empty Job document when all Jobs are complete. */ + if( ( idKeyFound == false ) && ( docKeyFound == false ) ) + { + IotLog( IOT_LOG_INFO, &_logHideAll, + "\r\n" + "/*-----------------------------------------------------------*/\r\n" + "\r\n" + "All available Jobs complete.\r\n" + "\r\n" + "/*-----------------------------------------------------------*/\r\n" + "\r\n" ); + } + else + { + IotLogWarn( "Received an invalid Job document: %.*s", + pCallbackInfo->u.callback.documentLength, + pCallbackInfo->u.callback.pDocument ); + } + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief The function that runs the Jobs demo, called by the demo runner. + * + * @param[in] awsIotMqttMode Ignored for the Jobs demo. + * @param[in] pIdentifier NULL-terminated Jobs Thing Name. + * @param[in] pNetworkServerInfo Passed to the MQTT connect function when + * establishing the MQTT connection for Jobs. + * @param[in] pNetworkCredentialInfo Passed to the MQTT connect function when + * establishing the MQTT connection for Jobs. + * @param[in] pNetworkInterface The network interface to use for this demo. + * + * @return `EXIT_SUCCESS` if the demo completes successfully; `EXIT_FAILURE` otherwise. + */ +int RunJobsDemo( bool awsIotMqttMode, + const char * pIdentifier, + void * pNetworkServerInfo, + void * pNetworkCredentialInfo, + const IotNetworkInterface_t * pNetworkInterface ) +{ + /* Return value of this function and the exit status of this program. */ + int status = EXIT_SUCCESS; + + /* Handle of the MQTT connection used in this demo. */ + IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; + + /* Length of Jobs Thing Name. */ + size_t thingNameLength = 0; + + /* The function that will be set as the Jobs Notify Next callback. */ + AwsIotJobsCallbackInfo_t callbackInfo = AWS_IOT_JOBS_CALLBACK_INFO_INITIALIZER; + + /* Status returned by the functions to set the Notify Next callback. */ + AwsIotJobsError_t callbackStatus = AWS_IOT_JOBS_SUCCESS; + + /* Flags for tracking which cleanup functions must be called. */ + bool initialized = false, connected = false; + + /* The first parameter of this demo function is not used. Jobs are specific + * to AWS IoT, so this value is hardcoded to true whenever needed. */ + ( void ) awsIotMqttMode; + + /* Determine the length of the Thing Name. */ + if( pIdentifier != NULL ) + { + thingNameLength = strlen( pIdentifier ); + + if( thingNameLength == 0 ) + { + IotLogError( "The length of the Thing Name (identifier) must be nonzero." ); + + status = EXIT_FAILURE; + } + } + else + { + IotLogError( "A Thing Name (identifier) must be provided for the Jobs demo." ); + + status = EXIT_FAILURE; + } + + /* Initialize the libraries required for this demo. */ + if( status == EXIT_SUCCESS ) + { + status = _initializeDemo(); + + if( status == EXIT_SUCCESS ) + { + initialized = true; + } + } + + /* Establish the MQTT connection used in this demo. */ + if( status == EXIT_SUCCESS ) + { + status = _establishMqttConnection( pIdentifier, + pNetworkServerInfo, + pNetworkCredentialInfo, + pNetworkInterface, + &mqttConnection ); + + if( status == EXIT_SUCCESS ) + { + connected = true; + } + } + + /* Set the Jobs Notify Next callback. This callback waits for the next available Job. */ + if( status == EXIT_SUCCESS ) + { + callbackInfo.function = _jobsCallback; + + callbackStatus = AwsIotJobs_SetNotifyNextCallback( mqttConnection, + pIdentifier, + thingNameLength, + 0, + &callbackInfo ); + + IotLogInfo( "Jobs NotifyNext callback for %.*s set with result %s.", + thingNameLength, + pIdentifier, + AwsIotJobs_strerror( callbackStatus ) ); + + if( callbackStatus != AWS_IOT_JOBS_SUCCESS ) + { + status = EXIT_FAILURE; + } + } + + /* Wait for incoming Jobs. */ + if( status == EXIT_SUCCESS ) + { + IotLog( IOT_LOG_INFO, &_logHideAll, + "\r\n" + "/*-----------------------------------------------------------*/\r\n" + "\r\n" + "The Jobs demo is now ready to accept Jobs.\r\n" + "Jobs may be created using the AWS IoT console or AWS CLI.\r\n" + "See the following link for more information.\r\n" + "\r\n" + "https://docs.aws.amazon.com/cli/latest/reference/iot/create-job.html\r\n" + "\r\n" + "This demo expects Job documents to have an \"action\" JSON key.\r\n" + "The following actions are currently supported:\r\n" + " - print\r\n" + " Logs a message to the local console. The Job document must also contain a \"message\".\r\n" + " For example: { \"action\": \"print\", \"message\": \"Hello world!\"} will cause\r\n" + " \"Hello world!\" to be printed on the console.\r\n" + " - publish\r\n" + " Publishes a message to an MQTT topic. The Job document must also contain a \"message\" and \"topic\".\r\n" + " For example: { \"action\": \"publish\", \"topic\": \"demo/jobs\", \"message\": \"Hello world!\"} will cause\r\n" + " \"Hello world!\" to be published to the topic \"demo/jobs\".\r\n" + " - exit\r\n" + " Exits the demo program. This program will run until { \"action\": \"exit\" } is received.\r\n" + "\r\n" + "/*-----------------------------------------------------------*/\r\n" ); + + /* Wait until a Job with { "action": "exit" } is received. */ + while( Atomic_CompareAndSwap_u32( &_exitFlag, 0, JOBS_DEMO_FINISHED ) == 0 ) + { + IotClock_SleepMs( 1000 ); + } + } + + /* Remove the Jobs Notify Next callback. */ + if( status == EXIT_SUCCESS ) + { + /* Specify that the _jobsCallback function should be replaced with NULL, + * i.e. removed. */ + callbackInfo.function = NULL; + callbackInfo.oldFunction = _jobsCallback; + + callbackStatus = AwsIotJobs_SetNotifyNextCallback( mqttConnection, + pIdentifier, + thingNameLength, + 0, + &callbackInfo ); + + IotLogInfo( "Jobs NotifyNext callback for %.*s removed with result %s.", + thingNameLength, + pIdentifier, + AwsIotJobs_strerror( callbackStatus ) ); + } + + /* Disconnect the MQTT connection and clean up the demo. */ + if( connected == true ) + { + IotMqtt_Disconnect( mqttConnection, 0 ); + } + + if( initialized == true ) + { + _cleanupDemo(); + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/demos/src/aws_iot_demo_shadow.c b/demos/src/aws_iot_demo_shadow.c index b288d3bb18..aee658a2e1 100644 --- a/demos/src/aws_iot_demo_shadow.c +++ b/demos/src/aws_iot_demo_shadow.c @@ -522,51 +522,41 @@ static int _establishMqttConnection( const char * pIdentifier, IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - if( pIdentifier == NULL ) + /* Set the members of the network info not set by the initializer. This + * struct provided information on the transport layer to the MQTT connection. */ + networkInfo.createNetworkConnection = true; + networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; + networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; + networkInfo.pNetworkInterface = pNetworkInterface; + + /* Set the members of the connection info not set by the initializer. */ + connectInfo.awsIotMqttMode = true; + connectInfo.cleanSession = true; + connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; + + /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ + connectInfo.pClientIdentifier = pIdentifier; + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); + + IotLogInfo( "Shadow Thing Name is %.*s (length %hu).", + connectInfo.clientIdentifierLength, + connectInfo.pClientIdentifier, + connectInfo.clientIdentifierLength ); + + /* Establish the MQTT connection. */ + connectStatus = IotMqtt_Connect( &networkInfo, + &connectInfo, + TIMEOUT_MS, + pMqttConnection ); + + if( connectStatus != IOT_MQTT_SUCCESS ) { - IotLogError( "Shadow Thing Name must be provided." ); + IotLogError( "MQTT CONNECT returned error %s.", + IotMqtt_strerror( connectStatus ) ); status = EXIT_FAILURE; } - if( status == EXIT_SUCCESS ) - { - /* Set the members of the network info not set by the initializer. This - * struct provided information on the transport layer to the MQTT connection. */ - networkInfo.createNetworkConnection = true; - networkInfo.u.setup.pNetworkServerInfo = pNetworkServerInfo; - networkInfo.u.setup.pNetworkCredentialInfo = pNetworkCredentialInfo; - networkInfo.pNetworkInterface = pNetworkInterface; - - /* Set the members of the connection info not set by the initializer. */ - connectInfo.awsIotMqttMode = true; - connectInfo.cleanSession = true; - connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; - - /* AWS IoT recommends the use of the Thing Name as the MQTT client ID. */ - connectInfo.pClientIdentifier = pIdentifier; - connectInfo.clientIdentifierLength = ( uint16_t ) strlen( pIdentifier ); - - IotLogInfo( "Shadow Thing Name is %.*s (length %hu).", - connectInfo.clientIdentifierLength, - connectInfo.pClientIdentifier, - connectInfo.clientIdentifierLength ); - - /* Establish the MQTT connection. */ - connectStatus = IotMqtt_Connect( &networkInfo, - &connectInfo, - TIMEOUT_MS, - pMqttConnection ); - - if( connectStatus != IOT_MQTT_SUCCESS ) - { - IotLogError( "MQTT CONNECT returned error %s.", - IotMqtt_strerror( connectStatus ) ); - - status = EXIT_FAILURE; - } - } - return status; } diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index eff895adfb..71b428690d 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -45,11 +45,77 @@ Currently, the Jobs library has the following dependencies: In addition to the components above, the Jobs library also depends on C standard library headers. */ +/** +@page jobs_demo Demo +@brief Demonstrates the usage of the Jobs library. + +This demo provides a simple example on using the Jobs library and working with AWS IoT Jobs. + +The Jobs demo establishes an MQTT connection and registers a [Jobs NotifyNext callback](@ref jobs_function_setnotifynextcallback). It then waits for new Jobs. When a Job arrives, the demo parses the Job document and takes action based on the document content. + +@section jobs_demo_setup Demo Setup +@brief How to set up Jobs for the Jobs demo. + +Because the Jobs MQTT API does not support creating Jobs, you must create Jobs for the demo separately. We recommend using AWS CLI to create Jobs. + +1. Create a user for AWS CLI. See steps 1 and 2 of @ref guide_developer_automated_tests_jobs for more information. +2. Build and run the demo as described [here](@ref building). +3. When the following message appears, you may begin creating Jobs for the demo. +@code{sh} +/*-----------------------------------------------------------*/ + +The Jobs demo is now ready to accept Jobs. +Jobs may be created using the AWS IoT console or AWS CLI. +See the following link for more information. + +https://docs.aws.amazon.com/cli/latest/reference/iot/create-job.html + +This demo expects Job documents to have an "action" JSON key. +The following actions are currently supported: + - print + Logs a message to the local console. The Job document must also contain a "message". + For example: { "action": "print", "message": "Hello world!"} will cause + "Hello world!" to be printed on the console. + - publish + Publishes a message to an MQTT topic. The Job document must also contain a "message" and "topic". + For example: { "action": "publish", "topic": "demo/jobs", "message": "Hello world!"} will cause + "Hello world!" to be published to the topic "demo/jobs". + - exit + Exits the demo program. This program will run until { "action": "exit" } is received. + +/*-----------------------------------------------------------*/ +@endcode + +The supported actions are described above. As an example, the command to create a "publish" Job with AWS CLI is below. Replace `UniqueId` with a Job ID that is unique, and `ThingARN` with the ARN of the target Thing. + +@code{sh} +aws iot create-job \ + --job-id UniqueId \ + --targets ThingARN \ + --document '{"action":"publish","message":"Hello world!","topic":"jobsdemo/1"}' +@endcode + +This will cause the Jobs demo to publish the message `Hello world!` on the topic `jobsdemo/1`. You may view this message by subscribing to the topic in AWS IoT Console. + +When the demo finishes with a Job, it will mark the Jobs as complete. Note that completed Jobs are not automatically removed from the AWS IoT Console. The following command can be used to remove completed Jobs. Replace `UniqueId` with the Job ID. + +@code{sh} +aws iot delete-job --job-id UniqueId +@endcode + +# Demo Structure +The Jobs demo uses the asynchronous API of the Jobs library. Most of the demo is run from the NotifyNext callback. + +@image html jobs_demo.png "Jobs Demo Execution Sequence" width=80% + +@note Messages from the Jobs service (which are sent at MQTT QoS 1) may be received multiple times. The Jobs NotifyNext callback must be able to handle duplicated Job documents. +*/ + /** @page jobs_tests Tests @brief Tests written for the Jobs library. -The Jobs tests reside in the `tests/jobs` directory. They are divided into the following subdirectories: +The Jobs tests reside in the `jobs/test` directory. They are divided into the following subdirectories: - `system`: Jobs system tests. These tests require a network connection and AWS IoT credentials. They also need an AWS account and registered Thing; see @ref jobs_system_tests_setup for instructions to configure an AWS account to run these tests. The command line option `-n` may be passed to the test executable to disable these tests. - `unit`: Jobs unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index 3299636cf4..b349a11017 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -114,7 +114,7 @@ This setting can be increased for a longer demo. The MQTT demo publishes a total @page mqtt_tests Tests @brief Tests written for the MQTT library. -The MQTT tests reside in the `tests/mqtt` directory. They are divided into the following subdirectories: +The MQTT tests reside in the `mqtt/test` directory. They are divided into the following subdirectories: - `access`: Helper files that allow access to internal variables and functions of the MQTT library. - `mock`: Simulates network responses to MQTT packets; used for testing other libraries that depend on MQTT. - `system`: MQTT system and stress tests. These tests require a network connection. Stress tests may run for a long time, so they are not run unless the command line option `-l` is given to the test executable. diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index ff33953f72..8197bb9d8d 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -84,7 +84,7 @@ This value may be `0`, which causes a new Shadow update to be sent as soon as th @page shadow_tests Tests @brief Tests written for the Shadow library. -The Shadow tests reside in the `tests/shadow` directory. They are divided into the following subdirectories: +The Shadow tests reside in the `shadow/test` directory. They are divided into the following subdirectories: - `system`: Shadow system tests. These tests require a network connection and AWS IoT credentials. The command line option `-n` may be passed to the test executable to disable these tests. - `unit`: Shadow unit tests. These tests do not require a network connection or credentials. These tests use the [MQTT mocks.](@ref mqtt_tests) diff --git a/doc/plantuml/images/jobs_demo.png b/doc/plantuml/images/jobs_demo.png new file mode 100644 index 0000000000000000000000000000000000000000..5a68b4ef176250c7b11bb01f3b012b55e48b3619 GIT binary patch literal 48193 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

8LU2U#WSH>o2K{n3v>191Uf#wG~Q*q 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 literal 0 HcmV?d00001 diff --git a/doc/plantuml/jobs_demo.pu b/doc/plantuml/jobs_demo.pu new file mode 100644 index 0000000000..57990246fe --- /dev/null +++ b/doc/plantuml/jobs_demo.pu @@ -0,0 +1,39 @@ +@startuml "jobs_demo" +skinparam classFontSize 8 +skinparam classFontName Helvetica +autonumber + +participant "Main Thread" as main +participant "Completed Operation Callback" as operation +participant "NotifyNext Callback" as notify +participant "Jobs Library" as lib + +box "Demo Code" #LightBlue + participant main + participant operation + participant notify +end box +box "Library" #LightGreen + participant lib +end box + +box "Jobs Service" #LightPink + participant "Server" as server +end box + +main -> lib: Register NotifyNext callback + +loop #transparent Until "exit" action succeeds + server -> lib: New Job is created + lib -> notify: Callback invoked + notify -// lib: StartNextAsync + notify -> notify: Execute action from Job document + notify -// lib: UpdateAsync with result of action + lib -> operation: Result of Update + + group #transparent "exit" command successfully updated + operation -> main: Signal end of demo + end +end + +@enduml diff --git a/libraries/aws/jobs/include/types/aws_iot_jobs_types.h b/libraries/aws/jobs/include/types/aws_iot_jobs_types.h index 0d91c8c59a..823bb69145 100644 --- a/libraries/aws/jobs/include/types/aws_iot_jobs_types.h +++ b/libraries/aws/jobs/include/types/aws_iot_jobs_types.h @@ -352,7 +352,7 @@ typedef enum AwsIotJobsError * (https://docs.aws.amazon.com/iot/latest/apireference/API_iot-jobs-data_JobExecutionState.html) * for more information on Job states. */ -typedef enum AwsIotJobsState +typedef enum AwsIotJobState { /** * @brief A Job is queued and awaiting execution. From 9426012dba24dfa9167ed3a185e43bdd9085bf0b Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Fri, 11 Oct 2019 13:46:08 -0700 Subject: [PATCH 290/844] Refactor serializer override logic * Added types for the IotMqttSerializer_t functions * Added internal utility macro to simplify serializer function override logic --- .../mqtt/include/types/iot_mqtt_types.h | 249 ++++++----- libraries/standard/mqtt/src/iot_mqtt_api.c | 398 +++++++++--------- .../standard/mqtt/src/iot_mqtt_network.c | 321 +++++--------- .../standard/mqtt/src/iot_mqtt_operation.c | 77 ++-- .../mqtt/src/private/iot_mqtt_internal.h | 32 +- 5 files changed, 511 insertions(+), 566 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index fa5cefca14..98212ed976 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -685,17 +685,135 @@ typedef struct IotMqttConnectInfo uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ } IotMqttConnectInfo_t; -#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. * * Forward declaration of the internal MQTT packet structure. */ - struct _mqttPacket; +struct _mqttPacket; /** @endcond */ +/** + * @brief Get the MQTT packet type from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + */ +typedef uint8_t ( * IotMqttGetPacketType_t )( + void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] pNetworkInterface Function pointers used to interact with the + * network. + */ +typedef size_t ( * IotMqttGetRemainingLength_t )( + void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); + +/** + * @brief Free a packet generated by the serializer. + * + * This function pointer must be set if any other serializer override is set. + * @param[in] uint8_t* The packet to free. + */ +typedef void ( * IotMqttFreePacket_t )( uint8_t * pPacket ); + +/** + * @brief CONNECT packet serializer function. + * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. + * @param[out] uint8_t** Where the CONNECT packet is written. + * @param[out] size_t* Size of the CONNECT packet. + */ +typedef IotMqttError_t ( * IotMqttSerializeConnect_t )( + const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ); + +/** + * @brief PINGREQ packet serializer function. + * @param[out] uint8_t** Where the PINGREQ packet is written. + * @param[out] size_t* Size of the PINGREQ packet. + */ +typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( + uint8_t ** pDisconnectPacket, + size_t * pPacketSize ); + +/** + * @brief PUBLISH packet serializer function. + * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. + * @param[out] uint8_t** Where the PUBLISH packet is written. + * @param[out] size_t* Size of the PUBLISH packet. + * @param[out] uint16_t* The packet identifier generated for this PUBLISH. + * @param[out] uint8_t** Where the high byte of the packet identifier + * is written. + */ +typedef IotMqttError_t ( * IotMqtt_SerializePublish_t )( + const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ); + +/** + * @brief SUBSCRIBE/UNSUBSCRIBE packet serializer function. + * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. + * @param[in] size_t Number of elements in the subscription array. + * @param[out] uint8_t** Where the SUBSCRIBE packet is written. + * @param[out] size_t* Size of the SUBSCRIBE packet. + * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. + */ +typedef IotMqttError_t ( * IotMqttSerializeSubscribe_t )( + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); + +/** + * @brief DISCONNECT packet serializer function. + * @param[out] uint8_t** Where the DISCONNECT packet is written. + * @param[out] size_t* Size of the DISCONNECT packet. + */ +typedef IotMqttError_t ( * IotMqttDisconnectSerializer_t )( + uint8_t ** ppDisconnectPacket, + size_t * pPacketSize ); + +/** + * @brief MQTT packet deserializer function. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure + */ +typedef IotMqttError_t ( * IotMqttDeserializer_t )( + struct _mqttPacket * pMqttPacket ); + +/** + * @brief PUBACK packet serializer function. + * @param[in] uint16_t The packet identifier to place in PUBACK. + * @param[out] uint8_t** Where the PUBACK packet is written. + * @param[out] size_t* Size of the PUBACK packet. + */ +typedef IotMqttError_t ( * IotMqttSerializePuback_t )( + uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ); +/** + * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. + * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. + * @param[in] uint8_t* The high byte of any packet identifier to modify. + * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). + */ +typedef void ( * IotMqttPublishSetDup_t )( + uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, + uint16_t * pNewPacketIdentifier ); + +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + /** * @ingroup mqtt_datatypes_paramstructs * @brief Function pointers for MQTT packet serializer overrides. @@ -717,197 +835,120 @@ typedef struct IotMqttConnectInfo { /** * @brief Get the MQTT packet type from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - * * Default implementation: #_IotMqtt_GetPacketType */ - uint8_t ( * getPacketType )( void * /* pNetworkConnection */, - const IotNetworkInterface_t * /* pNetworkInterface */ ); - + IotMqttGetPacketType_t getPacketType; /** * @brief Get the remaining length from a stream of bytes off the network. - * - * @param[in] pNetworkConnection Reference to the network connection. - * @param[in] pNetworkInterface Function pointers used to interact with the - * network. - * * Default implementation: #_IotMqtt_GetRemainingLength */ - size_t ( * getRemainingLength )( void * pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); - + IotMqttGetRemainingLength_t getRemainingLength; /** * @brief Free a packet generated by the serializer. * - * This function pointer must be set if any other serializer override is set. - * @param[in] uint8_t* The packet to free. - * * Default implementation: #_IotMqtt_FreePacket */ - void ( * freePacket )( uint8_t * /* pPacket */ ); + IotMqttFreePacket_t freePacket; struct { /** * @brief CONNECT packet serializer function. - * @param[in] IotMqttConnectInfo_t* User-provided CONNECT information. - * @param[out] uint8_t** Where the CONNECT packet is written. - * @param[out] size_t* Size of the CONNECT packet. * * Default implementation: #_IotMqtt_SerializeConnect */ - IotMqttError_t ( * connect )( const IotMqttConnectInfo_t * /* pConnectInfo */, - uint8_t ** /* pConnectPacket */, - size_t * /* pPacketSize */ ); - + IotMqttSerializeConnect_t connect; /** * @brief PUBLISH packet serializer function. - * @param[in] IotMqttPublishInfo_t* User-provided PUBLISH information. - * @param[out] uint8_t** Where the PUBLISH packet is written. - * @param[out] size_t* Size of the PUBLISH packet. - * @param[out] uint16_t* The packet identifier generated for this PUBLISH. - * @param[out] uint8_t** Where the high byte of the packet identifier - * is written. * * Default implementation: #_IotMqtt_SerializePublish */ - IotMqttError_t ( * publish )( const IotMqttPublishInfo_t * /* pPublishInfo */, - uint8_t ** /* pPublishPacket */, - size_t * /* pPacketSize */, - uint16_t * /* pPacketIdentifier */, - uint8_t ** /* pPacketIdentifierHigh */ ); - + IotMqtt_SerializePublish_t publish; /** * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. - * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. - * @param[in] uint8_t* The high byte of any packet identifier to modify. - * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). * * Default implementation: #_IotMqtt_PublishSetDup */ - void ( *publishSetDup )( uint8_t * /* pPublishPacket */, - uint8_t * /* pPacketIdentifierHigh */, - uint16_t * /* pNewPacketIdentifier */ ); - + IotMqttPublishSetDup_t publishSetDup; /** * @brief PUBACK packet serializer function. - * @param[in] uint16_t The packet identifier to place in PUBACK. - * @param[out] uint8_t** Where the PUBACK packet is written. - * @param[out] size_t* Size of the PUBACK packet. * * Default implementation: #_IotMqtt_SerializePuback */ - IotMqttError_t ( * puback )( uint16_t /* packetIdentifier */, - uint8_t ** /* pPubackPacket */, - size_t * /* pPacketSize */ ); - + IotMqttSerializePuback_t puback; /** * @brief SUBSCRIBE packet serializer function. - * @param[in] IotMqttSubscription_t* User-provided array of subscriptions. - * @param[in] size_t Number of elements in the subscription array. - * @param[out] uint8_t** Where the SUBSCRIBE packet is written. - * @param[out] size_t* Size of the SUBSCRIBE packet. - * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. * * Default implementation: #_IotMqtt_SerializeSubscribe */ - IotMqttError_t ( * subscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, - size_t /* subscriptionCount */, - uint8_t ** /* pSubscribePacket */, - size_t * /* pPacketSize */, - uint16_t * /* pPacketIdentifier */ ); - + IotMqttSerializeSubscribe_t subscribe; /** * @brief UNSUBSCRIBE packet serializer function. - * @param[in] IotMqttSubscription_t* User-provided array of subscriptions to remove. - * @param[in] size_t Number of elements in the subscription array. - * @param[out] uint8_t** Where the UNSUBSCRIBE packet is written. - * @param[out] size_t* Size of the UNSUBSCRIBE packet. - * @param[out] uint16_t* The packet identifier generated for this UNSUBSCRIBE. * * Default implementation: #_IotMqtt_SerializeUnsubscribe */ - IotMqttError_t ( * unsubscribe )( const IotMqttSubscription_t * /* pSubscriptionList */, - size_t /* subscriptionCount */, - uint8_t ** /* pUnsubscribePacket */, - size_t * /* pPacketSize */, - uint16_t * /* pPacketIdentifier */ ); - + IotMqttSerializeSubscribe_t unsubscribe; /** * @brief PINGREQ packet serializer function. - * @param[out] uint8_t** Where the PINGREQ packet is written. - * @param[out] size_t* Size of the PINGREQ packet. * * Default implementation: #_IotMqtt_SerializePingreq */ - IotMqttError_t ( * pingreq )( uint8_t ** /* pPingreqPacket */, - size_t * /* pPacketSize */ ); - + IotMqttSerializePingreq_t pingreq; /** * @brief DISCONNECT packet serializer function. - * @param[out] uint8_t** Where the DISCONNECT packet is written. - * @param[out] size_t* Size of the DISCONNECT packet. * * Default implementation: #_IotMqtt_SerializeDisconnect */ - IotMqttError_t ( * disconnect )( uint8_t ** /* pDisconnectPacket */, - size_t * /* pPacketSize */ ); + IotMqttDisconnectSerializer_t disconnect; } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ struct { /** * @brief CONNACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a CONNACK. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializeConnack */ - IotMqttError_t ( * connack )( struct _mqttPacket * /* pConnack */ ); - + IotMqttDeserializer_t connack; /** * @brief PUBLISH packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PUBLISH. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializePublish */ - IotMqttError_t ( * publish )( struct _mqttPacket * /* pPublish */ ); - + IotMqttDeserializer_t publish; /** * @brief PUBACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PUBACK. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializePuback */ - IotMqttError_t ( * puback )( struct _mqttPacket * pPuback ); - + IotMqttDeserializer_t puback; /** * @brief SUBACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a SUBACK. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializeSuback */ - IotMqttError_t ( * suback )( struct _mqttPacket * /* pSuback */ ); - + IotMqttDeserializer_t suback; /** * @brief UNSUBACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing an UNSUBACK. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializeUnsuback */ - IotMqttError_t ( * unsuback )( struct _mqttPacket * /* pUnsuback */ ); - + IotMqttDeserializer_t unsuback; /** * @brief PINGRESP packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet struct representing a PINGRESP. + * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializePingresp */ - IotMqttError_t ( * pingresp )( struct _mqttPacket * /* pPingresp */ ); + IotMqttDeserializer_t pingresp; } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ } IotMqttSerializer_t; + #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /* When MQTT packet serializer overrides are disabled, this struct is an diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 14052e16f9..1e0e50e198 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -132,6 +132,33 @@ static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, */ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); +/** + * @brief Common setup function for subscribe and unsubscribe operations. + * + * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a + * description of the parameters and return values. + */ +static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + IotMqttOperation_t * const pOperationReference ); +/** + * @brief Utility function for creating and serializing subscription requests + * + * See @ref mqtt_function_subscribeasync or @ref mqtt_function_unsubscribeasync for a + * description of the parameters and return values. + */ +static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + IotMqttSerializeSubscribe_t serializeSubscription, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + _mqttOperation_t ** ppSubscriptionOperation ); + /** * @brief The common component of both @ref mqtt_function_subscribeasync and @ref * mqtt_function_unsubscribeasync. @@ -141,12 +168,73 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ); */ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotMqttConnection_t mqttConnection, + IotMqttSerializeSubscribe_t serializeSubscription, const IotMqttSubscription_t * pSubscriptionList, size_t subscriptionCount, uint32_t flags, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * const pOperationReference ); +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Declaration of local MQTT serializer override selectors + */ +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializePingreq_t, + _getMqttPingreqSerializer, + _IotMqtt_SerializePingreq, + serialize.pingreq +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqtt_SerializePublish_t, + _getMqttPublishSerializer, + _IotMqtt_SerializePublish, + serialize.publish +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeSubscribe_t, + _getMqttSubscribeSerializer, + _IotMqtt_SerializeSubscribe, + serialize.subscribe +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeSubscribe_t, + _getMqttUnsubscribeSerializer, + _IotMqtt_SerializeUnsubscribe, + serialize.unsubscribe +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeConnect_t, + _getMqttConnectSerializer, + _IotMqtt_SerializeConnect, + serialize.connect +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDisconnectSerializer_t, + _getMqttDisconnectSerializer, + _IotMqtt_SerializeDisconnect, + serialize.disconnect +) +#else +#define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq +#define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish +#define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket +#define _getMqttSubscribeSerializer( pSerializer ) _IotMqtt_SerializeSubscribe +#define _getMqttUnsubscribeSerializer( pSerializer ) _IotMqtt_SerializeUnsubscribe +#define _getMqttConnectSerializer( pSerializer ) _IotMqtt_SerializeConnect +#define _getMqttDisconnectSerializer( pSerializer ) _IotMqtt_SerializeDisconnect +#endif +/** @endcond */ + /*-----------------------------------------------------------*/ /** @@ -282,10 +370,6 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo /* Network information is not used when MQTT packet serializers are disabled. */ ( void ) pNetworkInfo; - /* Default PINGREQ serializer function. */ - IotMqttError_t ( * serializePingreq )( uint8_t **, - size_t * ) = _IotMqtt_SerializePingreq; - /* Set PINGREQ operation members. */ pMqttConnection->pingreq.u.operation.type = IOT_MQTT_PINGREQ; @@ -293,28 +377,10 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = keepAliveSeconds * 1000; pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = keepAliveSeconds * 1000; - /* Choose a PINGREQ serializer function. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNetworkInfo->pMqttSerializer != NULL ) - { - if( pNetworkInfo->pMqttSerializer->serialize.pingreq != NULL ) - { - serializePingreq = pNetworkInfo->pMqttSerializer->serialize.pingreq; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Generate a PINGREQ packet. */ - serializeStatus = serializePingreq( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), - &( pMqttConnection->pingreq.u.operation.packetSize ) ); + serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( + &( pMqttConnection->pingreq.u.operation.pMqttPacket ), + &( pMqttConnection->pingreq.u.operation.packetSize ) ); if( serializeStatus != IOT_MQTT_SUCCESS ) { @@ -481,26 +547,12 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) { IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; - /* Default free packet function. */ - void (* freePacket)( uint8_t * ) = _IotMqtt_FreePacket; - /* Clean up keep-alive if still allocated. */ if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) { IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); - - /* Choose a function to free the packet. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->freePacket != NULL ) - { - freePacket = pMqttConnection->pSerializer->freePacket; - } - } - #endif - - freePacket( pMqttConnection->pingreq.u.operation.pMqttPacket ); + _getMqttFreePacketFunc( pMqttConnection->pSerializer )( + pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; @@ -563,24 +615,14 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) } /*-----------------------------------------------------------*/ - -static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, - IotMqttConnection_t mqttConnection, - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint32_t flags, - const IotMqttCallbackInfo_t * pCallbackInfo, - IotMqttOperation_t * const pOperationReference ) +static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + IotMqttOperation_t * const pOperationReference ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - _mqttOperation_t * pSubscriptionOperation = NULL; - - /* Subscription serializer function. */ - IotMqttError_t ( * serializeSubscription )( const IotMqttSubscription_t *, - size_t, - uint8_t **, - size_t *, - uint16_t * ) = NULL; /* This function should only be called for subscribe or unsubscribe. */ IotMqtt_Assert( ( operation == IOT_MQTT_SUBSCRIBE ) || @@ -628,70 +670,28 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { EMPTY_ELSE_MARKER; } + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - /* Choose a subscription serialize function. */ - if( operation == IOT_MQTT_SUBSCRIBE ) - { - serializeSubscription = _IotMqtt_SerializeSubscribe; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( mqttConnection->pSerializer != NULL ) - { - if( mqttConnection->pSerializer->serialize.subscribe != NULL ) - { - serializeSubscription = mqttConnection->pSerializer->serialize.subscribe; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - } - else - { - serializeSubscription = _IotMqtt_SerializeUnsubscribe; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( mqttConnection->pSerializer != NULL ) - { - if( mqttConnection->pSerializer->serialize.unsubscribe != NULL ) - { - serializeSubscription = mqttConnection->pSerializer->serialize.unsubscribe; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - } +/*-----------------------------------------------------------*/ - /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ - if( operation == IOT_MQTT_UNSUBSCRIBE ) - { - _IotMqtt_RemoveSubscriptionByTopicFilter( mqttConnection, - pSubscriptionList, - subscriptionCount ); - } - else - { - EMPTY_ELSE_MARKER; - } +static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + IotMqttSerializeSubscribe_t serializeSubscription, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + _mqttOperation_t ** ppSubscriptionOperation ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + _mqttOperation_t * pSubscriptionOperation = NULL; /* Create a subscription operation. */ status = _IotMqtt_CreateOperation( mqttConnection, flags, pCallbackInfo, - &pSubscriptionOperation ); + ppSubscriptionOperation ); if( status != IOT_MQTT_SUCCESS ) { @@ -699,7 +699,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, } else { - EMPTY_ELSE_MARKER; + pSubscriptionOperation = ( * ppSubscriptionOperation ); } /* Check the subscription operation data and set the operation type. */ @@ -709,10 +709,10 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Generate a subscription packet from the subscription list. */ status = serializeSubscription( pSubscriptionList, - subscriptionCount, - &( pSubscriptionOperation->u.operation.pMqttPacket ), - &( pSubscriptionOperation->u.operation.packetSize ), - &( pSubscriptionOperation->u.operation.packetIdentifier ) ); + subscriptionCount, + &( pSubscriptionOperation->u.operation.pMqttPacket ), + &( pSubscriptionOperation->u.operation.packetSize ), + &( pSubscriptionOperation->u.operation.packetIdentifier ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -727,6 +727,42 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, IotMqtt_Assert( pSubscriptionOperation->u.operation.pMqttPacket != NULL ); IotMqtt_Assert( pSubscriptionOperation->u.operation.packetSize > 0 ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, + IotMqttConnection_t mqttConnection, + IotMqttSerializeSubscribe_t serializeSubscription, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + const IotMqttCallbackInfo_t * pCallbackInfo, + IotMqttOperation_t * const pOperationReference ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + _mqttOperation_t * pSubscriptionOperation = NULL; + + /* Create and serialize the subscription operation. */ + status = _subscriptionCreateAndSerialize( operation, + mqttConnection, + serializeSubscription, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + &pSubscriptionOperation ); + + if( status != IOT_MQTT_SUCCESS ) + { + IOT_GOTO_CLEANUP(); + } + else + { + EMPTY_ELSE_MARKER; + } + /* Add the subscription list for a SUBSCRIBE. */ if( operation == IOT_MQTT_SUBSCRIBE ) { @@ -983,11 +1019,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, _mqttOperation_t * pOperation = NULL; _mqttConnection_t * pNewMqttConnection = NULL; - /* Default CONNECT serializer function. */ - IotMqttError_t ( * serializeConnect )( const IotMqttConnectInfo_t *, - uint8_t **, - size_t * ) = _IotMqtt_SerializeConnect; - /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { @@ -1118,6 +1149,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Set the MQTT packet serializer overrides. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 pNewMqttConnection->pSerializer = pNetworkInfo->pMqttSerializer; + #else + pNewMqttConnection->pSerializer = NULL; #endif } @@ -1187,27 +1220,9 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, EMPTY_ELSE_MARKER; } - /* Choose a CONNECT serializer function. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pNewMqttConnection->pSerializer != NULL ) - { - if( pNewMqttConnection->pSerializer->serialize.connect != NULL ) - { - serializeConnect = pNewMqttConnection->pSerializer->serialize.connect; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ - status = serializeConnect( pConnectInfo, + status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( + pConnectInfo, &( pOperation->u.operation.pMqttPacket ), &( pOperation->u.operation.packetSize ) ); @@ -1379,31 +1394,10 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, /* Set the operation type. */ pOperation->u.operation.type = IOT_MQTT_DISCONNECT; - /* Choose a disconnect serializer. */ - IotMqttError_t ( * serializeDisconnect )( uint8_t **, - size_t * ) = _IotMqtt_SerializeDisconnect; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( mqttConnection->pSerializer != NULL ) - { - if( mqttConnection->pSerializer->serialize.disconnect != NULL ) - { - serializeDisconnect = mqttConnection->pSerializer->serialize.disconnect; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Generate a DISCONNECT packet. */ - status = serializeDisconnect( &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); + status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); } else { @@ -1485,13 +1479,28 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * const pSubscribeOperation ) { - return _subscriptionCommon( IOT_MQTT_SUBSCRIBE, + IotMqttError_t status = _subscriptionCommonSetup( IOT_MQTT_SUBSCRIBE, + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pSubscribeOperation ); + if ( IOT_MQTT_SUCCESS == status ) + { + status = _subscriptionCommon( IOT_MQTT_SUBSCRIBE, mqttConnection, + _getMqttSubscribeSerializer( mqttConnection->pSerializer ), pSubscriptionList, subscriptionCount, flags, pCallbackInfo, pSubscribeOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + return status; } /*-----------------------------------------------------------*/ @@ -1541,13 +1550,33 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, const IotMqttCallbackInfo_t * pCallbackInfo, IotMqttOperation_t * const pUnsubscribeOperation ) { - return _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, + IotMqttError_t status = _subscriptionCommonSetup( IOT_MQTT_UNSUBSCRIBE, mqttConnection, pSubscriptionList, subscriptionCount, flags, + pUnsubscribeOperation ); + if ( IOT_MQTT_SUCCESS == status ) + { + /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ + _IotMqtt_RemoveSubscriptionByTopicFilter( mqttConnection, + pSubscriptionList, + subscriptionCount ); + + status = _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, + mqttConnection, + _getMqttUnsubscribeSerializer( mqttConnection->pSerializer ), + pSubscriptionList, + subscriptionCount, + flags, pCallbackInfo, pUnsubscribeOperation ); + } + else + { + EMPTY_ELSE_MARKER; + } + return status; } /*-----------------------------------------------------------*/ @@ -1600,13 +1629,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, _mqttOperation_t * pOperation = NULL; uint8_t ** pPacketIdentifierHigh = NULL; - /* Default PUBLISH serializer function. */ - IotMqttError_t ( * serializePublish )( const IotMqttPublishInfo_t *, - uint8_t **, - size_t *, - uint16_t *, - uint8_t ** ) = _IotMqtt_SerializePublish; - /* Check that IotMqtt_Init was called. */ if( _checkInit() == false ) { @@ -1700,25 +1722,6 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, IotMqtt_Assert( pOperation->u.operation.status == IOT_MQTT_STATUS_PENDING ); pOperation->u.operation.type = IOT_MQTT_PUBLISH_TO_SERVER; - /* Choose a PUBLISH serializer function. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( mqttConnection->pSerializer != NULL ) - { - if( mqttConnection->pSerializer->serialize.publish != NULL ) - { - serializePublish = mqttConnection->pSerializer->serialize.publish; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* In AWS IoT MQTT mode, a pointer to the packet identifier must be saved. */ if( mqttConnection->awsIotMqttMode == true ) { @@ -1730,7 +1733,8 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, } /* Generate a PUBLISH packet from pPublishInfo. */ - status = serializePublish( pPublishInfo, + status = _getMqttPublishSerializer( mqttConnection->pSerializer )( + pPublishInfo, &( pOperation->u.operation.pMqttPacket ), &( pOperation->u.operation.packetSize ), &( pOperation->u.operation.packetIdentifier ), diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index 2ec8f6114a..a604d93358 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -105,6 +105,87 @@ static void _flushPacket( void * pNetworkConnection, const _mqttConnection_t * pMqttConnection, size_t length ); +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Declaration of local MQTT serializer override selectors + */ +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttGetPacketType_t, + _getPacketTypeFunc, + _IotMqtt_GetPacketType, + getPacketType +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttGetRemainingLength_t, + _getRemainingLengthFunc, + _IotMqtt_GetRemainingLength, + getRemainingLength +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserializer_t, + _getConnackDeserializer, + _IotMqtt_DeserializeConnack, + deserialize.connack +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserializer_t, + _getPublishDeserializer, + _IotMqtt_DeserializePublish, + deserialize.publish +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserializer_t, + _getPubackDeserializer, + _IotMqtt_DeserializePuback, + deserialize.puback +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserializer_t, + _getSubackDeserializer, + _IotMqtt_DeserializeSuback, + deserialize.suback +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserializer_t, + _getUnsubackDeserializer, + _IotMqtt_DeserializeUnsuback, + deserialize.unsuback +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserializer_t, + _getPingrespDeserializer, + _IotMqtt_DeserializePingresp, + deserialize.pingresp +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializePuback_t, + _getMqttPubackSerializer, + _IotMqtt_SerializePuback, + serialize.puback +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket +) +#else +#define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType +#define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength +#define _getConnackDeserializer( pSerializer ) _IotMqtt_DeserializeConnack +#define _getPublishDeserializer( pSerializer ) _IotMqtt_DeserializePublish +#define _getPubackDeserializer( pSerializer ) _IotMqtt_DeserializePuback +#define _getSubackDeserializer( pSerializer ) _IotMqtt_DeserializeSuback +#define _getUnsubackDeserializer( pSerializer ) _IotMqtt_DeserializeUnsuback +#define _getPingrespDeserializer( pSerializer ) _IotMqtt_DeserializePingresp +#define _getMqttPubackSerializer( pSerializer ) _IotMqtt_SerializePuback +#define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket +#endif +/** @endcond */ + /*-----------------------------------------------------------*/ static bool _incomingPacketValid( uint8_t packetType ) @@ -141,47 +222,14 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); size_t dataBytesRead = 0; - /* Default functions for retrieving packet type and length. */ - uint8_t ( * getPacketType )( void *, - const IotNetworkInterface_t * ) = _IotMqtt_GetPacketType; - size_t ( * getRemainingLength )( void *, - const IotNetworkInterface_t * ) = _IotMqtt_GetRemainingLength; - /* No buffer for remaining data should be allocated. */ IotMqtt_Assert( pIncomingPacket->pRemainingData == NULL ); IotMqtt_Assert( pIncomingPacket->remainingLength == 0 ); - /* Choose packet type and length functions. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->getPacketType != NULL ) - { - getPacketType = pMqttConnection->pSerializer->getPacketType; - } - else - { - EMPTY_ELSE_MARKER; - } - - if( pMqttConnection->pSerializer->getRemainingLength != NULL ) - { - getRemainingLength = pMqttConnection->pSerializer->getRemainingLength; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Read the packet type, which is the first byte available. */ - pIncomingPacket->type = getPacketType( pNetworkConnection, - pMqttConnection->pNetworkInterface ); + pIncomingPacket->type = _getPacketTypeFunc( pMqttConnection->pSerializer )( + pNetworkConnection, + pMqttConnection->pNetworkInterface ); /* Check that the incoming packet type is valid. */ if( _incomingPacketValid( pIncomingPacket->type ) == false ) @@ -198,8 +246,9 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, } /* Read the remaining length. */ - pIncomingPacket->remainingLength = getRemainingLength( pNetworkConnection, - pMqttConnection->pNetworkInterface ); + pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( + pNetworkConnection, + pMqttConnection->pNetworkInterface ); if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { @@ -280,9 +329,6 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttOperation_t * pOperation = NULL; - /* Deserializer function. */ - IotMqttError_t ( * deserialize )( _mqttPacket_t * ) = NULL; - /* A buffer for remaining data must be allocated if remaining length is not 0. */ IotMqtt_Assert( ( pIncomingPacket->remainingLength > 0 ) == ( pIncomingPacket->pRemainingData != NULL ) ); @@ -295,30 +341,9 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { case MQTT_PACKET_TYPE_CONNACK: IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); - - /* Choose CONNACK deserializer. */ - deserialize = _IotMqtt_DeserializeConnack; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->deserialize.connack != NULL ) - { - deserialize = pMqttConnection->pSerializer->deserialize.connack; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Deserialize CONNACK and notify of result. */ - status = deserialize( pIncomingPacket ); + status = _getConnackDeserializer( pMqttConnection->pSerializer )( + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_CONNECT, NULL ); @@ -357,29 +382,9 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pIncomingPacket->u.pIncomingPublish = pOperation; } - /* Choose a PUBLISH deserializer. */ - deserialize = _IotMqtt_DeserializePublish; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->deserialize.publish != NULL ) - { - deserialize = pMqttConnection->pSerializer->deserialize.publish; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Deserialize incoming PUBLISH. */ - status = deserialize( pIncomingPacket ); + status = _getPublishDeserializer( pMqttConnection->pSerializer )( + pIncomingPacket ); if( status == IOT_MQTT_SUCCESS ) { @@ -462,29 +467,9 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne case MQTT_PACKET_TYPE_PUBACK: IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); - /* Choose PUBACK deserializer. */ - deserialize = _IotMqtt_DeserializePuback; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->deserialize.puback != NULL ) - { - deserialize = pMqttConnection->pSerializer->deserialize.puback; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Deserialize PUBACK and notify of result. */ - status = deserialize( pIncomingPacket ); + status = _getPubackDeserializer( pMqttConnection->pSerializer )( + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_PUBLISH_TO_SERVER, &( pIncomingPacket->packetIdentifier ) ); @@ -504,30 +489,11 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne case MQTT_PACKET_TYPE_SUBACK: IotLogDebug( "(MQTT connection %p) SUBACK in data stream.", pMqttConnection ); - /* Choose SUBACK deserializer. */ - deserialize = _IotMqtt_DeserializeSuback; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->deserialize.suback != NULL ) - { - deserialize = pMqttConnection->pSerializer->deserialize.suback; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Deserialize SUBACK and notify of result. */ pIncomingPacket->u.pMqttConnection = pMqttConnection; - status = deserialize( pIncomingPacket ); + + status = _getSubackDeserializer( pMqttConnection->pSerializer )( + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_SUBSCRIBE, &( pIncomingPacket->packetIdentifier ) ); @@ -546,30 +512,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne case MQTT_PACKET_TYPE_UNSUBACK: IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); - - /* Choose UNSUBACK deserializer. */ - deserialize = _IotMqtt_DeserializeUnsuback; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->deserialize.unsuback != NULL ) - { - deserialize = pMqttConnection->pSerializer->deserialize.unsuback; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Deserialize UNSUBACK and notify of result. */ - status = deserialize( pIncomingPacket ); + status = _getUnsubackDeserializer( pMqttConnection->pSerializer)( + pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_UNSUBSCRIBE, &( pIncomingPacket->packetIdentifier ) ); @@ -591,29 +537,9 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == MQTT_PACKET_TYPE_PINGRESP ); IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); - /* Choose PINGRESP deserializer. */ - deserialize = _IotMqtt_DeserializePingresp; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->deserialize.pingresp != NULL ) - { - deserialize = pMqttConnection->pSerializer->deserialize.pingresp; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Deserialize PINGRESP. */ - status = deserialize( pIncomingPacket ); + status = _getPingrespDeserializer( pMqttConnection->pSerializer)( + pIncomingPacket ); if( status == IOT_MQTT_SUCCESS ) { @@ -660,34 +586,10 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, IotMqttError_t status = IOT_MQTT_STATUS_PENDING; _mqttOperation_t * pPubackOperation = NULL; - /* Default PUBACK serializer function. */ - IotMqttError_t ( * serializePuback )( uint16_t, - uint8_t **, - size_t * ) = _IotMqtt_SerializePuback; - IotLogDebug( "(MQTT connection %p) Sending PUBACK for received PUBLISH %hu.", pMqttConnection, packetIdentifier ); - /* Choose PUBACK serializer and free packet functions. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->serialize.puback != NULL ) - { - serializePuback = pMqttConnection->pSerializer->serialize.puback; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Create a PUBACK operation. */ status = _IotMqtt_CreateOperation( pMqttConnection, 0, @@ -703,7 +605,8 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, pPubackOperation->u.operation.type = IOT_MQTT_PUBACK; /* Generate a PUBACK packet from the packet identifier. */ - status = serializePuback( packetIdentifier, + status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( + packetIdentifier, &( pPubackOperation->u.operation.pMqttPacket ), &( pPubackOperation->u.operation.packetSize ) ); @@ -813,9 +716,6 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason /* Network close function. */ IotNetworkError_t ( * closeConnection) ( void * ) = NULL; - /* Default free packet function. */ - void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; - /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); pMqttConnection->disconnected = true; @@ -839,18 +739,9 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason * the executing keep-alive job will clean up itself. */ if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { - /* Choose a function to free the packet. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->freePacket != NULL ) - { - freePacket = pMqttConnection->pSerializer->freePacket; - } - } - #endif - - freePacket( pMqttConnection->pingreq.u.operation.pMqttPacket ); + /* Free the packet */ + _getMqttFreePacketFunc( pMqttConnection->pSerializer )( + pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 5a099120dd..ce7f7d229e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -44,6 +44,31 @@ /* Atomics include. */ #include "iot_atomic.h" +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Declaration of local MQTT serializer override selectors + */ +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttPublishSetDup_t, + _getMqttPublishSetDupFunc, + _IotMqtt_PublishSetDup, + serialize.publishSetDup +) +_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( + IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket +) +#else +#define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket +#define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup +#endif +/** @endcond */ + /*-----------------------------------------------------------*/ /** @@ -135,29 +160,6 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; bool status = true, setDup = false; - /* Choose a set DUP function. */ - void ( * publishSetDup )( uint8_t *, - uint8_t *, - uint16_t * ) = _IotMqtt_PublishSetDup; - - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->serialize.publishSetDup != NULL ) - { - publishSetDup = pMqttConnection->pSerializer->serialize.publishSetDup; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Only PUBLISH may be retried. */ IotMqtt_Assert( pOperation->u.operation.type == IOT_MQTT_PUBLISH_TO_SERVER ); @@ -208,8 +210,9 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) EMPTY_ELSE_MARKER; } - /* Always set the DUP flag on the first retry. */ - publishSetDup( pOperation->u.operation.pMqttPacket, + /* Set the DUP flag */ + _getMqttPublishSetDupFunc( pMqttConnection->pSerializer )( + pOperation->u.operation.pMqttPacket, pOperation->u.operation.pPacketIdentifierHigh, &( pOperation->u.operation.packetIdentifier ) ); @@ -568,9 +571,6 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) { _mqttConnection_t * pMqttConnection = pOperation->pMqttConnection; - /* Default free packet function. */ - void ( * freePacket )( uint8_t * ) = _IotMqtt_FreePacket; - IotLogDebug( "(MQTT connection %p, %s operation %p) Destroying operation.", pMqttConnection, IotMqtt_OperationType( pOperation->u.operation.type ), @@ -607,25 +607,8 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) /* Free any allocated MQTT packet. */ if( pOperation->u.operation.pMqttPacket != NULL ) { - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - if( pMqttConnection->pSerializer != NULL ) - { - if( pMqttConnection->pSerializer->freePacket != NULL ) - { - freePacket = pMqttConnection->pSerializer->freePacket; - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - - freePacket( pOperation->u.operation.pMqttPacket ); + _getMqttFreePacketFunc( pMqttConnection->pSerializer )( + pOperation->u.operation.pMqttPacket ); IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", pMqttConnection, diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index ae4fc4703b..bc2c959390 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -373,9 +373,7 @@ typedef struct _mqttConnection const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ - #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ - #endif + const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ @@ -962,4 +960,32 @@ bool _IotMqtt_GetNextByte( void * pNetworkConnection, void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason, _mqttConnection_t * pMqttConnection ); +#if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 +/** + * @brief Utility macro for creating serializer override selector functions + */ +#define _IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( _funcType_t, _funcName, _defaultFunc, _serializerMember ) \ +static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ); \ +static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ) \ +{ \ + _funcType_t _returnValue = _defaultFunc; \ + if( pSerializer != NULL ) \ + { \ + if( pSerializer->_serializerMember != NULL ) \ + { \ + _returnValue = pSerializer->_serializerMember; \ + } \ + else \ + { \ + EMPTY_ELSE_MARKER; \ + } \ + } \ + else \ + { \ + EMPTY_ELSE_MARKER; \ + } \ + return _returnValue; \ +} +#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #endif /* ifndef IOT_MQTT_INTERNAL_H_ */ From 0aef3d2004dfd25d494a8b04f56c5f835a6d9137 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Fri, 11 Oct 2019 14:15:29 -0700 Subject: [PATCH 291/844] chore: Defender doxygen comment cleanup. (#599) Defender: Cleaned up doxygen comments and added parameter validation. --- libraries/aws/defender/include/aws_iot_defender.h | 12 ++++++------ libraries/aws/defender/src/aws_iot_defender_api.c | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/libraries/aws/defender/include/aws_iot_defender.h b/libraries/aws/defender/include/aws_iot_defender.h index ddee4d0c85..6f7ba05712 100644 --- a/libraries/aws/defender/include/aws_iot_defender.h +++ b/libraries/aws/defender/include/aws_iot_defender.h @@ -100,13 +100,13 @@ { \ .pCallbackContext = NULL, \ .function = NULL \ - } + } /**< Initializer of #AwsIotDefenderCallback_t. */ #define AWS_IOT_DEFENDER_START_INFO_INITIALIZER \ { .pClientIdentifier = NULL, \ .clientIdentifierLength = 0, \ .mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER, \ .callback = AWS_IOT_DEFENDER_CALLBACK_INITIALIZER \ - } /**< Initializer of #AwsIotDefenderCallbackInfo_t. */ + } /**< Initializer of #AwsIotDefenderStartInfo_t. */ /**@} */ /** @@ -196,9 +196,9 @@ typedef struct AwsIotDefenderCallback */ typedef struct AwsIotDefenderStartInfo { - const char * pClientIdentifier; /**< @brief MQTT client identifier. */ - uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - IotMqttConnection_t mqttConnection; /**< MQTT connection used by defender (required). */ + const char * pClientIdentifier; /**< @brief MQTT client identifier (required). */ + uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier (required). */ + IotMqttConnection_t mqttConnection; /**< @brief MQTT connection used by defender (required). */ AwsIotDefenderCallback_t callback; /**< Callback function parameter (optional). */ } AwsIotDefenderStartInfo_t; @@ -300,7 +300,7 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me * const AwsIotDefenderCallback_t callback = { .function = logDefenderCallback, .pCallbackContext = NULL }; * * // define parameters of AwsIotDefender_Start function - * // Note: This example assumes, connection is already established and metrics library is initialized. + * // Note: This example assumes MQTT connection is already established and metrics library is initialized. * const AwsIotDefenderStartInfo_t startInfo = * { * .pClientIdentifier = pClientIdentifier, diff --git a/libraries/aws/defender/src/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c index cd27f968e7..3f399e64ac 100644 --- a/libraries/aws/defender/src/aws_iot_defender_api.c +++ b/libraries/aws/defender/src/aws_iot_defender_api.c @@ -36,6 +36,7 @@ #define WAIT_METRICS_JOB_MAX_SECONDS ( 5 ) #define MAX_DEFENDER_OUTSTANDING_PUBLISH_REQ ( ( uint32_t ) 1 ) +#define MAX_CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) 128 ) #if WAIT_METRICS_JOB_MAX_SECONDS < AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS #error "_WAIT_METRICS_JOB_MAX_SECONDS must be greater than AWS_IOT_DEFENDER_WAIT_SERVER_MAX_SECONDS." @@ -148,7 +149,10 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn IOT_FUNCTION_ENTRY( AwsIotDefenderError_t, AWS_IOT_DEFENDER_SUCCESS ); - if( ( pStartInfo == NULL ) || ( pStartInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) ) + if( ( pStartInfo == NULL ) || + ( pStartInfo->mqttConnection == IOT_MQTT_CONNECTION_INITIALIZER ) || + ( pStartInfo->pClientIdentifier == NULL ) || + ( pStartInfo->clientIdentifierLength > MAX_CLIENT_IDENTIFIER_LENGTH ) ) { IotLogError( "Input startInfo is invalid." ); IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_DEFENDER_INVALID_INPUT ); From 43da0c87c64138d6a3ba1a1ee1e5cce73b17e424 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Sun, 13 Oct 2019 22:33:42 -0700 Subject: [PATCH 292/844] feat: Add 2 additional APIs to serializer decoder interface for container types Additional APIs include: * getBufferAddress() for obtaining the starting address of underlying raw encoded data in buffer * getEncodedDataSize() for obtaining the size of the underlying raw encoded data in buffer --- .../serializer/include/iot_serializer.h | 79 ++++-- .../cbor/iot_serializer_tinycbor_decoder.c | 129 ++++++++-- .../serializer/test/iot_tests_serializer.c | 4 +- .../test/unit/iot_tests_serializer_cbor.c | 225 +++++++++++++++++- 4 files changed, 398 insertions(+), 39 deletions(-) diff --git a/libraries/standard/serializer/include/iot_serializer.h b/libraries/standard/serializer/include/iot_serializer.h index 8b29afc9dd..87ef663f3e 100644 --- a/libraries/standard/serializer/include/iot_serializer.h +++ b/libraries/standard/serializer/include/iot_serializer.h @@ -181,27 +181,41 @@ #endif #endif /* if IOT_STATIC_MEMORY_ONLY */ -#define IOT_SERIALIZER_INDEFINITE_LENGTH 0xffffffff +#define IOT_SERIALIZER_INDEFINITE_LENGTH 0xffffffff -#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_STREAM } +#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_STREAM \ + { .pHandle = NULL, .type = \ + IOT_SERIALIZER_CONTAINER_STREAM } -#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_MAP } +#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_MAP \ + { .pHandle = NULL, .type = \ + IOT_SERIALIZER_CONTAINER_MAP } -#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY { .pHandle = NULL, .type = IOT_SERIALIZER_CONTAINER_ARRAY } +#define IOT_SERIALIZER_ENCODER_CONTAINER_INITIALIZER_ARRAY \ + { .pHandle = NULL, .type = \ + IOT_SERIALIZER_CONTAINER_ARRAY } -#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED } +#define IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER { .type = IOT_SERIALIZER_UNDEFINED } -#define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL +#define IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER NULL /* helper macro to create scalar data */ -#define IotSerializer_ScalarSignedInt( signedIntValue ) \ - ( IotSerializerScalarData_t ) { .value = { .u.signedInt = ( signedIntValue ) }, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT } \ - -#define IotSerializer_ScalarTextString( pTextStringValue ) \ - ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( ( uint8_t * ) pTextStringValue ), .u.string.length = strlen( pTextStringValue ) }, .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ - -#define IotSerializer_ScalarByteString( pByteStringValue, pByteStringLength ) \ - ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( pByteStringValue ), .u.string.length = ( pByteStringLength ) }, .type = IOT_SERIALIZER_SCALAR_BYTE_STRING } \ +#define IotSerializer_ScalarSignedInt( signedIntValue ) \ + ( IotSerializerScalarData_t ) { .value = { .u.signedInt = ( signedIntValue ) }, .type = \ + IOT_SERIALIZER_SCALAR_SIGNED_INT } \ + +#define IotSerializer_ScalarTextString( pTextStringValue ) \ + ( IotSerializerScalarData_t ) { .value = { .u.string.pString = \ + ( ( uint8_t * ) pTextStringValue ), \ + .u.string.length \ + = strlen( pTextStringValue ) }, \ + .type = IOT_SERIALIZER_SCALAR_TEXT_STRING } \ + +#define IotSerializer_ScalarByteString( pByteStringValue, pByteStringLength ) \ + ( IotSerializerScalarData_t ) { .value = { .u.string.pString = ( pByteStringValue ), \ + .u.string.length \ + = ( pByteStringLength ) }, .type = \ + IOT_SERIALIZER_SCALAR_BYTE_STRING } \ /* Determine if an object is a container. */ #define IotSerializer_IsContainer( object ) \ @@ -277,7 +291,7 @@ typedef struct IotSerializerDecoderObject IotSerializerDataType_t type; union { - /* useful when this is a container */ + /* Useful if the type is a container. */ void * pHandle; /* if the type is a container, the scalarValue is unuseful */ IotSerializerScalarValue_t value; @@ -311,7 +325,8 @@ typedef struct IotSerializerEncodeInterface /** * @brief Initialize the object's handle with specified buffer and max size. * - * @param[in] pEncoderObject Pointer of Encoder Object. After init, its type will be set to IOT_SERIALIZER_CONTAINER_STREAM. + * @param[in] pEncoderObject Pointer of Encoder Object. After init, its type will be set to + * IOT_SERIALIZER_CONTAINER_STREAM. * @param[in] pDataBuffer Pointer to allocated buffer by user; * NULL pDataBuffer is valid, used to calculate needed size by calling getExtraBufferSizeNeeded. * @param[in] maxSize Allocated buffer size @@ -354,7 +369,8 @@ typedef struct IotSerializerEncodeInterface */ IotSerializerError_t ( * openContainerWithKey )( IotSerializerEncoderObject_t * pEncoderObject, const char * pKey, - IotSerializerEncoderObject_t * pNewEncoderObject, + IotSerializerEncoderObject_t * + pNewEncoderObject, size_t length ); /** @@ -470,6 +486,35 @@ typedef struct IotSerializerDecodeInterface */ bool ( * isEndOfContainer )( IotSerializerDecoderIterator_t iterator ); + /** + * @brief Function to obtain the starting buffer address of the raw encoded data (scalar or container type) + * represented by the passed decoder object. + * Container SHOULD be of type array or map. + * @param[in] pDecoderObject The decoder object whose underlying data's starting location in the buffer + * is to be returned. + * @param[out] pEncodedDataStartAddr This will be populated with the starting location of the encoded object + * in the data buffer. + * @return #IOT_SERIALIZER_SUCCESS if successful; otherwise #IOT_SERIALIZER_NOT_SUPPORTED + * for a non-container type iterator. + */ + IotSerializerError_t ( * getBufferAddress )( IotSerializerDecoderObject_t * + pDecoderObject, + const uint8_t ** + pEncodedDataStartAddr ); + + + /** + * @brief Function to get the size of the raw encoded data in the buffer (ONLY of container type object) + * that is represented by the passed decoder object. + * Container SHOULD be of type array or map. + * @param[in] pDecoderObject The decoder objects whose underlying buffer data's length needs to be + * calculated. + * @param[out] The length of the underlying data in the buffer represented by the decoder object. + * @return #IOT_SERIALIZER_SUCCESS if successful; otherwise #IOT_SERIALIZER_NOT_SUPPORTED + * for a non-container type iterator. + */ + IotSerializerError_t ( * getSizeOfEncodedData )( IotSerializerDecoderObject_t * pDecoderObject, + size_t * pEncodedDataLength ); /** diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c index d431fe3895..65c92163f1 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c @@ -31,11 +31,16 @@ #include "iot_serializer.h" #include "cbor.h" -#define _castDecoderObjectToCborValue( pDecoderObject ) ( ( pDecoderObject )->u.pHandle ) +#define _castDecoderObjectToCborValue( \ + pDecoderObject ) ( ( pDecoderObject )->u.pHandle ) -#define _castDecoderIteratorToCborValue( iterator ) ( ( ( IotSerializerDecoderObject_t * ) iterator )->u.pHandle ) +#define _castDecoderIteratorToCborValue( \ + iterator ) ( ( ( IotSerializerDecoderObject_t * ) iterator )->u. \ + pHandle ) -#define _isArrayOrMap( dataType ) ( ( ( dataType ) == IOT_SERIALIZER_CONTAINER_ARRAY ) || ( ( dataType ) == IOT_SERIALIZER_CONTAINER_MAP ) ) +#define _isArrayOrMap( dataType ) \ + ( ( ( dataType ) == \ + IOT_SERIALIZER_CONTAINER_ARRAY ) || ( ( dataType ) == IOT_SERIALIZER_CONTAINER_MAP ) ) static IotSerializerError_t _init( IotSerializerDecoderObject_t * pDecoderObject, const uint8_t * pDataBuffer, @@ -57,16 +62,37 @@ static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ); static void _destroy( IotSerializerDecoderObject_t * pDecoderObject ); +static IotSerializerError_t _getBufferAddress( IotSerializerDecoderObject_t * pDecoderObject, + const uint8_t ** pEncodedDataStartAddr ); + + + +static IotSerializerError_t _getSizeOfEncodedData( IotSerializerDecoderObject_t * pDecoderObject, + size_t * pEncodedDataLength ); + + +/*-----------------------------------------------------------*/ + +/** + * @brief Utility to calculates the length of the raw encoded data represented by the + * passed `pValue` object. + */ +static size_t calculateSizeOfCborObject( CborValue * pValue ); + +/*-----------------------------------------------------------*/ + static const IotSerializerDecodeInterface_t _cborDecoder = { - .init = _init, - .get = _get, - .find = _find, - .stepIn = _stepIn, - .stepOut = _stepOut, - .next = _next, - .isEndOfContainer = _isEndOfContainer, - .destroy = _destroy + .init = _init, + .get = _get, + .find = _find, + .stepIn = _stepIn, + .stepOut = _stepOut, + .next = _next, + .isEndOfContainer = _isEndOfContainer, + .getBufferAddress = _getBufferAddress, + .getSizeOfEncodedData = _getSizeOfEncodedData, + .destroy = _destroy }; /* Wrapper CborValue with additional fields. */ @@ -78,6 +104,29 @@ typedef struct _cborValueWrapper /*-----------------------------------------------------------*/ +static size_t calculateSizeOfCborObject( CborValue * pValue ) +{ + IotSerializer_Assert( pValue != NULL ); + CborValue nextValue; + CborError status = CborNoError; + + /* Copy the contents of the current CBOR object so that we can advance to the next object. */ + /* memcpy( &nextValue, &pValue->cborValue, sizeof( CborValue ) ); */ + nextValue = *pValue; + + /* Advance to the next element in the container. */ + status = cbor_value_advance( &nextValue ); + IotSerializer_Assert( status == CborNoError ); + + /* Suppress compiler warning of "unused" variable. */ + ( void ) status; + + return( ( size_t ) ( nextValue.ptr - pValue->ptr ) ); +} + +/*-----------------------------------------------------------*/ + + static void _translateErrorCode( CborError cborError, IotSerializerError_t * pSerializerError ) { @@ -167,14 +216,16 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal if( _isArrayOrMap( dataType ) ) { /* Save to decoder object's handle. */ - pDecoderObject->u.pHandle = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + pDecoderObject->u.pHandle = IotSerializer_MallocCborValue( + sizeof( _cborValueWrapper_t ) ); if( pDecoderObject->u.pHandle == NULL ) { return IOT_SERIALIZER_OUT_OF_MEMORY; } - memcpy( pDecoderObject->u.pHandle, pCborValueWrapper, sizeof( _cborValueWrapper_t ) ); + memcpy( pDecoderObject->u.pHandle, pCborValueWrapper, + sizeof( _cborValueWrapper_t ) ); } else /* Create scalar object. */ { @@ -245,7 +296,9 @@ static IotSerializerError_t _createDecoderObject( _cborValueWrapper_t * pCborVal * If its a finite length text/byte string, and user have passed a null length buffer, * we avoid copying the string by storing pointer to the start of the string. */ - pDecoderObject->u.value.u.string.pString = ( uint8_t * ) ( cbor_value_get_next_byte( &next ) - ( pDecoderObject->u.value.u.string.length ) ); + pDecoderObject->u.value.u.string.pString = + ( uint8_t * ) ( cbor_value_get_next_byte( &next ) - + ( pDecoderObject->u.value.u.string.length ) ); } else { @@ -405,7 +458,8 @@ static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObje CborError cborError = CborNoError; _cborValueWrapper_t * pCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); - _cborValueWrapper_t * pNewCborValueWrapper = IotSerializer_MallocCborValue( sizeof( _cborValueWrapper_t ) ); + _cborValueWrapper_t * pNewCborValueWrapper = IotSerializer_MallocCborValue( + sizeof( _cborValueWrapper_t ) ); cborError = cbor_value_enter_container( &pCborValueWrapper->cborValue, @@ -413,7 +467,8 @@ static IotSerializerError_t _stepIn( IotSerializerDecoderObject_t * pDecoderObje if( cborError == CborNoError ) { - IotSerializerDecoderObject_t * pNewObject = IotSerializer_MallocDecoderObject( sizeof( IotSerializerDecoderObject_t ) ); + IotSerializerDecoderObject_t * pNewObject = IotSerializer_MallocDecoderObject( + sizeof( IotSerializerDecoderObject_t ) ); pNewObject->type = pDecoderObject->type; @@ -480,6 +535,48 @@ static bool _isEndOfContainer( IotSerializerDecoderIterator_t iterator ) return cbor_value_at_end( &pCborValueWrapper->cborValue ); } +/*-----------------------------------------------------------*/ + +static IotSerializerError_t _getBufferAddress( IotSerializerDecoderObject_t * pDecoderObject, + const uint8_t ** pEncodedDataStartAddr ) +{ + IotSerializer_Assert( pDecoderObject != NULL ); + + IotSerializerError_t status = IOT_SERIALIZER_SUCCESS; + + if( IotSerializer_IsContainer( pDecoderObject ) ) + { + *pEncodedDataStartAddr = + ( ( _cborValueWrapper_t * ) ( pDecoderObject->u.pHandle ) )->cborValue.ptr; + } + else + { + status = IOT_SERIALIZER_NOT_SUPPORTED; + } + + return status; +} + +/*-----------------------------------------------------------*/ + +IotSerializerError_t _getSizeOfEncodedData( IotSerializerDecoderObject_t * pDecoderObject, + size_t * pEncodedDataLength ) +{ + IotSerializer_Assert( pDecoderObject != NULL ); + IotSerializerError_t status = IOT_SERIALIZER_SUCCESS; + + if( IotSerializer_IsContainer( pDecoderObject ) ) + { + *pEncodedDataLength = calculateSizeOfCborObject( + &( ( ( _cborValueWrapper_t * ) pDecoderObject->u.pHandle )->cborValue ) ); + } + else + { + status = IOT_SERIALIZER_NOT_SUPPORTED; + } + + return status; +} /*-----------------------------------------------------------*/ diff --git a/libraries/standard/serializer/test/iot_tests_serializer.c b/libraries/standard/serializer/test/iot_tests_serializer.c index 8eaa776dd2..0833fbdfa4 100644 --- a/libraries/standard/serializer/test/iot_tests_serializer.c +++ b/libraries/standard/serializer/test/iot_tests_serializer.c @@ -39,13 +39,15 @@ * @param[in] disableNetworkTests Whether tests that require the network should run. * @param[in] disableLongTests Whether tests that take a long time should run. */ -void RunSerializerTests( bool disableNetworkTests, bool disableLongTests ) +void RunSerializerTests( bool disableNetworkTests, + bool disableLongTests ) { /* Silence warnings about unused parameters. */ ( void ) disableNetworkTests; ( void ) disableLongTests; RUN_TEST_GROUP( Serializer_Unit_CBOR ); + RUN_TEST_GROUP( Serializer_Decoder_Unit_CBOR ); } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c index 475c40ed8d..87f8388f7e 100644 --- a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c +++ b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c @@ -108,7 +108,9 @@ TEST( Serializer_Unit_CBOR, Encoder_init_with_null_buffer ) TEST_ASSERT_NOT_NULL( encoderObject.pHandle ); /* Append an integer. */ - TEST_ASSERT_EQUAL( IOT_SERIALIZER_BUFFER_TOO_SMALL, _pCborEncoder->append( &encoderObject, IotSerializer_ScalarSignedInt( 1 ) ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_BUFFER_TOO_SMALL, _pCborEncoder->append( &encoderObject, + IotSerializer_ScalarSignedInt( + 1 ) ) ); /* Needed buffer size is 1 to encode integer "1". */ TEST_ASSERT_EQUAL( 1, _pCborEncoder->getExtraBufferSizeNeeded( &encoderObject ) ); @@ -204,7 +206,8 @@ TEST( Serializer_Unit_CBOR, Encoder_append_byte_string ) size_t outputLength = 20; TEST_ASSERT_EQUAL( CborNoError, - cbor_value_copy_byte_string( &outermostValue, outputBytes, &outputLength, NULL ) ); + cbor_value_copy_byte_string( &outermostValue, outputBytes, &outputLength, + NULL ) ); TEST_ASSERT_EQUAL( inputLength, outputLength ); @@ -213,7 +216,10 @@ TEST( Serializer_Unit_CBOR, Encoder_append_byte_string ) TEST( Serializer_Unit_CBOR, Encoder_open_a_scalar ) { - IotSerializerEncoderObject_t integerObject = { .pHandle = NULL, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT }; + IotSerializerEncoderObject_t integerObject = + { + .pHandle = NULL, .type = IOT_SERIALIZER_SCALAR_SIGNED_INT + }; TEST_ASSERT_EQUAL( IOT_SERIALIZER_INVALID_INPUT, _pCborEncoder->openContainer( &_encoderObject, &integerObject, 1 ) ); @@ -323,7 +329,8 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_map ) _pCborEncoder->openContainer( &_encoderObject, &mapObject_1, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainerWithKey( &mapObject_1, "map1", &mapObject_2, 1 ) ); + _pCborEncoder->openContainerWithKey( &mapObject_1, "map1", &mapObject_2, + 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborEncoder->appendKeyValue( &mapObject_2, "key", scalarData ) ); @@ -376,7 +383,8 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) _pCborEncoder->openContainer( &_encoderObject, &mapObject, 1 ) ); TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, - _pCborEncoder->openContainerWithKey( &mapObject, "array", &arrayObject, 3 ) ); + _pCborEncoder->openContainerWithKey( &mapObject, "array", &arrayObject, + 3 ) ); for( i = 0; i < 3; i++ ) { @@ -422,3 +430,210 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) TEST_ASSERT_TRUE( cbor_value_at_end( &arrayElement ) ); } + +static const uint8_t _testEncodedNestedMap[] = +{ + 0xA2, /* # map(2) */ + 0x61, /* # text(1) */ + 0x31, /* # "1" */ + 0xA1, /* # map(1) */ + 0x61, /* # text(1) */ + 0x41, /* # "A" */ + 0x0A, /* # unsigned(10) */ + 0x61, /* # text(1) */ + 0x33, /* # "3" */ + 0xF4, /* # false */ +}; + +TEST_GROUP( Serializer_Decoder_Unit_CBOR ); + +TEST_SETUP( Serializer_Decoder_Unit_CBOR ) +{ + TEST_ASSERT_EQUAL_INT( true, IotSdk_Init() ); + + _pCborDecoder = IotSerializer_GetCborDecoder(); +} + +TEST_TEAR_DOWN( Serializer_Decoder_Unit_CBOR ) +{ + IotSdk_Cleanup(); +} + +TEST_GROUP_RUNNER( Serializer_Decoder_Unit_CBOR ) +{ + RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderObjectWithNestedMap ); + RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderIteratorWithNestedMap ); +} + +TEST( Serializer_Decoder_Unit_CBOR, TestDecoderObjectWithNestedMap ) +{ + IotSerializerDecoderObject_t outermostDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + const uint8_t * pDecoderObjectStartAddr = NULL; + size_t decoderDataLength = 0; + IotSerializerDecoderObject_t outerMapDecoder1 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t innerMapDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t outerMapDecoder2 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + size_t unsupportedTypeDecoderObjectLength = 0; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &outermostDecoder, + _testEncodedNestedMap, + sizeof( _testEncodedNestedMap ) ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, outermostDecoder.type ); + + /* Make sure that the getBufferAddress() API returns the first location of the buffer for the + * outermost decoder object.*/ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, + _pCborDecoder->getBufferAddress( &outermostDecoder, + &pDecoderObjectStartAddr ) ); + TEST_ASSERT_EQUAL_PTR( &_testEncodedNestedMap[ 0 ], pDecoderObjectStartAddr ); + + /* Verify that the getSizeOfEncodedData() correctly calculates the length of the outermost decoder + * data. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->getSizeOfEncodedData( + &outermostDecoder, &decoderDataLength ) ); + TEST_ASSERT_EQUAL( sizeof( _testEncodedNestedMap ), decoderDataLength ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outermostDecoder, "1", + &outerMapDecoder1 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_CONTAINER_MAP, outerMapDecoder1.type ); + + /* Make sure that the getBufferAddress() API returns the first location in the buffer to the value + * for the entry keyed by "1".*/ + TEST_ASSERT_EQUAL_PTR( IOT_SERIALIZER_SUCCESS, + _pCborDecoder->getBufferAddress( &outerMapDecoder1, + &pDecoderObjectStartAddr ) ); + TEST_ASSERT_EQUAL_PTR( &_testEncodedNestedMap[ 3 ], pDecoderObjectStartAddr ); + + /* Verify that the getSizeOfEncodedData() correctly calculates the length of the container type + * value of the entry keyed by "1" */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->getSizeOfEncodedData( + &outerMapDecoder1, &decoderDataLength ) ); + TEST_ASSERT_EQUAL( 4u, decoderDataLength ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outerMapDecoder1, "A", + &innerMapDecoder ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_SIGNED_INT, innerMapDecoder.type ); + + /* Make sure that the getBufferAddress() API does not support getting buffer address of value in the + * the nested entry keyed by "A".*/ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, + _pCborDecoder->getBufferAddress( &innerMapDecoder, + &pDecoderObjectStartAddr ) ); + + /* Verify that the getSizeOfEncodedData() does not support calculation of non-container type data.*/ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, + _pCborDecoder->getSizeOfEncodedData( + &innerMapDecoder, &unsupportedTypeDecoderObjectLength ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outermostDecoder, "3", + &outerMapDecoder2 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SCALAR_BOOL, outerMapDecoder2.type ); + + /* Make sure that the getBufferAddress() API does not give the buffer address of the value data + * in the entry keyed by "3".*/ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, + _pCborDecoder->getBufferAddress( &outerMapDecoder2, + &pDecoderObjectStartAddr ) ); + + /* Verify that the getSizeOfEncodedData() does not support calculation of non-container type data.*/ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_NOT_SUPPORTED, + _pCborDecoder->getSizeOfEncodedData( + &outerMapDecoder2, &unsupportedTypeDecoderObjectLength ) ); + + _pCborDecoder->destroy( &outerMapDecoder1 ); + _pCborDecoder->destroy( &outerMapDecoder2 ); + _pCborDecoder->destroy( &outermostDecoder ); +} + +TEST( Serializer_Decoder_Unit_CBOR, TestDecoderIteratorWithNestedMap ) +{ + IotSerializerDecoderObject_t outerDecoder1 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderIterator_t outerIter = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; + const uint8_t * pDecoderObjectStartAddr = NULL; + IotSerializerDecoderObject_t iterToDecoderObject = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t iterToDecoderObject2 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t iterToDecoderObject3 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t outerDecoder2 = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderObject_t nestedMapDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderIterator_t nestedMapIter = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &outerDecoder1, + _testEncodedNestedMap, + sizeof( _testEncodedNestedMap ) ) ); + + /* Obtain an iterator to the contents of the map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &outerDecoder1, + &outerIter ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, + &iterToDecoderObject ) ); + /* Validate that we can obtain the key data of the first entry in the outer map. */ + TEST_ASSERT_EQUAL_STRING_LEN( "1", iterToDecoderObject.u.value.u.string.pString, + iterToDecoderObject.u.value.u.string.length ); + + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, + &iterToDecoderObject ) ); + /* Validate that we can obtain the value data (i.e. nested map) of the first entry in the parent/outer map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->getBufferAddress( + &iterToDecoderObject, &pDecoderObjectStartAddr ) ); + TEST_ASSERT_EQUAL_PTR( &_testEncodedNestedMap[ 3 ], pDecoderObjectStartAddr ); + _pCborDecoder->destroy( &iterToDecoderObject ); + + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, + &iterToDecoderObject2 ) ); + /* Validate that we can obtain the key data of the second entry in the outer map. */ + TEST_ASSERT_EQUAL_STRING_LEN( "3", iterToDecoderObject2.u.value.u.string.pString, + iterToDecoderObject2.u.value.u.string.length ); + _pCborDecoder->destroy( &iterToDecoderObject2 ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( outerIter, + &iterToDecoderObject2 ) ); + /* Validate that we can obtain the boolean value data of the second entry in the outer map. */ + TEST_ASSERT_EQUAL( false, iterToDecoderObject2.u.value.u.booleanValue ); + _pCborDecoder->destroy( &iterToDecoderObject2 ); + + /* Iterate to the end of the outer map container. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( outerIter ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( outerIter, + &outerDecoder1 ) ); + _pCborDecoder->destroy( &outerDecoder1 ); + + + /* Test with iterating in the nested map in the entry keyed by "1" */ + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &outerDecoder2, + _testEncodedNestedMap, + sizeof( _testEncodedNestedMap ) ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &outerDecoder2, "1", + &nestedMapDecoder ) ); + + /* Obtain an iterator to the contents of the nested map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &nestedMapDecoder, + &nestedMapIter ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( nestedMapIter, + &iterToDecoderObject3 ) ); + /* Validate that we can obtain the key data of the only entry in the nested map. */ + TEST_ASSERT_EQUAL_STRING_LEN( "A", iterToDecoderObject3.u.value.u.string.pString, + iterToDecoderObject3.u.value.u.string.length ); + + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( nestedMapIter ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->get( nestedMapIter, + &iterToDecoderObject3 ) ); + /* Validate that we can obtain the integer value of the only entry in the nested map. */ + TEST_ASSERT_EQUAL_INT( 10, iterToDecoderObject3.u.value.u.signedInt ); + + /* Iterate to the end of the nested map container. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( nestedMapIter ) ); + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( nestedMapIter, + &nestedMapDecoder ) ); + _pCborDecoder->destroy( &nestedMapDecoder ); + _pCborDecoder->destroy( &outerDecoder2 ); +} From 6b58c87122d29a85701aca9dd8e249f700c026c7 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Mon, 14 Oct 2019 16:55:38 -0700 Subject: [PATCH 293/844] Don't build mbed TLS if not needed (#601) --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e51cf551ad..aa162a86b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,7 +119,9 @@ add_subdirectory( libraries/aws/defender ) add_subdirectory( third_party/tinycbor ) # mbed TLS library (third-party). -add_subdirectory( third_party/mbedtls ) +if( NOT IOT_NETWORK_USE_OPENSSL ) + add_subdirectory( third_party/mbedtls ) +endif() # Set startup projects in Visual Studio. if( ${IOT_BUILD_TESTS} ) From 52663b969e06528a56aceeea4fe9448edf8a94fb Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Tue, 15 Oct 2019 08:59:46 -0700 Subject: [PATCH 294/844] Address remaining comments from PR#594, run uncrustify (#602) --- .../mqtt/include/types/iot_mqtt_types.h | 115 +++++----- libraries/standard/mqtt/src/iot_mqtt_api.c | 210 +++++++++--------- .../standard/mqtt/src/iot_mqtt_network.c | 176 +++++++-------- .../standard/mqtt/src/iot_mqtt_operation.c | 40 ++-- .../mqtt/src/private/iot_mqtt_internal.h | 63 +++--- 5 files changed, 305 insertions(+), 299 deletions(-) diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 98212ed976..fddcfc0623 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -701,9 +701,8 @@ struct _mqttPacket; * @param[in] pNetworkInterface Function pointers used to interact with the * network. */ -typedef uint8_t ( * IotMqttGetPacketType_t )( - void * pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); +typedef uint8_t ( * IotMqttGetPacketType_t )( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); /** * @brief Get the remaining length from a stream of bytes off the network. @@ -712,9 +711,8 @@ typedef uint8_t ( * IotMqttGetPacketType_t )( * @param[in] pNetworkInterface Function pointers used to interact with the * network. */ -typedef size_t ( * IotMqttGetRemainingLength_t )( - void * pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ); +typedef size_t ( * IotMqttGetRemainingLength_t )( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ); /** * @brief Free a packet generated by the serializer. @@ -730,19 +728,17 @@ typedef void ( * IotMqttFreePacket_t )( uint8_t * pPacket ); * @param[out] uint8_t** Where the CONNECT packet is written. * @param[out] size_t* Size of the CONNECT packet. */ -typedef IotMqttError_t ( * IotMqttSerializeConnect_t )( - const IotMqttConnectInfo_t * pConnectInfo, - uint8_t ** pConnectPacket, - size_t * pPacketSize ); +typedef IotMqttError_t ( * IotMqttSerializeConnect_t )( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ); /** * @brief PINGREQ packet serializer function. * @param[out] uint8_t** Where the PINGREQ packet is written. * @param[out] size_t* Size of the PINGREQ packet. */ -typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( - uint8_t ** pDisconnectPacket, - size_t * pPacketSize ); +typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ); /** * @brief PUBLISH packet serializer function. @@ -753,12 +749,11 @@ typedef IotMqttError_t ( * IotMqttSerializePingreq_t )( * @param[out] uint8_t** Where the high byte of the packet identifier * is written. */ -typedef IotMqttError_t ( * IotMqtt_SerializePublish_t )( - const IotMqttPublishInfo_t * pPublishInfo, - uint8_t ** pPublishPacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh ); +typedef IotMqttError_t ( * IotMqtt_SerializePublish_t )( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ); /** * @brief SUBSCRIBE/UNSUBSCRIBE packet serializer function. @@ -768,28 +763,25 @@ typedef IotMqttError_t ( * IotMqtt_SerializePublish_t )( * @param[out] size_t* Size of the SUBSCRIBE packet. * @param[out] uint16_t* The packet identifier generated for this SUBSCRIBE. */ -typedef IotMqttError_t ( * IotMqttSerializeSubscribe_t )( - const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ); +typedef IotMqttError_t ( * IotMqttSerializeSubscribe_t )( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ); /** * @brief DISCONNECT packet serializer function. * @param[out] uint8_t** Where the DISCONNECT packet is written. * @param[out] size_t* Size of the DISCONNECT packet. */ -typedef IotMqttError_t ( * IotMqttDisconnectSerializer_t )( - uint8_t ** ppDisconnectPacket, - size_t * pPacketSize ); +typedef IotMqttError_t ( * IotMqttSerializeDisconnect_t )( uint8_t ** ppDisconnectPacket, + size_t * pPacketSize ); /** * @brief MQTT packet deserializer function. * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure */ -typedef IotMqttError_t ( * IotMqttDeserializer_t )( - struct _mqttPacket * pMqttPacket ); +typedef IotMqttError_t ( * IotMqttDeserialize_t )( struct _mqttPacket * pMqttPacket ); /** * @brief PUBACK packet serializer function. @@ -797,20 +789,19 @@ typedef IotMqttError_t ( * IotMqttDeserializer_t )( * @param[out] uint8_t** Where the PUBACK packet is written. * @param[out] size_t* Size of the PUBACK packet. */ -typedef IotMqttError_t ( * IotMqttSerializePuback_t )( - uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ); +typedef IotMqttError_t ( * IotMqttSerializePuback_t )( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ); + /** * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. * @param[in] uint8_t* Pointer to the PUBLISH packet to modify. * @param[in] uint8_t* The high byte of any packet identifier to modify. * @param[out] uint16_t* New packet identifier (AWS IoT MQTT mode only). */ -typedef void ( * IotMqttPublishSetDup_t )( - uint8_t * pPublishPacket, - uint8_t * pPacketIdentifierHigh, - uint16_t * pNewPacketIdentifier ); +typedef void ( * IotMqttPublishSetDup_t )( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, + uint16_t * pNewPacketIdentifier ); #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 @@ -838,11 +829,13 @@ typedef void ( * IotMqttPublishSetDup_t )( * Default implementation: #_IotMqtt_GetPacketType */ IotMqttGetPacketType_t getPacketType; + /** * @brief Get the remaining length from a stream of bytes off the network. * Default implementation: #_IotMqtt_GetRemainingLength */ IotMqttGetRemainingLength_t getRemainingLength; + /** * @brief Free a packet generated by the serializer. * @@ -857,95 +850,101 @@ typedef void ( * IotMqttPublishSetDup_t )( * * Default implementation: #_IotMqtt_SerializeConnect */ - IotMqttSerializeConnect_t connect; + IotMqttSerializeConnect_t connect; + /** * @brief PUBLISH packet serializer function. * * Default implementation: #_IotMqtt_SerializePublish */ - IotMqtt_SerializePublish_t publish; + IotMqtt_SerializePublish_t publish; + /** * @brief Set the `DUP` bit in a QoS `1` PUBLISH packet. * * Default implementation: #_IotMqtt_PublishSetDup */ - IotMqttPublishSetDup_t publishSetDup; + IotMqttPublishSetDup_t publishSetDup; + /** * @brief PUBACK packet serializer function. * * Default implementation: #_IotMqtt_SerializePuback */ - IotMqttSerializePuback_t puback; + IotMqttSerializePuback_t puback; + /** * @brief SUBSCRIBE packet serializer function. * * Default implementation: #_IotMqtt_SerializeSubscribe */ - IotMqttSerializeSubscribe_t subscribe; + IotMqttSerializeSubscribe_t subscribe; + /** * @brief UNSUBSCRIBE packet serializer function. * * Default implementation: #_IotMqtt_SerializeUnsubscribe */ - IotMqttSerializeSubscribe_t unsubscribe; + IotMqttSerializeSubscribe_t unsubscribe; + /** * @brief PINGREQ packet serializer function. * * Default implementation: #_IotMqtt_SerializePingreq */ - IotMqttSerializePingreq_t pingreq; + IotMqttSerializePingreq_t pingreq; + /** * @brief DISCONNECT packet serializer function. * * Default implementation: #_IotMqtt_SerializeDisconnect */ - IotMqttDisconnectSerializer_t disconnect; + IotMqttSerializeDisconnect_t disconnect; } serialize; /**< @brief Overrides the packet serialization functions for a single connection. */ struct { /** * @brief CONNACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializeConnack */ - IotMqttDeserializer_t connack; + IotMqttDeserialize_t connack; + /** * @brief PUBLISH packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializePublish */ - IotMqttDeserializer_t publish; + IotMqttDeserialize_t publish; + /** * @brief PUBACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializePuback */ - IotMqttDeserializer_t puback; + IotMqttDeserialize_t puback; + /** * @brief SUBACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializeSuback */ - IotMqttDeserializer_t suback; + IotMqttDeserialize_t suback; + /** * @brief UNSUBACK packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializeUnsuback */ - IotMqttDeserializer_t unsuback; + IotMqttDeserialize_t unsuback; + /** * @brief PINGRESP packet deserializer function. - * @param[in,out] _mqttPacket* Pointer to an MQTT packet structure * * Default implementation: #_IotMqtt_DeserializePingresp */ - IotMqttDeserializer_t pingresp; + IotMqttDeserialize_t pingresp; } deserialize; /**< @brief Overrides the packet deserialization functions for a single connection. */ } IotMqttSerializer_t; diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 1e0e50e198..5a1b76316b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -144,6 +144,7 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation size_t subscriptionCount, uint32_t flags, IotMqttOperation_t * const pOperationReference ); + /** * @brief Utility function for creating and serializing subscription requests * @@ -182,57 +183,57 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializePingreq_t, - _getMqttPingreqSerializer, - _IotMqtt_SerializePingreq, - serialize.pingreq -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqtt_SerializePublish_t, - _getMqttPublishSerializer, - _IotMqtt_SerializePublish, - serialize.publish -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeSubscribe_t, - _getMqttSubscribeSerializer, - _IotMqtt_SerializeSubscribe, - serialize.subscribe -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeSubscribe_t, - _getMqttUnsubscribeSerializer, - _IotMqtt_SerializeUnsubscribe, - serialize.unsubscribe -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeConnect_t, - _getMqttConnectSerializer, - _IotMqtt_SerializeConnect, - serialize.connect -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDisconnectSerializer_t, - _getMqttDisconnectSerializer, - _IotMqtt_SerializeDisconnect, - serialize.disconnect -) -#else -#define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq -#define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish -#define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket -#define _getMqttSubscribeSerializer( pSerializer ) _IotMqtt_SerializeSubscribe -#define _getMqttUnsubscribeSerializer( pSerializer ) _IotMqtt_SerializeUnsubscribe -#define _getMqttConnectSerializer( pSerializer ) _IotMqtt_SerializeConnect -#define _getMqttDisconnectSerializer( pSerializer ) _IotMqtt_SerializeDisconnect -#endif + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializePingreq_t, + _getMqttPingreqSerializer, + _IotMqtt_SerializePingreq, + serialize.pingreq + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqtt_SerializePublish_t, + _getMqttPublishSerializer, + _IotMqtt_SerializePublish, + serialize.publish + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeSubscribe_t, + _getMqttSubscribeSerializer, + _IotMqtt_SerializeSubscribe, + serialize.subscribe + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeSubscribe_t, + _getMqttUnsubscribeSerializer, + _IotMqtt_SerializeUnsubscribe, + serialize.unsubscribe + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeConnect_t, + _getMqttConnectSerializer, + _IotMqtt_SerializeConnect, + serialize.connect + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializeDisconnect_t, + _getMqttDisconnectSerializer, + _IotMqtt_SerializeDisconnect, + serialize.disconnect + ) +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq + #define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish + #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket + #define _getMqttSubscribeSerializer( pSerializer ) _IotMqtt_SerializeSubscribe + #define _getMqttUnsubscribeSerializer( pSerializer ) _IotMqtt_SerializeUnsubscribe + #define _getMqttConnectSerializer( pSerializer ) _IotMqtt_SerializeConnect + #define _getMqttDisconnectSerializer( pSerializer ) _IotMqtt_SerializeDisconnect +#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /** @endcond */ /*-----------------------------------------------------------*/ @@ -379,8 +380,8 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo /* Generate a PINGREQ packet. */ serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( - &( pMqttConnection->pingreq.u.operation.pMqttPacket ), - &( pMqttConnection->pingreq.u.operation.packetSize ) ); + &( pMqttConnection->pingreq.u.operation.pMqttPacket ), + &( pMqttConnection->pingreq.u.operation.packetSize ) ); if( serializeStatus != IOT_MQTT_SUCCESS ) { @@ -552,7 +553,7 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) { IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); _getMqttFreePacketFunc( pMqttConnection->pSerializer )( - pMqttConnection->pingreq.u.operation.pMqttPacket ); + pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; @@ -670,6 +671,7 @@ static IotMqttError_t _subscriptionCommonSetup( IotMqttOperationType_t operation { EMPTY_ELSE_MARKER; } + IOT_FUNCTION_EXIT_NO_CLEANUP(); } @@ -699,7 +701,7 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op } else { - pSubscriptionOperation = ( * ppSubscriptionOperation ); + pSubscriptionOperation = ( *ppSubscriptionOperation ); } /* Check the subscription operation data and set the operation type. */ @@ -709,10 +711,10 @@ static IotMqttError_t _subscriptionCreateAndSerialize( IotMqttOperationType_t op /* Generate a subscription packet from the subscription list. */ status = serializeSubscription( pSubscriptionList, - subscriptionCount, - &( pSubscriptionOperation->u.operation.pMqttPacket ), - &( pSubscriptionOperation->u.operation.packetSize ), - &( pSubscriptionOperation->u.operation.packetIdentifier ) ); + subscriptionCount, + &( pSubscriptionOperation->u.operation.pMqttPacket ), + &( pSubscriptionOperation->u.operation.packetSize ), + &( pSubscriptionOperation->u.operation.packetIdentifier ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -746,13 +748,13 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, /* Create and serialize the subscription operation. */ status = _subscriptionCreateAndSerialize( operation, - mqttConnection, - serializeSubscription, - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - &pSubscriptionOperation ); + mqttConnection, + serializeSubscription, + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + &pSubscriptionOperation ); if( status != IOT_MQTT_SUCCESS ) { @@ -1222,9 +1224,9 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( - pConnectInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); + pConnectInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -1396,8 +1398,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, /* Generate a DISCONNECT packet. */ status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); } else { @@ -1480,26 +1482,28 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, IotMqttOperation_t * const pSubscribeOperation ) { IotMqttError_t status = _subscriptionCommonSetup( IOT_MQTT_SUBSCRIBE, - mqttConnection, - pSubscriptionList, - subscriptionCount, - flags, - pSubscribeOperation ); - if ( IOT_MQTT_SUCCESS == status ) + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pSubscribeOperation ); + + if( IOT_MQTT_SUCCESS == status ) { status = _subscriptionCommon( IOT_MQTT_SUBSCRIBE, - mqttConnection, - _getMqttSubscribeSerializer( mqttConnection->pSerializer ), - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pSubscribeOperation ); + mqttConnection, + _getMqttSubscribeSerializer( mqttConnection->pSerializer ), + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pSubscribeOperation ); } else { EMPTY_ELSE_MARKER; } + return status; } @@ -1551,12 +1555,13 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, IotMqttOperation_t * const pUnsubscribeOperation ) { IotMqttError_t status = _subscriptionCommonSetup( IOT_MQTT_UNSUBSCRIBE, - mqttConnection, - pSubscriptionList, - subscriptionCount, - flags, - pUnsubscribeOperation ); - if ( IOT_MQTT_SUCCESS == status ) + mqttConnection, + pSubscriptionList, + subscriptionCount, + flags, + pUnsubscribeOperation ); + + if( IOT_MQTT_SUCCESS == status ) { /* Remove the MQTT subscription list for an UNSUBSCRIBE. */ _IotMqtt_RemoveSubscriptionByTopicFilter( mqttConnection, @@ -1564,18 +1569,19 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, subscriptionCount ); status = _subscriptionCommon( IOT_MQTT_UNSUBSCRIBE, - mqttConnection, - _getMqttUnsubscribeSerializer( mqttConnection->pSerializer ), - pSubscriptionList, - subscriptionCount, - flags, - pCallbackInfo, - pUnsubscribeOperation ); + mqttConnection, + _getMqttUnsubscribeSerializer( mqttConnection->pSerializer ), + pSubscriptionList, + subscriptionCount, + flags, + pCallbackInfo, + pUnsubscribeOperation ); } else { EMPTY_ELSE_MARKER; } + return status; } @@ -1734,11 +1740,11 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, /* Generate a PUBLISH packet from pPublishInfo. */ status = _getMqttPublishSerializer( mqttConnection->pSerializer )( - pPublishInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ), - &( pOperation->u.operation.packetIdentifier ), - pPacketIdentifierHigh ); + pPublishInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ), + &( pOperation->u.operation.packetIdentifier ), + pPacketIdentifierHigh ); if( status != IOT_MQTT_SUCCESS ) { diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index a604d93358..ac86fedf7a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -112,78 +112,78 @@ static void _flushPacket( void * pNetworkConnection, * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttGetPacketType_t, - _getPacketTypeFunc, - _IotMqtt_GetPacketType, - getPacketType -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttGetRemainingLength_t, - _getRemainingLengthFunc, - _IotMqtt_GetRemainingLength, - getRemainingLength -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserializer_t, - _getConnackDeserializer, - _IotMqtt_DeserializeConnack, - deserialize.connack -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserializer_t, - _getPublishDeserializer, - _IotMqtt_DeserializePublish, - deserialize.publish -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserializer_t, - _getPubackDeserializer, - _IotMqtt_DeserializePuback, - deserialize.puback -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserializer_t, - _getSubackDeserializer, - _IotMqtt_DeserializeSuback, - deserialize.suback -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserializer_t, - _getUnsubackDeserializer, - _IotMqtt_DeserializeUnsuback, - deserialize.unsuback -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserializer_t, - _getPingrespDeserializer, - _IotMqtt_DeserializePingresp, - deserialize.pingresp -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializePuback_t, - _getMqttPubackSerializer, - _IotMqtt_SerializePuback, - serialize.puback -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket -) -#else -#define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType -#define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength -#define _getConnackDeserializer( pSerializer ) _IotMqtt_DeserializeConnack -#define _getPublishDeserializer( pSerializer ) _IotMqtt_DeserializePublish -#define _getPubackDeserializer( pSerializer ) _IotMqtt_DeserializePuback -#define _getSubackDeserializer( pSerializer ) _IotMqtt_DeserializeSuback -#define _getUnsubackDeserializer( pSerializer ) _IotMqtt_DeserializeUnsuback -#define _getPingrespDeserializer( pSerializer ) _IotMqtt_DeserializePingresp -#define _getMqttPubackSerializer( pSerializer ) _IotMqtt_SerializePuback -#define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket -#endif + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttGetPacketType_t, + _getPacketTypeFunc, + _IotMqtt_GetPacketType, + getPacketType + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttGetRemainingLength_t, + _getRemainingLengthFunc, + _IotMqtt_GetRemainingLength, + getRemainingLength + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserialize_t, + _getConnackDeserializer, + _IotMqtt_DeserializeConnack, + deserialize.connack + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserialize_t, + _getPublishDeserializer, + _IotMqtt_DeserializePublish, + deserialize.publish + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserialize_t, + _getPubackDeserializer, + _IotMqtt_DeserializePuback, + deserialize.puback + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserialize_t, + _getSubackDeserializer, + _IotMqtt_DeserializeSuback, + deserialize.suback + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserialize_t, + _getUnsubackDeserializer, + _IotMqtt_DeserializeUnsuback, + deserialize.unsuback + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttDeserialize_t, + _getPingrespDeserializer, + _IotMqtt_DeserializePingresp, + deserialize.pingresp + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttSerializePuback_t, + _getMqttPubackSerializer, + _IotMqtt_SerializePuback, + serialize.puback + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket + ) +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType + #define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength + #define _getConnackDeserializer( pSerializer ) _IotMqtt_DeserializeConnack + #define _getPublishDeserializer( pSerializer ) _IotMqtt_DeserializePublish + #define _getPubackDeserializer( pSerializer ) _IotMqtt_DeserializePuback + #define _getSubackDeserializer( pSerializer ) _IotMqtt_DeserializeSuback + #define _getUnsubackDeserializer( pSerializer ) _IotMqtt_DeserializeUnsuback + #define _getPingrespDeserializer( pSerializer ) _IotMqtt_DeserializePingresp + #define _getMqttPubackSerializer( pSerializer ) _IotMqtt_SerializePuback + #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket +#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /** @endcond */ /*-----------------------------------------------------------*/ @@ -228,8 +228,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, /* Read the packet type, which is the first byte available. */ pIncomingPacket->type = _getPacketTypeFunc( pMqttConnection->pSerializer )( - pNetworkConnection, - pMqttConnection->pNetworkInterface ); + pNetworkConnection, + pMqttConnection->pNetworkInterface ); /* Check that the incoming packet type is valid. */ if( _incomingPacketValid( pIncomingPacket->type ) == false ) @@ -247,8 +247,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, /* Read the remaining length. */ pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( - pNetworkConnection, - pMqttConnection->pNetworkInterface ); + pNetworkConnection, + pMqttConnection->pNetworkInterface ); if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { @@ -343,7 +343,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); /* Deserialize CONNACK and notify of result. */ status = _getConnackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_CONNECT, NULL ); @@ -384,7 +384,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne /* Deserialize incoming PUBLISH. */ status = _getPublishDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + pIncomingPacket ); if( status == IOT_MQTT_SUCCESS ) { @@ -469,7 +469,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne /* Deserialize PUBACK and notify of result. */ status = _getPubackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_PUBLISH_TO_SERVER, &( pIncomingPacket->packetIdentifier ) ); @@ -493,7 +493,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne pIncomingPacket->u.pMqttConnection = pMqttConnection; status = _getSubackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_SUBSCRIBE, &( pIncomingPacket->packetIdentifier ) ); @@ -513,8 +513,8 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne case MQTT_PACKET_TYPE_UNSUBACK: IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); /* Deserialize UNSUBACK and notify of result. */ - status = _getUnsubackDeserializer( pMqttConnection->pSerializer)( - pIncomingPacket ); + status = _getUnsubackDeserializer( pMqttConnection->pSerializer )( + pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_UNSUBSCRIBE, @@ -538,8 +538,8 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); /* Deserialize PINGRESP. */ - status = _getPingrespDeserializer( pMqttConnection->pSerializer)( - pIncomingPacket ); + status = _getPingrespDeserializer( pMqttConnection->pSerializer )( + pIncomingPacket ); if( status == IOT_MQTT_SUCCESS ) { @@ -606,9 +606,9 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, /* Generate a PUBACK packet from the packet identifier. */ status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( - packetIdentifier, - &( pPubackOperation->u.operation.pMqttPacket ), - &( pPubackOperation->u.operation.packetSize ) ); + packetIdentifier, + &( pPubackOperation->u.operation.pMqttPacket ), + &( pPubackOperation->u.operation.packetSize ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -741,7 +741,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason { /* Free the packet */ _getMqttFreePacketFunc( pMqttConnection->pSerializer )( - pMqttConnection->pingreq.u.operation.pMqttPacket ); + pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index ce7f7d229e..0ba89108a0 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -51,22 +51,22 @@ * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttPublishSetDup_t, - _getMqttPublishSetDupFunc, - _IotMqtt_PublishSetDup, - serialize.publishSetDup -) -_IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( - IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket -) -#else -#define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket -#define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup -#endif + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttPublishSetDup_t, + _getMqttPublishSetDupFunc, + _IotMqtt_PublishSetDup, + serialize.publishSetDup + ) + _SERIALIZER_OVERRIDE_SELECTOR( + IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket + ) +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ + #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket + #define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup +#endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ /** @endcond */ /*-----------------------------------------------------------*/ @@ -212,9 +212,9 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) /* Set the DUP flag */ _getMqttPublishSetDupFunc( pMqttConnection->pSerializer )( - pOperation->u.operation.pMqttPacket, - pOperation->u.operation.pPacketIdentifierHigh, - &( pOperation->u.operation.packetIdentifier ) ); + pOperation->u.operation.pMqttPacket, + pOperation->u.operation.pPacketIdentifierHigh, + &( pOperation->u.operation.packetIdentifier ) ); if( pMqttConnection->awsIotMqttMode == true ) { @@ -608,7 +608,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) if( pOperation->u.operation.pMqttPacket != NULL ) { _getMqttFreePacketFunc( pMqttConnection->pSerializer )( - pOperation->u.operation.pMqttPacket ); + pOperation->u.operation.pMqttPacket ); IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", pMqttConnection, diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index bc2c959390..e67b89b8f9 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -373,18 +373,18 @@ typedef struct _mqttConnection const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */ IotMqttCallbackInfo_t disconnectCallback; /**< @brief A function to invoke when this connection is disconnected. */ - const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ + const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */ - bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ - IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ - int32_t references; /**< @brief Counts callbacks and operations using this connection. */ - IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ - IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ + bool disconnected; /**< @brief Tracks if this connection has been disconnected. */ + IotMutex_t referencesMutex; /**< @brief Recursive mutex. Grants access to connection state and operation lists. */ + int32_t references; /**< @brief Counts callbacks and operations using this connection. */ + IotListDouble_t pendingProcessing; /**< @brief List of operations waiting to be processed by a task pool routine. */ + IotListDouble_t pendingResponse; /**< @brief List of processed operations awaiting a server response. */ - IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ - IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ + IotListDouble_t subscriptionList; /**< @brief Holds subscriptions associated with this connection. */ + IotMutex_t subscriptionMutex; /**< @brief Grants exclusive access to the subscription list. */ - _mqttOperation_t pingreq; /**< @brief Operation used for MQTT keep-alive. */ + _mqttOperation_t pingreq; /**< @brief Operation used for MQTT keep-alive. */ } _mqttConnection_t; /** @@ -961,31 +961,32 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason _mqttConnection_t * pMqttConnection ); #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 + /** * @brief Utility macro for creating serializer override selector functions */ -#define _IOT_MQTT_SERIALIZER_OVERRIDE_SELECTOR( _funcType_t, _funcName, _defaultFunc, _serializerMember ) \ -static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ); \ -static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ) \ -{ \ - _funcType_t _returnValue = _defaultFunc; \ - if( pSerializer != NULL ) \ - { \ - if( pSerializer->_serializerMember != NULL ) \ - { \ - _returnValue = pSerializer->_serializerMember; \ - } \ - else \ - { \ - EMPTY_ELSE_MARKER; \ - } \ - } \ - else \ - { \ - EMPTY_ELSE_MARKER; \ - } \ - return _returnValue; \ -} + #define _SERIALIZER_OVERRIDE_SELECTOR( _funcType_t, _funcName, _defaultFunc, _serializerMember ) \ + static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ); \ + static _funcType_t _funcName( const IotMqttSerializer_t * pSerializer ) \ + { \ + _funcType_t _returnValue = _defaultFunc; \ + if( pSerializer != NULL ) \ + { \ + if( pSerializer->_serializerMember != NULL ) \ + { \ + _returnValue = pSerializer->_serializerMember; \ + } \ + else \ + { \ + EMPTY_ELSE_MARKER; \ + } \ + } \ + else \ + { \ + EMPTY_ELSE_MARKER; \ + } \ + return _returnValue; \ + } #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #endif /* ifndef IOT_MQTT_INTERNAL_H_ */ From 0c17433236777d81f252f8d4ec9bbb7867b2e305 Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Wed, 16 Oct 2019 08:04:12 -0700 Subject: [PATCH 295/844] Documentation cleanup/improved signature for IotNetworkInterface_t::create (#603) * Address remaining comments from PR#594, run uncrustify * Documentation cleanup --- libraries/platform/iot_network.h | 119 +++++++++--------- libraries/standard/mqtt/include/iot_mqtt.h | 62 +++++---- .../mqtt/include/types/iot_mqtt_types.h | 61 +++++---- ports/common/include/iot_network_mbedtls.h | 4 +- ports/common/include/iot_network_openssl.h | 4 +- ports/common/src/iot_network_mbedtls.c | 4 +- ports/common/src/iot_network_metrics.c | 10 +- ports/posix/src/iot_network_openssl.c | 4 +- 8 files changed, 133 insertions(+), 135 deletions(-) diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index b9174eac0e..10a40f51f9 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -91,6 +91,64 @@ typedef void ( * IotNetworkReceiveCallback_t )( void * pConnection, void * pContext ); /* @[declare_platform_network_receivecallback] */ +/** + * @ingroup platform_datatypes_paramstructs + * @brief Information on the remote server for connection setup. + * + * May be passed to #IotNetworkInterface_t.create as `pConnectionInfo`. This + * structure contains commonly-used parameters, but may be replaced with an + * alternative. + */ +typedef struct IotNetworkServerInfo +{ + const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ + uint16_t port; /**< @brief Server port in host-order. */ +} IotNetworkServerInfo_t; + +/** + * @ingroup platform_datatypes_paramstructs + * @brief Contains the credentials necessary for connection setup. + * + * May be passed to #IotNetworkInterface_t.create as `pCredentialInfo`. This + * structure contains commonly-used parameters, but may be replaced with an + * alternative. + */ +typedef struct IotNetworkCredentials +{ + /** + * @brief Set this to a non-NULL value to use ALPN. + * + * This string must be NULL-terminated. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char * pAlpnProtos; + + /** + * @brief Set this to a non-zero value to use TLS max fragment length + * negotiation (TLS MFLN). + * + * @note The network stack may have a minimum value for this parameter and + * may return an error if this parameter is too small. + */ + size_t maxFragmentLength; + + /** + * @brief Disable server name indication (SNI) for a TLS session. + */ + bool disableSni; + + const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ + size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials_t.pRootCa. */ + const char * pClientCert; /**< @brief String representing the client certificate. */ + size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials_t.pClientCert. */ + const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ + size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials_t.pPrivateKey. */ +} IotNetworkCredentials_t; + + /** * @ingroup platform_datatypes_paramstructs * @brief Represents the functions of a network stack. @@ -114,8 +172,8 @@ typedef struct IotNetworkInterface * @return Any #IotNetworkError_t, as defined by the network stack. */ /* @[declare_platform_network_create] */ - IotNetworkError_t ( * create )( void * pConnectionInfo, - void * pCredentialInfo, + IotNetworkError_t ( * create )( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ); /* @[declare_platform_network_create] */ @@ -230,61 +288,4 @@ typedef struct IotNetworkInterface /* @[declare_platform_network_destroy] */ } IotNetworkInterface_t; -/** - * @ingroup platform_datatypes_paramstructs - * @brief Information on the remote server for connection setup. - * - * May be passed to #IotNetworkInterface_t.create as `pConnectionInfo`. This - * structure contains commonly-used parameters, but may be replaced with an - * alternative. - */ -typedef struct IotNetworkServerInfo -{ - const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ - uint16_t port; /**< @brief Server port in host-order. */ -} IotNetworkServerInfo_t; - -/** - * @ingroup platform_datatypes_paramstructs - * @brief Contains the credentials necessary for connection setup. - * - * May be passed to #IotNetworkInterface_t.create as `pCredentialInfo`. This - * structure contains commonly-used parameters, but may be replaced with an - * alternative. - */ -typedef struct IotNetworkCredentials -{ - /** - * @brief Set this to a non-NULL value to use ALPN. - * - * This string must be NULL-terminated. - * - * See [this link] - * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) - * for more information. - */ - const char * pAlpnProtos; - - /** - * @brief Set this to a non-zero value to use TLS max fragment length - * negotiation (TLS MFLN). - * - * @note The network stack may have a minimum value for this parameter and - * may return an error if this parameter is too small. - */ - size_t maxFragmentLength; - - /** - * @brief Disable server name indication (SNI) for a TLS session. - */ - bool disableSni; - - const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ - size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials_t.pRootCa. */ - const char * pClientCert; /**< @brief String representing the client certificate. */ - size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials_t.pClientCert. */ - const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ - size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials_t.pPrivateKey. */ -} IotNetworkCredentials_t; - #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index b3818e1fff..391b3344a5 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -180,7 +180,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * to use with the MQTT connection. * @param[in] pConnectInfo MQTT connection setup parameters. * @param[in] timeoutMs If the MQTT server does not accept the connection within - * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle * if this function succeeds. * @@ -197,9 +197,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * * Example * @code{c} - * // An initialized and connected network connection. - * IotNetworkConnection_t pNetworkConnection; - * +* * // Parameters to MQTT connect. * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; @@ -226,9 +224,9 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * // Set the members of the will info (retain and retry not used). * willInfo.qos = IOT_MQTT_QOS_1; * willInfo.pTopicName = "will/topic/name"; - * willInfo.topicNameLength = 15; + * willInfo.topicNameLength = ( uint16_t )strlen( willInfo.pTopicName ); * willInfo.pPayload = "MQTT client unexpectedly disconnected."; - * willInfo.payloadLength = 38; + * willInfo.payloadLength = strlen( willInfo.pPayload ); * * // Set the pointer to the will info. * connectInfo.pWillInfo = &willInfo; @@ -280,8 +278,8 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, * connection; it still must be called even if the network is offline to avoid leaking * resources. * - * Once this function is called, its parameter `mqttConnection` should no longer - * be used. + * @ref mqtt_function_disconnect modifies `mqttConnection`, so it shouldn't + * be used after calling this function. * * @param[in] mqttConnection The MQTT connection to close and clean up. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. @@ -292,10 +290,10 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, /* @[declare_mqtt_disconnect] */ /** - * @brief Subscribes to the given array of topic filters and receive an asynchronous - * notification when the subscribe completes. + * @brief Subscribes to the given array of topic filters and optionally + * receive an asynchronous notification when the subscribe completes. * - * This function transmits an MQTT SUBSCRIBE packet to the server. A SUBSCRIBE + * This function sends an MQTT SUBSCRIBE packet to the server. A SUBSCRIBE * packet notifies the server to send any matching PUBLISH messages to this client. * A single SUBSCRIBE packet may carry more than one topic filter, hence the * parameters to this function include an array of [subscriptions] @@ -325,7 +323,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * subscriptions. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion (NULL to disable). * @param[out] pSubscribeOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the subscription operation completes. @@ -432,7 +430,7 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, /** * @brief Subscribes to the given array of topic filters with a timeout. * - * This function transmits an MQTT SUBSCRIBE packet to the server, then waits for + * This function sends an MQTT SUBSCRIBE packet to the server, then waits for * a server response to the packet. Internally, this function is a call to @ref * mqtt_function_subscribeasync followed by @ref mqtt_function_wait. See @ref * mqtt_function_subscribeasync for more information about the MQTT SUBSCRIBE operation. @@ -448,7 +446,7 @@ IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection, * Currently, flags are ignored by this function; this parameter is for * future-compatibility. * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within - * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. * * @return One of the following: * - #IOT_MQTT_SUCCESS @@ -470,10 +468,10 @@ IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, /* @[declare_mqtt_subscribesync] */ /** - * @brief Unsubscribes from the given array of topic filters and receive an asynchronous - * notification when the unsubscribe completes. + * @brief Unsubscribes from the given array of topic filters and optionally + * receive an asynchronous notification when the unsubscribe completes. * - * This function transmits an MQTT UNSUBSCRIBE packet to the server. An UNSUBSCRIBE + * This function sends an MQTT UNSUBSCRIBE packet to the server. An UNSUBSCRIBE * packet removes registered topic filters from the server. After unsubscribing, * the server will no longer send messages on these topic filters to the client. * @@ -486,7 +484,7 @@ IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, * subscriptions. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion (NULL to disable) * @param[out] pUnsubscribeOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the unsubscribe operation completes. @@ -519,7 +517,7 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, /** * @brief Unsubscribes from a given array of topic filters with a timeout. * - * This function transmits an MQTT UNSUBSCRIBE packet to the server, then waits + * This function sends an MQTT UNSUBSCRIBE packet to the server, then waits * for a server response to the packet. Internally, this function is a call to * @ref mqtt_function_unsubscribeasync followed by @ref mqtt_function_wait. See @ref * mqtt_function_unsubscribeasync for more information about the MQTT UNSUBSCRIBE @@ -530,10 +528,9 @@ IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection, * subscriptions. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * Currently, flags are ignored by this function; this parameter is for - * future-compatibility. + * Flags are currently ignored but reserved for future use. * @param[in] timeoutMs If the MQTT server does not acknowledge the UNSUBSCRIBE within - * this timeout, this function returns #IOT_MQTT_TIMEOUT. + * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. * * @return One of the following: * - #IOT_MQTT_SUCCESS @@ -553,10 +550,10 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, /* @[declare_mqtt_unsubscribesync] */ /** - * @brief Publishes a message to the given topic name and receive an asynchronous - * notification when the publish completes. + * @brief Publishes a message to the given topic name and optionally + * receive an asynchronous notification when the publish completes. * - * This function transmits an MQTT PUBLISH packet to the server. A PUBLISH packet + * This function sends an MQTT PUBLISH packet to the server. A PUBLISH packet * contains a payload and a topic name. Any clients with a subscription on a * topic filter matching the PUBLISH topic name will receive a copy of the * PUBLISH packet from the server. @@ -571,7 +568,7 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, * @param[in] mqttConnection The MQTT connection to use for the publish. * @param[in] pPublishInfo MQTT publish parameters. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion. + * @param[in] pCallbackInfo Asynchronous notification of this function's completion (NULL to disable). * @param[out] pPublishOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the publish operation completes. @@ -609,9 +606,9 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, * // Set the publish information. QoS 0 example (retain not used): * publishInfo.qos = IOT_MQTT_QOS_0; * publishInfo.pTopicName = "some/topic/name"; - * publishInfo.topicNameLength = 15; + * publishInfo.topicNameLength = ( uint16_t )strlen( publishInfo.pTopicName ); * publishInfo.pPayload = "payload"; - * publishInfo.payloadLength = 8; + * publishInfo.payloadLength = strlen( publishInfo.pPayload ); * * // QoS 0 publish should return IOT_MQTT_SUCCESS upon success. * IotMqttError_t qos0Result = IotMqtt_PublishAsync( mqttConnection, @@ -651,7 +648,7 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, /** * @brief Publish a message to the given topic name with a timeout. * - * This function transmits an MQTT PUBLISH packet to the server, then waits for + * This function sends an MQTT PUBLISH packet to the server, then waits for * a server response to the packet. Internally, this function is a call to @ref * mqtt_function_publishasync followed by @ref mqtt_function_wait. See @ref * mqtt_function_publishasync for more information about the MQTT PUBLISH operation. @@ -665,8 +662,8 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, * Currently, flags are ignored by this function; this parameter is for * future-compatibility. * @param[in] timeoutMs If the MQTT server does not acknowledge a QoS 1 PUBLISH - * within this timeout, this function returns #IOT_MQTT_TIMEOUT. This parameter - * is ignored for QoS 0 PUBLISH messages. + * within this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. + * This parameter is ignored for QoS 0 PUBLISH messages. * * @return One of the following: * - #IOT_MQTT_SUCCESS @@ -705,7 +702,8 @@ IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection, * * @param[in] operation Reference to the operation to wait for. The flag * #IOT_MQTT_FLAG_WAITABLE must have been set for this operation. - * @param[in] timeoutMs How long to wait before returning #IOT_MQTT_TIMEOUT. + * @param[in] timeoutMs How many milliseconds to wait before returning + * #IOT_MQTT_TIMEOUT. * * @return The return value of this function depends on the MQTT operation associated * with `reference`. See #IotMqttError_t for possible return values. diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index fddcfc0623..d011617ef3 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -22,7 +22,7 @@ /** * @file iot_mqtt_types.h - * @brief Types of the MQTT library. + * @brief MQTT library types. */ #ifndef IOT_MQTT_TYPES_H_ @@ -53,10 +53,9 @@ * @ingroup mqtt_datatypes_handles * @brief Opaque handle of an MQTT connection. * - * This type identifies an MQTT connection, which is valid after a successful call - * to @ref mqtt_function_connect. A variable of this type is passed as the first - * argument to [MQTT library functions](@ref mqtt_functions) to identify which - * connection that function acts on. + * MQTT connection handle type. MQTT connection handles are created by + * successful calls to @ref mqtt_function_connect and are used to refer to + * the connection when calling MQTT library functions. * * A call to @ref mqtt_function_disconnect makes a connection handle invalid. Once * @ref mqtt_function_disconnect returns, the connection handle should no longer @@ -363,18 +362,9 @@ typedef enum IotMqttDisconnectReason * @note The lengths of the strings in this struct should not include the NULL * terminator. Strings in this struct do not need to be NULL-terminated. * - * @note The AWS IoT MQTT server does not support the DUP bit. When - * [using this library with the AWS IoT MQTT server](@ref IotMqttConnectInfo_t.awsIotMqttMode), - * retransmissions will instead be sent with a new packet identifier in the PUBLISH - * packet. This is a nonstandard workaround. Note that this workaround has some - * flaws, including the following: - * - The previous packet identifier is forgotten, so if a PUBACK arrives for that - * packet identifier, it will be ignored. On an exceptionally busy network, this - * may cause excessive retransmissions when too many PUBACKS arrive after the - * PUBLISH packet identifier is changed. However, the exponential backoff - * retransmission strategy should mitigate this problem. - * - Log messages will be printed using the new packet identifier; the old packet - * identifier is not saved. + * @note The AWS IoT MQTT broker does not support the DUP bit. More + * information about connecting to AWS IoT via MQTT is available + * [here](https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html). * * Example * @@ -416,7 +406,7 @@ typedef struct IotMqttPublishInfo * * @paramfor MQTT callback functions * - * The MQTT library passes this struct to registered callback whenever an + * The MQTT library passes this struct to a registered callback whenever an * operation completes, a message is received on a topic filter, or an MQTT * connection is disconnected. * @@ -480,13 +470,13 @@ typedef struct IotMqttCallbackParam /** * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a user-provided MQTT callback function. + * @brief MQTT callback function and context. * * @paramfor @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, * and @ref mqtt_function_publishasync. Cannot be used with #IOT_MQTT_FLAG_WAITABLE. * - * Provides a function to be invoked when an operation completes or when a - * server-to-client PUBLISH is received. + * Specifies a function to be invoked with optional context when an operation + * completes or when a server-to-client PUBLISH is received. * * @initializer{IotMqttCallbackInfo_t,IOT_MQTT_CALLBACK_INFO_INITIALIZER} * @@ -533,7 +523,7 @@ typedef struct IotMqttCallbackInfo /** * @ingroup mqtt_datatypes_paramstructs - * @brief Information on an MQTT subscription. + * @brief MQTT subscription. * * @paramfor @ref mqtt_function_subscribeasync, @ref mqtt_function_unsubscribeasync, * @ref mqtt_function_subscribesync, @ref mqtt_function_unsubscribesync @@ -570,7 +560,7 @@ typedef struct IotMqttSubscription /** * @ingroup mqtt_datatypes_paramstructs - * @brief Information on a new MQTT connection. + * @brief MQTT connection details. * * @paramfor @ref mqtt_function_connect * @@ -588,11 +578,10 @@ typedef struct IotMqttConnectInfo /** * @brief Specifies if this MQTT connection is to an AWS IoT MQTT server. * - * The AWS IoT MQTT broker [differs somewhat from the MQTT specification.] + * Set this member to `true` when connecting to the AWS IoT MQTT broker or + * `false` otherwise. Additional details about connecting to AWS IoT + * via MQTT are available [here] * (https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html) - * When this member is `true`, the MQTT library will accommodate these - * differences. This setting should be `false` when communicating with a - * fully-compliant MQTT broker. * * @attention This setting MUST be `true` when using the AWS IoT MQTT * server; it MUST be `false` otherwise. @@ -956,10 +945,20 @@ typedef void ( * IotMqttPublishSetDup_t )( uint8_t * pPublishPacket, #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declarations of platform network server info and credentials + * types. + */ +struct IotNetworkServerInfo_t; +struct IotNetworkCredentials_t; +/** @endcond */ + /** * @ingroup mqtt_datatypes_paramstructs - * @brief Infomation on the transport-layer network connection for the new MQTT - * connection. + * @brief MQTT network connection details. * * @paramfor @ref mqtt_function_connect * @@ -999,7 +998,7 @@ typedef struct IotMqttNetworkInfo * interface when creating a new network connection. It is only valid when * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ - void * pNetworkServerInfo; + IotNetworkServerInfo_t * pNetworkServerInfo; /** * @brief Credentials for the MQTT server, passed as `pCredentialInfo` to @@ -1009,7 +1008,7 @@ typedef struct IotMqttNetworkInfo * interface when creating a new network connection. It is only valid when * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ - void * pNetworkCredentialInfo; + IotNetworkCredentials_t * pNetworkCredentialInfo; } setup; /** diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h index 604bf622e1..9c02f7158b 100644 --- a/ports/common/include/iot_network_mbedtls.h +++ b/ports/common/include/iot_network_mbedtls.h @@ -112,8 +112,8 @@ void IotNetworkMbedtls_Cleanup( void ); /** * @brief An implementation of #IotNetworkInterface_t::create for mbed TLS. */ -IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, - void * pCredentialInfo, +IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ); /** diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h index 2cc096fc91..fe08a3434b 100644 --- a/ports/common/include/iot_network_openssl.h +++ b/ports/common/include/iot_network_openssl.h @@ -124,8 +124,8 @@ void IotNetworkOpenssl_Cleanup( void ); * @brief An implementation of #IotNetworkInterface_t::create for POSIX systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, - void * pCredentialInfo, +IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ); /** diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index e3261b00c7..3fda2f4911 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -781,8 +781,8 @@ void IotNetworkMbedtls_Cleanup( void ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, - void * pCredentialInfo, +IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); diff --git a/ports/common/src/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c index 788440f27d..cb8ffa2aa9 100644 --- a/ports/common/src/iot_network_metrics.c +++ b/ports/common/src/iot_network_metrics.c @@ -64,8 +64,8 @@ /** * @brief Wraps the network connection creation function with metrics. */ -static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, - void * pCredentialInfo, +static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ); /** @@ -140,7 +140,7 @@ static IotMutex_t _connectionListMutex; /** * @brief Pointer to the metrics-wrapped network creation function. */ - static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkMbedtls_Create; + static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t *, IotNetworkCredentials_t *, void ** ) = IotNetworkMbedtls_Create; /** * @brief Pointer to the metrics-wrapped network close function. @@ -184,8 +184,8 @@ static bool _connectionMatch( const IotLink_t * pConnectionLink, /*-----------------------------------------------------------*/ -static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, - void * pCredentialInfo, +static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ) { IotMetricsTcpConnection_t * pTcpConnection = NULL; diff --git a/ports/posix/src/iot_network_openssl.c b/ports/posix/src/iot_network_openssl.c index 39ce7144e0..beaae966b1 100644 --- a/ports/posix/src/iot_network_openssl.c +++ b/ports/posix/src/iot_network_openssl.c @@ -661,8 +661,8 @@ void IotNetworkOpenssl_Cleanup( void ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, - void * pCredentialInfo, +IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t * pConnectionInfo, + IotNetworkCredentials_t * pCredentialInfo, void ** pConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); From 5bdb3fd6142f5b2482ee01b255fa264c95ef34d4 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Fri, 18 Oct 2019 10:45:52 -0700 Subject: [PATCH 296/844] Remove macOS and Windows ports. (#604) --- .gitignore | 3 - .travis.yml | 12 - CMakeLists.txt | 21 +- README.md | 8 +- doc/guide/building.txt | 8 +- doc/guide/cmake_build.png | Bin 22992 -> 0 bytes doc/guide/developer/porting.txt | 2 +- ports/README.md | 2 +- .../macos/include/iot_platform_types_macos.h | 77 --- ports/macos/macos.cmake | 24 - ports/macos/src/iot_clock_macos.c | 248 --------- ports/macos/src/iot_threads_macos.c | 475 ------------------ .../win32/include/iot_platform_types_win32.h | 71 --- ports/win32/src/iot_clock_win32.c | 251 --------- ports/win32/src/iot_threads_win32.c | 391 -------------- ports/win32/win32.cmake | 17 - scripts/ci_test_build.sh | 22 +- scripts/ci_test_mqtt.sh | 32 +- scripts/setup/ci_setup_osx.sh | 6 - scripts/setup/ci_setup_windows.sh | 10 - third_party/tinycbor/CMakeLists.txt | 5 +- 21 files changed, 17 insertions(+), 1668 deletions(-) delete mode 100644 doc/guide/cmake_build.png delete mode 100644 ports/macos/include/iot_platform_types_macos.h delete mode 100644 ports/macos/macos.cmake delete mode 100644 ports/macos/src/iot_clock_macos.c delete mode 100644 ports/macos/src/iot_threads_macos.c delete mode 100644 ports/win32/include/iot_platform_types_win32.h delete mode 100644 ports/win32/src/iot_clock_win32.c delete mode 100644 ports/win32/src/iot_threads_win32.c delete mode 100644 ports/win32/win32.cmake delete mode 100755 scripts/setup/ci_setup_osx.sh delete mode 100755 scripts/setup/ci_setup_windows.sh diff --git a/.gitignore b/.gitignore index 9bf604884d..3ca54d3698 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,3 @@ doc/tag/* # Ignore CMake build directory. build/ - -# Ignore macOS metadata files. -.DS_Store diff --git a/.travis.yml b/.travis.yml index ab92dafdc4..988f314efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,13 +18,6 @@ jobs: # Build checks for pull requests. - if: type = pull_request env: RUN_TEST=build - - if: type = pull_request - env: RUN_TEST=build - os: osx - - if: type = pull_request - env: RUN_TEST=build - os: windows - compiler: msvc # Documentation check for pull requests. - if: type = pull_request env: RUN_TEST=doc @@ -37,11 +30,6 @@ jobs: - if: type = push compiler: gcc env: RUN_TEST=coverage - - os: osx - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - - os: windows - env: RUN_TEST=mqtt NETWORK_STACK=mbedtls - compiler: msvc # Install dependencies. install: diff --git a/CMakeLists.txt b/CMakeLists.txt index aa162a86b0..0544ccfa4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,12 +28,7 @@ option( IOT_BUILD_CLONE_SUBMODULES ON ) # Unity test framework does not export the correct symbols for DLLs. -# Do not allow allow shared libraries to be built when building tests on Windows. -if( ${IOT_BUILD_TESTS} AND ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - set( ALLOW_SHARED_LIBRARIES OFF ) -else() - set( ALLOW_SHARED_LIBRARIES ON ) -endif() +set( ALLOW_SHARED_LIBRARIES ON ) include( CMakeDependentOption ) CMAKE_DEPENDENT_OPTION( BUILD_SHARED_LIBS @@ -50,18 +45,8 @@ if( NOT DEFINED IOT_PLATFORM_NAME ) option( IOT_NETWORK_USE_OPENSSL "Set this to ON to use a network abstraction implemented on OpenSSL. When OFF, the mbed TLS network abstraction is used." OFF ) - elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - set( IOT_PLATFORM_NAME "win32" CACHE STRING "Port to use for building the SDK." ) - - # Export all symbols when building Windows DLLs. - if( ${BUILD_SHARED_LIBS} ) - set( CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS - TRUE CACHE BOOL - "Export all symbols for Windows DLLs. This option must by enabled." - FORCE ) - endif() - elseif( ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" ) - set( IOT_PLATFORM_NAME "macos" CACHE STRING "Port to use for building the SDK." ) + else() + message( FATAL_ERROR "${CMAKE_SYSTEM_NAME} is not a supported platform." ) endif() endif() diff --git a/README.md b/README.md index 0d2262b4e3..dbc1743468 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,13 @@ Compared to the AWS IoT Device SDK Embedded C v3.0.1, the following features wer **Main documentation page:** [Building the SDK](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/building.html) -This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. This repo contains ready-to-use ports for Windows, macOS, and Linux. +This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. This repo contains a ready-to-use port for Linux. ### Prerequisites - CMake 3.5.0 or later and a C99 compiler. - A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. For reference, the version used by this repo's Travis CI builds are listed in parentheses. - Linux system with POSIX thread and timer APIs. (CI tests on Ubuntu 16.04).
On Linux systems, the OpenSSL network implementation may be used instead of the default network implementation based on mbed TLS. This requires the installation of OpenSSL development libraries and header files, version 1.0.2g or later. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. - - macOS system with POSIX thread APIs and Grand Central Dispatch. (CI tests on macOS 10.13). - - Windows system with the Windows 10 SDK and MSVC toolchain. (CI tests on Windows Server 1803 with Visual Studio 2017). ### Build Steps 1. Clone the source code and submodules. This SDK uses third-party libraries as submodules in the `third_party` directory. @@ -67,9 +65,7 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T ```sh cmake .. ``` - CMake will generate a project based on the detected operating system. On Linux and macOS, the default project is a Makefile. To build the SDK with this Makefile, run `make`. - - On Windows, CMake will create a Visual Studio solution. Open this solution in Visual Studio to build it. + CMake will generate a project based on the detected operating system. On Linux, the default project is a Makefile. To build the SDK with this Makefile, run `make`. You may also use CMake GUI. Specify the SDK's root directory as the source directory and the build directory created in step 4 as the build directory in CMake GUI. diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 0c7e1c8425..1a0baebd47 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -11,8 +11,6 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. - A supported operating system. The ports provided with this repo are expected to work with all recent versions of the following operating systems, although we cannot guarantee the behavior on all systems. For reference, the version used by this repo's Travis CI builds are listed in parentheses. - Linux system with POSIX thread and timer APIs. (CI tests on Ubuntu 16.04).
On Linux systems, the OpenSSL network implementation may be used instead of the default network implementation based on mbed TLS. This requires the installation of OpenSSL development libraries and header files, version 1.0.2g or later. The OpenSSL development libraries are usually called something like `libssl-dev` or `openssl-devel` when installed through a package manager. - - macOS system with POSIX thread APIs and Grand Central Dispatch. (CI tests on macOS 10.13). - - Windows system with the Windows 10 SDK and MSVC toolchain. (CI tests on Windows Server 1803 with Visual Studio 2017). This SDK uses third-party libraries as Git submodules in the `third_party` directory. If the source code was downloaded via `git clone`, nothing further needs to be done. The CMake build system can automatically clone submodules in this case. However, for any other download, the submodules must be downloaded and placed in their respective `third_party` directory. - [mbed TLS](https://github.com/ARMmbed/mbedtls/tree/mbedtls-2.17) → `third_party/mbedtls/mbedtls` @@ -48,7 +46,7 @@ Before building the demos, all desired configuration settings should be defined. @subsection building_demo_commmandline Command-line build @brief How to build the demo applications on the command-line. -In-source CMake builds are not allowed. A build directory should be created under the SDK root directory. Since CMake is cross-platform, build steps will vary depending on host OS. On Linux and macOS, the demo applications are built as follows: +In-source CMake builds are not allowed. A build directory should be created under the SDK root directory. Since CMake is cross-platform, build steps will vary depending on host OS. On Linux, the demo applications are built as follows: @code{sh} # Create build directory and change to build directory. mkdir build @@ -81,10 +79,6 @@ Delete the build directory to remove all demo application executables and build Alternatively, CMake GUI may be used instead of the command line. Specify the SDK's root directory in Where is the source code and a build directory in Where to build the binaries. Click Configure, then Generate. You may modify @ref building_configuration in the CMake GUI window. -The screenshot below is from CMake GUI on Windows. Similar options appear when using other systems. - -@image html cmake_build.png - @section demo_commandlineoptions Demo command line options @brief Command line options of the demo applications. diff --git a/doc/guide/cmake_build.png b/doc/guide/cmake_build.png deleted file mode 100644 index f80847d8b4139666205ea0c6ea5f1b9f4fcf13bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22992 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 diff --git a/doc/guide/developer/porting.txt b/doc/guide/developer/porting.txt index 54d723d9de..02fc2aac0f 100644 --- a/doc/guide/developer/porting.txt +++ b/doc/guide/developer/porting.txt @@ -64,7 +64,7 @@ As relative paths from the SDK's root directory: Port sources that are not specific to a single port, such as the network implementations. - `template`
Empty port sources containing stubbed-out functions. The files in this directory may be used as a starting point for a new port.
- - `macos, posix, win32, ...`
+ - `posix, ...`
Port sources and headers for a single implementation. The directory is named after the target OS.
- `tests/`
SDK test config file and test runner source. Individual library tests are in each library directory. When building tests, @ref IOT_BUILD_TESTS should be set to `1` globally. diff --git a/ports/README.md b/ports/README.md index ffb22cf864..f085a374e1 100644 --- a/ports/README.md +++ b/ports/README.md @@ -13,7 +13,7 @@ Its subdirectories are organized as follows: Port sources that are not specific to a single port, such as the network implementations. - `template`
Empty port sources containing stubbed-out functions. The files in this directory may be used as a starting point for a new port. -- `posix`, `macos`, `win32`
+- `posix`
Port sources and headers for a single implementation. The directory is named after the target OS. See [Porting guide](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/main/guide_developer_porting.html) for instructions on how to create a new port. diff --git a/ports/macos/include/iot_platform_types_macos.h b/ports/macos/include/iot_platform_types_macos.h deleted file mode 100644 index 5dd7acd358..0000000000 --- a/ports/macos/include/iot_platform_types_macos.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_platform_types_macos.h - * @brief Definitions of platform layer types on macOS systems. - */ - -#ifndef IOT_PLATFORM_TYPES_MACOS_H_ -#define IOT_PLATFORM_TYPES_MACOS_H_ - -/* POSIX includes that are available on macOS. */ -#ifdef POSIX_TYPES_HEADER - #include POSIX_TYPES_HEADER -#else - #include -#endif - -/* Grand Central Dispatch include. */ -#include - -/* Dispatch blocks API include. */ -#include - -/** - * @brief The native mutex type on macOS systems. - * - * macOS provides POSIX mutexes. - */ -typedef pthread_mutex_t _IotSystemMutex_t; - -/** - * @brief The native semaphore type on macOS systems. - * - * macOS provides a partial implementation of POSIX semaphores, but it does not - * provide all of the functions needed by this SDK. Grand Central Dispatch - * semaphores are used instead. - */ -typedef struct _IotSystemSemaphore -{ - uint32_t count; /**< @brief The current count of the semaphore. */ - dispatch_semaphore_t semaphore; /**< @brief Grand Central Dispatch semaphore. */ -} _IotSystemSemaphore_t; - -/** - * @brief The native timer type on macOS systems. - * - * POSIX timers are not available on macOS, so a timer implementation based on - * Grand Central Dispatch is used instead. - */ -typedef struct _IotSystemTimer -{ - dispatch_block_t dispatchBlock; /**< @brief A dispatch block that executes for the timer. */ - uint32_t periodMs; /**< @brief Expiration period of this timer, if any. */ - void * pArgument; /**< @brief First argument to threadRoutine. */ - void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ -} _IotSystemTimer_t; - -#endif /* ifndef IOT_PLATFORM_TYPES_MACOS_H_ */ diff --git a/ports/macos/macos.cmake b/ports/macos/macos.cmake deleted file mode 100644 index a6e5435b3d..0000000000 --- a/ports/macos/macos.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# Check for POSIX threads. -find_package( Threads REQUIRED ) - -if( NOT ${CMAKE_USE_PTHREADS_INIT} ) - message( FATAL_ERROR "POSIX threads required." ) -endif() - -# Add the network header for this platform. -list( APPEND PLATFORM_COMMON_HEADERS - ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) - -# Platform library source files. -set( PLATFORM_SOURCES - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c - ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ) - -# Set the types header for this port. -set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) - -# Set the dependencies required by this port. -# On macOS, mbed TLS and POSIX threads are used. -set( PLATFORM_DEPENDENCIES mbedtls Threads::Threads ) diff --git a/ports/macos/src/iot_clock_macos.c b/ports/macos/src/iot_clock_macos.c deleted file mode 100644 index 6c05cba212..0000000000 --- a/ports/macos/src/iot_clock_macos.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_clock_macos.c - * @brief Implementation of the functions in iot_clock.h for macOS. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Platform clock include. */ -#include "platform/iot_clock.h" - -/* Standard includes. */ -#include - -/* POSIX includes. Allow the default POSIX headers to be overridden. */ -#ifdef POSIX_ERRNO_HEADER - #include POSIX_ERRNO_HEADER -#else - #include -#endif -#ifdef POSIX_TIME_HEADER - #include POSIX_TIME_HEADER -#else - #include -#endif - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "CLOCK" ) -#include "iot_logging_setup.h" - -/*-----------------------------------------------------------*/ - -/** - * @brief The format of timestrings printed in logs. - * - * For more information on timestring formats, see [this link.] - * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html) - */ -#define TIMESTRING_FORMAT ( "%F %R:%S" ) - -/* - * Time conversion constants. - */ -#define NANOSECONDS_PER_MILLISECOND ( 1000000 ) /**< @brief Nanoseconds per millisecond. */ -#define MILLISECONDS_PER_SECOND ( 1000 ) /**< @brief Milliseconds per second. */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an #IotThreadRoutine_t with a GCD-compliant one. - * - * @param[in] pArgument The value passed to `dispatch_after_f`. - */ -static void _timerExpirationWrapper( void * pArgument ); - -/*-----------------------------------------------------------*/ - -static void _timerExpirationWrapper( void * pArgument ) -{ - IotTimer_t * pTimer = pArgument; - - /* Invoke the wrapped expiration routine. */ - pTimer->threadRoutine( pTimer->pArgument ); -} - -/*-----------------------------------------------------------*/ - -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ) -{ - bool status = true; - const time_t unixTime = time( NULL ); - struct tm localTime = { 0 }; - size_t timestringLength = 0; - - /* localtime_r is the thread-safe variant of localtime. Its return value - * should be the pointer to the localTime struct. */ - if( localtime_r( &unixTime, &localTime ) != &localTime ) - { - status = false; - } - - if( status == true ) - { - /* Convert the localTime struct to a string. */ - timestringLength = strftime( pBuffer, bufferSize, TIMESTRING_FORMAT, &localTime ); - - /* Check for error from strftime. */ - if( timestringLength == 0 ) - { - status = false; - } - else - { - /* Set the output parameter. */ - *pTimestringLength = timestringLength; - } - } - - return status; -} - -/*-----------------------------------------------------------*/ - -uint64_t IotClock_GetTimeMs( void ) -{ - struct timespec currentTime = { 0 }; - - if( clock_gettime( CLOCK_MONOTONIC, ¤tTime ) != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to read time from CLOCK_MONOTONIC. errno=%d", - errno ); - - abort(); - } - - return ( ( uint64_t ) currentTime.tv_sec ) * MILLISECONDS_PER_SECOND + - ( ( uint64_t ) currentTime.tv_nsec ) / NANOSECONDS_PER_MILLISECOND; -} - -/*-----------------------------------------------------------*/ - -void IotClock_SleepMs( uint32_t sleepTimeMs ) -{ - /* Convert parameter to timespec. */ - struct timespec sleepTime = - { - .tv_sec = sleepTimeMs / MILLISECONDS_PER_SECOND, - .tv_nsec = ( sleepTimeMs % MILLISECONDS_PER_SECOND ) * NANOSECONDS_PER_MILLISECOND - }; - - if( nanosleep( &sleepTime, NULL ) == -1 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Sleep failed. errno=%d.", errno ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerCreate( IotTimer_t * pNewTimer, - IotThreadRoutine_t expirationRoutine, - void * pArgument ) -{ - pNewTimer->dispatchBlock = NULL; - - /* Save the expiration routine and argument for use later. */ - pNewTimer->pArgument = pArgument; - pNewTimer->threadRoutine = expirationRoutine; - - return true; -} - -/*-----------------------------------------------------------*/ - -void IotClock_TimerDestroy( IotTimer_t * pTimer ) -{ - /* Cancel any timer work that may already be scheduled. */ - if( pTimer->dispatchBlock != NULL ) - { - dispatch_block_cancel( pTimer->dispatchBlock ); - - /* Block_release only needs to be called if ARC is not being used. */ - #if( OS_OBJECT_HAVE_OBJC_SUPPORT == 0 ) - Block_release( pTimer->dispatchBlock ); - #endif - } -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerArm( IotTimer_t * pTimer, - uint32_t relativeTimeoutMs, - uint32_t periodMs ) -{ - bool status = true; - - /* Get the handle to a global concurrent queue. Work will be submitted to - * this queue. */ - dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue( QOS_CLASS_DEFAULT, 0 ); - - /* Calculate when the timer should first expire. */ - int64_t delta = ( int64_t ) relativeTimeoutMs * NANOSECONDS_PER_MILLISECOND; - dispatch_time_t timeout = dispatch_time( DISPATCH_TIME_NOW, delta ); - - /* Save the period. */ - pTimer->periodMs = periodMs; - - /* Cancel any timer work that may already be scheduled (in case this call - * is a reschedule of an existing timer). */ - IotClock_TimerDestroy( pTimer ); - - /* Create a dispatch block to execute work on behalf of the timer. */ - pTimer->dispatchBlock = dispatch_block_create( 0, ^ { - _timerExpirationWrapper( pTimer ); - } ); - - if( pTimer->dispatchBlock == NULL ) - { - IotLogError( "Failed to create timer dispatch block." ); - - status = false; - } - else - { - /* Schedule the timer expiration to run at the timeout. */ - dispatch_after( timeout, globalDispatchQueue, pTimer->dispatchBlock ); - } - - return status; -} - -/*-----------------------------------------------------------*/ diff --git a/ports/macos/src/iot_threads_macos.c b/ports/macos/src/iot_threads_macos.c deleted file mode 100644 index 7096e0c166..0000000000 --- a/ports/macos/src/iot_threads_macos.c +++ /dev/null @@ -1,475 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_threads_macos.c - * @brief Implementation of the functions in iot_threads.h for macOS. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Atomic include. */ -#include "iot_atomic.h" - -/* POSIX includes. Allow the default POSIX headers to be overridden. */ -#ifdef POSIX_ERRNO_HEADER - #include POSIX_ERRNO_HEADER -#else - #include -#endif -#ifdef POSIX_PTHREAD_HEADER - #include POSIX_PTHREAD_HEADER -#else - #include -#endif - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "THREAD" ) -#include "iot_logging_setup.h" - -/* - * Provide default values for undefined memory allocation functions. - */ -#ifndef IotThreads_Malloc - #include - -/** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotThreads_Malloc malloc -#endif -#ifndef IotThreads_Free - #include - -/** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotThreads_Free free -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Holds information about an active detached thread. - */ -typedef struct _threadInfo -{ - void * pArgument; /**< @brief First argument to `threadRoutine`. */ - IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ -} _threadInfo_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an #IotThreadRoutine_t with a POSIX-compliant one. - * - * @param[in] pArgument The value passed as `arg` to `pthread_create`. - * - * @return Always returns `NULL`. - */ -static void * _threadRoutineWrapper( void * pArgument ); - -/*-----------------------------------------------------------*/ - -static void * _threadRoutineWrapper( void * pArgument ) -{ - _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; - - /* Read thread routine and argument, then free thread info. */ - IotThreadRoutine_t threadRoutine = pThreadInfo->threadRoutine; - void * pThreadRoutineArgument = pThreadInfo->pArgument; - - IotThreads_Free( pThreadInfo ); - - /* Run the thread routine. */ - threadRoutine( pThreadRoutineArgument ); - - return NULL; -} - -/*-----------------------------------------------------------*/ - -bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument, - int32_t priority, - size_t stackSize ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - int posixErrno = 0; - bool threadAttibutesCreated = false; - _threadInfo_t * pThreadInfo = NULL; - pthread_t newThread; - pthread_attr_t threadAttributes; - - /* Ignore priority and stack size. */ - ( void ) priority; - ( void ) stackSize; - - /* Allocate memory for the new thread info. */ - pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); - - if( pThreadInfo == NULL ) - { - IotLogError( "Failed to allocate memory for new thread." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Set up thread attributes object. */ - posixErrno = pthread_attr_init( &threadAttributes ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to initialize thread attributes. errno=%d.", - posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - threadAttibutesCreated = true; - - /* Set the new thread to detached. */ - posixErrno = pthread_attr_setdetachstate( &threadAttributes, - PTHREAD_CREATE_DETACHED ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to set detached thread attribute. errno=%d.", - posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Set the thread routine and argument. */ - pThreadInfo->threadRoutine = threadRoutine; - pThreadInfo->pArgument = pArgument; - - /* Create the underlying POSIX thread. */ - posixErrno = pthread_create( &newThread, - &threadAttributes, - _threadRoutineWrapper, - pThreadInfo ); - - if( posixErrno != 0 ) - { - IotLogError( "Failed to create new thread. errno=%d.", posixErrno ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Destroy thread attributes object. */ - if( threadAttibutesCreated == true ) - { - posixErrno = pthread_attr_destroy( &threadAttributes ); - - if( posixErrno != 0 ) - { - IotLogWarn( "Failed to destroy thread attributes. errno=%d.", - posixErrno ); - } - } - - /* Clean up on error. */ - if( status == false ) - { - if( pThreadInfo != NULL ) - { - IotThreads_Free( pThreadInfo ); - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_Create( IotMutex_t * pNewMutex, - bool recursive ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - int mutexError = 0; - pthread_mutexattr_t mutexAttributes, * pMutexAttributes = NULL; - - if( recursive == true ) - { - /* Create new mutex attributes object. */ - mutexError = pthread_mutexattr_init( &mutexAttributes ); - - if( mutexError != 0 ) - { - IotLogError( "Failed to initialize mutex attributes. errno=%d.", - mutexError ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - pMutexAttributes = &mutexAttributes; - - /* Set recursive mutex type. */ - mutexError = pthread_mutexattr_settype( &mutexAttributes, - PTHREAD_MUTEX_RECURSIVE ); - - if( mutexError != 0 ) - { - IotLogError( "Failed to set recursive mutex type. errno=%d.", - mutexError ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - } - - mutexError = pthread_mutex_init( pNewMutex, pMutexAttributes ); - - if( mutexError != 0 ) - { - IotLogError( "Failed to create new mutex %p. errno=%d.", - pNewMutex, - mutexError ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - IOT_FUNCTION_CLEANUP_BEGIN(); - - /* Destroy any created mutex attributes. */ - if( pMutexAttributes != NULL ) - { - ( void ) pthread_mutexattr_destroy( &mutexAttributes ); - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Destroy( IotMutex_t * pMutex ) -{ - int mutexError = pthread_mutex_destroy( pMutex ); - - if( mutexError != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to destroy mutex %p. errno=%d.", - pMutex, - mutexError ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Lock( IotMutex_t * pMutex ) -{ - int mutexError = pthread_mutex_lock( pMutex ); - - if( mutexError != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to lock mutex %p. errno=%d.", - pMutex, - mutexError ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_TryLock( IotMutex_t * pMutex ) -{ - bool status = true; - int mutexError = pthread_mutex_trylock( pMutex ); - - if( mutexError != 0 ) - { - IotLogDebug( "Mutex mutex %p is not available. errno=%d.", - pMutex, - mutexError ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Unlock( IotMutex_t * pMutex ) -{ - int mutexError = pthread_mutex_unlock( pMutex ); - - if( mutexError != 0 ) - { - /* This block should not be reached; log an error and abort if it is. */ - IotLogError( "Failed to unlock mutex %p. errno=%d.", - pMutex, - mutexError ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ) -{ - /* Unused parameter. */ - ( void ) maxValue; - - bool status = true; - - /* Create a GCD semaphore. */ - pNewSemaphore->semaphore = dispatch_semaphore_create( ( long ) initialValue ); - - if( pNewSemaphore->semaphore == NULL ) - { - IotLogError( "Failed to create new dispatch semaphore." ); - status = false; - } - else - { - /* Set initial semaphore count. */ - pNewSemaphore->count = initialValue; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) -{ - /* dispatch_release only needs to be called if ARC is not being used. */ - #if( OS_OBJECT_HAVE_OBJC_SUPPORT == 0 ) - dispatch_release( pSemaphore->semaphore ); - #endif -} - -/*-----------------------------------------------------------*/ - -uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) -{ - return Atomic_Add_u32( &( pSemaphore->count ), 0 ); -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) -{ - long dispatchError = dispatch_semaphore_wait( pSemaphore->semaphore, - DISPATCH_TIME_FOREVER ); - - /* Waiting forever for a valid semaphore should not fail. */ - if( dispatchError != 0 ) - { - IotLogError( "(Semaphore %p) Failed to wait on dispatch semaphore, returned %ld.", - pSemaphore, - dispatchError ); - - abort(); - } - - if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) - { - IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) -{ - return IotSemaphore_TimedWait( pSemaphore, 0 ); -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - bool status = true; - - /* Convert timeoutMs to nanoseconds. */ - int64_t delta = ( int64_t ) timeoutMs * 1000000LL; - - /* Create a dispatch time representation representing the timeout. */ - dispatch_time_t timeout = dispatch_time( DISPATCH_TIME_NOW, - delta ); - - /* Wait on the dispatch semaphore with a timeout. */ - long dispatchError = dispatch_semaphore_wait( pSemaphore->semaphore, - timeout ); - - if( dispatchError == 0 ) - { - if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) - { - IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); - - abort(); - } - } - else - { - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - Atomic_Increment_u32( &( pSemaphore->count ) ); - - /* Signal dispatch semaphore. The return value (whether a thread was woken) - * is ignored. */ - ( void ) dispatch_semaphore_signal( pSemaphore->semaphore ); -} - -/*-----------------------------------------------------------*/ diff --git a/ports/win32/include/iot_platform_types_win32.h b/ports/win32/include/iot_platform_types_win32.h deleted file mode 100644 index 6d499251c9..0000000000 --- a/ports/win32/include/iot_platform_types_win32.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_platform_types_win32.h - * @brief Definitions of platform layer types on Win32 systems. - */ - -#ifndef IOT_PLATFORM_TYPES_WIN32_H_ -#define IOT_PLATFORM_TYPES_WIN32_H_ - -/* Standard includes. */ -#include - -/* Win32 includes. WinSock2 is needed to prevent the usage of the WinSock - * header in Windows.h */ -#include -#include -#include - -/** - * @brief The "mutex" type on Win32 systems. - * - * The Win32 critical section object is used in place of mutex because it - * consumes less resources and does not need to be shared between processes. - */ -typedef CRITICAL_SECTION _IotSystemMutex_t; - -/** - * @brief The semaphore type on Win32 systems. - * - * Because the Win32 API does not provide a way to obtain a semaphore count, - * the count must be tracked with the semaphore. As noted by the API documentation, - * semaphore count should not be relied on as it may change before it can be tested. - * Semaphore count is currently only used in tests. - */ -typedef struct _IotSystemSemaphore -{ - uint32_t count; /**< @brief The current count of the semaphore. */ - HANDLE semaphore; /**< @brief Handle to the Win32 semaphore. */ -} _IotSystemSemaphore_t; - -/** - * @brief Represents an #IotTimer_t on Win32 systems. - */ -typedef struct _IotSystemTimer -{ - HANDLE timer; /**< @brief Handle of the Win32 timer. */ - void * pArgument; /**< @brief First argument to threadRoutine. */ - void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ -} _IotSystemTimer_t; - -#endif /* ifndef IOT_PLATFORM_TYPES_WIN32_H_ */ diff --git a/ports/win32/src/iot_clock_win32.c b/ports/win32/src/iot_clock_win32.c deleted file mode 100644 index b787b209f1..0000000000 --- a/ports/win32/src/iot_clock_win32.c +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_clock_win32.c - * @brief Implementation of the functions in iot_clock.h for Win32 systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include -#include - -/* Platform clock include. */ -#include "platform/iot_clock.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "CLOCK" ) -#include "iot_logging_setup.h" - -/* Logging macro for Win32 errors. */ -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - #include - - #define _logWin32TimerError( pTimer, pMessage ) \ - { \ - char * pErrorMessage = NULL; \ - \ - FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | \ - FORMAT_MESSAGE_FROM_SYSTEM | \ - FORMAT_MESSAGE_IGNORE_INSERTS, \ - NULL, \ - GetLastError(), \ - 0, \ - ( LPSTR ) ( &pErrorMessage ), \ - 0, \ - NULL ); \ - pErrorMessage[ strlen( pErrorMessage ) - 3 ] = '\0'; \ - \ - IotLogError( "(Timer %p) %s %s.", pTimer, pMessage, pErrorMessage ); \ - LocalFree( pErrorMessage ); \ - } -#else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - #define _logWin32TimerError( pTimer, pMessage ) -#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an #IotThreadRoutine_t with a Win32-compliant one. - * - * @param[in] pArgument The value passed as `Parameter` to `CreateTimerQueueTimer`. - * @param[in] timerOrWaitFired Ignored. - */ -static VOID CALLBACK _timerExpirationWrapper( _In_ PVOID pArgument, - _In_ BOOLEAN timerOrWaitFired ); - -/*-----------------------------------------------------------*/ - -static VOID CALLBACK _timerExpirationWrapper( _In_ PVOID pArgument, - _In_ BOOLEAN timerOrWaitFired ) -{ - IotTimer_t * pTimer = ( IotTimer_t * ) pArgument; - - /* Silence warnings about unused parameters. */ - UNREFERENCED_PARAMETER( timerOrWaitFired ); - - /* Call the wrapped thread routine. */ - pTimer->threadRoutine( pTimer->pArgument ); -} - -/*-----------------------------------------------------------*/ - -bool IotClock_GetTimestring( char * pBuffer, - size_t bufferSize, - size_t * pTimestringLength ) -{ - bool status = true; - int timestringLength = 0; - SYSTEMTIME systemTime = { 0 }; - - /* Get the Win32 system time and format it in the given buffer. */ - GetSystemTime( &systemTime ); - - timestringLength = snprintf( pBuffer, - bufferSize, - "%u-%02u-%02u %02u:%02u:%02u.%03u", - systemTime.wYear, - systemTime.wMonth, - systemTime.wDay, - systemTime.wHour, - systemTime.wMinute, - systemTime.wSecond, - systemTime.wMilliseconds ); - - /* Check for errors from snprintf. */ - if( timestringLength < 0 ) - { - /* Encoding error. */ - status = false; - } - else if( ( size_t ) timestringLength >= bufferSize ) - { - /* Buffer too small. */ - status = false; - } - else - { - /* Success; set the output parameter. */ - *pTimestringLength = ( size_t ) timestringLength; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -uint64_t IotClock_GetTimeMs( void ) -{ - return ( uint64_t ) GetTickCount64(); -} - -/*-----------------------------------------------------------*/ - -void IotClock_SleepMs( uint32_t sleepTimeMs ) -{ - Sleep( ( DWORD ) sleepTimeMs ); -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerCreate( IotTimer_t * pNewTimer, - IotThreadRoutine_t expirationRoutine, - void * pArgument ) -{ - /* Set the members of the new timer. */ - pNewTimer->pArgument = pArgument; - pNewTimer->threadRoutine = expirationRoutine; - pNewTimer->timer = INVALID_HANDLE_VALUE; - - return true; -} - -/*-----------------------------------------------------------*/ - -void IotClock_TimerDestroy( IotTimer_t * pTimer ) -{ - BOOL timerStatus = 0; - - if( pTimer->timer != INVALID_HANDLE_VALUE ) - { - /* Per the Win32 API docs, DeleteTimerQueueTimer should be retried until - * successful. */ - while( true ) - { - timerStatus = DeleteTimerQueueTimer( NULL, - pTimer->timer, - NULL ); - - /* Check if the timer was successfully deleted. */ - if( timerStatus == 0 ) - { - if( GetLastError() == ERROR_IO_PENDING ) - { - /* Nothing further needs to be done for ERROR_IO_PENDING. */ - break; - } - else - { - /* Sleep a short time before trying again. */ - Sleep( 100 ); - } - } - else - { - /* Timer was successfully deleted. */ - break; - } - } - - IotLogDebug( "(Timer %p) Timer destroyed.", pTimer ); - pTimer->timer = INVALID_HANDLE_VALUE; - } -} - -/*-----------------------------------------------------------*/ - -bool IotClock_TimerArm( IotTimer_t * pTimer, - uint32_t relativeTimeoutMs, - uint32_t periodMs ) -{ - BOOL timerStatus = 0; - - /* Any previous timer must be destroyed before scheduling a new one in the - * Win32 API. */ - IotClock_TimerDestroy( pTimer ); - - timerStatus = CreateTimerQueueTimer( &( pTimer->timer ), - NULL, - _timerExpirationWrapper, - pTimer, - ( DWORD ) relativeTimeoutMs, - ( DWORD ) periodMs, - WT_EXECUTEDEFAULT ); - - if( timerStatus == 0 ) - { - _logWin32TimerError( pTimer, "Failed to create timer." ); - } - else - { - IotLogDebug( "(Timer %p) Timer armed with timeout %lu and period %lu.", - pTimer, - ( unsigned long ) relativeTimeoutMs, - ( unsigned long ) periodMs ); - } - - return( timerStatus != 0 ); -} - -/*-----------------------------------------------------------*/ diff --git a/ports/win32/src/iot_threads_win32.c b/ports/win32/src/iot_threads_win32.c deleted file mode 100644 index d297dd9d59..0000000000 --- a/ports/win32/src/iot_threads_win32.c +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/** - * @file iot_threads_win32.c - * @brief Implementation of the functions in iot_threads.h for Win32 systems. - */ - -/* The config header is always included first. */ -#include "iot_config.h" - -/* Standard includes. */ -#include - -/* Platform threads include. */ -#include "platform/iot_threads.h" - -/* Atomic include. */ -#include "iot_atomic.h" - -/* Error handling include. */ -#include "iot_error.h" - -/* Configure logs for the functions in this file. */ -#ifdef IOT_LOG_LEVEL_PLATFORM - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_PLATFORM -#else - #ifdef IOT_LOG_LEVEL_GLOBAL - #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL - #else - #define LIBRARY_LOG_LEVEL IOT_LOG_NONE - #endif -#endif - -#define LIBRARY_LOG_NAME ( "THREAD" ) -#include "iot_logging_setup.h" - -/* Logging macro for Win32 errors. */ -#if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - #include - - #define _logWin32Error( pName, pObject, pMessage ) \ - { \ - char * pErrorMessage = NULL; \ - \ - FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | \ - FORMAT_MESSAGE_FROM_SYSTEM | \ - FORMAT_MESSAGE_IGNORE_INSERTS, \ - NULL, \ - GetLastError(), \ - 0, \ - ( LPSTR ) ( &pErrorMessage ), \ - 0, \ - NULL ); \ - pErrorMessage[ strlen( pErrorMessage ) - 3 ] = '\0'; \ - \ - IotLogError( "(%s %p) %s %s.", pName, pObject, pMessage, pErrorMessage ); \ - LocalFree( pErrorMessage ); \ - } -#else /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - #define _logWin32Error( pName, pObject, pMessage ) -#endif /* if LIBRARY_LOG_LEVEL > IOT_LOG_NONE */ - -/* - * Provide default values for undefined memory allocation functions. - */ -#ifndef IotThreads_Malloc - #include - -/** - * @brief Memory allocation. This function should have the same signature - * as [malloc](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html). - */ - #define IotThreads_Malloc malloc -#endif -#ifndef IotThreads_Free - #include - -/** - * @brief Free memory. This function should have the same signature as - * [free](http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html). - */ - #define IotThreads_Free free -#endif - -/*-----------------------------------------------------------*/ - -/** - * @brief Holds information about an active detached thread. - */ -typedef struct _threadInfo -{ - void * pArgument; /**< @brief First argument to `threadRoutine`. */ - IotThreadRoutine_t threadRoutine; /**< @brief Thread function to run. */ -} _threadInfo_t; - -/*-----------------------------------------------------------*/ - -/** - * @brief Wraps an #IotThreadRoutine_t with a Win32-compliant one. - * - * @param[in] pArgument The value passed as `lpParameter` to `CreateThread`. - * - * @return Does not return, calls `ExitThread` with an exit code of `0`. - */ -static DWORD WINAPI _threadRoutineWrapper( _In_ LPVOID pArgument ); - -/*-----------------------------------------------------------*/ - -static DWORD WINAPI _threadRoutineWrapper( _In_ LPVOID pArgument ) -{ - /* Cast argument to correct type. */ - _threadInfo_t * pThreadInfo = ( _threadInfo_t * ) pArgument; - - /* Read thread routine and argument, then free thread info. */ - IotThreadRoutine_t threadRoutine = pThreadInfo->threadRoutine; - void * pThreadRoutineArgument = pThreadInfo->pArgument; - - IotThreads_Free( pThreadInfo ); - - /* Run the thread routine. */ - threadRoutine( pThreadRoutineArgument ); - - ExitThread( 0 ); -} - -/*-----------------------------------------------------------*/ - -bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, - void * pArgument, - int32_t priority, - size_t stackSize ) -{ - IOT_FUNCTION_ENTRY( bool, true ); - HANDLE newThread = NULL; - _threadInfo_t * pThreadInfo = NULL; - - /* Priority is not used on Windows. */ - ( void ) priority; - - /* Determine the stack size of the thread to create. */ - SIZE_T threadStackSize = 0; - - if( stackSize != IOT_THREAD_DEFAULT_STACK_SIZE ) - { - threadStackSize = ( SIZE_T ) stackSize; - } - - /* Allocate memory for a new thread info. */ - pThreadInfo = IotThreads_Malloc( sizeof( _threadInfo_t ) ); - - if( pThreadInfo == NULL ) - { - IotLogError( "Failed to allocate memory for new thread." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Set the members of the thread info and create a new thread. */ - pThreadInfo->pArgument = pArgument; - pThreadInfo->threadRoutine = threadRoutine; - - newThread = CreateThread( NULL, - threadStackSize, - _threadRoutineWrapper, - pThreadInfo, - 0, - NULL ); - - if( newThread == NULL ) - { - IotLogError( "Failed to create new thread." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - IotLogDebug( "(Thread %p) New thread created.", newThread ); - - /* Close the thread handle; it's not needed. This won't terminate the thread. */ - if( CloseHandle( newThread ) == 0 ) - { - _logWin32Error( "Thread", newThread, "Failed to close thread handle." ); - abort(); - } - } - - /* Clean up on error. */ - IOT_FUNCTION_CLEANUP_BEGIN(); - - if( status == false ) - { - if( pThreadInfo != NULL ) - { - IotThreads_Free( pThreadInfo ); - } - } - - IOT_FUNCTION_CLEANUP_END(); -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_Create( IotMutex_t * pNewMutex, - bool recursive ) -{ - /* The Win32 critical section is always recursive, so this parameter is - * ignored. */ - UNREFERENCED_PARAMETER( recursive ); - - InitializeCriticalSection( pNewMutex ); - - return true; -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Destroy( IotMutex_t * pMutex ) -{ - DeleteCriticalSection( pMutex ); -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Lock( IotMutex_t * pMutex ) -{ - EnterCriticalSection( pMutex ); -} - -/*-----------------------------------------------------------*/ - -bool IotMutex_TryLock( IotMutex_t * pMutex ) -{ - bool status = true; - - if( TryEnterCriticalSection( pMutex ) == 0 ) - { - IotLogDebug( "(Mutex %p) Mutex not available.", pMutex ); - - status = false; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotMutex_Unlock( IotMutex_t * pMutex ) -{ - LeaveCriticalSection( pMutex ); -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_Create( IotSemaphore_t * pNewSemaphore, - uint32_t initialValue, - uint32_t maxValue ) -{ - bool status = true; - - /* Create a Win32 semaphore exclusive to this process. */ - pNewSemaphore->semaphore = CreateSemaphoreA( NULL, - ( LONG ) initialValue, - ( LONG ) maxValue, - NULL ); - - if( pNewSemaphore->semaphore == NULL ) - { - _logWin32Error( "Semaphore", pNewSemaphore, "Failed to create new semaphore." ); - - status = false; - } - else - { - /* Set initial semaphore count. */ - pNewSemaphore->count = initialValue; - } - - return status; -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Destroy( IotSemaphore_t * pSemaphore ) -{ - if( CloseHandle( pSemaphore->semaphore ) == 0 ) - { - /* This should never happen, log an error and abort if it does. */ - _logWin32Error( "Semaphore", pSemaphore, "Failed to close semaphore handle." ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -uint32_t IotSemaphore_GetCount( IotSemaphore_t * pSemaphore ) -{ - return Atomic_Add_u32( &( pSemaphore->count ), 0 ); -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Wait( IotSemaphore_t * pSemaphore ) -{ - if( WaitForSingleObject( pSemaphore->semaphore, INFINITE ) != WAIT_OBJECT_0 ) - { - /* This should never happen, log an error and abort if it does. */ - _logWin32Error( "Semaphore", pSemaphore, "Failed to wait on semaphore." ); - - abort(); - } - - if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) - { - IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TryWait( IotSemaphore_t * pSemaphore ) -{ - return IotSemaphore_TimedWait( pSemaphore, 0 ); -} - -/*-----------------------------------------------------------*/ - -bool IotSemaphore_TimedWait( IotSemaphore_t * pSemaphore, - uint32_t timeoutMs ) -{ - DWORD status = 0; - - status = WaitForSingleObject( pSemaphore->semaphore, ( DWORD ) timeoutMs ); - - if( status == WAIT_OBJECT_0 ) - { - if( Atomic_Decrement_u32( &( pSemaphore->count ) ) == 0 ) - { - IotLogError( "(Semaphore %p) Semaphore count decremented beyond 0.", pSemaphore ); - - abort(); - } - } - else if( status != WAIT_TIMEOUT ) - { - /* This should never happen, log an error and abort if it does. */ - _logWin32Error( "Semaphore", pSemaphore, "Failed to wait on semaphore." ); - - abort(); - } - - return( status == WAIT_OBJECT_0 ); -} - -/*-----------------------------------------------------------*/ - -void IotSemaphore_Post( IotSemaphore_t * pSemaphore ) -{ - Atomic_Increment_u32( &( pSemaphore->count ) ); - - if( ReleaseSemaphore( pSemaphore->semaphore, 1, NULL ) == 0 ) - { - /* This should never happen, log an error and abort if it does. */ - _logWin32Error( "Semaphore", pSemaphore, "Failed to post to semaphore." ); - - abort(); - } -} - -/*-----------------------------------------------------------*/ diff --git a/ports/win32/win32.cmake b/ports/win32/win32.cmake deleted file mode 100644 index 7da483c71c..0000000000 --- a/ports/win32/win32.cmake +++ /dev/null @@ -1,17 +0,0 @@ -# Add the network header for this platform. -list( APPEND PLATFORM_COMMON_HEADERS - ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) - -# Platform library source files. -set( PLATFORM_SOURCES - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_clock_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_threads_${IOT_PLATFORM_NAME}.c - ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c - ${PORTS_DIRECTORY}/common/src/iot_network_metrics.c ) - -# Set the types header for this port. -set( PORT_TYPES_HEADER ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/include/iot_platform_types_${IOT_PLATFORM_NAME}.h ) - -# Set the dependencies required by this port. -# On Windows, mbed TLS and Winsock are used. -set( PLATFORM_DEPENDENCIES mbedtls ws2_32 ) diff --git a/scripts/ci_test_build.sh b/scripts/ci_test_build.sh index da8747f5b9..e576c455a0 100755 --- a/scripts/ci_test_build.sh +++ b/scripts/ci_test_build.sh @@ -8,31 +8,13 @@ set -e # Treat warnings as errors. if [ "$TRAVIS_COMPILER" = "clang" ]; then COMPILER_OPTIONS+=" -Werror" -elif [ "$TRAVIS_COMPILER" = "msvc" ]; then - COMPILER_OPTIONS+=" /W4 /wd4200 /WX" fi # Build demos. cmake .. -DCMAKE_C_FLAGS="$COMPILER_OPTIONS" - -if [ "$TRAVIS_OS_NAME" = "windows" ]; then - MSBuild.exe ALL_BUILD.vcxproj -m -clp:summary -verbosity:minimal -else - make -j2 -fi - -# Unity places function declarations within other functions. -# Disable the MSVC warning about this. -if [ "$TRAVIS_COMPILER" = "msvc" ]; then - COMPILER_OPTIONS+=" /wd4210" -fi +make -j2 # Build tests. Enable all logging. rm -rf * cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_C_FLAGS="$COMPILER_OPTIONS -DIOT_LOG_LEVEL_GLOBAL=IOT_LOG_DEBUG" - -if [ "$TRAVIS_OS_NAME" = "windows" ]; then - MSBuild.exe ALL_BUILD.vcxproj -m -clp:summary -verbosity:minimal -else - make -j2 -fi +make -j2 diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index 530076398b..dd7af84150 100755 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -12,18 +12,12 @@ DEMO_OPTIONS="-i $IOT_IDENTIFIER" # For pull request builds on Linux, run against a local Mosquitto broker. # -# On macOS and Windows, a local Mosquitto broker is not available. Only tests -# that do not use the network should run on these platforms. -# # For commit builds all platforms will test against AWS IoT. if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then if [ "$TRAVIS_OS_NAME" = "linux" ]; then # Set the flags and options for a local Mosquitto broker on Linux. CMAKE_FLAGS+=" -DIOT_TEST_MQTT_MOSQUITTO=1 -DIOT_TEST_SERVER=\\\"localhost\\\"" DEMO_OPTIONS+=" -n -u -h localhost -p 1883" - else - # Specify that the tests should not use the network on macOS and Windows. - TEST_OPTIONS="-n" fi else # Set credentials for AWS IoT. @@ -33,31 +27,17 @@ fi # Build and run executables. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS" -if [ "$TRAVIS_OS_NAME" = "windows" ]; then - MSBuild.exe libraries/standard/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal - MSBuild.exe demos/iot_demo_mqtt.vcxproj -m -clp:summary -verbosity:minimal - - ./bin/Debug/iot_tests_mqtt.exe $TEST_OPTIONS -else - make -j2 iot_tests_mqtt iot_demo_mqtt +make -j2 iot_tests_mqtt iot_demo_mqtt - ./bin/iot_tests_mqtt $TEST_OPTIONS +./bin/iot_tests_mqtt $TEST_OPTIONS - if [ "$TRAVIS_OS_NAME" = "linux" ]; then - ./bin/iot_demo_mqtt $DEMO_OPTIONS - fi +if [ "$TRAVIS_OS_NAME" = "linux" ]; then + ./bin/iot_demo_mqtt $DEMO_OPTIONS fi # Rebuild and run tests in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL=$IOT_NETWORK_USE_OPENSSL -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" -if [ "$TRAVIS_OS_NAME" = "windows" ]; then - MSBuild.exe libraries/standard/mqtt/iot_tests_mqtt.vcxproj -m -clp:summary -verbosity:minimal - MSBuild.exe demos/iot_demo_mqtt.vcxproj -m -clp:summary -verbosity:minimal - - ./bin/Debug/iot_tests_mqtt.exe $TEST_OPTIONS -else - make -j2 iot_tests_mqtt iot_demo_mqtt +make -j2 iot_tests_mqtt iot_demo_mqtt - ./bin/iot_tests_mqtt $TEST_OPTIONS -fi +./bin/iot_tests_mqtt $TEST_OPTIONS diff --git a/scripts/setup/ci_setup_osx.sh b/scripts/setup/ci_setup_osx.sh deleted file mode 100755 index d0926ec91f..0000000000 --- a/scripts/setup/ci_setup_osx.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to set up the test environment on macOS. - -# Set compiler options for macOS. Silence field initializer warnings. -export COMPILER_OPTIONS="-Wall -Wextra -Wno-missing-field-initializers" diff --git a/scripts/setup/ci_setup_windows.sh b/scripts/setup/ci_setup_windows.sh deleted file mode 100755 index 0f2e311e75..0000000000 --- a/scripts/setup/ci_setup_windows.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -# Travis CI uses this script to set up the test environment on Windows. - -# Location of MSBuild.exe in the Travis CI test environment. -MSBUILD_PATH="/c/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/MSBuild/15.0/Bin" -export PATH=$PATH:$MSBUILD_PATH - -# Use default compiler options for MSVC. -export COMPILER_OPTIONS="" diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index d44691a8e5..51b9f1fc2f 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -38,10 +38,7 @@ add_library( tinycbor target_include_directories( tinycbor SYSTEM PUBLIC tinycbor/src ) -# Link math library. This is not needed on Windows. -if( NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) - target_link_libraries( tinycbor PRIVATE m ) -endif() +target_link_libraries( tinycbor PRIVATE m ) # Disable all warnings for this third-party library. target_compile_options( tinycbor From 70c34a354de188f1786478367674ed56ab46ee3a Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 18 Oct 2019 10:51:10 -0700 Subject: [PATCH 297/844] Minor formatting fixes (#605) --- libraries/standard/mqtt/src/iot_mqtt_api.c | 102 +++++-------- .../standard/mqtt/src/iot_mqtt_network.c | 144 ++++++++---------- .../standard/mqtt/src/iot_mqtt_operation.c | 30 ++-- 3 files changed, 114 insertions(+), 162 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 5a1b76316b..008675979b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -183,48 +183,34 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializePingreq_t, - _getMqttPingreqSerializer, - _IotMqtt_SerializePingreq, - serialize.pingreq - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqtt_SerializePublish_t, - _getMqttPublishSerializer, - _IotMqtt_SerializePublish, - serialize.publish - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeSubscribe_t, - _getMqttSubscribeSerializer, - _IotMqtt_SerializeSubscribe, - serialize.subscribe - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeSubscribe_t, - _getMqttUnsubscribeSerializer, - _IotMqtt_SerializeUnsubscribe, - serialize.unsubscribe - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeConnect_t, - _getMqttConnectSerializer, - _IotMqtt_SerializeConnect, - serialize.connect - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializeDisconnect_t, - _getMqttDisconnectSerializer, - _IotMqtt_SerializeDisconnect, - serialize.disconnect - ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePingreq_t, + _getMqttPingreqSerializer, + _IotMqtt_SerializePingreq, + serialize.pingreq ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqtt_SerializePublish_t, + _getMqttPublishSerializer, + _IotMqtt_SerializePublish, + serialize.publish ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, + _getMqttSubscribeSerializer, + _IotMqtt_SerializeSubscribe, + serialize.subscribe ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeSubscribe_t, + _getMqttUnsubscribeSerializer, + _IotMqtt_SerializeUnsubscribe, + serialize.unsubscribe ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeConnect_t, + _getMqttConnectSerializer, + _IotMqtt_SerializeConnect, + serialize.connect ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializeDisconnect_t, + _getMqttDisconnectSerializer, + _IotMqtt_SerializeDisconnect, + serialize.disconnect ) #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq #define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish @@ -379,9 +365,8 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo pMqttConnection->pingreq.u.operation.periodic.ping.nextPeriodMs = keepAliveSeconds * 1000; /* Generate a PINGREQ packet. */ - serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( - &( pMqttConnection->pingreq.u.operation.pMqttPacket ), - &( pMqttConnection->pingreq.u.operation.packetSize ) ); + serializeStatus = _getMqttPingreqSerializer( pMqttConnection->pSerializer )( &( pMqttConnection->pingreq.u.operation.pMqttPacket ), + &( pMqttConnection->pingreq.u.operation.packetSize ) ); if( serializeStatus != IOT_MQTT_SUCCESS ) { @@ -552,8 +537,8 @@ static void _destroyMqttConnection( _mqttConnection_t * pMqttConnection ) if( pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs != 0 ) { IotLogDebug( "(MQTT connection %p) Cleaning up keep-alive.", pMqttConnection ); - _getMqttFreePacketFunc( pMqttConnection->pSerializer )( - pMqttConnection->pingreq.u.operation.pMqttPacket ); + + _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; @@ -1223,10 +1208,9 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, } /* Convert the connect info and will info objects to an MQTT CONNECT packet. */ - status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( - pConnectInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); + status = _getMqttConnectSerializer( pNetworkInfo->pMqttSerializer )( pConnectInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -1397,9 +1381,8 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, pOperation->u.operation.type = IOT_MQTT_DISCONNECT; /* Generate a DISCONNECT packet. */ - status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ) ); + status = _getMqttDisconnectSerializer( mqttConnection->pSerializer )( &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ) ); } else { @@ -1739,12 +1722,11 @@ IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection, } /* Generate a PUBLISH packet from pPublishInfo. */ - status = _getMqttPublishSerializer( mqttConnection->pSerializer )( - pPublishInfo, - &( pOperation->u.operation.pMqttPacket ), - &( pOperation->u.operation.packetSize ), - &( pOperation->u.operation.packetIdentifier ), - pPacketIdentifierHigh ); + status = _getMqttPublishSerializer( mqttConnection->pSerializer )( pPublishInfo, + &( pOperation->u.operation.pMqttPacket ), + &( pOperation->u.operation.packetSize ), + &( pOperation->u.operation.packetIdentifier ), + pPacketIdentifierHigh ); if( status != IOT_MQTT_SUCCESS ) { diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index ac86fedf7a..cd5768a8d1 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -112,66 +112,46 @@ static void _flushPacket( void * pNetworkConnection, * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttGetPacketType_t, - _getPacketTypeFunc, - _IotMqtt_GetPacketType, - getPacketType - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttGetRemainingLength_t, - _getRemainingLengthFunc, - _IotMqtt_GetRemainingLength, - getRemainingLength - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserialize_t, - _getConnackDeserializer, - _IotMqtt_DeserializeConnack, - deserialize.connack - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserialize_t, - _getPublishDeserializer, - _IotMqtt_DeserializePublish, - deserialize.publish - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserialize_t, - _getPubackDeserializer, - _IotMqtt_DeserializePuback, - deserialize.puback - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserialize_t, - _getSubackDeserializer, - _IotMqtt_DeserializeSuback, - deserialize.suback - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserialize_t, - _getUnsubackDeserializer, - _IotMqtt_DeserializeUnsuback, - deserialize.unsuback - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttDeserialize_t, - _getPingrespDeserializer, - _IotMqtt_DeserializePingresp, - deserialize.pingresp - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttSerializePuback_t, - _getMqttPubackSerializer, - _IotMqtt_SerializePuback, - serialize.puback - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket - ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetPacketType_t, + _getPacketTypeFunc, + _IotMqtt_GetPacketType, + getPacketType ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttGetRemainingLength_t, + _getRemainingLengthFunc, + _IotMqtt_GetRemainingLength, + getRemainingLength ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getConnackDeserializer, + _IotMqtt_DeserializeConnack, + deserialize.connack ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getPublishDeserializer, + _IotMqtt_DeserializePublish, + deserialize.publish ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getPubackDeserializer, + _IotMqtt_DeserializePuback, + deserialize.puback ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getSubackDeserializer, + _IotMqtt_DeserializeSuback, + deserialize.suback ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getUnsubackDeserializer, + _IotMqtt_DeserializeUnsuback, + deserialize.unsuback ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttDeserialize_t, + _getPingrespDeserializer, + _IotMqtt_DeserializePingresp, + deserialize.pingresp ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttSerializePuback_t, + _getMqttPubackSerializer, + _IotMqtt_SerializePuback, + serialize.puback ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket ) #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType #define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength @@ -227,9 +207,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, IotMqtt_Assert( pIncomingPacket->remainingLength == 0 ); /* Read the packet type, which is the first byte available. */ - pIncomingPacket->type = _getPacketTypeFunc( pMqttConnection->pSerializer )( - pNetworkConnection, - pMqttConnection->pNetworkInterface ); + pIncomingPacket->type = _getPacketTypeFunc( pMqttConnection->pSerializer )( pNetworkConnection, + pMqttConnection->pNetworkInterface ); /* Check that the incoming packet type is valid. */ if( _incomingPacketValid( pIncomingPacket->type ) == false ) @@ -246,9 +225,8 @@ static IotMqttError_t _getIncomingPacket( void * pNetworkConnection, } /* Read the remaining length. */ - pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( - pNetworkConnection, - pMqttConnection->pNetworkInterface ); + pIncomingPacket->remainingLength = _getRemainingLengthFunc( pMqttConnection->pSerializer )( pNetworkConnection, + pMqttConnection->pNetworkInterface ); if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) { @@ -341,9 +319,10 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne { case MQTT_PACKET_TYPE_CONNACK: IotLogDebug( "(MQTT connection %p) CONNACK in data stream.", pMqttConnection ); + /* Deserialize CONNACK and notify of result. */ - status = _getConnackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + status = _getConnackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_CONNECT, NULL ); @@ -383,8 +362,7 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne } /* Deserialize incoming PUBLISH. */ - status = _getPublishDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + status = _getPublishDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); if( status == IOT_MQTT_SUCCESS ) { @@ -468,8 +446,8 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne IotLogDebug( "(MQTT connection %p) PUBACK in data stream.", pMqttConnection ); /* Deserialize PUBACK and notify of result. */ - status = _getPubackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + status = _getPubackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_PUBLISH_TO_SERVER, &( pIncomingPacket->packetIdentifier ) ); @@ -492,8 +470,8 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne /* Deserialize SUBACK and notify of result. */ pIncomingPacket->u.pMqttConnection = pMqttConnection; - status = _getSubackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + status = _getSubackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); + pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_SUBSCRIBE, &( pIncomingPacket->packetIdentifier ) ); @@ -512,9 +490,9 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne case MQTT_PACKET_TYPE_UNSUBACK: IotLogDebug( "(MQTT connection %p) UNSUBACK in data stream.", pMqttConnection ); + /* Deserialize UNSUBACK and notify of result. */ - status = _getUnsubackDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + status = _getUnsubackDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); pOperation = _IotMqtt_FindOperation( pMqttConnection, IOT_MQTT_UNSUBSCRIBE, @@ -535,11 +513,11 @@ static IotMqttError_t _deserializeIncomingPacket( _mqttConnection_t * pMqttConne default: /* The only remaining valid type is PINGRESP. */ IotMqtt_Assert( ( pIncomingPacket->type & 0xf0 ) == MQTT_PACKET_TYPE_PINGRESP ); + IotLogDebug( "(MQTT connection %p) PINGRESP in data stream.", pMqttConnection ); /* Deserialize PINGRESP. */ - status = _getPingrespDeserializer( pMqttConnection->pSerializer )( - pIncomingPacket ); + status = _getPingrespDeserializer( pMqttConnection->pSerializer )( pIncomingPacket ); if( status == IOT_MQTT_SUCCESS ) { @@ -605,10 +583,9 @@ static void _sendPuback( _mqttConnection_t * pMqttConnection, pPubackOperation->u.operation.type = IOT_MQTT_PUBACK; /* Generate a PUBACK packet from the packet identifier. */ - status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( - packetIdentifier, - &( pPubackOperation->u.operation.pMqttPacket ), - &( pPubackOperation->u.operation.packetSize ) ); + status = _getMqttPubackSerializer( pMqttConnection->pSerializer )( packetIdentifier, + &( pPubackOperation->u.operation.pMqttPacket ), + &( pPubackOperation->u.operation.packetSize ) ); if( status != IOT_MQTT_SUCCESS ) { @@ -740,8 +717,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason if( taskPoolStatus == IOT_TASKPOOL_SUCCESS ) { /* Free the packet */ - _getMqttFreePacketFunc( pMqttConnection->pSerializer )( - pMqttConnection->pingreq.u.operation.pMqttPacket ); + _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pMqttConnection->pingreq.u.operation.pMqttPacket ); /* Clear data about the keep-alive. */ pMqttConnection->pingreq.u.operation.periodic.ping.keepAliveMs = 0; diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 0ba89108a0..2acd78f0e2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -51,18 +51,14 @@ * Declaration of local MQTT serializer override selectors */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttPublishSetDup_t, - _getMqttPublishSetDupFunc, - _IotMqtt_PublishSetDup, - serialize.publishSetDup - ) - _SERIALIZER_OVERRIDE_SELECTOR( - IotMqttFreePacket_t, - _getMqttFreePacketFunc, - _IotMqtt_FreePacket, - freePacket - ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttPublishSetDup_t, + _getMqttPublishSetDupFunc, + _IotMqtt_PublishSetDup, + serialize.publishSetDup ) + _SERIALIZER_OVERRIDE_SELECTOR( IotMqttFreePacket_t, + _getMqttFreePacketFunc, + _IotMqtt_FreePacket, + freePacket ) #else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket #define _getMqttPublishSetDupFunc( pSerializer ) _IotMqtt_PublishSetDup @@ -211,10 +207,9 @@ static bool _checkRetryLimit( _mqttOperation_t * pOperation ) } /* Set the DUP flag */ - _getMqttPublishSetDupFunc( pMqttConnection->pSerializer )( - pOperation->u.operation.pMqttPacket, - pOperation->u.operation.pPacketIdentifierHigh, - &( pOperation->u.operation.packetIdentifier ) ); + _getMqttPublishSetDupFunc( pMqttConnection->pSerializer )( pOperation->u.operation.pMqttPacket, + pOperation->u.operation.pPacketIdentifierHigh, + &( pOperation->u.operation.packetIdentifier ) ); if( pMqttConnection->awsIotMqttMode == true ) { @@ -607,8 +602,7 @@ void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation ) /* Free any allocated MQTT packet. */ if( pOperation->u.operation.pMqttPacket != NULL ) { - _getMqttFreePacketFunc( pMqttConnection->pSerializer )( - pOperation->u.operation.pMqttPacket ); + _getMqttFreePacketFunc( pMqttConnection->pSerializer )( pOperation->u.operation.pMqttPacket ); IotLogDebug( "(MQTT connection %p, %s operation %p) MQTT packet freed.", pMqttConnection, From 92549482581a982bd2b53ebed3878df2c0e00d33 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Fri, 18 Oct 2019 13:57:47 -0400 Subject: [PATCH 298/844] Remove the assert for system taskpool check (#606) --- libraries/standard/mqtt/src/iot_mqtt_operation.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_operation.c b/libraries/standard/mqtt/src/iot_mqtt_operation.c index 2acd78f0e2..5235fdf6a3 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_operation.c +++ b/libraries/standard/mqtt/src/iot_mqtt_operation.c @@ -664,7 +664,6 @@ void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool, _mqttOperation_t * pPingreqOperation = &( pMqttConnection->pingreq ); /* Check parameters. */ - IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pKeepAliveJob == pPingreqOperation->job ); /* Check that keep-alive interval is valid. The MQTT spec states its maximum @@ -1032,7 +1031,6 @@ void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool, * are disabled. */ ( void ) pTaskPool; ( void ) pOperationJob; - IotMqtt_Assert( pTaskPool == IOT_SYSTEM_TASKPOOL ); IotMqtt_Assert( pOperationJob == pOperation->job ); /* The operation's callback function and status must be set. */ From 0ffbce523b62a63981aa8b1086370f408b66662c Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Fri, 18 Oct 2019 11:01:55 -0700 Subject: [PATCH 299/844] Revert signature change to IotNetworkInterface_t::create, doc updates (#607) --- libraries/platform/iot_network.h | 119 +++++++++--------- libraries/standard/mqtt/include/iot_mqtt.h | 13 +- .../mqtt/include/types/iot_mqtt_types.h | 15 +-- ports/common/include/iot_network_mbedtls.h | 4 +- ports/common/include/iot_network_openssl.h | 4 +- ports/common/src/iot_network_mbedtls.c | 4 +- ports/common/src/iot_network_metrics.c | 10 +- ports/posix/src/iot_network_openssl.c | 4 +- 8 files changed, 80 insertions(+), 93 deletions(-) diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 10a40f51f9..b9174eac0e 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -91,64 +91,6 @@ typedef void ( * IotNetworkReceiveCallback_t )( void * pConnection, void * pContext ); /* @[declare_platform_network_receivecallback] */ -/** - * @ingroup platform_datatypes_paramstructs - * @brief Information on the remote server for connection setup. - * - * May be passed to #IotNetworkInterface_t.create as `pConnectionInfo`. This - * structure contains commonly-used parameters, but may be replaced with an - * alternative. - */ -typedef struct IotNetworkServerInfo -{ - const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ - uint16_t port; /**< @brief Server port in host-order. */ -} IotNetworkServerInfo_t; - -/** - * @ingroup platform_datatypes_paramstructs - * @brief Contains the credentials necessary for connection setup. - * - * May be passed to #IotNetworkInterface_t.create as `pCredentialInfo`. This - * structure contains commonly-used parameters, but may be replaced with an - * alternative. - */ -typedef struct IotNetworkCredentials -{ - /** - * @brief Set this to a non-NULL value to use ALPN. - * - * This string must be NULL-terminated. - * - * See [this link] - * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) - * for more information. - */ - const char * pAlpnProtos; - - /** - * @brief Set this to a non-zero value to use TLS max fragment length - * negotiation (TLS MFLN). - * - * @note The network stack may have a minimum value for this parameter and - * may return an error if this parameter is too small. - */ - size_t maxFragmentLength; - - /** - * @brief Disable server name indication (SNI) for a TLS session. - */ - bool disableSni; - - const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ - size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials_t.pRootCa. */ - const char * pClientCert; /**< @brief String representing the client certificate. */ - size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials_t.pClientCert. */ - const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ - size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials_t.pPrivateKey. */ -} IotNetworkCredentials_t; - - /** * @ingroup platform_datatypes_paramstructs * @brief Represents the functions of a network stack. @@ -172,8 +114,8 @@ typedef struct IotNetworkInterface * @return Any #IotNetworkError_t, as defined by the network stack. */ /* @[declare_platform_network_create] */ - IotNetworkError_t ( * create )( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, + IotNetworkError_t ( * create )( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ); /* @[declare_platform_network_create] */ @@ -288,4 +230,61 @@ typedef struct IotNetworkInterface /* @[declare_platform_network_destroy] */ } IotNetworkInterface_t; +/** + * @ingroup platform_datatypes_paramstructs + * @brief Information on the remote server for connection setup. + * + * May be passed to #IotNetworkInterface_t.create as `pConnectionInfo`. This + * structure contains commonly-used parameters, but may be replaced with an + * alternative. + */ +typedef struct IotNetworkServerInfo +{ + const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ + uint16_t port; /**< @brief Server port in host-order. */ +} IotNetworkServerInfo_t; + +/** + * @ingroup platform_datatypes_paramstructs + * @brief Contains the credentials necessary for connection setup. + * + * May be passed to #IotNetworkInterface_t.create as `pCredentialInfo`. This + * structure contains commonly-used parameters, but may be replaced with an + * alternative. + */ +typedef struct IotNetworkCredentials +{ + /** + * @brief Set this to a non-NULL value to use ALPN. + * + * This string must be NULL-terminated. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char * pAlpnProtos; + + /** + * @brief Set this to a non-zero value to use TLS max fragment length + * negotiation (TLS MFLN). + * + * @note The network stack may have a minimum value for this parameter and + * may return an error if this parameter is too small. + */ + size_t maxFragmentLength; + + /** + * @brief Disable server name indication (SNI) for a TLS session. + */ + bool disableSni; + + const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ + size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials_t.pRootCa. */ + const char * pClientCert; /**< @brief String representing the client certificate. */ + size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials_t.pClientCert. */ + const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ + size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials_t.pPrivateKey. */ +} IotNetworkCredentials_t; + #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index 391b3344a5..c4c20d80d3 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -224,7 +224,7 @@ void IotMqtt_ReceiveCallback( void * pNetworkConnection, * // Set the members of the will info (retain and retry not used). * willInfo.qos = IOT_MQTT_QOS_1; * willInfo.pTopicName = "will/topic/name"; - * willInfo.topicNameLength = ( uint16_t )strlen( willInfo.pTopicName ); + * willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName ); * willInfo.pPayload = "MQTT client unexpectedly disconnected."; * willInfo.payloadLength = strlen( willInfo.pPayload ); * @@ -323,7 +323,7 @@ void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection, * subscriptions. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion (NULL to disable). + * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable). * @param[out] pSubscribeOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the subscription operation completes. @@ -484,7 +484,7 @@ IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection, * subscriptions. * @param[in] subscriptionCount The number of elements in pSubscriptionList. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion (NULL to disable) + * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable). * @param[out] pUnsubscribeOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the unsubscribe operation completes. @@ -568,7 +568,7 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, * @param[in] mqttConnection The MQTT connection to use for the publish. * @param[in] pPublishInfo MQTT publish parameters. * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. - * @param[in] pCallbackInfo Asynchronous notification of this function's completion (NULL to disable). + * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable). * @param[out] pPublishOperation Set to a handle by which this operation may be * referenced after this function returns. This reference is invalidated once * the publish operation completes. @@ -606,7 +606,7 @@ IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection, * // Set the publish information. QoS 0 example (retain not used): * publishInfo.qos = IOT_MQTT_QOS_0; * publishInfo.pTopicName = "some/topic/name"; - * publishInfo.topicNameLength = ( uint16_t )strlen( publishInfo.pTopicName ); + * publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName ); * publishInfo.pPayload = "payload"; * publishInfo.payloadLength = strlen( publishInfo.pPayload ); * @@ -807,8 +807,7 @@ const char * IotMqtt_OperationType( IotMqttOperationType_t operation ); * @param[in] pTopicFilter The topic filter to check. * @param[in] topicFilterLength Length of `pTopicFilter`. * @param[out] pCurrentSubscription If a subscription is found, its details are - * copied here. This output parameter is only valid if this function returns `true`. - * Pass `NULL` to ignore. + * copied here. This output parameter is only valid if this function returns `true` (`NULL` to disable). * * @return `true` if a subscription was found; `false` otherwise. * diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index d011617ef3..2d6e36ea94 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -945,17 +945,6 @@ typedef void ( * IotMqttPublishSetDup_t )( uint8_t * pPublishPacket, #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ -/** - * @cond DOXYGEN_IGNORE - * Doxygen should ignore this section. - * - * Forward declarations of platform network server info and credentials - * types. - */ -struct IotNetworkServerInfo_t; -struct IotNetworkCredentials_t; -/** @endcond */ - /** * @ingroup mqtt_datatypes_paramstructs * @brief MQTT network connection details. @@ -998,7 +987,7 @@ typedef struct IotMqttNetworkInfo * interface when creating a new network connection. It is only valid when * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ - IotNetworkServerInfo_t * pNetworkServerInfo; + void * pNetworkServerInfo; /** * @brief Credentials for the MQTT server, passed as `pCredentialInfo` to @@ -1008,7 +997,7 @@ typedef struct IotMqttNetworkInfo * interface when creating a new network connection. It is only valid when * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ - IotNetworkCredentials_t * pNetworkCredentialInfo; + void * pNetworkCredentialInfo; } setup; /** diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h index 9c02f7158b..604bf622e1 100644 --- a/ports/common/include/iot_network_mbedtls.h +++ b/ports/common/include/iot_network_mbedtls.h @@ -112,8 +112,8 @@ void IotNetworkMbedtls_Cleanup( void ); /** * @brief An implementation of #IotNetworkInterface_t::create for mbed TLS. */ -IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, +IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ); /** diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h index fe08a3434b..2cc096fc91 100644 --- a/ports/common/include/iot_network_openssl.h +++ b/ports/common/include/iot_network_openssl.h @@ -124,8 +124,8 @@ void IotNetworkOpenssl_Cleanup( void ); * @brief An implementation of #IotNetworkInterface_t::create for POSIX systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, +IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ); /** diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index 3fda2f4911..e3261b00c7 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -781,8 +781,8 @@ void IotNetworkMbedtls_Cleanup( void ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, +IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); diff --git a/ports/common/src/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c index cb8ffa2aa9..788440f27d 100644 --- a/ports/common/src/iot_network_metrics.c +++ b/ports/common/src/iot_network_metrics.c @@ -64,8 +64,8 @@ /** * @brief Wraps the network connection creation function with metrics. */ -static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, +static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ); /** @@ -140,7 +140,7 @@ static IotMutex_t _connectionListMutex; /** * @brief Pointer to the metrics-wrapped network creation function. */ - static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t *, IotNetworkCredentials_t *, void ** ) = IotNetworkMbedtls_Create; + static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkMbedtls_Create; /** * @brief Pointer to the metrics-wrapped network close function. @@ -184,8 +184,8 @@ static bool _connectionMatch( const IotLink_t * pConnectionLink, /*-----------------------------------------------------------*/ -static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, +static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ) { IotMetricsTcpConnection_t * pTcpConnection = NULL; diff --git a/ports/posix/src/iot_network_openssl.c b/ports/posix/src/iot_network_openssl.c index beaae966b1..39ce7144e0 100644 --- a/ports/posix/src/iot_network_openssl.c +++ b/ports/posix/src/iot_network_openssl.c @@ -661,8 +661,8 @@ void IotNetworkOpenssl_Cleanup( void ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t * pConnectionInfo, - IotNetworkCredentials_t * pCredentialInfo, +IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, + void * pCredentialInfo, void ** pConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); From a6b7296d3a99437dcc3bd93851d1ae71bac9ca4e Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Sat, 19 Oct 2019 08:55:54 -0700 Subject: [PATCH 300/844] Use #defines for max client ID & "remove all subscriptions" constants (#608) Use #defines for max client ID & "remove all subscriptions" constants. --- libraries/standard/mqtt/src/iot_mqtt_api.c | 4 +-- .../standard/mqtt/src/iot_mqtt_subscription.c | 6 ++-- .../standard/mqtt/src/iot_mqtt_validate.c | 36 +++++++++---------- .../mqtt/src/private/iot_mqtt_internal.h | 21 ++++++++--- .../test/unit/iot_tests_mqtt_subscription.c | 2 +- .../mqtt/test/unit/iot_tests_mqtt_validate.c | 6 ++-- 6 files changed, 43 insertions(+), 32 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 008675979b..7c347a49d2 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -799,7 +799,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, { _IotMqtt_RemoveSubscriptionByPacket( mqttConnection, pSubscriptionOperation->u.operation.packetIdentifier, - -1 ); + MQTT_REMOVE_ALL_SUBSCRIPTIONS ); } else { @@ -1979,7 +1979,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, _IotMqtt_RemoveSubscriptionByPacket( pMqttConnection, operation->u.operation.packetIdentifier, - -1 ); + MQTT_REMOVE_ALL_SUBSCRIPTIONS ); } else { diff --git a/libraries/standard/mqtt/src/iot_mqtt_subscription.c b/libraries/standard/mqtt/src/iot_mqtt_subscription.c index 1f057bcb35..782de35bb7 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_subscription.c +++ b/libraries/standard/mqtt/src/iot_mqtt_subscription.c @@ -59,7 +59,7 @@ typedef struct _topicMatchParams typedef struct _packetMatchParams { uint16_t packetIdentifier; /**< Packet identifier to match. */ - int32_t order; /**< Order to match. Set to `-1` to ignore. */ + int32_t order; /**< Order to match. Set to #MQTT_REMOVE_ALL_SUBSCRIPTIONS to ignore. */ } _packetMatchParams_t; /*-----------------------------------------------------------*/ @@ -265,8 +265,8 @@ static bool _packetMatch( const IotLink_t * pSubscriptionLink, /* Compare packet identifiers. */ if( pParam->packetIdentifier == pSubscription->packetInfo.identifier ) { - /* Compare orders if order is not -1. */ - if( pParam->order == -1 ) + /* Compare orders if order is not MQTT_REMOVE_ALL_SUBSCRIPTIONS. */ + if( pParam->order == MQTT_REMOVE_ALL_SUBSCRIPTIONS ) { match = true; } diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 16450708f4..af3ede95c5 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -39,6 +39,8 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IOT_FUNCTION_ENTRY( bool, true ); + uint16_t maxClientIdLength = IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; + bool enforceMaxClientIdLength = false; /* Check for NULL. */ if( pConnectInfo == NULL ) @@ -112,33 +114,29 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) EMPTY_ELSE_MARKER; } - /* In MQTT 3.1.1, servers are not obligated to accept client identifiers longer - * than 23 characters. */ - if( pConnectInfo->clientIdentifierLength > 23 ) - { - IotLogWarn( "A client identifier length of %hu is longer than 23, which is " - "the longest client identifier a server must accept.", - pConnectInfo->clientIdentifierLength ); - } - else + /* The AWS IoT MQTT service enforces a client ID length limit. */ + if( pConnectInfo->awsIotMqttMode == true ) { - EMPTY_ELSE_MARKER; + maxClientIdLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; + enforceMaxClientIdLength = true; } - /* Check for compatibility with the AWS IoT MQTT service limits. */ - if( pConnectInfo->awsIotMqttMode == true ) + if( pConnectInfo->clientIdentifierLength > maxClientIdLength ) { - /* Check that client identifier is within the service limit. */ - if( pConnectInfo->clientIdentifierLength > AWS_IOT_MQTT_SERVER_MAX_CLIENTID ) + if( enforceMaxClientIdLength == false ) { - IotLogError( "AWS IoT does not support client identifiers longer than %d bytes.", - AWS_IOT_MQTT_SERVER_MAX_CLIENTID ); - - IOT_SET_AND_GOTO_CLEANUP( false ); + IotLogWarn( "A client identifier length of %hu is longer than %hu, " + "which is " + "the longest client identifier a server must accept.", + pConnectInfo->clientIdentifierLength, + maxClientIdLength ); } else { - EMPTY_ELSE_MARKER; + IotLogError( "A client identifier length of %hu exceeds the " + "the maximum supported length of %hu.", + pConnectInfo->clientIdentifierLength, + maxClientIdLength ); } } else diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index e67b89b8f9..99b550b55e 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -235,6 +235,8 @@ #define EMPTY_ELSE_MARKER #endif +#define IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 23 ) /**< @brief Optional maximum length of client identifier specified by MQTT 3.1.1. */ + /* * Constants related to limits defined in AWS Service Limits. * @@ -243,9 +245,9 @@ * * Used to validate parameters if when connecting to an AWS IoT MQTT server. */ -#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID ( 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ +#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ /* * MQTT control packet type and flags. Always the first byte of an MQTT @@ -283,6 +285,17 @@ */ #define MQTT_INTERNAL_FLAG_BLOCK_ON_SEND ( 0x80000000 ) +/** + * @brief When calling #_IotMqtt_RemoveSubscriptionByPacket, use this value + * for `order` to delete all subscriptions for the packet. + * + * This flag is used along with @ref mqtt_constants_flags, but is intended for internal + * use only. + * + * @ref mqtt_constants_flags. + */ +#define MQTT_REMOVE_ALL_SUBSCRIPTIONS ( -1 ) + /*---------------------- MQTT internal data structures ----------------------*/ /** @@ -892,7 +905,7 @@ void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection, * @param[in] packetIdentifier The packet identifier associated with the subscription's * SUBSCRIBE packet. * @param[in] order The order of the subscription in the SUBSCRIBE packet. - * Pass `-1` to ignore order and remove all subscriptions for `packetIdentifier`. + * Pass #MQTT_REMOVE_ALL_SUBSCRIPTIONS to ignore order and remove all subscriptions for `packetIdentifier`. */ void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection, uint16_t packetIdentifier, diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c index 7974fc01ff..8856be4dee 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_subscription.c @@ -491,7 +491,7 @@ TEST( MQTT_Unit_Subscription, SubscriptionRemoveByPacket ) _populateList(); _IotMqtt_RemoveSubscriptionByPacket( _pMqttConnection, 1, - -1 ); + MQTT_REMOVE_ALL_SUBSCRIPTIONS ); TEST_ASSERT_EQUAL_INT( true, IotListDouble_IsEmpty( &( _pMqttConnection->subscriptionList ) ) ); } diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c index c951ec498d..9a1dd3e460 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c @@ -131,16 +131,16 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); - /* Client identifier longer than 23 characters. */ + /* Client identifier longer than the MQTT 3.1.1 recommended maximum length. */ connectInfo.pClientIdentifier = "longlongclientidentifier"; - connectInfo.clientIdentifierLength = 24; + connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier ); validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); /* AWS IoT MQTT service limit tests. */ #if AWS_IOT_MQTT_SERVER == true /* Client identifier too long. */ - connectInfo.clientIdentifierLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID + 1; + connectInfo.clientIdentifierLength = AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH + 1; validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( false, validateStatus ); connectInfo.clientIdentifierLength = 24; From 86bfe195d1c881e66205e23fbeeaf888473cd73f Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Wed, 23 Oct 2019 11:52:19 -0700 Subject: [PATCH 301/844] Simplify IotMqtt_Connect(); fix max client ID length check. (#610) --- libraries/standard/mqtt/src/iot_mqtt_api.c | 159 ++++++++---------- .../standard/mqtt/src/iot_mqtt_validate.c | 108 ++++++++++-- .../mqtt/src/private/iot_mqtt_internal.h | 23 ++- 3 files changed, 188 insertions(+), 102 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 7c347a49d2..3e697c209a 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -111,6 +111,20 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo uint16_t keepAliveSeconds, _mqttConnection_t * pMqttConnection ); +/** + * @brief Initialize a network connection, creating it if necessary. + * + * @param[in] pNetworkInfo User-provided network information for the connection + * connection. + * @param[out] pNetworkConnection On success, the created and/or initialized network connection. + * @param[out] pCreatedNewNetworkConnection On success, `true` if a new network connection was created; `false` if an existing one will be used. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + */ +static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, + void ** pNetworkConnection, + bool * pCreatedNewNetworkConnection ); + /** * @brief Creates a new MQTT connection and initializes its members. * @@ -211,7 +225,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, _getMqttDisconnectSerializer, _IotMqtt_SerializeDisconnect, serialize.disconnect ) -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getMqttPingreqSerializer( pSerializer ) _IotMqtt_SerializePingreq #define _getMqttPublishSerializer( pSerializer ) _IotMqtt_SerializePublish #define _getMqttFreePacketFunc( pSerializer ) _IotMqtt_FreePacket @@ -404,6 +418,58 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo /*-----------------------------------------------------------*/ +static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, + void ** pNetworkConnection, + bool * pCreatedNewNetworkConnection ) +{ + IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); + + /* Network info must not be NULL. */ + if( pNetworkInfo == NULL ) + { + IotLogError( "Network information cannot be NULL." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Create a new network connection if requested. Otherwise, copy the existing + * network connection. */ + if( pNetworkInfo->createNetworkConnection == true ) + { + status = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->u.setup.pNetworkServerInfo, + pNetworkInfo->u.setup.pNetworkCredentialInfo, + pNetworkConnection ); + + if( status == IOT_NETWORK_SUCCESS ) + { + /* This MQTT connection owns the network connection it created and + * should destroy it on cleanup. */ + *pCreatedNewNetworkConnection = true; + } + else + { + IotLogError( "Failed to create network connection: %d", status ); + + IOT_GOTO_CLEANUP(); + } + } + else + { + /* A connection already exists; the caller should not destroy + * it on cleanup. */ + *pNetworkConnection = pNetworkInfo->u.pNetworkConnection; + *pCreatedNewNetworkConnection = false; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + static _mqttConnection_t * _createMqttConnection( bool awsIotMqttMode, const IotMqttNetworkInfo_t * pNetworkInfo, uint16_t keepAliveSeconds ) @@ -999,7 +1065,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqttConnection_t * const pMqttConnection ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - bool networkCreated = false, ownNetworkConnection = false; + bool ownNetworkConnection = false; IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; void * pNetworkConnection = NULL; @@ -1016,16 +1082,6 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, EMPTY_ELSE_MARKER; } - /* Network info must not be NULL. */ - if( pNetworkInfo == NULL ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); - } - else - { - EMPTY_ELSE_MARKER; - } - /* Validate network interface and connect info. */ if( _IotMqtt_ValidateConnect( pConnectInfo ) == false ) { @@ -1036,86 +1092,19 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, EMPTY_ELSE_MARKER; } - /* If will info is provided, check that it is valid. */ - if( pConnectInfo->pWillInfo != NULL ) - { - if( _IotMqtt_ValidatePublish( pConnectInfo->awsIotMqttMode, - pConnectInfo->pWillInfo ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); - } - else if( pConnectInfo->pWillInfo->payloadLength > UINT16_MAX ) - { - /* Will message payloads cannot be larger than 65535. This restriction - * applies only to will messages, and not normal PUBLISH messages. */ - IotLogError( "Will payload cannot be larger than 65535." ); + networkStatus = _createNetworkConnection( pNetworkInfo, + &pNetworkConnection, + &ownNetworkConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } - - /* If previous subscriptions are provided, check that they are valid. */ - if( pConnectInfo->cleanSession == false ) + if( networkStatus != IOT_NETWORK_SUCCESS ) { - if( pConnectInfo->pPreviousSubscriptions != NULL ) - { - if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); - } - else - { - EMPTY_ELSE_MARKER; - } - } - else - { - EMPTY_ELSE_MARKER; - } + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); } else { EMPTY_ELSE_MARKER; } - /* Create a new MQTT connection if requested. Otherwise, copy the existing - * network connection. */ - if( pNetworkInfo->createNetworkConnection == true ) - { - networkStatus = pNetworkInfo->pNetworkInterface->create( pNetworkInfo->u.setup.pNetworkServerInfo, - pNetworkInfo->u.setup.pNetworkCredentialInfo, - &pNetworkConnection ); - - if( networkStatus == IOT_NETWORK_SUCCESS ) - { - networkCreated = true; - - /* This MQTT connection owns the network connection it created and - * should destroy it on cleanup. */ - ownNetworkConnection = true; - } - else - { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NETWORK_ERROR ); - } - } - else - { - pNetworkConnection = pNetworkInfo->u.pNetworkConnection; - networkCreated = true; - } - IotLogInfo( "Establishing new MQTT connection." ); /* Initialize a new MQTT connection object. */ @@ -1274,7 +1263,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, IotMqtt_strerror( status ) ); /* The network connection must be closed if it was created. */ - if( networkCreated == true ) + if( ownNetworkConnection == true ) { networkStatus = pNetworkInfo->pNetworkInterface->close( pNetworkConnection ); diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index af3ede95c5..5537534665 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -34,12 +34,28 @@ /* MQTT internal include. */ #include "private/iot_mqtt_internal.h" +/** + * @brief Check that an #IotMqttPublishInfo_t is valid. + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] maximumPayloadLength Maximum payload length. + * @param[in] pPublishTypeDescription String describing the publish type. + * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate. + * + * @return `true` if `pPublishInfo` is valid; `false` otherwise. + */ +static bool _validatePublish( bool awsIotMqttMode, + size_t maximumPayloadLength, + const char * pPublishTypeDescription, + const IotMqttPublishInfo_t * pPublishInfo ); + /*-----------------------------------------------------------*/ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { IOT_FUNCTION_ENTRY( bool, true ); - uint16_t maxClientIdLength = IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH; + uint16_t maxClientIdLength = MQTT_SERVER_MAX_CLIENTID_LENGTH; bool enforceMaxClientIdLength = false; /* Check for NULL. */ @@ -93,10 +109,11 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) { if( pConnectInfo->pPreviousSubscriptions != NULL ) { - if( pConnectInfo->previousSubscriptionCount == 0 ) + if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ) == false ) { - IotLogError( "Previous subscription count cannot be 0." ); - IOT_SET_AND_GOTO_CLEANUP( false ); } else @@ -114,6 +131,24 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) EMPTY_ELSE_MARKER; } + /* If will info is provided, check that it is valid. */ + if( pConnectInfo->pWillInfo != NULL ) + { + if( _IotMqtt_ValidateLwtPublish( pConnectInfo->awsIotMqttMode, + pConnectInfo->pWillInfo ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + /* The AWS IoT MQTT service enforces a client ID length limit. */ if( pConnectInfo->awsIotMqttMode == true ) { @@ -134,9 +169,11 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) else { IotLogError( "A client identifier length of %hu exceeds the " - "the maximum supported length of %hu.", + "maximum supported length of %hu.", pConnectInfo->clientIdentifierLength, maxClientIdLength ); + + IOT_SET_AND_GOTO_CLEANUP( false ); } } else @@ -148,9 +185,10 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) } /*-----------------------------------------------------------*/ - -bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, - const IotMqttPublishInfo_t * pPublishInfo ) +static bool _validatePublish( bool awsIotMqttMode, + size_t maximumPayloadLength, + const char * pPublishTypeDescription, + const IotMqttPublishInfo_t * pPublishInfo ) { IOT_FUNCTION_ENTRY( bool, true ); @@ -187,18 +225,29 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, EMPTY_ELSE_MARKER; } - /* Only allow NULL payloads with zero length. */ - if( pPublishInfo->pPayload == NULL ) + if( pPublishInfo->payloadLength != 0 ) { - if( pPublishInfo->payloadLength != 0 ) + if( pPublishInfo->payloadLength > maximumPayloadLength ) { - IotLogError( "Nonzero payload length cannot have a NULL payload." ); + IotLogError( "%s payload size of %zu exceeds maximum length of %zu.", + pPublishTypeDescription, + pPublishInfo->payloadLength, + maximumPayloadLength ); IOT_SET_AND_GOTO_CLEANUP( false ); } else { - EMPTY_ELSE_MARKER; + if( pPublishInfo->pPayload == NULL ) + { + IotLogError( "Nonzero payload length cannot have a NULL payload." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + else + { + EMPTY_ELSE_MARKER; + } } } else @@ -282,6 +331,39 @@ bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, /*-----------------------------------------------------------*/ +bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pPublishInfo ) +{ + size_t maximumPayloadLength = MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; + + if( awsIotMqttMode == true ) + { + maximumPayloadLength = AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH; + } + else + { + EMPTY_ELSE_MARKER; + } + + return _validatePublish( awsIotMqttMode, + maximumPayloadLength, + "Publish", + pPublishInfo ); +} + +/*-----------------------------------------------------------*/ + +bool _IotMqtt_ValidateLwtPublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pLwtPublishInfo ) +{ + return _validatePublish( awsIotMqttMode, + MQTT_SERVER_MAX_LWT_PAYLOAD_LENGTH, + "LWT", + pLwtPublishInfo ); +} + +/*-----------------------------------------------------------*/ + bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation ) { IOT_FUNCTION_ENTRY( bool, true ); diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 99b550b55e..596d3b5ba9 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -235,7 +235,9 @@ #define EMPTY_ELSE_MARKER #endif -#define IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 23 ) /**< @brief Optional maximum length of client identifier specified by MQTT 3.1.1. */ +#define MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 23 ) /**< @brief Optional maximum length of client identifier specified by MQTT 3.1.1. */ +#define MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 268435456 ) ) /**< @brief Maximum publish payload length supported by MQTT 3.1.1. */ +#define MQTT_SERVER_MAX_LWT_PAYLOAD_LENGTH ( ( size_t ) UINT16_MAX ) /**< @brief Maximum LWT payload length supported by MQTT 3.1.1. */ /* * Constants related to limits defined in AWS Service Limits. @@ -245,9 +247,10 @@ * * Used to validate parameters if when connecting to an AWS IoT MQTT server. */ -#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ -#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ +#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID_LENGTH ( ( uint16_t ) 128 ) /**< @brief Maximum length of client identifier accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH ( ( uint16_t ) 256 ) /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */ +#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE ( ( size_t ) 8 ) /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */ +#define AWS_IOT_MQTT_SERVER_MAX_PUBLISH_PAYLOAD_LENGTH ( ( size_t ) ( 131072 ) ) /**< @brief Maximum publish payload length accepted by AWS IoT. */ /* * MQTT control packet type and flags. Always the first byte of an MQTT @@ -483,6 +486,18 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ); bool _IotMqtt_ValidatePublish( bool awsIotMqttMode, const IotMqttPublishInfo_t * pPublishInfo ); +/** + * @brief Check that an #IotMqttPublishInfo_t is valid for an LWT publish + * + * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to + * an AWS IoT MQTT server. + * @param[in] pLwtPublishInfo The #IotMqttPublishInfo_t to validate. + * + * @return `true` if `pLwtPublishInfo` is valid; `false` otherwise. + */ +bool _IotMqtt_ValidateLwtPublish( bool awsIotMqttMode, + const IotMqttPublishInfo_t * pLwtPublishInfo ); + /** * @brief Check that an #IotMqttOperation_t is valid and waitable. * From a8b9c647d0f5db01d7ca1cfe5279bd1839fff9d0 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Wed, 23 Oct 2019 18:38:22 -0700 Subject: [PATCH 302/844] Correct some spelling errors found. (#614) --- .../standard/common/include/iot_taskpool.h | 24 +++++++++---------- .../common/include/types/iot_taskpool_types.h | 12 +++++----- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/libraries/standard/common/include/iot_taskpool.h b/libraries/standard/common/include/iot_taskpool.h index 441b20883a..e52ab728cd 100644 --- a/libraries/standard/common/include/iot_taskpool.h +++ b/libraries/standard/common/include/iot_taskpool.h @@ -110,7 +110,7 @@ IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * p /** * @brief Retrieves the one and only instance of a system task pool * - * This function retrieves the sytem task pool created with @ref IotTaskPool_CreateSystemTaskPool, and it is functionally + * This function retrieves the system task pool created with @ref IotTaskPool_CreateSystemTaskPool, and it is functionally * equivalent to using the shortcut @ref IOT_SYSTEM_TASKPOOL. * * @return The system task pool handle. @@ -127,7 +127,7 @@ IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ); /** * @brief Creates one instance of a task pool. * - * This function should be called by the user to initialiaze one instance of a task + * This function should be called by the user to initialize one instance of a task * pool. The task pool instance will be created around the storage pointed to by the `pTaskPool` * parameter. This function will create the minimum number of threads requested by the user * through an instance of the #IotTaskPoolInfo_t type specified with the `pInfo` parameter. @@ -138,7 +138,7 @@ IotTaskPool_t IotTaskPool_GetSystemTaskPool( void ); * @param[in] pInfo A pointer to the task pool initialization data. * @param[out] pTaskPool A pointer to the task pool handle to be used after initialization. * The pointer `pTaskPool` will hold a valid handle only if (@ref IotTaskPool_Create) - * completes succesfully. + * completes successfully. * * @return One of the following: * - #IOT_TASKPOOL_SUCCESS @@ -206,7 +206,7 @@ IotTaskPoolError_t IotTaskPool_SetMaxThreads( IotTaskPool_t taskPool, * @param[in] pUserContext A user-specified context for the callback. * @param[in,out] pJobStorage The storage for the job data structure. * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this - * function returns succesfully. This handle can be used to inspect the job status with + * function returns successfully. This handle can be used to inspect the job status with * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... * * @return One of the following: @@ -223,7 +223,7 @@ IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, /* @[declare_taskpool_createjob] */ /** - * brief Creates a job for the task pool by allocating the job dynamically. + * @brief Creates a job for the task pool by allocating the job dynamically. * * A recyclable job does not need to be allocated twice, but it can rather be reused through * subsequent calls to @ref IotTaskPool_CreateRecyclableJob. @@ -232,7 +232,7 @@ IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, * @param[in] userCallback A user-specified callback for the job. * @param[in] pUserContext A user-specified context for the callback. * @param[out] pJob A pointer to an instance of @ref IotTaskPoolJob_t that will be initialized when this - * function returns succesfully. This handle can be used to inspect the job status with + * function returns successfully. This handle can be used to inspect the job status with * @ref IotTaskPool_GetStatus or cancel the job with @ref IotTaskPool_TryCancel, etc.... * * @return One of the following: @@ -241,8 +241,6 @@ IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, * - #IOT_TASKPOOL_NO_MEMORY * - #IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS * - * @note This function will not allocate memory. - * * @warning A recyclable job should be recycled with a call to @ref IotTaskPool_RecycleJob rather than destroyed. * */ @@ -254,7 +252,7 @@ IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPool, /* @[declare_taskpool_createrecyclablejob] */ /** - * @brief This function uninitializes a job. + * @brief This function un-initializes a job. * * This function will destroy a job created with @ref IotTaskPool_CreateRecyclableJob. * A job should not be destroyed twice. A job that was previously scheduled but has not completed yet should not be destroyed, @@ -285,7 +283,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPool, /* @[declare_taskpool_destroyrecyclablejob] */ /** - * @brief Rrecycles a job into the task pool job cache. + * @brief Recycles a job into the task pool job cache. * * This function will try and recycle the job into the task pool cache. If the cache is full, * the job memory is destroyed as if the user called @ref IotTaskPool_DestroyRecyclableJob. The job should be recycled into @@ -311,7 +309,7 @@ IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPool, * * @warning This function should be used to recycle a job in the task pool cache when after the job executed. * Failing to call either this function or @ref IotTaskPool_DestroyRecyclableJob will result is a memory leak. Statically - * alloted jobs do not need to be recycled or destroyed. + * allocated jobs do not need to be recycled or destroyed. * */ /* @[declare_taskpool_recyclejob] */ @@ -495,7 +493,7 @@ IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPool, /** * @brief Returns a pointer to the job storage from an instance of a job handle - * of type @ref IotTaskPoolJob_t. This function is guarateed to succeed for a + * of type @ref IotTaskPoolJob_t. This function is guaranteed to succeed for a * valid job handle. * * @param[in] job The job handle. @@ -545,7 +543,7 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ); #endif /** - * @brief The maximum timeout i milliseconds to wait for a job to be scheduled before waking up a worker thread. + * @brief The maximum timeout in milliseconds to wait for a job to be scheduled before waking up a worker thread. * A worker thread that wakes up as a result of a timeout may exit to allow the task pool to fold back to its * minimum number of threads. */ diff --git a/libraries/standard/common/include/types/iot_taskpool_types.h b/libraries/standard/common/include/types/iot_taskpool_types.h index 26ff9b32b5..ac017ddf7b 100644 --- a/libraries/standard/common/include/types/iot_taskpool_types.h +++ b/libraries/standard/common/include/types/iot_taskpool_types.h @@ -70,7 +70,7 @@ typedef enum IotTaskPoolError IOT_TASKPOOL_SUCCESS = 0, /** - * @brief Task pool operation failed because at laest one parameter is invalid. + * @brief Task pool operation failed because at least one parameter is invalid. * * Functions that may return this value: * - @ref taskpool_function_createsystemtaskpool @@ -252,7 +252,7 @@ typedef struct _taskPoolJob * IotTaskPoolJob_t; * @brief Callback type for a user callback. * * This type identifies the user callback signature to execute a task pool job. This callback will be invoked - * by the task pool threads with the `pUserContext` parameter, as speficied by the user when + * by the task pool threads with the `pUserContext` parameter, as specified by the user when * calling @ref IotTaskPool_Schedule. * */ @@ -332,11 +332,11 @@ typedef struct IotTaskPoolInfo /** @brief Initializer for a typical #IotTaskPoolInfo_t. */ #define IOT_TASKPOOL_INFO_INITIALIZER IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM /** @brief Initializer for a #IotTaskPool_t. */ -#define IOT_TASKPOOL_INITIALIZER NULL +#define IOT_TASKPOOL_INITIALIZER NULL /** @brief Initializer for a #IotTaskPoolJobStorage_t. */ -#define IOT_TASKPOOL_JOB_STORAGE_INITIALIZER { { NULL, NULL }, NULL, NULL, 0, IOT_TASKPOOL_STATUS_UNDEFINED } +#define IOT_TASKPOOL_JOB_STORAGE_INITIALIZER { { NULL, NULL }, NULL, NULL, 0, IOT_TASKPOOL_STATUS_UNDEFINED } /** @brief Initializer for a #IotTaskPoolJob_t. */ -#define IOT_TASKPOOL_JOB_INITIALIZER NULL +#define IOT_TASKPOOL_JOB_INITIALIZER NULL /* @[define_taskpool_initializers] */ /** @@ -351,7 +351,7 @@ typedef struct IotTaskPoolInfo /** * @brief Allows the use of the handle to the system task pool. * - * @warning The task pool handle is not valid unless @ref IotTaskPool_CreateSystemTaskPool is + * @warning The system task pool handle is not valid unless @ref IotTaskPool_CreateSystemTaskPool is * called before the handle is used. */ #define IOT_SYSTEM_TASKPOOL ( IotTaskPool_GetSystemTaskPool() ) From 22f7ea674a6d8c0689aca69172491f900a18d8a8 Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Thu, 24 Oct 2019 15:30:01 -0700 Subject: [PATCH 303/844] Guard against multiple calls to IotMqtt_Init/IotMqtt_Cleanup (#613) --- libraries/standard/mqtt/src/iot_mqtt_api.c | 47 +++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 3e697c209a..1105f82a58 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -41,6 +41,9 @@ #include "platform/iot_clock.h" #include "platform/iot_threads.h" +/* Atomics include. */ +#include "iot_atomic.h" + /* Validate MQTT configuration settings. */ #if IOT_MQTT_ENABLE_ASSERTS != 0 && IOT_MQTT_ENABLE_ASSERTS != 1 #error "IOT_MQTT_ENABLE_ASSERTS must be 0 or 1." @@ -60,6 +63,18 @@ /*-----------------------------------------------------------*/ +/** + * @brief Uninitialized value for @ref _initCalled. + */ +#define MQTT_LIBRARY_UNINITIALIZED ( ( uint32_t ) 0 ) + +/** + * @brief Initialized value for @ref _initCalled. + */ +#define MQTT_LIBRARY_INITIALIZED ( ( uint32_t ) 1 ) + +/*-----------------------------------------------------------*/ + /** * @brief Check if the library is initialized. * @@ -243,7 +258,7 @@ static IotMqttError_t _subscriptionCommon( IotMqttOperationType_t operation, * * API functions will fail if @ref mqtt_function_init was not called. */ -static uint32_t _initCalled = 0; +static volatile uint32_t _initCalled = MQTT_LIBRARY_UNINITIALIZED; /*-----------------------------------------------------------*/ @@ -251,7 +266,7 @@ static bool _checkInit( void ) { bool status = true; - if( _initCalled == 0 ) + if( _initCalled == MQTT_LIBRARY_UNINITIALIZED ) { IotLogError( "IotMqtt_Init was not called." ); @@ -994,8 +1009,11 @@ void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection IotMqttError_t IotMqtt_Init( void ) { IotMqttError_t status = IOT_MQTT_SUCCESS; + uint32_t allowInitialization = Atomic_CompareAndSwap_u32( &_initCalled, + MQTT_LIBRARY_INITIALIZED, + MQTT_LIBRARY_UNINITIALIZED ); - if( _initCalled == 0 ) + if( allowInitialization == 1 ) { /* Call any additional serializer initialization function if serializer * overrides are enabled. */ @@ -1003,26 +1021,25 @@ IotMqttError_t IotMqtt_Init( void ) #ifdef _IotMqtt_InitSerializeAdditional if( _IotMqtt_InitSerializeAdditional() == false ) { + /* Log initialization status. */ + IotLogError( "Failed to initialize MQTT library serializer. " ); + status = IOT_MQTT_INIT_FAILED; } else { EMPTY_ELSE_MARKER; } - #endif + #endif /* ifdef _IotMqtt_InitSerializeAdditional */ #endif /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ - /* Log initialization status. */ - if( status != IOT_MQTT_SUCCESS ) + if( status == IOT_MQTT_SUCCESS ) { - IotLogError( "Failed to initialize MQTT library serializer. " ); + IotLogInfo( "MQTT library successfully initialized." ); } else { - /* Set the flag that specifies initialization is complete. */ - _initCalled = 1; - - IotLogInfo( "MQTT library successfully initialized." ); + EMPTY_ELSE_MARKER; } } else @@ -1037,10 +1054,12 @@ IotMqttError_t IotMqtt_Init( void ) void IotMqtt_Cleanup( void ) { - if( _initCalled == 1 ) - { - _initCalled = 0; + uint32_t allowCleanup = Atomic_CompareAndSwap_u32( &_initCalled, + MQTT_LIBRARY_UNINITIALIZED, + MQTT_LIBRARY_INITIALIZED ); + if( allowCleanup == 1 ) + { /* Call any additional serializer cleanup initialization function if serializer * overrides are enabled. */ #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 From 10ef34cac3e395ccf88d92578021f4eb500ca961 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Fri, 25 Oct 2019 10:53:07 -0700 Subject: [PATCH 304/844] CMakeList improvements (#615) --- CMakeLists.txt | 4 ++-- ports/posix/posix.cmake | 3 +++ third_party/tinycbor/CMakeLists.txt | 10 +++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0544ccfa4f..99244a2f07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,8 +103,8 @@ add_subdirectory( libraries/aws/defender ) # TinyCBOR library (third-party). add_subdirectory( third_party/tinycbor ) -# mbed TLS library (third-party). -if( NOT IOT_NETWORK_USE_OPENSSL ) +# mbed TLS library (third-party). This is only needed on ports using mbed TLS. +if( ${MBEDTLS_REQUIRED} ) add_subdirectory( third_party/mbedtls ) endif() diff --git a/ports/posix/posix.cmake b/ports/posix/posix.cmake index 11ca9ea319..3bfa109311 100644 --- a/ports/posix/posix.cmake +++ b/ports/posix/posix.cmake @@ -8,6 +8,8 @@ if( ${LIB_REALTIME} STREQUAL "LIB_REALTIME-NOTFOUND" ) message( FATAL_ERROR "POSIX realtime library (librt) is not available." ) endif() +unset( LIB_REALTIME CACHE ) + # Check for POSIX threads. find_package( Threads REQUIRED ) @@ -70,6 +72,7 @@ else() set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_openssl.h ) set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c ) set( PLATFORM_DEPENDENCIES mbedtls ) + set( MBEDTLS_REQUIRED TRUE PARENT_SCOPE ) endif() # Add the network header for this platform. diff --git a/third_party/tinycbor/CMakeLists.txt b/third_party/tinycbor/CMakeLists.txt index 51b9f1fc2f..4eaadb1823 100644 --- a/third_party/tinycbor/CMakeLists.txt +++ b/third_party/tinycbor/CMakeLists.txt @@ -38,7 +38,15 @@ add_library( tinycbor target_include_directories( tinycbor SYSTEM PUBLIC tinycbor/src ) -target_link_libraries( tinycbor PRIVATE m ) + +# Link libm (required for math functions on some systems) if available. +find_library( LIB_MATH m ) + +if( NOT ${LIB_MATH} STREQUAL "LIB_MATH-NOTFOUND" ) + target_link_libraries( tinycbor PRIVATE m ) +endif() + +unset( LIB_MATH CACHE ) # Disable all warnings for this third-party library. target_compile_options( tinycbor From e9d88f293e7848b4adf16e49fcfcff3e55d00f4f Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 30 Oct 2019 10:10:22 -0700 Subject: [PATCH 305/844] Group build artifacts in output directory (#616) --- CMakeLists.txt | 5 ++-- README.md | 2 +- doc/guide/building.txt | 4 +-- .../src/iot_network_openssl.c | 0 ports/posix/posix.cmake | 4 +-- scripts/ci_test_common.sh | 4 +-- scripts/ci_test_coverage.sh | 8 +++--- scripts/ci_test_jobs.sh | 4 +-- scripts/ci_test_mqtt.sh | 6 ++--- scripts/ci_test_shadow.sh | 6 ++--- tests/CMakeLists.txt | 27 ------------------- 11 files changed, 22 insertions(+), 48 deletions(-) rename ports/{posix => common}/src/iot_network_openssl.c (100%) delete mode 100644 tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 99244a2f07..630d7d891f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,8 +60,9 @@ if( NOT EXISTS ${PROJECT_SOURCE_DIR}/ports/${IOT_PLATFORM_NAME} ) endif() # Set output directories. -set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) -set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) +set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/bin ) +set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib ) +set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib ) # Set the path to the config header. if( ${IOT_BUILD_TESTS} ) diff --git a/README.md b/README.md index dbc1743468..ab8a1d5969 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T ```sh cmake .. ``` - CMake will generate a project based on the detected operating system. On Linux, the default project is a Makefile. To build the SDK with this Makefile, run `make`. + CMake will generate a project based on the detected operating system. On Linux, the default project is a Makefile. To build the SDK with this Makefile, run `make`. The resulting binaries (the demo executables) and libraries will be placed in the `build/output` directory. You may also use CMake GUI. Specify the SDK's root directory as the source directory and the build directory created in step 4 as the build directory in CMake GUI. diff --git a/doc/guide/building.txt b/doc/guide/building.txt index 1a0baebd47..aaeaeccde2 100644 --- a/doc/guide/building.txt +++ b/doc/guide/building.txt @@ -59,7 +59,7 @@ cmake .. make @endcode -Demo application executables will be placed in a `bin` directory of the CMake build directory, i.e. `build/bin` in the example above. The executables will be named `aws_iot_demo_library` or `iot_demo_library`, depending on whether the demo is specific to AWS IoT. For example, the MQTT demo application, which may be used with any MQTT server, will be named `iot_demo_mqtt`. The Thing Shadow demo, which is specific to AWS IoT, will be named `aws_iot_demo_shadow`. +Demo application executables will be placed in a `output/bin` directory of the CMake build directory, i.e. `build/output/bin` in the example above. The executables will be named `aws_iot_demo_library` or `iot_demo_library`, depending on whether the demo is specific to AWS IoT. For example, the MQTT demo application, which may be used with any MQTT server, will be named `iot_demo_mqtt`. The Thing Shadow demo, which is specific to AWS IoT, will be named `aws_iot_demo_shadow`. Configuration settings may also be set using CMake by setting `CMAKE_C_FLAGS`. However, placing configuration settings in `demos/iot_demo_config.h` is recommended over setting them using CMake. @code{sh} @@ -147,7 +147,7 @@ make The `IOT_BUILD_TESTS` option can also be set by a checkbox in CMake GUI. -Test executables will be placed in a `bin` directory of the CMake build directory (i.e. `build/bin`) alongside the demo applications. They will be named `aws_iot_tests_library` or `iot_tests_library`, depending on whether the tests are specific to AWS IoT. For example, the MQTT tests application, which may be used with any MQTT server, will be named `iot_tests_mqtt`. The Thing Shadow tests, which are specific to AWS IoT, will be named `aws_iot_tests_shadow`. +Test executables will be placed in a `output/bin` directory of the CMake build directory (i.e. `build/output/bin`) alongside the demo applications. They will be named `aws_iot_tests_library` or `iot_tests_library`, depending on whether the tests are specific to AWS IoT. For example, the MQTT tests application, which may be used with any MQTT server, will be named `iot_tests_mqtt`. The Thing Shadow tests, which are specific to AWS IoT, will be named `aws_iot_tests_shadow`. By default, running a test executable with no command line arguments will only run the basic unit tests and system tests. This run is expected to take up to a few minutes per library test. The command line option `-l` may be passed to a test executable to enable long-running tests (such as stress tests) which may run for several hours. The command line option `-n` may be passed to the test executable to disable tests that require network connectivity. */ diff --git a/ports/posix/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c similarity index 100% rename from ports/posix/src/iot_network_openssl.c rename to ports/common/src/iot_network_openssl.c diff --git a/ports/posix/posix.cmake b/ports/posix/posix.cmake index 3bfa109311..71f85a8f30 100644 --- a/ports/posix/posix.cmake +++ b/ports/posix/posix.cmake @@ -63,13 +63,13 @@ if( ${IOT_NETWORK_USE_OPENSSL} ) # Choose OpenSSL network source file. set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_openssl.h ) - set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/${IOT_PLATFORM_NAME}/src/iot_network_openssl.c ) + set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/common/src/iot_network_openssl.c ) # Link OpenSSL. set( PLATFORM_DEPENDENCIES OpenSSL::SSL OpenSSL::Crypto ) endif() else() - set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_openssl.h ) + set( NETWORK_HEADER ${PORTS_DIRECTORY}/common/include/iot_network_mbedtls.h ) set( NETWORK_SOURCE_FILE ${PORTS_DIRECTORY}/common/src/iot_network_mbedtls.c ) set( PLATFORM_DEPENDENCIES mbedtls ) set( MBEDTLS_REQUIRED TRUE PARENT_SCOPE ) diff --git a/scripts/ci_test_common.sh b/scripts/ci_test_common.sh index 3b9a4f6008..466a02d707 100755 --- a/scripts/ci_test_common.sh +++ b/scripts/ci_test_common.sh @@ -13,11 +13,11 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FL make -j2 iot_tests_common # Run common tests. -./bin/iot_tests_common +./output/bin/iot_tests_common # Rebuild in static memory mode. cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$CMAKE_FLAGS -DIOT_STATIC_MEMORY_ONLY=1" make -j2 iot_tests_common # Run common tests in static memory mode. -./bin/iot_tests_common +./output/bin/iot_tests_common diff --git a/scripts/ci_test_coverage.sh b/scripts/ci_test_coverage.sh index ac88b88509..9656ad8faf 100755 --- a/scripts/ci_test_coverage.sh +++ b/scripts/ci_test_coverage.sh @@ -36,18 +36,18 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="$AWS_IOT_ make -j2 # Run common tests with code coverage. -./bin/iot_tests_common +./output/bin/iot_tests_common # Run MQTT tests against AWS IoT with code coverage. -./bin/iot_tests_mqtt +./output/bin/iot_tests_mqtt # Run Shadow tests with code coverage. -./bin/aws_iot_tests_shadow +./output/bin/aws_iot_tests_shadow # Run Jobs tests with code coverage. create_jobs trap "delete_jobs" EXIT -./bin/aws_iot_tests_jobs +./output/bin/aws_iot_tests_jobs delete_jobs trap - EXIT diff --git a/scripts/ci_test_jobs.sh b/scripts/ci_test_jobs.sh index 3387d172b1..f62ddc9469 100644 --- a/scripts/ci_test_jobs.sh +++ b/scripts/ci_test_jobs.sh @@ -18,10 +18,10 @@ run_tests() { # For commit builds, run the full Jobs tests. For pull request builds, # run only the unit tests (credentials are not available for pull request builds). if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - ./bin/aws_iot_tests_jobs + ./output/bin/aws_iot_tests_jobs else # Run only Jobs unit tests. - ./bin/aws_iot_tests_jobs -n + ./output/bin/aws_iot_tests_jobs -n fi } diff --git a/scripts/ci_test_mqtt.sh b/scripts/ci_test_mqtt.sh index dd7af84150..e5dd2f586e 100755 --- a/scripts/ci_test_mqtt.sh +++ b/scripts/ci_test_mqtt.sh @@ -29,10 +29,10 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL= make -j2 iot_tests_mqtt iot_demo_mqtt -./bin/iot_tests_mqtt $TEST_OPTIONS +./output/bin/iot_tests_mqtt $TEST_OPTIONS if [ "$TRAVIS_OS_NAME" = "linux" ]; then - ./bin/iot_demo_mqtt $DEMO_OPTIONS + ./output/bin/iot_demo_mqtt $DEMO_OPTIONS fi # Rebuild and run tests in static memory mode. @@ -40,4 +40,4 @@ cmake .. -DIOT_BUILD_TESTS=1 -DCMAKE_BUILD_TYPE=Debug -DIOT_NETWORK_USE_OPENSSL= make -j2 iot_tests_mqtt iot_demo_mqtt -./bin/iot_tests_mqtt $TEST_OPTIONS +./output/bin/iot_tests_mqtt $TEST_OPTIONS diff --git a/scripts/ci_test_shadow.sh b/scripts/ci_test_shadow.sh index b29343ed51..780a5886c1 100755 --- a/scripts/ci_test_shadow.sh +++ b/scripts/ci_test_shadow.sh @@ -11,12 +11,12 @@ run_tests_and_demos() { # run only the unit tests (credentials are not available for pull request builds). # Sleep for 1.1 seconds between the runs to respect AWS service limits. if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - ./bin/aws_iot_tests_shadow + ./output/bin/aws_iot_tests_shadow sleep 1.1 - ./bin/aws_iot_demo_shadow + ./output/bin/aws_iot_demo_shadow else # Run only Shadow unit tests. - ./bin/aws_iot_tests_shadow -n + ./output/bin/aws_iot_tests_shadow -n fi } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index a71e4c753d..0000000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Test runner executable and config header. -set( IOT_TEST_APP_FILES - ${PROJECT_SOURCE_DIR}/tests/iot_config.h - ${PROJECT_SOURCE_DIR}/tests/iot_tests.c ) - -# MQTT mock sources. -set( MQTT_MOCK_SOURCES - ${PROJECT_SOURCE_DIR}/tests/mqtt/mock/iot_tests_mqtt_mock.c - ${PROJECT_SOURCE_DIR}/tests/mqtt/mock/iot_tests_mqtt_mock.h ) - -# Common tests. -add_subdirectory( common ) - -# Defender tests. -add_subdirectory( defender ) - -# MQTT tests. -add_subdirectory( mqtt ) - -# Serializer tests. -add_subdirectory( serializer ) - -# Shadow tests. -add_subdirectory( shadow ) - -# Jobs tests. -add_subdirectory( jobs ) From 456a942c65944fc46afb76a406695f84e88bec4b Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 30 Oct 2019 10:29:38 -0700 Subject: [PATCH 306/844] Move socket metrics to single file (#618) --- .../mqtt/test/unit/iot_tests_mqtt_validate.c | 2 +- ports/common/include/iot_network_mbedtls.h | 6 +- ports/common/include/iot_network_openssl.h | 6 +- ports/common/src/iot_network_mbedtls.c | 100 +------------- ports/common/src/iot_network_metrics.c | 126 ++++++++++++++++-- ports/common/src/iot_network_openssl.c | 86 +----------- 6 files changed, 129 insertions(+), 197 deletions(-) diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c index 9a1dd3e460..601cf450d4 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_validate.c @@ -133,7 +133,7 @@ TEST( MQTT_Unit_Validate, ValidateConnectInfo ) /* Client identifier longer than the MQTT 3.1.1 recommended maximum length. */ connectInfo.pClientIdentifier = "longlongclientidentifier"; - connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier ); + connectInfo.clientIdentifierLength = ( uint16_t ) strlen( connectInfo.pClientIdentifier ); validateStatus = _IotMqtt_ValidateConnect( &connectInfo ); TEST_ASSERT_EQUAL_INT( true, validateStatus ); diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h index 604bf622e1..64eaaec2f7 100644 --- a/ports/common/include/iot_network_mbedtls.h +++ b/ports/common/include/iot_network_mbedtls.h @@ -149,9 +149,9 @@ IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ); IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ); /** - * @brief Used by metrics to retrieve remote server and port of a connection. + * @brief Used by metrics to retrieve the socket (file descriptor) associated with + * a connection. */ -void IotNetworkMbedtls_GetServerInfo( void * pConnection, - IotMetricsTcpConnection_t * pServerInfo ); +int IotNetworkMbedtls_GetSocket( void * pConnection ); #endif /* ifndef IOT_NETWORK_MBEDTLS_H_ */ diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h index 2cc096fc91..1fffc0796b 100644 --- a/ports/common/include/iot_network_openssl.h +++ b/ports/common/include/iot_network_openssl.h @@ -165,9 +165,9 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ); IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); /** - * @brief Used by metrics to retrieve remote server and port of a connection. + * @brief Used by metrics to retrieve the socket (file descriptor) associated with + * a connection. */ -void IotNetworkOpenssl_GetServerInfo( void * pConnection, - IotMetricsTcpConnection_t * pServerInfo ); +int IotNetworkOpenssl_GetSocket( void * pConnection ); #endif /* ifndef IOT_NETWORK_OPENSSL_H_ */ diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index e3261b00c7..89a1b52b95 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -32,12 +32,6 @@ #include #include -#if !defined( _WIN32 ) && !defined( _WIN64 ) - #include - #include - #include -#endif - /* mbed TLS network include. */ #include "iot_network_mbedtls.h" @@ -1198,100 +1192,12 @@ IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ) /*-----------------------------------------------------------*/ -void IotNetworkMbedtls_GetServerInfo( void * pConnection, - IotMetricsTcpConnection_t * pServerInfo ) +int IotNetworkMbedtls_GetSocket( void * pConnection ) { - int status = 0, portLength = 0; - struct sockaddr_storage server = { 0 }; - socklen_t length = sizeof( struct sockaddr_storage ); - const void * pServerAddress = NULL; - char * pAddressStart = NULL; - const char * pPortFormat = NULL; - uint16_t remotePort = 0; - size_t addressLength = 0; - const _networkConnection_t * pNetworkConnection = NULL; - /* Cast function parameter to correct type. */ - if( ( pConnection != NULL ) && ( pServerInfo != NULL ) ) - { - pNetworkConnection = pConnection; - - /* Get peer info. */ - status = getpeername( pNetworkConnection->networkContext.fd, - ( struct sockaddr * ) &server, - &length ); - } - else - { - status = IOT_NETWORK_BAD_PARAMETER; - } - - if( status == 0 ) - { - /* Calculate the pointer to the IP address and get the remote port based - * on protocol version. */ - if( server.ss_family == AF_INET ) - { - /* IPv4. */ - pServerAddress = &( ( ( struct sockaddr_in * ) &server )->sin_addr ); - remotePort = ntohs( ( ( struct sockaddr_in * ) &server )->sin_port ); - - /* Print the IPv4 address at the start of the address buffer. */ - pAddressStart = pServerInfo->pRemoteAddress; - addressLength = IOT_METRICS_IP_ADDRESS_LENGTH; - pPortFormat = ":%hu"; - } - else - { - /* IPv6. */ - pServerAddress = &( ( ( struct sockaddr_in6 * ) &server )->sin6_addr ); - remotePort = ntohs( ( ( struct sockaddr_in6 * ) &server )->sin6_port ); - - /* Enclose the IPv6 address with []. */ - pServerInfo->pRemoteAddress[ 0 ] = '['; - pAddressStart = pServerInfo->pRemoteAddress + 1; - addressLength = IOT_METRICS_IP_ADDRESS_LENGTH - 1; - pPortFormat = "]:%hu"; - } - - /* Convert IP address to text. */ - if( inet_ntop( server.ss_family, - pServerAddress, - pAddressStart, - addressLength ) != NULL ) - { - /* Add the port to the end of the address. */ - addressLength = strlen( pServerInfo->pRemoteAddress ); - - portLength = snprintf( &( pServerInfo->pRemoteAddress[ addressLength ] ), - 7, - pPortFormat, - remotePort ); - - if( portLength > 0 ) - { - pServerInfo->addressLength = addressLength + ( size_t ) portLength; + _networkConnection_t * const pNetworkConnection = pConnection; - IotLogInfo( "(Socket %d) Collecting network metrics for %s.", - pNetworkConnection->networkContext.fd, - pServerInfo->pRemoteAddress ); - } - else - { - IotLogError( "(Socket %d) Failed to add port to IP address buffer." ); - } - } - else - { - IotLogError( "(Socket %d) Failed to convert IP address to text format.", - pNetworkConnection->networkContext.fd ); - } - } - else - { - IotLogError( "(Socket %d) Failed to query peer name.", - pNetworkConnection->networkContext.fd ); - } + return pNetworkConnection->networkContext.fd; } /*-----------------------------------------------------------*/ diff --git a/ports/common/src/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c index 788440f27d..066820fd77 100644 --- a/ports/common/src/iot_network_metrics.c +++ b/ports/common/src/iot_network_metrics.c @@ -30,6 +30,7 @@ /* Standard includes. */ #include +#include /* Platform threads include. */ #include "platform/iot_threads.h" @@ -37,6 +38,27 @@ /* Metrics networking include. */ #include "iot_network_metrics.h" +/* System headers for retrieving socket info. */ +#if !defined( _WIN32 ) && !defined( _WIN64 ) + #include + #include + #include +#endif + +/* Configure logs for the functions in this file. */ +#ifdef IOT_LOG_LEVEL_NETWORK + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_NETWORK +#else + #ifdef IOT_LOG_LEVEL_GLOBAL + #define LIBRARY_LOG_LEVEL IOT_LOG_LEVEL_GLOBAL + #else + #define LIBRARY_LOG_LEVEL IOT_LOG_NONE + #endif +#endif + +#define LIBRARY_LOG_NAME ( "NET" ) +#include "iot_logging_setup.h" + /* * Provide default values for undefined memory allocation functions. */ @@ -115,10 +137,9 @@ static IotMutex_t _connectionListMutex; static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkOpenssl_Close; /** - * @brief Pointer to the function that retrieves the server info for a connection. + * @brief Pointer to the function that retrieves the socket for a connection. */ - static void ( * _networkServerInfo )( void *, - IotMetricsTcpConnection_t * ) = IotNetworkOpenssl_GetServerInfo; + static int ( * _getSocket )( void * ) = IotNetworkOpenssl_GetSocket; /** * @brief An #IotNetworkInterface_t that wraps network abstraction functions with @@ -148,10 +169,9 @@ static IotMutex_t _connectionListMutex; static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkMbedtls_Close; /** - * @brief Pointer to the function that retrieves the server info for a connection. + * @brief Pointer to the function that retrieves the socket for a connection. */ - static void ( * _networkServerInfo )( void *, - IotMetricsTcpConnection_t * ) = IotNetworkMbedtls_GetServerInfo; + static int ( * _getSocket )( void * ) = IotNetworkMbedtls_GetSocket; /** * @brief An #IotNetworkInterface_t that wraps network abstraction functions with @@ -184,10 +204,98 @@ static bool _connectionMatch( const IotLink_t * pConnectionLink, /*-----------------------------------------------------------*/ +static void _getServerInfo( int socket, + IotMetricsTcpConnection_t * pServerInfo ) +{ + int status = 0, portLength = 0; + struct sockaddr_storage server = { 0 }; + socklen_t length = sizeof( struct sockaddr_storage ); + const void * pServerAddress = NULL; + char * pAddressStart = NULL; + const char * pPortFormat = NULL; + uint16_t remotePort = 0; + size_t addressLength = 0; + + /* Get peer info. */ + status = getpeername( socket, + ( struct sockaddr * ) &server, + &length ); + + if( status == 0 ) + { + /* Calculate the pointer to the IP address and get the remote port based + * on protocol version. */ + if( server.ss_family == AF_INET ) + { + /* IPv4. */ + pServerAddress = &( ( ( struct sockaddr_in * ) &server )->sin_addr ); + remotePort = ntohs( ( ( struct sockaddr_in * ) &server )->sin_port ); + + /* Print the IPv4 address at the start of the address buffer. */ + pAddressStart = pServerInfo->pRemoteAddress; + addressLength = IOT_METRICS_IP_ADDRESS_LENGTH; + pPortFormat = ":%hu"; + } + else + { + /* IPv6. */ + pServerAddress = &( ( ( struct sockaddr_in6 * ) &server )->sin6_addr ); + remotePort = ntohs( ( ( struct sockaddr_in6 * ) &server )->sin6_port ); + + /* Enclose the IPv6 address with []. */ + pServerInfo->pRemoteAddress[ 0 ] = '['; + pAddressStart = pServerInfo->pRemoteAddress + 1; + addressLength = IOT_METRICS_IP_ADDRESS_LENGTH - 1; + pPortFormat = "]:%hu"; + } + + /* Convert IP address to text. */ + if( inet_ntop( server.ss_family, + pServerAddress, + pAddressStart, + addressLength ) != NULL ) + { + /* Add the port to the end of the address. */ + addressLength = strlen( pServerInfo->pRemoteAddress ); + + portLength = snprintf( &( pServerInfo->pRemoteAddress[ addressLength ] ), + 7, + pPortFormat, + remotePort ); + + if( portLength > 0 ) + { + pServerInfo->addressLength = addressLength + ( size_t ) portLength; + + IotLogInfo( "(Socket %d) Collecting network metrics for %s.", + socket, + pServerInfo->pRemoteAddress ); + } + else + { + IotLogError( "(Socket %d) Failed to add port to IP address buffer." ); + } + } + else + { + IotLogError( "(Socket %d) Failed to convert IP address to text format.", + socket ); + } + } + else + { + IotLogError( "(Socket %d) Failed to query peer name.", + socket ); + } +} + +/*-----------------------------------------------------------*/ + static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, void * pCredentialInfo, void ** pConnection ) { + int socket = 0; IotMetricsTcpConnection_t * pTcpConnection = NULL; IotNetworkError_t status = IOT_NETWORK_SUCCESS; @@ -206,11 +314,11 @@ static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, /* Add the new metrics connection to the list of connections. */ if( status == IOT_NETWORK_SUCCESS ) { - /* Get the connection's IPv4 address and port. */ pTcpConnection->pNetworkContext = *pConnection; - _networkServerInfo( pTcpConnection->pNetworkContext, - pTcpConnection ); + /* Get the connection's address and port. */ + socket = _getSocket( pTcpConnection->pNetworkContext ); + _getServerInfo( socket, pTcpConnection ); /* Add the new connection to the list of connections. */ IotMutex_Lock( &_connectionListMutex ); diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index 39ce7144e0..2d98bdc644 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -30,7 +30,6 @@ /* Standard includes. */ #include -#include #include #include @@ -1047,93 +1046,12 @@ IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) /*-----------------------------------------------------------*/ -void IotNetworkOpenssl_GetServerInfo( void * pConnection, - IotMetricsTcpConnection_t * pServerInfo ) +int IotNetworkOpenssl_GetSocket( void * pConnection ) { - int status = 0, portLength = 0; - struct sockaddr_storage server = { 0 }; - socklen_t length = sizeof( struct sockaddr_storage ); - const void * pServerAddress = NULL; - char * pAddressStart = NULL; - const char * pPortFormat = NULL; - uint16_t remotePort = 0; - size_t addressLength = 0; - /* Cast function parameter to correct type. */ _networkConnection_t * const pNetworkConnection = pConnection; - /* Get peer info. */ - status = getpeername( pNetworkConnection->socket, - ( struct sockaddr * ) &server, - &length ); - - if( status == 0 ) - { - /* Calculate the pointer to the IP address and get the remote port based - * on protocol version. */ - if( server.ss_family == AF_INET ) - { - /* IPv4. */ - pServerAddress = &( ( ( struct sockaddr_in * ) &server )->sin_addr ); - remotePort = ntohs( ( ( struct sockaddr_in * ) &server )->sin_port ); - - /* Print the IPv4 address at the start of the address buffer. */ - pAddressStart = pServerInfo->pRemoteAddress; - addressLength = IOT_METRICS_IP_ADDRESS_LENGTH; - pPortFormat = ":%hu"; - } - else - { - /* IPv6. */ - pServerAddress = &( ( ( struct sockaddr_in6 * ) &server )->sin6_addr ); - remotePort = ntohs( ( ( struct sockaddr_in6 * ) &server )->sin6_port ); - - /* Enclose the IPv6 address with []. */ - pServerInfo->pRemoteAddress[ 0 ] = '['; - pAddressStart = pServerInfo->pRemoteAddress + 1; - addressLength = IOT_METRICS_IP_ADDRESS_LENGTH - 1; - pPortFormat = "]:%hu"; - } - - /* Convert IP address to text. */ - if( inet_ntop( server.ss_family, - pServerAddress, - pAddressStart, - addressLength ) != NULL ) - { - /* Add the port to the end of the address. */ - addressLength = strlen( pServerInfo->pRemoteAddress ); - - portLength = snprintf( &( pServerInfo->pRemoteAddress[ addressLength ] ), - 7, - pPortFormat, - remotePort ); - - if( portLength > 0 ) - { - pServerInfo->addressLength = addressLength + ( size_t ) portLength; - - IotLogInfo( "(Socket %d) Collecting network metrics for %s.", - pNetworkConnection->socket, - pServerInfo->pRemoteAddress ); - } - else - { - IotLogError( "(Socket %d) Failed to add port to IP address buffer." ); - } - } - else - { - IotLogError( "(Socket %d) Failed to convert IP address to text format.", - pNetworkConnection->socket ); - } - } - else - { - IotLogError( "(Socket %d) Failed to query peer name. errno=%d.", - pNetworkConnection->socket, - errno ); - } + return pNetworkConnection->socket; } /*-----------------------------------------------------------*/ From 791cc27ae9847892072e5bf9cfeee220b359fb8a Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Wed, 30 Oct 2019 14:08:14 -0700 Subject: [PATCH 307/844] Update MQTT System tests for connection retries (#620) * Update MQTT system tests for connection retry. - In Amazon FreeRTOS the number of retries is set to 3. - In the CSDK the number of retries is the default of 1, since there are no failures in these tests' connections. - Update doxygen for new test config IOT_TEST_MQTT_CONNECT_RETRY_COUNT --- doc/lib/mqtt.txt | 10 ++ .../mqtt/test/system/iot_tests_mqtt_system.c | 122 ++++++++++++------ 2 files changed, 92 insertions(+), 40 deletions(-) diff --git a/doc/lib/mqtt.txt b/doc/lib/mqtt.txt index b349a11017..1b75453d49 100644 --- a/doc/lib/mqtt.txt +++ b/doc/lib/mqtt.txt @@ -154,6 +154,16 @@ This value will be passed as `timeoutMs` to all calls to @ref mqtt_function_wait @configpossible Any non-negative integer.
@configrecommended This setting should be at least `1000`.
@configdefault `5000` + +@section IOT_TEST_MQTT_CONNECT_RETRY_COUNT +@brief The number of connection attempts for the MQTT system tests. + +The MQTT system tests require a network connection to an MQTT server. If the network is unreliable, this setting may be used to enable retries when connecting to the MQTT server. + +This value represents a limit on the number of retries. When set to 1, the tests will perform one connection attempt. When set to a value greater than 1, the tests will attempt to reconnect with an exponential back-off strategy, up to the limit specified by this setting. + +@configpossible Any integer of at least 1.
+@configdefault `1` */ /** diff --git a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c index e43d066ca0..bba665aa09 100644 --- a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c +++ b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c @@ -59,8 +59,15 @@ #ifndef IOT_TEST_MQTT_TIMEOUT_MS #define IOT_TEST_MQTT_TIMEOUT_MS ( 5000 ) #endif +#ifndef IOT_TEST_MQTT_CONNECT_RETRY_COUNT + #define IOT_TEST_MQTT_CONNECT_RETRY_COUNT ( 1 ) +#endif /** @endcond */ +#if IOT_TEST_MQTT_CONNECT_RETRY_COUNT < 1 + #error "IOT_TEST_MQTT_CONNECT_RETRY_COUNT must be at least 1." +#endif + /** * @brief Determine which MQTT server mode to test (AWS IoT or Mosquitto). */ @@ -220,6 +227,41 @@ static bool _disconnectSerializerOverride = false; /**< @brief Tracks if #_disc /*-----------------------------------------------------------*/ +/** + * @brief Establish an MQTT connection. Retry if enabled. + */ +static IotMqttError_t _mqttConnect( const IotMqttNetworkInfo_t * pNetworkInfo, + const IotMqttConnectInfo_t * pConnectInfo, + uint32_t timeoutMs, + IotMqttConnection_t * const pMqttConnection ) +{ + IotMqttError_t status = IOT_MQTT_STATUS_PENDING; + int32_t retryCount = 0; + + for( ; retryCount < IOT_TEST_MQTT_CONNECT_RETRY_COUNT; retryCount++ ) + { + status = IotMqtt_Connect( pNetworkInfo, pConnectInfo, timeoutMs, pMqttConnection ); + + #if ( IOT_TEST_MQTT_CONNECT_RETRY_COUNT > 1 ) + if( ( status == IOT_MQTT_NETWORK_ERROR ) || ( status == IOT_MQTT_TIMEOUT ) ) + { + /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. + * Initially wait until 1100 ms have elapsed since the last connection, then + * increase exponentially. */ + IotClock_SleepMs( 1100 << retryCount ); + } + else + { + break; + } + #endif + } + + return status; +} + +/*-----------------------------------------------------------*/ + /** * @brief Packet free function override */ @@ -508,10 +550,10 @@ static void _subscribePublishWait( IotMqttQos_t qos ) connectInfo.clientIdentifierLength = ( uint16_t ) strlen( _pClientIdentifier ); /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -764,10 +806,10 @@ TEST( MQTT_System, SubscribePublishAsync ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -886,10 +928,10 @@ TEST( MQTT_System, LastWillAndTestament ) if( TEST_PROTECT() ) { - status = IotMqtt_Connect( &lwtNetworkInfo, - &lwtConnectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &lwtListener ); + status = _mqttConnect( &lwtNetworkInfo, + &lwtConnectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &lwtListener ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -919,10 +961,10 @@ TEST( MQTT_System, LastWillAndTestament ) willInfo.pPayload = _pSamplePayload; willInfo.payloadLength = _samplePayloadLength; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Abruptly close the MQTT connection. This should cause the LWT @@ -970,10 +1012,10 @@ TEST( MQTT_System, RestorePreviousSession ) if( TEST_PROTECT() ) { /* Establish a persistent MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Add a subscription. */ @@ -998,10 +1040,10 @@ TEST( MQTT_System, RestorePreviousSession ) connectInfo.cleanSession = false; connectInfo.pPreviousSubscriptions = &subscription; connectInfo.previousSubscriptionCount = 1; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Publish a message to the subscription added in the previous session. */ @@ -1044,10 +1086,10 @@ TEST( MQTT_System, RestorePreviousSession ) * session to clean up persistent sessions on the MQTT server created by this * test. */ connectInfo.cleanSession = true; - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); if( status == IOT_MQTT_SUCCESS ) { @@ -1087,10 +1129,10 @@ TEST( MQTT_System, WaitAfterDisconnect ) publishInfo.retryMs = 5000; /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); if( TEST_PROTECT() ) @@ -1158,10 +1200,10 @@ TEST( MQTT_System, SubscribeCompleteReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with a completion callback. */ @@ -1234,10 +1276,10 @@ TEST( MQTT_System, IncomingPublishReentrancy ) if( TEST_PROTECT() ) { /* Establish the MQTT connection. */ - status = IotMqtt_Connect( &_networkInfo, - &connectInfo, - IOT_TEST_MQTT_TIMEOUT_MS, - &_mqttConnection ); + status = _mqttConnect( &_networkInfo, + &connectInfo, + IOT_TEST_MQTT_TIMEOUT_MS, + &_mqttConnection ); TEST_ASSERT_EQUAL( IOT_MQTT_SUCCESS, status ); /* Subscribe with to the test topics. */ From 4ac3711a308756bd209a23980df7fbfb55db26eb Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Wed, 30 Oct 2019 15:13:30 -0700 Subject: [PATCH 308/844] Use single MQTT connection in Shadow and Jobs tests (#621) --- .../aws/jobs/src/aws_iot_jobs_subscription.c | 4 +- .../test/system/aws_iot_tests_jobs_system.c | 128 +++++++------- .../shadow/src/aws_iot_shadow_subscription.c | 4 +- .../test/system/aws_iot_tests_shadow_system.c | 157 +++++++++--------- 4 files changed, 150 insertions(+), 143 deletions(-) diff --git a/libraries/aws/jobs/src/aws_iot_jobs_subscription.c b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c index 8487757679..d5cb9d6212 100644 --- a/libraries/aws/jobs/src/aws_iot_jobs_subscription.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_subscription.c @@ -551,8 +551,10 @@ AwsIotJobsError_t AwsIotJobs_RemovePersistentSubscriptions( const AwsIotJobsRequ break; } - /* Clear the persistent subscriptions flag. */ + /* Clear the persistent subscriptions flag and check if the + * subscription can be removed. */ pSubscription->operationReferences[ i ] = 0; + _AwsIotJobs_RemoveSubscription( pSubscription, NULL ); } else { diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c index 88d828b127..f3ad56d67d 100644 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c @@ -111,11 +111,6 @@ static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_IN static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; #endif -/** - * @brief An MQTT network setup parameter to share among the tests. - */ -static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - /** * @brief An MQTT connection to share among the tests. */ @@ -492,57 +487,33 @@ static void _jobsBlockingTest( _jobsOperationType_t type, /*-----------------------------------------------------------*/ /** - * @brief Test group for Jobs system tests. - */ -TEST_GROUP( Jobs_System ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Jobs system tests. + * @brief Initializes libraries and establishes an MQTT connection for the Jobs tests. */ -TEST_SETUP( Jobs_System ) +static void _setupJobsTests() { - static uint64_t lastConnectTime = 0; - uint64_t elapsedTime = 0; + int32_t i = 0; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - /* Initialize SDK. */ - if( IotSdk_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); - } - - /* Set up the network stack. */ - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to set up network stack." ); - } - - /* Initialize the MQTT library. */ - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - - /* Initialize the Jobs library. */ - if( AwsIotJobs_Init( 0 ) != AWS_IOT_JOBS_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize Jobs library." ); - } + /* Initialize SDK and libraries. */ + AwsIotJobs_Assert( IotSdk_Init() == true ); + AwsIotJobs_Assert( IotTestNetwork_Init() == IOT_NETWORK_SUCCESS ); + AwsIotJobs_Assert( IotMqtt_Init() == IOT_MQTT_SUCCESS ); + AwsIotJobs_Assert( AwsIotJobs_Init( 0 ) == AWS_IOT_JOBS_SUCCESS ); /* Set the MQTT network setup parameters. */ - ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - _networkInfo.createNetworkConnection = true; - _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + ( void ) memset( &networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); + networkInfo.createNetworkConnection = true; + networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; + networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; + networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; #endif #ifdef IOT_TEST_MQTT_SERIALIZER - _networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; #endif /* Set the members of the connect info. Use the Jobs Thing Name as the MQTT @@ -552,34 +523,34 @@ TEST_SETUP( Jobs_System ) connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_JOBS_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. - * Wait until 1100 ms have elapsed since the last connection. */ - elapsedTime = IotClock_GetTimeMs() - lastConnectTime; - - if( elapsedTime < 1100ULL ) + /* Establish an MQTT connection. Allow up to 3 attempts with a 5 second wait + * if the connection fails. */ + for( i = 0; i < 3; i++ ) { - IotClock_SleepMs( 1100UL - ( uint32_t ) elapsedTime ); - } + connectStatus = IotMqtt_Connect( &networkInfo, + &connectInfo, + AWS_IOT_TEST_JOBS_TIMEOUT, + &_mqttConnection ); - /* Establish an MQTT connection. */ - if( IotMqtt_Connect( &_networkInfo, - &connectInfo, - AWS_IOT_TEST_JOBS_TIMEOUT, - &_mqttConnection ) != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Jobs tests." ); + if( ( connectStatus == IOT_MQTT_TIMEOUT ) || ( connectStatus == IOT_MQTT_NETWORK_ERROR ) ) + { + IotClock_SleepMs( 5000 ); + } + else + { + break; + } } - /* Update the time of the last MQTT connect. */ - lastConnectTime = IotClock_GetTimeMs(); + AwsIotJobs_Assert( connectStatus == IOT_MQTT_SUCCESS ); } /*-----------------------------------------------------------*/ /** - * @brief Test tear down for Jobs system tests. + * @brief Cleans up libraries and closes the MQTT connection for the Jobs tests. */ -TEST_TEAR_DOWN( Jobs_System ) +static void _cleanupJobsTests() { /* Disconnect the MQTT connection if it was created. */ if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) @@ -603,11 +574,40 @@ TEST_TEAR_DOWN( Jobs_System ) /*-----------------------------------------------------------*/ +/** + * @brief Test group for Jobs system tests. + */ +TEST_GROUP( Jobs_System ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Jobs system tests. + */ +TEST_SETUP( Jobs_System ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Jobs system tests. + */ +TEST_TEAR_DOWN( Jobs_System ) +{ + /* Cool down time to avoid making too many requests. */ + IotClock_SleepMs( 100 ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group runner for Jobs system tests. */ TEST_GROUP_RUNNER( Jobs_System ) { + _setupJobsTests(); + /* The tests for Get Pending must run first, as they retrieve the list of * Jobs for the other tests. */ RUN_TEST_CASE( Jobs_System, GetPendingAsync ); @@ -626,6 +626,8 @@ TEST_GROUP_RUNNER( Jobs_System ) } RUN_TEST_CASE( Jobs_System, PersistentSubscriptions ); + + _cleanupJobsTests(); } /*-----------------------------------------------------------*/ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index 46c0ccae08..f2d10691b4 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -508,8 +508,10 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio break; } - /* Clear the persistent subscriptions flag. */ + /* Clear the persistent subscriptions flag and check if the + * subscription can be removed. */ pSubscription->references[ i ] = 0; + _AwsIotShadow_RemoveSubscription( pSubscription, NULL ); } else { diff --git a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c index 7c45d54f29..bf142e3de1 100644 --- a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c +++ b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c @@ -123,11 +123,6 @@ static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_IN static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; #endif -/** - * @brief An MQTT network setup parameter to share among the tests. - */ -static IotMqttNetworkInfo_t _networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; - /** * @brief An MQTT connection to share among the tests. */ @@ -421,58 +416,32 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) /*-----------------------------------------------------------*/ /** - * @brief Test group for Shadow system tests. - */ -TEST_GROUP( Shadow_System ); - -/*-----------------------------------------------------------*/ - -/** - * @brief Test setup for Shadow system tests. + * @brief Initializes libraries and establishes an MQTT connection for the Shadow tests. */ -TEST_SETUP( Shadow_System ) +static void _setupShadowTests() { - static uint64_t lastConnectTime = 0; - uint64_t elapsedTime = 0; + int32_t i = 0; + IotMqttError_t connectStatus = IOT_MQTT_STATUS_PENDING; + IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; - AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; - - /* Initialize SDK. */ - if( IotSdk_Init() == false ) - { - TEST_FAIL_MESSAGE( "Failed to initialize SDK." ); - } - /* Set up the network stack. */ - if( IotTestNetwork_Init() != IOT_NETWORK_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to set up network stack." ); - } - - /* Initialize the MQTT library. */ - if( IotMqtt_Init() != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize MQTT library." ); - } - - /* Initialize the Shadow library. */ - if( AwsIotShadow_Init( 0 ) != AWS_IOT_SHADOW_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to initialize Shadow library." ); - } + /* Initialize SDK and libraries. */ + AwsIotShadow_Assert( IotSdk_Init() == true ); + AwsIotShadow_Assert( IotTestNetwork_Init() == IOT_NETWORK_SUCCESS ); + AwsIotShadow_Assert( IotMqtt_Init() == IOT_MQTT_SUCCESS ); + AwsIotShadow_Assert( AwsIotShadow_Init( 0 ) == AWS_IOT_SHADOW_SUCCESS ); /* Set the MQTT network setup parameters. */ - ( void ) memset( &_networkInfo, 0x00, sizeof( IotMqttNetworkInfo_t ) ); - _networkInfo.createNetworkConnection = true; - _networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; - _networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; + networkInfo.createNetworkConnection = true; + networkInfo.u.setup.pNetworkServerInfo = ( void * ) &_serverInfo; + networkInfo.pNetworkInterface = IOT_TEST_NETWORK_INTERFACE; #if IOT_TEST_SECURED_CONNECTION == 1 - _networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; + networkInfo.u.setup.pNetworkCredentialInfo = ( void * ) &_credentials; #endif #ifdef IOT_TEST_MQTT_SERIALIZER - _networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; + networkInfo.pMqttSerializer = IOT_TEST_MQTT_SERIALIZER; #endif /* Set the members of the connect info. Use the Shadow Thing Name as the MQTT @@ -482,48 +451,34 @@ TEST_SETUP( Shadow_System ) connectInfo.clientIdentifierLength = ( uint16_t ) ( sizeof( AWS_IOT_TEST_SHADOW_THING_NAME ) - 1 ); connectInfo.keepAliveSeconds = IOT_TEST_MQTT_SHORT_KEEPALIVE_INTERVAL_S; - /* AWS IoT Service limits only allow 1 connection per MQTT client ID per second. - * Wait until 1100 ms have elapsed since the last connection. */ - elapsedTime = IotClock_GetTimeMs() - lastConnectTime; - - if( elapsedTime < 1100ULL ) + /* Establish an MQTT connection. Allow up to 3 attempts with a 5 second wait + * if the connection fails. */ + for( i = 0; i < 3; i++ ) { - IotClock_SleepMs( 1100UL - ( uint32_t ) elapsedTime ); - } + connectStatus = IotMqtt_Connect( &networkInfo, + &connectInfo, + AWS_IOT_TEST_SHADOW_TIMEOUT, + &_mqttConnection ); - /* Establish an MQTT connection. */ - if( IotMqtt_Connect( &_networkInfo, - &connectInfo, - AWS_IOT_TEST_SHADOW_TIMEOUT, - &_mqttConnection ) != IOT_MQTT_SUCCESS ) - { - TEST_FAIL_MESSAGE( "Failed to establish MQTT connection for Shadow tests." ); + if( ( connectStatus == IOT_MQTT_TIMEOUT ) || ( connectStatus == IOT_MQTT_NETWORK_ERROR ) ) + { + IotClock_SleepMs( 5000 ); + } + else + { + break; + } } - /* Update the time of the last MQTT connect. */ - lastConnectTime = IotClock_GetTimeMs(); - - /* Delete any existing Shadow so all tests start with no Shadow. */ - status = AwsIotShadow_DeleteSync( _mqttConnection, - AWS_IOT_TEST_SHADOW_THING_NAME, - THING_NAME_LENGTH, - 0, - AWS_IOT_TEST_SHADOW_TIMEOUT ); - - /* Acceptable statuses are SUCCESS and NOT FOUND. Both of these statuses allow - * the tests to start with no Shadow. */ - if( ( status != AWS_IOT_SHADOW_SUCCESS ) && ( status != AWS_IOT_SHADOW_NOT_FOUND ) ) - { - TEST_FAIL_MESSAGE( "Failed to delete shadow in test set up." ); - } + AwsIotShadow_Assert( connectStatus == IOT_MQTT_SUCCESS ); } /*-----------------------------------------------------------*/ /** - * @brief Test tear down for Shadow system tests. + * @brief Cleans up libraries and closes the MQTT connection for the Shadow tests. */ -TEST_TEAR_DOWN( Shadow_System ) +static void _cleanupShadowTests() { /* Disconnect the MQTT connection if it was created. */ if( _mqttConnection != IOT_MQTT_CONNECTION_INITIALIZER ) @@ -547,17 +502,63 @@ TEST_TEAR_DOWN( Shadow_System ) /*-----------------------------------------------------------*/ +/** + * @brief Test group for Shadow system tests. + */ +TEST_GROUP( Shadow_System ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Shadow system tests. + */ +TEST_SETUP( Shadow_System ) +{ + AwsIotShadowError_t status = AWS_IOT_SHADOW_STATUS_PENDING; + + /* Delete any existing Shadow so all tests start with no Shadow. */ + status = AwsIotShadow_DeleteSync( _mqttConnection, + AWS_IOT_TEST_SHADOW_THING_NAME, + THING_NAME_LENGTH, + 0, + AWS_IOT_TEST_SHADOW_TIMEOUT ); + + /* Acceptable statuses are SUCCESS and NOT FOUND. Both of these statuses allow + * the tests to start with no Shadow. */ + if( ( status != AWS_IOT_SHADOW_SUCCESS ) && ( status != AWS_IOT_SHADOW_NOT_FOUND ) ) + { + TEST_FAIL_MESSAGE( "Failed to delete shadow in test set up." ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Shadow system tests. + */ +TEST_TEAR_DOWN( Shadow_System ) +{ + /* Cool down time to avoid making too many requests. */ + IotClock_SleepMs( 100 ); +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group runner for Shadow system tests. */ TEST_GROUP_RUNNER( Shadow_System ) { + _setupShadowTests(); + RUN_TEST_CASE( Shadow_System, UpdateGetDeleteAsyncQoS0 ); RUN_TEST_CASE( Shadow_System, UpdateGetDeleteAsyncQoS1 ); RUN_TEST_CASE( Shadow_System, UpdateGetDeleteBlockingQoS0 ); RUN_TEST_CASE( Shadow_System, UpdateGetDeleteBlockingQoS1 ); RUN_TEST_CASE( Shadow_System, DeltaCallback ); RUN_TEST_CASE( Shadow_System, UpdatedCallback ); + + _cleanupShadowTests(); } /*-----------------------------------------------------------*/ From 731dda0af1e11acdb55b0e68aaf7265e5eb36880 Mon Sep 17 00:00:00 2001 From: Gordon Wang <36049150+gordonwang0@users.noreply.github.com> Date: Thu, 31 Oct 2019 13:51:02 -0700 Subject: [PATCH 309/844] Remove void* network pointers (#622) --- demos/app/iot_demo.c | 4 +- .../system/aws_iot_tests_defender_system.c | 6 +- .../test/system/aws_iot_tests_jobs_system.c | 4 +- .../test/system/aws_iot_tests_shadow_system.c | 4 +- libraries/platform/iot_network.h | 37 +-- libraries/platform/types/iot_platform_types.h | 59 +++- libraries/standard/mqtt/include/iot_mqtt.h | 2 +- .../mqtt/include/types/iot_mqtt_types.h | 8 +- libraries/standard/mqtt/src/iot_mqtt_api.c | 6 +- .../standard/mqtt/src/iot_mqtt_network.c | 4 +- .../mqtt/test/mock/iot_tests_mqtt_mock.c | 12 +- .../mqtt/test/system/iot_tests_mqtt_system.c | 4 +- .../mqtt/test/unit/iot_tests_mqtt_api.c | 14 +- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 18 +- ports/common/include/iot_network_mbedtls.h | 28 +- ports/common/include/iot_network_openssl.h | 50 ++- ports/common/src/iot_network_mbedtls.c | 289 ++++++++---------- ports/common/src/iot_network_metrics.c | 34 ++- ports/common/src/iot_network_openssl.c | 209 ++++++------- .../posix/include/iot_platform_types_posix.h | 26 ++ tests/iot_config.h | 5 - 21 files changed, 416 insertions(+), 407 deletions(-) diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 09e31ff795..27b7dd566b 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -99,8 +99,8 @@ int main( int argc, IotDemoArguments_t demoArguments = IOT_DEMO_ARGUMENTS_INITIALIZER; /* Network server info and credentials. */ - IotNetworkServerInfo_t serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; - IotNetworkCredentials_t credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, + struct IotNetworkServerInfo serverInfo = IOT_DEMO_SERVER_INFO_INITIALIZER; + struct IotNetworkCredentials credentials = IOT_DEMO_CREDENTIALS_INITIALIZER, * pCredentials = NULL; /* Parse and validate any command line arguments. */ diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c index e610cfb68a..fa4818eaad 100644 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c @@ -70,8 +70,8 @@ /* Empty callback structure passed to startInfo. */ static const AwsIotDefenderCallback_t _emptyCallback = { .function = NULL, .pCallbackContext = NULL }; -static IotNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; -static IotNetworkCredentials_t _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; +static struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; +static struct IotNetworkCredentials _credential = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; static IotMqttConnection_t _mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; /*------------------ global variables -----------------------------*/ @@ -168,7 +168,7 @@ TEST_SETUP( Defender_System ) } /* Reset server info. */ - _serverInfo = ( IotNetworkServerInfo_t ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; + _serverInfo = ( struct IotNetworkServerInfo ) IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /* Set fields of start info. */ _startInfo.pClientIdentifier = AWS_IOT_TEST_DEFENDER_THING_NAME; diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c index f3ad56d67d..ca52cbca71 100644 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c @@ -102,13 +102,13 @@ typedef struct _operationCompleteParams /** * @brief Network server info to share among the tests. */ -static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; +static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /** * @brief Network credential info to share among the tests. */ #if IOT_TEST_SECURED_CONNECTION == 1 - static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; + static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; #endif /** diff --git a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c index bf142e3de1..ae300a8030 100644 --- a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c +++ b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c @@ -114,13 +114,13 @@ typedef struct _operationCompleteParams /** * @brief Network server info to share among the tests. */ -static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; +static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /** * @brief Network credential info to share among the tests. */ #if IOT_TEST_SECURED_CONNECTION == 1 - static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; + static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; #endif /** diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index b9174eac0e..77eba836b6 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -28,6 +28,9 @@ #ifndef IOT_NETWORK_H_ #define IOT_NETWORK_H_ +/* The config header is always included first. */ +#include "iot_config.h" + /* Standard includes. */ #include #include @@ -87,7 +90,7 @@ typedef enum IotNetworkError * @param[in] pContext The third argument passed to @ref platform_network_function_setreceivecallback. */ /* @[declare_platform_network_receivecallback] */ -typedef void ( * IotNetworkReceiveCallback_t )( void * pConnection, +typedef void ( * IotNetworkReceiveCallback_t )( IotNetworkConnection_t pConnection, void * pContext ); /* @[declare_platform_network_receivecallback] */ @@ -104,7 +107,7 @@ typedef struct IotNetworkInterface * @brief Create a new network connection. * * This function allocates resources and establishes a new network connection. - * @param[in] pConnectionInfo Represents information needed to set up the + * @param[in] pServerInfo Represents information needed to set up the * new connection, defined by the network stack. * @param[in] pCredentialInfo Represents information needed to secure the * new connection, defined by the network stack. @@ -114,9 +117,9 @@ typedef struct IotNetworkInterface * @return Any #IotNetworkError_t, as defined by the network stack. */ /* @[declare_platform_network_create] */ - IotNetworkError_t ( * create )( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ); + IotNetworkError_t ( * create )( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ); /* @[declare_platform_network_create] */ /** @@ -140,7 +143,7 @@ typedef struct IotNetworkInterface * @see platform_network_function_receivecallback */ /* @[declare_platform_network_setreceivecallback] */ - IotNetworkError_t ( * setReceiveCallback )( void * pConnection, + IotNetworkError_t ( * setReceiveCallback )( IotNetworkConnection_t pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ); /* @[declare_platform_network_setreceivecallback] */ @@ -160,7 +163,7 @@ typedef struct IotNetworkInterface * @return The number of bytes successfully sent, `0` on failure. */ /* @[declare_platform_network_send] */ - size_t ( * send )( void * pConnection, + size_t ( * send )( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ); /* @[declare_platform_network_send] */ @@ -182,7 +185,7 @@ typedef struct IotNetworkInterface * `bytesRequested` when successful. Any other value may indicate an error. */ /* @[declare_platform_network_receive] */ - size_t ( * receive )( void * pConnection, + size_t ( * receive )( IotNetworkConnection_t pConnection, uint8_t * pBuffer, size_t bytesRequested ); /* @[declare_platform_network_receive] */ @@ -207,7 +210,7 @@ typedef struct IotNetworkInterface * @note It must be safe to call this function on an already-closed connection. */ /* @[declare_platform_network_close] */ - IotNetworkError_t ( * close )( void * pConnection ); + IotNetworkError_t ( * close )( IotNetworkConnection_t pConnection ); /* @[declare_platform_network_close] */ /** @@ -226,7 +229,7 @@ typedef struct IotNetworkInterface * [receive callback](@ref platform_network_function_receivecallback). */ /* @[declare_platform_network_destroy] */ - IotNetworkError_t ( * destroy )( void * pConnection ); + IotNetworkError_t ( * destroy )( IotNetworkConnection_t pConnection ); /* @[declare_platform_network_destroy] */ } IotNetworkInterface_t; @@ -238,11 +241,11 @@ typedef struct IotNetworkInterface * structure contains commonly-used parameters, but may be replaced with an * alternative. */ -typedef struct IotNetworkServerInfo +struct IotNetworkServerInfo { const char * pHostName; /**< @brief Server host name. Must be NULL-terminated. */ uint16_t port; /**< @brief Server port in host-order. */ -} IotNetworkServerInfo_t; +}; /** * @ingroup platform_datatypes_paramstructs @@ -252,7 +255,7 @@ typedef struct IotNetworkServerInfo * structure contains commonly-used parameters, but may be replaced with an * alternative. */ -typedef struct IotNetworkCredentials +struct IotNetworkCredentials { /** * @brief Set this to a non-NULL value to use ALPN. @@ -280,11 +283,11 @@ typedef struct IotNetworkCredentials bool disableSni; const char * pRootCa; /**< @brief String representing a trusted server root certificate. */ - size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials_t.pRootCa. */ + size_t rootCaSize; /**< @brief Size associated with #IotNetworkCredentials.pRootCa. */ const char * pClientCert; /**< @brief String representing the client certificate. */ - size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials_t.pClientCert. */ + size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ - size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials_t.pPrivateKey. */ -} IotNetworkCredentials_t; + size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ +}; #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/platform/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h index 18875ab7e1..53fa5a1553 100644 --- a/libraries/platform/types/iot_platform_types.h +++ b/libraries/platform/types/iot_platform_types.h @@ -51,10 +51,8 @@ * @brief The type used to represent mutexes, configured with the type * `_IotSystemMutex_t`. * - * - * `_IotSystemMutex_t` will be automatically configured during build and generally - * does not need to be defined. - * + * For the provided ports, `_IotSystemMutex_t` will be automatically configured. + * For other ports, `_IotSystemMutex_t` should be set in `iot_config.h`. * * Mutexes should only be released by the threads that take them. * @@ -72,10 +70,8 @@ typedef _IotSystemMutex_t IotMutex_t; * @brief The type used to represent semaphores, configured with the type * `_IotSystemSemaphore_t`. * - * - * `_IotSystemSemaphore_t` will be automatically configured during build and - * generally does not need to be defined. - * + * For the provided ports, `_IotSystemSemaphore_t` will be automatically configured. + * For other ports, `_IotSystemSemaphore_t` should be set in `iot_config.h`. * * Semaphores must be counting, and any thread may take (wait on) or release * (post to) a semaphore. @@ -104,10 +100,8 @@ typedef void ( * IotThreadRoutine_t )( void * ); * @brief The type used to represent timers, configured with the type * `_IotSystemTimer_t`. * - * - * `_IotSystemTimer_t` will be automatically configured during build and generally - * does not need to be defined. - * + * For the provided ports, `_IotSystemTimer_t` will be automatically configured. + * For other ports, `_IotSystemTimer_t` should be set in `iot_config.h`. * * Example
* To change the type of #IotTimer_t to `long`: @@ -118,6 +112,47 @@ typedef void ( * IotThreadRoutine_t )( void * ); */ typedef _IotSystemTimer_t IotTimer_t; +/*--------------------------- Network stack types ---------------------------*/ + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent network server info, configured with the + * type `_IotNetworkServerInfo_t`. + * + * For the provided ports, `_IotNetworkServerInfo_t` will be automatically configured. + * For other ports, `_IotNetworkServerInfo_t` should be set in `iot_config.h`. + * + * All of the provided ports configure this to #IotNetworkServerInfo, which provides + * the necessary information to connect to a TCP peer. For other network protocols, + * this type should be set to an alternate structure as needed by the other protocol. + */ +typedef _IotNetworkServerInfo_t IotNetworkServerInfo_t; + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent network credentials, configured with the + * type `_IotNetworkCredentials_t`. + * + * For the provided ports, `_IotNetworkCredentials_t` will be automatically configured. + * For other ports, `_IotNetworkCredentials_t` should be set in `iot_config.h`. + * + * All of the provided ports configure this to #IotNetworkCredentials, which provides + * the necessary information to connect to a TLS server over TCP. For other network + * protocols, this type should be set to an alternate structure as needed by the other + * protocol. + */ +typedef _IotNetworkCredentials_t IotNetworkCredentials_t; + +/** + * @ingroup platform_datatypes_handles + * @brief The type used to represent network connections, configured with the + * type `_IotNetworkConnection_t`. + * + * For the provided ports, `_IotNetworkConnection_t` will be automatically configured. + * For other ports, `_IotNetworkConnection_t` should be set in `iot_config.h`. + */ +typedef _IotNetworkConnection_t IotNetworkConnection_t; + /*------------------------------ Metrics types ------------------------------*/ /** diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index c4c20d80d3..316878b6ee 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -122,7 +122,7 @@ void IotMqtt_Cleanup( void ); * the packet was received. */ /* @[declare_mqtt_receivecallback] */ -void IotMqtt_ReceiveCallback( void * pNetworkConnection, +void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, void * pReceiveContext ); /* @[declare_mqtt_receivecallback] */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 2d6e36ea94..38fd4e8e54 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -980,14 +980,14 @@ typedef struct IotMqttNetworkInfo struct { /** - * @brief Information on the MQTT server, passed as `pConnectionInfo` to + * @brief Information on the MQTT server, passed as `pServerInfo` to * #IotNetworkInterface_t::create. * * This member is opaque to the MQTT library. It is passed to the network * interface when creating a new network connection. It is only valid when * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ - void * pNetworkServerInfo; + IotNetworkServerInfo_t pNetworkServerInfo; /** * @brief Credentials for the MQTT server, passed as `pCredentialInfo` to @@ -997,7 +997,7 @@ typedef struct IotMqttNetworkInfo * interface when creating a new network connection. It is only valid when * #IotMqttNetworkInfo_t::createNetworkConnection is `true`. */ - void * pNetworkCredentialInfo; + IotNetworkCredentials_t pNetworkCredentialInfo; } setup; /** @@ -1007,7 +1007,7 @@ typedef struct IotMqttNetworkInfo * interface to reference an established network connection. It is only * valid when #IotMqttNetworkInfo_t::createNetworkConnection is `false`. */ - void * pNetworkConnection; + IotNetworkConnection_t pNetworkConnection; } u /**< @brief Valid member depends of IotMqttNetworkInfo_t.createNetworkConnection. */; /** diff --git a/libraries/standard/mqtt/src/iot_mqtt_api.c b/libraries/standard/mqtt/src/iot_mqtt_api.c index 1105f82a58..98eefebbe7 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_api.c +++ b/libraries/standard/mqtt/src/iot_mqtt_api.c @@ -137,7 +137,7 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo * @return Any #IotNetworkError_t, as defined by the network stack. */ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - void ** pNetworkConnection, + IotNetworkConnection_t * pNetworkConnection, bool * pCreatedNewNetworkConnection ); /** @@ -434,7 +434,7 @@ static bool _createKeepAliveOperation( const IotMqttNetworkInfo_t * pNetworkInfo /*-----------------------------------------------------------*/ static IotNetworkError_t _createNetworkConnection( const IotMqttNetworkInfo_t * pNetworkInfo, - void ** pNetworkConnection, + IotNetworkConnection_t * pNetworkConnection, bool * pCreatedNewNetworkConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); @@ -1087,7 +1087,7 @@ IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo, bool ownNetworkConnection = false; IotNetworkError_t networkStatus = IOT_NETWORK_SUCCESS; IotTaskPoolError_t taskPoolStatus = IOT_TASKPOOL_SUCCESS; - void * pNetworkConnection = NULL; + IotNetworkConnection_t pNetworkConnection = { 0 }; _mqttOperation_t * pOperation = NULL; _mqttConnection_t * pNewMqttConnection = NULL; diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index cd5768a8d1..c85e7a9875 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -691,7 +691,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason IotMqttCallbackParam_t * ) = NULL; /* Network close function. */ - IotNetworkError_t ( * closeConnection) ( void * ) = NULL; + IotNetworkError_t ( * closeConnection) ( IotNetworkConnection_t ) = NULL; /* Mark the MQTT connection as disconnected and the keep-alive as failed. */ IotMutex_Lock( &( pMqttConnection->referencesMutex ) ); @@ -792,7 +792,7 @@ void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason /*-----------------------------------------------------------*/ -void IotMqtt_ReceiveCallback( void * pNetworkConnection, +void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, void * pReceiveContext ) { IotMqttError_t status = IOT_MQTT_SUCCESS; diff --git a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c index 70b38698bb..bac2a0d992 100644 --- a/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c +++ b/libraries/standard/mqtt/test/mock/iot_tests_mqtt_mock.c @@ -168,7 +168,7 @@ static void _receiveThread( void * pArgument ) } /* Call the MQTT receive callback to process the ACK packet. */ - IotMqtt_ReceiveCallback( &receiveContext, + IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, _pMqttConnection ); IotMutex_Unlock( &_lastPacketMutex ); @@ -180,7 +180,7 @@ static void _receiveThread( void * pArgument ) * @brief A send function that always "succeeds". It also sets the receive * timer to respond with an ACK when necessary. */ -static size_t _sendSuccess( void * pSendContext, +static size_t _sendSuccess( IotNetworkConnection_t pNetworkConnection, const uint8_t * pMessage, size_t messageLength ) { @@ -189,8 +189,8 @@ static size_t _sendSuccess( void * pSendContext, _mqttPacket_t mqttPacket = { .u.pMqttConnection = NULL }; _receiveContext_t receiveContext = { 0 }; - /* Ignore the send context. */ - ( void ) pSendContext; + /* Ignore the network connection. */ + ( void ) pNetworkConnection; /* Read the packet type, which is the first byte in the message. */ mqttPacket.type = *pMessage; @@ -278,12 +278,12 @@ static size_t _sendSuccess( void * pSendContext, /** * @brief Simulates a network receive function. */ -static size_t _receive( void * pConnection, +static size_t _receive( IotNetworkConnection_t pNetworkConnection, uint8_t * pBuffer, size_t bytesRequested ) { size_t bytesReceived = 0; - _receiveContext_t * pReceiveContext = pConnection; + _receiveContext_t * pReceiveContext = ( _receiveContext_t * ) pNetworkConnection; IotTest_Assert( bytesRequested != 0 ); IotTest_Assert( pReceiveContext->dataIndex < pReceiveContext->dataLength ); diff --git a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c index bba665aa09..a98deb1c10 100644 --- a/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c +++ b/libraries/standard/mqtt/test/system/iot_tests_mqtt_system.c @@ -124,13 +124,13 @@ typedef struct _operationCompleteParams /** * @brief Network server info to share among the tests. */ -static const IotTestNetworkServerInfo_t _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; +static const struct IotNetworkServerInfo _serverInfo = IOT_TEST_NETWORK_SERVER_INFO_INITIALIZER; /** * @brief Network credential info to share among the tests. */ #if IOT_TEST_SECURED_CONNECTION == 1 - static const IotTestNetworkCredentials_t _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; + static const struct IotNetworkCredentials _credentials = IOT_TEST_NETWORK_CREDENTIALS_INITIALIZER; #endif /** diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 0c89c3d988..71d16171e0 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -240,7 +240,7 @@ static void _publishSetDup( uint8_t * pPublishPacket, * @brief A send function that always "succeeds". May report that it was invoked * through a semaphore. */ -static size_t _sendSuccess( void * pSendContext, +static size_t _sendSuccess( IotNetworkConnection_t pSendContext, const uint8_t * pMessage, size_t messageLength ) { @@ -267,7 +267,7 @@ static size_t _sendSuccess( void * pSendContext, /** * @brief A send function for PINGREQ that responds with a PINGRESP. */ -static size_t _sendPingreq( void * pSendContext, +static size_t _sendPingreq( IotNetworkConnection_t pSendContext, const uint8_t * pMessage, size_t messageLength ) { @@ -294,7 +294,7 @@ static size_t _sendPingreq( void * pSendContext, /** * @brief A send function that delays. */ -static size_t _sendDelay( void * pSendContext, +static size_t _sendDelay( IotNetworkConnection_t pSendContext, const uint8_t * pMessage, size_t messageLength ) { @@ -319,7 +319,7 @@ static size_t _sendDelay( void * pSendContext, * @brief This send function checks that a duplicate outgoing message differs from * the original. */ -static size_t _dupChecker( void * pSendContext, +static size_t _dupChecker( IotNetworkConnection_t pSendContext, const uint8_t * pMessage, size_t messageLength ) { @@ -412,7 +412,7 @@ static size_t _dupChecker( void * pSendContext, /** * @brief A network receive function that simulates receiving a PINGRESP. */ -static size_t _receivePingresp( void * pReceiveContext, +static size_t _receivePingresp( IotNetworkConnection_t pReceiveContext, uint8_t * pBuffer, size_t bytesRequested ) { @@ -442,7 +442,7 @@ static size_t _receivePingresp( void * pReceiveContext, /** * @brief A function for setting the receive callback that just returns success. */ -static IotNetworkError_t _setReceiveCallback( void * pConnection, +static IotNetworkError_t _setReceiveCallback( IotNetworkConnection_t pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pReceiveContext ) { @@ -459,7 +459,7 @@ static IotNetworkError_t _setReceiveCallback( void * pConnection, /** * @brief A network close function that counts how many times it was invoked. */ -static IotNetworkError_t _close( void * pCloseContext ) +static IotNetworkError_t _close( IotNetworkConnection_t pCloseContext ) { /* Silence warnings about unused parameters. */ ( void ) pCloseContext; diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index c293520973..0f2fdf452b 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -325,7 +325,7 @@ static bool _processBuffer( const _mqttOperation_t * pOperation, receiveContext.dataLength = bufferSize; /* Call the receive callback on pBuffer. */ - IotMqtt_ReceiveCallback( &receiveContext, + IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, _pMqttConnection ); /* Check expected result if operation is given. */ @@ -374,7 +374,7 @@ static bool _processPublish( const uint8_t * pPublish, receiveContext.dataLength = publishSize; /* Call the receive callback on pPublish. */ - IotMqtt_ReceiveCallback( &receiveContext, + IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, _pMqttConnection ); /* Check how many times the publish callback is invoked. */ @@ -431,12 +431,12 @@ static void _publishCallback( void * pCallbackContext, /** * @brief Simulates a network receive function. */ -static size_t _receive( void * pConnection, +static size_t _receive( IotNetworkConnection_t pConnection, uint8_t * pBuffer, size_t bytesRequested ) { size_t bytesReceived = 0; - _receiveContext_t * pReceiveContext = pConnection; + _receiveContext_t * pReceiveContext = ( _receiveContext_t * ) pConnection; if( pReceiveContext->dataIndex != pReceiveContext->dataLength ) { @@ -474,7 +474,7 @@ static size_t _receive( void * pConnection, /** * @brief A network send function that checks the message is a PUBACK. */ -static size_t _checkPuback( void * pConnection, +static size_t _checkPuback( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ) { @@ -505,7 +505,7 @@ static size_t _checkPuback( void * pConnection, /** * @brief A network close function that reports if it was invoked. */ -static IotNetworkError_t _close( void * pConnection ) +static IotNetworkError_t _close( IotNetworkConnection_t pConnection ) { /* Silence warnings about unused parameters. */ ( void ) pConnection; @@ -760,7 +760,7 @@ TEST( MQTT_Unit_Receive, InvalidPacket ) receiveContext.dataLength = 1; /* Processing a control packet 0xf is a protocol violation. */ - IotMqtt_ReceiveCallback( &receiveContext, + IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, _pMqttConnection ); /* Processing an invalid packet should cause the network connection to be closed. */ @@ -801,12 +801,12 @@ TEST( MQTT_Unit_Receive, ReceiveMallocFail ) /* Set malloc to fail and process the first SUBACK. */ UnityMalloc_MakeMallocFailAfterCount( 0 ); - IotMqtt_ReceiveCallback( &receiveContext, + IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, _pMqttConnection ); /* Allow the use of malloc and process the second SUBACK. */ UnityMalloc_MakeMallocFailAfterCount( -1 ); - IotMqtt_ReceiveCallback( &receiveContext, + IotMqtt_ReceiveCallback( ( IotNetworkConnection_t ) &receiveContext, _pMqttConnection ); /* Network close function should not have been invoked. */ diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h index 64eaaec2f7..9475a086fa 100644 --- a/ports/common/include/iot_network_mbedtls.h +++ b/ports/common/include/iot_network_mbedtls.h @@ -38,12 +38,12 @@ #include "platform/iot_network.h" /** - * @brief Provides a default value for an #IotNetworkServerInfo_t. + * @brief Provides a default value for an #IotNetworkServerInfo. * - * All instances of #IotNetworkServerInfo_t should be initialized with + * All instances of #IotNetworkServerInfo should be initialized with * this constant when using this mbed TLS network stack. * - * @warning Failing to initialize an #IotNetworkServerInfo_t may result in + * @warning Failing to initialize an #IotNetworkServerInfo may result in * a crash! * @note This initializer may change at any time in future versions, but its * name will remain the same. @@ -51,7 +51,7 @@ #define IOT_NETWORK_SERVER_INFO_MBEDTLS_INITIALIZER { 0 } /** - * @brief Initialize an #IotNetworkCredentials_t for AWS IoT with ALPN enabled + * @brief Initialize an #IotNetworkCredentials for AWS IoT with ALPN enabled * when using this mbed TLS network stack. * * @note This initializer may change at any time in future versions, but its @@ -63,7 +63,7 @@ } /** - * @brief Generic initializer for an #IotNetworkCredentials_t when using this + * @brief Generic initializer for an #IotNetworkCredentials when using this * mbed TLS network stack. * * @note This initializer may change at any time in future versions, but its @@ -112,46 +112,46 @@ void IotNetworkMbedtls_Cleanup( void ); /** * @brief An implementation of #IotNetworkInterface_t::create for mbed TLS. */ -IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ); +IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for * mbed TLS. */ -IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( void * pConnection, +IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ); /** * @brief An implementation of #IotNetworkInterface_t::send for mbed TLS. */ -size_t IotNetworkMbedtls_Send( void * pConnection, +size_t IotNetworkMbedtls_Send( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ); /** * @brief An implementation of #IotNetworkInterface_t::receive for mbed TLS. */ -size_t IotNetworkMbedtls_Receive( void * pConnection, +size_t IotNetworkMbedtls_Receive( IotNetworkConnection_t pConnection, uint8_t * pBuffer, size_t bytesRequested ); /** * @brief An implementation of #IotNetworkInterface_t::close for mbed TLS. */ -IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ); +IotNetworkError_t IotNetworkMbedtls_Close( IotNetworkConnection_t pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::destroy for mbed TLS. */ -IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ); +IotNetworkError_t IotNetworkMbedtls_Destroy( IotNetworkConnection_t pConnection ); /** * @brief Used by metrics to retrieve the socket (file descriptor) associated with * a connection. */ -int IotNetworkMbedtls_GetSocket( void * pConnection ); +int IotNetworkMbedtls_GetSocket( IotNetworkConnection_t pConnection ); #endif /* ifndef IOT_NETWORK_MBEDTLS_H_ */ diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h index 1fffc0796b..a973318635 100644 --- a/ports/common/include/iot_network_openssl.h +++ b/ports/common/include/iot_network_openssl.h @@ -22,7 +22,7 @@ /** * @file iot_network_openssl.h * @brief Declares the network stack functions specified in iot_network.h for - * POSIX systems with OpenSSL. + * systems with OpenSSL. */ #ifndef IOT_NETWORK_OPENSSL_H_ @@ -41,20 +41,12 @@ #include "platform/iot_network.h" /** - * @brief Represents a network connection that uses OpenSSL. + * @brief Provides a default value for an #IotNetworkServerInfo. * - * This is an incomplete type. In application code, only pointers to this type - * should be used. - */ -typedef struct _networkConnection IotNetworkConnectionOpenssl_t; - -/** - * @brief Provides a default value for an #IotNetworkServerInfo_t. - * - * All instances of #IotNetworkServerInfo_t should be initialized with + * All instances of #IotNetworkServerInfo should be initialized with * this constant when using this OpenSSL network stack. * - * @warning Failing to initialize an #IotNetworkServerInfo_t may result in + * @warning Failing to initialize an #IotNetworkServerInfo may result in * a crash! * @note This initializer may change at any time in future versions, but its * name will remain the same. @@ -62,7 +54,7 @@ typedef struct _networkConnection IotNetworkConnectionOpenssl_t; #define IOT_NETWORK_SERVER_INFO_OPENSSL_INITIALIZER { 0 } /** - * @brief Initialize an #IotNetworkCredentials_t for AWS IoT with ALPN enabled + * @brief Initialize an #IotNetworkCredentials for AWS IoT with ALPN enabled * when using this OpenSSL network stack. * * @note This initializer may change at any time in future versions, but its @@ -74,7 +66,7 @@ typedef struct _networkConnection IotNetworkConnectionOpenssl_t; } /** - * @brief Generic initializer for an #IotNetworkCredentials_t when using this + * @brief Generic initializer for an #IotNetworkCredentials when using this * OpenSSL network stack. * * @note This initializer may change at any time in future versions, but its @@ -121,53 +113,53 @@ IotNetworkError_t IotNetworkOpenssl_Init( void ); void IotNetworkOpenssl_Cleanup( void ); /** - * @brief An implementation of #IotNetworkInterface_t::create for POSIX systems + * @brief An implementation of #IotNetworkInterface_t::create for systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ); +IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ); /** * @brief An implementation of #IotNetworkInterface_t::setReceiveCallback for - * POSIX systems with OpenSSL. + * systems with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, +IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( IotNetworkConnection_t pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ); /** - * @brief An implementation of #IotNetworkInterface_t::send for POSIX systems + * @brief An implementation of #IotNetworkInterface_t::send for systems * with OpenSSL. */ -size_t IotNetworkOpenssl_Send( void * pConnection, +size_t IotNetworkOpenssl_Send( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ); /** - * @brief An implementation of #IotNetworkInterface_t::receive for POSIX systems + * @brief An implementation of #IotNetworkInterface_t::receive for systems * with OpenSSL. */ -size_t IotNetworkOpenssl_Receive( void * pConnection, +size_t IotNetworkOpenssl_Receive( IotNetworkConnection_t pConnection, uint8_t * pBuffer, size_t bytesRequested ); /** - * @brief An implementation of #IotNetworkInterface_t::close for POSIX systems + * @brief An implementation of #IotNetworkInterface_t::close for systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ); +IotNetworkError_t IotNetworkOpenssl_Close( IotNetworkConnection_t pConnection ); /** - * @brief An implementation of #IotNetworkInterface_t::destroy for POSIX systems + * @brief An implementation of #IotNetworkInterface_t::destroy for systems * with OpenSSL. */ -IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ); +IotNetworkError_t IotNetworkOpenssl_Destroy( IotNetworkConnection_t pConnection ); /** * @brief Used by metrics to retrieve the socket (file descriptor) associated with * a connection. */ -int IotNetworkOpenssl_GetSocket( void * pConnection ); +int IotNetworkOpenssl_GetSocket( IotNetworkConnection_t pConnection ); #endif /* ifndef IOT_NETWORK_OPENSSL_H_ */ diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index 89a1b52b95..82d522f9b3 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -299,15 +299,15 @@ static int _mbedtlsMutexUnlock( mbedtls_threading_mutex_t * pMutex ) /** * @brief Initialize the mbed TLS structures in a network connection. * - * @param[in] pNetworkConnection The network connection to initialize. + * @param[in] pConnection The network connection to initialize. */ -static void _sslContextInit( _networkConnection_t * pNetworkConnection ) +static void _sslContextInit( _networkConnection_t * pConnection ) { - mbedtls_ssl_config_init( &( pNetworkConnection->ssl.config ) ); - mbedtls_x509_crt_init( &( pNetworkConnection->ssl.credentials.rootCa ) ); - mbedtls_x509_crt_init( &( pNetworkConnection->ssl.credentials.clientCert ) ); - mbedtls_pk_init( &( pNetworkConnection->ssl.credentials.privateKey ) ); - mbedtls_ssl_init( &( pNetworkConnection->ssl.context ) ); + mbedtls_ssl_config_init( &( pConnection->ssl.config ) ); + mbedtls_x509_crt_init( &( pConnection->ssl.credentials.rootCa ) ); + mbedtls_x509_crt_init( &( pConnection->ssl.credentials.clientCert ) ); + mbedtls_pk_init( &( pConnection->ssl.credentials.privateKey ) ); + mbedtls_ssl_init( &( pConnection->ssl.context ) ); } /*-----------------------------------------------------------*/ @@ -315,15 +315,15 @@ static void _sslContextInit( _networkConnection_t * pNetworkConnection ) /** * @brief Free the mbed TLS structures in a network connection. * - * @param[in] pNetworkConnection The network connection with the contexts to free. + * @param[in] pConnection The network connection with the contexts to free. */ -static void _sslContextFree( _networkConnection_t * pNetworkConnection ) +static void _sslContextFree( _networkConnection_t * pConnection ) { - mbedtls_ssl_free( &( pNetworkConnection->ssl.context ) ); - mbedtls_pk_free( &( pNetworkConnection->ssl.credentials.privateKey ) ); - mbedtls_x509_crt_free( &( pNetworkConnection->ssl.credentials.clientCert ) ); - mbedtls_x509_crt_free( &( pNetworkConnection->ssl.credentials.rootCa ) ); - mbedtls_ssl_config_free( &( pNetworkConnection->ssl.config ) ); + mbedtls_ssl_free( &( pConnection->ssl.context ) ); + mbedtls_pk_free( &( pConnection->ssl.credentials.privateKey ) ); + mbedtls_x509_crt_free( &( pConnection->ssl.credentials.clientCert ) ); + mbedtls_x509_crt_free( &( pConnection->ssl.credentials.rootCa ) ); + mbedtls_ssl_config_free( &( pConnection->ssl.config ) ); } /*-----------------------------------------------------------*/ @@ -331,27 +331,27 @@ static void _sslContextFree( _networkConnection_t * pNetworkConnection ) /** * @brief Destroy a network connection. * - * @param[in] pNetworkConnection The network connection to destroy. + * @param[in] pConnection The network connection to destroy. */ -static void _destroyConnection( _networkConnection_t * pNetworkConnection ) +static void _destroyConnection( _networkConnection_t * pConnection ) { /* Clean up the SSL context of secured connections. */ - if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) { - _sslContextFree( pNetworkConnection ); + _sslContextFree( pConnection ); } /* Destroy synchronization objects. */ - IotMutex_Destroy( &( pNetworkConnection->contextMutex ) ); - IotMutex_Destroy( &( pNetworkConnection->callbackMutex ) ); + IotMutex_Destroy( &( pConnection->contextMutex ) ); + IotMutex_Destroy( &( pConnection->callbackMutex ) ); - if( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == FLAG_HAS_RECEIVE_CALLBACK ) + if( ( pConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == FLAG_HAS_RECEIVE_CALLBACK ) { - IotSemaphore_Destroy( &( pNetworkConnection->destroyNotification ) ); + IotSemaphore_Destroy( &( pConnection->destroyNotification ) ); } /* Free memory. */ - IotNetwork_Free( pNetworkConnection ); + IotNetwork_Free( pConnection ); } /*-----------------------------------------------------------*/ @@ -370,12 +370,12 @@ static void _receiveThread( void * pArgument ) mbedtls_net_context context; /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pArgument; + _networkConnection_t * pConnection = pArgument; /* Record the context to poll. */ - IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); - context = pNetworkConnection->networkContext; - IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Lock( &( pConnection->contextMutex ) ); + context = pConnection->networkContext; + IotMutex_Unlock( &( pConnection->contextMutex ) ); /* Continuously poll the network connection for events. */ while( true ) @@ -387,7 +387,7 @@ static void _receiveThread( void * pArgument ) if( pollStatus < 0 ) { /* Error during poll. */ - _logConnectionError( pollStatus, pNetworkConnection, "Error polling network connection." ); + _logConnectionError( pollStatus, pConnection, "Error polling network connection." ); break; } else @@ -395,26 +395,26 @@ static void _receiveThread( void * pArgument ) /* Invoke receive callback if data is available. */ if( pollStatus == MBEDTLS_NET_POLL_READ ) { - IotMutex_Lock( &( pNetworkConnection->callbackMutex ) ); + IotMutex_Lock( &( pConnection->callbackMutex ) ); /* Only run the receive callback if the connection has not been * destroyed. */ - if( ( pNetworkConnection->flags & FLAG_CONNECTION_DESTROYED ) == 0 ) + if( ( pConnection->flags & FLAG_CONNECTION_DESTROYED ) == 0 ) { - pNetworkConnection->receiveCallback( pNetworkConnection, - pNetworkConnection->pReceiveContext ); + pConnection->receiveCallback( pConnection, + pConnection->pReceiveContext ); } - IotMutex_Unlock( &( pNetworkConnection->callbackMutex ) ); + IotMutex_Unlock( &( pConnection->callbackMutex ) ); } } } /* Wait for the call to network destroy, then destroy the connection. */ - IotSemaphore_Wait( &( pNetworkConnection->destroyNotification ) ); - _destroyConnection( pNetworkConnection ); + IotSemaphore_Wait( &( pConnection->destroyNotification ) ); + _destroyConnection( pConnection ); - IotLogDebug( "(Network connection %p) Receive thread terminating.", pNetworkConnection ); + IotLogDebug( "(Network connection %p) Receive thread terminating.", pConnection ); ( void ) Atomic_Decrement_u32( &_receiveThreadCount ); } @@ -426,14 +426,14 @@ static void _receiveThread( void * pArgument ) * * Uses mbed TLS to import the root CA certificate, client certificate, and * client certificate private key. - * @param[in] pNetworkConnection Network connection for the imported credentials. + * @param[in] pConnection Network connection for the imported credentials. * @param[in] pRootCaPath Path to the root CA certificate. * @param[in] pClientCertPath Path to the client certificate. * @param[in] pCertPrivateKeyPath Path to the client certificate private key. * * @return `true` if all credentials were successfully read; `false` otherwise. */ -static bool _readCredentials( _networkConnection_t * pNetworkConnection, +static bool _readCredentials( _networkConnection_t * pConnection, const char * pRootCaPath, const char * pClientCertPath, const char * pCertPrivateKeyPath ) @@ -442,12 +442,12 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, int mbedtlsError = 0; /* Read the root CA certificate. */ - mbedtlsError = mbedtls_x509_crt_parse_file( &( pNetworkConnection->ssl.credentials.rootCa ), + mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.rootCa ), pRootCaPath ); if( mbedtlsError < 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to read root CA certificate file." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to read root CA certificate file." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -459,12 +459,12 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, } /* Read the client certificate. */ - mbedtlsError = mbedtls_x509_crt_parse_file( &( pNetworkConnection->ssl.credentials.clientCert ), + mbedtlsError = mbedtls_x509_crt_parse_file( &( pConnection->ssl.credentials.clientCert ), pClientCertPath ); if( mbedtlsError < 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to read client certificate file." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate file." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -476,29 +476,29 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, } /* Read the client certificate private key. */ - mbedtlsError = mbedtls_pk_parse_keyfile( &( pNetworkConnection->ssl.credentials.privateKey ), + mbedtlsError = mbedtls_pk_parse_keyfile( &( pConnection->ssl.credentials.privateKey ), pCertPrivateKeyPath, NULL ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to read client certificate private key file." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to read client certificate private key file." ); IOT_SET_AND_GOTO_CLEANUP( false ); } /* Set the credentials in the SSL configuration. */ - mbedtls_ssl_conf_ca_chain( &( pNetworkConnection->ssl.config ), - &( pNetworkConnection->ssl.credentials.rootCa ), + mbedtls_ssl_conf_ca_chain( &( pConnection->ssl.config ), + &( pConnection->ssl.credentials.rootCa ), NULL ); - mbedtlsError = mbedtls_ssl_conf_own_cert( &( pNetworkConnection->ssl.config ), - &( pNetworkConnection->ssl.credentials.clientCert ), - &( pNetworkConnection->ssl.credentials.privateKey ) ); + mbedtlsError = mbedtls_ssl_conf_own_cert( &( pConnection->ssl.config ), + &( pConnection->ssl.credentials.clientCert ), + &( pConnection->ssl.credentials.privateKey ) ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to configure credentials." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to configure credentials." ); IOT_SET_AND_GOTO_CLEANUP( false ); } @@ -511,15 +511,15 @@ static bool _readCredentials( _networkConnection_t * pNetworkConnection, /** * @brief Set up TLS on a TCP connection. * - * @param[in] pNetworkConnection An established TCP connection. + * @param[in] pConnection An established TCP connection. * @param[in] pServerName Remote host name, used for server name indication. * @param[in] pMbedtlsCredentials TLS setup parameters. * * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. */ -static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, +static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, const char * pServerName, - const IotNetworkCredentials_t * pMbedtlsCredentials ) + const IotNetworkCredentials_t pMbedtlsCredentials ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int mbedtlsError = 0; @@ -531,32 +531,32 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, bool sslContextInitialized = false; /* Initialize SSL configuration. */ - _sslContextInit( pNetworkConnection ); + _sslContextInit( pConnection ); sslContextInitialized = true; - mbedtlsError = mbedtls_ssl_config_defaults( &( pNetworkConnection->ssl.config ), + mbedtlsError = mbedtls_ssl_config_defaults( &( pConnection->ssl.config ), MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set default SSL configuration." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to set default SSL configuration." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Set SSL authmode and the RNG context. */ - mbedtls_ssl_conf_authmode( &( pNetworkConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); - mbedtls_ssl_conf_rng( &( pNetworkConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); + mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); + mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); - if( _readCredentials( pNetworkConnection, + if( _readCredentials( pConnection, pMbedtlsCredentials->pRootCa, pMbedtlsCredentials->pClientCert, pMbedtlsCredentials->pPrivateKey ) == false ) { IotLogError( "(Network connection %p) Failed to read credentials.", - pNetworkConnection ); + pConnection ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -567,15 +567,15 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* mbed TLS expects a NULL-terminated array of protocols. These pointers * must remain in-scope for the lifetime of the connection, so they are * stored as part of the connection context. */ - pNetworkConnection->ssl.pAlpnProtos[ 0 ] = pMbedtlsCredentials->pAlpnProtos; - pNetworkConnection->ssl.pAlpnProtos[ 1 ] = NULL; + pConnection->ssl.pAlpnProtos[ 0 ] = pMbedtlsCredentials->pAlpnProtos; + pConnection->ssl.pAlpnProtos[ 1 ] = NULL; - mbedtlsError = mbedtls_ssl_conf_alpn_protocols( &( pNetworkConnection->ssl.config ), - pNetworkConnection->ssl.pAlpnProtos ); + mbedtlsError = mbedtls_ssl_conf_alpn_protocols( &( pConnection->ssl.config ), + pConnection->ssl.pAlpnProtos ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set ALPN protocols." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to set ALPN protocols." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -614,12 +614,12 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* Set MFLN if a valid fragment length is given. */ if( fragmentLengthValid == true ) { - mbedtlsError = mbedtls_ssl_conf_max_frag_len( &( pNetworkConnection->ssl.config ), + mbedtlsError = mbedtls_ssl_conf_max_frag_len( &( pConnection->ssl.config ), mflCode ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set TLS MFLN." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to set TLS MFLN." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -627,19 +627,19 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, } /* Initialize the mbed TLS secured connection context. */ - mbedtlsError = mbedtls_ssl_setup( &( pNetworkConnection->ssl.context ), - &( pNetworkConnection->ssl.config ) ); + mbedtlsError = mbedtls_ssl_setup( &( pConnection->ssl.context ), + &( pConnection->ssl.config ) ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set up mbed TLS SSL context." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to set up mbed TLS SSL context." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Set the underlying IO for the TLS connection. */ - mbedtls_ssl_set_bio( &( pNetworkConnection->ssl.context ), - &( pNetworkConnection->networkContext ), + mbedtls_ssl_set_bio( &( pConnection->ssl.context ), + &( pConnection->networkContext ), mbedtls_net_send, NULL, mbedtls_net_recv_timeout ); @@ -647,12 +647,12 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* Enable SNI if requested. */ if( pMbedtlsCredentials->disableSni == false ) { - mbedtlsError = mbedtls_ssl_set_hostname( &( pNetworkConnection->ssl.context ), + mbedtlsError = mbedtls_ssl_set_hostname( &( pConnection->ssl.context ), pServerName ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to set server name." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to set server name." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } @@ -661,19 +661,19 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* Perform the TLS handshake. */ do { - mbedtlsError = mbedtls_ssl_handshake( &( pNetworkConnection->ssl.context ) ); + mbedtlsError = mbedtls_ssl_handshake( &( pConnection->ssl.context ) ); } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to perform SSL handshake." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to perform SSL handshake." ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Check result of certificate verification. */ - verifyResult = mbedtls_ssl_get_verify_result( &( pNetworkConnection->ssl.context ) ); + verifyResult = mbedtls_ssl_get_verify_result( &( pConnection->ssl.context ) ); if( verifyResult != 0 ) { @@ -690,16 +690,16 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { if( sslContextInitialized == true ) { - _sslContextFree( pNetworkConnection ); + _sslContextFree( pConnection ); } } else { /* TLS setup succeeded; set the secured flag. */ - pNetworkConnection->flags |= FLAG_SECURED; + pConnection->flags |= FLAG_SECURED; IotLogInfo( "(Network connection %p) TLS handshake successful.", - pNetworkConnection ); + pConnection ); } IOT_FUNCTION_CLEANUP_END(); @@ -775,9 +775,9 @@ void IotNetworkMbedtls_Cleanup( void ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ) +IotNetworkError_t IotNetworkMbedtls_Create( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int mbedtlsError = 0; @@ -787,11 +787,6 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, /* Flags to track initialization. */ bool networkContextInitialized = false, networkMutexCreated = false, callbackMutexCreated = false; - /* Cast function parameters to correct types. */ - const IotNetworkServerInfo_t * const pServerInfo = pConnectionInfo; - const IotNetworkCredentials_t * const pMbedtlsCredentials = pCredentialInfo; - _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t ** const ) pConnection; - /* Allocate memory for a new connection. */ pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); @@ -866,11 +861,11 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, } /* Set up TLS if credentials are given. */ - if( pMbedtlsCredentials != NULL ) + if( pCredentialInfo != NULL ) { status = _tlsSetup( pNewNetworkConnection, pServerInfo->pHostName, - pMbedtlsCredentials ); + pCredentialInfo ); } IOT_FUNCTION_CLEANUP_BEGIN(); @@ -904,7 +899,7 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, pNewNetworkConnection ); /* Set the output parameter. */ - *pNetworkConnection = pNewNetworkConnection; + *pConnection = pNewNetworkConnection; } IOT_FUNCTION_CLEANUP_END(); @@ -912,49 +907,46 @@ IotNetworkError_t IotNetworkMbedtls_Create( void * pConnectionInfo, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( void * pConnection, +IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - /* Flags to track initialization. */ bool notifyInitialized = false, countIncremented = false; /* Initialize the semaphore that notifies the receive thread of connection * destruction. */ - notifyInitialized = IotSemaphore_Create( &( pNetworkConnection->destroyNotification ), + notifyInitialized = IotSemaphore_Create( &( pConnection->destroyNotification ), 0, 1 ); if( notifyInitialized == false ) { IotLogError( "(Network connection %p) Failed to create semaphore for " - "receive thread.", pNetworkConnection ); + "receive thread.", pConnection ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } /* Set the callback and parameter. */ - pNetworkConnection->receiveCallback = receiveCallback; - pNetworkConnection->pReceiveContext = pContext; + pConnection->receiveCallback = receiveCallback; + pConnection->pReceiveContext = pContext; /* Set the receive callback flag and increment the count of receive threads. */ - pNetworkConnection->flags |= FLAG_HAS_RECEIVE_CALLBACK; + pConnection->flags |= FLAG_HAS_RECEIVE_CALLBACK; ( void ) Atomic_Increment_u32( &_receiveThreadCount ); countIncremented = true; /* Create the thread to receive incoming data. */ if( Iot_CreateDetachedThread( _receiveThread, - pNetworkConnection, + pConnection, IOT_THREAD_DEFAULT_PRIORITY, IOT_THREAD_DEFAULT_STACK_SIZE ) == false ) { IotLogError( "(Network connection %p) Failed to create thread for receiving data.", - pNetworkConnection ); + pConnection ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } @@ -966,19 +958,19 @@ IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( void * pConnection, { if( notifyInitialized == true ) { - IotSemaphore_Destroy( &( pNetworkConnection->destroyNotification ) ); + IotSemaphore_Destroy( &( pConnection->destroyNotification ) ); } if( countIncremented == true ) { - pNetworkConnection->flags &= ~FLAG_HAS_RECEIVE_CALLBACK; + pConnection->flags &= ~FLAG_HAS_RECEIVE_CALLBACK; ( void ) Atomic_Decrement_u32( &_receiveThreadCount ); } } else { IotLogDebug( "(Network connection %p) Receive callback set.", - pNetworkConnection ); + pConnection ); } IOT_FUNCTION_CLEANUP_END(); @@ -986,36 +978,33 @@ IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( void * pConnection, /*-----------------------------------------------------------*/ -size_t IotNetworkMbedtls_Send( void * pConnection, +size_t IotNetworkMbedtls_Send( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ) { int mbedtlsError = 0; size_t bytesSent = 0; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - IotLogDebug( "(Network connection %p) Sending %lu bytes.", - pNetworkConnection, + pConnection, ( unsigned long ) messageLength ); - IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Lock( &( pConnection->contextMutex ) ); /* Check that it's possible to send right now. */ - mbedtlsError = mbedtls_net_poll( &( pNetworkConnection->networkContext ), + mbedtlsError = mbedtls_net_poll( &( pConnection->networkContext ), MBEDTLS_NET_POLL_WRITE, 0 ); if( mbedtlsError == MBEDTLS_NET_POLL_WRITE ) { /* Choose the send function based on state of the SSL context. */ - if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) { /* Secured send. */ while( bytesSent < messageLength ) { - mbedtlsError = mbedtls_ssl_write( &( pNetworkConnection->ssl.context ), + mbedtlsError = mbedtls_ssl_write( &( pConnection->ssl.context ), pMessage + bytesSent, messageLength - bytesSent ); @@ -1039,7 +1028,7 @@ size_t IotNetworkMbedtls_Send( void * pConnection, else { /* Unsecured send. */ - mbedtlsError = mbedtls_net_send( &( pNetworkConnection->networkContext ), + mbedtlsError = mbedtls_net_send( &( pConnection->networkContext ), pMessage, messageLength ); @@ -1052,52 +1041,49 @@ size_t IotNetworkMbedtls_Send( void * pConnection, /* Log errors. */ if( mbedtlsError < 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to send." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to send." ); bytesSent = 0; } } else { - _logConnectionError( mbedtlsError, pNetworkConnection, "Cannot send right now." ); + _logConnectionError( mbedtlsError, pConnection, "Cannot send right now." ); } - IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Unlock( &( pConnection->contextMutex ) ); return bytesSent; } /*-----------------------------------------------------------*/ -size_t IotNetworkMbedtls_Receive( void * pConnection, +size_t IotNetworkMbedtls_Receive( IotNetworkConnection_t pConnection, uint8_t * pBuffer, size_t bytesRequested ) { int mbedtlsError = 0; size_t bytesReceived = 0; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - while( bytesReceived < bytesRequested ) { - IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Lock( &( pConnection->contextMutex ) ); /* Choose the receive function based on state of the SSL context. */ - if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) { - mbedtlsError = mbedtls_ssl_read( &( pNetworkConnection->ssl.context ), + mbedtlsError = mbedtls_ssl_read( &( pConnection->ssl.context ), pBuffer + bytesReceived, bytesRequested - bytesReceived ); } else { - mbedtlsError = mbedtls_net_recv_timeout( &( pNetworkConnection->networkContext ), + mbedtlsError = mbedtls_net_recv_timeout( &( pConnection->networkContext ), pBuffer + bytesReceived, bytesRequested - bytesReceived, IOT_NETWORK_MBEDTLS_POLL_TIMEOUT_MS ); } - IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Unlock( &( pConnection->contextMutex ) ); if( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) || ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || @@ -1109,7 +1095,7 @@ size_t IotNetworkMbedtls_Receive( void * pConnection, else if( mbedtlsError < 0 ) { /* Error receiving, exit. */ - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to receive." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to receive." ); break; } else @@ -1123,68 +1109,62 @@ size_t IotNetworkMbedtls_Receive( void * pConnection, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkMbedtls_Close( void * pConnection ) +IotNetworkError_t IotNetworkMbedtls_Close( IotNetworkConnection_t pConnection ) { int mbedtlsError = 0; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - - IotMutex_Lock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Lock( &( pConnection->contextMutex ) ); /* Notify the server that the SSL connection is being closed. */ - if( ( pNetworkConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) + if( ( pConnection->flags & FLAG_SECURED ) == FLAG_SECURED ) { do { - mbedtlsError = mbedtls_ssl_close_notify( &( pNetworkConnection->ssl.context ) ); + mbedtlsError = mbedtls_ssl_close_notify( &( pConnection->ssl.context ) ); } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); if( mbedtlsError != 0 ) { - _logConnectionError( mbedtlsError, pNetworkConnection, "Failed to notify peer of SSL connection close." ); + _logConnectionError( mbedtlsError, pConnection, "Failed to notify peer of SSL connection close." ); } else { IotLogInfo( "(Network connection %p) TLS session terminated.", - pNetworkConnection ); + pConnection ); } } /* Shutdown and close the network connection. */ - mbedtls_net_free( &( pNetworkConnection->networkContext ) ); + mbedtls_net_free( &( pConnection->networkContext ) ); - IotMutex_Unlock( &( pNetworkConnection->contextMutex ) ); + IotMutex_Unlock( &( pConnection->contextMutex ) ); - IotLogInfo( "(Network connection %p) Connection closed.", pNetworkConnection ); + IotLogInfo( "(Network connection %p) Connection closed.", pConnection ); return IOT_NETWORK_SUCCESS; } /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ) +IotNetworkError_t IotNetworkMbedtls_Destroy( IotNetworkConnection_t pConnection ) { - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - /* Shutdown and close the network connection. */ - IotMutex_Lock( &( pNetworkConnection->callbackMutex ) ); - mbedtls_net_free( &( pNetworkConnection->networkContext ) ); - pNetworkConnection->flags |= FLAG_CONNECTION_DESTROYED; - IotMutex_Unlock( &( pNetworkConnection->callbackMutex ) ); + IotMutex_Lock( &( pConnection->callbackMutex ) ); + mbedtls_net_free( &( pConnection->networkContext ) ); + pConnection->flags |= FLAG_CONNECTION_DESTROYED; + IotMutex_Unlock( &( pConnection->callbackMutex ) ); /* Check if this connection has a receive callback. If it does not, it can * be destroyed here. Otherwise, notify the receive callback that destroy * has been called and rely on the receive callback to clean up. */ - if( ( pNetworkConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == 0 ) + if( ( pConnection->flags & FLAG_HAS_RECEIVE_CALLBACK ) == 0 ) { - _destroyConnection( pNetworkConnection ); + _destroyConnection( pConnection ); } else { - IotSemaphore_Post( &( pNetworkConnection->destroyNotification ) ); + IotSemaphore_Post( &( pConnection->destroyNotification ) ); } return IOT_NETWORK_SUCCESS; @@ -1192,12 +1172,9 @@ IotNetworkError_t IotNetworkMbedtls_Destroy( void * pConnection ) /*-----------------------------------------------------------*/ -int IotNetworkMbedtls_GetSocket( void * pConnection ) +int IotNetworkMbedtls_GetSocket( IotNetworkConnection_t pConnection ) { - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - - return pNetworkConnection->networkContext.fd; + return pConnection->networkContext.fd; } /*-----------------------------------------------------------*/ diff --git a/ports/common/src/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c index 066820fd77..8bd0b6a1e8 100644 --- a/ports/common/src/iot_network_metrics.c +++ b/ports/common/src/iot_network_metrics.c @@ -86,14 +86,14 @@ /** * @brief Wraps the network connection creation function with metrics. */ -static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ); +static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ); /** * @brief Wraps the network connection close function with metrics. */ -static IotNetworkError_t _metricsNetworkClose( void * pConnection ); +static IotNetworkError_t _metricsNetworkClose( IotNetworkConnection_t pConnection ); /** * @brief Used to match metrics connection records by network connection. @@ -129,17 +129,19 @@ static IotMutex_t _connectionListMutex; /** * @brief Pointer to the metrics-wrapped network creation function. */ - static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkOpenssl_Create; + static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t, + IotNetworkCredentials_t, + IotNetworkConnection_t * ) = IotNetworkOpenssl_Create; /** * @brief Pointer to the metrics-wrapped network close function. */ - static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkOpenssl_Close; + static IotNetworkError_t ( * _networkClose )( IotNetworkConnection_t ) = IotNetworkOpenssl_Close; /** * @brief Pointer to the function that retrieves the socket for a connection. */ - static int ( * _getSocket )( void * ) = IotNetworkOpenssl_GetSocket; + static int ( * _getSocket )( IotNetworkConnection_t ) = IotNetworkOpenssl_GetSocket; /** * @brief An #IotNetworkInterface_t that wraps network abstraction functions with @@ -161,17 +163,19 @@ static IotMutex_t _connectionListMutex; /** * @brief Pointer to the metrics-wrapped network creation function. */ - static IotNetworkError_t ( * _networkCreate )( void *, void *, void ** ) = IotNetworkMbedtls_Create; + static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t, + IotNetworkCredentials_t, + IotNetworkConnection_t * ) = IotNetworkMbedtls_Create; /** * @brief Pointer to the metrics-wrapped network close function. */ - static IotNetworkError_t ( * _networkClose )( void * ) = IotNetworkMbedtls_Close; + static IotNetworkError_t ( * _networkClose )( IotNetworkConnection_t ) = IotNetworkMbedtls_Close; /** * @brief Pointer to the function that retrieves the socket for a connection. */ - static int ( * _getSocket )( void * ) = IotNetworkMbedtls_GetSocket; + static int ( * _getSocket )( IotNetworkConnection_t ) = IotNetworkMbedtls_GetSocket; /** * @brief An #IotNetworkInterface_t that wraps network abstraction functions with @@ -291,9 +295,9 @@ static void _getServerInfo( int socket, /*-----------------------------------------------------------*/ -static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ) +static IotNetworkError_t _metricsNetworkCreate( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ) { int socket = 0; IotMetricsTcpConnection_t * pTcpConnection = NULL; @@ -307,7 +311,7 @@ static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, ( void ) memset( pTcpConnection, 0x00, sizeof( IotMetricsTcpConnection_t ) ); /* Call the wrapped network create function. */ - status = _networkCreate( pConnectionInfo, + status = _networkCreate( pServerInfo, pCredentialInfo, pConnection ); @@ -341,7 +345,7 @@ static IotNetworkError_t _metricsNetworkCreate( void * pConnectionInfo, /*-----------------------------------------------------------*/ -static IotNetworkError_t _metricsNetworkClose( void * pConnection ) +static IotNetworkError_t _metricsNetworkClose( IotNetworkConnection_t pConnection ) { IotLink_t * pConnectionLink = NULL; IotMetricsTcpConnection_t * pTcpConnection = NULL; diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index 2d98bdc644..ba69d01b54 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -22,7 +22,7 @@ /** * @file iot_network_openssl.c * @brief Implementation of the network interface functions in iot_network.h - * for POSIX systems with OpenSSL. + * for systems with OpenSSL. */ /* The config header is always included first. */ @@ -50,7 +50,7 @@ #include #include -/* POSIX+OpenSSL network include. */ +/* OpenSSL network include. */ #include "iot_network_openssl.h" /* Platform threads include. */ @@ -148,10 +148,10 @@ static void * _networkReceiveThread( void * pArgument ) }; /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pArgument; + _networkConnection_t * pConnection = pArgument; /* Set the file descriptor for poll. */ - fileDescriptor.fd = pNetworkConnection->socket; + fileDescriptor.fd = pConnection->socket; /* Continuously poll the network socket for events. */ while( true ) @@ -161,7 +161,7 @@ static void * _networkReceiveThread( void * pArgument ) if( pollStatus == -1 ) { IotLogError( "Failed to poll socket %d. errno=%d.", - pNetworkConnection->socket, + pConnection->socket, errno ); break; } @@ -172,17 +172,17 @@ static void * _networkReceiveThread( void * pArgument ) ( fileDescriptor.revents & POLLNVAL ) ) { IotLogError( "Detected error on socket %d, receive thread exiting.", - pNetworkConnection->socket ); + pConnection->socket ); break; } /* Invoke the callback function. */ - pNetworkConnection->receiveCallback( pNetworkConnection, - pNetworkConnection->pReceiveContext ); + pConnection->receiveCallback( pConnection, + pConnection->pReceiveContext ); } IotLogDebug( "Network receive thread for socket %d terminating.", - pNetworkConnection->socket ); + pConnection->socket ); return NULL; } @@ -196,7 +196,7 @@ static void * _networkReceiveThread( void * pArgument ) * * @return A connected TCP socket number; `-1` if the DNS lookup failed. */ -static int _dnsLookup( const IotNetworkServerInfo_t * pServerInfo ) +static int _dnsLookup( IotNetworkServerInfo_t pServerInfo ) { IOT_FUNCTION_ENTRY( int, 0 ); int tcpSocket = -1; @@ -402,15 +402,15 @@ static bool _readCredentials( SSL_CTX * pSslContext, /** * @brief Set up TLS on a TCP connection. * - * @param[in] pNetworkConnection An established TCP connection. + * @param[in] pConnection An established TCP connection. * @param[in] pServerName Remote host name, used for server name indication. * @param[in] pOpensslCredentials TLS setup parameters. * * @return #IOT_NETWORK_SUCCESS, #IOT_NETWORK_FAILURE, or #IOT_NETWORK_SYSTEM_ERROR. */ -static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, +static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, const char * pServerName, - const IotNetworkCredentials_t * pOpensslCredentials ) + const IotNetworkCredentials_t pOpensslCredentials ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); SSL_CTX * pSslContext = NULL; @@ -444,9 +444,9 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, } /* Create a new SSL connection context */ - pNetworkConnection->pSslContext = SSL_new( pSslContext ); + pConnection->pSslContext = SSL_new( pSslContext ); - if( pNetworkConnection->pSslContext == NULL ) + if( pConnection->pSslContext == NULL ) { IotLogError( "Failed to create new SSL connection context." ); @@ -455,13 +455,13 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* Enable SSL peer verification. */ IotLogDebug( "Setting SSL_VERIFY_PEER." ); - SSL_set_verify( pNetworkConnection->pSslContext, SSL_VERIFY_PEER, NULL ); + SSL_set_verify( pConnection->pSslContext, SSL_VERIFY_PEER, NULL ); /* Set the socket for the SSL connection. */ - if( SSL_set_fd( pNetworkConnection->pSslContext, - pNetworkConnection->socket ) != 1 ) + if( SSL_set_fd( pConnection->pSslContext, + pConnection->socket ) != 1 ) { - IotLogError( "Failed to set SSL socket %d.", pNetworkConnection->socket ); + IotLogError( "Failed to set SSL socket %d.", pConnection->socket ); IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } @@ -471,7 +471,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogDebug( "Setting ALPN protos." ); - if( ( SSL_set_alpn_protos( pNetworkConnection->pSslContext, + if( ( SSL_set_alpn_protos( pConnection->pSslContext, ( const unsigned char * ) pOpensslCredentials->pAlpnProtos, ( unsigned int ) strlen( pOpensslCredentials->pAlpnProtos ) ) != 0 ) ) { @@ -488,7 +488,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, ( unsigned long ) pOpensslCredentials->maxFragmentLength ); /* Set the maximum send fragment length. */ - if( SSL_set_max_send_fragment( pNetworkConnection->pSslContext, + if( SSL_set_max_send_fragment( pConnection->pSslContext, ( long ) pOpensslCredentials->maxFragmentLength ) != 1 ) { IotLogError( "Failed to set max send fragment length %lu.", @@ -502,7 +502,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, * Note that OpenSSL ignores this setting if it's smaller than the default. */ #if OPENSSL_VERSION_NUMBER > 0x10100000L - SSL_set_default_read_buffer_len( pNetworkConnection->pSslContext, + SSL_set_default_read_buffer_len( pConnection->pSslContext, pOpensslCredentials->maxFragmentLength + SSL3_RT_MAX_ENCRYPTED_OVERHEAD ); #endif @@ -513,7 +513,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, { IotLogDebug( "Setting server name %s for SNI.", pServerName ); - if( SSL_set_tlsext_host_name( pNetworkConnection->pSslContext, + if( SSL_set_tlsext_host_name( pConnection->pSslContext, pServerName ) != 1 ) { IotLogError( "Failed to set server name %s for SNI.", pServerName ); @@ -523,7 +523,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, } /* Perform the TLS handshake. */ - if( SSL_connect( pNetworkConnection->pSslContext ) != 1 ) + if( SSL_connect( pConnection->pSslContext ) != 1 ) { IotLogError( "TLS handshake failed." ); @@ -533,7 +533,7 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, IotLogInfo( "TLS handshake succeeded." ); /* Verify the peer certificate. */ - if( SSL_get_verify_result( pNetworkConnection->pSslContext ) != X509_V_OK ) + if( SSL_get_verify_result( pConnection->pSslContext ) != X509_V_OK ) { IotLogError( "Peer certificate verification failed." ); @@ -553,10 +553,10 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /* Clean up on error. */ if( status != IOT_NETWORK_SUCCESS ) { - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - SSL_free( pNetworkConnection->pSslContext ); - pNetworkConnection->pSslContext = NULL; + SSL_free( pConnection->pSslContext ); + pConnection->pSslContext = NULL; } } @@ -568,25 +568,25 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pNetworkConnection, /** * @brief Close a TLS connection. * - * @param[in] pNetworkConnection The TLS connection to close. + * @param[in] pConnection The TLS connection to close. */ -static void _tlsClose( _networkConnection_t * pNetworkConnection ) +static void _tlsClose( _networkConnection_t * pConnection ) { /* Shut down the TLS connection. */ IotLogInfo( "Shutting down TLS connection." ); /* Lock the SSL context mutex. */ - IotMutex_Lock( &( pNetworkConnection->sslMutex ) ); + IotMutex_Lock( &( pConnection->sslMutex ) ); /* SSL shutdown should be called twice: once to send "close notify" and once * more to receive the peer's "close notify". */ - if( SSL_shutdown( pNetworkConnection->pSslContext ) == 0 ) + if( SSL_shutdown( pConnection->pSslContext ) == 0 ) { IotLogDebug( "\"Close notify\" sent. Awaiting peer response." ); /* The previous call to SSL_shutdown marks the SSL connection as closed. * SSL_shutdown must be called again to read the peer response. */ - if( SSL_shutdown( pNetworkConnection->pSslContext ) != 1 ) + if( SSL_shutdown( pConnection->pSslContext ) != 1 ) { IotLogWarn( "No response from peer." ); } @@ -597,7 +597,7 @@ static void _tlsClose( _networkConnection_t * pNetworkConnection ) } /* Unlock the SSL context mutex. */ - IotMutex_Unlock( &( pNetworkConnection->sslMutex ) ); + IotMutex_Unlock( &( pConnection->sslMutex ) ); IotLogInfo( "TLS connection closed." ); } @@ -660,20 +660,15 @@ void IotNetworkOpenssl_Cleanup( void ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, - void * pCredentialInfo, - void ** pConnection ) +IotNetworkError_t IotNetworkOpenssl_Create( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ) { IOT_FUNCTION_ENTRY( IotNetworkError_t, IOT_NETWORK_SUCCESS ); int tcpSocket = -1; bool sslMutexCreated = false; _networkConnection_t * pNewNetworkConnection = NULL; - /* Cast function parameters to correct types. */ - const IotNetworkServerInfo_t * const pServerInfo = pConnectionInfo; - const IotNetworkCredentials_t * const pOpensslCredentials = pCredentialInfo; - _networkConnection_t ** const pNetworkConnection = ( _networkConnection_t ** const ) pConnection; - /* Allocate memory for a new connection. */ pNewNetworkConnection = IotNetwork_Malloc( sizeof( _networkConnection_t ) ); @@ -703,7 +698,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, pNewNetworkConnection->socket = tcpSocket; /* Set up TLS if credentials are provided. */ - if( pOpensslCredentials != NULL ) + if( pCredentialInfo != NULL ) { IotLogInfo( "Setting up TLS." ); @@ -719,7 +714,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, status = _tlsSetup( pNewNetworkConnection, pServerInfo->pHostName, - pOpensslCredentials ); + pCredentialInfo ); } /* Clean up on error. */ @@ -745,7 +740,7 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, else { /* Set the output parameter. */ - *pNetworkConnection = pNewNetworkConnection; + *pConnection = pNewNetworkConnection; } IOT_FUNCTION_CLEANUP_END(); @@ -753,35 +748,32 @@ IotNetworkError_t IotNetworkOpenssl_Create( void * pConnectionInfo, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, +IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( IotNetworkConnection_t pConnection, IotNetworkReceiveCallback_t receiveCallback, void * pContext ) { int posixError = 0; IotNetworkError_t status = IOT_NETWORK_SUCCESS; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - /* Set the callback and parameter. */ - pNetworkConnection->receiveCallback = receiveCallback; - pNetworkConnection->pReceiveContext = pContext; + pConnection->receiveCallback = receiveCallback; + pConnection->pReceiveContext = pContext; - posixError = pthread_create( &pNetworkConnection->receiveThread, - NULL, - _networkReceiveThread, - pNetworkConnection ); + posixError = pthread_create( &pConnection->receiveThread, + NULL, + _networkReceiveThread, + pConnection ); if( posixError != 0 ) { IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pNetworkConnection->socket, - posixError ); + pConnection->socket, + posixError ); status = IOT_NETWORK_SYSTEM_ERROR; } else { - pNetworkConnection->receiveThreadCreated = true; + pConnection->receiveThreadCreated = true; } return status; @@ -789,7 +781,7 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( void * pConnection, /*-----------------------------------------------------------*/ -size_t IotNetworkOpenssl_Send( void * pConnection, +size_t IotNetworkOpenssl_Send( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ) { @@ -800,21 +792,18 @@ size_t IotNetworkOpenssl_Send( void * pConnection, .revents = 0 }; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - IotLogDebug( "Sending %lu bytes over socket %d.", ( unsigned long ) messageLength, - pNetworkConnection->socket ); + pConnection->socket ); /* Set the file descriptor for poll. */ - fileDescriptor.fd = pNetworkConnection->socket; + fileDescriptor.fd = pConnection->socket; /* The SSL mutex must be locked to protect an SSL context from concurrent * access. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - IotMutex_Lock( &( pNetworkConnection->sslMutex ) ); + IotMutex_Lock( &( pConnection->sslMutex ) ); } /* Check that it's possible to send data right now. */ @@ -822,15 +811,15 @@ size_t IotNetworkOpenssl_Send( void * pConnection, { /* Use OpenSSL's SSL_write() function or the POSIX send() function based on * whether the SSL connection context is set up. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - bytesSent = SSL_write( pNetworkConnection->pSslContext, + bytesSent = SSL_write( pConnection->pSslContext, pMessage, ( int ) messageLength ); } else { - bytesSent = ( int ) send( pNetworkConnection->socket, + bytesSent = ( int ) send( pConnection->socket, pMessage, messageLength, 0 ); @@ -838,13 +827,13 @@ size_t IotNetworkOpenssl_Send( void * pConnection, } else { - IotLogError( "Not possible to send on socket %d.", pNetworkConnection->socket ); + IotLogError( "Not possible to send on socket %d.", pConnection->socket ); } /* Unlock the SSL context mutex. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - IotMutex_Unlock( &( pNetworkConnection->sslMutex ) ); + IotMutex_Unlock( &( pConnection->sslMutex ) ); } /* Log the number of bytes sent. */ @@ -871,25 +860,22 @@ size_t IotNetworkOpenssl_Send( void * pConnection, /*-----------------------------------------------------------*/ -size_t IotNetworkOpenssl_Receive( void * pConnection, +size_t IotNetworkOpenssl_Receive( IotNetworkConnection_t pConnection, uint8_t * pBuffer, size_t bytesRequested ) { int receiveStatus = 0; size_t bytesRead = 0, bytesRemaining = bytesRequested; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - IotLogDebug( "Blocking to wait for %lu bytes on socket %d.", ( unsigned long ) bytesRequested, - pNetworkConnection->socket ); + pConnection->socket ); /* The SSL mutex must be locked to protect an SSL context from concurrent * access. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - IotMutex_Lock( &( pNetworkConnection->sslMutex ) ); + IotMutex_Lock( &( pConnection->sslMutex ) ); } /* Loop until all bytes are received. */ @@ -897,15 +883,15 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, { /* Use OpenSSL's SSL_read() function or the POSIX recv() function based on * whether the SSL connection context is set up. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - receiveStatus = SSL_read( pNetworkConnection->pSslContext, + receiveStatus = SSL_read( pConnection->pSslContext, pBuffer + bytesRead, ( int ) bytesRemaining ); } else { - receiveStatus = ( int ) recv( pNetworkConnection->socket, + receiveStatus = ( int ) recv( pConnection->socket, pBuffer + bytesRead, bytesRemaining, 0 ); @@ -915,7 +901,7 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, if( receiveStatus <= 0 ) { IotLogError( "Error receiving from socket %d.", - pNetworkConnection->socket ); + pConnection->socket ); break; } @@ -927,9 +913,9 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, } /* Unlock the SSL context mutex. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - IotMutex_Unlock( &( pNetworkConnection->sslMutex ) ); + IotMutex_Unlock( &( pConnection->sslMutex ) ); } /* Check how many bytes were read. */ @@ -950,28 +936,25 @@ size_t IotNetworkOpenssl_Receive( void * pConnection, /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) +IotNetworkError_t IotNetworkOpenssl_Close( IotNetworkConnection_t pConnection ) { - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - /* If TLS was set up for this connection, clean it up. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - _tlsClose( pNetworkConnection ); + _tlsClose( pConnection ); } /* Shut down the connection. */ - if( shutdown( pNetworkConnection->socket, SHUT_RDWR ) != 0 ) + if( shutdown( pConnection->socket, SHUT_RDWR ) != 0 ) { IotLogWarn( "Could not shut down socket %d. errno=%d.", - pNetworkConnection->socket, + pConnection->socket, errno ); } else { IotLogInfo( "Connection (socket %d) shut down.", - pNetworkConnection->socket ); + pConnection->socket ); } return IOT_NETWORK_SUCCESS; @@ -979,79 +962,73 @@ IotNetworkError_t IotNetworkOpenssl_Close( void * pConnection ) /*-----------------------------------------------------------*/ -IotNetworkError_t IotNetworkOpenssl_Destroy( void * pConnection ) +IotNetworkError_t IotNetworkOpenssl_Destroy( IotNetworkConnection_t pConnection ) { int posixError = 0; - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - /* Check if a receive thread needs to be cleaned up. */ - if( pNetworkConnection->receiveThreadCreated == true ) + if( pConnection->receiveThreadCreated == true ) { - if( pthread_self() == pNetworkConnection->receiveThread ) + if( pthread_self() == pConnection->receiveThread ) { /* If this function is being called from the receive thread, detach * it so no other thread needs to clean it up. */ - posixError = pthread_detach( pNetworkConnection->receiveThread ); + posixError = pthread_detach( pConnection->receiveThread ); if( posixError != 0 ) { IotLogWarn( "Failed to detach receive thread for socket %d. errno=%d.", - pNetworkConnection->socket, + pConnection->socket, posixError ); } } else { /* Wait for the receive thread to exit. */ - posixError = pthread_join( pNetworkConnection->receiveThread, NULL ); + posixError = pthread_join( pConnection->receiveThread, NULL ); if( posixError != 0 ) { IotLogWarn( "Failed to join receive thread for socket %d. errno=%d.", - pNetworkConnection->socket, + pConnection->socket, posixError ); } } } /* Free the memory used by the TLS connection, if any. */ - if( pNetworkConnection->pSslContext != NULL ) + if( pConnection->pSslContext != NULL ) { - SSL_free( pNetworkConnection->pSslContext ); + SSL_free( pConnection->pSslContext ); /* Destroy the SSL context mutex. */ - IotMutex_Destroy( &( pNetworkConnection->sslMutex ) ); + IotMutex_Destroy( &( pConnection->sslMutex ) ); } /* Close the socket file descriptor. */ - if( close( pNetworkConnection->socket ) != 0 ) + if( close( pConnection->socket ) != 0 ) { IotLogWarn( "Could not close socket %d. errno=%d.", - pNetworkConnection->socket, + pConnection->socket, errno ); } else { IotLogInfo( "(Socket %d) Socket closed.", - pNetworkConnection->socket ); + pConnection->socket ); } /* Free the connection. */ - IotNetwork_Free( pNetworkConnection ); + IotNetwork_Free( pConnection ); return IOT_NETWORK_SUCCESS; } /*-----------------------------------------------------------*/ -int IotNetworkOpenssl_GetSocket( void * pConnection ) +int IotNetworkOpenssl_GetSocket( IotNetworkConnection_t pConnection ) { - /* Cast function parameter to correct type. */ - _networkConnection_t * const pNetworkConnection = pConnection; - - return pNetworkConnection->socket; + return pConnection->socket; } /*-----------------------------------------------------------*/ diff --git a/ports/posix/include/iot_platform_types_posix.h b/ports/posix/include/iot_platform_types_posix.h index 655f76bac6..2c985e05e4 100644 --- a/ports/posix/include/iot_platform_types_posix.h +++ b/ports/posix/include/iot_platform_types_posix.h @@ -59,4 +59,30 @@ typedef struct _IotSystemTimer void ( * threadRoutine )( void * ); /**< @brief Thread function to run on timer expiration. */ } _IotSystemTimer_t; +/** + * @cond DOXYGEN_IGNORE + * Doxygen should ignore this section. + * + * Forward declarations of the network types. + */ +struct IotNetworkServerInfo; +struct IotNetworkCredentials; +struct _networkConnection; +/** @endcond */ + +/** + * @brief The format for remote server host and port on this system. + */ +typedef struct IotNetworkServerInfo * _IotNetworkServerInfo_t; + +/** + * @brief The format for network credentials on this system. + */ +typedef struct IotNetworkCredentials * _IotNetworkCredentials_t; + +/** + * @brief The handle of a network connection on this system. + */ +typedef struct _networkConnection * _IotNetworkConnection_t; + #endif /* ifndef IOT_PLATFORM_TYPES_POSIX_H_ */ diff --git a/tests/iot_config.h b/tests/iot_config.h index 6190f1e1d7..687393b7ae 100644 --- a/tests/iot_config.h +++ b/tests/iot_config.h @@ -158,11 +158,6 @@ #define Iot_DefaultFree unity_free_mt #endif /* if IOT_STATIC_MEMORY_ONLY == 0 */ -/* Network types to use in the tests. These are forward declarations. */ -typedef struct _networkConnection IotTestNetworkConnection_t; -typedef struct IotNetworkServerInfo IotTestNetworkServerInfo_t; -typedef struct IotNetworkCredentials IotTestNetworkCredentials_t; - /* Choose the appropriate network abstraction implementation. */ #if IOT_NETWORK_USE_OPENSSL == 1 /* OpenSSL network include. */ From 8f1eea9590066e5c3d06d8fcdad879757766d6bc Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Mon, 4 Nov 2019 10:57:19 -0800 Subject: [PATCH 310/844] Make stepOut implementation more resilient (#623) --- .../cbor/iot_serializer_tinycbor_decoder.c | 13 +++- .../test/unit/iot_tests_serializer_cbor.c | 59 +++++++++++++++++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c index 65c92163f1..9b9a91a189 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c @@ -496,8 +496,19 @@ static IotSerializerError_t _stepOut( IotSerializerDecoderIterator_t iterator, _cborValueWrapper_t * pOuterCborValueWrapper = _castDecoderObjectToCborValue( pDecoderObject ); _cborValueWrapper_t * pInnerCborValueWrapper = _castDecoderIteratorToCborValue( iterator ); + + CborValue outerCborValueCopy; + + /* Clone the underlying CborValue object of the parent container, so that the original CborValue object's state/data + * is unaffected with this function's operation. This allows the container's decoder object re-usable for accessor + * and iteration operations. + * + * Note: By performing a cbor_value_leave_container() operation on the cloned copy, only the state/data of the copy + * is changed.*/ + memcpy( &outerCborValueCopy, &pOuterCborValueWrapper->cborValue, sizeof( CborValue ) ); + cborError = cbor_value_leave_container( - &pOuterCborValueWrapper->cborValue, + &outerCborValueCopy, &pInnerCborValueWrapper->cborValue ); if( cborError == CborNoError ) diff --git a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c index 87f8388f7e..cea483a5e9 100644 --- a/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c +++ b/libraries/standard/serializer/test/unit/iot_tests_serializer_cbor.c @@ -433,16 +433,16 @@ TEST( Serializer_Unit_CBOR, Encoder_map_nest_array ) static const uint8_t _testEncodedNestedMap[] = { - 0xA2, /* # map(2) */ - 0x61, /* # text(1) */ + 0xA2, /* # map(2) */ + 0x61, /* # text(1) */ 0x31, /* # "1" */ - 0xA1, /* # map(1) */ + 0xA1, /* # map(1) */ 0x61, /* # text(1) */ 0x41, /* # "A" */ 0x0A, /* # unsigned(10) */ - 0x61, /* # text(1) */ + 0x61, /* # text(1) */ 0x33, /* # "3" */ - 0xF4, /* # false */ + 0xF4, /* # false */ }; TEST_GROUP( Serializer_Decoder_Unit_CBOR ); @@ -463,6 +463,7 @@ TEST_GROUP_RUNNER( Serializer_Decoder_Unit_CBOR ) { RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderObjectWithNestedMap ); RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderIteratorWithNestedMap ); + RUN_TEST_CASE( Serializer_Decoder_Unit_CBOR, TestDecoderObjectReuseAfterIteration ); } TEST( Serializer_Decoder_Unit_CBOR, TestDecoderObjectWithNestedMap ) @@ -637,3 +638,51 @@ TEST( Serializer_Decoder_Unit_CBOR, TestDecoderIteratorWithNestedMap ) _pCborDecoder->destroy( &nestedMapDecoder ); _pCborDecoder->destroy( &outerDecoder2 ); } + +/* Verifies that a container decoder object remains valid for re-use after a complete round of iterating + * through its contents */ +TEST( Serializer_Decoder_Unit_CBOR, TestDecoderObjectReuseAfterIteration ) +{ + IotSerializerDecoderObject_t mapDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderIterator_t iterator1 = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; + IotSerializerDecoderObject_t valueDecoder = IOT_SERIALIZER_DECODER_OBJECT_INITIALIZER; + IotSerializerDecoderIterator_t iterator2 = IOT_SERIALIZER_DECODER_ITERATOR_INITIALIZER; + + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->init( &mapDecoder, + _testEncodedNestedMap, + sizeof( _testEncodedNestedMap ) ) ); + + /* Obtain an iterator to the contents of the map. */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &mapDecoder, + &iterator1 ) ); + + + /* Undergo one round of iteration through the map */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator1 ) ); + + /* End the iteration by invalidating the iterator */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator1, &mapDecoder ) ); + + /* NOW check that the decoder object of the map is still valid! */ + /* Sanity check with "find()" function". */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->find( &mapDecoder, "3", &valueDecoder ) ); + TEST_ASSERT_EQUAL( false, valueDecoder.u.value.u.booleanValue ); + + /* Sanity check with another round of iteration! */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepIn( &mapDecoder, + &iterator2 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->next( iterator2 ) ); + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator2, &mapDecoder ) ); + + /* End the second round of iteration */ + TEST_ASSERT_EQUAL( IOT_SERIALIZER_SUCCESS, _pCborDecoder->stepOut( iterator1, &mapDecoder ) ); + + _pCborDecoder->destroy( &valueDecoder ); + _pCborDecoder->destroy( &mapDecoder ); +} From 471888312c7d740e7e7e75d56d034a301b0dbed0 Mon Sep 17 00:00:00 2001 From: Jeff Kent Date: Mon, 4 Nov 2019 21:55:11 -0600 Subject: [PATCH 311/844] Allow override for STACK_SIZE and PRIORITY defines (#624) --- libraries/platform/types/iot_platform_types.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/platform/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h index 53fa5a1553..12e86f3c3f 100644 --- a/libraries/platform/types/iot_platform_types.h +++ b/libraries/platform/types/iot_platform_types.h @@ -39,12 +39,16 @@ /** * @brief A value representing the system default for new thread priority. */ +#ifndef IOT_THREAD_DEFAULT_PRIORITY #define IOT_THREAD_DEFAULT_PRIORITY 0 +#endif /** * @brief A value representhing the system default for new thread stack size. */ +#ifndef IOT_THREAD_DEFAULT_STACK_SIZE #define IOT_THREAD_DEFAULT_STACK_SIZE 0 +#endif /** * @ingroup platform_datatypes_handles From 6a2847ecb1bd9135572d30e53bd8298eeca00bba Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Wed, 6 Nov 2019 14:58:18 -0800 Subject: [PATCH 312/844] Added task pool general failure enum value. (#628) * Added task pool general failure enum value. * Update IotTaskPool_strerror with new error value. --- .../common/include/types/iot_taskpool_types.h | 69 ++----------------- libraries/standard/common/src/iot_taskpool.c | 4 ++ 2 files changed, 9 insertions(+), 64 deletions(-) diff --git a/libraries/standard/common/include/types/iot_taskpool_types.h b/libraries/standard/common/include/types/iot_taskpool_types.h index ac017ddf7b..dc796b7d5e 100644 --- a/libraries/standard/common/include/types/iot_taskpool_types.h +++ b/libraries/standard/common/include/types/iot_taskpool_types.h @@ -51,97 +51,38 @@ typedef enum IotTaskPoolError { /** * @brief Task pool operation completed successfully. - * - * Functions that may return this value: - * - @ref taskpool_function_createsystemtaskpool - * - @ref taskpool_function_create - * - @ref taskpool_function_destroy - * - @ref taskpool_function_setmaxthreads - * - @ref taskpool_function_createjob - * - @ref taskpool_function_createrecyclablejob - * - @ref taskpool_function_destroyrecyclablejob - * - @ref taskpool_function_recyclejob - * - @ref taskpool_function_schedule - * - @ref taskpool_function_scheduledeferred - * - @ref taskpool_function_getstatus - * - @ref taskpool_function_trycancel - * */ IOT_TASKPOOL_SUCCESS = 0, /** * @brief Task pool operation failed because at least one parameter is invalid. - * - * Functions that may return this value: - * - @ref taskpool_function_createsystemtaskpool - * - @ref taskpool_function_create - * - @ref taskpool_function_destroy - * - @ref taskpool_function_setmaxthreads - * - @ref taskpool_function_createjob - * - @ref taskpool_function_createrecyclablejob - * - @ref taskpool_function_destroyrecyclablejob - * - @ref taskpool_function_recyclejob - * - @ref taskpool_function_schedule - * - @ref taskpool_function_scheduledeferred - * - @ref taskpool_function_getstatus - * - @ref taskpool_function_trycancel - * */ IOT_TASKPOOL_BAD_PARAMETER, /** * @brief Task pool operation failed because it is illegal. - * - * Functions that may return this value: - * - @ref taskpool_function_createjob - * - @ref taskpool_function_createrecyclablejob - * - @ref taskpool_function_destroyrecyclablejob - * - @ref taskpool_function_recyclejob - * - @ref taskpool_function_schedule - * - @ref taskpool_function_scheduledeferred - * - @ref taskpool_function_trycancel - * */ IOT_TASKPOOL_ILLEGAL_OPERATION, /** * @brief Task pool operation failed because allocating memory failed. - * - * Functions that may return this value: - * - @ref taskpool_function_createsystemtaskpool - * - @ref taskpool_function_create - * - @ref taskpool_function_setmaxthreads - * - @ref taskpool_function_createrecyclablejob - * - @ref taskpool_function_scheduledeferred - * - @ref taskpool_function_getstatus - * */ IOT_TASKPOOL_NO_MEMORY, /** * @brief Task pool operation failed because of an invalid parameter. - * - * Functions that may return this value: - * - @ref taskpool_function_setmaxthreads - * - @ref taskpool_function_createrecyclablejob - * - @ref taskpool_function_destroyrecyclablejob - * - @ref taskpool_function_recyclejob - * - @ref taskpool_function_schedule - * - @ref taskpool_function_scheduledeferred - * - @ref taskpool_function_getstatus - * - @ref taskpool_function_trycancel - * */ IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS, /** * @brief Task pool cancellation failed. - * - * Functions that may return this value: - * - @ref taskpool_function_trycancel - * */ IOT_TASKPOOL_CANCEL_FAILED, + + /** + * @brief Task pool operation general failure. + */ + IOT_TASKPOOL_GENERAL_FAILURE, } IotTaskPoolError_t; /** diff --git a/libraries/standard/common/src/iot_taskpool.c b/libraries/standard/common/src/iot_taskpool.c index 04b34d646c..76c24c69e9 100644 --- a/libraries/standard/common/src/iot_taskpool.c +++ b/libraries/standard/common/src/iot_taskpool.c @@ -922,6 +922,10 @@ const char * IotTaskPool_strerror( IotTaskPoolError_t status ) pMessage = "CANCEL FAILED"; break; + case IOT_TASKPOOL_GENERAL_FAILURE: + pMessage = "GENERAL FAILURE"; + break; + default: pMessage = "INVALID STATUS"; break; From f8d9ed872bedeeae8d2edfe025ab7263ca261a21 Mon Sep 17 00:00:00 2001 From: Sarena Meas <6563840+sarenameas@users.noreply.github.com> Date: Wed, 6 Nov 2019 22:00:17 -0800 Subject: [PATCH 313/844] Fix some spelling, grammar, and whitespace (#625) * Fix some spelling, grammar, and whitespace in logging and taskpool comments. --- libraries/platform/types/iot_platform_types.h | 4 ++-- libraries/standard/common/include/iot_taskpool.h | 4 ++-- .../standard/common/include/types/iot_taskpool_types.h | 8 ++++---- libraries/standard/common/src/iot_logging.c | 2 +- libraries/standard/common/src/iot_taskpool.c | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/platform/types/iot_platform_types.h b/libraries/platform/types/iot_platform_types.h index 12e86f3c3f..4e4c7092ae 100644 --- a/libraries/platform/types/iot_platform_types.h +++ b/libraries/platform/types/iot_platform_types.h @@ -40,14 +40,14 @@ * @brief A value representing the system default for new thread priority. */ #ifndef IOT_THREAD_DEFAULT_PRIORITY -#define IOT_THREAD_DEFAULT_PRIORITY 0 + #define IOT_THREAD_DEFAULT_PRIORITY 0 #endif /** * @brief A value representhing the system default for new thread stack size. */ #ifndef IOT_THREAD_DEFAULT_STACK_SIZE -#define IOT_THREAD_DEFAULT_STACK_SIZE 0 + #define IOT_THREAD_DEFAULT_STACK_SIZE 0 #endif /** diff --git a/libraries/standard/common/include/iot_taskpool.h b/libraries/standard/common/include/iot_taskpool.h index e52ab728cd..2defbd37fd 100644 --- a/libraries/standard/common/include/iot_taskpool.h +++ b/libraries/standard/common/include/iot_taskpool.h @@ -83,8 +83,8 @@ * * This function should be called once by the application to initialize the one single instance of the system task pool. * An application should initialize the system task pool early in the boot sequence, before initializing any other library - * that uses the system task pool. such as MQTT, Shadow, Defernder, etc.. An application should also initialize the system - * task pool before posting any jobs. Early initialization it typically easy to accomplish by creating the system task pool + * that uses the system task pool, such as MQTT, Shadow, Defender, etc. An application should also initialize the system + * task pool before posting any jobs. Early initialization is typically easy to accomplish by creating the system task pool * before the scheduler is started. * * This function does not allocate memory to hold the task pool data structures and state, but it diff --git a/libraries/standard/common/include/types/iot_taskpool_types.h b/libraries/standard/common/include/types/iot_taskpool_types.h index dc796b7d5e..6416968da0 100644 --- a/libraries/standard/common/include/types/iot_taskpool_types.h +++ b/libraries/standard/common/include/types/iot_taskpool_types.h @@ -263,13 +263,13 @@ typedef struct IotTaskPoolInfo */ /* @[define_taskpool_initializers] */ /** @brief Initializer for a small #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_SMALL { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +#define IOT_TASKPOOL_INFO_INITIALIZER_SMALL { .minThreads = 1, .maxThreads = 1, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /** @brief Initializer for a medium #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +#define IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM { .minThreads = 1, .maxThreads = 2, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /** @brief Initializer for a large #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_LARGE { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +#define IOT_TASKPOOL_INFO_INITIALIZER_LARGE { .minThreads = 2, .maxThreads = 3, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /** @brief Initializer for a very large #IotTaskPoolInfo_t. */ -#define IOT_TASKPOOL_INFO_INITIALIZER_XLARGE { .minThreads = 2, .maxThreads = 4, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } +#define IOT_TASKPOOL_INFO_INITIALIZER_XLARGE { .minThreads = 2, .maxThreads = 4, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY } /** @brief Initializer for a typical #IotTaskPoolInfo_t. */ #define IOT_TASKPOOL_INFO_INITIALIZER IOT_TASKPOOL_INFO_INITIALIZER_MEDIUM /** @brief Initializer for a #IotTaskPool_t. */ diff --git a/libraries/standard/common/src/iot_logging.c b/libraries/standard/common/src/iot_logging.c index 985fd659f0..ce8a2690e1 100644 --- a/libraries/standard/common/src/iot_logging.c +++ b/libraries/standard/common/src/iot_logging.c @@ -214,7 +214,7 @@ void IotLog_Generic( int libraryLogSetting, /* Estimate the amount of buffer needed for this log message. */ if( ( pLogConfig == NULL ) || ( pLogConfig->hideLibraryName == false ) ) { - /* Add size of library name if requested. Add 2 to accomodate "[]". */ + /* Add size of library name if requested. Add 2 to accommodate "[]". */ bufferSize += strlen( pLibraryName ) + 2; } diff --git a/libraries/standard/common/src/iot_taskpool.c b/libraries/standard/common/src/iot_taskpool.c index 76c24c69e9..b37042c839 100644 --- a/libraries/standard/common/src/iot_taskpool.c +++ b/libraries/standard/common/src/iot_taskpool.c @@ -1586,12 +1586,12 @@ static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, break; case IOT_TASKPOOL_STATUS_COMPLETED: - /* Log mesggesong purposes. */ + /* Log message for debug purposes. */ IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." ); break; default: - /* Log mesggesong purposes. */ + /* Log message for debug purposes. */ IotLogError( "Attempt to cancel a job with an undefined state." ); break; } From 53eab7d0726be9325a40edde220a47bfd5772706 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 7 Nov 2019 16:21:45 -0500 Subject: [PATCH 314/844] JSON parser fix for all the CBMC proofs (#629) --- .../standard/serializer/src/iot_json_utils.c | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/libraries/standard/serializer/src/iot_json_utils.c b/libraries/standard/serializer/src/iot_json_utils.c index f976ef91fb..077db4b7d2 100644 --- a/libraries/standard/serializer/src/iot_json_utils.c +++ b/libraries/standard/serializer/src/iot_json_utils.c @@ -34,6 +34,11 @@ /* JSON utilities include. */ #include "iot_json_utils.h" +#define IS_QUOTE( str, idx ) \ + ( str[ idx ] == '"' && ( idx == 0 || str[ idx - 1 ] != '\\' ) ) +#define IS_WHITESPACE( str, idx ) \ + ( str[ idx ] == ' ' || str[ idx ] == '\n' || str[ idx ] == '\r' || str[ idx ] == '\t' ) + /*-----------------------------------------------------------*/ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, @@ -48,6 +53,13 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, char openCharacter = '\0', closeCharacter = '\0'; int nestingLevel = 0; + /* Validate all the arguements.*/ + if( ( pJsonDocument == NULL ) || ( pJsonKey == NULL ) || + ( jsonDocumentLength == 0 ) || ( jsonKeyLength == 0 ) ) + { + return false; + } + /* Ensure the JSON document is long enough to contain the key/value pair. At * the very least, a JSON key/value pair must contain the key and the 6 * characters {":""} */ @@ -63,23 +75,21 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, { /* If the first character in the key is found and there's an unescaped double * quote after the key length, do a string compare for the key. */ - if( ( pJsonDocument[ i ] == pJsonKey[ 0 ] ) && - ( pJsonDocument[ i + jsonKeyLength ] == '\"' ) && - ( pJsonDocument[ i + jsonKeyLength - 1 ] != '\\' ) && - ( strncmp( pJsonDocument + i, + if( ( IS_QUOTE( pJsonDocument, i ) ) && + ( IS_QUOTE( pJsonDocument, i + 1 + jsonKeyLength ) ) && + ( pJsonDocument[ i + 1 ] == pJsonKey[ 0 ] ) && + ( strncmp( pJsonDocument + i + 1, pJsonKey, jsonKeyLength ) == 0 ) ) { /* Key found; this is a potential match. */ /* Skip the characters in the JSON key and closing double quote. */ - i += jsonKeyLength + 1; + /* While loop guarantees that i < jsonDocumentLength - 1 */ + i += jsonKeyLength + 2; /* Skip all whitespace characters between the closing " and the : */ - while( pJsonDocument[ i ] == ' ' || - pJsonDocument[ i ] == '\n' || - pJsonDocument[ i ] == '\r' || - pJsonDocument[ i ] == '\t' ) + while( IS_WHITESPACE( pJsonDocument, i ) ) { i++; @@ -102,11 +112,14 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, i++; } + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + /* Skip all whitespace characters between : and the first character in the value. */ - while( pJsonDocument[ i ] == ' ' || - pJsonDocument[ i ] == '\n' || - pJsonDocument[ i ] == '\r' || - pJsonDocument[ i ] == '\t' ) + while( IS_WHITESPACE( pJsonDocument, i ) ) { i++; @@ -134,6 +147,12 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, /* Skip the opening double quote. */ i++; + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + /* Add the length of all characters in the JSON string. */ while( pJsonDocument[ i ] != '\"' ) { @@ -182,10 +201,7 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, pJsonDocument[ i ] != '}' ) { /* Any whitespace before a , or } means the JSON document is invalid. */ - if( ( pJsonDocument[ i ] == ' ' ) || - ( pJsonDocument[ i ] == '\n' ) || - ( pJsonDocument[ i ] == '\r' ) || - ( pJsonDocument[ i ] == '\t' ) ) + if( IS_WHITESPACE( pJsonDocument, i ) ) { return false; } @@ -212,6 +228,12 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, /* Skip the opening character. */ i++; + /* If the end of the document is reached, this isn't a match. */ + if( i >= jsonDocumentLength ) + { + return false; + } + /* Add the length of all characters in the JSON object or array. This * includes the length of nested objects. */ while( pJsonDocument[ i ] != closeCharacter || From fe6d27478d8ab21052cb391fe26ff6d263e31066 Mon Sep 17 00:00:00 2001 From: Gary Wicker Date: Tue, 12 Nov 2019 14:44:08 -0800 Subject: [PATCH 315/844] Add close callback to network interface (#634) --- libraries/platform/iot_network.h | 324 +++++++++++++-------- ports/common/include/iot_network_mbedtls.h | 32 +- ports/common/include/iot_network_openssl.h | 8 + ports/common/src/iot_network_mbedtls.c | 40 ++- ports/common/src/iot_network_metrics.c | 58 ++-- ports/common/src/iot_network_openssl.c | 71 ++++- 6 files changed, 348 insertions(+), 185 deletions(-) diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 77eba836b6..651f0d0ac1 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -48,6 +48,19 @@ typedef enum IotNetworkError IOT_NETWORK_SYSTEM_ERROR /**< An error occurred when calling a system API. */ } IotNetworkError_t; +/** + * @ingroup platform_datatypes_enums + * @brief Disconnect reasons for [the network close callback](@ref platform_network_function_closecallback). + */ +typedef enum IotNetworkCloseReason +{ + IOT_NETWORK_NOT_CLOSED = 0, /**< Not closed, still open */ + IOT_NETWORK_SERVER_CLOSED, /**< Server closed connection. */ + IOT_NETWORK_TRANSPORT_FAILURE, /**< Transport failed. */ + IOT_NETWORK_CLIENT_CLOSED, /**< Client closed connection. */ + IOT_NETWORK_UNKNOWN_CLOSED /**< Unknown close reason. */ +} IotNetworkCloseReason_t; + /** * @page platform_network_functions Networking * @brief Functions of the network abstraction component. @@ -62,21 +75,25 @@ typedef enum IotNetworkError * Together, they represent a network stack. * - @functionname{platform_network_function_create} * - @functionname{platform_network_function_setreceivecallback} + * - @functionname{platform_network_function_setclosecallback} * - @functionname{platform_network_function_send} * - @functionname{platform_network_function_receive} * - @functionname{platform_network_function_close} * - @functionname{platform_network_function_destroy} * - @functionname{platform_network_function_receivecallback} + * - @functionname{platform_network_function_closecallback} */ /** * @functionpage{IotNetworkInterface_t::create,platform_network,create} * @functionpage{IotNetworkInterface_t::setReceiveCallback,platform_network,setreceivecallback} + * @functionpage{IotNetworkInterface_t::setCloseCallback,platform_network,setclosecallback} * @functionpage{IotNetworkInterface_t::send,platform_network,send} * @functionpage{IotNetworkInterface_t::receive,platform_network,receive} * @functionpage{IotNetworkInterface_t::close,platform_network,close} * @functionpage{IotNetworkInterface_t::destroy,platform_network,destroy} * @functionpage{IotNetworkReceiveCallback_t,platform_network,receivecallback} + * @functionpage{IotNetworkReceiveCallback_t,platform_network,closecallback} */ /** @@ -94,6 +111,178 @@ typedef void ( * IotNetworkReceiveCallback_t )( IotNetworkConnection_t pConnecti void * pContext ); /* @[declare_platform_network_receivecallback] */ +/** + * @brief Provide an asynchronous notification of network closing + * + * A function with this signature may be set with @ref platform_network_function_setclosecallback + * to be invoked when the network connection is closed. + * + * @param[in] pConnection The connection that was closed, defined by + * the network stack. + * @param[in] reason The reason the connection was closed + * @param[in] pContext The third argument passed to @ref platform_network_function_setclosecallback. + */ +/* @[declare_platform_network_closecallback] */ +typedef void ( * IotNetworkCloseCallback_t )( IotNetworkConnection_t pConnection, + IotNetworkCloseReason_t reason, + void * pContext ); +/* @[declare_platform_network_closecallback] */ + +/** + * @brief Create a new network connection. + * + * This function allocates resources and establishes a new network connection. + * @param[in] pServerInfo Represents information needed to set up the + * new connection, defined by the network stack. + * @param[in] pCredentialInfo Represents information needed to secure the + * new connection, defined by the network stack. + * @param[out] pConnection Set to represent a new connection, defined by the + * network stack. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + */ +/* @[declare_platform_network_create] */ +typedef IotNetworkError_t ( * IotNetworkCreate_t )( IotNetworkServerInfo_t pServerInfo, + IotNetworkCredentials_t pCredentialInfo, + IotNetworkConnection_t * pConnection ); +/* @[declare_platform_network_create] */ + +/** + * @brief Register an @ref platform_network_function_receivecallback. + * + * Sets an @ref platform_network_function_receivecallback to be called + * asynchronously when data arrives on the network. The network stack + * should invoke this function "as if" it were the thread routine of a + * detached thread. + * + * Each network connection may only have one receive callback at any time. + * @ref platform_network_function_close is expected to remove any active + * receive callbacks. + * + * @param[in] pConnection The connection to associate with the receive callback. + * @param[in] receiveCallback The function to invoke for incoming network data. + * @param[in] pContext A value to pass as the first parameter to the receive callback. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @see platform_network_function_receivecallback + */ +/* @[declare_platform_network_setreceivecallback] */ +typedef IotNetworkError_t ( * IotNetworkSetReceiveCallback_t )( IotNetworkConnection_t pConnection, + IotNetworkReceiveCallback_t receiveCallback, + void * pContext ); +/* @[declare_platform_network_setreceivecallback] */ + +/** + * @brief Register an @ref platform_network_function_closecallback. + * + * Sets an @ref platform_network_function_closecallback to be called + * asynchronously when the network connection closes. The network stack + * should invoke this function "as if" it were the thread routine of a + * detached thread. + * + * Each network connection may only have one close callback at any time. + * @ref platform_network_function_close is expected to remove any active + * close callbacks. + * + * @param[in] pConnection The connection to associate with the close callback. + * @param[in] receiveCallback The function to invoke for incoming network close events. + * @param[in] pContext A value to pass as the first parameter to the close callback. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @see platform_network_function_closecallback + */ +/* @[declare_platform_network_setclosecallback] */ +typedef IotNetworkError_t ( * IotNetworkSetCloseCallback_t )( IotNetworkConnection_t pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ); +/* @[declare_platform_network_setclosecallback] */ + +/** + * @brief Send data over a return connection. + * + * Attempts to transmit `messageLength` bytes of `pMessage` across the + * connection represented by `pConnection`. Returns the number of bytes + * actually sent, `0` on failure. + * + * @param[in] pConnection The connection used to send data, defined by the + * network stack. + * @param[in] pMessage The message to send. + * @param[in] messageLength The length of `pMessage`. + * + * @return The number of bytes successfully sent, `0` on failure. + */ +/* @[declare_platform_network_send] */ +typedef size_t ( * IotNetworkSend_t )( IotNetworkConnection_t pConnection, + const uint8_t * pMessage, + size_t messageLength ); +/* @[declare_platform_network_send] */ + +/** + * @brief Block and wait for incoming network data. + * + * Wait for a message of size `bytesRequested` to arrive on the network and + * place it in `pBuffer`. + * + * @param[in] pConnection The connection to wait on, defined by the network + * stack. + * @param[out] pBuffer Where to place the incoming network data. This buffer + * must be at least `bytesRequested` in size. + * @param[in] bytesRequested How many bytes to wait for. `pBuffer` must be at + * least this size. + * + * @return The number of bytes successfully received. This should be + * `bytesRequested` when successful. Any other value may indicate an error. + */ +/* @[declare_platform_network_receive] */ +typedef size_t ( * IotNetworkReceive_t )( IotNetworkConnection_t pConnection, + uint8_t * pBuffer, + size_t bytesRequested ); +/* @[declare_platform_network_receive] */ + +/** + * @brief Close a network connection. + * + * This function closes the connection, but does not release the resources + * used by the connection. This allows calls to other networking functions + * to return an error and handle a closed connection without the risk of + * crashing. Once it can be guaranteed that `pConnection` will no longer be + * used, the connection can be destroyed with @ref platform_network_function_destroy. + * + * In addition to closing the connection, this function should also remove + * any active [receive callback](@ref platform_network_function_receivecallback). + * + * @param[in] pConnection The network connection to close, defined by the + * network stack. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @note It must be safe to call this function on an already-closed connection. + */ +/* @[declare_platform_network_close] */ +typedef IotNetworkError_t ( * IotNetworkClose_t )( IotNetworkConnection_t pConnection ); +/* @[declare_platform_network_close] */ + +/** + * @brief Free resources used by a network connection. + * + * This function releases the resources of a closed connection. It should be + * called after @ref platform_network_function_close. + * + * @param[in] pConnection The network connection to destroy, defined by + * the network stack. + * + * @return Any #IotNetworkError_t, as defined by the network stack. + * + * @attention No function should be called on the network connection after + * calling this function. This function must be safe to call from a + * [receive callback](@ref platform_network_function_receivecallback). + */ +/* @[declare_platform_network_destroy] */ +typedef IotNetworkError_t ( * IotNetworkDestroy_t )( IotNetworkConnection_t pConnection ); +/* @[declare_platform_network_destroy] */ + /** * @ingroup platform_datatypes_paramstructs * @brief Represents the functions of a network stack. @@ -103,134 +292,13 @@ typedef void ( * IotNetworkReceiveCallback_t )( IotNetworkConnection_t pConnecti */ typedef struct IotNetworkInterface { - /** - * @brief Create a new network connection. - * - * This function allocates resources and establishes a new network connection. - * @param[in] pServerInfo Represents information needed to set up the - * new connection, defined by the network stack. - * @param[in] pCredentialInfo Represents information needed to secure the - * new connection, defined by the network stack. - * @param[out] pConnection Set to represent a new connection, defined by the - * network stack. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - */ - /* @[declare_platform_network_create] */ - IotNetworkError_t ( * create )( IotNetworkServerInfo_t pServerInfo, - IotNetworkCredentials_t pCredentialInfo, - IotNetworkConnection_t * pConnection ); - /* @[declare_platform_network_create] */ - - /** - * @brief Register an @ref platform_network_function_receivecallback. - * - * Sets an @ref platform_network_function_receivecallback to be called - * asynchronously when data arrives on the network. The network stack - * should invoke this function "as if" it were the thread routine of a - * detached thread. - * - * Each network connection may only have one receive callback at any time. - * @ref platform_network_function_close is expected to remove any active - * receive callbacks. - * - * @param[in] pConnection The connection to associate with the receive callback. - * @param[in] receiveCallback The function to invoke for incoming network data. - * @param[in] pContext A value to pass as the first parameter to the receive callback. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @see platform_network_function_receivecallback - */ - /* @[declare_platform_network_setreceivecallback] */ - IotNetworkError_t ( * setReceiveCallback )( IotNetworkConnection_t pConnection, - IotNetworkReceiveCallback_t receiveCallback, - void * pContext ); - /* @[declare_platform_network_setreceivecallback] */ - - /** - * @brief Send data over a return connection. - * - * Attempts to transmit `messageLength` bytes of `pMessage` across the - * connection represented by `pConnection`. Returns the number of bytes - * actually sent, `0` on failure. - * - * @param[in] pConnection The connection used to send data, defined by the - * network stack. - * @param[in] pMessage The message to send. - * @param[in] messageLength The length of `pMessage`. - * - * @return The number of bytes successfully sent, `0` on failure. - */ - /* @[declare_platform_network_send] */ - size_t ( * send )( IotNetworkConnection_t pConnection, - const uint8_t * pMessage, - size_t messageLength ); - /* @[declare_platform_network_send] */ - - /** - * @brief Block and wait for incoming network data. - * - * Wait for a message of size `bytesRequested` to arrive on the network and - * place it in `pBuffer`. - * - * @param[in] pConnection The connection to wait on, defined by the network - * stack. - * @param[out] pBuffer Where to place the incoming network data. This buffer - * must be at least `bytesRequested` in size. - * @param[in] bytesRequested How many bytes to wait for. `pBuffer` must be at - * least this size. - * - * @return The number of bytes successfully received. This should be - * `bytesRequested` when successful. Any other value may indicate an error. - */ - /* @[declare_platform_network_receive] */ - size_t ( * receive )( IotNetworkConnection_t pConnection, - uint8_t * pBuffer, - size_t bytesRequested ); - /* @[declare_platform_network_receive] */ - - /** - * @brief Close a network connection. - * - * This function closes the connection, but does not release the resources - * used by the connection. This allows calls to other networking functions - * to return an error and handle a closed connection without the risk of - * crashing. Once it can be guaranteed that `pConnection` will no longer be - * used, the connection can be destroyed with @ref platform_network_function_destroy. - * - * In addition to closing the connection, this function should also remove - * any active [receive callback](@ref platform_network_function_receivecallback). - * - * @param[in] pConnection The network connection to close, defined by the - * network stack. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @note It must be safe to call this function on an already-closed connection. - */ - /* @[declare_platform_network_close] */ - IotNetworkError_t ( * close )( IotNetworkConnection_t pConnection ); - /* @[declare_platform_network_close] */ - - /** - * @brief Free resources used by a network connection. - * - * This function releases the resources of a closed connection. It should be - * called after @ref platform_network_function_close. - * - * @param[in] pConnection The network connection to destroy, defined by - * the network stack. - * - * @return Any #IotNetworkError_t, as defined by the network stack. - * - * @attention No function should be called on the network connection after - * calling this function. This function must be safe to call from a - * [receive callback](@ref platform_network_function_receivecallback). - */ - /* @[declare_platform_network_destroy] */ - IotNetworkError_t ( * destroy )( IotNetworkConnection_t pConnection ); - /* @[declare_platform_network_destroy] */ + IotNetworkCreate_t create; /**< @brief create network connection. */ + IotNetworkSetReceiveCallback_t setReceiveCallback; /**< @brief set receive callback. */ + IotNetworkSetCloseCallback_t setCloseCallback; /**< @brief set close callback. */ + IotNetworkSend_t send; /**< @brief send data. */ + IotNetworkReceive_t receive; /**< @brief block and wait for receive data. */ + IotNetworkClose_t close; /**< @brief close network connection. */ + IotNetworkDestroy_t destroy; /**< @brief destroy network connection. */ } IotNetworkInterface_t; /** diff --git a/ports/common/include/iot_network_mbedtls.h b/ports/common/include/iot_network_mbedtls.h index 9475a086fa..3cb5bc4441 100644 --- a/ports/common/include/iot_network_mbedtls.h +++ b/ports/common/include/iot_network_mbedtls.h @@ -82,18 +82,18 @@ */ const IotNetworkInterface_t * IotNetworkMbedtls_GetInterface( void ); - /** - * @brief One-time initialization function for this network stack. - * - * This function performs internal setup of this network stack. It must be - * called once (and only once) before calling any other function in this network - * stack. Calling this function more than once without first calling - * #IotNetworkMbedtls_Cleanup may result in a crash. - * - * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_FAILURE. - * - * @warning No thread-safety guarantees are provided for this function. - */ +/** + * @brief One-time initialization function for this network stack. + * + * This function performs internal setup of this network stack. It must be + * called once (and only once) before calling any other function in this network + * stack. Calling this function more than once without first calling + * #IotNetworkMbedtls_Cleanup may result in a crash. + * + * @return #IOT_NETWORK_SUCCESS or #IOT_NETWORK_FAILURE. + * + * @warning No thread-safety guarantees are provided for this function. + */ IotNetworkError_t IotNetworkMbedtls_Init( void ); /** @@ -124,6 +124,14 @@ IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t p IotNetworkReceiveCallback_t receiveCallback, void * pContext ); +/** + * @brief An implementation of #IotNetworkInterface_t::setCloseCallback for + * mbed TLS. + */ +IotNetworkError_t IotNetworkMbedtls_SetCloseCallback( IotNetworkConnection_t pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ); + /** * @brief An implementation of #IotNetworkInterface_t::send for mbed TLS. */ diff --git a/ports/common/include/iot_network_openssl.h b/ports/common/include/iot_network_openssl.h index a973318635..357595bca7 100644 --- a/ports/common/include/iot_network_openssl.h +++ b/ports/common/include/iot_network_openssl.h @@ -128,6 +128,14 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( IotNetworkConnection_t p IotNetworkReceiveCallback_t receiveCallback, void * pContext ); +/** + * @brief An implementation of #IotNetworkInterface_t::setCloseCallback for + * systems with OpenSSL. + */ +IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( IotNetworkConnection_t pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ); + /** * @brief An implementation of #IotNetworkInterface_t::send for systems * with OpenSSL. diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index 82d522f9b3..e8371ac2f7 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -156,6 +156,8 @@ typedef struct _networkConnection IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ void * pReceiveContext; /**< @brief The context for the receive callback. */ + IotNetworkCloseCallback_t closeCallback; /**< @brief Network close callback, if any. */ + void * pCloseContext; /**< @brief The context for the close callback. */ IotMutex_t callbackMutex; /**< @brief Synchronizes the receive callback with calls to destroy. */ IotSemaphore_t destroyNotification; /**< @brief Notifies the receive callback that the connection was destroyed. */ @@ -410,6 +412,17 @@ static void _receiveThread( void * pArgument ) } } + /** + * If a close callback has been defined, invoke it now; since we + * don't know what caused the close, use "unknown" as the reason. + */ + if( pConnection->closeCallback != NULL ) + { + pConnection->closeCallback( pConnection, + IOT_NETWORK_UNKNOWN_CLOSED, + pConnection->pCloseContext ); + } + /* Wait for the call to network destroy, then destroy the connection. */ IotSemaphore_Wait( &( pConnection->destroyNotification ) ); _destroyConnection( pConnection ); @@ -930,7 +943,12 @@ IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t p IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_SYSTEM_ERROR ); } - /* Set the callback and parameter. */ + /* Set the callback (must be non-NULL) and parameter. */ + if( receiveCallback == NULL ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_BAD_PARAMETER ); + } + pConnection->receiveCallback = receiveCallback; pConnection->pReceiveContext = pContext; @@ -978,6 +996,26 @@ IotNetworkError_t IotNetworkMbedtls_SetReceiveCallback( IotNetworkConnection_t p /*-----------------------------------------------------------*/ +IotNetworkError_t IotNetworkMbedtls_SetCloseCallback( IotNetworkConnection_t pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ) +{ + IotNetworkError_t status = IOT_NETWORK_BAD_PARAMETER; + + if( closeCallback != NULL ) + { + /* Set the callback and parameter. */ + pConnection->closeCallback = closeCallback; + pConnection->pCloseContext = pContext; + + status = IOT_NETWORK_SUCCESS; + } + + return status; +} + +/*-----------------------------------------------------------*/ + size_t IotNetworkMbedtls_Send( IotNetworkConnection_t pConnection, const uint8_t * pMessage, size_t messageLength ) diff --git a/ports/common/src/iot_network_metrics.c b/ports/common/src/iot_network_metrics.c index 8bd0b6a1e8..304ec8008b 100644 --- a/ports/common/src/iot_network_metrics.c +++ b/ports/common/src/iot_network_metrics.c @@ -126,71 +126,73 @@ static IotMutex_t _connectionListMutex; /* OpenSSL networking include. */ #include "iot_network_openssl.h" - /** - * @brief Pointer to the metrics-wrapped network creation function. - */ +/** + * @brief Pointer to the metrics-wrapped network creation function. + */ static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t, IotNetworkCredentials_t, IotNetworkConnection_t * ) = IotNetworkOpenssl_Create; - /** - * @brief Pointer to the metrics-wrapped network close function. - */ +/** + * @brief Pointer to the metrics-wrapped network close function. + */ static IotNetworkError_t ( * _networkClose )( IotNetworkConnection_t ) = IotNetworkOpenssl_Close; - /** - * @brief Pointer to the function that retrieves the socket for a connection. - */ +/** + * @brief Pointer to the function that retrieves the socket for a connection. + */ static int ( * _getSocket )( IotNetworkConnection_t ) = IotNetworkOpenssl_GetSocket; - /** - * @brief An #IotNetworkInterface_t that wraps network abstraction functions with - * metrics. - */ +/** + * @brief An #IotNetworkInterface_t that wraps network abstraction functions with + * metrics. + */ static const IotNetworkInterface_t _networkMetrics = { .create = _metricsNetworkCreate, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .setCloseCallback = IotNetworkOpenssl_SetCloseCallback, .send = IotNetworkOpenssl_Send, .receive = IotNetworkOpenssl_Receive, .close = _metricsNetworkClose, .destroy = IotNetworkOpenssl_Destroy }; -#else +#else /* if IOT_NETWORK_USE_OPENSSL == 1 */ /* mbed TLS networking include. */ #include "iot_network_mbedtls.h" - /** - * @brief Pointer to the metrics-wrapped network creation function. - */ +/** + * @brief Pointer to the metrics-wrapped network creation function. + */ static IotNetworkError_t ( * _networkCreate )( IotNetworkServerInfo_t, IotNetworkCredentials_t, IotNetworkConnection_t * ) = IotNetworkMbedtls_Create; - /** - * @brief Pointer to the metrics-wrapped network close function. - */ +/** + * @brief Pointer to the metrics-wrapped network close function. + */ static IotNetworkError_t ( * _networkClose )( IotNetworkConnection_t ) = IotNetworkMbedtls_Close; - /** - * @brief Pointer to the function that retrieves the socket for a connection. - */ +/** + * @brief Pointer to the function that retrieves the socket for a connection. + */ static int ( * _getSocket )( IotNetworkConnection_t ) = IotNetworkMbedtls_GetSocket; - /** - * @brief An #IotNetworkInterface_t that wraps network abstraction functions with - * metrics. - */ +/** + * @brief An #IotNetworkInterface_t that wraps network abstraction functions with + * metrics. + */ static const IotNetworkInterface_t _networkMetrics = { .create = _metricsNetworkCreate, .setReceiveCallback = IotNetworkMbedtls_SetReceiveCallback, + .setCloseCallback = IotNetworkMbedtls_SetCloseCallback, .send = IotNetworkMbedtls_Send, .receive = IotNetworkMbedtls_Receive, .close = _metricsNetworkClose, .destroy = IotNetworkMbedtls_Destroy }; -#endif +#endif /* if IOT_NETWORK_USE_OPENSSL == 1 */ /*-----------------------------------------------------------*/ diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index ba69d01b54..fa1c27a821 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -111,6 +111,8 @@ typedef struct _networkConnection IotNetworkReceiveCallback_t receiveCallback; /**< @brief Network receive callback, if any. */ void * pReceiveContext; /**< @brief The context for the receive callback. */ + IotNetworkCloseCallback_t closeCallback; /**< @brief Network close callback, if any. */ + void * pCloseContext; /**< @brief The context for the close callback. */ } _networkConnection_t; /*-----------------------------------------------------------*/ @@ -122,6 +124,7 @@ static const IotNetworkInterface_t _networkOpenssl = { .create = IotNetworkOpenssl_Create, .setReceiveCallback = IotNetworkOpenssl_SetReceiveCallback, + .setCloseCallback = IotNetworkOpenssl_SetCloseCallback, .send = IotNetworkOpenssl_Send, .receive = IotNetworkOpenssl_Receive, .close = IotNetworkOpenssl_Close, @@ -181,6 +184,17 @@ static void * _networkReceiveThread( void * pArgument ) pConnection->pReceiveContext ); } + /** + * If a close callback has been defined, invoke it now; since we + * don't know what caused the close, use "unknown" as the reason. + */ + if( pConnection->closeCallback != NULL ) + { + pConnection->closeCallback( pConnection, + IOT_NETWORK_UNKNOWN_CLOSED, + pConnection->pCloseContext ); + } + IotLogDebug( "Network receive thread for socket %d terminating.", pConnection->socket ); @@ -752,28 +766,53 @@ IotNetworkError_t IotNetworkOpenssl_SetReceiveCallback( IotNetworkConnection_t p IotNetworkReceiveCallback_t receiveCallback, void * pContext ) { + IotNetworkError_t status = IOT_NETWORK_BAD_PARAMETER; int posixError = 0; - IotNetworkError_t status = IOT_NETWORK_SUCCESS; - /* Set the callback and parameter. */ - pConnection->receiveCallback = receiveCallback; - pConnection->pReceiveContext = pContext; + /* The receive callback must be non-NULL) */ + if( receiveCallback != NULL ) + { + /* Set the callback and parameter. */ + pConnection->receiveCallback = receiveCallback; + pConnection->pReceiveContext = pContext; - posixError = pthread_create( &pConnection->receiveThread, - NULL, - _networkReceiveThread, - pConnection ); + posixError = pthread_create( &pConnection->receiveThread, + NULL, + _networkReceiveThread, + pConnection ); - if( posixError != 0 ) - { - IotLogError( "Failed to create socket %d network receive thread. errno=%d.", - pConnection->socket, - posixError ); - status = IOT_NETWORK_SYSTEM_ERROR; + if( posixError != 0 ) + { + IotLogError( "Failed to create socket %d network receive thread. errno=%d.", + pConnection->socket, + posixError ); + status = IOT_NETWORK_SYSTEM_ERROR; + } + else + { + pConnection->receiveThreadCreated = true; + status = IOT_NETWORK_SUCCESS; + } } - else + + return status; +} + +/*-----------------------------------------------------------*/ + +IotNetworkError_t IotNetworkOpenssl_SetCloseCallback( IotNetworkConnection_t pConnection, + IotNetworkCloseCallback_t closeCallback, + void * pContext ) +{ + IotNetworkError_t status = IOT_NETWORK_BAD_PARAMETER; + + if( closeCallback != NULL ) { - pConnection->receiveThreadCreated = true; + /* Set the callback and parameter. */ + pConnection->closeCallback = closeCallback; + pConnection->pCloseContext = pContext; + + status = IOT_NETWORK_SUCCESS; } return status; From bb12a9c644a4e7c1827ca24c147c14cadb75cbd9 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Wed, 13 Nov 2019 19:02:57 -0500 Subject: [PATCH 316/844] Shadow code restructure and documentation changes (#631) --- libraries/aws/common/include/aws_iot.h | 41 +++++++++++++++---- libraries/aws/common/src/aws_iot_operation.c | 3 +- libraries/aws/common/src/aws_iot_parser.c | 16 ++++---- .../aws/common/src/aws_iot_subscription.c | 10 +++-- .../include/types/aws_iot_shadow_types.h | 12 +++--- libraries/aws/shadow/src/aws_iot_shadow_api.c | 9 +--- .../aws/shadow/src/aws_iot_shadow_operation.c | 9 +--- .../shadow/src/aws_iot_shadow_subscription.c | 30 +------------- .../src/private/aws_iot_shadow_internal.h | 12 ++++++ 9 files changed, 71 insertions(+), 71 deletions(-) diff --git a/libraries/aws/common/include/aws_iot.h b/libraries/aws/common/include/aws_iot.h index cfc75dd9e2..0a1bdcb44d 100644 --- a/libraries/aws/common/include/aws_iot.h +++ b/libraries/aws/common/include/aws_iot.h @@ -107,18 +107,42 @@ * * Currently, this is used to represent @ref mqtt_function_subscribesync or * @ref mqtt_function_unsubscribesync. + * + * @param[in] mqttConnection The MQTT connection to use for the subscription. + * @param[in] pSubscriptionList Pointer to the first element in the array of + * subscriptions. + * @param[in] subscriptionCount The number of elements in pSubscriptionList. + * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags. + * Currently, flags are ignored by this function; this parameter is for + * future-compatibility. + * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within + * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_NOT_INITIALIZED + * - #IOT_MQTT_BAD_PARAMETER + * - #IOT_MQTT_NO_MEMORY + * - #IOT_MQTT_NETWORK_ERROR + * - #IOT_MQTT_SCHEDULING_ERROR + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_TIMEOUT + * - #IOT_MQTT_SERVER_REFUSED */ -typedef IotMqttError_t ( * AwsIotMqttFunction_t )( IotMqttConnection_t, - const IotMqttSubscription_t *, - size_t, - uint32_t, - uint32_t ); +typedef IotMqttError_t ( * AwsIotMqttFunction_t )( IotMqttConnection_t mqttConnection, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint32_t flags, + uint32_t timeoutMs ); /** * @brief Function pointer representing an MQTT library callback function. + * + * @param[in] pArgument Ignored. + * @param[in] pMessage The received DELTA document (as an MQTT PUBLISH message). */ -typedef void ( * AwsIotMqttCallbackFunction_t )( void *, - IotMqttCallbackParam_t * ); +typedef void ( * AwsIotMqttCallbackFunction_t )( void * pArgument, + IotMqttCallbackParam_t * pMessage ); /** * @brief Enumerations representing each of the statuses that may be parsed @@ -225,7 +249,8 @@ bool AwsIot_GetClientToken( const char * pJsonDocument, * @param[out] pThingName Set to point to the Thing Name. * @param[out] pThingNameLength Set to the length of the Thing Name. * - * @return `true` if a Thing Name was successfully parsed; `false` otherwise. + * @return `true` if a Thing Name was successfully parsed; `false` otherwise. The output + * parameters are only valid if this function returns `true`. */ bool AwsIot_ParseThingName( const char * pTopicName, uint16_t topicNameLength, diff --git a/libraries/aws/common/src/aws_iot_operation.c b/libraries/aws/common/src/aws_iot_operation.c index f5436a000a..37da478589 100644 --- a/libraries/aws/common/src/aws_iot_operation.c +++ b/libraries/aws/common/src/aws_iot_operation.c @@ -50,7 +50,8 @@ bool AwsIot_InitLists( IotListDouble_t * pPendingOperationsList, IOT_FUNCTION_ENTRY( bool, true ); /* Flags to track cleanup. */ - bool operationsMutexCreated = false, subscriptionsMutexCreated = false; + bool operationsMutexCreated = false; + bool subscriptionsMutexCreated = false; /* Create the mutex guarding the pending operations list. */ operationsMutexCreated = IotMutex_Create( pPendingOperationsMutex, false ); diff --git a/libraries/aws/common/src/aws_iot_parser.c b/libraries/aws/common/src/aws_iot_parser.c index 7da99ee5fc..731b77c798 100644 --- a/libraries/aws/common/src/aws_iot_parser.c +++ b/libraries/aws/common/src/aws_iot_parser.c @@ -145,8 +145,14 @@ AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, IOT_FUNCTION_ENTRY( AwsIotStatus_t, AWS_IOT_UNKNOWN ); const char * pSuffixStart = NULL; + /* Both 'accepted' and 'rejected' topics are of the same length + * The below is a defensive check at run time to ensure that. + */ + Iot_DefaultAssert( AWS_IOT_ACCEPTED_SUFFIX_LENGTH == AWS_IOT_REJECTED_SUFFIX_LENGTH ); + /* Check that the status topic name is at least as long as the - * "accepted" suffix. */ + * "accepted" suffix. This length check will be good for rejected also + * as both are of 8 characters in length. */ if( topicNameLength > AWS_IOT_ACCEPTED_SUFFIX_LENGTH ) { /* Calculate where the "accepted" suffix should start. */ @@ -159,14 +165,6 @@ AwsIotStatus_t AwsIot_ParseStatus( const char * pTopicName, { IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_ACCEPTED ); } - } - - /* Check that the status topic name is at least as long as the - * "rejected" suffix. */ - if( topicNameLength > AWS_IOT_REJECTED_SUFFIX_LENGTH ) - { - /* Calculate where the "rejected" suffix should start. */ - pSuffixStart = pTopicName + topicNameLength - AWS_IOT_REJECTED_SUFFIX_LENGTH; /* Check if the end of the status topic name is "/rejected". */ if( strncmp( pSuffixStart, diff --git a/libraries/aws/common/src/aws_iot_subscription.c b/libraries/aws/common/src/aws_iot_subscription.c index 0518ebf380..312197e473 100644 --- a/libraries/aws/common/src/aws_iot_subscription.c +++ b/libraries/aws/common/src/aws_iot_subscription.c @@ -77,7 +77,11 @@ static IotMqttError_t _modifySubscriptions( AwsIotMqttFunction_t mqttOperation, subscription.callback.pCallbackContext = NULL; subscription.callback.function = pSubscriptionInfo->callbackFunction; - /* Call the MQTT operation function. */ + /* Call the MQTT operation function. + * Subscription count is 1 in this case. + * None of the flags are set in this call. Hence 0 is passed for flags. + * Please refer to documentation for AwsIotMqttFunction_t for more details. + */ status = mqttOperation( pSubscriptionInfo->mqttConnection, &subscription, 1, @@ -112,9 +116,7 @@ IotMqttError_t AwsIot_ModifySubscriptions( AwsIotMqttFunction_t mqttOperation, if( acceptedStatus != IOT_MQTT_SUCCESS ) { - status = acceptedStatus; - - IOT_GOTO_CLEANUP(); + IOT_SET_AND_GOTO_CLEANUP( acceptedStatus ); } /* Place the topic "rejected" suffix at the end of the topic buffer. */ diff --git a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h index 5103f88657..7e44da5741 100644 --- a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h +++ b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h @@ -433,14 +433,14 @@ typedef struct AwsIotShadowCallbackInfo /** * @brief User-provided callback function signature. * - * @param[in] void* #AwsIotShadowCallbackInfo_t.pCallbackContext - * @param[in] AwsIotShadowCallbackParam_t* Details on the outcome of the Shadow + * @param[in] pCallbackContext #AwsIotShadowCallbackInfo_t.pCallbackContext + * @param[in] pCallbackParam Details on the outcome of the Shadow * operation or an incoming Shadow document. * * @see #AwsIotShadowCallbackParam_t for more information on the second parameter. */ - void ( * function )( void *, - AwsIotShadowCallbackParam_t * ); + void ( * function )( void * pCallbackContext, + AwsIotShadowCallbackParam_t * pCallbackParam ); } AwsIotShadowCallbackInfo_t; /** @@ -475,10 +475,12 @@ typedef struct AwsIotShadowDocumentInfo /** * @brief Function to allocate memory for an incoming Shadow document. * + * @param[in] documentLength Length of the document that needs to + * be allocated. * This only needs to be set if #AWS_IOT_SHADOW_FLAG_WAITABLE is passed to * @ref shadow_function_getasync. */ - void *( *mallocDocument )( size_t ); + void *( *mallocDocument )( size_t documentLength ); } get; /**< @brief Valid members for @ref shadow_function_getasync. */ /* Valid for Shadow update. */ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_api.c b/libraries/aws/shadow/src/aws_iot_shadow_api.c index 2388ff6b76..53a812b061 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_api.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_api.c @@ -559,14 +559,7 @@ static AwsIotShadowError_t _modifyCallbackSubscriptions( IotMqttConnection_t mqt IotMqtt_strerror( mqttStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( mqttStatus == IOT_MQTT_NO_MEMORY ) - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_NO_MEMORY ); - } - else - { - IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_SHADOW_MQTT_ERROR ); - } + IOT_SET_AND_GOTO_CLEANUP( SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( mqttStatus ) ); } IotLogDebug( "Successfully %s %.*s Shadow %s callback.", diff --git a/libraries/aws/shadow/src/aws_iot_shadow_operation.c b/libraries/aws/shadow/src/aws_iot_shadow_operation.c index 058048d453..5fdaf5ef72 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_operation.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_operation.c @@ -874,14 +874,7 @@ AwsIotShadowError_t _AwsIotShadow_ProcessOperation( IotMqttConnection_t mqttConn IotMqtt_strerror( publishStatus ) ); /* Convert the MQTT "NO MEMORY" error to a Shadow "NO MEMORY" error. */ - if( publishStatus == IOT_MQTT_NO_MEMORY ) - { - status = AWS_IOT_SHADOW_NO_MEMORY; - } - else - { - status = AWS_IOT_SHADOW_MQTT_ERROR; - } + status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( publishStatus ); /* If the "keep subscriptions" flag is not set, decrement the reference * count. */ diff --git a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c index f2d10691b4..c964351e6c 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_subscription.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_subscription.c @@ -306,20 +306,7 @@ AwsIotShadowError_t _AwsIotShadow_IncrementReferences( _shadowOperation_t * pOpe &subscriptionInfo ); /* Convert MQTT return code to Shadow return code. */ - switch( subscriptionStatus ) - { - case IOT_MQTT_SUCCESS: - status = AWS_IOT_SHADOW_SUCCESS; - break; - - case IOT_MQTT_NO_MEMORY: - status = AWS_IOT_SHADOW_NO_MEMORY; - break; - - default: - status = AWS_IOT_SHADOW_MQTT_ERROR; - break; - } + status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( subscriptionStatus ); if( status != AWS_IOT_SHADOW_SUCCESS ) { @@ -488,20 +475,7 @@ AwsIotShadowError_t AwsIotShadow_RemovePersistentSubscriptions( IotMqttConnectio &subscriptionInfo ); /* Convert MQTT return code to Shadow return code. */ - switch( unsubscribeStatus ) - { - case IOT_MQTT_SUCCESS: - status = AWS_IOT_SHADOW_SUCCESS; - break; - - case IOT_MQTT_NO_MEMORY: - status = AWS_IOT_SHADOW_NO_MEMORY; - break; - - default: - status = AWS_IOT_SHADOW_MQTT_ERROR; - break; - } + status = SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( unsubscribeStatus ); if( status != AWS_IOT_SHADOW_SUCCESS ) { diff --git a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h index b6ec2af511..0e7236c661 100644 --- a/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h +++ b/libraries/aws/shadow/src/private/aws_iot_shadow_internal.h @@ -258,6 +258,18 @@ */ #define SHADOW_LONGEST_SUFFIX_LENGTH SHADOW_UPDATED_SUFFIX_LENGTH +/** + * @brief The macro to convert MQTT error codes to Shadow error codes. + * Below are the conversions happening. + * IOT_MQTT_SUCCESS to AWS_IOT_SHADOW_SUCCESS + * IOT_MQTT_NO_MEMORY to AWS_IOT_SHADOW_NO_MEMORY + * all other error codes to AWS_IOT_SHADOW_MQTT_ERROR + */ +#define SHADOW_CONVERT_STATUS_CODE_MQTT_TO_SHADOW( X ) \ + ( ( X ) == IOT_MQTT_SUCCESS ) ? AWS_IOT_SHADOW_SUCCESS : \ + ( ( X ) == IOT_MQTT_NO_MEMORY ) ? AWS_IOT_SHADOW_NO_MEMORY : \ + AWS_IOT_SHADOW_MQTT_ERROR + /*----------------------- Shadow internal data types ------------------------*/ /** From 1fd28a404f3bb805b1c6dc528418362399857b6a Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Wed, 13 Nov 2019 19:09:09 -0500 Subject: [PATCH 317/844] Design page for Shadow in Doxygen (#638) --- doc/lib/shadow.txt | 13 +++ .../images/shadow_async_opertation_detail.png | Bin 0 -> 127219 bytes .../images/shadow_sync_opertation_detail.png | Bin 0 -> 120850 bytes .../shadow_async_opertation_detail.pu | 76 +++++++++++++++++ doc/plantuml/shadow_sync_opertation_detail.pu | 78 ++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 doc/plantuml/images/shadow_async_opertation_detail.png create mode 100644 doc/plantuml/images/shadow_sync_opertation_detail.png create mode 100644 doc/plantuml/shadow_async_opertation_detail.pu create mode 100644 doc/plantuml/shadow_sync_opertation_detail.pu diff --git a/doc/lib/shadow.txt b/doc/lib/shadow.txt index 8197bb9d8d..75685320f7 100644 --- a/doc/lib/shadow.txt +++ b/doc/lib/shadow.txt @@ -47,6 +47,19 @@ Currently, the Shadow library has the following dependencies: In addition to the components above, the Shadow library also depends on C standard library headers. */ +/** +@page shadow_design Design +@brief Architecture behind the Shadow library. + +Shadow library uses MQTT subscriptions and MQTT publishes for communicating with the AWS IoT Shadow Service. Shadow operations such as Shadow Delete, Shadow Get, and Shadow Update uses MQTT subscriptions to two MQTT topics to know the accepted or rejected status of the follwing MQTT publish and then publishes to the topic for the Shadow operation. Shadow Updated Callback and Shadow Delta Callback uses MQTT subscriptions to receive data from the AWS IoT Shadow Service. The callbacks will be run in MQTT's taskpool context. + +@section Synchronous_Design Synchronous Design +@image html shadow_sync_opertation_detail.png width=100% + +@section Asynchronous_Design Asynchronous Design +@image html shadow_async_opertation_detail.png width=100% +*/ + /** @page shadow_demo Demo @brief The Shadow demo demonstrates usage of the Shadow library. diff --git a/doc/plantuml/images/shadow_async_opertation_detail.png b/doc/plantuml/images/shadow_async_opertation_detail.png new file mode 100644 index 0000000000000000000000000000000000000000..eaa58b9896e0dad33bed4b493cb101785ad3321c GIT binary patch literal 127219 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 literal 0 HcmV?d00001 diff --git a/doc/plantuml/images/shadow_sync_opertation_detail.png b/doc/plantuml/images/shadow_sync_opertation_detail.png new file mode 100644 index 0000000000000000000000000000000000000000..5b91efff80d2fb8782e0ddd0fa42c54a798cf068 GIT binary patch literal 120850 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

F_ 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

{@+{JP^?P6lqXvY*bji=~$z_tCk! znRfoDGuzbdcfIS%>-%kj=IlMSp$PA!F{bNYyM@_dn|QbSZRQGaI{Nv8FVlj*jIxN}-*-Q>H=QkD%=-+*)BI zTu^AL(#Ukpns0fNw)pTZxccy$hR#1Nd~He8+G$&s4+a#)UYq3hzb=R?Se;FnwqI*h zR6X0yc-44%OudU;j;>&;Lt(+(6Z-@U!>*$9p8td%b*qqx@C*t1Je9#|h#eYj^7D&bdZ??;CCYN$Cf3{t`80e5N-G)Ps3nC! zrxtBCX))nq@2YH09wRppDa5rn`9ZmSDCX^T)+?g?>^?F@>m4PQ)t`KpB3o6J9&c1X zb@=wdn#ol;dB9fuN>toE>juU3MO49}R~wC^c#$k-_$!}53)(mA`pg6kP|7|s)FHf% zk1GEWyL9X`wlgp2G_^&w2-gJ>u2DHRJ_m;|gQ@%|tpEepEkT$2J$EWTjf=gj9@iGa z?ssxhJnZ@;xhLz1l=1qkS7v=#kpL;Vk%LIPf)u-)i}~~;CCN)?3;kn5GIZeV`d+*i zC*Nkvf4a{|VIPMuBvoDof-IIxLX@w(BK%Cgz4somw~{`J&FrKX3yfZ`Z_;O$p{2d2 z*`9BxSKx1Yw?MOW%dtB{?kJ02X=jRMbOq%W2`N!qb=)~RCWY{YFDFzyvF{#DS}9Jd z4=?3{?a-aE(tc(=L8OAVue1Ji*j?hB^kwEQX|(ey)BH>m>I~@WzBjBI zq5WT<4@n7$hQ1xPC4K3jw`6zqgR`}B!RbEcuj8B|L7~&>(QV^yt4%ext}@r}-wV)$ z(1)@2pM4Ke^|oJI45f#^=1e(sf1bnY3zVaIS}H|jU3Eq3<DMep`#q9XltYLEcshs3u)!er1fiigcWPIH}a zmlEUEnkS_qn);HjQH3#DiuQ;$qSmDB};>6irI-!A|vkd@b?l>|N9(38z{v#N=h;3x7 zZjgnyVd>9lHPV>TedG&fT_w~X=cd>1L<@_GDF*wkj1ED`WtxJz^he6(T4#G9|I1fy zoXSbxKV99tpu!=_DlH^xR^;IWkSht1H#XIn`0UC9ws)bR5E-VG>pg z(4S?u=7s2#kfwc4f9Rs34|Fjnmv0bny`_rYLUV0FlMXPF4GfXuVstnXPjDDajYG=}}s8 z(!$9UEhd}&&o^EFRxFw$9?1r)KeO4kAR)##b)WFgb#@2XitQwsQB(AkASpF7JK*F*&!mt_?v>AvaTNYw#?WXFJ{TJNH+pG=GU(jw_BsUEL^M&*g05tP@@C-~Ppv5pM)HhYD|G|vA7ADVhc z)5^#$k)p?>upXE?u8{43r~90KxgF$>5qE^o&)~>45(g=gF!8EZ5GJA2jG%cZ z%sZlkADE64G@*^k_V3j+kXBxb=V2W5XoBruhb4flBmjp7pZxBxy%3G>zXfC45re=$ zk$}AWB_!hnMwVWYm||eQScrbAT$xTm>qgk<&PrlqsKyL zF7li|UJV}n)&B@|iRnl8THPD;GQQ39*npdG;LG{aFD=Bwq3JmEfq(^s=)eOCdF6we zd_P@7RlWLt@F$OwKW{TVdV62F`~H~}{ge;*ao`0@0&gjH-sfw|-c;{iI7dL>xfSG0 z{!c`2%iOal#edulH>bqx9TbJfB3^0nB3-KxKo7G6mqO=P4WWy+4DQ!%oZnWfD5hrC zv1c*)V^(jNmrzETIixyRMLtoZ);|K4gR>OGOjwD+g9$~tCT38s^nEMeYKr~h(Due^WeHPvKt4upmjU4<^G&ynC)FW z1y6&e!LKNi8spo&;j9F)znydXblh+~-P>n)cqj68PoRT0^Ic=X+0Gw5$fg@{7sBI= z1%LA%@+`!1wc`JO_W;Ke>}>Ck7TjHB(vv#t?49Lq)CSEb&^at8sdysmobNXMD;9XN zHzzE`uUIg^;Bj2jq7iL-Pd2AD4fUD%5BC7RsBS+653RTHbFSN}!qe69a<+N4h*)gr zm9;#Bc>>;EQTsC*mrM7g8%UF1|04)s`bhicj;Ju@Lei&Tb|M2VtDk-A0AP@t%ou_g zk?3mz&XwV9tFsLoQ3z?oD;i!4h(>)@iYuS;D<}u@ScWaxGb&Y$hXy;BuIN5i&7QJM z%m`cK5ys(NgxN&mai}bTXFvKo4R;4$(c`X~pHpQ#c{b0%6Md~YWLP4o6{-v9tML*J zecdY#boQ+otIL@-3S9Xzxk{YVIaPyvG;uS!ty2x}6A;5kULtG1)`EswZ@tH7F_8eL z4Gssdui3SU;Ll)2PA7GYi3W__61(zctRsB&u-APxKw#<>5YES+A2?IkcL}*~R4-)` zoTEW8?d4oO%h9nK(RI?(bVSvu?X(k2TW7Lw+-g$El#=VKxXLn;0|=oVF4IhO%Uo7m z?>{Fc(CsSfXzaN-$1~ZzcB7!zqR_^y>|T?@rn$O?dKz4WHo>L!QV+xb!>DuCs;lC| z7w%27=jf}}bFWC03xYa224|Ucl75KRD4p)ech4XcrmW2qk!uyP98517sTx&cYnA&b zZ=`ZN@!a}RgC0?ZdWC0WPCo}ilFCQGHu01V(%kZt4J0JsDqdJj7CFq^WGI?9h*{`b zSon08#_`dXsXBcVg5YeGVo&gPA)qQI?V|I2cmG_X308+jxZtK3tr(l+N+KO$Jy*#k zZQ1>WlxnVeMH|DCM50y}Xxuo%n%29Olibc%^uA4}f6a+pXn=j`62NaE=NQ^V8vU1} zT@(2YF_M9WSNP9f{PL|i)N*R+MkwX2g02Ggx%XAI%uhlxuN4mzk$OM4$@4u^xA^;H zz!e1BB<56>TlR=IGKo*&>Xhl}zrT0!dAJdhI+$>xlxr_|2nce3>meY9_Nz^a1MkkQ zb;4@%PCmM~-dy%xGdg{-TDxzIJ#4h*(B%&OS0dCgE)P#ZSce=*5(2CBmP>==OD~~Q zOX9DRb?wSk4NJ|9UM^6^oaGlO>{z>haLl*ge&EO$S7KwSg0Nw0L65eLR%2lf-?7%z z!oI4zGeW#G;!2A_d_D-Qv^fr!$Dg2x4Uk?p@43Ge9ht27ff4(#fXLMuL^UI?f=Z57 zI{mzqc~d5)!Bxy!3zfXxzY`pBNt4D$BJA1CS=9FFpl{c$p3OC=OoRs}5kDXoVm4R2RJ)4o+On~<>i|KaO3UVK!XacdissJcZyhdGpkS=7@ygzTV zl*z|2qO3^sQm=Wi^;pZt>5gkHYd6fG>1m@-a7V{NUhkE*h7dycK_-JAr+DqiuB%pb zqvf}Y;pPT}Z9)=Qf_kSojdL^Q?<3D#qa2_N$P|_+XU_SHSc=9C@}tU?t@NL_NVzWB z=hOe-CqEN#b{OWi)Kg!hK&f|Al_s1=O70=xX6RaOc;0BSv6MMvZ@8_>+-N1w z$5{?!$=(V}=hsaZx$^rKb43f*Y!B?<6Qr$VAfZ@;(}Enm2HN;b+Y3$d1U2%v^um&= zI;`V0aprM#W>Kz`z5=eyec$7JtC}!pd3knkYbCRbp-euBAOq@}Av- z@zBFpr4El?os3m4z78Op8grxF4zem6(w7#gIokT3u7=8rpc60-NsSGeE{mj8#lKjC zQ*uMhohIP}{)O-b&0`YPPp24*~RSI^7d^wSAzo*`M5B>ONZ1nBA6#P4$N1_V^nR|UN@&-I4 zo|B9o6|PZYXg$1nNuoF-Exeuv;B29fY|Dc(+?8~s0|hxyW*NGoEW*|2EXY+tQKhwc z2}*sK+lg&pBgv08_Yo`OUv&?sIUiC(St;!Yisju$FD!xieN(Ic5^C>^yz$Geq*^xw zOa3#4A=}B>hOhGJyRTqLb)w-ku-VU%dVFvC8!={N1_^LfN@PvT4eA`Zh&H2lbJRn< z*{hIHR6BsP*Vvf+3&Ar8HbU?yKKvsv1;M?M&pclrXnq2LX0`*}#!>`5{`Z7HU~vCA4u(#az6$V%$}$KmQj0XFE9`Al2MkusK-j1u%oQ6;pSs z_s3WlUj6+u;A{YL1l*<~C`Z6LnF@yZ!t*Cc-=eGy>7gwhQg*s>6!KLW<^a2^O z#$kYei&Yq_&AvAhr*HEKx_4hVOODGNb}x`9-$jWqVx3jW0b(}(T<%BT#RiBoHq5*6 zZ?=O84kHJXOa^)F0#=L8B(`tGaZPjOj0A>G0SZ zk_!BXbOPqw%V2QxOZeXl4HkjS(+=o`uMms*&7<}HFM9|#i&s_e(_;T0QXQ79$0qv@ zfW;!8{6Fv_xRmP;Ish^IUoaSw3dCHDeriO{js^jt=G2pe&CawOZPA}x$mhbm1*FO? zHrc-2=wTj0Mc1et!qU2!hG0>MscDW+&pLZL@}+W(s)g!CwX|sbocl>R|Dn3`Qj(I4 zq!^olcn8HP%H`TZKa^`My+T%W#WXBClF1;boTBU0RRWPiqKr=gjY7||H~E=BB`}c7 z+559!RgO2to;?A)rqo*ho%f%Ca#FGSIVjb)z7UpM@^13GbwYh>U8Z`f@WPY2@U~l5 zu373k=*?dgt9J^&NX!ll3*@`Y(fd(MZWtU%i%&)oV}8p?lp|~5diRvVVb?YnjFsNf zMpg_43iwsgO&vr+*7M)UDrKSrm`~`|qAyND%e5dvXYYszda`saP(tsuHEw>=AOLuA zNJAsgz!ZjNN$ed!@)Lo5D^JbbVwz|G<_W6?%Ei+G*-A!sVq$`Z^>I6oT;I&1Eop!E zG6|5By8Yd0-X4|D`WFlgzZ1Z0 zv@$i+@GKjGuz|7t*+E_;zvdr~P&BYMer3s%i|lfiZU|}%um$46Awb374$vXH1PVLq zusqqh4dLU#V9fk7FqBzVxIEzPS$o*Yg8UH~y!1Tes} zn=dvvolmk_21=q3NV1mlhzy;u;8VRhePWdS4ml!Pfz$scMIQ7B8;8t7=WpkfyP}F_ zaD_M88i{L8nNx5vWybc~wmiZwAl?0$0{yW1O@BMjG- z8?p*cT@=UF!li7|nbF?MTvJvdHv8PCErB7j+dKe-&LXK^@zRvH2azSvEaBeGkb$7O zGfW%2KrEpBGV|>S(;=z!Q(+$;v5@>}6=e-uJCuGiYlD;7e2l7oXw+faBS((rOy8R@ z32A2KJSI5!`Ep0!OwUY_?H54?n-7G%o0d)WpJ}-PLB50x2B_J8XzYvp>&t*9?kvhy z-7c_MJ}kkO5v%JL${iQcm!A6cw$P(L96X)@2~NhG0tD`MTF7L8KZe7CJn^N7qq>1GRz3PY+^)M|ov9~ae&s3=RV+C*Z!1>SaWUm><~0VmW3PDLK%FVb zne299)T2jSlh@G0U~5Y>?0SIpLp$FbI=$IucJ(6g-=E(E5+XV8PF}%$4DkXY9cW;x z$<73PouFxM(I7JDVOBS>fs{~3_G9FU1CNIwed@EhJ8rom(%`g)aswi2q$pIkZ?h4K z0kw%jGwGZO_cul}Bu{LA=zx83oozcGQcj&3^btv!gmkK~4Lci5PSWSo)Kn1)W*f;r z(Y#0B_q!w0^3LGm`n$x1)rKB^9^`Nfo|i$8L4PGiOB`6@GDf1WWDv=Q?O2YUQUQ;R zub$wqc>v>RfD@lL%)ghp_ZUb@?n)N{nPq;*U@5gW`xf{+h?T{zG0Th_G z|83mD$M-*|w4D{CXxee{xQYL*7=gSW^TVC@Lk#hk25^VwBlsVvzu&$Gk3={#AYR0u z2Yg0pBs|8RwUu@r-p2N3rF~gUO%*dRrqVrYZkj0ES;}YmEq~PYz5RZ>gACUl*Q|?; zNu{uxicMUj?nT*ZezD zd7_kO#@D{_tF}o+_huO0?YpTw=S>ziGkQJ2NV0J_G=RB;!=gbxi19?Q-D&keb9G|l*vX*c%kt@Fz8AR5LOH(J9e^{hx^~heX)d0 zYqxIQ`l7J$EukgOgZGZR*oEp(x9^hQUhr;~ioU+h@^i85%(?|q&O)!RXEs`zm7K<& z#nOEjNnh_WbT1akiKT1oz9wX$kL^(yXj#jUqiGcrzQud$f{;re>dMcsAQHu`hRlKM zK66i1*xAoX%Fdh^xXUZ4X5ZF0ob`I1o(?nwJYmM+)Und)~XiVE-NrN$Kcb$#?~_X)c# z4QCRKJ-BIN1qCrh$M~2O`a!Aa=jTVFaqikRTc{~76eBwU?-Sa>FoBybE>T08F;q~F zGYt`nVFf^OLp)vOEVw`*v50;i)lZq!i8|?PBM%h>^$TrpNWH|Et{NNE#D42P5LK1) zGS6XiZ3C4nl-r#DSZ_|lJm8t~4+n0!qfOLSvOJ-c!;7&V_vE(e8p^&_9U4(;JiT0- z7nvZ~{)wdcT692Mk_7dJ(`MVPp!Ef>xER(|&zj7q#&wS~t9k<*BKl*|41I|TX8hGkV0PAF=FYtrS*tTAvWgM7u zS7O#^hS4XJ*T0lvmMGheJ;P3haPZfpV2lFgiLvkc{hPMVD=sUGbQ0@9CpD9i{Fdcu z&Y}S0D0ITnmX9PPM5!*AQCr6W*tIdGn=KUqp(77&iglh)znf^oxRp^RL(83}uG~(a zd)>OzaORD`w(FXAAa9hI9SsBNsjCm?Dc{B%Yo@y~CE+k0`_|l?G3bid@rm**hjXF) z#2Ik*12LSE`}z0O5A=hdjHgk~8g;lfm^;LHe}a{m~dB|C4pw zn%479PsXGzNBeHFG`DoLm@Phgf2j6R<*U=BuLRPDbIL;_=A!2&F1H;fjy~RYf-04w zS-Q|_D}!hv@qugUB2!8XB}=kB`CM#VR=b8^c9w{n73Pd6$%mpoeuPo zT#^FLwLvZ3uTJ4xH!2U<9ISF6U084TQ1z^#e$QVfu8;Lu#Q582+;n8T*;!anTS7-B zs$gfAp~Ce})EM{jm& zlPz_vXDTt`&qW%qaf`9!XXzxIjD7=GwHesA>0G~kHR`b9i;0w)rPf!i@aPjC;pBvj zlz-g4n_gw6*snQ>rHvcr>(f)ialXT3ueVCO-C0*MKI-@$Oo%oPB^)U}MOpQ7OpRjM zU{0Vqq5~xCRFxPXL_iDzp?5Q$2=Xx!`h?S#JhfpZ0$_h@VG^A5b?wh4x>%4M6xQkI z!sY9L#2&C&siO{gP&L=)`rYp2`Y9djOHQLY*^5zLT2^m~1oNEJp9(2Di?PYyq#1X7 zt1^cPLNM>tyO%s=_A{g|2*@|p=E}6<^-SS0F^6~*uZ68 zaMJwc1N0@{=&lUIs#V7zL;JQwDJIvS((a0$18!$+wY7B_u+jrPDHxyeMT&kQo2?V7 zAzD0VoyQaml6aQO^E1^xF%SAiEDEqF#sO5bnucFvZb zz=_JWZ}%u}Bc~uDzQY|GssTu&vBSUQIg8pTgEuaZQS+C>i-Ou=6KP1-SLcUbezf!w zw&Ekyz4y@4?SVCekezEdqjeevZhgU_#2Z7<1v5fi%Ol!AR39P{mFSGEZPLtVxJc#B z>!yAyITeve=#X40tgr=VMW7;t%jTa}K0(NyabBdk2&UOyeXHEyqKp4?#_3bKn@A@U z_%&Ri3VaURYoea#Gg3_~C$}G^SJ1*OBKRk_O*{S#+N2wp4@NTFa%J{Eic)^>yX`vbLy3jT@)GqQ}{&nn!p?`jZR$AOR#ONu_=(|Rm zmN)b{^WFN^#%vMvTN&QA-*yc7%#GYwhOe3Jk{dqb!!(q2?eCAGNEr}}_&ZVd@;)>% zsTIPElk09GfOF?h0gfPvJ@*ed3T~~_-mz`ERBS>-wI>J{-+3}AGB1{qd6yfiX8|?7lSZi&sF#FMcJ5cGvZ*Q{KE(%YI5&T!q z10T8fy`7RRyzlS#J44wiPb1R#9LRDY#~)Z38A8>8j6>r2VVVCyF!i`1G%2x8cVJ8F zpZdi`If7Dn?k89MqQ^2dbjq@{bHFB^W{03mkKJk(F}~rjc?XngRuO^@}0+faYGx zjS-w48W~xg=Wl-}o((&<}L4yD|&lBOw(QzY_-JQi^s=wNn?55i=?+;PJ^-e(U zTr%Xd8&4gaYssbtx>tSZ@X^ptPSMaJtJ}b;OIttqQJdbV{Dc;uQP?u|j*98h-@Gx8 za}^=?1l>>tnh5(4>4|bjB`JBF|4AT~21qfsKD^9e@Zjc3HZ2^>lv6ykv*5^T6To&o z>K%TXU-M?KVP(AP2^#zBP@9kp^eg7GF8h}#TXVtxbUPp(|s zWWaqkc}~UFspy~H znYo?pEdDU|9c|zIU@3LVz->~4p6NJc8vY(Sj(St5hfZdXfbb=N^^j{`UE@pjJe zYK8h`7`*MlgnJ)TOLlR^dQf&+l($nSb*6{woqo4Z1yVdn6L$;WphfcFBYs_Sp8krp z@0Lud;IxjlGIQ!$vW><^{`Bl}+Zh{Q*Fu-7G;7_`@De;}k%-hXi~}oo1Vh*AqPTFq zZSF*;w6&hjOizAPe7A|Rqlf7LvKf)Y78VhC4f|li$Za1rSc1_(0^l>wJ9n|@kMf>X zDAiAm&=ntIf8Z^*_7hGJBKj}3$8Oz1Al6DC3U>r{C^&mZs^eu0QXR<*K$POxCyxr# zNq%za$&wy_VLr`nsbA8AK@}V0y5Ri7Z!-y5&cy&VD&mi3ma)VWi77?h?0`{!Pl4O~ ze=+6AIY;kY#hL1FiA~W;Mx&pAty5$y6#nc@`l&ZUZ+Y&s*uiQgATPvN>QcioRwJ7A^bkaj_<_>pc|Y(Y;;e{ zafM^FYAzG~RvB@=qrO|l+2@I!T`T0O#H5ol%)8%a#%1iYyZyJ2$^}7x5MlNP5c0>j z&JGao+YL9k;|mCvKHH7fs#S5!UGe`!X>!hxtKrCh{Yt%)rN-dM-BYOH0DJMo+(+;gnqz@Bz&ir_5co?} zoog0p%E#im|N9^V2KQ?c|F470AMySFWsvzXO|^@=VD-UGIYXja*@QW^9Ktr<3EGLK z_1XB7pyLn=XRW~J&?uob3Kr;OXvD=k{p({k$ju7U|u^z||PPAUTFM*K6YWpfI0 zC*c0kP~(^88C5BUXlb*L$i!kwm!@ChS6eKl(&cx?vXuoQ2AbA<0xvQss2kZ0lzNEe zY?D>WZ8Fu7)5!$*L)X7p$s5q>R{-+MFW)8BJDRI8qO&eNcf!bzivU6Yx?cSevn-i^Z`gM+^m}YfZ=Bq_&UPF1q+pR3y9dlRXuaPnxfpXCZ0!rWlAM$?uTvKaH(&uO1$TlRxFHZ%jFaj5=v^mk@HPT+ zZJQz>+fZ`$bi+=9^oJ@dL0GT;%E9jJkF6(#-rf%9Z~Rz0oa11Fv4Ac?Ao|3Y=YQ!p zlav{M6opcDXJ97<9BP@V4)Kk4rIps}(DbwEYx~n<(RYlp;$6&%JrDmwc7pNR6d-ojYaE}p6!m3M;u_=CO-2HZo5}~{8Wp*(O$?Lu^hc-c zqF4BRj@E0W-bV_WS}*JiKHY@I@EJBZs?S&jY}4<$9z-WGQxzQg6^V@2X7`OBDt%_z z>hpujz)35^F&7gB(qJ;F5hme?Yuluy)=CbSp47rAkEYeu0=NOp+|cpXiG2irw2d_W z>nR-Ew5e&4>qk#7Ykg)pG~h#2l2J{lWl-N}vl?`vF2c}q=p3Q5a8T}#Qg)&TnU+N% z%Y&-g=LvwiUd1;_0wZ80n&ubW0ukeQ;W?Er0+a%mZ#Fu?GvVCI_c($7kGii6t8(kw z#Xv216tsLKX?9)+s_;Yoit~6GiVSB%c`_K>XA$+5s{PUN=PHeIQq|#fx~j z=~=W}st56O2u%1DOW|DKAJdK71IVjzvJls}=PMD{=<3D^DoGZ0LfFM4a6w?_!xy7R zo+!o<>Y6DN^mXkp&wew`U43+AqhPY34dkqUb6Qp%t{ zJ483CJ9q*|$#?9Ps9w73swZCvq{P!XxpN5#=w2f7!QFJW&Jis&Pz!-5aVZ&C);F&y z{kYY9!Y-K$@v-(8YJw6^9Tf*Ow}YCB8gR3{vY+I3xBxeQ2TmR`#YsaWr7<}x<6OoL z&kVAM%CchuHLI0^#+F}x2$Sq@Jb?FRQuW|(9df1>-kTb9LihG^|B3tSZQ_~BHQlMo zIT5y1Ju7eIAEw0@*!I*Gqos|psjZ;-j$y2A1U{VBA*1($A9U|lQ_>U}5ea&Ui(G%N zl=C@C?MR4&TF6I0i)aDl{83026T)nHDB_>B}zL0(a*4+%5K#BpD>^7`m<}%}n4$%O#&{ zN|XI&lxM`58Ehtx1Ks9(NoiGl-LqRi4=c;qXc~V*XYpKXYWtdYod=rWZKjXF4DQ(! z*eVm{=+`0dGpq;6rG!=AE%dKS({XcKjDY4(SPk@F8&ZxjD9>HZ;6&+W<3p=4*PID2 zFEGey34t;@%n*v@n^X<$gy&oBi;t@!-=Xjl;`h7!s=G{Iz$) z95_XAzjChXh^~g8)jhE}7w7kb`SX?EuE}~8hNwI$*Gn}`u&>QrTU%c=-&|Q(*(|J^ z=)sPLIIy{M9p;@}3L2gO>1;6*AYMbw*mSO zt!h^3^lFcIJV__#9FjMa`Z{`z$>HPGse)dzQN}c`B<#YgSL9b%qkpC<`f71;35*w& z)H8AD`>Ro_P9&Yr%!$$WmRqLqt%=|0lac$NW5+TjPcX}%gQ}^oo^!Z9^`LEsS;;9m z$QDi@4oG>NI=?SMt48pfS3W%(`Ef3h67D7AO?N8t#1g)8&Hco#E?AQ^k7P0B;}`js zOmknXUsJI&mXEa3NK2Cx6f@*=NmgA@wHsLw?TkjHW@nrc_*~u+UKc=8C;Vw_O0_n4 zdg@@h1sRc|rgejq)o@lzb6T{>A>#1Wnyl;vQC31r*I{PDkx|Xnf-{z%CY{bC6}CO# z!INY!fNC-F$bIkhIP z-Rklml+B(EU0yUoe@|a5?abtDZp*ZraL?(w+uLyT1OCE-*>b`A@IV`q+-;Quc+Jsk z6bN7@g`L-3gTC|ijptdu;b%f_W1^R+sJggrC>T?Peehi`amy1cm&k@b5bR4b8Gj9J zp7J14zDej-ZgyvxgNq@?hCjkGLF&0$c^-D5(`Nmzb@trlcEZwHQ67O(*|Qlb&B|wu zEM~VzxHsE%42$s-mBd99iy!uOe6Oz)SIUseJsbH$t9U4Hs`qqh9eR?=jbWB`FJge{ z;(rr_?-Ac3;;wMNGlzXjTXCkXTXk zOj2MDLuulVCMSlq`=7@xhvzN3o6_BFC!)?P%$t3h?Bl>-w2J5Qi27iRPq#=)msZXp zRv+v-DAFRn)d>E~u5P{eMmqMBzwpTzI6~^u9zS8e#NX2%3a1tS$3*FNBDkFs7K z)a3Zc`}|4sPFJX*$^^l=j4Ea&CJQ+?MZ>lJ*F)?Se8cjpmRI-X#aKNt4yPpx6SdPC zFxw)#2PcK-A14L5VSubS1K<~!SQDNcOZ~KpJV#5~MZbxeNGeFO2VT3{afWIwAv=_IMYxCl;#Z-l*y9CaMEiF_T7#l%IR$jgF;%9jr#Z*aEV|F0+Ff4|Qe(|?&HedY4) zD|r0VRNyGNk&?|N=((k1c-SH#$|K~s2SxN~Z?r&e5J-Z zI%VznXB#<&ytL;pk4><_wDhBHgY!HE`apOzysT%KEW3AteY&p1RP>u-eS+1uUBk9k z3_9y~Ug2(i;I0&?bxW+TWFh) z=cyuq#kK0yo7zZE+$`cr53>>yntOKgs*IghZdV^m#u}G8+O-zk*M{KzF>2zT!Gkt6 zlyCx2!!44c=l7?2HyhbNUqFU|OeOr=?*2p8 zJu)Nd-2vEvu~+^z16NC5RvtXGcLM(21GvqF_s}+in5@4?rTKvM75!(+s@Y3I4tJ%nOkp@i2DTG^D z!E_ses*QMhB=hVTjW_()kSE@K2`0s_2`XDGVup~7ii4}jQ}T1vx@_jnOJtkaTaHAF z20Cv3EO*)*q$-1iwJd^&0sX@8BG`_>Y{rIa3HTBk*H^60S2MkXBeB9lsB{g^7-9jO zZEwIqiD>507b=V&a6PgoS8?Kh?qu0P?lvtwWU`NfN>4@!%3$e8-Y%Rz(> z6`cH@kc`&zaXr+6K zVJX&Olz1vhSu~6@-TmOSPoahW_Pfgx;$hv&M;W&G_w9Sbkh(TpjfFPf8FnkNU74q$ z9BNI0!|m22idPsDEmV!oN=xD&kBt~D>0=9{LU}8_cXCTkF&vUve@1^M=n4%_ieAg* z&SaJ$g<|vp=F?(zcpvx2`;S+B>6zn__1)d6=PH+;weW z@vLt4)beu@+DcQg3|9M?X2;fKst-PmWEm>x}NjCRhfH(;lhR3A|geJD8!C&CL_u z+>IH>x{2Qf1_p+Qt12mN&g9NILSItoNPSt2L+e?LIQZMKYdAac`Lrj<({k80a1>3ftY=V`Iwaw%kD>+2Q)c60Skn4k~xIV08hG$&=0~L3ttU* zQ;;Y3+pAJIjgFkEE-Q8uXlF_>vF1$ll!Q z`d8)Ld&|@0KeEh?XWhVrY%J8*!+DJ;3>9!XEQINzd*=k^q;JRZwJr0Vu8fQyKfcqt zv(!X=TK2E8n-yygaA)proeOPLneJejRdYZ;O9_7%anN8wo34h}sC)ht%m5HHtLz>$@X%n9cV{J&gFK{$BkdoX13*BNV34?4or<;8# z&mP9Px$&$%Ho0F(X6gF%>&NKxzY*S^6ZSVorEP*0NliWUhN{nXCqT|;@og!5 zZmw@9>@R=e0;mmaw6aQs>~3Uz_FzPC5EORJ%P0SfsJjQ9Ab(wFYduoIW9T&Fgf|ih ztlA!i$HPIumlMo{yu7mhg_evqFh<$<@`lK1n&hJ-=g(ZGK~)<=Mc=iVT(Y!Ks6k%R zl{)~2w_u0&{wWcyxqeHjwRb|DD*2pp@!6(N9&}}}(eXXiNvPx{_BLvpPuF*cdfYV_ z&C1v3JMJr-LbYy!?FyQbwQ!N%7zqyYfp$n33a)jIc=RZZHg&#VIG^BTzLc?N!_Gv4 z_W2FTu7)|K2gg>11DouYiJC>IcfOt`SkKR1tviQ8wnge~I+7zgd4^q2;v~iID9p^x zG8(^Hdj_x(oG!9-2(F$y{xA;O8umScN#xmj%DyzYIKCqKn(?fMwXYAQq$?>+++(QR zeMLq(+46>ko;UbLz?Q8FFuQ#0-3&Rw&pt)uA#0 zC7HdpA~y1)T7!23jjW-io%zpDRVfjbXBAyF0l(Ns#)HJ7+s`BduIdZD!lxj$z7b%N_eFABtXmS|ePJ*Lx zQX#hPD9epQyi3y7qjijF4(BXIKO0~AM(SAS_p`IHfVB`k}bZv2@LRepJt z8O(4_!r-iv1YJ1Bq?N_XA=XgG`dT#QOr*O49MHRpCISprTd<1ubH)8MqKERzkM93Ms$0Eej6n~i+r7|zXfvZfmM9iOJOvxsqaj6B$JU!R&HZf`MoJHD1+}qMu44JWAr?X+zl5LmV@;U#(hG8sJ#3E!1ysE-bA3(^4m| zi=8ueyil_r;i!N*Kw#|*=bbEd6!o#G*2L)b(S??HbN6G0Oh2Ri?0?>MSypleC%_oc zC!GC?BmD}mwXMQEuc@f*pGiEo1>%S)yTx}79xT62j%>L)1ta;j&h(*j%E_y)sS6T( zq3TkPx>_>(>U{EKXACk+-7IvPzFDozJREjpd!bf;YdLcJBB9slkEtw$0f4YHJ4BvuA7fb|45m~NcI3Uxbj8!Jhd zwVG@fyU5#o%_f#-6P~w|k@6Hmvg0B%*t6g@AS92QF1_oy-sZCm0O5-GgFFYjVnQbF zn_3UFcLT!RNT&$xw@*TXP%k2EMa}f*ScajY84A4LBky{Ui|zWAz3@Q{QA*%KY_QSD zbf)XSgkSt4X4~@~@CWYT>_r;8Ujh+QE**JMJ`&Zyzu>|RJbpuJeWA*EVnvp=;iSqQ zsUh?!)b`|+$|%o}LR(7oi!8m&zR(hb0ro-BB;2!Y& z1G+?vxW8na4eyv+7UJ0O);vvB)%j4p!Z>*4or$>PCrV-b+tQ~wMCT{ozql?@^nk*Dub*koRs^ylqR& zrw;H9_mp5dwD-kX9E0DY-UQ{C(mTks_BO#?t!pq|D;VkE8*+A}8t}hxyR#k92|dTa z@Z_z(6 z2!*%DZ^1}a0YxJMJ0{yMHeC^?p+rF}6K*p{ zVe5Lw6NNPWi)&?n-LYvY{c)n3#=5SUBTlfD9W(HKK&qD`4m+gGPWUKO%&^RVQv5H!1DtjkE<(9Dcf}J8*gWdUnuXV##dP^^BQakv*S3_xj4Q8+AkfL1^>} zF#-gDUm6-D<6EG8;lq|U(7(0`>iI@rU$tt)!+XSb|Biq(HS~d+r~Sl#1KPDtccwQP ztHfclpvvOYr)dVHr%qWwgUUp4=+Tv#X{DhNC7seG`Qf@=^HrOrUb|Tyn|bOF$o2qB zhQ#{Kb3MmR<9{5YCXy^XMB*ca<@ped?UhXeMoY@OnZI&oEhc3pCEsRaOuF-= zvHhc?R-<(kl@$`fwVztnpe;R(`Np!LLu4y;IspN8&iFDd*%^dP13Qechpbt3F85$Z zd>Q{j8gSSRpYz^EoM3wdvuDeXjxzIYk2SBjlF}a!y(~~fwJwA4onHx~c@@+V;d>^9^c~|Y4Sy>Y)kQ$eRs0uOrCy6Bl~=*rNsxclqnOhbI;!132Ct?*%cAOOK7# zDxT03+j)|6XBIZh4Wju2rTfV|NohW+ZbvxT?`UcV-q)&XrJJVIk+aYeP+;XWPt&-h zeJ?-{mFiS+Z2Cl;KRm0amKu18KL}qPR2pdW(=x(3yX>QKTuIo z5fORy#Fuw-?snyQKF1IX@R&f4|1l4iL9Q>937)AvgUtuz^ zxml(>pOgNY<%&sVQ}wuFR)Cc}x}1qV-#k8{Amv-s)KgS~j~^_h#oP}UnM z?5?vPnnx=jVI-cU1OgQ@k$oWpVsE_o5Tnazk88Z-mlAA;3p6@n2imV*CS+5iV-uLgKCe%sTANJI*8h z4z_ne5#ooaYh1_^{yRs#7kmHqR=;o&j&1(UxbD*7W0-}OxNfn^c`_t|1`LIBdR(-FyOD zG)#(ENMN-e174H+l#CbFm1>p zJzhSgbFa#;CRWI5b-B-QJU;2s)wFvjR~O@v{Kr3I@FO*X%3u%(ap{SFFz!Z9wIz)w zF3-ChMx|M26*;{zqb^g^beO^4lY}eCJCD|P~_P~`?_m-sUa!DyIWa% zAC8!9zW%qhm#)WNWYezeZc&>{u6MIGJeDu?=d@j5vur`@7~Gmu7Z8q;ZcowP_wU!Q z1!Eensjc?JVe|_s{#pTU1XdrJcUYyeZ<=g`iipvaHT$KT(P`r?TAg0HmagChrHst7YLZ!MyP6^d4lKw~_+k@l ztYqj@*M@3+YpvuBwFHhO-hj3;R_KJfL(-!H)$ii4PJgTn6Qo!KTY~Tjx;hv2uoK4d zX{42htq9n>M}yN0q)uS_6BO`iT(Gy(W;!@G+^0GpqtZ?uKaSL)3%KI2V^S+<^RF3; zYeI8ptbX|tU#>^v$ z+w->F3;2xc>bM}|rZ`8yB^p$G^!;jDGIsIizUl~i#MW(npOIzK=P!32?R6CInr=3H zM9!{{CKvBx>9N4}XZgztakz{aewb9P-Q>g$I(zXl+JDpV{s*41)GHuPfMi3PdM7~I zP6IYRzC!5U1=Zov(CJk#os!2^T4r@TCLG#oU8A{v{kl))U9{>sK|VPFk3YSoYn zx1aYIN><8l{QT$&fHR2+0FfcGwM6pMQ_y4ILgxNpE`7f1hLP6{|I^jS$HXqBVPOf*@-^$Q6 zJlvUWWcgE;v7w!Lhe8e5f>EELq1P56gt^xUF4Bau-_Fo4{gG9CXOPmJ?-2-#p|Fta>j^ZV4=W4ZI z>Wq)>9qiXZozHC4KCik&JSQVmzfN17SG$L(w5@KBU7ikRH`ZGNQ26y)!P3f#+*yLx z&s|)ma%}qw9H32$Z_U{EkE-@Rkw$BkI)stwYC7UNGt_Nc`ICR?FNi z1K@-6BP2Y|e84Xk({Lw^k7qbW6g)IK8be{G$>mn4jZ#Qrq*G&Q1Wk zf!jdR;!Y*|O~PwNKSX?C%ApTbviujMlfb~hz(^wfQpBzoK|Forw;Gp*0*Q^@C${>8 z*K~$S-;jus2c$5lRgQ`RZuwSwt}Ozi)0kR05g*@jnNh?+Cpx6UmqUUxtTX4(N;c(! z{tly60Q8+TH#374jx6(v$lcD47zwKJzo5&z)5dh>NTQjg}`8 z{Dg6*2Z3V_4X*{(8Sv8%%Pf1(hTGr7+`z8Xw;gT zvP(~4zI3VNOC}bJ<$5}HfsdxpY`E&}+qbZN%RTomt|2&j_ppHnlhuTsN`rw^Wf(e1Jux1 z=poBg{EkmX!FpNJQ9T!Fjl-s__3CfxOy*$VTDa|l2d`fg^yFFS+`u!KkGo#P%wC~9*9jlNV zj41G~bbavKKv>Xe$|lKH%bC7HK5p)AEo`E4Zfl-X2*t_2eSC8iCYB|kAg2@Sp)Iqh z@+M{;zsBDOIWs+0lEGb=$QjMQy`}?#JKy7L|0k@Zr|B@rAmQ7bgNpvp@!|&rZ`Tnq z3eIWbAS)LbJU|x#Q&Z6MHpc%q1?7sKbXZonBJ~kW@rE2?ExQC=a z5d8%}FB}Z{?-&4HM7;ekc`Z&Ivx_Wug+UCCxz489~H3_%!1*8w0R&j)G>qJM#3 z*a5Bf>GW2QQa5pY-#^fwK^z?a?X#H)qk~rOhF%=Y%~k6Y;|kXf#oup$Xps;V z2oh3G%!6<#HVgCq-3`t0mnUqB4-gt`S+S4DT$5?<8Hl(*X_%^wBTc}8uoAZ^lq`ugh|XIBmZTT9;05qQC86gjJbAOXx6AbQ*u|X@%QTy zA@y$ZocR&M5vt+DUyQCH*b$*V0UjcXj5<(cAW_me@?&}O{d|)22JKkq-pgvJ3254D zMzHtn2rdMaqZ7!yY1ERys;cqbZdXyY%TED*X1j*PM6CP^bw6uA1IzmxTO(8bx4fnK z4XYWl)=LLrkC}J^@q|POu*ZH)z<=LDdsOV+ruyRpfH-&eTyyLH@Wq;Et_Kw%KM9oD z-?B3Nf?lL_@ty>z|ImMuUv7u$fq*&2so#gronPepWaTx{-GGf`tXM7+mB^ZxI`C1$ z(b6rroA^ekUst|Hx&rhxQXsgVc+JMnj)aOA=>K>xrjGPGh1m@LVDZk7kqwTIpCu)o zU!P14HkSHH36@^V%q+FAu&{X?+HFGF;w4edY$~yGx=O@ zKYgfUmzS55vXmNT7r>A($R&j^s|kK)ybdbI344HFq`jws*4>8i{``p(U%+%~8^-!k zzI{|tTe}5(3$@e6@^qRL?UEFD`n9wE3Q&LhA0ek+I!5y5qJex%Vr|yj@93zY7wZ`p zx{R5&Wf`b$K53|U{jM`vmnu4aqBX8LPze;F+H$TfiaPVoQXcPMJH7jx_ei*ZCSJy*2`+F zArg3}!NEt_T9H6__c?APP8QpLwY$L0ZU6PvDcVfOSXI8YAzy&is+{x}E5^g6Z3 z)i&==LKPr}Mu~zJVQ0>r?^nzW+B*d+_B-8iS^EeMG$#Z20pA_)z}_)0FgZ}x322%Q zJ`K@c_4KQQ%ui;ChR?m&{<#$xu6Qk|GWZI~hwBw7>5h?(o_vbvpt~`rS%LIq2&CZT z67UVIX~URUTGrLLRMHp;^sJ5JwB@y+mhl3nY<&{#Y-MZfg{9|9R5zG4ZFFYd$A-^3I@ zR3EwTOAG9*guh*_AV-ZLz8!xq^+ni8WiL-OIzvD14{hv);sW8`Z{CD5tCe&a7bdov zg6a6>r_xPB@!vpn33~wOYX)X?!yG-{=i_WwAE-Ol(}pcb)L0RX@SrKZa;G^H;(H3q zqVMFN?52@du8vUFWHu~MlTxK`;v{rCMgJW=MwlL3(EM^8*ir;_z#$KkJ#X?AU178{ zA6PhMoCOINRo3_TTUEy*BO|?*px<_Z`qx*cgWp`;-E)ml8VnZL`+z$Dib1nX#1tb6 zOE_CPP4TrUX=((yqyexo8_+re+kZ2MFD?R!y+XLulw>OzEb5_9e%(wf$}#BMxZS7B znGS_vF_k!oaSgTvffn5ky>>nCP9cD_`^I@c-ClG=ADT{en*MM^F2L@={rlgO`>)nQ zD_JZ5r2ebghE~tq27a7ySYL$JeI46d8--iTiso1oAXUrp>7sH41qGpvxWk+CFj9V= zXW#?7)q!Sceu+u3#Wd}_FIbm3Nc+_(MYWqbJC?TeNm$62hZ@1#OwbVv~)vOMH_RmtmjHbDF`k|e?bRbEZew)#3 zZ~iHELRuaOy0UDSCaMFOM7LWL6&pLDcj5Gbsi33b^lm*gE-wVATBTt0eg6>Dq> zZhbgH+HuZu2|DFb_Xi#Y2*XFP9i@<+LAw4(+_Wyp6(6PCL*6(BhuYj~_p{d3aieH@=91~ZPpWh`x+ZL4F`nlm;kmmy-yH>tid;TzvH!xH7FB4I?1Ovo z7RQX|)RoMtNOX44<;=0~_z`C8+tq0PCMJr|{6PRZmltaCvaS&3Q%bcl4J8GOA}P{8 zHp{d2b{TBM&)ha&%N=vpa^Qs<{u5mWVdxgei@^k8S98@^Sy#SWz9{mvW*@Jd9Dl3$FCY*>Q^0Mbld< z-W}Cx?=%Y${2V;irFvI+DB(Wq(rEEN!y&i=<^qXVn$hGM)m%;fso2zv+SZc7x?op& z&T>5#!u4f2Z%k~c6D1yAx;Z3Of03}jsz;=-=}C*atN~FOXPzmehFUxox3(20gS;0x zQhRJOWJ2^&B6X@i7mX6;VUW|`XVpY+>#w}r*NHzpp3N0iR;mt{Pr|-DN#1rmS{}+J zi<*~E9%E$|sj@rv(l4*|b(tmuIs1~~4}DR^m2ZvguM`gAi-z6}w)%V&xydryC?;aS zxH+1n@X^pe)%NyfrsO1tbzOE-VYLbU>{P0)5x3Dxtl`Q&r`mH8v9cV!Eb6J5Ql-|F zH5Jwz5iZRyFYOKklf~TI2zA&{yyey^u z`l$1Y>b(c%XQ;K4eP3_7-Xu8^;`ia&8iqkuG`ljl6e=>{))cX&kIynX+p3|`n#s2M;8^R#^Rb?Zb%CT|If6oN zUcq4@7@zv%_B)lQ%gO7`t)}Ls=I`WWB4KSjYlv4yK*?_KUL(!(ghpo4O~ns!tcp%2 zJvMWNh)jMO9VriZzrqjqvYPjwcW@{24(82-%S)9)L~oR{==z3wK4Y-|HQsm5O~5{0 z^;r%#BzWGkwARZjrqW?mV}GTNev(R9t>CEh_S$~9#pK)129xY|F@BpK&-ZFu)Tbx$ zN|+e_E||y21Y#EZ7RY8bf28RBcJDE|warKFYflM@D5!&Q7118`?iIC62i+O99POXF*uLn_0< zGFNV2S>9pTR}bjpFIRCf*odj!va_losRfIR*u!0xA>}p7nJdWE$9qR<-t5_Cf~Z5u z?ZXK1)9vuvW8f}k?OpSdZpW@kQcRK|D8Bg(+@JhyV909)E6bgFSqA+}Ms5#9=PC?D zQ&=QR5E&PP9YR(aCuGLscdaSu@jSUM&S0=AIbghwQlO+?oGR3Aa^yU|Yp^2rGJk#C zHN5@xwKm)S?pLPu&SAz*L?X#fUg>u03krJ~Hr#tbm+RbE>!P0d=@fhe-o(LOBsQmi z=Z^dlN#Zti-bofip~nv*RiCPPMiz>HBi@+CY|!Q(URwBd6)sB)SU?KW>=}8?F&?;2im0GW zAl>O_;1qLJXH_{HZoc�qw$XSA^5;MH*-gs0K2ld!^>xv5VAy%S?WeM$TP;0`Tx+ z)Bpd|U;Ww`IARoHsonkdJ3`%EqyL9ASS%a1fj3Dg!AujU?T+Z8C?gE;{ zzeL2_{=okI-Rj}LVv}EF9vea?dA3Mp@3ZHqz%fynx~21qZ>KX)J1O4P60nn5FV{<- zu$zMZv>=vYRY#Ogy-qt0xg`k+302;)KU1tBtP3+Y@d5y3xbRDw2Ku=pGME#CN1sf?OhM-Uiuluqd@jYf69w8 zLS?P=+7<3>+f^!MnFz#HCN)zWhwDi`4?rZ-5>2+=oNHyAcpV=fACfcg-n~oJp1$D@ z*~sdP>gwug@#Ik>>r0c61bW=AFugea+WCooOZ;B0cg0yvXne(EPuK_`y$qG0#G!Svyi7 zbKUHAdxUuWd<$lNg!Y0H0sGg5|%B}^y2FB}M$7gu^OVv#iblVk57tAu8 zPWM1VCdB<9t`N!OZ0q9-O*?IP30w0yPFpL!t?j1OQ7dfRw_bf5^l)*6$EV~Sjj~l^ z!+UQDtB5pndZ|4xxu@+lb0f`j2$*Eb0JaCiqJ}W!MJ)jr``n$mjn&onWi9{Y&k~uh zUKM6EN3@KIl8+9F(8QLEjg5tbgtWKISC*HRRU}EPH2TnpA__VO(V@1xo+w>nBwO4_ z-1+=WnkQih{+e4ND>qYVE7GP7=OtnM@Fe{{7-6(Xnk{BHCZnz-6dIYSq@oy9tMUP1%3E9G+BMsin|S~$IY zbiXILZu)J$jJ|9jknug}qu9Amtlv^-6tK}wq-12!4BTODUeiLNcrv*#NtVY1C1+=6 zrbJ08D;{OmX@IS7>XH^OHzV#h>x%DN2iG) zkek4jx*!hoPmb5WHaAOG4h;`SM@8vOqGJP2qfqzm+_}Zpm69$SFVmA7nQYLm09VQ! z!Sds7_Tn|4{w+B5{{T0?g^kmN`xEp1KtF)U2+!=w2xZi&zW7Dd2~a{^)wc6xyD8;{ z@92b8v%7GfqO?HwIo)x*B+qITV(o~-TnZNY(Xvj>3-eY?qU6*pJ7qmDd6ul>^gM=C zS6A_U-NwU-2ii#!!A&A^!#B{M?&lYI^!D~Ph(Ac(gUebrR%S^^NHRMN^xKkJj<&Fz z>rB&qq@F^mcx3QLbm{tl5X-cTbgJgAfvV3W)TbH#xN#C zI6Zjohs#XoMHpGWgp+yu>1?m^Bhqy(@(i|X*Tf6$pWoL9eakv{E#$fdWKJm^G{z3% z#cm<&zmETstYr zgIP3N{K^bH2*uH{?=qlxz~2N;G0*$dr&Vc-y0%doRdIApY+|CFfqstk2z>tiEpTLG z%91_9Q}td^(%tV4c%R0kTkL@kG&sU@{QkDbs3JP3BH*)3Gycv8{|YK9TtwB;XsxO+ z3Avkst5^U(49Un?D`ct?4DJWZ#{aPV?@CL$_vQbQ58sOpf1Sd;Kxan-$UZZ6nrdiY z`kTVUy%s@Kya^;9e)036mR&XEKg6Ev$W3_qESJF#$+CuZA=&YzkH>Fcv=Sq=x(&m% zX#G2B;a`Dpg<-aOR)dcCjE7OSi#S84njX9lmJ}vj*3smpy@=T`biaqCH z?4NqSd~%Q<4678ImOeYZ;O^DilVTj{4432IPCWltI+@-<_rNr%+ip@?`Ngl*+|?0Y zUHHpmFCnecg9sk~{d)c28A$d|SjB54u3hrJG{*+WIX_3kLg-YcW@`NTltRD2IC zcqm;=X8NTgmv@66Lg{2xb6~|DPi72`)LEHc8Rkm!3{8%Xvkr{^pv>CNAcOSOsB(h(QAKUohY`Z z3?e_(2rg~fNPo?Ro+o(7%}Sg~IRUBMrG~0vR+kkqQ8e2Y0s3r$$Su>}AQnJE1g(arx^{IH;f{YwPRNOQ_6iHZLeiuoG7qN_#y9nd-K)y>43Ypom z%dTxcI&Dk*+UHgt!q7}6_gZz!s;*A?Bowt8$~fNpR=qD#p;{be*sIy@Oi&;FRV z?n@$1&l|irMgP9ryjbo$gc&f!yWQnLP5`W>rNeSdRP$m*d3l`blW!95&dA@Oi)Swr z7ia!LV5z0n8y|d>uP|!1*_cT+R<-##zog_U{_>e=)-Y+rYxs&Ruor%21jtpNy+vpgXwcJ> zF2Oh5hD;`>m+oZm87ODPSbUt_!Ki-UJ0I^e=W>BH)UXx0#|L$lI%K=D9e2d+4deFB zKO)=bC8UaZIu_(wO=Yd|G9>-zwz6)Dtd9Ob;UqSh8oRJ~7dOr|R>E~7knh!r$0N}y z*!w^0-?Zf1^Fo<_8lBN0M*Rl_j$uBdm0Z&}K0e$h8x?c)j5r2?_Q8xLusouuY?)SEdM5B~;o9mare8 z>K56lx2!}mq+f&z-NP|ic~H%32)Jr{J@L4zNe0p9!G{laPOEx$$#BnFKc!3CASuj9 zd!hE6Ybi_BYkRl2m9A^;=7Hv~6ma^hHXCTKNFBpUPVA27cO@dpCt3oo#iWAzq69+) zRth&3RC-QYsv1x440T!%>jm8=Fco?rhIR)E&rYvXDz~JP5EXB6o`wu~Za`eHaYyz& zBMsLlq>37ydHdRt^`PMn@Pp0tMDuYOq|XXEZij_Xm&?S+uC7I|F80OAkc6;i?Xr7l z&J8&PeudvMj?}m(7Od&0<4Z>8j$!7$^Z5tA(>f09;?@t8R`fRsNsTj!2?MVx^x!v` z^G6+wmP&V4({)6&>V%JSkt2W+(9Y=w@*?6=gz zs)ZwP?h@G`kL~WV?g5eut`|AE3DT2y#gZsX+bUo0Ex6$ zHVTT*c)4HW*tYrk^Nwug|dWk64`+*5=sLFLdQW5zC8+@X<1d^S5m8=A9zRl#G zrh97r*BrY;bV}^_W!GMk*^adEapH&{a5IL`b0pE8F}{Cg5&B6Yrf65qHW7hI$rT<* zlAWVcXs0Gk#FNBmsns>soNnDTS}L#_!}UNxpc8VYngy_P42Wjs z)i#2zeL&8JaiotLa%C;7u3%8fpAD_OvVB_lU`im|3MdjoWLbO1W?nLNEA+>ALPByr zrKf#mxq-~k-mywpws@9h{?(Fj<%Gj*g56{(e7w!8Nwx7VEX0gO=H)$KnZ4;^Nl?m{ z{NtKRT96Gw!69>w6YO#C)@<77uf#0{@)8nmAzsUlp!5eLjnl2_DMaIdSUudr+JUMt zmSyhC{(hGIcov8{7}gEJP`eeAipq~V*F*a$fJp##k$*>I;;J5atFQN@jK9QZMRJ$$ zc%f(L?^E2Qd5JS?)Mi343@HkBq%4xffi>|9PBjq^QwJ%w=~p)n@ zrr*|H`#ULnAp4mK`__E!mM`S9D=4m{O!@U`8T7tO|2X8IFElJF)myGQCsfjR1!m@P zGkuc~DHhLv>2QX@K$>5PU-N;_guTistBXHsl5YT&=sLkd2;l3cPQ&AI^1G*k&S;QW z2Hj9IY_2es!?Qmew?)<-Us)ZH+?U7Au3A7nJjItWyv4HlZf=FZUU=V`^s2>oWoUlQ zt!Mc+omv-+xx=MQgpkp13 zwg?^)mHIY9(ec%tEq{aVzs$iTDvGfqZzxIW>Vu%2y3J@O1*FFx++%H@FAhQp6#Ee9 zwAfAxg-_nzit|O_5c^ zhYv-s0t!-Ttwcw1x`)X#w5G2jSFQg&7c|??5_@xOzmMEqm>-R%6jrvky^h!yF)g(c zb6wM>Q4y`0n$d5yp~>uokm&J!TmZ!!>AXWjB=xeDwWg+QJ|_>0XbpVYgw)gL63ftB zB>!mW`r>O4B-Omy&^l|Mbw#7S;zF_vG=~PCq#s3|#={MgznF6hLGPc<@*iX@!ma*2 zXspIO@&qN!`UC~#Ee&rif6-v0;5*e2m(H8lcJ1cX)h5+u3*wPs%OLnI|Bym;-cW0K zG_BVEsp~t#nrfPc6-AJ!pkip!MDzwi5ky3k5|!Qr0Rspq5Nbr4AXTc3Dn*Jk=^X+D z=>isd3lMrhq=hD-7sEiyb zl%B54Y*nR$%(#bQN=J~s=Z=&6YJ1O})f{zz9%6pq!V$dDtIuf6whi-mX)o}q*{k|!3B3m*Wc9y7Sjt zlI#F&1Lq)lLo>2gar(AtnaCKTVv;UI1+x@Q_B;;Jg#c9bu5ua4ti4pfwS?oBAQFdw zvwrIIn!xpMnIzr(aq?ybq}m4ZDZcKAT8zj|8B#fElav^}C$qnjt@;x#comYWAS3F% zA9*kJLIOKnU9L7Yh>gh)k!6QWVXgIH;rO`W{p{!Pb~w*`&T2g14Cb<*rWd-2q-~ib zOI}gw5f|f_jg@fY-SsMAF~^QjncB;B%I#|L(B?~L^s4q>J5Dt|IJufJk4)KcQpsms zvfg+F1iUBUr5i5Yi7eUoer48`o`mR4xB-`V+&0aC{#K|5Qhq-hqR+gXHzLF%wJc%l zpPo_%Xxw;gH=5tD*wGm5F}0Y1zSN6zC~Jw(!sZ9FX6K$cpLB!oj5%29T9vS(1C&6QHW#h$*=^FQ!Qo~Wr zc+5nSwjgO?U_EG*FJ?1mCtBt?(bU1vYDR(N?LNbKcFObniWw{yWnmzJi4n3N?MI0# zm(GVidT3xV`eAV>P$ah)HI$uOrs)}3E8KkAR4m+`NfEN#(b@&D%mZ~hxy z!R`tt))Mjdo^(Xq?+LV7(&~KebGOZr)sY0oiYlB`VGIbloZ;Z8lQG`hyFQH`Gx?>i zZ;MD!;ug@>T9WY?P~r{P5`(0tx0>nZ;jUg~oSG5t(UJEu(8XLBe@NV^uEqsErMq?W zH}Vd0BAOG5{r3GMDzaZy+^+8`B!;gHdU;bLSbQ{u%ddcLR`B9{zI;9tEPC7 zyy|8PCcfkrRSJfIz{RZbk&nLx=&z?YZC`2n#}$YL(|nUnxv{%4>0M+$d1j7Y)qU0? z@wuV1X^++1^WHQi87pgjVMp#q!x-@%o1FEjsoXbj#muKNlQ#B#Fhw}KTrj169_!|* zqZ8tE6HQEipSZEzVwO@?b|+9AZ6uhBKgRX&o_YrrqN)9%rF}<*t+AVN8~l7cHB0+Z zCdElo8RJD3TO}oMmVv)&ce?F7uIZ3^f2-y2W9^g{jFmm>w)6K-!_ZU<>}~GU=bt(r~!jzB3YYoQ{MrjrHkF*VAx{ zB>pT6s}x>rWQYrxn3LcUg_Q3V#axqyqD-x=TP`O#<%&red9F*o4feMdIkLlEW3>Ok zifu}dXk6knSIuo}5*)J>g_5vvKMF_HSK_WeYcuZ!$?AL&hh8;a>3V&(D%~bhTImw} zl)@7`yZmBP?6Z-Njq1ufar&;aDJyQ#yzza7Ng@*pZZ#n49M1z;U#EV{)EONR0KozW z32zg6%|Mjx#QrM~n!7(G?0w(3hqW%|%_w@*PqOd=3_Tc|1|ZH1&CXhR?#4X|He z{x$Zh1v3Yc_ZmHpg~cr81VMrodLH{_NLdw|vjpaJU)H-VTOCKet(Vayiqf4qn_W>{ zK$Y_3q$BvO>tqUoInhh(Ldnxs`nOh-rrp;Fc5_p*b8P)FPcGi~W6A04F`n~Y`D*WM z-3-VF<8D6OKFHytX;3sPzO&0V)9Ia<%dHp$yp&a6L~ut$j%g{T8XMIT1}&1pthB7= zMCX@uYovvj#bbLElQMB^Q59=KZZ%8JLBee3M{?!Q@$qdYNlxCoHLv0AmK5YewUJ-q z_`TGMex5T^!}|_Cw8eE}Jm!i^vy+N(*lWZql@gOGBjxwS_x-hwgh!*^b`ul}q#ngl zW7jkAFJhl`Ib9aSz{K@E&EF^0y;7!Iv4&!7o3%1eDDEb!C!z}swN_pN&awTCOe1qn z^!T^SZJ2w@iF5L{Y;7+D*(}C0P{`hsd0%eK_^m(o=BD@8^-+wMgyZE87 zX&f+;+*|z!<}^`e89P1ps%?>Y```BvzmI!%Y1xL zFKlF}@$t}>|KK%NjcnR6#WZBSE_6iohz$yATO~Qn!+=ykR&#z~GCu#loOyZE2f=mx z5!B;1Q+4drl2l?C{qHj_H@mNS`|U-KG5)YUaZx++MF|f6&>`FV{H+~z&nsz_FF)w3 ztEqL*EHqon7HTL};9Z51mOk`glj3AZ&z*(JZ)6(?vQ@a1Xc20OC)dvY&kVO7jcz| z8V{>1b&9RzJO`Aqfk z-R%?*l)|QRu zcb>TqzZ9(T z@o*~t`jd?X<7R_Ps$1c;uivj|4lkKy32tsmv|Q_p=;=L_piF0P1(n`icopf0)txma zxd{u*N9`_E=~+jV9@Dy&K0znF`cEa_+S85rdock9&JWsK#@32TSh(X*M-$)-%Fzhapb^}JzqDjp2){} zwFtIbhl7vF>v#ziTGhC_-Tq!Y9l(3V0?EBkIbXprKENlm&zYf~N$%+C_PAUpPTN3UO>lIoU;w{3`yAir z%~5eE$S0=s(A3$iG+B_`F!@UPx2>4tfRS9qAYht_r(a$IbaS}Z3NrPUnD$7=A!YmL zT{ncui7#o?|0K!sQtN{q*CI18aQMbiFBAB;UunnYHs&3&(9Ms)*=e-GoIXVw4)nRi zVWBUsFhoF;J*c7>Ez3k8rKZ5V^4c0I+B(J4F~t;!IQ%n;xD#gqJ*Tv)KdRY-)`-(n zH#qc7_FvE%U|k`Hkv>m5;74a*pQd2~*-nfLPYVid@&Pmi84U%uO;oHOedRr&MNAJ z2t*!TiH@#%UzlSMHPaTs!t+8<>wem2#p3XW2=7arU>o}CQAzSo`1~lgPf`*PP|ao) z&OOm%ZtH_YB}d}uH9|3)g@7TB?p@&Y=t%wP$Kh{&kFv#iXgx9a&@@CO6g4FY)5x6) zIL|;3{)6jqT_s3K*y#HjwC5BNT4&hgJWVz514k#=nm(X7!FL`;cEUa$hS3(>{#|AC z0DHsQdVF0bHDhNv|LN}ww9XB1wVJn?>TsCWdZwwl`>E!p&m?vg9aO^w@7nW6B^e!L zv+5!>lHXBvS>M&?6>MuTxQy2yEg2GK8fn$Lns=z^?wdD^%zW3hT;B+vBOmj7#SR*$ zVH7!yE-gSg%F8Vtu_AW2pY4f{W-RTkEY-HO1a9@^p3AVv(uIifCbl_4?n{6R^p$PD z?k}m0onpCg5k{P8l?%JgP2<0#LgxQN`MPO3Jq<9%Q_z+@kt$)STmhAgw}&u__rj@o zzRJ^SG)J8wue#IizPk(NRziKRQBq#<+?4zajNjM-^DE#C2Jgy zkm#v^t8%3t)+0UVD-B89H1`Pmzi`VX*7!r^tLWV}nFwLWyN3+YvJ`#7AM*rcE<}j_ z>6O5pHkUVmuMtC4csNaA3z>9u>vS;U%Qw(r2ADqA^w{c{Wi7HHlPjeNSZyWf!6L}8 zimdnCKZ}F9T8t|FSk-q zi=B_(gkCE>j@JCKG>+CF59&`S>=_(vi?s@R%#j-TSru|MO3)HH`_h&b z81?>EMI29NPUY^AH961;v71;3D|EcddfYW#{@R83cPAaZ70549+$XdEQ{)0Ue2Cn_ z!G}u#G9V(!0h^R(WD_|hgA}=R@LFeJvech(|L>>d%%NZs$QRjtK*`NWDUprl6kL!? z6yy3q;!jYb#0rxaVM@h6Il}(?z(Aw_zD6EAxk%dzOUnme*2T8|N5^n~4<{XIy<&@T z)Bk{kq4_wZGp`F-zq>U*JWNkM%L-fvFbB{s6^+-`|k@i;k$-}B6m(zbQ)#+#f+l~8GJZbZ4cC?hjIH0 zrrpTkTIby-K(Q!YLR7?)6h3l{26~w65!B%x=i#J^e>> z(EM|R(4<^R2kTufwBeKc zE%P<|M(a|_Y6#^jdk&A*O5e1MqP3c-Z%~4!|4jeHvb%KkkJvS|+leoUp#?c-p{2S5 z&yveOef54BFr=_oXvMm@(6M2}%XA&}r!#=^cfXJmskviTEJVJ<6~_&ar%J{&DP_J% zexAdp)=fq&&uTLaDen`dW6UBCT&sb{A4LYE;= z%_kb7R)^ppw%Xr!l=2+i5nQ`U9xvcPvhs5cGB!BBNAKr4doKOlh=nkXB{ibx)?pA2 zjO(V4_u4?a-S?Mn_rV?zEyWRK@73Wefy!TQk`$z+D)rrC?r=UysObnvJ*i=S|C-C4 zH6wES2O{RnUJ3V_Gs9th?!DDhAEoerX&dnS3KGhAZ$zNpHS`$u0nJmG^xXp_d3x?L zYJJ`^e4Qmh0q shadow: Call Shadow Operation\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags,\nCallback +activate shadow + +shadow -> internal: Validate arguments\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags,\nCallback +deactivate shadow +activate internal +note over internal: _validateThingNameFlags\n_validateDocumentInfo +internal -> internal: Check ThingNameFlags +internal -> internal: Check AwsIotShadowDocumentInfo_t +return Return SUCCESS or BAD_PARAMETER +activate shadow + +shadow -> internal: Create operation\nInput:\nOperation,\nFlags,\nCallback +deactivate shadow +activate internal +note over internal: _AwsIotShadow_CreateOperation +internal -> internal: Calculate required memory for AwsIotShadowOperation_t +internal -> internal: Malloc AwsIotShadowOperation_t +internal -> internal: Setup AwsIotShadowOperation_t, save Callback +return Return AwsIotShadowOperation_t +activate shadow + +shadow -> internal: Process operation\nInput:\nIotMqttConnection_t\nThingName\nAwsIotShadowOperation_t\nAwsIotShadowDocumentInfo_t +deactivate shadow +activate internal +note over internal: _AwsIotShadow_ProcessOperation +internal -> internal: Generate topic for operation +alt Topic has subscription +internal -> internal: Retrieve subscription +else +internal -> internal: Create subscription for 'accepted' and 'rejected' topics +internal -> mqtt: Subscribe for 'accepted' and 'rejected' topics for Shadow +mqtt -> internal: Return SUCCESS or error +alt If error for subscription +internal -> shadow: Return error +shadow -> app: Return AWS_IOT_SHADOW_MQTT_ERROR +end alt +end alt +internal -> internal: Create MQTT PUBLISH command +internal -> internal: Add AwsIotShadowOperation_t to pending list +internal -> mqtt: Send MQTT PUBLISH +mqtt -> : Transmit MQTT packet using network stack +mqtt -> internal: Return SUCCESS or error +internal -> shadow: Return SUCCESS or error +deactivate internal +shadow -> app: Return AwsIotShadowError_t + + +== Wait for response == + +note over callback: _commonOperationCallback\nResponse received: Topic, Message +activate callback +callback -> callback: Parse Thing Name in Topic +callback -> callback: Match Thing Name with pending operation +callback -> callback: Parse ACCEPTED or REJECTED Status +callback -> app: Invoke app callback for completed operation\nInput:\nAwsIotShadowCallbackParam_t\nApp callback runs from task pool +callback -> callback: Destroy AwsIotShadowOperation_t +deactivate callback + +@enduml diff --git a/doc/plantuml/shadow_sync_opertation_detail.pu b/doc/plantuml/shadow_sync_opertation_detail.pu new file mode 100644 index 0000000000..3ace6f22b4 --- /dev/null +++ b/doc/plantuml/shadow_sync_opertation_detail.pu @@ -0,0 +1,78 @@ +@startuml +skinparam classFontSize 8 +skinparam classFontName Helvetica + +box "Application Task" #LightBlue +actor Application as app +participant "Shadow API" as shadow +participant "Internal Shadow functions" as internal +participant "MQTT API" as mqtt +end box + + +box "Task Pool" #LightGreen +participant "MQTT subscription callback" as callback +end box + +app -> shadow: Call Shadow Operation\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags +activate shadow + +shadow -> internal: Validate arguments\nInput:\nIotMqttConnection_t,\nThingName,\nAwsIotShadowDocumentInfo_t,\nFlags +deactivate shadow +activate internal +note over internal: _validateThingNameFlags\n_validateDocumentInfo +internal -> internal: Check ThingNameFlags +internal -> internal: Check AwsIotShadowDocumentInfo_t +return Return SUCCESS or BAD_PARAMETER +activate shadow + +shadow -> internal: Create operation\nInput:\nOperation,\nFlags +deactivate shadow +activate internal +note over internal: _AwsIotShadow_CreateOperation +internal -> internal: Calculate required memory for AwsIotShadowOperation_t +internal -> internal: Malloc AwsIotShadowOperation_t +internal -> internal: Setup AwsIotShadowOperation_t +return Return AwsIotShadowOperation_t +activate shadow + +shadow -> internal: Process operation\nInput:\nIotMqttConnection_t\nThingName\nAwsIotShadowOperation_t\nAwsIotShadowDocumentInfo_t +deactivate shadow +activate internal +note over internal: _AwsIotShadow_ProcessOperation +internal -> internal: Generate topic for operation +alt Topic has subscription +internal -> internal: Retrieve subscription +else +internal -> internal: Create subscription for 'accepted' and 'rejected' topics +internal -> mqtt: Subscribe for 'accepted' and 'rejected' topics for Shadow +mqtt -> internal: Return SUCCESS or error +alt If error for subscription +internal -> shadow: Return error +shadow -> app: Return AWS_IOT_SHADOW_MQTT_ERROR +end alt +end alt +internal -> internal: Create MQTT PUBLISH command +internal -> internal: Add AwsIotShadowOperation_t to pending list +internal -> mqtt: Send MQTT PUBLISH +mqtt -> : Transmit MQTT packet using network stack +mqtt -> internal: Return SUCCESS or error +internal -> shadow: Return SUCCESS or error +deactivate internal +activate shadow +shadow -> shadow: Wait on a semaphore for MQTT response + + +note over callback: _commonOperationCallback\nResponse received: Topic, Message +activate callback +callback -> callback: Parse Thing Name in Topic +callback -> callback: Match Thing Name with pending operation +callback -> callback: Parse ACCEPTED or REJECTED Status +callback -> shadow: Post semaphore to unblock the wait. +callback -> callback: Destroy AwsIotShadowOperation_t +deactivate callback +shadow -> app: Return AwsIotShadowError_t +deactivate shadow + + +@enduml \ No newline at end of file From e3c332517328a320be952cc560440b58148e171b Mon Sep 17 00:00:00 2001 From: Dan Griffin Date: Wed, 13 Nov 2019 20:14:36 -0800 Subject: [PATCH 318/844] Add support for MQTT username/password and IANA protocol string. --- README.md | 2 + demos/app/iot_demo.c | 40 ++++++- demos/app/iot_demo_arguments.c | 106 +++++++++++------- demos/include/iot_demo_arguments.h | 2 + demos/iot_config.h | 2 + demos/src/iot_demo_mqtt.c | 5 + libraries/platform/iot_network.h | 4 + .../mqtt/include/types/iot_mqtt_types.h | 3 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 3 +- ports/common/src/iot_network_mbedtls.c | 21 ++-- ports/common/src/iot_network_openssl.c | 18 ++- 11 files changed, 150 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index ab8a1d5969..ffbdb51c5f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. + - Set `IOT_DEMO_USER_NAME` to the username string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-m`. + - Set `IOT_DEMO_PASSWORD` to the password string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-w`. 4. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 27b7dd566b..7fb9d274df 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -118,19 +118,55 @@ int main( int argc, /* For a secured connection, set the members of the credentials. */ if( demoArguments.securedConnection == true ) { - /* Set credential paths. */ + /* Set credential information. */ credentials.pClientCert = demoArguments.pClientCertPath; credentials.pPrivateKey = demoArguments.pPrivateKeyPath; credentials.pRootCa = demoArguments.pRootCaPath; + + /* Set the MQTT username, as long as it's not empty or NULL. */ + if( demoArguments.pUserName != NULL ) + { + credentials.userNameSize = strlen( demoArguments.pUserName ); + if( credentials.userNameSize > 0 ) + { + credentials.pUserName = demoArguments.pUserName; + } + else + { + credentials.pUserName = NULL; + } + } + + /* Set the MQTT password, as long as it's not empty or NULL. */ + if( demoArguments.pPassword != NULL ) + { + credentials.passwordSize = strlen( demoArguments.pPassword ); + if( credentials.passwordSize > 0 ) + { + credentials.pPassword = demoArguments.pPassword; + } + else + { + credentials.pPassword = NULL; + } + } /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Disable ALPN if another port is + * which only works over port 443. Clear that value if another port is * used. */ if( demoArguments.port != 443 ) { credentials.pAlpnProtos = NULL; } + /* Per IANA standard: + * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml. */ + if( ( demoArguments.pUserName != NULL ) && + ( demoArguments.awsIotMqttMode == true ) ) + { + credentials.pAlpnProtos = "mqtt"; + } + /* Set the pointer to the credentials. */ pCredentials = &credentials; } diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index d97974a58e..e79e5f9967 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -87,6 +87,16 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) #ifdef IOT_DEMO_PRIVATE_KEY pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif + + /* Set default MQTT broker username if defined. */ + #ifdef IOT_DEMO_USER_NAME + pArguments->pUserName = IOT_DEMO_USER_NAME; + #endif + + /* Set default MQTT broker password if defined. */ + #ifdef IOT_DEMO_PASSWORD + pArguments->pUserName = IOT_DEMO_PASSWORD; + #endif } /*-----------------------------------------------------------*/ @@ -133,22 +143,30 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) IOT_SET_AND_GOTO_CLEANUP( false ); } - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + /* There must either be a set of X.509 credentials or a + username/password. First check for username/password.*/ + if( ( pArguments->pUserName == NULL ) || + ( strlen( pArguments->pUserName ) == 0 ) || + ( pArguments->pPassword == NULL ) || + ( strlen( pArguments->pPassword ) == 0 ) ) { - IotLogError( "Client certificate private key not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + IotLogError( "Client certificate private key not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } } } else @@ -203,25 +221,39 @@ bool IotDemo_ParseArguments( int argc, switch( pOption[ 1 ] ) { - /* Secured connection. */ - case 's': - pArguments->securedConnection = true; + /* Client certificate path. */ + case 'c': + i++; + pArguments->pClientCertPath = argv[ i ]; break; - /* Unsecured connection. */ - case 'u': - pArguments->securedConnection = false; + /* Server. */ + case 'h': + i++; + pArguments->pHostName = argv[ i ]; break; - /* MQTT server is not AWS. */ - case 'n': - pArguments->awsIotMqttMode = false; + /* Client identifier or Thing Name. */ + case 'i': + i++; + pArguments->pIdentifier = argv[ i ]; break; - /* Server. */ - case 'h': + /* Client certificate private key path. */ + case 'k': i++; - pArguments->pHostName = argv[ i ]; + pArguments->pPrivateKeyPath = argv[ i ]; + break; + + /* Username for MQTT. */ + case 'm': + i++; + pArguments->pUserName = argv[ i ]; + break; + + /* MQTT server is not AWS. */ + case 'n': + pArguments->awsIotMqttMode = false; break; /* Server port. */ @@ -249,22 +281,20 @@ bool IotDemo_ParseArguments( int argc, pArguments->pRootCaPath = argv[ i ]; break; - /* Client certificate path. */ - case 'c': - i++; - pArguments->pClientCertPath = argv[ i ]; + /* Secured connection. */ + case 's': + pArguments->securedConnection = true; break; - /* Client certificate private key path. */ - case 'k': - i++; - pArguments->pPrivateKeyPath = argv[ i ]; + /* Unsecured connection. */ + case 'u': + pArguments->securedConnection = false; break; - /* Client identifier or Thing Name. */ - case 'i': + /* Password for MQTT. */ + case 'w': i++; - pArguments->pIdentifier = argv[ i ]; + pArguments->pPassword = argv[ i ]; break; default: diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index 8f1921f392..c48b42b464 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -56,6 +56,8 @@ typedef struct IotDemoArguments const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ + const char * pUserName; /**< @brief Username for authenticating to the MQTT broker. */ + const char * pPassword; /**< @brief Password for authenticating to the MQTT broker. */ const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ } IotDemoArguments_t; diff --git a/demos/iot_config.h b/demos/iot_config.h index 21a05fff5a..b91dd8105b 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -34,6 +34,8 @@ #define IOT_DEMO_ROOT_CA "" /* Command line: -r */ #define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ #define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ +#define IOT_DEMO_USER_NAME "" /* Command line: -m */ +#define IOT_DEMO_PASSWORD "" /* Command line: -w */ /* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at * runtime with the command line option -i. Identifiers are optional for the diff --git a/demos/src/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c index 17a34e2674..c2299c7855 100644 --- a/demos/src/iot_demo_mqtt.c +++ b/demos/src/iot_demo_mqtt.c @@ -399,6 +399,7 @@ static int _establishMqttConnection( bool awsIotMqttMode, IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + IotNetworkCredentials_t pCredentials = ( IotNetworkCredentials_t )pNetworkCredentialInfo; /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ @@ -416,6 +417,10 @@ static int _establishMqttConnection( bool awsIotMqttMode, connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; connectInfo.pWillInfo = &willInfo; + connectInfo.pUserName = pCredentials->pUserName; + connectInfo.userNameLength = pCredentials->userNameSize; + connectInfo.pPassword = pCredentials->pPassword; + connectInfo.passwordLength = pCredentials->passwordSize; /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 651f0d0ac1..03303b3fa9 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -356,6 +356,10 @@ struct IotNetworkCredentials size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ + const char * pUserName; /**< @brief String representing the username for MQTT. */ + size_t userNameSize; /**< @brief Size associated with #IotNetworkCredentials.pUserName. */ + const char * pPassword; /**< @brief String representing the password for MQTT. */ + size_t passwordSize; /**< @brief Size associated with #IotNetworkCredentials.pPassword. */ }; #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 38fd4e8e54..d996c450e5 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -666,8 +666,7 @@ typedef struct IotMqttConnectInfo const char * pClientIdentifier; /**< @brief MQTT client identifier. */ uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - /* These credentials are not used by AWS IoT and may be ignored if - * awsIotMqttMode is true. */ + /* Use these fields if your MQTT broker requires username and password. */ const char * pUserName; /**< @brief Username for MQTT connection. */ uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index fc3001dd9e..cb69556d0e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -741,7 +741,8 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } /* Username and password depend on MQTT mode. */ - if( pConnectInfo->awsIotMqttMode == true ) + if( ( pConnectInfo->pUserName == NULL ) && + ( pConnectInfo->awsIotMqttMode == true ) ) { /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server * never uses a password. */ diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index e8371ac2f7..dbfd372fde 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -563,15 +563,22 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); - if( _readCredentials( pConnection, - pMbedtlsCredentials->pRootCa, - pMbedtlsCredentials->pClientCert, - pMbedtlsCredentials->pPrivateKey ) == false ) + /* Setup TLS client certificate authentication, if requested. */ + if( ( pMbedtlsCredentials->pClientCert != NULL ) && + ( strlen( pMbedtlsCredentials->pClientCert ) != 0 ) && + ( pMbedtlsCredentials->pPrivateKey != NULL ) && + ( strlen( pMbedtlsCredentials->pPrivateKey ) ) ) { - IotLogError( "(Network connection %p) Failed to read credentials.", - pConnection ); + if( _readCredentials( pConnection, + pMbedtlsCredentials->pRootCa, + pMbedtlsCredentials->pClientCert, + pMbedtlsCredentials->pPrivateKey ) == false ) + { + IotLogError( "(Network connection %p) Failed to read credentials.", + pConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } } /* Set up ALPN if requested. */ diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index fa1c27a821..03fd33de2a 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -448,13 +448,19 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); - /* Import all credentials. */ - if( _readCredentials( pSslContext, - pOpensslCredentials->pRootCa, - pOpensslCredentials->pClientCert, - pOpensslCredentials->pPrivateKey ) == false ) + /* Setup TLS client certificate authentication, if requested. */ + if( ( pOpensslCredentials->pClientCert != NULL ) && + ( strlen( pOpensslCredentials->pClientCert ) != 0 ) && + ( pOpensslCredentials->pPrivateKey != NULL ) && + ( strlen( pOpensslCredentials->pPrivateKey ) ) ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + if( _readCredentials( pSslContext, + pOpensslCredentials->pRootCa, + pOpensslCredentials->pClientCert, + pOpensslCredentials->pPrivateKey ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } } /* Create a new SSL connection context */ From e93eb91982aa57c29cfa1503ea5fd4916b82d342 Mon Sep 17 00:00:00 2001 From: Dan Griffin Date: Wed, 13 Nov 2019 20:17:06 -0800 Subject: [PATCH 319/844] Revert "Add support for MQTT username/password and IANA protocol string." This reverts commit e3c332517328a320be952cc560440b58148e171b. --- README.md | 2 - demos/app/iot_demo.c | 40 +------ demos/app/iot_demo_arguments.c | 106 +++++++----------- demos/include/iot_demo_arguments.h | 2 - demos/iot_config.h | 2 - demos/src/iot_demo_mqtt.c | 5 - libraries/platform/iot_network.h | 4 - .../mqtt/include/types/iot_mqtt_types.h | 3 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 3 +- ports/common/src/iot_network_mbedtls.c | 21 ++-- ports/common/src/iot_network_openssl.c | 18 +-- 11 files changed, 56 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index ffbdb51c5f..ab8a1d5969 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,6 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. - - Set `IOT_DEMO_USER_NAME` to the username string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-m`. - - Set `IOT_DEMO_PASSWORD` to the password string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-w`. 4. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 7fb9d274df..27b7dd566b 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -118,55 +118,19 @@ int main( int argc, /* For a secured connection, set the members of the credentials. */ if( demoArguments.securedConnection == true ) { - /* Set credential information. */ + /* Set credential paths. */ credentials.pClientCert = demoArguments.pClientCertPath; credentials.pPrivateKey = demoArguments.pPrivateKeyPath; credentials.pRootCa = demoArguments.pRootCaPath; - - /* Set the MQTT username, as long as it's not empty or NULL. */ - if( demoArguments.pUserName != NULL ) - { - credentials.userNameSize = strlen( demoArguments.pUserName ); - if( credentials.userNameSize > 0 ) - { - credentials.pUserName = demoArguments.pUserName; - } - else - { - credentials.pUserName = NULL; - } - } - - /* Set the MQTT password, as long as it's not empty or NULL. */ - if( demoArguments.pPassword != NULL ) - { - credentials.passwordSize = strlen( demoArguments.pPassword ); - if( credentials.passwordSize > 0 ) - { - credentials.pPassword = demoArguments.pPassword; - } - else - { - credentials.pPassword = NULL; - } - } /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Clear that value if another port is + * which only works over port 443. Disable ALPN if another port is * used. */ if( demoArguments.port != 443 ) { credentials.pAlpnProtos = NULL; } - /* Per IANA standard: - * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml. */ - if( ( demoArguments.pUserName != NULL ) && - ( demoArguments.awsIotMqttMode == true ) ) - { - credentials.pAlpnProtos = "mqtt"; - } - /* Set the pointer to the credentials. */ pCredentials = &credentials; } diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index e79e5f9967..d97974a58e 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -87,16 +87,6 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) #ifdef IOT_DEMO_PRIVATE_KEY pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif - - /* Set default MQTT broker username if defined. */ - #ifdef IOT_DEMO_USER_NAME - pArguments->pUserName = IOT_DEMO_USER_NAME; - #endif - - /* Set default MQTT broker password if defined. */ - #ifdef IOT_DEMO_PASSWORD - pArguments->pUserName = IOT_DEMO_PASSWORD; - #endif } /*-----------------------------------------------------------*/ @@ -143,30 +133,22 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) IOT_SET_AND_GOTO_CLEANUP( false ); } - /* There must either be a set of X.509 credentials or a - username/password. First check for username/password.*/ - if( ( pArguments->pUserName == NULL ) || - ( strlen( pArguments->pUserName ) == 0 ) || - ( pArguments->pPassword == NULL ) || - ( strlen( pArguments->pPassword ) == 0 ) ) + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) { - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Client certificate private key not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } + IotLogError( "Client certificate private key not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); } } else @@ -221,34 +203,14 @@ bool IotDemo_ParseArguments( int argc, switch( pOption[ 1 ] ) { - /* Client certificate path. */ - case 'c': - i++; - pArguments->pClientCertPath = argv[ i ]; - break; - - /* Server. */ - case 'h': - i++; - pArguments->pHostName = argv[ i ]; - break; - - /* Client identifier or Thing Name. */ - case 'i': - i++; - pArguments->pIdentifier = argv[ i ]; - break; - - /* Client certificate private key path. */ - case 'k': - i++; - pArguments->pPrivateKeyPath = argv[ i ]; + /* Secured connection. */ + case 's': + pArguments->securedConnection = true; break; - /* Username for MQTT. */ - case 'm': - i++; - pArguments->pUserName = argv[ i ]; + /* Unsecured connection. */ + case 'u': + pArguments->securedConnection = false; break; /* MQTT server is not AWS. */ @@ -256,6 +218,12 @@ bool IotDemo_ParseArguments( int argc, pArguments->awsIotMqttMode = false; break; + /* Server. */ + case 'h': + i++; + pArguments->pHostName = argv[ i ]; + break; + /* Server port. */ case 'p': i++; @@ -281,20 +249,22 @@ bool IotDemo_ParseArguments( int argc, pArguments->pRootCaPath = argv[ i ]; break; - /* Secured connection. */ - case 's': - pArguments->securedConnection = true; + /* Client certificate path. */ + case 'c': + i++; + pArguments->pClientCertPath = argv[ i ]; break; - /* Unsecured connection. */ - case 'u': - pArguments->securedConnection = false; + /* Client certificate private key path. */ + case 'k': + i++; + pArguments->pPrivateKeyPath = argv[ i ]; break; - /* Password for MQTT. */ - case 'w': + /* Client identifier or Thing Name. */ + case 'i': i++; - pArguments->pPassword = argv[ i ]; + pArguments->pIdentifier = argv[ i ]; break; default: diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index c48b42b464..8f1921f392 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -56,8 +56,6 @@ typedef struct IotDemoArguments const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ - const char * pUserName; /**< @brief Username for authenticating to the MQTT broker. */ - const char * pPassword; /**< @brief Password for authenticating to the MQTT broker. */ const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ } IotDemoArguments_t; diff --git a/demos/iot_config.h b/demos/iot_config.h index b91dd8105b..21a05fff5a 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -34,8 +34,6 @@ #define IOT_DEMO_ROOT_CA "" /* Command line: -r */ #define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ #define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ -#define IOT_DEMO_USER_NAME "" /* Command line: -m */ -#define IOT_DEMO_PASSWORD "" /* Command line: -w */ /* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at * runtime with the command line option -i. Identifiers are optional for the diff --git a/demos/src/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c index c2299c7855..17a34e2674 100644 --- a/demos/src/iot_demo_mqtt.c +++ b/demos/src/iot_demo_mqtt.c @@ -399,7 +399,6 @@ static int _establishMqttConnection( bool awsIotMqttMode, IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - IotNetworkCredentials_t pCredentials = ( IotNetworkCredentials_t )pNetworkCredentialInfo; /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ @@ -417,10 +416,6 @@ static int _establishMqttConnection( bool awsIotMqttMode, connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; connectInfo.pWillInfo = &willInfo; - connectInfo.pUserName = pCredentials->pUserName; - connectInfo.userNameLength = pCredentials->userNameSize; - connectInfo.pPassword = pCredentials->pPassword; - connectInfo.passwordLength = pCredentials->passwordSize; /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 03303b3fa9..651f0d0ac1 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -356,10 +356,6 @@ struct IotNetworkCredentials size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ - const char * pUserName; /**< @brief String representing the username for MQTT. */ - size_t userNameSize; /**< @brief Size associated with #IotNetworkCredentials.pUserName. */ - const char * pPassword; /**< @brief String representing the password for MQTT. */ - size_t passwordSize; /**< @brief Size associated with #IotNetworkCredentials.pPassword. */ }; #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index d996c450e5..38fd4e8e54 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -666,7 +666,8 @@ typedef struct IotMqttConnectInfo const char * pClientIdentifier; /**< @brief MQTT client identifier. */ uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - /* Use these fields if your MQTT broker requires username and password. */ + /* These credentials are not used by AWS IoT and may be ignored if + * awsIotMqttMode is true. */ const char * pUserName; /**< @brief Username for MQTT connection. */ uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index cb69556d0e..fc3001dd9e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -741,8 +741,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } /* Username and password depend on MQTT mode. */ - if( ( pConnectInfo->pUserName == NULL ) && - ( pConnectInfo->awsIotMqttMode == true ) ) + if( pConnectInfo->awsIotMqttMode == true ) { /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server * never uses a password. */ diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index dbfd372fde..e8371ac2f7 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -563,22 +563,15 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); - /* Setup TLS client certificate authentication, if requested. */ - if( ( pMbedtlsCredentials->pClientCert != NULL ) && - ( strlen( pMbedtlsCredentials->pClientCert ) != 0 ) && - ( pMbedtlsCredentials->pPrivateKey != NULL ) && - ( strlen( pMbedtlsCredentials->pPrivateKey ) ) ) + if( _readCredentials( pConnection, + pMbedtlsCredentials->pRootCa, + pMbedtlsCredentials->pClientCert, + pMbedtlsCredentials->pPrivateKey ) == false ) { - if( _readCredentials( pConnection, - pMbedtlsCredentials->pRootCa, - pMbedtlsCredentials->pClientCert, - pMbedtlsCredentials->pPrivateKey ) == false ) - { - IotLogError( "(Network connection %p) Failed to read credentials.", - pConnection ); + IotLogError( "(Network connection %p) Failed to read credentials.", + pConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Set up ALPN if requested. */ diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index 03fd33de2a..fa1c27a821 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -448,19 +448,13 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); - /* Setup TLS client certificate authentication, if requested. */ - if( ( pOpensslCredentials->pClientCert != NULL ) && - ( strlen( pOpensslCredentials->pClientCert ) != 0 ) && - ( pOpensslCredentials->pPrivateKey != NULL ) && - ( strlen( pOpensslCredentials->pPrivateKey ) ) ) + /* Import all credentials. */ + if( _readCredentials( pSslContext, + pOpensslCredentials->pRootCa, + pOpensslCredentials->pClientCert, + pOpensslCredentials->pPrivateKey ) == false ) { - if( _readCredentials( pSslContext, - pOpensslCredentials->pRootCa, - pOpensslCredentials->pClientCert, - pOpensslCredentials->pPrivateKey ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Create a new SSL connection context */ From 19af44300483454dc5afa958d9847c8b18f7da8f Mon Sep 17 00:00:00 2001 From: Dan Griffin Date: Wed, 13 Nov 2019 20:25:03 -0800 Subject: [PATCH 320/844] Add support for MQTT username/password and IANA protocol string. --- README.md | 2 + demos/app/iot_demo.c | 40 ++++++- demos/app/iot_demo_arguments.c | 106 +++++++++++------- demos/include/iot_demo_arguments.h | 2 + demos/iot_config.h | 2 + demos/src/iot_demo_mqtt.c | 5 + libraries/platform/iot_network.h | 4 + .../mqtt/include/types/iot_mqtt_types.h | 3 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 3 +- ports/common/src/iot_network_mbedtls.c | 21 ++-- ports/common/src/iot_network_openssl.c | 18 ++- 11 files changed, 150 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index ab8a1d5969..ffbdb51c5f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. + - Set `IOT_DEMO_USER_NAME` to the username string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-m`. + - Set `IOT_DEMO_PASSWORD` to the password string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-w`. 4. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 27b7dd566b..7fb9d274df 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -118,19 +118,55 @@ int main( int argc, /* For a secured connection, set the members of the credentials. */ if( demoArguments.securedConnection == true ) { - /* Set credential paths. */ + /* Set credential information. */ credentials.pClientCert = demoArguments.pClientCertPath; credentials.pPrivateKey = demoArguments.pPrivateKeyPath; credentials.pRootCa = demoArguments.pRootCaPath; + + /* Set the MQTT username, as long as it's not empty or NULL. */ + if( demoArguments.pUserName != NULL ) + { + credentials.userNameSize = strlen( demoArguments.pUserName ); + if( credentials.userNameSize > 0 ) + { + credentials.pUserName = demoArguments.pUserName; + } + else + { + credentials.pUserName = NULL; + } + } + + /* Set the MQTT password, as long as it's not empty or NULL. */ + if( demoArguments.pPassword != NULL ) + { + credentials.passwordSize = strlen( demoArguments.pPassword ); + if( credentials.passwordSize > 0 ) + { + credentials.pPassword = demoArguments.pPassword; + } + else + { + credentials.pPassword = NULL; + } + } /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Disable ALPN if another port is + * which only works over port 443. Clear that value if another port is * used. */ if( demoArguments.port != 443 ) { credentials.pAlpnProtos = NULL; } + /* Per IANA standard: + * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml. */ + if( ( demoArguments.pUserName != NULL ) && + ( demoArguments.awsIotMqttMode == true ) ) + { + credentials.pAlpnProtos = "mqtt"; + } + /* Set the pointer to the credentials. */ pCredentials = &credentials; } diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index d97974a58e..e79e5f9967 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -87,6 +87,16 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) #ifdef IOT_DEMO_PRIVATE_KEY pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif + + /* Set default MQTT broker username if defined. */ + #ifdef IOT_DEMO_USER_NAME + pArguments->pUserName = IOT_DEMO_USER_NAME; + #endif + + /* Set default MQTT broker password if defined. */ + #ifdef IOT_DEMO_PASSWORD + pArguments->pUserName = IOT_DEMO_PASSWORD; + #endif } /*-----------------------------------------------------------*/ @@ -133,22 +143,30 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) IOT_SET_AND_GOTO_CLEANUP( false ); } - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + /* There must either be a set of X.509 credentials or a + username/password. First check for username/password.*/ + if( ( pArguments->pUserName == NULL ) || + ( strlen( pArguments->pUserName ) == 0 ) || + ( pArguments->pPassword == NULL ) || + ( strlen( pArguments->pPassword ) == 0 ) ) { - IotLogError( "Client certificate private key not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) + { + IotLogError( "Client certificate private key not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } } } else @@ -203,25 +221,39 @@ bool IotDemo_ParseArguments( int argc, switch( pOption[ 1 ] ) { - /* Secured connection. */ - case 's': - pArguments->securedConnection = true; + /* Client certificate path. */ + case 'c': + i++; + pArguments->pClientCertPath = argv[ i ]; break; - /* Unsecured connection. */ - case 'u': - pArguments->securedConnection = false; + /* Server. */ + case 'h': + i++; + pArguments->pHostName = argv[ i ]; break; - /* MQTT server is not AWS. */ - case 'n': - pArguments->awsIotMqttMode = false; + /* Client identifier or Thing Name. */ + case 'i': + i++; + pArguments->pIdentifier = argv[ i ]; break; - /* Server. */ - case 'h': + /* Client certificate private key path. */ + case 'k': i++; - pArguments->pHostName = argv[ i ]; + pArguments->pPrivateKeyPath = argv[ i ]; + break; + + /* Username for MQTT. */ + case 'm': + i++; + pArguments->pUserName = argv[ i ]; + break; + + /* MQTT server is not AWS. */ + case 'n': + pArguments->awsIotMqttMode = false; break; /* Server port. */ @@ -249,22 +281,20 @@ bool IotDemo_ParseArguments( int argc, pArguments->pRootCaPath = argv[ i ]; break; - /* Client certificate path. */ - case 'c': - i++; - pArguments->pClientCertPath = argv[ i ]; + /* Secured connection. */ + case 's': + pArguments->securedConnection = true; break; - /* Client certificate private key path. */ - case 'k': - i++; - pArguments->pPrivateKeyPath = argv[ i ]; + /* Unsecured connection. */ + case 'u': + pArguments->securedConnection = false; break; - /* Client identifier or Thing Name. */ - case 'i': + /* Password for MQTT. */ + case 'w': i++; - pArguments->pIdentifier = argv[ i ]; + pArguments->pPassword = argv[ i ]; break; default: diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index 8f1921f392..c48b42b464 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -56,6 +56,8 @@ typedef struct IotDemoArguments const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ + const char * pUserName; /**< @brief Username for authenticating to the MQTT broker. */ + const char * pPassword; /**< @brief Password for authenticating to the MQTT broker. */ const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ } IotDemoArguments_t; diff --git a/demos/iot_config.h b/demos/iot_config.h index 21a05fff5a..b91dd8105b 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -34,6 +34,8 @@ #define IOT_DEMO_ROOT_CA "" /* Command line: -r */ #define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ #define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ +#define IOT_DEMO_USER_NAME "" /* Command line: -m */ +#define IOT_DEMO_PASSWORD "" /* Command line: -w */ /* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at * runtime with the command line option -i. Identifiers are optional for the diff --git a/demos/src/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c index 17a34e2674..c2299c7855 100644 --- a/demos/src/iot_demo_mqtt.c +++ b/demos/src/iot_demo_mqtt.c @@ -399,6 +399,7 @@ static int _establishMqttConnection( bool awsIotMqttMode, IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; + IotNetworkCredentials_t pCredentials = ( IotNetworkCredentials_t )pNetworkCredentialInfo; /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ @@ -416,6 +417,10 @@ static int _establishMqttConnection( bool awsIotMqttMode, connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; connectInfo.pWillInfo = &willInfo; + connectInfo.pUserName = pCredentials->pUserName; + connectInfo.userNameLength = pCredentials->userNameSize; + connectInfo.pPassword = pCredentials->pPassword; + connectInfo.passwordLength = pCredentials->passwordSize; /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 651f0d0ac1..03303b3fa9 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -356,6 +356,10 @@ struct IotNetworkCredentials size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ + const char * pUserName; /**< @brief String representing the username for MQTT. */ + size_t userNameSize; /**< @brief Size associated with #IotNetworkCredentials.pUserName. */ + const char * pPassword; /**< @brief String representing the password for MQTT. */ + size_t passwordSize; /**< @brief Size associated with #IotNetworkCredentials.pPassword. */ }; #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 38fd4e8e54..d996c450e5 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -666,8 +666,7 @@ typedef struct IotMqttConnectInfo const char * pClientIdentifier; /**< @brief MQTT client identifier. */ uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - /* These credentials are not used by AWS IoT and may be ignored if - * awsIotMqttMode is true. */ + /* Use these fields if your MQTT broker requires username and password. */ const char * pUserName; /**< @brief Username for MQTT connection. */ uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index fc3001dd9e..cb69556d0e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -741,7 +741,8 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } /* Username and password depend on MQTT mode. */ - if( pConnectInfo->awsIotMqttMode == true ) + if( ( pConnectInfo->pUserName == NULL ) && + ( pConnectInfo->awsIotMqttMode == true ) ) { /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server * never uses a password. */ diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index e8371ac2f7..dbfd372fde 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -563,15 +563,22 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); - if( _readCredentials( pConnection, - pMbedtlsCredentials->pRootCa, - pMbedtlsCredentials->pClientCert, - pMbedtlsCredentials->pPrivateKey ) == false ) + /* Setup TLS client certificate authentication, if requested. */ + if( ( pMbedtlsCredentials->pClientCert != NULL ) && + ( strlen( pMbedtlsCredentials->pClientCert ) != 0 ) && + ( pMbedtlsCredentials->pPrivateKey != NULL ) && + ( strlen( pMbedtlsCredentials->pPrivateKey ) ) ) { - IotLogError( "(Network connection %p) Failed to read credentials.", - pConnection ); + if( _readCredentials( pConnection, + pMbedtlsCredentials->pRootCa, + pMbedtlsCredentials->pClientCert, + pMbedtlsCredentials->pPrivateKey ) == false ) + { + IotLogError( "(Network connection %p) Failed to read credentials.", + pConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } } /* Set up ALPN if requested. */ diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index fa1c27a821..03fd33de2a 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -448,13 +448,19 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); - /* Import all credentials. */ - if( _readCredentials( pSslContext, - pOpensslCredentials->pRootCa, - pOpensslCredentials->pClientCert, - pOpensslCredentials->pPrivateKey ) == false ) + /* Setup TLS client certificate authentication, if requested. */ + if( ( pOpensslCredentials->pClientCert != NULL ) && + ( strlen( pOpensslCredentials->pClientCert ) != 0 ) && + ( pOpensslCredentials->pPrivateKey != NULL ) && + ( strlen( pOpensslCredentials->pPrivateKey ) ) ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + if( _readCredentials( pSslContext, + pOpensslCredentials->pRootCa, + pOpensslCredentials->pClientCert, + pOpensslCredentials->pPrivateKey ) == false ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); + } } /* Create a new SSL connection context */ From 5e9dc846adfeac5f99d97b9c2c1e45d1dec3f825 Mon Sep 17 00:00:00 2001 From: Dan Griffin Date: Wed, 13 Nov 2019 20:28:27 -0800 Subject: [PATCH 321/844] Revert "Add support for MQTT username/password and IANA protocol string." This reverts commit 19af44300483454dc5afa958d9847c8b18f7da8f. --- README.md | 2 - demos/app/iot_demo.c | 40 +------ demos/app/iot_demo_arguments.c | 106 +++++++----------- demos/include/iot_demo_arguments.h | 2 - demos/iot_config.h | 2 - demos/src/iot_demo_mqtt.c | 5 - libraries/platform/iot_network.h | 4 - .../mqtt/include/types/iot_mqtt_types.h | 3 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 3 +- ports/common/src/iot_network_mbedtls.c | 21 ++-- ports/common/src/iot_network_openssl.c | 18 +-- 11 files changed, 56 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index ffbdb51c5f..ab8a1d5969 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,6 @@ This SDK builds with [CMake](https://cmake.org/), a cross-platform build tool. T - Set `IOT_DEMO_ROOT_CA` to the path of the root CA certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-r`. - Set `IOT_DEMO_CLIENT_CERT` to the path of the client certificate downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-c`. - Set `IOT_DEMO_PRIVATE_KEY` to the path of the private downloaded in [step 1.3](https://docs.aws.amazon.com/iot/latest/developerguide/create-device-certificate.html). The corresponding command line option for this constant is `-k`. - - Set `IOT_DEMO_USER_NAME` to the username string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-m`. - - Set `IOT_DEMO_PASSWORD` to the password string, if any, required to authenticate to your MQTT broker. The corresponding command line option for this constant is `-w`. 4. Make a build directory in the SDK's root directory and `cd` into it. ```sh mkdir build diff --git a/demos/app/iot_demo.c b/demos/app/iot_demo.c index 7fb9d274df..27b7dd566b 100644 --- a/demos/app/iot_demo.c +++ b/demos/app/iot_demo.c @@ -118,55 +118,19 @@ int main( int argc, /* For a secured connection, set the members of the credentials. */ if( demoArguments.securedConnection == true ) { - /* Set credential information. */ + /* Set credential paths. */ credentials.pClientCert = demoArguments.pClientCertPath; credentials.pPrivateKey = demoArguments.pPrivateKeyPath; credentials.pRootCa = demoArguments.pRootCaPath; - - /* Set the MQTT username, as long as it's not empty or NULL. */ - if( demoArguments.pUserName != NULL ) - { - credentials.userNameSize = strlen( demoArguments.pUserName ); - if( credentials.userNameSize > 0 ) - { - credentials.pUserName = demoArguments.pUserName; - } - else - { - credentials.pUserName = NULL; - } - } - - /* Set the MQTT password, as long as it's not empty or NULL. */ - if( demoArguments.pPassword != NULL ) - { - credentials.passwordSize = strlen( demoArguments.pPassword ); - if( credentials.passwordSize > 0 ) - { - credentials.pPassword = demoArguments.pPassword; - } - else - { - credentials.pPassword = NULL; - } - } /* By default, the credential initializer enables ALPN with AWS IoT, - * which only works over port 443. Clear that value if another port is + * which only works over port 443. Disable ALPN if another port is * used. */ if( demoArguments.port != 443 ) { credentials.pAlpnProtos = NULL; } - /* Per IANA standard: - * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml. */ - if( ( demoArguments.pUserName != NULL ) && - ( demoArguments.awsIotMqttMode == true ) ) - { - credentials.pAlpnProtos = "mqtt"; - } - /* Set the pointer to the credentials. */ pCredentials = &credentials; } diff --git a/demos/app/iot_demo_arguments.c b/demos/app/iot_demo_arguments.c index e79e5f9967..d97974a58e 100644 --- a/demos/app/iot_demo_arguments.c +++ b/demos/app/iot_demo_arguments.c @@ -87,16 +87,6 @@ static void _setDefaultArguments( IotDemoArguments_t * pArguments ) #ifdef IOT_DEMO_PRIVATE_KEY pArguments->pPrivateKeyPath = IOT_DEMO_PRIVATE_KEY; #endif - - /* Set default MQTT broker username if defined. */ - #ifdef IOT_DEMO_USER_NAME - pArguments->pUserName = IOT_DEMO_USER_NAME; - #endif - - /* Set default MQTT broker password if defined. */ - #ifdef IOT_DEMO_PASSWORD - pArguments->pUserName = IOT_DEMO_PASSWORD; - #endif } /*-----------------------------------------------------------*/ @@ -143,30 +133,22 @@ static bool _validateArguments( const IotDemoArguments_t * pArguments ) IOT_SET_AND_GOTO_CLEANUP( false ); } - /* There must either be a set of X.509 credentials or a - username/password. First check for username/password.*/ - if( ( pArguments->pUserName == NULL ) || - ( strlen( pArguments->pUserName ) == 0 ) || - ( pArguments->pPassword == NULL ) || - ( strlen( pArguments->pPassword ) == 0 ) ) + /* Check that a client certificate path was set. */ + if( ( pArguments->pClientCertPath == NULL ) || + ( strlen( pArguments->pClientCertPath ) == 0 ) ) + { + IotLogError( "Client certificate path not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); + } + + /* Check that a client certificate private key was set. */ + if( ( pArguments->pPrivateKeyPath == NULL ) || + ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) { - /* Check that a client certificate path was set. */ - if( ( pArguments->pClientCertPath == NULL ) || - ( strlen( pArguments->pClientCertPath ) == 0 ) ) - { - IotLogError( "Client certificate path not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } - - /* Check that a client certificate private key was set. */ - if( ( pArguments->pPrivateKeyPath == NULL ) || - ( strlen( pArguments->pPrivateKeyPath ) == 0 ) ) - { - IotLogError( "Client certificate private key not set. Exiting." ); - - IOT_SET_AND_GOTO_CLEANUP( false ); - } + IotLogError( "Client certificate private key not set. Exiting." ); + + IOT_SET_AND_GOTO_CLEANUP( false ); } } else @@ -221,34 +203,14 @@ bool IotDemo_ParseArguments( int argc, switch( pOption[ 1 ] ) { - /* Client certificate path. */ - case 'c': - i++; - pArguments->pClientCertPath = argv[ i ]; - break; - - /* Server. */ - case 'h': - i++; - pArguments->pHostName = argv[ i ]; - break; - - /* Client identifier or Thing Name. */ - case 'i': - i++; - pArguments->pIdentifier = argv[ i ]; - break; - - /* Client certificate private key path. */ - case 'k': - i++; - pArguments->pPrivateKeyPath = argv[ i ]; + /* Secured connection. */ + case 's': + pArguments->securedConnection = true; break; - /* Username for MQTT. */ - case 'm': - i++; - pArguments->pUserName = argv[ i ]; + /* Unsecured connection. */ + case 'u': + pArguments->securedConnection = false; break; /* MQTT server is not AWS. */ @@ -256,6 +218,12 @@ bool IotDemo_ParseArguments( int argc, pArguments->awsIotMqttMode = false; break; + /* Server. */ + case 'h': + i++; + pArguments->pHostName = argv[ i ]; + break; + /* Server port. */ case 'p': i++; @@ -281,20 +249,22 @@ bool IotDemo_ParseArguments( int argc, pArguments->pRootCaPath = argv[ i ]; break; - /* Secured connection. */ - case 's': - pArguments->securedConnection = true; + /* Client certificate path. */ + case 'c': + i++; + pArguments->pClientCertPath = argv[ i ]; break; - /* Unsecured connection. */ - case 'u': - pArguments->securedConnection = false; + /* Client certificate private key path. */ + case 'k': + i++; + pArguments->pPrivateKeyPath = argv[ i ]; break; - /* Password for MQTT. */ - case 'w': + /* Client identifier or Thing Name. */ + case 'i': i++; - pArguments->pPassword = argv[ i ]; + pArguments->pIdentifier = argv[ i ]; break; default: diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index c48b42b464..8f1921f392 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -56,8 +56,6 @@ typedef struct IotDemoArguments const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ - const char * pUserName; /**< @brief Username for authenticating to the MQTT broker. */ - const char * pPassword; /**< @brief Password for authenticating to the MQTT broker. */ const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ } IotDemoArguments_t; diff --git a/demos/iot_config.h b/demos/iot_config.h index b91dd8105b..21a05fff5a 100644 --- a/demos/iot_config.h +++ b/demos/iot_config.h @@ -34,8 +34,6 @@ #define IOT_DEMO_ROOT_CA "" /* Command line: -r */ #define IOT_DEMO_CLIENT_CERT "" /* Command line: -c */ #define IOT_DEMO_PRIVATE_KEY "" /* Command line: -k */ -#define IOT_DEMO_USER_NAME "" /* Command line: -m */ -#define IOT_DEMO_PASSWORD "" /* Command line: -w */ /* MQTT client identifier (MQTT demo only) or AWS IoT Thing Name. May be set at * runtime with the command line option -i. Identifiers are optional for the diff --git a/demos/src/iot_demo_mqtt.c b/demos/src/iot_demo_mqtt.c index c2299c7855..17a34e2674 100644 --- a/demos/src/iot_demo_mqtt.c +++ b/demos/src/iot_demo_mqtt.c @@ -399,7 +399,6 @@ static int _establishMqttConnection( bool awsIotMqttMode, IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; char pClientIdentifierBuffer[ CLIENT_IDENTIFIER_MAX_LENGTH ] = { 0 }; - IotNetworkCredentials_t pCredentials = ( IotNetworkCredentials_t )pNetworkCredentialInfo; /* Set the members of the network info not set by the initializer. This * struct provided information on the transport layer to the MQTT connection. */ @@ -417,10 +416,6 @@ static int _establishMqttConnection( bool awsIotMqttMode, connectInfo.cleanSession = true; connectInfo.keepAliveSeconds = KEEP_ALIVE_SECONDS; connectInfo.pWillInfo = &willInfo; - connectInfo.pUserName = pCredentials->pUserName; - connectInfo.userNameLength = pCredentials->userNameSize; - connectInfo.pPassword = pCredentials->pPassword; - connectInfo.passwordLength = pCredentials->passwordSize; /* Set the members of the Last Will and Testament (LWT) message info. The * MQTT server will publish the LWT message if this client disconnects diff --git a/libraries/platform/iot_network.h b/libraries/platform/iot_network.h index 03303b3fa9..651f0d0ac1 100644 --- a/libraries/platform/iot_network.h +++ b/libraries/platform/iot_network.h @@ -356,10 +356,6 @@ struct IotNetworkCredentials size_t clientCertSize; /**< @brief Size associated with #IotNetworkCredentials.pClientCert. */ const char * pPrivateKey; /**< @brief String representing the client certificate's private key. */ size_t privateKeySize; /**< @brief Size associated with #IotNetworkCredentials.pPrivateKey. */ - const char * pUserName; /**< @brief String representing the username for MQTT. */ - size_t userNameSize; /**< @brief Size associated with #IotNetworkCredentials.pUserName. */ - const char * pPassword; /**< @brief String representing the password for MQTT. */ - size_t passwordSize; /**< @brief Size associated with #IotNetworkCredentials.pPassword. */ }; #endif /* ifndef IOT_NETWORK_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index d996c450e5..38fd4e8e54 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -666,7 +666,8 @@ typedef struct IotMqttConnectInfo const char * pClientIdentifier; /**< @brief MQTT client identifier. */ uint16_t clientIdentifierLength; /**< @brief Length of #IotMqttConnectInfo_t.pClientIdentifier. */ - /* Use these fields if your MQTT broker requires username and password. */ + /* These credentials are not used by AWS IoT and may be ignored if + * awsIotMqttMode is true. */ const char * pUserName; /**< @brief Username for MQTT connection. */ uint16_t userNameLength; /**< @brief Length of #IotMqttConnectInfo_t.pUserName. */ const char * pPassword; /**< @brief Password for MQTT connection. */ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index cb69556d0e..fc3001dd9e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -741,8 +741,7 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI } /* Username and password depend on MQTT mode. */ - if( ( pConnectInfo->pUserName == NULL ) && - ( pConnectInfo->awsIotMqttMode == true ) ) + if( pConnectInfo->awsIotMqttMode == true ) { /* Set the username flag for AWS IoT metrics. The AWS IoT MQTT server * never uses a password. */ diff --git a/ports/common/src/iot_network_mbedtls.c b/ports/common/src/iot_network_mbedtls.c index dbfd372fde..e8371ac2f7 100644 --- a/ports/common/src/iot_network_mbedtls.c +++ b/ports/common/src/iot_network_mbedtls.c @@ -563,22 +563,15 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, mbedtls_ssl_conf_authmode( &( pConnection->ssl.config ), MBEDTLS_SSL_VERIFY_REQUIRED ); mbedtls_ssl_conf_rng( &( pConnection->ssl.config ), mbedtls_ctr_drbg_random, &_ctrDrbgContext ); - /* Setup TLS client certificate authentication, if requested. */ - if( ( pMbedtlsCredentials->pClientCert != NULL ) && - ( strlen( pMbedtlsCredentials->pClientCert ) != 0 ) && - ( pMbedtlsCredentials->pPrivateKey != NULL ) && - ( strlen( pMbedtlsCredentials->pPrivateKey ) ) ) + if( _readCredentials( pConnection, + pMbedtlsCredentials->pRootCa, + pMbedtlsCredentials->pClientCert, + pMbedtlsCredentials->pPrivateKey ) == false ) { - if( _readCredentials( pConnection, - pMbedtlsCredentials->pRootCa, - pMbedtlsCredentials->pClientCert, - pMbedtlsCredentials->pPrivateKey ) == false ) - { - IotLogError( "(Network connection %p) Failed to read credentials.", - pConnection ); + IotLogError( "(Network connection %p) Failed to read credentials.", + pConnection ); - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Set up ALPN if requested. */ diff --git a/ports/common/src/iot_network_openssl.c b/ports/common/src/iot_network_openssl.c index 03fd33de2a..fa1c27a821 100644 --- a/ports/common/src/iot_network_openssl.c +++ b/ports/common/src/iot_network_openssl.c @@ -448,19 +448,13 @@ static IotNetworkError_t _tlsSetup( _networkConnection_t * pConnection, IotLogDebug( "New SSL context created. Setting SSL_MODE_AUTO_RETRY." ); ( void ) SSL_CTX_set_mode( pSslContext, SSL_MODE_AUTO_RETRY ); - /* Setup TLS client certificate authentication, if requested. */ - if( ( pOpensslCredentials->pClientCert != NULL ) && - ( strlen( pOpensslCredentials->pClientCert ) != 0 ) && - ( pOpensslCredentials->pPrivateKey != NULL ) && - ( strlen( pOpensslCredentials->pPrivateKey ) ) ) + /* Import all credentials. */ + if( _readCredentials( pSslContext, + pOpensslCredentials->pRootCa, + pOpensslCredentials->pClientCert, + pOpensslCredentials->pPrivateKey ) == false ) { - if( _readCredentials( pSslContext, - pOpensslCredentials->pRootCa, - pOpensslCredentials->pClientCert, - pOpensslCredentials->pPrivateKey ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); - } + IOT_SET_AND_GOTO_CLEANUP( IOT_NETWORK_FAILURE ); } /* Create a new SSL connection context */ From 8e69f8db5fd224dace31231c4ed311a0154aaa06 Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Wed, 13 Nov 2019 21:51:35 -0800 Subject: [PATCH 322/844] feature: Lightweight serializer API for MQTT. (#626) * feature: Lightweight serializer API for MQTT. MQTT serializer API changes to support lightweight single threaded app. CSDK MQTT Library provides stateful MQTT API that makes use of taskpool and network abstraction. The library makes use dynamic memory allocations and makes use of multiple threads or tasks. Embedded applications running on microcontroller may want to use single threaded model to run MQTT. The API changes enable application developers to use MQTT serializer and deserializer APIs without use of taskpool and network abstraction. The API does no allocate any dynamic memory, therefore application can make use of statically allocated memory. Application can implement its own state machine and make use of any network interface like sockets to handle MQTT messages. --- libraries/standard/mqtt/CMakeLists.txt | 1 + .../mqtt/include/iot_mqtt_serialize.h | 702 +++++ .../mqtt/include/types/iot_mqtt_types.h | 33 + .../standard/mqtt/src/iot_mqtt_network.c | 41 +- .../standard/mqtt/src/iot_mqtt_serialize.c | 2252 +++++++++++------ .../mqtt/src/private/iot_mqtt_internal.h | 20 +- 6 files changed, 2255 insertions(+), 794 deletions(-) create mode 100644 libraries/standard/mqtt/include/iot_mqtt_serialize.h diff --git a/libraries/standard/mqtt/CMakeLists.txt b/libraries/standard/mqtt/CMakeLists.txt index f4bd1e0329..8ef20a7f43 100644 --- a/libraries/standard/mqtt/CMakeLists.txt +++ b/libraries/standard/mqtt/CMakeLists.txt @@ -13,6 +13,7 @@ add_library( iotmqtt ${CONFIG_HEADER_PATH}/iot_config.h ${MQTT_SOURCES} include/iot_mqtt.h + include/iot_mqtt_serialize.h include/types/iot_mqtt_types.h src/private/iot_mqtt_internal.h ) diff --git a/libraries/standard/mqtt/include/iot_mqtt_serialize.h b/libraries/standard/mqtt/include/iot_mqtt_serialize.h new file mode 100644 index 0000000000..29e7d7a97f --- /dev/null +++ b/libraries/standard/mqtt/include/iot_mqtt_serialize.h @@ -0,0 +1,702 @@ +/* + * IoT MQTT V2.1.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file iot_mqtt_serialize.h + * @brief User-facing functions for serializing MQTT 3.1.1 packets. This header should + * be included for building a single threaded light-weight MQTT client bypassing + * stateful CSDK MQTT library. + */ + +#ifndef _IOT_MQTT_SERIALIZE_H_ +#define _IOT_MQTT_SERIALIZE_H_ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* MQTT types include. */ +#include "types/iot_mqtt_types.h" + +/*------------------------- MQTT library functions --------------------------*/ + +/** + * @functionspage{mqtt,MQTT library} + * - @functionname{mqtt_function_getconnectpacketsize} + * - @functionname{mqtt_function_serializeconnect} + * - @functionname{mqtt_function_getsubscriptionpacketsize} + * - @functionname{mqtt_function_serializesubscribe} + * - @functionname{mqtt_function_serializeunsubscribe} + * - @functionname{mqtt_function_getpublishpacketsize} + * - @functionname{mqtt_function_serializepublish} + * - @functionname{mqtt_function_serializedisconnect} + * - @functionname{mqtt_function_serializepingreq} + * - @functionname{mqtt_function_getincomingmqttpackettypeandlength} + * - @functionname{mqtt_function_deserializeresponse} + * - @functionname{mqtt_function_deserializepublish} + */ + +/** + * @functionpage{IotMqtt_GetConnectPacketSize,mqtt,getconnectpacketsize} + * @functionpage{IotMqtt_SerializeConnect,mqtt,serializeconnect} + * @functionpage{IotMqtt_GetSubscriptionPacketSize,mqtt,getsubscriptionpacketsize} + * @functionpage{IotMqtt_SerializeSubscribe,mqtt,serializesubscribe} + * @functionpage{IotMqtt_SerializeUnsubscribe,mqtt,serializeunsubscribe} + * @functionpage{IotMqtt_GetPublishPacketSize,mqtt,getpublishpacketsize} + * @functionpage{IotMqtt_SerializePublish,mqtt,serializepublish} + * @functionpage{IotMqtt_SerializeDisconnect,mqtt,serializedisconnect} + * @functionpage{IotMqtt_SerializePingreq,mqtt,serializepingreq} + * @functionpage{IotMqtt_GetIncomingMQTTPacketTypeAndLength,mqtt,getincomingmqttpackettypeandlength} + * @functionpage{IotMqtt_DeserializeResponse,mqtt,deserializeresponse} + * @functionpage{IotMqtt_DeserializePublish,mqtt,deserializepublish} + */ + +/** + * @brief Calculate the size and "Remaining length" of a CONNECT packet generated + * from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return IOT_MQTT_SUCCESS if the packet is within the length allowed by MQTT 3.1.1 spec; + * IOT_MQTT_BAD_PARAMETER otherwise. If this function returns `IOT_MQTT_BAD_PARAMETER`, + * the output parameters should be ignored. + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how IotMqtt_GetConnectPacketSize() should be used to calculate + * // the size of connect request. + * + * IotMqttConnectInfo_t xConnectInfo; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * IotMqttError_t xResult; + * + * // start with everything set to zero + * memset( ( void * ) &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); + * + * // Initialize connection info, details are out of scope for this example. + * _initializeConnectInfo( &xConnectInfo ); + * // Get size requirement for the connect packet + * xResult = IotMqtt_GetConnectPacketSize( &xConnectInfo, &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * + * // Application should allocate buffer with size == xPacketSize or use static buffer + * // with size >= xPacketSize to serialize connect request. + * @endcode + */ +/* @[declare_mqtt_getconnectpacketsize] */ +IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); +/* @[declare_mqtt_getconnectpacketsize] */ + +/** + * @brief Generate a CONNECT packet from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[in] remainingLength remaining length of the packet to be serialized. + * @param[in, out] pBuffer User provided buffer where the CONNECT packet is written. + * @param[in] bufferSize Size of the buffer pointed to by pBuffer. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + * + * @note pBuffer must be allocated by caller. Use @ref mqtt_function_getconnectpacketsize + * to determine the required size. + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how IotMqtt_SerializeConnect() should be used to serialize + * // MQTT connect packet and send it to MQTT broker. + * // Example uses static memory but dynamically allocated memory can be used as well. + * // Get size requirement for the connect packet. + * + * #define mqttexampleSHARED_BUFFER_SIZE 100 + * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; + * void sendConnectPacket( int xMQTTSocket ) + * { + * IotMqttConnectInfo_t xConnectInfo; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * IotMqttError_t xResult; + * size_t xSentBytes = 0; + * // Get size requirement for MQTT connect packet. + * xResult = IotMqtt_GetConnectPacketSize( &xConnectInfo, &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // Make sure the packet size is less than static buffer size + * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); + * // Serialize MQTT connect packet into provided buffer + * xResult = IotMqtt_SerializeConnect( &xConnectInfo, xRemainingLength, ucSharedBuffer, xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); + * IotMqtt_Assert( xSentBytes == xPacketSize ); + * } + * @endcode + */ +/* @[declare_mqtt_serializeconnect] */ +IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pBuffer, + size_t bufferSize ); +/* @[declare_mqtt_serializeconnect] */ + +/** + * @brief Calculate the size and "Remaining length" of a SUBSCRIBE or UNSUBSCRIBE + * packet generated from the given parameters. + * + * @param[in] type Either IOT_MQTT_SUBSCRIBE or IOT_MQTT_UNSUBSCRIBE. + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return IOT_MQTT_SUCCESS if the packet is within the length allowed by MQTT 3.1.1 spec; + * IOT_MQTT_BAD_PARAMETER otherwise. If this function returns IOT_MQTT_BAD_PARAMETER, + * the output parameters should be ignored. + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how IotMqtt_GetSubscriptionPacketSize() should be used to calculate + * // the size of subscribe or unsubscribe request. + * + * IotMqttError_t xResult; + * IotMqttSubscription_t xMQTTSubscription[ 1 ]; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * + * // Initialize Subscribe parameters. Details are out of scope for this example. + * // It will involve setting QOS, topic filter and topic filter length. + * _initializeSubscribe( xMQTTSubscription ); + * + * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + * xMQTTSubscription, + * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), + * &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * + * // Application should allocate buffer with size == xPacketSize or use static buffer + * // with size >= xPacketSize to serialize connect request. + * @endcode + */ +/* @[declare_mqtt_getsubscriptionpacketsize] */ +IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ); +/* @[declare_mqtt_getsubscriptionpacketsize] */ + +/** + * @brief Generate a SUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[in] remainingLength remaining length of the packet to be serialized. + * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. + * @param[in, out] pBuffer User provide buffer where the SUBSCRIBE packet is written. + * @param[in] bufferSize Size of the buffer pointed to by pBuffer. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + * + * @note pBuffer must be allocated by caller. + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * Example + * @code{c} + * // Example code below shows how IotMqtt_SerializeSubscribe() should be used to serialize + * // MQTT Subscribe packet and send it to MQTT broker. + * // Example uses static memory, but dynamically allocated memory can be used as well. + * // Get size requirement for the MQTT subscribe packet. + * + * #define mqttexampleSHARED_BUFFER_SIZE 100 + * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; + * void sendSubscribePacket( int xMQTTSocket ) + * { + * IotMqttSubscription_t xMQTTSubscription[ 1 ]; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * IotMqttError_t xResult; + * size_t xSentBytes = 0; + * + * // Initialize Subscribe parameters. Details are out of scope for this example. + * // It will involve setting QOS, topic filter and topic filter length. + * _initializeSubscribe( xMQTTSubscription ); + * // Get size requirement for MQTT Subscribe packet. + * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + * xMQTTSubscription, + * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), + * &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // Make sure the packet size is less than static buffer size. + * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); + * + * // Serialize subscribe into statically allocated ucSharedBuffer. + * xResult = IotMqtt_SerializeSubscribe( xMQTTSubscription, + * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), + * xRemainingLength, + * &usPacketIdentifier, + * ucSharedBuffer, + * xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); + * IotMqtt_Assert( xSentBytes == xPacketSize ); + * } + * @endcode + */ +/* @[declare_mqtt_serializesubscribe] */ +IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t bufferSize ); +/* @[declare_mqtt_serializesubscribe] */ + +/** + * @brief Generate a UNSUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions to remove. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[in] remainingLength remaining length of the packet to be serialized. + * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. + * @param[in, out] pBuffer User provide buffer where the UNSUBSCRIBE packet is written. + * @param[in] bufferSize Size of the buffer pointed to by pBuffer. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY. + * + * @note pBuffer must be allocated by caller. + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how IotMqtt_SerializeUnsubscribe() should be used to serialize + * // MQTT unsubscribe packet and send it to MQTT broker. + * // Example uses static memory, but dynamically allocated memory can be used as well. + * // Get size requirement for the Unsubscribe packet. + * + * #define mqttexampleSHARED_BUFFER_SIZE 100 + * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; + * void sendUnsubscribePacket( int xMQTTSocket ) + * { + * // Following example shows one topic example. + * IotMqttSubscription_t xMQTTSubscription[ 1 ]; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * IotMqttError_t xResult; + * size_t xSentBytes = 0; + * // Get size requirement for MQTT unsubscribe packet. + * xResult = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, + * xMQTTSubscription, + * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), + * &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // Make sure the packet size is less than static buffer size. + * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); + * // Serialize subscribe into staticallu allocated ucSharedBuffer. + * xResult = IotMqtt_SerializeUnsubscribe( xMQTTSubscription, + * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), + * xRemainingLength, + * &usPacketIdentifier, + * ucSharedBuffer, + * xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); + * IotMqtt_Assert( xSentBytes == xPacketSize ); + * } + * @endcode + */ +/* @[declare_mqtt_serializeunsubscribe] */ +IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t bufferSize ); +/* @[declare_mqtt_serializeunsubscribe] */ + +/** + * @brief Calculate the size and "Remaining length" of a PUBLISH packet generated + * from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information struct. + * @param[out] pRemainingLength Output for calculated "Remaining length" field. + * @param[out] pPacketSize Output for calculated total packet size. + * + * @return IOT_MQTT_SUCCESS if the packet is within the length allowed by MQTT 3.1.1 spec; + * IOT_MQTT_BAD_PARAMETER otherwise. If this function returns IOT_MQTT_BAD_PARAMETER, + * the output parameters should be ignored. + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how IotMqtt_GetPublishPacketSize() should be used to calculate + * // the size of MQTT publish request. + * + * IotMqttError_t xResult; + * IotMqttPublishInfo_t xMQTTPublishInfo; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * + * // Initialize Publish parameters. Details are out of scope for this example. + * // It will involve setting QOS, topic filter, topic filter length, payload + * // payload length + * _initializePublish( &xMQTTPublishInfo ); + * + * // Find out length of Publish packet size. + * xResult = IotMqtt_GetPublishPacketSize( &xMQTTPublishInfo, &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * + * // Application should allocate buffer with size == xPacketSize or use static buffer + * // with size >= xPacketSize to serialize connect request. + * @endcode + */ +/* @[declare_mqtt_getpublishpacketsize] */ +IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ); +/* @[declare_mqtt_getpublishpacketsize] */ + +/** + * @brief Generate a PUBLISH packet from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information. + * @param[in] remainingLength remaining length of the packet to be serialized. + * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier + * is written. + * @param[in, out] pBuffer User provide buffer where the PUBLISH packet is written. + * @param[in] bufferSize Size of the buffer pointed to by pBuffer. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER. + * + * @note pBuffer must be allocated by caller. + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how IotMqtt_SerializePublish() should be used to serialize + * // MQTT Publish packet and send it to broker. + * // Example uses static memory, but dynamically allocated memory can be used as well. + * + * #define mqttexampleSHARED_BUFFER_SIZE 100 + * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; + * void sendUnsubscribePacket( int xMQTTSocket ) + * { + * IotMqttError_t xResult; + * IotMqttPublishInfo_t xMQTTPublishInfo; + * size_t xRemainingLength = 0; + * size_t xPacketSize = 0; + * size_t xSentBytes = 0; + * uint16_t usPacketIdentifier; + * uint8_t * pusPacketIdentifierHigh; + * + * // Initialize Publish parameters. Details are out of scope for this example. + * // It will involve setting QOS, topic filter, topic filter length, payload + * // payload length. + * _initializePublish( &xMQTTPublishInfo ); + * + * // Find out length of Publish packet size. + * xResult = IotMqtt_GetPublishPacketSize( &xMQTTPublishInfo, &xRemainingLength, &xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * // Make sure the packet size is less than static buffer size + * configASSERT( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); + * + * xResult = IotMqtt_SerializePublish( &xMQTTPublishInfo, + * xRemainingLength, + * &usPacketIdentifier, + * &pusPacketIdentifierHigh, + * ucSharedBuffer, + * xPacketSize ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * xSentBytes = send( xMQTTSocket, ( void * ) ucSharedBuffer, xPacketSize, 0 ); + * IotMqtt_Assert( xSentBytes == xPacketSize ); + * } + * @endcode + */ +/* @[declare_mqtt_serializepublish] */ +IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t bufferSize ); +/* @[declare_mqtt_serializepublish] */ + +/** + * @brief Generate a DISCONNECT packet + * + * @param[in, out] pBuffer User provide buffer where the DISCONNECT packet is written. + * @param[in] bufferSize Size of the buffer pointed to by pBuffer. + * + * @return returns #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example below shows how IotMqtt_SerializeDisconnect() should be used. + * + * #define mqttexampleSHARED_BUFFER_SIZE 100 + * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; + * void sendDisconnectRequest( int xMQTTSocket ) + * { + * size_t xSentBytes = 0; + * + * // Disconnect is fixed length packet, therefore there is no need to calculate the size, + * // just makes sure static buffer can accomodate disconnect request. + * IotMqtt_Assert( MQTT_PACKET_DISCONNECT_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); + * + * // Serialize Disconnect packet into static buffer (dynamicaly allocated buffer can be used as well) + * xResult = IotMqtt_SerializeDisconnect( ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * xSentByte = send( xMQTTSocket, ( void * ) ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE, 0 ); + * IotMqtt_Assert( xSentByte == MQTT_PACKET_DISCONNECT_SIZE ); + * } + * + * @endcode + */ +/* @[declare_mqtt_serializedisconnect] */ +IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, + size_t bufferSize ); +/* @[declare_mqtt_serializedisconnect] */ + +/** + * @brief Generate a PINGREQ packet. + * + * @param[in, out] pBuffer User provide buffer where the PINGREQ packet is written. + * @param[in] bufferSize Size of the buffer pointed to by pBuffer. + * + * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_BAD_PARAMETER. + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example below shows how IotMqtt_SerializePingReq() should be used. + * + * #define mqttexampleSHARED_BUFFER_SIZE 100 + * static ucSharedBuffer[mqttexampleSHARED_BUFFER_SIZE]; + * void sendPingRequest( int xMQTTSocket ) + * { + * size_t xSentBytes = 0; + * + * // PingReq is fixed length packet, therefore there is no need to calculate the size, + * // just makes sure static buffer can accomodate Ping request. + * IotMqtt_Assert( MQTT_PACKET_PINGREQ_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); + * + * xResult = IotMqtt_SerializePingreq( ucSharedBuffer, MQTT_PACKET_PINGREQ_SIZE ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * xSentByte = send( xMQTTSocket, ( void * ) ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE, 0 ); + * IotMqtt_Assert( xSentByte == MQTT_PACKET_PINGREQ_SIZE); + * } + * @endcode + */ +/* @[declare_mqtt_serializepingreq] */ +IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, + size_t bufferSize ); +/* @[declare_mqtt_serializepingreq] */ + +/** + * @brief Extract MQTT packet type and length from incoming packet + * + * @param[in, out] pIncomingPacket Pointer to IotMqttPacketInfo_t structure + * where type, remaining length and packet identifier are stored. + * @param[in] getNextByte Pointer to platform specific function which is used + * to extract type and length from incoming received stream (see example ). + * @param[in] pNetworkConnection Pointer to platform specific network connection + * which is used by getNextByte to receive network data + * + * @return #IOT_MQTT_SUCCESS on successful extraction of type and length, + * #IOT_MQTT_BAD_RESPONSE on failure. + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example code below shows how to implement getNetxByte function with posix sockets. + * // Note: IotMqttGetNextByte_t typedef IotMqttError_t (* IotMqttGetNextByte_t)( void * pNetworkContext, + * // uint8_t * pNextByte ); + * // Note: It is assumed that socket is aleady created and connected, + * + * IotMqttError_t getNextByte( void * pContext, + * uint8_t * pNextByte ) + * { + * int socket = ( int ) ( *pvContext ); + * int receivedBytes; + * IotMqttError_t result; + * + * receivedBytes = recv( socket, ( void * ) pNextByte, sizeof( uint8_t ), 0 ); + * + * if( receivedBytes == sizeof( uint8_t ) ) + * { + * result = IOT_MQTT_SUCCESS; + * } + * else + * { + * result = IOT_MQTT_TIMEOUT; + * } + * + * return result; + * } + * + * // Example below shows how IotMqtt_GetIncomingMQTTPacketTypeAndLength() is used to extract type + * // and length from incoming ping response. + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * void getTypeAndLengthFromIncomingMQTTPingResponse( int xMQTTSocket ) + * { + * IotMqttPacketInfo_t xIncomingPacket; + * IotMqttError_t xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * IotMqtt_Assert( xIncomingPacket.type == MQTT_PACKET_TYPE_PINGRESP ); + * } + * @endcode + * + */ +/* @[declare_mqtt_getincomingmqttpackettypeandlength] */ +IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, + IotMqttGetNextByte_t getNextByte, + void * pNetworkConnection ); +/* @[declare_mqtt_getincomingmqttpackettypeandlength] */ + +/** + * @brief Deserialize incoming publish packet. + * + * @param[in, out] pMqttPacket The caller of this API sets type, remainingLength and pRemainingData. + * On success, packetIdentifier and pubInfo will be set by the function. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_SERVER_REFUSED + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example below shows how IotMqtt_DeserializePublish() used to extract contents of incoming + * // Publish. xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * void processIncomingPublish( int xMQTTSocket ) + * { + * IotMqttError_t xResult; + * IotMqttPacketInfo_t xIncomingPacket; + * + * xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * IotMqtt_Assert( ( xIncomingPacket.type & 0xf0 ) == MQTT_PACKET_TYPE_PUBLISH ); + * IotMqtt_Assert( xIncomingPacket.remainingLength <= mqttexampleSHARED_BUFFER_SIZE ); + * + * // Receive the remaining bytes. + * if( recv( xMQTTSocket, ( void * ) ucSharedBuffer, xIncomingPacket.remainingLength, 0 ) == xIncomingPacket.remainingLength ) + * { + * xIncomingPacket.pRemainingData = ucSharedBuffer; + * + * if( IotMqtt_DeserializePublish( &xIncomingPacket ) != IOT_MQTT_SUCCESS ) + * { + * xResult = IOT_MQTT_BAD_RESPONSE; + * } + * else + * { + * // Process incoming Publish. + * IotLogInfo( "Incoming QOS : %d\n", xIncomingPacket.pubInfo.qos ); + * IotLogInfo( "Incoming Publish Topic Name: %.*s\n", xIncomingPacket.pubInfo.topicNameLength, xIncomingPacket.pubInfo.pTopicName ); + * IotLogInfo( "Incoming Publish Message : %.*s\n", xIncomingPacket.pubInfo.payloadLength, xIncomingPacket.pubInfo.pPayload ); + * } + * } + * else + * { + * xResult = IOT_MQTT_NETWORK_ERROR; + * } + * + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * } + * @endcode + */ +/* @[declare_mqtt_deserializepublish] */ +IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ); +/* @[declare_mqtt_deserializepublish] */ + +/** + * @brief Deserialize incoming ack packets. + * + * @param[in, out] pMqttPacket The caller of this API sets type, remainingLength and pRemainingData. + * On success, packetIdentifier will be set. + * + * @return One of the following: + * - #IOT_MQTT_SUCCESS + * - #IOT_MQTT_BAD_RESPONSE + * - #IOT_MQTT_SERVER_REFUSED + * + * @note This call is part of serializer API used for implementing light-weight MQTT client. + * + * Example + * @code{c} + * // Example below shows how IotMqtt_DeserializeResponse() is used to process unsubscribe ack. + * // xMQTTSocket here is posix socket created and connected to MQTT broker outside of this function. + * void processUnsubscribeAck( int xMQTTSocket ) + * { + * IotMqttError_t xResult; + * IotMqttPacketInfo_t xIncomingPacket; + * + * xResult = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &xIncomingPacket, getNextByte, ( void * ) xMQTTSocket ); + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * IotMqtt_Assert( xIncomingPacket.type == MQTT_PACKET_TYPE_UNSUBACK ); + * IotMqtt_Assert( xIncomingPacket.remainingLength <= sizeof( ucSharedBuffer ) ); + * + * // Receive the remaining bytes. + * if( recv( xMQTTSocket, ( void * ) ucSharedBuffer, xIncomingPacket.remainingLength, 0 ) == xIncomingPacket.remainingLength ) + * { + * xIncomingPacket.pRemainingData = ucSharedBuffer; + * + * if( IotMqtt_DeserializeResponse( &xIncomingPacket ) != IOT_MQTT_SUCCESS ) + * { + * xResult = IOT_MQTT_BAD_RESPONSE; + * } + * } + * else + * { + * xResult = IOT_MQTT_NETWORK_ERROR; + * } + * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); + * } + * @endcode + */ +/* @[declare_mqtt_deserializeresponse] */ +IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ); +/* @[declare_mqtt_deserializeresponse] */ + + + +#endif /* ifndef IOT_MQTT_SERIALIZE_H_ */ diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index 38fd4e8e54..ff24844cc8 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -674,6 +674,29 @@ typedef struct IotMqttConnectInfo uint16_t passwordLength; /**< @brief Length of #IotMqttConnectInfo_t.pPassword. */ } IotMqttConnectInfo_t; +/** + * @ingroup mqtt_datatypes_paramstructs + * @brief MQTT packet details. + * + * @paramfor @ref mqtt_function_deserializeresponse @ref mqtt_function_deserializepublish + * + * Passed as an argument to public low level mqtt deserialize functions. + * + * @initializer{IotMqttPacketInfo_t,IOT_MQTT_PACKET_INFO_INITIALIZER} + * + * @note This structure should be only used while accessing low level MQTT deserialization API. + * The low level serialization/ deserialization API should be only used for implementing + * light weight single threaded mqtt client. + */ +typedef struct IotMqttPacketInfo +{ + uint8_t * pRemainingData; /**< @brief (Input) The remaining data in MQTT packet. */ + size_t remainingLength; /**< @brief (Input) Length of the remaining data in the MQTT packet. */ + uint16_t packetIdentifier; /**< @brief (Output) MQTT packet identifier. */ + uint8_t type; /**< @brief (Input) A value identifying the packet type. */ + IotMqttPublishInfo_t pubInfo; /**< @brief (Output) Publish info in case of deserializing PUBLISH. */ +} IotMqttPacketInfo_t; + /** * @cond DOXYGEN_IGNORE * Doxygen should ignore this section. @@ -792,6 +815,14 @@ typedef void ( * IotMqttPublishSetDup_t )( uint8_t * pPublishPacket, uint8_t * pPacketIdentifierHigh, uint16_t * pNewPacketIdentifier ); +/** + * @brief Function pointer to read the next available byte on a network connection. + * @param[in] pNetworkContext reference to network connection like socket. + * @param[out] pNextByte Pointer to the byte read from the network. + */ +typedef IotMqttError_t (* IotMqttGetNextByte_t)( void * pNetworkContext, + uint8_t * pNextByte ); + #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 /** @@ -1098,6 +1129,8 @@ typedef struct IotMqttNetworkInfo #define IOT_MQTT_CONNECTION_INITIALIZER NULL /** @brief Initializer for #IotMqttOperation_t. */ #define IOT_MQTT_OPERATION_INITIALIZER NULL +/** @brief Initializer for #IotMqttPacketInfo_t. */ +#define IOT_MQTT_PACKET_INFO_INITIALIZER { .pRemainingData = NULL, remainingLength = 0, packetIdentifier = 0, .type = 0 } /* @[define_mqtt_initializers] */ /** diff --git a/libraries/standard/mqtt/src/iot_mqtt_network.c b/libraries/standard/mqtt/src/iot_mqtt_network.c index c85e7a9875..19c3f60f4e 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_network.c +++ b/libraries/standard/mqtt/src/iot_mqtt_network.c @@ -152,7 +152,7 @@ static void _flushPacket( void * pNetworkConnection, _getMqttFreePacketFunc, _IotMqtt_FreePacket, freePacket ) -#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ +#else /* if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1 */ #define _getPacketTypeFunc( pSerializer ) _IotMqtt_GetPacketType #define _getRemainingLengthFunc( pSerializer ) _IotMqtt_GetRemainingLength #define _getConnackDeserializer( pSerializer ) _IotMqtt_DeserializeConnack @@ -843,3 +843,42 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, } /*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_GetIncomingMQTTPacketTypeAndLength( IotMqttPacketInfo_t * pIncomingPacket, + IotMqttGetNextByte_t getNextByte, + void * pNetworkConnection ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Read the packet type, which is the first byte available. */ + if( getNextByte( pNetworkConnection, &( pIncomingPacket->type ) ) == IOT_MQTT_SUCCESS ) + { + /* Check that the incoming packet type is valid. */ + if( _incomingPacketValid( pIncomingPacket->type ) == false ) + { + IotLogError( "(MQTT connection) Unknown packet type %02x received.", + pIncomingPacket->type ); + + status = IOT_MQTT_BAD_RESPONSE; + } + else + { + /* Read the remaining length. */ + pIncomingPacket->remainingLength = _IotMqtt_GetRemainingLength_Generic( pNetworkConnection, + getNextByte ); + + if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID ) + { + status = IOT_MQTT_BAD_RESPONSE; + } + } + } + else + { + status = IOT_MQTT_NETWORK_ERROR; + } + + return status; +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index fc3001dd9e..b3e97af792 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -267,6 +267,75 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, size_t * pRemainingLength, size_t * pPacketSize ); +/** + * @brief Generate a CONNECT packet from the given parameters. + * + * @param[in] pConnectInfo User-provided CONNECT information. + * @param[in] remainingLength User provided remaining length. + * @param[in, out] pBuffer User provided buffer where the CONNECT packet is written. + * @param[in] connectPacketSize Size of the buffer pointed to by `pBuffer`. + * + */ +void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pBuffer, + size_t connectPacketSize ); + +/** + * @brief Generate a PUBLISH packet from the given parameters. + * + * @param[in] pPublishInfo User-provided PUBLISH information. + * @param[in] remainingLength User provided remaining length. + * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH. + * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier + * is written. + * @param[in, out] pBuffer User provided buffer where the PUBLISH packet is written. + * @param[in] publishPacketSize Size of buffer pointed to by `pBuffer`. + * + */ +void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t publishPacketSize ); + +/** + * @brief Generate a SUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[in] remainingLength User provided remaining length. + * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE. + * @param[in, out] pBuffer User provided buffer where the SUBSCRIBE packet is written. + * @param[in] subscribePacketSize Size of the buffer pointed to by `pBuffer`. + * + */ +void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t subscribePacketSize ); + +/** + * @brief Generate an UNSUBSCRIBE packet from the given parameters. + * + * @param[in] pSubscriptionList User-provided array of subscriptions to remove. + * @param[in] subscriptionCount Size of `pSubscriptionList`. + * @param[in] remainingLength User provided remaining length. + * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE. + * @param[in, out] pBuffer User provided buffer where the UNSUBSCRIBE packet is written. + * @param[in] unsubscribePacketSize size of the buffer pointed to by `pBuffer`. + * + */ +void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t unsubscribePacketSize ); + /*-----------------------------------------------------------*/ #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE @@ -400,14 +469,7 @@ static bool _connectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, connectPacketSize += 10U; /* Add the length of the client identifier if provided. */ - if( pConnectInfo->clientIdentifierLength > 0 ) - { - connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); - } - else - { - EMPTY_ELSE_MARKER; - } + connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t ); /* Add the lengths of the will message and topic name if provided. */ if( pConnectInfo->pWillInfo != NULL ) @@ -594,124 +656,13 @@ static bool _subscriptionPacketSize( IotMqttOperationType_t type, /*-----------------------------------------------------------*/ -uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ) -{ - uint8_t packetType = 0xff; - - /* The MQTT packet type is in the first byte of the packet. */ - ( void ) _IotMqtt_GetNextByte( pNetworkConnection, - pNetworkInterface, - &packetType ); - - return packetType; -} - -/*-----------------------------------------------------------*/ - -size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, - const IotNetworkInterface_t * pNetworkInterface ) -{ - uint8_t encodedByte = 0; - size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; - - /* This algorithm is copied from the MQTT v3.1.1 spec. */ - do - { - if( multiplier > 2097152 ) /* 128 ^ 3 */ - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - break; - } - else - { - if( _IotMqtt_GetNextByte( pNetworkConnection, - pNetworkInterface, - &encodedByte ) == true ) - { - remainingLength += ( encodedByte & 0x7F ) * multiplier; - multiplier *= 128; - bytesDecoded++; - } - else - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - break; - } - } - } while( ( encodedByte & 0x80 ) != 0 ); - - /* Check that the decoded remaining length conforms to the MQTT specification. */ - if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) - { - expectedSize = _remainingLengthEncodedSize( remainingLength ); - - if( bytesDecoded != expectedSize ) - { - remainingLength = MQTT_REMAINING_LENGTH_INVALID; - } - else - { - /* Valid remaining length should be at most 4 bytes. */ - IotMqtt_Assert( bytesDecoded <= 4 ); - } - } - else - { - EMPTY_ELSE_MARKER; - } - - return remainingLength; -} - -/*-----------------------------------------------------------*/ - -IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, - uint8_t ** pConnectPacket, - size_t * pPacketSize ) +void _serializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pBuffer, + size_t connectPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); uint8_t connectFlags = 0; - size_t remainingLength = 0, connectPacketSize = 0; - uint8_t * pBuffer = NULL; - - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _connectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) - { - IotLogError( "Connect packet length exceeds %lu, which is the maximum" - " size allowed by MQTT 3.1.1.", - MQTT_PACKET_CONNECT_MAX_SIZE ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); - } - else - { - EMPTY_ELSE_MARKER; - } - - /* Total size of the connect packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( connectPacketSize > remainingLength ); - - /* Allocate memory to hold the CONNECT packet. */ - pBuffer = IotMqtt_MallocMessage( connectPacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for CONNECT packet." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); - } - else - { - EMPTY_ELSE_MARKER; - } - - /* Set the output parameters. The remainder of this function always succeeds. */ - *pConnectPacket = pBuffer; - *pPacketSize = connectPacketSize; + uint8_t * pConnectPacket = pBuffer; /* The first byte in the CONNECT packet is the control packet type. */ *pBuffer = MQTT_PACKET_TYPE_CONNECT; @@ -875,117 +826,488 @@ IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectI /* Ensure that the difference between the end and beginning of the buffer * is equal to connectPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - *pConnectPacket ) == connectPacketSize ); + IotMqtt_Assert( ( size_t ) ( pBuffer - pConnectPacket ) == connectPacketSize ); /* Print out the serialized CONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT CONNECT packet:", *pConnectPacket, connectPacketSize ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); + IotLog_PrintBuffer( "MQTT CONNECT packet:", pConnectPacket, connectPacketSize ); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) +void _serializePublish( const IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t publishPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - const uint8_t * pRemainingData = pConnack->pRemainingData; - - /* If logging is enabled, declare the CONNACK response code strings. The - * fourth byte of CONNACK indexes into this array for the corresponding response. */ - #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE - static const char * pConnackResponses[ 6 ] = - { - "Connection accepted.", /* 0 */ - "Connection refused: unacceptable protocol version.", /* 1 */ - "Connection refused: identifier rejected.", /* 2 */ - "Connection refused: server unavailable", /* 3 */ - "Connection refused: bad user name or password.", /* 4 */ - "Connection refused: not authorized." /* 5 */ - }; - #endif + uint8_t publishFlags = 0; + uint16_t packetIdentifier = 0; + uint8_t * pPublishPacket = pBuffer; - /* Check that the control packet type is 0x20. */ - if( pConnack->type != MQTT_PACKET_TYPE_CONNACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pConnack->type ); + /* The first byte of a PUBLISH packet contains the packet type and flags. */ + publishFlags = MQTT_PACKET_TYPE_PUBLISH; - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else + if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) { - EMPTY_ELSE_MARKER; + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); } - - /* According to MQTT 3.1.1, the second byte of CONNACK must specify a - * "Remaining length" of 2. */ - if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "CONNACK does not have remaining length of %d.", - MQTT_PACKET_CONNACK_REMAINING_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); } else { EMPTY_ELSE_MARKER; } - /* Check the reserved bits in CONNACK. The high 7 bits of the second byte - * in CONNACK must be 0. */ - if( ( pRemainingData[ 0 ] | 0x01 ) != 0x01 ) + if( pPublishInfo->retain == true ) { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Reserved bits in CONNACK incorrect." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); } else { EMPTY_ELSE_MARKER; } - /* Determine if the "Session Present" bit it set. This is the lowest bit of - * the second byte in CONNACK. */ - if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) - == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + *pBuffer = publishFlags; + pBuffer++; + + /* The "Remaining length" is encoded from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* The topic name is placed after the "Remaining length". */ + pBuffer = _encodeString( pBuffer, + pPublishInfo->pTopicName, + pPublishInfo->topicNameLength ); + + /* A packet identifier is required for QoS 1 and 2 messages. */ + if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit set." ); + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + IotMqtt_Assert( packetIdentifier != 0 ); - /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the - * "Session Present" bit is set. */ - if( pRemainingData[ 1 ] != 0 ) + /* Set the packet identifier output parameters. */ + *pPacketIdentifier = packetIdentifier; + + if( pPacketIdentifierHigh != NULL ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + *pPacketIdentifierHigh = pBuffer; } else { EMPTY_ELSE_MARKER; } + + /* Place the packet identifier into the PUBLISH packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; } else { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK session present bit not set." ); + EMPTY_ELSE_MARKER; } - /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ - if( pRemainingData[ 1 ] > 5 ) + /* The payload is placed after the packet identifier. */ + if( pPublishInfo->payloadLength > 0 ) { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "CONNACK response %hhu is not valid.", - pRemainingData[ 1 ] ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); + pBuffer += pPublishInfo->payloadLength; + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - pPublishPacket ) == publishPacketSize ); + + /* Print out the serialized PUBLISH packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PUBLISH packet:", pPublishPacket, publishPacketSize ); +} + +/*-----------------------------------------------------------*/ + +void _serializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t subscribePacketSize ) +{ + uint16_t packetIdentifier = 0; + size_t i = 0; + uint8_t * pSubscribePacket = pBuffer; + + /* The first byte in SUBSCRIBE is the packet type. */ + *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + IotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the SUBSCRIBE packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter and QoS. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + + /* Place the QoS in the SUBSCRIBE packet. */ + *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); + pBuffer++; + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - pSubscribePacket ) == subscribePacketSize ); + + /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", pSubscribePacket, subscribePacketSize ); +} + +/*-----------------------------------------------------------*/ + +void _serializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t unsubscribePacketSize ) +{ + uint16_t packetIdentifier = 0; + size_t i = 0; + uint8_t * pUnsubscribePacket = pBuffer; + + /* The first byte in UNSUBSCRIBE is the packet type. */ + *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; + pBuffer++; + + /* Encode the "Remaining length" starting from the second byte. */ + pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + + /* Get the next packet identifier. It should always be nonzero. */ + packetIdentifier = _nextPacketIdentifier(); + *pPacketIdentifier = packetIdentifier; + IotMqtt_Assert( packetIdentifier != 0 ); + + /* Place the packet identifier into the UNSUBSCRIBE packet. */ + *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); + *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); + pBuffer += 2; + + /* Serialize each subscription topic filter. */ + for( i = 0; i < subscriptionCount; i++ ) + { + pBuffer = _encodeString( pBuffer, + pSubscriptionList[ i ].pTopicFilter, + pSubscriptionList[ i ].topicFilterLength ); + } + + /* Ensure that the difference between the end and beginning of the buffer + * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ + IotMqtt_Assert( ( size_t ) ( pBuffer - pUnsubscribePacket ) == unsubscribePacketSize ); + + /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", pUnsubscribePacket, unsubscribePacketSize ); +} + +/*-----------------------------------------------------------*/ + +uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) +{ + uint8_t packetType = 0xff; + + /* The MQTT packet type is in the first byte of the packet. */ + ( void ) _IotMqtt_GetNextByte( pNetworkConnection, + pNetworkInterface, + &packetType ); + + return packetType; +} + +/*-----------------------------------------------------------*/ + +size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, + const IotNetworkInterface_t * pNetworkInterface ) +{ + uint8_t encodedByte = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152 ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + break; + } + else + { + if( _IotMqtt_GetNextByte( pNetworkConnection, + pNetworkInterface, + &encodedByte ) == true ) + { + remainingLength += ( encodedByte & 0x7F ) * multiplier; + multiplier *= 128; + bytesDecoded++; + } + else + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + break; + } + } + } while( ( encodedByte & 0x80 ) != 0 ); + + /* Check that the decoded remaining length conforms to the MQTT specification. */ + if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) + { + expectedSize = _remainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + else + { + /* Valid remaining length should be at most 4 bytes. */ + IotMqtt_Assert( bytesDecoded <= 4 ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return remainingLength; +} + +/*-----------------------------------------------------------*/ + +size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnecton, + IotMqttGetNextByte_t getNextByte ) +{ + uint8_t encodedByte = 0; + size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0; + + /* This algorithm is copied from the MQTT v3.1.1 spec. */ + do + { + if( multiplier > 2097152 ) /* 128 ^ 3 */ + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + break; + } + else + { + if( getNextByte( pNetworkConnecton, &encodedByte ) == IOT_MQTT_SUCCESS ) + { + remainingLength += ( encodedByte & 0x7F ) * multiplier; + multiplier *= 128; + bytesDecoded++; + } + else + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + break; + } + } + } while( ( encodedByte & 0x80 ) != 0 ); + + /* Check that the decoded remaining length conforms to the MQTT specification. */ + if( remainingLength != MQTT_REMAINING_LENGTH_INVALID ) + { + expectedSize = _remainingLengthEncodedSize( remainingLength ); + + if( bytesDecoded != expectedSize ) + { + remainingLength = MQTT_REMAINING_LENGTH_INVALID; + } + else + { + /* Valid remaining length should be at most 4 bytes. */ + IotMqtt_Assert( bytesDecoded <= 4 ); + } + } + else + { + EMPTY_ELSE_MARKER; + } + + return remainingLength; +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + uint8_t ** pConnectPacket, + size_t * pPacketSize ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t remainingLength = 0, connectPacketSize = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _connectPacketSize( pConnectInfo, &remainingLength, &connectPacketSize ) == false ) + { + IotLogError( "Connect packet length exceeds %lu, which is the maximum" + " size allowed by MQTT 3.1.1.", + MQTT_PACKET_CONNECT_MAX_SIZE ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Total size of the connect packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( connectPacketSize > remainingLength ); + + /* Allocate memory to hold the CONNECT packet. */ + pBuffer = IotMqtt_MallocMessage( connectPacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for CONNECT packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pConnectPacket = pBuffer; + *pPacketSize = connectPacketSize; + + _serializeConnect( pConnectInfo, remainingLength, pBuffer, connectPacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + const uint8_t * pRemainingData = pConnack->pRemainingData; + + /* If logging is enabled, declare the CONNACK response code strings. The + * fourth byte of CONNACK indexes into this array for the corresponding response. */ + #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE + static const char * pConnackResponses[ 6 ] = + { + "Connection accepted.", /* 0 */ + "Connection refused: unacceptable protocol version.", /* 1 */ + "Connection refused: identifier rejected.", /* 2 */ + "Connection refused: server unavailable", /* 3 */ + "Connection refused: bad user name or password.", /* 4 */ + "Connection refused: not authorized." /* 5 */ + }; + #endif + + /* Check that the control packet type is 0x20. */ + if( pConnack->type != MQTT_PACKET_TYPE_CONNACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pConnack->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* According to MQTT 3.1.1, the second byte of CONNACK must specify a + * "Remaining length" of 2. */ + if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "CONNACK does not have remaining length of %d.", + MQTT_PACKET_CONNACK_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Check the reserved bits in CONNACK. The high 7 bits of the second byte + * in CONNACK must be 0. */ + if( ( pRemainingData[ 0 ] | 0x01 ) != 0x01 ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Reserved bits in CONNACK incorrect." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Determine if the "Session Present" bit it set. This is the lowest bit of + * the second byte in CONNACK. */ + if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit set." ); + + /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the + * "Session Present" bit is set. */ + if( pRemainingData[ 1 ] != 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK session present bit not set." ); + } + + /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */ + if( pRemainingData[ 1 ] > 5 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "CONNACK response %hhu is not valid.", + pRemainingData[ 1 ] ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { @@ -997,293 +1319,572 @@ IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack ) #if LIBRARY_LOG_LEVEL > IOT_LOG_NONE IotLog( IOT_LOG_DEBUG, &_logHideAll, - "%s", - pConnackResponses[ pRemainingData[ 1 ] ] ); - #endif + "%s", + pConnackResponses[ pRemainingData[ 1 ] ] ); + #endif + + /* A nonzero CONNACK response code means the connection was refused. */ + if( pRemainingData[ 1 ] > 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); + } + else + { + EMPTY_ELSE_MARKER; + } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, + uint8_t ** pPublishPacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t remainingLength = 0, publishPacketSize = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _publishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) + { + IotLogError( "Publish packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Total size of the publish packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( publishPacketSize > remainingLength ); + + /* Allocate memory to hold the PUBLISH packet. */ + pBuffer = IotMqtt_MallocMessage( publishPacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for PUBLISH packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPublishPacket = pBuffer; + *pPacketSize = publishPacketSize; + + /* Serialize publish into buffer pointed to by pBuffer */ + _serializePublish( pPublishInfo, + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + publishPacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, + uint8_t * pPacketIdentifierHigh, + uint16_t * pNewPacketIdentifier ) +{ + uint16_t newPacketIdentifier = 0; + + /* For an AWS IoT MQTT server, change the packet identifier. */ + if( pPacketIdentifierHigh != NULL ) + { + /* Output parameter for new packet identifier must be provided. */ + IotMqtt_Assert( pNewPacketIdentifier != NULL ); + + /* Generate a new packet identifier. */ + newPacketIdentifier = _nextPacketIdentifier(); + + IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", + UINT16_DECODE( pPacketIdentifierHigh ), + newPacketIdentifier ); + + /* Replace the packet identifier. */ + *pPacketIdentifierHigh = UINT16_HIGH_BYTE( newPacketIdentifier ); + *( pPacketIdentifierHigh + 1 ) = UINT16_LOW_BYTE( newPacketIdentifier ); + *pNewPacketIdentifier = newPacketIdentifier; + } + else + { + /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ + UINT8_SET_BIT( *pPublishPacket, MQTT_PUBLISH_FLAG_DUP ); + + IotLogDebug( "PUBLISH DUP flag set." ); + } +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); + uint8_t publishFlags = 0; + const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; + + /* The flags are the lower 4 bits of the first byte in PUBLISH. */ + publishFlags = pPublish->type; + + /* Parse the Retain bit. */ + pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Retain bit is %d.", pOutput->retain ); + + /* Check for QoS 2. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) + { + /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad QoS: 3." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + + pOutput->qos = IOT_MQTT_QOS_2; + } + /* Check for QoS 1. */ + else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) + { + pOutput->qos = IOT_MQTT_QOS_1; + } + /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ + else + { + pOutput->qos = IOT_MQTT_QOS_0; + } + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS is %d.", pOutput->qos ); + + /* Parse the DUP bit. */ + if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 1." ); + } + else + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "DUP is 0." ); + } - /* A nonzero CONNACK response code means the connection was refused. */ - if( pRemainingData[ 1 ] > 0 ) + /* Sanity checks for "Remaining length". */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_SERVER_REFUSED ); + /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate + * topic name length (2 bytes) and topic name (at least 1 byte). */ + if( pPublish->remainingLength < 3 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 0 PUBLISH cannot have a remaining length less than 3." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + /* A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 to + * accommodate a packet identifier as well as the topic name length and + * topic name. */ + if( pPublish->remainingLength < 5 ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Extract the topic name starting from the first byte of the variable header. + * The topic name string starts at byte 3 in the variable header. */ + pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); + + /* Sanity checks for topic name length and "Remaining length". */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) + { + /* Check that the "Remaining length" is at least as large as the variable + * header. */ + if( pPublish->remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + /* Check that the "Remaining length" is at least as large as the variable + * header. */ + if( pPublish->remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) + { + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Remaining length cannot be less than variable header length." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + + /* Parse the topic. */ + pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic name length %hu: %.*s", + pOutput->topicNameLength, + pOutput->topicNameLength, + pOutput->pTopicName ); + + /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet + * identifier starts immediately after the topic name. */ + pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); + + if( pOutput->qos > IOT_MQTT_QOS_0 ) + { + pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); + + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pPublish->packetIdentifier ); + + /* Packet identifier cannot be 0. */ + if( pPublish->packetIdentifier == 0 ) + { + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } + } + else + { + EMPTY_ELSE_MARKER; + } + + /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain + * a packet identifer, but QoS 0 PUBLISH packets do not. */ + if( pOutput->qos == IOT_MQTT_QOS_0 ) + { + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh; } else { - EMPTY_ELSE_MARKER; + pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); + pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); } + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Payload length %hu.", pOutput->payloadLength ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo, - uint8_t ** pPublishPacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier, - uint8_t ** pPacketIdentifierHigh ) +IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, + uint8_t ** pPubackPacket, + size_t * pPacketSize ) { - IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - uint8_t publishFlags = 0; - uint16_t packetIdentifier = 0; - size_t remainingLength = 0, publishPacketSize = 0; - uint8_t * pBuffer = NULL; + IotMqttError_t status = IOT_MQTT_SUCCESS; - /* Calculate the "Remaining length" field and total packet size. If it exceeds - * what is allowed in the MQTT standard, return an error. */ - if( _publishPacketSize( pPublishInfo, &remainingLength, &publishPacketSize ) == false ) + /* Allocate memory for PUBACK. */ + uint8_t * pBuffer = IotMqtt_MallocMessage( MQTT_PACKET_PUBACK_SIZE ); + + if( pBuffer == NULL ) { - IotLogError( "Publish packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + IotLogError( "Failed to allocate memory for PUBACK packet" ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + status = IOT_MQTT_NO_MEMORY; } else { - EMPTY_ELSE_MARKER; + /* Set the output parameters. The remainder of this function always succeeds. */ + *pPubackPacket = pBuffer; + *pPacketSize = MQTT_PACKET_PUBACK_SIZE; + + /* Set the 4 bytes in PUBACK. */ + pBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBACK; + pBuffer[ 1 ] = MQTT_PACKET_PUBACK_REMAINING_LENGTH; + pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetIdentifier ); + pBuffer[ 3 ] = UINT16_LOW_BYTE( packetIdentifier ); + + /* Print out the serialized PUBACK packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, MQTT_PACKET_PUBACK_SIZE ); } - /* Total size of the publish packet should be larger than the "Remaining length" - * field. */ - IotMqtt_Assert( publishPacketSize > remainingLength ); + return status; +} - /* Allocate memory to hold the PUBLISH packet. */ - pBuffer = IotMqtt_MallocMessage( publishPacketSize ); +/*-----------------------------------------------------------*/ - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) +IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + + /* Check the "Remaining length" of the received PUBACK. */ + if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) { - IotLogError( "Failed to allocate memory for PUBLISH packet." ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PUBACK does not have remaining length of %d.", + MQTT_PACKET_PUBACK_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { EMPTY_ELSE_MARKER; } - /* Set the output parameters. The remainder of this function always succeeds. */ - *pPublishPacket = pBuffer; - *pPacketSize = publishPacketSize; + /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ + pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); - /* The first byte of a PUBLISH packet contains the packet type and flags. */ - publishFlags = MQTT_PACKET_TYPE_PUBLISH; + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Packet identifier %hu.", pPuback->packetIdentifier ); - if( pPublishInfo->qos == IOT_MQTT_QOS_1 ) - { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ); - } - else if( pPublishInfo->qos == IOT_MQTT_QOS_2 ) + /* Packet identifier cannot be 0. */ + if( pPuback->packetIdentifier == 0 ) { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { EMPTY_ELSE_MARKER; } - if( pPublishInfo->retain == true ) + /* Check that the control packet type is 0x40 (this must be done after the + * packet identifier is parsed). */ + if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) { - UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pPuback->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { EMPTY_ELSE_MARKER; } - *pBuffer = publishFlags; - pBuffer++; + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - /* The "Remaining length" is encoded from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); +/*-----------------------------------------------------------*/ - /* The topic name is placed after the "Remaining length". */ - pBuffer = _encodeString( pBuffer, - pPublishInfo->pTopicName, - pPublishInfo->topicNameLength ); +IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pSubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t subscribePacketSize = 0, remainingLength = 0; + uint8_t * pBuffer = NULL; - /* A packet identifier is required for QoS 1 and 2 messages. */ - if( pPublishInfo->qos > IOT_MQTT_QOS_0 ) + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _subscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &subscribePacketSize ) == false ) { - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _nextPacketIdentifier(); - IotMqtt_Assert( packetIdentifier != 0 ); - - /* Set the packet identifier output parameters. */ - *pPacketIdentifier = packetIdentifier; - - if( pPacketIdentifierHigh != NULL ) - { - *pPacketIdentifierHigh = pBuffer; - } - else - { - EMPTY_ELSE_MARKER; - } + IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); - /* Place the packet identifier into the PUBLISH packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { EMPTY_ELSE_MARKER; } - /* The payload is placed after the packet identifier. */ - if( pPublishInfo->payloadLength > 0 ) + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( subscribePacketSize > remainingLength ); + + /* Allocate memory to hold the SUBSCRIBE packet. */ + pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); + + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) { - ( void ) memcpy( pBuffer, pPublishInfo->pPayload, pPublishInfo->payloadLength ); - pBuffer += pPublishInfo->payloadLength; + IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); } else { EMPTY_ELSE_MARKER; } - /* Ensure that the difference between the end and beginning of the buffer - * is equal to publishPacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - *pPublishPacket ) == publishPacketSize ); - - /* Print out the serialized PUBLISH packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBLISH packet:", *pPublishPacket, publishPacketSize ); - - IOT_FUNCTION_EXIT_NO_CLEANUP(); -} - -/*-----------------------------------------------------------*/ - -void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket, - uint8_t * pPacketIdentifierHigh, - uint16_t * pNewPacketIdentifier ) -{ - uint16_t newPacketIdentifier = 0; - - /* For an AWS IoT MQTT server, change the packet identifier. */ - if( pPacketIdentifierHigh != NULL ) - { - /* Output parameter for new packet identifier must be provided. */ - IotMqtt_Assert( pNewPacketIdentifier != NULL ); - - /* Generate a new packet identifier. */ - newPacketIdentifier = _nextPacketIdentifier(); + /* Set the output parameters. The remainder of this function always succeeds. */ + *pSubscribePacket = pBuffer; + *pPacketSize = subscribePacketSize; - IotLogDebug( "Changing PUBLISH packet identifier %hu to %hu.", - UINT16_DECODE( pPacketIdentifierHigh ), - newPacketIdentifier ); + /* Serialize subscribe into buffer pointed to by pBuffer */ + _serializeSubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + subscribePacketSize ); - /* Replace the packet identifier. */ - *pPacketIdentifierHigh = UINT16_HIGH_BYTE( newPacketIdentifier ); - *( pPacketIdentifierHigh + 1 ) = UINT16_LOW_BYTE( newPacketIdentifier ); - *pNewPacketIdentifier = newPacketIdentifier; - } - else - { - /* For a compliant MQTT 3.1.1 server, set the DUP flag. */ - UINT8_SET_BIT( *pPublishPacket, MQTT_PUBLISH_FLAG_DUP ); - IotLogDebug( "PUBLISH DUP flag set." ); - } + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) +IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - IotMqttPublishInfo_t * pOutput = &( pPublish->u.pIncomingPublish->u.publish.publishInfo ); - uint8_t publishFlags = 0; - const uint8_t * pVariableHeader = pPublish->pRemainingData, * pPacketIdentifierHigh = NULL; - - /* The flags are the lower 4 bits of the first byte in PUBLISH. */ - publishFlags = pPublish->type; - - /* Parse the Retain bit. */ - pOutput->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Retain bit is %d.", pOutput->retain ); + size_t i = 0, remainingLength = pSuback->remainingLength; + uint8_t subscriptionStatus = 0; + const uint8_t * pVariableHeader = pSuback->pRemainingData; - /* Check for QoS 2. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) == true ) + /* A SUBACK must have a remaining length of at least 3 to accommodate the + * packet identifer and at least 1 return code. */ + if( remainingLength < 3 ) { - /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad QoS: 3." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - EMPTY_ELSE_MARKER; - } + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "SUBACK cannot have a remaining length less than 3." ); - pOutput->qos = IOT_MQTT_QOS_2; - } - /* Check for QoS 1. */ - else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) == true ) - { - pOutput->qos = IOT_MQTT_QOS_1; + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } - /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */ else { - pOutput->qos = IOT_MQTT_QOS_0; + EMPTY_ELSE_MARKER; } + /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ + pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); + IotLog( IOT_LOG_DEBUG, &_logHideAll, - "QoS is %d.", pOutput->qos ); + "Packet identifier %hu.", pSuback->packetIdentifier ); - /* Parse the DUP bit. */ - if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP ) == true ) + /* Check that the control packet type is 0x90 (this must be done after the + * packet identifier is parsed). */ + if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) { - IotLog( IOT_LOG_DEBUG, + IotLog( IOT_LOG_ERROR, &_logHideAll, - "DUP is 1." ); + "Bad control packet type 0x%02x.", + pSuback->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "DUP is 0." ); + EMPTY_ELSE_MARKER; } - /* Sanity checks for "Remaining length". */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) + /* Iterate through each status byte in the SUBACK packet. */ + for( i = 0; i < remainingLength - sizeof( uint16_t ); i++ ) { - /* A QoS 0 PUBLISH must have a remaining length of at least 3 to accommodate - * topic name length (2 bytes) and topic name (at least 1 byte). */ - if( pPublish->remainingLength < 3 ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS 0 PUBLISH cannot have a remaining length less than 3." ); + /* Read a single status byte in SUBACK. */ + subscriptionStatus = *( pVariableHeader + sizeof( uint16_t ) + i ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else + /* MQTT 3.1.1 defines the following values as status codes. */ + switch( subscriptionStatus ) { - EMPTY_ELSE_MARKER; + case 0x00: + case 0x01: + case 0x02: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu accepted, max QoS %hhu.", + ( unsigned long ) i, subscriptionStatus ); + break; + + case 0x80: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Topic filter %lu refused.", ( unsigned long ) i ); + + /* Remove a rejected subscription from the subscription manager. */ + _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, + pSuback->packetIdentifier, + ( int32_t ) i ); + + status = IOT_MQTT_SERVER_REFUSED; + + break; + + default: + IotLog( IOT_LOG_DEBUG, + &_logHideAll, + "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); + + status = IOT_MQTT_BAD_RESPONSE; + + break; } - } - else - { - /* A QoS 1 or 2 PUBLISH must have a remaining length of at least 5 to - * accommodate a packet identifier as well as the topic name length and - * topic name. */ - if( pPublish->remainingLength < 5 ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "QoS 1 or 2 PUBLISH cannot have a remaining length less than 5." ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + /* Stop parsing the subscription statuses if a bad response was received. */ + if( status == IOT_MQTT_BAD_RESPONSE ) + { + break; } else { @@ -1291,152 +1892,166 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) } } - /* Extract the topic name starting from the first byte of the variable header. - * The topic name string starts at byte 3 in the variable header. */ - pOutput->topicNameLength = UINT16_DECODE( pVariableHeader ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - /* Sanity checks for topic name length and "Remaining length". */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + uint8_t ** pUnsubscribePacket, + size_t * pPacketSize, + uint16_t * pPacketIdentifier ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + size_t unsubscribePacketSize = 0, remainingLength = 0; + uint8_t * pBuffer = NULL; + + /* Calculate the "Remaining length" field and total packet size. If it exceeds + * what is allowed in the MQTT standard, return an error. */ + if( _subscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, + pSubscriptionList, + subscriptionCount, + &remainingLength, + &unsubscribePacketSize ) == false ) { - /* Check that the "Remaining length" is at least as large as the variable - * header. */ - if( pPublish->remainingLength < pOutput->topicNameLength + sizeof( uint16_t ) ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length cannot be less than variable header length." ); + IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - EMPTY_ELSE_MARKER; - } + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { - /* Check that the "Remaining length" is at least as large as the variable - * header. */ - if( pPublish->remainingLength < pOutput->topicNameLength + 2 * sizeof( uint16_t ) ) - { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Remaining length cannot be less than variable header length." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - EMPTY_ELSE_MARKER; - } + EMPTY_ELSE_MARKER; } - /* Parse the topic. */ - pOutput->pTopicName = ( const char * ) ( pVariableHeader + sizeof( uint16_t ) ); + /* Total size of the unsubscribe packet should be larger than the "Remaining length" + * field. */ + IotMqtt_Assert( unsubscribePacketSize > remainingLength ); - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic name length %hu: %.*s", - pOutput->topicNameLength, - pOutput->topicNameLength, - pOutput->pTopicName ); + /* Allocate memory to hold the UNSUBSCRIBE packet. */ + pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); - /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet - * identifier starts immediately after the topic name. */ - pPacketIdentifierHigh = ( const uint8_t * ) ( pOutput->pTopicName + pOutput->topicNameLength ); + /* Check that sufficient memory was allocated. */ + if( pBuffer == NULL ) + { + IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - if( pOutput->qos > IOT_MQTT_QOS_0 ) + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); + } + else { - pPublish->packetIdentifier = UINT16_DECODE( pPacketIdentifierHigh ); + EMPTY_ELSE_MARKER; + } - IotLog( IOT_LOG_DEBUG, + /* Set the output parameters. The remainder of this function always succeeds. */ + *pUnsubscribePacket = pBuffer; + *pPacketSize = unsubscribePacketSize; + + /* Serialize unsubscribe into buffer pointed to by pBuffer */ + _serializeUnsubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + unsubscribePacketSize ); + + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + + /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ + if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) + { + IotLog( IOT_LOG_ERROR, &_logHideAll, - "Packet identifier %hu.", pPublish->packetIdentifier ); + "UNSUBACK does not have remaining length of %d.", + MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - /* Packet identifier cannot be 0. */ - if( pPublish->packetIdentifier == 0 ) - { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else - { - EMPTY_ELSE_MARKER; - } + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { EMPTY_ELSE_MARKER; } - /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain - * a packet identifer, but QoS 0 PUBLISH packets do not. */ - if( pOutput->qos == IOT_MQTT_QOS_0 ) + /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ + pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); + + /* Packet identifier cannot be 0. */ + if( pUnsuback->packetIdentifier == 0 ) { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh; + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else { - pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - 2 * sizeof( uint16_t ) ); - pOutput->pPayload = pPacketIdentifierHigh + sizeof( uint16_t ); + EMPTY_ELSE_MARKER; } IotLog( IOT_LOG_DEBUG, &_logHideAll, - "Payload length %hu.", pOutput->payloadLength ); + "Packet identifier %hu.", pUnsuback->packetIdentifier ); + + /* Check that the control packet type is 0xb0 (this must be done after the + * packet identifier is parsed). */ + if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) + { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "Bad control packet type 0x%02x.", + pUnsuback->type ); + + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + } + else + { + EMPTY_ELSE_MARKER; + } IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier, - uint8_t ** pPubackPacket, - size_t * pPacketSize ) +IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, + size_t * pPacketSize ) { - IotMqttError_t status = IOT_MQTT_SUCCESS; - - /* Allocate memory for PUBACK. */ - uint8_t * pBuffer = IotMqtt_MallocMessage( MQTT_PACKET_PUBACK_SIZE ); - - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for PUBACK packet" ); - - status = IOT_MQTT_NO_MEMORY; - } - else + /* PINGREQ packets are always the same. */ + static const uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = { - /* Set the output parameters. The remainder of this function always succeeds. */ - *pPubackPacket = pBuffer; - *pPacketSize = MQTT_PACKET_PUBACK_SIZE; + MQTT_PACKET_TYPE_PINGREQ, + 0x00 + }; - /* Set the 4 bytes in PUBACK. */ - pBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBACK; - pBuffer[ 1 ] = MQTT_PACKET_PUBACK_REMAINING_LENGTH; - pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetIdentifier ); - pBuffer[ 3 ] = UINT16_LOW_BYTE( packetIdentifier ); + /* Set the output parameters. */ + *pPingreqPacket = ( uint8_t * ) pPingreq; + *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; - /* Print out the serialized PUBACK packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PUBACK packet:", *pPubackPacket, MQTT_PACKET_PUBACK_SIZE ); - } + /* Print out the PINGREQ packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, MQTT_PACKET_PINGREQ_SIZE ); - return status; + return IOT_MQTT_SUCCESS; } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) +IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - /* Check the "Remaining length" of the received PUBACK. */ - if( pPuback->remainingLength != MQTT_PACKET_PUBACK_REMAINING_LENGTH ) + /* Check that the control packet type is 0xd0. */ + if( pPingresp->type != MQTT_PACKET_TYPE_PINGRESP ) { IotLog( IOT_LOG_ERROR, &_logHideAll, - "PUBACK does not have remaining length of %d.", - MQTT_PACKET_PUBACK_REMAINING_LENGTH ); + "Bad control packet type 0x%02x.", + pPingresp->type ); IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } @@ -1445,16 +2060,14 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) EMPTY_ELSE_MARKER; } - /* Extract the packet identifier (third and fourth bytes) from PUBACK. */ - pPuback->packetIdentifier = UINT16_DECODE( pPuback->pRemainingData ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pPuback->packetIdentifier ); - - /* Packet identifier cannot be 0. */ - if( pPuback->packetIdentifier == 0 ) + /* Check the "Remaining length" (second byte) of the received PINGRESP. */ + if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) { + IotLog( IOT_LOG_ERROR, + &_logHideAll, + "PINGRESP does not have remaining length of %d.", + MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); } else @@ -1462,49 +2075,87 @@ IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback ) EMPTY_ELSE_MARKER; } - /* Check that the control packet type is 0x40 (this must be done after the - * packet identifier is parsed). */ - if( pPuback->type != MQTT_PACKET_TYPE_PUBACK ) + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} + +/*-----------------------------------------------------------*/ + +IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, + size_t * pPacketSize ) +{ + /* DISCONNECT packets are always the same. */ + static const uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pPuback->type ); + MQTT_PACKET_TYPE_DISCONNECT, + 0x00 + }; - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + /* Set the output parameters. */ + *pDisconnectPacket = ( uint8_t * ) pDisconnect; + *pPacketSize = MQTT_PACKET_DISCONNECT_SIZE; + + /* Print out the DISCONNECT packet for debugging purposes. */ + IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, MQTT_PACKET_DISCONNECT_SIZE ); + + return IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + +void _IotMqtt_FreePacket( uint8_t * pPacket ) +{ + uint8_t packetType = *pPacket; + + /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static + * memory. */ + if( packetType != MQTT_PACKET_TYPE_DISCONNECT ) + { + if( packetType != MQTT_PACKET_TYPE_PINGREQ ) + { + IotMqtt_FreeMessage( pPacket ); + } + else + { + EMPTY_ELSE_MARKER; + } } else { EMPTY_ELSE_MARKER; } - - IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pSubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ) +/* Public interface functions for serialization */ + +/*-----------------------------------------------------------*/ + +IotMqttError_t IotMqtt_GetConnectPacketSize( const IotMqttConnectInfo_t * pConnectInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - size_t i = 0, subscribePacketSize = 0, remainingLength = 0; - uint16_t packetIdentifier = 0; - uint8_t * pBuffer = NULL; + + if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + IotLogError( "IotMqtt_GetConnectPacketSize() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + + if( ( pConnectInfo->clientIdentifierLength == 0 ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_GetConnectPacketSize() client identifier must be set." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _subscriptionPacketSize( IOT_MQTT_SUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &subscribePacketSize ) == false ) + if( _connectPacketSize( pConnectInfo, pRemainingLength, pPacketSize ) == false ) { - IotLogError( "Subscribe packet remaining length exceeds %lu, which is the " - "maximum size allowed by MQTT 3.1.1.", - MQTT_MAX_REMAINING_LENGTH ); + IotLogError( "Connect packet length exceeds %lu, which is the maximum" + " size allowed by MQTT 3.1.1.", + MQTT_PACKET_CONNECT_MAX_SIZE ); IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } @@ -1515,191 +2166,173 @@ IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubsc /* Total size of the subscribe packet should be larger than the "Remaining length" * field. */ - IotMqtt_Assert( subscribePacketSize > remainingLength ); - - /* Allocate memory to hold the SUBSCRIBE packet. */ - pBuffer = IotMqtt_MallocMessage( subscribePacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for SUBSCRIBE packet." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); - } - else + if( ( *pPacketSize ) < ( *pRemainingLength ) ) { - EMPTY_ELSE_MARKER; + IotLogError( "Connection packet remaining length (%lu) exceeds packet size (%lu)", + ( *pRemainingLength ), ( *pPacketSize ) ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Set the output parameters. The remainder of this function always succeeds. */ - *pSubscribePacket = pBuffer; - *pPacketSize = subscribePacketSize; - - /* The first byte in SUBSCRIBE is the packet type. */ - *pBuffer = MQTT_PACKET_TYPE_SUBSCRIBE; - pBuffer++; - - /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _nextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0 ); +/*-----------------------------------------------------------*/ - /* Place the packet identifier into the SUBSCRIBE packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; +IotMqttError_t IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo, + size_t remainingLength, + uint8_t * pBuffer, + size_t bufferSize ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - /* Serialize each subscription topic filter and QoS. */ - for( i = 0; i < subscriptionCount; i++ ) + if( ( pBuffer == NULL ) || ( pConnectInfo == NULL ) ) { - pBuffer = _encodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + IotLogError( "IotMqtt_SerializeConnect() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - /* Place the QoS in the SUBSCRIBE packet. */ - *pBuffer = ( uint8_t ) ( pSubscriptionList[ i ].qos ); - pBuffer++; + if( ( pConnectInfo->clientIdentifierLength == 0 ) || ( pConnectInfo->pClientIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_SerializeConnect() client identifier must be set." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Ensure that the difference between the end and beginning of the buffer - * is equal to subscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - *pSubscribePacket ) == subscribePacketSize ); + if( remainingLength > bufferSize ) + { + IotLogError( " Serialize Connect packet remaining length (%lu) exceeds buffer size (%lu)", + remainingLength, bufferSize ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - /* Print out the serialized SUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT SUBSCRIBE packet:", *pSubscribePacket, subscribePacketSize ); + _serializeConnect( pConnectInfo, + remainingLength, + pBuffer, + bufferSize ); IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) +IotMqttError_t IotMqtt_GetSubscriptionPacketSize( IotMqttOperationType_t type, + const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t * pRemainingLength, + size_t * pPacketSize ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - size_t i = 0, remainingLength = pSuback->remainingLength; - uint8_t subscriptionStatus = 0; - const uint8_t * pVariableHeader = pSuback->pRemainingData; - /* A SUBACK must have a remaining length of at least 3 to accommodate the - * packet identifer and at least 1 return code. */ - if( remainingLength < 3 ) + if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) { - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "SUBACK cannot have a remaining length less than 3." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - else + + if( ( type != IOT_MQTT_SUBSCRIBE ) && ( type != IOT_MQTT_UNSUBSCRIBE ) ) { - EMPTY_ELSE_MARKER; + IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with unknown type." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */ - pSuback->packetIdentifier = UINT16_DECODE( pVariableHeader ); - - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pSuback->packetIdentifier ); - - /* Check that the control packet type is 0x90 (this must be done after the - * packet identifier is parsed). */ - if( pSuback->type != MQTT_PACKET_TYPE_SUBACK ) + if( subscriptionCount == 0 ) { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pSuback->type ); + IotLogError( "IotMqtt_GetSubscriptionPacketSize() called with zero subscription count." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + if( _subscriptionPacketSize( type, + pSubscriptionList, + subscriptionCount, + pRemainingLength, + pPacketSize ) == false ) + { + IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " + "maximum size allowed by MQTT 3.1.1.", + MQTT_MAX_REMAINING_LENGTH ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } else { EMPTY_ELSE_MARKER; } - /* Iterate through each status byte in the SUBACK packet. */ - for( i = 0; i < remainingLength - sizeof( uint16_t ); i++ ) + /* Total size of the subscribe packet should be larger than the "Remaining length" + * field. */ + if( ( *pPacketSize ) < ( *pRemainingLength ) ) { - /* Read a single status byte in SUBACK. */ - subscriptionStatus = *( pVariableHeader + sizeof( uint16_t ) + i ); - - /* MQTT 3.1.1 defines the following values as status codes. */ - switch( subscriptionStatus ) - { - case 0x00: - case 0x01: - case 0x02: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu accepted, max QoS %hhu.", - ( unsigned long ) i, subscriptionStatus ); - break; - - case 0x80: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Topic filter %lu refused.", ( unsigned long ) i ); - - /* Remove a rejected subscription from the subscription manager. */ - _IotMqtt_RemoveSubscriptionByPacket( pSuback->u.pMqttConnection, - pSuback->packetIdentifier, - ( int32_t ) i ); + IotLogError( "Subscription packet remaining length (%lu) exceeds packet size (%lu)", + ( *pRemainingLength ), ( *pPacketSize ) ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - status = IOT_MQTT_SERVER_REFUSED; + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - break; +/*-----------------------------------------------------------*/ - default: - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Bad SUBSCRIBE status %hhu.", subscriptionStatus ); +IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t bufferSize ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - status = IOT_MQTT_BAD_RESPONSE; + if( ( pBuffer == NULL ) || ( pSubscriptionList == NULL ) || ( pPacketIdentifier == NULL ) ) + { + IotLogError( "IotMqtt_SerializeSubscribe() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - break; - } + if( subscriptionCount == 0 ) + { + IotLogError( "IotMqtt_SerializeSubscribe() called with zero subscription count." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - /* Stop parsing the subscription statuses if a bad response was received. */ - if( status == IOT_MQTT_BAD_RESPONSE ) - { - break; - } - else - { - EMPTY_ELSE_MARKER; - } + if( remainingLength > bufferSize ) + { + IotLogError( " Subscribe packet remaining length (%lu) exceeds buffer size (%lu).", + remainingLength, bufferSize ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } + _serializeSubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, - size_t subscriptionCount, - uint8_t ** pUnsubscribePacket, - size_t * pPacketSize, - uint16_t * pPacketIdentifier ) +IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo, + size_t * pRemainingLength, + size_t * pPacketSize ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - size_t i = 0, unsubscribePacketSize = 0, remainingLength = 0; - uint16_t packetIdentifier = 0; - uint8_t * pBuffer = NULL; + + if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) ) + { + IotLogError( "IotMqtt_GetPublishPacketSize() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + + if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) + { + IotLogError( "IotMqtt_GetPublishPacketSize() called with no topic." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } /* Calculate the "Remaining length" field and total packet size. If it exceeds * what is allowed in the MQTT standard, return an error. */ - if( _subscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, - pSubscriptionList, - subscriptionCount, - &remainingLength, - &unsubscribePacketSize ) == false ) + if( _publishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false ) { - IotLogError( "Unsubscribe packet remaining length exceeds %lu, which is the " + IotLogError( "Publish packet remaining length exceeds %lu, which is the " "maximum size allowed by MQTT 3.1.1.", MQTT_MAX_REMAINING_LENGTH ); @@ -1710,177 +2343,196 @@ IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSub EMPTY_ELSE_MARKER; } - /* Total size of the unsubscribe packet should be larger than the "Remaining length" + /* Total size of the publish packet should be larger than the "Remaining length" * field. */ - IotMqtt_Assert( unsubscribePacketSize > remainingLength ); - - /* Allocate memory to hold the UNSUBSCRIBE packet. */ - pBuffer = IotMqtt_MallocMessage( unsubscribePacketSize ); - - /* Check that sufficient memory was allocated. */ - if( pBuffer == NULL ) - { - IotLogError( "Failed to allocate memory for UNSUBSCRIBE packet." ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_NO_MEMORY ); - } - else + if( ( *pPacketSize ) < ( *pRemainingLength ) ) { - EMPTY_ELSE_MARKER; + IotLogError( "Publish packet remaining length (%lu) exceeds packet size (%lu).", + ( *pRemainingLength ), ( *pPacketSize ) ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Set the output parameters. The remainder of this function always succeeds. */ - *pUnsubscribePacket = pBuffer; - *pPacketSize = unsubscribePacketSize; - - /* The first byte in UNSUBSCRIBE is the packet type. */ - *pBuffer = MQTT_PACKET_TYPE_UNSUBSCRIBE; - pBuffer++; + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - /* Encode the "Remaining length" starting from the second byte. */ - pBuffer = _encodeRemainingLength( pBuffer, remainingLength ); +/*-----------------------------------------------------------*/ - /* Get the next packet identifier. It should always be nonzero. */ - packetIdentifier = _nextPacketIdentifier(); - *pPacketIdentifier = packetIdentifier; - IotMqtt_Assert( packetIdentifier != 0 ); +IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t ** pPacketIdentifierHigh, + uint8_t * pBuffer, + size_t bufferSize ) - /* Place the packet identifier into the UNSUBSCRIBE packet. */ - *pBuffer = UINT16_HIGH_BYTE( packetIdentifier ); - *( pBuffer + 1 ) = UINT16_LOW_BYTE( packetIdentifier ); - pBuffer += 2; +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - /* Serialize each subscription topic filter. */ - for( i = 0; i < subscriptionCount; i++ ) + if( ( pBuffer == NULL ) || ( pPublishInfo == NULL ) || ( pPacketIdentifier == NULL ) ) { - pBuffer = _encodeString( pBuffer, - pSubscriptionList[ i ].pTopicFilter, - pSubscriptionList[ i ].topicFilterLength ); + IotLogError( "IotMqtt_SerializePublish() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Ensure that the difference between the end and beginning of the buffer - * is equal to unsubscribePacketSize, i.e. pBuffer did not overflow. */ - IotMqtt_Assert( ( size_t ) ( pBuffer - *pUnsubscribePacket ) == unsubscribePacketSize ); + if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0 ) ) + { + IotLogError( "IotMqtt_SerializePublish() called with no topic." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - /* Print out the serialized UNSUBSCRIBE packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT UNSUBSCRIBE packet:", *pUnsubscribePacket, unsubscribePacketSize ); + if( remainingLength > bufferSize ) + { + IotLogError( "Publish packet remaining length (%lu) exceeds buffer size (%lu).", + remainingLength, bufferSize ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + _serializePublish( pPublishInfo, + remainingLength, + pPacketIdentifier, + pPacketIdentifierHigh, + pBuffer, + bufferSize ); IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback ) +IotMqttError_t IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList, + size_t subscriptionCount, + size_t remainingLength, + uint16_t * pPacketIdentifier, + uint8_t * pBuffer, + size_t bufferSize ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); - /* Check the "Remaining length" (second byte) of the received UNSUBACK. */ - if( pUnsuback->remainingLength != MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "UNSUBACK does not have remaining length of %d.", - MQTT_PACKET_UNSUBACK_REMAINING_LENGTH ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else + if( ( pBuffer == NULL ) || ( pPacketIdentifier == NULL ) || ( pSubscriptionList == NULL ) ) { - EMPTY_ELSE_MARKER; + IotLogError( "IotMqtt_SerializeUnsubscribe() called with required parameter(s) set to NULL." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Extract the packet identifier (third and fourth bytes) from UNSUBACK. */ - pUnsuback->packetIdentifier = UINT16_DECODE( pUnsuback->pRemainingData ); - - /* Packet identifier cannot be 0. */ - if( pUnsuback->packetIdentifier == 0 ) + if( subscriptionCount == 0 ) { - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IotLogError( "IotMqtt_SerializeUnsubscribe() called with zero subscription count." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - else + + if( remainingLength > bufferSize ) { - EMPTY_ELSE_MARKER; + IotLogError( "Unsubscribe packet remaining length (%lu) exceeds buffer size (%lu).", + remainingLength, bufferSize ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - IotLog( IOT_LOG_DEBUG, - &_logHideAll, - "Packet identifier %hu.", pUnsuback->packetIdentifier ); + _serializeUnsubscribe( pSubscriptionList, + subscriptionCount, + remainingLength, + pPacketIdentifier, + pBuffer, + bufferSize ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); +} - /* Check that the control packet type is 0xb0 (this must be done after the - * packet identifier is parsed). */ - if( pUnsuback->type != MQTT_PACKET_TYPE_UNSUBACK ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pUnsuback->type ); +/*-----------------------------------------------------------*/ - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); +IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, + size_t bufferSize ) +{ + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + uint8_t * pDisconnectPacket = NULL; + size_t remainingLength = 0; + + if( pBuffer == NULL ) + { + IotLogError( "IotMqtt_SerializeDisconnect() called with NULL buffer pointer." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - else + + if( bufferSize < MQTT_PACKET_DISCONNECT_SIZE ) { - EMPTY_ELSE_MARKER; + IotLogError( "Disconnect packet length (%lu) exceeds buffer size (%lu).", + MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } + /* Call internal function with local variables, as disconnect uses + * static memory, there is no need to pass the buffer + * Note: _IotMqtt_SerializeDisconnect always succeeds */ + _IotMqtt_SerializeDisconnect( &pDisconnectPacket, &remainingLength ); + + memcpy( pBuffer, pDisconnectPacket, MQTT_PACKET_DISCONNECT_SIZE ); IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket, - size_t * pPacketSize ) +IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, + size_t bufferSize ) { - /* PINGREQ packets are always the same. */ - static const uint8_t pPingreq[ MQTT_PACKET_PINGREQ_SIZE ] = - { - MQTT_PACKET_TYPE_PINGREQ, - 0x00 - }; + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + uint8_t * pPingreqPacket = NULL; + size_t packetSize = 0; - /* Set the output parameters. */ - *pPingreqPacket = ( uint8_t * ) pPingreq; - *pPacketSize = MQTT_PACKET_PINGREQ_SIZE; + if( pBuffer == NULL ) + { + IotLogError( "IotMqtt_SerializePingreq() called with NULL buffer pointer." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - /* Print out the PINGREQ packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT PINGREQ packet:", pPingreq, MQTT_PACKET_PINGREQ_SIZE ); + if( bufferSize < MQTT_PACKET_PINGREQ_SIZE ) + { + IotLogError( "Pingreq length (%lu) exceeds buffer size (%lu).", + MQTT_PACKET_DISCONNECT_SIZE, bufferSize ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - return IOT_MQTT_SUCCESS; + /* Call internal function with local variables, as ping request uses + * static memory, there is no need to pass the buffer + * Note: _IotMqtt_SerializePingReq always succeeds */ + _IotMqtt_SerializePingreq( &pPingreqPacket, &packetSize ); + memcpy( pBuffer, pPingreqPacket, MQTT_PACKET_PINGREQ_SIZE ); + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) +IotMqttError_t IotMqtt_DeserializePublish( IotMqttPacketInfo_t * pMqttPacket ) { IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + /* Internal MQTT packet structure */ + _mqttPacket_t mqttPacket; + /* Internal MQTT operation structure needed for deserializing publish */ + _mqttOperation_t mqttOperation; - /* Check that the control packet type is 0xd0. */ - if( pPingresp->type != MQTT_PACKET_TYPE_PINGRESP ) + if( pMqttPacket == NULL ) { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "Bad control packet type 0x%02x.", - pPingresp->type ); - - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); + IotLogError( "IotMqtt_DeserializePublish()called with NULL pMqttPacket pointer." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - else + + if( ( pMqttPacket->type & 0xf0 ) != MQTT_PACKET_TYPE_PUBLISH ) { - EMPTY_ELSE_MARKER; + IotLogError( "IotMqtt_DeserializePublish() called with incorrect packet type:(%lu).", pMqttPacket->type ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); } - /* Check the "Remaining length" (second byte) of the received PINGRESP. */ - if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) - { - IotLog( IOT_LOG_ERROR, - &_logHideAll, - "PINGRESP does not have remaining length of %d.", - MQTT_PACKET_PINGRESP_REMAINING_LENGTH ); + /* Set internal mqtt packet parameters. */ + memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); + mqttPacket.pRemainingData = pMqttPacket->pRemainingData; + mqttPacket.remainingLength = pMqttPacket->remainingLength; + mqttPacket.type = pMqttPacket->type; - IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_RESPONSE ); - } - else + /* Set Publish specific parameters */ + memset( ( void * ) &mqttOperation, 0x00, sizeof( _mqttOperation_t ) ); + mqttOperation.incomingPublish = true; + mqttPacket.u.pIncomingPublish = &mqttOperation; + status = _IotMqtt_DeserializePublish( &mqttPacket ); + + if( status == IOT_MQTT_SUCCESS ) { - EMPTY_ELSE_MARKER; + pMqttPacket->pubInfo = mqttOperation.u.publish.publishInfo; + pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } IOT_FUNCTION_EXIT_NO_CLEANUP(); @@ -1888,49 +2540,65 @@ IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp ) /*-----------------------------------------------------------*/ -IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket, - size_t * pPacketSize ) +IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) { - /* DISCONNECT packets are always the same. */ - static const uint8_t pDisconnect[ MQTT_PACKET_DISCONNECT_SIZE ] = + IOT_FUNCTION_ENTRY( IotMqttError_t, IOT_MQTT_SUCCESS ); + /* Internal MQTT packet structure */ + _mqttPacket_t mqttPacket; + + if( ( pMqttPacket == NULL ) || ( pMqttPacket->pRemainingData == NULL ) ) { - MQTT_PACKET_TYPE_DISCONNECT, - 0x00 - }; + IotLogError( "IotMqtt_DeserializeResponse() called with NULL pMqttPacket pointer or NULL pRemainingLength." ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } - /* Set the output parameters. */ - *pDisconnectPacket = ( uint8_t * ) pDisconnect; - *pPacketSize = MQTT_PACKET_DISCONNECT_SIZE; + /* Set internal mqtt packet parameters. */ + memset( ( void * ) &mqttPacket, 0x00, sizeof( _mqttPacket_t ) ); - /* Print out the DISCONNECT packet for debugging purposes. */ - IotLog_PrintBuffer( "MQTT DISCONNECT packet:", pDisconnect, MQTT_PACKET_DISCONNECT_SIZE ); + mqttPacket.pRemainingData = pMqttPacket->pRemainingData; + mqttPacket.remainingLength = pMqttPacket->remainingLength; + mqttPacket.type = pMqttPacket->type; - return IOT_MQTT_SUCCESS; -} + /* Call internal deserialize */ + switch( pMqttPacket->type & 0xf0 ) + { + case MQTT_PACKET_TYPE_CONNACK: + status = _IotMqtt_DeserializeConnack( &mqttPacket ); + break; -/*-----------------------------------------------------------*/ + case MQTT_PACKET_TYPE_PUBACK: + status = _IotMqtt_DeserializePuback( &mqttPacket ); + break; -void _IotMqtt_FreePacket( uint8_t * pPacket ) -{ - uint8_t packetType = *pPacket; + case MQTT_PACKET_TYPE_SUBACK: + status = _IotMqtt_DeserializeSuback( &mqttPacket ); + break; - /* Don't call free on DISCONNECT and PINGREQ; those are allocated from static - * memory. */ - if( packetType != MQTT_PACKET_TYPE_DISCONNECT ) + case MQTT_PACKET_TYPE_UNSUBACK: + status = _IotMqtt_DeserializeUnsuback( &mqttPacket ); + break; + + case MQTT_PACKET_TYPE_PINGRESP: + status = _IotMqtt_DeserializePingresp( &mqttPacket ); + break; + + /* Any other packet type is invalid. */ + default: + IotLogError( "IotMqtt_DeserializeResponse() called with unknown packet type:(%lu).", pMqttPacket->type ); + IOT_SET_AND_GOTO_CLEANUP( IOT_MQTT_BAD_PARAMETER ); + } + + if( status != IOT_MQTT_SUCCESS ) { - if( packetType != MQTT_PACKET_TYPE_PINGREQ ) - { - IotMqtt_FreeMessage( pPacket ); - } - else - { - EMPTY_ELSE_MARKER; - } + IOT_SET_AND_GOTO_CLEANUP( status ); } else { - EMPTY_ELSE_MARKER; + /* set packetIdentfier only if success is returned */ + pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } + + IOT_FUNCTION_EXIT_NO_CLEANUP(); } /*-----------------------------------------------------------*/ diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index 596d3b5ba9..fec980ad14 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -321,7 +321,7 @@ typedef struct _mqttOperation /* Pointers to neighboring queue elements. */ IotLink_t link; /**< @brief List link member. */ - bool incomingPublish; /**< @brief Set to true if this operation an incoming PUBLISH. */ + bool incomingPublish; /**< @brief Set to true if this operation is an incoming PUBLISH. */ struct _mqttConnection * pMqttConnection; /**< @brief MQTT connection associated with this operation. */ IotTaskPoolJobStorage_t jobStorage; /**< @brief Task pool job storage associated with this operation. */ @@ -553,6 +553,24 @@ uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection, size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, const IotNetworkInterface_t * pNetworkInterface ); +/** + * @brief Get the remaining length from a stream of bytes off the network. + * + * @param[in] pNetworkConnection Reference to the network connection. + * @param[in] getNextByte Function pointer used to interact with the + * network to get next byte. + * + * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error. + * + * @note This function is similar to _IotMqtt_GetRemainingLength() but it uses + * user provided getNextByte function to parse the stream instead of using + * _IotMqtt_GetNextByte(). pNetworkConnection is impelementation dependent and + * user provided function makes use of it. + * + */ +size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, + IotMqttGetNextByte_t getNextByte ); + /** * @brief Generate a CONNECT packet from the given parameters. * From 4beb981d81d82a5a069f6dc8ae0a5581586ae873 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Thu, 14 Nov 2019 12:09:28 -0500 Subject: [PATCH 323/844] Aws parser (#632) --- demos/CMakeLists.txt | 4 +- demos/src/aws_iot_demo_jobs.c | 76 +++-- demos/src/aws_iot_demo_shadow.c | 86 +++-- libraries/aws/common/CMakeLists.txt | 34 +- .../aws/common/include/aws_iot_doc_parser.h | 69 ++++ .../common/src/aws_iot_doc_parser.c} | 127 +++---- libraries/aws/common/src/aws_iot_parser.c | 16 +- .../common/test/aws_iot_tests_common.c} | 40 ++- .../test/unit/aws_iot_tests_doc_parser.c | 317 ++++++++++++++++++ .../aws/jobs/src/aws_iot_jobs_serialize.c | 14 +- .../test/system/aws_iot_tests_jobs_system.c | 60 ++-- .../test/unit/aws_iot_tests_jobs_serialize.c | 300 ++++++++--------- .../aws/shadow/src/aws_iot_shadow_parser.c | 28 +- .../test/system/aws_iot_tests_shadow_system.c | 86 ++--- .../test/unit/aws_iot_tests_shadow_parser.c | 164 --------- libraries/standard/serializer/CMakeLists.txt | 7 +- 16 files changed, 871 insertions(+), 557 deletions(-) create mode 100644 libraries/aws/common/include/aws_iot_doc_parser.h rename libraries/{standard/serializer/src/iot_json_utils.c => aws/common/src/aws_iot_doc_parser.c} (64%) rename libraries/{standard/serializer/include/iot_json_utils.h => aws/common/test/aws_iot_tests_common.c} (57%) create mode 100644 libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 8fda1c7dd3..cd8aa43a60 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -38,9 +38,9 @@ set( DEMO_SOURCES src/aws_iot_demo_jobs.c ) set( DEMO_LIBRARY_DEPENDENCY iotmqtt - "awsiotshadow\\\;iotserializer" + "awsiotshadow\\\;awsiotcommon" awsiotdefender - "awsiotjobs\\\;iotserializer" ) + "awsiotjobs\\\;awsiotcommon" ) # Get the list length to iterate over it. Since CMake indexes from 0, subtract # 1 for the iteration range. diff --git a/demos/src/aws_iot_demo_jobs.c b/demos/src/aws_iot_demo_jobs.c index 3fb342cd7a..d9b5f92dab 100644 --- a/demos/src/aws_iot_demo_jobs.c +++ b/demos/src/aws_iot_demo_jobs.c @@ -46,7 +46,7 @@ #include "aws_iot_jobs.h" /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" /* Atomics include. */ #include "iot_atomic.h" @@ -56,14 +56,14 @@ /** * @brief The timeout for Jobs and MQTT operations in this demo. */ -#define TIMEOUT_MS ( ( uint32_t ) 5000u ) +#define TIMEOUT_MS ( ( uint32_t ) 5000u ) /** * @brief The keep-alive interval used for this demo. * * An MQTT ping request will be sent periodically at this interval. */ -#define KEEP_ALIVE_SECONDS ( ( uint16_t ) 60u ) +#define KEEP_ALIVE_SECONDS ( ( uint16_t ) 60u ) /** * @brief The JSON key of the Job ID. @@ -72,12 +72,12 @@ * All Job documents will contain this key, whose value represents the unique * identifier of a Job. */ -#define JOB_ID_KEY "jobId" +#define JOB_ID_KEY "jobId" /** * @brief The length of #JOB_ID_KEY. */ -#define JOB_ID_KEY_LENGTH ( sizeof( JOB_ID_KEY ) - 1 ) +#define JOB_ID_KEY_LENGTH ( sizeof( JOB_ID_KEY ) - 1 ) /** * @brief The JSON key of the Job document. @@ -86,12 +86,12 @@ * All Job documents will contain this key, whose value is an application-specific * JSON document. */ -#define JOB_DOC_KEY "jobDocument" +#define JOB_DOC_KEY "jobDocument" /** * @brief The length of #JOB_DOC_KEY. */ -#define JOB_DOC_KEY_LENGTH ( sizeof( JOB_DOC_KEY ) - 1 ) +#define JOB_DOC_KEY_LENGTH ( sizeof( JOB_DOC_KEY ) - 1 ) /** * @brief The JSON key whose value represents the action this demo should take. @@ -99,12 +99,12 @@ * This demo program expects this key to be in the Job document. It is a key * specific to this demo. */ -#define JOB_ACTION_KEY "action" +#define JOB_ACTION_KEY "action" /** * @brief The length of #JOB_ACTION_KEY. */ -#define JOB_ACTION_KEY_LENGTH ( sizeof( JOB_ACTION_KEY ) - 1 ) +#define JOB_ACTION_KEY_LENGTH ( sizeof( JOB_ACTION_KEY ) - 1 ) /** * @brief A message associated with the Job action. @@ -113,12 +113,12 @@ * is either "publish" or "print". It represents the message that should be * published or printed, respectively. */ -#define JOB_MESSAGE_KEY "message" +#define JOB_MESSAGE_KEY "message" /** * @brief The length of #JOB_MESSAGE_KEY. */ -#define JOB_MESSAGE_KEY_LENGTH ( sizeof( JOB_MESSAGE_KEY ) - 1 ) +#define JOB_MESSAGE_KEY_LENGTH ( sizeof( JOB_MESSAGE_KEY ) - 1 ) /** * @brief An MQTT topic associated with the Job "publish" action. @@ -127,12 +127,12 @@ * is "publish". It represents the MQTT topic on which the message should be * published. */ -#define JOB_TOPIC_KEY "topic" +#define JOB_TOPIC_KEY "topic" /** * @brief The length of #JOB_TOPIC_KEY. */ -#define JOB_TOPIC_KEY_LENGTH ( sizeof( JOB_TOPIC_KEY ) - 1 ) +#define JOB_TOPIC_KEY_LENGTH ( sizeof( JOB_TOPIC_KEY ) - 1 ) /** * @brief The minimum length of a string in a JSON Job document. @@ -140,7 +140,7 @@ * At the very least the Job ID must have the quotes that identify it as a JSON * string and 1 character for the string itself (the string must not be empty). */ -#define JSON_STRING_MIN_LENGTH ( ( size_t ) 3 ) +#define JSON_STRING_MIN_LENGTH ( ( size_t ) 3 ) /** * @brief The maximum length of a Job ID. @@ -150,20 +150,20 @@ * * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html#job-limits */ -#define JOB_ID_MAX_LENGTH ( ( size_t ) 64 ) +#define JOB_ID_MAX_LENGTH ( ( size_t ) 64 ) /** * @brief A value passed as context to #_operationCompleteCallback to specify that * it should set the #JOBS_DEMO_FINISHED flag. */ -#define JOBS_DEMO_SHOULD_EXIT ( ( void * ) ( ( intptr_t ) 1 ) ) +#define JOBS_DEMO_SHOULD_EXIT ( ( void * ) ( ( intptr_t ) 1 ) ) /** * @brief Flag value for signaling that the demo is still running. * * The initial value of #_exitFlag. */ -#define JOBS_DEMO_RUNNING ( ( uint32_t ) 0 ) +#define JOBS_DEMO_RUNNING ( ( uint32_t ) 0 ) /** * @brief Flag value for signaling that the demo is finished. @@ -171,7 +171,7 @@ * #_exitFlag will be set to this when a Job document with { "action": "exit" } * is received. */ -#define JOBS_DEMO_FINISHED ( ( uint32_t ) 1 ) +#define JOBS_DEMO_FINISHED ( ( uint32_t ) 1 ) /*-----------------------------------------------------------*/ @@ -364,12 +364,19 @@ static bool _getJsonString( const char * pJsonDoc, const char ** pValue, size_t * valueLength ) { - bool keyFound = IotJsonUtils_FindJsonValue( pJsonDoc, - jsonDocLength, - pKey, - keyLength, - pValue, - valueLength ); + /* + * Note: This parser used is specific for parsing AWS IoT document received + * through a mutually authenticated connection. This parser will not check + * for the correctness of the document as it is designed for low memory + * footprint rather than checking for correctness of the document. This + * parser is not meant to be used as a general purpose JSON parser. + */ + bool keyFound = AwsIotDocParser_FindValue( pJsonDoc, + jsonDocLength, + pKey, + keyLength, + pValue, + valueLength ); if( keyFound == true ) { @@ -659,13 +666,20 @@ static void _jobsCallback( void * pCallbackContext, } } - /* Get the Job document. */ - docKeyFound = IotJsonUtils_FindJsonValue( pCallbackInfo->u.callback.pDocument, - pCallbackInfo->u.callback.documentLength, - JOB_DOC_KEY, - JOB_DOC_KEY_LENGTH, - &pJobDoc, - &jobDocLength ); + /* Get the Job document. + * + * Note: This parser used is specific for parsing AWS IoT document received + * through a mutually authenticated connection. This parser will not check + * for the correctness of the document as it is designed for low memory + * footprint rather than checking for correctness of the document. This + * parser is not meant to be used as a general purpose JSON parser. + */ + docKeyFound = AwsIotDocParser_FindValue( pCallbackInfo->u.callback.pDocument, + pCallbackInfo->u.callback.documentLength, + JOB_DOC_KEY, + JOB_DOC_KEY_LENGTH, + &pJobDoc, + &jobDocLength ); /* When both the Job ID and Job document are available, process the Job. */ if( ( idKeyFound == true ) && ( docKeyFound == true ) ) diff --git a/demos/src/aws_iot_demo_shadow.c b/demos/src/aws_iot_demo_shadow.c index aee658a2e1..e352db2aca 100644 --- a/demos/src/aws_iot_demo_shadow.c +++ b/demos/src/aws_iot_demo_shadow.c @@ -50,7 +50,7 @@ #include "aws_iot_shadow.h" /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" /** * @cond DOXYGEN_IGNORE @@ -169,23 +169,37 @@ static bool _getDelta( const char * pDeltaDocument, const char * pState = NULL; size_t stateLength = 0; - /* Find the "state" key in the delta document. */ - stateFound = IotJsonUtils_FindJsonValue( pDeltaDocument, - deltaDocumentLength, - "state", - 5, - &pState, - &stateLength ); + /* Find the "state" key in the delta document. + * + * Note: This parser used is specific for parsing AWS IoT document received + * through a mutually authenticated connection. This parser will not check + * for the correctness of the document as it is designed for low memory + * footprint rather than checking for correctness of the document. This + * parser is not meant to be used as a general purpose JSON parser. + */ + stateFound = AwsIotDocParser_FindValue( pDeltaDocument, + deltaDocumentLength, + "state", + 5, + &pState, + &stateLength ); if( stateFound == true ) { - /* Find the delta key within the "state" section. */ - deltaFound = IotJsonUtils_FindJsonValue( pState, - stateLength, - pDeltaKey, - deltaKeyLength, - pDelta, - pDeltaLength ); + /* Find the delta key within the "state" section. + * + * Note: This parser used is specific for parsing AWS IoT document received + * through a mutually authenticated connection. This parser will not check + * for the correctness of the document as it is designed for low memory + * footprint rather than checking for correctness of the document. This + * parser is not meant to be used as a general purpose JSON parser. + */ + deltaFound = AwsIotDocParser_FindValue( pState, + stateLength, + pDeltaKey, + deltaKeyLength, + pDelta, + pDeltaLength ); } else { @@ -220,23 +234,37 @@ static bool _getUpdatedState( const char * pUpdatedDocument, const char * pSection = NULL; size_t sectionLength = 0; - /* Find the given section in the updated document. */ - sectionFound = IotJsonUtils_FindJsonValue( pUpdatedDocument, - updatedDocumentLength, - pSectionKey, - sectionKeyLength, - &pSection, - §ionLength ); + /* Find the given section in the updated document. + * + * Note: This parser used is specific for parsing AWS IoT document received + * through a mutually authenticated connection. This parser will not check + * for the correctness of the document as it is designed for low memory + * footprint rather than checking for correctness of the document. This + * parser is not meant to be used as a general purpose JSON parser. + */ + sectionFound = AwsIotDocParser_FindValue( pUpdatedDocument, + updatedDocumentLength, + pSectionKey, + sectionKeyLength, + &pSection, + §ionLength ); if( sectionFound == true ) { - /* Find the "state" key within the "previous" or "current" section. */ - stateFound = IotJsonUtils_FindJsonValue( pSection, - sectionLength, - "state", - 5, - pState, - pStateLength ); + /* Find the "state" key within the "previous" or "current" section. + * + * Note: This parser used is specific for parsing AWS IoT document received + * through a mutually authenticated connection. This parser will not check + * for the correctness of the document as it is designed for low memory + * footprint rather than checking for correctness of the document. This + * parser is not meant to be used as a general purpose JSON parser. + */ + stateFound = AwsIotDocParser_FindValue( pSection, + sectionLength, + "state", + 5, + pState, + pStateLength ); } else { diff --git a/libraries/aws/common/CMakeLists.txt b/libraries/aws/common/CMakeLists.txt index 41d20eb866..86458417f6 100644 --- a/libraries/aws/common/CMakeLists.txt +++ b/libraries/aws/common/CMakeLists.txt @@ -1,5 +1,6 @@ # AWS IoT common library source files. set( AWS_IOT_COMMON_SOURCES + src/aws_iot_doc_parser.c src/aws_iot_operation.c src/aws_iot_parser.c src/aws_iot_subscription.c @@ -9,13 +10,14 @@ set( AWS_IOT_COMMON_SOURCES add_library( awsiotcommon ${CONFIG_HEADER_PATH}/iot_config.h ${AWS_IOT_COMMON_SOURCES} - include/aws_iot.h ) + include/aws_iot.h + include/aws_iot_doc_parser.h ) # AWS IoT common public include path. target_include_directories( awsiotcommon PUBLIC include ) # Link required libraries. -target_link_libraries( awsiotcommon PRIVATE iotbase iotmqtt iotserializer ) +target_link_libraries( awsiotcommon PRIVATE iotbase iotmqtt ) # Link Unity test framework when building tests. if( ${IOT_BUILD_TESTS} ) @@ -25,5 +27,31 @@ endif() # Organization of AWS IoT common in folders. set_property( TARGET awsiotcommon PROPERTY FOLDER libraries/aws ) source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) -source_group( include FILES include/aws_iot.h ) +source_group( include FILES include/aws_iot.h include/aws_iotdoc_parser.h ) source_group( src FILES ${AWS_IOT_COMMON_SOURCES} ) + +# Build the test executable if needed. +if( ${IOT_BUILD_TESTS} ) + # AWS IoT Common unit test sources. + set( AWS_IOT_COMMON_UNIT_TEST_SOURCES + test/unit/aws_iot_tests_doc_parser.c ) + + # AWS IoT Common tests executable. + add_executable( aws_iot_tests_common + ${AWS_IOT_COMMON_UNIT_TEST_SOURCES} + test/aws_iot_tests_common.c + ${IOT_TEST_APP_SOURCE} + ${CONFIG_HEADER_PATH}/iot_config.h ) + + # Define the test to run. + target_compile_definitions( aws_iot_tests_common PRIVATE + -DRunTests=RunAwsIotCommonTests ) + + # AWS IoT Common tests library dependencies. + target_link_libraries( aws_iot_tests_common PRIVATE awsiotcommon iotbase unity ) + + # Organization of AWS IoT Common tests in folders. + set_property( TARGET aws_iot_tests_common PROPERTY FOLDER tests ) + source_group( unit FILES ${AWS_IOT_COMMON_UNIT_TEST_SOURCES} ) + source_group( "" FILES ${IOT_TEST_APP_SOURCE} test/aws_iot_tests_common.c ) +endif() \ No newline at end of file diff --git a/libraries/aws/common/include/aws_iot_doc_parser.h b/libraries/aws/common/include/aws_iot_doc_parser.h new file mode 100644 index 0000000000..bc07bef739 --- /dev/null +++ b/libraries/aws/common/include/aws_iot_doc_parser.h @@ -0,0 +1,69 @@ +/* + * AWS IoT Common V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_doc_parser.h + * @brief Parser for AWS IoT Services Documents. This is a JSON parser + * specifically designed to process and retrieve a value from a AWS IoT JSON + * document, used in AWS IoT libraries such as Shadow and Jobs. Given a key and + * a JSON document, AwsIotDocParser_FindValue() will find the first occurrence + * of the key and return its respective value. The design goal of this parser + * is to be light weight and to be of low memory footprint. However, it does + * not check the correctness of the JSON documents. Hence, this parser is not + * meant to be used for general purpose JSON parsing. + */ + +#ifndef AWS_IOT_DOC_PARSER_H_ +#define AWS_IOT_DOC_PARSER_H_ + +/* Standard includes. */ +#include +#include + +/** + * @brief Find a value for a key from a AWS IoT service JSON document. + * + * @warning The parsing will not check for the correctness of the JSON document. + * It is designed to be light weight and to be of low memory footprint rather + * than checking for the correctness of the JSON document. Hence this is not + * meant to be used for a general purpose JSON parsing. This is recommeded to + * be used only with mutually authenticated AWS IoT services such as Shadow and + * Jobs where the document will always be a well formatted JSON. + * + * @param[in] pAwsIotJsonDocument Pointer to AWS IoT Service JSON document. + * @param[in] awsIotJsonDocumentLength Length of AWS IoT Service JSON document. + * @param[in] pAwsIotJsonKey JSON key for finding the associated value. + * @param[in] awsIotJsonKeyLength Length of the JSON key. + * @param[out] pAwsIotJsonValue Pointer to the pointer of value found. + * @param[out] pAwsIotJsonValueLength Pointer to the length of the value found. + * + * @returns `true` if a value is found, `false` if a value cannot be found. If + * returns `false`, the values in out pointers will not be valid. + */ +bool AwsIotDocParser_FindValue( const char * pAwsIotJsonDocument, + size_t awsIotJsonDocumentLength, + const char * pAwsIotJsonKey, + size_t awsIotJsonKeyLength, + const char ** pAwsIotJsonValue, + size_t * pAwsIotJsonValueLength ); + +#endif /* ifndef AWS_IOT_DOC_PARSER_H_ */ diff --git a/libraries/standard/serializer/src/iot_json_utils.c b/libraries/aws/common/src/aws_iot_doc_parser.c similarity index 64% rename from libraries/standard/serializer/src/iot_json_utils.c rename to libraries/aws/common/src/aws_iot_doc_parser.c index 077db4b7d2..1256dd5f2d 100644 --- a/libraries/standard/serializer/src/iot_json_utils.c +++ b/libraries/aws/common/src/aws_iot_doc_parser.c @@ -1,5 +1,5 @@ /* - * IoT Serializer V1.1.0 + * AWS IoT Common V1.0.0 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -21,8 +21,8 @@ */ /** - * @file iot_json_utils.c - * @brief Implements the functions in iot_json_utils.h + * @file aws_iot_doc_parser.c + * @brief Implements the functions in aws_iot_doc_parser.h */ /* The config header is always included first. */ @@ -32,30 +32,31 @@ #include /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" #define IS_QUOTE( str, idx ) \ - ( str[ idx ] == '"' && ( idx == 0 || str[ idx - 1 ] != '\\' ) ) + ( ( str )[ ( idx ) ] == '"' && ( ( idx ) == 0 || ( str )[ ( idx ) - 1 ] != '\\' ) ) #define IS_WHITESPACE( str, idx ) \ - ( str[ idx ] == ' ' || str[ idx ] == '\n' || str[ idx ] == '\r' || str[ idx ] == '\t' ) + ( ( str )[ ( idx ) ] == ' ' || ( str )[ ( idx ) ] == '\n' || ( str )[ ( idx ) ] == '\r' || ( str )[ ( idx ) ] == '\t' ) /*-----------------------------------------------------------*/ -bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, - size_t jsonDocumentLength, - const char * pJsonKey, - size_t jsonKeyLength, - const char ** pJsonValue, - size_t * pJsonValueLength ) +bool AwsIotDocParser_FindValue( const char * pAwsIotJsonDocument, + size_t awsIotJsonDocumentLength, + const char * pAwsIotJsonKey, + size_t awsIotJsonKeyLength, + const char ** pAwsIotJsonValue, + size_t * pAwsIotJsonValueLength ) { size_t i = 0; size_t jsonValueLength = 0; char openCharacter = '\0', closeCharacter = '\0'; int nestingLevel = 0; + bool isWithinQuotes = false; - /* Validate all the arguements.*/ - if( ( pJsonDocument == NULL ) || ( pJsonKey == NULL ) || - ( jsonDocumentLength == 0 ) || ( jsonKeyLength == 0 ) ) + /* Validate all the arguments.*/ + if( ( pAwsIotJsonDocument == NULL ) || ( pAwsIotJsonKey == NULL ) || + ( awsIotJsonDocumentLength == 0 ) || ( awsIotJsonKeyLength == 0 ) ) { return false; } @@ -63,7 +64,7 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, /* Ensure the JSON document is long enough to contain the key/value pair. At * the very least, a JSON key/value pair must contain the key and the 6 * characters {":""} */ - if( jsonDocumentLength < jsonKeyLength + 6 ) + if( awsIotJsonDocumentLength < awsIotJsonKeyLength + 6 ) { return false; } @@ -71,30 +72,30 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, /* Search the characters in the JSON document for the key. The end of the JSON * document does not have to be searched once too few characters remain to hold a * value. */ - while( i < jsonDocumentLength - jsonKeyLength - 3 ) + while( i < awsIotJsonDocumentLength - awsIotJsonKeyLength - 3 ) { /* If the first character in the key is found and there's an unescaped double * quote after the key length, do a string compare for the key. */ - if( ( IS_QUOTE( pJsonDocument, i ) ) && - ( IS_QUOTE( pJsonDocument, i + 1 + jsonKeyLength ) ) && - ( pJsonDocument[ i + 1 ] == pJsonKey[ 0 ] ) && - ( strncmp( pJsonDocument + i + 1, - pJsonKey, - jsonKeyLength ) == 0 ) ) + if( ( IS_QUOTE( pAwsIotJsonDocument, i ) ) && + ( IS_QUOTE( pAwsIotJsonDocument, i + 1 + awsIotJsonKeyLength ) ) && + ( pAwsIotJsonDocument[ i + 1 ] == pAwsIotJsonKey[ 0 ] ) && + ( strncmp( pAwsIotJsonDocument + i + 1, + pAwsIotJsonKey, + awsIotJsonKeyLength ) == 0 ) ) { /* Key found; this is a potential match. */ /* Skip the characters in the JSON key and closing double quote. */ - /* While loop guarantees that i < jsonDocumentLength - 1 */ - i += jsonKeyLength + 2; + /* While loop guarantees that i < awsIotJsonDocumentLength - 1 */ + i += awsIotJsonKeyLength + 2; /* Skip all whitespace characters between the closing " and the : */ - while( IS_WHITESPACE( pJsonDocument, i ) ) + while( IS_WHITESPACE( pAwsIotJsonDocument, i ) ) { i++; /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } @@ -102,7 +103,7 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, /* The character immediately following a key (and any whitespace) must be a : * If it's another character, then this string is a JSON value; skip it. */ - if( pJsonDocument[ i ] != ':' ) + if( pAwsIotJsonDocument[ i ] != ':' ) { continue; } @@ -113,31 +114,31 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, } /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } /* Skip all whitespace characters between : and the first character in the value. */ - while( IS_WHITESPACE( pJsonDocument, i ) ) + while( IS_WHITESPACE( pAwsIotJsonDocument, i ) ) { i++; /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } } /* Value found. Set the output parameter. */ - if( pJsonValue != NULL ) + if( pAwsIotJsonValue != NULL ) { - *pJsonValue = pJsonDocument + i; + *pAwsIotJsonValue = pAwsIotJsonDocument + i; } /* Calculate the value's length. */ - switch( pJsonDocument[ i ] ) + switch( pAwsIotJsonDocument[ i ] ) { /* Calculate length of a JSON string. */ case '\"': @@ -148,18 +149,18 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, i++; /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } /* Add the length of all characters in the JSON string. */ - while( pJsonDocument[ i ] != '\"' ) + while( pAwsIotJsonDocument[ i ] != '\"' ) { /* Ignore escaped double quotes. */ - if( ( pJsonDocument[ i ] == '\\' ) && - ( i + 1 < jsonDocumentLength ) && - ( pJsonDocument[ i + 1 ] == '\"' ) ) + if( ( pAwsIotJsonDocument[ i ] == '\\' ) && + ( i + 1 < awsIotJsonDocumentLength ) && + ( pAwsIotJsonDocument[ i + 1 ] == '\"' ) ) { /* Skip the characters \" */ i += 2; @@ -173,7 +174,7 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, } /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } @@ -197,11 +198,11 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, default: /* Skip the characters in the JSON value. The JSON value ends with a , or } */ - while( pJsonDocument[ i ] != ',' && - pJsonDocument[ i ] != '}' ) + while( pAwsIotJsonDocument[ i ] != ',' && + pAwsIotJsonDocument[ i ] != '}' ) { /* Any whitespace before a , or } means the JSON document is invalid. */ - if( IS_WHITESPACE( pJsonDocument, i ) ) + if( IS_WHITESPACE( pAwsIotJsonDocument, i ) ) { return false; } @@ -210,7 +211,7 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, jsonValueLength++; /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } @@ -229,32 +230,46 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, i++; /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } /* Add the length of all characters in the JSON object or array. This * includes the length of nested objects. */ - while( pJsonDocument[ i ] != closeCharacter || - ( pJsonDocument[ i ] == closeCharacter && nestingLevel != 0 ) ) + while( pAwsIotJsonDocument[ i ] != closeCharacter || + ( pAwsIotJsonDocument[ i ] == closeCharacter && ( nestingLevel != 0 || isWithinQuotes ) ) ) { - /* An opening character starts a nested object. */ - if( pJsonDocument[ i ] == openCharacter ) + /* Check if its a quote so as to avoid considering the + * nested levels for opening and closing characters within + * quotes. + */ + if( IS_QUOTE( pAwsIotJsonDocument, i ) ) { - nestingLevel++; + /*Toggle the flag*/ + isWithinQuotes = !isWithinQuotes; } - /* A closing character ends a nested object. */ - else if( pJsonDocument[ i ] == closeCharacter ) + + /* Calculate the nesting levels only if not in quotes. */ + if( !isWithinQuotes ) { - nestingLevel--; + /* An opening character starts a nested object. */ + if( pAwsIotJsonDocument[ i ] == openCharacter ) + { + nestingLevel++; + } + /* A closing character ends a nested object. */ + else if( pAwsIotJsonDocument[ i ] == closeCharacter ) + { + nestingLevel--; + } } i++; jsonValueLength++; /* If the end of the document is reached, this isn't a match. */ - if( i >= jsonDocumentLength ) + if( i >= awsIotJsonDocumentLength ) { return false; } @@ -262,9 +277,9 @@ bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, } /* JSON value length calculated; set the output parameter. */ - if( pJsonValueLength != NULL ) + if( pAwsIotJsonValueLength != NULL ) { - *pJsonValueLength = jsonValueLength; + *pAwsIotJsonValueLength = jsonValueLength; } return true; diff --git a/libraries/aws/common/src/aws_iot_parser.c b/libraries/aws/common/src/aws_iot_parser.c index 731b77c798..fad00856bb 100644 --- a/libraries/aws/common/src/aws_iot_parser.c +++ b/libraries/aws/common/src/aws_iot_parser.c @@ -37,8 +37,8 @@ /* Error handling include. */ #include "iot_error.h" -/* JSON utils include. */ -#include "iot_json_utils.h" +/* AWS Parser include. */ +#include "aws_iot_doc_parser.h" /** * @brief Minimum allowed topic length for an AWS IoT status topic. @@ -68,12 +68,12 @@ bool AwsIot_GetClientToken( const char * pJsonDocument, size_t * pClientTokenLength ) { /* Extract the client token from the JSON document. */ - bool status = IotJsonUtils_FindJsonValue( pJsonDocument, - jsonDocumentLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - pClientToken, - pClientTokenLength ); + bool status = AwsIotDocParser_FindValue( pJsonDocument, + jsonDocumentLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + pClientToken, + pClientTokenLength ); if( status == true ) { diff --git a/libraries/standard/serializer/include/iot_json_utils.h b/libraries/aws/common/test/aws_iot_tests_common.c similarity index 57% rename from libraries/standard/serializer/include/iot_json_utils.h rename to libraries/aws/common/test/aws_iot_tests_common.c index 32dc5e84fa..956362e204 100644 --- a/libraries/standard/serializer/include/iot_json_utils.h +++ b/libraries/aws/common/test/aws_iot_tests_common.c @@ -1,6 +1,6 @@ /* - * IoT Serializer V1.1.0 - * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * AWS IoT Common V1.0.0 + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -21,22 +21,32 @@ */ /** - * @file iot_json_utils.h - * @brief Declares JSON utility functions. + * @file aws_iot_tests_common.c + * @brief Test runner for AWS IoT Common tests. */ -#ifndef IOT_JSON_UTILS_H_ -#define IOT_JSON_UTILS_H_ - /* Standard includes. */ #include -#include -bool IotJsonUtils_FindJsonValue( const char * pJsonDocument, - size_t jsonDocumentLength, - const char * pJsonKey, - size_t jsonKeyLength, - const char ** pJsonValue, - size_t * pJsonValueLength ); +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Runs the AWS IoT Common test groups. + * + * @param[in] disableNetworkTests Whether tests that require the network should run. + * @param[in] disableLongTests Whether tests that take a long time should run. + */ +void RunAwsIotCommonTests( bool disableNetworkTests, + bool disableLongTests ) +{ + /* Silence warnings about unused parameters. */ + ( void ) disableNetworkTests; + ( void ) disableLongTests; + + RUN_TEST_GROUP( Aws_Iot_Doc_Unit_Parser ); +} -#endif /* ifndef IOT_JSON_UTILS_H_ */ +/*-----------------------------------------------------------*/ diff --git a/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c b/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c new file mode 100644 index 0000000000..9398e7a504 --- /dev/null +++ b/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c @@ -0,0 +1,317 @@ +/* + * AWS IoT Common V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file aws_iot_tests_doc_parser.c + * @brief Tests for the AWS IoT Parser. + */ + +/* The config header is always included first. */ +#include "iot_config.h" + +/* Standard includes. */ +#include +#include +#include + +/* AWS IoT parser include. */ +#include "aws_iot_doc_parser.h" + +/* Test framework includes. */ +#include "unity_fixture.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Wrapper for parsing AWS IoT JSON documents and checking the result. + */ +static void _parseJson( bool expectedResult, + const char * pJsonDocument, + size_t jsonDocumentLength, + const char * pJsonKey, + const char * pExpectedJsonValue, + size_t expectedJsonValueLength ) +{ + const char * pJsonValue = NULL; + size_t jsonValueLength = 0; + + TEST_ASSERT_EQUAL_INT( expectedResult, + AwsIotDocParser_FindValue( pJsonDocument, + jsonDocumentLength, + pJsonKey, + strlen( pJsonKey ), + &pJsonValue, + &jsonValueLength ) ); + + if( expectedResult == true ) + { + TEST_ASSERT_EQUAL( expectedJsonValueLength, jsonValueLength ); + TEST_ASSERT_EQUAL_STRING_LEN( pExpectedJsonValue, pJsonValue, jsonValueLength ); + } +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group for Shadow parser tests. + */ +TEST_GROUP( Aws_Iot_Doc_Unit_Parser ); + +/*-----------------------------------------------------------*/ + +/** + * @brief Test setup for Shadow parser tests. + */ +TEST_SETUP( Aws_Iot_Doc_Unit_Parser ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test tear down for Shadow parser tests. + */ +TEST_TEAR_DOWN( Aws_Iot_Doc_Unit_Parser ) +{ +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Test group runner for Shadow parser tests. + */ +TEST_GROUP_RUNNER( Aws_Iot_Doc_Unit_Parser ) +{ + RUN_TEST_CASE( Aws_Iot_Doc_Unit_Parser, JsonValid ); + RUN_TEST_CASE( Aws_Iot_Doc_Unit_Parser, JsonInvalid ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests parsing valid JSON documents. + */ +TEST( Aws_Iot_Doc_Unit_Parser, JsonValid ) +{ + /* Parse JSON document with string, int, bool, and null. */ + { + const char pJsonDocument[ 82 ] = "{\"name\" \n\r:\n \"John Smith\", \"age\" :\n\r 30, \n \"isAlive\" : true, \r \"spouse\":null}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( true, pJsonDocument, jsonDocumentLength, "name", "\"John Smith\"", 12 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "age", "30", 2 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "isAlive", "true", 4 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "spouse", "null", 4 ); + + /* Attempt to find a key not in the JSON document. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "address", NULL, 0 ); + } + + /* Parse JSON document with objects and arrays. */ + { + const char pJsonDocument[ 91 ] = "{\"object\" : { \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( true, pJsonDocument, jsonDocumentLength, "object", "{ \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}", 76 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "nestedObject", "{ \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}", 57 ); + _parseJson( true, pJsonDocument, jsonDocumentLength, "array", "[[1,2,3],[1,2,3],[1,2,3]]", 25 ); + } + + /* JSON document with escape sequences. */ + { + const char pJsonDocument[ 40 ] = "{\"key\": \"value\", \"ke\\\"y2\": \"\\\"value\\\"\"}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a JSON key that is actually a value. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "value", NULL, 0 ); + + /* Attempt to find a JSON key that is a substring of a key. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "ke", NULL, 0 ); + + /* Find a key and string that contain escaped quotes. */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "ke\\\"y2", "\"\\\"value\\\"\"", 11 ); + } + + /* Short JSON document. */ + { + const char pJsonDocument[ 16 ] = "{\"key\":\"value\"}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a key longer than the JSON document. */ + _parseJson( false, pJsonDocument, jsonDocumentLength, "longlonglonglongkey", NULL, 0 ); + } + + /* JSON with '{' '}' in value*/ + { + const char pJsonDocument[ 18 ] = "{\"key\":\"valu}{e\"}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a value with '{' '}' */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"valu}{e\"", 9 ); + } + + /* JSON with '{' '}' in value in nested level*/ + { + const char pJsonDocument[ 42 ] = "{\"key\":{\"key2\":\"{{{{}}\", \"key3\":\"value\"}}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a value which has value a string with '{' '}' */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "{\"key2\":\"{{{{}}\", \"key3\":\"value\"}", 33 ); + } + + /* JSON with objects elements of the array */ + { + const char pJsonDocument[ 63 ] = "{\"key\":\"value\", \"arr\": [{\"key1\":\"value1\"}, {\"key2\":\"value2\"}]}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a value with objects as members of the array */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "arr", "[{\"key1\":\"value1\"}, {\"key2\":\"value2\"}]", 38 ); + } + + /* JSON with with false in the value */ + { + const char pJsonDocument[ 33 ] = "{\"key\":\"value\", \"keybool\":false}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a boolean value */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "keybool", "false", 5 ); + } + + /* JSON with with different elements in the array */ + { + const char pJsonDocument[ 85 ] = "{\"key\":\"value\", \"arr\":[4, \"string\", true, {\"key1\":\"value1\", \"arr1\":[1,2,3]}, 99]}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + /* Attempt to find a value with different elements in the array */ + _parseJson( true, pJsonDocument, jsonDocumentLength, "arr", "[4, \"string\", true, {\"key1\":\"value1\", \"arr1\":[1,2,3]}, 99]", 58 ); + } +} + + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that parsing invalid JSON documents does not read out-of-bounds + * memory. + */ +TEST( Aws_Iot_Doc_Unit_Parser, JsonInvalid ) +{ + /* JSON key not followed by a : */ + { + const char pJsonDocument[ 16 ] = "{\"string\" "; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* JSON value not followed by a , */ + { + const char pJsonDocument[ 30 ] = "{\"int\": 10 \"string\": \"hello\"}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); + } + + /* JSON key with no value. */ + { + const char pJsonDocument[ 18 ] = "{\"string\": "; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* Unterminated JSON primitive. */ + { + const char pJsonDocument[ 12 ] = "{\"int\":1000"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); + } + + /* Unterminated JSON string (ending is an escaped quote). */ + { + const char pJsonDocument[ 15 ] = "{\"string\": \"\\\""; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* Unterminated JSON string (ending is not a quote). */ + { + const char pJsonDocument[ 15 ] = "{\"string\": \" \\"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); + } + + /* Unterminated JSON object. */ + { + const char pJsonDocument[ 42 ] = "{\"object\": {\"key\": { \"nestedKey\":\"value\"}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "object", NULL, 0 ); + } + + /* Unterminated JSON array. */ + { + const char pJsonDocument[ 27 ] = "{\"array\": [[1,2,3],[1,2,3]"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( false, pJsonDocument, jsonDocumentLength, "array", NULL, 0 ); + } + + /* Invalid JSON not validated for correctness. Incorrect value. */ + { + const char pJsonDocument[ 30 ] = "{\"key\": \"value\", \"key2\": {]}}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); + } + + /* Invalid JSON not validated for correctness. Incorrect paranthesis. */ + { + const char pJsonDocument[ 30 ] = "{\"key\": \"value\", \"key2\": {}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); + } + + /* Invalid JSON not validated for correctness. Incorrect `,`s. */ + { + const char pJsonDocument[ 52 ] = "{\"key\": \"value\", \"key2\": \"value1\" \"key3\": \"value3\"}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); + } + + /* Invalid JSON not validated for correctness. Incorrect nesting level. + * Missing `:`. + */ + { + const char pJsonDocument[ 43 ] = "{\"key\": \"value\", \"key2\":{\"key3\" \"value3\"}}"; + size_t jsonDocumentLength = strlen( pJsonDocument ); + + _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); + } +} + +/*-----------------------------------------------------------*/ diff --git a/libraries/aws/jobs/src/aws_iot_jobs_serialize.c b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c index d4f6d9fc6b..2e9fcb53e5 100644 --- a/libraries/aws/jobs/src/aws_iot_jobs_serialize.c +++ b/libraries/aws/jobs/src/aws_iot_jobs_serialize.c @@ -39,7 +39,7 @@ #include "iot_error.h" /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" /** * @brief Minimum length of a Jobs request. @@ -1003,12 +1003,12 @@ static AwsIotJobsError_t _parseErrorDocument( const char * pErrorDocument, size_t codeLength = 0; /* Find the error code. */ - if( IotJsonUtils_FindJsonValue( pErrorDocument, - errorDocumentLength, - CODE_KEY, - CODE_KEY_LENGTH, - &pCode, - &codeLength ) == false ) + if( AwsIotDocParser_FindValue( pErrorDocument, + errorDocumentLength, + CODE_KEY, + CODE_KEY_LENGTH, + &pCode, + &codeLength ) == false ) { IOT_SET_AND_GOTO_CLEANUP( AWS_IOT_JOBS_BAD_RESPONSE ); } diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c index ca52cbca71..db198fc467 100644 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c @@ -54,7 +54,7 @@ #include "unity_fixture.h" /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" /*-----------------------------------------------------------*/ @@ -192,12 +192,12 @@ static void _jobsCallback( void * pArgument, } /* Parse the next Job ID. */ - AwsIotJobs_Assert( IotJsonUtils_FindJsonValue( pCallbackParam->u.callback.pDocument, - pCallbackParam->u.callback.documentLength, - "jobId", - 5, - &pJobId, - &jobIdLength ) == true ); + AwsIotJobs_Assert( AwsIotDocParser_FindValue( pCallbackParam->u.callback.pDocument, + pCallbackParam->u.callback.documentLength, + "jobId", + 5, + &pJobId, + &jobIdLength ) == true ); /* Verify that the previously queued Job is next. */ AwsIotJobs_Assert( jobIdLength - 2 == _pJobIdLengths[ checkJobId ] ); @@ -223,23 +223,23 @@ static void _parseJobIds( const AwsIotJobsResponse_t * pJobsResponse ) /* In-progress Jobs for this device will interfere with the tests; fail if * any in-progress Jobs are present. */ - status = IotJsonUtils_FindJsonValue( pJobsResponse->pJobsResponse, - pJobsResponse->jobsResponseLength, - "inProgressJobs", 14, - &pInProgressJobs, - &inProgressJobsLength ); + status = AwsIotDocParser_FindValue( pJobsResponse->pJobsResponse, + pJobsResponse->jobsResponseLength, + "inProgressJobs", 14, + &pInProgressJobs, + &inProgressJobsLength ); TEST_ASSERT_EQUAL_INT( true, status ); TEST_ASSERT_NOT_NULL( pInProgressJobs ); TEST_ASSERT_EQUAL_MESSAGE( 2, inProgressJobsLength, "In-progress Jobs detected. Tests will not run." ); /* Parse for the list of queued Jobs. This is where parsing for Job IDs will * start. */ - status = IotJsonUtils_FindJsonValue( pJobsResponse->pJobsResponse, - pJobsResponse->jobsResponseLength, - "queuedJobs", - 10, - &pParseStart, - &parseLength ); + status = AwsIotDocParser_FindValue( pJobsResponse->pJobsResponse, + pJobsResponse->jobsResponseLength, + "queuedJobs", + 10, + &pParseStart, + &parseLength ); TEST_ASSERT_EQUAL_INT_MESSAGE( true, status, "Response did not contain any queued Jobs." ); TEST_ASSERT_NOT_NULL( pParseStart ); TEST_ASSERT_GREATER_THAN( 0, parseLength ); @@ -247,12 +247,12 @@ static void _parseJobIds( const AwsIotJobsResponse_t * pJobsResponse ) /* Parse the Job IDs of the first two queued Jobs. */ for( i = 0; i < 2; i++ ) { - status = IotJsonUtils_FindJsonValue( pParseStart, - parseLength, - "jobId", - 5, - &pJobId, - &jobIdLength ); + status = AwsIotDocParser_FindValue( pParseStart, + parseLength, + "jobId", + 5, + &pJobId, + &jobIdLength ); TEST_ASSERT_EQUAL_INT_MESSAGE( true, status, "Response did not contain enough queued Jobs." ); TEST_ASSERT_NOT_NULL( pJobId ); TEST_ASSERT_GREATER_THAN( 0, jobIdLength ); @@ -450,12 +450,12 @@ static void _jobsBlockingTest( _jobsOperationType_t type, * UPDATE; its response does not include the Job ID. */ if( type != JOBS_UPDATE ) { - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( jobsResponse.pJobsResponse, - jobsResponse.jobsResponseLength, - "jobId", - 5, - &pJobId, - &jobIdLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( jobsResponse.pJobsResponse, + jobsResponse.jobsResponseLength, + "jobId", + 5, + &pJobId, + &jobIdLength ) ); for( i = 0; i < 2; i++ ) { diff --git a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c index 8bd73fd0ac..58f45e130f 100644 --- a/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c +++ b/libraries/aws/jobs/test/unit/aws_iot_tests_jobs_serialize.c @@ -41,7 +41,7 @@ #include "unity_fixture.h" /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" /*-----------------------------------------------------------*/ @@ -115,12 +115,12 @@ TEST( Jobs_Unit_Serialize, SerializeGetPending ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ) ); TEST_ASSERT_EQUAL_PTR( pClientToken, operation.pClientToken ); TEST_ASSERT_EQUAL( clientTokenLength, operation.clientTokenLength ); @@ -136,12 +136,12 @@ TEST( Jobs_Unit_Serialize, SerializeGetPending ) &operation ); /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pClientToken, - &clientTokenLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pClientToken, + &clientTokenLength ) ); TEST_ASSERT_EQUAL_PTR( pClientToken, operation.pClientToken ); TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", operation.pClientToken, 6 ); @@ -176,12 +176,12 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); @@ -199,12 +199,12 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextClientToken ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); @@ -238,12 +238,12 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) &operation ); TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); - TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); /* Free request document generated by serializer. */ AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); @@ -258,12 +258,12 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the step timeout is -1. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 2, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); @@ -280,12 +280,12 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStepTimeout ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the step timeout is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 4, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "3600", pJsonValue, 4 ); @@ -322,12 +322,12 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the status details are present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "statusDetails", - 13, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "statusDetails", + 13, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 12, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); @@ -346,32 +346,32 @@ TEST( Jobs_Unit_Serialize, SerializeStartNextStatusDetails ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the status details are present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "statusDetails", - 13, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "statusDetails", + 13, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 12, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); /* Check that step timeout is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 5, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "10080", pJsonValue, 5 ); /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); @@ -406,12 +406,12 @@ TEST( Jobs_Unit_Serialize, SerializeDescribe ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); @@ -431,12 +431,12 @@ TEST( Jobs_Unit_Serialize, SerializeDescribe ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the custom client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( 6, operation.clientTokenLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"test\"", pJsonValue, 6 ); @@ -488,30 +488,30 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateStatus ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check the status value. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "status", - 6, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "status", + 6, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( strlen( pValidStatusStrings[ i ] ), jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( pValidStatusStrings[ i ], pJsonValue, jsonValueLength ); /* Check that the step timeout is not present. */ - TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); - - /* Check that a client token is present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, + TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, operation.jobsRequestLength, - AWS_IOT_CLIENT_TOKEN_KEY, - AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + "stepTimeoutInMinutes", + 20, &pJsonValue, &jsonValueLength ) ); + + /* Check that a client token is present. */ + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + AWS_IOT_CLIENT_TOKEN_KEY, + AWS_IOT_CLIENT_TOKEN_KEY_LENGTH, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL_PTR( pJsonValue, operation.pClientToken ); TEST_ASSERT_EQUAL( jsonValueLength, operation.clientTokenLength ); @@ -530,12 +530,12 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateStatus ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that the status details are present. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "statusDetails", - 13, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "statusDetails", + 13, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 12, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "{\"status\":0}", pJsonValue, 12 ); @@ -571,12 +571,12 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check the expected version. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "expectedVersion", - 15, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "expectedVersion", + 15, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 3, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"1\"", pJsonValue, 3 ); @@ -593,12 +593,12 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check the execution number. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "executionNumber", - 15, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "executionNumber", + 15, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 9, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "555555555", pJsonValue, 9 ); @@ -615,12 +615,12 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check the step timeout. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 5, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "10080", pJsonValue, 5 ); @@ -637,12 +637,12 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateNumbers ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that step timeout is -1. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "stepTimeoutInMinutes", - 20, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "stepTimeoutInMinutes", + 20, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 2, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "-1", pJsonValue, 2 ); @@ -679,18 +679,18 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateFlags ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that no flags are present. */ - TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobDocument", - 18, - &pJsonValue, - &jsonValueLength ) ); - TEST_ASSERT_EQUAL_INT( false, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobExecutionState", - 24, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobDocument", + 18, + &pJsonValue, + &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( false, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobExecutionState", + 24, + &pJsonValue, + &jsonValueLength ) ); /* Free request document generated by serializer. */ AwsIotJobs_FreeString( ( void * ) operation.pJobsRequest ); @@ -706,21 +706,21 @@ TEST( Jobs_Unit_Serialize, SerializeUpdateFlags ) TEST_ASSERT_EQUAL( AWS_IOT_JOBS_SUCCESS, status ); /* Check that flags are set. */ - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobDocument", - 18, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobDocument", + 18, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 4, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "true", pJsonValue, 4 ); - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( operation.pJobsRequest, - operation.jobsRequestLength, - "includeJobExecutionState", - 24, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( operation.pJobsRequest, + operation.jobsRequestLength, + "includeJobExecutionState", + 24, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 4, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "true", pJsonValue, 4 ); diff --git a/libraries/aws/shadow/src/aws_iot_shadow_parser.c b/libraries/aws/shadow/src/aws_iot_shadow_parser.c index 3d2b7ee893..64abec633c 100644 --- a/libraries/aws/shadow/src/aws_iot_shadow_parser.c +++ b/libraries/aws/shadow/src/aws_iot_shadow_parser.c @@ -38,8 +38,8 @@ /* Error handling include. */ #include "iot_error.h" -/* JSON utilities include. */ -#include "iot_json_utils.h" +/* AWS Parser include. */ +#include "aws_iot_doc_parser.h" /*-----------------------------------------------------------*/ @@ -139,12 +139,12 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen uint32_t code = 0; /* Parse the code from the error document. */ - if( IotJsonUtils_FindJsonValue( pErrorDocument, - errorDocumentLength, - ERROR_DOCUMENT_CODE_KEY, - ERROR_DOCUMENT_CODE_KEY_LENGTH, - &pCode, - &codeLength ) == false ) + if( AwsIotDocParser_FindValue( pErrorDocument, + errorDocumentLength, + ERROR_DOCUMENT_CODE_KEY, + ERROR_DOCUMENT_CODE_KEY_LENGTH, + &pCode, + &codeLength ) == false ) { /* Error parsing JSON document, or no "code" key was found. */ IotLogWarn( "Failed to parse code from error document.\n%.*s", @@ -163,12 +163,12 @@ AwsIotShadowError_t _AwsIotShadow_ParseErrorDocument( const char * pErrorDocumen /* Parse the error message and print it. An error document must always contain * a message. */ - if( IotJsonUtils_FindJsonValue( pErrorDocument, - errorDocumentLength, - ERROR_DOCUMENT_MESSAGE_KEY, - ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, - &pMessage, - &messageLength ) == true ) + if( AwsIotDocParser_FindValue( pErrorDocument, + errorDocumentLength, + ERROR_DOCUMENT_MESSAGE_KEY, + ERROR_DOCUMENT_MESSAGE_KEY_LENGTH, + &pMessage, + &messageLength ) == true ) { IotLogWarn( "Code %lu: %.*s.", code, diff --git a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c index ae300a8030..a0c6287d97 100644 --- a/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c +++ b/libraries/aws/shadow/test/system/aws_iot_tests_shadow_system.c @@ -39,7 +39,7 @@ #include "private/aws_iot_shadow_internal.h" /* JSON utilities include. */ -#include "iot_json_utils.h" +#include "aws_iot_doc_parser.h" /* Platform layer includes. */ #include "platform/iot_clock.h" @@ -156,12 +156,12 @@ static void _operationComplete( void * pArgument, { AwsIotShadow_Assert( pOperation->u.operation.get.documentLength > 0 ); - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pOperation->u.operation.get.pDocument, - pOperation->u.operation.get.documentLength, - "key", - 3, - &pJsonValue, - &jsonValueLength ) == true ); + AwsIotShadow_Assert( AwsIotDocParser_FindValue( pOperation->u.operation.get.pDocument, + pOperation->u.operation.get.documentLength, + "key", + 3, + &pJsonValue, + &jsonValueLength ) == true ); AwsIotShadow_Assert( jsonValueLength == 7 ); AwsIotShadow_Assert( strncmp( pJsonValue, "\"value\"", 7 ) == 0 ); } @@ -188,22 +188,22 @@ static void _deltaCallback( void * pArgument, AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); /* Check delta document state. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "key", - 3, - &pValue, - &valueLength ) == true ); + AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, + "key", + 3, + &pValue, + &valueLength ) == true ); AwsIotShadow_Assert( valueLength == 4 ); AwsIotShadow_Assert( strncmp( pValue, "true", valueLength ) == 0 ); /* Check delta document client token. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "clientToken", - 11, - &pClientToken, - &clientTokenLength ) ); + AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, + "clientToken", + 11, + &pClientToken, + &clientTokenLength ) ); AwsIotShadow_Assert( clientTokenLength == 12 ); AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); @@ -228,36 +228,36 @@ static void _updatedCallback( void * pArgument, AwsIotShadow_Assert( pCallback->mqttConnection == _mqttConnection ); /* Check updated document previous state. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "previous", - 8, - &pPrevious, - &previousStateLength ) == true ); + AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, + "previous", + 8, + &pPrevious, + &previousStateLength ) == true ); AwsIotShadow_Assert( previousStateLength > 0 ); AwsIotShadow_Assert( strncmp( "{\"state\":{},\"metadata\":{},", pPrevious, 26 ) == 0 ); /* Check updated document current state. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "current", - 7, - &pCurrent, - ¤tStateLength ) == true ); + AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, + "current", + 7, + &pCurrent, + ¤tStateLength ) == true ); AwsIotShadow_Assert( currentStateLength > 0 ); AwsIotShadow_Assert( strncmp( "{\"state\":{\"desired\":{\"key\":true}}", pCurrent, 33 ) == 0 ); /* Check updated document client token. */ - AwsIotShadow_Assert( IotJsonUtils_FindJsonValue( pCallback->u.callback.pDocument, - pCallback->u.callback.documentLength, - "clientToken", - 11, - &pClientToken, - &clientTokenLength ) ); + AwsIotShadow_Assert( AwsIotDocParser_FindValue( pCallback->u.callback.pDocument, + pCallback->u.callback.documentLength, + "clientToken", + 11, + &pClientToken, + &clientTokenLength ) ); AwsIotShadow_Assert( clientTokenLength == 12 ); AwsIotShadow_Assert( strncmp( "\"shadowtest\"", pClientToken, 12 ) == 0 ); @@ -392,12 +392,12 @@ static void _updateGetDeleteBlocking( IotMqttQos_t qos ) /* Check the retrieved Shadow document. */ TEST_ASSERT_GREATER_THAN( 0, shadowDocumentLength ); TEST_ASSERT_NOT_NULL( pShadowDocument ); - TEST_ASSERT_EQUAL_INT( true, IotJsonUtils_FindJsonValue( pShadowDocument, - shadowDocumentLength, - "key", - 3, - &pJsonValue, - &jsonValueLength ) ); + TEST_ASSERT_EQUAL_INT( true, AwsIotDocParser_FindValue( pShadowDocument, + shadowDocumentLength, + "key", + 3, + &pJsonValue, + &jsonValueLength ) ); TEST_ASSERT_EQUAL( 7, jsonValueLength ); TEST_ASSERT_EQUAL_STRING_LEN( "\"value\"", pJsonValue, jsonValueLength ); diff --git a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c index a284d21b98..26da044388 100644 --- a/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c +++ b/libraries/aws/shadow/test/unit/aws_iot_tests_shadow_parser.c @@ -36,9 +36,6 @@ /* Shadow internal include. */ #include "private/aws_iot_shadow_internal.h" -/* JSON utilities include. */ -#include "iot_json_utils.h" - /* Test framework includes. */ #include "unity_fixture.h" @@ -54,36 +51,6 @@ /*-----------------------------------------------------------*/ -/** - * @brief Wrapper for parsing JSON documents and checking the result. - */ -static void _parseJson( bool expectedResult, - const char * pJsonDocument, - size_t jsonDocumentLength, - const char * pJsonKey, - const char * pExpectedJsonValue, - size_t expectedJsonValueLength ) -{ - const char * pJsonValue = NULL; - size_t jsonValueLength = 0; - - TEST_ASSERT_EQUAL_INT( expectedResult, - IotJsonUtils_FindJsonValue( pJsonDocument, - jsonDocumentLength, - pJsonKey, - strlen( pJsonKey ), - &pJsonValue, - &jsonValueLength ) ); - - if( expectedResult == true ) - { - TEST_ASSERT_EQUAL( expectedJsonValueLength, jsonValueLength ); - TEST_ASSERT_EQUAL_STRING_LEN( pExpectedJsonValue, pJsonValue, jsonValueLength ); - } -} - -/*-----------------------------------------------------------*/ - /** * @brief Wrapper for generating and parsing error documents. */ @@ -184,8 +151,6 @@ TEST_GROUP_RUNNER( Shadow_Unit_Parser ) { RUN_TEST_CASE( Shadow_Unit_Parser, StatusValid ); RUN_TEST_CASE( Shadow_Unit_Parser, StatusInvalid ); - RUN_TEST_CASE( Shadow_Unit_Parser, JsonValid ); - RUN_TEST_CASE( Shadow_Unit_Parser, JsonInvalid ); RUN_TEST_CASE( Shadow_Unit_Parser, ErrorDocument ); RUN_TEST_CASE( Shadow_Unit_Parser, ErrorDocumentInvalid ); RUN_TEST_CASE( Shadow_Unit_Parser, ThingName ); @@ -241,135 +206,6 @@ TEST( Shadow_Unit_Parser, StatusInvalid ) /*-----------------------------------------------------------*/ -/** - * @brief Tests parsing valid JSON documents. - */ -TEST( Shadow_Unit_Parser, JsonValid ) -{ - /* Parse JSON document with string, int, bool, and null. */ - { - const char pJsonDocument[ 82 ] = "{\"name\" \n\r:\n \"John Smith\", \"age\" :\n\r 30, \n \"isAlive\" : true, \r \"spouse\":null}"; - size_t jsonDocumentLength = 81; - - _parseJson( true, pJsonDocument, jsonDocumentLength, "name", "\"John Smith\"", 12 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "age", "30", 2 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "isAlive", "true", 4 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "spouse", "null", 4 ); - - /* Attempt to find a key not in the JSON document. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "address", NULL, 0 ); - } - - /* Parse JSON document with objects and arrays. */ - { - const char pJsonDocument[ 91 ] = "{\"object\" : { \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}}"; - size_t jsonDocumentLength = 90; - - _parseJson( true, pJsonDocument, jsonDocumentLength, "object", "{ \"nestedObject\": { \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}}", 76 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "nestedObject", "{ \"string\":\"\\\"test\\\"\", \"array\":[[1,2,3],[1,2,3],[1,2,3]]}", 57 ); - _parseJson( true, pJsonDocument, jsonDocumentLength, "array", "[[1,2,3],[1,2,3],[1,2,3]]", 25 ); - } - - /* JSON document with escape sequences. */ - { - const char pJsonDocument[ 40 ] = "{\"key\": \"value\", \"ke\\\"y2\": \"\\\"value\\\"\"}"; - size_t jsonDocumentLength = 40; - - /* Attempt to find a JSON key that is actually a value. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "value", NULL, 0 ); - - /* Attempt to find a JSON key that is a substring of a key. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "ke", NULL, 0 ); - - /* Find a key and string that contain escaped quotes. */ - _parseJson( true, pJsonDocument, jsonDocumentLength, "ke\\\"y2", "\"\\\"value\\\"\"", 11 ); - } - - /* Short JSON document. */ - { - const char pJsonDocument[ 16 ] = "{\"key\":\"value\"}"; - size_t jsonDocumentLength = 16; - - /* Attempt to find a key longer than the JSON document. */ - _parseJson( false, pJsonDocument, jsonDocumentLength, "longlonglonglongkey", NULL, 0 ); - } -} - -/*-----------------------------------------------------------*/ - -/** - * @brief Tests that parsing invalid JSON documents does not read out-of-bounds - * memory. - */ -TEST( Shadow_Unit_Parser, JsonInvalid ) -{ - /* JSON key not followed by a : */ - { - const char pJsonDocument[ 16 ] = "{\"string\" "; - size_t jsonDocumentLength = 15; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* JSON value not followed by a , */ - { - const char pJsonDocument[ 30 ] = "{\"int\": 10 \"string\": \"hello\"}"; - size_t jsonDocumentLength = 29; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); - } - - /* JSON key with no value. */ - { - const char pJsonDocument[ 18 ] = "{\"string\": "; - size_t jsonDocumentLength = 17; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* Unterminated JSON primitive. */ - { - const char pJsonDocument[ 12 ] = "{\"int\":1000"; - size_t jsonDocumentLength = 11; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "int", NULL, 0 ); - } - - /* Unterminated JSON string (ending is an escaped quote). */ - { - const char pJsonDocument[ 15 ] = "{\"string\": \"\\\""; - size_t jsonDocumentLength = 14; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* Unterminated JSON string (ending is not a quote). */ - { - const char pJsonDocument[ 15 ] = "{\"string\": \" \\"; - size_t jsonDocumentLength = 14; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "string", NULL, 0 ); - } - - /* Unterminated JSON object. */ - { - const char pJsonDocument[ 42 ] = "{\"object\": {\"key\": { \"nestedKey\":\"value\"}"; - size_t jsonDocumentLength = 41; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "object", NULL, 0 ); - } - - /* Unterminated JSON array. */ - { - const char pJsonDocument[ 27 ] = "{\"array\": [[1,2,3],[1,2,3]"; - size_t jsonDocumentLength = 26; - - _parseJson( false, pJsonDocument, jsonDocumentLength, "array", NULL, 0 ); - } -} - -/*-----------------------------------------------------------*/ - /** * @brief Tests parsing valid Shadow error documents. */ diff --git a/libraries/standard/serializer/CMakeLists.txt b/libraries/standard/serializer/CMakeLists.txt index 7e2bd11f2b..15ab4e2bab 100644 --- a/libraries/standard/serializer/CMakeLists.txt +++ b/libraries/standard/serializer/CMakeLists.txt @@ -1,6 +1,5 @@ # Serializer library source files. set( SERIALIZER_SOURCES - src/iot_json_utils.c src/iot_serializer_static_memory.c src/cbor/iot_serializer_tinycbor_decoder.c src/cbor/iot_serializer_tinycbor_encoder.c ) @@ -9,8 +8,7 @@ set( SERIALIZER_SOURCES add_library( iotserializer ${CONFIG_HEADER_PATH}/iot_config.h ${SERIALIZER_SOURCES} - include/iot_serializer.h - include/iot_json_utils.h ) + include/iot_serializer.h ) # Serializer public include path. target_include_directories( iotserializer PUBLIC include ) @@ -27,8 +25,7 @@ endif() set_property( TARGET iotserializer PROPERTY FOLDER libraries/standard ) source_group( "" FILES ${CONFIG_HEADER_PATH}/iot_config.h ) source_group( include FILES - include/iot_serializer.h - include/iot_json_utils.h ) + include/iot_serializer.h ) source_group( src FILES ${SERIALIZER_SOURCES} ) # Build the test executable if needed. From e1668df2cbced09ab3426e2427beadfcc5a7d4fc Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Thu, 14 Nov 2019 18:13:43 -0800 Subject: [PATCH 324/844] Unit tests for Lightweight serializer API. (#643) feature: Lightweight serializer API for MQTT. MQTT serializer API changes to support lightweight single threaded app. CSDK MQTT Library provides stateful MQTT API that makes use of taskpool and network abstraction. The library makes use dynamic memory allocations and makes use of multiple threads or tasks. Embedded applications running on microcontroller may want to use single threaded model to run MQTT. The API changes enable application developers to use MQTT serializer and deserializer APIs without use of taskpool and network abstraction. The API does no allocate any dynamic memory, therefore application can make use of statically allocated memory. Application can implement its own state machine and make use of any network interface like sockets to handle MQTT messages. --- .../mqtt/test/unit/iot_tests_mqtt_api.c | 648 ++++++++++++++++++ 1 file changed, 648 insertions(+) diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 71d16171e0..7d3c61415c 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -47,6 +47,9 @@ /* MQTT test access include. */ #include "iot_test_access_mqtt.h" +/* MQTT serializer API include */ +#include "iot_mqtt_serialize.h" + /* MQTT mock include. */ #include "iot_tests_mqtt_mock.h" @@ -512,6 +515,29 @@ static void _decrementReferencesJob( IotTaskPool_t pTaskPool, /*-----------------------------------------------------------*/ +/** + * @brief get next byte mock function to test MQTT serializer API + */ +static IotMqttError_t _getNextByte( void * pNetworkInterface, + uint8_t * nextByte ) +{ + uint8_t * buffer; + + /* Treat network interface as pointer to buffer for mocking */ + /* Send next byte */ + IotTest_Assert( pNetworkInterface != NULL ); + IotTest_Assert( nextByte != NULL ); + + buffer = ( *( uint8_t ** ) pNetworkInterface ); + /* read single byte */ + *nextByte = *buffer; + /* Move stream by 1 byte */ + ( *( uint8_t ** ) pNetworkInterface ) = ++buffer; + return IOT_MQTT_SUCCESS; +} + +/*-----------------------------------------------------------*/ + /** * @brief Test group for MQTT API tests. */ @@ -579,6 +605,18 @@ TEST_GROUP_RUNNER( MQTT_Unit_API ) RUN_TEST_CASE( MQTT_Unit_API, SingleThreaded ); RUN_TEST_CASE( MQTT_Unit_API, KeepAlivePeriodic ); RUN_TEST_CASE( MQTT_Unit_API, KeepAliveJobCleanup ); + RUN_TEST_CASE( MQTT_Unit_API, GetConnectPacketSizeChecks ); + RUN_TEST_CASE( MQTT_Unit_API, SerializeConnectChecks ); + RUN_TEST_CASE( MQTT_Unit_API, GetSubscribePacketSizeChecks ); + RUN_TEST_CASE( MQTT_Unit_API, SerializeSubscribeChecks ); + RUN_TEST_CASE( MQTT_Unit_API, SerializeUnsubscribeChecks ); + RUN_TEST_CASE( MQTT_Unit_API, GetPublishPacketSizeChecks ); + RUN_TEST_CASE( MQTT_Unit_API, SerializePublishChecks ); + RUN_TEST_CASE( MQTT_Unit_API, SerializeDisconnectChecks ); + RUN_TEST_CASE( MQTT_Unit_API, SerializePingReqChecks ); + RUN_TEST_CASE( MQTT_Unit_API, DeserializeResponseChecks ); + RUN_TEST_CASE( MQTT_Unit_API, DeserializePublishChecks ); + RUN_TEST_CASE( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ); } /*-----------------------------------------------------------*/ @@ -1628,3 +1666,613 @@ TEST( MQTT_Unit_API, KeepAliveJobCleanup ) } /*-----------------------------------------------------------*/ + +/* Tests for public serializer API */ + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_GetConnectPacketSize works as intended. + * to @ref mqtt_function_getconnectpacketsize. + */ + +TEST( MQTT_Unit_API, GetConnectPacketSizeChecks ) +{ + IotMqttConnectInfo_t connectInfo; + size_t remainingLength = 0; + size_t packetSize = 0; + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Call IotMqtt_GetConnectPacketSize() with various combinations of + * incorrect paramters */ + + status = IotMqtt_GetConnectPacketSize( NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetConnectPacketSize( &connectInfo, NULL, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, NULL ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Verify empty connect info fails. */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Verify good case */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = "TEST"; + connectInfo.clientIdentifierLength = 4; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure remaining size returned is 16. */ + TEST_ASSERT_EQUAL_INT( 16, remainingLength ); + /* Make sure packet size is 18. */ + TEST_ASSERT_EQUAL_INT( 18, packetSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_SerializeConnect works as intended. + * to @ref mqtt_function_serializeconnect. + */ +TEST( MQTT_Unit_API, SerializeConnectChecks ) +{ + IotMqttConnectInfo_t connectInfo; + size_t remainingLength = 0; + uint8_t buffer[ 20 ]; + size_t packetSize = sizeof( buffer ); + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Verify bad parameter errors. */ + status = IotMqtt_SerializeConnect( NULL, remainingLength, buffer, packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, NULL, packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + /* Make sure greater remaining length returns error. */ + remainingLength = 120; + status = IotMqtt_SerializeConnect( &connectInfo, 120, buffer, packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Good case succeeds */ + /* Calculate packet size. */ + memset( ( void * ) &connectInfo, 0x0, sizeof( connectInfo ) ); + connectInfo.cleanSession = true; + connectInfo.pClientIdentifier = "TEST"; + connectInfo.clientIdentifierLength = 4; + status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Make sure test suceeds. */ + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* For this example, IotMqtt_GetConnectPacketSize() will return + * packetSize = remainingLength +2 (two byte fixed header). + * Make sure IotMqtt_SerializeConnect() + * fails when remaining length is more than packet size. */ + status = IotMqtt_SerializeConnect( &connectInfo, remainingLength + 4, buffer, packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_GetSubscribePacketSize works as intended. + * to @ref mqtt_function_getsubscriptionpacketsize. + */ +TEST( MQTT_Unit_API, GetSubscribePacketSizeChecks ) +{ + IotMqttSubscription_t subscriptionList; + size_t subscriptionCount = 0; + size_t remainingLength = 0; + size_t packetSize = 0; + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Verify parameters. */ + + status = IotMqtt_GetSubscriptionPacketSize( 100, + &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + NULL, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + &subscriptionList, + subscriptionCount, + NULL, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + &subscriptionList, + subscriptionCount, + &remainingLength, + NULL ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + + /* Verify empty subscription list fails. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionCount = 0; + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Verify good case. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = IOT_MQTT_QOS_0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( IotMqttSubscription_t ); + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_GREATER_THAN( remainingLength, packetSize ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_SerializeSubscribe works as intended. + * to @ref mqtt_function_serializesubscribe. + */ +TEST( MQTT_Unit_API, SerializeSubscribeChecks ) +{ + IotMqttSubscription_t subscriptionList; + size_t subscriptionCount = 0; + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t buffer[ 20 ]; + size_t packetSize = sizeof( buffer ); + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Verify bad parameters fail. */ + status = IotMqtt_SerializeSubscribe( NULL, + subscriptionCount, + remainingLength, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_SerializeSubscribe( &subscriptionList, + subscriptionCount, + remainingLength, + NULL, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_SerializeSubscribe( &subscriptionList, + subscriptionCount, + remainingLength, + &packetIdentifier, + NULL, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Get correct values of packet size and remaining length. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = IOT_MQTT_QOS_0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( IotMqttSubscription_t ); + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_SUBSCRIBE, + &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Make sure subsciption count of zero fails. */ + status = IotMqtt_SerializeSubscribe( &subscriptionList, + 0, + remainingLength, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Make sure success is returned for good case. */ + status = IotMqtt_SerializeSubscribe( &subscriptionList, + subscriptionCount, + remainingLength, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* For this example, IotMqtt_GetSubscriptionPacketSize() will return + * packetSize = remainingLength +2 (two byte fixed header). + * Make sure IotMqtt_SerializeSubscribe() + * fails when remaining length is more than packet size. */ + status = IotMqtt_SerializeSubscribe( &subscriptionList, + subscriptionCount, + remainingLength + 4, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_SerializeUnsubscribe works as intended. + * to @ref mqtt_function_serializeunsubscribe. + */ +TEST( MQTT_Unit_API, SerializeUnsubscribeChecks ) +{ + IotMqttSubscription_t subscriptionList; + size_t subscriptionCount = 0; + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t buffer[ 20 ]; + size_t packetSize = sizeof( buffer ); + IotMqttError_t status = IOT_MQTT_SUCCESS; + + status = IotMqtt_SerializeUnsubscribe( NULL, + subscriptionCount, + remainingLength, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + remainingLength, + NULL, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + remainingLength, + &packetIdentifier, + NULL, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Get correct values of packetsize and remaining length. */ + memset( ( void * ) &subscriptionList, 0x0, sizeof( subscriptionList ) ); + subscriptionList.qos = IOT_MQTT_QOS_0; + subscriptionList.pTopicFilter = "/example/topic"; + subscriptionList.topicFilterLength = sizeof( "/example/topic" ); + subscriptionCount = sizeof( subscriptionList ) / sizeof( IotMqttSubscription_t ); + status = IotMqtt_GetSubscriptionPacketSize( IOT_MQTT_UNSUBSCRIBE, + &subscriptionList, + subscriptionCount, + &remainingLength, + &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* Make sure subsciption count of zero fails. */ + status = IotMqtt_SerializeUnsubscribe( &subscriptionList, + 0, + remainingLength, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Make sure success it returned for good case. */ + status = IotMqtt_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + remainingLength, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* For this example, IotMqtt_GetSubscriptionPacketSize() will return + * packetSize = remainingLength +2, make sure IotMqtt_SerializeUnsubscribe() + * fails when remaining length is more than packet size. */ + status = IotMqtt_SerializeUnsubscribe( &subscriptionList, + subscriptionCount, + remainingLength + 4, + &packetIdentifier, + buffer, + packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_GetPublishPacketSize works as intended. + * to @ref mqtt_function_getpublishpacketsize. + */ +TEST( MQTT_Unit_API, GetPublishPacketSizeChecks ) +{ + IotMqttPublishInfo_t publishInfo; + size_t remainingLength = 0; + size_t packetSize; + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Verify bad paramameters fail. */ + status = IotMqtt_GetPublishPacketSize( NULL, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetPublishPacketSize( &publishInfo, NULL, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, NULL ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Empty topic must fail. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Good case succeeds. */ + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &packetSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_GetPublishPacketSize works as intended. + * to @ref mqtt_function_serializepublish. + */ +TEST( MQTT_Unit_API, SerializePublishChecks ) +{ + IotMqttPublishInfo_t publishInfo; + size_t remainingLength = 98; + uint16_t packetIdentifier; + uint8_t * pPakcetIndentifierHigh; + uint8_t buffer[ 100 ]; + size_t bufferSize; + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Verify bad parameters fail. */ + memset( ( void * ) &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + NULL, + &pPakcetIndentifierHigh, + buffer, + 100 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_SerializePublish( NULL, + remainingLength, + &packetIdentifier, + &pPakcetIndentifierHigh, + buffer, + 100 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + &packetIdentifier, + &pPakcetIndentifierHigh, + NULL, + 100 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* NULL topic fails. */ + publishInfo.pTopicName = NULL; + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + &packetIdentifier, + &pPakcetIndentifierHigh, + buffer, + 100 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Good case succeeds */ + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = sizeof( "/test/topic" ); + /* Calculate exact packet size and remaining length. */ + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &bufferSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + &packetIdentifier, + &pPakcetIndentifierHigh, + buffer, + bufferSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_SerializeDisconnect works as intended. + * to @ref mqtt_function_serializedisconnect. + */ +TEST( MQTT_Unit_API, SerializeDisconnectChecks ) +{ + uint8_t buffer[ 10 ]; + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Buffer size less than disconnect request fails. */ + status = IotMqtt_SerializeDisconnect( buffer, 1 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* NULL buffer fails. */ + status = IotMqtt_SerializeDisconnect( NULL, 10 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Good case succeeds. */ + status = IotMqtt_SerializeDisconnect( buffer, 2 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_SerializePingReq works as intended. + * to @ref mqtt_function_serializepingreq. + */ +TEST( MQTT_Unit_API, SerializePingReqChecks ) +{ + uint8_t buffer[ 10 ]; + IotMqttError_t status = IOT_MQTT_SUCCESS; + + /* Buffer size less than disconnect request fails. */ + status = IotMqtt_SerializePingreq( buffer, 1 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* NULL buffer fails. */ + status = IotMqtt_SerializePingreq( NULL, 10 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Good case succeeds. */ + status = IotMqtt_SerializePingreq( buffer, 2 ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_GetIncomingMQTTPacketTypeAndLength works as intended. + * to @ref mqtt_function_getincomingmqttpackettypeandlength. + */ +TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) +{ + IotMqttError_t status = IOT_MQTT_SUCCESS; + IotMqttPacketInfo_t mqttPacket; + uint8_t buffer[ 10 ]; + uint8_t * bufPtr = buffer; + + /* Dummy network inteface - pointer to pointer to buffer. */ + void * pNetworkInterface = ( void * ) &bufPtr; + + buffer[ 0 ] = 0x20; /* CONN ACK */ + buffer[ 1 ] = 0x02; /* Remaining length. */ + + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacket, _getNextByte, pNetworkInterface ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + TEST_ASSERT_EQUAL_INT( 0x20, mqttPacket.type ); + TEST_ASSERT_EQUAL_INT( 0x02, mqttPacket.remainingLength ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_DeserializeResponse works as intended. + * to @ref mqtt_function_deserializeresponse. + */ +TEST( MQTT_Unit_API, DeserializeResponseChecks ) +{ + IotMqttPacketInfo_t mqttPacketInfo; + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t buffer[ 10 ]; + + /* Verify parameters */ + status = IotMqtt_DeserializeResponse( NULL ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + + mqttPacketInfo.type = 0x01; + mqttPacketInfo.pRemainingData = buffer; + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + /* Good case succeeds - Test for CONN ACK */ + /* Set conn ack variable portion */ + buffer[ 0 ] = 0x00; + buffer[ 1 ] = 0x00; + /* Set type, remaining length and remaining data. */ + mqttPacketInfo.type = 0x20; /* CONN ACK */ + mqttPacketInfo.pRemainingData = buffer; + mqttPacketInfo.remainingLength = 0x02; /* CONN ACK Remaining Length. */ + status = IotMqtt_DeserializeResponse( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + +/** + * @brief Tests that IotMqtt_DeserializePublish works as intended. + * to @ref mqtt_function_deserializepublish. + */ +TEST( MQTT_Unit_API, DeserializePublishChecks ) +{ + IotMqttPacketInfo_t mqttPacketInfo; + IotMqttPublishInfo_t publishInfo; + IotMqttError_t status = IOT_MQTT_SUCCESS; + uint8_t buffer[ 100 ]; + size_t bufferSize = sizeof(buffer); + size_t remainingLength = 0; + uint16_t packetIdentifier; + uint8_t * pPakcetIndentifierHigh; + uint8_t * pNetworkInterface; + + /* Verify parameters. */ + status = IotMqtt_DeserializePublish( NULL ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + memset( ( void * ) &mqttPacketInfo, 0x00, sizeof( mqttPacketInfo ) ); + + /* Bad Packet Type. */ + mqttPacketInfo.type = 0x01; + mqttPacketInfo.pRemainingData = buffer; + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_BAD_PARAMETER, status ); + + + /* Good case succeeds - Test for Publish. */ + /* 1. Find out lenght of the packet .*/ + memset( &publishInfo, 0x00, sizeof( publishInfo ) ); + publishInfo.pTopicName = "/test/topic"; + publishInfo.topicNameLength = strlen( "/test/topic" ); + publishInfo.pPayload = "Hello World"; + publishInfo.payloadLength = strlen( "Hello World" ); + publishInfo.qos = IOT_MQTT_QOS_0; + /* Calculate exact packet size and remaining length */ + status = IotMqtt_GetPublishPacketSize( &publishInfo, &remainingLength, &bufferSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* 2. Serialize packet in the buffer. */ + status = IotMqtt_SerializePublish( &publishInfo, + remainingLength, + &packetIdentifier, + &pPakcetIndentifierHigh, + buffer, + bufferSize ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + + /* 3. Deserialize - get type and length. */ + pNetworkInterface = buffer; + status = IotMqtt_GetIncomingMQTTPacketTypeAndLength( &mqttPacketInfo, _getNextByte, ( void * ) &pNetworkInterface ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); + /* Remaining data points to byte 3. */ + mqttPacketInfo.pRemainingData = &buffer[ 2 ]; + /* 4. Deserialize publish. */ + status = IotMqtt_DeserializePublish( &mqttPacketInfo ); + TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); +} + +/*-----------------------------------------------------------*/ + From a74d2822d83e743aac3f4f82a05c8df6e0cdb1ae Mon Sep 17 00:00:00 2001 From: Ravishankar Bhagavandas Date: Mon, 18 Nov 2019 20:25:06 -0800 Subject: [PATCH 325/844] Use previous subscriptions list for unsolicited messages (#644) * Use previous subscriptions list for unsolicited messages * Add doc on usage --- libraries/standard/mqtt/include/iot_mqtt.h | 23 ++++++++++++++++++- .../mqtt/include/types/iot_mqtt_types.h | 15 +++++++----- .../standard/mqtt/src/iot_mqtt_validate.c | 19 +++++---------- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index 316878b6ee..c842b95d15 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -148,6 +148,12 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * Any restored subscriptions MUST have been present in the persistent session; * this function does not send an MQTT SUBSCRIBE packet! * + * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions) + * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) can + * also be used to pass a list of subscriptions to be stored locally without a SUBSCRIBE packet being + * sent to broker. These subscriptions are useful to invoke application level callbacks for messages received + * on unsolicited topics from broker. + * * This MQTT library is network agnostic, meaning it has no knowledge of the * underlying network protocol carrying the MQTT packets. It interacts with the * network through a network abstraction layer, allowing it to be used with many @@ -197,13 +203,19 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * * Example * @code{c} -* + * + * // Callback function to receive messages from broker on an unsolicited topic. + * void unsolicitedMessageCallback( void * pArgument, IotMqttCallbackParam_t * pPublish ); + * * // Parameters to MQTT connect. * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER; * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER; * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER; * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER; * + * // A local subscription to receive messages from broker on an unsolicited topic. + * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER; + * * // Example network abstraction types. * IotNetworkServerInfo_t serverInfo = { ... }; * IotNetworkCredentials_t credentialInfo = { ... }; @@ -231,6 +243,15 @@ void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection, * // Set the pointer to the will info. * connectInfo.pWillInfo = &willInfo; * + * // [Optional] Set a local subscription to receive broker messages on an unsolicited topic. + * subscription.qos = IOT_MQTT_QOS_0; + * subscription.pTopicFilter = "some/unsolicited/topic"; + * subscription.topicLength = ( uint16_t ) strlen( subscription.pTopicFilter ); + * subscription.callback.function = unsolicitedMessageCallback; + * connectInfo.pPreviousSubscriptions = &subscription; + * connectInfo.previousSubscriptionCount = 1; + * + * * // Call CONNECT with a 5 second block time. Should return * // IOT_MQTT_SUCCESS when successful. * IotMqttError_t result = IotMqtt_Connect( &networkInfo, diff --git a/libraries/standard/mqtt/include/types/iot_mqtt_types.h b/libraries/standard/mqtt/include/types/iot_mqtt_types.h index ff24844cc8..2ef88f2e1d 100644 --- a/libraries/standard/mqtt/include/types/iot_mqtt_types.h +++ b/libraries/standard/mqtt/include/types/iot_mqtt_types.h @@ -627,9 +627,13 @@ typedef struct IotMqttConnectInfo * Pointer to the start of an array of subscriptions present a previous session, * if any. These subscriptions will be immediately restored upon reconnecting. * - * This member is ignored if it is `NULL` or #IotMqttConnectInfo_t.cleanSession - * is `true`. If this member is not `NULL`, #IotMqttConnectInfo_t.previousSubscriptionCount - * must be nonzero. + * [Optional] The field can also be used to pass a list of subscriptions to be + * stored locally without a SUBSCRIBE packet being sent to broker. These subscriptions + * are useful to invoke application level callbacks for messages received on unsolicited + * topics from broker. + * + * This member is ignored if it is `NULL`. If this member is not `NULL`, + * #IotMqttConnectInfo_t.previousSubscriptionCount must be nonzero. */ const IotMqttSubscription_t * pPreviousSubscriptions; @@ -640,9 +644,8 @@ typedef struct IotMqttConnectInfo * #IotMqttConnectInfo_t.pPreviousSubscriptions. * * This value is ignored if #IotMqttConnectInfo_t.pPreviousSubscriptions - * is `NULL` or #IotMqttConnectInfo_t.cleanSession is `true`. If - * #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, this value - * must be nonzero. + * is `NULL`. If #IotMqttConnectInfo_t.pPreviousSubscriptions is not `NULL`, + * this value must be nonzero. */ size_t previousSubscriptionCount; diff --git a/libraries/standard/mqtt/src/iot_mqtt_validate.c b/libraries/standard/mqtt/src/iot_mqtt_validate.c index 5537534665..993e65a3b6 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_validate.c +++ b/libraries/standard/mqtt/src/iot_mqtt_validate.c @@ -105,21 +105,14 @@ bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo ) } /* Check that the number of persistent session subscriptions is valid. */ - if( pConnectInfo->cleanSession == false ) + if( pConnectInfo->pPreviousSubscriptions != NULL ) { - if( pConnectInfo->pPreviousSubscriptions != NULL ) + if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, + pConnectInfo->awsIotMqttMode, + pConnectInfo->pPreviousSubscriptions, + pConnectInfo->previousSubscriptionCount ) == false ) { - if( _IotMqtt_ValidateSubscriptionList( IOT_MQTT_SUBSCRIBE, - pConnectInfo->awsIotMqttMode, - pConnectInfo->pPreviousSubscriptions, - pConnectInfo->previousSubscriptionCount ) == false ) - { - IOT_SET_AND_GOTO_CLEANUP( false ); - } - else - { - EMPTY_ELSE_MARKER; - } + IOT_SET_AND_GOTO_CLEANUP( false ); } else { From 346ba0d10ccc2eff6024ca0bec582aec3407357d Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Tue, 19 Nov 2019 17:56:42 -0500 Subject: [PATCH 326/844] Spell check fixes for Shadow lib (#645) --- libraries/aws/common/include/aws_iot_doc_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/aws/common/include/aws_iot_doc_parser.h b/libraries/aws/common/include/aws_iot_doc_parser.h index bc07bef739..193bdeb6ef 100644 --- a/libraries/aws/common/include/aws_iot_doc_parser.h +++ b/libraries/aws/common/include/aws_iot_doc_parser.h @@ -45,7 +45,7 @@ * @warning The parsing will not check for the correctness of the JSON document. * It is designed to be light weight and to be of low memory footprint rather * than checking for the correctness of the JSON document. Hence this is not - * meant to be used for a general purpose JSON parsing. This is recommeded to + * meant to be used for a general purpose JSON parsing. This is recommended to * be used only with mutually authenticated AWS IoT services such as Shadow and * Jobs where the document will always be a well formatted JSON. * From ef899ab837fd88a2d08125b03b9df9c445195d61 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Tue, 19 Nov 2019 19:48:14 -0500 Subject: [PATCH 327/844] Spell check fixes for Defender and Jobs (#646) --- demos/src/aws_iot_demo_defender.c | 2 +- libraries/aws/defender/src/aws_iot_defender_api.c | 8 ++++---- .../defender/test/system/aws_iot_tests_defender_system.c | 6 +++--- libraries/aws/jobs/include/aws_iot_jobs.h | 2 +- .../aws/jobs/test/system/aws_iot_tests_jobs_system.c | 2 +- ports/posix/src/iot_threads_posix.c | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/demos/src/aws_iot_demo_defender.c b/demos/src/aws_iot_demo_defender.c index a7428369cd..1c373da140 100644 --- a/demos/src/aws_iot_demo_defender.c +++ b/demos/src/aws_iot_demo_defender.c @@ -63,7 +63,7 @@ * @brief Callback used to get notification of defender's events. * * @param[in] pCallbackContext context pointer passed by the application - * when callback is regiested in AwsIotDefender_Start() + * when callback is registered in AwsIotDefender_Start() * * @param[im] pointer to AwsIotDefenderCallbackInfo_t containing status of * publish diff --git a/libraries/aws/defender/src/aws_iot_defender_api.c b/libraries/aws/defender/src/aws_iot_defender_api.c index 3f399e64ac..07cb6fe4f3 100644 --- a/libraries/aws/defender/src/aws_iot_defender_api.c +++ b/libraries/aws/defender/src/aws_iot_defender_api.c @@ -161,7 +161,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn /* Assert system task pool is pre-created! */ AwsIotDefender_Assert( IOT_SYSTEM_TASKPOOL != NULL ); - /* Silence warnigns when asserts are disabled. */ + /* Silence warnings when asserts are disabled. */ ( void ) taskPoolError; /* Initialize flow control states to false. */ @@ -224,7 +224,7 @@ AwsIotDefenderError_t AwsIotDefender_Start( AwsIotDefenderStartInfo_t * pStartIn { /* Create metrics publish job, which will periodically publish metrics */ taskPoolError = IotTaskPool_CreateJob( _metricsPublishRoutine, NULL, &_metricsPublishJobStorage, &_metricsPublishJob ); - /* Silence warnigns when asserts are disabled. */ + /* Silence warnings when asserts are disabled. */ ( void ) taskPoolError; AwsIotDefender_Assert( taskPoolError == IOT_TASKPOOL_SUCCESS ); /* Schedule Publish Job */ @@ -398,7 +398,7 @@ const char * AwsIotDefender_EventType( AwsIotDefenderEventType_t eventType ) { const char * pEvent = NULL; - /* Convert defent event to string */ + /* Convert defender event to string */ switch( eventType ) { case AWS_IOT_DEFENDER_METRICS_ACCEPTED: @@ -450,7 +450,7 @@ static void _metricsPublishRoutine( IotTaskPool_t pTaskPool, IotTaskPoolJob_t pJob, void * pUserContext ) { - /* Unsed parameter; silence the compiler. */ + /* Unused parameter; silence the compiler. */ ( void ) pTaskPool; ( void ) pJob; ( void ) pUserContext; diff --git a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c index fa4818eaad..fff91bc311 100644 --- a/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c +++ b/libraries/aws/defender/test/system/aws_iot_tests_defender_system.c @@ -104,7 +104,7 @@ static void _copyDataCallbackFunction( void * param1, static bool _waitForAnyEvent( uint32_t timeoutSec ); -/* Wait for metrics to be accepted by defender service, for maxinum timeout. */ +/* Wait for metrics to be accepted by defender service, for maximum timeout. */ static void _waitForMetricsAccepted( uint32_t timeoutSec ); /* Verify common section of metrics report. */ @@ -754,10 +754,10 @@ static void _assertRejectDueToThrottle( void ) static void _waitForMetricsAccepted( uint32_t timeoutSec ) { - /* If not event has occured, simply fail the test. */ + /* If not event has occurred, simply fail the test. */ if( !_waitForAnyEvent( timeoutSec ) ) { - TEST_FAIL_MESSAGE( "No event has occured within timeout." ); + TEST_FAIL_MESSAGE( "No event has occurred within timeout." ); } if( _callbackInfo.eventType == AWS_IOT_DEFENDER_METRICS_REJECTED ) diff --git a/libraries/aws/jobs/include/aws_iot_jobs.h b/libraries/aws/jobs/include/aws_iot_jobs.h index d1c31d0719..95e26f7026 100644 --- a/libraries/aws/jobs/include/aws_iot_jobs.h +++ b/libraries/aws/jobs/include/aws_iot_jobs.h @@ -614,7 +614,7 @@ AwsIotJobsError_t AwsIotJobs_UpdateSync( const AwsIotJobsRequestInfo_t * pReques * requestInfo.pThingName = THING_NAME; * requestInfo.thingNameLength = THING_NAME_LENGTH; * - * // Set the function used to allocte memory for an incoming response. + * // Set the function used to allocate memory for an incoming response. * requestInfo.mallocResponse = malloc; * * // A Job ID must be set. AWS_IOT_JOBS_NEXT_JOB is not valid for UPDATE. diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c index db198fc467..e97322f69f 100644 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c @@ -758,7 +758,7 @@ TEST( Jobs_System, PersistentSubscriptions ) IotTest_Free( ( void * ) response.pJobsResponse ); - /* Becuase the second operation has persistent subscriptions and does not + /* Because the second operation has persistent subscriptions and does not * need to subscribe to anything, it should be significantly faster. */ TEST_ASSERT_LESS_THAN( elapsedTime1, elapsedTime2 ); diff --git a/ports/posix/src/iot_threads_posix.c b/ports/posix/src/iot_threads_posix.c index 90e38c6ca7..95d331e83b 100644 --- a/ports/posix/src/iot_threads_posix.c +++ b/ports/posix/src/iot_threads_posix.c @@ -156,7 +156,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, { IOT_FUNCTION_ENTRY( bool, true ); int posixErrno = 0; - bool threadAttibutesCreated = false; + bool threadAttributesCreated = false; _threadInfo_t * pThreadInfo = NULL; pthread_t newThread; pthread_attr_t threadAttributes; @@ -186,7 +186,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IOT_SET_AND_GOTO_CLEANUP( false ); } - threadAttibutesCreated = true; + threadAttributesCreated = true; /* Set the new thread to detached. */ posixErrno = pthread_attr_setdetachstate( &threadAttributes, @@ -220,7 +220,7 @@ bool Iot_CreateDetachedThread( IotThreadRoutine_t threadRoutine, IOT_FUNCTION_CLEANUP_BEGIN(); /* Destroy thread attributes object. */ - if( threadAttibutesCreated == true ) + if( threadAttributesCreated == true ) { posixErrno = pthread_attr_destroy( &threadAttributes ); From a5f0872e8d8b56d113b7566692da8ac9f7be637b Mon Sep 17 00:00:00 2001 From: abhidixi11 <44424462+abhidixi11@users.noreply.github.com> Date: Tue, 19 Nov 2019 17:43:18 -0800 Subject: [PATCH 328/844] Fix Spellings and Typos (#647) --- libraries/platform/iot_threads.h | 2 +- .../standard/common/include/iot_taskpool.h | 6 +- libraries/standard/common/src/iot_init.c | 2 +- libraries/standard/common/src/iot_taskpool.c | 14 ++-- .../common/test/unit/iot_tests_taskpool.c | 76 +++++++++---------- libraries/standard/mqtt/include/iot_mqtt.h | 2 +- .../mqtt/include/iot_mqtt_serialize.h | 12 +-- .../standard/mqtt/src/iot_mqtt_serialize.c | 6 +- .../mqtt/src/private/iot_mqtt_internal.h | 2 +- 9 files changed, 61 insertions(+), 61 deletions(-) diff --git a/libraries/platform/iot_threads.h b/libraries/platform/iot_threads.h index 057fde0cbe..15152b9a1d 100644 --- a/libraries/platform/iot_threads.h +++ b/libraries/platform/iot_threads.h @@ -213,7 +213,7 @@ void IotMutex_Unlock( IotMutex_t * pMutex ); /** * @brief Create a new counting semaphore. * - * This function creates a new counting semaphore with a given intial and + * This function creates a new counting semaphore with a given initial and * maximum value. It must be called on an uninitialized #IotSemaphore_t. * This function must not be called on an already-initialized #IotSemaphore_t. * diff --git a/libraries/standard/common/include/iot_taskpool.h b/libraries/standard/common/include/iot_taskpool.h index 2defbd37fd..c4980d26a6 100644 --- a/libraries/standard/common/include/iot_taskpool.h +++ b/libraries/standard/common/include/iot_taskpool.h @@ -157,8 +157,8 @@ IotTaskPoolError_t IotTaskPool_Create( const IotTaskPoolInfo_t * pInfo, * * This function should be called to destroy one instance of a task pool previously created with a call * to @ref IotTaskPool_Create or @ref IotTaskPool_CreateSystemTaskPool. - * Calling this fuction release all underlying resources. After calling this function, any job scheduled but not yet executed - * will be cancelled and destroyed. + * Calling this function release all underlying resources. After calling this function, any job scheduled but not yet executed + * will be canceled and destroyed. * The `taskPool` instance will no longer be valid after this function returns. * * @param[in] taskPool A handle to the task pool, e.g. as returned by a call to @ref IotTaskPool_Create or @@ -509,7 +509,7 @@ IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t /** * @brief Returns a string that describes an @ref IotTaskPoolError_t. * - * Like the POSIX's `strerror`, this function returns a string describing a + * Like the POSIX `strerror`, this function returns a string describing a * return code. In this case, the return code is a task pool library error code, * `status`. * diff --git a/libraries/standard/common/src/iot_init.c b/libraries/standard/common/src/iot_init.c index 4b58afac93..9226bacad3 100644 --- a/libraries/standard/common/src/iot_init.c +++ b/libraries/standard/common/src/iot_init.c @@ -22,7 +22,7 @@ /** * @file iot_init.c - * @brief Implements functions for common intialization and cleanup of this SDK. + * @brief Implements functions for common initialization and cleanup of this SDK. */ /* The config header is always included first. */ diff --git a/libraries/standard/common/src/iot_taskpool.c b/libraries/standard/common/src/iot_taskpool.c index b37042c839..1dcc22ffed 100644 --- a/libraries/standard/common/src/iot_taskpool.c +++ b/libraries/standard/common/src/iot_taskpool.c @@ -1208,7 +1208,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Check if this thread needs to exit because the worker woke up after a timeout. */ else if( jobAvailable == false ) { - /* If there was a timeout, shrink back the task pool to the minimum nunber of threads. */ + /* If there was a timeout, shrink back the task pool to the minimum number of threads. */ if( pTaskPool->activeThreads > pTaskPool->minThreads ) { /* After waking up from a timeout, the thread will try and pick up a new job, but . */ @@ -1355,7 +1355,7 @@ static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache ) } else { - /* Log alocation failure for troubleshooting purposes. */ + /* Log allocation failure for troubleshooting purposes. */ IotLogInfo( "Failed to allocate job." ); } } @@ -1435,7 +1435,7 @@ static void _signalShutdown( _taskPool_t * const pTaskPool, /* Set the exit condition. */ pTaskPool->maxThreads = 0; - /* Broadcast to all active threads to wake-up. Active threads do check the exit condition right after wakein up. */ + /* Broadcast to all active threads to wake-up. Active threads do check the exit condition right after waking up. */ for( count = 0; count < threads; ++count ) { IotSemaphore_Post( &pTaskPool->dispatchSignal ); @@ -1460,7 +1460,7 @@ static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, pTaskPool->activeJobs++; /* If all threads are busy, try and create a new one. Failing to create a new thread - * only has performance implications on correctly exeuting th scheduled job. + * only has performance implications on correctly executing th scheduled job. */ uint32_t activeThreads = pTaskPool->activeThreads; @@ -1535,7 +1535,7 @@ static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool, else { /* Scheduling can only fail to allocate a new worker, which is an error - * only for high prority tasks. */ + * only for high priority tasks. */ IotTaskPool_Assert( mustGrow == true ); /* Revert updating the number of active jobs. */ @@ -1633,7 +1633,7 @@ static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, { bool shouldReschedule = false; - /* If the job being cancelled was at the head of the timeouts queue, then we need to reschedule the timer + /* If the job being canceled was at the head of the timeouts queue, then we need to reschedule the timer * with the next job timeout */ IotLink_t * pHeadLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); @@ -1659,7 +1659,7 @@ static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool, } else { - /* A cancelable job status should be either 'scheduled' or 'deferrred'. */ + /* A cancelable job status should be either 'scheduled' or 'deferred'. */ IotTaskPool_Assert( ( currentStatus == IOT_TASKPOOL_STATUS_READY ) || ( currentStatus == IOT_TASKPOOL_STATUS_CANCELED ) ); } } diff --git a/libraries/standard/common/test/unit/iot_tests_taskpool.c b/libraries/standard/common/test/unit/iot_tests_taskpool.c index 5e18b47c9a..865cb31247 100644 --- a/libraries/standard/common/test/unit/iot_tests_taskpool.c +++ b/libraries/standard/common/test/unit/iot_tests_taskpool.c @@ -58,7 +58,7 @@ typedef struct JobUserContext { IotMutex_t lock; /**< @brief Protection from concurrent updates. */ - uint32_t counter; /**< @brief A counter to keep track of callback invokations. */ + uint32_t counter; /**< @brief A counter to keep track of callback invocations. */ } JobUserContext_t; /** @@ -154,15 +154,15 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) /** * @brief Define the number of running jobs to grow the taskpool for. */ -#ifndef TEST_TAKPOOL_NUMBER_OF_JOBS - #define TEST_TAKPOOL_NUMBER_OF_JOBS 4 +#ifndef TEST_TASKPOOL_NUMBER_OF_JOBS + #define TEST_TASKPOOL_NUMBER_OF_JOBS 4 #endif /** * @brief Define the number of threads to grow the taskpool to. */ #ifndef TEST_TASKPOOL_NUMBER_OF_THREADS - #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TAKPOOL_NUMBER_OF_JOBS - 1 ) + #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TASKPOOL_NUMBER_OF_JOBS - 1 ) #endif /** @@ -182,15 +182,15 @@ TEST_GROUP_RUNNER( Common_Unit_Task_Pool ) /** * @brief Define the number of running jobs to grow the taskpool for. */ -#ifndef TEST_TAKPOOL_NUMBER_OF_JOBS - #define TEST_TAKPOOL_NUMBER_OF_JOBS 4 +#ifndef TEST_TASKPOOL_NUMBER_OF_JOBS + #define TEST_TASKPOOL_NUMBER_OF_JOBS 4 #endif /** * @brief Define the number of threads to grow the taskpool to. */ #ifndef TEST_TASKPOOL_NUMBER_OF_THREADS - #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TAKPOOL_NUMBER_OF_JOBS - 1 ) + #define TEST_TASKPOOL_NUMBER_OF_THREADS ( TEST_TASKPOOL_NUMBER_OF_JOBS - 1 ) #endif /** @@ -734,7 +734,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); @@ -757,7 +757,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_LongRunningAndCachedJobsAndDestroy ) for( count = 0; count < TEST_TASKPOOL_LONG_JOBS_NUMBER; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpDeferredJobsStorage[ count ], &tpDeferredJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpDeferredJobs[ count ], ONE_HOUR_FROM_NOW_MS ); @@ -824,13 +824,13 @@ TEST( Common_Unit_Task_Pool, TaskPool_ScheduleRecyclableTasksError ) TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) { IotTaskPool_t taskPool = IOT_TASKPOOL_INITIALIZER; - const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TAKPOOL_NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; + const IotTaskPoolInfo_t tpInfo = { .minThreads = 2, .maxThreads = TEST_TASKPOOL_NUMBER_OF_JOBS, .stackSize = IOT_THREAD_DEFAULT_STACK_SIZE, .priority = IOT_THREAD_DEFAULT_PRIORITY }; JobBlockingUserContext_t userContext; /* Initialize user context. */ - TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); - TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -839,18 +839,18 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) /* Statically allocated job, schedule one, then wait. */ { uint32_t count; - IotTaskPoolJobStorage_t jobsStorage[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; - IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; + IotTaskPoolJobStorage_t jobsStorage[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; + IotTaskPoolJob_t jobs[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { TEST_ASSERT( IotTaskPool_Schedule( taskPool, jobs[ count ], 0 ) == IOT_TASKPOOL_SUCCESS ); } @@ -864,14 +864,14 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) ++count; - if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) + if( count == TEST_TASKPOOL_NUMBER_OF_JOBS ) { break; } } /* Signal all taskpool threads to exit. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { IotSemaphore_Post( &userContext.block ); } @@ -900,8 +900,8 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) JobBlockingUserContext_t userContext; /* Initialize user context. */ - TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); - TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TAKPOOL_NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.signal, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); + TEST_ASSERT( IotSemaphore_Create( &userContext.block, 0, TEST_TASKPOOL_NUMBER_OF_JOBS ) ); TEST_ASSERT( IotTaskPool_Create( &tpInfo, &taskPool ) == IOT_TASKPOOL_SUCCESS ); @@ -910,13 +910,13 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) /* Statically allocated job, schedule one, then wait. */ { uint32_t count; - IotTaskPoolJobStorage_t jobsStorage[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; - IotTaskPoolJob_t jobs[ TEST_TAKPOOL_NUMBER_OF_JOBS ]; + IotTaskPoolJobStorage_t jobsStorage[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; + IotTaskPoolJob_t jobs[ TEST_TASKPOOL_NUMBER_OF_JOBS ]; /* Create a number of jobs that is equal to the max number of threads in the taskpool. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } @@ -939,14 +939,14 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) ++count; - if( count == TEST_TAKPOOL_NUMBER_OF_JOBS ) + if( count == TEST_TASKPOOL_NUMBER_OF_JOBS ) { break; } } /* Signal all taskpool threads to exit. */ - for( count = 0; count < TEST_TAKPOOL_NUMBER_OF_JOBS; ++count ) + for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { IotSemaphore_Post( &userContext.block ); } @@ -988,7 +988,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneThenWait ) for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, job, 0 ); @@ -1076,7 +1076,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneDeferredThenWait ) for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &jobStorage, &job ) == IOT_TASKPOOL_SUCCESS ); IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, job, 10 + ( rand() % 50 ) ); @@ -1163,7 +1163,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleOneRecyclableThenWait ) for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - /* Shedule the job to be recycle in the callback. */ + /* Schedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &pJob ) == IOT_TASKPOOL_SUCCESS ); IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, pJob, 0 ); @@ -1247,7 +1247,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllThenWait ) for( count = 0; count < TEST_TASKPOOL_ITERATIONS; ++count ) { - /* Shedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ + /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); @@ -1329,7 +1329,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllRecyclableThenWait ) for( count = 0; count < maxJobs; ++count ) { - /* Shedule the job to be recycle in the callback. */ + /* Schedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); IotTaskPoolError_t errorSchedule = IotTaskPool_Schedule( taskPool, tpJobs[ count ], 0 ); @@ -1412,7 +1412,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ScheduleAllDeferredRecyclableThenWait for( count = 0; count < maxJobs; ++count ) { - /* Shedule the job to be recycle in the callback. */ + /* Schedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateRecyclableJob( taskPool, &ExecutionWithRecycleCb, &userContext, &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); IotTaskPoolError_t errorSchedule = IotTaskPool_ScheduleDeferred( taskPool, tpJobs[ count ], 10 + ( rand() % 500 ) ); @@ -1482,7 +1482,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) /* Statically allocated jobs, schedule all, then wait all. */ { uint32_t count, maxJobs; - uint32_t scheduled = 0, rescheduled = 0, failedReschudule = 0; + uint32_t scheduled = 0, rescheduled = 0, failedReschedule = 0; /* In static memory mode, only the recyclable job limit may be allocated. */ #if IOT_STATIC_MEMORY_ONLY == 1 @@ -1498,7 +1498,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) /* Create all jobs. */ for( count = 0; count < maxJobs; ++count ) { - /* Shedule the job to be recycled in the callback. */ + /* Schedule the job to be recycled in the callback. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionLongWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } @@ -1545,7 +1545,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) case IOT_TASKPOOL_ILLEGAL_OPERATION: /* Job already executed. */ - failedReschudule++; + failedReschedule++; break; case IOT_TASKPOOL_BAD_PARAMETER: @@ -1558,7 +1558,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReSchedule ) } } - TEST_ASSERT_TRUE( ( rescheduled + failedReschudule ) == scheduled ); + TEST_ASSERT_TRUE( ( rescheduled + failedReschedule ) == scheduled ); /* Wait until callback is executed. */ while( true ) @@ -1627,7 +1627,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_ReScheduleDeferred ) { IotTaskPoolError_t errorSchedule; - /* Shedule the job to be recycle in the callback. */ + /* Schedule the job to be recycle in the callback. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionWithoutDestroyCb, &userContext, &tpJobsStorage[ count ], &tpJobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); /* Schedule jobs for a really really long time from now, so we know that they will not execute. */ diff --git a/libraries/standard/mqtt/include/iot_mqtt.h b/libraries/standard/mqtt/include/iot_mqtt.h index c842b95d15..7096bc32c0 100644 --- a/libraries/standard/mqtt/include/iot_mqtt.h +++ b/libraries/standard/mqtt/include/iot_mqtt.h @@ -764,7 +764,7 @@ IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation, /** * @brief Returns a string that describes an #IotMqttError_t. * - * Like the POSIX's `strerror`, this function returns a string describing a + * Like the POSIX `strerror`, this function returns a string describing a * return code. In this case, the return code is an MQTT library error code, * `status`. * diff --git a/libraries/standard/mqtt/include/iot_mqtt_serialize.h b/libraries/standard/mqtt/include/iot_mqtt_serialize.h index 29e7d7a97f..1ca98a616d 100644 --- a/libraries/standard/mqtt/include/iot_mqtt_serialize.h +++ b/libraries/standard/mqtt/include/iot_mqtt_serialize.h @@ -317,7 +317,7 @@ IotMqttError_t IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscr * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // Make sure the packet size is less than static buffer size. * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); - * // Serialize subscribe into staticallu allocated ucSharedBuffer. + * // Serialize subscribe into statically allocated ucSharedBuffer. * xResult = IotMqtt_SerializeUnsubscribe( xMQTTSubscription, * sizeof( xMQTTSubscription ) / sizeof( IotMqttSubscription_t ), * xRemainingLength, @@ -426,7 +426,7 @@ IotMqttError_t IotMqtt_GetPublishPacketSize( IotMqttPublishInfo_t * pPublishInfo * xResult = IotMqtt_GetPublishPacketSize( &xMQTTPublishInfo, &xRemainingLength, &xPacketSize ); * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * // Make sure the packet size is less than static buffer size - * configASSERT( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); + * IotMqtt_Assert( xPacketSize < mqttexampleSHARED_BUFFER_SIZE ); * * xResult = IotMqtt_SerializePublish( &xMQTTPublishInfo, * xRemainingLength, @@ -472,10 +472,10 @@ IotMqttError_t IotMqtt_SerializePublish( IotMqttPublishInfo_t * pPublishInfo, * size_t xSentBytes = 0; * * // Disconnect is fixed length packet, therefore there is no need to calculate the size, - * // just makes sure static buffer can accomodate disconnect request. + * // just makes sure static buffer can accommodate disconnect request. * IotMqtt_Assert( MQTT_PACKET_DISCONNECT_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); * - * // Serialize Disconnect packet into static buffer (dynamicaly allocated buffer can be used as well) + * // Serialize Disconnect packet into static buffer (dynamically allocated buffer can be used as well) * xResult = IotMqtt_SerializeDisconnect( ucSharedBuffer, MQTT_PACKET_DISCONNECT_SIZE ); * IotMqtt_Assert( xResult == IOT_MQTT_SUCCESS ); * @@ -512,7 +512,7 @@ IotMqttError_t IotMqtt_SerializeDisconnect( uint8_t * pBuffer, * size_t xSentBytes = 0; * * // PingReq is fixed length packet, therefore there is no need to calculate the size, - * // just makes sure static buffer can accomodate Ping request. + * // just makes sure static buffer can accommodate Ping request. * IotMqtt_Assert( MQTT_PACKET_PINGREQ_SIZE <= mqttexampleSHARED_BUFFER_SIZE ); * * xResult = IotMqtt_SerializePingreq( ucSharedBuffer, MQTT_PACKET_PINGREQ_SIZE ); @@ -549,7 +549,7 @@ IotMqttError_t IotMqtt_SerializePingreq( uint8_t * pBuffer, * // Example code below shows how to implement getNetxByte function with posix sockets. * // Note: IotMqttGetNextByte_t typedef IotMqttError_t (* IotMqttGetNextByte_t)( void * pNetworkContext, * // uint8_t * pNextByte ); - * // Note: It is assumed that socket is aleady created and connected, + * // Note: It is assumed that socket is already created and connected, * * IotMqttError_t getNextByte( void * pContext, * uint8_t * pNextByte ) diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index b3e97af792..87e935273b 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1099,7 +1099,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, /*-----------------------------------------------------------*/ -size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnecton, +size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnection, IotMqttGetNextByte_t getNextByte ) { uint8_t encodedByte = 0; @@ -1115,7 +1115,7 @@ size_t _IotMqtt_GetRemainingLength_Generic( void * pNetworkConnecton, } else { - if( getNextByte( pNetworkConnecton, &encodedByte ) == IOT_MQTT_SUCCESS ) + if( getNextByte( pNetworkConnection, &encodedByte ) == IOT_MQTT_SUCCESS ) { remainingLength += ( encodedByte & 0x7F ) * multiplier; multiplier *= 128; @@ -2594,7 +2594,7 @@ IotMqttError_t IotMqtt_DeserializeResponse( IotMqttPacketInfo_t * pMqttPacket ) } else { - /* set packetIdentfier only if success is returned */ + /* Set packetIdentifier only if success is returned. */ pMqttPacket->packetIdentifier = mqttPacket.packetIdentifier; } diff --git a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h index fec980ad14..0dc28262cb 100644 --- a/libraries/standard/mqtt/src/private/iot_mqtt_internal.h +++ b/libraries/standard/mqtt/src/private/iot_mqtt_internal.h @@ -564,7 +564,7 @@ size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection, * * @note This function is similar to _IotMqtt_GetRemainingLength() but it uses * user provided getNextByte function to parse the stream instead of using - * _IotMqtt_GetNextByte(). pNetworkConnection is impelementation dependent and + * _IotMqtt_GetNextByte(). pNetworkConnection is implementation dependent and * user provided function makes use of it. * */ From 7a9d63cd6d083bd37ca24926fd46f81d5a97dc45 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Wed, 20 Nov 2019 14:45:01 -0800 Subject: [PATCH 329/844] Fix some spellings in Jobs (#648) --- libraries/aws/jobs/src/private/aws_iot_jobs_internal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h b/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h index 1e68ee9b6e..a5d43ef51a 100644 --- a/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h +++ b/libraries/aws/jobs/src/private/aws_iot_jobs_internal.h @@ -438,7 +438,7 @@ extern IotMutex_t _AwsIotJobsSubscriptionsMutex; * * @param[in] type The type of Jobs operation for the request. * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pRequestContents Additional values to place in tne JSON document, + * @param[in] pRequestContents Additional values to place in the JSON document, * depending on `type`. * @param[in] flags Flags variables passed to a user-facing Jobs function. * @param[in] pCallbackInfo User-provided callback function and parameter. @@ -600,7 +600,7 @@ void _AwsIotJobs_DecrementReferences( _jobsOperation_t * pOperation, * * @param[in] type The type of Jobs operation for the request. * @param[in] pRequestInfo Common Jobs request parameters. - * @param[in] pRequestContents Additional values to place in tne JSON document, + * @param[in] pRequestContents Additional values to place in the JSON document, * depending on `type`. * @param[in] pOperation Operation associated with the Jobs request. * From 00b768aabc97cc48935e94771887dcdc1dd02a85 Mon Sep 17 00:00:00 2001 From: leegeth <51681119+leegeth@users.noreply.github.com> Date: Wed, 20 Nov 2019 19:36:21 -0500 Subject: [PATCH 330/844] Spell check fixes (#650) --- demos/src/aws_iot_demo_defender.c | 4 ++-- .../common/test/unit/aws_iot_tests_doc_parser.c | 2 +- .../aws/defender/include/aws_iot_defender.h | 4 ++-- .../defender/src/aws_iot_defender_collector.c | 2 +- .../jobs/test/system/aws_iot_tests_jobs_system.c | 4 ++-- .../shadow/include/types/aws_iot_shadow_types.h | 2 +- libraries/standard/common/src/iot_taskpool.c | 16 ++++++++-------- .../common/src/private/iot_taskpool_internal.h | 4 ++-- .../common/test/unit/iot_tests_taskpool.c | 4 ++-- libraries/standard/mqtt/src/iot_mqtt_serialize.c | 4 ++-- .../standard/mqtt/test/unit/iot_tests_mqtt_api.c | 6 +++--- .../mqtt/test/unit/iot_tests_mqtt_receive.c | 2 +- .../src/cbor/iot_serializer_tinycbor_decoder.c | 2 +- .../src/cbor/iot_serializer_tinycbor_encoder.c | 4 ++-- 14 files changed, 30 insertions(+), 30 deletions(-) diff --git a/demos/src/aws_iot_demo_defender.c b/demos/src/aws_iot_demo_defender.c index 1c373da140..3bb2fc3506 100644 --- a/demos/src/aws_iot_demo_defender.c +++ b/demos/src/aws_iot_demo_defender.c @@ -53,7 +53,7 @@ #define TIMEOUT_MS ( ( uint32_t ) 5000 ) /** - * @brief Defender metrics publish interval, 5 minutes (300 seconds) is minumum. + * @brief Defender metrics publish interval, 5 minutes (300 seconds) is minimum. */ #define DEFENDER_PUBLISH_INTERVAL ( ( uint32_t ) 300 ) @@ -65,7 +65,7 @@ * @param[in] pCallbackContext context pointer passed by the application * when callback is registered in AwsIotDefender_Start() * - * @param[im] pointer to AwsIotDefenderCallbackInfo_t containing status of + * @param[in] pointer to AwsIotDefenderCallbackInfo_t containing status of * publish */ void _defenderCallback( void * pCallbackContext, diff --git a/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c b/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c index 9398e7a504..c721b33c82 100644 --- a/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c +++ b/libraries/aws/common/test/unit/aws_iot_tests_doc_parser.c @@ -287,7 +287,7 @@ TEST( Aws_Iot_Doc_Unit_Parser, JsonInvalid ) _parseJson( true, pJsonDocument, jsonDocumentLength, "key", "\"value\"", 7 ); } - /* Invalid JSON not validated for correctness. Incorrect paranthesis. */ + /* Invalid JSON not validated for correctness. Incorrect parenthesis. */ { const char pJsonDocument[ 30 ] = "{\"key\": \"value\", \"key2\": {}"; size_t jsonDocumentLength = strlen( pJsonDocument ); diff --git a/libraries/aws/defender/include/aws_iot_defender.h b/libraries/aws/defender/include/aws_iot_defender.h index 6f7ba05712..b56a3ec5ff 100644 --- a/libraries/aws/defender/include/aws_iot_defender.h +++ b/libraries/aws/defender/include/aws_iot_defender.h @@ -251,7 +251,7 @@ AwsIotDefenderError_t AwsIotDefender_SetMetrics( AwsIotDefenderMetricsGroup_t me * * @param[in] pStartInfo Pointer of parameters of start function * - * Periodically, defender agent collects metrics and publish to specifc AWS reserved MQTT topic. + * Periodically, defender agent collects metrics and publish to specific AWS reserved MQTT topic. * * @return * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. @@ -362,7 +362,7 @@ void AwsIotDefender_Stop( void ); * @brief Set period in seconds. * * - * @param[in] periodSeconds Period is specified in seconds. Mininum is 300 (5 minutes) + * @param[in] periodSeconds Period is specified in seconds. Minimum is 300 (5 minutes) * * @return * * On success, #AWS_IOT_DEFENDER_SUCCESS is returned. diff --git a/libraries/aws/defender/src/aws_iot_defender_collector.c b/libraries/aws/defender/src/aws_iot_defender_collector.c index 14c2e48a18..d1ffde8f12 100644 --- a/libraries/aws/defender/src/aws_iot_defender_collector.c +++ b/libraries/aws/defender/src/aws_iot_defender_collector.c @@ -161,7 +161,7 @@ bool AwsIotDefenderInternal_CreateReport( void ) /* Actual serialization. */ _serialize(); - /* Ouput the report to stdout if debugging mode is enabled. */ + /* Output the report to stdout if debugging mode is enabled. */ #if DEBUG_CBOR_PRINT == 1 _printReport(); #endif diff --git a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c index e97322f69f..2fb03f74a6 100644 --- a/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c +++ b/libraries/aws/jobs/test/system/aws_iot_tests_jobs_system.c @@ -156,7 +156,7 @@ static void _operationComplete( void * pArgument, AwsIotJobs_Assert( pOperation->u.operation.pResponse != NULL ); AwsIotJobs_Assert( pOperation->u.operation.responseLength > 0 ); - /* Unblock the main test thead. */ + /* Unblock the main test thread. */ IotSemaphore_Post( &( pParams->waitSem ) ); } @@ -205,7 +205,7 @@ static void _jobsCallback( void * pArgument, pJobId + 1, _pJobIdLengths[ checkJobId ] ) == 0 ); - /* Unblock the main test thead. */ + /* Unblock the main test thread. */ IotSemaphore_Post( pWaitSem ); } diff --git a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h index 7e44da5741..bc7eb92082 100644 --- a/libraries/aws/shadow/include/types/aws_iot_shadow_types.h +++ b/libraries/aws/shadow/include/types/aws_iot_shadow_types.h @@ -163,7 +163,7 @@ typedef enum AwsIotShadowError AWS_IOT_SHADOW_MQTT_ERROR = 5, /** - * @brief Reponse received from Shadow service not understood. + * @brief Response received from Shadow service not understood. * * Functions that may return this value: * - @ref shadow_function_deletesync diff --git a/libraries/standard/common/src/iot_taskpool.c b/libraries/standard/common/src/iot_taskpool.c index 1dcc22ffed..d21faa6da9 100644 --- a/libraries/standard/common/src/iot_taskpool.c +++ b/libraries/standard/common/src/iot_taskpool.c @@ -90,7 +90,7 @@ static void _initJobsCache( _taskPoolCache_t * const pCache ); * @param[in] pJob The job to initialize. * @param[in] userCallback The user callback for the job. * @param[in] pUserContext The context tp be passed to the callback. - * @param[in] isStatic A flag to indicate whether the job is statically or synamically allocated. + * @param[in] isStatic A flag to indicate whether the job is statically or dynamically allocated. */ static void _initializeJob( _taskPoolJob_t * const pJob, IotTaskPoolRoutine_t userCallback, @@ -147,7 +147,7 @@ static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, * Reschedules the timer for handling deferred jobs to the next timeout. * * param[in] pTimer The timer to reschedule. - * param[in] pFirstTimerEvent The timer event that carries the timeout and job inforamtion. + * param[in] pFirstTimerEvent The timer event that carries the timeout and job information. */ static void _rescheduleDeferredJobsTimer( IotTimer_t * const pTimer, _taskPoolTimerEvent_t * const pFirstTimerEvent ); @@ -218,7 +218,7 @@ static void _signalShutdown( _taskPool_t * const pTaskPool, /** * Places a job in the dispatch queue. * - * @param[in] pTaskPool The task pool to scheduel the job with. + * @param[in] pTaskPool The task pool to schedule the job with. * @param[in] pJob The job to schedule. * @param[in] flags The job flags. * @@ -967,7 +967,7 @@ static IotTaskPoolError_t _initTaskPoolControlStructures( const IotTaskPoolInfo_ memset( ( void * ) pTaskPool, 0x00, sizeof( IotTaskPool_t ) ); /* Initialize a job data structures that require no de-initialization. - * All other data structures carry a value of 'NULL' before initailization. + * All other data structures carry a value of 'NULL' before initialization. */ IotDeQueue_Create( &pTaskPool->dispatchQueue ); IotListDouble_Create( &pTaskPool->timerEventsList ); @@ -1154,7 +1154,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Signal that this worker completed initialization and it is ready to receive notifications. */ IotSemaphore_Post( &pTaskPool->startStopSignal ); - /* OUTER LOOP: it controls the lifetiem of the worker thread: exit condition for a worker thread + /* OUTER LOOP: it controls the lifetime of the worker thread: exit condition for a worker thread * is setting maxThreads to zero. A worker thread is running until the maximum number of allowed * threads is not zero and the active threads are less than the maximum number of allowed threads. */ @@ -1194,7 +1194,7 @@ static void _taskPoolWorker( void * pUserContext ) /* Check if this thread needs to exit because 'max threads' quota was exceeded. * In that case, let is run once, so we can support the case for scheduling 'high priority' * jobs that causes exceeding the max threads quota for the purpose of executing - * the high-piority task. */ + * the high-priority task. */ if( pTaskPool->activeThreads > pTaskPool->maxThreads ) { IotLogDebug( "Worker thread will exit because maximum quota was exceeded." ); @@ -1261,7 +1261,7 @@ static void _taskPoolWorker( void * pUserContext ) /* If this thread exceeded the quota, then let it terminate. */ if( running == false ) { - /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ + /* Abandon the INNER LOOP. Execution will transfer back to the OUTER LOOP condition. */ break; } } @@ -1283,7 +1283,7 @@ static void _taskPoolWorker( void * pUserContext ) { TASKPOOL_EXIT_CRITICAL(); - /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */ + /* Abandon the INNER LOOP. Execution will transfer back to the OUTER LOOP condition. */ break; } else diff --git a/libraries/standard/common/src/private/iot_taskpool_internal.h b/libraries/standard/common/src/private/iot_taskpool_internal.h index 509e9c416d..9364da2250 100644 --- a/libraries/standard/common/src/private/iot_taskpool_internal.h +++ b/libraries/standard/common/src/private/iot_taskpool_internal.h @@ -39,12 +39,12 @@ /* Establish a few convenience macros to handle errors in a standard way. */ /** - * @brief Every public API return an enumeration value with an undelying value of 0 in case of success. + * @brief Every public API return an enumeration value with an underlying value of 0 in case of success. */ #define TASKPOOL_SUCCEEDED( x ) ( ( x ) == IOT_TASKPOOL_SUCCESS ) /** - * @brief Every public API returns an enumeration value with an undelying value different than 0 in case of success. + * @brief Every public API returns an enumeration value with an underlying value different than 0 in case of success. */ #define TASKPOOL_FAILED( x ) ( ( x ) != IOT_TASKPOOL_SUCCESS ) diff --git a/libraries/standard/common/test/unit/iot_tests_taskpool.c b/libraries/standard/common/test/unit/iot_tests_taskpool.c index 865cb31247..fa2933018c 100644 --- a/libraries/standard/common/test/unit/iot_tests_taskpool.c +++ b/libraries/standard/common/test/unit/iot_tests_taskpool.c @@ -846,7 +846,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_Grow ) for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ + /* The callback will block indefinitely, stealing a task pool thread. The task pool will need to grow to pass this test. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } @@ -917,7 +917,7 @@ TEST( Common_Unit_Task_Pool, ScheduleTasks_GrowHighPri ) for( count = 0; count < TEST_TASKPOOL_NUMBER_OF_JOBS; ++count ) { /* Schedule the job NOT to be recycle in the callback, since the buffer is statically allocated. */ - /* The callback will block indefintely, stealing a task pool thread. The task pool will need to grow to pass this test. */ + /* The callback will block indefinitely, stealing a task pool thread. The task pool will need to grow to pass this test. */ TEST_ASSERT( IotTaskPool_CreateJob( &ExecutionBlockingWithoutDestroyCb, &userContext, &jobsStorage[ count ], &jobs[ count ] ) == IOT_TASKPOOL_SUCCESS ); } diff --git a/libraries/standard/mqtt/src/iot_mqtt_serialize.c b/libraries/standard/mqtt/src/iot_mqtt_serialize.c index 87e935273b..d517fc6733 100644 --- a/libraries/standard/mqtt/src/iot_mqtt_serialize.c +++ b/libraries/standard/mqtt/src/iot_mqtt_serialize.c @@ -1614,7 +1614,7 @@ IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish ) } /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain - * a packet identifer, but QoS 0 PUBLISH packets do not. */ + * a packet identifier, but QoS 0 PUBLISH packets do not. */ if( pOutput->qos == IOT_MQTT_QOS_0 ) { pOutput->payloadLength = ( pPublish->remainingLength - pOutput->topicNameLength - sizeof( uint16_t ) ); @@ -1802,7 +1802,7 @@ IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback ) const uint8_t * pVariableHeader = pSuback->pRemainingData; /* A SUBACK must have a remaining length of at least 3 to accommodate the - * packet identifer and at least 1 return code. */ + * packet identifier and at least 1 return code. */ if( remainingLength < 3 ) { IotLog( IOT_LOG_DEBUG, diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c index 7d3c61415c..c5769a036a 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_api.c @@ -1747,7 +1747,7 @@ TEST( MQTT_Unit_API, SerializeConnectChecks ) connectInfo.clientIdentifierLength = 4; status = IotMqtt_GetConnectPacketSize( &connectInfo, &remainingLength, &packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); - /* Make sure test suceeds. */ + /* Make sure test succeeds. */ status = IotMqtt_SerializeConnect( &connectInfo, remainingLength, buffer, packetSize ); TEST_ASSERT_EQUAL_INT( IOT_MQTT_SUCCESS, status ); @@ -2164,7 +2164,7 @@ TEST( MQTT_Unit_API, GetIncomingMQTTPacketTypeAndLengthChecks ) uint8_t buffer[ 10 ]; uint8_t * bufPtr = buffer; - /* Dummy network inteface - pointer to pointer to buffer. */ + /* Dummy network interface - pointer to pointer to buffer. */ void * pNetworkInterface = ( void * ) &bufPtr; buffer[ 0 ] = 0x20; /* CONN ACK */ @@ -2243,7 +2243,7 @@ TEST( MQTT_Unit_API, DeserializePublishChecks ) /* Good case succeeds - Test for Publish. */ - /* 1. Find out lenght of the packet .*/ + /* 1. Find out length of the packet .*/ memset( &publishInfo, 0x00, sizeof( publishInfo ) ); publishInfo.pTopicName = "/test/topic"; publishInfo.topicNameLength = strlen( "/test/topic" ); diff --git a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c index 0f2fdf452b..26a8f869ea 100644 --- a/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c +++ b/libraries/standard/mqtt/test/unit/iot_tests_mqtt_receive.c @@ -1081,7 +1081,7 @@ TEST( MQTT_Unit_Receive, PublishValid ) /*-----------------------------------------------------------*/ /** - * @brief Tests the behavior of @ref mqtt_function_receivecallback with a PUBLIS + * @brief Tests the behavior of @ref mqtt_function_receivecallback with a PUBLISH * that doesn't comply to MQTT spec. */ TEST( MQTT_Unit_Receive, PublishInvalid ) diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c index 9b9a91a189..12ba2848b9 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_decoder.c @@ -132,7 +132,7 @@ static void _translateErrorCode( CborError cborError, { /* TODO: assert cborError == 0 || *pSerializerError == 0 */ - /* Only translate if there is no error on serizlier currently. */ + /* Only translate if there is no error on serializer currently. */ if( *pSerializerError == IOT_SERIALIZER_SUCCESS ) { switch( cborError ) diff --git a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c index 7551e6d49a..c804eff014 100644 --- a/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c +++ b/libraries/standard/serializer/src/cbor/iot_serializer_tinycbor_encoder.c @@ -82,7 +82,7 @@ static void _translateErrorCode( CborError cborError, */ IotSerializer_Assert( cborError == 0 || *pSerializerError == 0 ); - /* Only translate if there is no error on serizlier currently. */ + /* Only translate if there is no error on serializer currently. */ if( *pSerializerError == IOT_SERIALIZER_SUCCESS ) { switch( cborError ) @@ -160,7 +160,7 @@ static void _destroy( IotSerializerEncoderObject_t * pEncoderObject ) { CborEncoder * pCborEncoder = ( CborEncoder * ) pEncoderObject->pHandle; - /* Free the memorry allocated in init function. */ + /* Free the memory allocated in init function. */ IotSerializer_FreeCborEncoder( pCborEncoder ); /* Reset pHandle to be NULL. */ From 9d22ed304b25872714c8fc9cc80aaa82435d3fe1 Mon Sep 17 00:00:00 2001 From: Muneeb Ahmed Date: Wed, 20 Nov 2019 21:11:00 -0800 Subject: [PATCH 331/844] Add design page to Jobs docs (#649) * Add design page to Jobs documentation --- demos/include/iot_demo_arguments.h | 2 +- doc/lib/jobs.txt | 13 +++++ doc/plantuml/images/jobs_async_detail.png | Bin 0 -> 155351 bytes doc/plantuml/images/jobs_sync_detail.png | Bin 0 -> 152414 bytes doc/plantuml/jobs_async_detail.pu | 67 +++++++++++++++++++++ doc/plantuml/jobs_sync_detail.pu | 68 ++++++++++++++++++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 doc/plantuml/images/jobs_async_detail.png create mode 100644 doc/plantuml/images/jobs_sync_detail.png create mode 100644 doc/plantuml/jobs_async_detail.pu create mode 100644 doc/plantuml/jobs_sync_detail.pu diff --git a/demos/include/iot_demo_arguments.h b/demos/include/iot_demo_arguments.h index 8f1921f392..9f7cfd6ea3 100644 --- a/demos/include/iot_demo_arguments.h +++ b/demos/include/iot_demo_arguments.h @@ -55,7 +55,7 @@ typedef struct IotDemoArguments /* These credentials are only used if securedConnection is true. */ const char * pRootCaPath; /**< @brief The path to the server root certificate to use for the connection. */ const char * pClientCertPath; /**< @brief The path to the client certificate to use for the connection. */ - const char * pPrivateKeyPath; /**< @brief the path to the private key that matches the client certificate. */ + const char * pPrivateKeyPath; /**< @brief The path to the private key that matches the client certificate. */ const char * pIdentifier; /**< @brief Client identifier or Thing Name to use for demo. */ } IotDemoArguments_t; diff --git a/doc/lib/jobs.txt b/doc/lib/jobs.txt index 71b428690d..7b18b06f51 100644 --- a/doc/lib/jobs.txt +++ b/doc/lib/jobs.txt @@ -45,6 +45,19 @@ Currently, the Jobs library has the following dependencies: In addition to the components above, the Jobs library also depends on C standard library headers. */ +/** +@page jobs_design Design +@brief Architecture behind the Jobs library. + +The Jobs library uses MQTT subscriptions and publishes to communicate with the AWS IoT Jobs Service. Jobs operations such as Get Pending, Start Next, Describe, and Update use MQTT subscriptions to two MQTT topics in order to receive their accepted/rejected status, and publish to the appropriate topic for the operation. The Jobs Notify Next callback uses an MQTT subscription to receive data from the AWS IoT Jobs Service when the information for the next job in the queue changes. The Notify Pending callbacks does the same for the case when any job changes. These callbacks are run in MQTT's taskpool context. + +@section jobs_synchronous_design Synchronous Design +@image html jobs_sync_detail.png width=100% + +@section jobs_asynchronous_design Asynchronous Design +@image html jobs_async_detail.png width=100% +*/ + /** @page jobs_demo Demo @brief Demonstrates the usage of the Jobs library. diff --git a/doc/plantuml/images/jobs_async_detail.png b/doc/plantuml/images/jobs_async_detail.png new file mode 100644 index 0000000000000000000000000000000000000000..29cfa575bc069e5f5434988fd4a4dc0536d67580 GIT binary patch literal 155351 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